Skip to main content

lex_bytecode/
vm.rs

1//! M5: bytecode VM. Stack machine with effect dispatch through a host handler.
2
3use crate::op::*;
4use crate::program::*;
5use crate::value::Value;
6use indexmap::IndexMap;
7
8#[derive(Debug, Clone, thiserror::Error)]
9pub enum VmError {
10    #[error("runtime panic: {0}")]
11    Panic(String),
12    #[error("type mismatch at runtime: {0}")]
13    TypeMismatch(String),
14    #[error("stack underflow")]
15    StackUnderflow,
16    #[error("unknown function id: {0}")]
17    UnknownFunction(u32),
18    #[error("effect handler error: {0}")]
19    Effect(String),
20    #[error("call stack overflow: recursion depth exceeded ({0})")]
21    CallStackOverflow(u32),
22}
23
24/// Maximum simultaneous call frames. Defends against unbounded
25/// recursion in agent-emitted code: a body that calls itself
26/// without a base case would otherwise blow the host's native
27/// stack and crash the process. Real Lex code rarely exceeds
28/// ~30 frames; 1024 is generous headroom while still well under
29/// the OS stack limit at any per-frame size we use.
30pub const MAX_CALL_DEPTH: u32 = 1024;
31
32/// Host-side effect dispatch. Implementors decide what `kind`/`op` mean
33/// and how arguments map to side effects.
34pub trait EffectHandler {
35    fn dispatch(&mut self, kind: &str, op: &str, args: Vec<Value>) -> Result<Value, String>;
36}
37
38/// A handler that fails any effect call. Useful as a default for pure-only runs.
39pub struct DenyAllEffects;
40impl EffectHandler for DenyAllEffects {
41    fn dispatch(&mut self, kind: &str, op: &str, _args: Vec<Value>) -> Result<Value, String> {
42        Err(format!("effects not permitted (attempted {kind}.{op})"))
43    }
44}
45
46/// Trace receiver. Implementors record the call/effect tree and may
47/// substitute effect responses (for replay).
48pub trait Tracer {
49    fn enter_call(&mut self, node_id: &str, name: &str, args: &[Value]);
50    fn enter_effect(&mut self, node_id: &str, kind: &str, op: &str, args: &[Value]);
51    fn exit_ok(&mut self, value: &Value);
52    fn exit_err(&mut self, message: &str);
53    /// Tail-call optimization: pop the current frame's open call without
54    /// re-entering the parent (the new call takes its place).
55    fn exit_call_tail(&mut self);
56    /// During replay, return Some(v) to substitute an effect's output.
57    fn override_effect(&mut self, _node_id: &str) -> Option<Value> { None }
58}
59
60/// No-op tracer for normal execution.
61pub struct NullTracer;
62impl Tracer for NullTracer {
63    fn enter_call(&mut self, _: &str, _: &str, _: &[Value]) {}
64    fn enter_effect(&mut self, _: &str, _: &str, _: &str, _: &[Value]) {}
65    fn exit_ok(&mut self, _: &Value) {}
66    fn exit_err(&mut self, _: &str) {}
67    fn exit_call_tail(&mut self) {}
68}
69
70#[derive(Debug, Clone)]
71pub(crate) enum FrameKind {
72    /// Top-level entry frame; doesn't correspond to a Call opcode.
73    Entry,
74    /// Frame opened by Call/TailCall. The `String` is the originating
75    /// `NodeId`; useful for diagnostics even if currently unread.
76    Call(#[allow(dead_code)] String),
77}
78
79pub struct Vm<'a> {
80    program: &'a Program,
81    handler: Box<dyn EffectHandler + 'a>,
82    pub(crate) tracer: Box<dyn Tracer + 'a>,
83    /// Per-call frames. Each frame has its own locals array and pc.
84    frames: Vec<Frame>,
85    stack: Vec<Value>,
86    /// Soft cap to avoid runaway computations in tests.
87    pub step_limit: u64,
88    pub steps: u64,
89}
90
91struct Frame {
92    fn_id: u32,
93    pc: usize,
94    locals: Vec<Value>,
95    /// Stack base when this frame started (for cleanup on return).
96    stack_base: usize,
97    trace_kind: FrameKind,
98}
99
100fn const_str(constants: &[Const], idx: u32) -> String {
101    match constants.get(idx as usize) {
102        Some(Const::NodeId(s)) | Some(Const::Str(s)) => s.clone(),
103        _ => String::new(),
104    }
105}
106
107impl<'a> Vm<'a> {
108    pub fn new(program: &'a Program) -> Self {
109        Self::with_handler(program, Box::new(DenyAllEffects))
110    }
111
112    pub fn with_handler(program: &'a Program, handler: Box<dyn EffectHandler + 'a>) -> Self {
113        Self {
114            program,
115            handler,
116            tracer: Box::new(NullTracer),
117            frames: Vec::new(),
118            stack: Vec::new(),
119            step_limit: 10_000_000,
120            steps: 0,
121        }
122    }
123
124    pub fn set_tracer(&mut self, tracer: Box<dyn Tracer + 'a>) {
125        self.tracer = tracer;
126    }
127
128    /// Cap the number of opcode dispatches before the VM aborts with
129    /// `step limit exceeded`. Useful as a runtime DoS guard against
130    /// untrusted code (e.g. the `agent-tool` sandbox, where an LLM
131    /// could emit `list.fold(list.range(0, 1_000_000_000), …)` to hang
132    /// the host). Default is 10_000_000.
133    pub fn set_step_limit(&mut self, limit: u64) {
134        self.step_limit = limit;
135    }
136
137    pub fn call(&mut self, name: &str, args: Vec<Value>) -> Result<Value, VmError> {
138        let fn_id = self.program.lookup(name).ok_or_else(|| VmError::Panic(format!("no function `{name}`")))?;
139        self.invoke(fn_id, args)
140    }
141
142    pub fn invoke(&mut self, fn_id: u32, args: Vec<Value>) -> Result<Value, VmError> {
143        let f = &self.program.functions[fn_id as usize];
144        if args.len() != f.arity as usize {
145            return Err(VmError::Panic(format!("arity mismatch calling {}", f.name)));
146        }
147        let mut locals = vec![Value::Unit; f.locals_count.max(f.arity) as usize];
148        for (i, v) in args.into_iter().enumerate() { locals[i] = v; }
149        self.push_frame(Frame {
150            fn_id, pc: 0, locals, stack_base: self.stack.len(),
151            trace_kind: FrameKind::Entry,
152        })?;
153        self.run()
154    }
155
156    /// All call-frame pushes funnel through here so the depth
157    /// check can't be skipped by a missing branch. Returns
158    /// `CallStackOverflow` instead of letting recursion blow the
159    /// host's native stack.
160    fn push_frame(&mut self, frame: Frame) -> Result<(), VmError> {
161        if self.frames.len() as u32 >= MAX_CALL_DEPTH {
162            return Err(VmError::CallStackOverflow(MAX_CALL_DEPTH));
163        }
164        self.frames.push(frame);
165        Ok(())
166    }
167
168    fn run(&mut self) -> Result<Value, VmError> {
169        loop {
170            if self.steps > self.step_limit {
171                return Err(VmError::Panic(format!(
172                    "step limit exceeded ({} > {})",
173                    self.steps, self.step_limit,
174                )));
175            }
176            self.steps += 1;
177            let frame_idx = self.frames.len() - 1;
178            let pc = self.frames[frame_idx].pc;
179            let fn_id = self.frames[frame_idx].fn_id;
180            let code = &self.program.functions[fn_id as usize].code;
181            if pc >= code.len() {
182                return Err(VmError::Panic("ran past end of code".into()));
183            }
184            let op = code[pc].clone();
185            self.frames[frame_idx].pc = pc + 1;
186
187            match op {
188                Op::PushConst(i) => {
189                    let c = &self.program.constants[i as usize];
190                    self.stack.push(const_to_value(c));
191                }
192                Op::Pop => { self.pop()?; }
193                Op::Dup => {
194                    let v = self.peek()?.clone();
195                    self.stack.push(v);
196                }
197                Op::LoadLocal(i) => {
198                    let v = self.frames[frame_idx].locals[i as usize].clone();
199                    self.stack.push(v);
200                }
201                Op::StoreLocal(i) => {
202                    let v = self.pop()?;
203                    self.frames[frame_idx].locals[i as usize] = v;
204                }
205                Op::MakeRecord { field_name_indices } => {
206                    let n = field_name_indices.len();
207                    let mut values: Vec<Value> = (0..n).map(|_| Value::Unit).collect();
208                    for i in (0..n).rev() {
209                        values[i] = self.pop()?;
210                    }
211                    let mut rec = IndexMap::new();
212                    for (i, val) in values.into_iter().enumerate() {
213                        let name = match &self.program.constants[field_name_indices[i] as usize] {
214                            Const::FieldName(s) => s.clone(),
215                            _ => return Err(VmError::TypeMismatch("expected FieldName const".into())),
216                        };
217                        rec.insert(name, val);
218                    }
219                    self.stack.push(Value::Record(rec));
220                }
221                Op::MakeTuple(n) => {
222                    let mut items: Vec<Value> = (0..n).map(|_| Value::Unit).collect();
223                    for i in (0..n as usize).rev() { items[i] = self.pop()?; }
224                    self.stack.push(Value::Tuple(items));
225                }
226                Op::MakeList(n) => {
227                    let mut items: Vec<Value> = (0..n).map(|_| Value::Unit).collect();
228                    for i in (0..n as usize).rev() { items[i] = self.pop()?; }
229                    self.stack.push(Value::List(items));
230                }
231                Op::MakeVariant { name_idx, arity } => {
232                    let mut args: Vec<Value> = (0..arity).map(|_| Value::Unit).collect();
233                    for i in (0..arity as usize).rev() { args[i] = self.pop()?; }
234                    let name = match &self.program.constants[name_idx as usize] {
235                        Const::VariantName(s) => s.clone(),
236                        _ => return Err(VmError::TypeMismatch("expected VariantName const".into())),
237                    };
238                    self.stack.push(Value::Variant { name, args });
239                }
240                Op::GetField(i) => {
241                    let name = match &self.program.constants[i as usize] {
242                        Const::FieldName(s) => s.clone(),
243                        _ => return Err(VmError::TypeMismatch("expected FieldName const".into())),
244                    };
245                    let v = self.pop()?;
246                    match v {
247                        Value::Record(r) => {
248                            let v = r.get(&name).cloned()
249                                .ok_or_else(|| VmError::TypeMismatch(format!("missing field `{name}`")))?;
250                            self.stack.push(v);
251                        }
252                        other => return Err(VmError::TypeMismatch(format!("GetField on non-record: {other:?}"))),
253                    }
254                }
255                Op::GetElem(i) => {
256                    let v = self.pop()?;
257                    match v {
258                        Value::Tuple(items) => {
259                            let v = items.into_iter().nth(i as usize)
260                                .ok_or_else(|| VmError::TypeMismatch(format!("tuple index {i} out of range")))?;
261                            self.stack.push(v);
262                        }
263                        other => return Err(VmError::TypeMismatch(format!("GetElem on non-tuple: {other:?}"))),
264                    }
265                }
266                Op::TestVariant(i) => {
267                    let name = match &self.program.constants[i as usize] {
268                        Const::VariantName(s) => s.clone(),
269                        _ => return Err(VmError::TypeMismatch("expected VariantName const".into())),
270                    };
271                    let v = self.pop()?;
272                    match &v {
273                        Value::Variant { name: vname, .. } => {
274                            self.stack.push(Value::Bool(vname == &name));
275                        }
276                        // For tag-only enums of primitive type (e.g. ParseError = Empty | NotNumber)
277                        // the value is currently a Variant too, since constructors emit MakeVariant.
278                        other => return Err(VmError::TypeMismatch(format!("TestVariant on non-variant: {other:?}"))),
279                    }
280                }
281                Op::GetVariant(_i) => {
282                    let v = self.pop()?;
283                    match v {
284                        Value::Variant { args, .. } => {
285                            self.stack.push(Value::Tuple(args));
286                        }
287                        other => return Err(VmError::TypeMismatch(format!("GetVariant on non-variant: {other:?}"))),
288                    }
289                }
290                Op::GetVariantArg(i) => {
291                    let v = self.pop()?;
292                    match v {
293                        Value::Variant { mut args, .. } => {
294                            if (i as usize) >= args.len() {
295                                return Err(VmError::TypeMismatch("variant arg index oob".into()));
296                            }
297                            self.stack.push(args.swap_remove(i as usize));
298                        }
299                        other => return Err(VmError::TypeMismatch(format!("GetVariantArg on non-variant: {other:?}"))),
300                    }
301                }
302                Op::GetListLen => {
303                    let v = self.pop()?;
304                    match v {
305                        Value::List(items) => self.stack.push(Value::Int(items.len() as i64)),
306                        other => return Err(VmError::TypeMismatch(format!("GetListLen on non-list: {other:?}"))),
307                    }
308                }
309                Op::GetListElem(i) => {
310                    let v = self.pop()?;
311                    match v {
312                        Value::List(items) => {
313                            let v = items.into_iter().nth(i as usize)
314                                .ok_or_else(|| VmError::TypeMismatch("list index oob".into()))?;
315                            self.stack.push(v);
316                        }
317                        other => return Err(VmError::TypeMismatch(format!("GetListElem on non-list: {other:?}"))),
318                    }
319                }
320                Op::GetListElemDyn => {
321                    // Stack: [list, idx]
322                    let idx = match self.pop()? {
323                        Value::Int(n) => n as usize,
324                        other => return Err(VmError::TypeMismatch(format!("GetListElemDyn idx: {other:?}"))),
325                    };
326                    let v = self.pop()?;
327                    match v {
328                        Value::List(items) => {
329                            let v = items.into_iter().nth(idx)
330                                .ok_or_else(|| VmError::TypeMismatch("list index oob".into()))?;
331                            self.stack.push(v);
332                        }
333                        other => return Err(VmError::TypeMismatch(format!("GetListElemDyn on non-list: {other:?}"))),
334                    }
335                }
336                Op::ListAppend => {
337                    let value = self.pop()?;
338                    let list = self.pop()?;
339                    match list {
340                        Value::List(mut items) => {
341                            items.push(value);
342                            self.stack.push(Value::List(items));
343                        }
344                        other => return Err(VmError::TypeMismatch(format!("ListAppend on non-list: {other:?}"))),
345                    }
346                }
347                Op::Jump(off) => {
348                    let new_pc = (self.frames[frame_idx].pc as i32 + off) as usize;
349                    self.frames[frame_idx].pc = new_pc;
350                }
351                Op::JumpIf(off) => {
352                    let v = self.pop()?;
353                    if v.as_bool() {
354                        let new_pc = (self.frames[frame_idx].pc as i32 + off) as usize;
355                        self.frames[frame_idx].pc = new_pc;
356                    }
357                }
358                Op::JumpIfNot(off) => {
359                    let v = self.pop()?;
360                    if !v.as_bool() {
361                        let new_pc = (self.frames[frame_idx].pc as i32 + off) as usize;
362                        self.frames[frame_idx].pc = new_pc;
363                    }
364                }
365                Op::MakeClosure { fn_id, capture_count } => {
366                    let n = capture_count as usize;
367                    let mut captures: Vec<Value> = (0..n).map(|_| Value::Unit).collect();
368                    for i in (0..n).rev() { captures[i] = self.pop()?; }
369                    self.stack.push(Value::Closure { fn_id, captures });
370                }
371                Op::CallClosure { arity, node_id_idx } => {
372                    let mut args: Vec<Value> = (0..arity).map(|_| Value::Unit).collect();
373                    for i in (0..arity as usize).rev() { args[i] = self.pop()?; }
374                    let closure = self.pop()?;
375                    let (fn_id, captures) = match closure {
376                        Value::Closure { fn_id, captures } => (fn_id, captures),
377                        other => return Err(VmError::TypeMismatch(format!("CallClosure on non-closure: {other:?}"))),
378                    };
379                    let node_id = const_str(&self.program.constants, node_id_idx);
380                    let callee_name = self.program.functions[fn_id as usize].name.clone();
381                    let mut combined = captures;
382                    combined.extend(args);
383                    self.tracer.enter_call(&node_id, &callee_name, &combined);
384                    let f = &self.program.functions[fn_id as usize];
385                    let mut locals = vec![Value::Unit; f.locals_count.max(f.arity) as usize];
386                    for (i, v) in combined.into_iter().enumerate() { locals[i] = v; }
387                    self.push_frame(Frame {
388                        fn_id, pc: 0, locals, stack_base: self.stack.len(),
389                        trace_kind: FrameKind::Call(node_id),
390                    })?;
391                }
392                Op::Call { fn_id, arity, node_id_idx } => {
393                    let mut args: Vec<Value> = (0..arity).map(|_| Value::Unit).collect();
394                    for i in (0..arity as usize).rev() { args[i] = self.pop()?; }
395                    let node_id = const_str(&self.program.constants, node_id_idx);
396                    let callee_name = self.program.functions[fn_id as usize].name.clone();
397                    self.tracer.enter_call(&node_id, &callee_name, &args);
398                    let f = &self.program.functions[fn_id as usize];
399                    let mut locals = vec![Value::Unit; f.locals_count.max(f.arity) as usize];
400                    for (i, v) in args.into_iter().enumerate() { locals[i] = v; }
401                    self.push_frame(Frame {
402                        fn_id, pc: 0, locals, stack_base: self.stack.len(),
403                        trace_kind: FrameKind::Call(node_id),
404                    })?;
405                }
406                Op::TailCall { fn_id, arity, node_id_idx } => {
407                    let mut args: Vec<Value> = (0..arity).map(|_| Value::Unit).collect();
408                    for i in (0..arity as usize).rev() { args[i] = self.pop()?; }
409                    let node_id = const_str(&self.program.constants, node_id_idx);
410                    let callee_name = self.program.functions[fn_id as usize].name.clone();
411                    // A tail call closes the current call's trace frame and
412                    // opens a new one in its place — preserves the caller's
413                    // tree depth in the trace.
414                    self.tracer.exit_call_tail();
415                    self.tracer.enter_call(&node_id, &callee_name, &args);
416                    let f = &self.program.functions[fn_id as usize];
417                    let frame = self.frames.last_mut().unwrap();
418                    frame.fn_id = fn_id;
419                    frame.pc = 0;
420                    frame.locals = vec![Value::Unit; f.locals_count.max(f.arity) as usize];
421                    for (i, v) in args.into_iter().enumerate() { frame.locals[i] = v; }
422                    frame.trace_kind = FrameKind::Call(node_id);
423                }
424                Op::EffectCall { kind_idx, op_idx, arity, node_id_idx } => {
425                    let mut args: Vec<Value> = (0..arity).map(|_| Value::Unit).collect();
426                    for i in (0..arity as usize).rev() { args[i] = self.pop()?; }
427                    let kind = match &self.program.constants[kind_idx as usize] {
428                        Const::Str(s) => s.clone(),
429                        _ => return Err(VmError::TypeMismatch("expected Str const for effect kind".into())),
430                    };
431                    let op_name = match &self.program.constants[op_idx as usize] {
432                        Const::Str(s) => s.clone(),
433                        _ => return Err(VmError::TypeMismatch("expected Str const for effect op".into())),
434                    };
435                    let node_id = const_str(&self.program.constants, node_id_idx);
436                    self.tracer.enter_effect(&node_id, &kind, &op_name, &args);
437                    let result = match self.tracer.override_effect(&node_id) {
438                        Some(v) => Ok(v),
439                        None => self.handler.dispatch(&kind, &op_name, args.clone()),
440                    };
441                    match result {
442                        Ok(v) => {
443                            self.tracer.exit_ok(&v);
444                            self.stack.push(v);
445                        }
446                        Err(e) => {
447                            self.tracer.exit_err(&e);
448                            return Err(VmError::Effect(e));
449                        }
450                    }
451                }
452                Op::Return => {
453                    let v = self.pop()?;
454                    let frame = self.frames.pop().unwrap();
455                    // Trim any extra stuff that the function pushed but didn't pop.
456                    self.stack.truncate(frame.stack_base);
457                    if matches!(frame.trace_kind, FrameKind::Call(_)) {
458                        self.tracer.exit_ok(&v);
459                    }
460                    if self.frames.is_empty() {
461                        return Ok(v);
462                    }
463                    self.stack.push(v);
464                }
465                Op::Panic(i) => {
466                    let msg = match &self.program.constants[i as usize] {
467                        Const::Str(s) => s.clone(),
468                        _ => "panic".into(),
469                    };
470                    return Err(VmError::Panic(msg));
471                }
472                // Arithmetic
473                Op::IntAdd => self.bin_int(|a, b| Value::Int(a + b))?,
474                Op::IntSub => self.bin_int(|a, b| Value::Int(a - b))?,
475                Op::IntMul => self.bin_int(|a, b| Value::Int(a * b))?,
476                Op::IntDiv => self.bin_int(|a, b| Value::Int(a / b))?,
477                Op::IntMod => self.bin_int(|a, b| Value::Int(a % b))?,
478                Op::IntNeg => {
479                    let a = self.pop()?.as_int();
480                    self.stack.push(Value::Int(-a));
481                }
482                Op::IntEq => self.bin_int(|a, b| Value::Bool(a == b))?,
483                Op::IntLt => self.bin_int(|a, b| Value::Bool(a < b))?,
484                Op::IntLe => self.bin_int(|a, b| Value::Bool(a <= b))?,
485                Op::FloatAdd => self.bin_float(|a, b| Value::Float(a + b))?,
486                Op::FloatSub => self.bin_float(|a, b| Value::Float(a - b))?,
487                Op::FloatMul => self.bin_float(|a, b| Value::Float(a * b))?,
488                Op::FloatDiv => self.bin_float(|a, b| Value::Float(a / b))?,
489                Op::FloatNeg => {
490                    let a = self.pop()?.as_float();
491                    self.stack.push(Value::Float(-a));
492                }
493                Op::FloatEq => self.bin_float(|a, b| Value::Bool(a == b))?,
494                Op::FloatLt => self.bin_float(|a, b| Value::Bool(a < b))?,
495                Op::FloatLe => self.bin_float(|a, b| Value::Bool(a <= b))?,
496                Op::NumAdd => self.bin_num(|a, b| Value::Int(a + b), |a, b| Value::Float(a + b))?,
497                Op::NumSub => self.bin_num(|a, b| Value::Int(a - b), |a, b| Value::Float(a - b))?,
498                Op::NumMul => self.bin_num(|a, b| Value::Int(a * b), |a, b| Value::Float(a * b))?,
499                Op::NumDiv => self.bin_num(|a, b| Value::Int(a / b), |a, b| Value::Float(a / b))?,
500                Op::NumMod => self.bin_int(|a, b| Value::Int(a % b))?,
501                Op::NumNeg => {
502                    let v = self.pop()?;
503                    match v {
504                        Value::Int(n) => self.stack.push(Value::Int(-n)),
505                        Value::Float(f) => self.stack.push(Value::Float(-f)),
506                        other => return Err(VmError::TypeMismatch(format!("NumNeg on {other:?}"))),
507                    }
508                }
509                Op::NumEq => self.bin_eq()?,
510                Op::NumLt => self.bin_num(|a, b| Value::Bool(a < b), |a, b| Value::Bool(a < b))?,
511                Op::NumLe => self.bin_num(|a, b| Value::Bool(a <= b), |a, b| Value::Bool(a <= b))?,
512                Op::BoolAnd => {
513                    let b = self.pop()?.as_bool();
514                    let a = self.pop()?.as_bool();
515                    self.stack.push(Value::Bool(a && b));
516                }
517                Op::BoolOr => {
518                    let b = self.pop()?.as_bool();
519                    let a = self.pop()?.as_bool();
520                    self.stack.push(Value::Bool(a || b));
521                }
522                Op::BoolNot => {
523                    let a = self.pop()?.as_bool();
524                    self.stack.push(Value::Bool(!a));
525                }
526                Op::StrConcat => {
527                    let b = self.pop()?;
528                    let a = self.pop()?;
529                    let s = format!("{}{}", a.as_str(), b.as_str());
530                    self.stack.push(Value::Str(s));
531                }
532                Op::StrLen => {
533                    let v = self.pop()?;
534                    self.stack.push(Value::Int(v.as_str().len() as i64));
535                }
536                Op::StrEq => {
537                    let b = self.pop()?;
538                    let a = self.pop()?;
539                    self.stack.push(Value::Bool(a.as_str() == b.as_str()));
540                }
541                Op::BytesLen => {
542                    let v = self.pop()?;
543                    match v {
544                        Value::Bytes(b) => self.stack.push(Value::Int(b.len() as i64)),
545                        other => return Err(VmError::TypeMismatch(format!("BytesLen on {other:?}"))),
546                    }
547                }
548                Op::BytesEq => {
549                    let b = self.pop()?;
550                    let a = self.pop()?;
551                    let eq = match (a, b) {
552                        (Value::Bytes(x), Value::Bytes(y)) => x == y,
553                        _ => return Err(VmError::TypeMismatch("BytesEq operands".into())),
554                    };
555                    self.stack.push(Value::Bool(eq));
556                }
557            }
558        }
559    }
560
561    fn pop(&mut self) -> Result<Value, VmError> {
562        self.stack.pop().ok_or(VmError::StackUnderflow)
563    }
564    fn peek(&self) -> Result<&Value, VmError> {
565        self.stack.last().ok_or(VmError::StackUnderflow)
566    }
567
568    fn bin_int(&mut self, f: impl Fn(i64, i64) -> Value) -> Result<(), VmError> {
569        let b = self.pop()?.as_int();
570        let a = self.pop()?.as_int();
571        self.stack.push(f(a, b));
572        Ok(())
573    }
574    fn bin_float(&mut self, f: impl Fn(f64, f64) -> Value) -> Result<(), VmError> {
575        let b = self.pop()?.as_float();
576        let a = self.pop()?.as_float();
577        self.stack.push(f(a, b));
578        Ok(())
579    }
580    fn bin_num(
581        &mut self,
582        i: impl Fn(i64, i64) -> Value,
583        f: impl Fn(f64, f64) -> Value,
584    ) -> Result<(), VmError> {
585        let b = self.pop()?;
586        let a = self.pop()?;
587        match (a, b) {
588            (Value::Int(x), Value::Int(y)) => { self.stack.push(i(x, y)); Ok(()) }
589            (Value::Float(x), Value::Float(y)) => { self.stack.push(f(x, y)); Ok(()) }
590            (a, b) => Err(VmError::TypeMismatch(format!("Num op: {a:?} {b:?}"))),
591        }
592    }
593    fn bin_eq(&mut self) -> Result<(), VmError> {
594        let b = self.pop()?;
595        let a = self.pop()?;
596        self.stack.push(Value::Bool(a == b));
597        Ok(())
598    }
599}
600
601fn const_to_value(c: &Const) -> Value {
602    match c {
603        Const::Int(n) => Value::Int(*n),
604        Const::Float(f) => Value::Float(*f),
605        Const::Bool(b) => Value::Bool(*b),
606        Const::Str(s) => Value::Str(s.clone()),
607        Const::Bytes(b) => Value::Bytes(b.clone()),
608        Const::Unit => Value::Unit,
609        Const::FieldName(s) | Const::VariantName(s) | Const::NodeId(s) => Value::Str(s.clone()),
610    }
611}