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 {
849                        let result = self.call_named_builtin(&name, args).await?;
850                        self.stack.push(result);
851                    }
852                }
853                VmValue::Closure(closure) => {
854                    self.push_closure_frame(&closure, &args, &functions)?;
855                }
856                _ => {
857                    return Err(VmError::TypeError(format!(
858                        "Cannot call {}",
859                        callee.display()
860                    )));
861                }
862            }
863        } else if op == Op::TailCall as u8 {
864            let frame = self.frames.last_mut().unwrap();
865            let argc = frame.chunk.code[frame.ip] as usize;
866            frame.ip += 1;
867
868            let args: Vec<VmValue> = self.stack.split_off(self.stack.len().saturating_sub(argc));
869            let callee = self.pop()?;
870
871            // Resolve the callee to a closure (or fall through to builtin)
872            let resolved_closure = match &callee {
873                VmValue::Closure(cl) => Some(Rc::clone(cl)),
874                VmValue::String(name) => {
875                    if let Some(VmValue::Closure(cl)) = self.env.get(name) {
876                        Some(cl)
877                    } else {
878                        None
879                    }
880                }
881                _ => None,
882            };
883
884            if let Some(closure) = resolved_closure {
885                // Tail call optimization: replace current frame instead of pushing.
886                // Pop the current frame and reuse its stack_base and saved_env.
887                let popped = self.frames.pop().unwrap();
888                let stack_base = popped.stack_base;
889                let parent_env = popped.saved_env;
890
891                // Clear this frame's stack data
892                self.stack.truncate(stack_base);
893
894                // Set up the callee's environment
895                let mut call_env = Self::merge_env_into_closure(&parent_env, &closure);
896                call_env.push_scope();
897                for (i, param) in closure.func.params.iter().enumerate() {
898                    let val = args.get(i).cloned().unwrap_or(VmValue::Nil);
899                    call_env.define(param, val, false);
900                }
901                self.env = call_env;
902
903                // Push replacement frame at the same stack depth
904                self.frames.push(CallFrame {
905                    chunk: closure.func.chunk.clone(),
906                    ip: 0,
907                    stack_base,
908                    saved_env: parent_env,
909                    fn_name: closure.func.name.clone(),
910                });
911                // Continue the loop — execution proceeds in the new frame
912            } else {
913                // Not a closure — fall back to regular call behavior for builtins.
914                match callee {
915                    VmValue::String(name) => {
916                        let result = self.call_named_builtin(&name, args).await?;
917                        self.stack.push(result);
918                    }
919                    _ => {
920                        return Err(VmError::TypeError(format!(
921                            "Cannot call {}",
922                            callee.display()
923                        )));
924                    }
925                }
926                // Result is on stack; the following Return opcode will return it.
927            }
928        } else if op == Op::Return as u8 {
929            let val = self.pop().unwrap_or(VmValue::Nil);
930            return Err(VmError::Return(val));
931        } else if op == Op::Closure as u8 {
932            let frame = self.frames.last_mut().unwrap();
933            let fn_idx = frame.chunk.read_u16(frame.ip) as usize;
934            frame.ip += 2;
935            let func = frame.chunk.functions[fn_idx].clone();
936            let closure = VmClosure {
937                func,
938                env: self.env.clone(),
939            };
940            self.stack.push(VmValue::Closure(Rc::new(closure)));
941        } else if op == Op::BuildList as u8 {
942            let frame = self.frames.last_mut().unwrap();
943            let count = frame.chunk.read_u16(frame.ip) as usize;
944            frame.ip += 2;
945            let items = self.stack.split_off(self.stack.len().saturating_sub(count));
946            self.stack.push(VmValue::List(Rc::new(items)));
947        } else if op == Op::BuildDict as u8 {
948            let frame = self.frames.last_mut().unwrap();
949            let count = frame.chunk.read_u16(frame.ip) as usize;
950            frame.ip += 2;
951            let pairs = self
952                .stack
953                .split_off(self.stack.len().saturating_sub(count * 2));
954            let mut map = BTreeMap::new();
955            for pair in pairs.chunks(2) {
956                if pair.len() == 2 {
957                    let key = pair[0].display();
958                    map.insert(key, pair[1].clone());
959                }
960            }
961            self.stack.push(VmValue::Dict(Rc::new(map)));
962        } else if op == Op::Subscript as u8 {
963            let idx = self.pop()?;
964            let obj = self.pop()?;
965            let result = match (&obj, &idx) {
966                (VmValue::List(items), VmValue::Int(i)) => {
967                    if *i < 0 {
968                        let pos = items.len() as i64 + *i;
969                        if pos < 0 {
970                            VmValue::Nil
971                        } else {
972                            items.get(pos as usize).cloned().unwrap_or(VmValue::Nil)
973                        }
974                    } else {
975                        items.get(*i as usize).cloned().unwrap_or(VmValue::Nil)
976                    }
977                }
978                (VmValue::Dict(map), _) => map.get(&idx.display()).cloned().unwrap_or(VmValue::Nil),
979                (VmValue::String(s), VmValue::Int(i)) => {
980                    if *i < 0 {
981                        let pos = s.chars().count() as i64 + *i;
982                        if pos < 0 {
983                            VmValue::Nil
984                        } else {
985                            s.chars()
986                                .nth(pos as usize)
987                                .map(|c| VmValue::String(Rc::from(c.to_string())))
988                                .unwrap_or(VmValue::Nil)
989                        }
990                    } else {
991                        s.chars()
992                            .nth(*i as usize)
993                            .map(|c| VmValue::String(Rc::from(c.to_string())))
994                            .unwrap_or(VmValue::Nil)
995                    }
996                }
997                _ => {
998                    return Err(VmError::TypeError(format!(
999                        "cannot index into {} with {}",
1000                        obj.type_name(),
1001                        idx.type_name()
1002                    )));
1003                }
1004            };
1005            self.stack.push(result);
1006        } else if op == Op::Slice as u8 {
1007            let end_val = self.pop()?;
1008            let start_val = self.pop()?;
1009            let obj = self.pop()?;
1010
1011            let result = match &obj {
1012                VmValue::List(items) => {
1013                    let len = items.len() as i64;
1014                    let start = match &start_val {
1015                        VmValue::Nil => 0i64,
1016                        VmValue::Int(i) => {
1017                            if *i < 0 {
1018                                (len + *i).max(0)
1019                            } else {
1020                                (*i).min(len)
1021                            }
1022                        }
1023                        _ => {
1024                            return Err(VmError::TypeError(format!(
1025                                "slice start must be an integer, got {}",
1026                                start_val.type_name()
1027                            )));
1028                        }
1029                    };
1030                    let end = match &end_val {
1031                        VmValue::Nil => len,
1032                        VmValue::Int(i) => {
1033                            if *i < 0 {
1034                                (len + *i).max(0)
1035                            } else {
1036                                (*i).min(len)
1037                            }
1038                        }
1039                        _ => {
1040                            return Err(VmError::TypeError(format!(
1041                                "slice end must be an integer, got {}",
1042                                end_val.type_name()
1043                            )));
1044                        }
1045                    };
1046                    if start >= end {
1047                        VmValue::List(Rc::new(vec![]))
1048                    } else {
1049                        let sliced: Vec<VmValue> = items[start as usize..end as usize].to_vec();
1050                        VmValue::List(Rc::new(sliced))
1051                    }
1052                }
1053                VmValue::String(s) => {
1054                    let chars: Vec<char> = s.chars().collect();
1055                    let len = chars.len() as i64;
1056                    let start = match &start_val {
1057                        VmValue::Nil => 0i64,
1058                        VmValue::Int(i) => {
1059                            if *i < 0 {
1060                                (len + *i).max(0)
1061                            } else {
1062                                (*i).min(len)
1063                            }
1064                        }
1065                        _ => {
1066                            return Err(VmError::TypeError(format!(
1067                                "slice start must be an integer, got {}",
1068                                start_val.type_name()
1069                            )));
1070                        }
1071                    };
1072                    let end = match &end_val {
1073                        VmValue::Nil => len,
1074                        VmValue::Int(i) => {
1075                            if *i < 0 {
1076                                (len + *i).max(0)
1077                            } else {
1078                                (*i).min(len)
1079                            }
1080                        }
1081                        _ => {
1082                            return Err(VmError::TypeError(format!(
1083                                "slice end must be an integer, got {}",
1084                                end_val.type_name()
1085                            )));
1086                        }
1087                    };
1088                    if start >= end {
1089                        VmValue::String(Rc::from(String::new()))
1090                    } else {
1091                        let sliced: String = chars[start as usize..end as usize].iter().collect();
1092                        VmValue::String(Rc::from(sliced))
1093                    }
1094                }
1095                _ => {
1096                    return Err(VmError::TypeError(format!(
1097                        "cannot slice {}",
1098                        obj.type_name()
1099                    )));
1100                }
1101            };
1102            self.stack.push(result);
1103        } else if op == Op::GetProperty as u8 {
1104            let frame = self.frames.last_mut().unwrap();
1105            let idx = frame.chunk.read_u16(frame.ip) as usize;
1106            frame.ip += 2;
1107            let name = Self::const_string(&frame.chunk.constants[idx])?;
1108            let obj = self.pop()?;
1109            let result = match &obj {
1110                VmValue::Dict(map) => map.get(&name).cloned().unwrap_or(VmValue::Nil),
1111                VmValue::List(items) => match name.as_str() {
1112                    "count" => VmValue::Int(items.len() as i64),
1113                    "empty" => VmValue::Bool(items.is_empty()),
1114                    "first" => items.first().cloned().unwrap_or(VmValue::Nil),
1115                    "last" => items.last().cloned().unwrap_or(VmValue::Nil),
1116                    _ => VmValue::Nil,
1117                },
1118                VmValue::String(s) => match name.as_str() {
1119                    "count" => VmValue::Int(s.chars().count() as i64),
1120                    "empty" => VmValue::Bool(s.is_empty()),
1121                    _ => VmValue::Nil,
1122                },
1123                VmValue::EnumVariant {
1124                    variant, fields, ..
1125                } => match name.as_str() {
1126                    "variant" => VmValue::String(Rc::from(variant.as_str())),
1127                    "fields" => VmValue::List(Rc::new(fields.clone())),
1128                    _ => VmValue::Nil,
1129                },
1130                VmValue::StructInstance { fields, .. } => {
1131                    fields.get(&name).cloned().unwrap_or(VmValue::Nil)
1132                }
1133                VmValue::Nil => {
1134                    return Err(VmError::TypeError(format!(
1135                        "cannot access property `{name}` on nil"
1136                    )));
1137                }
1138                _ => {
1139                    return Err(VmError::TypeError(format!(
1140                        "cannot access property `{name}` on {}",
1141                        obj.type_name()
1142                    )));
1143                }
1144            };
1145            self.stack.push(result);
1146        } else if op == Op::GetPropertyOpt as u8 {
1147            // Optional chaining: obj?.property — returns nil if obj is nil
1148            let frame = self.frames.last_mut().unwrap();
1149            let idx = frame.chunk.read_u16(frame.ip) as usize;
1150            frame.ip += 2;
1151            let name = Self::const_string(&frame.chunk.constants[idx])?;
1152            let obj = self.pop()?;
1153            let result = match &obj {
1154                VmValue::Nil => VmValue::Nil,
1155                VmValue::Dict(map) => map.get(&name).cloned().unwrap_or(VmValue::Nil),
1156                VmValue::List(items) => match name.as_str() {
1157                    "count" => VmValue::Int(items.len() as i64),
1158                    "empty" => VmValue::Bool(items.is_empty()),
1159                    "first" => items.first().cloned().unwrap_or(VmValue::Nil),
1160                    "last" => items.last().cloned().unwrap_or(VmValue::Nil),
1161                    _ => VmValue::Nil,
1162                },
1163                VmValue::String(s) => match name.as_str() {
1164                    "count" => VmValue::Int(s.chars().count() as i64),
1165                    "empty" => VmValue::Bool(s.is_empty()),
1166                    _ => VmValue::Nil,
1167                },
1168                VmValue::EnumVariant {
1169                    variant, fields, ..
1170                } => match name.as_str() {
1171                    "variant" => VmValue::String(Rc::from(variant.as_str())),
1172                    "fields" => VmValue::List(Rc::new(fields.clone())),
1173                    _ => VmValue::Nil,
1174                },
1175                VmValue::StructInstance { fields, .. } => {
1176                    fields.get(&name).cloned().unwrap_or(VmValue::Nil)
1177                }
1178                _ => VmValue::Nil,
1179            };
1180            self.stack.push(result);
1181        } else if op == Op::SetProperty as u8 {
1182            let frame = self.frames.last_mut().unwrap();
1183            let prop_idx = frame.chunk.read_u16(frame.ip) as usize;
1184            frame.ip += 2;
1185            let var_idx = frame.chunk.read_u16(frame.ip) as usize;
1186            frame.ip += 2;
1187            let prop_name = Self::const_string(&frame.chunk.constants[prop_idx])?;
1188            let var_name = Self::const_string(&frame.chunk.constants[var_idx])?;
1189            let new_value = self.pop()?;
1190            if let Some(obj) = self.env.get(&var_name) {
1191                match obj {
1192                    VmValue::Dict(map) => {
1193                        let mut new_map = (*map).clone();
1194                        new_map.insert(prop_name, new_value);
1195                        self.env
1196                            .assign(&var_name, VmValue::Dict(Rc::new(new_map)))?;
1197                    }
1198                    VmValue::StructInstance {
1199                        struct_name,
1200                        fields,
1201                    } => {
1202                        let mut new_fields = fields.clone();
1203                        new_fields.insert(prop_name, new_value);
1204                        self.env.assign(
1205                            &var_name,
1206                            VmValue::StructInstance {
1207                                struct_name,
1208                                fields: new_fields,
1209                            },
1210                        )?;
1211                    }
1212                    _ => {
1213                        return Err(VmError::TypeError(format!(
1214                            "cannot set property `{prop_name}` on {}",
1215                            obj.type_name()
1216                        )));
1217                    }
1218                }
1219            }
1220        } else if op == Op::SetSubscript as u8 {
1221            let frame = self.frames.last_mut().unwrap();
1222            let var_idx = frame.chunk.read_u16(frame.ip) as usize;
1223            frame.ip += 2;
1224            let var_name = Self::const_string(&frame.chunk.constants[var_idx])?;
1225            let index = self.pop()?;
1226            let new_value = self.pop()?;
1227            if let Some(obj) = self.env.get(&var_name) {
1228                match obj {
1229                    VmValue::List(items) => {
1230                        if let Some(i) = index.as_int() {
1231                            let mut new_items = (*items).clone();
1232                            let idx = if i < 0 {
1233                                (new_items.len() as i64 + i).max(0) as usize
1234                            } else {
1235                                i as usize
1236                            };
1237                            if idx < new_items.len() {
1238                                new_items[idx] = new_value;
1239                                self.env
1240                                    .assign(&var_name, VmValue::List(Rc::new(new_items)))?;
1241                            } else {
1242                                return Err(VmError::Runtime(format!(
1243                                    "Index {} out of bounds for list of length {}",
1244                                    i,
1245                                    items.len()
1246                                )));
1247                            }
1248                        }
1249                    }
1250                    VmValue::Dict(map) => {
1251                        let key = index.display();
1252                        let mut new_map = (*map).clone();
1253                        new_map.insert(key, new_value);
1254                        self.env
1255                            .assign(&var_name, VmValue::Dict(Rc::new(new_map)))?;
1256                    }
1257                    _ => {}
1258                }
1259            }
1260        } else if op == Op::MethodCall as u8 || op == Op::MethodCallOpt as u8 {
1261            let optional = op == Op::MethodCallOpt as u8;
1262            let frame = self.frames.last_mut().unwrap();
1263            let name_idx = frame.chunk.read_u16(frame.ip) as usize;
1264            frame.ip += 2;
1265            let argc = frame.chunk.code[frame.ip] as usize;
1266            frame.ip += 1;
1267            let method = Self::const_string(&frame.chunk.constants[name_idx])?;
1268            let functions = frame.chunk.functions.clone();
1269            let args: Vec<VmValue> = self.stack.split_off(self.stack.len().saturating_sub(argc));
1270            let obj = self.pop()?;
1271            if optional && matches!(obj, VmValue::Nil) {
1272                self.stack.push(VmValue::Nil);
1273            } else {
1274                let result = self.call_method(obj, &method, &args, &functions).await?;
1275                self.stack.push(result);
1276            }
1277        } else if op == Op::Concat as u8 {
1278            let frame = self.frames.last_mut().unwrap();
1279            let count = frame.chunk.read_u16(frame.ip) as usize;
1280            frame.ip += 2;
1281            let parts = self.stack.split_off(self.stack.len().saturating_sub(count));
1282            let result: String = parts.iter().map(|p| p.display()).collect();
1283            self.stack.push(VmValue::String(Rc::from(result)));
1284        } else if op == Op::Pipe as u8 {
1285            let callable = self.pop()?;
1286            let value = self.pop()?;
1287            let functions = self.frames.last().unwrap().chunk.functions.clone();
1288            match callable {
1289                VmValue::Closure(closure) => {
1290                    self.push_closure_frame(&closure, &[value], &functions)?;
1291                }
1292                VmValue::String(name) => {
1293                    if let Some(VmValue::Closure(closure)) = self.env.get(&name) {
1294                        self.push_closure_frame(&closure, &[value], &functions)?;
1295                    } else {
1296                        let result = self.call_named_builtin(&name, vec![value]).await?;
1297                        self.stack.push(result);
1298                    }
1299                }
1300                _ => {
1301                    return Err(VmError::TypeError(format!(
1302                        "cannot pipe into {}",
1303                        callable.type_name()
1304                    )));
1305                }
1306            }
1307        } else if op == Op::Dup as u8 {
1308            let val = self.peek()?.clone();
1309            self.stack.push(val);
1310        } else if op == Op::Swap as u8 {
1311            let len = self.stack.len();
1312            if len >= 2 {
1313                self.stack.swap(len - 1, len - 2);
1314            }
1315        } else if op == Op::IterInit as u8 {
1316            let iterable = self.pop()?;
1317            let items = match iterable {
1318                VmValue::List(items) => (*items).clone(),
1319                VmValue::Dict(map) => map
1320                    .iter()
1321                    .map(|(k, v)| {
1322                        VmValue::Dict(Rc::new(BTreeMap::from([
1323                            ("key".to_string(), VmValue::String(Rc::from(k.as_str()))),
1324                            ("value".to_string(), v.clone()),
1325                        ])))
1326                    })
1327                    .collect(),
1328                _ => Vec::new(),
1329            };
1330            self.iterators.push((items, 0));
1331        } else if op == Op::IterNext as u8 {
1332            let frame = self.frames.last_mut().unwrap();
1333            let target = frame.chunk.read_u16(frame.ip) as usize;
1334            frame.ip += 2;
1335            if let Some((items, idx)) = self.iterators.last_mut() {
1336                if *idx < items.len() {
1337                    let item = items[*idx].clone();
1338                    *idx += 1;
1339                    self.stack.push(item);
1340                } else {
1341                    self.iterators.pop();
1342                    let frame = self.frames.last_mut().unwrap();
1343                    frame.ip = target;
1344                }
1345            } else {
1346                let frame = self.frames.last_mut().unwrap();
1347                frame.ip = target;
1348            }
1349        } else if op == Op::PopIterator as u8 {
1350            self.iterators.pop();
1351        } else if op == Op::Throw as u8 {
1352            let val = self.pop()?;
1353            return Err(VmError::Thrown(val));
1354        } else if op == Op::TryCatchSetup as u8 {
1355            let frame = self.frames.last_mut().unwrap();
1356            let catch_offset = frame.chunk.read_u16(frame.ip) as usize;
1357            frame.ip += 2;
1358            // Read the error type name index (extra u16)
1359            let type_idx = frame.chunk.read_u16(frame.ip) as usize;
1360            frame.ip += 2;
1361            let error_type = match &frame.chunk.constants[type_idx] {
1362                Constant::String(s) => s.clone(),
1363                _ => String::new(),
1364            };
1365            self.exception_handlers.push(ExceptionHandler {
1366                catch_ip: catch_offset,
1367                stack_depth: self.stack.len(),
1368                frame_depth: self.frames.len(),
1369                error_type,
1370            });
1371        } else if op == Op::PopHandler as u8 {
1372            self.exception_handlers.pop();
1373        } else if op == Op::Parallel as u8 {
1374            let closure = self.pop()?;
1375            let count_val = self.pop()?;
1376            let count = match &count_val {
1377                VmValue::Int(n) => (*n).max(0) as usize,
1378                _ => 0,
1379            };
1380            if let VmValue::Closure(closure) = closure {
1381                let mut handles = Vec::with_capacity(count);
1382                for i in 0..count {
1383                    let mut child = self.child_vm();
1384                    let closure = closure.clone();
1385                    handles.push(tokio::task::spawn_local(async move {
1386                        let result = child
1387                            .call_closure(&closure, &[VmValue::Int(i as i64)], &[])
1388                            .await?;
1389                        Ok((result, std::mem::take(&mut child.output)))
1390                    }));
1391                }
1392                let mut results = vec![VmValue::Nil; count];
1393                for (i, handle) in handles.into_iter().enumerate() {
1394                    let (val, task_output): (VmValue, String) = handle
1395                        .await
1396                        .map_err(|e| VmError::Runtime(format!("Parallel task error: {e}")))??;
1397                    self.output.push_str(&task_output);
1398                    results[i] = val;
1399                }
1400                self.stack.push(VmValue::List(Rc::new(results)));
1401            } else {
1402                self.stack.push(VmValue::Nil);
1403            }
1404        } else if op == Op::ParallelMap as u8 {
1405            let closure = self.pop()?;
1406            let list_val = self.pop()?;
1407            match (&list_val, &closure) {
1408                (VmValue::List(items), VmValue::Closure(closure)) => {
1409                    let len = items.len();
1410                    let mut handles = Vec::with_capacity(len);
1411                    for item in items.iter() {
1412                        let mut child = self.child_vm();
1413                        let closure = closure.clone();
1414                        let item = item.clone();
1415                        handles.push(tokio::task::spawn_local(async move {
1416                            let result = child.call_closure(&closure, &[item], &[]).await?;
1417                            Ok((result, std::mem::take(&mut child.output)))
1418                        }));
1419                    }
1420                    let mut results = Vec::with_capacity(len);
1421                    for handle in handles {
1422                        let (val, task_output): (VmValue, String) = handle
1423                            .await
1424                            .map_err(|e| VmError::Runtime(format!("Parallel map error: {e}")))??;
1425                        self.output.push_str(&task_output);
1426                        results.push(val);
1427                    }
1428                    self.stack.push(VmValue::List(Rc::new(results)));
1429                }
1430                _ => self.stack.push(VmValue::Nil),
1431            }
1432        } else if op == Op::Spawn as u8 {
1433            let closure = self.pop()?;
1434            if let VmValue::Closure(closure) = closure {
1435                self.task_counter += 1;
1436                let task_id = format!("vm_task_{}", self.task_counter);
1437                let mut child = self.child_vm();
1438                let handle = tokio::task::spawn_local(async move {
1439                    let result = child.call_closure(&closure, &[], &[]).await?;
1440                    Ok((result, std::mem::take(&mut child.output)))
1441                });
1442                self.spawned_tasks.insert(task_id.clone(), handle);
1443                self.stack.push(VmValue::TaskHandle(task_id));
1444            } else {
1445                self.stack.push(VmValue::Nil);
1446            }
1447        } else if op == Op::Import as u8 {
1448            let frame = self.frames.last_mut().unwrap();
1449            let path_idx = frame.chunk.read_u16(frame.ip) as usize;
1450            frame.ip += 2;
1451            let import_path = Self::const_string(&frame.chunk.constants[path_idx])?;
1452            self.execute_import(&import_path, None).await?;
1453        } else if op == Op::SelectiveImport as u8 {
1454            let frame = self.frames.last_mut().unwrap();
1455            let path_idx = frame.chunk.read_u16(frame.ip) as usize;
1456            frame.ip += 2;
1457            let names_idx = frame.chunk.read_u16(frame.ip) as usize;
1458            frame.ip += 2;
1459            let import_path = Self::const_string(&frame.chunk.constants[path_idx])?;
1460            let names_str = Self::const_string(&frame.chunk.constants[names_idx])?;
1461            let names: Vec<String> = names_str.split(',').map(|s| s.to_string()).collect();
1462            self.execute_import(&import_path, Some(&names)).await?;
1463        } else if op == Op::DeadlineSetup as u8 {
1464            let dur_val = self.pop()?;
1465            let ms = match &dur_val {
1466                VmValue::Duration(ms) => *ms,
1467                VmValue::Int(n) => (*n).max(0) as u64,
1468                _ => 30_000,
1469            };
1470            let deadline = Instant::now() + std::time::Duration::from_millis(ms);
1471            self.deadlines.push((deadline, self.frames.len()));
1472        } else if op == Op::DeadlineEnd as u8 {
1473            self.deadlines.pop();
1474        } else if op == Op::BuildEnum as u8 {
1475            let frame = self.frames.last_mut().unwrap();
1476            let enum_idx = frame.chunk.read_u16(frame.ip) as usize;
1477            frame.ip += 2;
1478            let variant_idx = frame.chunk.read_u16(frame.ip) as usize;
1479            frame.ip += 2;
1480            let field_count = frame.chunk.read_u16(frame.ip) as usize;
1481            frame.ip += 2;
1482            let enum_name = Self::const_string(&frame.chunk.constants[enum_idx])?;
1483            let variant = Self::const_string(&frame.chunk.constants[variant_idx])?;
1484            let fields = self
1485                .stack
1486                .split_off(self.stack.len().saturating_sub(field_count));
1487            self.stack.push(VmValue::EnumVariant {
1488                enum_name,
1489                variant,
1490                fields,
1491            });
1492        } else if op == Op::MatchEnum as u8 {
1493            let frame = self.frames.last_mut().unwrap();
1494            let enum_idx = frame.chunk.read_u16(frame.ip) as usize;
1495            frame.ip += 2;
1496            let variant_idx = frame.chunk.read_u16(frame.ip) as usize;
1497            frame.ip += 2;
1498            let enum_name = Self::const_string(&frame.chunk.constants[enum_idx])?;
1499            let variant_name = Self::const_string(&frame.chunk.constants[variant_idx])?;
1500            let val = self.pop()?;
1501            let matches = match &val {
1502                VmValue::EnumVariant {
1503                    enum_name: en,
1504                    variant: vn,
1505                    ..
1506                } => *en == enum_name && *vn == variant_name,
1507                _ => false,
1508            };
1509            // Push the value back (we only peeked conceptually), then push the bool
1510            self.stack.push(val);
1511            self.stack.push(VmValue::Bool(matches));
1512        } else {
1513            return Err(VmError::InvalidInstruction(op));
1514        }
1515
1516        Ok(None)
1517    }
1518
1519    const MAX_FRAMES: usize = 512;
1520
1521    /// Merge the caller's env into a closure's captured env for function calls.
1522    fn merge_env_into_closure(caller_env: &VmEnv, closure: &VmClosure) -> VmEnv {
1523        let mut call_env = closure.env.clone();
1524        for scope in &caller_env.scopes {
1525            for (name, (val, mutable)) in &scope.vars {
1526                if call_env.get(name).is_none() {
1527                    call_env.define(name, val.clone(), *mutable);
1528                }
1529            }
1530        }
1531        call_env
1532    }
1533
1534    /// Push a new call frame for a closure invocation.
1535    fn push_closure_frame(
1536        &mut self,
1537        closure: &VmClosure,
1538        args: &[VmValue],
1539        _parent_functions: &[CompiledFunction],
1540    ) -> Result<(), VmError> {
1541        if self.frames.len() >= Self::MAX_FRAMES {
1542            return Err(VmError::StackOverflow);
1543        }
1544        let saved_env = self.env.clone();
1545
1546        let mut call_env = Self::merge_env_into_closure(&saved_env, closure);
1547        call_env.push_scope();
1548
1549        for (i, param) in closure.func.params.iter().enumerate() {
1550            let val = args.get(i).cloned().unwrap_or(VmValue::Nil);
1551            call_env.define(param, val, false);
1552        }
1553
1554        self.env = call_env;
1555
1556        self.frames.push(CallFrame {
1557            chunk: closure.func.chunk.clone(),
1558            ip: 0,
1559            stack_base: self.stack.len(),
1560            saved_env,
1561            fn_name: closure.func.name.clone(),
1562        });
1563
1564        Ok(())
1565    }
1566
1567    fn pop(&mut self) -> Result<VmValue, VmError> {
1568        self.stack.pop().ok_or(VmError::StackUnderflow)
1569    }
1570
1571    fn peek(&self) -> Result<&VmValue, VmError> {
1572        self.stack.last().ok_or(VmError::StackUnderflow)
1573    }
1574
1575    fn const_string(c: &Constant) -> Result<String, VmError> {
1576        match c {
1577            Constant::String(s) => Ok(s.clone()),
1578            _ => Err(VmError::TypeError("expected string constant".into())),
1579        }
1580    }
1581
1582    /// Call a closure (used by method calls like .map/.filter etc.)
1583    /// Uses recursive execution for simplicity in method dispatch.
1584    fn call_closure<'a>(
1585        &'a mut self,
1586        closure: &'a VmClosure,
1587        args: &'a [VmValue],
1588        _parent_functions: &'a [CompiledFunction],
1589    ) -> Pin<Box<dyn Future<Output = Result<VmValue, VmError>> + 'a>> {
1590        Box::pin(async move {
1591            let saved_env = self.env.clone();
1592            let saved_frames = std::mem::take(&mut self.frames);
1593            let saved_handlers = std::mem::take(&mut self.exception_handlers);
1594            let saved_iterators = std::mem::take(&mut self.iterators);
1595            let saved_deadlines = std::mem::take(&mut self.deadlines);
1596
1597            let mut call_env = Self::merge_env_into_closure(&saved_env, closure);
1598            call_env.push_scope();
1599
1600            for (i, param) in closure.func.params.iter().enumerate() {
1601                let val = args.get(i).cloned().unwrap_or(VmValue::Nil);
1602                call_env.define(param, val, false);
1603            }
1604
1605            self.env = call_env;
1606            let result = self.run_chunk(&closure.func.chunk).await;
1607
1608            self.env = saved_env;
1609            self.frames = saved_frames;
1610            self.exception_handlers = saved_handlers;
1611            self.iterators = saved_iterators;
1612            self.deadlines = saved_deadlines;
1613
1614            result
1615        })
1616    }
1617
1618    /// Resolve a named builtin: sync builtins → async builtins → bridge → error.
1619    /// Used by Call, TailCall, and Pipe handlers to avoid duplicating this lookup.
1620    async fn call_named_builtin(
1621        &mut self,
1622        name: &str,
1623        args: Vec<VmValue>,
1624    ) -> Result<VmValue, VmError> {
1625        if let Some(builtin) = self.builtins.get(name).cloned() {
1626            builtin(&args, &mut self.output)
1627        } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
1628            async_builtin(args).await
1629        } else if let Some(bridge) = &self.bridge {
1630            let args_json: Vec<serde_json::Value> =
1631                args.iter().map(crate::llm::vm_value_to_json).collect();
1632            let result = bridge
1633                .call(
1634                    "builtin_call",
1635                    serde_json::json!({"name": name, "args": args_json}),
1636                )
1637                .await?;
1638            Ok(crate::bridge::json_result_to_vm_value(&result))
1639        } else {
1640            Err(VmError::UndefinedBuiltin(name.to_string()))
1641        }
1642    }
1643
1644    fn call_method<'a>(
1645        &'a mut self,
1646        obj: VmValue,
1647        method: &'a str,
1648        args: &'a [VmValue],
1649        functions: &'a [CompiledFunction],
1650    ) -> Pin<Box<dyn Future<Output = Result<VmValue, VmError>> + 'a>> {
1651        Box::pin(async move {
1652            match &obj {
1653                VmValue::String(s) => match method {
1654                    "count" => Ok(VmValue::Int(s.chars().count() as i64)),
1655                    "empty" => Ok(VmValue::Bool(s.is_empty())),
1656                    "contains" => Ok(VmValue::Bool(
1657                        s.contains(&*args.first().map(|a| a.display()).unwrap_or_default()),
1658                    )),
1659                    "replace" if args.len() >= 2 => Ok(VmValue::String(Rc::from(
1660                        s.replace(&args[0].display(), &args[1].display()),
1661                    ))),
1662                    "split" => {
1663                        let sep = args.first().map(|a| a.display()).unwrap_or(",".into());
1664                        Ok(VmValue::List(Rc::new(
1665                            s.split(&*sep)
1666                                .map(|p| VmValue::String(Rc::from(p)))
1667                                .collect(),
1668                        )))
1669                    }
1670                    "trim" => Ok(VmValue::String(Rc::from(s.trim()))),
1671                    "starts_with" => Ok(VmValue::Bool(
1672                        s.starts_with(&*args.first().map(|a| a.display()).unwrap_or_default()),
1673                    )),
1674                    "ends_with" => Ok(VmValue::Bool(
1675                        s.ends_with(&*args.first().map(|a| a.display()).unwrap_or_default()),
1676                    )),
1677                    "lowercase" => Ok(VmValue::String(Rc::from(s.to_lowercase()))),
1678                    "uppercase" => Ok(VmValue::String(Rc::from(s.to_uppercase()))),
1679                    "substring" => {
1680                        let start = args.first().and_then(|a| a.as_int()).unwrap_or(0);
1681                        let len = s.chars().count() as i64;
1682                        let start = start.max(0).min(len) as usize;
1683                        let end =
1684                            args.get(1).and_then(|a| a.as_int()).unwrap_or(len).min(len) as usize;
1685                        let end = end.max(start);
1686                        let substr: String = s.chars().skip(start).take(end - start).collect();
1687                        Ok(VmValue::String(Rc::from(substr)))
1688                    }
1689                    "index_of" => {
1690                        let needle = args.first().map(|a| a.display()).unwrap_or_default();
1691                        Ok(VmValue::Int(
1692                            s.find(&needle).map(|i| i as i64).unwrap_or(-1),
1693                        ))
1694                    }
1695                    "chars" => Ok(VmValue::List(Rc::new(
1696                        s.chars()
1697                            .map(|c| VmValue::String(Rc::from(c.to_string())))
1698                            .collect(),
1699                    ))),
1700                    "repeat" => {
1701                        let n = args.first().and_then(|a| a.as_int()).unwrap_or(1);
1702                        Ok(VmValue::String(Rc::from(s.repeat(n.max(0) as usize))))
1703                    }
1704                    "reverse" => Ok(VmValue::String(Rc::from(
1705                        s.chars().rev().collect::<String>(),
1706                    ))),
1707                    "pad_left" => {
1708                        let width = args.first().and_then(|a| a.as_int()).unwrap_or(0) as usize;
1709                        let pad_char = args
1710                            .get(1)
1711                            .map(|a| a.display())
1712                            .and_then(|s| s.chars().next())
1713                            .unwrap_or(' ');
1714                        let current_len = s.chars().count();
1715                        if current_len >= width {
1716                            Ok(VmValue::String(Rc::clone(s)))
1717                        } else {
1718                            let padding: String =
1719                                std::iter::repeat_n(pad_char, width - current_len).collect();
1720                            Ok(VmValue::String(Rc::from(format!("{padding}{s}"))))
1721                        }
1722                    }
1723                    "pad_right" => {
1724                        let width = args.first().and_then(|a| a.as_int()).unwrap_or(0) as usize;
1725                        let pad_char = args
1726                            .get(1)
1727                            .map(|a| a.display())
1728                            .and_then(|s| s.chars().next())
1729                            .unwrap_or(' ');
1730                        let current_len = s.chars().count();
1731                        if current_len >= width {
1732                            Ok(VmValue::String(Rc::clone(s)))
1733                        } else {
1734                            let padding: String =
1735                                std::iter::repeat_n(pad_char, width - current_len).collect();
1736                            Ok(VmValue::String(Rc::from(format!("{s}{padding}"))))
1737                        }
1738                    }
1739                    _ => Ok(VmValue::Nil),
1740                },
1741                VmValue::List(items) => match method {
1742                    "count" => Ok(VmValue::Int(items.len() as i64)),
1743                    "empty" => Ok(VmValue::Bool(items.is_empty())),
1744                    "map" => {
1745                        if let Some(VmValue::Closure(closure)) = args.first() {
1746                            let mut results = Vec::new();
1747                            for item in items.iter() {
1748                                results.push(
1749                                    self.call_closure(closure, &[item.clone()], functions)
1750                                        .await?,
1751                                );
1752                            }
1753                            Ok(VmValue::List(Rc::new(results)))
1754                        } else {
1755                            Ok(VmValue::Nil)
1756                        }
1757                    }
1758                    "filter" => {
1759                        if let Some(VmValue::Closure(closure)) = args.first() {
1760                            let mut results = Vec::new();
1761                            for item in items.iter() {
1762                                let result = self
1763                                    .call_closure(closure, &[item.clone()], functions)
1764                                    .await?;
1765                                if result.is_truthy() {
1766                                    results.push(item.clone());
1767                                }
1768                            }
1769                            Ok(VmValue::List(Rc::new(results)))
1770                        } else {
1771                            Ok(VmValue::Nil)
1772                        }
1773                    }
1774                    "reduce" => {
1775                        if args.len() >= 2 {
1776                            if let VmValue::Closure(closure) = &args[1] {
1777                                let mut acc = args[0].clone();
1778                                for item in items.iter() {
1779                                    acc = self
1780                                        .call_closure(closure, &[acc, item.clone()], functions)
1781                                        .await?;
1782                                }
1783                                return Ok(acc);
1784                            }
1785                        }
1786                        Ok(VmValue::Nil)
1787                    }
1788                    "find" => {
1789                        if let Some(VmValue::Closure(closure)) = args.first() {
1790                            for item in items.iter() {
1791                                let result = self
1792                                    .call_closure(closure, &[item.clone()], functions)
1793                                    .await?;
1794                                if result.is_truthy() {
1795                                    return Ok(item.clone());
1796                                }
1797                            }
1798                        }
1799                        Ok(VmValue::Nil)
1800                    }
1801                    "any" => {
1802                        if let Some(VmValue::Closure(closure)) = args.first() {
1803                            for item in items.iter() {
1804                                let result = self
1805                                    .call_closure(closure, &[item.clone()], functions)
1806                                    .await?;
1807                                if result.is_truthy() {
1808                                    return Ok(VmValue::Bool(true));
1809                                }
1810                            }
1811                            Ok(VmValue::Bool(false))
1812                        } else {
1813                            Ok(VmValue::Bool(false))
1814                        }
1815                    }
1816                    "all" => {
1817                        if let Some(VmValue::Closure(closure)) = args.first() {
1818                            for item in items.iter() {
1819                                let result = self
1820                                    .call_closure(closure, &[item.clone()], functions)
1821                                    .await?;
1822                                if !result.is_truthy() {
1823                                    return Ok(VmValue::Bool(false));
1824                                }
1825                            }
1826                            Ok(VmValue::Bool(true))
1827                        } else {
1828                            Ok(VmValue::Bool(true))
1829                        }
1830                    }
1831                    "flat_map" => {
1832                        if let Some(VmValue::Closure(closure)) = args.first() {
1833                            let mut results = Vec::new();
1834                            for item in items.iter() {
1835                                let result = self
1836                                    .call_closure(closure, &[item.clone()], functions)
1837                                    .await?;
1838                                if let VmValue::List(inner) = result {
1839                                    results.extend(inner.iter().cloned());
1840                                } else {
1841                                    results.push(result);
1842                                }
1843                            }
1844                            Ok(VmValue::List(Rc::new(results)))
1845                        } else {
1846                            Ok(VmValue::Nil)
1847                        }
1848                    }
1849                    "sort" => {
1850                        let mut sorted: Vec<VmValue> = items.iter().cloned().collect();
1851                        sorted.sort_by(|a, b| compare_values(a, b).cmp(&0));
1852                        Ok(VmValue::List(Rc::new(sorted)))
1853                    }
1854                    "sort_by" => {
1855                        if let Some(VmValue::Closure(closure)) = args.first() {
1856                            let mut keyed: Vec<(VmValue, VmValue)> = Vec::new();
1857                            for item in items.iter() {
1858                                let key = self
1859                                    .call_closure(closure, &[item.clone()], functions)
1860                                    .await?;
1861                                keyed.push((item.clone(), key));
1862                            }
1863                            keyed.sort_by(|(_, ka), (_, kb)| compare_values(ka, kb).cmp(&0));
1864                            Ok(VmValue::List(Rc::new(
1865                                keyed.into_iter().map(|(v, _)| v).collect(),
1866                            )))
1867                        } else {
1868                            Ok(VmValue::Nil)
1869                        }
1870                    }
1871                    "reverse" => {
1872                        let mut rev: Vec<VmValue> = items.iter().cloned().collect();
1873                        rev.reverse();
1874                        Ok(VmValue::List(Rc::new(rev)))
1875                    }
1876                    "join" => {
1877                        let sep = if args.is_empty() {
1878                            String::new()
1879                        } else {
1880                            args[0].display()
1881                        };
1882                        let joined: String = items
1883                            .iter()
1884                            .map(|v| v.display())
1885                            .collect::<Vec<_>>()
1886                            .join(&sep);
1887                        Ok(VmValue::String(Rc::from(joined)))
1888                    }
1889                    "contains" => {
1890                        let needle = args.first().unwrap_or(&VmValue::Nil);
1891                        Ok(VmValue::Bool(items.iter().any(|v| values_equal(v, needle))))
1892                    }
1893                    "index_of" => {
1894                        let needle = args.first().unwrap_or(&VmValue::Nil);
1895                        let idx = items.iter().position(|v| values_equal(v, needle));
1896                        Ok(VmValue::Int(idx.map(|i| i as i64).unwrap_or(-1)))
1897                    }
1898                    "enumerate" => {
1899                        let result: Vec<VmValue> = items
1900                            .iter()
1901                            .enumerate()
1902                            .map(|(i, v)| {
1903                                VmValue::Dict(Rc::new(BTreeMap::from([
1904                                    ("index".to_string(), VmValue::Int(i as i64)),
1905                                    ("value".to_string(), v.clone()),
1906                                ])))
1907                            })
1908                            .collect();
1909                        Ok(VmValue::List(Rc::new(result)))
1910                    }
1911                    "zip" => {
1912                        if let Some(VmValue::List(other)) = args.first() {
1913                            let result: Vec<VmValue> = items
1914                                .iter()
1915                                .zip(other.iter())
1916                                .map(|(a, b)| VmValue::List(Rc::new(vec![a.clone(), b.clone()])))
1917                                .collect();
1918                            Ok(VmValue::List(Rc::new(result)))
1919                        } else {
1920                            Ok(VmValue::List(Rc::new(Vec::new())))
1921                        }
1922                    }
1923                    "slice" => {
1924                        let len = items.len() as i64;
1925                        let start_raw = args.first().and_then(|a| a.as_int()).unwrap_or(0);
1926                        let start = if start_raw < 0 {
1927                            (len + start_raw).max(0) as usize
1928                        } else {
1929                            (start_raw.min(len)) as usize
1930                        };
1931                        let end = if args.len() > 1 {
1932                            let end_raw = args[1].as_int().unwrap_or(len);
1933                            if end_raw < 0 {
1934                                (len + end_raw).max(0) as usize
1935                            } else {
1936                                (end_raw.min(len)) as usize
1937                            }
1938                        } else {
1939                            len as usize
1940                        };
1941                        let end = end.max(start);
1942                        Ok(VmValue::List(Rc::new(items[start..end].to_vec())))
1943                    }
1944                    "unique" => {
1945                        let mut seen: Vec<VmValue> = Vec::new();
1946                        let mut result = Vec::new();
1947                        for item in items.iter() {
1948                            if !seen.iter().any(|s| values_equal(s, item)) {
1949                                seen.push(item.clone());
1950                                result.push(item.clone());
1951                            }
1952                        }
1953                        Ok(VmValue::List(Rc::new(result)))
1954                    }
1955                    "take" => {
1956                        let n = args.first().and_then(|a| a.as_int()).unwrap_or(0).max(0) as usize;
1957                        Ok(VmValue::List(Rc::new(
1958                            items.iter().take(n).cloned().collect(),
1959                        )))
1960                    }
1961                    "skip" => {
1962                        let n = args.first().and_then(|a| a.as_int()).unwrap_or(0).max(0) as usize;
1963                        Ok(VmValue::List(Rc::new(
1964                            items.iter().skip(n).cloned().collect(),
1965                        )))
1966                    }
1967                    "sum" => {
1968                        let mut int_sum: i64 = 0;
1969                        let mut has_float = false;
1970                        let mut float_sum: f64 = 0.0;
1971                        for item in items.iter() {
1972                            match item {
1973                                VmValue::Int(n) => {
1974                                    int_sum = int_sum.wrapping_add(*n);
1975                                    float_sum += *n as f64;
1976                                }
1977                                VmValue::Float(n) => {
1978                                    has_float = true;
1979                                    float_sum += n;
1980                                }
1981                                _ => {}
1982                            }
1983                        }
1984                        if has_float {
1985                            Ok(VmValue::Float(float_sum))
1986                        } else {
1987                            Ok(VmValue::Int(int_sum))
1988                        }
1989                    }
1990                    "min" => {
1991                        if items.is_empty() {
1992                            return Ok(VmValue::Nil);
1993                        }
1994                        let mut min_val = items[0].clone();
1995                        for item in &items[1..] {
1996                            if compare_values(item, &min_val) < 0 {
1997                                min_val = item.clone();
1998                            }
1999                        }
2000                        Ok(min_val)
2001                    }
2002                    "max" => {
2003                        if items.is_empty() {
2004                            return Ok(VmValue::Nil);
2005                        }
2006                        let mut max_val = items[0].clone();
2007                        for item in &items[1..] {
2008                            if compare_values(item, &max_val) > 0 {
2009                                max_val = item.clone();
2010                            }
2011                        }
2012                        Ok(max_val)
2013                    }
2014                    "flatten" => {
2015                        let mut result = Vec::new();
2016                        for item in items.iter() {
2017                            if let VmValue::List(inner) = item {
2018                                result.extend(inner.iter().cloned());
2019                            } else {
2020                                result.push(item.clone());
2021                            }
2022                        }
2023                        Ok(VmValue::List(Rc::new(result)))
2024                    }
2025                    "push" => {
2026                        let mut new_list: Vec<VmValue> = items.iter().cloned().collect();
2027                        if let Some(item) = args.first() {
2028                            new_list.push(item.clone());
2029                        }
2030                        Ok(VmValue::List(Rc::new(new_list)))
2031                    }
2032                    "pop" => {
2033                        let mut new_list: Vec<VmValue> = items.iter().cloned().collect();
2034                        new_list.pop();
2035                        Ok(VmValue::List(Rc::new(new_list)))
2036                    }
2037                    _ => Ok(VmValue::Nil),
2038                },
2039                VmValue::Dict(map) => match method {
2040                    "keys" => Ok(VmValue::List(Rc::new(
2041                        map.keys()
2042                            .map(|k| VmValue::String(Rc::from(k.as_str())))
2043                            .collect(),
2044                    ))),
2045                    "values" => Ok(VmValue::List(Rc::new(map.values().cloned().collect()))),
2046                    "entries" => Ok(VmValue::List(Rc::new(
2047                        map.iter()
2048                            .map(|(k, v)| {
2049                                VmValue::Dict(Rc::new(BTreeMap::from([
2050                                    ("key".to_string(), VmValue::String(Rc::from(k.as_str()))),
2051                                    ("value".to_string(), v.clone()),
2052                                ])))
2053                            })
2054                            .collect(),
2055                    ))),
2056                    "count" => Ok(VmValue::Int(map.len() as i64)),
2057                    "has" => Ok(VmValue::Bool(map.contains_key(
2058                        &args.first().map(|a| a.display()).unwrap_or_default(),
2059                    ))),
2060                    "merge" => {
2061                        if let Some(VmValue::Dict(other)) = args.first() {
2062                            let mut result = (**map).clone();
2063                            result.extend(other.iter().map(|(k, v)| (k.clone(), v.clone())));
2064                            Ok(VmValue::Dict(Rc::new(result)))
2065                        } else {
2066                            Ok(VmValue::Dict(Rc::clone(map)))
2067                        }
2068                    }
2069                    "map_values" => {
2070                        if let Some(VmValue::Closure(closure)) = args.first() {
2071                            let mut result = BTreeMap::new();
2072                            for (k, v) in map.iter() {
2073                                let mapped =
2074                                    self.call_closure(closure, &[v.clone()], functions).await?;
2075                                result.insert(k.clone(), mapped);
2076                            }
2077                            Ok(VmValue::Dict(Rc::new(result)))
2078                        } else {
2079                            Ok(VmValue::Nil)
2080                        }
2081                    }
2082                    "filter" => {
2083                        if let Some(VmValue::Closure(closure)) = args.first() {
2084                            let mut result = BTreeMap::new();
2085                            for (k, v) in map.iter() {
2086                                let keep =
2087                                    self.call_closure(closure, &[v.clone()], functions).await?;
2088                                if keep.is_truthy() {
2089                                    result.insert(k.clone(), v.clone());
2090                                }
2091                            }
2092                            Ok(VmValue::Dict(Rc::new(result)))
2093                        } else {
2094                            Ok(VmValue::Nil)
2095                        }
2096                    }
2097                    "remove" => {
2098                        let key = args.first().map(|a| a.display()).unwrap_or_default();
2099                        let mut result = (**map).clone();
2100                        result.remove(&key);
2101                        Ok(VmValue::Dict(Rc::new(result)))
2102                    }
2103                    "get" => {
2104                        let key = args.first().map(|a| a.display()).unwrap_or_default();
2105                        let default = args.get(1).cloned().unwrap_or(VmValue::Nil);
2106                        Ok(map.get(&key).cloned().unwrap_or(default))
2107                    }
2108                    _ => Ok(VmValue::Nil),
2109                },
2110                _ => Ok(VmValue::Nil),
2111            }
2112        })
2113    }
2114
2115    // --- Arithmetic helpers ---
2116
2117    fn add(&self, a: VmValue, b: VmValue) -> VmValue {
2118        match (&a, &b) {
2119            (VmValue::Int(x), VmValue::Int(y)) => VmValue::Int(x.wrapping_add(*y)),
2120            (VmValue::Float(x), VmValue::Float(y)) => VmValue::Float(x + y),
2121            (VmValue::Int(x), VmValue::Float(y)) => VmValue::Float(*x as f64 + y),
2122            (VmValue::Float(x), VmValue::Int(y)) => VmValue::Float(x + *y as f64),
2123            (VmValue::String(x), _) => VmValue::String(Rc::from(format!("{x}{}", b.display()))),
2124            (VmValue::List(x), VmValue::List(y)) => {
2125                let mut result = (**x).clone();
2126                result.extend(y.iter().cloned());
2127                VmValue::List(Rc::new(result))
2128            }
2129            (VmValue::Dict(x), VmValue::Dict(y)) => {
2130                let mut result = (**x).clone();
2131                result.extend(y.iter().map(|(k, v)| (k.clone(), v.clone())));
2132                VmValue::Dict(Rc::new(result))
2133            }
2134            _ => VmValue::String(Rc::from(format!("{}{}", a.display(), b.display()))),
2135        }
2136    }
2137
2138    fn sub(&self, a: VmValue, b: VmValue) -> VmValue {
2139        match (&a, &b) {
2140            (VmValue::Int(x), VmValue::Int(y)) => VmValue::Int(x.wrapping_sub(*y)),
2141            (VmValue::Float(x), VmValue::Float(y)) => VmValue::Float(x - y),
2142            (VmValue::Int(x), VmValue::Float(y)) => VmValue::Float(*x as f64 - y),
2143            (VmValue::Float(x), VmValue::Int(y)) => VmValue::Float(x - *y as f64),
2144            _ => VmValue::Nil,
2145        }
2146    }
2147
2148    fn mul(&self, a: VmValue, b: VmValue) -> VmValue {
2149        match (&a, &b) {
2150            (VmValue::Int(x), VmValue::Int(y)) => VmValue::Int(x.wrapping_mul(*y)),
2151            (VmValue::Float(x), VmValue::Float(y)) => VmValue::Float(x * y),
2152            (VmValue::Int(x), VmValue::Float(y)) => VmValue::Float(*x as f64 * y),
2153            (VmValue::Float(x), VmValue::Int(y)) => VmValue::Float(x * *y as f64),
2154            _ => VmValue::Nil,
2155        }
2156    }
2157
2158    fn div(&self, a: VmValue, b: VmValue) -> Result<VmValue, VmError> {
2159        match (&a, &b) {
2160            (VmValue::Int(_), VmValue::Int(y)) if *y == 0 => Err(VmError::DivisionByZero),
2161            (VmValue::Int(x), VmValue::Int(y)) => Ok(VmValue::Int(x / y)),
2162            (VmValue::Float(_), VmValue::Float(y)) if *y == 0.0 => Err(VmError::DivisionByZero),
2163            (VmValue::Float(x), VmValue::Float(y)) => Ok(VmValue::Float(x / y)),
2164            (VmValue::Int(_), VmValue::Float(y)) if *y == 0.0 => Err(VmError::DivisionByZero),
2165            (VmValue::Int(x), VmValue::Float(y)) => Ok(VmValue::Float(*x as f64 / y)),
2166            (VmValue::Float(_), VmValue::Int(y)) if *y == 0 => Err(VmError::DivisionByZero),
2167            (VmValue::Float(x), VmValue::Int(y)) => Ok(VmValue::Float(x / *y as f64)),
2168            _ => Err(VmError::Runtime(format!(
2169                "Cannot divide {} by {}",
2170                a.type_name(),
2171                b.type_name()
2172            ))),
2173        }
2174    }
2175
2176    fn modulo(&self, a: VmValue, b: VmValue) -> Result<VmValue, VmError> {
2177        match (&a, &b) {
2178            (VmValue::Int(_), VmValue::Int(y)) if *y == 0 => Err(VmError::DivisionByZero),
2179            (VmValue::Int(x), VmValue::Int(y)) => Ok(VmValue::Int(x % y)),
2180            (VmValue::Float(_), VmValue::Float(y)) if *y == 0.0 => Err(VmError::DivisionByZero),
2181            (VmValue::Float(x), VmValue::Float(y)) => Ok(VmValue::Float(x % y)),
2182            (VmValue::Int(_), VmValue::Float(y)) if *y == 0.0 => Err(VmError::DivisionByZero),
2183            (VmValue::Int(x), VmValue::Float(y)) => Ok(VmValue::Float(*x as f64 % y)),
2184            (VmValue::Float(_), VmValue::Int(y)) if *y == 0 => Err(VmError::DivisionByZero),
2185            (VmValue::Float(x), VmValue::Int(y)) => Ok(VmValue::Float(x % *y as f64)),
2186            _ => Err(VmError::Runtime(format!(
2187                "Cannot modulo {} by {}",
2188                a.type_name(),
2189                b.type_name()
2190            ))),
2191        }
2192    }
2193
2194    /// Format a runtime error with stack trace and source context.
2195    ///
2196    /// Produces rustc-style output with source context and caret highlighting:
2197    /// ```text
2198    /// error: Undefined variable: x
2199    ///   --> file.harn:5:12
2200    ///    |
2201    ///  5 |     let y = x + 1
2202    ///    |             ^
2203    ///    = note: called from greet at file.harn:2:1
2204    /// ```
2205    pub fn format_runtime_error(&self, error: &VmError) -> String {
2206        let source = match &self.source_text {
2207            Some(s) => s.as_str(),
2208            None => return format!("error: {error}"),
2209        };
2210        let filename = self.source_file.as_deref().unwrap_or("<unknown>");
2211
2212        let error_msg = format!("{error}");
2213        let mut out = String::new();
2214
2215        // Error header
2216        out.push_str(&format!("error: {error_msg}\n"));
2217
2218        // Get the error location from the top frame (line, column)
2219        let frames: Vec<(&str, usize, usize)> = self
2220            .frames
2221            .iter()
2222            .map(|f| {
2223                let idx = if f.ip > 0 { f.ip - 1 } else { 0 };
2224                let line = if idx < f.chunk.lines.len() {
2225                    f.chunk.lines[idx] as usize
2226                } else {
2227                    0
2228                };
2229                let col = if idx < f.chunk.columns.len() {
2230                    f.chunk.columns[idx] as usize
2231                } else {
2232                    0
2233                };
2234                (f.fn_name.as_str(), line, col)
2235            })
2236            .collect();
2237
2238        if let Some((_name, line, col)) = frames.last() {
2239            let line = *line;
2240            let col = *col;
2241            if line > 0 {
2242                let display_col = if col > 0 { col } else { 1 };
2243                let gutter_width = line.to_string().len();
2244                out.push_str(&format!(
2245                    "{:>width$}--> {filename}:{line}:{display_col}\n",
2246                    " ",
2247                    width = gutter_width + 1,
2248                ));
2249                // Show source line with caret
2250                if let Some(source_line) = source.lines().nth(line.saturating_sub(1)) {
2251                    out.push_str(&format!("{:>width$} |\n", " ", width = gutter_width + 1));
2252                    out.push_str(&format!(
2253                        "{:>width$} | {source_line}\n",
2254                        line,
2255                        width = gutter_width + 1,
2256                    ));
2257                    // Render caret line
2258                    let caret_col = if col > 0 { col } else { 1 };
2259                    let trimmed = source_line.trim();
2260                    let leading = source_line
2261                        .len()
2262                        .saturating_sub(source_line.trim_start().len());
2263                    // Calculate how many carets to show
2264                    let caret_len = if col > 0 {
2265                        // Try to find a reasonable token length at this column
2266                        Self::token_len_at(source_line, col)
2267                    } else {
2268                        // No column info: underline the trimmed content
2269                        trimmed.len().max(1)
2270                    };
2271                    let padding = if col > 0 {
2272                        " ".repeat(caret_col.saturating_sub(1))
2273                    } else {
2274                        " ".repeat(leading)
2275                    };
2276                    let carets = "^".repeat(caret_len);
2277                    out.push_str(&format!(
2278                        "{:>width$} | {padding}{carets}\n",
2279                        " ",
2280                        width = gutter_width + 1,
2281                    ));
2282                }
2283            }
2284        }
2285
2286        // Show call stack (bottom-up, skipping top frame which is already shown)
2287        if frames.len() > 1 {
2288            for (name, line, _col) in frames.iter().rev().skip(1) {
2289                let display_name = if name.is_empty() { "pipeline" } else { name };
2290                if *line > 0 {
2291                    out.push_str(&format!(
2292                        "  = note: called from {display_name} at {filename}:{line}\n"
2293                    ));
2294                }
2295            }
2296        }
2297
2298        out
2299    }
2300
2301    /// Estimate the length of the token at the given 1-based column position
2302    /// in a source line. Scans forward from that position to find a word/operator
2303    /// boundary.
2304    fn token_len_at(source_line: &str, col: usize) -> usize {
2305        let chars: Vec<char> = source_line.chars().collect();
2306        let start = col.saturating_sub(1);
2307        if start >= chars.len() {
2308            return 1;
2309        }
2310        let first = chars[start];
2311        if first.is_alphanumeric() || first == '_' {
2312            // Scan forward through identifier/number chars
2313            let mut end = start + 1;
2314            while end < chars.len() && (chars[end].is_alphanumeric() || chars[end] == '_') {
2315                end += 1;
2316            }
2317            end - start
2318        } else {
2319            // Operator or punctuation: just one caret
2320            1
2321        }
2322    }
2323}
2324
2325impl Default for Vm {
2326    fn default() -> Self {
2327        Self::new()
2328    }
2329}
2330
2331#[cfg(test)]
2332mod tests {
2333    use super::*;
2334    use crate::compiler::Compiler;
2335    use crate::stdlib::register_vm_stdlib;
2336    use harn_lexer::Lexer;
2337    use harn_parser::Parser;
2338
2339    fn run_harn(source: &str) -> (String, VmValue) {
2340        let rt = tokio::runtime::Builder::new_current_thread()
2341            .enable_all()
2342            .build()
2343            .unwrap();
2344        rt.block_on(async {
2345            let local = tokio::task::LocalSet::new();
2346            local
2347                .run_until(async {
2348                    let mut lexer = Lexer::new(source);
2349                    let tokens = lexer.tokenize().unwrap();
2350                    let mut parser = Parser::new(tokens);
2351                    let program = parser.parse().unwrap();
2352                    let chunk = Compiler::new().compile(&program).unwrap();
2353
2354                    let mut vm = Vm::new();
2355                    register_vm_stdlib(&mut vm);
2356                    let result = vm.execute(&chunk).await.unwrap();
2357                    (vm.output().to_string(), result)
2358                })
2359                .await
2360        })
2361    }
2362
2363    fn run_output(source: &str) -> String {
2364        run_harn(source).0.trim_end().to_string()
2365    }
2366
2367    fn run_harn_result(source: &str) -> Result<(String, VmValue), VmError> {
2368        let rt = tokio::runtime::Builder::new_current_thread()
2369            .enable_all()
2370            .build()
2371            .unwrap();
2372        rt.block_on(async {
2373            let local = tokio::task::LocalSet::new();
2374            local
2375                .run_until(async {
2376                    let mut lexer = Lexer::new(source);
2377                    let tokens = lexer.tokenize().unwrap();
2378                    let mut parser = Parser::new(tokens);
2379                    let program = parser.parse().unwrap();
2380                    let chunk = Compiler::new().compile(&program).unwrap();
2381
2382                    let mut vm = Vm::new();
2383                    register_vm_stdlib(&mut vm);
2384                    let result = vm.execute(&chunk).await?;
2385                    Ok((vm.output().to_string(), result))
2386                })
2387                .await
2388        })
2389    }
2390
2391    #[test]
2392    fn test_arithmetic() {
2393        let out =
2394            run_output("pipeline t(task) { log(2 + 3)\nlog(10 - 4)\nlog(3 * 5)\nlog(10 / 3) }");
2395        assert_eq!(out, "[harn] 5\n[harn] 6\n[harn] 15\n[harn] 3");
2396    }
2397
2398    #[test]
2399    fn test_mixed_arithmetic() {
2400        let out = run_output("pipeline t(task) { log(3 + 1.5)\nlog(10 - 2.5) }");
2401        assert_eq!(out, "[harn] 4.5\n[harn] 7.5");
2402    }
2403
2404    #[test]
2405    fn test_comparisons() {
2406        let out =
2407            run_output("pipeline t(task) { log(1 < 2)\nlog(2 > 3)\nlog(1 == 1)\nlog(1 != 2) }");
2408        assert_eq!(out, "[harn] true\n[harn] false\n[harn] true\n[harn] true");
2409    }
2410
2411    #[test]
2412    fn test_let_var() {
2413        let out = run_output("pipeline t(task) { let x = 42\nlog(x)\nvar y = 1\ny = 2\nlog(y) }");
2414        assert_eq!(out, "[harn] 42\n[harn] 2");
2415    }
2416
2417    #[test]
2418    fn test_if_else() {
2419        let out = run_output(
2420            r#"pipeline t(task) { if true { log("yes") } if false { log("wrong") } else { log("no") } }"#,
2421        );
2422        assert_eq!(out, "[harn] yes\n[harn] no");
2423    }
2424
2425    #[test]
2426    fn test_while_loop() {
2427        let out = run_output("pipeline t(task) { var i = 0\n while i < 5 { i = i + 1 }\n log(i) }");
2428        assert_eq!(out, "[harn] 5");
2429    }
2430
2431    #[test]
2432    fn test_for_in() {
2433        let out = run_output("pipeline t(task) { for item in [1, 2, 3] { log(item) } }");
2434        assert_eq!(out, "[harn] 1\n[harn] 2\n[harn] 3");
2435    }
2436
2437    #[test]
2438    fn test_fn_decl_and_call() {
2439        let out = run_output("pipeline t(task) { fn add(a, b) { return a + b }\nlog(add(3, 4)) }");
2440        assert_eq!(out, "[harn] 7");
2441    }
2442
2443    #[test]
2444    fn test_closure() {
2445        let out = run_output("pipeline t(task) { let double = { x -> x * 2 }\nlog(double(5)) }");
2446        assert_eq!(out, "[harn] 10");
2447    }
2448
2449    #[test]
2450    fn test_closure_capture() {
2451        let out = run_output(
2452            "pipeline t(task) { let base = 10\nfn offset(x) { return x + base }\nlog(offset(5)) }",
2453        );
2454        assert_eq!(out, "[harn] 15");
2455    }
2456
2457    #[test]
2458    fn test_string_concat() {
2459        let out = run_output(
2460            r#"pipeline t(task) { let a = "hello" + " " + "world"
2461log(a) }"#,
2462        );
2463        assert_eq!(out, "[harn] hello world");
2464    }
2465
2466    #[test]
2467    fn test_list_map() {
2468        let out = run_output(
2469            "pipeline t(task) { let doubled = [1, 2, 3].map({ x -> x * 2 })\nlog(doubled) }",
2470        );
2471        assert_eq!(out, "[harn] [2, 4, 6]");
2472    }
2473
2474    #[test]
2475    fn test_list_filter() {
2476        let out = run_output(
2477            "pipeline t(task) { let big = [1, 2, 3, 4, 5].filter({ x -> x > 3 })\nlog(big) }",
2478        );
2479        assert_eq!(out, "[harn] [4, 5]");
2480    }
2481
2482    #[test]
2483    fn test_list_reduce() {
2484        let out = run_output(
2485            "pipeline t(task) { let sum = [1, 2, 3, 4].reduce(0, { acc, x -> acc + x })\nlog(sum) }",
2486        );
2487        assert_eq!(out, "[harn] 10");
2488    }
2489
2490    #[test]
2491    fn test_dict_access() {
2492        let out = run_output(
2493            r#"pipeline t(task) { let d = {name: "test", value: 42}
2494log(d.name)
2495log(d.value) }"#,
2496        );
2497        assert_eq!(out, "[harn] test\n[harn] 42");
2498    }
2499
2500    #[test]
2501    fn test_dict_methods() {
2502        let out = run_output(
2503            r#"pipeline t(task) { let d = {a: 1, b: 2}
2504log(d.keys())
2505log(d.values())
2506log(d.has("a"))
2507log(d.has("z")) }"#,
2508        );
2509        assert_eq!(
2510            out,
2511            "[harn] [a, b]\n[harn] [1, 2]\n[harn] true\n[harn] false"
2512        );
2513    }
2514
2515    #[test]
2516    fn test_pipe_operator() {
2517        let out = run_output(
2518            "pipeline t(task) { fn double(x) { return x * 2 }\nlet r = 5 |> double\nlog(r) }",
2519        );
2520        assert_eq!(out, "[harn] 10");
2521    }
2522
2523    #[test]
2524    fn test_pipe_with_closure() {
2525        let out = run_output(
2526            r#"pipeline t(task) { let r = "hello world" |> { s -> s.split(" ") }
2527log(r) }"#,
2528        );
2529        assert_eq!(out, "[harn] [hello, world]");
2530    }
2531
2532    #[test]
2533    fn test_nil_coalescing() {
2534        let out = run_output(
2535            r#"pipeline t(task) { let a = nil ?? "fallback"
2536log(a)
2537let b = "present" ?? "fallback"
2538log(b) }"#,
2539        );
2540        assert_eq!(out, "[harn] fallback\n[harn] present");
2541    }
2542
2543    #[test]
2544    fn test_logical_operators() {
2545        let out =
2546            run_output("pipeline t(task) { log(true && false)\nlog(true || false)\nlog(!true) }");
2547        assert_eq!(out, "[harn] false\n[harn] true\n[harn] false");
2548    }
2549
2550    #[test]
2551    fn test_match() {
2552        let out = run_output(
2553            r#"pipeline t(task) { let x = "b"
2554match x { "a" -> { log("first") } "b" -> { log("second") } "c" -> { log("third") } } }"#,
2555        );
2556        assert_eq!(out, "[harn] second");
2557    }
2558
2559    #[test]
2560    fn test_subscript() {
2561        let out = run_output("pipeline t(task) { let arr = [10, 20, 30]\nlog(arr[1]) }");
2562        assert_eq!(out, "[harn] 20");
2563    }
2564
2565    #[test]
2566    fn test_string_methods() {
2567        let out = run_output(
2568            r#"pipeline t(task) { log("hello world".replace("world", "harn"))
2569log("a,b,c".split(","))
2570log("  hello  ".trim())
2571log("hello".starts_with("hel"))
2572log("hello".ends_with("lo"))
2573log("hello".substring(1, 3)) }"#,
2574        );
2575        assert_eq!(
2576            out,
2577            "[harn] hello harn\n[harn] [a, b, c]\n[harn] hello\n[harn] true\n[harn] true\n[harn] el"
2578        );
2579    }
2580
2581    #[test]
2582    fn test_list_properties() {
2583        let out = run_output(
2584            "pipeline t(task) { let list = [1, 2, 3]\nlog(list.count)\nlog(list.empty)\nlog(list.first)\nlog(list.last) }",
2585        );
2586        assert_eq!(out, "[harn] 3\n[harn] false\n[harn] 1\n[harn] 3");
2587    }
2588
2589    #[test]
2590    fn test_recursive_function() {
2591        let out = run_output(
2592            "pipeline t(task) { fn fib(n) { if n <= 1 { return n } return fib(n - 1) + fib(n - 2) }\nlog(fib(10)) }",
2593        );
2594        assert_eq!(out, "[harn] 55");
2595    }
2596
2597    #[test]
2598    fn test_ternary() {
2599        let out = run_output(
2600            r#"pipeline t(task) { let x = 5
2601let r = x > 0 ? "positive" : "non-positive"
2602log(r) }"#,
2603        );
2604        assert_eq!(out, "[harn] positive");
2605    }
2606
2607    #[test]
2608    fn test_for_in_dict() {
2609        let out = run_output(
2610            "pipeline t(task) { let d = {a: 1, b: 2}\nfor entry in d { log(entry.key) } }",
2611        );
2612        assert_eq!(out, "[harn] a\n[harn] b");
2613    }
2614
2615    #[test]
2616    fn test_list_any_all() {
2617        let out = run_output(
2618            "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 })) }",
2619        );
2620        assert_eq!(out, "[harn] true\n[harn] true\n[harn] false");
2621    }
2622
2623    #[test]
2624    fn test_disassembly() {
2625        let mut lexer = Lexer::new("pipeline t(task) { log(2 + 3) }");
2626        let tokens = lexer.tokenize().unwrap();
2627        let mut parser = Parser::new(tokens);
2628        let program = parser.parse().unwrap();
2629        let chunk = Compiler::new().compile(&program).unwrap();
2630        let disasm = chunk.disassemble("test");
2631        assert!(disasm.contains("CONSTANT"));
2632        assert!(disasm.contains("ADD"));
2633        assert!(disasm.contains("CALL"));
2634    }
2635
2636    // --- Error handling tests ---
2637
2638    #[test]
2639    fn test_try_catch_basic() {
2640        let out = run_output(
2641            r#"pipeline t(task) { try { throw "oops" } catch(e) { log("caught: " + e) } }"#,
2642        );
2643        assert_eq!(out, "[harn] caught: oops");
2644    }
2645
2646    #[test]
2647    fn test_try_no_error() {
2648        let out = run_output(
2649            r#"pipeline t(task) {
2650var result = 0
2651try { result = 42 } catch(e) { result = 0 }
2652log(result)
2653}"#,
2654        );
2655        assert_eq!(out, "[harn] 42");
2656    }
2657
2658    #[test]
2659    fn test_throw_uncaught() {
2660        let result = run_harn_result(r#"pipeline t(task) { throw "boom" }"#);
2661        assert!(result.is_err());
2662    }
2663
2664    // --- Additional test coverage ---
2665
2666    fn run_vm(source: &str) -> String {
2667        let rt = tokio::runtime::Builder::new_current_thread()
2668            .enable_all()
2669            .build()
2670            .unwrap();
2671        rt.block_on(async {
2672            let local = tokio::task::LocalSet::new();
2673            local
2674                .run_until(async {
2675                    let mut lexer = Lexer::new(source);
2676                    let tokens = lexer.tokenize().unwrap();
2677                    let mut parser = Parser::new(tokens);
2678                    let program = parser.parse().unwrap();
2679                    let chunk = Compiler::new().compile(&program).unwrap();
2680                    let mut vm = Vm::new();
2681                    register_vm_stdlib(&mut vm);
2682                    vm.execute(&chunk).await.unwrap();
2683                    vm.output().to_string()
2684                })
2685                .await
2686        })
2687    }
2688
2689    fn run_vm_err(source: &str) -> String {
2690        let rt = tokio::runtime::Builder::new_current_thread()
2691            .enable_all()
2692            .build()
2693            .unwrap();
2694        rt.block_on(async {
2695            let local = tokio::task::LocalSet::new();
2696            local
2697                .run_until(async {
2698                    let mut lexer = Lexer::new(source);
2699                    let tokens = lexer.tokenize().unwrap();
2700                    let mut parser = Parser::new(tokens);
2701                    let program = parser.parse().unwrap();
2702                    let chunk = Compiler::new().compile(&program).unwrap();
2703                    let mut vm = Vm::new();
2704                    register_vm_stdlib(&mut vm);
2705                    match vm.execute(&chunk).await {
2706                        Err(e) => format!("{}", e),
2707                        Ok(_) => panic!("Expected error"),
2708                    }
2709                })
2710                .await
2711        })
2712    }
2713
2714    #[test]
2715    fn test_hello_world() {
2716        let out = run_vm(r#"pipeline default(task) { log("hello") }"#);
2717        assert_eq!(out, "[harn] hello\n");
2718    }
2719
2720    #[test]
2721    fn test_arithmetic_new() {
2722        let out = run_vm("pipeline default(task) { log(2 + 3) }");
2723        assert_eq!(out, "[harn] 5\n");
2724    }
2725
2726    #[test]
2727    fn test_string_concat_new() {
2728        let out = run_vm(r#"pipeline default(task) { log("a" + "b") }"#);
2729        assert_eq!(out, "[harn] ab\n");
2730    }
2731
2732    #[test]
2733    fn test_if_else_new() {
2734        let out = run_vm("pipeline default(task) { if true { log(1) } else { log(2) } }");
2735        assert_eq!(out, "[harn] 1\n");
2736    }
2737
2738    #[test]
2739    fn test_for_loop_new() {
2740        let out = run_vm("pipeline default(task) { for i in [1, 2, 3] { log(i) } }");
2741        assert_eq!(out, "[harn] 1\n[harn] 2\n[harn] 3\n");
2742    }
2743
2744    #[test]
2745    fn test_while_loop_new() {
2746        let out = run_vm("pipeline default(task) { var i = 0\nwhile i < 3 { log(i)\ni = i + 1 } }");
2747        assert_eq!(out, "[harn] 0\n[harn] 1\n[harn] 2\n");
2748    }
2749
2750    #[test]
2751    fn test_function_call_new() {
2752        let out =
2753            run_vm("pipeline default(task) { fn add(a, b) { return a + b }\nlog(add(2, 3)) }");
2754        assert_eq!(out, "[harn] 5\n");
2755    }
2756
2757    #[test]
2758    fn test_closure_new() {
2759        let out = run_vm("pipeline default(task) { let f = { x -> x * 2 }\nlog(f(5)) }");
2760        assert_eq!(out, "[harn] 10\n");
2761    }
2762
2763    #[test]
2764    fn test_recursion() {
2765        let out = run_vm("pipeline default(task) { fn fact(n) { if n <= 1 { return 1 }\nreturn n * fact(n - 1) }\nlog(fact(5)) }");
2766        assert_eq!(out, "[harn] 120\n");
2767    }
2768
2769    #[test]
2770    fn test_try_catch_new() {
2771        let out = run_vm(r#"pipeline default(task) { try { throw "err" } catch (e) { log(e) } }"#);
2772        assert_eq!(out, "[harn] err\n");
2773    }
2774
2775    #[test]
2776    fn test_try_no_error_new() {
2777        let out = run_vm("pipeline default(task) { try { log(1) } catch (e) { log(2) } }");
2778        assert_eq!(out, "[harn] 1\n");
2779    }
2780
2781    #[test]
2782    fn test_list_map_new() {
2783        let out =
2784            run_vm("pipeline default(task) { let r = [1, 2, 3].map({ x -> x * 2 })\nlog(r) }");
2785        assert_eq!(out, "[harn] [2, 4, 6]\n");
2786    }
2787
2788    #[test]
2789    fn test_list_filter_new() {
2790        let out = run_vm(
2791            "pipeline default(task) { let r = [1, 2, 3, 4].filter({ x -> x > 2 })\nlog(r) }",
2792        );
2793        assert_eq!(out, "[harn] [3, 4]\n");
2794    }
2795
2796    #[test]
2797    fn test_dict_access_new() {
2798        let out = run_vm("pipeline default(task) { let d = {name: \"Alice\"}\nlog(d.name) }");
2799        assert_eq!(out, "[harn] Alice\n");
2800    }
2801
2802    #[test]
2803    fn test_string_interpolation() {
2804        let out = run_vm("pipeline default(task) { let x = 42\nlog(\"val=${x}\") }");
2805        assert_eq!(out, "[harn] val=42\n");
2806    }
2807
2808    #[test]
2809    fn test_match_new() {
2810        let out = run_vm(
2811            "pipeline default(task) { let x = \"b\"\nmatch x { \"a\" -> { log(1) } \"b\" -> { log(2) } } }",
2812        );
2813        assert_eq!(out, "[harn] 2\n");
2814    }
2815
2816    #[test]
2817    fn test_json_roundtrip() {
2818        let out = run_vm("pipeline default(task) { let s = json_stringify({a: 1})\nlog(s) }");
2819        assert!(out.contains("\"a\""));
2820        assert!(out.contains("1"));
2821    }
2822
2823    #[test]
2824    fn test_type_of() {
2825        let out = run_vm("pipeline default(task) { log(type_of(42))\nlog(type_of(\"hi\")) }");
2826        assert_eq!(out, "[harn] int\n[harn] string\n");
2827    }
2828
2829    #[test]
2830    fn test_stack_overflow() {
2831        let err = run_vm_err("pipeline default(task) { fn f() { f() }\nf() }");
2832        assert!(
2833            err.contains("stack") || err.contains("overflow") || err.contains("recursion"),
2834            "Expected stack overflow error, got: {}",
2835            err
2836        );
2837    }
2838
2839    #[test]
2840    fn test_division_by_zero() {
2841        let err = run_vm_err("pipeline default(task) { log(1 / 0) }");
2842        assert!(
2843            err.contains("Division by zero") || err.contains("division"),
2844            "Expected division by zero error, got: {}",
2845            err
2846        );
2847    }
2848
2849    #[test]
2850    fn test_try_catch_nested() {
2851        let out = run_output(
2852            r#"pipeline t(task) {
2853try {
2854    try {
2855        throw "inner"
2856    } catch(e) {
2857        log("inner caught: " + e)
2858        throw "outer"
2859    }
2860} catch(e) {
2861    log("outer caught: " + e)
2862}
2863}"#,
2864        );
2865        assert_eq!(
2866            out,
2867            "[harn] inner caught: inner\n[harn] outer caught: outer"
2868        );
2869    }
2870
2871    // --- Concurrency tests ---
2872
2873    #[test]
2874    fn test_parallel_basic() {
2875        let out = run_output(
2876            "pipeline t(task) { let results = parallel(3) { i -> i * 10 }\nlog(results) }",
2877        );
2878        assert_eq!(out, "[harn] [0, 10, 20]");
2879    }
2880
2881    #[test]
2882    fn test_parallel_no_variable() {
2883        let out = run_output("pipeline t(task) { let results = parallel(3) { 42 }\nlog(results) }");
2884        assert_eq!(out, "[harn] [42, 42, 42]");
2885    }
2886
2887    #[test]
2888    fn test_parallel_map_basic() {
2889        let out = run_output(
2890            "pipeline t(task) { let results = parallel_map([1, 2, 3]) { x -> x * x }\nlog(results) }",
2891        );
2892        assert_eq!(out, "[harn] [1, 4, 9]");
2893    }
2894
2895    #[test]
2896    fn test_spawn_await() {
2897        let out = run_output(
2898            r#"pipeline t(task) {
2899let handle = spawn { log("spawned") }
2900let result = await(handle)
2901log("done")
2902}"#,
2903        );
2904        assert_eq!(out, "[harn] spawned\n[harn] done");
2905    }
2906
2907    #[test]
2908    fn test_spawn_cancel() {
2909        let out = run_output(
2910            r#"pipeline t(task) {
2911let handle = spawn { log("should be cancelled") }
2912cancel(handle)
2913log("cancelled")
2914}"#,
2915        );
2916        assert_eq!(out, "[harn] cancelled");
2917    }
2918
2919    #[test]
2920    fn test_spawn_returns_value() {
2921        let out = run_output("pipeline t(task) { let h = spawn { 42 }\nlet r = await(h)\nlog(r) }");
2922        assert_eq!(out, "[harn] 42");
2923    }
2924
2925    // --- Deadline tests ---
2926
2927    #[test]
2928    fn test_deadline_success() {
2929        let out = run_output(
2930            r#"pipeline t(task) {
2931let result = deadline 5s { log("within deadline")
293242 }
2933log(result)
2934}"#,
2935        );
2936        assert_eq!(out, "[harn] within deadline\n[harn] 42");
2937    }
2938
2939    #[test]
2940    fn test_deadline_exceeded() {
2941        let result = run_harn_result(
2942            r#"pipeline t(task) {
2943deadline 1ms {
2944  var i = 0
2945  while i < 1000000 { i = i + 1 }
2946}
2947}"#,
2948        );
2949        assert!(result.is_err());
2950    }
2951
2952    #[test]
2953    fn test_deadline_caught_by_try() {
2954        let out = run_output(
2955            r#"pipeline t(task) {
2956try {
2957  deadline 1ms {
2958    var i = 0
2959    while i < 1000000 { i = i + 1 }
2960  }
2961} catch(e) {
2962  log("caught")
2963}
2964}"#,
2965        );
2966        assert_eq!(out, "[harn] caught");
2967    }
2968}