982d2239b6
The `GlobalStore` was refactored to clearly distinguish between immutable RTL values and mutable user-defined global slots. The `Environment` struct now holds: - `rtl_values`: An immutable `Rc<[Value]>` for pre-defined RTL values. - `user_values`: An `Rc<RefCell<Vec<Value>>>` for user-defined mutable globals. The `GlobalStore` struct now takes both `rtl` and `user` as parameters and uses `rtl_len` to determine which store to access. This change separates concerns and better reflects the immutability of RTL values after the bootstrap phase, aligning with the project's concurrency rules. Documentation in `docs/Analysis_Environment.md` was updated to reflect these structural changes.
1024 lines
43 KiB
Rust
1024 lines
43 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));
|
|
}
|
|
}
|
|
|
|
/// 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<RefCell<Vec<Value>>>`, 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<RefCell<Vec<Value>>>,
|
|
/// 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<RefCell<Vec<Value>>>, rtl_len: usize) -> Self {
|
|
Self { rtl, user, rtl_len }
|
|
}
|
|
|
|
pub fn get(&self, idx: usize) -> Option<Value> {
|
|
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<Value>,
|
|
globals: GlobalStore,
|
|
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: GlobalStore) -> 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 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
|
(f.func)(&arg_vals)
|
|
}))
|
|
.map_err(|e| {
|
|
e.downcast_ref::<String>()
|
|
.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::<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 = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
|
(f.func)(&self.stack[base..])
|
|
}))
|
|
.map_err(|e| {
|
|
e.downcast_ref::<String>()
|
|
.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::<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) => {
|
|
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<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) => {
|
|
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(())
|
|
}
|
|
_ => Err("Invalid node in parameter pattern".to_string()),
|
|
}
|
|
}
|
|
}
|