Refactor VM evaluation for observer pattern

The `dispatch_eval` macro has been replaced with explicit
`eval_internal` and `eval_core` methods. This change aims to streamline
the evaluation process and better support the observer pattern by
providing clearer hooks for observing VM execution.
This commit is contained in:
Michael Schimmel
2026-02-23 18:10:20 +01:00
parent 229ca3eefa
commit be7ce31408
+236 -185
View File
@@ -128,141 +128,6 @@ pub struct VM {
frames: Vec<CallFrame>,
}
macro_rules! dispatch_eval {
($self:ident, $node:ident, $eval_method:ident $(, $observer:ident)?) => {
match &$node.kind {
BoundKind::Nop => Ok(Value::Void),
BoundKind::Constant(v) => Ok(v.clone()),
BoundKind::Parameter { .. } => Ok(Value::Void),
BoundKind::DefGlobal { global_index, value, .. } => {
let val = $self.$eval_method($($observer,)? value)?;
let idx = *global_index as usize;
let mut globals = $self.globals.borrow_mut();
if idx >= globals.len() { globals.resize(idx + 1, Value::Void); }
globals[idx] = val.clone();
Ok(val)
},
BoundKind::Get { addr, .. } => $self.get_value(*addr),
BoundKind::Set { addr, value } => {
let val = $self.$eval_method($($observer,)? value)?;
$self.set_value(*addr, val.clone())?;
Ok(val)
},
BoundKind::DefLocal { slot, value, captured_by, .. } => {
let val = $self.$eval_method($($observer,)? value)?;
let final_val = if !captured_by.is_empty() { Value::Cell(Rc::new(RefCell::new(val))) } else { val };
let frame = $self.frames.last().ok_or("No call frame")?;
let abs_index = frame.stack_base + (*slot as usize);
if abs_index == $self.stack.len() { $self.stack.push(final_val.clone()); }
else if abs_index < $self.stack.len() { $self.stack[abs_index] = final_val.clone(); }
else { return Err(format!("Stack gap at local {}", slot)); }
Ok(final_val)
},
BoundKind::If { cond, then_br, else_br } => {
let c = $self.$eval_method($($observer,)? cond)?;
if c.is_truthy() { $self.$eval_method($($observer,)? then_br) }
else if let Some(e) = else_br { $self.$eval_method($($observer,)? e) }
else { Ok(Value::Void) }
},
BoundKind::Block { exprs } => {
let mut last = Value::Void;
for e in exprs { last = $self.$eval_method($($observer,)? 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 mut func_val = $self.$eval_method($($observer,)? callee)?;
macro_rules! get_arg_vals {
($s:ident, $a:ident) => { $s.prepare_args($a)? };
($s:ident, $o:ident, $a:ident) => { $s.prepare_args_observed($o, $a)? };
}
let mut arg_vals = match &args.kind {
BoundKind::Tuple { elements } => {
let mut vals = Vec::with_capacity(elements.len());
let mut is_complex = false;
for e in elements {
if matches!(e.kind, BoundKind::Tuple { .. }) { is_complex = true; break; }
vals.push($self.$eval_method($($observer,)? e)?);
}
if is_complex { get_arg_vals!($self, $($observer,)? args) } else { vals }
}
_ => { get_arg_vals!($self, $($observer,)? args) }
};
if $node.ty.is_tail {
match func_val {
Value::Object(obj) => return Ok(Value::TailCallRequest(Box::new((obj, arg_vals)))),
Value::Function(f) => return Ok((f.func)(arg_vals)),
_ => return Err(format!("Tail call target is not a function: {}", func_val)),
}
}
loop {
match func_val {
Value::Function(f) => break Ok((f.func)(arg_vals)),
Value::Object(obj) => {
if let Some(closure) = obj.as_any().downcast_ref::<Closure>() {
let old_stack_top = $self.stack.len();
let closure_rc = Rc::new(closure.clone());
$self.frames.push(CallFrame { stack_base: old_stack_top, closure: Some(closure_rc.clone()) });
if let Some(count) = closure.positional_count && arg_vals.len() == count as usize {
$self.stack.extend(arg_vals);
} else {
$self.unpack(&closure.parameter_node, &arg_vals, &mut 0)?;
}
let result = $self.$eval_method($($observer,)? &closure.exec_node);
$self.frames.pop();
$self.stack.truncate(old_stack_top);
match result {
Ok(Value::TailCallRequest(payload)) => {
let (next_obj, next_args) = *payload;
func_val = Value::Object(next_obj);
arg_vals = next_args;
continue;
},
res => break res,
}
} else { break Err(format!("Object is not a closure: {}", obj.type_name())); }
},
_ => break Err(format!("Attempt to call non-function: {}", func_val)),
}
}
},
BoundKind::Tuple { elements } => {
let mut vals = Vec::with_capacity(elements.len());
for e in elements { vals.push($self.$eval_method($($observer,)? e)?); }
Ok(Value::make_tuple(vals))
},
BoundKind::Record { fields } => {
let mut keys = Vec::with_capacity(fields.len());
let mut values = Vec::with_capacity(fields.len());
for (k, v) in fields {
let key = $self.$eval_method($($observer,)? k)?;
let val = $self.$eval_method($($observer,)? v)?;
if let Value::Keyword(kw) = key { keys.push(kw); values.push(val); }
else { return Err(format!("Record key must be keyword, got {}", key)); }
}
Ok(Value::make_record(keys, values))
},
BoundKind::Expansion { bound_expanded, .. } => { $self.$eval_method($($observer,)? bound_expanded) }
BoundKind::Extension(ext) => { Err(format!("Execution of extension '{}' not implemented yet", ext.display_name())) }
}
};
}
impl VM {
pub fn new(globals: Rc<RefCell<Vec<Value>>>) -> Self {
Self {
@@ -369,9 +234,236 @@ impl VM {
#[inline(always)]
fn eval(&mut self, node: &ExecNode) -> Result<Value, String> {
dispatch_eval!(self, node, eval)
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::Parameter { .. } => Ok(Value::Void),
BoundKind::DefGlobal {
global_index,
value,
..
} => {
let val = self.eval_internal(obs, value)?;
let idx = *global_index as usize;
let mut globals = self.globals.borrow_mut();
if idx >= globals.len() {
globals.resize(idx + 1, Value::Void);
}
globals[idx] = val.clone();
Ok(val)
}
BoundKind::Get { addr, .. } => self.get_value(*addr),
BoundKind::Set { addr, value } => {
let val = self.eval_internal(obs, value)?;
self.set_value(*addr, val.clone())?;
Ok(val)
}
BoundKind::DefLocal {
slot,
value,
captured_by,
..
} => {
let val = self.eval_internal(obs, value)?;
let final_val = if !captured_by.is_empty() {
Value::Cell(Rc::new(RefCell::new(val)))
} else {
val
};
let frame = self.frames.last().ok_or("No call frame")?;
let abs_index = frame.stack_base + (*slot as usize);
if abs_index == self.stack.len() {
self.stack.push(final_val.clone());
} else if abs_index < self.stack.len() {
self.stack[abs_index] = final_val.clone();
} else {
return Err(format!("Stack gap at local {}", slot));
}
Ok(final_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::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 mut func_val = self.eval_internal(obs, callee)?;
let mut arg_vals = match &args.kind {
BoundKind::Tuple { elements } => {
let mut vals = Vec::with_capacity(elements.len());
let mut is_complex = false;
for e in elements {
if matches!(e.kind, BoundKind::Tuple { .. }) {
is_complex = true;
break;
}
vals.push(self.eval_internal(obs, e)?);
}
if is_complex {
self.prepare_args_internal(obs, args)?
} else {
vals
}
}
_ => self.prepare_args_internal(obs, args)?,
};
if node.ty.is_tail {
match func_val {
Value::Object(obj) => {
return Ok(Value::TailCallRequest(Box::new((obj, arg_vals))))
}
Value::Function(f) => return Ok((f.func)(arg_vals)),
_ => {
return Err(format!(
"Tail call target is not a function: {}",
func_val
))
}
}
}
loop {
match func_val {
Value::Function(f) => break Ok((f.func)(arg_vals)),
Value::Object(obj) => {
if let Some(closure) = obj.as_any().downcast_ref::<Closure>() {
let old_stack_top = self.stack.len();
let closure_rc = Rc::new(closure.clone());
self.frames.push(CallFrame {
stack_base: old_stack_top,
closure: Some(closure_rc.clone()),
});
if let Some(count) = closure.positional_count
&& arg_vals.len() == count as usize
{
self.stack.extend(arg_vals);
} else {
self.unpack(&closure.parameter_node, &arg_vals, &mut 0)?;
}
let result = self.eval_internal(obs, &closure.exec_node);
self.frames.pop();
self.stack.truncate(old_stack_top);
match result {
Ok(Value::TailCallRequest(payload)) => {
let (next_obj, next_args) = *payload;
func_val = Value::Object(next_obj);
arg_vals = next_args;
continue;
}
res => break res,
}
} else {
break Err(format!(
"Object is not a closure: {}",
obj.type_name()
));
}
}
_ => break Err(format!("Attempt to call non-function: {}", func_val)),
}
}
}
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 { fields } => {
let mut keys = Vec::with_capacity(fields.len());
let mut values = Vec::with_capacity(fields.len());
for (k, v) in fields {
let key = self.eval_internal(obs, k)?;
let val = self.eval_internal(obs, v)?;
if let Value::Keyword(kw) = key {
keys.push(kw);
values.push(val);
} else {
return Err(format!("Record key must be keyword, got {}", key));
}
}
Ok(Value::make_record(keys, values))
}
BoundKind::Expansion { bound_expanded, .. } => self.eval_internal(obs, bound_expanded),
BoundKind::Extension(ext) => Err(format!(
"Execution of extension '{}' not implemented yet",
ext.display_name()
)),
}
}
pub fn resolve_tail_calls(&mut self, mut result: Value) -> Value {
while let Value::TailCallRequest(payload) = result {
let (next_obj, next_args) = *payload;
@@ -390,20 +482,6 @@ impl VM {
result
}
fn eval_observed<O: VMObserver>(
&mut self,
observer: &mut O,
node: &ExecNode,
) -> Result<Value, String> {
observer.before_eval(self, node);
let wrapper = |vm: &mut Self, obs: &mut O| -> Result<Value, String> {
dispatch_eval!(vm, node, eval_observed, obs)
};
let result = wrapper(self, observer);
observer.after_eval(self, node, &result);
result
}
fn capture_upvalue(&mut self, addr: Address) -> Result<Rc<RefCell<Value>>, String> {
match addr {
Address::Local(idx) => {
@@ -532,62 +610,35 @@ impl VM {
}
}
fn prepare_args(&mut self, args: &ExecNode) -> Result<Vec<Value>, String> {
let mut arg_vals = Vec::new();
match &args.kind {
BoundKind::Tuple { elements } => {
self.eval_and_flatten(elements, &mut arg_vals)?;
}
_ => {
VM::flatten_value(self.eval(args)?, &mut arg_vals);
}
}
Ok(arg_vals)
}
fn prepare_args_observed<O: VMObserver>(
fn prepare_args_internal<O: VMObserver>(
&mut self,
observer: &mut O,
obs: &mut O,
args: &ExecNode,
) -> Result<Vec<Value>, String> {
let mut arg_vals = Vec::new();
match &args.kind {
BoundKind::Tuple { elements } => {
self.eval_observed_and_flatten(observer, elements, &mut arg_vals)?;
self.eval_and_flatten_internal(obs, elements, &mut arg_vals)?;
}
_ => {
VM::flatten_value(self.eval_observed(observer, args)?, &mut arg_vals);
VM::flatten_value(self.eval_internal(obs, args)?, &mut arg_vals);
}
}
Ok(arg_vals)
}
fn eval_and_flatten(
fn eval_and_flatten_internal<O: VMObserver>(
&mut self,
elements: &[ExecNode],
into: &mut Vec<Value>,
) -> Result<(), String> {
for e in elements {
match &e.kind {
BoundKind::Tuple { elements: sub } => self.eval_and_flatten(sub, into)?,
_ => into.push(self.eval(e)?),
}
}
Ok(())
}
fn eval_observed_and_flatten<O: VMObserver>(
&mut self,
observer: &mut O,
obs: &mut O,
elements: &[ExecNode],
into: &mut Vec<Value>,
) -> Result<(), String> {
for e in elements {
match &e.kind {
BoundKind::Tuple { elements: sub } => {
self.eval_observed_and_flatten(observer, sub, into)?
self.eval_and_flatten_internal(obs, sub, into)?
}
_ => into.push(self.eval_observed(observer, e)?),
_ => into.push(self.eval_internal(obs, e)?),
}
}
Ok(())