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