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 VmAsyncBuiltinFn, VmBuiltinFn, VmClosure, VmEnv, VmError, VmTaskHandle, VmValue,
14};
15
16pub(crate) struct CallFrame {
18 pub(crate) chunk: Chunk,
19 pub(crate) ip: usize,
20 pub(crate) stack_base: usize,
21 pub(crate) saved_env: VmEnv,
22 pub(crate) fn_name: String,
24}
25
26pub(crate) struct ExceptionHandler {
28 pub(crate) catch_ip: usize,
29 pub(crate) stack_depth: usize,
30 pub(crate) frame_depth: usize,
31 pub(crate) error_type: String,
33}
34
35#[derive(Debug, Clone, PartialEq)]
37pub enum DebugAction {
38 Continue,
40 Stop,
42}
43
44#[derive(Debug, Clone)]
46pub struct DebugState {
47 pub line: usize,
48 pub variables: BTreeMap<String, VmValue>,
49 pub frame_name: String,
50 pub frame_depth: usize,
51}
52
53pub(crate) enum IterState {
55 Vec {
56 items: Vec<VmValue>,
57 idx: usize,
58 },
59 Channel {
60 receiver: std::sync::Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<VmValue>>>,
61 closed: std::sync::Arc<std::sync::atomic::AtomicBool>,
62 },
63}
64
65pub struct Vm {
67 pub(crate) stack: Vec<VmValue>,
68 pub(crate) env: VmEnv,
69 pub(crate) output: String,
70 pub(crate) builtins: BTreeMap<String, VmBuiltinFn>,
71 pub(crate) async_builtins: BTreeMap<String, VmAsyncBuiltinFn>,
72 pub(crate) iterators: Vec<IterState>,
74 pub(crate) frames: Vec<CallFrame>,
76 pub(crate) exception_handlers: Vec<ExceptionHandler>,
78 pub(crate) spawned_tasks: BTreeMap<String, VmTaskHandle>,
80 pub(crate) task_counter: u64,
82 pub(crate) deadlines: Vec<(Instant, usize)>,
84 pub(crate) breakpoints: Vec<usize>,
86 pub(crate) step_mode: bool,
88 pub(crate) step_frame_depth: usize,
90 pub(crate) stopped: bool,
92 pub(crate) last_line: usize,
94 pub(crate) source_dir: Option<std::path::PathBuf>,
96 pub(crate) imported_paths: Vec<std::path::PathBuf>,
98 pub(crate) source_file: Option<String>,
100 pub(crate) source_text: Option<String>,
102 pub(crate) bridge: Option<Rc<crate::bridge::HostBridge>>,
104 pub(crate) denied_builtins: HashSet<String>,
106}
107
108impl Vm {
109 pub fn new() -> Self {
110 Self {
111 stack: Vec::with_capacity(256),
112 env: VmEnv::new(),
113 output: String::new(),
114 builtins: BTreeMap::new(),
115 async_builtins: BTreeMap::new(),
116 iterators: Vec::new(),
117 frames: Vec::new(),
118 exception_handlers: Vec::new(),
119 spawned_tasks: BTreeMap::new(),
120 task_counter: 0,
121 deadlines: Vec::new(),
122 breakpoints: Vec::new(),
123 step_mode: false,
124 step_frame_depth: 0,
125 stopped: false,
126 last_line: 0,
127 source_dir: None,
128 imported_paths: Vec::new(),
129 source_file: None,
130 source_text: None,
131 bridge: None,
132 denied_builtins: HashSet::new(),
133 }
134 }
135
136 pub fn set_bridge(&mut self, bridge: Rc<crate::bridge::HostBridge>) {
138 self.bridge = Some(bridge);
139 }
140
141 pub fn set_denied_builtins(&mut self, denied: HashSet<String>) {
144 self.denied_builtins = denied;
145 }
146
147 pub fn set_source_info(&mut self, file: &str, text: &str) {
149 self.source_file = Some(file.to_string());
150 self.source_text = Some(text.to_string());
151 }
152
153 pub fn set_breakpoints(&mut self, lines: Vec<usize>) {
155 self.breakpoints = lines;
156 }
157
158 pub fn set_step_mode(&mut self, step: bool) {
160 self.step_mode = step;
161 self.step_frame_depth = self.frames.len();
162 }
163
164 pub fn set_step_over(&mut self) {
166 self.step_mode = true;
167 self.step_frame_depth = self.frames.len();
168 }
169
170 pub fn set_step_out(&mut self) {
172 self.step_mode = true;
173 self.step_frame_depth = self.frames.len().saturating_sub(1);
174 }
175
176 pub fn is_stopped(&self) -> bool {
178 self.stopped
179 }
180
181 pub fn debug_state(&self) -> DebugState {
183 let line = self.current_line();
184 let variables = self.env.all_variables();
185 let frame_name = if self.frames.len() > 1 {
186 format!("frame_{}", self.frames.len() - 1)
187 } else {
188 "pipeline".to_string()
189 };
190 DebugState {
191 line,
192 variables,
193 frame_name,
194 frame_depth: self.frames.len(),
195 }
196 }
197
198 pub fn debug_stack_frames(&self) -> Vec<(String, usize)> {
200 let mut frames = Vec::new();
201 for (i, frame) in self.frames.iter().enumerate() {
202 let line = if frame.ip > 0 && frame.ip - 1 < frame.chunk.lines.len() {
203 frame.chunk.lines[frame.ip - 1] as usize
204 } else {
205 0
206 };
207 let name = if frame.fn_name.is_empty() {
208 if i == 0 {
209 "pipeline".to_string()
210 } else {
211 format!("fn_{}", i)
212 }
213 } else {
214 frame.fn_name.clone()
215 };
216 frames.push((name, line));
217 }
218 frames
219 }
220
221 fn current_line(&self) -> usize {
223 if let Some(frame) = self.frames.last() {
224 let ip = if frame.ip > 0 { frame.ip - 1 } else { 0 };
225 if ip < frame.chunk.lines.len() {
226 return frame.chunk.lines[ip] as usize;
227 }
228 }
229 0
230 }
231
232 pub async fn step_execute(&mut self) -> Result<Option<(VmValue, bool)>, VmError> {
235 let current_line = self.current_line();
237 let line_changed = current_line != self.last_line && current_line > 0;
238
239 if line_changed {
240 self.last_line = current_line;
241
242 if self.breakpoints.contains(¤t_line) {
244 self.stopped = true;
245 return Ok(Some((VmValue::Nil, true))); }
247
248 if self.step_mode && self.frames.len() <= self.step_frame_depth + 1 {
250 self.step_mode = false;
251 self.stopped = true;
252 return Ok(Some((VmValue::Nil, true))); }
254 }
255
256 self.stopped = false;
258 self.execute_one_cycle().await
259 }
260
261 async fn execute_one_cycle(&mut self) -> Result<Option<(VmValue, bool)>, VmError> {
263 if let Some(&(deadline, _)) = self.deadlines.last() {
265 if Instant::now() > deadline {
266 self.deadlines.pop();
267 let err = VmError::Thrown(VmValue::String(Rc::from("Deadline exceeded")));
268 match self.handle_error(err) {
269 Ok(None) => return Ok(None),
270 Ok(Some(val)) => return Ok(Some((val, false))),
271 Err(e) => return Err(e),
272 }
273 }
274 }
275
276 let frame = match self.frames.last_mut() {
278 Some(f) => f,
279 None => {
280 let val = self.stack.pop().unwrap_or(VmValue::Nil);
281 return Ok(Some((val, false)));
282 }
283 };
284
285 if frame.ip >= frame.chunk.code.len() {
287 let val = self.stack.pop().unwrap_or(VmValue::Nil);
288 let popped_frame = self.frames.pop().unwrap();
289 if self.frames.is_empty() {
290 return Ok(Some((val, false)));
291 } else {
292 self.env = popped_frame.saved_env;
293 self.stack.truncate(popped_frame.stack_base);
294 self.stack.push(val);
295 return Ok(None);
296 }
297 }
298
299 let op = frame.chunk.code[frame.ip];
300 frame.ip += 1;
301
302 match self.execute_op(op).await {
303 Ok(Some(val)) => Ok(Some((val, false))),
304 Ok(None) => Ok(None),
305 Err(VmError::Return(val)) => {
306 if let Some(popped_frame) = self.frames.pop() {
307 let current_depth = self.frames.len();
308 self.exception_handlers
309 .retain(|h| h.frame_depth <= current_depth);
310 if self.frames.is_empty() {
311 return Ok(Some((val, false)));
312 }
313 self.env = popped_frame.saved_env;
314 self.stack.truncate(popped_frame.stack_base);
315 self.stack.push(val);
316 Ok(None)
317 } else {
318 Ok(Some((val, false)))
319 }
320 }
321 Err(e) => match self.handle_error(e) {
322 Ok(None) => Ok(None),
323 Ok(Some(val)) => Ok(Some((val, false))),
324 Err(e) => Err(e),
325 },
326 }
327 }
328
329 pub fn start(&mut self, chunk: &Chunk) {
331 self.frames.push(CallFrame {
332 chunk: chunk.clone(),
333 ip: 0,
334 stack_base: self.stack.len(),
335 saved_env: self.env.clone(),
336 fn_name: String::new(),
337 });
338 }
339
340 pub fn register_builtin<F>(&mut self, name: &str, f: F)
342 where
343 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + 'static,
344 {
345 self.builtins.insert(name.to_string(), Rc::new(f));
346 }
347
348 pub fn unregister_builtin(&mut self, name: &str) {
350 self.builtins.remove(name);
351 }
352
353 pub fn register_async_builtin<F, Fut>(&mut self, name: &str, f: F)
355 where
356 F: Fn(Vec<VmValue>) -> Fut + 'static,
357 Fut: Future<Output = Result<VmValue, VmError>> + 'static,
358 {
359 self.async_builtins
360 .insert(name.to_string(), Rc::new(move |args| Box::pin(f(args))));
361 }
362
363 fn child_vm(&self) -> Vm {
366 Vm {
367 stack: Vec::with_capacity(64),
368 env: self.env.clone(),
369 output: String::new(),
370 builtins: self.builtins.clone(),
371 async_builtins: self.async_builtins.clone(),
372 iterators: Vec::new(),
373 frames: Vec::new(),
374 exception_handlers: Vec::new(),
375 spawned_tasks: BTreeMap::new(),
376 task_counter: 0,
377 deadlines: Vec::new(),
378 breakpoints: Vec::new(),
379 step_mode: false,
380 step_frame_depth: 0,
381 stopped: false,
382 last_line: 0,
383 source_dir: None,
384 imported_paths: Vec::new(),
385 source_file: self.source_file.clone(),
386 source_text: self.source_text.clone(),
387 bridge: self.bridge.clone(),
388 denied_builtins: self.denied_builtins.clone(),
389 }
390 }
391
392 pub fn set_source_dir(&mut self, dir: &std::path::Path) {
394 self.source_dir = Some(dir.to_path_buf());
395 }
396
397 pub fn builtin_names(&self) -> Vec<String> {
399 let mut names: Vec<String> = self.builtins.keys().cloned().collect();
400 names.extend(self.async_builtins.keys().cloned());
401 names
402 }
403
404 pub fn set_global(&mut self, name: &str, value: VmValue) {
406 self.env.define(name, value, false);
407 }
408
409 fn execute_import<'a>(
411 &'a mut self,
412 path: &'a str,
413 selected_names: Option<&'a [String]>,
414 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + 'a>> {
415 Box::pin(async move {
416 use std::path::PathBuf;
417
418 if let Some(module) = path.strip_prefix("std/") {
420 if let Some(source) = crate::stdlib_modules::get_stdlib_source(module) {
421 let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
422 if self.imported_paths.contains(&synthetic) {
423 return Ok(());
424 }
425 self.imported_paths.push(synthetic);
426
427 let mut lexer = harn_lexer::Lexer::new(source);
428 let tokens = lexer.tokenize().map_err(|e| {
429 VmError::Runtime(format!("stdlib lex error in std/{module}: {e}"))
430 })?;
431 let mut parser = harn_parser::Parser::new(tokens);
432 let program = parser.parse().map_err(|e| {
433 VmError::Runtime(format!("stdlib parse error in std/{module}: {e}"))
434 })?;
435
436 self.import_declarations(&program, selected_names, None)
437 .await?;
438 return Ok(());
439 }
440 return Err(VmError::Runtime(format!(
441 "Unknown stdlib module: std/{module}"
442 )));
443 }
444
445 let base = self
447 .source_dir
448 .clone()
449 .unwrap_or_else(|| PathBuf::from("."));
450 let mut file_path = base.join(path);
451
452 if !file_path.exists() && file_path.extension().is_none() {
454 file_path.set_extension("harn");
455 }
456
457 if !file_path.exists() {
459 for pkg_dir in [".harn/packages", ".burin/packages"] {
460 let pkg_path = base.join(pkg_dir).join(path);
461 if pkg_path.exists() {
462 file_path = if pkg_path.is_dir() {
463 let lib = pkg_path.join("lib.harn");
464 if lib.exists() {
465 lib
466 } else {
467 pkg_path
468 }
469 } else {
470 pkg_path
471 };
472 break;
473 }
474 let mut pkg_harn = pkg_path.clone();
475 pkg_harn.set_extension("harn");
476 if pkg_harn.exists() {
477 file_path = pkg_harn;
478 break;
479 }
480 }
481 }
482
483 let canonical = file_path
485 .canonicalize()
486 .unwrap_or_else(|_| file_path.clone());
487 if self.imported_paths.contains(&canonical) {
488 return Ok(()); }
490 self.imported_paths.push(canonical);
491
492 let source = std::fs::read_to_string(&file_path).map_err(|e| {
494 VmError::Runtime(format!(
495 "Import error: cannot read '{}': {e}",
496 file_path.display()
497 ))
498 })?;
499
500 let mut lexer = harn_lexer::Lexer::new(&source);
501 let tokens = lexer
502 .tokenize()
503 .map_err(|e| VmError::Runtime(format!("Import lex error: {e}")))?;
504 let mut parser = harn_parser::Parser::new(tokens);
505 let program = parser
506 .parse()
507 .map_err(|e| VmError::Runtime(format!("Import parse error: {e}")))?;
508
509 self.import_declarations(&program, selected_names, Some(&file_path))
510 .await?;
511
512 Ok(())
513 })
514 }
515
516 fn import_declarations<'a>(
519 &'a mut self,
520 program: &'a [harn_parser::SNode],
521 selected_names: Option<&'a [String]>,
522 file_path: Option<&'a std::path::Path>,
523 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + 'a>> {
524 Box::pin(async move {
525 let has_pub = program
526 .iter()
527 .any(|n| matches!(&n.node, harn_parser::Node::FnDecl { is_pub: true, .. }));
528
529 for node in program {
530 match &node.node {
531 harn_parser::Node::FnDecl {
532 name,
533 params,
534 body,
535 is_pub,
536 ..
537 } => {
538 if selected_names.is_none() && has_pub && !is_pub {
542 continue;
543 }
544 if let Some(names) = selected_names {
545 if !names.contains(name) {
546 continue;
547 }
548 }
549 let mut compiler = crate::Compiler::new();
551 let func_chunk = compiler
552 .compile_fn_body(params, body)
553 .map_err(|e| VmError::Runtime(format!("Import compile error: {e}")))?;
554 let closure = VmClosure {
555 func: func_chunk,
556 env: self.env.clone(),
557 };
558 self.env
559 .define(name, VmValue::Closure(Rc::new(closure)), false);
560 }
561 harn_parser::Node::ImportDecl { path: sub_path } => {
562 let old_dir = self.source_dir.clone();
563 if let Some(fp) = file_path {
564 if let Some(parent) = fp.parent() {
565 self.source_dir = Some(parent.to_path_buf());
566 }
567 }
568 self.execute_import(sub_path, None).await?;
569 self.source_dir = old_dir;
570 }
571 harn_parser::Node::SelectiveImport {
572 names,
573 path: sub_path,
574 } => {
575 let old_dir = self.source_dir.clone();
576 if let Some(fp) = file_path {
577 if let Some(parent) = fp.parent() {
578 self.source_dir = Some(parent.to_path_buf());
579 }
580 }
581 self.execute_import(sub_path, Some(names)).await?;
582 self.source_dir = old_dir;
583 }
584 _ => {} }
586 }
587
588 Ok(())
589 })
590 }
591
592 pub fn output(&self) -> &str {
594 &self.output
595 }
596
597 pub async fn execute(&mut self, chunk: &Chunk) -> Result<VmValue, VmError> {
599 self.run_chunk(chunk).await
600 }
601
602 fn handle_error(&mut self, error: VmError) -> Result<Option<VmValue>, VmError> {
604 let thrown_value = match &error {
606 VmError::Thrown(v) => v.clone(),
607 other => VmValue::String(Rc::from(other.to_string())),
608 };
609
610 if let Some(handler) = self.exception_handlers.pop() {
611 if !handler.error_type.is_empty() {
613 let matches = match &thrown_value {
614 VmValue::EnumVariant { enum_name, .. } => *enum_name == handler.error_type,
615 _ => false,
616 };
617 if !matches {
618 return self.handle_error(error);
620 }
621 }
622
623 while self.frames.len() > handler.frame_depth {
625 if let Some(frame) = self.frames.pop() {
626 self.env = frame.saved_env;
627 }
628 }
629
630 while self
632 .deadlines
633 .last()
634 .is_some_and(|d| d.1 > handler.frame_depth)
635 {
636 self.deadlines.pop();
637 }
638
639 self.stack.truncate(handler.stack_depth);
641
642 self.stack.push(thrown_value);
644
645 if let Some(frame) = self.frames.last_mut() {
647 frame.ip = handler.catch_ip;
648 }
649
650 Ok(None) } else {
652 Err(error) }
654 }
655
656 async fn run_chunk(&mut self, chunk: &Chunk) -> Result<VmValue, VmError> {
657 self.frames.push(CallFrame {
659 chunk: chunk.clone(),
660 ip: 0,
661 stack_base: self.stack.len(),
662 saved_env: self.env.clone(),
663 fn_name: String::new(),
664 });
665
666 loop {
667 if let Some(&(deadline, _)) = self.deadlines.last() {
669 if Instant::now() > deadline {
670 self.deadlines.pop();
671 let err = VmError::Thrown(VmValue::String(Rc::from("Deadline exceeded")));
672 match self.handle_error(err) {
673 Ok(None) => continue,
674 Ok(Some(val)) => return Ok(val),
675 Err(e) => return Err(e),
676 }
677 }
678 }
679
680 let frame = match self.frames.last_mut() {
682 Some(f) => f,
683 None => return Ok(self.stack.pop().unwrap_or(VmValue::Nil)),
684 };
685
686 if frame.ip >= frame.chunk.code.len() {
688 let val = self.stack.pop().unwrap_or(VmValue::Nil);
689 let popped_frame = self.frames.pop().unwrap();
690
691 if self.frames.is_empty() {
692 return Ok(val);
694 } else {
695 self.env = popped_frame.saved_env;
697 self.stack.truncate(popped_frame.stack_base);
698 self.stack.push(val);
699 continue;
700 }
701 }
702
703 let op = frame.chunk.code[frame.ip];
704 frame.ip += 1;
705
706 match self.execute_op(op).await {
707 Ok(Some(val)) => return Ok(val),
708 Ok(None) => continue,
709 Err(VmError::Return(val)) => {
710 if let Some(popped_frame) = self.frames.pop() {
712 let current_depth = self.frames.len();
714 self.exception_handlers
715 .retain(|h| h.frame_depth <= current_depth);
716
717 if self.frames.is_empty() {
718 return Ok(val);
719 }
720 self.env = popped_frame.saved_env;
721 self.stack.truncate(popped_frame.stack_base);
722 self.stack.push(val);
723 } else {
724 return Ok(val);
725 }
726 }
727 Err(e) => {
728 match self.handle_error(e) {
729 Ok(None) => continue, Ok(Some(val)) => return Ok(val),
731 Err(e) => return Err(e), }
733 }
734 }
735 }
736 }
737
738 const MAX_FRAMES: usize = 512;
739
740 fn merge_env_into_closure(caller_env: &VmEnv, closure: &VmClosure) -> VmEnv {
742 let mut call_env = closure.env.clone();
743 for scope in &caller_env.scopes {
744 for (name, (val, mutable)) in &scope.vars {
745 if call_env.get(name).is_none() {
746 call_env.define(name, val.clone(), *mutable);
747 }
748 }
749 }
750 call_env
751 }
752
753 fn push_closure_frame(
755 &mut self,
756 closure: &VmClosure,
757 args: &[VmValue],
758 _parent_functions: &[CompiledFunction],
759 ) -> Result<(), VmError> {
760 if self.frames.len() >= Self::MAX_FRAMES {
761 return Err(VmError::StackOverflow);
762 }
763 let saved_env = self.env.clone();
764
765 let mut call_env = Self::merge_env_into_closure(&saved_env, closure);
766 call_env.push_scope();
767
768 for (i, param) in closure.func.params.iter().enumerate() {
769 let val = args.get(i).cloned().unwrap_or(VmValue::Nil);
770 call_env.define(param, val, false);
771 }
772
773 self.env = call_env;
774
775 self.frames.push(CallFrame {
776 chunk: closure.func.chunk.clone(),
777 ip: 0,
778 stack_base: self.stack.len(),
779 saved_env,
780 fn_name: closure.func.name.clone(),
781 });
782
783 Ok(())
784 }
785
786 fn pop(&mut self) -> Result<VmValue, VmError> {
787 self.stack.pop().ok_or(VmError::StackUnderflow)
788 }
789
790 fn peek(&self) -> Result<&VmValue, VmError> {
791 self.stack.last().ok_or(VmError::StackUnderflow)
792 }
793
794 fn const_string(c: &Constant) -> Result<String, VmError> {
795 match c {
796 Constant::String(s) => Ok(s.clone()),
797 _ => Err(VmError::TypeError("expected string constant".into())),
798 }
799 }
800
801 fn call_closure<'a>(
804 &'a mut self,
805 closure: &'a VmClosure,
806 args: &'a [VmValue],
807 _parent_functions: &'a [CompiledFunction],
808 ) -> Pin<Box<dyn Future<Output = Result<VmValue, VmError>> + 'a>> {
809 Box::pin(async move {
810 let saved_env = self.env.clone();
811 let saved_frames = std::mem::take(&mut self.frames);
812 let saved_handlers = std::mem::take(&mut self.exception_handlers);
813 let saved_iterators = std::mem::take(&mut self.iterators);
814 let saved_deadlines = std::mem::take(&mut self.deadlines);
815
816 let mut call_env = Self::merge_env_into_closure(&saved_env, closure);
817 call_env.push_scope();
818
819 for (i, param) in closure.func.params.iter().enumerate() {
820 let val = args.get(i).cloned().unwrap_or(VmValue::Nil);
821 call_env.define(param, val, false);
822 }
823
824 self.env = call_env;
825 let result = self.run_chunk(&closure.func.chunk).await;
826
827 self.env = saved_env;
828 self.frames = saved_frames;
829 self.exception_handlers = saved_handlers;
830 self.iterators = saved_iterators;
831 self.deadlines = saved_deadlines;
832
833 result
834 })
835 }
836
837 async fn call_named_builtin(
840 &mut self,
841 name: &str,
842 args: Vec<VmValue>,
843 ) -> Result<VmValue, VmError> {
844 if self.denied_builtins.contains(name) {
846 return Err(VmError::Runtime(format!(
847 "Permission denied: builtin '{}' is not allowed in sandbox mode",
848 name
849 )));
850 }
851 if let Some(builtin) = self.builtins.get(name).cloned() {
852 builtin(&args, &mut self.output)
853 } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
854 async_builtin(args).await
855 } else if let Some(bridge) = &self.bridge {
856 let args_json: Vec<serde_json::Value> =
857 args.iter().map(crate::llm::vm_value_to_json).collect();
858 let result = bridge
859 .call(
860 "builtin_call",
861 serde_json::json!({"name": name, "args": args_json}),
862 )
863 .await?;
864 Ok(crate::bridge::json_result_to_vm_value(&result))
865 } else {
866 Err(VmError::UndefinedBuiltin(name.to_string()))
867 }
868 }
869}
870
871impl Default for Vm {
872 fn default() -> Self {
873 Self::new()
874 }
875}
876
877#[cfg(test)]
878mod tests {
879 use super::*;
880 use crate::compiler::Compiler;
881 use crate::stdlib::register_vm_stdlib;
882 use harn_lexer::Lexer;
883 use harn_parser::Parser;
884
885 fn run_harn(source: &str) -> (String, VmValue) {
886 let rt = tokio::runtime::Builder::new_current_thread()
887 .enable_all()
888 .build()
889 .unwrap();
890 rt.block_on(async {
891 let local = tokio::task::LocalSet::new();
892 local
893 .run_until(async {
894 let mut lexer = Lexer::new(source);
895 let tokens = lexer.tokenize().unwrap();
896 let mut parser = Parser::new(tokens);
897 let program = parser.parse().unwrap();
898 let chunk = Compiler::new().compile(&program).unwrap();
899
900 let mut vm = Vm::new();
901 register_vm_stdlib(&mut vm);
902 let result = vm.execute(&chunk).await.unwrap();
903 (vm.output().to_string(), result)
904 })
905 .await
906 })
907 }
908
909 fn run_output(source: &str) -> String {
910 run_harn(source).0.trim_end().to_string()
911 }
912
913 fn run_harn_result(source: &str) -> Result<(String, VmValue), VmError> {
914 let rt = tokio::runtime::Builder::new_current_thread()
915 .enable_all()
916 .build()
917 .unwrap();
918 rt.block_on(async {
919 let local = tokio::task::LocalSet::new();
920 local
921 .run_until(async {
922 let mut lexer = Lexer::new(source);
923 let tokens = lexer.tokenize().unwrap();
924 let mut parser = Parser::new(tokens);
925 let program = parser.parse().unwrap();
926 let chunk = Compiler::new().compile(&program).unwrap();
927
928 let mut vm = Vm::new();
929 register_vm_stdlib(&mut vm);
930 let result = vm.execute(&chunk).await?;
931 Ok((vm.output().to_string(), result))
932 })
933 .await
934 })
935 }
936
937 #[test]
938 fn test_arithmetic() {
939 let out =
940 run_output("pipeline t(task) { log(2 + 3)\nlog(10 - 4)\nlog(3 * 5)\nlog(10 / 3) }");
941 assert_eq!(out, "[harn] 5\n[harn] 6\n[harn] 15\n[harn] 3");
942 }
943
944 #[test]
945 fn test_mixed_arithmetic() {
946 let out = run_output("pipeline t(task) { log(3 + 1.5)\nlog(10 - 2.5) }");
947 assert_eq!(out, "[harn] 4.5\n[harn] 7.5");
948 }
949
950 #[test]
951 fn test_comparisons() {
952 let out =
953 run_output("pipeline t(task) { log(1 < 2)\nlog(2 > 3)\nlog(1 == 1)\nlog(1 != 2) }");
954 assert_eq!(out, "[harn] true\n[harn] false\n[harn] true\n[harn] true");
955 }
956
957 #[test]
958 fn test_let_var() {
959 let out = run_output("pipeline t(task) { let x = 42\nlog(x)\nvar y = 1\ny = 2\nlog(y) }");
960 assert_eq!(out, "[harn] 42\n[harn] 2");
961 }
962
963 #[test]
964 fn test_if_else() {
965 let out = run_output(
966 r#"pipeline t(task) { if true { log("yes") } if false { log("wrong") } else { log("no") } }"#,
967 );
968 assert_eq!(out, "[harn] yes\n[harn] no");
969 }
970
971 #[test]
972 fn test_while_loop() {
973 let out = run_output("pipeline t(task) { var i = 0\n while i < 5 { i = i + 1 }\n log(i) }");
974 assert_eq!(out, "[harn] 5");
975 }
976
977 #[test]
978 fn test_for_in() {
979 let out = run_output("pipeline t(task) { for item in [1, 2, 3] { log(item) } }");
980 assert_eq!(out, "[harn] 1\n[harn] 2\n[harn] 3");
981 }
982
983 #[test]
984 fn test_fn_decl_and_call() {
985 let out = run_output("pipeline t(task) { fn add(a, b) { return a + b }\nlog(add(3, 4)) }");
986 assert_eq!(out, "[harn] 7");
987 }
988
989 #[test]
990 fn test_closure() {
991 let out = run_output("pipeline t(task) { let double = { x -> x * 2 }\nlog(double(5)) }");
992 assert_eq!(out, "[harn] 10");
993 }
994
995 #[test]
996 fn test_closure_capture() {
997 let out = run_output(
998 "pipeline t(task) { let base = 10\nfn offset(x) { return x + base }\nlog(offset(5)) }",
999 );
1000 assert_eq!(out, "[harn] 15");
1001 }
1002
1003 #[test]
1004 fn test_string_concat() {
1005 let out = run_output(
1006 r#"pipeline t(task) { let a = "hello" + " " + "world"
1007log(a) }"#,
1008 );
1009 assert_eq!(out, "[harn] hello world");
1010 }
1011
1012 #[test]
1013 fn test_list_map() {
1014 let out = run_output(
1015 "pipeline t(task) { let doubled = [1, 2, 3].map({ x -> x * 2 })\nlog(doubled) }",
1016 );
1017 assert_eq!(out, "[harn] [2, 4, 6]");
1018 }
1019
1020 #[test]
1021 fn test_list_filter() {
1022 let out = run_output(
1023 "pipeline t(task) { let big = [1, 2, 3, 4, 5].filter({ x -> x > 3 })\nlog(big) }",
1024 );
1025 assert_eq!(out, "[harn] [4, 5]");
1026 }
1027
1028 #[test]
1029 fn test_list_reduce() {
1030 let out = run_output(
1031 "pipeline t(task) { let sum = [1, 2, 3, 4].reduce(0, { acc, x -> acc + x })\nlog(sum) }",
1032 );
1033 assert_eq!(out, "[harn] 10");
1034 }
1035
1036 #[test]
1037 fn test_dict_access() {
1038 let out = run_output(
1039 r#"pipeline t(task) { let d = {name: "test", value: 42}
1040log(d.name)
1041log(d.value) }"#,
1042 );
1043 assert_eq!(out, "[harn] test\n[harn] 42");
1044 }
1045
1046 #[test]
1047 fn test_dict_methods() {
1048 let out = run_output(
1049 r#"pipeline t(task) { let d = {a: 1, b: 2}
1050log(d.keys())
1051log(d.values())
1052log(d.has("a"))
1053log(d.has("z")) }"#,
1054 );
1055 assert_eq!(
1056 out,
1057 "[harn] [a, b]\n[harn] [1, 2]\n[harn] true\n[harn] false"
1058 );
1059 }
1060
1061 #[test]
1062 fn test_pipe_operator() {
1063 let out = run_output(
1064 "pipeline t(task) { fn double(x) { return x * 2 }\nlet r = 5 |> double\nlog(r) }",
1065 );
1066 assert_eq!(out, "[harn] 10");
1067 }
1068
1069 #[test]
1070 fn test_pipe_with_closure() {
1071 let out = run_output(
1072 r#"pipeline t(task) { let r = "hello world" |> { s -> s.split(" ") }
1073log(r) }"#,
1074 );
1075 assert_eq!(out, "[harn] [hello, world]");
1076 }
1077
1078 #[test]
1079 fn test_nil_coalescing() {
1080 let out = run_output(
1081 r#"pipeline t(task) { let a = nil ?? "fallback"
1082log(a)
1083let b = "present" ?? "fallback"
1084log(b) }"#,
1085 );
1086 assert_eq!(out, "[harn] fallback\n[harn] present");
1087 }
1088
1089 #[test]
1090 fn test_logical_operators() {
1091 let out =
1092 run_output("pipeline t(task) { log(true && false)\nlog(true || false)\nlog(!true) }");
1093 assert_eq!(out, "[harn] false\n[harn] true\n[harn] false");
1094 }
1095
1096 #[test]
1097 fn test_match() {
1098 let out = run_output(
1099 r#"pipeline t(task) { let x = "b"
1100match x { "a" -> { log("first") } "b" -> { log("second") } "c" -> { log("third") } } }"#,
1101 );
1102 assert_eq!(out, "[harn] second");
1103 }
1104
1105 #[test]
1106 fn test_subscript() {
1107 let out = run_output("pipeline t(task) { let arr = [10, 20, 30]\nlog(arr[1]) }");
1108 assert_eq!(out, "[harn] 20");
1109 }
1110
1111 #[test]
1112 fn test_string_methods() {
1113 let out = run_output(
1114 r#"pipeline t(task) { log("hello world".replace("world", "harn"))
1115log("a,b,c".split(","))
1116log(" hello ".trim())
1117log("hello".starts_with("hel"))
1118log("hello".ends_with("lo"))
1119log("hello".substring(1, 3)) }"#,
1120 );
1121 assert_eq!(
1122 out,
1123 "[harn] hello harn\n[harn] [a, b, c]\n[harn] hello\n[harn] true\n[harn] true\n[harn] el"
1124 );
1125 }
1126
1127 #[test]
1128 fn test_list_properties() {
1129 let out = run_output(
1130 "pipeline t(task) { let list = [1, 2, 3]\nlog(list.count)\nlog(list.empty)\nlog(list.first)\nlog(list.last) }",
1131 );
1132 assert_eq!(out, "[harn] 3\n[harn] false\n[harn] 1\n[harn] 3");
1133 }
1134
1135 #[test]
1136 fn test_recursive_function() {
1137 let out = run_output(
1138 "pipeline t(task) { fn fib(n) { if n <= 1 { return n } return fib(n - 1) + fib(n - 2) }\nlog(fib(10)) }",
1139 );
1140 assert_eq!(out, "[harn] 55");
1141 }
1142
1143 #[test]
1144 fn test_ternary() {
1145 let out = run_output(
1146 r#"pipeline t(task) { let x = 5
1147let r = x > 0 ? "positive" : "non-positive"
1148log(r) }"#,
1149 );
1150 assert_eq!(out, "[harn] positive");
1151 }
1152
1153 #[test]
1154 fn test_for_in_dict() {
1155 let out = run_output(
1156 "pipeline t(task) { let d = {a: 1, b: 2}\nfor entry in d { log(entry.key) } }",
1157 );
1158 assert_eq!(out, "[harn] a\n[harn] b");
1159 }
1160
1161 #[test]
1162 fn test_list_any_all() {
1163 let out = run_output(
1164 "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 })) }",
1165 );
1166 assert_eq!(out, "[harn] true\n[harn] true\n[harn] false");
1167 }
1168
1169 #[test]
1170 fn test_disassembly() {
1171 let mut lexer = Lexer::new("pipeline t(task) { log(2 + 3) }");
1172 let tokens = lexer.tokenize().unwrap();
1173 let mut parser = Parser::new(tokens);
1174 let program = parser.parse().unwrap();
1175 let chunk = Compiler::new().compile(&program).unwrap();
1176 let disasm = chunk.disassemble("test");
1177 assert!(disasm.contains("CONSTANT"));
1178 assert!(disasm.contains("ADD"));
1179 assert!(disasm.contains("CALL"));
1180 }
1181
1182 #[test]
1185 fn test_try_catch_basic() {
1186 let out = run_output(
1187 r#"pipeline t(task) { try { throw "oops" } catch(e) { log("caught: " + e) } }"#,
1188 );
1189 assert_eq!(out, "[harn] caught: oops");
1190 }
1191
1192 #[test]
1193 fn test_try_no_error() {
1194 let out = run_output(
1195 r#"pipeline t(task) {
1196var result = 0
1197try { result = 42 } catch(e) { result = 0 }
1198log(result)
1199}"#,
1200 );
1201 assert_eq!(out, "[harn] 42");
1202 }
1203
1204 #[test]
1205 fn test_throw_uncaught() {
1206 let result = run_harn_result(r#"pipeline t(task) { throw "boom" }"#);
1207 assert!(result.is_err());
1208 }
1209
1210 fn run_vm(source: &str) -> String {
1213 let rt = tokio::runtime::Builder::new_current_thread()
1214 .enable_all()
1215 .build()
1216 .unwrap();
1217 rt.block_on(async {
1218 let local = tokio::task::LocalSet::new();
1219 local
1220 .run_until(async {
1221 let mut lexer = Lexer::new(source);
1222 let tokens = lexer.tokenize().unwrap();
1223 let mut parser = Parser::new(tokens);
1224 let program = parser.parse().unwrap();
1225 let chunk = Compiler::new().compile(&program).unwrap();
1226 let mut vm = Vm::new();
1227 register_vm_stdlib(&mut vm);
1228 vm.execute(&chunk).await.unwrap();
1229 vm.output().to_string()
1230 })
1231 .await
1232 })
1233 }
1234
1235 fn run_vm_err(source: &str) -> String {
1236 let rt = tokio::runtime::Builder::new_current_thread()
1237 .enable_all()
1238 .build()
1239 .unwrap();
1240 rt.block_on(async {
1241 let local = tokio::task::LocalSet::new();
1242 local
1243 .run_until(async {
1244 let mut lexer = Lexer::new(source);
1245 let tokens = lexer.tokenize().unwrap();
1246 let mut parser = Parser::new(tokens);
1247 let program = parser.parse().unwrap();
1248 let chunk = Compiler::new().compile(&program).unwrap();
1249 let mut vm = Vm::new();
1250 register_vm_stdlib(&mut vm);
1251 match vm.execute(&chunk).await {
1252 Err(e) => format!("{}", e),
1253 Ok(_) => panic!("Expected error"),
1254 }
1255 })
1256 .await
1257 })
1258 }
1259
1260 #[test]
1261 fn test_hello_world() {
1262 let out = run_vm(r#"pipeline default(task) { log("hello") }"#);
1263 assert_eq!(out, "[harn] hello\n");
1264 }
1265
1266 #[test]
1267 fn test_arithmetic_new() {
1268 let out = run_vm("pipeline default(task) { log(2 + 3) }");
1269 assert_eq!(out, "[harn] 5\n");
1270 }
1271
1272 #[test]
1273 fn test_string_concat_new() {
1274 let out = run_vm(r#"pipeline default(task) { log("a" + "b") }"#);
1275 assert_eq!(out, "[harn] ab\n");
1276 }
1277
1278 #[test]
1279 fn test_if_else_new() {
1280 let out = run_vm("pipeline default(task) { if true { log(1) } else { log(2) } }");
1281 assert_eq!(out, "[harn] 1\n");
1282 }
1283
1284 #[test]
1285 fn test_for_loop_new() {
1286 let out = run_vm("pipeline default(task) { for i in [1, 2, 3] { log(i) } }");
1287 assert_eq!(out, "[harn] 1\n[harn] 2\n[harn] 3\n");
1288 }
1289
1290 #[test]
1291 fn test_while_loop_new() {
1292 let out = run_vm("pipeline default(task) { var i = 0\nwhile i < 3 { log(i)\ni = i + 1 } }");
1293 assert_eq!(out, "[harn] 0\n[harn] 1\n[harn] 2\n");
1294 }
1295
1296 #[test]
1297 fn test_function_call_new() {
1298 let out =
1299 run_vm("pipeline default(task) { fn add(a, b) { return a + b }\nlog(add(2, 3)) }");
1300 assert_eq!(out, "[harn] 5\n");
1301 }
1302
1303 #[test]
1304 fn test_closure_new() {
1305 let out = run_vm("pipeline default(task) { let f = { x -> x * 2 }\nlog(f(5)) }");
1306 assert_eq!(out, "[harn] 10\n");
1307 }
1308
1309 #[test]
1310 fn test_recursion() {
1311 let out = run_vm("pipeline default(task) { fn fact(n) { if n <= 1 { return 1 }\nreturn n * fact(n - 1) }\nlog(fact(5)) }");
1312 assert_eq!(out, "[harn] 120\n");
1313 }
1314
1315 #[test]
1316 fn test_try_catch_new() {
1317 let out = run_vm(r#"pipeline default(task) { try { throw "err" } catch (e) { log(e) } }"#);
1318 assert_eq!(out, "[harn] err\n");
1319 }
1320
1321 #[test]
1322 fn test_try_no_error_new() {
1323 let out = run_vm("pipeline default(task) { try { log(1) } catch (e) { log(2) } }");
1324 assert_eq!(out, "[harn] 1\n");
1325 }
1326
1327 #[test]
1328 fn test_list_map_new() {
1329 let out =
1330 run_vm("pipeline default(task) { let r = [1, 2, 3].map({ x -> x * 2 })\nlog(r) }");
1331 assert_eq!(out, "[harn] [2, 4, 6]\n");
1332 }
1333
1334 #[test]
1335 fn test_list_filter_new() {
1336 let out = run_vm(
1337 "pipeline default(task) { let r = [1, 2, 3, 4].filter({ x -> x > 2 })\nlog(r) }",
1338 );
1339 assert_eq!(out, "[harn] [3, 4]\n");
1340 }
1341
1342 #[test]
1343 fn test_dict_access_new() {
1344 let out = run_vm("pipeline default(task) { let d = {name: \"Alice\"}\nlog(d.name) }");
1345 assert_eq!(out, "[harn] Alice\n");
1346 }
1347
1348 #[test]
1349 fn test_string_interpolation() {
1350 let out = run_vm("pipeline default(task) { let x = 42\nlog(\"val=${x}\") }");
1351 assert_eq!(out, "[harn] val=42\n");
1352 }
1353
1354 #[test]
1355 fn test_match_new() {
1356 let out = run_vm(
1357 "pipeline default(task) { let x = \"b\"\nmatch x { \"a\" -> { log(1) } \"b\" -> { log(2) } } }",
1358 );
1359 assert_eq!(out, "[harn] 2\n");
1360 }
1361
1362 #[test]
1363 fn test_json_roundtrip() {
1364 let out = run_vm("pipeline default(task) { let s = json_stringify({a: 1})\nlog(s) }");
1365 assert!(out.contains("\"a\""));
1366 assert!(out.contains("1"));
1367 }
1368
1369 #[test]
1370 fn test_type_of() {
1371 let out = run_vm("pipeline default(task) { log(type_of(42))\nlog(type_of(\"hi\")) }");
1372 assert_eq!(out, "[harn] int\n[harn] string\n");
1373 }
1374
1375 #[test]
1376 fn test_stack_overflow() {
1377 let err = run_vm_err("pipeline default(task) { fn f() { f() }\nf() }");
1378 assert!(
1379 err.contains("stack") || err.contains("overflow") || err.contains("recursion"),
1380 "Expected stack overflow error, got: {}",
1381 err
1382 );
1383 }
1384
1385 #[test]
1386 fn test_division_by_zero() {
1387 let err = run_vm_err("pipeline default(task) { log(1 / 0) }");
1388 assert!(
1389 err.contains("Division by zero") || err.contains("division"),
1390 "Expected division by zero error, got: {}",
1391 err
1392 );
1393 }
1394
1395 #[test]
1396 fn test_try_catch_nested() {
1397 let out = run_output(
1398 r#"pipeline t(task) {
1399try {
1400 try {
1401 throw "inner"
1402 } catch(e) {
1403 log("inner caught: " + e)
1404 throw "outer"
1405 }
1406} catch(e) {
1407 log("outer caught: " + e)
1408}
1409}"#,
1410 );
1411 assert_eq!(
1412 out,
1413 "[harn] inner caught: inner\n[harn] outer caught: outer"
1414 );
1415 }
1416
1417 #[test]
1420 fn test_parallel_basic() {
1421 let out = run_output(
1422 "pipeline t(task) { let results = parallel(3) { i -> i * 10 }\nlog(results) }",
1423 );
1424 assert_eq!(out, "[harn] [0, 10, 20]");
1425 }
1426
1427 #[test]
1428 fn test_parallel_no_variable() {
1429 let out = run_output("pipeline t(task) { let results = parallel(3) { 42 }\nlog(results) }");
1430 assert_eq!(out, "[harn] [42, 42, 42]");
1431 }
1432
1433 #[test]
1434 fn test_parallel_map_basic() {
1435 let out = run_output(
1436 "pipeline t(task) { let results = parallel_map([1, 2, 3]) { x -> x * x }\nlog(results) }",
1437 );
1438 assert_eq!(out, "[harn] [1, 4, 9]");
1439 }
1440
1441 #[test]
1442 fn test_spawn_await() {
1443 let out = run_output(
1444 r#"pipeline t(task) {
1445let handle = spawn { log("spawned") }
1446let result = await(handle)
1447log("done")
1448}"#,
1449 );
1450 assert_eq!(out, "[harn] spawned\n[harn] done");
1451 }
1452
1453 #[test]
1454 fn test_spawn_cancel() {
1455 let out = run_output(
1456 r#"pipeline t(task) {
1457let handle = spawn { log("should be cancelled") }
1458cancel(handle)
1459log("cancelled")
1460}"#,
1461 );
1462 assert_eq!(out, "[harn] cancelled");
1463 }
1464
1465 #[test]
1466 fn test_spawn_returns_value() {
1467 let out = run_output("pipeline t(task) { let h = spawn { 42 }\nlet r = await(h)\nlog(r) }");
1468 assert_eq!(out, "[harn] 42");
1469 }
1470
1471 #[test]
1474 fn test_deadline_success() {
1475 let out = run_output(
1476 r#"pipeline t(task) {
1477let result = deadline 5s { log("within deadline")
147842 }
1479log(result)
1480}"#,
1481 );
1482 assert_eq!(out, "[harn] within deadline\n[harn] 42");
1483 }
1484
1485 #[test]
1486 fn test_deadline_exceeded() {
1487 let result = run_harn_result(
1488 r#"pipeline t(task) {
1489deadline 1ms {
1490 var i = 0
1491 while i < 1000000 { i = i + 1 }
1492}
1493}"#,
1494 );
1495 assert!(result.is_err());
1496 }
1497
1498 #[test]
1499 fn test_deadline_caught_by_try() {
1500 let out = run_output(
1501 r#"pipeline t(task) {
1502try {
1503 deadline 1ms {
1504 var i = 0
1505 while i < 1000000 { i = i + 1 }
1506 }
1507} catch(e) {
1508 log("caught")
1509}
1510}"#,
1511 );
1512 assert_eq!(out, "[harn] caught");
1513 }
1514
1515 fn run_harn_with_denied(
1517 source: &str,
1518 denied: HashSet<String>,
1519 ) -> Result<(String, VmValue), VmError> {
1520 let rt = tokio::runtime::Builder::new_current_thread()
1521 .enable_all()
1522 .build()
1523 .unwrap();
1524 rt.block_on(async {
1525 let local = tokio::task::LocalSet::new();
1526 local
1527 .run_until(async {
1528 let mut lexer = Lexer::new(source);
1529 let tokens = lexer.tokenize().unwrap();
1530 let mut parser = Parser::new(tokens);
1531 let program = parser.parse().unwrap();
1532 let chunk = Compiler::new().compile(&program).unwrap();
1533
1534 let mut vm = Vm::new();
1535 register_vm_stdlib(&mut vm);
1536 vm.set_denied_builtins(denied);
1537 let result = vm.execute(&chunk).await?;
1538 Ok((vm.output().to_string(), result))
1539 })
1540 .await
1541 })
1542 }
1543
1544 #[test]
1545 fn test_sandbox_deny_builtin() {
1546 let denied: HashSet<String> = ["push".to_string()].into_iter().collect();
1547 let result = run_harn_with_denied(
1548 r#"pipeline t(task) {
1549let xs = [1, 2]
1550push(xs, 3)
1551}"#,
1552 denied,
1553 );
1554 let err = result.unwrap_err();
1555 let msg = format!("{err}");
1556 assert!(
1557 msg.contains("Permission denied"),
1558 "expected permission denied, got: {msg}"
1559 );
1560 assert!(
1561 msg.contains("push"),
1562 "expected builtin name in error, got: {msg}"
1563 );
1564 }
1565
1566 #[test]
1567 fn test_sandbox_allowed_builtin_works() {
1568 let denied: HashSet<String> = ["push".to_string()].into_iter().collect();
1570 let result = run_harn_with_denied(
1571 r#"pipeline t(task) { log("hello") }"#,
1572 denied,
1573 );
1574 let (output, _) = result.unwrap();
1575 assert_eq!(output.trim(), "[harn] hello");
1576 }
1577
1578 #[test]
1579 fn test_sandbox_empty_denied_set() {
1580 let result = run_harn_with_denied(
1582 r#"pipeline t(task) { log("ok") }"#,
1583 HashSet::new(),
1584 );
1585 let (output, _) = result.unwrap();
1586 assert_eq!(output.trim(), "[harn] ok");
1587 }
1588}