diff --git a/src/ast/environment.rs b/src/ast/environment.rs index b426b04..f04ce96 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -7,7 +7,7 @@ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; -use crate::ast::compiler::bound_nodes::{Address, BoundNode}; +use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode}; use crate::ast::compiler::dumper::Dumper; use crate::ast::compiler::lambda_collector::LambdaCollector; use crate::ast::compiler::macros::{MacroEvaluator, MacroExpander, MacroRegistry}; @@ -118,8 +118,7 @@ impl Environment { &self, name: &str, ty: StaticType, - purity_level: Purity, - func: impl Fn(Vec) -> Value + 'static, + func: Rc, ) { let mut names = self.global_names.borrow_mut(); let mut types = self.global_types.borrow_mut(); @@ -129,8 +128,26 @@ impl Environment { let idx = values.len() as u32; names.insert(Symbol::from(name), idx); types.insert(idx, ty); - purity.insert(idx, purity_level); - values.push(Value::make_function(purity_level, func)); + purity.insert(idx, func.purity); + values.push(Value::Function(func)); + } + + /// Utility to register a native function from a closure. + pub fn register_native_fn( + &self, + name: &str, + ty: StaticType, + purity_level: Purity, + func: impl Fn(Vec) -> Value + 'static, + ) { + self.register_native( + name, + ty, + Rc::new(crate::ast::types::NativeFunction { + func: Rc::new(func), + purity: purity_level, + }), + ); } pub fn register_constant(&self, name: &str, ty: StaticType, val: Value) { @@ -190,7 +207,7 @@ impl Environment { Ok(typed_ast) } - /// Backend: Optimization (TCO, etc.) + /// Backend Phase 1: Optimization (TCO, etc.) and Lowering pub fn link(&self, node: TypedNode) -> ExecNode { // 1. Specialize (Always performed for correctness) let specialized = self.specialize_node(node); @@ -206,6 +223,72 @@ impl Environment { TCO::optimize(optimized) } + /// Backend Phase 2: Packaging into an invokable NativeFunction + pub fn instantiate(&self, node: ExecNode) -> Rc { + let global_values = self.global_values.clone(); + + // OPTIMIZATION: Fast path for top-level Lambdas. + // If the script root is a Lambda with no captures (root functions usually have none), + // we can pre-create the closure and call it directly. + if let BoundKind::Lambda { + params, + upvalues, + body, + positional_count, + } = &node.kind + && upvalues.is_empty() + { + let closure = Rc::new(crate::ast::vm::Closure::new( + params.ty.original.clone(), + body.ty.original.clone(), + body.clone(), + vec![], + *positional_count, + )); + + return Rc::new(crate::ast::types::NativeFunction { + purity: Purity::Impure, + func: Rc::new(move |args| { + let mut vm = VM::new(global_values.clone()); + let res = match vm.run_with_args(&closure, args) { + Ok(v) => v, + Err(e) => panic!("Myc Runtime Error: {}", e), + }; + vm.resolve_tail_calls(res) + }), + }); + } + + // FALLBACK: Generic script body (Block, If, etc.) + let exec_node = Rc::new(node); + Rc::new(crate::ast::types::NativeFunction { + purity: Purity::Impure, + func: Rc::new(move |args| { + let mut vm = VM::new(global_values.clone()); + + // 1. Execute the main body (Block, etc.) + let res = match vm.run(&exec_node) { + Ok(v) => v, + Err(e) => panic!("Myc Runtime Error: {}", e), + }; + + // 2. Auto-apply: If the script returned a closure, we apply arguments to it. + let mut final_res = res; + if let Value::Object(obj) = &final_res { + if let Some(closure) = obj.as_any().downcast_ref::() { + final_res = match vm.run_with_args(closure, args) { + Ok(v) => v, + Err(e) => panic!("Myc Runtime Error (Closure): {}", e), + }; + } + } + + // 3. Resolve Tail Calls + vm.resolve_tail_calls(final_res) + }), + }) + } + fn specialize_node(&self, node: TypedNode) -> TypedNode { let registry = Rc::new(EnvFunctionRegistry { registry: self.function_registry.clone(), @@ -281,33 +364,6 @@ impl Environment { specializer.specialize(node) } - /// Runtime: Execute the linked AST in the VM - pub fn run(&self, node: &ExecNode) -> Result { - let mut vm = VM::new(self.global_values.clone()); - let mut result = vm.run(node)?; - - // Handle potential script body closure - if let Value::Object(obj) = &result - && let Some(closure) = obj.as_any().downcast_ref::() - { - result = vm.run(&closure.exec_node)?; - } - - // IMPORTANT: Resolve any pending tail call requests from the top-level execution - while let Value::TailCallRequest(payload) = result { - let (next_obj, next_args) = *payload; - if let Some(closure) = next_obj.as_any().downcast_ref::() { - result = vm.run_with_args(closure, next_args)?; - } else { - return Err(format!( - "Tail call target is not a closure: {}", - next_obj.type_name() - )); - } - } - Ok(result) - } - pub fn run_script(&self, source: &str) -> Result { if self.debug_mode { let (res, logs) = self.run_debug(source)?; @@ -318,7 +374,8 @@ impl Environment { } else { let compiled = self.compile(source)?; let linked = self.link(compiled); - self.run(&linked) + let func = self.instantiate(linked); + Ok((func.func)(vec![])) } } diff --git a/src/ast/rtl/core.rs b/src/ast/rtl/core.rs index 2a1ad0a..7dece9a 100644 --- a/src/ast/rtl/core.rs +++ b/src/ast/rtl/core.rs @@ -39,7 +39,7 @@ fn register_arithmetic(env: &Environment) { ret: StaticType::DateTime, }, ]); - env.register_native("+", add_ty, Purity::Pure, |args| { + env.register_native_fn("+", add_ty, Purity::Pure, |args| { if args.len() == 2 { match (&args[0], &args[1]) { (Value::Int(a), Value::Int(b)) => Value::Int(a + b), @@ -99,7 +99,7 @@ fn register_arithmetic(env: &Environment) { ret: StaticType::DateTime, }, ]); - env.register_native("-", sub_ty, Purity::Pure, |args| { + env.register_native_fn("-", sub_ty, Purity::Pure, |args| { if args.is_empty() { return Value::Void; } @@ -152,7 +152,7 @@ fn register_arithmetic(env: &Environment) { ret: StaticType::Float, }, ]); - env.register_native("*", mul_ty, Purity::Pure, |args| { + env.register_native_fn("*", mul_ty, Purity::Pure, |args| { if args.len() == 2 { match (&args[0], &args[1]) { (Value::Int(a), Value::Int(b)) => Value::Int(a * b), @@ -189,7 +189,7 @@ fn register_arithmetic(env: &Environment) { ret: StaticType::Float, }, ]); - env.register_native("/", div_ty, Purity::Pure, |args| { + env.register_native_fn("/", div_ty, Purity::Pure, |args| { if args.len() != 2 { return Value::Void; } @@ -215,7 +215,7 @@ fn register_arithmetic(env: &Environment) { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int, })); - env.register_native("//", int_div_ty, Purity::Pure, |args| { + env.register_native_fn("//", int_div_ty, Purity::Pure, |args| { if args.len() != 2 { return Value::Void; } @@ -238,7 +238,7 @@ fn register_arithmetic(env: &Environment) { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int, })); - env.register_native("%", mod_ty, Purity::Pure, |args| { + env.register_native_fn("%", mod_ty, Purity::Pure, |args| { if args.len() != 2 { return Value::Void; } @@ -272,7 +272,7 @@ fn register_comparison(env: &Environment) { ]); // --- Greater Than (>) --- - env.register_native(">", cmp_ty.clone(), Purity::Pure, |args| { + env.register_native_fn(">", cmp_ty.clone(), Purity::Pure, |args| { if args.len() != 2 { return Value::Void; } @@ -287,7 +287,7 @@ fn register_comparison(env: &Environment) { }); // --- Less Than (<) --- - env.register_native("<", cmp_ty.clone(), Purity::Pure, |args| { + env.register_native_fn("<", cmp_ty.clone(), Purity::Pure, |args| { if args.len() != 2 { return Value::Void; } @@ -302,7 +302,7 @@ fn register_comparison(env: &Environment) { }); // --- Greater Or Equal (>=) --- - env.register_native(">=", cmp_ty.clone(), Purity::Pure, |args| { + env.register_native_fn(">=", cmp_ty.clone(), Purity::Pure, |args| { if args.len() != 2 { return Value::Void; } @@ -317,7 +317,7 @@ fn register_comparison(env: &Environment) { }); // --- Less Or Equal (<=) --- - env.register_native("<=", cmp_ty.clone(), Purity::Pure, |args| { + env.register_native_fn("<=", cmp_ty.clone(), Purity::Pure, |args| { if args.len() != 2 { return Value::Void; } @@ -336,7 +336,7 @@ fn register_comparison(env: &Environment) { params: StaticType::Tuple(vec![StaticType::Any, StaticType::Any]), ret: StaticType::Bool, })); - env.register_native("=", eq_ty.clone(), Purity::Pure, |args| { + env.register_native_fn("=", eq_ty.clone(), Purity::Pure, |args| { if args.len() != 2 { return Value::Void; } @@ -357,7 +357,7 @@ fn register_comparison(env: &Environment) { }); // --- Not Equal (<>) --- - env.register_native("<>", eq_ty, Purity::Pure, |args| { + env.register_native_fn("<>", eq_ty, Purity::Pure, |args| { if args.len() != 2 { return Value::Void; } @@ -388,7 +388,7 @@ fn register_logic(env: &Environment) { ret: StaticType::Int, }, ]); - env.register_native("not", not_ty, Purity::Pure, |args| { + env.register_native_fn("not", not_ty, Purity::Pure, |args| { if args.len() != 1 { return Value::Void; } @@ -410,7 +410,7 @@ fn register_logic(env: &Environment) { ret: StaticType::Int, }, ]); - env.register_native("and", logic_op_ty.clone(), Purity::Pure, |args| { + env.register_native_fn("and", logic_op_ty.clone(), Purity::Pure, |args| { if args.len() != 2 { return Value::Void; } @@ -422,7 +422,7 @@ fn register_logic(env: &Environment) { }); // --- Or (or) --- - env.register_native("or", logic_op_ty.clone(), Purity::Pure, |args| { + env.register_native_fn("or", logic_op_ty.clone(), Purity::Pure, |args| { if args.len() != 2 { return Value::Void; } @@ -434,7 +434,7 @@ fn register_logic(env: &Environment) { }); // --- Xor (xor) --- - env.register_native("xor", logic_op_ty.clone(), Purity::Pure, |args| { + env.register_native_fn("xor", logic_op_ty.clone(), Purity::Pure, |args| { if args.len() != 2 { return Value::Void; } @@ -450,7 +450,7 @@ fn register_logic(env: &Environment) { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int, })); - env.register_native("<<", shift_ty.clone(), Purity::Pure, |args| { + env.register_native_fn("<<", shift_ty.clone(), Purity::Pure, |args| { if args.len() != 2 { return Value::Void; } @@ -461,7 +461,7 @@ fn register_logic(env: &Environment) { }); // --- Shift Right (>>) --- - env.register_native(">>", shift_ty, Purity::Pure, |args| { + env.register_native_fn(">>", shift_ty, Purity::Pure, |args| { if args.len() != 2 { return Value::Void; } diff --git a/src/ast/rtl/datetime.rs b/src/ast/rtl/datetime.rs index 081624c..6c00313 100644 --- a/src/ast/rtl/datetime.rs +++ b/src/ast/rtl/datetime.rs @@ -8,7 +8,7 @@ pub fn register(env: &Environment) { ret: StaticType::DateTime, })); - env.register_native("date", date_ty, Purity::Pure, |args| { + env.register_native_fn("date", date_ty, Purity::Pure, |args| { if let Value::Text(s) = &args[0] { // Try parse YYYY-MM-DD if let Ok(dt) = NaiveDate::parse_from_str(s, "%Y-%m-%d") { @@ -32,7 +32,7 @@ pub fn register(env: &Environment) { ret: StaticType::DateTime, })); - env.register_native("now", now_ty, Purity::SideEffectFree, |_| { + env.register_native_fn("now", now_ty, Purity::SideEffectFree, |_| { let ts = chrono::Utc::now().timestamp_millis(); Value::DateTime(ts) }); diff --git a/src/ast/rtl/math.rs b/src/ast/rtl/math.rs index 7b6eb0a..0efc319 100644 --- a/src/ast/rtl/math.rs +++ b/src/ast/rtl/math.rs @@ -22,7 +22,7 @@ pub fn register(env: &Environment) { params: StaticType::Tuple(vec![StaticType::Float]), ret: StaticType::Float, })); - env.register_native(name, ty, Purity::Pure, move |args| { + env.register_native_fn(name, ty, Purity::Pure, move |args| { if let Some(x) = to_f64(&args[0]) { Value::Float(f(x)) } else { @@ -37,7 +37,7 @@ pub fn register(env: &Environment) { params: StaticType::Tuple(vec![StaticType::Float]), ret: StaticType::Int, })); - env.register_native(name, ty, Purity::Pure, move |args| { + env.register_native_fn(name, ty, Purity::Pure, move |args| { if let Some(x) = to_f64(&args[0]) { Value::Int(f(x) as i64) } else { @@ -70,7 +70,7 @@ pub fn register(env: &Environment) { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float, })); - env.register_native(name, ty, Purity::Pure, move |args| { + env.register_native_fn(name, ty, Purity::Pure, move |args| { if let (Some(a), Some(b)) = (to_f64(&args[0]), to_f64(&args[1])) { Value::Float(f(a, b)) } else { @@ -88,7 +88,7 @@ pub fn register(env: &Environment) { params: StaticType::Tuple(vec![StaticType::Any]), ret: StaticType::Any, })); - env.register_native("abs", abs_ty, Purity::Pure, |args| { + env.register_native_fn("abs", abs_ty, Purity::Pure, |args| { if args.len() != 1 { return Value::Void; } @@ -105,7 +105,7 @@ pub fn register(env: &Environment) { ret: StaticType::Any, })); - env.register_native("min", variadic_ty.clone(), Purity::Pure, |args| { + env.register_native_fn("min", variadic_ty.clone(), Purity::Pure, |args| { if args.is_empty() { return Value::Void; } @@ -120,7 +120,7 @@ pub fn register(env: &Environment) { best }); - env.register_native("max", variadic_ty, Purity::Pure, |args| { + env.register_native_fn("max", variadic_ty, Purity::Pure, |args| { if args.is_empty() { return Value::Void; } @@ -142,7 +142,7 @@ pub fn register(env: &Environment) { })); let prng = env.prng.clone(); - env.register_native("random", random_ty, Purity::SideEffectFree, move |_| { + env.register_native_fn("random", random_ty, Purity::SideEffectFree, move |_| { Value::Float(prng.borrow_mut().f64()) }); @@ -152,7 +152,7 @@ pub fn register(env: &Environment) { })); let prng_seed = env.prng.clone(); - env.register_native("seed!", seed_ty, Purity::Impure, move |args| { + env.register_native_fn("seed!", seed_ty, Purity::Impure, move |args| { if let Value::Int(s) = args[0] { prng_seed.borrow_mut().seed(s as u64); } diff --git a/src/ast/vm.rs b/src/ast/vm.rs index 6d5ab42..9300d7a 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -361,6 +361,25 @@ impl VM { dispatch_eval!(self, node, eval) } + /// Resolves potential tail call requests iteratively until a final value is reached. + pub fn resolve_tail_calls(&mut self, mut result: Value) -> Value { + while let Value::TailCallRequest(payload) = result { + let (next_obj, next_args) = *payload; + if let Some(closure) = next_obj.as_any().downcast_ref::() { + result = match self.run_with_args(closure, next_args) { + Ok(v) => v, + Err(e) => panic!("Myc Runtime Error (TailCall): {}", e), + }; + } else { + panic!( + "Tail call target is not a closure: {}", + next_obj.type_name() + ); + } + } + result + } + fn eval_observed( &mut self, observer: &mut O, diff --git a/src/integration_test.rs b/src/integration_test.rs index ce3470a..d97fd74 100644 --- a/src/integration_test.rs +++ b/src/integration_test.rs @@ -71,10 +71,11 @@ mod tests { let env = Environment::new(); let compiled = env.compile(source).expect("Failed to compile"); let linked = env.link(compiled); - let result = env.run(&linked); + let func = env.instantiate(linked); + let result: Result = Ok((func.func)(vec![])); match result { - Ok(Value::Int(val)) => assert_eq!(val, 20, "Closure should modify outer variable"), + Ok(Value::Int(20)) => (), Ok(val) => panic!("Expected Int(20), got {:?}", val), Err(e) => panic!("VM Error: {}", e), } diff --git a/src/utils/tester.rs b/src/utils/tester.rs index 28c0b28..c57e49b 100644 --- a/src/utils/tester.rs +++ b/src/utils/tester.rs @@ -123,10 +123,11 @@ pub fn run_benchmarks(update: bool) -> Vec { let env = Environment::new(); // Link once per sample let linked = env.link(node.clone()); + let func = env.instantiate(linked); let mut total = Duration::ZERO; for _ in 0..n { let start = Instant::now(); - let _ = env.run(&linked)?; + let _ = (func.func)(vec![]); total += start.elapsed(); } Ok(total)