Skip to main content

harn_vm/
vm.rs

1use std::collections::BTreeMap;
2use std::future::Future;
3use std::pin::Pin;
4use std::rc::Rc;
5use std::time::Instant;
6
7use crate::chunk::{Chunk, CompiledFunction, Constant, Op};
8use crate::value::{
9    compare_values, values_equal, VmAsyncBuiltinFn, VmBuiltinFn, VmClosure, VmEnv, VmError,
10    VmTaskHandle, VmValue,
11};
12
13/// Call frame for function execution.
14struct CallFrame {
15    chunk: Chunk,
16    ip: usize,
17    stack_base: usize,
18    saved_env: VmEnv,
19    /// Function name for stack traces (empty for top-level pipeline).
20    fn_name: String,
21}
22
23/// Exception handler for try/catch.
24struct ExceptionHandler {
25    catch_ip: usize,
26    stack_depth: usize,
27    frame_depth: usize,
28    /// If non-empty, this catch only handles errors whose enum_name matches.
29    error_type: String,
30}
31
32/// Debug action returned by the debug hook.
33#[derive(Debug, Clone, PartialEq)]
34pub enum DebugAction {
35    /// Continue execution normally.
36    Continue,
37    /// Stop (breakpoint hit, step complete).
38    Stop,
39}
40
41/// Information about current execution state for the debugger.
42#[derive(Debug, Clone)]
43pub struct DebugState {
44    pub line: usize,
45    pub variables: BTreeMap<String, VmValue>,
46    pub frame_name: String,
47    pub frame_depth: usize,
48}
49
50/// The Harn bytecode virtual machine.
51pub struct Vm {
52    stack: Vec<VmValue>,
53    env: VmEnv,
54    output: String,
55    builtins: BTreeMap<String, VmBuiltinFn>,
56    async_builtins: BTreeMap<String, VmAsyncBuiltinFn>,
57    /// Iterator state for for-in loops.
58    iterators: Vec<(Vec<VmValue>, usize)>,
59    /// Call frame stack.
60    frames: Vec<CallFrame>,
61    /// Exception handler stack.
62    exception_handlers: Vec<ExceptionHandler>,
63    /// Spawned async task handles.
64    spawned_tasks: BTreeMap<String, VmTaskHandle>,
65    /// Counter for generating unique task IDs.
66    task_counter: u64,
67    /// Active deadline stack: (deadline_instant, frame_depth).
68    deadlines: Vec<(Instant, usize)>,
69    /// Breakpoints (source line numbers).
70    breakpoints: Vec<usize>,
71    /// Whether the VM is in step mode.
72    step_mode: bool,
73    /// The frame depth at which stepping started (for step-over).
74    step_frame_depth: usize,
75    /// Whether the VM is currently stopped at a debug point.
76    stopped: bool,
77    /// Last source line executed (to detect line changes).
78    last_line: usize,
79    /// Source directory for resolving imports.
80    source_dir: Option<std::path::PathBuf>,
81    /// Already-imported file paths (cycle prevention).
82    imported_paths: Vec<std::path::PathBuf>,
83    /// Source file path for error reporting.
84    source_file: Option<String>,
85    /// Source text for error reporting.
86    source_text: Option<String>,
87}
88
89impl Vm {
90    pub fn new() -> Self {
91        Self {
92            stack: Vec::with_capacity(256),
93            env: VmEnv::new(),
94            output: String::new(),
95            builtins: BTreeMap::new(),
96            async_builtins: BTreeMap::new(),
97            iterators: Vec::new(),
98            frames: Vec::new(),
99            exception_handlers: Vec::new(),
100            spawned_tasks: BTreeMap::new(),
101            task_counter: 0,
102            deadlines: Vec::new(),
103            breakpoints: Vec::new(),
104            step_mode: false,
105            step_frame_depth: 0,
106            stopped: false,
107            last_line: 0,
108            source_dir: None,
109            imported_paths: Vec::new(),
110            source_file: None,
111            source_text: None,
112        }
113    }
114
115    /// Set source info for error reporting (file path and source text).
116    pub fn set_source_info(&mut self, file: &str, text: &str) {
117        self.source_file = Some(file.to_string());
118        self.source_text = Some(text.to_string());
119    }
120
121    /// Set breakpoints by source line number.
122    pub fn set_breakpoints(&mut self, lines: Vec<usize>) {
123        self.breakpoints = lines;
124    }
125
126    /// Enable step mode (stop at next line).
127    pub fn set_step_mode(&mut self, step: bool) {
128        self.step_mode = step;
129        self.step_frame_depth = self.frames.len();
130    }
131
132    /// Enable step-over mode (stop at next line at same or lower frame depth).
133    pub fn set_step_over(&mut self) {
134        self.step_mode = true;
135        self.step_frame_depth = self.frames.len();
136    }
137
138    /// Enable step-out mode (stop when returning from current frame).
139    pub fn set_step_out(&mut self) {
140        self.step_mode = true;
141        self.step_frame_depth = self.frames.len().saturating_sub(1);
142    }
143
144    /// Check if the VM is stopped at a debug point.
145    pub fn is_stopped(&self) -> bool {
146        self.stopped
147    }
148
149    /// Get the current debug state (variables, line, etc.).
150    pub fn debug_state(&self) -> DebugState {
151        let line = self.current_line();
152        let variables = self.env.all_variables();
153        let frame_name = if self.frames.len() > 1 {
154            format!("frame_{}", self.frames.len() - 1)
155        } else {
156            "pipeline".to_string()
157        };
158        DebugState {
159            line,
160            variables,
161            frame_name,
162            frame_depth: self.frames.len(),
163        }
164    }
165
166    /// Get all stack frames for the debugger.
167    pub fn debug_stack_frames(&self) -> Vec<(String, usize)> {
168        let mut frames = Vec::new();
169        for (i, frame) in self.frames.iter().enumerate() {
170            let line = if frame.ip > 0 && frame.ip - 1 < frame.chunk.lines.len() {
171                frame.chunk.lines[frame.ip - 1] as usize
172            } else {
173                0
174            };
175            let name = if frame.fn_name.is_empty() {
176                if i == 0 {
177                    "pipeline".to_string()
178                } else {
179                    format!("fn_{}", i)
180                }
181            } else {
182                frame.fn_name.clone()
183            };
184            frames.push((name, line));
185        }
186        frames
187    }
188
189    /// Get the current source line.
190    fn current_line(&self) -> usize {
191        if let Some(frame) = self.frames.last() {
192            let ip = if frame.ip > 0 { frame.ip - 1 } else { 0 };
193            if ip < frame.chunk.lines.len() {
194                return frame.chunk.lines[ip] as usize;
195            }
196        }
197        0
198    }
199
200    /// Execute one instruction, returning whether to stop (breakpoint/step).
201    /// Returns Ok(None) to continue, Ok(Some(val)) on program end, Err on error.
202    pub async fn step_execute(&mut self) -> Result<Option<(VmValue, bool)>, VmError> {
203        // Check if we need to stop at this line
204        let current_line = self.current_line();
205        let line_changed = current_line != self.last_line && current_line > 0;
206
207        if line_changed {
208            self.last_line = current_line;
209
210            // Check breakpoints
211            if self.breakpoints.contains(&current_line) {
212                self.stopped = true;
213                return Ok(Some((VmValue::Nil, true))); // true = stopped
214            }
215
216            // Check step mode
217            if self.step_mode && self.frames.len() <= self.step_frame_depth + 1 {
218                self.step_mode = false;
219                self.stopped = true;
220                return Ok(Some((VmValue::Nil, true))); // true = stopped
221            }
222        }
223
224        // Execute one instruction cycle
225        self.stopped = false;
226        self.execute_one_cycle().await
227    }
228
229    /// Execute a single instruction cycle.
230    async fn execute_one_cycle(&mut self) -> Result<Option<(VmValue, bool)>, VmError> {
231        // Check deadline
232        if let Some(&(deadline, _)) = self.deadlines.last() {
233            if Instant::now() > deadline {
234                self.deadlines.pop();
235                let err = VmError::Thrown(VmValue::String(Rc::from("Deadline exceeded")));
236                match self.handle_error(err) {
237                    Ok(None) => return Ok(None),
238                    Ok(Some(val)) => return Ok(Some((val, false))),
239                    Err(e) => return Err(e),
240                }
241            }
242        }
243
244        // Get current frame
245        let frame = match self.frames.last_mut() {
246            Some(f) => f,
247            None => {
248                let val = self.stack.pop().unwrap_or(VmValue::Nil);
249                return Ok(Some((val, false)));
250            }
251        };
252
253        // Check if we've reached end of chunk
254        if frame.ip >= frame.chunk.code.len() {
255            let val = self.stack.pop().unwrap_or(VmValue::Nil);
256            let popped_frame = self.frames.pop().unwrap();
257            if self.frames.is_empty() {
258                return Ok(Some((val, false)));
259            } else {
260                self.env = popped_frame.saved_env;
261                self.stack.truncate(popped_frame.stack_base);
262                self.stack.push(val);
263                return Ok(None);
264            }
265        }
266
267        let op = frame.chunk.code[frame.ip];
268        frame.ip += 1;
269
270        match self.execute_op(op).await {
271            Ok(Some(val)) => Ok(Some((val, false))),
272            Ok(None) => Ok(None),
273            Err(VmError::Return(val)) => {
274                if let Some(popped_frame) = self.frames.pop() {
275                    let current_depth = self.frames.len();
276                    self.exception_handlers
277                        .retain(|h| h.frame_depth <= current_depth);
278                    if self.frames.is_empty() {
279                        return Ok(Some((val, false)));
280                    }
281                    self.env = popped_frame.saved_env;
282                    self.stack.truncate(popped_frame.stack_base);
283                    self.stack.push(val);
284                    Ok(None)
285                } else {
286                    Ok(Some((val, false)))
287                }
288            }
289            Err(e) => match self.handle_error(e) {
290                Ok(None) => Ok(None),
291                Ok(Some(val)) => Ok(Some((val, false))),
292                Err(e) => Err(e),
293            },
294        }
295    }
296
297    /// Initialize execution (push the initial frame).
298    pub fn start(&mut self, chunk: &Chunk) {
299        self.frames.push(CallFrame {
300            chunk: chunk.clone(),
301            ip: 0,
302            stack_base: self.stack.len(),
303            saved_env: self.env.clone(),
304            fn_name: String::new(),
305        });
306    }
307
308    /// Register a sync builtin function.
309    pub fn register_builtin<F>(&mut self, name: &str, f: F)
310    where
311        F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + 'static,
312    {
313        self.builtins.insert(name.to_string(), Rc::new(f));
314    }
315
316    /// Remove a sync builtin (so an async version can take precedence).
317    pub fn unregister_builtin(&mut self, name: &str) {
318        self.builtins.remove(name);
319    }
320
321    /// Register an async builtin function.
322    pub fn register_async_builtin<F, Fut>(&mut self, name: &str, f: F)
323    where
324        F: Fn(Vec<VmValue>) -> Fut + 'static,
325        Fut: Future<Output = Result<VmValue, VmError>> + 'static,
326    {
327        self.async_builtins
328            .insert(name.to_string(), Rc::new(move |args| Box::pin(f(args))));
329    }
330
331    /// Create a child VM that shares builtins and env but has fresh execution state.
332    /// Used for parallel/spawn to fork the VM for concurrent tasks.
333    fn child_vm(&self) -> Vm {
334        Vm {
335            stack: Vec::with_capacity(64),
336            env: self.env.clone(),
337            output: String::new(),
338            builtins: self.builtins.clone(),
339            async_builtins: self.async_builtins.clone(),
340            iterators: Vec::new(),
341            frames: Vec::new(),
342            exception_handlers: Vec::new(),
343            spawned_tasks: BTreeMap::new(),
344            task_counter: 0,
345            deadlines: Vec::new(),
346            breakpoints: Vec::new(),
347            step_mode: false,
348            step_frame_depth: 0,
349            stopped: false,
350            last_line: 0,
351            source_dir: None,
352            imported_paths: Vec::new(),
353            source_file: self.source_file.clone(),
354            source_text: self.source_text.clone(),
355        }
356    }
357
358    /// Set the source directory for import resolution.
359    pub fn set_source_dir(&mut self, dir: &std::path::Path) {
360        self.source_dir = Some(dir.to_path_buf());
361    }
362
363    /// Set a global variable in the VM's environment.
364    pub fn set_global(&mut self, name: &str, value: VmValue) {
365        self.env.define(name, value, false);
366    }
367
368    /// Execute an import, reading and running the file's declarations.
369    fn execute_import<'a>(
370        &'a mut self,
371        path: &'a str,
372        selected_names: Option<&'a [String]>,
373    ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + 'a>> {
374        Box::pin(async move {
375            use std::path::PathBuf;
376
377            // Resolve the file path
378            let base = self
379                .source_dir
380                .clone()
381                .unwrap_or_else(|| PathBuf::from("."));
382            let mut file_path = base.join(path);
383
384            // Try with .harn extension if no extension
385            if !file_path.exists() && file_path.extension().is_none() {
386                file_path.set_extension("harn");
387            }
388
389            // Try .harn/packages/ fallback (then .burin/packages/ for compat)
390            if !file_path.exists() {
391                for pkg_dir in [".harn/packages", ".burin/packages"] {
392                    let pkg_path = base.join(pkg_dir).join(path);
393                    if pkg_path.exists() {
394                        file_path = if pkg_path.is_dir() {
395                            let lib = pkg_path.join("lib.harn");
396                            if lib.exists() {
397                                lib
398                            } else {
399                                pkg_path
400                            }
401                        } else {
402                            pkg_path
403                        };
404                        break;
405                    }
406                    let mut pkg_harn = pkg_path.clone();
407                    pkg_harn.set_extension("harn");
408                    if pkg_harn.exists() {
409                        file_path = pkg_harn;
410                        break;
411                    }
412                }
413            }
414
415            // Cycle detection
416            let canonical = file_path
417                .canonicalize()
418                .unwrap_or_else(|_| file_path.clone());
419            if self.imported_paths.contains(&canonical) {
420                return Ok(()); // already imported
421            }
422            self.imported_paths.push(canonical);
423
424            // Read, lex, parse
425            let source = std::fs::read_to_string(&file_path).map_err(|e| {
426                VmError::Runtime(format!(
427                    "Import error: cannot read '{}': {e}",
428                    file_path.display()
429                ))
430            })?;
431
432            let mut lexer = harn_lexer::Lexer::new(&source);
433            let tokens = lexer
434                .tokenize()
435                .map_err(|e| VmError::Runtime(format!("Import lex error: {e}")))?;
436            let mut parser = harn_parser::Parser::new(tokens);
437            let program = parser
438                .parse()
439                .map_err(|e| VmError::Runtime(format!("Import parse error: {e}")))?;
440
441            // Check if the module has any pub fn declarations
442            let has_pub = program
443                .iter()
444                .any(|n| matches!(&n.node, harn_parser::Node::FnDecl { is_pub: true, .. }));
445
446            // Execute top-level declarations from the imported file
447            for node in &program {
448                match &node.node {
449                    harn_parser::Node::FnDecl {
450                        name,
451                        params,
452                        body,
453                        is_pub,
454                        ..
455                    } => {
456                        // For selective imports: import any function that was explicitly named
457                        // For wildcard imports: if module has pub fns, only import pub ones;
458                        //   if no pub fns, import everything (backward compat)
459                        if selected_names.is_none() && has_pub && !is_pub {
460                            continue;
461                        }
462                        if let Some(names) = selected_names {
463                            if !names.contains(name) {
464                                continue;
465                            }
466                        }
467                        // Compile the function body into a closure and define it
468                        let mut compiler = crate::Compiler::new();
469                        let func_chunk = compiler
470                            .compile_fn_body(params, body)
471                            .map_err(|e| VmError::Runtime(format!("Import compile error: {e}")))?;
472                        let closure = VmClosure {
473                            func: func_chunk,
474                            env: self.env.clone(),
475                        };
476                        self.env
477                            .define(name, VmValue::Closure(Rc::new(closure)), false);
478                    }
479                    harn_parser::Node::ImportDecl { path: sub_path } => {
480                        // Handle nested imports - set source_dir relative to the imported file
481                        let old_dir = self.source_dir.clone();
482                        if let Some(parent) = file_path.parent() {
483                            self.source_dir = Some(parent.to_path_buf());
484                        }
485                        self.execute_import(sub_path, None).await?;
486                        self.source_dir = old_dir;
487                    }
488                    harn_parser::Node::SelectiveImport {
489                        names,
490                        path: sub_path,
491                    } => {
492                        let old_dir = self.source_dir.clone();
493                        if let Some(parent) = file_path.parent() {
494                            self.source_dir = Some(parent.to_path_buf());
495                        }
496                        self.execute_import(sub_path, Some(names)).await?;
497                        self.source_dir = old_dir;
498                    }
499                    _ => {} // Skip other top-level nodes (pipelines, enums, etc.)
500                }
501            }
502
503            Ok(())
504        })
505    }
506
507    /// Get the captured output.
508    pub fn output(&self) -> &str {
509        &self.output
510    }
511
512    /// Execute a compiled chunk.
513    pub async fn execute(&mut self, chunk: &Chunk) -> Result<VmValue, VmError> {
514        self.run_chunk(chunk).await
515    }
516
517    /// Convert a VmError into either a handled exception (returning Ok) or a propagated error.
518    fn handle_error(&mut self, error: VmError) -> Result<Option<VmValue>, VmError> {
519        // Extract the thrown value from the error
520        let thrown_value = match &error {
521            VmError::Thrown(v) => v.clone(),
522            other => VmValue::String(Rc::from(other.to_string())),
523        };
524
525        if let Some(handler) = self.exception_handlers.pop() {
526            // Check if this is a typed catch that doesn't match the thrown value
527            if !handler.error_type.is_empty() {
528                let matches = match &thrown_value {
529                    VmValue::EnumVariant { enum_name, .. } => *enum_name == handler.error_type,
530                    _ => false,
531                };
532                if !matches {
533                    // This handler doesn't match — try the next one
534                    return self.handle_error(error);
535                }
536            }
537
538            // Unwind call frames back to the handler's frame depth
539            while self.frames.len() > handler.frame_depth {
540                if let Some(frame) = self.frames.pop() {
541                    self.env = frame.saved_env;
542                }
543            }
544
545            // Clean up deadlines from unwound frames
546            while self
547                .deadlines
548                .last()
549                .is_some_and(|d| d.1 > handler.frame_depth)
550            {
551                self.deadlines.pop();
552            }
553
554            // Restore stack to handler's depth
555            self.stack.truncate(handler.stack_depth);
556
557            // Push the error value onto the stack (catch body can access it)
558            self.stack.push(thrown_value);
559
560            // Set the IP in the current frame to the catch handler
561            if let Some(frame) = self.frames.last_mut() {
562                frame.ip = handler.catch_ip;
563            }
564
565            Ok(None) // Continue execution
566        } else {
567            Err(error) // No handler, propagate
568        }
569    }
570
571    async fn run_chunk(&mut self, chunk: &Chunk) -> Result<VmValue, VmError> {
572        // Push initial frame
573        self.frames.push(CallFrame {
574            chunk: chunk.clone(),
575            ip: 0,
576            stack_base: self.stack.len(),
577            saved_env: self.env.clone(),
578            fn_name: String::new(),
579        });
580
581        loop {
582            // Check deadline before each instruction
583            if let Some(&(deadline, _)) = self.deadlines.last() {
584                if Instant::now() > deadline {
585                    self.deadlines.pop();
586                    let err = VmError::Thrown(VmValue::String(Rc::from("Deadline exceeded")));
587                    match self.handle_error(err) {
588                        Ok(None) => continue,
589                        Ok(Some(val)) => return Ok(val),
590                        Err(e) => return Err(e),
591                    }
592                }
593            }
594
595            // Get current frame
596            let frame = match self.frames.last_mut() {
597                Some(f) => f,
598                None => return Ok(self.stack.pop().unwrap_or(VmValue::Nil)),
599            };
600
601            // Check if we've reached end of chunk
602            if frame.ip >= frame.chunk.code.len() {
603                let val = self.stack.pop().unwrap_or(VmValue::Nil);
604                let popped_frame = self.frames.pop().unwrap();
605
606                if self.frames.is_empty() {
607                    // We're done with the top-level chunk
608                    return Ok(val);
609                } else {
610                    // Returning from a function call
611                    self.env = popped_frame.saved_env;
612                    self.stack.truncate(popped_frame.stack_base);
613                    self.stack.push(val);
614                    continue;
615                }
616            }
617
618            let op = frame.chunk.code[frame.ip];
619            frame.ip += 1;
620
621            match self.execute_op(op).await {
622                Ok(Some(val)) => return Ok(val),
623                Ok(None) => continue,
624                Err(VmError::Return(val)) => {
625                    // Pop the current frame
626                    if let Some(popped_frame) = self.frames.pop() {
627                        // Clean up exception handlers from the returned frame
628                        let current_depth = self.frames.len();
629                        self.exception_handlers
630                            .retain(|h| h.frame_depth <= current_depth);
631
632                        if self.frames.is_empty() {
633                            return Ok(val);
634                        }
635                        self.env = popped_frame.saved_env;
636                        self.stack.truncate(popped_frame.stack_base);
637                        self.stack.push(val);
638                    } else {
639                        return Ok(val);
640                    }
641                }
642                Err(e) => {
643                    match self.handle_error(e) {
644                        Ok(None) => continue, // Handler found, continue
645                        Ok(Some(val)) => return Ok(val),
646                        Err(e) => return Err(e), // No handler, propagate
647                    }
648                }
649            }
650        }
651    }
652
653    /// Execute a single opcode. Returns:
654    /// - Ok(None): continue execution
655    /// - Ok(Some(val)): return this value (top-level exit)
656    /// - Err(e): error occurred
657    async fn execute_op(&mut self, op: u8) -> Result<Option<VmValue>, VmError> {
658        // We need to borrow frame fields, but we also need &mut self for other ops.
659        // Strategy: read what we need from the frame first, then do the work.
660
661        if op == Op::Constant as u8 {
662            let frame = self.frames.last_mut().unwrap();
663            let idx = frame.chunk.read_u16(frame.ip) as usize;
664            frame.ip += 2;
665            let val = match &frame.chunk.constants[idx] {
666                Constant::Int(n) => VmValue::Int(*n),
667                Constant::Float(n) => VmValue::Float(*n),
668                Constant::String(s) => VmValue::String(Rc::from(s.as_str())),
669                Constant::Bool(b) => VmValue::Bool(*b),
670                Constant::Nil => VmValue::Nil,
671                Constant::Duration(ms) => VmValue::Duration(*ms),
672            };
673            self.stack.push(val);
674        } else if op == Op::Nil as u8 {
675            self.stack.push(VmValue::Nil);
676        } else if op == Op::True as u8 {
677            self.stack.push(VmValue::Bool(true));
678        } else if op == Op::False as u8 {
679            self.stack.push(VmValue::Bool(false));
680        } else if op == Op::GetVar as u8 {
681            let frame = self.frames.last_mut().unwrap();
682            let idx = frame.chunk.read_u16(frame.ip) as usize;
683            frame.ip += 2;
684            let name = match &frame.chunk.constants[idx] {
685                Constant::String(s) => s.clone(),
686                _ => return Err(VmError::TypeError("expected string constant".into())),
687            };
688            match self.env.get(&name) {
689                Some(val) => self.stack.push(val),
690                None => return Err(VmError::UndefinedVariable(name)),
691            }
692        } else if op == Op::DefLet as u8 {
693            let frame = self.frames.last_mut().unwrap();
694            let idx = frame.chunk.read_u16(frame.ip) as usize;
695            frame.ip += 2;
696            let name = Self::const_string(&frame.chunk.constants[idx])?;
697            let val = self.pop()?;
698            self.env.define(&name, val, false);
699        } else if op == Op::DefVar as u8 {
700            let frame = self.frames.last_mut().unwrap();
701            let idx = frame.chunk.read_u16(frame.ip) as usize;
702            frame.ip += 2;
703            let name = Self::const_string(&frame.chunk.constants[idx])?;
704            let val = self.pop()?;
705            self.env.define(&name, val, true);
706        } else if op == Op::SetVar as u8 {
707            let frame = self.frames.last_mut().unwrap();
708            let idx = frame.chunk.read_u16(frame.ip) as usize;
709            frame.ip += 2;
710            let name = Self::const_string(&frame.chunk.constants[idx])?;
711            let val = self.pop()?;
712            self.env.assign(&name, val)?;
713        } else if op == Op::Add as u8 {
714            let b = self.pop()?;
715            let a = self.pop()?;
716            self.stack.push(self.add(a, b));
717        } else if op == Op::Sub as u8 {
718            let b = self.pop()?;
719            let a = self.pop()?;
720            self.stack.push(self.sub(a, b));
721        } else if op == Op::Mul as u8 {
722            let b = self.pop()?;
723            let a = self.pop()?;
724            self.stack.push(self.mul(a, b));
725        } else if op == Op::Div as u8 {
726            let b = self.pop()?;
727            let a = self.pop()?;
728            self.stack.push(self.div(a, b)?);
729        } else if op == Op::Mod as u8 {
730            let b = self.pop()?;
731            let a = self.pop()?;
732            self.stack.push(self.modulo(a, b)?);
733        } else if op == Op::Negate as u8 {
734            let v = self.pop()?;
735            self.stack.push(match v {
736                VmValue::Int(n) => VmValue::Int(n.wrapping_neg()),
737                VmValue::Float(n) => VmValue::Float(-n),
738                _ => {
739                    return Err(VmError::Runtime(format!(
740                        "Cannot negate value of type {}",
741                        v.type_name()
742                    )))
743                }
744            });
745        } else if op == Op::Equal as u8 {
746            let b = self.pop()?;
747            let a = self.pop()?;
748            self.stack.push(VmValue::Bool(values_equal(&a, &b)));
749        } else if op == Op::NotEqual as u8 {
750            let b = self.pop()?;
751            let a = self.pop()?;
752            self.stack.push(VmValue::Bool(!values_equal(&a, &b)));
753        } else if op == Op::Less as u8 {
754            let b = self.pop()?;
755            let a = self.pop()?;
756            self.stack.push(VmValue::Bool(compare_values(&a, &b) < 0));
757        } else if op == Op::Greater as u8 {
758            let b = self.pop()?;
759            let a = self.pop()?;
760            self.stack.push(VmValue::Bool(compare_values(&a, &b) > 0));
761        } else if op == Op::LessEqual as u8 {
762            let b = self.pop()?;
763            let a = self.pop()?;
764            self.stack.push(VmValue::Bool(compare_values(&a, &b) <= 0));
765        } else if op == Op::GreaterEqual as u8 {
766            let b = self.pop()?;
767            let a = self.pop()?;
768            self.stack.push(VmValue::Bool(compare_values(&a, &b) >= 0));
769        } else if op == Op::Not as u8 {
770            let v = self.pop()?;
771            self.stack.push(VmValue::Bool(!v.is_truthy()));
772        } else if op == Op::Jump as u8 {
773            let frame = self.frames.last_mut().unwrap();
774            let target = frame.chunk.read_u16(frame.ip) as usize;
775            frame.ip = target;
776        } else if op == Op::JumpIfFalse as u8 {
777            let frame = self.frames.last_mut().unwrap();
778            let target = frame.chunk.read_u16(frame.ip) as usize;
779            frame.ip += 2;
780            let val = self.peek()?;
781            if !val.is_truthy() {
782                let frame = self.frames.last_mut().unwrap();
783                frame.ip = target;
784            }
785        } else if op == Op::JumpIfTrue as u8 {
786            let frame = self.frames.last_mut().unwrap();
787            let target = frame.chunk.read_u16(frame.ip) as usize;
788            frame.ip += 2;
789            let val = self.peek()?;
790            if val.is_truthy() {
791                let frame = self.frames.last_mut().unwrap();
792                frame.ip = target;
793            }
794        } else if op == Op::Pop as u8 {
795            self.pop()?;
796        } else if op == Op::Call as u8 {
797            let frame = self.frames.last_mut().unwrap();
798            let argc = frame.chunk.code[frame.ip] as usize;
799            frame.ip += 1;
800            // Clone the functions list so we don't borrow frame across call
801            let functions = frame.chunk.functions.clone();
802
803            // Arguments are on stack above the function name/value
804            let args: Vec<VmValue> = self.stack.split_off(self.stack.len().saturating_sub(argc));
805            let callee = self.pop()?;
806
807            match callee {
808                VmValue::String(name) => {
809                    if name.as_ref() == "await" {
810                        let task_id = args.first().and_then(|a| match a {
811                            VmValue::TaskHandle(id) => Some(id.clone()),
812                            _ => None,
813                        });
814                        if let Some(id) = task_id {
815                            if let Some(handle) = self.spawned_tasks.remove(&id) {
816                                let (result, task_output) = handle.await.map_err(|e| {
817                                    VmError::Runtime(format!("Task join error: {e}"))
818                                })??;
819                                self.output.push_str(&task_output);
820                                self.stack.push(result);
821                            } else {
822                                self.stack.push(VmValue::Nil);
823                            }
824                        } else {
825                            self.stack
826                                .push(args.into_iter().next().unwrap_or(VmValue::Nil));
827                        }
828                    } else if name.as_ref() == "cancel" {
829                        if let Some(VmValue::TaskHandle(id)) = args.first() {
830                            if let Some(handle) = self.spawned_tasks.remove(id) {
831                                handle.abort();
832                            }
833                        }
834                        self.stack.push(VmValue::Nil);
835                    } else if let Some(VmValue::Closure(closure)) = self.env.get(&name) {
836                        // Check closures in env
837                        self.push_closure_frame(&closure, &args, &functions)?;
838                        // Don't push result - frame will handle it on return
839                    } else if let Some(builtin) = self.builtins.get(name.as_ref()).cloned() {
840                        let result = builtin(&args, &mut self.output)?;
841                        self.stack.push(result);
842                    } else if let Some(async_builtin) =
843                        self.async_builtins.get(name.as_ref()).cloned()
844                    {
845                        let result = async_builtin(args).await?;
846                        self.stack.push(result);
847                    } else {
848                        return Err(VmError::UndefinedBuiltin(name.to_string()));
849                    }
850                }
851                VmValue::Closure(closure) => {
852                    self.push_closure_frame(&closure, &args, &functions)?;
853                }
854                _ => {
855                    return Err(VmError::TypeError(format!(
856                        "Cannot call {}",
857                        callee.display()
858                    )));
859                }
860            }
861        } else if op == Op::TailCall as u8 {
862            let frame = self.frames.last_mut().unwrap();
863            let argc = frame.chunk.code[frame.ip] as usize;
864            frame.ip += 1;
865
866            let args: Vec<VmValue> = self.stack.split_off(self.stack.len().saturating_sub(argc));
867            let callee = self.pop()?;
868
869            // Resolve the callee to a closure (or fall through to builtin)
870            let resolved_closure = match &callee {
871                VmValue::Closure(cl) => Some(Rc::clone(cl)),
872                VmValue::String(name) => {
873                    if let Some(VmValue::Closure(cl)) = self.env.get(name) {
874                        Some(cl)
875                    } else {
876                        None
877                    }
878                }
879                _ => None,
880            };
881
882            if let Some(closure) = resolved_closure {
883                // Tail call optimization: replace current frame instead of pushing.
884                // Pop the current frame and reuse its stack_base and saved_env.
885                let popped = self.frames.pop().unwrap();
886                let stack_base = popped.stack_base;
887                let parent_env = popped.saved_env;
888
889                // Clear this frame's stack data
890                self.stack.truncate(stack_base);
891
892                // Set up the callee's environment
893                let mut call_env = Self::merge_env_into_closure(&parent_env, &closure);
894                call_env.push_scope();
895                for (i, param) in closure.func.params.iter().enumerate() {
896                    let val = args.get(i).cloned().unwrap_or(VmValue::Nil);
897                    call_env.define(param, val, false);
898                }
899                self.env = call_env;
900
901                // Push replacement frame at the same stack depth
902                self.frames.push(CallFrame {
903                    chunk: closure.func.chunk.clone(),
904                    ip: 0,
905                    stack_base,
906                    saved_env: parent_env,
907                    fn_name: closure.func.name.clone(),
908                });
909                // Continue the loop — execution proceeds in the new frame
910            } else {
911                // Not a closure — fall back to regular call behavior for builtins.
912                match callee {
913                    VmValue::String(name) => {
914                        if let Some(builtin) = self.builtins.get(name.as_ref()).cloned() {
915                            let result = builtin(&args, &mut self.output)?;
916                            self.stack.push(result);
917                        } else if let Some(async_builtin) =
918                            self.async_builtins.get(name.as_ref()).cloned()
919                        {
920                            let result = async_builtin(args).await?;
921                            self.stack.push(result);
922                        } else {
923                            return Err(VmError::UndefinedBuiltin(name.to_string()));
924                        }
925                    }
926                    _ => {
927                        return Err(VmError::TypeError(format!(
928                            "Cannot call {}",
929                            callee.display()
930                        )));
931                    }
932                }
933                // Result is on stack; the following Return opcode will return it.
934            }
935        } else if op == Op::Return as u8 {
936            let val = self.pop().unwrap_or(VmValue::Nil);
937            return Err(VmError::Return(val));
938        } else if op == Op::Closure as u8 {
939            let frame = self.frames.last_mut().unwrap();
940            let fn_idx = frame.chunk.read_u16(frame.ip) as usize;
941            frame.ip += 2;
942            let func = frame.chunk.functions[fn_idx].clone();
943            let closure = VmClosure {
944                func,
945                env: self.env.clone(),
946            };
947            self.stack.push(VmValue::Closure(Rc::new(closure)));
948        } else if op == Op::BuildList as u8 {
949            let frame = self.frames.last_mut().unwrap();
950            let count = frame.chunk.read_u16(frame.ip) as usize;
951            frame.ip += 2;
952            let items = self.stack.split_off(self.stack.len().saturating_sub(count));
953            self.stack.push(VmValue::List(Rc::new(items)));
954        } else if op == Op::BuildDict as u8 {
955            let frame = self.frames.last_mut().unwrap();
956            let count = frame.chunk.read_u16(frame.ip) as usize;
957            frame.ip += 2;
958            let pairs = self
959                .stack
960                .split_off(self.stack.len().saturating_sub(count * 2));
961            let mut map = BTreeMap::new();
962            for pair in pairs.chunks(2) {
963                if pair.len() == 2 {
964                    let key = pair[0].display();
965                    map.insert(key, pair[1].clone());
966                }
967            }
968            self.stack.push(VmValue::Dict(Rc::new(map)));
969        } else if op == Op::Subscript as u8 {
970            let idx = self.pop()?;
971            let obj = self.pop()?;
972            let result = match (&obj, &idx) {
973                (VmValue::List(items), VmValue::Int(i)) => {
974                    if *i < 0 {
975                        let pos = items.len() as i64 + *i;
976                        if pos < 0 {
977                            VmValue::Nil
978                        } else {
979                            items.get(pos as usize).cloned().unwrap_or(VmValue::Nil)
980                        }
981                    } else {
982                        items.get(*i as usize).cloned().unwrap_or(VmValue::Nil)
983                    }
984                }
985                (VmValue::Dict(map), _) => map.get(&idx.display()).cloned().unwrap_or(VmValue::Nil),
986                (VmValue::String(s), VmValue::Int(i)) => {
987                    if *i < 0 {
988                        let pos = s.chars().count() as i64 + *i;
989                        if pos < 0 {
990                            VmValue::Nil
991                        } else {
992                            s.chars()
993                                .nth(pos as usize)
994                                .map(|c| VmValue::String(Rc::from(c.to_string())))
995                                .unwrap_or(VmValue::Nil)
996                        }
997                    } else {
998                        s.chars()
999                            .nth(*i as usize)
1000                            .map(|c| VmValue::String(Rc::from(c.to_string())))
1001                            .unwrap_or(VmValue::Nil)
1002                    }
1003                }
1004                _ => {
1005                    return Err(VmError::TypeError(format!(
1006                        "cannot index into {} with {}",
1007                        obj.type_name(),
1008                        idx.type_name()
1009                    )));
1010                }
1011            };
1012            self.stack.push(result);
1013        } else if op == Op::GetProperty as u8 {
1014            let frame = self.frames.last_mut().unwrap();
1015            let idx = frame.chunk.read_u16(frame.ip) as usize;
1016            frame.ip += 2;
1017            let name = Self::const_string(&frame.chunk.constants[idx])?;
1018            let obj = self.pop()?;
1019            let result = match &obj {
1020                VmValue::Dict(map) => map.get(&name).cloned().unwrap_or(VmValue::Nil),
1021                VmValue::List(items) => match name.as_str() {
1022                    "count" => VmValue::Int(items.len() as i64),
1023                    "empty" => VmValue::Bool(items.is_empty()),
1024                    "first" => items.first().cloned().unwrap_or(VmValue::Nil),
1025                    "last" => items.last().cloned().unwrap_or(VmValue::Nil),
1026                    _ => VmValue::Nil,
1027                },
1028                VmValue::String(s) => match name.as_str() {
1029                    "count" => VmValue::Int(s.chars().count() as i64),
1030                    "empty" => VmValue::Bool(s.is_empty()),
1031                    _ => VmValue::Nil,
1032                },
1033                VmValue::EnumVariant {
1034                    variant, fields, ..
1035                } => match name.as_str() {
1036                    "variant" => VmValue::String(Rc::from(variant.as_str())),
1037                    "fields" => VmValue::List(Rc::new(fields.clone())),
1038                    _ => VmValue::Nil,
1039                },
1040                VmValue::StructInstance { fields, .. } => {
1041                    fields.get(&name).cloned().unwrap_or(VmValue::Nil)
1042                }
1043                VmValue::Nil => {
1044                    return Err(VmError::TypeError(format!(
1045                        "cannot access property `{name}` on nil"
1046                    )));
1047                }
1048                _ => {
1049                    return Err(VmError::TypeError(format!(
1050                        "cannot access property `{name}` on {}",
1051                        obj.type_name()
1052                    )));
1053                }
1054            };
1055            self.stack.push(result);
1056        } else if op == Op::SetProperty as u8 {
1057            let frame = self.frames.last_mut().unwrap();
1058            let prop_idx = frame.chunk.read_u16(frame.ip) as usize;
1059            frame.ip += 2;
1060            let var_idx = frame.chunk.read_u16(frame.ip) as usize;
1061            frame.ip += 2;
1062            let prop_name = Self::const_string(&frame.chunk.constants[prop_idx])?;
1063            let var_name = Self::const_string(&frame.chunk.constants[var_idx])?;
1064            let new_value = self.pop()?;
1065            if let Some(obj) = self.env.get(&var_name) {
1066                match obj {
1067                    VmValue::Dict(map) => {
1068                        let mut new_map = (*map).clone();
1069                        new_map.insert(prop_name, new_value);
1070                        self.env
1071                            .assign(&var_name, VmValue::Dict(Rc::new(new_map)))?;
1072                    }
1073                    VmValue::StructInstance {
1074                        struct_name,
1075                        fields,
1076                    } => {
1077                        let mut new_fields = fields.clone();
1078                        new_fields.insert(prop_name, new_value);
1079                        self.env.assign(
1080                            &var_name,
1081                            VmValue::StructInstance {
1082                                struct_name,
1083                                fields: new_fields,
1084                            },
1085                        )?;
1086                    }
1087                    _ => {
1088                        return Err(VmError::TypeError(format!(
1089                            "cannot set property `{prop_name}` on {}",
1090                            obj.type_name()
1091                        )));
1092                    }
1093                }
1094            }
1095        } else if op == Op::SetSubscript as u8 {
1096            let frame = self.frames.last_mut().unwrap();
1097            let var_idx = frame.chunk.read_u16(frame.ip) as usize;
1098            frame.ip += 2;
1099            let var_name = Self::const_string(&frame.chunk.constants[var_idx])?;
1100            let index = self.pop()?;
1101            let new_value = self.pop()?;
1102            if let Some(obj) = self.env.get(&var_name) {
1103                match obj {
1104                    VmValue::List(items) => {
1105                        if let Some(i) = index.as_int() {
1106                            let mut new_items = (*items).clone();
1107                            let idx = if i < 0 {
1108                                (new_items.len() as i64 + i).max(0) as usize
1109                            } else {
1110                                i as usize
1111                            };
1112                            if idx < new_items.len() {
1113                                new_items[idx] = new_value;
1114                                self.env
1115                                    .assign(&var_name, VmValue::List(Rc::new(new_items)))?;
1116                            }
1117                        }
1118                    }
1119                    VmValue::Dict(map) => {
1120                        let key = index.display();
1121                        let mut new_map = (*map).clone();
1122                        new_map.insert(key, new_value);
1123                        self.env
1124                            .assign(&var_name, VmValue::Dict(Rc::new(new_map)))?;
1125                    }
1126                    _ => {}
1127                }
1128            }
1129        } else if op == Op::MethodCall as u8 {
1130            let frame = self.frames.last_mut().unwrap();
1131            let name_idx = frame.chunk.read_u16(frame.ip) as usize;
1132            frame.ip += 2;
1133            let argc = frame.chunk.code[frame.ip] as usize;
1134            frame.ip += 1;
1135            let method = Self::const_string(&frame.chunk.constants[name_idx])?;
1136            let functions = frame.chunk.functions.clone();
1137            let args: Vec<VmValue> = self.stack.split_off(self.stack.len().saturating_sub(argc));
1138            let obj = self.pop()?;
1139            let result = self.call_method(obj, &method, &args, &functions).await?;
1140            self.stack.push(result);
1141        } else if op == Op::Concat as u8 {
1142            let frame = self.frames.last_mut().unwrap();
1143            let count = frame.chunk.read_u16(frame.ip) as usize;
1144            frame.ip += 2;
1145            let parts = self.stack.split_off(self.stack.len().saturating_sub(count));
1146            let result: String = parts.iter().map(|p| p.display()).collect();
1147            self.stack.push(VmValue::String(Rc::from(result)));
1148        } else if op == Op::Pipe as u8 {
1149            let callable = self.pop()?;
1150            let value = self.pop()?;
1151            let functions = self.frames.last().unwrap().chunk.functions.clone();
1152            match callable {
1153                VmValue::Closure(closure) => {
1154                    self.push_closure_frame(&closure, &[value], &functions)?;
1155                }
1156                VmValue::String(name) => {
1157                    if let Some(VmValue::Closure(closure)) = self.env.get(&name) {
1158                        self.push_closure_frame(&closure, &[value], &functions)?;
1159                    } else if let Some(builtin) = self.builtins.get(name.as_ref()) {
1160                        let result = builtin(&[value], &mut self.output)?;
1161                        self.stack.push(result);
1162                    } else if let Some(async_builtin) =
1163                        self.async_builtins.get(name.as_ref()).cloned()
1164                    {
1165                        let result = async_builtin(vec![value]).await?;
1166                        self.stack.push(result);
1167                    } else {
1168                        return Err(VmError::UndefinedBuiltin(name.to_string()));
1169                    }
1170                }
1171                _ => {
1172                    return Err(VmError::TypeError(format!(
1173                        "cannot pipe into {}",
1174                        callable.type_name()
1175                    )));
1176                }
1177            }
1178        } else if op == Op::Dup as u8 {
1179            let val = self.peek()?.clone();
1180            self.stack.push(val);
1181        } else if op == Op::Swap as u8 {
1182            let len = self.stack.len();
1183            if len >= 2 {
1184                self.stack.swap(len - 1, len - 2);
1185            }
1186        } else if op == Op::IterInit as u8 {
1187            let iterable = self.pop()?;
1188            let items = match iterable {
1189                VmValue::List(items) => (*items).clone(),
1190                VmValue::Dict(map) => map
1191                    .iter()
1192                    .map(|(k, v)| {
1193                        VmValue::Dict(Rc::new(BTreeMap::from([
1194                            ("key".to_string(), VmValue::String(Rc::from(k.as_str()))),
1195                            ("value".to_string(), v.clone()),
1196                        ])))
1197                    })
1198                    .collect(),
1199                _ => Vec::new(),
1200            };
1201            self.iterators.push((items, 0));
1202        } else if op == Op::IterNext as u8 {
1203            let frame = self.frames.last_mut().unwrap();
1204            let target = frame.chunk.read_u16(frame.ip) as usize;
1205            frame.ip += 2;
1206            if let Some((items, idx)) = self.iterators.last_mut() {
1207                if *idx < items.len() {
1208                    let item = items[*idx].clone();
1209                    *idx += 1;
1210                    self.stack.push(item);
1211                } else {
1212                    self.iterators.pop();
1213                    let frame = self.frames.last_mut().unwrap();
1214                    frame.ip = target;
1215                }
1216            } else {
1217                let frame = self.frames.last_mut().unwrap();
1218                frame.ip = target;
1219            }
1220        } else if op == Op::PopIterator as u8 {
1221            self.iterators.pop();
1222        } else if op == Op::Throw as u8 {
1223            let val = self.pop()?;
1224            return Err(VmError::Thrown(val));
1225        } else if op == Op::TryCatchSetup as u8 {
1226            let frame = self.frames.last_mut().unwrap();
1227            let catch_offset = frame.chunk.read_u16(frame.ip) as usize;
1228            frame.ip += 2;
1229            // Read the error type name index (extra u16)
1230            let type_idx = frame.chunk.read_u16(frame.ip) as usize;
1231            frame.ip += 2;
1232            let error_type = match &frame.chunk.constants[type_idx] {
1233                Constant::String(s) => s.clone(),
1234                _ => String::new(),
1235            };
1236            self.exception_handlers.push(ExceptionHandler {
1237                catch_ip: catch_offset,
1238                stack_depth: self.stack.len(),
1239                frame_depth: self.frames.len(),
1240                error_type,
1241            });
1242        } else if op == Op::PopHandler as u8 {
1243            self.exception_handlers.pop();
1244        } else if op == Op::Parallel as u8 {
1245            let closure = self.pop()?;
1246            let count_val = self.pop()?;
1247            let count = match &count_val {
1248                VmValue::Int(n) => (*n).max(0) as usize,
1249                _ => 0,
1250            };
1251            if let VmValue::Closure(closure) = closure {
1252                let mut handles = Vec::with_capacity(count);
1253                for i in 0..count {
1254                    let mut child = self.child_vm();
1255                    let closure = closure.clone();
1256                    handles.push(tokio::task::spawn_local(async move {
1257                        let result = child
1258                            .call_closure(&closure, &[VmValue::Int(i as i64)], &[])
1259                            .await?;
1260                        Ok((result, std::mem::take(&mut child.output)))
1261                    }));
1262                }
1263                let mut results = vec![VmValue::Nil; count];
1264                for (i, handle) in handles.into_iter().enumerate() {
1265                    let (val, task_output): (VmValue, String) = handle
1266                        .await
1267                        .map_err(|e| VmError::Runtime(format!("Parallel task error: {e}")))??;
1268                    self.output.push_str(&task_output);
1269                    results[i] = val;
1270                }
1271                self.stack.push(VmValue::List(Rc::new(results)));
1272            } else {
1273                self.stack.push(VmValue::Nil);
1274            }
1275        } else if op == Op::ParallelMap as u8 {
1276            let closure = self.pop()?;
1277            let list_val = self.pop()?;
1278            match (&list_val, &closure) {
1279                (VmValue::List(items), VmValue::Closure(closure)) => {
1280                    let len = items.len();
1281                    let mut handles = Vec::with_capacity(len);
1282                    for item in items.iter() {
1283                        let mut child = self.child_vm();
1284                        let closure = closure.clone();
1285                        let item = item.clone();
1286                        handles.push(tokio::task::spawn_local(async move {
1287                            let result = child.call_closure(&closure, &[item], &[]).await?;
1288                            Ok((result, std::mem::take(&mut child.output)))
1289                        }));
1290                    }
1291                    let mut results = Vec::with_capacity(len);
1292                    for handle in handles {
1293                        let (val, task_output): (VmValue, String) = handle
1294                            .await
1295                            .map_err(|e| VmError::Runtime(format!("Parallel map error: {e}")))??;
1296                        self.output.push_str(&task_output);
1297                        results.push(val);
1298                    }
1299                    self.stack.push(VmValue::List(Rc::new(results)));
1300                }
1301                _ => self.stack.push(VmValue::Nil),
1302            }
1303        } else if op == Op::Spawn as u8 {
1304            let closure = self.pop()?;
1305            if let VmValue::Closure(closure) = closure {
1306                self.task_counter += 1;
1307                let task_id = format!("vm_task_{}", self.task_counter);
1308                let mut child = self.child_vm();
1309                let handle = tokio::task::spawn_local(async move {
1310                    let result = child.call_closure(&closure, &[], &[]).await?;
1311                    Ok((result, std::mem::take(&mut child.output)))
1312                });
1313                self.spawned_tasks.insert(task_id.clone(), handle);
1314                self.stack.push(VmValue::TaskHandle(task_id));
1315            } else {
1316                self.stack.push(VmValue::Nil);
1317            }
1318        } else if op == Op::Import as u8 {
1319            let frame = self.frames.last_mut().unwrap();
1320            let path_idx = frame.chunk.read_u16(frame.ip) as usize;
1321            frame.ip += 2;
1322            let import_path = Self::const_string(&frame.chunk.constants[path_idx])?;
1323            self.execute_import(&import_path, None).await?;
1324        } else if op == Op::SelectiveImport as u8 {
1325            let frame = self.frames.last_mut().unwrap();
1326            let path_idx = frame.chunk.read_u16(frame.ip) as usize;
1327            frame.ip += 2;
1328            let names_idx = frame.chunk.read_u16(frame.ip) as usize;
1329            frame.ip += 2;
1330            let import_path = Self::const_string(&frame.chunk.constants[path_idx])?;
1331            let names_str = Self::const_string(&frame.chunk.constants[names_idx])?;
1332            let names: Vec<String> = names_str.split(',').map(|s| s.to_string()).collect();
1333            self.execute_import(&import_path, Some(&names)).await?;
1334        } else if op == Op::DeadlineSetup as u8 {
1335            let dur_val = self.pop()?;
1336            let ms = match &dur_val {
1337                VmValue::Duration(ms) => *ms,
1338                VmValue::Int(n) => (*n).max(0) as u64,
1339                _ => 30_000,
1340            };
1341            let deadline = Instant::now() + std::time::Duration::from_millis(ms);
1342            self.deadlines.push((deadline, self.frames.len()));
1343        } else if op == Op::DeadlineEnd as u8 {
1344            self.deadlines.pop();
1345        } else if op == Op::BuildEnum as u8 {
1346            let frame = self.frames.last_mut().unwrap();
1347            let enum_idx = frame.chunk.read_u16(frame.ip) as usize;
1348            frame.ip += 2;
1349            let variant_idx = frame.chunk.read_u16(frame.ip) as usize;
1350            frame.ip += 2;
1351            let field_count = frame.chunk.read_u16(frame.ip) as usize;
1352            frame.ip += 2;
1353            let enum_name = Self::const_string(&frame.chunk.constants[enum_idx])?;
1354            let variant = Self::const_string(&frame.chunk.constants[variant_idx])?;
1355            let fields = self
1356                .stack
1357                .split_off(self.stack.len().saturating_sub(field_count));
1358            self.stack.push(VmValue::EnumVariant {
1359                enum_name,
1360                variant,
1361                fields,
1362            });
1363        } else if op == Op::MatchEnum as u8 {
1364            let frame = self.frames.last_mut().unwrap();
1365            let enum_idx = frame.chunk.read_u16(frame.ip) as usize;
1366            frame.ip += 2;
1367            let variant_idx = frame.chunk.read_u16(frame.ip) as usize;
1368            frame.ip += 2;
1369            let enum_name = Self::const_string(&frame.chunk.constants[enum_idx])?;
1370            let variant_name = Self::const_string(&frame.chunk.constants[variant_idx])?;
1371            let val = self.pop()?;
1372            let matches = match &val {
1373                VmValue::EnumVariant {
1374                    enum_name: en,
1375                    variant: vn,
1376                    ..
1377                } => *en == enum_name && *vn == variant_name,
1378                _ => false,
1379            };
1380            // Push the value back (we only peeked conceptually), then push the bool
1381            self.stack.push(val);
1382            self.stack.push(VmValue::Bool(matches));
1383        } else {
1384            return Err(VmError::InvalidInstruction(op));
1385        }
1386
1387        Ok(None)
1388    }
1389
1390    const MAX_FRAMES: usize = 512;
1391
1392    /// Merge the caller's env into a closure's captured env for function calls.
1393    fn merge_env_into_closure(caller_env: &VmEnv, closure: &VmClosure) -> VmEnv {
1394        let mut call_env = closure.env.clone();
1395        for scope in &caller_env.scopes {
1396            for (name, (val, mutable)) in &scope.vars {
1397                if call_env.get(name).is_none() {
1398                    call_env.define(name, val.clone(), *mutable);
1399                }
1400            }
1401        }
1402        call_env
1403    }
1404
1405    /// Push a new call frame for a closure invocation.
1406    fn push_closure_frame(
1407        &mut self,
1408        closure: &VmClosure,
1409        args: &[VmValue],
1410        _parent_functions: &[CompiledFunction],
1411    ) -> Result<(), VmError> {
1412        if self.frames.len() >= Self::MAX_FRAMES {
1413            return Err(VmError::StackOverflow);
1414        }
1415        let saved_env = self.env.clone();
1416
1417        let mut call_env = Self::merge_env_into_closure(&saved_env, closure);
1418        call_env.push_scope();
1419
1420        for (i, param) in closure.func.params.iter().enumerate() {
1421            let val = args.get(i).cloned().unwrap_or(VmValue::Nil);
1422            call_env.define(param, val, false);
1423        }
1424
1425        self.env = call_env;
1426
1427        self.frames.push(CallFrame {
1428            chunk: closure.func.chunk.clone(),
1429            ip: 0,
1430            stack_base: self.stack.len(),
1431            saved_env,
1432            fn_name: closure.func.name.clone(),
1433        });
1434
1435        Ok(())
1436    }
1437
1438    fn pop(&mut self) -> Result<VmValue, VmError> {
1439        self.stack.pop().ok_or(VmError::StackUnderflow)
1440    }
1441
1442    fn peek(&self) -> Result<&VmValue, VmError> {
1443        self.stack.last().ok_or(VmError::StackUnderflow)
1444    }
1445
1446    fn const_string(c: &Constant) -> Result<String, VmError> {
1447        match c {
1448            Constant::String(s) => Ok(s.clone()),
1449            _ => Err(VmError::TypeError("expected string constant".into())),
1450        }
1451    }
1452
1453    /// Call a closure (used by method calls like .map/.filter etc.)
1454    /// Uses recursive execution for simplicity in method dispatch.
1455    fn call_closure<'a>(
1456        &'a mut self,
1457        closure: &'a VmClosure,
1458        args: &'a [VmValue],
1459        _parent_functions: &'a [CompiledFunction],
1460    ) -> Pin<Box<dyn Future<Output = Result<VmValue, VmError>> + 'a>> {
1461        Box::pin(async move {
1462            let saved_env = self.env.clone();
1463            let saved_frames = std::mem::take(&mut self.frames);
1464            let saved_handlers = std::mem::take(&mut self.exception_handlers);
1465            let saved_iterators = std::mem::take(&mut self.iterators);
1466            let saved_deadlines = std::mem::take(&mut self.deadlines);
1467
1468            let mut call_env = Self::merge_env_into_closure(&saved_env, closure);
1469            call_env.push_scope();
1470
1471            for (i, param) in closure.func.params.iter().enumerate() {
1472                let val = args.get(i).cloned().unwrap_or(VmValue::Nil);
1473                call_env.define(param, val, false);
1474            }
1475
1476            self.env = call_env;
1477            let result = self.run_chunk(&closure.func.chunk).await;
1478
1479            self.env = saved_env;
1480            self.frames = saved_frames;
1481            self.exception_handlers = saved_handlers;
1482            self.iterators = saved_iterators;
1483            self.deadlines = saved_deadlines;
1484
1485            result
1486        })
1487    }
1488
1489    fn call_method<'a>(
1490        &'a mut self,
1491        obj: VmValue,
1492        method: &'a str,
1493        args: &'a [VmValue],
1494        functions: &'a [CompiledFunction],
1495    ) -> Pin<Box<dyn Future<Output = Result<VmValue, VmError>> + 'a>> {
1496        Box::pin(async move {
1497            match &obj {
1498                VmValue::String(s) => match method {
1499                    "count" => Ok(VmValue::Int(s.chars().count() as i64)),
1500                    "empty" => Ok(VmValue::Bool(s.is_empty())),
1501                    "contains" => Ok(VmValue::Bool(
1502                        s.contains(&*args.first().map(|a| a.display()).unwrap_or_default()),
1503                    )),
1504                    "replace" if args.len() >= 2 => Ok(VmValue::String(Rc::from(
1505                        s.replace(&args[0].display(), &args[1].display()),
1506                    ))),
1507                    "split" => {
1508                        let sep = args.first().map(|a| a.display()).unwrap_or(",".into());
1509                        Ok(VmValue::List(Rc::new(
1510                            s.split(&*sep)
1511                                .map(|p| VmValue::String(Rc::from(p)))
1512                                .collect(),
1513                        )))
1514                    }
1515                    "trim" => Ok(VmValue::String(Rc::from(s.trim()))),
1516                    "starts_with" => Ok(VmValue::Bool(
1517                        s.starts_with(&*args.first().map(|a| a.display()).unwrap_or_default()),
1518                    )),
1519                    "ends_with" => Ok(VmValue::Bool(
1520                        s.ends_with(&*args.first().map(|a| a.display()).unwrap_or_default()),
1521                    )),
1522                    "lowercase" => Ok(VmValue::String(Rc::from(s.to_lowercase()))),
1523                    "uppercase" => Ok(VmValue::String(Rc::from(s.to_uppercase()))),
1524                    "substring" => {
1525                        let start = args.first().and_then(|a| a.as_int()).unwrap_or(0);
1526                        let len = s.chars().count() as i64;
1527                        let start = start.max(0).min(len) as usize;
1528                        let end =
1529                            args.get(1).and_then(|a| a.as_int()).unwrap_or(len).min(len) as usize;
1530                        let end = end.max(start);
1531                        let substr: String = s.chars().skip(start).take(end - start).collect();
1532                        Ok(VmValue::String(Rc::from(substr)))
1533                    }
1534                    "index_of" => {
1535                        let needle = args.first().map(|a| a.display()).unwrap_or_default();
1536                        Ok(VmValue::Int(
1537                            s.find(&needle).map(|i| i as i64).unwrap_or(-1),
1538                        ))
1539                    }
1540                    "chars" => Ok(VmValue::List(Rc::new(
1541                        s.chars()
1542                            .map(|c| VmValue::String(Rc::from(c.to_string())))
1543                            .collect(),
1544                    ))),
1545                    "repeat" => {
1546                        let n = args.first().and_then(|a| a.as_int()).unwrap_or(1);
1547                        Ok(VmValue::String(Rc::from(s.repeat(n.max(0) as usize))))
1548                    }
1549                    "reverse" => Ok(VmValue::String(Rc::from(
1550                        s.chars().rev().collect::<String>(),
1551                    ))),
1552                    "pad_left" => {
1553                        let width = args.first().and_then(|a| a.as_int()).unwrap_or(0) as usize;
1554                        let pad_char = args
1555                            .get(1)
1556                            .map(|a| a.display())
1557                            .and_then(|s| s.chars().next())
1558                            .unwrap_or(' ');
1559                        let current_len = s.chars().count();
1560                        if current_len >= width {
1561                            Ok(VmValue::String(Rc::clone(s)))
1562                        } else {
1563                            let padding: String =
1564                                std::iter::repeat_n(pad_char, width - current_len).collect();
1565                            Ok(VmValue::String(Rc::from(format!("{padding}{s}"))))
1566                        }
1567                    }
1568                    "pad_right" => {
1569                        let width = args.first().and_then(|a| a.as_int()).unwrap_or(0) as usize;
1570                        let pad_char = args
1571                            .get(1)
1572                            .map(|a| a.display())
1573                            .and_then(|s| s.chars().next())
1574                            .unwrap_or(' ');
1575                        let current_len = s.chars().count();
1576                        if current_len >= width {
1577                            Ok(VmValue::String(Rc::clone(s)))
1578                        } else {
1579                            let padding: String =
1580                                std::iter::repeat_n(pad_char, width - current_len).collect();
1581                            Ok(VmValue::String(Rc::from(format!("{s}{padding}"))))
1582                        }
1583                    }
1584                    _ => Ok(VmValue::Nil),
1585                },
1586                VmValue::List(items) => match method {
1587                    "count" => Ok(VmValue::Int(items.len() as i64)),
1588                    "empty" => Ok(VmValue::Bool(items.is_empty())),
1589                    "map" => {
1590                        if let Some(VmValue::Closure(closure)) = args.first() {
1591                            let mut results = Vec::new();
1592                            for item in items.iter() {
1593                                results.push(
1594                                    self.call_closure(closure, &[item.clone()], functions)
1595                                        .await?,
1596                                );
1597                            }
1598                            Ok(VmValue::List(Rc::new(results)))
1599                        } else {
1600                            Ok(VmValue::Nil)
1601                        }
1602                    }
1603                    "filter" => {
1604                        if let Some(VmValue::Closure(closure)) = args.first() {
1605                            let mut results = Vec::new();
1606                            for item in items.iter() {
1607                                let result = self
1608                                    .call_closure(closure, &[item.clone()], functions)
1609                                    .await?;
1610                                if result.is_truthy() {
1611                                    results.push(item.clone());
1612                                }
1613                            }
1614                            Ok(VmValue::List(Rc::new(results)))
1615                        } else {
1616                            Ok(VmValue::Nil)
1617                        }
1618                    }
1619                    "reduce" => {
1620                        if args.len() >= 2 {
1621                            if let VmValue::Closure(closure) = &args[1] {
1622                                let mut acc = args[0].clone();
1623                                for item in items.iter() {
1624                                    acc = self
1625                                        .call_closure(closure, &[acc, item.clone()], functions)
1626                                        .await?;
1627                                }
1628                                return Ok(acc);
1629                            }
1630                        }
1631                        Ok(VmValue::Nil)
1632                    }
1633                    "find" => {
1634                        if let Some(VmValue::Closure(closure)) = args.first() {
1635                            for item in items.iter() {
1636                                let result = self
1637                                    .call_closure(closure, &[item.clone()], functions)
1638                                    .await?;
1639                                if result.is_truthy() {
1640                                    return Ok(item.clone());
1641                                }
1642                            }
1643                        }
1644                        Ok(VmValue::Nil)
1645                    }
1646                    "any" => {
1647                        if let Some(VmValue::Closure(closure)) = args.first() {
1648                            for item in items.iter() {
1649                                let result = self
1650                                    .call_closure(closure, &[item.clone()], functions)
1651                                    .await?;
1652                                if result.is_truthy() {
1653                                    return Ok(VmValue::Bool(true));
1654                                }
1655                            }
1656                            Ok(VmValue::Bool(false))
1657                        } else {
1658                            Ok(VmValue::Bool(false))
1659                        }
1660                    }
1661                    "all" => {
1662                        if let Some(VmValue::Closure(closure)) = args.first() {
1663                            for item in items.iter() {
1664                                let result = self
1665                                    .call_closure(closure, &[item.clone()], functions)
1666                                    .await?;
1667                                if !result.is_truthy() {
1668                                    return Ok(VmValue::Bool(false));
1669                                }
1670                            }
1671                            Ok(VmValue::Bool(true))
1672                        } else {
1673                            Ok(VmValue::Bool(true))
1674                        }
1675                    }
1676                    "flat_map" => {
1677                        if let Some(VmValue::Closure(closure)) = args.first() {
1678                            let mut results = Vec::new();
1679                            for item in items.iter() {
1680                                let result = self
1681                                    .call_closure(closure, &[item.clone()], functions)
1682                                    .await?;
1683                                if let VmValue::List(inner) = result {
1684                                    results.extend(inner.iter().cloned());
1685                                } else {
1686                                    results.push(result);
1687                                }
1688                            }
1689                            Ok(VmValue::List(Rc::new(results)))
1690                        } else {
1691                            Ok(VmValue::Nil)
1692                        }
1693                    }
1694                    "sort" => {
1695                        let mut sorted: Vec<VmValue> = items.iter().cloned().collect();
1696                        sorted.sort_by(|a, b| compare_values(a, b).cmp(&0));
1697                        Ok(VmValue::List(Rc::new(sorted)))
1698                    }
1699                    "sort_by" => {
1700                        if let Some(VmValue::Closure(closure)) = args.first() {
1701                            let mut keyed: Vec<(VmValue, VmValue)> = Vec::new();
1702                            for item in items.iter() {
1703                                let key = self
1704                                    .call_closure(closure, &[item.clone()], functions)
1705                                    .await?;
1706                                keyed.push((item.clone(), key));
1707                            }
1708                            keyed.sort_by(|(_, ka), (_, kb)| compare_values(ka, kb).cmp(&0));
1709                            Ok(VmValue::List(Rc::new(
1710                                keyed.into_iter().map(|(v, _)| v).collect(),
1711                            )))
1712                        } else {
1713                            Ok(VmValue::Nil)
1714                        }
1715                    }
1716                    "reverse" => {
1717                        let mut rev: Vec<VmValue> = items.iter().cloned().collect();
1718                        rev.reverse();
1719                        Ok(VmValue::List(Rc::new(rev)))
1720                    }
1721                    "join" => {
1722                        let sep = if args.is_empty() {
1723                            String::new()
1724                        } else {
1725                            args[0].display()
1726                        };
1727                        let joined: String = items
1728                            .iter()
1729                            .map(|v| v.display())
1730                            .collect::<Vec<_>>()
1731                            .join(&sep);
1732                        Ok(VmValue::String(Rc::from(joined)))
1733                    }
1734                    "contains" => {
1735                        let needle = args.first().unwrap_or(&VmValue::Nil);
1736                        Ok(VmValue::Bool(items.iter().any(|v| values_equal(v, needle))))
1737                    }
1738                    "index_of" => {
1739                        let needle = args.first().unwrap_or(&VmValue::Nil);
1740                        let idx = items.iter().position(|v| values_equal(v, needle));
1741                        Ok(VmValue::Int(idx.map(|i| i as i64).unwrap_or(-1)))
1742                    }
1743                    "enumerate" => {
1744                        let result: Vec<VmValue> = items
1745                            .iter()
1746                            .enumerate()
1747                            .map(|(i, v)| {
1748                                VmValue::Dict(Rc::new(BTreeMap::from([
1749                                    ("index".to_string(), VmValue::Int(i as i64)),
1750                                    ("value".to_string(), v.clone()),
1751                                ])))
1752                            })
1753                            .collect();
1754                        Ok(VmValue::List(Rc::new(result)))
1755                    }
1756                    "zip" => {
1757                        if let Some(VmValue::List(other)) = args.first() {
1758                            let result: Vec<VmValue> = items
1759                                .iter()
1760                                .zip(other.iter())
1761                                .map(|(a, b)| VmValue::List(Rc::new(vec![a.clone(), b.clone()])))
1762                                .collect();
1763                            Ok(VmValue::List(Rc::new(result)))
1764                        } else {
1765                            Ok(VmValue::List(Rc::new(Vec::new())))
1766                        }
1767                    }
1768                    "slice" => {
1769                        let len = items.len() as i64;
1770                        let start_raw = args.first().and_then(|a| a.as_int()).unwrap_or(0);
1771                        let start = if start_raw < 0 {
1772                            (len + start_raw).max(0) as usize
1773                        } else {
1774                            (start_raw.min(len)) as usize
1775                        };
1776                        let end = if args.len() > 1 {
1777                            let end_raw = args[1].as_int().unwrap_or(len);
1778                            if end_raw < 0 {
1779                                (len + end_raw).max(0) as usize
1780                            } else {
1781                                (end_raw.min(len)) as usize
1782                            }
1783                        } else {
1784                            len as usize
1785                        };
1786                        let end = end.max(start);
1787                        Ok(VmValue::List(Rc::new(items[start..end].to_vec())))
1788                    }
1789                    "unique" => {
1790                        let mut seen: Vec<VmValue> = Vec::new();
1791                        let mut result = Vec::new();
1792                        for item in items.iter() {
1793                            if !seen.iter().any(|s| values_equal(s, item)) {
1794                                seen.push(item.clone());
1795                                result.push(item.clone());
1796                            }
1797                        }
1798                        Ok(VmValue::List(Rc::new(result)))
1799                    }
1800                    "take" => {
1801                        let n = args.first().and_then(|a| a.as_int()).unwrap_or(0).max(0) as usize;
1802                        Ok(VmValue::List(Rc::new(
1803                            items.iter().take(n).cloned().collect(),
1804                        )))
1805                    }
1806                    "skip" => {
1807                        let n = args.first().and_then(|a| a.as_int()).unwrap_or(0).max(0) as usize;
1808                        Ok(VmValue::List(Rc::new(
1809                            items.iter().skip(n).cloned().collect(),
1810                        )))
1811                    }
1812                    "sum" => {
1813                        let mut int_sum: i64 = 0;
1814                        let mut has_float = false;
1815                        let mut float_sum: f64 = 0.0;
1816                        for item in items.iter() {
1817                            match item {
1818                                VmValue::Int(n) => {
1819                                    int_sum = int_sum.wrapping_add(*n);
1820                                    float_sum += *n as f64;
1821                                }
1822                                VmValue::Float(n) => {
1823                                    has_float = true;
1824                                    float_sum += n;
1825                                }
1826                                _ => {}
1827                            }
1828                        }
1829                        if has_float {
1830                            Ok(VmValue::Float(float_sum))
1831                        } else {
1832                            Ok(VmValue::Int(int_sum))
1833                        }
1834                    }
1835                    "min" => {
1836                        if items.is_empty() {
1837                            return Ok(VmValue::Nil);
1838                        }
1839                        let mut min_val = items[0].clone();
1840                        for item in &items[1..] {
1841                            if compare_values(item, &min_val) < 0 {
1842                                min_val = item.clone();
1843                            }
1844                        }
1845                        Ok(min_val)
1846                    }
1847                    "max" => {
1848                        if items.is_empty() {
1849                            return Ok(VmValue::Nil);
1850                        }
1851                        let mut max_val = items[0].clone();
1852                        for item in &items[1..] {
1853                            if compare_values(item, &max_val) > 0 {
1854                                max_val = item.clone();
1855                            }
1856                        }
1857                        Ok(max_val)
1858                    }
1859                    "flatten" => {
1860                        let mut result = Vec::new();
1861                        for item in items.iter() {
1862                            if let VmValue::List(inner) = item {
1863                                result.extend(inner.iter().cloned());
1864                            } else {
1865                                result.push(item.clone());
1866                            }
1867                        }
1868                        Ok(VmValue::List(Rc::new(result)))
1869                    }
1870                    "push" => {
1871                        let mut new_list: Vec<VmValue> = items.iter().cloned().collect();
1872                        if let Some(item) = args.first() {
1873                            new_list.push(item.clone());
1874                        }
1875                        Ok(VmValue::List(Rc::new(new_list)))
1876                    }
1877                    "pop" => {
1878                        let mut new_list: Vec<VmValue> = items.iter().cloned().collect();
1879                        new_list.pop();
1880                        Ok(VmValue::List(Rc::new(new_list)))
1881                    }
1882                    _ => Ok(VmValue::Nil),
1883                },
1884                VmValue::Dict(map) => match method {
1885                    "keys" => Ok(VmValue::List(Rc::new(
1886                        map.keys()
1887                            .map(|k| VmValue::String(Rc::from(k.as_str())))
1888                            .collect(),
1889                    ))),
1890                    "values" => Ok(VmValue::List(Rc::new(map.values().cloned().collect()))),
1891                    "entries" => Ok(VmValue::List(Rc::new(
1892                        map.iter()
1893                            .map(|(k, v)| {
1894                                VmValue::Dict(Rc::new(BTreeMap::from([
1895                                    ("key".to_string(), VmValue::String(Rc::from(k.as_str()))),
1896                                    ("value".to_string(), v.clone()),
1897                                ])))
1898                            })
1899                            .collect(),
1900                    ))),
1901                    "count" => Ok(VmValue::Int(map.len() as i64)),
1902                    "has" => Ok(VmValue::Bool(map.contains_key(
1903                        &args.first().map(|a| a.display()).unwrap_or_default(),
1904                    ))),
1905                    "merge" => {
1906                        if let Some(VmValue::Dict(other)) = args.first() {
1907                            let mut result = (**map).clone();
1908                            result.extend(other.iter().map(|(k, v)| (k.clone(), v.clone())));
1909                            Ok(VmValue::Dict(Rc::new(result)))
1910                        } else {
1911                            Ok(VmValue::Dict(Rc::clone(map)))
1912                        }
1913                    }
1914                    "map_values" => {
1915                        if let Some(VmValue::Closure(closure)) = args.first() {
1916                            let mut result = BTreeMap::new();
1917                            for (k, v) in map.iter() {
1918                                let mapped =
1919                                    self.call_closure(closure, &[v.clone()], functions).await?;
1920                                result.insert(k.clone(), mapped);
1921                            }
1922                            Ok(VmValue::Dict(Rc::new(result)))
1923                        } else {
1924                            Ok(VmValue::Nil)
1925                        }
1926                    }
1927                    "filter" => {
1928                        if let Some(VmValue::Closure(closure)) = args.first() {
1929                            let mut result = BTreeMap::new();
1930                            for (k, v) in map.iter() {
1931                                let keep =
1932                                    self.call_closure(closure, &[v.clone()], functions).await?;
1933                                if keep.is_truthy() {
1934                                    result.insert(k.clone(), v.clone());
1935                                }
1936                            }
1937                            Ok(VmValue::Dict(Rc::new(result)))
1938                        } else {
1939                            Ok(VmValue::Nil)
1940                        }
1941                    }
1942                    "remove" => {
1943                        let key = args.first().map(|a| a.display()).unwrap_or_default();
1944                        let mut result = (**map).clone();
1945                        result.remove(&key);
1946                        Ok(VmValue::Dict(Rc::new(result)))
1947                    }
1948                    "get" => {
1949                        let key = args.first().map(|a| a.display()).unwrap_or_default();
1950                        let default = args.get(1).cloned().unwrap_or(VmValue::Nil);
1951                        Ok(map.get(&key).cloned().unwrap_or(default))
1952                    }
1953                    _ => Ok(VmValue::Nil),
1954                },
1955                _ => Ok(VmValue::Nil),
1956            }
1957        })
1958    }
1959
1960    // --- Arithmetic helpers ---
1961
1962    fn add(&self, a: VmValue, b: VmValue) -> VmValue {
1963        match (&a, &b) {
1964            (VmValue::Int(x), VmValue::Int(y)) => VmValue::Int(x.wrapping_add(*y)),
1965            (VmValue::Float(x), VmValue::Float(y)) => VmValue::Float(x + y),
1966            (VmValue::Int(x), VmValue::Float(y)) => VmValue::Float(*x as f64 + y),
1967            (VmValue::Float(x), VmValue::Int(y)) => VmValue::Float(x + *y as f64),
1968            (VmValue::String(x), _) => VmValue::String(Rc::from(format!("{x}{}", b.display()))),
1969            (VmValue::List(x), VmValue::List(y)) => {
1970                let mut result = (**x).clone();
1971                result.extend(y.iter().cloned());
1972                VmValue::List(Rc::new(result))
1973            }
1974            (VmValue::Dict(x), VmValue::Dict(y)) => {
1975                let mut result = (**x).clone();
1976                result.extend(y.iter().map(|(k, v)| (k.clone(), v.clone())));
1977                VmValue::Dict(Rc::new(result))
1978            }
1979            _ => VmValue::String(Rc::from(format!("{}{}", a.display(), b.display()))),
1980        }
1981    }
1982
1983    fn sub(&self, a: VmValue, b: VmValue) -> VmValue {
1984        match (&a, &b) {
1985            (VmValue::Int(x), VmValue::Int(y)) => VmValue::Int(x.wrapping_sub(*y)),
1986            (VmValue::Float(x), VmValue::Float(y)) => VmValue::Float(x - y),
1987            (VmValue::Int(x), VmValue::Float(y)) => VmValue::Float(*x as f64 - y),
1988            (VmValue::Float(x), VmValue::Int(y)) => VmValue::Float(x - *y as f64),
1989            _ => VmValue::Nil,
1990        }
1991    }
1992
1993    fn mul(&self, a: VmValue, b: VmValue) -> VmValue {
1994        match (&a, &b) {
1995            (VmValue::Int(x), VmValue::Int(y)) => VmValue::Int(x.wrapping_mul(*y)),
1996            (VmValue::Float(x), VmValue::Float(y)) => VmValue::Float(x * y),
1997            (VmValue::Int(x), VmValue::Float(y)) => VmValue::Float(*x as f64 * y),
1998            (VmValue::Float(x), VmValue::Int(y)) => VmValue::Float(x * *y as f64),
1999            _ => VmValue::Nil,
2000        }
2001    }
2002
2003    fn div(&self, a: VmValue, b: VmValue) -> Result<VmValue, VmError> {
2004        match (&a, &b) {
2005            (VmValue::Int(_), VmValue::Int(y)) if *y == 0 => Err(VmError::DivisionByZero),
2006            (VmValue::Int(x), VmValue::Int(y)) => Ok(VmValue::Int(x / y)),
2007            (VmValue::Float(_), VmValue::Float(y)) if *y == 0.0 => Err(VmError::DivisionByZero),
2008            (VmValue::Float(x), VmValue::Float(y)) => Ok(VmValue::Float(x / y)),
2009            (VmValue::Int(_), VmValue::Float(y)) if *y == 0.0 => Err(VmError::DivisionByZero),
2010            (VmValue::Int(x), VmValue::Float(y)) => Ok(VmValue::Float(*x as f64 / y)),
2011            (VmValue::Float(_), VmValue::Int(y)) if *y == 0 => Err(VmError::DivisionByZero),
2012            (VmValue::Float(x), VmValue::Int(y)) => Ok(VmValue::Float(x / *y as f64)),
2013            _ => Err(VmError::Runtime(format!(
2014                "Cannot divide {} by {}",
2015                a.type_name(),
2016                b.type_name()
2017            ))),
2018        }
2019    }
2020
2021    fn modulo(&self, a: VmValue, b: VmValue) -> Result<VmValue, VmError> {
2022        match (&a, &b) {
2023            (VmValue::Int(_), VmValue::Int(y)) if *y == 0 => Err(VmError::DivisionByZero),
2024            (VmValue::Int(x), VmValue::Int(y)) => Ok(VmValue::Int(x % y)),
2025            (VmValue::Float(_), VmValue::Float(y)) if *y == 0.0 => Err(VmError::DivisionByZero),
2026            (VmValue::Float(x), VmValue::Float(y)) => Ok(VmValue::Float(x % y)),
2027            (VmValue::Int(_), VmValue::Float(y)) if *y == 0.0 => Err(VmError::DivisionByZero),
2028            (VmValue::Int(x), VmValue::Float(y)) => Ok(VmValue::Float(*x as f64 % y)),
2029            (VmValue::Float(_), VmValue::Int(y)) if *y == 0 => Err(VmError::DivisionByZero),
2030            (VmValue::Float(x), VmValue::Int(y)) => Ok(VmValue::Float(x % *y as f64)),
2031            _ => Err(VmError::Runtime(format!(
2032                "Cannot modulo {} by {}",
2033                a.type_name(),
2034                b.type_name()
2035            ))),
2036        }
2037    }
2038
2039    /// Format a runtime error with stack trace and source context.
2040    ///
2041    /// Produces rustc-style output:
2042    /// ```text
2043    /// error: Undefined variable: x
2044    ///   --> file.harn:5:1
2045    ///    |
2046    ///  5 | log(x)
2047    ///    |
2048    ///   = note: called from greet at file.harn:2:1
2049    /// ```
2050    pub fn format_runtime_error(&self, error: &VmError) -> String {
2051        let source = match &self.source_text {
2052            Some(s) => s.as_str(),
2053            None => return format!("error: {error}"),
2054        };
2055        let filename = self.source_file.as_deref().unwrap_or("<unknown>");
2056
2057        let error_msg = format!("{error}");
2058        let mut out = String::new();
2059
2060        // Error header
2061        out.push_str(&format!("error: {error_msg}\n"));
2062
2063        // Get the error location from the top frame
2064        let frames: Vec<(&str, usize)> = self
2065            .frames
2066            .iter()
2067            .map(|f| {
2068                let line = if f.ip > 0 && f.ip - 1 < f.chunk.lines.len() {
2069                    f.chunk.lines[f.ip - 1] as usize
2070                } else {
2071                    0
2072                };
2073                (f.fn_name.as_str(), line)
2074            })
2075            .collect();
2076
2077        if let Some((_name, line)) = frames.last() {
2078            let line = *line;
2079            if line > 0 {
2080                let gutter_width = line.to_string().len();
2081                out.push_str(&format!(
2082                    "{:>width$}--> {filename}:{line}:1\n",
2083                    " ",
2084                    width = gutter_width + 1,
2085                ));
2086                // Show source line
2087                if let Some(source_line) = source.lines().nth(line.saturating_sub(1)) {
2088                    out.push_str(&format!("{:>width$} |\n", " ", width = gutter_width + 1));
2089                    out.push_str(&format!(
2090                        "{:>width$} | {source_line}\n",
2091                        line,
2092                        width = gutter_width + 1,
2093                    ));
2094                    out.push_str(&format!("{:>width$} |\n", " ", width = gutter_width + 1));
2095                }
2096            }
2097        }
2098
2099        // Show call stack (bottom-up, skipping top frame which is already shown)
2100        if frames.len() > 1 {
2101            for (name, line) in frames.iter().rev().skip(1) {
2102                let display_name = if name.is_empty() { "pipeline" } else { name };
2103                if *line > 0 {
2104                    out.push_str(&format!(
2105                        "  = note: called from {display_name} at {filename}:{line}\n"
2106                    ));
2107                }
2108            }
2109        }
2110
2111        out
2112    }
2113}
2114
2115impl Default for Vm {
2116    fn default() -> Self {
2117        Self::new()
2118    }
2119}
2120
2121#[cfg(test)]
2122mod tests {
2123    use super::*;
2124    use crate::compiler::Compiler;
2125    use crate::stdlib::register_vm_stdlib;
2126    use harn_lexer::Lexer;
2127    use harn_parser::Parser;
2128
2129    fn run_harn(source: &str) -> (String, VmValue) {
2130        let rt = tokio::runtime::Builder::new_current_thread()
2131            .enable_all()
2132            .build()
2133            .unwrap();
2134        rt.block_on(async {
2135            let local = tokio::task::LocalSet::new();
2136            local
2137                .run_until(async {
2138                    let mut lexer = Lexer::new(source);
2139                    let tokens = lexer.tokenize().unwrap();
2140                    let mut parser = Parser::new(tokens);
2141                    let program = parser.parse().unwrap();
2142                    let chunk = Compiler::new().compile(&program).unwrap();
2143
2144                    let mut vm = Vm::new();
2145                    register_vm_stdlib(&mut vm);
2146                    let result = vm.execute(&chunk).await.unwrap();
2147                    (vm.output().to_string(), result)
2148                })
2149                .await
2150        })
2151    }
2152
2153    fn run_output(source: &str) -> String {
2154        run_harn(source).0.trim_end().to_string()
2155    }
2156
2157    fn run_harn_result(source: &str) -> Result<(String, VmValue), VmError> {
2158        let rt = tokio::runtime::Builder::new_current_thread()
2159            .enable_all()
2160            .build()
2161            .unwrap();
2162        rt.block_on(async {
2163            let local = tokio::task::LocalSet::new();
2164            local
2165                .run_until(async {
2166                    let mut lexer = Lexer::new(source);
2167                    let tokens = lexer.tokenize().unwrap();
2168                    let mut parser = Parser::new(tokens);
2169                    let program = parser.parse().unwrap();
2170                    let chunk = Compiler::new().compile(&program).unwrap();
2171
2172                    let mut vm = Vm::new();
2173                    register_vm_stdlib(&mut vm);
2174                    let result = vm.execute(&chunk).await?;
2175                    Ok((vm.output().to_string(), result))
2176                })
2177                .await
2178        })
2179    }
2180
2181    #[test]
2182    fn test_arithmetic() {
2183        let out =
2184            run_output("pipeline t(task) { log(2 + 3)\nlog(10 - 4)\nlog(3 * 5)\nlog(10 / 3) }");
2185        assert_eq!(out, "[harn] 5\n[harn] 6\n[harn] 15\n[harn] 3");
2186    }
2187
2188    #[test]
2189    fn test_mixed_arithmetic() {
2190        let out = run_output("pipeline t(task) { log(3 + 1.5)\nlog(10 - 2.5) }");
2191        assert_eq!(out, "[harn] 4.5\n[harn] 7.5");
2192    }
2193
2194    #[test]
2195    fn test_comparisons() {
2196        let out =
2197            run_output("pipeline t(task) { log(1 < 2)\nlog(2 > 3)\nlog(1 == 1)\nlog(1 != 2) }");
2198        assert_eq!(out, "[harn] true\n[harn] false\n[harn] true\n[harn] true");
2199    }
2200
2201    #[test]
2202    fn test_let_var() {
2203        let out = run_output("pipeline t(task) { let x = 42\nlog(x)\nvar y = 1\ny = 2\nlog(y) }");
2204        assert_eq!(out, "[harn] 42\n[harn] 2");
2205    }
2206
2207    #[test]
2208    fn test_if_else() {
2209        let out = run_output(
2210            r#"pipeline t(task) { if true { log("yes") } if false { log("wrong") } else { log("no") } }"#,
2211        );
2212        assert_eq!(out, "[harn] yes\n[harn] no");
2213    }
2214
2215    #[test]
2216    fn test_while_loop() {
2217        let out = run_output("pipeline t(task) { var i = 0\n while i < 5 { i = i + 1 }\n log(i) }");
2218        assert_eq!(out, "[harn] 5");
2219    }
2220
2221    #[test]
2222    fn test_for_in() {
2223        let out = run_output("pipeline t(task) { for item in [1, 2, 3] { log(item) } }");
2224        assert_eq!(out, "[harn] 1\n[harn] 2\n[harn] 3");
2225    }
2226
2227    #[test]
2228    fn test_fn_decl_and_call() {
2229        let out = run_output("pipeline t(task) { fn add(a, b) { return a + b }\nlog(add(3, 4)) }");
2230        assert_eq!(out, "[harn] 7");
2231    }
2232
2233    #[test]
2234    fn test_closure() {
2235        let out = run_output("pipeline t(task) { let double = { x -> x * 2 }\nlog(double(5)) }");
2236        assert_eq!(out, "[harn] 10");
2237    }
2238
2239    #[test]
2240    fn test_closure_capture() {
2241        let out = run_output(
2242            "pipeline t(task) { let base = 10\nfn offset(x) { return x + base }\nlog(offset(5)) }",
2243        );
2244        assert_eq!(out, "[harn] 15");
2245    }
2246
2247    #[test]
2248    fn test_string_concat() {
2249        let out = run_output(
2250            r#"pipeline t(task) { let a = "hello" + " " + "world"
2251log(a) }"#,
2252        );
2253        assert_eq!(out, "[harn] hello world");
2254    }
2255
2256    #[test]
2257    fn test_list_map() {
2258        let out = run_output(
2259            "pipeline t(task) { let doubled = [1, 2, 3].map({ x -> x * 2 })\nlog(doubled) }",
2260        );
2261        assert_eq!(out, "[harn] [2, 4, 6]");
2262    }
2263
2264    #[test]
2265    fn test_list_filter() {
2266        let out = run_output(
2267            "pipeline t(task) { let big = [1, 2, 3, 4, 5].filter({ x -> x > 3 })\nlog(big) }",
2268        );
2269        assert_eq!(out, "[harn] [4, 5]");
2270    }
2271
2272    #[test]
2273    fn test_list_reduce() {
2274        let out = run_output(
2275            "pipeline t(task) { let sum = [1, 2, 3, 4].reduce(0, { acc, x -> acc + x })\nlog(sum) }",
2276        );
2277        assert_eq!(out, "[harn] 10");
2278    }
2279
2280    #[test]
2281    fn test_dict_access() {
2282        let out = run_output(
2283            r#"pipeline t(task) { let d = {name: "test", value: 42}
2284log(d.name)
2285log(d.value) }"#,
2286        );
2287        assert_eq!(out, "[harn] test\n[harn] 42");
2288    }
2289
2290    #[test]
2291    fn test_dict_methods() {
2292        let out = run_output(
2293            r#"pipeline t(task) { let d = {a: 1, b: 2}
2294log(d.keys())
2295log(d.values())
2296log(d.has("a"))
2297log(d.has("z")) }"#,
2298        );
2299        assert_eq!(
2300            out,
2301            "[harn] [a, b]\n[harn] [1, 2]\n[harn] true\n[harn] false"
2302        );
2303    }
2304
2305    #[test]
2306    fn test_pipe_operator() {
2307        let out = run_output(
2308            "pipeline t(task) { fn double(x) { return x * 2 }\nlet r = 5 |> double\nlog(r) }",
2309        );
2310        assert_eq!(out, "[harn] 10");
2311    }
2312
2313    #[test]
2314    fn test_pipe_with_closure() {
2315        let out = run_output(
2316            r#"pipeline t(task) { let r = "hello world" |> { s -> s.split(" ") }
2317log(r) }"#,
2318        );
2319        assert_eq!(out, "[harn] [hello, world]");
2320    }
2321
2322    #[test]
2323    fn test_nil_coalescing() {
2324        let out = run_output(
2325            r#"pipeline t(task) { let a = nil ?? "fallback"
2326log(a)
2327let b = "present" ?? "fallback"
2328log(b) }"#,
2329        );
2330        assert_eq!(out, "[harn] fallback\n[harn] present");
2331    }
2332
2333    #[test]
2334    fn test_logical_operators() {
2335        let out =
2336            run_output("pipeline t(task) { log(true && false)\nlog(true || false)\nlog(!true) }");
2337        assert_eq!(out, "[harn] false\n[harn] true\n[harn] false");
2338    }
2339
2340    #[test]
2341    fn test_match() {
2342        let out = run_output(
2343            r#"pipeline t(task) { let x = "b"
2344match x { "a" -> { log("first") } "b" -> { log("second") } "c" -> { log("third") } } }"#,
2345        );
2346        assert_eq!(out, "[harn] second");
2347    }
2348
2349    #[test]
2350    fn test_subscript() {
2351        let out = run_output("pipeline t(task) { let arr = [10, 20, 30]\nlog(arr[1]) }");
2352        assert_eq!(out, "[harn] 20");
2353    }
2354
2355    #[test]
2356    fn test_string_methods() {
2357        let out = run_output(
2358            r#"pipeline t(task) { log("hello world".replace("world", "harn"))
2359log("a,b,c".split(","))
2360log("  hello  ".trim())
2361log("hello".starts_with("hel"))
2362log("hello".ends_with("lo"))
2363log("hello".substring(1, 3)) }"#,
2364        );
2365        assert_eq!(
2366            out,
2367            "[harn] hello harn\n[harn] [a, b, c]\n[harn] hello\n[harn] true\n[harn] true\n[harn] el"
2368        );
2369    }
2370
2371    #[test]
2372    fn test_list_properties() {
2373        let out = run_output(
2374            "pipeline t(task) { let list = [1, 2, 3]\nlog(list.count)\nlog(list.empty)\nlog(list.first)\nlog(list.last) }",
2375        );
2376        assert_eq!(out, "[harn] 3\n[harn] false\n[harn] 1\n[harn] 3");
2377    }
2378
2379    #[test]
2380    fn test_recursive_function() {
2381        let out = run_output(
2382            "pipeline t(task) { fn fib(n) { if n <= 1 { return n } return fib(n - 1) + fib(n - 2) }\nlog(fib(10)) }",
2383        );
2384        assert_eq!(out, "[harn] 55");
2385    }
2386
2387    #[test]
2388    fn test_ternary() {
2389        let out = run_output(
2390            r#"pipeline t(task) { let x = 5
2391let r = x > 0 ? "positive" : "non-positive"
2392log(r) }"#,
2393        );
2394        assert_eq!(out, "[harn] positive");
2395    }
2396
2397    #[test]
2398    fn test_for_in_dict() {
2399        let out = run_output(
2400            "pipeline t(task) { let d = {a: 1, b: 2}\nfor entry in d { log(entry.key) } }",
2401        );
2402        assert_eq!(out, "[harn] a\n[harn] b");
2403    }
2404
2405    #[test]
2406    fn test_list_any_all() {
2407        let out = run_output(
2408            "pipeline t(task) { let nums = [2, 4, 6]\nlog(nums.any({ x -> x > 5 }))\nlog(nums.all({ x -> x > 0 }))\nlog(nums.all({ x -> x > 3 })) }",
2409        );
2410        assert_eq!(out, "[harn] true\n[harn] true\n[harn] false");
2411    }
2412
2413    #[test]
2414    fn test_disassembly() {
2415        let mut lexer = Lexer::new("pipeline t(task) { log(2 + 3) }");
2416        let tokens = lexer.tokenize().unwrap();
2417        let mut parser = Parser::new(tokens);
2418        let program = parser.parse().unwrap();
2419        let chunk = Compiler::new().compile(&program).unwrap();
2420        let disasm = chunk.disassemble("test");
2421        assert!(disasm.contains("CONSTANT"));
2422        assert!(disasm.contains("ADD"));
2423        assert!(disasm.contains("CALL"));
2424    }
2425
2426    // --- Error handling tests ---
2427
2428    #[test]
2429    fn test_try_catch_basic() {
2430        let out = run_output(
2431            r#"pipeline t(task) { try { throw "oops" } catch(e) { log("caught: " + e) } }"#,
2432        );
2433        assert_eq!(out, "[harn] caught: oops");
2434    }
2435
2436    #[test]
2437    fn test_try_no_error() {
2438        let out = run_output(
2439            r#"pipeline t(task) {
2440var result = 0
2441try { result = 42 } catch(e) { result = 0 }
2442log(result)
2443}"#,
2444        );
2445        assert_eq!(out, "[harn] 42");
2446    }
2447
2448    #[test]
2449    fn test_throw_uncaught() {
2450        let result = run_harn_result(r#"pipeline t(task) { throw "boom" }"#);
2451        assert!(result.is_err());
2452    }
2453
2454    // --- Additional test coverage ---
2455
2456    fn run_vm(source: &str) -> String {
2457        let rt = tokio::runtime::Builder::new_current_thread()
2458            .enable_all()
2459            .build()
2460            .unwrap();
2461        rt.block_on(async {
2462            let local = tokio::task::LocalSet::new();
2463            local
2464                .run_until(async {
2465                    let mut lexer = Lexer::new(source);
2466                    let tokens = lexer.tokenize().unwrap();
2467                    let mut parser = Parser::new(tokens);
2468                    let program = parser.parse().unwrap();
2469                    let chunk = Compiler::new().compile(&program).unwrap();
2470                    let mut vm = Vm::new();
2471                    register_vm_stdlib(&mut vm);
2472                    vm.execute(&chunk).await.unwrap();
2473                    vm.output().to_string()
2474                })
2475                .await
2476        })
2477    }
2478
2479    fn run_vm_err(source: &str) -> String {
2480        let rt = tokio::runtime::Builder::new_current_thread()
2481            .enable_all()
2482            .build()
2483            .unwrap();
2484        rt.block_on(async {
2485            let local = tokio::task::LocalSet::new();
2486            local
2487                .run_until(async {
2488                    let mut lexer = Lexer::new(source);
2489                    let tokens = lexer.tokenize().unwrap();
2490                    let mut parser = Parser::new(tokens);
2491                    let program = parser.parse().unwrap();
2492                    let chunk = Compiler::new().compile(&program).unwrap();
2493                    let mut vm = Vm::new();
2494                    register_vm_stdlib(&mut vm);
2495                    match vm.execute(&chunk).await {
2496                        Err(e) => format!("{}", e),
2497                        Ok(_) => panic!("Expected error"),
2498                    }
2499                })
2500                .await
2501        })
2502    }
2503
2504    #[test]
2505    fn test_hello_world() {
2506        let out = run_vm(r#"pipeline default(task) { log("hello") }"#);
2507        assert_eq!(out, "[harn] hello\n");
2508    }
2509
2510    #[test]
2511    fn test_arithmetic_new() {
2512        let out = run_vm("pipeline default(task) { log(2 + 3) }");
2513        assert_eq!(out, "[harn] 5\n");
2514    }
2515
2516    #[test]
2517    fn test_string_concat_new() {
2518        let out = run_vm(r#"pipeline default(task) { log("a" + "b") }"#);
2519        assert_eq!(out, "[harn] ab\n");
2520    }
2521
2522    #[test]
2523    fn test_if_else_new() {
2524        let out = run_vm("pipeline default(task) { if true { log(1) } else { log(2) } }");
2525        assert_eq!(out, "[harn] 1\n");
2526    }
2527
2528    #[test]
2529    fn test_for_loop_new() {
2530        let out = run_vm("pipeline default(task) { for i in [1, 2, 3] { log(i) } }");
2531        assert_eq!(out, "[harn] 1\n[harn] 2\n[harn] 3\n");
2532    }
2533
2534    #[test]
2535    fn test_while_loop_new() {
2536        let out = run_vm("pipeline default(task) { var i = 0\nwhile i < 3 { log(i)\ni = i + 1 } }");
2537        assert_eq!(out, "[harn] 0\n[harn] 1\n[harn] 2\n");
2538    }
2539
2540    #[test]
2541    fn test_function_call_new() {
2542        let out =
2543            run_vm("pipeline default(task) { fn add(a, b) { return a + b }\nlog(add(2, 3)) }");
2544        assert_eq!(out, "[harn] 5\n");
2545    }
2546
2547    #[test]
2548    fn test_closure_new() {
2549        let out = run_vm("pipeline default(task) { let f = { x -> x * 2 }\nlog(f(5)) }");
2550        assert_eq!(out, "[harn] 10\n");
2551    }
2552
2553    #[test]
2554    fn test_recursion() {
2555        let out = run_vm("pipeline default(task) { fn fact(n) { if n <= 1 { return 1 }\nreturn n * fact(n - 1) }\nlog(fact(5)) }");
2556        assert_eq!(out, "[harn] 120\n");
2557    }
2558
2559    #[test]
2560    fn test_try_catch_new() {
2561        let out = run_vm(r#"pipeline default(task) { try { throw "err" } catch (e) { log(e) } }"#);
2562        assert_eq!(out, "[harn] err\n");
2563    }
2564
2565    #[test]
2566    fn test_try_no_error_new() {
2567        let out = run_vm("pipeline default(task) { try { log(1) } catch (e) { log(2) } }");
2568        assert_eq!(out, "[harn] 1\n");
2569    }
2570
2571    #[test]
2572    fn test_list_map_new() {
2573        let out =
2574            run_vm("pipeline default(task) { let r = [1, 2, 3].map({ x -> x * 2 })\nlog(r) }");
2575        assert_eq!(out, "[harn] [2, 4, 6]\n");
2576    }
2577
2578    #[test]
2579    fn test_list_filter_new() {
2580        let out = run_vm(
2581            "pipeline default(task) { let r = [1, 2, 3, 4].filter({ x -> x > 2 })\nlog(r) }",
2582        );
2583        assert_eq!(out, "[harn] [3, 4]\n");
2584    }
2585
2586    #[test]
2587    fn test_dict_access_new() {
2588        let out = run_vm("pipeline default(task) { let d = {name: \"Alice\"}\nlog(d.name) }");
2589        assert_eq!(out, "[harn] Alice\n");
2590    }
2591
2592    #[test]
2593    fn test_string_interpolation() {
2594        let out = run_vm("pipeline default(task) { let x = 42\nlog(\"val=${x}\") }");
2595        assert_eq!(out, "[harn] val=42\n");
2596    }
2597
2598    #[test]
2599    fn test_match_new() {
2600        let out = run_vm(
2601            "pipeline default(task) { let x = \"b\"\nmatch x { \"a\" -> { log(1) } \"b\" -> { log(2) } } }",
2602        );
2603        assert_eq!(out, "[harn] 2\n");
2604    }
2605
2606    #[test]
2607    fn test_json_roundtrip() {
2608        let out = run_vm("pipeline default(task) { let s = json_stringify({a: 1})\nlog(s) }");
2609        assert!(out.contains("\"a\""));
2610        assert!(out.contains("1"));
2611    }
2612
2613    #[test]
2614    fn test_type_of() {
2615        let out = run_vm("pipeline default(task) { log(type_of(42))\nlog(type_of(\"hi\")) }");
2616        assert_eq!(out, "[harn] int\n[harn] string\n");
2617    }
2618
2619    #[test]
2620    fn test_stack_overflow() {
2621        let err = run_vm_err("pipeline default(task) { fn f() { f() }\nf() }");
2622        assert!(
2623            err.contains("stack") || err.contains("overflow") || err.contains("recursion"),
2624            "Expected stack overflow error, got: {}",
2625            err
2626        );
2627    }
2628
2629    #[test]
2630    fn test_division_by_zero() {
2631        let err = run_vm_err("pipeline default(task) { log(1 / 0) }");
2632        assert!(
2633            err.contains("Division by zero") || err.contains("division"),
2634            "Expected division by zero error, got: {}",
2635            err
2636        );
2637    }
2638
2639    #[test]
2640    fn test_try_catch_nested() {
2641        let out = run_output(
2642            r#"pipeline t(task) {
2643try {
2644    try {
2645        throw "inner"
2646    } catch(e) {
2647        log("inner caught: " + e)
2648        throw "outer"
2649    }
2650} catch(e) {
2651    log("outer caught: " + e)
2652}
2653}"#,
2654        );
2655        assert_eq!(
2656            out,
2657            "[harn] inner caught: inner\n[harn] outer caught: outer"
2658        );
2659    }
2660
2661    // --- Concurrency tests ---
2662
2663    #[test]
2664    fn test_parallel_basic() {
2665        let out = run_output(
2666            "pipeline t(task) { let results = parallel(3) { i -> i * 10 }\nlog(results) }",
2667        );
2668        assert_eq!(out, "[harn] [0, 10, 20]");
2669    }
2670
2671    #[test]
2672    fn test_parallel_no_variable() {
2673        let out = run_output("pipeline t(task) { let results = parallel(3) { 42 }\nlog(results) }");
2674        assert_eq!(out, "[harn] [42, 42, 42]");
2675    }
2676
2677    #[test]
2678    fn test_parallel_map_basic() {
2679        let out = run_output(
2680            "pipeline t(task) { let results = parallel_map([1, 2, 3]) { x -> x * x }\nlog(results) }",
2681        );
2682        assert_eq!(out, "[harn] [1, 4, 9]");
2683    }
2684
2685    #[test]
2686    fn test_spawn_await() {
2687        let out = run_output(
2688            r#"pipeline t(task) {
2689let handle = spawn { log("spawned") }
2690let result = await(handle)
2691log("done")
2692}"#,
2693        );
2694        assert_eq!(out, "[harn] spawned\n[harn] done");
2695    }
2696
2697    #[test]
2698    fn test_spawn_cancel() {
2699        let out = run_output(
2700            r#"pipeline t(task) {
2701let handle = spawn { log("should be cancelled") }
2702cancel(handle)
2703log("cancelled")
2704}"#,
2705        );
2706        assert_eq!(out, "[harn] cancelled");
2707    }
2708
2709    #[test]
2710    fn test_spawn_returns_value() {
2711        let out = run_output("pipeline t(task) { let h = spawn { 42 }\nlet r = await(h)\nlog(r) }");
2712        assert_eq!(out, "[harn] 42");
2713    }
2714
2715    // --- Deadline tests ---
2716
2717    #[test]
2718    fn test_deadline_success() {
2719        let out = run_output(
2720            r#"pipeline t(task) {
2721let result = deadline 5s { log("within deadline")
272242 }
2723log(result)
2724}"#,
2725        );
2726        assert_eq!(out, "[harn] within deadline\n[harn] 42");
2727    }
2728
2729    #[test]
2730    fn test_deadline_exceeded() {
2731        let result = run_harn_result(
2732            r#"pipeline t(task) {
2733deadline 1ms {
2734  var i = 0
2735  while i < 1000000 { i = i + 1 }
2736}
2737}"#,
2738        );
2739        assert!(result.is_err());
2740    }
2741
2742    #[test]
2743    fn test_deadline_caught_by_try() {
2744        let out = run_output(
2745            r#"pipeline t(task) {
2746try {
2747  deadline 1ms {
2748    var i = 0
2749    while i < 1000000 { i = i + 1 }
2750  }
2751} catch(e) {
2752  log("caught")
2753}
2754}"#,
2755        );
2756        assert_eq!(out, "[harn] caught");
2757    }
2758}