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