1use std::sync::{Arc, Mutex};
11
12use tatara_lisp::Span;
13use thiserror::Error;
14
15use super::chunk::{CaptureSource, Chunk, CompiledFn};
16use super::op::Op;
17use crate::eval::Interpreter;
18use crate::value::Value;
19
20#[derive(Debug, Error)]
21pub enum VmError {
22 #[error("stack underflow at op {ip}")]
23 Underflow { ip: usize },
24 #[error("unbound symbol `{name}` at {at}")]
25 Unbound { name: String, at: Span },
26 #[error("not callable: {kind} at {at}")]
27 NotCallable { kind: &'static str, at: Span },
28 #[error("arity mismatch: expected {expected}, got {got} at {at}")]
29 Arity {
30 expected: usize,
31 got: usize,
32 at: Span,
33 },
34 #[error("eval error: {0}")]
35 Eval(#[from] crate::error::EvalError),
36 #[error("local index out of bounds: {0}")]
37 BadLocal(usize),
38}
39
40#[derive(Debug, Clone, Copy)]
44struct HandlerRecord {
45 catch_ip: usize,
47 error_local: usize,
49 stack_at_push: usize,
53}
54
55struct Frame {
57 func: Arc<CompiledFn>,
59 ip: usize,
61 locals_base: usize,
64 stack_base: usize,
67 captures: Vec<Arc<Mutex<Value>>>,
72 local_cells: std::collections::HashMap<usize, Arc<Mutex<Value>>>,
78 handlers: Vec<HandlerRecord>,
80}
81
82pub struct Vm {
84 stack: Vec<Value>,
85 frames: Vec<Frame>,
86}
87
88impl Vm {
89 pub fn new() -> Self {
90 Self {
91 stack: Vec::with_capacity(256),
92 frames: Vec::with_capacity(64),
93 }
94 }
95
96 pub fn run<H: 'static>(
104 &mut self,
105 chunk: &Chunk,
106 interp: &mut Interpreter<H>,
107 host: &mut H,
108 ) -> Result<Value, VmError> {
109 let chunk_arc = Arc::new(chunk.clone());
110 self.run_arc(chunk_arc, interp, host)
111 }
112
113 pub fn run_arc<H: 'static>(
117 &mut self,
118 chunk: Arc<Chunk>,
119 interp: &mut Interpreter<H>,
120 host: &mut H,
121 ) -> Result<Value, VmError> {
122 self.stack.clear();
124 self.frames.clear();
125 let top_func = Arc::new(chunk.top.clone());
127 let top_locals = top_func.locals;
128 let top_frame = Frame {
129 func: top_func.clone(),
130 ip: 0,
131 locals_base: 0,
132 stack_base: top_locals,
133 captures: Vec::new(),
134 local_cells: std::collections::HashMap::new(),
135 handlers: Vec::new(),
136 };
137 for _ in 0..top_locals {
139 self.stack.push(Value::Nil);
140 }
141 self.frames.push(top_frame);
142
143 self.run_with_handlers(&chunk, interp, host)
145 }
146
147 fn run_inner<H: 'static>(
153 &mut self,
154 chunk: &Arc<Chunk>,
155 interp: &mut Interpreter<H>,
156 host: &mut H,
157 ) -> Result<Value, VmError> {
158 loop {
159 let frame_idx = self.frames.len() - 1;
162 let (op, span);
163 {
164 let f = &self.frames[frame_idx];
165 if f.ip >= f.func.ops.len() {
166 return Ok(self.pop_or_nil());
169 }
170 op = f.func.ops[f.ip].clone();
171 span = f.func.spans.get(f.ip).copied().unwrap_or(Span::synthetic());
172 }
173 self.frames[frame_idx].ip += 1;
174
175 match op {
176 Op::Halt => {
177 return Ok(self.pop_or_nil());
178 }
179 Op::Nil => self.stack.push(Value::Nil),
180 Op::True => self.stack.push(Value::Bool(true)),
181 Op::False => self.stack.push(Value::Bool(false)),
182 Op::Int(n) => self.stack.push(Value::Int(n)),
183 Op::Const(idx) => {
184 let v = chunk.consts.get(idx).clone();
185 self.stack.push(v);
186 }
187
188 Op::Pop => {
189 self.stack.pop().ok_or(VmError::Underflow { ip: 0 })?;
190 }
191 Op::Dup => {
192 let v = self
193 .stack
194 .last()
195 .ok_or(VmError::Underflow { ip: 0 })?
196 .clone();
197 self.stack.push(v);
198 }
199
200 Op::LoadLocal(idx) => {
201 let f = &self.frames[frame_idx];
202 if let Some(cell) = f.local_cells.get(&idx) {
206 let v = cell.lock().unwrap().clone();
207 self.stack.push(v);
208 } else {
209 let abs = f.locals_base + idx;
210 let v = self
211 .stack
212 .get(abs)
213 .cloned()
214 .ok_or(VmError::BadLocal(idx))?;
215 self.stack.push(v);
216 }
217 }
218 Op::StoreLocal(idx) => {
219 let v = self.stack.pop().ok_or(VmError::Underflow { ip: 0 })?;
220 let f = &self.frames[frame_idx];
221 if let Some(cell) = f.local_cells.get(&idx).cloned() {
224 *cell.lock().unwrap() = v;
225 } else {
226 let abs = f.locals_base + idx;
227 if abs >= self.stack.len() {
228 return Err(VmError::BadLocal(idx));
229 }
230 self.stack[abs] = v;
231 }
232 }
233
234 Op::LoadCaptured(idx) => {
235 let f = &self.frames[frame_idx];
236 let cell = f
237 .captures
238 .get(idx)
239 .cloned()
240 .ok_or(VmError::BadLocal(idx))?;
241 let v = cell.lock().unwrap().clone();
242 self.stack.push(v);
243 }
244 Op::StoreCaptured(idx) => {
245 let v = self.stack.pop().ok_or(VmError::Underflow { ip: 0 })?;
246 let f = &self.frames[frame_idx];
247 let cell = f
248 .captures
249 .get(idx)
250 .cloned()
251 .ok_or(VmError::BadLocal(idx))?;
252 *cell.lock().unwrap() = v;
253 }
254
255 Op::LoadGlobal(name_idx) => {
256 let name = chunk.names.get(name_idx).clone();
257 let v = self
258 .lookup_global(interp, &name)
259 .ok_or_else(|| VmError::Unbound {
260 name: name.to_string(),
261 at: span,
262 })?;
263 self.stack.push(v);
264 }
265 Op::StoreGlobal(name_idx) => {
266 let name = chunk.names.get(name_idx).clone();
267 let v = self.stack.pop().ok_or(VmError::Underflow { ip: 0 })?;
268 interp.define_global(name, v);
269 }
270
271 Op::Jmp(target) => {
272 self.frames[frame_idx].ip = target;
273 }
274 Op::JmpNot(target) => {
275 let v = self.stack.pop().ok_or(VmError::Underflow { ip: 0 })?;
276 if !v.is_truthy() {
277 self.frames[frame_idx].ip = target;
278 }
279 }
280 Op::JmpIf(target) => {
281 let v = self.stack.pop().ok_or(VmError::Underflow { ip: 0 })?;
282 if v.is_truthy() {
283 self.frames[frame_idx].ip = target;
284 }
285 }
286
287 Op::MakeClosure(fn_idx) => {
288 let body = chunk.fn_table[fn_idx].clone();
289 let mut closure_captures: Vec<Arc<Mutex<Value>>> =
300 Vec::with_capacity(body.captures.len());
301 for (_, source) in &body.captures {
302 let cell = match source {
303 CaptureSource::Local(local_idx) => {
304 let f = &mut self.frames[frame_idx];
305 if let Some(existing) = f.local_cells.get(local_idx).cloned() {
306 existing
307 } else {
308 let abs = f.locals_base + local_idx;
309 let v = self
310 .stack
311 .get(abs)
312 .cloned()
313 .ok_or(VmError::BadLocal(*local_idx))?;
314 let cell = Arc::new(Mutex::new(v));
315 self.frames[frame_idx]
316 .local_cells
317 .insert(*local_idx, cell.clone());
318 cell
319 }
320 }
321 CaptureSource::Captured(cap_idx) => self.frames[frame_idx]
322 .captures
323 .get(*cap_idx)
324 .cloned()
325 .ok_or(VmError::BadLocal(*cap_idx))?,
326 };
327 closure_captures.push(cell);
328 }
329 let compiled = CompiledClosure {
330 body: Arc::new(body),
331 captures: closure_captures,
332 chunk: Arc::clone(chunk),
333 globals: interp.globals_snapshot().clone(),
334 };
335 self.stack.push(Value::Foreign(Arc::new(compiled)));
336 }
337
338 Op::Call(arity) => {
339 self.do_call(chunk, interp, host, arity, span, false)?;
340 }
341 Op::TailCall(arity) => {
342 self.do_call(chunk, interp, host, arity, span, true)?;
343 }
344 Op::Return => {
345 let ret = self.stack.pop().ok_or(VmError::Underflow { ip: 0 })?;
346 let f = self
348 .frames
349 .pop()
350 .expect("Return with no active frame");
351 self.stack.truncate(f.locals_base);
352 if self.frames.is_empty() {
353 return Ok(ret);
354 }
355 self.stack.push(ret);
356 }
357
358 Op::MakeList(n) => {
359 let len = self.stack.len();
360 if n > len {
361 return Err(VmError::Underflow { ip: 0 });
362 }
363 let items: Vec<Value> = self.stack.drain(len - n..).collect();
364 self.stack.push(Value::list(items));
365 }
366
367 Op::EvalSexp(idx) => {
368 let v = chunk.consts.get(idx).clone();
373 let (sexp, sp) = match v {
374 Value::Sexp(s, sp) => (s, sp),
375 _ => {
376 return Err(VmError::Eval(crate::error::EvalError::native_fn(
377 Arc::<str>::from("vm:eval-sexp"),
378 "expected a Sexp constant in EvalSexp",
379 span,
380 )));
381 }
382 };
383 let spanned = tatara_lisp::Spanned::from_sexp_at(&sexp, sp);
384 let result = interp.eval_spanned(&spanned, host)?;
385 self.stack.push(result);
386 }
387
388 Op::PushHandler {
389 catch_ip,
390 error_local,
391 } => {
392 let stack_at_push = self.stack.len();
393 self.frames[frame_idx].handlers.push(HandlerRecord {
394 catch_ip,
395 error_local,
396 stack_at_push,
397 });
398 }
399 Op::PopHandler => {
400 self.frames[frame_idx].handlers.pop();
401 }
402 }
403 }
404 }
405
406 fn run_with_handlers<H: 'static>(
411 &mut self,
412 chunk: &Arc<Chunk>,
413 interp: &mut Interpreter<H>,
414 host: &mut H,
415 ) -> Result<Value, VmError> {
416 loop {
417 match self.run_inner(chunk, interp, host) {
418 Ok(v) => return Ok(v),
419 Err(VmError::Eval(eval_err)) => {
420 let err_value = vm_err_to_value(&eval_err);
421 if !self.unwind_to_handler(err_value) {
422 return Err(VmError::Eval(eval_err));
423 }
424 }
425 Err(other) => {
426 let err_value = vm_runtime_err_to_value(&other);
427 if !self.unwind_to_handler(err_value) {
428 return Err(other);
429 }
430 }
431 }
432 }
433 }
434
435 fn unwind_to_handler(&mut self, err_value: Value) -> bool {
442 for frame_idx in (0..self.frames.len()).rev() {
444 if !self.frames[frame_idx].handlers.is_empty() {
445 while self.frames.len() > frame_idx + 1 {
447 let f = self.frames.pop().unwrap();
448 self.stack.truncate(f.locals_base);
449 }
450 let handler = self.frames[frame_idx]
452 .handlers
453 .pop()
454 .expect("handler present");
455 self.stack.truncate(handler.stack_at_push);
458 let abs = self.frames[frame_idx].locals_base + handler.error_local;
460 if let Some(cell) = self.frames[frame_idx]
462 .local_cells
463 .get(&handler.error_local)
464 .cloned()
465 {
466 *cell.lock().unwrap() = err_value;
467 } else if abs < self.stack.len() {
468 self.stack[abs] = err_value;
469 } else {
470 while self.stack.len() <= abs {
472 self.stack.push(Value::Nil);
473 }
474 self.stack[abs] = err_value;
475 }
476 self.frames[frame_idx].ip = handler.catch_ip;
478 return true;
479 }
480 }
481 false
482 }
483
484 fn pop_or_nil(&mut self) -> Value {
485 self.stack.pop().unwrap_or(Value::Nil)
486 }
487
488 fn lookup_global<H: 'static>(
489 &self,
490 interp: &Interpreter<H>,
491 name: &str,
492 ) -> Option<Value> {
493 interp.lookup_global(name)
494 }
495
496 fn do_call<H: 'static>(
497 &mut self,
498 _chunk: &Chunk,
499 interp: &mut Interpreter<H>,
500 host: &mut H,
501 arity: usize,
502 span: Span,
503 tail: bool,
504 ) -> Result<(), VmError> {
505 let stack_len = self.stack.len();
506 if stack_len < arity + 1 {
507 return Err(VmError::Underflow { ip: 0 });
508 }
509 let callee_idx = stack_len - arity - 1;
510 let callee = self.stack[callee_idx].clone();
511
512 let tail = tail && self.frames.len() > 1;
516
517 match &callee {
519 Value::Foreign(any) => {
521 if let Ok(cc) = any.clone().downcast::<CompiledClosure>() {
522 return self.invoke_compiled(cc, arity, span, tail);
523 }
524 Err(VmError::NotCallable {
526 kind: callee.type_name(),
527 at: span,
528 })
529 }
530 Value::NativeFn(_) | Value::Closure(_) => {
534 let args: Vec<Value> = self.stack.drain(callee_idx + 1..).collect();
537 self.stack.pop();
538 let result = interp.apply_external_value(&callee, args, host, span)?;
539 self.stack.push(result);
540 Ok(())
541 }
542 other => Err(VmError::NotCallable {
543 kind: other.type_name(),
544 at: span,
545 }),
546 }
547 }
548
549 fn invoke_compiled(
550 &mut self,
551 cc: Arc<CompiledClosure>,
552 arity: usize,
553 span: Span,
554 tail: bool,
555 ) -> Result<(), VmError> {
556 let body = cc.body.clone();
557 let required = body.params.len();
558 let has_rest = body.rest.is_some();
559 if !has_rest && arity != required {
560 return Err(VmError::Arity {
561 expected: required,
562 got: arity,
563 at: span,
564 });
565 }
566 if has_rest && arity < required {
567 return Err(VmError::Arity {
568 expected: required,
569 got: arity,
570 at: span,
571 });
572 }
573
574 let stack_len = self.stack.len();
576 let args_start = stack_len - arity;
577 let args: Vec<Value> = self.stack.drain(args_start..).collect();
578 self.stack.pop();
580
581 let mut locals: Vec<Value> = Vec::with_capacity(body.locals);
583 for v in args.iter().take(required) {
584 locals.push(v.clone());
585 }
586 if let Some(_) = &body.rest {
587 let rest_args: Vec<Value> = args.iter().skip(required).cloned().collect();
588 locals.push(Value::list(rest_args));
589 }
590 while locals.len() < body.locals {
591 locals.push(Value::Nil);
592 }
593
594 if tail && !self.frames.is_empty() {
595 let frame_idx = self.frames.len() - 1;
597 let f = &mut self.frames[frame_idx];
598 self.stack.truncate(f.locals_base);
599 for v in locals {
600 self.stack.push(v);
601 }
602 f.func = body.clone();
603 f.ip = 0;
604 f.captures = cc.captures.clone();
605 f.local_cells.clear();
606 f.handlers.clear();
607 f.stack_base = f.locals_base + body.locals;
609 } else {
610 let locals_base = self.stack.len();
612 for v in locals {
613 self.stack.push(v);
614 }
615 let stack_base = self.stack.len();
616 self.frames.push(Frame {
617 func: body.clone(),
618 ip: 0,
619 locals_base,
620 stack_base,
621 captures: cc.captures.clone(),
622 local_cells: std::collections::HashMap::new(),
623 handlers: Vec::new(),
624 });
625 }
626 Ok(())
627 }
628}
629
630impl Default for Vm {
631 fn default() -> Self {
632 Self::new()
633 }
634}
635
636fn vm_err_to_value(err: &crate::error::EvalError) -> Value {
641 use crate::error::EvalError::*;
642 if let User { value, .. } = err {
643 return value.clone();
644 }
645 let tag: Arc<str> = match err {
646 UnboundSymbol { .. } => Arc::from("unbound-symbol"),
647 ArityMismatch { .. } => Arc::from("arity-mismatch"),
648 TypeMismatch { .. } => Arc::from("type-mismatch"),
649 DivisionByZero { .. } => Arc::from("division-by-zero"),
650 NotCallable { .. } => Arc::from("not-callable"),
651 BadSpecialForm { .. } => Arc::from("bad-special-form"),
652 NativeFn { .. } => Arc::from("native-fn"),
653 Reader(_) => Arc::from("reader"),
654 Halted => Arc::from("halted"),
655 NotImplemented(_) => Arc::from("not-implemented"),
656 User { .. } => unreachable!(),
657 };
658 Value::Error(Arc::new(crate::value::ErrorObj {
659 tag,
660 message: Arc::from(err.short_message()),
661 data: Vec::new(),
662 }))
663}
664
665fn vm_runtime_err_to_value(err: &VmError) -> Value {
668 let (tag, message): (&str, String) = match err {
669 VmError::Underflow { ip } => ("vm-underflow", format!("stack underflow at op {ip}")),
670 VmError::Unbound { name, .. } => ("unbound-symbol", format!("unbound symbol `{name}`")),
671 VmError::NotCallable { kind, .. } => {
672 ("not-callable", format!("value of type {kind} is not callable"))
673 }
674 VmError::Arity { expected, got, .. } => (
675 "arity-mismatch",
676 format!("expected {expected} args, got {got}"),
677 ),
678 VmError::BadLocal(idx) => ("bad-local", format!("local index out of bounds: {idx}")),
679 VmError::Eval(inner) => return vm_err_to_value(inner),
680 };
681 Value::Error(Arc::new(crate::value::ErrorObj {
682 tag: Arc::from(tag),
683 message: Arc::from(message),
684 data: Vec::new(),
685 }))
686}
687
688#[derive(Clone)]
709pub struct CompiledClosure {
710 pub body: Arc<CompiledFn>,
711 pub captures: Vec<Arc<Mutex<Value>>>,
712 pub chunk: Arc<super::chunk::Chunk>,
713 pub globals: crate::env::Env,
714}
715
716impl CompiledClosure {
717 pub fn lift_to_closure(&self) -> Arc<crate::value::Closure> {
743 let mut captured_env = self.globals.clone();
744 captured_env.push();
745 for ((name, _), cell) in self.body.captures.iter().zip(self.captures.iter()) {
746 let v = cell.lock().unwrap().clone();
747 captured_env.define(name.clone(), v);
748 }
749 Arc::new(crate::value::Closure {
750 params: self.body.params.clone(),
751 rest: self.body.rest.clone(),
752 body: self.body.source_body.clone(),
753 captured_env,
754 source: self.body.source_span,
755 })
756 }
757}
758
759impl std::fmt::Debug for CompiledClosure {
760 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
761 f.debug_struct("CompiledClosure")
762 .field("params", &self.body.params)
763 .field("ops_len", &self.body.ops.len())
764 .field("captures", &self.captures.len())
765 .finish()
766 }
767}
768
769#[cfg(test)]
770mod tests {
771 use super::*;
772 use crate::Interpreter;
773 use crate::install_full_stdlib_with;
774 use crate::vm::compile::compile_program;
775 use tatara_lisp::read_spanned;
776
777 struct NoHost;
778
779 fn run_vm(src: &str) -> Value {
781 let mut i: Interpreter<NoHost> = Interpreter::new();
782 install_full_stdlib_with(&mut i, &mut NoHost);
783 let forms = read_spanned(src).unwrap();
784 let mut expanded: Vec<tatara_lisp::Spanned> = Vec::new();
788 for form in &forms {
789 if i.expander_mut().try_register_macro(form).unwrap() {
790 continue;
791 }
792 expanded.push(i.fully_expand(form, &mut NoHost).unwrap());
793 }
794 let chunk = compile_program(&expanded).unwrap();
795 let mut vm = Vm::new();
796 vm.run(&chunk, &mut i, &mut NoHost).unwrap()
797 }
798
799 #[test]
800 fn run_int_literal() {
801 assert!(matches!(run_vm("42"), Value::Int(42)));
802 }
803
804 #[test]
805 fn run_arithmetic_via_native_add() {
806 assert!(matches!(run_vm("(+ 1 2 3)"), Value::Int(6)));
807 }
808
809 #[test]
810 fn run_if_picks_branch() {
811 assert!(matches!(run_vm("(if #t 100 200)"), Value::Int(100)));
812 assert!(matches!(run_vm("(if #f 100 200)"), Value::Int(200)));
813 }
814
815 #[test]
816 fn run_let_binds_and_uses() {
817 assert!(matches!(
818 run_vm("(let ((x 10) (y 20)) (+ x y))"),
819 Value::Int(30)
820 ));
821 }
822
823 #[test]
824 fn run_define_then_use() {
825 assert!(matches!(run_vm("(define x 99) x"), Value::Int(99)));
826 }
827
828 #[test]
829 fn run_define_function_shorthand() {
830 assert!(matches!(
831 run_vm("(define (sq x) (* x x)) (sq 7)"),
832 Value::Int(49)
833 ));
834 }
835
836 #[test]
837 fn run_lambda_inline_application() {
838 assert!(matches!(
839 run_vm("((lambda (x y) (+ x y)) 3 4)"),
840 Value::Int(7)
841 ));
842 }
843
844 #[test]
845 fn run_recursion_via_global_define() {
846 let v = run_vm(
847 "(define (fact n)
848 (if (= n 0) 1 (* n (fact (- n 1)))))
849 (fact 6)",
850 );
851 assert!(matches!(v, Value::Int(720)));
852 }
853
854 #[test]
855 fn run_begin_returns_last() {
856 assert!(matches!(run_vm("(begin 1 2 3)"), Value::Int(3)));
857 }
858
859 #[test]
860 fn run_and_short_circuits() {
861 assert!(matches!(run_vm("(and #t 5)"), Value::Int(5)));
863 assert!(matches!(run_vm("(and #f 5)"), Value::Bool(false)));
864 }
865
866 #[test]
867 fn run_or_short_circuits() {
868 assert!(matches!(run_vm("(or #f 7)"), Value::Int(7)));
869 assert!(matches!(run_vm("(or #f #f)"), Value::Bool(false)));
870 }
871
872 #[test]
873 fn run_not_inverts() {
874 assert!(matches!(run_vm("(not #t)"), Value::Bool(false)));
875 assert!(matches!(run_vm("(not #f)"), Value::Bool(true)));
876 }
877
878 #[test]
879 fn run_quoted_symbol_passes_through() {
880 let v = run_vm("'foo");
881 assert!(matches!(v, Value::Symbol(s) if &*s == "foo"));
882 }
883
884 #[test]
885 fn run_set_mutates_global() {
886 assert!(matches!(
887 run_vm("(define x 1) (set! x 99) x"),
888 Value::Int(99)
889 ));
890 }
891
892 #[test]
893 fn run_tail_call_loops_in_constant_space() {
894 let v = run_vm(
899 "(define (loop n) (if (= n 0) :done (loop (- n 1))))
900 (loop 50000)",
901 );
902 assert!(matches!(v, Value::Keyword(s) if &*s == "done"));
903 }
904
905 #[test]
908 fn closure_captures_outer_let_local() {
909 let v = run_vm("(let ((x 10)) ((lambda (y) (+ x y)) 5))");
913 assert!(matches!(v, Value::Int(15)));
914 }
915
916 #[test]
917 fn closure_returned_from_let_still_sees_captured() {
918 let v = run_vm(
923 "(define (make-adder n) (lambda (x) (+ x n)))
924 ((make-adder 10) 32)",
925 );
926 assert!(matches!(v, Value::Int(42)));
927 }
928
929 #[test]
930 fn nested_closures_chain_captures() {
931 let v = run_vm(
938 "(let ((x 5))
939 (let ((f (lambda (a) (lambda (b) (+ x a b)))))
940 ((f 3) 4)))",
941 );
942 assert!(matches!(v, Value::Int(12)));
943 }
944
945 #[test]
948 fn try_returns_body_value_when_no_throw() {
949 let v = run_vm(
950 "(try
951 (+ 1 2 3)
952 (catch (e) :unreachable))",
953 );
954 assert!(matches!(v, Value::Int(6)));
955 }
956
957 #[test]
958 fn try_catches_user_throw() {
959 let v = run_vm(
960 "(try
961 (throw (ex-info \"boom\" (list)))
962 (catch (e) (error-message e)))",
963 );
964 match v {
965 Value::Str(s) => assert_eq!(&*s, "boom"),
966 other => panic!("{other:?}"),
967 }
968 }
969
970 #[test]
971 fn try_catches_runtime_error() {
972 let v = run_vm(
976 "(try
977 (+ 1 \"oops\")
978 (catch (e) (error-tag e)))",
979 );
980 match v {
982 Value::Keyword(s) => assert_eq!(&*s, "type-mismatch"),
983 other => panic!("{other:?}"),
984 }
985 }
986
987 #[test]
988 fn try_inside_a_function_body() {
989 let v = run_vm(
993 "(define (safe-div a b)
994 (try
995 (/ a b)
996 (catch (e) :div-failed)))
997 (safe-div 10 0)",
998 );
999 assert!(matches!(v, Value::Keyword(s) if &*s == "div-failed"));
1000 }
1001
1002 #[test]
1003 fn nested_try_inner_catches_first() {
1004 let v = run_vm(
1005 "(try
1006 (try
1007 (throw (ex-info \"inner\" (list)))
1008 (catch (e) :inner-caught))
1009 (catch (e) :outer-caught))",
1010 );
1011 assert!(matches!(v, Value::Keyword(s) if &*s == "inner-caught"));
1012 }
1013
1014 #[test]
1017 fn vm_falls_back_to_tree_walker_for_quasi_quote() {
1018 let v = run_vm("(define x 99) `(a ,x c)");
1024 match v {
1025 Value::List(xs) => {
1026 assert_eq!(xs.len(), 3);
1027 assert!(matches!(&xs[1], Value::Int(99)));
1028 }
1029 other => panic!("{other:?}"),
1030 }
1031 }
1032
1033 #[test]
1034 fn vm_falls_back_for_eval() {
1035 let v = run_vm("(eval '(+ 1 2 3))");
1037 assert!(matches!(v, Value::Int(6)));
1038 }
1039
1040 #[test]
1041 fn vm_falls_back_for_macroexpand() {
1042 let v = run_vm(
1045 "(defmacro twice (x) `(* ,x 2))
1046 (macroexpand-1 '(twice 7))",
1047 );
1048 match v {
1051 Value::List(xs) => assert_eq!(xs.len(), 3),
1052 other => panic!("{other:?}"),
1053 }
1054 }
1055
1056 #[test]
1057 fn try_handler_can_rethrow_to_outer() {
1058 let v = run_vm(
1059 "(try
1060 (try
1061 (throw (ex-info \"first\" (list)))
1062 (catch (e) (throw (ex-info \"rethrown\" (list)))))
1063 (catch (e) (error-message e)))",
1064 );
1065 match v {
1066 Value::Str(s) => assert_eq!(&*s, "rethrown"),
1067 other => panic!("{other:?}"),
1068 }
1069 }
1070
1071 #[test]
1072 fn closure_set_on_captured_propagates() {
1073 let v = run_vm(
1077 "(define get (let ((x 0))
1078 (define setter (lambda (v) (set! x v)))
1079 (define getter (lambda () x))
1080 (setter 42)
1081 getter))
1082 (get)",
1083 );
1084 assert!(matches!(v, Value::Int(42)));
1085 }
1086}