1pub mod compiler;
9#[cfg(not(target_arch = "wasm32"))]
12mod bg_compile;
13#[cfg(not(target_arch = "wasm32"))]
15pub mod aot_tier;
16#[cfg(not(target_arch = "wasm32"))]
18pub mod bg_aot;
19pub mod fn_bytecode;
22pub mod tier_trace;
24pub mod tier_cache;
26#[cfg(test)]
27mod fuzz;
28pub mod disasm;
31mod instruction;
32mod machine;
33#[cfg_attr(not(feature = "narrow-value"), allow(dead_code))]
40mod nanbox;
41mod native_tier;
42mod value;
43
44pub mod wasm;
49
50#[cfg(feature = "wasm-jit")]
55pub mod wasm_jit {
56 pub use super::wasm::func::{compile_function_to_wasm, compile_region_to_wasm};
57 pub use super::wasm::region_jit::WasmTier;
58 #[cfg(target_arch = "wasm32")]
59 pub use super::wasm::region_jit::run_on_host;
60}
61
62pub use compiler::Compiler;
63pub use disasm::{disassemble, format_constant, op_io, DisasmLine, OpIo};
64pub use instruction::{ChanElem, CompiledProgram, Constant, Op};
65pub use machine::Vm;
66pub(crate) use machine::{DebugFrameView, DebugView, DebugVmState, HeapObjView};
67pub(crate) use machine::{VmBlock, VmStep};
68pub use native_tier::{
69 install_native_tier, installed_native_tier, ArrayPin, CalleeSig, FnTable, HoistGuard, NativeCtx,
70 NativeFn,
71 NativeFrame, NativeOutcome, NativeRet, NativeTier, ObservedKind, ParamKind, PinElem,
72 RegBox, RegionFn, RegionOutcome, RegionReturn, RegionReturnKind, SlotKind,
73 NATIVE_TIER_THRESHOLD, REGION_TIER_THRESHOLD,
74};
75pub use value::Value;
76
77use crate::ast::stmt::Stmt;
78use crate::intern::Interner;
79
80pub const MAX_REGISTERS_PER_FRAME: usize = 16_384;
83pub const MAX_REGISTER_FILE: usize = 1 << 20;
86pub const MAX_EXPR_DEPTH: usize = 64;
95
96pub fn branch_entropy(taken: u64, not_taken: u64) -> f64 {
101 let total = taken + not_taken;
102 if total == 0 || taken == 0 || not_taken == 0 {
103 return 0.0;
104 }
105 let p = taken as f64 / total as f64;
106 -(p * p.log2() + (1.0 - p) * (1.0 - p).log2())
107}
108
109pub fn compile_and_run(stmts: &[Stmt], interner: &Interner) -> Result<String, String> {
111 let program = Compiler::compile(stmts, interner)?;
112 let mut vm = Vm::new(&program);
113 if let Some(tier) = installed_native_tier() {
114 vm = vm.with_native_tier(tier);
115 }
116 vm.run()?;
117 Ok(vm.output().to_string())
118}
119
120pub fn run_to_outcome(
125 stmts: &[Stmt],
126 interner: &Interner,
127 types: Option<&crate::analysis::TypeRegistry>,
128 policies: Option<&crate::analysis::PolicyRegistry>,
129) -> (String, Option<String>) {
130 let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
131 let program = match Compiler::compile_with_oracle(stmts, interner, types, Some(oracle)) {
132 Ok(p) => p,
133 Err(e) => return (String::new(), Some(e)),
134 };
135 let mut vm = Vm::new(&program);
136 if let Some(tier) = installed_native_tier() {
137 vm = vm.with_native_tier(tier);
138 }
139 if let Some(registry) = policies {
140 vm = vm.with_policy_ctx(registry, interner);
141 }
142 match vm.run() {
143 Ok(()) => (vm.output().to_string(), None),
144 Err(e) => (vm.output().to_string(), Some(e)),
145 }
146}
147
148pub fn run_to_outcome_with_args(
154 stmts: &[Stmt],
155 interner: &Interner,
156 types: Option<&crate::analysis::TypeRegistry>,
157 policies: Option<&crate::analysis::PolicyRegistry>,
158 program_args: &[String],
159 tier: Option<&dyn NativeTier>,
160) -> (String, Option<String>) {
161 let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
162 let program = match Compiler::compile_with_oracle(stmts, interner, types, Some(oracle)) {
163 Ok(p) => p,
164 Err(e) => return (String::new(), Some(e)),
165 };
166 let mut vm = Vm::new(&program).with_program_args(program_args.to_vec());
167 let chosen: Option<&dyn NativeTier> = match tier {
168 Some(t) => Some(t),
169 None => installed_native_tier().map(|t| t as &dyn NativeTier),
170 };
171 if let Some(t) = chosen {
172 vm = vm.with_native_tier(t);
173 }
174 if let Some(registry) = policies {
175 vm = vm.with_policy_ctx(registry, interner);
176 }
177 match vm.run() {
178 Ok(()) => (vm.output().to_string(), None),
179 Err(e) => (vm.output().to_string(), Some(e)),
180 }
181}
182
183#[cfg(not(target_arch = "wasm32"))]
190pub fn run_to_outcome_bg(
191 stmts: &[Stmt],
192 interner: &Interner,
193 types: Option<&crate::analysis::TypeRegistry>,
194 policies: Option<&crate::analysis::PolicyRegistry>,
195 program_args: &[String],
196) -> (String, Option<String>) {
197 let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
198 let program = match Compiler::compile_with_oracle(stmts, interner, types, Some(oracle)) {
199 Ok(p) => p,
200 Err(e) => return (String::new(), Some(e)),
201 };
202 let mut vm = Vm::new(&program).with_program_args(program_args.to_vec());
203 if let Some(t) = installed_native_tier() {
204 vm = vm.with_bg_native_tier(t);
205 }
206 if let Some(registry) = policies {
207 vm = vm.with_policy_ctx(registry, interner);
208 }
209 let result = match vm.run() {
210 Ok(()) => (vm.output().to_string(), None),
211 Err(e) => (vm.output().to_string(), Some(e)),
212 };
213 vm.drain_pending_compiles();
214 result
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220 use crate::arena::Arena;
221 use crate::ast::stmt::{BinaryOpKind, Expr, Literal, Stmt};
222 use crate::intern::Interner;
223 use std::cell::RefCell;
224 use std::rc::Rc;
225
226 fn run_treewalk(stmts: &[Stmt], interner: &Interner) -> Result<String, String> {
229 use crate::interpreter::{Interpreter, OutputCallback};
230 let buf = Rc::new(RefCell::new(String::new()));
231 let sink = buf.clone();
232 let cb: OutputCallback = Rc::new(RefCell::new(move |s: String| {
233 sink.borrow_mut().push_str(&s);
234 sink.borrow_mut().push('\n');
235 }));
236 let mut interp = Interpreter::new(interner).with_output_callback(cb);
237 interp.run_sync(stmts)?;
238 let out = buf.borrow().clone();
239 Ok(out)
240 }
241
242 fn normalize(s: &str) -> String {
243 s.lines()
244 .map(|l| l.trim_end())
245 .filter(|l| !l.is_empty())
246 .collect::<Vec<_>>()
247 .join("\n")
248 }
249
250 fn assert_vm_eq_treewalk(stmts: &[Stmt], interner: &Interner) {
253 let vm_out = compile_and_run(stmts, interner).expect("vm run failed");
254 let tw_out = run_treewalk(stmts, interner).expect("tree-walker run failed");
255 assert_eq!(
256 normalize(&vm_out),
257 normalize(&tw_out),
258 "VM output diverged from the tree-walker oracle"
259 );
260 }
261
262 use crate::ast::stmt::TypeExpr;
265 use crate::intern::Symbol;
266
267 fn num<'a>(ea: &'a Arena<Expr<'a>>, n: i64) -> &'a Expr<'a> {
268 ea.alloc(Expr::Literal(Literal::Number(n)))
269 }
270 fn idref<'a>(ea: &'a Arena<Expr<'a>>, s: Symbol) -> &'a Expr<'a> {
271 ea.alloc(Expr::Identifier(s))
272 }
273 fn bin<'a>(ea: &'a Arena<Expr<'a>>, op: BinaryOpKind, l: &'a Expr<'a>, r: &'a Expr<'a>) -> &'a Expr<'a> {
274 ea.alloc(Expr::BinaryOp { op, left: l, right: r })
275 }
276 fn calle<'a>(ea: &'a Arena<Expr<'a>>, f: Symbol, args: Vec<&'a Expr<'a>>) -> &'a Expr<'a> {
277 ea.alloc(Expr::Call { function: f, args })
278 }
279 fn letb<'a>(var: Symbol, value: &'a Expr<'a>) -> Stmt<'a> {
280 Stmt::Let { var, ty: None, value, mutable: false }
281 }
282 fn ret<'a>(value: &'a Expr<'a>) -> Stmt<'a> {
283 Stmt::Return { value: Some(value) }
284 }
285 fn show<'a>(ea: &'a Arena<Expr<'a>>, show_sym: Symbol, obj: &'a Expr<'a>) -> Stmt<'a> {
286 Stmt::Show { object: obj, recipient: idref(ea, show_sym) }
287 }
288 fn fndef<'a>(
289 name: Symbol,
290 params: Vec<(Symbol, &'a TypeExpr<'a>)>,
291 body: &'a [Stmt<'a>],
292 ) -> Stmt<'a> {
293 Stmt::FunctionDef {
294 name,
295 generics: vec![],
296 params,
297 body,
298 return_type: None,
299 is_native: false,
300 native_path: None,
301 is_exported: false,
302 export_target: None,
303 opt_flags: Default::default(),
304 }
305 }
306
307 #[test]
308 fn vm_function_single_param() {
309 let ea: Arena<Expr> = Arena::new();
312 let sa: Arena<Stmt> = Arena::new();
313 let ta: Arena<TypeExpr> = Arena::new();
314 let mut it = Interner::new();
315 let double = it.intern("double");
316 let n = it.intern("n");
317 let r = it.intern("r");
318 let show_s = it.intern("show");
319 let int_ty = ta.alloc(TypeExpr::Primitive(it.intern("Int")));
320
321 let body: &[Stmt] = sa.alloc_slice(vec![ret(bin(&ea, BinaryOpKind::Multiply, idref(&ea, n), num(&ea, 2)))]);
322 let main_call = calle(&ea, double, vec![num(&ea, 21)]);
323
324 let stmts = vec![
325 fndef(double, vec![(n, int_ty)], body),
326 letb(r, main_call),
327 show(&ea, show_s, idref(&ea, r)),
328 ];
329 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "42");
330 assert_vm_eq_treewalk(&stmts, &it);
331 }
332
333 #[test]
334 fn vm_function_two_params() {
335 let ea: Arena<Expr> = Arena::new();
337 let sa: Arena<Stmt> = Arena::new();
338 let ta: Arena<TypeExpr> = Arena::new();
339 let mut it = Interner::new();
340 let add = it.intern("add");
341 let a = it.intern("a");
342 let b = it.intern("b");
343 let r = it.intern("r");
344 let show_s = it.intern("show");
345 let int_ty = ta.alloc(TypeExpr::Primitive(it.intern("Int")));
346
347 let body: &[Stmt] = sa.alloc_slice(vec![ret(bin(&ea, BinaryOpKind::Add, idref(&ea, a), idref(&ea, b)))]);
348 let stmts = vec![
349 fndef(add, vec![(a, int_ty), (b, int_ty)], body),
350 letb(r, calle(&ea, add, vec![num(&ea, 3), num(&ea, 4)])),
351 show(&ea, show_s, idref(&ea, r)),
352 ];
353 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "7");
354 assert_vm_eq_treewalk(&stmts, &it);
355 }
356
357 #[test]
358 fn vm_function_recursion_factorial() {
359 let ea: Arena<Expr> = Arena::new();
362 let sa: Arena<Stmt> = Arena::new();
363 let ta: Arena<TypeExpr> = Arena::new();
364 let mut it = Interner::new();
365 let factorial = it.intern("factorial");
366 let n = it.intern("n");
367 let r = it.intern("r");
368 let show_s = it.intern("show");
369 let int_ty = ta.alloc(TypeExpr::Primitive(it.intern("Int")));
370
371 let then_blk: &[Stmt] = sa.alloc_slice(vec![ret(num(&ea, 1))]);
372 let cond = bin(&ea, BinaryOpKind::LtEq, idref(&ea, n), num(&ea, 1));
373 let rec = calle(&ea, factorial, vec![bin(&ea, BinaryOpKind::Subtract, idref(&ea, n), num(&ea, 1))]);
374 let tail = ret(bin(&ea, BinaryOpKind::Multiply, idref(&ea, n), rec));
375 let body: &[Stmt] = sa.alloc_slice(vec![
376 Stmt::If { cond, then_block: then_blk, else_block: None },
377 tail,
378 ]);
379
380 let stmts = vec![
381 fndef(factorial, vec![(n, int_ty)], body),
382 letb(r, calle(&ea, factorial, vec![num(&ea, 5)])),
383 show(&ea, show_s, idref(&ea, r)),
384 ];
385 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "120");
386 assert_vm_eq_treewalk(&stmts, &it);
387 }
388
389 #[test]
390 fn vm_function_nested_calls() {
391 let ea: Arena<Expr> = Arena::new();
393 let sa: Arena<Stmt> = Arena::new();
394 let ta: Arena<TypeExpr> = Arena::new();
395 let mut it = Interner::new();
396 let triple = it.intern("triple");
397 let double = it.intern("double");
398 let n = it.intern("n");
399 let r = it.intern("r");
400 let show_s = it.intern("show");
401 let int_ty = ta.alloc(TypeExpr::Primitive(it.intern("Int")));
402
403 let triple_body: &[Stmt] = sa.alloc_slice(vec![ret(bin(&ea, BinaryOpKind::Multiply, idref(&ea, n), num(&ea, 3)))]);
404 let double_body: &[Stmt] = sa.alloc_slice(vec![ret(bin(&ea, BinaryOpKind::Multiply, idref(&ea, n), num(&ea, 2)))]);
405 let inner = calle(&ea, triple, vec![num(&ea, 2)]);
406 let outer = calle(&ea, double, vec![inner]);
407 let stmts = vec![
408 fndef(triple, vec![(n, int_ty)], triple_body),
409 fndef(double, vec![(n, int_ty)], double_body),
410 letb(r, outer),
411 show(&ea, show_s, idref(&ea, r)),
412 ];
413 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "12");
414 assert_vm_eq_treewalk(&stmts, &it);
415 }
416
417 #[test]
418 fn vm_function_with_local_variable() {
419 let ea: Arena<Expr> = Arena::new();
421 let sa: Arena<Stmt> = Arena::new();
422 let ta: Arena<TypeExpr> = Arena::new();
423 let mut it = Interner::new();
424 let compute = it.intern("compute");
425 let x = it.intern("x");
426 let y = it.intern("y");
427 let r = it.intern("r");
428 let show_s = it.intern("show");
429 let int_ty = ta.alloc(TypeExpr::Primitive(it.intern("Int")));
430
431 let body: &[Stmt] = sa.alloc_slice(vec![
432 letb(y, bin(&ea, BinaryOpKind::Multiply, idref(&ea, x), idref(&ea, x))),
433 ret(bin(&ea, BinaryOpKind::Add, idref(&ea, y), num(&ea, 1))),
434 ]);
435 let stmts = vec![
436 fndef(compute, vec![(x, int_ty)], body),
437 letb(r, calle(&ea, compute, vec![num(&ea, 4)])),
438 show(&ea, show_s, idref(&ea, r)),
439 ];
440 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "17");
441 assert_vm_eq_treewalk(&stmts, &it);
442 }
443
444 #[test]
445 fn vm_function_conditional_return_fallthrough() {
446 let ea: Arena<Expr> = Arena::new();
449 let sa: Arena<Stmt> = Arena::new();
450 let ta: Arena<TypeExpr> = Arena::new();
451 let mut it = Interner::new();
452 let sign = it.intern("sign");
453 let n = it.intern("n");
454 let p = it.intern("p");
455 let q = it.intern("q");
456 let show_s = it.intern("show");
457 let int_ty = ta.alloc(TypeExpr::Primitive(it.intern("Int")));
458
459 let then_blk: &[Stmt] = sa.alloc_slice(vec![ret(num(&ea, 1))]);
460 let body: &[Stmt] = sa.alloc_slice(vec![
461 Stmt::If { cond: bin(&ea, BinaryOpKind::Gt, idref(&ea, n), num(&ea, 0)), then_block: then_blk, else_block: None },
462 ret(num(&ea, 0)),
463 ]);
464 let stmts = vec![
465 fndef(sign, vec![(n, int_ty)], body),
466 letb(p, calle(&ea, sign, vec![num(&ea, 5)])),
467 show(&ea, show_s, idref(&ea, p)),
468 letb(q, calle(&ea, sign, vec![num(&ea, -3)])),
469 show(&ea, show_s, idref(&ea, q)),
470 ];
471 let out = compile_and_run(&stmts, &it).unwrap();
472 let lines: Vec<&str> = out.lines().collect();
473 assert_eq!(lines[0], "1");
474 assert_eq!(lines[1], "0");
475 assert_vm_eq_treewalk(&stmts, &it);
476 }
477
478 fn list_lit<'a>(ea: &'a Arena<Expr<'a>>, items: Vec<&'a Expr<'a>>) -> &'a Expr<'a> {
481 ea.alloc(Expr::List(items))
482 }
483 fn new_coll<'a>(ea: &'a Arena<Expr<'a>>, type_name: Symbol) -> &'a Expr<'a> {
484 ea.alloc(Expr::New { type_name, type_args: vec![], init_fields: vec![] })
485 }
486 fn length_of<'a>(ea: &'a Arena<Expr<'a>>, c: &'a Expr<'a>) -> &'a Expr<'a> {
487 ea.alloc(Expr::Length { collection: c })
488 }
489 fn index_at<'a>(ea: &'a Arena<Expr<'a>>, c: &'a Expr<'a>, i: &'a Expr<'a>) -> &'a Expr<'a> {
490 ea.alloc(Expr::Index { collection: c, index: i })
491 }
492 fn contains_e<'a>(ea: &'a Arena<Expr<'a>>, c: &'a Expr<'a>, v: &'a Expr<'a>) -> &'a Expr<'a> {
493 ea.alloc(Expr::Contains { collection: c, value: v })
494 }
495 fn range_e<'a>(ea: &'a Arena<Expr<'a>>, s: &'a Expr<'a>, e: &'a Expr<'a>) -> &'a Expr<'a> {
496 ea.alloc(Expr::Range { start: s, end: e })
497 }
498 fn push_to<'a>(value: &'a Expr<'a>, collection: &'a Expr<'a>) -> Stmt<'a> {
499 Stmt::Push { value, collection }
500 }
501
502 #[test]
503 fn vm_list_literal_and_length() {
504 let ea: Arena<Expr> = Arena::new();
506 let mut it = Interner::new();
507 let xs = it.intern("xs");
508 let show_s = it.intern("show");
509 let lst = list_lit(&ea, vec![num(&ea, 10), num(&ea, 20), num(&ea, 30)]);
510 let stmts = vec![
511 letb(xs, lst),
512 show(&ea, show_s, length_of(&ea, idref(&ea, xs))),
513 ];
514 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "3");
515 assert_vm_eq_treewalk(&stmts, &it);
516 }
517
518 #[test]
519 fn vm_new_seq_push_length() {
520 let ea: Arena<Expr> = Arena::new();
522 let mut it = Interner::new();
523 let xs = it.intern("xs");
524 let seq = it.intern("Seq");
525 let show_s = it.intern("show");
526 let stmts = vec![
527 Stmt::Let { var: xs, ty: None, value: new_coll(&ea, seq), mutable: true },
528 push_to(num(&ea, 10), idref(&ea, xs)),
529 push_to(num(&ea, 20), idref(&ea, xs)),
530 show(&ea, show_s, length_of(&ea, idref(&ea, xs))),
531 ];
532 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "2");
533 assert_vm_eq_treewalk(&stmts, &it);
534 }
535
536 #[test]
537 fn vm_empty_seq_length_is_zero() {
538 let ea: Arena<Expr> = Arena::new();
539 let mut it = Interner::new();
540 let xs = it.intern("xs");
541 let seq = it.intern("Seq");
542 let show_s = it.intern("show");
543 let stmts = vec![
544 Stmt::Let { var: xs, ty: None, value: new_coll(&ea, seq), mutable: true },
545 show(&ea, show_s, length_of(&ea, idref(&ea, xs))),
546 ];
547 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "0");
548 assert_vm_eq_treewalk(&stmts, &it);
549 }
550
551 #[test]
552 fn vm_list_index_one_based() {
553 let ea: Arena<Expr> = Arena::new();
555 let mut it = Interner::new();
556 let xs = it.intern("xs");
557 let show_s = it.intern("show");
558 let lst = list_lit(&ea, vec![num(&ea, 5), num(&ea, 6), num(&ea, 7)]);
559 let stmts = vec![
560 letb(xs, lst),
561 show(&ea, show_s, index_at(&ea, idref(&ea, xs), num(&ea, 2))),
562 ];
563 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "6");
564 assert_vm_eq_treewalk(&stmts, &it);
565 }
566
567 #[test]
568 fn vm_range_to_list() {
569 let ea: Arena<Expr> = Arena::new();
571 let mut it = Interner::new();
572 let xs = it.intern("xs");
573 let show_s = it.intern("show");
574 let stmts = vec![
575 letb(xs, range_e(&ea, num(&ea, 1), num(&ea, 5))),
576 show(&ea, show_s, length_of(&ea, idref(&ea, xs))),
577 show(&ea, show_s, index_at(&ea, idref(&ea, xs), num(&ea, 3))),
578 ];
579 let out = compile_and_run(&stmts, &it).unwrap();
580 let lines: Vec<&str> = out.lines().collect();
581 assert_eq!(lines[0], "5");
582 assert_eq!(lines[1], "3");
583 assert_vm_eq_treewalk(&stmts, &it);
584 }
585
586 #[test]
587 fn vm_list_contains() {
588 let ea: Arena<Expr> = Arena::new();
590 let mut it = Interner::new();
591 let xs = it.intern("xs");
592 let show_s = it.intern("show");
593 let lst = list_lit(&ea, vec![num(&ea, 1), num(&ea, 2), num(&ea, 3)]);
594 let stmts = vec![
595 letb(xs, lst),
596 show(&ea, show_s, contains_e(&ea, idref(&ea, xs), num(&ea, 2))),
597 show(&ea, show_s, contains_e(&ea, idref(&ea, xs), num(&ea, 5))),
598 ];
599 let out = compile_and_run(&stmts, &it).unwrap();
600 let lines: Vec<&str> = out.lines().collect();
601 assert_eq!(lines[0], "true");
602 assert_eq!(lines[1], "false");
603 assert_vm_eq_treewalk(&stmts, &it);
604 }
605
606 #[test]
607 fn vm_push_then_index_sum_loop() {
608 let ea: Arena<Expr> = Arena::new();
610 let sa: Arena<Stmt> = Arena::new();
611 let mut it = Interner::new();
612 let xs = it.intern("xs");
613 let seq = it.intern("Seq");
614 let total = it.intern("total");
615 let i = it.intern("i");
616 let show_s = it.intern("show");
617
618 let cond = bin(&ea, BinaryOpKind::LtEq, idref(&ea, i), length_of(&ea, idref(&ea, xs)));
619 let elem = index_at(&ea, idref(&ea, xs), idref(&ea, i));
620 let body: &[Stmt] = sa.alloc_slice(vec![
621 Stmt::Set { target: total, value: bin(&ea, BinaryOpKind::Add, idref(&ea, total), elem) },
622 Stmt::Set { target: i, value: bin(&ea, BinaryOpKind::Add, idref(&ea, i), num(&ea, 1)) },
623 ]);
624
625 let stmts = vec![
626 Stmt::Let { var: xs, ty: None, value: new_coll(&ea, seq), mutable: true },
627 push_to(num(&ea, 2), idref(&ea, xs)),
628 push_to(num(&ea, 4), idref(&ea, xs)),
629 push_to(num(&ea, 6), idref(&ea, xs)),
630 Stmt::Let { var: total, ty: None, value: num(&ea, 0), mutable: true },
631 Stmt::Let { var: i, ty: None, value: num(&ea, 1), mutable: true },
632 Stmt::While { cond, body, decreasing: None },
633 show(&ea, show_s, idref(&ea, total)),
634 ];
635 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "12");
636 assert_vm_eq_treewalk(&stmts, &it);
637 }
638
639 #[test]
640 fn vm_index_out_of_bounds_errors_like_treewalk() {
641 let ea: Arena<Expr> = Arena::new();
643 let mut it = Interner::new();
644 let xs = it.intern("xs");
645 let show_s = it.intern("show");
646 let lst = list_lit(&ea, vec![num(&ea, 1), num(&ea, 2)]);
647 let stmts = vec![
648 letb(xs, lst),
649 show(&ea, show_s, index_at(&ea, idref(&ea, xs), num(&ea, 5))),
650 ];
651 assert!(compile_and_run(&stmts, &it).is_err(), "VM should error on OOB index");
652 assert!(run_treewalk(&stmts, &it).is_err(), "tree-walker should error on OOB index");
653 }
654
655 fn text<'a>(ea: &'a Arena<Expr<'a>>, sym: Symbol) -> &'a Expr<'a> {
656 ea.alloc(Expr::Literal(Literal::Text(sym)))
657 }
658
659 #[test]
660 fn vm_set_add_dedups() {
661 let ea: Arena<Expr> = Arena::new();
663 let mut it = Interner::new();
664 let s = it.intern("s");
665 let set_ty = it.intern("Set");
666 let show_s = it.intern("show");
667 let stmts = vec![
668 Stmt::Let { var: s, ty: None, value: new_coll(&ea, set_ty), mutable: true },
669 Stmt::Add { value: num(&ea, 1), collection: idref(&ea, s) },
670 Stmt::Add { value: num(&ea, 2), collection: idref(&ea, s) },
671 Stmt::Add { value: num(&ea, 1), collection: idref(&ea, s) },
672 show(&ea, show_s, length_of(&ea, idref(&ea, s))),
673 ];
674 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "2");
675 assert_vm_eq_treewalk(&stmts, &it);
676 }
677
678 #[test]
679 fn vm_set_contains_and_remove() {
680 let ea: Arena<Expr> = Arena::new();
682 let mut it = Interner::new();
683 let s = it.intern("s");
684 let set_ty = it.intern("Set");
685 let show_s = it.intern("show");
686 let stmts = vec![
687 Stmt::Let { var: s, ty: None, value: new_coll(&ea, set_ty), mutable: true },
688 Stmt::Add { value: num(&ea, 1), collection: idref(&ea, s) },
689 Stmt::Add { value: num(&ea, 2), collection: idref(&ea, s) },
690 Stmt::Add { value: num(&ea, 3), collection: idref(&ea, s) },
691 Stmt::Remove { value: num(&ea, 2), collection: idref(&ea, s) },
692 show(&ea, show_s, length_of(&ea, idref(&ea, s))),
693 show(&ea, show_s, contains_e(&ea, idref(&ea, s), num(&ea, 2))),
694 show(&ea, show_s, contains_e(&ea, idref(&ea, s), num(&ea, 3))),
695 ];
696 let out = compile_and_run(&stmts, &it).unwrap();
697 let lines: Vec<&str> = out.lines().collect();
698 assert_eq!(lines, vec!["2", "false", "true"]);
699 assert_vm_eq_treewalk(&stmts, &it);
700 }
701
702 #[test]
703 fn vm_map_set_get_length() {
704 let ea: Arena<Expr> = Arena::new();
707 let mut it = Interner::new();
708 let m = it.intern("m");
709 let map_ty = it.intern("Map");
710 let key_a = it.intern("a");
711 let key_b = it.intern("b");
712 let show_s = it.intern("show");
713 let stmts = vec![
714 Stmt::Let { var: m, ty: None, value: new_coll(&ea, map_ty), mutable: true },
715 Stmt::SetIndex { collection: idref(&ea, m), index: text(&ea, key_a), value: num(&ea, 10) },
716 Stmt::SetIndex { collection: idref(&ea, m), index: text(&ea, key_b), value: num(&ea, 20) },
717 show(&ea, show_s, index_at(&ea, idref(&ea, m), text(&ea, key_a))),
718 show(&ea, show_s, length_of(&ea, idref(&ea, m))),
719 ];
720 let out = compile_and_run(&stmts, &it).unwrap();
721 let lines: Vec<&str> = out.lines().collect();
722 assert_eq!(lines[0], "10");
723 assert_eq!(lines[1], "2");
724 assert_vm_eq_treewalk(&stmts, &it);
725 }
726
727 #[test]
728 fn vm_map_overwrite_and_remove() {
729 let ea: Arena<Expr> = Arena::new();
732 let mut it = Interner::new();
733 let m = it.intern("m");
734 let map_ty = it.intern("Map");
735 let key_a = it.intern("a");
736 let key_b = it.intern("b");
737 let show_s = it.intern("show");
738 let stmts = vec![
739 Stmt::Let { var: m, ty: None, value: new_coll(&ea, map_ty), mutable: true },
740 Stmt::SetIndex { collection: idref(&ea, m), index: text(&ea, key_a), value: num(&ea, 1) },
741 Stmt::SetIndex { collection: idref(&ea, m), index: text(&ea, key_a), value: num(&ea, 9) },
742 Stmt::SetIndex { collection: idref(&ea, m), index: text(&ea, key_b), value: num(&ea, 2) },
743 Stmt::Remove { value: text(&ea, key_a), collection: idref(&ea, m) },
744 show(&ea, show_s, length_of(&ea, idref(&ea, m))),
745 show(&ea, show_s, index_at(&ea, idref(&ea, m), text(&ea, key_b))),
746 ];
747 let out = compile_and_run(&stmts, &it).unwrap();
748 let lines: Vec<&str> = out.lines().collect();
749 assert_eq!(lines[0], "1");
750 assert_eq!(lines[1], "2");
751 assert_vm_eq_treewalk(&stmts, &it);
752 }
753
754 #[test]
755 fn vm_list_set_index_one_based() {
756 let ea: Arena<Expr> = Arena::new();
758 let mut it = Interner::new();
759 let xs = it.intern("xs");
760 let show_s = it.intern("show");
761 let lst = list_lit(&ea, vec![num(&ea, 1), num(&ea, 2), num(&ea, 3)]);
762 let stmts = vec![
763 Stmt::Let { var: xs, ty: None, value: lst, mutable: true },
764 Stmt::SetIndex { collection: idref(&ea, xs), index: num(&ea, 2), value: num(&ea, 99) },
765 show(&ea, show_s, index_at(&ea, idref(&ea, xs), num(&ea, 2))),
766 ];
767 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "99");
768 assert_vm_eq_treewalk(&stmts, &it);
769 }
770
771 struct SplitMix64 {
779 state: u64,
780 }
781 impl SplitMix64 {
782 fn new(seed: u64) -> Self {
783 SplitMix64 { state: seed.wrapping_add(0x9E37_79B9_7F4A_7C15) }
784 }
785 fn next_u64(&mut self) -> u64 {
786 self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15);
787 let mut z = self.state;
788 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
789 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
790 z ^ (z >> 31)
791 }
792 fn below(&mut self, n: u64) -> u64 {
793 self.next_u64() % n
794 }
795 }
796
797 fn gen_atom<'a>(ea: &'a Arena<Expr<'a>>, rng: &mut SplitMix64, vars: &[Symbol]) -> &'a Expr<'a> {
798 if rng.below(2) == 0 {
799 num(ea, rng.below(6) as i64)
800 } else {
801 idref(ea, vars[rng.below(vars.len() as u64) as usize])
802 }
803 }
804
805 fn gen_arith<'a>(ea: &'a Arena<Expr<'a>>, rng: &mut SplitMix64, vars: &[Symbol]) -> &'a Expr<'a> {
809 match rng.below(8) {
810 0 | 1 | 2 => {
811 let l = gen_atom(ea, rng, vars);
812 let r = gen_atom(ea, rng, vars);
813 bin(ea, BinaryOpKind::Add, l, r)
814 }
815 3 | 4 => {
816 let l = gen_atom(ea, rng, vars);
817 let r = gen_atom(ea, rng, vars);
818 bin(ea, BinaryOpKind::Subtract, l, r)
819 }
820 5 => {
821 let l = gen_atom(ea, rng, vars);
822 bin(ea, BinaryOpKind::Multiply, l, num(ea, rng.below(4) as i64))
823 }
824 6 => {
825 let l = gen_atom(ea, rng, vars);
826 bin(ea, BinaryOpKind::Divide, l, num(ea, 1 + rng.below(5) as i64))
827 }
828 _ => {
829 let l = gen_atom(ea, rng, vars);
830 bin(ea, BinaryOpKind::Modulo, l, num(ea, 1 + rng.below(5) as i64))
831 }
832 }
833 }
834
835 fn gen_cmp<'a>(ea: &'a Arena<Expr<'a>>, rng: &mut SplitMix64, vars: &[Symbol]) -> &'a Expr<'a> {
836 let l = idref(ea, vars[rng.below(vars.len() as u64) as usize]);
837 let r = num(ea, rng.below(6) as i64);
838 let op = match rng.below(6) {
839 0 => BinaryOpKind::Lt,
840 1 => BinaryOpKind::Gt,
841 2 => BinaryOpKind::LtEq,
842 3 => BinaryOpKind::GtEq,
843 4 => BinaryOpKind::Eq,
844 _ => BinaryOpKind::NotEq,
845 };
846 bin(ea, op, l, r)
847 }
848
849 fn gen_program<'a>(
850 seed: u64,
851 ea: &'a Arena<Expr<'a>>,
852 sa: &'a Arena<Stmt<'a>>,
853 vars: &[Symbol],
854 show_s: Symbol,
855 ) -> Vec<Stmt<'a>> {
856 let mut rng = SplitMix64::new(seed);
857 let mut stmts: Vec<Stmt> = Vec::new();
858 for &v in vars {
859 stmts.push(Stmt::Let { var: v, ty: None, value: num(ea, rng.below(6) as i64), mutable: true });
860 }
861 let m = 4 + rng.below(8);
862 for _ in 0..m {
863 match rng.below(10) {
864 0..=5 => {
865 let v = vars[rng.below(vars.len() as u64) as usize];
866 stmts.push(Stmt::Set { target: v, value: gen_arith(ea, &mut rng, vars) });
867 }
868 6..=7 => {
869 let cond = gen_cmp(ea, &mut rng, vars);
870 let v = vars[rng.below(vars.len() as u64) as usize];
871 let then_blk: &[Stmt] =
872 sa.alloc_slice(vec![Stmt::Set { target: v, value: gen_arith(ea, &mut rng, vars) }]);
873 if rng.below(2) == 0 {
874 let v2 = vars[rng.below(vars.len() as u64) as usize];
875 let else_blk: &[Stmt] =
876 sa.alloc_slice(vec![Stmt::Set { target: v2, value: gen_arith(ea, &mut rng, vars) }]);
877 stmts.push(Stmt::If { cond, then_block: then_blk, else_block: Some(else_blk) });
878 } else {
879 stmts.push(Stmt::If { cond, then_block: then_blk, else_block: None });
880 }
881 }
882 _ => {
883 let li = rng.below(vars.len() as u64) as usize;
886 let loop_var = vars[li];
887 let other = vars[(li + 1 + rng.below(vars.len() as u64 - 1) as usize) % vars.len()];
888 let bound = 2 + rng.below(4);
889 let cond = bin(ea, BinaryOpKind::Lt, idref(ea, loop_var), num(ea, bound as i64));
890 let body: &[Stmt] = sa.alloc_slice(vec![
891 Stmt::Set { target: other, value: gen_arith(ea, &mut rng, vars) },
892 Stmt::Set {
893 target: loop_var,
894 value: bin(ea, BinaryOpKind::Add, idref(ea, loop_var), num(ea, 1)),
895 },
896 ]);
897 stmts.push(Stmt::While { cond, body, decreasing: None });
898 }
899 }
900 }
901 for &v in vars {
902 stmts.push(show(ea, show_s, idref(ea, v)));
903 }
904 stmts
905 }
906
907 #[test]
908 fn vm_differential_fuzz_300_programs() {
909 for seed in 0..300u64 {
910 let ea: Arena<Expr> = Arena::new();
911 let sa: Arena<Stmt> = Arena::new();
912 let mut it = Interner::new();
913 let show_s = it.intern("show");
914 let vars: Vec<Symbol> = (0..4u32).map(|k| it.intern(&format!("v{k}"))).collect();
915 let stmts = gen_program(seed, &ea, &sa, &vars, show_s);
916
917 let vm = compile_and_run(&stmts, &it);
918 let tw = run_treewalk(&stmts, &it);
919 match (vm, tw) {
920 (Ok(a), Ok(b)) => assert_eq!(
921 normalize(&a),
922 normalize(&b),
923 "seed {} diverged:\nVM:\n{}\nTREE-WALKER:\n{}",
924 seed, a, b
925 ),
926 (Err(_), Err(_)) => {}
927 (a, b) => panic!("seed {} one engine errored: vm={:?} tw={:?}", seed, a, b),
928 }
929 }
930 }
931
932 #[test]
933 fn vm_differential_fuzz_functions() {
934 for seed in 0..150u64 {
935 let ea: Arena<Expr> = Arena::new();
936 let sa: Arena<Stmt> = Arena::new();
937 let ta: Arena<TypeExpr> = Arena::new();
938 let mut it = Interner::new();
939 let show_s = it.intern("show");
940 let int_ty = ta.alloc(TypeExpr::Primitive(it.intern("Int")));
941 let mut rng = SplitMix64::new(seed ^ 0xF00D);
942
943 let f0 = it.intern("f0");
944 let f1 = it.intern("f1");
945 let pa = it.intern("pa");
946 let pb = it.intern("pb");
947 let params = [pa, pb];
948 let body0: &[Stmt] = sa.alloc_slice(vec![ret(gen_arith(&ea, &mut rng, ¶ms))]);
951 let body1: &[Stmt] = sa.alloc_slice(vec![ret(gen_arith(&ea, &mut rng, ¶ms))]);
952 let mut stmts = vec![
953 fndef(f0, vec![(pa, int_ty), (pb, int_ty)], body0),
954 fndef(f1, vec![(pa, int_ty), (pb, int_ty)], body1),
955 ];
956 let calls = 3 + rng.below(4);
957 for _ in 0..calls {
958 let f = if rng.below(2) == 0 { f0 } else { f1 };
959 let a = num(&ea, rng.below(6) as i64);
960 let b = num(&ea, 1 + rng.below(5) as i64);
961 stmts.push(show(&ea, show_s, calle(&ea, f, vec![a, b])));
962 }
963
964 let vm = compile_and_run(&stmts, &it);
965 let tw = run_treewalk(&stmts, &it);
966 match (vm, tw) {
967 (Ok(a), Ok(b)) => assert_eq!(normalize(&a), normalize(&b), "fn seed {} diverged", seed),
968 (Err(_), Err(_)) => {}
969 (a, b) => panic!("fn seed {} one engine errored: vm={:?} tw={:?}", seed, a, b),
970 }
971 }
972 }
973
974 fn gen_float_lit<'a>(ea: &'a Arena<Expr<'a>>, rng: &mut SplitMix64) -> &'a Expr<'a> {
975 ea.alloc(Expr::Literal(Literal::Float(rng.below(10) as f64 * 0.5)))
976 }
977 fn gen_float_atom<'a>(ea: &'a Arena<Expr<'a>>, rng: &mut SplitMix64, vars: &[Symbol]) -> &'a Expr<'a> {
978 if rng.below(2) == 0 {
979 gen_float_lit(ea, rng)
980 } else {
981 idref(ea, vars[rng.below(vars.len() as u64) as usize])
982 }
983 }
984 fn gen_float_arith<'a>(ea: &'a Arena<Expr<'a>>, rng: &mut SplitMix64, vars: &[Symbol]) -> &'a Expr<'a> {
987 let l = gen_float_atom(ea, rng, vars);
988 let r = gen_float_atom(ea, rng, vars);
989 let op = match rng.below(3) {
990 0 => BinaryOpKind::Add,
991 1 => BinaryOpKind::Subtract,
992 _ => BinaryOpKind::Multiply,
993 };
994 bin(ea, op, l, r)
995 }
996
997 #[test]
998 fn vm_differential_fuzz_floats() {
999 for seed in 0..200u64 {
1000 let ea: Arena<Expr> = Arena::new();
1001 let sa: Arena<Stmt> = Arena::new();
1002 let mut it = Interner::new();
1003 let show_s = it.intern("show");
1004 let vars: Vec<Symbol> = (0..3u32).map(|k| it.intern(&format!("f{k}"))).collect();
1005 let mut rng = SplitMix64::new(seed ^ 0xBEEF);
1006
1007 let mut stmts: Vec<Stmt> = Vec::new();
1008 for &v in &vars {
1009 stmts.push(Stmt::Let { var: v, ty: None, value: gen_float_lit(&ea, &mut rng), mutable: true });
1010 }
1011 let m = 4 + rng.below(6);
1012 for _ in 0..m {
1013 if rng.below(8) < 5 {
1014 let v = vars[rng.below(vars.len() as u64) as usize];
1015 stmts.push(Stmt::Set { target: v, value: gen_float_arith(&ea, &mut rng, &vars) });
1016 } else {
1017 let l = idref(&ea, vars[rng.below(vars.len() as u64) as usize]);
1018 let r = idref(&ea, vars[rng.below(vars.len() as u64) as usize]);
1019 let op = match rng.below(4) {
1020 0 => BinaryOpKind::Lt,
1021 1 => BinaryOpKind::Gt,
1022 2 => BinaryOpKind::LtEq,
1023 _ => BinaryOpKind::GtEq,
1024 };
1025 let cond = bin(&ea, op, l, r);
1026 let v = vars[rng.below(vars.len() as u64) as usize];
1027 let then_blk: &[Stmt] =
1028 sa.alloc_slice(vec![Stmt::Set { target: v, value: gen_float_arith(&ea, &mut rng, &vars) }]);
1029 stmts.push(Stmt::If { cond, then_block: then_blk, else_block: None });
1030 }
1031 }
1032 for &v in &vars {
1033 stmts.push(show(&ea, show_s, idref(&ea, v)));
1034 }
1035
1036 let vm = compile_and_run(&stmts, &it);
1037 let tw = run_treewalk(&stmts, &it);
1038 match (vm, tw) {
1039 (Ok(a), Ok(b)) => assert_eq!(normalize(&a), normalize(&b), "float seed {} diverged", seed),
1040 (Err(_), Err(_)) => {}
1041 (a, b) => panic!("float seed {} one engine errored: vm={:?} tw={:?}", seed, a, b),
1042 }
1043 }
1044 }
1045
1046 #[test]
1047 fn vm_function_mutual_recursion() {
1048 let ea: Arena<Expr> = Arena::new();
1052 let sa: Arena<Stmt> = Arena::new();
1053 let ta: Arena<TypeExpr> = Arena::new();
1054 let mut it = Interner::new();
1055 let is_even = it.intern("isEven");
1056 let is_odd = it.intern("isOdd");
1057 let n = it.intern("n");
1058 let r = it.intern("r");
1059 let show_s = it.intern("show");
1060 let int_ty = ta.alloc(TypeExpr::Primitive(it.intern("Int")));
1061
1062 let even_then: &[Stmt] = sa.alloc_slice(vec![ret(num(&ea, 1))]);
1063 let even_body: &[Stmt] = sa.alloc_slice(vec![
1064 Stmt::If { cond: bin(&ea, BinaryOpKind::Eq, idref(&ea, n), num(&ea, 0)), then_block: even_then, else_block: None },
1065 ret(calle(&ea, is_odd, vec![bin(&ea, BinaryOpKind::Subtract, idref(&ea, n), num(&ea, 1))])),
1066 ]);
1067 let odd_then: &[Stmt] = sa.alloc_slice(vec![ret(num(&ea, 0))]);
1068 let odd_body: &[Stmt] = sa.alloc_slice(vec![
1069 Stmt::If { cond: bin(&ea, BinaryOpKind::Eq, idref(&ea, n), num(&ea, 0)), then_block: odd_then, else_block: None },
1070 ret(calle(&ea, is_even, vec![bin(&ea, BinaryOpKind::Subtract, idref(&ea, n), num(&ea, 1))])),
1071 ]);
1072 let stmts = vec![
1073 fndef(is_even, vec![(n, int_ty)], even_body),
1074 fndef(is_odd, vec![(n, int_ty)], odd_body),
1075 letb(r, calle(&ea, is_even, vec![num(&ea, 4)])),
1076 show(&ea, show_s, idref(&ea, r)),
1077 ];
1078 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "1");
1079 assert_vm_eq_treewalk(&stmts, &it);
1080 }
1081
1082 #[test]
1083 fn vm_runs_arithmetic_let_show() {
1084 let ea: Arena<Expr> = Arena::new();
1086 let mut it = Interner::new();
1087 let x = it.intern("x");
1088 let y = it.intern("y");
1089 let console = it.intern("show");
1090
1091 let five = ea.alloc(Expr::Literal(Literal::Number(5)));
1092 let xref = ea.alloc(Expr::Identifier(x));
1093 let seven = ea.alloc(Expr::Literal(Literal::Number(7)));
1094 let sum = ea.alloc(Expr::BinaryOp { op: BinaryOpKind::Add, left: xref, right: seven });
1095 let yref = ea.alloc(Expr::Identifier(y));
1096 let console_ref = ea.alloc(Expr::Identifier(console));
1097
1098 let stmts = vec![
1099 Stmt::Let { var: x, ty: None, value: five, mutable: false },
1100 Stmt::Let { var: y, ty: None, value: sum, mutable: false },
1101 Stmt::Show { object: yref, recipient: console_ref },
1102 ];
1103
1104 let out = compile_and_run(&stmts, &it).unwrap();
1105 assert_eq!(out.trim(), "12");
1106 assert_vm_eq_treewalk(&stmts, &it);
1107 }
1108
1109 #[test]
1110 fn vm_arithmetic_and_comparison_chain() {
1111 let ea: Arena<Expr> = Arena::new();
1114 let mut it = Interner::new();
1115 let a = it.intern("a");
1116 let b = it.intern("b");
1117 let c = it.intern("c");
1118 let d = it.intern("d");
1119 let console = it.intern("show");
1120
1121 let six = ea.alloc(Expr::Literal(Literal::Number(6)));
1122 let aref = ea.alloc(Expr::Identifier(a));
1123 let four = ea.alloc(Expr::Literal(Literal::Number(4)));
1124 let mul = ea.alloc(Expr::BinaryOp { op: BinaryOpKind::Multiply, left: aref, right: four });
1125 let bref = ea.alloc(Expr::Identifier(b));
1126 let two = ea.alloc(Expr::Literal(Literal::Number(2)));
1127 let subx = ea.alloc(Expr::BinaryOp { op: BinaryOpKind::Subtract, left: bref, right: two });
1128 let cref1 = ea.alloc(Expr::Identifier(c));
1129 let cref2 = ea.alloc(Expr::Identifier(c));
1130 let twenty = ea.alloc(Expr::Literal(Literal::Number(20)));
1131 let gt = ea.alloc(Expr::BinaryOp { op: BinaryOpKind::Gt, left: cref2, right: twenty });
1132 let dref = ea.alloc(Expr::Identifier(d));
1133 let console_ref1 = ea.alloc(Expr::Identifier(console));
1134 let console_ref2 = ea.alloc(Expr::Identifier(console));
1135
1136 let stmts = vec![
1137 Stmt::Let { var: a, ty: None, value: six, mutable: false },
1138 Stmt::Let { var: b, ty: None, value: mul, mutable: false },
1139 Stmt::Let { var: c, ty: None, value: subx, mutable: false },
1140 Stmt::Show { object: cref1, recipient: console_ref1 },
1141 Stmt::Let { var: d, ty: None, value: gt, mutable: false },
1142 Stmt::Show { object: dref, recipient: console_ref2 },
1143 ];
1144
1145 let out = compile_and_run(&stmts, &it).unwrap();
1146 let lines: Vec<&str> = out.lines().collect();
1147 assert_eq!(lines[0], "22");
1148 assert_eq!(lines[1], "true");
1149 assert_vm_eq_treewalk(&stmts, &it);
1150 }
1151
1152 #[test]
1153 fn vm_while_loop_sums_to_15() {
1154 let ea: Arena<Expr> = Arena::new();
1158 let sa: Arena<Stmt> = Arena::new();
1159 let mut it = Interner::new();
1160 let total = it.intern("total");
1161 let i = it.intern("i");
1162 let console = it.intern("show");
1163
1164 let zero = ea.alloc(Expr::Literal(Literal::Number(0)));
1165 let one_init = ea.alloc(Expr::Literal(Literal::Number(1)));
1166
1167 let i_c = ea.alloc(Expr::Identifier(i));
1169 let five = ea.alloc(Expr::Literal(Literal::Number(5)));
1170 let cond = ea.alloc(Expr::BinaryOp { op: BinaryOpKind::LtEq, left: i_c, right: five });
1171
1172 let total_l = ea.alloc(Expr::Identifier(total));
1174 let i_r = ea.alloc(Expr::Identifier(i));
1175 let tplus = ea.alloc(Expr::BinaryOp { op: BinaryOpKind::Add, left: total_l, right: i_r });
1176 let i_l = ea.alloc(Expr::Identifier(i));
1178 let one = ea.alloc(Expr::Literal(Literal::Number(1)));
1179 let iplus = ea.alloc(Expr::BinaryOp { op: BinaryOpKind::Add, left: i_l, right: one });
1180 let body: &[Stmt] = sa.alloc_slice(vec![
1181 Stmt::Set { target: total, value: tplus },
1182 Stmt::Set { target: i, value: iplus },
1183 ]);
1184
1185 let total_show = ea.alloc(Expr::Identifier(total));
1186 let console_ref = ea.alloc(Expr::Identifier(console));
1187
1188 let stmts = vec![
1189 Stmt::Let { var: total, ty: None, value: zero, mutable: true },
1190 Stmt::Let { var: i, ty: None, value: one_init, mutable: true },
1191 Stmt::While { cond, body, decreasing: None },
1192 Stmt::Show { object: total_show, recipient: console_ref },
1193 ];
1194
1195 let out = compile_and_run(&stmts, &it).unwrap();
1196 assert_eq!(out.trim(), "15");
1197 assert_vm_eq_treewalk(&stmts, &it);
1198 }
1199
1200 fn assert_err_parity(stmts: &[Stmt], interner: &Interner) {
1206 let vm_err = compile_and_run(stmts, interner)
1207 .expect_err("VM should error");
1208 let tw_err = run_treewalk(stmts, interner)
1209 .expect_err("tree-walker should error");
1210 assert_eq!(vm_err, tw_err, "error strings diverged");
1211 }
1212
1213 fn boolean<'a>(ea: &'a Arena<Expr<'a>>, b: bool) -> &'a Expr<'a> {
1214 ea.alloc(Expr::Literal(Literal::Boolean(b)))
1215 }
1216 fn nothing_lit<'a>(ea: &'a Arena<Expr<'a>>) -> &'a Expr<'a> {
1217 ea.alloc(Expr::Literal(Literal::Nothing))
1218 }
1219
1220 #[test]
1221 fn vm_add_type_error_matches_treewalk() {
1222 let ea: Arena<Expr> = Arena::new();
1224 let mut it = Interner::new();
1225 let x = it.intern("x");
1226 let stmts = vec![letb(x, bin(&ea, BinaryOpKind::Add, boolean(&ea, true), nothing_lit(&ea)))];
1227 assert_err_parity(&stmts, &it);
1228 }
1229
1230 #[test]
1231 fn vm_subtract_type_error_matches_treewalk() {
1232 let ea: Arena<Expr> = Arena::new();
1234 let mut it = Interner::new();
1235 let x = it.intern("x");
1236 let stmts = vec![letb(x, bin(&ea, BinaryOpKind::Subtract, boolean(&ea, true), nothing_lit(&ea)))];
1237 assert_err_parity(&stmts, &it);
1238 }
1239
1240 #[test]
1241 fn vm_multiply_type_error_matches_treewalk() {
1242 let ea: Arena<Expr> = Arena::new();
1243 let mut it = Interner::new();
1244 let x = it.intern("x");
1245 let stmts = vec![letb(x, bin(&ea, BinaryOpKind::Multiply, boolean(&ea, true), nothing_lit(&ea)))];
1246 assert_err_parity(&stmts, &it);
1247 }
1248
1249 #[test]
1250 fn vm_divide_and_modulo_zero_messages_match_treewalk() {
1251 let ea: Arena<Expr> = Arena::new();
1253 let mut it = Interner::new();
1254 let x = it.intern("x");
1255 let stmts = vec![letb(x, bin(&ea, BinaryOpKind::Divide, num(&ea, 1), num(&ea, 0)))];
1256 assert_err_parity(&stmts, &it);
1257
1258 let y = it.intern("y");
1259 let stmts = vec![letb(y, bin(&ea, BinaryOpKind::Modulo, num(&ea, 1), num(&ea, 0)))];
1260 assert_err_parity(&stmts, &it);
1261 }
1262
1263 #[test]
1264 fn vm_comparison_type_error_matches_treewalk() {
1265 let ea: Arena<Expr> = Arena::new();
1267 let mut it = Interner::new();
1268 let x = it.intern("x");
1269 let stmts = vec![letb(x, bin(&ea, BinaryOpKind::Lt, boolean(&ea, true), num(&ea, 1)))];
1270 assert_err_parity(&stmts, &it);
1271 }
1272
1273 #[test]
1274 fn vm_index_and_length_type_errors_match_treewalk() {
1275 let ea: Arena<Expr> = Arena::new();
1277 let mut it = Interner::new();
1278 let x = it.intern("x");
1279 let stmts = vec![letb(x, index_at(&ea, num(&ea, 1), num(&ea, 1)))];
1280 assert_err_parity(&stmts, &it);
1281
1282 let y = it.intern("y");
1283 let stmts = vec![letb(y, length_of(&ea, boolean(&ea, true)))];
1284 assert_err_parity(&stmts, &it);
1285 }
1286
1287 fn fuzz_one(seed: u64, features: super::fuzz::FeatureSet) -> Option<String> {
1292 let ea: Arena<Expr> = Arena::new();
1293 let sa: Arena<Stmt> = Arena::new();
1294 let ta: Arena<TypeExpr> = Arena::new();
1295 let mut it = Interner::new();
1296 let generated = super::fuzz::generate(seed, features, &ea, &sa, &ta, &mut it);
1297
1298 let (vm_out, vm_err) = run_to_outcome(&generated.stmts, &it, None, None);
1299 let (tw_out, tw_err) = run_treewalk_outcome(&generated.stmts, &it);
1300 if normalize(&vm_out) != normalize(&tw_out) || vm_err != tw_err {
1301 return Some(format!(
1302 "SEED={} FEATURES={:#x}\nvm out:\n{}\nvm err: {:?}\ntw out:\n{}\ntw err: {:?}",
1303 seed, features.0, vm_out, vm_err, tw_out, tw_err
1304 ));
1305 }
1306 None
1307 }
1308
1309 #[test]
1310 fn fuzz_generator_is_deterministic() {
1311 for seed in [0u64, 42, 1234] {
1313 let f = super::fuzz::FeatureSet::all_supported();
1314 let run = |seed| {
1315 let ea: Arena<Expr> = Arena::new();
1316 let sa: Arena<Stmt> = Arena::new();
1317 let ta: Arena<TypeExpr> = Arena::new();
1318 let mut it = Interner::new();
1319 let g = super::fuzz::generate(seed, f, &ea, &sa, &ta, &mut it);
1320 run_to_outcome(&g.stmts, &it, None, None)
1321 };
1322 assert_eq!(run(seed), run(seed), "seed {seed} not deterministic");
1323 }
1324 }
1325
1326 #[test]
1327 fn vm_fuzz_full_feature_differential() {
1328 let features = super::fuzz::FeatureSet::all_supported();
1330 for seed in 0..1500u64 {
1331 if let Some(divergence) = fuzz_one(seed, features) {
1332 panic!("fuzz divergence:\n{divergence}");
1333 }
1334 }
1335 }
1336
1337 #[test]
1338 fn vm_fuzz_error_injection_differential() {
1339 let features = super::fuzz::FeatureSet(
1342 super::fuzz::FeatureSet::all_supported().0 | super::fuzz::FeatureSet::ERROR_INJECTION,
1343 );
1344 for seed in 0..500u64 {
1345 if let Some(divergence) = fuzz_one(seed, features) {
1346 panic!("fuzz (error-injection) divergence:\n{divergence}");
1347 }
1348 }
1349 }
1350
1351 #[test]
1353 #[ignore]
1354 fn vm_fuzz_overnight() {
1355 let features = super::fuzz::FeatureSet::all_supported();
1356 let with_errors =
1357 super::fuzz::FeatureSet(features.0 | super::fuzz::FeatureSet::ERROR_INJECTION);
1358 for seed in 0..500_000u64 {
1359 if let Some(d) = fuzz_one(seed, features) {
1360 panic!("fuzz divergence:\n{d}");
1361 }
1362 if let Some(d) = fuzz_one(seed, with_errors) {
1363 panic!("fuzz divergence:\n{d}");
1364 }
1365 }
1366 }
1367
1368 #[test]
1371 fn vm_interpolated_string_spec_matrix() {
1372 use crate::ast::stmt::StringPart;
1373 let ea: Arena<Expr> = Arena::new();
1375 let mut it = Interner::new();
1376 let n = it.intern("n");
1377 let fv = it.intern("fv");
1378 let r = it.intern("r");
1379 let show_s = it.intern("show");
1380 let sep = it.intern(" | ");
1381 let spec_cur = it.intern("$");
1382 let spec_p1 = it.intern(".1");
1383 let spec_r5 = it.intern(">5");
1384 let spec_l5 = it.intern("<5");
1385 let spec_c5 = it.intern("^5");
1386
1387 let parts = vec![
1388 StringPart::Expr { value: idref(&ea, n), format_spec: None, debug: false },
1389 StringPart::Literal(sep),
1390 StringPart::Expr { value: idref(&ea, fv), format_spec: Some(spec_cur), debug: false },
1391 StringPart::Literal(sep),
1392 StringPart::Expr { value: idref(&ea, fv), format_spec: Some(spec_p1), debug: false },
1393 StringPart::Literal(sep),
1394 StringPart::Expr { value: idref(&ea, n), format_spec: Some(spec_r5), debug: false },
1395 StringPart::Literal(sep),
1396 StringPart::Expr { value: idref(&ea, n), format_spec: Some(spec_l5), debug: false },
1397 StringPart::Literal(sep),
1398 StringPart::Expr { value: idref(&ea, n), format_spec: Some(spec_c5), debug: false },
1399 StringPart::Literal(sep),
1400 StringPart::Expr { value: idref(&ea, n), format_spec: None, debug: true },
1401 ];
1402 let interp = ea.alloc(Expr::InterpolatedString(parts));
1403 let f25 = ea.alloc(Expr::Literal(Literal::Float(2.5)));
1404 let stmts = vec![
1405 letb(n, num(&ea, 42)),
1406 letb(fv, f25),
1407 letb(r, interp),
1408 show(&ea, show_s, idref(&ea, r)),
1409 ];
1410 let out = compile_and_run(&stmts, &it).unwrap();
1411 assert_eq!(out.trim_end(), "42 | $2.50 | 2.5 | 42 | 42 | 42 | n=42");
1412 assert_vm_eq_treewalk(&stmts, &it);
1413 }
1414
1415 #[test]
1416 fn vm_slice_copy_tuple_union_intersection() {
1417 let ea: Arena<Expr> = Arena::new();
1418 let mut it = Interner::new();
1419 let xs = it.intern("xs");
1420 let part = it.intern("part");
1421 let cp = it.intern("cp");
1422 let tup = it.intern("tup");
1423 let s1 = it.intern("s1");
1424 let s2 = it.intern("s2");
1425 let u = it.intern("u");
1426 let i_ = it.intern("i_");
1427 let set_ty = it.intern("Set");
1428 let show_s = it.intern("show");
1429
1430 let slice_e = ea.alloc(Expr::Slice {
1431 collection: idref(&ea, xs),
1432 start: num(&ea, 2),
1433 end: num(&ea, 4),
1434 });
1435 let copy_e = ea.alloc(Expr::Copy { expr: idref(&ea, xs) });
1436 let tuple_e = ea.alloc(Expr::Tuple(vec![num(&ea, 7), num(&ea, 8)]));
1437 let union_e = ea.alloc(Expr::Union { left: idref(&ea, s1), right: idref(&ea, s2) });
1438 let inter_e = ea.alloc(Expr::Intersection { left: idref(&ea, s1), right: idref(&ea, s2) });
1439
1440 let stmts = vec![
1441 Stmt::Let { var: xs, ty: None, value: list_lit(&ea, (1..=5).map(|k| num(&ea, k)).collect()), mutable: true },
1442 letb(part, slice_e),
1443 show(&ea, show_s, length_of(&ea, idref(&ea, part))),
1444 show(&ea, show_s, index_at(&ea, idref(&ea, part), num(&ea, 1))),
1445 letb(cp, copy_e),
1446 push_to(num(&ea, 9), idref(&ea, xs)),
1447 show(&ea, show_s, length_of(&ea, idref(&ea, cp))),
1448 letb(tup, tuple_e),
1449 show(&ea, show_s, index_at(&ea, idref(&ea, tup), num(&ea, 2))),
1450 show(&ea, show_s, length_of(&ea, idref(&ea, tup))),
1451 Stmt::Let { var: s1, ty: None, value: new_coll(&ea, set_ty), mutable: true },
1452 Stmt::Let { var: s2, ty: None, value: new_coll(&ea, set_ty), mutable: true },
1453 Stmt::Add { value: num(&ea, 1), collection: idref(&ea, s1) },
1454 Stmt::Add { value: num(&ea, 2), collection: idref(&ea, s1) },
1455 Stmt::Add { value: num(&ea, 2), collection: idref(&ea, s2) },
1456 Stmt::Add { value: num(&ea, 3), collection: idref(&ea, s2) },
1457 letb(u, union_e),
1458 show(&ea, show_s, length_of(&ea, idref(&ea, u))),
1459 letb(i_, inter_e),
1460 show(&ea, show_s, length_of(&ea, idref(&ea, i_))),
1461 ];
1462 let out = compile_and_run(&stmts, &it).unwrap();
1463 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["3", "2", "5", "8", "2", "3", "1"]);
1464 assert_vm_eq_treewalk(&stmts, &it);
1465 }
1466
1467 #[test]
1468 fn vm_option_some_none_match_treewalk() {
1469 let ea: Arena<Expr> = Arena::new();
1470 let mut it = Interner::new();
1471 let a = it.intern("a");
1472 let b = it.intern("b");
1473 let show_s = it.intern("show");
1474 let some_e = ea.alloc(Expr::OptionSome { value: num(&ea, 5) });
1475 let none_e = ea.alloc(Expr::OptionNone);
1476 let stmts = vec![
1477 letb(a, some_e),
1478 show(&ea, show_s, idref(&ea, a)),
1479 letb(b, none_e),
1480 show(&ea, show_s, idref(&ea, b)),
1481 ];
1482 let out = compile_and_run(&stmts, &it).unwrap();
1483 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["5", "nothing"]);
1484 assert_vm_eq_treewalk(&stmts, &it);
1485 }
1486
1487 #[test]
1488 fn vm_today_now_with_fixed_clock_match_treewalk() {
1489 crate::semantics::temporal::set_fixed_clock(19753, 1_700_000_000_000_000_000);
1493 let result = std::panic::catch_unwind(|| {
1494 let ea: Arena<Expr> = Arena::new();
1495 let mut it = Interner::new();
1496 let today_sym = it.intern("today");
1497 let a = it.intern("a");
1498 let b = it.intern("b");
1499 let show_s = it.intern("show");
1500 let stmts = vec![
1501 letb(a, idref(&ea, today_sym)),
1502 show(&ea, show_s, idref(&ea, a)),
1503 letb(b, idref(&ea, it.intern("now"))),
1504 show(&ea, show_s, idref(&ea, b)),
1505 letb(today_sym, num(&ea, 5)),
1506 show(&ea, show_s, idref(&ea, today_sym)),
1507 ];
1508 assert_vm_eq_treewalk(&stmts, &it);
1509 });
1510 crate::semantics::temporal::clear_fixed_clock();
1511 result.unwrap();
1512 }
1513
1514 #[test]
1515 fn vm_escape_expr_errors_like_treewalk() {
1516 let ea: Arena<Expr> = Arena::new();
1517 let mut it = Interner::new();
1518 let x = it.intern("x");
1519 let lang = it.intern("Rust");
1520 let code = it.intern("raw");
1521 let esc = ea.alloc(Expr::Escape { language: lang, code });
1522 let stmts = vec![letb(x, esc)];
1523 assert_err_parity(&stmts, &it);
1524 }
1525
1526 use crate::ast::stmt::ClosureBody;
1529
1530 fn closure_expr<'a>(
1531 ea: &'a Arena<Expr<'a>>,
1532 params: Vec<(Symbol, &'a TypeExpr<'a>)>,
1533 body: ClosureBody<'a>,
1534 ) -> &'a Expr<'a> {
1535 ea.alloc(Expr::Closure { params, body, return_type: None })
1536 }
1537 fn call_expr<'a>(
1538 ea: &'a Arena<Expr<'a>>,
1539 callee: &'a Expr<'a>,
1540 args: Vec<&'a Expr<'a>>,
1541 ) -> &'a Expr<'a> {
1542 ea.alloc(Expr::CallExpr { callee, args })
1543 }
1544
1545 #[test]
1546 fn vm_closure_capture_is_snapshot() {
1547 let ea: Arena<Expr> = Arena::new();
1550 let mut it = Interner::new();
1551 let x = it.intern("x");
1552 let get_x = it.intern("getX");
1553 let show_s = it.intern("show");
1554 let stmts = vec![
1555 Stmt::Let { var: x, ty: None, value: num(&ea, 10), mutable: true },
1556 letb(get_x, closure_expr(&ea, vec![], ClosureBody::Expression(idref(&ea, x)))),
1557 Stmt::Set { target: x, value: num(&ea, 999) },
1558 show(&ea, show_s, call_expr(&ea, idref(&ea, get_x), vec![])),
1559 ];
1560 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "10");
1561 assert_vm_eq_treewalk(&stmts, &it);
1562 }
1563
1564 #[test]
1565 fn vm_closure_capture_recloned_each_call() {
1566 let ea: Arena<Expr> = Arena::new();
1569 let sa: Arena<Stmt> = Arena::new();
1570 let mut it = Interner::new();
1571 let xs = it.intern("xs");
1572 let f = it.intern("f");
1573 let show_s = it.intern("show");
1574 let body: &[Stmt] = sa.alloc_slice(vec![
1575 push_to(num(&ea, 9), idref(&ea, xs)),
1576 show(&ea, show_s, length_of(&ea, idref(&ea, xs))),
1577 ret(num(&ea, 0)),
1578 ]);
1579 let stmts = vec![
1580 Stmt::Let { var: xs, ty: None, value: list_lit(&ea, vec![num(&ea, 1)]), mutable: true },
1581 letb(f, closure_expr(&ea, vec![], ClosureBody::Block(body))),
1582 Stmt::Call { function: f, args: vec![] },
1583 Stmt::Call { function: f, args: vec![] },
1584 show(&ea, show_s, length_of(&ea, idref(&ea, xs))),
1585 ];
1586 let out = compile_and_run(&stmts, &it).unwrap();
1587 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["2", "2", "1"]);
1589 assert_vm_eq_treewalk(&stmts, &it);
1590 }
1591
1592 #[test]
1593 fn vm_closure_sees_live_global_when_created_before_definition() {
1594 let ea: Arena<Expr> = Arena::new();
1597 let mut it = Interner::new();
1598 let g = it.intern("g");
1599 let f = it.intern("f");
1600 let show_s = it.intern("show");
1601 let stmts = vec![
1602 letb(f, closure_expr(&ea, vec![], ClosureBody::Expression(idref(&ea, g)))),
1603 Stmt::Let { var: g, ty: None, value: num(&ea, 5), mutable: true },
1604 show(&ea, show_s, call_expr(&ea, idref(&ea, f), vec![])),
1605 Stmt::Set { target: g, value: num(&ea, 7) },
1606 show(&ea, show_s, call_expr(&ea, idref(&ea, f), vec![])),
1607 ];
1608 let out = compile_and_run(&stmts, &it).unwrap();
1609 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["5", "7"]);
1610 assert_vm_eq_treewalk(&stmts, &it);
1611 }
1612
1613 #[test]
1614 fn vm_function_reads_and_writes_global_live() {
1615 let ea: Arena<Expr> = Arena::new();
1617 let sa: Arena<Stmt> = Arena::new();
1618 let mut it = Interner::new();
1619 let g = it.intern("g");
1620 let reader = it.intern("reader");
1621 let writer = it.intern("writer");
1622 let show_s = it.intern("show");
1623 let reader_body: &[Stmt] = sa.alloc_slice(vec![ret(idref(&ea, g))]);
1624 let writer_body: &[Stmt] = sa.alloc_slice(vec![
1625 Stmt::Set { target: g, value: num(&ea, 42) },
1626 ret(num(&ea, 0)),
1627 ]);
1628 let stmts = vec![
1629 fndef(reader, vec![], reader_body),
1630 fndef(writer, vec![], writer_body),
1631 Stmt::Let { var: g, ty: None, value: num(&ea, 1), mutable: true },
1632 show(&ea, show_s, calle(&ea, reader, vec![])),
1633 Stmt::Set { target: g, value: num(&ea, 2) },
1634 show(&ea, show_s, calle(&ea, reader, vec![])),
1635 Stmt::Call { function: writer, args: vec![] },
1636 show(&ea, show_s, idref(&ea, g)),
1637 ];
1638 let out = compile_and_run(&stmts, &it).unwrap();
1639 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["1", "2", "42"]);
1640 assert_vm_eq_treewalk(&stmts, &it);
1641 }
1642
1643 #[test]
1644 fn vm_main_block_var_invisible_to_function() {
1645 let ea: Arena<Expr> = Arena::new();
1649 let sa: Arena<Stmt> = Arena::new();
1650 let mut it = Interner::new();
1651 let t = it.intern("t");
1652 let f = it.intern("f");
1653 let show_s = it.intern("show");
1654 let f_body: &[Stmt] = sa.alloc_slice(vec![ret(idref(&ea, t))]);
1655 let then_blk: &[Stmt] = sa.alloc_slice(vec![
1656 letb(t, num(&ea, 5)),
1657 show(&ea, show_s, calle(&ea, f, vec![])),
1658 ]);
1659 let stmts = vec![
1660 fndef(f, vec![], f_body),
1661 show(&ea, show_s, num(&ea, 1)),
1662 Stmt::If { cond: boolean(&ea, true), then_block: then_blk, else_block: None },
1663 ];
1664 assert_outcome_eq_treewalk(&stmts, &it);
1665 }
1666
1667 #[test]
1668 fn vm_closure_param_shadows_capture() {
1669 let ea: Arena<Expr> = Arena::new();
1670 let ta: Arena<TypeExpr> = Arena::new();
1671 let mut it = Interner::new();
1672 let x = it.intern("x");
1673 let f = it.intern("f");
1674 let show_s = it.intern("show");
1675 let int_ty = ta.alloc(TypeExpr::Primitive(it.intern("Int")));
1676 let stmts = vec![
1677 Stmt::Let { var: x, ty: None, value: num(&ea, 10), mutable: true },
1678 letb(
1679 f,
1680 closure_expr(&ea, vec![(x, int_ty)], ClosureBody::Expression(idref(&ea, x))),
1681 ),
1682 show(&ea, show_s, call_expr(&ea, idref(&ea, f), vec![num(&ea, 77)])),
1683 ];
1684 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "77");
1685 assert_vm_eq_treewalk(&stmts, &it);
1686 }
1687
1688 #[test]
1689 fn vm_callexpr_on_non_function_and_arity_errors_match() {
1690 let ea: Arena<Expr> = Arena::new();
1691 let mut it = Interner::new();
1692 let x = it.intern("x");
1693 let f = it.intern("f");
1694
1695 let stmts = vec![letb(x, call_expr(&ea, num(&ea, 5), vec![]))];
1697 assert_err_parity(&stmts, &it);
1698
1699 let stmts = vec![
1701 letb(f, closure_expr(&ea, vec![], ClosureBody::Expression(num(&ea, 1)))),
1702 letb(x, call_expr(&ea, idref(&ea, f), vec![num(&ea, 9)])),
1703 ];
1704 assert_err_parity(&stmts, &it);
1705 }
1706
1707 #[test]
1708 fn vm_closure_passed_to_function_and_called_via_param() {
1709 let ea: Arena<Expr> = Arena::new();
1711 let sa: Arena<Stmt> = Arena::new();
1712 let ta: Arena<TypeExpr> = Arena::new();
1713 let mut it = Interner::new();
1714 let apply = it.intern("apply");
1715 let fp = it.intern("fp");
1716 let xp = it.intern("xp");
1717 let n = it.intern("n");
1718 let show_s = it.intern("show");
1719 let int_ty = ta.alloc(TypeExpr::Primitive(it.intern("Int")));
1720
1721 let apply_body: &[Stmt] =
1722 sa.alloc_slice(vec![ret(call_expr(&ea, idref(&ea, fp), vec![idref(&ea, xp)]))]);
1723 let doubler = closure_expr(
1724 &ea,
1725 vec![(n, int_ty)],
1726 ClosureBody::Expression(bin(&ea, BinaryOpKind::Multiply, idref(&ea, n), num(&ea, 2))),
1727 );
1728 let stmts = vec![
1729 fndef(apply, vec![(fp, int_ty), (xp, int_ty)], apply_body),
1730 show(&ea, show_s, calle(&ea, apply, vec![doubler, num(&ea, 21)])),
1731 ];
1732 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "42");
1733 assert_vm_eq_treewalk(&stmts, &it);
1734 }
1735
1736 fn struct_def<'a>(name: Symbol, fields: Vec<(Symbol, Symbol, bool)>) -> Stmt<'a> {
1739 Stmt::StructDef { name, fields, is_portable: false }
1740 }
1741 fn new_struct<'a>(
1742 ea: &'a Arena<Expr<'a>>,
1743 type_name: Symbol,
1744 init_fields: Vec<(Symbol, &'a Expr<'a>)>,
1745 ) -> &'a Expr<'a> {
1746 ea.alloc(Expr::New { type_name, type_args: vec![], init_fields })
1747 }
1748 fn field_access<'a>(ea: &'a Arena<Expr<'a>>, object: &'a Expr<'a>, field: Symbol) -> &'a Expr<'a> {
1749 ea.alloc(Expr::FieldAccess { object, field })
1750 }
1751
1752 #[test]
1753 fn vm_struct_new_with_defaults_and_field_access() {
1754 let ea: Arena<Expr> = Arena::new();
1756 let mut it = Interner::new();
1757 let point = it.intern("Point");
1758 let x = it.intern("x");
1759 let y = it.intern("y");
1760 let int_s = it.intern("Int");
1761 let p = it.intern("p");
1762 let show_s = it.intern("show");
1763 let stmts = vec![
1764 struct_def(point, vec![(x, int_s, true), (y, int_s, true)]),
1765 letb(p, new_struct(&ea, point, vec![(x, num(&ea, 3))])),
1766 show(&ea, show_s, field_access(&ea, idref(&ea, p), x)),
1767 show(&ea, show_s, field_access(&ea, idref(&ea, p), y)),
1768 ];
1769 let out = compile_and_run(&stmts, &it).unwrap();
1770 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["3", "0"]);
1771 assert_vm_eq_treewalk(&stmts, &it);
1772 }
1773
1774 #[test]
1775 fn vm_struct_value_semantics_no_aliasing() {
1776 let ea: Arena<Expr> = Arena::new();
1778 let mut it = Interner::new();
1779 let point = it.intern("Point");
1780 let x = it.intern("x");
1781 let int_s = it.intern("Int");
1782 let a = it.intern("a");
1783 let b = it.intern("b");
1784 let show_s = it.intern("show");
1785 let stmts = vec![
1786 struct_def(point, vec![(x, int_s, true)]),
1787 Stmt::Let { var: a, ty: None, value: new_struct(&ea, point, vec![(x, num(&ea, 1))]), mutable: true },
1788 Stmt::Let { var: b, ty: None, value: idref(&ea, a), mutable: true },
1789 Stmt::SetField { object: idref(&ea, b), field: x, value: num(&ea, 99) },
1790 show(&ea, show_s, field_access(&ea, idref(&ea, a), x)),
1791 show(&ea, show_s, field_access(&ea, idref(&ea, b), x)),
1792 ];
1793 let out = compile_and_run(&stmts, &it).unwrap();
1794 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["1", "99"]);
1795 assert_vm_eq_treewalk(&stmts, &it);
1796 }
1797
1798 #[test]
1799 fn vm_field_errors_match_treewalk() {
1800 let ea: Arena<Expr> = Arena::new();
1801 let mut it = Interner::new();
1802 let point = it.intern("Point");
1803 let x = it.intern("x");
1804 let ghost = it.intern("ghost");
1805 let int_s = it.intern("Int");
1806 let p = it.intern("p");
1807 let v = it.intern("v");
1808
1809 let stmts = vec![
1811 struct_def(point, vec![(x, int_s, true)]),
1812 letb(p, new_struct(&ea, point, vec![])),
1813 letb(v, field_access(&ea, idref(&ea, p), ghost)),
1814 ];
1815 assert_err_parity(&stmts, &it);
1816
1817 let stmts = vec![letb(v, field_access(&ea, num(&ea, 5), x))];
1819 assert_err_parity(&stmts, &it);
1820
1821 let stmts = vec![
1823 letb(p, num(&ea, 5)),
1824 Stmt::SetField { object: idref(&ea, p), field: x, value: num(&ea, 1) },
1825 ];
1826 assert_err_parity(&stmts, &it);
1827 }
1828
1829 #[test]
1830 fn vm_inspect_struct_arms_and_otherwise() {
1831 use crate::ast::stmt::MatchArm;
1832 let ea: Arena<Expr> = Arena::new();
1835 let sa: Arena<Stmt> = Arena::new();
1836 let mut it = Interner::new();
1837 let point = it.intern("Point");
1838 let circle = it.intern("Circle");
1839 let x = it.intern("x");
1840 let bx = it.intern("bx");
1841 let int_s = it.intern("Int");
1842 let p = it.intern("p");
1843 let show_s = it.intern("show");
1844
1845 let point_body: &[Stmt] = sa.alloc_slice(vec![show(&ea, show_s, idref(&ea, bx))]);
1846 let circle_body: &[Stmt] = sa.alloc_slice(vec![show(&ea, show_s, num(&ea, 111))]);
1847 let otherwise_body: &[Stmt] = sa.alloc_slice(vec![show(&ea, show_s, num(&ea, 222))]);
1848
1849 let stmts = vec![
1850 struct_def(point, vec![(x, int_s, true)]),
1851 letb(p, new_struct(&ea, point, vec![(x, num(&ea, 7))])),
1852 Stmt::Inspect {
1853 target: idref(&ea, p),
1854 arms: vec![
1855 MatchArm { enum_name: None, variant: Some(circle), bindings: vec![], body: circle_body },
1856 MatchArm { enum_name: None, variant: Some(point), bindings: vec![(x, bx)], body: point_body },
1857 MatchArm { enum_name: None, variant: None, bindings: vec![], body: otherwise_body },
1858 ],
1859 has_otherwise: true,
1860 },
1861 Stmt::Inspect {
1862 target: num(&ea, 5),
1863 arms: vec![
1864 MatchArm { enum_name: None, variant: Some(point), bindings: vec![], body: circle_body },
1865 MatchArm { enum_name: None, variant: None, bindings: vec![], body: otherwise_body },
1866 ],
1867 has_otherwise: true,
1868 },
1869 ];
1870 let out = compile_and_run(&stmts, &it).unwrap();
1871 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["7", "222"]);
1872 assert_vm_eq_treewalk(&stmts, &it);
1873 }
1874
1875 #[test]
1876 fn vm_inspect_inductive_positional_bindings() {
1877 use crate::ast::stmt::MatchArm;
1878 let ea: Arena<Expr> = Arena::new();
1879 let sa: Arena<Stmt> = Arena::new();
1880 let mut it = Interner::new();
1881 let shape = it.intern("Shape");
1882 let circle = it.intern("Circle");
1883 let r_field = it.intern("radius");
1884 let r_bind = it.intern("r");
1885 let c = it.intern("c");
1886 let show_s = it.intern("show");
1887
1888 let circle_expr = ea.alloc(Expr::NewVariant {
1889 enum_name: shape,
1890 variant: circle,
1891 fields: vec![(r_field, num(&ea, 10))],
1892 });
1893 let circle_body: &[Stmt] = sa.alloc_slice(vec![show(&ea, show_s, idref(&ea, r_bind))]);
1894
1895 let stmts = vec![
1896 letb(c, circle_expr),
1897 Stmt::Inspect {
1899 target: idref(&ea, c),
1900 arms: vec![MatchArm {
1901 enum_name: None,
1902 variant: Some(circle),
1903 bindings: vec![(r_field, r_bind)],
1904 body: circle_body,
1905 }],
1906 has_otherwise: false,
1907 },
1908 show(&ea, show_s, num(&ea, 1)),
1909 ];
1910 let out = compile_and_run(&stmts, &it).unwrap();
1911 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["10", "1"]);
1912 assert_vm_eq_treewalk(&stmts, &it);
1913 }
1914
1915 #[test]
1916 fn vm_inspect_unhandled_variant_without_otherwise_is_loud() {
1917 use crate::ast::stmt::MatchArm;
1918 let ea: Arena<Expr> = Arena::new();
1919 let sa: Arena<Stmt> = Arena::new();
1920 let mut it = Interner::new();
1921 let shape = it.intern("Shape");
1922 let circle = it.intern("Circle");
1923 let square = it.intern("Square");
1924 let r_field = it.intern("radius");
1925 let c = it.intern("c");
1926 let show_s = it.intern("show");
1927
1928 let circle_expr = ea.alloc(Expr::NewVariant {
1929 enum_name: shape,
1930 variant: circle,
1931 fields: vec![(r_field, num(&ea, 10))],
1932 });
1933 let square_body: &[Stmt] = sa.alloc_slice(vec![show(&ea, show_s, num(&ea, 333))]);
1934
1935 let stmts = vec![
1938 letb(c, circle_expr),
1939 Stmt::Inspect {
1940 target: idref(&ea, c),
1941 arms: vec![MatchArm { enum_name: None, variant: Some(square), bindings: vec![], body: square_body }],
1942 has_otherwise: false,
1943 },
1944 ];
1945 assert_err_parity(&stmts, &it);
1946 }
1947
1948 #[test]
1949 fn vm_inductive_equality_is_structural() {
1950 let ea: Arena<Expr> = Arena::new();
1951 let mut it = Interner::new();
1952 let shape = it.intern("Shape");
1953 let circle = it.intern("Circle");
1954 let r_field = it.intern("radius");
1955 let a = it.intern("a");
1956 let b = it.intern("b");
1957 let show_s = it.intern("show");
1958 let c1 = ea.alloc(Expr::NewVariant {
1959 enum_name: shape,
1960 variant: circle,
1961 fields: vec![(r_field, num(&ea, 5))],
1962 });
1963 let c2 = ea.alloc(Expr::NewVariant {
1964 enum_name: shape,
1965 variant: circle,
1966 fields: vec![(r_field, num(&ea, 5))],
1967 });
1968 let stmts = vec![
1969 letb(a, c1),
1970 letb(b, c2),
1971 show(&ea, show_s, bin(&ea, BinaryOpKind::Eq, idref(&ea, a), idref(&ea, b))),
1972 ];
1973 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "true");
1974 assert_vm_eq_treewalk(&stmts, &it);
1975 }
1976
1977 #[test]
1978 fn vm_crdt_increase_decrease_merge_match_treewalk() {
1979 let ea: Arena<Expr> = Arena::new();
1980 let mut it = Interner::new();
1981 let counter = it.intern("Counter");
1982 let n_field = it.intern("n");
1983 let int_s = it.intern("Int");
1984 let a = it.intern("a");
1985 let b = it.intern("b");
1986 let show_s = it.intern("show");
1987 let stmts = vec![
1988 struct_def(counter, vec![(n_field, int_s, true)]),
1989 Stmt::Let { var: a, ty: None, value: new_struct(&ea, counter, vec![(n_field, num(&ea, 1))]), mutable: true },
1990 Stmt::Let { var: b, ty: None, value: new_struct(&ea, counter, vec![(n_field, num(&ea, 10))]), mutable: true },
1991 Stmt::IncreaseCrdt { object: idref(&ea, a), field: n_field, amount: num(&ea, 5) },
1992 Stmt::DecreaseCrdt { object: idref(&ea, a), field: n_field, amount: num(&ea, 2) },
1993 Stmt::MergeCrdt { source: idref(&ea, b), target: idref(&ea, a) },
1994 show(&ea, show_s, field_access(&ea, idref(&ea, a), n_field)),
1995 ];
1996 let out = compile_and_run(&stmts, &it).unwrap();
1997 assert_eq!(out.trim(), "14");
1998 assert_vm_eq_treewalk(&stmts, &it);
1999 }
2000
2001 #[test]
2004 fn vm_builtins_match_treewalk() {
2005 let ea: Arena<Expr> = Arena::new();
2007 let mut it = Interner::new();
2008 let show_s = it.intern("show");
2009 let f25 = ea.alloc(Expr::Literal(Literal::Float(2.5)));
2010 let f29 = ea.alloc(Expr::Literal(Literal::Float(2.9)));
2011 let neg = bin(&ea, BinaryOpKind::Subtract, num(&ea, 0), num(&ea, 7));
2012 let calls: Vec<&Expr> = vec![
2013 calle(&ea, it.intern("abs"), vec![neg]),
2014 calle(&ea, it.intern("sqrt"), vec![num(&ea, 9)]),
2015 calle(&ea, it.intern("min"), vec![num(&ea, 3), f25]),
2016 calle(&ea, it.intern("max"), vec![num(&ea, 3), f25]),
2017 calle(&ea, it.intern("floor"), vec![f29]),
2018 calle(&ea, it.intern("ceil"), vec![f29]),
2019 calle(&ea, it.intern("round"), vec![f29]),
2020 calle(&ea, it.intern("pow"), vec![num(&ea, 2), num(&ea, 10)]),
2021 calle(&ea, it.intern("chr"), vec![num(&ea, 65)]),
2022 calle(&ea, it.intern("length"), vec![text(&ea, it.intern("hello"))]),
2023 calle(&ea, it.intern("format"), vec![num(&ea, 42)]),
2024 ];
2025 let mut stmts = Vec::new();
2026 for (k, c) in calls.into_iter().enumerate() {
2027 let v = it.intern(&format!("b{k}"));
2028 stmts.push(letb(v, c));
2029 stmts.push(show(&ea, show_s, idref(&ea, v)));
2030 }
2031 assert_vm_eq_treewalk(&stmts, &it);
2032 }
2033
2034 #[test]
2035 fn vm_parse_int_and_float_match_treewalk() {
2036 let ea: Arena<Expr> = Arena::new();
2037 let mut it = Interner::new();
2038 let show_s = it.intern("show");
2039 let a = it.intern("a");
2040 let b = it.intern("b");
2041 let s42 = it.intern(" 42 ");
2042 let s25 = it.intern("2.5");
2043 let stmts = vec![
2044 letb(a, calle(&ea, it.intern("parseInt"), vec![text(&ea, s42)])),
2045 show(&ea, show_s, idref(&ea, a)),
2046 letb(b, calle(&ea, it.intern("parseFloat"), vec![text(&ea, s25)])),
2047 show(&ea, show_s, idref(&ea, b)),
2048 ];
2049 assert_vm_eq_treewalk(&stmts, &it);
2050 }
2051
2052 #[test]
2053 fn vm_builtin_error_messages_match_treewalk() {
2054 let ea: Arena<Expr> = Arena::new();
2056 let mut it = Interner::new();
2057 let x = it.intern("x");
2058 let zz = it.intern("zz");
2059
2060 let stmts = vec![letb(x, calle(&ea, it.intern("parseInt"), vec![text(&ea, zz)]))];
2061 assert_err_parity(&stmts, &it);
2062
2063 let neg1 = bin(&ea, BinaryOpKind::Subtract, num(&ea, 0), num(&ea, 1));
2064 let stmts = vec![letb(x, calle(&ea, it.intern("chr"), vec![neg1]))];
2065 assert_err_parity(&stmts, &it);
2066
2067 let stmts = vec![letb(x, calle(&ea, it.intern("abs"), vec![num(&ea, 1), num(&ea, 2)]))];
2069 assert_err_parity(&stmts, &it);
2070 }
2071
2072 #[test]
2073 fn vm_copy_builtin_is_deep() {
2074 let ea: Arena<Expr> = Arena::new();
2076 let mut it = Interner::new();
2077 let xs = it.intern("xs");
2078 let ys = it.intern("ys");
2079 let show_s = it.intern("show");
2080 let stmts = vec![
2081 Stmt::Let { var: xs, ty: None, value: list_lit(&ea, vec![num(&ea, 1)]), mutable: true },
2082 letb(ys, calle(&ea, it.intern("copy"), vec![idref(&ea, xs)])),
2083 push_to(num(&ea, 2), idref(&ea, xs)),
2084 show(&ea, show_s, length_of(&ea, idref(&ea, xs))),
2085 show(&ea, show_s, length_of(&ea, idref(&ea, ys))),
2086 ];
2087 let out = compile_and_run(&stmts, &it).unwrap();
2088 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["2", "1"]);
2089 assert_vm_eq_treewalk(&stmts, &it);
2090 }
2091
2092 #[test]
2093 fn vm_let_binding_isolates_value_semantics() {
2094 let ea: Arena<Expr> = Arena::new();
2097 let mut it = Interner::new();
2098 let xs = it.intern("xs");
2099 let a = it.intern("a");
2100 let show_s = it.intern("show");
2101 let stmts = vec![
2102 Stmt::Let { var: xs, ty: None, value: list_lit(&ea, vec![num(&ea, 1)]), mutable: true },
2103 Stmt::Let { var: a, ty: None, value: idref(&ea, xs), mutable: true },
2104 push_to(num(&ea, 2), idref(&ea, a)),
2105 show(&ea, show_s, length_of(&ea, idref(&ea, xs))),
2106 ];
2107 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "1");
2108 assert_vm_eq_treewalk(&stmts, &it);
2109 }
2110
2111 #[test]
2112 fn vm_unknown_function_error_matches_treewalk() {
2113 let ea: Arena<Expr> = Arena::new();
2114 let mut it = Interner::new();
2115 let x = it.intern("x");
2116 let stmts = vec![letb(x, calle(&ea, it.intern("frobnicate"), vec![num(&ea, 1)]))];
2117 assert_err_parity(&stmts, &it);
2118 }
2119
2120 #[test]
2121 fn vm_user_fn_arity_mismatch_matches_treewalk() {
2122 let ea: Arena<Expr> = Arena::new();
2123 let sa: Arena<Stmt> = Arena::new();
2124 let ta: Arena<TypeExpr> = Arena::new();
2125 let mut it = Interner::new();
2126 let f = it.intern("f");
2127 let n = it.intern("n");
2128 let x = it.intern("x");
2129 let int_ty = ta.alloc(TypeExpr::Primitive(it.intern("Int")));
2130 let body: &[Stmt] = sa.alloc_slice(vec![ret(idref(&ea, n))]);
2131 let stmts = vec![
2132 fndef(f, vec![(n, int_ty)], body),
2133 letb(x, calle(&ea, f, vec![num(&ea, 1), num(&ea, 2)])),
2134 ];
2135 assert_err_parity(&stmts, &it);
2136 }
2137
2138 #[test]
2139 fn vm_infinite_recursion_hits_call_depth_limit_like_treewalk() {
2140 std::thread::Builder::new()
2153 .stack_size(256 * 1024 * 1024)
2154 .spawn(|| {
2155 let ea: Arena<Expr> = Arena::new();
2156 let sa: Arena<Stmt> = Arena::new();
2157 let ta: Arena<TypeExpr> = Arena::new();
2158 let mut it = Interner::new();
2159 let spin = it.intern("spin");
2160 let n = it.intern("n");
2161 let x = it.intern("x");
2162 let int_ty = ta.alloc(TypeExpr::Primitive(it.intern("Int")));
2163 let body: &[Stmt] = sa.alloc_slice(vec![ret(bin(
2164 &ea,
2165 BinaryOpKind::Subtract,
2166 calle(
2167 &ea,
2168 spin,
2169 vec![bin(&ea, BinaryOpKind::Add, idref(&ea, n), num(&ea, 1))],
2170 ),
2171 num(&ea, 1),
2172 ))]);
2173 let stmts = vec![
2174 fndef(spin, vec![(n, int_ty)], body),
2175 letb(x, calle(&ea, spin, vec![num(&ea, 0)])),
2176 ];
2177 let vm_err = compile_and_run(&stmts, &it).unwrap_err();
2178 assert_eq!(vm_err, "Stack overflow: maximum call depth exceeded");
2179 let (_, tw_err) = run_treewalk_outcome(&stmts, &it);
2180 assert_eq!(
2181 tw_err.as_deref(),
2182 Some("Stack overflow: maximum call depth exceeded")
2183 );
2184 })
2185 .unwrap()
2186 .join()
2187 .unwrap();
2188 }
2189
2190 #[test]
2191 fn vm_depth_900_recursion_still_fine() {
2192 let ea: Arena<Expr> = Arena::new();
2194 let sa: Arena<Stmt> = Arena::new();
2195 let ta: Arena<TypeExpr> = Arena::new();
2196 let mut it = Interner::new();
2197 let down = it.intern("down");
2198 let n = it.intern("n");
2199 let x = it.intern("x");
2200 let show_s = it.intern("show");
2201 let int_ty = ta.alloc(TypeExpr::Primitive(it.intern("Int")));
2202 let base: &[Stmt] = sa.alloc_slice(vec![ret(num(&ea, 0))]);
2203 let body: &[Stmt] = sa.alloc_slice(vec![
2204 Stmt::If {
2205 cond: bin(&ea, BinaryOpKind::LtEq, idref(&ea, n), num(&ea, 0)),
2206 then_block: base,
2207 else_block: None,
2208 },
2209 ret(calle(&ea, down, vec![bin(&ea, BinaryOpKind::Subtract, idref(&ea, n), num(&ea, 1))])),
2210 ]);
2211 let stmts = vec![
2212 fndef(down, vec![(n, int_ty)], body),
2213 letb(x, calle(&ea, down, vec![num(&ea, 900)])),
2214 show(&ea, show_s, idref(&ea, x)),
2215 ];
2216 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "0");
2217 }
2218
2219 #[test]
2220 fn vm_show_to_function_recipient_calls_it() {
2221 let ea: Arena<Expr> = Arena::new();
2223 let sa: Arena<Stmt> = Arena::new();
2224 let ta: Arena<TypeExpr> = Arena::new();
2225 let mut it = Interner::new();
2226 let sink = it.intern("sink");
2227 let v = it.intern("v");
2228 let show_s = it.intern("show");
2229 let int_ty = ta.alloc(TypeExpr::Primitive(it.intern("Int")));
2230 let body: &[Stmt] = sa.alloc_slice(vec![show(
2231 &ea,
2232 show_s,
2233 bin(&ea, BinaryOpKind::Multiply, idref(&ea, v), num(&ea, 2)),
2234 )]);
2235 let stmts = vec![
2236 fndef(sink, vec![(v, int_ty)], body),
2237 Stmt::Show { object: num(&ea, 21), recipient: idref(&ea, sink) },
2238 Stmt::Give { object: num(&ea, 5), recipient: idref(&ea, sink) },
2239 ];
2240 let out = compile_and_run(&stmts, &it).unwrap();
2241 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["42", "10"]);
2242 assert_vm_eq_treewalk(&stmts, &it);
2243 }
2244
2245 fn assert_outcome_eq_treewalk(stmts: &[Stmt], interner: &Interner) {
2249 let (vm_out, vm_err) = run_to_outcome(stmts, interner, None, None);
2250 let (tw_out, tw_err) = match run_treewalk_outcome(stmts, interner) {
2251 (o, e) => (o, e),
2252 };
2253 assert_eq!(
2254 normalize(&vm_out),
2255 normalize(&tw_out),
2256 "partial output diverged (vm err {:?}, tw err {:?})",
2257 vm_err,
2258 tw_err
2259 );
2260 assert_eq!(vm_err, tw_err, "error diverged");
2261 }
2262
2263 fn run_treewalk_outcome(stmts: &[Stmt], interner: &Interner) -> (String, Option<String>) {
2265 use crate::interpreter::{Interpreter, OutputCallback};
2266 let buf = Rc::new(RefCell::new(String::new()));
2267 let sink = buf.clone();
2268 let cb: OutputCallback = Rc::new(RefCell::new(move |s: String| {
2269 sink.borrow_mut().push_str(&s);
2270 sink.borrow_mut().push('\n');
2271 }));
2272 let mut interp = Interpreter::new(interner).with_output_callback(cb);
2273 let err = interp.run_sync(stmts).err();
2274 let out = buf.borrow().clone();
2275 (out, err)
2276 }
2277
2278 fn brk<'a>() -> Stmt<'a> {
2279 Stmt::Break
2280 }
2281 fn repeat_ident<'a>(var: Symbol, iterable: &'a Expr<'a>, body: &'a [Stmt<'a>]) -> Stmt<'a> {
2282 use crate::ast::stmt::Pattern;
2283 Stmt::Repeat { pattern: Pattern::Identifier(var), iterable, body }
2284 }
2285
2286 #[test]
2287 fn vm_break_exits_innermost_while() {
2288 let ea: Arena<Expr> = Arena::new();
2290 let sa: Arena<Stmt> = Arena::new();
2291 let mut it = Interner::new();
2292 let i = it.intern("i");
2293 let show_s = it.intern("show");
2294 let break_blk: &[Stmt] = sa.alloc_slice(vec![brk()]);
2295 let body: &[Stmt] = sa.alloc_slice(vec![
2296 Stmt::If {
2297 cond: bin(&ea, BinaryOpKind::GtEq, idref(&ea, i), num(&ea, 3)),
2298 then_block: break_blk,
2299 else_block: None,
2300 },
2301 show(&ea, show_s, idref(&ea, i)),
2302 Stmt::Set { target: i, value: bin(&ea, BinaryOpKind::Add, idref(&ea, i), num(&ea, 1)) },
2303 ]);
2304 let stmts = vec![
2305 Stmt::Let { var: i, ty: None, value: num(&ea, 0), mutable: true },
2306 Stmt::While { cond: bin(&ea, BinaryOpKind::Lt, idref(&ea, i), num(&ea, 100)), body, decreasing: None },
2307 show(&ea, show_s, idref(&ea, i)),
2308 ];
2309 let out = compile_and_run(&stmts, &it).unwrap();
2310 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["0", "1", "2", "3"]);
2311 assert_vm_eq_treewalk(&stmts, &it);
2312 }
2313
2314 #[test]
2315 fn vm_break_only_exits_inner_loop() {
2316 let ea: Arena<Expr> = Arena::new();
2318 let sa: Arena<Stmt> = Arena::new();
2319 let mut it = Interner::new();
2320 let i = it.intern("i");
2321 let j = it.intern("j");
2322 let show_s = it.intern("show");
2323 let inner_body: &[Stmt] = sa.alloc_slice(vec![brk()]);
2324 let outer_body: &[Stmt] = sa.alloc_slice(vec![
2325 Stmt::Let { var: j, ty: None, value: num(&ea, 0), mutable: true },
2326 Stmt::While { cond: bin(&ea, BinaryOpKind::Lt, idref(&ea, j), num(&ea, 5)), body: inner_body, decreasing: None },
2327 show(&ea, show_s, idref(&ea, i)),
2328 Stmt::Set { target: i, value: bin(&ea, BinaryOpKind::Add, idref(&ea, i), num(&ea, 1)) },
2329 ]);
2330 let stmts = vec![
2331 Stmt::Let { var: i, ty: None, value: num(&ea, 0), mutable: true },
2332 Stmt::While { cond: bin(&ea, BinaryOpKind::Lt, idref(&ea, i), num(&ea, 2)), body: outer_body, decreasing: None },
2333 ];
2334 let out = compile_and_run(&stmts, &it).unwrap();
2335 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["0", "1"]);
2336 assert_vm_eq_treewalk(&stmts, &it);
2337 }
2338
2339 #[test]
2340 fn vm_break_at_top_level_halts_program() {
2341 let ea: Arena<Expr> = Arena::new();
2343 let mut it = Interner::new();
2344 let show_s = it.intern("show");
2345 let stmts = vec![
2346 show(&ea, show_s, num(&ea, 1)),
2347 brk(),
2348 show(&ea, show_s, num(&ea, 2)),
2349 ];
2350 assert_outcome_eq_treewalk(&stmts, &it);
2351 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "1");
2352 }
2353
2354 #[test]
2355 fn vm_return_at_top_level_halts_program() {
2356 let ea: Arena<Expr> = Arena::new();
2358 let mut it = Interner::new();
2359 let show_s = it.intern("show");
2360 let stmts = vec![
2361 show(&ea, show_s, num(&ea, 1)),
2362 ret(num(&ea, 99)),
2363 show(&ea, show_s, num(&ea, 2)),
2364 ];
2365 assert_outcome_eq_treewalk(&stmts, &it);
2366 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "1");
2367 }
2368
2369 #[test]
2370 fn vm_block_let_does_not_leak_scope() {
2371 let ea: Arena<Expr> = Arena::new();
2375 let sa: Arena<Stmt> = Arena::new();
2376 let mut it = Interner::new();
2377 let t = it.intern("t");
2378 let show_s = it.intern("show");
2379 let then_blk: &[Stmt] = sa.alloc_slice(vec![
2380 letb(t, num(&ea, 5)),
2381 show(&ea, show_s, idref(&ea, t)),
2382 ]);
2383 let stmts = vec![
2384 Stmt::If { cond: boolean(&ea, true), then_block: then_blk, else_block: None },
2385 show(&ea, show_s, idref(&ea, t)),
2386 ];
2387 assert_outcome_eq_treewalk(&stmts, &it);
2388 }
2389
2390 #[test]
2391 fn vm_unbound_identifier_in_dead_branch_is_free() {
2392 let ea: Arena<Expr> = Arena::new();
2395 let sa: Arena<Stmt> = Arena::new();
2396 let mut it = Interner::new();
2397 let ghost = it.intern("ghost");
2398 let show_s = it.intern("show");
2399 let dead: &[Stmt] = sa.alloc_slice(vec![show(&ea, show_s, idref(&ea, ghost))]);
2400 let stmts = vec![
2401 Stmt::If { cond: boolean(&ea, false), then_block: dead, else_block: None },
2402 show(&ea, show_s, num(&ea, 7)),
2403 ];
2404 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "7");
2405 assert_outcome_eq_treewalk(&stmts, &it);
2406 }
2407
2408 #[test]
2409 fn vm_repeat_list_snapshot_semantics() {
2410 let ea: Arena<Expr> = Arena::new();
2413 let sa: Arena<Stmt> = Arena::new();
2414 let mut it = Interner::new();
2415 let xs = it.intern("xs");
2416 let x = it.intern("x");
2417 let show_s = it.intern("show");
2418 let body: &[Stmt] = sa.alloc_slice(vec![
2419 push_to(num(&ea, 9), idref(&ea, xs)),
2420 show(&ea, show_s, idref(&ea, x)),
2421 ]);
2422 let stmts = vec![
2423 Stmt::Let { var: xs, ty: None, value: list_lit(&ea, vec![num(&ea, 1), num(&ea, 2)]), mutable: true },
2424 repeat_ident(x, idref(&ea, xs), body),
2425 show(&ea, show_s, length_of(&ea, idref(&ea, xs))),
2426 ];
2427 let out = compile_and_run(&stmts, &it).unwrap();
2428 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["1", "2", "4"]);
2429 assert_vm_eq_treewalk(&stmts, &it);
2430 }
2431
2432 #[test]
2433 fn vm_repeat_over_text_iterates_chars() {
2434 let ea: Arena<Expr> = Arena::new();
2435 let sa: Arena<Stmt> = Arena::new();
2436 let mut it = Interner::new();
2437 let s = it.intern("s");
2438 let c = it.intern("c");
2439 let show_s = it.intern("show");
2440 let abc = it.intern("abc");
2441 let body: &[Stmt] = sa.alloc_slice(vec![show(&ea, show_s, idref(&ea, c))]);
2442 let stmts = vec![
2443 letb(s, text(&ea, abc)),
2444 repeat_ident(c, idref(&ea, s), body),
2445 ];
2446 let out = compile_and_run(&stmts, &it).unwrap();
2447 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["a", "b", "c"]);
2448 assert_vm_eq_treewalk(&stmts, &it);
2449 }
2450
2451 #[test]
2452 fn vm_repeat_map_single_entry_tuple_pattern() {
2453 use crate::ast::stmt::Pattern;
2454 let ea: Arena<Expr> = Arena::new();
2456 let sa: Arena<Stmt> = Arena::new();
2457 let mut it = Interner::new();
2458 let m = it.intern("m");
2459 let k = it.intern("k");
2460 let v = it.intern("v");
2461 let map_ty = it.intern("Map");
2462 let key_a = it.intern("a");
2463 let show_s = it.intern("show");
2464 let body: &[Stmt] = sa.alloc_slice(vec![
2465 show(&ea, show_s, idref(&ea, k)),
2466 show(&ea, show_s, idref(&ea, v)),
2467 ]);
2468 let stmts = vec![
2469 Stmt::Let { var: m, ty: None, value: new_coll(&ea, map_ty), mutable: true },
2470 Stmt::SetIndex { collection: idref(&ea, m), index: text(&ea, key_a), value: num(&ea, 10) },
2471 Stmt::Repeat { pattern: Pattern::Tuple(vec![k, v]), iterable: idref(&ea, m), body },
2472 ];
2473 let out = compile_and_run(&stmts, &it).unwrap();
2474 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["a", "10"]);
2475 assert_vm_eq_treewalk(&stmts, &it);
2476 }
2477
2478 #[test]
2479 fn vm_repeat_tuple_pattern_on_non_tuple_errors_like_treewalk() {
2480 use crate::ast::stmt::Pattern;
2481 let ea: Arena<Expr> = Arena::new();
2482 let sa: Arena<Stmt> = Arena::new();
2483 let mut it = Interner::new();
2484 let k = it.intern("k");
2485 let v = it.intern("v");
2486 let show_s = it.intern("show");
2487 let body: &[Stmt] = sa.alloc_slice(vec![show(&ea, show_s, idref(&ea, k))]);
2488 let stmts = vec![Stmt::Repeat {
2489 pattern: Pattern::Tuple(vec![k, v]),
2490 iterable: list_lit(&ea, vec![num(&ea, 1)]),
2491 body,
2492 }];
2493 assert_err_parity(&stmts, &it);
2494 }
2495
2496 #[test]
2497 fn vm_repeat_break_exits_and_loop_var_scoped() {
2498 let ea: Arena<Expr> = Arena::new();
2500 let sa: Arena<Stmt> = Arena::new();
2501 let mut it = Interner::new();
2502 let xs = it.intern("xs");
2503 let x = it.intern("x");
2504 let show_s = it.intern("show");
2505 let break_blk: &[Stmt] = sa.alloc_slice(vec![brk()]);
2506 let body: &[Stmt] = sa.alloc_slice(vec![
2507 show(&ea, show_s, idref(&ea, x)),
2508 Stmt::If {
2509 cond: bin(&ea, BinaryOpKind::GtEq, idref(&ea, x), num(&ea, 2)),
2510 then_block: break_blk,
2511 else_block: None,
2512 },
2513 ]);
2514 let stmts = vec![
2515 letb(xs, list_lit(&ea, vec![num(&ea, 1), num(&ea, 2), num(&ea, 3)])),
2516 repeat_ident(x, idref(&ea, xs), body),
2517 show(&ea, show_s, idref(&ea, x)),
2518 ];
2519 assert_outcome_eq_treewalk(&stmts, &it);
2521 }
2522
2523 #[test]
2524 fn vm_repeat_over_int_errors_like_treewalk() {
2525 let ea: Arena<Expr> = Arena::new();
2526 let sa: Arena<Stmt> = Arena::new();
2527 let mut it = Interner::new();
2528 let x = it.intern("x");
2529 let show_s = it.intern("show");
2530 let body: &[Stmt] = sa.alloc_slice(vec![show(&ea, show_s, idref(&ea, x))]);
2531 let stmts = vec![repeat_ident(x, num(&ea, 42), body)];
2532 assert_err_parity(&stmts, &it);
2533 }
2534
2535 #[test]
2538 fn vm_pop_into_binds_and_empty_pop_is_nothing() {
2539 let ea: Arena<Expr> = Arena::new();
2540 let mut it = Interner::new();
2541 let xs = it.intern("xs");
2542 let v = it.intern("v");
2543 let w = it.intern("w");
2544 let show_s = it.intern("show");
2545 let stmts = vec![
2546 Stmt::Let { var: xs, ty: None, value: list_lit(&ea, vec![num(&ea, 1), num(&ea, 2)]), mutable: true },
2547 Stmt::Pop { collection: idref(&ea, xs), into: Some(v) },
2548 show(&ea, show_s, idref(&ea, v)),
2549 show(&ea, show_s, length_of(&ea, idref(&ea, xs))),
2550 Stmt::Pop { collection: idref(&ea, xs), into: None },
2551 Stmt::Pop { collection: idref(&ea, xs), into: Some(w) },
2552 show(&ea, show_s, idref(&ea, w)),
2553 ];
2554 let out = compile_and_run(&stmts, &it).unwrap();
2555 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["2", "1", "nothing"]);
2556 assert_vm_eq_treewalk(&stmts, &it);
2557 }
2558
2559 #[test]
2560 fn vm_runtime_assert_passes_and_fails_like_treewalk() {
2561 let ea: Arena<Expr> = Arena::new();
2562 let mut it = Interner::new();
2563 let show_s = it.intern("show");
2564 let ok_stmts = vec![
2565 Stmt::RuntimeAssert { condition: boolean(&ea, true) , hard: false },
2566 show(&ea, show_s, num(&ea, 1)),
2567 ];
2568 assert_eq!(compile_and_run(&ok_stmts, &it).unwrap().trim(), "1");
2569 assert_vm_eq_treewalk(&ok_stmts, &it);
2570
2571 let fail_stmts = vec![
2572 show(&ea, show_s, num(&ea, 1)),
2573 Stmt::RuntimeAssert { condition: boolean(&ea, false) , hard: false },
2574 show(&ea, show_s, num(&ea, 2)),
2575 ];
2576 assert_outcome_eq_treewalk(&fail_stmts, &it);
2577 }
2578
2579 #[test]
2580 fn vm_zone_swallows_return_inside_function() {
2581 let ea: Arena<Expr> = Arena::new();
2584 let sa: Arena<Stmt> = Arena::new();
2585 let ta: Arena<TypeExpr> = Arena::new();
2586 let mut it = Interner::new();
2587 let f = it.intern("f");
2588 let z = it.intern("z");
2589 let r = it.intern("r");
2590 let show_s = it.intern("show");
2591 let int_ty = ta.alloc(TypeExpr::Primitive(it.intern("Int")));
2592 let _ = int_ty;
2593
2594 let zone_body: &[Stmt] = sa.alloc_slice(vec![ret(num(&ea, 5))]);
2595 let f_body: &[Stmt] = sa.alloc_slice(vec![
2596 Stmt::Zone { name: z, capacity: None, source_file: None, body: zone_body },
2597 show(&ea, show_s, num(&ea, 77)),
2598 ret(num(&ea, 1)),
2599 ]);
2600 let stmts = vec![
2601 fndef(f, vec![], f_body),
2602 letb(r, calle(&ea, f, vec![])),
2603 show(&ea, show_s, idref(&ea, r)),
2604 ];
2605 let out = compile_and_run(&stmts, &it).unwrap();
2606 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["77", "1"]);
2607 assert_vm_eq_treewalk(&stmts, &it);
2608 }
2609
2610 #[test]
2611 fn vm_zone_swallows_break_in_loop() {
2612 let ea: Arena<Expr> = Arena::new();
2615 let sa: Arena<Stmt> = Arena::new();
2616 let mut it = Interner::new();
2617 let i = it.intern("i");
2618 let z = it.intern("z");
2619 let show_s = it.intern("show");
2620 let zone_body: &[Stmt] = sa.alloc_slice(vec![brk()]);
2621 let body: &[Stmt] = sa.alloc_slice(vec![
2622 Stmt::Zone { name: z, capacity: None, source_file: None, body: zone_body },
2623 show(&ea, show_s, idref(&ea, i)),
2624 Stmt::Set { target: i, value: bin(&ea, BinaryOpKind::Add, idref(&ea, i), num(&ea, 1)) },
2625 ]);
2626 let stmts = vec![
2627 Stmt::Let { var: i, ty: None, value: num(&ea, 0), mutable: true },
2628 Stmt::While { cond: bin(&ea, BinaryOpKind::Lt, idref(&ea, i), num(&ea, 2)), body, decreasing: None },
2629 ];
2630 let out = compile_and_run(&stmts, &it).unwrap();
2631 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["0", "1"]);
2632 assert_vm_eq_treewalk(&stmts, &it);
2633 }
2634
2635 #[test]
2636 fn vm_return_inside_repeat_inside_zone_unwinds_iterator() {
2637 let ea: Arena<Expr> = Arena::new();
2640 let sa: Arena<Stmt> = Arena::new();
2641 let mut it = Interner::new();
2642 let z = it.intern("z");
2643 let x = it.intern("x");
2644 let y = it.intern("y");
2645 let show_s = it.intern("show");
2646 let rep_body: &[Stmt] = sa.alloc_slice(vec![ret(idref(&ea, x))]);
2647 let zone_body: &[Stmt] = sa.alloc_slice(vec![repeat_ident(
2648 x,
2649 list_lit(&ea, vec![num(&ea, 1), num(&ea, 2), num(&ea, 3)]),
2650 rep_body,
2651 )]);
2652 let second_body: &[Stmt] = sa.alloc_slice(vec![show(&ea, show_s, idref(&ea, y))]);
2653 let stmts = vec![
2654 Stmt::Zone { name: z, capacity: None, source_file: None, body: zone_body },
2655 show(&ea, show_s, num(&ea, 0)),
2656 repeat_ident(y, list_lit(&ea, vec![num(&ea, 7), num(&ea, 8)]), second_body),
2657 ];
2658 let out = compile_and_run(&stmts, &it).unwrap();
2659 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["0", "7", "8"]);
2660 assert_vm_eq_treewalk(&stmts, &it);
2661 }
2662
2663 #[test]
2664 fn vm_concurrent_tasks_run_sequentially_share_scope_and_swallow_flow() {
2665 let ea: Arena<Expr> = Arena::new();
2666 let sa: Arena<Stmt> = Arena::new();
2667 let mut it = Interner::new();
2668 let a = it.intern("a");
2669 let show_s = it.intern("show");
2670 let tasks: &[Stmt] = sa.alloc_slice(vec![
2671 letb(a, num(&ea, 1)),
2672 show(&ea, show_s, idref(&ea, a)),
2673 ret(num(&ea, 9)),
2674 show(&ea, show_s, num(&ea, 3)),
2675 ]);
2676 let stmts = vec![
2677 Stmt::Concurrent { tasks },
2678 show(&ea, show_s, idref(&ea, a)),
2681 ];
2682 let out = compile_and_run(&stmts, &it).unwrap();
2683 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["1", "3", "1"]);
2684 assert_vm_eq_treewalk(&stmts, &it);
2685 }
2686
2687 fn not_e<'a>(ea: &'a Arena<Expr<'a>>, operand: &'a Expr<'a>) -> &'a Expr<'a> {
2690 ea.alloc(Expr::Not { operand })
2691 }
2692
2693 fn noisy_fn<'a>(
2695 ea: &'a Arena<Expr<'a>>,
2696 sa: &'a Arena<Stmt<'a>>,
2697 noisy: Symbol,
2698 show_s: Symbol,
2699 ) -> Stmt<'a> {
2700 let body: &[Stmt] = sa.alloc_slice(vec![
2701 show(ea, show_s, num(ea, 9)),
2702 ret(num(ea, 1)),
2703 ]);
2704 fndef(noisy, vec![], body)
2705 }
2706
2707 #[test]
2708 fn vm_and_short_circuits_side_effects() {
2709 let ea: Arena<Expr> = Arena::new();
2711 let sa: Arena<Stmt> = Arena::new();
2712 let mut it = Interner::new();
2713 let noisy = it.intern("noisy");
2714 let r = it.intern("r");
2715 let show_s = it.intern("show");
2716 let stmts = vec![
2717 noisy_fn(&ea, &sa, noisy, show_s),
2718 letb(r, bin(&ea, BinaryOpKind::And, boolean(&ea, false), calle(&ea, noisy, vec![]))),
2719 show(&ea, show_s, idref(&ea, r)),
2720 ];
2721 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "false");
2722 assert_vm_eq_treewalk(&stmts, &it);
2723 }
2724
2725 #[test]
2726 fn vm_or_short_circuits_side_effects() {
2727 let ea: Arena<Expr> = Arena::new();
2729 let sa: Arena<Stmt> = Arena::new();
2730 let mut it = Interner::new();
2731 let noisy = it.intern("noisy");
2732 let r = it.intern("r");
2733 let show_s = it.intern("show");
2734 let stmts = vec![
2735 noisy_fn(&ea, &sa, noisy, show_s),
2736 letb(r, bin(&ea, BinaryOpKind::Or, boolean(&ea, true), calle(&ea, noisy, vec![]))),
2737 show(&ea, show_s, idref(&ea, r)),
2738 ];
2739 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "true");
2740 assert_vm_eq_treewalk(&stmts, &it);
2741 }
2742
2743 #[test]
2744 fn vm_and_is_logical_truthiness_to_bool() {
2745 let ea: Arena<Expr> = Arena::new();
2749 let sa: Arena<Stmt> = Arena::new();
2750 let mut it = Interner::new();
2751 let noisy = it.intern("noisy");
2752 let a = it.intern("a");
2753 let b = it.intern("b");
2754 let show_s = it.intern("show");
2755 let stmts = vec![
2756 noisy_fn(&ea, &sa, noisy, show_s),
2757 letb(a, bin(&ea, BinaryOpKind::And, num(&ea, 6), num(&ea, 3))),
2758 show(&ea, show_s, idref(&ea, a)),
2759 letb(b, bin(&ea, BinaryOpKind::And, num(&ea, 6), calle(&ea, noisy, vec![]))),
2760 show(&ea, show_s, idref(&ea, b)),
2761 ];
2762 let out = compile_and_run(&stmts, &it).unwrap();
2763 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["true", "9", "true"]);
2764 assert_vm_eq_treewalk(&stmts, &it);
2765 }
2766
2767 #[test]
2768 fn vm_bitand_bitor_int_are_bitwise() {
2769 let ea: Arena<Expr> = Arena::new();
2771 let mut it = Interner::new();
2772 let a = it.intern("a");
2773 let b = it.intern("b");
2774 let show_s = it.intern("show");
2775 let stmts = vec![
2776 letb(a, bin(&ea, BinaryOpKind::BitAnd, num(&ea, 6), num(&ea, 3))),
2777 show(&ea, show_s, idref(&ea, a)),
2778 letb(b, bin(&ea, BinaryOpKind::BitOr, num(&ea, 6), num(&ea, 3))),
2779 show(&ea, show_s, idref(&ea, b)),
2780 ];
2781 let out = compile_and_run(&stmts, &it).unwrap();
2782 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["2", "7"]);
2783 assert_vm_eq_treewalk(&stmts, &it);
2784 }
2785
2786 #[test]
2787 fn vm_and_or_mixed_int_bool_uses_truthiness() {
2788 let ea: Arena<Expr> = Arena::new();
2790 let mut it = Interner::new();
2791 let a = it.intern("a");
2792 let b = it.intern("b");
2793 let show_s = it.intern("show");
2794 let stmts = vec![
2795 letb(a, bin(&ea, BinaryOpKind::And, num(&ea, 1), boolean(&ea, false))),
2796 show(&ea, show_s, idref(&ea, a)),
2797 letb(b, bin(&ea, BinaryOpKind::Or, num(&ea, 0), boolean(&ea, true))),
2798 show(&ea, show_s, idref(&ea, b)),
2799 ];
2800 let out = compile_and_run(&stmts, &it).unwrap();
2801 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["false", "true"]);
2802 assert_vm_eq_treewalk(&stmts, &it);
2803 }
2804
2805 #[test]
2806 fn vm_not_is_logical_truthiness() {
2807 let ea: Arena<Expr> = Arena::new();
2810 let mut it = Interner::new();
2811 let a = it.intern("a");
2812 let b = it.intern("b");
2813 let show_s = it.intern("show");
2814 let stmts = vec![
2815 letb(a, not_e(&ea, num(&ea, 6))),
2816 show(&ea, show_s, idref(&ea, a)),
2817 letb(b, not_e(&ea, boolean(&ea, true))),
2818 show(&ea, show_s, idref(&ea, b)),
2819 ];
2820 let out = compile_and_run(&stmts, &it).unwrap();
2821 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["false", "false"]);
2822 assert_vm_eq_treewalk(&stmts, &it);
2823 }
2824
2825 #[test]
2826 fn vm_not_text_is_emptiness_matches_treewalk() {
2827 let ea: Arena<Expr> = Arena::new();
2829 let mut it = Interner::new();
2830 let x = it.intern("x");
2831 let hi = it.intern("hi");
2832 let show_s = it.intern("show");
2833 let stmts = vec![
2834 letb(x, not_e(&ea, text(&ea, hi))),
2835 show(&ea, show_s, idref(&ea, x)),
2836 ];
2837 let out = compile_and_run(&stmts, &it).unwrap();
2838 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["false"]);
2839 assert_vm_eq_treewalk(&stmts, &it);
2840 }
2841
2842 #[test]
2843 fn vm_concat_bitxor_shl_shr_match_treewalk() {
2844 let ea: Arena<Expr> = Arena::new();
2845 let mut it = Interner::new();
2846 let show_s = it.intern("show");
2847 let names: Vec<Symbol> = (0..4).map(|k| it.intern(&format!("o{k}"))).collect();
2848 let stmts = vec![
2849 letb(names[0], bin(&ea, BinaryOpKind::Concat, num(&ea, 1), num(&ea, 2))),
2850 show(&ea, show_s, idref(&ea, names[0])),
2851 letb(names[1], bin(&ea, BinaryOpKind::BitXor, num(&ea, 6), num(&ea, 3))),
2852 show(&ea, show_s, idref(&ea, names[1])),
2853 letb(names[2], bin(&ea, BinaryOpKind::Shl, num(&ea, 1), num(&ea, 3))),
2854 show(&ea, show_s, idref(&ea, names[2])),
2855 letb(names[3], bin(&ea, BinaryOpKind::Shr, num(&ea, 16), num(&ea, 2))),
2856 show(&ea, show_s, idref(&ea, names[3])),
2857 ];
2858 let out = compile_and_run(&stmts, &it).unwrap();
2859 assert_eq!(out.lines().collect::<Vec<_>>(), vec!["12", "5", "8", "4"]);
2860 assert_vm_eq_treewalk(&stmts, &it);
2861 }
2862
2863 #[test]
2864 fn vm_char_nothing_and_temporal_literals_display_like_treewalk() {
2865 let ea: Arena<Expr> = Arena::new();
2866 let mut it = Interner::new();
2867 let show_s = it.intern("show");
2868 let lits: Vec<&Expr> = vec![
2869 ea.alloc(Expr::Literal(Literal::Char('x'))),
2870 ea.alloc(Expr::Literal(Literal::Nothing)),
2871 ea.alloc(Expr::Literal(Literal::Duration(1_500_000_000))),
2872 ea.alloc(Expr::Literal(Literal::Date(19753))),
2873 ea.alloc(Expr::Literal(Literal::Moment(86_400_000_000_000))),
2874 ea.alloc(Expr::Literal(Literal::Span { months: 2, days: 3 })),
2875 ea.alloc(Expr::Literal(Literal::Time(3_600_000_000_000))),
2876 ];
2877 let mut stmts = Vec::new();
2878 for (k, lit) in lits.into_iter().enumerate() {
2879 let v = it.intern(&format!("lit{k}"));
2880 stmts.push(letb(v, lit));
2881 stmts.push(show(&ea, show_s, idref(&ea, v)));
2882 }
2883 assert_vm_eq_treewalk(&stmts, &it);
2884 }
2885
2886 #[test]
2889 fn vm_compiles_300_element_list_literal() {
2890 let ea: Arena<Expr> = Arena::new();
2892 let mut it = Interner::new();
2893 let xs = it.intern("xs");
2894 let show_s = it.intern("show");
2895 let items: Vec<&Expr> = (0..300).map(|k| num(&ea, k)).collect();
2896 let stmts = vec![
2897 letb(xs, list_lit(&ea, items)),
2898 show(&ea, show_s, length_of(&ea, idref(&ea, xs))),
2899 show(&ea, show_s, index_at(&ea, idref(&ea, xs), num(&ea, 300))),
2900 ];
2901 let out = compile_and_run(&stmts, &it).unwrap();
2902 let lines: Vec<&str> = out.lines().collect();
2903 assert_eq!(lines, vec!["300", "299"]);
2904 assert_vm_eq_treewalk(&stmts, &it);
2905 }
2906
2907 #[test]
2908 fn vm_compiles_main_with_400_locals() {
2909 let ea: Arena<Expr> = Arena::new();
2912 let mut it = Interner::new();
2913 let show_s = it.intern("show");
2914 let mut stmts = Vec::new();
2915 let mut last = None;
2916 for k in 0..400i64 {
2917 let v = it.intern(&format!("v{k}"));
2918 stmts.push(letb(v, num(&ea, k)));
2919 last = Some(v);
2920 }
2921 stmts.push(show(&ea, show_s, idref(&ea, last.unwrap())));
2922 assert_eq!(compile_and_run(&stmts, &it).unwrap().trim(), "399");
2923 assert_vm_eq_treewalk(&stmts, &it);
2924 }
2925
2926 #[test]
2927 fn vm_const_pool_dedups_identical_literals() {
2928 let ea: Arena<Expr> = Arena::new();
2930 let mut it = Interner::new();
2931 let mut stmts = Vec::new();
2932 for k in 0..50 {
2933 let v = it.intern(&format!("c{k}"));
2934 stmts.push(letb(v, num(&ea, 7)));
2935 }
2936 let program = Compiler::compile(&stmts, &it).unwrap();
2937 assert_eq!(
2938 program.constants.len(),
2939 1,
2940 "identical Int literals must dedup to a single pool entry"
2941 );
2942 }
2943
2944 #[test]
2945 fn vm_patch_jump_is_total() {
2946 let mut code = vec![Op::Halt];
2949 assert!(super::compiler::patch_jump(&mut code, 0, 5).is_err());
2950
2951 let mut code = vec![Op::Jump { target: usize::MAX }];
2952 assert!(super::compiler::patch_jump(&mut code, 0, 5).is_ok());
2953 assert!(matches!(code[0], Op::Jump { target: 5 }));
2954
2955 let mut code = vec![Op::Halt];
2957 assert!(super::compiler::patch_jump(&mut code, 9, 5).is_err());
2958 }
2959
2960 #[test]
2961 fn vm_deeply_nested_expr_errors_not_overflows() {
2962 let ea: Arena<Expr> = Arena::new();
2965 let mut it = Interner::new();
2966 let x = it.intern("x");
2967 let mut e = num(&ea, 1);
2968 for _ in 0..100_000 {
2969 e = bin(&ea, BinaryOpKind::Add, e, num(&ea, 1));
2970 }
2971 let stmts = vec![letb(x, e)];
2972 let err = Compiler::compile(&stmts, &it).unwrap_err();
2973 assert!(err.contains("too deeply nested"), "got: {err}");
2974 }
2975
2976 #[test]
2977 fn vm_range_with_float_bound_errors_not_panics() {
2978 let ea: Arena<Expr> = Arena::new();
2981 let mut it = Interner::new();
2982 let xs = it.intern("xs");
2983 let f = ea.alloc(Expr::Literal(Literal::Float(2.5)));
2984 let stmts = vec![letb(xs, range_e(&ea, num(&ea, 1), f))];
2985 let vm_err = compile_and_run(&stmts, &it).unwrap_err();
2986 let tw_err = run_treewalk(&stmts, &it).unwrap_err();
2987 assert_eq!(vm_err, tw_err);
2988 }
2989
2990 #[test]
2991 fn vm_register_file_growth_is_capped() {
2992 let ea: Arena<Expr> = Arena::new();
3003 let sa: Arena<Stmt> = Arena::new();
3004 let ta: Arena<TypeExpr> = Arena::new();
3005 let mut it = Interner::new();
3006 let spin = it.intern("spin");
3007 let n = it.intern("n");
3008 let big = it.intern("big");
3009 let r = it.intern("r");
3010 let show_s = it.intern("show");
3011 let int_ty = ta.alloc(TypeExpr::Primitive(it.intern("Int")));
3012
3013 let then_blk: &[Stmt] = sa.alloc_slice(vec![ret(num(&ea, 0))]);
3014 let cond = bin(&ea, BinaryOpKind::GtEq, idref(&ea, n), num(&ea, 900));
3015 let wide: Vec<&Expr> = (0..3000).map(|_| num(&ea, 0)).collect();
3016 let rec = calle(&ea, spin, vec![bin(&ea, BinaryOpKind::Add, idref(&ea, n), num(&ea, 1))]);
3017 let rec_nontail = bin(&ea, BinaryOpKind::Subtract, rec, num(&ea, 1));
3018 let body: &[Stmt] = sa.alloc_slice(vec![
3019 Stmt::If { cond, then_block: then_blk, else_block: None },
3020 letb(big, list_lit(&ea, wide)),
3021 ret(rec_nontail),
3022 ]);
3023 let stmts = vec![
3024 fndef(spin, vec![(n, int_ty)], body),
3025 letb(r, calle(&ea, spin, vec![num(&ea, 0)])),
3026 show(&ea, show_s, idref(&ea, r)),
3027 ];
3028 let err = compile_and_run(&stmts, &it).unwrap_err();
3029 assert!(err.contains("register file"), "got: {err}");
3030 }
3031
3032 #[test]
3033 fn vm_if_else_takes_correct_branch() {
3034 let ea: Arena<Expr> = Arena::new();
3036 let sa: Arena<Stmt> = Arena::new();
3037 let mut it = Interner::new();
3038 let x = it.intern("x");
3039 let console = it.intern("show");
3040
3041 let three = ea.alloc(Expr::Literal(Literal::Number(3)));
3042 let x_c = ea.alloc(Expr::Identifier(x));
3043 let five = ea.alloc(Expr::Literal(Literal::Number(5)));
3044 let cond = ea.alloc(Expr::BinaryOp { op: BinaryOpKind::Gt, left: x_c, right: five });
3045
3046 let hundred = ea.alloc(Expr::Literal(Literal::Number(100)));
3047 let two_hundred = ea.alloc(Expr::Literal(Literal::Number(200)));
3048 let cref1 = ea.alloc(Expr::Identifier(console));
3049 let cref2 = ea.alloc(Expr::Identifier(console));
3050 let then_block: &[Stmt] = sa.alloc_slice(vec![Stmt::Show { object: hundred, recipient: cref1 }]);
3051 let else_block: &[Stmt] = sa.alloc_slice(vec![Stmt::Show { object: two_hundred, recipient: cref2 }]);
3052
3053 let stmts = vec![
3054 Stmt::Let { var: x, ty: None, value: three, mutable: false },
3055 Stmt::If { cond, then_block, else_block: Some(else_block) },
3056 ];
3057
3058 let out = compile_and_run(&stmts, &it).unwrap();
3059 assert_eq!(out.trim(), "200");
3060 assert_vm_eq_treewalk(&stmts, &it);
3061 }
3062}