//! 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, Value, Vm, VmError, }; use frontend::{DiagnosticKind, EndorReader, ParseMode, SourceUnit, parse_ast}; /// 测试共用的源码入口,按 mode 编译,不声明额外宿主名;返回未加载、独立于 VM 堆的模板。 fn compile(text: &str, mode: ParseMode) -> CompiledCode { compile_source( &EndorReader::from_text("", text), mode, &CompileOptions::default(), ) .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 { // 环境键命中必须是函数对象;这里只借用,不新取得外部根。 let Some(Value::Object(function)) = vm.dict_get(globals, &Value::Text(name.into())).unwrap() else { panic!("missing function {name}") }; function } /// 测试结束时归还代码根、清环境并归还环境根,再收集引用环;预期结果根已由调用者释放。 fn cleanup(vm: &mut Vm, code: ObjectRef, globals: ObjectRef) { // 归还本轮代码外部根,函数或返回结果需要的内部图仍由各自引用持有。 vm.release(code).unwrap(); // 清除环境拥有边,打断声明函数/类型到环境的可能引用环。 vm.dict_clear(globals).unwrap(); // 归还环境初始根;之后只应留下测试明确保存的结果或回调根。 vm.release(globals).unwrap(); // 清理零外根的引用环,随后对象数应回到零。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] /// 由 Rust 测试框架调用:逐个加载示例模块并调用 main,验证前向调用、函数值遮蔽、Unicode 返回及精确十进制,并检查代码名和清理。 fn source_files_execute_through_module_and_function_code_objects() { // 逐项使用独立代码路径核对指定源码与预期值,任一不符都使本测试失败。 for (text, expected) in [ ( include_str!("../examples/hello.endor"), Value::Text("你好,Endor 🌱".into()), ), ( include_str!("../examples/calls.endor"), Value::Text("函数调用已接通".into()), ), ( include_str!("../examples/shadowing.endor"), Value::Text("局部名称优先".into()), ), ( include_str!("../examples/number.endor"), Value::Number(EndorNumber::decimal("-123456789012345678901234567890.00100").unwrap()), ), ] { // 编译得到模板,尚未分配 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::(main, "function").unwrap(); assert_eq!( vm.payload::(data.code(), "code") .unwrap() .code() .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"), Value::Text("表达式 → 字节码 → VM".into()), ), ( "-99999999999999999999999999999999999999", Value::Number(EndorNumber::integer("-99999999999999999999999999999999999999").unwrap()), ), ("--00042", Value::Number(42_i64.into())), ( "-0.000", Value::Number(EndorNumber::decimal("-0.000").unwrap()), ), ] { // 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。 let mut vm = Vm::new().unwrap(); // 表达式模板在当前 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"]); assert_eq!( symbols.functions[0] .references .iter() .map(|reference| reference.binding) .collect::>(), [ NameBinding::Local(0), NameBinding::Global(0), NameBinding::Local(1) ] ); // 当前源码函数不捕获外层局部,所有代码布局的自由变量表都应为空。 assert!( symbols .functions .iter() .all(|scope| scope.layout.free_variables.is_empty()) ); } #[test] /// 由 Rust 测试框架调用:参数/局部/顶层重复、未声明及未初始化读取都在编译时拒绝,并检查 Unicode 与 CRLF 的诊断行列。 fn semantic_errors_prevent_loading_and_keep_unicode_locations() { // 每例给出非法源码及应包含的诊断片段,确保失败原因与目标规则对应。 for (text, fragment) in [ ( "function f(): String { return missing }", "undefined name: missing", ), ("function f(x: T, x: T): T { return x }", "declaration: x"), ( "function f(x: T): T { let x = 1 return x }", "declaration: x", ), ( "function f(): T { let x = 1 let x = 2 return x }", "declaration: x", ), ( "function f(): T { return 1 } function f(): T { return 2 }", "declaration: f", ), ( "function f(): T { let x = x return x }", "before initialization: x", ), ( "function f(): T { let x = later let later = 1 return x }", "before initialization: later", ), ( "function later(): T { return 1 } function f(): T { let x = later let later = 1 return x }", "before initialization: later", ), ] { // 保留来源名和原始源码,编译诊断应带有正确位置及错误说明。 let unit = EndorReader::from_text("", text); // 预期编译失败,之后检查诊断阶段或位置,不能走加载执行路径。 let error = compile_source(&unit, ParseMode::Module, &CompileOptions::default()).unwrap_err(); assert_eq!(error.diagnostic.kind, DiagnosticKind::Semantic); assert!(error.to_string().contains(fragment), "{error}"); } // 保留来源名和原始源码,编译诊断应带有正确位置及错误说明。 let unit = EndorReader::from_text("<日本語>", "function 表示(): T {\r\n return 未定義\r\n}"); // 预期编译失败,之后检查诊断阶段或位置,不能走加载执行路径。 let error = compile_source(&unit, ParseMode::Module, &CompileOptions::default()).unwrap_err(); assert_eq!((error.location.line, error.location.column), (2, 10)); assert!(error.to_string().starts_with("<日本語>:2:10:")); } /// 宿主 NativeFunction 调用的恒等回调;参数为借用值,clone 不增对象根,VM 返回协议负责保留结果。 fn identity(arguments: &[Value]) -> Result { // 恒等回调只接受一个实参,多参/缺参明确报错而非越界取参数。 if arguments.len() != 1 { return Err(VmError::InvalidArgument("identity needs one argument")); } Ok(arguments[0].clone()) } #[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("", "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, Value::Text("service".into()), Value::Object(service), ) .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(); } assert_eq!(left.object_count(), before); cleanup(&mut left, left_code, left_globals); cleanup(&mut right, right_code, right_globals); } #[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, .. }) )); assert_eq!(vm.object_count(), baseline); assert_eq!(vm.last_traceback().last().unwrap().first_line, 2); cleanup(&mut vm, code, globals); // 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。 let mut vm = Vm::new().unwrap(); // 加载并执行模块声明,保存两个拥有根,之后统一交 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( main, &[], ExecutionLimits { max_frames: 4, max_instructions: 100 } ), Err(VmError::ExecutionLimit("frames")) ); assert_eq!(vm.last_traceback().len(), 4); cleanup(&mut vm, code, globals); } #[test] /// 由 Rust 测试框架调用:编译选项只声明合法名字,不注入 VM 值;空环境执行仍报告 NameNotFound。 fn declared_host_names_are_checked_again_at_runtime() { // 当前用例的源码或带来源快照,下面按指定模式解析/编译。 let source = EndorReader::from_text("", "missing"); // 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。 let compiled = compile_source( &source, ParseMode::Expression, &CompileOptions { globals: vec!["missing".into()], }, ) .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), Err(VmError::NameNotFound("missing".into())) ); cleanup(&mut vm, code, globals); } #[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 = \"保留\"", CompileOptions::default(), Value::Nil, ), ( "value", CompileOptions { globals: vec!["value".into()], }, Value::Text("保留".into()), ), ] { // 当前用例的源码或带来源快照,下面按指定模式解析/编译。 let source = EndorReader::from_text("", 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("", ".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("", "\"日\""); // 解析合法表达式 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. } assert!(compile_ast(&root, &source, &CompileOptions::default()).is_err()); } #[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("", 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"), &[Value::Text("original".into())], ) .unwrap(); assert_eq!(result, Value::Text("updated".into())); // 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。 vm.release_value(result).unwrap(); cleanup(&mut vm, code, globals); assert!( compile_source( &EndorReader::from_text("", "function f(): T { x = x return x }"), ParseMode::Module, &CompileOptions::default() ) .is_err() ); assert!( compile_source( &EndorReader::from_text( "", "function f(): T { let x = 1 let x = 2 return x }" ), ParseMode::Module, &CompileOptions::default() ) .is_err() ); } #[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() .unwrap() .source_signature() .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 { .. }) )); assert_eq!(vm.object_count(), baseline); cleanup(&mut vm, code, globals); } #[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)), ( "100000000000000000000 + 1 > 100000000000000000000", Value::Bool(true), ), ("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 {"); // 重复运行同一函数三次,每次归还结果,以对象数验证代码复用。 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}", ] { assert!( compile_source( &EndorReader::from_text("", text), ParseMode::Module, &CompileOptions::default() ) .is_err() ); } // 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。 let (code, globals) = module( &mut vm, &compile( "function main(): Unit { while 1 { value=2 } }", ParseMode::Module, ), ); // 无限循环没有返回出口,必须由累计指令预算终止,之后 cleanup 验证错误清理。 assert!(matches!( vm.call_with_limits( function(&vm, globals, "main"), &[], ExecutionLimits { max_frames: 10, max_instructions: 100 } ), Err(VmError::ExecutionLimit("instructions")) )); cleanup(&mut vm, code, globals); }