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, StreamNode}; use crate::ast::types::Value; use std::cell::RefCell; use std::rc::Rc; #[derive(Debug)] struct CallFrame { stack_base: usize, closure: Option>, } pub trait VMObserver { const ACTIVE: bool = false; fn before_eval(&mut self, _vm: &VM, _node: &ExecNode) {} fn after_eval(&mut self, _vm: &VM, _node: &ExecNode, _res: &Result) {} } pub struct NoOpObserver; impl VMObserver for NoOpObserver {} pub struct TracingObserver { pub logs: Vec, indent: usize, } impl TracingObserver { pub fn new() -> Self { Self { logs: Vec::new(), indent: 0, } } fn pad(&self) -> String { "| ".repeat(self.indent) } } impl Default for TracingObserver { fn default() -> Self { Self::new() } } impl VMObserver for TracingObserver { const ACTIVE: bool = true; fn before_eval(&mut self, _vm: &VM, node: &ExecNode) { let pad = self.pad(); let metrics = &node.ty.original.ty; self.logs.push(format!( "{}{} [{} | P:{:?}{}]: {{", pad, node.kind.display_name(), node.ty.ty, metrics.purity, if metrics.is_recursive { " | REC" } else { "" } )); self.indent += 1; } fn after_eval(&mut self, vm: &VM, node: &ExecNode, res: &Result) { self.indent = self.indent.saturating_sub(1); let pad = self.pad(); if matches!(&node.kind, NodeKind::Def { .. } | NodeKind::Assign { .. }) { let s_pad = format!("{}| ", pad); self.logs.push(format!("{}--- Scope Status ---", s_pad)); self.logs.push(format!( "{}Stack (top 5): {:?}", s_pad, vm.stack.iter().rev().take(5).collect::>() )); } let res_str = match res { Ok(v) => format!("{}", v), Err(e) => format!("ERROR: {}", e), }; self.logs.push(format!("{}}} -> {}", pad, res_str)); } } /// Unified view of the global value space for VM and optimizer access. /// /// Splits the global address space into two regions: /// - RTL slots `[0..rtl_len)`: immutable `Rc<[Value]>`, shared across all environments /// created from the same [`Rtl`](crate::ast::environment::Rtl) snapshot. No `RefCell` needed. /// - User slots `[rtl_len..)`: mutable `Rc>>`, per-environment. /// /// During the RTL bootstrap phase `rtl_len` is 0 and both RTL and user values live in the /// `user` vec. After the freeze, `rtl` holds the immutable snapshot and `user` starts /// empty and grows as the script defines new globals. /// /// Stream closures capture a `GlobalStore` during bootstrap (before the freeze), so their /// `rtl_len` remains 0 and they read all values from `user`. This enforces the invariant /// that stream lambdas run in an RTL-only execution context. #[derive(Clone)] pub struct GlobalStore { rtl: Rc<[Value]>, user: Rc>>, /// Index boundary: slots `[0..rtl_len)` are served from `rtl`, the rest from `user`. pub rtl_len: usize, } impl GlobalStore { pub fn new(rtl: Rc<[Value]>, user: Rc>>, rtl_len: usize) -> Self { Self { rtl, user, rtl_len } } pub fn get(&self, idx: usize) -> Option { if idx < self.rtl_len { self.rtl.get(idx).cloned() } else { self.user.borrow().get(idx - self.rtl_len).cloned() } } fn set(&self, idx: usize, value: Value) { debug_assert!(idx >= self.rtl_len, "Cannot write to immutable RTL slot {}", idx); let u = idx - self.rtl_len; let mut v = self.user.borrow_mut(); if u >= v.len() { v.resize(u + 1, Value::Void); } v[u] = value; } } pub struct VM { stack: Vec, globals: GlobalStore, 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<(Value, Vec)>, } impl VM { pub fn new(globals: GlobalStore) -> Self { Self { stack: Vec::new(), globals, frames: Vec::new(), tail_call: None, } } pub fn run(&mut self, root: &ExecNode) -> Result { self.run_with_observer(&mut NoOpObserver, root) } pub fn resolve_tail_calls( &mut self, observer: &mut O, mut result: Result, ) -> Result { loop { 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); } result = self.eval_observed(observer, &closure.exec_node); self.frames.pop(); } Value::Series(s) => { if next_args.len() != 1 { return Err(format!( "{} indexer expects exactly 1 argument (the lookback index), got {}", s.series_type_name(), next_args.len() )); } if let Value::Int(idx) = &next_args[0] { if *idx < 0 { return Err(format!( "{} lookback index cannot be negative: {}", s.series_type_name(), idx )); } result = Ok(s.get_item(*idx as usize).unwrap_or(Value::Void)); } else { return Err(format!( "{} index must be an integer, got {}", s.series_type_name(), next_args[0] )); } } Value::Stream(_) => { return Err("Tail call target is not callable: stream".to_string()); } other => { return Err(format!("Tail call target is not callable: {}", other)); } } } else { return result; } } } pub fn run_with_args( &mut self, closure_rc: Rc, args: &[Value], ) -> Result { self.run_with_args_observed(&mut NoOpObserver, closure_rc, args) } pub fn run_with_args_observed( &mut self, observer: &mut O, closure_rc: Rc, args: &[Value], ) -> Result { self.stack.clear(); self.frames.clear(); // Push the call frame early so that `unpack` (slow path) can use // `set_value` / `get_value`, which require a valid call frame. 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 && args.len() == count as usize { self.stack.extend_from_slice(args); } else { self.unpack(closure.parameter_node.as_ref(), args, &mut 0)?; } // Fill remaining stack slots with Void let current_stack = self.stack.len() as u32; if closure.stack_size > current_stack { self.stack.resize(closure.stack_size as usize, Value::Void); } let result = self.eval_internal(observer, &closure.exec_node); self.frames.pop(); self.resolve_tail_calls(observer, result) } pub fn run_with_observer( &mut self, observer: &mut O, root: &ExecNode, ) -> Result { self.stack.clear(); self.frames.clear(); self.stack.resize(root.ty.stack_size as usize, Value::Void); self.frames.push(CallFrame { stack_base: 0, closure: None, }); let result = self.eval_internal(observer, root); self.frames.pop(); self.resolve_tail_calls(observer, result) } fn eval_observed( &mut self, observer: &mut O, node: &ExecNode, ) -> Result { self.eval_internal(observer, node) } #[inline(always)] fn eval_internal( &mut self, obs: &mut O, node: &ExecNode, ) -> Result { if O::ACTIVE { obs.before_eval(self, node); let result = self.eval_core(obs, node); obs.after_eval(self, node, &result); result } else { self.eval_core(obs, node) } } #[inline(always)] fn eval_core(&mut self, obs: &mut O, node: &ExecNode) -> Result { match &node.kind { NodeKind::Nop => Ok(Value::Void), NodeKind::Constant(v) => Ok(v.clone()), NodeKind::Def { pattern, value, info } => { let val = self.eval_internal(obs, value)?; match &pattern.kind { NodeKind::Identifier { binding, .. } => { let addr = match binding { IdentifierBinding::Declaration { addr, .. } | IdentifierBinding::Reference(addr) => *addr, }; let mut needs_cell_wrap = false; if !info.captured_by.is_empty() && let Address::Local(slot) = addr { let frame = self.frames.last().unwrap(); let abs_index = frame.stack_base + (slot.0 as usize); // Robustness Fix: If the slot is already a Cell (due to forward capture // in a recursive scenario or complex pre-allocation), don't wrap it again. // This prevents Cell(Cell(Value)) nesting which causes type errors. if abs_index >= self.stack.len() || !matches!(self.stack[abs_index], Value::Cell(_)) { needs_cell_wrap = true; } } let store_val = if needs_cell_wrap { Value::Cell(Rc::new(RefCell::new(val.clone()))) } else { val.clone() }; self.set_value(addr, store_val)?; } _ => { // Destructuring (was Destructure variant) let mut offset = 0; if let Some(vals) = val.as_slice() { self.unpack(pattern.as_ref(), vals, &mut offset)?; } else { self.unpack(pattern.as_ref(), std::slice::from_ref(&val), &mut offset)?; } } } // Def always evaluates to the unwrapped value for immediate use. Ok(val) } NodeKind::Assign { target, value, info } => { let val = self.eval_internal(obs, value)?; if let Some(addr) = info.addr { self.set_value(addr, val.clone())?; } else { // Destructuring assign let mut offset = 0; if let Some(vals) = val.as_slice() { self.unpack(target.as_ref(), vals, &mut offset)?; } else { self.unpack(target.as_ref(), std::slice::from_ref(&val), &mut offset)?; } } Ok(val) } NodeKind::Identifier { binding, .. } => { match binding { IdentifierBinding::Reference(addr) | IdentifierBinding::Declaration { addr, .. } => self.get_value(*addr), } } NodeKind::FieldAccessor(k) => Ok(Value::FieldAccessor(*k)), NodeKind::GetField { rec, field } => { let rec_val = self.eval_internal(obs, rec)?; match rec_val { Value::Record(layout, values) => { if let Some(idx) = layout.index_of(*field) { Ok(values[idx].clone()) } else { Err(format!("Record does not have field :{}", field.name())) } } Value::Series(s) => { if let Some(record_series) = s.as_any().downcast_ref::() { if let Some(field_series) = record_series.field(*field) { let view = SeriesView::new(field_series, *field); return Ok(Value::Series(std::rc::Rc::new(view))); } else { return Err(format!( "RecordSeries does not have field :{}", field.name() )); } } Err(format!( "Field accessor .{} expects a record or RecordSeries, got series:{}", field.name(), s.series_type_name() )) } Value::Stream(s) => { if let Some(sn) = s.as_any().downcast_ref::() { let mapped = build_map_stream(sn.inner.clone(), *field); return Ok(Value::Stream(Rc::new(mapped))); } Err("Attempt to access field on non-record stream".to_string()) } _ => Err(format!( "Attempt to access field on non-record: {}", rec_val )), } } NodeKind::If { cond, then_br, else_br, } => { let c = self.eval_internal(obs, cond)?; if c.is_truthy() { self.eval_internal(obs, then_br) } else if let Some(e) = else_br { self.eval_internal(obs, e) } else { Ok(Value::Void) } } NodeKind::Block { exprs } | NodeKind::Program { exprs } => { let mut last = Value::Void; for e in exprs { last = self.eval_internal(obs, e)?; } Ok(last) } NodeKind::Lambda { params, body, info, } => { let mut captured = Vec::with_capacity(info.upvalues.len()); for addr in &info.upvalues { captured.push(self.capture_upvalue(*addr)?); } let stack_size = node.ty.stack_size; let closure = Closure::new( params.clone(), body.ty.original.clone(), body.clone(), captured, info.positional_count, stack_size, ); Ok(Value::Closure(Rc::new(closure))) } NodeKind::Call { callee, args } => { let func_val = self.eval_internal(obs, callee)?; let base = self.stack.len(); if let Err(e) = self.eval_args_to_stack(obs, args) { self.stack.truncate(base); return Err(e); } if node.ty.is_tail { let arg_vals = self.stack[base..].to_vec(); self.stack.truncate(base); match func_val { Value::Closure(_) | Value::Stream(_) | Value::Series(_) => { self.tail_call = Some((func_val, arg_vals)); return Ok(Value::Void); } Value::Function(f) => { return std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { (f.func)(&arg_vals) })) .map_err(|e| { e.downcast_ref::() .cloned() .or_else(|| { e.downcast_ref::<&str>().map(|s| s.to_string()) }) .unwrap_or_else(|| "runtime panic".to_string()) }); } Value::FieldAccessor(k) => { if arg_vals.len() != 1 { return Err(format!( "Field accessor .{} expects exactly 1 argument, got {}", k.name(), arg_vals.len() )); } let rec = &arg_vals[0]; if let Value::Record(layout, values) = rec { if let Some(idx) = layout.index_of(k) { return Ok(values[idx].clone()); } else { return Err(format!( "Record does not have field :{}", k.name() )); } } else if let Value::Series(s) = rec { if let Some(rs) = s.as_any().downcast_ref::() { if let Some(field_series) = rs.field(k) { let view = SeriesView::new(field_series, k); return Ok(Value::Series(std::rc::Rc::new(view))); } else { return Err(format!( "RecordSeries does not have field :{}", k.name() )); } } return Err(format!( "Field accessor .{} expects a record or RecordSeries, got series:{}", k.name(), s.series_type_name() )); } else if let Value::Stream(s) = rec { if let Some(sn) = s.as_any().downcast_ref::() { let mapped = build_map_stream(sn.inner.clone(), k); return Ok(Value::Stream(Rc::new(mapped))); } return Err(format!( "Field accessor .{} expects a record, RecordSeries or Stream, got {}", k.name(), s.stream_type_name() )); } else { return Err(format!( "Field accessor .{} expects a record, RecordSeries or Stream, got {}", k.name(), rec )); } } _ => { return Err(format!( "Tail call target is not a function: {}", func_val )); } } } // Standard Call Path let mut current_func = func_val; loop { let result = match ¤t_func { Value::Function(f) => { let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { (f.func)(&self.stack[base..]) })) .map_err(|e| { e.downcast_ref::() .cloned() .or_else(|| { e.downcast_ref::<&str>().map(|s| s.to_string()) }) .unwrap_or_else(|| "runtime panic".to_string()) })?; self.stack.truncate(base); return Ok(res); } Value::FieldAccessor(k) => { let arg_len = self.stack.len() - base; let res = if arg_len != 1 { Err(format!( "Field accessor .{} expects exactly 1 argument, got {}", k.name(), arg_len )) } else { let rec = &self.stack[base]; if let Value::Record(layout, values) = rec { if let Some(idx) = layout.index_of(*k) { Ok(values[idx].clone()) } else { Err(format!("Record does not have field :{}", k.name())) } } else if let Value::Series(s) = rec { if let Some(rs) = s.as_any().downcast_ref::() { if let Some(field_series) = rs.field(*k) { let view = SeriesView::new(field_series, *k); Ok(Value::Series(std::rc::Rc::new(view))) } else { Err(format!( "RecordSeries does not have field :{}", k.name() )) } } else { Err(format!( "Field accessor .{} expects a record or RecordSeries, got series:{}", k.name(), s.series_type_name() )) } } else if let Value::Stream(s) = rec { if let Some(sn) = s.as_any().downcast_ref::() { let mapped = build_map_stream(sn.inner.clone(), *k); Ok(Value::Stream(Rc::new(mapped))) } else { Err(format!( "Field accessor .{} expects a record, RecordSeries or Stream, got {}", k.name(), s.stream_type_name() )) } } else { Err(format!( "Field accessor .{} expects a record, RecordSeries or Stream, got {}", k.name(), rec )) } }; self.stack.truncate(base); return res; } 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 { Ok(()) } 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) } }; 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) } Err(e) => Err(e), }; self.frames.pop(); res } Value::Series(s) => { let arg_len = self.stack.len() - base; let res = if arg_len != 1 { Err(format!( "{} indexer expects exactly 1 argument (the lookback index)", s.series_type_name() )) } else if let Value::Int(idx) = self.stack[base] { if idx < 0 { Err(format!( "{} lookback index cannot be negative", s.series_type_name() )) } else if let Some(val) = s.get_item(idx as usize) { Ok(val) } else { Ok(Value::Void) } } else { Err(format!( "{} index must be an integer", s.series_type_name() )) }; self.stack.truncate(base); return res; } Value::Stream(_) => { self.stack.truncate(base); return Err("Stream is not callable".to_string()); } _ => { self.stack.truncate(base); let variant_name = match current_func { Value::Void => "Void", Value::Bool(_) => "Bool", Value::Int(_) => "Int", Value::Float(_) => "Float", Value::DateTime(_) => "DateTime", Value::Text(_) => "Text", Value::Keyword(_) => "Keyword", Value::Tuple(_) => "Tuple", Value::Record(_, _) => "Record", Value::FieldAccessor(_) => "FieldAccessor", Value::Function(_) => "Function", Value::Closure(_) => "Closure", Value::Quote(_) => "Quote", Value::Series(_) => "Series", Value::Stream(_) => "Stream", Value::Cell(_) => "Cell", }; return Err(format!("Attempt to call non-function: {} (Variant: {})", current_func, variant_name)); } }; 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; } else { self.stack.truncate(base); return result; } } } NodeKind::Again { args } => { let base = self.stack.len(); if let Err(e) = self.eval_args_to_stack(obs, args) { self.stack.truncate(base); return Err(e); } let arg_vals = self.stack[base..].to_vec(); self.stack.truncate(base); let frame = self.frames.last().ok_or("No call frame for 'again'")?; if let Some(closure_obj) = &frame.closure { self.tail_call = Some((Value::Closure(closure_obj.clone()), arg_vals)); Ok(Value::Void) } else { Err("'again' called outside of a closure".to_string()) } } NodeKind::Tuple { elements } => { let mut vals = Vec::with_capacity(elements.len()); for e in elements { vals.push(self.eval_internal(obs, e)?); } Ok(Value::make_tuple(vals)) } NodeKind::Record { fields, layout } => { let mut evaluated_values = Vec::with_capacity(fields.len()); for (_, v) in fields { evaluated_values.push(self.eval_internal(obs, v)?); } Ok(Value::Record( layout.clone(), std::rc::Rc::new(evaluated_values), )) } NodeKind::Expansion { expanded, .. } => { let mut curr = expanded; if !O::ACTIVE { while let NodeKind::Expansion { expanded: next, .. } = &curr.kind { curr = next; } } self.eval_internal(obs, curr) } NodeKind::Extension(ext) => Err(format!( "Execution of extension '{}' not implemented yet", ext.display_name() )), NodeKind::Error => Err("Cannot execute a poisoned AST node".to_string()), // Syntax-only variants that should never appear in runtime phase NodeKind::MacroDecl { .. } | NodeKind::Template(_) | NodeKind::Placeholder(_) | NodeKind::Splice(_) => { Err("Syntax-only node reached the VM".to_string()) } } } fn eval_args_to_stack( &mut self, obs: &mut O, args: &ExecNode, ) -> Result<(), String> { match &args.kind { NodeKind::Tuple { elements } => { for e in elements { let mut curr = e.as_ref(); if !O::ACTIVE { while let NodeKind::Expansion { expanded: next, .. } = &curr.kind { curr = next; } } match &curr.kind { NodeKind::Constant(v) if !O::ACTIVE => self.stack.push(v.clone()), _ => { let val = self.eval_internal(obs, curr)?; self.stack.push(val); } } } Ok(()) } NodeKind::Constant(v) => { if let Some(slice) = v.as_slice() { self.stack.extend_from_slice(slice); } else { self.stack.push(v.clone()); } Ok(()) } NodeKind::Expansion { expanded, .. } => { let mut curr = expanded; if !O::ACTIVE { while let NodeKind::Expansion { expanded: next, .. } = &curr.kind { curr = next; } } let val = self.eval_internal(obs, curr)?; if let Some(slice) = val.as_slice() { self.stack.extend_from_slice(slice); } else { self.stack.push(val); } Ok(()) } // All other variants: evaluate and push result(s) onto stack. NodeKind::Nop | NodeKind::Identifier { .. } | NodeKind::FieldAccessor(_) | NodeKind::Def { .. } | NodeKind::Assign { .. } | NodeKind::Lambda { .. } | NodeKind::Call { .. } | NodeKind::Again { .. } | NodeKind::If { .. } | NodeKind::Block { .. } | NodeKind::Program { .. } | NodeKind::Record { .. } | NodeKind::GetField { .. } | NodeKind::Extension(_) | NodeKind::Error | NodeKind::MacroDecl { .. } | NodeKind::Template(_) | NodeKind::Placeholder(_) | NodeKind::Splice(_) => { let val = self.eval_internal(obs, args)?; if let Some(slice) = val.as_slice() { self.stack.extend_from_slice(slice); } else { self.stack.push(val); } Ok(()) } } } fn capture_upvalue(&mut self, addr: Address) -> Result>, String> { match addr { Address::Local(slot) => { let frame = self.frames.last().ok_or("No call frame")?; let abs_index = frame.stack_base + (slot.0 as usize); if abs_index < self.stack.len() { if let Value::Cell(cell) = &self.stack[abs_index] { Ok(cell.clone()) } else { let val = self.stack[abs_index].clone(); let cell = Rc::new(RefCell::new(val)); self.stack[abs_index] = Value::Cell(cell.clone()); Ok(cell) } } else { Err(format!("Stack access out of bounds: trying to capture local {} at index {} but stack size is only {}", slot, abs_index, self.stack.len())) } } Address::Upvalue(idx) => { let frame = self.frames.last().ok_or("No call frame")?; if let Some(closure) = &frame.closure { let u_idx = idx.0 as usize; if u_idx < closure.upvalues.len() { Ok(closure.upvalues[u_idx].clone()) } else { Err(format!("Upvalue access out of bounds capture {}", idx)) } } else { Err("Current frame has no closure".to_string()) } } Address::Global(_) => Err("Cannot capture global directly".to_string()), } } fn get_value(&self, addr: Address) -> Result { match addr { Address::Local(slot) => { let frame = self.frames.last().ok_or("No call frame")?; let abs_index = frame.stack_base + (slot.0 as usize); if abs_index < self.stack.len() { match &self.stack[abs_index] { Value::Cell(cell) => Ok(cell.borrow().clone()), val => Ok(val.clone()), } } else { Err(format!("Stack underflow access local {}", slot)) } } Address::Global(idx) => { self.globals .get(idx.0 as usize) .ok_or_else(|| format!("Global access out of bounds {}", idx)) } Address::Upvalue(idx) => { let frame = self.frames.last().ok_or("No call frame")?; if let Some(closure) = &frame.closure { let u_idx = idx.0 as usize; if u_idx < closure.upvalues.len() { Ok(closure.upvalues[u_idx].borrow().clone()) } else { Err(format!("Upvalue access out of bounds {}", idx)) } } else { Err("Current frame has no closure (cannot access upvalues)".to_string()) } } } } fn set_value(&mut self, addr: Address, value: Value) -> Result<(), String> { match addr { Address::Local(slot) => { let frame = self.frames.last().ok_or("No call frame")?; let abs_index = frame.stack_base + (slot.0 as usize); if abs_index < self.stack.len() { if let Value::Cell(cell) = &self.stack[abs_index] { *cell.borrow_mut() = value; } else { self.stack[abs_index] = value; } } else if abs_index == self.stack.len() { self.stack.push(value); } else { return Err(format!("Stack gap write local {}", slot)); } Ok(()) } Address::Global(idx) => { self.globals.set(idx.0 as usize, value); Ok(()) } Address::Upvalue(idx) => { let frame = self.frames.last().ok_or("No call frame")?; if let Some(closure) = &frame.closure { let u_idx = idx.0 as usize; if u_idx < closure.upvalues.len() { *closure.upvalues[u_idx].borrow_mut() = value; Ok(()) } else { Err(format!("Upvalue assignment out of bounds {}", idx)) } } else { Err("Current frame has no closure".to_string()) } } } } fn unpack( &mut self, pattern: &ExecNode, values: &[Value], offset: &mut usize, ) -> Result<(), String> { match &pattern.kind { NodeKind::Identifier { binding, .. } => { let addr = match binding { IdentifierBinding::Declaration { addr, .. } | IdentifierBinding::Reference(addr) => *addr, }; let val = values.get(*offset).cloned().unwrap_or(Value::Void); *offset += 1; self.set_value(addr, val) } NodeKind::Tuple { elements } => { if let Some(sub_values) = values.get(*offset).and_then(|v| v.as_slice()) { *offset += 1; let mut sub_offset = 0; for el in elements { self.unpack(el.as_ref(), sub_values, &mut sub_offset)?; } return Ok(()); } for el in elements { self.unpack(el.as_ref(), values, offset)?; } Ok(()) } // Parameter patterns should only contain Identifier and Tuple. NodeKind::Nop | NodeKind::Constant(_) | NodeKind::FieldAccessor(_) | NodeKind::Def { .. } | NodeKind::Assign { .. } | NodeKind::Lambda { .. } | NodeKind::Call { .. } | NodeKind::Again { .. } | NodeKind::If { .. } | NodeKind::Block { .. } | NodeKind::Program { .. } | NodeKind::Record { .. } | NodeKind::GetField { .. } | NodeKind::Expansion { .. } | NodeKind::Extension(_) | NodeKind::Error | NodeKind::MacroDecl { .. } | NodeKind::Template(_) | NodeKind::Placeholder(_) | NodeKind::Splice(_) => Err("Invalid node in parameter pattern".to_string()), } } }