Refactor tail call resolution logic
The tail call resolution logic was duplicated in `Environment::call` and `VM::run`. This commit extracts the tail call resolution logic into a single method `VM::resolve_tail_calls` and uses it in both places. Additionally, this commit adds support for series indexing as a form of tail call, allowing for direct access to series elements through the `series(index)` syntax. This is useful for back-referencing in time-series data. A new example `err.myc` is added to demonstrate basic series usage and error handling.
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
;; Output: 0
|
||||
(do
|
||||
(def history (series :float))
|
||||
(push history 0.0)
|
||||
(history 0)
|
||||
)
|
||||
@@ -10,6 +10,7 @@
|
||||
(def sum (+ val (history 0)))
|
||||
(push history sum)
|
||||
(/ sum length)
|
||||
(history 0)
|
||||
))
|
||||
)))
|
||||
|
||||
|
||||
+8
-19
@@ -501,7 +501,10 @@ impl Environment {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("Myc Runtime Error: {}", e),
|
||||
};
|
||||
vm.resolve_tail_calls(res)
|
||||
match vm.resolve_tail_calls(&mut crate::ast::vm::NoOpObserver, Ok(res)) {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("Myc Runtime Error: {}", e),
|
||||
}
|
||||
}),
|
||||
});
|
||||
}
|
||||
@@ -528,7 +531,10 @@ impl Environment {
|
||||
Err(e) => panic!("Myc Runtime Error (Closure): {}", e),
|
||||
};
|
||||
}
|
||||
vm.resolve_tail_calls(final_res)
|
||||
match vm.resolve_tail_calls(&mut crate::ast::vm::NoOpObserver, Ok(final_res)) {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("Myc Runtime Error: {}", e),
|
||||
}
|
||||
}),
|
||||
})
|
||||
}
|
||||
@@ -645,23 +651,6 @@ impl Environment {
|
||||
result = vm.run_with_observer(&mut observer, &closure.exec_node);
|
||||
}
|
||||
|
||||
while let Ok(Value::TailCallRequest(payload)) = result {
|
||||
let (next_obj, next_args) = *payload;
|
||||
if next_obj
|
||||
.as_any()
|
||||
.downcast_ref::<crate::ast::vm::Closure>()
|
||||
.is_some()
|
||||
{
|
||||
result = vm.run_with_args_observed(&mut observer, next_obj, &next_args);
|
||||
} else {
|
||||
result = Err(format!(
|
||||
"Tail call target is not a closure: {}",
|
||||
next_obj.type_name()
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
self.run_pipeline();
|
||||
|
||||
Ok((result, observer.logs))
|
||||
|
||||
+37
-22
@@ -144,14 +144,23 @@ impl VM {
|
||||
stack_base: 0,
|
||||
closure: None,
|
||||
});
|
||||
let mut result = self.eval(root);
|
||||
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()),
|
||||
@@ -163,11 +172,35 @@ impl VM {
|
||||
} else {
|
||||
self.unpack(&closure.parameter_node, &next_args, &mut 0)?;
|
||||
}
|
||||
result = self.eval(&closure.exec_node);
|
||||
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!(
|
||||
"Tail call target is not a closure: {}",
|
||||
"{} 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()
|
||||
));
|
||||
}
|
||||
@@ -235,7 +268,7 @@ impl VM {
|
||||
});
|
||||
let result = self.eval_observed(observer, root);
|
||||
self.frames.pop();
|
||||
result
|
||||
self.resolve_tail_calls(observer, result)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@@ -745,24 +778,6 @@ impl VM {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_tail_calls(&mut self, mut result: Value) -> Value {
|
||||
while let Value::TailCallRequest(payload) = result {
|
||||
let (next_obj, next_args) = *payload;
|
||||
if next_obj.as_any().downcast_ref::<Closure>().is_some() {
|
||||
result = match self.run_with_args(next_obj, &next_args) {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("Myc Runtime Error (TailCall): {}", e),
|
||||
};
|
||||
} else {
|
||||
panic!(
|
||||
"Tail call target is not a closure: {}",
|
||||
next_obj.type_name()
|
||||
);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn eval_args_to_stack<O: VMObserver>(
|
||||
&mut self,
|
||||
obs: &mut O,
|
||||
|
||||
Reference in New Issue
Block a user