use compiler::{CompileOptions, CompiledCode, NameBinding, analyze, compile_ast, compile_source}; use endor_core::{ EndorCodeObject, EndorFunctionObject, EndorNumber, ExecutionLimits, NativeFunction, ObjectRef, Value, Vm, VmError, }; use frontend::{DiagnosticKind, EndorReader, ParseMode, SourceUnit, parse_ast}; fn compile(text: &str, mode: ParseMode) -> CompiledCode { compile_source( &EndorReader::from_text("", text), mode, &CompileOptions::default(), ) .unwrap() } fn module(vm: &mut Vm, compiled: &CompiledCode) -> (ObjectRef, ObjectRef) { let code = compiled.load(vm).unwrap(); let globals = vm.new_dict().unwrap(); let value = vm.execute(code, globals).unwrap(); assert_eq!(value, Value::Nil); vm.release_value(value).unwrap(); (code, globals) } fn function(vm: &Vm, globals: ObjectRef, name: &str) -> ObjectRef { let Some(Value::Object(function)) = vm.dict_get(globals, &Value::Text(name.into())).unwrap() else { panic!("missing function {name}") }; function } fn cleanup(vm: &mut Vm, code: ObjectRef, globals: ObjectRef) { vm.release(code).unwrap(); vm.dict_clear(globals).unwrap(); vm.release(globals).unwrap(); vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] fn source_files_execute_through_module_and_function_code_objects() { for (text, expected) in [ ( include_str!("../examples/hello.endor"), Value::Text("你好,Endor 🌱".into()), ), ( include_str!("../examples/calls.endor"), Value::Text("函数调用已接通".into()), ), ( include_str!("../examples/shadowing.endor"), Value::Text("局部名称优先".into()), ), ( include_str!("../examples/number.endor"), Value::Number(EndorNumber::decimal("-123456789012345678901234567890.00100").unwrap()), ), ] { let compiled = compile(text, ParseMode::Module); let mut vm = Vm::new().unwrap(); let (code, globals) = module(&mut vm, &compiled); let main = function(&vm, globals, "main"); let data = vm.payload::(main, "function").unwrap(); assert_eq!( vm.payload::(data.code(), "code") .unwrap() .code() .name(), "main" ); let result = vm.call(main, &[]).unwrap(); assert_eq!(result, expected); vm.release_value(result).unwrap(); cleanup(&mut vm, code, globals); } } #[test] fn expression_mode_preserves_exact_literals_and_signs() { for (text, expected) in [ ( include_str!("../examples/literal.endor"), Value::Text("表达式 → 字节码 → VM".into()), ), ( "-99999999999999999999999999999999999999", Value::Number(EndorNumber::integer("-99999999999999999999999999999999999999").unwrap()), ), ("--00042", Value::Number(42_i64.into())), ( "-0.000", Value::Number(EndorNumber::decimal("-0.000").unwrap()), ), ] { let mut vm = Vm::new().unwrap(); let code = compile(text, ParseMode::Expression).load(&mut vm).unwrap(); let globals = vm.new_dict().unwrap(); let value = vm.execute(code, globals).unwrap(); assert_eq!(value, expected); vm.release_value(value).unwrap(); cleanup(&mut vm, code, globals); } } #[test] fn scope_analysis_resolves_local_shadowing_and_forward_globals() { let source = "function main(target: Callable): String { let copy = target return later(copy) } function later(value: String): String { return value }"; let root = parse_ast(source, ParseMode::Module).unwrap(); let symbols = analyze(&root, &CompileOptions::default()).unwrap(); assert_eq!(symbols.functions[0].layout.parameters, ["target"]); assert_eq!(symbols.functions[0].layout.locals, ["target", "copy"]); assert_eq!( symbols.functions[0] .references .iter() .map(|reference| reference.binding) .collect::>(), [ NameBinding::Local(0), NameBinding::Global(0), NameBinding::Local(1) ] ); assert!( symbols .functions .iter() .all(|scope| scope.layout.free_variables.is_empty()) ); } #[test] fn semantic_errors_prevent_loading_and_keep_unicode_locations() { for (text, fragment) in [ ( "function f(): String { return missing }", "undefined name: missing", ), ("function f(x: T, x: T): T { return x }", "declaration: x"), ( "function f(x: T): T { let x = 1 return x }", "declaration: x", ), ( "function f(): T { let x = 1 let x = 2 return x }", "declaration: x", ), ( "function f(): T { return 1 } function f(): T { return 2 }", "declaration: f", ), ( "function f(): T { let x = x return x }", "before initialization: x", ), ( "function f(): T { let x = later let later = 1 return x }", "before initialization: later", ), ( "function later(): T { return 1 } function f(): T { let x = later let later = 1 return x }", "before initialization: later", ), ] { let unit = EndorReader::from_text("", text); let error = compile_source(&unit, ParseMode::Module, &CompileOptions::default()).unwrap_err(); assert_eq!(error.diagnostic.kind, DiagnosticKind::Semantic); assert!(error.to_string().contains(fragment), "{error}"); } let unit = EndorReader::from_text("<日本語>", "function 表示(): T {\r\n return 未定義\r\n}"); let error = compile_source(&unit, ParseMode::Module, &CompileOptions::default()).unwrap_err(); assert_eq!((error.location.line, error.location.column), (2, 10)); assert!(error.to_string().starts_with("<日本語>:2:10:")); } fn identity(arguments: &[Value]) -> Result { if arguments.len() != 1 { return Err(VmError::InvalidArgument("identity needs one argument")); } Ok(arguments[0].clone()) } #[test] fn host_globals_member_calls_and_object_results_keep_owned_roots() { let options = CompileOptions { globals: vec!["service".into(), "value".into()], }; let unit = EndorReader::from_text("", "service.echo(value)"); let compiled = compile_source(&unit, ParseMode::Expression, &options).unwrap(); let mut vm = Vm::new().unwrap(); let native = vm.allocate(NativeFunction::new("echo", identity)).unwrap(); let service = vm.new_object().unwrap(); vm.set_attr(service, "echo", Value::Object(native)).unwrap(); vm.release(native).unwrap(); let object = vm.new_object().unwrap(); let globals = vm.new_dict().unwrap(); vm.dict_set( globals, Value::Text("service".into()), Value::Object(service), ) .unwrap(); vm.dict_set(globals, Value::Text("value".into()), Value::Object(object)) .unwrap(); vm.release(service).unwrap(); vm.release(object).unwrap(); let code = compiled.load(&mut vm).unwrap(); let result = vm.execute(code, globals).unwrap(); assert_eq!(result, Value::Object(object)); vm.dict_clear(globals).unwrap(); assert!(vm.object(object).is_ok()); vm.release_value(result).unwrap(); assert_eq!(vm.object(object).unwrap_err(), VmError::InvalidHandle); cleanup(&mut vm, code, globals); } #[test] fn code_templates_load_independently_and_functions_share_loaded_code() { let compiled = compile(include_str!("../examples/calls.endor"), ParseMode::Module); let mut left = Vm::new().unwrap(); let mut right = Vm::new().unwrap(); let (left_code, left_globals) = module(&mut left, &compiled); let (right_code, right_globals) = module(&mut right, &compiled); assert_eq!(left.object(right_code).unwrap_err(), VmError::ForeignHandle); let before = left.object_count(); let main = function(&left, left_globals, "main"); for _ in 0..3 { let result = left.call(main, &[]).unwrap(); left.release_value(result).unwrap(); } assert_eq!(left.object_count(), before); cleanup(&mut left, left_code, left_globals); cleanup(&mut right, right_code, right_globals); } #[test] fn argument_and_recursion_failures_unwind_vm_frames_and_keep_traceback() { let source = "\nfunction main(): T { return echo() }\nfunction echo(value: T): T { return value }"; let compiled = compile(source, ParseMode::Module); let mut vm = Vm::new().unwrap(); let (code, globals) = module(&mut vm, &compiled); let baseline = vm.object_count(); let main = function(&vm, globals, "main"); assert!(matches!( vm.call(main, &[]), Err(VmError::ArgumentCount { provided: 0, .. }) )); assert_eq!(vm.object_count(), baseline); assert_eq!(vm.last_traceback().last().unwrap().first_line, 2); cleanup(&mut vm, code, globals); let mut vm = Vm::new().unwrap(); let (code, globals) = module( &mut vm, &compile("function main(): T { return main() }", ParseMode::Module), ); let main = function(&vm, globals, "main"); assert_eq!( vm.call_with_limits( main, &[], ExecutionLimits { max_frames: 4, max_instructions: 100 } ), Err(VmError::ExecutionLimit("frames")) ); assert_eq!(vm.last_traceback().len(), 4); cleanup(&mut vm, code, globals); } #[test] fn declared_host_names_are_checked_again_at_runtime() { let source = EndorReader::from_text("", "missing"); let compiled = compile_source( &source, ParseMode::Expression, &CompileOptions { globals: vec!["missing".into()], }, ) .unwrap(); let mut vm = Vm::new().unwrap(); let code = compiled.load(&mut vm).unwrap(); let globals = vm.new_dict().unwrap(); assert_eq!( vm.execute(code, globals), Err(VmError::NameNotFound("missing".into())) ); cleanup(&mut vm, code, globals); } #[test] fn interactive_submissions_can_share_a_host_owned_namespace() { let mut vm = Vm::new().unwrap(); let globals = vm.new_dict().unwrap(); for (text, options, expected) in [ ( "let value = \"保留\"", CompileOptions::default(), Value::Nil, ), ( "value", CompileOptions { globals: vec!["value".into()], }, Value::Text("保留".into()), ), ] { let source = EndorReader::from_text("", text); let compiled = compile_source(&source, ParseMode::Interactive, &options).unwrap(); let code = compiled.load(&mut vm).unwrap(); let result = vm.execute(code, globals).unwrap(); assert_eq!(result, expected); vm.release_value(result).unwrap(); vm.release(code).unwrap(); } vm.dict_clear(globals).unwrap(); vm.release(globals).unwrap(); assert_eq!(vm.object_count(), 0); } #[test] fn extended_operands_work_for_large_local_and_constant_tables() { let mut source = String::from("function main(): Number { "); for index in 0..270 { source.push_str(&format!("let value{index} = {index} ")); } source.push_str("return value269 }"); let mut vm = Vm::new().unwrap(); let (code, globals) = module(&mut vm, &compile(&source, ParseMode::Module)); let main = function(&vm, globals, "main"); let result = vm.call(main, &[]).unwrap(); assert_eq!(result, Value::Number(269_i64.into())); vm.release_value(result).unwrap(); cleanup(&mut vm, code, globals); } #[test] fn unsupported_execution_features_are_compile_errors_instead_of_fake_results() { 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] fn compiling_ast_rejects_invalid_source_ranges() { let source: SourceUnit = EndorReader::from_text("", "\"日\""); let mut root = parse_ast(source.text(), ParseMode::Expression).unwrap(); if let frontend::ast::AstRoot::Expression(expression) = &mut root { expression.span.end = 2; // Splits the three-byte character. } assert!(compile_ast(&root, &source, &CompileOptions::default()).is_err()); } #[test] 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"] { let options = CompileOptions { globals: vm .dict_items(globals) .unwrap() .iter() .filter_map(|(key, _)| match key { Value::Text(name) => Some(name.clone()), _ => None, }) .collect(), }; let compiled = compile_source( &EndorReader::from_text("", text), ParseMode::Interactive, &options, ) .unwrap(); let code = compiled.load(&mut vm).unwrap(); let result = vm.execute(code, globals).unwrap(); 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] fn assignments_update_parameters_and_allocate_one_local_slot_per_target() { let compiled = compile( "function main(value: String): String { copy = value copy = \"updated\" value = copy return value }", ParseMode::Module, ); let mut vm = Vm::new().unwrap(); let (code, globals) = module(&mut vm, &compiled); 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] fn source_signatures_survive_template_load_and_enable_runtime_checks() { 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(); let (code, globals) = module(&mut vm, &compiled); let identity = function(&vm, globals, "identity"); assert_eq!( vm.call(identity, &[Value::Number(7.into())]).unwrap(), Value::Number(7.into()) ); assert!(matches!( vm.call(identity, &[Value::Text("wrong".into())]), Err(VmError::TypeMismatch { .. }) )); let bad = function(&vm, globals, "bad"); let baseline = vm.object_count(); assert!(matches!( vm.call(bad, &[]), Err(VmError::TypeMismatch { .. }) )); assert_eq!(vm.object_count(), baseline); cleanup(&mut vm, code, globals); } #[test] fn source_control_flow_and_exact_arithmetic_execute_real_bytecode() { let mut vm = Vm::new().unwrap(); 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 }"; let (code, globals) = module(&mut vm, &compile(text, ParseMode::Module)); let main = function(&vm, globals, "main"); 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())), ] { let code = compile(text, ParseMode::Expression).load(&mut vm).unwrap(); let globals = vm.new_dict().unwrap(); 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] fn control_flow_definite_initialization_large_jumps_and_loop_budgets() { 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(); let (code, globals) = module(&mut vm, &compile(&body, ParseMode::Module)); 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); 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() ); } let (code, globals) = module( &mut vm, &compile( "function main(): Unit { while 1 { value=2 } }", ParseMode::Module, ), ); 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); }