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)))
|
(def sum (+ val (history 0)))
|
||||||
(push history sum)
|
(push history sum)
|
||||||
(/ sum length)
|
(/ sum length)
|
||||||
|
(history 0)
|
||||||
))
|
))
|
||||||
)))
|
)))
|
||||||
|
|
||||||
|
|||||||
+8
-19
@@ -501,7 +501,10 @@ impl Environment {
|
|||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => panic!("Myc Runtime Error: {}", e),
|
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),
|
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);
|
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();
|
self.run_pipeline();
|
||||||
|
|
||||||
Ok((result, observer.logs))
|
Ok((result, observer.logs))
|
||||||
|
|||||||
+37
-22
@@ -144,14 +144,23 @@ impl VM {
|
|||||||
stack_base: 0,
|
stack_base: 0,
|
||||||
closure: None,
|
closure: None,
|
||||||
});
|
});
|
||||||
let mut result = self.eval(root);
|
let result = self.eval(root);
|
||||||
self.frames.pop();
|
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 {
|
loop {
|
||||||
match result {
|
match result {
|
||||||
Ok(Value::TailCallRequest(payload)) => {
|
Ok(Value::TailCallRequest(payload)) => {
|
||||||
let (next_obj, next_args) = *payload;
|
let (next_obj, next_args) = *payload;
|
||||||
if let Some(closure) = next_obj.as_any().downcast_ref::<Closure>() {
|
if let Some(closure) = next_obj.as_any().downcast_ref::<Closure>() {
|
||||||
self.stack.clear();
|
self.stack.clear();
|
||||||
|
// frames should be empty here since we popped before entering the loop
|
||||||
self.frames.push(CallFrame {
|
self.frames.push(CallFrame {
|
||||||
stack_base: 0,
|
stack_base: 0,
|
||||||
closure: Some(next_obj.clone()),
|
closure: Some(next_obj.clone()),
|
||||||
@@ -163,11 +172,35 @@ impl VM {
|
|||||||
} else {
|
} else {
|
||||||
self.unpack(&closure.parameter_node, &next_args, &mut 0)?;
|
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();
|
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 {
|
} else {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Tail call target is not a closure: {}",
|
"Tail call target is not callable: {}",
|
||||||
next_obj.type_name()
|
next_obj.type_name()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -235,7 +268,7 @@ impl VM {
|
|||||||
});
|
});
|
||||||
let result = self.eval_observed(observer, root);
|
let result = self.eval_observed(observer, root);
|
||||||
self.frames.pop();
|
self.frames.pop();
|
||||||
result
|
self.resolve_tail_calls(observer, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[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>(
|
fn eval_args_to_stack<O: VMObserver>(
|
||||||
&mut self,
|
&mut self,
|
||||||
obs: &mut O,
|
obs: &mut O,
|
||||||
|
|||||||
Reference in New Issue
Block a user