diff --git a/examples/closure.myc b/examples/closure.myc index bf02503..b44f791 100644 --- a/examples/closure.myc +++ b/examples/closure.myc @@ -1,5 +1,5 @@ ;; Closure Scope Test -;; Benchmark: 600ns +;; Benchmark: 700ns ;; Output: 15 (do (def make-adder (fn [x] diff --git a/examples/data.myc b/examples/data.myc index 916fcd2..c97be19 100644 --- a/examples/data.myc +++ b/examples/data.myc @@ -1,5 +1,5 @@ ;; Complex Data Structure Test -;; Benchmark: 49.4us +;; Benchmark: 51.8us ;; Output: {:input 10, :name "Fibonacci", :output 55} (do (def fib (fn [n] diff --git a/examples/extreme_capture.myc b/examples/extreme_capture.myc index 9b80bfc..e86b19e 100644 --- a/examples/extreme_capture.myc +++ b/examples/extreme_capture.myc @@ -1,4 +1,4 @@ -;; Benchmark: 1.5us +;; Benchmark: 1.6us ;; Output: 36 (do ;; Excessive capture test diff --git a/examples/macro_power.myc b/examples/macro_power.myc index c1c85b3..0b0b025 100644 --- a/examples/macro_power.myc +++ b/examples/macro_power.myc @@ -1,4 +1,4 @@ -;; Benchmark: 300ns +;; Benchmark: 400ns ;; Nested Macro and Arithmetic example ;; Output: 81 diff --git a/examples/macro_splice.myc b/examples/macro_splice.myc index a6f4a28..9a2a931 100644 --- a/examples/macro_splice.myc +++ b/examples/macro_splice.myc @@ -1,4 +1,4 @@ -;; Benchmark: 200ns +;; Benchmark: 300ns ;; Macro Splicing example ;; Demonstrates unrolling a list into another list. ;; Output: [0 1 2 3 4] diff --git a/examples/macro_unless.myc b/examples/macro_unless.myc index dcd7f78..63d8e0e 100644 --- a/examples/macro_unless.myc +++ b/examples/macro_unless.myc @@ -1,4 +1,4 @@ -;; Benchmark: 100ns +;; Benchmark: 200ns ;; Classic "unless" macro example ;; Demonstrates simple template substitution. ;; Output: 42 diff --git a/examples/tak.myc b/examples/tak.myc index b46a2c7..568f61e 100644 --- a/examples/tak.myc +++ b/examples/tak.myc @@ -1,17 +1,16 @@ -;; Benchmark: 602.1us ;; Takeuchi Function Benchmark -;; Benchmark: ~150ms (Specialized) vs ~1.5s (Dynamic) ;; heavily recursive, benefits significantly from specialization of integer arithmetic ;; and direct function calls (skipping dynamic dispatch). -;; Output: 7 - +;; Output: 4 + ;; Benchmark: 468.7us + (do (def tak (fn [x y z] (if (<= x y) - y + z (tak (tak (- x 1) y z) (tak (- y 1) z x) (tak (- z 1) x y))))) - (tak 10 6 3) + (tak 12 8 4) ) diff --git a/examples/tco_test.myc b/examples/tco_test.myc index 2ac1006..02e98f0 100644 --- a/examples/tco_test.myc +++ b/examples/tco_test.myc @@ -1,4 +1,4 @@ -;; Benchmark: 2.7ms +;; Benchmark: 2.8ms ;; Output: "done" (do (def count_down (fn [n] diff --git a/src/ast/compiler/specializer.rs b/src/ast/compiler/specializer.rs index c2ae00c..4d64fda 100644 --- a/src/ast/compiler/specializer.rs +++ b/src/ast/compiler/specializer.rs @@ -232,7 +232,7 @@ mod tests { // Mock Registry struct MockRegistry { - functions: HashMap, + functions: HashMap, } impl MockRegistry { @@ -240,13 +240,13 @@ mod tests { Self { functions: HashMap::new() } } - fn register(&mut self, addr: Address, node: TypedNode) { + fn register(&mut self, addr: Address, node: BoundNode) { self.functions.insert(addr, node); } } impl FunctionRegistry for MockRegistry { - fn resolve(&self, addr: Address) -> Option { + fn resolve(&self, addr: Address) -> Option { self.functions.get(&addr).cloned() } } @@ -259,19 +259,19 @@ mod tests { let name = Symbol::from("test_func"); // Def: (fn [x] x) -- generic identity - let func_node = TypedNode { + let func_node = BoundNode { identity: make_identity(), kind: BoundKind::Lambda { param_count: 1, upvalues: vec![], - body: Rc::new(TypedNode { identity: make_identity(), kind: BoundKind::Nop, ty: StaticType::Void }) + body: Rc::new(BoundNode { identity: make_identity(), kind: BoundKind::Nop, ty: () }) }, - ty: StaticType::Void + ty: () }; registry.register(addr, func_node); // Setup Compiler Mock - let compiler: CompileFunc = Rc::new(|_node: TypedNode, _args: &[StaticType]| -> Result<(Value, StaticType), String> { + let compiler: CompileFunc = Rc::new(|_node: BoundNode, _args: &[StaticType]| -> Result<(Value, StaticType), String> { // Return a specialized "compiled" value Ok((Value::Int(12345), StaticType::Int)) }); diff --git a/src/ast/compiler/type_checker.rs b/src/ast/compiler/type_checker.rs index 59a42b7..02216d9 100644 --- a/src/ast/compiler/type_checker.rs +++ b/src/ast/compiler/type_checker.rs @@ -288,42 +288,54 @@ mod tests { env.compile(source).unwrap() } + fn get_ret_type(node: &TypedNode) -> StaticType { + if let StaticType::Function(sig) = &node.ty { + sig.ret.clone() + } else { + node.ty.clone() + } + } + #[test] fn test_inference_constants() { - assert_eq!(check_source("10").ty, StaticType::Int); - assert_eq!(check_source("10.5").ty, StaticType::Float); - assert_eq!(check_source("true").ty, StaticType::Bool); - assert_eq!(check_source("\"hello\"").ty, StaticType::Text); + assert_eq!(get_ret_type(&check_source("10")), StaticType::Int); + assert_eq!(get_ret_type(&check_source("10.5")), StaticType::Float); + assert_eq!(get_ret_type(&check_source("true")), StaticType::Bool); + assert_eq!(get_ret_type(&check_source("\"hello\"")), StaticType::Text); } #[test] fn test_inference_variable_propagation() { // (do (def x 10) x) -> The last 'x' must be Int let typed = check_source("(do (def x 10) x)"); - if let BoundKind::Block { exprs } = typed.kind { - let last_expr = exprs.last().unwrap(); - assert_eq!(last_expr.ty, StaticType::Int, "Variable 'x' should be inferred as Int"); - } else { panic!("Expected block"); } + // Outer is Lambda, Body is Block + if let BoundKind::Lambda { body, .. } = &typed.kind { + if let BoundKind::Block { exprs } = &body.kind { + let last_expr = exprs.last().unwrap(); + assert_eq!(last_expr.ty, StaticType::Int, "Variable 'x' should be inferred as Int"); + } else { panic!("Expected block in lambda body"); } + } else { panic!("Expected Lambda wrapper"); } } #[test] fn test_inference_block_type() { // Block type = last expression type - assert_eq!(check_source("(do 1 2.5)").ty, StaticType::Float); - assert_eq!(check_source("(do 1.5 \"test\")").ty, StaticType::Text); + assert_eq!(get_ret_type(&check_source("(do 1 2.5)")), StaticType::Float); + assert_eq!(get_ret_type(&check_source("(do 1.5 \"test\")")), StaticType::Text); } #[test] fn test_inference_lambda_return() { // (fn [a] 10) -> fn(any) -> Int + // Since it's already a Lambda, it's NOT wrapped further. let typed = check_source("(fn [a] 10)"); - if let StaticType::Function(sig) = typed.ty { + if let StaticType::Function(sig) = &typed.ty { assert_eq!(sig.ret, StaticType::Int); } else { panic!("Expected function type, got {:?}", typed.ty); } // Nested: (fn [] (do 1 2.5)) -> fn() -> Float let typed_nested = check_source("(fn [] (do 1 2.5))"); - if let StaticType::Function(sig) = typed_nested.ty { + if let StaticType::Function(sig) = &typed_nested.ty { assert_eq!(sig.ret, StaticType::Float); } else { panic!("Expected function type"); } } @@ -332,29 +344,31 @@ mod tests { fn test_inference_assignment_updates_type() { // (do (def x 10) (assign x 20.5) x) -> x becomes Float after assignment let typed = check_source("(do (def x 10) (assign x 20.5) x)"); - if let BoundKind::Block { exprs } = typed.kind { - let last_expr = exprs.last().unwrap(); - assert_eq!(last_expr.ty, StaticType::Float, "Variable 'x' should be specialized to Float after assignment"); - } else { panic!("Expected block"); } + if let BoundKind::Lambda { body, .. } = &typed.kind { + if let BoundKind::Block { exprs } = &body.kind { + let last_expr = exprs.last().unwrap(); + assert_eq!(last_expr.ty, StaticType::Float, "Variable 'x' should be specialized to Float after assignment"); + } else { panic!("Expected block"); } + } else { panic!("Expected Lambda"); } } #[test] fn test_operator_overloading_inference() { - assert_eq!(check_source("(+ 1 2)").ty, StaticType::Int); - assert_eq!(check_source("(+ 1.0 2.0)").ty, StaticType::Float); - assert_eq!(check_source("(+ \"a\" \"b\")").ty, StaticType::Text); - assert_eq!(check_source("(/ 1 2)").ty, StaticType::Float); + assert_eq!(get_ret_type(&check_source("(+ 1 2)")), StaticType::Int); + assert_eq!(get_ret_type(&check_source("(+ 1.0 2.0)")), StaticType::Float); + assert_eq!(get_ret_type(&check_source("(+ \"a\" \"b\")")), StaticType::Text); + assert_eq!(get_ret_type(&check_source("(/ 1 2)")), StaticType::Float); } #[test] fn test_datetime_inference() { // date("2023-01-01") -> DateTime - assert_eq!(check_source("(date \"2023-01-01\")").ty, StaticType::DateTime); + assert_eq!(get_ret_type(&check_source("(date \"2023-01-01\")")), StaticType::DateTime); // DateTime + Int -> DateTime - assert_eq!(check_source("(+ (date \"2023-01-01\") 86400000)").ty, StaticType::DateTime); + assert_eq!(get_ret_type(&check_source("(+ (date \"2023-01-01\") 86400000)")), StaticType::DateTime); // DateTime - DateTime -> Int (Duration) - assert_eq!(check_source("(- (date \"2023-01-02\") (date \"2023-01-01\"))").ty, StaticType::Int); + assert_eq!(get_ret_type(&check_source("(- (date \"2023-01-02\") (date \"2023-01-01\"))")), StaticType::Int); // DateTime comparison -> Bool - assert_eq!(check_source("(> (date \"2023-01-02\") (date \"2023-01-01\"))").ty, StaticType::Bool); + assert_eq!(get_ret_type(&check_source("(> (date \"2023-01-02\") (date \"2023-01-01\"))")), StaticType::Bool); } } diff --git a/src/ast/environment.rs b/src/ast/environment.rs index 9d77d68..f7ee9f2 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -45,10 +45,8 @@ struct RuntimeMacroEvaluator { global_names: Rc>>, global_types: Rc>>, global_values: Rc>>, - function_registry: Rc>>, } - impl MacroEvaluator for RuntimeMacroEvaluator { fn evaluate(&self, node: &Node, bindings: &HashMap, Node>) -> Result { // 1. Check if it's a simple parameter substitution @@ -97,7 +95,6 @@ impl Environment { global_names: self.global_names.clone(), global_types: self.global_types.clone(), global_values: self.global_values.clone(), - function_registry: self.function_registry.clone(), }; MacroExpander::new(MacroRegistry::new(), evaluator) } @@ -234,7 +231,16 @@ impl Environment { /// Runtime: Execute the linked AST in the VM pub fn run(&self, node: &TypedNode) -> Result { let mut vm = VM::new(self.global_values.clone()); - vm.run(node) + let result = vm.run(node)?; + + if let Value::Object(obj) = &result { + if let Some(closure) = obj.as_any().downcast_ref::() { + // Execute the script body + return vm.run(&closure.function_node); + } + } + + Ok(result) } pub fn run_script(&self, source: &str) -> Result { @@ -258,8 +264,15 @@ impl Environment { // Execute with TracingObserver let mut vm = VM::new(self.global_values.clone()); let mut observer = TracingObserver::new(); - let result = vm.run_with_observer(&mut observer, &linked); + let mut result = vm.run_with_observer(&mut observer, &linked); + // If result is a closure (script entry), execute the body too + if let Ok(Value::Object(obj)) = &result { + if let Some(closure) = obj.as_any().downcast_ref::() { + result = vm.run_with_observer(&mut observer, &closure.function_node); + } + } + Ok((result, observer.logs)) } } diff --git a/src/ast/vm.rs b/src/ast/vm.rs index 4826106..64aea82 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -288,10 +288,36 @@ impl VM { closure: None, }); - let result = self.eval(root); - + let mut result = self.eval(root); self.frames.pop(); - result + + loop { + match result { + Ok(Value::TailCallRequest(payload)) => { + let (next_obj, next_args) = *payload; + if let Some(closure) = next_obj.as_any().downcast_ref::() { + let old_stack_top = 0; // Root is always base 0 + let closure_rc = Rc::new(closure.clone()); + + // Reset stack for the next call (TCO) + self.stack.clear(); + self.stack.extend(next_args); + + self.frames.push(CallFrame { + stack_base: old_stack_top, + closure: Some(closure_rc), + }); + + result = self.eval(&closure.function_node); + + self.frames.pop(); + } else { + return Err(format!("Tail call target is not a closure: {}", next_obj.type_name())); + } + } + _ => return result, + } + } } pub fn run_with_observer(&mut self, observer: &mut O, root: &TypedNode) -> Result { diff --git a/src/integration_test.rs b/src/integration_test.rs index 529c2e6..a71ba30 100644 --- a/src/integration_test.rs +++ b/src/integration_test.rs @@ -1,10 +1,6 @@ #[cfg(test)] mod tests { - use std::rc::Rc; - use std::cell::RefCell; use crate::ast::parser::Parser; - use crate::ast::compiler::binder::Binder; - use crate::ast::vm::VM; use crate::ast::types::Value; use crate::ast::nodes::UntypedKind; use crate::ast::environment::Environment; @@ -73,21 +69,10 @@ mod tests { ) "#; - let mut parser = Parser::new(source).expect("Failed to create parser"); - let ast = parser.parse_expression().expect("Failed to parse"); - - let globals = Rc::new(RefCell::new(std::collections::HashMap::new())); - let mut binder = Binder::new(globals.clone()); - let bound_ast = binder.bind(&ast).expect("Failed to bind"); - - // Use the real TypeChecker instead of the dummy placeholder - let global_types = Rc::new(RefCell::new(std::collections::HashMap::new())); - let checker = crate::ast::compiler::TypeChecker::new(global_types); - let typed_ast = checker.check(bound_ast).expect("Type check failed"); - - let vm_globals = Rc::new(RefCell::new(Vec::new())); - let mut vm = VM::new(vm_globals); - let result = vm.run(&typed_ast); + let env = Environment::new(); + let compiled = env.compile(source).expect("Failed to compile"); + let linked = env.link(compiled); + let result = env.run(&linked); match result { Ok(Value::Int(val)) => assert_eq!(val, 20, "Closure should modify outer variable"),