feat: connect source compilation and VM execution
Verify databases and deploy Aster VM / verify-and-deploy (push) Successful in 7m56s
Verify databases and deploy Aster VM / verify-and-deploy (push) Successful in 7m56s
This commit is contained in:
Generated
+9
@@ -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"
|
||||
|
||||
+2
-1
@@ -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"] }
|
||||
|
||||
@@ -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 工作流 |
|
||||
|
||||
+4
-3
@@ -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)。
|
||||
|
||||
@@ -10,6 +10,7 @@ name = "cli"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
compiler.workspace = true
|
||||
core.workspace = true
|
||||
frontend.workspace = true
|
||||
host.workspace = true
|
||||
|
||||
@@ -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)。
|
||||
|
||||
@@ -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,来源为 `<stdin>`。词法/语法错误显示来源、行、列;不创建 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)。
|
||||
|
||||
@@ -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<dyn Error>> {
|
||||
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<dyn Error>> {
|
||||
let source = read(path)?;
|
||||
let output = execute(&source, ParseMode::Expression, None)?;
|
||||
println!("{output}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read(path: &str) -> Result<SourceUnit, Box<dyn Error>> {
|
||||
Ok(if path == "-" {
|
||||
EndorReader::from_stream("<stdin>", std::io::stdin().lock())?
|
||||
} else {
|
||||
EndorReader::from_file(path)?
|
||||
})
|
||||
}
|
||||
|
||||
fn execute(
|
||||
source: &SourceUnit,
|
||||
mode: ParseMode,
|
||||
entry: Option<&str>,
|
||||
) -> Result<String, Box<dyn Error>> {
|
||||
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<String, Box<dyn Error>> {
|
||||
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::<dyn Error>::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<String, Box<dyn Error>> {
|
||||
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)?,
|
||||
})
|
||||
}
|
||||
@@ -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] <file|-> | metadata put <key> <value> | metadata get <key> | metadata import | help]";
|
||||
mod execute;
|
||||
|
||||
const USAGE: &str = "Usage: cli [bootstrap | run [--entry name] <file|-> | eval <file|-> | parse [--mode module|expression|interactive] [--json] <file|-> | metadata put <key> <value> | metadata get <key> | metadata import | help]";
|
||||
|
||||
fn main() -> ExitCode {
|
||||
match run() {
|
||||
@@ -48,6 +50,8 @@ fn run() -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
["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)");
|
||||
|
||||
@@ -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)。
|
||||
|
||||
@@ -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}",
|
||||
"<stdin>: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 (<stdin>: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:"));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)。
|
||||
@@ -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)。
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// cli run crates/compiler/examples/hello.endor → 你好,Endor 🌱
|
||||
function main(): String {
|
||||
return "你好,Endor 🌱"
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// cli eval crates/compiler/examples/literal.endor
|
||||
"表达式 → 字节码 → VM"
|
||||
@@ -0,0 +1,5 @@
|
||||
// Signed decimal constants retain all digits; no f64 conversion or arithmetic.
|
||||
function main(): Number {
|
||||
let value = -123456789012345678901234567890.00100
|
||||
return value
|
||||
}
|
||||
@@ -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, "局部名称优先")
|
||||
}
|
||||
@@ -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)。
|
||||
@@ -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<NameUse>,
|
||||
}
|
||||
|
||||
#[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<Scope>,
|
||||
}
|
||||
|
||||
fn error(span: Span, message: impl Into<String>) -> 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<SymbolTable, Diagnostic> {
|
||||
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<String> = 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, "<module>"),
|
||||
AstRoot::Expression(_) => (ScopeKind::Expression, "<expression>"),
|
||||
AstRoot::Interactive(_) => (ScopeKind::Interactive, "<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::<Result<Vec<_>, _>>()?;
|
||||
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<String>, 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<String>) -> Result<Scope, Diagnostic> {
|
||||
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<String>,
|
||||
}
|
||||
|
||||
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<String>,
|
||||
) -> 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(())
|
||||
}
|
||||
}
|
||||
@@ -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<Item = &CompiledCode> {
|
||||
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<ObjectRef, VmError> {
|
||||
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<String>) -> Diagnostic {
|
||||
diagnostic(DiagnosticKind::Compilation, span, message)
|
||||
}
|
||||
|
||||
pub(crate) fn generate(
|
||||
root: &AstRoot,
|
||||
source: &SourceUnit,
|
||||
symbols: &SymbolTable,
|
||||
) -> Result<CompiledCode, Diagnostic> {
|
||||
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<Instruction>,
|
||||
constants: Vec<Value>,
|
||||
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<CompiledCode, Diagnostic> {
|
||||
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<String> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -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<String>,
|
||||
}
|
||||
|
||||
pub fn compile_source(
|
||||
source: &SourceUnit,
|
||||
mode: ParseMode,
|
||||
options: &CompileOptions,
|
||||
) -> Result<CompiledCode, SourceDiagnostic> {
|
||||
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<CompiledCode, Diagnostic> {
|
||||
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<String>) -> Diagnostic {
|
||||
Diagnostic {
|
||||
kind,
|
||||
span,
|
||||
message: message.into(),
|
||||
expected: Vec::new(),
|
||||
}
|
||||
}
|
||||
@@ -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)。
|
||||
@@ -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("<test>", 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::<EndorFunctionObject>(main, "function").unwrap();
|
||||
assert_eq!(
|
||||
vm.payload::<EndorCodeObject>(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::<Vec<_>>(),
|
||||
[
|
||||
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("<invalid>", 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<Value, VmError> {
|
||||
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("<host>", "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("<host>", "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("<submission>", 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("<unsupported>", 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("<source>", "\"日\"");
|
||||
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());
|
||||
}
|
||||
@@ -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)。
|
||||
|
||||
@@ -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)。
|
||||
|
||||
| 内容 | 职责 |
|
||||
| --- | --- |
|
||||
|
||||
@@ -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 位置接口与便捷方法 |
|
||||
|
||||
@@ -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.
|
||||
|
||||
+2
-1
@@ -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) |
|
||||
|
||||
@@ -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) |
|
||||
|
||||
|
||||
@@ -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)。
|
||||
|
||||
@@ -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)。
|
||||
|
||||
## 导出与迁移
|
||||
|
||||
|
||||
@@ -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。
|
||||
|
||||
@@ -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,输入来源为 `<stdin>`,没有 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
|
||||
|
||||
@@ -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 的就绪状态。
|
||||
|
||||
| 路径 | 用途 |
|
||||
| --- | --- |
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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) | 显式连接、调用方数据模型、未来类接入与资源生命周期 |
|
||||
|
||||
+2
-2
@@ -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)。
|
||||
|
||||
@@ -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) 生成代码对象;尚无源码闭包语法或捕获分析。
|
||||
|
||||
## 字符串与异常的准确含义
|
||||
|
||||
|
||||
@@ -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。
|
||||
|
||||
## 验证
|
||||
|
||||
|
||||
@@ -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] <file|-> 编译模块、执行初始化、调用无参数入口(默认 main),输出返回值一行。cli eval <file|-> 编译执行一个表达式。字符串输出原始内容,数字输出无损表示,对象使用 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)。
|
||||
@@ -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 <file>` 与 `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)。源码闭包尚未实现。解析器也不是不可信代码沙箱,当前没有通用输入大小或解析递归深度限额。
|
||||
|
||||
@@ -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)。
|
||||
|
||||
@@ -42,7 +42,7 @@ revision 是宿主返回的原样标签;Reader 只拒绝全空白标签,不
|
||||
|
||||
## CLI 与交互输入
|
||||
|
||||
CLI parse 文件通过 from_file → parse_source 输出 AST;`cli parse -` 通过 from_stream 读取 stdin 到 EOF,来源为 `<stdin>`。错误保留文件或流名及行列;文件读取与非法 UTF-8 错误在进入 lexer 前返回。命令仍不执行源码。
|
||||
CLI parse 文件通过 from_file → parse_source 输出 AST;`cli parse -` 通过 from_stream 读取 stdin 到 EOF,来源为 `<stdin>`。错误保留文件或流名及行列;文件读取与非法 UTF-8 错误在进入 lexer 前返回。parse 不执行源码;run/eval 使用相同阅读器后进入 [Compiler](compiler.md),支持范围见该契约。
|
||||
|
||||
stdin 入口不是 REPL,不打印提示符、不保持求值环境,也不区分“需要继续输入”和“真正语法错误”。未来 REPL 宿主先收集一次提交的输入单元,再使用 from_text 并指定会话来源;reader 不承担交互调度。包与 namespace 的成员查找和依赖解析也不由 reader 自动完成。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user