1#[cfg(test)]
20use crate::CallError;
21use crate::UpcallError;
22use crate::bytecode::{ExitCode, Register};
23use crate::callable::{Callable, Scope};
24use crate::compiler::SymbolKey;
25use crate::image::{GlobalVarInfo, Image};
26use crate::mem::{ConstantDatum, DatumPtr, Heap, HeapDatum};
27use crate::num::U24;
28use crate::reader::LineCol;
29use std::collections::HashMap;
30use std::rc::Rc;
31
32mod context;
33use context::{Context, ErrorHandler, InternalStopReason};
34
35const DEFAULT_MAX_CALL_STACK: usize = 4096;
37
38#[derive(Clone, Copy, Debug, Eq, PartialEq)]
40pub struct Limits {
41 pub max_call_stack: usize,
43
44 pub max_heap_entries: U24,
46}
47
48impl Default for Limits {
49 fn default() -> Self {
50 Self { max_call_stack: DEFAULT_MAX_CALL_STACK, max_heap_entries: U24::MAX }
51 }
52}
53
54#[derive(Debug, thiserror::Error)]
59pub enum GetGlobalError {
60 #[error("'{0}' is an array variable; use get_global_array to access it")]
62 IsArray(String),
63
64 #[error("'{0}' is a scalar variable; use get_global to access it")]
66 IsScalar(String),
67
68 #[error("{0}")]
70 SubscriptOutOfBounds(String),
71}
72
73pub type GetGlobalResult<T> = Result<T, GetGlobalError>;
75
76pub struct UpcallHandler<'a> {
78 vm: &'a mut Vm,
79 image: &'a Image,
80}
81
82impl<'a> UpcallHandler<'a> {
83 pub async fn invoke(self) -> Result<(), UpcallError> {
85 let vm = self.vm;
86 let image = self.image;
87 let (index, first_reg, upcall_pc) = vm
88 .pending_upcall
89 .take()
90 .expect("This is only reachable when the VM has a pending upcall");
91 let (upcall, scope) = vm.prepare_upcall(image, index, first_reg, upcall_pc);
92 let result = upcall.async_exec(scope).await;
93 match vm.handle_upcall_result(image, upcall_pc, result) {
94 Ok(()) => Ok(()),
95 Err(e) => {
96 vm.park_at_eof(image);
97 Err(e)
98 }
99 }
100 }
101}
102
103pub enum StopReason<'a> {
105 End(ExitCode),
107
108 Eof,
110
111 Exception(LineCol, String),
113
114 UpcallAsync(UpcallHandler<'a>),
116
117 Yield,
119}
120
121pub struct Vm {
123 upcalls_by_name: HashMap<SymbolKey, Rc<dyn Callable>>,
125
126 upcall_names: Vec<SymbolKey>,
128
129 upcalls: Vec<Rc<dyn Callable>>,
131
132 heap: Heap,
134
135 context: Context,
137
138 last_error: Option<(LineCol, String)>,
140
141 pending_upcall: Option<(u16, Register, usize)>,
146}
147
148impl Vm {
149 fn prepare_upcall<'a>(
151 &'a mut self,
152 image: &'a Image,
153 index: u16,
154 first_reg: Register,
155 upcall_pc: usize,
156 ) -> (Rc<dyn Callable>, Scope<'a>) {
157 let upcall = self.upcalls[usize::from(index)].clone();
158 let is_function = upcall.metadata().return_type().is_some();
159 let scope = self.upcall_scope(image, first_reg, is_function, upcall_pc);
160 (upcall, scope)
161 }
162
163 fn handle_upcall_result(
168 &mut self,
169 image: &Image,
170 upcall_pc: usize,
171 result: crate::callable::CallResult<()>,
172 ) -> Result<(), UpcallError> {
173 match result {
174 Ok(()) => Ok(()),
175 Err(e) => {
176 let default_pos = image.debug_info.instrs[upcall_pc].linecol;
177 let upcall_error = e.to_upcall_error(default_pos);
178 let (pos, message) = upcall_error.parts();
179 if self.handle_exception(image, upcall_pc, pos, message) {
180 Ok(())
181 } else {
182 Err(upcall_error)
183 }
184 }
185 }
186 }
187
188 fn get_scalar_var(
190 &self,
191 image: &Image,
192 key: &SymbolKey,
193 vars: &HashMap<SymbolKey, GlobalVarInfo>,
194 read_raw: fn(&Context, u8) -> u64,
195 ) -> GetGlobalResult<Option<ConstantDatum>> {
196 let Some(info) = vars.get(key) else {
197 return Ok(None);
198 };
199 if info.ndims != 0 {
200 return Err(GetGlobalError::IsArray(key.to_string()));
201 }
202 let raw = read_raw(&self.context, info.reg);
203 Ok(Some(ConstantDatum::from_raw(raw, info.subtype, &image.constants, &self.heap)))
204 }
205
206 fn get_array_var(
208 &self,
209 image: &Image,
210 key: &SymbolKey,
211 vars: &HashMap<SymbolKey, GlobalVarInfo>,
212 subscripts: &[i32],
213 read_raw: fn(&Context, u8) -> u64,
214 ) -> GetGlobalResult<Option<ConstantDatum>> {
215 let Some(info) = vars.get(key) else {
216 return Ok(None);
217 };
218 if info.ndims == 0 {
219 return Err(GetGlobalError::IsScalar(key.to_string()));
220 }
221 let raw = read_raw(&self.context, info.reg);
222 let ptr = DatumPtr::from(raw);
223 let heap_idx = ptr.heap_index();
224 let HeapDatum::Array(a) = self.heap.get(heap_idx) else {
225 panic!("Array variable does not point to an array on the heap");
226 };
227 let flat_idx = a.flat_index(subscripts).map_err(GetGlobalError::SubscriptOutOfBounds)?;
228 let v = a.values[flat_idx];
229 Ok(Some(ConstantDatum::from_raw(v, info.subtype, &image.constants, &self.heap)))
230 }
231
232 pub fn new(upcalls_by_name: HashMap<SymbolKey, Rc<dyn Callable>>) -> Self {
234 Self::new_with_limits(upcalls_by_name, Limits::default())
235 }
236
237 pub fn new_with_limits(
239 upcalls_by_name: HashMap<SymbolKey, Rc<dyn Callable>>,
240 limits: Limits,
241 ) -> Self {
242 Self {
243 upcalls_by_name,
244 upcall_names: vec![],
245 upcalls: vec![],
246 heap: Heap::new(limits.max_heap_entries),
247 context: Context::new(limits.max_call_stack),
248 last_error: None,
249 pending_upcall: None,
250 }
251 }
252
253 pub fn reset(&mut self) {
255 self.upcall_names.clear();
256 self.upcalls.clear();
257 self.heap.clear();
258 self.context.clear_runtime_state();
259 self.last_error = None;
260 self.pending_upcall = None;
261 }
262
263 pub fn clear(&mut self) {
269 self.heap.clear();
270 self.context.clear_runtime_state();
271 self.last_error = None;
272 self.pending_upcall = None;
273 }
274
275 pub fn clear_error_handler(&mut self) {
277 self.context.clear_error_handler();
278 }
279
280 fn sync_upcalls(&mut self, image: &Image) {
282 debug_assert!(
283 image.upcalls.starts_with(self.upcall_names.as_slice()),
284 "Vm::reset() is required before executing a different image",
285 );
286
287 for key in &image.upcalls[self.upcalls.len()..] {
288 self.upcalls.push(
289 self.upcalls_by_name
290 .get(key)
291 .expect("All upcalls exposed during compilation must be present at runtime")
292 .clone(),
293 );
294 self.upcall_names.push(key.clone());
295 }
296 }
297
298 fn park_at_eof(&mut self, image: &Image) {
300 debug_assert!(!image.code.is_empty());
301 self.context.set_pc(image.code.len() - 1);
302 }
303
304 fn upcall_scope<'a>(
311 &'a mut self,
312 image: &'a Image,
313 reg: Register,
314 is_function: bool,
315 upcall_pc: usize,
316 ) -> Scope<'a> {
317 let arg_linecols = image
318 .debug_info
319 .instrs
320 .get(upcall_pc)
321 .map(|m| m.arg_linecols.as_slice())
322 .unwrap_or(&[]);
323 self.context.upcall_scope(
324 reg,
325 is_function,
326 image.constants.as_slice(),
327 &mut self.heap,
328 arg_linecols,
329 &self.last_error,
330 image.data.as_slice(),
331 )
332 }
333
334 fn handle_exception(
336 &mut self,
337 image: &Image,
338 pc: usize,
339 pos: LineCol,
340 message: String,
341 ) -> bool {
342 self.last_error = Some((pos, message));
343
344 match self.context.error_handler() {
345 ErrorHandler::None => false,
346 ErrorHandler::Jump { active: true, .. } => false,
347 ErrorHandler::Jump { active: false, addr } => {
348 self.context.set_error_handler_active();
349 self.context.set_pc(addr);
350 true
351 }
352 ErrorHandler::ResumeNext => {
353 let mut next_pc = image.code.len();
354 for (idx, meta) in image.debug_info.instrs.iter().enumerate().skip(pc + 1) {
355 if meta.is_stmt_start {
356 next_pc = idx;
357 break;
358 }
359 }
360 self.context.set_pc(next_pc);
361 true
362 }
363 }
364 }
365
366 pub fn get_global(
372 &self,
373 image: &Image,
374 key: &SymbolKey,
375 ) -> GetGlobalResult<Option<ConstantDatum>> {
376 self.get_scalar_var(image, key, &image.debug_info.global_vars, Context::get_global_reg_raw)
377 }
378
379 pub fn get_global_array(
386 &self,
387 image: &Image,
388 key: &SymbolKey,
389 subscripts: &[i32],
390 ) -> GetGlobalResult<Option<ConstantDatum>> {
391 self.get_array_var(
392 image,
393 key,
394 &image.debug_info.global_vars,
395 subscripts,
396 Context::get_global_reg_raw,
397 )
398 }
399
400 pub fn get_program(
406 &self,
407 image: &Image,
408 key: &SymbolKey,
409 ) -> GetGlobalResult<Option<ConstantDatum>> {
410 self.get_scalar_var(
411 image,
412 key,
413 &image.debug_info.program_vars,
414 Context::get_program_reg_raw,
415 )
416 }
417
418 pub fn get_program_array(
425 &self,
426 image: &Image,
427 key: &SymbolKey,
428 subscripts: &[i32],
429 ) -> GetGlobalResult<Option<ConstantDatum>> {
430 self.get_array_var(
431 image,
432 key,
433 &image.debug_info.program_vars,
434 subscripts,
435 Context::get_program_reg_raw,
436 )
437 }
438
439 pub fn exec<'a>(&'a mut self, image: &'a Image) -> StopReason<'a> {
444 self.sync_upcalls(image);
445
446 loop {
447 if self.pending_upcall.is_some() {
448 return StopReason::UpcallAsync(UpcallHandler { vm: self, image });
449 }
450
451 match self.context.exec(image, &mut self.heap) {
452 InternalStopReason::End(code) => {
453 self.park_at_eof(image);
454 return StopReason::End(code);
455 }
456 InternalStopReason::Eof => return StopReason::Eof,
457 InternalStopReason::Exception(pc, e) => {
458 let pos = image.debug_info.instrs[pc].linecol;
459 if !self.handle_exception(image, pc, pos, e.clone()) {
460 self.park_at_eof(image);
461 return StopReason::Exception(pos, e);
462 }
463 }
464 InternalStopReason::Upcall(index, first_reg, upcall_pc) => {
465 let (upcall, scope) = self.prepare_upcall(image, index, first_reg, upcall_pc);
466 let result = upcall.exec(scope);
467 if let Err(upcall_error) = self.handle_upcall_result(image, upcall_pc, result) {
468 let (pos, message) = upcall_error.parts();
469 self.park_at_eof(image);
470 return StopReason::Exception(pos, message);
471 }
472 }
473
474 InternalStopReason::UpcallAsync(index, first_reg, upcall_pc) => {
475 self.pending_upcall = Some((index, first_reg, upcall_pc));
476 return StopReason::UpcallAsync(UpcallHandler { vm: self, image });
477 }
478
479 InternalStopReason::Yield => return StopReason::Yield,
480 }
481 }
482 }
483
484 pub fn interrupt(&mut self, image: &Image) {
489 self.pending_upcall = None;
490 self.park_at_eof(image);
491 }
492}
493
494#[cfg(test)]
495mod tests {
496 use super::*;
497 use crate::Compiler;
498 use crate::ast::{ArgSep, ExprType};
499 use crate::callable::{
500 ArgSepSyntax, CallResult, CallableMetadata, CallableMetadataBuilder, RequiredValueSyntax,
501 SingularArgSyntax,
502 };
503 use crate::compiler::SymbolKey;
504 use crate::image::Image;
505 use crate::reader::LineCol;
506 use crate::testutils::OutCommand;
507 use async_trait::async_trait;
508 use futures_lite::future::yield_now;
509 use std::borrow::Cow;
510 use std::cell::RefCell;
511 use std::collections::HashMap;
512 use std::io;
513 use std::rc::Rc;
514
515 struct PosCapture {
520 metadata: Rc<CallableMetadata>,
521 nargs: u8,
522 positions: Rc<RefCell<Vec<LineCol>>>,
523 }
524
525 impl PosCapture {
526 fn new(nargs: u8, positions: Rc<RefCell<Vec<LineCol>>>) -> Rc<Self> {
529 let singular: Vec<SingularArgSyntax> = (0..nargs)
530 .map(|i| {
531 let sep = if i == nargs - 1 {
532 ArgSepSyntax::End
533 } else {
534 ArgSepSyntax::Exactly(ArgSep::Long)
535 };
536 SingularArgSyntax::RequiredValue(
537 RequiredValueSyntax {
538 name: Cow::Borrowed("arg"),
539 vtype: ExprType::Integer,
540 },
541 sep,
542 )
543 })
544 .collect();
545 let md = CallableMetadataBuilder::new("POS_CAPTURE")
546 .with_dynamic_syntax(vec![(singular, None)])
547 .test_build();
548 Rc::from(Self { metadata: md, nargs, positions })
549 }
550 }
551
552 impl Callable for PosCapture {
553 fn metadata(&self) -> Rc<CallableMetadata> {
554 self.metadata.clone()
555 }
556
557 fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
558 let mut positions = self.positions.borrow_mut();
559 for i in 0..self.nargs {
560 positions.push(scope.get_pos(i));
561 }
562 Ok(())
563 }
564 }
565
566 struct ReturnFortyTwoFunction {
567 metadata: Rc<CallableMetadata>,
568 }
569
570 impl ReturnFortyTwoFunction {
571 fn new() -> Rc<Self> {
572 let md = CallableMetadataBuilder::new("RET42")
573 .with_return_type(ExprType::Integer)
574 .with_syntax(&[(&[], None)])
575 .test_build();
576 Rc::from(Self { metadata: md })
577 }
578 }
579
580 impl Callable for ReturnFortyTwoFunction {
581 fn metadata(&self) -> Rc<CallableMetadata> {
582 self.metadata.clone()
583 }
584
585 fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
586 scope.return_integer(42)
587 }
588 }
589
590 struct IoErrorCommand {
591 metadata: Rc<CallableMetadata>,
592 }
593
594 impl IoErrorCommand {
595 fn new() -> Rc<Self> {
596 let md = CallableMetadataBuilder::new("IOFAIL")
597 .with_dynamic_syntax(vec![(vec![], None)])
598 .test_build();
599 Rc::from(Self { metadata: md })
600 }
601 }
602
603 struct AsyncIncrementFunction {
604 metadata: Rc<CallableMetadata>,
605 }
606
607 impl AsyncIncrementFunction {
608 fn new() -> Rc<Self> {
609 let md = CallableMetadataBuilder::new("ASYNC_INCREMENT")
610 .with_return_type(ExprType::Integer)
611 .with_async(true)
612 .with_syntax(&[(
613 &[SingularArgSyntax::RequiredValue(
614 RequiredValueSyntax {
615 name: Cow::Borrowed("value"),
616 vtype: ExprType::Integer,
617 },
618 ArgSepSyntax::End,
619 )],
620 None,
621 )])
622 .test_build();
623 Rc::from(Self { metadata: md })
624 }
625 }
626
627 #[async_trait(?Send)]
628 impl Callable for AsyncIncrementFunction {
629 fn metadata(&self) -> Rc<CallableMetadata> {
630 self.metadata.clone()
631 }
632
633 async fn async_exec(&self, scope: Scope<'_>) -> CallResult<()> {
634 let value = scope.get_integer(0) + 1;
635 yield_now().await;
636 scope.return_integer(value)
637 }
638 }
639
640 struct AsyncIoErrorCommand {
641 metadata: Rc<CallableMetadata>,
642 }
643
644 impl AsyncIoErrorCommand {
645 fn new() -> Rc<Self> {
646 let md = CallableMetadataBuilder::new("ASYNC_IOFAIL")
647 .with_async(true)
648 .with_dynamic_syntax(vec![(vec![], None)])
649 .test_build();
650 Rc::from(Self { metadata: md })
651 }
652 }
653
654 #[async_trait(?Send)]
655 impl Callable for AsyncIoErrorCommand {
656 fn metadata(&self) -> Rc<CallableMetadata> {
657 self.metadata.clone()
658 }
659
660 async fn async_exec(&self, _scope: Scope<'_>) -> CallResult<()> {
661 yield_now().await;
662 Err(CallError::from(io::Error::other("mock async I/O error")))
663 }
664 }
665
666 #[async_trait(?Send)]
667 impl Callable for IoErrorCommand {
668 fn metadata(&self) -> Rc<CallableMetadata> {
669 self.metadata.clone()
670 }
671
672 fn exec(&self, _scope: Scope<'_>) -> CallResult<()> {
673 Err(CallError::from(io::Error::other("mock I/O error")))
674 }
675 }
676
677 async fn run_to_end(vm: &mut Vm, image: &Image) {
679 loop {
680 match vm.exec(image) {
681 StopReason::End(_) => break,
682 StopReason::Eof => break,
683 StopReason::Exception(_, msg) => panic!("Unexpected exception: {}", msg),
684 StopReason::UpcallAsync(handler) => handler.invoke().await.unwrap(),
685 StopReason::Yield => (),
686 }
687 }
688 }
689
690 #[test]
691 fn test_exec_without_load_is_eof() {
692 let mut vm = Vm::new(HashMap::default());
693 let image = Image::default();
694 match vm.exec(&image) {
695 StopReason::Eof => (),
696 _ => panic!("Unexpected stop reason"),
697 }
698 }
699
700 #[test]
701 fn test_exec_empty_image_is_eof() {
702 let mut vm = Vm::new(HashMap::default());
703 let image = Image::default();
704 match vm.exec(&image) {
705 StopReason::Eof => (),
706 _ => panic!("Unexpected stop reason"),
707 }
708 }
709
710 #[test]
711 fn test_exec_empty_compilation_is_eof() {
712 let mut vm = Vm::new(HashMap::default());
713 let compiler = Compiler::new(&HashMap::default(), &[]).unwrap();
714 let image = compiler.compile(&mut b"".as_slice()).unwrap();
715 match vm.exec(&image) {
716 StopReason::Eof => (),
717 _ => panic!("Unexpected stop reason"),
718 }
719 }
720
721 #[tokio::test]
722 async fn test_exec_upcall_flow() {
723 let data = Rc::from(RefCell::from(vec![]));
724 let mut upcalls_by_name: HashMap<SymbolKey, Rc<dyn Callable>> = HashMap::new();
725 upcalls_by_name.insert(SymbolKey::from("OUT"), OutCommand::new(data.clone()));
726
727 let compiler = Compiler::new(&upcalls_by_name, &[]).unwrap();
728 let image = compiler.compile(&mut b"OUT 30: OUT 20".as_slice()).unwrap();
729
730 let mut vm = Vm::new(upcalls_by_name);
731
732 match vm.exec(&image) {
733 StopReason::Eof => (),
734 _ => panic!("Execution should stop at EOF"),
735 }
736 assert_eq!(["30", "20"], *data.borrow().as_slice());
737 }
738
739 #[tokio::test]
740 async fn test_exec_async_upcall_flow() {
741 let data = Rc::from(RefCell::from(vec![]));
742 let mut upcalls_by_name: HashMap<SymbolKey, Rc<dyn Callable>> = HashMap::new();
743 upcalls_by_name.insert(SymbolKey::from("ASYNC_INCREMENT"), AsyncIncrementFunction::new());
744 upcalls_by_name.insert(SymbolKey::from("OUT"), OutCommand::new(data.clone()));
745
746 let compiler = Compiler::new(&upcalls_by_name, &[]).unwrap();
747 let image = compiler.compile(&mut b"OUT ASYNC_INCREMENT(123): OUT 5".as_slice()).unwrap();
748 let mut vm = Vm::new(upcalls_by_name);
749
750 match vm.exec(&image) {
751 StopReason::UpcallAsync(handler) => handler.invoke().await.unwrap(),
752 _ => panic!("Execution should stop at ASYNC_INCREMENT upcall"),
753 }
754
755 assert!(data.borrow().is_empty());
756
757 match vm.exec(&image) {
758 StopReason::Eof => (),
759 _ => panic!("Execution should stop at EOF"),
760 }
761
762 assert_eq!(["124", "5"], *data.borrow().as_slice());
763 }
764
765 #[tokio::test]
766 async fn test_exec_async_upcall_error_can_resume_after_append() {
767 let data = Rc::from(RefCell::from(vec![]));
768 let mut upcalls_by_name: HashMap<SymbolKey, Rc<dyn Callable>> = HashMap::new();
769 upcalls_by_name.insert(SymbolKey::from("ASYNC_IOFAIL"), AsyncIoErrorCommand::new());
770 upcalls_by_name.insert(SymbolKey::from("OUT"), OutCommand::new(data.clone()));
771
772 let mut compiler = Compiler::new(&upcalls_by_name, &[]).unwrap();
773 let mut image = Image::default();
774 compiler.compile_more(&mut image, &mut b"ASYNC_IOFAIL".as_slice()).unwrap();
775
776 let mut vm = Vm::new(upcalls_by_name);
777 match vm.exec(&image) {
778 StopReason::UpcallAsync(handler) => {
779 let error = handler.invoke().await.unwrap_err();
780 let (pos, message) = error.parts();
781 assert_eq!(LineCol { line: 1, col: 1 }, pos);
782 assert_eq!("mock async I/O error", message);
783 }
784 _ => panic!("Execution should stop at ASYNC_IOFAIL upcall"),
785 }
786
787 match vm.exec(&image) {
788 StopReason::Eof => (),
789 _ => panic!("Execution should park at EOF after an ASYNC_IOFAIL exception"),
790 }
791
792 compiler.compile_more(&mut image, &mut b"OUT 2".as_slice()).unwrap();
793 match vm.exec(&image) {
794 StopReason::Eof => (),
795 _ => panic!("Execution should resume at newly appended code"),
796 }
797 assert_eq!(["2"], *data.borrow().as_slice());
798
799 match vm.exec(&image) {
800 StopReason::Eof => (),
801 _ => panic!("Execution should stop at EOF after appended code"),
802 }
803 }
804
805 #[tokio::test]
806 async fn test_interrupt_cancels_pending_async_upcall() {
807 let mut upcalls_by_name: HashMap<SymbolKey, Rc<dyn Callable>> = HashMap::new();
808 upcalls_by_name.insert(SymbolKey::from("ASYNC_INCREMENT"), AsyncIncrementFunction::new());
809
810 let compiler = Compiler::new(&upcalls_by_name, &[]).unwrap();
811 let image = compiler.compile(&mut b"x = ASYNC_INCREMENT(123)".as_slice()).unwrap();
812 let mut vm = Vm::new(upcalls_by_name);
813
814 match vm.exec(&image) {
815 StopReason::UpcallAsync(_) => (),
816 _ => panic!("Execution should stop at ASYNC_INCREMENT upcall"),
817 }
818
819 vm.interrupt(&image);
820 match vm.exec(&image) {
821 StopReason::Eof => (),
822 _ => panic!("Execution should stop at EOF after interrupting a pending upcall"),
823 }
824 }
825
826 #[tokio::test]
827 async fn test_interrupt_after_pending_async_upcall_can_resume_after_append() {
828 let data = Rc::from(RefCell::from(vec![]));
829 let mut upcalls_by_name: HashMap<SymbolKey, Rc<dyn Callable>> = HashMap::new();
830 upcalls_by_name.insert(SymbolKey::from("ASYNC_INCREMENT"), AsyncIncrementFunction::new());
831 upcalls_by_name.insert(SymbolKey::from("OUT"), OutCommand::new(data.clone()));
832
833 let mut compiler = Compiler::new(&upcalls_by_name, &[]).unwrap();
834 let mut image = Image::default();
835 compiler.compile_more(&mut image, &mut b"x = ASYNC_INCREMENT(123)".as_slice()).unwrap();
836
837 let mut vm = Vm::new(upcalls_by_name);
838 match vm.exec(&image) {
839 StopReason::UpcallAsync(_) => (),
840 _ => panic!("Execution should stop at ASYNC_INCREMENT upcall"),
841 }
842
843 vm.interrupt(&image);
844 match vm.exec(&image) {
845 StopReason::Eof => (),
846 _ => panic!("Execution should stop at EOF after interrupting a pending upcall"),
847 }
848
849 compiler.compile_more(&mut image, &mut b"OUT 2".as_slice()).unwrap();
850 match vm.exec(&image) {
851 StopReason::Eof => (),
852 _ => panic!("Execution should resume at newly appended code"),
853 }
854 assert_eq!(["2"], *data.borrow().as_slice());
855
856 match vm.exec(&image) {
857 StopReason::Eof => (),
858 _ => panic!("Execution should stop at EOF after appended code"),
859 }
860 }
861
862 #[tokio::test]
863 async fn test_exec_end_code_default() {
864 let mut vm = Vm::new(HashMap::default());
865 let compiler = Compiler::new(&HashMap::default(), &[]).unwrap();
866 let image = compiler.compile(&mut b"END".as_slice()).unwrap();
867 match vm.exec(&image) {
868 StopReason::End(code) if code.is_success() => (),
869 _ => panic!("Unexpected stop reason"),
870 }
871 }
872
873 #[tokio::test]
874 async fn test_exec_end_code_explicit() {
875 let mut vm = Vm::new(HashMap::default());
876 let compiler = Compiler::new(&HashMap::default(), &[]).unwrap();
877 let image = compiler.compile(&mut b"END 3".as_slice()).unwrap();
878 match vm.exec(&image) {
879 StopReason::End(code) if code.to_i32() == 3 => (),
880 _ => panic!("Unexpected stop reason"),
881 }
882 }
883
884 #[tokio::test]
885 async fn test_exec_end_can_resume_after_append() {
886 let data = Rc::from(RefCell::from(vec![]));
887 let mut upcalls_by_name: HashMap<SymbolKey, Rc<dyn Callable>> = HashMap::new();
888 upcalls_by_name.insert(SymbolKey::from("OUT"), OutCommand::new(data.clone()));
889
890 let mut compiler = Compiler::new(&upcalls_by_name, &[]).unwrap();
891 let mut image = Image::default();
892 compiler.compile_more(&mut image, &mut b"END 3".as_slice()).unwrap();
893
894 let mut vm = Vm::new(upcalls_by_name);
895 match vm.exec(&image) {
896 StopReason::End(code) if code.to_i32() == 3 => (),
897 _ => panic!("Unexpected stop reason"),
898 }
899 match vm.exec(&image) {
900 StopReason::Eof => (),
901 _ => panic!("Execution should park at EOF after END"),
902 }
903
904 compiler.compile_more(&mut image, &mut b"OUT 2".as_slice()).unwrap();
905 match vm.exec(&image) {
906 StopReason::Eof => (),
907 _ => panic!("Execution should resume at newly appended code"),
908 }
909 assert_eq!(["2"], *data.borrow().as_slice());
910
911 match vm.exec(&image) {
912 StopReason::Eof => (),
913 _ => panic!("Execution should stop at EOF after appended code"),
914 }
915 }
916
917 #[tokio::test]
918 async fn test_exec_exception_can_resume_after_append() {
919 let data = Rc::from(RefCell::from(vec![]));
920 let mut upcalls_by_name: HashMap<SymbolKey, Rc<dyn Callable>> = HashMap::new();
921 upcalls_by_name.insert(SymbolKey::from("OUT"), OutCommand::new(data.clone()));
922
923 let mut compiler = Compiler::new(&upcalls_by_name, &[]).unwrap();
924 let mut image = Image::default();
925 compiler.compile_more(&mut image, &mut b"a = 1 / 0".as_slice()).unwrap();
926
927 let mut vm = Vm::new(upcalls_by_name);
928 match vm.exec(&image) {
929 StopReason::Exception(_, msg) if msg == "Division by zero" => (),
930 _ => panic!("Unexpected stop reason"),
931 }
932 match vm.exec(&image) {
933 StopReason::Eof => (),
934 _ => panic!("Execution should park at EOF after an exception"),
935 }
936
937 compiler.compile_more(&mut image, &mut b"OUT 2".as_slice()).unwrap();
938 match vm.exec(&image) {
939 StopReason::Eof => (),
940 _ => panic!("Execution should resume at newly appended code"),
941 }
942 assert_eq!(["2"], *data.borrow().as_slice());
943
944 match vm.exec(&image) {
945 StopReason::Eof => (),
946 _ => panic!("Execution should stop at EOF after appended code"),
947 }
948 }
949
950 #[tokio::test]
951 async fn test_exec_upcall_can_return_with_scope_helper() {
952 let mut upcalls_by_name: HashMap<SymbolKey, Rc<dyn Callable>> = HashMap::new();
953 upcalls_by_name.insert(SymbolKey::from("RET42"), ReturnFortyTwoFunction::new());
954
955 let compiler = Compiler::new(&upcalls_by_name, &[]).unwrap();
956 let image = compiler.compile(&mut b"x = RET42".as_slice()).unwrap();
957 let mut vm = Vm::new(upcalls_by_name);
958 run_to_end(&mut vm, &image).await;
959
960 assert_eq!(
961 Some(ConstantDatum::Integer(42)),
962 vm.get_program(&image, &SymbolKey::from("x")).unwrap()
963 );
964 }
965
966 #[tokio::test]
967 async fn test_exec_upcall_io_error_is_reported() {
968 let mut upcalls_by_name: HashMap<SymbolKey, Rc<dyn Callable>> = HashMap::new();
969 upcalls_by_name.insert(SymbolKey::from("IOFAIL"), IoErrorCommand::new());
970
971 let compiler = Compiler::new(&upcalls_by_name, &[]).unwrap();
972 let image = compiler.compile(&mut b"IOFAIL".as_slice()).unwrap();
973 let mut vm = Vm::new(upcalls_by_name);
974
975 match vm.exec(&image) {
976 StopReason::Exception(_, msg) if msg == "mock I/O error" => (),
977 _ => panic!("Execution should stop at an IOFAIL exception"),
978 };
979
980 match vm.exec(&image) {
981 StopReason::Eof => (),
982 _ => panic!("Execution should stop at EOF after serving error"),
983 }
984 }
985
986 #[tokio::test]
987 async fn test_exec_upcall_io_error_can_be_caught() {
988 let data = Rc::from(RefCell::from(vec![]));
989 let mut upcalls_by_name: HashMap<SymbolKey, Rc<dyn Callable>> = HashMap::new();
990 upcalls_by_name.insert(SymbolKey::from("IOFAIL"), IoErrorCommand::new());
991 upcalls_by_name.insert(SymbolKey::from("OUT"), OutCommand::new(data.clone()));
992
993 let compiler = Compiler::new(&upcalls_by_name, &[]).unwrap();
994 let image = compiler
995 .compile(
996 &mut br#"
997 ON ERROR GOTO @recover
998 IOFAIL
999 END 5
1000 @recover
1001 OUT "ok"
1002 "#
1003 .as_slice(),
1004 )
1005 .unwrap();
1006 let mut vm = Vm::new(upcalls_by_name);
1007
1008 match vm.exec(&image) {
1009 StopReason::Eof => (),
1010 _ => panic!("Execution should complete after handling IOFAIL"),
1011 };
1012
1013 match vm.exec(&image) {
1014 StopReason::Eof => (),
1015 _ => panic!("Execution should have reached EOF after OUT"),
1016 }
1017
1018 assert_eq!(["ok"], *data.borrow().as_slice());
1019 assert_eq!(
1020 Some((LineCol { line: 3, col: 21 }, "mock I/O error".to_owned())),
1021 vm.last_error
1022 );
1023 }
1024
1025 #[tokio::test]
1026 async fn test_exec_yields_on_backward_jump() {
1027 let compiler = Compiler::new(&HashMap::default(), &[]).unwrap();
1028 let image = compiler.compile(&mut b"x = 0: DO: x = x + 1: LOOP".as_slice()).unwrap();
1029 let mut vm = Vm::new(HashMap::default());
1030
1031 match vm.exec(&image) {
1032 StopReason::Yield => (),
1033 _ => panic!("Execution should yield in a loop"),
1034 }
1035 assert_eq!(
1036 Some(ConstantDatum::Integer(1)),
1037 vm.get_program(&image, &SymbolKey::from("x")).unwrap()
1038 );
1039
1040 match vm.exec(&image) {
1041 StopReason::Yield => (),
1042 _ => panic!("Execution should continue yielding in a loop"),
1043 }
1044 assert_eq!(
1045 Some(ConstantDatum::Integer(2)),
1046 vm.get_program(&image, &SymbolKey::from("x")).unwrap()
1047 );
1048
1049 vm.interrupt(&image);
1050 match vm.exec(&image) {
1051 StopReason::Eof => (),
1052 _ => panic!("Execution should stop at EOF after interrupt"),
1053 }
1054 }
1055
1056 #[tokio::test]
1057 async fn test_exec_yields_after_gosub_return() {
1058 let compiler = Compiler::new(&HashMap::default(), &[]).unwrap();
1059 let image =
1060 compiler.compile(&mut b"GOSUB @foo: END\n@foo: x = x + 1: RETURN".as_slice()).unwrap();
1061 let mut vm = Vm::new(HashMap::default());
1062
1063 match vm.exec(&image) {
1064 StopReason::Yield => (),
1065 _ => panic!("Execution should yield after returning from GOSUB"),
1066 }
1067 assert_eq!(
1068 Some(ConstantDatum::Integer(1)),
1069 vm.get_program(&image, &SymbolKey::from("x")).unwrap()
1070 );
1071
1072 match vm.exec(&image) {
1073 StopReason::End(code) if code.is_success() => (),
1074 _ => panic!("Execution should continue after yield"),
1075 }
1076 }
1077
1078 #[tokio::test]
1079 async fn test_interrupt_parks_execution_at_eof() {
1080 let data = Rc::from(RefCell::from(vec![]));
1081 let mut upcalls_by_name: HashMap<SymbolKey, Rc<dyn Callable>> = HashMap::new();
1082 upcalls_by_name.insert(SymbolKey::from("OUT"), OutCommand::new(data.clone()));
1083
1084 let compiler = Compiler::new(&upcalls_by_name, &[]).unwrap();
1085 let image = compiler.compile(&mut b"OUT 1: OUT 2".as_slice()).unwrap();
1086 let mut vm = Vm::new(upcalls_by_name);
1087
1088 match vm.exec(&image) {
1089 StopReason::Eof => (),
1090 _ => panic!("Execution should stop at EOF"),
1091 }
1092 assert_eq!(["1", "2"], *data.borrow().as_slice());
1093
1094 vm.interrupt(&image);
1095 match vm.exec(&image) {
1096 StopReason::Eof => (),
1097 _ => panic!("Execution should be parked at EOF after interruption"),
1098 }
1099 assert_eq!(["1", "2"], *data.borrow().as_slice());
1100 }
1101
1102 #[tokio::test]
1103 async fn test_clear_resets_runtime_state() {
1104 let compiler = Compiler::new(&HashMap::default(), &[]).unwrap();
1105 let image = compiler.compile(&mut b"x = 7".as_slice()).unwrap();
1106 let mut vm = Vm::new(HashMap::default());
1107 run_to_end(&mut vm, &image).await;
1108
1109 assert_eq!(
1110 Some(ConstantDatum::Integer(7)),
1111 vm.get_program(&image, &SymbolKey::from("x")).unwrap()
1112 );
1113
1114 vm.clear();
1115
1116 assert_eq!(
1117 Some(ConstantDatum::Integer(0)),
1118 vm.get_program(&image, &SymbolKey::from("x")).unwrap()
1119 );
1120 }
1121
1122 #[tokio::test]
1123 async fn test_clear_preserves_upcall_caches() {
1124 let data = Rc::from(RefCell::from(vec![]));
1125 let mut upcalls_by_name: HashMap<SymbolKey, Rc<dyn Callable>> = HashMap::new();
1126 upcalls_by_name.insert(SymbolKey::from("OUT"), OutCommand::new(data.clone()));
1127
1128 let compiler = Compiler::new(&upcalls_by_name, &[]).unwrap();
1129 let image = compiler.compile(&mut b"OUT 3".as_slice()).unwrap();
1130 let mut vm = Vm::new(upcalls_by_name);
1131
1132 match vm.exec(&image) {
1133 StopReason::Eof => (),
1134 _ => panic!("Execution should stop at EOF"),
1135 }
1136 assert_eq!(["3"], *data.borrow().as_slice());
1137
1138 vm.clear();
1139
1140 match vm.exec(&image) {
1141 StopReason::Eof => (),
1142 _ => panic!("Execution should still stop at EOF after clear"),
1143 }
1144 assert_eq!(["3", "3"], *data.borrow().as_slice());
1145 }
1146
1147 #[tokio::test]
1148 async fn test_reset_preserves_call_stack_limit() {
1149 let compiler = Compiler::new(&HashMap::default(), &[]).unwrap();
1150 let image = compiler
1151 .compile(
1152 &mut br#"
1153 SUB recurse(n%)
1154 IF n < 20 THEN
1155 recurse n + 1
1156 END IF
1157 END SUB
1158
1159 recurse 0
1160 "#
1161 .as_slice(),
1162 )
1163 .unwrap();
1164 let mut vm = Vm::new_with_limits(
1165 HashMap::default(),
1166 Limits { max_call_stack: 8, max_heap_entries: U24::MAX },
1167 );
1168
1169 match vm.exec(&image) {
1170 StopReason::Exception(_, msg) if msg == "Out of call stack space" => (),
1171 _ => panic!("Execution should stop when the call stack limit is reached"),
1172 }
1173
1174 vm.reset();
1175
1176 match vm.exec(&image) {
1177 StopReason::Exception(_, msg) if msg == "Out of call stack space" => (),
1178 _ => panic!("Execution should preserve the configured call stack limit after reset"),
1179 }
1180 }
1181
1182 #[tokio::test]
1183 async fn test_scope_get_pos_no_args() {
1184 let positions: Rc<RefCell<Vec<LineCol>>> = Rc::default();
1185 let cmd = PosCapture::new(0, positions.clone());
1186 let mut upcalls_by_name: HashMap<SymbolKey, Rc<dyn Callable>> = HashMap::new();
1187 upcalls_by_name.insert(SymbolKey::from("POS_CAPTURE"), cmd);
1188
1189 let compiler = Compiler::new(&upcalls_by_name, &[]).unwrap();
1190 let image = compiler.compile(&mut b"POS_CAPTURE".as_slice()).unwrap();
1191 let mut vm = Vm::new(upcalls_by_name);
1192 run_to_end(&mut vm, &image).await;
1193
1194 let pos = positions.borrow();
1195 assert_eq!(&[] as &[LineCol], pos.as_slice());
1196 }
1197
1198 #[tokio::test]
1199 async fn test_scope_get_pos_single_arg() {
1200 let positions: Rc<RefCell<Vec<LineCol>>> = Rc::default();
1201 let cmd = PosCapture::new(1, positions.clone());
1202 let mut upcalls_by_name: HashMap<SymbolKey, Rc<dyn Callable>> = HashMap::new();
1203 upcalls_by_name.insert(SymbolKey::from("POS_CAPTURE"), cmd);
1204
1205 let compiler = Compiler::new(&upcalls_by_name, &[]).unwrap();
1206 let image = compiler.compile(&mut b"POS_CAPTURE 42".as_slice()).unwrap();
1207 let mut vm = Vm::new(upcalls_by_name);
1208 run_to_end(&mut vm, &image).await;
1209
1210 let pos = positions.borrow();
1211 assert_eq!(&[LineCol { line: 1, col: 13 }], pos.as_slice());
1212 }
1213
1214 #[tokio::test]
1215 async fn test_scope_get_pos_multiple_args() {
1216 let positions: Rc<RefCell<Vec<LineCol>>> = Rc::default();
1217 let cmd = PosCapture::new(3, positions.clone());
1218 let mut upcalls_by_name: HashMap<SymbolKey, Rc<dyn Callable>> = HashMap::new();
1219 upcalls_by_name.insert(SymbolKey::from("POS_CAPTURE"), cmd);
1220
1221 let compiler = Compiler::new(&upcalls_by_name, &[]).unwrap();
1222 let image = compiler.compile(&mut b"POS_CAPTURE 1, 2, 3".as_slice()).unwrap();
1223 let mut vm = Vm::new(upcalls_by_name);
1224 run_to_end(&mut vm, &image).await;
1225
1226 let pos = positions.borrow();
1227 assert_eq!(
1228 &[
1229 LineCol { line: 1, col: 13 },
1230 LineCol { line: 1, col: 16 },
1231 LineCol { line: 1, col: 19 }
1232 ],
1233 pos.as_slice()
1234 );
1235 }
1236
1237 #[tokio::test]
1238 async fn test_scope_get_pos_expression_arg() {
1239 let positions: Rc<RefCell<Vec<LineCol>>> = Rc::default();
1240 let cmd = PosCapture::new(1, positions.clone());
1241 let mut upcalls_by_name: HashMap<SymbolKey, Rc<dyn Callable>> = HashMap::new();
1242 upcalls_by_name.insert(SymbolKey::from("POS_CAPTURE"), cmd);
1243
1244 let compiler = Compiler::new(&upcalls_by_name, &[]).unwrap();
1245 let image = compiler.compile(&mut b"POS_CAPTURE 1 + 2".as_slice()).unwrap();
1246 let mut vm = Vm::new(upcalls_by_name);
1247 run_to_end(&mut vm, &image).await;
1248
1249 let pos = positions.borrow();
1250 assert_eq!(&[LineCol { line: 1, col: 13 }], pos.as_slice());
1251 }
1252
1253 #[tokio::test]
1254 async fn test_get_program_scalar() {
1255 let compiler = Compiler::new(&HashMap::default(), &[]).unwrap();
1256 let image = compiler.compile(&mut b"x = 123".as_slice()).unwrap();
1257 let mut vm = Vm::new(HashMap::default());
1258 run_to_end(&mut vm, &image).await;
1259
1260 assert_eq!(
1261 Some(ConstantDatum::Integer(123)),
1262 vm.get_program(&image, &SymbolKey::from("x")).unwrap()
1263 );
1264 assert_eq!(None, vm.get_program(&image, &SymbolKey::from("missing")).unwrap());
1265 }
1266
1267 #[tokio::test]
1268 async fn test_get_program_array() {
1269 let compiler = Compiler::new(&HashMap::default(), &[]).unwrap();
1270 let image =
1271 compiler.compile(&mut b"DIM arr(2) AS INTEGER: arr(1) = 45".as_slice()).unwrap();
1272 let mut vm = Vm::new(HashMap::default());
1273 run_to_end(&mut vm, &image).await;
1274
1275 assert_eq!(
1276 Some(ConstantDatum::Integer(45)),
1277 vm.get_program_array(&image, &SymbolKey::from("arr"), &[1]).unwrap()
1278 );
1279 }
1280
1281 #[tokio::test]
1282 async fn test_get_program_type_mismatch_errors() {
1283 let compiler = Compiler::new(&HashMap::default(), &[]).unwrap();
1284 let image =
1285 compiler.compile(&mut b"x = 1: DIM arr(2) AS INTEGER: arr(1) = 45".as_slice()).unwrap();
1286 let mut vm = Vm::new(HashMap::default());
1287 run_to_end(&mut vm, &image).await;
1288
1289 match vm.get_program(&image, &SymbolKey::from("arr")) {
1290 Err(GetGlobalError::IsArray(name)) => assert_eq!("ARR", name),
1291 other => panic!("Unexpected result: {:?}", other),
1292 }
1293
1294 match vm.get_program_array(&image, &SymbolKey::from("x"), &[0]) {
1295 Err(GetGlobalError::IsScalar(name)) => assert_eq!("X", name),
1296 other => panic!("Unexpected result: {:?}", other),
1297 }
1298 }
1299
1300 #[tokio::test]
1301 async fn test_get_program_array_out_of_bounds() {
1302 let compiler = Compiler::new(&HashMap::default(), &[]).unwrap();
1303 let image =
1304 compiler.compile(&mut b"DIM arr(2) AS INTEGER: arr(1) = 45".as_slice()).unwrap();
1305 let mut vm = Vm::new(HashMap::default());
1306 run_to_end(&mut vm, &image).await;
1307
1308 match vm.get_program_array(&image, &SymbolKey::from("arr"), &[3]) {
1309 Err(GetGlobalError::SubscriptOutOfBounds(_)) => (),
1310 other => panic!("Unexpected result: {:?}", other),
1311 }
1312 }
1313}