Skip to main content

zapcode_core/vm/
mod.rs

1use std::collections::{HashMap, HashSet};
2use std::sync::Arc;
3
4use indexmap::IndexMap;
5
6use crate::compiler::instruction::{Constant, Instruction};
7use crate::compiler::CompiledProgram;
8use crate::error::{Result, ZapcodeError};
9use crate::sandbox::{ResourceLimits, ResourceTracker};
10use crate::snapshot::ZapcodeSnapshot;
11use crate::value::{Closure, FunctionId, GeneratorObject, SuspendedFrame, Value};
12
13mod builtins;
14
15/// The result of VM execution.
16#[derive(Debug)]
17pub enum VmState {
18    Complete(Value),
19    Suspended {
20        function_name: String,
21        args: Vec<Value>,
22        snapshot: ZapcodeSnapshot,
23    },
24}
25
26/// Tracks where a method receiver originated so that mutations to `this`
27/// inside the method can be written back to the source variable.
28#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
29pub(crate) enum ReceiverSource {
30    /// The receiver was loaded from a global variable with the given name.
31    Global(String),
32    /// The receiver was loaded from a local variable at the given slot index
33    /// in the frame at the given depth (index into `self.frames`).
34    Local { frame_index: usize, slot: usize },
35}
36
37/// A call frame in the VM stack.
38#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
39pub(crate) struct CallFrame {
40    pub(crate) func_index: Option<usize>,
41    pub(crate) ip: usize,
42    pub(crate) locals: Vec<Value>,
43    pub(crate) stack_base: usize,
44    /// The `this` value for method/constructor calls.
45    pub(crate) this_value: Option<Value>,
46    /// Where the method receiver came from, so we can write back mutations.
47    pub(crate) receiver_source: Option<ReceiverSource>,
48}
49
50/// The Zapcode VM.
51pub struct Vm {
52    pub(crate) program: CompiledProgram,
53    pub(crate) stack: Vec<Value>,
54    pub(crate) frames: Vec<CallFrame>,
55    pub(crate) globals: HashMap<String, Value>,
56    pub(crate) stdout: String,
57    pub(crate) limits: ResourceLimits,
58    pub(crate) tracker: ResourceTracker,
59    pub(crate) external_functions: HashSet<String>,
60    pub(crate) try_stack: Vec<TryInfo>,
61    /// The last object a property was accessed on — used for method dispatch.
62    last_receiver: Option<Value>,
63    /// Where the last receiver came from — used to write back `this` mutations.
64    last_receiver_source: Option<ReceiverSource>,
65    /// The name of the last global loaded — used to identify known globals.
66    last_global_name: Option<String>,
67    /// Tracks the source of the most recent Load instruction for receiver tracking.
68    last_load_source: Option<ReceiverSource>,
69    /// Counter for assigning unique generator IDs.
70    next_generator_id: u64,
71}
72
73#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
74pub(crate) struct TryInfo {
75    pub(crate) catch_ip: usize,
76    pub(crate) frame_depth: usize,
77    pub(crate) stack_depth: usize,
78}
79
80impl Vm {
81    fn new(
82        program: CompiledProgram,
83        limits: ResourceLimits,
84        external_functions: HashSet<String>,
85    ) -> Self {
86        let mut globals = HashMap::new();
87
88        // Register built-in globals
89        builtins::register_globals(&mut globals);
90
91        Self {
92            program,
93            stack: Vec::new(),
94            frames: Vec::new(),
95            globals,
96            stdout: String::new(),
97            limits,
98            tracker: ResourceTracker::default(),
99            external_functions,
100            try_stack: Vec::new(),
101            last_receiver: None,
102            last_receiver_source: None,
103            last_global_name: None,
104            last_load_source: None,
105            next_generator_id: 0,
106        }
107    }
108
109    /// Names of all builtin globals registered by `register_globals`.
110    pub(crate) const BUILTIN_GLOBAL_NAMES: &'static [&'static str] =
111        &["console", "JSON", "Object", "Array", "Math", "Promise"];
112
113    /// Restore a VM from snapshot state and continue execution.
114    /// Builtins are re-registered after restoring user globals.
115    /// The return_value is pushed onto the stack (result of the external call).
116    #[allow(clippy::too_many_arguments)]
117    pub(crate) fn from_snapshot(
118        program: CompiledProgram,
119        stack: Vec<Value>,
120        frames: Vec<CallFrame>,
121        user_globals: HashMap<String, Value>,
122        try_stack: Vec<TryInfo>,
123        stdout: String,
124        limits: ResourceLimits,
125        external_functions: HashSet<String>,
126    ) -> Self {
127        let mut globals = HashMap::new();
128        // Re-register builtins first
129        builtins::register_globals(&mut globals);
130        // Then overlay user globals (user globals take precedence if names collide)
131        for (k, v) in user_globals {
132            globals.insert(k, v);
133        }
134
135        Self {
136            program,
137            stack,
138            frames,
139            globals,
140            stdout,
141            limits,
142            tracker: ResourceTracker::default(),
143            external_functions,
144            try_stack,
145            last_receiver: None,
146            last_receiver_source: None,
147            last_global_name: None,
148            last_load_source: None,
149            next_generator_id: 0,
150        }
151    }
152
153    /// Resume execution after a snapshot restore. The return value from
154    /// the external function should already be pushed onto the stack.
155    pub(crate) fn resume_execution(&mut self) -> Result<VmState> {
156        self.tracker.start();
157        self.execute()
158    }
159
160    fn push(&mut self, value: Value) -> Result<()> {
161        self.tracker.track_allocation(&self.limits)?;
162        self.stack.push(value);
163        Ok(())
164    }
165
166    fn pop(&mut self) -> Result<Value> {
167        self.stack
168            .pop()
169            .ok_or_else(|| ZapcodeError::RuntimeError("stack underflow".to_string()))
170    }
171
172    fn peek(&self) -> Result<&Value> {
173        self.stack
174            .last()
175            .ok_or_else(|| ZapcodeError::RuntimeError("stack underflow".to_string()))
176    }
177
178    fn current_frame(&self) -> &CallFrame {
179        // Frames are always non-empty during execution (run() pushes the initial frame).
180        // This is an internal invariant, not reachable by guest code.
181        self.frames.last().expect("internal error: no active frame")
182    }
183
184    fn current_frame_mut(&mut self) -> &mut CallFrame {
185        self.frames
186            .last_mut()
187            .expect("internal error: no active frame")
188    }
189
190    #[allow(dead_code)]
191    fn instructions(&self) -> &[Instruction] {
192        match self.current_frame().func_index {
193            Some(idx) => &self.program.functions[idx].instructions,
194            None => &self.program.instructions,
195        }
196    }
197
198    /// Build the locals vec by binding `args` to the function's declared `params`.
199    /// Handles positional, rest, and default-value patterns.
200    fn bind_params(params: &[ParamPattern], args: &[Value], local_count: usize) -> Vec<Value> {
201        let mut locals = Vec::with_capacity(local_count);
202        for (i, param) in params.iter().enumerate() {
203            match param {
204                ParamPattern::Ident(_) => {
205                    locals.push(args.get(i).cloned().unwrap_or(Value::Undefined));
206                }
207                ParamPattern::Rest(_) => {
208                    let rest: Vec<Value> = args[i..].to_vec();
209                    locals.push(Value::Array(rest));
210                }
211                ParamPattern::DefaultValue { .. } => {
212                    let val = args.get(i).cloned().unwrap_or(Value::Undefined);
213                    // Keep Undefined so the compiler-emitted default init can fire
214                    locals.push(val);
215                }
216                _ => {
217                    locals.push(args.get(i).cloned().unwrap_or(Value::Undefined));
218                }
219            }
220        }
221        locals
222    }
223
224    /// Common setup for calling a closure: inject captures, bind params, push frame.
225    fn push_call_frame(
226        &mut self,
227        closure: &Closure,
228        args: &[Value],
229        this_value: Option<Value>,
230    ) -> Result<()> {
231        self.tracker.push_frame();
232        self.tracker.check_stack(&self.limits)?;
233
234        // Inject captured variables as globals
235        for (name, val) in &closure.captured {
236            if !self.globals.contains_key(name) {
237                self.globals.insert(name.clone(), val.clone());
238            }
239        }
240
241        let func = &self.program.functions[closure.func_id.0];
242        let locals = Self::bind_params(&func.params, args, func.local_count);
243
244        // If this is a method call (has this_value from a receiver), transfer
245        // the receiver source so we can write back mutations on return.
246        let receiver_source = if this_value.is_some() {
247            self.last_receiver_source.take()
248        } else {
249            self.last_receiver_source = None;
250            None
251        };
252
253        self.frames.push(CallFrame {
254            func_index: Some(closure.func_id.0),
255            ip: 0,
256            locals,
257            stack_base: self.stack.len(),
258            this_value,
259            receiver_source,
260        });
261        Ok(())
262    }
263
264    fn run(&mut self) -> Result<VmState> {
265        self.tracker.start();
266
267        // Set up top-level frame
268        self.frames.push(CallFrame {
269            func_index: None,
270            ip: 0,
271            locals: Vec::new(),
272            stack_base: 0,
273            this_value: None,
274            receiver_source: None,
275        });
276
277        self.execute()
278    }
279
280    fn execute(&mut self) -> Result<VmState> {
281        loop {
282            // Resource checks
283            self.tracker.check_time(&self.limits)?;
284
285            let frame = self.frames.last().unwrap();
286            let instructions = match frame.func_index {
287                Some(idx) => &self.program.functions[idx].instructions,
288                None => &self.program.instructions,
289            };
290
291            if frame.ip >= instructions.len() {
292                // End of function/program
293                if self.frames.len() <= 1 {
294                    // Top-level: return last value on stack or undefined
295                    let result = if self.stack.is_empty() {
296                        Value::Undefined
297                    } else {
298                        self.stack.pop().unwrap_or(Value::Undefined)
299                    };
300                    return Ok(VmState::Complete(result));
301                } else {
302                    // Return from function
303                    let frame = self.frames.pop().unwrap();
304                    self.tracker.pop_frame();
305                    // If this was a constructor, return `this`
306                    if let Some(this_val) = frame.this_value {
307                        self.stack.truncate(frame.stack_base);
308                        self.push(this_val)?;
309                    } else {
310                        self.push(Value::Undefined)?;
311                    }
312                    continue;
313                }
314            }
315
316            let instr = instructions[frame.ip].clone();
317            let result = self.dispatch(instr);
318
319            match result {
320                Ok(Some(state)) => return Ok(state),
321                Ok(None) => {}
322                Err(err) => {
323                    // Try to catch the error
324                    if let Some(try_info) = self.try_stack.pop() {
325                        // Unwind to catch block
326                        while self.frames.len() > try_info.frame_depth {
327                            self.frames.pop();
328                            self.tracker.pop_frame();
329                        }
330                        self.stack.truncate(try_info.stack_depth);
331
332                        // Push error value
333                        let error_val = Value::String(Arc::from(err.to_string()));
334                        self.push(error_val)?;
335
336                        // Jump to catch
337                        self.current_frame_mut().ip = try_info.catch_ip;
338                    } else {
339                        return Err(err);
340                    }
341                }
342            }
343        }
344    }
345
346    /// Call a function value with the given arguments and run it to completion.
347    /// Returns the function's return value.
348    fn call_function_internal(&mut self, callee: &Value, args: Vec<Value>) -> Result<Value> {
349        let closure = match callee {
350            Value::Function(c) => c.clone(),
351            other => {
352                return Err(ZapcodeError::TypeError(format!(
353                    "{} is not a function",
354                    other.to_js_string()
355                )));
356            }
357        };
358
359        let target_frame_depth = self.frames.len();
360        self.push_call_frame(&closure, &args, None)?;
361
362        // Run until the new frame returns
363        loop {
364            self.tracker.check_time(&self.limits)?;
365
366            let frame = self.frames.last().unwrap();
367            let instructions = match frame.func_index {
368                Some(idx) => &self.program.functions[idx].instructions,
369                None => &self.program.instructions,
370            };
371
372            if frame.ip >= instructions.len() {
373                // End of function without explicit return
374                if self.frames.len() > target_frame_depth + 1 {
375                    // Inner function ended, pop and continue
376                    self.frames.pop();
377                    self.tracker.pop_frame();
378                    self.push(Value::Undefined)?;
379                    continue;
380                } else {
381                    // Our target function ended
382                    self.frames.pop();
383                    self.tracker.pop_frame();
384                    return Ok(Value::Undefined);
385                }
386            }
387
388            let instr = instructions[frame.ip].clone();
389            let result = self.dispatch(instr);
390
391            match result {
392                Ok(Some(VmState::Complete(val))) => {
393                    // A return happened that completed the top-level program.
394                    // This shouldn't happen inside a callback but handle gracefully.
395                    return Ok(val);
396                }
397                Ok(Some(VmState::Suspended { .. })) => {
398                    return Err(ZapcodeError::RuntimeError(
399                        "cannot suspend inside array callback".to_string(),
400                    ));
401                }
402                Ok(None) => {
403                    // Check if the frame was popped by a Return instruction
404                    if self.frames.len() == target_frame_depth {
405                        // The function returned; return value is on the stack
406                        return Ok(self.pop().unwrap_or(Value::Undefined));
407                    }
408                }
409                Err(err) => {
410                    // Try to catch the error within the callback
411                    if let Some(try_info) = self.try_stack.pop() {
412                        while self.frames.len() > try_info.frame_depth {
413                            self.frames.pop();
414                            self.tracker.pop_frame();
415                        }
416                        self.stack.truncate(try_info.stack_depth);
417                        let error_val = Value::String(Arc::from(err.to_string()));
418                        self.push(error_val)?;
419                        self.current_frame_mut().ip = try_info.catch_ip;
420                    } else {
421                        return Err(err);
422                    }
423                }
424            }
425        }
426    }
427
428    /// Call a callback for each array element. Passes (item, index) — the full
429    /// array reference (3rd JS argument) is only built lazily if the callback
430    /// actually uses 3+ params, avoiding O(n²) cloning.
431    fn call_element_callback(
432        &mut self,
433        callback: &Value,
434        item: &Value,
435        index: usize,
436    ) -> Result<Value> {
437        self.call_function_internal(callback, vec![item.clone(), Value::Int(index as i64)])
438    }
439
440    /// Execute an array callback method (map, filter, reduce, forEach, etc.)
441    fn execute_array_callback_method(
442        &mut self,
443        arr: Vec<Value>,
444        method: &str,
445        all_args: Vec<Value>,
446    ) -> Result<Value> {
447        let callback = all_args.first().cloned().unwrap_or(Value::Undefined);
448
449        match method {
450            "map" => {
451                let mut result = Vec::with_capacity(arr.len());
452                for (i, item) in arr.iter().enumerate() {
453                    result.push(self.call_element_callback(&callback, item, i)?);
454                }
455                Ok(Value::Array(result))
456            }
457            "filter" => {
458                let mut result = Vec::new();
459                for (i, item) in arr.iter().enumerate() {
460                    if self.call_element_callback(&callback, item, i)?.is_truthy() {
461                        result.push(item.clone());
462                    }
463                }
464                Ok(Value::Array(result))
465            }
466            "forEach" => {
467                for (i, item) in arr.iter().enumerate() {
468                    self.call_element_callback(&callback, item, i)?;
469                }
470                Ok(Value::Undefined)
471            }
472            "find" => {
473                for (i, item) in arr.iter().enumerate() {
474                    if self.call_element_callback(&callback, item, i)?.is_truthy() {
475                        return Ok(item.clone());
476                    }
477                }
478                Ok(Value::Undefined)
479            }
480            "findIndex" => {
481                for (i, item) in arr.iter().enumerate() {
482                    if self.call_element_callback(&callback, item, i)?.is_truthy() {
483                        return Ok(Value::Int(i as i64));
484                    }
485                }
486                Ok(Value::Int(-1))
487            }
488            "every" => {
489                for (i, item) in arr.iter().enumerate() {
490                    if !self.call_element_callback(&callback, item, i)?.is_truthy() {
491                        return Ok(Value::Bool(false));
492                    }
493                }
494                Ok(Value::Bool(true))
495            }
496            "some" => {
497                for (i, item) in arr.iter().enumerate() {
498                    if self.call_element_callback(&callback, item, i)?.is_truthy() {
499                        return Ok(Value::Bool(true));
500                    }
501                }
502                Ok(Value::Bool(false))
503            }
504            "reduce" => {
505                let mut acc = match all_args.get(1).cloned() {
506                    Some(init) => Some(init),
507                    None if !arr.is_empty() => Some(arr[0].clone()),
508                    None => {
509                        return Err(ZapcodeError::TypeError(
510                            "Reduce of empty array with no initial value".to_string(),
511                        ));
512                    }
513                };
514                let start = if all_args.get(1).is_some() { 0 } else { 1 };
515                for (i, item) in arr.iter().enumerate().skip(start) {
516                    acc = Some(self.call_function_internal(
517                        &callback,
518                        vec![acc.unwrap(), item.clone(), Value::Int(i as i64)],
519                    )?);
520                }
521                Ok(acc.unwrap_or(Value::Undefined))
522            }
523            "sort" => {
524                let mut result = arr;
525                if matches!(callback, Value::Function(_)) {
526                    // Insertion sort — O(n²) worst case but stable, and sort
527                    // with a VM callback can't use Rust's built-in sort
528                    let len = result.len();
529                    for i in 1..len {
530                        let mut j = i;
531                        while j > 0 {
532                            let cmp = self
533                                .call_function_internal(
534                                    &callback,
535                                    vec![result[j - 1].clone(), result[j].clone()],
536                                )?
537                                .to_number();
538                            if cmp > 0.0 {
539                                result.swap(j - 1, j);
540                                j -= 1;
541                            } else {
542                                break;
543                            }
544                        }
545                    }
546                } else {
547                    result.sort_by_key(|a| a.to_js_string());
548                }
549                Ok(Value::Array(result))
550            }
551            "flatMap" => {
552                let mut result = Vec::new();
553                for (i, item) in arr.iter().enumerate() {
554                    match self.call_element_callback(&callback, item, i)? {
555                        Value::Array(inner) => result.extend(inner),
556                        other => result.push(other),
557                    }
558                }
559                Ok(Value::Array(result))
560            }
561            _ => Err(ZapcodeError::TypeError(format!(
562                "Unknown array callback method: {}",
563                method
564            ))),
565        }
566    }
567
568    fn alloc_generator_id(&mut self) -> u64 {
569        let id = self.next_generator_id;
570        self.next_generator_id += 1;
571        id
572    }
573
574    fn generator_next(&mut self, mut gen_obj: GeneratorObject, arg: Value) -> Result<Value> {
575        if gen_obj.done {
576            return Ok(self.make_iterator_result(Value::Undefined, true));
577        }
578        for (name, val) in &gen_obj.captured {
579            if !self.globals.contains_key(name) {
580                self.globals.insert(name.clone(), val.clone());
581            }
582        }
583        let func_idx = gen_obj.func_id.0;
584        match gen_obj.suspended.take() {
585            None => {
586                let func = &self.program.functions[func_idx];
587                self.tracker.push_frame();
588                let mut locals = Vec::with_capacity(func.local_count);
589                for param in func.params.iter() {
590                    match param {
591                        ParamPattern::Ident(name) => {
592                            let val = gen_obj
593                                .captured
594                                .iter()
595                                .find(|(n, _)| n == name)
596                                .map(|(_, v)| v.clone())
597                                .unwrap_or(Value::Undefined);
598                            locals.push(val);
599                        }
600                        ParamPattern::Rest(name) => {
601                            let val = gen_obj
602                                .captured
603                                .iter()
604                                .find(|(n, _)| n == name)
605                                .map(|(_, v)| v.clone())
606                                .unwrap_or(Value::Array(Vec::new()));
607                            locals.push(val);
608                        }
609                        _ => {
610                            locals.push(Value::Undefined);
611                        }
612                    }
613                }
614                let stack_base = self.stack.len();
615                self.frames.push(CallFrame {
616                    func_index: Some(func_idx),
617                    ip: 0,
618                    locals,
619                    stack_base,
620                    this_value: None,
621                    receiver_source: None,
622                });
623                self.run_generator_until_yield_or_return(gen_obj)
624            }
625            Some(suspended) => {
626                self.tracker.push_frame();
627                let stack_base = self.stack.len();
628                for val in &suspended.stack {
629                    self.push(val.clone())?;
630                }
631                self.push(arg)?;
632                self.frames.push(CallFrame {
633                    func_index: Some(func_idx),
634                    ip: suspended.ip,
635                    locals: suspended.locals,
636                    stack_base,
637                    this_value: None,
638                    receiver_source: None,
639                });
640                self.run_generator_until_yield_or_return(gen_obj)
641            }
642        }
643    }
644
645    /// Store generator state back into the globals registry.
646    /// For done generators, the key is removed to prevent unbounded growth.
647    fn store_generator(&mut self, gen_obj: GeneratorObject) {
648        let gen_key = format!("__gen_{}", gen_obj.id);
649        if gen_obj.done {
650            self.globals.remove(&gen_key);
651        } else {
652            self.globals.insert(gen_key, Value::Generator(gen_obj));
653        }
654    }
655
656    /// Mark a generator as done, store it, and return the final iterator result.
657    fn finish_generator(&mut self, mut gen_obj: GeneratorObject, value: Value) -> Value {
658        gen_obj.done = true;
659        gen_obj.suspended = None;
660        self.store_generator(gen_obj);
661        self.make_iterator_result(value, true)
662    }
663
664    fn run_generator_until_yield_or_return(
665        &mut self,
666        mut gen_obj: GeneratorObject,
667    ) -> Result<Value> {
668        let target_frame_depth = self.frames.len() - 1;
669        loop {
670            self.tracker.check_time(&self.limits)?;
671            let frame = self.frames.last().unwrap();
672            let instructions = match frame.func_index {
673                Some(idx) => &self.program.functions[idx].instructions,
674                None => &self.program.instructions,
675            };
676            if frame.ip >= instructions.len() {
677                if self.frames.len() > target_frame_depth + 1 {
678                    let frame = self.frames.pop().unwrap();
679                    self.tracker.pop_frame();
680                    if let Some(this_val) = frame.this_value {
681                        self.stack.truncate(frame.stack_base);
682                        self.push(this_val)?;
683                    } else {
684                        self.push(Value::Undefined)?;
685                    }
686                    continue;
687                }
688                let frame = self.frames.pop().unwrap();
689                self.tracker.pop_frame();
690                self.stack.truncate(frame.stack_base);
691                let result = self.finish_generator(gen_obj, Value::Undefined);
692                return Ok(result);
693            }
694            let instr = instructions[frame.ip].clone();
695            if matches!(instr, Instruction::Yield) {
696                self.current_frame_mut().ip += 1;
697                let yielded_value = self.pop()?;
698                let frame = self.frames.pop().unwrap();
699                self.tracker.pop_frame();
700                let frame_stack: Vec<Value> = self.stack.drain(frame.stack_base..).collect();
701                gen_obj.suspended = Some(SuspendedFrame {
702                    ip: frame.ip,
703                    locals: frame.locals,
704                    stack: frame_stack,
705                });
706                gen_obj.done = false;
707                self.store_generator(gen_obj);
708                return Ok(self.make_iterator_result(yielded_value, false));
709            }
710            if matches!(instr, Instruction::Return) {
711                self.current_frame_mut().ip += 1;
712                let return_val = self.pop().unwrap_or(Value::Undefined);
713                if self.frames.len() > target_frame_depth + 1 {
714                    let frame = self.frames.pop().unwrap();
715                    self.tracker.pop_frame();
716                    self.stack.truncate(frame.stack_base);
717                    self.push(return_val)?;
718                    continue;
719                }
720                let frame = self.frames.pop().unwrap();
721                self.tracker.pop_frame();
722                self.stack.truncate(frame.stack_base);
723                let result = self.finish_generator(gen_obj, return_val);
724                return Ok(result);
725            }
726            let result = self.dispatch(instr);
727            match result {
728                Ok(Some(VmState::Complete(val))) => return Ok(val),
729                Ok(Some(VmState::Suspended { .. })) => {
730                    return Err(ZapcodeError::RuntimeError(
731                        "cannot suspend inside a generator".to_string(),
732                    ));
733                }
734                Ok(None) => {
735                    if self.frames.len() == target_frame_depth {
736                        let return_val = self.pop().unwrap_or(Value::Undefined);
737                        let result = self.finish_generator(gen_obj, return_val);
738                        return Ok(result);
739                    }
740                }
741                Err(err) => {
742                    if let Some(try_info) = self.try_stack.pop() {
743                        while self.frames.len() > try_info.frame_depth {
744                            self.frames.pop();
745                            self.tracker.pop_frame();
746                        }
747                        self.stack.truncate(try_info.stack_depth);
748                        let error_val = Value::String(Arc::from(err.to_string()));
749                        self.push(error_val)?;
750                        self.current_frame_mut().ip = try_info.catch_ip;
751                    } else {
752                        return Err(err);
753                    }
754                }
755            }
756        }
757    }
758
759    fn make_iterator_result(&self, value: Value, done: bool) -> Value {
760        let mut obj = IndexMap::new();
761        obj.insert(Arc::from("value"), value);
762        obj.insert(Arc::from("done"), Value::Bool(done));
763        Value::Object(obj)
764    }
765
766    fn dispatch(&mut self, instr: Instruction) -> Result<Option<VmState>> {
767        self.current_frame_mut().ip += 1;
768
769        match instr {
770            Instruction::Push(constant) => {
771                let value = match constant {
772                    Constant::Undefined => Value::Undefined,
773                    Constant::Null => Value::Null,
774                    Constant::Bool(b) => Value::Bool(b),
775                    Constant::Int(n) => Value::Int(n),
776                    Constant::Float(n) => Value::Float(n),
777                    Constant::String(s) => Value::String(Arc::from(s.as_str())),
778                };
779                self.push(value)?;
780            }
781            Instruction::Pop => {
782                self.pop()?;
783            }
784            Instruction::Dup => {
785                let val = self.peek()?.clone();
786                self.push(val)?;
787            }
788            Instruction::LoadLocal(idx) => {
789                let frame_index = self.frames.len() - 1;
790                let frame = self.current_frame();
791                let val = frame.locals.get(idx).cloned().unwrap_or(Value::Undefined);
792                self.last_load_source = Some(ReceiverSource::Local {
793                    frame_index,
794                    slot: idx,
795                });
796                self.push(val)?;
797            }
798            Instruction::StoreLocal(idx) => {
799                let val = self.pop()?;
800                let frame = self.current_frame_mut();
801                while frame.locals.len() <= idx {
802                    frame.locals.push(Value::Undefined);
803                }
804                frame.locals[idx] = val;
805            }
806            Instruction::LoadGlobal(name) => {
807                let val = self.globals.get(&name).cloned().unwrap_or(Value::Undefined);
808                self.last_global_name = Some(name.clone());
809                // Only track receiver source for user-defined globals — builtins
810                // (console, Math, JSON, etc.) contain non-serializable BuiltinMethod
811                // values that would break snapshot serialization if written back.
812                if Self::BUILTIN_GLOBAL_NAMES.contains(&name.as_str()) {
813                    self.last_load_source = None;
814                } else {
815                    self.last_load_source = Some(ReceiverSource::Global(name));
816                }
817                self.push(val)?;
818            }
819            Instruction::StoreGlobal(name) => {
820                let val = self.pop()?;
821                self.globals.insert(name, val);
822            }
823            Instruction::DeclareLocal(_) => {
824                let frame = self.current_frame_mut();
825                frame.locals.push(Value::Undefined);
826            }
827
828            // Arithmetic
829            Instruction::Add => {
830                let right = self.pop()?;
831                let left = self.pop()?;
832                let result = match (&left, &right) {
833                    (Value::Int(a), Value::Int(b)) => match a.checked_add(*b) {
834                        Some(r) => Value::Int(r),
835                        None => Value::Float(*a as f64 + *b as f64),
836                    },
837                    (Value::Float(a), Value::Float(b)) => Value::Float(a + b),
838                    (Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 + b),
839                    (Value::Float(a), Value::Int(b)) => Value::Float(a + *b as f64),
840                    (Value::String(a), _) => {
841                        let rhs = right.to_js_string();
842                        let new_len = a.len().saturating_add(rhs.len());
843                        if new_len > 10_000_000 {
844                            return Err(ZapcodeError::AllocationLimitExceeded);
845                        }
846                        let mut s = a.to_string();
847                        s.push_str(&rhs);
848                        Value::String(Arc::from(s.as_str()))
849                    }
850                    (_, Value::String(b)) => {
851                        let lhs = left.to_js_string();
852                        let new_len = lhs.len().saturating_add(b.len());
853                        if new_len > 10_000_000 {
854                            return Err(ZapcodeError::AllocationLimitExceeded);
855                        }
856                        let mut s = lhs;
857                        s.push_str(b);
858                        Value::String(Arc::from(s.as_str()))
859                    }
860                    _ => Value::Float(left.to_number() + right.to_number()),
861                };
862                self.push(result)?;
863            }
864            Instruction::Sub => {
865                let right = self.pop()?;
866                let left = self.pop()?;
867                let result = match (&left, &right) {
868                    (Value::Int(a), Value::Int(b)) => match a.checked_sub(*b) {
869                        Some(r) => Value::Int(r),
870                        None => Value::Float(*a as f64 - *b as f64),
871                    },
872                    _ => Value::Float(left.to_number() - right.to_number()),
873                };
874                self.push(result)?;
875            }
876            Instruction::Mul => {
877                let right = self.pop()?;
878                let left = self.pop()?;
879                let result = match (&left, &right) {
880                    (Value::Int(a), Value::Int(b)) => match a.checked_mul(*b) {
881                        Some(r) => Value::Int(r),
882                        None => Value::Float(*a as f64 * *b as f64),
883                    },
884                    _ => Value::Float(left.to_number() * right.to_number()),
885                };
886                self.push(result)?;
887            }
888            Instruction::Div => {
889                let right = self.pop()?;
890                let left = self.pop()?;
891                let result = Value::Float(left.to_number() / right.to_number());
892                self.push(result)?;
893            }
894            Instruction::Rem => {
895                let right = self.pop()?;
896                let left = self.pop()?;
897                let result = match (&left, &right) {
898                    (Value::Int(a), Value::Int(b)) if *b != 0 => Value::Int(a % b),
899                    _ => Value::Float(left.to_number() % right.to_number()),
900                };
901                self.push(result)?;
902            }
903            Instruction::Pow => {
904                let right = self.pop()?;
905                let left = self.pop()?;
906                let result = Value::Float(left.to_number().powf(right.to_number()));
907                self.push(result)?;
908            }
909            Instruction::Neg => {
910                let val = self.pop()?;
911                let result = match val {
912                    Value::Int(n) => Value::Int(-n),
913                    _ => Value::Float(-val.to_number()),
914                };
915                self.push(result)?;
916            }
917            Instruction::BitNot => {
918                let val = self.pop()?;
919                let n = val.to_number() as i32;
920                self.push(Value::Int(!n as i64))?;
921            }
922            Instruction::BitAnd => {
923                let right = self.pop()?;
924                let left = self.pop()?;
925                let result = (left.to_number() as i32) & (right.to_number() as i32);
926                self.push(Value::Int(result as i64))?;
927            }
928            Instruction::BitOr => {
929                let right = self.pop()?;
930                let left = self.pop()?;
931                let result = (left.to_number() as i32) | (right.to_number() as i32);
932                self.push(Value::Int(result as i64))?;
933            }
934            Instruction::BitXor => {
935                let right = self.pop()?;
936                let left = self.pop()?;
937                let result = (left.to_number() as i32) ^ (right.to_number() as i32);
938                self.push(Value::Int(result as i64))?;
939            }
940            Instruction::Shl => {
941                let right = self.pop()?;
942                let left = self.pop()?;
943                let shift = (right.to_number() as u32) & 0x1f;
944                let result = (left.to_number() as i32) << shift;
945                self.push(Value::Int(result as i64))?;
946            }
947            Instruction::Shr => {
948                let right = self.pop()?;
949                let left = self.pop()?;
950                let shift = (right.to_number() as u32) & 0x1f;
951                let result = (left.to_number() as i32) >> shift;
952                self.push(Value::Int(result as i64))?;
953            }
954            Instruction::Ushr => {
955                let right = self.pop()?;
956                let left = self.pop()?;
957                let shift = (right.to_number() as u32) & 0x1f;
958                let result = (left.to_number() as u32) >> shift;
959                self.push(Value::Int(result as i64))?;
960            }
961
962            // Comparison
963            Instruction::Eq | Instruction::StrictEq => {
964                let right = self.pop()?;
965                let left = self.pop()?;
966                self.push(Value::Bool(left.strict_eq(&right)))?;
967            }
968            Instruction::Neq | Instruction::StrictNeq => {
969                let right = self.pop()?;
970                let left = self.pop()?;
971                self.push(Value::Bool(!left.strict_eq(&right)))?;
972            }
973            Instruction::Lt => {
974                let right = self.pop()?;
975                let left = self.pop()?;
976                self.push(Value::Bool(left.to_number() < right.to_number()))?;
977            }
978            Instruction::Lte => {
979                let right = self.pop()?;
980                let left = self.pop()?;
981                self.push(Value::Bool(left.to_number() <= right.to_number()))?;
982            }
983            Instruction::Gt => {
984                let right = self.pop()?;
985                let left = self.pop()?;
986                self.push(Value::Bool(left.to_number() > right.to_number()))?;
987            }
988            Instruction::Gte => {
989                let right = self.pop()?;
990                let left = self.pop()?;
991                self.push(Value::Bool(left.to_number() >= right.to_number()))?;
992            }
993
994            // Logical
995            Instruction::Not => {
996                let val = self.pop()?;
997                self.push(Value::Bool(!val.is_truthy()))?;
998            }
999
1000            // Objects & Arrays
1001            Instruction::CreateArray(count) => {
1002                self.tracker.track_allocation(&self.limits)?;
1003                let mut arr = Vec::with_capacity(count);
1004                for _ in 0..count {
1005                    arr.push(self.pop()?);
1006                }
1007                arr.reverse();
1008                self.push(Value::Array(arr))?;
1009            }
1010            Instruction::CreateObject(count) => {
1011                self.tracker.track_allocation(&self.limits)?;
1012                let mut obj = IndexMap::new();
1013                // Pop key-value pairs (or spread values)
1014                let mut entries = Vec::new();
1015                for _ in 0..count {
1016                    let val = self.pop()?;
1017                    let key = self.pop()?;
1018                    entries.push((key, val));
1019                }
1020                entries.reverse();
1021                for (key, val) in entries {
1022                    match key {
1023                        Value::String(k) => {
1024                            obj.insert(k, val);
1025                        }
1026                        _ => {
1027                            let k: Arc<str> = Arc::from(key.to_js_string().as_str());
1028                            obj.insert(k, val);
1029                        }
1030                    }
1031                }
1032                self.push(Value::Object(obj))?;
1033            }
1034            Instruction::GetProperty(name) => {
1035                let obj = self.pop()?;
1036                let result = self.get_property(&obj, &name)?;
1037                // Store receiver for method calls
1038                if matches!(result, Value::BuiltinMethod { .. } | Value::Function(_)) {
1039                    self.last_receiver = Some(obj);
1040                    self.last_receiver_source = self.last_load_source.take();
1041                } else {
1042                    self.last_receiver_source = None;
1043                }
1044                self.push(result)?;
1045            }
1046            Instruction::SetProperty(name) => {
1047                // Stack: [value_to_store, object] with object on top
1048                // (compile_store pushes object after the value)
1049                let obj_val = self.pop()?;
1050                let value = self.pop()?;
1051                match obj_val {
1052                    Value::Object(mut obj) => {
1053                        obj.insert(Arc::from(name.as_str()), value);
1054                        // Push modified object back so compile_store can store it
1055                        self.push(Value::Object(obj))?;
1056                    }
1057                    _ => {
1058                        return Err(ZapcodeError::TypeError(format!(
1059                            "cannot set property '{}' on {}",
1060                            name,
1061                            obj_val.type_name()
1062                        )));
1063                    }
1064                }
1065            }
1066            Instruction::GetIndex => {
1067                let index = self.pop()?;
1068                let obj = self.pop()?;
1069                let result = match (&obj, &index) {
1070                    (Value::Array(arr), Value::Int(i)) => {
1071                        arr.get(*i as usize).cloned().unwrap_or(Value::Undefined)
1072                    }
1073                    (Value::Array(arr), Value::Float(f)) => {
1074                        arr.get(*f as usize).cloned().unwrap_or(Value::Undefined)
1075                    }
1076                    (Value::Object(map), Value::String(key)) => {
1077                        map.get(key.as_ref()).cloned().unwrap_or(Value::Undefined)
1078                    }
1079                    (Value::Object(map), _) => {
1080                        let key: Arc<str> = Arc::from(index.to_js_string().as_str());
1081                        map.get(key.as_ref()).cloned().unwrap_or(Value::Undefined)
1082                    }
1083                    (Value::String(s), Value::Int(i)) => s
1084                        .chars()
1085                        .nth(*i as usize)
1086                        .map(|c| Value::String(Arc::from(c.to_string().as_str())))
1087                        .unwrap_or(Value::Undefined),
1088                    _ => Value::Undefined,
1089                };
1090                self.push(result)?;
1091            }
1092            Instruction::SetIndex => {
1093                let index = self.pop()?;
1094                let mut obj = self.pop()?;
1095                let value = self.pop()?;
1096                match &mut obj {
1097                    Value::Array(arr) => {
1098                        let idx = match &index {
1099                            Value::Int(i) if *i >= 0 => *i as usize,
1100                            Value::Float(f) if *f >= 0.0 && *f == (*f as usize as f64) => {
1101                                *f as usize
1102                            }
1103                            _ => {
1104                                // Negative or non-numeric index: treat as no-op (like JS)
1105                                self.push(obj)?;
1106                                return Ok(None);
1107                            }
1108                        };
1109                        // Cap maximum sparse array growth to prevent memory exhaustion
1110                        if idx > arr.len() + 1024 {
1111                            return Err(ZapcodeError::RuntimeError(format!(
1112                                "array index {} too far beyond length {}",
1113                                idx,
1114                                arr.len()
1115                            )));
1116                        }
1117                        while arr.len() <= idx {
1118                            arr.push(Value::Undefined);
1119                        }
1120                        arr[idx] = value;
1121                    }
1122                    Value::Object(map) => {
1123                        let key: Arc<str> = Arc::from(index.to_js_string().as_str());
1124                        map.insert(key, value);
1125                    }
1126                    _ => {}
1127                }
1128                // Push modified object back so compile_store can store it to the variable
1129                self.push(obj)?;
1130            }
1131            Instruction::Spread => {
1132                // Handled contextually in CreateArray/CreateObject
1133            }
1134            Instruction::In => {
1135                let right = self.pop()?;
1136                let left = self.pop()?;
1137                let result = match &right {
1138                    Value::Object(map) => {
1139                        let key = left.to_js_string();
1140                        map.contains_key(key.as_str())
1141                    }
1142                    Value::Array(arr) => {
1143                        if let Value::Int(i) = left {
1144                            (i as usize) < arr.len()
1145                        } else {
1146                            false
1147                        }
1148                    }
1149                    _ => false,
1150                };
1151                self.push(Value::Bool(result))?;
1152            }
1153            Instruction::InstanceOf => {
1154                let right = self.pop()?;
1155                let left = self.pop()?;
1156                // Check if left's __class__ matches right's __class_name__
1157                let result = match (&left, &right) {
1158                    (Value::Object(instance), Value::Object(class_obj)) => {
1159                        if let (Some(inst_class), Some(class_name)) =
1160                            (instance.get("__class__"), class_obj.get("__class_name__"))
1161                        {
1162                            inst_class == class_name
1163                        } else {
1164                            false
1165                        }
1166                    }
1167                    _ => false,
1168                };
1169                self.push(Value::Bool(result))?;
1170            }
1171
1172            // Functions
1173            Instruction::CreateClosure(func_idx) => {
1174                // Capture current scope for closure
1175                let mut captured = Vec::new();
1176                // Capture all locals from all active frames using local_names
1177                for frame in &self.frames {
1178                    let local_names = if let Some(fidx) = frame.func_index {
1179                        &self.program.functions[fidx].local_names
1180                    } else {
1181                        &self.program.local_names
1182                    };
1183                    for (i, val) in frame.locals.iter().enumerate() {
1184                        if let Some(name) = local_names.get(i) {
1185                            captured.push((name.clone(), val.clone()));
1186                        }
1187                    }
1188                }
1189                // Also capture all globals that are user-defined (not builtins)
1190                let builtins = ["console", "Math", "JSON", "Object", "Array"];
1191                for (name, val) in &self.globals {
1192                    if !builtins.contains(&name.as_str()) {
1193                        captured.push((name.clone(), val.clone()));
1194                    }
1195                }
1196                let closure = Closure {
1197                    func_id: FunctionId(func_idx),
1198                    captured,
1199                };
1200                self.push(Value::Function(closure))?;
1201            }
1202            Instruction::Call(arg_count) => {
1203                let mut args = Vec::with_capacity(arg_count);
1204                for _ in 0..arg_count {
1205                    args.push(self.pop()?);
1206                }
1207                args.reverse();
1208
1209                let callee = self.pop()?;
1210                match callee {
1211                    Value::Function(closure) => {
1212                        let func_idx = closure.func_id.0;
1213                        let is_generator = self.program.functions[func_idx].is_generator;
1214
1215                        // Generator function: create a Generator object instead of running
1216                        if is_generator {
1217                            let params = self.program.functions[func_idx].params.clone();
1218                            let gen_id = self.alloc_generator_id();
1219                            // Capture args as named params so generator_next can restore them
1220                            let mut captured = closure.captured.clone();
1221                            for (i, param) in params.iter().enumerate() {
1222                                match param {
1223                                    ParamPattern::Ident(name) => {
1224                                        captured.push((
1225                                            name.clone(),
1226                                            args.get(i).cloned().unwrap_or(Value::Undefined),
1227                                        ));
1228                                    }
1229                                    ParamPattern::Rest(name) => {
1230                                        let rest: Vec<Value> = args[i..].to_vec();
1231                                        captured.push((name.clone(), Value::Array(rest)));
1232                                    }
1233                                    _ => {}
1234                                }
1235                            }
1236                            let gen_obj = GeneratorObject {
1237                                id: gen_id,
1238                                func_id: closure.func_id,
1239                                captured,
1240                                suspended: None,
1241                                done: false,
1242                            };
1243                            // Store in globals registry so we can look it up by ID later
1244                            self.globals.insert(
1245                                format!("__gen_{}", gen_id),
1246                                Value::Generator(gen_obj.clone()),
1247                            );
1248                            self.push(Value::Generator(gen_obj))?;
1249                            self.last_receiver = None;
1250                            self.last_receiver_source = None;
1251                        } else {
1252                            let this_value = self.last_receiver.take();
1253                            self.push_call_frame(&closure, &args, this_value)?;
1254                        }
1255                    }
1256                    Value::BuiltinMethod {
1257                        object_name,
1258                        method_name,
1259                    } => {
1260                        let receiver = self.last_receiver.take();
1261                        let result = match object_name.as_ref() {
1262                            "__array__" => {
1263                                if let Some(Value::Array(arr)) = &receiver {
1264                                    // Check if this is a callback method first
1265                                    match method_name.as_ref() {
1266                                        "map" | "filter" | "forEach" | "find" | "findIndex"
1267                                        | "every" | "some" | "reduce" | "sort" | "flatMap" => {
1268                                            let result = self.execute_array_callback_method(
1269                                                arr.clone(),
1270                                                &method_name,
1271                                                args,
1272                                            )?;
1273                                            Some(result)
1274                                        }
1275                                        _ => builtins::call_builtin(
1276                                            &Value::Array(arr.clone()),
1277                                            &method_name,
1278                                            &args,
1279                                            &mut self.stdout,
1280                                        )?,
1281                                    }
1282                                } else {
1283                                    None
1284                                }
1285                            }
1286                            "__string__" => {
1287                                if let Some(Value::String(s)) = &receiver {
1288                                    builtins::call_builtin(
1289                                        &Value::String(s.clone()),
1290                                        &method_name,
1291                                        &args,
1292                                        &mut self.stdout,
1293                                    )?
1294                                } else {
1295                                    None
1296                                }
1297                            }
1298                            "__generator__" => {
1299                                if let Some(Value::Generator(gen_obj)) = receiver {
1300                                    match method_name.as_ref() {
1301                                        "next" => {
1302                                            let arg =
1303                                                args.into_iter().next().unwrap_or(Value::Undefined);
1304                                            // Get the latest generator state from registry.
1305                                            // If the key is missing, the generator has finished
1306                                            // and was cleaned up — return done immediately.
1307                                            let gen_key = format!("__gen_{}", gen_obj.id);
1308                                            if let Some(Value::Generator(g)) =
1309                                                self.globals.remove(&gen_key)
1310                                            {
1311                                                let result = self.generator_next(g, arg)?;
1312                                                Some(result)
1313                                            } else {
1314                                                Some(
1315                                                    self.make_iterator_result(
1316                                                        Value::Undefined,
1317                                                        true,
1318                                                    ),
1319                                                )
1320                                            }
1321                                        }
1322                                        "return" => {
1323                                            let val =
1324                                                args.into_iter().next().unwrap_or(Value::Undefined);
1325                                            let gen_key = format!("__gen_{}", gen_obj.id);
1326                                            if let Some(Value::Generator(g)) =
1327                                                self.globals.remove(&gen_key)
1328                                            {
1329                                                let result = self.finish_generator(g, val);
1330                                                Some(result)
1331                                            } else {
1332                                                Some(self.make_iterator_result(val, true))
1333                                            }
1334                                        }
1335                                        _ => None,
1336                                    }
1337                                } else {
1338                                    None
1339                                }
1340                            }
1341                            global_name => builtins::call_global_method(
1342                                global_name,
1343                                &method_name,
1344                                &args,
1345                                &mut self.stdout,
1346                            )?,
1347                        };
1348                        match result {
1349                            Some(val) => self.push(val)?,
1350                            None => {
1351                                return Err(ZapcodeError::TypeError(format!(
1352                                    "{}.{} is not a function",
1353                                    object_name, method_name
1354                                )));
1355                            }
1356                        }
1357                    }
1358                    _ => {
1359                        return Err(ZapcodeError::TypeError(format!(
1360                            "{} is not a function",
1361                            callee.to_js_string()
1362                        )));
1363                    }
1364                }
1365            }
1366            Instruction::Return => {
1367                let return_val = self.pop().unwrap_or(Value::Undefined);
1368
1369                if self.frames.len() <= 1 {
1370                    return Ok(Some(VmState::Complete(return_val)));
1371                }
1372
1373                let frame = self.frames.pop().unwrap();
1374                self.tracker.pop_frame();
1375
1376                // If this was a constructor frame (has this_value), return the
1377                // updated `this` instead of the explicit return value (unless
1378                // the constructor explicitly returns an object).
1379                let actual_return = if let Some(ref this_val) = frame.this_value {
1380                    // Also propagate this back to parent frame (for super() calls)
1381                    if let Some(parent) = self.frames.last_mut() {
1382                        if parent.this_value.is_some() {
1383                            parent.this_value = Some(this_val.clone());
1384                        }
1385                    }
1386                    // Write back the mutated `this` to the original variable
1387                    // that the method receiver came from. This ensures that
1388                    // value-type semantics work correctly for method calls
1389                    // that mutate `this` properties (e.g., this.count += 1).
1390                    if let Some(ref source) = frame.receiver_source {
1391                        match source {
1392                            ReceiverSource::Global(name) => {
1393                                self.globals.insert(name.clone(), this_val.clone());
1394                            }
1395                            ReceiverSource::Local { frame_index, slot } => {
1396                                if let Some(target_frame) = self.frames.get_mut(*frame_index) {
1397                                    while target_frame.locals.len() <= *slot {
1398                                        target_frame.locals.push(Value::Undefined);
1399                                    }
1400                                    target_frame.locals[*slot] = this_val.clone();
1401                                }
1402                            }
1403                        }
1404                    }
1405                    if matches!(return_val, Value::Undefined) {
1406                        this_val.clone()
1407                    } else {
1408                        return_val
1409                    }
1410                } else {
1411                    return_val
1412                };
1413
1414                self.stack.truncate(frame.stack_base);
1415                self.push(actual_return)?;
1416            }
1417            Instruction::CallExternal(name, arg_count) => {
1418                if !self.external_functions.contains(&name) {
1419                    return Err(ZapcodeError::UnknownExternalFunction(name));
1420                }
1421                let mut args = Vec::with_capacity(arg_count);
1422                for _ in 0..arg_count {
1423                    args.push(self.pop()?);
1424                }
1425                args.reverse();
1426                // Suspend execution
1427                let snapshot = ZapcodeSnapshot::capture(self)?;
1428                return Ok(Some(VmState::Suspended {
1429                    function_name: name,
1430                    args,
1431                    snapshot,
1432                }));
1433            }
1434
1435            // Control flow
1436            Instruction::Jump(target) => {
1437                self.current_frame_mut().ip = target;
1438            }
1439            Instruction::JumpIfFalse(target) => {
1440                let val = self.pop()?;
1441                if !val.is_truthy() {
1442                    self.current_frame_mut().ip = target;
1443                }
1444            }
1445            Instruction::JumpIfTrue(target) => {
1446                let val = self.pop()?;
1447                if val.is_truthy() {
1448                    self.current_frame_mut().ip = target;
1449                }
1450            }
1451            Instruction::JumpIfNullish(target) => {
1452                let val = self.peek()?;
1453                if matches!(val, Value::Null | Value::Undefined) {
1454                    self.current_frame_mut().ip = target;
1455                }
1456            }
1457
1458            // Loops
1459            Instruction::SetupLoop => {}
1460            Instruction::Break | Instruction::Continue => {
1461                // These should have been compiled to jumps
1462            }
1463
1464            // Iterators
1465            Instruction::GetIterator => {
1466                let val = self.pop()?;
1467                match val {
1468                    Value::Array(arr) => {
1469                        // Push an iterator object: [array, index]
1470                        let iter_obj = Value::Array(vec![Value::Array(arr), Value::Int(0)]);
1471                        self.push(iter_obj)?;
1472                    }
1473                    Value::String(s) => {
1474                        let chars: Vec<Value> = s
1475                            .chars()
1476                            .map(|c| Value::String(Arc::from(c.to_string().as_str())))
1477                            .collect();
1478                        let iter_obj = Value::Array(vec![Value::Array(chars), Value::Int(0)]);
1479                        self.push(iter_obj)?;
1480                    }
1481                    Value::Generator(gen_obj) => {
1482                        let iter_obj = Value::Array(vec![
1483                            Value::String(Arc::from("__gen__")),
1484                            Value::Int(gen_obj.id as i64),
1485                            Value::Bool(false),
1486                        ]);
1487                        self.push(iter_obj)?;
1488                    }
1489                    _ => {
1490                        return Err(ZapcodeError::TypeError(format!(
1491                            "{} is not iterable",
1492                            val.type_name()
1493                        )));
1494                    }
1495                }
1496            }
1497            Instruction::IteratorNext => {
1498                let iter = self.pop()?;
1499                // Check for generator iterator (3-element sentinel)
1500                if let Value::Array(ref items) = iter {
1501                    if items.len() == 3 {
1502                        if let Value::String(ref s) = items[0] {
1503                            if s.as_ref() == "__gen__" {
1504                                let gen_id = match &items[1] {
1505                                    Value::Int(id) => *id as u64,
1506                                    _ => {
1507                                        return Err(ZapcodeError::RuntimeError(
1508                                            "bad gen iter".into(),
1509                                        ))
1510                                    }
1511                                };
1512                                let gen_key = format!("__gen_{}", gen_id);
1513                                let gen_obj = if let Some(Value::Generator(g)) =
1514                                    self.globals.remove(&gen_key)
1515                                {
1516                                    g
1517                                } else {
1518                                    self.push(Value::Array(vec![
1519                                        Value::String(Arc::from("__gen__")),
1520                                        Value::Int(gen_id as i64),
1521                                        Value::Bool(true),
1522                                    ]))?;
1523                                    self.push(Value::Undefined)?;
1524                                    return Ok(None);
1525                                };
1526                                let result = self.generator_next(gen_obj, Value::Undefined)?;
1527                                if let Value::Object(ref obj) = result {
1528                                    let done = obj
1529                                        .get("done")
1530                                        .is_some_and(|v| matches!(v, Value::Bool(true)));
1531                                    let value =
1532                                        obj.get("value").cloned().unwrap_or(Value::Undefined);
1533                                    self.push(Value::Array(vec![
1534                                        Value::String(Arc::from("__gen__")),
1535                                        Value::Int(gen_id as i64),
1536                                        Value::Bool(done),
1537                                    ]))?;
1538                                    self.push(value)?;
1539                                } else {
1540                                    self.push(iter)?;
1541                                    self.push(Value::Undefined)?;
1542                                }
1543                                return Ok(None);
1544                            }
1545                        }
1546                    }
1547                }
1548                match iter {
1549                    Value::Array(ref items) if items.len() == 2 => {
1550                        let arr = match &items[0] {
1551                            Value::Array(a) => a,
1552                            _ => return Err(ZapcodeError::RuntimeError("invalid iterator".into())),
1553                        };
1554                        let idx = match &items[1] {
1555                            Value::Int(i) => *i as usize,
1556                            _ => return Err(ZapcodeError::RuntimeError("invalid iterator".into())),
1557                        };
1558                        if idx < arr.len() {
1559                            let value = arr[idx].clone();
1560                            // Update iterator
1561                            let new_iter =
1562                                Value::Array(vec![items[0].clone(), Value::Int((idx + 1) as i64)]);
1563                            // Push updated iterator back, then the value
1564                            self.push(new_iter)?;
1565                            self.push(value)?;
1566                        } else {
1567                            // Done
1568                            self.push(iter)?;
1569                            self.push(Value::Undefined)?;
1570                        }
1571                    }
1572                    _ => {
1573                        return Err(ZapcodeError::RuntimeError("invalid iterator state".into()));
1574                    }
1575                }
1576            }
1577            Instruction::IteratorDone => {
1578                let value = self.pop()?;
1579                let iter = self.peek()?;
1580                // Check for generator iterator first
1581                if let Value::Array(items) = iter {
1582                    if items.len() == 3 {
1583                        if let Value::String(ref s) = items[0] {
1584                            if s.as_ref() == "__gen__" {
1585                                let done = matches!(&items[2], Value::Bool(true));
1586                                if !done {
1587                                    self.push(value)?;
1588                                }
1589                                self.push(Value::Bool(done))?;
1590                                return Ok(None);
1591                            }
1592                        }
1593                    }
1594                }
1595                let iter = self.peek()?;
1596                match iter {
1597                    Value::Array(items) if items.len() == 2 => {
1598                        let arr = match &items[0] {
1599                            Value::Array(a) => a,
1600                            _ => {
1601                                self.push(value)?;
1602                                self.push(Value::Bool(true))?;
1603                                return Ok(None);
1604                            }
1605                        };
1606                        let idx = match &items[1] {
1607                            Value::Int(i) => *i as usize,
1608                            _ => {
1609                                self.push(value)?;
1610                                self.push(Value::Bool(true))?;
1611                                return Ok(None);
1612                            }
1613                        };
1614                        let done = idx > arr.len();
1615                        if !done {
1616                            // Push value back for the binding
1617                            self.push(value)?;
1618                        }
1619                        self.push(Value::Bool(done))?;
1620                    }
1621                    _ => {
1622                        self.push(value)?;
1623                        self.push(Value::Bool(true))?;
1624                    }
1625                }
1626            }
1627
1628            // Error handling
1629            Instruction::SetupTry(catch_ip, _) => {
1630                self.try_stack.push(TryInfo {
1631                    catch_ip,
1632                    frame_depth: self.frames.len(),
1633                    stack_depth: self.stack.len(),
1634                });
1635            }
1636            Instruction::Throw => {
1637                let val = self.pop()?;
1638                let msg = val.to_js_string();
1639                return Err(ZapcodeError::RuntimeError(msg));
1640            }
1641            Instruction::EndTry => {
1642                self.try_stack.pop();
1643            }
1644
1645            // Typeof
1646            Instruction::TypeOf => {
1647                let val = self.pop()?;
1648                let type_str = val.type_name();
1649                self.push(Value::String(Arc::from(type_str)))?;
1650            }
1651
1652            // Void
1653            Instruction::Void => {
1654                self.pop()?;
1655                self.push(Value::Undefined)?;
1656            }
1657
1658            // Update
1659            Instruction::Increment => {
1660                let val = self.pop()?;
1661                let result = match val {
1662                    Value::Int(n) => Value::Int(n + 1),
1663                    _ => Value::Float(val.to_number() + 1.0),
1664                };
1665                self.push(result)?;
1666            }
1667            Instruction::Decrement => {
1668                let val = self.pop()?;
1669                let result = match val {
1670                    Value::Int(n) => Value::Int(n - 1),
1671                    _ => Value::Float(val.to_number() - 1.0),
1672                };
1673                self.push(result)?;
1674            }
1675
1676            // Template literals
1677            Instruction::ConcatStrings(count) => {
1678                let mut parts = Vec::with_capacity(count);
1679                for _ in 0..count {
1680                    parts.push(self.pop()?);
1681                }
1682                parts.reverse();
1683                let result: String = parts.iter().map(|v| v.to_js_string()).collect();
1684                self.push(Value::String(Arc::from(result.as_str())))?;
1685            }
1686
1687            // Destructuring
1688            Instruction::DestructureObject(keys) => {
1689                let obj = self.pop()?;
1690                for key in keys {
1691                    let val = self.get_property(&obj, &key)?;
1692                    self.push(val)?;
1693                }
1694            }
1695            Instruction::DestructureArray(count) => {
1696                let arr = self.pop()?;
1697                match arr {
1698                    Value::Array(items) => {
1699                        for i in 0..count {
1700                            self.push(items.get(i).cloned().unwrap_or(Value::Undefined))?;
1701                        }
1702                    }
1703                    _ => {
1704                        for _ in 0..count {
1705                            self.push(Value::Undefined)?;
1706                        }
1707                    }
1708                }
1709            }
1710
1711            Instruction::Nop => {}
1712
1713            // Generators
1714            Instruction::CreateGenerator(_func_idx) => {
1715                // Generator creation is handled at Call time via is_generator check.
1716            }
1717            Instruction::Yield => {
1718                // Yield is handled in run_generator_until_yield_or_return.
1719                // Reaching here means yield outside a generator function.
1720                return Err(ZapcodeError::RuntimeError(
1721                    "yield can only be used inside a generator function".to_string(),
1722                ));
1723            }
1724
1725            Instruction::Await => {
1726                // Check if the value on the stack is a Promise object.
1727                // If resolved, unwrap its value. If rejected, throw its reason.
1728                // If it's a regular (non-promise) value, leave it as-is.
1729                let val = self.pop()?;
1730                if builtins::is_promise(&val) {
1731                    if let Value::Object(map) = &val {
1732                        let status = map.get("status").cloned().unwrap_or(Value::Undefined);
1733                        match status {
1734                            Value::String(s) if s.as_ref() == "resolved" => {
1735                                let inner = map.get("value").cloned().unwrap_or(Value::Undefined);
1736                                self.push(inner)?;
1737                            }
1738                            Value::String(s) if s.as_ref() == "rejected" => {
1739                                let reason = map.get("reason").cloned().unwrap_or(Value::Undefined);
1740                                return Err(ZapcodeError::RuntimeError(format!(
1741                                    "Unhandled promise rejection: {}",
1742                                    reason.to_js_string()
1743                                )));
1744                            }
1745                            _ => {
1746                                // Unknown status — pass through
1747                                self.push(val)?;
1748                            }
1749                        }
1750                    } else {
1751                        self.push(val)?;
1752                    }
1753                } else {
1754                    // Not a promise — pass through (await on non-promise returns the value)
1755                    self.push(val)?;
1756                }
1757            }
1758
1759            // Classes
1760            Instruction::CreateClass {
1761                name,
1762                n_methods,
1763                n_statics,
1764                has_super,
1765            } => {
1766                // Stack layout (top to bottom):
1767                // constructor closure (or undefined)
1768                // n_methods * (closure, method_name_string) pairs
1769                // n_statics * (closure, method_name_string) pairs
1770                // [optional super class if has_super]
1771
1772                let constructor = self.pop()?;
1773
1774                // Pop instance methods
1775                let mut prototype = IndexMap::new();
1776                for _ in 0..n_methods {
1777                    let method_closure = self.pop()?;
1778                    let method_name = self.pop()?;
1779                    if let Value::String(mn) = method_name {
1780                        prototype.insert(mn, method_closure);
1781                    }
1782                }
1783
1784                // Pop static methods
1785                let mut statics = IndexMap::new();
1786                for _ in 0..n_statics {
1787                    let method_closure = self.pop()?;
1788                    let method_name = self.pop()?;
1789                    if let Value::String(mn) = method_name {
1790                        statics.insert(mn, method_closure);
1791                    }
1792                }
1793
1794                // Pop super class if present
1795                let super_class = if has_super { Some(self.pop()?) } else { None };
1796
1797                // If super class, copy its prototype methods to ours (inheritance)
1798                if let Some(Value::Object(ref sc)) = super_class {
1799                    if let Some(Value::Object(super_proto)) = sc.get("__prototype__").cloned() {
1800                        // Super prototype methods go first, then our own (which override)
1801                        let mut merged = super_proto;
1802                        for (k, v) in prototype {
1803                            merged.insert(k, v);
1804                        }
1805                        prototype = merged;
1806                    }
1807                }
1808
1809                // Build the class object
1810                let mut class_obj = IndexMap::new();
1811                class_obj.insert(
1812                    Arc::from("__class_name__"),
1813                    Value::String(Arc::from(name.as_str())),
1814                );
1815                class_obj.insert(Arc::from("__constructor__"), constructor);
1816                class_obj.insert(Arc::from("__prototype__"), Value::Object(prototype));
1817
1818                // Store super class reference for super() calls
1819                if let Some(sc) = super_class {
1820                    class_obj.insert(Arc::from("__super__"), sc);
1821                }
1822
1823                // Add static methods directly on the class object
1824                for (k, v) in statics {
1825                    class_obj.insert(k, v);
1826                }
1827
1828                self.push(Value::Object(class_obj))?;
1829            }
1830
1831            Instruction::Construct(arg_count) => {
1832                let mut args = Vec::with_capacity(arg_count);
1833                for _ in 0..arg_count {
1834                    args.push(self.pop()?);
1835                }
1836                args.reverse();
1837
1838                let callee = self.pop()?;
1839
1840                match &callee {
1841                    Value::Object(class_obj) if class_obj.contains_key("__class_name__") => {
1842                        // Create a new instance object
1843                        let mut instance = IndexMap::new();
1844
1845                        // Copy prototype methods onto the instance
1846                        if let Some(Value::Object(proto)) = class_obj.get("__prototype__") {
1847                            for (k, v) in proto {
1848                                instance.insert(k.clone(), v.clone());
1849                            }
1850                        }
1851
1852                        // Store class reference for instanceof
1853                        if let Some(class_name) = class_obj.get("__class_name__") {
1854                            instance.insert(Arc::from("__class__"), class_name.clone());
1855                        }
1856
1857                        let instance_val = Value::Object(instance);
1858
1859                        // Call the constructor with `this` bound to the instance
1860                        if let Some(ctor) = class_obj.get("__constructor__") {
1861                            if let Value::Function(closure) = ctor {
1862                                // Clear receiver source — constructors should not
1863                                // write back to a receiver variable.
1864                                self.last_receiver_source = None;
1865                                self.push_call_frame(closure, &args, Some(instance_val))?;
1866                                self.last_receiver = None;
1867                            } else {
1868                                // No valid constructor, just return the instance
1869                                self.push(instance_val)?;
1870                            }
1871                        } else {
1872                            // No constructor, just return the instance
1873                            self.push(instance_val)?;
1874                        }
1875                    }
1876                    Value::Function(closure) => {
1877                        // `new` on a plain function — just call it
1878                        self.push_call_frame(closure, &args, None)?;
1879                        self.last_receiver = None;
1880                    }
1881                    _ => {
1882                        return Err(ZapcodeError::TypeError(format!(
1883                            "{} is not a constructor",
1884                            callee.to_js_string()
1885                        )));
1886                    }
1887                }
1888            }
1889
1890            Instruction::LoadThis => {
1891                // Walk frames from top to find the nearest `this` value
1892                let this_val = self
1893                    .frames
1894                    .iter()
1895                    .rev()
1896                    .find_map(|f| f.this_value.clone())
1897                    .unwrap_or(Value::Undefined);
1898                self.push(this_val)?;
1899            }
1900            Instruction::StoreThis => {
1901                let val = self.pop()?;
1902                // Update this_value in the nearest frame that has one
1903                for frame in self.frames.iter_mut().rev() {
1904                    if frame.this_value.is_some() {
1905                        frame.this_value = Some(val);
1906                        break;
1907                    }
1908                }
1909            }
1910            Instruction::CallSuper(arg_count) => {
1911                let mut args = Vec::with_capacity(arg_count);
1912                for _ in 0..arg_count {
1913                    args.push(self.pop()?);
1914                }
1915                args.reverse();
1916
1917                // Get current `this` value (the instance being constructed)
1918                let this_val = self
1919                    .frames
1920                    .iter()
1921                    .rev()
1922                    .find_map(|f| f.this_value.clone())
1923                    .unwrap_or(Value::Undefined);
1924
1925                // Find the super class constructor from the class that's being constructed.
1926                // We need to look it up from the globals — the class with __super__ key.
1927                // The super class info is stored on the class object.
1928                // We'll look through globals for the class that has __super__.
1929                let mut super_ctor = None;
1930                for val in self.globals.values() {
1931                    if let Value::Object(obj) = val {
1932                        if let Some(Value::Object(super_class)) = obj.get("__super__") {
1933                            if let Some(ctor) = super_class.get("__constructor__") {
1934                                super_ctor = Some(ctor.clone());
1935                                break;
1936                            }
1937                        }
1938                    }
1939                }
1940
1941                if let Some(Value::Function(closure)) = super_ctor {
1942                    self.last_receiver_source = None;
1943                    self.push_call_frame(&closure, &args, Some(this_val))?;
1944                    self.last_receiver = None;
1945                } else {
1946                    // No super constructor found — push undefined
1947                    self.push(Value::Undefined)?;
1948                }
1949            }
1950        }
1951
1952        Ok(None)
1953    }
1954
1955    fn get_property(&self, obj: &Value, name: &str) -> Result<Value> {
1956        // Property access on null/undefined throws TypeError (like JS)
1957        if matches!(obj, Value::Null | Value::Undefined) {
1958            return Err(ZapcodeError::TypeError(format!(
1959                "Cannot read properties of {} (reading '{}')",
1960                obj.to_js_string(),
1961                name
1962            )));
1963        }
1964        match obj {
1965            Value::Object(map) => {
1966                // Check if property exists as a real value on the object
1967                if let Some(val) = map.get(name) {
1968                    if !matches!(val, Value::Undefined) {
1969                        return Ok(val.clone());
1970                    }
1971                }
1972                // Check if this is a known global object — return builtin method handle
1973                if let Some(global_name) = &self.last_global_name {
1974                    let known_globals = ["console", "Math", "JSON", "Object", "Array", "Promise"];
1975                    if known_globals.contains(&global_name.as_str()) {
1976                        return Ok(Value::BuiltinMethod {
1977                            object_name: Arc::from(global_name.as_str()),
1978                            method_name: Arc::from(name),
1979                        });
1980                    }
1981                }
1982                Ok(Value::Undefined)
1983            }
1984            Value::Array(arr) => match name {
1985                "length" => Ok(Value::Int(arr.len() as i64)),
1986                _ if is_array_method(name) => Ok(Value::BuiltinMethod {
1987                    object_name: Arc::from("__array__"),
1988                    method_name: Arc::from(name),
1989                }),
1990                _ => {
1991                    if let Ok(idx) = name.parse::<usize>() {
1992                        Ok(arr.get(idx).cloned().unwrap_or(Value::Undefined))
1993                    } else {
1994                        Ok(Value::Undefined)
1995                    }
1996                }
1997            },
1998            Value::String(s) => match name {
1999                "length" => Ok(Value::Int(s.chars().count() as i64)),
2000                _ if is_string_method(name) => Ok(Value::BuiltinMethod {
2001                    object_name: Arc::from("__string__"),
2002                    method_name: Arc::from(name),
2003                }),
2004                _ => Ok(Value::Undefined),
2005            },
2006            Value::Generator(_) => match name {
2007                "next" | "return" | "throw" => Ok(Value::BuiltinMethod {
2008                    object_name: Arc::from("__generator__"),
2009                    method_name: Arc::from(name),
2010                }),
2011                _ => Ok(Value::Undefined),
2012            },
2013            _ => Ok(Value::Undefined),
2014        }
2015    }
2016}
2017
2018// Re-export for the ParamPattern type used in function calls
2019use crate::parser::ir::ParamPattern;
2020
2021fn is_array_method(name: &str) -> bool {
2022    matches!(
2023        name,
2024        "push"
2025            | "pop"
2026            | "shift"
2027            | "unshift"
2028            | "splice"
2029            | "slice"
2030            | "concat"
2031            | "join"
2032            | "reverse"
2033            | "sort"
2034            | "indexOf"
2035            | "lastIndexOf"
2036            | "includes"
2037            | "find"
2038            | "findIndex"
2039            | "map"
2040            | "filter"
2041            | "reduce"
2042            | "forEach"
2043            | "every"
2044            | "some"
2045            | "flat"
2046            | "flatMap"
2047            | "fill"
2048            | "at"
2049            | "entries"
2050            | "keys"
2051            | "values"
2052    )
2053}
2054
2055fn is_string_method(name: &str) -> bool {
2056    matches!(
2057        name,
2058        "charAt"
2059            | "charCodeAt"
2060            | "indexOf"
2061            | "lastIndexOf"
2062            | "includes"
2063            | "startsWith"
2064            | "endsWith"
2065            | "slice"
2066            | "substring"
2067            | "substr"
2068            | "toUpperCase"
2069            | "toLowerCase"
2070            | "trim"
2071            | "trimStart"
2072            | "trimEnd"
2073            | "trimLeft"
2074            | "trimRight"
2075            | "padStart"
2076            | "padEnd"
2077            | "repeat"
2078            | "replace"
2079            | "replaceAll"
2080            | "split"
2081            | "concat"
2082            | "at"
2083            | "match"
2084            | "search"
2085            | "normalize"
2086    )
2087}
2088
2089/// Main entry point: compile and run TypeScript code.
2090pub struct ZapcodeRun {
2091    source: String,
2092    #[allow(dead_code)]
2093    inputs: Vec<String>,
2094    external_functions: Vec<String>,
2095    limits: ResourceLimits,
2096}
2097
2098impl ZapcodeRun {
2099    pub fn new(
2100        source: String,
2101        inputs: Vec<String>,
2102        external_functions: Vec<String>,
2103        limits: ResourceLimits,
2104    ) -> Result<Self> {
2105        Ok(Self {
2106            source,
2107            inputs,
2108            external_functions,
2109            limits,
2110        })
2111    }
2112
2113    pub fn run(&self, input_values: Vec<(String, Value)>) -> Result<RunResult> {
2114        let program = crate::parser::parse(&self.source)?;
2115        let ext_set: HashSet<String> = self.external_functions.iter().cloned().collect();
2116        let compiled = crate::compiler::compile_with_externals(&program, ext_set.clone())?;
2117        let mut vm = Vm::new(compiled, self.limits.clone(), ext_set);
2118
2119        // Inject inputs as globals
2120        for (name, value) in input_values {
2121            vm.globals.insert(name, value);
2122        }
2123
2124        let state = vm.run()?;
2125        Ok(RunResult {
2126            state,
2127            stdout: vm.stdout,
2128        })
2129    }
2130
2131    /// Start execution. Like `run()`, but returns the raw `VmState` directly
2132    /// instead of wrapping it in a `RunResult`. This is the primary entry point
2133    /// for code that needs to handle suspension / snapshot / resume.
2134    pub fn start(&self, input_values: Vec<(String, Value)>) -> Result<VmState> {
2135        let program = crate::parser::parse(&self.source)?;
2136        let ext_set: HashSet<String> = self.external_functions.iter().cloned().collect();
2137        let compiled = crate::compiler::compile_with_externals(&program, ext_set.clone())?;
2138        let mut vm = Vm::new(compiled, self.limits.clone(), ext_set);
2139
2140        for (name, value) in input_values {
2141            vm.globals.insert(name, value);
2142        }
2143
2144        vm.run()
2145    }
2146
2147    pub fn run_simple(&self) -> Result<Value> {
2148        let result = self.run(Vec::new())?;
2149        match result.state {
2150            VmState::Complete(v) => Ok(v),
2151            VmState::Suspended { function_name, .. } => Err(ZapcodeError::RuntimeError(format!(
2152                "execution suspended on external function '{}' — use run() instead",
2153                function_name
2154            ))),
2155        }
2156    }
2157}
2158
2159/// Result of running a Zapcode program.
2160pub struct RunResult {
2161    pub state: VmState,
2162    pub stdout: String,
2163}
2164
2165/// Quick helper to evaluate a TypeScript expression.
2166pub fn eval_ts(source: &str) -> Result<Value> {
2167    let runner = ZapcodeRun::new(
2168        source.to_string(),
2169        Vec::new(),
2170        Vec::new(),
2171        ResourceLimits::default(),
2172    )?;
2173    runner.run_simple()
2174}
2175
2176/// Evaluate TypeScript and return both the value and stdout output.
2177pub fn eval_ts_with_output(source: &str) -> Result<(Value, String)> {
2178    let runner = ZapcodeRun::new(
2179        source.to_string(),
2180        Vec::new(),
2181        Vec::new(),
2182        ResourceLimits::default(),
2183    )?;
2184    let result = runner.run(Vec::new())?;
2185    match result.state {
2186        VmState::Complete(v) => Ok((v, result.stdout)),
2187        VmState::Suspended { function_name, .. } => Err(ZapcodeError::RuntimeError(format!(
2188            "execution suspended on external function '{}'",
2189            function_name
2190        ))),
2191    }
2192}