Skip to main content

harn_vm/
vm.rs

1mod format;
2mod methods;
3mod ops;
4
5use std::collections::{BTreeMap, HashSet};
6use std::future::Future;
7use std::pin::Pin;
8use std::rc::Rc;
9use std::time::Instant;
10
11use crate::chunk::{Chunk, CompiledFunction, Constant};
12use crate::value::{
13    ErrorCategory, VmAsyncBuiltinFn, VmBuiltinFn, VmClosure, VmEnv, VmError, VmTaskHandle, VmValue,
14};
15
16/// RAII guard that starts a tracing span on creation and ends it on drop.
17struct ScopeSpan(u64);
18
19impl ScopeSpan {
20    fn new(kind: crate::tracing::SpanKind, name: String) -> Self {
21        Self(crate::tracing::span_start(kind, name))
22    }
23}
24
25impl Drop for ScopeSpan {
26    fn drop(&mut self) {
27        crate::tracing::span_end(self.0);
28    }
29}
30
31/// Call frame for function execution.
32pub(crate) struct CallFrame {
33    pub(crate) chunk: Chunk,
34    pub(crate) ip: usize,
35    pub(crate) stack_base: usize,
36    pub(crate) saved_env: VmEnv,
37    /// Function name for stack traces (empty for top-level pipeline).
38    pub(crate) fn_name: String,
39    /// Number of arguments actually passed by the caller (for default arg support).
40    pub(crate) argc: usize,
41}
42
43/// Exception handler for try/catch.
44pub(crate) struct ExceptionHandler {
45    pub(crate) catch_ip: usize,
46    pub(crate) stack_depth: usize,
47    pub(crate) frame_depth: usize,
48    /// If non-empty, this catch only handles errors whose enum_name matches.
49    pub(crate) error_type: String,
50}
51
52/// Debug action returned by the debug hook.
53#[derive(Debug, Clone, PartialEq)]
54pub enum DebugAction {
55    /// Continue execution normally.
56    Continue,
57    /// Stop (breakpoint hit, step complete).
58    Stop,
59}
60
61/// Information about current execution state for the debugger.
62#[derive(Debug, Clone)]
63pub struct DebugState {
64    pub line: usize,
65    pub variables: BTreeMap<String, VmValue>,
66    pub frame_name: String,
67    pub frame_depth: usize,
68}
69
70/// Iterator state for for-in loops: either a pre-collected vec, an async channel, or a generator.
71pub(crate) enum IterState {
72    Vec {
73        items: Vec<VmValue>,
74        idx: usize,
75    },
76    Channel {
77        receiver: std::sync::Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<VmValue>>>,
78        closed: std::sync::Arc<std::sync::atomic::AtomicBool>,
79    },
80    Generator {
81        gen: crate::value::VmGenerator,
82    },
83}
84
85/// The Harn bytecode virtual machine.
86pub struct Vm {
87    pub(crate) stack: Vec<VmValue>,
88    pub(crate) env: VmEnv,
89    pub(crate) output: String,
90    pub(crate) builtins: BTreeMap<String, VmBuiltinFn>,
91    pub(crate) async_builtins: BTreeMap<String, VmAsyncBuiltinFn>,
92    /// Iterator state for for-in loops.
93    pub(crate) iterators: Vec<IterState>,
94    /// Call frame stack.
95    pub(crate) frames: Vec<CallFrame>,
96    /// Exception handler stack.
97    pub(crate) exception_handlers: Vec<ExceptionHandler>,
98    /// Spawned async task handles.
99    pub(crate) spawned_tasks: BTreeMap<String, VmTaskHandle>,
100    /// Counter for generating unique task IDs.
101    pub(crate) task_counter: u64,
102    /// Active deadline stack: (deadline_instant, frame_depth).
103    pub(crate) deadlines: Vec<(Instant, usize)>,
104    /// Breakpoints (source line numbers).
105    pub(crate) breakpoints: Vec<usize>,
106    /// Whether the VM is in step mode.
107    pub(crate) step_mode: bool,
108    /// The frame depth at which stepping started (for step-over).
109    pub(crate) step_frame_depth: usize,
110    /// Whether the VM is currently stopped at a debug point.
111    pub(crate) stopped: bool,
112    /// Last source line executed (to detect line changes).
113    pub(crate) last_line: usize,
114    /// Source directory for resolving imports.
115    pub(crate) source_dir: Option<std::path::PathBuf>,
116    /// Already-imported file paths (cycle prevention).
117    pub(crate) imported_paths: Vec<std::path::PathBuf>,
118    /// Source file path for error reporting.
119    pub(crate) source_file: Option<String>,
120    /// Source text for error reporting.
121    pub(crate) source_text: Option<String>,
122    /// Optional bridge for delegating unknown builtins in bridge mode.
123    pub(crate) bridge: Option<Rc<crate::bridge::HostBridge>>,
124    /// Builtins denied by sandbox mode (`--deny` / `--allow` flags).
125    pub(crate) denied_builtins: HashSet<String>,
126    /// Cancellation token for cooperative graceful shutdown (set by parent).
127    pub(crate) cancel_token: Option<std::sync::Arc<std::sync::atomic::AtomicBool>>,
128    /// Captured stack trace from the most recent error (fn_name, line, col).
129    pub(crate) error_stack_trace: Vec<(String, usize, usize)>,
130    /// Yield channel sender for generator execution. When set, `Op::Yield`
131    /// sends values through this channel instead of being a no-op.
132    pub(crate) yield_sender: Option<tokio::sync::mpsc::Sender<VmValue>>,
133    /// Project root directory (detected via harn.toml).
134    /// Used as base directory for metadata, store, and checkpoint operations.
135    pub(crate) project_root: Option<std::path::PathBuf>,
136    /// Global constants (e.g. `pi`, `e`). Checked as a fallback in `GetVar`
137    /// after the environment, so user-defined variables can shadow them.
138    pub(crate) globals: BTreeMap<String, VmValue>,
139}
140
141impl Vm {
142    pub fn new() -> Self {
143        Self {
144            stack: Vec::with_capacity(256),
145            env: VmEnv::new(),
146            output: String::new(),
147            builtins: BTreeMap::new(),
148            async_builtins: BTreeMap::new(),
149            iterators: Vec::new(),
150            frames: Vec::new(),
151            exception_handlers: Vec::new(),
152            spawned_tasks: BTreeMap::new(),
153            task_counter: 0,
154            deadlines: Vec::new(),
155            breakpoints: Vec::new(),
156            step_mode: false,
157            step_frame_depth: 0,
158            stopped: false,
159            last_line: 0,
160            source_dir: None,
161            imported_paths: Vec::new(),
162            source_file: None,
163            source_text: None,
164            bridge: None,
165            denied_builtins: HashSet::new(),
166            cancel_token: None,
167            error_stack_trace: Vec::new(),
168            yield_sender: None,
169            project_root: None,
170            globals: BTreeMap::new(),
171        }
172    }
173
174    /// Set the bridge for delegating unknown builtins in bridge mode.
175    pub fn set_bridge(&mut self, bridge: Rc<crate::bridge::HostBridge>) {
176        self.bridge = Some(bridge);
177    }
178
179    /// Set builtins that are denied in sandbox mode.
180    /// When called, the given builtin names will produce a permission error.
181    pub fn set_denied_builtins(&mut self, denied: HashSet<String>) {
182        self.denied_builtins = denied;
183    }
184
185    /// Set source info for error reporting (file path and source text).
186    pub fn set_source_info(&mut self, file: &str, text: &str) {
187        self.source_file = Some(file.to_string());
188        self.source_text = Some(text.to_string());
189    }
190
191    /// Set breakpoints by source line number.
192    pub fn set_breakpoints(&mut self, lines: Vec<usize>) {
193        self.breakpoints = lines;
194    }
195
196    /// Enable step mode (stop at next line).
197    pub fn set_step_mode(&mut self, step: bool) {
198        self.step_mode = step;
199        self.step_frame_depth = self.frames.len();
200    }
201
202    /// Enable step-over mode (stop at next line at same or lower frame depth).
203    pub fn set_step_over(&mut self) {
204        self.step_mode = true;
205        self.step_frame_depth = self.frames.len();
206    }
207
208    /// Enable step-out mode (stop when returning from current frame).
209    pub fn set_step_out(&mut self) {
210        self.step_mode = true;
211        self.step_frame_depth = self.frames.len().saturating_sub(1);
212    }
213
214    /// Check if the VM is stopped at a debug point.
215    pub fn is_stopped(&self) -> bool {
216        self.stopped
217    }
218
219    /// Get the current debug state (variables, line, etc.).
220    pub fn debug_state(&self) -> DebugState {
221        let line = self.current_line();
222        let variables = self.env.all_variables();
223        let frame_name = if self.frames.len() > 1 {
224            format!("frame_{}", self.frames.len() - 1)
225        } else {
226            "pipeline".to_string()
227        };
228        DebugState {
229            line,
230            variables,
231            frame_name,
232            frame_depth: self.frames.len(),
233        }
234    }
235
236    /// Get all stack frames for the debugger.
237    pub fn debug_stack_frames(&self) -> Vec<(String, usize)> {
238        let mut frames = Vec::new();
239        for (i, frame) in self.frames.iter().enumerate() {
240            let line = if frame.ip > 0 && frame.ip - 1 < frame.chunk.lines.len() {
241                frame.chunk.lines[frame.ip - 1] as usize
242            } else {
243                0
244            };
245            let name = if frame.fn_name.is_empty() {
246                if i == 0 {
247                    "pipeline".to_string()
248                } else {
249                    format!("fn_{}", i)
250                }
251            } else {
252                frame.fn_name.clone()
253            };
254            frames.push((name, line));
255        }
256        frames
257    }
258
259    /// Get the current source line.
260    fn current_line(&self) -> usize {
261        if let Some(frame) = self.frames.last() {
262            let ip = if frame.ip > 0 { frame.ip - 1 } else { 0 };
263            if ip < frame.chunk.lines.len() {
264                return frame.chunk.lines[ip] as usize;
265            }
266        }
267        0
268    }
269
270    /// Execute one instruction, returning whether to stop (breakpoint/step).
271    /// Returns Ok(None) to continue, Ok(Some(val)) on program end, Err on error.
272    pub async fn step_execute(&mut self) -> Result<Option<(VmValue, bool)>, VmError> {
273        // Check if we need to stop at this line
274        let current_line = self.current_line();
275        let line_changed = current_line != self.last_line && current_line > 0;
276
277        if line_changed {
278            self.last_line = current_line;
279
280            // Check breakpoints
281            if self.breakpoints.contains(&current_line) {
282                self.stopped = true;
283                return Ok(Some((VmValue::Nil, true))); // true = stopped
284            }
285
286            // Check step mode
287            if self.step_mode && self.frames.len() <= self.step_frame_depth + 1 {
288                self.step_mode = false;
289                self.stopped = true;
290                return Ok(Some((VmValue::Nil, true))); // true = stopped
291            }
292        }
293
294        // Execute one instruction cycle
295        self.stopped = false;
296        self.execute_one_cycle().await
297    }
298
299    /// Execute a single instruction cycle.
300    async fn execute_one_cycle(&mut self) -> Result<Option<(VmValue, bool)>, VmError> {
301        // Check deadline
302        if let Some(&(deadline, _)) = self.deadlines.last() {
303            if Instant::now() > deadline {
304                self.deadlines.pop();
305                let err = VmError::Thrown(VmValue::String(Rc::from("Deadline exceeded")));
306                match self.handle_error(err) {
307                    Ok(None) => return Ok(None),
308                    Ok(Some(val)) => return Ok(Some((val, false))),
309                    Err(e) => return Err(e),
310                }
311            }
312        }
313
314        // Get current frame
315        let frame = match self.frames.last_mut() {
316            Some(f) => f,
317            None => {
318                let val = self.stack.pop().unwrap_or(VmValue::Nil);
319                return Ok(Some((val, false)));
320            }
321        };
322
323        // Check if we've reached end of chunk
324        if frame.ip >= frame.chunk.code.len() {
325            let val = self.stack.pop().unwrap_or(VmValue::Nil);
326            let popped_frame = self.frames.pop().unwrap();
327            if self.frames.is_empty() {
328                return Ok(Some((val, false)));
329            } else {
330                self.env = popped_frame.saved_env;
331                self.stack.truncate(popped_frame.stack_base);
332                self.stack.push(val);
333                return Ok(None);
334            }
335        }
336
337        let op = frame.chunk.code[frame.ip];
338        frame.ip += 1;
339
340        match self.execute_op(op).await {
341            Ok(Some(val)) => Ok(Some((val, false))),
342            Ok(None) => Ok(None),
343            Err(VmError::Return(val)) => {
344                if let Some(popped_frame) = self.frames.pop() {
345                    let current_depth = self.frames.len();
346                    self.exception_handlers
347                        .retain(|h| h.frame_depth <= current_depth);
348                    if self.frames.is_empty() {
349                        return Ok(Some((val, false)));
350                    }
351                    self.env = popped_frame.saved_env;
352                    self.stack.truncate(popped_frame.stack_base);
353                    self.stack.push(val);
354                    Ok(None)
355                } else {
356                    Ok(Some((val, false)))
357                }
358            }
359            Err(e) => {
360                if self.error_stack_trace.is_empty() {
361                    self.error_stack_trace = self.capture_stack_trace();
362                }
363                match self.handle_error(e) {
364                    Ok(None) => {
365                        self.error_stack_trace.clear();
366                        Ok(None)
367                    }
368                    Ok(Some(val)) => Ok(Some((val, false))),
369                    Err(e) => Err(self.enrich_error_with_line(e)),
370                }
371            }
372        }
373    }
374
375    /// Initialize execution (push the initial frame).
376    pub fn start(&mut self, chunk: &Chunk) {
377        self.frames.push(CallFrame {
378            chunk: chunk.clone(),
379            ip: 0,
380            stack_base: self.stack.len(),
381            saved_env: self.env.clone(),
382            fn_name: String::new(),
383            argc: 0,
384        });
385    }
386
387    /// Register a sync builtin function.
388    pub fn register_builtin<F>(&mut self, name: &str, f: F)
389    where
390        F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + 'static,
391    {
392        self.builtins.insert(name.to_string(), Rc::new(f));
393    }
394
395    /// Remove a sync builtin (so an async version can take precedence).
396    pub fn unregister_builtin(&mut self, name: &str) {
397        self.builtins.remove(name);
398    }
399
400    /// Register an async builtin function.
401    pub fn register_async_builtin<F, Fut>(&mut self, name: &str, f: F)
402    where
403        F: Fn(Vec<VmValue>) -> Fut + 'static,
404        Fut: Future<Output = Result<VmValue, VmError>> + 'static,
405    {
406        self.async_builtins
407            .insert(name.to_string(), Rc::new(move |args| Box::pin(f(args))));
408    }
409
410    /// Create a child VM that shares builtins and env but has fresh execution state.
411    /// Used for parallel/spawn to fork the VM for concurrent tasks.
412    fn child_vm(&self) -> Vm {
413        Vm {
414            stack: Vec::with_capacity(64),
415            env: self.env.clone(),
416            output: String::new(),
417            builtins: self.builtins.clone(),
418            async_builtins: self.async_builtins.clone(),
419            iterators: Vec::new(),
420            frames: Vec::new(),
421            exception_handlers: Vec::new(),
422            spawned_tasks: BTreeMap::new(),
423            task_counter: 0,
424            deadlines: self.deadlines.clone(),
425            breakpoints: Vec::new(),
426            step_mode: false,
427            step_frame_depth: 0,
428            stopped: false,
429            last_line: 0,
430            source_dir: self.source_dir.clone(),
431            imported_paths: Vec::new(),
432            source_file: self.source_file.clone(),
433            source_text: self.source_text.clone(),
434            bridge: self.bridge.clone(),
435            denied_builtins: self.denied_builtins.clone(),
436            cancel_token: None,
437            error_stack_trace: Vec::new(),
438            yield_sender: None,
439            project_root: self.project_root.clone(),
440            globals: self.globals.clone(),
441        }
442    }
443
444    /// Set the source directory for import resolution and introspection.
445    /// Also auto-detects the project root if not already set.
446    pub fn set_source_dir(&mut self, dir: &std::path::Path) {
447        self.source_dir = Some(dir.to_path_buf());
448        crate::stdlib::set_thread_source_dir(dir);
449        // Auto-detect project root if not explicitly set.
450        if self.project_root.is_none() {
451            self.project_root = crate::stdlib::process::find_project_root(dir);
452        }
453    }
454
455    /// Explicitly set the project root directory.
456    /// Used by ACP/CLI to override auto-detection.
457    pub fn set_project_root(&mut self, root: &std::path::Path) {
458        self.project_root = Some(root.to_path_buf());
459    }
460
461    /// Get the project root directory, falling back to source_dir.
462    pub fn project_root(&self) -> Option<&std::path::Path> {
463        self.project_root.as_deref().or(self.source_dir.as_deref())
464    }
465
466    /// Return all registered builtin names (sync + async).
467    pub fn builtin_names(&self) -> Vec<String> {
468        let mut names: Vec<String> = self.builtins.keys().cloned().collect();
469        names.extend(self.async_builtins.keys().cloned());
470        names
471    }
472
473    /// Set a global constant (e.g. `pi`, `e`).
474    /// Stored separately from the environment so user-defined variables can shadow them.
475    pub fn set_global(&mut self, name: &str, value: VmValue) {
476        self.globals.insert(name.to_string(), value);
477    }
478
479    /// Execute an import, reading and running the file's declarations.
480    fn execute_import<'a>(
481        &'a mut self,
482        path: &'a str,
483        selected_names: Option<&'a [String]>,
484    ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + 'a>> {
485        Box::pin(async move {
486            use std::path::PathBuf;
487            let _import_span = ScopeSpan::new(crate::tracing::SpanKind::Import, path.to_string());
488
489            // ── Embedded stdlib modules (import "std/...") ──────────────
490            if let Some(module) = path.strip_prefix("std/") {
491                if let Some(source) = crate::stdlib_modules::get_stdlib_source(module) {
492                    let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
493                    if self.imported_paths.contains(&synthetic) {
494                        return Ok(());
495                    }
496                    self.imported_paths.push(synthetic);
497
498                    let mut lexer = harn_lexer::Lexer::new(source);
499                    let tokens = lexer.tokenize().map_err(|e| {
500                        VmError::Runtime(format!("stdlib lex error in std/{module}: {e}"))
501                    })?;
502                    let mut parser = harn_parser::Parser::new(tokens);
503                    let program = parser.parse().map_err(|e| {
504                        VmError::Runtime(format!("stdlib parse error in std/{module}: {e}"))
505                    })?;
506
507                    self.import_declarations(&program, selected_names, None)
508                        .await?;
509                    return Ok(());
510                }
511                return Err(VmError::Runtime(format!(
512                    "Unknown stdlib module: std/{module}"
513                )));
514            }
515
516            // ── Filesystem-based imports ────────────────────────────────
517            let base = self
518                .source_dir
519                .clone()
520                .unwrap_or_else(|| PathBuf::from("."));
521            let mut file_path = base.join(path);
522
523            // Try with .harn extension if no extension
524            if !file_path.exists() && file_path.extension().is_none() {
525                file_path.set_extension("harn");
526            }
527
528            // Try .harn/packages/ fallback (then .burin/packages/ for compat)
529            if !file_path.exists() {
530                for pkg_dir in [".harn/packages", ".burin/packages"] {
531                    let pkg_path = base.join(pkg_dir).join(path);
532                    if pkg_path.exists() {
533                        file_path = if pkg_path.is_dir() {
534                            let lib = pkg_path.join("lib.harn");
535                            if lib.exists() {
536                                lib
537                            } else {
538                                pkg_path
539                            }
540                        } else {
541                            pkg_path
542                        };
543                        break;
544                    }
545                    let mut pkg_harn = pkg_path.clone();
546                    pkg_harn.set_extension("harn");
547                    if pkg_harn.exists() {
548                        file_path = pkg_harn;
549                        break;
550                    }
551                }
552            }
553
554            // Cycle detection
555            let canonical = file_path
556                .canonicalize()
557                .unwrap_or_else(|_| file_path.clone());
558            if self.imported_paths.contains(&canonical) {
559                return Ok(()); // already imported
560            }
561            self.imported_paths.push(canonical);
562
563            // Read, lex, parse
564            let source = std::fs::read_to_string(&file_path).map_err(|e| {
565                VmError::Runtime(format!(
566                    "Import error: cannot read '{}': {e}",
567                    file_path.display()
568                ))
569            })?;
570
571            let mut lexer = harn_lexer::Lexer::new(&source);
572            let tokens = lexer
573                .tokenize()
574                .map_err(|e| VmError::Runtime(format!("Import lex error: {e}")))?;
575            let mut parser = harn_parser::Parser::new(tokens);
576            let program = parser
577                .parse()
578                .map_err(|e| VmError::Runtime(format!("Import parse error: {e}")))?;
579
580            self.import_declarations(&program, selected_names, Some(&file_path))
581                .await?;
582
583            Ok(())
584        })
585    }
586
587    /// Process top-level declarations from an imported module.
588    /// `file_path` is `None` for embedded stdlib modules.
589    fn import_declarations<'a>(
590        &'a mut self,
591        program: &'a [harn_parser::SNode],
592        selected_names: Option<&'a [String]>,
593        file_path: Option<&'a std::path::Path>,
594    ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + 'a>> {
595        Box::pin(async move {
596            let has_pub = program
597                .iter()
598                .any(|n| matches!(&n.node, harn_parser::Node::FnDecl { is_pub: true, .. }));
599
600            for node in program {
601                match &node.node {
602                    harn_parser::Node::FnDecl {
603                        name,
604                        params,
605                        body,
606                        is_pub,
607                        ..
608                    } => {
609                        // For selective imports: import any function that was explicitly named
610                        // For wildcard imports: if module has pub fns, only import pub ones;
611                        //   if no pub fns, import everything (backward compat)
612                        if selected_names.is_none() && has_pub && !is_pub {
613                            continue;
614                        }
615                        if let Some(names) = selected_names {
616                            if !names.contains(name) {
617                                continue;
618                            }
619                        }
620                        // Compile the function body into a closure and define it
621                        let mut compiler = crate::Compiler::new();
622                        let func_chunk = compiler
623                            .compile_fn_body(params, body)
624                            .map_err(|e| VmError::Runtime(format!("Import compile error: {e}")))?;
625                        let closure = VmClosure {
626                            func: func_chunk,
627                            env: self.env.clone(),
628                        };
629                        self.env
630                            .define(name, VmValue::Closure(Rc::new(closure)), false)?;
631                    }
632                    harn_parser::Node::ImportDecl { path: sub_path } => {
633                        let old_dir = self.source_dir.clone();
634                        if let Some(fp) = file_path {
635                            if let Some(parent) = fp.parent() {
636                                self.source_dir = Some(parent.to_path_buf());
637                            }
638                        }
639                        self.execute_import(sub_path, None).await?;
640                        self.source_dir = old_dir;
641                    }
642                    harn_parser::Node::SelectiveImport {
643                        names,
644                        path: sub_path,
645                    } => {
646                        let old_dir = self.source_dir.clone();
647                        if let Some(fp) = file_path {
648                            if let Some(parent) = fp.parent() {
649                                self.source_dir = Some(parent.to_path_buf());
650                            }
651                        }
652                        self.execute_import(sub_path, Some(names)).await?;
653                        self.source_dir = old_dir;
654                    }
655                    _ => {} // Skip other top-level nodes (pipelines, enums, etc.)
656                }
657            }
658
659            Ok(())
660        })
661    }
662
663    /// Get the captured output.
664    pub fn output(&self) -> &str {
665        &self.output
666    }
667
668    /// Execute a compiled chunk.
669    pub async fn execute(&mut self, chunk: &Chunk) -> Result<VmValue, VmError> {
670        let span_id = crate::tracing::span_start(crate::tracing::SpanKind::Pipeline, "main".into());
671        let result = self.run_chunk(chunk).await;
672        crate::tracing::span_end(span_id);
673        result
674    }
675
676    /// Convert a VmError into either a handled exception (returning Ok) or a propagated error.
677    fn handle_error(&mut self, error: VmError) -> Result<Option<VmValue>, VmError> {
678        // Extract the thrown value from the error
679        let thrown_value = match &error {
680            VmError::Thrown(v) => v.clone(),
681            other => VmValue::String(Rc::from(other.to_string())),
682        };
683
684        if let Some(handler) = self.exception_handlers.pop() {
685            // Check if this is a typed catch that doesn't match the thrown value
686            if !handler.error_type.is_empty() {
687                let matches = match &thrown_value {
688                    VmValue::EnumVariant { enum_name, .. } => *enum_name == handler.error_type,
689                    _ => false,
690                };
691                if !matches {
692                    // This handler doesn't match — try the next one
693                    return self.handle_error(error);
694                }
695            }
696
697            // Unwind call frames back to the handler's frame depth
698            while self.frames.len() > handler.frame_depth {
699                if let Some(frame) = self.frames.pop() {
700                    self.env = frame.saved_env;
701                }
702            }
703
704            // Clean up deadlines from unwound frames
705            while self
706                .deadlines
707                .last()
708                .is_some_and(|d| d.1 > handler.frame_depth)
709            {
710                self.deadlines.pop();
711            }
712
713            // Restore stack to handler's depth
714            self.stack.truncate(handler.stack_depth);
715
716            // Push the error value onto the stack (catch body can access it)
717            self.stack.push(thrown_value);
718
719            // Set the IP in the current frame to the catch handler
720            if let Some(frame) = self.frames.last_mut() {
721                frame.ip = handler.catch_ip;
722            }
723
724            Ok(None) // Continue execution
725        } else {
726            Err(error) // No handler, propagate
727        }
728    }
729
730    async fn run_chunk(&mut self, chunk: &Chunk) -> Result<VmValue, VmError> {
731        self.run_chunk_with_argc(chunk, 0).await
732    }
733
734    async fn run_chunk_with_argc(
735        &mut self,
736        chunk: &Chunk,
737        argc: usize,
738    ) -> Result<VmValue, VmError> {
739        self.frames.push(CallFrame {
740            chunk: chunk.clone(),
741            ip: 0,
742            stack_base: self.stack.len(),
743            saved_env: self.env.clone(),
744            fn_name: String::new(),
745            argc,
746        });
747
748        loop {
749            // Check deadline before each instruction
750            if let Some(&(deadline, _)) = self.deadlines.last() {
751                if Instant::now() > deadline {
752                    self.deadlines.pop();
753                    let err = VmError::Thrown(VmValue::String(Rc::from("Deadline exceeded")));
754                    match self.handle_error(err) {
755                        Ok(None) => continue,
756                        Ok(Some(val)) => return Ok(val),
757                        Err(e) => return Err(e),
758                    }
759                }
760            }
761
762            // Get current frame
763            let frame = match self.frames.last_mut() {
764                Some(f) => f,
765                None => return Ok(self.stack.pop().unwrap_or(VmValue::Nil)),
766            };
767
768            // Check if we've reached end of chunk
769            if frame.ip >= frame.chunk.code.len() {
770                let val = self.stack.pop().unwrap_or(VmValue::Nil);
771                let popped_frame = self.frames.pop().unwrap();
772
773                if self.frames.is_empty() {
774                    // We're done with the top-level chunk
775                    return Ok(val);
776                } else {
777                    // Returning from a function call
778                    self.env = popped_frame.saved_env;
779                    self.stack.truncate(popped_frame.stack_base);
780                    self.stack.push(val);
781                    continue;
782                }
783            }
784
785            let op = frame.chunk.code[frame.ip];
786            frame.ip += 1;
787
788            match self.execute_op(op).await {
789                Ok(Some(val)) => return Ok(val),
790                Ok(None) => continue,
791                Err(VmError::Return(val)) => {
792                    // Pop the current frame
793                    if let Some(popped_frame) = self.frames.pop() {
794                        // Clean up exception handlers from the returned frame
795                        let current_depth = self.frames.len();
796                        self.exception_handlers
797                            .retain(|h| h.frame_depth <= current_depth);
798
799                        if self.frames.is_empty() {
800                            return Ok(val);
801                        }
802                        self.env = popped_frame.saved_env;
803                        self.stack.truncate(popped_frame.stack_base);
804                        self.stack.push(val);
805                    } else {
806                        return Ok(val);
807                    }
808                }
809                Err(e) => {
810                    // Capture stack trace before error handling unwinds frames
811                    if self.error_stack_trace.is_empty() {
812                        self.error_stack_trace = self.capture_stack_trace();
813                    }
814                    match self.handle_error(e) {
815                        Ok(None) => {
816                            self.error_stack_trace.clear();
817                            continue; // Handler found, continue
818                        }
819                        Ok(Some(val)) => return Ok(val),
820                        Err(e) => return Err(self.enrich_error_with_line(e)),
821                    }
822                }
823            }
824        }
825    }
826
827    /// Capture the current call stack as (fn_name, line, col) tuples.
828    fn capture_stack_trace(&self) -> Vec<(String, usize, usize)> {
829        self.frames
830            .iter()
831            .map(|f| {
832                let idx = if f.ip > 0 { f.ip - 1 } else { 0 };
833                let line = f.chunk.lines.get(idx).copied().unwrap_or(0) as usize;
834                let col = f.chunk.columns.get(idx).copied().unwrap_or(0) as usize;
835                (f.fn_name.clone(), line, col)
836            })
837            .collect()
838    }
839
840    /// Enrich a VmError with source line information from the captured stack
841    /// trace. Appends ` (line N)` to error variants whose messages don't
842    /// already carry location context.
843    fn enrich_error_with_line(&self, error: VmError) -> VmError {
844        // Determine the line from the captured stack trace (innermost frame).
845        let line = self
846            .error_stack_trace
847            .last()
848            .map(|(_, l, _)| *l)
849            .unwrap_or_else(|| self.current_line());
850        if line == 0 {
851            return error;
852        }
853        let suffix = format!(" (line {line})");
854        match error {
855            VmError::Runtime(msg) => VmError::Runtime(format!("{msg}{suffix}")),
856            VmError::TypeError(msg) => VmError::TypeError(format!("{msg}{suffix}")),
857            VmError::DivisionByZero => VmError::Runtime(format!("Division by zero{suffix}")),
858            VmError::UndefinedVariable(name) => {
859                VmError::Runtime(format!("Undefined variable: {name}{suffix}"))
860            }
861            VmError::UndefinedBuiltin(name) => {
862                VmError::Runtime(format!("Undefined builtin: {name}{suffix}"))
863            }
864            VmError::ImmutableAssignment(name) => VmError::Runtime(format!(
865                "Cannot assign to immutable binding: {name}{suffix}"
866            )),
867            VmError::StackOverflow => {
868                VmError::Runtime(format!("Stack overflow: too many nested calls{suffix}"))
869            }
870            // Leave these untouched:
871            // - Thrown: user-thrown errors should not be silently modified
872            // - CategorizedError: structured errors for agent orchestration
873            // - Return: control flow, not a real error
874            // - StackUnderflow / InvalidInstruction: internal VM bugs
875            other => other,
876        }
877    }
878
879    const MAX_FRAMES: usize = 512;
880
881    /// Merge the caller's env into a closure's captured env for function calls.
882    fn merge_env_into_closure(caller_env: &VmEnv, closure: &VmClosure) -> VmEnv {
883        let mut call_env = closure.env.clone();
884        for scope in &caller_env.scopes {
885            for (name, (val, mutable)) in &scope.vars {
886                if call_env.get(name).is_none() {
887                    let _ = call_env.define(name, val.clone(), *mutable);
888                }
889            }
890        }
891        call_env
892    }
893
894    /// Push a new call frame for a closure invocation.
895    fn push_closure_frame(
896        &mut self,
897        closure: &VmClosure,
898        args: &[VmValue],
899        _parent_functions: &[CompiledFunction],
900    ) -> Result<(), VmError> {
901        if self.frames.len() >= Self::MAX_FRAMES {
902            return Err(VmError::StackOverflow);
903        }
904        let saved_env = self.env.clone();
905
906        let mut call_env = Self::merge_env_into_closure(&saved_env, closure);
907        call_env.push_scope();
908
909        let default_start = closure
910            .func
911            .default_start
912            .unwrap_or(closure.func.params.len());
913        for (i, param) in closure.func.params.iter().enumerate() {
914            if i < args.len() {
915                let _ = call_env.define(param, args[i].clone(), false);
916            } else if i < default_start {
917                let _ = call_env.define(param, VmValue::Nil, false);
918            }
919        }
920
921        self.env = call_env;
922
923        self.frames.push(CallFrame {
924            chunk: closure.func.chunk.clone(),
925            ip: 0,
926            stack_base: self.stack.len(),
927            saved_env,
928            fn_name: closure.func.name.clone(),
929            argc: args.len(),
930        });
931
932        Ok(())
933    }
934
935    /// Create a generator value by spawning the closure body as an async task.
936    /// The generator body communicates yielded values through an mpsc channel.
937    pub(crate) fn create_generator(&self, closure: &VmClosure, args: &[VmValue]) -> VmValue {
938        use crate::value::VmGenerator;
939
940        // Buffer size of 1: the generator produces one value at a time.
941        let (tx, rx) = tokio::sync::mpsc::channel::<VmValue>(1);
942
943        let mut child = self.child_vm();
944        child.yield_sender = Some(tx);
945
946        // Set up the environment for the generator body
947        let saved_env = child.env.clone();
948        let mut call_env = Self::merge_env_into_closure(&saved_env, closure);
949        call_env.push_scope();
950
951        let default_start = closure
952            .func
953            .default_start
954            .unwrap_or(closure.func.params.len());
955        for (i, param) in closure.func.params.iter().enumerate() {
956            if i < args.len() {
957                let _ = call_env.define(param, args[i].clone(), false);
958            } else if i < default_start {
959                let _ = call_env.define(param, VmValue::Nil, false);
960            }
961        }
962        child.env = call_env;
963
964        let chunk = closure.func.chunk.clone();
965        // Spawn the generator body as an async task.
966        // The task will execute until return, sending yielded values through the channel.
967        tokio::task::spawn_local(async move {
968            let _ = child.run_chunk(&chunk).await;
969            // When the generator body finishes (return or fall-through),
970            // the sender is dropped, signaling completion to the receiver.
971        });
972
973        VmValue::Generator(VmGenerator {
974            done: Rc::new(std::cell::Cell::new(false)),
975            receiver: Rc::new(tokio::sync::Mutex::new(rx)),
976        })
977    }
978
979    fn pop(&mut self) -> Result<VmValue, VmError> {
980        self.stack.pop().ok_or(VmError::StackUnderflow)
981    }
982
983    fn peek(&self) -> Result<&VmValue, VmError> {
984        self.stack.last().ok_or(VmError::StackUnderflow)
985    }
986
987    fn const_string(c: &Constant) -> Result<String, VmError> {
988        match c {
989            Constant::String(s) => Ok(s.clone()),
990            _ => Err(VmError::TypeError("expected string constant".into())),
991        }
992    }
993
994    /// Call a closure (used by method calls like .map/.filter etc.)
995    /// Uses recursive execution for simplicity in method dispatch.
996    fn call_closure<'a>(
997        &'a mut self,
998        closure: &'a VmClosure,
999        args: &'a [VmValue],
1000        _parent_functions: &'a [CompiledFunction],
1001    ) -> Pin<Box<dyn Future<Output = Result<VmValue, VmError>> + 'a>> {
1002        Box::pin(async move {
1003            let saved_env = self.env.clone();
1004            let saved_frames = std::mem::take(&mut self.frames);
1005            let saved_handlers = std::mem::take(&mut self.exception_handlers);
1006            let saved_iterators = std::mem::take(&mut self.iterators);
1007            let saved_deadlines = std::mem::take(&mut self.deadlines);
1008
1009            let mut call_env = Self::merge_env_into_closure(&saved_env, closure);
1010            call_env.push_scope();
1011
1012            let default_start = closure
1013                .func
1014                .default_start
1015                .unwrap_or(closure.func.params.len());
1016            for (i, param) in closure.func.params.iter().enumerate() {
1017                if i < args.len() {
1018                    let _ = call_env.define(param, args[i].clone(), false);
1019                } else if i < default_start {
1020                    let _ = call_env.define(param, VmValue::Nil, false);
1021                }
1022            }
1023
1024            self.env = call_env;
1025            let argc = args.len();
1026            let result = self.run_chunk_with_argc(&closure.func.chunk, argc).await;
1027
1028            self.env = saved_env;
1029            self.frames = saved_frames;
1030            self.exception_handlers = saved_handlers;
1031            self.iterators = saved_iterators;
1032            self.deadlines = saved_deadlines;
1033
1034            result
1035        })
1036    }
1037
1038    /// Public wrapper for `call_closure`, used by the MCP server to invoke
1039    /// tool handler closures from outside the VM execution loop.
1040    pub async fn call_closure_pub(
1041        &mut self,
1042        closure: &VmClosure,
1043        args: &[VmValue],
1044        functions: &[CompiledFunction],
1045    ) -> Result<VmValue, VmError> {
1046        self.call_closure(closure, args, functions).await
1047    }
1048
1049    /// Resolve a named builtin: sync builtins → async builtins → bridge → error.
1050    /// Used by Call, TailCall, and Pipe handlers to avoid duplicating this lookup.
1051    async fn call_named_builtin(
1052        &mut self,
1053        name: &str,
1054        args: Vec<VmValue>,
1055    ) -> Result<VmValue, VmError> {
1056        // Auto-trace LLM calls and tool calls
1057        let span_kind = match name {
1058            "llm_call" | "llm_stream" | "agent_loop" => Some(crate::tracing::SpanKind::LlmCall),
1059            "mcp_call" => Some(crate::tracing::SpanKind::ToolCall),
1060            _ => None,
1061        };
1062        let _span = span_kind.map(|kind| ScopeSpan::new(kind, name.to_string()));
1063
1064        // Sandbox check: deny builtins blocked by --deny/--allow flags.
1065        if self.denied_builtins.contains(name) {
1066            return Err(VmError::CategorizedError {
1067                message: format!("Tool '{}' is not permitted.", name),
1068                category: ErrorCategory::ToolRejected,
1069            });
1070        }
1071        crate::orchestration::enforce_current_policy_for_builtin(name, &args)?;
1072        if let Some(builtin) = self.builtins.get(name).cloned() {
1073            builtin(&args, &mut self.output)
1074        } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
1075            async_builtin(args).await
1076        } else if let Some(bridge) = &self.bridge {
1077            crate::orchestration::enforce_current_policy_for_bridge_builtin(name)?;
1078            let args_json: Vec<serde_json::Value> =
1079                args.iter().map(crate::llm::vm_value_to_json).collect();
1080            let result = bridge
1081                .call(
1082                    "builtin_call",
1083                    serde_json::json!({"name": name, "args": args_json}),
1084                )
1085                .await?;
1086            Ok(crate::bridge::json_result_to_vm_value(&result))
1087        } else {
1088            let all_builtins = self
1089                .builtins
1090                .keys()
1091                .chain(self.async_builtins.keys())
1092                .map(|s| s.as_str());
1093            if let Some(suggestion) = crate::value::closest_match(name, all_builtins) {
1094                return Err(VmError::Runtime(format!(
1095                    "Undefined builtin: {name} (did you mean `{suggestion}`?)"
1096                )));
1097            }
1098            Err(VmError::UndefinedBuiltin(name.to_string()))
1099        }
1100    }
1101}
1102
1103impl Default for Vm {
1104    fn default() -> Self {
1105        Self::new()
1106    }
1107}
1108
1109#[cfg(test)]
1110mod tests {
1111    use super::*;
1112    use crate::compiler::Compiler;
1113    use crate::stdlib::register_vm_stdlib;
1114    use harn_lexer::Lexer;
1115    use harn_parser::Parser;
1116
1117    fn run_harn(source: &str) -> (String, VmValue) {
1118        let rt = tokio::runtime::Builder::new_current_thread()
1119            .enable_all()
1120            .build()
1121            .unwrap();
1122        rt.block_on(async {
1123            let local = tokio::task::LocalSet::new();
1124            local
1125                .run_until(async {
1126                    let mut lexer = Lexer::new(source);
1127                    let tokens = lexer.tokenize().unwrap();
1128                    let mut parser = Parser::new(tokens);
1129                    let program = parser.parse().unwrap();
1130                    let chunk = Compiler::new().compile(&program).unwrap();
1131
1132                    let mut vm = Vm::new();
1133                    register_vm_stdlib(&mut vm);
1134                    let result = vm.execute(&chunk).await.unwrap();
1135                    (vm.output().to_string(), result)
1136                })
1137                .await
1138        })
1139    }
1140
1141    fn run_output(source: &str) -> String {
1142        run_harn(source).0.trim_end().to_string()
1143    }
1144
1145    fn run_harn_result(source: &str) -> Result<(String, VmValue), VmError> {
1146        let rt = tokio::runtime::Builder::new_current_thread()
1147            .enable_all()
1148            .build()
1149            .unwrap();
1150        rt.block_on(async {
1151            let local = tokio::task::LocalSet::new();
1152            local
1153                .run_until(async {
1154                    let mut lexer = Lexer::new(source);
1155                    let tokens = lexer.tokenize().unwrap();
1156                    let mut parser = Parser::new(tokens);
1157                    let program = parser.parse().unwrap();
1158                    let chunk = Compiler::new().compile(&program).unwrap();
1159
1160                    let mut vm = Vm::new();
1161                    register_vm_stdlib(&mut vm);
1162                    let result = vm.execute(&chunk).await?;
1163                    Ok((vm.output().to_string(), result))
1164                })
1165                .await
1166        })
1167    }
1168
1169    #[test]
1170    fn test_arithmetic() {
1171        let out =
1172            run_output("pipeline t(task) { log(2 + 3)\nlog(10 - 4)\nlog(3 * 5)\nlog(10 / 3) }");
1173        assert_eq!(out, "[harn] 5\n[harn] 6\n[harn] 15\n[harn] 3");
1174    }
1175
1176    #[test]
1177    fn test_mixed_arithmetic() {
1178        let out = run_output("pipeline t(task) { log(3 + 1.5)\nlog(10 - 2.5) }");
1179        assert_eq!(out, "[harn] 4.5\n[harn] 7.5");
1180    }
1181
1182    #[test]
1183    fn test_comparisons() {
1184        let out =
1185            run_output("pipeline t(task) { log(1 < 2)\nlog(2 > 3)\nlog(1 == 1)\nlog(1 != 2) }");
1186        assert_eq!(out, "[harn] true\n[harn] false\n[harn] true\n[harn] true");
1187    }
1188
1189    #[test]
1190    fn test_let_var() {
1191        let out = run_output("pipeline t(task) { let x = 42\nlog(x)\nvar y = 1\ny = 2\nlog(y) }");
1192        assert_eq!(out, "[harn] 42\n[harn] 2");
1193    }
1194
1195    #[test]
1196    fn test_if_else() {
1197        let out = run_output(
1198            r#"pipeline t(task) { if true { log("yes") } if false { log("wrong") } else { log("no") } }"#,
1199        );
1200        assert_eq!(out, "[harn] yes\n[harn] no");
1201    }
1202
1203    #[test]
1204    fn test_while_loop() {
1205        let out = run_output("pipeline t(task) { var i = 0\n while i < 5 { i = i + 1 }\n log(i) }");
1206        assert_eq!(out, "[harn] 5");
1207    }
1208
1209    #[test]
1210    fn test_for_in() {
1211        let out = run_output("pipeline t(task) { for item in [1, 2, 3] { log(item) } }");
1212        assert_eq!(out, "[harn] 1\n[harn] 2\n[harn] 3");
1213    }
1214
1215    #[test]
1216    fn test_fn_decl_and_call() {
1217        let out = run_output("pipeline t(task) { fn add(a, b) { return a + b }\nlog(add(3, 4)) }");
1218        assert_eq!(out, "[harn] 7");
1219    }
1220
1221    #[test]
1222    fn test_closure() {
1223        let out = run_output("pipeline t(task) { let double = { x -> x * 2 }\nlog(double(5)) }");
1224        assert_eq!(out, "[harn] 10");
1225    }
1226
1227    #[test]
1228    fn test_closure_capture() {
1229        let out = run_output(
1230            "pipeline t(task) { let base = 10\nfn offset(x) { return x + base }\nlog(offset(5)) }",
1231        );
1232        assert_eq!(out, "[harn] 15");
1233    }
1234
1235    #[test]
1236    fn test_string_concat() {
1237        let out = run_output(
1238            r#"pipeline t(task) { let a = "hello" + " " + "world"
1239log(a) }"#,
1240        );
1241        assert_eq!(out, "[harn] hello world");
1242    }
1243
1244    #[test]
1245    fn test_list_map() {
1246        let out = run_output(
1247            "pipeline t(task) { let doubled = [1, 2, 3].map({ x -> x * 2 })\nlog(doubled) }",
1248        );
1249        assert_eq!(out, "[harn] [2, 4, 6]");
1250    }
1251
1252    #[test]
1253    fn test_list_filter() {
1254        let out = run_output(
1255            "pipeline t(task) { let big = [1, 2, 3, 4, 5].filter({ x -> x > 3 })\nlog(big) }",
1256        );
1257        assert_eq!(out, "[harn] [4, 5]");
1258    }
1259
1260    #[test]
1261    fn test_list_reduce() {
1262        let out = run_output(
1263            "pipeline t(task) { let sum = [1, 2, 3, 4].reduce(0, { acc, x -> acc + x })\nlog(sum) }",
1264        );
1265        assert_eq!(out, "[harn] 10");
1266    }
1267
1268    #[test]
1269    fn test_dict_access() {
1270        let out = run_output(
1271            r#"pipeline t(task) { let d = {name: "test", value: 42}
1272log(d.name)
1273log(d.value) }"#,
1274        );
1275        assert_eq!(out, "[harn] test\n[harn] 42");
1276    }
1277
1278    #[test]
1279    fn test_dict_methods() {
1280        let out = run_output(
1281            r#"pipeline t(task) { let d = {a: 1, b: 2}
1282log(d.keys())
1283log(d.values())
1284log(d.has("a"))
1285log(d.has("z")) }"#,
1286        );
1287        assert_eq!(
1288            out,
1289            "[harn] [a, b]\n[harn] [1, 2]\n[harn] true\n[harn] false"
1290        );
1291    }
1292
1293    #[test]
1294    fn test_pipe_operator() {
1295        let out = run_output(
1296            "pipeline t(task) { fn double(x) { return x * 2 }\nlet r = 5 |> double\nlog(r) }",
1297        );
1298        assert_eq!(out, "[harn] 10");
1299    }
1300
1301    #[test]
1302    fn test_pipe_with_closure() {
1303        let out = run_output(
1304            r#"pipeline t(task) { let r = "hello world" |> { s -> s.split(" ") }
1305log(r) }"#,
1306        );
1307        assert_eq!(out, "[harn] [hello, world]");
1308    }
1309
1310    #[test]
1311    fn test_nil_coalescing() {
1312        let out = run_output(
1313            r#"pipeline t(task) { let a = nil ?? "fallback"
1314log(a)
1315let b = "present" ?? "fallback"
1316log(b) }"#,
1317        );
1318        assert_eq!(out, "[harn] fallback\n[harn] present");
1319    }
1320
1321    #[test]
1322    fn test_logical_operators() {
1323        let out =
1324            run_output("pipeline t(task) { log(true && false)\nlog(true || false)\nlog(!true) }");
1325        assert_eq!(out, "[harn] false\n[harn] true\n[harn] false");
1326    }
1327
1328    #[test]
1329    fn test_match() {
1330        let out = run_output(
1331            r#"pipeline t(task) { let x = "b"
1332match x { "a" -> { log("first") } "b" -> { log("second") } "c" -> { log("third") } } }"#,
1333        );
1334        assert_eq!(out, "[harn] second");
1335    }
1336
1337    #[test]
1338    fn test_subscript() {
1339        let out = run_output("pipeline t(task) { let arr = [10, 20, 30]\nlog(arr[1]) }");
1340        assert_eq!(out, "[harn] 20");
1341    }
1342
1343    #[test]
1344    fn test_string_methods() {
1345        let out = run_output(
1346            r#"pipeline t(task) { log("hello world".replace("world", "harn"))
1347log("a,b,c".split(","))
1348log("  hello  ".trim())
1349log("hello".starts_with("hel"))
1350log("hello".ends_with("lo"))
1351log("hello".substring(1, 3)) }"#,
1352        );
1353        assert_eq!(
1354            out,
1355            "[harn] hello harn\n[harn] [a, b, c]\n[harn] hello\n[harn] true\n[harn] true\n[harn] el"
1356        );
1357    }
1358
1359    #[test]
1360    fn test_list_properties() {
1361        let out = run_output(
1362            "pipeline t(task) { let list = [1, 2, 3]\nlog(list.count)\nlog(list.empty)\nlog(list.first)\nlog(list.last) }",
1363        );
1364        assert_eq!(out, "[harn] 3\n[harn] false\n[harn] 1\n[harn] 3");
1365    }
1366
1367    #[test]
1368    fn test_recursive_function() {
1369        let out = run_output(
1370            "pipeline t(task) { fn fib(n) { if n <= 1 { return n } return fib(n - 1) + fib(n - 2) }\nlog(fib(10)) }",
1371        );
1372        assert_eq!(out, "[harn] 55");
1373    }
1374
1375    #[test]
1376    fn test_ternary() {
1377        let out = run_output(
1378            r#"pipeline t(task) { let x = 5
1379let r = x > 0 ? "positive" : "non-positive"
1380log(r) }"#,
1381        );
1382        assert_eq!(out, "[harn] positive");
1383    }
1384
1385    #[test]
1386    fn test_for_in_dict() {
1387        let out = run_output(
1388            "pipeline t(task) { let d = {a: 1, b: 2}\nfor entry in d { log(entry.key) } }",
1389        );
1390        assert_eq!(out, "[harn] a\n[harn] b");
1391    }
1392
1393    #[test]
1394    fn test_list_any_all() {
1395        let out = run_output(
1396            "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 })) }",
1397        );
1398        assert_eq!(out, "[harn] true\n[harn] true\n[harn] false");
1399    }
1400
1401    #[test]
1402    fn test_disassembly() {
1403        let mut lexer = Lexer::new("pipeline t(task) { log(2 + 3) }");
1404        let tokens = lexer.tokenize().unwrap();
1405        let mut parser = Parser::new(tokens);
1406        let program = parser.parse().unwrap();
1407        let chunk = Compiler::new().compile(&program).unwrap();
1408        let disasm = chunk.disassemble("test");
1409        assert!(disasm.contains("CONSTANT"));
1410        assert!(disasm.contains("ADD"));
1411        assert!(disasm.contains("CALL"));
1412    }
1413
1414    // --- Error handling tests ---
1415
1416    #[test]
1417    fn test_try_catch_basic() {
1418        let out = run_output(
1419            r#"pipeline t(task) { try { throw "oops" } catch(e) { log("caught: " + e) } }"#,
1420        );
1421        assert_eq!(out, "[harn] caught: oops");
1422    }
1423
1424    #[test]
1425    fn test_try_no_error() {
1426        let out = run_output(
1427            r#"pipeline t(task) {
1428var result = 0
1429try { result = 42 } catch(e) { result = 0 }
1430log(result)
1431}"#,
1432        );
1433        assert_eq!(out, "[harn] 42");
1434    }
1435
1436    #[test]
1437    fn test_throw_uncaught() {
1438        let result = run_harn_result(r#"pipeline t(task) { throw "boom" }"#);
1439        assert!(result.is_err());
1440    }
1441
1442    // --- Additional test coverage ---
1443
1444    fn run_vm(source: &str) -> String {
1445        let rt = tokio::runtime::Builder::new_current_thread()
1446            .enable_all()
1447            .build()
1448            .unwrap();
1449        rt.block_on(async {
1450            let local = tokio::task::LocalSet::new();
1451            local
1452                .run_until(async {
1453                    let mut lexer = Lexer::new(source);
1454                    let tokens = lexer.tokenize().unwrap();
1455                    let mut parser = Parser::new(tokens);
1456                    let program = parser.parse().unwrap();
1457                    let chunk = Compiler::new().compile(&program).unwrap();
1458                    let mut vm = Vm::new();
1459                    register_vm_stdlib(&mut vm);
1460                    vm.execute(&chunk).await.unwrap();
1461                    vm.output().to_string()
1462                })
1463                .await
1464        })
1465    }
1466
1467    fn run_vm_err(source: &str) -> String {
1468        let rt = tokio::runtime::Builder::new_current_thread()
1469            .enable_all()
1470            .build()
1471            .unwrap();
1472        rt.block_on(async {
1473            let local = tokio::task::LocalSet::new();
1474            local
1475                .run_until(async {
1476                    let mut lexer = Lexer::new(source);
1477                    let tokens = lexer.tokenize().unwrap();
1478                    let mut parser = Parser::new(tokens);
1479                    let program = parser.parse().unwrap();
1480                    let chunk = Compiler::new().compile(&program).unwrap();
1481                    let mut vm = Vm::new();
1482                    register_vm_stdlib(&mut vm);
1483                    match vm.execute(&chunk).await {
1484                        Err(e) => format!("{}", e),
1485                        Ok(_) => panic!("Expected error"),
1486                    }
1487                })
1488                .await
1489        })
1490    }
1491
1492    #[test]
1493    fn test_hello_world() {
1494        let out = run_vm(r#"pipeline default(task) { log("hello") }"#);
1495        assert_eq!(out, "[harn] hello\n");
1496    }
1497
1498    #[test]
1499    fn test_arithmetic_new() {
1500        let out = run_vm("pipeline default(task) { log(2 + 3) }");
1501        assert_eq!(out, "[harn] 5\n");
1502    }
1503
1504    #[test]
1505    fn test_string_concat_new() {
1506        let out = run_vm(r#"pipeline default(task) { log("a" + "b") }"#);
1507        assert_eq!(out, "[harn] ab\n");
1508    }
1509
1510    #[test]
1511    fn test_if_else_new() {
1512        let out = run_vm("pipeline default(task) { if true { log(1) } else { log(2) } }");
1513        assert_eq!(out, "[harn] 1\n");
1514    }
1515
1516    #[test]
1517    fn test_for_loop_new() {
1518        let out = run_vm("pipeline default(task) { for i in [1, 2, 3] { log(i) } }");
1519        assert_eq!(out, "[harn] 1\n[harn] 2\n[harn] 3\n");
1520    }
1521
1522    #[test]
1523    fn test_while_loop_new() {
1524        let out = run_vm("pipeline default(task) { var i = 0\nwhile i < 3 { log(i)\ni = i + 1 } }");
1525        assert_eq!(out, "[harn] 0\n[harn] 1\n[harn] 2\n");
1526    }
1527
1528    #[test]
1529    fn test_function_call_new() {
1530        let out =
1531            run_vm("pipeline default(task) { fn add(a, b) { return a + b }\nlog(add(2, 3)) }");
1532        assert_eq!(out, "[harn] 5\n");
1533    }
1534
1535    #[test]
1536    fn test_closure_new() {
1537        let out = run_vm("pipeline default(task) { let f = { x -> x * 2 }\nlog(f(5)) }");
1538        assert_eq!(out, "[harn] 10\n");
1539    }
1540
1541    #[test]
1542    fn test_recursion() {
1543        let out = run_vm("pipeline default(task) { fn fact(n) { if n <= 1 { return 1 }\nreturn n * fact(n - 1) }\nlog(fact(5)) }");
1544        assert_eq!(out, "[harn] 120\n");
1545    }
1546
1547    #[test]
1548    fn test_try_catch_new() {
1549        let out = run_vm(r#"pipeline default(task) { try { throw "err" } catch (e) { log(e) } }"#);
1550        assert_eq!(out, "[harn] err\n");
1551    }
1552
1553    #[test]
1554    fn test_try_no_error_new() {
1555        let out = run_vm("pipeline default(task) { try { log(1) } catch (e) { log(2) } }");
1556        assert_eq!(out, "[harn] 1\n");
1557    }
1558
1559    #[test]
1560    fn test_list_map_new() {
1561        let out =
1562            run_vm("pipeline default(task) { let r = [1, 2, 3].map({ x -> x * 2 })\nlog(r) }");
1563        assert_eq!(out, "[harn] [2, 4, 6]\n");
1564    }
1565
1566    #[test]
1567    fn test_list_filter_new() {
1568        let out = run_vm(
1569            "pipeline default(task) { let r = [1, 2, 3, 4].filter({ x -> x > 2 })\nlog(r) }",
1570        );
1571        assert_eq!(out, "[harn] [3, 4]\n");
1572    }
1573
1574    #[test]
1575    fn test_dict_access_new() {
1576        let out = run_vm("pipeline default(task) { let d = {name: \"Alice\"}\nlog(d.name) }");
1577        assert_eq!(out, "[harn] Alice\n");
1578    }
1579
1580    #[test]
1581    fn test_string_interpolation() {
1582        let out = run_vm("pipeline default(task) { let x = 42\nlog(\"val=${x}\") }");
1583        assert_eq!(out, "[harn] val=42\n");
1584    }
1585
1586    #[test]
1587    fn test_match_new() {
1588        let out = run_vm(
1589            "pipeline default(task) { let x = \"b\"\nmatch x { \"a\" -> { log(1) } \"b\" -> { log(2) } } }",
1590        );
1591        assert_eq!(out, "[harn] 2\n");
1592    }
1593
1594    #[test]
1595    fn test_json_roundtrip() {
1596        let out = run_vm("pipeline default(task) { let s = json_stringify({a: 1})\nlog(s) }");
1597        assert!(out.contains("\"a\""));
1598        assert!(out.contains("1"));
1599    }
1600
1601    #[test]
1602    fn test_type_of() {
1603        let out = run_vm("pipeline default(task) { log(type_of(42))\nlog(type_of(\"hi\")) }");
1604        assert_eq!(out, "[harn] int\n[harn] string\n");
1605    }
1606
1607    #[test]
1608    fn test_stack_overflow() {
1609        let err = run_vm_err("pipeline default(task) { fn f() { f() }\nf() }");
1610        assert!(
1611            err.contains("stack") || err.contains("overflow") || err.contains("recursion"),
1612            "Expected stack overflow error, got: {}",
1613            err
1614        );
1615    }
1616
1617    #[test]
1618    fn test_division_by_zero() {
1619        let err = run_vm_err("pipeline default(task) { log(1 / 0) }");
1620        assert!(
1621            err.contains("Division by zero") || err.contains("division"),
1622            "Expected division by zero error, got: {}",
1623            err
1624        );
1625    }
1626
1627    #[test]
1628    fn test_float_division_by_zero_uses_ieee_values() {
1629        let out = run_vm(
1630            "pipeline default(task) { log(is_nan(0.0 / 0.0))\nlog(is_infinite(1.0 / 0.0))\nlog(is_infinite(-1.0 / 0.0)) }",
1631        );
1632        assert_eq!(out, "[harn] true\n[harn] true\n[harn] true\n");
1633    }
1634
1635    #[test]
1636    fn test_reusing_catch_binding_name_in_same_block() {
1637        let out = run_vm(
1638            r#"pipeline default(task) {
1639try {
1640    throw "a"
1641} catch e {
1642    log(e)
1643}
1644try {
1645    throw "b"
1646} catch e {
1647    log(e)
1648}
1649}"#,
1650        );
1651        assert_eq!(out, "[harn] a\n[harn] b\n");
1652    }
1653
1654    #[test]
1655    fn test_try_catch_nested() {
1656        let out = run_output(
1657            r#"pipeline t(task) {
1658try {
1659    try {
1660        throw "inner"
1661    } catch(e) {
1662        log("inner caught: " + e)
1663        throw "outer"
1664    }
1665} catch(e2) {
1666    log("outer caught: " + e2)
1667}
1668}"#,
1669        );
1670        assert_eq!(
1671            out,
1672            "[harn] inner caught: inner\n[harn] outer caught: outer"
1673        );
1674    }
1675
1676    // --- Concurrency tests ---
1677
1678    #[test]
1679    fn test_parallel_basic() {
1680        let out = run_output(
1681            "pipeline t(task) { let results = parallel(3) { i -> i * 10 }\nlog(results) }",
1682        );
1683        assert_eq!(out, "[harn] [0, 10, 20]");
1684    }
1685
1686    #[test]
1687    fn test_parallel_no_variable() {
1688        let out = run_output("pipeline t(task) { let results = parallel(3) { 42 }\nlog(results) }");
1689        assert_eq!(out, "[harn] [42, 42, 42]");
1690    }
1691
1692    #[test]
1693    fn test_parallel_map_basic() {
1694        let out = run_output(
1695            "pipeline t(task) { let results = parallel_map([1, 2, 3]) { x -> x * x }\nlog(results) }",
1696        );
1697        assert_eq!(out, "[harn] [1, 4, 9]");
1698    }
1699
1700    #[test]
1701    fn test_spawn_await() {
1702        let out = run_output(
1703            r#"pipeline t(task) {
1704let handle = spawn { log("spawned") }
1705let result = await(handle)
1706log("done")
1707}"#,
1708        );
1709        assert_eq!(out, "[harn] spawned\n[harn] done");
1710    }
1711
1712    #[test]
1713    fn test_spawn_cancel() {
1714        let out = run_output(
1715            r#"pipeline t(task) {
1716let handle = spawn { log("should be cancelled") }
1717cancel(handle)
1718log("cancelled")
1719}"#,
1720        );
1721        assert_eq!(out, "[harn] cancelled");
1722    }
1723
1724    #[test]
1725    fn test_spawn_returns_value() {
1726        let out = run_output("pipeline t(task) { let h = spawn { 42 }\nlet r = await(h)\nlog(r) }");
1727        assert_eq!(out, "[harn] 42");
1728    }
1729
1730    // --- Deadline tests ---
1731
1732    #[test]
1733    fn test_deadline_success() {
1734        let out = run_output(
1735            r#"pipeline t(task) {
1736let result = deadline 5s { log("within deadline")
173742 }
1738log(result)
1739}"#,
1740        );
1741        assert_eq!(out, "[harn] within deadline\n[harn] 42");
1742    }
1743
1744    #[test]
1745    fn test_deadline_exceeded() {
1746        let result = run_harn_result(
1747            r#"pipeline t(task) {
1748deadline 1ms {
1749  var i = 0
1750  while i < 1000000 { i = i + 1 }
1751}
1752}"#,
1753        );
1754        assert!(result.is_err());
1755    }
1756
1757    #[test]
1758    fn test_deadline_caught_by_try() {
1759        let out = run_output(
1760            r#"pipeline t(task) {
1761try {
1762  deadline 1ms {
1763    var i = 0
1764    while i < 1000000 { i = i + 1 }
1765  }
1766} catch(e) {
1767  log("caught")
1768}
1769}"#,
1770        );
1771        assert_eq!(out, "[harn] caught");
1772    }
1773
1774    /// Helper that runs Harn source with a set of denied builtins.
1775    fn run_harn_with_denied(
1776        source: &str,
1777        denied: HashSet<String>,
1778    ) -> Result<(String, VmValue), VmError> {
1779        let rt = tokio::runtime::Builder::new_current_thread()
1780            .enable_all()
1781            .build()
1782            .unwrap();
1783        rt.block_on(async {
1784            let local = tokio::task::LocalSet::new();
1785            local
1786                .run_until(async {
1787                    let mut lexer = Lexer::new(source);
1788                    let tokens = lexer.tokenize().unwrap();
1789                    let mut parser = Parser::new(tokens);
1790                    let program = parser.parse().unwrap();
1791                    let chunk = Compiler::new().compile(&program).unwrap();
1792
1793                    let mut vm = Vm::new();
1794                    register_vm_stdlib(&mut vm);
1795                    vm.set_denied_builtins(denied);
1796                    let result = vm.execute(&chunk).await?;
1797                    Ok((vm.output().to_string(), result))
1798                })
1799                .await
1800        })
1801    }
1802
1803    #[test]
1804    fn test_sandbox_deny_builtin() {
1805        let denied: HashSet<String> = ["push".to_string()].into_iter().collect();
1806        let result = run_harn_with_denied(
1807            r#"pipeline t(task) {
1808let xs = [1, 2]
1809push(xs, 3)
1810}"#,
1811            denied,
1812        );
1813        let err = result.unwrap_err();
1814        let msg = format!("{err}");
1815        assert!(
1816            msg.contains("not permitted"),
1817            "expected not permitted, got: {msg}"
1818        );
1819        assert!(
1820            msg.contains("push"),
1821            "expected builtin name in error, got: {msg}"
1822        );
1823    }
1824
1825    #[test]
1826    fn test_sandbox_allowed_builtin_works() {
1827        // Denying "push" should not block "log"
1828        let denied: HashSet<String> = ["push".to_string()].into_iter().collect();
1829        let result = run_harn_with_denied(r#"pipeline t(task) { log("hello") }"#, denied);
1830        let (output, _) = result.unwrap();
1831        assert_eq!(output.trim(), "[harn] hello");
1832    }
1833
1834    #[test]
1835    fn test_sandbox_empty_denied_set() {
1836        // With an empty denied set, everything should work.
1837        let result = run_harn_with_denied(r#"pipeline t(task) { log("ok") }"#, HashSet::new());
1838        let (output, _) = result.unwrap();
1839        assert_eq!(output.trim(), "[harn] ok");
1840    }
1841
1842    #[test]
1843    fn test_sandbox_propagates_to_spawn() {
1844        // Denied builtins should propagate to spawned VMs.
1845        let denied: HashSet<String> = ["push".to_string()].into_iter().collect();
1846        let result = run_harn_with_denied(
1847            r#"pipeline t(task) {
1848let handle = spawn {
1849  let xs = [1, 2]
1850  push(xs, 3)
1851}
1852await(handle)
1853}"#,
1854            denied,
1855        );
1856        let err = result.unwrap_err();
1857        let msg = format!("{err}");
1858        assert!(
1859            msg.contains("not permitted"),
1860            "expected not permitted in spawned VM, got: {msg}"
1861        );
1862    }
1863
1864    #[test]
1865    fn test_sandbox_propagates_to_parallel() {
1866        // Denied builtins should propagate to parallel VMs.
1867        let denied: HashSet<String> = ["push".to_string()].into_iter().collect();
1868        let result = run_harn_with_denied(
1869            r#"pipeline t(task) {
1870let results = parallel(2) { i ->
1871  let xs = [1, 2]
1872  push(xs, 3)
1873}
1874}"#,
1875            denied,
1876        );
1877        let err = result.unwrap_err();
1878        let msg = format!("{err}");
1879        assert!(
1880            msg.contains("not permitted"),
1881            "expected not permitted in parallel VM, got: {msg}"
1882        );
1883    }
1884}