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