diff --git a/assets/badges.svg b/assets/badges.svg
deleted file mode 100644
index 1d5e7ef..0000000
--- a/assets/badges.svg
+++ /dev/null
@@ -1,28 +0,0 @@
-
diff --git a/assets/social-preview.png b/assets/social-preview.png
deleted file mode 100644
index a7cbb50..0000000
Binary files a/assets/social-preview.png and /dev/null differ
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
deleted file mode 100644
index 0f325a9..0000000
--- a/rust/Cargo.lock
+++ /dev/null
@@ -1,43 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 4
-
-[[package]]
-name = "bbb-cli"
-version = "1.0.0"
-dependencies = [
- "bbb-core",
- "bbb-llvm",
- "bbb-syntax",
- "bbb-vm",
-]
-
-[[package]]
-name = "bbb-core"
-version = "1.0.0"
-
-[[package]]
-name = "bbb-llvm"
-version = "1.0.0"
-dependencies = [
- "bbb-syntax",
-]
-
-[[package]]
-name = "bbb-syntax"
-version = "1.0.0"
-
-[[package]]
-name = "bbb-vm"
-version = "1.0.0"
-dependencies = [
- "bbb-core",
- "bbb-syntax",
-]
-
-[[package]]
-name = "bbb-wasm"
-version = "1.0.0"
-dependencies = [
- "bbb-syntax",
-]
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
deleted file mode 100644
index 7155f5d..0000000
--- a/rust/Cargo.toml
+++ /dev/null
@@ -1,13 +0,0 @@
-[workspace]
-resolver = "2"
-members = ["crates/bbb-syntax", "crates/bbb-core", "crates/bbb-vm", "crates/bbb-llvm", "crates/bbb-wasm", "crates/bbb-cli"]
-
-[workspace.package]
-version = "1.0.0"
-edition = "2021"
-license = "MIT"
-
-[profile.release]
-opt-level = 3
-lto = true
-codegen-units = 1
diff --git a/rust/crates/bbb-cli/Cargo.toml b/rust/crates/bbb-cli/Cargo.toml
deleted file mode 100644
index 1f64d18..0000000
--- a/rust/crates/bbb-cli/Cargo.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-[package]
-name = "bbb-cli"
-version.workspace = true
-edition.workspace = true
-license.workspace = true
-description = "BioLang Rust 实现 CLI(Rust+LLVM 重写阶段 M1)"
-
-[[bin]]
-name = "bbb"
-path = "src/main.rs"
-
-[dependencies]
-bbb-syntax = { path = "../bbb-syntax" }
-bbb-core = { path = "../bbb-core" }
-bbb-vm = { path = "../bbb-vm" }
-bbb-llvm = { path = "../bbb-llvm" }
diff --git a/rust/crates/bbb-cli/src/main.rs b/rust/crates/bbb-cli/src/main.rs
deleted file mode 100644
index 1114ad6..0000000
--- a/rust/crates/bbb-cli/src/main.rs
+++ /dev/null
@@ -1,861 +0,0 @@
-//! bbb — BiuBiuBiu 语言 CLI(Rust + LLVM 实现)。
-//!
-//! 标准 CLI(README 定义,与旧 C 实现一致):
-//! bbb 解释运行脚本
-//! bbb shell run 解释运行脚本(显式)
-//! bbb shell build [-o out] 编译脚本 → 原生可执行
-//! bbb init 创建项目骨架(package.toml + src/ + utils/)
-//! bbb build [dir] [-s|-m] [-o out] 构建项目
-//! bbb run [dir] 运行项目(解释)
-//! bbb install [dir] 安装依赖
-//! bbb destroy [dir] 清理构建产物
-//! bbb pack [--entry NAME]
-//! bbb unpack [dir]
-//! bbb --tokens dump tokens(调试)
-//! bbb -e 解释器内存限制(0 = unlimited)
-//! bbb -h | --help 帮助
-
-use std::io::Read;
-use std::path::{Path, PathBuf};
-use std::process::ExitCode;
-
-use bbb_core::arena::{BumpArena, StrArena};
-use bbb_core::value::Value;
-use bbb_syntax::lexer::{self, TokenKind};
-use bbb_syntax::parser::parse_source;
-use bbb_vm::interp::Interp;
-
-const USAGE: &str = "\
-🧬 BiuBiuBiu (bbb) — interpret or compile .bio/.bl programs
-
-Usage:
- bbb run (interpret) a script
- bbb shell run run a script (interpret)
- bbb shell build [-o out] compile a script → standalone executable
- bbb -e set interpreter memory limit (0 = unlimited, default 256M)
- bbb --tokens dump tokens (debug)
- bbb init create a project skeleton (src/ utils/ package.toml)
- bbb build [dir] [-o out] build a project (bundle needs, compile)
- bbb build [dir] -s build → standalone executable (default)
- bbb build [dir] -m [out.img|out.zip] build → .img/.zip package
- bbb run [dir] run a project (bundle needs, interpret)
- bbb install [dir] install deps from package.toml
- bbb destroy [dir] remove build artifacts
- bbb pack [--entry NAME]
- package compiled products (raw .img / .zip)
- bbb unpack [dir] unpack a .img / .zip package
- bbb run built-in demos
- bbb -h | --help show this help
-
- env: BIOLANG_CONFIG → global config file (TOML, may contain repo=)
-";
-
-fn read_source(path: &str) -> Result {
- let mut buf = Vec::new();
- std::fs::File::open(path)
- .map_err(|e| format!("cannot open file: {path} ({e})"))?
- .read_to_end(&mut buf)
- .map_err(|e| format!("read failed: {e}"))?;
- String::from_utf8(buf).map_err(|_| "source is not UTF-8".to_string())
-}
-
-fn parse_or_err(src: &str) -> Result {
- let (prog, errs) = parse_source(src);
- if !errs.is_empty() {
- return Err(errs.iter().map(|e| e.to_string()).collect::>().join("\n"));
- }
- Ok(prog)
-}
-
-/// 运行单个脚本文件(解释)。
-fn run_script_file(path: &str) -> Result<(), String> {
- let src = read_source(path)?;
- let prog = parse_or_err(&src)?;
- let mut interp = Interp::new();
- let unmet = interp.reg.register(&prog);
- let unmet = filter_value_needs(unmet, &prog);
- if !unmet.is_empty() {
- return Err(unmet
- .iter()
- .map(|(k, n)| format!("need {k} {n} has no provider"))
- .collect::>()
- .join("\n"));
- }
- let out = interp.run(&prog);
- print!("{}", out.stdout);
- Ok(())
-}
-
-/// 运行项目目录(解释):合并 src/ + utils/。
-fn run_project_dir(root: &Path) -> Result<(), String> {
- let prog = bbb_vm::load_project_sources(&root.to_path_buf())?;
- let mut interp = Interp::new();
- let unmet = interp.reg.register(&prog);
- let unmet = filter_value_needs(unmet, &prog);
- if !unmet.is_empty() {
- return Err(unmet
- .iter()
- .map(|(k, n)| format!("need {k} {n} has no provider"))
- .collect::>()
- .join("\n"));
- }
- let out = interp.run(&prog);
- print!("{}", out.stdout);
- Ok(())
-}
-
-fn filter_value_needs(
- unmet: Vec<(String, String)>,
- prog: &bbb_syntax::ast::Program,
-) -> Vec<(String, String)> {
- unmet
- .into_iter()
- .filter(|(k, n)| {
- !(k == "value"
- && prog
- .decls
- .iter()
- .any(|d| matches!(d, bbb_syntax::Decl::Const { name, .. } if name == n)))
- })
- .collect()
-}
-
-/// LLVM 编译:AST → IR → clang → 可执行文件(不运行)。
-fn compile_to_executable(prog: &bbb_syntax::ast::Program, out: &str) -> Result<(), String> {
- let ir = bbb_llvm::compile(prog).map_err(|e| format!("IR generation failed: {e}"))?;
- let dir = std::env::temp_dir().join(format!("bbb-build-{}", std::process::id()));
- std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?;
- let ir_path = dir.join("out.ll");
- std::fs::write(&ir_path, &ir).map_err(|e| e.to_string())?;
- // 确保输出目录存在
- if let Some(parent) = Path::new(out).parent() {
- if !parent.as_os_str().is_empty() {
- std::fs::create_dir_all(parent).map_err(|e| format!("cannot create {}: {e}", parent.display()))?;
- }
- }
- let status = std::process::Command::new("clang")
- .arg(&ir_path)
- .arg("-o")
- .arg(out)
- .status()
- .map_err(|e| format!("clang invocation failed: {e}"))?;
- if !status.success() {
- return Err(format!("clang failed (IR kept at {})", ir_path.display()));
- }
- Ok(())
-}
-
-// ───────────────────────── 命令实现 ─────────────────────────
-
-fn cmd_tokens(path: &str) -> Result<(), String> {
- let src = read_source(path)?;
- let mut toks = Vec::new();
- lexer::tokenize(&src, &mut toks).map_err(|e| format!("lex error: {e}"))?;
- for t in &toks {
- let kind = match t.kind {
- TokenKind::Ident => "ident",
- TokenKind::Keyword => "keyword",
- TokenKind::Int => "int",
- TokenKind::Float => "float",
- TokenKind::Str => "string",
- TokenKind::Char => "char",
- TokenKind::Op => "op",
- TokenKind::Eof => "eof",
- };
- println!("{:>5}:{:<3} {:<8} {:?}", t.span.line, t.span.col, kind, t.text);
- }
- Ok(())
-}
-
-fn cmd_shell_run(file: &str) -> Result<(), String> {
- run_script_file(file)
-}
-
-fn cmd_shell_build(file: &str, out: Option<&str>) -> Result<(), String> {
- let src = read_source(file)?;
- let prog = parse_or_err(&src)?;
- // 默认输出:bin/
- let out_path = match out {
- Some(o) => o.to_string(),
- None => {
- let base = Path::new(file)
- .file_name()
- .map(|s| s.to_string_lossy().to_string())
- .unwrap_or_else(|| "a.out".to_string());
- let base = base
- .strip_suffix(".bio")
- .or_else(|| base.strip_suffix(".bl"))
- .unwrap_or(&base)
- .to_string();
- format!("bin/{base}")
- }
- };
- println!("compiling {file} → {out_path}");
- compile_to_executable(&prog, &out_path)?;
- println!("✔ compiled: {out_path}");
- Ok(())
-}
-
-fn cmd_run(target: &str) -> Result<(), String> {
- let path = PathBuf::from(target);
- if path.is_dir() {
- run_project_dir(&path)
- } else {
- run_script_file(target)
- }
-}
-
-fn cmd_init(name: &str) -> Result<(), String> {
- let root = PathBuf::from(name);
- if root.exists() {
- return Err(format!("{name}: already exists"));
- }
- std::fs::create_dir_all(root.join("src")).map_err(|e| e.to_string())?;
- std::fs::create_dir_all(root.join("utils")).map_err(|e| e.to_string())?;
- let toml = format!(
- "# {name} — BiuBiuBiu project manifest\n\
- # standard fields: name / version / repo (optional) + [dependencies]\n\
- name = \"{name}\"\n\
- version = \"0.1.0\"\n\
- \n\
- [dependencies]\n\
- # libfoo = {{ version = \"1.0.0\" }}\n\
- # libbar = {{ version = \"0.2.0\", repo = \"https://...\" }}\n"
- );
- std::fs::write(root.join("package.toml"), toml).map_err(|e| e.to_string())?;
- let main = format!(
- "program main;\n\
- \n\
- Main {{\n\
- void exec() {{\n\
- CIO::println(\"Hello from {name}!\");\n\
- }}\n\
- }}\n"
- );
- std::fs::write(root.join("src").join("main.bio"), main).map_err(|e| e.to_string())?;
- println!("✔ project created: {name}");
- println!(" package.toml — manifest (name/version/repo + deps)");
- println!(" src/main.bio — entry");
- println!(" utils/ — libraries (need providers)");
- Ok(())
-}
-
-/// 项目构建:need bundling(load_project_sources 合并)+ LLVM 编译 → 可执行。
-/// -s standalone(默认);-m 打包 .img/.zip(v1 简化:先做 standalone 产物,包格式后续)。
-fn cmd_build(dir: &str, _mode: &str, out: Option<&str>) -> Result<(), String> {
- let root = PathBuf::from(dir);
- let prog = bbb_vm::load_project_sources(&root)?;
- // 项目名:package.toml 的 name 字段(简单解析),默认目录名
- let pname = parse_package_name(&root).unwrap_or_else(|| {
- root.file_name()
- .map(|s| s.to_string_lossy().to_string())
- .unwrap_or_else(|| "app".to_string())
- });
- let out_path = match out {
- Some(o) => o.to_string(),
- None => {
- let dir_trim = dir.trim_end_matches('/');
- if dir_trim.is_empty() || dir_trim == "." {
- format!("bin/{pname}")
- } else {
- format!("{dir_trim}/bin/{pname}")
- }
- }
- };
- println!("building {dir} → {out_path}");
- compile_to_executable(&prog, &out_path)?;
- println!("✔ build ok: {out_path}");
- Ok(())
-}
-
-/// 简单解析 package.toml 的 name = "..."。
-fn parse_package_name(root: &Path) -> Option {
- let toml = std::fs::read_to_string(root.join("package.toml")).ok()?;
- for line in toml.lines() {
- let line = line.trim();
- if let Some(rest) = line.strip_prefix("name") {
- let rest = rest.trim_start();
- if let Some(rest) = rest.strip_prefix('=') {
- let rest = rest.trim().trim_matches('"');
- if !rest.is_empty() {
- return Some(rest.to_string());
- }
- }
- }
- }
- None
-}
-
-fn cmd_install(dir: &str) -> Result<(), String> {
- let root = PathBuf::from(dir);
- let toml_path = root.join("package.toml");
- let toml = match std::fs::read_to_string(&toml_path) {
- Ok(t) => t,
- Err(_) => return Err(format!("no package.toml in {dir}")),
- };
- // 解析 [dependencies] 下的 name = { version, repo } 或 name = "version"
- let mut found = false;
- let mut installed = 0;
- let mut in_deps = false;
- for line in toml.lines() {
- let line = line.trim();
- if line.is_empty() || line.starts_with('#') {
- continue;
- }
- if line.starts_with('[') {
- in_deps = line.starts_with("[dependencies]");
- continue;
- }
- if !in_deps {
- continue;
- }
- let Some(eq) = line.find('=') else { continue };
- let dep = line[..eq].trim().to_string();
- let spec = line[eq + 1..].trim();
- if dep.is_empty() {
- continue;
- }
- found = true;
- // repo:spec 里的 repo = "...";否则从 BIOLANG_CONFIG 全局配置读
- let repo = extract_repo(spec).or_else(global_repo);
- match repo {
- Some(r) => {
- let dest = root.join(".biolang").join("deps").join(&dep);
- let rc = fetch_dep(&r, &dest);
- if rc == 0 {
- println!("✔ installed {dep}");
- installed += 1;
- } else {
- eprintln!("⛔ dep {dep}: fetch failed from {r}");
- }
- }
- None => {
- eprintln!("⛔ dep {dep}: no repo (set repo= or BIOLANG_CONFIG global repo)");
- }
- }
- }
- if !found {
- println!("ℹ️ no dependencies in {}", toml_path.display());
- }
- if installed > 0 {
- println!("✔ {installed} dependency(ies) installed → {}/.biolang/deps", root.display());
- }
- Ok(())
-}
-
-fn extract_repo(spec: &str) -> Option {
- // 形如 { version = "1.0.0", repo = "https://..." } 或 "1.0.0"
- if let Some(inner) = spec.strip_prefix('{') {
- let inner = inner.trim_end_matches('}');
- for part in inner.split(',') {
- let part = part.trim();
- if let Some(rest) = part.strip_prefix("repo") {
- let rest = rest.trim_start();
- if let Some(rest) = rest.strip_prefix('=') {
- return Some(rest.trim().trim_matches('"').to_string());
- }
- }
- }
- None
- } else {
- None
- }
-}
-
-fn global_repo() -> Option {
- let path = std::env::var("BIOLANG_CONFIG")
- .map(PathBuf::from)
- .unwrap_or_else(|_| {
- let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
- PathBuf::from(home).join(".biolang").join("config.toml")
- });
- let text = std::fs::read_to_string(path).ok()?;
- for line in text.lines() {
- let line = line.trim();
- if let Some(rest) = line.strip_prefix("repo") {
- let rest = rest.trim_start();
- if let Some(rest) = rest.strip_prefix('=') {
- return Some(rest.trim().trim_matches('"').to_string());
- }
- }
- }
- None
-}
-
-/// 拉取依赖:git 仓库 → git clone;http → curl 下载 package.toml;本地路径 → 复制。
-fn fetch_dep(repo: &str, dest: &Path) -> i32 {
- if let Some(parent) = dest.parent() {
- let _ = std::fs::create_dir_all(parent);
- }
- if repo.starts_with("http") && repo.contains(".git") {
- let _ = std::fs::remove_dir_all(dest);
- std::process::Command::new("git")
- .args(["clone", "--depth", "1", repo])
- .arg(dest)
- .status()
- .map(|s| if s.success() { 0 } else { 1 })
- .unwrap_or(1)
- } else if repo.starts_with("http") {
- let _ = std::fs::create_dir_all(dest);
- let outfile = dest.join("package.toml");
- std::process::Command::new("curl")
- .args(["-fsSL", repo, "-o"])
- .arg(&outfile)
- .status()
- .map(|s| if s.success() { 0 } else { 1 })
- .unwrap_or(1)
- } else {
- copy_tree(Path::new(repo), dest)
- }
-}
-
-fn copy_tree(src: &Path, dest: &Path) -> i32 {
- if !src.is_dir() {
- return 1;
- }
- let _ = std::fs::remove_dir_all(dest);
- fn rec(src: &Path, dest: &Path) -> std::io::Result<()> {
- std::fs::create_dir_all(dest)?;
- for entry in std::fs::read_dir(src)? {
- let entry = entry?;
- let from = entry.path();
- let to = dest.join(entry.file_name());
- if from.is_dir() {
- rec(&from, &to)?;
- } else {
- std::fs::copy(&from, &to)?;
- }
- }
- Ok(())
- }
- match rec(src, dest) {
- Ok(()) => 0,
- Err(_) => 1,
- }
-}
-
-fn cmd_destroy(dir: &str) -> Result<(), String> {
- let root = PathBuf::from(dir);
- let biolang = root.join(".biolang");
- if biolang.exists() {
- std::fs::remove_dir_all(&biolang).map_err(|e| e.to_string())?;
- }
- let app = root.join("app");
- if app.exists() {
- let _ = std::fs::remove_dir_all(&app);
- }
- let bin_cache = root.join("bin").join(".cache");
- if bin_cache.exists() {
- let _ = std::fs::remove_dir_all(&bin_cache);
- }
- println!("✔ destroyed build artifacts: {}/.biolang, {}/app", dir, dir);
- Ok(())
-}
-
-// ── .img / .zip 打包(v1:基于系统 zip/unzip 的 .zip + 原始 .img) ──
-
-const IMG_MAGIC: &[u8; 7] = b"BIOIMG1";
-const IMG_VERSION: u32 = 2;
-
-fn cmd_pack(out: &str, entry: Option<&str>, files: &[String]) -> Result<(), String> {
- if files.is_empty() {
- return Err("pack: no files".to_string());
- }
- if out.ends_with(".img") {
- img_create(out, entry, files)
- } else {
- zip_create(out, files)
- }
-}
-
-fn zip_create(out: &str, files: &[String]) -> Result<(), String> {
- // 用系统 zip(STORE 无压缩);不存在则报错
- let status = std::process::Command::new("zip")
- .arg("-0")
- .arg("-j")
- .arg(out)
- .args(files)
- .status()
- .map_err(|e| format!("zip invocation failed: {e}"))?;
- if !status.success() {
- return Err(format!("pack failed: {out}"));
- }
- println!("packed {} file(s) → {out}", files.len());
- Ok(())
-}
-
-fn img_create(out: &str, entry: Option<&str>, files: &[String]) -> Result<(), String> {
- use std::io::Write;
- let mut data: Vec = Vec::new();
- // header 占位
- let entry_name = entry.unwrap_or(&files[0]);
- data.extend_from_slice(IMG_MAGIC);
- data.extend_from_slice(&IMG_VERSION.to_le_bytes());
- data.extend_from_slice(&0u32.to_le_bytes()); // flags
- data.extend_from_slice(&(entry_name.len() as u32).to_le_bytes());
- data.extend_from_slice(entry_name.as_bytes());
- data.extend_from_slice(&(files.len() as u32).to_le_bytes());
- // 目录记录区:先写 records(offset 暂填 0),再写 payload
- let header_size = 7 + 4 + 4 + 4 + entry_name.len() + 4; // magic(7) + ver + flags + entry_len + name + count
- // 每条记录 = name_len(4) + mode(4) + name(nl) + offset(8) + size(8)
- let mut offset = header_size as u64;
- for f in files {
- offset += (4 + 4 + f.len() + 8 + 8) as u64;
- }
- let mut records: Vec<(String, u32, u64, u64)> = Vec::new();
- for f in files {
- let bytes = std::fs::read(f).map_err(|e| format!("cannot read {f}: {e}"))?;
- let mode = file_mode(f);
- records.push((f.clone(), mode, offset, bytes.len() as u64));
- offset += bytes.len() as u64;
- }
- for (name, mode, off, size) in &records {
- data.extend_from_slice(&(name.len() as u32).to_le_bytes());
- data.extend_from_slice(&mode.to_le_bytes());
- data.extend_from_slice(name.as_bytes());
- data.extend_from_slice(&off.to_le_bytes());
- data.extend_from_slice(&size.to_le_bytes());
- }
- for f in files {
- let bytes = std::fs::read(f).map_err(|e| format!("cannot read {f}: {e}"))?;
- data.extend_from_slice(&bytes);
- }
- let mut f = std::fs::File::create(out).map_err(|e| e.to_string())?;
- f.write_all(&data).map_err(|e| e.to_string())?;
- println!("packed {} file(s) → {out}", files.len());
- Ok(())
-}
-
-fn file_mode(path: &str) -> u32 {
- use std::os::unix::fs::PermissionsExt;
- std::fs::metadata(path)
- .map(|m| m.permissions().mode() & 0o777)
- .unwrap_or(0o644)
-}
-
-fn cmd_unpack(pkg: &str, dir: &str) -> Result<(), String> {
- if pkg.ends_with(".img") {
- img_unpack(pkg, dir)
- } else {
- std::fs::create_dir_all(dir).map_err(|e| e.to_string())?;
- let status = std::process::Command::new("unzip")
- .arg("-o")
- .arg(pkg)
- .arg("-d")
- .arg(dir)
- .status()
- .map_err(|e| format!("unzip invocation failed: {e}"))?;
- if !status.success() {
- return Err(format!("unpack failed: {pkg}"));
- }
- println!("unpacked {pkg} → {dir}");
- Ok(())
- }
-}
-
-fn img_unpack(pkg: &str, dir: &str) -> Result<(), String> {
- let data = std::fs::read(pkg).map_err(|e| format!("cannot read {pkg}: {e}"))?;
- if data.len() < 24 || &data[..7] != IMG_MAGIC {
- return Err(format!("{pkg}: not a BIOIMG1 image"));
- }
- let mut pos = 7usize;
- let _version = rd_u32(&data, &mut pos);
- let _flags = rd_u32(&data, &mut pos);
- let entry_len = rd_u32(&data, &mut pos) as usize;
- pos += entry_len; // entry name
- let count = rd_u32(&data, &mut pos) as usize;
- std::fs::create_dir_all(dir).map_err(|e| e.to_string())?;
- for _ in 0..count {
- let name_len = rd_u32(&data, &mut pos) as usize;
- let mode = rd_u32(&data, &mut pos);
- let name = String::from_utf8_lossy(&data[pos..pos + name_len]).to_string();
- pos += name_len;
- let off = rd_u64(&data, &mut pos);
- let size = rd_u64(&data, &mut pos);
- let bytes = &data[off as usize..(off + size) as usize];
- let out_path = Path::new(dir).join(Path::new(&name).file_name().unwrap_or_default());
- std::fs::write(&out_path, bytes).map_err(|e| e.to_string())?;
- #[cfg(unix)]
- {
- use std::os::unix::fs::PermissionsExt;
- let _ = std::fs::set_permissions(&out_path, std::fs::Permissions::from_mode(mode));
- }
- }
- println!("unpacked {pkg} → {dir}");
- Ok(())
-}
-
-fn rd_u32(data: &[u8], pos: &mut usize) -> u32 {
- let v = u32::from_le_bytes(data[*pos..*pos + 4].try_into().unwrap());
- *pos += 4;
- v
-}
-
-fn rd_u64(data: &[u8], pos: &mut usize) -> u64 {
- let v = u64::from_le_bytes(data[*pos..*pos + 8].try_into().unwrap());
- *pos += 8;
- v
-}
-
-// ───────────────────────── 内部调试命令(保留) ─────────────────────────
-
-fn cmd_parse(path: &str) -> Result<(), String> {
- let src = read_source(path)?;
- let (prog, errs) = parse_source(&src);
- if !errs.is_empty() {
- for e in &errs {
- eprintln!("parse error: {e}");
- }
- return Err(format!("{} parse errors", errs.len()));
- }
- println!("kind: {}", if prog.kind.is_empty() { "" } else { &prog.kind });
- println!("decls: {} | main: {} | methods: {}",
- prog.decls.len(),
- if prog.main.is_some() { "yes" } else { "no" },
- prog.main.as_ref().map(|m| m.methods.len()).unwrap_or(0));
- for d in &prog.decls {
- let name = match d {
- bbb_syntax::Decl::Const { name, .. } => format!("const {name}"),
- bbb_syntax::Decl::Need { kind, name } => format!("need {kind} {name}"),
- bbb_syntax::Decl::StreamSig { name, members, .. } =>
- format!("stream {name} ({} members)", members.len()),
- bbb_syntax::Decl::StreamBin { name, file, .. } =>
- format!("bin-stream {name} <- {file}"),
- bbb_syntax::Decl::Class { name, members, .. } =>
- format!("class {name} ({} members)", members.len()),
- bbb_syntax::Decl::Fork { sig, name, members, .. } =>
- format!("fork {sig} {name} ({} members)", members.len()),
- bbb_syntax::Decl::Interface { name, members, .. } =>
- format!("interface {name} ({} members)", members.len()),
- };
- println!(" {name}");
- }
- Ok(())
-}
-
-fn cmd_arena(n: u32) {
- let mut arena = BumpArena::::new();
- let mut last = 0;
- for i in 1..=n {
- last = arena.alloc(i as u64);
- }
- assert_eq!(*arena.get(last), n as u64);
- println!("BumpArena: {} slots, {} pages, handle {last} valid", n, arena.pages());
-
- let mut strs = StrArena::new();
- let mut handle = None;
- for i in 0..n {
- let s = format!("value-{i}-{}", "x".repeat((i % 97) as usize));
- handle = Some(strs.push(&s));
- }
- let h = handle.unwrap();
- assert!(strs.get(h).starts_with("value-"));
- println!("StrArena: {} writes, last len {} (cross-page intact)", n, strs.get(h).len());
-
- let v = Value::int(42).with_refused();
- println!("Value: size={}B, refused int displays as {v}", std::mem::size_of::());
-}
-
-// ───────────────────────── 入口 ─────────────────────────
-
-fn main() -> ExitCode {
- let args: Vec = std::env::args().collect();
- let argc = args.len();
-
- // 无参数:内置演示(v1:打印 usage 提示)
- if argc == 1 {
- print!("{USAGE}");
- return ExitCode::SUCCESS;
- }
-
- let a1 = args[1].as_str();
- // 全局选项
- if a1 == "-h" || a1 == "--help" {
- print!("{USAGE}");
- return ExitCode::SUCCESS;
- }
- if a1 == "--version" || a1 == "-V" {
- println!("bbb {}", env!("CARGO_PKG_VERSION"));
- return ExitCode::SUCCESS;
- }
- if a1 == "-e" {
- // 内存限制:v1 接受参数(0 = unlimited),解释器当前无硬限制
- if argc < 3 {
- eprintln!("usage: bbb -e ");
- return ExitCode::FAILURE;
- }
- let _limit = parse_size(&args[2]);
- // 剩余参数按普通命令处理(-e 仅设置限制)
- let rest: Vec = args[2..].to_vec();
- if rest.is_empty() {
- print!("{USAGE}");
- return ExitCode::SUCCESS;
- }
- return dispatch(&rest[0], &rest[1..]);
- }
- if a1 == "--tokens" {
- if argc < 3 {
- eprintln!("usage: bbb --tokens ");
- return ExitCode::FAILURE;
- }
- return finish(cmd_tokens(&args[2]));
- }
-
- dispatch(a1, &args[2..])
-}
-
-fn parse_size(s: &str) -> u64 {
- let (num, mult) = match s.chars().last() {
- Some('K' | 'k') => (&s[..s.len() - 1], 1024u64),
- Some('M' | 'm') => (&s[..s.len() - 1], 1024u64 * 1024),
- Some('G' | 'g') => (&s[..s.len() - 1], 1024u64 * 1024 * 1024),
- _ => (s, 1),
- };
- num.parse::().unwrap_or(0) * mult
-}
-
-fn dispatch(cmd: &str, rest: &[String]) -> ExitCode {
- match cmd {
- "shell" => {
- match rest.first().map(|s| s.as_str()) {
- Some("run") => {
- if rest.len() < 2 {
- eprintln!("usage: bbb shell run ");
- return ExitCode::FAILURE;
- }
- finish(cmd_shell_run(&rest[1]))
- }
- Some("build") => {
- if rest.len() < 2 {
- eprintln!("usage: bbb shell build [-o out]");
- return ExitCode::FAILURE;
- }
- let file = &rest[1];
- let mut out = None;
- let mut i = 2;
- while i < rest.len() {
- if rest[i] == "-o" && i + 1 < rest.len() {
- out = Some(rest[i + 1].clone());
- i += 2;
- } else {
- eprintln!("usage: bbb shell build [-o out]");
- return ExitCode::FAILURE;
- }
- }
- finish(cmd_shell_build(file, out.as_deref()))
- }
- _ => {
- eprintln!("usage: bbb shell run | bbb shell build [-o out]");
- ExitCode::FAILURE
- }
- }
- }
- "init" => {
- if rest.is_empty() {
- eprintln!("usage: bbb init ");
- return ExitCode::FAILURE;
- }
- finish(cmd_init(&rest[0]))
- }
- "build" => {
- // bbb build [dir] [-s|-m [out]] [-o out]
- let mut dir = ".".to_string();
- let mut mode = "s";
- let mut out = None;
- let mut i = 0;
- while i < rest.len() {
- match rest[i].as_str() {
- "-s" => mode = "s",
- "-m" => {
- mode = "m";
- if i + 1 < rest.len() && !rest[i + 1].starts_with('-') {
- out = Some(rest[i + 1].clone());
- i += 1;
- }
- }
- "-o" => {
- if i + 1 < rest.len() {
- out = Some(rest[i + 1].clone());
- i += 1;
- }
- }
- other => dir = other.to_string(),
- }
- i += 1;
- }
- finish(cmd_build(&dir, mode, out.as_deref()))
- }
- "run" => {
- let target = rest.first().map(|s| s.as_str()).unwrap_or(".");
- finish(cmd_run(target))
- }
- "install" => {
- let dir = rest.first().map(|s| s.as_str()).unwrap_or(".");
- finish(cmd_install(dir))
- }
- "destroy" => {
- let dir = rest.first().map(|s| s.as_str()).unwrap_or(".");
- finish(cmd_destroy(dir))
- }
- "pack" => {
- if rest.len() < 2 {
- eprintln!("usage: bbb pack [--entry NAME] ");
- return ExitCode::FAILURE;
- }
- let out = &rest[0];
- let mut entry = None;
- let mut files = Vec::new();
- let mut i = 1;
- while i < rest.len() {
- if rest[i] == "--entry" && i + 1 < rest.len() {
- entry = Some(rest[i + 1].clone());
- i += 2;
- } else {
- files.push(rest[i].clone());
- i += 1;
- }
- }
- finish(cmd_pack(out, entry.as_deref(), &files))
- }
- "unpack" => {
- if rest.is_empty() {
- eprintln!("usage: bbb unpack [dir]");
- return ExitCode::FAILURE;
- }
- let dir = rest.get(1).map(|s| s.as_str()).unwrap_or(".");
- finish(cmd_unpack(&rest[0], dir))
- }
- // 内部调试命令(保留)
- "lexer" => {
- if rest.is_empty() {
- eprintln!("usage: bbb lexer ");
- return ExitCode::FAILURE;
- }
- finish(cmd_tokens(&rest[0]))
- }
- "parse" => {
- if rest.is_empty() {
- eprintln!("usage: bbb parse ");
- return ExitCode::FAILURE;
- }
- finish(cmd_parse(&rest[0]))
- }
- "arena" => {
- let n: u32 = rest.first().and_then(|s| s.parse().ok()).unwrap_or(10_000);
- cmd_arena(n);
- ExitCode::SUCCESS
- }
- // 默认:`bbb ` 解释运行
- _ => finish(cmd_run(cmd)),
- }
-}
-
-fn finish(r: Result<(), String>) -> ExitCode {
- match r {
- Ok(()) => ExitCode::SUCCESS,
- Err(e) => {
- eprintln!("{e}");
- ExitCode::FAILURE
- }
- }
-}
diff --git a/rust/crates/bbb-core/Cargo.toml b/rust/crates/bbb-core/Cargo.toml
deleted file mode 100644
index 44e7029..0000000
--- a/rust/crates/bbb-core/Cargo.toml
+++ /dev/null
@@ -1,8 +0,0 @@
-[package]
-name = "bbb-core"
-version.workspace = true
-edition.workspace = true
-license.workspace = true
-description = "BioLang 运行时核心:arena 内存规划 + Value/请求模型"
-
-[dependencies]
diff --git a/rust/crates/bbb-core/src/arena.rs b/rust/crates/bbb-core/src/arena.rs
deleted file mode 100644
index 88c7a77..0000000
--- a/rust/crates/bbb-core/src/arena.rs
+++ /dev/null
@@ -1,210 +0,0 @@
-//! Arena 内存规划(bbb-core)。
-//!
-//! # BumpArena — 类型化 bump 分配器
-//!
-//! - 分页:每页固定 `PAGE_SLOTS` 槽;页表 `Vec>` 只增不减;
-//! - `alloc(v) -> u32`:句柄 = `(page << SHIFT) | slot`,0 保留为 null;
-//! - `get(h) -> &T` / `get_mut`:O(1);
-//! - 永不释放单个槽(程序级生命周期,与旧 C `aalloc` 语义一致);
-//! - 句柄优点:扩容搬迁安全、可序列化、对齐无空洞、u32 省内存。
-//!
-//! # StrArena — 字符串字节池
-//!
-//! 分页字节缓冲(页 4 KiB 起步,几何增长),`push(&str) -> StrRef{off,len}`
-//! 只拷贝一次;之后取用零拷贝。`StrRef` 8 字节,可安全穿越线程边界
-//! (字节池只增,读不竞争——协作式调度下无并发写)。
-
-use std::marker::PhantomData;
-
-/// 字符串引用:(offset, len) 指向 StrArena 字节池。
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub struct StrRef {
- pub off: u32,
- pub len: u32,
-}
-
-impl StrRef {
- pub const NULL: StrRef = StrRef { off: 0, len: 0 };
- pub fn is_null(self) -> bool {
- self.off == 0 && self.len == 0
- }
-}
-
-/// 类型化 bump arena。`T: Copy` 约束保证句柄读取无别名问题。
-pub struct BumpArena {
- pages: Vec>,
- next: u32, // 当前页已用槽数
- _marker: PhantomData,
-}
-
-const PAGE_SLOTS: u32 = 256; // 每页槽数(2^8)
-const SHIFT: u32 = 8;
-const SLOT_MASK: u32 = PAGE_SLOTS - 1;
-const MAX_HANDLE: u32 = u32::MAX >> 1; // 最高位留给 null 标志扩展
-
-impl BumpArena {
- pub fn new() -> Self {
- BumpArena { pages: Vec::new(), next: 0, _marker: PhantomData }
- }
-
- fn ensure_page(&mut self) {
- if self.next == PAGE_SLOTS || self.pages.is_empty() {
- let page: Box<[T]> = (0..PAGE_SLOTS).map(|_| unsafe { std::mem::zeroed() }).collect();
- self.pages.push(page);
- self.next = 1; // 槽 0 保留为 null 哨兵,句柄永不等于 0
- }
- }
-
- /// 分配一个槽,返回 u32 句柄。0 永不返回(保留为 null)。
- #[inline]
- pub fn alloc(&mut self, v: T) -> u32 {
- self.ensure_page();
- let page = self.pages.len() as u32 - 1;
- let slot = self.next;
- self.pages[page as usize][slot as usize] = v;
- self.next += 1;
- (page << SHIFT) | slot
- }
-
- /// 句柄 → 不可变引用。
- #[inline]
- pub fn get(&self, h: u32) -> &T {
- debug_assert!(h != 0 && h <= MAX_HANDLE);
- let page = (h >> SHIFT) as usize;
- let slot = (h & SLOT_MASK) as usize;
- &self.pages[page][slot]
- }
-
- /// 句柄 → 可变引用(bump 语义下互斥由外部保证)。
- #[inline]
- pub fn get_mut(&mut self, h: u32) -> &mut T {
- debug_assert!(h != 0 && h <= MAX_HANDLE);
- let page = (h >> SHIFT) as usize;
- let slot = (h & SLOT_MASK) as usize;
- &mut self.pages[page][slot]
- }
-
- pub fn pages(&self) -> usize {
- self.pages.len()
- }
-
- pub fn capacity(&self) -> u32 {
- self.pages.len() as u32 * PAGE_SLOTS
- }
-}
-
-impl Default for BumpArena {
- fn default() -> Self {
- Self::new()
- }
-}
-
-/// 字符串字节池。
-pub struct StrArena {
- pages: Vec>,
- cur: Vec,
-}
-
-impl StrArena {
- pub fn new() -> Self {
- StrArena { pages: Vec::new(), cur: Vec::with_capacity(4096) }
- }
-
- /// 写入一个字符串,返回 (offset, len)。数据拷贝一次后永驻。
- #[inline]
- pub fn push(&mut self, s: &str) -> StrRef {
- let bytes = s.as_bytes();
- if self.cur.len() + bytes.len() > self.cur.capacity() {
- // 当前页放不下:封页,开新页(容量几何增长)
- if !self.cur.is_empty() {
- self.pages.push(std::mem::take(&mut self.cur));
- }
- let cap = (4096usize).max(bytes.len().next_power_of_two());
- self.cur = Vec::with_capacity(cap);
- }
- let off = self.total_len() as u32;
- self.cur.extend_from_slice(bytes);
- StrRef { off, len: bytes.len() as u32 }
- }
-
- /// 按 StrRef 取回字符串视图(零拷贝)。
- #[inline]
- pub fn get<'a>(&'a self, r: StrRef) -> &'a str {
- if r.is_null() {
- return "";
- }
- let start = r.off as usize;
- let end = start + r.len as usize;
- let mut acc = 0usize;
- for page in &self.pages {
- let page_len = page.len();
- if start < acc + page_len && end <= acc + page_len {
- return std::str::from_utf8(&page[start - acc..end - acc]).unwrap_or("");
- }
- acc += page_len;
- }
- std::str::from_utf8(&self.cur[start - acc..end - acc]).unwrap_or("")
- }
-
- fn total_len(&self) -> usize {
- self.pages.iter().map(|p| p.len()).sum::() + self.cur.len()
- }
-}
-
-impl Default for StrArena {
- fn default() -> Self {
- Self::new()
- }
-}
-
-/// 通用别名:对象/数组/线程等句柄表都用 BumpArena。
-pub type Arena = BumpArena;
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn bump_arena_alloc_get() {
- let mut a = BumpArena::::new();
- let h1 = a.alloc(42);
- let h2 = a.alloc(7);
- assert_ne!(h1, h2);
- assert_eq!(*a.get(h1), 42);
- assert_eq!(*a.get(h2), 7);
- *a.get_mut(h1) = 99;
- assert_eq!(*a.get(h1), 99);
- }
-
- #[test]
- fn bump_arena_multi_page() {
- let mut a = BumpArena::::new();
- let mut last = 0;
- for i in 1..1000u32 {
- last = a.alloc(i);
- }
- assert_eq!(*a.get(last), 999);
- assert!(a.pages() >= 3);
- }
-
- #[test]
- fn str_arena_roundtrip() {
- let mut s = StrArena::new();
- let a = s.push("hello");
- let b = s.push("世界");
- let c = s.push("x".repeat(5000).as_str());
- assert_eq!(s.get(a), "hello");
- assert_eq!(s.get(b), "世界");
- assert_eq!(s.get(c).len(), 5000);
- assert!(s.get(StrRef::NULL).is_empty());
- }
-
- #[test]
- fn str_arena_cross_page() {
- // 跨页边界的长串必须完整可读
- let mut s = StrArena::new();
- let long = "abc".repeat(2000);
- let r = s.push(&long);
- assert_eq!(s.get(r), long);
- }
-}
diff --git a/rust/crates/bbb-core/src/lib.rs b/rust/crates/bbb-core/src/lib.rs
deleted file mode 100644
index d8fe6eb..0000000
--- a/rust/crates/bbb-core/src/lib.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-//! bbb-core — BioLang 运行时核心。
-//!
-//! 内存规划(手写掌控版,对标旧 C 的 arena 设计并强化):
-//!
-//! 1. **一切值进 arena,引用用 u32 句柄而非指针**
-//! - 句柄 = (page, slot) 打包,arena 扩容/搬迁不需要修指针;
-//! - 句柄天然可序列化(.img 打包、跨线程传递);8 字节对齐无空洞;
-//! - 与旧 C 的 `aalloc`(裸指针 + 永不释放)相比,句柄方案在保持
-//! "程序级一次性生命周期" 的同时,获得可搬迁 + 可序列化两个能力。
-//! 2. **字符串 = 全局字节池中的 (offset, len)**,写入即 intern,
-//! 零拷贝复用;`StrRef` 8 字节。
-//! 3. **Value 16 字节**:u32 tag(含 REFUSED 标志位)+ u64 负载 + 4B pad;
-//! 所有标量(int/float/double/bool/char)内联,字符串/对象/数组走句柄。
-//! (v2 候选:NaN-boxing 压到 8 字节,代价是 int 精度受限——旧 LLVM
-//! 后端统一 double 语义,解释器保留 i64,故 v1 不采用。)
-//! 4. **请求模型 = Value 的 tag 位**:bit31 = refused,负载为 cause 的
-//! 字符串句柄;`res`/`ref` 不产生堆分配,随值传递。
-//! 5. **区域划分**:每个流(Unistream/Remstream/Threadstream...)在 arena
-//! 内拥有自己的页区,线程隔离靠区域隔离实现(协作式调度,无锁)。
-
-pub mod arena;
-pub mod value;
-
-pub use arena::{Arena, BumpArena, StrArena, StrRef};
-pub use value::{Cause, Outcome, Tag, Value};
diff --git a/rust/crates/bbb-core/src/value.rs b/rust/crates/bbb-core/src/value.rs
deleted file mode 100644
index 2773dc0..0000000
--- a/rust/crates/bbb-core/src/value.rs
+++ /dev/null
@@ -1,342 +0,0 @@
-//! Value / 请求模型(bbb-core)。
-//!
-//! # Value — 16 字节标量优先表示
-//!
-//! ```text
-//! ┌──────────────┬──────────────────┬──────────────┐
-//! │ tag: u32 │ data: u64 │ pad: u32 │
-//! │ bit31 REFUSED│ 负载 │ (对齐) │
-//! └──────────────┴──────────────────┴──────────────┘
-//! ```
-//!
-//! - 标量(Int/Num/Bool/Char)全部内联,零堆分配;
-//! - Str/Obj/Arr/Ref 走句柄(u32 → arena),8 字节以内;
-//! - **REFUSED 位**:`ref "原因"` 产生的请求结果 = 同值 + 标志位,
-//! 不额外分配;`get`/`cause` 只是位测试;
-//! - `Outcome`:解释器内部用,`Res(Value)` / `Ref(Cause)` 二态,
-//! 与语法层 ResStatement/RefStatement 一一对应。
-//!
-//! # 类型标签(低 24 位)
-//!
-//! Nil / Int / Num / Bool / Str / Char / Obj / Arr / Ref
-
-use crate::arena::StrRef;
-
-pub const TAG_MASK: u32 = 0x00FF_FFFF;
-pub const REFUSED: u32 = 0x8000_0000;
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-#[repr(u32)]
-pub enum Tag {
- Nil = 0,
- Int = 1,
- Num = 2, // float/double 统一 double 语义(LLVM 后端同款)
- Bool = 3,
- Str = 4,
- Char = 5,
- Obj = 6,
- Arr = 7,
- Ref = 8, // 智能引用句柄(&perm follow base)
-}
-
-impl Tag {
- #[inline]
- pub fn from_bits(bits: u32) -> Tag {
- match bits & TAG_MASK {
- 0 => Tag::Nil,
- 1 => Tag::Int,
- 2 => Tag::Num,
- 3 => Tag::Bool,
- 4 => Tag::Str,
- 5 => Tag::Char,
- 6 => Tag::Obj,
- 7 => Tag::Arr,
- _ => Tag::Ref,
- }
- }
-}
-
-/// 16 字节 Value(对齐 8;字段按对齐降序排列保证紧凑)。
-#[derive(Debug, Clone, Copy)]
-#[repr(C, align(8))]
-pub struct Value {
- data: u64, // 负载
- tag: u32, // 低 24 位类型 + bit31 REFUSED
- _pad: u32,
-}
-
-impl Value {
- pub const NIL: Value = Value { tag: Tag::Nil as u32, data: 0, _pad: 0 };
-
- #[inline]
- pub fn nil() -> Self {
- Self::NIL
- }
-
- #[inline]
- pub fn int(v: i64) -> Self {
- Value { tag: Tag::Int as u32, data: v as u64, _pad: 0 }
- }
-
- #[inline]
- pub fn num(v: f64) -> Self {
- Value { tag: Tag::Num as u32, data: v.to_bits(), _pad: 0 }
- }
-
- #[inline]
- pub fn boolean(v: bool) -> Self {
- Value { tag: Tag::Bool as u32, data: v as u64, _pad: 0 }
- }
-
- #[inline]
- pub fn string(r: StrRef) -> Self {
- Value { tag: Tag::Str as u32, data: ((r.off as u64) << 32) | r.len as u64, _pad: 0 }
- }
-
- #[inline]
- pub fn chr(v: u8) -> Self {
- Value { tag: Tag::Char as u32, data: v as u64, _pad: 0 }
- }
-
- #[inline]
- pub fn obj(h: u32) -> Self {
- Value { tag: Tag::Obj as u32, data: h as u64, _pad: 0 }
- }
-
- #[inline]
- pub fn arr(h: u32) -> Self {
- Value { tag: Tag::Arr as u32, data: h as u64, _pad: 0 }
- }
-
- #[inline]
- pub fn reff(h: u32) -> Self {
- Value { tag: Tag::Ref as u32, data: h as u64, _pad: 0 }
- }
-
- #[inline]
- pub fn tag(&self) -> Tag {
- Tag::from_bits(self.tag)
- }
-
- #[inline]
- pub fn refused(&self) -> bool {
- self.tag & REFUSED != 0
- }
-
- /// 标记为拒绝(请求模型:ref)。保留原值,仅置位。
- #[inline]
- pub fn with_refused(mut self) -> Self {
- self.tag |= REFUSED;
- self
- }
-
- /// 拒绝值:REFUSED 位 + 原因字符串句柄(cause)。
- #[inline]
- pub fn refused_str(r: StrRef) -> Self {
- Value {
- tag: Tag::Str as u32 | REFUSED,
- data: ((r.off as u64) << 32) | r.len as u64,
- _pad: 0,
- }
- }
-
- /// 拒绝原因(未拒绝时返回空串句柄)。
- #[inline]
- pub fn cause(&self) -> StrRef {
- if self.refused() && self.tag() == Tag::Str {
- self.as_str()
- } else {
- StrRef::NULL
- }
- }
-
- /// 数值视图:Int → i64 → f64;Num → f64(统一 double 语义)。
- #[inline]
- pub fn as_int_or_num(&self) -> f64 {
- match self.tag() {
- Tag::Int => self.as_int() as f64,
- Tag::Num => self.as_num(),
- Tag::Bool => self.as_bool() as u8 as f64,
- Tag::Char => self.as_char() as f64,
- _ => 0.0,
- }
- }
-
- #[inline]
- pub fn as_int(&self) -> i64 {
- debug_assert_eq!(self.tag(), Tag::Int);
- self.data as i64
- }
-
- #[inline]
- pub fn as_num(&self) -> f64 {
- debug_assert_eq!(self.tag(), Tag::Num);
- f64::from_bits(self.data)
- }
-
- #[inline]
- pub fn as_bool(&self) -> bool {
- debug_assert_eq!(self.tag(), Tag::Bool);
- self.data != 0
- }
-
- #[inline]
- pub fn as_str(&self) -> StrRef {
- debug_assert_eq!(self.tag(), Tag::Str);
- StrRef { off: (self.data >> 32) as u32, len: self.data as u32 }
- }
-
- #[inline]
- pub fn as_char(&self) -> u8 {
- debug_assert_eq!(self.tag(), Tag::Char);
- self.data as u8
- }
-
- #[inline]
- pub fn as_handle(&self) -> u32 {
- debug_assert!(matches!(self.tag(), Tag::Obj | Tag::Arr | Tag::Ref));
- self.data as u32
- }
-
- /// 真值判定(与旧实现一致):0 / "" / 拒绝 = false,其余 true。
- #[inline]
- pub fn truthy(&self) -> bool {
- if self.refused() {
- return false;
- }
- match self.tag() {
- Tag::Nil => false,
- Tag::Int => self.as_int() != 0,
- Tag::Num => self.as_num() != 0.0,
- Tag::Bool => self.as_bool(),
- Tag::Str => !self.as_str().is_null(),
- Tag::Char => self.as_char() != 0,
- _ => true,
- }
- }
-}
-
-impl PartialEq for Value {
- fn eq(&self, other: &Self) -> bool {
- if self.refused() != other.refused() {
- return false;
- }
- self.tag == other.tag && self.data == other.data
- }
-}
-impl Eq for Value {}
-
-impl std::fmt::Display for Value {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- if self.refused() {
- return write!(f, "");
- }
- match self.tag() {
- Tag::Nil => write!(f, "nil"),
- Tag::Int => write!(f, "{}", self.as_int()),
- Tag::Num => write!(f, "{}", self.as_num()),
- Tag::Bool => write!(f, "{}", self.as_bool()),
- Tag::Str => write!(f, "", self.data),
- Tag::Char => write!(f, "{}", self.as_char() as char),
- Tag::Obj => write!(f, "