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