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