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