595bcf09e5
The `PipelineNode` has been refactored into `StreamNode`. This commit updates the example files to reflect this change and also updates the `series` constructor to accept a lookback parameter. The `PipelineNode` was an artifact of a previous implementation and is no longer needed. The `StreamNode` is the appropriate type for representing the output of a pipe operation. The `series` function now requires a `lookback` argument to specify the maximum number of items to store. This makes the behavior of series more explicit and prevents accidental unbounded memory growth.
1023 lines
42 KiB
Rust
1023 lines
42 KiB
Rust
use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind};
|
|
use crate::ast::compiler::tco::ExecNode;
|
|
use crate::ast::nodes::Node;
|
|
use crate::ast::types::{Object, Value};
|
|
use std::any::Any;
|
|
use std::cell::RefCell;
|
|
use std::rc::Rc;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Closure {
|
|
/// The analyzed parameter pattern.
|
|
pub parameter_node: Rc<AnalyzedNode>,
|
|
/// The analyzed body (before TCO).
|
|
pub function_node: Rc<AnalyzedNode>,
|
|
/// The executable node (after TCO).
|
|
pub exec_node: Rc<ExecNode>,
|
|
pub upvalues: Vec<Rc<RefCell<Value>>>,
|
|
pub positional_count: Option<u32>,
|
|
}
|
|
|
|
impl Closure {
|
|
#[inline]
|
|
pub fn new(
|
|
params: Rc<AnalyzedNode>,
|
|
body: Rc<AnalyzedNode>,
|
|
exec: Rc<ExecNode>,
|
|
upvalues: Vec<Rc<RefCell<Value>>>,
|
|
positional_count: Option<u32>,
|
|
) -> Self {
|
|
Self {
|
|
parameter_node: params,
|
|
function_node: body,
|
|
exec_node: exec,
|
|
upvalues,
|
|
positional_count,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Object for Closure {
|
|
fn type_name(&self) -> &'static str {
|
|
"closure"
|
|
}
|
|
fn as_any(&self) -> &dyn Any {
|
|
self
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct CallFrame {
|
|
stack_base: usize,
|
|
closure: Option<Rc<dyn Object>>,
|
|
}
|
|
|
|
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 {
|
|
BoundKind::Define { .. } | BoundKind::Set { .. } => {
|
|
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>,
|
|
}
|
|
|
|
impl VM {
|
|
pub fn new(globals: Rc<RefCell<Vec<Value>>>) -> Self {
|
|
Self {
|
|
stack: Vec::new(),
|
|
globals,
|
|
frames: Vec::new(),
|
|
}
|
|
}
|
|
|
|
pub fn run(&mut self, root: &ExecNode) -> Result<Value, String> {
|
|
self.stack.clear();
|
|
self.frames.clear();
|
|
self.frames.push(CallFrame {
|
|
stack_base: 0,
|
|
closure: None,
|
|
});
|
|
let result = self.eval(root);
|
|
self.frames.pop();
|
|
self.resolve_tail_calls(&mut NoOpObserver, result)
|
|
}
|
|
|
|
pub fn resolve_tail_calls<O: VMObserver>(
|
|
&mut self,
|
|
observer: &mut O,
|
|
mut result: Result<Value, String>,
|
|
) -> Result<Value, String> {
|
|
loop {
|
|
match result {
|
|
Ok(Value::TailCallRequest(payload)) => {
|
|
let (next_obj, next_args) = *payload;
|
|
if let Some(closure) = next_obj.as_any().downcast_ref::<Closure>() {
|
|
self.stack.clear();
|
|
// frames should be empty here since we popped before entering the loop
|
|
self.frames.push(CallFrame {
|
|
stack_base: 0,
|
|
closure: Some(next_obj.clone()),
|
|
});
|
|
if let Some(count) = closure.positional_count
|
|
&& next_args.len() == count as usize
|
|
{
|
|
self.stack.extend(next_args);
|
|
} else {
|
|
self.unpack(&closure.parameter_node, &next_args, &mut 0)?;
|
|
}
|
|
result = self.eval_observed(observer, &closure.exec_node);
|
|
self.frames.pop();
|
|
} else if let Some(series) = next_obj.as_series() {
|
|
if next_args.len() != 1 {
|
|
return Err(format!(
|
|
"{} indexer expects exactly 1 argument (the lookback index), got {}",
|
|
next_obj.type_name(),
|
|
next_args.len()
|
|
));
|
|
}
|
|
if let Value::Int(idx) = &next_args[0] {
|
|
if *idx < 0 {
|
|
return Err(format!(
|
|
"{} lookback index cannot be negative: {}",
|
|
next_obj.type_name(),
|
|
idx
|
|
));
|
|
}
|
|
result = Ok(series.get_item(*idx as usize).unwrap_or(Value::Void));
|
|
} else {
|
|
return Err(format!(
|
|
"{} index must be an integer, got {}",
|
|
next_obj.type_name(),
|
|
next_args[0]
|
|
));
|
|
}
|
|
} else {
|
|
return Err(format!(
|
|
"Tail call target is not callable: {}",
|
|
next_obj.type_name()
|
|
));
|
|
}
|
|
}
|
|
_ => return result,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn run_with_args(
|
|
&mut self,
|
|
closure_obj: Rc<dyn Object>,
|
|
args: &[Value],
|
|
) -> Result<Value, String> {
|
|
let closure = closure_obj.as_any().downcast_ref::<Closure>().unwrap();
|
|
self.stack.clear();
|
|
self.frames.clear();
|
|
self.frames.push(CallFrame {
|
|
stack_base: 0,
|
|
closure: Some(closure_obj.clone()),
|
|
});
|
|
if let Some(count) = closure.positional_count
|
|
&& args.len() == count as usize
|
|
{
|
|
self.stack.extend_from_slice(args);
|
|
} else {
|
|
self.unpack(&closure.parameter_node, args, &mut 0)?;
|
|
}
|
|
let result = self.eval(&closure.exec_node);
|
|
self.frames.pop();
|
|
self.resolve_tail_calls(&mut NoOpObserver, result)
|
|
}
|
|
|
|
pub fn run_with_args_observed<O: VMObserver>(
|
|
&mut self,
|
|
observer: &mut O,
|
|
closure_obj: Rc<dyn Object>,
|
|
args: &[Value],
|
|
) -> Result<Value, String> {
|
|
let closure = closure_obj.as_any().downcast_ref::<Closure>().unwrap();
|
|
self.stack.clear();
|
|
self.frames.clear();
|
|
self.frames.push(CallFrame {
|
|
stack_base: 0,
|
|
closure: Some(closure_obj.clone()),
|
|
});
|
|
if let Some(count) = closure.positional_count
|
|
&& args.len() == count as usize
|
|
{
|
|
self.stack.extend_from_slice(args);
|
|
} else {
|
|
self.unpack(&closure.parameter_node, args, &mut 0)?;
|
|
}
|
|
let result = self.eval_observed(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.frames.push(CallFrame {
|
|
stack_base: 0,
|
|
closure: None,
|
|
});
|
|
let result = self.eval_observed(observer, root);
|
|
self.frames.pop();
|
|
self.resolve_tail_calls(observer, result)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn eval(&mut self, node: &ExecNode) -> Result<Value, String> {
|
|
self.eval_internal(&mut NoOpObserver, node)
|
|
}
|
|
|
|
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 {
|
|
BoundKind::Nop => Ok(Value::Void),
|
|
BoundKind::Constant(v) => Ok(v.clone()),
|
|
BoundKind::Define {
|
|
addr,
|
|
value,
|
|
captured_by,
|
|
..
|
|
} => {
|
|
let val = self.eval_internal(obs, value)?;
|
|
let final_val = if !captured_by.is_empty() && matches!(addr, Address::Local(_)) {
|
|
Value::Cell(Rc::new(RefCell::new(val)))
|
|
} else {
|
|
val
|
|
};
|
|
self.set_value(*addr, final_val.clone())?;
|
|
Ok(final_val)
|
|
}
|
|
BoundKind::Destructure { pattern, value } => {
|
|
let val = self.eval_internal(obs, value)?;
|
|
let mut offset = 0;
|
|
|
|
// Destructuring works on tuples/vectors, or single values wrapped in a slice
|
|
if let Some(vals) = val.as_slice() {
|
|
self.unpack(pattern, vals, &mut offset)?;
|
|
} else {
|
|
self.unpack(pattern, std::slice::from_ref(&val), &mut offset)?;
|
|
}
|
|
Ok(val)
|
|
}
|
|
BoundKind::Get { addr, .. } => self.get_value(*addr),
|
|
|
|
BoundKind::FieldAccessor(k) => Ok(Value::FieldAccessor(*k)),
|
|
|
|
BoundKind::GetField { rec, field } => {
|
|
let rec_val = self.eval_internal(obs, rec)?;
|
|
|
|
// In Rust, pattern matching (`match`) is the idiomatic way to handle variants safely.
|
|
// Previously, this only handled `Value::Record`. Now, we handle objects (like `RecordSeries`) polymorphically.
|
|
match rec_val {
|
|
// Case 1: The classic Record.
|
|
// This is a struct-like tuple containing an Arc<RecordLayout> and a Vec<Value>.
|
|
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()))
|
|
}
|
|
}
|
|
|
|
// Case 2: A dynamic Object (our SoA / Struct-of-Arrays optimization).
|
|
// `Value::Object` holds an `Rc<dyn Object>` - a reference-counted trait object (type-erased).
|
|
Value::Object(obj) => {
|
|
// 1. We get the raw `&dyn Any` reference (Rust's standard mechanism for runtime type reflection).
|
|
let any_ptr = obj.as_any();
|
|
|
|
// 2. Downcast! We check at runtime if the pointer actually points to a `RecordSeries`.
|
|
// `downcast_ref` is very fast (essentially an O(1) type ID comparison under the hood).
|
|
if let Some(record_series) =
|
|
any_ptr.downcast_ref::<crate::ast::rtl::series::RecordSeries>()
|
|
{
|
|
// 3. We call our highly performant 0-copy method on the series.
|
|
// It returns an `Rc<RefCell<dyn SeriesMember>>`, which is a shared pointer
|
|
// to the concrete column array (e.g., a `ScalarSeries<f64>`).
|
|
if let Some(field_series) = record_series.field(*field) {
|
|
// 4. We wrap this RefCell in our `SeriesView` struct.
|
|
// The `SeriesView` acts as a pure `Object` for the VM, holding the reference.
|
|
// CRITICAL: No array elements are copied here! This is pure, fast pointer juggling.
|
|
// This single operation turns a SoA `RecordSeries` into a high-speed `FloatSeries` view.
|
|
let view =
|
|
crate::ast::rtl::series::SeriesView::new(field_series, *field);
|
|
return Ok(Value::Object(std::rc::Rc::new(view)));
|
|
} else {
|
|
return Err(format!(
|
|
"RecordSeries does not have field :{}",
|
|
field.name()
|
|
));
|
|
}
|
|
} else if let Some(sn) = obj.as_any().downcast_ref::<crate::ast::rtl::streams::StreamNode>() {
|
|
let mapped = crate::ast::rtl::streams::build_map_stream(sn.inner.clone(), *field);
|
|
return Ok(Value::Object(Rc::new(mapped)));
|
|
}
|
|
|
|
// Fallback if it's another type of object that is not a RecordSeries.
|
|
Err(format!(
|
|
"Attempt to access field on non-record object: {}",
|
|
obj.type_name()
|
|
))
|
|
}
|
|
|
|
// Error handling for primitives (Int, Float, etc.).
|
|
_ => Err(format!(
|
|
"Attempt to access field on non-record: {}",
|
|
rec_val
|
|
)),
|
|
}
|
|
}
|
|
|
|
BoundKind::Set { addr, value } => {
|
|
let val = self.eval_internal(obs, value)?;
|
|
self.set_value(*addr, val.clone())?;
|
|
Ok(val)
|
|
}
|
|
BoundKind::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)
|
|
}
|
|
}
|
|
BoundKind::Pipe {
|
|
inputs,
|
|
lambda,
|
|
out_type,
|
|
} => {
|
|
use crate::ast::rtl::streams::StreamNode;
|
|
|
|
let mut obs_streams = Vec::new();
|
|
for input in inputs {
|
|
let val = self.eval_internal(obs, input)?;
|
|
if let Value::Object(obj) = val {
|
|
if let Some(s) = obj.as_any().downcast_ref::<StreamNode>() {
|
|
obs_streams.push(s.inner.clone());
|
|
} else {
|
|
return Err(format!(
|
|
"Pipe input must be a stream, found {}",
|
|
obj.type_name()
|
|
));
|
|
}
|
|
} else {
|
|
return Err("Pipe input must be an object (stream)".to_string());
|
|
}
|
|
}
|
|
|
|
let lambda_val = self.eval_internal(obs, lambda)?;
|
|
|
|
// Create the persistent execution closure for the PipeStream
|
|
let mut pipe_vm = crate::ast::vm::VM::new(self.globals.clone());
|
|
|
|
let executor: Box<crate::ast::types::PipeFn> = match lambda_val {
|
|
Value::Object(obj) => {
|
|
let my_closure = obj.clone();
|
|
Box::new(move |args: &[Value]| -> Value {
|
|
match pipe_vm.run_with_args(my_closure.clone(), args) {
|
|
Ok(res) => res,
|
|
Err(e) => panic!("Pipeline lambda execution failed: {}", e),
|
|
}
|
|
})
|
|
}
|
|
Value::Function(func) => {
|
|
let my_func = func.clone();
|
|
Box::new(move |args: &[Value]| -> Value {
|
|
(my_func.func)(args)
|
|
})
|
|
}
|
|
_ => return Err("Pipe lambda must be a function/closure".to_string()),
|
|
};
|
|
|
|
// Delegate to the RTL Factory for specialized buffer instantiation
|
|
let node =
|
|
crate::ast::rtl::streams::build_pipeline_node(obs_streams, executor, out_type);
|
|
|
|
Ok(Value::Object(node))
|
|
}
|
|
BoundKind::Block { exprs } => {
|
|
let mut last = Value::Void;
|
|
for e in exprs {
|
|
last = self.eval_internal(obs, e)?;
|
|
}
|
|
Ok(last)
|
|
}
|
|
BoundKind::Lambda {
|
|
params,
|
|
upvalues,
|
|
body,
|
|
positional_count,
|
|
} => {
|
|
let mut captured = Vec::with_capacity(upvalues.len());
|
|
for addr in upvalues {
|
|
captured.push(self.capture_upvalue(*addr)?);
|
|
}
|
|
let closure = Closure::new(
|
|
params.ty.original.clone(),
|
|
body.ty.original.clone(),
|
|
body.clone(),
|
|
captured,
|
|
*positional_count,
|
|
);
|
|
Ok(Value::Object(Rc::new(closure)))
|
|
}
|
|
BoundKind::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::Object(obj) => {
|
|
return Ok(Value::TailCallRequest(Box::new((obj, arg_vals))));
|
|
}
|
|
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::Object(obj) = rec {
|
|
// Polymorphic Field Access: Allow `.field` on a RecordSeries
|
|
if let Some(rs) = obj
|
|
.as_any()
|
|
.downcast_ref::<crate::ast::rtl::series::RecordSeries>()
|
|
{
|
|
if let Some(field_series) = rs.field(k) {
|
|
let view = crate::ast::rtl::series::SeriesView::new(
|
|
field_series,
|
|
k,
|
|
);
|
|
return Ok(Value::Object(std::rc::Rc::new(view)));
|
|
} else {
|
|
return Err(format!(
|
|
"RecordSeries does not have field :{}",
|
|
k.name()
|
|
));
|
|
}
|
|
} else if let Some(sn) = obj.as_any().downcast_ref::<crate::ast::rtl::streams::StreamNode>() {
|
|
let mapped = crate::ast::rtl::streams::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::Object(obj) = rec {
|
|
if let Some(rs) = obj
|
|
.as_any()
|
|
.downcast_ref::<crate::ast::rtl::series::RecordSeries>()
|
|
{
|
|
if let Some(field_series) = rs.field(*k) {
|
|
let view = crate::ast::rtl::series::SeriesView::new(
|
|
field_series,
|
|
*k,
|
|
);
|
|
Ok(Value::Object(std::rc::Rc::new(view)))
|
|
} else {
|
|
Err(format!(
|
|
"RecordSeries does not have field :{}",
|
|
k.name()
|
|
))
|
|
}
|
|
} else if let Some(sn) = obj.as_any().downcast_ref::<crate::ast::rtl::streams::StreamNode>() {
|
|
let mapped = crate::ast::rtl::streams::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::Object(obj) => {
|
|
if let Some(closure) = obj.as_any().downcast_ref::<Closure>() {
|
|
self.frames.push(CallFrame {
|
|
stack_base: base,
|
|
closure: Some(obj.clone()),
|
|
});
|
|
|
|
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 BoundKind::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, &args_for_unpack, &mut offset)
|
|
{
|
|
res = Err(e);
|
|
break;
|
|
}
|
|
}
|
|
res
|
|
} else {
|
|
self.unpack(&closure.parameter_node, &args_for_unpack, &mut 0)
|
|
}
|
|
};
|
|
|
|
let res = match unpack_res {
|
|
Ok(_) => self.eval_internal(obs, &closure.exec_node),
|
|
Err(e) => Err(e),
|
|
};
|
|
|
|
self.frames.pop();
|
|
res
|
|
} else if let Some(series) = obj.as_series() {
|
|
let arg_len = self.stack.len() - base;
|
|
let res = if arg_len != 1 {
|
|
Err(format!(
|
|
"{} indexer expects exactly 1 argument (the lookback index)",
|
|
obj.type_name()
|
|
))
|
|
} else if let Value::Int(idx) = self.stack[base] {
|
|
if idx < 0 {
|
|
Err(format!(
|
|
"{} lookback index cannot be negative",
|
|
obj.type_name()
|
|
))
|
|
} else if let Some(val) = series.get_item(idx as usize) {
|
|
Ok(val)
|
|
} else {
|
|
Ok(Value::Void)
|
|
}
|
|
} else {
|
|
Err(format!(
|
|
"{} index must be an integer",
|
|
obj.type_name()
|
|
))
|
|
};
|
|
self.stack.truncate(base);
|
|
return res;
|
|
} else {
|
|
self.stack.truncate(base);
|
|
return Err(format!("Object is not callable: {}", obj.type_name()));
|
|
}
|
|
}
|
|
_ => {
|
|
self.stack.truncate(base);
|
|
return Err(format!("Attempt to call non-function: {}", current_func));
|
|
}
|
|
};
|
|
|
|
match result {
|
|
Ok(Value::TailCallRequest(payload)) => {
|
|
let (next_obj, next_args) = *payload;
|
|
current_func = Value::Object(next_obj);
|
|
self.stack.truncate(base);
|
|
self.stack.extend(next_args);
|
|
continue;
|
|
}
|
|
res => {
|
|
self.stack.truncate(base);
|
|
return res;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
BoundKind::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 {
|
|
Ok(Value::TailCallRequest(Box::new((
|
|
closure_obj.clone(),
|
|
arg_vals,
|
|
))))
|
|
} else {
|
|
Err("'again' called outside of a closure".to_string())
|
|
}
|
|
}
|
|
BoundKind::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))
|
|
}
|
|
BoundKind::Record { layout, values } => {
|
|
let mut evaluated_values = Vec::with_capacity(values.len());
|
|
for v in values {
|
|
evaluated_values.push(self.eval_internal(obs, v)?);
|
|
}
|
|
Ok(Value::Record(
|
|
layout.clone(),
|
|
std::rc::Rc::new(evaluated_values),
|
|
))
|
|
}
|
|
BoundKind::Expansion { bound_expanded, .. } => {
|
|
let mut curr = bound_expanded;
|
|
if !O::ACTIVE {
|
|
while let BoundKind::Expansion {
|
|
bound_expanded: next,
|
|
..
|
|
} = &curr.kind
|
|
{
|
|
curr = next;
|
|
}
|
|
}
|
|
self.eval_internal(obs, curr)
|
|
}
|
|
BoundKind::Extension(ext) => Err(format!(
|
|
"Execution of extension '{}' not implemented yet",
|
|
ext.display_name()
|
|
)),
|
|
BoundKind::Error => Err("Cannot execute a poisoned AST node".to_string()),
|
|
}
|
|
}
|
|
|
|
fn eval_args_to_stack<O: VMObserver>(
|
|
&mut self,
|
|
obs: &mut O,
|
|
args: &ExecNode,
|
|
) -> Result<(), String> {
|
|
match &args.kind {
|
|
BoundKind::Tuple { elements } => {
|
|
for e in elements {
|
|
let mut curr = e.as_ref();
|
|
if !O::ACTIVE {
|
|
while let BoundKind::Expansion {
|
|
bound_expanded: next,
|
|
..
|
|
} = &curr.kind
|
|
{
|
|
curr = next;
|
|
}
|
|
}
|
|
|
|
match &curr.kind {
|
|
BoundKind::Constant(v) if !O::ACTIVE => self.stack.push(v.clone()),
|
|
_ => {
|
|
let val = self.eval_internal(obs, curr)?;
|
|
self.stack.push(val);
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
BoundKind::Constant(v) => {
|
|
if let Some(slice) = v.as_slice() {
|
|
self.stack.extend_from_slice(slice);
|
|
} else {
|
|
self.stack.push(v.clone());
|
|
}
|
|
Ok(())
|
|
}
|
|
BoundKind::Expansion { bound_expanded, .. } => {
|
|
let mut curr = bound_expanded;
|
|
if !O::ACTIVE {
|
|
while let BoundKind::Expansion {
|
|
bound_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) -> 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 underflow capture local {}", slot))
|
|
}
|
|
}
|
|
Address::Upvalue(idx) => {
|
|
let frame = self.frames.last().ok_or("No call frame")?;
|
|
if let Some(closure_obj) = &frame.closure {
|
|
let closure = closure_obj.as_any().downcast_ref::<Closure>().unwrap();
|
|
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<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_obj) = &frame.closure {
|
|
let closure = closure_obj.as_any().downcast_ref::<Closure>().unwrap();
|
|
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) => {
|
|
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_obj) = &frame.closure {
|
|
let closure = closure_obj.as_any().downcast_ref::<Closure>().unwrap();
|
|
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<T>(
|
|
&mut self,
|
|
pattern: &Node<BoundKind<T>, T>,
|
|
values: &[Value],
|
|
offset: &mut usize,
|
|
) -> Result<(), String> {
|
|
match &pattern.kind {
|
|
BoundKind::Define { addr, .. } => {
|
|
let val = values.get(*offset).cloned().unwrap_or(Value::Void);
|
|
*offset += 1;
|
|
self.set_value(*addr, val)
|
|
}
|
|
BoundKind::Set { addr, .. } => {
|
|
let val = values.get(*offset).cloned().unwrap_or(Value::Void);
|
|
*offset += 1;
|
|
self.set_value(*addr, val)
|
|
}
|
|
BoundKind::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, sub_values, &mut sub_offset)?;
|
|
}
|
|
return Ok(());
|
|
}
|
|
for el in elements {
|
|
self.unpack(el, values, offset)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
_ => Err("Invalid node in parameter pattern".to_string()),
|
|
}
|
|
}
|
|
}
|