6042415dfc
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.
965 lines
41 KiB
Rust
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 ¤t_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()),
|
|
}
|
|
}
|
|
}
|