Merge pull request 'docs: 补全 Aster 中文调用链、控制流与生命周期注释' (#3) from codex/detailed-chinese-comments into main
Reviewed-on: #3
This commit was merged in pull request #3.
This commit is contained in:
@@ -1,13 +1,21 @@
|
||||
// compiler/tests/pipeline.rs 将此文件作为模块编译,加载后由测试调用 main,检查前向全局函数查找。
|
||||
// The callee is declared after main. Parameters, locals and calls use wordcode.
|
||||
// 模块入口由宿主/测试在模块执行完成后调用;先 echo,再把局部结果交给 relay。
|
||||
function main(): String {
|
||||
// 直接调用后面声明的 echo,返回值写入当前帧的 message 局部槽。
|
||||
let message = echo("函数调用已接通")
|
||||
// 将局部值作为实参传给 relay,返回其结果给宿主。
|
||||
return relay(message)
|
||||
}
|
||||
|
||||
// main 调用的中转函数;value 是当前帧参数槽,继续调用全局 echo。
|
||||
function relay(value: String): String {
|
||||
// 普通函数调用逐层返回相同字符串,验证嵌套调用的参数传递与返回。
|
||||
return echo(value)
|
||||
}
|
||||
|
||||
// main 和 relay 共用的恒等函数,参数值直接成为调用结果。
|
||||
function echo(value: String): String {
|
||||
// 无额外转换,直接把参数值交给调用者。
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// cli run crates/compiler/examples/hello.endor → 你好,Endor 🌱
|
||||
// compiler/tests/pipeline.rs 编译并加载本模块后调用 main,期望返回“你好,Endor 🌱”。
|
||||
function main(): String {
|
||||
// 返回 Unicode 文本字面量,测试核对编译及 VM 返回过程保留原文。
|
||||
return "你好,Endor 🌱"
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
// cli eval crates/compiler/examples/literal.endor
|
||||
// compiler/tests/pipeline.rs 以 Expression 模式编译此文件,VM 执行后直接取得文本;不定义 main。
|
||||
"表达式 → 字节码 → VM"
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
// compiler/tests/pipeline.rs 按 Module 模式编译,执行模块后调用 main 并按精确十进制值比较。
|
||||
// Signed decimal constants retain all digits; no f64 conversion or arithmetic.
|
||||
// 由测试/宿主调用,验证长十进制带符号常量经过编译与返回仍不丢精度。
|
||||
function main(): Number {
|
||||
// 字面量保存所有十进制数字,不先经 f64;源码负号经 signed_literal 合入精确数值拼写。
|
||||
let value = -123456789012345678901234567890.00100
|
||||
// 返回局部槽中的数值,用于检查整个管线保持原来的精确值。
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
// compiler/tests/pipeline.rs 加载模块并调用 main,检查函数值传参及局部名优先于全局声明。
|
||||
// A parameter can shadow a module function, and functions are ordinary values.
|
||||
// main 把此全局函数作为普通值传入 invoke;实际调用时返回收到的参数。
|
||||
function echo(value: String): String {
|
||||
// 恒等返回,不改变用于确认调用链的文本。
|
||||
return value
|
||||
}
|
||||
|
||||
// main 调用;参数 echo 遮蔽同名模块函数,value 保存待转发的字符串。
|
||||
function invoke(echo: Callable, value: String): String {
|
||||
// 从局部参数 echo 复制函数值,而非再次查找模块的同名全局。
|
||||
let chosen = echo
|
||||
// 经局部 chosen 间接调用函数,把 value 作为实参并返回调用结果。
|
||||
return chosen(value)
|
||||
}
|
||||
|
||||
// 宿主/测试入口,先取得全局 echo 函数值,再传给 invoke 的同名参数。
|
||||
function main(): String {
|
||||
// invoke 内部用局部参数解析 echo,最终应原样返回这段文本。
|
||||
return invoke(echo, "局部名称优先")
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! compile_ast 调用的名称与确定初始化分析;只构造 Scope/CodeLayout,不分配 VM 对象。
|
||||
//! 先登记所有顶层声明,再分析函数体;codegen 根据名称、跨度和绑定索引选择 LoadFast/LoadGlobal。
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use endor_core::CodeLayout;
|
||||
@@ -7,48 +9,73 @@ use frontend::{Diagnostic, DiagnosticKind, Span};
|
||||
use crate::{CompileOptions, diagnostic};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// 根节点或函数的编译上下文;生成器据此区分局部槽写入与全局环境写入。
|
||||
pub enum ScopeKind {
|
||||
// 模块声明入口,执行后通常返回 Nil。
|
||||
Module,
|
||||
// 单个表达式入口,直接返回其求值结果。
|
||||
Expression,
|
||||
// 单条交互输入,可声明、赋值或返回表达式结果。
|
||||
Interactive,
|
||||
// 函数或类方法体,拥有参数、局部槽和 return 语义。
|
||||
Function,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// expression 解析的读取位置;codegen 使用此索引编码对应的加载指令。
|
||||
pub enum NameBinding {
|
||||
// CodeLayout.locals 中的位置,参数也占局部槽。
|
||||
Local(usize),
|
||||
// CodeLayout.names 中的位置,运行时先查 globals 再查捕获的 builtins。
|
||||
Global(usize),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// 一次名称表达式的解析结果;名字加源码跨度定位具体使用点,避免同名出现互相混淆。
|
||||
pub struct NameUse {
|
||||
// 本次读取的名称文本,生成器与 span 联合匹配,取得该表达式的绑定。
|
||||
pub name: String,
|
||||
// 原源码中的半开字节范围,用于诊断及生成阶段定位。
|
||||
pub span: Span,
|
||||
// 名称读取已解析到局部槽或全局名字索引。
|
||||
pub binding: NameBinding,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// 一个代码单元的布局与读取记录,由 analyze/function_scope 构造,Emitter 借用。
|
||||
pub struct Scope {
|
||||
// 决定赋值存局部还是全局,以及是否允许 return。
|
||||
pub kind: ScopeKind,
|
||||
// 根的合成诊断名或源码函数名;生成器同时用 span 区分不同类的同名方法。
|
||||
pub name: String,
|
||||
// 原源码中的半开字节范围,用于诊断及生成阶段定位。
|
||||
pub span: Span,
|
||||
// VM 代码验证及帧分配需要的参数、局部名和名字表。
|
||||
pub layout: CodeLayout,
|
||||
// 每次名称表达式的绑定,按分析访问顺序追加,供生成器按跨度检索。
|
||||
pub references: Vec<NameUse>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// analyze 返回、compile_ast 交给 codegen 的分析结果;根布局和每个函数体布局分开保存。
|
||||
/// 字段公开以供宿主/测试检查,但改动此表不会更新原 AST,也没有公开入口把任意改表直接编译。
|
||||
pub struct SymbolTable {
|
||||
// 根代码单元,即模块、表达式或交互输入的作用域。
|
||||
pub root: Scope,
|
||||
/// Function and class-method scopes in declaration order. Nested declarations
|
||||
/// are rejected until source closure semantics are implemented.
|
||||
// 顶层函数和类方法按声明顺序展平;生成器以名称和跨度查找对应体。
|
||||
pub functions: Vec<Scope>,
|
||||
}
|
||||
|
||||
/// 本文件所有拒绝路径共用,把具体节点跨度与说明包装为 Semantic 诊断。
|
||||
fn error(span: Span, message: impl Into<String>) -> Diagnostic {
|
||||
diagnostic(DiagnosticKind::Semantic, span, message)
|
||||
}
|
||||
|
||||
/// analyze 登记顶层声明时调用,统一取得函数或类的名字节点(含诊断位置)。
|
||||
fn declaration_name(declaration: &Decl) -> &frontend::ast::Name {
|
||||
// 按声明种类取相同用途的名字,不把类方法当作顶层全局声明。
|
||||
match &declaration.kind {
|
||||
DeclKind::Function(function) => &function.name,
|
||||
DeclKind::Class(class) => &class.name,
|
||||
@@ -58,7 +85,12 @@ fn declaration_name(declaration: &Decl) -> &frontend::ast::Name {
|
||||
/// 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.
|
||||
/// compile_ast 和直接分析测试调用;options 仅声明额外全局名,返回根及各函数的绑定布局。
|
||||
/// 函数体先收集所有局部名,因此后面的 let 也会遮蔽前面的全局读取,但未初始化读取会报错。
|
||||
/// 直接调用只做本阶段的名称/控制流分析,不执行 AstDocument 跨度验证、类型解析或字节码验证;
|
||||
/// 需要完整流水线的宿主使用 compile_ast/compile_source,不能把此处成功视为代码已可执行。
|
||||
pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result<SymbolTable, Diagnostic> {
|
||||
// 交互模式要求恰好一条语句;空输入或批量输入不是本编译入口支持的单位。
|
||||
if let AstRoot::Interactive(input) = root
|
||||
&& input.statements.len() != 1
|
||||
{
|
||||
@@ -67,15 +99,19 @@ pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result<SymbolTable,
|
||||
"interactive input must contain exactly one statement",
|
||||
));
|
||||
}
|
||||
// 宿主名称先去重,后续加入源码声明;这里只登记文本,不校验其是否符合词法名称规则或提供 VM 值。
|
||||
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 declarations: Vec<&Decl> = match root {
|
||||
AstRoot::Module(program) => program.declarations.iter().collect(),
|
||||
AstRoot::Interactive(input) => input
|
||||
.statements
|
||||
.iter()
|
||||
// 交互输入仅声明语句进入预登记列表,其余语句稍后按执行语义分析。
|
||||
.filter_map(|statement| match &statement.kind {
|
||||
StmtKind::Declaration(declaration) => Some(declaration),
|
||||
_ => None,
|
||||
@@ -83,32 +119,43 @@ pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result<SymbolTable,
|
||||
.collect(),
|
||||
AstRoot::Expression(_) => Vec::new(),
|
||||
};
|
||||
// 展平顶层函数及类方法,保留原声明顺序供统一分析。
|
||||
let functions: Vec<&Function> = declarations
|
||||
.iter()
|
||||
// 类本身不生成函数体作用域,其 methods 逐个参与分析。
|
||||
.flat_map(|declaration| match &declaration.kind {
|
||||
DeclKind::Function(function) => vec![function],
|
||||
DeclKind::Class(class) => class.methods.iter().collect(),
|
||||
})
|
||||
.collect();
|
||||
// 按根类型选作用域种类及用于代码诊断的合成名字。
|
||||
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 declaration in &declarations {
|
||||
// 取得声明的名字和精确跨度,重名错误定位到名字本身。
|
||||
let name = declaration_name(declaration);
|
||||
declare(&mut declared, &name.text, name.span)?;
|
||||
globals.insert(name.text.clone());
|
||||
root_scope.intern(&name.text);
|
||||
}
|
||||
// 遍历根级声明;首轮注册名称,次轮检查类结构,保证函数体分析时名字已齐全。
|
||||
for declaration in &declarations {
|
||||
// 类声明额外检查基类名和成员结构,普通函数跳过这一段。
|
||||
if let DeclKind::Class(class) = &declaration.kind {
|
||||
// 未指定基类时默认 Object;这里只确认名称合法,实际 Type 在执行时解析。
|
||||
let base_name = class
|
||||
.base
|
||||
.as_ref()
|
||||
.map_or("Object", |base| base.text.as_str());
|
||||
// 基类必须是宿主或源码已声明的名称,不隐式引入未知全局。
|
||||
if !globals.contains(base_name) {
|
||||
return Err(error(
|
||||
class.span,
|
||||
@@ -116,12 +163,16 @@ pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result<SymbolTable,
|
||||
));
|
||||
}
|
||||
root_scope.intern(base_name);
|
||||
// 字段与方法共用类内去重集合,不能跨两类成员重复名称。
|
||||
let mut members = BTreeSet::new();
|
||||
// 字段名参与类成员去重;类型名留给后续代码签名/运行时类型协议处理。
|
||||
for field in &class.fields {
|
||||
declare(&mut members, &field.name.text, field.span)?;
|
||||
}
|
||||
// 方法名也参与去重,且必须显式接收第一个 self 参数。
|
||||
for method in &class.methods {
|
||||
declare(&mut members, &method.name.text, method.span)?;
|
||||
// 检查第一个参数确实存在且名为 self,不自动插入接收者。
|
||||
if method
|
||||
.parameters
|
||||
.first()
|
||||
@@ -137,37 +188,50 @@ pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result<SymbolTable,
|
||||
}
|
||||
// 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) => {
|
||||
// analyze 开头已保证只有一条,仍按列表结构遍历 AST。
|
||||
for statement in &input.statements {
|
||||
// 区分绑定、赋值、成员更新与控制流,不能把 return 当成交互结果。
|
||||
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::Assignment(assignment) => {
|
||||
root_scope.expression(&assignment.value, &globals)?;
|
||||
globals.insert(assignment.name.text.clone());
|
||||
root_scope.intern(&assignment.name.text);
|
||||
}
|
||||
// 成员写入分析接收对象与右值,并把属性名加入名字表。
|
||||
StmtKind::MemberAssignment(assignment) => {
|
||||
root_scope.expression(&assignment.object, &globals)?;
|
||||
root_scope.expression(&assignment.value, &globals)?;
|
||||
root_scope.intern(&assignment.name.text);
|
||||
}
|
||||
// 声明已经预登记且函数体已统一分析,不能重复分析。
|
||||
StmtKind::Declaration(_) => {}
|
||||
// 交互表达式只需验证读取,不创建新绑定。
|
||||
StmtKind::Expression(expression) => {
|
||||
root_scope.expression(expression, &globals)?
|
||||
}
|
||||
// 复合语句先收集块内名称到根名字表,再复用语句控制流分析。
|
||||
StmtKind::If(_) | StmtKind::While(_) => {
|
||||
// 借一个节点组成切片,复用预收集和控制流分析,不克隆 AST。
|
||||
let statements = std::slice::from_ref(statement);
|
||||
collect_locals(
|
||||
statements,
|
||||
@@ -177,6 +241,7 @@ pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result<SymbolTable,
|
||||
globals.extend(root_scope.scope.layout.names.iter().cloned());
|
||||
root_scope.statements(statements, &globals)?;
|
||||
}
|
||||
// 根交互语句没有函数帧,拒绝 return。
|
||||
StmtKind::Return(_) => {
|
||||
return Err(error(statement.span, "return outside a function"));
|
||||
}
|
||||
@@ -190,7 +255,9 @@ pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result<SymbolTable,
|
||||
})
|
||||
}
|
||||
|
||||
/// 顶层、参数、局部及类成员共用的去重检查;仅向传入集合登记,不分配布局索引。
|
||||
fn declare(names: &mut BTreeSet<String>, name: &str, span: Span) -> Result<(), Diagnostic> {
|
||||
// 空名或重复插入失败立即报错;成功 insert 同时完成名称登记。
|
||||
if name.is_empty() || !names.insert(name.to_owned()) {
|
||||
return Err(error(
|
||||
span,
|
||||
@@ -200,9 +267,13 @@ fn declare(names: &mut BTreeSet<String>, name: &str, span: Span) -> Result<(), D
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// analyze 为每个顶层函数/类方法调用;参数先占局部槽,再收集全体局部名并按控制流检查初始化。
|
||||
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
|
||||
@@ -226,47 +297,63 @@ fn function_scope(function: &Function, globals: &BTreeSet<String>) -> Result<Sco
|
||||
Ok(builder.scope)
|
||||
}
|
||||
|
||||
/// function_scope 或交互复合语句调用;递归预分配函数级名称,不在此推断执行后是否已初始化。
|
||||
/// let 重复声明报错,普通赋值首次出现才追加槽,if/while 块共享同一组名称。
|
||||
fn collect_locals(
|
||||
statements: &[frontend::ast::Stmt],
|
||||
locals: &mut BTreeSet<String>,
|
||||
layout: &mut Vec<String>,
|
||||
) -> Result<(), Diagnostic> {
|
||||
// 依源码顺序遍历当前语句序列,递归进入块但不创建块级局部作用域。
|
||||
for statement in statements {
|
||||
// 预收集阶段仅关心能引入名称的语句或嵌套声明拒绝规则。
|
||||
match &statement.kind {
|
||||
// let 必须是首次声明,即使出现在不同分支也不能重复同一函数级名。
|
||||
StmtKind::Binding(b) => {
|
||||
declare(locals, &b.name.text, b.name.span)?;
|
||||
layout.push(b.name.text.clone());
|
||||
}
|
||||
// 赋值允许写已有名;仅第一次出现时追加一个布局槽。
|
||||
StmtKind::Assignment(a) => {
|
||||
// 首次出现的赋值目标才分配槽;后续普通赋值复用原槽。
|
||||
if locals.insert(a.name.text.clone()) {
|
||||
layout.push(a.name.text.clone());
|
||||
}
|
||||
}
|
||||
// 两侧的名称都预分配;这里不决定分支之后是否能读取它们。
|
||||
StmtKind::If(b) => {
|
||||
collect_locals(&b.body.statements, locals, layout)?;
|
||||
// 预收集 else 中的局部名,与 then 和外围共享同一函数级布局。
|
||||
if let Some(other) = &b.otherwise {
|
||||
collect_locals(&other.statements, locals, layout)?;
|
||||
}
|
||||
}
|
||||
// 循环体也预分配函数级名字,但执行零次的情况由后续初始化分析处理。
|
||||
StmtKind::While(b) => collect_locals(&b.body.statements, locals, layout)?,
|
||||
// 源码闭包/嵌套类尚未支持,不生成看似合法但缺捕获语义的布局。
|
||||
StmtKind::Declaration(_) => {
|
||||
return Err(error(
|
||||
statement.span,
|
||||
"nested declarations are not compiled yet",
|
||||
));
|
||||
}
|
||||
// 不引入名字的表达式、返回和成员写入留给后续语句分析。
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 分析单个代码单元的可变状态;Scope 是输出,initialized 是当前可达路径上确定初始化的名称。
|
||||
struct Builder {
|
||||
// 正在构造的作用域,最终移交 SymbolTable。
|
||||
scope: Scope,
|
||||
// 仅控制流中保证赋值的局部名;分支取交集,循环出口恢复进入前集合。
|
||||
initialized: BTreeSet<String>,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
/// analyze/function_scope 创建空分析器;源码身份和布局初始值在这里统一设置。
|
||||
fn new(kind: ScopeKind, name: &str, span: Span) -> Self {
|
||||
Self {
|
||||
scope: Scope {
|
||||
@@ -280,7 +367,9 @@ impl Builder {
|
||||
}
|
||||
}
|
||||
|
||||
/// 分析名称/属性/声明时调用;复用或追加名字表项,保证生成器查索引时必能找到。
|
||||
fn intern(&mut self, name: &str) -> usize {
|
||||
// 按文本查找已登记名字;命中时保留稳定索引。
|
||||
if let Some(index) = self
|
||||
.scope
|
||||
.layout
|
||||
@@ -290,6 +379,7 @@ impl Builder {
|
||||
{
|
||||
index
|
||||
} else {
|
||||
// 没有现有名字索引才追加,保留此前已记录的索引。
|
||||
self.scope.layout.names.push(name.to_owned());
|
||||
self.scope.layout.names.len() - 1
|
||||
}
|
||||
@@ -297,75 +387,100 @@ impl Builder {
|
||||
|
||||
/// Function-scoped locals; branches merge definite initialization. A loop
|
||||
/// may execute zero times, so its writes do not initialize later reads.
|
||||
/// 函数体与复合语句递归调用;返回本语句序列是否所有可达路径都已 return。
|
||||
fn statements(
|
||||
&mut self,
|
||||
statements: &[frontend::ast::Stmt],
|
||||
globals: &BTreeSet<String>,
|
||||
) -> Result<bool, Diagnostic> {
|
||||
// 表示当前语句序列是否所有可达路径都已返回,供后继不可达检查使用。
|
||||
let mut returned = false;
|
||||
// 按源码顺序推进当前可达路径的初始化状态,检查每条后继语句。
|
||||
for statement in statements {
|
||||
// 前面的 return 或双侧必返分支已终止所有路径,后继语句报不可达。
|
||||
if returned {
|
||||
return Err(error(
|
||||
statement.span,
|
||||
"statement after return is unreachable",
|
||||
));
|
||||
}
|
||||
// 按控制流检查实际读取与初始化,不重新分配预收集过的局部槽。
|
||||
match &statement.kind {
|
||||
// 先分析右值,之后才把本局部标记已初始化,防止 let x = x 读取自身未绑定槽。
|
||||
StmtKind::Binding(b) => {
|
||||
self.expression(&b.value, globals)?;
|
||||
self.initialized.insert(b.name.text.clone());
|
||||
// 根作用域写入全局而非局部槽,需要为 StoreGlobal 登记名字。
|
||||
if self.scope.kind != ScopeKind::Function {
|
||||
self.intern(&b.name.text);
|
||||
}
|
||||
}
|
||||
// 普通赋值同样先检查右值,再令目标在当前路径上已初始化。
|
||||
StmtKind::Assignment(a) => {
|
||||
self.expression(&a.value, globals)?;
|
||||
self.initialized.insert(a.name.text.clone());
|
||||
// 根作用域写入全局而非局部槽,需要为 StoreGlobal 登记名字。
|
||||
if self.scope.kind != ScopeKind::Function {
|
||||
self.intern(&a.name.text);
|
||||
}
|
||||
}
|
||||
// 检查对象和右值读取,并为后续 StoreAttr 登记属性名字。
|
||||
StmtKind::MemberAssignment(a) => {
|
||||
self.expression(&a.object, globals)?;
|
||||
self.expression(&a.value, globals)?;
|
||||
self.intern(&a.name.text);
|
||||
}
|
||||
StmtKind::Expression(e) => self.expression(e, globals)?,
|
||||
// 只有函数可返回;可选返回表达式先接受同样的名称检查。
|
||||
StmtKind::Return(r) => {
|
||||
// 只有函数有 return 语义,根控制流块也不能绕过此限制。
|
||||
if self.scope.kind != ScopeKind::Function {
|
||||
return Err(error(statement.span, "return outside a function"));
|
||||
}
|
||||
// 无返回表达式由生成器补 Nil;有表达式时先检查其中所有名称读取。
|
||||
if let Some(e) = &r.value {
|
||||
self.expression(e, globals)?;
|
||||
}
|
||||
returned = true;
|
||||
}
|
||||
// 先分析条件,再以相同入口初始化集合分别分析两侧。
|
||||
StmtKind::If(b) => {
|
||||
self.expression(&b.condition, globals)?;
|
||||
// 保存控制流入口的确定初始化集合,另一分支及循环出口需要恢复它。
|
||||
let before = self.initialized.clone();
|
||||
// 分析 then 分支并取得其是否必定返回的标志。
|
||||
let yes_return = self.statements(&b.body.statements, globals)?;
|
||||
// 保存 then 出口状态,恢复入口后再单独分析 else。
|
||||
let yes = self.initialized.clone();
|
||||
self.initialized = before;
|
||||
// 有 else 时分析其状态;无 else 视为存在不返回的空路径。
|
||||
let no_return = if let Some(other) = &b.otherwise {
|
||||
self.statements(&other.statements, globals)?
|
||||
} else {
|
||||
false
|
||||
};
|
||||
// then 已返回则只保留 else 的继续路径;否则按下方规则合并。
|
||||
self.initialized = if yes_return {
|
||||
self.initialized.clone()
|
||||
} else if no_return {
|
||||
// 只有 else 返回时保留 then 的继续路径。
|
||||
yes
|
||||
} else {
|
||||
// 两边都能继续时,只保留双方都保证初始化的名称。
|
||||
yes.intersection(&self.initialized).cloned().collect()
|
||||
};
|
||||
// 只有两侧都必返时整个 if 才让后继语句不可达。
|
||||
returned = yes_return && no_return;
|
||||
}
|
||||
// 检查条件及循环体,但循环可能零次执行,出口恢复入口初始化状态。
|
||||
StmtKind::While(b) => {
|
||||
self.expression(&b.condition, globals)?;
|
||||
// 保存控制流入口的确定初始化集合,另一分支及循环出口需要恢复它。
|
||||
let before = self.initialized.clone();
|
||||
self.statements(&b.body.statements, globals)?;
|
||||
self.initialized = before;
|
||||
}
|
||||
// 函数体/复合语句不支持嵌套声明,不能在这里隐式生成闭包作用域。
|
||||
StmtKind::Declaration(_) => {
|
||||
return Err(error(
|
||||
statement.span,
|
||||
@@ -377,13 +492,17 @@ impl Builder {
|
||||
Ok(returned)
|
||||
}
|
||||
|
||||
/// analyze/statements 及自身递归调用;检查名称已声明/已初始化,记录读取绑定并登记属性名字。
|
||||
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
|
||||
@@ -391,6 +510,7 @@ impl Builder {
|
||||
.iter()
|
||||
.position(|local| local == name)
|
||||
{
|
||||
// 槽已预分配不代表当前路径已经赋值,未确定初始化必须拒绝读取。
|
||||
if !self.initialized.contains(name) {
|
||||
return Err(error(
|
||||
expression.span,
|
||||
@@ -399,33 +519,41 @@ impl Builder {
|
||||
}
|
||||
NameBinding::Local(index)
|
||||
} else if globals.contains(name) {
|
||||
// 没有同名局部时,宿主或源码登记过的全局名才能读取。
|
||||
NameBinding::Global(self.intern(name))
|
||||
} else {
|
||||
return Err(error(expression.span, format!("undefined name: {name}")));
|
||||
};
|
||||
// 为此名字及源码位置保存绑定,codegen 据此选择 LoadFast 或 LoadGlobal。
|
||||
self.scope.references.push(NameUse {
|
||||
name: name.clone(),
|
||||
span: expression.span,
|
||||
binding,
|
||||
});
|
||||
}
|
||||
// 一元及 await 都只分析操作数;是否允许实际等待由执行器处理。
|
||||
ExprKind::Unary { operand, .. } | ExprKind::Await(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)?;
|
||||
}
|
||||
}
|
||||
// 字面量无变量读取;Symbol 是否可生成运行时值由 codegen 再拒绝。
|
||||
ExprKind::Number(_) | ExprKind::String(_) | ExprKind::Symbol(_) => {}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! compile_ast 的字节码生成阶段:消费 analysis 的稳定布局,先产生逻辑指令,再解析物理跳转距离。
|
||||
//! 编译产物没有 VM 对象引用;load 才递归分配子代码并替换常量占位,交由宿主执行。
|
||||
use endor_core::{
|
||||
CodeLayout, DecodedInstruction, EndorCode, EndorNumber, Instruction, ObjectRef, Opcode, Value,
|
||||
Vm, VmError,
|
||||
@@ -10,30 +12,40 @@ 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)]
|
||||
/// 宿主和编译集成测试取得的已验证模板,可多次加载到不同 VM,不跨堆共享 ObjectRef。
|
||||
pub struct CompiledCode {
|
||||
// 当前代码单元,嵌套代码位置先放 Nil;load 时构造目标 VM 专属的常量表。
|
||||
code: EndorCode,
|
||||
// (父常量索引, 子模板);Emitter 记录待替换位置,CompiledCode::load 递归装入。
|
||||
children: Vec<(usize, CompiledCode)>,
|
||||
}
|
||||
|
||||
impl CompiledCode {
|
||||
/// 宿主/测试检查已解码指令及偏移,借用模板,不执行代码;子代码常量此时仍为 Nil 占位。
|
||||
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 source_signature(&self) -> Option<&endor_core::SourceSignature> {
|
||||
self.code.source_signature()
|
||||
}
|
||||
/// 宿主/测试查看来自 SourceUnit 的来源标识,装载后用于回溯定位。
|
||||
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)
|
||||
}
|
||||
@@ -41,15 +53,25 @@ impl CompiledCode {
|
||||
/// 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.
|
||||
/// 宿主/测试显式装入目标 VM;成功返回一个拥有代码根,调用者执行完成后负责 release。
|
||||
/// 已分配子根无论后续成功或失败都在外层归还,父代码成功分配时已有自己的常量内部边。
|
||||
/// Tessera Candidate 或开发者页面安装器持有这份代码根;本方法不运行模块、不安装 globals,
|
||||
/// 每次 load 重新分配目标堆身份,模板可复用,调用者仍需显式执行并清理返回值。
|
||||
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();
|
||||
// 递归装入每个子模板,失败时先前成功得到的根仍在 roots 中可清理。
|
||||
for (index, child) in &self.children {
|
||||
// 子装载成功返回一个拥有根,父对象尚未分配,必须先临时保存它。
|
||||
let reference = child.load(vm)?;
|
||||
roots.push(reference);
|
||||
constants[*index] = Value::Object(reference);
|
||||
}
|
||||
// 用目标 VM 的子代码常量重新构造并验证代码,再恢复原位置元数据。
|
||||
let mut code = EndorCode::with_layout(
|
||||
self.code.bytecode().to_vec(),
|
||||
constants,
|
||||
@@ -60,11 +82,13 @@ impl CompiledCode {
|
||||
self.code.filename(),
|
||||
self.code.first_line(),
|
||||
);
|
||||
// 函数签名随模板一同复制,保证装载后仍有类型注解/方法归属信息。
|
||||
if let Some(signature) = self.code.source_signature() {
|
||||
code = code.with_source_signature(signature.clone())?;
|
||||
}
|
||||
vm.new_code(code)
|
||||
})();
|
||||
// 父代码已取得内部边或装载失败后,临时子根都必须释放一次。
|
||||
for reference in roots {
|
||||
vm.release(reference)
|
||||
.expect("loader owns each temporary code root");
|
||||
@@ -73,18 +97,23 @@ impl CompiledCode {
|
||||
}
|
||||
}
|
||||
|
||||
/// 生成、装载前验证或编码失败的公共包装,附上当前源码跨度并标为 Compilation。
|
||||
fn error(span: Span, message: impl Into<String>) -> Diagnostic {
|
||||
diagnostic(DiagnosticKind::Compilation, span, message)
|
||||
}
|
||||
|
||||
/// compile_ast 在名称分析成功后调用;按根模式生成最终返回协议并收束为已验证模板。
|
||||
pub(crate) fn generate(
|
||||
root: &AstRoot,
|
||||
source: &SourceUnit,
|
||||
symbols: &SymbolTable,
|
||||
) -> Result<CompiledCode, Diagnostic> {
|
||||
// 根生成器借用已经分析好的根布局,模式分支只决定根的指令及返回值。
|
||||
let mut emitter = Emitter::new(&symbols.root, source);
|
||||
// 模块生成声明并返回 Nil,表达式返回其值,交互语句返回表达式或 Nil。
|
||||
match root {
|
||||
AstRoot::Module(program) => {
|
||||
// 源码声明依原顺序生成,函数调用的前向名称由执行完模块后的环境提供。
|
||||
for declaration in &program.declarations {
|
||||
emitter.declaration(declaration, symbols)?;
|
||||
}
|
||||
@@ -96,7 +125,9 @@ pub(crate) fn generate(
|
||||
emitter.simple(Opcode::ReturnValue);
|
||||
}
|
||||
AstRoot::Interactive(input) => {
|
||||
// analyze 已限制交互输入只有一条;这里复用 AST 列表结构。
|
||||
for statement in &input.statements {
|
||||
// 交互表达式保留结果作为根返回值,其他语句执行后补 Nil。
|
||||
if let StmtKind::Expression(expression) = &statement.kind {
|
||||
emitter.expression(expression)?;
|
||||
} else {
|
||||
@@ -110,16 +141,24 @@ pub(crate) fn generate(
|
||||
emitter.finish()
|
||||
}
|
||||
|
||||
/// 单个代码单元的生成状态;借用分析布局与源码,持有逻辑指令、常量及尚未解析的跳转。
|
||||
struct Emitter<'a> {
|
||||
// 当前根/函数作用域,提供名字、槽索引及源跨度。
|
||||
scope: &'a Scope,
|
||||
// 代码来源和文本快照,finish 据此设置文件身份与起始行。
|
||||
source: &'a SourceUnit,
|
||||
// 尚未编码扩展前缀的逻辑指令;索引不能直接当作物理 code-unit 偏移。
|
||||
instructions: Vec<Instruction>,
|
||||
// 按出现顺序追加常量;嵌套代码在 load 前使用 Nil 占位。
|
||||
constants: Vec<Value>,
|
||||
// (跳转逻辑指令索引, 目标逻辑边界索引),finish 根据编码宽度计算相对距离。
|
||||
jumps: Vec<(usize, usize)>,
|
||||
// (父常量索引, 子模板);Emitter 记录待替换位置,CompiledCode::load 递归装入。
|
||||
children: Vec<(usize, CompiledCode)>,
|
||||
}
|
||||
|
||||
impl<'a> Emitter<'a> {
|
||||
/// generate/function_value 创建当前代码单元的空生成器,各函数独享指令及常量列表。
|
||||
fn new(scope: &'a Scope, source: &'a SourceUnit) -> Self {
|
||||
Self {
|
||||
scope,
|
||||
@@ -131,23 +170,29 @@ impl<'a> Emitter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 各生成分支追加带参数指令;usize 索引/数量必须能装入 VM 的 u32 操作数。
|
||||
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));
|
||||
}
|
||||
|
||||
/// 表达式、声明和补 Nil 路径共用;追加常量并发出 LoadConst,执行时压入值。
|
||||
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
|
||||
@@ -157,24 +202,31 @@ impl<'a> Emitter<'a> {
|
||||
.expect("analysis interns every emitted name")
|
||||
}
|
||||
|
||||
/// generate/statement 调用,把顶层函数或类构造成运行时值并写入对应全局名。
|
||||
fn declaration(&mut self, declaration: &Decl, symbols: &SymbolTable) -> Result<(), Diagnostic> {
|
||||
// 函数直接构造值,类还需按 BuildClass 协议压入名字、基类、字段表与方法表。
|
||||
match &declaration.kind {
|
||||
// 先创建函数值再 StoreGlobal,函数体此时尚未调用。
|
||||
DeclKind::Function(function) => {
|
||||
self.function_value(function, symbols, None)?;
|
||||
self.emit(Opcode::StoreGlobal, self.name_index(&function.name.text))
|
||||
}
|
||||
// 构造类元数据;父类名来自分析阶段校验过的名字表。
|
||||
DeclKind::Class(class) => {
|
||||
self.constant(Value::Text(class.name.text.clone()))?;
|
||||
// 未显式指定时查全局 Object,真正的父类型对象由运行时解析。
|
||||
let base = class
|
||||
.base
|
||||
.as_ref()
|
||||
.map_or("Object", |base| base.text.as_str());
|
||||
self.emit(Opcode::LoadGlobal, self.name_index(base))?;
|
||||
// 每字段压入名称和类型名文本,随后 BuildMap 汇总为字段注解映射。
|
||||
for field in &class.fields {
|
||||
self.constant(Value::Text(field.name.text.clone()))?;
|
||||
self.constant(Value::Text(field.type_name.text.clone()))?;
|
||||
}
|
||||
self.emit(Opcode::BuildMap, class.fields.len())?;
|
||||
// 每方法压入名称及源码函数值,签名额外记录当前类名。
|
||||
for method in &class.methods {
|
||||
self.constant(Value::Text(method.name.text.clone()))?;
|
||||
self.function_value(method, symbols, Some(&class.name.text))?;
|
||||
@@ -186,21 +238,27 @@ impl<'a> Emitter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// declaration 为普通函数或类方法调用;生成子模板、附源码类型签名并发出 MakeFunction。
|
||||
/// owner 仅类方法传入类名,供运行时签名校验使用;当前源码函数没有默认值与闭包捕获。
|
||||
fn function_value(
|
||||
&mut self,
|
||||
function: &frontend::ast::Function,
|
||||
symbols: &SymbolTable,
|
||||
owner: Option<&str>,
|
||||
) -> Result<(), Diagnostic> {
|
||||
// 按源码跨度及名称定位函数分析结果,避免不同类的同名方法混用布局。
|
||||
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);
|
||||
// 按源码顺序生成函数体语句,语义和不可达错误已由 analysis 检查。
|
||||
for statement in &function.body.statements {
|
||||
body.statement(statement, symbols)?;
|
||||
}
|
||||
// 末条语句不是显式 return 时补隐式 Nil 返回;末条 if 即使双侧返回也会补兜底出口。
|
||||
if !matches!(
|
||||
function
|
||||
.body
|
||||
@@ -212,7 +270,9 @@ impl<'a> Emitter<'a> {
|
||||
body.constant(Value::Nil)?;
|
||||
body.simple(Opcode::ReturnValue);
|
||||
}
|
||||
// 先完成子代码跳转及验证,再把参数/返回类型注解附到模板。
|
||||
let mut child = body.finish()?;
|
||||
// 签名保留源码注解名,运行时在环境中解析类型;类方法额外记录 owner。
|
||||
child.code = child
|
||||
.code
|
||||
.with_source_signature(endor_core::SourceSignature {
|
||||
@@ -225,16 +285,23 @@ impl<'a> Emitter<'a> {
|
||||
method_owner: owner.map(str::to_owned),
|
||||
})
|
||||
.map_err(|failure| error(function.span, failure.to_string()))?;
|
||||
// 下一常量槽位;函数模板登记和常量加载都必须使用追加前的长度。
|
||||
let index = self.constants.len();
|
||||
// 子模板与即将追加的 Nil 常量槽成对保存,load 才替换为实际代码句柄。
|
||||
self.children.push((index, child));
|
||||
// 为子代码保留常量槽,load 时用已分配的托管代码对象替换 Nil。
|
||||
self.constant(Value::Nil)?; // Replaced by a managed code object during load().
|
||||
// 源码函数当前没有默认参数或自由 Cell,按零默认值、零捕获构造。
|
||||
self.instructions.push(Instruction::make_function(0, 0));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// statement 的 let/赋值共用;先生成右值,再按作用域写局部槽或全局名。
|
||||
fn store_binding(&mut self, name: &str, value: &Expr) -> Result<(), Diagnostic> {
|
||||
self.expression(value)?;
|
||||
// 函数级目标写 StoreFast,其余根写 StoreGlobal,右值已经在栈顶。
|
||||
if self.scope.kind == ScopeKind::Function {
|
||||
// 从预分配局部表取得目标槽,分析阶段保证所有绑定和赋值目标存在。
|
||||
let index = self
|
||||
.scope
|
||||
.layout
|
||||
@@ -249,48 +316,65 @@ impl<'a> Emitter<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// generate/function_value 及块递归调用,维护语句栈平衡,并记录待 finish 解析的跳转目标。
|
||||
fn statement(&mut self, statement: &Stmt, symbols: &SymbolTable) -> Result<(), Diagnostic> {
|
||||
// 语句需消费自身临时值;return 和控制流分别生成出口或跳转。
|
||||
match &statement.kind {
|
||||
// 条件为假跳到 else 起点,then 结束无条件越过 else。
|
||||
StmtKind::If(branch) => {
|
||||
self.expression(&branch.condition)?;
|
||||
// 保存条件跳转的逻辑索引,当前先用零操作数占位。
|
||||
let conditional = self.instructions.len();
|
||||
self.emit(Opcode::PopJumpIfFalse, 0)?;
|
||||
// 递归生成 then 语句,嵌套跳转同样追加到当前生成器。
|
||||
for statement in &branch.body.statements {
|
||||
self.statement(statement, symbols)?;
|
||||
}
|
||||
// 记录 then 尾部无条件跳转位置,稍后指向整个 if 之后。
|
||||
let end = self.instructions.len();
|
||||
self.emit(Opcode::JumpForward, 0)?;
|
||||
self.jumps.push((conditional, self.instructions.len()));
|
||||
// 存在 else 才生成其代码;无 else 时两个出口可指向同一边界。
|
||||
if let Some(otherwise) = &branch.otherwise {
|
||||
// else 同样保持语句栈平衡,结束后接 if 的共同后继。
|
||||
for statement in &otherwise.statements {
|
||||
self.statement(statement, symbols)?;
|
||||
}
|
||||
}
|
||||
self.jumps.push((end, self.instructions.len()));
|
||||
}
|
||||
// 每次回到条件开头,条件为假退出;记录两个逻辑跳转后交 finish 解析。
|
||||
StmtKind::While(loop_) => {
|
||||
// 保存条件求值起点,回边必须重算条件而非仅回到循环体。
|
||||
let start = self.instructions.len();
|
||||
self.expression(&loop_.condition)?;
|
||||
// 保存条件失败的占位跳转位置。
|
||||
let exit = self.instructions.len();
|
||||
self.emit(Opcode::PopJumpIfFalse, 0)?;
|
||||
// 生成循环体,再生成回到条件开头的后向跳转。
|
||||
for statement in &loop_.body.statements {
|
||||
self.statement(statement, symbols)?;
|
||||
}
|
||||
// 回边指令的逻辑位置,用于计算从该指令末端到 start 的距离。
|
||||
let back = self.instructions.len();
|
||||
self.emit(Opcode::JumpBackward, 0)?;
|
||||
self.jumps.push((back, start));
|
||||
self.jumps.push((exit, self.instructions.len()));
|
||||
}
|
||||
StmtKind::Binding(binding) => self.store_binding(&binding.name.text, &binding.value)?,
|
||||
// 普通赋值与 let 共用存储规则;分析阶段已确定目标槽或全局名。
|
||||
StmtKind::Assignment(assignment) => {
|
||||
self.store_binding(&assignment.name.text, &assignment.value)?
|
||||
}
|
||||
// StoreAttr 协议要求先压右值,再压接收对象,最后由指令消费两者。
|
||||
StmtKind::MemberAssignment(assignment) => {
|
||||
self.expression(&assignment.value)?;
|
||||
self.expression(&assignment.object)?;
|
||||
self.emit(Opcode::StoreAttr, self.name_index(&assignment.name.text))?;
|
||||
}
|
||||
// 返回表达式或补 Nil,然后 ReturnValue 将值交给调用方。
|
||||
StmtKind::Return(statement) => {
|
||||
// 显式返回值正常求值;无表达式的 return 需要先压入 Nil。
|
||||
if let Some(value) = &statement.value {
|
||||
self.expression(value)?;
|
||||
} else {
|
||||
@@ -298,6 +382,7 @@ impl<'a> Emitter<'a> {
|
||||
}
|
||||
self.simple(Opcode::ReturnValue);
|
||||
}
|
||||
// 语句位置不保留结果,PopTop 使后续语句开始时栈深恢复。
|
||||
StmtKind::Expression(expression) => {
|
||||
self.expression(expression)?;
|
||||
self.simple(Opcode::PopTop);
|
||||
@@ -307,40 +392,51 @@ impl<'a> Emitter<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 根、语句或子表达式递归调用;成功路径把一个值留栈,具体对象协议在 VM 执行时处理。
|
||||
fn expression(&mut self, expression: &Expr) -> Result<(), Diagnostic> {
|
||||
// 按 AST 种类发出求值序列,每个成功分支最终留一个值。
|
||||
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");
|
||||
// 局部槽用 LoadFast,全局名字索引用 LoadGlobal。
|
||||
let (opcode, index) = match reference.binding {
|
||||
NameBinding::Local(index) => (Opcode::LoadFast, index),
|
||||
NameBinding::Global(index) => (Opcode::LoadGlobal, index),
|
||||
};
|
||||
self.emit(opcode, index)?;
|
||||
}
|
||||
// 按 VM 调用布局压被调对象、Null 接收者哨兵及各实参;Call 使用实参数量。
|
||||
ExprKind::Call { callee, arguments } => {
|
||||
self.expression(callee)?;
|
||||
self.simple(Opcode::PushNull);
|
||||
// 参数按源码顺序求值,每个结果在栈上保留到 Call 消费。
|
||||
for argument in arguments {
|
||||
self.expression(argument)?;
|
||||
}
|
||||
self.emit(Opcode::Call, arguments.len())?;
|
||||
}
|
||||
// 仅求值一次等待对象,再由 Await 指令负责停放/轮询;生成器不实现调度。
|
||||
ExprKind::Await(operand) => {
|
||||
self.expression(operand)?;
|
||||
self.simple(Opcode::Await);
|
||||
}
|
||||
// 先求接收对象,再用分析已登记的属性名字索引读取。
|
||||
ExprKind::Member { object, name } => {
|
||||
self.expression(object)?;
|
||||
self.emit(Opcode::LoadAttr, self.name_index(&name.text))?;
|
||||
}
|
||||
// 纯取负字面量直接合并精确拼写,否则生成操作数及运行时 UnaryNegate。
|
||||
ExprKind::Unary { operand, .. } => {
|
||||
// 只折叠数值及连续取负,避免先把超长十进制转换为浮点。
|
||||
if let Some(spelling) = signed_literal(expression, false) {
|
||||
self.number(&spelling, expression.span)?;
|
||||
} else {
|
||||
@@ -348,6 +444,7 @@ impl<'a> Emitter<'a> {
|
||||
self.simple(Opcode::UnaryNegate);
|
||||
}
|
||||
}
|
||||
// 左右操作数依次压栈,再由算术或比较指令消费两个值并返回一个值。
|
||||
ExprKind::Binary {
|
||||
operation,
|
||||
left,
|
||||
@@ -355,11 +452,13 @@ impl<'a> Emitter<'a> {
|
||||
} => {
|
||||
self.expression(left)?;
|
||||
self.expression(right)?;
|
||||
// 算术使用 BinaryArithmetic 子操作码;其余已知二元操作编码为比较。
|
||||
match operation {
|
||||
BinaryOp::Add => self.emit(Opcode::BinaryArithmetic, 0)?,
|
||||
BinaryOp::Subtract => self.emit(Opcode::BinaryArithmetic, 1)?,
|
||||
BinaryOp::Multiply => self.emit(Opcode::BinaryArithmetic, 2)?,
|
||||
BinaryOp::Divide => self.emit(Opcode::BinaryArithmetic, 3)?,
|
||||
// 算术种类已在前四个分支处理,此分支只应剩余比较操作。
|
||||
_ => self
|
||||
.instructions
|
||||
.push(Instruction::compare(match operation {
|
||||
@@ -373,6 +472,7 @@ impl<'a> Emitter<'a> {
|
||||
})),
|
||||
}
|
||||
}
|
||||
// 前端能表示符号字面量,但当前运行时无对应值,明确拒绝生成。
|
||||
ExprKind::Symbol(_) => {
|
||||
return Err(error(
|
||||
expression.span,
|
||||
@@ -383,7 +483,9 @@ impl<'a> Emitter<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 数值字面量和已合并符号的字面量共用,直接从十进制拼写构造精确数值,不经过 f64。
|
||||
fn number(&mut self, spelling: &str, span: Span) -> Result<(), Diagnostic> {
|
||||
// 当前源码数字不含指数,按小数点选择精确十进制或整数解析;手工 AST 拼写也走此选择而不重分词。
|
||||
let value = if spelling.contains('.') {
|
||||
EndorNumber::decimal(spelling)
|
||||
} else {
|
||||
@@ -393,12 +495,17 @@ impl<'a> Emitter<'a> {
|
||||
self.constant(Value::Number(value))
|
||||
}
|
||||
|
||||
/// generate/function_value 结束当前单元时调用;迭代修正扩展指令宽度与相对跳转,再验证编码及布局。
|
||||
fn finish(mut self) -> Result<CompiledCode, Diagnostic> {
|
||||
// Labels refer to logical instructions. Resolve relative code-unit
|
||||
// offsets after EXTENDED_ARG expansion, to a fixed point.
|
||||
// 跳转距离变大会改变 EXTENDED_ARG 数量,最多迭代六十四轮求稳定布局。
|
||||
for attempt in 0..64 {
|
||||
// 记录每条逻辑指令起点及尾部边界的物理 code-unit 偏移,初始零。
|
||||
let mut offsets = vec![0_usize];
|
||||
// 按当前操作数宽度计算一到四个物理单元,包含必要的扩展前缀。
|
||||
for i in &self.instructions {
|
||||
// 操作数每多八位需要一个 EXTENDED_ARG 前缀,最终 opcode 本身占一个单元。
|
||||
let units = if i.argument <= 0xff {
|
||||
1
|
||||
} else if i.argument <= 0xffff {
|
||||
@@ -410,30 +517,38 @@ impl<'a> Emitter<'a> {
|
||||
};
|
||||
offsets.push(offsets.last().unwrap() + units);
|
||||
}
|
||||
// 本轮若任一跳转操作数改变,就需要重算可能变化的指令宽度。
|
||||
let mut changed = false;
|
||||
// 每个目标是逻辑边界,换算时必须使用本轮物理 offsets。
|
||||
for &(index, target) in &self.jumps {
|
||||
// 相对距离以完整跳转指令的末端为基准,后向与前向分别相减。
|
||||
let distance = if self.instructions[index].opcode == Opcode::JumpBackward {
|
||||
offsets[index + 1] - offsets[target]
|
||||
} else {
|
||||
offsets[target] - offsets[index + 1]
|
||||
};
|
||||
// 距离也受 u32 编码限制,超界向宿主返回诊断而非截断。
|
||||
let operand = u32::try_from(distance)
|
||||
.map_err(|_| error(self.scope.span, "jump exceeds u32"))?;
|
||||
changed |= self.instructions[index].argument != operand;
|
||||
self.instructions[index].argument = operand;
|
||||
}
|
||||
// 所有跳转操作数稳定时,当前宽度与距离自洽,可结束迭代。
|
||||
if !changed {
|
||||
break;
|
||||
}
|
||||
// 达到迭代上限仍变化时拒绝产物,不能输出未收敛的跳转。
|
||||
if attempt == 63 {
|
||||
return Err(error(self.scope.span, "jump layout did not converge"));
|
||||
}
|
||||
}
|
||||
// 从作用域跨度取起始行,无法定位时使用第一行作为回溯元数据。
|
||||
let line = self
|
||||
.scope
|
||||
.span
|
||||
.location(self.source.text())
|
||||
.map_or(1, |location| location.line);
|
||||
// 编码并验证控制流、栈和布局,错误归到当前作用域,再附源码身份。
|
||||
let code = EndorCode::from_instructions(
|
||||
&self.instructions,
|
||||
self.constants,
|
||||
@@ -448,17 +563,23 @@ impl<'a> Emitter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// expression 的一元负号分支调用;仅对纯数值和连续取负递归合并符号,其余表达式交运行时求值。
|
||||
/// negative 记录累计取负奇偶;每遇到一个 Negate 翻转,返回最终精确数字拼写。
|
||||
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,由 expression 生成实际运行时操作。
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,47 @@
|
||||
//! 宿主与集成测试使用的源码编译门面:输出不依赖 VM 堆的模板,显式加载后才得到代码对象。
|
||||
//! 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;
|
||||
// 生成可跨 VM 复用的代码模板;仅显式 load 时才分配目标 VM 的对象。
|
||||
mod codegen;
|
||||
|
||||
// 对宿主和测试公开名称解析结果,便于检查绑定与源码位置。
|
||||
pub use analysis::{NameBinding, NameUse, Scope, ScopeKind, SymbolTable, analyze};
|
||||
// 宿主取得模板后负责选择 VM 并调用 CompiledCode::load。
|
||||
pub use codegen::CompiledCode;
|
||||
|
||||
use frontend::ast::{AstDocument, AstRoot};
|
||||
use frontend::{Diagnostic, DiagnosticKind, ParseMode, SourceDiagnostic, SourceUnit, Span};
|
||||
|
||||
/// 宿主通过 compile_source/compile_ast 提供的编译选项;名称分析据此允许访问宿主全局或内置名。
|
||||
/// 这里只声明名称,宿主仍需在执行前向 VM 的环境字典提供实际值。
|
||||
/// 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 {
|
||||
// 编译期已知名称列表;analyze 与源码声明共同解析可用的全局名,不在此持有 VM 值。
|
||||
pub globals: Vec<String>,
|
||||
}
|
||||
|
||||
/// 宿主及 compiler 集成测试的源码编译入口:按指定模式解析,再校验 AST、解析名称并生成模板。
|
||||
/// source 提供文件身份与原始文本;mode 决定模块、表达式或交互根;options 声明额外宿主名称。
|
||||
/// 解析错误已有源码诊断;后续编译错误在这里附加同一源码的来源和行列位置,成功不执行任何 VM 指令。
|
||||
pub fn compile_source(
|
||||
source: &SourceUnit,
|
||||
mode: ParseMode,
|
||||
options: &CompileOptions,
|
||||
) -> Result<CompiledCode, SourceDiagnostic> {
|
||||
// 前端根据模式返回对应 AstRoot,解析失败立即把带源码信息的错误传给宿主。
|
||||
let root = frontend::parse_source_ast(source, mode)?;
|
||||
// AST 入口返回不含源码快照的 Diagnostic;在此统一补上 SourceUnit 上下文。
|
||||
compile_ast(&root, source, options).map_err(|error| source.annotate(error))
|
||||
}
|
||||
|
||||
/// compile_source、Tessera 源码计划/开发者页面安装器及已有 AST 的宿主/测试调用;
|
||||
/// 先验证结构与跨度,再分析名称,最后输出代码模板。
|
||||
/// 传入 source 必须是期望匹配的位置快照;跨度有效只说明位置在范围内,不能证明 AST 与文本语义相同。
|
||||
/// 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(
|
||||
@@ -33,8 +49,10 @@ pub fn compile_ast(
|
||||
source: &SourceUnit,
|
||||
options: &CompileOptions,
|
||||
) -> Result<CompiledCode, Diagnostic> {
|
||||
// 包装并校验 AST 文档结构,再检查节点跨度对当前 source 文本是否有效;不修改调用方的树。
|
||||
AstDocument::new(root.clone())
|
||||
.and_then(|document| document.validate_source(source.text()))
|
||||
// 文档结构/跨度验证错误归入编译错误,尚无可靠单一节点位置,使用默认跨度。
|
||||
.map_err(|error| {
|
||||
diagnostic(
|
||||
DiagnosticKind::Compilation,
|
||||
@@ -42,11 +60,16 @@ pub fn compile_ast(
|
||||
error.to_string(),
|
||||
)
|
||||
})?;
|
||||
// 收集并解析所有作用域的绑定及代码布局;错误会阻止生成阶段继续。
|
||||
let symbols = analyze(root, options)?;
|
||||
// 生成器按解析后的槽/名字索引编码,不在此加载模板或分配 VM 堆对象。
|
||||
codegen::generate(root, source, &symbols)
|
||||
}
|
||||
|
||||
/// 本模块的 AST 验证及 analysis/codegen 的错误辅助函数共用,统一构造不带源码快照的诊断。
|
||||
/// kind 区分语义/编译错误,span 标记位置,message 接受可转 String 的说明;语法期望项留空。
|
||||
fn diagnostic(kind: DiagnosticKind, span: Span, message: impl Into<String>) -> Diagnostic {
|
||||
// 编译器错误没有解析器的“期望 token”列表,源码上下文由上层 compile_source 统一附加。
|
||||
Diagnostic {
|
||||
kind,
|
||||
span,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Rust 测试框架驱动的源码类集成测试:从前端/编译器到 VM 的宿主构造、绑定方法、字段检查及 GC。
|
||||
//! execute/try_execute 按现有 globals 声明可用宿主名,加载临时代码,执行后统一归还代码根。
|
||||
use compiler::{CompileOptions, compile_source};
|
||||
use endor_core::{
|
||||
ContextNativeFunction, EndorTypeObject, NativeCallable, NativeContext, NativeResult,
|
||||
@@ -7,23 +9,30 @@ use endor_core::{ObjectRef, Value, Vm};
|
||||
use frontend::{EndorReader, ParseMode};
|
||||
|
||||
#[derive(Debug)]
|
||||
/// 原生实例载荷,WidgetFactory 分配;字符串是宿主标签,没有需要 trace 的托管对象边。
|
||||
struct NativeWidget(String);
|
||||
impl ObjectPayload for NativeWidget {
|
||||
/// Store::payload 的类型检查通过 Any 下转,测试据此读取原生标签。
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
/// 对象种类诊断入口使用稳定标签 widget,与测试载荷检查保持一致。
|
||||
fn kind_name(&self) -> &'static str {
|
||||
"widget"
|
||||
}
|
||||
/// Store 属性协议查询此开关;允许在原生实例上存放源码类声明的动态字段。
|
||||
fn allows_attributes(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// NativeTyped 构造策略使用的宿主回调;源码子类沿继承链复用它,但实际实例附着请求的子类。
|
||||
struct WidgetFactory;
|
||||
impl NativeCallable for WidgetFactory {
|
||||
/// VM 原生调用入口调用;参数依次为请求的 Type、宿主标签、声明字段值,结果由 NativeContext 返回协议接管。
|
||||
fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
|
||||
// 解构 NativeTyped 调用布局;类型对象与标签必须存在,其余切片作为字段参数。
|
||||
let [
|
||||
Value::Object(requested),
|
||||
Value::Text(native_label),
|
||||
@@ -32,17 +41,23 @@ impl NativeCallable for WidgetFactory {
|
||||
else {
|
||||
return Err(VmError::InvalidArgument("widget constructor arguments"));
|
||||
};
|
||||
// 复制请求类型的完整字段元数据,以便稍后可变访问 Store 并分配实例。
|
||||
let fields = cx
|
||||
.store()
|
||||
.payload::<EndorTypeObject>(*requested, "Type")?
|
||||
.fields()
|
||||
.to_vec();
|
||||
// 字段数量必须匹配,错误时还没有分配新实例。
|
||||
if values.len() != fields.len() {
|
||||
return Err(VmError::InvalidArgument("widget field count"));
|
||||
}
|
||||
// 先校验字段注解,避免无效实参进入实例分配和字段写入阶段。
|
||||
cx.store().check_class_field_arguments(*requested, values)?;
|
||||
// 通过当前原生上下文分配载荷;发生后续错误时由上下文清理临时分配根。
|
||||
let instance = cx.allocate(NativeWidget(native_label.clone()))?;
|
||||
// 附着实际请求的 Type,而非固定 Widget,保持 Child/Grand 的名义身份。
|
||||
cx.attach_type(instance, *requested)?;
|
||||
// 按声明字段顺序写值,Value clone 仍由 Store 属性更新建立内部引用边。
|
||||
for (field, value) in fields.iter().zip(values) {
|
||||
cx.set_attr(instance, &field.name, value.clone())?;
|
||||
}
|
||||
@@ -51,14 +66,21 @@ impl NativeCallable for WidgetFactory {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:验证源码两级继承仍由原生工厂分配载荷,字段约束和名义父类型保留,结果根独立保活类型图。
|
||||
fn source_subclasses_preserve_native_allocation_fields_and_nominal_identity() {
|
||||
// 隔离本场景堆、类型配置与执行状态,最后检查没有遗留对象。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 开启源码注解及字段检查,下面的类型错误才会按约束拒绝。
|
||||
vm.set_source_type_checks(true).unwrap();
|
||||
// 测试持环境初始根,源码函数和类型声明可在内部引用此环境。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 引导得到 Object/Type 拥有根,注册环境后分别归还测试根。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 注册 NativeContext 回调,后续通过 NativeTyped 类型构造协议调用。
|
||||
let factory = vm
|
||||
.allocate(ContextNativeFunction::new("widget", WidgetFactory, vec![]))
|
||||
.unwrap();
|
||||
// 创建宿主 Widget 类型,以 Object 为父类并保存原生构造器引用。
|
||||
let widget = vm
|
||||
.new_type(
|
||||
"Widget",
|
||||
@@ -66,6 +88,7 @@ fn source_subclasses_preserve_native_allocation_fields_and_nominal_identity() {
|
||||
TypeConstructor::NativeTyped(factory),
|
||||
)
|
||||
.unwrap();
|
||||
// 把引导类型/宿主类型放入源码可访问的环境,再归还各自外部根。
|
||||
for (name, object) in [
|
||||
("Object", roots.object),
|
||||
("Type", roots.type_type),
|
||||
@@ -75,12 +98,15 @@ fn source_subclasses_preserve_native_allocation_fields_and_nominal_identity() {
|
||||
.unwrap();
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// Widget 已持有工厂内部边,测试不再额外持工厂根。
|
||||
vm.release(factory).unwrap();
|
||||
// Child 增加数值字段,Grand 再增加文本字段,两层源码继承都沿用 Widget 原生分配策略。
|
||||
let source = "class Child: Widget { value: Number }\nclass Grand: Child { label: String }";
|
||||
assert_eq!(
|
||||
execute(&mut vm, globals, source, ParseMode::Module),
|
||||
Value::Nil
|
||||
);
|
||||
// 检查确实由运行时字段类型约束拒绝,而非返回一个看似成功的实例。
|
||||
assert!(matches!(
|
||||
try_execute(
|
||||
&mut vm,
|
||||
@@ -90,12 +116,14 @@ fn source_subclasses_preserve_native_allocation_fields_and_nominal_identity() {
|
||||
),
|
||||
Err(VmError::TypeMismatch { .. })
|
||||
));
|
||||
// 实际调用 Grand 得到拥有实例结果,随后独立于 globals 验证其原生载荷及继承字段。
|
||||
let value = execute(
|
||||
&mut vm,
|
||||
globals,
|
||||
"Grand(\"native\", 9, \"extra\")",
|
||||
ParseMode::Expression,
|
||||
);
|
||||
// 成功结果必须是托管实例;此句柄持有 execute 返回的拥有根。
|
||||
let Value::Object(instance) = value else {
|
||||
panic!("native instance")
|
||||
};
|
||||
@@ -112,30 +140,41 @@ fn source_subclasses_preserve_native_allocation_fields_and_nominal_identity() {
|
||||
vm.get_attr(instance, "label").unwrap(),
|
||||
Value::Text("extra".into())
|
||||
);
|
||||
// 先清环境的全局拥有边,打断函数/类型与环境之间可能的引用环。
|
||||
vm.dict_clear(globals).unwrap();
|
||||
// 归还环境外部根,剩余图是否存活只由其他拥有根决定。
|
||||
vm.release(globals).unwrap();
|
||||
// 收集已无外部根的类型/函数/环境环;仍由实例或回调拥有的图必须保留。
|
||||
vm.collect_cycles();
|
||||
assert!(vm.is_instance(instance, widget).unwrap());
|
||||
// 归还实例拥有结果根,之后其类型链及内部图也应可清理。
|
||||
vm.release(instance).unwrap();
|
||||
// 收集已无外部根的类型/函数/环境环;仍由实例或回调拥有的图必须保留。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
/// 成功路径测试复用的包装,try_execute 的 VM 错误直接使该断言场景失败;返回 Value 归调用方释放。
|
||||
fn execute(vm: &mut Vm, globals: ObjectRef, source: &str, mode: ParseMode) -> Value {
|
||||
try_execute(vm, globals, source, mode).unwrap()
|
||||
}
|
||||
|
||||
/// 类测试共用的编译/执行桥接,解析与加载失败直接使测试失败,VM 执行错误则原样返回给错误用例。
|
||||
/// globals 是借用环境,返回对象值有独立拥有根;无论执行成功或失败,本辅助函数都归还加载的代码根。
|
||||
fn try_execute(
|
||||
vm: &mut Vm,
|
||||
globals: ObjectRef,
|
||||
source: &str,
|
||||
mode: ParseMode,
|
||||
) -> Result<Value, VmError> {
|
||||
// 从当前环境条目提取文本键作为编译期已知名;这只声明名称,不复制运行时值。
|
||||
let names = vm
|
||||
.dict_items(globals)
|
||||
.unwrap()
|
||||
.iter()
|
||||
// 非文本键不能作为源码标识符,忽略它们而不向编译器提供伪名称。
|
||||
.filter_map(|(name, _)| {
|
||||
// 只把文本键转成声明名,其余键留在环境但不参与源码解析。
|
||||
if let Value::Text(name) = name {
|
||||
Some(name.clone())
|
||||
} else {
|
||||
@@ -143,24 +182,35 @@ fn try_execute(
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
// 为临时源码添加测试来源身份,错误诊断据此显示文件名与位置。
|
||||
let source = EndorReader::from_text("<classes>", source);
|
||||
// 根据当前名字列表编译,前端或语义失败属于测试设置错误而非期望的 VM 错误。
|
||||
let template = compile_source(&source, mode, &CompileOptions { globals: names }).unwrap();
|
||||
// 显式加载模板取得当前 VM 的拥有代码根,禁止跨堆复用对象句柄。
|
||||
let code = template.load(vm).unwrap();
|
||||
// 先保存 Result,随后即使执行失败也必须释放当前代码根。
|
||||
let result = vm.execute(code, globals);
|
||||
// 临时代码的拥有根归还,成功结果若含对象仍有自己的根。
|
||||
vm.release(code).unwrap();
|
||||
result
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:验证继承方法绑定真实子类实例、绑定参数数目扣除 self,单独回调根在清空环境后仍能动态派发。
|
||||
fn source_inheritance_binding_and_dynamic_dispatch_keep_the_real_receiver_alive() {
|
||||
// 隔离本场景堆、类型配置与执行状态,最后检查没有遗留对象。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 测试持环境初始根,源码函数和类型声明可在内部引用此环境。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 引导得到 Object/Type 拥有根,注册环境后分别归还测试根。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 把引导类型/宿主类型放入源码可访问的环境,再归还各自外部根。
|
||||
for (name, object) in [("Object", roots.object), ("Type", roots.type_type)] {
|
||||
vm.dict_set(globals, Value::Text(name.into()), Value::Object(object))
|
||||
.unwrap();
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// Base.via 通过 self.read 动态派发;Child 覆盖 read,withEvent 用于检查绑定后的参数数量。
|
||||
let source = r#"
|
||||
class Base {
|
||||
value: Number
|
||||
@@ -177,12 +227,14 @@ class Child: Base {
|
||||
execute(&mut vm, globals, source, ParseMode::Module),
|
||||
Value::Nil
|
||||
);
|
||||
// 读取临时 Child 实例的继承方法 via,返回绑定方法拥有根,方法必须保存实际接收者。
|
||||
let callback = execute(
|
||||
&mut vm,
|
||||
globals,
|
||||
"Child(9, \"ok\").via",
|
||||
ParseMode::Expression,
|
||||
);
|
||||
// 回调必须是托管绑定方法,拥有根一直保留到环境清理与实际调用之后。
|
||||
let Value::Object(callback) = callback else {
|
||||
panic!("bound callback")
|
||||
};
|
||||
@@ -192,12 +244,14 @@ class Child: Base {
|
||||
.unwrap(),
|
||||
Some(0)
|
||||
);
|
||||
// 另取带 event 参数的绑定方法,检查 self 自动绑定后仍剩一个位置参数。
|
||||
let with_event = execute(
|
||||
&mut vm,
|
||||
globals,
|
||||
"Child(9, \"ok\").withEvent",
|
||||
ParseMode::Expression,
|
||||
);
|
||||
// 将第二个回调结果解构为拥有句柄,稍后单独释放。
|
||||
let Value::Object(with_event) = with_event else {
|
||||
panic!("bound callback")
|
||||
};
|
||||
@@ -207,27 +261,37 @@ class Child: Base {
|
||||
.unwrap(),
|
||||
Some(1)
|
||||
);
|
||||
// 参数计数检查结束,归还第二个绑定方法,避免它掩盖主回调的保活责任。
|
||||
vm.release(with_event).unwrap();
|
||||
// 先清环境的全局拥有边,打断函数/类型与环境之间可能的引用环。
|
||||
vm.dict_clear(globals).unwrap();
|
||||
// 归还环境外部根,剩余图是否存活只由其他拥有根决定。
|
||||
vm.release(globals).unwrap();
|
||||
// 收集已无外部根的类型/函数/环境环;仍由实例或回调拥有的图必须保留。
|
||||
vm.collect_cycles();
|
||||
// 绑定回调是接收者最后的外部可达入口;Base.via 内的 self.read 应派发到 Child.read。
|
||||
// No global name or external instance root remains. The bound callback
|
||||
// alone retains its receiver and can still dispatch to Child.read.
|
||||
assert_eq!(
|
||||
vm.call(callback, &[]).unwrap(),
|
||||
Value::Number(12_i64.into())
|
||||
);
|
||||
// 完成清空环境后的动态派发验证,归还最后回调根。
|
||||
vm.release(callback).unwrap();
|
||||
// 收集已无外部根的类型/函数/环境环;仍由实例或回调拥有的图必须保留。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:验证重复字段及缺显式 self 在语义分析阶段被拒绝,不需创建 VM 或执行源码。
|
||||
fn duplicate_members_and_missing_receivers_fail_before_execution() {
|
||||
// 两个独立语义错误案例:同类重复字段,以及方法没有显式 self 参数。
|
||||
for text in [
|
||||
"class Bad { value: Number value: String }",
|
||||
"class Bad { function read(): Number { return 1 } }",
|
||||
] {
|
||||
// 为临时源码添加测试来源身份,错误诊断据此显示文件名与位置。
|
||||
let source = EndorReader::from_text("<invalid-class>", text);
|
||||
assert!(
|
||||
compile_source(
|
||||
@@ -243,10 +307,15 @@ fn duplicate_members_and_missing_receivers_fail_before_execution() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:验证继承字段构造/赋值/删除检查、失败前后版本不变、动态名义注解重绑定与清理。
|
||||
fn declared_field_checks_cover_construction_inheritance_writes_and_gc() {
|
||||
// 隔离本场景堆、类型配置与执行状态,最后检查没有遗留对象。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 开启源码注解及字段检查,下面的类型错误才会按约束拒绝。
|
||||
vm.set_source_type_checks(true).unwrap();
|
||||
// 测试持环境初始根,源码函数和类型声明可在内部引用此环境。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 引导得到 Object/Type 拥有根,注册环境后分别归还测试根。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
vm.dict_set(
|
||||
globals,
|
||||
@@ -256,6 +325,7 @@ fn declared_field_checks_cover_construction_inheritance_writes_and_gc() {
|
||||
.unwrap();
|
||||
vm.release(roots.object).unwrap();
|
||||
vm.release(roots.type_type).unwrap();
|
||||
// 一次模块声明建立继承字段、名义对象字段及未知注解三种约束,真正的构造/写入在后面分别触发。
|
||||
execute(
|
||||
&mut vm,
|
||||
globals,
|
||||
@@ -267,6 +337,7 @@ class Unknown { value: Missing }
|
||||
"#,
|
||||
ParseMode::Module,
|
||||
);
|
||||
// 每份交互源码只提交一条语句:先登记 Child 实例,再让 Holder 的字段持有同一实例。
|
||||
// Interactive intentionally accepts one complete statement per source unit.
|
||||
execute(
|
||||
&mut vm,
|
||||
@@ -280,6 +351,7 @@ class Unknown { value: Missing }
|
||||
"holder = Holder(value)",
|
||||
ParseMode::Interactive,
|
||||
);
|
||||
// 从 globals 借用已登记实例;测试不额外持该句柄的拥有根。
|
||||
let Value::Object(value) = vm
|
||||
.dict_get(globals, &Value::Text("value".into()))
|
||||
.unwrap()
|
||||
@@ -287,7 +359,9 @@ class Unknown { value: Missing }
|
||||
else {
|
||||
panic!("instance")
|
||||
};
|
||||
// 记录合法实例当前版本,后续失败的字段写入不得改变它。
|
||||
let version = vm.object_store().change_version(value).unwrap();
|
||||
// 覆盖继承字段、子字段、名义对象字段、未知注解及错误赋值,错误必须在更新前返回。
|
||||
for source in [
|
||||
r#"Child("bad", "ok")"#,
|
||||
"Child(9, 2)",
|
||||
@@ -295,6 +369,7 @@ class Unknown { value: Missing }
|
||||
"Unknown(1)",
|
||||
r#"value.count = "bad""#,
|
||||
] {
|
||||
// 错误应是类型不匹配或未知注解,不接受其他无关失败掩盖约束问题。
|
||||
assert!(matches!(
|
||||
try_execute(&mut vm, globals, source, ParseMode::Interactive),
|
||||
Err(VmError::TypeMismatch { .. } | VmError::UnknownAnnotation(_))
|
||||
@@ -308,26 +383,37 @@ class Unknown { value: Missing }
|
||||
assert!(vm.del_attr(value, "count").is_err());
|
||||
vm.set_attr(value, "count", Value::Number(10.into()))
|
||||
.unwrap();
|
||||
// 用同名新类替换全局 Base,旧 value 的名义类型图仍保留最初的 Base 身份。
|
||||
execute(&mut vm, globals, "class Base {}", ParseMode::Interactive);
|
||||
// 其他名义类型名称在声明环境中动态解析;Base 重定义后 Holder 的字段约束应按新身份检查。
|
||||
// Other nominal names resolve dynamically in the declaring environment.
|
||||
assert!(matches!(
|
||||
try_execute(&mut vm, globals, "Holder(value)", ParseMode::Expression),
|
||||
Err(VmError::TypeMismatch { .. })
|
||||
));
|
||||
// 继承来的 count 保留其原声明字段元数据,环境同名类重定义不抹掉旧实例的数值约束。
|
||||
// An inherited scalar field still belongs to its original declaration.
|
||||
vm.set_attr(value, "count", Value::Number(11.into()))
|
||||
.unwrap();
|
||||
// 先清环境的全局拥有边,打断函数/类型与环境之间可能的引用环。
|
||||
vm.dict_clear(globals).unwrap();
|
||||
// 归还环境外部根,剩余图是否存活只由其他拥有根决定。
|
||||
vm.release(globals).unwrap();
|
||||
// 收集已无外部根的类型/函数/环境环;仍由实例或回调拥有的图必须保留。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:验证声明类自身的递归类型按原身份检查,即使全局同名类重定义;关闭检查允许临时写入 Nil。
|
||||
fn checked_fields_keep_declaring_self_type_and_can_be_opted_out() {
|
||||
// 隔离本场景堆、类型配置与执行状态,最后检查没有遗留对象。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 开启源码注解及字段检查,下面的类型错误才会按约束拒绝。
|
||||
vm.set_source_type_checks(true).unwrap();
|
||||
// 测试持环境初始根,源码函数和类型声明可在内部引用此环境。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 引导得到 Object/Type 拥有根,注册环境后分别归还测试根。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
vm.dict_set(
|
||||
globals,
|
||||
@@ -337,7 +423,7 @@ fn checked_fields_keep_declaring_self_type_and_can_be_opted_out() {
|
||||
.unwrap();
|
||||
vm.release(roots.object).unwrap();
|
||||
vm.release(roots.type_type).unwrap();
|
||||
// Object permits nil, so a separate numeric field provides an initial instance.
|
||||
// Link.next 是递归且不可空的字段;Seed 声明展示继承,后续由可信宿主直接构造旧 Link 实例。
|
||||
execute(
|
||||
&mut vm,
|
||||
globals,
|
||||
@@ -348,6 +434,7 @@ class Seed: Link {}
|
||||
ParseMode::Module,
|
||||
);
|
||||
// A recursive nonnullable field can be initialized by the trusted host in two steps.
|
||||
// 从 globals 借用最初 Link 类型,后面同名重定义不应改变此类型的自引用身份。
|
||||
let Value::Object(link) = vm
|
||||
.dict_get(globals, &Value::Text("Link".into()))
|
||||
.unwrap()
|
||||
@@ -355,11 +442,14 @@ class Seed: Link {}
|
||||
else {
|
||||
panic!("class")
|
||||
};
|
||||
// 宿主先分配空对象再附类型,用两步构造填入不可空的递归 next 字段。
|
||||
let seed = vm.new_object().unwrap();
|
||||
// 附上旧 Link 名义类型,此时先由可信宿主完成递归对象初始化。
|
||||
vm.attach_type(seed, link).unwrap();
|
||||
vm.set_attr(seed, "next", Value::Object(seed)).unwrap();
|
||||
vm.dict_set(globals, Value::Text("old".into()), Value::Object(seed))
|
||||
.unwrap();
|
||||
// 新 Link 采用不同字段布局,只替换环境绑定;seed 及其 next 的原声明身份应不受影响。
|
||||
execute(
|
||||
&mut vm,
|
||||
globals,
|
||||
@@ -367,17 +457,24 @@ class Seed: Link {}
|
||||
ParseMode::Interactive,
|
||||
);
|
||||
vm.set_attr(seed, "next", Value::Object(seed)).unwrap();
|
||||
// 旧 Link 的自引用字段不可空,即使当前全局 Link 已换成另一个类。
|
||||
assert!(matches!(
|
||||
vm.set_attr(seed, "next", Value::Nil),
|
||||
Err(VmError::TypeMismatch { .. })
|
||||
));
|
||||
// 显式关闭检查,允许下一步向不可空字段写 Nil;这不会改变声明本身。
|
||||
vm.set_source_type_checks(false).unwrap();
|
||||
vm.set_attr(seed, "next", Value::Nil).unwrap();
|
||||
// 恢复检查配置后写回旧 Link 自身实例,验证声明身份和约束没有被关闭操作修改。
|
||||
vm.set_source_type_checks(true).unwrap();
|
||||
vm.set_attr(seed, "next", Value::Object(seed)).unwrap();
|
||||
// 归还种子外部根;环境清理后,自引用环仍需循环收集器处理。
|
||||
vm.release(seed).unwrap();
|
||||
// 先清环境的全局拥有边,打断函数/类型与环境之间可能的引用环。
|
||||
vm.dict_clear(globals).unwrap();
|
||||
// 归还环境外部根,剩余图是否存活只由其他拥有根决定。
|
||||
vm.release(globals).unwrap();
|
||||
// 收集已无外部根的类型/函数/环境环;仍由实例或回调拥有的图必须保留。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Rust 测试框架驱动的源码→模板→代码对象→VM 集成验证;成功和错误路径都检查拥有根清理。
|
||||
//! compile/module/function/cleanup 为本文件测试复用,编译器本身不执行源码,只有测试显式 load/execute/call。
|
||||
use compiler::{CompileOptions, CompiledCode, NameBinding, analyze, compile_ast, compile_source};
|
||||
use endor_core::{
|
||||
EndorCodeObject, EndorFunctionObject, EndorNumber, ExecutionLimits, NativeFunction, ObjectRef,
|
||||
@@ -5,6 +7,7 @@ use endor_core::{
|
||||
};
|
||||
use frontend::{DiagnosticKind, EndorReader, ParseMode, SourceUnit, parse_ast};
|
||||
|
||||
/// 测试共用的源码入口,按 mode 编译,不声明额外宿主名;返回未加载、独立于 VM 堆的模板。
|
||||
fn compile(text: &str, mode: ParseMode) -> CompiledCode {
|
||||
compile_source(
|
||||
&EndorReader::from_text("<test>", text),
|
||||
@@ -14,16 +17,23 @@ fn compile(text: &str, mode: ParseMode) -> CompiledCode {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// 测试共用的模块加载入口:取得代码根、创建环境并执行声明,模块结果应为 Nil;返回两个拥有根供 cleanup。
|
||||
fn module(vm: &mut Vm, compiled: &CompiledCode) -> (ObjectRef, ObjectRef) {
|
||||
// 把模板显式装入当前 VM,取得需要归还的代码拥有根。
|
||||
let code = compiled.load(vm).unwrap();
|
||||
// 新建环境拥有根;函数声明和宿主值通过内部边持有,测试结束需清理环境。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 执行加载的代码取得拥有返回值;模块为 Nil,表达式则为其实际结果。
|
||||
let value = vm.execute(code, globals).unwrap();
|
||||
assert_eq!(value, Value::Nil);
|
||||
// 归还 execute/call 返回值拥有根;立即值也可以统一通过此入口处理。
|
||||
vm.release_value(value).unwrap();
|
||||
(code, globals)
|
||||
}
|
||||
|
||||
/// 测试按名称从环境借用函数句柄;不 retain,调用者须保持 globals 或其他拥有图存活。
|
||||
fn function(vm: &Vm, globals: ObjectRef, name: &str) -> ObjectRef {
|
||||
// 环境键必须命中对象句柄;此辅助函数不再下转型证明函数类别,实际可调用性由后续 VM 调用验证。
|
||||
let Some(Value::Object(function)) = vm.dict_get(globals, &Value::Text(name.into())).unwrap()
|
||||
else {
|
||||
panic!("missing function {name}")
|
||||
@@ -31,16 +41,23 @@ fn function(vm: &Vm, globals: ObjectRef, name: &str) -> ObjectRef {
|
||||
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]
|
||||
/// 由 Rust 测试框架调用:逐个加载示例模块并调用 main,验证前向调用、函数值遮蔽、Unicode 返回及精确十进制,并检查代码名和清理。
|
||||
fn source_files_execute_through_module_and_function_code_objects() {
|
||||
// 逐项使用独立代码路径核对指定源码与预期值,任一不符都使本测试失败。
|
||||
for (text, expected) in [
|
||||
(
|
||||
include_str!("../examples/hello.endor"),
|
||||
@@ -59,10 +76,15 @@ fn source_files_execute_through_module_and_function_code_objects() {
|
||||
Value::Number(EndorNumber::decimal("-123456789012345678901234567890.00100").unwrap()),
|
||||
),
|
||||
] {
|
||||
// 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。
|
||||
let compiled = compile(text, ParseMode::Module);
|
||||
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。
|
||||
let (code, globals) = module(&mut vm, &compiled);
|
||||
// 从环境借用 main,环境在调用完成前保持存活。
|
||||
let main = function(&vm, globals, "main");
|
||||
// 检查源码函数的代码元数据,证明 main 不是宿主替代回调。
|
||||
let data = vm.payload::<EndorFunctionObject>(main, "function").unwrap();
|
||||
assert_eq!(
|
||||
vm.payload::<EndorCodeObject>(data.code(), "code")
|
||||
@@ -71,15 +93,19 @@ fn source_files_execute_through_module_and_function_code_objects() {
|
||||
.name(),
|
||||
"main"
|
||||
);
|
||||
// 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。
|
||||
let result = vm.call(main, &[]).unwrap();
|
||||
assert_eq!(result, expected);
|
||||
// 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。
|
||||
vm.release_value(result).unwrap();
|
||||
cleanup(&mut vm, code, globals);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:表达式模式直接返回结果,验证文本、大整数、连续负号、前导零和负零均保持精确数值语义。
|
||||
fn expression_mode_preserves_exact_literals_and_signs() {
|
||||
// 逐项使用独立代码路径核对指定源码与预期值,任一不符都使本测试失败。
|
||||
for (text, expected) in [
|
||||
(
|
||||
include_str!("../examples/literal.endor"),
|
||||
@@ -95,20 +121,29 @@ fn expression_mode_preserves_exact_literals_and_signs() {
|
||||
Value::Number(EndorNumber::decimal("-0.000").unwrap()),
|
||||
),
|
||||
] {
|
||||
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 表达式模板在当前 VM 单独装载,加载代码拥有根由本轮测试负责归还。
|
||||
let code = compile(text, ParseMode::Expression).load(&mut vm).unwrap();
|
||||
// 新建环境拥有根;函数声明和宿主值通过内部边持有,测试结束需清理环境。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 执行加载的代码取得拥有返回值;模块为 Nil,表达式则为其实际结果。
|
||||
let value = vm.execute(code, globals).unwrap();
|
||||
assert_eq!(value, expected);
|
||||
// 归还 execute/call 返回值拥有根;立即值也可以统一通过此入口处理。
|
||||
vm.release_value(value).unwrap();
|
||||
cleanup(&mut vm, code, globals);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:直接检查符号表的参数/局部槽、前向全局名绑定与空自由变量表,不加载 VM。
|
||||
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 }";
|
||||
// 先解析 AST,再单独调用名称分析,隔离解析与分析阶段的结果。
|
||||
let root = parse_ast(source, ParseMode::Module).unwrap();
|
||||
// 不创建 VM,直接检查 analyze 输出的稳定参数/局部与全局索引。
|
||||
let symbols = analyze(&root, &CompileOptions::default()).unwrap();
|
||||
assert_eq!(symbols.functions[0].layout.parameters, ["target"]);
|
||||
assert_eq!(symbols.functions[0].layout.locals, ["target", "copy"]);
|
||||
@@ -124,6 +159,7 @@ fn scope_analysis_resolves_local_shadowing_and_forward_globals() {
|
||||
NameBinding::Local(1)
|
||||
]
|
||||
);
|
||||
// 当前源码函数不捕获外层局部,所有代码布局的自由变量表都应为空。
|
||||
assert!(
|
||||
symbols
|
||||
.functions
|
||||
@@ -133,7 +169,9 @@ fn scope_analysis_resolves_local_shadowing_and_forward_globals() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:参数/局部/顶层重复、未声明及未初始化读取都在编译时拒绝,并检查 Unicode 与 CRLF 的诊断行列。
|
||||
fn semantic_errors_prevent_loading_and_keep_unicode_locations() {
|
||||
// 每例给出非法源码及应包含的诊断片段,确保失败原因与目标规则对应。
|
||||
for (text, fragment) in [
|
||||
(
|
||||
"function f(): String { return missing }",
|
||||
@@ -165,19 +203,25 @@ fn semantic_errors_prevent_loading_and_keep_unicode_locations() {
|
||||
"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:"));
|
||||
}
|
||||
|
||||
/// 宿主 NativeFunction 调用的恒等回调;参数为借用值,clone 不增对象根,VM 返回协议负责保留结果。
|
||||
fn identity(arguments: &[Value]) -> Result<Value, VmError> {
|
||||
// 恒等回调只接受一个实参,多参/缺参明确报错而非越界取参数。
|
||||
if arguments.len() != 1 {
|
||||
return Err(VmError::InvalidArgument("identity needs one argument"));
|
||||
}
|
||||
@@ -185,18 +229,28 @@ fn identity(arguments: &[Value]) -> Result<Value, VmError> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:验证宿主声明、属性原生调用和拥有对象返回;清空环境后仅结果根保活对象,释放结果后句柄失效。
|
||||
fn host_globals_member_calls_and_object_results_keep_owned_roots() {
|
||||
// 编译期仅声明可用宿主名;真正的绑定值仍由 globals 提供。
|
||||
let options = CompileOptions {
|
||||
globals: vec!["service".into(), "value".into()],
|
||||
};
|
||||
// 保留来源名和原始源码,编译诊断应带有正确位置及错误说明。
|
||||
let unit = EndorReader::from_text("<host>", "service.echo(value)");
|
||||
// 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。
|
||||
let compiled = compile_source(&unit, ParseMode::Expression, &options).unwrap();
|
||||
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 分配恒等原生回调作为 service.echo,属性内部边随后保留它。
|
||||
let native = vm.allocate(NativeFunction::new("echo", identity)).unwrap();
|
||||
// 普通宿主对象承载 echo 属性,用于验证成员查找与原生调用链。
|
||||
let service = vm.new_object().unwrap();
|
||||
vm.set_attr(service, "echo", Value::Object(native)).unwrap();
|
||||
// service.echo 已取得内部边,测试不再额外持原生函数根。
|
||||
vm.release(native).unwrap();
|
||||
// 作为实参及最终返回的托管对象,后续归还原根以检查 VM 返回协议。
|
||||
let object = vm.new_object().unwrap();
|
||||
// 新建环境拥有根;函数声明和宿主值通过内部边持有,测试结束需清理环境。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
vm.dict_set(
|
||||
globals,
|
||||
@@ -206,29 +260,45 @@ fn host_globals_member_calls_and_object_results_keep_owned_roots() {
|
||||
.unwrap();
|
||||
vm.dict_set(globals, Value::Text("value".into()), Value::Object(object))
|
||||
.unwrap();
|
||||
// 环境已持 service,归还宿主初始根。
|
||||
vm.release(service).unwrap();
|
||||
// 环境已持实参对象,归还初始根,之后返回结果必须获得自己的根。
|
||||
vm.release(object).unwrap();
|
||||
// 把模板显式装入当前 VM,取得需要归还的代码拥有根。
|
||||
let code = compiled.load(&mut vm).unwrap();
|
||||
// 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。
|
||||
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]
|
||||
/// 由 Rust 测试框架调用:同一模板装入两个独立 VM,跨堆句柄被拒绝;重复调用复用已加载代码,不增加堆对象。
|
||||
fn code_templates_load_independently_and_functions_share_loaded_code() {
|
||||
// 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。
|
||||
let compiled = compile(include_str!("../examples/calls.endor"), ParseMode::Module);
|
||||
// 第一个 VM 单独加载模板,句柄不能被另一个堆接受。
|
||||
let mut left = Vm::new().unwrap();
|
||||
// 第二个 VM 加载同一模板,但得到自身堆专属代码及环境。
|
||||
let mut right = Vm::new().unwrap();
|
||||
// 左堆建立独立模块图,后续重复调用仅访问这份代码。
|
||||
let (left_code, left_globals) = module(&mut left, &compiled);
|
||||
// 右堆建立另一份图,用来检查 ForeignHandle 拒绝跨 VM 引用。
|
||||
let (right_code, right_globals) = module(&mut right, &compiled);
|
||||
assert_eq!(left.object(right_code).unwrap_err(), VmError::ForeignHandle);
|
||||
// 记录多次调用前堆大小,调用结束不应重建源码函数代码或残留临时图。
|
||||
let before = left.object_count();
|
||||
// 从环境借用 main,环境在调用完成前保持存活。
|
||||
let main = function(&left, left_globals, "main");
|
||||
// 重复运行同一函数三次,每次归还结果,以对象数验证代码复用。
|
||||
for _ in 0..3 {
|
||||
// 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。
|
||||
let result = left.call(main, &[]).unwrap();
|
||||
left.release_value(result).unwrap();
|
||||
}
|
||||
@@ -238,14 +308,22 @@ fn code_templates_load_independently_and_functions_share_loaded_code() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:缺参和递归帧上限错误释放执行临时图,同时保留源码位置或限制深度对应的回溯。
|
||||
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 }";
|
||||
// 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。
|
||||
let compiled = compile(source, ParseMode::Module);
|
||||
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。
|
||||
let (code, globals) = module(&mut vm, &compiled);
|
||||
// 记录错误调用前对象数,参数/返回检查或帧清理后不能增加存活对象。
|
||||
let baseline = vm.object_count();
|
||||
// 从环境借用 main,环境在调用完成前保持存活。
|
||||
let main = function(&vm, globals, "main");
|
||||
// main 调用 echo 时漏传 value,错误出在被调函数预检,不能残留执行帧或临时根。
|
||||
assert!(matches!(
|
||||
vm.call(main, &[]),
|
||||
Err(VmError::ArgumentCount { provided: 0, .. })
|
||||
@@ -254,11 +332,14 @@ fn argument_and_recursion_failures_unwind_vm_frames_and_keep_traceback() {
|
||||
assert_eq!(vm.last_traceback().last().unwrap().first_line, 2);
|
||||
cleanup(&mut vm, code, globals);
|
||||
|
||||
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。
|
||||
let (code, globals) = module(
|
||||
&mut vm,
|
||||
&compile("function main(): T { return main() }", ParseMode::Module),
|
||||
);
|
||||
// 从环境借用 main,环境在调用完成前保持存活。
|
||||
let main = function(&vm, globals, "main");
|
||||
assert_eq!(
|
||||
vm.call_with_limits(
|
||||
@@ -276,8 +357,11 @@ fn argument_and_recursion_failures_unwind_vm_frames_and_keep_traceback() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:编译选项只登记可见名字,不注入 VM 值;空环境执行仍报告 NameNotFound。
|
||||
fn declared_host_names_are_checked_again_at_runtime() {
|
||||
// 当前用例的源码或带来源快照,下面按指定模式解析/编译。
|
||||
let source = EndorReader::from_text("<host>", "missing");
|
||||
// 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。
|
||||
let compiled = compile_source(
|
||||
&source,
|
||||
ParseMode::Expression,
|
||||
@@ -286,8 +370,11 @@ fn declared_host_names_are_checked_again_at_runtime() {
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 把模板显式装入当前 VM,取得需要归还的代码拥有根。
|
||||
let code = compiled.load(&mut vm).unwrap();
|
||||
// 新建环境拥有根;函数声明和宿主值通过内部边持有,测试结束需清理环境。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
assert_eq!(
|
||||
vm.execute(code, globals),
|
||||
@@ -297,9 +384,13 @@ fn declared_host_names_are_checked_again_at_runtime() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:两次单语句交互共享宿主环境,首次绑定返回 Nil,后续读取返回此前保存值。
|
||||
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 = \"保留\"",
|
||||
@@ -314,47 +405,70 @@ fn interactive_submissions_can_share_a_host_owned_namespace() {
|
||||
Value::Text("保留".into()),
|
||||
),
|
||||
] {
|
||||
// 当前用例的源码或带来源快照,下面按指定模式解析/编译。
|
||||
let source = EndorReader::from_text("<submission>", text);
|
||||
// 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。
|
||||
let compiled = compile_source(&source, ParseMode::Interactive, &options).unwrap();
|
||||
// 把模板显式装入当前 VM,取得需要归还的代码拥有根。
|
||||
let code = compiled.load(&mut vm).unwrap();
|
||||
// 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。
|
||||
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]
|
||||
/// 由 Rust 测试框架调用:构造 270 个局部与常量,迫使索引使用扩展操作数,执行返回最后一个值验证编码解码贯通。
|
||||
fn extended_operands_work_for_large_local_and_constant_tables() {
|
||||
// 动态拼接大量独立局部声明,测试超过单字节的索引编码。
|
||||
let mut source = String::from("function main(): Number { ");
|
||||
// 超过 255 的局部/常量索引需要 EXTENDED_ARG,返回值来自最后一个槽。
|
||||
for index in 0..270 {
|
||||
source.push_str(&format!("let value{index} = {index} "));
|
||||
}
|
||||
source.push_str("return value269 }");
|
||||
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。
|
||||
let (code, globals) = module(&mut vm, &compile(&source, ParseMode::Module));
|
||||
// 从环境借用 main,环境在调用完成前保持存活。
|
||||
let main = function(&vm, globals, "main");
|
||||
// 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。
|
||||
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]
|
||||
/// 由 Rust 测试框架调用:前端可解析的 Symbol 暂无 VM 值,在编译生成阶段明确失败。
|
||||
fn unsupported_execution_features_are_compile_errors_instead_of_fake_results() {
|
||||
// 当前用例的源码或带来源快照,下面按指定模式解析/编译。
|
||||
let source = EndorReader::from_text("<unsupported>", ".title");
|
||||
// 预期编译失败,之后检查诊断阶段或位置,不能走加载执行路径。
|
||||
let error =
|
||||
compile_source(&source, ParseMode::Expression, &CompileOptions::default()).unwrap_err();
|
||||
assert_eq!(error.diagnostic.kind, DiagnosticKind::Compilation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:破坏手工 AST 的 UTF-8 边界,验证直接 AST 编译入口先拒绝无效源跨度。
|
||||
fn compiling_ast_rejects_invalid_source_ranges() {
|
||||
// 保留含三字节字符的原始快照,用于故意制造 UTF-8 非边界跨度。
|
||||
let source: SourceUnit = EndorReader::from_text("<source>", "\"日\"");
|
||||
// 解析合法表达式 AST,再手工修改跨度,仅测试 AST 入口验证。
|
||||
let mut root = parse_ast(source.text(), ParseMode::Expression).unwrap();
|
||||
// 将 end 改到日字的多字节编码内部,语法树内容不变但位置应非法。
|
||||
if let frontend::ast::AstRoot::Expression(expression) = &mut root {
|
||||
expression.span.end = 2; // Splits the three-byte character.
|
||||
}
|
||||
@@ -362,53 +476,73 @@ fn compiling_ast_rejects_invalid_source_ranges() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:交互裸赋值创建再替换全局,下一次编译从现有环境取得宿主名字,最终读取最新值。
|
||||
fn bare_assignments_create_and_replace_real_interactive_globals() {
|
||||
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 新建环境拥有根;函数声明和宿主值通过内部边持有,测试结束需清理环境。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 同一环境连续执行创建、替换和读取,后面的编译应看到前面登记的名字。
|
||||
for text in ["value = \"first\"", "value = \"second\"", "value"] {
|
||||
// 编译期仅声明可用宿主名;真正的绑定值仍由 globals 提供。
|
||||
let options = CompileOptions {
|
||||
globals: vm
|
||||
.dict_items(globals)
|
||||
.unwrap()
|
||||
.iter()
|
||||
// 只允许文本环境键成为源码可用名称,不把非文本键当标识符。
|
||||
.filter_map(|(key, _)| match key {
|
||||
Value::Text(name) => Some(name.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
// 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。
|
||||
let compiled = compile_source(
|
||||
&EndorReader::from_text("<session>", text),
|
||||
ParseMode::Interactive,
|
||||
&options,
|
||||
)
|
||||
.unwrap();
|
||||
// 把模板显式装入当前 VM,取得需要归还的代码拥有根。
|
||||
let code = compiled.load(&mut vm).unwrap();
|
||||
// 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。
|
||||
let result = vm.execute(code, globals).unwrap();
|
||||
// 只有最后的表达式提交返回文本;赋值提交只产生 Nil。
|
||||
if text == "value" {
|
||||
assert_eq!(result, Value::Text("second".into()));
|
||||
}
|
||||
// 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。
|
||||
vm.release_value(result).unwrap();
|
||||
// 归还本轮代码外部根,函数或返回结果需要的内部图仍由各自引用持有。
|
||||
vm.release(code).unwrap();
|
||||
}
|
||||
assert_eq!(
|
||||
vm.dict_get(globals, &Value::Text("value".into())).unwrap(),
|
||||
Some(Value::Text("second".into()))
|
||||
);
|
||||
// 清除环境拥有边,打断声明函数/类型到环境的可能引用环。
|
||||
vm.dict_clear(globals).unwrap();
|
||||
// 归还环境初始根;之后只应留下测试明确保存的结果或回调根。
|
||||
vm.release(globals).unwrap();
|
||||
// 清理零外根的引用环,随后对象数应回到零。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:赋值复用参数及同名局部槽,执行验证更新结果;自读未初始化与重复 let 仍编译失败。
|
||||
fn assignments_update_parameters_and_allocate_one_local_slot_per_target() {
|
||||
// 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。
|
||||
let compiled = compile(
|
||||
"function main(value: String): String { copy = value copy = \"updated\" value = copy return value }",
|
||||
ParseMode::Module,
|
||||
);
|
||||
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。
|
||||
let (code, globals) = module(&mut vm, &compiled);
|
||||
// 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。
|
||||
let result = vm
|
||||
.call(
|
||||
function(&vm, globals, "main"),
|
||||
@@ -416,6 +550,7 @@ fn assignments_update_parameters_and_allocate_one_local_slot_per_target() {
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(result, Value::Text("updated".into()));
|
||||
// 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。
|
||||
vm.release_value(result).unwrap();
|
||||
cleanup(&mut vm, code, globals);
|
||||
assert!(
|
||||
@@ -440,11 +575,14 @@ fn assignments_update_parameters_and_allocate_one_local_slot_per_target() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:检查模板保存类型签名,加载后开启运行时检查分别拒绝错误参数与错误返回,错误不泄漏对象。
|
||||
fn source_signatures_survive_template_load_and_enable_runtime_checks() {
|
||||
// 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。
|
||||
let compiled = compile(
|
||||
"function identity(value: Number): Number { return value }\nfunction bad(): Number { return \"wrong\" }",
|
||||
ParseMode::Module,
|
||||
);
|
||||
// 检查首个子函数模板的源码签名,尚未加载时也必须保留注解。
|
||||
let signature = compiled
|
||||
.children()
|
||||
.next()
|
||||
@@ -453,20 +591,28 @@ fn source_signatures_survive_template_load_and_enable_runtime_checks() {
|
||||
.unwrap();
|
||||
assert_eq!(signature.parameters, ["Number"]);
|
||||
assert_eq!(signature.return_type, "Number");
|
||||
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 启用运行时源码签名检查,错误参数在函数体前拒绝,错误返回在出口拒绝。
|
||||
vm.set_source_type_checks(true).unwrap();
|
||||
// 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。
|
||||
let (code, globals) = module(&mut vm, &compiled);
|
||||
// 借用已加载的 Number 恒等函数,分别尝试合法数值和非法文本。
|
||||
let identity = function(&vm, globals, "identity");
|
||||
assert_eq!(
|
||||
vm.call(identity, &[Value::Number(7.into())]).unwrap(),
|
||||
Value::Number(7.into())
|
||||
);
|
||||
// 开启检查后,String 实参不能通过 Number 参数约束。
|
||||
assert!(matches!(
|
||||
vm.call(identity, &[Value::Text("wrong".into())]),
|
||||
Err(VmError::TypeMismatch { .. })
|
||||
));
|
||||
// 借用声明返回 Number 但实际返回文本的函数,专门验证返回类型约束。
|
||||
let bad = function(&vm, globals, "bad");
|
||||
// 记录错误调用前对象数,参数/返回检查或帧清理后不能增加存活对象。
|
||||
let baseline = vm.object_count();
|
||||
// bad 的函数体能执行,但出口文本不符合声明的 Number 返回类型。
|
||||
assert!(matches!(
|
||||
vm.call(bad, &[]),
|
||||
Err(VmError::TypeMismatch { .. })
|
||||
@@ -476,15 +622,23 @@ fn source_signatures_survive_template_load_and_enable_runtime_checks() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:运行 while/if 与加减得到 -100,再检查精确小数、大整数、有理数和非字面量取负。
|
||||
fn source_control_flow_and_exact_arithmetic_execute_real_bytecode() {
|
||||
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 先累加 0..9 再减去 10..19,循环与分支结果应是精确整数 -100。
|
||||
let text = "function main(): Number { i=0 total=0 while i<20 { if i<10 {total=total+i} else {total=total-i} i=i+1 } return total }";
|
||||
// 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。
|
||||
let (code, globals) = module(&mut vm, &compile(text, ParseMode::Module));
|
||||
// 从环境借用 main,环境在调用完成前保持存活。
|
||||
let main = function(&vm, globals, "main");
|
||||
// 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。
|
||||
let result = vm.call(main, &[]).unwrap();
|
||||
assert_eq!(result, Value::Number((-100_i64).into()));
|
||||
// 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。
|
||||
vm.release_value(result).unwrap();
|
||||
cleanup(&mut vm, code, globals);
|
||||
// 逐项使用独立代码路径核对指定源码与预期值,任一不符都使本测试失败。
|
||||
for (text, expected) in [
|
||||
("0.1 + 0.2 == 0.3", Value::Bool(true)),
|
||||
(
|
||||
@@ -494,29 +648,43 @@ fn source_control_flow_and_exact_arithmetic_execute_real_bytecode() {
|
||||
("1 / 3 + 1 / 3 + 1 / 3", Value::Number(1_i64.into())),
|
||||
("-(2 + 3)", Value::Number((-5_i64).into())),
|
||||
] {
|
||||
// 表达式模板在当前 VM 单独装载,加载代码拥有根由本轮测试负责归还。
|
||||
let code = compile(text, ParseMode::Expression).load(&mut vm).unwrap();
|
||||
// 新建环境拥有根;函数声明和宿主值通过内部边持有,测试结束需清理环境。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。
|
||||
let result = vm.execute(code, globals).unwrap();
|
||||
assert_eq!(result, expected);
|
||||
// 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。
|
||||
vm.release_value(result).unwrap();
|
||||
// 归还本轮代码外部根,函数或返回结果需要的内部图仍由各自引用持有。
|
||||
vm.release(code).unwrap();
|
||||
// 归还环境初始根;之后只应留下测试明确保存的结果或回调根。
|
||||
vm.release(globals).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:长循环体迫使相对跳转扩展,单侧初始化和零次循环后的读取编译失败,合法无限循环受指令总预算限制。
|
||||
fn control_flow_definite_initialization_large_jumps_and_loop_budgets() {
|
||||
// 拼接较长 if/while 体,迫使条件出口和回边跨越扩展编码距离。
|
||||
let mut body = String::from("function main(): Number { i=0 while i<2 { if i==0 {");
|
||||
// 追加三百条不改变 i 的赋值,仅增大跳转跨越的代码宽度;真正的循环推进在后面单独追加。
|
||||
for _ in 0..300 {
|
||||
body.push_str("i=i+0 ");
|
||||
}
|
||||
body.push_str("} i=i+1 } return i }");
|
||||
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。
|
||||
let (code, globals) = module(&mut vm, &compile(&body, ParseMode::Module));
|
||||
// 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。
|
||||
let result = vm.call(function(&vm, globals, "main"), &[]).unwrap();
|
||||
assert_eq!(result, Value::Number(2_i64.into()));
|
||||
// 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。
|
||||
vm.release_value(result).unwrap();
|
||||
cleanup(&mut vm, code, globals);
|
||||
// 两种未确定初始化用例:if 只有一侧赋值,以及 while 可能零次执行。
|
||||
for text in [
|
||||
"function f(): Number { if 1 {x=2} return x }",
|
||||
"function f(): Number {while 0 {x=2} return x}",
|
||||
@@ -530,6 +698,7 @@ fn control_flow_definite_initialization_large_jumps_and_loop_budgets() {
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
// 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。
|
||||
let (code, globals) = module(
|
||||
&mut vm,
|
||||
&compile(
|
||||
@@ -537,6 +706,7 @@ fn control_flow_definite_initialization_large_jumps_and_loop_budgets() {
|
||||
ParseMode::Module,
|
||||
),
|
||||
);
|
||||
// 无限循环没有返回出口,必须由累计指令预算终止,之后 cleanup 验证错误清理。
|
||||
assert!(matches!(
|
||||
vm.call_with_limits(
|
||||
function(&vm, globals, "main"),
|
||||
|
||||
+377
-10
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,7 @@
|
||||
//! eval 创建、推进并清理执行状态的数据结构;这里不执行指令,也不自动释放复制句柄。
|
||||
//! 活跃帧及 VM 内的只读续执行帧用外部执行根保留对象;停放到 Coroutine 载荷后改由 trace 边保留。
|
||||
//! Arc 只共享 Rust 帧/代码内存,根与内部边的转换由 eval 显式完成。
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{EndorCode, ObjectRef, Value};
|
||||
@@ -6,16 +10,21 @@ use crate::{EndorCode, ObjectRef, Value};
|
||||
/// Each Value::Object in a live frame owns one execution root.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum StackEntry {
|
||||
/// 调用协议占位,不能作为语言 Value 逃逸。
|
||||
Null,
|
||||
/// 活跃帧中对象值拥有执行根,Rust Clone 不替调用者增加计数。
|
||||
Value(Value),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct NativeReturn {
|
||||
/// 委托返回约束的诊断名,不代表按名字查回原函数。
|
||||
pub name: String,
|
||||
/// 需要保持的返回类型约束;句柄 clone 本身不 retain。
|
||||
pub expected: crate::CallType,
|
||||
}
|
||||
impl NativeReturn {
|
||||
/// eval 在普通/委托返回时对结果执行原生约束,错误保留诊断名和 return 位置。
|
||||
pub fn check(&self, store: &objects::ObjectStore, value: &Value) -> Result<(), crate::VmError> {
|
||||
self.expected
|
||||
.check(store, &self.name, "return".into(), value)
|
||||
@@ -25,46 +34,68 @@ impl NativeReturn {
|
||||
/// Internal activation record, analogous to _PyInterpreterFrame. It has no
|
||||
/// EndorObject header; introspection objects can be added independently later.
|
||||
#[derive(Debug)]
|
||||
/// eval 内部持有的源码帧;公开 getter 只能观察已有帧引用,当前 Vm 不提供活动帧枚举或借出入口。
|
||||
pub struct EndorFrame {
|
||||
/// 活跃帧拥有的函数根;函数强边使 code/globals/builtins 保持可达。
|
||||
pub(crate) function: ObjectRef,
|
||||
/// 已解析源码返回注解;含定义句柄时另持一份执行根。
|
||||
pub(crate) return_type: Option<crate::AnnotationType>,
|
||||
/// 原生委托链带来的返回检查,每项定义根由 eval 显式保留/释放。
|
||||
pub(crate) native_returns: Vec<NativeReturn>,
|
||||
/// Index of the caller in the execution's frame vector, never a pointer.
|
||||
pub(crate) previous: Option<usize>,
|
||||
/// 借用函数指向的托管代码身份,不额外作为独立执行根。
|
||||
pub(crate) code: ObjectRef,
|
||||
/// 共享不可变执行数据;不替代函数/代码的托管引用保活。
|
||||
pub(crate) executable: Arc<EndorCode>,
|
||||
/// 借用函数拥有的全局字典。
|
||||
pub(crate) globals: ObjectRef,
|
||||
/// 借用函数创建时捕获的内置字典,可为 None。
|
||||
pub(crate) builtins: Option<ObjectRef>,
|
||||
/// 本帧诊断名,回溯构造时复制。
|
||||
pub(crate) name: String,
|
||||
/// 下一条/恢复时将执行的代码单元偏移。
|
||||
pub(crate) instruction_offset: usize,
|
||||
/// 当前或最近执行的指令起点,错误回溯使用。
|
||||
pub(crate) current_offset: usize,
|
||||
/// 当前是否因 await Pending 停放;不是普通切片预算耗尽。
|
||||
pub(crate) waiting: bool,
|
||||
/// 局部和自由变量槽;None 为未绑定,每个对象值在活跃帧中拥有一根。
|
||||
pub(crate) localsplus: Vec<Option<Value>>,
|
||||
/// 求值栈对象项各拥有一根,Null 标记不拥有对象。
|
||||
pub(crate) stack: Vec<StackEntry>,
|
||||
}
|
||||
|
||||
impl EndorFrame {
|
||||
/// 宿主只读查询调用者帧索引,None 表示此帧无调用者。
|
||||
pub fn previous(&self) -> Option<usize> {
|
||||
self.previous
|
||||
}
|
||||
/// 宿主检查当前函数的借用身份,不新增根。
|
||||
pub fn function(&self) -> ObjectRef {
|
||||
self.function
|
||||
}
|
||||
/// 宿主查询托管代码借用身份,不复制执行数据。
|
||||
pub fn code(&self) -> ObjectRef {
|
||||
self.code
|
||||
}
|
||||
/// 宿主查询全局字典借用身份。
|
||||
pub fn globals(&self) -> ObjectRef {
|
||||
self.globals
|
||||
}
|
||||
/// 宿主查询已捕获内置空间借用身份,未配置为 None。
|
||||
pub fn builtins(&self) -> Option<ObjectRef> {
|
||||
self.builtins
|
||||
}
|
||||
/// 宿主查看下一次执行位置,单位为代码单元。
|
||||
pub fn instruction_offset(&self) -> usize {
|
||||
self.instruction_offset
|
||||
}
|
||||
/// 宿主借用槽快照视图,克隆对象 Value 不自动变成拥有根。
|
||||
pub fn localsplus(&self) -> &[Option<Value>] {
|
||||
&self.localsplus
|
||||
}
|
||||
/// 宿主/测试观察栈项数量,包含协议 Null。
|
||||
pub fn stack_depth(&self) -> usize {
|
||||
self.stack.len()
|
||||
}
|
||||
@@ -72,19 +103,26 @@ impl EndorFrame {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ExecutionLocation {
|
||||
/// 回溯的函数诊断名称。
|
||||
pub function: String,
|
||||
/// 回溯的源码文件标识,不访问磁盘。
|
||||
pub filename: String,
|
||||
/// 代码声明起始行,不是精确逐指令行表。
|
||||
pub first_line: usize,
|
||||
/// 故障指令的代码单元偏移,不是源码字符位置。
|
||||
pub instruction_offset: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ExecutionLimits {
|
||||
/// 嵌套源码帧硬上限,零可阻止源码帧执行。
|
||||
pub max_frames: usize,
|
||||
/// 累计执行/调用准备预算,跨切片继续累计。
|
||||
pub max_instructions: usize,
|
||||
}
|
||||
|
||||
impl Default for ExecutionLimits {
|
||||
/// 未指定预算时默认最多 1000 个源码帧,指令限制为 usize::MAX。
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_frames: 1000,
|
||||
@@ -97,22 +135,33 @@ impl Default for ExecutionLimits {
|
||||
/// owned value plus borrowed read dependencies. Instruction counts accumulate.
|
||||
#[derive(Debug)]
|
||||
pub enum ExecutionProgress {
|
||||
/// 只读软切片耗尽,帧和只读作用域仍保存于 VM 的单个续执行槽,不重放此前代码。
|
||||
Pending {
|
||||
/// 从执行开始累计的计数,不是当前切片的局部数量。
|
||||
instructions: usize,
|
||||
},
|
||||
/// 已完成;对象结果移交调用者一份拥有根,须通过 VM 归还。
|
||||
Complete {
|
||||
/// 完成时移出的拥有结果;直接标量没有托管根。
|
||||
value: Value,
|
||||
/// 借用读取身份,不保留被观察图。
|
||||
reads: Vec<ObjectRef>,
|
||||
/// 精确借用观察,含名称值快照也不建立拥有根。
|
||||
dependencies: Vec<crate::ReadDependency>,
|
||||
/// 从执行开始累计的计数,不是当前切片的局部数量。
|
||||
instructions: usize,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct SuspendedCall {
|
||||
/// 暂停的只读调用帧,执行根仍由 VM 持有。
|
||||
pub frames: Vec<EndorFrame>,
|
||||
/// 跨 Pending 保持活跃的 Heap 只读作用域令牌。
|
||||
pub scope: usize,
|
||||
/// 创建执行时固定的累计硬限制。
|
||||
pub limits: ExecutionLimits,
|
||||
/// 已消耗的累计指令/调用准备计数,不随每个 quantum 清零。
|
||||
pub executed: usize,
|
||||
}
|
||||
|
||||
@@ -120,44 +169,64 @@ pub(crate) struct SuspendedCall {
|
||||
/// not keep a VM-wide heap scope active between quanta.
|
||||
#[derive(Debug)]
|
||||
pub enum CoroutineProgress {
|
||||
/// await 尚未就绪,等待对象句柄借自仍停放的协程帧。
|
||||
Waiting {
|
||||
/// 待观察对象的借用身份,协程仍须保持有根才能保证它存活。
|
||||
awaitable: ObjectRef,
|
||||
/// 从执行开始累计的计数,不是当前切片的局部数量。
|
||||
instructions: usize,
|
||||
},
|
||||
/// 软切片耗尽,状态仍保存于 VM/托管协程中,不能重放此前代码。
|
||||
Pending {
|
||||
/// 从执行开始累计的计数,不是当前切片的局部数量。
|
||||
instructions: usize,
|
||||
},
|
||||
/// 已完成;对象结果移交调用者一份拥有根,须通过 VM 归还。
|
||||
Complete {
|
||||
/// 完成时移出的拥有结果;直接标量没有托管根。
|
||||
value: Value,
|
||||
/// 从执行开始累计的计数,不是当前切片的局部数量。
|
||||
instructions: usize,
|
||||
},
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum CoroutineState {
|
||||
/// 停放的帧快照,执行根已转为协程载荷强边。
|
||||
Frames(Arc<Vec<EndorFrame>>),
|
||||
/// 准备阶段原生调用已返回结果,作为载荷边等待 resume 移交。
|
||||
Ready(Value),
|
||||
/// eval 正推进帧的重入保护标记,执行根暂由 eval 持有。
|
||||
Running,
|
||||
/// 已完成或取消,不再持有帧/结果图。
|
||||
Finished,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Coroutine {
|
||||
/// 托管协程的当前状态,eval 用载荷替换提交状态变化。
|
||||
pub state: CoroutineState,
|
||||
/// 创建执行时固定的累计硬限制。
|
||||
pub limits: ExecutionLimits,
|
||||
/// 已消耗的累计指令/调用准备计数,不随每个 quantum 清零。
|
||||
pub executed: usize,
|
||||
}
|
||||
impl EndorFrame {
|
||||
/// Exactly the references held as execution roots while this frame runs.
|
||||
/// code/globals/builtins remain reachable through its owning function.
|
||||
/// eval 停放/恢复协程时枚举根身份,也是 Coroutine 载荷 trace 的内部边来源。
|
||||
pub(crate) fn trace_roots(&self, visit: &mut dyn FnMut(ObjectRef)) {
|
||||
visit(self.function);
|
||||
// 返回注解中只有名义/能力类别引用定义,这些根也要停放到协程图。
|
||||
if let Some(reference) = self.return_type.and_then(|kind| kind.reference()) {
|
||||
visit(reference);
|
||||
}
|
||||
// 每个委托约束都独立持有定义根,重复引用不能去重。
|
||||
for check in &self.native_returns {
|
||||
// 返回联合约束可能包含多个定义,按实际出现次数枚举完整引用集合。
|
||||
for reference in check.expected.references() {
|
||||
visit(reference);
|
||||
}
|
||||
}
|
||||
// 局部空槽和 Null 不持有根,其他对象槽逐项输出身份。
|
||||
for value in self
|
||||
.localsplus
|
||||
.iter()
|
||||
@@ -167,6 +236,7 @@ impl EndorFrame {
|
||||
StackEntry::Null => None,
|
||||
}))
|
||||
{
|
||||
// 直接数值/文本等不形成托管对象边。
|
||||
if let Value::Object(reference) = value {
|
||||
visit(*reference);
|
||||
}
|
||||
@@ -174,18 +244,24 @@ impl EndorFrame {
|
||||
}
|
||||
}
|
||||
impl crate::ObjectPayload for Coroutine {
|
||||
/// VM 从托管对象载荷下转型为 Coroutine 状态。
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
/// 协程协议错误与诊断的类别名称。
|
||||
fn kind_name(&self) -> &'static str {
|
||||
"coroutine"
|
||||
}
|
||||
/// 协程载荷无旧式原地赋值,普通引用重绑定不用改其状态。
|
||||
fn reference_assignment_only(&self) -> bool {
|
||||
true
|
||||
}
|
||||
/// Heap 分配/替换协程载荷时记录停放状态拥有边,运行/完成状态不保留帧图。
|
||||
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
|
||||
// 停放帧与准备结果拥有图,Running/Finished 不重复登记执行器当前持有的根。
|
||||
match &self.state {
|
||||
CoroutineState::Frames(frames) => {
|
||||
// 帧根枚举与 eval 恢复时的保护清单保持一致。
|
||||
for frame in frames.iter() {
|
||||
frame.trace_roots(visit);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! compiler 和宿主导入的 VM 公共入口;对象表示与堆操作从 objects 重导出,避免维护第二套类型。
|
||||
//! 两段示例分别展示显式根生命周期和直接执行已验证指令,文档测试使用同一公开 API。
|
||||
//! Embeddable wordcode VM backed by the independent objects crate. No parser or
|
||||
//! Python language type system; wordcode is not CPython bytecode compatible.
|
||||
//!
|
||||
@@ -38,14 +40,19 @@
|
||||
//! # Ok::<(), endor_core::VmError>(())
|
||||
//! ```
|
||||
|
||||
// 指令求值、调用准备、暂停/协程与根清理的内部实现。
|
||||
mod eval;
|
||||
// 执行帧、预算、回溯与进度状态结构;不定义另一套对象堆。
|
||||
mod frame;
|
||||
// VM 持有 Store 与执行状态,并提供宿主访问门面。
|
||||
mod vm;
|
||||
|
||||
// 公开执行观察和进度类型,内部 Coroutine 载荷状态不导出。
|
||||
pub use frame::{
|
||||
CoroutineProgress, EndorFrame, ExecutionLimits, ExecutionLocation, ExecutionProgress,
|
||||
};
|
||||
// Preserve existing embedding imports; objects owns these definitions.
|
||||
// 独立存储入口来自 objects,宿主可不创建求值器就使用。
|
||||
pub use objects::ObjectStore;
|
||||
pub use objects::{
|
||||
AnnotationType, AwaitPoll, ClassField, EndorTraitObject, EndorTypeObject, MethodRequirement,
|
||||
@@ -61,4 +68,5 @@ pub use objects::{
|
||||
NativeResult, ObjectChange, ObjectHeader, ObjectPayload, ObjectRef, Opcode, ReadAccess,
|
||||
ReadDependency, ReadSet, Record, SourceSignature, TrackedReads, Value, VmError, WeakObjectRef,
|
||||
};
|
||||
// 嵌入方启动/恢复调用和访问对象的主要入口。
|
||||
pub use vm::Vm;
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
//! 嵌入宿主及 eval 共用的 VM 门面;本文件多数方法直接委托 ObjectStore,不重复实现堆规则。
|
||||
//! 指令执行/调用/暂停在 eval,状态类型在 frame;这里仅持有状态并暴露受控对象 API。
|
||||
//! new_*/allocate/成功 upgrade 返回拥有根;get_attr/cell_get/dict_get 等读取仍为借用句柄。
|
||||
|
||||
use objects::ObjectStore;
|
||||
|
||||
use crate::{
|
||||
@@ -9,36 +13,49 @@ use crate::{
|
||||
/// reference accounting and builtin operations live in the objects crate.
|
||||
#[derive(Debug)]
|
||||
pub struct Vm {
|
||||
// 唯一对象图与受控堆门面,对象规则不在 VM 重复实现。
|
||||
pub(crate) objects: ObjectStore,
|
||||
// 最近执行失败的回溯快照,eval 启动新调用时清空。
|
||||
pub(crate) last_traceback: Vec<ExecutionLocation>,
|
||||
// 源码参数/返回检查开关,与 Store 字段检查一起配置,默认关闭。
|
||||
pub(crate) source_type_checks: bool,
|
||||
// VM 最多保存一个只读续执行及其活跃只读作用域;可变协程另在托管载荷中。
|
||||
pub(crate) suspended_call: Option<crate::frame::SuspendedCall>,
|
||||
}
|
||||
|
||||
impl Vm {
|
||||
/// 宿主开启源码参数/返回及 Store 字段检查;只读调用期间不能改变配置。
|
||||
pub fn set_source_type_checks(&mut self, enabled: bool) -> Result<(), VmError> {
|
||||
// 配置会影响后续语义,不能由只读调用改变,即使写入相同值也拒绝。
|
||||
if self.objects.is_read_only() {
|
||||
return Err(VmError::UnsupportedOperation(
|
||||
"configure source type checks during read-only call",
|
||||
));
|
||||
}
|
||||
// 先让 Store 接受字段配置,成功后才更新 VM 的源码检查开关。
|
||||
self.objects.set_field_type_checks(enabled)?;
|
||||
self.source_type_checks = enabled;
|
||||
Ok(())
|
||||
}
|
||||
/// Known whole-call effect; unproved source/delegated calls return None.
|
||||
/// 宿主调度/契约查询当前整次调用效果;无法证明或动态委托返回 None,不代表确定写入。
|
||||
pub fn call_effect(&self, function: ObjectRef) -> Result<Option<objects::CallEffect>, VmError> {
|
||||
self.objects.call_effect(function)
|
||||
}
|
||||
|
||||
/// 宿主参数检查委托 Store,统一源码/原生数量区间并扣除已绑定接收者。
|
||||
/// None 表示未发布数量元数据,不能仅凭此结果断定该对象可以调用。
|
||||
pub fn call_arity(&self, function: ObjectRef) -> Result<Option<objects::CallArity>, VmError> {
|
||||
self.objects.call_arity(function)
|
||||
}
|
||||
|
||||
/// 宿主初始化 Function 名义类型配置,Store 保存弱引用用于后续函数分配。
|
||||
/// 同一身份可重复设置,配置后不能换成另一类型;存活由宿主根或已附着对象等其他拥有图保证。
|
||||
pub fn set_function_type(&mut self, definition: ObjectRef) -> Result<(), VmError> {
|
||||
self.objects.set_function_type(definition)
|
||||
}
|
||||
|
||||
/// 宿主注册最小方法数量能力,返回一份外部拥有根,要求数量不含 self。
|
||||
pub fn new_trait(
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
@@ -46,6 +63,7 @@ impl Vm {
|
||||
) -> Result<ObjectRef, VmError> {
|
||||
self.objects.new_trait(name, requirements)
|
||||
}
|
||||
/// 宿主注册含 self 的完整参数/返回/效果能力,成功返回一份拥有根。
|
||||
pub fn new_typed_trait(
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
@@ -53,6 +71,7 @@ impl Vm {
|
||||
) -> Result<ObjectRef, VmError> {
|
||||
self.objects.new_typed_trait(name, requirements)
|
||||
}
|
||||
/// 宿主给类型显式声明能力,Store 先校验后代方法和已有实例再提交。
|
||||
pub fn implement_trait(
|
||||
&mut self,
|
||||
definition: ObjectRef,
|
||||
@@ -60,6 +79,7 @@ impl Vm {
|
||||
) -> Result<(), VmError> {
|
||||
self.objects.implement_trait(definition, capability)
|
||||
}
|
||||
/// 宿主查询类型显式/继承声明,命中还会重验当前契约,不做结构猜测。
|
||||
pub fn implements_trait(
|
||||
&self,
|
||||
definition: ObjectRef,
|
||||
@@ -67,10 +87,12 @@ impl Vm {
|
||||
) -> Result<bool, VmError> {
|
||||
self.objects.implements_trait(definition, capability)
|
||||
}
|
||||
/// 宿主/UI bridge 查询值级能力,同时核对有效类型及实例存储遮蔽。
|
||||
pub fn supports(&self, object: ObjectRef, capability: ObjectRef) -> Result<bool, VmError> {
|
||||
self.objects.supports(object, capability)
|
||||
}
|
||||
|
||||
/// 宿主注册或替换方法,Store 校验能力/覆盖并维护方法强边,保留类型身份。
|
||||
pub fn define_method(
|
||||
&mut self,
|
||||
type_object: ObjectRef,
|
||||
@@ -82,15 +104,18 @@ impl Vm {
|
||||
|
||||
/// Owned result; release_value when finished. Unlike get_attr, methods bind
|
||||
/// their receiver and retain it until the bound method itself is released.
|
||||
/// eval LoadAttr/宿主取得拥有结果;方法包装持有原接收者,结果用 release_value 归还。
|
||||
pub fn bind_attr(&mut self, object: ObjectRef, name: &str) -> Result<Value, VmError> {
|
||||
self.objects.bind_attr(object, name)
|
||||
}
|
||||
|
||||
/// 宿主显式自举 Object 与 Type,返回两份根;Vm::new 不隐式调用它。
|
||||
pub fn bootstrap_types(&mut self) -> Result<crate::RootTypes, VmError> {
|
||||
self.objects.bootstrap_types()
|
||||
}
|
||||
|
||||
/// Host registration uses the same validated class builder as source code.
|
||||
/// 宿主使用与源码相同的类构造校验,继承字段在前,失败不交付半成品。
|
||||
pub fn new_class(
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
@@ -101,6 +126,7 @@ impl Vm {
|
||||
self.objects.new_class(name, base, fields, methods)
|
||||
}
|
||||
|
||||
/// 宿主构造名义派生类型及实例分配策略,返回一份拥有根。
|
||||
pub fn new_type(
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
@@ -110,6 +136,7 @@ impl Vm {
|
||||
self.objects.new_type(name, base, constructor)
|
||||
}
|
||||
|
||||
/// 宿主/构造路径附着不可替换的名义类型,能力和只读屏障由 Store/Heap 检查。
|
||||
pub fn attach_type(
|
||||
&mut self,
|
||||
object: ObjectRef,
|
||||
@@ -118,27 +145,33 @@ impl Vm {
|
||||
self.objects.attach_type(object, type_object)
|
||||
}
|
||||
|
||||
/// 宿主/执行器查询有效名义类型,遵循 type_target 转发;返回句柄借用,无类型为 None。
|
||||
pub fn type_of(&self, object: ObjectRef) -> Result<Option<ObjectRef>, VmError> {
|
||||
self.objects.type_of(object)
|
||||
}
|
||||
|
||||
/// 宿主类型检查沿固定基类链按身份判定,包含自反关系。
|
||||
pub fn is_subtype(&self, candidate: ObjectRef, expected: ObjectRef) -> Result<bool, VmError> {
|
||||
self.objects.is_subtype(candidate, expected)
|
||||
}
|
||||
|
||||
/// 宿主值类型检查以有效类型判定,无名义类型对象为 false。
|
||||
pub fn is_instance(&self, object: ObjectRef, expected: ObjectRef) -> Result<bool, VmError> {
|
||||
self.objects.is_instance(object, expected)
|
||||
}
|
||||
|
||||
/// Read-only access for embedding protocols sharing the same object graph.
|
||||
/// 宿主共享同一对象图的只读协议入口,不提供绕过写屏障的可变存储。
|
||||
pub fn object_store(&self) -> &ObjectStore {
|
||||
&self.objects
|
||||
}
|
||||
|
||||
/// 宿主缓存弱身份,不新增计数、不消耗当前拥有根;句柄仍须先有效。
|
||||
pub fn downgrade(&self, reference: ObjectRef) -> Result<crate::WeakObjectRef, VmError> {
|
||||
self.objects.downgrade(reference)
|
||||
}
|
||||
|
||||
/// 宿主尝试把弱身份转为一份新拥有根;过期返回 None,异堆仍报错。
|
||||
pub fn upgrade(
|
||||
&mut self,
|
||||
reference: crate::WeakObjectRef,
|
||||
@@ -146,14 +179,17 @@ impl Vm {
|
||||
self.objects.upgrade(reference)
|
||||
}
|
||||
|
||||
/// 宿主渲染/模型缓存取走累计变更与溢出标志,通知本身不保留源对象。
|
||||
pub fn take_changes(&mut self) -> crate::ChangeBatch {
|
||||
self.objects.take_changes()
|
||||
}
|
||||
|
||||
/// 缓存观察当前对象版本;读取会经过 Store 的依赖收集入口。
|
||||
pub fn change_version(&self, reference: ObjectRef) -> Result<u64, VmError> {
|
||||
self.objects.change_version(reference)
|
||||
}
|
||||
|
||||
/// 宿主受控提交载荷及 trace 边快照,稳定对象身份,先校验/保留新边再释放旧边。
|
||||
pub fn replace_payload(
|
||||
&mut self,
|
||||
reference: ObjectRef,
|
||||
@@ -162,7 +198,9 @@ impl Vm {
|
||||
self.objects.replace_payload(reference, payload)
|
||||
}
|
||||
|
||||
/// 嵌入方创建空 Store 和空执行状态;不注册类型、函数或语言全局空间。
|
||||
pub fn new() -> Result<Self, VmError> {
|
||||
// 空状态与 Store::new 保持一致;对象/环境由调用方显式分配,执行入口按需准备帧。
|
||||
Ok(Self {
|
||||
objects: ObjectStore::new()?,
|
||||
last_traceback: Vec::new(),
|
||||
@@ -171,56 +209,69 @@ impl Vm {
|
||||
})
|
||||
}
|
||||
|
||||
/// 宿主/测试查询当前存活堆对象数,不包括直接 Value 标量。
|
||||
pub fn object_count(&self) -> usize {
|
||||
self.objects.object_count()
|
||||
}
|
||||
|
||||
/// Returns one external strong reference. Copies of the returned handle
|
||||
/// are borrowed; use retain/release to manage additional owned references.
|
||||
/// 宿主/内部构造载荷的统一门面,Store 负责内置校验、强边计数与函数类型附着。
|
||||
pub fn allocate(&mut self, payload: impl ObjectPayload) -> Result<ObjectRef, VmError> {
|
||||
self.objects.allocate(payload)
|
||||
}
|
||||
|
||||
/// 宿主/原生构造器创建可附动态字段的 Record,返回一份根,类型可另附着。
|
||||
pub fn new_object(&mut self) -> Result<ObjectRef, VmError> {
|
||||
self.objects.new_object()
|
||||
}
|
||||
|
||||
/// eval/宿主借用完整对象,验证句柄并收集整对象读取,不新增根。
|
||||
pub fn object(&self, reference: ObjectRef) -> Result<&EndorObject, VmError> {
|
||||
self.objects.object(reference)
|
||||
}
|
||||
|
||||
/// 宿主/执行槽取得额外外部根,复制 ObjectRef 本身不会自动执行此操作。
|
||||
pub fn retain(&mut self, reference: ObjectRef) -> Result<(), VmError> {
|
||||
self.objects.retain(reference)
|
||||
}
|
||||
|
||||
/// 宿主/执行槽归还一份拥有根,零强计数时级联释放;不能归还仅借用的内部边。
|
||||
pub fn release(&mut self, reference: ObjectRef) -> Result<(), VmError> {
|
||||
self.objects.release(reference)
|
||||
}
|
||||
|
||||
/// 测试/诊断读总强计数,包含外部根和堆内边,不是仅宿主引用数。
|
||||
pub fn ref_count(&self, reference: ObjectRef) -> Result<usize, VmError> {
|
||||
self.objects.ref_count(reference)
|
||||
}
|
||||
|
||||
/// 宿主/执行器先验证两端再比较完整身份,不调用数值或载荷相等协议。
|
||||
pub fn is_same(&self, left: ObjectRef, right: ObjectRef) -> Result<bool, VmError> {
|
||||
self.objects.is_same(left, right)
|
||||
}
|
||||
|
||||
/// 宿主/错误显示委托载荷诊断表示,不运行源码 repr 方法。
|
||||
pub fn repr(&self, reference: ObjectRef) -> Result<String, VmError> {
|
||||
self.objects.repr(reference)
|
||||
}
|
||||
|
||||
/// 宿主显示文本协议,默认载荷可回退 repr,不自动转为语言调用。
|
||||
pub fn str(&self, reference: ObjectRef) -> Result<String, VmError> {
|
||||
self.objects.str(reference)
|
||||
}
|
||||
|
||||
/// eval 条件分支/宿主走载荷真值协议,协议失败保持错误。
|
||||
pub fn is_true(&self, reference: ObjectRef) -> Result<bool, VmError> {
|
||||
self.objects.is_true(reference)
|
||||
}
|
||||
|
||||
/// 字典键/宿主走载荷哈希,数值等未实现种类可明确拒绝。
|
||||
pub fn hash(&self, reference: ObjectRef) -> Result<u64, VmError> {
|
||||
self.objects.hash(reference)
|
||||
}
|
||||
|
||||
/// eval 对象比较委托 Store 的正向、反向及身份回退;直接数值另走 eval 数学比较。
|
||||
pub fn compare(
|
||||
&self,
|
||||
left: ObjectRef,
|
||||
@@ -232,12 +283,14 @@ impl Vm {
|
||||
|
||||
/// Reads a borrowed value. An Object result needs retain() if the host
|
||||
/// wants it to survive removal of the attribute that currently owns it.
|
||||
/// 宿主借用属性值,方法以函数句柄返回,不分配接收者绑定对象。
|
||||
pub fn get_attr(&self, reference: ObjectRef, name: &str) -> Result<Value, VmError> {
|
||||
self.objects.get_attr(reference, name)
|
||||
}
|
||||
|
||||
/// Object-valued attributes own internal strong references independently
|
||||
/// of the caller's external references. Failure leaves the field unchanged.
|
||||
/// 宿主/执行器写属性,值对象由字段独立强边持有,不消耗调用者外部根。
|
||||
pub fn set_attr(
|
||||
&mut self,
|
||||
reference: ObjectRef,
|
||||
@@ -247,20 +300,24 @@ impl Vm {
|
||||
self.objects.set_attr(reference, name, value)
|
||||
}
|
||||
|
||||
/// 宿主/执行器删除属性,经类型字段保护和转发定位后释放原值内部边。
|
||||
pub fn del_attr(&mut self, reference: ObjectRef, name: &str) -> Result<(), VmError> {
|
||||
self.objects.del_attr(reference, name)
|
||||
}
|
||||
|
||||
/// eval 验证借用 Value 中对象句柄的堆归属和存活,不 retain 或新增读取依赖。
|
||||
pub(crate) fn validate_value(&self, value: &Value) -> Result<(), VmError> {
|
||||
self.objects.validate_value(value)
|
||||
}
|
||||
|
||||
/// Explicit full collection. Execution slots use owned roots, so values
|
||||
/// on live execution stacks participate in the same root accounting.
|
||||
/// 宿主显式回收外部根不可达的环;活跃帧根与停放协程边参与同一对象图计数。
|
||||
pub fn collect_cycles(&mut self) -> usize {
|
||||
self.objects.collect_cycles()
|
||||
}
|
||||
|
||||
/// eval/宿主借用具体 Rust 载荷,expected 只是错误标签,不等于名义类型注解。
|
||||
pub fn payload<T: ObjectPayload>(
|
||||
&self,
|
||||
reference: ObjectRef,
|
||||
@@ -269,14 +326,17 @@ impl Vm {
|
||||
self.objects.payload::<T>(reference, expected)
|
||||
}
|
||||
|
||||
/// 宿主创建不可变堆字符串,返回拥有根;与直接 Value::Text 区分。
|
||||
pub fn new_string(&mut self, text: impl Into<String>) -> Result<ObjectRef, VmError> {
|
||||
self.objects.new_string(text)
|
||||
}
|
||||
|
||||
/// 编译器加载/宿主把已校验 EndorCode 分配成托管代码对象,常量强边由 Store 维护。
|
||||
pub fn new_code(&mut self, code: EndorCode) -> Result<ObjectRef, VmError> {
|
||||
self.objects.new_code(code)
|
||||
}
|
||||
|
||||
/// eval 执行入口/闭包构造与宿主创建源码函数,Store 校验代码/默认值/捕获槽并捕获内置空间。
|
||||
pub fn new_function(
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
@@ -289,23 +349,28 @@ impl Vm {
|
||||
.new_function(name, code, globals, defaults, closure)
|
||||
}
|
||||
|
||||
/// 宿主创建堆数值对象,返回拥有根;直接 Value::Number 不占该堆槽。
|
||||
pub fn new_number(&mut self, number: EndorNumber) -> Result<ObjectRef, VmError> {
|
||||
self.objects.new_number(number)
|
||||
}
|
||||
|
||||
/// eval 字典构造/宿主创建空插入顺序字典,返回拥有根。
|
||||
pub fn new_dict(&mut self) -> Result<ObjectRef, VmError> {
|
||||
self.objects.new_dict()
|
||||
}
|
||||
|
||||
/// Returns borrowed keys and values in insertion order.
|
||||
/// eval 构类及宿主借用插入顺序条目,观察整个字典,不新增对象根。
|
||||
pub fn dict_items(&self, dictionary: ObjectRef) -> Result<&[(Value, Value)], VmError> {
|
||||
self.objects.dict_items(dictionary)
|
||||
}
|
||||
|
||||
/// eval/宿主通用键读取,缺键 None 与已存 Nil 分开,结果对象句柄仍借用。
|
||||
pub fn dict_get(&self, dictionary: ObjectRef, key: &Value) -> Result<Option<Value>, VmError> {
|
||||
self.objects.dict_get(dictionary, key)
|
||||
}
|
||||
|
||||
/// eval 构图/全局赋值与宿主写字典,Store 保留首键及插入顺序并事务维护强边。
|
||||
pub fn dict_set(
|
||||
&mut self,
|
||||
dictionary: ObjectRef,
|
||||
@@ -315,27 +380,33 @@ impl Vm {
|
||||
self.objects.dict_set(dictionary, key, value)
|
||||
}
|
||||
|
||||
/// eval 删除全局名/宿主删除键,缺键 false,不存在时不产生替换通知。
|
||||
pub fn dict_delete(&mut self, dictionary: ObjectRef, key: &Value) -> Result<bool, VmError> {
|
||||
self.objects.dict_delete(dictionary, key)
|
||||
}
|
||||
|
||||
/// 宿主清空字典,保留对象身份,旧键值边由 Heap 释放;空字典也受写屏障。
|
||||
pub fn dict_clear(&mut self, dictionary: ObjectRef) -> Result<(), VmError> {
|
||||
self.objects.dict_clear(dictionary)
|
||||
}
|
||||
|
||||
/// eval 把局部提升为闭包共享槽时创建,None 为未初始化,Some(Nil) 是已初始化。
|
||||
pub fn new_cell(&mut self, value: Option<Value>) -> Result<ObjectRef, VmError> {
|
||||
self.objects.new_cell(value)
|
||||
}
|
||||
|
||||
/// Reads a borrowed value; an empty cell is not Value::Nil.
|
||||
/// eval 自由变量读取/宿主借用槽值,空槽返回 EmptyCell,不把 Nil 当未初始化。
|
||||
pub fn cell_get(&self, cell: ObjectRef) -> Result<Value, VmError> {
|
||||
self.objects.cell_get(cell)
|
||||
}
|
||||
|
||||
/// eval 捕获变量赋值/宿主更新共享槽,保留 Cell 身份,引用边由 Heap 替换。
|
||||
pub fn cell_set(&mut self, cell: ObjectRef, value: Value) -> Result<(), VmError> {
|
||||
self.objects.cell_set(cell, value)
|
||||
}
|
||||
|
||||
/// eval 删除捕获变量/宿主恢复空槽,释放旧值边,不把它替换为 Some(Nil)。
|
||||
pub fn cell_clear(&mut self, cell: ObjectRef) -> Result<(), VmError> {
|
||||
self.objects.cell_clear(cell)
|
||||
}
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
//! 测试框架调用的内置值/载荷集成回归,通过公开 Vm API 验证 objects 与执行器协议一致。
|
||||
//! 各场景区分 Rust 表示相等、语言数值/内容比较、外部拥有根和容器内部强边。
|
||||
//! 部分场景只核对局部协议并以 VM 析构收尾;显式释放/环回收场景另检查句柄失效、计数或空堆。
|
||||
|
||||
use endor_core::{
|
||||
CodeLayout, CompareOp, EndorBaseExceptionObject, EndorCode, EndorDictObject,
|
||||
EndorFunctionObject, EndorInteger, EndorNativeFunctionObject, EndorNumber, EndorNumberObject,
|
||||
EndorReal, EndorStringObject, EndorTracebackEntry, Instruction, Opcode, Value, Vm, VmError,
|
||||
};
|
||||
|
||||
/// 测试数据辅助:构造直接 Text,不分配堆字符串对象。
|
||||
fn text(value: &str) -> Value {
|
||||
Value::Text(value.into())
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证整数规范化、任意长字面量保存、小数表示差异、分母符号和非法词法。
|
||||
fn numbers_preserve_large_digits_decimal_scale_exponents_and_fraction_parts() {
|
||||
// 超过 i64 的数字串仍可精确保存,只有显式 as_i64 收窄失败。
|
||||
let huge = "123456789012345678901234567890123456789012345678901234567890";
|
||||
assert_eq!(EndorNumber::integer(huge).unwrap().to_string(), huge);
|
||||
assert_eq!(EndorNumber::integer("-000").unwrap().to_string(), "0");
|
||||
assert_eq!(EndorNumber::integer("+0012").unwrap().as_i64(), Some(12));
|
||||
assert_eq!(EndorNumber::integer(huge).unwrap().as_i64(), None);
|
||||
// 逐个验证合法小数原样保存,包括负零、尾零、边界小数点和超长指数。
|
||||
for literal in [
|
||||
"-0.0000",
|
||||
"1.2300000000000000000001",
|
||||
@@ -34,18 +42,24 @@ fn numbers_preserve_large_digits_decimal_scale_exponents_and_fraction_parts() {
|
||||
);
|
||||
assert_eq!(EndorNumber::rational("0", "-6").unwrap().to_string(), "0/6");
|
||||
assert!(EndorNumber::rational("1", "0").is_err());
|
||||
// 逐个拒绝非法词法,不忽略空白或把 Unicode 数字当 ASCII 数字。
|
||||
for invalid in ["", "+", " 1", "1 ", "1_000", "12", "1.0"] {
|
||||
assert!(EndorNumber::integer(invalid).is_err(), "{invalid}");
|
||||
}
|
||||
// 逐个拒绝非法词法,不忽略空白或把 Unicode 数字当 ASCII 数字。
|
||||
for invalid in ["", ".", "1.2.3", "1e", "1e+", "1e2e3", "nan", " 0.1", "--1"] {
|
||||
assert!(EndorNumber::decimal(invalid).is_err(), "{invalid}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证 IEEE 位保真、零真值、复数显示,以及数值 hash/复数比较的明确拒绝;实数数学比较另有实现。
|
||||
fn number_objects_preserve_ieee_bits_and_reject_undefined_numeric_operations() {
|
||||
// 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 带特定 NaN payload 的 IEEE 位,验证存储不会规范化掉低位信息。
|
||||
let bits = 0x7ff8_0000_0000_0042;
|
||||
// 显式 float 构造保留位,而非按数学等价改写。
|
||||
let nan = EndorNumber::binary_float(f64::from_bits(bits));
|
||||
assert_eq!(nan.as_real(), Some(&EndorReal::BinaryFloat(bits)));
|
||||
assert_ne!(
|
||||
@@ -53,10 +67,12 @@ fn number_objects_preserve_ieee_bits_and_reject_undefined_numeric_operations() {
|
||||
EndorNumber::binary_float(0.0)
|
||||
);
|
||||
assert!(!EndorNumber::binary_float(f64::INFINITY).is_zero());
|
||||
// 超长指数的十进制负零仍为零,真值判断不需要进行指数运算。
|
||||
let zero = vm
|
||||
.new_number(EndorNumber::decimal("-0.000e99999999999").unwrap())
|
||||
.unwrap();
|
||||
assert!(!vm.is_true(zero).unwrap());
|
||||
// 复数两分量可保留不同精确形式,但不代表复数算术/比较已经实现。
|
||||
let complex = EndorNumber::complex(
|
||||
EndorReal::Integer(EndorInteger::parse("3").unwrap()),
|
||||
EndorNumber::rational("1", "3")
|
||||
@@ -65,6 +81,7 @@ fn number_objects_preserve_ieee_bits_and_reject_undefined_numeric_operations() {
|
||||
.unwrap()
|
||||
.clone(),
|
||||
);
|
||||
// 将复数放入堆载荷,验证通过 VM 协议访问的行为。
|
||||
let object = vm.new_number(complex).unwrap();
|
||||
assert_eq!(vm.str(object).unwrap(), "(3 + (1/3)i)");
|
||||
assert_eq!(
|
||||
@@ -85,11 +102,17 @@ fn number_objects_preserve_ieee_bits_and_reject_undefined_numeric_operations() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证字符串按 Unicode 标量计数/索引,按内容相等及哈希,空串为假。
|
||||
fn strings_use_unicode_characters_and_content_equality() {
|
||||
// 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 两个分别分配的字符串身份不同,内容/哈希协议仍应相等。
|
||||
let a = vm.new_string("Endor中文🦀").unwrap();
|
||||
// 相同内容的另一对象用于排除仅身份比较的错误实现。
|
||||
let b = vm.new_string("Endor中文🦀").unwrap();
|
||||
// 空字符串真值应为假。
|
||||
let empty = vm.new_string("").unwrap();
|
||||
// 借用具体载荷检查 Unicode 标量索引,不把 UTF-8 字节数当字符数。
|
||||
let string = vm.payload::<EndorStringObject>(a, "string").unwrap();
|
||||
assert_eq!(string.char_count(), 8);
|
||||
assert_eq!(string.char_at(7), Some('🦀'));
|
||||
@@ -101,14 +124,21 @@ fn strings_use_unicode_characters_and_content_equality() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证堆字符串键与直接文本等价时保留首键及顺序,覆盖/删除正确释放旧边。
|
||||
fn dictionaries_keep_order_and_first_key_and_own_keys_and_values() {
|
||||
// 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 字典返回一份外部根;插入对象键值时另持内部边。
|
||||
let dict = vm.new_dict().unwrap();
|
||||
// 堆字符串键之后与同内容直接 Text 比较,字典应保留此首键身份。
|
||||
let key = vm.new_string("answer").unwrap();
|
||||
// 覆盖前的对象值,归还外部根后只由字典持有,便于检查覆盖释放。
|
||||
let old = vm.new_object().unwrap();
|
||||
vm.dict_set(dict, Value::Object(key), Value::Object(old))
|
||||
.unwrap();
|
||||
// 归还测试根,后续键仍通过字典键边存活。
|
||||
vm.release(key).unwrap();
|
||||
// 归还旧值测试根,字典覆盖时应释放其最后强边。
|
||||
vm.release(old).unwrap();
|
||||
vm.dict_set(dict, text("second"), Value::Nil).unwrap();
|
||||
vm.dict_set(dict, text("answer"), text("replacement"))
|
||||
@@ -119,8 +149,10 @@ fn dictionaries_keep_order_and_first_key_and_own_keys_and_values() {
|
||||
);
|
||||
assert_eq!(vm.dict_items(dict).unwrap()[0].0, Value::Object(key));
|
||||
assert_eq!(vm.dict_items(dict).unwrap().len(), 2);
|
||||
// 字典覆盖旧值后,old 已无任何拥有根,访问必须报告失效句柄。
|
||||
assert!(matches!(vm.object(old), Err(VmError::InvalidHandle)));
|
||||
assert!(vm.dict_delete(dict, &text("answer")).unwrap());
|
||||
// 删除 answer 同时释放保留的首键对象,等价 Text 键不会替它延长寿命。
|
||||
assert!(matches!(vm.object(key), Err(VmError::InvalidHandle)));
|
||||
assert!(!vm.dict_delete(dict, &text("missing")).unwrap());
|
||||
vm.dict_clear(dict).unwrap();
|
||||
@@ -128,11 +160,17 @@ fn dictionaries_keep_order_and_first_key_and_own_keys_and_values() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证异堆/过期句柄、不可哈希键和错误容器种类不破坏既有值与计数。
|
||||
fn failed_container_writes_leave_values_and_counts_unchanged() {
|
||||
// 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 第二个 VM 提供异堆句柄,拒绝时不能把它保留在本堆容器。
|
||||
let mut other = Vm::new().unwrap();
|
||||
// 异堆对象始终由其所属 VM 管理,不允许跨堆建立托管边。
|
||||
let foreign = other.new_object().unwrap();
|
||||
// 字典返回一份外部根;插入对象键值时另持内部边。
|
||||
let dict = vm.new_dict().unwrap();
|
||||
// 子对象根加上字典/Cell 内部边应按实际拥有次数计数。
|
||||
let child = vm.new_object().unwrap();
|
||||
vm.dict_set(dict, text("x"), Value::Object(child)).unwrap();
|
||||
assert_eq!(
|
||||
@@ -152,13 +190,16 @@ fn failed_container_writes_leave_values_and_counts_unchanged() {
|
||||
vm.dict_set(dict, Value::Object(dict), Value::Nil),
|
||||
Err(VmError::UnsupportedOperation("dict hash"))
|
||||
);
|
||||
// Cell 创建即有一份外部根,初值对象额外成为内部边;None 则保持未初始化。
|
||||
let cell = vm.new_cell(Some(Value::Object(child))).unwrap();
|
||||
assert_eq!(
|
||||
vm.cell_set(cell, Value::Object(foreign)),
|
||||
Err(VmError::ForeignHandle)
|
||||
);
|
||||
assert_eq!(vm.cell_get(cell).unwrap(), Value::Object(child));
|
||||
// 分配后立即释放以制造真正的过期身份,不能误认为普通缺值。
|
||||
let dead = vm.new_object().unwrap();
|
||||
// 此后向 Cell 写入该身份必须报 InvalidHandle,不改变原值。
|
||||
vm.release(dead).unwrap();
|
||||
assert_eq!(
|
||||
vm.cell_set(cell, Value::Object(dead)),
|
||||
@@ -172,42 +213,59 @@ fn failed_container_writes_leave_values_and_counts_unchanged() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证 None 空槽与已存 Nil 不同,同对象重写不提前释放,clear 后旧值失效。
|
||||
fn cells_share_rebinding_distinguish_empty_and_none_and_keep_stable_objects() {
|
||||
// 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// Cell 创建即有一份外部根,初值对象额外成为内部边;None 则保持未初始化。
|
||||
let cell = vm.new_cell(None).unwrap();
|
||||
assert_eq!(vm.cell_get(cell), Err(VmError::EmptyCell));
|
||||
vm.cell_set(cell, Value::Nil).unwrap();
|
||||
assert_eq!(vm.cell_get(cell).unwrap(), Value::Nil);
|
||||
// 子对象根加上字典/Cell 内部边应按实际拥有次数计数。
|
||||
let child = vm.new_object().unwrap();
|
||||
vm.cell_set(cell, Value::Object(child)).unwrap();
|
||||
// 归还测试根后,容器/Cell 内部边仍须维持子对象存活。
|
||||
vm.release(child).unwrap();
|
||||
vm.cell_set(cell, Value::Object(child)).unwrap();
|
||||
assert_eq!(vm.ref_count(child).unwrap(), 1);
|
||||
vm.cell_clear(cell).unwrap();
|
||||
// 清空 Cell 归还子对象最后的内部边,child 不能仍然可访问。
|
||||
assert!(matches!(vm.object(child), Err(VmError::InvalidHandle)));
|
||||
assert_eq!(vm.cell_get(cell), Err(VmError::EmptyCell));
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证同一 Cell 被字典多键持有要计多条边,外部根移除后互引环整体回收。
|
||||
fn container_cycles_and_duplicate_edges_are_collected() {
|
||||
// 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 字典返回一份外部根;插入对象键值时另持内部边。
|
||||
let dict = vm.new_dict().unwrap();
|
||||
// Cell 创建即有一份外部根,初值对象额外成为内部边;None 则保持未初始化。
|
||||
let cell = vm.new_cell(Some(Value::Object(dict))).unwrap();
|
||||
vm.dict_set(dict, text("a"), Value::Object(cell)).unwrap();
|
||||
vm.dict_set(dict, text("b"), Value::Object(cell)).unwrap();
|
||||
assert_eq!(vm.ref_count(cell).unwrap(), 3);
|
||||
vm.release(dict).unwrap();
|
||||
// 归还 Cell 原根,闭包或字典边仍可持有它,最后无根环交给 GC。
|
||||
vm.release(cell).unwrap();
|
||||
assert_eq!(vm.collect_cycles(), 2);
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证代码常量、默认值、全局和共享捕获槽均有强边,闭包更新可见且无根环可回收。
|
||||
fn functions_retain_globals_constants_defaults_and_shared_closure_cells() {
|
||||
// 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 合法字典环境由两函数共同持有,稍后再反向保存第一个函数形成待回收的环境环。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 常量初始有测试根,代码常量表和默认值还会独立拥有强边。
|
||||
let constant = vm.new_string("constant").unwrap();
|
||||
// Cell 创建即有一份外部根,初值对象额外成为内部边;None 则保持未初始化。
|
||||
let cell = vm.new_cell(Some(text("first"))).unwrap();
|
||||
// 构造已验证代码元数据,只有分配代码载荷后才建立常量托管边。
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadDeref, 1),
|
||||
@@ -222,7 +280,9 @@ fn functions_retain_globals_constants_defaults_and_shared_closure_cells() {
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
// 托管代码对象有一份根,函数载荷还会额外持有代码边。
|
||||
let code = vm.new_code(code).unwrap();
|
||||
// 函数/原生函数分配结果由测试持有根,载荷内部引用另计。
|
||||
let function = vm
|
||||
.new_function(
|
||||
"read",
|
||||
@@ -232,15 +292,21 @@ fn functions_retain_globals_constants_defaults_and_shared_closure_cells() {
|
||||
vec![cell],
|
||||
)
|
||||
.unwrap();
|
||||
// 第二个函数共用代码/全局/Cell,但默认值布局不同,拥有边各自计数。
|
||||
let second = vm
|
||||
.new_function("read_again", code, globals, vec![], vec![cell])
|
||||
.unwrap();
|
||||
assert_eq!(vm.ref_count(constant).unwrap(), 3);
|
||||
// 释放代码测试根后函数仍持有代码内部边,不能使执行数据常量失效。
|
||||
vm.release(code).unwrap();
|
||||
// 常量原测试根归还,其余代码/默认值边仍保持存活。
|
||||
vm.release(constant).unwrap();
|
||||
// 两函数都已保存同一捕获 Cell,归还测试根后更新仍通过这两条内部边保持合法。
|
||||
vm.release(cell).unwrap();
|
||||
vm.cell_set(cell, text("updated")).unwrap();
|
||||
// 逐个读取两个函数捕获的同一 Cell,必须观察相同更新。
|
||||
for reference in [function, second] {
|
||||
// 借用函数载荷的闭包引用,不为读取额外保留根。
|
||||
let f = vm
|
||||
.payload::<EndorFunctionObject>(reference, "function")
|
||||
.unwrap();
|
||||
@@ -256,15 +322,21 @@ fn functions_retain_globals_constants_defaults_and_shared_closure_cells() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证函数全局种类/闭包数量及代码重复槽名错误不新增根或持有边。
|
||||
fn malformed_function_environments_are_rejected_without_retaining_any_edges() {
|
||||
// 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 故意以普通 Record 冒充全局字典,第一次函数构造应在保留边之前拒绝。
|
||||
let globals = vm.new_object().unwrap();
|
||||
// 最小装载常量并返回的逻辑指令,用于只改变环境或闭包布局的错误测试。
|
||||
let instructions = [
|
||||
Instruction::new(Opcode::LoadConst, 0),
|
||||
Instruction::simple(Opcode::ReturnValue),
|
||||
];
|
||||
// 构造已验证代码元数据,只有分配代码载荷后才建立常量托管边。
|
||||
let code = EndorCode::from_instructions(&instructions, vec![Value::Nil], CodeLayout::default())
|
||||
.unwrap();
|
||||
// 托管代码对象有一份根,函数载荷还会额外持有代码边。
|
||||
let code = vm.new_code(code).unwrap();
|
||||
assert_eq!(
|
||||
vm.new_function("bad", code, globals, vec![], vec![]),
|
||||
@@ -273,6 +345,7 @@ fn malformed_function_environments_are_rejected_without_retaining_any_edges() {
|
||||
assert_eq!(vm.ref_count(globals).unwrap(), 1);
|
||||
assert_eq!(vm.ref_count(code).unwrap(), 1);
|
||||
assert!(EndorCode::new(vec![], vec![], vec!["x".into(), "x".into()], vec![]).is_err());
|
||||
// 构造已验证代码元数据,只有分配代码载荷后才建立常量托管边。
|
||||
let code = EndorCode::from_instructions(
|
||||
&instructions,
|
||||
vec![Value::Nil],
|
||||
@@ -282,7 +355,9 @@ fn malformed_function_environments_are_rejected_without_retaining_any_edges() {
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
// 托管代码对象有一份根,函数载荷还会额外持有代码边。
|
||||
let code = vm.new_code(code).unwrap();
|
||||
// 第二次改用合法字典环境,只保留“代码需要一个自由 Cell、实传零个”的错误。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
assert!(
|
||||
vm.new_function("bad", code, globals, vec![], vec![])
|
||||
@@ -292,15 +367,20 @@ fn malformed_function_environments_are_rejected_without_retaining_any_edges() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证异常数据载荷的参数/链强边与注释/回溯元数据,错误 cause 种类被拒绝。
|
||||
fn exceptions_preserve_arguments_chains_notes_and_traceback_without_type_classes() {
|
||||
// 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 异常参数对象由 cause 的参数边持有,之后归还测试根仍须存活。
|
||||
let argument = vm.new_string("detail").unwrap();
|
||||
// cause 异常载荷持有参数;主异常的 cause/context 两项都指向它。
|
||||
let cause = vm
|
||||
.allocate(EndorBaseExceptionObject::new(
|
||||
"ValueError",
|
||||
vec![Value::Object(argument)],
|
||||
))
|
||||
.unwrap();
|
||||
// 同一 cause 被两条字段边拥有,注释/回溯文本不构成托管对象边。
|
||||
let exception = vm
|
||||
.allocate(
|
||||
EndorBaseExceptionObject::new("RuntimeError", vec![text("failed")])
|
||||
@@ -315,8 +395,11 @@ fn exceptions_preserve_arguments_chains_notes_and_traceback_without_type_classes
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(vm.ref_count(cause).unwrap(), 3);
|
||||
// 归还参数外部根,链上异常仍通过参数边持有它。
|
||||
vm.release(argument).unwrap();
|
||||
// 归还 cause 外部根,主异常两条链边继续保活。
|
||||
vm.release(cause).unwrap();
|
||||
// 借用异常元数据观察;不在此处抛出异常或自动创建语言异常类。
|
||||
let data = vm
|
||||
.payload::<EndorBaseExceptionObject>(exception, "exception")
|
||||
.unwrap();
|
||||
@@ -324,8 +407,10 @@ fn exceptions_preserve_arguments_chains_notes_and_traceback_without_type_classes
|
||||
assert_eq!(data.notes(), &["while loading"]);
|
||||
assert_eq!(data.traceback()[0].line, 12);
|
||||
assert_eq!(vm.str(exception).unwrap(), "failed");
|
||||
// 归还主异常最后根会级联释放 cause 和参数,无需手动重复 release 内部边。
|
||||
vm.release(exception).unwrap();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
// 字典不是异常,不能通过 with_cause 绕过内置校验。
|
||||
let wrong = vm.new_dict().unwrap();
|
||||
assert_eq!(
|
||||
vm.allocate(EndorBaseExceptionObject::new("Error", vec![]).with_cause(wrong)),
|
||||
@@ -333,7 +418,9 @@ fn exceptions_preserve_arguments_chains_notes_and_traceback_without_type_classes
|
||||
);
|
||||
}
|
||||
|
||||
/// 旧式原生回调借用返回原参数;VM 必须为最终对象结果另取拥有根。
|
||||
fn echo(arguments: &[Value]) -> Result<Value, VmError> {
|
||||
// 回调只接受一个参数,clone 不增加计数,最终拥有根由 VM 原生调用路径取得。
|
||||
match arguments {
|
||||
[value] => Ok(value.clone()),
|
||||
_ => Err(VmError::InvalidArgument("expected one value")),
|
||||
@@ -341,20 +428,27 @@ fn echo(arguments: &[Value]) -> Result<Value, VmError> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证原生传参不损失数值表示,即使原样返回对象参数也要独立结果根。
|
||||
fn native_functions_pass_lossless_numbers_and_return_owned_objects() {
|
||||
// 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 函数/原生函数分配结果由测试持有根,载荷内部引用另计。
|
||||
let function = vm
|
||||
.allocate(EndorNativeFunctionObject::new("echo", echo))
|
||||
.unwrap();
|
||||
// 精确小数作为直接参数,经原生回调传回仍保留全部数字和尾零。
|
||||
let number = Value::Number(EndorNumber::decimal("12345678901234567890.000100").unwrap());
|
||||
assert_eq!(
|
||||
vm.call(function, std::slice::from_ref(&number)).unwrap(),
|
||||
number
|
||||
);
|
||||
// 字典返回一份外部根;插入对象键值时另持内部边。
|
||||
let dict = vm.new_dict().unwrap();
|
||||
// 调用结果与原参数身份相同,但对象结果必须额外有一份外部根。
|
||||
let returned = vm.call(function, &[Value::Object(dict)]).unwrap();
|
||||
assert_eq!(returned, Value::Object(dict));
|
||||
assert_eq!(vm.ref_count(dict).unwrap(), 2);
|
||||
// 只归还结果根,原 dict 根仍存在,计数应从二降为一。
|
||||
vm.release_value(returned).unwrap();
|
||||
assert_eq!(vm.ref_count(dict).unwrap(), 1);
|
||||
assert_eq!(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,4 @@
|
||||
//! 方法替换规则的集成回归:使用源码/原生完整签名,观察定义提交与调用前后契约边界。
|
||||
//! Compile here; execute only in the designated remote validation environment.
|
||||
use endor_core::{
|
||||
AnnotationType as T, CallEffect, CallSignature, CallType, CodeLayout, ContextNativeFunction,
|
||||
@@ -6,14 +7,21 @@ use endor_core::{
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// 测试辅助,生成先写 globals.ran=42 再返回 result 的源码方法,附显式 owner/参数/返回注解。
|
||||
fn source(
|
||||
vm: &mut Vm,
|
||||
// 借用全局字典,源码函数持环境边;副作用 ran 与动态注解名称都从这里观察。
|
||||
globals: ObjectRef,
|
||||
// 写入源码签名的方法所属类名,按接收者继承链解析该声明身份。
|
||||
owner: &str,
|
||||
// 含显式 self 的参数注解文本,也决定位置参数及局部槽数量。
|
||||
parameters: &[&str],
|
||||
// 返回注解文本,实际类型在注册/调用契约检查时解析。
|
||||
returns: &str,
|
||||
// 写 ran 后返回的常量;对象身份由托管代码常量边持有,不因 Value 移动自动 retain。
|
||||
result: Value,
|
||||
) -> ObjectRef {
|
||||
// 手工指令保证源码体可观测,参数/局部槽按注解数量保持一致。
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadConst, 0),
|
||||
@@ -36,13 +44,18 @@ fn source(
|
||||
method_owner: Some(owner.into()),
|
||||
})
|
||||
.unwrap();
|
||||
// 代码先分配拥有根,后续函数持有它的内部边。
|
||||
let code = vm.new_code(code).unwrap();
|
||||
// 辅助方法函数持有代码/全局,返回拥有根供类方法表引用。
|
||||
let function = vm
|
||||
.new_function("check", code, globals, vec![], vec![])
|
||||
.unwrap();
|
||||
// 归还辅助构造代码临时根,不释放函数内部拥有边。
|
||||
vm.release(code).unwrap();
|
||||
function
|
||||
}
|
||||
/// 测试辅助,以给定基类构造只声明 check 方法、无自有字段的源码类。
|
||||
/// base/function 均借用,成功返回新类拥有根并建立内部边;失败不消耗调用方持有的根。
|
||||
fn class(
|
||||
vm: &mut Vm,
|
||||
name: &str,
|
||||
@@ -58,11 +71,17 @@ fn class(
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 创建派生类即检查参数可接受集合不能缩窄、返回集合不能扩大。
|
||||
fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation() {
|
||||
// 独立 VM 及名义类型图,避免全局重绑定与定义修改跨测试污染。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 显式自举 Object/Type 根,最终归还并收集其类型图环。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 普通 override 检查显式开启,不能仅依赖能力 opt-in 强制规则。
|
||||
vm.set_source_type_checks(true).unwrap();
|
||||
// 当前源码方法自己的注解解析环境,同时保存 ran 副作用标志。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 父签名确定可接受参数集合和返回上界,方法自身类名由 owner 解析。
|
||||
let base_fn = source(
|
||||
&mut vm,
|
||||
globals,
|
||||
@@ -71,7 +90,9 @@ fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation()
|
||||
"Object",
|
||||
Value::Nil,
|
||||
);
|
||||
// 创建基类并提交父方法,自身没有父同名覆盖约束。
|
||||
let base = class(&mut vm, "Base", roots.object, base_fn).unwrap();
|
||||
// 参数 Number 放宽到 Object,返回 Object 收窄到 Number,符合可替换关系。
|
||||
let good_fn = source(
|
||||
&mut vm,
|
||||
globals,
|
||||
@@ -80,7 +101,9 @@ fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation()
|
||||
"Number",
|
||||
Value::Number(7.into()),
|
||||
);
|
||||
// 合法派生创建成功,保留其函数及基类引用。
|
||||
let good = class(&mut vm, "Good", base, good_fn).unwrap();
|
||||
// String 不能接纳父方法允许的 Number,所以即使返回约束合法也应拒绝。
|
||||
let narrow = source(
|
||||
&mut vm,
|
||||
globals,
|
||||
@@ -89,6 +112,7 @@ fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation()
|
||||
"Number",
|
||||
Value::Nil,
|
||||
);
|
||||
// 子方法缩窄参数可接受范围会破坏替换关系,必须在建类时报告覆盖违规。
|
||||
assert!(matches!(
|
||||
class(&mut vm, "Narrow", base, narrow),
|
||||
Err(VmError::OverrideViolation {
|
||||
@@ -96,6 +120,7 @@ fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation()
|
||||
..
|
||||
})
|
||||
));
|
||||
// 另建父方法要求返回 Number,用于单独测试返回协变方向。
|
||||
let numeric = source(
|
||||
&mut vm,
|
||||
globals,
|
||||
@@ -104,7 +129,9 @@ fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation()
|
||||
"Number",
|
||||
Value::Number(0.into()),
|
||||
);
|
||||
// 以返回 Number 的方法作为新基线,不和先前 Object 返回父方法混淆。
|
||||
let numeric_type = class(&mut vm, "Numeric", roots.object, numeric).unwrap();
|
||||
// 返回 Object 比 Number 更宽,派生实现不能让父调用者失去数值返回保证。
|
||||
let broad = source(
|
||||
&mut vm,
|
||||
globals,
|
||||
@@ -113,6 +140,7 @@ fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation()
|
||||
"Object",
|
||||
Value::Nil,
|
||||
);
|
||||
// 子方法放宽返回类型会破坏父方法承诺,必须在建类时拒绝。
|
||||
assert!(matches!(
|
||||
class(&mut vm, "Broad", numeric_type, broad),
|
||||
Err(VmError::OverrideViolation {
|
||||
@@ -120,6 +148,7 @@ fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation()
|
||||
..
|
||||
})
|
||||
));
|
||||
// 归还每份测试拥有根,类方法/基类/签名内部边由 Heap 维护。
|
||||
for object in [
|
||||
good,
|
||||
good_fn,
|
||||
@@ -135,16 +164,23 @@ fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation()
|
||||
] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 显式回收无根类型/函数图环,最后验证存活对象数为零。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 父方法拟议更新必须检查现有覆盖后代,失败保持父版本及双方原方法表。
|
||||
fn replacing_an_ancestor_validates_overriding_descendants_before_mutation() {
|
||||
// 独立 VM 及名义类型图,避免全局重绑定与定义修改跨测试污染。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 显式自举 Object/Type 根,最终归还并收集其类型图环。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 普通 override 检查显式开启,不能仅依赖能力 opt-in 强制规则。
|
||||
vm.set_source_type_checks(true).unwrap();
|
||||
// 当前源码方法自己的注解解析环境,同时保存 ran 副作用标志。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 父签名确定可接受参数集合和返回上界,方法自身类名由 owner 解析。
|
||||
let base_fn = source(
|
||||
&mut vm,
|
||||
globals,
|
||||
@@ -153,7 +189,9 @@ fn replacing_an_ancestor_validates_overriding_descendants_before_mutation() {
|
||||
"Object",
|
||||
Value::Nil,
|
||||
);
|
||||
// 创建基类并提交父方法,自身没有父同名覆盖约束。
|
||||
let base = class(&mut vm, "Base", roots.object, base_fn).unwrap();
|
||||
// 后代自己的签名需与当前父签名保持替换关系,即使后代已先创建。
|
||||
let child_fn = source(
|
||||
&mut vm,
|
||||
globals,
|
||||
@@ -162,7 +200,9 @@ fn replacing_an_ancestor_validates_overriding_descendants_before_mutation() {
|
||||
"Number",
|
||||
Value::Number(0.into()),
|
||||
);
|
||||
// 先创建当前合法后代,再对父方法提出可能使它失效的更新。
|
||||
let child = class(&mut vm, "Child", base, child_fn).unwrap();
|
||||
// 父参数从 Number 放宽到 Object 后,现有子方法仍只接收 Number,将不再可替换。
|
||||
let replacement = source(
|
||||
&mut vm,
|
||||
globals,
|
||||
@@ -171,6 +211,7 @@ fn replacing_an_ancestor_validates_overriding_descendants_before_mutation() {
|
||||
"Object",
|
||||
Value::Nil,
|
||||
);
|
||||
// 记录父定义版本,验证失败在真实载荷替换前发生。
|
||||
let version = vm.change_version(base).unwrap();
|
||||
assert!(matches!(
|
||||
vm.define_method(base, "check", replacement),
|
||||
@@ -185,6 +226,7 @@ fn replacing_an_ancestor_validates_overriding_descendants_before_mutation() {
|
||||
vm.object_store().lookup_method(child, "check").unwrap(),
|
||||
Some(child_fn)
|
||||
);
|
||||
// 归还每份测试拥有根,类方法/基类/签名内部边由 Heap 维护。
|
||||
for object in [
|
||||
replacement,
|
||||
child,
|
||||
@@ -197,27 +239,38 @@ fn replacing_an_ancestor_validates_overriding_descendants_before_mutation() {
|
||||
] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 显式回收无根类型/函数图环,最后验证存活对象数为零。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 类型名全局重绑定可破坏已有覆盖关系,调用前须重验并阻止体副作用,恢复绑定后可再调用。
|
||||
fn global_rebinding_is_rechecked_before_the_source_body_and_recovers() {
|
||||
// 独立 VM 及名义类型图,避免全局重绑定与定义修改跨测试污染。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 显式自举 Object/Type 根,最终归还并收集其类型图环。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 普通 override 检查显式开启,不能仅依赖能力 opt-in 强制规则。
|
||||
vm.set_source_type_checks(true).unwrap();
|
||||
// Result 基类型是父方法返回约束,ResultChild 初始满足其协变要求。
|
||||
let parent = vm
|
||||
.new_type("Result", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// 派生返回类型起初是父返回类型子类,后续全局绑定将故意破坏该关系。
|
||||
let result_type = vm
|
||||
.new_type("ResultChild", parent, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// 无关类型可实例化但不是 Result 子类,不能作为合法协变返回类型。
|
||||
let other = vm
|
||||
.new_type("Other", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// 实际返回对象先保留测试根,源码常量表也会持有它的边。
|
||||
let result = vm.new_object().unwrap();
|
||||
vm.attach_type(result, result_type).unwrap();
|
||||
// 父方法独立环境固定 Result,子环境 Actual 重绑定不应改变父约束。
|
||||
let base_globals = vm.new_dict().unwrap();
|
||||
// 当前源码方法自己的注解解析环境,同时保存 ran 副作用标志。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
vm.dict_set(
|
||||
base_globals,
|
||||
@@ -233,6 +286,7 @@ fn global_rebinding_is_rechecked_before_the_source_body_and_recovers() {
|
||||
.unwrap();
|
||||
vm.dict_set(globals, Value::Text("ran".into()), Value::Number(0.into()))
|
||||
.unwrap();
|
||||
// 父签名确定可接受参数集合和返回上界,方法自身类名由 owner 解析。
|
||||
let base_fn = source(
|
||||
&mut vm,
|
||||
base_globals,
|
||||
@@ -241,7 +295,9 @@ fn global_rebinding_is_rechecked_before_the_source_body_and_recovers() {
|
||||
"Result",
|
||||
Value::Nil,
|
||||
);
|
||||
// 创建基类并提交父方法,自身没有父同名覆盖约束。
|
||||
let base = class(&mut vm, "Base", roots.object, base_fn).unwrap();
|
||||
// 后代自己的签名需与当前父签名保持替换关系,即使后代已先创建。
|
||||
let child_fn = source(
|
||||
&mut vm,
|
||||
globals,
|
||||
@@ -250,11 +306,14 @@ fn global_rebinding_is_rechecked_before_the_source_body_and_recovers() {
|
||||
"Actual",
|
||||
Value::Object(result),
|
||||
);
|
||||
// 先创建当前合法后代;下面只重绑定其 Actual 注解名,不修改父方法或子方法载荷。
|
||||
let child = class(&mut vm, "Child", base, child_fn).unwrap();
|
||||
// 显式 self 为 Child 实例,调用时据其继承链解析各方法 own-class 注解。
|
||||
let receiver = vm.new_object().unwrap();
|
||||
vm.attach_type(receiver, child).unwrap();
|
||||
vm.dict_set(globals, Value::Text("Actual".into()), Value::Object(other))
|
||||
.unwrap();
|
||||
// Actual 的全局类型绑定变化后,调用前重新检查覆盖关系,失败时不能运行方法体。
|
||||
assert!(matches!(
|
||||
vm.call(child_fn, &[Value::Object(receiver)]),
|
||||
Err(VmError::OverrideViolation { .. })
|
||||
@@ -269,9 +328,12 @@ fn global_rebinding_is_rechecked_before_the_source_body_and_recovers() {
|
||||
Value::Object(result_type),
|
||||
)
|
||||
.unwrap();
|
||||
// 恢复合法全局绑定后调用成功,结果根仍须与原测试对象根分别归还。
|
||||
let value = vm.call(child_fn, &[Value::Object(receiver)]).unwrap();
|
||||
assert_eq!(value, Value::Object(result));
|
||||
// 只释放本次调用新增的结果根,源码常量及测试原根还在。
|
||||
vm.release_value(value).unwrap();
|
||||
// 归还每份测试拥有根,类方法/基类/签名内部边由 Heap 维护。
|
||||
for object in [
|
||||
receiver,
|
||||
child,
|
||||
@@ -289,34 +351,46 @@ fn global_rebinding_is_rechecked_before_the_source_body_and_recovers() {
|
||||
] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 显式回收无根类型/函数图环,最后验证存活对象数为零。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// 无捕获测试回调,签名通过包装载荷 trace 保活,不手动按定义身份 retain。
|
||||
struct Native(CallSignature);
|
||||
impl NativeCallable for Native {
|
||||
/// 包装载荷取得完整签名快照,定义引用由 ContextNativeFunction trace 持有。
|
||||
fn signature(&self) -> Option<CallSignature> {
|
||||
Some(self.0.clone())
|
||||
}
|
||||
/// 测试原生体只返回直接 Number;被拒绝场景主要检查声明的替换关系而非运行副作用。
|
||||
fn invoke(&self, _: &mut NativeContext<'_>, _: &[Value]) -> Result<NativeResult, VmError> {
|
||||
Ok(NativeResult::Return(Value::Number(0.into())))
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
/// 原生覆盖不能丢弃父联合参数分支,也不能把已知 ReadOnly 扩大为 Mutating。
|
||||
fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() {
|
||||
// 独立 VM 及名义类型图,避免全局重绑定与定义修改跨测试污染。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 显式自举 Object/Type 根,最终归还并收集其类型图环。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 普通 override 检查显式开启,不能仅依赖能力 opt-in 强制规则。
|
||||
vm.set_source_type_checks(true).unwrap();
|
||||
// 原生场景的名义 Base,签名首项按它校验接收者。
|
||||
let base = vm
|
||||
.new_type("Base", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// 先建立完整父签名,再把普通参数换为联合 Number | String。
|
||||
let mut signature = CallSignature::exact(
|
||||
vec![T::Nominal(base), T::Any],
|
||||
T::Number,
|
||||
CallEffect::ReadOnly,
|
||||
);
|
||||
// 保留两条可接受输入分支,子实现必须全部接纳。
|
||||
signature.parameters[1] = CallType::Union(vec![T::Number, T::String]);
|
||||
// ContextNativeFunction 签名快照持有 Base 定义,方法表又持函数边,最终可能成环。
|
||||
let base_fn = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"check",
|
||||
@@ -325,7 +399,9 @@ fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() {
|
||||
))
|
||||
.unwrap();
|
||||
vm.define_method(base, "check", base_fn).unwrap();
|
||||
// 接收者由 Base 派生,子签名仍须接受声明类型实例。
|
||||
let child = vm.new_type("Child", base, TypeConstructor::Record).unwrap();
|
||||
// 输入 Any 足够宽,但 Mutating 扩大父 ReadOnly 效果,必须拒绝。
|
||||
let effect_fn = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"effect",
|
||||
@@ -337,6 +413,7 @@ fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() {
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
// 只读父方法不能被已知有写效果的子方法替换,更新必须在提交前失败。
|
||||
assert!(matches!(
|
||||
vm.define_method(child, "check", effect_fn),
|
||||
Err(VmError::OverrideViolation {
|
||||
@@ -344,6 +421,7 @@ fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() {
|
||||
..
|
||||
})
|
||||
));
|
||||
// 效果保持只读却只接受 Number,丢失父 String 分支,同样拒绝。
|
||||
let narrow_fn = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"narrow",
|
||||
@@ -355,6 +433,7 @@ fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() {
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
// 子方法仅接受 Number 会丢失父联合参数的 String 候选,不能缩窄父方法可接受的输入。
|
||||
assert!(matches!(
|
||||
vm.define_method(child, "check", narrow_fn),
|
||||
Err(VmError::OverrideViolation {
|
||||
@@ -362,6 +441,7 @@ fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() {
|
||||
..
|
||||
})
|
||||
));
|
||||
// 归还每份测试拥有根,类方法/基类/签名内部边由 Heap 维护。
|
||||
for object in [
|
||||
narrow_fn,
|
||||
effect_fn,
|
||||
@@ -373,18 +453,25 @@ fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() {
|
||||
] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 显式回收无根类型/函数图环,最后验证存活对象数为零。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 构类时可暂缓未知前向注解,调用时必须解析并重验覆盖;未知和已知但不兼容都不能进入源码体。
|
||||
fn unknown_forward_names_defer_until_call_without_entering_the_body() {
|
||||
// 独立 VM 隔离全局与方法重绑定,避免其他场景状态干扰快照证明。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 显式类型自举根,用于实例与绑定方法测试,最终归还并收集类型环。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 启用普通覆盖验证,前向未知仅在定义阶段暂缓,调用阶段不豁免。
|
||||
vm.set_source_type_checks(true).unwrap();
|
||||
// 源码名查找环境,更新其中键可改变契约或调用图,不能把首次查询缓存为永久证明。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
vm.dict_set(globals, Value::Text("ran".into()), Value::Number(0.into()))
|
||||
.unwrap();
|
||||
// 父方法要求 Number 参数,子类尚未定义的 Later 参数暂缓到调用检查。
|
||||
let base_fn = source(
|
||||
&mut vm,
|
||||
globals,
|
||||
@@ -393,7 +480,9 @@ fn unknown_forward_names_defer_until_call_without_entering_the_body() {
|
||||
"Number",
|
||||
Value::Number(0.into()),
|
||||
);
|
||||
// 父定义先合法创建,后代契约需相对这份真实定义检查。
|
||||
let base = class(&mut vm, "Base", roots.object, base_fn).unwrap();
|
||||
// 子类使用尚未解析的 Later 名称,不代表可永久跳过参数替换验证。
|
||||
let child_fn = source(
|
||||
&mut vm,
|
||||
globals,
|
||||
@@ -402,7 +491,9 @@ fn unknown_forward_names_defer_until_call_without_entering_the_body() {
|
||||
"Number",
|
||||
Value::Number(0.into()),
|
||||
);
|
||||
// 定义阶段允许未知名称的派生创建,实例调用时会重新核对。
|
||||
let child = class(&mut vm, "Child", base, child_fn).unwrap();
|
||||
// 显式 self 使用 Child 实例,让调用预检按其继承链解析 Base/Child,再检查仍未知的 Later。
|
||||
let receiver = vm.new_object().unwrap();
|
||||
vm.attach_type(receiver, child).unwrap();
|
||||
assert!(matches!(
|
||||
@@ -412,11 +503,13 @@ fn unknown_forward_names_defer_until_call_without_entering_the_body() {
|
||||
),
|
||||
Err(VmError::UnknownAnnotation(_))
|
||||
));
|
||||
// Later 最终解析为独立名义类型,不接纳父参数 Number,故由未知错误变为覆盖不兼容。
|
||||
let later = vm
|
||||
.new_type("Later", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
vm.dict_set(globals, Value::Text("Later".into()), Value::Object(later))
|
||||
.unwrap();
|
||||
// Later 从未知名解析成不兼容名义类型后,错误应转为覆盖违规且仍在方法体前发生。
|
||||
assert!(matches!(
|
||||
vm.call(
|
||||
child_fn,
|
||||
@@ -428,6 +521,7 @@ fn unknown_forward_names_defer_until_call_without_entering_the_body() {
|
||||
vm.dict_get(globals, &Value::Text("ran".into())).unwrap(),
|
||||
Some(Value::Number(0.into()))
|
||||
);
|
||||
// 归还测试拥有根,内部方法/签名/类型边由堆维护,避免重复 release。
|
||||
for object in [
|
||||
receiver,
|
||||
child,
|
||||
@@ -441,16 +535,23 @@ fn unknown_forward_names_defer_until_call_without_entering_the_body() {
|
||||
] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 回收无根函数/全局/类型环,最终对象数应为零。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 已验证源码只读效果约束原生覆盖;Mutating/External 拒绝,Delegated 仍为未知而不是确定只读。
|
||||
fn verified_source_read_only_effects_constrain_native_overrides() {
|
||||
// 独立 VM 隔离全局与方法重绑定,避免其他场景状态干扰快照证明。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 显式类型自举根,用于实例与绑定方法测试,最终归还并收集类型环。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 启用普通覆盖验证,前向未知仅在定义阶段暂缓,调用阶段不豁免。
|
||||
vm.set_source_type_checks(true).unwrap();
|
||||
// 源码名查找环境,更新其中键可改变契约或调用图,不能把首次查询缓存为永久证明。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 手工指令使控制流和调用目标来源可精确观察,构造器先验证栈高/跳转。
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadFast, 1),
|
||||
@@ -471,26 +572,35 @@ fn verified_source_read_only_effects_constrain_native_overrides() {
|
||||
})
|
||||
.unwrap();
|
||||
assert!(code.is_statically_read_only());
|
||||
// 代码数据只验证与分配,call_effect 不应实际执行其中指令。
|
||||
let code = vm.new_code(code).unwrap();
|
||||
// 源码方法只装载参数并返回,可由本地白名单直接证明 ReadOnly。
|
||||
let source = vm
|
||||
.new_function("check", code, globals, vec![], vec![])
|
||||
.unwrap();
|
||||
// 父定义先合法创建,后代契约需相对这份真实定义检查。
|
||||
let base = class(&mut vm, "PureBase", roots.object, source).unwrap();
|
||||
// 后代继承已证明只读的方法,候选原生覆盖不能扩大已知效果。
|
||||
let child = vm.new_type("Child", base, TypeConstructor::Record).unwrap();
|
||||
assert_eq!(vm.call_effect(source).unwrap(), Some(CallEffect::ReadOnly));
|
||||
// 以 Child 实例创建绑定方法,核对绑定形式仍可证明只读;此处未绑定源码本身也已由白名单证明。
|
||||
let receiver = vm.new_object().unwrap();
|
||||
vm.attach_type(receiver, child).unwrap();
|
||||
// 绑定对象拥有函数和实际接收者边,分析可用 receiver 区分实例方法/字段。
|
||||
let Value::Object(bound) = vm.bind_attr(receiver, "check").unwrap() else {
|
||||
panic!("bound method")
|
||||
};
|
||||
assert_eq!(vm.call_effect(bound).unwrap(), Some(CallEffect::ReadOnly));
|
||||
// 保存所有候选原生函数拥有根,避免只清理最后成功安装的方法。
|
||||
let mut functions = vec![];
|
||||
// 四种原生声明分别测试;未知 Delegated 不等于允许在只读域执行任意目标。
|
||||
for effect in [
|
||||
CallEffect::Mutating,
|
||||
CallEffect::External,
|
||||
CallEffect::ReadOnly,
|
||||
CallEffect::Delegated,
|
||||
] {
|
||||
// 候选为原生包装器,仅签名引用类型形成 trace 边;效果查询读取其声明,不存在源码代码/全局字段。
|
||||
let function = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"check",
|
||||
@@ -502,8 +612,11 @@ fn verified_source_read_only_effects_constrain_native_overrides() {
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
// 失败更新不能改变后代定义版本,用于证明检查在提交方法表之前完成。
|
||||
let version = vm.change_version(child).unwrap();
|
||||
// 保存更新结果,再按已知效果是否扩大区分成功与拒绝。
|
||||
let update = vm.define_method(child, "check", function);
|
||||
// Mutating/External 扩大 ReadOnly;其余候选按当前规则检查与查询。
|
||||
if matches!(effect, CallEffect::Mutating | CallEffect::External) {
|
||||
assert!(matches!(
|
||||
update,
|
||||
@@ -522,6 +635,7 @@ fn verified_source_read_only_effects_constrain_native_overrides() {
|
||||
);
|
||||
functions.push(function);
|
||||
}
|
||||
// 归还测试拥有根,内部方法/签名/类型边由堆维护,避免重复 release。
|
||||
for object in functions.into_iter().chain([
|
||||
bound,
|
||||
receiver,
|
||||
@@ -535,12 +649,15 @@ fn verified_source_read_only_effects_constrain_native_overrides() {
|
||||
]) {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 回收无根函数/全局/类型环,最终对象数应为零。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 局部共享引用赋值可能修改旧对象,Cell 写入/未知调用也不能由本地白名单直接证明纯。
|
||||
fn shared_local_assignment_and_dynamic_calls_are_not_inferred_pure() {
|
||||
// StoreFast 可能触发旧对象共享引用赋值,不能仅因目标是局部变量就判纯。
|
||||
let assignment = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadFast, 1),
|
||||
@@ -557,6 +674,7 @@ fn shared_local_assignment_and_dynamic_calls_are_not_inferred_pure() {
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!assignment.is_statically_read_only());
|
||||
// StoreDeref 修改已有捕获 Cell,必须从本地只读白名单排除。
|
||||
let captured = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadConst, 0),
|
||||
@@ -572,6 +690,7 @@ fn shared_local_assignment_and_dynamic_calls_are_not_inferred_pure() {
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!captured.is_statically_read_only());
|
||||
// 动态 Call 没有本地目标证明,is_statically_read_only 不能直接通过。
|
||||
let call = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadGlobal, 0),
|
||||
@@ -589,7 +708,9 @@ fn shared_local_assignment_and_dynamic_calls_are_not_inferred_pure() {
|
||||
assert!(!call.is_statically_read_only());
|
||||
}
|
||||
|
||||
/// 效果测试辅助,手工建立调用指定全局名并返回的源码函数,不在查询时执行。
|
||||
fn global_caller(vm: &mut Vm, globals: ObjectRef, name: &str) -> ObjectRef {
|
||||
// 手工指令使控制流和调用目标来源可精确观察,构造器先验证栈高/跳转。
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadGlobal, 0),
|
||||
@@ -604,18 +725,25 @@ fn global_caller(vm: &mut Vm, globals: ObjectRef, name: &str) -> ObjectRef {
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
// 代码数据只验证与分配,call_effect 不应实际执行其中指令。
|
||||
let code = vm.new_code(code).unwrap();
|
||||
// 函数持有其代码/全局边,效果查询从当前载荷及环境读取快照。
|
||||
let function = vm
|
||||
.new_function("caller", code, globals, vec![], vec![])
|
||||
.unwrap();
|
||||
// 辅助函数归还临时代码根,函数仍保留代码对象。
|
||||
vm.release(code).unwrap();
|
||||
function
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 调用效果快照随全局遮蔽、内置回退、闭包 Cell 及递归边变化重新求证,不保留过期目标。
|
||||
fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() {
|
||||
// 独立 VM 隔离全局与方法重绑定,避免其他场景状态干扰快照证明。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 源码名查找环境,更新其中键可改变契约或调用图,不能把首次查询缓存为永久证明。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 函数创建时捕获该内置字典身份,全局缺名才回退这里。
|
||||
let builtins = vm.new_dict().unwrap();
|
||||
vm.dict_set(
|
||||
globals,
|
||||
@@ -623,6 +751,7 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() {
|
||||
Value::Object(builtins),
|
||||
)
|
||||
.unwrap();
|
||||
// 明确声明 ReadOnly 的原生目标,作为可证明调用边。
|
||||
let pure = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"pure",
|
||||
@@ -634,6 +763,7 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() {
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
// 声明 Mutating/External 的目标,使源码整体调用效果无法证明 ReadOnly。
|
||||
let impure = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"impure",
|
||||
@@ -647,7 +777,9 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() {
|
||||
.unwrap();
|
||||
vm.dict_set(builtins, Value::Text("target".into()), Value::Object(pure))
|
||||
.unwrap();
|
||||
// 第一层从环境解析 target,初始由内置字典提供 pure。
|
||||
let wrapper = global_caller(&mut vm, globals, "target");
|
||||
// 外层再经全局 wrapper 调用,检验传递分析不只看一跳。
|
||||
let outer = global_caller(&mut vm, globals, "wrapper");
|
||||
vm.dict_set(
|
||||
globals,
|
||||
@@ -662,7 +794,9 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() {
|
||||
vm.dict_delete(globals, &Value::Text("target".into()))
|
||||
.unwrap();
|
||||
assert_eq!(vm.call_effect(outer).unwrap(), Some(CallEffect::ReadOnly));
|
||||
// 闭包捕获目标通过共享 Cell 更新,函数身份不变而调用图可变化。
|
||||
let cell = vm.new_cell(Some(Value::Object(pure))).unwrap();
|
||||
// 手工指令使控制流和调用目标来源可精确观察,构造器先验证栈高/跳转。
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadDeref, 0),
|
||||
@@ -677,7 +811,9 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() {
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
// 代码数据只验证与分配,call_effect 不应实际执行其中指令。
|
||||
let code = vm.new_code(code).unwrap();
|
||||
// 源码经 LoadDeref 取目标再 Call,分析需读 Cell 当前内容而不执行函数体。
|
||||
let closure = vm
|
||||
.new_function("closure", code, globals, vec![], vec![cell])
|
||||
.unwrap();
|
||||
@@ -686,6 +822,7 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() {
|
||||
assert_eq!(vm.call_effect(closure).unwrap(), None);
|
||||
vm.cell_set(cell, Value::Object(pure)).unwrap();
|
||||
assert_eq!(vm.call_effect(closure).unwrap(), Some(CallEffect::ReadOnly));
|
||||
// 全局同名绑定回自己形成不终止调用图;查询必须通过去重收敛而非执行。
|
||||
let recursive = global_caller(&mut vm, globals, "recursive");
|
||||
vm.dict_set(
|
||||
globals,
|
||||
@@ -693,7 +830,7 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() {
|
||||
Value::Object(recursive),
|
||||
)
|
||||
.unwrap();
|
||||
// No function body is executed, including this non-terminating call graph.
|
||||
// 查询不会执行任何源码体;即使图有无限自递归,visited 去重也应使分析结束。
|
||||
assert_eq!(
|
||||
vm.call_effect(recursive).unwrap(),
|
||||
Some(CallEffect::ReadOnly)
|
||||
@@ -705,19 +842,25 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() {
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(vm.call_effect(recursive).unwrap(), None);
|
||||
// 归还测试拥有根,内部方法/签名/类型边由堆维护,避免重复 release。
|
||||
for object in [
|
||||
recursive, closure, code, cell, outer, wrapper, pure, impure, builtins, globals,
|
||||
] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 回收无根函数/全局/类型环,最终对象数应为零。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 条件两路径必须汇合事实,目标不一致时不可保留旧的已知纯目标;恢复相同目标后可重新证明。
|
||||
fn effect_control_flow_joins_do_not_keep_a_stale_known_call_target() {
|
||||
// 独立 VM 隔离全局与方法重绑定,避免其他场景状态干扰快照证明。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 源码名查找环境,更新其中键可改变契约或调用图,不能把首次查询缓存为永久证明。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 明确声明 ReadOnly 的原生目标,作为可证明调用边。
|
||||
let pure = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"pure",
|
||||
@@ -729,6 +872,7 @@ fn effect_control_flow_joins_do_not_keep_a_stale_known_call_target() {
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
// 声明 Mutating/External 的目标,使源码整体调用效果无法证明 ReadOnly。
|
||||
let impure = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"impure",
|
||||
@@ -740,10 +884,12 @@ fn effect_control_flow_joins_do_not_keep_a_stale_known_call_target() {
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
// 两个分支初始指向同一 pure 身份,汇合可以保留该确定目标。
|
||||
for name in ["left", "right"] {
|
||||
vm.dict_set(globals, Value::Text(name.into()), Value::Object(pure))
|
||||
.unwrap();
|
||||
}
|
||||
// 手工指令使控制流和调用目标来源可精确观察,构造器先验证栈高/跳转。
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadFast, 0),
|
||||
@@ -764,7 +910,9 @@ fn effect_control_flow_joins_do_not_keep_a_stale_known_call_target() {
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
// 代码数据只验证与分配,call_effect 不应实际执行其中指令。
|
||||
let code = vm.new_code(code).unwrap();
|
||||
// 函数持有其代码/全局边,效果查询从当前载荷及环境读取快照。
|
||||
let function = vm
|
||||
.new_function("choose", code, globals, vec![], vec![])
|
||||
.unwrap();
|
||||
@@ -781,21 +929,29 @@ fn effect_control_flow_joins_do_not_keep_a_stale_known_call_target() {
|
||||
vm.call_effect(function).unwrap(),
|
||||
Some(CallEffect::ReadOnly)
|
||||
);
|
||||
// 归还测试拥有根,内部方法/签名/类型边由堆维护,避免重复 release。
|
||||
for object in [function, code, pure, impure, globals] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 回收无根函数/全局/类型环,最终对象数应为零。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 绑定接收者提供方法目标身份;类方法更新和实例字段遮蔽都必须影响效果查询。
|
||||
fn bound_source_effects_follow_the_actual_receiver_method_and_instance_field() {
|
||||
// 独立 VM 隔离全局与方法重绑定,避免其他场景状态干扰快照证明。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 显式类型自举根,用于实例与绑定方法测试,最终归还并收集类型环。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 源码名查找环境,更新其中键可改变契约或调用图,不能把首次查询缓存为永久证明。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// Receiver 名义类型保存 helper/check 方法,实例可另以字段遮蔽 helper。
|
||||
let definition = vm
|
||||
.new_type("Receiver", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// 明确声明 ReadOnly 的原生目标,作为可证明调用边。
|
||||
let pure = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"helper",
|
||||
@@ -807,6 +963,7 @@ fn bound_source_effects_follow_the_actual_receiver_method_and_instance_field() {
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
// 声明 Mutating/External 的目标,使源码整体调用效果无法证明 ReadOnly。
|
||||
let impure = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"helper",
|
||||
@@ -818,6 +975,7 @@ fn bound_source_effects_follow_the_actual_receiver_method_and_instance_field() {
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
// 手工指令使控制流和调用目标来源可精确观察,构造器先验证栈高/跳转。
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadFast, 0),
|
||||
@@ -835,14 +993,18 @@ fn bound_source_effects_follow_the_actual_receiver_method_and_instance_field() {
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
// 代码数据只验证与分配,call_effect 不应实际执行其中指令。
|
||||
let code = vm.new_code(code).unwrap();
|
||||
// 函数持有其代码/全局边,效果查询从当前载荷及环境读取快照。
|
||||
let function = vm
|
||||
.new_function("check", code, globals, vec![], vec![])
|
||||
.unwrap();
|
||||
vm.define_method(definition, "helper", pure).unwrap();
|
||||
vm.define_method(definition, "check", function).unwrap();
|
||||
// 实例提供实际接收者身份,绑定效果分析可据此查 helper,未绑定函数则未知。
|
||||
let receiver = vm.new_object().unwrap();
|
||||
vm.attach_type(receiver, definition).unwrap();
|
||||
// 绑定对象拥有函数和实际接收者边,分析可用 receiver 区分实例方法/字段。
|
||||
let Value::Object(bound) = vm.bind_attr(receiver, "check").unwrap() else {
|
||||
panic!("bound")
|
||||
};
|
||||
@@ -857,6 +1019,7 @@ fn bound_source_effects_follow_the_actual_receiver_method_and_instance_field() {
|
||||
assert_eq!(vm.call_effect(bound).unwrap(), None);
|
||||
vm.del_attr(receiver, "helper").unwrap();
|
||||
assert_eq!(vm.call_effect(bound).unwrap(), Some(CallEffect::ReadOnly));
|
||||
// 归还测试拥有根,内部方法/签名/类型边由堆维护,避免重复 release。
|
||||
for object in [
|
||||
bound,
|
||||
receiver,
|
||||
@@ -871,6 +1034,7 @@ fn bound_source_effects_follow_the_actual_receiver_method_and_instance_field() {
|
||||
] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 回收无根函数/全局/类型环,最终对象数应为零。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//! NativeContext 临时根、委托、观察依赖与完整签名的公开 API 回归;测试仍遵守远程执行约定。
|
||||
//! Remote-only regression coverage. Do not execute locally.
|
||||
use endor_core::{
|
||||
ChangeKey, ContextNativeFunction, ExecutionLimits, NativeCallable, NativeContext, NativeResult,
|
||||
@@ -5,12 +6,17 @@ use endor_core::{
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
/// bool 控制是否故意失败,两个临时分配用于观察成功/失败清理差异。
|
||||
struct Allocate(bool);
|
||||
impl NativeCallable for Allocate {
|
||||
/// VM 在 NativeContext 内调用此测试分派器,临时分配与输入由上下文保活,返回/委托后移交输出根。
|
||||
fn invoke(&self, cx: &mut NativeContext<'_>, _args: &[Value]) -> Result<NativeResult, VmError> {
|
||||
// 分配一份上下文临时根,成功返回时仅输出另取根移交,其余根由 Drop 归还。
|
||||
let value = cx.new_object()?;
|
||||
cx.set_attr(value, "label", Value::Text("scoped".into()))?;
|
||||
// 故意创建不返回对象,验证正常/报错都不会泄漏临时根。
|
||||
let _temporary = cx.new_object()?;
|
||||
// 故意在完成分配后报错,检验 RAII 不依赖回调正常返回。
|
||||
if self.0 {
|
||||
return Err(VmError::InvalidArgument("intentional failure"));
|
||||
}
|
||||
@@ -19,8 +25,11 @@ impl NativeCallable for Allocate {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证成功只移交一个结果根,未返回临时对象及错误路径分配全部回收。
|
||||
fn scoped_allocations_return_one_root_and_clean_up_errors() {
|
||||
// 独立 VM 隔离测试对象图,所有结果根由场景显式归还。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 分配上下文函数载荷,captures 管理显式托管边,分派器 Arc 不代替它们。
|
||||
let function = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"allocate",
|
||||
@@ -28,12 +37,15 @@ fn scoped_allocations_return_one_root_and_clean_up_errors() {
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
// VM 调用已完成根移交,返回对象应只有一份调用者根。
|
||||
let value = vm.call(function, &[]).unwrap();
|
||||
// 解构返回值只复制身份,不增加引用计数。
|
||||
let Value::Object(object) = value else {
|
||||
panic!("expected object")
|
||||
};
|
||||
assert_eq!(vm.ref_count(object).unwrap(), 1);
|
||||
assert_eq!(vm.object_count(), 2);
|
||||
// 归还成功结果,之后只剩函数载荷,临时对象应已自动清理。
|
||||
vm.release_value(value).unwrap();
|
||||
vm.replace_payload(
|
||||
function,
|
||||
@@ -47,8 +59,10 @@ fn scoped_allocations_return_one_root_and_clean_up_errors() {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// 返回目标借用身份,由包装载荷 captures 保活,NativeContext 为 Invoke 输出取得拥有根。
|
||||
struct Delegate(ObjectRef);
|
||||
impl NativeCallable for Delegate {
|
||||
/// VM 分派时原样委托给 captures 保活的目标;参数 clone 仍借用,NativeContext 随后为 Invoke 输出逐项取根。
|
||||
fn invoke(&self, _cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
|
||||
Ok(NativeResult::Invoke {
|
||||
function: self.0,
|
||||
@@ -58,8 +72,11 @@ impl NativeCallable for Delegate {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证 intrinsic 检查读取当前载荷的真实 Rust 实现,不能仅按不变函数身份推断。
|
||||
fn native_implementation_inspection_follows_payload_replacement() {
|
||||
// 独立 VM 隔离测试对象图,所有结果根由场景显式归还。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 分配上下文函数载荷,captures 管理显式托管边,分派器 Arc 不代替它们。
|
||||
let function = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"allocate",
|
||||
@@ -67,12 +84,15 @@ fn native_implementation_inspection_follows_payload_replacement() {
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
// 借用当前 ContextNativeFunction 载荷,下转型检查不分配对象或执行回调。
|
||||
let payload = vm
|
||||
.payload::<ContextNativeFunction>(function, "native")
|
||||
.unwrap();
|
||||
assert!(payload.implementation::<Allocate>().is_some());
|
||||
assert!(payload.implementation::<Delegate>().is_none());
|
||||
// 记录更新前版本,替换实现后身份不变但版本必须增加。
|
||||
let version = vm.change_version(function).unwrap();
|
||||
// 委托目标有测试根,也会由 Delegate 包装载荷显式 captures 保留。
|
||||
let target = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"target",
|
||||
@@ -85,12 +105,14 @@ fn native_implementation_inspection_follows_payload_replacement() {
|
||||
ContextNativeFunction::new("delegate", Delegate(target), vec![target]),
|
||||
)
|
||||
.unwrap();
|
||||
// 借用当前 ContextNativeFunction 载荷,下转型检查不分配对象或执行回调。
|
||||
let payload = vm
|
||||
.payload::<ContextNativeFunction>(function, "native")
|
||||
.unwrap();
|
||||
assert!(payload.implementation::<Allocate>().is_none());
|
||||
assert_eq!(payload.implementation::<Delegate>().unwrap().0, target);
|
||||
assert!(vm.change_version(function).unwrap() > version);
|
||||
// 逐项归还测试拥有根,包装载荷内部捕获边由 Heap 自动释放。
|
||||
for object in [function, target] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
@@ -98,8 +120,11 @@ fn native_implementation_inspection_follows_payload_replacement() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证原生委托与目标共享累计预算,预算失败后目标/参数临时根计数恢复。
|
||||
fn native_delegation_shares_budget_and_cleans_owned_arguments() {
|
||||
// 独立 VM 隔离测试对象图,所有结果根由场景显式归还。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 委托目标有测试根,也会由 Delegate 包装载荷显式 captures 保留。
|
||||
let target = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"target",
|
||||
@@ -107,6 +132,7 @@ fn native_delegation_shares_budget_and_cleans_owned_arguments() {
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
// Delegate 返回 Invoke 而不是递归 vm.call,让外层共享预算与清理流程。
|
||||
let delegate = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"delegate",
|
||||
@@ -114,6 +140,7 @@ fn native_delegation_shares_budget_and_cleans_owned_arguments() {
|
||||
vec![target],
|
||||
))
|
||||
.unwrap();
|
||||
// 额外对象参数用于观察失败委托是否遗留执行根。
|
||||
let arg = vm.new_object().unwrap();
|
||||
assert!(
|
||||
vm.call_with_limits(
|
||||
@@ -128,7 +155,9 @@ fn native_delegation_shares_budget_and_cleans_owned_arguments() {
|
||||
);
|
||||
assert_eq!(vm.ref_count(arg).unwrap(), 1);
|
||||
assert_eq!(vm.ref_count(target).unwrap(), 2);
|
||||
// VM 调用已完成根移交,返回对象应只有一份调用者根。
|
||||
let value = vm.call(delegate, &[]).unwrap();
|
||||
// 归还 Allocate 返回的独立结果根;delegate、target、arg 的测试根仍在下面分别释放。
|
||||
vm.release_value(value).unwrap();
|
||||
vm.release(delegate).unwrap();
|
||||
vm.release(target).unwrap();
|
||||
@@ -137,32 +166,45 @@ fn native_delegation_shares_budget_and_cleans_owned_arguments() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 弱句柄不持根;升级产生根,原对象释放后槽复用也不复活旧弱身份。
|
||||
fn weak_references_expire_without_retaining_reused_slots() {
|
||||
// 独立 VM 隔离测试对象图,所有结果根由场景显式归还。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 原始对象有一份分配根,弱身份或通知记录都不应额外持有它。
|
||||
let object = vm.new_object().unwrap();
|
||||
// downgrade 只形成借用弱身份,不延长对象寿命。
|
||||
let weak = vm.downgrade(object).unwrap();
|
||||
// 成功升级才取得一份独立外部根,随后必须归还。
|
||||
let upgraded = vm.upgrade(weak).unwrap().unwrap();
|
||||
vm.release(upgraded).unwrap();
|
||||
vm.release(object).unwrap();
|
||||
// 新对象可能复用同槽,但代际不同不能复活 weak。
|
||||
let replacement = vm.new_object().unwrap();
|
||||
assert!(vm.upgrade(weak).unwrap().is_none());
|
||||
// 另一堆升级旧身份应明确报 ForeignHandle,而不是过期 None。
|
||||
let mut foreign = Vm::new().unwrap();
|
||||
assert_eq!(foreign.upgrade(weak), Err(VmError::ForeignHandle));
|
||||
vm.release(replacement).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 同对象/字段通知合并最新版本,容器更新发 Contents,通知不充当 GC 根。
|
||||
fn changes_coalesce_cover_containers_and_do_not_keep_sources_alive() {
|
||||
// 独立 VM 隔离测试对象图,所有结果根由场景显式归还。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 原始对象有一份分配根,弱身份或通知记录都不应额外持有它。
|
||||
let object = vm.new_object().unwrap();
|
||||
vm.set_attr(object, "value", Value::Bool(false)).unwrap();
|
||||
vm.set_attr(object, "value", Value::Bool(true)).unwrap();
|
||||
// 两次同名写入的最终版本应出现在合并通知中。
|
||||
let version = vm.change_version(object).unwrap();
|
||||
// 取走通知批次同时重置缓存,数据中的源身份不 retain。
|
||||
let changes = vm.take_changes();
|
||||
assert!(!changes.rescan_required);
|
||||
assert_eq!(changes.changes.len(), 1);
|
||||
assert_eq!(changes.changes[0].key, ChangeKey::Attribute("value".into()));
|
||||
assert_eq!(changes.changes[0].version, version);
|
||||
// Cell 更新/清空使用载荷替换,通知键为 Contents,不是普通字段。
|
||||
let cell = vm.new_cell(None).unwrap();
|
||||
vm.cell_set(cell, Value::Object(object)).unwrap();
|
||||
vm.cell_clear(cell).unwrap();
|
||||
@@ -175,12 +217,17 @@ fn changes_coalesce_cover_containers_and_do_not_keep_sources_alive() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 超过通知唯一键上限清空精确条目并标记重扫,取走批次后新通知正常开始。
|
||||
fn notification_overflow_requires_rescan_instead_of_losing_changes_silently() {
|
||||
// 独立 VM 隔离测试对象图,所有结果根由场景显式归还。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 原始对象有一份分配根,弱身份或通知记录都不应额外持有它。
|
||||
let object = vm.new_object().unwrap();
|
||||
// 制造超过 4096 个不同字段键,重复同键不足以触发通知溢出。
|
||||
for index in 0..4100 {
|
||||
vm.set_attr(object, index.to_string(), Value::Nil).unwrap();
|
||||
}
|
||||
// 取走通知批次同时重置缓存,数据中的源身份不 retain。
|
||||
let changes = vm.take_changes();
|
||||
assert!(changes.rescan_required);
|
||||
assert!(changes.changes.is_empty());
|
||||
@@ -190,16 +237,23 @@ fn notification_overflow_requires_rescan_instead_of_losing_changes_silently() {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// 只读场景的复制器:读输入字段并构造新结果,可选故意改旧输入以验证写屏障及错误退出。
|
||||
struct ReadResult {
|
||||
/// 输入借用身份,包装载荷 captures 负责保活。
|
||||
source: ObjectRef,
|
||||
/// 是否故意写旧输入,触发只读保护错误。
|
||||
mutate: bool,
|
||||
}
|
||||
impl NativeCallable for ReadResult {
|
||||
/// VM 在 NativeContext 内调用此测试分派器,临时分配与输入由上下文保活,返回/委托后移交输出根。
|
||||
fn invoke(&self, cx: &mut NativeContext<'_>, _: &[Value]) -> Result<NativeResult, VmError> {
|
||||
// 借用读取 source 的输入字段,读取路径纳入当前只读依赖收集。
|
||||
let value = cx.store().get_attr(self.source, "value")?;
|
||||
// 非法分支试图修改进入只读前已存在的 source,应在写屏障处失败。
|
||||
if self.mutate {
|
||||
cx.set_attr(self.source, "value", Value::Number(99_i64.into()))?;
|
||||
}
|
||||
// 新结果不在进入只读时的保护快照内,允许初始化 copied 字段。
|
||||
let result = cx.new_object()?;
|
||||
cx.set_attr(result, "copied", value)?;
|
||||
Ok(NativeResult::Return(Value::Object(result)))
|
||||
@@ -207,11 +261,15 @@ impl NativeCallable for ReadResult {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 只读调用记录输入观察并允许构造新结果,非法改旧对象报错后作用域必须恢复。
|
||||
fn read_only_calls_track_borrowed_dependencies_allow_results_and_restore_after_error() {
|
||||
// 独立 VM 隔离测试对象图,所有结果根由场景显式归还。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 输入源在调用前创建并初始化,是只读保护与读取依赖的对象。
|
||||
let source = vm.new_object().unwrap();
|
||||
vm.set_attr(source, "value", Value::Number(7_i64.into()))
|
||||
.unwrap();
|
||||
// 分配上下文函数载荷,captures 管理显式托管边,分派器 Arc 不代替它们。
|
||||
let function = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"read result",
|
||||
@@ -222,12 +280,15 @@ fn read_only_calls_track_borrowed_dependencies_allow_results_and_restore_after_e
|
||||
vec![source],
|
||||
))
|
||||
.unwrap();
|
||||
// 丢弃场景初始化通知,只检查本次只读派生构造是否发布了输入变更。
|
||||
vm.take_changes();
|
||||
// 结果是拥有值,reads 是借用观察集合,两个所有权语义不能混同。
|
||||
let (value, reads) = vm
|
||||
.call_read_only_with_limits(function, &[], ExecutionLimits::default())
|
||||
.unwrap();
|
||||
assert!(reads.contains(&source));
|
||||
assert!(vm.take_changes().changes.is_empty());
|
||||
// 检查新结果 copied 值后归还结果根,不能 release 读取依赖句柄。
|
||||
let Value::Object(result) = value else {
|
||||
panic!("result")
|
||||
};
|
||||
@@ -264,40 +325,51 @@ fn read_only_calls_track_borrowed_dependencies_allow_results_and_restore_after_e
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 注解验证也记录依赖,但单独标为非内容读取;同对象普通读取随后提升为内容读取。
|
||||
fn annotation_reads_remain_dependencies_without_becoming_program_content_reads() {
|
||||
use endor_core::AnnotationType;
|
||||
// 本测试直接使用 Store 精确分类入口,不需要执行器帧。
|
||||
let mut store = objects::ObjectStore::new().unwrap();
|
||||
// 普通 Record 不符合 Number,但有效对象应符合 Any。
|
||||
let object = store.new_object().unwrap();
|
||||
store
|
||||
.set_attr(object, "value", Value::Number(1.into()))
|
||||
.unwrap();
|
||||
// 只读范围收集整体观察,子区间分类不会清空外层依赖。
|
||||
let scope = store.begin_read_only();
|
||||
// 每个子区间重新开始收集,按 LIFO 结束;重复读取也要在新区域出现。
|
||||
let token = store.begin_read_tracking().unwrap();
|
||||
assert!(
|
||||
store
|
||||
.matches_annotation(&Value::Object(object), AnnotationType::Any)
|
||||
.unwrap()
|
||||
);
|
||||
// 返回 ReadAccess 区分 content_read,句柄仍不拥有根。
|
||||
let reads = store.end_read_tracking_details(token).unwrap();
|
||||
assert!(
|
||||
reads
|
||||
.iter()
|
||||
.any(|read| read.object == object && !read.content_read)
|
||||
);
|
||||
// 每个子区间重新开始收集,按 LIFO 结束;重复读取也要在新区域出现。
|
||||
let token = store.begin_read_tracking().unwrap();
|
||||
assert!(
|
||||
!store
|
||||
.matches_annotation(&Value::Object(object), AnnotationType::Number)
|
||||
.unwrap()
|
||||
);
|
||||
// 异堆对象即使匹配 Any 也先拒绝无效归属。
|
||||
let mut foreign = objects::ObjectStore::new().unwrap();
|
||||
// 有效但异堆的测试输入,用于排除 Any 跳过句柄校验的错误实现。
|
||||
let alien = foreign.new_object().unwrap();
|
||||
assert!(matches!(
|
||||
store.matches_annotation(&Value::Object(alien), AnnotationType::Any),
|
||||
Err(VmError::ForeignHandle)
|
||||
));
|
||||
foreign.release(alien).unwrap();
|
||||
// 补一次普通字段读取,与前面的验证读取合并时 content_read 必须为 true。
|
||||
store.get_attr(object, "value").unwrap();
|
||||
// 返回 ReadAccess 区分 content_read,句柄仍不拥有根。
|
||||
let reads = store.end_read_tracking_details(token).unwrap();
|
||||
assert!(
|
||||
reads
|
||||
@@ -310,24 +382,34 @@ fn annotation_reads_remain_dependencies_without_becoming_program_content_reads()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// 完整签名场景共用分派器,以计数器区分预检/回调/返回检查,支持委托、新分配或回传实参。
|
||||
struct TypedNative {
|
||||
/// 构造时快照的完整签名,包装载荷负责 trace 类型定义。
|
||||
signature: endor_core::CallSignature,
|
||||
/// VM 外共享调用计数,clone Arc 不涉及托管对象根。
|
||||
calls: std::sync::Arc<std::sync::atomic::AtomicUsize>,
|
||||
/// 可选委托目标借用身份,包装载荷 captures 必须持有对应强边。
|
||||
delegate: Option<ObjectRef>,
|
||||
/// 无委托时是否分配新对象,否则回传最后参数/Nil。
|
||||
allocate_result: bool,
|
||||
}
|
||||
impl NativeCallable for TypedNative {
|
||||
/// ContextNativeFunction 快照此完整元数据并 trace 定义边;clone 本身不增加外部根。
|
||||
fn signature(&self) -> Option<endor_core::CallSignature> {
|
||||
Some(self.signature.clone())
|
||||
}
|
||||
/// 测试原生分派入口,回调内借用参数/目标,NativeContext 校验并移交 Return 或 Invoke 输出根。
|
||||
fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
|
||||
// 计数器只在真实回调进入后增加,测试据此区分预检失败与返回后失败。
|
||||
self.calls.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||
// 委托不递归执行 VM,返回目标/参数借用表示给统一准备循环。
|
||||
if let Some(function) = self.delegate {
|
||||
return Ok(NativeResult::Invoke {
|
||||
function,
|
||||
arguments: args.to_vec(),
|
||||
});
|
||||
}
|
||||
// 可选择产生新对象或回传最后参数,分别检验临时根清理和借用结果移交。
|
||||
Ok(NativeResult::Return(if self.allocate_result {
|
||||
Value::Object(cx.new_object()?)
|
||||
} else {
|
||||
@@ -337,14 +419,18 @@ impl NativeCallable for TypedNative {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 参数错误必须在 calls 增加前拒绝,错误对象结果由上下文回收,不留下临时分配。
|
||||
fn native_types_preflight_arguments_and_clean_wrong_result_roots() {
|
||||
use endor_core::{AnnotationType as T, CallEffect, CallSignature};
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
// 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。
|
||||
let calls = Arc::new(AtomicUsize::new(0));
|
||||
// 分配上下文载荷,签名类型与显式 captures 各自通过 trace 保活。
|
||||
let function = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"typed",
|
||||
@@ -358,10 +444,12 @@ fn native_types_preflight_arguments_and_clean_wrong_result_roots() {
|
||||
))
|
||||
.unwrap();
|
||||
assert!(
|
||||
// 错误实参在回调前由 parameter 1 约束拒绝,后面的计数零验证未进入宿主代码。
|
||||
matches!(vm.call(function, &[Value::Text("bad".into())]), Err(VmError::TypeMismatch { position, .. }) if position == "parameter 1")
|
||||
);
|
||||
assert_eq!(calls.load(Ordering::SeqCst), 0);
|
||||
assert!(
|
||||
// 合法数值允许回调执行一次,但宿主返回值违约应明确定位 return。
|
||||
matches!(vm.call(function, &[Value::Number(7.into())]), Err(VmError::TypeMismatch { position, .. }) if position == "return")
|
||||
);
|
||||
assert_eq!(calls.load(Ordering::SeqCst), 1);
|
||||
@@ -371,12 +459,15 @@ fn native_types_preflight_arguments_and_clean_wrong_result_roots() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 多层原生包装委托同一源码帧,最终结果必须满足链上全部返回约束,不额外消耗源码帧深度。
|
||||
fn delegated_source_results_obey_every_native_contract_without_extra_frames() {
|
||||
use endor_core::{
|
||||
AnnotationType as T, CallEffect, CallSignature, CodeLayout, EndorCode, Instruction, Opcode,
|
||||
};
|
||||
use std::sync::{Arc, atomic::AtomicUsize};
|
||||
// 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 最小源码函数返回固定常量,区分原生包装约束与源码帧自身的执行。
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadConst, 0),
|
||||
@@ -386,12 +477,17 @@ fn delegated_source_results_obey_every_native_contract_without_extra_frames() {
|
||||
CodeLayout::default(),
|
||||
)
|
||||
.unwrap();
|
||||
// 托管代码拥有常量强边,源码函数还会持有代码边。
|
||||
let code = vm.new_code(code).unwrap();
|
||||
// 源码环境有分配根,函数持有它的内部边。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 此源码函数固定返回文本;inner 的 String 返回约束可通过,outer 的 Number 约束必须失败。
|
||||
let source = vm
|
||||
.new_function("source", code, globals, vec![], vec![])
|
||||
.unwrap();
|
||||
// VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。
|
||||
let calls = Arc::new(AtomicUsize::new(0));
|
||||
// 内层包装允许 String,与源码文本结果相符。
|
||||
let inner = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"inner",
|
||||
@@ -404,6 +500,7 @@ fn delegated_source_results_obey_every_native_contract_without_extra_frames() {
|
||||
vec![source],
|
||||
))
|
||||
.unwrap();
|
||||
// 外层却要求 Number,必须在源码最终返回时拒绝,即使内层已通过。
|
||||
let outer = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"outer",
|
||||
@@ -416,6 +513,7 @@ fn delegated_source_results_obey_every_native_contract_without_extra_frames() {
|
||||
vec![inner],
|
||||
))
|
||||
.unwrap();
|
||||
// 只允许一层源码帧,验证多层原生委托不会伪造额外源码帧。
|
||||
let limits = ExecutionLimits {
|
||||
max_frames: 1,
|
||||
max_instructions: 100,
|
||||
@@ -425,8 +523,10 @@ fn delegated_source_results_obey_every_native_contract_without_extra_frames() {
|
||||
Value::Text("source result".into())
|
||||
);
|
||||
assert!(
|
||||
// 多层原生委托结果返回外层时,仍需兑现 outer 自己的返回约束并标明其名称。
|
||||
matches!(vm.call_with_limits(outer, &[], limits), Err(VmError::TypeMismatch { function, position, .. }) if function == "outer" && position == "return")
|
||||
);
|
||||
// 零源码帧预算必须由受委托源码入口拒绝,不能因原生委托不占帧而绕过限制。
|
||||
assert!(matches!(
|
||||
vm.call_with_limits(
|
||||
outer,
|
||||
@@ -438,26 +538,34 @@ fn delegated_source_results_obey_every_native_contract_without_extra_frames() {
|
||||
),
|
||||
Err(VmError::ExecutionLimit("frames"))
|
||||
));
|
||||
// 归还测试每份拥有根,签名/捕获/方法内部边由 Heap 处理。
|
||||
for object in [outer, inner, source, globals, code] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 清理可能由类型/签名/方法持有关系形成的无根环,断言堆归零。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 签名独立 trace 名义类型定义,绑定后仍使用真实目标签名并扣除 self。
|
||||
fn signatures_trace_nominal_types_and_bound_receivers_use_the_actual_contract() {
|
||||
use endor_core::{AnnotationType as T, CallEffect, CallSignature, TypeConstructor};
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
// 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 显式自举 Object/Type,名义定义在同一对象图中受计数管理。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 名义定义用于签名约束,归还测试根后仍需由签名边保活。
|
||||
let definition = vm
|
||||
.new_type("Receiver", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。
|
||||
let calls = Arc::new(AtomicUsize::new(0));
|
||||
// 分配上下文载荷,签名类型与显式 captures 各自通过 trace 保活。
|
||||
let function = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"echo",
|
||||
@@ -474,11 +582,15 @@ fn signatures_trace_nominal_types_and_bound_receivers_use_the_actual_contract()
|
||||
vec![],
|
||||
))
|
||||
.unwrap(); // Metadata alone traces the definition.
|
||||
// 弱身份不保活定义,清理后升级 None 用于检查无隐藏根。
|
||||
let weak = vm.downgrade(definition).unwrap();
|
||||
vm.define_method(definition, "echo", function).unwrap();
|
||||
// 实例持有定义类型边,绑定方法再持有实例边。
|
||||
let receiver = vm.new_object().unwrap();
|
||||
vm.attach_type(receiver, definition).unwrap();
|
||||
// 归还原始定义根;其后存活来自实例/签名或执行检查根。
|
||||
vm.release(definition).unwrap();
|
||||
// bind_attr 交付绑定对象拥有根,实际调用会先补接收者。
|
||||
let Value::Object(bound) = vm.bind_attr(receiver, "echo").unwrap() else {
|
||||
panic!("bound method")
|
||||
};
|
||||
@@ -492,24 +604,31 @@ fn signatures_trace_nominal_types_and_bound_receivers_use_the_actual_contract()
|
||||
vm.call(bound, &[Value::Number(9.into())]).unwrap(),
|
||||
Value::Number(9.into())
|
||||
);
|
||||
// 归还测试每份拥有根,签名/捕获/方法内部边由 Heap 处理。
|
||||
for object in [bound, receiver, function, roots.object, roots.type_type] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 清理可能由类型/签名/方法持有关系形成的无根环,断言堆归零。
|
||||
vm.collect_cycles();
|
||||
assert!(vm.upgrade(weak).unwrap().is_none());
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 只读域中 Mutating/External 在回调前拒绝,普通调用仍允许进入。
|
||||
fn declared_host_effects_are_rejected_before_read_only_dispatch() {
|
||||
use endor_core::{AnnotationType as T, CallEffect, CallSignature};
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
// 两个不可在只读域调用的声明分别测试,不能只覆盖其中一个。
|
||||
for effect in [CallEffect::Mutating, CallEffect::External] {
|
||||
// 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。
|
||||
let calls = Arc::new(AtomicUsize::new(0));
|
||||
// 分配上下文载荷,签名类型与显式 captures 各自通过 trace 保活。
|
||||
let function = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"effect",
|
||||
@@ -534,12 +653,16 @@ fn declared_host_effects_are_rejected_before_read_only_dispatch() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 旧式函数也执行完整签名/可变参数/返回检查,独立 arity 与签名不一致必须拒绝。
|
||||
fn legacy_native_signatures_check_optional_variadic_types_and_metadata() {
|
||||
use endor_core::{AnnotationType as T, CallArity, CallEffect, CallSignature, NativeFunction};
|
||||
/// 旧式回调返回最后一个借用参数,空参数返回 Nil;拥有根仍由 VM 补充。
|
||||
fn echo(args: &[Value]) -> Result<Value, VmError> {
|
||||
Ok(args.last().cloned().unwrap_or(Value::Nil))
|
||||
}
|
||||
// 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 分配旧式 NativeFunction 载荷,签名引用的类型定义仍通过 trace 保活。
|
||||
let function = vm
|
||||
.allocate(
|
||||
NativeFunction::new("legacy", echo).with_signature(CallSignature {
|
||||
@@ -561,8 +684,10 @@ fn legacy_native_signatures_check_optional_variadic_types_and_metadata() {
|
||||
);
|
||||
assert!(vm.call(function, &[Value::Bool(true)]).is_err());
|
||||
assert!(
|
||||
// 可变参数允许空列表,但旧式 echo 此时返回 Nil,不符合声明的 Number 返回约束。
|
||||
matches!(vm.call(function, &[]), Err(VmError::TypeMismatch { position, .. }) if position == "return")
|
||||
);
|
||||
// 故意同时配置互相矛盾的签名和独立 arity,查询/调用都应报元数据错误。
|
||||
let invalid = vm
|
||||
.allocate(
|
||||
NativeFunction::new("invalid", echo)
|
||||
@@ -582,15 +707,22 @@ fn legacy_native_signatures_check_optional_variadic_types_and_metadata() {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// 移除自身载荷的回归分派器,迫使签名类型在原持边消失后仍由执行检查根保活。
|
||||
struct EraseThenDelegate {
|
||||
/// 构造时快照的完整签名,包装载荷负责 trace 类型定义。
|
||||
signature: endor_core::CallSignature,
|
||||
/// 被委托源码函数最初有 captures 边;载荷自替换后该边消失,本测试另保留 source 根保护目标。
|
||||
target: ObjectRef,
|
||||
}
|
||||
impl NativeCallable for EraseThenDelegate {
|
||||
/// ContextNativeFunction 快照此完整元数据并 trace 定义边;clone 本身不增加外部根。
|
||||
fn signature(&self) -> Option<endor_core::CallSignature> {
|
||||
Some(self.signature.clone())
|
||||
}
|
||||
/// 测试原生分派入口,回调内借用参数/目标,NativeContext 校验并移交 Return 或 Invoke 输出根。
|
||||
fn invoke(&self, cx: &mut NativeContext<'_>, _: &[Value]) -> Result<NativeResult, VmError> {
|
||||
// 将当前函数换为普通 Record,移除签名及捕获边,迫使返回约束靠执行器自己的根存活。
|
||||
// self.target 的存活此时依赖测试未归还的 source 根,Arc 中的 ObjectRef 不构成托管边。
|
||||
cx.replace_payload(cx.callee(), endor_core::Record)?;
|
||||
Ok(NativeResult::Invoke {
|
||||
function: self.target,
|
||||
@@ -600,17 +732,23 @@ impl NativeCallable for EraseThenDelegate {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 回调替换自己的载荷删除签名边后,委托链独立持有返回类型根,诊断完成后才释放。
|
||||
fn delegated_return_type_survives_removal_of_the_original_signature_edges() {
|
||||
use endor_core::{
|
||||
AnnotationType as T, CallEffect, CallSignature, CodeLayout, EndorCode, Instruction, Opcode,
|
||||
TypeConstructor,
|
||||
};
|
||||
// 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 显式自举 Object/Type,名义定义在同一对象图中受计数管理。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 名义定义用于签名约束,归还测试根后仍需由签名边保活。
|
||||
let definition = vm
|
||||
.new_type("Transient", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// 弱身份不保活定义,清理后升级 None 用于检查无隐藏根。
|
||||
let weak = vm.downgrade(definition).unwrap();
|
||||
// 最小源码函数返回固定常量,区分原生包装约束与源码帧自身的执行。
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadConst, 0),
|
||||
@@ -620,13 +758,19 @@ fn delegated_return_type_survives_removal_of_the_original_signature_edges() {
|
||||
CodeLayout::default(),
|
||||
)
|
||||
.unwrap();
|
||||
// 托管代码拥有常量强边,源码函数还会持有代码边。
|
||||
let code = vm.new_code(code).unwrap();
|
||||
// 源码环境有分配根,函数持有它的内部边。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 此源码函数固定返回 Nil,故意不满足包装器的 Transient | Number 返回联合。
|
||||
let source = vm
|
||||
.new_function("source", code, globals, vec![], vec![])
|
||||
.unwrap();
|
||||
// 先构造完整数量/效果骨架,再替换联合类型项,最终查询/调用才验证完整性。
|
||||
let mut signature = CallSignature::exact(vec![], T::Any, CallEffect::Mutating);
|
||||
// 联合第一项引用将失去原载荷保活的 Transient,错误诊断仍应能读取其名字。
|
||||
signature.returns = endor_core::CallType::Union(vec![T::Nominal(definition), T::Number]);
|
||||
// 包装函数显式 captures 持有委托目标,输出目标根由 NativeContext 另取得。
|
||||
let wrapper = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"erased",
|
||||
@@ -637,11 +781,13 @@ fn delegated_return_type_survives_removal_of_the_original_signature_edges() {
|
||||
vec![source],
|
||||
))
|
||||
.unwrap();
|
||||
// 归还原始定义根;其后存活来自实例/签名或执行检查根。
|
||||
vm.release(definition).unwrap();
|
||||
assert!(
|
||||
matches!(vm.call(wrapper, &[]), Err(VmError::TypeMismatch { function, expected, .. }) if function == "erased" && expected == "Transient | Number")
|
||||
);
|
||||
assert!(vm.upgrade(weak).unwrap().is_none());
|
||||
// 归还测试每份拥有根,签名/捕获/方法内部边由 Heap 处理。
|
||||
for object in [
|
||||
wrapper,
|
||||
source,
|
||||
@@ -652,22 +798,28 @@ fn delegated_return_type_survives_removal_of_the_original_signature_edges() {
|
||||
] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 清理可能由类型/签名/方法持有关系形成的无根环,断言堆归零。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 联合各分支可接纳,非法后续类型及空联合不得被 Any/提前匹配掩盖。
|
||||
fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_metadata() {
|
||||
use endor_core::{AnnotationType as T, CallEffect, CallSignature, CallType};
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
// 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。
|
||||
let calls = Arc::new(AtomicUsize::new(0));
|
||||
// 先构造完整数量/效果骨架,再替换联合类型项,最终查询/调用才验证完整性。
|
||||
let mut signature = CallSignature::exact(vec![T::Any], T::Any, CallEffect::ReadOnly);
|
||||
signature.parameters[0] = CallType::Union(vec![T::String, T::Bool]);
|
||||
signature.returns = signature.parameters[0].clone();
|
||||
// 分配上下文载荷,签名类型与显式 captures 各自通过 trace 保活。
|
||||
let function = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"union",
|
||||
@@ -680,6 +832,7 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
// 每个合法联合/nullable 分支都实际调用一次,计数器验证非法输入未进入。
|
||||
for value in [
|
||||
Value::Text("capture".into()),
|
||||
Value::Bool(true),
|
||||
@@ -691,9 +844,11 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
// 联合都不匹配时诊断需完整保留 String | Bool 候选,不只显示最后尝试的一项。
|
||||
matches!(vm.call(function, &[Value::Number(3.into())]), Err(VmError::TypeMismatch { expected, .. }) if expected == "String | Bool")
|
||||
);
|
||||
assert_eq!(calls.load(Ordering::SeqCst), 3);
|
||||
// 先构造完整数量/效果骨架,再替换联合类型项,最终查询/调用才验证完整性。
|
||||
let mut signature = CallSignature::exact(vec![T::Any], T::Any, CallEffect::ReadOnly);
|
||||
signature.parameters[0] = CallType::nullable(T::Number);
|
||||
signature.returns = signature.parameters[0].clone();
|
||||
@@ -711,15 +866,18 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
// 每个合法联合/nullable 分支都实际调用一次,计数器验证非法输入未进入。
|
||||
for value in [Value::Nil, Value::Number(2.into())] {
|
||||
assert_eq!(
|
||||
vm.call(function, std::slice::from_ref(&value)).unwrap(),
|
||||
value
|
||||
);
|
||||
}
|
||||
// 普通 Record 不能用作 Nominal 的类型定义,即使前分支 Any 已能匹配也须拒绝。
|
||||
let record = vm.new_object().unwrap();
|
||||
// 先构造完整数量/效果骨架,再替换联合类型项,最终查询/调用才验证完整性。
|
||||
let mut signature = CallSignature::exact(vec![T::Any], T::Any, CallEffect::ReadOnly);
|
||||
// A matching first branch must not hide a later invalid nominal definition.
|
||||
// 完整联合先验证全部定义,不能因第一项 Any 匹配而跳过后面的非法类型句柄。
|
||||
signature.parameters[0] = CallType::Union(vec![T::Any, T::Nominal(record)]);
|
||||
vm.replace_payload(
|
||||
function,
|
||||
@@ -737,6 +895,7 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met
|
||||
.unwrap();
|
||||
assert!(vm.call(function, &[Value::Nil]).is_err());
|
||||
assert_eq!(calls.load(Ordering::SeqCst), 5);
|
||||
// 先构造完整数量/效果骨架,再替换联合类型项,最终查询/调用才验证完整性。
|
||||
let mut signature = CallSignature::exact(vec![], T::Any, CallEffect::ReadOnly);
|
||||
signature.returns = CallType::Union(vec![]);
|
||||
vm.replace_payload(
|
||||
@@ -761,15 +920,20 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Delegated 包装器能进入只读域,但实际目标仍在同域核对效果,目标改为 ReadOnly 声明后可通过。
|
||||
fn delegated_effects_keep_the_original_read_only_scope_and_check_the_actual_target() {
|
||||
use endor_core::{AnnotationType as T, CallEffect, CallSignature};
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
// 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。
|
||||
let calls = Arc::new(AtomicUsize::new(0));
|
||||
// 目标独立计数,用于证明包装器已经执行但目标在副作用前被效果检查拦截。
|
||||
let target_calls = Arc::new(AtomicUsize::new(0));
|
||||
// 初始目标声明 Mutating,在只读委托链中不允许执行;随后替换成 ReadOnly。
|
||||
let target = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"target",
|
||||
@@ -782,6 +946,7 @@ fn delegated_effects_keep_the_original_read_only_scope_and_check_the_actual_targ
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
// 包装函数显式 captures 持有委托目标,输出目标根由 NativeContext 另取得。
|
||||
let wrapper = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"wrapper",
|
||||
@@ -821,6 +986,7 @@ fn delegated_effects_keep_the_original_read_only_scope_and_check_the_actual_targ
|
||||
.0,
|
||||
Value::Nil
|
||||
);
|
||||
// 归还测试每份拥有根,签名/捕获/方法内部边由 Heap 处理。
|
||||
for object in [wrapper, target] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
//! Rust 测试框架调用的 VM/Store 基础协议集成场景,不依赖源码编译器。
|
||||
//! Resource 用 Rust Drop 计数观察实际析构,Number/ReflectedOrdering 验证宿主载荷协议覆盖和反向分派。
|
||||
//! 每个测试独立建 VM;未显式归还的根及未收集环最终仍须由整个 VM 的销毁清理。
|
||||
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
@@ -5,69 +9,87 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use endor_core::{CompareOp, NativeFunction, ObjectPayload, ObjectRef, Record, Value, Vm, VmError};
|
||||
|
||||
#[derive(Debug)]
|
||||
/// 生命周期场景的宿主资源:以 Drop 计数观测真正销毁,edges 同时让载荷参与托管图。
|
||||
struct Resource {
|
||||
/// VM 外观察实际析构次数的共享计数器。
|
||||
drops: Arc<AtomicUsize>,
|
||||
/// 分配时由 Heap 快照持有的内部强边,含重复项。
|
||||
edges: Vec<ObjectRef>,
|
||||
}
|
||||
|
||||
impl Drop for Resource {
|
||||
/// Resource 真正销毁时累加共享计数,区分句柄复制/归还与实际 Rust 资源释放。
|
||||
fn drop(&mut self) {
|
||||
self.drops.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectPayload for Resource {
|
||||
/// Store/比较协议的具体载荷下转型入口,不依赖语言名义类型。
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
/// 错误与诊断显示的类别标签。
|
||||
fn kind_name(&self) -> &'static str {
|
||||
"resource"
|
||||
}
|
||||
|
||||
/// Heap 分配时枚举载荷拥有边;重复项必须逐项计数,不能去重。
|
||||
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
|
||||
// 每个槽都是独立拥有边,不能因为身份相同而合并计数。
|
||||
for reference in &self.edges {
|
||||
visit(*reference);
|
||||
}
|
||||
}
|
||||
|
||||
/// Resource 允许公共属性表,使测试能组合载荷边和属性边成环。
|
||||
fn allows_attributes(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// 协议覆盖场景的自定义整数载荷,与内置 EndorNumberObject 分开,专门验证 Store 的动态分派。
|
||||
struct Number(i64);
|
||||
|
||||
impl ObjectPayload for Number {
|
||||
/// Store/比较协议的具体载荷下转型入口,不依赖语言名义类型。
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
/// 错误与诊断显示的类别标签。
|
||||
fn kind_name(&self) -> &'static str {
|
||||
"number"
|
||||
}
|
||||
|
||||
/// 测试 Number 用值覆盖默认身份表示,供 str 默认回退调用。
|
||||
fn repr(&self, _identity: ObjectRef) -> Result<String, VmError> {
|
||||
Ok(self.0.to_string())
|
||||
}
|
||||
|
||||
/// 测试 Number 仅零为假,不使用默认对象恒真规则。
|
||||
fn is_true(&self) -> Result<bool, VmError> {
|
||||
Ok(self.0 != 0)
|
||||
}
|
||||
|
||||
/// Store::hash 调用此载荷协议,按整数值生成哈希;负数按 Rust as 转为 u64。
|
||||
fn hash(&self, _identity: ObjectRef) -> Result<u64, VmError> {
|
||||
Ok(self.0 as u64)
|
||||
}
|
||||
|
||||
/// Store 先正向后反向调用;返回 None 是未实现,Some(false) 则不再回退。
|
||||
fn compare(
|
||||
&self,
|
||||
other: &dyn ObjectPayload,
|
||||
operation: CompareOp,
|
||||
) -> Result<Option<bool>, VmError> {
|
||||
// Number 只处理同类,其他载荷返回 None 允许 Store 继续分派。
|
||||
let Some(other) = other.as_any().downcast_ref::<Self>() else {
|
||||
return Ok(None);
|
||||
};
|
||||
// 同类 Number 每个关系都返回明确结果,false 也不是未实现。
|
||||
Ok(Some(match operation {
|
||||
CompareOp::Eq => self.0 == other.0,
|
||||
CompareOp::Ne => self.0 != other.0,
|
||||
@@ -79,6 +101,7 @@ impl ObjectPayload for Number {
|
||||
}
|
||||
}
|
||||
|
||||
/// 各生命周期测试共用分配器,保存计数器 Arc 与给定不可变边,返回一份拥有根。
|
||||
fn resource(vm: &mut Vm, drops: &Arc<AtomicUsize>, edges: Vec<ObjectRef>) -> ObjectRef {
|
||||
vm.allocate(Resource {
|
||||
drops: drops.clone(),
|
||||
@@ -88,15 +111,21 @@ fn resource(vm: &mut Vm, drops: &Arc<AtomicUsize>, edges: Vec<ObjectRef>) -> Obj
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证新 VM 不隐式自举名义类型或内置对象;不代表 VM 没有显式类型 API。
|
||||
fn starts_empty_without_language_classes_or_a_type_system() {
|
||||
// 只观察初始空堆,不因查询自动初始化对象。
|
||||
let vm = Vm::new().unwrap();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证普通 Record 的身份表示、恒真和身份相等回退,无序比较及不可调用的错误。
|
||||
fn defaults_provide_identity_representation_hash_and_truth() {
|
||||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 第一个独立对象/资源保留一份根,与 second/equal 比较身份或引用关系。
|
||||
let first = vm.new_object().unwrap();
|
||||
// 另一个分配结果有独立身份,即使内容相同也不是同对象。
|
||||
let second = vm.new_object().unwrap();
|
||||
assert_eq!(vm.object(first).unwrap().header().identity(), first);
|
||||
assert!(vm.is_same(first, first).unwrap());
|
||||
@@ -118,12 +147,18 @@ fn defaults_provide_identity_representation_hash_and_truth() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证复制句柄不保留对象,显式 retain/release 控制资源销毁与过期访问。
|
||||
fn explicit_references_control_lifetime_and_copies_are_borrowed() {
|
||||
// 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。
|
||||
let drops = Arc::new(AtomicUsize::new(0));
|
||||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// resource 返回一份外部根,后续复制不新增根。
|
||||
let owned = resource(&mut vm, &drops, vec![]);
|
||||
// 直接复制 ObjectRef,ref_count 仍为一,不能额外 release。
|
||||
let borrowed = owned;
|
||||
assert_eq!(vm.ref_count(borrowed).unwrap(), 1);
|
||||
// 显式多取得一份外部根,需两次归还后才实际析构。
|
||||
vm.retain(owned).unwrap();
|
||||
assert_eq!(vm.ref_count(owned).unwrap(), 2);
|
||||
vm.release(owned).unwrap();
|
||||
@@ -131,18 +166,25 @@ fn explicit_references_control_lifetime_and_copies_are_borrowed() {
|
||||
vm.release(owned).unwrap();
|
||||
assert_eq!(drops.load(Ordering::SeqCst), 1);
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
// 最后拥有根释放后,复制来的借用句柄必须失效,不能当作额外拥有根。
|
||||
assert!(matches!(vm.object(borrowed), Err(VmError::InvalidHandle)));
|
||||
assert_eq!(vm.release(borrowed), Err(VmError::InvalidHandle));
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证字段独立持有内部边,不能用外部 release 归还仅借用的属性边。
|
||||
fn attributes_keep_objects_alive_and_delete_releases_them() {
|
||||
// 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。
|
||||
let drops = Arc::new(AtomicUsize::new(0));
|
||||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 普通 Record 字段作为子对象强边来源。
|
||||
let owner = vm.new_object().unwrap();
|
||||
// 子对象先有分配根,写入属性后另有内部边。
|
||||
let child = resource(&mut vm, &drops, vec![]);
|
||||
vm.set_attr(owner, "child", Value::Object(child)).unwrap();
|
||||
assert_eq!(vm.ref_count(child).unwrap(), 2);
|
||||
// 归还分配根后属性边继续保活,不能再当作拥有根重复 release。
|
||||
vm.release(child).unwrap();
|
||||
assert_eq!(vm.ref_count(child).unwrap(), 1);
|
||||
assert_eq!(vm.release(child), Err(VmError::NoOwnedReference));
|
||||
@@ -156,16 +198,22 @@ fn attributes_keep_objects_alive_and_delete_releases_them() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证属性同对象重写先保留新边再归还旧边,随后改标量才释放最后强边。
|
||||
fn replacing_the_same_edge_does_not_destroy_the_target() {
|
||||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 普通 Record 字段作为子对象强边来源。
|
||||
let owner = vm.new_object().unwrap();
|
||||
// 独立目标用于属性替换或多重载荷边计数。
|
||||
let target = vm.new_object().unwrap();
|
||||
vm.set_attr(owner, "child", Value::Object(target)).unwrap();
|
||||
// 归还原根,后续存活完全依赖已建立的内部边。
|
||||
vm.release(target).unwrap();
|
||||
vm.set_attr(owner, "child", Value::Object(target)).unwrap();
|
||||
assert_eq!(vm.ref_count(target).unwrap(), 1);
|
||||
vm.set_attr(owner, "child", Value::Number(7_i64.into()))
|
||||
.unwrap();
|
||||
// 属性从对象改为数值会归还原目标最后的边,因此旧 target 句柄应失效。
|
||||
assert!(matches!(vm.object(target), Err(VmError::InvalidHandle)));
|
||||
assert_eq!(
|
||||
vm.get_attr(owner, "child").unwrap(),
|
||||
@@ -174,15 +222,22 @@ fn replacing_the_same_edge_does_not_destroy_the_target() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证宿主主动 retain 属性借用结果后,可在原父对象释放后继续使用。
|
||||
fn borrowed_attribute_results_can_be_retained_by_the_host() {
|
||||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 父对象拥有 child 字段边,释放父根会移除该边。
|
||||
let parent = vm.new_object().unwrap();
|
||||
// 子对象先有分配根,写入属性后另有内部边。
|
||||
let child = vm.new_object().unwrap();
|
||||
vm.set_attr(parent, "child", Value::Object(child)).unwrap();
|
||||
// 归还分配根后属性边继续保活,不能再当作拥有根重复 release。
|
||||
vm.release(child).unwrap();
|
||||
// get_attr 返回借用值,宿主若要跨父对象寿命使用须手动 retain。
|
||||
let Value::Object(borrowed) = vm.get_attr(parent, "child").unwrap() else {
|
||||
panic!("expected an object");
|
||||
};
|
||||
// 取得独立宿主根后,原持有字段消失也不会使结果失效。
|
||||
vm.retain(borrowed).unwrap();
|
||||
vm.release(parent).unwrap();
|
||||
assert_eq!(vm.ref_count(borrowed).unwrap(), 1);
|
||||
@@ -191,12 +246,19 @@ fn borrowed_attribute_results_can_be_retained_by_the_host() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证异堆和过期值写入失败保持旧字段,retain/release/身份比较也验证堆归属。
|
||||
fn failed_foreign_and_stale_writes_preserve_the_old_attribute() {
|
||||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 第二个 VM 用于构造确实属于异堆的有效身份。
|
||||
let mut other = Vm::new().unwrap();
|
||||
// 普通 Record 字段作为子对象强边来源。
|
||||
let owner = vm.new_object().unwrap();
|
||||
// 句柄在所属 VM 中有效,但本 VM 不能 retain、存储或当作结果返回。
|
||||
let foreign = other.new_object().unwrap();
|
||||
// 制造旧身份;释放后写入必须失败而非覆盖旧值。
|
||||
let stale = vm.new_object().unwrap();
|
||||
// 销毁对象使句柄过期,后续不再拥有任何可归还的根。
|
||||
vm.release(stale).unwrap();
|
||||
vm.set_attr(owner, "value", Value::Number(1_i64.into()))
|
||||
.unwrap();
|
||||
@@ -218,22 +280,32 @@ fn failed_foreign_and_stale_writes_preserve_the_old_attribute() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证槽代际与堆身份共同防止旧句柄别名新对象。
|
||||
fn released_handles_cannot_alias_new_objects_or_new_vms() {
|
||||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 释放后保留旧句柄表示,检查槽复用不会让它复活。
|
||||
let old = vm.new_object().unwrap();
|
||||
vm.release(old).unwrap();
|
||||
// 新分配可复用槽号,但代际必须不同。
|
||||
let new = vm.new_object().unwrap();
|
||||
assert_ne!(old, new);
|
||||
assert_eq!(vm.repr(old), Err(VmError::InvalidHandle));
|
||||
// 同号槽在另一堆也不是有效身份,必须报 ForeignHandle。
|
||||
let other = Vm::new().unwrap();
|
||||
assert!(matches!(other.object(new), Err(VmError::ForeignHandle)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证槽向量扩容不移动独立装箱对象实体。
|
||||
fn boxed_objects_keep_their_addresses_when_the_slot_vector_grows() {
|
||||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 保持首个对象存活,扩容前后比较其独立 Box 地址而非槽向量中的位置。
|
||||
let first = vm.new_object().unwrap();
|
||||
// 保存对象实体地址,不保存可能因向量扩容变化的槽地址。
|
||||
let address = std::ptr::from_ref(vm.object(first).unwrap());
|
||||
// 额外分配四千零九十六个对象,迫使 slots 向量扩容;所有对象在 VM 结束时清理。
|
||||
for _ in 0..4096 {
|
||||
vm.new_object().unwrap();
|
||||
}
|
||||
@@ -241,25 +313,37 @@ fn boxed_objects_keep_their_addresses_when_the_slot_vector_grows() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 构造长单向拥有链,释放唯一头根应通过显式工作栈完成级联回收。
|
||||
fn zero_reference_chains_are_released_without_recursive_destructors() {
|
||||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 长链唯一保留外部根的起点。
|
||||
let head = vm.new_object().unwrap();
|
||||
// tail 只是借用游标,后续节点仅由前节点 next 属性持有。
|
||||
let mut tail = head;
|
||||
// 压力设置:足够多次分配或链节点,检验稳定盒与非递归清理。
|
||||
for _ in 0..4096 {
|
||||
// 新节点先分配根,接上链后归还该根,仅留下内部边。
|
||||
let next = vm.new_object().unwrap();
|
||||
vm.set_attr(tail, "next", Value::Object(next)).unwrap();
|
||||
vm.release(next).unwrap();
|
||||
tail = next;
|
||||
}
|
||||
// 唯一头根移除应触发整条无环链的级联释放。
|
||||
vm.release(head).unwrap();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证有外部根的互引环保留,最后根归还后 collect_cycles 才整环回收。
|
||||
fn cycles_remain_while_rooted_and_are_collected_when_unreachable() {
|
||||
// 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。
|
||||
let drops = Arc::new(AtomicUsize::new(0));
|
||||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 第一个独立对象/资源保留一份根,与 second/equal 比较身份或引用关系。
|
||||
let first = resource(&mut vm, &drops, vec![]);
|
||||
// 另一个分配结果有独立身份,即使内容相同也不是同对象。
|
||||
let second = resource(&mut vm, &drops, vec![]);
|
||||
vm.set_attr(first, "next", Value::Object(second)).unwrap();
|
||||
vm.set_attr(second, "next", Value::Object(first)).unwrap();
|
||||
@@ -270,14 +354,20 @@ fn cycles_remain_while_rooted_and_are_collected_when_unreachable() {
|
||||
assert_eq!(vm.collect_cycles(), 2);
|
||||
assert_eq!(vm.collect_cycles(), 0);
|
||||
assert_eq!(drops.load(Ordering::SeqCst), 2);
|
||||
// 无根环回收后第一个旧句柄失效,不能只依赖析构计数判断槽是否仍可访问。
|
||||
assert!(matches!(vm.object(first), Err(VmError::InvalidHandle)));
|
||||
// 同一垃圾环中的第二个对象也必须失效,避免只回收部分环。
|
||||
assert!(matches!(vm.object(second), Err(VmError::InvalidHandle)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证回收垃圾环时只扣除其指向幸存者的边,不销毁仍有根的目标。
|
||||
fn cycle_collection_preserves_rooted_targets_and_updates_counts() {
|
||||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 幸存者保留外部根,即使垃圾环另持有它也不能被 GC 误删。
|
||||
let live = vm.new_object().unwrap();
|
||||
// 自环使普通强计数无法归零;无外部根后交给显式环回收或 VM Drop。
|
||||
let cycle = vm.new_object().unwrap();
|
||||
vm.set_attr(cycle, "self", Value::Object(cycle)).unwrap();
|
||||
vm.set_attr(cycle, "live", Value::Object(live)).unwrap();
|
||||
@@ -290,12 +380,18 @@ fn cycle_collection_preserves_rooted_targets_and_updates_counts() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证 trace 两次报告同一目标就持有两条强边,释放载荷逐边归还。
|
||||
fn payload_edges_are_owned_and_duplicate_edges_are_counted() {
|
||||
// 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。
|
||||
let drops = Arc::new(AtomicUsize::new(0));
|
||||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 独立目标用于属性替换或多重载荷边计数。
|
||||
let target = vm.new_object().unwrap();
|
||||
// 载荷同时拥有同一目标两次,总强计数应包含两条内部边。
|
||||
let holder = resource(&mut vm, &drops, vec![target, target]);
|
||||
assert_eq!(vm.ref_count(target).unwrap(), 3);
|
||||
// 归还原根,后续存活完全依赖已建立的内部边。
|
||||
vm.release(target).unwrap();
|
||||
vm.release(holder).unwrap();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
@@ -303,10 +399,15 @@ fn payload_edges_are_owned_and_duplicate_edges_are_counted() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证混合载荷边与属性边的环能被同一次图遍历回收。
|
||||
fn payload_edges_participate_in_cycle_tracing() {
|
||||
// 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。
|
||||
let drops = Arc::new(AtomicUsize::new(0));
|
||||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 普通属性对象作为混合环的一端。
|
||||
let record = vm.new_object().unwrap();
|
||||
// Resource 载荷拥有 record,record 的字段再反向拥有 Resource。
|
||||
let native = resource(&mut vm, &drops, vec![record]);
|
||||
vm.set_attr(record, "native", Value::Object(native))
|
||||
.unwrap();
|
||||
@@ -317,11 +418,17 @@ fn payload_edges_participate_in_cycle_tracing() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证分配前完整边预检,后部异堆边失败不能部分保留前部有效边。
|
||||
fn invalid_payload_edges_abort_allocation_without_retaining_valid_edges() {
|
||||
// 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。
|
||||
let drops = Arc::new(AtomicUsize::new(0));
|
||||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 第二个 VM 用于构造确实属于异堆的有效身份。
|
||||
let mut other = Vm::new().unwrap();
|
||||
// 合法本堆边放在错误边前面,验证失败不部分提交其计数。
|
||||
let local = vm.new_object().unwrap();
|
||||
// 句柄在所属 VM 中有效,但本 VM 不能 retain、存储或当作结果返回。
|
||||
let foreign = other.new_object().unwrap();
|
||||
assert_eq!(
|
||||
vm.allocate(Resource {
|
||||
@@ -336,10 +443,15 @@ fn invalid_payload_edges_abort_allocation_without_retaining_valid_edges() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证 Rust 载荷可覆盖显示、真值、哈希和比较,无需附着语言类型。
|
||||
fn rust_payload_operations_override_defaults_without_language_types() {
|
||||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 测试用 Number(0) 自定义真值为假,不是内置 EndorNumberObject。
|
||||
let zero = vm.allocate(Number(0)).unwrap();
|
||||
// 第一个独立对象/资源保留一份根,与 second/equal 比较身份或引用关系。
|
||||
let first = vm.allocate(Number(42)).unwrap();
|
||||
// 同值不同身份的 Number,必须内容相等且哈希一致。
|
||||
let equal = vm.allocate(Number(42)).unwrap();
|
||||
assert!(!vm.is_same(first, equal).unwrap());
|
||||
assert!(vm.compare(first, equal, CompareOp::Eq).unwrap());
|
||||
@@ -351,17 +463,21 @@ fn rust_payload_operations_override_defaults_without_language_types() {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// 反向比较场景的右操作数:只对 Gt 返回 true,用于识别左 Lt 是否正确交换方向。
|
||||
struct ReflectedOrdering;
|
||||
|
||||
impl ObjectPayload for ReflectedOrdering {
|
||||
/// Store/比较协议的具体载荷下转型入口,不依赖语言名义类型。
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
/// 错误与诊断显示的类别标签。
|
||||
fn kind_name(&self) -> &'static str {
|
||||
"reflected-ordering"
|
||||
}
|
||||
|
||||
/// Store 先正向后反向调用;返回 None 是未实现,Some(false) 则不再回退。
|
||||
fn compare(
|
||||
&self,
|
||||
_other: &dyn ObjectPayload,
|
||||
@@ -370,23 +486,31 @@ impl ObjectPayload for ReflectedOrdering {
|
||||
Ok(Some(operation == CompareOp::Gt))
|
||||
}
|
||||
|
||||
/// Store::hash 调用时明确返回不支持,不能退回默认对象身份哈希。
|
||||
fn hash(&self, _identity: ObjectRef) -> Result<u64, VmError> {
|
||||
Err(VmError::UnsupportedOperation("hash"))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证左侧未实现时把 Lt 反向为右侧 Gt,显式不支持 hash 原样传播。
|
||||
fn reflected_operations_and_explicitly_unsupported_hashing_are_respected() {
|
||||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 左侧 Record 不实现有序比较,Store 应尝试右侧反向协议。
|
||||
let left = vm.allocate(Record).unwrap();
|
||||
// 右侧专门只对 Gt 返回真,证明左 Lt 正确反射。
|
||||
let right = vm.allocate(ReflectedOrdering).unwrap();
|
||||
assert!(vm.compare(left, right, CompareOp::Lt).unwrap());
|
||||
assert_eq!(vm.hash(right), Err(VmError::UnsupportedOperation("hash")));
|
||||
}
|
||||
|
||||
/// 简单旧式原生回调,仅接受两个直接整数并检查 i64 加法溢出;不是 Endor 通用算术实现。
|
||||
fn add(arguments: &[Value]) -> Result<Value, VmError> {
|
||||
// 先按参数形状匹配两个直接 Number,其他输入走回调参数错误。
|
||||
match arguments {
|
||||
[Value::Number(left), Value::Number(right)] => {
|
||||
// as_i64 同时成功才允许此测试回调运算,不接受大整数或小数隐式转换。
|
||||
let (left, right) = left
|
||||
.as_i64()
|
||||
.zip(right.as_i64())
|
||||
@@ -400,8 +524,11 @@ fn add(arguments: &[Value]) -> Result<Value, VmError> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证原生分派、回调错误、禁止动态属性,以及异堆参数在回调前拒绝。
|
||||
fn native_calls_dispatch_validate_arguments_and_propagate_errors() {
|
||||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 把 Rust add 包装为旧式原生函数,由 VM 负责参数/结果身份验证。
|
||||
let function = vm.allocate(NativeFunction::new("add", add)).unwrap();
|
||||
assert_eq!(vm.repr(function).unwrap(), "<native-function add>");
|
||||
assert_eq!(
|
||||
@@ -427,7 +554,9 @@ fn native_calls_dispatch_validate_arguments_and_propagate_errors() {
|
||||
vm.set_attr(function, "x", Value::Nil),
|
||||
Err(VmError::UnsupportedOperation("attributes"))
|
||||
);
|
||||
// 第二个 VM 用于构造确实属于异堆的有效身份。
|
||||
let mut other = Vm::new().unwrap();
|
||||
// 句柄在所属 VM 中有效,但本 VM 不能 retain、存储或当作结果返回。
|
||||
let foreign = other.new_object().unwrap();
|
||||
assert_eq!(
|
||||
vm.call(function, &[Value::Object(foreign)]),
|
||||
@@ -436,41 +565,58 @@ fn native_calls_dispatch_validate_arguments_and_propagate_errors() {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// 非法结果场景的故障注入载荷:只保存未 trace 的借用身份,调用时故意返还它让 VM 拒绝。
|
||||
struct ReturnsObject(ObjectRef);
|
||||
|
||||
impl ObjectPayload for ReturnsObject {
|
||||
/// Store/比较协议的具体载荷下转型入口,不依赖语言名义类型。
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
/// 错误与诊断显示的类别标签。
|
||||
fn kind_name(&self) -> &'static str {
|
||||
"invalid-return"
|
||||
}
|
||||
|
||||
/// 故意返回未经验证的原始对象句柄,测试 VM 能否在结果移交前阻止非法身份。
|
||||
fn call(&self, _arguments: &[Value]) -> Result<Value, VmError> {
|
||||
Ok(Value::Object(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证原生结果仍需堆归属/存活检查,宿主回调不能借返回通道塞入非法句柄。
|
||||
fn native_results_cannot_smuggle_foreign_or_dead_handles_into_the_vm() {
|
||||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 第二个 VM 用于构造确实属于异堆的有效身份。
|
||||
let mut other = Vm::new().unwrap();
|
||||
// 句柄在所属 VM 中有效,但本 VM 不能 retain、存储或当作结果返回。
|
||||
let foreign = other.new_object().unwrap();
|
||||
// 故意不 trace 持有的句柄,因为这里测试的是非法借用结果而非合法拥有边。
|
||||
let invalid_return = vm.allocate(ReturnsObject(foreign)).unwrap();
|
||||
assert_eq!(vm.call(invalid_return, &[]), Err(VmError::ForeignHandle));
|
||||
// 本堆对象释放后制造过期返回值,不与异堆错误混淆。
|
||||
let dead = vm.new_object().unwrap();
|
||||
// 之后只剩失效的借用身份,回调返回不能把它重新保活。
|
||||
vm.release(dead).unwrap();
|
||||
// 故意不 trace 持有的句柄,因为这里测试的是非法借用结果而非合法拥有边。
|
||||
let invalid_return = vm.allocate(ReturnsObject(dead)).unwrap();
|
||||
assert_eq!(vm.call(invalid_return, &[]), Err(VmError::InvalidHandle));
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证离开 VM 作用域最终销毁仍有根对象及尚未显式 GC 的自环。
|
||||
fn dropping_the_vm_cleans_up_rooted_objects_and_uncollected_cycles() {
|
||||
// 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。
|
||||
let drops = Arc::new(AtomicUsize::new(0));
|
||||
{
|
||||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 离开局部 VM 作用域时仍有一份根,VM Drop 也必须释放宿主资源。
|
||||
let rooted = resource(&mut vm, &drops, vec![]);
|
||||
// 自环使普通强计数无法归零;无外部根后交给显式环回收或 VM Drop。
|
||||
let cycle = resource(&mut vm, &drops, vec![]);
|
||||
vm.set_attr(cycle, "self", Value::Object(cycle)).unwrap();
|
||||
vm.release(cycle).unwrap();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 完整类型化能力的集成回归:注册替换关系、调用实参/结果验证,以及能力定义的强边保活。
|
||||
//! 测试框架使用公开 Vm API,源码辅助函数手工构造已验证指令,无需源码解析器。
|
||||
//! Remote-only coverage of explicit typed capability contracts.
|
||||
use endor_core::{
|
||||
AnnotationType as T, CallEffect, CallSignature, CodeLayout, ContextNativeFunction, EndorCode,
|
||||
@@ -5,6 +7,7 @@ use endor_core::{
|
||||
TypeConstructor, TypedMethodRequirement, Value, Vm, VmError,
|
||||
};
|
||||
|
||||
/// 测试辅助:构造单个 check 方法能力,完整参数列表含显式 self,返回一份能力根。
|
||||
fn capability(
|
||||
vm: &mut Vm,
|
||||
name: &str,
|
||||
@@ -21,14 +24,21 @@ fn capability(
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
/// 测试辅助:构造先写 globals.ran=42 再返回给定常量的源码方法,附 owner 与类型注解。
|
||||
fn source(
|
||||
vm: &mut Vm,
|
||||
// 借用源码函数的全局环境,new_function 为它建立内部边;体执行时向其中写 ran。
|
||||
globals: ObjectRef,
|
||||
// 附入 SourceSignature 的声明类名,运行时结合实际接收者解析 owner 类型。
|
||||
owner: &str,
|
||||
// 含显式 self 的参数注解列表,长度同时决定参数和局部槽数量。
|
||||
types: &[&str],
|
||||
// 返回注解文本,调用/契约检查时解析,不在本辅助函数绑定为类型句柄。
|
||||
returns: &str,
|
||||
// 体写 ran 后返回的常量;对象变体在代码分配后由常量表内部边持有。
|
||||
value: Value,
|
||||
) -> ObjectRef {
|
||||
// 手工代码提供可观察 ran 副作用,参数/局部布局按类型列表一一对应。
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadConst, 0),
|
||||
@@ -51,30 +61,41 @@ fn source(
|
||||
method_owner: Some(owner.into()),
|
||||
})
|
||||
.unwrap();
|
||||
// 分配代码后常量成为内部强边,函数将持有代码引用。
|
||||
let code = vm.new_code(code).unwrap();
|
||||
// 分配源码函数根,载荷分别持有代码和 globals;这里没有默认值或自由 Cell,也不使用原生 captures。
|
||||
let function = vm
|
||||
.new_function("check", code, globals, vec![], vec![])
|
||||
.unwrap();
|
||||
// 归还辅助函数临时代码根,源码函数仍拥有代码边。
|
||||
vm.release(code).unwrap();
|
||||
function
|
||||
}
|
||||
#[derive(Debug)]
|
||||
/// 返回固定直接整数的测试原生分派器,签名由包装载荷快照持有。
|
||||
struct Native(CallSignature);
|
||||
impl NativeCallable for Native {
|
||||
/// 原生实现公布不可变完整签名,ContextNativeFunction 会快照并 trace 类型定义引用。
|
||||
fn signature(&self) -> Option<CallSignature> {
|
||||
Some(self.0.clone())
|
||||
}
|
||||
/// VM 原生分派调用,忽略已由签名检查的实参,只返回直接整数,不访问环境或分配对象。
|
||||
fn invoke(&self, _: &mut NativeContext<'_>, _: &[Value]) -> Result<NativeResult, VmError> {
|
||||
Ok(NativeResult::Return(Value::Number(7.into())))
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
/// 注册失败不改变版本/声明;实现参数可比能力更宽,调用允许其自己签名支持的额外值。
|
||||
fn typed_registration_is_atomic_and_wider_native_inputs_remain_callable() {
|
||||
// 每个测试独立 VM,避免契约/全局修改污染其他场景。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// Object/Type 自举根在末尾归还,元类型环随后显式回收。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 名义类型用于方法 owner/签名;身份不是按类型名字符串比较。
|
||||
let definition = vm
|
||||
.new_type("Model", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// 能力签名供注册与实际调用核对,名义/能力引用由能力载荷 trace 保活。
|
||||
let contract = capability(
|
||||
&mut vm,
|
||||
"Numeric",
|
||||
@@ -82,10 +103,12 @@ fn typed_registration_is_atomic_and_wider_native_inputs_remain_callable() {
|
||||
T::Number,
|
||||
CallEffect::ReadOnly,
|
||||
);
|
||||
// 分别测试参数缩窄与效果扩大,两者都必须在声明阶段失败。
|
||||
for (argument, effect) in [
|
||||
(T::String, CallEffect::ReadOnly),
|
||||
(T::Any, CallEffect::Mutating),
|
||||
] {
|
||||
// 分配源码/原生函数拥有根,完整签名与显式 captures 各自报告托管引用。
|
||||
let function = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"bad",
|
||||
@@ -98,6 +121,7 @@ fn typed_registration_is_atomic_and_wider_native_inputs_remain_callable() {
|
||||
))
|
||||
.unwrap();
|
||||
vm.define_method(definition, "check", function).unwrap();
|
||||
// 失败能力声明不能修改类型载荷版本或留下已声明关系。
|
||||
let version = vm.change_version(definition).unwrap();
|
||||
assert!(matches!(
|
||||
vm.implement_trait(definition, contract),
|
||||
@@ -107,6 +131,7 @@ fn typed_registration_is_atomic_and_wider_native_inputs_remain_callable() {
|
||||
assert!(!vm.implements_trait(definition, contract).unwrap());
|
||||
vm.release(function).unwrap();
|
||||
}
|
||||
// 分配源码/原生函数拥有根,完整签名与显式 captures 各自报告托管引用。
|
||||
let function = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"good",
|
||||
@@ -120,6 +145,7 @@ fn typed_registration_is_atomic_and_wider_native_inputs_remain_callable() {
|
||||
.unwrap();
|
||||
vm.define_method(definition, "check", function).unwrap();
|
||||
vm.implement_trait(definition, contract).unwrap();
|
||||
// 实例先分配根再附着类型,作为显式 self 传入方法。
|
||||
let receiver = vm.new_object().unwrap();
|
||||
vm.attach_type(receiver, definition).unwrap();
|
||||
assert_eq!(
|
||||
@@ -133,6 +159,7 @@ fn typed_registration_is_atomic_and_wider_native_inputs_remain_callable() {
|
||||
.unwrap(),
|
||||
Value::Number(7.into())
|
||||
);
|
||||
// 逐项归还测试拥有根,类型/签名/捕获边由 Heap 自动维护,不能重复释放。
|
||||
for object in [
|
||||
receiver,
|
||||
function,
|
||||
@@ -143,18 +170,25 @@ fn typed_registration_is_atomic_and_wider_native_inputs_remain_callable() {
|
||||
] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 收集无根元类型/签名/方法图环,断言最终没有存活堆对象。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 显式能力启用源码注解检查,参数错误应阻止函数体副作用,返回错误发生在体执行之后。
|
||||
fn typed_source_methods_enforce_their_annotations_even_when_global_checks_are_off() {
|
||||
// 每个测试独立 VM,避免契约/全局修改污染其他场景。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// Object/Type 自举根在末尾归还,元类型环随后显式回收。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 名义类型用于方法 owner/签名;身份不是按类型名字符串比较。
|
||||
let definition = vm
|
||||
.new_type("Model", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// 源码方法注解在此环境解析,同时 ran 标志观测函数体是否进入。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 源码参数声明 Number 但返回 Bool,分别验证参数阶段和返回阶段的检查。
|
||||
let function = source(
|
||||
&mut vm,
|
||||
globals,
|
||||
@@ -164,14 +198,16 @@ fn typed_source_methods_enforce_their_annotations_even_when_global_checks_are_of
|
||||
Value::Bool(true),
|
||||
);
|
||||
vm.define_method(definition, "check", function).unwrap();
|
||||
// 实例先分配根再附着类型,作为显式 self 传入方法。
|
||||
let receiver = vm.new_object().unwrap();
|
||||
vm.attach_type(receiver, definition).unwrap();
|
||||
// Existing untyped declarations retain the default-off compatibility mode.
|
||||
// 尚未声明类型化能力且未开全局源码检查时,旧方法保持兼容:Bool 参数与结果都暂不验注解。
|
||||
assert_eq!(
|
||||
vm.call(function, &[Value::Object(receiver), Value::Bool(false)])
|
||||
.unwrap(),
|
||||
Value::Bool(true)
|
||||
);
|
||||
// 能力签名供注册与实际调用核对,名义/能力引用由能力载荷 trace 保活。
|
||||
let contract = capability(
|
||||
&mut vm,
|
||||
"Numeric",
|
||||
@@ -180,6 +216,7 @@ fn typed_source_methods_enforce_their_annotations_even_when_global_checks_are_of
|
||||
CallEffect::ReadOnly,
|
||||
);
|
||||
vm.implement_trait(definition, contract).unwrap();
|
||||
// 先复位 ran;类型不匹配参数应在任何源码 StoreGlobal 前被拒绝。
|
||||
vm.dict_set(globals, Value::Text("ran".into()), Value::Number(0.into()))
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
@@ -191,12 +228,14 @@ fn typed_source_methods_enforce_their_annotations_even_when_global_checks_are_of
|
||||
Some(Value::Number(0.into()))
|
||||
);
|
||||
assert!(
|
||||
// 参数合法时方法体可运行,但违背能力方法的返回约束应在出口定位 return。
|
||||
matches!(vm.call(function, &[Value::Object(receiver), Value::Number(1.into())]), Err(VmError::TypeMismatch { position, .. }) if position=="return")
|
||||
);
|
||||
assert_eq!(
|
||||
vm.dict_get(globals, &Value::Text("ran".into())).unwrap(),
|
||||
Some(Value::Number(42.into()))
|
||||
);
|
||||
// 逐项归还测试拥有根,类型/签名/捕获边由 Heap 自动维护,不能重复释放。
|
||||
for object in [
|
||||
receiver,
|
||||
function,
|
||||
@@ -208,18 +247,25 @@ fn typed_source_methods_enforce_their_annotations_even_when_global_checks_are_of
|
||||
] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 收集无根元类型/签名/方法图环,断言最终没有存活堆对象。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 能力签名的参数/返回名义定义被强边保留,能力销毁后无隐藏注册根阻止回收。
|
||||
fn typed_trait_definitions_trace_their_signature_types_without_hidden_roots() {
|
||||
// 每个测试独立 VM,避免契约/全局修改污染其他场景。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// Object/Type 自举根在末尾归还,元类型环随后显式回收。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 名义类型用于方法 owner/签名;身份不是按类型名字符串比较。
|
||||
let definition = vm
|
||||
.new_type("Value", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// 弱身份不持根,用于观察签名边是否真的保留定义及何时释放。
|
||||
let weak = vm.downgrade(definition).unwrap();
|
||||
// 能力签名供注册与实际调用核对,名义/能力引用由能力载荷 trace 保活。
|
||||
let contract = capability(
|
||||
&mut vm,
|
||||
"ValueContract",
|
||||
@@ -227,34 +273,49 @@ fn typed_trait_definitions_trace_their_signature_types_without_hidden_roots() {
|
||||
T::Nominal(definition),
|
||||
CallEffect::ReadOnly,
|
||||
);
|
||||
// 归还定义测试根,能力参数/返回签名的拥有边必须继续保活它。
|
||||
vm.release(definition).unwrap();
|
||||
// 成功弱升级取得额外根,读完必须归还,否则会掩盖隐藏根检查。
|
||||
let retained = vm
|
||||
.upgrade(weak)
|
||||
.unwrap()
|
||||
.expect("signature retains its type");
|
||||
// 仅归还弱升级的那一份根,签名仍持有自己的内部边。
|
||||
vm.release(retained).unwrap();
|
||||
// 能力最后根归还后,其签名定义边也应消失。
|
||||
vm.release(contract).unwrap();
|
||||
// 收集无根元类型/签名/方法图环,断言最终没有存活堆对象。
|
||||
vm.collect_cycles();
|
||||
assert!(vm.upgrade(weak).unwrap().is_none());
|
||||
// 逐项归还测试拥有根,类型/签名/捕获边由 Heap 自动维护,不能重复释放。
|
||||
for object in [roots.object, roots.type_type] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 收集无根元类型/签名/方法图环,断言最终没有存活堆对象。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// 最后一项回归的宿主实现:包装器显式 captures 保活这些句柄,体内重绑定会使外层返回能力失效。
|
||||
struct RebindThenReturn {
|
||||
/// 自身直接返回约束仍满足 Model,但外层能力返回约束需更严格重验 Feature。
|
||||
signature: CallSignature,
|
||||
/// 含 Current 注解名称的字典,包装载荷通过 captures 持边。
|
||||
globals: ObjectRef,
|
||||
/// 用于破坏 Current 约束的无关类型,captures 负责保活。
|
||||
other: ObjectRef,
|
||||
/// 返回的借用 Model 实例,结果根由 NativeContext 获取。
|
||||
result: ObjectRef,
|
||||
}
|
||||
impl NativeCallable for RebindThenReturn {
|
||||
/// 原生实现公布不可变完整签名,ContextNativeFunction 会快照并 trace 类型定义引用。
|
||||
fn signature(&self) -> Option<CallSignature> {
|
||||
Some(self.signature.clone())
|
||||
}
|
||||
/// VM 原生分派调用,先通过上下文重绑定 Current,再借用返回 Model 实例以触发外层能力重验。
|
||||
fn invoke(&self, cx: &mut NativeContext<'_>, _: &[Value]) -> Result<NativeResult, VmError> {
|
||||
// 函数体把 Current 重绑定为无关 Other,主动使返回 Model 的 Feature 契约失效。
|
||||
cx.dict_set(
|
||||
self.globals,
|
||||
Value::Text("Current".into()),
|
||||
@@ -264,21 +325,29 @@ impl NativeCallable for RebindThenReturn {
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
/// 原生体重绑定返回对象的能力所需注解环境后,返回时必须重验并报错,重复失败不能残留递归标记。
|
||||
fn actual_trait_returns_revalidate_capabilities_after_body_side_effects_and_restore_guards() {
|
||||
// 每个测试独立 VM,避免契约/全局修改污染其他场景。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// Object/Type 自举根在末尾归还,元类型环随后显式回收。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// Feature 方法返回值必须属于该基类型或其子类型。
|
||||
let result_base = vm
|
||||
.new_type("Result", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// Current 初始指向合法子类型,回调结束后的重验会观察该名称被替换。
|
||||
let result_child = vm
|
||||
.new_type("ResultChild", result_base, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// 同为 Record 但不是 Result 子类,替换 Current 后应破坏 Feature 契约。
|
||||
let other = vm
|
||||
.new_type("Other", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// 返回值实例的类型,check 源码方法声明 Current 作为返回类型。
|
||||
let model = vm
|
||||
.new_type("Model", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// 源码方法的 Current 注解在此字典动态解析;本场景只检查该方法契约,不执行其写 ran 的体。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
vm.dict_set(
|
||||
globals,
|
||||
@@ -286,8 +355,10 @@ fn actual_trait_returns_revalidate_capabilities_after_body_side_effects_and_rest
|
||||
Value::Object(result_child),
|
||||
)
|
||||
.unwrap();
|
||||
// 手工方法虽此处不运行,其当前签名仍用于能力符合性验证。
|
||||
let read = source(&mut vm, globals, "Model", &["Model"], "Current", Value::Nil);
|
||||
vm.define_method(model, "check", read).unwrap();
|
||||
// Model 显式实现 Feature,要求 check 返回 Result 基类型。
|
||||
let feature = capability(
|
||||
&mut vm,
|
||||
"Feature",
|
||||
@@ -296,11 +367,14 @@ fn actual_trait_returns_revalidate_capabilities_after_body_side_effects_and_rest
|
||||
CallEffect::External,
|
||||
);
|
||||
vm.implement_trait(model, feature).unwrap();
|
||||
// 原生 produce 返回此实例;返回约束要求它仍满足 Feature 而非仅有历史声明。
|
||||
let value = vm.new_object().unwrap();
|
||||
vm.attach_type(value, model).unwrap();
|
||||
// 调用原生 produce 的接收者类型,与被返回 Model 分开。
|
||||
let actor = vm
|
||||
.new_type("Actor", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// 能力签名供注册与实际调用核对,名义/能力引用由能力载荷 trace 保活。
|
||||
let contract = capability(
|
||||
&mut vm,
|
||||
"Producer",
|
||||
@@ -308,6 +382,7 @@ fn actual_trait_returns_revalidate_capabilities_after_body_side_effects_and_rest
|
||||
T::Capability(feature),
|
||||
CallEffect::External,
|
||||
);
|
||||
// 分配源码/原生函数拥有根,完整签名与显式 captures 各自报告托管引用。
|
||||
let function = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"produce",
|
||||
@@ -326,13 +401,15 @@ fn actual_trait_returns_revalidate_capabilities_after_body_side_effects_and_rest
|
||||
.unwrap();
|
||||
vm.define_method(actor, "check", function).unwrap();
|
||||
vm.implement_trait(actor, contract).unwrap();
|
||||
// 实例先分配根再附着类型,作为显式 self 传入方法。
|
||||
let receiver = vm.new_object().unwrap();
|
||||
vm.attach_type(receiver, actor).unwrap();
|
||||
assert!(matches!(
|
||||
vm.call(function, &[Value::Object(receiver)]),
|
||||
Err(VmError::TraitViolation { .. })
|
||||
));
|
||||
// Repeated failures must not leave a recursive-validation guard marked active.
|
||||
// 验证失败必须清理递归 guard;环境仍坏时每一次重新检查都应再次失败。
|
||||
// 重复能力失败验证 RAII 已清除递归 key,不能第二次被当重入跳过。
|
||||
for _ in 0..2 {
|
||||
assert!(vm.implements_trait(model, feature).is_err());
|
||||
}
|
||||
@@ -343,6 +420,7 @@ fn actual_trait_returns_revalidate_capabilities_after_body_side_effects_and_rest
|
||||
)
|
||||
.unwrap();
|
||||
assert!(vm.implements_trait(model, feature).unwrap());
|
||||
// 逐项归还测试拥有根,类型/签名/捕获边由 Heap 自动维护,不能重复释放。
|
||||
for object in [
|
||||
receiver,
|
||||
function,
|
||||
@@ -361,6 +439,7 @@ fn actual_trait_returns_revalidate_capabilities_after_body_side_effects_and_rest
|
||||
] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 收集无根元类型/签名/方法图环,断言最终没有存活堆对象。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
+109
-2
@@ -1,14 +1,23 @@
|
||||
//! 通过公开 Vm API 验证名义类型、显式能力和调用数量协议,由 Rust 测试框架逐场景调用。
|
||||
//! 同名不是同身份;继承与实例关系分开;所有类型、方法、实例都由托管强边和外部根保活。
|
||||
|
||||
use endor_core::{TypeConstructor, Value, Vm, VmError};
|
||||
|
||||
#[test]
|
||||
/// 区分子类型/实例关系、同名类型身份、不可变头部类型,以及实例独立保活定义图。
|
||||
fn nominal_types_have_distinct_inheritance_and_instance_relations() {
|
||||
// 各测试建立独立堆,不把其他场景的类型身份带入。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
// 显式自举 Object/Type 两个拥有根,新 VM 本身不预建语言类型。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 抽象 Block 作为继承基类,不代表其派生不能使用显式 Record 构造策略。
|
||||
let block = vm
|
||||
.new_type("Block", roots.object, TypeConstructor::Abstract)
|
||||
.unwrap();
|
||||
// Page 是 Block 子类型,同时自身是 Type 的实例。
|
||||
let page = vm.new_type("Page", block, TypeConstructor::Record).unwrap();
|
||||
// 同名 Page 再分配一次必须得到另一名义身份。
|
||||
let other = vm.new_type("Page", block, TypeConstructor::Record).unwrap();
|
||||
assert!(vm.is_subtype(page, block).unwrap());
|
||||
assert!(!vm.is_subtype(block, page).unwrap());
|
||||
@@ -17,6 +26,7 @@ fn nominal_types_have_distinct_inheritance_and_instance_relations() {
|
||||
assert!(vm.is_instance(roots.object, roots.type_type).unwrap());
|
||||
assert!(vm.is_instance(roots.type_type, roots.type_type).unwrap());
|
||||
assert!(!vm.is_subtype(roots.object, roots.type_type).unwrap());
|
||||
// call 构造的实例结果拥有一份根,后续清理必须显式 release。
|
||||
let Value::Object(instance) = vm.call(page, &[]).unwrap() else {
|
||||
panic!("instance required")
|
||||
};
|
||||
@@ -27,24 +37,32 @@ fn nominal_types_have_distinct_inheritance_and_instance_relations() {
|
||||
assert!(vm.set_attr(instance, "type", Value::Object(other)).is_err());
|
||||
assert!(vm.attach_type(instance, other).is_err());
|
||||
assert!(vm.replace_payload(page, endor_core::Record).is_err());
|
||||
// 归还每份测试拥有根,内部类型/基类/方法边不手动重复释放。
|
||||
for object in [roots.object, roots.type_type, block, page, other] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 清除无外部根的类型/元类型环;仍由实例可达的定义不得回收。
|
||||
vm.collect_cycles();
|
||||
// The instance keeps its definition, base and metatype reachable without
|
||||
// any host-owned type registry. The unrelated same-name type was collected.
|
||||
// 仅保留实例根仍可达定义、基类和元类型,无关的同名类型应被收集;没有堆外拥有注册表。
|
||||
assert_eq!(vm.object_count(), 5);
|
||||
assert!(vm.is_instance(instance, block).unwrap());
|
||||
// 实例最后根归还后,其类型图不再因该实例保活。
|
||||
vm.release(instance).unwrap();
|
||||
// 清除无外部根的类型/元类型环;仍由实例可达的定义不得回收。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 异堆类型附着失败不得改变头部;已有动态 type 字段也不得被托管类型附着覆盖。
|
||||
fn foreign_type_edges_fail_without_changing_the_target() {
|
||||
// 第一个 VM 拥有类型定义,不能把它附着到第二个 VM 的对象。
|
||||
let mut first = Vm::new().unwrap();
|
||||
// 第二个 VM 独立拥有对象和自己的类型自举图。
|
||||
let mut second = Vm::new().unwrap();
|
||||
// 显式自举 Object/Type 两个拥有根,新 VM 本身不预建语言类型。
|
||||
let roots = first.bootstrap_types().unwrap();
|
||||
// 待附着对象在第二堆,初始无名义类型且只有一根。
|
||||
let object = second.new_object().unwrap();
|
||||
assert_eq!(
|
||||
second.attach_type(object, roots.object),
|
||||
@@ -55,6 +73,7 @@ fn foreign_type_edges_fail_without_changing_the_target() {
|
||||
second
|
||||
.set_attr(object, "type", Value::Text("existing field".into()))
|
||||
.unwrap();
|
||||
// 同堆类型虽有效,仍应因对象已有动态 type 字段冲突而拒绝附着。
|
||||
let local = second.bootstrap_types().unwrap();
|
||||
assert!(second.attach_type(object, local.object).is_err());
|
||||
assert_eq!(
|
||||
@@ -64,9 +83,12 @@ fn foreign_type_edges_fail_without_changing_the_target() {
|
||||
assert_eq!(second.type_of(object).unwrap(), None);
|
||||
}
|
||||
|
||||
/// 能力测试辅助,生成指定显式参数数目的源码函数,含 self,总返回 Nil,不附完整类型签名。
|
||||
fn contract_method(vm: &mut Vm, parameters: usize) -> endor_core::ObjectRef {
|
||||
use endor_core::{CodeLayout, EndorCode, EndorCodeObject, Instruction, Opcode};
|
||||
// 生成唯一参数名称,使 CodeLayout 参数前缀与局部布局一致。
|
||||
let names: Vec<_> = (0..parameters).map(|index| format!("arg{index}")).collect();
|
||||
// 测试用已验证指令,返回常量 Nil;数量由参数布局决定。
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadConst, 0),
|
||||
@@ -80,27 +102,39 @@ fn contract_method(vm: &mut Vm, parameters: usize) -> endor_core::ObjectRef {
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
// 分配代码载荷使常量引用(如有)成为托管边。
|
||||
let code = vm.allocate(EndorCodeObject::new(code)).unwrap();
|
||||
// 函数全局字典用于注解解析和环境持有,函数内部边不依赖测试根。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 函数构造校验布局并持有代码/全局/默认值引用,返回一份根。
|
||||
let function = vm
|
||||
.new_function("contract method", code, globals, vec![], vec![])
|
||||
.unwrap();
|
||||
// 辅助函数归还临时代码根,函数内部边仍保留代码。
|
||||
vm.release(code).unwrap();
|
||||
// 辅助函数归还临时全局根,函数环境边继续保活。
|
||||
vm.release(globals).unwrap();
|
||||
function
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 显式能力可继承且按身份区分,错误方法/派生声明不能破坏已有契约,能力边维持定义存活。
|
||||
fn explicit_traits_inherit_validate_overrides_and_own_definition_edges() {
|
||||
use endor_core::MethodRequirement;
|
||||
// 各测试建立独立堆,不把其他场景的类型身份带入。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 显式自举 Object/Type 两个拥有根,新 VM 本身不预建语言类型。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 可构造基类作为能力继承与方法更新测试起点。
|
||||
let base = vm
|
||||
.new_type("Base", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// 含 self 和一个普通参数,满足 render 的最小能力数量要求。
|
||||
let valid = contract_method(&mut vm, 2);
|
||||
// 仅 self 一个参数,无法接受 render 所需普通参数。
|
||||
let invalid = contract_method(&mut vm, 1);
|
||||
vm.define_method(base, "render", valid).unwrap();
|
||||
// 最小能力声明只要求方法及显式参数数,不推断结构等价。
|
||||
let capability = vm
|
||||
.new_trait(
|
||||
"Drawable",
|
||||
@@ -110,14 +144,17 @@ fn explicit_traits_inherit_validate_overrides_and_own_definition_edges() {
|
||||
}],
|
||||
)
|
||||
.unwrap();
|
||||
// 同名能力仍为不同身份,声明一个不会自动支持另一个。
|
||||
let same_name = vm.new_trait("Drawable", vec![]).unwrap();
|
||||
assert!(!vm.implements_trait(base, capability).unwrap());
|
||||
vm.implement_trait(base, capability).unwrap();
|
||||
assert!(!vm.implements_trait(base, same_name).unwrap());
|
||||
// 已有派生类用于核对继承能力/覆盖方法,方法来源由真实继承查找决定。
|
||||
let child = vm
|
||||
.new_class("Child", base, vec![], std::collections::BTreeMap::new())
|
||||
.unwrap();
|
||||
assert!(vm.implements_trait(child, capability).unwrap());
|
||||
// 祖先方法更新不得破坏已声明后代能力,失败后原方法仍应保持不变。
|
||||
assert!(matches!(
|
||||
vm.define_method(base, "render", invalid),
|
||||
Err(VmError::TraitViolation { .. })
|
||||
@@ -130,12 +167,15 @@ fn explicit_traits_inherit_validate_overrides_and_own_definition_edges() {
|
||||
vm.new_class("Bad", base, vec![], [("render".into(), invalid)].into())
|
||||
.is_err()
|
||||
);
|
||||
// call 构造的实例结果拥有一份根,后续清理必须显式 release。
|
||||
let Value::Object(instance) = vm.call(child, &[]).unwrap() else {
|
||||
panic!("instance required");
|
||||
};
|
||||
// 归还测试根后类型声明的能力强边仍应保活,supports 不能依赖堆外注册表。
|
||||
vm.release(capability).unwrap();
|
||||
assert!(vm.supports(instance, capability).unwrap());
|
||||
assert!(vm.replace_payload(capability, endor_core::Record).is_err());
|
||||
// 归还每份测试拥有根,内部类型/基类/方法边不手动重复释放。
|
||||
for object in [
|
||||
roots.object,
|
||||
roots.type_type,
|
||||
@@ -148,24 +188,33 @@ fn explicit_traits_inherit_validate_overrides_and_own_definition_edges() {
|
||||
] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 清除无外部根的类型/元类型环;仍由实例可达的定义不得回收。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 后注册能力必须核对已有后代,父方法更新也不能破坏后代自己声明的能力。
|
||||
fn late_trait_registration_and_ancestor_updates_validate_existing_descendants() {
|
||||
use endor_core::MethodRequirement;
|
||||
// 各测试建立独立堆,不把其他场景的类型身份带入。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 显式自举 Object/Type 两个拥有根,新 VM 本身不预建语言类型。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 可构造基类作为能力继承与方法更新测试起点。
|
||||
let base = vm
|
||||
.new_type("Base", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// 满足 self 加一个参数的源码函数,用于恢复合法方法。
|
||||
let good = contract_method(&mut vm, 2);
|
||||
// 少一个参数的源码函数,不可用于已声明能力的方法。
|
||||
let bad = contract_method(&mut vm, 1);
|
||||
vm.define_method(base, "render", good).unwrap();
|
||||
// 已有派生类用于核对继承能力/覆盖方法,方法来源由真实继承查找决定。
|
||||
let child = vm
|
||||
.new_class("Child", base, vec![], [("render".into(), bad)].into())
|
||||
.unwrap();
|
||||
// 最小能力声明只要求方法及显式参数数,不推断结构等价。
|
||||
let capability = vm
|
||||
.new_trait(
|
||||
"Drawable",
|
||||
@@ -178,6 +227,7 @@ fn late_trait_registration_and_ancestor_updates_validate_existing_descendants()
|
||||
assert!(vm.implement_trait(base, capability).is_err());
|
||||
assert!(!vm.implements_trait(base, capability).unwrap());
|
||||
vm.define_method(child, "render", good).unwrap();
|
||||
// 不覆盖父方法的后代自行声明能力,父方法替换也必须顾及它。
|
||||
let inheriting = vm
|
||||
.new_class(
|
||||
"Inheriting",
|
||||
@@ -192,6 +242,7 @@ fn late_trait_registration_and_ancestor_updates_validate_existing_descendants()
|
||||
vm.object_store().lookup_method(base, "render").unwrap(),
|
||||
Some(good)
|
||||
);
|
||||
// 另建独立 VM 查询原堆类型/能力应报 ForeignHandle,不是简单 false。
|
||||
let foreign = Vm::new().unwrap();
|
||||
assert_eq!(
|
||||
foreign.implements_trait(base, capability),
|
||||
@@ -200,21 +251,28 @@ fn late_trait_registration_and_ancestor_updates_validate_existing_descendants()
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Function 配置覆盖源码/原生/绑定方法,但普通可调用类不属于函数;配置不能切换。
|
||||
fn configured_function_identity_covers_source_native_and_bound_methods() {
|
||||
use endor_core::{NativeFunction, SourceSignature};
|
||||
// 各测试建立独立堆,不把其他场景的类型身份带入。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 显式自举 Object/Type 两个拥有根,新 VM 本身不预建语言类型。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 显式注册函数名义类型,构造为 Abstract 避免把普通记录伪造成函数载荷。
|
||||
let function_type = vm
|
||||
.new_type("Function", roots.object, TypeConstructor::Abstract)
|
||||
.unwrap();
|
||||
vm.set_function_type(function_type).unwrap();
|
||||
// 源码函数应由分配入口自动附着已配置 Function。
|
||||
let source = contract_method(&mut vm, 1);
|
||||
// 原生载荷也按 is_function 分类附着类型,数量协议仍独立。
|
||||
let native = vm
|
||||
.allocate(NativeFunction::new("native", |_| Ok(Value::Nil)))
|
||||
.unwrap();
|
||||
assert!(vm.is_instance(source, function_type).unwrap());
|
||||
assert!(vm.is_instance(native, function_type).unwrap());
|
||||
vm.attach_type(source, function_type).unwrap();
|
||||
// 普通类可调用但不是 Function;类方法表持有函数边。
|
||||
let class = vm
|
||||
.new_class(
|
||||
"Data",
|
||||
@@ -223,15 +281,18 @@ fn configured_function_identity_covers_source_native_and_bound_methods() {
|
||||
[("read".into(), source)].into(),
|
||||
)
|
||||
.unwrap();
|
||||
// call 构造的实例结果拥有一份根,后续清理必须显式 release。
|
||||
let Value::Object(instance) = vm.call(class, &[]).unwrap() else {
|
||||
panic!("instance required");
|
||||
};
|
||||
// 绑定结果持有函数与 receiver,且本身应自动取得 Function 类型。
|
||||
let Value::Object(bound) = vm.bind_attr(instance, "read").unwrap() else {
|
||||
panic!("method required");
|
||||
};
|
||||
assert!(vm.is_instance(bound, function_type).unwrap());
|
||||
assert!(!vm.is_instance(class, function_type).unwrap());
|
||||
assert!(vm.set_function_type(class).is_err());
|
||||
// 零参数代码却附一项参数注解,源码签名构造阶段即应拒绝。
|
||||
let invalid = endor_core::EndorCode::from_instructions(
|
||||
&[
|
||||
endor_core::Instruction::new(endor_core::Opcode::LoadConst, 0),
|
||||
@@ -247,6 +308,7 @@ fn configured_function_identity_covers_source_native_and_bound_methods() {
|
||||
method_owner: None,
|
||||
});
|
||||
assert!(invalid.is_err());
|
||||
// 归还每份测试拥有根,内部类型/基类/方法边不手动重复释放。
|
||||
for object in [
|
||||
source,
|
||||
native,
|
||||
@@ -259,27 +321,35 @@ fn configured_function_identity_covers_source_native_and_bound_methods() {
|
||||
] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 清除无外部根的类型/元类型环;仍由实例可达的定义不得回收。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 源码返回类型不符时检查结果和已解析类型根的计数恢复,无隐藏执行根泄漏。
|
||||
fn failed_nominal_return_releases_value_and_captured_type_roots() {
|
||||
use endor_core::{
|
||||
CodeLayout, EndorCode, EndorCodeObject, Instruction, Opcode, SourceSignature,
|
||||
};
|
||||
// 各测试建立独立堆,不把其他场景的类型身份带入。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
vm.set_source_type_checks(true).unwrap();
|
||||
// 显式自举 Object/Type 两个拥有根,新 VM 本身不预建语言类型。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 预期返回类型通过全局字典解析,执行时另保留返回定义根。
|
||||
let expected = vm
|
||||
.new_type("Expected", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// 创建与 Expected 同父但身份不同的类型,返回其实例应触发名义返回约束失败。
|
||||
let other = vm
|
||||
.new_type("Other", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// 错误返回对象先有测试根,代码常量表还会独立持有它。
|
||||
let Value::Object(value) = vm.call(other, &[]).unwrap() else {
|
||||
panic!("instance required");
|
||||
};
|
||||
// 函数全局字典用于注解解析和环境持有,函数内部边不依赖测试根。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
vm.dict_set(
|
||||
globals,
|
||||
@@ -287,6 +357,7 @@ fn failed_nominal_return_releases_value_and_captured_type_roots() {
|
||||
Value::Object(expected),
|
||||
)
|
||||
.unwrap();
|
||||
// 零参数代码返回 Other 实例常量,故意违背下面声明的 Expected 返回类型。
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadConst, 0),
|
||||
@@ -302,11 +373,15 @@ fn failed_nominal_return_releases_value_and_captured_type_roots() {
|
||||
method_owner: None,
|
||||
})
|
||||
.unwrap();
|
||||
// 分配代码载荷使常量引用(如有)成为托管边。
|
||||
let code = vm.allocate(EndorCodeObject::new(code)).unwrap();
|
||||
// 函数构造校验布局并持有代码/全局/默认值引用,返回一份根。
|
||||
let function = vm
|
||||
.new_function("bad return", code, globals, vec![], vec![])
|
||||
.unwrap();
|
||||
// 记录调用前定义计数,返回失败后帧持有的类型根应全部归还。
|
||||
let before_type = vm.ref_count(expected).unwrap();
|
||||
// 记录错误结果计数,失败后不应遗留栈根或交付根。
|
||||
let before_value = vm.ref_count(value).unwrap();
|
||||
assert!(matches!(
|
||||
vm.call(function, &[]),
|
||||
@@ -314,6 +389,7 @@ fn failed_nominal_return_releases_value_and_captured_type_roots() {
|
||||
));
|
||||
assert_eq!(vm.ref_count(expected).unwrap(), before_type);
|
||||
assert_eq!(vm.ref_count(value).unwrap(), before_value);
|
||||
// 归还每份测试拥有根,内部类型/基类/方法边不手动重复释放。
|
||||
for object in [
|
||||
function,
|
||||
code,
|
||||
@@ -326,33 +402,43 @@ fn failed_nominal_return_releases_value_and_captured_type_roots() {
|
||||
] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 清除无外部根的类型/元类型环;仍由实例可达的定义不得回收。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// 借用状态身份的测试分派器,包装载荷通过 captures 显式 trace 保活它。
|
||||
struct CountedNative(endor_core::ObjectRef);
|
||||
impl endor_core::NativeCallable for CountedNative {
|
||||
/// CountedNative 发布含 self 的原生位置参数区间,供 VM 在副作用前检查。
|
||||
fn arity(&self) -> Option<endor_core::CallArity> {
|
||||
Some(endor_core::CallArity::range(1, 2))
|
||||
}
|
||||
/// 数量检查通过后才设置 called 标志,返回直接 Nil,用于观测是否进入回调。
|
||||
fn invoke(
|
||||
&self,
|
||||
cx: &mut endor_core::NativeContext<'_>,
|
||||
_: &[Value],
|
||||
) -> Result<endor_core::NativeResult, VmError> {
|
||||
// 回调唯一副作用,错误数量调用后的 called=false 可证明回调未进入。
|
||||
cx.set_attr(self.0, "called", Value::Bool(true))?;
|
||||
Ok(endor_core::NativeResult::Return(Value::Nil))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证数量错误先于副作用、绑定参数扣减、能力更新拒绝未知/错误数量及畸形元数据禁止执行。
|
||||
fn native_arity_checks_precede_effects_and_traits_require_published_signatures() {
|
||||
use endor_core::{CallArity, ContextNativeFunction, MethodRequirement, NativeFunction};
|
||||
// 各测试建立独立堆,不把其他场景的类型身份带入。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 显式自举 Object/Type 两个拥有根,新 VM 本身不预建语言类型。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 副作用标志载体被 CountedNative 的显式 captures 边保活。
|
||||
let state = vm.new_object().unwrap();
|
||||
vm.set_attr(state, "called", Value::Bool(false)).unwrap();
|
||||
// 本场景未配置 Function 名义类型;包装器发布的数量区间独立用于预检及能力匹配。
|
||||
let native = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"counted",
|
||||
@@ -361,11 +447,13 @@ fn native_arity_checks_precede_effects_and_traits_require_published_signatures()
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(vm.call_arity(native).unwrap(), Some(CallArity::range(1, 2)));
|
||||
// 零实参违反原生函数声明的 arity,必须在回调修改 called 状态之前拒绝。
|
||||
assert!(matches!(
|
||||
vm.call(native, &[]),
|
||||
Err(VmError::ArgumentCount { .. })
|
||||
));
|
||||
assert_eq!(vm.get_attr(state, "called").unwrap(), Value::Bool(false));
|
||||
// 普通类可调用但不是 Function;类方法表持有函数边。
|
||||
let class = vm
|
||||
.new_class(
|
||||
"Data",
|
||||
@@ -374,6 +462,7 @@ fn native_arity_checks_precede_effects_and_traits_require_published_signatures()
|
||||
[("run".into(), native)].into(),
|
||||
)
|
||||
.unwrap();
|
||||
// 最小能力声明只要求方法及显式参数数,不推断结构等价。
|
||||
let capability = vm
|
||||
.new_trait(
|
||||
"Runnable",
|
||||
@@ -384,20 +473,24 @@ fn native_arity_checks_precede_effects_and_traits_require_published_signatures()
|
||||
)
|
||||
.unwrap();
|
||||
vm.implement_trait(class, capability).unwrap();
|
||||
// call 构造的实例结果拥有一份根,后续清理必须显式 release。
|
||||
let Value::Object(instance) = vm.call(class, &[]).unwrap() else {
|
||||
panic!("instance")
|
||||
};
|
||||
// 绑定结果持有 native 和 instance;这里只核对扣除 self 后的数量区间,不要求 Function 名义类型。
|
||||
let Value::Object(bound) = vm.bind_attr(instance, "run").unwrap() else {
|
||||
panic!("bound")
|
||||
};
|
||||
assert_eq!(vm.call_arity(bound).unwrap(), Some(CallArity::range(0, 1)));
|
||||
assert_eq!(vm.call(bound, &[]).unwrap(), Value::Nil);
|
||||
assert_eq!(vm.get_attr(state, "called").unwrap(), Value::Bool(true));
|
||||
// 未发布 arity 的旧原生函数可独立调用,但不能证明满足已声明最小能力。
|
||||
let unknown = vm
|
||||
.allocate(NativeFunction::new("unknown", |_| Ok(Value::Nil)))
|
||||
.unwrap();
|
||||
assert_eq!(vm.call_arity(unknown).unwrap(), None);
|
||||
assert!(vm.define_method(class, "run", unknown).is_err());
|
||||
// 固定三参数函数与要求 self-only 的 run 不兼容,替换不能提交。
|
||||
let wrong = vm
|
||||
.allocate(NativeFunction::new("wrong", |_| Ok(Value::Nil)).with_arity(CallArity::exact(3)))
|
||||
.unwrap();
|
||||
@@ -406,6 +499,7 @@ fn native_arity_checks_precede_effects_and_traits_require_published_signatures()
|
||||
vm.object_store().lookup_method(class, "run").unwrap(),
|
||||
Some(native)
|
||||
);
|
||||
// 开放上界的原生区间,绑定后只扣下界 self,最大值仍须保持 usize::MAX。
|
||||
let variadic = vm
|
||||
.allocate(
|
||||
NativeFunction::new("variadic", |_| Ok(Value::Nil))
|
||||
@@ -413,6 +507,7 @@ fn native_arity_checks_precede_effects_and_traits_require_published_signatures()
|
||||
)
|
||||
.unwrap();
|
||||
vm.define_method(class, "many", variadic).unwrap();
|
||||
// 绑定开放区间函数,测试可见数量而不实际执行任意多参数。
|
||||
let Value::Object(variadic_bound) = vm.bind_attr(instance, "many").unwrap() else {
|
||||
panic!("bound")
|
||||
};
|
||||
@@ -422,6 +517,7 @@ fn native_arity_checks_precede_effects_and_traits_require_published_signatures()
|
||||
);
|
||||
vm.release(variadic_bound).unwrap();
|
||||
vm.release(variadic).unwrap();
|
||||
// 颠倒区间并放置 panic 回调,数量元数据错误必须在调用回调前返回。
|
||||
let malformed = vm
|
||||
.allocate(
|
||||
NativeFunction::new("malformed", |_| panic!("invalid metadata must not execute"))
|
||||
@@ -430,6 +526,7 @@ fn native_arity_checks_precede_effects_and_traits_require_published_signatures()
|
||||
.unwrap();
|
||||
assert!(vm.call_arity(malformed).is_err());
|
||||
assert!(vm.call(malformed, &[]).is_err());
|
||||
// 归还每份测试拥有根,内部类型/基类/方法边不手动重复释放。
|
||||
for object in [
|
||||
roots.object,
|
||||
roots.type_type,
|
||||
@@ -445,15 +542,20 @@ fn native_arity_checks_precede_effects_and_traits_require_published_signatures()
|
||||
] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 清除无外部根的类型/元类型环;仍由实例可达的定义不得回收。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证源码默认值缩小必填区间,同时没有数量元数据的旧原生调用仍保持可调用。
|
||||
fn unified_arity_preserves_source_defaults_and_legacy_native_calls() {
|
||||
use endor_core::{CallArity, CodeLayout, EndorCode, Instruction, NativeFunction, Opcode};
|
||||
// 各测试建立独立堆,不把其他场景的类型身份带入。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 函数全局字典用于注解解析和环境持有,函数内部边不依赖测试根。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 测试用已验证指令,返回常量 Nil;数量由参数布局决定。
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadConst, 0),
|
||||
@@ -467,7 +569,9 @@ fn unified_arity_preserves_source_defaults_and_legacy_native_calls() {
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
// 统一分配代码拥有根,后续函数也持代码内部边。
|
||||
let code = vm.new_code(code).unwrap();
|
||||
// 函数构造校验布局并持有代码/全局/默认值引用,返回一份根。
|
||||
let function = vm
|
||||
.new_function("optional", code, globals, vec![Value::Nil], vec![])
|
||||
.unwrap();
|
||||
@@ -476,14 +580,17 @@ fn unified_arity_preserves_source_defaults_and_legacy_native_calls() {
|
||||
Some(CallArity::range(0, 1))
|
||||
);
|
||||
assert_eq!(vm.call(function, &[]).unwrap(), Value::Nil);
|
||||
// 无数量元数据仍是兼容原生函数,None 不应被误解为不支持调用。
|
||||
let legacy = vm
|
||||
.allocate(NativeFunction::new("legacy", |_| Ok(Value::Nil)))
|
||||
.unwrap();
|
||||
assert_eq!(vm.call_arity(legacy).unwrap(), None);
|
||||
assert_eq!(vm.call(legacy, &[]).unwrap(), Value::Nil);
|
||||
// 归还每份测试拥有根,内部类型/基类/方法边不手动重复释放。
|
||||
for object in [globals, code, function, legacy] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 清除无外部根的类型/元类型环;仍由实例可达的定义不得回收。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
@@ -1,245 +1,546 @@
|
||||
// Generated by scripts/generate_ast.py from Endor.asdl. Do not edit.
|
||||
|
||||
// 与 Rust wire 共同核对的 ASDL 原文协议身份;生成源码注释不改变此值。
|
||||
export const AST_SCHEMA_ID = "urn:endor:ast:sha256:21b0349ac295d9bc8cedcad8f07d8e2d45cf220e19e7c2fad10269555fcfd71a" as const;
|
||||
// 原 UTF-8 半开字节范围;number 类型本身不验证安全整数、顺序或原文边界。
|
||||
export interface Span { start: number; end: number; }
|
||||
// 宿主/编辑器传输的版本化语法文档,wire 校验后供 compiler 使用。
|
||||
// schema: 协议身份,wire 按 AST_SCHEMA_ID 核对;不是源码内容哈希。
|
||||
// root: 拥有的语法根,文档校验及编译从这里递归。
|
||||
export type AstDocument = { schema: typeof AST_SCHEMA_ID; root: AstRoot; };
|
||||
// parse_ast 按模式构造的模块、表达式或交互根,compiler 据此选择生成入口。
|
||||
// Module: 模块根,包含函数/类声明。
|
||||
// Module.body: 当前模式选中的模块、表达式或交互根载荷,compiler 按对应构造器生成入口代码。
|
||||
// Expression: 表达式根,编译后直接返回求值结果。
|
||||
// Expression.body: 当前模式选中的模块、表达式或交互根载荷,compiler 按对应构造器生成入口代码。
|
||||
// Interactive: 交互根,包含一次完整提交。
|
||||
// Interactive.body: 当前模式选中的模块、表达式或交互根载荷,compiler 按对应构造器生成入口代码。
|
||||
export type AstRoot = { kind: "Module"; data: Program; } | { kind: "Expression"; data: Expr; } | { kind: "Interactive"; data: InteractiveInput; };
|
||||
// parser 复制的名称文本与位置,用于声明、类型注解或属性名。
|
||||
// text: 拥有名称文本,不依赖输入缓冲存活。
|
||||
// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
export type Name = { text: string; span: Span; };
|
||||
// 模块声明序列,compiler 先注册全部顶层名称再分析各函数体。
|
||||
// declarations: 按源码顺序的声明;源码模块解析要求非空,文档结构本身不限制数量。
|
||||
// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
export type Program = { declarations: Array<Decl>; span: Span; };
|
||||
// 顶层/交互声明的种类及完整范围,compiler 按函数或类分派。
|
||||
// kind: 函数/类声明载荷,JSON 和类型采用 kind/data 标签。
|
||||
// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
export type Decl = { kind: DeclKind; span: Span; };
|
||||
// 函数与类声明的判别载荷,外层 Decl 另外保存完整跨度。
|
||||
// Function: 显式参数/返回注解与函数体,compiler 生成独立模板及源码签名;解析本身不调用函数。
|
||||
// Function.definition: 函数或类的完整声明,包含其名称、成员/参数及原文范围。
|
||||
// Class: 类名、可选基类、字段和方法,compiler 检查成员后生成构类指令。
|
||||
// Class.definition: 函数或类的完整声明,包含其名称、成员/参数及原文范围。
|
||||
export type DeclKind = { kind: "Function"; data: Function; } | { kind: "Class"; data: Class; };
|
||||
// 类名、可选基类、字段和方法,compiler 检查成员后生成构类指令。
|
||||
// name: 类声明名与精确位置。
|
||||
// base: 显式基类名,可省略;当前 compiler 对省略项使用 Object。
|
||||
// fields: 按出现顺序的字段名称/类型注解,不含方法。
|
||||
// methods: 按出现顺序的方法;显式 self 约束由 compiler 检查。
|
||||
// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
export type Class = { name: Name; base?: Name | null; fields: Array<Parameter>; methods: Array<Function>; span: Span; };
|
||||
// 显式参数/返回注解与函数体,compiler 生成独立模板及源码签名;解析本身不调用函数。
|
||||
// name: 函数/方法名,代码模板沿用为诊断名称。
|
||||
// parameters: 位置参数列表,compiler 依次分配前部局部槽。
|
||||
// return_type: 返回类型名,生成源码签名后由运行时解析。
|
||||
// body: 独立函数作用域的语句体,当前源码不支持嵌套声明闭包。
|
||||
// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
export type Function = { name: Name; parameters: Array<Parameter>; return_type: Name; body: Block; span: Span; };
|
||||
// 函数参数或类字段共用的名称/类型注解,实际类型身份由运行时环境解析。
|
||||
// name: 参数/字段名,用于布局和重复检查。
|
||||
// type_name: 声明类型名,前端只保留文本与位置。
|
||||
// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
export type Parameter = { name: Name; type_name: Name; span: Span; };
|
||||
// 顺序语句块,compiler 检查确定初始化并按语句生成指令。
|
||||
// statements: 有序语句列表,不引入独立块级局部作用域。
|
||||
// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
export type Block = { statements: Array<Stmt>; span: Span; };
|
||||
// 语句种类及原文范围,compiler 处理副作用、控制流或返回。
|
||||
// kind: 具体语句载荷,外层另存整句范围。
|
||||
// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
export type Stmt = { kind: StmtKind; span: Span; };
|
||||
// 语句判别载荷,parser 选择分支,compiler 按绑定、赋值、控制流等规则处理。
|
||||
// Binding: let 新绑定,compiler 检查重名并分析右值后才标记目标已初始化。
|
||||
// Binding.binding: let 新绑定,compiler 先分析/求右值,再登记目标初始化。
|
||||
// Assignment: 普通名称赋值,compiler 按作用域写局部槽或全局环境。
|
||||
// Assignment.assignment: 普通名称或成员赋值的完整节点,compiler 据构造器选择槽写入或属性写入。
|
||||
// MemberAssignment: 成员写入的对象、属性名与右值,compiler 生成 StoreAttr。
|
||||
// MemberAssignment.assignment: 普通名称或成员赋值的完整节点,compiler 据构造器选择槽写入或属性写入。
|
||||
// If: 条件、then 和可选 else,compiler 合并继续路径的初始化集合并生成跳转。
|
||||
// If.statement: 条件、循环或返回节点,compiler 按构造器处理路径、跳转或帧出口。
|
||||
// While: 循环条件与体,compiler 生成回边并处理循环可能零次执行。
|
||||
// While.statement: 条件、循环或返回节点,compiler 按构造器处理路径、跳转或帧出口。
|
||||
// Return: 函数返回,compiler 对缺省值补 Nil;当前源码语法要求 return 后有表达式。
|
||||
// Return.statement: 条件、循环或返回节点,compiler 按构造器处理路径、跳转或帧出口。
|
||||
// Expression: 表达式载荷及完整范围,compiler 生成留下一个结果的求值序列。
|
||||
// Expression.expression: 语句位置的表达式;函数块丢弃结果,交互根可把它作为返回值。
|
||||
// Declaration: 顶层/交互声明的种类及完整范围,compiler 按函数或类分派。
|
||||
// Declaration.declaration: 交互根中的函数/类声明,compiler 预登记名称;嵌套声明当前被拒绝。
|
||||
export type StmtKind = { kind: "Binding"; data: Binding; } | { kind: "Assignment"; data: Assignment; } | { kind: "MemberAssignment"; data: MemberAssignment; } | { kind: "If"; data: IfStatement; } | { kind: "While"; data: WhileStatement; } | { kind: "Return"; data: ReturnStatement; } | { kind: "Expression"; data: Expr; } | { kind: "Declaration"; data: Decl; };
|
||||
// let 新绑定,compiler 检查重名并分析右值后才标记目标已初始化。
|
||||
// name: let 目标名,重复声明由语义分析拒绝。
|
||||
// value: 绑定右值,本局部不能在初始化前读取自身。
|
||||
// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
export type Binding = { name: Name; value: Expr; span: Span; };
|
||||
// 普通名称赋值,compiler 按作用域写局部槽或全局环境。
|
||||
// name: 普通赋值目标,函数内首次赋值可预分配局部槽。
|
||||
// value: 先求右值再更新目标。
|
||||
// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
export type Assignment = { name: Name; value: Expr; span: Span; };
|
||||
// 成员写入的对象、属性名与右值,compiler 生成 StoreAttr。
|
||||
// object: 属性写入接收对象表达式,前端不求值。
|
||||
// name: 待写属性名,compiler 加入名字表。
|
||||
// value: 待写右值,当前 compiler 先于接收对象生成求值以匹配 VM 栈布局。
|
||||
// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
export type MemberAssignment = { object: Expr; name: Name; value: Expr; span: Span; };
|
||||
// 条件、then 和可选 else,compiler 合并继续路径的初始化集合并生成跳转。
|
||||
// condition: 条件表达式,运行时按真值协议选择分支。
|
||||
// body: 条件为真的 then 块。
|
||||
// otherwise: 可选 else 块,缺省时存在直接继续路径。
|
||||
// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
export type IfStatement = { condition: Expr; body: Block; otherwise?: Block | null; span: Span; };
|
||||
// 循环条件与体,compiler 生成回边并处理循环可能零次执行。
|
||||
// condition: 每轮重新求值的条件,假值退出。
|
||||
// body: 循环体,可能一次也不执行。
|
||||
// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
export type WhileStatement = { condition: Expr; body: Block; span: Span; };
|
||||
// 函数返回,compiler 对缺省值补 Nil;当前源码语法要求 return 后有表达式。
|
||||
// value: 可选返回表达式,手工 AST 缺省时由 compiler 补 Nil。
|
||||
// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
export type ReturnStatement = { value?: Expr | null; span: Span; };
|
||||
// 单次交互提交容器,parser 产生一条语句,compiler 也检查数量,不保存宿主会话环境。
|
||||
// statements: 当前 parse/compile 约定恰好一个完整提交的语句。
|
||||
// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
export type InteractiveInput = { statements: Array<Stmt>; span: Span; };
|
||||
// 表达式载荷及完整范围,compiler 生成留下一个结果的求值序列。
|
||||
// kind: 求值形状,不缓存名称解析或运行时类型。
|
||||
// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
export type Expr = { kind: ExprKind; span: Span; };
|
||||
// 名称、字面量、等待、运算和调用等表达式载荷,范围由外层 Expr 提供。
|
||||
// Name: 变量引用文本,compiler 决定局部槽或全局索引。
|
||||
// Name.text: 名称引用原文,由 compiler 名称分析解析到局部槽或全局名字表。
|
||||
// Number: 原始十进制拼写,保留全部位数、前导零及尾零,不转浮点。
|
||||
// Number.spelling: 原始数字拼写,compiler 构造精确数值;不把文本先转为二进制浮点。
|
||||
// String: 已解码受支持转义的拥有文本,原引号范围在外层 Expr。
|
||||
// String.value: 字符串解码后的拥有文本,原引号和转义仍可通过外层范围读取。
|
||||
// Symbol: 点开头的符号名,前端可表示,当前 compiler 拒绝生成运行时值。
|
||||
// Symbol.name: 成员或符号名,不是已解析的运行时属性句柄。
|
||||
// Await: 等待子表达式,VM 执行上下文决定是否允许停放。
|
||||
// Await.operand: 一元子表达式,Rust 的递归回边间接存储。
|
||||
// Unary: 一元取负,可在纯数值链上合并符号。
|
||||
// Unary.operation: 运算种类,决定算术、比较或取负指令。
|
||||
// Unary.operand: 一元子表达式,Rust 的递归回边间接存储。
|
||||
// Binary: 左右操作数及算术/比较种类,保留解析优先级树。
|
||||
// Binary.operation: 运算种类,决定算术、比较或取负指令。
|
||||
// Binary.left: 二元左操作数,当前 compiler 先于右侧求值。
|
||||
// Binary.right: 二元右操作数,与左侧一起被运行时运算消费。
|
||||
// Member: 接收对象及属性名的读取。
|
||||
// Member.object: 成员读取接收对象。
|
||||
// Member.name: 成员或符号名,不是已解析的运行时属性句柄。
|
||||
// Call: 被调用表达式与位置实参,由 VM 实际求值。
|
||||
// Call.callee: 调用目标,先于实参求值。
|
||||
// Call.arguments: 有序位置实参,AST 不隐式添加接收者。
|
||||
export type ExprKind = { kind: "Name"; data: string; } | { kind: "Number"; data: string; } | { kind: "String"; data: string; } | { kind: "Symbol"; data: Name; } | { kind: "Await"; data: Expr; } | { kind: "Unary"; data: { operation: UnaryOp; operand: Expr; }; } | { kind: "Binary"; data: { operation: BinaryOp; left: Expr; right: Expr; }; } | { kind: "Member"; data: { object: Expr; name: Name; }; } | { kind: "Call"; data: { callee: Expr; arguments: Array<Expr>; }; };
|
||||
// 一元运算种类,compiler 合并纯数值负号或生成运行时取负。
|
||||
// Negate: 一元取负。
|
||||
export type UnaryOp = "Negate";
|
||||
// 二元算术/比较种类,parser 保留优先级树,compiler 映射为对应 VM 操作。
|
||||
// Add: 加法。
|
||||
// Subtract: 减法。
|
||||
// Multiply: 乘法。
|
||||
// Divide: 除法。
|
||||
// Equal: 相等比较。
|
||||
// NotEqual: 不等比较。
|
||||
// Less: 小于比较。
|
||||
// LessEqual: 小于等于比较。
|
||||
// Greater: 大于比较。
|
||||
// GreaterEqual: 大于等于比较。
|
||||
export type BinaryOp = "Add" | "Subtract" | "Multiply" | "Divide" | "Equal" | "NotEqual" | "Less" | "LessEqual" | "Greater" | "GreaterEqual";
|
||||
// 编辑器可选节点回调;walk<Type> 先回调,返回 false 只剪掉当前子树,其他值继续递归。
|
||||
export interface Visitor {
|
||||
// 跨度叶子回调没有子结构,其返回值不参与后续剪枝。
|
||||
visitSpan?(node: Span): boolean | void;
|
||||
// walkAstDocument 到达 AstDocument 时调用,false 跳过该节点的后续子字段。
|
||||
visitAstDocument?(node: AstDocument): boolean | void;
|
||||
// walkAstRoot 到达 AstRoot 时调用,false 跳过该节点的后续子字段。
|
||||
visitAstRoot?(node: AstRoot): boolean | void;
|
||||
// walkName 到达 Name 时调用,false 跳过该节点的后续子字段。
|
||||
visitName?(node: Name): boolean | void;
|
||||
// walkProgram 到达 Program 时调用,false 跳过该节点的后续子字段。
|
||||
visitProgram?(node: Program): boolean | void;
|
||||
// walkDecl 到达 Decl 时调用,false 跳过该节点的后续子字段。
|
||||
visitDecl?(node: Decl): boolean | void;
|
||||
// walkDeclKind 到达 DeclKind 时调用,false 跳过该节点的后续子字段。
|
||||
visitDeclKind?(node: DeclKind): boolean | void;
|
||||
// walkClass 到达 Class 时调用,false 跳过该节点的后续子字段。
|
||||
visitClass?(node: Class): boolean | void;
|
||||
// walkFunction 到达 Function 时调用,false 跳过该节点的后续子字段。
|
||||
visitFunction?(node: Function): boolean | void;
|
||||
// walkParameter 到达 Parameter 时调用,false 跳过该节点的后续子字段。
|
||||
visitParameter?(node: Parameter): boolean | void;
|
||||
// walkBlock 到达 Block 时调用,false 跳过该节点的后续子字段。
|
||||
visitBlock?(node: Block): boolean | void;
|
||||
// walkStmt 到达 Stmt 时调用,false 跳过该节点的后续子字段。
|
||||
visitStmt?(node: Stmt): boolean | void;
|
||||
// walkStmtKind 到达 StmtKind 时调用,false 跳过该节点的后续子字段。
|
||||
visitStmtKind?(node: StmtKind): boolean | void;
|
||||
// walkBinding 到达 Binding 时调用,false 跳过该节点的后续子字段。
|
||||
visitBinding?(node: Binding): boolean | void;
|
||||
// walkAssignment 到达 Assignment 时调用,false 跳过该节点的后续子字段。
|
||||
visitAssignment?(node: Assignment): boolean | void;
|
||||
// walkMemberAssignment 到达 MemberAssignment 时调用,false 跳过该节点的后续子字段。
|
||||
visitMemberAssignment?(node: MemberAssignment): boolean | void;
|
||||
// walkIfStatement 到达 IfStatement 时调用,false 跳过该节点的后续子字段。
|
||||
visitIfStatement?(node: IfStatement): boolean | void;
|
||||
// walkWhileStatement 到达 WhileStatement 时调用,false 跳过该节点的后续子字段。
|
||||
visitWhileStatement?(node: WhileStatement): boolean | void;
|
||||
// walkReturnStatement 到达 ReturnStatement 时调用,false 跳过该节点的后续子字段。
|
||||
visitReturnStatement?(node: ReturnStatement): boolean | void;
|
||||
// walkInteractiveInput 到达 InteractiveInput 时调用,false 跳过该节点的后续子字段。
|
||||
visitInteractiveInput?(node: InteractiveInput): boolean | void;
|
||||
// walkExpr 到达 Expr 时调用,false 跳过该节点的后续子字段。
|
||||
visitExpr?(node: Expr): boolean | void;
|
||||
// walkExprKind 到达 ExprKind 时调用,false 跳过该节点的后续子字段。
|
||||
visitExprKind?(node: ExprKind): boolean | void;
|
||||
// walkUnaryOp 到达 UnaryOp 时调用,false 跳过该节点的后续子字段。
|
||||
visitUnaryOp?(node: UnaryOp): boolean | void;
|
||||
// walkBinaryOp 到达 BinaryOp 时调用,false 跳过该节点的后续子字段。
|
||||
visitBinaryOp?(node: BinaryOp): boolean | void;
|
||||
}
|
||||
// 上层 walker/宿主调用的跨度叶子入口,仅通知 visitSpan,不验证输入或修改节点。
|
||||
export function walkSpan(node: Span, visitor: Visitor): void { visitor.visitSpan?.(node); }
|
||||
// 上层 walker 或宿主调用,先通知 AstDocument 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkAstDocument(node: AstDocument, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitAstDocument?.(node) === false) return;
|
||||
// 递归访问 node.root 的 AstRoot 子结构或跨度。
|
||||
walkAstRoot(node.root, visitor);
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 AstRoot 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkAstRoot(node: AstRoot, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitAstRoot?.(node) === false) return;
|
||||
// 判别 kind 只选择当前载荷,普通文本/数字拼写没有进一步语法子结构。
|
||||
switch (node.kind) {
|
||||
// 模块根,包含函数/类声明。
|
||||
case "Module":
|
||||
// 递归访问 node.data 的 Program 子结构或跨度。
|
||||
walkProgram(node.data, visitor);
|
||||
break;
|
||||
// 表达式根,编译后直接返回求值结果。
|
||||
case "Expression":
|
||||
// 递归访问 node.data 的 Expr 子结构或跨度。
|
||||
walkExpr(node.data, visitor);
|
||||
break;
|
||||
// 交互根,包含一次完整提交。
|
||||
case "Interactive":
|
||||
// 递归访问 node.data 的 InteractiveInput 子结构或跨度。
|
||||
walkInteractiveInput(node.data, visitor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 Name 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkName(node: Name, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitName?.(node) === false) return;
|
||||
// 递归访问 node.span 的 span 子结构或跨度。
|
||||
walkSpan(node.span, visitor);
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 Program 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkProgram(node: Program, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitProgram?.(node) === false) return;
|
||||
// 按声明顺序访问 node.declarations 列表中的每个 Decl 子节点。
|
||||
for (const value of node.declarations) walkDecl(value, visitor);
|
||||
// 递归访问 node.span 的 span 子结构或跨度。
|
||||
walkSpan(node.span, visitor);
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 Decl 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkDecl(node: Decl, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitDecl?.(node) === false) return;
|
||||
// 递归访问 node.kind 的 DeclKind 子结构或跨度。
|
||||
walkDeclKind(node.kind, visitor);
|
||||
// 递归访问 node.span 的 span 子结构或跨度。
|
||||
walkSpan(node.span, visitor);
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 DeclKind 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkDeclKind(node: DeclKind, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitDeclKind?.(node) === false) return;
|
||||
// 判别 kind 只选择当前载荷,普通文本/数字拼写没有进一步语法子结构。
|
||||
switch (node.kind) {
|
||||
// 显式参数/返回注解与函数体,compiler 生成独立模板及源码签名;解析本身不调用函数。
|
||||
case "Function":
|
||||
// 递归访问 node.data 的 Function 子结构或跨度。
|
||||
walkFunction(node.data, visitor);
|
||||
break;
|
||||
// 类名、可选基类、字段和方法,compiler 检查成员后生成构类指令。
|
||||
case "Class":
|
||||
// 递归访问 node.data 的 Class 子结构或跨度。
|
||||
walkClass(node.data, visitor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 Class 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkClass(node: Class, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitClass?.(node) === false) return;
|
||||
// 递归访问 node.name 的 Name 子结构或跨度。
|
||||
walkName(node.name, visitor);
|
||||
// node.base 可缺省或为 null,只有存在时才访问该子节点。
|
||||
if (node.base != null) walkName(node.base, visitor);
|
||||
// 按声明顺序访问 node.fields 列表中的每个 Parameter 子节点。
|
||||
for (const value of node.fields) walkParameter(value, visitor);
|
||||
// 按声明顺序访问 node.methods 列表中的每个 Function 子节点。
|
||||
for (const value of node.methods) walkFunction(value, visitor);
|
||||
// 递归访问 node.span 的 span 子结构或跨度。
|
||||
walkSpan(node.span, visitor);
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 Function 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkFunction(node: Function, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitFunction?.(node) === false) return;
|
||||
// 递归访问 node.name 的 Name 子结构或跨度。
|
||||
walkName(node.name, visitor);
|
||||
// 按声明顺序访问 node.parameters 列表中的每个 Parameter 子节点。
|
||||
for (const value of node.parameters) walkParameter(value, visitor);
|
||||
// 递归访问 node.return_type 的 Name 子结构或跨度。
|
||||
walkName(node.return_type, visitor);
|
||||
// 递归访问 node.body 的 Block 子结构或跨度。
|
||||
walkBlock(node.body, visitor);
|
||||
// 递归访问 node.span 的 span 子结构或跨度。
|
||||
walkSpan(node.span, visitor);
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 Parameter 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkParameter(node: Parameter, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitParameter?.(node) === false) return;
|
||||
// 递归访问 node.name 的 Name 子结构或跨度。
|
||||
walkName(node.name, visitor);
|
||||
// 递归访问 node.type_name 的 Name 子结构或跨度。
|
||||
walkName(node.type_name, visitor);
|
||||
// 递归访问 node.span 的 span 子结构或跨度。
|
||||
walkSpan(node.span, visitor);
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 Block 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkBlock(node: Block, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitBlock?.(node) === false) return;
|
||||
// 按声明顺序访问 node.statements 列表中的每个 Stmt 子节点。
|
||||
for (const value of node.statements) walkStmt(value, visitor);
|
||||
// 递归访问 node.span 的 span 子结构或跨度。
|
||||
walkSpan(node.span, visitor);
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 Stmt 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkStmt(node: Stmt, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitStmt?.(node) === false) return;
|
||||
// 递归访问 node.kind 的 StmtKind 子结构或跨度。
|
||||
walkStmtKind(node.kind, visitor);
|
||||
// 递归访问 node.span 的 span 子结构或跨度。
|
||||
walkSpan(node.span, visitor);
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 StmtKind 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkStmtKind(node: StmtKind, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitStmtKind?.(node) === false) return;
|
||||
// 判别 kind 只选择当前载荷,普通文本/数字拼写没有进一步语法子结构。
|
||||
switch (node.kind) {
|
||||
// let 新绑定,compiler 检查重名并分析右值后才标记目标已初始化。
|
||||
case "Binding":
|
||||
// 递归访问 node.data 的 Binding 子结构或跨度。
|
||||
walkBinding(node.data, visitor);
|
||||
break;
|
||||
// 普通名称赋值,compiler 按作用域写局部槽或全局环境。
|
||||
case "Assignment":
|
||||
// 递归访问 node.data 的 Assignment 子结构或跨度。
|
||||
walkAssignment(node.data, visitor);
|
||||
break;
|
||||
// 成员写入的对象、属性名与右值,compiler 生成 StoreAttr。
|
||||
case "MemberAssignment":
|
||||
// 递归访问 node.data 的 MemberAssignment 子结构或跨度。
|
||||
walkMemberAssignment(node.data, visitor);
|
||||
break;
|
||||
// 条件、then 和可选 else,compiler 合并继续路径的初始化集合并生成跳转。
|
||||
case "If":
|
||||
// 递归访问 node.data 的 IfStatement 子结构或跨度。
|
||||
walkIfStatement(node.data, visitor);
|
||||
break;
|
||||
// 循环条件与体,compiler 生成回边并处理循环可能零次执行。
|
||||
case "While":
|
||||
// 递归访问 node.data 的 WhileStatement 子结构或跨度。
|
||||
walkWhileStatement(node.data, visitor);
|
||||
break;
|
||||
// 函数返回,compiler 对缺省值补 Nil;当前源码语法要求 return 后有表达式。
|
||||
case "Return":
|
||||
// 递归访问 node.data 的 ReturnStatement 子结构或跨度。
|
||||
walkReturnStatement(node.data, visitor);
|
||||
break;
|
||||
// 表达式载荷及完整范围,compiler 生成留下一个结果的求值序列。
|
||||
case "Expression":
|
||||
// 递归访问 node.data 的 Expr 子结构或跨度。
|
||||
walkExpr(node.data, visitor);
|
||||
break;
|
||||
// 顶层/交互声明的种类及完整范围,compiler 按函数或类分派。
|
||||
case "Declaration":
|
||||
// 递归访问 node.data 的 Decl 子结构或跨度。
|
||||
walkDecl(node.data, visitor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 Binding 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkBinding(node: Binding, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitBinding?.(node) === false) return;
|
||||
// 递归访问 node.name 的 Name 子结构或跨度。
|
||||
walkName(node.name, visitor);
|
||||
// 递归访问 node.value 的 Expr 子结构或跨度。
|
||||
walkExpr(node.value, visitor);
|
||||
// 递归访问 node.span 的 span 子结构或跨度。
|
||||
walkSpan(node.span, visitor);
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 Assignment 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkAssignment(node: Assignment, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitAssignment?.(node) === false) return;
|
||||
// 递归访问 node.name 的 Name 子结构或跨度。
|
||||
walkName(node.name, visitor);
|
||||
// 递归访问 node.value 的 Expr 子结构或跨度。
|
||||
walkExpr(node.value, visitor);
|
||||
// 递归访问 node.span 的 span 子结构或跨度。
|
||||
walkSpan(node.span, visitor);
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 MemberAssignment 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkMemberAssignment(node: MemberAssignment, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitMemberAssignment?.(node) === false) return;
|
||||
// 递归访问 node.object 的 Expr 子结构或跨度。
|
||||
walkExpr(node.object, visitor);
|
||||
// 递归访问 node.name 的 Name 子结构或跨度。
|
||||
walkName(node.name, visitor);
|
||||
// 递归访问 node.value 的 Expr 子结构或跨度。
|
||||
walkExpr(node.value, visitor);
|
||||
// 递归访问 node.span 的 span 子结构或跨度。
|
||||
walkSpan(node.span, visitor);
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 IfStatement 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkIfStatement(node: IfStatement, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitIfStatement?.(node) === false) return;
|
||||
// 递归访问 node.condition 的 Expr 子结构或跨度。
|
||||
walkExpr(node.condition, visitor);
|
||||
// 递归访问 node.body 的 Block 子结构或跨度。
|
||||
walkBlock(node.body, visitor);
|
||||
// node.otherwise 可缺省或为 null,只有存在时才访问该子节点。
|
||||
if (node.otherwise != null) walkBlock(node.otherwise, visitor);
|
||||
// 递归访问 node.span 的 span 子结构或跨度。
|
||||
walkSpan(node.span, visitor);
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 WhileStatement 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkWhileStatement(node: WhileStatement, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitWhileStatement?.(node) === false) return;
|
||||
// 递归访问 node.condition 的 Expr 子结构或跨度。
|
||||
walkExpr(node.condition, visitor);
|
||||
// 递归访问 node.body 的 Block 子结构或跨度。
|
||||
walkBlock(node.body, visitor);
|
||||
// 递归访问 node.span 的 span 子结构或跨度。
|
||||
walkSpan(node.span, visitor);
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 ReturnStatement 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkReturnStatement(node: ReturnStatement, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitReturnStatement?.(node) === false) return;
|
||||
// node.value 可缺省或为 null,只有存在时才访问该子节点。
|
||||
if (node.value != null) walkExpr(node.value, visitor);
|
||||
// 递归访问 node.span 的 span 子结构或跨度。
|
||||
walkSpan(node.span, visitor);
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 InteractiveInput 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkInteractiveInput(node: InteractiveInput, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitInteractiveInput?.(node) === false) return;
|
||||
// 按声明顺序访问 node.statements 列表中的每个 Stmt 子节点。
|
||||
for (const value of node.statements) walkStmt(value, visitor);
|
||||
// 递归访问 node.span 的 span 子结构或跨度。
|
||||
walkSpan(node.span, visitor);
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 Expr 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkExpr(node: Expr, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitExpr?.(node) === false) return;
|
||||
// 递归访问 node.kind 的 ExprKind 子结构或跨度。
|
||||
walkExprKind(node.kind, visitor);
|
||||
// 递归访问 node.span 的 span 子结构或跨度。
|
||||
walkSpan(node.span, visitor);
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 ExprKind 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkExprKind(node: ExprKind, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitExprKind?.(node) === false) return;
|
||||
// 判别 kind 只选择当前载荷,普通文本/数字拼写没有进一步语法子结构。
|
||||
switch (node.kind) {
|
||||
// 变量引用文本,compiler 决定局部槽或全局索引。
|
||||
case "Name":
|
||||
break;
|
||||
// 原始十进制拼写,保留全部位数、前导零及尾零,不转浮点。
|
||||
case "Number":
|
||||
break;
|
||||
// 已解码受支持转义的拥有文本,原引号范围在外层 Expr。
|
||||
case "String":
|
||||
break;
|
||||
// 点开头的符号名,前端可表示,当前 compiler 拒绝生成运行时值。
|
||||
case "Symbol":
|
||||
// 递归访问 node.data 的 Name 子结构或跨度。
|
||||
walkName(node.data, visitor);
|
||||
break;
|
||||
// 等待子表达式,VM 执行上下文决定是否允许停放。
|
||||
case "Await":
|
||||
// 递归访问 node.data 的 Expr 子结构或跨度。
|
||||
walkExpr(node.data, visitor);
|
||||
break;
|
||||
// 一元取负,可在纯数值链上合并符号。
|
||||
case "Unary":
|
||||
// 递归访问 node.data.operation 的 UnaryOp 子结构或跨度。
|
||||
walkUnaryOp(node.data.operation, visitor);
|
||||
// 递归访问 node.data.operand 的 Expr 子结构或跨度。
|
||||
walkExpr(node.data.operand, visitor);
|
||||
break;
|
||||
// 左右操作数及算术/比较种类,保留解析优先级树。
|
||||
case "Binary":
|
||||
// 递归访问 node.data.operation 的 BinaryOp 子结构或跨度。
|
||||
walkBinaryOp(node.data.operation, visitor);
|
||||
// 递归访问 node.data.left 的 Expr 子结构或跨度。
|
||||
walkExpr(node.data.left, visitor);
|
||||
// 递归访问 node.data.right 的 Expr 子结构或跨度。
|
||||
walkExpr(node.data.right, visitor);
|
||||
break;
|
||||
// 接收对象及属性名的读取。
|
||||
case "Member":
|
||||
// 递归访问 node.data.object 的 Expr 子结构或跨度。
|
||||
walkExpr(node.data.object, visitor);
|
||||
// 递归访问 node.data.name 的 Name 子结构或跨度。
|
||||
walkName(node.data.name, visitor);
|
||||
break;
|
||||
// 被调用表达式与位置实参,由 VM 实际求值。
|
||||
case "Call":
|
||||
// 递归访问 node.data.callee 的 Expr 子结构或跨度。
|
||||
walkExpr(node.data.callee, visitor);
|
||||
// 按声明顺序访问 node.data.arguments 列表中的每个 Expr 子节点。
|
||||
for (const value of node.data.arguments) walkExpr(value, visitor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 UnaryOp 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkUnaryOp(node: UnaryOp, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitUnaryOp?.(node) === false) return;
|
||||
}
|
||||
// 上层 walker 或宿主调用,先通知 BinaryOp 回调,再按 ASDL 顺序访问子结构;不修改树。
|
||||
export function walkBinaryOp(node: BinaryOp, visitor: Visitor): void {
|
||||
// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。
|
||||
if (visitor.visitBinaryOp?.(node) === false) return;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
// frontend/tests/syntax.rs 解析此模块并检查两个声明;这里只演示 AST 输入,不自动调用函数或执行数值运算。
|
||||
// Parsing produces an AST; it does not execute these functions.
|
||||
// 供语法测试解析的恒等函数,显式 String 参数与返回注解均保存在 AST 中。
|
||||
function echo(text: String): String {
|
||||
// let 绑定把参数读取存为独立 Binding 节点,供前端检查语句结构。
|
||||
let result = text
|
||||
// 末尾 return 引用 result,解析后保留名称及原文范围。
|
||||
return result
|
||||
}
|
||||
|
||||
// 供语法测试解析的双参数函数;参数和返回类型名仅作为注解文本,不在前端解析其身份。
|
||||
function discountedPrice(price: Number, discount: Number): Number {
|
||||
// 减法在 AST 中是包含两个名称读取的 Binary 节点,此示例解析过程不计算结果。
|
||||
return price - discount
|
||||
}
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
//! compiler 和宿主共用的语法树门面:生成类型、可定制访问器、版本化 JSON 文档及源码位置辅助接口。
|
||||
//! AST types and walkers generated from the single Endor.asdl schema.
|
||||
//! Runtime objects remain in the objects crate.
|
||||
|
||||
// 类型及 Spanned 实现来自 Endor.asdl 对应生成器,避免前端与编辑器各维护一套结构。
|
||||
mod generated;
|
||||
// 公开借用节点的遍历协议,文档校验和宿主工具可覆盖访问并积累自己的状态。
|
||||
pub mod visitor;
|
||||
// JSON 编解码、schema 和跨度/父子范围校验通过此模块实现。
|
||||
mod wire;
|
||||
|
||||
// 宿主和编译器从 ast 直接引用 Program/Expr/Stmt 等生成类型。
|
||||
pub use generated::*;
|
||||
// 文档验证失败使用统一错误类型,不等同于 VM 执行错误。
|
||||
pub use wire::AstError;
|
||||
|
||||
use crate::{SourceLocation, Span};
|
||||
|
||||
/// A syntax node's half-open byte range in its original UTF-8 source.
|
||||
/// 生成 AST 节点实现的原文定位协议,编译分析、编辑器和测试用它取得字节范围或源片段。
|
||||
pub trait Spanned {
|
||||
/// 具体节点实现返回存储的半开字节范围,不自行读取源码。
|
||||
fn span(&self) -> Span;
|
||||
|
||||
/// 宿主/测试按节点范围借用原文;无效 UTF-8 边界或越界返回 None,不分配字符串。
|
||||
fn source_text<'a>(&self, source: &'a str) -> Option<&'a str> {
|
||||
self.span().slice(source)
|
||||
}
|
||||
|
||||
/// 宿主/测试从对应原文计算起点行列;范围不合法时返回 None。
|
||||
fn location(&self, source: &str) -> Option<SourceLocation> {
|
||||
self.span().location(source)
|
||||
}
|
||||
@@ -25,9 +35,11 @@ pub trait Spanned {
|
||||
|
||||
impl Program {
|
||||
/// Iterate the currently supported top-level function declarations.
|
||||
/// 前端 AST 测试或宿主只需顶层函数时使用;保持声明顺序,类及其方法不进入此迭代器。
|
||||
pub fn functions(&self) -> impl Iterator<Item = &Function> {
|
||||
self.declarations
|
||||
.iter()
|
||||
// 仅取函数声明借用,类仍保留在 declarations,调用方可另行遍历。
|
||||
.filter_map(|declaration| match &declaration.kind {
|
||||
DeclKind::Function(function) => Some(function),
|
||||
DeclKind::Class(_) => None,
|
||||
|
||||
@@ -2,23 +2,33 @@
|
||||
|
||||
use super::Spanned;
|
||||
use crate::Span;
|
||||
/// wire/编辑器核对的协议身份,来自 ASDL 原文哈希;生成节点注释不参与此值。
|
||||
pub const AST_SCHEMA_ID: &str =
|
||||
"urn:endor:ast:sha256:21b0349ac295d9bc8cedcad8f07d8e2d45cf220e19e7c2fad10269555fcfd71a";
|
||||
/// 宿主/编辑器传输的版本化语法文档,wire 校验后供 compiler 使用。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct AstDocument {
|
||||
/// 协议身份,wire 按 AST_SCHEMA_ID 核对;不是源码内容哈希。
|
||||
pub schema: String,
|
||||
/// 拥有的语法根,文档校验及编译从这里递归。
|
||||
pub root: AstRoot,
|
||||
}
|
||||
/// parse_ast 按模式构造的模块、表达式或交互根,compiler 据此选择生成入口。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(tag = "kind", content = "data", deny_unknown_fields)]
|
||||
pub enum AstRoot {
|
||||
/// 模块根,包含函数/类声明。
|
||||
Module(Program),
|
||||
/// 表达式根,编译后直接返回求值结果。
|
||||
Expression(Expr),
|
||||
/// 交互根,包含一次完整提交。
|
||||
Interactive(InteractiveInput),
|
||||
}
|
||||
impl Spanned for AstRoot {
|
||||
/// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。
|
||||
fn span(&self) -> Span {
|
||||
// 按实际构造器取得唯一可定位子节点的范围。
|
||||
match self {
|
||||
Self::Module(node) => node.span(),
|
||||
Self::Expression(node) => node.span(),
|
||||
@@ -26,129 +36,184 @@ impl Spanned for AstRoot {
|
||||
}
|
||||
}
|
||||
}
|
||||
/// parser 复制的名称文本与位置,用于声明、类型注解或属性名。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Name {
|
||||
/// 拥有名称文本,不依赖输入缓冲存活。
|
||||
pub text: String,
|
||||
/// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
pub span: Span,
|
||||
}
|
||||
impl Spanned for Name {
|
||||
/// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
/// 模块声明序列,compiler 先注册全部顶层名称再分析各函数体。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Program {
|
||||
/// 按源码顺序的声明;源码模块解析要求非空,文档结构本身不限制数量。
|
||||
pub declarations: Vec<Decl>,
|
||||
/// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
pub span: Span,
|
||||
}
|
||||
impl Spanned for Program {
|
||||
/// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
/// 顶层/交互声明的种类及完整范围,compiler 按函数或类分派。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Decl {
|
||||
/// 函数/类声明载荷,JSON 和类型采用 kind/data 标签。
|
||||
pub kind: DeclKind,
|
||||
/// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
pub span: Span,
|
||||
}
|
||||
impl Spanned for Decl {
|
||||
/// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
/// 函数与类声明的判别载荷,外层 Decl 另外保存完整跨度。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(tag = "kind", content = "data", deny_unknown_fields)]
|
||||
pub enum DeclKind {
|
||||
/// 显式参数/返回注解与函数体,compiler 生成独立模板及源码签名;解析本身不调用函数。
|
||||
Function(Function),
|
||||
/// 类名、可选基类、字段和方法,compiler 检查成员后生成构类指令。
|
||||
Class(Class),
|
||||
}
|
||||
impl Spanned for DeclKind {
|
||||
/// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。
|
||||
fn span(&self) -> Span {
|
||||
// 按实际构造器取得唯一可定位子节点的范围。
|
||||
match self {
|
||||
Self::Function(node) => node.span(),
|
||||
Self::Class(node) => node.span(),
|
||||
}
|
||||
}
|
||||
}
|
||||
/// 类名、可选基类、字段和方法,compiler 检查成员后生成构类指令。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Class {
|
||||
/// 类声明名与精确位置。
|
||||
pub name: Name,
|
||||
/// 显式基类名,可省略;当前 compiler 对省略项使用 Object。
|
||||
pub base: Option<Name>,
|
||||
/// 按出现顺序的字段名称/类型注解,不含方法。
|
||||
pub fields: Vec<Parameter>,
|
||||
/// 按出现顺序的方法;显式 self 约束由 compiler 检查。
|
||||
pub methods: Vec<Function>,
|
||||
/// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
pub span: Span,
|
||||
}
|
||||
impl Spanned for Class {
|
||||
/// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
/// 显式参数/返回注解与函数体,compiler 生成独立模板及源码签名;解析本身不调用函数。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Function {
|
||||
/// 函数/方法名,代码模板沿用为诊断名称。
|
||||
pub name: Name,
|
||||
/// 位置参数列表,compiler 依次分配前部局部槽。
|
||||
pub parameters: Vec<Parameter>,
|
||||
/// 返回类型名,生成源码签名后由运行时解析。
|
||||
pub return_type: Name,
|
||||
/// 独立函数作用域的语句体,当前源码不支持嵌套声明闭包。
|
||||
pub body: Block,
|
||||
/// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
pub span: Span,
|
||||
}
|
||||
impl Spanned for Function {
|
||||
/// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
/// 函数参数或类字段共用的名称/类型注解,实际类型身份由运行时环境解析。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Parameter {
|
||||
/// 参数/字段名,用于布局和重复检查。
|
||||
pub name: Name,
|
||||
/// 声明类型名,前端只保留文本与位置。
|
||||
pub type_name: Name,
|
||||
/// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
pub span: Span,
|
||||
}
|
||||
impl Spanned for Parameter {
|
||||
/// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
/// 顺序语句块,compiler 检查确定初始化并按语句生成指令。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Block {
|
||||
/// 有序语句列表,不引入独立块级局部作用域。
|
||||
pub statements: Vec<Stmt>,
|
||||
/// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
pub span: Span,
|
||||
}
|
||||
impl Spanned for Block {
|
||||
/// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
/// 语句种类及原文范围,compiler 处理副作用、控制流或返回。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Stmt {
|
||||
/// 具体语句载荷,外层另存整句范围。
|
||||
pub kind: StmtKind,
|
||||
/// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
pub span: Span,
|
||||
}
|
||||
impl Spanned for Stmt {
|
||||
/// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
/// 语句判别载荷,parser 选择分支,compiler 按绑定、赋值、控制流等规则处理。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(tag = "kind", content = "data", deny_unknown_fields)]
|
||||
pub enum StmtKind {
|
||||
/// let 新绑定,compiler 检查重名并分析右值后才标记目标已初始化。
|
||||
Binding(Binding),
|
||||
/// 普通名称赋值,compiler 按作用域写局部槽或全局环境。
|
||||
Assignment(Assignment),
|
||||
/// 成员写入的对象、属性名与右值,compiler 生成 StoreAttr。
|
||||
MemberAssignment(MemberAssignment),
|
||||
/// 条件、then 和可选 else,compiler 合并继续路径的初始化集合并生成跳转。
|
||||
If(IfStatement),
|
||||
/// 循环条件与体,compiler 生成回边并处理循环可能零次执行。
|
||||
While(WhileStatement),
|
||||
/// 函数返回,compiler 对缺省值补 Nil;当前源码语法要求 return 后有表达式。
|
||||
Return(ReturnStatement),
|
||||
/// 表达式载荷及完整范围,compiler 生成留下一个结果的求值序列。
|
||||
Expression(Expr),
|
||||
/// 顶层/交互声明的种类及完整范围,compiler 按函数或类分派。
|
||||
Declaration(Decl),
|
||||
}
|
||||
impl Spanned for StmtKind {
|
||||
/// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。
|
||||
fn span(&self) -> Span {
|
||||
// 按实际构造器取得唯一可定位子节点的范围。
|
||||
match self {
|
||||
Self::Binding(node) => node.span(),
|
||||
Self::Assignment(node) => node.span(),
|
||||
@@ -161,141 +226,212 @@ impl Spanned for StmtKind {
|
||||
}
|
||||
}
|
||||
}
|
||||
/// let 新绑定,compiler 检查重名并分析右值后才标记目标已初始化。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Binding {
|
||||
/// let 目标名,重复声明由语义分析拒绝。
|
||||
pub name: Name,
|
||||
/// 绑定右值,本局部不能在初始化前读取自身。
|
||||
pub value: Expr,
|
||||
/// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
pub span: Span,
|
||||
}
|
||||
impl Spanned for Binding {
|
||||
/// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
/// 普通名称赋值,compiler 按作用域写局部槽或全局环境。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Assignment {
|
||||
/// 普通赋值目标,函数内首次赋值可预分配局部槽。
|
||||
pub name: Name,
|
||||
/// 先求右值再更新目标。
|
||||
pub value: Expr,
|
||||
/// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
pub span: Span,
|
||||
}
|
||||
impl Spanned for Assignment {
|
||||
/// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
/// 成员写入的对象、属性名与右值,compiler 生成 StoreAttr。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MemberAssignment {
|
||||
/// 属性写入接收对象表达式,前端不求值。
|
||||
pub object: Expr,
|
||||
/// 待写属性名,compiler 加入名字表。
|
||||
pub name: Name,
|
||||
/// 待写右值,当前 compiler 先于接收对象生成求值以匹配 VM 栈布局。
|
||||
pub value: Expr,
|
||||
/// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
pub span: Span,
|
||||
}
|
||||
impl Spanned for MemberAssignment {
|
||||
/// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
/// 条件、then 和可选 else,compiler 合并继续路径的初始化集合并生成跳转。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct IfStatement {
|
||||
/// 条件表达式,运行时按真值协议选择分支。
|
||||
pub condition: Expr,
|
||||
/// 条件为真的 then 块。
|
||||
pub body: Block,
|
||||
/// 可选 else 块,缺省时存在直接继续路径。
|
||||
pub otherwise: Option<Block>,
|
||||
/// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
pub span: Span,
|
||||
}
|
||||
impl Spanned for IfStatement {
|
||||
/// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
/// 循环条件与体,compiler 生成回边并处理循环可能零次执行。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WhileStatement {
|
||||
/// 每轮重新求值的条件,假值退出。
|
||||
pub condition: Expr,
|
||||
/// 循环体,可能一次也不执行。
|
||||
pub body: Block,
|
||||
/// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
pub span: Span,
|
||||
}
|
||||
impl Spanned for WhileStatement {
|
||||
/// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
/// 函数返回,compiler 对缺省值补 Nil;当前源码语法要求 return 后有表达式。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ReturnStatement {
|
||||
/// 可选返回表达式,手工 AST 缺省时由 compiler 补 Nil。
|
||||
pub value: Option<Expr>,
|
||||
/// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
pub span: Span,
|
||||
}
|
||||
impl Spanned for ReturnStatement {
|
||||
/// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
/// 单次交互提交容器,parser 产生一条语句,compiler 也检查数量,不保存宿主会话环境。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct InteractiveInput {
|
||||
/// 当前 parse/compile 约定恰好一个完整提交的语句。
|
||||
pub statements: Vec<Stmt>,
|
||||
/// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
pub span: Span,
|
||||
}
|
||||
impl Spanned for InteractiveInput {
|
||||
/// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
/// 表达式载荷及完整范围,compiler 生成留下一个结果的求值序列。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Expr {
|
||||
/// 求值形状,不缓存名称解析或运行时类型。
|
||||
pub kind: ExprKind,
|
||||
/// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。
|
||||
pub span: Span,
|
||||
}
|
||||
impl Spanned for Expr {
|
||||
/// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
/// 名称、字面量、等待、运算和调用等表达式载荷,范围由外层 Expr 提供。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(tag = "kind", content = "data", deny_unknown_fields)]
|
||||
pub enum ExprKind {
|
||||
/// 变量引用文本,compiler 决定局部槽或全局索引。
|
||||
Name(String),
|
||||
/// 原始十进制拼写,保留全部位数、前导零及尾零,不转浮点。
|
||||
Number(String),
|
||||
/// 已解码受支持转义的拥有文本,原引号范围在外层 Expr。
|
||||
String(String),
|
||||
/// 点开头的符号名,前端可表示,当前 compiler 拒绝生成运行时值。
|
||||
Symbol(Name),
|
||||
/// 等待子表达式,VM 执行上下文决定是否允许停放。
|
||||
Await(Box<Expr>),
|
||||
/// 一元取负,可在纯数值链上合并符号。
|
||||
Unary {
|
||||
/// 运算种类,决定算术、比较或取负指令。
|
||||
operation: UnaryOp,
|
||||
/// 一元子表达式,Rust 的递归回边间接存储。
|
||||
operand: Box<Expr>,
|
||||
},
|
||||
/// 左右操作数及算术/比较种类,保留解析优先级树。
|
||||
Binary {
|
||||
/// 运算种类,决定算术、比较或取负指令。
|
||||
operation: BinaryOp,
|
||||
/// 二元左操作数,当前 compiler 先于右侧求值。
|
||||
left: Box<Expr>,
|
||||
/// 二元右操作数,与左侧一起被运行时运算消费。
|
||||
right: Box<Expr>,
|
||||
},
|
||||
/// 接收对象及属性名的读取。
|
||||
Member {
|
||||
/// 成员读取接收对象。
|
||||
object: Box<Expr>,
|
||||
/// 成员或符号名,不是已解析的运行时属性句柄。
|
||||
name: Name,
|
||||
},
|
||||
/// 被调用表达式与位置实参,由 VM 实际求值。
|
||||
Call {
|
||||
/// 调用目标,先于实参求值。
|
||||
callee: Box<Expr>,
|
||||
/// 有序位置实参,AST 不隐式添加接收者。
|
||||
arguments: Vec<Expr>,
|
||||
},
|
||||
}
|
||||
/// 一元运算种类,compiler 合并纯数值负号或生成运行时取负。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Copy)]
|
||||
pub enum UnaryOp {
|
||||
/// 一元取负。
|
||||
Negate,
|
||||
}
|
||||
/// 二元算术/比较种类,parser 保留优先级树,compiler 映射为对应 VM 操作。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Copy)]
|
||||
pub enum BinaryOp {
|
||||
/// 加法。
|
||||
Add,
|
||||
/// 减法。
|
||||
Subtract,
|
||||
/// 乘法。
|
||||
Multiply,
|
||||
/// 除法。
|
||||
Divide,
|
||||
/// 相等比较。
|
||||
Equal,
|
||||
/// 不等比较。
|
||||
NotEqual,
|
||||
/// 小于比较。
|
||||
Less,
|
||||
/// 小于等于比较。
|
||||
LessEqual,
|
||||
/// 大于比较。
|
||||
Greater,
|
||||
/// 大于等于比较。
|
||||
GreaterEqual,
|
||||
}
|
||||
|
||||
@@ -6,382 +6,566 @@ use super::{
|
||||
ReturnStatement, Spanned, Stmt, StmtKind, UnaryOp, WhileStatement,
|
||||
};
|
||||
use crate::Span;
|
||||
/// wire 校验与宿主分析实现的借用节点访问协议;默认按 ASDL 字段顺序递归,不修改树。
|
||||
/// 覆盖 visit 方法后可显式调用对应 walk 继续子遍历;walk 本身不执行当前节点的进入/退出钩子。
|
||||
pub trait Visitor {
|
||||
/// 实现者选择的错误类型,任一子访问失败即向上返回。
|
||||
type Error;
|
||||
/// 默认可定位 visit 在遍历前调用;失败时本节点尚未成功进入,不再调用它的 leave_node。
|
||||
fn enter_node(&mut self, _span: Span) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
/// 已成功进入节点的遍历结束后调用,子节点错误也需退出;默认无操作。
|
||||
fn leave_node(&mut self) {}
|
||||
/// walk 遇到显式 span 字段时调用,默认不处理;区别于围绕整个节点的 enter_node。
|
||||
fn visit_span(&mut self, _span: &Span) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 AstDocument 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_ast_document(&mut self, node: &AstDocument) -> Result<(), Self::Error> {
|
||||
walk_ast_document(self, node)
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 AstRoot 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_ast_root(&mut self, node: &AstRoot) -> Result<(), Self::Error> {
|
||||
// 先进入;进入失败则不启动子遍历。
|
||||
self.enter_node(node.span())?;
|
||||
// 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。
|
||||
let result = walk_ast_root(self, node);
|
||||
self.leave_node();
|
||||
result
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 Name 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_name(&mut self, node: &Name) -> Result<(), Self::Error> {
|
||||
// 先进入;进入失败则不启动子遍历。
|
||||
self.enter_node(node.span())?;
|
||||
// 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。
|
||||
let result = walk_name(self, node);
|
||||
self.leave_node();
|
||||
result
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 Program 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_program(&mut self, node: &Program) -> Result<(), Self::Error> {
|
||||
// 先进入;进入失败则不启动子遍历。
|
||||
self.enter_node(node.span())?;
|
||||
// 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。
|
||||
let result = walk_program(self, node);
|
||||
self.leave_node();
|
||||
result
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 Decl 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_decl(&mut self, node: &Decl) -> Result<(), Self::Error> {
|
||||
// 先进入;进入失败则不启动子遍历。
|
||||
self.enter_node(node.span())?;
|
||||
// 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。
|
||||
let result = walk_decl(self, node);
|
||||
self.leave_node();
|
||||
result
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 DeclKind 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_decl_kind(&mut self, node: &DeclKind) -> Result<(), Self::Error> {
|
||||
// 先进入;进入失败则不启动子遍历。
|
||||
self.enter_node(node.span())?;
|
||||
// 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。
|
||||
let result = walk_decl_kind(self, node);
|
||||
self.leave_node();
|
||||
result
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 Class 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_class(&mut self, node: &Class) -> Result<(), Self::Error> {
|
||||
// 先进入;进入失败则不启动子遍历。
|
||||
self.enter_node(node.span())?;
|
||||
// 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。
|
||||
let result = walk_class(self, node);
|
||||
self.leave_node();
|
||||
result
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 Function 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_function(&mut self, node: &Function) -> Result<(), Self::Error> {
|
||||
// 先进入;进入失败则不启动子遍历。
|
||||
self.enter_node(node.span())?;
|
||||
// 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。
|
||||
let result = walk_function(self, node);
|
||||
self.leave_node();
|
||||
result
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 Parameter 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_parameter(&mut self, node: &Parameter) -> Result<(), Self::Error> {
|
||||
// 先进入;进入失败则不启动子遍历。
|
||||
self.enter_node(node.span())?;
|
||||
// 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。
|
||||
let result = walk_parameter(self, node);
|
||||
self.leave_node();
|
||||
result
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 Block 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_block(&mut self, node: &Block) -> Result<(), Self::Error> {
|
||||
// 先进入;进入失败则不启动子遍历。
|
||||
self.enter_node(node.span())?;
|
||||
// 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。
|
||||
let result = walk_block(self, node);
|
||||
self.leave_node();
|
||||
result
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 Stmt 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_stmt(&mut self, node: &Stmt) -> Result<(), Self::Error> {
|
||||
// 先进入;进入失败则不启动子遍历。
|
||||
self.enter_node(node.span())?;
|
||||
// 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。
|
||||
let result = walk_stmt(self, node);
|
||||
self.leave_node();
|
||||
result
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 StmtKind 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_stmt_kind(&mut self, node: &StmtKind) -> Result<(), Self::Error> {
|
||||
// 先进入;进入失败则不启动子遍历。
|
||||
self.enter_node(node.span())?;
|
||||
// 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。
|
||||
let result = walk_stmt_kind(self, node);
|
||||
self.leave_node();
|
||||
result
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 Binding 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_binding(&mut self, node: &Binding) -> Result<(), Self::Error> {
|
||||
// 先进入;进入失败则不启动子遍历。
|
||||
self.enter_node(node.span())?;
|
||||
// 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。
|
||||
let result = walk_binding(self, node);
|
||||
self.leave_node();
|
||||
result
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 Assignment 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_assignment(&mut self, node: &Assignment) -> Result<(), Self::Error> {
|
||||
// 先进入;进入失败则不启动子遍历。
|
||||
self.enter_node(node.span())?;
|
||||
// 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。
|
||||
let result = walk_assignment(self, node);
|
||||
self.leave_node();
|
||||
result
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 MemberAssignment 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_member_assignment(&mut self, node: &MemberAssignment) -> Result<(), Self::Error> {
|
||||
// 先进入;进入失败则不启动子遍历。
|
||||
self.enter_node(node.span())?;
|
||||
// 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。
|
||||
let result = walk_member_assignment(self, node);
|
||||
self.leave_node();
|
||||
result
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 IfStatement 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_if_statement(&mut self, node: &IfStatement) -> Result<(), Self::Error> {
|
||||
// 先进入;进入失败则不启动子遍历。
|
||||
self.enter_node(node.span())?;
|
||||
// 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。
|
||||
let result = walk_if_statement(self, node);
|
||||
self.leave_node();
|
||||
result
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 WhileStatement 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_while_statement(&mut self, node: &WhileStatement) -> Result<(), Self::Error> {
|
||||
// 先进入;进入失败则不启动子遍历。
|
||||
self.enter_node(node.span())?;
|
||||
// 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。
|
||||
let result = walk_while_statement(self, node);
|
||||
self.leave_node();
|
||||
result
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 ReturnStatement 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_return_statement(&mut self, node: &ReturnStatement) -> Result<(), Self::Error> {
|
||||
// 先进入;进入失败则不启动子遍历。
|
||||
self.enter_node(node.span())?;
|
||||
// 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。
|
||||
let result = walk_return_statement(self, node);
|
||||
self.leave_node();
|
||||
result
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 InteractiveInput 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_interactive_input(&mut self, node: &InteractiveInput) -> Result<(), Self::Error> {
|
||||
// 先进入;进入失败则不启动子遍历。
|
||||
self.enter_node(node.span())?;
|
||||
// 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。
|
||||
let result = walk_interactive_input(self, node);
|
||||
self.leave_node();
|
||||
result
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 Expr 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_expr(&mut self, node: &Expr) -> Result<(), Self::Error> {
|
||||
// 先进入;进入失败则不启动子遍历。
|
||||
self.enter_node(node.span())?;
|
||||
// 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。
|
||||
let result = walk_expr(self, node);
|
||||
self.leave_node();
|
||||
result
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 ExprKind 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_expr_kind(&mut self, node: &ExprKind) -> Result<(), Self::Error> {
|
||||
walk_expr_kind(self, node)
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 UnaryOp 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_unary_op(&mut self, node: &UnaryOp) -> Result<(), Self::Error> {
|
||||
walk_unary_op(self, node)
|
||||
}
|
||||
/// 上层 walk 或宿主调用的 BinaryOp 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。
|
||||
fn visit_binary_op(&mut self, node: &BinaryOp) -> Result<(), Self::Error> {
|
||||
walk_binary_op(self, node)
|
||||
}
|
||||
}
|
||||
/// 默认 visit_ast_document 或自定义访问器调用,按字段顺序借用 AstDocument 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_ast_document<V: Visitor + ?Sized>(
|
||||
visitor: &mut V,
|
||||
node: &AstDocument,
|
||||
) -> Result<(), V::Error> {
|
||||
// 访问 &node.root 的 AstRoot 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_ast_root(&node.root)?;
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_ast_root 或自定义访问器调用,按字段顺序借用 AstRoot 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_ast_root<V: Visitor + ?Sized>(visitor: &mut V, node: &AstRoot) -> Result<(), V::Error> {
|
||||
// 按构造器只递归当前实际载荷,字符串等标量没有语法子节点。
|
||||
match node {
|
||||
// 模块根,包含函数/类声明。
|
||||
AstRoot::Module(value) => {
|
||||
// 访问 value 的 Program 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_program(value)?;
|
||||
}
|
||||
// 表达式根,编译后直接返回求值结果。
|
||||
AstRoot::Expression(value) => {
|
||||
// 访问 value 的 Expr 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_expr(value)?;
|
||||
}
|
||||
// 交互根,包含一次完整提交。
|
||||
AstRoot::Interactive(value) => {
|
||||
// 访问 value 的 InteractiveInput 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_interactive_input(value)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_name 或自定义访问器调用,按字段顺序借用 Name 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_name<V: Visitor + ?Sized>(visitor: &mut V, node: &Name) -> Result<(), V::Error> {
|
||||
// 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_span(&node.span)?;
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_program 或自定义访问器调用,按字段顺序借用 Program 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_program<V: Visitor + ?Sized>(visitor: &mut V, node: &Program) -> Result<(), V::Error> {
|
||||
// 按顺序借用 &node.declarations 中每个 Decl,任一访问失败立即向上传播。
|
||||
for value in &node.declarations {
|
||||
visitor.visit_decl(value)?;
|
||||
}
|
||||
// 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_span(&node.span)?;
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_decl 或自定义访问器调用,按字段顺序借用 Decl 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_decl<V: Visitor + ?Sized>(visitor: &mut V, node: &Decl) -> Result<(), V::Error> {
|
||||
// 访问 &node.kind 的 DeclKind 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_decl_kind(&node.kind)?;
|
||||
// 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_span(&node.span)?;
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_decl_kind 或自定义访问器调用,按字段顺序借用 DeclKind 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_decl_kind<V: Visitor + ?Sized>(
|
||||
visitor: &mut V,
|
||||
node: &DeclKind,
|
||||
) -> Result<(), V::Error> {
|
||||
// 按构造器只递归当前实际载荷,字符串等标量没有语法子节点。
|
||||
match node {
|
||||
// 显式参数/返回注解与函数体,compiler 生成独立模板及源码签名;解析本身不调用函数。
|
||||
DeclKind::Function(value) => {
|
||||
// 访问 value 的 Function 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_function(value)?;
|
||||
}
|
||||
// 类名、可选基类、字段和方法,compiler 检查成员后生成构类指令。
|
||||
DeclKind::Class(value) => {
|
||||
// 访问 value 的 Class 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_class(value)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_class 或自定义访问器调用,按字段顺序借用 Class 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_class<V: Visitor + ?Sized>(visitor: &mut V, node: &Class) -> Result<(), V::Error> {
|
||||
// 访问 &node.name 的 Name 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_name(&node.name)?;
|
||||
// &node.base 是可选字段,仅 Some 时访问其 Name 子节点。
|
||||
if let Some(value) = &node.base {
|
||||
visitor.visit_name(value)?;
|
||||
}
|
||||
// 按顺序借用 &node.fields 中每个 Parameter,任一访问失败立即向上传播。
|
||||
for value in &node.fields {
|
||||
visitor.visit_parameter(value)?;
|
||||
}
|
||||
// 按顺序借用 &node.methods 中每个 Function,任一访问失败立即向上传播。
|
||||
for value in &node.methods {
|
||||
visitor.visit_function(value)?;
|
||||
}
|
||||
// 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_span(&node.span)?;
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_function 或自定义访问器调用,按字段顺序借用 Function 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_function<V: Visitor + ?Sized>(
|
||||
visitor: &mut V,
|
||||
node: &Function,
|
||||
) -> Result<(), V::Error> {
|
||||
// 访问 &node.name 的 Name 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_name(&node.name)?;
|
||||
// 按顺序借用 &node.parameters 中每个 Parameter,任一访问失败立即向上传播。
|
||||
for value in &node.parameters {
|
||||
visitor.visit_parameter(value)?;
|
||||
}
|
||||
// 访问 &node.return_type 的 Name 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_name(&node.return_type)?;
|
||||
// 访问 &node.body 的 Block 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_block(&node.body)?;
|
||||
// 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_span(&node.span)?;
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_parameter 或自定义访问器调用,按字段顺序借用 Parameter 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_parameter<V: Visitor + ?Sized>(
|
||||
visitor: &mut V,
|
||||
node: &Parameter,
|
||||
) -> Result<(), V::Error> {
|
||||
// 访问 &node.name 的 Name 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_name(&node.name)?;
|
||||
// 访问 &node.type_name 的 Name 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_name(&node.type_name)?;
|
||||
// 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_span(&node.span)?;
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_block 或自定义访问器调用,按字段顺序借用 Block 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_block<V: Visitor + ?Sized>(visitor: &mut V, node: &Block) -> Result<(), V::Error> {
|
||||
// 按顺序借用 &node.statements 中每个 Stmt,任一访问失败立即向上传播。
|
||||
for value in &node.statements {
|
||||
visitor.visit_stmt(value)?;
|
||||
}
|
||||
// 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_span(&node.span)?;
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_stmt 或自定义访问器调用,按字段顺序借用 Stmt 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_stmt<V: Visitor + ?Sized>(visitor: &mut V, node: &Stmt) -> Result<(), V::Error> {
|
||||
// 访问 &node.kind 的 StmtKind 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_stmt_kind(&node.kind)?;
|
||||
// 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_span(&node.span)?;
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_stmt_kind 或自定义访问器调用,按字段顺序借用 StmtKind 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_stmt_kind<V: Visitor + ?Sized>(
|
||||
visitor: &mut V,
|
||||
node: &StmtKind,
|
||||
) -> Result<(), V::Error> {
|
||||
// 按构造器只递归当前实际载荷,字符串等标量没有语法子节点。
|
||||
match node {
|
||||
// let 新绑定,compiler 检查重名并分析右值后才标记目标已初始化。
|
||||
StmtKind::Binding(value) => {
|
||||
// 访问 value 的 Binding 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_binding(value)?;
|
||||
}
|
||||
// 普通名称赋值,compiler 按作用域写局部槽或全局环境。
|
||||
StmtKind::Assignment(value) => {
|
||||
// 访问 value 的 Assignment 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_assignment(value)?;
|
||||
}
|
||||
// 成员写入的对象、属性名与右值,compiler 生成 StoreAttr。
|
||||
StmtKind::MemberAssignment(value) => {
|
||||
// 访问 value 的 MemberAssignment 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_member_assignment(value)?;
|
||||
}
|
||||
// 条件、then 和可选 else,compiler 合并继续路径的初始化集合并生成跳转。
|
||||
StmtKind::If(value) => {
|
||||
// 访问 value 的 IfStatement 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_if_statement(value)?;
|
||||
}
|
||||
// 循环条件与体,compiler 生成回边并处理循环可能零次执行。
|
||||
StmtKind::While(value) => {
|
||||
// 访问 value 的 WhileStatement 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_while_statement(value)?;
|
||||
}
|
||||
// 函数返回,compiler 对缺省值补 Nil;当前源码语法要求 return 后有表达式。
|
||||
StmtKind::Return(value) => {
|
||||
// 访问 value 的 ReturnStatement 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_return_statement(value)?;
|
||||
}
|
||||
// 表达式载荷及完整范围,compiler 生成留下一个结果的求值序列。
|
||||
StmtKind::Expression(value) => {
|
||||
// 访问 value 的 Expr 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_expr(value)?;
|
||||
}
|
||||
// 顶层/交互声明的种类及完整范围,compiler 按函数或类分派。
|
||||
StmtKind::Declaration(value) => {
|
||||
// 访问 value 的 Decl 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_decl(value)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_binding 或自定义访问器调用,按字段顺序借用 Binding 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_binding<V: Visitor + ?Sized>(visitor: &mut V, node: &Binding) -> Result<(), V::Error> {
|
||||
// 访问 &node.name 的 Name 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_name(&node.name)?;
|
||||
// 访问 &node.value 的 Expr 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_expr(&node.value)?;
|
||||
// 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_span(&node.span)?;
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_assignment 或自定义访问器调用,按字段顺序借用 Assignment 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_assignment<V: Visitor + ?Sized>(
|
||||
visitor: &mut V,
|
||||
node: &Assignment,
|
||||
) -> Result<(), V::Error> {
|
||||
// 访问 &node.name 的 Name 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_name(&node.name)?;
|
||||
// 访问 &node.value 的 Expr 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_expr(&node.value)?;
|
||||
// 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_span(&node.span)?;
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_member_assignment 或自定义访问器调用,按字段顺序借用 MemberAssignment 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_member_assignment<V: Visitor + ?Sized>(
|
||||
visitor: &mut V,
|
||||
node: &MemberAssignment,
|
||||
) -> Result<(), V::Error> {
|
||||
// 访问 &node.object 的 Expr 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_expr(&node.object)?;
|
||||
// 访问 &node.name 的 Name 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_name(&node.name)?;
|
||||
// 访问 &node.value 的 Expr 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_expr(&node.value)?;
|
||||
// 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_span(&node.span)?;
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_if_statement 或自定义访问器调用,按字段顺序借用 IfStatement 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_if_statement<V: Visitor + ?Sized>(
|
||||
visitor: &mut V,
|
||||
node: &IfStatement,
|
||||
) -> Result<(), V::Error> {
|
||||
// 访问 &node.condition 的 Expr 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_expr(&node.condition)?;
|
||||
// 访问 &node.body 的 Block 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_block(&node.body)?;
|
||||
// &node.otherwise 是可选字段,仅 Some 时访问其 Block 子节点。
|
||||
if let Some(value) = &node.otherwise {
|
||||
visitor.visit_block(value)?;
|
||||
}
|
||||
// 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_span(&node.span)?;
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_while_statement 或自定义访问器调用,按字段顺序借用 WhileStatement 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_while_statement<V: Visitor + ?Sized>(
|
||||
visitor: &mut V,
|
||||
node: &WhileStatement,
|
||||
) -> Result<(), V::Error> {
|
||||
// 访问 &node.condition 的 Expr 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_expr(&node.condition)?;
|
||||
// 访问 &node.body 的 Block 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_block(&node.body)?;
|
||||
// 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_span(&node.span)?;
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_return_statement 或自定义访问器调用,按字段顺序借用 ReturnStatement 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_return_statement<V: Visitor + ?Sized>(
|
||||
visitor: &mut V,
|
||||
node: &ReturnStatement,
|
||||
) -> Result<(), V::Error> {
|
||||
// &node.value 是可选字段,仅 Some 时访问其 Expr 子节点。
|
||||
if let Some(value) = &node.value {
|
||||
visitor.visit_expr(value)?;
|
||||
}
|
||||
// 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_span(&node.span)?;
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_interactive_input 或自定义访问器调用,按字段顺序借用 InteractiveInput 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_interactive_input<V: Visitor + ?Sized>(
|
||||
visitor: &mut V,
|
||||
node: &InteractiveInput,
|
||||
) -> Result<(), V::Error> {
|
||||
// 按顺序借用 &node.statements 中每个 Stmt,任一访问失败立即向上传播。
|
||||
for value in &node.statements {
|
||||
visitor.visit_stmt(value)?;
|
||||
}
|
||||
// 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_span(&node.span)?;
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_expr 或自定义访问器调用,按字段顺序借用 Expr 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_expr<V: Visitor + ?Sized>(visitor: &mut V, node: &Expr) -> Result<(), V::Error> {
|
||||
// 访问 &node.kind 的 ExprKind 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_expr_kind(&node.kind)?;
|
||||
// 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_span(&node.span)?;
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_expr_kind 或自定义访问器调用,按字段顺序借用 ExprKind 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_expr_kind<V: Visitor + ?Sized>(
|
||||
visitor: &mut V,
|
||||
node: &ExprKind,
|
||||
) -> Result<(), V::Error> {
|
||||
// 按构造器只递归当前实际载荷,字符串等标量没有语法子节点。
|
||||
match node {
|
||||
// 变量引用文本,compiler 决定局部槽或全局索引。
|
||||
ExprKind::Name(_) => {}
|
||||
// 原始十进制拼写,保留全部位数、前导零及尾零,不转浮点。
|
||||
ExprKind::Number(_) => {}
|
||||
// 已解码受支持转义的拥有文本,原引号范围在外层 Expr。
|
||||
ExprKind::String(_) => {}
|
||||
// 点开头的符号名,前端可表示,当前 compiler 拒绝生成运行时值。
|
||||
ExprKind::Symbol(value) => {
|
||||
// 访问 value 的 Name 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_name(value)?;
|
||||
}
|
||||
// 等待子表达式,VM 执行上下文决定是否允许停放。
|
||||
ExprKind::Await(value) => {
|
||||
// 访问 value 的 Expr 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_expr(value)?;
|
||||
}
|
||||
// 一元取负,可在纯数值链上合并符号。
|
||||
ExprKind::Unary {
|
||||
operation, operand, ..
|
||||
} => {
|
||||
// 访问 operation 的 UnaryOp 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_unary_op(operation)?;
|
||||
// 访问 operand 的 Expr 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_expr(operand)?;
|
||||
}
|
||||
// 左右操作数及算术/比较种类,保留解析优先级树。
|
||||
ExprKind::Binary {
|
||||
operation,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} => {
|
||||
// 访问 operation 的 BinaryOp 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_binary_op(operation)?;
|
||||
// 访问 left 的 Expr 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_expr(left)?;
|
||||
// 访问 right 的 Expr 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_expr(right)?;
|
||||
}
|
||||
// 接收对象及属性名的读取。
|
||||
ExprKind::Member { object, name, .. } => {
|
||||
// 访问 object 的 Expr 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_expr(object)?;
|
||||
// 访问 name 的 Name 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_name(name)?;
|
||||
}
|
||||
// 被调用表达式与位置实参,由 VM 实际求值。
|
||||
ExprKind::Call {
|
||||
callee, arguments, ..
|
||||
} => {
|
||||
// 访问 callee 的 Expr 子结构或跨度,借用不转移节点所有权。
|
||||
visitor.visit_expr(callee)?;
|
||||
// 按顺序借用 arguments 中每个 Expr,任一访问失败立即向上传播。
|
||||
for value in arguments {
|
||||
visitor.visit_expr(value)?;
|
||||
}
|
||||
@@ -389,12 +573,14 @@ pub fn walk_expr_kind<V: Visitor + ?Sized>(
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_unary_op 或自定义访问器调用,按字段顺序借用 UnaryOp 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_unary_op<V: Visitor + ?Sized>(
|
||||
_visitor: &mut V,
|
||||
_node: &UnaryOp,
|
||||
) -> Result<(), V::Error> {
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit_binary_op 或自定义访问器调用,按字段顺序借用 BinaryOp 的子结构;本函数不进入/退出当前节点。
|
||||
pub fn walk_binary_op<V: Visitor + ?Sized>(
|
||||
_visitor: &mut V,
|
||||
_node: &BinaryOp,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//! 宿主与 compiler::compile_ast 使用的版本化 AST 文档接口;校验结构、跨度和包含关系,不执行源码。
|
||||
use std::fmt;
|
||||
|
||||
use super::visitor::Visitor;
|
||||
@@ -5,21 +6,32 @@ use super::{AST_SCHEMA_ID, AstDocument, AstRoot};
|
||||
use crate::Span;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// JSON 编解码或文档验证的公开错误,供宿主、编译器包装及 AST 测试按类别判断。
|
||||
pub enum AstError {
|
||||
// JSON 语法、生成类型反序列化约束或序列化失败,保留 serde 原因。
|
||||
Json(serde_json::Error),
|
||||
// 文档 schema 身份与当前生成类型不一致,不能按此版本解释。
|
||||
SchemaMismatch {
|
||||
// 当前编译进前端的 schema 标识,来自生成代码的 AST_SCHEMA_ID。
|
||||
expected: &'static str,
|
||||
// 外部文档提供的拥有型标识,错误不依赖原输入缓冲存活。
|
||||
found: String,
|
||||
},
|
||||
// 逆序、超出 JS 安全整数,或有源码校验时越界/切断 UTF-8 的节点范围。
|
||||
InvalidSpan(Span),
|
||||
// 子节点虽可能单独合法,但没有完全落在直接父节点范围内。
|
||||
ChildOutsideParent {
|
||||
// Visitor 祖先栈顶的直接父范围,用于诊断包含关系。
|
||||
parent: Span,
|
||||
// 当前试图进入、但越过父边界的子范围。
|
||||
child: Span,
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Display for AstError {
|
||||
/// 宿主显示文档错误时调用,按类别保留 schema 或字节范围细节。
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// 根据具体错误分类输出信息或提供底层原因;schema/跨度错误由本模块产生。
|
||||
match self {
|
||||
Self::Json(error) => write!(f, "invalid AST JSON: {error}"),
|
||||
Self::SchemaMismatch { expected, found } => {
|
||||
@@ -38,7 +50,9 @@ impl fmt::Display for AstError {
|
||||
}
|
||||
|
||||
impl std::error::Error for AstError {
|
||||
/// 标准 Error 链查询入口;只有 serde JSON 失败有外部底层错误对象。
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
// 根据具体错误分类输出信息或提供底层原因;schema/跨度错误由本模块产生。
|
||||
match self {
|
||||
Self::Json(error) => Some(error),
|
||||
_ => None,
|
||||
@@ -47,13 +61,16 @@ impl std::error::Error for AstError {
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for AstError {
|
||||
/// from_json/to_json 使用 ? 时,把 serde 错误统一转换为 AstError::Json。
|
||||
fn from(error: serde_json::Error) -> Self {
|
||||
Self::Json(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl AstDocument {
|
||||
/// compiler::compile_ast、宿主及测试为已有根创建文档;附当前 schema 并校验结构,尚无原文可查边界。
|
||||
pub fn new(root: AstRoot) -> Result<Self, AstError> {
|
||||
// 按当前 schema 包装拥有根,再校验,失败不把无效文档作为成功结果返回。
|
||||
let document = Self {
|
||||
schema: AST_SCHEMA_ID.into(),
|
||||
root,
|
||||
@@ -63,35 +80,45 @@ impl AstDocument {
|
||||
}
|
||||
|
||||
/// Decode a versioned syntax tree. This does not compile or execute it.
|
||||
/// 宿主/编辑器或测试输入 JSON 时调用;先解码类型结构,再检查 schema 身份及节点跨度。
|
||||
pub fn from_json(text: &str) -> Result<Self, AstError> {
|
||||
// 生成类型上的 serde 约束会在此拒绝未知字段或错误形状,再由 validate 检查文档级规则。
|
||||
let document: Self = serde_json::from_str(text)?;
|
||||
document.validate()?;
|
||||
Ok(document)
|
||||
}
|
||||
|
||||
/// 宿主/编辑器传输及往返测试调用;先验证当前文档,避免序列化已被手工改坏的结构。
|
||||
pub fn to_json(&self) -> Result<String, AstError> {
|
||||
self.validate()?;
|
||||
Ok(serde_json::to_string(self)?)
|
||||
}
|
||||
|
||||
/// Check schema identity, ranges, and parent/child containment.
|
||||
/// new/from_json/to_json 共用的无原文验证:范围顺序、JS 安全整数及父子包含;不推断文本长度。
|
||||
pub fn validate(&self) -> Result<(), AstError> {
|
||||
self.validate_with_source(None)
|
||||
}
|
||||
|
||||
/// Additionally check bounds and UTF-8 boundaries against original text.
|
||||
/// This does not check that the text and AST have identical semantics.
|
||||
/// compiler::compile_ast 和源码范围测试调用;额外用对应原文检查字节边界及长度,不证明 AST 语义与文本相同。
|
||||
pub fn validate_source(&self, source: &str) -> Result<(), AstError> {
|
||||
self.validate_with_source(Some(source))
|
||||
}
|
||||
|
||||
/// validate/validate_source 共用实现,source 的有无决定是否检查真实 UTF-8 原文范围。
|
||||
fn validate_with_source(&self, source: Option<&str>) -> Result<(), AstError> {
|
||||
// 先核对文档版本,避免用当前访问器接受另一结构版本的数据。
|
||||
if self.schema != AST_SCHEMA_ID {
|
||||
return Err(AstError::SchemaMismatch {
|
||||
// 当前编译进前端的 schema 标识,来自生成代码的 AST_SCHEMA_ID。
|
||||
expected: AST_SCHEMA_ID,
|
||||
// 外部文档提供的拥有型标识,错误不依赖原输入缓冲存活。
|
||||
found: self.schema.clone(),
|
||||
});
|
||||
}
|
||||
// 每次验证使用新祖先栈,从根递归,失败后这份临时状态随调用结束释放。
|
||||
SpanValidator {
|
||||
source,
|
||||
parents: Vec::new(),
|
||||
@@ -100,16 +127,21 @@ impl AstDocument {
|
||||
}
|
||||
}
|
||||
|
||||
/// AstDocument 校验使用的借用节点访问器;source 可选,parents 跟踪当前递归路径的节点范围。
|
||||
struct SpanValidator<'a> {
|
||||
// 有原文时校验 slice;无原文时只做结构与可表示范围检查。
|
||||
source: Option<&'a str>,
|
||||
// 成功进入才压栈,离开时弹栈,确保每个子节点与直接父节点比较。
|
||||
parents: Vec<Span>,
|
||||
}
|
||||
|
||||
impl Visitor for SpanValidator<'_> {
|
||||
type Error = AstError;
|
||||
|
||||
/// 默认 Visitor 在进入各语法节点前调用;只有检查通过才登记本节点,失败不需要为当前节点弹栈。
|
||||
fn enter_node(&mut self, span: Span) -> Result<(), AstError> {
|
||||
// JSON offsets must also be exactly representable by editor JS numbers.
|
||||
// end 不超过 JS Number 的最大安全整数;顺序合法同时保证 start 不会比 end 更大。
|
||||
if span.start > span.end
|
||||
|| span.end as u128 > 9_007_199_254_740_991
|
||||
|| self
|
||||
@@ -118,6 +150,7 @@ impl Visitor for SpanValidator<'_> {
|
||||
{
|
||||
return Err(AstError::InvalidSpan(span));
|
||||
}
|
||||
// 根没有父范围;其余节点不能越过直接父节点的任一端点,允许范围相等。
|
||||
if let Some(parent) = self.parents.last().copied()
|
||||
&& (span.start < parent.start || span.end > parent.end)
|
||||
{
|
||||
@@ -126,10 +159,12 @@ impl Visitor for SpanValidator<'_> {
|
||||
child: span,
|
||||
});
|
||||
}
|
||||
// 当前节点全部合法后才成为后续子节点的父范围。
|
||||
self.parents.push(span);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 默认 Visitor 在已进入节点的遍历结束后调用,即使子遍历失败,也会为成功进入的祖先执行退出。
|
||||
fn leave_node(&mut self) {
|
||||
self.parents.pop();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//! lexer、parser、AST 与 compiler 共用的位置/错误协议;字节跨度与用户显示的 Unicode 行列分开保存。
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
@@ -5,55 +6,76 @@ use std::fmt;
|
||||
/// the first and last syntax token. EOF has an empty range at source.len().
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
/// 语法节点及 Token 的半开原文字节范围 [start,end),EOF 使用 source.len() 处的空区间。
|
||||
pub struct Span {
|
||||
// 包含的起始字节偏移,应落在 UTF-8 字符边界。
|
||||
pub start: usize,
|
||||
// 不包含的结束字节偏移,包含两端 Token 之间的注释/空白。
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
impl Span {
|
||||
/// 词法/语法规则及测试构造范围,仅保存偏移;合法性由 slice 或 AST 校验验证。
|
||||
pub const fn new(start: usize, end: usize) -> Self {
|
||||
Self { start, end }
|
||||
}
|
||||
|
||||
/// Safely read this range, rejecting reversed, out-of-bounds or split UTF-8 ranges.
|
||||
/// AST Spanned::source_text 及文档校验调用;安全拒绝倒序、越界或切断 UTF-8 字符的范围。
|
||||
pub fn slice(self, source: &str) -> Option<&str> {
|
||||
source.get(self.start..self.end)
|
||||
}
|
||||
|
||||
/// AST Spanned::location 和编译器调用,先验证整个范围,再计算起点的显示行列。
|
||||
pub fn location(self, source: &str) -> Option<SourceLocation> {
|
||||
self.slice(source)?;
|
||||
Some(source_location(source, self.start))
|
||||
}
|
||||
|
||||
/// parser 组合左右节点及后缀范围时调用;沿用第一个起点和最后一个终点,不另做顺序检查。
|
||||
pub(crate) fn through(self, last: Self) -> Self {
|
||||
Self::new(self.start, last.end)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// 阶段分类供宿主和测试区分错误发生于分词、语法、名称分析还是生成。
|
||||
pub enum DiagnosticKind {
|
||||
// 词法规则无法接受字符或字符串转义等输入。
|
||||
Lexical,
|
||||
// Token 已合法,但无法匹配指定根的语法。
|
||||
Syntax,
|
||||
// 编译名称/控制流分析拒绝重复、未知或未初始化读取。
|
||||
Semantic,
|
||||
// AST 校验或指令生成/验证失败,尚未执行 VM。
|
||||
Compilation,
|
||||
}
|
||||
|
||||
/// Lines and Unicode scalar columns are one-based. CRLF counts as one newline.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// source_location 输出的一基位置;列按 Unicode 标量计数,CRLF 合并为一个换行。
|
||||
pub struct SourceLocation {
|
||||
// 从一开始的逻辑行号,独立 CR 和 LF 均换行,CRLF 只换一次。
|
||||
pub line: usize,
|
||||
// 从一开始的 Unicode 标量列,不是字节数、字素簇数或终端显示宽度。
|
||||
pub column: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// 各阶段产生的不带来源快照的错误,SourceUnit::annotate 可补来源和行列。
|
||||
pub struct Diagnostic {
|
||||
// 原始失败阶段,便于测试和宿主按类别处理。
|
||||
pub kind: DiagnosticKind,
|
||||
// 原源码字节位置,Display 直接显示这个范围。
|
||||
pub span: Span,
|
||||
// 面向用户的说明,不含来源名及行列前缀。
|
||||
pub message: String,
|
||||
// 语法解析器报告的期望项;词法和编译器辅助构造通常留空。
|
||||
pub expected: Vec<String>,
|
||||
}
|
||||
|
||||
impl Diagnostic {
|
||||
/// lexer 和词法规则共用的失败包装,统一设置 Lexical 类别并保留原文字节范围。
|
||||
pub(crate) fn lexical(span: Span, message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
kind: DiagnosticKind::Lexical,
|
||||
@@ -63,12 +85,14 @@ impl Diagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
/// SourceUnit::annotate 及宿主根据起始偏移计算位置;本入口不校验整个 span,调用方应传对应原文。
|
||||
pub fn location(&self, source: &str) -> SourceLocation {
|
||||
source_location(source, self.span.start)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Diagnostic {
|
||||
/// 未绑定来源的错误显示说明及字节区间;来源行列由 SourceDiagnostic 的 Display 提供。
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
formatter,
|
||||
@@ -80,25 +104,36 @@ impl fmt::Display for Diagnostic {
|
||||
|
||||
impl Error for Diagnostic {}
|
||||
|
||||
/// Span::location 和 Diagnostic::location 共用的纯文本扫描,计算 position 前缀的行列,不执行 IO。
|
||||
/// 调用方应提供有效字节边界;此辅助函数只扫描到偏移或 EOF,不负责校验范围。
|
||||
fn source_location(source: &str, position: usize) -> SourceLocation {
|
||||
// 空前缀位于第一行第一列,每消费一个普通 Unicode 标量才前进一列。
|
||||
let mut location = SourceLocation { line: 1, column: 1 };
|
||||
// 记录上一个已消费字符是否 CR,使紧随其后的 LF 不再次增加行号。
|
||||
let mut previous_cr = false;
|
||||
// char_indices 同时提供字节边界和完整字符,避免把多字节字符算成多列。
|
||||
for (offset, character) in source.char_indices() {
|
||||
// 当前字符起点已到目标位置,不能把目标字符本身算进前缀。
|
||||
if offset >= position {
|
||||
break;
|
||||
}
|
||||
// 换行重置列;普通字符(包括 tab)仅按一个 Unicode 标量计列。
|
||||
match character {
|
||||
// CR 自身就是换行,可能随后与 LF 配成一个换行。
|
||||
'\r' => {
|
||||
location.line += 1;
|
||||
location.column = 1;
|
||||
}
|
||||
// CR 已完成换行,这个 LF 不再增加行号。
|
||||
'\n' if previous_cr => {}
|
||||
// 独立 LF 换到下一行第一列。
|
||||
'\n' => {
|
||||
location.line += 1;
|
||||
location.column = 1;
|
||||
}
|
||||
_ => location.column += 1,
|
||||
}
|
||||
// 每轮都更新,只有紧邻 CR 的 LF 才会被合并。
|
||||
previous_cr = character == '\r';
|
||||
}
|
||||
location
|
||||
|
||||
@@ -1,79 +1,110 @@
|
||||
// parser.rs 编译时引入,生成 syntax 的 Token 解析规则;source 是这些 Token 对应的原始 UTF-8 文本。
|
||||
// 规则只构造拥有文本及原文跨度的 AST,不解析名称/类型,也不执行函数或操作 VM。
|
||||
peg::parser! {
|
||||
// parse/parse_expression/parse_ast 的交互分支调用相应公开根,默认要求完整消费 Token 输入。
|
||||
grammar syntax(source: &str) for [Token] {
|
||||
// 各语法上下文按原文精确匹配 Name;词法器不把这些词全局保留为独立关键字种类。
|
||||
rule keyword(word: &str) -> Span
|
||||
= [Token { kind: TokenKind::Name, span } if &source[span.start..span.end] == word] { span }
|
||||
|
||||
// function 规则的开头;quiet 隐藏内部 Token 细节,失败期望显示可读的 function。
|
||||
rule function_keyword() -> Span
|
||||
= quiet!{keyword("function")} / expected!("'function'")
|
||||
// binding 的开头,同样提供稳定可读的期望项。
|
||||
rule let_keyword() -> Span
|
||||
= quiet!{keyword("let")} / expected!("'let'")
|
||||
// return_statement 的开头及 local_statement 的负向前瞻,避免 return 被普通表达式语句消费。
|
||||
rule return_keyword() -> Span
|
||||
= quiet!{keyword("return")} / expected!("'return'")
|
||||
|
||||
// 声明、类型名及表达式引用共用;从 Token 范围复制名称文本,AST 不借用原源码缓冲。
|
||||
rule name() -> Name
|
||||
= span:quiet!{[Token { kind: TokenKind::Name, span }] { span }} {
|
||||
Name { text: source[span.start..span.end].into(), span }
|
||||
}
|
||||
/ expected!("identifier")
|
||||
|
||||
// 函数参数、调用及括号表达式复用左括号匹配,返回范围供组合节点使用。
|
||||
rule lparen() -> Span
|
||||
= quiet!{[Token { kind: TokenKind::LeftParen, span }] { span }} / expected!("'('")
|
||||
// 参数/调用/括号表达式复用右括号匹配,其终点可成为完整表达式终点。
|
||||
rule rparen() -> Span
|
||||
= quiet!{[Token { kind: TokenKind::RightParen, span }] { span }} / expected!("')'")
|
||||
// 类和 block 的左边界,缺失时报告可读的左花括号期望项。
|
||||
rule lbrace() -> Span
|
||||
= quiet!{[Token { kind: TokenKind::LeftBrace, span }] { span }} / expected!("'{'")
|
||||
// 类和 block 的右边界,节点跨度包含这个闭合括号。
|
||||
rule rbrace() -> Span
|
||||
= quiet!{[Token { kind: TokenKind::RightBrace, span }] { span }} / expected!("'}'")
|
||||
// 类型注解和类继承共用冒号,不额外构造 AST 节点。
|
||||
rule colon()
|
||||
= quiet!{[Token { kind: TokenKind::Colon, .. }]} / expected!("':'")
|
||||
// 参数/实参列表分隔;列表组合器决定空列表及分隔规则。
|
||||
rule comma()
|
||||
= quiet!{[Token { kind: TokenKind::Comma, .. }]} / expected!("','")
|
||||
// 绑定和赋值共用单等号 Token,不接受相等比较 Token。
|
||||
rule equal()
|
||||
= quiet!{[Token { kind: TokenKind::Equal, .. }]} / expected!("'='")
|
||||
// 成员后缀和符号字面量共用点,返回其范围以保留符号起点。
|
||||
rule dot() -> Span
|
||||
= quiet!{[Token { kind: TokenKind::Dot, span }] { span }} / expected!("'.'")
|
||||
// 加法优先级层消费 Plus Token。
|
||||
rule plus()
|
||||
= [Token { kind: TokenKind::Plus, .. }]
|
||||
// 减法及一元取负共用 Minus,前缀负号需要它的跨度作为节点起点。
|
||||
rule minus() -> Span
|
||||
= [Token { kind: TokenKind::Minus, span }] { span }
|
||||
// 乘法优先级层消费 Star Token。
|
||||
rule star()
|
||||
= [Token { kind: TokenKind::Star, .. }]
|
||||
// 除法优先级层消费 Slash,注释已在词法 trivia 中跳过。
|
||||
rule slash()
|
||||
= [Token { kind: TokenKind::Slash, .. }]
|
||||
|
||||
// parse 的模块入口;至少一个顶层函数或类声明,默认拒绝剩余 Token。
|
||||
pub rule program() -> Program
|
||||
= declarations:declaration()+ {
|
||||
// 声明列表由 + 保证非空,根范围从首声明起点延伸到末声明终点。
|
||||
let span = declarations[0].span.through(declarations.last().unwrap().span);
|
||||
Program { declarations, span }
|
||||
}
|
||||
|
||||
// program/interactive_statement 的声明分支;函数直接包装,类则收集基类及成员。
|
||||
rule declaration() -> Decl
|
||||
= function:function() {
|
||||
Decl { span: function.span, kind: DeclKind::Function(function) }
|
||||
}
|
||||
/ start:keyword("class") name:name() base:(colon() base:name() { base })?
|
||||
lbrace() members:class_member()* end:rbrace() {
|
||||
// 按字段出现顺序收集字段参数结构,类的布局稍后由编译器及 VM 解释。
|
||||
let mut fields = Vec::new();
|
||||
// 按方法出现顺序收集函数结构,与字段分开存储但不改变各自顺序。
|
||||
let mut methods = Vec::new();
|
||||
// 解析阶段先统一取得成员,再按中间分类分发到两份 AST 列表。
|
||||
for member in members {
|
||||
// 字段和方法保留各自原节点及跨度,不在这里检查成员重名或 self。
|
||||
match member {
|
||||
ClassMember::Field(field) => fields.push(field),
|
||||
ClassMember::Method(method) => methods.push(method),
|
||||
}
|
||||
}
|
||||
// 类范围包括 class 开头与闭合花括号,中间空白/注释仍属于原文范围。
|
||||
let span = start.through(end);
|
||||
Decl { span, kind: DeclKind::Class(Class { name, base, fields, methods, span }) }
|
||||
}
|
||||
|
||||
// declaration 的类体调用,先尝试完整方法,再尝试名称加类型的字段。
|
||||
rule class_member() -> ClassMember
|
||||
= function:function() { ClassMember::Method(function) }
|
||||
/ field:parameter() { ClassMember::Field(field) }
|
||||
|
||||
// parse_ast 的交互入口;恰好一个完整语句包装为列表,不管理多次提交的环境。
|
||||
pub rule interactive() -> InteractiveInput
|
||||
= statement:interactive_statement() {
|
||||
InteractiveInput { span: statement.span, statements: vec![statement] }
|
||||
}
|
||||
|
||||
// 交互允许声明、控制流、let、属性/普通赋值及表达式,不提供根级 return。
|
||||
rule interactive_statement() -> Stmt
|
||||
= declaration:declaration() {
|
||||
Stmt { span: declaration.span, kind: StmtKind::Declaration(declaration) }
|
||||
@@ -86,24 +117,30 @@ peg::parser! {
|
||||
Stmt { span: expression.span, kind: StmtKind::Expression(expression) }
|
||||
}
|
||||
|
||||
// 顶层和类方法共用函数语法,显式参数类型、返回类型及函数体;self 合法性留给分析。
|
||||
rule function() -> Function
|
||||
= start:function_keyword() name:name() lparen()
|
||||
parameters:(parameter() ** comma()) rparen() colon() return_type:name() body:block() {
|
||||
Function { span: start.through(body.span), name, parameters, return_type, body }
|
||||
}
|
||||
|
||||
// 函数参数或类字段共用名称/类型名节点,语法只保留注解文本而不解析类型身份。
|
||||
rule parameter() -> Parameter
|
||||
= name:name() colon() type_name:name() {
|
||||
Parameter { span: name.span.through(type_name.span), name, type_name }
|
||||
}
|
||||
|
||||
// function 和控制流共用块语法,普通语句后可接一个末尾 return;return 当前要求表达式。
|
||||
rule block() -> Block
|
||||
= start:lbrace() locals:local_statement()* returned:return_statement()? end:rbrace() {
|
||||
// 先保留普通语句序列,下面仅在存在末尾 return 时追加,保持源码顺序。
|
||||
let mut statements = locals;
|
||||
// 可选 return 不会被当作普通表达式消费,也不能后接其他块内语句。
|
||||
if let Some(returned) = returned { statements.push(returned); }
|
||||
Block { statements, span: start.through(end) }
|
||||
}
|
||||
|
||||
// 块内语句分派;成员赋值先于普通赋值,最后表达式分支显式排除 return 前缀。
|
||||
rule local_statement() -> Stmt
|
||||
= control:control_statement() { control }
|
||||
/ binding:binding() { binding_statement(binding) }
|
||||
@@ -113,26 +150,34 @@ peg::parser! {
|
||||
Stmt { span: expression.span, kind: StmtKind::Expression(expression) }
|
||||
}
|
||||
|
||||
// 交互或块内解析 if/else、while,条件和嵌套 block 都保留为语法树,尚不推断初始化。
|
||||
rule control_statement() -> Stmt
|
||||
= start:keyword("if") condition:expression() body:block()
|
||||
otherwise:(keyword("else") body:block() { body })? {
|
||||
// if 范围有 else 时延伸到 else 块末尾,否则只延伸到 then 块末尾。
|
||||
let span=start.through(otherwise.as_ref().map_or(body.span,|b|b.span));
|
||||
Stmt {span,kind:StmtKind::If(IfStatement {condition,body,otherwise,span})}
|
||||
}
|
||||
/ start:keyword("while") condition:expression() body:block() {
|
||||
// while 范围覆盖关键字、条件及整个循环体。
|
||||
let span=start.through(body.span);
|
||||
Stmt {span,kind:StmtKind::While(WhileStatement {condition,body,span})}
|
||||
}
|
||||
|
||||
// 交互或局部普通赋值;允许语法上未声明目标,具体全局/局部规则由 compiler::analyze 决定。
|
||||
rule assignment() -> Stmt
|
||||
= name:name() equal() value:expression() {
|
||||
// 赋值语句的原文范围从目标名延伸到右值末尾。
|
||||
let span = name.span.through(value.span);
|
||||
Stmt { span, kind: StmtKind::Assignment(Assignment { name, value, span }) }
|
||||
}
|
||||
|
||||
// 先解析后缀目标再检查其最外层确为 Member;错误目标使本规则失败,不执行对象写入。
|
||||
rule member_assignment() -> Stmt
|
||||
= target:postfix_expression() equal() value:expression() {?
|
||||
// 属性赋值保留整个接收对象/属性链及右值的原文范围。
|
||||
let span = target.span.through(value.span);
|
||||
// 只有成员访问可拆成接收对象与字段名,裸名字留给普通赋值分支识别。
|
||||
match target.kind {
|
||||
ExprKind::Member { object, name } => Ok(Stmt {
|
||||
span, kind: StmtKind::MemberAssignment(MemberAssignment { object: *object, name, value, span }),
|
||||
@@ -141,18 +186,23 @@ peg::parser! {
|
||||
}
|
||||
}
|
||||
|
||||
// 交互/局部 let 绑定,范围包括 let 关键字;重复名和右值初始化检查在后续语义分析。
|
||||
rule binding() -> Binding
|
||||
= start:let_keyword() name:name() equal() value:expression() {
|
||||
Binding { span: start.through(value.span), name, value }
|
||||
}
|
||||
|
||||
// block 的可选末尾返回,语法要求一个表达式,构造 ReturnStatement 的 Some 值。
|
||||
rule return_statement() -> Stmt
|
||||
= start:return_keyword() value:expression() {
|
||||
// return 范围从关键字到返回表达式末尾,包括其间空白。
|
||||
let span = start.through(value.span);
|
||||
Stmt { span, kind: StmtKind::Return(ReturnStatement { value: Some(value), span }) }
|
||||
}
|
||||
|
||||
// parse_expression 公开入口及各子表达式共用;precedence 层由低到高组织,二元操作在同层左结合。
|
||||
pub rule expression() -> Expr = precedence! {
|
||||
// 比较层优先级最低;六种比较均复用 binary 包装左右节点和范围。
|
||||
left:(@) [Token {kind:TokenKind::EqualEqual,..}] right:@ {binary(left,BinaryOp::Equal,right)}
|
||||
left:(@) [Token {kind:TokenKind::NotEqual,..}] right:@ {binary(left,BinaryOp::NotEqual,right)}
|
||||
left:(@) [Token {kind:TokenKind::Less,..}] right:@ {binary(left,BinaryOp::Less,right)}
|
||||
@@ -160,42 +210,55 @@ peg::parser! {
|
||||
left:(@) [Token {kind:TokenKind::Greater,..}] right:@ {binary(left,BinaryOp::Greater,right)}
|
||||
left:(@) [Token {kind:TokenKind::GreaterEqual,..}] right:@ {binary(left,BinaryOp::GreaterEqual,right)}
|
||||
--
|
||||
// 加减层高于比较、低于乘除,同层按左结合嵌套。
|
||||
left:(@) plus() right:@ { binary(left, BinaryOp::Add, right) }
|
||||
left:(@) minus() right:@ { binary(left, BinaryOp::Subtract, right) }
|
||||
--
|
||||
// 乘除层先构造节点,再交给外围的加减/比较层。
|
||||
left:(@) star() right:@ { binary(left, BinaryOp::Multiply, right) }
|
||||
left:(@) slash() right:@ { binary(left, BinaryOp::Divide, right) }
|
||||
--
|
||||
// 前缀 await 构造等待节点,是否允许停放由执行上下文决定。
|
||||
start:keyword("await") operand:@ {
|
||||
Expr { span: start.through(operand.span), kind: ExprKind::Await(Box::new(operand)) }
|
||||
}
|
||||
// 前缀取负包住当前高优先级操作数,不在语法阶段计算数值。
|
||||
start:minus() operand:@ {
|
||||
Expr { span: start.through(operand.span), kind: ExprKind::Unary { operation: UnaryOp::Negate, operand: Box::new(operand) } }
|
||||
}
|
||||
--
|
||||
// 后缀表达式位于最高优先级层,先形成调用/成员链,再被一元或二元节点包裹。
|
||||
expression:postfix_expression() { expression }
|
||||
}
|
||||
|
||||
// 优先级最高的 primary 后接零或多个成员/调用后缀,从左到右 fold 保持链式求值结构。
|
||||
rule postfix_expression() -> Expr
|
||||
= expression:primary() suffixes:suffix()* {
|
||||
// parser::postfix 每次把先前结果作为接收对象或被调用表达式,构造 a.b(c).d 一类链。
|
||||
suffixes.into_iter().fold(expression, postfix)
|
||||
}
|
||||
|
||||
// postfix_expression 共用后缀;点后取 Name,调用保存参数列表及右括号范围。
|
||||
rule suffix() -> Suffix
|
||||
= dot() name:name() { Suffix::Member(name) }
|
||||
/ lparen() arguments:(expression() ** comma()) end:rparen() { Suffix::Call(arguments, end) }
|
||||
|
||||
// 原子表达式:括号、符号、数字、字符串或名称;AST 文本均从匹配 Token 原文复制/解码。
|
||||
rule primary() -> Expr
|
||||
= start:lparen() expression:expression() end:rparen() {
|
||||
// 括号不新增语义节点种类,只把子表达式范围扩大到两侧括号。
|
||||
Expr { span: start.through(end), ..expression }
|
||||
}
|
||||
/ start:dot() name:name() {
|
||||
// 点开头的符号与成员后缀不同;前端保留它,编译器当前尚不支持生成该运行时值。
|
||||
Expr { span: start.through(name.span), kind: ExprKind::Symbol(name) }
|
||||
}
|
||||
/ [Token { kind: TokenKind::Number, span }] {
|
||||
// 数值保存原始十进制拼写,精确解析与负号处理留给 codegen。
|
||||
Expr { span, kind: ExprKind::Number(source[span.start..span.end].into()) }
|
||||
}
|
||||
/ [Token { kind: TokenKind::String, span }] {
|
||||
// Token 跨度含引号,去掉引号后调用已验证转义的 decode_string,AST 保存解码结果。
|
||||
Expr { span, kind: ExprKind::String(decode_string(&source[span.start + 1..span.end - 1])) }
|
||||
}
|
||||
/ name:name() { Expr { span: name.span, kind: ExprKind::Name(name.text) } }
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
// lexer.rs 编译时引入,生成 lexical::next;输入为本轮尚未消费的 UTF-8 后缀,所有 position! 都相对这个后缀。
|
||||
peg::parser! {
|
||||
// Lexer::next 调用的字符串扫描规则;每轮消费 trivia 后至多一个 Token。
|
||||
grammar lexical() for str {
|
||||
// next 首先跳过 ASCII 空白和双斜杠行注释,不产生 Token;注释在 CR/LF 前结束。
|
||||
rule trivia()
|
||||
= ([' ' | '\t' | '\r' | '\n'] / "//" [^'\r' | '\n']*)*
|
||||
|
||||
// name 首字符允许下划线或 Unicode 字母,不允许数字开头。
|
||||
rule name_start()
|
||||
= [character if character == '_' || character.is_alphabetic()]
|
||||
|
||||
// name 后续字符允许下划线或 Unicode 字母/数字;number 也用它识别非法黏连后缀。
|
||||
rule name_continue()
|
||||
= [character if character == '_' || character.is_alphanumeric()]
|
||||
|
||||
// kind 的名称分支;关键字也返回 Name,具体语法上下文稍后才识别。
|
||||
rule name() -> Result<TokenKind, Diagnostic>
|
||||
= name_start() name_continue()* { Ok(TokenKind::Name) }
|
||||
|
||||
// kind 的十进制数分支;无符号整数后可接点和至少一位小数,负号留给单独操作符。
|
||||
rule number() -> Result<TokenKind, Diagnostic>
|
||||
= start:position!() ['0'..='9']+ ("." ['0'..='9']+)?
|
||||
suffix:$(name_continue()*) end:position!() {
|
||||
// 数字后没有名称字符才是完整数 Token;否则将整段作为词法错误,避免误拆成两个合法 Token。
|
||||
if suffix.is_empty() {
|
||||
Ok(TokenKind::Number)
|
||||
} else {
|
||||
@@ -25,32 +33,42 @@ peg::parser! {
|
||||
}
|
||||
}
|
||||
|
||||
// string 只接受换行、制表、双引号、反斜杠四种反斜杠转义,解码在 parser 的 decode_string 中进行。
|
||||
rule escape()
|
||||
= "\\" ['n' | 't' | '"' | '\\']
|
||||
|
||||
// string 的普通字符不能是引号、反斜杠、原始换行或 NUL;其他 Unicode 字符原样消费。
|
||||
rule string_character()
|
||||
= [^'"' | '\\' | '\r' | '\n' | '\0']
|
||||
|
||||
// string 主体停止后检查闭引号或报告具体错误;start 是开引号位置,供未闭合字符串定位。
|
||||
rule string_end(start: usize) -> Result<(), Diagnostic>
|
||||
= "\"" { Ok(()) }
|
||||
// 主体已消费所有合法转义,剩余反斜杠加字符必是未支持的转义。
|
||||
/ position:position!() "\\" [_] end:position!() {
|
||||
Err(Diagnostic::lexical(Span::new(position, end), "unsupported string escape"))
|
||||
}
|
||||
// 反斜杠后直接 EOF,按从开引号到 EOF 的未闭合范围报告。
|
||||
/ "\\" end:position!() ![_] {
|
||||
Err(Diagnostic::lexical(Span::new(start, end), "unterminated string"))
|
||||
}
|
||||
// 原始换行/NUL 不可出现在字符串体内,定位具体违规字符。
|
||||
/ position:position!() ['\r' | '\n' | '\0'] end:position!() {
|
||||
Err(Diagnostic::lexical(Span::new(position, end), "unescaped newline or NUL in string"))
|
||||
}
|
||||
// 普通 EOF 也表示缺少闭引号,范围从开引号覆盖到输入结尾。
|
||||
/ end:position!() ![_] {
|
||||
Err(Diagnostic::lexical(Span::new(start, end), "unterminated string"))
|
||||
}
|
||||
|
||||
// kind 的字符串分支;扫描已验证主体及终结情况,Token 跨度仍包含引号并保持未解码原文。
|
||||
rule string() -> Result<TokenKind, Diagnostic>
|
||||
= start:position!() "\"" (escape() / string_character())* end:string_end(start) {
|
||||
// 闭引号验证通过才确认 String 种类,错误按值继续向上传递。
|
||||
end.map(|()| TokenKind::String)
|
||||
}
|
||||
|
||||
// kind 的操作符/分隔符分支;共享前缀的双字符比较必须放在单字符比较/赋值之前。
|
||||
rule punctuation() -> TokenKind
|
||||
= "(" { TokenKind::LeftParen }
|
||||
/ ")" { TokenKind::RightParen }
|
||||
@@ -71,6 +89,7 @@ peg::parser! {
|
||||
/ "*" { TokenKind::Star }
|
||||
/ "/" { TokenKind::Slash }
|
||||
|
||||
// token 按顺序尝试名称、数字、字符串、标点;未知字符作为错误值返回,避免吞掉后重试别种 Token。
|
||||
rule kind() -> Result<TokenKind, Diagnostic>
|
||||
= name()
|
||||
/ number()
|
||||
@@ -80,6 +99,7 @@ peg::parser! {
|
||||
Err(Diagnostic::lexical(Span::new(start, end), "unexpected source character"))
|
||||
}
|
||||
|
||||
// next 用此包装单词种类及相对字节范围;只在 kind 成功时生成 Token。
|
||||
rule token() -> Result<Token, Diagnostic>
|
||||
= start:position!() kind:kind() end:position!() {
|
||||
kind.map(|kind| Token { kind, span: Span::new(start, end) })
|
||||
@@ -87,9 +107,12 @@ peg::parser! {
|
||||
|
||||
// Scan only one prefix. Errors are returned as values, so a matched
|
||||
// malformed number/string cannot backtrack into another token kind.
|
||||
// 禁用默认整段消费检查,才能逐个前缀扫描;最终 EOF 由规则内的 ![_] 判断。
|
||||
#[no_eof]
|
||||
// Lexer::next 的唯一公开扫描入口;允许剩余后缀尚有内容,返回本轮消费字节数和 Token/EOF/诊断。
|
||||
pub rule next() -> (usize, Result<Option<Token>, Diagnostic>)
|
||||
= trivia() result:(token:token() { token.map(Some) } / ![_] { Ok(None) }) end:position!() {
|
||||
// end 包括前导 trivia 与当前 Token;Lexer 用它推进游标,再把结果跨度加回本轮原文起点。
|
||||
(end, result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,45 @@
|
||||
//! parser 及宿主分词入口;每次交给 PEG 从游标起的剩余源码后缀,再把相对跨度换回原文绝对字节位置。
|
||||
use crate::{Diagnostic, Span, Token, TokenKind};
|
||||
|
||||
/// Lex a reader snapshot and preserve provenance in any diagnostic.
|
||||
/// 宿主对 SourceUnit 分词时调用;失败以快照补来源及行列,成功 Token 仍仅存原文字节跨度。
|
||||
pub fn lex_source(source: &crate::SourceUnit) -> Result<Vec<Token>, crate::SourceDiagnostic> {
|
||||
lex(source.text()).map_err(|error| source.annotate(error))
|
||||
}
|
||||
|
||||
/// Lex the basic Endorphin draft syntax without reading files or changing
|
||||
/// locale. Positions always refer to bytes in this exact UTF-8 source.
|
||||
/// parser 的各语法入口及词法测试调用;只消费传入文本,Token 不持有源码字符串。
|
||||
pub fn lex(source: &str) -> Result<Vec<Token>, Diagnostic> {
|
||||
// 游标从原文零偏移开始,PEG 的每次扫描都在 offset 之后的后缀上进行。
|
||||
let mut lexer = Lexer { source, offset: 0 };
|
||||
// 按原输入顺序保存有效 Token,空白与行注释由词法规则跳过。
|
||||
let mut tokens = Vec::new();
|
||||
// next 返回 None 表示跳过末尾 trivia 后到 EOF;任一诊断立即中止整个分词。
|
||||
while let Some(token) = lexer.next()? {
|
||||
tokens.push(token);
|
||||
}
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
/// lex 内部的借用游标,原文保持不变,offset 始终对应已消费前缀的字节数。
|
||||
struct Lexer<'source> {
|
||||
// 原始完整 UTF-8 文本,供后缀扫描和全局错误位置计算。
|
||||
source: &'source str,
|
||||
// 下次扫描的原文起点,包含此前已跳过的空白/注释。
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl Lexer<'_> {
|
||||
/// lex 循环调用;分别处理 PEG 自身失败和词法动作返回的 Diagnostic,二者位置都换回全局偏移。
|
||||
fn next(&mut self) -> Result<Option<Token>, Diagnostic> {
|
||||
// 保存本轮起点,不能用后面已经前进的 offset 修正当前 Token 或错误。
|
||||
let base = self.offset;
|
||||
// PEG 返回消费字节数与 Result<Option<Token>>;规则错误作为值保留,避免回溯改认其他 Token。
|
||||
let (consumed, token) = lexical::next(&self.source[base..]).map_err(|error| {
|
||||
// PEG 自身报错位置相对于后缀,加 base 得到完整原文偏移。
|
||||
let start = base + error.location.offset;
|
||||
// 覆盖当前一个完整 UTF-8 字符;EOF 没有字符,使用空范围。
|
||||
let end = start
|
||||
+ self.source[start..]
|
||||
.chars()
|
||||
@@ -33,8 +47,10 @@ impl Lexer<'_> {
|
||||
.map_or(0, char::len_utf8);
|
||||
Diagnostic::lexical(Span::new(start, end), "unexpected source character")
|
||||
})?;
|
||||
// consumed 包括本轮 trivia 和 Token;词法错误随后返回给 lex,不继续扫描。
|
||||
self.offset += consumed;
|
||||
token
|
||||
// 成功且非 EOF 的 Token 才需要把两个端点加回原文起点。
|
||||
.map(|token| {
|
||||
token.map(|mut token| {
|
||||
token.span.start += base;
|
||||
@@ -42,6 +58,7 @@ impl Lexer<'_> {
|
||||
token
|
||||
})
|
||||
})
|
||||
// 词法动作返回的错误同样使用后缀坐标;转换后与 Token 遵循同一位置协议。
|
||||
.map_err(|mut error| {
|
||||
error.span.start += base;
|
||||
error.span.end += base;
|
||||
@@ -50,11 +67,17 @@ impl Lexer<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// grammar.peg 的字符串 primary 调用,raw 已去掉引号且转义由 lexer.peg 验证,只解码四种受支持转义。
|
||||
pub(crate) fn decode_string(raw: &str) -> String {
|
||||
// AST 持有解码文本,和 Token 所指向的带引号原文分开保存。
|
||||
let mut result = String::new();
|
||||
// 按 Unicode 字符遍历,普通多字节字符不做额外转换。
|
||||
let mut characters = raw.chars();
|
||||
// 遇反斜杠消耗下一个转义字符,其他字符原样追加。
|
||||
while let Some(character) = characters.next() {
|
||||
// lexer 已保证反斜杠后有合法转义;缺字符或未知字符表示内部阶段契约被破坏。
|
||||
if character == '\\' {
|
||||
// 只支持换行、制表、双引号和反斜杠,不解释额外 Unicode/数字转义。
|
||||
result.push(match characters.next().expect("lexer validated escape") {
|
||||
'n' => '\n',
|
||||
't' => '\t',
|
||||
@@ -69,4 +92,5 @@ pub(crate) fn decode_string(raw: &str) -> String {
|
||||
result
|
||||
}
|
||||
|
||||
// 编译时引入 lexical::next 的 rust-peg 规则实现;运行时由 Lexer::next 调用。
|
||||
include!("lexer.peg");
|
||||
|
||||
@@ -1,23 +1,37 @@
|
||||
//! 宿主、编译器与测试共用的前端门面:读取不可变源码快照,分词并构造拥有文本的 AST。
|
||||
//! UTF-8 source reader and rust-peg rules for lexing and token-based parsing.
|
||||
//! Produces syntax only; it does not resolve types, generate code or execute VM
|
||||
//! operations. AST data owns its text and does not borrow the source buffer.
|
||||
|
||||
// 公开语法树、访问器及 JSON 文档接口,供编译器分析和编辑器传输使用。
|
||||
pub mod ast;
|
||||
// 统一维护半开字节跨度、Unicode 行列及阶段错误,避免词法/语法各自计算位置。
|
||||
mod diagnostic;
|
||||
// 把 UTF-8 文本转成 Token;rust-peg 词法规则由该模块引入。
|
||||
mod lexer;
|
||||
// 把 Token 序列转成模块、表达式或交互根,不执行类型解析和 VM 操作。
|
||||
mod parser;
|
||||
// 从文本、流、文件或宿主提供的 endor URI 读取原样 UTF-8 快照。
|
||||
mod reader;
|
||||
// 保存源码来源/版本标签及文本,错误附加来源和已经计算的行列位置。
|
||||
mod source;
|
||||
// Token 只存种类与原文跨度,具体文本由解析器从源码读取。
|
||||
mod token;
|
||||
|
||||
// 宿主和编译器可沿用同一套位置与诊断数据结构。
|
||||
pub use diagnostic::{Diagnostic, DiagnosticKind, SourceLocation, Span};
|
||||
// 同时公开裸文本和保留 SourceUnit 来源的分词入口。
|
||||
pub use lexer::{lex, lex_source};
|
||||
// 公开各语法根及来源包装入口;ParseMode 选择语法单位,不维护 REPL 会话。
|
||||
pub use parser::{
|
||||
ParseMode, parse, parse_ast, parse_expression, parse_source, parse_source_ast,
|
||||
parse_source_expression,
|
||||
};
|
||||
// 宿主实现 EndorSourceProvider 掌握 URI 授权与版本选择,前端只消费返回字节。
|
||||
pub use reader::{
|
||||
EndorReader, EndorSourceProvider, ResolvedEndorSource, SourceReadError, SourceReadErrorKind,
|
||||
};
|
||||
// 源码快照和带来源诊断供跨读取/解析/编译阶段传递。
|
||||
pub use source::{SourceDiagnostic, SourceOrigin, SourceUnit};
|
||||
// 公开词法输出,供解析测试及直接 Token 使用者检查种类与字节位置。
|
||||
pub use token::{Token, TokenKind};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//! compiler 与宿主使用的 Token 解析门面;grammar.peg 构造拥有文本的 AST,类型名及名称绑定留待编译分析。
|
||||
use crate::ast::{
|
||||
Assignment, AstRoot, BinaryOp, Binding, Block, Class, Decl, DeclKind, Expr, ExprKind, Function,
|
||||
IfStatement, InteractiveInput, MemberAssignment, Name, Parameter, Program, ReturnStatement,
|
||||
@@ -8,17 +9,25 @@ use crate::{Diagnostic, DiagnosticKind, Span, Token, TokenKind, lex};
|
||||
|
||||
/// Select the syntax root without implying any execution or REPL session.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// parse_ast/parse_source_ast 调用方选择的语法根,不表示开启执行器或 REPL 状态。
|
||||
pub enum ParseMode {
|
||||
// 一个或多个顶层函数/类声明。
|
||||
Module,
|
||||
// 恰好一个完整表达式,不能附带其他语句。
|
||||
Expression,
|
||||
// 一个完整的交互语句,可为声明、绑定、赋值、控制流或表达式。
|
||||
Interactive,
|
||||
}
|
||||
|
||||
/// compile_source 和语法测试调用,按 mode 返回 AstRoot;词法失败先返回,语法失败转换为原文跨度诊断。
|
||||
pub fn parse_ast(source: &str, mode: ParseMode) -> Result<AstRoot, Diagnostic> {
|
||||
// 模块/表达式复用专门入口,交互入口直接调用对应 PEG 根规则。
|
||||
match mode {
|
||||
ParseMode::Module => parse(source).map(AstRoot::Module),
|
||||
ParseMode::Expression => parse_expression(source).map(AstRoot::Expression),
|
||||
// 语法只收一个完整提交,不负责多次提交共享环境或未完成输入提示。
|
||||
ParseMode::Interactive => {
|
||||
// 先分词,保留 Token 的原文跨度,供语法错误从 Token 索引映射回字节。
|
||||
let tokens = lex(source)?;
|
||||
syntax::interactive(&tokens, source)
|
||||
.map(AstRoot::Interactive)
|
||||
@@ -27,6 +36,7 @@ pub fn parse_ast(source: &str, mode: ParseMode) -> Result<AstRoot, Diagnostic> {
|
||||
}
|
||||
}
|
||||
|
||||
/// compiler 与宿主的快照入口;语法根由 mode 决定,错误增加该快照的来源和行列。
|
||||
pub fn parse_source_ast(
|
||||
source: &crate::SourceUnit,
|
||||
mode: ParseMode,
|
||||
@@ -35,10 +45,12 @@ pub fn parse_source_ast(
|
||||
}
|
||||
|
||||
/// Parse a reader snapshot, retaining its origin and resolved revision on error.
|
||||
/// 宿主从 Reader 快照解析模块时调用,保留来源及 Endor 版本身份到错误包装。
|
||||
pub fn parse_source(source: &crate::SourceUnit) -> Result<Program, crate::SourceDiagnostic> {
|
||||
parse(source.text()).map_err(|error| source.annotate(error))
|
||||
}
|
||||
|
||||
/// 宿主从快照解析一个表达式时调用,与 parse_expression 共享语法,仅增加错误来源。
|
||||
pub fn parse_source_expression(
|
||||
source: &crate::SourceUnit,
|
||||
) -> Result<Expr, crate::SourceDiagnostic> {
|
||||
@@ -47,21 +59,28 @@ pub fn parse_source_expression(
|
||||
|
||||
/// Parse one or more typed function or class declarations. This is a syntax
|
||||
/// operation: type names and name references are retained without resolution.
|
||||
/// parse_ast 的 Module 分支、parse_source 和语法测试调用;只构造声明树,不注册 VM 全局值。
|
||||
pub fn parse(source: &str) -> Result<Program, Diagnostic> {
|
||||
// 词法诊断原样传播;语法 PEG 处理的是 Token 序列,不直接按 UTF-8 字节推进。
|
||||
let tokens = lex(source)?;
|
||||
syntax::program(&tokens, source).map_err(|error| diagnostic(error, &tokens, source.len()))
|
||||
}
|
||||
|
||||
/// Parse exactly one complete expression, useful independently of declarations.
|
||||
/// parse_ast 的 Expression 分支、快照包装及表达式测试调用;要求整份 Token 输入被消费。
|
||||
pub fn parse_expression(source: &str) -> Result<Expr, Diagnostic> {
|
||||
// 词法诊断原样传播;语法 PEG 处理的是 Token 序列,不直接按 UTF-8 字节推进。
|
||||
let tokens = lex(source)?;
|
||||
syntax::expression(&tokens, source).map_err(|error| diagnostic(error, &tokens, source.len()))
|
||||
}
|
||||
|
||||
/// 各 PEG 语法根的 map_err 调用,把失败 Token 索引映射为原文范围,并保留期望项列表。
|
||||
fn diagnostic(error: peg::error::ParseError<usize>, tokens: &[Token], end: usize) -> Diagnostic {
|
||||
// 命中 Token 则定位该 Token,EOF 或越界索引使用原文尾部空范围。
|
||||
let span = tokens
|
||||
.get(error.location)
|
||||
.map_or(Span::new(end, end), |token| token.span);
|
||||
// PEG 期望项转成拥有字符串,既写入可读消息,也保留供宿主程序化显示。
|
||||
let expected: Vec<String> = error.expected.tokens().map(str::to_owned).collect();
|
||||
Diagnostic {
|
||||
kind: DiagnosticKind::Syntax,
|
||||
@@ -71,6 +90,7 @@ fn diagnostic(error: peg::error::ParseError<usize>, tokens: &[Token], end: usize
|
||||
}
|
||||
}
|
||||
|
||||
/// grammar.peg 的交互绑定/局部绑定规则调用,复用 Binding 自身范围包装成语句节点。
|
||||
fn binding_statement(binding: Binding) -> Stmt {
|
||||
Stmt {
|
||||
span: binding.span,
|
||||
@@ -78,6 +98,7 @@ fn binding_statement(binding: Binding) -> Stmt {
|
||||
}
|
||||
}
|
||||
|
||||
/// grammar.peg 的优先级规则调用,把已解析左右操作数包装为二元节点;跨度覆盖两端及中间原文。
|
||||
fn binary(left: Expr, operation: BinaryOp, right: Expr) -> Expr {
|
||||
Expr {
|
||||
span: left.span.through(right.span),
|
||||
@@ -89,17 +110,25 @@ fn binary(left: Expr, operation: BinaryOp, right: Expr) -> Expr {
|
||||
}
|
||||
}
|
||||
|
||||
/// grammar.peg 暂存一个后缀,postfix 按源码从左到右把链式成员/调用折叠成 AST。
|
||||
enum Suffix {
|
||||
// 点后属性名节点,自身含原文字节跨度。
|
||||
Member(Name),
|
||||
// 参数表达式与右括号范围,后者用于延长整个调用节点的跨度。
|
||||
Call(Vec<Expr>, Span),
|
||||
}
|
||||
|
||||
/// grammar.peg 的 class_member 返回中间分类,再由 class 规则分别收集字段和方法。
|
||||
enum ClassMember {
|
||||
// 字段使用名称加类型名结构,与 Parameter 共用形状。
|
||||
Field(Parameter),
|
||||
// 类方法沿用函数 AST;显式 self 等约束由 compiler::analyze 检查。
|
||||
Method(Function),
|
||||
}
|
||||
|
||||
/// grammar.peg 的 postfix_expression fold 调用;每步把前一个结果作为新的接收对象或被调表达式。
|
||||
fn postfix(expression: Expr, suffix: Suffix) -> Expr {
|
||||
// 成员节点延长到属性名末尾,调用节点延长到右括号,保持连续后缀完整跨度。
|
||||
match suffix {
|
||||
Suffix::Member(name) => Expr {
|
||||
span: expression.span.through(name.span),
|
||||
@@ -118,4 +147,5 @@ fn postfix(expression: Expr, suffix: Suffix) -> Expr {
|
||||
}
|
||||
}
|
||||
|
||||
// 编译时引入 syntax 根规则;上述公开入口与 AST 构造辅助函数在规则中互相调用。
|
||||
include!("grammar.peg");
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//! 宿主及前端测试在词法处理前调用的无状态读取适配器;统一验证 UTF-8,原样保留字节解码后的文本与来源。
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
@@ -9,26 +10,34 @@ use crate::{SourceOrigin, SourceUnit};
|
||||
|
||||
/// The host resolves names, authorizes access and selects a revision before
|
||||
/// returning its source bytes. No URI path layout or hash algorithm is assumed.
|
||||
/// 宿主实现的 endor URI 解析协议;Reader 不内置仓库、网络、路径布局或版本算法。
|
||||
pub trait EndorSourceProvider {
|
||||
/// EndorReader::from_endor 在基础 URI 检查后调用;宿主负责授权、定位版本并返回该版本原始字节。
|
||||
fn load(&self, uri: &str) -> io::Result<ResolvedEndorSource>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// provider.load 的拥有型结果;Reader 验证非空版本标签及 UTF-8 后移入 SourceUnit。
|
||||
pub struct ResolvedEndorSource {
|
||||
// 所选版本的原始标签,前端仅检查 trim 后非空,不规范化或验证哈希。
|
||||
pub revision: String,
|
||||
// 该版本的源码字节,尚未验证 UTF-8,也未进行换行或编码转换。
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Stateless source adapters, before lexing. Streams are read to EOF; session
|
||||
/// prompting and incomplete REPL input detection belong to the interactive host.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
/// 宿主/测试共用的读取门面;每次调用独立产生快照,流读到 EOF,不保存 REPL 会话状态。
|
||||
pub struct EndorReader;
|
||||
|
||||
impl EndorReader {
|
||||
/// 宿主和编译测试提供现成 UTF-8 文本时调用;String 类型已保证编码有效,只包装来源与原文。
|
||||
pub fn from_text(name: impl Into<String>, text: impl Into<String>) -> SourceUnit {
|
||||
SourceUnit::new(SourceOrigin::Text { name: name.into() }, text.into())
|
||||
}
|
||||
|
||||
/// 宿主/测试把 Read 流读成一个快照;name 仅用于来源显示,读取失败保留流来源名。
|
||||
pub fn from_stream(
|
||||
name: impl Into<String>,
|
||||
reader: impl Read,
|
||||
@@ -36,11 +45,15 @@ impl EndorReader {
|
||||
Self::read(SourceOrigin::Stream { name: name.into() }, reader)
|
||||
}
|
||||
|
||||
/// 宿主/文件测试调用,先保存提供的路径身份再打开文件;打开/读取/解码错误均带相同来源。
|
||||
pub fn from_file(path: impl AsRef<Path>) -> Result<SourceUnit, SourceReadError> {
|
||||
// 借用调用方传入路径用于打开;来源另存拥有副本,不要求先规范化或转为绝对路径。
|
||||
let path = path.as_ref();
|
||||
// 在打开之前保存来源,连文件不存在或权限错误也能指明请求的路径。
|
||||
let origin = SourceOrigin::File {
|
||||
path: path.to_owned(),
|
||||
};
|
||||
// 打开失败属于 IO 错误;成功后与流读取共用 read 和 decode。
|
||||
let file = File::open(path).map_err(|error| SourceReadError {
|
||||
origin: origin.to_string(),
|
||||
kind: SourceReadErrorKind::Io(error),
|
||||
@@ -48,33 +61,39 @@ impl EndorReader {
|
||||
Self::read(origin, file)
|
||||
}
|
||||
|
||||
/// 宿主/URI 适配测试调用;前端做基础定位符检查,宿主 provider 选择版本,成功后才解码源码。
|
||||
pub fn from_endor<P: EndorSourceProvider + ?Sized>(
|
||||
uri: &str,
|
||||
provider: &P,
|
||||
) -> Result<SourceUnit, SourceReadError> {
|
||||
// Only recognize the scheme and reject empty/control-filled locators.
|
||||
// Namespace syntax, revision selectors and percent decoding are host policy.
|
||||
// 协议大小写不敏感,冒号后不能为空,整串不能含空白/控制字符;其余语义交给宿主。
|
||||
let valid = uri.split_once(':').is_some_and(|(scheme, address)| {
|
||||
scheme.eq_ignore_ascii_case("endor") && !address.is_empty()
|
||||
}) && !uri
|
||||
.chars()
|
||||
.any(|character| character.is_whitespace() || character.is_control());
|
||||
// 无效定位符先拒绝,不能调用 provider 触发不必要的外部访问。
|
||||
if !valid {
|
||||
return Err(SourceReadError {
|
||||
origin: uri.into(),
|
||||
kind: SourceReadErrorKind::InvalidEndorUri,
|
||||
});
|
||||
}
|
||||
// 基础检查通过后委托宿主,宿主解析失败统一包装为带原 URI 的 IO 错误。
|
||||
let resolved = provider.load(uri).map_err(|error| SourceReadError {
|
||||
origin: uri.into(),
|
||||
kind: SourceReadErrorKind::Io(error),
|
||||
})?;
|
||||
// 没有解析后的版本身份就不能构成稳定来源;仅检查,不把原标签改成 trim 后内容。
|
||||
if resolved.revision.trim().is_empty() {
|
||||
return Err(SourceReadError {
|
||||
origin: uri.into(),
|
||||
kind: SourceReadErrorKind::MissingRevision,
|
||||
});
|
||||
}
|
||||
// 保存原 URI 和 provider 返回的版本标签,诊断及生成代码文件名沿用它们。
|
||||
let origin = SourceOrigin::Endor {
|
||||
uri: uri.into(),
|
||||
revision: resolved.revision,
|
||||
@@ -82,7 +101,9 @@ impl EndorReader {
|
||||
Self::decode(origin, resolved.bytes)
|
||||
}
|
||||
|
||||
/// from_stream/from_file 共用;完整读到 EOF 才尝试 UTF-8 解码,中途失败不返回部分源码快照。
|
||||
fn read(origin: SourceOrigin, mut reader: impl Read) -> Result<SourceUnit, SourceReadError> {
|
||||
// 收集原始字节,不过滤 BOM、换行、空白或 NUL;编码和后续语法各按自己的阶段处理。
|
||||
let mut bytes = Vec::new();
|
||||
reader
|
||||
.read_to_end(&mut bytes)
|
||||
@@ -93,7 +114,9 @@ impl EndorReader {
|
||||
Self::decode(origin, bytes)
|
||||
}
|
||||
|
||||
/// read/from_endor 共用;只接受严格 UTF-8,不做有损替换,成功原样构造拥有快照。
|
||||
fn decode(origin: SourceOrigin, bytes: Vec<u8>) -> Result<SourceUnit, SourceReadError> {
|
||||
// String::from_utf8 失败保留 FromUtf8Error,来源显示中包含文件/流/已解析版本身份。
|
||||
let text = String::from_utf8(bytes).map_err(|error| SourceReadError {
|
||||
origin: origin.to_string(),
|
||||
kind: SourceReadErrorKind::InvalidUtf8(error),
|
||||
@@ -103,22 +126,32 @@ impl EndorReader {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Reader 的失败分类,区别外部 IO、文本编码及 URI/版本协议问题,尚未进入词法阶段。
|
||||
pub enum SourceReadErrorKind {
|
||||
// 文件打开、流读取或 provider.load 返回的底层 IO 原因。
|
||||
Io(io::Error),
|
||||
// 严格 UTF-8 解码失败,保留有效字节前缀位置及原始错误对象。
|
||||
InvalidUtf8(FromUtf8Error),
|
||||
// Reader 的基础 endor URI 检查不通过,没有调用 provider。
|
||||
InvalidEndorUri,
|
||||
// provider 成功返回字节但未提供非空版本标签,不能创建已解析来源。
|
||||
MissingRevision,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Reader 公开返回给宿主的错误,拥有来源显示文本与具体原因,可沿标准 Error 链追查底层失败。
|
||||
pub struct SourceReadError {
|
||||
// 错误发生时固化的来源字符串,不依赖失败的输入流或 SourceUnit 仍存在。
|
||||
pub origin: String,
|
||||
// 分类及可能的底层错误对象,供宿主程序化判断。
|
||||
pub kind: SourceReadErrorKind,
|
||||
}
|
||||
|
||||
impl fmt::Display for SourceReadError {
|
||||
/// 宿主显示错误时先输出来源,再按类别显示底层说明、无效 UTF-8 字节位置或协议错误。
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(formatter, "{}: ", self.origin)?;
|
||||
// IO/编码错误保留底层原因;URI 和缺版本是 Reader 自身协议错误。
|
||||
match &self.kind {
|
||||
SourceReadErrorKind::Io(error) => write!(formatter, "{error}"),
|
||||
SourceReadErrorKind::InvalidUtf8(error) => write!(
|
||||
@@ -137,7 +170,9 @@ impl fmt::Display for SourceReadError {
|
||||
}
|
||||
|
||||
impl Error for SourceReadError {
|
||||
/// 标准错误链查询入口;IO 与编码错误返回原始原因,Reader 协议错误没有下层异常。
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
// IO/编码错误保留底层原因;URI 和缺版本是 Reader 自身协议错误。
|
||||
match &self.kind {
|
||||
SourceReadErrorKind::Io(error) => Some(error),
|
||||
SourceReadErrorKind::InvalidUtf8(error) => Some(error),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//! Reader 产生 SourceUnit,lexer/parser/compiler 借用其文本;错误在 annotate 时固化来源及行列。
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
@@ -7,15 +8,22 @@ use crate::{Diagnostic, SourceLocation};
|
||||
/// Source provenance, independent of filesystem layout or VM heap handles.
|
||||
/// Endor revisions are opaque labels supplied by the host, not verified hashes.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// Reader 选择的来源身份,诊断和代码文件名显示使用;Endor revision 是宿主标签,不代表已验证哈希。
|
||||
pub enum SourceOrigin {
|
||||
// 内存文本的宿主显示名,不表示磁盘路径。
|
||||
Text { name: String },
|
||||
// 已读到 EOF 的字节流显示名,流本身不再存于快照。
|
||||
Stream { name: String },
|
||||
// 实际读取时保存的路径,不要求规范化为绝对路径。
|
||||
File { path: PathBuf },
|
||||
// 经宿主解析的 URI 与所选版本身份,前端不推断其命名空间或哈希算法。
|
||||
Endor { uri: String, revision: String },
|
||||
}
|
||||
|
||||
impl fmt::Display for SourceOrigin {
|
||||
/// 诊断与生成代码文件名调用 Display,按来源种类生成可读身份文本。
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// 文本/流显示名称,文件显示路径,Endor 来源附带版本标签。
|
||||
match self {
|
||||
Self::Text { name } | Self::Stream { name } => formatter.write_str(name),
|
||||
Self::File { path } => write!(formatter, "{}", path.display()),
|
||||
@@ -27,25 +35,33 @@ impl fmt::Display for SourceOrigin {
|
||||
/// An owned, immutable UTF-8 snapshot. Reader adapters preserve the text exactly
|
||||
/// so token byte offsets still refer to the original decoded source.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// EndorReader 构造的拥有型 UTF-8 快照;原文不规范化,以保持 Token 和 AST 字节跨度对应原输入。
|
||||
pub struct SourceUnit {
|
||||
// 来源身份与文本一起保存,供错误和代码元数据使用。
|
||||
origin: SourceOrigin,
|
||||
// 已验证 UTF-8 的原始文本,解析阶段只借用它,不修改换行或空白。
|
||||
text: String,
|
||||
}
|
||||
|
||||
impl SourceUnit {
|
||||
/// Reader 适配器完成解码后调用;将来源及文本移入快照,不再读取外部数据。
|
||||
pub(crate) fn new(origin: SourceOrigin, text: String) -> Self {
|
||||
Self { origin, text }
|
||||
}
|
||||
|
||||
/// 编译器生成文件名、宿主检查来源时借用身份,保留 Endor 的已解析版本。
|
||||
pub fn origin(&self) -> &SourceOrigin {
|
||||
&self.origin
|
||||
}
|
||||
|
||||
/// lexer/parser/compiler 及位置校验借用 UTF-8 文本,返回生命周期依附当前快照。
|
||||
pub fn text(&self) -> &str {
|
||||
&self.text
|
||||
}
|
||||
|
||||
/// Bind a diagnostic to this snapshot before discarding its source buffer.
|
||||
/// lex_source、parse_source 系列及 compile_source 在失败时调用,把字节诊断绑定到此来源和行列。
|
||||
/// 返回值保存来源、位置及原诊断,不保存源码全文;后续无需再次读源即可显示基本错误。
|
||||
pub fn annotate(&self, diagnostic: Diagnostic) -> SourceDiagnostic {
|
||||
SourceDiagnostic {
|
||||
location: diagnostic.location(&self.text),
|
||||
@@ -56,13 +72,18 @@ impl SourceUnit {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// 带来源及预计算位置的阶段错误,由 SourceUnit::annotate 创建,交给宿主显示或追查底层 Diagnostic。
|
||||
pub struct SourceDiagnostic {
|
||||
// 错误对应的来源,独立于原快照生命周期。
|
||||
pub origin: SourceOrigin,
|
||||
// annotate 当时根据原文计算的一基行列,后续 Display 不重新计算。
|
||||
pub location: SourceLocation,
|
||||
// 原始字节跨度、阶段、说明及期望项,保留给程序化处理与 Error::source。
|
||||
pub diagnostic: Box<Diagnostic>,
|
||||
}
|
||||
|
||||
impl fmt::Display for SourceDiagnostic {
|
||||
/// 宿主显示错误时输出“来源:行:列:说明”,跨度及期望项仍可从 diagnostic 单独读取。
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
formatter,
|
||||
@@ -73,6 +94,7 @@ impl fmt::Display for SourceDiagnostic {
|
||||
}
|
||||
|
||||
impl Error for SourceDiagnostic {
|
||||
/// 标准错误链使用者调用,暴露原始阶段诊断作为本错误的直接原因。
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
Some(self.diagnostic.as_ref())
|
||||
}
|
||||
|
||||
@@ -1,35 +1,61 @@
|
||||
//! lexer 产生、parser 消费的轻量 Token;文本通过 span 从原 SourceUnit 借取,不在 Token 中复制。
|
||||
use crate::Span;
|
||||
|
||||
/// Text is located by span in the original source. Names stay contextual: the
|
||||
/// lexer does not turn syntax words into globally reserved keywords.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// lexer.peg 输出的种类;语法关键字仍是 Name,由 grammar.peg 按原文上下文识别。
|
||||
pub enum TokenKind {
|
||||
// Unicode 名称,包含上下文关键字;这里只区分词形,不决定语义用途。
|
||||
Name,
|
||||
// 无符号十进制整数/小数拼写;负号是单独 Token,精确值由编译器构造。
|
||||
Number,
|
||||
/// Span includes quotes. Escapes have been checked, not decoded.
|
||||
// 含引号的字符串范围,转义已校验但未解码;解析时再生成拥有文本。
|
||||
String,
|
||||
// 左圆括号,用于参数、调用或括号表达式。
|
||||
LeftParen,
|
||||
// 右圆括号,调用范围在它之后结束。
|
||||
RightParen,
|
||||
// 左花括号,开始类或语句块。
|
||||
LeftBrace,
|
||||
// 右花括号,结束类或语句块。
|
||||
RightBrace,
|
||||
// 类型注解及继承声明的冒号。
|
||||
Colon,
|
||||
// 参数和实参间的分隔符。
|
||||
Comma,
|
||||
// 成员访问或符号字面量的点。
|
||||
Dot,
|
||||
// 单等号赋值,不与双等号比较混淆。
|
||||
Equal,
|
||||
// 双等号相等比较。
|
||||
EqualEqual,
|
||||
// 不等比较。
|
||||
NotEqual,
|
||||
// 小于比较。
|
||||
Less,
|
||||
// 小于等于比较。
|
||||
LessEqual,
|
||||
// 大于比较。
|
||||
Greater,
|
||||
// 大于等于比较。
|
||||
GreaterEqual,
|
||||
// 加法操作符。
|
||||
Plus,
|
||||
// 减法或一元取负,由语法上下文区分。
|
||||
Minus,
|
||||
// 乘法操作符。
|
||||
Star,
|
||||
// 除法操作符;双斜杠行注释在 trivia 阶段跳过。
|
||||
Slash,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// 一次词法输出;Token 可复制,但只保存种类和字节范围,调用方须保留匹配的原文以读取内容。
|
||||
pub struct Token {
|
||||
// parser 按此选择语法分支,名称关键字额外检查原文。
|
||||
pub kind: TokenKind,
|
||||
// 原始 UTF-8 输入的半开范围,不含前面被跳过的空白/注释。
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//! Rust 测试框架验证语法根、生成 Visitor、原文定位和版本化 JSON 接口,不执行编译后代码。
|
||||
use frontend::ast::visitor::{Visitor, walk_expr, walk_function};
|
||||
use frontend::ast::{
|
||||
AST_SCHEMA_ID, AstDocument, AstError, AstRoot, Expr, ExprKind, Function, Spanned, StmtKind,
|
||||
@@ -5,15 +6,19 @@ use frontend::ast::{
|
||||
use frontend::{EndorReader, ParseMode, Span, parse_ast, parse_source_ast};
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:块内允许多个调用表达式语句及无 return 空体,但显式 return 必须位于块末尾。
|
||||
fn function_bodies_accept_method_statements_and_optional_trailing_return() {
|
||||
// 仅解析指定模式的源码,取得拥有型 AstRoot,不分配 VM 对象。
|
||||
let root = parse_ast(
|
||||
"function click(): Unit { title.setText(\"changed\") card.setColor(\"#FFEEDD\") }",
|
||||
ParseMode::Module,
|
||||
)
|
||||
.unwrap();
|
||||
// 模块模式必须产出 Program,不能把声明错误地包装成表达式或交互根。
|
||||
let AstRoot::Module(program) = root else {
|
||||
panic!("expected module")
|
||||
};
|
||||
// 借用首个顶层函数,用于检查块内语句分类及顺序。
|
||||
let function = program.functions().next().unwrap();
|
||||
assert_eq!(function.body.statements.len(), 2);
|
||||
assert!(
|
||||
@@ -21,6 +26,7 @@ fn function_bodies_accept_method_statements_and_optional_trailing_return() {
|
||||
.body
|
||||
.statements
|
||||
.iter()
|
||||
// 两个调用都应是普通 Expression 语句,不能被误识别为返回或绑定。
|
||||
.all(|s| matches!(s.kind, StmtKind::Expression(_)))
|
||||
);
|
||||
assert!(parse_ast("function empty(): Unit {}", ParseMode::Module).is_ok());
|
||||
@@ -34,18 +40,25 @@ fn function_bodies_accept_method_statements_and_optional_trailing_return() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:核对模块/表达式/单条交互的根类型与语句种类,文档范围有效,来源诊断保留 Unicode 行列。
|
||||
fn parse_modes_produce_distinct_roots_and_preserve_source_diagnostics() {
|
||||
// 保留与 AST 跨度相对应的原始 UTF-8 文本,后续不规范化空白或换行。
|
||||
let source = "function echo(value: String): String { let copy = value return copy }";
|
||||
// 仅解析指定模式的源码,取得拥有型 AstRoot,不分配 VM 对象。
|
||||
let root = parse_ast(source, ParseMode::Module).unwrap();
|
||||
// 模块模式必须产出 Program,不能把声明错误地包装成表达式或交互根。
|
||||
let AstRoot::Module(program) = &root else {
|
||||
panic!("expected module")
|
||||
};
|
||||
// 借用首个顶层函数,用于检查块内语句分类及顺序。
|
||||
let function = program.functions().next().unwrap();
|
||||
assert_eq!(function.body.statements.len(), 2);
|
||||
// 第一条是 let Binding,不能与后面的 Return 混成同一语句种类。
|
||||
assert!(matches!(
|
||||
function.body.statements[0].kind,
|
||||
StmtKind::Binding(_)
|
||||
));
|
||||
// 第二条明确为末尾 Return,顺序应与源码一致。
|
||||
assert!(matches!(
|
||||
function.body.statements[1].kind,
|
||||
StmtKind::Return(_)
|
||||
@@ -55,10 +68,15 @@ fn parse_modes_produce_distinct_roots_and_preserve_source_diagnostics() {
|
||||
.validate_source(source)
|
||||
.unwrap();
|
||||
|
||||
// 仅解析指定模式的源码,取得拥有型 AstRoot,不分配 VM 对象。
|
||||
let root = parse_ast("echo(\"日\")", ParseMode::Expression).unwrap();
|
||||
// Expression 模式只能返回表达式根,不带交互语句包装。
|
||||
assert!(matches!(root, AstRoot::Expression(_)));
|
||||
// 一条 let、一次调用和一个函数声明分别构成交互提交,均应只含一个语句。
|
||||
for text in ["let value = \"日\"", "echo(value)", source] {
|
||||
// 仅解析指定模式的源码,取得拥有型 AstRoot,不分配 VM 对象。
|
||||
let root = parse_ast(text, ParseMode::Interactive).unwrap();
|
||||
// 解构交互根并借用其单条语句,避免只检查解析成功而忽略根形状。
|
||||
let AstRoot::Interactive(input) = &root else {
|
||||
panic!("expected interactive")
|
||||
};
|
||||
@@ -70,7 +88,9 @@ fn parse_modes_produce_distinct_roots_and_preserve_source_diagnostics() {
|
||||
}
|
||||
assert!(parse_ast("let x = 1 let y = 2", ParseMode::Interactive).is_err());
|
||||
assert!(parse_ast("", ParseMode::Interactive).is_err());
|
||||
// 给语法错误附会话来源身份,字符串中的日字使字节偏移与 Unicode 列不同。
|
||||
let source = EndorReader::from_text("<session:1>", "\"日\" + )");
|
||||
// 故意错误的快照解析必须返回带来源诊断,之后核对实际显示行列。
|
||||
let error = parse_source_ast(&source, ParseMode::Expression).unwrap_err();
|
||||
assert_eq!(error.location.line, 1);
|
||||
assert_eq!(error.location.column, 7);
|
||||
@@ -78,29 +98,41 @@ fn parse_modes_produce_distinct_roots_and_preserve_source_diagnostics() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:自定义访问器收集函数名与引用名,并验证子遍历提前失败后,成功进入的祖先节点退出钩子仍全部配平。
|
||||
fn generated_visitors_reach_names_and_unwind_node_hooks_on_error() {
|
||||
#[derive(Default)]
|
||||
/// 本测试的可变访问器状态;节点只借用,收集文本拥有副本,depth 跟踪默认节点钩子的配平。
|
||||
struct Names {
|
||||
// 自定义 visit_function 记录声明名,和表达式引用名分开。
|
||||
functions: Vec<String>,
|
||||
// visit_expr 仅收集 ExprKind::Name,不包含类型注解或属性名。
|
||||
references: Vec<String>,
|
||||
// enter/leave 计数,遍历结束或提前失败后都应回到零。
|
||||
depth: usize,
|
||||
// 为 true 时在第一个引用名处主动失败,用来检查错误回溯退出路径。
|
||||
stop: bool,
|
||||
}
|
||||
impl Visitor for Names {
|
||||
type Error = &'static str;
|
||||
/// 生成的默认 visit 方法成功进入节点时调用,记录当前已进入祖先数量。
|
||||
fn enter_node(&mut self, _span: Span) -> Result<(), Self::Error> {
|
||||
self.depth += 1;
|
||||
Ok(())
|
||||
}
|
||||
/// 默认 visit 在子遍历完成或失败后调用,撤销此前成功进入的节点计数。
|
||||
fn leave_node(&mut self) {
|
||||
self.depth -= 1;
|
||||
}
|
||||
/// 自定义函数访问入口先记录名称,再显式 walk_function 保持所有字段的递归访问。
|
||||
fn visit_function(&mut self, node: &Function) -> Result<(), Self::Error> {
|
||||
self.functions.push(node.name.text.clone());
|
||||
walk_function(self, node)
|
||||
}
|
||||
/// 自定义表达式访问入口只特殊处理引用名;其余结构继续由 walk_expr 分派。
|
||||
fn visit_expr(&mut self, node: &Expr) -> Result<(), Self::Error> {
|
||||
// 仅变量引用表达式进入收集/主动失败分支,字面量等节点仍照常递归。
|
||||
if let ExprKind::Name(name) = &node.kind {
|
||||
// 在调用 walk_expr 之前失败;默认祖先 visit 必须仍执行各自的 leave_node。
|
||||
if self.stop {
|
||||
return Err("stopped at reference");
|
||||
}
|
||||
@@ -109,16 +141,19 @@ fn generated_visitors_reach_names_and_unwind_node_hooks_on_error() {
|
||||
walk_expr(self, node)
|
||||
}
|
||||
}
|
||||
// 仅解析指定模式的源码,取得拥有型 AstRoot,不分配 VM 对象。
|
||||
let root = parse_ast(
|
||||
"function echo(value: String): String { let copy = value return copy }",
|
||||
ParseMode::Module,
|
||||
)
|
||||
.unwrap();
|
||||
// 正常遍历从空状态开始,检查名称收集顺序及最终深度。
|
||||
let mut names = Names::default();
|
||||
names.visit_ast_root(&root).unwrap();
|
||||
assert_eq!(names.functions, ["echo"]);
|
||||
assert_eq!(names.references, ["value", "copy"]);
|
||||
assert_eq!(names.depth, 0);
|
||||
// 第二份访问器主动终止在引用名,不能复用正常访问器掩盖深度泄漏。
|
||||
let mut stopped = Names {
|
||||
stop: true,
|
||||
..Names::default()
|
||||
@@ -128,10 +163,14 @@ fn generated_visitors_reach_names_and_unwind_node_hooks_on_error() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:括号范围对应原文,CRLF 按一行、Unicode 按一列;安全定位拒绝逆序、越界及切断字符的范围。
|
||||
fn spans_share_unicode_locations_and_safe_original_source_access() {
|
||||
// 保留与 AST 跨度相对应的原始 UTF-8 文本,后续不规范化空白或换行。
|
||||
let source = " \r\n(値)";
|
||||
// 仅解析指定模式的源码,取得拥有型 AstRoot,不分配 VM 对象。
|
||||
let root = parse_ast(source, ParseMode::Expression).unwrap();
|
||||
assert_eq!(root.source_text(source), Some("(値)"));
|
||||
// 用同一原文把根节点的字节范围转换成一基行列,CRLF 只增加一行。
|
||||
let location = root.location(source).unwrap();
|
||||
assert_eq!((location.line, location.column), (2, 1));
|
||||
assert_eq!(Span::new(1, 3).slice("値"), None);
|
||||
@@ -140,7 +179,9 @@ fn spans_share_unicode_locations_and_safe_original_source_access() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:三个语法根 JSON 往返不丢文本/数值拼写,文档 schema 与生成的 JSON Schema/TypeScript 标识一致。
|
||||
fn versioned_json_roundtrips_all_roots_and_preserves_number_spelling() {
|
||||
// 分别覆盖含长十进制的模块、含符号/转义字符串的表达式和交互绑定。
|
||||
for (source, mode) in [
|
||||
(
|
||||
"function f(): Number { return 00012345678901234567890.00100 }",
|
||||
@@ -149,16 +190,21 @@ fn versioned_json_roundtrips_all_roots_and_preserves_number_spelling() {
|
||||
("f(.名前, \"日\\n\")", ParseMode::Expression),
|
||||
("let value = 1", ParseMode::Interactive),
|
||||
] {
|
||||
// 用当前 schema 包装合法解析树并验证结构,为后续往返或定点破坏提供基线。
|
||||
let document = AstDocument::new(parse_ast(source, mode).unwrap()).unwrap();
|
||||
// 序列化前接口再次验证文档;输出应保留 schema、节点种类和原文字节范围。
|
||||
let json = document.to_json().unwrap();
|
||||
// 解码后重新验证文档结构,结果应与原文档完整相等。
|
||||
let restored = AstDocument::from_json(&json).unwrap();
|
||||
assert_eq!(restored, document);
|
||||
restored.validate_source(source).unwrap();
|
||||
assert!(json.contains(AST_SCHEMA_ID));
|
||||
// 模块案例包含带前导零和尾零的长数值,JSON 必须保留原拼写而非转浮点数。
|
||||
if mode == ParseMode::Module {
|
||||
assert!(json.contains("00012345678901234567890.00100"));
|
||||
}
|
||||
}
|
||||
// 读取仓库已生成的 JSON Schema,只检查标识与常量,不在测试中重新生成文件。
|
||||
let schema: serde_json::Value =
|
||||
serde_json::from_str(include_str!("../editor/ast.schema.json")).unwrap();
|
||||
assert_eq!(schema["$id"], AST_SCHEMA_ID);
|
||||
@@ -170,39 +216,50 @@ fn versioned_json_roundtrips_all_roots_and_preserves_number_spelling() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:分别拒绝错误 schema、未知字段、子节点越界、逆序范围以及对原文切断 UTF-8 字符的范围。
|
||||
fn json_interfaces_reject_unknown_fields_schema_changes_and_invalid_ranges() {
|
||||
// 保留与 AST 跨度相对应的原始 UTF-8 文本,后续不规范化空白或换行。
|
||||
let source = "(f(値))";
|
||||
// 用当前 schema 包装合法解析树并验证结构,为后续往返或定点破坏提供基线。
|
||||
let document = AstDocument::new(parse_ast(source, ParseMode::Expression).unwrap()).unwrap();
|
||||
// 用可编辑 JSON 值破坏一个属性;每个错误案例之后重新从合法 document 创建。
|
||||
let mut value = serde_json::to_value(&document).unwrap();
|
||||
// 首先只破坏版本身份,应在 schema 比较阶段报告 SchemaMismatch。
|
||||
value["schema"] = "urn:endor:ast:unknown".into();
|
||||
assert!(matches!(
|
||||
AstDocument::from_json(&value.to_string()),
|
||||
Err(AstError::SchemaMismatch { .. })
|
||||
));
|
||||
value = serde_json::to_value(&document).unwrap();
|
||||
// 恢复合法 JSON 后增加未知字段,生成类型的 deny_unknown_fields 应在解码阶段拒绝。
|
||||
value["root"]["data"]["unexpected"] = true.into();
|
||||
assert!(matches!(
|
||||
AstDocument::from_json(&value.to_string()),
|
||||
Err(AstError::Json(_))
|
||||
));
|
||||
value = serde_json::to_value(&document).unwrap();
|
||||
// 再次恢复后缩小父范围,子节点仍在原位置,应报告 ChildOutsideParent。
|
||||
value["root"]["data"]["span"]["end"] = 1.into();
|
||||
assert!(matches!(
|
||||
AstDocument::from_json(&value.to_string()),
|
||||
Err(AstError::ChildOutsideParent { .. })
|
||||
));
|
||||
// 取出合法表达式后直接破坏其范围,验证 new 也拒绝无效手工构造树。
|
||||
let AstRoot::Expression(mut expression) = document.root else {
|
||||
unreachable!()
|
||||
};
|
||||
// 无需原文也能识别 end 小于 start 的逆序范围。
|
||||
expression.span = Span::new(5, 4);
|
||||
assert!(matches!(
|
||||
AstDocument::new(AstRoot::Expression(expression)),
|
||||
Err(AstError::InvalidSpan(_))
|
||||
));
|
||||
// 无原文时范围本身合法,但放回三字节字符原文时起点切断 UTF-8,应由 validate_source 拒绝。
|
||||
let root = AstRoot::Expression(Expr {
|
||||
kind: ExprKind::Name("値".into()),
|
||||
span: Span::new(1, 3),
|
||||
});
|
||||
// 结构范围虽非逆序,起点切断三字节日文字符,仍须在提供原文的校验阶段失败。
|
||||
assert!(matches!(
|
||||
AstDocument::new(root).unwrap().validate_source("値"),
|
||||
Err(AstError::InvalidSpan(_))
|
||||
@@ -210,27 +267,37 @@ fn json_interfaces_reject_unknown_fields_schema_changes_and_invalid_ranges() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:普通赋值使用独立 AST 节点并保留 Unicode 原文;链式赋值和非成员调用目标赋值语法失败。
|
||||
fn bare_assignment_has_its_own_ast_and_unicode_source_ranges() {
|
||||
// 保留与 AST 跨度相对应的原始 UTF-8 文本,后续不规范化空白或换行。
|
||||
let source = "标题 = ui.text(\"你好\")";
|
||||
// 仅解析指定模式的源码,取得拥有型 AstRoot,不分配 VM 对象。
|
||||
let root = parse_ast(source, ParseMode::Interactive).unwrap();
|
||||
// 解构交互根并借用其单条语句,避免只检查解析成功而忽略根形状。
|
||||
let AstRoot::Interactive(input) = &root else {
|
||||
panic!("interactive input")
|
||||
};
|
||||
// 普通赋值不是 let Binding,也不是成员赋值;名称节点和整句范围都要保持原文。
|
||||
let StmtKind::Assignment(assignment) = &input.statements[0].kind else {
|
||||
panic!("assignment")
|
||||
};
|
||||
assert_eq!(assignment.name.text, "标题");
|
||||
assert_eq!(assignment.name.source_text(source), Some("标题"));
|
||||
assert_eq!(assignment.source_text(source), Some(source));
|
||||
// 用当前 schema 包装合法解析树并验证结构,为后续往返或定点破坏提供基线。
|
||||
let document = AstDocument::new(root).unwrap();
|
||||
document.validate_source(source).unwrap();
|
||||
// 序列化前接口再次验证文档;输出应保留 schema、节点种类和原文字节范围。
|
||||
let json = document.to_json().unwrap();
|
||||
assert_eq!(AstDocument::from_json(&json).unwrap(), document);
|
||||
assert!(parse_ast("value = 1 = 2", ParseMode::Interactive).is_err());
|
||||
// 单独解析属性写入,确保普通赋值的新增路径没有抢走 MemberAssignment。
|
||||
let member = parse_ast("object.field = 1", ParseMode::Interactive).unwrap();
|
||||
// 必须检查节点类别,不能只根据字符串包含点就假定走了成员赋值规则。
|
||||
let AstRoot::Interactive(member) = member else {
|
||||
panic!("interactive")
|
||||
};
|
||||
// 属性写入使用 MemberAssignment,不能退化为普通目标名赋值。
|
||||
assert!(matches!(
|
||||
&member.statements[0].kind,
|
||||
StmtKind::MemberAssignment(_)
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
//! Rust 测试框架直接验证 lex 的原文偏移与 Token 边界,不进入语法分析、编译或 VM。
|
||||
use frontend::{DiagnosticKind, Span, TokenKind, lex};
|
||||
|
||||
#[test]
|
||||
/// 验证多 Token 和 Unicode 前缀之后的错误仍定位原文绝对字节范围,并区分数字/转义/换行/EOF/未知字符失败。
|
||||
fn lexical_errors_keep_original_byte_ranges_after_multiple_tokens() {
|
||||
// 固定有效前缀含多字节名称,防止只在零偏移或 ASCII 输入上通过的错误位置实现。
|
||||
let prefix = "let 商品 = ";
|
||||
// 各尾部给出违规片段及准确错误说明,包含 Unicode 转义、原始 CR/LF/NUL 与两种未闭合结尾。
|
||||
for (tail, offending, message) in [
|
||||
(
|
||||
"12.50名前 ;",
|
||||
@@ -19,8 +23,11 @@ fn lexical_errors_keep_original_byte_ranges_after_multiple_tokens() {
|
||||
("\"未完\\", "\"未完\\", "unterminated string"),
|
||||
("🌸 ;", "🌸", "unexpected source character"),
|
||||
] {
|
||||
// 把合法前缀与错误尾部拼成真实输入,后续位置必须指向完整原文。
|
||||
let source = format!("{prefix}{tail}");
|
||||
// 分词必须失败,不允许回溯把非法数字/字符串拆成若干合法 Token。
|
||||
let error = lex(&source).unwrap_err();
|
||||
// 从完整原文查找预期片段,用字节长度构造期望半开范围。
|
||||
let start = source.find(offending).unwrap();
|
||||
assert_eq!(error.kind, DiagnosticKind::Lexical, "{source:?}");
|
||||
assert_eq!(error.span, Span::new(start, start + offending.len()));
|
||||
@@ -30,9 +37,13 @@ fn lexical_errors_keep_original_byte_ranges_after_multiple_tokens() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证注释与字符串中的双斜杠、成员点、除号和小数边界互不混淆,空白/注释输入不产生 Token。
|
||||
fn token_boundaries_distinguish_members_division_comments_and_string_contents() {
|
||||
// 原文混合 CRLF、Unicode、行注释中的 NUL,以及字符串内必须保留的双斜杠。
|
||||
let source = " \t// 前置\r\n商品.価格 / 12.50// 行末\0\n\"//実文\" \r\n// 終端";
|
||||
// lex 跳过 trivia 后只保留语法 Token,所有跨度仍指向未经修改的 source。
|
||||
let tokens = lex(source).unwrap();
|
||||
// 把种类与原文切片配对,既检查 Token 分类,也检查是否误吞相邻内容。
|
||||
let spellings: Vec<_> = tokens
|
||||
.iter()
|
||||
.map(|token| (token.kind, &source[token.span.start..token.span.end]))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//! Rust 测试框架验证 Reader 适配器:完整 UTF-8 快照、来源/版本身份、错误阶段和宿主 URI 授权边界。
|
||||
use std::cell::Cell;
|
||||
use std::error::Error;
|
||||
use std::io::{self, Cursor, Read};
|
||||
@@ -9,19 +10,26 @@ use frontend::{
|
||||
SourceReadErrorKind, lex_source, parse_source, parse_source_expression,
|
||||
};
|
||||
|
||||
/// 流测试包装器,让 read_to_end 每次最多取得一个字节,强制 UTF-8 字符跨多次底层读取。
|
||||
struct OneByte<R>(R);
|
||||
|
||||
impl<R: Read> Read for OneByte<R> {
|
||||
/// Reader 的 read_to_end 调用此适配器,把一次底层读取限制为至多一个字节。
|
||||
fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
|
||||
// 空输出缓冲保持零长度,其余读取都限制为一个字节,不能按单次读取独立解码。
|
||||
let count = buffer.len().min(1);
|
||||
self.0.read(&mut buffer[..count])
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:文本与单字节流保留相同 CRLF/Unicode 原文并解析成相同 AST/Token;空流可读取但空模块无效,BOM 保留到词法阶段拒绝。
|
||||
fn strings_and_split_utf8_streams_produce_the_same_ast_and_exact_text() {
|
||||
// 保留注释、CRLF、日文和四字节 emoji,使原文精确性及跨读取解码同时受检。
|
||||
let text = "// 日本語\r\nfunction 表示(): String { return \"こんにちは🌸\" }\r\n";
|
||||
// String 文本已是 UTF-8,Reader 只包装拥有快照和显示名。
|
||||
let memory = EndorReader::from_text("<editor>", text);
|
||||
// 同一字节序列经 OneByte 拆分读取,Reader 应先完整聚合再验证 UTF-8。
|
||||
let stream = EndorReader::from_stream("<pipe>", OneByte(Cursor::new(text.as_bytes()))).unwrap();
|
||||
assert_eq!(memory.text(), text);
|
||||
assert_eq!(stream.text(), text);
|
||||
@@ -42,22 +50,28 @@ fn strings_and_split_utf8_streams_produce_the_same_ast_and_exact_text() {
|
||||
}
|
||||
);
|
||||
assert_eq!(lex_source(&memory).unwrap(), lex_source(&stream).unwrap());
|
||||
// 空字节流是合法 UTF-8 快照,是否为有效模块由后续语法阶段决定。
|
||||
let empty = EndorReader::from_stream("<empty>", Cursor::new(b"")).unwrap();
|
||||
assert!(empty.text().is_empty());
|
||||
assert_eq!(
|
||||
parse_source(&empty).unwrap_err().diagnostic.kind,
|
||||
DiagnosticKind::Syntax
|
||||
);
|
||||
// Reader 不剥除 BOM,词法器看到原始开头字符后才决定是否接受。
|
||||
let bom = EndorReader::from_stream("<bom>", Cursor::new("\u{feff}1".as_bytes())).unwrap();
|
||||
assert_eq!(bom.text(), "\u{feff}1");
|
||||
// BOM 保留了三字节 UTF-8 编码,词法错误范围应恰好覆盖它,而不是 Reader 静默剥除。
|
||||
let error = lex_source(&bom).unwrap_err();
|
||||
assert_eq!(error.diagnostic.kind, DiagnosticKind::Lexical);
|
||||
assert_eq!(error.diagnostic.span.end, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:语法与词法错误保留原始字节跨度、会话名及 Unicode 行列,源快照销毁后诊断仍可显示并提供错误链。
|
||||
fn source_diagnostics_keep_session_names_unicode_locations_and_original_offsets() {
|
||||
// 建立带来源名的原文快照,后续错误必须定位这份文本而非临时转换后的内容。
|
||||
let source = EndorReader::from_text("<session:one/input:2>", "\"日\" + )");
|
||||
// 右括号没有对应表达式,语法失败仍需按含日文字符的完整原文计算列号和跨度。
|
||||
let error = parse_source_expression(&source).unwrap_err();
|
||||
assert_eq!(error.location.line, 1);
|
||||
assert_eq!(error.location.column, 7);
|
||||
@@ -66,11 +80,14 @@ fn source_diagnostics_keep_session_names_unicode_locations_and_original_offsets(
|
||||
")"
|
||||
);
|
||||
assert_eq!(error.origin, source.origin().clone());
|
||||
// 丢弃文本快照后仍应能显示已固化的来源和行列,不依赖借用的源码缓冲。
|
||||
drop(source);
|
||||
assert!(error.to_string().starts_with("<session:one/input:2>:1:7:"));
|
||||
assert!(error.source().is_some());
|
||||
|
||||
// 建立带来源名的原文快照,后续错误必须定位这份文本而非临时转换后的内容。
|
||||
let source = EndorReader::from_text("<editor>", "\r\n\"日\\q\"");
|
||||
// 第二行字符串含不支持的反斜杠转义,词法错误应保留 CRLF 后的行号和 Unicode 列。
|
||||
let error = lex_source(&source).unwrap_err();
|
||||
assert_eq!(error.diagnostic.kind, DiagnosticKind::Lexical);
|
||||
assert_eq!(error.location.line, 2);
|
||||
@@ -79,8 +96,11 @@ fn source_diagnostics_keep_session_names_unicode_locations_and_original_offsets(
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:非法字节、截断多字节字符及合法前缀后非法字节都在读取解码阶段拒绝,不作有损替换。
|
||||
fn incomplete_or_invalid_utf8_is_rejected_without_replacement_characters() {
|
||||
// 覆盖无效起始字节、不完整 UTF-8 序列,以及一个合法 ASCII 前缀后的错误。
|
||||
for bytes in [vec![0xff], vec![0xe6, 0x97], vec![b'a', 0xff]] {
|
||||
// 即使每次只读一个字节,聚合后非法 UTF-8 仍须作为读取编码错误拒绝,不能交给 lexer。
|
||||
let error = EndorReader::from_stream("<bytes>", OneByte(Cursor::new(bytes))).unwrap_err();
|
||||
assert!(matches!(error.kind, SourceReadErrorKind::InvalidUtf8(_)));
|
||||
assert!(error.to_string().contains("<bytes>: invalid UTF-8 at byte"));
|
||||
@@ -88,13 +108,17 @@ fn incomplete_or_invalid_utf8_is_rejected_without_replacement_characters() {
|
||||
}
|
||||
}
|
||||
|
||||
/// 模拟中途失败的流;布尔值记录是否已返回过第一个合法字节。
|
||||
struct FailingStream(bool);
|
||||
|
||||
impl Read for FailingStream {
|
||||
/// Reader 的 read_to_end 调用;先成功一次再失败,检验中途错误不会变成部分成功。
|
||||
fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
|
||||
// 空缓冲不消耗模拟状态,符合 Read 的零长度读取约定。
|
||||
if buffer.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
// 第一次非空读取给出 x,后续非空读取才返回 IO 错误。
|
||||
if !self.0 {
|
||||
self.0 = true;
|
||||
buffer[0] = b'x';
|
||||
@@ -105,18 +129,24 @@ impl Read for FailingStream {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:读到一个有效字节后发生 IO 失败时仍整体失败,不向解析器提供半份快照。
|
||||
fn a_failed_stream_never_returns_partially_read_source() {
|
||||
// 流先返回 x 后报错,Reader 必须整体传播 IO 失败,不能把已读前缀作为成功源码。
|
||||
let error = EndorReader::from_stream("<failed>", FailingStream(false)).unwrap_err();
|
||||
assert!(matches!(error.kind, SourceReadErrorKind::Io(_)));
|
||||
assert!(error.to_string().contains("stream failure"));
|
||||
}
|
||||
|
||||
// 并行文件测试使用进程内递增编号避免重名;这里只需要原子唯一性,不发布共享数据。
|
||||
static NEXT_FILE: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
/// 本文件测试用的临时文件拥有者;构造写入字节,Drop 尽力删除,不留测试文件。
|
||||
struct SourceFile(PathBuf);
|
||||
|
||||
impl SourceFile {
|
||||
/// 文件测试创建临时输入;路径组合进程 ID 和原子编号,写入失败直接使测试失败。
|
||||
fn new(bytes: &[u8]) -> Self {
|
||||
// 放在系统临时目录,不依赖当前仓库布局,同一进程的不同测试实例用不同编号。
|
||||
let path = std::env::temp_dir().join(format!(
|
||||
"aster-reader-{}-{}.endor",
|
||||
std::process::id(),
|
||||
@@ -128,14 +158,19 @@ impl SourceFile {
|
||||
}
|
||||
|
||||
impl Drop for SourceFile {
|
||||
/// 退出文件测试作用域时清理文件,忽略已不存在等删除错误,避免 Drop 再次失败。
|
||||
fn drop(&mut self) {
|
||||
// 只尽力删除测试文件,显式忽略删除结果,不在析构中制造第二个错误。
|
||||
let _ = std::fs::remove_file(&self.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:文件保留原路径及 CRLF,坏编码与文件不存在分别产生读取编码错误和 NotFound IO 错误。
|
||||
fn file_sources_preserve_the_path_and_read_errors_are_not_lexical_errors() {
|
||||
// 建立合法模块临时文件,用作成功读取及路径来源的基线。
|
||||
let file = SourceFile::new(b"function f(): Number { return 1 }\r\n");
|
||||
// 建立带来源名的原文快照,后续错误必须定位这份文本而非临时转换后的内容。
|
||||
let source = EndorReader::from_file(&file.0).unwrap();
|
||||
assert_eq!(
|
||||
source.origin(),
|
||||
@@ -145,25 +180,34 @@ fn file_sources_preserve_the_path_and_read_errors_are_not_lexical_errors() {
|
||||
);
|
||||
assert!(source.text().ends_with("\r\n"));
|
||||
assert!(parse_source(&source).is_ok());
|
||||
// 单字节 0xff 不是 UTF-8,打开文件成功后应在解码阶段拒绝。
|
||||
let invalid = SourceFile::new(&[0xff]);
|
||||
// 文件能打开但 0xff 无法解码,错误应带该文件路径并归为 InvalidUtf8。
|
||||
let error = EndorReader::from_file(&invalid.0).unwrap_err();
|
||||
assert!(matches!(error.kind, SourceReadErrorKind::InvalidUtf8(_)));
|
||||
assert!(error.to_string().contains(&invalid.0.display().to_string()));
|
||||
// 从基线路径换一个未创建的扩展名,验证文件不存在作为 IO 错误暴露。
|
||||
let missing = file.0.with_extension("missing");
|
||||
// 未创建的路径应返回 NotFound IO 原因,不能误分类为词法或编码失败。
|
||||
let error = EndorReader::from_file(&missing).unwrap_err();
|
||||
// 必须能解构出原始 IO 错误,之后检查 NotFound 或 PermissionDenied 的具体种类。
|
||||
let SourceReadErrorKind::Io(error) = error.kind else {
|
||||
panic!("expected I/O error");
|
||||
};
|
||||
assert_eq!(error.kind(), io::ErrorKind::NotFound);
|
||||
}
|
||||
|
||||
/// URI 测试的可计数宿主,每次 load 返回不同版本和源码,用来检测隐式重复解析。
|
||||
struct Provider {
|
||||
// &self 的 load 通过 Cell 累计调用次数,不要求多线程共享。
|
||||
calls: Cell<usize>,
|
||||
}
|
||||
|
||||
impl EndorSourceProvider for Provider {
|
||||
/// from_endor 调用,检查定位符并累计解析次数,返回新版本拥有字节。
|
||||
fn load(&self, uri: &str) -> io::Result<ResolvedEndorSource> {
|
||||
assert_eq!(uri, "endor:workspace/namespace/function");
|
||||
// 每次解析选择新的版本编号,同时反映在标签与源码返回值中。
|
||||
let version = self.calls.get() + 1;
|
||||
self.calls.set(version);
|
||||
Ok(ResolvedEndorSource {
|
||||
@@ -174,12 +218,17 @@ impl EndorSourceProvider for Provider {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:同一 URI 每次读取只调用一次宿主,后续解析不再解析 URI,旧快照不被新版本改写。
|
||||
fn each_uri_read_resolves_once_and_old_snapshots_keep_their_original_revision() {
|
||||
// 为本场景新建计数宿主,不与其他 URI 测试共享解析次数。
|
||||
let provider = Provider {
|
||||
calls: Cell::new(0),
|
||||
};
|
||||
// URI 作为宿主可解释的定位符,Reader 不把 namespace/function 猜成磁盘路径。
|
||||
let uri = "endor:workspace/namespace/function";
|
||||
// 第一份快照保存第一次解析的原文及 revision-1。
|
||||
let first = EndorReader::from_endor(uri, &provider).unwrap();
|
||||
// 同一 URI 再读取得第二份独立快照,不能回写第一份文本或来源。
|
||||
let second = EndorReader::from_endor(uri, &provider).unwrap();
|
||||
assert_eq!(provider.calls.get(), 2);
|
||||
assert!(first.text().contains("return 1"));
|
||||
@@ -203,12 +252,16 @@ fn each_uri_read_resolves_once_and_old_snapshots_keep_their_original_revision()
|
||||
assert_eq!(provider.calls.get(), 2); // Parsing never resolves the URI again.
|
||||
}
|
||||
|
||||
/// 错误测试的固定宿主,按指定版本标签和字节返回,Reader 仍须自行校验协议及编码。
|
||||
struct StaticProvider {
|
||||
// 借用测试静态版本标签,load 时复制为拥有字符串。
|
||||
revision: &'static str,
|
||||
// 借用测试静态字节,load 时复制给 Reader 解码。
|
||||
bytes: &'static [u8],
|
||||
}
|
||||
|
||||
impl EndorSourceProvider for StaticProvider {
|
||||
/// from_endor 调用,把测试指定标签与字节复制到结果,不提前替 Reader 做编码/版本验证。
|
||||
fn load(&self, _uri: &str) -> io::Result<ResolvedEndorSource> {
|
||||
Ok(ResolvedEndorSource {
|
||||
revision: self.revision.into(),
|
||||
@@ -218,36 +271,48 @@ impl EndorSourceProvider for StaticProvider {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:URI 结果必须有非空版本身份;语法错误与编码错误都显示实际解析版本,空白标签被拒绝。
|
||||
fn uri_snapshots_require_a_revision_and_preserve_it_in_source_diagnostics() {
|
||||
// URI 作为宿主可解释的定位符,Reader 不把 namespace/function 猜成磁盘路径。
|
||||
let uri = "endor:workspace/namespace/function";
|
||||
// 使用合法版本身份但语法错误的源码,错误显示应继续包含该版本。
|
||||
let provider = StaticProvider {
|
||||
revision: "rev-one",
|
||||
bytes: b"function f(): Number { return ) }",
|
||||
};
|
||||
// 建立带来源名的原文快照,后续错误必须定位这份文本而非临时转换后的内容。
|
||||
let source = EndorReader::from_endor(uri, &provider).unwrap();
|
||||
// 已解析 URI 的字节是 UTF-8 但函数语法无效,解析诊断仍须显示 rev-one 来源。
|
||||
let error = parse_source(&source).unwrap_err();
|
||||
assert_eq!(error.origin, source.origin().clone());
|
||||
assert!(error.to_string().contains("[revision rev-one]"));
|
||||
// 版本全为空白,即使字节是有效源码也必须在创建 URI 来源前拒绝。
|
||||
let missing = StaticProvider {
|
||||
revision: " \t",
|
||||
bytes: b"1",
|
||||
};
|
||||
// 字节有效不能补救缺版本身份,空白版本标签应单独报告 MissingRevision。
|
||||
assert!(matches!(
|
||||
EndorReader::from_endor(uri, &missing).unwrap_err().kind,
|
||||
SourceReadErrorKind::MissingRevision
|
||||
));
|
||||
// 版本有效但字节非法,编码失败说明中也应保留已解析版本身份。
|
||||
let invalid = StaticProvider {
|
||||
revision: "rev-two",
|
||||
// 借用测试静态字节,load 时复制给 Reader 解码。
|
||||
bytes: &[0xff],
|
||||
};
|
||||
// 宿主已选择 rev-two 后才遇到非法 UTF-8,解码失败也必须保留该实际版本身份。
|
||||
let error = EndorReader::from_endor(uri, &invalid).unwrap_err();
|
||||
assert!(matches!(error.kind, SourceReadErrorKind::InvalidUtf8(_)));
|
||||
assert!(error.to_string().contains("[revision rev-two]"));
|
||||
}
|
||||
|
||||
/// 宿主拒绝授权的模拟,不提供任何替代源码或路径。
|
||||
struct DeniedProvider;
|
||||
|
||||
impl EndorSourceProvider for DeniedProvider {
|
||||
/// from_endor 调用,始终返回宿主 PermissionDenied,禁止任何本地文件回退。
|
||||
fn load(&self, _uri: &str) -> io::Result<ResolvedEndorSource> {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::PermissionDenied,
|
||||
@@ -257,10 +322,13 @@ impl EndorSourceProvider for DeniedProvider {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:基础非法 URI 不触发宿主,合法 URI 被宿主拒绝时保留 PermissionDenied,不改走文件读取。
|
||||
fn invalid_uris_do_not_call_the_provider_and_denials_do_not_fall_back_to_files() {
|
||||
// 为本场景新建计数宿主,不与其他 URI 测试共享解析次数。
|
||||
let provider = Provider {
|
||||
calls: Cell::new(0),
|
||||
};
|
||||
// 覆盖空定位符、空地址、错误 scheme、空白和控制字符,均应在访问 provider 前被拒绝。
|
||||
for uri in [
|
||||
"",
|
||||
"endor:",
|
||||
@@ -269,13 +337,16 @@ fn invalid_uris_do_not_call_the_provider_and_denials_do_not_fall_back_to_files()
|
||||
"endor:space here",
|
||||
"endor:bad\nname",
|
||||
] {
|
||||
// 每个非法定位符都必须先被基础 URI 校验拒绝,后面的计数零验证未访问宿主。
|
||||
assert!(matches!(
|
||||
EndorReader::from_endor(uri, &provider).unwrap_err().kind,
|
||||
SourceReadErrorKind::InvalidEndorUri
|
||||
));
|
||||
}
|
||||
assert_eq!(provider.calls.get(), 0);
|
||||
// 基础 URI 合法但宿主拒绝访问,Reader 应保留 PermissionDenied,不尝试文件回退。
|
||||
let error = EndorReader::from_endor("endor:anywhere", &DeniedProvider).unwrap_err();
|
||||
// 必须能解构出原始 IO 错误,之后检查 NotFound 或 PermissionDenied 的具体种类。
|
||||
let SourceReadErrorKind::Io(error) = error.kind else {
|
||||
panic!("expected provider error");
|
||||
};
|
||||
|
||||
@@ -1,30 +1,39 @@
|
||||
//! Rust 测试框架验证 Token、语法树形状、优先级、拥有文本及原文诊断;不进行名称分析或 VM 执行。
|
||||
use frontend::ast::{
|
||||
BinaryOp, Binding, DeclKind, Expr, ExprKind, Function, Program, StmtKind, UnaryOp,
|
||||
};
|
||||
use frontend::{DiagnosticKind, Span, TokenKind, lex, parse, parse_expression};
|
||||
|
||||
/// 声明测试借用 program 指定位置的函数,错误节点种类直接使测试失败,不创建或调用 VM 函数。
|
||||
fn function(program: &Program, index: usize) -> &Function {
|
||||
// 借用指定声明并要求确为函数,类等其他声明不应被辅助函数静默接受。
|
||||
let DeclKind::Function(function) = &program.declarations[index].kind else {
|
||||
panic!("expected function")
|
||||
};
|
||||
function
|
||||
}
|
||||
|
||||
/// 声明测试借用函数体指定位置的 let Binding,核对解析树语句形状与名称。
|
||||
fn binding(function: &Function, index: usize) -> &Binding {
|
||||
// 要求当前语句是 let 节点,普通表达式或赋值不满足此测试前提。
|
||||
let StmtKind::Binding(binding) = &function.body.statements[index].kind else {
|
||||
panic!("expected binding")
|
||||
};
|
||||
binding
|
||||
}
|
||||
|
||||
/// 声明测试借用函数体末尾 Return 的表达式,断言本用例确实有返回节点及值。
|
||||
fn result(function: &Function) -> &Expr {
|
||||
// 末语句必须是 Return,下面取得其中表达式,不把普通表达式语句误当返回。
|
||||
let StmtKind::Return(returned) = &function.body.statements.last().unwrap().kind else {
|
||||
panic!("expected return")
|
||||
};
|
||||
returned.value.as_ref().unwrap()
|
||||
}
|
||||
|
||||
/// 优先级测试拆二元节点,先核对操作种类再借用左右操作数,供逐层检查嵌套树。
|
||||
fn binary(expression: &Expr, expected: BinaryOp) -> (&Expr, &Expr) {
|
||||
// 拆出借用的操作符及两个子表达式,操作种类在返回前统一核对。
|
||||
let ExprKind::Binary {
|
||||
operation,
|
||||
left,
|
||||
@@ -37,13 +46,17 @@ fn binary(expression: &Expr, expected: BinaryOp) -> (&Expr, &Expr) {
|
||||
(left, right)
|
||||
}
|
||||
|
||||
/// 表达式测试核对原始数值拼写,前端保存 String,不在这里转换或计算数值。
|
||||
fn number(expression: &Expr, expected: &str) {
|
||||
assert_eq!(expression.kind, ExprKind::Number(expected.into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:Unicode 名称、全角数字及 emoji 字符串的 Token 范围精确切回原文,所有端点都在字符边界。
|
||||
fn unicode_tokens_refer_to_exact_original_byte_ranges() {
|
||||
// 保留当前原始文本用于字节切片与位置比较,不规范化其中的 Unicode、注释、空白或换行。
|
||||
let source = "// 日本語\r\nlet 商品_2 = \"こんにちは🌸\"";
|
||||
// 仅分词,后续同时检查种类与通过 span 切回的原文,不借助编译器判断数值。
|
||||
let tokens = lex(source).unwrap();
|
||||
assert_eq!(tokens.len(), 4);
|
||||
assert_eq!(tokens[0].kind, TokenKind::Name);
|
||||
@@ -51,6 +64,7 @@ fn unicode_tokens_refer_to_exact_original_byte_ranges() {
|
||||
assert_eq!(&source[tokens[1].span.start..tokens[1].span.end], "商品_2");
|
||||
assert_eq!(tokens[2].kind, TokenKind::Equal);
|
||||
assert_eq!(tokens[3].kind, TokenKind::String);
|
||||
// 每个半开范围的两端都必须是 UTF-8 边界,不能只检查特定一个名称。
|
||||
for token in tokens {
|
||||
assert!(source.is_char_boundary(token.span.start));
|
||||
assert!(source.is_char_boundary(token.span.end));
|
||||
@@ -58,8 +72,11 @@ fn unicode_tokens_refer_to_exact_original_byte_ranges() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:负号是独立 Token 和一元节点,长十进制的前导/尾随零在解析树中完整保留。
|
||||
fn decimal_spelling_and_minus_tokens_are_preserved_without_float_conversion() {
|
||||
// 保留当前原始文本用于字节切片与位置比较,不规范化其中的 Unicode、注释、空白或换行。
|
||||
let source = "-000123456789012345678901234567890.00100 / 7 // comment";
|
||||
// 仅分词,后续同时检查种类与通过 span 切回的原文,不借助编译器判断数值。
|
||||
let tokens = lex(source).unwrap();
|
||||
assert_eq!(
|
||||
tokens.iter().map(|token| token.kind).collect::<Vec<_>>(),
|
||||
@@ -74,8 +91,11 @@ fn decimal_spelling_and_minus_tokens_are_preserved_without_float_conversion() {
|
||||
&source[tokens[1].span.start..tokens[1].span.end],
|
||||
"000123456789012345678901234567890.00100"
|
||||
);
|
||||
// 解析完整表达式得到拥有型节点,下面直接核对语法结构而非运行结果。
|
||||
let expression = parse_expression(source).unwrap();
|
||||
// 根为 Divide;左子树应是取负,右子树保持原始数字 7。
|
||||
let (negative, divisor) = binary(&expression, BinaryOp::Divide);
|
||||
// 当前层必须是前缀取负节点,后面的操作数继续用于检查内部树。
|
||||
let ExprKind::Unary {
|
||||
operation: UnaryOp::Negate,
|
||||
operand,
|
||||
@@ -88,7 +108,9 @@ fn decimal_spelling_and_minus_tokens_are_preserved_without_float_conversion() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:四种支持转义被解码,字符串内双斜杠作为文本保留,节点范围仍覆盖带引号原文。
|
||||
fn strings_decode_only_the_supported_escapes_and_keep_comments_inside_strings() {
|
||||
// 解析完整表达式得到拥有型节点,下面直接核对语法结构而非运行结果。
|
||||
let expression = parse_expression(r#""商品\n\t\"\\// text""#).unwrap();
|
||||
assert_eq!(
|
||||
expression.kind,
|
||||
@@ -101,7 +123,9 @@ fn strings_decode_only_the_supported_escapes_and_keep_comments_inside_strings()
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:坏字符串、未支持数字后缀/格式和未知字符在词法阶段失败,诊断端点仍是合法原文字节边界。
|
||||
fn malformed_strings_numbers_and_unknown_characters_report_lexical_errors() {
|
||||
// 枚举非法输入;每例必须整体失败,不能只成功消费一个前缀就返回 AST。
|
||||
for source in [
|
||||
"\"unfinished",
|
||||
"\"bad\\q\"",
|
||||
@@ -117,6 +141,7 @@ fn malformed_strings_numbers_and_unknown_characters_report_lexical_errors() {
|
||||
"🌸",
|
||||
"\0",
|
||||
] {
|
||||
// 要求失败并检查阶段/范围/位置,确保不是被其他解析路径误接收。
|
||||
let error = lex(source).unwrap_err();
|
||||
assert_eq!(error.kind, DiagnosticKind::Lexical, "{source:?}");
|
||||
assert!(error.span.start <= error.span.end);
|
||||
@@ -124,34 +149,48 @@ fn malformed_strings_numbers_and_unknown_characters_report_lexical_errors() {
|
||||
assert!(source.is_char_boundary(error.span.start));
|
||||
assert!(source.is_char_boundary(error.span.end));
|
||||
}
|
||||
// 保留当前原始文本用于字节切片与位置比较,不规范化其中的 Unicode、注释、空白或换行。
|
||||
let source = "\"日\\q\"";
|
||||
// 要求失败并检查阶段/范围/位置,确保不是被其他解析路径误接收。
|
||||
let error = lex(source).unwrap_err();
|
||||
assert_eq!(&source[error.span.start..error.span.end], "\\q");
|
||||
assert_eq!(error.location(source).column, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:逐层拆 AST 验证乘除优先于加减、同层左结合、括号扩大节点范围及连续负号保持两层一元节点。
|
||||
fn arithmetic_precedence_and_left_associativity_form_the_expected_tree() {
|
||||
// 解析完整表达式得到拥有型节点,下面直接核对语法结构而非运行结果。
|
||||
let expression = parse_expression("1 - 2 - 3 * 4 / 5 + 6").unwrap();
|
||||
// 最外层是最后的加法,右侧是 6,左侧包含前面的减法链及乘除。
|
||||
let (subtract, last) = binary(&expression, BinaryOp::Add);
|
||||
number(last, "6");
|
||||
// 第二次减法的左侧仍是第一次减法,体现同层左结合。
|
||||
let (first_subtract, divide) = binary(subtract, BinaryOp::Subtract);
|
||||
// 第一次减法直接连接数字 1 和 2。
|
||||
let (first, second) = binary(first_subtract, BinaryOp::Subtract);
|
||||
number(first, "1");
|
||||
number(second, "2");
|
||||
// 乘除同层左结合,因此除法左边是 3*4,右边是 5。
|
||||
let (multiply, fifth) = binary(divide, BinaryOp::Divide);
|
||||
number(fifth, "5");
|
||||
// 乘法的两个叶子分别为数字 3 和 4。
|
||||
let (third, fourth) = binary(multiply, BinaryOp::Multiply);
|
||||
number(third, "3");
|
||||
number(fourth, "4");
|
||||
// 括号让加法作为乘法左操作数,且加法节点范围包含括号本身。
|
||||
let grouped = parse_expression("(1 + 2) * 3").unwrap();
|
||||
// 根为 Multiply,取左边的括号加法验证优先级被分组覆盖。
|
||||
let (sum, _) = binary(&grouped, BinaryOp::Multiply);
|
||||
binary(sum, BinaryOp::Add);
|
||||
assert_eq!(sum.span, Span::new(0, 7));
|
||||
// 两个负号在语法阶段保留两个节点,精确字面量符号合并属于之后的 codegen。
|
||||
let nested = parse_expression("--1").unwrap();
|
||||
// 当前层必须是前缀取负节点,后面的操作数继续用于检查内部树。
|
||||
let ExprKind::Unary { operand, .. } = nested.kind else {
|
||||
panic!("expected outer negate");
|
||||
};
|
||||
// 外层负号的操作数仍是 Negate,证明解析器保留两个前缀节点而未提前计算数值。
|
||||
assert!(matches!(
|
||||
operand.kind,
|
||||
ExprKind::Unary {
|
||||
@@ -162,8 +201,11 @@ fn arithmetic_precedence_and_left_associativity_form_the_expected_tree() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:取负包住整个 factory/成员/调用后缀链,逐层检查接收对象、符号实参、文本实参及属性名。
|
||||
fn member_and_call_chains_bind_more_tightly_than_unary_minus() {
|
||||
// 解析完整表达式得到拥有型节点,下面直接核对语法结构而非运行结果。
|
||||
let expression = parse_expression("-factory(1).build(.title)(\"日\", 2)").unwrap();
|
||||
// 当前层必须是前缀取负节点,后面的操作数继续用于检查内部树。
|
||||
let ExprKind::Unary {
|
||||
operation: UnaryOp::Negate,
|
||||
operand,
|
||||
@@ -171,22 +213,27 @@ fn member_and_call_chains_bind_more_tightly_than_unary_minus() {
|
||||
else {
|
||||
panic!("expected negate");
|
||||
};
|
||||
// 拆下一层调用的被调子树及实参;同名变量在逐层拆解时遮蔽上一层借用/拥有值。
|
||||
let ExprKind::Call { callee, arguments } = operand.kind else {
|
||||
panic!("expected last call");
|
||||
};
|
||||
assert_eq!(arguments.len(), 2);
|
||||
assert_eq!(arguments[0].kind, ExprKind::String("日".into()));
|
||||
// 拆下一层调用的被调子树及实参;同名变量在逐层拆解时遮蔽上一层借用/拥有值。
|
||||
let ExprKind::Call { callee, arguments } = callee.kind else {
|
||||
panic!("expected build call");
|
||||
};
|
||||
// build 的第一个参数是 .title 符号节点,不是普通名称读取或成员访问。
|
||||
let ExprKind::Symbol(name) = &arguments[0].kind else {
|
||||
panic!("expected a symbol");
|
||||
};
|
||||
assert_eq!(name.text, "title");
|
||||
// 成员节点保留 build 名称,其接收对象应是 factory(1) 调用。
|
||||
let ExprKind::Member { object, name } = callee.kind else {
|
||||
panic!("expected member");
|
||||
};
|
||||
assert_eq!(name.text, "build");
|
||||
// 拆下一层调用的被调子树及实参;同名变量在逐层拆解时遮蔽上一层借用/拥有值。
|
||||
let ExprKind::Call { callee, arguments } = object.kind else {
|
||||
panic!("expected factory call");
|
||||
};
|
||||
@@ -195,9 +242,12 @@ fn member_and_call_chains_bind_more_tightly_than_unary_minus() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:解析示例和多函数源码,核对显式类型、绑定/返回、Unicode 原文范围及 AST 独立拥有文本。
|
||||
fn parses_typed_functions_bindings_and_multiple_top_level_definitions() {
|
||||
// 使用仓库原始示例,测试该文件仍是两个可独立解析的顶层函数声明。
|
||||
let example = parse(include_str!("../examples/basic.endor")).unwrap();
|
||||
assert_eq!(example.declarations.len(), 2);
|
||||
// 保留当前原始文本用于字节切片与位置比较,不规范化其中的 Unicode、注释、空白或换行。
|
||||
let source = r#"
|
||||
// Basic draft syntax, including a Unicode name.
|
||||
function discountedPrice(price: Number, discount: Number): Number {
|
||||
@@ -206,8 +256,10 @@ fn parses_typed_functions_bindings_and_multiple_top_level_definitions() {
|
||||
}
|
||||
function 表示(): String { return "こんにちは" }
|
||||
"#;
|
||||
// 解析模块语法树,随后通过辅助函数核对声明、参数、语句及返回。
|
||||
let program = parse(source).unwrap();
|
||||
assert_eq!(program.declarations.len(), 2);
|
||||
// 只借用第一个顶层函数,所有文本由 program 持有而非原文临时切片。
|
||||
let first = function(&program, 0);
|
||||
assert_eq!(first.name.text, "discountedPrice");
|
||||
assert_eq!(first.parameters.len(), 2);
|
||||
@@ -230,7 +282,9 @@ fn parses_typed_functions_bindings_and_multiple_top_level_definitions() {
|
||||
"function 表示(): String { return \"こんにちは\" }"
|
||||
);
|
||||
// The output owns its text and can outlive the input buffer.
|
||||
// 内层 String 离开块后销毁,外层 Program 仍应拥有函数名和字面量文本。
|
||||
let owned = {
|
||||
// 保留当前原始文本用于字节切片与位置比较,不规范化其中的 Unicode、注释、空白或换行。
|
||||
let source = String::from("function f(): String { return \"ok\" }");
|
||||
parse(&source).unwrap()
|
||||
};
|
||||
@@ -238,13 +292,16 @@ fn parses_typed_functions_bindings_and_multiple_top_level_definitions() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:关键字形名称可在其他上下文作标识符,CRLF 可出现在参数/块边界而不改变树结构。
|
||||
fn syntax_words_are_contextual_and_block_layout_does_not_depend_on_newlines() {
|
||||
// 解析模块语法树,随后通过辅助函数核对声明、参数、语句及返回。
|
||||
let program =
|
||||
parse("function class(function: Type): Type { let agent = function return agent }")
|
||||
.unwrap();
|
||||
assert_eq!(function(&program, 0).name.text, "class");
|
||||
assert_eq!(function(&program, 0).parameters[0].name.text, "function");
|
||||
assert_eq!(binding(function(&program, 0), 0).name.text, "agent");
|
||||
// 在语法分隔点插入 CRLF,验证空白布局不依赖特定换行格式。
|
||||
let multiline =
|
||||
parse("function f\r\n(\r\nx: Number\r\n): Number\r\n{\r\nreturn x\r\n}").unwrap();
|
||||
assert_eq!(
|
||||
@@ -254,7 +311,9 @@ fn syntax_words_are_contextual_and_block_layout_does_not_depend_on_newlines() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:模块与表达式都拒绝空输入、剩余 Token、缺少必需语法及当前未支持形式。
|
||||
fn parser_requires_complete_input_and_rejects_unimplemented_or_malformed_syntax() {
|
||||
// 枚举非法输入;每例必须整体失败,不能只成功消费一个前缀就返回 AST。
|
||||
for source in [
|
||||
"",
|
||||
"// empty",
|
||||
@@ -270,6 +329,7 @@ fn parser_requires_complete_input_and_rejects_unimplemented_or_malformed_syntax(
|
||||
] {
|
||||
assert!(parse(source).is_err(), "{source}");
|
||||
}
|
||||
// 枚举非法输入;每例必须整体失败,不能只成功消费一个前缀就返回 AST。
|
||||
for source in [
|
||||
"", "x y", "1 +", "+1", ".5", "1.", "f(1,)", "f(,1)", "f(1 2)", "(1", "obj.", "[1]",
|
||||
"1.2.3",
|
||||
@@ -279,20 +339,27 @@ fn parser_requires_complete_input_and_rejects_unimplemented_or_malformed_syntax(
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 由 Rust 测试框架调用:语法 Token 索引准确转回原文违规字符或 EOF 空范围,CRLF 和 Unicode 不破坏显示行列。
|
||||
fn syntax_errors_map_token_indexes_to_utf8_source_and_eof_locations() {
|
||||
// 保留当前原始文本用于字节切片与位置比较,不规范化其中的 Unicode、注释、空白或换行。
|
||||
let source = "function f(): String {\r\n let 商品 = \"日\"\r\n return )\r\n}";
|
||||
// 要求失败并检查阶段/范围/位置,确保不是被其他解析路径误接收。
|
||||
let error = parse(source).unwrap_err();
|
||||
assert_eq!(error.kind, DiagnosticKind::Syntax);
|
||||
assert_eq!(&source[error.span.start..error.span.end], ")");
|
||||
assert_eq!(error.location(source).line, 3);
|
||||
assert_eq!(error.location(source).column, 10);
|
||||
assert!(!error.expected.is_empty());
|
||||
// 保留当前原始文本用于字节切片与位置比较,不规范化其中的 Unicode、注释、空白或换行。
|
||||
let source = "function f(): Number { return 1\r\n ";
|
||||
// 要求失败并检查阶段/范围/位置,确保不是被其他解析路径误接收。
|
||||
let error = parse(source).unwrap_err();
|
||||
assert_eq!(error.span, Span::new(source.len(), source.len()));
|
||||
assert_eq!(error.location(source).line, 2);
|
||||
assert_eq!(error.location(source).column, 3);
|
||||
// 保留当前原始文本用于字节切片与位置比较,不规范化其中的 Unicode、注释、空白或换行。
|
||||
let source = "\r\n\r\n🌸";
|
||||
// 要求失败并检查阶段/范围/位置,确保不是被其他解析路径误接收。
|
||||
let error = lex(source).unwrap_err();
|
||||
assert_eq!(error.location(source).line, 3);
|
||||
assert_eq!(error.location(source).column, 1);
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
//! 内置字符串、字典、闭包单元、源函数和异常实例的 Rust 载荷定义。
|
||||
//! ObjectStore 的 store_builtins 创建/更新这些载荷;core VM 调用和容器操作通过同一对象存储访问它们。
|
||||
//! 这里的构造器只组织 Rust 数据,不分配托管身份、不验证跨堆引用,也不因 Clone 自动 retain ObjectRef。
|
||||
//! 分配/替换时由 Heap 按 trace 列出的每一条拥有边验证并计数;trace 必须报告重复引用,不能自行去重。
|
||||
//! 载荷 trace 在分配/替换时生成边快照;循环回收读取 EndorObject 保存的快照,而非依赖 Clone 追踪引用。
|
||||
//! kind_name 只是诊断标签;语言名义类型由对象头/类型注册表另行管理。
|
||||
|
||||
use std::any::Any;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
@@ -5,56 +12,73 @@ use std::hash::{Hash, Hasher};
|
||||
use crate::{CompareOp, ObjectPayload, ObjectRef, Value, VmError};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// 堆内不可变字符串载荷;宿主可通过 ObjectStore 分配,字符索引按 Unicode 标量而非字节。
|
||||
pub struct EndorStringObject {
|
||||
// 自有 UTF-8 文本,不包含托管对象引用;与即时 Value::Text 是不同存储表示。
|
||||
text: String,
|
||||
}
|
||||
|
||||
impl EndorStringObject {
|
||||
/// ObjectStore::new_string 调用,接收可转为 String 的文本并保存;此步尚未进入托管堆。
|
||||
pub fn new(text: impl Into<String>) -> Self {
|
||||
Self { text: text.into() }
|
||||
}
|
||||
|
||||
/// 字典 key_equal、eval 文本比较和宿主查询借用 UTF-8 内容;借用不创建托管根,也不修改字符串。
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.text
|
||||
}
|
||||
|
||||
/// 宿主/内置对象测试查询 Unicode 标量数量;不是 UTF-8 字节数或用户可见字素数。
|
||||
pub fn char_count(&self) -> usize {
|
||||
self.text.chars().count()
|
||||
}
|
||||
|
||||
/// 宿主/内置对象测试按零基 Unicode 标量索引查询;从头线性扫描,越界返回 None。
|
||||
pub fn char_at(&self, index: usize) -> Option<char> {
|
||||
self.text.chars().nth(index)
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectPayload for EndorStringObject {
|
||||
/// ObjectStore::assign_from 调用,声明默认 assign_from 不会就地修改载荷;只读阶段可跳过写尝试,允许调用方重绑局部引用。
|
||||
fn reference_assignment_only(&self) -> bool {
|
||||
true
|
||||
}
|
||||
/// ObjectStore::payload、内置验证和比较通过此入口向下转换实际 Rust 载荷;不做语言类型判断。
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
/// ObjectStore 的错误/观察信息调用,返回静态诊断标签,不声明名义类型或继承。
|
||||
fn kind_name(&self) -> &'static str {
|
||||
"string"
|
||||
}
|
||||
/// ObjectStore::repr 调用,用 Rust 字符串 Debug 生成带引号/转义的文本;不展开托管对象。
|
||||
fn repr(&self, _identity: ObjectRef) -> Result<String, VmError> {
|
||||
Ok(format!("{:?}", self.text))
|
||||
}
|
||||
/// ObjectStore::str 调用,返回原文的独立 String 副本,不带 repr 引号。
|
||||
fn str(&self, _identity: ObjectRef) -> Result<String, VmError> {
|
||||
Ok(self.text.clone())
|
||||
}
|
||||
/// VM 条件或宿主真值查询调用;空字符串为假,其余为真,不修剪空白。
|
||||
fn is_true(&self) -> Result<bool, VmError> {
|
||||
Ok(!self.text.is_empty())
|
||||
}
|
||||
/// ObjectStore::hash/字典键校验调用,按完整文本求哈希;不是稳定跨版本的持久化摘要。
|
||||
fn hash(&self, _identity: ObjectRef) -> Result<u64, VmError> {
|
||||
// 每次从空哈希状态混入同样的文本内容,使相等字符串提供兼容哈希;这里不缓存哈希值。
|
||||
let mut hasher = DefaultHasher::new();
|
||||
self.text.hash(&mut hasher);
|
||||
Ok(hasher.finish())
|
||||
}
|
||||
/// ObjectStore::compare 调用,仅与同种字符串载荷按 Rust 字符串词典序比较;不按 locale 或字素排序。
|
||||
fn compare(&self, other: &dyn ObjectPayload, op: CompareOp) -> Result<Option<bool>, VmError> {
|
||||
// 其他载荷返回“不支持”而不是 false,ObjectStore 才能继续尝试反向比较或其错误处理。
|
||||
let Some(other) = other.as_any().downcast_ref::<Self>() else {
|
||||
return Ok(None);
|
||||
};
|
||||
// 比较种类由调用方提供;六种操作都比较文本内容,ObjectRef 身份不参与结果。
|
||||
Ok(Some(match op {
|
||||
CompareOp::Eq => self.text == other.text,
|
||||
CompareOp::Ne => self.text != other.text,
|
||||
@@ -70,47 +94,61 @@ impl ObjectPayload for EndorStringObject {
|
||||
/// key/value reference is validated, retained, traced, and released correctly.
|
||||
/// Linear lookup is intentional for the initial representation layer.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
/// 按插入顺序拥有键值的字典载荷;查询/写入语义由 store_builtins 统一实施。
|
||||
pub struct EndorDictObject {
|
||||
// 按首次插入顺序保存键值对;只允许存储层整体替换载荷,不能绕过 Heap 的拥有边和只读写屏障。
|
||||
pub(crate) entries: Vec<(Value, Value)>,
|
||||
}
|
||||
|
||||
impl EndorDictObject {
|
||||
/// ObjectStore::new_dict/dict_clear 创建空的插入顺序容器,再交 Heap 管理实际身份与拥有边。
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
/// 容器 repr 和宿主查询获取键值对数量,不是托管引用数;同一对象可在多个项中重复出现。
|
||||
pub fn len(&self) -> usize {
|
||||
self.entries.len()
|
||||
}
|
||||
/// 字典真值查询复用此检查;只判断 entries 是否为空,不调用键或值的语言代码。
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.entries.is_empty()
|
||||
}
|
||||
/// ObjectStore::dict_items 借出按插入顺序的切片;键值中的 ObjectRef 仍属借用,Clone 不会增根。
|
||||
pub fn entries(&self) -> &[(Value, Value)] {
|
||||
&self.entries
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectPayload for EndorDictObject {
|
||||
/// ObjectStore::assign_from 调用,声明默认 assign_from 不会就地修改载荷;只读阶段可跳过写尝试,允许调用方重绑局部引用。
|
||||
fn reference_assignment_only(&self) -> bool {
|
||||
true
|
||||
}
|
||||
/// ObjectStore::payload、内置验证和比较通过此入口向下转换实际 Rust 载荷;不做语言类型判断。
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
/// ObjectStore 的错误/观察信息调用,返回静态诊断标签,不声明名义类型或继承。
|
||||
fn kind_name(&self) -> &'static str {
|
||||
"dict"
|
||||
}
|
||||
/// Heap 分配/替换时收集拥有边,循环回收再使用保存的边快照;键和值中的每个 ObjectRef 都须报告,包括重复引用。
|
||||
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
|
||||
// 键和值分别计一条边;同一对象同时作键和值时必须报告两次。
|
||||
for (key, value) in &self.entries {
|
||||
trace_value(key, visit);
|
||||
trace_value(value, visit);
|
||||
}
|
||||
}
|
||||
/// ObjectStore::repr 查询仅显示容器长度,不递归格式化键值,避免循环图导致无限展开。
|
||||
fn repr(&self, _identity: ObjectRef) -> Result<String, VmError> {
|
||||
Ok(format!("<dict len={}>", self.len()))
|
||||
}
|
||||
/// ObjectStore::is_true 查询字典是否非空;内容可变,所以每次取当前长度。
|
||||
fn is_true(&self) -> Result<bool, VmError> {
|
||||
Ok(!self.is_empty())
|
||||
}
|
||||
/// 字典键校验/ObjectStore::hash 调用时明确拒绝;可变容器没有内容稳定的键哈希契约。
|
||||
fn hash(&self, _identity: ObjectRef) -> Result<u64, VmError> {
|
||||
Err(VmError::UnsupportedOperation("dict hash"))
|
||||
}
|
||||
@@ -118,35 +156,46 @@ impl ObjectPayload for EndorDictObject {
|
||||
|
||||
/// An empty cell differs from a cell containing Value::Nil.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
/// 闭包共享单元载荷;VM 的局部捕获和 free-variable 操作通过 ObjectStore 更新其内容。
|
||||
pub struct EndorCellObject {
|
||||
// None 是未初始化单元;Some(Nil) 是合法已初始化值,二者在 cell_get/字节码闭包读取时必须区分。
|
||||
pub(crate) value: Option<Value>,
|
||||
}
|
||||
|
||||
impl EndorCellObject {
|
||||
/// ObjectStore::new_cell/cell_set 创建单元载荷;Option 区分未初始化与已保存 Nil,Heap 才负责引用计数。
|
||||
pub fn new(value: Option<Value>) -> Self {
|
||||
Self { value }
|
||||
}
|
||||
/// ObjectStore::cell_get 借出已有值;空单元保持 None,由调用方转为 EmptyCell 错误。
|
||||
pub fn value(&self) -> Option<&Value> {
|
||||
self.value.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectPayload for EndorCellObject {
|
||||
/// ObjectStore::assign_from 调用,声明默认 assign_from 不会就地修改载荷;只读阶段可跳过写尝试,允许调用方重绑局部引用。
|
||||
fn reference_assignment_only(&self) -> bool {
|
||||
true
|
||||
}
|
||||
/// ObjectStore::payload、内置验证和比较通过此入口向下转换实际 Rust 载荷;不做语言类型判断。
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
/// ObjectStore 的错误/观察信息调用,返回静态诊断标签,不声明名义类型或继承。
|
||||
fn kind_name(&self) -> &'static str {
|
||||
"cell"
|
||||
}
|
||||
/// Heap 读取已初始化单元的拥有边;空单元无边,Some(Nil) 也不含对象边。
|
||||
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
|
||||
// 未初始化单元不持有 Value;已初始化即时值由 trace_value 识别为没有托管边。
|
||||
if let Some(value) = &self.value {
|
||||
trace_value(value, visit);
|
||||
}
|
||||
}
|
||||
/// 宿主 repr 只区分 occupied/empty,不把单元值展开,避免递归闭包或单元环。
|
||||
fn repr(&self, _identity: ObjectRef) -> Result<String, VmError> {
|
||||
// 只看 Option 是否占用,Some(Nil) 与其他已初始化值一样显示 occupied。
|
||||
Ok(if self.value.is_some() {
|
||||
"<cell occupied>"
|
||||
} else {
|
||||
@@ -154,22 +203,31 @@ impl ObjectPayload for EndorCellObject {
|
||||
}
|
||||
.into())
|
||||
}
|
||||
/// 字典键校验拒绝把可变 Cell 用作可哈希键,不退化为一个看似内容稳定的哈希值。
|
||||
fn hash(&self, _identity: ObjectRef) -> Result<u64, VmError> {
|
||||
Err(VmError::UnsupportedOperation("cell hash"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// 源函数的代码与定义环境载荷;core 根据这些借用访问器建立真实 VM 帧,不由 payload.call 递归解释。
|
||||
pub struct EndorFunctionObject {
|
||||
// 源函数显示名,供 repr/诊断使用,不是依此重新查找全局函数的键。
|
||||
name: String,
|
||||
// 已加载 EndorCodeObject 的强边;执行器从中读取参数、局部槽和指令。
|
||||
code: ObjectRef,
|
||||
// new_function 捕获创建时 globals.__builtins__ 的字典身份;None 表示没有该回退环境。
|
||||
builtins: Option<ObjectRef>,
|
||||
// 调用期间按名称读取的全局字典强边,不复制函数定义时的所有全局值。
|
||||
globals: ObjectRef,
|
||||
// 最后若干位置参数的缺省值;对象值由 trace 声明拥有,执行器按缺参位置补齐。
|
||||
defaults: Vec<Value>,
|
||||
// 与 code.free_variables 顺序一致的 Cell 强边;多个函数可共享同一单元以观察后续写入。
|
||||
closure: Vec<ObjectRef>,
|
||||
}
|
||||
|
||||
impl EndorFunctionObject {
|
||||
/// 仅 ObjectStore::new_function 创建源函数载荷;代码/字典/闭包形状由存储层验证,字段强边由 trace 声明。
|
||||
pub(crate) fn new(
|
||||
name: String,
|
||||
code: ObjectRef,
|
||||
@@ -187,80 +245,106 @@ impl EndorFunctionObject {
|
||||
closure,
|
||||
}
|
||||
}
|
||||
/// 执行器和诊断借用源函数显示名;不按这个字符串重新绑定其他同名函数。
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
/// core 执行器获取代码身份以建立帧;返回 ObjectRef 是借用句柄,不新增外部根。
|
||||
pub fn code(&self) -> ObjectRef {
|
||||
self.code
|
||||
}
|
||||
/// core 执行器/注解解析读取创建时捕获的 builtin 回退字典;不在每次调用重取 globals.__builtins__。
|
||||
pub fn builtins(&self) -> Option<ObjectRef> {
|
||||
self.builtins
|
||||
}
|
||||
/// core 执行器/效果分析读取函数自己的全局环境身份;环境内容仍可在后续输入改变。
|
||||
pub fn globals(&self) -> ObjectRef {
|
||||
self.globals
|
||||
}
|
||||
/// core 参数准备借用尾部缺省值;省略参数时使用这些实际 Value,不重新执行缺省值表达式。
|
||||
pub fn defaults(&self) -> &[Value] {
|
||||
&self.defaults
|
||||
}
|
||||
/// core 建帧按 free-variable 顺序借用 Cell 身份,使兄弟闭包共享实际单元而非复制当前值。
|
||||
pub fn closure(&self) -> &[ObjectRef] {
|
||||
&self.closure
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectPayload for EndorFunctionObject {
|
||||
/// VM/契约检查调用,明确源函数属于 Function;与可调用但不属 Function 的 Type 对象区分。
|
||||
fn is_function(&self) -> bool {
|
||||
true
|
||||
}
|
||||
/// ObjectStore::assign_from 调用,声明默认 assign_from 不会就地修改载荷;只读阶段可跳过写尝试,允许调用方重绑局部引用。
|
||||
fn reference_assignment_only(&self) -> bool {
|
||||
true
|
||||
}
|
||||
/// ObjectStore::payload、内置验证和比较通过此入口向下转换实际 Rust 载荷;不做语言类型判断。
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
/// ObjectStore 的错误/观察信息调用,返回静态诊断标签,不声明名义类型或继承。
|
||||
fn kind_name(&self) -> &'static str {
|
||||
"function"
|
||||
}
|
||||
/// ObjectStore 设置普通实例字段前查询;允许对象头保存属性,不表示载荷私有字段可被任意直接修改。
|
||||
fn allows_attributes(&self) -> bool {
|
||||
true
|
||||
}
|
||||
/// Heap 按函数拥有的代码、全局、可选 builtins、Cell 和默认值逐条计数/追踪;同一身份重复出现也要报告。
|
||||
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
|
||||
visit(self.code);
|
||||
visit(self.globals);
|
||||
// 可选回退环境存在时同样拥有;它与 globals 若为同一对象也不能省略这一次访问。
|
||||
if let Some(builtins) = self.builtins {
|
||||
visit(builtins);
|
||||
}
|
||||
// Cell 本身是强边;当前 Cell 的内容由 Cell.trace 另行声明,不在这里跳过单元直接追踪值。
|
||||
for reference in &self.closure {
|
||||
visit(*reference);
|
||||
}
|
||||
// 缺省参数中的即时值不含对象边,Object 变体则按每个位置报告。
|
||||
for value in &self.defaults {
|
||||
trace_value(value, visit);
|
||||
}
|
||||
}
|
||||
/// ObjectStore::repr 返回函数显示名,不执行函数、不展开其 globals/closure。
|
||||
fn repr(&self, _identity: ObjectRef) -> Result<String, VmError> {
|
||||
Ok(format!("<function {}>", self.name))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// 异常构建器接收的纯数据位置记录,没有对代码对象或文件的托管拥有边。
|
||||
pub struct EndorTracebackEntry {
|
||||
// 宿主提供的函数显示名和文件来源;这里不保留可执行函数或文件资源句柄。
|
||||
pub function: String,
|
||||
pub file: String,
|
||||
// 调用方提供的行位置,本记录不做位置换算或非零验证。
|
||||
pub line: usize,
|
||||
}
|
||||
|
||||
/// Common exception instance data, not an exception class or inheritance tree.
|
||||
#[derive(Debug, Clone)]
|
||||
/// 宿主可分配的公共异常实例数据;本文件不实现 raise/catch 控制流或异常类层次。
|
||||
pub struct EndorBaseExceptionObject {
|
||||
// 异常显示名称,不建立同名异常类或继承关系。
|
||||
name: String,
|
||||
// 参数可包含即时值或对象;对象参数通过 trace 成为真实托管拥有边。
|
||||
args: Vec<Value>,
|
||||
// 显式原因与隐式上下文分别保存;存储层要求它们指向异常载荷。
|
||||
cause: Option<ObjectRef>,
|
||||
context: Option<ObjectRef>,
|
||||
// 仅保存上下文抑制标记;设置此值不会解除 context 的拥有边。
|
||||
suppress_context: bool,
|
||||
// 说明和 traceback 是自有文本/位置记录,本载荷不据此读取文件或执行回调。
|
||||
notes: Vec<String>,
|
||||
traceback: Vec<EndorTracebackEntry>,
|
||||
}
|
||||
|
||||
impl EndorBaseExceptionObject {
|
||||
/// 宿主/内置对象测试构造异常实例数据;初始没有 cause/context/notes/traceback,尚未分配或抛出异常。
|
||||
pub fn new(name: impl Into<String>, args: Vec<Value>) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
@@ -272,71 +356,91 @@ impl EndorBaseExceptionObject {
|
||||
traceback: Vec::new(),
|
||||
}
|
||||
}
|
||||
/// 分配前的构建器记录显式原因并打开上下文抑制;不在此验证句柄或释放已有 context。
|
||||
pub fn with_cause(mut self, cause: ObjectRef) -> Self {
|
||||
self.cause = Some(cause);
|
||||
self.suppress_context = true;
|
||||
self
|
||||
}
|
||||
/// 分配前的构建器保存上下文句柄;allocate 后才登记强边,不自动改变 suppress_context 或执行异常。
|
||||
pub fn with_context(mut self, context: ObjectRef) -> Self {
|
||||
self.context = Some(context);
|
||||
self
|
||||
}
|
||||
/// 宿主构建器覆盖抑制标记,仍保留 context 引用,显示标记与 GC 可达关系分开。
|
||||
pub fn suppress_context(mut self, suppress: bool) -> Self {
|
||||
self.suppress_context = suppress;
|
||||
self
|
||||
}
|
||||
/// 宿主构建器追加自有说明文本,保留调用顺序,不做换行/长度校验或输出。
|
||||
pub fn with_note(mut self, note: impl Into<String>) -> Self {
|
||||
self.notes.push(note.into());
|
||||
self
|
||||
}
|
||||
/// 宿主构建器按调用顺序追加位置记录;本方法不遍历 VM 帧,也不验证记录的来源位置。
|
||||
pub fn with_traceback(mut self, entry: EndorTracebackEntry) -> Self {
|
||||
self.traceback.push(entry);
|
||||
self
|
||||
}
|
||||
/// 异常查询和格式化借用显示名;名称不是语言 Type 身份。
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
/// 宿主查询借用实际参数切片;其中对象值由异常载荷拥有,复制句柄不自动 retain。
|
||||
pub fn args(&self) -> &[Value] {
|
||||
&self.args
|
||||
}
|
||||
/// ObjectStore::validate_builtin 和宿主读取可选显式原因;返回借用身份,不创建新拥有根。
|
||||
pub fn cause(&self) -> Option<ObjectRef> {
|
||||
self.cause
|
||||
}
|
||||
/// ObjectStore::validate_builtin 和宿主读取可选上下文;即使已抑制显示,分配后的上下文边仍参与追踪。
|
||||
pub fn context(&self) -> Option<ObjectRef> {
|
||||
self.context
|
||||
}
|
||||
/// 宿主显示/测试读取标志;本载荷的 repr/str 不据此展开异常链。
|
||||
pub fn context_suppressed(&self) -> bool {
|
||||
self.suppress_context
|
||||
}
|
||||
/// 宿主查询按追加顺序借用说明;repr/str 当前不会自动把 notes 拼入输出。
|
||||
pub fn notes(&self) -> &[String] {
|
||||
&self.notes
|
||||
}
|
||||
/// 宿主查询借用显式位置记录;不因读取此切片而捕获当前 VM 调用栈。
|
||||
pub fn traceback(&self) -> &[EndorTracebackEntry] {
|
||||
&self.traceback
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectPayload for EndorBaseExceptionObject {
|
||||
/// ObjectStore::assign_from 调用,声明默认 assign_from 不会就地修改载荷;只读阶段可跳过写尝试,允许调用方重绑局部引用。
|
||||
fn reference_assignment_only(&self) -> bool {
|
||||
true
|
||||
}
|
||||
/// ObjectStore::payload、内置验证和比较通过此入口向下转换实际 Rust 载荷;不做语言类型判断。
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
/// ObjectStore 的错误/观察信息调用,返回静态诊断标签,不声明名义类型或继承。
|
||||
fn kind_name(&self) -> &'static str {
|
||||
"exception"
|
||||
}
|
||||
/// ObjectStore 设置普通实例字段前查询;允许对象头保存属性,不表示载荷私有字段可被任意直接修改。
|
||||
fn allows_attributes(&self) -> bool {
|
||||
true
|
||||
}
|
||||
/// Heap 追踪参数对象与两种异常链边;suppress_context 仅是显示数据,不得让 GC 漏掉上下文。
|
||||
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
|
||||
// 参数按原顺序逐个报告;String/Number 等即时值由统一助手跳过。
|
||||
for value in &self.args {
|
||||
trace_value(value, visit);
|
||||
}
|
||||
// flatten 仅跳过 None,不去重;两种链指向同一异常时仍是两条真实拥有边。
|
||||
for reference in [self.cause, self.context].into_iter().flatten() {
|
||||
visit(reference);
|
||||
}
|
||||
}
|
||||
/// ObjectStore::repr 组合名称和参数的 Rust Debug 文本;不递归调用参数对象的语言 repr。
|
||||
fn repr(&self, _identity: ObjectRef) -> Result<String, VmError> {
|
||||
Ok(format!(
|
||||
"{}({})",
|
||||
@@ -348,7 +452,9 @@ impl ObjectPayload for EndorBaseExceptionObject {
|
||||
.join(", ")
|
||||
))
|
||||
}
|
||||
/// ObjectStore::str 按参数形状生成消息;不自动拼接 cause/context、notes 或 traceback。
|
||||
fn str(&self, _identity: ObjectRef) -> Result<String, VmError> {
|
||||
// 零参数为空消息;单 Text/Number 用其内容;其他形状(含单 Object)保留 Rust Debug 形式。
|
||||
Ok(match self.args.as_slice() {
|
||||
[] => String::new(),
|
||||
[Value::Text(text)] => text.clone(),
|
||||
@@ -358,7 +464,9 @@ impl ObjectPayload for EndorBaseExceptionObject {
|
||||
}
|
||||
}
|
||||
|
||||
/// 内置载荷及其他对象的 trace 共用助手:只把 Value::Object 声明为拥有边,不 retain/release 或递归遍历子图。
|
||||
pub(crate) fn trace_value(value: &Value, visit: &mut dyn FnMut(ObjectRef)) {
|
||||
// Heap 的外层遍历负责后续子图;这里只有一层变体检查,即时值无需 GC 边。
|
||||
if let Value::Object(reference) = value {
|
||||
visit(*reference);
|
||||
}
|
||||
|
||||
@@ -1,26 +1,38 @@
|
||||
//! Heap 产生、Store/VM 取走、宿主缓存消费的变更通知数据;不保存对象所有权。
|
||||
//! 通知反映已提交的修改,不是可重放操作日志,也不保证消费时源对象仍存活。
|
||||
|
||||
use crate::ObjectRef;
|
||||
|
||||
/// A precise field change or a replacement of an object's native contents.
|
||||
/// Entries are notifications, never roots; a source can be dead when consumed.
|
||||
/// Heap::record_change 用于合并通知的键;同一对象的不同属性仍各自保留。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum ChangeKey {
|
||||
/// Heap 属性写入/删除发布具体字段名;消费者可只失效对应字段的观察。
|
||||
Attribute(String),
|
||||
/// 载荷替换、类型附着或共享引用更新改变整体内容;消费者按内容依赖处理。
|
||||
Contents,
|
||||
}
|
||||
|
||||
/// Heap::take_changes 从内部有序映射生成的单条合并通知,供宿主失效缓存。
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ObjectChange {
|
||||
/// 修改来源的完整借用身份,包含代际;不能当作有效拥有根直接使用。
|
||||
pub object: ObjectRef,
|
||||
/// 被修改的字段或整体载荷类别。
|
||||
pub key: ChangeKey,
|
||||
/// 此对象/键在本批最后一次通知的对象版本,不是批次序号。
|
||||
pub version: u64,
|
||||
}
|
||||
|
||||
/// Coalesced changes since the previous drain. Overflow requires the consumer
|
||||
/// to rescan its dependencies, rather than silently missing notifications.
|
||||
/// Store::take_changes 返回上次取走后累计发布的合并结果;取走时重置内部批次。
|
||||
/// 只读域中新建结果的允许写入更新对象版本,但不发布到此应用通知批次。
|
||||
/// 溢出会丢弃精确条目并要求全面重验,消费者不能把空 changes 解释为没有修改。
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ChangeBatch {
|
||||
/// 精确通知;同一对象/键重复修改仅保留最新版本,排序不表示实际修改先后。
|
||||
pub changes: Vec<ObjectChange>,
|
||||
/// true 时须全面重验所持依赖,不能仅处理 changes 中的对象。
|
||||
pub rescan_required: bool,
|
||||
}
|
||||
|
||||
/// Heap::record_change 的唯一对象/键数量上限;更新已有键不额外占用容量。
|
||||
pub(crate) const MAX_PENDING_CHANGES: usize = 4096;
|
||||
|
||||
+111
-20
@@ -1,3 +1,7 @@
|
||||
//! 编译器/宿主提交代码后在此构造不可变执行数据;core::frame 共享代码,eval 按偏移读取指令。
|
||||
//! 构造先验证名字布局、解码和控制流栈高度;这不是执行器,也不证明运行时值类型或调用一定成功。
|
||||
//! 常量中的对象句柄在 EndorCode 内只是表示,由堆上 EndorCodeObject 的 trace 建立托管强边。
|
||||
|
||||
use std::any::Any;
|
||||
use std::collections::{BTreeSet, VecDeque};
|
||||
use std::sync::Arc;
|
||||
@@ -6,39 +10,58 @@ use crate::builtins::trace_value;
|
||||
use crate::instruction::{compare_operation, decode, function_counts, invalid};
|
||||
use crate::{DecodedInstruction, Instruction, ObjectPayload, ObjectRef, Opcode, Value, VmError};
|
||||
|
||||
/// Local slots start with positional parameters. Captured locals occupy their
|
||||
/// original slots as cells; free-variable cells follow all local slots.
|
||||
/// 编译器生成、执行帧消费的槽布局:参数占局部槽前缀,捕获局部仍占原槽但存 Cell。
|
||||
/// 外部自由变量 Cell 追加在所有局部槽之后,不能与局部变量重名。
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct CodeLayout {
|
||||
/// 位置参数名称顺序,必须与 locals 前缀相同。
|
||||
pub parameters: Vec<String>,
|
||||
/// 全部局部名称,参数必须是其完整前缀。
|
||||
pub locals: Vec<String>,
|
||||
/// 需要提升为 Cell 的局部名称子集,不额外新增局部槽。
|
||||
pub cell_variables: Vec<String>,
|
||||
/// 从外层捕获的 Cell 名称,顺序决定闭包传入槽顺序。
|
||||
pub free_variables: Vec<String>,
|
||||
/// 全局与属性操作使用的文本名称索引表。
|
||||
pub names: Vec<String>,
|
||||
}
|
||||
|
||||
/// Source declaration metadata, separate from executable slot layout.
|
||||
/// 编译器传入的源码类型声明,与执行槽布局分开;类型名称在调用/契约检查时解析。
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SourceSignature {
|
||||
/// 位置参数的类型注解顺序,数量必须与代码参数槽数相同。
|
||||
pub parameters: Vec<String>,
|
||||
/// 非空的返回类型注解名称,尚不保证该名称可成功解析。
|
||||
pub return_type: String,
|
||||
/// 可选方法所属类型名,用于源码方法契约;Some 空串非法。
|
||||
pub method_owner: Option<String>,
|
||||
}
|
||||
|
||||
/// 已通过构造校验的 Rust 执行数据,尚未分配为托管代码对象;clone 不为常量对象增加根。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EndorCode {
|
||||
/// 保留的物理双字节单元编码,含扩展前缀。
|
||||
bytecode: Vec<u8>,
|
||||
/// 解码后按偏移排序的逻辑指令列表,执行时无需重新组合前缀。
|
||||
instructions: Vec<DecodedInstruction>,
|
||||
/// 常量表示;Rust clone 不为其中的对象句柄 retain。
|
||||
constants: Vec<Value>,
|
||||
/// 经 validate_layout 检查的槽和名称布局。
|
||||
layout: CodeLayout,
|
||||
/// 从可达控制流计算的最大栈高度,供帧预留容量。
|
||||
stack_size: usize,
|
||||
/// 诊断显示的代码名称。
|
||||
name: String,
|
||||
/// 诊断显示的源码文件名,不读取该文件。
|
||||
filename: String,
|
||||
/// 调用方给出的起始行,供回溯显示,不是逐指令行号表。
|
||||
first_line: usize,
|
||||
/// 可选源码声明;不改变已校验的指令布局。
|
||||
signature: Option<SourceSignature>,
|
||||
}
|
||||
|
||||
impl EndorCode {
|
||||
/// 宿主从原始字节码建立只有参数局部槽的代码;需要额外局部或本地 Cell 时用 with_layout。
|
||||
pub fn new(
|
||||
bytecode: Vec<u8>,
|
||||
constants: Vec<Value>,
|
||||
@@ -57,6 +80,7 @@ impl EndorCode {
|
||||
)
|
||||
}
|
||||
|
||||
/// 编译器与宿主把逻辑指令编码后走统一构造校验;此入口不替调用者修补跳转距离。
|
||||
pub fn from_instructions(
|
||||
instructions: &[Instruction],
|
||||
constants: Vec<Value>,
|
||||
@@ -65,13 +89,17 @@ impl EndorCode {
|
||||
Self::with_layout(Instruction::encode_all(instructions)?, constants, layout)
|
||||
}
|
||||
|
||||
/// 全部构造入口汇合处:先检查布局,再解码,最后验证索引、跳转和可达路径栈高度。
|
||||
pub fn with_layout(
|
||||
bytecode: Vec<u8>,
|
||||
constants: Vec<Value>,
|
||||
layout: CodeLayout,
|
||||
) -> Result<Self, VmError> {
|
||||
// 布局错误必须先拒绝,后续槽索引校验依赖这些不变量。
|
||||
validate_layout(&layout)?;
|
||||
// 合并 EXTENDED_ARG 并保留物理单元偏移,未知/截断编码在这里失败。
|
||||
let instructions = decode(&bytecode)?;
|
||||
// 校验成功后才建立可供执行器使用的代码,栈高只来自可达指令。
|
||||
let stack_size = verify(&instructions, constants.len(), &layout)?;
|
||||
Ok(Self {
|
||||
bytecode,
|
||||
@@ -86,7 +114,9 @@ impl EndorCode {
|
||||
})
|
||||
}
|
||||
|
||||
/// 编译器在代码构造后附加声明;这里只检查数量和非空字符串,不解析类型名。
|
||||
pub fn with_source_signature(mut self, signature: SourceSignature) -> Result<Self, VmError> {
|
||||
// 参数注解数量必须对应位置参数;每项与可选 owner 都不允许空串。
|
||||
if signature.parameters.len() != self.parameters().len()
|
||||
|| signature.return_type.is_empty()
|
||||
|| signature.parameters.iter().any(String::is_empty)
|
||||
@@ -102,20 +132,20 @@ impl EndorCode {
|
||||
self.signature = Some(signature);
|
||||
Ok(self)
|
||||
}
|
||||
/// Store 的源码调用/效果/契约检查借用声明;None 表示没有附加源码类型元数据。
|
||||
pub fn source_signature(&self) -> Option<&SourceSignature> {
|
||||
self.signature.as_ref()
|
||||
}
|
||||
|
||||
/// Conservative proof over validated instructions. Local assignment is not
|
||||
/// automatically pure: shared-reference assignment may update its old value.
|
||||
/// Unknown calls and writes remain unknown, even when they could be safe for
|
||||
/// a particular invocation. New opcodes require an explicit audit here.
|
||||
/// Store/effects 的保守只读证明:所有指令(包括不可达部分)都须在白名单内。
|
||||
/// 局部赋值可能触发共享引用更新,不自动视为纯;未知调用/写入也不在这里证明安全。
|
||||
pub fn is_statically_read_only(&self) -> bool {
|
||||
self.instructions
|
||||
.iter()
|
||||
.all(|decoded| read_only_opcode(decoded.instruction.opcode))
|
||||
}
|
||||
|
||||
/// 编译器/宿主附加诊断位置;不校验文件存在或起始行范围,也不改变执行布局。
|
||||
pub fn with_location(
|
||||
mut self,
|
||||
name: impl Into<String>,
|
||||
@@ -128,55 +158,67 @@ impl EndorCode {
|
||||
self
|
||||
}
|
||||
|
||||
/// 宿主诊断/序列查看借用原编码;不允许绕过验证修改字节。
|
||||
pub fn bytecode(&self) -> &[u8] {
|
||||
&self.bytecode
|
||||
}
|
||||
/// eval、效果分析与诊断借用已解码逻辑指令。
|
||||
pub fn instructions(&self) -> &[DecodedInstruction] {
|
||||
&self.instructions
|
||||
}
|
||||
/// 执行器读取常量,代码载荷 trace 枚举其对象边;返回切片不新增托管根。
|
||||
pub fn constants(&self) -> &[Value] {
|
||||
&self.constants
|
||||
}
|
||||
/// 帧参数绑定与函数默认值校验读取参数顺序。
|
||||
pub fn parameters(&self) -> &[String] {
|
||||
&self.layout.parameters
|
||||
}
|
||||
/// 函数闭包校验和帧初始化读取捕获槽顺序。
|
||||
pub fn free_variables(&self) -> &[String] {
|
||||
&self.layout.free_variables
|
||||
}
|
||||
/// 编译器/帧与 Store 分析借用完整布局。
|
||||
pub fn layout(&self) -> &CodeLayout {
|
||||
&self.layout
|
||||
}
|
||||
/// 执行帧用可达路径最大栈高预留容量,不是执行步数上限。
|
||||
pub fn stack_size(&self) -> usize {
|
||||
self.stack_size
|
||||
}
|
||||
/// 错误和代码 repr 读取诊断名称。
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
/// 回溯与 repr 读取文件标识,不访问文件系统。
|
||||
pub fn filename(&self) -> &str {
|
||||
&self.filename
|
||||
}
|
||||
/// 回溯/诊断读取调用方设置的起始行。
|
||||
pub fn first_line(&self) -> usize {
|
||||
self.first_line
|
||||
}
|
||||
/// 帧创建 localsplus 时计算局部槽加外部自由变量槽的总数。
|
||||
pub fn slot_count(&self) -> usize {
|
||||
self.layout.locals.len() + self.layout.free_variables.len()
|
||||
}
|
||||
/// Whether a local/free slot holds a managed closure cell.
|
||||
/// 帧初始化/闭包访问查询槽是否存 Cell,越界槽返回 false。
|
||||
pub fn is_cell_slot(&self, slot: usize) -> bool {
|
||||
is_cell_slot(&self.layout, slot)
|
||||
}
|
||||
/// Name of a local/free slot used by validated instructions.
|
||||
/// 执行器为已验证槽生成未绑定变量等诊断时查询名称。
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the slot is outside this code's local/free-variable layout.
|
||||
/// slot 超出局部/自由变量布局时 panic;调用者不能传入未经边界检查的槽。
|
||||
pub fn slot_name(&self, slot: usize) -> &str {
|
||||
// 局部槽保留原顺序,自由变量索引从局部槽末尾偏移。
|
||||
if slot < self.layout.locals.len() {
|
||||
&self.layout.locals[slot]
|
||||
} else {
|
||||
&self.layout.free_variables[slot - self.layout.locals.len()]
|
||||
}
|
||||
}
|
||||
/// Look up a logical instruction by its code-unit offset.
|
||||
/// eval 按指令指针查询完整逻辑起点;非法偏移返回带位置的字节码错误。
|
||||
pub fn instruction_at(&self, offset: usize) -> Result<DecodedInstruction, VmError> {
|
||||
instruction_index(&self.instructions, offset)
|
||||
.map(|index| self.instructions[index])
|
||||
@@ -189,45 +231,52 @@ impl EndorCode {
|
||||
}
|
||||
}
|
||||
|
||||
/// Heap-allocated, immutable and shareable executable. Function objects hold a
|
||||
/// managed reference to this object rather than embedding a copy of its code.
|
||||
/// Store::new_code 分配的代码载荷;函数持有其托管引用,帧可共享内部不可变执行数据。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EndorCodeObject {
|
||||
/// 共享不可变 Rust 执行数据;Arc 本身不是常量对象的托管根。
|
||||
code: Arc<EndorCode>,
|
||||
}
|
||||
|
||||
impl EndorCodeObject {
|
||||
/// Store::new_code 将已校验执行数据包装成待分配载荷,共享数据由 Arc 持有。
|
||||
pub fn new(code: EndorCode) -> Self {
|
||||
Self {
|
||||
code: Arc::new(code),
|
||||
}
|
||||
}
|
||||
/// Store 校验函数引用的代码布局时借用共享执行数据。
|
||||
pub fn code(&self) -> &EndorCode {
|
||||
&self.code
|
||||
}
|
||||
/// Share immutable executable data without copying constants or wordcode.
|
||||
/// This Rust Arc does not retain heap objects referenced by constants;
|
||||
/// callers must keep the managed code object rooted while using them.
|
||||
/// core::frame 共享执行数据,不深拷贝常量和编码;Arc 不是常量托管对象的拥有根。
|
||||
/// 调用方使用常量时仍须保持托管代码对象有根,不能只靠此 Arc 延长常量对象寿命。
|
||||
pub fn shared_code(&self) -> Arc<EndorCode> {
|
||||
Arc::clone(&self.code)
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectPayload for EndorCodeObject {
|
||||
/// 不可变代码载荷不实现原地赋值,允许只读局部引用重绑定。
|
||||
fn reference_assignment_only(&self) -> bool {
|
||||
true
|
||||
}
|
||||
/// Store::payload 的 Rust 下转型入口。
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
/// 代码载荷的诊断类别,不是名义类型判断。
|
||||
fn kind_name(&self) -> &'static str {
|
||||
"code"
|
||||
}
|
||||
/// Heap 分配/替换代码载荷时登记常量对象边;不递归扫描其指令或源码文本。
|
||||
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
|
||||
// 仅对象变体贡献边,重复出现的同一对象仍逐次计数。
|
||||
for value in self.code.constants() {
|
||||
trace_value(value, visit);
|
||||
}
|
||||
}
|
||||
/// Store::repr 用代码名/文件/起始行生成诊断,不运行代码。
|
||||
fn repr(&self, _identity: ObjectRef) -> Result<String, VmError> {
|
||||
Ok(format!(
|
||||
"<code {} at {}:{}>",
|
||||
@@ -238,7 +287,8 @@ impl ObjectPayload for EndorCodeObject {
|
||||
}
|
||||
}
|
||||
|
||||
// Shared by the local proof and call-graph analysis. Unknown opcodes fail closed.
|
||||
// 白名单之外一律不在此处证明只读;动态调用图分析可以另行判断已知调用。
|
||||
/// 本地只读证明和 effects 调用图分析共享白名单;新增指令需显式审查其副作用。
|
||||
pub(crate) fn read_only_opcode(opcode: Opcode) -> bool {
|
||||
matches!(
|
||||
opcode,
|
||||
@@ -269,7 +319,9 @@ pub(crate) fn read_only_opcode(opcode: Opcode) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
/// with_layout 在解码前调用,验证各名称表内部唯一性以及局部/捕获槽的交叉关系。
|
||||
fn validate_layout(layout: &CodeLayout) -> Result<(), VmError> {
|
||||
// 每类名称表都检查内部唯一性,不能把全部表混在一个 seen 中。
|
||||
for names in [
|
||||
&layout.parameters,
|
||||
&layout.locals,
|
||||
@@ -277,8 +329,11 @@ fn validate_layout(layout: &CodeLayout) -> Result<(), VmError> {
|
||||
&layout.free_variables,
|
||||
&layout.names,
|
||||
] {
|
||||
// 每张表单独去重;不同表间允许的重名关系在后面单独检查。
|
||||
let mut seen = BTreeSet::new();
|
||||
// 空名称无法可靠绑定,表内重复会使槽或名称索引产生歧义。
|
||||
for name in names {
|
||||
// 发现首个违规就退出,构造不产生可执行半成品。
|
||||
if name.is_empty() || !seen.insert(name) {
|
||||
return Err(VmError::InvalidArgument(
|
||||
"empty or duplicate code variable name",
|
||||
@@ -286,11 +341,13 @@ fn validate_layout(layout: &CodeLayout) -> Result<(), VmError> {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 帧按局部槽前缀绑定参数,不能仅检查包含关系。
|
||||
if !layout.locals.starts_with(&layout.parameters) {
|
||||
return Err(VmError::InvalidArgument(
|
||||
"parameter names must lead local slots",
|
||||
));
|
||||
}
|
||||
// Cell 局部必须来自 locals;外部自由变量不得同时作为本地变量。
|
||||
if layout
|
||||
.cell_variables
|
||||
.iter()
|
||||
@@ -307,22 +364,29 @@ fn validate_layout(layout: &CodeLayout) -> Result<(), VmError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 代码公共查询与操作数校验共用:外部自由变量槽全是 Cell,局部槽按名称判断。
|
||||
fn is_cell_slot(layout: &CodeLayout, slot: usize) -> bool {
|
||||
// 自由变量段范围内都为 Cell;段外返回 false,避免越界索引。
|
||||
if slot >= layout.locals.len() {
|
||||
slot < layout.locals.len() + layout.free_variables.len()
|
||||
} else {
|
||||
// 局部槽按名字判断是否提升为 Cell,不改变其原有位置。
|
||||
layout.cell_variables.contains(&layout.locals[slot])
|
||||
}
|
||||
}
|
||||
|
||||
/// instruction_at、跳转验证与栈分析用二分查找逻辑起点;前缀中段不是合法目标。
|
||||
fn instruction_index(instructions: &[DecodedInstruction], offset: usize) -> Option<usize> {
|
||||
instructions
|
||||
.binary_search_by_key(&offset, |instruction| instruction.offset)
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// DecodedInstruction 与 verify 共用相对偏移计算;此助手只算数,不验证目标边界。
|
||||
pub(crate) fn jump_target(instruction: DecodedInstruction) -> Result<usize, VmError> {
|
||||
// 操作数是代码单元距离,不是字节数或逻辑指令数量。
|
||||
let argument = instruction.instruction.argument as usize;
|
||||
// 仅 JumpBackward 做减法,条件跳转与 JumpForward 做加法;checked 防止绕回。
|
||||
let target = if instruction.instruction.opcode == Opcode::JumpBackward {
|
||||
instruction.next_offset.checked_sub(argument)
|
||||
} else {
|
||||
@@ -331,17 +395,20 @@ pub(crate) fn jump_target(instruction: DecodedInstruction) -> Result<usize, VmEr
|
||||
target.ok_or_else(|| invalid(instruction.offset, "jump offset overflow"))
|
||||
}
|
||||
|
||||
/// Validate every operand, then propagate stack heights through reachable
|
||||
/// control flow. Bad jumps, underflow, mismatched joins and fallthrough fail
|
||||
/// before execution; loops must return to the same stack height.
|
||||
/// 拒绝非法索引/跳转、可达栈下溢、路径汇合高度不符和落出代码;循环回边必须保持同高。
|
||||
/// with_layout 解码后调用;先检查全部指令操作数,再分析入口可达控制流并返回最大栈高。
|
||||
fn verify(
|
||||
instructions: &[DecodedInstruction],
|
||||
constants: usize,
|
||||
layout: &CodeLayout,
|
||||
) -> Result<usize, VmError> {
|
||||
// 操作数检查覆盖全部指令,包括入口不可达部分。
|
||||
for instruction in instructions {
|
||||
// 逻辑操作码与完整 u32 参数;前缀已由解码器合并。
|
||||
let Instruction { opcode, argument } = instruction.instruction;
|
||||
// 表索引以 usize 使用;其上界由对应布局或常量数量限定。
|
||||
let index = argument as usize;
|
||||
// Fast 不能指向 Cell,Deref 必须指向 Cell;跳转必须落在完整逻辑起点。
|
||||
let valid = match opcode {
|
||||
Opcode::LoadConst => index < constants,
|
||||
Opcode::LoadFast | Opcode::StoreFast | Opcode::DeleteFast => {
|
||||
@@ -368,6 +435,7 @@ fn verify(
|
||||
Opcode::ExtendedArg => false,
|
||||
_ => true,
|
||||
};
|
||||
// 将错误定位到指令起点,尚未开始传播栈状态。
|
||||
if !valid {
|
||||
return Err(invalid(
|
||||
instruction.offset,
|
||||
@@ -375,15 +443,25 @@ fn verify(
|
||||
));
|
||||
}
|
||||
}
|
||||
// None 表示尚未从入口到达,不表示其操作数未检查。
|
||||
let mut heights = vec![None; instructions.len()];
|
||||
// decode 保证列表非空;代码入口求值栈为空。
|
||||
heights[0] = Some(0_usize);
|
||||
// 工作队列保存待展开的指令索引,每个新到达位置只加入一次。
|
||||
let mut pending = VecDeque::from([0]);
|
||||
// 全部可达指令执行前后的最大栈高度。
|
||||
let mut maximum = 0;
|
||||
// 前向/回向边共用工作队列,汇合高度一致时不重复展开。
|
||||
while let Some(index) = pending.pop_front() {
|
||||
// 队列只保存已赋高度的位置,因此下方 unwrap 由入队不变量保证。
|
||||
let instruction = instructions[index];
|
||||
// 当前指令执行前高度,不含任何类型或值内容信息。
|
||||
let height = heights[index].unwrap();
|
||||
// 逻辑操作码与完整 u32 参数;前缀已由解码器合并。
|
||||
let Instruction { opcode, argument } = instruction.instruction;
|
||||
// 完整 u32 操作数转成当前平台索引/数量,用于下面栈需求与布局计算。
|
||||
let argument = argument as usize;
|
||||
// required 是最深访问需求,popped/pushed 是净变化来源;Copy/Swap 不弹出其深层项。
|
||||
let (required, popped, pushed) = match opcode {
|
||||
Opcode::LoadConst
|
||||
| Opcode::LoadFast
|
||||
@@ -407,19 +485,23 @@ fn verify(
|
||||
Opcode::Copy => (argument, 0, 1),
|
||||
Opcode::Swap => (argument, 0, 0),
|
||||
Opcode::Call => {
|
||||
// 显式参数之外还消费函数与接收者/Null 两项,先检查加法溢出。
|
||||
let consumed = argument
|
||||
.checked_add(2)
|
||||
.ok_or_else(|| invalid(instruction.offset, "call operand overflow"))?;
|
||||
(consumed, consumed, 1)
|
||||
}
|
||||
Opcode::BuildMap => {
|
||||
// 每对键值占两项,先检查乘法溢出。
|
||||
let consumed = argument
|
||||
.checked_mul(2)
|
||||
.ok_or_else(|| invalid(instruction.offset, "map operand overflow"))?;
|
||||
(consumed, consumed, 1)
|
||||
}
|
||||
Opcode::MakeFunction => {
|
||||
// 函数创建消费默认值、捕获 Cell 和一个代码对象,压回一个函数。
|
||||
let (defaults, closure) = function_counts(instruction.instruction.argument);
|
||||
// 两个计数来自 16 位段,总和加代码对象项有界。
|
||||
let consumed = defaults + closure + 1;
|
||||
(consumed, consumed, 1)
|
||||
}
|
||||
@@ -427,17 +509,21 @@ fn verify(
|
||||
Opcode::ReturnValue => (1, 1, 0),
|
||||
_ => (0, 0, 0),
|
||||
};
|
||||
// 在减 popped 前检查足够高度,避免栈下溢。
|
||||
if height < required {
|
||||
return Err(invalid(instruction.offset, "evaluation stack underflow"));
|
||||
}
|
||||
// 返回必须恰好留下一个结果供移出,禁止帧内遗留其他栈项。
|
||||
if opcode == Opcode::ReturnValue && height != 1 {
|
||||
return Err(invalid(
|
||||
instruction.offset,
|
||||
"RETURN_VALUE must leave an empty frame stack",
|
||||
));
|
||||
}
|
||||
// 校验保证减法安全;随后统计当前和后继高度。
|
||||
let next_height = height - popped + pushed;
|
||||
maximum = maximum.max(height).max(next_height);
|
||||
// 返回无后继;条件跳转同时传播跳转/顺序两条路径,不猜测条件值。
|
||||
let successors: Vec<usize> = match opcode {
|
||||
Opcode::ReturnValue => Vec::new(),
|
||||
Opcode::JumpForward | Opcode::JumpBackward => vec![jump_target(instruction)?],
|
||||
@@ -446,11 +532,15 @@ fn verify(
|
||||
}
|
||||
_ => vec![instruction.next_offset],
|
||||
};
|
||||
// 每条后继都必须指向现存逻辑指令,顺序落到代码末尾也属于错误。
|
||||
for successor in successors {
|
||||
// 将物理单元偏移转换为逻辑指令索引,拒绝扩展前缀中部。
|
||||
let target = instruction_index(instructions, successor).ok_or_else(|| {
|
||||
invalid(instruction.offset, "execution falls off the code object")
|
||||
})?;
|
||||
// 已到达位置只能接受同高;新位置设置高度并排队。
|
||||
match heights[target] {
|
||||
// 回边或分支汇合必须与第一次到达时的高度完全一致。
|
||||
Some(existing) if existing != next_height => {
|
||||
return Err(invalid(
|
||||
successor,
|
||||
@@ -458,6 +548,7 @@ fn verify(
|
||||
));
|
||||
}
|
||||
Some(_) => {}
|
||||
// 首次到达的后继写入高度,之后排队检查它的栈需求。
|
||||
None => {
|
||||
heights[target] = Some(next_height);
|
||||
pending.push_back(target);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
//! Store 类型/能力方法的替换约束;types 子模块可读取方法表,无需向宿主公开可变内部布局。
|
||||
//! 参数要求逆变、返回要求协变;检查只观察当前定义,拟议更新通过 Patch 预演,不先修改再回滚。
|
||||
//! UnknownAnnotation 仅在显式 defer_unknown 时暂缓,调用时检查使用完整解析路径。
|
||||
//! Method substitution rules. This is a child of types so validation can inspect
|
||||
//! method tables without exporting mutable definition internals.
|
||||
use super::{AnnotationType, EndorTypeObject};
|
||||
@@ -7,18 +10,25 @@ use crate::{
|
||||
};
|
||||
|
||||
struct Contract {
|
||||
/// 可接受的位置参数区间,包括接收者。
|
||||
arity: CallArity,
|
||||
/// 固定位置参数约束。
|
||||
parameters: Vec<CallType>,
|
||||
/// 固定部分之后的统一参数约束。
|
||||
variadic: Option<CallType>,
|
||||
/// 实现返回值必须落入父契约或能力允许范围。
|
||||
returns: CallType,
|
||||
/// 已知整体效果,None 表示不能证明,不等于 ReadOnly。
|
||||
effect: Option<CallEffect>,
|
||||
}
|
||||
impl Contract {
|
||||
/// 签名替换检查按位置选固定参数,超出固定部分回退可变参数约束。
|
||||
fn parameter(&self, index: usize) -> Option<&CallType> {
|
||||
self.parameters.get(index).or(self.variadic.as_ref())
|
||||
}
|
||||
}
|
||||
impl From<CallSignature> for Contract {
|
||||
/// 原生签名转内部契约,Delegated 不是已知整次调用效果,转换为 None。
|
||||
fn from(value: CallSignature) -> Self {
|
||||
Self {
|
||||
arity: value.arity,
|
||||
@@ -35,25 +45,35 @@ impl From<CallSignature> for Contract {
|
||||
}
|
||||
#[derive(Clone, Copy)]
|
||||
struct Patch<'a> {
|
||||
/// 拟议更新所属的类型身份,借用。
|
||||
owner: ObjectRef,
|
||||
/// 拟议更新的方法名,仅精确匹配此名。
|
||||
name: &'a str,
|
||||
/// 拟议的新函数身份,不由 Patch 保留根。
|
||||
function: ObjectRef,
|
||||
}
|
||||
|
||||
struct TraitCheckGuard<'a> {
|
||||
/// 借用递归校验状态表。
|
||||
store: &'a ObjectStore,
|
||||
/// 创建守卫时的线程身份。
|
||||
thread: std::thread::ThreadId,
|
||||
/// 当前定义/能力对,Drop 时移除。
|
||||
key: (ObjectRef, ObjectRef),
|
||||
}
|
||||
impl Drop for TraitCheckGuard<'_> {
|
||||
/// 递归校验 RAII 清理,正常/报错/展开路径都移除本线程当前定义与能力对。
|
||||
fn drop(&mut self) {
|
||||
// 取得线程校验表的锁,守卫只会在成功插入 key 后创建。
|
||||
let mut checks = self
|
||||
.store
|
||||
.active_trait_checks
|
||||
.lock()
|
||||
.expect("trait checker poisoned");
|
||||
// 只删除当前递归对,不影响同线程其他活跃校验。
|
||||
let active = checks.get_mut(&self.thread).expect("active trait check");
|
||||
active.remove(&self.key);
|
||||
// 线程没有剩余递归检查时移除空项。
|
||||
if active.is_empty() {
|
||||
checks.remove(&self.thread);
|
||||
}
|
||||
@@ -64,12 +84,19 @@ impl CallType {
|
||||
/// Whether every value admitted by provided is admitted by self.
|
||||
/// Nominal inheritance and explicit capabilities participate; no structural
|
||||
/// or callable-signature inference is implied by this relation.
|
||||
/// 替换校验的值集合包含关系:provided 的每项都必须被 self 的至少一项接纳。
|
||||
/// check_substitution 用它检查参数逆变和返回协变;只检查声明,不调用实现或保留定义根。
|
||||
pub fn accepts_type(&self, store: &ObjectStore, provided: &Self) -> Result<bool, VmError> {
|
||||
// 先验证联合全部定义边,再做包含/匹配判断,不能让无效分支藏在后面。
|
||||
self.validate(store)?;
|
||||
provided.validate(store)?;
|
||||
// provided 是待被接纳的值集合,必须逐个候选分支都被覆盖。
|
||||
for actual in provided.alternatives() {
|
||||
// 每个 provided 分支单独寻找一个可接受它的 self 分支。
|
||||
let mut accepted = false;
|
||||
// self 联合只需一个分支能接纳当前 actual,不要求所有分支都匹配。
|
||||
for expected in self.alternatives() {
|
||||
// Any 接受所有值;名义继承和显式能力参与,不推断结构相似或可调用签名。
|
||||
accepted = match (*expected, *actual) {
|
||||
(AnnotationType::Any, _) => true,
|
||||
(AnnotationType::Nominal(base), AnnotationType::Nominal(child)) => {
|
||||
@@ -81,10 +108,12 @@ impl CallType {
|
||||
) => store.implements_trait(definition, capability)?,
|
||||
(expected, actual) => expected == actual,
|
||||
};
|
||||
// 当前 actual 已找到覆盖分支即可结束内层循环。
|
||||
if accepted {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 存在一个未被接纳的 provided 分支即整体不包含。
|
||||
if !accepted {
|
||||
return Ok(false);
|
||||
}
|
||||
@@ -94,14 +123,18 @@ impl CallType {
|
||||
}
|
||||
|
||||
impl ObjectStore {
|
||||
/// types::validate_trait 调用的递归守卫;同线程同定义/能力对重入时暂不重复展开。
|
||||
pub(super) fn with_trait_check(
|
||||
&self,
|
||||
definition: ObjectRef,
|
||||
capability: ObjectRef,
|
||||
check: impl FnOnce() -> Result<(), VmError>,
|
||||
) -> Result<(), VmError> {
|
||||
// 递归重入按线程隔离,避免把其他线程的检查误作当前递归。
|
||||
let thread = std::thread::current().id();
|
||||
// 重入身份包含类型定义与能力,不仅是单个类型。
|
||||
let key = (definition, capability);
|
||||
// 同线程同定义/能力对已经在检查时返回,避免递归契约无限展开。
|
||||
if !self
|
||||
.active_trait_checks
|
||||
.lock()
|
||||
@@ -112,6 +145,7 @@ impl ObjectStore {
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
// 锁已释放,守卫负责退出时清除 key;执行 check 时不持全局校验锁。
|
||||
let _guard = TraitCheckGuard {
|
||||
store: self,
|
||||
thread,
|
||||
@@ -119,14 +153,17 @@ impl ObjectStore {
|
||||
};
|
||||
check()
|
||||
}
|
||||
/// 替换检查读取原生签名或解析源码签名,缺失元数据返回 None,不猜测类型。
|
||||
fn method_contract(
|
||||
&self,
|
||||
function: ObjectRef,
|
||||
owner: ObjectRef,
|
||||
) -> Result<Option<Contract>, VmError> {
|
||||
// 优先使用已解析原生签名,其引用有效性由 Store 的签名入口校验。
|
||||
if let Some(native) = self.native_signature(function)? {
|
||||
return Ok(Some(native.into()));
|
||||
}
|
||||
// 既非原生完整签名又非源码函数时无法取得契约。
|
||||
let Some(data) = self
|
||||
.object(function)?
|
||||
.payload()
|
||||
@@ -135,15 +172,23 @@ impl ObjectStore {
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
// 源码类型声明在函数指向的代码对象内,不从函数名字猜测。
|
||||
let code = self.payload::<EndorCodeObject>(data.code(), "code")?.code();
|
||||
// 源码没有声明就返回 None,调用方决定缺失元数据是否允许。
|
||||
let Some(signature) = code.source_signature() else {
|
||||
return Ok(None);
|
||||
};
|
||||
// 解析源码 owner 名称时考虑继承中的真实声明,避免只查全局同名对象。
|
||||
let resolve = |name: &str| {
|
||||
// 仅源码声明的 owner 名称走特殊继承解析,其余名称回退常规注解解析。
|
||||
if signature.method_owner.as_deref() == Some(name) {
|
||||
// 从当前声明 owner 向基类查找,不扫描任意后代。
|
||||
let mut current = Some(owner);
|
||||
// 逐层读取不可变类型定义,必要时确认父层方法身份仍是当前函数。
|
||||
while let Some(definition) = current {
|
||||
// 读取类型载荷并验证句柄,后续方法表与基类访问都基于该借用。
|
||||
let data = self.payload::<EndorTypeObject>(definition, "Type")?;
|
||||
// 名称相同还需属于当前 owner 或确实在父方法表持有该函数。
|
||||
if data.name == name
|
||||
&& (definition == owner
|
||||
|| data
|
||||
@@ -158,6 +203,7 @@ impl ObjectStore {
|
||||
}
|
||||
self.annotation_type(function, name, None)
|
||||
};
|
||||
// 逐个解析全部参数注解,首个错误即传播,不返回部分契约。
|
||||
let parameters = signature
|
||||
.parameters
|
||||
.iter()
|
||||
@@ -173,12 +219,14 @@ impl ObjectStore {
|
||||
effect: self.call_effect(function)?,
|
||||
}))
|
||||
}
|
||||
/// 查类型自身方法,若 Patch 精确匹配该类型/名称则读取拟议替代函数。
|
||||
fn own_method(
|
||||
&self,
|
||||
owner: ObjectRef,
|
||||
name: &str,
|
||||
patch: Option<Patch<'_>>,
|
||||
) -> Result<Option<ObjectRef>, VmError> {
|
||||
// 只在 owner 和 name 都相同的查询位置替换视图,不修改真实方法表。
|
||||
if let Some(patch) = patch
|
||||
&& patch.owner == owner
|
||||
&& patch.name == name
|
||||
@@ -191,13 +239,16 @@ impl ObjectStore {
|
||||
.get(name)
|
||||
.copied())
|
||||
}
|
||||
/// 沿单继承链寻找最近方法及声明 owner,支持拟议 Patch 的查找视图。
|
||||
fn inherited_method(
|
||||
&self,
|
||||
mut owner: Option<ObjectRef>,
|
||||
name: &str,
|
||||
patch: Option<Patch<'_>>,
|
||||
) -> Result<Option<(ObjectRef, ObjectRef)>, VmError> {
|
||||
// 向基类遍历;不假定候选函数一定是接收者当前生效的方法。
|
||||
while let Some(definition) = owner {
|
||||
// 最靠近当前类型的声明遮蔽更远的同名父方法。
|
||||
if let Some(function) = self.own_method(definition, name, patch)? {
|
||||
return Ok(Some((definition, function)));
|
||||
}
|
||||
@@ -205,6 +256,7 @@ impl ObjectStore {
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
/// 检查当前方法替换最近父方法是否可代换;父方法无类型元数据时保持兼容。
|
||||
fn check_override(
|
||||
&self,
|
||||
owner: ObjectRef,
|
||||
@@ -213,32 +265,40 @@ impl ObjectStore {
|
||||
patch: Option<Patch<'_>>,
|
||||
defer_unknown: bool,
|
||||
) -> Result<(), VmError> {
|
||||
// 检查 owner 类型载荷,诊断使用其声明名称。
|
||||
let definition = self.payload::<EndorTypeObject>(owner, "Type")?;
|
||||
// 没有同名父方法即不是 override,无需普通替换检查。
|
||||
let Some((base_owner, base_function)) =
|
||||
self.inherited_method(definition.base, name, patch)?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
// 闭包收集解析/校验结果,外层仅在允许时暂缓 UnknownAnnotation。
|
||||
let checked = (|| {
|
||||
// 父方法没有完整元数据时不新增更严格的旧接口约束。
|
||||
let Some(base) = self.method_contract(base_function, base_owner)? else {
|
||||
return Ok(());
|
||||
};
|
||||
// 按当前调用用途构造带类型/能力与方法定位的错误,不改变验证逻辑。
|
||||
let invalid = |reason| VmError::OverrideViolation {
|
||||
definition: definition.name.clone(),
|
||||
method: name.into(),
|
||||
reason,
|
||||
};
|
||||
// 父契约存在时,派生实现也必须提供完整可检查的类型元数据。
|
||||
let derived = self
|
||||
.method_contract(function, owner)?
|
||||
.ok_or_else(|| invalid("override lacks type metadata"))?;
|
||||
self.check_substitution(owner, &derived, &base, invalid)?;
|
||||
Ok(())
|
||||
})();
|
||||
// 只有 UnknownAnnotation 可在 defer_unknown 时暂缓,其他失败始终传播。
|
||||
match checked {
|
||||
Err(VmError::UnknownAnnotation(_)) if defer_unknown => Ok(()),
|
||||
result => result,
|
||||
}
|
||||
}
|
||||
/// 父方法/能力签名的公共替换算法;所有不兼容原因交给调用方构造具体错误。
|
||||
fn check_substitution(
|
||||
&self,
|
||||
owner: ObjectRef,
|
||||
@@ -246,12 +306,15 @@ impl ObjectStore {
|
||||
base: &Contract,
|
||||
invalid: impl Fn(&'static str) -> VmError,
|
||||
) -> Result<(), VmError> {
|
||||
// 实现不得要求更多必填参数,也不得缩小最多可接收参数数量。
|
||||
if derived.arity.required > base.arity.required
|
||||
|| derived.arity.maximum < base.arity.maximum
|
||||
{
|
||||
return Err(invalid("override narrows accepted argument counts"));
|
||||
}
|
||||
// 接收者按实现声明类型核对,不能只对比两份签名的第零项文本。
|
||||
let receiver = CallType::from(AnnotationType::Nominal(owner));
|
||||
// 派生签名必须接受它自己声明类型的实例作为接收者。
|
||||
if !derived
|
||||
.parameter(0)
|
||||
.ok_or_else(|| invalid("override lacks receiver"))?
|
||||
@@ -259,6 +322,7 @@ impl ObjectStore {
|
||||
{
|
||||
return Err(invalid("override receiver rejects its declaring type"));
|
||||
}
|
||||
// 父/能力签名必须接纳实现类型,返回值则必须落在父约束范围。
|
||||
if !base
|
||||
.parameter(0)
|
||||
.ok_or_else(|| invalid("required signature lacks receiver"))?
|
||||
@@ -266,48 +330,62 @@ impl ObjectStore {
|
||||
{
|
||||
return Err(invalid("required receiver rejects implementing type"));
|
||||
}
|
||||
// 检查双方固定参数覆盖到的最大位置,但不超过父方法允许上界。
|
||||
let fixed = base
|
||||
.parameters
|
||||
.len()
|
||||
.max(derived.parameters.len())
|
||||
.min(base.arity.maximum);
|
||||
// 跳过已单独核对的接收者,第一个普通参数起检查逆变。
|
||||
for index in 1..fixed {
|
||||
// 调用方按父契约可传的类型集合,实现必须全部接纳。
|
||||
let expected = base
|
||||
.parameter(index)
|
||||
.ok_or_else(|| invalid("incomplete base parameter metadata"))?;
|
||||
// 实现的实际参数/方法契约必须存在,不能通过缺失元数据绕过检查。
|
||||
let actual = derived
|
||||
.parameter(index)
|
||||
.ok_or_else(|| invalid("incomplete override parameter metadata"))?;
|
||||
// 逆变方向是 actual 接纳 expected,反过来会错误允许缩窄参数。
|
||||
if !actual.accepts_type(self, expected)? {
|
||||
return Err(invalid("override narrows a parameter type"));
|
||||
}
|
||||
}
|
||||
// 父契约还有固定段之后的位置时,用该位置的尾部约束核对其余参数。
|
||||
if base.arity.maximum > fixed {
|
||||
// 调用方按父契约可传的类型集合,实现必须全部接纳。
|
||||
let expected = base
|
||||
.parameter(fixed)
|
||||
.ok_or_else(|| invalid("incomplete base variadic metadata"))?;
|
||||
// 实现的实际参数/方法契约必须存在,不能通过缺失元数据绕过检查。
|
||||
let actual = derived
|
||||
.parameter(fixed)
|
||||
.ok_or_else(|| invalid("incomplete override variadic metadata"))?;
|
||||
// 逆变方向是 actual 接纳 expected,反过来会错误允许缩窄参数。
|
||||
if !actual.accepts_type(self, expected)? {
|
||||
return Err(invalid("override narrows a variadic parameter type"));
|
||||
}
|
||||
}
|
||||
// 父/能力签名必须接纳实现类型,返回值则必须落在父约束范围。
|
||||
if !base.returns.accepts_type(self, &derived.returns)? {
|
||||
return Err(invalid("override widens the return type"));
|
||||
}
|
||||
// 仅双方效果都已知时比较;未知效果不在此处强行推断。
|
||||
if let (Some(expected), Some(actual)) = (base.effect, derived.effect) {
|
||||
// 只读只能替换为只读;Mutating 不接受 External;External/Delegated 不再收紧。
|
||||
let compatible = match expected {
|
||||
CallEffect::ReadOnly => actual == CallEffect::ReadOnly,
|
||||
CallEffect::Mutating => actual != CallEffect::External,
|
||||
CallEffect::External | CallEffect::Delegated => true,
|
||||
};
|
||||
// 效果扩大报替换错误,不靠实际执行检测。
|
||||
if !compatible {
|
||||
return Err(invalid("override expands known effects"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// types 能力验证把实现方法与声明签名比较,报 TraitViolation 而非普通覆盖错误。
|
||||
pub(super) fn validate_typed_requirement(
|
||||
&self,
|
||||
definition: ObjectRef,
|
||||
@@ -317,6 +395,7 @@ impl ObjectStore {
|
||||
expected: &CallSignature,
|
||||
defer_unknown: bool,
|
||||
) -> Result<(), VmError> {
|
||||
// 按当前调用用途构造带类型/能力与方法定位的错误,不改变验证逻辑。
|
||||
let invalid = |reason| VmError::TraitViolation {
|
||||
capability: self
|
||||
.payload::<super::EndorTraitObject>(capability, "Trait")
|
||||
@@ -326,38 +405,49 @@ impl ObjectStore {
|
||||
method: name.into(),
|
||||
reason,
|
||||
};
|
||||
// 闭包收集解析/校验结果,外层仅在允许时暂缓 UnknownAnnotation。
|
||||
let checked = (|| {
|
||||
// 实现的实际参数/方法契约必须存在,不能通过缺失元数据绕过检查。
|
||||
let actual = self
|
||||
.method_contract(function, definition)?
|
||||
.ok_or_else(|| invalid("required member lacks type metadata"))?;
|
||||
self.check_substitution(definition, &actual, &expected.clone().into(), invalid)
|
||||
})();
|
||||
// 只有 UnknownAnnotation 可在 defer_unknown 时暂缓,其他失败始终传播。
|
||||
match checked {
|
||||
Err(VmError::UnknownAnnotation(_)) if defer_unknown => Ok(()),
|
||||
result => result,
|
||||
}
|
||||
}
|
||||
/// 类型构造时按开关校验自身方法覆盖,允许暂缓尚未解析的注解。
|
||||
pub(super) fn validate_class_overrides(&self, definition: ObjectRef) -> Result<(), VmError> {
|
||||
// 类定义/更新阶段普通 override 检查受配置开关控制。
|
||||
if self.field_type_checks {
|
||||
// 逐个检查当前类型自身方法,不把继承方法当作自身声明。
|
||||
for (name, function) in &self.payload::<EndorTypeObject>(definition, "Type")?.methods {
|
||||
self.check_override(definition, name, *function, None, true)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// define_method 提交前,以 Patch 校验受影响后代,避免先改表再发现错误。
|
||||
pub(super) fn validate_override_update(
|
||||
&self,
|
||||
owner: ObjectRef,
|
||||
name: &str,
|
||||
function: ObjectRef,
|
||||
) -> Result<(), VmError> {
|
||||
// 类定义/更新阶段普通 override 检查受配置开关控制。
|
||||
if self.field_type_checks {
|
||||
// 拟议 owner/name/function 只作为查询视图,验证完成前不提交。
|
||||
let patch = Some(Patch {
|
||||
owner,
|
||||
name,
|
||||
function,
|
||||
});
|
||||
// 父方法变化可能破坏已有子方法的替换关系,所以检查全部后代。
|
||||
for descendant in self.descendant_types(owner)? {
|
||||
// 最靠近当前类型的声明遮蔽更远的同名父方法。
|
||||
if let Some(function) = self.own_method(descendant, name, patch)? {
|
||||
self.check_override(descendant, name, function, patch, true)?;
|
||||
}
|
||||
@@ -366,6 +456,7 @@ impl ObjectStore {
|
||||
Ok(())
|
||||
}
|
||||
/// Explicit override validation, including any typed capabilities.
|
||||
/// 宿主显式要求本次普通覆盖检查,同时校验接收者的类型化能力约束;不修改 VM/Store 检查开关。
|
||||
pub fn check_method_override_call(
|
||||
&self,
|
||||
function: ObjectRef,
|
||||
@@ -376,25 +467,35 @@ impl ObjectStore {
|
||||
}
|
||||
/// Typed capabilities are explicit opt-ins and always checked. Ordinary
|
||||
/// override checking remains configurable by the VM's source-check flag.
|
||||
/// eval 的源码建帧、原生调用及 NativeContext 准备时核对方法;能力约束始终检查,普通覆盖由 overrides 控制。
|
||||
pub fn check_method_contract_call(
|
||||
&self,
|
||||
function: ObjectRef,
|
||||
receiver: ObjectRef,
|
||||
overrides: bool,
|
||||
) -> Result<bool, VmError> {
|
||||
// 返回值表示是否命中类型化能力约束,不代表普通覆盖检查是否执行。
|
||||
let mut typed = false;
|
||||
// 接收者的有效类型可能经 type_target 转发;无类型时不会枚举名义方法。
|
||||
let actual = self.type_of(receiver)?;
|
||||
// 从实际类型开始向基类枚举可能声明当前函数的方法名。
|
||||
let mut owner = actual;
|
||||
// 向基类遍历;不假定候选函数一定是接收者当前生效的方法。
|
||||
while let Some(definition) = owner {
|
||||
// 读取类型载荷并验证句柄,后续方法表与基类访问都基于该借用。
|
||||
let data = self.payload::<EndorTypeObject>(definition, "Type")?;
|
||||
// 同一函数可出现在多个方法名下,各自可能承担不同能力契约。
|
||||
for (name, candidate) in &data.methods {
|
||||
// 这里只处理正在调用的函数身份,其余方法不检查。
|
||||
if *candidate != function {
|
||||
continue;
|
||||
}
|
||||
self.check_typed_instance_storage(receiver)?;
|
||||
// 普通 override 检查由调用方选择,能力约束不受该开关豁免。
|
||||
if overrides {
|
||||
self.check_override(definition, name, function, None, false)?;
|
||||
}
|
||||
// 实际方法查找仍选中该函数时按实际类型能力检查,否则按当前声明层处理。
|
||||
let effective = if self
|
||||
.lookup_method(actual.expect("method receiver has a type"), name)?
|
||||
== Some(function)
|
||||
@@ -403,7 +504,9 @@ impl ObjectStore {
|
||||
} else {
|
||||
definition
|
||||
};
|
||||
// 有效类型继承的全部能力都参与,不能只检查第一个匹配能力。
|
||||
for capability in self.inherited_traits(effective)? {
|
||||
// 只有该能力声明当前方法的完整类型签名时才有约束可检查。
|
||||
if let Some(expected) = self
|
||||
.payload::<super::EndorTraitObject>(capability, "Trait")?
|
||||
.signatures
|
||||
@@ -423,20 +526,30 @@ impl ObjectStore {
|
||||
|
||||
/// Borrowed expected results for all typed capabilities of this effective
|
||||
/// method. The evaluator retains definitions before entering the body.
|
||||
/// eval/NativeContext 收集有效方法的全部能力返回约束;返回类型句柄仍为借用。
|
||||
/// 这是约束收集,不独立替代方法参数/覆盖或实际返回值校验;跨执行阶段保活由调用方负责。
|
||||
pub fn method_trait_returns(
|
||||
&self,
|
||||
function: ObjectRef,
|
||||
receiver: ObjectRef,
|
||||
) -> Result<Vec<(String, CallType)>, VmError> {
|
||||
// 接收者的有效类型可能经 type_target 转发;无类型时不会枚举名义方法。
|
||||
let actual = self.type_of(receiver)?;
|
||||
// 从实际类型开始向基类枚举可能声明当前函数的方法名。
|
||||
let mut owner = actual;
|
||||
// 累计所有能力返回约束,clone 不保留其中托管定义根。
|
||||
let mut returns = Vec::new();
|
||||
// 向基类遍历;不假定候选函数一定是接收者当前生效的方法。
|
||||
while let Some(definition) = owner {
|
||||
// 读取类型载荷并验证句柄,后续方法表与基类访问都基于该借用。
|
||||
let data = self.payload::<EndorTypeObject>(definition, "Type")?;
|
||||
// 同一函数可出现在多个方法名下,各自可能承担不同能力契约。
|
||||
for (name, candidate) in &data.methods {
|
||||
// 这里只处理正在调用的函数身份,其余方法不检查。
|
||||
if *candidate != function {
|
||||
continue;
|
||||
}
|
||||
// 实际方法查找仍选中该函数时按实际类型能力检查,否则按当前声明层处理。
|
||||
let effective = if self
|
||||
.lookup_method(actual.expect("method receiver has a type"), name)?
|
||||
== Some(function)
|
||||
@@ -445,8 +558,11 @@ impl ObjectStore {
|
||||
} else {
|
||||
definition
|
||||
};
|
||||
// 有效类型继承的全部能力都参与,不能只检查第一个匹配能力。
|
||||
for capability in self.inherited_traits(effective)? {
|
||||
// 读取能力签名表,名称用于返回错误的定位。
|
||||
let contract = self.payload::<super::EndorTraitObject>(capability, "Trait")?;
|
||||
// 只有该能力声明当前方法的完整类型签名时才有约束可检查。
|
||||
if let Some(expected) = contract.signatures.get(name) {
|
||||
returns.push((
|
||||
format!("{}.{}", contract.name, name),
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
//! Store::call_effect 的当前快照证明;None 表示无法证明,不代表确定有副作用。
|
||||
//! 查询经过 Store 读取追踪,但不执行源码或分配托管对象;所有身份仅借用。
|
||||
//! 返回效果不会保留目标或冻结全局/Cell/方法表;缓存者需在追踪作用域收集观察并在使用前重验。
|
||||
//! Snapshot proof of read-only call graphs. All identities are borrowed and all
|
||||
//! traversals iterative; analysis neither executes code nor allocates VM objects.
|
||||
use super::{AttributeLookup, BoundMethod};
|
||||
@@ -8,25 +11,36 @@ use crate::{
|
||||
use std::collections::{BTreeSet, VecDeque};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// infer_call_effect 队列与去重集合中的借用调用身份,read_only_callees 识别直接/绑定调用后构造。
|
||||
struct Target {
|
||||
/// 底层被调函数身份;队列本身不新增拥有根。
|
||||
function: ObjectRef,
|
||||
/// 已知的首个接收者,可用于解析 LoadFast(0) 和方法;未知时保持 None。
|
||||
receiver: Option<ObjectRef>,
|
||||
}
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum Fact {
|
||||
// 没有可证明的身份,不是实际 Nil。
|
||||
Unknown,
|
||||
// 调用协议 Null 标记,不是语言空值。
|
||||
Null,
|
||||
// 已知借用对象身份,不增加根引用。
|
||||
Object(ObjectRef),
|
||||
// 已解析函数和绑定接收者,不创建托管绑定对象。
|
||||
Method(Target),
|
||||
}
|
||||
impl Fact {
|
||||
// read_only_callees 对常量、全局值及 Cell 内容降为身份事实,直接标量不保留其内容。
|
||||
fn value(value: Value) -> Self {
|
||||
// 对象保留借用身份,标量统一变成 Unknown;不把抽象事实当作实际求值结果。
|
||||
match value {
|
||||
Value::Object(object) => Self::Object(object),
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
// read_only_callees 的属性接收者/调用参数识别只提取普通对象身份,不把方法对当作对象。
|
||||
fn object(self) -> Option<ObjectRef> {
|
||||
// 仅 Object 事实可用于属性接收者或调用身份,Unknown/Null/Method 都不冒充普通对象。
|
||||
match self {
|
||||
Self::Object(object) => Some(object),
|
||||
_ => None,
|
||||
@@ -35,9 +49,13 @@ impl Fact {
|
||||
}
|
||||
|
||||
impl ObjectStore {
|
||||
// 解包 BoundMethod,保留底层函数和实际接收者,供调用图节点归一化。
|
||||
fn effect_target(&self, mut target: Target) -> Result<Target, VmError> {
|
||||
// 逐层解绑定载荷,不递归进入 Rust 调用栈。
|
||||
loop {
|
||||
// 验证函数身份并记录当前载荷读取,避免绕过依赖追踪。
|
||||
let payload = self.object(target.function)?.payload();
|
||||
// 不是绑定方法即结束归一化,不更改现有接收者。
|
||||
let Some(bound) = payload.as_any().downcast_ref::<BoundMethod>() else {
|
||||
return Ok(target);
|
||||
};
|
||||
@@ -48,14 +66,17 @@ impl ObjectStore {
|
||||
}
|
||||
}
|
||||
|
||||
// types::call_effect 委托入口;原生根返回已声明效果,源码根只尝试证明全图只读。
|
||||
pub(super) fn infer_call_effect(
|
||||
&self,
|
||||
function: ObjectRef,
|
||||
) -> Result<Option<CallEffect>, VmError> {
|
||||
// 根也可能是绑定方法,先解包再识别源码/原生。
|
||||
let root = self.effect_target(Target {
|
||||
function,
|
||||
receiver: None,
|
||||
})?;
|
||||
// 非源码根查询原生签名,Delegated 效果动态决定,不能直接当作整体效果。
|
||||
if !self
|
||||
.object(root.function)?
|
||||
.payload()
|
||||
@@ -66,19 +87,26 @@ impl ObjectStore {
|
||||
(signature.effect != CallEffect::Delegated).then_some(signature.effect)
|
||||
}));
|
||||
}
|
||||
// 迭代工作队列防止递归调用图造成 Rust 递归。
|
||||
let mut pending = VecDeque::from([root]);
|
||||
// 节点去重包含接收者,同一函数的不同绑定对象不能混为一项。
|
||||
let mut visited = BTreeSet::new();
|
||||
// 所有可达节点都能证明只读才允许根返回 ReadOnly。
|
||||
while let Some(target) = pending.pop_front() {
|
||||
// 子调用也可能经过绑定方法,统一归一化。
|
||||
let target = self.effect_target(target)?;
|
||||
// 递归回边已访问就跳过,不重复展开。
|
||||
if !visited.insert(target) {
|
||||
continue;
|
||||
}
|
||||
// 源码节点分析栈事实,原生节点则必须明确声明 ReadOnly。
|
||||
if let Some(source) = self
|
||||
.object(target.function)?
|
||||
.payload()
|
||||
.as_any()
|
||||
.downcast_ref::<EndorFunctionObject>()
|
||||
{
|
||||
// 任意一个节点无法证明,整个源码根的证明立即返回 None。
|
||||
let Some(callees) = self.read_only_callees(source, target.receiver)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
@@ -87,41 +115,55 @@ impl ObjectStore {
|
||||
.native_signature(target.function)?
|
||||
.is_none_or(|signature| signature.effect != CallEffect::ReadOnly)
|
||||
{
|
||||
// An impure target could rebind later globals/Cells/methods, so
|
||||
// snapshot edges cannot bound this source invocation's effects.
|
||||
// 未知/非只读目标可重绑定后续全局、Cell 或方法,当前快照不足以证明整体效果。
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
Ok(Some(CallEffect::ReadOnly))
|
||||
}
|
||||
|
||||
// 分析一个源码函数的抽象栈,收集已知被调目标;不执行实际调用。
|
||||
fn read_only_callees(
|
||||
&self,
|
||||
function: &EndorFunctionObject,
|
||||
receiver: Option<ObjectRef>,
|
||||
) -> Result<Option<BTreeSet<Target>>, VmError> {
|
||||
// 借用函数持有的代码;后续索引和栈高度依赖代码构造时的验证。
|
||||
let code = self
|
||||
.payload::<EndorCodeObject>(function.code(), "code")?
|
||||
.code();
|
||||
// 全表白名单已足够时无需分析调用图,返回空被调集合。
|
||||
if code.is_statically_read_only() {
|
||||
return Ok(Some(BTreeSet::new()));
|
||||
}
|
||||
// 有 Call 时要求其他全部指令都通过本地白名单。
|
||||
let instructions = code.instructions();
|
||||
// 过滤覆盖不可达指令,不仅检查当前可达路径。
|
||||
if instructions.iter().any(|decoded| {
|
||||
decoded.instruction.opcode != Opcode::Call
|
||||
&& !crate::code::read_only_opcode(decoded.instruction.opcode)
|
||||
}) {
|
||||
return Ok(None);
|
||||
}
|
||||
// 每个逻辑指令入口一份抽象栈,None 表示尚未到达。
|
||||
let mut states: Vec<Option<Vec<Fact>>> = vec![None; instructions.len()];
|
||||
// 已验证代码非空,入口求值栈为空。
|
||||
states[0] = Some(vec![]);
|
||||
// 分支汇合只能遗忘事实,状态变保守后再入队,最终收敛。
|
||||
let mut work = VecDeque::from([0]);
|
||||
// 调用边只增加不删除,保守保留曾经识别的目标。
|
||||
let mut callees = BTreeSet::new();
|
||||
// 逐指令模拟身份流动,不计算实际数值或条件。
|
||||
while let Some(index) = work.pop_front() {
|
||||
// 偏移使用物理代码单元,工作队列使用逻辑指令索引。
|
||||
let decoded = instructions[index];
|
||||
// 操作码控制抽象栈变换,不在分析阶段执行其载荷协议。
|
||||
let opcode = decoded.instruction.opcode;
|
||||
// 完整操作数已合并扩展前缀。
|
||||
let argument = decoded.instruction.argument as usize;
|
||||
// 复制当前入口事实,模拟本步后再传给后继。
|
||||
let mut stack = states[index].clone().expect("reachable effect state");
|
||||
// 仅保留调用目标识别所需事实,结果通常退化为 Unknown。
|
||||
match opcode {
|
||||
Opcode::Await => return Ok(None),
|
||||
Opcode::LoadConst => stack.push(Fact::value(code.constants()[argument].clone())),
|
||||
@@ -131,8 +173,11 @@ impl ObjectStore {
|
||||
Fact::Unknown
|
||||
}),
|
||||
Opcode::LoadGlobal => {
|
||||
// 与 eval 一致,先查询全局字典,再回退捕获的内置空间。
|
||||
let name = &code.layout().names[argument];
|
||||
// 名称读取登记命中/缺失依赖,后续重绑定能影响快照证明。
|
||||
let mut value = self.dict_get_name(function.globals(), name)?;
|
||||
// 只有缺键才回退,Some(Nil) 不等于名称不存在。
|
||||
if value.is_none()
|
||||
&& let Some(builtins) = function.builtins()
|
||||
{
|
||||
@@ -141,16 +186,20 @@ impl ObjectStore {
|
||||
stack.push(value.map(Fact::value).unwrap_or(Fact::Unknown));
|
||||
}
|
||||
Opcode::LoadDeref | Opcode::LoadClosure => {
|
||||
// 外部自由变量可读当前捕获 Cell;局部只尝试识别已知接收者。
|
||||
let fact = if argument >= code.layout().locals.len() {
|
||||
// 把 localsplus 自由变量段索引换算到函数 closure 列表。
|
||||
let cell = *function
|
||||
.closure()
|
||||
.get(argument - code.layout().locals.len())
|
||||
.ok_or(VmError::InvalidArgument(
|
||||
"function closure does not match code",
|
||||
))?;
|
||||
// LoadClosure 保留 Cell 自身身份,LoadDeref 读取其当前内容。
|
||||
if opcode == Opcode::LoadClosure {
|
||||
Fact::Object(cell)
|
||||
} else {
|
||||
// 空 Cell 为 Unknown,其他协议/句柄错误仍传播。
|
||||
match self.cell_get(cell) {
|
||||
Ok(value) => Fact::value(value),
|
||||
Err(VmError::EmptyCell) => Fact::Unknown,
|
||||
@@ -167,6 +216,7 @@ impl ObjectStore {
|
||||
Opcode::PushNull => stack.push(Fact::Null),
|
||||
Opcode::Copy => stack.push(stack[stack.len() - argument]),
|
||||
Opcode::Swap => {
|
||||
// Swap 深度已由代码验证,直接模拟事实位置交换。
|
||||
let length = stack.len();
|
||||
stack.swap(length - 1, length - argument);
|
||||
}
|
||||
@@ -184,8 +234,11 @@ impl ObjectStore {
|
||||
*stack.last_mut().unwrap() = Fact::Unknown;
|
||||
}
|
||||
Opcode::LoadAttr => {
|
||||
// 只有 owner 身份已知才可查询其属性,未知 owner 不猜测方法。
|
||||
let fact = stack.last_mut().unwrap();
|
||||
// 绑定方法保留函数与接收者对,不实际分配方法载荷。
|
||||
*fact = if let Some(owner) = fact.object() {
|
||||
// 缺属性/不支持属性视为未知,其他错误不吞掉。
|
||||
match self.lookup_attr(owner, &code.layout().names[argument]) {
|
||||
Ok(AttributeLookup::Value(value)) => Fact::value(value),
|
||||
Ok(AttributeLookup::Method { function, receiver }) => {
|
||||
@@ -205,7 +258,9 @@ impl ObjectStore {
|
||||
};
|
||||
}
|
||||
Opcode::Call => {
|
||||
// 调用输入包括函数、接收者或 Null 及显式参数,代码校验保证足够高度。
|
||||
let start = stack.len() - argument - 2;
|
||||
// Null 协议从首显式实参识别可能接收者,否则用隐式接收者项。
|
||||
let implicit = if stack[start + 1] == Fact::Null {
|
||||
(argument > 0)
|
||||
.then(|| stack[start + 2])
|
||||
@@ -213,6 +268,7 @@ impl ObjectStore {
|
||||
} else {
|
||||
stack[start + 1].object()
|
||||
};
|
||||
// 未知函数目标无法证明整个函数;已知方法直接保留绑定接收者。
|
||||
let target = match stack[start] {
|
||||
Fact::Object(function) => Target {
|
||||
function,
|
||||
@@ -221,6 +277,7 @@ impl ObjectStore {
|
||||
Fact::Method(target) => target,
|
||||
_ => return Ok(None),
|
||||
};
|
||||
// 记录目标后移除输入,调用结果不猜测身份,标为 Unknown。
|
||||
callees.insert(target);
|
||||
stack.truncate(start);
|
||||
stack.push(Fact::Unknown);
|
||||
@@ -230,14 +287,17 @@ impl ObjectStore {
|
||||
stack.push(Fact::Unknown);
|
||||
}
|
||||
Opcode::MakeFunction => {
|
||||
// 函数构造消耗默认值、捕获槽和代码项;新对象未实际分配,结果未知。
|
||||
let (defaults, closure) =
|
||||
crate::Instruction::function_counts(decoded.instruction.argument);
|
||||
stack.truncate(stack.len() - defaults - closure - 1);
|
||||
stack.push(Fact::Unknown);
|
||||
}
|
||||
Opcode::Nop | Opcode::DeleteFast | Opcode::JumpForward | Opcode::JumpBackward => {}
|
||||
// 注意:当前白名单含算术操作,但此模拟器尚无对应分支;含 Call 的混合路径需单独修复。
|
||||
_ => unreachable!("unproved opcodes were rejected before analysis"),
|
||||
}
|
||||
// 条件跳转传播两条后继,返回无后继,不根据当前值猜测条件真假。
|
||||
let successors = match opcode {
|
||||
Opcode::ReturnValue => vec![],
|
||||
Opcode::JumpForward | Opcode::JumpBackward => {
|
||||
@@ -248,13 +308,19 @@ impl ObjectStore {
|
||||
}
|
||||
_ => vec![decoded.next_offset],
|
||||
};
|
||||
// 后继都已通过代码验证,必须落在完整逻辑指令起点。
|
||||
for offset in successors {
|
||||
// 将物理单元偏移查为逻辑索引,expect 依赖已验证控制流。
|
||||
let next = instructions
|
||||
.binary_search_by_key(&offset, |item| item.offset)
|
||||
.expect("validated successor");
|
||||
// 汇合时逐槽遗忘有分歧的身份;栈长度由代码验证保证一致。
|
||||
let changed = if let Some(existing) = &mut states[next] {
|
||||
// 只有新增状态或事实首次退化才再次传播。
|
||||
let mut changed = false;
|
||||
// Unknown 不恢复为具体事实,相同身份继续保留。
|
||||
for (old, new) in existing.iter_mut().zip(&stack) {
|
||||
// 不同路径的目标不能任取其一,合并为 Unknown。
|
||||
if *old != *new && *old != Fact::Unknown {
|
||||
*old = Fact::Unknown;
|
||||
changed = true;
|
||||
@@ -265,6 +331,7 @@ impl ObjectStore {
|
||||
states[next] = Some(stack.clone());
|
||||
true
|
||||
};
|
||||
// 状态更保守时重新处理后继,直至所有入口稳定。
|
||||
if changed {
|
||||
work.push_back(next);
|
||||
}
|
||||
|
||||
@@ -1,55 +1,97 @@
|
||||
//! objects 定义、compiler/core 与宿主共用的错误协议,避免底层存储反向依赖 VM 执行器。
|
||||
//! Display 只生成诊断文本,不恢复对象、不回滚操作,也不等同于托管的异常对象载荷。
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// Shared object/bytecode/execution errors. The established VmError name is
|
||||
/// retained for embedders; defining it here avoids a dependency on the VM.
|
||||
/// 覆盖对象、字节码与执行错误;保留 VmError 名称供既有嵌入方匹配。
|
||||
/// 各操作用 Result 返回;Clone 复制诊断信息,不复制 VM 状态或托管对象。
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum VmError {
|
||||
/// 宿主等待对象的异步操作失败,携带提供方报告的原因。
|
||||
AwaitFailed(String),
|
||||
/// 宿主等待对象报告取消,与失败原因或暂未就绪区分。
|
||||
AwaitCancelled,
|
||||
/// Heap::get 发现句柄属于其他堆;不是本堆对象已过期。
|
||||
ForeignHandle,
|
||||
/// Heap::get 发现越界、代际不符或空槽;不能再访问该身份。
|
||||
InvalidHandle,
|
||||
/// Heap::new 的进程级身份计数耗尽,拒绝复用身份。
|
||||
IdentityExhausted,
|
||||
/// Heap 引用增量预检溢出,阻止计数绕回。
|
||||
RefCountOverflow,
|
||||
/// Heap::release 没有可归还的外部根;内部强边不能当外部根释放。
|
||||
NoOwnedReference,
|
||||
/// 属性存储查找/删除未找到字段,携带字段名。
|
||||
AttributeNotFound(String),
|
||||
/// 载荷不支持操作或当前上下文禁止操作,携带静态操作说明。
|
||||
UnsupportedOperation(&'static str),
|
||||
/// 构造或协议校验不满足结构约束,携带静态原因。
|
||||
InvalidArgument(&'static str),
|
||||
/// Store 下转型或执行器要求某种载荷而实际不符,携带预期诊断名称。
|
||||
ObjectKindMismatch(&'static str),
|
||||
/// 能力契约校验失败,定位能力、方法及不兼容原因。
|
||||
TraitViolation {
|
||||
/// 能力名称,用于定位失败契约。
|
||||
capability: String,
|
||||
/// 被核对的方法名称。
|
||||
method: String,
|
||||
/// 无需动态分配的具体失败原因。
|
||||
reason: &'static str,
|
||||
},
|
||||
/// 方法替换不满足父级契约,定位定义、方法和原因。
|
||||
OverrideViolation {
|
||||
/// 方法所属类型定义名称。
|
||||
definition: String,
|
||||
/// 被核对的方法名称。
|
||||
method: String,
|
||||
/// 无需动态分配的具体失败原因。
|
||||
reason: &'static str,
|
||||
},
|
||||
/// 类型解析无法接受注解名称;部分延后校验路径可暂缓此错误。
|
||||
UnknownAnnotation(String),
|
||||
/// 源码/原生签名或字段校验不匹配,携带调用位置与预期/实际类型诊断。
|
||||
TypeMismatch {
|
||||
/// 被校验函数或相关诊断主体。
|
||||
function: String,
|
||||
/// 参数、返回值或字段等发生不匹配的位置。
|
||||
position: String,
|
||||
/// 期望类型的可读名称。
|
||||
expected: String,
|
||||
/// 实际值类型的可读名称。
|
||||
actual: String,
|
||||
},
|
||||
/// Store::cell_get 读取尚未初始化的闭包槽,不等于槽中存放 Nil。
|
||||
EmptyCell,
|
||||
/// 解码或代码校验拒绝指令,offset 是代码单元偏移,reason 是静态说明。
|
||||
InvalidBytecode {
|
||||
/// 字节码代码单元偏移,不是源文本行号。
|
||||
offset: usize,
|
||||
/// 无需动态分配的具体失败原因。
|
||||
reason: &'static str,
|
||||
},
|
||||
/// 执行器读取/删除未绑定局部或相关捕获变量,携带变量名称。
|
||||
UnboundLocal(String),
|
||||
/// 执行器全局/内置查找或删除名称失败,携带名称。
|
||||
NameNotFound(String),
|
||||
/// 调用入口数量校验失败,记录函数名、最少/最多及实际位置参数数目。
|
||||
ArgumentCount {
|
||||
/// 被调用对象的诊断名称。
|
||||
name: String,
|
||||
/// 允许的最少位置参数数。
|
||||
required: usize,
|
||||
/// 允许的最多位置参数数。
|
||||
maximum: usize,
|
||||
/// 调用方实际提供的位置参数数。
|
||||
provided: usize,
|
||||
},
|
||||
/// 执行帧、指令等预算或内部有界计数达到限制,携带具体限制类别。
|
||||
ExecutionLimit(&'static str),
|
||||
}
|
||||
|
||||
impl fmt::Display for VmError {
|
||||
/// 宿主日志、UI 错误文本及标准 Error 展示调用;只借用错误,不消费其中的诊断数据。
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// 按变体生成稳定的可读诊断;结构化匹配应使用变体,不能依赖解析此文本。
|
||||
match self {
|
||||
Self::AwaitFailed(message) => write!(f, "await failed: {message}"),
|
||||
Self::AwaitCancelled => f.write_str("await cancelled"),
|
||||
@@ -59,6 +101,7 @@ impl fmt::Display for VmError {
|
||||
Self::RefCountOverflow => f.write_str("object reference count exhausted"),
|
||||
Self::NoOwnedReference => f.write_str("no external strong reference to release"),
|
||||
Self::AttributeNotFound(name) => write!(f, "attribute not found: {name}"),
|
||||
// 保留具体受限操作,便于区分协议缺失和只读上下文等限制。
|
||||
Self::UnsupportedOperation(operation) => {
|
||||
write!(f, "object does not support {operation}")
|
||||
}
|
||||
@@ -69,6 +112,7 @@ impl fmt::Display for VmError {
|
||||
method,
|
||||
reason,
|
||||
} => write!(f, "trait {capability} method {method}: {reason}"),
|
||||
// 名称来自注解解析,不把它伪装成已经解析的语言类型。
|
||||
Self::UnknownAnnotation(name) => {
|
||||
write!(f, "unknown or invalid type annotation: {name}")
|
||||
}
|
||||
@@ -87,6 +131,7 @@ impl fmt::Display for VmError {
|
||||
"{function} {position} expects {expected}, received {actual}"
|
||||
),
|
||||
Self::EmptyCell => f.write_str("cell is empty"),
|
||||
// 偏移单位与指令解码器一致,避免把代码单元误报为源代码行号。
|
||||
Self::InvalidBytecode { offset, reason } => {
|
||||
write!(f, "invalid bytecode at code unit {offset}: {reason}")
|
||||
}
|
||||
@@ -106,4 +151,5 @@ impl fmt::Display for VmError {
|
||||
}
|
||||
}
|
||||
|
||||
// 使宿主可通过标准 Error 接口传播/封装;当前未另提供嵌套 source 错误链。
|
||||
impl std::error::Error for VmError {}
|
||||
|
||||
+228
-18
@@ -1,3 +1,7 @@
|
||||
//! ObjectStore 的底层对象堆;不执行语言指令,负责稳定对象盒、代际句柄、引用边和回收。
|
||||
//! Store/types/store_builtins 先做领域校验,再调用这里的受控修改入口。
|
||||
//! 读追踪用 Mutex 支持 &self 协议路径记录观察;对象修改仍要求 &mut self,并非并发可变堆。
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||
use std::fmt;
|
||||
use std::sync::Mutex;
|
||||
@@ -8,82 +12,114 @@ use crate::changes::MAX_PENDING_CHANGES;
|
||||
use crate::{ChangeBatch, ChangeKey, ObjectChange};
|
||||
use crate::{EndorObject, ObjectHeader, ObjectPayload, Value, VmError};
|
||||
|
||||
// 进程级单调身份源;Relaxed 只需要原子唯一性,不承担对象发布或内存同步。
|
||||
static NEXT_HEAP_ID: AtomicU64 = AtomicU64::new(1);
|
||||
|
||||
/// Checked, process-local reference. Copying it never retains an object.
|
||||
/// The generation prevents a released handle from aliasing a reused slot.
|
||||
/// 进程内受校验句柄;复制不会 retain。heap_id 防跨堆,generation 防止旧句柄别名复用槽。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct ObjectRef {
|
||||
// 分配它的堆身份。
|
||||
heap_id: u64,
|
||||
// slots 向量中的位置,不是对象内存地址。
|
||||
index: usize,
|
||||
// 槽释放后递增的代际。
|
||||
generation: u64,
|
||||
}
|
||||
|
||||
impl fmt::Display for ObjectRef {
|
||||
/// ObjectRef 的日志/错误格式化入口;输出进程内身份三元组,不代表可持久化或跨进程引用。
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}:{}:{}", self.heap_id, self.index, self.generation)
|
||||
}
|
||||
}
|
||||
|
||||
/// A non-owning reference. Only the originating store can upgrade it; slot
|
||||
/// reuse never makes a dead weak reference point at a different object.
|
||||
/// 非拥有弱句柄;仅原 Store 可升级,代际检查保证槽复用不会复活过期弱引用。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WeakObjectRef(pub(crate) ObjectRef);
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Slot {
|
||||
// 当前槽代际;空槽仍保留历史代际,耗尽后不进入 free。
|
||||
generation: u64,
|
||||
// Box 保持对象地址稳定,外层 slots 扩容不会移动对象实体。
|
||||
object: Option<Box<EndorObject>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// 一层只读作用域;protected 是进入时的存活身份快照,其余字段只收集借用观察。
|
||||
struct ReadOnly {
|
||||
// 该层不可修改的已有对象,后续新对象不在此集合中。
|
||||
protected: BTreeSet<ObjectRef>,
|
||||
// 兼容的对象级去重读取集合。
|
||||
reads: Mutex<BTreeSet<ObjectRef>>,
|
||||
// 子区间栈;bool 表示是否发生程序内容读取(而非仅验证)。
|
||||
regions: Mutex<Vec<BTreeMap<ObjectRef, bool>>>,
|
||||
// 按依赖键去重,保留该层首次观察的版本/名称值。
|
||||
dependencies: Mutex<BTreeMap<ReadKey, ReadDependency>>,
|
||||
// 与 regions 同步压栈/弹栈的精确子区间依赖。
|
||||
dependency_regions: Mutex<Vec<BTreeMap<ReadKey, ReadDependency>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// ObjectStore 独占的对象槽及生命周期状态;其他模块不能直接修改槽或引用计数。
|
||||
pub(crate) struct Heap {
|
||||
// 本堆的进程内唯一身份。
|
||||
id: u64,
|
||||
// 已分配过的槽,可含空槽;对象实体单独装箱。
|
||||
slots: Vec<Slot>,
|
||||
// 可复用空槽索引,按栈顺序取回。
|
||||
free: Vec<usize>,
|
||||
// 实际占用槽数,分配/摘除时同步维护。
|
||||
live: usize,
|
||||
// 同一对象/变更键只保留最新版本,限制通知内存占用。
|
||||
changes: BTreeMap<(ObjectRef, ChangeKey), u64>,
|
||||
// 本批已溢出:停止精确通知,要求消费者全面重验依赖。
|
||||
changes_overflow: bool,
|
||||
// 活跃只读作用域栈,任意一层保护对象就禁止修改。
|
||||
read_only: Vec<ReadOnly>,
|
||||
// 当前线程的验证读取嵌套深度,RAII 退出时递减。
|
||||
validation_reads: Mutex<HashMap<ThreadId, usize>>,
|
||||
}
|
||||
|
||||
/// Borrowed read identity; content_read distinguishes ordinary reads from
|
||||
/// annotation validation alone. Neither form retains the object.
|
||||
/// 借用读取身份及用途分类,不创建 GC 根;普通内容读取会覆盖“仅验证”的分类。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ReadAccess {
|
||||
/// 读取的对象身份。
|
||||
pub object: ObjectRef,
|
||||
/// true 表示至少一次程序内容读取,false 表示只有注解验证读取。
|
||||
pub content_read: bool,
|
||||
}
|
||||
|
||||
/// Borrowed observations; Object handles never retain the observed graph.
|
||||
/// Named values are immutable comparison snapshots, not managed owning edges.
|
||||
/// 缓存重验的借用观察;包括 Name.value 内句柄都不持有对象图,不是托管拥有边。
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ReadDependency {
|
||||
/// 整对象版本变化即失效。
|
||||
Object {
|
||||
/// 被观察对象的完整身份。
|
||||
object: ObjectRef,
|
||||
/// 读取时的版本快照。
|
||||
version: u64,
|
||||
},
|
||||
/// 只要求原对象仍是字典,不依赖其他条目的变化。
|
||||
DictionaryKind {
|
||||
/// 已确认具有字典载荷的对象身份。
|
||||
object: ObjectRef,
|
||||
},
|
||||
/// 指定文本名称的命中/缺失值快照;None 与 Some(Nil) 区分。
|
||||
/// Store 重验使用 Value 的表示相等;其中对象按句柄身份比较,不递归验证被引用对象内容。
|
||||
Name {
|
||||
/// 接受名称查找的字典身份。
|
||||
object: ObjectRef,
|
||||
/// 本次观察的文本键,不代表全局与内置空间的合并查找。
|
||||
name: String,
|
||||
/// 首次查到的值或缺失;含对象句柄时也只是比较快照。
|
||||
value: Option<crate::Value>,
|
||||
},
|
||||
}
|
||||
impl ReadDependency {
|
||||
/// 缓存/读取分析使用者取得观察所属对象,不创建外部拥有根。
|
||||
pub fn object(&self) -> ObjectRef {
|
||||
// 三种依赖形状都提取其观察对象身份,不重新读取内容或改变依赖精度。
|
||||
match self {
|
||||
Self::Object { object, .. }
|
||||
| Self::DictionaryKind { object }
|
||||
@@ -92,37 +128,56 @@ impl ReadDependency {
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// 精确依赖的排序/去重键;与 ReadDependency 分开,观察值不参与键身份。
|
||||
enum ReadKey {
|
||||
/// 整对象观察按完整身份去重。
|
||||
Object(ObjectRef),
|
||||
/// 字典种类观察与同对象的版本/名称观察分别保留。
|
||||
DictionaryKind(ObjectRef),
|
||||
/// 同一字典的不同文本键各自保存首次查找值。
|
||||
Name(ObjectRef, String),
|
||||
}
|
||||
/// Store::end_read_tracking_precise 返回的子调用观察;结束子区间不结束外层只读作用域。
|
||||
/// 列表中的身份均借用,内容读取优先于验证分类;只包含该作用域进入前已有对象的已登记读取。
|
||||
#[derive(Debug)]
|
||||
pub struct TrackedReads {
|
||||
/// 子区间对象读取与用途分类。
|
||||
pub accesses: Vec<ReadAccess>,
|
||||
/// 精确观察列表,供 Store::read_dependency_valid 重验。
|
||||
pub dependencies: Vec<ReadDependency>,
|
||||
}
|
||||
/// Store::end_read_only_precise 和 VM 精确只读调用返回的整个作用域观察。
|
||||
/// 对象/键分别去重且保留首次快照,不保活观察目标;缓存使用者通过 Store 重验依赖。
|
||||
#[derive(Debug)]
|
||||
pub struct ReadSet {
|
||||
/// 整个作用域读取到的借用对象身份。
|
||||
pub objects: Vec<ObjectRef>,
|
||||
/// 精确观察列表,供 Store::read_dependency_valid 重验。
|
||||
pub dependencies: Vec<ReadDependency>,
|
||||
}
|
||||
|
||||
/// 仅借用堆的验证读取守卫;不拥有对象,也不延长只读作用域寿命。
|
||||
struct ValidationScope<'a> {
|
||||
// 被借用的堆,守卫存活期间保证分类表可访问。
|
||||
heap: &'a Heap,
|
||||
// 进入验证的线程,用于归还同一线程的嵌套深度。
|
||||
thread: ThreadId,
|
||||
}
|
||||
impl Drop for ValidationScope<'_> {
|
||||
/// 验证读取 RAII 清理:compute 正常返回、提前报错或展开栈时都归还当前线程的一层标记。
|
||||
fn drop(&mut self) {
|
||||
// 在锁下找到本线程深度;守卫只在成功增加深度后创建。
|
||||
let mut active = self
|
||||
.heap
|
||||
.validation_reads
|
||||
.lock()
|
||||
.expect("read tracker poisoned");
|
||||
// 嵌套验证只减少本层,不清除仍活跃的外层验证标记。
|
||||
let depth = active
|
||||
.get_mut(&self.thread)
|
||||
.expect("active validation scope");
|
||||
*depth -= 1;
|
||||
// 最外层退出时移除线程项,后续读取重新归类为程序内容读取。
|
||||
if *depth == 0 {
|
||||
active.remove(&self.thread);
|
||||
}
|
||||
@@ -130,23 +185,32 @@ impl Drop for ValidationScope<'_> {
|
||||
}
|
||||
|
||||
impl Heap {
|
||||
/// ObjectStore::new 创建空堆;分配独立进程内身份,计数器耗尽时报错而不循环复用。
|
||||
pub(crate) fn new() -> Result<Self, VmError> {
|
||||
// checked_add 防止身份绕回;fetch_update 返回更新前的唯一值。
|
||||
let id = NEXT_HEAP_ID
|
||||
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
|
||||
.map_err(|_| VmError::IdentityExhausted)?;
|
||||
// 初始没有对象、空槽、通知或读取作用域。
|
||||
Ok(Self {
|
||||
id,
|
||||
// 已分配过的槽,可含空槽;对象实体单独装箱。
|
||||
slots: Vec::new(),
|
||||
// 可复用空槽索引,按栈顺序取回。
|
||||
free: Vec::new(),
|
||||
live: 0,
|
||||
// 同一对象/变更键只保留最新版本,限制通知内存占用。
|
||||
changes: BTreeMap::new(),
|
||||
changes_overflow: false,
|
||||
read_only: vec![],
|
||||
// 当前线程的验证读取嵌套深度,RAII 退出时递减。
|
||||
validation_reads: Mutex::new(HashMap::new()),
|
||||
})
|
||||
}
|
||||
|
||||
/// types 的字段/方法契约变更校验遍历存活对象时使用;输出借用身份,不增加根计数。
|
||||
pub(crate) fn live_references(&self) -> impl Iterator<Item = ObjectRef> + '_ {
|
||||
// 空槽跳过;按每个当前代际重建身份,而不是复用历史外部句柄。
|
||||
self.slots.iter().enumerate().filter_map(|(index, slot)| {
|
||||
slot.object.as_ref().map(|_| ObjectRef {
|
||||
heap_id: self.id,
|
||||
@@ -156,7 +220,9 @@ impl Heap {
|
||||
})
|
||||
}
|
||||
|
||||
/// Store/VM 进入只读调用时快照现有身份;返回当前嵌套深度作为结束令牌。
|
||||
pub(crate) fn begin_read_only(&mut self) -> usize {
|
||||
// 只快照已存在对象;外层新建对象在随后嵌套层进入时也会变为该层受保护对象。
|
||||
let protected = self
|
||||
.slots
|
||||
.iter()
|
||||
@@ -169,23 +235,32 @@ impl Heap {
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
// 新层观察容器独立,之后的读取还会传播到适用的外层。
|
||||
self.read_only.push(ReadOnly {
|
||||
protected,
|
||||
// 兼容的对象级去重读取集合。
|
||||
reads: Mutex::new(BTreeSet::new()),
|
||||
// 子区间栈;bool 表示是否发生程序内容读取(而非仅验证)。
|
||||
regions: Mutex::new(Vec::new()),
|
||||
// 按依赖键去重,保留该层首次观察的版本/名称值。
|
||||
dependencies: Mutex::new(BTreeMap::new()),
|
||||
// 与 regions 同步压栈/弹栈的精确子区间依赖。
|
||||
dependency_regions: Mutex::new(Vec::new()),
|
||||
});
|
||||
self.read_only.len()
|
||||
}
|
||||
|
||||
/// Store 的兼容结束入口,仅返回读取对象身份,不保留精确观察键。
|
||||
pub(crate) fn end_read_only(&mut self, scope: usize) -> Result<Vec<ObjectRef>, VmError> {
|
||||
Ok(self.end_read_only_precise(scope)?.objects)
|
||||
}
|
||||
/// Store/VM 结束最内层只读作用域并取走全部观察;错误令牌不弹出状态。
|
||||
pub(crate) fn end_read_only_precise(&mut self, scope: usize) -> Result<ReadSet, VmError> {
|
||||
// 令牌是深度而非永久唯一 ID;调用方必须遵守同一作用域内严格 LIFO。
|
||||
if scope == 0 || scope != self.read_only.len() {
|
||||
return Err(VmError::InvalidArgument("read-only scope nesting mismatch"));
|
||||
}
|
||||
// 检查已保证栈顶存在,取走 Mutex 内的数据,输出按有序集合/键排列。
|
||||
let scope = self.read_only.pop().unwrap();
|
||||
Ok(ReadSet {
|
||||
objects: scope
|
||||
@@ -203,11 +278,15 @@ impl Heap {
|
||||
})
|
||||
}
|
||||
|
||||
/// Store/VM 在当前只读调用内开始一个子调用区间,同时建立分类与精确依赖两套容器。
|
||||
pub(crate) fn begin_read_tracking(&self) -> Result<usize, VmError> {
|
||||
// 子区间必须依附当前最内层只读作用域,不能独立开启读取追踪。
|
||||
let scope = self.read_only.last().ok_or(VmError::InvalidArgument(
|
||||
"read tracking requires a read-only scope",
|
||||
))?;
|
||||
// 锁的取得顺序与结束路径一致:先分类区间,再精确依赖区间。
|
||||
let mut regions = scope.regions.lock().expect("read tracker poisoned");
|
||||
// 两套栈使用相同深度令牌,不能只创建其中一套。
|
||||
regions.push(BTreeMap::new());
|
||||
scope
|
||||
.dependency_regions
|
||||
@@ -217,6 +296,7 @@ impl Heap {
|
||||
Ok(regions.len())
|
||||
}
|
||||
|
||||
/// 兼容子区间结束入口,只保留读取对象列表,丢弃分类与精确依赖。
|
||||
pub(crate) fn end_read_tracking(&self, token: usize) -> Result<Vec<ObjectRef>, VmError> {
|
||||
Ok(self
|
||||
.end_read_tracking_details(token)?
|
||||
@@ -225,24 +305,31 @@ impl Heap {
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Store/VM 结束子区间,保留程序内容读取与注解验证读取的分类。
|
||||
pub(crate) fn end_read_tracking_details(
|
||||
&self,
|
||||
token: usize,
|
||||
) -> Result<Vec<ReadAccess>, VmError> {
|
||||
Ok(self.end_read_tracking_precise(token)?.accesses)
|
||||
}
|
||||
/// 精确结束子调用区间;同时弹出两套追踪栈,外层区间和只读作用域继续有效。
|
||||
pub(crate) fn end_read_tracking_precise(&self, token: usize) -> Result<TrackedReads, VmError> {
|
||||
// 子区间必须依附当前最内层只读作用域,不能独立开启读取追踪。
|
||||
let scope = self.read_only.last().ok_or(VmError::InvalidArgument(
|
||||
"read tracking requires a read-only scope",
|
||||
))?;
|
||||
// 锁的取得顺序与结束路径一致:先分类区间,再精确依赖区间。
|
||||
let mut regions = scope.regions.lock().expect("read tracker poisoned");
|
||||
// 与分类区间相同锁顺序取得精确依赖区间栈,令牌检查必须同时核对两者。
|
||||
let mut precise = scope
|
||||
.dependency_regions
|
||||
.lock()
|
||||
.expect("read tracker poisoned");
|
||||
// 同时检查两套栈,错误不弹出任何一边,避免分类与精确观察错位。
|
||||
if token == 0 || token != regions.len() || token != precise.len() {
|
||||
return Err(VmError::InvalidArgument("read tracking nesting mismatch"));
|
||||
}
|
||||
// 将最内层区间移出;外层已在记录时同步收到观察,不需要结束时再合并。
|
||||
Ok(TrackedReads {
|
||||
accesses: regions
|
||||
.pop()
|
||||
@@ -257,30 +344,39 @@ impl Heap {
|
||||
})
|
||||
}
|
||||
|
||||
/// Store 的写配置保护和 VM 效果判断查询是否存在只读作用域。
|
||||
pub(crate) fn is_read_only(&self) -> bool {
|
||||
!self.read_only.is_empty()
|
||||
}
|
||||
|
||||
/// types::matches_annotation 在类型校验期间调用,将该线程内的读取标为验证用途。
|
||||
pub(crate) fn with_validation_reads<T>(
|
||||
&self,
|
||||
compute: impl FnOnce() -> Result<T, VmError>,
|
||||
) -> Result<T, VmError> {
|
||||
// 无活跃只读作用域就没有需要收集/分类的观察。
|
||||
if self.read_only.is_empty() {
|
||||
return compute();
|
||||
}
|
||||
// 分类标记按线程隔离,嵌套深度使内层退出不影响外层。
|
||||
let thread = std::thread::current().id();
|
||||
{
|
||||
// 限定锁作用域,不能持锁执行用户传入的 compute。
|
||||
let mut active = self.validation_reads.lock().expect("read tracker poisoned");
|
||||
// 溢出时不写回新深度,也不建立清理守卫。
|
||||
let depth = active.entry(thread).or_default();
|
||||
*depth = depth
|
||||
.checked_add(1)
|
||||
.ok_or(VmError::ExecutionLimit("validation scopes"))?;
|
||||
}
|
||||
// 守卫覆盖整个 compute;? 提前返回或 panic 展开也会恢复分类标记。
|
||||
let _scope = ValidationScope { heap: self, thread };
|
||||
compute()
|
||||
}
|
||||
|
||||
/// Store::object 取得对象后登记整对象版本观察;本方法不验证句柄也不 retain。
|
||||
pub(crate) fn record_read(&self, reference: ObjectRef, version: u64) {
|
||||
// 无活跃只读作用域就没有需要收集/分类的观察。
|
||||
if self.read_only.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -291,17 +387,20 @@ impl Heap {
|
||||
}
|
||||
});
|
||||
}
|
||||
/// store_builtins::dictionary_kind 已验证后登记“仍是字典”,不依赖字典全部条目。
|
||||
pub(crate) fn record_dictionary_kind(&self, reference: ObjectRef) {
|
||||
self.record_observation(reference, ReadKey::DictionaryKind(reference), || {
|
||||
ReadDependency::DictionaryKind { object: reference }
|
||||
});
|
||||
}
|
||||
/// store_builtins::dict_get_name 登记命中值或缺失快照,供之后按名称重验依赖。
|
||||
pub(crate) fn record_name(
|
||||
&self,
|
||||
reference: ObjectRef,
|
||||
name: &str,
|
||||
value: &Option<crate::Value>,
|
||||
) {
|
||||
// 无活跃只读作用域就没有需要收集/分类的观察。
|
||||
if self.read_only.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -313,28 +412,35 @@ impl Heap {
|
||||
}
|
||||
});
|
||||
}
|
||||
/// 三种 record_* 共用的收集器,把观察传播给每个保护该对象的外层作用域及其活跃子区间。
|
||||
fn record_observation(
|
||||
&self,
|
||||
reference: ObjectRef,
|
||||
key: ReadKey,
|
||||
build: impl Fn() -> ReadDependency,
|
||||
) {
|
||||
// 无活跃只读作用域就没有需要收集/分类的观察。
|
||||
if self.read_only.is_empty() {
|
||||
return;
|
||||
}
|
||||
// 当前线程在任何验证层内都先分类为验证读取;区间中若另有普通读取仍以普通为准。
|
||||
let content = !self
|
||||
.validation_reads
|
||||
.lock()
|
||||
.expect("read tracker poisoned")
|
||||
.contains_key(&std::thread::current().id());
|
||||
// 所有适用外层都要收到读取,不能只记录最内层导致父调用漏依赖。
|
||||
for scope in &self.read_only {
|
||||
// 本层新建结果不属于本层外部输入,不作为该层已有对象依赖。
|
||||
if scope.protected.contains(&reference) {
|
||||
// 同键保留首次观察;build 惰性生成快照,未命中时才克隆名称值。
|
||||
scope
|
||||
.dependencies
|
||||
.lock()
|
||||
.expect("read tracker poisoned")
|
||||
.entry(key.clone())
|
||||
.or_insert_with(&build);
|
||||
// 每个活跃子区间独立保存该精确观察。
|
||||
for region in scope
|
||||
.dependency_regions
|
||||
.lock()
|
||||
@@ -343,11 +449,13 @@ impl Heap {
|
||||
{
|
||||
region.entry(key.clone()).or_insert_with(&build);
|
||||
}
|
||||
// 兼容对象集合只去重身份,不区分读取用途。
|
||||
scope
|
||||
.reads
|
||||
.lock()
|
||||
.expect("read tracker poisoned")
|
||||
.insert(reference);
|
||||
// 用 OR 合并分类,避免后来发生的验证读取覆盖已有程序内容读取。
|
||||
for region in scope
|
||||
.regions
|
||||
.lock()
|
||||
@@ -363,25 +471,32 @@ impl Heap {
|
||||
}
|
||||
}
|
||||
|
||||
/// Store::allocate 提交已校验的载荷;trace 引用边先检查,成功返回一份外部拥有根。
|
||||
pub(crate) fn allocate(
|
||||
&mut self,
|
||||
payload: Box<dyn ObjectPayload>,
|
||||
) -> Result<ObjectRef, VmError> {
|
||||
// 收集每条载荷强边,包括重复项;仅构造身份快照,不立即改计数。
|
||||
let mut edges = Vec::new();
|
||||
payload.trace(&mut |reference| edges.push(reference));
|
||||
// 先验证所有目标及累积计数溢出,失败时尚未改变堆。
|
||||
let increments = self.prepare_increments(&edges)?;
|
||||
// 优先复用已释放且代际未耗尽的空槽,否则追加一个新槽。
|
||||
let index = self.free.pop().unwrap_or(self.slots.len());
|
||||
// 新槽从代际零开始;复用槽保留 take 已递增的代际。
|
||||
if index == self.slots.len() {
|
||||
self.slots.push(Slot {
|
||||
generation: 0,
|
||||
object: None,
|
||||
});
|
||||
}
|
||||
// 完整身份把堆、位置和当前代际绑定,不能用 index 单独代替。
|
||||
let reference = ObjectRef {
|
||||
heap_id: self.id,
|
||||
index,
|
||||
generation: self.slots[index].generation,
|
||||
};
|
||||
// 新对象初始一份外部根、没有名义类型和属性;对象实体在 Box 中地址稳定。
|
||||
let object = Box::new(EndorObject {
|
||||
header: ObjectHeader {
|
||||
identity: reference,
|
||||
@@ -395,11 +510,13 @@ impl Heap {
|
||||
payload_edges: edges,
|
||||
});
|
||||
self.apply_increments(increments);
|
||||
// 引用增量已提交后才发布占用槽,live 与占用状态一起增加。
|
||||
self.slots[index].object = Some(object);
|
||||
self.live += 1;
|
||||
Ok(reference)
|
||||
}
|
||||
|
||||
/// 所有堆操作的只读验证入口;检查堆归属、槽范围、代际及占用状态,不登记读取依赖。
|
||||
pub(crate) fn get(&self, reference: ObjectRef) -> Result<&EndorObject, VmError> {
|
||||
self.validate_owner(reference)?;
|
||||
self.slots
|
||||
@@ -409,22 +526,27 @@ impl Heap {
|
||||
.ok_or(VmError::InvalidHandle)
|
||||
}
|
||||
|
||||
/// types::attach_type 完成类型合法性校验后调用;建立不可替换的头部类型强边。
|
||||
pub(crate) fn attach_type(
|
||||
&mut self,
|
||||
reference: ObjectRef,
|
||||
type_object: ObjectRef,
|
||||
) -> Result<(), VmError> {
|
||||
// 同一类型重复附着是无操作,不产生版本变化或写屏障检查。
|
||||
if self.get(reference)?.header.type_object == Some(type_object) {
|
||||
return Ok(());
|
||||
}
|
||||
// 一旦附着不能改成另一种类型。
|
||||
if self.get(reference)?.header.type_object.is_some() {
|
||||
return Err(VmError::InvalidArgument("object already has a type"));
|
||||
}
|
||||
// 防止动态 type 属性与托管头部类型产生两个冲突来源。
|
||||
if self.get(reference)?.attributes.contains_key("type") {
|
||||
return Err(VmError::InvalidArgument(
|
||||
"type attribute conflicts with managed type",
|
||||
));
|
||||
}
|
||||
// 类型边和目标写权限全部预检后,才共同提交计数、头部和版本通知。
|
||||
let increments = self.prepare_increments(&[type_object])?;
|
||||
self.prepare_change(reference)?;
|
||||
self.apply_increments(increments);
|
||||
@@ -433,22 +555,26 @@ impl Heap {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Transactional copy-on-update for built-in containers. The EndorObject
|
||||
/// box and identity stay stable; only its payload and edge snapshot change.
|
||||
/// All new edges are validated/retained before any old edge is released.
|
||||
/// Store/内置容器的复制后替换入口;保持对象盒与身份,只更新载荷和边快照。
|
||||
/// 所有新边先验证并保留,再释放旧边;同一目标同时出现在新旧边中也不会中途被回收。
|
||||
pub(crate) fn replace_payload(
|
||||
&mut self,
|
||||
reference: ObjectRef,
|
||||
payload: impl ObjectPayload,
|
||||
) -> Result<(), VmError> {
|
||||
self.get(reference)?;
|
||||
// 收集每条载荷强边,包括重复项;仅构造身份快照,不立即改计数。
|
||||
let mut edges = Vec::new();
|
||||
payload.trace(&mut |target| edges.push(target));
|
||||
// 先验证所有目标及累积计数溢出,失败时尚未改变堆。
|
||||
let increments = self.prepare_increments(&edges)?;
|
||||
self.prepare_change(reference)?;
|
||||
self.apply_increments(increments);
|
||||
// 写屏障和引用增量已提交,此处只更换载荷及其快照,不移动外层对象。
|
||||
let object = self.get_mut(reference)?;
|
||||
// 暂存旧边/旧载荷,等新状态与通知建立后再释放旧状态。
|
||||
let old_edges = std::mem::replace(&mut object.payload_edges, edges);
|
||||
// 旧载荷也要暂存到变更通知及旧引用边释放之后再析构,保持状态提交顺序。
|
||||
let old_payload = std::mem::replace(&mut object.payload, Box::new(payload));
|
||||
self.record_change(reference, ChangeKey::Contents);
|
||||
self.release_edges(old_edges);
|
||||
@@ -456,28 +582,36 @@ impl Heap {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 内部受控修改借用;仍先复用 get 的完整校验,不向 Store 外公开任意可变对象。
|
||||
fn get_mut(&mut self, reference: ObjectRef) -> Result<&mut EndorObject, VmError> {
|
||||
self.get(reference)?;
|
||||
Ok(self.slots[reference.index].object.as_deref_mut().unwrap())
|
||||
}
|
||||
|
||||
/// get 首先区分跨堆句柄,避免把异堆对象误认为本堆的同号槽。
|
||||
fn validate_owner(&self, reference: ObjectRef) -> Result<(), VmError> {
|
||||
// owner 错误独立于槽是否存在,弱引用升级不能把跨堆误判成过期。
|
||||
if reference.heap_id != self.id {
|
||||
return Err(VmError::ForeignHandle);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 分配/替换载荷、类型附着、属性写入共同预检引用边;只计算增量,不实际改计数。
|
||||
fn prepare_increments(
|
||||
&self,
|
||||
references: &[ObjectRef],
|
||||
) -> Result<BTreeMap<ObjectRef, usize>, VmError> {
|
||||
// 按目标聚合重复边,既保留多重拥有关系,又能一次预检总增量。
|
||||
let mut counts = BTreeMap::<ObjectRef, usize>::new();
|
||||
// 第一遍验证每条边并累计次数;没有任何已提交的计数修改。
|
||||
for reference in references {
|
||||
self.get(*reference)?;
|
||||
// 连重复次数本身也要 checked_add,不能依赖后续总计数检查替代。
|
||||
let count = counts.entry(*reference).or_default();
|
||||
*count = count.checked_add(1).ok_or(VmError::RefCountOverflow)?;
|
||||
}
|
||||
// 第二遍确保每个目标的当前强计数能容纳完整增量。
|
||||
for (reference, count) in &counts {
|
||||
self.get(*reference)?
|
||||
.header
|
||||
@@ -488,18 +622,24 @@ impl Heap {
|
||||
Ok(counts)
|
||||
}
|
||||
|
||||
/// 上述写入入口在预检和写屏障均通过后提交内部强边增量;不增加外部根。
|
||||
fn apply_increments(&mut self, increments: BTreeMap<ObjectRef, usize>) {
|
||||
// 仅消费刚刚预检的增量;这段提交路径没有新的可恢复失败分支。
|
||||
for (reference, count) in increments {
|
||||
self.get_mut(reference).unwrap().header.strong_refs += count;
|
||||
}
|
||||
}
|
||||
|
||||
/// Store/VM 将借用句柄变为额外外部根;两个计数全部预检成功后再共同更新。
|
||||
pub(crate) fn retain(&mut self, reference: ObjectRef) -> Result<(), VmError> {
|
||||
// 取得已验证的头部;外部根与总强计数必须保持一致。
|
||||
let header = &mut self.get_mut(reference)?.header;
|
||||
// 先计算两个新值,第二项溢出时第一项也尚未写回。
|
||||
let strong = header
|
||||
.strong_refs
|
||||
.checked_add(1)
|
||||
.ok_or(VmError::RefCountOverflow)?;
|
||||
// 外部根同样独立检查溢出,不把内部强边误计为宿主根。
|
||||
let external = header
|
||||
.external_refs
|
||||
.checked_add(1)
|
||||
@@ -509,13 +649,17 @@ impl Heap {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Store/VM 归还一份外部根;总强计数归零时启动非递归级联释放。
|
||||
pub(crate) fn release(&mut self, reference: ObjectRef) -> Result<(), VmError> {
|
||||
// 取得已验证的头部;外部根与总强计数必须保持一致。
|
||||
let header = &mut self.get_mut(reference)?.header;
|
||||
// 只有拥有的外部根可 release;借用或内部边不能从这里归还。
|
||||
if header.external_refs == 0 {
|
||||
return Err(VmError::NoOwnedReference);
|
||||
}
|
||||
header.external_refs -= 1;
|
||||
header.strong_refs -= 1;
|
||||
// 仍有内部边时留待后续释放或环回收;只有总强计数为零才立即析构。
|
||||
let pending = if header.strong_refs == 0 {
|
||||
vec![reference]
|
||||
} else {
|
||||
@@ -525,6 +669,7 @@ impl Heap {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Store 定位实际属性存储后写入;先保留新值强边,再替换并释放旧值边。
|
||||
pub(crate) fn set_attr(
|
||||
&mut self,
|
||||
reference: ObjectRef,
|
||||
@@ -532,24 +677,29 @@ impl Heap {
|
||||
value: Value,
|
||||
) -> Result<(), VmError> {
|
||||
self.check_attributes(reference)?;
|
||||
// 只有对象变体形成内部强边,直接标量不参与托管对象计数。
|
||||
let edges: Vec<_> = match &value {
|
||||
Value::Object(target) => vec![*target],
|
||||
_ => Vec::new(),
|
||||
};
|
||||
// 先验证所有目标及累积计数溢出,失败时尚未改变堆。
|
||||
let increments = self.prepare_increments(&edges)?;
|
||||
self.prepare_change(reference)?;
|
||||
self.apply_increments(increments);
|
||||
// 写屏障通过后移出旧值;属性更新本身不消耗调用者的外部根。
|
||||
let old = self
|
||||
.get_mut(reference)?
|
||||
.attributes
|
||||
.insert(name.clone(), value);
|
||||
self.record_change(reference, ChangeKey::Attribute(name));
|
||||
// 新对象引用已先保留,最后释放旧属性拥有的那一条内部边。
|
||||
if let Some(Value::Object(target)) = old {
|
||||
self.release_edges(vec![target]);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// types 的属性查找读取自身字段表;返回借用 Value 表示,继承/转发由上层处理。
|
||||
pub(crate) fn get_attr(&self, reference: ObjectRef, name: &str) -> Result<Value, VmError> {
|
||||
self.check_attributes(reference)?;
|
||||
self.get(reference)?
|
||||
@@ -559,23 +709,28 @@ impl Heap {
|
||||
.ok_or_else(|| VmError::AttributeNotFound(name.into()))
|
||||
}
|
||||
|
||||
/// Store 定位存储并通过类型保护后删除属性,成功才发通知并释放旧值强边。
|
||||
pub(crate) fn del_attr(&mut self, reference: ObjectRef, name: &str) -> Result<(), VmError> {
|
||||
self.check_attributes(reference)?;
|
||||
self.prepare_change(reference)?;
|
||||
// 写屏障通过后移出旧值;属性更新本身不消耗调用者的外部根。
|
||||
let old = self
|
||||
.get_mut(reference)?
|
||||
.attributes
|
||||
.remove(name)
|
||||
.ok_or_else(|| VmError::AttributeNotFound(name.into()))?;
|
||||
self.record_change(reference, ChangeKey::Attribute(name.into()));
|
||||
// 删除成功后释放该属性原有的对象边,标量仅由 Rust Drop 清理。
|
||||
if let Value::Object(target) = old {
|
||||
self.release_edges(vec![target]);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 所有受控写入在实际修改前调用:拒绝受保护对象,并预检版本是否还能递增。
|
||||
pub(crate) fn prepare_change(&self, reference: ObjectRef) -> Result<(), VmError> {
|
||||
self.get(reference)?;
|
||||
// 任意外层保护都有效;不能借进入/退出更内层作用域绕过只读限制。
|
||||
if self
|
||||
.read_only
|
||||
.iter()
|
||||
@@ -593,28 +748,36 @@ impl Heap {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 受控写入成功后递增对象版本,再合并通知;调用者必须先通过 prepare_change。
|
||||
pub(crate) fn record_change(&mut self, reference: ObjectRef, key: ChangeKey) {
|
||||
// prepare_change 已验证版本可递增;失败说明内部调用顺序破坏了约定。
|
||||
let object = self.get_mut(reference).expect("validated change target");
|
||||
object.header.change_version += 1;
|
||||
// 先保存递增后版本,即使不发通知也保持版本真实反映修改。
|
||||
let version = object.header.change_version;
|
||||
// Fresh result construction has no existing observers. Keep versions,
|
||||
// but do not notify application dependencies about derived allocations.
|
||||
// 只读期间允许的写入只涉及新结果;保留版本,但不向应用发布派生结果构造通知。
|
||||
if self.is_read_only() {
|
||||
return;
|
||||
}
|
||||
// 本批已丢失精确信息,继续积累条目也无法补全,等待消费者全面重验。
|
||||
if self.changes_overflow {
|
||||
return;
|
||||
}
|
||||
// 同对象/键合并为最新版本;其他键仍分别保留。
|
||||
let entry = (reference, key);
|
||||
// 达上限时仅新增键触发溢出;已有键可继续更新。溢出后清空并设置全面重验标志。
|
||||
if self.changes.len() >= MAX_PENDING_CHANGES && !self.changes.contains_key(&entry) {
|
||||
self.changes.clear();
|
||||
self.changes_overflow = true;
|
||||
} else {
|
||||
// 未溢出或仅更新已有键,保存该次提交的版本。
|
||||
self.changes.insert(entry, version);
|
||||
}
|
||||
}
|
||||
|
||||
/// Store/宿主取走本批通知及溢出标志,使后续写入开始新的批次。
|
||||
pub(crate) fn take_changes(&mut self) -> ChangeBatch {
|
||||
// mem::take 将记录和标志一起归还调用者,本堆恢复空批次。
|
||||
ChangeBatch {
|
||||
changes: std::mem::take(&mut self.changes)
|
||||
.into_iter()
|
||||
@@ -628,18 +791,25 @@ impl Heap {
|
||||
}
|
||||
}
|
||||
|
||||
/// 属性读写共用载荷能力检查;不允许字段的载荷即使没有该属性也先报协议错误。
|
||||
fn check_attributes(&self, reference: ObjectRef) -> Result<(), VmError> {
|
||||
// get 先验证身份;协议能力由载荷声明,不由属性表是否为空推断。
|
||||
if !self.get(reference)?.payload.allows_attributes() {
|
||||
return Err(VmError::UnsupportedOperation("attributes"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 载荷替换或属性更新移除旧内部边;逐边减强计数,随后统一处理新出现的零计数对象。
|
||||
fn release_edges(&mut self, edges: Vec<ObjectRef>) {
|
||||
// 工作栈只保存待处理身份,不拥有额外根;用于批量级联或可达性遍历。
|
||||
let mut pending = Vec::new();
|
||||
// 每条旧边恰好减一次,重复边不能去重。
|
||||
for target in edges {
|
||||
// 已登记强边的目标应仍存活;这里 unwrap 表示内部图计数不变量。
|
||||
let header = &mut self.get_mut(target).unwrap().header;
|
||||
header.strong_refs -= 1;
|
||||
// 最后一条强边移除后加入工作栈,稍后再展开其拥有边。
|
||||
if header.strong_refs == 0 {
|
||||
pending.push(target);
|
||||
}
|
||||
@@ -647,11 +817,13 @@ impl Heap {
|
||||
self.drain_zero_counts(pending);
|
||||
}
|
||||
|
||||
/// Removes the object before running Drop. Reuse increments its generation;
|
||||
/// an exhausted generation permanently retires that slot.
|
||||
/// 级联释放和环回收先从槽中摘除对象,再允许 Rust Drop;只接收已知存活身份。
|
||||
fn take(&mut self, reference: ObjectRef) -> Box<EndorObject> {
|
||||
// 取走盒后槽立即不可访问,析构尚未发生。
|
||||
let slot = &mut self.slots[reference.index];
|
||||
// 所有调用方都先证明对象存在,因此这里无需恢复性错误分支。
|
||||
let object = slot.object.take().unwrap();
|
||||
// 仅能递增代际的空槽才加入 free;耗尽时永久停用,防身份绕回。
|
||||
if let Some(next) = slot.generation.checked_add(1) {
|
||||
slot.generation = next;
|
||||
self.free.push(reference.index);
|
||||
@@ -660,12 +832,18 @@ impl Heap {
|
||||
object
|
||||
}
|
||||
|
||||
/// release/release_edges 共用工作栈,避免长引用链触发递归析构栈溢出。
|
||||
fn drain_zero_counts(&mut self, mut pending: Vec<ObjectRef>) {
|
||||
// 用显式工作栈逐个处理,避免对深链递归。
|
||||
while let Some(reference) = pending.pop() {
|
||||
// 先摘除自己,再递减出边;归零对象不可能仍有一条计入自身的有效强入边。
|
||||
let object = self.take(reference);
|
||||
// edges 枚举头部类型、载荷快照和属性边,均参与生命周期维护。
|
||||
for target in object.edges() {
|
||||
// 已登记强边的目标应仍存活;这里 unwrap 表示内部图计数不变量。
|
||||
let header = &mut self.get_mut(target).unwrap().header;
|
||||
header.strong_refs -= 1;
|
||||
// 最后一条强边移除后加入工作栈,稍后再展开其拥有边。
|
||||
if header.strong_refs == 0 {
|
||||
pending.push(target);
|
||||
}
|
||||
@@ -674,25 +852,31 @@ impl Heap {
|
||||
}
|
||||
}
|
||||
|
||||
/// Full, explicit cycle collection. External references are roots; every
|
||||
/// managed attribute and declared payload edge participates in tracing.
|
||||
/// Store 显式全堆环回收:从外部根标记可达对象,余下对象统一摘除并释放。
|
||||
pub(crate) fn collect_cycles(&mut self) -> usize {
|
||||
// 标记按槽索引存储;本次 &mut self 独占期间槽不会被其他调用修改。
|
||||
let mut marked = vec![false; self.slots.len()];
|
||||
// 工作栈只保存待处理身份,不拥有额外根;用于批量级联或可达性遍历。
|
||||
let mut pending = Vec::new();
|
||||
// 只有外部根作为起点;纯内部互引环即使 strong_refs 非零也不是根。
|
||||
for slot in &self.slots {
|
||||
// 跳过空槽和没有外部根的对象,后者仍可能经其他根遍历到。
|
||||
if let Some(object) = &slot.object
|
||||
&& object.header.external_refs > 0
|
||||
{
|
||||
pending.push(object.header.identity);
|
||||
}
|
||||
}
|
||||
// 从根沿强边展开对象图,工作栈允许重复入栈,marked 保证只展开一次。
|
||||
while let Some(reference) = pending.pop() {
|
||||
// 已标记身份无需重复展开,保证带环图遍历会终止。
|
||||
if marked[reference.index] {
|
||||
continue;
|
||||
}
|
||||
marked[reference.index] = true;
|
||||
pending.extend(self.get(reference).unwrap().edges());
|
||||
}
|
||||
// 所有未标记的占用槽都是垃圾;空槽不进入集合。
|
||||
let garbage: Vec<_> = self
|
||||
.slots
|
||||
.iter()
|
||||
@@ -700,23 +884,29 @@ impl Heap {
|
||||
.filter(|(index, _)| !marked[*index])
|
||||
.filter_map(|(_, slot)| slot.object.as_ref().map(|object| object.header.identity))
|
||||
.collect();
|
||||
// 先摘除整批垃圾,避免环内互引让析构依赖逐对象处理的次序。
|
||||
let objects: Vec<_> = garbage
|
||||
.iter()
|
||||
.map(|reference| self.take(*reference))
|
||||
.collect();
|
||||
// All garbage slots are detached before touching edges or running Drop.
|
||||
// 垃圾槽已全部摘除:垃圾到垃圾的边无需再减计数,只有指向幸存者的边要归还。
|
||||
// 用已取出的对象盒枚举边快照,不尝试通过失效句柄重新借用垃圾对象。
|
||||
for object in &objects {
|
||||
// edges 枚举头部类型、载荷快照和属性边,均参与生命周期维护。
|
||||
for target in object.edges() {
|
||||
// 仍可 get 的目标是外部根可达幸存者;它的强计数扣除来自垃圾的一条边。
|
||||
if let Ok(survivor) = self.get_mut(target) {
|
||||
survivor.header.strong_refs -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 返回摘除对象数;最后统一 Drop 释放宿主资源。
|
||||
let collected = objects.len();
|
||||
drop(objects);
|
||||
collected
|
||||
}
|
||||
|
||||
/// Store::object_count 查询当前存活对象数,不包含已释放空槽。
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.live
|
||||
}
|
||||
@@ -728,31 +918,45 @@ mod tests {
|
||||
use crate::Record;
|
||||
|
||||
#[test]
|
||||
/// 内嵌回归测试:伪造越界槽与错误代际,分别验证不可变/可变入口都拒绝。
|
||||
fn invalid_slots_and_generations_are_rejected() {
|
||||
// 测试直接使用内部 Heap,以便注入公共 Store API 不允许构造的异常状态。
|
||||
let mut heap = Heap::new().unwrap();
|
||||
// 一个真实有效的对象作为基准,之后只篡改句柄中的单个维度。
|
||||
let reference = heap.allocate(Box::new(Record)).unwrap();
|
||||
// 保持堆身份与代际有效,仅让槽越界,隔离范围检查。
|
||||
let invalid = ObjectRef {
|
||||
index: usize::MAX,
|
||||
..reference
|
||||
};
|
||||
// 保持槽位置有效,仅伪造代际,覆盖可变借用路径的同一校验。
|
||||
let stale = ObjectRef {
|
||||
generation: 1,
|
||||
..reference
|
||||
};
|
||||
// 槽索引越界时,即使堆身份和代际来自有效对象,也必须拒绝只读访问。
|
||||
assert!(matches!(heap.get(invalid), Err(VmError::InvalidHandle)));
|
||||
// 可变访问同样检查代际,伪造旧代际不能借出当前对象。
|
||||
assert!(matches!(heap.get_mut(stale), Err(VmError::InvalidHandle)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 内嵌回归测试:槽复用会改变代际,代际耗尽则永久退休,不让旧句柄指向新对象。
|
||||
fn reused_slots_never_alias_and_exhausted_generations_retire() {
|
||||
// 测试直接使用内部 Heap,以便注入公共 Store API 不允许构造的异常状态。
|
||||
let mut heap = Heap::new().unwrap();
|
||||
// 先释放第一个对象,使它的槽成为下一次分配的复用候选。
|
||||
let first = heap.allocate(Box::new(Record)).unwrap();
|
||||
heap.release(first).unwrap();
|
||||
// 复用索引相同、代际不同,旧身份必须失效。
|
||||
let second = heap.allocate(Box::new(Record)).unwrap();
|
||||
assert_eq!(first.index, second.index);
|
||||
assert_ne!(first.generation, second.generation);
|
||||
// 复用同一槽但代际变化后,旧句柄必须失效,不能别名到新对象。
|
||||
assert!(matches!(heap.get(first), Err(VmError::InvalidHandle)));
|
||||
// 内部故障注入:把槽及对象身份同步改成最高代际,再验证释放后的永久退休。
|
||||
heap.slots[second.index].generation = u64::MAX;
|
||||
// 构造与被注入槽一致的当前有效身份,避免把测试误变成代际不匹配。
|
||||
let last = ObjectRef {
|
||||
generation: u64::MAX,
|
||||
..second
|
||||
@@ -764,15 +968,21 @@ mod tests {
|
||||
.header
|
||||
.identity = last;
|
||||
heap.release(last).unwrap();
|
||||
// 耗尽槽不得复用,新的对象只能取得其他索引。
|
||||
let next = heap.allocate(Box::new(Record)).unwrap();
|
||||
assert_ne!(next.index, last.index);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 内嵌回归测试:注入强计数上限,确认 retain 与属性写入溢出时没有部分提交。
|
||||
fn failed_reference_increments_leave_counts_and_attributes_unchanged() {
|
||||
// 测试直接使用内部 Heap,以便注入公共 Store API 不允许构造的异常状态。
|
||||
let mut heap = Heap::new().unwrap();
|
||||
// owner 接收属性边,target 模拟无法再增加强计数的对象。
|
||||
let owner = heap.allocate(Box::new(Record)).unwrap();
|
||||
// 另建目标对象,随后将强计数推至上限,测试 retain 失败不能修改外部根或 owner 的边。
|
||||
let target = heap.allocate(Box::new(Record)).unwrap();
|
||||
// 仅改强计数,外部根仍为一;后面的断言检查失败是否意外改动 external_refs。
|
||||
heap.get_mut(target).unwrap().header.strong_refs = usize::MAX;
|
||||
assert_eq!(heap.retain(target), Err(VmError::RefCountOverflow));
|
||||
assert_eq!(heap.get(target).unwrap().header.external_refs, 1);
|
||||
|
||||
@@ -1,48 +1,87 @@
|
||||
//! 编译器与 VM 共享的指令协议:编译器构造逻辑指令,编码器产生双字节代码单元,
|
||||
//! EndorCode 构造时解码并验证,eval 消费已解码指令;这里不执行指令或维护运行栈。
|
||||
|
||||
use crate::{CompareOp, VmError};
|
||||
|
||||
/// Endor wordcode, following CPython's opcode/oparg code-unit design.
|
||||
/// Numeric opcode IDs are Endor's own, not a CPython bytecode ABI.
|
||||
/// Endor 自有操作码;每个物理代码单元含 opcode/oparg 两字节。
|
||||
/// 采用类似 CPython 的单元设计,但编号与其字节码 ABI 不兼容。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum Opcode {
|
||||
/// 无操作,无操作数。
|
||||
Nop = 0,
|
||||
/// 以操作数索引代码常量表,将借用常量保留为栈根。
|
||||
LoadConst = 1,
|
||||
/// 读取 localsplus 局部槽,操作数为槽索引。
|
||||
LoadFast = 2,
|
||||
/// 弹出栈顶写局部槽,执行器先处理对象赋值协议。
|
||||
StoreFast = 3,
|
||||
/// 清除局部槽并归还其根,未绑定时报错。
|
||||
DeleteFast = 4,
|
||||
/// 以 names 索引查全局空间,缺失再查函数捕获的内置空间。
|
||||
LoadGlobal = 5,
|
||||
/// 以 names 索引将栈顶写入全局字典并弹出。
|
||||
StoreGlobal = 6,
|
||||
/// 以 names 索引删除全局键,缺失时报错。
|
||||
DeleteGlobal = 7,
|
||||
/// 丢弃栈顶并释放对应根。
|
||||
PopTop = 8,
|
||||
/// 复制距栈顶第 argument 项(从 1 起),对象值新增栈根。
|
||||
Copy = 9,
|
||||
/// 交换栈顶与第 argument 项,合法深度至少为 2。
|
||||
Swap = 10,
|
||||
/// 压入调用协议专用 Null 标记,不是语言 Nil。
|
||||
PushNull = 11,
|
||||
/// 消费函数、接收者或 Null 及 argument 个显式参数,调用后留下结果。
|
||||
Call = 12,
|
||||
/// 移出帧结果并清理执行帧,无操作数。
|
||||
ReturnValue = 13,
|
||||
/// 从当前逻辑指令末尾向前跳 argument 个代码单元。
|
||||
JumpForward = 14,
|
||||
/// 从当前逻辑指令末尾向后跳 argument 个代码单元。
|
||||
JumpBackward = 15,
|
||||
/// 弹出条件,假时向前跳 argument 个代码单元。
|
||||
PopJumpIfFalse = 16,
|
||||
/// 弹出条件,真时向前跳 argument 个代码单元。
|
||||
PopJumpIfTrue = 17,
|
||||
/// 把栈顶经真值协议变为布尔值,无操作数。
|
||||
ToBool = 18,
|
||||
/// 对栈顶求真后逻辑取反,无操作数。
|
||||
UnaryNot = 19,
|
||||
/// 比较栈顶两个值;操作数采用 compare/compare_operation 的映射。
|
||||
CompareOp = 20,
|
||||
/// 读取 localsplus 对应 Cell 内值,空 Cell 报未绑定。
|
||||
LoadDeref = 21,
|
||||
/// 把栈顶写入对应 Cell,保持闭包共享槽身份。
|
||||
StoreDeref = 22,
|
||||
/// 清空对应 Cell,后续读取视为未绑定。
|
||||
DeleteDeref = 23,
|
||||
/// 压入对应 Cell 自身句柄,供创建嵌套闭包使用。
|
||||
LoadClosure = 24,
|
||||
/// 以 names 索引绑定栈顶对象属性,以所得值替换 owner 栈项。
|
||||
LoadAttr = 25,
|
||||
/// 以 names 索引把栈中的值写入对象属性。
|
||||
StoreAttr = 26,
|
||||
/// 消费 argument 对键值,构造插入顺序字典。
|
||||
BuildMap = 27,
|
||||
/// 操作数低/高 16 位分别计默认值与捕获槽数量。
|
||||
MakeFunction = 28,
|
||||
/// 消费四个构类输入并压入类型结果,无操作数。
|
||||
BuildClass = 29,
|
||||
/// 观察栈顶等待对象,Pending 保存暂停位置,Ready 替换为结果。
|
||||
Await = 30,
|
||||
/// 操作数指定算术种类,对两个栈值求值并压入结果。
|
||||
BinaryArithmetic = 31,
|
||||
/// 对栈顶数值取负,无操作数。
|
||||
UnaryNegate = 32,
|
||||
/// 编码器专用高位前缀;逻辑指令构造方不得直接提交。
|
||||
ExtendedArg = 255,
|
||||
}
|
||||
|
||||
impl Opcode {
|
||||
/// 物理解码器验证一个操作码字节;未知编号返回 None,不做不安全 enum 转换。
|
||||
fn decode(byte: u8) -> Option<Self> {
|
||||
// 显式白名单保证保留编号不会误解为已实现指令。
|
||||
Some(match byte {
|
||||
0 => Self::Nop,
|
||||
1 => Self::LoadConst,
|
||||
@@ -82,6 +121,7 @@ impl Opcode {
|
||||
})
|
||||
}
|
||||
|
||||
/// 编解码共用的操作数分类;无操作数指令只能携带零且不得有扩展前缀。
|
||||
pub(crate) fn takes_argument(self) -> bool {
|
||||
!matches!(
|
||||
self,
|
||||
@@ -98,25 +138,28 @@ impl Opcode {
|
||||
}
|
||||
}
|
||||
|
||||
/// A decoded logical instruction; EXTENDED_ARG is handled by the codec.
|
||||
/// 编译器构造的逻辑指令;完整操作数为 u32,扩展前缀由编解码器处理。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Instruction {
|
||||
/// 逻辑操作,不应手工指定 ExtendedArg。
|
||||
pub opcode: Opcode,
|
||||
/// 完整操作数;具体含义由 opcode 决定,构造时尚未做代码布局校验。
|
||||
pub argument: u32,
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
/// 编译器/宿主组装指令;这里只赋值,合法索引和栈效果由 EndorCode 校验。
|
||||
pub const fn new(opcode: Opcode, argument: u32) -> Self {
|
||||
Self { opcode, argument }
|
||||
}
|
||||
/// 编译器发出无参数指令的便捷入口,把 argument 明确置零;此处不检查 opcode 是否真的无参数。
|
||||
pub const fn simple(opcode: Opcode) -> Self {
|
||||
Self::new(opcode, 0)
|
||||
}
|
||||
|
||||
/// Build a function from [defaults..., closure cells..., code] on the stack.
|
||||
/// The low 16 operand bits count trailing positional defaults; the high
|
||||
/// 16 bits count cells in the code's free-variable order. No tuples or
|
||||
/// subsequent mutation of the function object are required.
|
||||
/// 编译器为栈布局 [默认值..., 捕获 Cell..., code] 生成函数创建指令。
|
||||
/// 低 16 位计末尾位置参数默认值,高 16 位计按自由变量顺序排列的捕获槽。
|
||||
/// VM 一次构造函数,不需要额外元组或事后修改函数字段。
|
||||
pub const fn make_function(defaults: u16, closure: u16) -> Self {
|
||||
Self::new(
|
||||
Opcode::MakeFunction,
|
||||
@@ -124,9 +167,11 @@ impl Instruction {
|
||||
)
|
||||
}
|
||||
|
||||
/// 编译器将比较运算转成固定操作数,与下方反向解码表配对。
|
||||
pub const fn compare(operation: CompareOp) -> Self {
|
||||
Self::new(
|
||||
Opcode::CompareOp,
|
||||
// 将六种比较映射为稳定字节码编号,必须与 eval 的 CompareOp 解码一致。
|
||||
match operation {
|
||||
CompareOp::Lt => 0,
|
||||
CompareOp::Le => 1,
|
||||
@@ -138,29 +183,37 @@ impl Instruction {
|
||||
)
|
||||
}
|
||||
|
||||
/// Decode a comparison operand; None denotes an invalid operand.
|
||||
/// VM/校验器解析比较种类;超出六个固定值时返回 None。
|
||||
pub fn compare_operation(argument: u32) -> Option<CompareOp> {
|
||||
compare_operation(argument)
|
||||
}
|
||||
|
||||
/// Decode the default-value and closure-cell counts of MAKE_FUNCTION.
|
||||
/// VM 创建函数及栈校验器拆出默认值数、闭包槽数,不访问运行栈。
|
||||
pub fn function_counts(argument: u32) -> (usize, usize) {
|
||||
function_counts(argument)
|
||||
}
|
||||
|
||||
/// Encodes 8-bit operands directly and prefixes up to three EXTENDED_ARG
|
||||
/// code units for larger operands. Each code unit is [opcode, operand].
|
||||
/// EndorCode::from_instructions 调用:小操作数直接编码,大操作数最多用三个前缀。
|
||||
/// 每单元两字节,前缀携带高位;不在这里重算跳转距离或校验常量/槽索引。
|
||||
/// 空输入可编码为空字节串,EndorCode 的后续解码才拒绝空代码,编码成功不等于可执行。
|
||||
pub fn encode_all(instructions: &[Self]) -> Result<Vec<u8>, VmError> {
|
||||
// 逐条追加;报错时局部缓冲丢弃,不向调用者返回部分编码。
|
||||
let mut bytes = Vec::new();
|
||||
// 逻辑列表中每项最终对应一个非前缀单元及零到三个前缀单元。
|
||||
for instruction in instructions {
|
||||
// 前缀由完整 u32 操作数推导,拒绝手工插入导致分组歧义。
|
||||
if instruction.opcode == Opcode::ExtendedArg {
|
||||
return Err(invalid(0, "EXTENDED_ARG is emitted by the encoder"));
|
||||
}
|
||||
// 无参数操作携带非零数据是协议错误,报告当前输出的代码单元位置。
|
||||
if !instruction.opcode.takes_argument() && instruction.argument != 0 {
|
||||
return Err(invalid(bytes.len() / 2, "unexpected instruction operand"));
|
||||
}
|
||||
// 大端拆分方便先发高位前缀,最低字节始终放在最终 opcode 单元。
|
||||
let parts = instruction.argument.to_be_bytes();
|
||||
// 跳过前导零;全零时选最后字节,保证仍发出一个有效单元。
|
||||
let first = parts.iter().position(|part| *part != 0).unwrap_or(3);
|
||||
// 只输出所需高位,最长三项,不把最低字节重复编码。
|
||||
for part in &parts[first..3] {
|
||||
bytes.extend([Opcode::ExtendedArg as u8, *part]);
|
||||
}
|
||||
@@ -170,17 +223,21 @@ impl Instruction {
|
||||
}
|
||||
}
|
||||
|
||||
/// 解码器产出、代码校验器和 eval 消费的逻辑位置;公开字段本身不保证由验证过的代码产生。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct DecodedInstruction {
|
||||
/// Offsets count two-byte code units, including EXTENDED_ARG prefixes.
|
||||
/// 逻辑指令起点(包括首个前缀),单位是双字节代码单元。
|
||||
pub offset: usize,
|
||||
/// 最后一个实际 opcode 单元之后的位置,作为相对跳转的基点。
|
||||
pub next_offset: usize,
|
||||
/// 已合并前缀的完整逻辑指令。
|
||||
pub instruction: Instruction,
|
||||
}
|
||||
|
||||
impl DecodedInstruction {
|
||||
/// Resolve a jump relative to the end of this logical instruction.
|
||||
/// VM/校验器相对逻辑指令末尾解析跳转;此处不检查目标是否为有效指令边界。
|
||||
pub fn jump_target(self) -> Result<usize, VmError> {
|
||||
// 非跳转操作不能借用跳转计算;条件跳转按前向距离解释。
|
||||
if !matches!(
|
||||
self.instruction.opcode,
|
||||
Opcode::JumpForward
|
||||
@@ -194,37 +251,52 @@ impl DecodedInstruction {
|
||||
}
|
||||
}
|
||||
|
||||
/// EndorCode::with_layout 调用,将物理单元分组成逻辑指令;后续 verify 检查控制流与栈。
|
||||
pub(crate) fn decode(bytes: &[u8]) -> Result<Vec<DecodedInstruction>, VmError> {
|
||||
// 拒绝空代码与半个单元,保证下方 cursor+1 总能访问操作数字节。
|
||||
if bytes.is_empty() || !bytes.len().is_multiple_of(2) {
|
||||
return Err(invalid(
|
||||
0,
|
||||
"wordcode must contain complete two-byte code units",
|
||||
));
|
||||
}
|
||||
// 输出逻辑指令列表与物理字节游标分开,前缀不会独立进入输出。
|
||||
let mut instructions = Vec::new();
|
||||
// cursor 以字节计,保存到 offset/next_offset 时再除以二。
|
||||
let mut cursor = 0;
|
||||
// 外层每次完整读一条逻辑指令,内层吸收连续高位前缀。
|
||||
while cursor < bytes.len() {
|
||||
// 记住首前缀/实际指令的起点,错误定位与跳转边界均用它。
|
||||
let start = cursor / 2;
|
||||
// 从高到低合并最多四个操作数字节。
|
||||
let mut argument = 0_u32;
|
||||
// 前缀最多三个,第四个将溢出 u32 编码容量。
|
||||
let mut prefixes = 0;
|
||||
// 遇到实际 opcode 才完成本条;流末尾仍只有前缀时报告悬空前缀。
|
||||
loop {
|
||||
// 前缀之后必须还有完整单元,不能将截断流视作正常结束。
|
||||
if cursor >= bytes.len() {
|
||||
return Err(invalid(start, "dangling EXTENDED_ARG"));
|
||||
}
|
||||
// 验证编号后再组合操作数,未知编号的错误位置指向该物理单元。
|
||||
let opcode = Opcode::decode(bytes[cursor])
|
||||
.ok_or_else(|| invalid(cursor / 2, "unknown opcode"))?;
|
||||
argument = (argument << 8) | u32::from(bytes[cursor + 1]);
|
||||
cursor += 2;
|
||||
// 前缀只累积数据,不压入逻辑指令列表。
|
||||
if opcode == Opcode::ExtendedArg {
|
||||
prefixes += 1;
|
||||
// 在读取第五个数据字节之前拒绝,防截断高位。
|
||||
if prefixes > 3 {
|
||||
return Err(invalid(start, "too many EXTENDED_ARG prefixes"));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// 即使扩展数据全零,无参数指令也不允许前缀。
|
||||
if !opcode.takes_argument() && (argument != 0 || prefixes != 0) {
|
||||
return Err(invalid(start, "unexpected instruction operand"));
|
||||
}
|
||||
// 起点保留首前缀位置,末尾记录已消费单元之后的位置。
|
||||
instructions.push(DecodedInstruction {
|
||||
offset: start,
|
||||
next_offset: cursor / 2,
|
||||
@@ -236,6 +308,7 @@ pub(crate) fn decode(bytes: &[u8]) -> Result<Vec<DecodedInstruction>, VmError> {
|
||||
Ok(instructions)
|
||||
}
|
||||
|
||||
/// Instruction 公共查询和代码校验共用的比较编号反向映射。
|
||||
pub(crate) fn compare_operation(argument: u32) -> Option<CompareOp> {
|
||||
Some(match argument {
|
||||
0 => CompareOp::Lt,
|
||||
@@ -248,10 +321,12 @@ pub(crate) fn compare_operation(argument: u32) -> Option<CompareOp> {
|
||||
})
|
||||
}
|
||||
|
||||
/// 解出 MAKE_FUNCTION 的两个无符号计数;合法槽布局由 EndorCode/执行器另行检查。
|
||||
pub(crate) fn function_counts(argument: u32) -> (usize, usize) {
|
||||
((argument & 0xffff) as usize, (argument >> 16) as usize)
|
||||
}
|
||||
|
||||
/// 编解码与代码校验统一构造带代码单元偏移的错误,不把偏移转成源文件行号。
|
||||
pub(crate) fn invalid(offset: usize, reason: &'static str) -> VmError {
|
||||
VmError::InvalidBytecode { offset, reason }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Core Endor objects, values, validated executable data and object storage.
|
||||
//! This crate has no VM evaluator or external dependencies. Handles, reference
|
||||
//! accounting and mutable payload replacement remain encapsulated here.
|
||||
//! Endor 对象、数值、已验证代码和存储的公共 API;compiler/core 和宿主都从这里导入。
|
||||
//! 不包含 VM 指令求值器;数值精确运算使用 num_* 依赖,句柄/计数/载荷修改仍封装在本 crate。
|
||||
//! 下方示例由文档测试调用:创建拥有根、读写属性,最后 release,不把复制句柄误当新增根。
|
||||
//!
|
||||
//! ```
|
||||
//! use objects::{ObjectStore, Value};
|
||||
@@ -13,41 +13,64 @@
|
||||
//! # Ok::<(), objects::VmError>(())
|
||||
//! ```
|
||||
|
||||
// 内部内置载荷:字符串、字典、函数、闭包槽和异常。
|
||||
mod builtins;
|
||||
// 内部变更通知结构,消费者据此失效缓存。
|
||||
mod changes;
|
||||
// 代码布局和构造校验,不执行代码。
|
||||
mod code;
|
||||
// 各层共享错误,避免对象层依赖 VM。
|
||||
mod error;
|
||||
// 私有堆:代际身份、根/边计数、只读追踪和回收。
|
||||
mod heap;
|
||||
// 指令协议及字节编解码,供编译器和执行器共用。
|
||||
mod instruction;
|
||||
// 宿主回调契约、上下文和结果根移交。
|
||||
mod native;
|
||||
// 无损数值表示和受预算约束的精确运算。
|
||||
mod number;
|
||||
// 直接值、载荷协议及公共对象实体。
|
||||
mod object;
|
||||
// 受控对象操作门面,宿主可独立于 VM 使用。
|
||||
mod store;
|
||||
// 字典/Cell/函数等内置载荷的 Store 扩展。
|
||||
mod store_builtins;
|
||||
// 名义类型、能力、字段注解及子模块中的契约/效果分析。
|
||||
mod types;
|
||||
|
||||
// 向宿主公开内置载荷,分配和修改仍经 Store。
|
||||
pub use builtins::{
|
||||
EndorBaseExceptionObject, EndorCellObject, EndorDictObject, EndorFunctionObject,
|
||||
EndorStringObject, EndorTracebackEntry,
|
||||
};
|
||||
// 向缓存消费者公开借用变更通知,不提供对象拥有根。
|
||||
pub use changes::{ChangeBatch, ChangeKey, ObjectChange};
|
||||
// 编译器构造与 VM 共享执行数据需要的代码 API。
|
||||
pub use code::{CodeLayout, EndorCode, EndorCodeObject, SourceSignature};
|
||||
// 调用者结构化匹配的统一错误。
|
||||
pub use error::VmError;
|
||||
// 只公开句柄和读取观察结果,不导出可变 Heap。
|
||||
pub use heap::{ObjectRef, ReadAccess, ReadDependency, ReadSet, TrackedReads, WeakObjectRef};
|
||||
// 编译器/宿主组装指令,执行器读取解码结果。
|
||||
pub use instruction::{DecodedInstruction, Instruction, Opcode};
|
||||
// 宿主实现原生函数并声明参数、返回值和效果。
|
||||
pub use native::{
|
||||
CallArity, CallEffect, CallSignature, CallType, ContextNativeFunction, NativeCallable,
|
||||
NativeContext, NativeResult,
|
||||
};
|
||||
// 编译器字面量、VM 算术和宿主显式转换共用数值 API。
|
||||
pub use number::{
|
||||
ArithmeticOp, EndorDecimal, EndorInteger, EndorNumber, EndorNumberObject, EndorRational,
|
||||
EndorReal,
|
||||
};
|
||||
// 宿主实现载荷及等待协议,保留旧原生函数别名兼容。
|
||||
pub use object::{
|
||||
AwaitPoll, CompareOp, EndorNativeCall, EndorNativeFunctionObject, EndorObject, NativeCall,
|
||||
NativeFunction, ObjectHeader, ObjectPayload, Record, Value,
|
||||
};
|
||||
// 独立存储的主要入口,VM 也持有同一门面。
|
||||
pub use store::ObjectStore;
|
||||
// 宿主注册类型/能力,执行器检查构造和调用契约。
|
||||
pub use types::{
|
||||
AnnotationType, ClassField, EndorTraitObject, EndorTypeObject, MethodRequirement, RootTypes,
|
||||
TypeConstructor, TypedMethodRequirement,
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
//! 宿主原生调用协议:Store 校验声明,VM 分派调用,NativeContext 管理临时根与输出所有权。
|
||||
//! 返回 Invoke 把调用交回 VM 的帧循环,避免 Rust 回调递归进入求值器。
|
||||
//! ReadOnly 是宿主承诺加托管写屏障;本模块不能拦截任意 Rust 回调自行执行的外部 I/O。
|
||||
|
||||
use std::{fmt, sync::Arc};
|
||||
|
||||
use crate::{ObjectPayload, ObjectRef, ObjectStore, Value, VmError, WeakObjectRef};
|
||||
@@ -5,8 +9,11 @@ use crate::{ObjectPayload, ObjectRef, ObjectStore, Value, VmError, WeakObjectRef
|
||||
/// Borrowed results produced inside a native call. Invoke delegates to another
|
||||
/// callable through the VM's existing frame loop, without recursively entering it.
|
||||
#[derive(Debug)]
|
||||
/// 同一结果类型在回调返回时携带借用值,NativeContext::execute 成功输出后才携带已保留的输出根。
|
||||
pub enum NativeResult {
|
||||
/// 回调内为借用结果;execute 成功输出时其中对象已获得移交给 VM 的根。
|
||||
Return(Value),
|
||||
/// 委托目标与参数在 execute 成功输出时各有拥有根,VM 接管后负责释放。
|
||||
Invoke {
|
||||
function: ObjectRef,
|
||||
arguments: Vec<Value>,
|
||||
@@ -17,27 +24,37 @@ pub enum NativeResult {
|
||||
/// The store validates metadata before use; usize::MAX denotes an open upper bound.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct CallArity {
|
||||
/// 最少位置参数数量。
|
||||
pub required: usize,
|
||||
/// 最多位置参数数量,usize::MAX 表示开放上界。
|
||||
pub maximum: usize,
|
||||
}
|
||||
impl CallArity {
|
||||
/// 宿主声明固定位置参数数量,含显式接收者。
|
||||
pub const fn exact(count: usize) -> Self {
|
||||
Self::range(count, count)
|
||||
}
|
||||
/// 宿主声明闭区间,构造时不验证上下界;使用前 validate。
|
||||
pub const fn range(required: usize, maximum: usize) -> Self {
|
||||
Self { required, maximum }
|
||||
}
|
||||
/// VM/契约检查查询数量是否落在闭区间,不改变元数据。
|
||||
/// 本方法不校验上下界本身;需要诊断非法元数据时应先走 validate/check 或 Store 查询。
|
||||
pub fn accepts(self, count: usize) -> bool {
|
||||
(self.required..=self.maximum).contains(&count)
|
||||
}
|
||||
/// Store/调用入口拒绝颠倒的上下界;成功返回原数量声明。
|
||||
pub(crate) fn validate(self) -> Result<Self, VmError> {
|
||||
// 上界不能小于下界,否则任何实际调用都无法满足有效区间。
|
||||
if self.required > self.maximum {
|
||||
return Err(VmError::InvalidArgument("invalid callable arity metadata"));
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
/// 调用入口先验证声明,再生成携带函数名和实参数量的错误。
|
||||
pub(crate) fn check(self, name: String, provided: usize) -> Result<(), VmError> {
|
||||
self.validate()?;
|
||||
// 元数据合法后再区分用户提供的数量不匹配。
|
||||
if !self.accepts(provided) {
|
||||
return Err(VmError::ArgumentCount {
|
||||
name,
|
||||
@@ -54,11 +71,15 @@ impl CallArity {
|
||||
/// existing objects or perform external I/O. Unknown legacy calls remain unknown.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CallEffect {
|
||||
/// 可分配结果,但承诺不改已有对象或执行外部 I/O。
|
||||
ReadOnly,
|
||||
/// The native body itself is read-only; delegated effects are dynamic and
|
||||
/// checked by the VM in the same execution/heap scope.
|
||||
/// 本体承诺只读;目标继续走同一作用域内的已发布效果检查和受控堆写屏障,不代表整条链已证明只读。
|
||||
Delegated,
|
||||
/// 声明可能修改已有托管对象,只读执行不允许。
|
||||
Mutating,
|
||||
/// 声明包含外部效果,只读执行不允许。
|
||||
External,
|
||||
}
|
||||
|
||||
@@ -66,40 +87,53 @@ pub enum CallEffect {
|
||||
/// all definition edges even when an earlier alternative already matches.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CallType {
|
||||
/// 单个已解析注解,不是源码字符串。
|
||||
Single(crate::AnnotationType),
|
||||
/// 平坦非空候选列表;所有定义引用都须有效。
|
||||
Union(Vec<crate::AnnotationType>),
|
||||
}
|
||||
impl From<crate::AnnotationType> for CallType {
|
||||
/// 宿主用单个 AnnotationType 构建原生签名时由 From/Into 包成 Single;这里不新增类型边或校验堆身份。
|
||||
fn from(value: crate::AnnotationType) -> Self {
|
||||
Self::Single(value)
|
||||
}
|
||||
}
|
||||
impl CallType {
|
||||
/// 宿主构造类型或 Unit 的平坦联合,供可空参数/返回值约束使用。
|
||||
pub fn nullable(value: crate::AnnotationType) -> Self {
|
||||
Self::Union(vec![value, crate::AnnotationType::Unit])
|
||||
}
|
||||
/// 内部检查和契约分析将单类型与联合统一为借用切片。
|
||||
pub(crate) fn alternatives(&self) -> &[crate::AnnotationType] {
|
||||
// 单项通过 from_ref 借用,联合直接借用 Vec,不额外分配或复制定义引用。
|
||||
match self {
|
||||
Self::Single(value) => std::slice::from_ref(value),
|
||||
Self::Union(values) => values,
|
||||
}
|
||||
}
|
||||
/// 载荷 trace/调用保护枚举所有名义类型与能力引用,不因提前匹配而漏边。
|
||||
pub fn references(&self) -> impl Iterator<Item = ObjectRef> + '_ {
|
||||
self.alternatives()
|
||||
.iter()
|
||||
.copied()
|
||||
.filter_map(crate::AnnotationType::reference)
|
||||
}
|
||||
/// 契约分析检查联合是否含 Any,不代表其他分支的定义边可省略。
|
||||
pub fn is_any(&self) -> bool {
|
||||
self.alternatives()
|
||||
.iter()
|
||||
// 只检测是否存在 Any 候选,不据此省略联合中其他定义引用的验证或保活。
|
||||
.any(|value| matches!(value, crate::AnnotationType::Any))
|
||||
}
|
||||
/// 调用/签名检查确认联合非空,且每个托管类型/能力引用有效并有正确载荷。
|
||||
pub fn validate(&self, store: &ObjectStore) -> Result<(), VmError> {
|
||||
// 空联合没有可接受值,必须作为声明错误拒绝。
|
||||
if self.alternatives().is_empty() {
|
||||
return Err(VmError::InvalidArgument("empty native union type"));
|
||||
}
|
||||
// 即使首个分支是 Any,也校验后续全部定义,不允许无效句柄藏在联合中。
|
||||
for value in self.alternatives() {
|
||||
// 只有名义/能力注解包含定义句柄,其他直接注解没有需验证的对象。
|
||||
match *value {
|
||||
crate::AnnotationType::Nominal(reference) => {
|
||||
store.payload::<crate::EndorTypeObject>(reference, "Type")?;
|
||||
@@ -112,6 +146,7 @@ impl CallType {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// 参数/返回检查先验证全部分支,再按顺序尝试匹配;全不匹配才生成类型错误。
|
||||
pub fn check(
|
||||
&self,
|
||||
store: &ObjectStore,
|
||||
@@ -120,11 +155,14 @@ impl CallType {
|
||||
value: &Value,
|
||||
) -> Result<(), VmError> {
|
||||
self.validate(store)?;
|
||||
// 通过完整声明校验后才允许按首个匹配项短路。
|
||||
for expected in self.alternatives() {
|
||||
// 匹配即成功;协议失败仍传播错误,而非当作不匹配。
|
||||
if store.matches_annotation(value, *expected)? {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
// 所有项均不匹配时才组装诊断类型名,不在成功热路径构造文本。
|
||||
let names = self
|
||||
.alternatives()
|
||||
.iter()
|
||||
@@ -142,14 +180,21 @@ impl CallType {
|
||||
/// Immutable native contract, including an explicit receiver where applicable.
|
||||
/// Missing fixed slots are covered by variadic; optionality comes from arity.
|
||||
#[derive(Debug, Clone)]
|
||||
/// 宿主在构造阶段可填写公开字段;载荷发布后按不可变声明使用,不能私自改变已快照的定义引用。
|
||||
pub struct CallSignature {
|
||||
/// 参数数量区间决定可省略的尾部参数,不由类型列表长度直接决定。
|
||||
pub arity: CallArity,
|
||||
/// 固定位置参数约束,含需要显式传入的接收者。
|
||||
pub parameters: Vec<CallType>,
|
||||
/// 固定列表之外的参数统一使用此约束。
|
||||
pub variadic: Option<CallType>,
|
||||
/// 直接/委托完成后应检查的结果约束。
|
||||
pub returns: CallType,
|
||||
/// 回调效果声明,供只读许可与方法替换契约判断。
|
||||
pub effect: CallEffect,
|
||||
}
|
||||
impl CallSignature {
|
||||
/// 宿主构造固定参数签名,不含可变参数;传入类型尚需使用前验证。
|
||||
pub fn exact(
|
||||
parameters: Vec<crate::AnnotationType>,
|
||||
returns: crate::AnnotationType,
|
||||
@@ -163,6 +208,7 @@ impl CallSignature {
|
||||
effect,
|
||||
}
|
||||
}
|
||||
/// 载荷 trace 和临时保护枚举固定、可变及返回类型中的全部托管引用。
|
||||
pub fn references(&self) -> impl Iterator<Item = ObjectRef> + '_ {
|
||||
self.parameters
|
||||
.iter()
|
||||
@@ -170,13 +216,16 @@ impl CallSignature {
|
||||
.chain([&self.returns])
|
||||
.flat_map(CallType::references)
|
||||
}
|
||||
/// Store/执行入口校验数量区间和类型覆盖完整性,并验证全部定义引用。
|
||||
pub fn validate(&self, store: &ObjectStore) -> Result<(), VmError> {
|
||||
self.arity.validate()?;
|
||||
// 没有可变约束时固定类型列表必须覆盖最大参数数;有可变约束时不能超出上界。
|
||||
if self.parameters.len() > self.arity.maximum
|
||||
|| (self.variadic.is_none() && self.parameters.len() != self.arity.maximum)
|
||||
{
|
||||
return Err(VmError::InvalidArgument("incomplete native signature"));
|
||||
}
|
||||
// 参数、可变项与返回项的定义引用都校验,不只验证本次实际传入的参数。
|
||||
for expected in self
|
||||
.parameters
|
||||
.iter()
|
||||
@@ -187,6 +236,7 @@ impl CallSignature {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// 原生调用进入回调前校验数量、效果许可和逐项类型,包括显式接收者。
|
||||
pub fn check_arguments(
|
||||
&self,
|
||||
store: &ObjectStore,
|
||||
@@ -195,6 +245,7 @@ impl CallSignature {
|
||||
) -> Result<(), VmError> {
|
||||
self.validate(store)?;
|
||||
self.arity.check(name.into(), arguments.len())?;
|
||||
// Delegated 只保证宿主本体只读,被委托目标的效果由 VM 在相同作用域继续检查。
|
||||
if store.is_read_only()
|
||||
&& !matches!(self.effect, CallEffect::ReadOnly | CallEffect::Delegated)
|
||||
{
|
||||
@@ -202,7 +253,9 @@ impl CallSignature {
|
||||
"native effect is forbidden during read-only execution",
|
||||
));
|
||||
}
|
||||
// 按位置选择固定约束,超出固定部分才回退到可变约束。
|
||||
for (index, value) in arguments.iter().enumerate() {
|
||||
// 数量合法不代表类型覆盖完整,因此缺少约束仍作为声明错误。
|
||||
let expected = self
|
||||
.parameters
|
||||
.get(index)
|
||||
@@ -212,6 +265,8 @@ impl CallSignature {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// 原生调用返回后校验结果类型;不重新验证整份参数元数据。
|
||||
/// 此处只检查借用 value,不取得结果拥有根;结果保留/移交由 NativeContext 或 VM 另行处理。
|
||||
pub fn check_return(
|
||||
&self,
|
||||
store: &ObjectStore,
|
||||
@@ -222,18 +277,22 @@ impl CallSignature {
|
||||
}
|
||||
}
|
||||
|
||||
/// 宿主实现接口;VM 克隆分派器 Arc 后调用 invoke,不持有载荷借用跨可变 Store 操作。
|
||||
pub trait NativeCallable: fmt::Debug + Send + Sync + std::any::Any {
|
||||
/// Immutable resolved types; referenced definitions must be traced by the
|
||||
/// payload. ContextNativeFunction snapshots and traces them automatically.
|
||||
/// ContextNativeFunction::new 在构造时调用并保存快照;查询不得经内部可变性悄悄改变已发布的类型引用。
|
||||
fn signature(&self) -> Option<CallSignature> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Optional positional contract. Missing metadata remains an explicit legacy gap.
|
||||
/// VM/NativeContext 查询数量;默认从 signature 推出区间,覆盖时需与已发布完整签名一致。
|
||||
fn arity(&self) -> Option<CallArity> {
|
||||
self.signature().map(|signature| signature.arity)
|
||||
}
|
||||
|
||||
/// 宿主在临时根作用域中执行;参数借用,结果可直接返回或委托 VM 调用其他函数。
|
||||
fn invoke(
|
||||
&self,
|
||||
context: &mut NativeContext<'_>,
|
||||
@@ -245,8 +304,11 @@ pub trait NativeCallable: fmt::Debug + Send + Sync + std::any::Any {
|
||||
/// are temporary owned roots. Return/Invoke promotes only its output roots;
|
||||
/// Drop releases everything else, including on error or unwinding.
|
||||
pub struct NativeContext<'a> {
|
||||
// 整次回调独占借用存储,提供受控修改与查询。
|
||||
store: &'a mut ObjectStore,
|
||||
// 本作用域拥有的根清单,重复身份代表多份拥有根,不能去重。
|
||||
roots: Vec<ObjectRef>,
|
||||
// 当前调用函数的借用身份,execute 另外将其加入保护根。
|
||||
callee: ObjectRef,
|
||||
}
|
||||
|
||||
@@ -256,6 +318,7 @@ impl<'a> NativeContext<'a> {
|
||||
self.callee
|
||||
}
|
||||
|
||||
/// 宿主经 Store 附着不可替换的名义类型,沿用只读屏障。
|
||||
pub fn attach_type(
|
||||
&mut self,
|
||||
object: ObjectRef,
|
||||
@@ -264,6 +327,7 @@ impl<'a> NativeContext<'a> {
|
||||
self.store.attach_type(object, type_object)
|
||||
}
|
||||
|
||||
/// 宿主注册能力,经 Store 校验,不直接改类型表。
|
||||
pub fn implement_trait(
|
||||
&mut self,
|
||||
definition: ObjectRef,
|
||||
@@ -272,27 +336,34 @@ impl<'a> NativeContext<'a> {
|
||||
self.store.implement_trait(definition, capability)
|
||||
}
|
||||
|
||||
/// 宿主取得 &ObjectStore 查询视图,修改使用上下文受控入口。
|
||||
/// 这里只限制 Rust 借用权限,不自行开启 Heap 只读作用域;是否处于只读执行由调用入口决定。
|
||||
pub fn store(&self) -> &ObjectStore {
|
||||
self.store
|
||||
}
|
||||
|
||||
/// 分配载荷并登记临时拥有根;未作为输出移交时由 Drop 释放。
|
||||
pub fn allocate(&mut self, payload: impl ObjectPayload) -> Result<ObjectRef, VmError> {
|
||||
// 只有分配/升级成功后才登记根,错误路径不留下虚假的释放条目。
|
||||
let object = self.store.allocate(payload)?;
|
||||
self.roots.push(object);
|
||||
Ok(object)
|
||||
}
|
||||
|
||||
/// 创建 Record,经 allocate 统一登记临时根。
|
||||
pub fn new_object(&mut self) -> Result<ObjectRef, VmError> {
|
||||
self.allocate(crate::Record)
|
||||
}
|
||||
|
||||
/// Build a language function under this native scope using validated code.
|
||||
/// 从代码创建无默认值/无捕获槽的源码函数,并登记临时根。
|
||||
pub fn new_function(
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
code: ObjectRef,
|
||||
globals: ObjectRef,
|
||||
) -> Result<ObjectRef, VmError> {
|
||||
// Store 校验代码布局和命名空间;这里不自动创建默认值或捕获槽。
|
||||
let function = self
|
||||
.store
|
||||
.new_function(name, code, globals, vec![], vec![])?;
|
||||
@@ -300,18 +371,23 @@ impl<'a> NativeContext<'a> {
|
||||
Ok(function)
|
||||
}
|
||||
|
||||
/// 创建空字典并登记上下文拥有的临时根;借出的身份不由回调手工 release。
|
||||
pub fn new_dict(&mut self) -> Result<ObjectRef, VmError> {
|
||||
// 只有分配/升级成功后才登记根,错误路径不留下虚假的释放条目。
|
||||
let object = self.store.new_dict()?;
|
||||
self.roots.push(object);
|
||||
Ok(object)
|
||||
}
|
||||
|
||||
/// 创建空/已初始化 Cell 并登记上下文临时根;若作为输出交付,execute 另保留一份输出根。
|
||||
pub fn new_cell(&mut self, value: Option<Value>) -> Result<ObjectRef, VmError> {
|
||||
// 只有分配/升级成功后才登记根,错误路径不留下虚假的释放条目。
|
||||
let object = self.store.new_cell(value)?;
|
||||
self.roots.push(object);
|
||||
Ok(object)
|
||||
}
|
||||
|
||||
/// 属性写入交给 Store/Heap 维护强边、类型约束和写屏障。
|
||||
pub fn set_attr(
|
||||
&mut self,
|
||||
object: ObjectRef,
|
||||
@@ -321,22 +397,27 @@ impl<'a> NativeContext<'a> {
|
||||
self.store.set_attr(object, name, value)
|
||||
}
|
||||
|
||||
/// 属性删除交给 Store 校验,成功释放旧字段强边。
|
||||
pub fn del_attr(&mut self, object: ObjectRef, name: &str) -> Result<(), VmError> {
|
||||
self.store.del_attr(object, name)
|
||||
}
|
||||
|
||||
/// 受控字典写入,保持容器身份和引用计数。
|
||||
pub fn dict_set(&mut self, object: ObjectRef, key: Value, value: Value) -> Result<(), VmError> {
|
||||
self.store.dict_set(object, key, value)
|
||||
}
|
||||
|
||||
/// 受控删除字典键;缺键返回 false,协议失败另行报错。
|
||||
pub fn dict_delete(&mut self, object: ObjectRef, key: &Value) -> Result<bool, VmError> {
|
||||
self.store.dict_delete(object, key)
|
||||
}
|
||||
|
||||
/// 受控更新共享 Cell,先保留新边再释放旧边。
|
||||
pub fn cell_set(&mut self, object: ObjectRef, value: Value) -> Result<(), VmError> {
|
||||
self.store.cell_set(object, value)
|
||||
}
|
||||
|
||||
/// 替换载荷并重建边快照,保持对象身份。
|
||||
pub fn replace_payload(
|
||||
&mut self,
|
||||
object: ObjectRef,
|
||||
@@ -345,15 +426,20 @@ impl<'a> NativeContext<'a> {
|
||||
self.store.replace_payload(object, payload)
|
||||
}
|
||||
|
||||
/// 升级成功的弱引用根登记为临时根;过期 None 不登记。
|
||||
pub fn upgrade(&mut self, weak: WeakObjectRef) -> Result<Option<ObjectRef>, VmError> {
|
||||
// 只有分配/升级成功后才登记根,错误路径不留下虚假的释放条目。
|
||||
let object = self.store.upgrade(weak)?;
|
||||
// 已过期弱引用不产生根,成功升级的一份根交给 Drop 管理。
|
||||
if let Some(object) = object {
|
||||
self.roots.push(object);
|
||||
}
|
||||
Ok(object)
|
||||
}
|
||||
|
||||
/// 给借用对象 retain 一份临时根;直接值不需要托管计数。
|
||||
fn protect(&mut self, value: &Value) -> Result<(), VmError> {
|
||||
// retain 先成功才入清单,避免失败时 Drop 多释放一份根。
|
||||
if let Value::Object(object) = value {
|
||||
self.store.retain(*object)?;
|
||||
self.roots.push(*object);
|
||||
@@ -363,58 +449,81 @@ impl<'a> NativeContext<'a> {
|
||||
|
||||
/// Internal execution bridge. Every returned object reference owns a root;
|
||||
/// consumers must release Return values or all Invoke arguments/function.
|
||||
/// VM 的 invoke_native 调用此桥:校验/保护输入,执行回调,验证直接结果,最后移交输出根。
|
||||
/// VM 传入从 function 载荷克隆的分派器;手工调用需自行保证二者对应,本方法不验证 Arc 身份。
|
||||
/// Invoke 的最终返回约束由 VM 的 prepare_call/源码帧继续维护,不能把此结果当作最终调用完成。
|
||||
/// 此桥不推进源码委托,也不维护执行预算或回溯;普通覆盖检查选项由 VM 上层传递处理。
|
||||
pub fn execute(
|
||||
// 整次回调独占借用存储,提供受控修改与查询。
|
||||
store: &'a mut ObjectStore,
|
||||
function: ObjectRef,
|
||||
arguments: &[Value],
|
||||
// 可共享回调分派器;执行前克隆 Arc,避免持有载荷借用。
|
||||
callable: Arc<dyn NativeCallable>,
|
||||
) -> Result<NativeResult, VmError> {
|
||||
// 执行回调前检查分派器声明的数量约束。
|
||||
if let Some(arity) = callable.arity() {
|
||||
arity.check(
|
||||
store.object(function)?.payload().kind_name().into(),
|
||||
arguments.len(),
|
||||
)?;
|
||||
}
|
||||
// 从载荷快照取得完整签名,与 callable 的动态返回值分开。
|
||||
let signature = store.native_signature(function)?;
|
||||
// 诊断名在可变借用上下文建立前复制,避免跨调用持有载荷引用。
|
||||
let name = store.object(function)?.payload().kind_name().to_owned();
|
||||
// 可选完整签名存在时才校验/保护;缺失元数据保留既有调用协议。
|
||||
if let Some(signature) = &signature {
|
||||
signature.check_arguments(store, &name, arguments)?;
|
||||
}
|
||||
// 首参为对象时查询接收者的方法契约及应保持的返回约束。
|
||||
let trait_returns = if let Some(Value::Object(receiver)) = arguments.first() {
|
||||
store.check_method_contract_call(function, *receiver, false)?;
|
||||
store.method_trait_returns(function, *receiver)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
// 从这里起所有已登记根由 RAII Drop 清理,错误和展开路径同样有效。
|
||||
let mut context = Self {
|
||||
store,
|
||||
roots: Vec::new(),
|
||||
callee: function,
|
||||
};
|
||||
context.protect(&Value::Object(function))?;
|
||||
// 可选完整签名存在时才校验/保护;缺失元数据保留既有调用协议。
|
||||
if let Some(signature) = &signature {
|
||||
// 逐项保护签名持有的类型/能力定义,使回调执行期间不依赖宿主另外保留根。
|
||||
for reference in signature.references() {
|
||||
context.protect(&Value::Object(reference))?;
|
||||
}
|
||||
}
|
||||
// 能力返回约束也可能持有类型定义,执行回调时必须保持其存活。
|
||||
for (_, expected) in &trait_returns {
|
||||
// 每条声明引用独立保护,重复边对应多份根并会逐次释放。
|
||||
for reference in expected.references() {
|
||||
context.protect(&Value::Object(reference))?;
|
||||
}
|
||||
}
|
||||
// 输入每个对象参数取得临时根,防回调删除原持有边后参数失效。
|
||||
for argument in arguments {
|
||||
context.protect(argument)?;
|
||||
}
|
||||
// 回调错误直接传播,context 自动释放输入保护和回调内创建的临时对象根。
|
||||
let result = callable.invoke(&mut context, arguments)?;
|
||||
// 直接结果立即检查;Invoke 的最终结果由 VM 的委托返回检查继续处理。
|
||||
if let (Some(signature), NativeResult::Return(value)) = (&signature, &result) {
|
||||
signature.check_return(context.store(), &name, value)?;
|
||||
}
|
||||
// 直接结果还须满足接收者能力方法要求,不只满足原生自身签名。
|
||||
if let NativeResult::Return(value) = &result {
|
||||
// 每个能力的返回约束都要满足,不能在第一个成功后跳过其他能力。
|
||||
for (name, expected) in &trait_returns {
|
||||
expected.check(context.store(), name, "return".into(), value)?;
|
||||
}
|
||||
}
|
||||
// 记录移交边界;新增输出根验证成功后才从清单中移走。
|
||||
let transferred_from = context.roots.len();
|
||||
// 给每个输出位置 retain;中途失败则清单仍完整,由 Drop 回收已取得的输出根。
|
||||
match &result {
|
||||
NativeResult::Return(value) => context.protect(value)?,
|
||||
NativeResult::Invoke {
|
||||
@@ -422,19 +531,22 @@ impl<'a> NativeContext<'a> {
|
||||
arguments,
|
||||
} => {
|
||||
context.protect(&Value::Object(*function))?;
|
||||
// 每个委托参数取得待移交的输出根,重复对象参数也逐项保留。
|
||||
for argument in arguments {
|
||||
context.protect(argument)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Transfer exactly the output roots; all other roots remain scoped.
|
||||
// truncate 只移走输出根的清单条目,不 release;调用者接管这些拥有根,其他根仍由 Drop 释放。
|
||||
context.roots.truncate(transferred_from);
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for NativeContext<'_> {
|
||||
/// 作用域结束、回调错误或栈展开时回收全部未移交拥有根。
|
||||
fn drop(&mut self) {
|
||||
// 按取得的反序归还所有未移交根;每项代表一份所有权,不能按身份去重。
|
||||
for root in self.roots.drain(..).rev() {
|
||||
self.store
|
||||
.release(root)
|
||||
@@ -446,9 +558,13 @@ impl Drop for NativeContext<'_> {
|
||||
/// A context-aware function with explicit, immutable captured object edges.
|
||||
#[derive(Debug)]
|
||||
pub struct ContextNativeFunction {
|
||||
// 宿主静态诊断名,不承担语言类型身份。
|
||||
name: &'static str,
|
||||
// 可共享回调分派器;执行前克隆 Arc,避免持有载荷借用。
|
||||
callable: Arc<dyn NativeCallable>,
|
||||
// 显式不可变托管捕获边,由 trace 逐条登记。
|
||||
captures: Vec<ObjectRef>,
|
||||
// 构造时从 callable 快照,作为不可变元数据并参与 trace。
|
||||
signature: Option<CallSignature>,
|
||||
}
|
||||
|
||||
@@ -456,14 +572,20 @@ impl ContextNativeFunction {
|
||||
/// Borrow the concrete host implementation for intrinsic dispatch checks.
|
||||
/// Function identity or a mutable class-table slot alone is not proof that
|
||||
/// its current payload still implements a host intrinsic.
|
||||
/// 宿主内建分派借用具体回调实现;类型不匹配返回 None,不以函数身份猜测实现。
|
||||
pub fn implementation<T: NativeCallable>(&self) -> Option<&T> {
|
||||
// 仅做 Rust Any 下转型,不执行回调,不检查语言层名义类型。
|
||||
let implementation: &dyn std::any::Any = self.callable.as_ref();
|
||||
implementation.downcast_ref()
|
||||
}
|
||||
|
||||
/// 宿主包装上下文回调,快照签名与显式捕获边,随后由 Store 分配。
|
||||
/// 构造本身不 retain 或校验托管句柄;allocate 时登记边,Store::native_signature/调用时再验证签名结构。
|
||||
pub fn new(
|
||||
// 宿主静态诊断名,不承担语言类型身份。
|
||||
name: &'static str,
|
||||
callable: impl NativeCallable,
|
||||
// 显式不可变托管捕获边,由 trace 逐条登记。
|
||||
captures: Vec<ObjectRef>,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -476,31 +598,41 @@ impl ContextNativeFunction {
|
||||
}
|
||||
|
||||
impl ObjectPayload for ContextNativeFunction {
|
||||
// Store 分配时可附着 Function 类型;可调用性和函数分类分开。
|
||||
fn is_function(&self) -> bool {
|
||||
true
|
||||
}
|
||||
// 本载荷无原地赋值实现,只读调用可以直接重绑定局部引用。
|
||||
fn reference_assignment_only(&self) -> bool {
|
||||
true
|
||||
}
|
||||
// Store/宿主 intrinsic 分派所需的载荷下转型入口。
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
// 使用宿主配置的静态名生成诊断。
|
||||
fn kind_name(&self) -> &'static str {
|
||||
self.name
|
||||
}
|
||||
// Heap 快照签名定义边及显式捕获边;不能在分配后私下修改这些引用。
|
||||
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
|
||||
// 签名中的参数、可变参数和返回约束定义也属于载荷拥有边。
|
||||
if let Some(signature) = &self.signature {
|
||||
// 按签名实际引用报告每条拥有边,包括重复出现的定义,不因某个联合候选已匹配而省略。
|
||||
for reference in signature.references() {
|
||||
visit(reference);
|
||||
}
|
||||
}
|
||||
// 每个捕获项都拥有独立强边,包括重复捕获。
|
||||
for capture in &self.captures {
|
||||
visit(*capture);
|
||||
}
|
||||
}
|
||||
// Store 查询构造时的签名快照,clone 不新增托管根。
|
||||
fn call_signature(&self) -> Option<CallSignature> {
|
||||
self.signature.clone()
|
||||
}
|
||||
// VM 克隆回调 Arc 后释放载荷借用,再创建可变 NativeContext。
|
||||
fn native_callable(&self) -> Option<Arc<dyn NativeCallable>> {
|
||||
Some(self.callable.clone())
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 编译器字面量、VM 算术比较与宿主数值转换共用的表示;构造保存与运算预算分开。
|
||||
//! Eq 比较存储表示,numeric_compare 比较数学值;Rust 数值载荷不持有托管对象边。
|
||||
//! Lossless number representations. Arithmetic and numeric equivalence are
|
||||
//! deliberately separate from representation equality; real operations preserve
|
||||
//! exact values unless a binary-float operand explicitly selects float mode.
|
||||
@@ -8,18 +10,25 @@ use std::fmt;
|
||||
use crate::{CompareOp, ObjectPayload, ObjectRef, VmError};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// 编译器字面量和精确运算的规范整数;仅保存符号和数字,构造不执行算术预算。
|
||||
pub struct EndorInteger {
|
||||
/// 规范化整数符号,零为 false。
|
||||
negative: bool,
|
||||
/// 无符号、无前导零的数字串;零保留单个 0。
|
||||
digits: String,
|
||||
}
|
||||
|
||||
impl EndorInteger {
|
||||
/// Decimal digits of any length, limited only by available memory.
|
||||
/// 编译器/宿主解析字面量,构造可保留长数字,实际算术再执行预算检查。
|
||||
pub fn parse(text: &str) -> Result<Self, VmError> {
|
||||
// 拆符号后校验数字,空串或只有符号均非法。
|
||||
let (negative, digits) = split_sign(text);
|
||||
// 只允许非空 ASCII 数字,不接受空白、下划线或进制前缀。
|
||||
if !is_digits(digits) {
|
||||
return Err(VmError::InvalidArgument("invalid integer"));
|
||||
}
|
||||
// 去前导零,全零统一保存单个正零。
|
||||
let digits = digits.trim_start_matches('0');
|
||||
Ok(Self {
|
||||
negative: negative && !digits.is_empty(),
|
||||
@@ -27,21 +36,28 @@ impl EndorInteger {
|
||||
})
|
||||
}
|
||||
|
||||
/// 宿主/转换借用规范化整数绝对值数字串。
|
||||
pub fn digits(&self) -> &str {
|
||||
&self.digits
|
||||
}
|
||||
|
||||
/// 查询整数符号,构造已消除负零。
|
||||
pub fn is_negative(&self) -> bool {
|
||||
self.negative
|
||||
}
|
||||
|
||||
/// 数值真值/分母检查使用的零判断,具体实现按表示处理。
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.digits == "0"
|
||||
}
|
||||
|
||||
/// 算术快路径尝试收窄整数,超范围返回 None,不截断。
|
||||
pub fn to_i64(&self) -> Option<i64> {
|
||||
// 先读 u64 幅值以容纳 i64::MIN 的绝对值,再检查有符号范围。
|
||||
let magnitude = self.digits.parse::<u64>().ok()?;
|
||||
// 根据符号选择有符号范围检查,负数还需容纳 i64::MIN 的额外一单位幅值。
|
||||
if self.negative {
|
||||
// 最小负数的幅值比最大正数多一,必须单独处理。
|
||||
if magnitude == (i64::MAX as u64) + 1 {
|
||||
Some(i64::MIN)
|
||||
} else {
|
||||
@@ -54,7 +70,9 @@ impl EndorInteger {
|
||||
}
|
||||
|
||||
impl fmt::Display for EndorInteger {
|
||||
/// Display/宿主诊断按当前表示输出,精确表示不经二进制浮点中转。
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// 仅负值输出前导减号,数字幅值随后原样写出,不通过浮点格式化。
|
||||
if self.negative {
|
||||
f.write_str("-")?;
|
||||
}
|
||||
@@ -63,27 +81,40 @@ impl fmt::Display for EndorInteger {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// 编译器保留原拼写的十进制值;表示相等会区分 1.0 与 1.00,数学比较另走 numeric_compare。
|
||||
pub struct EndorDecimal {
|
||||
/// 已验证但未规范化的原十进制字面量。
|
||||
literal: String,
|
||||
/// 缓存尾数是否全零。
|
||||
zero: bool,
|
||||
}
|
||||
|
||||
impl EndorDecimal {
|
||||
/// Retains all digits, trailing zeroes, sign, and an arbitrarily long
|
||||
/// decimal exponent. No intermediate binary floating-point conversion.
|
||||
/// 编译器/宿主解析字面量,构造可保留长数字,实际算术再执行预算检查。
|
||||
pub fn parse(text: &str) -> Result<Self, VmError> {
|
||||
// 校验时去符号,但最终仍保存完整 text,不丢失显式正号和负零。
|
||||
let (_, unsigned) = split_sign(text);
|
||||
// 指数最多一段,可带符号;解析不立刻收窄指数到机器整数。
|
||||
let mut parts = unsigned.split(['e', 'E']);
|
||||
// 尾数后续允许一个点和至少一个数字。
|
||||
let mantissa = parts.next().unwrap_or_default();
|
||||
// 指数必须有数字且不得有第二个指数段。
|
||||
if let Some(exponent) = parts.next()
|
||||
&& (!is_digits(split_sign(exponent).1) || parts.next().is_some())
|
||||
{
|
||||
return Err(VmError::InvalidArgument("invalid decimal exponent"));
|
||||
}
|
||||
// 分别记录已有小数点、数字数量与尾数是否全零。
|
||||
let mut decimal_point = false;
|
||||
// 统计尾数数字个数,允许前导零,但至少要有一个数字。
|
||||
let mut digits = 0_usize;
|
||||
// 初始假设尾数全零,每读到一个非零数字便保持为 false,用于零值语义。
|
||||
let mut zero = true;
|
||||
// 按 ASCII 字节扫描,第二个点或其他字符都拒绝。
|
||||
for byte in mantissa.bytes() {
|
||||
// 数字更新计数及全零状态,首个点只登记位置,第二个点或其他字节明确失败。
|
||||
match byte {
|
||||
b'.' if !decimal_point => decimal_point = true,
|
||||
b'0'..=b'9' => {
|
||||
@@ -93,6 +124,7 @@ impl EndorDecimal {
|
||||
_ => return Err(VmError::InvalidArgument("invalid decimal")),
|
||||
}
|
||||
}
|
||||
// 允许 .5 和 5.,但不允许只有点或空尾数。
|
||||
if digits == 0 {
|
||||
return Err(VmError::InvalidArgument("decimal needs digits"));
|
||||
}
|
||||
@@ -102,16 +134,19 @@ impl EndorDecimal {
|
||||
})
|
||||
}
|
||||
|
||||
/// 显示和精确转换借用原字面量,保留符号、指数及尾零。
|
||||
pub fn literal(&self) -> &str {
|
||||
&self.literal
|
||||
}
|
||||
|
||||
/// 数值真值/分母检查使用的零判断,具体实现按表示处理。
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.zero
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for EndorDecimal {
|
||||
/// Display/宿主诊断按当前表示输出,精确表示不经二进制浮点中转。
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(&self.literal)
|
||||
}
|
||||
@@ -119,21 +154,27 @@ impl fmt::Display for EndorDecimal {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EndorRational {
|
||||
/// 带符号分子,不要求已约分。
|
||||
numerator: EndorInteger,
|
||||
/// 构造保证正且非零的分母。
|
||||
denominator: EndorInteger,
|
||||
}
|
||||
|
||||
impl EndorRational {
|
||||
/// Normalizes denominator sign, without reducing or rounding the fraction.
|
||||
/// EndorNumber::rational 把已解析的整数交到这里;只校正分母符号,不执行约分或运算预算。
|
||||
pub fn new(
|
||||
mut numerator: EndorInteger,
|
||||
mut denominator: EndorInteger,
|
||||
) -> Result<Self, VmError> {
|
||||
// 构造时拒绝零分母,后续零判断只需看分子。
|
||||
if denominator.is_zero() {
|
||||
return Err(VmError::InvalidArgument("zero rational denominator"));
|
||||
}
|
||||
// 分母归正,同时翻分子符号;不执行约分。
|
||||
if denominator.negative {
|
||||
denominator.negative = false;
|
||||
// 零分子保持正零,不引入负零。
|
||||
if !numerator.is_zero() {
|
||||
numerator.negative = !numerator.negative;
|
||||
}
|
||||
@@ -144,16 +185,19 @@ impl EndorRational {
|
||||
})
|
||||
}
|
||||
|
||||
/// 显示与精确转换借用分子表示。
|
||||
pub fn numerator(&self) -> &EndorInteger {
|
||||
&self.numerator
|
||||
}
|
||||
|
||||
/// 显示与精确转换借用正且非零的分母表示。
|
||||
pub fn denominator(&self) -> &EndorInteger {
|
||||
&self.denominator
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for EndorRational {
|
||||
/// Display/宿主诊断按当前表示输出,精确表示不经二进制浮点中转。
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}/{}", self.numerator, self.denominator)
|
||||
}
|
||||
@@ -162,15 +206,20 @@ impl fmt::Display for EndorRational {
|
||||
/// Internal storage forms within one Endor number API, not language classes.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EndorReal {
|
||||
/// 已规范符号和前导零的整数。
|
||||
Integer(EndorInteger),
|
||||
/// 保留符号、尾零和指数原拼写的十进制值。
|
||||
Decimal(EndorDecimal),
|
||||
/// 正分母的精确分数;构造阶段不保证已约分。
|
||||
Rational(EndorRational),
|
||||
/// IEEE-754 bits preserve signed zero, infinities and NaN payloads.
|
||||
BinaryFloat(u64),
|
||||
}
|
||||
|
||||
impl EndorReal {
|
||||
/// 数值真值/分母检查使用的零判断,具体实现按表示处理。
|
||||
pub fn is_zero(&self) -> bool {
|
||||
// 按实际实数表示判断零;分数看分子,二进制浮点按其原有 f64 零语义判断。
|
||||
match self {
|
||||
Self::Integer(value) => value.is_zero(),
|
||||
Self::Decimal(value) => value.is_zero(),
|
||||
@@ -181,7 +230,9 @@ impl EndorReal {
|
||||
}
|
||||
|
||||
impl fmt::Display for EndorReal {
|
||||
/// Display/宿主诊断按当前表示输出,精确表示不经二进制浮点中转。
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// 各精确表示委托自己的 Display;BinaryFloat 才按存储的浮点位模式显示。
|
||||
match self {
|
||||
Self::Integer(value) => value.fmt(f),
|
||||
Self::Decimal(value) => value.fmt(f),
|
||||
@@ -196,22 +247,29 @@ impl fmt::Display for EndorReal {
|
||||
/// Language-level comparison and hashing are not defined by these Rust traits.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EndorNumber {
|
||||
/// VM 算术、比较和图形收窄接受的实数表示。
|
||||
Real(EndorReal),
|
||||
/// 可保存、显示并判断真值;当前算术与数学比较均拒绝此变体。
|
||||
Complex {
|
||||
/// 复数实部分量。
|
||||
real: EndorReal,
|
||||
/// 复数虚部分量。
|
||||
imaginary: EndorReal,
|
||||
},
|
||||
}
|
||||
|
||||
impl EndorNumber {
|
||||
/// 编译器/宿主构造整数实数表示,解析错误原样传播。
|
||||
pub fn integer(text: &str) -> Result<Self, VmError> {
|
||||
Ok(Self::Real(EndorReal::Integer(EndorInteger::parse(text)?)))
|
||||
}
|
||||
|
||||
/// 编译器/宿主构造保留字面量的十进制实数。
|
||||
pub fn decimal(text: &str) -> Result<Self, VmError> {
|
||||
Ok(Self::Real(EndorReal::Decimal(EndorDecimal::parse(text)?)))
|
||||
}
|
||||
|
||||
/// 宿主/运算结果从两段整数文本建立有理数,拒绝零分母。
|
||||
pub fn rational(numerator: &str, denominator: &str) -> Result<Self, VmError> {
|
||||
Ok(Self::Real(EndorReal::Rational(EndorRational::new(
|
||||
EndorInteger::parse(numerator)?,
|
||||
@@ -219,29 +277,37 @@ impl EndorNumber {
|
||||
)?)))
|
||||
}
|
||||
|
||||
/// 宿主显式选择二进制浮点,保存全部 IEEE 位。
|
||||
pub fn binary_float(value: f64) -> Self {
|
||||
Self::Real(EndorReal::BinaryFloat(value.to_bits()))
|
||||
}
|
||||
|
||||
/// 组合两分量,不因虚部为零自动退化为实数;当前不支持复数算术。
|
||||
pub fn complex(real: EndorReal, imaginary: EndorReal) -> Self {
|
||||
Self::Complex { real, imaginary }
|
||||
}
|
||||
|
||||
/// VM 算术/转换查询实数,Complex 即使虚部为零也返回 None。
|
||||
pub fn as_real(&self) -> Option<&EndorReal> {
|
||||
// 只有 Real 能借用实数部分;Complex 即使虚部为零也不在此自动降级。
|
||||
match self {
|
||||
Self::Real(value) => Some(value),
|
||||
Self::Complex { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// VM 快路径仅接受整数变体,不自动转小数或可整除分数。
|
||||
pub fn as_i64(&self) -> Option<i64> {
|
||||
// 仅整数实数尝试收窄为 i64,小数、分数、浮点和复数均不隐式转换。
|
||||
match self {
|
||||
Self::Real(EndorReal::Integer(value)) => value.to_i64(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 数值真值/分母检查使用的零判断,具体实现按表示处理。
|
||||
pub fn is_zero(&self) -> bool {
|
||||
// 实数委托自身零判断;复数要求实部和虚部都为零。
|
||||
match self {
|
||||
Self::Real(value) => value.is_zero(),
|
||||
Self::Complex { real, imaginary } => real.is_zero() && imaginary.is_zero(),
|
||||
@@ -250,19 +316,23 @@ impl EndorNumber {
|
||||
}
|
||||
|
||||
impl From<i64> for EndorNumber {
|
||||
/// Rust 宿主转换入口,按输入类型选择整数文本或显式二进制浮点表示。
|
||||
fn from(value: i64) -> Self {
|
||||
Self::integer(&value.to_string()).expect("i64 always has valid decimal digits")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for EndorNumber {
|
||||
/// Rust 宿主转换入口,按输入类型选择整数文本或显式二进制浮点表示。
|
||||
fn from(value: f64) -> Self {
|
||||
Self::binary_float(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for EndorNumber {
|
||||
/// Display/宿主诊断按当前表示输出,精确表示不经二进制浮点中转。
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// 实数保持原表示,复数分别显示两个实数部分和显式虚数单位。
|
||||
match self {
|
||||
Self::Real(value) => value.fmt(f),
|
||||
Self::Complex { real, imaginary } => write!(f, "({real} + ({imaginary})i)"),
|
||||
@@ -271,46 +341,59 @@ impl fmt::Display for EndorNumber {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// Store::new_number 使用的堆载荷;与直接 Value::Number 共用数值语义,但需遵守托管句柄所有权。
|
||||
pub struct EndorNumberObject {
|
||||
/// 拥有数值表示,不含托管对象强边。
|
||||
value: EndorNumber,
|
||||
}
|
||||
|
||||
impl EndorNumberObject {
|
||||
/// Store::new_number 在托管分配前包装数值;本方法不分配堆槽,也不新增外部根。
|
||||
pub fn new(value: EndorNumber) -> Self {
|
||||
Self { value }
|
||||
}
|
||||
|
||||
/// VM/宿主借用堆数值,不增加根或复制内容。
|
||||
pub fn value(&self) -> &EndorNumber {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectPayload for EndorNumberObject {
|
||||
/// 数值载荷不可变,不实现旧式原地赋值,允许只读局部重绑定。
|
||||
fn reference_assignment_only(&self) -> bool {
|
||||
true
|
||||
}
|
||||
/// Store 的 Rust 载荷下转型入口。
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
/// Store 错误与诊断使用的载荷类别,不是名义类型。
|
||||
fn kind_name(&self) -> &'static str {
|
||||
"number"
|
||||
}
|
||||
|
||||
/// Store 显示数值,不打印对象地址。
|
||||
fn repr(&self, _identity: ObjectRef) -> Result<String, VmError> {
|
||||
Ok(self.value.to_string())
|
||||
}
|
||||
|
||||
/// Store 真值协议按非零判断,复数要求两分量都为零才为假。
|
||||
fn is_true(&self) -> Result<bool, VmError> {
|
||||
Ok(!self.value.is_zero())
|
||||
}
|
||||
|
||||
/// 统一数值哈希尚未定义,拒绝以免数学相等而表示不同的键哈希不兼容。
|
||||
fn hash(&self, _identity: ObjectRef) -> Result<u64, VmError> {
|
||||
Err(VmError::UnsupportedOperation("numeric hash"))
|
||||
}
|
||||
|
||||
/// Store 对象比较协议,只在对方也是数值载荷时处理,否则返回 None。
|
||||
fn compare(&self, other: &dyn ObjectPayload, op: CompareOp) -> Result<Option<bool>, VmError> {
|
||||
// 异类载荷不强行比较,交给 Store 的反向协议或身份回退。
|
||||
if let Some(other) = other.as_any().downcast_ref::<Self>() {
|
||||
// 复数目前连相等比较也不支持,明确报协议错误。
|
||||
if self.value.as_real().is_none() || other.value.as_real().is_none() {
|
||||
return Err(VmError::UnsupportedOperation("numeric comparison"));
|
||||
}
|
||||
@@ -320,7 +403,9 @@ impl ObjectPayload for EndorNumberObject {
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析共用单个符号拆分,不去空白或重复符号。
|
||||
fn split_sign(text: &str) -> (bool, &str) {
|
||||
// 只剥离一个前导符号,剩余文本交给数字语法校验。
|
||||
if let Some(rest) = text.strip_prefix('-') {
|
||||
(true, rest)
|
||||
} else {
|
||||
@@ -328,6 +413,7 @@ fn split_sign(text: &str) -> (bool, &str) {
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析共用非空 ASCII 数字检查,不接受 Unicode 数字。
|
||||
fn is_digits(text: &str) -> bool {
|
||||
!text.is_empty() && text.bytes().all(|byte| byte.is_ascii_digit())
|
||||
}
|
||||
@@ -343,7 +429,9 @@ pub enum ArithmeticOp {
|
||||
Divide,
|
||||
}
|
||||
impl ArithmeticOp {
|
||||
/// eval 解 BinaryArithmetic 的 0..3 运算编号,其他值返回 None。
|
||||
pub fn from_argument(value: u32) -> Option<Self> {
|
||||
// 只接受字节码定义的四个算术编号,其他值返回 None 交调用方拒绝。
|
||||
match value {
|
||||
0 => Some(Self::Add),
|
||||
1 => Some(Self::Subtract),
|
||||
@@ -354,7 +442,9 @@ impl ArithmeticOp {
|
||||
}
|
||||
}
|
||||
impl EndorNumber {
|
||||
/// eval 一元负号:浮点翻符号位,其余走零减当前值,因此仍受运算预算限制。
|
||||
pub fn negated(&self) -> Result<Self, VmError> {
|
||||
// 翻转符号位保留负零和 NaN payload,不经算术规范化。
|
||||
if let Some(EndorReal::BinaryFloat(bits)) = self.as_real() {
|
||||
return Ok(Self::Real(EndorReal::BinaryFloat(bits ^ (1_u64 << 63))));
|
||||
}
|
||||
@@ -362,11 +452,15 @@ impl EndorNumber {
|
||||
}
|
||||
/// Explicit narrowing for graphics/FFI boundaries; exact values themselves
|
||||
/// retain their representation. Non-real or non-finite values are rejected.
|
||||
/// Tessera 图形桥在宿主边界显式收窄,结果可能舍入,不承诺精确往返;拒绝复数、非有限值和超范围结果。
|
||||
/// 精确侧转换也受 exact_real 的文本/指数预算限制,能保存的字面量不一定能在此转换。
|
||||
pub fn to_f64(&self) -> Result<f64, VmError> {
|
||||
use num_traits::ToPrimitive;
|
||||
// 先要求 Real,不能静默丢弃复数虚部。
|
||||
let real = self
|
||||
.as_real()
|
||||
.ok_or(VmError::UnsupportedOperation("complex to float"))?;
|
||||
// 浮点还原原位,精确表示先转 BigRational 再收窄。
|
||||
let value = if let EndorReal::BinaryFloat(bits) = real {
|
||||
f64::from_bits(*bits)
|
||||
} else {
|
||||
@@ -374,13 +468,18 @@ impl EndorNumber {
|
||||
"number exceeds binary float range",
|
||||
))?
|
||||
};
|
||||
// 图形/FFI 边界不接受 NaN 或无穷。
|
||||
if !value.is_finite() {
|
||||
return Err(VmError::InvalidArgument("non-finite float conversion"));
|
||||
}
|
||||
Ok(value)
|
||||
}
|
||||
/// eval 的 BinaryArithmetic 使用:i64 安全快路径,任一浮点选择 float,否则精确有理数运算。
|
||||
/// 精确运算受输入预算和结果 16384 位上限限制;成功结果规范为 Integer 或约分 Rational。
|
||||
pub fn arithmetic(&self, other: &Self, op: ArithmeticOp) -> Result<Self, VmError> {
|
||||
// 双方为可容纳 i64 的 Integer 才用快路径;运算溢出回退,不截断。
|
||||
if let (Some(a), Some(b)) = (self.as_i64(), other.as_i64()) {
|
||||
// 加减乘检查溢出,除法仅整除且不溢出时保持整数快路径。
|
||||
let integer = match op {
|
||||
ArithmeticOp::Add => a.checked_add(b),
|
||||
ArithmeticOp::Subtract => a.checked_sub(b),
|
||||
@@ -390,11 +489,13 @@ impl EndorNumber {
|
||||
.filter(|&r| r == 0)
|
||||
.and_then(|_| a.checked_div(b)),
|
||||
};
|
||||
// 快路径成功后包装为规范化整数。
|
||||
if let Some(result) = integer {
|
||||
return Ok(Self::from(result));
|
||||
}
|
||||
}
|
||||
use num_traits::{ToPrimitive, Zero};
|
||||
// 先要求两个输入均为实数;后续按精确/浮点模式转换。
|
||||
let (a, b) = (
|
||||
self.as_real()
|
||||
.ok_or(VmError::UnsupportedOperation("complex arithmetic"))?,
|
||||
@@ -402,8 +503,11 @@ impl EndorNumber {
|
||||
.as_real()
|
||||
.ok_or(VmError::UnsupportedOperation("complex arithmetic"))?,
|
||||
);
|
||||
// 显式浮点参与才启用 float,精确十进制本身不自动选 float。
|
||||
if matches!(a, EndorReal::BinaryFloat(_)) || matches!(b, EndorReal::BinaryFloat(_)) {
|
||||
// 混合算术中精确侧可收窄,浮点侧还原原位。
|
||||
let convert = |n: &EndorReal| -> Result<f64, VmError> {
|
||||
// 已为 float 则还原,否则精确转换后检查能否收窄。
|
||||
if let EndorReal::BinaryFloat(bits) = n {
|
||||
Ok(f64::from_bits(*bits))
|
||||
} else {
|
||||
@@ -412,44 +516,56 @@ impl EndorNumber {
|
||||
))
|
||||
}
|
||||
};
|
||||
// 将已确认的两个实数转换为浮点,精确侧在这里可能因预算或范围失败。
|
||||
let (a, b) = (convert(a)?, convert(b)?);
|
||||
// 两种运算模式都拒绝零除,包括浮点正负零。
|
||||
if matches!(op, ArithmeticOp::Divide) && b == 0. {
|
||||
return Err(VmError::InvalidArgument("division by zero"));
|
||||
}
|
||||
// 执行指定运算;浮点后检查有限性,精确后检查结果预算。
|
||||
let result = match op {
|
||||
ArithmeticOp::Add => a + b,
|
||||
ArithmeticOp::Subtract => a - b,
|
||||
ArithmeticOp::Multiply => a * b,
|
||||
ArithmeticOp::Divide => a / b,
|
||||
};
|
||||
// 拒绝 NaN 传播或溢出为无穷,不返回非有限算术结果。
|
||||
if !result.is_finite() {
|
||||
return Err(VmError::InvalidArgument("non-finite arithmetic result"));
|
||||
}
|
||||
return Ok(Self::binary_float(result));
|
||||
}
|
||||
// 无浮点操作数时转为精确分数;转换先检查各表示的输入预算。
|
||||
let (a, b) = (exact_real(a)?, exact_real(b)?);
|
||||
// 精确除法同样拒绝零分母,零的不同精确表示已在转换后统一。
|
||||
if matches!(op, ArithmeticOp::Divide) && b.is_zero() {
|
||||
return Err(VmError::InvalidArgument("division by zero"));
|
||||
}
|
||||
// 执行指定运算;浮点后检查有限性,精确后检查结果预算。
|
||||
let result = match op {
|
||||
ArithmeticOp::Add => a + b,
|
||||
ArithmeticOp::Subtract => a - b,
|
||||
ArithmeticOp::Multiply => a * b,
|
||||
ArithmeticOp::Divide => a / b,
|
||||
};
|
||||
// 限制运算后分子/分母位数,构造保存上限与实际运算预算不同。
|
||||
if result.numer().bits() > 16_384 || result.denom().bits() > 16_384 {
|
||||
return Err(VmError::InvalidArgument(
|
||||
"arithmetic result exceeds operation budget",
|
||||
));
|
||||
}
|
||||
// 可整除输出 Integer,否则输出约分后 Rational,不伪造 Decimal 小数位。
|
||||
if result.is_integer() {
|
||||
Self::integer(&result.to_integer().to_string())
|
||||
} else {
|
||||
Self::rational(&result.numer().to_string(), &result.denom().to_string())
|
||||
}
|
||||
}
|
||||
/// eval/数值载荷数学比较,拒绝复数,处理 NaN 无序与无穷,不依赖 Rust Eq。
|
||||
/// 有限混合比较把二进制浮点精确转为分数,不先舍入另一侧;精确转换仍可能因输入预算报错。
|
||||
pub fn numeric_compare(&self, other: &Self, op: CompareOp) -> Result<bool, VmError> {
|
||||
use std::cmp::Ordering;
|
||||
// 先要求两个输入均为实数;后续按精确/浮点模式转换。
|
||||
let (a, b) = (
|
||||
self.as_real()
|
||||
.ok_or(VmError::UnsupportedOperation("complex comparison"))?,
|
||||
@@ -457,6 +573,7 @@ impl EndorNumber {
|
||||
.as_real()
|
||||
.ok_or(VmError::UnsupportedOperation("complex comparison"))?,
|
||||
);
|
||||
// 双方均为可容纳 i64 的 Integer 时直接比较,不需要大整数分配。
|
||||
if let (Some(a), Some(b)) = (self.as_i64(), other.as_i64()) {
|
||||
return Ok(match op {
|
||||
CompareOp::Eq => a == b,
|
||||
@@ -469,15 +586,18 @@ impl EndorNumber {
|
||||
}
|
||||
// Finite binary floats compare as exact binary rationals, so a large
|
||||
// integer never loses precision merely to answer an ordering question.
|
||||
// 双浮点使用 partial_cmp;有限混合比较转精确分数,避免大整数失真。
|
||||
let order = if let (EndorReal::BinaryFloat(a), EndorReal::BinaryFloat(b)) = (a, b) {
|
||||
f64::from_bits(*a).partial_cmp(&f64::from_bits(*b))
|
||||
} else {
|
||||
// 识别混合比较中的浮点 NaN/无穷,其他表示只有有限值。
|
||||
let nonfinite = |n: &EndorReal| match n {
|
||||
EndorReal::BinaryFloat(bits) if !f64::from_bits(*bits).is_finite() => {
|
||||
Some(f64::from_bits(*bits))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
// NaN 无序,无穷按符号超出有限值,其他输入走精确比较。
|
||||
match (nonfinite(a), nonfinite(b)) {
|
||||
(Some(a), _) if a.is_nan() => None,
|
||||
(_, Some(b)) if b.is_nan() => None,
|
||||
@@ -494,20 +614,26 @@ impl EndorNumber {
|
||||
_ => Some(exact_real(a)?.cmp(&exact_real(b)?)),
|
||||
}
|
||||
};
|
||||
// 无序令 Eq 和有序关系为 false,Ne 为 true。
|
||||
Ok(match op {
|
||||
CompareOp::Eq => order == Some(Ordering::Equal),
|
||||
CompareOp::Ne => order != Some(Ordering::Equal),
|
||||
CompareOp::Lt => order == Some(Ordering::Less),
|
||||
// 小于等于仅接受已知 Less 或 Equal;NaN 造成的无序 None 不满足关系。
|
||||
CompareOp::Le => matches!(order, Some(Ordering::Less | Ordering::Equal)),
|
||||
CompareOp::Gt => order == Some(Ordering::Greater),
|
||||
// 大于等于仅接受已知 Greater 或 Equal,无序值同样返回 false。
|
||||
CompareOp::Ge => matches!(order, Some(Ordering::Greater | Ordering::Equal)),
|
||||
})
|
||||
}
|
||||
}
|
||||
/// 算术/比较/收窄共用精确有理数转换,有限浮点按精确二进制分数处理,有操作预算。
|
||||
fn exact_real(value: &EndorReal) -> Result<num_rational::BigRational, VmError> {
|
||||
use num_bigint::BigInt;
|
||||
use num_rational::BigRational;
|
||||
// 大整数转换先限制文本长度,含可能的符号。
|
||||
let integer = |s: &str| -> Result<BigInt, VmError> {
|
||||
// 超预算报错,不以截断或浮点近似继续。
|
||||
if s.len() > 4096 {
|
||||
return Err(VmError::InvalidArgument(
|
||||
"numeric operand exceeds operation budget",
|
||||
@@ -516,6 +642,7 @@ fn exact_real(value: &EndorReal) -> Result<num_rational::BigRational, VmError> {
|
||||
s.parse()
|
||||
.map_err(|_| VmError::InvalidArgument("invalid exact number"))
|
||||
};
|
||||
// 不同实数表示转成运算用精确有理数;浮点必须有限,十进制另检查指数运算预算。
|
||||
match value {
|
||||
EndorReal::Integer(n) => Ok(BigRational::from_integer(integer(&n.to_string())?)),
|
||||
EndorReal::Rational(n) => Ok(BigRational::new(
|
||||
@@ -525,7 +652,9 @@ fn exact_real(value: &EndorReal) -> Result<num_rational::BigRational, VmError> {
|
||||
EndorReal::BinaryFloat(bits) => BigRational::from_float(f64::from_bits(*bits))
|
||||
.ok_or(VmError::InvalidArgument("non-finite exact conversion")),
|
||||
EndorReal::Decimal(n) => {
|
||||
// 十进制已通过构造语法检查,这里为运算解析指数。
|
||||
let parts: Vec<_> = n.literal.split(['e', 'E']).collect();
|
||||
// 保存允许很长指数,实际运算要求可转为 i32。
|
||||
let exponent = if parts.len() == 2 {
|
||||
parts[1].parse::<i32>().map_err(|_| {
|
||||
VmError::InvalidArgument("numeric exponent exceeds operation budget")
|
||||
@@ -533,15 +662,21 @@ fn exact_real(value: &EndorReal) -> Result<num_rational::BigRational, VmError> {
|
||||
} else {
|
||||
0
|
||||
};
|
||||
// 尾数的小数位数须从指数扣除,确定整体十进制缩放。
|
||||
let decimals = parts[0].split_once('.').map_or(0, |(_, s)| s.len()) as i64;
|
||||
// 提升到 i64 计算指数差,随后检查绝对幂次预算。
|
||||
let power = i64::from(exponent) - decimals;
|
||||
// 限制生成巨大十次幂的工作量,不只限制结果显示长度。
|
||||
if power.abs() > 4096 {
|
||||
return Err(VmError::InvalidArgument(
|
||||
"numeric exponent exceeds operation budget",
|
||||
));
|
||||
}
|
||||
// 去掉小数点但保留符号,将尾数转为整数。
|
||||
let digits = integer(&parts[0].replace('.', ""))?;
|
||||
// 预算通过才计算精确的 10 次幂,不调用浮点 pow。
|
||||
let factor = BigInt::from(10).pow(power.unsigned_abs() as u32);
|
||||
// 负幂放分母,非负幂乘分子;BigRational 规范化结果。
|
||||
Ok(if power < 0 {
|
||||
BigRational::new(digits, factor)
|
||||
} else {
|
||||
|
||||
+118
-49
@@ -1,3 +1,6 @@
|
||||
//! 对象公共表示与宿主载荷协议;Heap 管生命周期,ObjectStore 调协议,core::Vm 管指令与调用帧。
|
||||
//! Value 的 Rust clone 只复制表示,不变更托管引用计数;所有权由产生/接收它的 API 约定决定。
|
||||
|
||||
use std::any::Any;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
@@ -6,31 +9,43 @@ use std::hash::{Hash, Hasher};
|
||||
|
||||
use crate::{EndorNumber, ObjectRef, VmError};
|
||||
|
||||
/// Immediate values stay outside the object heap. Copying an ObjectRef or
|
||||
/// cloning a Value never retains a reference; ownership follows the API that
|
||||
/// produced it. In particular, call/execute results own a root until released.
|
||||
/// 直接值不占对象堆槽;复制 ObjectRef 或 clone Value 不自动保留引用,所有权以 API 为准。
|
||||
/// 特别是 VM 的 call/execute 返回对象时带有一份外部根,使用者需要归还。
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Value {
|
||||
// 语言空值,与 Cell 的未初始化 None 不同。
|
||||
Nil,
|
||||
// 直接布尔值,不占用对象槽。
|
||||
Bool(bool),
|
||||
/// Rust equality preserves representation; it is not numeric equivalence.
|
||||
/// 直接数值;Rust 相等比较保留具体数值表示,不等于语言层数值等价判断。
|
||||
Number(EndorNumber),
|
||||
// 拥有文本的直接表示,与堆上的 EndorStringObject 可在字典键比较中按内容互通。
|
||||
Text(String),
|
||||
// 仅保存句柄;是否拥有外部根取决于 API,复制/丢弃此变体不会 retain/release。
|
||||
Object(ObjectRef),
|
||||
}
|
||||
|
||||
/// VM 的比较运算标识;ObjectStore::compare 传入载荷,必要时再尝试反向运算。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CompareOp {
|
||||
/// 相等;传给载荷解释,不直接使用 Rust 的对象地址比较。
|
||||
Eq,
|
||||
/// 不等;传给载荷解释,不直接使用 Rust 的对象地址比较。
|
||||
Ne,
|
||||
/// 小于;传给载荷解释,不直接使用 Rust 的对象地址比较。
|
||||
Lt,
|
||||
/// 小于等于;传给载荷解释,不直接使用 Rust 的对象地址比较。
|
||||
Le,
|
||||
/// 大于;传给载荷解释,不直接使用 Rust 的对象地址比较。
|
||||
Gt,
|
||||
/// 大于等于;传给载荷解释,不直接使用 Rust 的对象地址比较。
|
||||
Ge,
|
||||
}
|
||||
|
||||
impl CompareOp {
|
||||
/// Store 对调左右载荷时使用;相等类不变,有序运算反转方向。
|
||||
pub(crate) fn reflected(self) -> Self {
|
||||
// 运算映射只改变方向,不执行比较,也不取反比较结果。
|
||||
match self {
|
||||
Self::Eq => Self::Eq,
|
||||
Self::Ne => Self::Ne,
|
||||
@@ -42,82 +57,89 @@ impl CompareOp {
|
||||
}
|
||||
}
|
||||
|
||||
/// Non-blocking awaitable observation. Ready object values are borrowed from
|
||||
/// the managed graph and must be retained by the consumer before dropping it.
|
||||
/// eval 的非阻塞等待观察结果;Ready 中的对象仍借自托管图,消费方须在释放原图前保留结果。
|
||||
#[derive(Debug)]
|
||||
pub enum AwaitPoll {
|
||||
// 结果尚未就绪;执行器保存等待状态,之后再观察。
|
||||
Pending,
|
||||
// 已就绪的借用结果;消费方在释放原结果图之前须保留所需对象根。
|
||||
Ready(Value),
|
||||
}
|
||||
|
||||
/// Rust-side operation interface, not a language type or metaclass.
|
||||
///
|
||||
/// Implementations must report every payload-owned object reference in trace(),
|
||||
/// including duplicates. Those edges must stay immutable after allocation;
|
||||
/// mutable object references belong in VM-managed attributes or built-in
|
||||
/// containers updated transactionally by the VM instead.
|
||||
/// Rust Drop releases host resources when the payload is destroyed.
|
||||
/// Equal payloads must provide compatible hashes, or reject hashing explicitly.
|
||||
/// 宿主实现的 Rust 操作协议,不是语言层类型或元类;Store/VM 根据具体操作调用这些方法。
|
||||
/// trace 必须报告载荷拥有的每一条对象引用(包括重复边),分配后这些边不能私自改变。
|
||||
/// 可变托管引用应放入受控属性或内置容器,经 Store/Heap 更新;Rust Drop 负责释放宿主资源。
|
||||
/// 内容相等的载荷必须提供兼容哈希,或明确拒绝哈希。
|
||||
pub trait ObjectPayload: Any + fmt::Debug + Send + Sync {
|
||||
/// eval 的 await 路径轮询载荷;默认拒绝等待,宿主实现不得阻塞执行线程等待完成。
|
||||
fn poll_await(&self) -> Result<AwaitPoll, VmError> {
|
||||
Err(VmError::UnsupportedOperation("await"))
|
||||
}
|
||||
|
||||
/// Explicit function category; callable Type objects are not functions.
|
||||
/// allocate 判定是否自动附着 Function 类型;可调用 Type 并不属于函数分类。
|
||||
fn is_function(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Ordinary field storage may be indirect, independently of type identity.
|
||||
/// Targets must be traced; retargeting records Contents on the forwarder.
|
||||
/// Store::attribute_path 与类型属性协议查询转发目标;None 表示在自身存储属性。
|
||||
/// 目标须由托管强边持有;更换目标须在转发对象上记录 Contents,属性身份与类型身份分开。
|
||||
fn attribute_target(&self, _store: &crate::ObjectStore) -> Result<Option<ObjectRef>, VmError> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Shared-reference facades may expose the current referent's type. The
|
||||
/// referent must also be held by traced managed edges. Ordinary objects
|
||||
/// keep their immutable header type; this does not change inheritance.
|
||||
/// types 的类型解析查询共享引用门面的当前目标;None 表示使用自身头部名义类型。
|
||||
/// 目标须有 trace 强边;此协议不修改普通对象不可变的头部类型或继承关系。
|
||||
fn type_target(&self, _store: &crate::ObjectStore) -> Result<Option<ObjectRef>, VmError> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Optional context-aware native entry. The VM clones this dispatcher before
|
||||
/// borrowing the store mutably; callbacks never borrow a live payload.
|
||||
/// VM 查找可访问 NativeContext 的原生分派器;先克隆 Arc,再可变借用 Store 执行。
|
||||
/// 回调期间不保留载荷借用,避免同时借用载荷和可变 Store。
|
||||
fn native_callable(&self) -> Option<std::sync::Arc<dyn crate::NativeCallable>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Store::payload 等借助 Any 下转型到具体 Rust 载荷;不代表语言层类型判断。
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
/// A diagnostic label only, with no language type or inheritance semantics.
|
||||
/// 错误/默认 repr 的诊断名称;不能据此判断语言名义类型、继承或能力。
|
||||
fn kind_name(&self) -> &'static str;
|
||||
|
||||
/// Heap 在分配/替换载荷时收集内部强边(含重复边);GC 此后使用边快照。
|
||||
/// 单独调用 trace 只枚举借用身份,不替调用者取得拥有根。
|
||||
/// 默认无边;实现不能在快照建立后通过内部可变性悄悄改变这些引用。
|
||||
fn trace(&self, _visit: &mut dyn FnMut(ObjectRef)) {}
|
||||
|
||||
/// Heap 属性写入检查是否允许动态字段;默认不允许,Record 显式开启。
|
||||
fn allows_attributes(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Store::repr 的默认诊断表示包含载荷名称与对象身份,不递归展开整个对象图。
|
||||
fn repr(&self, identity: ObjectRef) -> Result<String, VmError> {
|
||||
Ok(format!("<{} {identity}>", self.kind_name()))
|
||||
}
|
||||
|
||||
/// Store::str 的文本协议;未覆盖时复用 repr,宿主可提供更适合显示的文本。
|
||||
fn str(&self, identity: ObjectRef) -> Result<String, VmError> {
|
||||
self.repr(identity)
|
||||
}
|
||||
|
||||
/// Store::is_true/VM 分支求真时调用;普通对象默认真,容器或标量载荷可覆盖。
|
||||
fn is_true(&self) -> Result<bool, VmError> {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Store::hash 默认按身份哈希;若实现内容相等,须同步实现兼容哈希或拒绝哈希。
|
||||
fn hash(&self, identity: ObjectRef) -> Result<u64, VmError> {
|
||||
// 哈希整个句柄身份,不使用载荷地址;结果不是跨进程稳定标识。
|
||||
let mut hasher = DefaultHasher::new();
|
||||
identity.hash(&mut hasher);
|
||||
Ok(hasher.finish())
|
||||
}
|
||||
|
||||
/// None means "not implemented"; the VM tries the reflected operation,
|
||||
/// then falls back to identity for Eq/Ne or rejects ordering.
|
||||
/// Store::compare 调用;None 表示未实现,Some(false) 表示已经实现且不满足比较。
|
||||
/// Store 仅在 None 时尝试反向协议,最后相等回退身份、有序比较报错。
|
||||
fn compare(
|
||||
&self,
|
||||
_other: &dyn ObjectPayload,
|
||||
@@ -126,42 +148,39 @@ pub trait ObjectPayload: Any + fmt::Debug + Send + Sync {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Host-call entry: arguments and raw results are borrowed handles. The VM
|
||||
/// validates/retains the result before exposing it as an owned call result.
|
||||
/// Language functions are dispatched by Vm's frame evaluator instead.
|
||||
/// Positional metadata for native call protocols; source code is queried by the store.
|
||||
/// VM 原生调用协议查询位置参数/返回约束;默认从上下文分派器获取。
|
||||
/// 源码函数签名由 Store 查询代码对象,此方法不是执行调用的入口。
|
||||
fn call_signature(&self) -> Option<crate::CallSignature> {
|
||||
self.native_callable()
|
||||
.and_then(|callable| callable.signature())
|
||||
}
|
||||
|
||||
/// VM 查询参数数量限制;默认从上下文分派器获取,None 表示没有此处的数量元数据。
|
||||
fn call_arity(&self) -> Option<crate::CallArity> {
|
||||
self.native_callable().and_then(|callable| callable.arity())
|
||||
}
|
||||
|
||||
/// VM 的无上下文宿主调用入口:参数及原始结果句柄是借用,VM 随后验证并保留结果根。
|
||||
/// 默认不可调用;源码函数另走执行帧,需访问 Store 的原生函数另走 native_callable。
|
||||
fn call(&self, _arguments: &[Value]) -> Result<Value, VmError> {
|
||||
Err(VmError::UnsupportedOperation("call"))
|
||||
}
|
||||
|
||||
/// Opt-in shared-reference assignment. Returning true preserves the existing
|
||||
/// object identity after replacing its host-owned target. Ordinary payloads
|
||||
/// return false and retain normal value assignment. Implementations must
|
||||
/// validate before mutation and must not add untraced managed-object edges.
|
||||
/// True only when legacy assign_from always returns false. Read-only
|
||||
/// callers can then rebind a local reference without attempting mutation.
|
||||
/// assignment_cell is checked first and remains a protected write protocol.
|
||||
/// Store::assign_from 的只读优化标志:true 承诺旧式 assign_from 总返回 false。
|
||||
/// 因而执行器可在只读调用中直接重绑定局部引用,不尝试修改旧载荷。
|
||||
/// 托管 assignment_cell 协议优先且仍受写屏障保护;此标志不赋予修改旧对象的权限。
|
||||
fn reference_assignment_only(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Store 的旧式宿主原地赋值协议;true 保留目标身份,false 让执行器使用新引用。
|
||||
/// 实现须先校验再修改,且不能改变未重新登记的托管强边;默认不原地赋值。
|
||||
fn assign_from(&self, _source: &dyn ObjectPayload) -> Result<bool, VmError> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Optional managed shared-reference assignment. Return a built-in Cell
|
||||
/// owned by this payload after validating the source and any domain cycles.
|
||||
/// The store writes the source object to the Cell transactionally. This
|
||||
/// keeps mutable edges inside the GC graph, unlike native assign_from.
|
||||
/// Store 优先采用的共享引用赋值协议:校验 source 与领域环路后返回本载荷持有的 Cell。
|
||||
/// Store 将 source 写入该 Cell 并记录变更,确保可变引用边始终由 GC 管理;None 表示不用此协议。
|
||||
fn assignment_cell(
|
||||
&self,
|
||||
_source: ObjectRef,
|
||||
@@ -171,39 +190,53 @@ pub trait ObjectPayload: Any + fmt::Debug + Send + Sync {
|
||||
}
|
||||
}
|
||||
|
||||
/// Store::new_object 和普通名义实例的无额外载荷记录;字段保存在 EndorObject.attributes。
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Record;
|
||||
|
||||
impl ObjectPayload for Record {
|
||||
// Record 没有原地赋值实现,允许只读调用直接重绑定引用。
|
||||
fn reference_assignment_only(&self) -> bool {
|
||||
true
|
||||
}
|
||||
// Store 下转型入口,返回自身的 Rust 类型信息。
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
// 供错误或默认对象表示使用的诊断名称。
|
||||
fn kind_name(&self) -> &'static str {
|
||||
"record"
|
||||
}
|
||||
|
||||
// 普通记录允许 Heap 在对象公共属性表中写入动态字段。
|
||||
fn allows_attributes(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// 无捕获函数指针型宿主回调;含状态/上下文的调用应使用 NativeCallable 协议。
|
||||
pub type EndorNativeCall = fn(&[Value]) -> Result<Value, VmError>;
|
||||
/// 保留给既有嵌入方的回调类型别名。
|
||||
pub type NativeCall = EndorNativeCall;
|
||||
|
||||
/// 宿主把简单 Rust 回调包装成可分配函数载荷,供 VM 的原生调用路径分派。
|
||||
#[derive(Debug)]
|
||||
pub struct EndorNativeFunctionObject {
|
||||
// 诊断和参数数量错误使用的函数名称。
|
||||
name: String,
|
||||
// 无捕获 Rust 回调,不包含额外托管引用边。
|
||||
function: NativeCall,
|
||||
// 可选位置参数数量约束,也可由完整 signature 提供。
|
||||
arity: Option<crate::CallArity>,
|
||||
// 完整签名可能引用托管类型,必须通过 trace 持有这些引用。
|
||||
signature: Option<crate::CallSignature>,
|
||||
}
|
||||
|
||||
impl EndorNativeFunctionObject {
|
||||
/// 宿主构造尚未分配的载荷;初始没有数量或类型签名约束,之后由 Store::allocate 接管。
|
||||
pub fn new(name: impl Into<String>, function: NativeCall) -> Self {
|
||||
// 名称转为拥有的 String;函数指针本身无需额外所有权管理。
|
||||
Self {
|
||||
name: name.into(),
|
||||
function,
|
||||
@@ -212,56 +245,73 @@ impl EndorNativeFunctionObject {
|
||||
}
|
||||
}
|
||||
|
||||
/// 宿主/诊断借用函数名,不执行回调。
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
/// 构造阶段补充完整签名,并同步数量限制;分配后签名中的引用由 Heap 边快照持有。
|
||||
/// 此构建器不验证签名结构;Store::native_signature 查询或 VM 调用时再校验完整契约。
|
||||
pub fn with_signature(mut self, signature: crate::CallSignature) -> Self {
|
||||
self.arity = Some(signature.arity);
|
||||
self.signature = Some(signature);
|
||||
self
|
||||
}
|
||||
/// 构造阶段单独设置数量限制;不会重写已有完整签名,调用方应避免配置互相矛盾。
|
||||
pub fn with_arity(mut self, arity: crate::CallArity) -> Self {
|
||||
self.arity = Some(arity);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Compatibility name for existing embedders.
|
||||
/// 为已有嵌入方保留的函数载荷名称,不引入第二种运行时对象。
|
||||
pub type NativeFunction = EndorNativeFunctionObject;
|
||||
|
||||
impl ObjectPayload for EndorNativeFunctionObject {
|
||||
// 让 Store::allocate 按配置为该原生函数附着 Function 名义类型。
|
||||
fn is_function(&self) -> bool {
|
||||
true
|
||||
}
|
||||
// 不实现旧式原地赋值,因此引用重绑定不要求修改本载荷。
|
||||
fn reference_assignment_only(&self) -> bool {
|
||||
true
|
||||
}
|
||||
// Store 下转型入口,返回自身的 Rust 类型信息。
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
// 供错误或默认对象表示使用的诊断名称。
|
||||
fn kind_name(&self) -> &'static str {
|
||||
"native-function"
|
||||
}
|
||||
|
||||
// 用名称生成诊断表示,不调用回调,也不显示内存地址。
|
||||
fn repr(&self, _identity: ObjectRef) -> Result<String, VmError> {
|
||||
Ok(format!("<native-function {}>", self.name))
|
||||
}
|
||||
|
||||
// Heap 分配/替换时收集签名持有的类型引用;函数指针和名称不产生托管强边。
|
||||
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
|
||||
// 无完整签名时没有签名边;references 统一枚举参数与返回约束中的引用。
|
||||
if let Some(signature) = &self.signature {
|
||||
// 不去重:每次拥有的引用都要计入 Heap 的强边数量。
|
||||
for reference in signature.references() {
|
||||
visit(reference);
|
||||
}
|
||||
}
|
||||
}
|
||||
// VM 查询签名时复制元数据,不因其中句柄的 clone 而增加外部根。
|
||||
fn call_signature(&self) -> Option<crate::CallSignature> {
|
||||
self.signature.clone()
|
||||
}
|
||||
// VM 查询构造时声明的数量约束,不经上下文分派器。
|
||||
fn call_arity(&self) -> Option<crate::CallArity> {
|
||||
self.arity
|
||||
}
|
||||
// VM 分派或宿主直接调用:此载荷方法只检查数量,再将同一参数切片借给 Rust 回调。
|
||||
// 直接调用不执行 VM 的完整参数/返回类型检查,结果中的对象也仍借用;完整调用请经 Vm::call。
|
||||
fn call(&self, arguments: &[Value]) -> Result<Value, VmError> {
|
||||
// 未声明 arity 时不在这里限制数量;声明后错误必须先于回调的任何副作用返回。
|
||||
if let Some(arity) = self.arity {
|
||||
arity.check(self.name.clone(), arguments.len())?;
|
||||
}
|
||||
@@ -269,77 +319,96 @@ impl ObjectPayload for EndorNativeFunctionObject {
|
||||
}
|
||||
}
|
||||
|
||||
/// Common runtime state. Embedders may attach an immutable managed type.
|
||||
/// Heap 维护的公共头部;宿主只读访问,计数、版本和类型附着由受控 Store 操作更新。
|
||||
#[derive(Debug)]
|
||||
pub struct ObjectHeader {
|
||||
// 完整身份含堆、槽位和代际;槽复用后旧身份不能再次访问新对象。
|
||||
pub(crate) identity: ObjectRef,
|
||||
// 总强计数 = 外部根 + 对象图内部强边。
|
||||
pub(crate) strong_refs: usize,
|
||||
// 宿主持有和 VM 活跃执行槽持有的根数,是环回收的可达性起点。
|
||||
pub(crate) external_refs: usize,
|
||||
// 已记录修改的单调版本,用于整对象依赖重验。
|
||||
pub(crate) change_version: u64,
|
||||
// 可选名义类型强边,附着后不能通过普通属性改写。
|
||||
pub(crate) type_object: Option<ObjectRef>,
|
||||
}
|
||||
|
||||
impl ObjectHeader {
|
||||
/// types/宿主读取头部附着的借用类型句柄;共享引用的有效类型仍需走 type_target 协议。
|
||||
pub fn type_object(&self) -> Option<ObjectRef> {
|
||||
self.type_object
|
||||
}
|
||||
|
||||
/// Store/缓存借用版本快照;此 getter 本身不登记读取。
|
||||
pub fn change_version(&self) -> u64 {
|
||||
self.change_version
|
||||
}
|
||||
|
||||
/// 宿主诊断读取完整句柄,不新建根引用。
|
||||
pub fn identity(&self) -> ObjectRef {
|
||||
self.identity
|
||||
}
|
||||
|
||||
/// Store/测试读取包含内部边的总强计数。
|
||||
pub fn ref_count(&self) -> usize {
|
||||
self.strong_refs
|
||||
}
|
||||
|
||||
/// Host-owned references and live execution roots, excluding heap edges.
|
||||
/// Heap 环回收及诊断读取外部根数,不包含对象间强边。
|
||||
pub fn external_ref_count(&self) -> usize {
|
||||
self.external_refs
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtime object foundation, analogous in role to CPython's common object
|
||||
/// structure, without its binary layout. The heap boxes this
|
||||
/// structure so its address remains stable throughout its lifetime.
|
||||
/// Heap 中稳定装箱的对象实体,把公共头部、动态载荷、属性和强边快照放在同一基础结构。
|
||||
/// 对象存活期间该实体地址稳定;这不保证单独替换的载荷盒地址不变。
|
||||
#[derive(Debug)]
|
||||
pub struct EndorObject {
|
||||
// 生命周期、身份、版本和名义类型公共信息。
|
||||
pub(crate) header: ObjectHeader,
|
||||
// 动态 Rust 载荷;替换载荷不会移动外层 EndorObject 的稳定盒地址。
|
||||
pub(crate) payload: Box<dyn ObjectPayload>,
|
||||
// 受 Heap 写屏障和计数维护的动态属性表,键是普通 Rust 字符串。
|
||||
pub(crate) attributes: BTreeMap<String, Value>,
|
||||
// 分配/替换载荷时记录的强边快照,保留重复项供精确计数与回收。
|
||||
pub(crate) payload_edges: Vec<ObjectRef>,
|
||||
}
|
||||
|
||||
impl EndorObject {
|
||||
/// Store/宿主只读借用公共头部,不提供绕过计数和类型保护的可变入口。
|
||||
pub fn header(&self) -> &ObjectHeader {
|
||||
&self.header
|
||||
}
|
||||
|
||||
/// Store/宿主借用动态载荷执行协议或下转型;getter 自身不验证句柄或登记读取。
|
||||
pub fn payload(&self) -> &dyn ObjectPayload {
|
||||
self.payload.as_ref()
|
||||
}
|
||||
|
||||
/// Visits owned edges, not external roots. Payload edges are snapshotted at
|
||||
/// allocation and refreshed on VM-managed payload replacement; attributes
|
||||
/// are maintained by the VM's reference accounting.
|
||||
/// Heap 的回收/释放路径枚举内部拥有边,不含外部根;类型、载荷快照和属性各自计数。
|
||||
/// 不重新调用载荷 trace,避免内部可变性使 GC 所见图与引用计数脱节。
|
||||
pub fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
|
||||
// 类型附着本身拥有一条强边。
|
||||
if let Some(reference) = self.header.type_object {
|
||||
visit(reference);
|
||||
}
|
||||
// 逐项输出载荷快照,重复引用不能合并。
|
||||
for reference in &self.payload_edges {
|
||||
visit(*reference);
|
||||
}
|
||||
// 属性只有 Object 变体贡献托管边;文本/数值等由 Value 自身持有。
|
||||
for value in self.attributes.values() {
|
||||
// 将属性拥有的对象引用交给访问器,不改变计数。
|
||||
if let Value::Object(reference) = value {
|
||||
visit(*reference);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Heap 需要拥有的边列表时使用,把 trace 的回调输出收集成 Vec,不新增引用根。
|
||||
pub(crate) fn edges(&self) -> Vec<ObjectRef> {
|
||||
// 这是身份列表快照,不是深拷贝对象图。
|
||||
let mut edges = Vec::new();
|
||||
self.trace(&mut |reference| edges.push(reference));
|
||||
edges
|
||||
|
||||
+113
-27
@@ -1,20 +1,27 @@
|
||||
//! VM 与宿主共用的对象存储门面:分配、根引用、载荷操作、属性路径和读取依赖都从这里进入。
|
||||
//! heap 负责底层句柄/计数/写屏障;types 与 store_builtins 扩展此 impl,分别提供类型协议和容器操作。
|
||||
|
||||
use crate::heap::Heap;
|
||||
use crate::{
|
||||
ChangeBatch, ChangeKey, CompareOp, EndorObject, ObjectPayload, ObjectRef, Record, Value,
|
||||
VmError, WeakObjectRef,
|
||||
};
|
||||
|
||||
/// Object storage independent of the VM evaluator. Construction creates an empty
|
||||
/// heap; dropping it releases all objects and their host resources. Mutable
|
||||
/// access is limited to operations that validate and account for owned edges.
|
||||
/// 独立于指令执行器的存储;Vm 持有它,宿主也可单独使用。构造时堆为空,Drop 时释放全部载荷。
|
||||
/// 修改必须经受控入口,使引用边、只读保护和变更记录保持一致。
|
||||
#[derive(Debug)]
|
||||
pub struct ObjectStore {
|
||||
// 实际对象槽、引用计数、读依赖和待取变更批次的唯一存储。
|
||||
pub(crate) heap: Heap,
|
||||
// 新建函数自动附着的名义类型;使用弱引用,避免配置本身延长类型寿命。
|
||||
function_type: Option<WeakObjectRef>,
|
||||
// 宿主显式开启的字段声明/替换检查开关;默认关闭。
|
||||
pub(crate) field_type_checks: bool,
|
||||
// types 的 RAII guard 按线程登记正在校验的对象,阻止属性/类型转发导致递归重入。
|
||||
pub(crate) active_attribute_checks: std::sync::Mutex<
|
||||
std::collections::HashMap<std::thread::ThreadId, std::collections::BTreeSet<ObjectRef>>,
|
||||
>,
|
||||
// contracts 按线程记录正在核对的对象/能力对,处理递归契约检查时的重入。
|
||||
pub(crate) active_trait_checks: std::sync::Mutex<
|
||||
std::collections::HashMap<
|
||||
std::thread::ThreadId,
|
||||
@@ -24,8 +31,9 @@ pub struct ObjectStore {
|
||||
}
|
||||
|
||||
impl ObjectStore {
|
||||
/// Opt-in declared-field and method substitution checks, shared with the VM.
|
||||
/// Vm::set_source_type_checks 委托的配置入口;影响后续字段与方法替换检查,不遍历重验已有对象。
|
||||
pub fn set_field_type_checks(&mut self, enabled: bool) -> Result<(), VmError> {
|
||||
// 配置属于持久状态,不能由只读调用改变;即使传入相同配置也拒绝。
|
||||
if self.is_read_only() {
|
||||
return Err(VmError::UnsupportedOperation(
|
||||
"configure field type checks during read-only call",
|
||||
@@ -35,18 +43,24 @@ impl ObjectStore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Existing objects are immutable; fresh result graphs may be constructed.
|
||||
/// Scopes must end in LIFO order. Read handles are borrowed, never GC roots.
|
||||
/// VM 的只读调用路径进入此作用域:进入前已有对象被冻结,新建结果图仍可构造。
|
||||
/// 返回嵌套深度令牌,结束必须按 LIFO;读集合中的句柄是借用,不是 GC 根。
|
||||
pub fn begin_read_only(&mut self) -> usize {
|
||||
self.heap.begin_read_only()
|
||||
}
|
||||
/// VM 结束只读调用,返回兼容的对象读取集合;令牌错误时保持作用域未弹出。
|
||||
pub fn end_read_only(&mut self, scope: usize) -> Result<Vec<ObjectRef>, VmError> {
|
||||
self.heap.end_read_only(scope)
|
||||
}
|
||||
/// 只读调用的精确版本,额外保留整对象、字典种类、指定名称等不同依赖。
|
||||
pub fn end_read_only_precise(&mut self, scope: usize) -> Result<crate::ReadSet, VmError> {
|
||||
self.heap.end_read_only_precise(scope)
|
||||
}
|
||||
/// 缓存使用者重验一次观察;失效句柄或当前读取失败都表示依赖不再有效。
|
||||
/// 经正常读取入口比较,因此若当前另有读取作用域,也会登记相应观察。
|
||||
/// 名称/字典种类比较当前状态而非永久失效位,删除遮蔽或恢复原值后可再次有效。
|
||||
pub fn read_dependency_valid(&self, dependency: &crate::ReadDependency) -> bool {
|
||||
// 整对象检查版本;种类只检查仍是字典;名称检查当前 Option<Value> 是否保持相等。
|
||||
match dependency {
|
||||
crate::ReadDependency::Object { object, version } => {
|
||||
self.change_version(*object).ok() == Some(*version)
|
||||
@@ -63,21 +77,21 @@ impl ObjectStore {
|
||||
.is_ok_and(|current| current == *value),
|
||||
}
|
||||
}
|
||||
/// VM 子调用结束读取区间,同时取回程序/验证分类以及精确依赖;不结束外层只读作用域。
|
||||
pub fn end_read_tracking_precise(&self, token: usize) -> Result<crate::TrackedReads, VmError> {
|
||||
self.heap.end_read_tracking_precise(token)
|
||||
}
|
||||
/// Track one subcall inside the active read-only scope. Regions nest in
|
||||
/// LIFO order and do not clear the enclosing call's read dependencies.
|
||||
/// Handles returned by end_read_tracking are borrowed, never owning roots.
|
||||
/// VM 在已有只读作用域内标记一次子调用;区间按 LIFO 嵌套,不清除外层已收集的依赖。
|
||||
/// 没有只读作用域会报错;结束区间返回的句柄不新增根引用。
|
||||
pub fn begin_read_tracking(&self) -> Result<usize, VmError> {
|
||||
self.heap.begin_read_tracking()
|
||||
}
|
||||
/// 兼容入口:结束子区间,只返回读取对象,不保留读取分类和具体依赖键。
|
||||
pub fn end_read_tracking(&self, token: usize) -> Result<Vec<ObjectRef>, VmError> {
|
||||
self.heap.end_read_tracking(token)
|
||||
}
|
||||
|
||||
/// Like end_read_tracking, retaining the distinction between program reads
|
||||
/// and VM annotation validation. Ordinary reads always dominate.
|
||||
/// VM 核对调用契约时使用,区分程序内容读取与注解验证读取;两者都有时内容读取优先。
|
||||
pub fn end_read_tracking_details(
|
||||
&self,
|
||||
token: usize,
|
||||
@@ -85,18 +99,22 @@ impl ObjectStore {
|
||||
self.heap.end_read_tracking_details(token)
|
||||
}
|
||||
|
||||
/// VM/宿主判断当前是否位于任意只读作用域,不表示所有新建对象也不可写。
|
||||
pub fn is_read_only(&self) -> bool {
|
||||
self.heap.is_read_only()
|
||||
}
|
||||
|
||||
/// 宿主缓存或类型配置把有效强句柄转为弱句柄;不增加计数,也不消耗调用方已有根。
|
||||
/// 仍经 object 验证,处于只读追踪作用域时会登记此对象的版本读取。
|
||||
pub fn downgrade(&self, reference: ObjectRef) -> Result<WeakObjectRef, VmError> {
|
||||
self.object(reference)?;
|
||||
Ok(WeakObjectRef(reference))
|
||||
}
|
||||
|
||||
/// Returns a new owned root, or None when the original object was released.
|
||||
/// A foreign store is an error, not an expired reference.
|
||||
/// 宿主升级弱句柄:存活则 retain 一份外部根,已释放返回 None;跨堆引用仍是错误。
|
||||
/// 存活验证经过 object,可能登记读取;升级失败不交付拥有根。
|
||||
pub fn upgrade(&mut self, weak: WeakObjectRef) -> Result<Option<ObjectRef>, VmError> {
|
||||
// 先验证代际与堆身份;只有 InvalidHandle 被解释为过期,其他错误原样传播。
|
||||
match self.object(weak.0) {
|
||||
Ok(_) => {
|
||||
self.retain(weak.0)?;
|
||||
@@ -107,20 +125,23 @@ impl ObjectStore {
|
||||
}
|
||||
}
|
||||
|
||||
/// Vm::take_changes 及宿主缓存取走累计变更批次;清空待取记录,不重置对象版本。
|
||||
pub fn take_changes(&mut self) -> ChangeBatch {
|
||||
self.heap.take_changes()
|
||||
}
|
||||
|
||||
/// 缓存读取对象版本;通过 object 进入,若正在收集依赖也会记录此对象的读取。
|
||||
pub fn change_version(&self, reference: ObjectRef) -> Result<u64, VmError> {
|
||||
Ok(self.object(reference)?.header().change_version())
|
||||
}
|
||||
|
||||
/// Replace a host payload transactionally, including all owned GC edges.
|
||||
/// 宿主提交新载荷及其全部引用边;保持对象身份、属性和头部类型,校验失败不替换载荷。
|
||||
pub fn replace_payload(
|
||||
&mut self,
|
||||
reference: ObjectRef,
|
||||
payload: impl ObjectPayload,
|
||||
) -> Result<(), VmError> {
|
||||
// Type 定义不可被换成其他载荷,防止类型布局在创建后发生变化。
|
||||
if self
|
||||
.object(reference)?
|
||||
.payload()
|
||||
@@ -131,6 +152,7 @@ impl ObjectStore {
|
||||
"replace immutable type definition",
|
||||
));
|
||||
}
|
||||
// Trait 定义同样不可替换,防止已登记的能力契约发生隐式变化。
|
||||
if self
|
||||
.object(reference)?
|
||||
.payload()
|
||||
@@ -141,44 +163,57 @@ impl ObjectStore {
|
||||
"replace immutable trait definition",
|
||||
));
|
||||
}
|
||||
// 先核对内置载荷结构,再交由 Heap 验证 trace 强边与引用计数。
|
||||
self.validate_builtin(&payload)?;
|
||||
self.heap.replace_payload(reference, payload)
|
||||
}
|
||||
|
||||
/// eval::assignment_value 在新旧值都是对象时调用;true 表示更新旧对象并保留其身份。
|
||||
/// false 表示未执行此类原地赋值,执行器可改用传入的新对象引用。
|
||||
pub fn assign_from(&mut self, target: ObjectRef, source: ObjectRef) -> Result<bool, VmError> {
|
||||
// 两端必须是本堆存活对象;不能把异堆或过期句柄传给宿主赋值协议。
|
||||
self.object(target)?;
|
||||
self.object(source)?;
|
||||
// 优先采用托管 Cell 协议;载荷负责校验来源和领域环路,Store 负责实际更新引用边。
|
||||
if let Some(cell) = self
|
||||
.object(target)?
|
||||
.payload()
|
||||
.assignment_cell(source, self)?
|
||||
{
|
||||
// 转发对象本身也受只读保护,不能只检查承载引用的 Cell。
|
||||
self.heap.prepare_change(target)?;
|
||||
// Replacing the Cell edge may otherwise reclaim a target which is
|
||||
// reachable only through the edge being removed.
|
||||
// 更新 Cell 可能移除维持 target 存活的最后一条内部边;临时根保证记录变更前仍存活。
|
||||
self.retain(target)?;
|
||||
// 无论写入成功与否都释放临时根;仅成功时通知 target 的观察者。
|
||||
let result = self.cell_set(cell, Value::Object(source));
|
||||
// Cell 自己会记录变更;这里另记转发对象的 Contents,覆盖经转发器读取的依赖。
|
||||
if result.is_ok() {
|
||||
self.heap.record_change(target, ChangeKey::Contents);
|
||||
}
|
||||
self.release(target)?;
|
||||
return result.map(|()| true);
|
||||
}
|
||||
// 仅引用赋值载荷承诺 legacy assign_from 总是 false,可在只读局部重绑定时跳过写屏障。
|
||||
if self.is_read_only() && self.object(target)?.payload().reference_assignment_only() {
|
||||
return Ok(false);
|
||||
}
|
||||
// 旧式宿主原地赋值在调用前检查写屏障;实现不得悄悄改变 trace 中的托管边。
|
||||
self.heap.prepare_change(target)?;
|
||||
// 调用宿主原地赋值协议,返回是否确实改变;只有 true 才发布 Contents 变更。
|
||||
let changed = self
|
||||
.object(target)?
|
||||
.payload()
|
||||
.assign_from(self.object(source)?.payload())?;
|
||||
// false 不产生变更;true 表示宿主已更新载荷,发布一次 Contents 通知。
|
||||
if changed {
|
||||
self.heap.record_change(target, ChangeKey::Contents);
|
||||
}
|
||||
Ok(changed)
|
||||
}
|
||||
|
||||
/// Vm::new 及独立宿主创建空堆;不隐式创建语言根类型或内置函数。
|
||||
pub fn new() -> Result<Self, VmError> {
|
||||
// 初始没有函数类型配置,检查开关关闭,递归校验集合按需要惰性填充。
|
||||
Ok(Self {
|
||||
heap: Heap::new()?,
|
||||
function_type: None,
|
||||
@@ -188,25 +223,34 @@ impl ObjectStore {
|
||||
})
|
||||
}
|
||||
|
||||
/// 宿主/测试查询存活堆对象数;不是累计分配数,也不含直接 Value 标量。
|
||||
pub fn object_count(&self) -> usize {
|
||||
self.heap.len()
|
||||
}
|
||||
|
||||
/// Returns one external strong reference. Copies of the returned handle
|
||||
/// are borrowed; use retain/release to manage additional owned references.
|
||||
/// new_*、类型构造及宿主创建载荷的共同入口;成功返回一份外部强根。
|
||||
/// 复制返回句柄不会 retain,额外所有权须显式 retain/release。
|
||||
pub fn allocate(&mut self, payload: impl ObjectPayload) -> Result<ObjectRef, VmError> {
|
||||
// 先核对内置载荷结构,再交由 Heap 验证 trace 强边与引用计数。
|
||||
self.validate_builtin(&payload)?;
|
||||
// 函数分类由载荷声明;不是所有可调用对象(例如 Type)都自动附着 Function 类型。
|
||||
let function = payload.is_function();
|
||||
// Heap 分配稳定的对象盒并登记载荷强边;从此该对象已持有一份外部根。
|
||||
let object = self.heap.allocate(Box::new(payload))?;
|
||||
// 只有函数且配置过 Function 类型才附着;过期配置是错误,不静默创建无类型函数。
|
||||
if function && let Some(weak) = self.function_type {
|
||||
// 局部闭包收集附着结果,以便外层统一清理新对象的根。
|
||||
let result = (|| {
|
||||
// 升级得到临时类型根,附着后对象自身的类型边承担持有责任。
|
||||
let definition = self.upgrade(weak)?.ok_or(VmError::InvalidArgument(
|
||||
"configured Function type has expired",
|
||||
))?;
|
||||
// 保存附着结果,先释放升级产生的根,再把附着错误交给外层处理。
|
||||
let result = self.attach_type(object, definition);
|
||||
self.release(definition)?;
|
||||
result
|
||||
})();
|
||||
// 类型附着失败时释放本次分配的对象,不向调用者泄漏半初始化结果。
|
||||
if let Err(error) = result {
|
||||
self.release(object)?;
|
||||
return Err(error);
|
||||
@@ -215,14 +259,18 @@ impl ObjectStore {
|
||||
Ok(object)
|
||||
}
|
||||
|
||||
/// Vm::set_function_type 在根类型初始化时配置后续函数的名义类型;同一类型可重复设置。
|
||||
pub fn set_function_type(&mut self, definition: ObjectRef) -> Result<(), VmError> {
|
||||
self.payload::<crate::EndorTypeObject>(definition, "Type")?;
|
||||
// 配置属于持久状态,不能由只读调用改变;即使传入相同配置也拒绝。
|
||||
if self.is_read_only() {
|
||||
return Err(VmError::UnsupportedOperation(
|
||||
"configure Function type during read-only call",
|
||||
));
|
||||
}
|
||||
// 只保存弱引用;类型生命周期由根类型集合或已附着的对象负责。
|
||||
let weak = self.downgrade(definition)?;
|
||||
// 已配置后不能换成另一个类型,避免新旧函数的隐式类型规则不一致。
|
||||
if self.function_type.is_some_and(|old| old != weak) {
|
||||
return Err(VmError::InvalidArgument(
|
||||
"Function type is already configured",
|
||||
@@ -232,28 +280,37 @@ impl ObjectStore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Vm::new_object/宿主创建普通可附属性的 Record,名义类型由调用者另行附着。
|
||||
pub fn new_object(&mut self) -> Result<ObjectRef, VmError> {
|
||||
self.allocate(Record)
|
||||
}
|
||||
|
||||
/// 宿主及各访问器借用完整对象;验证句柄并在只读收集上下文登记当前版本读取。
|
||||
pub fn object(&self, reference: ObjectRef) -> Result<&EndorObject, VmError> {
|
||||
// 不增加外部根;返回引用受 self 的 Rust 借用期约束。
|
||||
let object = self.heap.get(reference)?;
|
||||
self.heap
|
||||
.record_read(reference, object.header().change_version());
|
||||
Ok(object)
|
||||
}
|
||||
|
||||
/// Borrowed storage path: depend on Contents for intermediate objects and
|
||||
/// Attribute(name) on the final storage object. Every handle is validated.
|
||||
/// 属性读写与类型协议沿 attribute_target 查找实际存储对象,返回含起点和终点的借用句柄链。
|
||||
/// 此处每一跳通过 object 记录整对象读取;最终具体属性的读取由调用方继续处理。
|
||||
pub fn attribute_path(&self, reference: ObjectRef) -> Result<Vec<ObjectRef>, VmError> {
|
||||
// path 保存访问顺序,seen 检测转发环,current 是本轮需要验证并展开的对象。
|
||||
let mut path = Vec::new();
|
||||
// 记录已经展开的对象身份,转发环在再次遇到同一对象时失败。
|
||||
let mut seen = std::collections::BTreeSet::new();
|
||||
// 从原始门面开始逐跳追踪 attribute_target,最终返回整个存储路径。
|
||||
let mut current = reference;
|
||||
// 每次至少加入一个对象;遇到无转发目标才正常结束,因此成功路径一定非空。
|
||||
loop {
|
||||
// 重访同一身份说明转发环路,不能靠无限追踪寻找属性终点。
|
||||
if !seen.insert(current) {
|
||||
return Err(VmError::InvalidArgument("cyclic attribute storage"));
|
||||
}
|
||||
path.push(current);
|
||||
// 有目标则继续;目标在下一轮验证归属/存活;None 表示该对象自行存储属性。
|
||||
match self.object(current)?.payload().attribute_target(self)? {
|
||||
Some(target) => current = target,
|
||||
None => return Ok(path),
|
||||
@@ -261,54 +318,73 @@ impl ObjectStore {
|
||||
}
|
||||
}
|
||||
|
||||
/// VM 执行槽和宿主把借用句柄变为额外拥有的根,同时增加 external 和 strong 计数。
|
||||
pub fn retain(&mut self, reference: ObjectRef) -> Result<(), VmError> {
|
||||
self.heap.retain(reference)
|
||||
}
|
||||
|
||||
/// VM 清理槽或宿主归还一份外部根;计数降零时连带释放对象拥有的内部边。
|
||||
/// 没有外部根时会报错,不能用它归还仅由其他对象持有的内部边。
|
||||
pub fn release(&mut self, reference: ObjectRef) -> Result<(), VmError> {
|
||||
self.heap.release(reference)
|
||||
}
|
||||
|
||||
/// 测试/诊断查询总强计数,包含外部根和堆内强边,不新增读取版本依赖。
|
||||
pub fn ref_count(&self, reference: ObjectRef) -> Result<usize, VmError> {
|
||||
Ok(self.heap.get(reference)?.header().ref_count())
|
||||
}
|
||||
|
||||
/// VM/宿主做身份比较:两端均先验证,随后比较完整堆身份、槽与代际,不调用载荷相等协议。
|
||||
pub fn is_same(&self, left: ObjectRef, right: ObjectRef) -> Result<bool, VmError> {
|
||||
self.heap.get(left)?;
|
||||
self.heap.get(right)?;
|
||||
Ok(left == right)
|
||||
}
|
||||
|
||||
/// Vm::repr 委托载荷生成诊断表示;直接 heap.get,不自动记录整对象读取。
|
||||
pub fn repr(&self, reference: ObjectRef) -> Result<String, VmError> {
|
||||
self.heap.get(reference)?.payload().repr(reference)
|
||||
}
|
||||
|
||||
/// Vm::str 委托用户可见文本表示,默认载荷实现回退到 repr。
|
||||
/// 当前文本协议直接借用 Heap 载荷,不自动登记对象版本读取;不能把调用成功当成已收集内容依赖。
|
||||
pub fn str(&self, reference: ObjectRef) -> Result<String, VmError> {
|
||||
self.heap.get(reference)?.payload().str(reference)
|
||||
}
|
||||
|
||||
/// VM 真值转换调用载荷协议;错误保持为错误,不把协议失败解释为 false。
|
||||
/// 当前真值协议直接借用 Heap 载荷,不自动登记对象版本读取;不能把调用成功当成已收集内容依赖。
|
||||
pub fn is_true(&self, reference: ObjectRef) -> Result<bool, VmError> {
|
||||
self.heap.get(reference)?.payload().is_true()
|
||||
}
|
||||
|
||||
/// 通用字典键准入及宿主哈希调用载荷协议,载荷可明确拒绝哈希。
|
||||
/// 当前哈希协议直接借用 Heap 载荷,不自动登记对象版本读取;不能把调用成功当成已收集内容依赖。
|
||||
pub fn hash(&self, reference: ObjectRef) -> Result<u64, VmError> {
|
||||
self.heap.get(reference)?.payload().hash(reference)
|
||||
}
|
||||
|
||||
/// VM 对象比较和字典对象键查找共用的双向协议;不运行源码函数比较方法。
|
||||
/// 此入口自身不登记双方版本;调用方其他读取可能另外收集依赖。
|
||||
pub fn compare(
|
||||
&self,
|
||||
left: ObjectRef,
|
||||
right: ObjectRef,
|
||||
operation: CompareOp,
|
||||
) -> Result<bool, VmError> {
|
||||
// 借用双方载荷;这条底层协议路径不通过 object,因此不自动收集对象版本依赖。
|
||||
let left_payload = self.heap.get(left)?.payload();
|
||||
// 借用右侧载荷,左侧不支持时才反向比较;此底层访问不自动登记内容依赖。
|
||||
let right_payload = self.heap.get(right)?.payload();
|
||||
// 左侧能处理就立即返回;Some(false) 也是已实现结果,不能继续尝试右侧。
|
||||
if let Some(result) = left_payload.compare(right_payload, operation)? {
|
||||
return Ok(result);
|
||||
}
|
||||
// 右侧收到对调后的运算(如 a < b 变为 b > a),仅在左侧返回 None 时调用。
|
||||
if let Some(result) = right_payload.compare(left_payload, operation.reflected())? {
|
||||
return Ok(result);
|
||||
}
|
||||
// 两端均未实现:相等/不等回退身份,有序比较不能凭身份定义大小。
|
||||
match operation {
|
||||
CompareOp::Eq => Ok(left == right),
|
||||
CompareOp::Ne => Ok(left != right),
|
||||
@@ -316,34 +392,39 @@ impl ObjectStore {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads a borrowed value. An Object result needs retain() if the host
|
||||
/// wants it to survive removal of the attribute that currently owns it.
|
||||
/// Vm::get_attr/宿主读取属性或继承方法;返回对象句柄是借用,跨原持有者寿命需 retain。
|
||||
pub fn get_attr(&self, reference: ObjectRef, name: &str) -> Result<Value, VmError> {
|
||||
self.lookup_attr(reference, name)
|
||||
// 此兼容入口将方法查找结果摊平成函数句柄;接收者绑定由 VM 的方法调用路径处理。
|
||||
.map(|attribute| match attribute {
|
||||
crate::types::AttributeLookup::Value(value) => value,
|
||||
crate::types::AttributeLookup::Method { function, .. } => Value::Object(function),
|
||||
})
|
||||
}
|
||||
|
||||
/// Object-valued attributes own internal strong references independently
|
||||
/// of the caller's external references. Failure leaves the field unchanged.
|
||||
/// Vm::set_attr 和宿主属性写入入口;对象值成为独立内部强边,不消耗传入句柄的外部根。
|
||||
/// 写入前核对类型/契约限制及实际存储路径,再由 Heap 执行写屏障与引用更新。
|
||||
pub fn set_attr(
|
||||
&mut self,
|
||||
reference: ObjectRef,
|
||||
name: impl Into<String>,
|
||||
value: Value,
|
||||
) -> Result<(), VmError> {
|
||||
// 名称只拥有一次,后续校验借用,最终交给 Heap 作为属性键。
|
||||
let name = name.into();
|
||||
// 已附着的名义类型不能通过普通属性写入/删除来替换。
|
||||
if name == "type" && self.type_of(reference)?.is_some() {
|
||||
return Err(VmError::UnsupportedOperation("write object type"));
|
||||
}
|
||||
// 只有开启声明检查才校验字段值;能力方法的遮蔽限制在下一段独立执行。
|
||||
if self.field_type_checks {
|
||||
self.check_field_write(reference, &name, &value)?;
|
||||
}
|
||||
// 转发链每个对象都可能声明能力方法,不能借转发终点的属性绕过方法契约。
|
||||
for object in self.attribute_path(reference)? {
|
||||
self.check_typed_method_write(object, &name)?;
|
||||
}
|
||||
// 取经过验证的最后一个对象作为实际属性存储;attribute_path 成功时必非空。
|
||||
let storage = *self
|
||||
.attribute_path(reference)?
|
||||
.last()
|
||||
@@ -351,13 +432,17 @@ impl ObjectStore {
|
||||
self.heap.set_attr(storage, name, value)
|
||||
}
|
||||
|
||||
/// Vm::del_attr/宿主删除属性;先防止删除受保护类型与声明字段,再定位实际存储。
|
||||
pub fn del_attr(&mut self, reference: ObjectRef, name: &str) -> Result<(), VmError> {
|
||||
// 开启声明检查后,字段必须维持存在;不能用删除规避字段类型约束。
|
||||
if self.field_type_checks && self.declared_field(reference, name)?.is_some() {
|
||||
return Err(VmError::UnsupportedOperation("delete declared class field"));
|
||||
}
|
||||
// 已附着的名义类型不能通过普通属性写入/删除来替换。
|
||||
if name == "type" && self.type_of(reference)?.is_some() {
|
||||
return Err(VmError::UnsupportedOperation("delete object type"));
|
||||
}
|
||||
// 取经过验证的最后一个对象作为实际属性存储;attribute_path 成功时必非空。
|
||||
let storage = *self
|
||||
.attribute_path(reference)?
|
||||
.last()
|
||||
@@ -365,16 +450,17 @@ impl ObjectStore {
|
||||
self.heap.del_attr(storage, name)
|
||||
}
|
||||
|
||||
/// Validate a borrowed object handle without taking an owned reference.
|
||||
/// 容器写入等路径验证 Value:仅对象变体需要检查句柄,不 retain,也不登记内容读取。
|
||||
pub fn validate_value(&self, value: &Value) -> Result<(), VmError> {
|
||||
// 直接值没有堆归属;对象值必须属于本堆且对应存活代际。
|
||||
if let Value::Object(reference) = value {
|
||||
self.heap.get(*reference)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Explicit full collection. Execution slots use owned roots, so values
|
||||
/// on live execution stacks participate in the same root accounting.
|
||||
/// Vm::collect_cycles/宿主显式回收没有外部根可达的环;返回回收对象数。
|
||||
/// 活跃执行槽以外部根计数,正常参与存活判定,不另建特殊的 VM 栈扫描通道。
|
||||
pub fn collect_cycles(&mut self) -> usize {
|
||||
self.heap.collect_cycles()
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
//! ObjectStore 的内置载荷入口;core::Vm 的同名方法把创建和访问委托到这里。
|
||||
//! eval 使用字典实现全局命名空间,使用 Cell 让多个闭包共享可更新的捕获槽。
|
||||
//! 返回句柄的 new_* 经 allocate 建立一份外部强引用;读取时复制 Value/ObjectRef
|
||||
//! 不会自动 retain。修改已有容器统一经 Heap::replace_payload 更新强边与变更记录。
|
||||
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
@@ -8,11 +13,16 @@ use crate::{
|
||||
};
|
||||
|
||||
impl ObjectStore {
|
||||
/// allocate 与 ObjectStore::replace_payload 在写入前调用,检查特定内置载荷的结构约束。
|
||||
/// 这里只检查函数、异常的特定关系;通用引用边的有效性仍由 Heap 的 trace 路径检查。
|
||||
pub(crate) fn validate_builtin(&self, payload: &dyn ObjectPayload) -> Result<(), VmError> {
|
||||
// 源码函数载荷必须引用代码对象,默认值和捕获槽必须与该代码的布局一致。
|
||||
if let Some(function) = payload.as_any().downcast_ref::<EndorFunctionObject>() {
|
||||
// 借用代码元数据,不执行指令,也不为该借用新增引用计数。
|
||||
let code = self
|
||||
.payload::<EndorCodeObject>(function.code(), "code")?
|
||||
.code();
|
||||
// 默认值可以只覆盖末尾部分参数;闭包槽数量则必须与自由变量数量完全一致。
|
||||
if function.defaults().len() > code.parameters().len()
|
||||
|| function.closure().len() != code.free_variables().len()
|
||||
{
|
||||
@@ -20,15 +30,20 @@ impl ObjectStore {
|
||||
"function defaults or closure do not match code",
|
||||
));
|
||||
}
|
||||
// 全局空间及可选的内置空间都必须是字典;无需读取它们的全部键值内容。
|
||||
self.dictionary_kind(function.globals())?;
|
||||
// 只有创建时捕获了内置空间才检查;None 不要求补建默认字典。
|
||||
if let Some(builtins) = function.builtins() {
|
||||
self.dictionary_kind(builtins)?;
|
||||
}
|
||||
// 每个捕获引用必须指向 Cell;槽的内容可以暂时为空,稍后再由执行器写入。
|
||||
for cell in function.closure() {
|
||||
self.payload::<EndorCellObject>(*cell, "cell")?;
|
||||
}
|
||||
}
|
||||
// 异常链只接受异常载荷,避免后续格式化或异常传播拿到其他对象种类。
|
||||
if let Some(exception) = payload.as_any().downcast_ref::<EndorBaseExceptionObject>() {
|
||||
// flatten 跳过不存在的 cause/context,存在的两条边逐一检查。
|
||||
for reference in [exception.cause(), exception.context()]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
@@ -39,6 +54,8 @@ impl ObjectStore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// VM、内置访问器和宿主通过此入口借用具体 Rust 载荷;expected 仅用于错误提示。
|
||||
/// object 会验证句柄并记录对象读取;这里的 Rust downcast 不等同于语言层名义类型判断。
|
||||
pub fn payload<T: ObjectPayload>(
|
||||
&self,
|
||||
reference: ObjectRef,
|
||||
@@ -51,14 +68,19 @@ impl ObjectStore {
|
||||
.ok_or(VmError::ObjectKindMismatch(expected))
|
||||
}
|
||||
|
||||
/// Vm::new_string 委托的堆字符串构造入口;文本存入不可变字符串载荷。
|
||||
pub fn new_string(&mut self, text: impl Into<String>) -> Result<ObjectRef, VmError> {
|
||||
self.allocate(EndorStringObject::new(text))
|
||||
}
|
||||
|
||||
/// Vm::new_code 将已构造的指令/元数据封装为代码对象;此处不启动执行。
|
||||
pub fn new_code(&mut self, code: EndorCode) -> Result<ObjectRef, VmError> {
|
||||
self.allocate(EndorCodeObject::new(code))
|
||||
}
|
||||
|
||||
/// Vm 的执行入口及 eval 创建闭包时调用,组合代码、全局空间、默认值和捕获槽。
|
||||
/// name 用作函数标识;code 指向可执行代码;closure 按该代码的自由变量顺序排列。
|
||||
/// 输入对象均借用,成功返回一份函数拥有根;代码、环境、默认值和捕获对象由函数内部边保活。
|
||||
pub fn new_function(
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
@@ -67,15 +89,20 @@ impl ObjectStore {
|
||||
defaults: Vec<Value>,
|
||||
closure: Vec<ObjectRef>,
|
||||
) -> Result<ObjectRef, VmError> {
|
||||
// 先检查全局空间,再读取它在函数创建时指定的内置命名空间。
|
||||
self.dictionary_kind(globals)?;
|
||||
// 保存的是字典身份:字典内部后续更新可见,但改写 globals.__builtins__ 不会重绑定此字段。
|
||||
let builtins = match self.dict_get_name(globals, "__builtins__")? {
|
||||
// 有该键时必须是字典对象;普通对象或标量(包括 Nil)都不能充当内置空间。
|
||||
Some(Value::Object(reference)) => {
|
||||
self.dictionary_kind(reference)?;
|
||||
Some(reference)
|
||||
}
|
||||
// 缺少键表示无内置空间,与显式存放 Nil 不同。
|
||||
None => None,
|
||||
_ => return Err(VmError::ObjectKindMismatch("dict")),
|
||||
};
|
||||
// allocate 再校验代码/槽布局,并由 trace 为这些字段引用的对象建立内部强边。
|
||||
self.allocate(EndorFunctionObject::new(
|
||||
name.into(),
|
||||
code,
|
||||
@@ -86,25 +113,30 @@ impl ObjectStore {
|
||||
))
|
||||
}
|
||||
|
||||
/// Vm::new_number 构造堆上的数值对象,与 Value::Number 的直接数值表示分开。
|
||||
pub fn new_number(&mut self, number: EndorNumber) -> Result<ObjectRef, VmError> {
|
||||
self.allocate(EndorNumberObject::new(number))
|
||||
}
|
||||
|
||||
/// Vm::new_dict 及 eval 的字典构造路径使用,初始没有条目或内部引用边。
|
||||
pub fn new_dict(&mut self) -> Result<ObjectRef, VmError> {
|
||||
self.allocate(EndorDictObject::new())
|
||||
}
|
||||
|
||||
/// Returns borrowed keys and values in insertion order.
|
||||
/// Vm::dict_items 和宿主迭代入口按插入顺序借用条目;不复制容器,也不新增外部强引用。
|
||||
/// payload 路径记录整个对象的读取,因此迭代依赖整个字典内容。
|
||||
pub fn dict_items(&self, dictionary: ObjectRef) -> Result<&[(Value, Value)], VmError> {
|
||||
Ok(self
|
||||
.payload::<EndorDictObject>(dictionary, "dict")?
|
||||
.entries())
|
||||
}
|
||||
|
||||
/// 函数校验与按名称查找共用的种类检查,只观察“仍是字典”,不观察其全部内容。
|
||||
pub(crate) fn dictionary_kind(
|
||||
&self,
|
||||
dictionary: ObjectRef,
|
||||
) -> Result<&EndorDictObject, VmError> {
|
||||
// 直接走 heap.get:若改用 payload/object,会附带过宽的整对象读取依赖。
|
||||
let dict = self
|
||||
.heap
|
||||
.get(dictionary)?
|
||||
@@ -112,25 +144,33 @@ impl ObjectStore {
|
||||
.as_any()
|
||||
.downcast_ref::<EndorDictObject>()
|
||||
.ok_or(VmError::ObjectKindMismatch("dict"))?;
|
||||
// 仅在依赖收集上下文中登记种类观察;有效句柄和 downcast 已在上面检查。
|
||||
self.heap.record_dictionary_kind(dictionary);
|
||||
Ok(dict)
|
||||
}
|
||||
/// Name lookup records both present and missing text keys. General dict_get
|
||||
/// and iteration continue to observe the entire dictionary contents.
|
||||
/// eval 查找全局名和内置名时调用:既记录命中值,也记录“此名称不存在”。
|
||||
/// 这里只查一个字典;全局未命中后再查 builtins 的顺序由 eval 决定。
|
||||
/// 通用 dict_get 和条目迭代仍采用整字典依赖。
|
||||
/// 返回 Value 中对象身份仅借用;名称观察快照也不取得对象根。
|
||||
pub fn dict_get_name(
|
||||
&self,
|
||||
dictionary: ObjectRef,
|
||||
name: &str,
|
||||
) -> Result<Option<Value>, VmError> {
|
||||
// 先确认字典种类,再以文本键查找,避免无关名称变更污染本次读取的依赖。
|
||||
let dict = self.dictionary_kind(dictionary)?;
|
||||
// clone 复制返回的 Value 表示;若其中含对象句柄,它仍是借用句柄。
|
||||
let value = self
|
||||
.dict_position(dict, &Value::Text(name.into()))?
|
||||
.map(|index| dict.entries[index].1.clone());
|
||||
// None 也要登记,使未来新增这个名称能使原先的缺失观察失效。
|
||||
self.heap.record_name(dictionary, name, &value);
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Vm::dict_get 与宿主通用键查找入口;返回 None 表示缺键,Some(Nil) 表示存了空值。
|
||||
pub fn dict_get(&self, dictionary: ObjectRef, key: &Value) -> Result<Option<Value>, VmError> {
|
||||
// 通用读取保守地观察整个字典;先检查键可哈希,再进行线性相等比较。
|
||||
let dict = self.payload::<EndorDictObject>(dictionary, "dict")?;
|
||||
self.key_hash(key)?;
|
||||
Ok(self
|
||||
@@ -138,43 +178,60 @@ impl ObjectStore {
|
||||
.map(|index| dict.entries[index].1.clone()))
|
||||
}
|
||||
|
||||
/// Vm::dict_set、eval 构造字典和写入全局名时调用;修改保持字典对象身份。
|
||||
pub fn dict_set(
|
||||
&mut self,
|
||||
dictionary: ObjectRef,
|
||||
key: Value,
|
||||
value: Value,
|
||||
) -> Result<(), VmError> {
|
||||
// 先复制载荷准备新状态;Rust clone 不 retain,提交前仍由旧字典持有原有引用边。
|
||||
let mut dict = self.payload::<EndorDictObject>(dictionary, "dict")?.clone();
|
||||
self.key_hash(&key)?;
|
||||
// 对象值必须来自本堆且仍有效;标量无需额外句柄检查。
|
||||
self.validate_value(&value)?;
|
||||
// 先找相等键再决定覆盖或追加,保证保留首个键对象及插入顺序。
|
||||
if let Some(index) = self.dict_position(&dict, &key)? {
|
||||
// Preserve the first key object and insertion position.
|
||||
// 相等键只覆盖值,保留第一次插入的键对象及其顺序,不保存本次传入的替代键。
|
||||
dict.entries[index].1 = value;
|
||||
} else {
|
||||
// 新键追加到末尾;因此遍历顺序就是首次插入顺序。
|
||||
dict.entries.push((key, value));
|
||||
}
|
||||
// Heap 在实际替换前检查新边和只读屏障,先保留新边再释放旧边,并记录 Contents 变更。
|
||||
self.heap.replace_payload(dictionary, dict)
|
||||
}
|
||||
|
||||
/// Vm::dict_delete 删除一个键;返回值说明是否存在该键,错误通过 Result 单独传播。
|
||||
/// 缺键时只完成读取并返回 false,不触发替换写屏障;已有键在只读域内删除仍会被拒绝。
|
||||
pub fn dict_delete(&mut self, dictionary: ObjectRef, key: &Value) -> Result<bool, VmError> {
|
||||
// 先复制载荷准备新状态;Rust clone 不 retain,提交前仍由旧字典持有原有引用边。
|
||||
let mut dict = self.payload::<EndorDictObject>(dictionary, "dict")?.clone();
|
||||
self.key_hash(key)?;
|
||||
// 缺键直接返回,不提交替换,因此也不会触发已有对象的写屏障或产生变更记录。
|
||||
let Some(index) = self.dict_position(&dict, key)? else {
|
||||
return Ok(false);
|
||||
};
|
||||
// 删除后其余条目的相对顺序不变;被删除键值的强边在 Heap 提交时才释放。
|
||||
dict.entries.remove(index);
|
||||
// Heap 在实际替换前检查新边和只读屏障,先保留新边再释放旧边,并记录 Contents 变更。
|
||||
self.heap.replace_payload(dictionary, dict)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Vm::dict_clear 用空载荷替换条目集合;对象身份保留,原键值的强边由 Heap 释放。
|
||||
pub fn dict_clear(&mut self, dictionary: ObjectRef) -> Result<(), VmError> {
|
||||
// 先确认字典种类;即使原本为空仍走替换路径,受相同只读保护约束。
|
||||
self.payload::<EndorDictObject>(dictionary, "dict")?;
|
||||
self.heap
|
||||
.replace_payload(dictionary, EndorDictObject::new())
|
||||
}
|
||||
|
||||
/// 字典读写共用的线性定位;条目向量没有哈希索引,hash 只负责准入校验。
|
||||
fn dict_position(&self, dict: &EndorDictObject, key: &Value) -> Result<Option<usize>, VmError> {
|
||||
// 只比较键,忽略值;相等比较失败时立即返回错误,不把失败解释为不相等。
|
||||
for (index, (existing, _)) in dict.entries.iter().enumerate() {
|
||||
// 返回第一个相等键的位置,供读值、覆盖值或删除整条记录使用。
|
||||
if self.key_equal(existing, key)? {
|
||||
return Ok(Some(index));
|
||||
}
|
||||
@@ -182,23 +239,31 @@ impl ObjectStore {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// 通用字典操作检查键是否支持哈希;返回值当前不用于条目定位,也不用于持久化。
|
||||
fn key_hash(&self, value: &Value) -> Result<u64, VmError> {
|
||||
// 对象键先验证归属和存活,随后才允许访问它的载荷哈希实现。
|
||||
self.validate_value(value)?;
|
||||
// 直接值使用标准哈希器;对象委托各自实现,可由载荷拒绝不可哈希种类。
|
||||
let mut hasher = DefaultHasher::new();
|
||||
// 标量按已定义类别散列;对象委托载荷哈希,尚无数值哈希协议时拒绝 Number。
|
||||
match value {
|
||||
Value::Nil => "nil".hash(&mut hasher),
|
||||
Value::Bool(value) => value.hash(&mut hasher),
|
||||
Value::Text(value) => value.hash(&mut hasher),
|
||||
// 数值直接表示尚未定义字典哈希语义,因此明确拒绝,不隐式转换成文本或整数。
|
||||
Value::Number(_) => return Err(VmError::UnsupportedOperation("numeric hash")),
|
||||
Value::Object(reference) => return self.hash(*reference),
|
||||
}
|
||||
Ok(hasher.finish())
|
||||
}
|
||||
|
||||
/// dict_position 的键比较:直接文本与堆字符串按内容互通,其余按对象协议或 Value 比较。
|
||||
fn key_equal(&self, left: &Value, right: &Value) -> Result<bool, VmError> {
|
||||
// 将两种字符串表示临时取成拥有的 String;其他对象返回 None,仍保留后续比较路径。
|
||||
let text = |value: &Value| -> Result<Option<String>, VmError> {
|
||||
Ok(match value {
|
||||
Value::Text(text) => Some(text.clone()),
|
||||
// 借用载荷尝试字符串 downcast;非字符串不是错误,无效句柄才会传播错误。
|
||||
Value::Object(reference) => self
|
||||
.object(*reference)?
|
||||
.payload()
|
||||
@@ -208,9 +273,11 @@ impl ObjectStore {
|
||||
_ => None,
|
||||
})
|
||||
};
|
||||
// 两边都是文本才做内容相等;不做 Unicode 规范化或大小写折叠。
|
||||
if let (Some(left), Some(right)) = (text(left)?, text(right)?) {
|
||||
return Ok(left == right);
|
||||
}
|
||||
// 两个对象走 Store::compare 的正向/反向协议及身份回退;其他组合使用 Value 的相等实现。
|
||||
match (left, right) {
|
||||
(Value::Object(left), Value::Object(right)) => {
|
||||
self.compare(*left, *right, CompareOp::Eq)
|
||||
@@ -219,11 +286,13 @@ impl ObjectStore {
|
||||
}
|
||||
}
|
||||
|
||||
/// Vm::new_cell 在 eval 把局部槽提升为闭包共享槽时调用;None 表示尚未初始化。
|
||||
pub fn new_cell(&mut self, value: Option<Value>) -> Result<ObjectRef, VmError> {
|
||||
self.allocate(EndorCellObject::new(value))
|
||||
}
|
||||
|
||||
/// Reads a borrowed value; an empty cell is not Value::Nil.
|
||||
/// Vm::cell_get 供自由变量读取及赋值前读取旧值使用;复制出的对象句柄仍是借用。
|
||||
/// 空槽返回 EmptyCell;已初始化为 Some(Value::Nil) 则正常返回 Nil。
|
||||
pub fn cell_get(&self, cell: ObjectRef) -> Result<Value, VmError> {
|
||||
self.payload::<EndorCellObject>(cell, "cell")?
|
||||
.value()
|
||||
@@ -231,13 +300,18 @@ impl ObjectStore {
|
||||
.ok_or(VmError::EmptyCell)
|
||||
}
|
||||
|
||||
/// Vm::cell_set 在自由变量赋值时调用;所有共享该 Cell 的闭包随后读取相同的新值。
|
||||
pub fn cell_set(&mut self, cell: ObjectRef, value: Value) -> Result<(), VmError> {
|
||||
// 确认是 Cell 后由 Heap 检查引用边和只读屏障,更新时保留 Cell 身份。
|
||||
self.payload::<EndorCellObject>(cell, "cell")?;
|
||||
self.heap
|
||||
.replace_payload(cell, EndorCellObject::new(Some(value)))
|
||||
}
|
||||
|
||||
/// Vm::cell_clear 在删除捕获变量时调用;恢复未初始化状态,并释放旧值的内部强边。
|
||||
/// 即使槽原本为空仍提交替换,不能通过重复清空绕过只读屏障。
|
||||
pub fn cell_clear(&mut self, cell: ObjectRef) -> Result<(), VmError> {
|
||||
// 确认是 Cell 后由 Heap 检查引用边和只读屏障,更新时保留 Cell 身份。
|
||||
self.payload::<EndorCellObject>(cell, "cell")?;
|
||||
self.heap.replace_payload(cell, EndorCellObject::default())
|
||||
}
|
||||
|
||||
+336
-4
File diff suppressed because it is too large
Load Diff
@@ -1,20 +1,27 @@
|
||||
//! number.rs 的公开 API 回归:比较数学值与存储表示分开,且构造/运算使用不同预算。
|
||||
use objects::{ArithmeticOp, CompareOp, EndorNumber, EndorReal};
|
||||
/// 测试辅助:用 numeric_compare 断言数学相等,不能用 Rust Eq 比较不同表示。
|
||||
fn equal(a: &EndorNumber, b: &EndorNumber) {
|
||||
assert!(a.numeric_compare(b, CompareOp::Eq).unwrap());
|
||||
}
|
||||
#[test]
|
||||
/// 测试框架调用:验证精确跨表示求和、整数溢出提升、有理数等价和大整数混合浮点比较。
|
||||
fn exact_real_arithmetic_keeps_large_values_and_cross_representation_equivalence() {
|
||||
// 十进制保留尾零,后续与分数求和应保持精确数学值。
|
||||
let tenth = EndorNumber::decimal("0.10").unwrap();
|
||||
// 分数构造不要求约分,算术结果仍应等价于精确 0.3。
|
||||
let fifth = EndorNumber::rational("2", "10").unwrap();
|
||||
equal(
|
||||
&tenth.arithmetic(&fifth, ArithmeticOp::Add).unwrap(),
|
||||
&EndorNumber::decimal("0.3").unwrap(),
|
||||
);
|
||||
// 机器最大整数加一必须提升到大整数,不能溢出或丢精度。
|
||||
let large = EndorNumber::integer("9223372036854775807").unwrap();
|
||||
equal(
|
||||
&large.arithmetic(&1_i64.into(), ArithmeticOp::Add).unwrap(),
|
||||
&EndorNumber::integer("9223372036854775808").unwrap(),
|
||||
);
|
||||
// 最小负整数转换需单独处理其超出正 i64 范围的幅值。
|
||||
let min = EndorNumber::integer("-9223372036854775808").unwrap();
|
||||
assert_eq!(min.as_i64(), Some(i64::MIN));
|
||||
equal(
|
||||
@@ -33,12 +40,15 @@ fn exact_real_arithmetic_keeps_large_values_and_cross_representation_equivalence
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
/// 测试框架调用:验证 IEEE 符号零/NaN、零除/非有限结果拒绝,以及巨大指数可存不可运算。
|
||||
fn ieee_signed_zero_nan_and_operation_limits_are_explicit() {
|
||||
// 浮点取负翻符号位,+0 应变 -0 而不是规范成整数零。
|
||||
let negative = EndorNumber::binary_float(0.).negated().unwrap();
|
||||
assert_eq!(
|
||||
negative.as_real(),
|
||||
Some(&EndorReal::BinaryFloat((-0.0_f64).to_bits()))
|
||||
);
|
||||
// 同一 NaN 数学比较也不等于自身,Ne 必须为真。
|
||||
let nan = EndorNumber::binary_float(f64::NAN);
|
||||
assert!(!nan.numeric_compare(&nan, CompareOp::Eq).unwrap());
|
||||
assert!(nan.numeric_compare(&nan, CompareOp::Ne).unwrap());
|
||||
@@ -52,6 +62,7 @@ fn ieee_signed_zero_nan_and_operation_limits_are_explicit() {
|
||||
.arithmetic(&2_i64.into(), ArithmeticOp::Multiply)
|
||||
.is_err()
|
||||
);
|
||||
// 极长指数语法合法且可原样显示,但实际运算要触发预算保护。
|
||||
let huge = EndorNumber::decimal("1e99999999999999999").unwrap();
|
||||
assert_eq!(huge.to_string(), "1e99999999999999999");
|
||||
assert!(huge.arithmetic(&1_i64.into(), ArithmeticOp::Add).is_err());
|
||||
|
||||
@@ -1,48 +1,73 @@
|
||||
//! Rust 测试框架调用的属性存储回归,不执行源码指令。
|
||||
//! Proxy 让字段存储与名义身份分开;ReflectiveType 触发类型查询对自身属性的重入。
|
||||
//! 断言覆盖托管边生命周期、失败不修改版本、能力方法不得被实例属性遮蔽。
|
||||
|
||||
use objects::{ObjectPayload, ObjectRef, ObjectStore, TypeConstructor, Value, VmError};
|
||||
|
||||
#[derive(Debug)]
|
||||
/// 一个拥有后端存储引用的门面,type_target 保持默认 None。
|
||||
struct Proxy(ObjectRef);
|
||||
impl ObjectPayload for Proxy {
|
||||
/// Store 下转型测试载荷,不进行语言类型判定。
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
/// 测试错误/诊断使用的类别标签。
|
||||
fn kind_name(&self) -> &'static str {
|
||||
"proxy"
|
||||
}
|
||||
/// Heap 分配/替换 Proxy 时记录后端存储边,释放门面或改指后端时归还该边。
|
||||
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
|
||||
visit(self.0);
|
||||
}
|
||||
/// Store::attribute_path 追踪实际字段存储,不改变代理对象头部类型。
|
||||
fn attribute_target(&self, _: &ObjectStore) -> Result<Option<ObjectRef>, VmError> {
|
||||
Ok(Some(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证代理改指新存储后,名义类型不变、旧值释放、读写删除转到新终点。
|
||||
fn storage_retargeting_preserves_type_identity_and_releases_old_state() {
|
||||
// 建立独立堆隔离场景,所有分配根在测试末尾明确归还。
|
||||
let mut store = ObjectStore::new().unwrap();
|
||||
// 自举 Object/Type,各有一份外部根;其图内自环需最终 collect_cycles。
|
||||
let roots = store.bootstrap_types().unwrap();
|
||||
// 给代理/反射实例附着真实名义类型,字段转发不应替换它。
|
||||
let class = store
|
||||
.new_type("Proxy", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// 普通 Record 作为无类型后端存储,只有代理持有时也须存活。
|
||||
let storage = store.new_object().unwrap();
|
||||
// 旧属性值先由测试根持有,之后转由字段强边持有。
|
||||
let old_value = store.new_object().unwrap();
|
||||
// 弱句柄不持有旧值,替换存储后可观测其真正释放。
|
||||
let weak_value = store.downgrade(old_value).unwrap();
|
||||
store
|
||||
.set_attr(storage, "value", Value::Object(old_value))
|
||||
.unwrap();
|
||||
// 归还外部根,留下 storage.value 一条强边。
|
||||
store.release(old_value).unwrap();
|
||||
// Proxy 的 trace 持有后端,字段实际写入后端但类型留在 Proxy 头部。
|
||||
let proxy = store.allocate(Proxy(storage)).unwrap();
|
||||
store.attach_type(proxy, class).unwrap();
|
||||
// 归还后端外部根后仍由 Proxy 的载荷边维持存活。
|
||||
store.release(storage).unwrap();
|
||||
// 确认路径同时包含门面和终点,不只返回头部类型对象。
|
||||
assert_eq!(store.attribute_path(proxy).unwrap(), vec![proxy, storage]);
|
||||
// 保留 type 属性查询代理自身名义类型,不跟随后端身份变化。
|
||||
assert_eq!(store.get_attr(proxy, "type").unwrap(), Value::Object(class));
|
||||
assert_eq!(
|
||||
store.get_attr(proxy, "value").unwrap(),
|
||||
Value::Object(old_value)
|
||||
);
|
||||
// 新后端先由根持有,替换成功后 Proxy 自己持有一条边。
|
||||
let next = store.new_object().unwrap();
|
||||
// 受控替换重新登记边,旧后端及其唯一持有的属性值应被级联释放。
|
||||
store.replace_payload(proxy, Proxy(next)).unwrap();
|
||||
// 归还新后端根,后续访问证明其仍由 Proxy 强边保留。
|
||||
store.release(next).unwrap();
|
||||
// 旧值没有残余根,弱升级必须返回 None。
|
||||
assert!(store.upgrade(weak_value).unwrap().is_none());
|
||||
store
|
||||
.set_attr(proxy, "value", Value::Number(9_i64.into()))
|
||||
@@ -51,48 +76,67 @@ fn storage_retargeting_preserves_type_identity_and_releases_old_state() {
|
||||
store.get_attr(next, "value").unwrap(),
|
||||
Value::Number(9_i64.into())
|
||||
);
|
||||
// 通过代理删除应作用于新后端,不只是门面自己的属性表。
|
||||
store.del_attr(proxy, "value").unwrap();
|
||||
assert!(store.get_attr(next, "value").is_err());
|
||||
// 逐项释放测试拥有根,不手动 release 内部边,避免重复归还。
|
||||
for object in [proxy, class, roots.object, roots.type_type] {
|
||||
store.release(object).unwrap();
|
||||
}
|
||||
// 回收类型自举环及其他已无外部根的环,最终堆应为空。
|
||||
store.collect_cycles();
|
||||
assert_eq!(store.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证属性转发环和异堆身份被拒绝,失败不产生新的变更版本。
|
||||
fn cyclic_and_foreign_attribute_storage_fail_without_mutating_fields() {
|
||||
// 建立独立堆隔离场景,所有分配根在测试末尾明确归还。
|
||||
let mut store = ObjectStore::new().unwrap();
|
||||
// 普通 Record 作为无类型后端存储,只有代理持有时也须存活。
|
||||
let storage = store.new_object().unwrap();
|
||||
// Proxy 的 trace 持有后端,字段实际写入后端但类型留在 Proxy 头部。
|
||||
let proxy = store.allocate(Proxy(storage)).unwrap();
|
||||
// 注入合法托管自环但非法属性转发环;属性路径必须检测,不无限循环。
|
||||
store.replace_payload(proxy, Proxy(proxy)).unwrap();
|
||||
// 记录失败读写前版本,路径检查失败不得修改对象。
|
||||
let version = store.change_version(proxy).unwrap();
|
||||
assert!(store.get_attr(proxy, "field").is_err());
|
||||
assert!(store.set_attr(proxy, "field", Value::Nil).is_err());
|
||||
assert_eq!(store.change_version(proxy).unwrap(), version);
|
||||
// 异堆即使槽号相同也必须报 ForeignHandle,不当成普通缺字段。
|
||||
let other = ObjectStore::new().unwrap();
|
||||
assert_eq!(other.attribute_path(proxy), Err(VmError::ForeignHandle));
|
||||
// Proxy 已改为自引用,不再持有 storage;归还其唯一外部根即可回收后端。
|
||||
store.release(storage).unwrap();
|
||||
store.release(proxy).unwrap();
|
||||
// 外部根释放后只有 Proxy 自环残留,显式回收恰好一个对象。
|
||||
assert_eq!(store.collect_cycles(), 1);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// 拥有一个 Cell,type_target 读 Cell 指回自己,测试递归属性查询。
|
||||
struct ReflectiveType(ObjectRef);
|
||||
impl ObjectPayload for ReflectiveType {
|
||||
/// Store 下转型测试载荷,不进行语言类型判定。
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
/// 测试错误/诊断使用的类别标签。
|
||||
fn kind_name(&self) -> &'static str {
|
||||
"reflective-type"
|
||||
}
|
||||
/// 反射对象自行存字段,允许 selector 查询从公共属性表读取。
|
||||
fn allows_attributes(&self) -> bool {
|
||||
true
|
||||
}
|
||||
/// Heap 分配反射载荷时记录 self_cell 强边,与稍后 Cell 反向持边构成测试环。
|
||||
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
|
||||
visit(self.0);
|
||||
}
|
||||
/// Store::type_of 调用时通过 Cell 找自己并读 selector,专门制造属性检查重入。
|
||||
fn type_target(&self, store: &ObjectStore) -> Result<Option<ObjectRef>, VmError> {
|
||||
// Cell 初始 Nil 时不重入,安装自引用后读自身 selector 触发保护守卫路径。
|
||||
if let Value::Object(receiver) = store.cell_get(self.0)? {
|
||||
store.get_attr(receiver, "selector")?;
|
||||
}
|
||||
@@ -101,8 +145,10 @@ impl ObjectPayload for ReflectiveType {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// 无捕获且不修改存储的能力方法测试实现;完整签名供声明/调用门禁读取。
|
||||
struct TypedCheck;
|
||||
impl objects::NativeCallable for TypedCheck {
|
||||
/// 为 check 声明显式 self、Number 返回和 ReadOnly 效果,供能力契约核对。
|
||||
fn signature(&self) -> Option<objects::CallSignature> {
|
||||
Some(objects::CallSignature::exact(
|
||||
vec![objects::AnnotationType::Any],
|
||||
@@ -110,6 +156,7 @@ impl objects::NativeCallable for TypedCheck {
|
||||
objects::CallEffect::ReadOnly,
|
||||
))
|
||||
}
|
||||
/// 原生分派返回直接整数,无对象结果需要额外保留;本文件主要验证契约门禁。
|
||||
fn invoke(
|
||||
&self,
|
||||
_: &mut objects::NativeContext<'_>,
|
||||
@@ -120,12 +167,17 @@ impl objects::NativeCallable for TypedCheck {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证能力声明/调用各入口均拒绝同名字段遮蔽,且递归守卫失败后正确清理。
|
||||
fn typed_method_storage_cannot_diverge_from_class_dispatch() {
|
||||
// 建立独立堆隔离场景,所有分配根在测试末尾明确归还。
|
||||
let mut store = ObjectStore::new().unwrap();
|
||||
// 自举 Object/Type,各有一份外部根;其图内自环需最终 collect_cycles。
|
||||
let roots = store.bootstrap_types().unwrap();
|
||||
// 给代理/反射实例附着真实名义类型,字段转发不应替换它。
|
||||
let class = store
|
||||
.new_type("Checked", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// 上下文原生函数发布完整签名;类方法表还会单独拥有它的强边。
|
||||
let function = store
|
||||
.allocate(objects::ContextNativeFunction::new(
|
||||
"check",
|
||||
@@ -134,6 +186,7 @@ fn typed_method_storage_cannot_diverge_from_class_dispatch() {
|
||||
))
|
||||
.unwrap();
|
||||
store.define_method(class, "check", function).unwrap();
|
||||
// 能力要求与原生方法完整契约一致,包含显式 self 参数。
|
||||
let capability = store
|
||||
.new_typed_trait(
|
||||
"Checked",
|
||||
@@ -147,19 +200,28 @@ fn typed_method_storage_cannot_diverge_from_class_dispatch() {
|
||||
}],
|
||||
)
|
||||
.unwrap();
|
||||
// 普通 Record 作为无类型后端存储,只有代理持有时也须存活。
|
||||
let storage = store.new_object().unwrap();
|
||||
// Proxy 的 trace 持有后端,字段实际写入后端但类型留在 Proxy 头部。
|
||||
let proxy = store.allocate(Proxy(storage)).unwrap();
|
||||
store.attach_type(proxy, class).unwrap();
|
||||
// 直接写无类型后端可以造出对代理而言不符合能力的实例,后续门禁应拒绝。
|
||||
store.set_attr(storage, "check", Value::Nil).unwrap();
|
||||
// 能力声明失败应保持定义版本和声明集合不变。
|
||||
let version = store.change_version(class).unwrap();
|
||||
assert!(matches!(
|
||||
// 当前后端仍有遮蔽字段,能力声明必须在状态提交前失败。
|
||||
store.implement_trait(class, capability),
|
||||
Err(VmError::TraitViolation { .. })
|
||||
));
|
||||
assert_eq!(store.change_version(class).unwrap(), version);
|
||||
// 失败声明不能留下一半能力关系。
|
||||
assert!(!store.implements_trait(class, capability).unwrap());
|
||||
// 删除遮蔽字段恢复合法存储,后续能力声明/查询应重新成功。
|
||||
store.del_attr(storage, "check").unwrap();
|
||||
// 存储冲突消除后能力声明才允许提交。
|
||||
store.implement_trait(class, capability).unwrap();
|
||||
// 代理受控写入在真正触及后端前失败,后端版本不得增加。
|
||||
let version = store.change_version(storage).unwrap();
|
||||
assert!(matches!(
|
||||
store.set_attr(proxy, "check", Value::Nil),
|
||||
@@ -167,36 +229,44 @@ fn typed_method_storage_cannot_diverge_from_class_dispatch() {
|
||||
));
|
||||
assert_eq!(store.change_version(storage).unwrap(), version);
|
||||
assert!(store.supports(proxy, capability).unwrap());
|
||||
// A trusted host can access the untyped backing record directly. Every
|
||||
// language/host dispatch gate must detect the resulting invalid instance.
|
||||
// 可信宿主能直接写无类型后端;supports/get_attr/bind_attr/调用检查/直接存储检查都必须发现失效实例。
|
||||
// 直接写无类型后端可以造出对代理而言不符合能力的实例,后续门禁应拒绝。
|
||||
store.set_attr(storage, "check", Value::Nil).unwrap();
|
||||
assert!(matches!(
|
||||
store.supports(proxy, capability),
|
||||
Err(VmError::TraitViolation { .. })
|
||||
));
|
||||
// 后端已出现遮蔽方法的冲突字段时,即使只读属性查询也必须拒绝不合法的能力存储。
|
||||
assert!(matches!(
|
||||
store.get_attr(proxy, "check"),
|
||||
Err(VmError::TraitViolation { .. })
|
||||
));
|
||||
// 绑定方法入口不能绕过同一存储校验,不能先创建可调用绑定再放过冲突。
|
||||
assert!(matches!(
|
||||
store.bind_attr(proxy, "check"),
|
||||
Err(VmError::TraitViolation { .. })
|
||||
));
|
||||
// 直接方法契约检查也拒绝代理后端冲突,防止跳过属性查找后绕过门禁。
|
||||
assert!(matches!(
|
||||
store.check_method_contract_call(function, proxy, false),
|
||||
Err(VmError::TraitViolation { .. })
|
||||
));
|
||||
// 显式实例存储检查应给出同类能力违规,保持各调用入口的检查一致。
|
||||
assert!(matches!(
|
||||
store.check_typed_instance_storage(proxy),
|
||||
Err(VmError::TraitViolation { .. })
|
||||
));
|
||||
// 另建已有冲突字段的记录,验证 attach_type 也守住能力存储约束。
|
||||
let candidate = store.new_object().unwrap();
|
||||
// 先放同名字段再附着类型,不能绕过“创建后写入”的校验。
|
||||
store.set_attr(candidate, "check", Value::Nil).unwrap();
|
||||
assert!(matches!(
|
||||
store.attach_type(candidate, class),
|
||||
Err(VmError::TraitViolation { .. })
|
||||
));
|
||||
// 附着失败不能留下部分头部类型关系。
|
||||
assert_eq!(store.type_of(candidate).unwrap(), None);
|
||||
// 删除遮蔽字段恢复合法存储,后续能力声明/查询应重新成功。
|
||||
store.del_attr(storage, "check").unwrap();
|
||||
assert!(store.supports(proxy, capability).unwrap());
|
||||
assert_eq!(
|
||||
@@ -206,7 +276,9 @@ fn typed_method_storage_cannot_diverge_from_class_dispatch() {
|
||||
store
|
||||
.check_method_contract_call(function, proxy, false)
|
||||
.unwrap();
|
||||
// 用托管 Cell 保存反射对象,初始 Nil 避免初始化时就递归查询未设字段。
|
||||
let self_cell = store.new_cell(Some(Value::Nil)).unwrap();
|
||||
// 载荷持有 Cell,Cell 稍后反向持有对象,形成可回收的托管环。
|
||||
let reflective = store.allocate(ReflectiveType(self_cell)).unwrap();
|
||||
store
|
||||
.set_attr(reflective, "selector", Value::Bool(true))
|
||||
@@ -214,24 +286,29 @@ fn typed_method_storage_cannot_diverge_from_class_dispatch() {
|
||||
store
|
||||
.cell_set(self_cell, Value::Object(reflective))
|
||||
.unwrap();
|
||||
// 附着能力类型期间查询 selector 会重入检查,守卫应避免无限递归。
|
||||
store.attach_type(reflective, class).unwrap();
|
||||
assert_eq!(
|
||||
store.get_attr(reflective, "selector").unwrap(),
|
||||
Value::Bool(true)
|
||||
);
|
||||
assert!(store.supports(reflective, capability).unwrap());
|
||||
// 带反射检查的载荷第一次非法写入仍须拒绝,递归防护不能误跳过最外层约束。
|
||||
assert!(matches!(
|
||||
store.set_attr(reflective, "check", Value::Nil),
|
||||
Err(VmError::TraitViolation { .. })
|
||||
));
|
||||
// A failed guarded check must not suppress the next one.
|
||||
// 首次失败必须通过 RAII 移除标记;第二次同样的非法写入仍应失败,不能被误当成递归跳过。
|
||||
assert!(matches!(
|
||||
store.set_attr(reflective, "check", Value::Nil),
|
||||
Err(VmError::TraitViolation { .. })
|
||||
));
|
||||
// 归还自引用图外部根;环内边仍在,统一由最终回收处理。
|
||||
store.release(reflective).unwrap();
|
||||
// Cell 的外部根也归还,不能仅释放反射门面而留下测试根。
|
||||
store.release(self_cell).unwrap();
|
||||
|
||||
// 逐项释放测试拥有根,不手动 release 内部边,避免重复归还。
|
||||
for object in [
|
||||
candidate,
|
||||
proxy,
|
||||
@@ -244,6 +321,7 @@ fn typed_method_storage_cannot_diverge_from_class_dispatch() {
|
||||
] {
|
||||
store.release(object).unwrap();
|
||||
}
|
||||
// 回收类型自举环及其他已无外部根的环,最终堆应为空。
|
||||
store.collect_cycles();
|
||||
assert_eq!(store.object_count(), 0);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 独立 ObjectStore 回归场景,由 Rust 测试框架调用;不需要 VM 执行指令。
|
||||
//! 场景覆盖根/边所有权、失败原子性与读取作用域。
|
||||
use std::sync::Arc;
|
||||
|
||||
use objects::{
|
||||
@@ -6,13 +8,21 @@ use objects::{
|
||||
};
|
||||
|
||||
#[test]
|
||||
/// 验证容器替换保持对象地址、跨堆边被拒绝且失去外部根的互引环可回收。
|
||||
fn standalone_storage_preserves_identity_validates_edges_and_collects_cycles() {
|
||||
// 每个测试建立独立存储,避免对象/作用域状态跨场景污染。
|
||||
let mut store = ObjectStore::new().unwrap();
|
||||
// 独立堆用于证明句柄归属不能仅以槽号判断。
|
||||
let mut other = ObjectStore::new().unwrap();
|
||||
// 字典与下面 Cell 各自有一份外部根,Cell 另外持有字典强边。
|
||||
let dictionary = store.new_dict().unwrap();
|
||||
// Cell 初始拥有字典内部边,后续反向写入形成环。
|
||||
let cell = store.new_cell(Some(Value::Object(dictionary))).unwrap();
|
||||
// 异堆对象保持自身外部根,不应被本堆写入接管。
|
||||
let foreign = other.new_object().unwrap();
|
||||
// 保存对象实体地址,验证替换载荷不会移动外层稳定 Box。
|
||||
let address = store.object(dictionary).unwrap() as *const _;
|
||||
// 字典键使用直接文本,不产生托管强边。
|
||||
let key = Value::Text("cell".into());
|
||||
|
||||
store
|
||||
@@ -32,15 +42,21 @@ fn standalone_storage_preserves_identity_validates_edges_and_collects_cycles() {
|
||||
store.release(cell).unwrap();
|
||||
assert_eq!(store.collect_cycles(), 2);
|
||||
assert_eq!(store.object_count(), 0);
|
||||
// 独立 Store 释放完拥有图后,原 Cell 借用句柄也应失效。
|
||||
assert!(matches!(store.object(cell), Err(VmError::InvalidHandle)));
|
||||
other.release(foreign).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证无效函数构造不分配对象,两个函数共享同一执行数据并通过代码边保留常量。
|
||||
fn standalone_function_factories_validate_and_own_shared_executable_data() {
|
||||
// 每个测试建立独立存储,避免对象/作用域状态跨场景污染。
|
||||
let mut store = ObjectStore::new().unwrap();
|
||||
// 字符串最初由测试根持有,随后代码常量也建立内部边。
|
||||
let constant = store.new_string("kept by code").unwrap();
|
||||
// 函数全局空间在两个函数载荷中各持有一条内部边。
|
||||
let globals = store.new_dict().unwrap();
|
||||
// 最小已验证代码只装载常量再返回,不在本测试执行。
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadConst, 0),
|
||||
@@ -50,7 +66,9 @@ fn standalone_function_factories_validate_and_own_shared_executable_data() {
|
||||
CodeLayout::default(),
|
||||
)
|
||||
.unwrap();
|
||||
// 分配代码载荷后常量由其 trace 强边持有。
|
||||
let code = store.new_code(code).unwrap();
|
||||
// 记录失败构造前对象数,断言失败不得泄漏半成品。
|
||||
let before = store.object_count();
|
||||
assert_eq!(
|
||||
store.new_function("bad", code, globals, vec![Value::Nil], vec![]),
|
||||
@@ -60,50 +78,67 @@ fn standalone_function_factories_validate_and_own_shared_executable_data() {
|
||||
);
|
||||
assert_eq!(store.object_count(), before);
|
||||
assert_eq!(store.ref_count(code).unwrap(), 1);
|
||||
// 第一个函数持有代码和全局字典,不复制代码对象。
|
||||
let first = store
|
||||
.new_function("first", code, globals, vec![], vec![])
|
||||
.unwrap();
|
||||
// 第二个函数独立持有相同代码/全局边,供释放顺序验证。
|
||||
let second = store
|
||||
.new_function("second", code, globals, vec![], vec![])
|
||||
.unwrap();
|
||||
// 借用第一个函数记录的代码句柄,不新增根。
|
||||
let first_code = store
|
||||
.payload::<EndorFunctionObject>(first, "function")
|
||||
.unwrap()
|
||||
.code();
|
||||
// 借用第二个函数代码句柄,与第一函数应指向相同执行数据。
|
||||
let second_code = store
|
||||
.payload::<EndorFunctionObject>(second, "function")
|
||||
.unwrap()
|
||||
.code();
|
||||
// 取得 Rust Arc 只用于指针共享验证,它不替代托管代码根。
|
||||
let first_executable = store
|
||||
.payload::<EndorCodeObject>(first_code, "code")
|
||||
.unwrap()
|
||||
.shared_code();
|
||||
// 第二份 Arc 与第一份应共享同一 EndorCode 分配。
|
||||
let second_executable = store
|
||||
.payload::<EndorCodeObject>(second_code, "code")
|
||||
.unwrap()
|
||||
.shared_code();
|
||||
assert!(Arc::ptr_eq(&first_executable, &second_executable));
|
||||
// 先释放 Rust 共享引用,再测试托管函数/代码边生命周期。
|
||||
drop(first_executable);
|
||||
drop(second_executable);
|
||||
// 归还测试拥有根后,常量仍应通过两个函数到代码的图存活。
|
||||
store.release(constant).unwrap();
|
||||
store.release(code).unwrap();
|
||||
store.release(globals).unwrap();
|
||||
assert_eq!(store.str(constant).unwrap(), "kept by code");
|
||||
// 移除一函数仅减一条代码边,另一函数仍保留代码和常量。
|
||||
store.release(first).unwrap();
|
||||
assert_eq!(store.ref_count(code).unwrap(), 1);
|
||||
// 最后一个函数释放后,代码/全局/常量无根链一起回收。
|
||||
store.release(second).unwrap();
|
||||
assert_eq!(store.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证已有对象受保护、新结果可写、嵌套层会保护外层新对象及退出后恢复权限。
|
||||
fn read_only_scopes_block_existing_container_and_type_writes_but_allow_fresh_results() {
|
||||
// 每个测试建立独立存储,避免对象/作用域状态跨场景污染。
|
||||
let mut store = objects::ObjectStore::new().unwrap();
|
||||
// 显式自举两个类型根,清理时需归还它们并收集类型图环。
|
||||
let roots = store.bootstrap_types().unwrap();
|
||||
// 此记录在进入只读前分配,属性和类型附着都应被写屏障保护。
|
||||
let record = store.new_object().unwrap();
|
||||
// 已有字典受同一只读屏障保护,不只保护普通属性。
|
||||
let dict = store.new_dict().unwrap();
|
||||
// 进入只读前创建的 Cell 同样属于受保护堆对象,不能靠内部值更新绕过写屏障。
|
||||
let cell = store
|
||||
.new_cell(Some(objects::Value::Number(1_i64.into())))
|
||||
.unwrap();
|
||||
// 进入时快照已有对象,之后分配的 fresh 不在本层保护集合。
|
||||
let scope = store.begin_read_only();
|
||||
assert!(
|
||||
store
|
||||
@@ -129,57 +164,76 @@ fn read_only_scopes_block_existing_container_and_type_writes_but_allow_fresh_res
|
||||
store.cell_get(cell).unwrap(),
|
||||
objects::Value::Number(1_i64.into())
|
||||
);
|
||||
// 在只读调用内分配的新结果可初始化属性。
|
||||
let fresh = store.new_object().unwrap();
|
||||
store.set_attr(fresh, "value", objects::Value::Nil).unwrap();
|
||||
// Record 不原地赋值,只读引用重绑定可以返回 false 而不修改旧对象。
|
||||
assert!(!store.assign_from(record, fresh).unwrap());
|
||||
// 内层重新快照所有存活对象,fresh 此时也受保护;结束必须按 LIFO。
|
||||
let nested = store.begin_read_only();
|
||||
assert!(
|
||||
store
|
||||
.set_attr(fresh, "value", objects::Value::Bool(true))
|
||||
.is_err()
|
||||
);
|
||||
// 退出内层后 fresh 又不受外层快照保护。
|
||||
store.end_read_only(nested).unwrap();
|
||||
store
|
||||
.set_attr(fresh, "value", objects::Value::Bool(true))
|
||||
.unwrap();
|
||||
// 结束外层只读同时取得借用读取集合,Cell 读取应被记录。
|
||||
let reads = store.end_read_only(scope).unwrap();
|
||||
assert!(reads.contains(&cell));
|
||||
store
|
||||
.set_attr(record, "value", objects::Value::Nil)
|
||||
.unwrap();
|
||||
// 逐项归还测试拥有根;类型/实例内部边由 Heap 清理,不手动归还。
|
||||
for object in [record, dict, cell, fresh, roots.object, roots.type_type] {
|
||||
store.release(object).unwrap();
|
||||
}
|
||||
// 显式回收自举类型图中的环,最后验证堆为空。
|
||||
store.collect_cycles();
|
||||
assert_eq!(store.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 验证子区间重复读取独立记录、观察传播到外层,错误结束顺序不损坏状态。
|
||||
fn read_tracking_regions_repeat_shared_reads_and_preserve_enclosing_dependencies() {
|
||||
// 每个测试建立独立存储,避免对象/作用域状态跨场景污染。
|
||||
let mut store = ObjectStore::new().unwrap();
|
||||
// 两个独立对象用于验证读取传播,不建立彼此引用。
|
||||
let first = store.new_object().unwrap();
|
||||
// 第二对象仅在内层首次读取,外层集合也必须包含它。
|
||||
let second = store.new_object().unwrap();
|
||||
assert!(store.begin_read_tracking().is_err());
|
||||
// 进入时把 first、second 列为已有对象,后面的读取才会成为本层依赖。
|
||||
let scope = store.begin_read_only();
|
||||
// 外层区间开始后,其内所有读取都要包含在最终结果中。
|
||||
let outer = store.begin_read_tracking().unwrap();
|
||||
store.object(first).unwrap();
|
||||
// 子区间嵌套不另建对象保护快照;其读取同时传播给 outer,结束仍须按 LIFO。
|
||||
let nested = store.begin_read_tracking().unwrap();
|
||||
store.object(first).unwrap();
|
||||
store.object(second).unwrap();
|
||||
// 内层未结束时不能弹出外层,失败后应仍能正确结束两层。
|
||||
assert!(store.end_read_tracking(outer).is_err());
|
||||
assert_eq!(
|
||||
store.end_read_tracking(nested).unwrap(),
|
||||
vec![first, second]
|
||||
);
|
||||
assert_eq!(store.end_read_tracking(outer).unwrap(), vec![first, second]);
|
||||
// 新子区间重新读取同一对象,也必须得到自己的观察记录。
|
||||
let repeated = store.begin_read_tracking().unwrap();
|
||||
store.object(first).unwrap();
|
||||
assert_eq!(store.end_read_tracking(repeated).unwrap(), vec![first]);
|
||||
// Ending the call discards an unfinished region, as on callback failure.
|
||||
// 模拟回调失败:结束整次调用时丢弃尚未正常结束的区间。
|
||||
store.begin_read_tracking().unwrap();
|
||||
assert_eq!(store.end_read_only(scope).unwrap(), vec![first, second]);
|
||||
// 整个调用结束后未完成区间已丢弃,不能再拿旧令牌结束。
|
||||
assert!(store.end_read_tracking(1).is_err());
|
||||
assert!(!store.is_read_only());
|
||||
// first 没有内部拥有边,归还唯一外部根后可立即回收;借用读取记录不会保活它。
|
||||
store.release(first).unwrap();
|
||||
// second 同样独立归还唯一根;本场景没有函数、代码对象或对象互引环。
|
||||
store.release(second).unwrap();
|
||||
}
|
||||
|
||||
+8
-3
@@ -25,6 +25,10 @@ ASDL 支持 module、产品类型 `(Type field, ...)`、构造器联合 `A(...)
|
||||
|
||||
Rust 用 struct/enum、Vec/Option 表达结构;生成器在按值依赖图的回边放置 Box,Vec 已经断开按值递归。增加节点后,Rust 类型、两种语言的遍历接口和 JSON Schema 由同一次生成更新。
|
||||
|
||||
中文节点、字段和构造器说明维护在 [generate_ast.py](../../scripts/generate_ast.py) 的 `TYPE_NOTES`、`FIELD_NOTES`、`VARIANT_NOTES` 中,由模板写入 Rust 和 TypeScript 源码。字段说明按所属类型区分,`span` 统一说明原文定位;访问器同时说明遍历顺序、失败退出和子树跳过规则。阅读 schema 时可对照 [Rust 节点](../../crates/frontend/src/ast/generated.rs)、[Rust 遍历器](../../crates/frontend/src/ast/visitor.rs) 或 [TypeScript 接口](../../crates/frontend/editor/ast.ts)。更改这些说明后也应运行生成命令,手改派生文件会在下次生成时丢失。
|
||||
|
||||
本次注释补充保留 `Endor.asdl` 原文:其注释和空白也参与下述协议身份计算,直接添加中文注释会令已有 JSON 文档的 schema 标识失配。中文说明因此随生成源码维护;ASDL 仍是唯一结构定义源,说明字典不新增节点或改变协议结构。
|
||||
|
||||
## 根、声明、语句、表达式
|
||||
|
||||
| 层级 | 当前数据模型 |
|
||||
@@ -33,15 +37,16 @@ Rust 用 struct/enum、Vec/Option 表达结构;生成器在按值依赖图
|
||||
| Program | declarations: Vec<Decl> 与 span;声明种类为 Function、Class |
|
||||
| Decl/DeclKind | 声明的位置及其具体种类;Function 保存参数、返回类型名称和 Block;Class 保存名称、可选基类、字段及方法 |
|
||||
| Block | statements: Vec<Stmt> 与 span,不再用固定的 bindings/result 字段表示 |
|
||||
| Stmt/StmtKind | Binding、Assignment、MemberAssignment、Return、Expression、Declaration;每条语句有自身范围 |
|
||||
| Stmt/StmtKind | Binding、Assignment、MemberAssignment、If、While、Return、Expression、Declaration;每条语句有自身范围 |
|
||||
| IfStatement/WhileStatement | 条件和嵌套 Block;if 另有可选 otherwise,while 每轮重新求条件 |
|
||||
| MemberAssignment | 对象表达式、属性名称、右侧表达式与原文范围 |
|
||||
| Assignment | 名称目标、右侧表达式及原文范围;独立于 let 的 Binding 节点 |
|
||||
| ReturnStatement | 可选表达式与范围;当前源码中的 return 仍要求表达式 |
|
||||
| Expr/ExprKind | 名称、数字原文、字符串、符号、一元/二元运算、成员和调用 |
|
||||
| Expr/ExprKind | 名称、数字原文、字符串、符号、等待、一元/二元运算、成员和调用 |
|
||||
|
||||
参数与返回类型仍为 Name,只保留语法,不实施类型或名称检查。Program::functions() 提供函数迭代便捷接口。旧的 Program.functions、Block.bindings/result/return_span 字段已移除;源码构造和使用方改为声明/语句列表。ExprKind 的既有表达式结构保留。
|
||||
|
||||
AST 数据模型允许一般语句列表,不代表 parser 接受了所有组合。模块要求一个或多个函数/类声明;函数体接受 let/名称赋值/属性赋值/表达式语句,以及可选的末尾带值 return。Interactive 模式接受一次函数/类声明、let 绑定、名称/属性赋值或表达式,必须消费完整输入;不创建交互会话,不处理续行提示,不执行语言代码。多条交互提交由宿主分别解析。条件、循环、导入、trait 等尚未实现,也不加入 FunctionType 这种 Python 专用入口。
|
||||
AST 数据模型允许一般语句列表,不代表 parser 接受了所有组合。模块要求一个或多个函数/类声明;函数体接受 let/名称赋值/属性赋值/表达式语句、if/else 和 while,以及各块可选的末尾带值 return。Interactive 模式解析一次函数/类声明、控制流、let 绑定、名称/属性赋值或表达式,必须消费完整输入;不创建交互会话,不处理续行提示,不执行语言代码。多条交互提交由宿主分别解析。函数和交互根的条件、循环均有对应名称/初始化分析与跳转指令生成;具体 AST 仍需通过后续语义及字节码校验。导入和 trait 声明尚无源码语法,也没有 FunctionType 这种 Python 专用入口。
|
||||
|
||||
## 解析与位置
|
||||
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
#!/bin/sh
|
||||
# 项目完整检查入口,供开发者或 CI 调用;下方 Cargo 测试和 lint 会触发编译。
|
||||
# 启用首个失败即退出和未定义变量报错,避免前置检查失败后继续报告后续成功。
|
||||
set -eu
|
||||
# 比较 ASDL 对应的已提交生成源码/契约是否过期;只检查,不写目标文件,内部会格式化 Rust 文本。
|
||||
python3 scripts/generate_ast.py --check
|
||||
# 运行生成器的纯 Python 回归场景,验证递归装箱、跨目标同步及非法 schema 拒绝。
|
||||
python3 scripts/test_generate_ast.py
|
||||
# 检查整个 Rust workspace 格式,不改文件;不运行 VM。
|
||||
cargo fmt --all --check
|
||||
# 单独检查 include 引入的两个 PEG 文件格式,补足普通 Cargo 源码格式检查的覆盖。
|
||||
rustfmt --edition 2024 --check crates/frontend/src/lexer.peg crates/frontend/src/grammar.peg
|
||||
# 编译并运行 workspace 测试,locked 要求使用已锁定依赖;这一步会产生构建产物。
|
||||
cargo test --workspace --locked
|
||||
# 对全部目标执行 Clippy,包含编译检查,并把所有警告作为错误拒绝。
|
||||
cargo clippy --workspace --all-targets --locked -- -D warnings
|
||||
|
||||
+356
-16
@@ -15,85 +15,255 @@ import re
|
||||
import subprocess
|
||||
|
||||
|
||||
# ASDL 内置标量到 Rust 类型的映射;自定义节点类型另由 definitions 解析,不在这里注册。
|
||||
BUILTINS = {"string": "String", "span": "Span", "schema_id": "String"}
|
||||
# 所有生成源码共用的来源提示;修改生成注释应改模板,不手改派生文件。
|
||||
HEADER = "// Generated by scripts/generate_ast.py from Endor.asdl. Do not edit.\n"
|
||||
# parse_schema 拒绝 Rust 保留字标识符,避免输出无法表示的字段/类型名;schema 名称必须可跨目标使用。
|
||||
RESERVED = set("as break const continue crate else enum extern false fn for if impl in let loop match mod move mut pub ref return self Self static struct super trait true type unsafe use where while async await dyn abstract become box do final macro override priv typeof unsized virtual yield try gen".split())
|
||||
|
||||
|
||||
# 说明与 ASDL 分开维护,仅输出源码注释,不改变按 ASDL 原文计算的协议身份。
|
||||
TYPE_NOTES = {
|
||||
"AstDocument": "宿主/编辑器传输的版本化语法文档,wire 校验后供 compiler 使用。",
|
||||
"AstRoot": "parse_ast 按模式构造的模块、表达式或交互根,compiler 据此选择生成入口。",
|
||||
"Name": "parser 复制的名称文本与位置,用于声明、类型注解或属性名。",
|
||||
"Program": "模块声明序列,compiler 先注册全部顶层名称再分析各函数体。",
|
||||
"Decl": "顶层/交互声明的种类及完整范围,compiler 按函数或类分派。",
|
||||
"DeclKind": "函数与类声明的判别载荷,外层 Decl 另外保存完整跨度。",
|
||||
"Class": "类名、可选基类、字段和方法,compiler 检查成员后生成构类指令。",
|
||||
"Function": "显式参数/返回注解与函数体,compiler 生成独立模板及源码签名;解析本身不调用函数。",
|
||||
"Parameter": "函数参数或类字段共用的名称/类型注解,实际类型身份由运行时环境解析。",
|
||||
"Block": "顺序语句块,compiler 检查确定初始化并按语句生成指令。",
|
||||
"Stmt": "语句种类及原文范围,compiler 处理副作用、控制流或返回。",
|
||||
"StmtKind": "语句判别载荷,parser 选择分支,compiler 按绑定、赋值、控制流等规则处理。",
|
||||
"Binding": "let 新绑定,compiler 检查重名并分析右值后才标记目标已初始化。",
|
||||
"Assignment": "普通名称赋值,compiler 按作用域写局部槽或全局环境。",
|
||||
"MemberAssignment": "成员写入的对象、属性名与右值,compiler 生成 StoreAttr。",
|
||||
"IfStatement": "条件、then 和可选 else,compiler 合并继续路径的初始化集合并生成跳转。",
|
||||
"WhileStatement": "循环条件与体,compiler 生成回边并处理循环可能零次执行。",
|
||||
"ReturnStatement": "函数返回,compiler 对缺省值补 Nil;当前源码语法要求 return 后有表达式。",
|
||||
"InteractiveInput": "单次交互提交容器,parser 产生一条语句,compiler 也检查数量,不保存宿主会话环境。",
|
||||
"Expr": "表达式载荷及完整范围,compiler 生成留下一个结果的求值序列。",
|
||||
"ExprKind": "名称、字面量、等待、运算和调用等表达式载荷,范围由外层 Expr 提供。",
|
||||
"UnaryOp": "一元运算种类,compiler 合并纯数值负号或生成运行时取负。",
|
||||
"BinaryOp": "二元算术/比较种类,parser 保留优先级树,compiler 映射为对应 VM 操作。",
|
||||
}
|
||||
# 字段说明以拥有类型和字段名为键,不将不同节点的同名字段混为相同用途。
|
||||
FIELD_NOTES = {
|
||||
("AstDocument", "schema"): "协议身份,wire 按 AST_SCHEMA_ID 核对;不是源码内容哈希。",
|
||||
("AstDocument", "root"): "拥有的语法根,文档校验及编译从这里递归。",
|
||||
("AstRoot", "body"): "当前模式选中的模块、表达式或交互根载荷,compiler 按对应构造器生成入口代码。",
|
||||
("DeclKind", "definition"): "函数或类的完整声明,包含其名称、成员/参数及原文范围。",
|
||||
("StmtKind", "binding"): "let 新绑定,compiler 先分析/求右值,再登记目标初始化。",
|
||||
("StmtKind", "assignment"): "普通名称或成员赋值的完整节点,compiler 据构造器选择槽写入或属性写入。",
|
||||
("StmtKind", "statement"): "条件、循环或返回节点,compiler 按构造器处理路径、跳转或帧出口。",
|
||||
("StmtKind", "expression"): "语句位置的表达式;函数块丢弃结果,交互根可把它作为返回值。",
|
||||
("StmtKind", "declaration"): "交互根中的函数/类声明,compiler 预登记名称;嵌套声明当前被拒绝。",
|
||||
("ExprKind", "text"): "名称引用原文,由 compiler 名称分析解析到局部槽或全局名字表。",
|
||||
("ExprKind", "spelling"): "原始数字拼写,compiler 构造精确数值;不把文本先转为二进制浮点。",
|
||||
("ExprKind", "value"): "字符串解码后的拥有文本,原引号和转义仍可通过外层范围读取。",
|
||||
("Name", "text"): "拥有名称文本,不依赖输入缓冲存活。",
|
||||
("Program", "declarations"): "按源码顺序的声明;源码模块解析要求非空,文档结构本身不限制数量。",
|
||||
("Decl", "kind"): "函数/类声明载荷,JSON 和类型采用 kind/data 标签。",
|
||||
("Class", "name"): "类声明名与精确位置。",
|
||||
("Class", "base"): "显式基类名,可省略;当前 compiler 对省略项使用 Object。",
|
||||
("Class", "fields"): "按出现顺序的字段名称/类型注解,不含方法。",
|
||||
("Class", "methods"): "按出现顺序的方法;显式 self 约束由 compiler 检查。",
|
||||
("Function", "name"): "函数/方法名,代码模板沿用为诊断名称。",
|
||||
("Function", "parameters"): "位置参数列表,compiler 依次分配前部局部槽。",
|
||||
("Function", "return_type"): "返回类型名,生成源码签名后由运行时解析。",
|
||||
("Function", "body"): "独立函数作用域的语句体,当前源码不支持嵌套声明闭包。",
|
||||
("Parameter", "name"): "参数/字段名,用于布局和重复检查。",
|
||||
("Parameter", "type_name"): "声明类型名,前端只保留文本与位置。",
|
||||
("Block", "statements"): "有序语句列表,不引入独立块级局部作用域。",
|
||||
("Stmt", "kind"): "具体语句载荷,外层另存整句范围。",
|
||||
("Binding", "name"): "let 目标名,重复声明由语义分析拒绝。",
|
||||
("Binding", "value"): "绑定右值,本局部不能在初始化前读取自身。",
|
||||
("Assignment", "name"): "普通赋值目标,函数内首次赋值可预分配局部槽。",
|
||||
("Assignment", "value"): "先求右值再更新目标。",
|
||||
("MemberAssignment", "object"): "属性写入接收对象表达式,前端不求值。",
|
||||
("MemberAssignment", "name"): "待写属性名,compiler 加入名字表。",
|
||||
("MemberAssignment", "value"): "待写右值,当前 compiler 先于接收对象生成求值以匹配 VM 栈布局。",
|
||||
("IfStatement", "condition"): "条件表达式,运行时按真值协议选择分支。",
|
||||
("IfStatement", "body"): "条件为真的 then 块。",
|
||||
("IfStatement", "otherwise"): "可选 else 块,缺省时存在直接继续路径。",
|
||||
("WhileStatement", "condition"): "每轮重新求值的条件,假值退出。",
|
||||
("WhileStatement", "body"): "循环体,可能一次也不执行。",
|
||||
("ReturnStatement", "value"): "可选返回表达式,手工 AST 缺省时由 compiler 补 Nil。",
|
||||
("InteractiveInput", "statements"): "当前 parse/compile 约定恰好一个完整提交的语句。",
|
||||
("Expr", "kind"): "求值形状,不缓存名称解析或运行时类型。",
|
||||
("ExprKind", "operation"): "运算种类,决定算术、比较或取负指令。",
|
||||
("ExprKind", "operand"): "一元子表达式,Rust 的递归回边间接存储。",
|
||||
("ExprKind", "left"): "二元左操作数,当前 compiler 先于右侧求值。",
|
||||
("ExprKind", "right"): "二元右操作数,与左侧一起被运行时运算消费。",
|
||||
("ExprKind", "object"): "成员读取接收对象。",
|
||||
("ExprKind", "name"): "成员或符号名,不是已解析的运行时属性句柄。",
|
||||
("ExprKind", "callee"): "调用目标,先于实参求值。",
|
||||
("ExprKind", "arguments"): "有序位置实参,AST 不隐式添加接收者。",
|
||||
}
|
||||
VARIANT_NOTES = {
|
||||
("AstRoot", "Module"): "模块根,包含函数/类声明。",
|
||||
("AstRoot", "Expression"): "表达式根,编译后直接返回求值结果。",
|
||||
("AstRoot", "Interactive"): "交互根,包含一次完整提交。",
|
||||
("ExprKind", "Name"): "变量引用文本,compiler 决定局部槽或全局索引。",
|
||||
("ExprKind", "Number"): "原始十进制拼写,保留全部位数、前导零及尾零,不转浮点。",
|
||||
("ExprKind", "String"): "已解码受支持转义的拥有文本,原引号范围在外层 Expr。",
|
||||
("ExprKind", "Symbol"): "点开头的符号名,前端可表示,当前 compiler 拒绝生成运行时值。",
|
||||
("ExprKind", "Await"): "等待子表达式,VM 执行上下文决定是否允许停放。",
|
||||
("ExprKind", "Unary"): "一元取负,可在纯数值链上合并符号。",
|
||||
("ExprKind", "Binary"): "左右操作数及算术/比较种类,保留解析优先级树。",
|
||||
("ExprKind", "Member"): "接收对象及属性名的读取。",
|
||||
("ExprKind", "Call"): "被调用表达式与位置实参,由 VM 实际求值。",
|
||||
("UnaryOp", "Negate"): "一元取负。",
|
||||
}
|
||||
|
||||
|
||||
# 操作符叶子没有子节点,仍需给各构造器明确语义;扩展项继续使用通用说明。
|
||||
VARIANT_NOTES.update({("BinaryOp", name): note for name, note in {
|
||||
"Add": "加法。", "Subtract": "减法。", "Multiply": "乘法。", "Divide": "除法。",
|
||||
"Equal": "相等比较。", "NotEqual": "不等比较。", "Less": "小于比较。",
|
||||
"LessEqual": "小于等于比较。", "Greater": "大于比较。", "GreaterEqual": "大于等于比较。",
|
||||
}.items()})
|
||||
|
||||
|
||||
# Rust/TS 类型声明生成共用,未记录的扩展节点仍能按结构正常生成。
|
||||
def type_note(name):
|
||||
return TYPE_NOTES.get(name, f"ASDL 定义的 {name} 节点,由宿主构造并由访问器消费。")
|
||||
|
||||
|
||||
# 字段说明共用;span 的位置协议在所有节点中保持一致。
|
||||
def field_note(owner, field):
|
||||
# 内置 span 的统一位置协议优先于拥有节点的特定字段含义。
|
||||
if field.type == "span":
|
||||
return "原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。"
|
||||
return FIELD_NOTES.get((owner, field.name), f"{field.name} 保存 {field.type} 类型" + ("的有序列表。" if field.quantifier == "*" else "的可选值。" if field.quantifier == "?" else "的值。"))
|
||||
|
||||
|
||||
# 和类型说明共用,包装已知节点时复用其语义,否则按构造器形状提供结构说明。
|
||||
def variant_note(owner, variant):
|
||||
# 单载荷包装已知节点时复用节点说明,未知扩展构造器仍保留通用结构说明。
|
||||
if len(variant.fields) == 1 and variant.fields[0].type in TYPE_NOTES:
|
||||
fallback = type_note(variant.fields[0].type)
|
||||
else:
|
||||
fallback = f"{owner} 的 {variant.name} 构造器,载荷由 ASDL 字段定义。"
|
||||
return VARIANT_NOTES.get((owner, variant.name), fallback)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
# 不可变字段描述,由 parse_schema 构造,Rust/TS/JSON 及遍历器生成阶段共同消费。
|
||||
class Field:
|
||||
# 字段引用的 ASDL 类型名,可能是 BUILTINS 标量或用户定义节点。
|
||||
type: str
|
||||
# 字段、构造器或定义的原始标识符,生成器不自动改写它。
|
||||
name: str
|
||||
# 空串为必选单值,星号为序列,问号为可空/可缺省字段。
|
||||
quantifier: str = ""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
# 和类型的一个构造器;fields 可空、单项或多项,对应三种不同生成形状。
|
||||
class Variant:
|
||||
# 字段、构造器或定义的原始标识符,生成器不自动改写它。
|
||||
name: str
|
||||
# 产品类型字段或构造器字段,保持 ASDL 顺序;Definition 的 None 才表示和类型。
|
||||
fields: tuple[Field, ...]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
# 一个命名定义;fields 非 None 为产品类型,None 为带 variants 的和类型。
|
||||
class Definition:
|
||||
# 字段、构造器或定义的原始标识符,生成器不自动改写它。
|
||||
name: str
|
||||
# 产品类型字段或构造器字段,保持 ASDL 顺序;Definition 的 None 才表示和类型。
|
||||
fields: tuple[Field, ...] | None
|
||||
# 和类型构造器列表,产品类型保持为空元组。
|
||||
variants: tuple[Variant, ...] = ()
|
||||
|
||||
# 图分析与类型校验调用;产品直接取字段,和类型按构造器顺序展平所有字段。
|
||||
def all_fields(self):
|
||||
return self.fields if self.fields is not None else tuple(f for v in self.variants for f in v.fields)
|
||||
|
||||
# Rust/TS/Schema 生成共用;只有全部构造器均无载荷的和类型才可作为简单枚举。
|
||||
def simple(self):
|
||||
return self.fields is None and all(not v.fields for v in self.variants)
|
||||
|
||||
|
||||
# generate 及生成器回归测试调用;解析本项目 ASDL 子集,拒绝未知语法、重名与未定义字段类型。
|
||||
def parse_schema(text: str) -> dict[str, Definition]:
|
||||
"""Parse the project's ASDL subset; reject unknown syntax and field types."""
|
||||
# ASDL 双横线注释不影响语法解析;generate 的协议哈希仍使用未经删注释的原文。
|
||||
text = re.sub(r"--[^\n]*", "", text)
|
||||
# 保存标识符及结构标点,后面的递归小解析器只按此序列推进。
|
||||
tokens = []
|
||||
# ASDL 原始文本的 Python 字符索引,用于词法扫描错误定位片段。
|
||||
cursor = 0
|
||||
# 只接受 ASCII 标识符和已支持的 ASDL 标点,其他字符不能被静默跳过。
|
||||
pattern = re.compile(r"\s*([A-Za-z_][A-Za-z_0-9]*|[{}()=|,*?])")
|
||||
# 按输入顺序扫描 Token,纯尾部空白不生成额外 Token。
|
||||
while cursor < len(text):
|
||||
# 只有剩余部分全为空白才正常结束,非空未知内容必须进入失败路径。
|
||||
if not text[cursor:].strip():
|
||||
break
|
||||
# 从当前位置匹配一个含前导空白的 Token,禁止跳过未识别片段向后搜索。
|
||||
match = pattern.match(text, cursor)
|
||||
# 未知字符直接报错,避免生成不完整的部分 schema。
|
||||
if not match:
|
||||
raise ValueError(f"unsupported ASDL syntax near {text[cursor:cursor + 30]!r}")
|
||||
tokens.append(match[1])
|
||||
cursor = match.end()
|
||||
# 语法游标与前面的文本游标分开,指向尚未消费的 Token。
|
||||
index = 0
|
||||
|
||||
# parse_schema 内部前瞻,不前进游标;不完整输入在这里明确报告 EOF。
|
||||
def peek():
|
||||
# 无可消费/前瞻 Token 时报告输入不完整,不让 Python 索引异常泄漏为协议。
|
||||
if index == len(tokens):
|
||||
raise ValueError("unexpected end of ASDL")
|
||||
return tokens[index]
|
||||
|
||||
# 内部消费一个 Token,可同时校验固定标点;只有成功后才推进 index。
|
||||
def take(expected=None):
|
||||
# 多个内部解析函数共享外层 Token 游标,不能各自维护副本。
|
||||
nonlocal index
|
||||
# 无可消费/前瞻 Token 时报告输入不完整,不让 Python 索引异常泄漏为协议。
|
||||
if index == len(tokens):
|
||||
raise ValueError("unexpected end of ASDL")
|
||||
# 保留待返回 Token,在验证期望值后再推进游标。
|
||||
value = tokens[index]
|
||||
# 固定词/标点与当前 Token 必须相同,错误显示两者便于定位 schema 编辑问题。
|
||||
if expected is not None and value != expected:
|
||||
raise ValueError(f"expected {expected!r}, found {value!r}")
|
||||
index += 1
|
||||
return value
|
||||
|
||||
# 定义名、类型名、构造器名及字段名共用的标识符检查。
|
||||
def identifier():
|
||||
# 消费一个候选名称,然后检查允许的标识符形状与保留字约束。
|
||||
value = take()
|
||||
# 禁止未知命名形式及 Rust 保留字,以免后续模板生成不可用源码。
|
||||
if not re.fullmatch(r"[A-Za-z_][A-Za-z_0-9]*", value) or value in RESERVED:
|
||||
raise ValueError(f"invalid or reserved identifier: {value}")
|
||||
return value
|
||||
|
||||
# 产品及带载荷构造器共用括号字段列表解析,返回不可变元组保留原顺序。
|
||||
def fields():
|
||||
take("(")
|
||||
# 当前字段列表的累积结果,结束时再做字段名去重。
|
||||
result = []
|
||||
# 右括号之前逐项读取类型、可选数量修饰符及字段名。
|
||||
while peek() != ")":
|
||||
# 先读字段类型,实际是否已定义在整个 schema 读完后统一检查,允许前向类型引用。
|
||||
type_name = identifier()
|
||||
# 仅问号或星号作为数量修饰,缺省是单个必需字段。
|
||||
quantifier = take() if peek() in ("?", "*") else ""
|
||||
result.append(Field(type_name, identifier(), quantifier))
|
||||
# 没有逗号则结束列表,随后必须读到闭括号。
|
||||
if peek() != ",":
|
||||
break
|
||||
take(",")
|
||||
take(")")
|
||||
# 同一产品/构造器内不允许重复字段名。
|
||||
if len({f.name for f in result}) != len(result):
|
||||
raise ValueError("duplicate field name")
|
||||
return tuple(result)
|
||||
@@ -101,115 +271,172 @@ def parse_schema(text: str) -> dict[str, Definition]:
|
||||
take("module")
|
||||
identifier()
|
||||
take("{")
|
||||
# 按声明顺序保留定义;后续生成及 DFS 使用稳定顺序以得到可重复输出。
|
||||
definitions = {}
|
||||
# 逐个读取命名定义,模块闭括号结束后不允许残留输入。
|
||||
while peek() != "}":
|
||||
# 读取当前定义名并检查是否覆盖已定义类型或内置标量。
|
||||
name = identifier()
|
||||
# 自定义类型不能重名或覆写内置类型映射。
|
||||
if name in definitions or name in BUILTINS:
|
||||
raise ValueError(f"duplicate or builtin type: {name}")
|
||||
take("=")
|
||||
# 等号后直接括号表示产品;否则解析一个或多个和类型构造器。
|
||||
if peek() == "(":
|
||||
# 产品类型保存字段元组,区别于和类型的 fields=None。
|
||||
definition = Definition(name, fields())
|
||||
else:
|
||||
# 按顺序收集当前和类型构造器,供输出判别联合/枚举。
|
||||
variants = []
|
||||
# 持续收集由竖线分隔的构造器,遇到非竖线后交外层处理下一定义。
|
||||
while True:
|
||||
# 当前构造器名称,是否带载荷由接下来的左括号决定。
|
||||
variant = identifier()
|
||||
# 没有括号的构造器使用空元组,生成时成为无载荷分支。
|
||||
members = fields() if peek() == "(" else ()
|
||||
variants.append(Variant(variant, members))
|
||||
# 构造器列表结束;不提前消费下一定义的标识符。
|
||||
if peek() != "|":
|
||||
break
|
||||
take("|")
|
||||
# 同一和类型的构造器必须唯一,否则 JSON 判别标签会歧义。
|
||||
if len({v.name for v in variants}) != len(variants):
|
||||
raise ValueError(f"duplicate constructor in {name}")
|
||||
# 和类型以 None 区别产品的空字段元组,保留完整构造器列表。
|
||||
definition = Definition(name, None, tuple(variants))
|
||||
definitions[name] = definition
|
||||
take("}")
|
||||
# 模块闭括号之后必须 EOF,拒绝尾随文本。
|
||||
if index != len(tokens):
|
||||
raise ValueError("trailing ASDL input")
|
||||
# 依稳定 schema 顺序处理每个定义,保留生成结果的可重复性。
|
||||
for definition in definitions.values():
|
||||
# 产品及所有构造器字段都必须引用内置标量或已声明类型。
|
||||
for field in definition.all_fields():
|
||||
# 解析完所有声明再检查类型,因此支持合法前向引用但拒绝拼错的未知类型。
|
||||
if field.type not in definitions and field.type not in BUILTINS:
|
||||
raise ValueError(f"undefined field type: {field.type}")
|
||||
return definitions
|
||||
|
||||
|
||||
# Rust 访问器方法名生成调用,把类型名大写边界转成蛇形小写,不改变 schema 原类型名。
|
||||
def snake(name):
|
||||
return re.sub(r"(?<!^)(?=[A-Z])", "_", name).lower()
|
||||
|
||||
|
||||
# rust_types 和回归测试调用;在按值引用图上 DFS 标记回边,Vec 自带间接存储而无需 Box。
|
||||
def recursive_edges(definitions):
|
||||
"""Box DFS back edges in the by-value graph; Vec already breaks recursion."""
|
||||
# 分别记录当前递归路径、已完成节点、需要装箱的 (拥有类型, 被引用类型) 边。
|
||||
active, done, boxed = set(), set(), set()
|
||||
|
||||
# DFS 内部递归访问一个定义,进入 active 后检查其所有按值字段,结束时转到 done。
|
||||
def visit(name):
|
||||
active.add(name)
|
||||
# 检查当前类型的全部产品/构造器字段,不遗漏和类型形成的递归环。
|
||||
for field in definitions[name].all_fields():
|
||||
# 序列 Vec 已打断 Rust 按值递归,内置标量也无需进入定义图。
|
||||
if field.quantifier == "*" or field.type not in definitions:
|
||||
continue
|
||||
# 指向当前递归路径形成回边,在 Rust 字段映射时为这条边加 Box。
|
||||
if field.type in active:
|
||||
boxed.add((name, field.type))
|
||||
# 尚未遍历完成的引用类型继续 DFS,已完成类型不重复递归。
|
||||
elif field.type not in done:
|
||||
visit(field.type)
|
||||
active.remove(name)
|
||||
done.add(name)
|
||||
|
||||
# 按定义顺序遍历/输出每个命名类型,避免由无序集合决定源码结构。
|
||||
for name in definitions:
|
||||
# 每个未访问连通分量都需启动 DFS,不能只从 AstRoot 假设全部类型可达。
|
||||
if name not in done:
|
||||
visit(name)
|
||||
return boxed
|
||||
|
||||
|
||||
# rust_types/rust_visitor 共用;显式 span 产品和只包装单个可定位子节点的和类型可实现 Spanned。
|
||||
def spanned_types(definitions):
|
||||
# 起始集合仅含拥有必需 span 字段的产品类型,不把可选 span 误当作可靠位置。
|
||||
result = {d.name for d in definitions.values() if d.fields is not None and Field("span", "span") in d.fields}
|
||||
# 求可定位类型闭包,包装层可能依赖前一轮刚加入的类型。
|
||||
while True:
|
||||
# 所有构造器均恰有一个必选且已可定位的子节点,才可把该和类型加入集合。
|
||||
expanded = result | {
|
||||
d.name for d in definitions.values()
|
||||
if d.variants and all(len(v.fields) == 1 and not v.fields[0].quantifier and v.fields[0].type in result for v in d.variants)
|
||||
}
|
||||
# 集合不再增长即达到不动点,后续生成 span 及节点钩子使用同一判定。
|
||||
if expanded == result:
|
||||
return result
|
||||
result = expanded
|
||||
|
||||
|
||||
# generate 生成 Rust AST 源码,递归布局、serde 形状和 Spanned 实现都来自同一份定义。
|
||||
def rust_types(definitions, schema_id):
|
||||
# 先标出必须间接存储的回边,不把所有字段无差别装箱。
|
||||
boxed = recursive_edges(definitions)
|
||||
# 取得结构上可提供跨度的类型集合,决定 span 实现或访问器节点钩子。
|
||||
spanned = spanned_types(definitions)
|
||||
|
||||
# Rust 字段映射:内置标量/命名类型、Vec、必要 Box 和可空 Option 的组合。
|
||||
def field_type(owner, field):
|
||||
# 先映射内置名,用户节点保持原类型名。
|
||||
value = BUILTINS.get(field.type, field.type)
|
||||
# 序列优先映射到 Vec/Array/array;容器本身处理重复元素并打断 Rust 按值递归。
|
||||
if field.quantifier == "*":
|
||||
return f"Vec<{value}>"
|
||||
# 仅 DFS 回边添加 Box;可选字段之后再包 Option,以保留空值语义。
|
||||
if (owner, field.type) in boxed:
|
||||
value = f"Box<{value}>"
|
||||
return f"Option<{value}>" if field.quantifier == "?" else value
|
||||
|
||||
lines = [HEADER, "use crate::Span;", "use super::Spanned;", f'pub const AST_SCHEMA_ID: &str = "{schema_id}";']
|
||||
lines = [HEADER, "use crate::Span;", "use super::Spanned;", "/// wire/编辑器核对的协议身份,来自 ASDL 原文哈希;生成节点注释不参与此值。", f'pub const AST_SCHEMA_ID: &str = "{schema_id}";']
|
||||
# 依稳定 schema 顺序处理每个定义,保留生成结果的可重复性。
|
||||
for definition in definitions.values():
|
||||
# 所有 AST 类型支持拥有副本、相等比较及 serde,简单无载荷枚举才额外 Copy。
|
||||
lines.append("/// " + type_note(definition.name))
|
||||
derives = "Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize"
|
||||
# 无载荷和类型可用简单枚举/字符串联合表示,无需 kind/data 的载荷包装。
|
||||
if definition.simple():
|
||||
derives += ", Copy"
|
||||
lines.append(f"#[derive({derives})]")
|
||||
# 产品及带载荷和类型拒绝未知字段;后者额外采用 kind/data 邻接标签。
|
||||
if not definition.simple():
|
||||
# 只有和类型才加 kind/data 标签,产品字段直接序列化成对象。
|
||||
tag = 'tag = "kind", content = "data", ' if definition.fields is None else ""
|
||||
lines.append(f"#[serde({tag}deny_unknown_fields)]")
|
||||
# 产品按字段顺序生成;和类型则按构造器分支选择对应形状。
|
||||
if definition.fields is not None:
|
||||
lines.append(f"pub struct {definition.name} {{")
|
||||
lines.extend(f"pub {f.name}: {field_type(definition.name, f)}," for f in definition.fields)
|
||||
for field in definition.fields:
|
||||
# 注释与字段同序输出,重新生成时一起保留。
|
||||
lines += ["/// " + field_note(definition.name, field), f"pub {field.name}: {field_type(definition.name, field)},"]
|
||||
else:
|
||||
lines.append(f"pub enum {definition.name} {{")
|
||||
# 每个构造器按稳定顺序生成,不合并名称相近但身份不同的分支。
|
||||
for variant in definition.variants:
|
||||
lines.append("/// " + variant_note(definition.name, variant))
|
||||
# 无载荷构造器不携带额外字段,单值和多字段载荷分别采用相应表示。
|
||||
if not variant.fields:
|
||||
lines.append(f"{variant.name},")
|
||||
# 单字段构造器直接包装字段类型,多字段才使用命名字段对象。
|
||||
elif len(variant.fields) == 1:
|
||||
lines.append(f"{variant.name}({field_type(definition.name, variant.fields[0])}),")
|
||||
else:
|
||||
fields = ", ".join(f"{f.name}: {field_type(definition.name, f)}" for f in variant.fields)
|
||||
# 多字段构造器的具名成员保留每个字段自己的类型/数量修饰。
|
||||
fields = ",\n".join(f"/// {field_note(definition.name, f)}\n{f.name}: {field_type(definition.name, f)}" for f in variant.fields)
|
||||
lines.append(f"{variant.name} {{ {fields} }},")
|
||||
lines.append("}")
|
||||
# 只为结构上可定位的类型生成 Spanned,不编造 ExprKind 这类混合载荷的范围。
|
||||
if definition.name in spanned:
|
||||
lines.append(f"impl Spanned for {definition.name} {{ fn span(&self) -> Span {{")
|
||||
lines.append(f"impl Spanned for {definition.name} {{")
|
||||
lines.append("/// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。")
|
||||
lines.append("fn span(&self) -> Span {")
|
||||
if definition.fields is not None:
|
||||
lines.append("self.span")
|
||||
else:
|
||||
lines.append("// 按实际构造器取得唯一可定位子节点的范围。")
|
||||
lines.append("match self {")
|
||||
lines.extend(f"Self::{v.name}(node) => node.span()," for v in definition.variants)
|
||||
lines.append("}")
|
||||
@@ -217,115 +444,193 @@ def rust_types(definitions, schema_id):
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
# generate 生成 Rust 借用节点访问器及 walk 函数;可定位节点通过 enter/leave 支持校验状态栈。
|
||||
def rust_visitor(definitions):
|
||||
# 取得结构上可提供跨度的类型集合,决定 span 实现或访问器节点钩子。
|
||||
spanned = spanned_types(definitions)
|
||||
# 按 schema 顺序导入所有节点类型,walk 可以直接引用它们。
|
||||
names = ", ".join(definitions)
|
||||
lines = [HEADER, f"use super::{{{names}, Spanned}};", "use crate::Span;", "pub trait Visitor {", "type Error;",
|
||||
lines = [HEADER, f"use super::{{{names}, Spanned}};", "use crate::Span;",
|
||||
"/// wire 校验与宿主分析实现的借用节点访问协议;默认按 ASDL 字段顺序递归,不修改树。",
|
||||
"/// 覆盖 visit 方法后可显式调用对应 walk 继续子遍历;walk 本身不执行当前节点的进入/退出钩子。",
|
||||
"pub trait Visitor {", "/// 实现者选择的错误类型,任一子访问失败即向上返回。", "type Error;",
|
||||
"/// 默认可定位 visit 在遍历前调用;失败时本节点尚未成功进入,不再调用它的 leave_node。",
|
||||
"fn enter_node(&mut self, _span: Span) -> Result<(), Self::Error> { Ok(()) }",
|
||||
"/// 已成功进入节点的遍历结束后调用,子节点错误也需退出;默认无操作。",
|
||||
"fn leave_node(&mut self) {}",
|
||||
"/// walk 遇到显式 span 字段时调用,默认不处理;区别于围绕整个节点的 enter_node。",
|
||||
"fn visit_span(&mut self, _span: &Span) -> Result<(), Self::Error> { Ok(()) }"]
|
||||
# 按定义顺序遍历/输出每个命名类型,避免由无序集合决定源码结构。
|
||||
for name in definitions:
|
||||
lines.append(f"/// 上层 walk 或宿主调用的 {name} 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。")
|
||||
lines.append(f"fn visit_{snake(name)}(&mut self, node: &{name}) -> Result<(), Self::Error> {{")
|
||||
# 可定位节点先进入,再保存子遍历 Result,退出后才返回错误,保证成功进入祖先的钩子配平。
|
||||
if name in spanned:
|
||||
lines += ["self.enter_node(node.span())?;", f"let result = walk_{snake(name)}(self, node);", "self.leave_node();", "result"]
|
||||
lines += ["// 先进入;进入失败则不启动子遍历。", "self.enter_node(node.span())?;", "// 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。", f"let result = walk_{snake(name)}(self, node);", "self.leave_node();", "result"]
|
||||
else:
|
||||
lines.append(f"walk_{snake(name)}(self, node)")
|
||||
lines.append("}")
|
||||
lines.append("}")
|
||||
|
||||
# 访问器生成共用的字段递归模板:跳过普通文本/标识,仅递归节点或显式跨度。
|
||||
def children(field, access):
|
||||
# 字符串和 schema_id 无语法子节点,不能生成不存在的 visit_string/walkstring 调用。
|
||||
if field.type not in definitions and field.type != "span":
|
||||
return []
|
||||
# Rust 字段类型名映射成 visitor.visit_*,span 走默认可覆盖的 visit_span。
|
||||
method = f"visitor.visit_{snake(field.type)}"
|
||||
# 序列优先映射到 Vec/Array/array;容器本身处理重复元素并打断 Rust 按值递归。
|
||||
if field.quantifier == "*":
|
||||
return [f"for value in {access} {{ {method}(value)?; }}"]
|
||||
return [f"// 按顺序借用 {access} 中每个 {field.type},任一访问失败立即向上传播。", f"for value in {access} {{ {method}(value)?; }}"]
|
||||
# 可选字段只有 Some/非空时才递归,缺省或 null 不访问。
|
||||
if field.quantifier == "?":
|
||||
return [f"if let Some(value) = {access} {{ {method}(value)?; }}"]
|
||||
return [f"{method}({access})?;"]
|
||||
return [f"// {access} 是可选字段,仅 Some 时访问其 {field.type} 子节点。", f"if let Some(value) = {access} {{ {method}(value)?; }}"]
|
||||
return [f"// 访问 {access} 的 {field.type} 子结构或跨度,借用不转移节点所有权。", f"{method}({access})?;"]
|
||||
|
||||
# 依稳定 schema 顺序处理每个定义,保留生成结果的可重复性。
|
||||
for definition in definitions.values():
|
||||
# 为当前 walk 函数累积字段/构造器递归语句,不在生成器内运行访问。
|
||||
body = []
|
||||
# 产品按字段顺序生成;和类型则按构造器分支选择对应形状。
|
||||
if definition.fields is not None:
|
||||
# 产品字段依声明顺序递归,保证访问顺序与数据契约一致。
|
||||
for field in definition.fields:
|
||||
body += children(field, f"&node.{field.name}")
|
||||
# 带载荷和类型按判别分支访问,简单枚举没有子节点。
|
||||
elif not definition.simple():
|
||||
body.append("// 按构造器只递归当前实际载荷,字符串等标量没有语法子节点。")
|
||||
body.append("match node {")
|
||||
# 每个构造器按稳定顺序生成,不合并名称相近但身份不同的分支。
|
||||
for variant in definition.variants:
|
||||
# 只提取节点或 span 载荷;纯文本载荷匹配时忽略,避免无用 Rust 绑定。
|
||||
visited = [f for f in variant.fields if f.type in definitions or f.type == "span"]
|
||||
# 无载荷构造器不携带额外字段,单值和多字段载荷分别采用相应表示。
|
||||
if not variant.fields:
|
||||
# 按构造器载荷形状生成 Rust match 模式,仅绑定需要访问的成员。
|
||||
pattern = f"{definition.name}::{variant.name}"
|
||||
# 单字段构造器直接包装字段类型,多字段才使用命名字段对象。
|
||||
elif len(variant.fields) == 1:
|
||||
# 按构造器载荷形状生成 Rust match 模式,仅绑定需要访问的成员。
|
||||
pattern = f"{definition.name}::{variant.name}({'value' if visited else '_'})"
|
||||
else:
|
||||
# 多字段构造器只绑定需要递归的成员,其余通过 .. 忽略。
|
||||
members = ", ".join(f.name for f in visited)
|
||||
# 按构造器载荷形状生成 Rust match 模式,仅绑定需要访问的成员。
|
||||
pattern = f"{definition.name}::{variant.name} {{ " + (members + ", " if members else "") + ".. }"
|
||||
body.append("// " + variant_note(definition.name, variant))
|
||||
body.append(pattern + " => {")
|
||||
# 为每个被选中的载荷展开对应序列、可选或单值访问。
|
||||
for field in visited:
|
||||
body += children(field, "value" if len(variant.fields) == 1 else field.name)
|
||||
body.append("}")
|
||||
body.append("}")
|
||||
# 没有实际递归调用的 walker 使用下划线参数名,避免生成未使用变量告警。
|
||||
visitor = "visitor" if any("visitor." in line for line in body) else "_visitor"
|
||||
# 完全无子结构的 walker 也不使用 node,生成下划线名字以满足警告检查。
|
||||
node = "node" if body else "_node"
|
||||
lines.append(f"/// 默认 visit_{snake(definition.name)} 或自定义访问器调用,按字段顺序借用 {definition.name} 的子结构;本函数不进入/退出当前节点。")
|
||||
lines.append(f"pub fn walk_{snake(definition.name)}<V: Visitor + ?Sized>({visitor}: &mut V, {node}: &{definition.name}) -> Result<(), V::Error> {{")
|
||||
lines.extend(body)
|
||||
lines += ["Ok(())", "}"]
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
# generate 生成与 serde 形状匹配的 TypeScript 类型及可剪枝 walker,供编辑器消费 AST JSON。
|
||||
def editor_types(definitions, schema_id):
|
||||
# 编辑器字段映射:标量名、数组、可缺省/可空联合;schema_id 限定为当前常量的字面量类型。
|
||||
def field_type(field):
|
||||
# 将内置标量映射为 TypeScript 对应类型;命名节点保持 schema 名称。
|
||||
result = {"string": "string", "span": "Span", "schema_id": "typeof AST_SCHEMA_ID"}.get(field.type, field.type)
|
||||
# 序列优先映射到 Vec/Array/array;容器本身处理重复元素并打断 Rust 按值递归。
|
||||
if field.quantifier == "*":
|
||||
result = f"Array<{result}>"
|
||||
# 问号字段允许空值,目标类型/Schema 同时处理字段缺省。
|
||||
elif field.quantifier == "?":
|
||||
result += " | null"
|
||||
return result
|
||||
|
||||
# TypeScript 产品或多载荷对象形状,问号字段标为可缺省,并保留其 null 可能性。
|
||||
def object_type(fields):
|
||||
return "{ " + " ".join(f"{f.name}{'?' if f.quantifier == '?' else ''}: {field_type(f)};" for f in fields) + " }"
|
||||
|
||||
lines = [HEADER, f'export const AST_SCHEMA_ID = "{schema_id}" as const;', "export interface Span { start: number; end: number; }"]
|
||||
lines = [HEADER, "// 与 Rust wire 共同核对的 ASDL 原文协议身份;生成源码注释不改变此值。", f'export const AST_SCHEMA_ID = "{schema_id}" as const;', "// 原 UTF-8 半开字节范围;number 类型本身不验证安全整数、顺序或原文边界。", "export interface Span { start: number; end: number; }"]
|
||||
# 依稳定 schema 顺序处理每个定义,保留生成结果的可重复性。
|
||||
for definition in definitions.values():
|
||||
# 节点及字段说明均输出成独立注释行,不改变 TypeScript 的类型/执行语句。
|
||||
lines.append("// " + type_note(definition.name))
|
||||
if definition.fields is not None:
|
||||
for field in definition.fields:
|
||||
lines.append(f"// {field.name}: {field_note(definition.name, field)}")
|
||||
else:
|
||||
for variant in definition.variants:
|
||||
lines.append(f"// {variant.name}: {variant_note(definition.name, variant)}")
|
||||
for field in variant.fields:
|
||||
lines.append(f"// {variant.name}.{field.name}: {field_note(definition.name, field)}")
|
||||
# 产品按字段顺序生成;和类型则按构造器分支选择对应形状。
|
||||
if definition.fields is not None:
|
||||
lines.append(f"export type {definition.name} = {object_type(definition.fields)};")
|
||||
else:
|
||||
# 按顺序收集当前和类型构造器,供输出判别联合/枚举。
|
||||
variants = []
|
||||
# 每个构造器按稳定顺序生成,不合并名称相近但身份不同的分支。
|
||||
for variant in definition.variants:
|
||||
if definition.simple():
|
||||
# 无载荷简单枚举直接编码为字符串,不构造 kind/data 对象。
|
||||
value = json.dumps(variant.name)
|
||||
# 当前分支不是无载荷简单枚举,需要构造带 kind 及可选 data 的判别对象。
|
||||
else:
|
||||
# 无载荷分支没有 data 属性;确有载荷才生成相应 data 类型。
|
||||
data = ""
|
||||
# 仅有载荷的分支附加 data,单项直接取类型,多项包具名对象。
|
||||
if variant.fields:
|
||||
# 选择直接载荷或多字段对象,保持与 Rust serde 的 data 形状一致。
|
||||
result = field_type(variant.fields[0]) if len(variant.fields) == 1 else object_type(variant.fields)
|
||||
data = f" data: {result};"
|
||||
# 拼接当前构造器的 kind 字面量和数据对象声明。
|
||||
value = '{ kind: ' + json.dumps(variant.name) + ';' + data + ' }'
|
||||
variants.append(value)
|
||||
lines.append(f"export type {definition.name} = " + " | ".join(variants) + ";")
|
||||
lines += ["export interface Visitor {", "visitSpan?(node: Span): boolean | void;"]
|
||||
lines.extend(f"visit{name}?(node: {name}): boolean | void;" for name in definitions)
|
||||
lines += ["// 编辑器可选节点回调;walk<Type> 先回调,返回 false 只剪掉当前子树,其他值继续递归。", "export interface Visitor {", "// 跨度叶子回调没有子结构,其返回值不参与后续剪枝。", "visitSpan?(node: Span): boolean | void;"]
|
||||
for name in definitions:
|
||||
lines += [f"// walk{name} 到达 {name} 时调用,false 跳过该节点的后续子字段。", f"visit{name}?(node: {name}): boolean | void;"]
|
||||
lines.append("}")
|
||||
|
||||
# 访问器生成共用的字段递归模板:跳过普通文本/标识,仅递归节点或显式跨度。
|
||||
def children(field, access):
|
||||
# 字符串和 schema_id 无语法子节点,不能生成不存在的 visit_string/walkstring 调用。
|
||||
if field.type not in definitions and field.type != "span":
|
||||
return []
|
||||
# TS span 特殊走 walkSpan,其余命名类型使用对应 walk<Type>。
|
||||
function = "walkSpan" if field.type == "span" else f"walk{field.type}"
|
||||
# 序列优先映射到 Vec/Array/array;容器本身处理重复元素并打断 Rust 按值递归。
|
||||
if field.quantifier == "*":
|
||||
return [f"for (const value of {access}) {function}(value, visitor);"]
|
||||
return [f"// 按声明顺序访问 {access} 列表中的每个 {field.type} 子节点。", f"for (const value of {access}) {function}(value, visitor);"]
|
||||
# 可选字段只有 Some/非空时才递归,缺省或 null 不访问。
|
||||
if field.quantifier == "?":
|
||||
return [f"if ({access} != null) {function}({access}, visitor);"]
|
||||
return [f"{function}({access}, visitor);"]
|
||||
return [f"// {access} 可缺省或为 null,只有存在时才访问该子节点。", f"if ({access} != null) {function}({access}, visitor);"]
|
||||
return [f"// 递归访问 {access} 的 {field.type} 子结构或跨度。", f"{function}({access}, visitor);"]
|
||||
|
||||
lines.append("// 上层 walker/宿主调用的跨度叶子入口,仅通知 visitSpan,不验证输入或修改节点。")
|
||||
lines.append("export function walkSpan(node: Span, visitor: Visitor): void { visitor.visitSpan?.(node); }")
|
||||
# 依稳定 schema 顺序处理每个定义,保留生成结果的可重复性。
|
||||
for definition in definitions.values():
|
||||
lines += [f"export function walk{definition.name}(node: {definition.name}, visitor: Visitor): void {{", f"if (visitor.visit{definition.name}?.(node) === false) return;"]
|
||||
lines.append(f"// 上层 walker 或宿主调用,先通知 {definition.name} 回调,再按 ASDL 顺序访问子结构;不修改树。")
|
||||
lines += [f"export function walk{definition.name}(node: {definition.name}, visitor: Visitor): void {{", "// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。", f"if (visitor.visit{definition.name}?.(node) === false) return;"]
|
||||
# 产品按字段顺序生成;和类型则按构造器分支选择对应形状。
|
||||
if definition.fields is not None:
|
||||
# 产品字段依声明顺序递归,保证访问顺序与数据契约一致。
|
||||
for field in definition.fields:
|
||||
lines += children(field, "node." + field.name)
|
||||
# 带载荷和类型按判别分支访问,简单枚举没有子节点。
|
||||
elif not definition.simple():
|
||||
lines.append("// 判别 kind 只选择当前载荷,普通文本/数字拼写没有进一步语法子结构。")
|
||||
lines.append("switch (node.kind) {")
|
||||
# 每个构造器按稳定顺序生成,不合并名称相近但身份不同的分支。
|
||||
for variant in definition.variants:
|
||||
lines.append("// " + variant_note(definition.name, variant))
|
||||
lines.append(f"case {json.dumps(variant.name)}:")
|
||||
for field in variant.fields:
|
||||
# 单项载荷取 node.data,多字段载荷再按字段名取 node.data.<field>。
|
||||
access = "node.data" + ("." + field.name if len(variant.fields) > 1 else "")
|
||||
lines += children(field, access)
|
||||
lines.append("break;")
|
||||
@@ -334,41 +639,62 @@ def editor_types(definitions, schema_id):
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
# generate 生成编辑器 JSON Schema,结构与 Rust serde/TS 对齐;范围包含关系仍由运行时文档校验负责。
|
||||
def json_schema(definitions, schema_id):
|
||||
# 字段映射为内置约束或 $ref,并按数量修饰包数组或可空 anyOf。
|
||||
def field_schema(field):
|
||||
# 内置 string、schema_id 常量和 Span 引用特殊处理;其他字段引用对应定义。
|
||||
value = {"string": {"type": "string"}, "schema_id": {"const": schema_id}, "span": {"$ref": "#/$defs/Span"}}.get(field.type, {"$ref": f"#/$defs/{field.type}"})
|
||||
# 序列优先映射到 Vec/Array/array;容器本身处理重复元素并打断 Rust 按值递归。
|
||||
if field.quantifier == "*":
|
||||
value = {"type": "array", "items": value}
|
||||
# 问号字段允许空值,目标类型/Schema 同时处理字段缺省。
|
||||
elif field.quantifier == "?":
|
||||
value = {"anyOf": [value, {"type": "null"}]}
|
||||
return value
|
||||
|
||||
# 产品和带标签构造器共用,默认所有属性必需,可选字段调用方提供较小 required 列表。
|
||||
def object_schema(properties, required=None):
|
||||
return {"type": "object", "properties": properties, "required": list(properties) if required is None else required, "additionalProperties": False}
|
||||
|
||||
# Span 两端要求非负 JS 安全整数,但此 Schema 不表达 start<=end 或父子包含关系。
|
||||
definitions_json = {"Span": object_schema({name: {"type": "integer", "minimum": 0, "maximum": 9007199254740991} for name in ["start", "end"]})}
|
||||
# 依稳定 schema 顺序处理每个定义,保留生成结果的可重复性。
|
||||
for definition in definitions.values():
|
||||
# 产品按字段顺序生成;和类型则按构造器分支选择对应形状。
|
||||
if definition.fields is not None:
|
||||
# 产品属性以字段名为键,只有非问号字段进入 required,且拒绝额外属性。
|
||||
value = object_schema({f.name: field_schema(f) for f in definition.fields}, [f.name for f in definition.fields if f.quantifier != "?"])
|
||||
# 无载荷枚举映射成字符串 enum,与 serde 和 TS 字面量联合一致。
|
||||
elif definition.simple():
|
||||
# 每个枚举项使用 schema 中的构造器原名。
|
||||
value = {"enum": [v.name for v in definition.variants]}
|
||||
else:
|
||||
# 按顺序收集当前和类型构造器,供输出判别联合/枚举。
|
||||
variants = []
|
||||
# 每个构造器按稳定顺序生成,不合并名称相近但身份不同的分支。
|
||||
for variant in definition.variants:
|
||||
# 带载荷和类型先固定 kind 标签,有字段时再加入 data 约束。
|
||||
properties = {"kind": {"const": variant.name}}
|
||||
if variant.fields:
|
||||
properties["data"] = field_schema(variant.fields[0]) if len(variant.fields) == 1 else object_schema({f.name: field_schema(f) for f in variant.fields}, [f.name for f in variant.fields if f.quantifier != "?"])
|
||||
variants.append(object_schema(properties))
|
||||
# 联合要求恰匹配一个构造器,kind 常量把各分支区分开。
|
||||
value = {"oneOf": variants}
|
||||
definitions_json[definition.name] = value
|
||||
return json.dumps({"$schema": "https://json-schema.org/draft/2020-12/schema", "$id": schema_id, "$ref": "#/$defs/AstDocument", "$defs": definitions_json}, ensure_ascii=False, indent=2) + "\n"
|
||||
|
||||
|
||||
# main 和生成器回归测试调用的纯生成入口,返回目标相对路径到源码/Schema 文本,不写文件。
|
||||
def generate(text):
|
||||
# 先完成所有 ASDL 语法及引用校验,失败不返回部分产物。
|
||||
definitions = parse_schema(text)
|
||||
# 文档协议要求存在两个根定义,不能把任意 ASDL 片段当完整接口生成。
|
||||
for required in ("AstDocument", "AstRoot"):
|
||||
# 缺少入口类型时明确拒绝,避免输出互相引用但没有协议根的文件。
|
||||
if required not in definitions:
|
||||
raise ValueError(f"missing required schema type: {required}")
|
||||
# 协议身份对原始 ASDL 全文做 SHA-256,连注释或空白变化也会改变身份;缓存或传输文档应核对这一身份。
|
||||
schema_id = "urn:endor:ast:sha256:" + hashlib.sha256(text.encode("utf-8")).hexdigest()
|
||||
return {
|
||||
"crates/frontend/src/ast/generated.rs": rust_types(definitions, schema_id),
|
||||
@@ -378,28 +704,42 @@ def generate(text):
|
||||
}
|
||||
|
||||
|
||||
# 命令行 --check/--write 入口,先生成内存文本,Rust 交给 rustfmt,再按模式比较或写入源码文件。
|
||||
def main():
|
||||
# 建立命令行参数说明,check 与 write 必须且只能选择一个。
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
# 互斥参数组阻止检查和写入混用,避免验证命令隐式修改仓库。
|
||||
mode = parser.add_mutually_exclusive_group(required=True)
|
||||
mode.add_argument("--check", action="store_true")
|
||||
mode.add_argument("--write", action="store_true")
|
||||
# 解析参数后才定位仓库、读取 schema 及生成所有目标。
|
||||
args = parser.parse_args()
|
||||
# 脚本文件所在仓库根,不依赖用户运行命令时的工作目录。
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
# 读取原始 ASDL 文本一次,generate 为四个目标共享同一 schema 身份。
|
||||
outputs = generate((root / "crates/frontend/Endor.asdl").read_text(encoding="utf-8"))
|
||||
# check 模式收集缺失或内容过期的相对路径,最终统一报告。
|
||||
stale = []
|
||||
# 每个目标独立处理;仅 Rust 文本需标准格式化,TS/JSON 保留确定性模板输出。
|
||||
for relative, content in outputs.items():
|
||||
# rustfmt 只格式化源码并输出 stdout,不运行 Cargo、编译器或 Endor 代码。
|
||||
if relative.endswith(".rs"):
|
||||
content = subprocess.run(["rustfmt", "--edition", "2024", "--emit", "stdout"], input=content, text=True, capture_output=True, check=True).stdout
|
||||
# 目标路径相对于固定仓库根,防止工作目录改变写入位置。
|
||||
path = root / relative
|
||||
# 显式写入模式才创建父目录和更新文件,供开发者将生成源码纳入提交。
|
||||
if args.write:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content, encoding="utf-8")
|
||||
# 检查模式不写入;缺失目标或任意文本差异都算过期。
|
||||
elif not path.is_file() or path.read_text(encoding="utf-8") != content:
|
||||
stale.append(relative)
|
||||
# 存在过期目标时以非零状态退出,报告如何通过 --write 显式更新。
|
||||
if stale:
|
||||
parser.exit(1, "stale generated AST files:\n" + "\n".join(stale) + "\nRun python3 scripts/generate_ast.py --write\n")
|
||||
print("AST artifacts generated." if args.write else "AST artifacts match Endor.asdl.")
|
||||
|
||||
|
||||
# 直接运行脚本才解析命令行,测试导入 generate 等函数不会触发文件写入或格式化进程。
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -8,38 +8,54 @@ import unittest
|
||||
from generate_ast import generate, parse_schema, recursive_edges
|
||||
|
||||
|
||||
# 从固定仓库路径读取真实 ASDL,全体测试以同一协议输入为基线,不改磁盘 schema。
|
||||
SCHEMA = (Path(__file__).resolve().parents[1] / "crates/frontend/Endor.asdl").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
# unittest 直接调用下列回归场景;generate 仅返回内存文本,不触发 main 的 rustfmt 或文件写入。
|
||||
class AstGenerationTests(unittest.TestCase):
|
||||
# unittest 检查生成 TS 中每个 walk 调用都有声明,防止内置标量被错误生成为不存在的 walker。
|
||||
def test_editor_walkers_only_call_declared_functions(self):
|
||||
# 获取内存生成的编辑器源码,不导入或执行 TypeScript。
|
||||
editor = generate(SCHEMA)["crates/frontend/editor/ast.ts"]
|
||||
# 提取导出的 walk 函数名,用集合表示可调用的完整声明空间。
|
||||
declared = set(re.findall(r"export function (walk\w+)\(", editor))
|
||||
# 提取所有 walk 调用名(含声明中的名字),每项必须属于 declared。
|
||||
calls = set(re.findall(r"\b(walk\w+)\(", editor))
|
||||
self.assertLessEqual(calls, declared)
|
||||
self.assertIn("walkSpan(node.span, visitor)", editor)
|
||||
|
||||
# unittest 在内存 schema 增加构造器,确认三种语言契约及协议身份跟着更新且生成结果确定。
|
||||
def test_schema_changes_reach_all_languages_and_schema_identity(self):
|
||||
# 原 schema 的全目标输出,作为修改后的身份和源码比较基线。
|
||||
before = generate(SCHEMA)
|
||||
# 只给 UnaryOp 增加 LogicalNot,不写原 ASDL 或生成文件;输出应包含新分支。
|
||||
after = generate(SCHEMA.replace("UnaryOp = Negate", "UnaryOp = Negate | LogicalNot"))
|
||||
self.assertIn("LogicalNot", after["crates/frontend/src/ast/generated.rs"])
|
||||
self.assertIn('"LogicalNot"', after["crates/frontend/editor/ast.ts"])
|
||||
# JSON Schema 在内存解析,检查 UnaryOp 的 enum 同样获得新项。
|
||||
editor_schema = json.loads(after["crates/frontend/editor/ast.schema.json"])
|
||||
self.assertIn("LogicalNot", editor_schema["$defs"]["UnaryOp"]["enum"])
|
||||
self.assertNotEqual(before["crates/frontend/src/ast/generated.rs"], after["crates/frontend/src/ast/generated.rs"])
|
||||
self.assertEqual(after, generate(SCHEMA.replace("UnaryOp = Negate", "UnaryOp = Negate | LogicalNot")))
|
||||
|
||||
# unittest 增加可选自引用 Link,验证 Rust 的 Option<Box>、访问递归及 TS 可空/可缺省形状。
|
||||
def test_recursive_optional_nodes_are_boxed_and_walked(self):
|
||||
# 在模块右括号前追加测试定义,原始 SCHEMA 常量保持不变。
|
||||
extended = SCHEMA.rsplit("}", 1)[0] + "Link = (Link? next)\n}"
|
||||
# 一次生成得到所有目标源码,分别核对同一递归边在不同目标上的表示。
|
||||
outputs = generate(extended)
|
||||
self.assertIn("pub next: Option<Box<Link>>", outputs["crates/frontend/src/ast/generated.rs"])
|
||||
self.assertIn("visitor.visit_link(value)?", outputs["crates/frontend/src/ast/visitor.rs"])
|
||||
self.assertIn("next?: Link | null", outputs["crates/frontend/editor/ast.ts"])
|
||||
# 直接解析真实 schema 检查 DFS 选择的回边,不要求所有递归方向都装箱。
|
||||
definitions = parse_schema(SCHEMA)
|
||||
self.assertIn(("ExprKind", "Expr"), recursive_edges(definitions))
|
||||
self.assertNotIn(("Expr", "ExprKind"), recursive_edges(definitions))
|
||||
|
||||
# unittest 覆盖未定义类型、重名、不完整、尾随输入及未知字符,均须失败而非输出部分类型。
|
||||
def test_invalid_schemas_fail_instead_of_emitting_partial_types(self):
|
||||
# 每份输入只针对一个非法结构,subTest 让失败报告指明当前 schema。
|
||||
for schema in [
|
||||
"module Endor { Root = (Missing value) }",
|
||||
"module Endor { Root = (string x, string x) }",
|
||||
@@ -49,9 +65,11 @@ class AstGenerationTests(unittest.TestCase):
|
||||
"module Endor { Root = A } trailing",
|
||||
"module Endor { Root = @ }",
|
||||
]:
|
||||
# 当前子用例要求 parse_schema 明确抛 ValueError,不接受静默成功或无关异常。
|
||||
with self.subTest(schema=schema), self.assertRaises(ValueError):
|
||||
parse_schema(schema)
|
||||
|
||||
|
||||
# 脚本直接执行时启动 unittest;被其他检查导入时不自动运行用例。
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user