Skip to main content

lex_bytecode/vm/
closures.rs

1//! Closure invocation from the host side (#221): `Vm` as a
2//! `ClosureCaller` for the parser interpreter, and the
3//! `invoke_closure_*` helpers that splice a closure's captures in
4//! front of its call arguments before dispatching to the underlying
5//! function. Reentrant: a closure may be invoked from inside an
6//! already-running frame stack.
7
8use super::*;
9
10/// `Vm` exposes itself as a `ClosureCaller` so the parser interpreter
11/// can invoke user-supplied closures during a `parser.run` walk
12/// (#221). The Vm is reentrant for closure invocation: pushing a new
13/// frame onto an active call stack is supported, and the handler
14/// stays in place so any effects the closure body fires dispatch
15/// normally.
16impl<'a> crate::parser_runtime::ClosureCaller for Vm<'a> {
17    fn call_closure(&mut self, closure: Value, args: Vec<Value>) -> Result<Value, String> {
18        self.invoke_closure_value(closure, args)
19            .map_err(|e| format!("{e:?}"))
20    }
21}
22
23impl<'a> Vm<'a> {
24    /// Invoke a `Value::Closure` by combining its captures with the
25    /// supplied call args and dispatching to the underlying function.
26    /// Used by the parser interpreter (#221) to call user-supplied
27    /// `f` arguments inside `parser.map` / `parser.and_then` nodes.
28    pub fn invoke_closure_value(
29        &mut self,
30        closure: Value,
31        args: Vec<Value>,
32    ) -> Result<Value, VmError> {
33        let (fn_id, captures) = match closure {
34            Value::Closure { fn_id, captures, .. } => (fn_id, captures),
35            other => return Err(VmError::TypeMismatch(
36                format!("invoke_closure_value: not a closure: {other:?}"))),
37        };
38        let mut combined = captures;
39        combined.extend(args);
40        self.invoke(fn_id, combined)
41    }
42
43    /// Invoke a 1-arg closure without allocating a separate args
44    /// `Vec` (#464 call-overhead). The closure's own `captures` Vec
45    /// is reused as the combined `captures ++ [arg]` argument buffer,
46    /// so the per-element call in `ListMap`/`ListFilter`/`SortByKey`
47    /// allocates at most once (the `push`) instead of twice (a fresh
48    /// `vec![arg]` plus the `extend`). Semantically identical to
49    /// `invoke_closure_value(closure, vec![arg])`.
50    pub fn invoke_closure_1(&mut self, closure: Value, arg: Value) -> Result<Value, VmError> {
51        let (fn_id, mut combined) = match closure {
52            Value::Closure { fn_id, captures, .. } => (fn_id, captures),
53            other => return Err(VmError::TypeMismatch(
54                format!("invoke_closure_1: not a closure: {other:?}"))),
55        };
56        combined.push(arg);
57        self.invoke(fn_id, combined)
58    }
59
60    /// Invoke a 2-arg closure without a separate args `Vec` — the
61    /// `ListFold` combiner path. See `invoke_closure_1`.
62    pub fn invoke_closure_2(&mut self, closure: Value, a: Value, b: Value) -> Result<Value, VmError> {
63        let (fn_id, mut combined) = match closure {
64            Value::Closure { fn_id, captures, .. } => (fn_id, captures),
65            other => return Err(VmError::TypeMismatch(
66                format!("invoke_closure_2: not a closure: {other:?}"))),
67        };
68        combined.push(a);
69        combined.push(b);
70        self.invoke(fn_id, combined)
71    }
72}