Refactor: Use Value::Closure enum variant
Previously, compiled closures were stored as `Value::Object` and then downcasted. This commit introduces a dedicated `Value::Closure` enum variant to represent compiled closures. This improves type safety and simplifies handling of closures throughout the compiler and VM. This change involves: - Updating type definitions to use `Value::Closure`. - Adjusting downcasting logic to directly access the `Closure` struct. - Modifying `run_with_args` and `run_with_args_observed` to expect `Value::Closure` for tail-call targets. - Refining the handling of closures in the `Optimizer` and `Inliner` for better type clarity.
This commit is contained in:
+113
-110
@@ -2,7 +2,7 @@ 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, build_pipeline_node, StreamNode};
|
||||
use crate::ast::types::{Object, PipeFn, Value};
|
||||
use crate::ast::types::{PipeFn, Value};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
@@ -88,7 +88,7 @@ pub struct VM {
|
||||
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<(Rc<dyn Object>, Vec<Value>)>,
|
||||
tail_call: Option<(Value, Vec<Value>)>,
|
||||
}
|
||||
|
||||
impl VM {
|
||||
@@ -111,60 +111,68 @@ impl VM {
|
||||
mut result: Result<Value, String>,
|
||||
) -> Result<Value, String> {
|
||||
loop {
|
||||
if let Some((next_obj, next_args)) = self.tail_call.take() {
|
||||
if let Ok(closure_rc) = next_obj.clone().into_rc_any().downcast::<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(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)?;
|
||||
}
|
||||
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);
|
||||
}
|
||||
// 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();
|
||||
} 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()
|
||||
));
|
||||
result = self.eval_observed(observer, &closure.exec_node);
|
||||
self.frames.pop();
|
||||
}
|
||||
if let Value::Int(idx) = &next_args[0] {
|
||||
if *idx < 0 {
|
||||
Value::Object(obj) => {
|
||||
if let Some(series) = obj.as_series() {
|
||||
if next_args.len() != 1 {
|
||||
return Err(format!(
|
||||
"{} indexer expects exactly 1 argument (the lookback index), got {}",
|
||||
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: {}",
|
||||
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 {}",
|
||||
obj.type_name(),
|
||||
next_args[0]
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(format!(
|
||||
"{} lookback index cannot be negative: {}",
|
||||
next_obj.type_name(),
|
||||
idx
|
||||
"Tail call target is not callable: {}",
|
||||
obj.type_name()
|
||||
));
|
||||
}
|
||||
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()
|
||||
));
|
||||
other => {
|
||||
return Err(format!("Tail call target is not callable: {}", other));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return result;
|
||||
@@ -174,23 +182,18 @@ impl VM {
|
||||
|
||||
pub fn run_with_args(
|
||||
&mut self,
|
||||
closure_obj: Rc<dyn Object>,
|
||||
closure_rc: Rc<Closure>,
|
||||
args: &[Value],
|
||||
) -> Result<Value, String> {
|
||||
self.run_with_args_observed(&mut NoOpObserver, closure_obj, args)
|
||||
self.run_with_args_observed(&mut NoOpObserver, closure_rc, args)
|
||||
}
|
||||
|
||||
pub fn run_with_args_observed<O: VMObserver>(
|
||||
&mut self,
|
||||
observer: &mut O,
|
||||
closure_obj: Rc<dyn Object>,
|
||||
closure_rc: Rc<Closure>,
|
||||
args: &[Value],
|
||||
) -> Result<Value, String> {
|
||||
let closure_rc = closure_obj
|
||||
.into_rc_any()
|
||||
.downcast::<Closure>()
|
||||
.map_err(|_| "Object is not a closure".to_string())?;
|
||||
|
||||
self.stack.clear();
|
||||
self.frames.clear();
|
||||
|
||||
@@ -416,8 +419,8 @@ impl VM {
|
||||
let mut pipe_vm = VM::new(self.globals.clone());
|
||||
|
||||
let executor: Box<PipeFn> = match lambda_val {
|
||||
Value::Object(obj) => {
|
||||
let my_closure = obj.clone();
|
||||
Value::Closure(rc) => {
|
||||
let my_closure = rc.clone();
|
||||
Box::new(move |args: &[Value]| -> Value {
|
||||
match pipe_vm.run_with_args(my_closure.clone(), args) {
|
||||
Ok(res) => res,
|
||||
@@ -465,7 +468,7 @@ impl VM {
|
||||
info.positional_count,
|
||||
stack_size,
|
||||
);
|
||||
Ok(Value::Object(Rc::new(closure)))
|
||||
Ok(Value::Closure(Rc::new(closure)))
|
||||
}
|
||||
NodeKind::Call { callee, args } => {
|
||||
let func_val = self.eval_internal(obs, callee)?;
|
||||
@@ -481,8 +484,8 @@ impl VM {
|
||||
self.stack.truncate(base);
|
||||
|
||||
match func_val {
|
||||
Value::Object(obj) => {
|
||||
self.tail_call = Some((obj, arg_vals));
|
||||
Value::Closure(_) | Value::Object(_) => {
|
||||
self.tail_call = Some((func_val, arg_vals));
|
||||
return Ok(Value::Void);
|
||||
}
|
||||
Value::Function(f) => return Ok((f.func)(&arg_vals)),
|
||||
@@ -612,57 +615,56 @@ impl VM {
|
||||
self.stack.truncate(base);
|
||||
return res;
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
if let Ok(closure_rc) =
|
||||
obj.clone().into_rc_any().downcast::<Closure>()
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
Ok(())
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
};
|
||||
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)
|
||||
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);
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
self.eval_internal(obs, &closure.exec_node)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
|
||||
self.frames.pop();
|
||||
res
|
||||
} else if let Some(series) = obj.as_series() {
|
||||
self.frames.pop();
|
||||
res
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
if let Some(series) = obj.as_series() {
|
||||
let arg_len = self.stack.len() - base;
|
||||
let res = if arg_len != 1 {
|
||||
Err(format!(
|
||||
@@ -707,6 +709,7 @@ impl VM {
|
||||
Value::Record(_, _) => "Record",
|
||||
Value::FieldAccessor(_) => "FieldAccessor",
|
||||
Value::Function(_) => "Function",
|
||||
Value::Closure(_) => "Closure",
|
||||
Value::Object(_) => "Object",
|
||||
Value::Cell(_) => "Cell",
|
||||
};
|
||||
@@ -714,8 +717,8 @@ impl VM {
|
||||
}
|
||||
};
|
||||
|
||||
if let Some((next_obj, next_args)) = self.tail_call.take() {
|
||||
current_func = Value::Object(next_obj);
|
||||
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;
|
||||
@@ -736,7 +739,7 @@ impl VM {
|
||||
|
||||
let frame = self.frames.last().ok_or("No call frame for 'again'")?;
|
||||
if let Some(closure_obj) = &frame.closure {
|
||||
self.tail_call = Some((closure_obj.clone() as Rc<dyn Object>, arg_vals));
|
||||
self.tail_call = Some((Value::Closure(closure_obj.clone()), arg_vals));
|
||||
Ok(Value::Void)
|
||||
} else {
|
||||
Err("'again' called outside of a closure".to_string())
|
||||
|
||||
Reference in New Issue
Block a user