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}