Add Endor source reader before lexing with provenance and URI provider
Verify databases and deploy Aster VM / verify-and-deploy (push) Successful in 6m53s

This commit is contained in:
2026-10-19 03:23:38 +08:00
parent 4bf889e5b7
commit 483d9d1717
27 changed files with 667 additions and 37 deletions
+2 -2
View File
@@ -5,7 +5,7 @@
| 内容 | 职责 |
| --- | --- |
| Cargo.toml | cli 二进制与 core/frontend/host 依赖;无 storage 或 Tokio 依赖 |
| [src](src/README.md) | 参数路由、引导演示、源码解析和退出码 |
| [src](src/README.md) | 参数路由、引导演示、文件/stdin 阅读、源码解析和退出码 |
| [tests](tests/README.md) | parse 命令的 AST 输出、错误位置和严格 UTF-8 检查 |
运行方式见 [运行指南](../../docs/operations/README.md),输入边界见 [配置](../../docs/operations/configuration.md)。parse 严格读取 UTF-8 文件并输出 AST,不执行源码,见 [前端](../../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 并输出 AST,不执行源码,见 [前端](../../docs/runtime/frontend.md);metadata put/get 和只导入缺失项的 import 均使用文件,见 [宿主信息](../../docs/operations/host-information.md)。返回 [工作区](../README.md)。
+2 -2
View File
@@ -1,7 +1,7 @@
# CLI 入口
[main.rs](main.rs) 是唯一入口:同步 main 将 run 的结果转成进程退出码;run 处理 bootstrap、parse 文件、help、文件 metadata put/get/import,错误打印到 stderr 并返回失败。
[main.rs](main.rs) 是唯一入口:同步 main 将 run 的结果转成进程退出码;run 处理 bootstrap、parse 文件/stdin、help、文件 metadata put/get/import,错误打印到 stderr 并返回失败。
bootstrap 创建并显式释放两个对象,不读取数据库环境、不打开数据库。parse 使用 frontend 输出 AST,词法/语法错误显示文件、行、列;不创建 VM 或执行源码。未知命令报错;metadata 通过 host::MetadataStore 读写文件,import 从标准输入接收旧记录且不覆盖已有文件。参数目前由 std::env::args 读取,非 Unicode 参数可能 panic,边界见 [配置](../../../docs/operations/configuration.md)。
bootstrap 创建并显式释放两个对象,不读取数据库环境、不打开数据库。parse 使用 EndorReader 和 parse_source 输出 AST;`-` 从 stdin 读到 EOF,来源为 `<stdin>`。词法/语法错误显示来源、行、列;不创建 VM 或执行源码。未知命令报错;metadata 通过 host::MetadataStore 读写文件,import 从标准输入接收旧记录且不覆盖已有文件。参数目前由 std::env::args 读取,非 Unicode 参数可能 panic,边界见 [配置](../../../docs/operations/configuration.md)。
新增命令时更新用法、[运行指南](../../../docs/operations/README.md) 和 [smoke.py 的导航](../../../scripts/README.md)。返回 [cli](../README.md)。
+8 -10
View File
@@ -2,9 +2,10 @@ use std::error::Error;
use std::process::ExitCode;
use endor_core::{Value, Vm};
use frontend::{EndorReader, parse_source};
use host::MetadataStore;
const USAGE: &str = "Usage: cli [bootstrap | parse <file> | metadata put <key> <value> | metadata get <key> | metadata import | help]";
const USAGE: &str = "Usage: cli [bootstrap | parse <file|-> | metadata put <key> <value> | metadata get <key> | metadata import | help]";
fn main() -> ExitCode {
match run() {
@@ -46,15 +47,12 @@ fn run() -> Result<(), Box<dyn Error>> {
}
["help"] | ["--help"] | ["-h"] => println!("{USAGE}"),
["parse", path] => {
let source =
std::fs::read_to_string(path).map_err(|error| format!("{path}: {error}"))?;
let program = frontend::parse(&source).map_err(|error| {
let location = error.location(&source);
format!(
"{path}:{}:{}: {}",
location.line, location.column, error.message
)
})?;
let source = if *path == "-" {
EndorReader::from_stream("<stdin>", std::io::stdin().lock())?
} else {
EndorReader::from_file(path)?
};
let program = parse_source(&source)?;
println!("{program:#?}");
}
["metadata", "put", key, value] => {
+1 -1
View File
@@ -1,5 +1,5 @@
# CLI 命令检查
[parse.rs](parse.rs) 使用 Cargo 提供的 cli 测试二进制检查 parse 命令:合法 UTF-8 源码输出 AST 且不执行名称调用;非法语法输出文件/行/列并返回失败;非法 UTF-8 不替换字符。
[parse.rs](parse.rs) 使用 Cargo 提供的 cli 测试二进制检查 parse 命令:合法 UTF-8 源码输出 AST 且不执行名称调用;非法语法输出文件/行/列并返回失败;非法 UTF-8 不替换字符;`parse -` 检查 stdin 输入、EOF 和带流名的错误。
测试仅在 ces-control 的工作区检查中运行,本地不执行。测试临时文件使用独立名称并在结束时清理。返回 [cli](../README.md),验证约定见 [验证](../../../docs/operations/validation.md)。
+41 -1
View File
@@ -1,5 +1,6 @@
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicUsize, Ordering};
static NEXT_FILE: AtomicUsize = AtomicUsize::new(0);
@@ -65,3 +66,42 @@ fn parse_command_reports_source_locations_and_rejects_invalid_utf8() {
assert!(!output.status.success());
assert!(output.stdout.is_empty());
}
fn parse_stdin(source: &[u8]) -> std::process::Output {
let mut child = Command::new(env!("CARGO_BIN_EXE_cli"))
.args(["parse", "-"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
let mut stdin = child.stdin.take().unwrap();
stdin.write_all(source).unwrap();
drop(stdin);
child.wait_with_output().unwrap()
}
#[test]
fn parse_command_reads_stdin_to_eof_and_reports_its_origin() {
let output = parse_stdin("function 表示(): String { return \"日\" }".as_bytes());
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
assert!(String::from_utf8(output.stdout).unwrap().contains("表示"));
let output = parse_stdin(b"function f(): Number {\n return )\n}");
assert!(!output.status.success());
assert!(
String::from_utf8(output.stderr)
.unwrap()
.contains("<stdin>:2:10:")
);
let output = parse_stdin(&[0xff]);
assert!(!output.status.success());
assert!(
String::from_utf8(output.stderr)
.unwrap()
.contains("<stdin>: invalid UTF-8 at byte 0")
);
}