diff --git a/src/ast/compiler/optimizer/engine.rs b/src/ast/compiler/optimizer/engine.rs index 51e9398..7c55641 100644 --- a/src/ast/compiler/optimizer/engine.rs +++ b/src/ast/compiler/optimizer/engine.rs @@ -3,7 +3,7 @@ use crate::ast::nodes::{ IdentifierBinding, LambdaBinding, Node, NodeKind, UpvalueIdx, VirtualId, }; use crate::ast::types::{Purity, Value}; -use crate::ast::closure::Closure; + use std::cell::RefCell; use std::rc::Rc; @@ -343,9 +343,9 @@ impl Optimizer { } } - if let NodeKind::Constant(Value::Object(ref obj)) = callee_opt.kind + if let NodeKind::Constant(Value::Closure(ref closure_rc)) = callee_opt.kind && path.inlining_depth < 5 - && let Some(closure) = obj.as_any().downcast_ref::() + && let closure = closure_rc.as_ref() && (closure.upvalues.is_empty() || closure.function_node.ty.purity >= Purity::SideEffectFree) && !closure.function_node.ty.is_recursive diff --git a/src/ast/compiler/optimizer/inliner.rs b/src/ast/compiler/optimizer/inliner.rs index 71e4694..6ba1897 100644 --- a/src/ast/compiler/optimizer/inliner.rs +++ b/src/ast/compiler/optimizer/inliner.rs @@ -2,7 +2,7 @@ use crate::ast::nodes::{ Address, AnalyzedNode, IdentifierBinding, NodeKind, VirtualId, }; use crate::ast::types::{Purity, Value}; -use crate::ast::closure::Closure; + use std::cell::RefCell; use std::collections::HashSet; use std::rc::Rc; @@ -35,12 +35,8 @@ impl<'a> Inliner<'a> { | Value::Keyword(_) | Value::Record(_, _) | Value::DateTime(_) => true, - Value::Object(obj) => { - if let Some(closure) = obj.as_any().downcast_ref::() { - closure.upvalues.is_empty() && !closure.function_node.ty.is_recursive - } else { - false - } + Value::Closure(closure_rc) => { + closure_rc.upvalues.is_empty() && !closure_rc.function_node.ty.is_recursive } _ => false, }; diff --git a/src/ast/compiler/optimizer/utils.rs b/src/ast/compiler/optimizer/utils.rs index 833f420..8399512 100644 --- a/src/ast/compiler/optimizer/utils.rs +++ b/src/ast/compiler/optimizer/utils.rs @@ -3,7 +3,7 @@ use crate::ast::nodes::{ NodeKind, VirtualId, }; use crate::ast::types::{Identity, Value}; -use crate::ast::closure::Closure; + use std::collections::HashSet; // --- PathTracker --- @@ -56,12 +56,10 @@ impl UsageInfo { self.collect(value); } NodeKind::Constant(v) => { - if let Value::Object(obj) = v - && let Some(closure) = obj.as_any().downcast_ref::() - { + if let Value::Closure(closure_rc) = v { self.used_identities - .insert(closure.function_node.identity.clone()); - self.collect(&closure.function_node); + .insert(closure_rc.function_node.identity.clone()); + self.collect(&closure_rc.function_node); } } NodeKind::Identifier { binding, .. } => { diff --git a/src/ast/environment.rs b/src/ast/environment.rs index 1a0cc2c..6db7312 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -668,13 +668,11 @@ impl Environment { info.positional_count, node.ty.stack_size, )); - let closure_obj: Rc = closure; - return Rc::new(NativeFunction { purity: Purity::Impure, func: Rc::new(move |args| { let mut vm = VM::new(root_values.clone()); - match vm.run_with_args(closure_obj.clone(), args) { + match vm.run_with_args(closure.clone(), args) { Ok(v) => v, Err(e) => panic!("Myc Runtime Error: {}", e), } @@ -692,12 +690,7 @@ impl Environment { Err(e) => panic!("Myc Runtime Error: {}", e), }; - if let Value::Object(obj) = &res - && obj - .as_any() - .downcast_ref::() - .is_some() - { + if let Value::Closure(obj) = &res { match vm.run_with_args(obj.clone(), args) { Ok(v) => v, Err(e) => panic!("Myc Runtime Error (Closure): {}", e), @@ -837,9 +830,7 @@ impl Environment { // 2. Execute the root closure immediately to get the actual script result. // All Myc scripts are wrapped in a parameterless lambda for consistency. let mut final_result = result; - if let Ok(Value::Object(obj)) = &final_result - && let Some(_closure) = obj.as_any().downcast_ref::() - { + if let Ok(Value::Closure(obj)) = &final_result { final_result = vm.run_with_args_observed(&mut observer, obj.clone(), &[]); } diff --git a/src/ast/rtl/streams.rs b/src/ast/rtl/streams.rs index d45f8f6..45d27eb 100644 --- a/src/ast/rtl/streams.rs +++ b/src/ast/rtl/streams.rs @@ -432,8 +432,8 @@ pub fn register(env: &Environment) { panic!("create-ticker expects exactly 1 argument (the condition closure)"); } - let closure_obj = if let Value::Object(obj) = &args[0] { - obj.clone() + let closure_obj = if let Value::Closure(rc) = &args[0] { + rc.clone() } else { panic!("create-ticker expects a closure as its argument"); }; diff --git a/src/ast/types.rs b/src/ast/types.rs index 9410701..e63d14b 100644 --- a/src/ast/types.rs +++ b/src/ast/types.rs @@ -1,3 +1,4 @@ +use crate::ast::closure::Closure; use chrono::{TimeZone, Utc}; use std::any::Any; use std::cell::RefCell; @@ -257,7 +258,8 @@ pub enum Value { Record(std::sync::Arc, ValueList), FieldAccessor(Keyword), Function(Rc), - Object(Rc), // For compiled Closures and other opaque types + Closure(Rc), // Compiled function with captured upvalues + Object(Rc), // For Series, Streams, SyntaxNodes, and custom extensions Cell(Rc>), // Boxed value for captures } @@ -277,6 +279,7 @@ impl PartialEq for Value { } (Value::FieldAccessor(a), Value::FieldAccessor(b)) => a == b, (Value::Function(a), Value::Function(b)) => Rc::ptr_eq(a, b), + (Value::Closure(a), Value::Closure(b)) => Rc::ptr_eq(a, b), (Value::Object(a), Value::Object(b)) => Rc::ptr_eq(a, b), (Value::Cell(a), Value::Cell(b)) => Rc::ptr_eq(a, b), _ => false, @@ -617,6 +620,7 @@ impl Value { Value::Record(layout, _) => StaticType::Record(layout.clone()), Value::FieldAccessor(k) => StaticType::FieldAccessor(*k), Value::Function(_) => StaticType::Any, // Dynamic function + Value::Closure(_) => StaticType::Object("closure"), Value::Object(o) => StaticType::Object(o.type_name()), Value::Cell(c) => c.borrow().static_type(), } @@ -660,6 +664,7 @@ impl fmt::Display for Value { } Value::FieldAccessor(k) => write!(f, ".{}", k.name()), Value::Function(f_meta) => write!(f, "", f_meta.purity), + Value::Closure(_) => write!(f, ""), Value::Object(o) => write!(f, "<{:?}>", o), Value::Cell(c) => write!(f, "{}", c.borrow()), } diff --git a/src/ast/vm.rs b/src/ast/vm.rs index ec20d4d..b2b58f6 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -2,7 +2,7 @@ use crate::ast::closure::Closure; use crate::ast::nodes::{Address, ExecNode, IdentifierBinding, NodeKind, StackOffset}; use crate::ast::rtl::series::{RecordSeries, SeriesView}; use crate::ast::rtl::streams::{build_map_stream, build_pipeline_node, StreamNode}; -use crate::ast::types::{Object, PipeFn, Value}; +use crate::ast::types::{PipeFn, Value}; use std::cell::RefCell; use std::rc::Rc; @@ -88,7 +88,7 @@ pub struct VM { frames: Vec, /// Side-channel for tail-call signaling. Set by `eval_internal` when a tail-call /// is requested; consumed by `resolve_tail_calls` and the inline TCO loop. - tail_call: Option<(Rc, Vec)>, + tail_call: Option<(Value, Vec)>, } impl VM { @@ -111,60 +111,68 @@ impl VM { mut result: Result, ) -> Result { loop { - if let Some((next_obj, next_args)) = self.tail_call.take() { - if let Ok(closure_rc) = next_obj.clone().into_rc_any().downcast::() { - self.stack.clear(); - // frames should be empty here since we popped before entering the loop - self.frames.push(CallFrame { - stack_base: 0, - closure: Some(closure_rc.clone()), - }); - let closure = closure_rc.as_ref(); - if let Some(count) = closure.positional_count - && next_args.len() == count as usize - { - self.stack.extend(next_args); - } else { - self.unpack(closure.parameter_node.as_ref(), &next_args, &mut 0)?; - } + if let Some((next_val, next_args)) = self.tail_call.take() { + match next_val { + Value::Closure(closure_rc) => { + self.stack.clear(); + // frames should be empty here since we popped before entering the loop + self.frames.push(CallFrame { + stack_base: 0, + closure: Some(closure_rc.clone()), + }); + let closure = closure_rc.as_ref(); + if let Some(count) = closure.positional_count + && next_args.len() == count as usize + { + self.stack.extend(next_args); + } else { + self.unpack(closure.parameter_node.as_ref(), &next_args, &mut 0)?; + } - // PRE-ALLOCATION - let current_stack = self.stack.len() as u32; - if closure.stack_size > current_stack { - self.stack.resize(closure.stack_size as usize, Value::Void); - } + // PRE-ALLOCATION + let current_stack = self.stack.len() as u32; + if closure.stack_size > current_stack { + self.stack.resize(closure.stack_size as usize, Value::Void); + } - result = self.eval_observed(observer, &closure.exec_node); - self.frames.pop(); - } else if let Some(series) = next_obj.as_series() { - if next_args.len() != 1 { - return Err(format!( - "{} indexer expects exactly 1 argument (the lookback index), got {}", - next_obj.type_name(), - next_args.len() - )); + result = self.eval_observed(observer, &closure.exec_node); + self.frames.pop(); } - if let Value::Int(idx) = &next_args[0] { - if *idx < 0 { + Value::Object(obj) => { + if let Some(series) = obj.as_series() { + if next_args.len() != 1 { + return Err(format!( + "{} indexer expects exactly 1 argument (the lookback index), got {}", + obj.type_name(), + next_args.len() + )); + } + if let Value::Int(idx) = &next_args[0] { + if *idx < 0 { + return Err(format!( + "{} lookback index cannot be negative: {}", + obj.type_name(), + idx + )); + } + result = Ok(series.get_item(*idx as usize).unwrap_or(Value::Void)); + } else { + return Err(format!( + "{} index must be an integer, got {}", + obj.type_name(), + next_args[0] + )); + } + } else { return Err(format!( - "{} lookback index cannot be negative: {}", - next_obj.type_name(), - idx + "Tail call target is not callable: {}", + obj.type_name() )); } - result = Ok(series.get_item(*idx as usize).unwrap_or(Value::Void)); - } else { - return Err(format!( - "{} index must be an integer, got {}", - next_obj.type_name(), - next_args[0] - )); } - } else { - return Err(format!( - "Tail call target is not callable: {}", - next_obj.type_name() - )); + other => { + return Err(format!("Tail call target is not callable: {}", other)); + } } } else { return result; @@ -174,23 +182,18 @@ impl VM { pub fn run_with_args( &mut self, - closure_obj: Rc, + closure_rc: Rc, args: &[Value], ) -> Result { - self.run_with_args_observed(&mut NoOpObserver, closure_obj, args) + self.run_with_args_observed(&mut NoOpObserver, closure_rc, args) } pub fn run_with_args_observed( &mut self, observer: &mut O, - closure_obj: Rc, + closure_rc: Rc, args: &[Value], ) -> Result { - let closure_rc = closure_obj - .into_rc_any() - .downcast::() - .map_err(|_| "Object is not a closure".to_string())?; - self.stack.clear(); self.frames.clear(); @@ -416,8 +419,8 @@ impl VM { let mut pipe_vm = VM::new(self.globals.clone()); let executor: Box = match lambda_val { - Value::Object(obj) => { - let my_closure = obj.clone(); + Value::Closure(rc) => { + let my_closure = rc.clone(); Box::new(move |args: &[Value]| -> Value { match pipe_vm.run_with_args(my_closure.clone(), args) { Ok(res) => res, @@ -465,7 +468,7 @@ impl VM { info.positional_count, stack_size, ); - Ok(Value::Object(Rc::new(closure))) + Ok(Value::Closure(Rc::new(closure))) } NodeKind::Call { callee, args } => { let func_val = self.eval_internal(obs, callee)?; @@ -481,8 +484,8 @@ impl VM { self.stack.truncate(base); match func_val { - Value::Object(obj) => { - self.tail_call = Some((obj, arg_vals)); + Value::Closure(_) | Value::Object(_) => { + self.tail_call = Some((func_val, arg_vals)); return Ok(Value::Void); } Value::Function(f) => return Ok((f.func)(&arg_vals)), @@ -612,57 +615,56 @@ impl VM { self.stack.truncate(base); return res; } - Value::Object(obj) => { - if let Ok(closure_rc) = - obj.clone().into_rc_any().downcast::() + Value::Closure(closure_rc) => { + self.frames.push(CallFrame { + stack_base: base, + closure: Some(closure_rc.clone()), + }); + let closure = closure_rc.as_ref(); + + let unpack_res = if let Some(count) = closure.positional_count + && (self.stack.len() - base) == count as usize { - self.frames.push(CallFrame { - stack_base: base, - closure: Some(closure_rc.clone()), - }); - let closure = closure_rc.as_ref(); - - let unpack_res = if let Some(count) = closure.positional_count - && (self.stack.len() - base) == count as usize + Ok(()) + } else { + let args_for_unpack = self.stack[base..].to_vec(); + self.stack.truncate(base); + if let NodeKind::Tuple { elements } = + &closure.parameter_node.kind { - Ok(()) + let mut offset = 0; + let mut res = Ok(()); + for el in elements { + if let Err(e) = + self.unpack(el.as_ref(), &args_for_unpack, &mut offset) + { + res = Err(e); + break; + } + } + res } else { - let args_for_unpack = self.stack[base..].to_vec(); - self.stack.truncate(base); - if let NodeKind::Tuple { elements } = - &closure.parameter_node.kind - { - let mut offset = 0; - let mut res = Ok(()); - for el in elements { - if let Err(e) = - self.unpack(el.as_ref(), &args_for_unpack, &mut offset) - { - res = Err(e); - break; - } - } - res - } else { - self.unpack(closure.parameter_node.as_ref(), &args_for_unpack, &mut 0) - } - }; + self.unpack(closure.parameter_node.as_ref(), &args_for_unpack, &mut 0) + } + }; - let res = match unpack_res { - Ok(_) => { - // PRE-ALLOCATION - let current_stack = (self.stack.len() - base) as u32; - if closure.stack_size > current_stack { - self.stack.resize(base + closure.stack_size as usize, Value::Void); - } - self.eval_internal(obs, &closure.exec_node) + let res = match unpack_res { + Ok(_) => { + // PRE-ALLOCATION + let current_stack = (self.stack.len() - base) as u32; + if closure.stack_size > current_stack { + self.stack.resize(base + closure.stack_size as usize, Value::Void); } - Err(e) => Err(e), - }; + self.eval_internal(obs, &closure.exec_node) + } + Err(e) => Err(e), + }; - self.frames.pop(); - res - } else if let Some(series) = obj.as_series() { + self.frames.pop(); + res + } + Value::Object(obj) => { + if let Some(series) = obj.as_series() { let arg_len = self.stack.len() - base; let res = if arg_len != 1 { Err(format!( @@ -707,6 +709,7 @@ impl VM { Value::Record(_, _) => "Record", Value::FieldAccessor(_) => "FieldAccessor", Value::Function(_) => "Function", + Value::Closure(_) => "Closure", Value::Object(_) => "Object", Value::Cell(_) => "Cell", }; @@ -714,8 +717,8 @@ impl VM { } }; - if let Some((next_obj, next_args)) = self.tail_call.take() { - current_func = Value::Object(next_obj); + if let Some((next_val, next_args)) = self.tail_call.take() { + current_func = next_val; self.stack.truncate(base); self.stack.extend(next_args); continue; @@ -736,7 +739,7 @@ impl VM { let frame = self.frames.last().ok_or("No call frame for 'again'")?; if let Some(closure_obj) = &frame.closure { - self.tail_call = Some((closure_obj.clone() as Rc, arg_vals)); + self.tail_call = Some((Value::Closure(closure_obj.clone()), arg_vals)); Ok(Value::Void) } else { Err("'again' called outside of a closure".to_string())