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 @@ - - - - version - - v0.4.0 - - license - - MIT - - language - - C - - compiler - - LLVM - - build - - zig - - docs - - 7 langs - - 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, "", self.as_handle()), - Tag::Arr => write!(f, "", self.as_handle()), - Tag::Ref => write!(f, "", self.as_handle()), - } - } -} - -/// 请求结果:Res = 响应,Ref = 拒绝(携带 cause 字符串)。 -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Outcome { - Res(Value), - Ref(Cause), -} - -/// 拒绝原因:字符串句柄(arena 内,零拷贝)。 -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Cause(pub StrRef); - -impl Outcome { - #[inline] - pub fn is_refused(&self) -> bool { - matches!(self, Outcome::Ref(_)) - } - - /// `get`:取实际值(拒绝时返回 Nil)。 - #[inline] - pub fn get(self) -> Value { - match self { - Outcome::Res(v) => v, - Outcome::Ref(_) => Value::nil(), - } - } - - /// `cause`:取拒绝原因(未拒绝时返回空字符串)。 - #[inline] - pub fn cause(self) -> StrRef { - match self { - Outcome::Res(_) => StrRef::NULL, - Outcome::Ref(c) => c.0, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn value_size_is_16() { - assert_eq!(std::mem::size_of::(), 16); - } - - #[test] - fn scalars_inline() { - assert_eq!(Value::int(42).as_int(), 42); - assert_eq!(Value::num(3.5).as_num(), 3.5); - assert!(Value::boolean(true).as_bool()); - assert_eq!(Value::chr(b'x').as_char(), b'x'); - assert_eq!(Value::nil().tag(), Tag::Nil); - } - - #[test] - fn string_roundtrip() { - let r = StrRef { off: 12, len: 5 }; - let v = Value::string(r); - assert_eq!(v.tag(), Tag::Str); - assert_eq!(v.as_str(), r); - } - - #[test] - fn refused_flag() { - let ok = Value::int(7); - let bad = ok.with_refused(); - assert!(bad.refused()); - assert!(!ok.refused()); - assert_ne!(ok, bad); - assert_eq!(bad.truthy(), false); - } - - #[test] - fn truthiness() { - assert!(!Value::nil().truthy()); - assert!(!Value::int(0).truthy()); - assert!(Value::int(1).truthy()); - assert!(!Value::num(0.0).truthy()); - assert!(!Value::boolean(false).truthy()); - assert!(!Value::string(StrRef::NULL).truthy()); - } - - #[test] - fn outcome_get_cause() { - let res = Outcome::Res(Value::int(10)); - assert_eq!(res.get(), Value::int(10)); - assert!(res.cause().is_null()); - - let cause = Cause(StrRef { off: 3, len: 9 }); - let rej = Outcome::Ref(cause); - assert!(rej.is_refused()); - assert_eq!(rej.get(), Value::nil()); - assert_eq!(rej.cause(), cause.0); - } -} diff --git a/rust/crates/bbb-llvm/Cargo.toml b/rust/crates/bbb-llvm/Cargo.toml deleted file mode 100644 index 3eb704d..0000000 --- a/rust/crates/bbb-llvm/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "bbb-llvm" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "BioLang LLVM 后端(M4):AST → LLVM IR 文本 → 系统 clang 编译" - -[dependencies] -bbb-syntax = { path = "../bbb-syntax" } diff --git a/rust/crates/bbb-llvm/src/lib.rs b/rust/crates/bbb-llvm/src/lib.rs deleted file mode 100644 index 5ff3f3d..0000000 --- a/rust/crates/bbb-llvm/src/lib.rs +++ /dev/null @@ -1,887 +0,0 @@ -//! bbb-llvm — BioLang 编译器(M4 里程碑)。 -//! -//! 方案(对齐旧 src/llvm.c):**零依赖**发射 LLVM IR 文本,交给系统 clang -//! 编译成原生可执行文件。统一 double 语义在解释器与编译器间保持一致。 -//! -//! M4 v1 支持子集(17-llvm.bio 为准): -//! - int/float/double 变量声明与赋值、算术 + - * / %、比较 == != < > <= >= -//! - if/else、while、for(含 break/continue) -//! - 方法定义与调用(含 Main::exec 外的方法,如 square)、递归 v2 -//! - res 返回值 / ref(打印后退出)、get/cause、ALL 声明 -//! - CIO::println / CIO::print(字符串+数字混合 → printf) -//! - Class + new(对象 = malloc struct,this 指针传参,__init__ 自动调用, -//! this::字段 GEP 读写,对象方法调用)——2026-08-23 扩展 - -use std::collections::HashMap; - -use bbb_syntax::ast::*; - -/// 值类型。 -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum Ty { - I64, - F64, - Ptr, // 对象/流指针 -} - -impl Ty { - fn llvm(self) -> &'static str { - match self { - Ty::I64 => "i64", - Ty::F64 => "double", - Ty::Ptr => "ptr", - } - } -} - -/// 编译产物。 -pub struct Module { - pub ir: String, -} - -struct Ctx { - out: String, - tmp: usize, - str_i: usize, - vars: Vec>, // 作用域栈:变量名 → (类型, 寄存器名) - funcs: HashMap)>, // 方法表:名 → (返回类型, 参数) - void_methods: std::collections::HashSet, // void 方法集合(调用时用 call void) - classes: HashMap>, // 类:名 → 字段列表(名, 类型) - var_ty: HashMap, // 变量名 → 类名(对象变量,属性访问用) - current_class: Option, // 当前编译的类名(this:: 属性用) - this_reg: Option, // 当前方法 this 指针寄存器(类方法) - labels: usize, - loop_end: Vec, - loop_continue: Vec, // continue 目标(for=update,while=cond) - current_ret: Ty, - str_list: String, // 字符串常量延迟输出(函数外) -} - -impl Ctx { - fn new() -> Self { - Ctx { - out: String::new(), - tmp: 0, - str_i: 0, - vars: vec![HashMap::new()], - funcs: HashMap::new(), - void_methods: std::collections::HashSet::new(), - classes: HashMap::new(), - var_ty: HashMap::new(), - current_class: None, - this_reg: None, - labels: 0, - loop_end: Vec::new(), - loop_continue: Vec::new(), - current_ret: Ty::I64, - str_list: String::new(), - } - } - - fn emit(&mut self, s: impl AsRef) { - self.out.push_str(s.as_ref()); - self.out.push('\n'); - } - - fn reg(&mut self, hint: &str) -> String { - self.tmp += 1; - format!("%{hint}{}", self.tmp) - } - - fn label(&mut self, hint: &str) -> String { - self.labels += 1; - format!("{hint}{}", self.labels) - } - - fn str_const(&mut self, s: &str) -> String { - // C 风格转义 - let mut esc = String::new(); - for b in s.bytes() { - match b { - b'"' => esc.push_str("\\22"), - b'\\' => esc.push_str("\\5C"), - b'\n' => esc.push_str("\\0A"), - b'\t' => esc.push_str("\\09"), - 0x20..=0x7E => esc.push(b as char), - _ => esc.push_str(&format!("\\{:02X}", b)), - } - } - self.str_i += 1; - let name = format!("@.str{}", self.str_i); - // LLVM 字节数:\XX 转义序列计 1 字节,其余字符 1 字节 - let mut llvm_len = 0usize; - let chars: Vec = esc.chars().collect(); - let mut k = 0; - while k < chars.len() { - if chars[k] == '\\' && k + 2 < chars.len() { - llvm_len += 1; - k += 3; - } else { - llvm_len += 1; - k += 1; - } - } - self.str_list.push_str(&format!("{name} = private unnamed_addr constant [{} x i8] c\"{esc}\\00\", align 1\n", - llvm_len + 1)); - name - } - - fn var_get(&self, name: &str) -> Option<(Ty, String)> { - for scope in self.vars.iter().rev() { - if let Some(v) = scope.get(name) { - return Some(v.clone()); - } - } - None - } - - fn var_set(&mut self, name: &str, ty: Ty, reg: String) { - self.vars.last_mut().unwrap().insert(name.to_string(), (ty, reg)); - } -} - -/// 编译 Program → IR 文本。 -pub fn compile(prog: &Program) -> Result { - let mut ctx = Ctx::new(); - ctx.emit("; BioLang LLVM backend (M4) — generated from AST"); - ctx.emit("declare i32 @printf(ptr, ...)"); - ctx.emit("declare void @exit(i32)"); - ctx.emit("declare ptr @malloc(i64)"); - ctx.emit(""); - - // 收集类字段表(先于类型声明) - collect_classes(prog, &mut ctx); - // 发射 struct 类型声明 - let class_names: Vec = ctx.classes.keys().cloned().collect(); - for cname in &class_names { - let fields = &ctx.classes[cname]; - let mut ty = String::from("type {"); - for (i, (_n, t)) in fields.iter().enumerate() { - if i > 0 { - ty.push_str(", "); - } - ty.push_str(t.llvm()); - } - if fields.is_empty() { - ty.push_str("i8"); - } - ty.push('}'); - ctx.emit(format!("%struct.{cname} = {ty}")); - } - if !ctx.classes.is_empty() { - ctx.emit(""); - } - - // 收集方法签名 - collect_methods(prog, &mut ctx); - - // 编译 Main 流方法(exec 之外的方法作为普通函数) - if let Some(m) = &prog.main { - for method in &m.methods { - if method.name != "exec" { - let fname = format!("@main_{}", method.name); - compile_function(&mut ctx, &fname, method, false)?; - } - } - } - // 编译 fork/class 方法(@<流名>_<方法名>,类方法首参为 this 指针) - for d in &prog.decls { - if let (Decl::Class { name, members, .. } | Decl::Fork { name, members, .. }) = d { - for mem in members { - if let Member::Method(m2) = mem { - let fname = format!("@{}_{}", name, m2.name); - let is_class = ctx.classes.contains_key(name); - compile_function(&mut ctx, &fname, m2, is_class)?; - } - } - } - } - // Main::exec → @__bio_main - if let Some(m) = &prog.main { - if let Some(exec) = m.methods.iter().find(|x| x.name == "exec") { - let mut e2 = exec.clone(); - e2.name = "__bio_main".into(); - e2.ret = "void".into(); - compile_function(&mut ctx, "@__bio_main", &e2, false)?; - } - } - // 字符串常量(函数外) - if !ctx.str_list.is_empty() { - ctx.out.push_str(&ctx.str_list); - ctx.out.push('\n'); - } - // C main 包装 - ctx.emit("define i32 @main(i32 %argc, ptr %argv) {"); - ctx.emit("entry:"); - ctx.emit(" call void @__bio_main()"); - ctx.emit(" ret i32 0"); - ctx.emit("}"); - Ok(ctx.out) -} - -fn collect_methods(prog: &Program, ctx: &mut Ctx) { - let mut methods: Vec<(String, Ty, Vec<(String, Ty)>)> = Vec::new(); - if let Some(m) = &prog.main { - for m2 in &m.methods { - if m2.name != "exec" { - let (rt, params) = sig_of(m2); - methods.push((format!("main_{}", m2.name), rt, params)); - } - } - } - for d in &prog.decls { - if let (Decl::Class { name, members, .. } | Decl::Fork { name, members, .. }) = d { - let is_class = ctx.classes.contains_key(name); - for mem in members { - if let Member::Method(m2) = mem { - let (rt, mut params) = sig_of(m2); - if is_class { - // 类方法:首参 this 指针 - params.insert(0, ("this".into(), Ty::Ptr)); - } - methods.push((format!("{}_{}", name, m2.name), rt, params)); - } - } - } - } - for (n, rt, params) in methods { - ctx.funcs.insert(n.clone(), (rt, params)); - let _ = &n; - } - // void 方法登记(从原始方法表再扫一遍) - for d in &prog.decls { - if let (Decl::Class { name, members, .. } | Decl::Fork { name, members, .. }) = d { - for mem in members { - if let Member::Method(m2) = mem { - if m2.ret == "void" { - ctx.void_methods.insert(format!("{}_{}", name, m2.name)); - } - } - } - } - } - if let Some(m) = &prog.main { - for m2 in &m.methods { - if m2.ret == "void" && m2.name != "exec" { - ctx.void_methods.insert(format!("main_{}", m2.name)); - } - } - } -} - -fn decl_name(d: &Decl) -> String { - match d { - Decl::Class { name, .. } => name.clone(), - _ => "s".into(), - } -} - -/// 收集类字段表:类名 → [(字段名, 类型)](new/this:: 用)。 -fn collect_classes(prog: &Program, ctx: &mut Ctx) { - for d in &prog.decls { - if let Decl::Class { name, members, .. } = d { - let mut fields = Vec::new(); - for mem in members { - if let Member::Field { ty, names } = mem { - for n in names { - fields.push((n.clone(), ty_of(ty))); - } - } - } - ctx.classes.insert(name.clone(), fields); - } - } -} - -fn sig_of(m: &Method) -> (Ty, Vec<(String, Ty)>) { - let rt = ty_of(&m.ret); - let params = m - .params - .iter() - .map(|p| (p.name.clone(), ty_of(&p.ty))) - .collect(); - (rt, params) -} - -fn ty_of(t: &str) -> Ty { - match t { - "float" | "double" => Ty::F64, - "int" | "char" | "bool" => Ty::I64, - "void" => Ty::I64, // void 方法:返回 i64 0(调用方忽略) - _ => Ty::Ptr, // 类名/流名/对象 → 指针 - } -} - -fn compile_function(ctx: &mut Ctx, fname: &str, method: &Method, is_class: bool) -> Result<(), String> { - // 类方法:记录当前类名(this:: 属性访问) - let saved_class = ctx.current_class.clone(); - if is_class { - if let Some(cname) = fname.strip_prefix('@').and_then(|f| f.split('_').next()) { - ctx.current_class = Some(cname.to_string()); - } - } - let r = compile_function_inner(ctx, fname, method, is_class); - ctx.current_class = saved_class; - r -} - -fn compile_function_inner(ctx: &mut Ctx, fname: &str, method: &Method, is_class: bool) -> Result<(), String> { - let (rt, params) = sig_of(method); - ctx.current_ret = rt; - let mut sig = format!("define {} {fname}(", rt.llvm()); - let mut body_sig = Vec::new(); - let mut first = true; - // 类方法:首参为 this 指针(隐含) - if is_class { - sig.push_str("ptr %this"); - first = false; - } - for (i, (n, t)) in params.iter().enumerate() { - if !first { - sig.push_str(", "); - } - first = false; - let arg = format!("%{}", n); - sig.push_str(&format!("{} {arg}", t.llvm())); - body_sig.push((n.clone(), *t, arg)); - } - sig.push(')'); - ctx.emit(sig); - ctx.emit("{"); - ctx.emit("entry:"); - // 参数 alloc + store - ctx.vars.push(HashMap::new()); - if is_class { - let alloca = ctx.reg("this_"); - ctx.emit(format!(" {alloca} = alloca ptr")); - ctx.emit(format!(" store ptr %this, ptr {alloca}")); - ctx.this_reg = Some(alloca.clone()); - } - for (n, t, arg) in body_sig { - let alloca = ctx.reg(&format!("{}_", n)); - ctx.emit(format!(" {alloca} = alloca {}", t.llvm())); - ctx.emit(format!(" store {} {arg}, ptr {alloca}", t.llvm())); - ctx.var_set(&n, t, alloca); - } - // 语句 - let (flow, ret_reg) = compile_block(ctx, &method.body)?; - if let Some(r) = ret_reg { - ctx.emit(format!(" ret {} {r}", rt.llvm())); - } else if !flow { - ctx.emit(format!(" ret {} {}", rt.llvm(), if rt == Ty::I64 { "0" } else if rt == Ty::F64 { "0.0" } else { "null" })); - } - ctx.emit("}"); - ctx.emit(""); - ctx.vars.pop(); - ctx.this_reg = None; - Ok(()) -} - -/// 语句块编译结果。 -struct BlockOut { - /// 最后一个 res 的寄存器(Some = 函数已 ret 前) - ret: Option, -} - -/// 编译语句块,返回 (是否以终止指令结束, res 寄存器)。 -/// 块内 break/continue/ret 后不再编译后续语句(不可达丢弃,LLVM 合法)。 -fn compile_block(ctx: &mut Ctx, stmts: &[Stmt]) -> Result<(bool, Option), String> { - let mut ret = None; - for st in stmts { - match st { - Stmt::Ret { kind, values } => { - if *kind == RetKind::Ref { - // ref:拒绝 → 打印消息后退出(消息表达式 v2;先直接 exit) - ctx.emit(" call void @exit(i32 1)"); - ctx.emit(" unreachable"); - return Ok((true, None)); - } else if let Some(v) = values.first() { - let (ty, val) = compile_expr(ctx, v)?; - ctx.current_ret = ty; - return Ok((true, Some(val))); - } - } - Stmt::Expr(e) => { - // 调用语句:CIO::println 等 - compile_call_stmt(ctx, e)?; - } - Stmt::Assign { vtype, target, op, value, .. } => { - if let AssignTarget::Var(name) = target { - let (vty, vval) = compile_expr(ctx, value)?; - let _ = op; - match ctx.var_get(name) { - Some((t, reg)) => { - let cast = coerce(ctx, vval, vty, t); - ctx.emit(format!(" store {} {cast}, ptr {reg}", t.llvm())); - } - None => { - let alloca_t = if vtype.as_deref() == Some("ALL") || vtype.is_none() { vty } else { ty_of(vtype.as_deref().unwrap_or("int")) }; - let alloca = ctx.reg(&format!("{}a_", name)); - ctx.emit(format!(" {alloca} = alloca {}", alloca_t.llvm())); - let cast = coerce(ctx, vval, vty, alloca_t); - ctx.emit(format!(" store {} {cast}, ptr {alloca}", alloca_t.llvm())); - ctx.var_set(name, alloca_t, alloca); - // 对象变量:记录类名(属性访问用) - if alloca_t == Ty::Ptr { - if let Some(vt) = vtype { - if !matches!(vt.as_str(), "ALL") && !vt.is_empty() { - ctx.var_ty.insert(name.clone(), vt.clone()); - } - } - } - } - } - } else if let AssignTarget::Prop { base, name } = target { - // 属性赋值:obj.field = v / this.field = v - let (vty, vval) = compile_expr(ctx, value)?; - let (bt, bv) = compile_expr(ctx, base)?; - if bt != Ty::Ptr { - return Err(format!("property assignment on non-object: {name}")); - } - let bval = if let Expr::Var(vn) = base.as_ref() { - if vn == "this" { - match ctx.this_reg.clone() { - Some(reg) => { - let p = ctx.reg("thp"); - ctx.emit(format!(" {p} = load ptr, ptr {reg}")); - p - } - None => bv, - } - } else { - bv - } - } else { - bv - }; - let cls = field_owner(ctx, base); - let (ft, idx) = field_index(ctx, &cls, name)?; - let gp = ctx.reg("gep"); - ctx.emit(format!( - " {gp} = getelementptr %struct.{cls}, ptr {bval}, i32 0, i32 {idx}" - )); - let cast = coerce(ctx, vval, vty, ft); - ctx.emit(format!(" store {} {cast}, ptr {gp}", ft.llvm())); - } - } - Stmt::If { cond, then, els } => { - let (_, cval) = compile_expr(ctx, cond)?; - let then_l = ctx.label("then"); - let else_l = ctx.label("else"); - let end_l = ctx.label("endif"); - let c = ctx.reg("c"); - ctx.emit(format!(" {c} = icmp ne i64 {cval}, 0")); - ctx.emit(format!(" br i1 {c}, label %{then_l}, label %{else_l}")); - ctx.emit(format!("{then_l}:")); - let (t1, r1) = compile_block(ctx, then)?; - if !t1 && r1.is_none() { - ctx.emit(format!(" br label %{end_l}")); - } - ctx.emit(format!("{else_l}:")); - if let Some(e) = els { - let (t2, r2) = compile_block(ctx, e)?; - if !t2 && r2.is_none() { - ctx.emit(format!(" br label %{end_l}")); - } - } else { - ctx.emit(format!(" br label %{end_l}")); - } - ctx.emit(format!("{end_l}:")); - } - Stmt::While { cond, body } => { - let cond_l = ctx.label("wcond"); - let body_l = ctx.label("wbody"); - let end_l = ctx.label("wend"); - ctx.emit(format!(" br label %{cond_l}")); - ctx.emit(format!("{cond_l}:")); - let (_, cval) = compile_expr(ctx, cond)?; - let c = ctx.reg("c"); - ctx.emit(format!(" {c} = icmp ne i64 {cval}, 0")); - ctx.emit(format!(" br i1 {c}, label %{body_l}, label %{end_l}")); - ctx.emit(format!("{body_l}:")); - ctx.loop_end.push(end_l.clone()); - ctx.loop_continue.push(cond_l.clone()); - let (flow, r) = compile_block(ctx, body)?; - ctx.loop_end.pop(); - ctx.loop_continue.pop(); - if !flow { - ctx.emit(format!(" br label %{cond_l}")); - } - if let Some(r) = r { - ret = Some(r); - } - ctx.emit(format!("{end_l}:")); - } - Stmt::For { init, cond, update, body } => { - if let Some(i) = init { - compile_block(ctx, std::slice::from_ref(i))?; - } - let cond_l = ctx.label("fcond"); - let body_l = ctx.label("fbody"); - let upd_l = ctx.label("fupd"); - let end_l = ctx.label("fend"); - ctx.emit(format!(" br label %{cond_l}")); - ctx.emit(format!("{cond_l}:")); - if let Some(c) = cond { - let (_, cval) = compile_expr(ctx, c)?; - let c = ctx.reg("c"); - ctx.emit(format!(" {c} = icmp ne i64 {cval}, 0")); - ctx.emit(format!(" br i1 {c}, label %{body_l}, label %{end_l}")); - } else { - ctx.emit(format!(" br label %{body_l}")); - } - ctx.emit(format!("{body_l}:")); - ctx.loop_end.push(end_l.clone()); - ctx.loop_continue.push(upd_l.clone()); - let (flow, r) = compile_block(ctx, body)?; - ctx.loop_end.pop(); - ctx.loop_continue.pop(); - if let Some(r) = r { - ret = Some(r); - } - if !flow { - ctx.emit(format!(" br label %{upd_l}")); - } - ctx.emit(format!("{upd_l}:")); - if let Some(u) = update { - compile_block(ctx, std::slice::from_ref(u))?; - } - ctx.emit(format!(" br label %{cond_l}")); - ctx.emit(format!("{end_l}:")); - } - Stmt::Break => { - if let Some(e) = ctx.loop_end.last() { - ctx.emit(format!(" br label %{e}")); - return Ok((true, None)); - } - } - Stmt::Continue => { - if let Some(c) = ctx.loop_continue.last() { - ctx.emit(format!(" br label %{c}")); - return Ok((true, None)); - } - } - Stmt::Inc { name, op } => { - if let Some((t, reg)) = ctx.var_get(name) { - let delta = if op == "++" { 1 } else { -1 }; - let t1 = ctx.reg("inc"); - ctx.emit(format!(" {t1} = load {}, ptr {reg}", t.llvm())); - let t2 = ctx.reg("inc"); - ctx.emit(format!(" {t2} = add {} {t1}, {delta}", t.llvm())); - ctx.emit(format!(" store {} {t2}, ptr {reg}", t.llvm())); - } - } - Stmt::RefDecl { .. } => {} - } - } - Ok((false, ret)) -} - -/// 调用语句:CIO::println/print → printf;其他调用(对象方法等)→ 正常 call。 -fn compile_call_stmt(ctx: &mut Ctx, e: &Expr) -> Result<(), String> { - if let Expr::Call { qual, name, args } = e { - if qual.as_deref() == Some("CIO") || qual.as_deref() == Some("IO") { - if name == "println" || name == "print" { - return emit_printf(ctx, args, name == "println"); - } - } - } - // 其他调用:编译(副作用保留) - compile_expr(ctx, e)?; - Ok(()) -} - -fn emit_printf(ctx: &mut Ctx, args: &[Expr], newline: bool) -> Result<(), String> { - // 拼格式串:字符串参数原样(转义 %),数字参数 → %ld / %lf - let mut fmt = String::new(); - let mut call_args: Vec<(Ty, String)> = Vec::new(); - // println 参数空格分隔(print 直接拼接——与解释器一致) - let sep = if newline { " " } else { "" }; - let mut first = true; - for a in args { - if !first { - fmt.push_str(sep); - } - first = false; - match a { - Expr::Str(s) => { - fmt.push_str(&s.replace('%', "%%")); - } - other => { - let (ty, val) = compile_expr(ctx, other)?; - match ty { - Ty::I64 => fmt.push_str("%ld"), - Ty::F64 => fmt.push_str("%lf"), - Ty::Ptr => fmt.push_str("%p"), - } - call_args.push((ty, val)); - } - } - } - if newline { - fmt.push('\n'); // 真换行字节,由 str_const 转义成 \0A - } - let sc = ctx.str_const(&fmt); - let mut call = format!(" call i32 (ptr, ...) @printf(ptr {sc}"); - for (ty, val) in &call_args { - call.push_str(&format!(", {} {}", ty.llvm(), val)); - } - call.push(')'); - ctx.emit(call); - Ok(()) -} - -fn is_void_method(ctx: &Ctx, key: &str) -> bool { - ctx.void_methods.contains(key) -} -fn coerce(ctx: &mut Ctx, val: String, from: Ty, to: Ty) -> String { - if from == to { - return val; - } - let r = ctx.reg("cv"); - match (from, to) { - (Ty::I64, Ty::F64) => ctx.emit(format!(" {r} = sitofp i64 {val} to double")), - (Ty::F64, Ty::I64) => ctx.emit(format!(" {r} = fptosi double {val} to i64")), - _ => return val, - } - r -} - -/// 属性所属类:base 是 this → 当前类;base 是对象变量 → 变量类型对应的类。 -/// 简化:从变量类型名推断(vars 表里对象变量以类名注册)。 -fn field_owner(ctx: &Ctx, base: &Expr) -> String { - if let Expr::Var(vn) = base { - if vn == "this" { - // 当前类:找 this_reg 所在函数——用 classes 里第一个含该字段的类兜底 - // 更准确:compile_function 时记录当前类名 - if let Some(c) = &ctx.current_class { - return c.clone(); - } - } - // 对象变量:vars 存 (Ty, reg),类型信息丢失——用 var_ty 表 - if let Some(t) = ctx.var_ty.get(vn) { - return t.clone(); - } - } - String::new() -} - -fn field_index(ctx: &Ctx, cls: &str, name: &str) -> Result<(Ty, u32), String> { - let fields = ctx.classes.get(cls).ok_or_else(|| { - format!("property {name} on unknown class {cls}") - })?; - for (i, (n, t)) in fields.iter().enumerate() { - if n == name { - return Ok((*t, i as u32)); - } - } - Err(format!("class {cls} has no field {name}")) -} - -fn compile_expr(ctx: &mut Ctx, e: &Expr) -> Result<(Ty, String), String> { - match e { - Expr::Int(v) => Ok((Ty::I64, v.to_string())), - Expr::Float(v) => Ok((Ty::F64, format!("{v:.17}"))), - Expr::Var(name) => { - if name == "this" { - // this 指针 - match ctx.this_reg.clone() { - Some(reg) => { - let p = ctx.reg("thisv"); - ctx.emit(format!(" {p} = load ptr, ptr {reg}")); - return Ok((Ty::Ptr, p)); - } - None => return Err("this used outside a class method".to_string()), - } - } - match ctx.var_get(name) { - Some((t, reg)) => { - let r = ctx.reg("v"); - ctx.emit(format!(" {r} = load {}, ptr {reg}", t.llvm())); - Ok((t, r)) - } - None => Err(format!("undefined variable {name}")), - } - } - Expr::Unwrap { op, l } => { - let (t, v) = compile_expr(ctx, l)?; - let _ = op; - Ok((t, v)) // get/cause 单值语义 - } - Expr::BinOp { op, l, r } => { - let (lt, lv) = compile_expr(ctx, l)?; - let (rt, rv) = compile_expr(ctx, r)?; - let ty = if lt == Ty::F64 || rt == Ty::F64 { Ty::F64 } else { Ty::I64 }; - let lv = coerce(ctx, lv, lt, ty); - let rv = coerce(ctx, rv, rt, ty); - let out = ctx.reg("b"); - match op.as_str() { - "+" | "-" | "*" | "/" | "%" => { - let opc = match (op.as_str(), ty) { - ("+", Ty::I64) => "add", ("+", Ty::F64) => "fadd", - ("-", Ty::I64) => "sub", ("-", Ty::F64) => "fsub", - ("*", Ty::I64) => "mul", ("*", Ty::F64) => "fmul", - ("/", Ty::I64) => "sdiv", ("/", Ty::F64) => "fdiv", - ("%", Ty::I64) => "srem", ("%", Ty::F64) => "frem", - _ => "add", - }; - ctx.emit(format!(" {out} = {opc} {} {lv}, {rv}", ty.llvm())); - Ok((ty, out)) - } - "==" | "!=" | "<" | ">" | "<=" | ">=" => { - let cond_op = match op.as_str() { - "==" => "eq", "!=" => "ne", "<" => "slt", ">" => "sgt", - "<=" => "sle", ">=" => "sge", _ => "eq", - }; - let cmp_op = if ty == Ty::F64 { - match cond_op { - "eq" => "oeq", "ne" => "one", "slt" => "olt", "sgt" => "ogt", - "sle" => "ole", "sge" => "oge", _ => "oeq", - } - } else { - cond_op - }; - if ty == Ty::F64 { - let t1 = ctx.reg("cmp"); - ctx.emit(format!(" {t1} = fcmp {cmp_op} double {lv}, {rv}")); - ctx.emit(format!(" {out} = zext i1 {t1} to i64")); - } else { - let t1 = ctx.reg("cmp"); - ctx.emit(format!(" {t1} = icmp {cmp_op} i64 {lv}, {rv}")); - ctx.emit(format!(" {out} = zext i1 {t1} to i64")); - } - Ok((Ty::I64, out)) - } - _ => Err(format!("unsupported operator {op}")), - } - } - Expr::Call { qual, name, args } => { - // 对象方法调用:qual 是对象变量 → 类名_方法名 + 传 this - let obj_this = if let Some(q) = qual { - ctx.var_ty.get(q).cloned() - } else { - None - }; - let key = if let Some(owner) = &obj_this { - format!("{owner}_{name}") - } else if let Some(q) = qual { - format!("{q}_{name}") - } else if ctx.funcs.contains_key(name) { - name.clone() - } else { - format!("main_{name}") - }; - let fname = format!("@{key}"); - let (rt, _) = ctx - .funcs - .get(&key) - .cloned() - .unwrap_or((Ty::I64, vec![])); - if !ctx.funcs.contains_key(&key) { - // 未知方法:拒绝(printf 消息 + exit)——后端子集边界 - let msg = ctx.str_const(&format!("stream {key} refuses: no method {name}\n")); - ctx.emit(format!(" call i32 (ptr, ...) @printf(ptr {msg})")); - ctx.emit(" call void @exit(i32 1)"); - ctx.emit(" unreachable"); - return Ok((rt, "0".into())); - } - let call_reg = ctx.reg("call"); - let is_void = rt == Ty::I64 && is_void_method(ctx, &key); - let mut call = if is_void { - format!(" call void {fname}(") - } else { - format!(" {call_reg} = call {} {fname}(", rt.llvm()) - }; - let mut first = true; - // 对象方法调用:qual 是对象变量 → 传 this 指针 - if obj_this.is_some() { - if let Some(q) = qual { - if let Some((Ty::Ptr, reg)) = ctx.var_get(q) { - let p = ctx.reg("thisp"); - ctx.emit(format!(" {p} = load ptr, ptr {reg}")); - call.push_str(&format!("ptr {p}")); - first = false; - } - } - } - for a in args { - let (at, av) = compile_expr(ctx, a)?; - if !first { - call.push_str(", "); - } - first = false; - call.push_str(&format!("{} {av}", at.llvm())); - } - call.push(')'); - ctx.emit(call); - let _ = qual; - if is_void { - Ok((Ty::I64, "0".into())) - } else { - Ok((rt, call_reg)) - } - } - Expr::New { cls, args } => { - // new Class(args...) → malloc 对象 + 调 __init__(this, args...) - let fields = ctx - .classes - .get(cls) - .cloned() - .unwrap_or_default(); - let size = fields.len().max(1) * 8; - let obj = ctx.reg("obj"); - ctx.emit(format!(" {obj} = call ptr @malloc(i64 {size})")); - // 调 __init__(若存在) - let init_key = format!("{cls}___init__"); - if ctx.funcs.contains_key(&init_key) { - let call_reg = ctx.reg("init"); - let mut call = format!(" call void @{init_key}(ptr {obj}"); - for a in args { - let (at, av) = compile_expr(ctx, a)?; - call.push_str(&format!(", {} {av}", at.llvm())); - } - call.push(')'); - ctx.emit(call); - let _ = call_reg; - } - Ok((Ty::Ptr, obj)) - } - Expr::Prop { base, name } => { - // 对象属性读:obj.name / this.name → GEP load - let (bt, bv) = compile_expr(ctx, base)?; - if bt != Ty::Ptr { - return Err(format!("property access on non-object: {name}")); - } - // 从 this 指针寄存器取值(若 base 是 this 变量) - let bval = if let Expr::Var(vn) = base.as_ref() { - if vn == "this" { - match ctx.this_reg.clone() { - Some(reg) => { - let p = ctx.reg("thp"); - ctx.emit(format!(" {p} = load ptr, ptr {reg}")); - p - } - None => bv, - } - } else { - bv - } - } else { - bv - }; - // 找字段类型 - let cls = field_owner(ctx, base); - let (ft, idx) = field_index(ctx, &cls, name)?; - let gp = ctx.reg("gep"); - ctx.emit(format!( - " {gp} = getelementptr %struct.{cls}, ptr {bval}, i32 0, i32 {idx}" - )); - let v = ctx.reg("fld"); - ctx.emit(format!(" {v} = load {}, ptr {gp}", ft.llvm())); - Ok((ft, v)) - } - _ => Err(format!("LLVM backend does not support this expression yet: {e:?}")), - } -} - -// 占位(保持模块结构) diff --git a/rust/crates/bbb-llvm/tests/objects.rs b/rust/crates/bbb-llvm/tests/objects.rs deleted file mode 100644 index 665f469..0000000 --- a/rust/crates/bbb-llvm/tests/objects.rs +++ /dev/null @@ -1,91 +0,0 @@ -//! bbb-llvm 集成测试:编译 → clang → 运行 → 输出断言。 -//! 需要系统 clang(与 CLI shell build 相同路径)。 - -use std::process::Command; - -use bbb_llvm::compile; -use bbb_syntax::parser::parse_source; - -fn build_and_run(src: &str) -> String { - let (prog, errs) = parse_source(src); - assert!(errs.is_empty(), "parse errors: {errs:?}"); - let ir = compile(&prog).expect("IR generation failed"); - - // 目录含进程+时间戳,避免并行测试冲突 - let nanos = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.subsec_nanos()) - .unwrap_or(0); - let dir = std::env::temp_dir().join(format!("bbb-llvm-test-{}-{nanos}", std::process::id())); - std::fs::create_dir_all(&dir).unwrap(); - let ir_path = dir.join("out.ll"); - let bin_path = dir.join("a.out"); - std::fs::write(&ir_path, &ir).unwrap(); - - let status = Command::new("clang") - .arg(&ir_path) - .arg("-o") - .arg(&bin_path) - .status() - .expect("clang not found"); - assert!(status.success(), "clang failed"); - - let out = Command::new(&bin_path).output().expect("run failed"); - let _ = std::fs::remove_dir_all(&dir); - String::from_utf8_lossy(&out.stdout).to_string() -} - -#[test] -fn llvm_class_new_fields_and_methods() { - let src = r#" -program main; -Class Student { - void __init__(age int, solve double) { - this::age = age; - this::solve = solve; - } - int getAge() { res this::age; } - double getSolve() { res this::solve; } - void bump() { this::age = this::age + 1; } - int age; - double solve; -} -Main { - void exec() { - Student s = new Student(12, 1.2); - CIO::println("age:", get s::getAge()); - CIO::println("solve:", get s::getSolve()); - s::bump(); - CIO::println("after bump:", get s::getAge()); - s::age = 20; - CIO::println("direct:", s::age); - } -} -"#; - let out = build_and_run(src); - assert!(out.contains("age: 12"), "got: {out}"); - assert!(out.contains("solve: 1.200000"), "got: {out}"); - assert!(out.contains("after bump: 13"), "got: {out}"); - assert!(out.contains("direct: 20"), "got: {out}"); -} - -#[test] -fn llvm_class_method_call_with_args() { - let src = r#" -program main; -Class Calc { - int add(a int, b int) { res a + b; } - int mul(a int, b int) { res a * b; } -} -Main { - void exec() { - Calc c = new Calc(); - CIO::println("sum:", get c::add(2, 3)); - CIO::println("prod:", get c::mul(4, 5)); - } -} -"#; - let out = build_and_run(src); - assert!(out.contains("sum: 5"), "got: {out}"); - assert!(out.contains("prod: 20"), "got: {out}"); -} diff --git a/rust/crates/bbb-syntax/Cargo.toml b/rust/crates/bbb-syntax/Cargo.toml deleted file mode 100644 index f70e648..0000000 --- a/rust/crates/bbb-syntax/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "bbb-syntax" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "BiuBiuBiu 语法层:手写 AST + 词法器 + 解析器" - -[dependencies] diff --git a/rust/crates/bbb-syntax/src/ast.rs b/rust/crates/bbb-syntax/src/ast.rs deleted file mode 100644 index cdda54c..0000000 --- a/rust/crates/bbb-syntax/src/ast.rs +++ /dev/null @@ -1,132 +0,0 @@ -//! BiuBiuBiu AST(完全原生手写版)。 -//! -//! 完全原生手写。设计贴合旧 C 实现(src/parser.c)的节点形态,并做了枚举化 -//! 与所有权整理: -//! - 表达式/语句/声明全枚举,无 NULL 指针(Option 表达可选); -//! - 字符串用 `String`(解析期零拷贝优化留给后续:arena 字符串池); -//! - 语法面覆盖 examples/01-17:流签名/分叉/类/need/注解/智能引用/ -//! 数组字面量/多返回值/二进制库流。 - -/// 程序 = 声明序列(Main 流单独存放,语义上总在最后执行)。 -#[derive(Debug, Clone, PartialEq)] -pub struct Program { - pub kind: String, // "main" | "utils" - pub decls: Vec, - pub main: Option, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Decl { - /// `const int x = 10;`(顶层 → Constantstream) - Const { name: String, ty: String, init: Expr }, - /// `need value/function/stream/Class X;` - Need { kind: String, name: String }, - /// `Stream Name { members }` — 签名流 - StreamSig { name: String, members: Vec, annos: Vec }, - /// `Stream Name & "lib.so" { members }` — 二进制库流 - StreamBin { name: String, file: String, members: Vec, annos: Vec }, - /// `Class Name implements A, B { members }` — 类(流的分叉),可实现接口 - Class { name: String, members: Vec, annos: Vec, implements: Vec }, - /// `Interface Name { 方法签名 }` — 接口(流的一种:只有签名的方法集合) - Interface { name: String, members: Vec, annos: Vec }, - /// `Sig Name { members }` — 分叉实现 - Fork { sig: String, name: String, members: Vec, annos: Vec }, -} - -/// Main 流:`Main { void exec() {...} ... }`,仅方法。 -#[derive(Debug, Clone, PartialEq)] -pub struct MainDecl { - pub methods: Vec, -} - -/// 流/类成员:字段与方法可任意交错;字段支持逗号分隔 `int x, y;`。 -#[derive(Debug, Clone, PartialEq)] -pub enum Member { - Field { ty: String, names: Vec }, - Method(Method), -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Method { - pub ret: String, // void / int / int[] / Hero / T[]... - pub name: String, - pub params: Vec, - pub body: Vec, // 空 = 签名(分号结尾) - pub annos: Vec, // @read/@write/@call/@ucall -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Param { - pub name: String, - pub ty: String, // 基类型(含流名/类名) - pub is_arr: bool, - /// 智能引用参数:`& name type` - pub ref_perm: Option, - pub ref_follow: Option, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Stmt { - If { cond: Expr, then: Vec, els: Option> }, - While { cond: Expr, body: Vec }, - For { init: Option>, cond: Option, update: Option>, body: Vec }, - Break, - Continue, - /// `res expr;` / `res a, b, c;`(多值 → 数组)/ `ref "reason";` - Ret { kind: RetKind, values: Vec }, - /// 变量声明与赋值:`int x = e;` / `ALL x = e;` / `x = e;` / `x += e;` / - /// `const int x = e;` / `thread int x = e;` / `this::attr = e;` / `a[i] = e;` - Assign { - vtype: Option, // Some = 声明(含 "ALL"/"const"/"thread" 变体) - is_const: bool, - is_thread: bool, - target: AssignTarget, - op: String, - value: Expr, - }, - /// `&perm follow base name = &lvalue;` — 智能引用声明 - RefDecl { perm: String, follow: String, base: String, name: String, init: Expr }, - /// `i++;` / `i--;` - Inc { name: String, op: String }, - /// 表达式语句:裸调用 `add(1,2);`、`a[i];` 等 - Expr(Expr), -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum RetKind { - Res, // res = respond - Ref, // ref = refuse -} - -#[derive(Debug, Clone, PartialEq)] -pub enum AssignTarget { - Var(String), - Index { base: Box, idx: Box }, - Prop { base: Box, name: String }, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Expr { - Int(i64), - Float(f64), - Str(String), - Char(u8), - Bool(bool), - /// 变量引用(含 `this`) - Var(String), - /// `qual::name(args)`(qual=Some)或裸调用 `name(args)`(qual=None) - Call { qual: Option, name: String, args: Vec }, - /// `obj.field` 属性访问(对象属性用 Objstream) - Prop { base: Box, name: String }, - /// `a[i]` 索引 - Index { base: Box, idx: Box }, - BinOp { op: String, l: Box, r: Box }, - /// 前缀解包:`get X` / `cause X` - Unwrap { op: String, l: Box }, - /// `new Class(args...)` → 分叉类流 + 自动 __init__ - New { cls: String, args: Vec }, - /// `new Type[expr]` → 数组字面量 - NewArray { ty: String, size: Box }, - /// `&lvalue` — 取址创建引用值(权限来自声明) - RefOf(Box), -} diff --git a/rust/crates/bbb-syntax/src/lexer.rs b/rust/crates/bbb-syntax/src/lexer.rs deleted file mode 100644 index 28c1b9f..0000000 --- a/rust/crates/bbb-syntax/src/lexer.rs +++ /dev/null @@ -1,335 +0,0 @@ -//! BiuBiuBiu 词法器(手写,Rust 版)。 -//! -//! 规则对标旧 C 实现(src/lexer.c)+ examples/ 实际语法: -//! - 关键字:program / Main / Stream / Class / const / thread / need / -//! res / ref / get / cause / ALL / if / else / while / for / break / -//! continue / new / this / 基础类型 / true / false -//! - 注释:`//` 与 `/* */`;字符串 `"..."`(`\"` 转义);字符 `'x'` -//! - 数字:int / float(`3.14`、`.5`、`1e3`) -//! - 运算符:`::` `==` `!=` `<=` `>=` `&&` `||` `++` `--` `->` + 单字符集 -//! -//! 设计目标(内存规划):Token 零堆分配——`kind: u8` + `len: u32` 引用 -//! 源切片,字符串内容不拷贝;行/列只在出错时按需计算(错误路径才扫描)。 - -/// 词法错误:源位置 + 信息。 -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct LexError { - pub line: u32, - pub col: u32, - pub msg: &'static str, -} - -impl std::fmt::Display for LexError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}:{}: {}", self.line, self.col, self.msg) - } -} - -/// 源位置(行:列,1 起)。 -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Span { - pub line: u32, - pub col: u32, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] -pub enum TokenKind { - Ident, - Keyword, - Int, - Float, - Str, - Char, - Op, - Eof, -} - -/// Token:零拷贝——`text` 是源字符串的切片。 -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Token<'a> { - pub kind: TokenKind, - pub text: &'a str, - pub span: Span, -} - -const KEYWORDS: &[&str] = &[ - "program", "Main", "Stream", "Class", "Interface", "implements", "const", "thread", "need", - "res", "ref", "get", "cause", "ALL", "if", "else", "while", "for", - "break", "continue", "new", "this", - "void", "int", "float", "double", "string", "char", "bool", - "true", "false", -]; - -const OPS2: &[&str] = &["==", "!=", "<=", ">=", "&&", "||", "::", "++", "--", "->"]; - -fn is_kw(s: &str) -> bool { - KEYWORDS.contains(&s) // 线性查找:29 个关键字,正确性优先 -} - -fn is_ident_start(c: u8) -> bool { - c.is_ascii_alphabetic() || c == b'_' -} - -fn is_ident_char(c: u8) -> bool { - c.is_ascii_alphanumeric() || c == b'_' -} - -fn is_op1(c: u8) -> bool { - matches!(c, b'+' | b'-' | b'*' | b'/' | b'%' | b'<' | b'>' | b'=' | b'!' - | b'&' | b'|' | b'.' | b':' | b',' | b';' | b'(' | b')' - | b'{' | b'}' | b'[' | b']' | b'@') -} - -fn is_digit(c: u8) -> bool { - c.is_ascii_digit() -} - -struct Scan<'a> { - src: &'a [u8], - pos: usize, - line: u32, - col: u32, -} - -impl<'a> Scan<'a> { - fn peek(&self, k: usize) -> Option { - self.src.get(self.pos + k).copied() - } - - fn advance(&mut self) -> Option { - let c = self.src.get(self.pos).copied(); - if let Some(b) = c { - self.pos += 1; - if b == b'\n' { - self.line += 1; - self.col = 1; - } else { - self.col += 1; - } - } - c - } - - fn span(&self) -> Span { - Span { line: self.line, col: self.col } - } -} - -/// 把源码切成 Token 流。`tokens` 预先分配(容量即上限),零堆分配。 -pub fn tokenize<'a>(src: &'a str, tokens: &mut Vec>) -> Result<(), LexError> { - let mut s = Scan { src: src.as_bytes(), pos: 0, line: 1, col: 1 }; - tokens.clear(); - loop { - // 空白 - while matches!(s.peek(0), Some(b' ') | Some(b'\t') | Some(b'\r') | Some(b'\n')) { - s.advance(); - } - // 注释 - if s.peek(0) == Some(b'/') && s.peek(1) == Some(b'/') { - while let Some(c) = s.advance() { - if c == b'\n' { - break; - } - } - continue; - } - if s.peek(0) == Some(b'/') && s.peek(1) == Some(b'*') { - let start = s.span(); - s.advance(); - s.advance(); - loop { - if s.peek(0).is_none() { - return Err(LexError { line: start.line, col: start.col, msg: "unterminated block comment /*" }); - } - if s.peek(0) == Some(b'*') && s.peek(1) == Some(b'/') { - s.advance(); - s.advance(); - break; - } - s.advance(); - } - continue; - } - let start = s.span(); - let c = match s.peek(0) { - None => { - tokens.push(Token { kind: TokenKind::Eof, text: "", span: start }); - return Ok(()); - } - Some(c) => c, - }; - - // 字符串 - if c == b'"' { - s.advance(); - let begin = s.pos; - loop { - match s.peek(0) { - None => return Err(LexError { line: start.line, col: start.col, msg: "unterminated string literal" }), - Some(b'"') => { - let end = s.pos; - s.advance(); - tokens.push(Token { kind: TokenKind::Str, text: &src[begin..end], span: start }); - break; - } - Some(b'\\') => { - s.advance(); - s.advance(); - } - Some(_) => { - s.advance(); - } - } - } - continue; - } - - // 字符 - if c == b'\'' { - s.advance(); - let begin = s.pos; - match s.peek(0) { - None => return Err(LexError { line: start.line, col: start.col, msg: "unterminated character literal" }), - Some(b'\\') => { - s.advance(); - s.advance(); - } - Some(_) => { - s.advance(); - } - } - if s.peek(0) != Some(b'\'') { - return Err(LexError { line: start.line, col: start.col, msg: "character literal must be exactly one character" }); - } - s.advance(); - tokens.push(Token { kind: TokenKind::Char, text: &src[begin..s.pos - 1], span: start }); - continue; - } - - // 数字 - if is_digit(c) || (c == b'.' && s.peek(1).map(is_digit).unwrap_or(false)) { - let begin = s.pos; - let mut is_float = false; - while let Some(d) = s.peek(0) { - if is_digit(d) { - s.advance(); - } else if d == b'.' && !is_float { - is_float = true; - s.advance(); - } else if (d == b'e' || d == b'E') && !is_float { - is_float = true; - s.advance(); - if matches!(s.peek(0), Some(b'+') | Some(b'-')) { - s.advance(); - } - } else { - break; - } - } - let kind = if is_float { TokenKind::Float } else { TokenKind::Int }; - tokens.push(Token { kind, text: &src[begin..s.pos], span: start }); - continue; - } - - // 标识符 / 关键字 - if is_ident_start(c) { - let begin = s.pos; - while s.peek(0).map(is_ident_char).unwrap_or(false) { - s.advance(); - } - let word = &src[begin..s.pos]; - let kind = if is_kw(word) { TokenKind::Keyword } else { TokenKind::Ident }; - tokens.push(Token { kind, text: word, span: start }); - continue; - } - - // 运算符 - let mut matched = false; - for op in OPS2 { - if s.src[s.pos..].starts_with(op.as_bytes()) { - s.advance(); - s.advance(); - tokens.push(Token { kind: TokenKind::Op, text: op, span: start }); - matched = true; - break; - } - } - if matched { - continue; - } - if is_op1(c) { - s.advance(); - tokens.push(Token { kind: TokenKind::Op, text: &src[s.pos - 1..s.pos], span: start }); - continue; - } - - return Err(LexError { - line: start.line, - col: start.col, - msg: "unrecognized character", - }); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn kinds(src: &str) -> Vec<(TokenKind, &str)> { - let mut toks = Vec::new(); - tokenize(src, &mut toks).unwrap(); - toks.iter().map(|t| (t.kind, t.text)).collect() - } - - #[test] - fn hello() { - let toks = kinds(r#"program main; Main { void exec() { CIO::println("hi"); } }"#); - assert_eq!(toks[0], (TokenKind::Keyword, "program")); - assert!(toks.contains(&(TokenKind::Keyword, "Main"))); - assert!(toks.contains(&(TokenKind::Keyword, "void"))); - assert!(toks.contains(&(TokenKind::Str, "hi"))); - assert_eq!(toks.last().unwrap().0, TokenKind::Eof); - } - - #[test] - fn numbers() { - let toks = kinds("1 3.14 .5 1e3"); - assert_eq!(toks[0], (TokenKind::Int, "1")); - assert_eq!(toks[1], (TokenKind::Float, "3.14")); - assert_eq!(toks[2], (TokenKind::Float, ".5")); - assert_eq!(toks[3], (TokenKind::Float, "1e3")); - } - - #[test] - fn ops() { - let toks = kinds("a::b == c && d <= e;"); - let ops: Vec<&str> = toks.iter().filter(|t| t.0 == TokenKind::Op).map(|t| t.1).collect(); - assert_eq!(ops, vec!["::", "==", "&&", "<=", ";"]); - } - - #[test] - fn comment_and_string() { - let src = r#"// line -CIO::println("a\"b"); /* block -comment */ x"#; - let toks = kinds(src); - assert!(toks.contains(&(TokenKind::Str, "a\\\"b"))); - assert!(toks.contains(&(TokenKind::Ident, "x"))); - } - - #[test] - fn unclosed_string_err() { - let mut toks = Vec::new(); - let err = tokenize("CIO::println(\"oops);", &mut toks).unwrap_err(); - assert_eq!(err.msg, "unterminated string literal"); - } - - #[test] - fn line_col() { - let mut toks = Vec::new(); - let err = tokenize("a = 1;\nb = \"x;\n", &mut toks).unwrap_err(); - assert_eq!(err.line, 2); - assert_eq!(err.col, 5); // `b = "` — 引号在第 5 列 - } -} diff --git a/rust/crates/bbb-syntax/src/lib.rs b/rust/crates/bbb-syntax/src/lib.rs deleted file mode 100644 index ad480b3..0000000 --- a/rust/crates/bbb-syntax/src/lib.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! bbb-syntax — BiuBiuBiu 语法层(完全原生手写)。 -//! -//! - `ast`:手写 AST(唯一事实源) -//! - `parser`:手写解析器(语法面覆盖 examples/01-17) -//! - `lexer`:手写词法器(零拷贝 token) - -pub mod ast; -pub mod lexer; -pub mod parser; - -pub use ast::*; -pub use lexer::{LexError, Span, Token, TokenKind, tokenize}; -pub use parser::{ParseError, Parser, parse_source}; diff --git a/rust/crates/bbb-syntax/src/parser.rs b/rust/crates/bbb-syntax/src/parser.rs deleted file mode 100644 index ee07515..0000000 --- a/rust/crates/bbb-syntax/src/parser.rs +++ /dev/null @@ -1,1244 +0,0 @@ -//! BioLang 解析器(完全原生手写)。 -//! -//! 语法面以旧 C 实现(src/parser.c)与 examples/01-17 为准: -//! - 表达式:优先级 字面量/引用/调用/索引/属性链 → 一元(get/cause/&/new) -//! → 算术(+ - * / %) → 比较(== != < > <= >=) → 逻辑(&& ||) -//! - 语句:if/while/for/break/continue/res/ref/ALL/const/thread/ -//! &引用声明/类型声明/赋值/自增自减/调用/表达式语句 -//! - 声明:program/const/need/Stream(签名|二进制库)/Class/Main/分叉 -//! - 成员:方法(体或签名)+ 字段(逗号分隔)+ type T; 泛型 + 泛型风格 -//! - 参数:`name type`(名字在前),可带 `&perm follow` 引用修饰 -//! -//! 错误处理:收集全部错误(ParseError 带行列),恢复策略为推进 token, -//! 保证不死循环;返回的 Program 在 errors 非空时不可信(调用方检查)。 - -use crate::ast::*; -use crate::lexer::{Token, TokenKind}; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ParseError { - pub line: u32, - pub col: u32, - pub msg: String, -} - -impl std::fmt::Display for ParseError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}:{}: {}", self.line, self.col, self.msg) - } -} - -const TYPE_NAMES: &[&str] = &["int", "float", "double", "string", "char", "bool"]; -const PERMS: &[&str] = &["r", "w", "m", "rw", "rm", "wm", "rwm"]; -const FOLLOWS: &[&str] = &["u", "f", "a", "t"]; -const ASSIGN_OPS: &[&str] = &["=", "+=", "-=", "*=", "/=", "%="]; -const METHOD_ANNOS: &[&str] = &["read", "write", "call", "ucall"]; -const DECL_ANNOS: &[&str] = &["onlyread", "unfork"]; - -fn is_type_name(s: &str) -> bool { - TYPE_NAMES.contains(&s) -} - -pub struct Parser<'a> { - toks: &'a [Token<'a>], - pos: usize, - pub errors: Vec, -} - -impl<'a> Parser<'a> { - pub fn new(toks: &'a [Token<'a>]) -> Self { - Parser { toks, pos: 0, errors: Vec::new() } - } - - // ---- 游标 ---- - - fn peek(&self, k: usize) -> &'a Token<'a> { - let i = (self.pos + k).min(self.toks.len().saturating_sub(1)); - &self.toks[i] - } - - fn next(&mut self) -> &'a Token<'a> { - let t = self.peek(0); - if self.pos < self.toks.len().saturating_sub(1) { - self.pos += 1; - } - t - } - - fn at_eof(&self) -> bool { - self.peek(0).kind == TokenKind::Eof - } - - fn is_op(&self, k: usize, op: &str) -> bool { - let t = self.peek(k); - t.kind == TokenKind::Op && t.text == op - } - - fn at_kw(&self, kw: &str) -> bool { - let t = self.peek(0); - t.kind == TokenKind::Keyword && t.text == kw - } - - fn eat_op(&mut self, op: &str) -> bool { - if self.is_op(0, op) { - self.next(); - true - } else { - false - } - } - - fn error(&mut self, msg: impl Into) { - let t = self.peek(0); - self.errors.push(ParseError { line: t.span.line, col: t.span.col, msg: msg.into() }); - } - - fn expect_op(&mut self, op: &str) -> bool { - if self.eat_op(op) { - true - } else { - self.error(format!("expected '{0}', got '{1}'", op, self.peek(0).text)); - false - } - } - - fn expect_id(&mut self) -> String { - let t = self.peek(0); - if t.kind == TokenKind::Ident || (t.kind == TokenKind::Keyword && t.text != "program") { - self.next(); - t.text.to_string() - } else { - self.error(format!("expected identifier, got '{0}'", t.text)); - String::new() - } - } - - /// 方法名:关键字 `new` 允许(如 Array::new / Obj::new)。 - fn expect_method_name(&mut self) -> String { - if self.at_kw("new") { - self.next(); - return "new".to_string(); - } - self.expect_id() - } - - fn err_if(&mut self, cond: bool, msg: impl Into) { - if cond { - self.error(msg); - } - } - - // ---- 顶层 ---- - - pub fn parse_program(&mut self) -> Program { - let mut kind = String::new(); - let mut decls = Vec::new(); - let mut main = None; - - while !self.at_eof() && self.errors.len() < 50 { - if self.at_kw("program") { - self.next(); - let k = self.expect_id(); - self.expect_op(";"); - kind = k; - continue; - } - if self.at_kw("const") { - self.next(); - let ty = self.expect_id(); // 类型(int/string/...) - let name = self.expect_id(); - let init = if self.eat_op("=") { self.parse_expr() } else { Expr::Int(0) }; - self.expect_op(";"); - decls.push(Decl::Const { name, ty, init }); - continue; - } - if self.at_kw("need") { - self.next(); - let k = self.next().text.to_string(); - let name = self.expect_id(); - if self.is_op(0, "{") { - self.skip_block(); - self.eat_op(";"); - } else { - self.expect_op(";"); - } - self.err_if(!matches!(k.as_str(), "value" | "function" | "stream" | "Stream" | "Class"), - format!("need only supports value/function/stream/Class, got '{k}'")); - decls.push(Decl::Need { kind: k, name }); - continue; - } - if self.at_kw("Stream") { - self.next(); - let name = self.expect_id(); - if self.eat_op("&") { - let t = self.peek(0); - let file = match t.kind { - TokenKind::Str | TokenKind::Ident => self.next().text.to_string(), - _ => { self.error("expected binary library file name (string or identifier)"); String::new() } - }; - let members = self.parse_members(); - let annos = self.parse_decl_annos(); - decls.push(Decl::StreamBin { name, file, members, annos }); - } else { - let members = self.parse_members(); - let annos = self.parse_decl_annos(); - decls.push(Decl::StreamSig { name, members, annos }); - } - continue; - } - if self.at_kw("Class") { - self.next(); - let name = self.expect_id(); - let mut implements = Vec::new(); - if self.at_kw("implements") { - self.next(); - implements.push(self.expect_id()); - while self.eat_op(",") { - implements.push(self.expect_id()); - } - } - let members = self.parse_members(); - let annos = self.parse_decl_annos(); - decls.push(Decl::Class { name, members, annos, implements }); - continue; - } - if self.at_kw("Interface") { - self.next(); - let name = self.expect_id(); - let members = self.parse_members(); - let annos = self.parse_decl_annos(); - decls.push(Decl::Interface { name, members, annos }); - continue; - } - if self.at_kw("Main") { - self.next(); - self.expect_op("{"); - let methods = self.parse_methods_until("}"); - self.expect_op("}"); - main = Some(MainDecl { methods }); - continue; - } - if self.peek(0).kind == TokenKind::Ident { - let sig = self.next().text.to_string(); - let name = self.expect_id(); - let members = self.parse_members(); - let annos = self.parse_decl_annos(); - decls.push(Decl::Fork { sig, name, members, annos }); - continue; - } - self.error(format!("cannot parse top-level declaration '{0}'", self.peek(0).text)); - self.next(); // 推进防死循环 - } - Program { kind, decls, main } - } - - /// 跳过 { ... } 块(need 的细节假设块)。 - fn skip_block(&mut self) { - if !self.eat_op("{") { - return; - } - let mut depth = 1; - while depth > 0 && !self.at_eof() { - if self.eat_op("{") { - depth += 1; - } else if self.eat_op("}") { - depth -= 1; - } else { - self.next(); - } - } - } - - // ---- 成员(流/类公共) ---- - - /// 成员解析:方法与字段任意交错,直到 }。 - /// - `void m(params) {}` / `void m(params);`(签名) - /// - `int m() {...}` / `int x, y;` / `int[] a;` - /// - `type T;` 泛型占位 - /// - `T n;` / `T[] a;` / `T m() {...}` 泛型风格 - fn parse_members(&mut self) -> Vec { - let mut out = Vec::new(); - self.expect_op("{"); - while !self.is_op(0, "}") && !self.at_eof() { - let t = self.peek(0); - let is_prim_kw = t.kind == TokenKind::Keyword && is_type_name(t.text); - if t.kind == TokenKind::Keyword && t.text == "void" { - self.next(); - let name = self.expect_id(); - let params = self.parse_params(); - let (body, _is_sig) = self.parse_method_tail(); - let annos = self.parse_method_annos(); - out.push(Member::Method(Method { ret: "void".into(), name, params, body, annos })); - continue; - } - // 泛型占位:`type T;`(type 是关键字语义,词法器按 ident 处理) - if t.kind == TokenKind::Ident && t.text == "type" { - self.next(); - self.expect_id(); - self.expect_op(";"); - continue; - } - if t.kind == TokenKind::Ident || is_prim_kw { - let ty0 = self.next().text.to_string(); - let mut ty = ty0.clone(); - let is_arr = self.eat_arr_suffix(&mut ty); - if is_arr { - // T[] a;(字段)或 T[] m() {...}(数组返回方法) - if (self.peek(0).kind == TokenKind::Ident || self.peek(0).kind == TokenKind::Keyword) - && self.is_op(1, "(") { - let name = self.expect_id(); - let params = self.parse_params(); - let (body, _is_sig) = self.parse_method_tail(); - let annos = self.parse_method_annos(); - out.push(Member::Method(Method { ret: ty, name, params, body, annos })); - } else { - let names = self.parse_field_names(); - out.push(Member::Field { ty, names }); - } - continue; - } - // T m() {...} — 泛型/基类型返回方法(lookahead: 名字 + (;get/cause 等关键字可作方法名) - if (self.peek(0).kind == TokenKind::Ident || self.peek(0).kind == TokenKind::Keyword) - && self.is_op(1, "(") { - let name = self.expect_id(); - let params = self.parse_params(); - let (body, _is_sig) = self.parse_method_tail(); - let annos = self.parse_method_annos(); - out.push(Member::Method(Method { ret: ty, name, params, body, annos })); - continue; - } - // 字段:T x, y; 或(基类型)int x; - let names = self.parse_field_names(); - out.push(Member::Field { ty, names }); - continue; - } - self.error(format!("cannot parse stream/class member '{0}'", t.text)); - self.next(); - } - self.expect_op("}"); - out - } - - /// Main 流专用:只有方法。 - fn parse_methods_until(&mut self, end: &str) -> Vec { - let mut out = Vec::new(); - while !self.is_op(0, end) && !self.at_eof() { - let t = self.peek(0); - let (ret, name) = if t.kind == TokenKind::Keyword && t.text == "void" { - self.next(); - ("void".to_string(), self.expect_id()) - } else if (t.kind == TokenKind::Ident || t.kind == TokenKind::Keyword) && is_type_name(t.text) { - let mut ty = self.next().text.to_string(); - self.eat_arr_suffix(&mut ty); - (ty, self.expect_id()) - } else { - self.error(format!("expected method (return type + name), got '{0}'", t.text)); - self.next(); - continue; - }; - let params = self.parse_params(); - let (body, _sig) = self.parse_method_tail(); - let annos = self.parse_method_annos(); - out.push(Method { ret, name, params, body, annos }); - } - out - } - - fn parse_method_tail(&mut self) -> (Vec, bool) { - if self.eat_op("{") { - let stmts = self.parse_stmts_until("}"); - (stmts, false) - } else { - self.expect_op(";"); - (Vec::new(), true) - } - } - - fn eat_arr_suffix(&mut self, ty: &mut String) -> bool { - if self.is_op(0, "[") { - self.next(); - self.expect_op("]"); - ty.push_str("[]"); - true - } else { - false - } - } - - /// 字段名列表:`x, y;`(逗号分隔后跟分号)。 - fn parse_field_names(&mut self) -> Vec { - let mut names = vec![self.expect_id()]; - while self.eat_op(",") { - names.push(self.expect_id()); - } - self.expect_op(";"); - names - } - - /// 参数:`a int` / `a int[]` / `&perm follow a IO` - fn parse_params(&mut self) -> Vec { - let mut out = Vec::new(); - self.expect_op("("); - while !self.is_op(0, ")") && !self.at_eof() { - // 引用参数两种写法都接受(引用是一种类型,参数 = 名字在前 类型在后): - // 标准:name &perm follow type e.g. `cio &r u CIO` - // 兼容:&perm follow name type e.g. `&r f io IO`(旧 C 写法) - let mut ref_perm: Option = None; - let mut ref_follow: Option = None; - let mut name = String::new(); - let mut ty = String::new(); - let mut is_arr = false; - - // 兼容旧写法:&perm follow name type(权限在最前) - if self.is_op(0, "&") { - self.next(); // & - ref_perm = Some(self.expect_id()); - ref_follow = Some(self.expect_id()); - let t = self.peek(0); - if t.kind == TokenKind::Ident || (t.kind == TokenKind::Keyword && t.text != "program") { - name = self.next().text.to_string(); - } else { - self.error(format!("cannot parse parameter '{0}'", t.text)); - self.next(); - if !self.eat_op(",") { - break; - } - continue; - } - // 类型:基类型关键字(int/string/...)或任意标识符(流名/类名/泛型 T) - if self.peek(0).kind == TokenKind::Ident - || (self.peek(0).kind == TokenKind::Keyword && is_type_name(self.peek(0).text)) { - ty = self.next().text.to_string(); - } - // 无类型参数(旧 C 记为 void,解释器不区分):`push(v)` 合法 - } else { - // 标准写法:name [&perm follow] type(名字在前,引用/类型在后) - let t = self.peek(0); - if t.kind == TokenKind::Ident || (t.kind == TokenKind::Keyword && t.text != "program") { - name = self.next().text.to_string(); - } else { - self.error(format!("cannot parse parameter '{0}'", t.text)); - self.next(); - if !self.eat_op(",") { - break; - } - continue; - } - if self.eat_op("[") { - self.expect_op("]"); - is_arr = true; - } - // 引用类型:name &perm follow type - if self.eat_op("&") { - ref_perm = Some(self.expect_id()); - ref_follow = Some(self.expect_id()); - if self.peek(0).kind == TokenKind::Ident - || (self.peek(0).kind == TokenKind::Keyword && is_type_name(self.peek(0).text)) { - ty = self.next().text.to_string(); - } - } else if self.peek(0).kind == TokenKind::Ident - || (self.peek(0).kind == TokenKind::Keyword && is_type_name(self.peek(0).text)) { - ty = self.next().text.to_string(); - if self.eat_op("[") { - self.expect_op("]"); - is_arr = true; - } - } - } - out.push(Param { name, ty, is_arr, ref_perm, ref_follow }); - if !self.eat_op(",") { - break; - } - } - self.expect_op(")"); - out - } - - fn parse_method_annos(&mut self) -> Vec { - let mut out = Vec::new(); - while self.is_op(0, "@") { - self.next(); - let a = self.expect_id(); - self.err_if(!METHOD_ANNOS.contains(&a.as_str()), - format!("unknown method annotation @{a} (only @read/@write/@call/@ucall)")); - out.push(a); - } - out - } - - fn parse_decl_annos(&mut self) -> Vec { - let mut out = Vec::new(); - while self.is_op(0, "@") { - self.next(); - let a = self.expect_id(); - self.err_if(!DECL_ANNOS.contains(&a.as_str()), - format!("unknown stream annotation @{a} (only @onlyread/@unfork)")); - out.push(a); - } - out - } - - // ---- 语句 ---- - - fn parse_stmts_until(&mut self, end: &str) -> Vec { - let mut out = Vec::new(); - while !self.is_op(0, end) && !self.at_eof() { - let before = self.errors.len(); - let s = self.parse_stmt(); - out.push(s); - if self.errors.len() > before { - // 出错时推进到分号或块结束,避免级联 - while !self.is_op(0, ";") && !self.is_op(0, end) && !self.at_eof() { - self.next(); - } - self.eat_op(";"); - } - } - if !self.at_eof() { - self.next(); // 消费 end - } - out - } - - fn parse_stmt(&mut self) -> Stmt { - if self.at_kw("if") { - return self.parse_if(); - } - if self.at_kw("while") { - return self.parse_while(); - } - if self.at_kw("for") { - return self.parse_for(); - } - if self.at_kw("break") { - self.next(); - self.expect_op(";"); - return Stmt::Break; - } - if self.at_kw("continue") { - self.next(); - self.expect_op(";"); - return Stmt::Continue; - } - if self.at_kw("res") || self.at_kw("ref") { - let kind = if self.at_kw("res") { RetKind::Res } else { RetKind::Ref }; - self.next(); - let mut values = Vec::new(); - if !self.is_op(0, ";") { - values.push(self.parse_expr()); - while self.eat_op(",") { - values.push(self.parse_expr()); - } - } - self.expect_op(";"); - return Stmt::Ret { kind, values }; - } - if self.at_kw("const") || self.at_kw("thread") { - let is_const = self.at_kw("const"); - let modif = self.next().text.to_string(); - let _ = &modif; - // const/thread 后可跟 ALL 或类型(const int x = 10; / thread int x = 10;) - let t = self.peek(0); - if t.kind == TokenKind::Keyword && t.text == "ALL" { - self.next(); - let name = self.expect_id(); - let value = self.parse_assign_rhs("="); - return Stmt::Assign { - vtype: Some("ALL".into()), is_const, is_thread: !is_const, - target: AssignTarget::Var(name), op: "=".into(), value, - }; - } - let mut ty = self.expect_id(); - self.eat_arr_suffix(&mut ty); - let name = self.expect_id(); - let value = if self.eat_op("=") { self.parse_expr() } else { Expr::Int(0) }; - self.expect_op(";"); - return Stmt::Assign { - vtype: Some(ty), is_const, is_thread: !is_const, - target: AssignTarget::Var(name), op: "=".into(), value, - }; - } - if self.at_kw("ALL") { - self.next(); - let name = self.expect_id(); - let value = self.parse_assign_rhs("="); - self.expect_op(";"); - return Stmt::Assign { - vtype: Some("ALL".into()), is_const: false, is_thread: false, - target: AssignTarget::Var(name), op: "=".into(), value, - }; - } - // 基类型声明:`int x = e;` / `int[] a = e;` / `string s;`(类型是关键字) - if self.peek(0).kind == TokenKind::Keyword && is_type_name(self.peek(0).text) { - let mut ty = self.next().text.to_string(); - self.eat_arr_suffix(&mut ty); - let name = self.expect_id(); - let value = if self.eat_op("=") { self.parse_expr() } else { Expr::Int(0) }; - self.expect_op(";"); - return Stmt::Assign { - vtype: Some(ty), is_const: false, is_thread: false, - target: AssignTarget::Var(name), op: "=".into(), value, - }; - } - if self.is_op(0, "&") { - return self.parse_ref_decl(); - } - // this:: 开头的语句(this 是关键字):属性赋值/方法调用 - if self.peek(0).kind == TokenKind::Ident || self.at_kw("this") { - return self.parse_ident_stmt(); - } - self.error(format!("cannot parse statement '{0}'", self.peek(0).text)); - Stmt::Expr(Expr::Int(0)) - } - - fn parse_assign_rhs(&mut self, op: &str) -> Expr { - if !self.eat_op(op) { - self.error(format!("expected '{0}'", op)); - } - self.parse_expr() - } - - fn parse_if(&mut self) -> Stmt { - self.next(); // if - self.expect_op("("); - let cond = self.parse_expr(); - self.expect_op(")"); - self.expect_op("{"); - let then = self.parse_stmts_until("}"); - let els = if self.at_kw("else") { - self.next(); - if self.at_kw("if") { - Some(vec![self.parse_if()]) - } else { - self.expect_op("{"); - Some(self.parse_stmts_until("}")) - } - } else { - None - }; - Stmt::If { cond, then, els } - } - - fn parse_while(&mut self) -> Stmt { - self.next(); // while - self.expect_op("("); - let cond = self.parse_expr(); - self.expect_op(")"); - self.expect_op("{"); - let body = self.parse_stmts_until("}"); - Stmt::While { cond, body } - } - - fn parse_for(&mut self) -> Stmt { - self.next(); // for - self.expect_op("("); - let init = if self.is_op(0, ";") { - self.next(); // 空 init,消费分号 - None - } else { - Some(Box::new(self.parse_stmt())) // 完整语句(含分号) - }; - let cond = if self.is_op(0, ";") { - self.next(); // 空 cond,消费分号 - None - } else { - let c = self.parse_expr(); - self.expect_op(";"); - Some(c) - }; - // update 是完整语句(examples 写法 `k = k + 1;`);`i++` 无分号由 Inc 分支兼容 - let update = if self.is_op(0, ")") { None } else { Some(Box::new(self.parse_stmt())) }; - self.expect_op(")"); - self.expect_op("{"); - let body = self.parse_stmts_until("}"); - Stmt::For { init, cond, update, body } - } - - /// `&perm follow base name = &lvalue;` — 智能引用声明。 - fn parse_ref_decl(&mut self) -> Stmt { - self.next(); // & - let perm = self.expect_id(); - self.err_if(!PERMS.contains(&perm.as_str()), - format!("invalid reference permission '{perm}' (r/w/m stacks: r, w, m, rw, rm, wm, rwm)")); - let follow = self.expect_id(); - self.err_if(!FOLLOWS.contains(&follow.as_str()), - format!("invalid reference follow layer '{follow}' (expected u/f/a/t)")); - let mut base = self.expect_id(); - self.eat_arr_suffix(&mut base); - let name = self.expect_id(); - self.expect_op("="); - let init = self.parse_expr(); - self.err_if(!matches!(init, Expr::RefOf(_)), - "reference declaration requires & initializer (e.g. &rw u int p = &a[0];)"); - self.expect_op(";"); - Stmt::RefDecl { perm, follow, base, name, init } - } - - /// 标识符开头的语句:声明/赋值/自增/索引/属性/调用。 - fn parse_ident_stmt(&mut self) -> Stmt { - let t = self.peek(0); - - // int[] a = expr; / int[] a;(类型名 + []) - if is_type_name(t.text) && self.is_op(1, "[") && self.is_op(2, "]") { - self.next(); self.next(); self.next(); - let name = self.expect_id(); - let value = if self.eat_op("=") { self.parse_expr() } else { Expr::Int(0) }; - self.expect_op(";"); - return Stmt::Assign { - vtype: Some("int[]".into()), is_const: false, is_thread: false, - target: AssignTarget::Var(name), op: "=".into(), value, - }; - } - // int x = e; / int x;(类型名 + 标识符) - if is_type_name(t.text) && self.peek(1).kind == TokenKind::Ident { - let mut ty = self.next().text.to_string(); - self.eat_arr_suffix(&mut ty); - let name = self.expect_id(); - let value = if self.eat_op("=") { self.parse_expr() } else { Expr::Int(0) }; - self.expect_op(";"); - return Stmt::Assign { - vtype: Some(ty), is_const: false, is_thread: false, - target: AssignTarget::Var(name), op: "=".into(), value, - }; - } - // 类名/流名类型声明:Box b = new Box(42); / Hero h;(第一个是 Ident,第二个是 Ident) - if t.kind == TokenKind::Ident && self.peek(1).kind == TokenKind::Ident { - let ty = self.next().text.to_string(); - let name = self.expect_id(); - let value = if self.eat_op("=") { self.parse_expr() } else { Expr::Int(0) }; - self.expect_op(";"); - return Stmt::Assign { - vtype: Some(ty), is_const: false, is_thread: false, - target: AssignTarget::Var(name), op: "=".into(), value, - }; - } - // i++; / i--; - if self.is_op(1, "++") || self.is_op(1, "--") { - let name = self.next().text.to_string(); - let op = self.next().text.to_string(); - if !self.is_op(0, ";") { - // for update 子句里无分号 - return Stmt::Inc { name, op }; - } - self.next(); - return Stmt::Inc { name, op }; - } - // a[i] = v; / a[i] += v; / a[i]; 索引 - if self.is_op(1, "[") { - let arr = self.next().text.to_string(); - self.next(); // [ - let idx = self.parse_expr(); - self.expect_op("]"); - let target = AssignTarget::Index { - base: Box::new(Expr::Var(arr.clone())), - idx: Box::new(idx.clone()), - }; - if self.is_op(0, "=") || ASSIGN_OPS.contains(&self.peek(0).text) { - let op = self.next().text.to_string(); - let value = self.parse_expr(); - self.expect_op(";"); - return Stmt::Assign { vtype: None, is_const: false, is_thread: false, target, op, value }; - } - self.expect_op(";"); - return Stmt::Expr(Expr::Index { base: Box::new(Expr::Var(arr)), idx: Box::new(idx) }); - } - // qual::name(...); / qual::attr = v; / qual::attr += v; - if self.is_op(1, "::") { - let qual = self.next().text.to_string(); - self.next(); // :: - let nm = self.expect_method_name(); - if self.is_op(0, "=") || ASSIGN_OPS.contains(&self.peek(0).text) { - let op = self.next().text.to_string(); - let value = self.parse_expr(); - self.expect_op(";"); - return Stmt::Assign { - vtype: None, is_const: false, is_thread: false, - target: AssignTarget::Prop { base: Box::new(Expr::Var(qual)), name: nm }, - op, value, - }; - } - self.expect_op("("); - let mut args = Vec::new(); - while !self.is_op(0, ")") && !self.at_eof() { - args.push(self.parse_expr()); - if !self.eat_op(",") { - break; - } - } - self.expect_op(")"); - self.expect_op(";"); - return Stmt::Expr(Expr::Call { qual: Some(qual), name: nm, args }); - } - // x = e; / x += e; - if self.is_op(1, "=") || ASSIGN_OPS.contains(&self.peek(1).text) { - let name = self.next().text.to_string(); - let op = self.next().text.to_string(); - let value = self.parse_expr(); - self.expect_op(";"); - return Stmt::Assign { - vtype: None, is_const: false, is_thread: false, - target: AssignTarget::Var(name), op, value, - }; - } - // fname(args); 裸调用语句 - if self.is_op(1, "(") { - let name = self.next().text.to_string(); - self.next(); // ( - let mut args = Vec::new(); - while !self.is_op(0, ")") && !self.at_eof() { - args.push(self.parse_expr()); - if !self.eat_op(",") { - break; - } - } - self.expect_op(")"); - self.expect_op(";"); - return Stmt::Expr(Expr::Call { qual: None, name, args }); - } - // 其他表达式语句 - let e = self.parse_expr(); - if !self.is_op(0, ";") && !self.is_op(0, ")") { - self.error(format!("expected ';' after statement, got '{0}'", self.peek(0).text)); - } else { - self.eat_op(";"); - } - Stmt::Expr(e) - } - - // ---- 表达式(Pratt 风格优先级) ---- - - pub fn parse_expr(&mut self) -> Expr { - self.parse_binop(0) - } - - fn parse_binop(&mut self, min_bp: u8) -> Expr { - let mut left = self.parse_unary(); - loop { - let t = self.peek(0); - if t.kind != TokenKind::Op { - break; - } - let (bp, op) = match t.text { - "||" => (1, "||"), - "&&" => (2, "&&"), - "==" | "!=" => (3, t.text), - "<" | ">" | "<=" | ">=" => (4, t.text), - "+" | "-" => (5, t.text), - "*" | "/" | "%" => (6, t.text), - _ => break, - }; - if bp < min_bp { - break; - } - self.next(); - let right = self.parse_binop(bp + 1); - left = Expr::BinOp { op: op.to_string(), l: Box::new(left), r: Box::new(right) }; - } - left - } - - fn parse_unary(&mut self) -> Expr { - let t = self.peek(0); - if t.kind == TokenKind::Op && t.text == "&" { - self.next(); - return Expr::RefOf(Box::new(self.parse_unary())); - } - self.parse_primary() - } - - fn parse_primary(&mut self) -> Expr { - let t = self.peek(0); - match t.kind { - TokenKind::Int => { - self.next(); - let v = t.text.parse::().unwrap_or(0); - self.parse_prop_chain(Expr::Int(v)) - } - TokenKind::Float => { - self.next(); - let v = t.text.parse::().unwrap_or(0.0); - self.parse_prop_chain(Expr::Float(v)) - } - TokenKind::Str => { - self.next(); - self.parse_prop_chain(Expr::Str(t.text.to_string())) - } - TokenKind::Char => { - self.next(); - self.parse_prop_chain(Expr::Char(decode_char(t.text))) - } - TokenKind::Keyword if t.text == "true" || t.text == "false" => { - self.next(); - self.parse_prop_chain(Expr::Bool(t.text == "true")) - } - TokenKind::Keyword if t.text == "new" => { - self.next(); - // new Type[expr] → 数组字面量;new Class(args) → Obj::new - let cls = self.expect_id(); - if self.eat_op("[") { - let size = self.parse_expr(); - self.expect_op("]"); - self.parse_prop_chain(Expr::NewArray { ty: cls, size: Box::new(size) }) - } else { - self.expect_op("("); - let mut args = Vec::new(); - while !self.is_op(0, ")") && !self.at_eof() { - args.push(self.parse_expr()); - if !self.eat_op(",") { - break; - } - } - self.expect_op(")"); - self.parse_prop_chain(Expr::New { cls, args }) - } - } - TokenKind::Keyword if t.text == "get" || t.text == "cause" => { - // 前缀解包:get/cause X;`get(...)`/`cause(...)` 是裸调用(方法名 get/cause) - let op = t.text; - if self.is_op(1, "(") { - self.next(); - self.next(); // ( - let mut args = Vec::new(); - while !self.is_op(0, ")") && !self.at_eof() { - args.push(self.parse_expr()); - if !self.eat_op(",") { - break; - } - } - self.expect_op(")"); - self.parse_prop_chain(Expr::Call { qual: None, name: op.to_string(), args }) - } else { - let is_prefix = !(self.is_op(1, "::") || self.is_op(1, ".") || self.is_op(1, "[")); - if is_prefix { - self.next(); - let l = self.parse_unary(); - self.parse_prop_chain(Expr::Unwrap { op: op.to_string(), l: Box::new(l) }) - } else { - // get 作普通标识符(变量名等) - self.next(); - self.parse_prop_chain(Expr::Var(op.to_string())) - } - } - } - TokenKind::Keyword if t.text == "this" => { - self.next(); - self.ident_primary("this".to_string()) - } - TokenKind::Keyword => { - self.next(); - self.parse_prop_chain(Expr::Var(t.text.to_string())) - } - TokenKind::Ident => { - let name = self.next().text.to_string(); - self.ident_primary(name) - } - TokenKind::Op if t.text == "(" => { - self.next(); - let e = self.parse_expr(); - self.expect_op(")"); - self.parse_prop_chain(e) - } - TokenKind::Op if t.text == "-" => { - // 一元负号(宽松支持;语法面未见,作扩展) - self.next(); - let e = self.parse_unary(); - Expr::BinOp { op: "-".into(), l: Box::new(Expr::Int(0)), r: Box::new(e) } - } - _ => { - self.error(format!("cannot parse expression '{0}'", t.text)); - self.next(); - Expr::Int(0) - } - } - } - - /// 标识符开头的 primary:`name` / `qual::m(...)` / `qual::prop` / - /// `name(...)` 裸调用 / `name[i]` 索引(this 关键字也走这里)。 - fn ident_primary(&mut self, name: String) -> Expr { - if self.eat_op("::") { - let mname = self.expect_method_name(); - if self.is_op(0, "(") { - self.next(); - let mut args = Vec::new(); - while !self.is_op(0, ")") && !self.at_eof() { - args.push(self.parse_expr()); - if !self.eat_op(",") { - break; - } - } - self.expect_op(")"); - self.parse_prop_chain(Expr::Call { qual: Some(name), name: mname, args }) - } else { - // qual::name 属性访问(this::data) - self.parse_prop_chain(Expr::Prop { - base: Box::new(Expr::Var(name)), - name: mname, - }) - } - } else if self.is_op(0, "(") { - // 裸调用 - self.next(); - let mut args = Vec::new(); - while !self.is_op(0, ")") && !self.at_eof() { - args.push(self.parse_expr()); - if !self.eat_op(",") { - break; - } - } - self.expect_op(")"); - self.parse_prop_chain(Expr::Call { qual: None, name, args }) - } else if self.is_op(0, "[") { - // 索引读取 - self.next(); - let idx = self.parse_expr(); - self.expect_op("]"); - self.parse_prop_chain(Expr::Index { base: Box::new(Expr::Var(name)), idx: Box::new(idx) }) - } else { - self.parse_prop_chain(Expr::Var(name)) - } - } - - /// 后缀属性链:`obj.field.field...`(对象属性访问)。 - fn parse_prop_chain(&mut self, mut e: Expr) -> Expr { - while self.is_op(0, ".") { - self.next(); - let t = self.peek(0); - if t.kind != TokenKind::Ident && t.kind != TokenKind::Keyword { - self.error(format!("invalid property name '{0}'", t.text)); - self.next(); - break; - } - let name = self.next().text.to_string(); - e = Expr::Prop { base: Box::new(e), name }; - } - e - } -} - -/// 字符字面量解码:`'x'` / `'\n'` / `'\\'` / `'\''` 等。 -fn decode_char(s: &str) -> u8 { - match s { - "\\n" => b'\n', - "\\t" => b'\t', - "\\r" => b'\r', - "\\0" => 0, - "\\\\" => b'\\', - "\\'" => b'\'', - "\\\"" => b'"', - _ => s.as_bytes().first().copied().unwrap_or(0), - } -} - -/// 便捷入口:源码 → Program。errors 非空时解析失败。 -pub fn parse_source(src: &str) -> (Program, Vec) { - let mut toks = Vec::new(); - match crate::lexer::tokenize(src, &mut toks) { - Ok(()) => {} - Err(e) => { - let prog = Program { kind: String::new(), decls: Vec::new(), main: None }; - return (prog, vec![ParseError { line: e.line, col: e.col, msg: e.msg.to_string() }]); - } - } - let mut p = Parser::new(&toks); - let prog = p.parse_program(); - (prog, p.errors) -} - -#[cfg(test)] -mod tests { - use super::*; - - fn parse_ok(src: &str) -> Program { - let (p, errs) = parse_source(src); - assert!(errs.is_empty(), "parse errors: {errs:?}"); - p - } - - #[test] - fn hello() { - let p = parse_ok("program main;\nMain { void exec() { CIO::println(\"hi\"); } }"); - assert_eq!(p.kind, "main"); - let m = p.main.unwrap(); - assert_eq!(m.methods[0].name, "exec"); - assert!(matches!(m.methods[0].body[0], Stmt::Expr(Expr::Call { ref qual, .. }) if qual.as_deref() == Some("CIO"))); - } - - #[test] - fn requests_model() { - let src = r#" -program main; -Stream Calc { int add(a int, b int); } -Calc MyCalc { int add(a int, b int) { res a + b; } } -Main { - void exec() { - ALL r = MyCalc::add(3, 4); - ALL bad = MyCalc::div(1, 0); - CIO::println("x", get r, cause bad); - if (r) { CIO::println("ok"); } else { CIO::println("no"); } - } -}"#; - let p = parse_ok(src); - assert_eq!(p.decls.len(), 2); - let m = p.main.unwrap(); - assert!(matches!(m.methods[0].body[0], Stmt::Assign { vtype: Some(ref v), .. } if v == "ALL")); - assert!(matches!(m.methods[0].body[2], Stmt::Expr(Expr::Call { ref args, .. }) if args.len() == 3)); - } - - #[test] - fn control_flow() { - let src = r#" -program main; -Main { - void exec() { - ALL i = 1; - while (i <= 10) { i = i + 1; } - for (ALL k = 1; k <= 5; k = k + 1;) { } - for (;;) { break; } - if (i > 5) { } else if (i < 2) { } else { } - } -}"#; - let p = parse_ok(src); - let m = p.main.unwrap(); - assert!(matches!(m.methods[0].body[2], Stmt::For { .. })); - assert!(matches!(m.methods[0].body[3], Stmt::For { cond: None, .. })); - } - - #[test] - fn smart_refs_and_threads() { - let src = r#" -program main; -Calc Worker { - void threadJob(n int) { - thread int local_note = 0; - &w a int wa = &local_note; - Ref::write(wa, n * 2); - res get Ref::read(ra); - } -} -Main { - void exec() { - int counter = 0; - &rwm t int p = &counter; - ALL t1 = get Threads::spawn("factorial", 10); - } -}"#; - let p = parse_ok(src); - assert_eq!(p.decls.len(), 1); - } - - #[test] - fn classes_fields_and_arrays() { - let src = r#" -program main; -Class Hero { - void __init__(name string, hp int) { Obj::set(this, "name", name); } - int getHp() { res 100; } -} -Main { - void exec() { - ALL h = new Hero("TAK", 88); - int[] a = Solid::new().res; - a[0] = 1; - ALL x = a[0]; - CIO::println("hp =", h.hp); - } -}"#; - let p = parse_ok(src); - let m = p.main.unwrap(); - let exec = &m.methods[0].body; - assert!(matches!(exec[1], Stmt::Assign { vtype: Some(ref v), .. } if v == "int[]")); - // a[0] = 1; → Assign(target: Index) - assert!(matches!(exec[2], Stmt::Assign { target: AssignTarget::Index { .. }, .. })); - // ALL x = a[0]; → 右侧是 Index - assert!(matches!(exec[3], Stmt::Assign { value: Expr::Index { .. }, .. })); - } - - #[test] - fn need_and_binary_stream() { - let src = r#" -program main; -need value GREETING; -need function greet; -need stream IO; -Stream m & "libm.so.6" { double sin(x double); } -Main { void exec() { } } -"#; - let p = parse_ok(src); - assert_eq!(p.decls.len(), 4); - assert!(matches!(p.decls[3], Decl::StreamBin { ref file, .. } if file == "libm.so.6")); - } - - #[test] - fn annotations_and_phonebooth() { - let src = r#" -program main; -Class Hero { void __init__() { } } @unfork -Main { - void exec() { - } - int fast() { res 1; } @call -}"#; - let p = parse_ok(src); - assert!(matches!(p.decls[0], Decl::Class { ref annos, .. } if annos == &["unfork"])); - let m = p.main.unwrap(); - assert_eq!(m.methods[1].annos, vec!["call"]); - } - - #[test] - fn new_array_literal() { - let src = r#" -program main; -Main { void exec() { ALL a = new int[10]; ALL b = new Hero[3]; } } -"#; - let p = parse_ok(src); - let m = p.main.unwrap(); - assert!(matches!(m.methods[0].body[0], Stmt::Assign { value: Expr::NewArray { ref ty, .. }, .. } if ty == "int")); - assert!(matches!(m.methods[0].body[1], Stmt::Assign { value: Expr::NewArray { ref ty, .. }, .. } if ty == "Hero")); - } - - #[test] - fn multi_value_res() { - let src = r#" -program main; -Main { void exec() { } int pair() { res 1, 2, 3; } } -"#; - let p = parse_ok(src); - let m = p.main.unwrap(); - match &m.methods[1].body[0] { - Stmt::Ret { kind: RetKind::Res, values } => assert_eq!(values.len(), 3), - other => panic!("unexpected {other:?}"), - } - } - - #[test] - fn parse_error_reported() { - let (_p, errs) = parse_source("program main;\nMain { void exec() { x = ; } }"); - assert!(!errs.is_empty()); - assert!(errs[0].line >= 1); - } - - #[test] - fn generic_type_member() { - let src = r#" -program main; -Class Box { - type T; - T n; - T[] a; - T get() { res n; } -} -Main { void exec() { } } -"#; - let p = parse_ok(src); - let cls = &p.decls[0]; - if let Decl::Class { members, .. } = cls { - // type T; 只注册名字不产生成员:T n; / T[] a; / T get() = 3 个 - assert_eq!(members.len(), 3); - assert!(matches!(members[0], Member::Field { ref ty, .. } if ty == "T")); - assert!(matches!(members[1], Member::Field { ref ty, .. } if ty == "T[]")); - assert!(matches!(members[2], Member::Method(Method { ref ret, .. }) if ret == "T")); - } else { - panic!("expected class"); - } - } -} diff --git a/rust/crates/bbb-syntax/tests/examples.rs b/rust/crates/bbb-syntax/tests/examples.rs deleted file mode 100644 index d0d992d..0000000 --- a/rust/crates/bbb-syntax/tests/examples.rs +++ /dev/null @@ -1,77 +0,0 @@ -//! 标准层回归:examples/01-17 + project 全部必须 parse 成功。 -//! -//! 这是"标准层不改变"的硬验证:任何解析器改动导致 examples 解析失败 -//! 即回归失败。examples 目录位于仓库根(相对本 crate 的 ../examples)。 - -use std::path::PathBuf; - -use bbb_syntax::parser::parse_source; - -fn examples_dir() -> PathBuf { - // CARGO_MANIFEST_DIR = rust/crates/bbb-syntax → 上溯 3 级到仓库根 - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .parent() - .unwrap() - .parent() - .unwrap() - .parent() - .unwrap() - .join("examples") -} - -fn collect_bio_files() -> Vec { - let mut out = Vec::new(); - let ex = examples_dir(); - let mut dirs = vec![ex.clone()]; - dirs.push(ex.join("project").join("src")); - dirs.push(ex.join("project").join("utils")); - for d in dirs { - if let Ok(entries) = std::fs::read_dir(&d) { - for e in entries.flatten() { - let p = e.path(); - if p.extension().map(|x| x == "bio").unwrap_or(false) { - out.push(p); - } - } - } - } - out.sort(); - out -} - -#[test] -fn all_examples_parse() { - let files = collect_bio_files(); - assert!(!files.is_empty(), "examples 目录为空?"); - let mut failed = Vec::new(); - for f in &files { - let src = std::fs::read_to_string(f).expect("read"); - let (prog, errs) = parse_source(&src); - if !errs.is_empty() { - failed.push((f.display().to_string(), errs.clone())); - } - // 主程序必须声明了 kind - if prog.kind.is_empty() && !errs.is_empty() { - failed.push((f.display().to_string(), errs)); - } - } - assert!(failed.is_empty(), "解析失败:\n{}", - failed.iter().map(|(f, es)| format!( - "{f}: {}", es.iter().map(|e| e.to_string()).collect::>().join("; "))) - .collect::>().join("\n")); -} - -/// 每个示例必须声明 program main/utils(标准层契约)。 -#[test] -fn examples_declare_program_kind() { - let files = collect_bio_files(); - let mut bad = Vec::new(); - for f in &files { - let src = std::fs::read_to_string(f).expect("read"); - let (prog, errs) = parse_source(&src); - if errs.is_empty() && prog.kind.is_empty() { - bad.push(f.display().to_string()); - } - } - assert!(bad.is_empty(), "缺少 program 声明:{bad:?}"); -} diff --git a/rust/crates/bbb-vm/Cargo.toml b/rust/crates/bbb-vm/Cargo.toml deleted file mode 100644 index 29d7315..0000000 --- a/rust/crates/bbb-vm/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "bbb-vm" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "BioLang 解释器(M3):流注册表 + eval + 内置流" - -[dependencies] -bbb-syntax = { path = "../bbb-syntax" } -bbb-core = { path = "../bbb-core" } diff --git a/rust/crates/bbb-vm/src/builtin.rs b/rust/crates/bbb-vm/src/builtin.rs deleted file mode 100644 index 69cf3bf..0000000 --- a/rust/crates/bbb-vm/src/builtin.rs +++ /dev/null @@ -1,738 +0,0 @@ -//! 内置流(Rust 实现):CIO/SIO/FIO/Com/Time/Obj/Solid/Arrays。 -//! -//! 方法表 `HashMap<(qual, method), fn>`。签名统一: -//! `fn(&mut Interp, &[Value]) -> Outcome`。 -//! 实现原则:与旧 C builtin.c 语义一致(examples 期望输出为准)。 - -use std::collections::HashMap; -use std::fs; -use std::time::Instant; - -use bbb_core::value::{Cause, Outcome, Tag, Value}; - -use crate::interp::Interp; - -pub type BuiltinFn = fn(&mut Interp, &[Value]) -> Outcome; - -fn f(q: &'static str, m: &'static str, f: BuiltinFn) -> ((&'static str, &'static str), BuiltinFn) { - ((q, m), f) -} - -pub fn table() -> HashMap<(&'static str, &'static str), BuiltinFn> { - let mut t = HashMap::new(); - for (k, v) in [ - // ---- CIO 控制台 ---- - f("CIO", "println", cio_println), - f("CIO", "print", cio_print), - f("CIO", "error", cio_error), - f("IO", "println", cio_println), - f("IO", "print", cio_print), - // ---- SIO 字符串缓冲 ---- - f("SIO", "format", sio_format), - f("SIO", "upper", sio_upper), - f("SIO", "lower", sio_lower), - f("SIO", "trim", sio_trim), - f("SIO", "contains", sio_contains), - f("SIO", "substring", sio_substring), - f("SIO", "replace", sio_replace), - f("SIO", "println", sio_println), - f("SIO", "print", sio_print), - f("SIO", "getln", sio_getln), - // ---- FIO 文件 ---- - f("FIO", "writeFile", fio_write_file), - f("FIO", "appendFile", fio_append_file), - f("FIO", "readFile", fio_read_file), - f("FIO", "exists", fio_exists), - // ---- Com 计算 ---- - f("Com", "abs", com_abs), - f("Com", "min", com_min), - f("Com", "max", com_max), - f("Com", "pow", com_pow), - f("Com", "sqrt", com_sqrt), - f("Com", "floor", com_floor), - f("Com", "ceil", com_ceil), - f("Com", "round", com_round), - f("Com", "sign", com_sign), - f("Com", "sin", com_sin), - f("Com", "cos", com_cos), - f("Com", "tan", com_tan), - f("Com", "log", com_log), - f("Com", "exp", com_exp), - // ---- Time 定时器 ---- - f("Time", "start", time_start), - f("Time", "sleep", time_sleep), - f("Time", "elapsed", time_elapsed), - f("Time", "fork", time_fork), - f("Time", "reset", time_reset), - // ---- Obj 对象 ---- - f("Obj", "set", obj_set), - f("Obj", "get", obj_get), - f("Obj", "call", obj_call), - f("Obj", "new", obj_new), - // ---- Solid 连续存储 ---- - f("Solid", "new", solid_new), - f("Solid", "len", solid_len), - f("Solid", "get", solid_get), - f("Solid", "set", solid_set), - f("Solid", "push", solid_push), - f("Solid", "pop", solid_pop), - f("Solid", "join", solid_join), - f("Solid", "clear", solid_clear), - // 裸数组(SolidData)复用 Solid 的数据方法 - f("SolidData", "len", solid_len), - f("SolidData", "get", solid_get), - f("SolidData", "set", solid_set), - f("SolidData", "push", solid_push), - f("SolidData", "pop", solid_pop), - f("SolidData", "join", solid_join), - f("SolidData", "clear", solid_clear), - // ---- Arrays 集合 ---- - f("Arrays", "add", arrays_add), - f("Arrays", "count", arrays_count), - f("Arrays", "all", arrays_all), - f("Arrays", "get", arrays_get), - f("Arrays", "forget", arrays_forget), - f("Arrays", "vector", arrays_vector), - f("Arrays", "sort", arrays_sort), - // ---- Threads 协作线程(顺序 join 式) ---- - f("Threads", "spawn", threads_spawn), - f("Threads", "join", threads_join), - f("Threads", "yield", threads_yield), - f("Threads", "active", threads_active), - f("Threads", "self", threads_self), - // ---- Taskm 任务管理器 ---- - f("Taskm", "add", taskm_add), - f("Taskm", "interval", taskm_interval), - f("Taskm", "run", taskm_run), - f("Taskm", "stop", taskm_stop), - f("Taskm", "active", taskm_active), - // ---- Ref 智能引用 ---- - f("Ref", "read", ref_read), - f("Ref", "write", ref_write), - f("Ref", "move", ref_move), - f("Ref", "target", ref_target), - f("Ref", "perm", ref_perm), - ] { - t.insert(k, v); - } - t -} - -pub fn lookup(q: &str, m: &str) -> Option { - // 静态表每次重建开销可忽略(21 条);或 once_cell——stdlib only,直接静态构造 - use std::sync::OnceLock; - static T: OnceLock> = OnceLock::new(); - let t = T.get_or_init(table); - t.get(&(q, m)).copied() -} - -fn arg_str(interp: &mut Interp, a: &Value) -> String { - interp.fmt_value(a) -} - -fn res(v: Value) -> Outcome { - Outcome::Res(v) -} - -fn refn(interp: &mut Interp, msg: &str) -> Outcome { - Outcome::Ref(Cause(interp.intern(msg))) -} - -// ---- CIO ---- - -fn cio_println(interp: &mut Interp, args: &[Value]) -> Outcome { - let line = args.iter().map(|a| arg_str(interp, a)).collect::>().join(" "); - interp.stdout.push_str(&line); - interp.stdout.push('\n'); - res(Value::nil()) -} - -fn cio_print(interp: &mut Interp, args: &[Value]) -> Outcome { - // print 直接拼接(无分隔),println 空格分隔——旧 C 行为(03 输出依赖) - let line = args.iter().map(|a| arg_str(interp, a)).collect::(); - interp.stdout.push_str(&line); - res(Value::nil()) -} - -fn cio_error(interp: &mut Interp, args: &[Value]) -> Outcome { - let line = args.iter().map(|a| arg_str(interp, a)).collect::>().join(" "); - interp.stdout.push_str(&line); - interp.stdout.push('\n'); - res(Value::nil()) -} - -// ---- SIO ---- - -fn sio_format(interp: &mut Interp, args: &[Value]) -> Outcome { - let Some(fmt) = args.first() else { return refn(interp, "SIO::format requires a format string") }; - let fmt_s = arg_str(interp, fmt); - let rest = &args[1..]; - let mut out = String::new(); - let mut it = fmt_s.chars().peekable(); - let mut ai = 0; - while let Some(c) = it.next() { - if c != '%' { - out.push(c); - continue; - } - match it.next() { - Some('d') => { - if ai < rest.len() { - out.push_str(&format!("{}", rest[ai].as_int_or_num() as i64)); - } - ai += 1; - } - Some('f') => { - if ai < rest.len() { - out.push_str(&format!("{}", rest[ai].as_int_or_num())); - } - ai += 1; - } - Some('s') => { - if ai < rest.len() { - out.push_str(&arg_str(interp, &rest[ai])); - } - ai += 1; - } - Some('%') => out.push('%'), - Some(other) => { - out.push('%'); - out.push(other); - } - None => out.push('%'), - } - } - res(Value::string(interp.intern(&out))) -} - -fn sio_upper(interp: &mut Interp, args: &[Value]) -> Outcome { - let s = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - res(Value::string(interp.intern(&s.to_uppercase()))) -} - -fn sio_lower(interp: &mut Interp, args: &[Value]) -> Outcome { - let s = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - res(Value::string(interp.intern(&s.to_lowercase()))) -} - -fn sio_trim(interp: &mut Interp, args: &[Value]) -> Outcome { - let s = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - res(Value::string(interp.intern(s.trim()))) -} - -fn sio_contains(interp: &mut Interp, args: &[Value]) -> Outcome { - let hay = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - let needle = args.get(1).map(|a| arg_str(interp, a)).unwrap_or_default(); - res(Value::boolean(hay.contains(&needle))) -} - -fn sio_substring(interp: &mut Interp, args: &[Value]) -> Outcome { - let s = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - let start = args.get(1).map(|a| a.as_int_or_num() as usize).unwrap_or(0); - let len = args.get(2).map(|a| a.as_int_or_num() as usize); - let sub: String = s.chars().skip(start).take(len.unwrap_or(s.len())).collect(); - res(Value::string(interp.intern(&sub))) -} - -fn sio_replace(interp: &mut Interp, args: &[Value]) -> Outcome { - let s = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - let from = args.get(1).map(|a| arg_str(interp, a)).unwrap_or_default(); - let to = args.get(2).map(|a| arg_str(interp, a)).unwrap_or_default(); - res(Value::string(interp.intern(&s.replace(&from, &to)))) -} - -fn sio_println(interp: &mut Interp, args: &[Value]) -> Outcome { - let line = args.iter().map(|a| arg_str(interp, a)).collect::>().join(" "); - interp.sio_buf.push_str(&line); - interp.sio_buf.push('\n'); - res(Value::nil()) -} - -fn sio_print(interp: &mut Interp, args: &[Value]) -> Outcome { - let line = args.iter().map(|a| arg_str(interp, a)).collect::(); - interp.sio_buf.push_str(&line); - res(Value::nil()) -} - -fn sio_getln(interp: &mut Interp, _args: &[Value]) -> Outcome { - if let Some(pos) = interp.sio_buf.find('\n') { - let line: String = interp.sio_buf.drain(..pos + 1).collect(); - let line = line.trim_end_matches('\n').to_string(); - res(Value::string(interp.intern(&line))) - } else { - let all = std::mem::take(&mut interp.sio_buf); - res(Value::string(interp.intern(&all))) - } -} - -// ---- FIO ---- - -fn fio_write_file(interp: &mut Interp, args: &[Value]) -> Outcome { - let path = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - let content = args.get(1).map(|a| arg_str(interp, a)).unwrap_or_default(); - match fs::write(&path, content) { - Ok(()) => res(Value::boolean(true)), - Err(e) => refn(interp, &format!("FIO::writeFile failed: {e}")), - } -} - -fn fio_append_file(interp: &mut Interp, args: &[Value]) -> Outcome { - let path = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - let content = args.get(1).map(|a| arg_str(interp, a)).unwrap_or_default(); - match fs::OpenOptions::new().append(true).create(true).open(&path) - .and_then(|mut f| std::io::Write::write_all(&mut f, content.as_bytes())) { - Ok(()) => res(Value::boolean(true)), - Err(e) => refn(interp, &format!("FIO::appendFile failed: {e}")), - } -} - -fn fio_read_file(interp: &mut Interp, args: &[Value]) -> Outcome { - let path = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - match fs::read_to_string(&path) { - Ok(s) => res(Value::string(interp.intern(&s))), - Err(e) => refn(interp, &format!("FIO::readFile failed: {e}")), - } -} - -fn fio_exists(interp: &mut Interp, args: &[Value]) -> Outcome { - let path = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - res(Value::int(std::path::Path::new(&path).exists() as i64)) -} - -// ---- Com ---- - -fn num1(interp: &mut Interp, a: &Value) -> Result { - if matches!(a.tag(), Tag::Int | Tag::Num) { - Ok(a.as_int_or_num()) - } else { - Err(refn(interp, "Com requires a numeric argument")) - } -} - -fn com_abs(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::num(v.abs())), - Err(e) => e, - } -} - -fn com_min(interp: &mut Interp, args: &[Value]) -> Outcome { - let a = match num1(interp, args.first().unwrap_or(&Value::nil())) { Ok(v) => v, Err(e) => return e }; - let b = match num1(interp, args.get(1).unwrap_or(&Value::nil())) { Ok(v) => v, Err(e) => return e }; - res(Value::num(a.min(b))) -} - -fn com_max(interp: &mut Interp, args: &[Value]) -> Outcome { - let a = match num1(interp, args.first().unwrap_or(&Value::nil())) { Ok(v) => v, Err(e) => return e }; - let b = match num1(interp, args.get(1).unwrap_or(&Value::nil())) { Ok(v) => v, Err(e) => return e }; - res(Value::num(a.max(b))) -} - -fn com_pow(interp: &mut Interp, args: &[Value]) -> Outcome { - let a = match num1(interp, args.first().unwrap_or(&Value::nil())) { Ok(v) => v, Err(e) => return e }; - let b = match num1(interp, args.get(1).unwrap_or(&Value::nil())) { Ok(v) => v, Err(e) => return e }; - res(Value::num(a.powf(b))) -} - -fn com_sqrt(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::num(v.sqrt())), - Err(e) => e, - } -} - -fn com_floor(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::num(v.floor())), - Err(e) => e, - } -} - -fn com_ceil(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::num(v.ceil())), - Err(e) => e, - } -} - -fn com_round(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::num(v.round())), - Err(e) => e, - } -} - -fn com_sign(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::int(v.signum() as i64)), - Err(e) => e, - } -} - -fn com_sin(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::num(v.sin())), - Err(e) => e, - } -} - -fn com_cos(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::num(v.cos())), - Err(e) => e, - } -} - -fn com_tan(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::num(v.tan())), - Err(e) => e, - } -} - -fn com_log(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::num(v.ln())), - Err(e) => e, - } -} - -fn com_exp(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::num(v.exp())), - Err(e) => e, - } -} - -// ---- Time ---- - -fn time_start(interp: &mut Interp, _args: &[Value]) -> Outcome { - interp.timers.insert(0, Instant::now()); - res(Value::nil()) -} - -fn time_sleep(_interp: &mut Interp, args: &[Value]) -> Outcome { - let ms = args.first().map(|a| a.as_int_or_num()).unwrap_or(0.0); - std::thread::sleep(std::time::Duration::from_millis(ms as u64)); - res(Value::nil()) -} - -fn time_elapsed(interp: &mut Interp, args: &[Value]) -> Outcome { - let id = args.first().map(|a| a.as_int_or_num() as u32).unwrap_or(0); - match interp.timers.get(&id) { - Some(t) => { - let secs = t.elapsed().as_secs_f64(); - res(Value::num(secs)) - } - None => refn(interp, "Time: timer not started"), - } -} - -fn time_fork(interp: &mut Interp, _args: &[Value]) -> Outcome { - interp.timer_seq += 1; - let id = interp.timer_seq; - interp.timers.insert(id, Instant::now()); - res(Value::int(id as i64)) -} - -fn time_reset(interp: &mut Interp, args: &[Value]) -> Outcome { - let id = args.first().map(|a| a.as_int_or_num() as u32).unwrap_or(0); - if id == 0 { - return refn(interp, "Time refused: first timer (thread default) cannot be reset; use Time::fork()"); - } - interp.timers.insert(id, Instant::now()); - res(Value::nil()) -} - -// ---- Obj ---- - -fn obj_set(interp: &mut Interp, args: &[Value]) -> Outcome { - let Some(obj) = args.first() else { return refn(interp, "Obj::set requires an object") }; - let (Tag::Obj | Tag::Arr) = obj.tag() else { return refn(interp, "Obj::set first argument is not an object") }; - let h = obj.as_handle(); - let key = args.get(1).map(|a| arg_str(interp, a)).unwrap_or_default(); - let val = args.get(2).copied().unwrap_or(Value::nil()); - interp.obj_prop_set(h, &key, val); - res(Value::nil()) -} - -fn obj_get(interp: &mut Interp, args: &[Value]) -> Outcome { - let Some(obj) = args.first() else { return refn(interp, "Obj::get requires an object") }; - let (Tag::Obj | Tag::Arr) = obj.tag() else { return refn(interp, "Obj::get first argument is not an object") }; - let h = obj.as_handle(); - let key = args.get(1).map(|a| arg_str(interp, a)).unwrap_or_default(); - match interp.obj_prop_get(h, &key) { - Some(v) => res(v), - None => refn(interp, "missing attribute"), - } -} - -fn obj_call(interp: &mut Interp, args: &[Value]) -> Outcome { - let Some(obj) = args.first() else { return refn(interp, "Obj::call requires an object") }; - let (Tag::Obj | Tag::Arr) = obj.tag() else { return refn(interp, "Obj::call first argument is not an object") }; - let h = obj.as_handle(); - let name = args.get(1).map(|a| arg_str(interp, a)).unwrap_or_default(); - let rest = args[2..].to_vec(); - interp.invoke_on_obj(h, &name, rest) -} - -fn obj_new(interp: &mut Interp, args: &[Value]) -> Outcome { - let cls = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - let rest = args[1..].to_vec(); - res(interp.new_class(&cls, rest)) -} - -// ---- Solid ---- - -fn solid_new(interp: &mut Interp, _args: &[Value]) -> Outcome { - let h = interp.solid_new(Vec::new()); - res(Value::obj(h)) -} - -fn solid_data_h(interp: &mut Interp, args: &[Value]) -> Result { - let Some(a) = args.first() else { return Err(refn(interp, "Solid method requires a storage handle")) }; - let (Tag::Obj | Tag::Arr) = a.tag() else { return Err(refn(interp, "Solid argument is not a handle")) }; - let h = a.as_handle(); - let cls = interp.obj_class(h); - if cls != "Solid" && cls != "SolidData" { - return Err(refn(interp, "Solid argument is not a Solid instance")); - } - Ok(h) -} - -fn solid_len(interp: &mut Interp, args: &[Value]) -> Outcome { - let h = match solid_data_h(interp, args) { Ok(h) => h, Err(e) => return e }; - res(Value::int(interp.solid_data(h).len() as i64)) -} - -fn solid_get(interp: &mut Interp, args: &[Value]) -> Outcome { - let h = match solid_data_h(interp, args) { Ok(h) => h, Err(e) => return e }; - let i = match args.get(1).ok_or(0).and_then(|a| Ok(a.as_int_or_num() as i64)) { - Ok(i) => i, - Err(_) => 0, - }; - let data = interp.solid_data(h); - if i < 0 || i as usize >= data.len() { - return refn(interp, "index out of bounds"); - } - res(data[i as usize]) -} - -fn solid_set(interp: &mut Interp, args: &[Value]) -> Outcome { - let h = match solid_data_h(interp, args) { Ok(h) => h, Err(e) => return e }; - let i = args.get(1).map(|a| a.as_int_or_num() as i64).unwrap_or(0); - let v = args.get(2).copied().unwrap_or(Value::nil()); - let len = interp.solid_data(h).len() as i64; - if i < 0 || i >= len { - return refn(interp, "index out of bounds"); - } - interp.solid_set(h, i as usize, v); - res(Value::nil()) -} - -fn solid_push(interp: &mut Interp, args: &[Value]) -> Outcome { - let h = match solid_data_h(interp, args) { Ok(h) => h, Err(e) => return e }; - let v = args.get(1).copied().unwrap_or(Value::nil()); - interp.solid_push(h, v); - res(Value::nil()) -} - -fn solid_pop(interp: &mut Interp, args: &[Value]) -> Outcome { - let h = match solid_data_h(interp, args) { Ok(h) => h, Err(e) => return e }; - let data_h = match interp.solid_data_handle(h) { - Some(dh) => dh, - None => return refn(interp, "no data"), - }; - match interp.objects[data_h as usize].fields.pop() { - Some(v) => res(v), - None => refn(interp, "pop from empty"), - } -} - -fn solid_join(interp: &mut Interp, args: &[Value]) -> Outcome { - let h = match solid_data_h(interp, args) { Ok(h) => h, Err(e) => return e }; - let sep = args.get(1).map(|a| arg_str(interp, a)).unwrap_or_default(); - let data = interp.solid_data(h); - let parts: Vec = data.iter().map(|v| interp.fmt_value(v)).collect(); - res(Value::string(interp.intern(&parts.join(&sep)))) -} - -fn solid_clear(interp: &mut Interp, args: &[Value]) -> Outcome { - let h = match solid_data_h(interp, args) { Ok(h) => h, Err(e) => return e }; - if let Some(dh) = interp.solid_data_handle(h) { - interp.objects[dh as usize].fields.clear(); - } - res(Value::nil()) -} - -// ---- Arrays ---- - -fn arrays_add(interp: &mut Interp, args: &[Value]) -> Outcome { - if let Some(a) = args.first() { - if let Tag::Obj | Tag::Arr = a.tag() { - interp.arrays.push(a.as_handle()); - } - } - res(Value::nil()) -} - -fn arrays_count(interp: &mut Interp, _args: &[Value]) -> Outcome { - res(Value::int(interp.arrays.len() as i64)) -} - -fn arrays_all(interp: &mut Interp, _args: &[Value]) -> Outcome { - // 返回整个注册表(所有 Array/Vector 实例组成的数组) - let vals: Vec = interp.arrays.iter().map(|h| Value::obj(*h)).collect(); - let dh = interp.objs_data_handle(vals); - res(Value::arr(dh)) -} - -fn arrays_get(interp: &mut Interp, args: &[Value]) -> Outcome { - let i = args.first().map(|a| a.as_int_or_num() as usize).unwrap_or(0); - match interp.arrays.get(i) { - Some(h) => res(Value::obj(*h)), - None => refn(interp, "Arrays: index out of bounds"), - } -} - -fn arrays_forget(interp: &mut Interp, args: &[Value]) -> Outcome { - if let Some(a) = args.first() { - if let Tag::Obj | Tag::Arr = a.tag() { - let h = a.as_handle(); - interp.arrays.retain(|x| *x != h); - } - } - res(Value::nil()) -} - -fn arrays_vector(interp: &mut Interp, _args: &[Value]) -> Outcome { - let v = interp.new_class("Vector", Vec::new()); - res(v) -} - -fn arrays_sort(interp: &mut Interp, args: &[Value]) -> Outcome { - // Arrays::sort(arr) — 原地排序数组(内部调用数组的 __sort__ 内部方法) - let Some(a) = args.first() else { - return refn(interp, "Arrays::sort requires an array argument"); - }; - let (Tag::Obj | Tag::Arr) = a.tag() else { - return refn(interp, "Arrays::sort argument is not an array object"); - }; - let h = a.as_handle(); - // 通过内部方法 __sort__ 原地排序(数组对象 → Solid 数据) - let out = interp.invoke_on_obj(h, "__sort__", Vec::new()); - if out.is_refused() { - // 裸数组(SolidData)直接排 - if let Some(dh) = interp.solid_data_handle(h) { - interp.sort_data(dh); - return res(Value::nil()); - } - return refn(interp, "Arrays::sort failed: not a sortable array"); - } - res(Value::nil()) -} - -// ---- Threads ---- - -fn threads_spawn(interp: &mut Interp, args: &[Value]) -> Outcome { - let name = args.first().map(|a| interp.fmt_value(a)).unwrap_or_default(); - let rest = args[1..].to_vec(); - interp.thread_seq += 1; - let id = interp.thread_seq; - let def_name = interp.reg.find_bare_method(&name).map(|(d, _)| d.name.clone()); - interp.threads.push(crate::interp::ThreadTask { id, name, def_name, args: rest, done: None }); - Outcome::Res(Value::int(id as i64)) -} - -fn threads_join(interp: &mut Interp, args: &[Value]) -> Outcome { - let id = args.first().map(|a| a.as_int_or_num() as u32).unwrap_or(0); - // 先执行其它未完成任务(逆序——11 期望 thread 2 先打印),再执行目标 - let others: Vec = interp.threads.iter().filter(|t| t.done.is_none() && t.id != id).map(|t| t.id).rev().collect(); - for oid in others { - interp.run_thread(oid); - } - interp.run_thread(id) -} - -fn threads_yield(_interp: &mut Interp, _args: &[Value]) -> Outcome { - Outcome::Res(Value::nil()) // 顺序执行:no-op -} - -fn threads_active(interp: &mut Interp, _args: &[Value]) -> Outcome { - let n = interp.threads.iter().filter(|t| t.done.is_none()).count(); - Outcome::Res(Value::int(n as i64)) -} - -fn threads_self(interp: &mut Interp, _args: &[Value]) -> Outcome { - Outcome::Res(Value::int(interp.running_thread.unwrap_or(0) as i64)) -} - -// ---- Taskm ---- - -fn taskm_add(interp: &mut Interp, args: &[Value]) -> Outcome { - threads_spawn(interp, args) -} - -fn taskm_interval(_interp: &mut Interp, _args: &[Value]) -> Outcome { - Outcome::Res(Value::nil()) -} - -fn taskm_run(interp: &mut Interp, _args: &[Value]) -> Outcome { - let ids: Vec = interp.threads.iter().filter(|t| t.done.is_none()).map(|t| t.id).collect(); - for id in ids { - interp.run_thread(id); - } - Outcome::Res(Value::nil()) -} - -fn taskm_stop(interp: &mut Interp, _args: &[Value]) -> Outcome { - interp.threads.clear(); - Outcome::Res(Value::nil()) -} - -fn taskm_active(interp: &mut Interp, _args: &[Value]) -> Outcome { - threads_active(interp, &[]) -} - -// ---- Ref ---- - -fn ref_read(interp: &mut Interp, args: &[Value]) -> Outcome { - let Some(a) = args.first() else { return refn(interp, "Ref::read requires a reference") }; - if a.tag() != Tag::Ref { return refn(interp, "Ref::read argument is not a reference"); } - interp.ref_read(a.as_handle()) -} - -fn ref_write(interp: &mut Interp, args: &[Value]) -> Outcome { - let Some(a) = args.first() else { return refn(interp, "Ref::write requires a reference") }; - if a.tag() != Tag::Ref { return refn(interp, "Ref::write argument is not a reference") }; - let v = args.get(1).copied().unwrap_or(Value::nil()); - match interp.ref_write(a.as_handle(), v) { - Outcome::Res(_) => Outcome::Res(Value::nil()), - // Ref::write 方法级拒绝消息带 "Ref refused: " 前缀(11 的 cause 输出) - Outcome::Ref(_) => Outcome::Ref(Cause(interp.intern("Ref refused: reference is read-only, cannot write"))), - } -} - -fn ref_move(interp: &mut Interp, args: &[Value]) -> Outcome { - let Some(a) = args.first() else { return refn(interp, "Ref::move requires a reference") }; - if a.tag() != Tag::Ref { return refn(interp, "Ref::move argument is not a reference") }; - interp.ref_move(a.as_handle()) -} - -fn ref_target(interp: &mut Interp, args: &[Value]) -> Outcome { - let Some(a) = args.first() else { return refn(interp, "Ref::target requires a reference") }; - if a.tag() != Tag::Ref { return refn(interp, "Ref::target argument is not a reference") }; - let r = interp.refs[a.as_handle() as usize].clone(); - match &r.target { - crate::interp::RefTarget::Var { name, .. } => Outcome::Res(Value::string(interp.intern(name))), - crate::interp::RefTarget::ArrElem { index, .. } => Outcome::Res(Value::int(*index)), - crate::interp::RefTarget::ObjProp { name, .. } => Outcome::Res(Value::string(interp.intern(name))), - } -} - -fn ref_perm(interp: &mut Interp, args: &[Value]) -> Outcome { - let Some(a) = args.first() else { return refn(interp, "Ref::perm requires a reference") }; - if a.tag() != Tag::Ref { return refn(interp, "Ref::perm argument is not a reference") }; - let r = interp.refs[a.as_handle() as usize].clone(); - Outcome::Res(Value::string(interp.intern(&r.perm))) -} diff --git a/rust/crates/bbb-vm/src/dylib.rs b/rust/crates/bbb-vm/src/dylib.rs deleted file mode 100644 index 4a8d08e..0000000 --- a/rust/crates/bbb-vm/src/dylib.rs +++ /dev/null @@ -1,174 +0,0 @@ -//! dylib — 跨平台动态库加载(二进制库流底层)。 -//! -//! 平台适配: -//! - Linux / Android:`dlopen` / `dlsym`(.so) -//! - macOS / iOS:`dlopen` / `dlsym`(.dylib;dlopen 可用) -//! - Windows:`LoadLibraryA` / `GetProcAddress`(.dll) -//! -//! 库名归一化:声明 `Stream m & "libm.so"` 时,按当前平台尝试 -//! 候选文件名(libm.so → libm.dylib → libm.dll / m.dll), -//! 并保留原始名兜底。这样同一份 .bio 源码可以跨系统运行。 - -use std::ffi::CString; -use std::os::raw::c_char; - -/// 打开的库句柄(平台特定的不透明指针)。 -#[cfg(unix)] -pub type LibHandle = *mut std::ffi::c_void; -#[cfg(windows)] -pub type LibHandle = *mut std::ffi::c_void; - -/// 平台库名候选:把声明的名字转成当前平台可能的真实文件名。 -/// 策略:原样优先 + 平台等价后缀 + 版本化后缀剥离(libm.so.6 → libm.so)。 -pub fn candidate_names(declared: &str) -> Vec { - let mut out = Vec::new(); - let base = declared.trim(); - if base.is_empty() { - return out; - } - // 1. 原样 - out.push(base.to_string()); - - // 2. 剥离版本后缀:libm.so.6 → libm.so;foo.1.2 → foo(仅 .so/.dylib/.dll 前的版本号) - if let Some((stem, ver)) = split_version(base) { - if !ver.is_empty() && out.iter().all(|x| x != &stem) { - out.push(stem.clone()); - } - } - - // 3. 平台等价后缀 - let (stem, ext) = split_ext(base); - // ext 为纯数字版本号(libm.so.6 的 "6")时视为版本化,不按后缀处理 - let ext_is_ver = !ext.is_empty() && ext.chars().all(|c| c.is_ascii_digit()); - #[cfg(target_os = "windows")] - { - if ext != "dll" && !ext_is_ver { - out.push(format!("{stem}.dll")); - } - if let Some(rest) = stem.strip_prefix("lib") { - if !rest.is_empty() { - out.push(format!("{rest}.dll")); - } - } - // .so.6 → .dll 也试:libm → m.dll - if let Some(rest) = base.split('.').next().and_then(|s| s.strip_prefix("lib")) { - if !rest.is_empty() { - out.push(format!("{rest}.dll")); - } - } - } - #[cfg(target_os = "macos")] - { - if ext != "dylib" && !ext_is_ver { - out.push(format!("{stem}.dylib")); - } - // libfoo.so.6 → libfoo.dylib - if let Some((s, _)) = split_version(base) { - let (s2, _) = split_ext(&s); - if s2 != stem && s2 != "" { - out.push(format!("{s2}.dylib")); - } - } - } - #[cfg(all(unix, not(target_os = "macos")))] - { - if ext != "so" && !ext_is_ver { - out.push(format!("{stem}.so")); - } - // libm.so 可能是链接脚本 → 常见版本化真实库兜底 - if (ext == "so" || ext == "") && !ext_is_ver { - out.push(format!("{stem}.so.6")); - out.push(format!("{stem}.so.1")); - } - } - // 去重 - let mut seen = std::collections::HashSet::new(); - out.retain(|x| seen.insert(x.clone())); - out -} - -/// 剥离版本号:libm.so.6 → ("libm.so", "6");foo.1.2 → ("foo", "1.2")。 -/// 仅当最后一段是数字或 .so.X/.dylib.X 形态。 -fn split_version(name: &str) -> Option<(String, String)> { - let (stem, last) = name.rsplit_once('.')?; - if last.is_empty() || !last.chars().all(|c| c.is_ascii_digit()) { - return None; - } - Some((stem.to_string(), last.to_string())) -} - -fn split_ext(name: &str) -> (String, String) { - match name.rsplit_once('.') { - Some((s, e)) if !s.is_empty() => (s.to_string(), e.to_string()), - _ => (name.to_string(), String::new()), - } -} - -/// 打开动态库,返回句柄;失败返回 None。 -pub fn open(path: &str) -> Option { - let c = CString::new(path).ok()?; - #[cfg(unix)] - { - // RTLD_LAZY = 1 - let h = unsafe { dlopen(c.as_ptr() as *const c_char, 1) }; - if h.is_null() { None } else { Some(h) } - } - #[cfg(windows)] - { - let h = unsafe { LoadLibraryA(c.as_ptr() as *const c_char) }; - if h.is_null() { None } else { Some(h) } - } -} - -/// 按声明名尝试打开库:遍历候选名,第一个成功的返回。 -pub fn open_any(declared: &str) -> Option { - for name in candidate_names(declared) { - if let Some(h) = open(&name) { - return Some(h); - } - } - None -} - -/// 查找符号,返回裸指针;失败返回 None。 -pub fn symbol(h: LibHandle, name: &str) -> Option<*mut std::ffi::c_void> { - let c = CString::new(name).ok()?; - #[cfg(unix)] - { - let p = unsafe { dlsym(h, c.as_ptr() as *const c_char) }; - if p.is_null() { None } else { Some(p) } - } - #[cfg(windows)] - { - let p = unsafe { GetProcAddress(h as *mut _, c.as_ptr() as *const c_char) }; - if p.is_null() { None } else { Some(p.cast()) } - } -} - -/// 关闭动态库。 -pub fn close(h: LibHandle) { - #[cfg(unix)] - unsafe { - dlclose(h); - } - #[cfg(windows)] - unsafe { - FreeLibrary(h as *mut _); - } -} - -// ---- FFI 声明 ---- - -#[cfg(unix)] -extern "C" { - fn dlopen(filename: *const c_char, flags: i32) -> *mut std::ffi::c_void; - fn dlsym(handle: *mut std::ffi::c_void, symbol: *const c_char) -> *mut std::ffi::c_void; - fn dlclose(handle: *mut std::ffi::c_void) -> i32; -} - -#[cfg(windows)] -extern "system" { - fn LoadLibraryA(lpFileName: *const c_char) -> *mut std::ffi::c_void; - fn GetProcAddress(hModule: *mut std::ffi::c_void, lpProcName: *const c_char) -> *mut std::ffi::c_void; - fn FreeLibrary(hLibModule: *mut std::ffi::c_void) -> i32; -} diff --git a/rust/crates/bbb-vm/src/interp.rs b/rust/crates/bbb-vm/src/interp.rs deleted file mode 100644 index 81a97a2..0000000 --- a/rust/crates/bbb-vm/src/interp.rs +++ /dev/null @@ -1,1256 +0,0 @@ -//! 解释器核心(M3):作用域、流调用、表达式求值、语句执行。 - -use std::collections::HashMap; -use std::time::Instant; - -use bbb_core::arena::{StrArena, StrRef}; -use bbb_core::value::{Cause, Outcome, Tag, Value}; -use bbb_syntax::ast::*; -use bbb_syntax::parser::parse_source; - -use crate::builtin; -use crate::registry::{Registry, StreamDef, StreamKind}; -use crate::BUILTIN_CLASS_SRC; - -/// 控制流信号(语句执行结果)。 -pub enum Flow { - Next, - Ret(Outcome), - Break, - Continue, -} - -/// 调用栈帧:方法作用域 + this。 -pub struct Frame { - pub scope: HashMap, - pub this: Option, // 对象/流实例句柄 - pub method: String, // 当前方法名(电话亭递归检测) - pub booth: bool, // @call/@ucall 电话亭方法 -} - -/// 协作线程任务(顺序 join 式:spawn 注册,join 时按逆序执行) -pub struct ThreadTask { - pub id: u32, - pub name: String, // 裸方法名 - pub def_name: Option, // 所属流名 - pub args: Vec, - pub done: Option, -} - -/// 对象数据:类名 + 声明字段 + 动态属性。 -#[derive(Clone)] -pub struct ObjData { - pub def: StrRef, // 类名("Array"/"Vector"/"Solid"/用户类) - pub fields: Vec, - pub attrs: Vec<(StrRef, Value)>, -} - -impl Default for ObjData { - fn default() -> Self { - ObjData { def: StrRef::NULL, fields: Vec::new(), attrs: Vec::new() } - } -} - -/// 引用值(智能引用,&perm follow base)——11 的语义: -/// r=读 w=写 m=移动(纯 m 不可读不可写,只能 p++/Ref::move) -#[derive(Clone)] -pub enum RefTarget { - /// 变量(帧内绑定:frame 索引 + 名字,跨方法调用仍指向原存储位置) - Var { frame: usize, name: String }, - ArrElem { obj: u32, index: i64 }, // 数组元素(m 权限指针移动) - ObjProp { obj: u32, name: String }, -} - -#[derive(Clone)] -pub struct RefVal2 { - pub target: RefTarget, - pub perm: String, - pub follow: String, -} - -/// 运行结果。 -pub struct RunOutcome { - pub stdout: String, - pub unmet_needs: Vec<(String, String)>, -} - -pub struct Interp { - pub strs: StrArena, - pub objects: Vec, - pub refs: Vec, - pub reg: Registry, - pub frames: Vec, - pub stdout: String, - pub sio_buf: String, - pub timers: HashMap, - pub timer_seq: u32, - pub arrays: Vec, // Arrays 集合(对象句柄) - pub consts: Vec<(String, Value)>, - pub stream_instances: HashMap, // fork/class 流单例(持久字段状态) - pub threads: Vec, // 协作线程任务(顺序 join 式) - pub thread_seq: u32, - pub running_thread: Option, // Threads::self() - pub open_libs: Vec<(String, crate::dylib::LibHandle)>, // 已加载库:声明名 → 句柄 - pub pending_ref_perm: Option, - pub pending_ref_follow: Option, -} - -impl Interp { - pub fn new() -> Self { - Interp { - strs: StrArena::new(), - objects: Vec::new(), - refs: Vec::new(), - reg: Registry::new(), - frames: Vec::new(), - stdout: String::new(), - sio_buf: String::new(), - timers: HashMap::new(), - timer_seq: 0, - arrays: Vec::new(), - consts: Vec::new(), - stream_instances: HashMap::new(), - threads: Vec::new(), - thread_seq: 0, - running_thread: None, - open_libs: Vec::new(), - pending_ref_perm: None, - pending_ref_follow: None, - } - } - - pub fn intern(&mut self, s: &str) -> StrRef { - self.strs.push(s) - } - - fn cause(&mut self, s: &str) -> Outcome { - Outcome::Ref(Cause(self.intern(s))) - } - - // ---- 顶层入口 ---- - - /// 解释单个 Program(含 need 校验)。 - pub fn run(&mut self, prog: &Program) -> RunOutcome { - // 注入内置类(Array/Vector,Bio 语言编写) - let builtin_src = BUILTIN_CLASS_SRC.to_string(); - let (builtin_prog, errs) = parse_source(&builtin_src); - if errs.is_empty() { - self.reg.register(&builtin_prog); - } - // 用户声明:@unfork 签名流的 fork 跳过(15:启动打印拒绝) - let mut prog2 = prog.clone(); - let mut unfork_msgs = Vec::new(); - prog2.decls.retain(|d| { - if let Decl::Fork { sig, .. } = d { - let blocked = prog.decls.iter().any(|x| matches!(x, Decl::StreamSig { name, annos, .. } if name == sig && annos.contains(&"unfork".to_string()))); - if blocked { - unfork_msgs.push(format!("refused: stream {sig} is @unfork, cannot fork")); - return false; - } - } - true - }); - let mut unmet = self.reg.register(&prog2); - for m in unfork_msgs { - self.stdout.push_str(&m); - self.stdout.push('\n'); - } - // 顶层常量求值 - for d in &prog2.decls { - if let Decl::Const { name, init, .. } = d { - let v = self.eval_expr(init); - self.consts.push((name.clone(), v)); - } - } - // 执行 Main::exec - let exec = prog2 - .main - .as_ref() - .and_then(|m| m.methods.iter().find(|x| x.name == "exec")) - .cloned(); - if let Some(method) = exec { - self.frames.push(Frame { scope: HashMap::new(), this: None, method: "exec".into(), booth: false }); - let outcome = self.exec_method_body(&method); - self.frames.pop(); - // Main::exec 拒绝:自然结束(nothing)静默;显式/传播拒绝打印 ⛔ 并终止(11 的 mp=99) - if let Outcome::Ref(c) = outcome { - let cause_s = self.strs.get(c.0).to_string(); - if cause_s != "nothing" { - self.stdout.push_str(&format!("⛔ main stream refused: {cause_s}\n")); - } - } - } - // need 校验(多文件/单文件统一) - unmet.retain(|(k, n)| !self.consts.iter().any(|(cn, _)| cn == n && k == "value")); - RunOutcome { stdout: self.stdout.clone(), unmet_needs: unmet } - } - - // ---- 对象 ---- - - pub fn new_object(&mut self, def_name: &str, field_count: usize) -> u32 { - self.new_object_typed(def_name, &[]) - } - - /// 按字段类型初始化默认值:数值 0 / string "" / 其他 nil(13-need 依赖 int hp = 0)。 - pub fn new_object_typed(&mut self, def_name: &str, field_types: &[String]) -> u32 { - let def = self.intern(def_name); - let fields = field_types - .iter() - .map(|ty| match ty.as_str() { - "int" | "float" | "double" => Value::int(0), - "string" => Value::string(self.intern("")), - "char" => Value::chr(0), - "bool" => Value::boolean(false), - _ => Value::nil(), - }) - .collect(); - self.objects.push(ObjData { def, fields, attrs: Vec::new() }); - (self.objects.len() - 1) as u32 - } - - /// 对象字段查找(this 或对象值上的 Prop)。先声明字段后动态属性。 - pub fn obj_prop_get(&mut self, h: u32, name: &str) -> Option { - let def_name = self.objects[h as usize].def; - let defname_str = self.strs.get(def_name).to_string(); - if let Some(d) = self.reg.streams.get(&defname_str) { - if let Some(i) = d.field_names.iter().position(|n| n == name) { - return Some(self.objects[h as usize].fields[i]); - } - } - for (k, v) in self.objects[h as usize].attrs.iter() { - if self.strs.get(*k) == name { - return Some(*v); - } - } - None - } - - pub fn obj_prop_set(&mut self, h: u32, name: &str, v: Value) { - let nref = self.intern(name); - let def_name = self.objects[h as usize].def; - let defname_str = self.strs.get(def_name).to_string(); - if let Some(d) = self.reg.streams.get(&defname_str) { - if let Some(i) = d.field_names.iter().position(|n| n == name) { - self.objects[h as usize].fields[i] = v; - return; - } - } - let o = &mut self.objects[h as usize]; - for (k, slot) in o.attrs.iter_mut() { - if self.strs.get(*k) == name { - *slot = v; - return; - } - } - o.attrs.push((nref, v)); - } - - /// 对象类名。 - pub fn obj_class(&self, h: u32) -> String { - self.strs.get(self.objects[h as usize].def).to_string() - } - - // ---- 变量 ---- - - fn var_get(&mut self, name: &str) -> Option { - for f in self.frames.iter().rev() { - if let Some(v) = f.scope.get(name) { - return Some(*v); - } - } - for (n, v) in self.consts.iter().rev() { - if n == name { - return Some(*v); - } - } - // this 关键字 → 当前实例 - if name == "this" { - return self.current_this().map(Value::obj); - } - // 流字段(fork/class 单例字段)→ 当前 this 实例字段 - if let Some(h) = self.current_this() { - if let Some(v) = self.obj_prop_get(h, name) { - return Some(v); - } - } - // 流名 → 流值(流作为参数/对象传递:CIO、Calc...) - if self.reg.streams.contains_key(name) || builtin_stream_name(name) { - let key = format!("$stream:{name}"); - if let Some(h) = self.stream_instances.get(&key) { - return Some(Value::obj(*h)); - } - let h = self.new_object(name, 0); - self.stream_instances.insert(key, h); - return Some(Value::obj(h)); - } - None - } - - /// 只查 frame 链 + consts(不含流名/this 等解析)。 - fn var_get_raw(&mut self, name: &str) -> Option { - for f in self.frames.iter().rev() { - if let Some(v) = f.scope.get(name) { - return Some(*v); - } - } - for (n, v) in self.consts.iter().rev() { - if n == name { - return Some(*v); - } - } - None - } - - /// 引用读(get p / Ref::read):perm 无 r → 拒绝 "refused: reference is write-only, cannot read" - pub fn ref_read(&mut self, h: u32) -> Outcome { - let r = self.refs[h as usize].clone(); - // 读需要 r 权限(纯 m/纯 w 不可读——11:get mp 拒绝) - if !r.perm.contains('r') { - return Outcome::Ref(Cause(self.intern("refused: reference is write-only, cannot read"))); - } - match &r.target { - RefTarget::Var { frame, name } => { - let v = self - .frames - .get(*frame) - .and_then(|f| f.scope.get(name)) - .copied() - .or_else(|| self.var_get_raw(name)) - .unwrap_or(Value::nil()); - Outcome::Res(v) - } - RefTarget::ArrElem { obj, index } => { - self.invoke_on_obj(*obj, "get", vec![Value::int(*index)]) - } - RefTarget::ObjProp { obj, name } => { - let v = self.obj_prop_get(*obj, name).unwrap_or(Value::nil()); - Outcome::Res(v) - } - } - } - - /// 引用写(p = v / Ref::write):perm 无 w → 拒绝 "refused: reference is read-only, cannot write" - pub fn ref_write(&mut self, h: u32, v: Value) -> Outcome { - let r = self.refs[h as usize].clone(); - if !r.perm.contains('w') { - return Outcome::Ref(Cause(self.intern("refused: reference is read-only, cannot write"))); - } - match &r.target { - RefTarget::Var { frame, name } => { - if let Some(f) = self.frames.get_mut(*frame) { - f.scope.insert(name.clone(), v); - } else { - self.var_set(name, v); - } - Outcome::Res(Value::nil()) - } - RefTarget::ArrElem { obj, index } => { - self.invoke_on_obj(*obj, "set", vec![Value::int(*index), v]); - Outcome::Res(Value::nil()) - } - RefTarget::ObjProp { obj, name } => { - self.obj_prop_set(*obj, name, v); - Outcome::Res(Value::nil()) - } - } - } - - /// 引用移动(Ref::move / p++):perm 无 m → 拒绝;数组越界 → 拒绝 - pub fn ref_move(&mut self, h: u32) -> Outcome { - let r = self.refs[h as usize].clone(); - if !r.perm.contains('m') { - return Outcome::Ref(Cause(self.intern("Ref refused: reference has no move permission"))); - } - if let RefTarget::ArrElem { obj, index } = r.target { - let cls = self.obj_class(obj); - let len = if cls == "Array" || cls == "Vector" { - match self.invoke_on_obj(obj, "len", vec![]) { - Outcome::Res(v) => v.as_int_or_num() as i64, - Outcome::Ref(_) => 0, - } - } else { - 0 - }; - if index + 1 >= len { - return Outcome::Ref(Cause(self.intern("refused: reference moved out of bounds"))); - } - self.refs[h as usize].target = RefTarget::ArrElem { obj, index: index + 1 }; - Outcome::Res(Value::nil()) - } else { - Outcome::Ref(Cause(self.intern("Ref refused: reference is not a moving pointer"))) - } - } - - /// 引用变量赋值语句(rw = 5)→ 引用写;拒绝则传播。 - fn ref_assign(&mut self, _name: &str, old: Value, v: Value) -> Flow { - let h = old.as_handle(); - match self.ref_write(h, v) { - Outcome::Res(_) => Flow::Next, - Outcome::Ref(c) => Flow::Ret(Outcome::Ref(c)), - } - } - - fn var_set(&mut self, name: &str, v: Value) { - for f in self.frames.iter_mut().rev() { - if f.scope.contains_key(name) { - f.scope.insert(name.to_string(), v); - return; - } - } - // 未声明:写入当前帧(宽松;标准:变量须先声明) - if let Some(f) = self.frames.last_mut() { - f.scope.insert(name.to_string(), v); - } - } - - // ---- 方法调用 ---- - - /// 执行方法体(当前帧已压栈)。返回 res/ref 或默认 ref(nothing)。 - fn exec_method_body(&mut self, method: &Method) -> Outcome { - for st in &method.body { - match self.exec_stmt(st) { - Flow::Ret(o) => return o, - Flow::Break => return self.cause("break outside loop"), - Flow::Continue => return self.cause("continue outside loop"), - Flow::Next => {} - } - } - self.cause("nothing") - } - - /// 调用方法(def 为所属流,this 为实例句柄)。 - fn call_method( - &mut self, - def: Option, - method: Method, - this: Option, - args: Vec, - ) -> Outcome { - let _ = def; - let mut scope = HashMap::new(); - for (p, a) in method.params.iter().zip(args.iter()) { - scope.insert(p.name.clone(), *a); - } - self.frames.push(Frame { scope, this, method: method.name.clone(), booth: !method.annos.is_empty() && (method.annos.contains(&"call".to_string()) || method.annos.contains(&"ucall".to_string())) }); - // 电话亭:@call/@ucall 方法递归拒绝(16) - let name = method.name.clone(); - if self.frames.iter().filter(|f| f.method == name && f.booth).count() > 1 { - self.frames.pop(); - return self.cause(&format!("refused: phone-booth method {name} does not support recursion")); - } - let r = self.exec_method_body(&method); - self.frames.pop(); - r - } - - /// 调用表达式:Outcome → Value(refused 位 + cause)。 - fn call_to_value(&mut self, qual: Option<&str>, name: &str, args: Vec) -> Value { - match self.invoke(qual, name, args) { - Outcome::Res(v) => v, - Outcome::Ref(c) => Value::refused_str(c.0), - } - } - - /// 统一调用入口:内置流 → 对象方法 → 流方法 → 裸方法 → 拒绝。 - fn invoke(&mut self, qual: Option<&str>, name: &str, args: Vec) -> Outcome { - if let Some(q) = qual { - // 内置流 - if builtin::lookup(q, name).is_some() { - let f = builtin::lookup(q, name).unwrap(); - return f(self, &args); - } - // this::method - if q == "this" { - if let Some(h) = self.current_this() { - return self.invoke_on_obj(h, name, args); - } - return self.cause("no this context"); - } - // 流名 - if let Some(def) = self.reg.resolve_qual(q).cloned() { - // StreamBin:库方法优先 dl(签名成员 = 声明导出符号,无 Bio 体) - if let Some(lib) = def.bin_file.clone() { - // 带 body 的 Bio 方法仍走 Bio 执行 - let has_body = def.methods.get(name).map(|m| !m.body.is_empty()).unwrap_or(false); - if !has_body { - return self.dl_call(&lib, name, args); - } - } - if let Some(m) = def.methods.get(name).cloned() { - // @onlyread:@write 注解方法拒绝(15) - if def.annos.contains(&"onlyread".to_string()) - && m.annos.contains(&"write".to_string()) { - return self.cause(&format!("refused: stream {} is @onlyread — {name}() is a write method", def.name)); - } - let singleton = self.stream_instance(&def); - return self.call_method(Some(def), m, singleton, args); - } - // StreamBin:Bio 方法体之外回退 dlsym(14) - if let Some(lib) = def.bin_file.clone() { - return self.dl_call(&lib, name, args); - } - } - // 变量(对象值 / 流值) - if let Some(v) = self.var_get(q) { - if let Tag::Obj | Tag::Arr = v.tag() { - let h = v.as_handle(); - let cls = self.obj_class(h); - // 内置流实例(cio CIO 传参) - if let Some(bf) = builtin::lookup(&cls, name) { - if cls == "Solid" || cls == "SolidData" { - // Solid 方法需要 self 作第一个参数(旧 C:all[0] = self) - let mut a2 = Vec::with_capacity(args.len() + 1); - a2.push(v); - a2.extend_from_slice(&args); - return bf(self, &a2); - } - return bf(self, &args); - } - if self.reg.class(&cls).is_some() || cls == "Solid" { - return self.invoke_on_obj(h, name, args); - } - } - } - return self.cause(&format!("stream {q} refuses: no method {name}")); - } else { - // 裸调用:先查当前流上下文(04:流内 bare call),再全局方法名 - if let Some(h) = self.current_this() { - let cls = self.obj_class(h); - // 内部方法 __sort__:对 this 的 Solid 数据原地排序(sort() 委托) - if name == "__sort__" { - if let Some(dh) = self.solid_data_handle(h) { - self.sort_data(dh); - return Outcome::Res(Value::nil()); - } - return self.cause("no data to sort"); - } - if let Some(def) = self.reg.streams.get(&cls).cloned() { - if let Some(m) = def.methods.get(name).cloned() { - if !m.body.is_empty() { - return self.call_method(Some(def), m, Some(h), args); - } - } - } - } - let found = self.reg.find_bare_method(name).map(|(d, m)| (d.clone(), m.clone())); - if let Some((def, m)) = found { - let singleton = self.stream_instance(&def); - return self.call_method(Some(def), m, singleton, args); - } - return self.cause(&format!("no method {name}")); - } - } - - fn current_this(&self) -> Option { - self.frames.iter().rev().find_map(|f| f.this) - } - - /// 对象方法调用(a::set(0,10) / h::getHp())。 - pub fn invoke_on_obj(&mut self, h: u32, name: &str, args: Vec) -> Outcome { - let cls = self.obj_class(h); - if let Some(def) = self.reg.class(&cls).cloned() { - if let Some(m) = def.methods.get(name).cloned() { - return self.call_method(Some(def), m, Some(h), args); - } - } - self.cause(&format!("object {cls} refuses: no method {name}")) - } - - /// 执行一个线程任务(裸方法),结果存 task.done。 - pub fn run_thread(&mut self, id: u32) -> Outcome { - if let Some(t) = self.threads.iter().find(|t| t.id == id) { - if let Some(d) = t.done { - return d; - } - } - let Some(idx) = self.threads.iter().position(|t| t.id == id) else { - return self.cause("no such thread"); - }; - let name = self.threads[idx].name.clone(); - let def_name = self.threads[idx].def_name.clone(); - let args = self.threads[idx].args.clone(); - let found = if let Some(dn) = &def_name { - self.reg.streams.get(dn).cloned() - .and_then(|d| d.methods.get(&name).cloned().map(|m| (d, m))) - .or_else(|| self.reg.find_bare_method(&name).map(|(d, m)| (d.clone(), m.clone()))) - } else { - self.reg.find_bare_method(&name).map(|(d, m)| (d.clone(), m.clone())) - }; - let old_running = self.running_thread; - self.running_thread = Some(id); - let out = if let Some((def, m)) = found { - let singleton = self.stream_instance(&def); - self.call_method(Some(def), m, singleton, args) - } else { - self.cause(&format!("no method {name}")) - }; - self.running_thread = old_running; - if let Some(t) = self.threads.iter_mut().find(|t| t.id == id) { - t.done = Some(out); - } - out - } - - /// 流实例:fork/class 有**持久单例**(流级字段状态跨调用保持);signature/main 无。 - fn stream_instance(&mut self, def: &StreamDef) -> Option { - match def.kind { - StreamKind::Fork | StreamKind::Class => { - let key = def.name.clone(); - if let Some(h) = self.stream_instances.get(&key) { - return Some(*h); - } - let h = self.new_object_typed(&def.name, &def.field_types); - self.stream_instances.insert(key, h); - Some(h) - } - _ => None, - } - } - - // ---- 表达式 ---- - - pub fn eval_expr(&mut self, e: &Expr) -> Value { - match e { - Expr::Int(v) => Value::int(*v), - Expr::Float(v) => Value::num(*v), - Expr::Str(s) => Value::string(self.intern(s)), - Expr::Char(c) => Value::chr(*c), - Expr::Bool(b) => Value::boolean(*b), - Expr::Var(name) => self - .var_get(name) - .unwrap_or_else(|| Value::nil()), - Expr::Call { qual, name, args } => { - let vals: Vec = args.iter().map(|a| self.eval_expr(a)).collect(); - self.call_to_value(qual.as_deref(), name, vals) - } - Expr::Prop { base, name } => { - if name == "res" { - // Solid::new().res — 取响应值本身 - return self.eval_expr(base); - } - let bv = self.eval_expr(base); - match bv.tag() { - Tag::Obj | Tag::Arr => { - let h = bv.as_handle(); - self.obj_prop_get(h, name).unwrap_or(Value::nil()) - } - Tag::Ref => Value::nil(), - _ => Value::nil(), - } - } - Expr::Index { base, idx } => { - let bv = self.eval_expr(base); - let i = self.eval_expr(idx); - self.index_get(bv, i) - } - Expr::BinOp { op, l, r } => { - let lv = self.eval_expr(l); - let rv = self.eval_expr(r); - self.binop(op, lv, rv) - } - Expr::Unwrap { op, l } => { - let v = self.eval_expr(l); - if op == "get" { - // 引用值 → 引用读(11:get rw / get mp) - if v.tag() == Tag::Ref { - return match self.ref_read(v.as_handle()) { - Outcome::Res(val) => val, - Outcome::Ref(c) => Value::refused_str(c.0), - }; - } - // 拒绝传播(11:get mp 打印 refused: refused: ...) - v - } else { - // cause - if v.refused() { - Value::string(v.cause()) - } else { - Value::string(self.intern("")) - } - } - } - Expr::New { cls, args } => { - let vals: Vec = args.iter().map(|a| self.eval_expr(a)).collect(); - self.new_class(cls, vals) - } - Expr::NewArray { ty: _ty, size } => { - let n = self.eval_expr(size); - let n = n.as_int_or_num() as usize; - self.new_class("Array", vec![Value::int(n as i64)]) - } - Expr::RefOf(target) => self.make_ref(target), - } - } - - /// dlopen 调用导出符号(double fn(double...) -> double,14 用)。 - pub fn dl_call(&mut self, lib: &str, sym: &str, args: Vec) -> Outcome { - // 已加载缓存:声明名 → 句柄(多库独立,不串) - let cached = self.open_libs.iter().find(|(name, _)| name == lib).map(|(_, h)| *h); - let handle = match cached { - Some(h) => h, - None => { - match crate::dylib::open_any(lib) { - Some(h) => { - self.open_libs.push((lib.to_string(), h)); - h - } - None => { - return self.cause(&format!("cannot open library {lib}")); - } - } - } - }; - let Some(fptr) = crate::dylib::symbol(handle, sym) else { - return self.cause(&format!("stream {lib} refuses: no symbol {sym}")); - }; - let nums: Vec = args.iter().map(|a| a.as_int_or_num()).collect(); - unsafe { - let result: f64 = match nums.len() { - 0 => std::mem::transmute::<*mut core::ffi::c_void, fn() -> f64>(fptr)(), - 1 => std::mem::transmute::<*mut core::ffi::c_void, fn(f64) -> f64>(fptr)(nums[0]), - _ => std::mem::transmute::<*mut core::ffi::c_void, fn(f64, f64) -> f64>(fptr)(nums[0], nums[1]), - }; - Outcome::Res(Value::num(result)) - } - } - - pub fn new_class(&mut self, cls: &str, args: Vec) -> Value { - if let Some(def) = self.reg.class(cls).cloned() { - // @unfork 类拒绝(15) - if def.annos.contains(&"unfork".to_string()) { - let c = self.intern(&format!("Obj refused: class {cls} is @unfork, cannot fork")); - return Value::refused_str(c); - } - let h = self.new_object_typed(cls, &def.field_types); - if let Some(m) = def.methods.get("__init__").cloned() { - self.call_method(Some(def), m, Some(h), args); - } - Value::obj(h) - } else { - // 内置类(Array/Vector 已注入 registry;未知类拒绝) - Value::nil() - } - } - - fn make_ref(&mut self, target: &Expr) -> Value { - let perm = self.pending_ref_perm.take().unwrap_or_else(|| "rw".into()); - let follow = self.pending_ref_follow.take().unwrap_or_else(|| "u".into()); - let t = match target { - Expr::Var(name) => RefTarget::Var { frame: self.frames.len().saturating_sub(1), name: name.clone() }, - Expr::Index { base, idx } => { - let bv = self.eval_expr(base); - let i = self.eval_expr(idx).as_int_or_num() as i64; - if let Tag::Obj | Tag::Arr = bv.tag() { - RefTarget::ArrElem { obj: bv.as_handle(), index: i } - } else { - RefTarget::Var { frame: self.frames.len().saturating_sub(1), name: "?".into() } - } - } - Expr::Prop { base, name } => { - let bv = self.eval_expr(base); - if let Tag::Obj | Tag::Arr = bv.tag() { - RefTarget::ObjProp { obj: bv.as_handle(), name: name.clone() } - } else { - RefTarget::Var { frame: self.frames.len().saturating_sub(1), name: "?".into() } - } - } - _ => RefTarget::Var { frame: self.frames.len().saturating_sub(1), name: "?".into() }, - }; - self.refs.push(RefVal2 { target: t, perm, follow }); - Value::reff((self.refs.len() - 1) as u32) - } - - fn index_get(&mut self, base: Value, idx: Value) -> Value { - let i = idx.as_int_or_num() as i64; - match base.tag() { - Tag::Obj | Tag::Arr => { - let h = base.as_handle(); - let cls = self.obj_class(h); - if cls == "Solid" || cls == "SolidData" { - // 裸数组 / Solid 数据:直接下标读 - let data = self.solid_data(h); - if i < 0 || i as usize >= data.len() { - return Value::nil(); - } - data[i as usize] - } else { - // Array/Vector:调对象 get 方法 - self.invoke_on_obj(h, "get", vec![Value::int(i)]).get() - } - } - _ => Value::nil(), - } - } - - fn binop(&mut self, op: &str, l: Value, r: Value) -> Value { - // 拒绝传播保留 cause(16:get down(...) 拒绝后 + 1 仍带原因) - if l.refused() { - return l; - } - if r.refused() { - return r; - } - match op { - "+" => match (l.tag(), r.tag()) { - (Tag::Int, Tag::Int) => Value::int(l.as_int_or_num() as i64 + r.as_int_or_num() as i64), - _ => Value::num(l.as_int_or_num() + r.as_int_or_num()), - }, - "-" => match (l.tag(), r.tag()) { - (Tag::Int, Tag::Int) => Value::int(l.as_int_or_num() as i64 - r.as_int_or_num() as i64), - _ => Value::num(l.as_int_or_num() - r.as_int_or_num()), - }, - "*" => match (l.tag(), r.tag()) { - (Tag::Int, Tag::Int) => Value::int(l.as_int_or_num() as i64 * r.as_int_or_num() as i64), - _ => Value::num(l.as_int_or_num() * r.as_int_or_num()), - }, - "/" => { - let d = r.as_int_or_num(); - if d == 0.0 { - return Value::nil().with_refused(); - } - match (l.tag(), r.tag()) { - (Tag::Int, Tag::Int) => Value::int(l.as_int_or_num() as i64 / d as i64), - _ => Value::num(l.as_int_or_num() / d), - } - } - "%" => { - let d = r.as_int_or_num() as i64; - if d == 0 { - return Value::nil().with_refused(); - } - Value::int(l.as_int_or_num() as i64 % d) - } - "==" => Value::boolean(self.val_cmp(&l, &r) == std::cmp::Ordering::Equal), - "!=" => Value::boolean(self.val_cmp(&l, &r) != std::cmp::Ordering::Equal), - "<" => Value::boolean(self.val_cmp(&l, &r) == std::cmp::Ordering::Less), - ">" => Value::boolean(self.val_cmp(&l, &r) == std::cmp::Ordering::Greater), - "<=" => Value::boolean(self.val_cmp(&l, &r) != std::cmp::Ordering::Greater), - ">=" => Value::boolean(self.val_cmp(&l, &r) != std::cmp::Ordering::Less), - _ => Value::nil(), - } - } - - fn val_cmp(&self, l: &Value, r: &Value) -> std::cmp::Ordering { - if !matches!((l.tag(), r.tag()), (Tag::Int | Tag::Num, Tag::Int | Tag::Num)) { - return std::cmp::Ordering::Equal; - } - l.as_int_or_num().partial_cmp(&r.as_int_or_num()).unwrap_or(std::cmp::Ordering::Equal) - } - - // ---- 语句 ---- - - pub fn exec_stmt(&mut self, s: &Stmt) -> Flow { - match s { - Stmt::If { cond, then, els } => { - let c = self.eval_expr(cond); - if c.truthy() { - self.exec_block(then) - } else if let Some(e) = els { - self.exec_block(e) - } else { - Flow::Next - } - } - Stmt::While { cond, body } => { - loop { - let c = self.eval_expr(cond); - if !c.truthy() { - return Flow::Next; - } - match self.exec_block(body) { - Flow::Break => return Flow::Next, - Flow::Continue => continue, - Flow::Ret(o) => return Flow::Ret(o), - Flow::Next => {} - } - } - } - Stmt::For { init, cond, update, body } => { - if let Some(i) = init { - if let Flow::Ret(o) = self.exec_stmt(i) { - return Flow::Ret(o); - } - } - loop { - if let Some(c) = cond { - if !self.eval_expr(c).truthy() { - return Flow::Next; - } - } - match self.exec_block(body) { - Flow::Break => return Flow::Next, - Flow::Continue => { - if let Some(u) = update { - if let Flow::Ret(o) = self.exec_stmt(u) { - return Flow::Ret(o); - } - } - continue; - } - Flow::Ret(o) => return Flow::Ret(o), - Flow::Next => {} - } - if let Some(u) = update { - if let Flow::Ret(o) = self.exec_stmt(u) { - return Flow::Ret(o); - } - } - } - } - Stmt::Break => Flow::Break, - Stmt::Continue => Flow::Continue, - Stmt::Ret { kind, values } => match kind { - RetKind::Res => { - let mut vals: Vec = values.iter().map(|v| self.eval_expr(v)).collect(); - match vals.len() { - 0 => Flow::Ret(Outcome::Res(Value::nil())), - 1 => Flow::Ret(Outcome::Res(vals.remove(0))), - _ => { - // 多值 → 数组(Solid) - let h = self.solid_new(vals); - Flow::Ret(Outcome::Res(Value::obj(h))) - } - } - } - RetKind::Ref => { - let reason = values - .first() - .map(|v| self.eval_expr(v)) - .map(|v| match v.tag() { - Tag::Str => v.as_str(), - _ => { - let s = self.fmt_value(&v); - self.intern(&s) - } - }) - .unwrap_or_else(|| self.intern("nothing")); - Flow::Ret(Outcome::Ref(Cause(reason))) - } - }, - Stmt::Assign { vtype, is_const, is_thread, target, op, value } => { - let _ = (is_const, is_thread); - let v = self.eval_expr(value); - // 拒绝传播:非 ALL 声明赋值右侧拒绝 → 方法拒绝(11:mp = 99 → ⛔) - // nothing(void 方法自然结束)不传播 - if v.refused() && vtype.as_deref() != Some("ALL") - && self.strs.get(v.cause()) != "nothing" { - return Flow::Ret(Outcome::Ref(Cause(v.cause()))); - } - // 赋值目标是引用变量 → 引用写(rw = 5) - if let AssignTarget::Var(name) = target { - if op == "=" && vtype.is_none() { - if let Some(old) = self.var_get_raw(name) { - if old.tag() == Tag::Ref { - return self.ref_assign(name, old, v); - } - } - } - } - match target { - AssignTarget::Var(name) => { - let v = if op == "=" { - v - } else { - let old = self.var_get(name).unwrap_or(Value::nil()); - self.binop(&op[..1], old, v) - }; - if vtype.is_some() { - if let Some(f) = self.frames.last_mut() { - f.scope.insert(name.clone(), v); - } - } else { - self.var_set(name, v); - } - } - AssignTarget::Prop { base, name } => { - let bv = self.eval_expr(base); - if let Tag::Obj | Tag::Arr = bv.tag() { - let h = bv.as_handle(); - let v = if op == "=" { - v - } else { - let old = self.obj_prop_get(h, name).unwrap_or(Value::nil()); - self.binop(&op[..1], old, v) - }; - self.obj_prop_set(h, name, v); - } - } - AssignTarget::Index { base, idx } => { - let bv = self.eval_expr(base); - let i = self.eval_expr(idx); - self.index_set(bv, i, v); - } - } - Flow::Next - } - Stmt::RefDecl { perm, follow, base, name, init } => { - let _ = base; - self.pending_ref_perm = Some(perm.clone()); - self.pending_ref_follow = Some(follow.clone()); - let rv = self.eval_expr(init); - if let Some(f) = self.frames.last_mut() { - f.scope.insert(name.clone(), rv); - } - Flow::Next - } - Stmt::Inc { name, op } => { - let old = self.var_get(name).unwrap_or(Value::nil()); - let delta = if op == "++" { 1 } else { -1 }; - if old.tag() == Tag::Ref { - // m 权限指针移动(11:mp++) - let h = old.as_handle(); - if !self.refs[h as usize].perm.contains('m') { - return Flow::Ret(Outcome::Ref(Cause(self.intern("reference has no move permission")))); - } - if let RefTarget::ArrElem { obj, index } = self.refs[h as usize].target.clone() { - self.refs[h as usize].target = RefTarget::ArrElem { obj, index: index + delta }; - } - Flow::Next - } else { - let nv = match old.tag() { - Tag::Int => Value::int(old.as_int_or_num() as i64 + delta), - Tag::Num => Value::num(old.as_int_or_num() + delta as f64), - _ => old, - }; - self.var_set(name, nv); - Flow::Next - } - } - Stmt::Expr(e) => { - let v = self.eval_expr(e); - // 调用语句拒绝 → 传播(nothing = void 自然结束,不传播) - if v.refused() && matches!(e, Expr::Call { .. }) - && self.strs.get(v.cause()) != "nothing" { - return Flow::Ret(Outcome::Ref(Cause(v.cause()))); - } - Flow::Next - } - } - } - - fn exec_block(&mut self, stmts: &[Stmt]) -> Flow { - for s in stmts { - match self.exec_stmt(s) { - Flow::Next => {} - other => return other, - } - } - Flow::Next - } - - fn index_set(&mut self, base: Value, idx: Value, v: Value) { - let i = idx.as_int_or_num() as i64; - match base.tag() { - Tag::Obj | Tag::Arr => { - let h = base.as_handle(); - let cls = self.obj_class(h); - if cls == "Solid" { - if let Some(d) = self.objects.get_mut(h as usize) { - if let Some(slot) = d.attrs.iter().position(|(k, _)| self.strs.get(*k) == "$data") { - // Solid 数据存 attrs 的 "$data" 键(builtin 约定) - let _ = slot; - } - let _ = (d, i, v); - } - } else { - self.invoke_on_obj(h, "set", vec![Value::int(i), v]); - } - } - _ => {} - } - } - - /// 创建 Solid 实例(多值返回/内置)。 - pub fn solid_new(&mut self, data: Vec) -> u32 { - let def = self.intern("Solid"); - self.objects.push(ObjData { def, fields: vec![], attrs: Vec::new() }); - let h = (self.objects.len() - 1) as u32; - // 数据存 attrs "$data" - let key = self.intern("$data"); - let dh = self.objs_data_handle(data); - self.objects[h as usize].attrs.push((key, Value::arr(dh))); - h - } - - pub(crate) fn objs_data_handle(&mut self, data: Vec) -> u32 { - // 数据 Vec 存独立 "Data" 对象 - let def = self.intern("SolidData"); - self.objects.push(ObjData { def, fields: data, attrs: Vec::new() }); - (self.objects.len() - 1) as u32 - } - - // ---- 值格式化(打印/字符串化) ---- - - pub fn fmt_value(&mut self, v: &Value) -> String { - if v.refused() { - return format!("refused: {}", self.strs.get(v.cause())); - } - match v.tag() { - Tag::Nil => "nil".to_string(), - Tag::Int => v.as_int_or_num().to_string(), - Tag::Num => { - let f = v.as_int_or_num(); - let s = format!("{f}"); - s - } - Tag::Bool => if v.as_bool() { "true" } else { "false" }.to_string(), - Tag::Str => self.strs.get(v.as_str()).to_string(), - Tag::Char => (v.as_char() as char).to_string(), - Tag::Obj | Tag::Arr => { - let h = v.as_handle(); - self.fmt_object(h) - } - Tag::Ref => "".to_string(), - } - } - - fn fmt_object(&mut self, h: u32) -> String { - let cls = self.obj_class(h); - if cls == "Solid" || cls == "SolidData" { - let data = self.solid_data(h).to_vec(); - return format!("[{}]", data.iter().map(|x| self.fmt_value(x)).collect::>().join(", ")); - } - if cls == "Array" || cls == "Vector" { - // data 属性(this::data = Solid::new().res)或声明字段 - let o = &self.objects[h as usize]; - let mut data_h = o.fields.first().map(|v| v.as_handle()); - if data_h.is_none() { - for (k, v) in &o.attrs { - if self.strs.get(*k) == "data" { - if let Tag::Obj | Tag::Arr = v.tag() { - data_h = Some(v.as_handle()); - } - } - } - } - if let Some(dh) = data_h { - let data = self.solid_data(dh).to_vec(); - return format!("[{}]", data.iter().map(|x| self.fmt_value(x)).collect::>().join(", ")); - } - } - // 一般对象: - let o = self.objects[h as usize].clone(); - let mut parts = Vec::new(); - for (k, val) in &o.attrs { - let kn = self.strs.get(*k).to_string(); - if kn == "$data" { - continue; - } - parts.push(format!("{kn}: {}", self.fmt_value(val))); - } - format!("", cls, parts.join(", ")) - } - - /// Solid/SolidData 的数据读取(借用处理:复制出来)。 - pub fn solid_data(&self, h: u32) -> Vec { - let o = &self.objects[h as usize]; - let cls = self.strs.get(o.def).to_string(); - if cls == "Solid" { - // attrs "$data" → Arr 句柄 → SolidData - for (k, v) in &o.attrs { - if self.strs.get(*k) == "$data" { - if let Tag::Arr = v.tag() { - return self.objects[v.as_handle() as usize].fields.clone(); - } - } - } - Vec::new() - } else { - o.fields.clone() - } - } - - /// 修改 Solid 数据。 - pub fn solid_set(&mut self, h: u32, i: usize, v: Value) { - let data_h = self.solid_data_handle(h); - if let Some(dh) = data_h { - self.objects[dh as usize].fields[i] = v; - } - } - - pub fn solid_data_handle(&self, h: u32) -> Option { - let o = &self.objects[h as usize]; - let cls = self.strs.get(o.def).to_string(); - if cls == "SolidData" { - return Some(h); - } - // Array/Vector 类:data 字段(this::data = Solid::new().res)→ 继续解 Solid 的 $data - if let Some(d) = self.reg.streams.get(&cls) { - if let Some(i) = d.field_names.iter().position(|n| n == "data") { - if let Tag::Obj | Tag::Arr = o.fields[i].tag() { - let solid_h = o.fields[i].as_handle(); - if let Some(dh) = self.solid_data_handle(solid_h) { - return Some(dh); - } - } - } - } - for (k, v) in &o.attrs { - let kn = self.strs.get(*k); - if (kn == "$data" || kn == "data") { - if let Tag::Obj | Tag::Arr = v.tag() { - let solid_h = v.as_handle(); - if let Some(dh) = self.solid_data_handle(solid_h) { - return Some(dh); - } - } - } - } - None - } - - pub fn solid_push(&mut self, h: u32, v: Value) { - let data_h = self.solid_data_handle(h); - if let Some(dh) = data_h { - self.objects[dh as usize].fields.push(v); - } - } - - /// 原地排序 Solid 数据(__sort__ 内部方法):数字升序 → 字符串字典序 → 其余保持稳定序。 - pub fn sort_data(&mut self, dh: u32) { - use std::cmp::Ordering; - // 先复制出来排序,避免借用冲突 - let mut vals = self.objects[dh as usize].fields.clone(); - vals.sort_by(|a, b| { - let ka = self.sort_key(a); - let kb = self.sort_key(b); - match (ka, kb) { - (Some(x), Some(y)) => x.partial_cmp(&y).unwrap_or(Ordering::Equal), - (Some(_), None) => Ordering::Less, - (None, Some(_)) => Ordering::Greater, - (None, None) => Ordering::Equal, - } - }); - self.objects[dh as usize].fields = vals; - } - - fn sort_key(&self, v: &Value) -> Option { - match v.tag() { - Tag::Int | Tag::Num => Some(v.as_int_or_num()), - Tag::Str => { - // 字符串字典序:用字符串池内容编码成可比较键(前缀优先) - let s = self.strs.get(v.as_str()); - // 用前 4 字符的字节值编码(大端),保证字典序近似;短串自然靠前 - let b = s.as_bytes(); - let mut key = 0.0f64; - for (i, c) in b.iter().take(4).enumerate() { - key += (*c as f64) * 256f64.powi(3 - i as i32); - } - // 长度作为微小尾数(保证短串 < 长串同前缀) - Some(key + (s.len() as f64) * 1e-9) - } - _ => None, - } - } -} - -impl Default for Interp { - fn default() -> Self { - Self::new() - } -} - -/// 内置流名(流可作为值传递)。 -pub fn builtin_stream_name(name: &str) -> bool { - matches!(name, "CIO" | "SIO" | "FIO" | "IO" | "Com" | "Time" | "Obj" | "Solid" | "Arrays" | "Ref" | "Threads" | "Taskm") -} diff --git a/rust/crates/bbb-vm/src/lib.rs b/rust/crates/bbb-vm/src/lib.rs deleted file mode 100644 index 167f738..0000000 --- a/rust/crates/bbb-vm/src/lib.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! bbb-vm — BioLang 解释器(M3)。 -//! -//! 设计(对齐旧 C interp.c + examples 语义): -//! - 流注册表:签名流/分叉/类/Main 统一登记,方法名解析(qual::name → -//! 流方法 → 签名回退分叉 → 对象方法;裸调用 → 全局方法名扫描); -//! - 值语义:Value 16B(bbb-core),调用结果带 REFUSED 位(cause 为 -//! 字符串句柄),`get`/`cause` 是位测试; -//! - 对象:arena 句柄(ObjData = 类定义 + 字段 + 动态属性), -//! Array/Vector 是注入的 Bio 类源码(底层 Solid 流,Rust 实现); -//! - 控制流:Flow 枚举(Next/Ret/Break/Continue)驱动语句块。 - -pub mod builtin; -pub mod dylib; -pub mod interp; -pub mod project; -pub mod registry; - -pub use interp::{Interp, RunOutcome}; -pub use project::load_project_sources; -pub use registry::Registry; - -/// 预置的 Array/Vector 类源码(Bio 语言编写,与旧 C 注入的类一致)。 -pub const BUILTIN_CLASS_SRC: &str = r#" -Class Array { - void __init__(n int) { - this::data = Solid::new().res; - ALL i = 0; - while (i < n) { Solid::push(this::data, 0); i = i + 1; } - Arrays::add(this); - } - int len() { res Solid::len(this::data); } - void set(i int, v) { Solid::set(this::data, i, v); } - int get(i int) { res Solid::get(this::data, i); } - void push(v) { Solid::push(this::data, v); } - string join(sep string) { res Solid::join(this::data, sep); } - void sort() { __sort__(); } -} -Class Vector { - void __init__() { this::data = Solid::new().res; Arrays::add(this); } - int len() { res Solid::len(this::data); } - void set(i int, v) { Solid::set(this::data, i, v); } - int get(i int) { res Solid::get(this::data, i); } - void push(v) { Solid::push(this::data, v); } - void sort() { __sort__(); } -} -"#; diff --git a/rust/crates/bbb-vm/src/project.rs b/rust/crates/bbb-vm/src/project.rs deleted file mode 100644 index 13174e5..0000000 --- a/rust/crates/bbb-vm/src/project.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! 项目加载:目录模式(package.toml + src/ + utils/ 合并解析)。 - -use std::path::PathBuf; - -use bbb_syntax::ast::{Decl, Program}; -use bbb_syntax::parser::parse_source; - -/// 加载项目所有 .bio 文件并合并成一个 Program。 -/// need 跨文件配对由 registry.register 统一校验。 -pub fn load_project_sources(root: &PathBuf) -> Result { - let mut files: Vec = Vec::new(); - for base in ["src", "utils"] { - let d = root.join(base); - if !d.is_dir() { - continue; - } - if let Ok(entries) = std::fs::read_dir(&d) { - for e in entries.flatten() { - let p = e.path(); - if p.extension().map(|x| x == "bio" || x == "bl").unwrap_or(false) { - files.push(p); - } - } - } - } - files.sort(); - if files.is_empty() { - return Err(format!("{}: no .bio files under src/ or utils/", root.display())); - } - - let mut decls: Vec = Vec::new(); - let mut main = None; - let mut kind = String::new(); - for f in &files { - let src = std::fs::read_to_string(f) - .map_err(|e| format!("{}: read failed: {e}", f.display()))?; - let (prog, errs) = parse_source(&src); - if !errs.is_empty() { - return Err(format!( - "{}: {}", - f.display(), - errs.iter().map(|e| e.to_string()).collect::>().join("; ") - )); - } - if !prog.kind.is_empty() { - kind = prog.kind.clone(); - } - if prog.main.is_some() { - if main.is_some() { - return Err(format!("{}: multiple Main stream definitions", f.display())); - } - main = prog.main; - } - decls.extend(prog.decls); - } - Ok(Program { kind, decls, main }) -} diff --git a/rust/crates/bbb-vm/src/registry.rs b/rust/crates/bbb-vm/src/registry.rs deleted file mode 100644 index 36865cf..0000000 --- a/rust/crates/bbb-vm/src/registry.rs +++ /dev/null @@ -1,216 +0,0 @@ -//! 流注册表:把 Program 的声明构建成可调用的流/方法表。 - -use std::collections::HashMap; - -use bbb_syntax::ast::{Decl, Member, Method, Program}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum StreamKind { - Signature, // Stream X { ... } 仅签名 - Fork, // Sig X { ... } 实现 - Class, // Class X { ... } - Binary, // Stream X & "lib.so"(本轮仅注册,调用拒绝) - Main, // Main 流 -} - -#[derive(Debug, Clone)] -pub struct StreamDef { - pub name: String, - pub kind: StreamKind, - pub sig: Option, // fork 的签名流名 - pub bin_file: Option, // StreamBin 的库文件 - pub methods: HashMap, - pub field_names: Vec, - pub field_types: Vec, // 与 field_names 一一对应(默认值初始化用) - pub annos: Vec, // @onlyread/@unfork(流注解) -} - -impl StreamDef { - pub fn is_class(&self) -> bool { - self.kind == StreamKind::Class - } -} - -#[derive(Debug, Default)] -pub struct Registry { - pub streams: HashMap, - pub order: Vec, // 声明顺序(对象打印/调试用) -} - -impl Registry { - pub fn new() -> Self { - Registry::default() - } - - /// 把 Program 的声明注册进表。返回未满足的 need(name, kind)。 - pub fn register(&mut self, prog: &Program) -> Vec<(String, String)> { - let mut unmet = Vec::new(); - let mut needs = Vec::new(); - for d in &prog.decls { - match d { - Decl::Need { kind, name } => needs.push((kind.clone(), name.clone())), - Decl::StreamSig { name, members, annos, .. } => { - let def = build_stream(name.clone(), StreamKind::Signature, None, members, annos); - self.insert(def); - } - Decl::StreamBin { name, file, members, .. } => { - let def = build_stream(name.clone(), StreamKind::Binary, None, members, &[]); - let def = StreamDef { bin_file: Some(file.clone()), ..def }; - self.insert(def); - } - Decl::Class { name, members, annos, implements } => { - let def = build_stream(name.clone(), StreamKind::Class, None, members, annos); - // 接口实现检查:类必须提供接口的全部方法签名 - for iname in implements { - let Some(idef) = self.streams.get(iname).cloned() else { - unmet.push(("Interface".into(), iname.clone())); - continue; - }; - for (mn, mm) in &idef.methods { - if !def.methods.contains_key(mn) { - unmet.push(( - format!("Interface {iname} method {mn}"), - format!("class {name} does not implement it"), - )); - } - } - } - self.insert(def); - } - Decl::Interface { name, members, annos, .. } => { - // 接口 = 签名流(只有签名方法;成员 body 必须为空) - let def = build_stream(name.clone(), StreamKind::Signature, None, members, annos); - self.insert(def); - } - Decl::Fork { sig, name, members, annos, .. } => { - let mut def = build_stream(name.clone(), StreamKind::Fork, Some(sig.clone()), members, annos); - // 字段/签名方法继承自签名流(15:int count 声明在 Stream ReadOnly) - if let Some(sig_def) = self.streams.get(sig).cloned() { - let mut names = sig_def.field_names.clone(); - let mut types = sig_def.field_types.clone(); - for (i, n) in names.iter().enumerate() { - if !def.field_names.contains(n) { - def.field_names.push(n.clone()); - def.field_types.push(types[i].clone()); - } - } - for (mn, mm) in &sig_def.methods { - def.methods.entry(mn.clone()).or_insert_with(|| mm.clone()); - } - } - self.insert(def); - } - Decl::Const { .. } => {} - } - } - if let Some(m) = &prog.main { - let mut methods = HashMap::new(); - for meth in &m.methods { - methods.insert(meth.name.clone(), meth.clone()); - } - let def = StreamDef { - name: "Main".into(), - kind: StreamKind::Main, - sig: None, - methods, - field_names: Vec::new(), - field_types: Vec::new(), - annos: Vec::new(), - bin_file: None, - }; - self.insert(def); - } - // need 校验 - for (kind, name) in needs { - let ok = match kind.as_str() { - "value" => prog.decls.iter().any(|d| matches!(d, Decl::Const { name: n, .. } if n == &name)), - "function" => self.find_bare_method(&name).is_some(), - "stream" | "Stream" => self.streams.contains_key(&name), - "Class" => self.streams.get(&name).map(|s| s.is_class()).unwrap_or(false), - _ => false, - }; - if !ok { - unmet.push((kind, name)); - } - } - unmet - } - - fn insert(&mut self, def: StreamDef) { - self.order.push(def.name.clone()); - self.streams.insert(def.name.clone(), def); - } - - /// 签名流调用回退:qual 是签名流时找其分叉实现。 - pub fn resolve_qual(&self, qual: &str) -> Option<&StreamDef> { - let d = self.streams.get(qual)?; - if d.kind == StreamKind::Signature { - for other in self.streams.values() { - if other.sig.as_deref() == Some(qual) { - return Some(other); - } - } - } - Some(d) - } - - /// 裸调用:全局按方法名扫描(Main 优先,然后声明顺序)。 - pub fn find_bare_method(&self, name: &str) -> Option<(&StreamDef, &Method)> { - if let Some(main) = self.streams.get("Main") { - if let Some(m) = main.methods.get(name) { - return Some((main, m)); - } - } - for key in &self.order { - if key == "Main" { - continue; - } - if let Some(d) = self.streams.get(key) { - if let Some(m) = d.methods.get(name) { - // 签名方法(无体)不是实现,跳过;找分叉的实现 - if m.body.is_empty() { - continue; - } - return Some((d, m)); - } - } - } - None - } - - /// 类定义(new 用)。 - pub fn class(&self, name: &str) -> Option<&StreamDef> { - let d = self.streams.get(name)?; - if d.is_class() { - Some(d) - } else { - None - } - } -} - -fn build_stream( - name: String, - kind: StreamKind, - sig: Option, - members: &[Member], - annos: &[String], -) -> StreamDef { - let mut methods = HashMap::new(); - let mut field_names = Vec::new(); - let mut field_types = Vec::new(); - for m in members { - match m { - Member::Method(meth) => { - methods.insert(meth.name.clone(), meth.clone()); - } - Member::Field { ty, names } => { - for n in names { - field_names.push(n.clone()); - field_types.push(ty.clone()); - } - } - } - } - StreamDef { name, kind, sig, bin_file: None, methods, field_names, field_types, annos: annos.to_vec() } -} diff --git a/rust/crates/bbb-vm/tests/dylib.rs b/rust/crates/bbb-vm/tests/dylib.rs deleted file mode 100644 index 5a6afba..0000000 --- a/rust/crates/bbb-vm/tests/dylib.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! dylib 模块测试:候选名生成 + 实际加载/符号查找(Linux 环境)。 - -use bbb_vm::dylib; - -#[test] -fn candidate_names_keeps_original() { - let c = dylib::candidate_names("libm.so"); - assert_eq!(c[0], "libm.so"); - assert!(c.contains(&"libm.so".to_string())); -} - -#[test] -fn candidate_names_linux_so() { - // Linux:libm.so → 版本化兜底 libm.so.6 / libm.so.1 - let c = dylib::candidate_names("libm.so"); - assert!(c.contains(&"libm.so.6".to_string()), "{c:?}"); -} - -#[test] -fn candidate_names_versioned_no_garbage() { - // libm.so.6 不应生成 libm.so.so 之类的垃圾 - let c = dylib::candidate_names("libm.so.6"); - assert!(!c.iter().any(|x| x.contains(".so.so")), "{c:?}"); - assert!(c.contains(&"libm.so".to_string()), "{c:?}"); -} - -#[test] -fn candidate_names_bare_lib() { - let c = dylib::candidate_names("libm"); - assert!(c.contains(&"libm.so".to_string()), "{c:?}"); - assert!(c.contains(&"libm.so.6".to_string()), "{c:?}"); -} - -#[cfg(unix)] -#[test] -fn open_and_symbol_libm() { - // 平台真实库:声明 libm.so(链接脚本),应通过候选命中 libm.so.6 - let h = dylib::open_any("libm.so").expect("open libm"); - let s = dylib::symbol(h, "sin").expect("symbol sin"); - let f: fn(f64) -> f64 = unsafe { std::mem::transmute(s) }; - assert_eq!(f(0.0), 0.0); - assert!((f(std::f64::consts::FRAC_PI_2) - 1.0).abs() < 1e-9); -} diff --git a/rust/crates/bbb-vm/tests/regression.rs b/rust/crates/bbb-vm/tests/regression.rs deleted file mode 100644 index 5372041..0000000 --- a/rust/crates/bbb-vm/tests/regression.rs +++ /dev/null @@ -1,259 +0,0 @@ -//! M3 解释器回归:examples 01-08 + 12 + 13 输出断言。 -//! 完整期望值以旧 C 解释器(bin/bio)实测输出为准(标准层)。 - -use std::path::PathBuf; - -use bbb_syntax::parser::parse_source; -use bbb_vm::interp::Interp; - -fn examples_dir() -> PathBuf { - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .parent().unwrap().parent().unwrap().parent().unwrap() - .join("examples") -} - -fn run_file(name: &str) -> String { - let path = examples_dir().join(name); - let src = std::fs::read_to_string(&path).unwrap(); - let (prog, errs) = parse_source(&src); - assert!(errs.is_empty(), "{name} parse errors: {errs:?}"); - let mut interp = Interp::new(); - let out = interp.run(&prog); - assert!(out.unmet_needs.is_empty(), "{name} unmet needs: {:?}", out.unmet_needs); - out.stdout -} - -#[test] -fn ex01_hello() { - let out = run_file("01-hello.bio"); - assert_eq!(out, "Hello, BioLang!\nEvery call in BioLang is a request.\nThis one was just a request to print a line.\n"); -} - -#[test] -fn ex02_requests() { - let out = run_file("02-requests.bio"); - assert!(out.contains("3 + 4 = 7")); - assert!(out.contains("div cause: division by zero")); - assert!(out.contains("missing method cause: stream MyCalc refuses: no method sqrt")); - assert!(out.contains("default return cause: nothing")); - assert!(out.contains("if is false (default ref nothing)")); -} - -#[test] -fn ex03_control_flow() { - let out = run_file("03-control-flow.bio"); - assert!(out.contains("1..10 sum (while) = 55")); - assert!(out.contains("5! (for) = 120")); - assert!(out.contains("1 2 3 5 6 7")); - assert!(out.contains("for(;;) counted: 3")); -} - -#[test] -fn ex04_streams_fork() { - let out = run_file("04-streams-fork.bio"); - assert!(out.contains("Calc::add(2,3) = 5")); - assert!(out.contains("bare add(10,20) = 30")); - assert!(out.contains("count = 3 doubleGet = 6")); - assert!(out.contains("hello from a passed stream!")); -} - -#[test] -fn ex05_io() { - let out = run_file("05-io-substreams.bio"); - assert!(out.contains("SIO::format → 2 + 3 = 5")); - assert!(out.contains("SIO::upper → HELLO")); - assert!(out.contains("SIO::getln → line one")); - assert!(out.contains("FIO read back: Hello from BioLang! Appended line")); -} - -#[test] -fn ex06_classes() { - let out = run_file("06-classes-objects.bio"); - assert!(out.contains("object: ")); - assert!(out.contains("name = TAK hp = 88")); - assert!(out.contains("Obj::call getHp() = 100")); - assert!(out.contains("h::getName() = TAK")); -} - -#[test] -fn ex07_arrays() { - let out = run_file("07-arrays.bio"); - assert!(out.contains("array: [10, 20, 30]")); - assert!(out.contains("after push: [10, 20, 30, 40] len: 4")); - assert!(out.contains("join(-): 10-20-30-40")); - assert!(out.contains("a[1] = 20")); - assert!(out.contains("after a[1] = 99: [10, 99, 30, 40]")); - assert!(out.contains("new int[4] squares: [0, 1, 4, 9]")); - assert!(out.contains("vector: [10, 20, 30] len: 3")); - assert!(out.contains("Arrays count: 3")); - assert!(out.contains("after forget: 2")); -} - -#[test] -fn ex08_multi_return() { - let out = run_file("08-multi-return.bio"); - assert!(out.contains("triple(10) = [10, 20, 30]")); - assert!(out.contains("arr = [10, 20, 30] arr[1] = 20 len = 3")); -} - -#[test] -fn ex12_computation() { - let out = run_file("12-computation.bio"); - assert!(out.contains("Com::abs(0-5) = 5 Com::sqrt(9) = 3")); - assert!(out.contains("Com::pow(2,10) = 1024")); - assert!(out.contains("Time::reset(forked) ok")); -} - -#[test] -fn ex13_need() { - let out = run_file("13-need.bio"); - assert!(out.contains("PI = 3")); - assert!(out.contains("hello, TAK")); - assert!(out.contains("writing via a needed stream")); - assert!(out.contains("hero created, hp = 0")); -} - -#[test] -fn project_multi_file() { - let root = examples_dir().join("project"); - let prog = bbb_vm::load_project_sources(&root).unwrap(); - let mut interp = Interp::new(); - let out = interp.run(&prog); - assert!(out.unmet_needs.is_empty(), "unmet: {:?}", out.unmet_needs); - assert_eq!(out.stdout, "main entry — Hello from utils/\nhello, project\n"); -} - -#[test] -fn need_unmet_is_error() { - let src = r#" -program main; -need value MISSING; -Main { void exec() { CIO::println("x"); } } -"#; - let (prog, errs) = parse_source(src); - assert!(errs.is_empty()); - let mut interp = Interp::new(); - let out = interp.run(&prog); - assert_eq!(out.unmet_needs, vec![("value".to_string(), "MISSING".to_string())]); -} - -#[test] -fn ex09_threads() { - let out = run_file("09-threads.bio"); - assert!(out.contains("live threads: 2")); - assert!(out.contains("thread 1 10! = 3628800")); - assert!(out.contains("thread 2 countUp = 5")); -} - -#[test] -fn ex10_taskm() { - let out = run_file("10-taskm.bio"); - assert!(out.contains("tasks: 2")); - assert!(out.contains("tasks done: 0")); - assert!(out.contains("jobA sum = 15")); - assert!(out.contains("jobB 2^4 = 16")); -} - -#[test] -fn ex11_smart_refs() { - let out = run_file("11-smart-refs.bio"); - assert!(out.contains("u read counter = 10")); - assert!(out.contains("read-only write → Ref refused: reference is read-only, cannot write")); - assert!(out.contains("rw read = 10")); - assert!(out.contains("counter after rw = 5 → 5")); - assert!(out.contains("thread 2 a-layer = 44")); - assert!(out.contains("thread 1 a-layer = 42")); - assert!(out.contains("t1 = 42 t2 = 44")); - assert!(out.contains("at [1] = refused: refused: reference is write-only, cannot read")); - assert!(out.contains("⛔ main stream refused: refused: reference is read-only, cannot write")); -} - -#[test] -fn ex14_binary_lib() { - let out = run_file("14-binary-lib.bio"); - assert!(out.contains("m::sin(0) = 0")); - assert!(out.contains("m::cos(0) = 1")); - assert!(out.contains("m::pow(2,10) = 1024")); - assert!(out.contains("m::doubleIt(21) = 42")); -} - -#[test] -fn ex15_annotations() { - let out = run_file("15-annotations.bio"); - assert!(out.contains("refused: stream Sealed is @unfork, cannot fork")); - assert!(out.contains("new @unfork class → Obj refused: class Frozen is @unfork, cannot fork")); - assert!(out.contains("onlyread get = 0")); - assert!(out.contains("onlyread bump → refused: stream RO is @onlyread — bump() is a write method")); - assert!(out.contains("alias get = 0")); - assert!(out.contains("alias touch → refused: stream RA is @onlyread — touch() is a write method")); - assert!(out.contains("marked write → refused: stream G is @onlyread — markedWrite() is a write method")); - assert!(out.contains("marked read = 1")); - assert!(out.contains("safe read = 1")); -} - -#[test] -fn ex16_phonebooth() { - let out = run_file("16-phonebooth.bio"); - assert!(out.contains("t1 sum = 5050 t2 sum = 20100")); - assert!(out.contains("global 5! = 120")); - assert!(out.contains("global 6! = 720")); - assert!(out.contains("direct recursion → refused: phone-booth method down does not support recursion")); - assert!(out.contains("indirect recursion → refused: phone-booth method down2 does not support recursion")); - assert!(out.contains("ucall recursion → refused: phone-booth method uDown does not support recursion")); - assert!(out.contains("plain fact(10) = 3628800")); -} - -fn fixtures_dir() -> PathBuf { - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .parent().unwrap().parent().unwrap() - .join("tests").join("fixtures") -} - -fn run_fixture(name: &str) -> String { - let path = fixtures_dir().join(name); - let src = std::fs::read_to_string(&path).unwrap(); - let (prog, errs) = parse_source(&src); - assert!(errs.is_empty(), "{name} parse errors: {errs:?}"); - let mut interp = Interp::new(); - let out = interp.run(&prog); - assert!(out.unmet_needs.is_empty(), "{name} unmet needs: {:?}", out.unmet_needs); - out.stdout -} - -#[test] -fn new_classname_decl() { - // 类名 = new 类():宏展开语法(parser 层) - let out = run_fixture("test_new.bio"); - assert!(out.contains("b.val = 42"), "got: {out}"); -} - -#[test] -fn arrays_sort_inplace() { - // arr::sort() → 内部 __sort__() 原地排序 - let out = run_fixture("test_sort.bio"); - assert!(out.contains("after: [10, 20, 30, 40, 50]"), "got: {out}"); - // Arrays::sort(arr) 流方法同效 - let out2 = run_fixture("test_sort2.bio"); - assert!(out2.contains("after: [10, 20, 30, 50]"), "got: {out2}"); -} - -#[test] -fn interface_basic_and_polymorphism() { - let out = run_fixture("test_iface2.bio"); - assert!(out.contains("circle area: 12.56"), "got: {out}"); - assert!(out.contains("square area: 9"), "got: {out}"); -} - -#[test] -fn interface_missing_method_rejected() { - let src = std::fs::read_to_string(fixtures_dir().join("test_iface_bad.bio")).unwrap(); - let (prog, errs) = parse_source(&src); - assert!(errs.is_empty(), "parse errors: {errs:?}"); - let mut interp = Interp::new(); - let out = interp.run(&prog); - assert!( - out.unmet_needs.iter().any(|(k, _)| k.contains("draw")), - "expected missing draw method, got: {:?}", - out.unmet_needs - ); -} diff --git a/rust/crates/bbb-wasm/Cargo.toml b/rust/crates/bbb-wasm/Cargo.toml deleted file mode 100644 index 6667e54..0000000 --- a/rust/crates/bbb-wasm/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "bbb-wasm" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "BiuBiuBiu 插件 wasm 核心:Rust 实现的格式化器/词法器(VSCode 插件用)" - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -bbb-syntax = { path = "../bbb-syntax" } diff --git a/rust/crates/bbb-wasm/src/lib.rs b/rust/crates/bbb-wasm/src/lib.rs deleted file mode 100644 index b2e4e0d..0000000 --- a/rust/crates/bbb-wasm/src/lib.rs +++ /dev/null @@ -1,265 +0,0 @@ -//! bbb-wasm — BiuBiuBiu 格式化器/词法器,编译为 wasm32 供 VSCode 插件调用。 -//! -//! 导出(手写 C ABI,零 wasm-bindgen 依赖): -//! - `format(ptr, len, out_cap) -> usize`:格式化源码,写入输出缓冲,返回长度 -//! - `format_len(ptr, len) -> usize`:预计算格式化后长度(JS 分配缓冲) -//! -//! formatter 规则(对齐旧 formatter.js 0.16.0 行为,17 examples 幂等): -//! - 4 空格缩进(块级);`{` 换行、`}` 缩进减一换行 -//! - 运算符两侧空格;`::`/`.`/`,`/`;`/括号规则明确 -//! - 关键字与 `(` 之间空格(if/while/for);函数调用名与 `(` 无空格 -//! - 字符串/字符 token 重新包裹引号(内容原样);注释原样 -//! - 连续空行压缩为单空行;行尾无空格 - -use std::slice; - -/// 格式化输入(len 字节 UTF-8),写入 out(out_cap 字节),返回写入字节数。 -#[no_mangle] -pub extern "C" fn format(src_ptr: *const u8, src_len: usize, out_ptr: *mut u8, out_cap: usize) -> usize { - if src_ptr.is_null() || src_len == 0 || out_ptr.is_null() || out_cap == 0 { - return 0; - } - let src = unsafe { slice::from_raw_parts(src_ptr, src_len) }; - let Some(text) = std::str::from_utf8(src).ok() else { return 0 }; - let out = fmt(text); - let bytes = out.as_bytes(); - if bytes.len() > out_cap { - return 0; - } - unsafe { - std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_ptr, bytes.len()); - } - bytes.len() -} - -/// 预计算格式化后长度。 -#[no_mangle] -pub extern "C" fn format_len(src_ptr: *const u8, src_len: usize) -> usize { - if src_ptr.is_null() || src_len == 0 { - return 0; - } - let src = unsafe { slice::from_raw_parts(src_ptr, src_len) }; - let Some(text) = std::str::from_utf8(src).ok() else { return 0 }; - fmt(text).len() -} - -use bbb_syntax::lexer::{Token, TokenKind}; - -const CONTROL_KW: &[&str] = &["if", "while", "for", "else"]; -const SPACE_OP: &[&str] = &[ - "+", "-", "*", "/", "%", "=", "==", "!=", "<", ">", "<=", ">=", - "&&", "||", "+=", "-=", "*=", "/=", "%=", -]; - -fn is_ident_like(t: &Token) -> bool { - matches!(t.kind, TokenKind::Ident | TokenKind::Keyword) -} - -/// Rust 格式化器主体。 -pub fn fmt(src: &str) -> String { - let mut toks = Vec::new(); - if bbb_syntax::lexer::tokenize(src, &mut toks).is_err() { - return src.to_string(); // 词法错误:原样返回 - } - let mut out = String::new(); - let mut indent: usize = 0; - let mut prev: Option = None; - let mut line_start = true; - let mut paren_depth: usize = 0; - - for t in toks.iter() { - if t.kind == TokenKind::Eof { - break; - } - // 行首缩进(; 和 , 不缩进;} 用 indent-1) - if line_start { - if t.text != ";" && t.text != "," { - let n = if t.text == "}" { indent.saturating_sub(1) } else { indent }; - for _ in 0..n * 4 { - out.push(' '); - } - } - line_start = false; - } - - let text = t.text; - let is_block_open = text == "{"; - let is_block_close = text == "}"; - let is_semi = text == ";"; - let is_comma = text == ","; - let is_paren_open = text == "("; - let is_paren_close = text == ")"; - if is_paren_open { - paren_depth += 1; - } else if is_paren_close && paren_depth > 0 { - paren_depth -= 1; - } - let is_colon2 = text == "::"; - let is_dot = text == "."; - let is_comment = text.starts_with("//") || text.starts_with("/*"); - - // 前置空格决策 - let space_before = if let Some(p) = &prev { - if line_start { - false - } else if p.text == "{" || p.text == ";" || p.text == "," || p.text == "(" || is_colon2 || is_dot { - false - } else if is_block_close { - // } 前无空格(但 `} else {` 由 else 关键字前补空格处理) - false - } else if is_semi || is_comma || is_paren_close { - false - } else if is_paren_open { - // 控制关键字后空格;调用名后无 - is_ident_like(p) && CONTROL_KW.contains(&p.text) - } else if is_comment { - true - } else if is_block_open { - // { 前空格:Main {、while (...) {、} else { - true - } else if is_ident_like(t) && is_ident_like(p) { - // 标识符/关键字相邻 → 空格(program main、void exec) - true - } else if is_ident_like(t) || is_ident_like(p) { - // 标识符与运算符/括号之间:看运算符 - SPACE_OP.contains(&text) || SPACE_OP.contains(&p.text) || text == "=" || p.text == "=" - } else { - // 运算符之间 - SPACE_OP.contains(&text) || SPACE_OP.contains(&p.text) - } - } else { - false - }; - if space_before && !out.ends_with(' ') && !out.ends_with('\n') { - out.push(' '); - } - // ) 前无空格(分号后 push 的空格在此清理) - if is_paren_close && out.ends_with(' ') { - out.pop(); - } - - // 输出 token 文本(字符串/字符重新加引号) - match t.kind { - TokenKind::Str => { - out.push('"'); - out.push_str(text); - out.push('"'); - } - TokenKind::Char => { - out.push('\''); - out.push_str(text); - out.push('\''); - } - _ => out.push_str(text), - } - - // 后置处理 - if is_block_open { - out.push('\n'); - indent += 1; - line_start = true; - } else if is_block_close { - if indent > 0 { - indent -= 1; - } - out.push('\n'); - line_start = true; - } else if is_semi { - if paren_depth == 0 { - out.push('\n'); - line_start = true; - } else { - out.push(' '); // for 头内分号不换行 - } - } else if is_comma { - out.push(' '); - } else if is_comment && text.starts_with("//") { - out.push('\n'); - line_start = true; - } - prev = Some(*t); - } - - // 清理:空行压缩 + 行尾空格 - let mut cleaned = String::new(); - let mut nl = 0usize; - for c in out.chars() { - if c == '\n' { - nl += 1; - if nl <= 2 { - cleaned.push('\n'); - } - } else { - nl = 0; - cleaned.push(c); - } - } - let lines: Vec<&str> = cleaned.split('\n').collect(); - let trimmed: Vec = lines.iter().map(|l| l.trim_end().to_string()).collect(); - let mut result = trimmed.join("\n"); - while result.ends_with('\n') { - result.pop(); - } - result.push('\n'); - result -} - -#[cfg(test)] -mod tests { - use super::*; - - fn fmt2(s: &str) -> String { - let a = fmt(s); - let b = fmt(&a); - assert_eq!(a, b, "格式化不幂等:\n---1---\n{a}\n---2---\n{b}"); - a - } - - #[test] - fn hello() { - let src = r#"program main; -Main { void exec() { CIO::println("Hello"); } }"#; - let out = fmt2(src); - assert!( - out.contains("program main;\nMain {\n void exec() {\n CIO::println(\"Hello\");\n }\n}"), - "{out}" - ); - } - - #[test] - fn control_flow() { - let src = "program main;\nMain {\nvoid exec() {\nALL i=1;\nwhile(i<=10){i=i+1;}\nfor(ALL k=1;k<=5;k=k+1;){}\n}\n}"; - let out = fmt2(src); - assert!(out.contains("ALL i = 1;"), "{out}"); - assert!(out.contains("while (i <= 10) {"), "{out}"); - assert!(out.contains("for (ALL k = 1; k <= 5; k = k + 1;) {"), "{out}"); - assert!(out.contains(" void exec() {"), "{out}"); - } - - #[test] - fn strings_untouched() { - let src = r#"CIO::println("a + b keep spaces");"#; - let out = fmt2(src); - assert!(out.contains("\"a + b keep spaces\""), "{out}"); - } - - #[test] - fn idempotent_on_examples() { - let ex = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .parent().unwrap().parent().unwrap().parent().unwrap().join("examples"); - let mut n = 0; - if let Ok(entries) = std::fs::read_dir(&ex) { - for e in entries.flatten() { - let p = e.path(); - if p.extension().map(|x| x == "bio").unwrap_or(false) { - let src = std::fs::read_to_string(&p).unwrap(); - let a = fmt(&src); - let b = fmt(&a); - assert_eq!(a, b, "not idempotent: {}", p.display()); - n += 1; - } - } - } - assert!(n >= 10, "examples 太少: {n}"); - } -} diff --git a/rust/tests/fixtures/dbg_attrs.bio b/rust/tests/fixtures/dbg_attrs.bio deleted file mode 100644 index 9459014..0000000 --- a/rust/tests/fixtures/dbg_attrs.bio +++ /dev/null @@ -1,13 +0,0 @@ -program main; - -Main { - void exec() { - Array a = new Array(3); - a::set(0, 30); a::set(1, 10); a::set(2, 20); - // 直接看 a 的属性 - CIO::println("len:", get a::len()); - CIO::println("data type check"); - // 用 Arrays::all 拿注册表看 - CIO::println("count:", get Arrays::count()); - } -} diff --git a/rust/tests/fixtures/test_arrays_all.bio b/rust/tests/fixtures/test_arrays_all.bio deleted file mode 100644 index 8e0e1de..0000000 --- a/rust/tests/fixtures/test_arrays_all.bio +++ /dev/null @@ -1,15 +0,0 @@ -program main; - -Main { - void exec() { - ALL a = new Array(2); - a::set(0, 10); a::set(1, 20); - ALL v = Arrays::vector(); - v::push(5); - CIO::println("count:", get Arrays::count()); - ALL all_arr = get Arrays::all(); - CIO::println("all:", all_arr); - CIO::println("all len:", get all_arr::len()); - CIO::println("all[0]:", all_arr[0]); - } -} diff --git a/rust/tests/fixtures/test_err.bio b/rust/tests/fixtures/test_err.bio deleted file mode 100644 index 6cd906d..0000000 --- a/rust/tests/fixtures/test_err.bio +++ /dev/null @@ -1 +0,0 @@ -program main; Main { void exec() { int x = ; } } diff --git a/rust/tests/fixtures/test_iface.bio b/rust/tests/fixtures/test_iface.bio deleted file mode 100644 index 569ddce..0000000 --- a/rust/tests/fixtures/test_iface.bio +++ /dev/null @@ -1,21 +0,0 @@ -program main; - -Interface Shape { - double area(); - void draw(); -} - -Class Circle implements Shape { - double area() { res 3.14 * this::r * this::r; } - void draw() { CIO::println("drawing circle"); } - double r; -} - -Main { - void exec() { - Circle c = new Circle(); - c::r = 2.0; - CIO::println("area:", get c::area()); - c::draw(); - } -} diff --git a/rust/tests/fixtures/test_iface2.bio b/rust/tests/fixtures/test_iface2.bio deleted file mode 100644 index 200b6c1..0000000 --- a/rust/tests/fixtures/test_iface2.bio +++ /dev/null @@ -1,26 +0,0 @@ -program main; - -Interface Shape { - double area(); -} - -Class Circle implements Shape { - double area() { res 3.14 * this::r * this::r; } - double r; -} - -Class Square implements Shape { - double area() { res this::side * this::side; } - double side; -} - -Main { - void exec() { - Shape s = new Circle(); - s::r = 2.0; - CIO::println("circle area:", get s::area()); - Shape t = new Square(); - t::side = 3.0; - CIO::println("square area:", get t::area()); - } -} diff --git a/rust/tests/fixtures/test_iface_bad.bio b/rust/tests/fixtures/test_iface_bad.bio deleted file mode 100644 index 0619fd2..0000000 --- a/rust/tests/fixtures/test_iface_bad.bio +++ /dev/null @@ -1,16 +0,0 @@ -program main; - -Interface Shape { - double area(); - void draw(); -} - -Class Circle implements Shape { - double area() { res 0.0; } -} - -Main { - void exec() { - Circle c = new Circle(); - } -} diff --git a/rust/tests/fixtures/test_new.bio b/rust/tests/fixtures/test_new.bio deleted file mode 100644 index 12c50ab..0000000 --- a/rust/tests/fixtures/test_new.bio +++ /dev/null @@ -1,13 +0,0 @@ -program main; - -Class Box { - void __init__(v int) { this::val = v; } - int val; -} - -Main { - void exec() { - Box b = new Box(42); - CIO::println("b.val =", get b::val); - } -} diff --git a/rust/tests/fixtures/test_new2.bio b/rust/tests/fixtures/test_new2.bio deleted file mode 100644 index d71944a..0000000 --- a/rust/tests/fixtures/test_new2.bio +++ /dev/null @@ -1,16 +0,0 @@ -program main; - -Class Box { - void __init__(v int) { this::val = v; } - int val; -} - -Main { - void exec() { - Box b = new Box(99); - CIO::println("b.val =", get b::val); - Array a = new Array(3); - a::set(0, 7); - CIO::println("a[0] =", a[0], "len:", get a::len()); - } -} diff --git a/rust/tests/fixtures/test_obj_llvm.bio b/rust/tests/fixtures/test_obj_llvm.bio deleted file mode 100644 index 46a58b6..0000000 --- a/rust/tests/fixtures/test_obj_llvm.bio +++ /dev/null @@ -1,25 +0,0 @@ -program main; - -Class Student { - void __init__(age int, solve double) { - this::age = age; - this::solve = solve; - } - int getAge() { res this::age; } - double getSolve() { res this::solve; } - void bump() { this::age = this::age + 1; } - int age; - double solve; -} - -Main { - void exec() { - Student s = new Student(12, 1.2); - CIO::println("age:", get s::getAge()); - CIO::println("solve:", get s::getSolve()); - s::bump(); - CIO::println("after bump:", get s::getAge()); - s::age = 20; - CIO::println("direct:", s::age); - } -} diff --git a/rust/tests/fixtures/test_ref2.bio b/rust/tests/fixtures/test_ref2.bio deleted file mode 100644 index 5c305fd..0000000 --- a/rust/tests/fixtures/test_ref2.bio +++ /dev/null @@ -1,11 +0,0 @@ -program main; - -Main { - void exec() { - int x = 42; - &r u int p = &x; - CIO::println("p =", get p); - p = 99; - CIO::println("x =", x); - } -} diff --git a/rust/tests/fixtures/test_ref3.bio b/rust/tests/fixtures/test_ref3.bio deleted file mode 100644 index 74d9dd4..0000000 --- a/rust/tests/fixtures/test_ref3.bio +++ /dev/null @@ -1,15 +0,0 @@ -program main; - -Class Box { - void take(p &r u int) { - CIO::println("got:", get p); - } -} - -Main { - void exec() { - int x = 42; - Box b = new Box(); - b::take(&x); - } -} diff --git a/rust/tests/fixtures/test_ref4.bio b/rust/tests/fixtures/test_ref4.bio deleted file mode 100644 index 8193383..0000000 --- a/rust/tests/fixtures/test_ref4.bio +++ /dev/null @@ -1,16 +0,0 @@ -program main; - -Class Box { - void bump(p &w u int) { - p = get p + 1; - } -} - -Main { - void exec() { - int x = 41; - Box b = new Box(); - b::bump(&x); - CIO::println("x =", x); - } -} diff --git a/rust/tests/fixtures/test_refparam.bio b/rust/tests/fixtures/test_refparam.bio deleted file mode 100644 index d154aea..0000000 --- a/rust/tests/fixtures/test_refparam.bio +++ /dev/null @@ -1,15 +0,0 @@ -program main; - -Class Box { - void take(&r u int p) { - CIO::println("got:", get p); - } -} - -Main { - void exec() { - int x = 42; - Box b = new Box(); - b::take(&x); - } -} diff --git a/rust/tests/fixtures/test_sort.bio b/rust/tests/fixtures/test_sort.bio deleted file mode 100644 index 9094839..0000000 --- a/rust/tests/fixtures/test_sort.bio +++ /dev/null @@ -1,11 +0,0 @@ -program main; - -Main { - void exec() { - Array a = new Array(5); - a::set(0, 30); a::set(1, 10); a::set(2, 50); a::set(3, 20); a::set(4, 40); - CIO::println("before:", a); - a::sort(); - CIO::println("after: ", a); - } -} diff --git a/rust/tests/fixtures/test_sort2.bio b/rust/tests/fixtures/test_sort2.bio deleted file mode 100644 index 202543c..0000000 --- a/rust/tests/fixtures/test_sort2.bio +++ /dev/null @@ -1,11 +0,0 @@ -program main; - -Main { - void exec() { - Array a = new Array(4); - a::set(0, 30); a::set(1, 10); a::set(2, 50); a::set(3, 20); - CIO::println("before:", a); - Arrays::sort(a); - CIO::println("after: ", a); - } -} diff --git a/vscode/bbb-vscode/bbb-1.0.0.vsix b/vscode/bbb-vscode/bbb-1.0.0.vsix deleted file mode 100644 index 53f4f1b..0000000 Binary files a/vscode/bbb-vscode/bbb-1.0.0.vsix and /dev/null differ diff --git a/vscode/bbb-vscode/language-configuration.json b/vscode/bbb-vscode/language-configuration.json deleted file mode 100644 index e07785d..0000000 --- a/vscode/bbb-vscode/language-configuration.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "comments": { - "lineComment": "//", - "blockComment": ["/*", "*/"] - }, - "brackets": [ - ["{", "}"], - ["(", ")"], - ["[", "]"] - ], - "autoClosingPairs": [ - { "open": "{", "close": "}" }, - { "open": "(", "close": ")" }, - { "open": "[", "close": "]" }, - { "open": "\"", "close": "\"", "notIn": ["string"] } - ], - "surroundingPairs": [ - { "open": "{", "close": "}" }, - { "open": "(", "close": ")" }, - { "open": "\"", "close": "\"" } - ] -} diff --git a/vscode/bbb-vscode/package.json b/vscode/bbb-vscode/package.json deleted file mode 100644 index ff47b1d..0000000 --- a/vscode/bbb-vscode/package.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "name": "bbb", - "displayName": "BBB — BiuBiuBiu", - "description": "BBB (BiuBiuBiu) language support: syntax highlighting, snippets, completions, formatter (Rust wasm), run commands (bbb CLI)", - "version": "1.0.0", - "publisher": "bio", - "license": "MIT", - "engines": { - "vscode": "^1.85.0" - }, - "categories": [ - "Programming Languages" - ], - "main": "./src/extension.js", - "contributes": { - "languages": [ - { - "id": "bbb", - "aliases": [ - "BBB", - "BiuBiuBiu", - "bbb" - ], - "extensions": [ - ".bl", - ".bio" - ], - "configuration": "./language-configuration.json" - } - ], - "grammars": [ - { - "language": "bbb", - "scopeName": "source.bbb", - "path": "./syntaxes/bbb.tmLanguage.json" - } - ], - "snippets": [ - { - "language": "bbb", - "path": "./snippets/bbb.code-snippets" - } - ], - "commands": [ - { - "command": "bbb.runFile", - "title": "BBB: Run current file (bio)" - }, - { - "command": "bbb.compileFile", - "title": "BBB: Compile current file (bio shell build)" - }, - { - "command": "bbb.projectInit", - "title": "BBB: Create project" - }, - { - "command": "bbb.projectBuild", - "title": "BBB: Build project" - }, - { - "command": "bbb.projectRun", - "title": "BBB: Run project" - }, - { - "command": "bbb.projectInstall", - "title": "BBB: Install project dependencies" - }, - { - "command": "bbb.projectDestroy", - "title": "BBB: Destroy project build artifacts" - }, - { - "command": "bbb.runDemo", - "title": "BBB: Run built-in demos" - } - ], - "menus": { - "editor/title": [ - { - "when": "resourceLangId == bbb", - "command": "bbb.runFile", - "group": "navigation" - }, - { - "when": "resourceLangId == bbb", - "command": "bbb.compileFile", - "group": "navigation" - } - ], - "commandPalette": [ - { - "command": "bbb.runFile", - "when": "resourceLangId == bbb" - }, - { - "command": "bbb.compileFile", - "when": "resourceLangId == bbb" - }, - { - "command": "bbb.projectInit" - }, - { - "command": "bbb.projectBuild" - }, - { - "command": "bbb.projectRun" - }, - { - "command": "bbb.projectInstall" - }, - { - "command": "bbb.projectDestroy" - }, - { - "command": "bbb.runDemo" - } - ] - } - }, - "repository": { - "type": "git", - "url": "https://github.com/Bio-Studio/Bio.lang.git" - } -} \ No newline at end of file diff --git a/vscode/bbb-vscode/snippets/bbb.code-snippets b/vscode/bbb-vscode/snippets/bbb.code-snippets deleted file mode 100644 index f556a66..0000000 --- a/vscode/bbb-vscode/snippets/bbb.code-snippets +++ /dev/null @@ -1,255 +0,0 @@ -{ - "BioLang main skeleton": { - "prefix": "main", - "body": [ - "program main;", - "", - "Main {", - "\tvoid exec() {", - "\t\t$0", - "\t}", - "}", - "" - ], - "description": "Main program stream skeleton" - }, - "BioLang stream signature": { - "prefix": "stream", - "body": [ - "Stream ${1:Name} {", - "\t${2:int} ${3:method}(${4:a int});", - "}", - "" - ], - "description": "Stream signature declaration (args written 'name type')" - }, - "BioLang stream fork": { - "prefix": "fork", - "body": [ - "${1:Signature} ${2:Impl} {", - "\t${3:void} ${4:method}(${5:a int}) {", - "\t\tres ${6:value};", - "\t}", - "}", - "" - ], - "description": "Fork an implementation from a signature stream" - }, - "BioLang class declaration": { - "prefix": "class", - "body": [ - "Class ${1:Name} {", - "\tvoid __init__() {", - "\t\t$2", - "\t}", - "\tint ${3:field};", - "}", - "" - ], - "description": "Class (essentially a stream; methods need no overwrite)" - }, - "BioLang field declaration": { - "prefix": "field", - "body": [ - "${1|int,float,double,string,char,type|} ${2:x}${3:, ${4:y}};" - ], - "description": "Field declaration (comma-separated; type is a generic type)" - }, - "BioLang array field": { - "prefix": "arrfield", - "body": [ - "${1|int,float,double,string,char|}[] ${2:a};" - ], - "description": "Array field declaration (int[] a;)" - }, - "BioLang generic type": { - "prefix": "generic", - "body": [ - "type ${1:T};", - "${1:T} ${2:val};", - "${1:T}[] ${3:list};" - ], - "description": "Generic type (type T; T val; T[] list;)" - }, - "BioLang this:: attribute assignment": { - "prefix": "thisattr", - "body": [ - "this::${1:attr} = ${2:value};" - ], - "description": "this:: attribute assignment (writes object/stream field)" - }, - "BioLang this:: attribute read": { - "prefix": "thisget", - "body": [ - "get ${1:this::attr}" - ], - "description": "this:: attribute read (get the actual value)" - }, - "BioLang request call": { - "prefix": "call", - "body": [ - "ALL ${1:result} = ${2:Stream}::${3:method}($4);" - ], - "description": "Make a request and capture the result" - }, - "BioLang respond (res)": { - "prefix": "res", - "body": [ - "res ${1:value};" - ], - "description": "Respond with a result" - }, - "BioLang refuse (ref)": { - "prefix": "ref", - "body": [ - "ref ${1:reason};" - ], - "description": "Refuse a request with a reason" - }, - "BioLang unwrap get": { - "prefix": "getop", - "body": [ - "get ${1:request}" - ], - "description": "get X prefix unwrap: X's actual returned value (e.g. get CIO::readInt())" - }, - "BioLang unwrap cause": { - "prefix": "causeop", - "body": [ - "cause ${1:request}" - ], - "description": "cause X prefix unwrap: X's refusal reason (e.g. cause Calc::div(1,0))" - }, - "BioLang print line": { - "prefix": "println", - "body": [ - "CIO::println(${1:message});" - ], - "description": "CIO console output (newline)" - }, - "BioLang assumption (need)": { - "prefix": "need", - "body": [ - "need ${1|value,function,stream,Class|} ${2:name};" - ], - "description": "Declare an assumption (value/function/stream/Class); unmet → refuses to run" - }, - "BioLang new (instantiate class)": { - "prefix": "new", - "body": [ - "ALL ${1:obj} = new ${2:Class}(${3:args});" - ], - "description": "new syntax: fork class stream + auto __init__ (equivalent to Obj::new)" - }, - "BioLang smart reference": { - "prefix": "sref", - "body": [ - "&${1|r,w,m,rw,rm,wm,rwm|} ${2|u,f,a,t|} ${3:int} ${4:name} = &${5:target};" - ], - "description": "Typed smart reference: &perm follow base name = &expr (12 types), get p / p = v / p++" - }, - "BioLang reference variable declaration": { - "prefix": "realme", - "body": [ - "&${1|r,w,m,rw,rm,wm,rwm|} ${2|u,f,a,t|} ${3:int} ${4:name} = &${5:expr};" - ], - "description": "Reference variable declaration: &perm follow base type name = &expr" - }, - "BioLang binary library stream": { - "prefix": "bins", - "body": [ - "Stream ${1:Name} & \"${2:lib.so}\" {", - "}", - "" - ], - "description": "Binary library stream: dlopen-loaded; exported symbols become stream methods" - }, - "BioLang thread (spawn)": { - "prefix": "spawn", - "body": [ - "ALL ${1:t} = get Threads::spawn(\"${2:method}\", ${3:args});" - ], - "description": "Create a cooperative thread (Threads::spawn/yield/join/active/self)" - }, - "BioLang task manager": { - "prefix": "taskm", - "body": [ - "Taskm::interval(${1:1});", - "ALL ${2:t} = get Taskm::add(\"${3:job}\", ${4:args});", - "Taskm::run();" - ], - "description": "Task manager: round-robins threads until done" - }, - "BioLang if": { - "prefix": "if", - "body": [ - "if (${1:condition}) {", - "\t$2", - "} else {", - "\t$3", - "}" - ], - "description": "Conditional branch" - }, - "BioLang while": { - "prefix": "while", - "body": [ - "while (${1:condition}) {", - "\t$2", - "}" - ], - "description": "Loop" - }, - "BioLang for": { - "prefix": "for", - "body": [ - "for (ALL ${1:i} = ${2:0}; ${3:i < n}; ${4:i = i + 1}) {", - "\t$5", - "}" - ], - "description": "C-style for loop" - }, - "BioLang const declaration": { - "prefix": "const", - "body": [ - "const ${1:int} ${2:x} = ${3:10};" - ], - "description": "Constant variable (original spec: const → Constantstream, read-only)" - }, - "BioLang thread variable": { - "prefix": "thread", - "body": [ - "thread ${1:int} ${2:x} = ${3:10};" - ], - "description": "Thread variable (original spec: thread → thread scope)" - }, - "BioLang interface": { - "prefix": "interface", - "body": [ - "Interface ${1:Name} {", - "\t${2:void} ${3:method}(${4:a int});", - "}", - "" - ], - "description": "Interface declaration (signature methods only; classes implement it)" - }, - "BioLang class implements interface": { - "prefix": "implements", - "body": [ - "Class ${1:Name} implements ${2:InterfaceName} {", - "\t${3:void} ${4:method}(${5:a int}) {", - "\t\tres ${6:value};", - "\t}", - "}", - "" - ], - "description": "Class implementing an interface" - }, - "BioLang array sort": { - "prefix": "sort", - "body": [ - "${1:a}::sort();" - ], - "description": "In-place sort via the __sort__ internal method (Arrays::sort(a) also works)" - } -} \ No newline at end of file diff --git a/vscode/bbb-vscode/src/extension.js b/vscode/bbb-vscode/src/extension.js deleted file mode 100644 index 8669480..0000000 --- a/vscode/bbb-vscode/src/extension.js +++ /dev/null @@ -1,676 +0,0 @@ -/** - * BBB (BiuBiuBiu) VSCode extension — entry - * Features: syntax highlighting, code snippets, run commands (bbb CLI), - * stream method completion (Qual::), smart-reference completion (&), - * Rust-wasm formatter (format/format_len exports from bbb-wasm crate). - */ -const vscode = require('vscode'); -const { execFile, spawn } = require('child_process'); -const path = require('path'); -const { formatBBB } = require('./formatter'); // JS fallback - -/* Rust wasm formatter (bbb-wasm crate). Falls back to JS formatter on load failure. */ -let wasmFormat = null; -let wasmLoaded = false; - -function loadWasmFormatter() { - try { - const fs = require('fs'); - const wasmPath = path.join(__dirname, 'wasm', 'bbb_wasm.wasm'); - const bytes = fs.readFileSync(wasmPath); - const mod = new WebAssembly.Module(bytes); - const inst = new WebAssembly.Instance(mod, {}); - wasmFormat = (src) => { - const enc = new TextEncoder(); - const input = enc.encode(src); - return formatViaWasm(inst, input); - }; - wasmLoaded = true; - } catch (e) { - wasmLoaded = false; - wasmFormat = null; - } -} - -function formatViaWasm(inst, input) { - const mem = inst.exports.memory; - const pageSize = 65536; - // grow memory to fit input + output (2x input + 64k headroom) - const needed = input.length + 65536; - const cur = mem.buffer.byteLength; - const grow = Math.ceil(Math.max(0, needed - cur) / pageSize); - if (grow > 0) inst.exports.memory.grow(grow); - const buf = new Uint8Array(mem.buffer); - const inPtr = 16; // past the first page header (wasm memory starts clean) - buf.set(input, inPtr); - const outCap = input.length * 4 + 4096; - const outPtr = inPtr + input.length + 8; - const needed2 = outPtr + outCap; - if (needed2 > mem.buffer.byteLength) { - inst.exports.memory.grow(Math.ceil((needed2 - mem.buffer.byteLength) / pageSize)); - } - const n = inst.exports.format(inPtr, input.length, outPtr, outCap); - if (n === 0) return null; - return new TextDecoder().decode(new Uint8Array(mem.buffer, outPtr, n)); -} - -/* Method tables for the builtin substreams */ -const STREAMS = { - CIO: [ - /* Text streams: println/print write text, get/getln read text */ - { name: 'println', detail: 'CIO::println(...) — outputs args (space-separated) and a newline', doc: 'text stream; e.g. `CIO::println("3 + 4 =", get r);`' }, - { name: 'print', detail: 'CIO::print(...) — outputs args without a newline', doc: 'text stream; e.g. `CIO::print("please wait...");`' }, - { name: 'get', detail: 'CIO::get() — read one character (text stream)', doc: 'text stream; empty string on EOF' }, - { name: 'getln', detail: 'CIO::getln(prompt?) — read one line (text stream)', doc: 'text stream; e.g. `ALL line = CIO::getln("name: ");`' }, - /* Byte streams: write/read raw bytes */ - { name: 'write', detail: 'CIO::write(...) — write raw bytes (byte stream)', doc: 'byte stream; no newline, no formatting' }, - { name: 'read', detail: 'CIO::read() — read one raw byte 0-255 (byte stream)', doc: 'byte stream; -1 on EOF' }, - /* Numeric / errors */ - { name: 'readInt', detail: 'CIO::readInt(prompt?) — read an integer (refused on failure)', doc: 'e.g. `ALL n = CIO::readInt("age: ");`' }, - { name: 'readNumber',detail: 'CIO::readNumber(prompt?) — read a float (refused on failure)',doc: 'e.g. `ALL x = CIO::readNumber("number: ");`' }, - { name: 'error', detail: 'CIO::error(...) — output to stderr (no newline)', doc: 'e.g. `CIO::error("an error occurred");`' } - ], - FIO: [ - /* IO core methods (file implementation): open the current file stream first */ - { name: 'open', detail: 'FIO::open(path, mode?) — open the current file stream', doc: 'mode: "r" (default)/"w"/"a"; write/read/println/getln operate on it afterwards' }, - { name: 'close', detail: 'FIO::close() — close the current file stream', doc: 'e.g. `FIO::close();`' }, - { name: 'println', detail: 'FIO::println(...) — write a text line to the current file', doc: 'text stream; requires open(path,"w") first' }, - { name: 'print', detail: 'FIO::print(...) — write text to the current file (no newline)', doc: 'text stream; requires open(path,"w") first' }, - { name: 'write', detail: 'FIO::write(...) — write raw bytes to the current file', doc: 'byte stream; requires open(path,"w") first' }, - { name: 'getln', detail: 'FIO::getln() — read one line from the current file (text stream)', doc: 'text stream; requires open(path) first' }, - { name: 'get', detail: 'FIO::get() — read one character from the current file (text stream)', doc: 'text stream; empty string on EOF' }, - { name: 'read', detail: 'FIO::read() — read one raw byte from the current file', doc: 'byte stream; -1 on EOF' }, - /* Convenience file operations */ - { name: 'readFile', detail: 'FIO::readFile(path) — read an entire file (refused if missing)', doc: 'e.g. `ALL t = FIO::readFile("/tmp/a.txt");`' }, - { name: 'writeFile', detail: 'FIO::writeFile(path, content) — write a file (overwrite)', doc: 'e.g. `FIO::writeFile("/tmp/a.txt", "hi");`' }, - { name: 'appendFile',detail: 'FIO::appendFile(path, content) — append to a file', doc: 'e.g. `FIO::appendFile("/tmp/a.txt", "more");`' }, - { name: 'exists', detail: 'FIO::exists(path) — whether a file exists (1/0)', doc: 'e.g. `ALL ok = FIO::exists("/tmp/a.txt");`' } - ], - SIO: [ - /* Text streams (string implementation): print/println write text, get/getln read text */ - { name: 'println', detail: 'SIO::println(...) — write text with a newline', doc: 'text stream; readable afterwards via getln/get/content' }, - { name: 'print', detail: 'SIO::print(...) — write text (no newline)', doc: 'text stream' }, - { name: 'get', detail: 'SIO::get() — read one character (text stream)', doc: 'text stream; empty string when empty' }, - { name: 'getln', detail: 'SIO::getln() — read one line (text stream)', doc: 'text stream; empty string when empty' }, - /* Byte streams: write/read raw bytes */ - { name: 'write', detail: 'SIO::write(...) — write raw bytes to the buffer', doc: 'byte stream' }, - { name: 'read', detail: 'SIO::read() — read one raw byte 0-255', doc: 'byte stream; -1 when empty' }, - /* Buffer utilities */ - { name: 'content', detail: 'SIO::content() — read remaining buffer (non-consuming)', doc: 'e.g. `get SIO::content()`' }, - { name: 'buf', detail: 'SIO::buf() — whole buffer content, including consumed bytes (non-consuming)', doc: 'e.g. `get SIO::buf()`' }, - { name: 'clear', detail: 'SIO::clear() — clear the buffer', doc: 'e.g. `SIO::clear();`' }, - /* String utilities */ - { name: 'format', detail: 'SIO::format(fmt, ...) — format a string (%d %s %f)', doc: 'e.g. `ALL s = SIO::format("%d + %d = %d", 2, 3, 5);`' }, - { name: 'length', detail: 'SIO::length(str) — length', doc: 'e.g. `ALL n = SIO::length("abc");`' }, - { name: 'upper', detail: 'SIO::upper(str) — uppercase', doc: '`SIO::upper("hello")` → HELLO' }, - { name: 'lower', detail: 'SIO::lower(str) — lowercase', doc: '`SIO::lower("ABC")` → abc' }, - { name: 'trim', detail: 'SIO::trim(str) — strip surrounding whitespace', doc: '`SIO::trim(" x ")` → x' }, - { name: 'contains', detail: 'SIO::contains(str, sub) — whether it contains (1/0)', doc: '`SIO::contains("hello", "ell")` → 1' }, - { name: 'substring', detail: 'SIO::substring(str, start, end) — slice', doc: '`SIO::substring("hello", 1, 3)` → el' }, - { name: 'replace', detail: 'SIO::replace(str, old, new) — replace', doc: '`SIO::replace("a-b", "-", "+")` → a+b' } - ], - Threads: [ - { name: 'spawn', detail: 'Threads::spawn("method", args...) — create a thread, returns its id', doc: 'e.g. `ALL t = get Threads::spawn("factorial", 10);`' }, - { name: 'yield', detail: 'Threads::yield() — yield the CPU, schedule other threads', doc: 'cooperative thread switch' }, - { name: 'join', detail: 'Threads::join(threadId) — wait for a thread and take its result', doc: 'e.g. `ALL r = Threads::join(t);` → get r' }, - { name: 'active', detail: 'Threads::active() — number of live threads', doc: 'e.g. `get Threads::active()`' }, - { name: 'self', detail: 'Threads::self() — current thread id (main = 0)', doc: 'e.g. `get Threads::self()`' } - ], - Taskm: [ - { name: 'add', detail: 'Taskm::add("method", args...) — register a task, returns its id', doc: 'e.g. `ALL t = get Taskm::add("jobA", 5);`' }, - { name: 'interval', detail: 'Taskm::interval(ms) — set the round-robin interval (default 0)', doc: 'e.g. `Taskm::interval(10);`' }, - { name: 'run', detail: 'Taskm::run() — scheduling loop: runs all tasks round-robin until done', doc: 'automatically switches threads through the loop' }, - { name: 'stop', detail: 'Taskm::stop() — stop the scheduling loop', doc: 'call from inside a thread, then yield' }, - { name: 'active', detail: 'Taskm::active() — number of unfinished tasks', doc: 'e.g. `get Taskm::active()`' } - ], - Arrays: [ - { name: 'count', detail: 'Arrays::count() — number of registered Array/Vector instances', doc: 'e.g. `get Arrays::count()`' }, - { name: 'all', detail: 'Arrays::all() — all instances (an array of arrays)', doc: 'e.g. `ALL xs = get Arrays::all();`' }, - { name: 'get', detail: 'Arrays::get(index) — the i-th instance', doc: 'e.g. `get Arrays::get(0)`' }, - { name: 'add', detail: 'Arrays::add(arrayObject) — register an instance (called by Array/Vector __init__)', doc: 'new Array is inserted into Arrays by default' }, - { name: 'vector', detail: 'Arrays::vector() — dynamic array (new Vector, auto-growing push)', doc: 'e.g. `ALL v = Arrays::vector(); v::push(10);`' }, - { name: 'forget', detail: 'Arrays::forget(arrayObject) — remove from the registry', doc: 'e.g. `Arrays::forget(v);`' }, - { name: 'sort', detail: 'Arrays::sort(arr) — in-place sort (calls the __sort__ internal method)', doc: 'e.g. `Arrays::sort(a);` or `a::sort();`' } - ], - Solid: [ - { name: 'new', detail: 'Solid::new() — create a contiguous stream (contiguous storage + moving head pointer)', doc: 'the underlying storage of the Array/Vector classes' }, - { name: 'len', detail: 'Solid::len(stream) — remaining length (head pointer to end)', doc: 'e.g. `get Solid::len(s)`' }, - { name: 'get', detail: 'Solid::get(stream, index) — get an element relative to the head pointer', doc: 'e.g. `get Solid::get(s, 0)`' }, - { name: 'set', detail: 'Solid::set(stream, index, value) — set an element relative to the head pointer', doc: 'e.g. `Solid::set(s, 0, 42);`' }, - { name: 'push', detail: 'Solid::push(stream, value) — append at the end', doc: 'e.g. `Solid::push(s, 10);`' }, - { name: 'pop', detail: 'Solid::pop(stream) — pop from the end', doc: 'e.g. `get Solid::pop(s)`' }, - { name: 'read', detail: 'Solid::read(stream) — read at the head pointer and advance it', doc: 'e.g. `get Solid::read(s)`' }, - { name: 'peek', detail: 'Solid::peek(stream) — read at the head pointer without moving', doc: 'e.g. `get Solid::peek(s)`' }, - { name: 'head', detail: 'Solid::head(stream) — current head pointer position', doc: 'e.g. `get Solid::head(s)`' }, - { name: 'resetHead', detail: 'Solid::resetHead(stream) — reset the head pointer to zero', doc: 'e.g. `Solid::resetHead(s);`' }, - { name: 'clear', detail: 'Solid::clear(stream) — clear', doc: 'e.g. `Solid::clear(s);`' }, - { name: 'join', detail: 'Solid::join(stream, sep?) — join remaining data into a string', doc: 'e.g. `get Solid::join(s, "-")`' } - ], - Ref: [ - { name: 'read', detail: 'Ref::read(ref) — read the reference target (r/rw/m permission)', doc: 'e.g. `ALL v = get Ref::read(rr);`' }, - { name: 'write', detail: 'Ref::write(ref, value) — write the reference target (w/rw/m permission)', doc: 'e.g. `Ref::write(wr, 10);`' }, - { name: 'move', detail: 'Ref::move(ref) — take the target (movable m permission only)', doc: 'e.g. `ALL taken = Ref::move(mv);` → target washed away' }, - { name: 'target', detail: 'Ref::target(ref) — target name', doc: 'e.g. `get Ref::target(rr)` → counter' }, - { name: 'perm', detail: 'Ref::perm(ref) — permission (r/w/rw/m)', doc: 'e.g. `get Ref::perm(rr)`' } - ], - Com: [ - { name: 'abs', detail: 'Com::abs(x) — absolute value', doc: 'e.g. `get Com::abs(0-5)` → 5' }, - { name: 'min', detail: 'Com::min(x, y) — smaller of the two', doc: 'e.g. `get Com::min(3, 5)` → 3' }, - { name: 'max', detail: 'Com::max(x, y) — larger of the two', doc: 'e.g. `get Com::max(3, 5)` → 5' }, - { name: 'pow', detail: 'Com::pow(x, y) — x raised to the power y', doc: 'e.g. `get Com::pow(2, 10)` → 1024' }, - { name: 'sqrt', detail: 'Com::sqrt(x) — square root (refused for negatives)', doc: 'e.g. `get Com::sqrt(9)` → 3' }, - { name: 'floor', detail: 'Com::floor(x) — floor', doc: 'e.g. `get Com::floor(2.7)` → 2' }, - { name: 'ceil', detail: 'Com::ceil(x) — ceiling', doc: 'e.g. `get Com::ceil(2.1)` → 3' }, - { name: 'round', detail: 'Com::round(x) — round to nearest', doc: 'e.g. `get Com::round(2.5)` → 3' }, - { name: 'sign', detail: 'Com::sign(x) — sign (-1/0/1)', doc: 'e.g. `get Com::sign(0-3)` → -1' }, - { name: 'sin', detail: 'Com::sin(x) — sine', doc: 'e.g. `get Com::sin(0)` → 0' }, - { name: 'cos', detail: 'Com::cos(x) — cosine', doc: 'e.g. `get Com::cos(0)` → 1' }, - { name: 'tan', detail: 'Com::tan(x) — tangent', doc: 'e.g. `get Com::tan(0)` → 0' }, - { name: 'log', detail: 'Com::log(x) — natural logarithm (refused for x≤0)', doc: 'e.g. `get Com::log(1)` → 0' }, - { name: 'exp', detail: 'Com::exp(x) — e raised to the power x', doc: 'e.g. `get Com::exp(1)` → e' } - ], - Time: [ - { name: 'now', detail: 'Time::now() — monotonic clock seconds', doc: 'e.g. `get Time::now()`' }, - { name: 'sleep', detail: 'Time::sleep(ms) — sleep', doc: 'e.g. `Time::sleep(100);`' }, - { name: 'start', detail: 'Time::start(?) — start a timer (default first timer owned by the thread)', doc: 'e.g. `Time::start();` → thread\'s first timer, cannot be reset' }, - { name: 'fork', detail: 'Time::fork() — fork a new timer (resettable)', doc: 'e.g. `ALL t = Time::fork(); Time::reset(get t);`' }, - { name: 'elapsed', detail: 'Time::elapsed(?) — elapsed milliseconds of a timer', doc: 'e.g. `get Time::elapsed()`' }, - { name: 'reset', detail: 'Time::reset(id?) — reset (first timer refuses; forked timers allowed)', doc: 'e.g. `Time::reset(get t);`' } - ] -}; -/* IOStream is the abstract parent; CIO/FIO/SIO implement; Console is CIO's pre-forked implementation */ -STREAMS.IO = []; /* abstract: no methods of its own */ -STREAMS.Console = STREAMS.CIO; -const BUILTIN_STREAMS = ['CIO', 'FIO', 'SIO', 'IO', 'Com', 'Time', 'Solid', 'Arrays', 'Threads', 'Taskm', 'Ref', 'Console']; - -// Interface methods (completion after `Interface Name {` / when implementing) -const INTERFACES = {}; // name -> { method -> signature } filled by the parser - -// Built-in Array/Vector class methods (Bio-code classes on top of Solid) -const ARRAY_METHODS = [ - { name: 'len', detail: 'a::len() — element count', doc: 'e.g. `get a::len()`' }, - { name: 'get', detail: 'a::get(i) — element at i', doc: 'e.g. `get a::get(0)`' }, - { name: 'set', detail: 'a::set(i, v) — write element at i', doc: 'e.g. `a::set(0, 42);`' }, - { name: 'push', detail: 'a::push(v) — append', doc: 'e.g. `a::push(10);`' }, - { name: 'pop', detail: 'a::pop() — pop from the end', doc: 'e.g. `get a::pop()`' }, - { name: 'join', detail: 'a::join(sep) — join elements into a string', doc: 'e.g. `get a::join("-")`' }, - { name: 'sort', detail: 'a::sort() — in-place sort (calls __sort__ internal method)', doc: 'e.g. `a::sort();` or `Arrays::sort(a);`' }, -]; - -/** - * Find the position of the "}" matching the "{" at start (skipping strings/comments/char literals) - */ -function blockEnd(text, start) { - let depth = 0, inStr = false, inLine = false, inBlock = false; - for (let i = start; i < text.length; i++) { - const c = text[i], n = text[i + 1]; - if (inLine) { if (c === '\n') inLine = false; continue; } - if (inBlock) { if (c === '*' && n === '/') { inBlock = false; i++; } continue; } - if (inStr) { if (c === '\\') { i++; continue; } if (c === '"') inStr = false; continue; } - if (c === '/' && n === '/') { inLine = true; i++; continue; } - if (c === '/' && n === '*') { inBlock = true; i++; continue; } - if (c === '"') { inStr = true; continue; } - if (c === "'") { i++; continue; } // roughly skip char literals - if (c === '{') depth++; - else if (c === '}') { depth--; if (depth === 0) return i; } - } - return -1; -} - -/** Extract method names inside a block (line-start void/int/float/double/string/char ... name() */ -function methodsIn(block) { - const methods = new Set(); - for (const mm of block.matchAll(/\b(?:void|int|float|double|string|char)\s+([A-Za-z_]\w*)\s*\(/g)) methods.add(mm[1]); - return methods; -} - -/** Extract field names inside a block (int x, y; / int[] a; / string s; / type T; T n; generic style) */ -function fieldsIn(block) { - const fields = new Set(); - // type name[, name...]; — a name followed by a comma or semicolon is a field; a '(' means a method - for (const mm of block.matchAll(/\b(?:int|float|double|string|char|[A-Z]\w*)\s*(?:\[\])?\s+([A-Za-z_]\w*)\s*(?=(?:,|;))/g)) - fields.add(mm[1]); - // subsequent comma-separated field names: int x, y; - for (const mm of block.matchAll(/,\s*([A-Za-z_]\w*)\s*(?=(?:,|;))/g)) - fields.add(mm[1]); - return fields; -} - -/** - * Parse declared streams and their members (methods + fields) from the document text. - * Returns { known: Set(signature stream/class names), map: Map(impl name → { methods, fields }) } - */ -function parseStreams(text) { - const known = new Set(); - for (const m of text.matchAll(/\bStream\s+([A-Za-z_]\w*)/g)) known.add(m[1]); - for (const m of text.matchAll(/\bClass\s+([A-Za-z_]\w*)/g)) known.add(m[1]); - const map = new Map(); - const classNames = new Set(); - - // pass 1: signature streams' own methods/fields: Stream X { void hello(); int count; } - for (const m of text.matchAll(/\bStream\s+([A-Za-z_]\w*)\s*\{/g)) { - const braceAt = m.index + m[0].length - 1; - const end = blockEnd(text, braceAt); - if (end === -1) continue; - const inner = text.slice(braceAt + 1, end); - map.set(m[1], { methods: methodsIn(inner), fields: fieldsIn(inner) }); - } - // class declaration: Class X { int x, y; void m() {...} } - for (const m of text.matchAll(/\bClass\s+([A-Za-z_]\w*)\s*\{/g)) { - const braceAt = m.index + m[0].length - 1; - const end = blockEnd(text, braceAt); - if (end === -1) continue; - const inner = text.slice(braceAt + 1, end); - map.set(m[1], { methods: methodsIn(inner), fields: fieldsIn(inner) }); - classNames.add(m[1]); - } - - // pass 2: forks — { ... }, inheriting the signature's - // (or builtin stream's) methods/fields so completions show inherited members. - for (const m of text.matchAll(/([A-Za-z_]\w*)\s+([A-Za-z_]\w*)\s*\{/g)) { - const sig = m[1], impl = m[2]; - if (!known.has(sig) && !BUILTIN_STREAMS.includes(sig)) continue; - const braceAt = m.index + m[0].length - 1; - const end = blockEnd(text, braceAt); - if (end === -1) continue; - const inner = text.slice(braceAt + 1, end); - const methods = methodsIn(inner), fields = fieldsIn(inner); - const base = map.get(sig); - if (base) { - for (const n of base.methods) methods.add(n); - for (const n of base.fields) fields.add(n); - } - if (STREAMS[sig]) for (const mm of STREAMS[sig]) methods.add(mm.name); - map.set(impl, { methods, fields }); - } - return { known, map, classNames }; -} - -function methodItem(name, detail) { - const item = new vscode.CompletionItem(name, vscode.CompletionItemKind.Method); - item.insertText = new vscode.SnippetString(`${name}($0)`); - item.detail = detail; - return item; -} - -function fieldItem(name, detail) { - const item = new vscode.CompletionItem(name, vscode.CompletionItemKind.Property); - item.insertText = new vscode.SnippetString(`${name}`); - item.detail = detail; - return item; -} - -function activate(context) { - /* ---------- Completion: Qual:: → methods; Qual: → stream names ---------- */ - const provider = vscode.languages.registerCompletionItemProvider('bbb', { - provideCompletionItems(doc, position) { - const text = doc.getText(); - const before = text.slice(0, doc.offsetAt(position)); - const items = []; - - // already typed `Stream::` or `this::` → suggest methods + fields - const m2 = before.match(/([A-Za-z_]\w*)::$/); - if (m2) { - const qual = m2[1]; - if (STREAMS[qual]) { - for (const m of STREAMS[qual]) items.push(methodItem(m.name, m.detail)); - return items; - } - const { map } = parseStreams(text); - if (qual === 'this') { - // this:: → aggregate all declared class members (fields + methods) - const agg = { methods: new Set(), fields: new Set() }; - for (const { methods, fields } of map.values()) { - for (const n of methods) agg.methods.add(n); - for (const n of fields) agg.fields.add(n); - } - for (const name of agg.fields) items.push(fieldItem(name, 'class field (this:: attribute)')); - for (const name of agg.methods) items.push(methodItem(name, 'class method')); - return items; - } - const mem = map.get(qual); - if (mem) { - for (const name of mem.fields) items.push(fieldItem(name, `field of stream/class ${qual}`)); - for (const name of mem.methods) items.push(methodItem(name, `method of stream ${qual}`)); - return items; - } - // array/vector variable → built-in Array/Vector class methods (incl. sort) - const arrRe = new RegExp(`(?:ALL|Array|Vector)\\s+${qual}\\s*=\\s*(?:new (?:Array|Vector)|Arrays::vector\\(\\))`); - if (arrRe.test(text)) { - for (const m of ARRAY_METHODS) items.push(methodItem(m.name, m.detail)); - return items; - } - return items; - } - - // already typed `Stream:` (single colon) → suggest stream names (auto-appends ::) - const m1 = before.match(/([A-Za-z_]\w*):$/); - if (m1) { - const { known, map } = parseStreams(text); - const names = new Set([...BUILTIN_STREAMS, ...known, ...map.keys()]); - for (const n of names) { - const item = new vscode.CompletionItem(n, vscode.CompletionItemKind.Class); - item.insertText = new vscode.SnippetString(`${n}::`); - item.detail = BUILTIN_STREAMS.includes(n) ? 'builtin stream' : 'declared stream'; - items.push(item); - } - } - - // already typed `object.` → suggest class fields (request results are unwrapped with get/cause, not dot props) - const dot = before.match(/([A-Za-z_]\w*)\.$/); - if (dot) { - const { map } = parseStreams(text); - const seen = new Set(); - for (const { fields } of map.values()) - for (const n of fields) if (!seen.has(n)) { seen.add(n); items.push(fieldItem(n, 'class field (object attribute)')); } - return items; - } - - // already typed `new ` → suggest class names + array types (new int[n]) - if (before.match(/new\s+$/)) { - const { known } = parseStreams(text); - const names = new Set([...BUILTIN_STREAMS, ...known]); - for (const n of names) { - const item = new vscode.CompletionItem(n, vscode.CompletionItemKind.Class); - item.insertText = new vscode.SnippetString(`${n}($0)`); - item.detail = 'class instantiation'; - items.push(item); - } - for (const t of ['int', 'float', 'double', 'string', 'char']) { - const item = new vscode.CompletionItem(t, vscode.CompletionItemKind.Struct); - item.insertText = new vscode.SnippetString(t + '[$1:n]'); - item.detail = 'array literal new int[n]'; - items.push(item); - } - return items; - } - - // already typed `&` (smart-ref start) → suggest permissions r/w/rw/m - if (before.match(/&\s*$/)) { - for (const p of ['r', 'w', 'm', 'rw', 'rm', 'wm', 'rwm']) { - const item = new vscode.CompletionItem(p, vscode.CompletionItemKind.Keyword); - item.insertText = new vscode.SnippetString(`${p} `); - item.detail = `smart-ref permission ${p}`; - item.documentation = 'syntax: &perm follow base, e.g. &r u int p = &x'; - items.push(item); - } - return items; - } - - // already typed `&r ` (after a permission) → suggest follow layers u/f/a/t - if (before.match(/&\s*(?:r|w|m|rw|rm|wm|rwm)\s+$/)) { - for (const f of ['u', 'f', 'a', 't']) { - const item = new vscode.CompletionItem(f, vscode.CompletionItemKind.Keyword); - item.insertText = new vscode.SnippetString(`${f} `); - item.detail = `reference follow layer ${f}`; - item.documentation = 'u = program · f = method · a = area · t = thread'; - items.push(item); - } - return items; - } - return items; - } - }, ':', '&', '.', ' '); - - /* ---------- Run commands ---------- */ - /* Webview interactive run panel HTML: output area + input box (input feeds CIO/stdin directly) */ - function panelHtml(fileName) { - return ` - - - - - - -
${escapeHtml(fileName)}running…
-
-
- - -
- - -`; - } - function escapeHtml(s) { - return String(s).replace(/[&<>"']/g, c => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); - } - - const runFile = vscode.commands.registerCommand('bbb.runFile', async () => { - const doc = vscode.window.activeTextEditor && vscode.window.activeTextEditor.document; - if (!doc) { vscode.window.showWarningMessage('No file is open'); return; } - if (doc.languageId !== 'bbb') { vscode.window.showWarningMessage('The active file is not BBB (.bl/.bio)'); return; } - await doc.save(); - const fileName = doc.fileName; - const panel = vscode.window.createWebviewPanel( - 'bbbRun', `BBB — ${path.basename(fileName)}`, vscode.ViewColumn.One, - { enableScripts: true, retainContextWhenHidden: true } - ); - panel.webview.html = panelHtml(fileName); - const cwd = vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0] - ? vscode.workspace.workspaceFolders[0].uri.fsPath : path.dirname(fileName); - /* interactive child process: stdout/stderr → panel, panel input → stdin (CIO) */ - const child = spawn('bbb', [fileName], { cwd }); - let killed = false; - child.stdout.on('data', d => panel.webview.postMessage({ type: 'out', text: d.toString() })); - child.stderr.on('data', d => panel.webview.postMessage({ type: 'err', text: d.toString() })); - child.on('error', e => panel.webview.postMessage({ type: 'err', text: '[bbb] ' + e.message + '\n' })); - child.on('close', code => { - if (!killed) panel.webview.postMessage({ type: 'done', code }); - }); - panel.webview.onDidReceiveMessage(msg => { - if (msg.type === 'input') { - if (child.stdin.writable) child.stdin.write(msg.text + '\n'); - } - }); - panel.onDidDispose(() => { killed = true; child.kill(); }); - panel.webview.postMessage({ type: 'ready' }); - }); - - /* Compile current file: bbb shell build file → self-contained executable (output into workspace bin/) */ - const compileFile = vscode.commands.registerCommand('bbb.compileFile', async () => { - const doc = vscode.window.activeTextEditor && vscode.window.activeTextEditor.document; - if (!doc) { vscode.window.showWarningMessage('No file is open'); return; } - if (doc.languageId !== 'bbb') { vscode.window.showWarningMessage('The active file is not BBB (.bl/.bio)'); return; } - await doc.save(); - const out = vscode.window.createOutputChannel('BBB Compile'); - out.show(true); - const base = doc.fileName.replace(/\.[^.]+$/, ''); - const cwd = vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0] - ? vscode.workspace.workspaceFolders[0].uri.fsPath : path.dirname(doc.fileName); - const outBin = path.join('bin', path.basename(base)); /* e.g. bin/example */ - out.appendLine(`$ bbb shell build ${doc.fileName} -o ${outBin}`); - execFile('bbb', ['shell', 'build', doc.fileName, '-o', outBin], { cwd }, (err, stdout, stderr) => { - if (stdout) out.append(stdout); - if (stderr) out.append(stderr); - if (err) { - out.appendLine(`[bio compile failed, exit code ${err.code}] (make sure bio is on PATH: ~/.local/bin/bio)`); - } else { - out.appendLine(`✔ compiled: ${outBin}`); - } - }); - }); - - /* Project root: current workspace (with package.toml) or upward from the active file's directory */ - function projectRoot() { - if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0]) { - const root = vscode.workspace.workspaceFolders[0].uri.fsPath; - if (require('fs').existsSync(require('path').join(root, 'package.toml'))) return root; - } - const doc = vscode.window.activeTextEditor && vscode.window.activeTextEditor.document; - if (doc) { - let dir = path.dirname(doc.fileName); - for (let i = 0; i < 6; i++) { - if (require('fs').existsSync(path.join(dir, 'package.toml'))) return dir; - const up = path.dirname(dir); - if (up === dir) break; - dir = up; - } - } - return null; - } - - function projCmd(cmd, args, successMsg, isRun) { - const dir = projectRoot(); - if (!dir) { vscode.window.showWarningMessage('No project found (package.toml)'); return; } - const out = vscode.window.createOutputChannel('BBB Project'); - out.show(true); - out.appendLine(`$ bbb ${cmd} ${dir}${args ? ' ' + args : ''}`); - if (isRun) { - /* project run: Webview interactive (stdin input) */ - const panel = vscode.window.createWebviewPanel('bbbProjRun', `BBB Project Run`, vscode.ViewColumn.One, { enableScripts: true, retainContextWhenHidden: true }); - panel.webview.html = panelHtml('Project run (bio run)'); - const child = spawn('bbb', ['run', dir], { cwd: dir }); - let killed = false; - child.stdout.on('data', d => panel.webview.postMessage({ type: 'out', text: d.toString() })); - child.stderr.on('data', d => panel.webview.postMessage({ type: 'err', text: d.toString() })); - child.on('error', e => panel.webview.postMessage({ type: 'err', text: '[bbb] ' + e.message + '\n' })); - child.on('close', code => { if (!killed) panel.webview.postMessage({ type: 'done', code }); }); - panel.webview.onDidReceiveMessage(msg => { if (msg.type === 'input' && child.stdin.writable) child.stdin.write(msg.text + '\n'); }); - panel.onDidDispose(() => { killed = true; child.kill(); }); - panel.webview.postMessage({ type: 'ready' }); - return; - } - execFile('bbb', [cmd, dir].concat(args ? args.split(' ') : []), { cwd: dir }, (err, stdout, stderr) => { - if (stdout) out.append(stdout); - if (stderr) out.append(stderr); - if (err) out.appendLine(`[bio ${cmd} failed, exit code ${err.code}]`); - else if (successMsg) out.appendLine(successMsg); - }); - } - - const projectInit = vscode.commands.registerCommand('bbb.projectInit', async () => { - const name = await vscode.window.showInputBox({ prompt: 'Project name', value: 'myapp' }); - if (!name) return; - const out = vscode.window.createOutputChannel('BBB Project'); - out.show(true); - const ws = vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0] - ? vscode.workspace.workspaceFolders[0].uri.fsPath : ''; - execFile('bbb', ['init', ws ? path.join(ws, name) : name], {}, (err, stdout, stderr) => { - if (stdout) out.append(stdout); - if (stderr) out.append(stderr); - if (err) out.appendLine(`[bio init failed, exit code ${err.code}]`); - }); - }); - - const projectBuild = vscode.commands.registerCommand('bbb.projectBuild', () => { - const dir = projectRoot(); - if (!dir) { vscode.window.showWarningMessage('No project found (package.toml)'); return; } - const out = vscode.window.createOutputChannel('BBB Project'); - out.show(true); - out.appendLine(`$ bbb build ${dir}`); - execFile('bbb', ['build', dir, '-o', path.join(dir, 'app')], { cwd: dir }, (err, stdout, stderr) => { - if (stdout) out.append(stdout); - if (stderr) out.append(stderr); - if (err) out.appendLine(`[bio build failed, exit code ${err.code}]`); - else out.appendLine('✔ project compiled'); - }); - }); - - const projectRun = vscode.commands.registerCommand('bbb.projectRun', () => projCmd('run', null, null, true)); - - const projectInstall = vscode.commands.registerCommand('bbb.projectInstall', () => projCmd('install', null, '✔ dependencies installed')); - - const projectDestroy = vscode.commands.registerCommand('bbb.projectDestroy', async () => { - const dir = projectRoot(); - if (!dir) { vscode.window.showWarningMessage('No project found (package.toml)'); return; } - const yes = await vscode.window.showWarningMessage(`Destroy the build artifacts of ${path.basename(dir)}?`, { modal: true }, 'Destroy'); - if (!yes) return; - const out = vscode.window.createOutputChannel('BBB Project'); - out.show(true); - execFile('bbb', ['destroy', dir], { cwd: dir }, (err, stdout, stderr) => { - if (stdout) out.append(stdout); - if (stderr) out.append(stderr); - if (err) out.appendLine(`[bio destroy failed, exit code ${err.code}]`); - }); - }); - - const runDemo = vscode.commands.registerCommand('bbb.runDemo', () => { - const out = vscode.window.createOutputChannel('BBB'); - out.show(true); - out.appendLine('$ bbb'); - execFile('bbb', [], {}, (err, stdout, stderr) => { - if (stdout) out.append(stdout); - if (stderr) out.append(stderr); - if (err) out.appendLine(`[bio exit code ${err.code}] (make sure bio is on PATH: ~/.local/bin/bio)`); - }); - }); - - /* Document formatting (Shift+Alt+F): Rust wasm formatter (bbb-wasm), - * falls back to the JS formatter if the wasm cannot be loaded. */ - loadWasmFormatter(); - const formatter = vscode.languages.registerDocumentFormattingProvider('bbb', { - provideDocumentFormattingEdits(document) { - const text = document.getText(); - let formatted = null; - if (wasmLoaded && wasmFormat) { - try { formatted = wasmFormat(text); } catch (e) { formatted = null; } - } - if (formatted === null || formatted === undefined) { - formatted = formatBBB(text); // JS fallback - } - if (formatted === text) return []; - const fullRange = new vscode.Range(document.positionAt(0), - document.positionAt(text.length)); - return [vscode.TextEdit.replace(fullRange, formatted)]; - } - }); - - context.subscriptions.push(provider, formatter, runFile, compileFile, projectInit, projectBuild, projectRun, projectInstall, projectDestroy, runDemo); -} - -function deactivate() {} - -module.exports = { activate, deactivate }; diff --git a/vscode/bbb-vscode/src/formatter.js b/vscode/bbb-vscode/src/formatter.js deleted file mode 100644 index a1c80be..0000000 --- a/vscode/bbb-vscode/src/formatter.js +++ /dev/null @@ -1,224 +0,0 @@ -/* formatter.js — BBB document formatter. - * - * Formatting is deliberately conservative: it only normalizes indentation and - * whitespace around operators/commas/keywords. Strings, characters and line - * comments are passed through untouched, so formatting never changes program - * semantics (verified against the interpreter's examples). - * - * Style (matching examples/): - * - 4-space indentation, one level per block - * - spaces around binary operators ( + - * / % = == != < > <= >= ) - * - space after if/while/for and after commas - * - no space around :: or inside parens/brackets - */ - -'use strict'; - -/* True while scanning inside a "..." string, '...' char, or // comment. */ -function isInString(text, i) { - let quote = null; // '"' or "'" while inside a literal - let lineComment = false; - for (let j = 0; j < i; j++) { - const c = text[j]; - if (lineComment) continue; - if (quote) { - if (c === '\\') { j++; continue; } - if (c === quote) quote = null; - continue; - } - if (c === '"' || c === "'") { quote = c; continue; } - if (c === '/' && text[j + 1] === '/') { lineComment = true; j++; continue; } - } - return quote !== null || lineComment; -} - -/* Normalize whitespace on a single (already trimmed) line. */ -function formatLine(line) { - let out = ''; - let i = 0; - const n = line.length; - let prev = ''; // last emitted non-space char - while (i < n) { - const c = line[i]; - const next = line[i + 1]; - - if (c === '"' || c === "'") { - /* Copy string/char literal verbatim. */ - const q = c; - out += c; - i++; - while (i < n && line[i] !== q) { - if (line[i] === '\\' && i + 1 < n) { out += line[i] + line[i + 1]; i += 2; continue; } - out += line[i]; - i++; - } - if (i < n) { out += line[i]; i++; } - prev = q; - continue; - } - if (c === '/' && next === '/') { - /* Line comment: copy the rest verbatim. */ - out += line.slice(i); - break; - } - - if (c === ' ' || c === '\t') { i++; continue; } - - const two = c + (next || ''); - const three = c + (next || '') + (line[i + 2] || ''); - - if (c === '(' && (prev === 'if' || prev === 'while' || prev === 'for') && - !out.endsWith(' ')) { - out += ' '; - } - - /* Multi-char operators that never take spaces around them. */ - if (two === '::' || two === '++' || two === '--' || two === '&&' || two === '||') { - out += two; - i += 2; - prev = two; - continue; - } - /* Word operators inside strings were handled above; skip. */ - - /* Punctuation: no space before; ';' separates statements (space after), - * others take no space after. */ - if ('(),;[]'.includes(c)) { - out += c; - if (c === ';' && next !== ')') out += ' '; - if (c === ',') out += ' '; - i++; - prev = c; - continue; - } - if (c === '{' || c === '}') { - /* Space before { (e.g. "Main {") unless after '('; space after - * both braces for one-line blocks ({ x; }). */ - if (prev && prev !== '(' && prev !== ',' && !out.endsWith(' ') && !out.endsWith('{')) - out += ' '; - out += c + ' '; - i++; - prev = c; - continue; - } - - /* Binary operators: space both sides. */ - if ('=+-*/%<>!'.includes(c)) { - if (three === '===' || three === '!==') { - out += (prev && !out.endsWith(' ') ? ' ' : '') + three + ' '; - i += 3; - prev = three; - continue; - } - if (two === '==' || two === '!=' || two === '<=' || two === '>=') { - out += (prev && !out.endsWith(' ') ? ' ' : '') + two + ' '; - i += 2; - prev = two; - continue; - } - /* Single-char operator. */ - out += (prev && !out.endsWith(' ') ? ' ' : '') + c + ' '; - i++; - prev = c; - continue; - } - - /* Keywords: space after if/while/for when followed by '('. */ - if (c === ')' && next === ' ' && line[i + 2] === '{') { - out += ') '; - i += 2; - prev = ')'; - continue; - } - - /* Identifiers/numbers/keywords. */ - if (/[A-Za-z0-9_]/.test(c)) { - let j = i; - while (j < n && /[A-Za-z0-9_]/.test(line[j])) j++; - const word = line.slice(i, j); - /* Word separated from a previous word/number (e.g. `int count`). */ - if (prev && /[A-Za-z0-9_]/.test(prev[prev.length - 1]) && !out.endsWith(' ')) - out += ' '; - out += word; - i = j; - prev = word; - continue; - } - - if (c === '.') { - /* Decimal point or property access: keep glued (2.5, h.hp). */ - out += c; - i++; - prev = c; - continue; - } - - /* Anything else: keep as-is with a space if needed. */ - out += (prev && !out.endsWith(' ') ? ' ' : '') + c; - i++; - prev = c; - } - - /* Trim trailing whitespace only; the scanner never produces double - * spaces outside string literals, and string content stays untouched. */ - out = out.replace(/ +$/g, ''); - return out; -} - -/* Count leading/trailing braces that affect indentation (outside strings). */ -function countBraces(text, atStart) { - let count = 0; - let quote = null; - let comment = false; - /* Trailing-pass: leading '}' already handled the dedent; skip them so a - * line like `} else {` nets to +1 (open) and `}` alone nets to 0. */ - let i = 0; - if (!atStart) while (i < text.length && text[i] === '}') i++; - for (; i < text.length; i++) { - const c = text[i]; - if (comment) break; - if (quote) { - if (c === '\\') { i++; continue; } - if (c === quote) quote = null; - continue; - } - if (c === '"' || c === "'") { quote = c; continue; } - if (c === '/' && text[i + 1] === '/') { comment = true; continue; } - if (c === '{' || c === '}') { - if (atStart) { - if (c === '}') count--; - break; /* only leading braces matter for dedent */ - } else if (c === '{') { - count++; - } else if (c === '}') { - count--; - } - } - } - return count; -} - -function formatBBB(text) { - const lines = text.split('\n'); - const out = []; - let indent = 0; - for (const raw of lines) { - const line = raw.trim(); - if (!line) { out.push(''); continue; } - - /* Dedent before emitting a line that starts with }. */ - const leading = countBraces(line, true); - if (leading < 0) indent = Math.max(0, indent + leading); - - const formatted = formatLine(line); - out.push(' '.repeat(indent) + formatted); - - /* Indent after a line that opens a block. */ - indent += countBraces(line, false); - } - /* Remove multiple trailing blank lines. */ - while (out.length > 1 && out[out.length - 1] === '') out.pop(); - return out.join('\n') + '\n'; -} - -module.exports = { formatBBB }; diff --git a/vscode/bbb-vscode/syntaxes/bbb.tmLanguage.json b/vscode/bbb-vscode/syntaxes/bbb.tmLanguage.json deleted file mode 100644 index a7b5c93..0000000 --- a/vscode/bbb-vscode/syntaxes/bbb.tmLanguage.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", - "name": "BBB", - "scopeName": "source.bbb", - "patterns": [ - { - "include": "#comments" - }, - { - "include": "#strings" - }, - { - "include": "#numbers" - }, - { - "include": "#keywords" - }, - { - "include": "#types" - }, - { - "include": "#smartrefs" - }, - { - "include": "#qualifiers" - }, - { - "include": "#operators" - }, - { - "include": "#identifiers" - } - ], - "repository": { - "comments": { - "patterns": [ - { - "match": "//.*$", - "name": "comment.line.double-slash.bbb" - }, - { - "begin": "/\\*", - "end": "\\*/", - "name": "comment.block.bbb" - } - ] - }, - "strings": { - "patterns": [ - { - "begin": "\"", - "end": "\"", - "name": "string.quoted.double.bbb" - } - ] - }, - "numbers": { - "match": "\\b\\d+(\\.\\d+)?\\b", - "name": "constant.numeric.bbb" - }, - "keywords": { - "match": "\\b(program|Stream|Class|Interface|implements|Main|need|value|function|void|overwrite|ALL|res|ref|cause|new|const|thread|type|if|else|while|for|break|continue)\\b", - "name": "keyword.control.bbb" - }, - "types": { - "match": "\\b(int|float|double|string|char)\\b", - "name": "storage.type.bbb" - }, - "smartrefs": { - "match": "&\\s*(r|w|rw|m)\\s+[uaf]\\s+[A-Za-z_]\\w*", - "name": "variable.reference.bbb" - }, - "qualifiers": { - "match": "\\b[A-Za-z_][A-Za-z0-9_]*\\s*::\\s*[A-Za-z_][A-Za-z0-9_]*", - "name": "entity.name.function.bbb" - }, - "operators": { - "match": "::|==|!=|<=|>=|\\+=|-=|\\*=|/=|%=|\\+\\+|--|[-+*/=;,.(){}<>\\[\\]&]", - "name": "keyword.operator.bbb" - }, - "identifiers": { - "match": "\\b[A-Za-z_][A-Za-z0-9_]*\\b", - "name": "variable.other.bbb" - } - } -} \ No newline at end of file diff --git a/vscode/bbb-vscode/wasm/bbb_wasm.wasm b/vscode/bbb-vscode/wasm/bbb_wasm.wasm deleted file mode 100755 index 2ca11dd..0000000 Binary files a/vscode/bbb-vscode/wasm/bbb_wasm.wasm and /dev/null differ