Skip to main content

zapcode_core/vm/
mod.rs

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