diff --git a/Cargo.lock b/Cargo.lock index ebb4731..0900a5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,11 +130,20 @@ checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" name = "cli" version = "0.1.0" dependencies = [ + "compiler", "core", "frontend", "host", ] +[[package]] +name = "compiler" +version = "0.1.0" +dependencies = [ + "core", + "frontend", +] + [[package]] name = "core" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 8af5007..8f66fae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["crates/core", "crates/objects", "crates/frontend", "crates/storage", "crates/cli", "crates/server", "crates/host"] +members = ["crates/core", "crates/objects", "crates/frontend", "crates/compiler", "crates/storage", "crates/cli", "crates/server", "crates/host"] resolver = "2" [workspace.package] @@ -14,6 +14,7 @@ objects = { path = "crates/objects" } storage = { path = "crates/storage" } host = { path = "crates/host" } frontend = { path = "crates/frontend" } +compiler = { path = "crates/compiler" } peg = "=0.8.6" axum = { version = "0.8", default-features = false, features = ["http1", "json", "tokio"] } serde = { version = "1", features = ["derive"] } diff --git a/README.md b/README.md index 9fbb7ed..bbe4624 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Rust 编写的 Endor 虚拟机实现。核心对象与对象存储位于独立 o Endor 采用平台解释执行模型;AST 到字节码的生成属于解释器内部准备步骤,使用者无需先构建独立程序。资源修订与依赖锁用于选择用户代码,解释器由平台统一升级;相关设计方向见 [解释执行与平台资源版本](docs/architecture/execution-and-versions.md),资源版本管理尚未实现。 -独立 frontend 已提供 Endor 阅读器、字符 PEG lexer、peg parser、ASDL 生成的根/声明/语句/表达式 AST、遍历与编辑器接口,以及 cli parse 文件/stdin/JSON 入口。VM 从空堆启动;AST 到字节码生成、Python type/元类体系、数值算术和完整异常传播均未实现。数据库连接仅为需要数据库的特殊类保留;普通宿主不连接数据库;基础信息改用 host 包的 UTF-8 JSON 文件,不保存 VM 堆。当前边界见 [系统架构](docs/architecture/README.md)。 +独立 frontend 提供阅读器、字符 PEG lexer、token PEG parser、ASDL 生成的 AST 与编辑器接口。compiler 已连接名称/作用域分析、字节码生成和代码对象装载,CLI run/eval 可执行基础函数与表达式;可执行示例见 [compiler/examples](crates/compiler/examples/README.md)。VM 从空堆启动;Python type/元类体系、数值算术、源码闭包和完整异常传播尚未实现。数据库连接仅为需要数据库的特殊类保留;普通宿主不连接数据库;基础信息改用 host 包的 UTF-8 JSON 文件,不保存 VM 堆。当前边界见 [系统架构](docs/architecture/README.md)。 ## 从哪里读 @@ -17,6 +17,7 @@ Endor 采用平台解释执行模型;AST 到字节码的生成属于解释器 | 看字符串、流、文件与平台 URI 如何进入 lexer | [Endor 阅读器](docs/runtime/reader.md) | | 看 AST 根节点、ASDL、遍历与 JSON/编辑器接口 | [AST 模型](docs/runtime/ast.md) | | 看源码词法、语法、AST 和解析命令 | [源码前端](docs/runtime/frontend.md) | +| 看名称/作用域分析、源码编译与执行示例 | [编译链路](docs/runtime/compiler.md) | | 看指令、代码对象、执行帧和执行循环 | [字节码 VM](docs/runtime/bytecode.md) | | 看 VM/帧/对象如何存活和销毁 | [生命周期](docs/runtime/lifecycle.md) | | 看基础信息文件和旧库迁移 | [宿主信息](docs/operations/host-information.md) | @@ -29,8 +30,8 @@ Endor 采用平台解释执行模型;AST 到字节码的生成属于解释器 | 文件或目录 | 职责 | | --- | --- | -| Cargo.toml / Cargo.lock | 工作区、包依赖、Rust 版本要求及依赖锁定;包名与目录一致:core、objects、frontend、storage、host、cli、server | -| crates/ | core、objects、frontend、storage、host、cli、server 七个包,每个目录的 README 解释本目录 | +| Cargo.toml / Cargo.lock | 工作区、包依赖、Rust 版本要求及依赖锁定;包名与目录一致:core、objects、frontend、compiler、storage、host、cli、server | +| crates/ | core、objects、frontend、compiler、storage、host、cli、server 八个包,每个目录的 README 解释本目录 | | docs/ | 跨目录的架构、运行时契约、配置、运行、部署与维护说明 | | scripts/ | 指定远端环境中的检查、冒烟、CI 与部署脚本 | | .gitea/ | Gitea Actions 工作流 | diff --git a/crates/README.md b/crates/README.md index 199e900..47b5bd1 100644 --- a/crates/README.md +++ b/crates/README.md @@ -7,13 +7,14 @@ | [core](core/README.md) | core,库 endor_core | VM、执行帧与字节码求值循环,仅依赖本地 objects | | [objects](objects/README.md) | objects | 零依赖的核心对象、对象存储、代码对象与指令数据/静态验证 | | [frontend](frontend/README.md) | frontend | Endor 阅读器、字符 PEG lexer、token PEG parser、ASDL 生成的 AST/遍历/JSON 与编辑器接口,不执行源码 | +| [compiler](compiler/README.md) | compiler | AST 名称/作用域分析、wordcode 模板生成和 VM 代码对象装载 | | [host](host/README.md) | host | UTF-8 JSON 文件基础信息,与数据库分离 | | [storage](storage/README.md) | storage | 为数据库特殊类保留的显式连接适配,不提供基础信息存储 | -| [cli](cli/README.md) | cli,二进制 cli | 引导演示、源码解析与文件信息命令,不使用数据库 | +| [cli](cli/README.md) | cli,二进制 cli | 引导演示、源码解析/执行与文件信息命令,不使用数据库 | | [server](server/README.md) | server,二进制 server | HTTP 宿主与文件版本信息,不使用数据库 | -CLI 依赖 Core、Frontend 与 Host,Server 依赖 Core 与 Host,二者不依赖 Storage;Frontend 依赖 peg,不依赖 Core;Core 只依赖本地 Objects;Objects 不依赖 Core 或第三方包,二者均不依赖 Storage。未来特殊类的宿主实现按需依赖 Storage,见 [数据库对象](../docs/runtime/database-objects.md)。 +CLI 依赖 Compiler、Core、Frontend 与 Host,Server 依赖 Core 与 Host,二者不依赖 Storage;Compiler 依赖 Frontend 与 Core;Frontend 依赖 peg、serde/serde_json,不依赖 Core;Core 只依赖本地 Objects;Objects 不依赖 Core 或第三方包,二者均不依赖 Storage。未来特殊类的宿主实现按需依赖 Storage,见 [数据库对象](../docs/runtime/database-objects.md)。 -Endor 的解释执行流程与平台资源版本方向见 [执行与版本模型](../docs/architecture/execution-and-versions.md)。Frontend 到 AST 与 Core 执行字节码目前仍是两段独立能力,后续需要连接内部字节码生成;版本锁、平台资源后端和持久化字节码缓存均未实现。 +Endor 的解释执行流程与平台资源版本方向见 [执行与版本模型](../docs/architecture/execution-and-versions.md)。Compiler 已连接 Frontend 与 Core 的最小源码执行链路,见 [编译契约](../docs/runtime/compiler.md);源码闭包、数值算术、版本锁、平台资源后端和持久化字节码缓存仍未实现。 core 的 Rust 库名为 endor_core,避免与标准库 core 重名,见 [Cargo 目标说明](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-name-field)。网络、数据库、进程配置和信号不进入 Core。全局说明见 [架构](../docs/architecture/README.md),返回 [项目入口](../README.md)。 diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 4ff34f5..c53b3d7 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -10,6 +10,7 @@ name = "cli" path = "src/main.rs" [dependencies] +compiler.workspace = true core.workspace = true frontend.workspace = true host.workspace = true diff --git a/crates/cli/README.md b/crates/cli/README.md index 96c9c2f..23c1d91 100644 --- a/crates/cli/README.md +++ b/crates/cli/README.md @@ -1,11 +1,11 @@ # cli -同步命令行宿主,提供 bootstrap、parse、help 和文件 metadata 命令。bootstrap 演示两个运行时对象并释放,不读取数据库配置、不连接或写入数据库。 +同步命令行宿主,提供 bootstrap、parse、run、eval、help 和文件 metadata 命令。bootstrap 演示两个运行时对象并释放,不读取数据库配置、不连接或写入数据库。 | 内容 | 职责 | | --- | --- | -| Cargo.toml | cli 二进制与 core/frontend/host 依赖;无 storage 或 Tokio 依赖 | -| [src](src/README.md) | 参数路由、引导演示、文件/stdin 阅读、源码解析和退出码 | -| [tests](tests/README.md) | parse 命令的 AST/JSON 输出、解析模式、错误位置和严格 UTF-8 检查 | +| Cargo.toml | cli 二进制与 compiler/core/frontend/host 依赖;无 storage 或 Tokio 依赖 | +| [src](src/README.md) | 参数路由、引导演示、文件/stdin 阅读、源码解析/执行和退出码 | +| [tests](tests/README.md) | parse 的 AST/JSON 与 run/eval 的文件/stdin 执行、错误和退出码 | -运行方式见 [运行指南](../../docs/operations/README.md),输入边界见 [配置](../../docs/operations/configuration.md)。parse 通过 EndorReader 严格读取 UTF-8 文件或 `-` 指定的 stdin 并输出 AstRoot;--mode 选择 module/expression/interactive,--json 输出带 schema 的 AST JSON,不执行源码,见 [前端](../../docs/runtime/frontend.md);metadata put/get 和只导入缺失项的 import 均使用文件,见 [宿主信息](../../docs/operations/host-information.md)。返回 [工作区](../README.md)。 +运行方式见 [运行指南](../../docs/operations/README.md),输入边界见 [配置](../../docs/operations/configuration.md)。parse 通过 EndorReader 严格读取 UTF-8 文件或 `-` 指定的 stdin 并输出 AstRoot;--mode 选择 module/expression/interactive,--json 输出带 schema 的 AST JSON,不执行源码,见 [前端](../../docs/runtime/frontend.md);run 调用模块的无参数入口(默认 main),eval 执行独立表达式,见 [编译链路](../../docs/runtime/compiler.md);metadata put/get 和只导入缺失项的 import 均使用文件,见 [宿主信息](../../docs/operations/host-information.md)。返回 [工作区](../README.md)。 diff --git a/crates/cli/src/README.md b/crates/cli/src/README.md index e5b1a12..31c24be 100644 --- a/crates/cli/src/README.md +++ b/crates/cli/src/README.md @@ -1,7 +1,9 @@ # CLI 入口 -[main.rs](main.rs) 是唯一入口:同步 main 将 run 的结果转成进程退出码;run 处理 bootstrap、parse 文件/stdin、help、文件 metadata put/get/import,错误打印到 stderr 并返回失败。 +[main.rs](main.rs) 是二进制入口:同步 main 将 run 的结果转成进程退出码;run 路由 bootstrap、parse、源码 run/eval、help、文件 metadata put/get/import,错误打印到 stderr 并返回失败。 bootstrap 创建并显式释放两个对象,不读取数据库环境、不打开数据库。parse_command 使用 EndorReader 和 parse_source_ast 输出 AstRoot;--mode 选择 module/expression/interactive(默认 module),--json 通过 AstDocument 输出 JSON,格式见 [AST 模型](../../../docs/runtime/ast.md);`-` 从 stdin 读到 EOF,来源为 ``。词法/语法错误显示来源、行、列;不创建 VM 或执行源码。未知命令报错;metadata 通过 host::MetadataStore 读写文件,import 从标准输入接收旧记录且不覆盖已有文件。参数目前由 std::env::args 读取,非 Unicode 参数可能 panic,边界见 [配置](../../../docs/operations/configuration.md)。 +[execute.rs](execute.rs) 负责 run/eval 的文件/stdin 阅读、调用 compiler、创建 VM/globals、模块初始化与入口调用、结果格式化、traceback 和成功/失败后的根释放;命名空间由 CLI 自己创建并清空,见 [编译契约](../../../docs/runtime/compiler.md)。 + 新增命令时更新用法、[运行指南](../../../docs/operations/README.md) 和 [smoke.py 的导航](../../../scripts/README.md)。返回 [cli](../README.md)。 diff --git a/crates/cli/src/execute.rs b/crates/cli/src/execute.rs new file mode 100644 index 0000000..8e92486 --- /dev/null +++ b/crates/cli/src/execute.rs @@ -0,0 +1,105 @@ +use std::error::Error; + +use compiler::{CompileOptions, compile_source}; +use endor_core::{Value, Vm}; +use frontend::{EndorReader, ParseMode, SourceUnit}; + +pub(super) fn run_command(arguments: &[&str]) -> Result<(), Box> { + let mut entry = "main"; + let mut path = None; + let mut arguments = arguments.iter().copied(); + while let Some(argument) = arguments.next() { + match argument { + "--entry" => { + entry = arguments + .next() + .filter(|name| !name.is_empty() && !name.starts_with("--")) + .ok_or(super::USAGE)?; + } + value if !value.starts_with("--") && path.is_none() => path = Some(value), + _ => return Err(super::USAGE.into()), + } + } + let source = read(path.ok_or(super::USAGE)?)?; + let output = execute(&source, ParseMode::Module, Some(entry))?; + println!("{output}"); + Ok(()) +} + +pub(super) fn eval_command(path: &str) -> Result<(), Box> { + let source = read(path)?; + let output = execute(&source, ParseMode::Expression, None)?; + println!("{output}"); + Ok(()) +} + +fn read(path: &str) -> Result> { + Ok(if path == "-" { + EndorReader::from_stream("", std::io::stdin().lock())? + } else { + EndorReader::from_file(path)? + }) +} + +fn execute( + source: &SourceUnit, + mode: ParseMode, + entry: Option<&str>, +) -> Result> { + let compiled = compile_source(source, mode, &CompileOptions::default())?; + let mut vm = Vm::new()?; + let code = compiled.load(&mut vm)?; + let globals = match vm.new_dict() { + Ok(globals) => globals, + Err(error) => { + vm.release(code)?; + return Err(error.into()); + } + }; + let result = (|| -> Result> { + let mut value = vm.execute(code, globals)?; + if let Some(entry) = entry { + vm.release_value(value)?; + let Some(Value::Object(function)) = vm.dict_get(globals, &Value::Text(entry.into()))? + else { + return Err(format!("entry function not found: {entry}").into()); + }; + value = vm.call(function, &[])?; + } + let output = format_value(&vm, &value); + vm.release_value(value)?; + output + })(); + // Copy traceback metadata before cleanup. This is the entry location of + // each code object plus bytecode offset, not a per-instruction source map. + let result = result.map_err(|error| { + let mut message = format!("{}: {error}", source.origin()); + for location in vm.last_traceback() { + message.push_str(&format!( + "\n at {} ({}:{}, code unit {})", + location.function, + location.filename, + location.first_line, + location.instruction_offset + )); + } + Box::::from(message) + }); + vm.release(code)?; + // CLI owns this fresh namespace. Clearing it breaks function↔globals + // cycles on both success and failure; embedders choose their own lifetime. + vm.dict_clear(globals)?; + vm.release(globals)?; + vm.collect_cycles(); + result +} + +fn format_value(vm: &Vm, value: &Value) -> Result> { + Ok(match value { + Value::Nil => "nil".into(), + Value::Bool(value) => value.to_string(), + Value::Text(value) => value.clone(), + Value::Number(value) => value.to_string(), + Value::Object(reference) => vm.str(*reference)?, + }) +} diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 1dd720f..a0f2c4e 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -6,7 +6,9 @@ use frontend::ast::AstDocument; use frontend::{EndorReader, ParseMode, parse_source_ast}; use host::MetadataStore; -const USAGE: &str = "Usage: cli [bootstrap | parse [--mode module|expression|interactive] [--json] | metadata put | metadata get | metadata import | help]"; +mod execute; + +const USAGE: &str = "Usage: cli [bootstrap | run [--entry name] | eval | parse [--mode module|expression|interactive] [--json] | metadata put | metadata get | metadata import | help]"; fn main() -> ExitCode { match run() { @@ -48,6 +50,8 @@ fn run() -> Result<(), Box> { } ["help"] | ["--help"] | ["-h"] => println!("{USAGE}"), ["parse", arguments @ ..] => parse_command(arguments)?, + ["run", arguments @ ..] => execute::run_command(arguments)?, + ["eval", path] => execute::eval_command(path)?, ["metadata", "put", key, value] => { MetadataStore::from_env()?.put(key, value)?; println!("stored (file)"); diff --git a/crates/cli/tests/README.md b/crates/cli/tests/README.md index 7fee55b..8105067 100644 --- a/crates/cli/tests/README.md +++ b/crates/cli/tests/README.md @@ -2,4 +2,6 @@ [parse.rs](parse.rs) 使用 Cargo 提供的 cli 测试二进制检查 parse 命令:合法 UTF-8 源码输出 AST 且不执行名称调用;非法语法输出文件/行/列并返回失败;非法 UTF-8 不替换字符;`parse -` 检查 stdin 输入、EOF 和带流名的错误;--json/--mode 检查表达式与交互根、schema 标识以及非法模式退出。 +[execute.rs](execute.rs) 检查 compiler/examples 中的真实 Endor 文件、run/eval、显式入口、stdin、运行时 traceback、名称/编译/语法/UTF-8 错误和无成功输出的失败退出。 + 测试仅在 ces-control 的工作区检查中运行,本地不执行。测试临时文件使用独立名称并在结束时清理。返回 [cli](../README.md),验证约定见 [验证](../../../docs/operations/validation.md)。 diff --git a/crates/cli/tests/execute.rs b/crates/cli/tests/execute.rs new file mode 100644 index 0000000..c5e04c9 --- /dev/null +++ b/crates/cli/tests/execute.rs @@ -0,0 +1,136 @@ +use std::io::Write; +use std::process::{Command, Output, Stdio}; + +fn stdin_command(arguments: &[&str], source: &[u8]) -> Output { + let mut child = Command::new(env!("CARGO_BIN_EXE_cli")) + .args(arguments) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + child.stdin.take().unwrap().write_all(source).unwrap(); + child.wait_with_output().unwrap() +} + +#[test] +fn run_executes_real_endor_files_and_prints_entry_results() { + for (file, expected) in [ + ("hello.endor", "你好,Endor 🌱\n"), + ("calls.endor", "函数调用已接通\n"), + ("shadowing.endor", "局部名称优先\n"), + ("number.endor", "-123456789012345678901234567890.00100\n"), + ] { + let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../compiler/examples") + .join(file); + let output = Command::new(env!("CARGO_BIN_EXE_cli")) + .arg("run") + .arg(path) + .output() + .unwrap(); + assert!( + output.status.success(), + "{}", + String::from_utf8_lossy(&output.stderr) + ); + assert_eq!(String::from_utf8(output.stdout).unwrap(), expected); + assert!(output.stderr.is_empty()); + } +} + +#[test] +fn eval_and_run_read_stdin_and_support_explicit_entry_names() { + let output = stdin_command(&["eval", "-"], "\"表达式🌱\"".as_bytes()); + assert!( + output.status.success(), + "{}", + String::from_utf8_lossy(&output.stderr) + ); + assert_eq!(String::from_utf8(output.stdout).unwrap(), "表达式🌱\n"); + let output = stdin_command( + &["run", "--entry", "greet", "-"], + b"function greet(): String { return \"ok\" }", + ); + assert!( + output.status.success(), + "{}", + String::from_utf8_lossy(&output.stderr) + ); + assert_eq!(String::from_utf8(output.stdout).unwrap(), "ok\n"); + let path = + std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../compiler/examples/literal.endor"); + let output = Command::new(env!("CARGO_BIN_EXE_cli")) + .arg("eval") + .arg(path) + .output() + .unwrap(); + assert!(output.status.success()); + assert_eq!( + String::from_utf8(output.stdout).unwrap(), + "表达式 → 字节码 → VM\n" + ); +} + +#[test] +fn execution_errors_fail_without_success_output_and_report_their_stage() { + for (arguments, source, expected) in [ + ( + vec!["run", "-"], + "function main(): T {\n return missing\n}", + ":2:10: undefined name: missing", + ), + ( + vec!["run", "-"], + "function main(): T { return f() } function f(x: T): T { return x }", + "positional arguments", + ), + ( + vec!["run", "-"], + "function other(): T { return 1 }", + "entry function not found: main", + ), + ( + vec!["eval", "-"], + "1 + 2", + "arithmetic bytecode is not implemented", + ), + (vec!["eval", "-"], "1()", "does not support call"), + ( + vec!["run", "-"], + "function main(): T { return ) }", + "expected", + ), + ] { + let output = stdin_command(&arguments, source.as_bytes()); + assert!(!output.status.success()); + assert!(output.stdout.is_empty()); + let error = String::from_utf8(output.stderr).unwrap(); + assert!(error.contains(expected), "{error}"); + if expected == "positional arguments" { + assert!(error.contains("at main (:1, code unit")); + } + } + let output = stdin_command(&["eval", "-"], &[0xff]); + assert!(!output.status.success()); + assert!( + String::from_utf8(output.stderr) + .unwrap() + .contains("invalid UTF-8") + ); + for arguments in [ + vec!["run"], + vec!["run", "--entry"], + vec!["run", "--unknown", "-"], + vec!["eval"], + vec!["eval", "-", "extra"], + ] { + let output = Command::new(env!("CARGO_BIN_EXE_cli")) + .args(arguments) + .output() + .unwrap(); + assert!(!output.status.success()); + assert!(output.stdout.is_empty()); + assert!(String::from_utf8(output.stderr).unwrap().contains("Usage:")); + } +} diff --git a/crates/compiler/Cargo.toml b/crates/compiler/Cargo.toml new file mode 100644 index 0000000..65e0222 --- /dev/null +++ b/crates/compiler/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "compiler" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true + +[dependencies] +core.workspace = true +frontend.workspace = true + +[lints] +workspace = true diff --git a/crates/compiler/README.md b/crates/compiler/README.md new file mode 100644 index 0000000..fc38943 --- /dev/null +++ b/crates/compiler/README.md @@ -0,0 +1,12 @@ +# compiler + +最小 Endor 编译器:frontend AST → 名称/作用域分析 → wordcode 模板 → VM 代码对象。依赖本地 frontend 和 core(Rust 库名 endor_core),不引入第三方依赖,不让 core 反向依赖 parser。运行时基础类和类型注解求值不是编译前提。 + +| 内容 | 职责 | +| --- | --- | +| [Cargo.toml](Cargo.toml) | compiler 包、本地 frontend/core 依赖与工作区 lint | +| [src](src/README.md) | 分析、字节码生成与显式代码对象装载 | +| [examples](examples/README.md) | 可供 cli run/eval 的 Endor 源码文件,也是远端集成测试输入 | +| [tests](tests/README.md) | 源码到 VM、错误路径、名称与对象生命周期检查 | + +公开入口为 analyze、compile_ast、compile_source、CompileOptions 和 CompiledCode::load。完整规则与所有权见 [编译链路](../../docs/runtime/compiler.md);CLI 使用见 [运行指南](../../docs/operations/README.md)。修改语法仍进入 [frontend](../frontend/README.md),修改指令/验证进入 [objects](../objects/README.md),修改执行进入 [core](../core/README.md)。返回 [工作区](../README.md)。 diff --git a/crates/compiler/examples/README.md b/crates/compiler/examples/README.md new file mode 100644 index 0000000..d0d77c5 --- /dev/null +++ b/crates/compiler/examples/README.md @@ -0,0 +1,21 @@ +# 可执行 Endor 示例 + +这些 UTF-8 文件由 compiler 和 CLI 集成测试直接使用;运行只在指定远端环境进行,本地不运行。 + +| 文件 | 输入模式与预期结果 | +| --- | --- | +| [hello.endor](hello.endor) | run;main 返回“你好,Endor 🌱” | +| [calls.endor](calls.endor) | run;前向函数引用、参数和 let,返回“函数调用已接通” | +| [shadowing.endor](shadowing.endor) | run;参数遮蔽全局函数名并传递函数值,返回“局部名称优先” | +| [number.endor](number.endor) | run;返回无损负十进制常量 -123456789012345678901234567890.00100 | +| [literal.endor](literal.endor) | eval;返回“表达式 → 字节码 → VM” | + +```sh +cli run crates/compiler/examples/hello.endor +cli run crates/compiler/examples/calls.endor +cli run crates/compiler/examples/shadowing.endor +cli run crates/compiler/examples/number.endor +cli eval crates/compiler/examples/literal.endor +``` + +run 默认调用无参数 main,可用 --entry name 选择另一个无参数入口;eval 解析一个完整表达式,二者都支持 - 读取 stdin 到 EOF。类型名称暂只保留语法,Callable 不要求预先注册语言类。例子不依赖 print 或其他默认标准库。编译器尚无算术运行语义;前端 basic.endor 含算术,只是解析示例,不能混作可执行示例。规则见 [编译契约](../../../docs/runtime/compiler.md),返回 [compiler](../README.md)。 diff --git a/crates/compiler/examples/calls.endor b/crates/compiler/examples/calls.endor new file mode 100644 index 0000000..4d5744c --- /dev/null +++ b/crates/compiler/examples/calls.endor @@ -0,0 +1,13 @@ +// The callee is declared after main. Parameters, locals and calls use wordcode. +function main(): String { + let message = echo("函数调用已接通") + return relay(message) +} + +function relay(value: String): String { + return echo(value) +} + +function echo(value: String): String { + return value +} diff --git a/crates/compiler/examples/hello.endor b/crates/compiler/examples/hello.endor new file mode 100644 index 0000000..74f4030 --- /dev/null +++ b/crates/compiler/examples/hello.endor @@ -0,0 +1,4 @@ +// cli run crates/compiler/examples/hello.endor → 你好,Endor 🌱 +function main(): String { + return "你好,Endor 🌱" +} diff --git a/crates/compiler/examples/literal.endor b/crates/compiler/examples/literal.endor new file mode 100644 index 0000000..d60820f --- /dev/null +++ b/crates/compiler/examples/literal.endor @@ -0,0 +1,2 @@ +// cli eval crates/compiler/examples/literal.endor +"表达式 → 字节码 → VM" diff --git a/crates/compiler/examples/number.endor b/crates/compiler/examples/number.endor new file mode 100644 index 0000000..3da4a47 --- /dev/null +++ b/crates/compiler/examples/number.endor @@ -0,0 +1,5 @@ +// Signed decimal constants retain all digits; no f64 conversion or arithmetic. +function main(): Number { + let value = -123456789012345678901234567890.00100 + return value +} diff --git a/crates/compiler/examples/shadowing.endor b/crates/compiler/examples/shadowing.endor new file mode 100644 index 0000000..e509fdb --- /dev/null +++ b/crates/compiler/examples/shadowing.endor @@ -0,0 +1,13 @@ +// A parameter can shadow a module function, and functions are ordinary values. +function echo(value: String): String { + return value +} + +function invoke(echo: Callable, value: String): String { + let chosen = echo + return chosen(value) +} + +function main(): String { + return invoke(echo, "局部名称优先") +} diff --git a/crates/compiler/src/README.md b/crates/compiler/src/README.md new file mode 100644 index 0000000..069d398 --- /dev/null +++ b/crates/compiler/src/README.md @@ -0,0 +1,9 @@ +# Compiler 源码 + +| 文件 | 职责 | +| --- | --- | +| [lib.rs](lib.rs) | CompileOptions、公开分析/编译入口、AST 原文范围检查与带来源诊断 | +| [analysis.rs](analysis.rs) | 模块/函数/表达式/单次交互作用域、局部槽、全局名称、逐引用绑定和初始化检查 | +| [codegen.rs](codegen.rs) | AST 到已有指令、嵌套代码模板、EndorCode 验证、显式 VM 装载与临时根释放 | + +节点定义在 frontend/Endor.asdl;这里匹配生成的 AST,不复制节点类型。源码和调用方提供的 AST 都先分析后生成,不执行 AST。修改名称语义、代码布局或所有权时同步 [编译契约](../../../docs/runtime/compiler.md)、[检查](../tests/README.md) 与 [示例](../examples/README.md)。返回 [compiler](../README.md)。 diff --git a/crates/compiler/src/analysis.rs b/crates/compiler/src/analysis.rs new file mode 100644 index 0000000..d8820ef --- /dev/null +++ b/crates/compiler/src/analysis.rs @@ -0,0 +1,287 @@ +use std::collections::BTreeSet; + +use endor_core::CodeLayout; +use frontend::ast::{AstRoot, Decl, DeclKind, Expr, ExprKind, Function, Spanned, StmtKind}; +use frontend::{Diagnostic, DiagnosticKind, Span}; + +use crate::{CompileOptions, diagnostic}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ScopeKind { + Module, + Expression, + Interactive, + Function, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum NameBinding { + Local(usize), + Global(usize), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NameUse { + pub name: String, + pub span: Span, + pub binding: NameBinding, +} + +#[derive(Debug, Clone)] +pub struct Scope { + pub kind: ScopeKind, + pub name: String, + pub span: Span, + pub layout: CodeLayout, + pub references: Vec, +} + +#[derive(Debug, Clone)] +pub struct SymbolTable { + pub root: Scope, + /// Top-level function scopes in declaration order. Nested declarations + /// are rejected until source closure semantics are implemented. + pub functions: Vec, +} + +fn error(span: Span, message: impl Into) -> Diagnostic { + diagnostic(DiagnosticKind::Semantic, span, message) +} + +fn definition(declaration: &Decl) -> &Function { + let DeclKind::Function(function) = &declaration.kind; + function +} + +/// Resolve a closed source unit plus explicitly declared host globals. +/// Function locals are collected before reading expressions; a later let +/// shadows the global for the entire function, but cannot be read early. +pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result { + if let AstRoot::Interactive(input) = root + && input.statements.len() != 1 + { + return Err(error( + input.span, + "interactive input must contain exactly one statement", + )); + } + let mut globals: BTreeSet = options.globals.iter().cloned().collect(); + if globals.contains("") { + return Err(error(root.span(), "host global names must not be empty")); + } + let functions: Vec<&Function> = match root { + AstRoot::Module(program) => program.declarations.iter().map(definition).collect(), + AstRoot::Interactive(input) => input + .statements + .iter() + .filter_map(|statement| { + if let StmtKind::Declaration(declaration) = &statement.kind { + Some(definition(declaration)) + } else { + None + } + }) + .collect(), + AstRoot::Expression(_) => Vec::new(), + }; + let (kind, name) = match root { + AstRoot::Module(_) => (ScopeKind::Module, ""), + AstRoot::Expression(_) => (ScopeKind::Expression, ""), + AstRoot::Interactive(_) => (ScopeKind::Interactive, ""), + }; + let mut root_scope = Builder::new(kind, name, root.span()); + let mut declared = BTreeSet::new(); + for function in &functions { + declare(&mut declared, &function.name.text, function.name.span)?; + globals.insert(function.name.text.clone()); + root_scope.intern(&function.name.text); + } + // Analyze all bodies after registering all functions, enabling forward + // references and recursion without instantiating function objects here. + let scopes = functions + .iter() + .map(|function| function_scope(function, &globals)) + .collect::, _>>()?; + match root { + AstRoot::Module(_) => {} + AstRoot::Expression(expression) => root_scope.expression(expression, &globals)?, + AstRoot::Interactive(input) => { + for statement in &input.statements { + match &statement.kind { + StmtKind::Binding(binding) => { + declare(&mut declared, &binding.name.text, binding.name.span)?; + root_scope.expression(&binding.value, &globals)?; + globals.insert(binding.name.text.clone()); + root_scope.intern(&binding.name.text); + } + StmtKind::Declaration(_) => {} + StmtKind::Expression(expression) => { + root_scope.expression(expression, &globals)? + } + StmtKind::Return(_) => { + return Err(error(statement.span, "return outside a function")); + } + } + } + } + } + Ok(SymbolTable { + root: root_scope.scope, + functions: scopes, + }) +} + +fn declare(names: &mut BTreeSet, name: &str, span: Span) -> Result<(), Diagnostic> { + if name.is_empty() || !names.insert(name.to_owned()) { + return Err(error( + span, + format!("duplicate or empty declaration: {name}"), + )); + } + Ok(()) +} + +fn function_scope(function: &Function, globals: &BTreeSet) -> Result { + let mut builder = Builder::new(ScopeKind::Function, &function.name.text, function.span); + let mut locals = BTreeSet::new(); + for parameter in &function.parameters { + declare(&mut locals, ¶meter.name.text, parameter.name.span)?; + builder + .scope + .layout + .parameters + .push(parameter.name.text.clone()); + builder + .scope + .layout + .locals + .push(parameter.name.text.clone()); + builder.initialized.insert(parameter.name.text.clone()); + } + for statement in &function.body.statements { + match &statement.kind { + StmtKind::Binding(binding) => { + declare(&mut locals, &binding.name.text, binding.name.span)?; + builder.scope.layout.locals.push(binding.name.text.clone()); + } + StmtKind::Declaration(_) => { + return Err(error( + statement.span, + "nested function declarations are not compiled yet", + )); + } + _ => {} + } + } + let mut returned = false; + for statement in &function.body.statements { + if returned { + return Err(error( + statement.span, + "statement after return is unreachable", + )); + } + match &statement.kind { + StmtKind::Binding(binding) => { + builder.expression(&binding.value, globals)?; + builder.initialized.insert(binding.name.text.clone()); + } + StmtKind::Return(statement) => { + if let Some(value) = &statement.value { + builder.expression(value, globals)?; + } + returned = true; + } + StmtKind::Expression(expression) => builder.expression(expression, globals)?, + StmtKind::Declaration(_) => unreachable!("nested declaration rejected above"), + } + } + Ok(builder.scope) +} + +struct Builder { + scope: Scope, + initialized: BTreeSet, +} + +impl Builder { + fn new(kind: ScopeKind, name: &str, span: Span) -> Self { + Self { + scope: Scope { + kind, + name: name.into(), + span, + layout: CodeLayout::default(), + references: Vec::new(), + }, + initialized: BTreeSet::new(), + } + } + + fn intern(&mut self, name: &str) -> usize { + if let Some(index) = self + .scope + .layout + .names + .iter() + .position(|candidate| candidate == name) + { + index + } else { + self.scope.layout.names.push(name.to_owned()); + self.scope.layout.names.len() - 1 + } + } + + fn expression( + &mut self, + expression: &Expr, + globals: &BTreeSet, + ) -> Result<(), Diagnostic> { + match &expression.kind { + ExprKind::Name(name) => { + let binding = if let Some(index) = self + .scope + .layout + .locals + .iter() + .position(|local| local == name) + { + if !self.initialized.contains(name) { + return Err(error( + expression.span, + format!("local variable read before initialization: {name}"), + )); + } + NameBinding::Local(index) + } else if globals.contains(name) { + NameBinding::Global(self.intern(name)) + } else { + return Err(error(expression.span, format!("undefined name: {name}"))); + }; + self.scope.references.push(NameUse { + name: name.clone(), + span: expression.span, + binding, + }); + } + ExprKind::Unary { operand, .. } => self.expression(operand, globals)?, + ExprKind::Binary { left, right, .. } => { + self.expression(left, globals)?; + self.expression(right, globals)?; + } + ExprKind::Member { object, name } => { + self.expression(object, globals)?; + self.intern(&name.text); + } + ExprKind::Call { callee, arguments } => { + self.expression(callee, globals)?; + for argument in arguments { + self.expression(argument, globals)?; + } + } + ExprKind::Number(_) | ExprKind::String(_) | ExprKind::Symbol(_) => {} + } + Ok(()) + } +} diff --git a/crates/compiler/src/codegen.rs b/crates/compiler/src/codegen.rs new file mode 100644 index 0000000..e65d20e --- /dev/null +++ b/crates/compiler/src/codegen.rs @@ -0,0 +1,315 @@ +use endor_core::{ + CodeLayout, DecodedInstruction, EndorCode, EndorNumber, Instruction, ObjectRef, Opcode, Value, + Vm, VmError, +}; +use frontend::ast::{AstRoot, Decl, DeclKind, Expr, ExprKind, Stmt, StmtKind, UnaryOp}; +use frontend::{Diagnostic, DiagnosticKind, SourceUnit, Span}; + +use crate::{NameBinding, Scope, ScopeKind, SymbolTable, diagnostic}; + +/// Validated, heap-independent executable template. Nested code constants +/// remain templates until load(), so no ObjectRef is shared across VM heaps. +#[derive(Debug, Clone)] +pub struct CompiledCode { + code: EndorCode, + children: Vec<(usize, CompiledCode)>, +} + +impl CompiledCode { + pub fn instructions(&self) -> &[DecodedInstruction] { + self.code.instructions() + } + pub fn layout(&self) -> &CodeLayout { + self.code.layout() + } + pub fn name(&self) -> &str { + self.code.name() + } + pub fn filename(&self) -> &str { + self.code.filename() + } + pub fn first_line(&self) -> usize { + self.code.first_line() + } + pub fn children(&self) -> impl Iterator { + self.children.iter().map(|(_, child)| child) + } + + /// Return one owned code-object root; release it after execution. The + /// parent retains its nested code constants. Temporary child roots are + /// released on both success and failure, including partial recursive loads. + pub fn load(&self, vm: &mut Vm) -> Result { + let mut roots = Vec::new(); + let result = (|| { + let mut constants = self.code.constants().to_vec(); + for (index, child) in &self.children { + let reference = child.load(vm)?; + roots.push(reference); + constants[*index] = Value::Object(reference); + } + let code = EndorCode::with_layout( + self.code.bytecode().to_vec(), + constants, + self.code.layout().clone(), + )? + .with_location( + self.code.name(), + self.code.filename(), + self.code.first_line(), + ); + vm.new_code(code) + })(); + for reference in roots { + vm.release(reference) + .expect("loader owns each temporary code root"); + } + result + } +} + +fn error(span: Span, message: impl Into) -> Diagnostic { + diagnostic(DiagnosticKind::Compilation, span, message) +} + +pub(crate) fn generate( + root: &AstRoot, + source: &SourceUnit, + symbols: &SymbolTable, +) -> Result { + let mut emitter = Emitter::new(&symbols.root, source); + match root { + AstRoot::Module(program) => { + for declaration in &program.declarations { + emitter.declaration(declaration, symbols)?; + } + emitter.constant(Value::Nil)?; + emitter.simple(Opcode::ReturnValue); + } + AstRoot::Expression(expression) => { + emitter.expression(expression)?; + emitter.simple(Opcode::ReturnValue); + } + AstRoot::Interactive(input) => { + for statement in &input.statements { + if let StmtKind::Expression(expression) = &statement.kind { + emitter.expression(expression)?; + } else { + emitter.statement(statement, symbols)?; + emitter.constant(Value::Nil)?; + } + } + emitter.simple(Opcode::ReturnValue); + } + } + emitter.finish() +} + +struct Emitter<'a> { + scope: &'a Scope, + source: &'a SourceUnit, + instructions: Vec, + constants: Vec, + children: Vec<(usize, CompiledCode)>, +} + +impl<'a> Emitter<'a> { + fn new(scope: &'a Scope, source: &'a SourceUnit) -> Self { + Self { + scope, + source, + instructions: Vec::new(), + constants: Vec::new(), + children: Vec::new(), + } + } + + fn emit(&mut self, opcode: Opcode, argument: usize) -> Result<(), Diagnostic> { + let argument = u32::try_from(argument) + .map_err(|_| error(self.scope.span, "bytecode operand exceeds u32"))?; + self.instructions.push(Instruction::new(opcode, argument)); + Ok(()) + } + + fn simple(&mut self, opcode: Opcode) { + self.instructions.push(Instruction::simple(opcode)); + } + + fn constant(&mut self, value: Value) -> Result<(), Diagnostic> { + let index = self.constants.len(); + self.constants.push(value); + self.emit(Opcode::LoadConst, index) + } + + fn name_index(&self, name: &str) -> usize { + self.scope + .layout + .names + .iter() + .position(|candidate| candidate == name) + .expect("analysis interns every emitted name") + } + + fn declaration(&mut self, declaration: &Decl, symbols: &SymbolTable) -> Result<(), Diagnostic> { + let DeclKind::Function(function) = &declaration.kind; + let scope = symbols + .functions + .iter() + .find(|scope| scope.span == function.span && scope.name == function.name.text) + .expect("analysis creates each function scope"); + let mut body = Emitter::new(scope, self.source); + for statement in &function.body.statements { + body.statement(statement, symbols)?; + } + if !matches!( + function + .body + .statements + .last() + .map(|statement| &statement.kind), + Some(StmtKind::Return(_)) + ) { + body.constant(Value::Nil)?; + body.simple(Opcode::ReturnValue); + } + let child = body.finish()?; + let index = self.constants.len(); + self.children.push((index, child)); + self.constant(Value::Nil)?; // Replaced by a managed code object during load(). + self.instructions.push(Instruction::make_function(0, 0)); + self.emit(Opcode::StoreGlobal, self.name_index(&function.name.text)) + } + + fn statement(&mut self, statement: &Stmt, symbols: &SymbolTable) -> Result<(), Diagnostic> { + match &statement.kind { + StmtKind::Binding(binding) => { + self.expression(&binding.value)?; + if self.scope.kind == ScopeKind::Function { + let index = self + .scope + .layout + .locals + .iter() + .position(|name| name == &binding.name.text) + .expect("analysis allocates each local"); + self.emit(Opcode::StoreFast, index)?; + } else { + self.emit(Opcode::StoreGlobal, self.name_index(&binding.name.text))?; + } + } + StmtKind::Return(statement) => { + if let Some(value) = &statement.value { + self.expression(value)?; + } else { + self.constant(Value::Nil)?; + } + self.simple(Opcode::ReturnValue); + } + StmtKind::Expression(expression) => { + self.expression(expression)?; + self.simple(Opcode::PopTop); + } + StmtKind::Declaration(declaration) => self.declaration(declaration, symbols)?, + } + Ok(()) + } + + fn expression(&mut self, expression: &Expr) -> Result<(), Diagnostic> { + match &expression.kind { + ExprKind::String(value) => self.constant(Value::Text(value.clone()))?, + ExprKind::Number(spelling) => self.number(spelling, expression.span)?, + ExprKind::Name(name) => { + let reference = self + .scope + .references + .iter() + .find(|reference| reference.span == expression.span && &reference.name == name) + .expect("analysis resolves every name expression"); + let (opcode, index) = match reference.binding { + NameBinding::Local(index) => (Opcode::LoadFast, index), + NameBinding::Global(index) => (Opcode::LoadGlobal, index), + }; + self.emit(opcode, index)?; + } + ExprKind::Call { callee, arguments } => { + self.expression(callee)?; + self.simple(Opcode::PushNull); + for argument in arguments { + self.expression(argument)?; + } + self.emit(Opcode::Call, arguments.len())?; + } + ExprKind::Member { object, name } => { + self.expression(object)?; + self.emit(Opcode::LoadAttr, self.name_index(&name.text))?; + } + ExprKind::Unary { .. } => { + // Literal sign folding preserves arbitrarily large integers + // and decimal spelling; this is not general numeric arithmetic. + let Some(spelling) = signed_literal(expression, false) else { + return Err(error( + expression.span, + "numeric unary operations are not implemented; only signed number literals are supported", + )); + }; + self.number(&spelling, expression.span)?; + } + ExprKind::Binary { .. } => { + return Err(error( + expression.span, + "numeric arithmetic bytecode is not implemented yet", + )); + } + ExprKind::Symbol(_) => { + return Err(error( + expression.span, + "symbol runtime values are not implemented yet", + )); + } + } + Ok(()) + } + + fn number(&mut self, spelling: &str, span: Span) -> Result<(), Diagnostic> { + let value = if spelling.contains('.') { + EndorNumber::decimal(spelling) + } else { + EndorNumber::integer(spelling) + } + .map_err(|failure| error(span, failure.to_string()))?; + self.constant(Value::Number(value)) + } + + fn finish(self) -> Result { + let line = self + .scope + .span + .location(self.source.text()) + .map_or(1, |location| location.line); + let code = EndorCode::from_instructions( + &self.instructions, + self.constants, + self.scope.layout.clone(), + ) + .map_err(|failure| error(self.scope.span, failure.to_string()))? + .with_location(&self.scope.name, self.source.origin().to_string(), line); + Ok(CompiledCode { + code, + children: self.children, + }) + } +} + +fn signed_literal(expression: &Expr, negative: bool) -> Option { + match &expression.kind { + ExprKind::Number(spelling) => Some(if negative { + format!("-{spelling}") + } else { + spelling.clone() + }), + ExprKind::Unary { + operation: UnaryOp::Negate, + operand, + } => signed_literal(operand, !negative), + _ => None, + } +} diff --git a/crates/compiler/src/lib.rs b/crates/compiler/src/lib.rs new file mode 100644 index 0000000..bdaec01 --- /dev/null +++ b/crates/compiler/src/lib.rs @@ -0,0 +1,56 @@ +//! Minimal source-to-wordcode compiler. Analysis and emission do not allocate +//! VM objects; a compiled unit can be loaded into independent VMs explicitly. + +mod analysis; +mod codegen; + +pub use analysis::{NameBinding, NameUse, Scope, ScopeKind, SymbolTable, analyze}; +pub use codegen::CompiledCode; + +use frontend::ast::{AstDocument, AstRoot}; +use frontend::{Diagnostic, DiagnosticKind, ParseMode, SourceDiagnostic, SourceUnit, Span}; + +/// Names supplied by the host's globals or builtins dictionary. These are +/// compile-time declarations only; loading code never injects their values. +#[derive(Debug, Clone, Default)] +pub struct CompileOptions { + pub globals: Vec, +} + +pub fn compile_source( + source: &SourceUnit, + mode: ParseMode, + options: &CompileOptions, +) -> Result { + let root = frontend::parse_source_ast(source, mode)?; + compile_ast(&root, source, options).map_err(|error| source.annotate(error)) +} + +/// Compile an AST against its original source snapshot. Span validation does +/// not prove the AST's semantic equivalence to the supplied text. +pub fn compile_ast( + root: &AstRoot, + source: &SourceUnit, + options: &CompileOptions, +) -> Result { + AstDocument::new(root.clone()) + .and_then(|document| document.validate_source(source.text())) + .map_err(|error| { + diagnostic( + DiagnosticKind::Compilation, + Span::default(), + error.to_string(), + ) + })?; + let symbols = analyze(root, options)?; + codegen::generate(root, source, &symbols) +} + +fn diagnostic(kind: DiagnosticKind, span: Span, message: impl Into) -> Diagnostic { + Diagnostic { + kind, + span, + message: message.into(), + expected: Vec::new(), + } +} diff --git a/crates/compiler/tests/README.md b/crates/compiler/tests/README.md new file mode 100644 index 0000000..3e6a8eb --- /dev/null +++ b/crates/compiler/tests/README.md @@ -0,0 +1,5 @@ +# 编译链路检查 + +[pipeline.rs](pipeline.rs) 将真实 Endor 示例交给 reader/parser/名称分析/代码生成/VM,检查函数值、前向引用、局部遮蔽、精确数字常量、语义诊断、宿主全局与属性回调、对象结果保活、跨 VM 模板装载、反复调用、错误帧清理、递归限额、共享交互命名空间及超过 255 的操作数。 + +同时检查重复名称、局部提前读取、未声明名称、不支持的执行特性和不合法 AST 原文范围。CLI 的文件/stdin 与退出码检查见 [cli/tests](../../cli/tests/README.md)。源码说明验证意图;仅由 ces-control 的 cargo test --workspace --locked 执行,本地不运行,结果边界见 [验证](../../../docs/operations/validation.md)。返回 [compiler](../README.md)。 diff --git a/crates/compiler/tests/pipeline.rs b/crates/compiler/tests/pipeline.rs new file mode 100644 index 0000000..f333f97 --- /dev/null +++ b/crates/compiler/tests/pipeline.rs @@ -0,0 +1,370 @@ +use compiler::{CompileOptions, CompiledCode, NameBinding, analyze, compile_ast, compile_source}; +use endor_core::{ + EndorCodeObject, EndorFunctionObject, EndorNumber, ExecutionLimits, NativeFunction, ObjectRef, + Value, Vm, VmError, +}; +use frontend::{DiagnosticKind, EndorReader, ParseMode, SourceUnit, parse_ast}; + +fn compile(text: &str, mode: ParseMode) -> CompiledCode { + compile_source( + &EndorReader::from_text("", text), + mode, + &CompileOptions::default(), + ) + .unwrap() +} + +fn module(vm: &mut Vm, compiled: &CompiledCode) -> (ObjectRef, ObjectRef) { + let code = compiled.load(vm).unwrap(); + let globals = vm.new_dict().unwrap(); + let value = vm.execute(code, globals).unwrap(); + assert_eq!(value, Value::Nil); + vm.release_value(value).unwrap(); + (code, globals) +} + +fn function(vm: &Vm, globals: ObjectRef, name: &str) -> ObjectRef { + let Some(Value::Object(function)) = vm.dict_get(globals, &Value::Text(name.into())).unwrap() + else { + panic!("missing function {name}") + }; + function +} + +fn cleanup(vm: &mut Vm, code: ObjectRef, globals: ObjectRef) { + vm.release(code).unwrap(); + vm.dict_clear(globals).unwrap(); + vm.release(globals).unwrap(); + vm.collect_cycles(); + assert_eq!(vm.object_count(), 0); +} + +#[test] +fn source_files_execute_through_module_and_function_code_objects() { + for (text, expected) in [ + ( + include_str!("../examples/hello.endor"), + Value::Text("你好,Endor 🌱".into()), + ), + ( + include_str!("../examples/calls.endor"), + Value::Text("函数调用已接通".into()), + ), + ( + include_str!("../examples/shadowing.endor"), + Value::Text("局部名称优先".into()), + ), + ( + include_str!("../examples/number.endor"), + Value::Number(EndorNumber::decimal("-123456789012345678901234567890.00100").unwrap()), + ), + ] { + let compiled = compile(text, ParseMode::Module); + let mut vm = Vm::new().unwrap(); + let (code, globals) = module(&mut vm, &compiled); + let main = function(&vm, globals, "main"); + let data = vm.payload::(main, "function").unwrap(); + assert_eq!( + vm.payload::(data.code(), "code") + .unwrap() + .code() + .name(), + "main" + ); + let result = vm.call(main, &[]).unwrap(); + assert_eq!(result, expected); + vm.release_value(result).unwrap(); + cleanup(&mut vm, code, globals); + } +} + +#[test] +fn expression_mode_preserves_exact_literals_and_signs() { + for (text, expected) in [ + ( + include_str!("../examples/literal.endor"), + Value::Text("表达式 → 字节码 → VM".into()), + ), + ( + "-99999999999999999999999999999999999999", + Value::Number(EndorNumber::integer("-99999999999999999999999999999999999999").unwrap()), + ), + ("--00042", Value::Number(42_i64.into())), + ( + "-0.000", + Value::Number(EndorNumber::decimal("-0.000").unwrap()), + ), + ] { + let mut vm = Vm::new().unwrap(); + let code = compile(text, ParseMode::Expression).load(&mut vm).unwrap(); + let globals = vm.new_dict().unwrap(); + let value = vm.execute(code, globals).unwrap(); + assert_eq!(value, expected); + vm.release_value(value).unwrap(); + cleanup(&mut vm, code, globals); + } +} + +#[test] +fn scope_analysis_resolves_local_shadowing_and_forward_globals() { + let source = "function main(target: Callable): String { let copy = target return later(copy) } function later(value: String): String { return value }"; + let root = parse_ast(source, ParseMode::Module).unwrap(); + let symbols = analyze(&root, &CompileOptions::default()).unwrap(); + assert_eq!(symbols.functions[0].layout.parameters, ["target"]); + assert_eq!(symbols.functions[0].layout.locals, ["target", "copy"]); + assert_eq!( + symbols.functions[0] + .references + .iter() + .map(|reference| reference.binding) + .collect::>(), + [ + NameBinding::Local(0), + NameBinding::Global(0), + NameBinding::Local(1) + ] + ); + assert!( + symbols + .functions + .iter() + .all(|scope| scope.layout.free_variables.is_empty()) + ); +} + +#[test] +fn semantic_errors_prevent_loading_and_keep_unicode_locations() { + for (text, fragment) in [ + ( + "function f(): String { return missing }", + "undefined name: missing", + ), + ("function f(x: T, x: T): T { return x }", "declaration: x"), + ( + "function f(x: T): T { let x = 1 return x }", + "declaration: x", + ), + ( + "function f(): T { let x = 1 let x = 2 return x }", + "declaration: x", + ), + ( + "function f(): T { return 1 } function f(): T { return 2 }", + "declaration: f", + ), + ( + "function f(): T { let x = x return x }", + "before initialization: x", + ), + ( + "function f(): T { let x = later let later = 1 return x }", + "before initialization: later", + ), + ( + "function later(): T { return 1 } function f(): T { let x = later let later = 1 return x }", + "before initialization: later", + ), + ] { + let unit = EndorReader::from_text("", text); + let error = + compile_source(&unit, ParseMode::Module, &CompileOptions::default()).unwrap_err(); + assert_eq!(error.diagnostic.kind, DiagnosticKind::Semantic); + assert!(error.to_string().contains(fragment), "{error}"); + } + let unit = EndorReader::from_text("<日本語>", "function 表示(): T {\r\n return 未定義\r\n}"); + let error = compile_source(&unit, ParseMode::Module, &CompileOptions::default()).unwrap_err(); + assert_eq!((error.location.line, error.location.column), (2, 10)); + assert!(error.to_string().starts_with("<日本語>:2:10:")); +} + +fn identity(arguments: &[Value]) -> Result { + if arguments.len() != 1 { + return Err(VmError::InvalidArgument("identity needs one argument")); + } + Ok(arguments[0].clone()) +} + +#[test] +fn host_globals_member_calls_and_object_results_keep_owned_roots() { + let options = CompileOptions { + globals: vec!["service".into(), "value".into()], + }; + let unit = EndorReader::from_text("", "service.echo(value)"); + let compiled = compile_source(&unit, ParseMode::Expression, &options).unwrap(); + let mut vm = Vm::new().unwrap(); + let native = vm.allocate(NativeFunction::new("echo", identity)).unwrap(); + let service = vm.new_object().unwrap(); + vm.set_attr(service, "echo", Value::Object(native)).unwrap(); + vm.release(native).unwrap(); + let object = vm.new_object().unwrap(); + let globals = vm.new_dict().unwrap(); + vm.dict_set( + globals, + Value::Text("service".into()), + Value::Object(service), + ) + .unwrap(); + vm.dict_set(globals, Value::Text("value".into()), Value::Object(object)) + .unwrap(); + vm.release(service).unwrap(); + vm.release(object).unwrap(); + let code = compiled.load(&mut vm).unwrap(); + let result = vm.execute(code, globals).unwrap(); + assert_eq!(result, Value::Object(object)); + vm.dict_clear(globals).unwrap(); + assert!(vm.object(object).is_ok()); + vm.release_value(result).unwrap(); + assert_eq!(vm.object(object).unwrap_err(), VmError::InvalidHandle); + cleanup(&mut vm, code, globals); +} + +#[test] +fn code_templates_load_independently_and_functions_share_loaded_code() { + let compiled = compile(include_str!("../examples/calls.endor"), ParseMode::Module); + let mut left = Vm::new().unwrap(); + let mut right = Vm::new().unwrap(); + let (left_code, left_globals) = module(&mut left, &compiled); + let (right_code, right_globals) = module(&mut right, &compiled); + assert_eq!(left.object(right_code).unwrap_err(), VmError::ForeignHandle); + let before = left.object_count(); + let main = function(&left, left_globals, "main"); + for _ in 0..3 { + let result = left.call(main, &[]).unwrap(); + left.release_value(result).unwrap(); + } + assert_eq!(left.object_count(), before); + cleanup(&mut left, left_code, left_globals); + cleanup(&mut right, right_code, right_globals); +} + +#[test] +fn argument_and_recursion_failures_unwind_vm_frames_and_keep_traceback() { + let source = + "\nfunction main(): T { return echo() }\nfunction echo(value: T): T { return value }"; + let compiled = compile(source, ParseMode::Module); + let mut vm = Vm::new().unwrap(); + let (code, globals) = module(&mut vm, &compiled); + let baseline = vm.object_count(); + let main = function(&vm, globals, "main"); + assert!(matches!( + vm.call(main, &[]), + Err(VmError::ArgumentCount { provided: 0, .. }) + )); + assert_eq!(vm.object_count(), baseline); + assert_eq!(vm.last_traceback().last().unwrap().first_line, 2); + cleanup(&mut vm, code, globals); + + let mut vm = Vm::new().unwrap(); + let (code, globals) = module( + &mut vm, + &compile("function main(): T { return main() }", ParseMode::Module), + ); + let main = function(&vm, globals, "main"); + assert_eq!( + vm.call_with_limits( + main, + &[], + ExecutionLimits { + max_frames: 4, + max_instructions: 100 + } + ), + Err(VmError::ExecutionLimit("frames")) + ); + assert_eq!(vm.last_traceback().len(), 4); + cleanup(&mut vm, code, globals); +} + +#[test] +fn declared_host_names_are_checked_again_at_runtime() { + let source = EndorReader::from_text("", "missing"); + let compiled = compile_source( + &source, + ParseMode::Expression, + &CompileOptions { + globals: vec!["missing".into()], + }, + ) + .unwrap(); + let mut vm = Vm::new().unwrap(); + let code = compiled.load(&mut vm).unwrap(); + let globals = vm.new_dict().unwrap(); + assert_eq!( + vm.execute(code, globals), + Err(VmError::NameNotFound("missing".into())) + ); + cleanup(&mut vm, code, globals); +} + +#[test] +fn interactive_submissions_can_share_a_host_owned_namespace() { + let mut vm = Vm::new().unwrap(); + let globals = vm.new_dict().unwrap(); + for (text, options, expected) in [ + ( + "let value = \"保留\"", + CompileOptions::default(), + Value::Nil, + ), + ( + "value", + CompileOptions { + globals: vec!["value".into()], + }, + Value::Text("保留".into()), + ), + ] { + let source = EndorReader::from_text("", text); + let compiled = compile_source(&source, ParseMode::Interactive, &options).unwrap(); + let code = compiled.load(&mut vm).unwrap(); + let result = vm.execute(code, globals).unwrap(); + assert_eq!(result, expected); + vm.release_value(result).unwrap(); + vm.release(code).unwrap(); + } + vm.dict_clear(globals).unwrap(); + vm.release(globals).unwrap(); + assert_eq!(vm.object_count(), 0); +} + +#[test] +fn extended_operands_work_for_large_local_and_constant_tables() { + let mut source = String::from("function main(): Number { "); + for index in 0..270 { + source.push_str(&format!("let value{index} = {index} ")); + } + source.push_str("return value269 }"); + let mut vm = Vm::new().unwrap(); + let (code, globals) = module(&mut vm, &compile(&source, ParseMode::Module)); + let main = function(&vm, globals, "main"); + let result = vm.call(main, &[]).unwrap(); + assert_eq!(result, Value::Number(269_i64.into())); + vm.release_value(result).unwrap(); + cleanup(&mut vm, code, globals); +} + +#[test] +fn unsupported_execution_features_are_compile_errors_instead_of_fake_results() { + for (text, mode) in [ + ("1 + 2", ParseMode::Expression), + (".title", ParseMode::Expression), + ( + "function f(value: Number): Number { return -value }", + ParseMode::Module, + ), + ] { + let source = EndorReader::from_text("", text); + let error = compile_source(&source, mode, &CompileOptions::default()).unwrap_err(); + assert_eq!(error.diagnostic.kind, DiagnosticKind::Compilation); + } +} + +#[test] +fn compiling_ast_rejects_invalid_source_ranges() { + let source: SourceUnit = EndorReader::from_text("", "\"日\""); + let mut root = parse_ast(source.text(), ParseMode::Expression).unwrap(); + if let frontend::ast::AstRoot::Expression(expression) = &mut root { + expression.span.end = 2; // Splits the three-byte character. + } + assert!(compile_ast(&root, &source, &CompileOptions::default()).is_err()); +} diff --git a/crates/core/README.md b/crates/core/README.md index e075266..fea1a05 100644 --- a/crates/core/README.md +++ b/crates/core/README.md @@ -10,6 +10,6 @@ Endor VM 包,仅负责 Vm、执行帧、调用协议和 wordcode 求值循环 Vm::new 创建独立 ObjectStore 和空执行状态;代码对象、全局字典和函数仍按宿主需要显式创建,再 execute/call。EndorObject、Number、ObjectRef、内置载荷、代码对象/指令定义与 VmError 在 objects 中实现;endor_core 保留这些类型的兼容重导出,既有 Vm 对象方法委托到 ObjectStore。 -没有 Python type/元类或语言根类。网络、数据库和进程环境属于宿主,源码阅读器与 lexer/parser 属于 [frontend](../frontend/README.md),AST 到字节码生成尚未实现,见 [执行与版本模型](../../docs/architecture/execution-and-versions.md)。 +没有 Python type/元类或语言根类。网络、数据库和进程环境属于宿主,源码阅读器与 lexer/parser 属于 [frontend](../frontend/README.md),AST 名称分析与字节码生成属于独立 [compiler](../compiler/README.md),见 [执行与版本模型](../../docs/architecture/execution-and-versions.md)。 返回值所有权见 [生命周期](../../docs/runtime/lifecycle.md),执行契约见 [字节码 VM](../../docs/runtime/bytecode.md)。本地不构建或测试,见 [验证](../../docs/operations/validation.md)。返回 [工作区](../README.md)。 diff --git a/crates/frontend/README.md b/crates/frontend/README.md index c575fee..30f8709 100644 --- a/crates/frontend/README.md +++ b/crates/frontend/README.md @@ -1,6 +1,6 @@ # frontend -独立源码前端:EndorReader → SourceUnit → 字符 PEG lexer → token → token PEG parser → 拥有文本的 AST。AST 节点和 Rust/编辑器接口由 Endor.asdl 统一生成;支持模块、表达式及单次交互语法输入。当前解析基础函数与表达式,不生成字节码、不进行类型/名称解析、不执行源码。语法是可继续调整的首版实现,当前契约见 [源码前端](../../docs/runtime/frontend.md)。 +独立源码前端:EndorReader → SourceUnit → 字符 PEG lexer → token → token PEG parser → 拥有文本的 AST。AST 节点和 Rust/编辑器接口由 Endor.asdl 统一生成;支持模块、表达式及单次交互语法输入。当前 frontend 解析基础函数与表达式;名称分析、字节码生成和装载由独立 [compiler](../compiler/README.md) 负责,frontend 本身不执行源码或进行类型检查。语法是可继续调整的首版实现,当前契约见 [源码前端](../../docs/runtime/frontend.md)。 | 内容 | 职责 | | --- | --- | diff --git a/crates/frontend/src/README.md b/crates/frontend/src/README.md index 93560f9..11d0944 100644 --- a/crates/frontend/src/README.md +++ b/crates/frontend/src/README.md @@ -6,7 +6,7 @@ | [source.rs](source.rs) | 不可变 SourceUnit、来源类别和携带来源/行列的 SourceDiagnostic | | [reader.rs](reader.rs) | 字符串、流、文件读取、严格 UTF-8 解码与宿主 EndorSourceProvider 适配 | | [token.rs](token.rs) | 可复制的 token 种类和原始源码范围,文本由源码切片取得 | -| [diagnostic.rs](diagnostic.rs) | UTF-8 字节范围、词法/语法诊断与一基行列转换 | +| [diagnostic.rs](diagnostic.rs) | UTF-8 字节范围、词法/语法及 compiler 使用的语义/编译诊断种类与一基行列转换 | | [lexer.rs](lexer.rs) | lex/lex_source 接口、源码游标、逐 Token 调用、位置/诊断换算与字符串解码 | | [lexer.peg](lexer.peg) | 字符输入的 PEG 词法规则:名称、数字、字符串/转义、标点、空白/注释和错误识别 | | [ast.rs](ast.rs) | 生成类型的公开门面、Spanned 位置接口与便捷方法 | diff --git a/crates/frontend/src/diagnostic.rs b/crates/frontend/src/diagnostic.rs index da2b13f..2ae2495 100644 --- a/crates/frontend/src/diagnostic.rs +++ b/crates/frontend/src/diagnostic.rs @@ -34,6 +34,8 @@ impl Span { pub enum DiagnosticKind { Lexical, Syntax, + Semantic, + Compilation, } /// Lines and Unicode scalar columns are one-based. CRLF counts as one newline. diff --git a/docs/README.md b/docs/README.md index 1418b91..7582762 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,12 +7,13 @@ | 想知道什么 | 权威说明 | 代码入口 | | --- | --- | --- | | 各个 crate 如何分层,哪些能力已实现 | [架构](architecture/README.md) | [crates](../crates/README.md) | -| 解释型为何生成字节码,资源修订与平台升级如何区分 | [执行与版本模型(设计方向)](architecture/execution-and-versions.md) | [frontend](../crates/frontend/README.md)、[core](../crates/core/README.md);源码执行与版本管理尚未接入 | +| 解释型为何生成字节码,资源修订与平台升级如何区分 | [执行与版本模型(设计方向)](architecture/execution-and-versions.md) | [frontend](../crates/frontend/README.md)、[core](../crates/core/README.md);最小源码执行已接入,平台资源版本管理未实现 | | EndorObject、对象头、句柄、默认操作 | [对象模型](runtime/objects.md) | [objects/src](../crates/objects/src/README.md) | | Number、String、Dict、Cell、Function、异常数据 | [基本对象](runtime/builtins.md) | [objects/src](../crates/objects/src/README.md) | | 字符串、文件、流与 endor URI 如何进入 lexer | [Endor 阅读器](runtime/reader.md) | [reader.rs](../crates/frontend/src/reader.rs)、[source.rs](../crates/frontend/src/source.rs) | | 源码如何分词、PEG 规则、AST 和诊断 | [源码前端](runtime/frontend.md) | [lexer.peg](../crates/frontend/src/lexer.peg)、[grammar.peg](../crates/frontend/src/grammar.peg)、[frontend](../crates/frontend/README.md)、[CLI](../crates/cli/README.md) | | AST 根/声明/语句、ASDL 生成、遍历、位置与 JSON/编辑器接口 | [AST 模型](runtime/ast.md) | [Endor.asdl](../crates/frontend/Endor.asdl)、[AST 实现](../crates/frontend/src/ast/README.md)、[editor](../crates/frontend/editor/README.md) | +| 名称/作用域分析、源码到字节码和 VM、可执行示例 | [编译链路](runtime/compiler.md) | [compiler](../crates/compiler/README.md)、[CLI](../crates/cli/README.md) | | 操作码、代码验证、局部槽、函数定义与调用协议 | [字节码 VM](runtime/bytecode.md) | instruction.rs/code.rs 在 [objects/src](../crates/objects/src/README.md),frame.rs/eval.rs 在 [core/src](../crates/core/src/README.md) | | 基础信息保存在哪里、旧库如何迁移 | [宿主文件信息](operations/host-information.md) | [host](../crates/host/README.md) | | UTF-8 在日本地区是否适用,其他编码怎样进入 VM | [文本编码](runtime/text-encoding.md) | [core](../crates/core/README.md)、[host](../crates/host/README.md) | diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 9ea43c6..e7e767a 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -1,6 +1,6 @@ # 系统架构 -Endor 的目标是云原生语言;本仓库当前实现运行时、基础语法前端和宿主演示,尚未实现 AST 到字节码生成,也没有云调度器或多租户执行沙箱。当前侧重核心语义独立于进程环境,以及数据库能力按对象需求显式提供。 +Endor 的目标是云原生语言;本仓库当前实现运行时、基础语法前端、最小源码编译链路和宿主演示,尚无云调度器或多租户执行沙箱。当前侧重核心语义独立于进程环境,以及数据库能力按对象需求显式提供。 ## 本目录 @@ -12,24 +12,26 @@ Endor 的目标是云原生语言;本仓库当前实现运行时、基础语 ## 组件与依赖方向 ```text -CLI ────→ Core + Frontend + Host +CLI ────→ Compiler + Core + Frontend + Host +Compiler → Frontend + Core Core ───→ Objects Objects → 标准库 -Frontend → peg +Frontend → peg + serde + serde_json Server ─→ Core + Host 未来数据库特殊类的宿主实现 ─→ Storage ``` -CLI、Server 依赖 Host 保存文件信息,不依赖 Storage;Core 只依赖本地 Objects,Objects 无依赖且不反向依赖 Core;二者均不依赖 Storage。CLI 的 parse 命令通过 Frontend 的 EndorReader 读取文件/stdin,再解析 AST;Frontend 使用 peg 及工作区已有 serde/serde_json,读取适配使用标准库;Endor.asdl 在开发时生成 AST、遍历器与编辑器接口,普通构建不运行生成器。平台 URI 的存储与授权由宿主 provider 提供,Core 不依赖 Frontend。HTTP、Tokio、SQLx、数据库、网络和浏览器能力不进入 core 或 objects;两包均无第三方依赖。包与文件定位见 [crates](../../crates/README.md)。 +CLI、Server 依赖 Host 保存文件信息,不依赖 Storage;Core 只依赖本地 Objects,Objects 无依赖且不反向依赖 Core;二者均不依赖 Storage。Compiler 负责名称/作用域分析、wordcode 生成与代码对象装载,CLI run/eval 连接源码执行;CLI 的 parse 命令通过 Frontend 的 EndorReader 读取文件/stdin,再解析 AST;Frontend 使用 peg 及工作区已有 serde/serde_json,读取适配使用标准库;Endor.asdl 在开发时生成 AST、遍历器与编辑器接口,普通构建不运行生成器。平台 URI 的存储与授权由宿主 provider 提供,Core 不依赖 Frontend。HTTP、Tokio、SQLx、数据库、网络和浏览器能力不进入 core 或 objects;两包均无第三方依赖。包与文件定位见 [crates](../../crates/README.md)。 | 组件 | 当前职责 | 全局契约 | | --- | --- | --- | | Core | Vm、执行帧、调用协议与取指求值循环;对象方法委托到 Objects | [字节码 VM](../runtime/bytecode.md) | | Objects | 核心对象、私有堆、ObjectStore、Number、代码对象、指令数据和静态验证;无求值器 | [对象模型](../runtime/objects.md) | | Frontend | Endor 阅读器、UTF-8 字符 PEG lexer、token PEG parser、ASDL 生成的根/声明/语句/表达式 AST 与跨工具接口,不执行源码 | [源码前端](../runtime/frontend.md) | +| Compiler | AST 名称/作用域分析、wordcode 模板、代码对象装载;不引入默认语言类 | [编译链路](../runtime/compiler.md) | | Host | 基础信息的逐键 UTF-8 JSON 文件与旧库导入 | [宿主信息](../operations/host-information.md) | | Storage | 按显式配置建立 SQLite/PostgreSQL 连接池,不定义应用表 | [数据库特殊类](../runtime/database-objects.md) | -| CLI | 对象引导演示、源码解析与文件信息命令 | [CLI](../../crates/cli/README.md) | +| CLI | 对象引导演示、源码解析/执行与文件信息命令 | [CLI](../../crates/cli/README.md) | | Server | HTTP 存活检查、只读 VM 信息及文件版本记录 | [Server](../../crates/server/README.md) | | Gitea/Docker | 双数据库适配验证、镜像构建、宿主与 PostgreSQL 资源部署 | [部署](../operations/deployment.md) | diff --git a/docs/architecture/execution-and-versions.md b/docs/architecture/execution-and-versions.md index 8d6deec..267cb9e 100644 --- a/docs/architecture/execution-and-versions.md +++ b/docs/architecture/execution-and-versions.md @@ -1,6 +1,6 @@ # 解释执行与平台资源版本 -本页记录 Endor 的执行模型与平台资源管理方向。阅读器、基础 AST 和字节码 VM 已分别实现;AST 到字节码生成、平台资源存储、发布和依赖锁尚未实现。下述版本模型是后续设计方向,不是现有 API 或已定稿的 URI 协议。 +本页记录 Endor 的执行模型与平台资源管理方向。阅读器、AST、基础名称/作用域分析、字节码生成和 VM 已连接;平台资源存储、发布和依赖锁尚未实现。下述版本模型是后续设计方向,不是现有 API 或已定稿的 URI 协议。 ## 解释型语言的执行过程 @@ -11,9 +11,9 @@ Endor 由平台解释执行,目标流程为: → 名称/局部槽与闭包分析 → 生成字节码 → VM 解释执行 ``` -生成字节码是解释器内部的执行准备,可以称为内部编译。使用者提交或调用源码资源,由平台完成这些步骤,无需先构建独立程序。现有 VM 解释 wordcode;后续接入源码时沿用该执行器。 +生成字节码是解释器内部的执行准备,可以称为内部编译。使用者提交或调用源码资源,由平台完成这些步骤,无需先构建独立程序。现有 VM 解释 wordcode,源码编译沿用该执行器。 -当前 frontend 到 AST 为止,core 可执行由 Rust API 构造的代码对象,两者还未连接。`cli parse` 仅输出 AST,尚无源码执行命令或 REPL。仓库中的 Cargo/Docker 构建用于构建 Rust 实现和宿主,不是 Endor 使用者的程序构建步骤。 +Frontend 产出 AST,由独立 Compiler 分析名称并生成代码模板,装载后交给 Core 执行。`cli run` 执行基础函数模块,`cli eval` 执行表达式,`cli parse` 继续只输出 AST;支持范围见 [编译链路](../runtime/compiler.md)。源码闭包与数值算术尚未接入,没有 CLI REPL。仓库中的 Cargo/Docker 构建用于构建 Rust 实现和宿主,不是 Endor 使用者的程序构建步骤。 ## 名称、资源与修订 @@ -50,4 +50,4 @@ Endor 由平台解释执行,目标流程为: 只有引入跨进程或跨升级的持久化字节码缓存后,才需要明确失效策略。缓存有效性至少取决于相关源码/依赖输入与解释器、字节码格式兼容性;可在平台升级时统一失效,也可使用内部兼容标识。无需把实现版本强塞进每个资源 URI;具体缓存方案尚未选择。 -近期先完成源码到现有 VM 的连接,再按平台接口需求设计资源存储、修订与依赖锁。Reader 已提供宿主 provider 扩展点,当前只接收不透明 revision 标签,实际契约见 [Endor 阅读器](../runtime/reader.md)。返回 [系统架构](README.md) 或 [文档索引](../README.md)。 +最小源码到 VM 的连接已完成;后续按语言能力和平台接口需求扩展,并设计资源存储、修订与依赖锁。Reader 已提供宿主 provider 扩展点,当前只接收不透明 revision 标签,实际契约见 [Endor 阅读器](../runtime/reader.md)。返回 [系统架构](README.md) 或 [文档索引](../README.md)。 diff --git a/docs/contributing/README.md b/docs/contributing/README.md index e97f65a..0331df4 100644 --- a/docs/contributing/README.md +++ b/docs/contributing/README.md @@ -18,6 +18,7 @@ | lexer 前的输入适配、SourceUnit 与来源诊断 | [reader.md](../runtime/reader.md) | frontend 的 reader/source、CLI 与读取检查 | | Token、lexer.peg 词法/grammar.peg 语法规则、AST 与源码诊断 | [frontend.md](../runtime/frontend.md) | frontend 源码/测试/样例和 CLI | | AST 节点/根模式、ASDL 生成、Visitor、位置与 JSON/编辑器接口 | [ast.md](../runtime/ast.md) | Endor.asdl、生成器、生成产物与 AST/CLI 检查 | +| 名称/作用域、源码编译、代码装载与执行示例 | [compiler.md](../runtime/compiler.md) | compiler 源码/检查/示例、CLI 与运行指南 | | 指令编码、栈效果、调用协议与执行限制 | [bytecode.md](../runtime/bytecode.md) | instruction/code/frame/eval 文件职责 | | 宿主基础信息、JSON 文件和旧库导入 | [host-information.md](../operations/host-information.md) | host、cli、server 与脚本 README | | 地区与文本编码边界 | [text-encoding.md](../runtime/text-encoding.md) | 配置主题与实际 I/O 入口 | @@ -51,7 +52,7 @@ Endor 是由平台解释执行的语言,字节码生成是内部准备步骤 AST 节点以 frontend/Endor.asdl 为唯一来源,Rust 类型/遍历器和 TypeScript/JSON Schema 为提交到 Git 的生成产物,不直接编辑;通过 scripts/generate_ast.py --write 更新,--check 做静态同步检查。生成器行为测试仅在指定远端执行。 -独立 Frontend 已提供阅读器、lexer/parser 和 ASDL 生成的 AST/跨工具接口;后续按实际需求接入名称/闭包分析和字节码编译、扩充迭代器等指令、宿主回调、错误传播和资源边界。语法可继续调整,AST 到字节码和云服务机制尚未实现,不写成既有功能。 +独立 Frontend 提供阅读器、lexer/parser 和 ASDL AST;Compiler 已完成模块/函数名称分析、局部槽布局、最小字节码生成及代码装载。源码闭包、数值算术、迭代器等指令、类型检查与云服务机制仍待实现;语法可继续调整,范围见 [编译契约](../runtime/compiler.md)。 ## 导出与迁移 diff --git a/docs/contributing/handoff.md b/docs/contributing/handoff.md index 7f47929..cd39539 100644 --- a/docs/contributing/handoff.md +++ b/docs/contributing/handoff.md @@ -4,7 +4,7 @@ ## 当前项目与命名 -仓库为 endorphin-aster-vm,归属 ProjectEndorphin。包与目录为 core、objects、frontend、storage、host、cli、server;core 的 Rust 库名为 endor_core,命令为 cli/server。旧部署服务、数据库和路径保留既有标识以保护数据。 +仓库为 endorphin-aster-vm,归属 ProjectEndorphin。包与目录为 core、objects、frontend、compiler、storage、host、cli、server;core 的 Rust 库名为 endor_core,命令为 cli/server。旧部署服务、数据库和路径保留既有标识以保护数据。 ## 当前包边界 @@ -18,9 +18,10 @@ Objects 零依赖,统一拥有核心对象、Number、代码对象/指令数 - EndorReader 在 lexer 前提供字符串/流/文件/宿主 URI provider 输入,产出不可变 SourceUnit,保存来源与不透明修订标签;带来源的词法/语法错误保留行列,CLI parse 支持文件和 stdin。没有平台 URI 后端或版本锁实现,见 [阅读器](../runtime/reader.md)。 - Endor.asdl 是 AST 唯一定义源,生成 Rust 节点/Spanned/Visitor、TypeScript 类型/遍历器和 JSON Schema。AstRoot 支持 Module/Expression/Interactive,Program/Block 使用声明/语句列表;AstDocument 提供带 schema 的 JSON 及范围校验,CLI 支持 --mode/--json,见 [AST 模型](../runtime/ast.md)。生成产物需通过 scripts/generate_ast.py 更新,不能手改。 - Frontend 使用 peg 0.8.6,词法规则在 lexer.peg(字符 → Token),语法规则在 grammar.peg(Token → AST);lexer.rs 保留逐 Token 游标与诊断位置换算,提供完整输入解析、基础函数/表达式 AST、Unicode 字节范围与行列诊断;CLI parse 可读 UTF-8 文件/stdin 输出 AST,不执行源码。当前语法以未完成设计稿为起点,可以继续调整,见 [源码前端](../runtime/frontend.md)。 +- Compiler 已连接 AST → 模块/函数名称与初始化检查 → wordcode 模板 → VM 代码对象;CLI run 调用模块无参数入口,eval 执行表达式。Compiler 不依赖语言类注册;无损常量、函数/参数/let/调用和宿主成员可执行,详见 [编译链路](../runtime/compiler.md) 与 [示例](../../crates/compiler/examples/README.md)。 - Host 文件信息独立于数据库:host.version、deployment.release 与 metadata put/get 使用 UTF-8 JSON。旧 PostgreSQL 基础信息只读导入缺失文件,不删除原库,见 [文件信息](../operations/host-information.md)。 - Storage 只提供显式 DatabaseConfig/DatabaseConnection;没有默认数据库、自动应用建表或基础信息 KV。特殊类的名称/接口尚未定义,没有将连接接入 VM。 -- CLI 引导与文件信息,Server HTTP 存活/只读 VM 信息;生产 PostgreSQL 作为未来特殊类的预置资源保留。 +- CLI 提供引导、解析、源码执行与文件信息,Server HTTP 存活/只读 VM 信息;生产 PostgreSQL 作为未来特殊类的预置资源保留。 ## 资料位置 @@ -28,7 +29,7 @@ Objects 零依赖,统一拥有核心对象、Number、代码对象/指令数 ## 尚未实现 -AST 到字节码生成、平台资源存储/修订与依赖锁、名称/闭包与类型分析、源码闭包声明、导入/类/Agent 语法、迭代器等扩展指令、数值算术/相等/哈希、完整异常传播、可变 VM/异步数据库回调、生成器、自动 GC、堆快照、内存配额、取消及不可信代码沙箱。宿主回调 panic 后的执行根恢复也未完成。不能把未来设计写成已有能力。 +平台资源存储/修订与依赖锁、闭包捕获与类型分析、源码闭包声明、导入/类/Agent 语法、迭代器等扩展指令、数值算术/相等/哈希、完整异常传播、可变 VM/异步数据库回调、生成器、自动 GC、堆快照、内存配额、取消及不可信代码沙箱。宿主回调 panic 后的执行根恢复也未完成。不能把未来设计写成已有能力。 ## 工作约定 @@ -44,4 +45,4 @@ Endor 由平台解释执行;AST 到字节码是解释器内部准备步骤, ## 下一步 -前端已完成 ASDL 单一来源、统一根/声明/语句层次、遍历及 JSON/编辑器接口;语法仍为可用子集,Interactive 仅解析单次提交,不是 REPL。下一步连接解释器内部的字节码生成:AST → 名称/局部槽与闭包分析 → 字节码 → 现有 VM,不改成 AST 直接解释。可以先连接字符串、名称、let、位置参数函数和调用,再补算术指令与数值语义。参数与返回类型目前只保存为语法节点,不引入 Python type/元类或借此实现整个语言类型系统。语法不必等外部设计稿全部定稿,按用户要求先实现可用子集、以后再调整;当前尚无源码执行命令。 +最小源码执行链路已连接;下一步可补数值算术指令与明确的数值语义、逐指令源码映射,再按需求开放块语句和嵌套函数/闭包。现有 Interactive 编译接口允许宿主复用命名空间,但没有 CLI REPL。参数与返回类型仍只保存为语法节点,不引入 Python type/元类或借此实现整个语言类型系统。语法不必等外部设计稿全部定稿,先扩展可用子集。新增测试已交给既有远端 workspace 流程,本地不运行,也不主动查询 CI。 diff --git a/docs/operations/README.md b/docs/operations/README.md index 53b925c..d74530d 100644 --- a/docs/operations/README.md +++ b/docs/operations/README.md @@ -1,10 +1,10 @@ # 运行宿主 -CLI 提供 VM 引导、源码解析及文件基础信息,Server 提供状态与文件基础信息,二者不连接数据库。Storage 为未来数据库特殊类保留显式连接能力,见 [数据库对象](../runtime/database-objects.md)。以下构建和运行命令仅供用户指定的远端环境,本地不执行。 +CLI 提供 VM 引导、源码解析/执行及文件基础信息,Server 提供状态与文件基础信息,二者不连接数据库。Storage 为未来数据库特殊类保留显式连接能力,见 [数据库对象](../runtime/database-objects.md)。以下构建和运行命令仅供用户指定的远端环境,本地不执行。 ## CLI -下列 Cargo 构建针对 Rust 实现与宿主;Endor 的设计是由平台自动准备字节码并解释执行,无需使用者自行构建程序。当前尚无源码执行命令,见 [执行与版本模型](../architecture/execution-and-versions.md)。 +下列 Cargo 构建针对 Rust 实现与宿主;Endor 的设计是由平台自动准备字节码并解释执行,无需使用者自行构建程序。当前已有最小 run/eval 源码执行入口,见 [执行与版本模型](../architecture/execution-and-versions.md)。 工作区要求 Rust 1.91 或兼容更高版本,镜像固定 rust:1.91.1-bookworm,依赖由 Cargo.lock 锁定。 @@ -15,12 +15,17 @@ cargo run -p cli -- help cargo run -p cli -- parse crates/frontend/examples/basic.endor cargo run -p cli -- parse - < crates/frontend/examples/basic.endor cargo run -p cli -- parse --json crates/frontend/examples/basic.endor +cargo run -p cli -- run crates/compiler/examples/hello.endor +cargo run -p cli -- run --entry main crates/compiler/examples/calls.endor +cargo run -p cli -- eval - < crates/compiler/examples/literal.endor ``` bootstrap 创建两个 Record,输出 runtime=EndorObject、type_system=disabled、objects_distinct=true 和 heap_objects=2,显式释放后输出 released heap_objects=0。默认无参数执行同一示例。 parse 通过 EndorReader 严格读取 UTF-8 源码文件;`parse -` 从 stdin 读到 EOF,输入来源为 ``,没有 REPL 提示符或会话求值。默认解析 module 模式,成功时输出 Rust Debug 格式的 AstRoot;--mode 支持 expression/interactive,--json 输出带 schema 的 JSON,见 [AST 模型](../runtime/ast.md);失败时输出来源、行、列并返回非零退出码;阅读错误在进入 lexer 前返回。输入适配与 URI provider 见 [阅读器](../runtime/reader.md)。该命令只解析语法,不执行源码;语法与当前边界见 [源码前端](../runtime/frontend.md)。 +run 编译模块、初始化函数并调用无参数入口(默认 main),--entry 可指定名称;eval 执行一个完整表达式。二者支持文件或 - 读取 stdin 到 EOF,输出返回值,失败不输出成功结果。支持范围、命名空间释放及 traceback 精度见 [编译链路](../runtime/compiler.md),真实 Endor 输入见 [可执行示例](../../crates/compiler/examples/README.md);frontend/basic.endor 含算术,仍仅作为解析样例。 + CLI 不读取数据库环境,不创建基础信息数据库表。metadata put/get 通过 host 包写 UTF-8 JSON 文件,metadata import 仅导入缺失记录;示例、配置和协议见 [宿主信息](host-information.md)。输入与退出码见 [cli/src](../../crates/cli/src/README.md)。 ## HTTP diff --git a/docs/operations/deployment.md b/docs/operations/deployment.md index 9eec568..5635e54 100644 --- a/docs/operations/deployment.md +++ b/docs/operations/deployment.md @@ -38,7 +38,7 @@ docker compose -f docker-compose.postgres.yml exec aster cli bootstrap ## 生产目录与数据 -Cargo 包名为 core/objects/frontend/storage/host/cli/server,core 的 Rust 库名为 endor_core,可执行文件为 cli/server。Compose 应用服务名 aster、数据库名、卷和生产路径是已有部署资源标识,运行命令为该服务中的 cli。应用不接收 DATABASE_URL、不依赖数据库健康才能启动;Compose 的整体 --wait 仍检查所预置 PostgreSQL 的就绪状态。 +Cargo 包名为 core/objects/frontend/compiler/storage/host/cli/server,core 的 Rust 库名为 endor_core,可执行文件为 cli/server。Compose 应用服务名 aster、数据库名、卷和生产路径是已有部署资源标识,运行命令为该服务中的 cli。应用不接收 DATABASE_URL、不依赖数据库健康才能启动;Compose 的整体 --wait 仍检查所预置 PostgreSQL 的就绪状态。 | 路径 | 用途 | | --- | --- | diff --git a/docs/operations/validation.md b/docs/operations/validation.md index 17af963..93cbbee 100644 --- a/docs/operations/validation.md +++ b/docs/operations/validation.md @@ -32,6 +32,8 @@ ci-ces-control.sh 负责准备全新专用临时 PostgreSQL 和测试 URL;成 | Endor 阅读器 | 字符串、分块 UTF-8 流、文件、读取失败、URI provider、修订标签与来源诊断 | [frontend/tests](../../crates/frontend/tests/README.md) 的 reader.rs | | AST 与接口生成 | 三类根、声明/语句层次、Visitor 及失败退出、Unicode 位置、JSON 往返、schema/范围错误和跨语言产物标识 | frontend/tests/ast.rs 与 scripts/test_generate_ast.py,见 [AST 模型](../runtime/ast.md) | | 源码前端 | 字符 PEG 词法边界、逐 Token 原文位置与首个错误诊断、Unicode 范围、数字原文、字符串与错误、优先级/结合性、函数/绑定、完整输入与错误位置、源码样例 | [frontend/tests](../../crates/frontend/tests/README.md) | +| 源码编译与执行 | 真实 Endor 文件到 VM、名称/初始化检查、参数与函数值、精确常量、宿主成员回调、跨 VM 模板、错误清理、递归限额、交互命名空间及扩展操作数 | [compiler/tests](../../crates/compiler/tests/README.md) | +| CLI 执行 | run/eval 文件与 stdin、入口、返回值、语义/编译/运行错误、traceback 和退出码 | [cli/tests](../../crates/cli/tests/README.md) | | CLI 解析 | AST 输出、未执行源码、文件/stdin 来源和行列错误、EOF 读取与非法 UTF-8 退出 | [cli/tests](../../crates/cli/tests/README.md) | | 公开示例 | 对象操作、手写指令执行与释放 | objects 与 core 的 src/lib.rs rustdoc,见 [objects/src](../../crates/objects/src/README.md)、[core/src](../../crates/core/src/README.md) | | 宿主文件信息 | UTF-8 值/路径、持久读取、同键替换、并发、坏记录、幂等迁移与保留新值 | [host/tests](../../crates/host/tests/README.md) | diff --git a/docs/runtime/README.md b/docs/runtime/README.md index d987838..1c25c1d 100644 --- a/docs/runtime/README.md +++ b/docs/runtime/README.md @@ -9,6 +9,7 @@ | [Endor 阅读器](reader.md) | SourceUnit、来源/修订标签、字符串/流/文件和宿主 URI provider | | [源码前端](frontend.md) | 字符 PEG lexer、token PEG parser、AST、基础函数/表达式语法与诊断 | | [AST 模型与 ASDL](ast.md) | 统一根、声明/语句/表达式、生成器、Visitor、位置和 JSON/编辑器接口 | +| [源码编译](compiler.md) | 名称/作用域、局部槽、字节码模板、代码装载、CLI 执行与示例 | | [字节码 VM](bytecode.md) | 指令编码、代码校验、局部槽、执行帧、函数创建、调用/返回与边界 | | [文本编码](text-encoding.md) | UTF-8、日文、外部 Shift_JIS/CP932 与宿主解码边界 | | [数据库特殊类](database-objects.md) | 显式连接、调用方数据模型、未来类接入与资源生命周期 | diff --git a/docs/runtime/ast.md b/docs/runtime/ast.md index cbe96bf..9ab9b67 100644 --- a/docs/runtime/ast.md +++ b/docs/runtime/ast.md @@ -51,7 +51,7 @@ Spanned 为有位置的节点提供 span()、source_text(&str)、location(&str) 生成的 Rust Visitor 有每个节点/种类的 visit_* 方法、默认 walk_* 函数,以及 enter_node/leave_node 钩子。Error 为关联类型,子节点失败时默认遍历返回错误,并配对调用已进入节点的离开钩子。可在 visit_function、visit_binding、visit_expr 等位置实现名称分析或字节码生成。 -覆盖 visit_* 后,调用 walk_* 可继续访问子节点;覆盖方法自身的钩子管理由实现者决定,直接调用 walk_* 不会重复调用当前节点的 visit_*。这些接口只提供遍历,尚未实现名称绑定、闭包捕获分析或字节码生成。TypeScript Visitor 对应编辑器使用,回调返回 false 可跳过子树。 +覆盖 visit_* 后,调用 walk_* 可继续访问子节点;覆盖方法自身的钩子管理由实现者决定,直接调用 walk_* 不会重复调用当前节点的 visit_*。这些接口只提供遍历;独立 [Compiler](compiler.md) 实现当前模块/函数名称分析和最小字节码生成,闭包捕获分析尚未实现。TypeScript Visitor 对应编辑器使用,回调返回 false 可跳过子树。 ## JSON 交换 @@ -69,6 +69,6 @@ cli parse --mode expression --json - cli parse --mode interactive --json - ``` -文件/stdin 阅读保持原有 UTF-8 和错误来源规则,stdin 到 EOF 后才解析。普通 cli parse 输出 AstRoot 的 Debug 文本,稳定交换使用带 schema 的 JSON;所有入口均不执行源码。命令在指定远端运行。 +文件/stdin 阅读保持原有 UTF-8 和错误来源规则,stdin 到 EOF 后才解析。普通 cli parse 输出 AstRoot 的 Debug 文本,稳定交换使用带 schema 的 JSON;parse 的各模式均不执行源码;run/eval 执行入口见 [编译链路](compiler.md)。命令在指定远端运行。 检查源码见 [frontend/tests](../../crates/frontend/tests/README.md) 和 [生成器检查](../../scripts/test_generate_ast.py),边界见 [验证](../operations/validation.md)。返回 [运行时索引](README.md)。 diff --git a/docs/runtime/builtins.md b/docs/runtime/builtins.md index eb293f0..f308701 100644 --- a/docs/runtime/builtins.md +++ b/docs/runtime/builtins.md @@ -119,7 +119,7 @@ fn main() -> Result<(), endor_core::VmError> { } ``` -当前已能通过字节码读取、写入和删除共享 Cell,并创建捕获参数的 Cell。LoadClosure 与 MakeFunction 可以在外围函数运行时创建共享这些 Cell 的嵌套函数,外围帧返回后 Cell 仍由闭包保活。上面的 Python 代码说明闭包语义;独立 frontend 已能解析基础函数,但尚无源码闭包语法或 AST 到字节码生成,代码对象仍由宿主准备。 +当前已能通过字节码读取、写入和删除共享 Cell,并创建捕获参数的 Cell。LoadClosure 与 MakeFunction 可以在外围函数运行时创建共享这些 Cell 的嵌套函数,外围帧返回后 Cell 仍由闭包保活。上面的 Python 代码说明闭包语义;独立 frontend 解析基础函数,由 [Compiler](compiler.md) 生成代码对象;尚无源码闭包语法或捕获分析。 ## 字符串与异常的准确含义 diff --git a/docs/runtime/bytecode.md b/docs/runtime/bytecode.md index b83191c..e8c283b 100644 --- a/docs/runtime/bytecode.md +++ b/docs/runtime/bytecode.md @@ -1,6 +1,6 @@ # 小型字节码 VM -当前提供指令与代码对象、内部执行帧、取指执行循环,语言函数已接入执行器。参考 CPython 3.14 的职责划分和栈式执行设计;源码 lexer/parser 由独立 [frontend](frontend.md) 提供,尚未接入 AST 到字节码生成;执行器不读取 Python `.pyc`,不承诺 CPython 操作码编号、ABI 或完整语言兼容性。 +当前提供指令与代码对象、内部执行帧、取指执行循环,语言函数已接入执行器。参考 CPython 3.14 的职责划分和栈式执行设计;源码 lexer/parser 由独立 [frontend](frontend.md) 提供,基础源码已由独立 [compiler](compiler.md) 接入字节码生成;执行器不读取 Python `.pyc`,不承诺 CPython 操作码编号、ABI 或完整语言兼容性。 Endor 是解释型语言,字节码是内部执行表示;从 AST 生成字节码属于自动执行准备,不要求使用者发布编译产物。当前没有字节码持久化缓存或跨版本装载协议;现有不可变代码对象仍可在 VM 内共享复用。平台升级与源码修订的区别见 [执行与版本模型](../architecture/execution-and-versions.md)。 @@ -160,7 +160,7 @@ fn main() -> Result<(), endor_core::VmError> { 运行错误仍返回 `VmError`。`last_traceback()` 另提供从外到内的函数名、文件、代码首行和出错指令偏移;它保存独立元数据,不保活已经清理的帧。首行是代码声明的 first_line,不是由行号表推导出的实际错误源码行。成功的新一次调用会清空旧记录。尚无语言异常对象传播、raise/try/except 或完整源码行号表。 -暂未实现:AST 到字节码生成、关键字/可变参数、生成器/协程、迭代器专用指令、数值算术、自动方法绑定、指令特化和 JIT。 +暂未实现:源码闭包编译、关键字/可变参数、生成器/协程、迭代器专用指令、数值算术、自动方法绑定、指令特化和 JIT。 ## 验证 diff --git a/docs/runtime/compiler.md b/docs/runtime/compiler.md new file mode 100644 index 0000000..bcac6a1 --- /dev/null +++ b/docs/runtime/compiler.md @@ -0,0 +1,55 @@ +# 最小源码编译与执行链路 + +当前已连接 reader → lexer → PEG parser → AST → 名称/作用域分析 → wordcode → 代码对象 → VM。实现位于独立 [compiler](../../crates/compiler/README.md);frontend 只负责语法,objects 拥有指令/代码数据与验证,core 解释字节码。用户无需手写指令,也不需要先补全 Python 基础类。 + +## 接口与阶段 + +| 接口 | 行为 | +| --- | --- | +| analyze(&AstRoot, &CompileOptions) | 返回 SymbolTable:根与函数作用域、CodeLayout、每次名称读取的位置和 Local/Global 绑定 | +| compile_ast(&AstRoot, &SourceUnit, &CompileOptions) | 校验节点与原文范围、分析并生成 CompiledCode;不验证 AST 与原文的语义等价性 | +| compile_source(&SourceUnit, ParseMode, &CompileOptions) | 先调用 frontend 解析,再编译;错误携带原始文件/流/URI 与修订来源及一基行列 | +| CompiledCode::load(&mut Vm) | 将模板及子模板装载为该 VM 的代码对象,返回一个拥有根的 ObjectRef | + +解析错误沿用 Lexical/Syntax,名称错误为 Semantic,不支持的执行特性或代码验证错误为 Compilation。编译不修改 VM 或全局字典;词法动作也不执行语言代码。模板提供 instructions/layout/name/filename/first_line/children 只读检查入口。生成时使用已有 EndorCode 验证器检查槽位、操作数和栈平衡;装载再次验证真实常量。 + +## 名称与作用域 + +首版是模块作用域和函数作用域。模块先登记全部函数名,再分析函数体,因此支持前向引用、自递归和函数作为普通值传递。函数参数占据最前面的局部槽,let 按声明顺序分配后续槽;引用优先使用函数局部,其次使用已声明全局。 + +函数内先收集全部局部名称,再按语句顺序检查初始化。后面声明的 let 会遮蔽整个函数中的同名全局,但提前读取报错;let x = x 也会报未初始化。参数、局部绑定和同一模块的函数不允许在各自作用域内重复声明;参数或局部可以遮蔽模块函数。let 当前没有赋值/重绑定语法。嵌套函数/闭包分析和块级作用域尚未实现。 + +CompileOptions.globals 显式声明宿主会提供的 globals/builtins 名称,默认空。不认识的名称在编译时报错;已声明的宿主名称仍必须在执行时存在,否则 VM 返回 NameNotFound。编译器不自动注入 print、默认基础类或标准库,也不读取这些名字的值。类型注解只保留语法,不参与名称绑定、运行时求值或类型检查。 + +## 指令与代码对象 + +| 源结构 | 生成方式 | +| --- | --- | +| 字符串、整数、十进制常量 | 放入常量表并 LoadConst;数字经现有 EndorNumber 解析,不转换为 f64 | +| 数字字面量前的一个或多个负号 | 折叠符号为精确常量;不是任意表达式的一元算术 | +| 局部读取/let | LoadFast/StoreFast;参数和局部名称在 CodeLayout 中 | +| 全局读取 | LoadGlobal,使用该代码对象的 names 表 | +| 函数声明 | 子代码模板 → LoadConst(code) → MakeFunction(0, 0) → StoreGlobal | +| 普通调用 | 求值 callee → PushNull → 从左到右求值位置参数 → Call(argc) | +| 成员读取 | 求值对象 → LoadAttr;沿用 VM 属性协议,无自动 self 绑定 | +| return | 求值结果 → ReturnValue;无值 return 或 AST 函数末尾无 return 时生成 nil | + +源码 parser 仍要求函数体为 let* 加一个带值 return;更通用的表达式语句和空 return 可以由 AST 调用方构造,但不能因此宣称源码语法已开放。模块初始化只发布函数并返回 nil,不自动调用 main。CLI 选择入口属于宿主行为。 + +二元算术、非字面量的一元负号和符号值 .name 目前产生明确 Compilation 诊断,不伪造运行结果。导入、类、Agent、条件/循环、关键字参数等仍未实现。函数参数个数和对象是否可调用由 VM 在运行时检查。 + +## 装载、执行与所有权 + +CompiledCode 是纯 Rust 模板,嵌套代码常量在模板内部使用占位槽,装载时替换为当前 VM 的子代码对象;模板不含跨 VM 句柄,可复用到多个 VM。父代码对象成功分配后持有子代码,装载成功或失败都会释放临时子根。返回的根代码对象由调用方 release。 + +宿主为模块提供 globals 字典,调用 vm.execute(code, globals) 发布函数,然后从字典借用函数句柄并调用 vm.call。函数共享装载后的代码,反复调用不重新编译;execute/call 的对象结果持有根,用后 release_value。函数指向 globals,globals 保存函数,因此宿主需保留命名空间期间的根,并在销毁时清空字典或释放根后显式 collect_cycles。编译器不清空宿主字典。 + +Expression 模式返回表达式值;Interactive 模式严格编译一次声明、let 或表达式。声明/绑定返回 nil,表达式返回值;宿主可以跨提交复用 globals,并在下一次 CompileOptions 中声明可见名称。CLI 当前没有 REPL、续行或自动会话管理。 + +代码对象保存来源字符串、函数名和定义起始行,现有 VM traceback 保存该位置及指令偏移;没有每条指令对应的精确源码行/列映射。现有 ExecutionLimits 帧数/指令数限制可由嵌入方继续使用;本链路不构成不可信代码沙箱。 + +## CLI 与检查 + +cli run [--entry name] 编译模块、执行初始化、调用无参数入口(默认 main),输出返回值一行。cli eval 编译执行一个表达式。字符串输出原始内容,数字输出无损表示,对象使用 VM str;失败仅输出 stderr 并返回非零退出码。文件和 stdin 严格 UTF-8,stdin 到 EOF 后才处理。CLI 在成功或失败后释放代码/结果并清空自己创建的命名空间。 + +可执行文件见 [示例目录](../../crates/compiler/examples/README.md),API/所有权与错误路径见 [pipeline.rs](../../crates/compiler/tests/pipeline.rs),进程入口见 [CLI 检查](../../crates/cli/tests/README.md)。这些检查由既有远端 workspace 测试覆盖;本地只做静态检查,不据此宣称实际执行通过,见 [验证约定](../operations/validation.md)。返回 [运行时索引](README.md)。 diff --git a/docs/runtime/frontend.md b/docs/runtime/frontend.md index 783cfda..ee0e1c5 100644 --- a/docs/runtime/frontend.md +++ b/docs/runtime/frontend.md @@ -1,6 +1,6 @@ # 源码词法与 PEG 语法前端 -frontend 包提供 [Endor 阅读器](reader.md)、rust-peg 生成的字符 lexer 与 token parser。宿主通过 SourceUnit 调用带来源的词法/语法入口,原有 lex、parse、parse_expression 保留;CLI 提供 parse 文件/stdin 命令;结果是 AST,尚未实现 AST 到字节码生成、类型检查、名称解析或源码执行。Core 仅依赖本地 Objects,不依赖 frontend 或 peg;Objects 保持零依赖。 +frontend 包提供 [Endor 阅读器](reader.md)、rust-peg 生成的字符 lexer 与 token parser。宿主通过 SourceUnit 调用带来源的词法/语法入口,原有 lex、parse、parse_expression 保留;CLI 提供 parse 文件/stdin 命令;结果是 AST;独立 [Compiler](compiler.md) 已接入名称/作用域分析、最小字节码生成和 CLI run/eval 执行,frontend 自身不执行代码或检查类型。Core 仅依赖本地 Objects,不依赖 frontend 或 peg;Objects 保持零依赖。 首版以 Endorphin-Language 设计稿的基础词法、表达式与函数章节为起点,语法可以继续修改;设计稿中的未完成部分不作为阻塞前端实现的前置条件,也不被整体视作已经实现的语言能力。这里维护当前解析契约;源码位置见 [frontend](../../crates/frontend/README.md)。 @@ -75,4 +75,4 @@ use、namespace、class、agent、完整类型语法、条件/循环、比较 `cli parse ` 与 `cli parse -` 成功时输出 Rust Debug 格式的 AST,失败时在 stderr 输出来源/行/列和诊断并返回非零退出码;`-` 表示从 stdin 读到 EOF,不是 REPL。阅读与来源包装规则见 [阅读器](reader.md)。可用 --mode 选择 module/expression/interactive,用 --json 输出带 schema 标识的 AstDocument;普通输出为 AstRoot Debug 文本,稳定交换格式见 [AST 模型](ast.md)。命令不执行源码、不初始化 VM、不连接数据库;具体运行与本地禁测约定见 [运行](../operations/README.md) 和 [验证](../operations/validation.md)。 -后续需要将 AST 经名称/闭包分析转换为现有字节码,再交给 VM 解释执行。这是解释器内部的编译/执行准备步骤,不要求使用者先构建程序,见 [执行与版本模型](../architecture/execution-and-versions.md)。这条连接尚未实现;解析成功不表示表达式已有运行语义。VM 虽已支持 MakeFunction,但从源码创建函数仍需要字节码生成接入。解析器也不是不可信代码沙箱,当前没有通用输入大小或解析递归深度限额。 +当前 Compiler 将 AST 经模块/函数名称分析转换为现有字节码,再交给 VM 解释执行。这是解释器内部的编译/执行准备步骤,不要求使用者先构建程序,见 [执行与版本模型](../architecture/execution-and-versions.md)。MakeFunction 已接入源码函数编译,但解析接受的二元算术和符号值仍没有执行语义;编译器会给出明确诊断,见 [编译链路](compiler.md)。源码闭包尚未实现。解析器也不是不可信代码沙箱,当前没有通用输入大小或解析递归深度限额。 diff --git a/docs/runtime/objects.md b/docs/runtime/objects.md index 1f274ec..031395c 100644 --- a/docs/runtime/objects.md +++ b/docs/runtime/objects.md @@ -102,6 +102,6 @@ compare 的反向规则是 Lt 对应 Gt、Le 对应 Ge,Eq/Ne 保持不变。 已实现:统一对象结构、稳定地址、代际句柄、身份、显式引用计数、托管属性、原生操作覆盖、比较与哈希、原生调用、资源清理和显式循环回收。 -尚未实现:Python type 系统、语言根类、继承和描述符、AST 到字节码生成、可变 VM 调用上下文、自动/分代/增量 GC、语言终结器/复活、完整资源限额和 VM 快照;独立 frontend 已提供阅读器、lexer/parser 与 AST,尚未接入源码执行,见 [源码前端](frontend.md);执行器已有帧数和指令数限额。数据库仅为未来特殊类提供按需连接,普通对象或宿主基础信息不自动持久化,详见 [数据库特殊类](database-objects.md)。 +尚未实现:Python type 系统、语言根类、继承和描述符、可变 VM 调用上下文、自动/分代/增量 GC、语言终结器/复活、完整资源限额和 VM 快照;独立 frontend 已提供阅读器、lexer/parser 与 AST,基础源码执行由独立 [Compiler](compiler.md) 接入,见 [源码前端](frontend.md);执行器已有帧数和指令数限额。数据库仅为未来特殊类提供按需连接,普通对象或宿主基础信息不自动持久化,详见 [数据库特殊类](database-objects.md)。 验证范围与执行约定见 [验证](../operations/validation.md)。 diff --git a/docs/runtime/reader.md b/docs/runtime/reader.md index 3d606d3..3ce8e57 100644 --- a/docs/runtime/reader.md +++ b/docs/runtime/reader.md @@ -42,7 +42,7 @@ revision 是宿主返回的原样标签;Reader 只拒绝全空白标签,不 ## CLI 与交互输入 -CLI parse 文件通过 from_file → parse_source 输出 AST;`cli parse -` 通过 from_stream 读取 stdin 到 EOF,来源为 ``。错误保留文件或流名及行列;文件读取与非法 UTF-8 错误在进入 lexer 前返回。命令仍不执行源码。 +CLI parse 文件通过 from_file → parse_source 输出 AST;`cli parse -` 通过 from_stream 读取 stdin 到 EOF,来源为 ``。错误保留文件或流名及行列;文件读取与非法 UTF-8 错误在进入 lexer 前返回。parse 不执行源码;run/eval 使用相同阅读器后进入 [Compiler](compiler.md),支持范围见该契约。 stdin 入口不是 REPL,不打印提示符、不保持求值环境,也不区分“需要继续输入”和“真正语法错误”。未来 REPL 宿主先收集一次提交的输入单元,再使用 from_text 并指定会话来源;reader 不承担交互调度。包与 namespace 的成员查找和依赖解析也不由 reader 自动完成。