From 13dc6beb522be8ea34f30e2c0332c7f3eb6ca134 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Fri, 6 Mar 2026 12:35:00 +0100 Subject: [PATCH] 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. --- examples/err.myc | 6 +++++ examples/sma.myc | 1 + src/ast/environment.rs | 27 ++++++------------- src/ast/vm.rs | 59 ++++++++++++++++++++++++++---------------- 4 files changed, 52 insertions(+), 41 deletions(-) create mode 100644 examples/err.myc diff --git a/examples/err.myc b/examples/err.myc new file mode 100644 index 0000000..ab2b37b --- /dev/null +++ b/examples/err.myc @@ -0,0 +1,6 @@ +;; Output: 0 +(do + (def history (series :float)) + (push history 0.0) + (history 0) +) \ No newline at end of file diff --git a/examples/sma.myc b/examples/sma.myc index ede3fa8..fd43f66 100644 --- a/examples/sma.myc +++ b/examples/sma.myc @@ -10,6 +10,7 @@ (def sum (+ val (history 0))) (push history sum) (/ sum length) +(history 0) )) ))) diff --git a/src/ast/environment.rs b/src/ast/environment.rs index 638a4e0..feda2db 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -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::() - .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)) diff --git a/src/ast/vm.rs b/src/ast/vm.rs index 40cc314..a17c289 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -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( + &mut self, + observer: &mut O, + mut result: Result, + ) -> Result { loop { match result { Ok(Value::TailCallRequest(payload)) => { let (next_obj, next_args) = *payload; if let Some(closure) = next_obj.as_any().downcast_ref::() { 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!( + "{} index must be an integer, got {}", + next_obj.type_name(), + next_args[0] + )); + } } else { return Err(format!( - "Tail call target is not a closure: {}", + "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::().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( &mut self, obs: &mut O,