Files
RustAst/src/ast/vm.rs
T
Brummel 6042415dfc Refactor series types and value enum
This commit refactors the way series are represented and handled within
the AST.
Key changes include:

- Introducing `SeriesStorage` and `PushableStorage` traits to provide a
  more
  unified and type-safe interface for series data.
- Renaming `Object` trait and its methods to clarify that it's for RTL
  extensions
  other than series (like Streams).
- Updating the `Value` enum to have a distinct `Series` variant,
  separating it
  from `Object`.
- Adjusting various parts of the `VM` and `register` functions to work
  with the
  new series traits and `Value::Series` variant.

This change aims to improve the type system's clarity and safety when
dealing with
series data, aligning with Rust's best practices for trait design.
2026-03-23 16:05:58 +01:00

965 lines
41 KiB
Rust

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<Rc<Closure>>,
}
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<Value, String>) {}
}
pub struct NoOpObserver;
impl VMObserver for NoOpObserver {}
pub struct TracingObserver {
pub logs: Vec<String>,
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<Value, String>) {
self.indent = self.indent.saturating_sub(1);
let pad = self.pad();
match &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::<Vec<_>>()
));
}
_ => {}
}
let res_str = match res {
Ok(v) => format!("{}", v),
Err(e) => format!("ERROR: {}", e),
};
self.logs.push(format!("{}}} -> {}", pad, res_str));
}
}
pub struct VM {
stack: Vec<Value>,
globals: Rc<RefCell<Vec<Value>>>,
frames: Vec<CallFrame>,
/// 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<Value>)>,
}
impl VM {
pub fn new(globals: Rc<RefCell<Vec<Value>>>) -> Self {
Self {
stack: Vec::new(),
globals,
frames: Vec::new(),
tail_call: None,
}
}
pub fn run(&mut self, root: &ExecNode) -> Result<Value, String> {
self.run_with_observer(&mut NoOpObserver, root)
}
pub fn resolve_tail_calls<O: VMObserver>(
&mut self,
observer: &mut O,
mut result: Result<Value, String>,
) -> Result<Value, String> {
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::Object(obj) => {
return Err(format!(
"Tail call target is not callable: {}",
obj.type_name()
));
}
other => {
return Err(format!("Tail call target is not callable: {}", other));
}
}
} else {
return result;
}
}
}
pub fn run_with_args(
&mut self,
closure_rc: Rc<Closure>,
args: &[Value],
) -> Result<Value, String> {
self.run_with_args_observed(&mut NoOpObserver, closure_rc, args)
}
pub fn run_with_args_observed<O: VMObserver>(
&mut self,
observer: &mut O,
closure_rc: Rc<Closure>,
args: &[Value],
) -> Result<Value, String> {
self.stack.clear();
self.frames.clear();
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);
}
self.frames.push(CallFrame {
stack_base: 0,
closure: Some(closure_rc.clone()),
});
let result = self.eval_internal(observer, &closure.exec_node);
self.frames.pop();
self.resolve_tail_calls(observer, result)
}
pub fn run_with_observer<O: VMObserver>(
&mut self,
observer: &mut O,
root: &ExecNode,
) -> Result<Value, String> {
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<O: VMObserver>(
&mut self,
observer: &mut O,
node: &ExecNode,
) -> Result<Value, String> {
self.eval_internal(observer, node)
}
#[inline(always)]
fn eval_internal<O: VMObserver>(
&mut self,
obs: &mut O,
node: &ExecNode,
) -> Result<Value, String> {
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<O: VMObserver>(&mut self, obs: &mut O, node: &ExecNode) -> Result<Value, String> {
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::<RecordSeries>() {
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::Object(obj) => {
if let Some(sn) = obj.as_any().downcast_ref::<StreamNode>() {
let mapped = build_map_stream(sn.inner.clone(), *field);
return Ok(Value::Object(Rc::new(mapped)));
}
Err(format!(
"Attempt to access field on non-record object: {}",
obj.type_name()
))
}
_ => 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 } => {
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::Object(_) | Value::Series(_) => {
self.tail_call = Some((func_val, arg_vals));
return Ok(Value::Void);
}
Value::Function(f) => return Ok((f.func)(&arg_vals)),
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::<RecordSeries>() {
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::Object(obj) = rec {
if let Some(sn) = obj.as_any().downcast_ref::<StreamNode>() {
let mapped = build_map_stream(sn.inner.clone(), k);
return Ok(Value::Object(Rc::new(mapped)));
}
return Err(format!(
"Field accessor .{} expects a record, RecordSeries or Stream, got {}",
k.name(),
obj.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 &current_func {
Value::Function(f) => {
let res = (f.func)(&self.stack[base..]);
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::<RecordSeries>() {
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::Object(obj) = rec {
if let Some(sn) = obj.as_any().downcast_ref::<StreamNode>() {
let mapped = build_map_stream(sn.inner.clone(), *k);
Ok(Value::Object(Rc::new(mapped)))
} else {
Err(format!(
"Field accessor .{} expects a record, RecordSeries or Stream, got {}",
k.name(),
obj.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::Object(obj) => {
self.stack.truncate(base);
return Err(format!("Object is not callable: {}", obj.type_name()));
}
_ => {
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::Object(_) => "Object",
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<O: VMObserver>(
&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(())
}
_ => {
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<StackOffset>) -> Result<Rc<RefCell<Value>>, 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<StackOffset>) -> Result<Value, 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() {
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) => {
let g_idx = idx.0 as usize;
let globals = self.globals.borrow();
if g_idx < globals.len() {
Ok(globals[g_idx].clone())
} else {
Err(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<StackOffset>, 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) => {
let g_idx = idx.0 as usize;
let mut globals = self.globals.borrow_mut();
if g_idx >= globals.len() {
globals.resize(g_idx + 1, Value::Void);
}
globals[g_idx] = 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(())
}
_ => Err("Invalid node in parameter pattern".to_string()),
}
}
}