1use crate::errors::QalaError;
53use crate::opcode::STDLIB_FN_BASE;
54use crate::value::Value;
55use crate::vm::{HeapObject, Vm};
56
57#[allow(dead_code)]
66const VARIANT_ID_OK: u16 = 0;
67
68pub fn dispatch(vm: &mut Vm, fn_id: u16, args: &[Value]) -> Result<Value, QalaError> {
84 if fn_id < STDLIB_FN_BASE {
85 return Err(vm.runtime_err(&format!("internal error: fn-id {fn_id} is not a stdlib id")));
86 }
87 match fn_id {
88 40000 => print(vm, args),
89 40001 => println(vm, args),
90 40002 => sqrt(vm, args),
91 40003 => abs(vm, args),
92 40004 => assert(vm, args),
93 40005 => len(vm, args),
94 40006 => push(vm, args),
95 40007 => pop(vm, args),
96 40008 => type_of(vm, args),
97 40009 => open(vm, args),
98 40010 => close(vm, args),
99 40011 => map(vm, args),
100 40012 => filter(vm, args),
101 40013 => reduce(vm, args),
102 40014 => read_all(vm, args),
103 other => Err(vm.runtime_err(&format!("unknown stdlib function {other}"))),
104 }
105}
106
107fn one_arg(vm: &Vm, args: &[Value], name: &str) -> Result<Value, QalaError> {
115 match args {
116 [a] => Ok(*a),
117 _ => Err(vm.runtime_err(&format!("{name} expects 1 argument, got {}", args.len()))),
118 }
119}
120
121fn alloc_int(vm: &mut Vm, n: i64) -> Result<Value, QalaError> {
124 let slot = vm
125 .heap
126 .alloc(HeapObject::Int(n))
127 .ok_or_else(|| vm.runtime_err("heap exhausted"))?;
128 Ok(Value::pointer(slot))
129}
130
131fn alloc_str(vm: &mut Vm, s: String) -> Result<Value, QalaError> {
134 let slot = vm
135 .heap
136 .alloc(HeapObject::Str(s))
137 .ok_or_else(|| vm.runtime_err("heap exhausted"))?;
138 Ok(Value::pointer(slot))
139}
140
141fn alloc_array(vm: &mut Vm, items: Vec<Value>) -> Result<Value, QalaError> {
144 let slot = vm
145 .heap
146 .alloc(HeapObject::Array(items))
147 .ok_or_else(|| vm.runtime_err("heap exhausted"))?;
148 Ok(Value::pointer(slot))
149}
150
151fn make_option(vm: &mut Vm, payload: Option<Value>) -> Result<Value, QalaError> {
158 let object = match payload {
159 Some(v) => HeapObject::EnumVariant {
160 type_name: "Option".to_string(),
161 variant: "Some".to_string(),
162 payload: vec![v],
163 },
164 None => HeapObject::EnumVariant {
165 type_name: "Option".to_string(),
166 variant: "None".to_string(),
167 payload: Vec::new(),
168 },
169 };
170 let slot = vm
171 .heap
172 .alloc(object)
173 .ok_or_else(|| vm.runtime_err("heap exhausted"))?;
174 Ok(Value::pointer(slot))
175}
176
177fn make_ok(vm: &mut Vm, payload: Value) -> Result<Value, QalaError> {
180 let slot = vm
181 .heap
182 .alloc(HeapObject::EnumVariant {
183 type_name: "Result".to_string(),
184 variant: "Ok".to_string(),
185 payload: vec![payload],
186 })
187 .ok_or_else(|| vm.runtime_err("heap exhausted"))?;
188 Ok(Value::pointer(slot))
189}
190
191fn print(vm: &mut Vm, args: &[Value]) -> Result<Value, QalaError> {
204 let arg = one_arg(vm, args, "print")?;
205 let text = vm.value_to_string(arg);
206 vm.console.push(text);
207 Ok(Value::void())
208}
209
210fn println(vm: &mut Vm, args: &[Value]) -> Result<Value, QalaError> {
219 let arg = one_arg(vm, args, "println")?;
220 let mut text = vm.value_to_string(arg);
221 text.push('\n');
222 vm.console.push(text);
223 Ok(Value::void())
224}
225
226fn sqrt(vm: &mut Vm, args: &[Value]) -> Result<Value, QalaError> {
231 let arg = one_arg(vm, args, "sqrt")?;
232 let x = arg
233 .as_f64()
234 .ok_or_else(|| vm.runtime_err("sqrt: expected a float"))?;
235 Ok(Value::from_f64(x.sqrt()))
236}
237
238fn abs(vm: &mut Vm, args: &[Value]) -> Result<Value, QalaError> {
246 let arg = one_arg(vm, args, "abs")?;
247 if let Some(x) = arg.as_f64() {
249 return Ok(Value::from_f64(x.abs()));
250 }
251 let int = arg
253 .as_pointer()
254 .and_then(|slot| match vm.heap.get(slot) {
255 Some(HeapObject::Int(n)) => Some(*n),
256 _ => None,
257 })
258 .ok_or_else(|| vm.runtime_err("abs: expected an integer or a float"))?;
259 let result = int
260 .checked_abs()
261 .ok_or_else(|| vm.runtime_err("abs: integer overflow"))?;
262 alloc_int(vm, result)
263}
264
265fn assert(vm: &mut Vm, args: &[Value]) -> Result<Value, QalaError> {
270 let arg = one_arg(vm, args, "assert")?;
271 let condition = arg
272 .as_bool()
273 .ok_or_else(|| vm.runtime_err("assert: expected a boolean"))?;
274 if condition {
275 Ok(Value::void())
276 } else {
277 Err(vm.runtime_err("assertion failed"))
278 }
279}
280
281fn len(vm: &mut Vm, args: &[Value]) -> Result<Value, QalaError> {
288 let arg = one_arg(vm, args, "len")?;
289 let slot = arg
290 .as_pointer()
291 .ok_or_else(|| vm.runtime_err("len: expected an array or string"))?;
292 let length = match vm.heap.get(slot) {
293 Some(HeapObject::Array(items)) | Some(HeapObject::Tuple(items)) => items.len(),
294 Some(HeapObject::Str(s)) => s.chars().count(),
295 _ => return Err(vm.runtime_err("len: expected an array or string")),
296 };
297 alloc_int(vm, length as i64)
298}
299
300fn push(vm: &mut Vm, args: &[Value]) -> Result<Value, QalaError> {
307 let (array, value) = match args {
308 [a, v] => (*a, *v),
309 _ => {
310 return Err(vm.runtime_err(&format!("push expects 2 arguments, got {}", args.len())));
311 }
312 };
313 let slot = array
314 .as_pointer()
315 .ok_or_else(|| vm.runtime_err("push: expected an array"))?;
316 match vm.heap.get_mut(slot) {
317 Some(HeapObject::Array(items)) => {
318 items.push(value);
319 Ok(Value::void())
320 }
321 _ => Err(vm.runtime_err("push: expected an array")),
322 }
323}
324
325fn pop(vm: &mut Vm, args: &[Value]) -> Result<Value, QalaError> {
331 let arg = one_arg(vm, args, "pop")?;
332 let slot = arg
333 .as_pointer()
334 .ok_or_else(|| vm.runtime_err("pop: expected an array"))?;
335 let removed = match vm.heap.get_mut(slot) {
336 Some(HeapObject::Array(items)) => items.pop(),
337 _ => return Err(vm.runtime_err("pop: expected an array")),
338 };
339 make_option(vm, removed)
340}
341
342fn type_of(vm: &mut Vm, args: &[Value]) -> Result<Value, QalaError> {
350 let arg = one_arg(vm, args, "type_of")?;
351 let name = vm.runtime_type_name(arg);
352 alloc_str(vm, name)
353}
354
355fn open(vm: &mut Vm, args: &[Value]) -> Result<Value, QalaError> {
363 let arg = one_arg(vm, args, "open")?;
364 let path_slot = arg
365 .as_pointer()
366 .ok_or_else(|| vm.runtime_err("open: expected a string path"))?;
367 let path = match vm.heap.get(path_slot) {
368 Some(HeapObject::Str(s)) => s.clone(),
369 _ => return Err(vm.runtime_err("open: expected a string path")),
370 };
371 let slot = vm
372 .heap
373 .alloc(HeapObject::FileHandle {
374 path,
375 content: String::new(),
376 closed: false,
377 })
378 .ok_or_else(|| vm.runtime_err("heap exhausted"))?;
379 Ok(Value::pointer(slot))
380}
381
382fn close(vm: &mut Vm, args: &[Value]) -> Result<Value, QalaError> {
388 let arg = one_arg(vm, args, "close")?;
389 let slot = arg
390 .as_pointer()
391 .ok_or_else(|| vm.runtime_err("close: expected a file handle"))?;
392 match vm.heap.get_mut(slot) {
393 Some(HeapObject::FileHandle { closed, .. }) => {
394 *closed = true;
395 Ok(Value::void())
396 }
397 _ => Err(vm.runtime_err("close: expected a file handle")),
398 }
399}
400
401fn map(vm: &mut Vm, args: &[Value]) -> Result<Value, QalaError> {
411 let (array, callback) = match args {
412 [a, c] => (*a, *c),
413 _ => {
414 return Err(vm.runtime_err(&format!("map expects 2 arguments, got {}", args.len())));
415 }
416 };
417 let elements = heap_array_elements(vm, array, "map")?;
418 let mut results: Vec<Value> = Vec::with_capacity(elements.len());
419 for element in elements {
420 let mapped = vm.call_function_value(callback, &[element])?;
421 results.push(mapped);
422 }
423 alloc_array(vm, results)
424}
425
426fn filter(vm: &mut Vm, args: &[Value]) -> Result<Value, QalaError> {
433 let (array, callback) = match args {
434 [a, c] => (*a, *c),
435 _ => {
436 return Err(vm.runtime_err(&format!("filter expects 2 arguments, got {}", args.len())));
437 }
438 };
439 let elements = heap_array_elements(vm, array, "filter")?;
440 let mut kept: Vec<Value> = Vec::new();
441 for element in elements {
442 let verdict = vm.call_function_value(callback, &[element])?;
443 let keep = verdict
444 .as_bool()
445 .ok_or_else(|| vm.runtime_err("filter: the callback must return a boolean"))?;
446 if keep {
447 kept.push(element);
448 }
449 }
450 alloc_array(vm, kept)
451}
452
453fn reduce(vm: &mut Vm, args: &[Value]) -> Result<Value, QalaError> {
461 let (array, initial, callback) = match args {
462 [a, i, c] => (*a, *i, *c),
463 _ => {
464 return Err(vm.runtime_err(&format!("reduce expects 3 arguments, got {}", args.len())));
465 }
466 };
467 let elements = heap_array_elements(vm, array, "reduce")?;
468 let mut accumulator = initial;
469 for element in elements {
470 accumulator = vm.call_function_value(callback, &[accumulator, element])?;
471 }
472 Ok(accumulator)
473}
474
475fn read_all(vm: &mut Vm, args: &[Value]) -> Result<Value, QalaError> {
483 let arg = one_arg(vm, args, "read_all")?;
484 let slot = arg
485 .as_pointer()
486 .ok_or_else(|| vm.runtime_err("read_all: expected a file handle"))?;
487 let content = match vm.heap.get(slot) {
488 Some(HeapObject::FileHandle { content, .. }) => content.clone(),
489 _ => return Err(vm.runtime_err("read_all: expected a file handle")),
490 };
491 let payload = alloc_str(vm, content)?;
492 make_ok(vm, payload)
493}
494
495fn heap_array_elements(vm: &Vm, value: Value, name: &str) -> Result<Vec<Value>, QalaError> {
503 let slot = value
504 .as_pointer()
505 .ok_or_else(|| vm.runtime_err(&format!("{name}: expected an array")))?;
506 match vm.heap.get(slot) {
507 Some(HeapObject::Array(items)) => Ok(items.clone()),
508 _ => Err(vm.runtime_err(&format!("{name}: expected an array"))),
509 }
510}
511
512#[cfg(test)]
513mod tests {
514 use super::*;
515 use crate::chunk::{Chunk, Program};
516 use crate::codegen::compile_program;
517 use crate::lexer::Lexer;
518 use crate::parser::Parser;
519 use crate::typechecker::check_program;
520
521 fn bare_vm() -> Vm {
527 let mut chunk = Chunk::new();
528 chunk.write_op(crate::opcode::Opcode::Return, 1);
529 let mut program = Program::new();
530 program.chunks.push(chunk);
531 program.fn_names.push("main".to_string());
532 program.main_index = 0;
533 Vm::new(program, String::new())
534 }
535
536 fn compile(src: &str) -> Program {
541 let tokens = Lexer::tokenize(src).expect("lex");
542 let ast = Parser::parse(&tokens).expect("parse");
543 let (typed, errors, _) = check_program(&ast, src);
544 assert!(errors.is_empty(), "typecheck errors: {errors:?}");
545 compile_program(&typed, src).expect("codegen")
546 }
547
548 fn fn_id(program: &Program, name: &str) -> u16 {
550 program
551 .fn_names
552 .iter()
553 .position(|n| n == name)
554 .unwrap_or_else(|| panic!("no function named {name}")) as u16
555 }
556
557 fn int(vm: &mut Vm, n: i64) -> Value {
559 alloc_int(vm, n).expect("alloc int")
560 }
561
562 fn string(vm: &mut Vm, s: &str) -> Value {
564 alloc_str(vm, s.to_string()).expect("alloc str")
565 }
566
567 fn result_i64(vm: &Vm, value: Value) -> i64 {
569 let slot = value.as_pointer().expect("a heap pointer");
570 match vm.heap.get(slot) {
571 Some(HeapObject::Int(n)) => *n,
572 _ => panic!("the value does not point at a heap Int"),
573 }
574 }
575
576 fn result_str(vm: &Vm, value: Value) -> String {
578 let slot = value.as_pointer().expect("a heap pointer");
579 match vm.heap.get(slot) {
580 Some(HeapObject::Str(s)) => s.clone(),
581 _ => panic!("the value does not point at a heap Str"),
582 }
583 }
584
585 fn result_array(vm: &Vm, value: Value) -> Vec<Value> {
587 let slot = value.as_pointer().expect("a heap pointer");
588 match vm.heap.get(slot) {
589 Some(HeapObject::Array(items)) => items.clone(),
590 _ => panic!("the value does not point at a heap array"),
591 }
592 }
593
594 fn program_result(vm: &Vm) -> String {
602 vm.get_state()
603 .stack
604 .last()
605 .expect("a finished program left a result value")
606 .rendered
607 .clone()
608 }
609
610 #[test]
611 fn len_of_an_array_and_of_a_string() {
612 let mut vm = bare_vm();
613 let a = int(&mut vm, 10);
615 let b = int(&mut vm, 20);
616 let c = int(&mut vm, 30);
617 let array = alloc_array(&mut vm, vec![a, b, c]).expect("alloc array");
618 let array_len = len(&mut vm, &[array]).expect("len of an array");
619 assert_eq!(result_i64(&vm, array_len), 3, "an array of 3 has len 3");
620 let s = string(&mut vm, "hello");
622 let str_len = len(&mut vm, &[s]).expect("len of a string");
623 assert_eq!(result_i64(&vm, str_len), 5, "\"hello\" has len 5");
624 }
625
626 #[test]
627 fn push_appends_and_pop_returns_some_then_none() {
628 let mut vm = bare_vm();
629 let array = alloc_array(&mut vm, Vec::new()).expect("alloc array");
630 let one = int(&mut vm, 1);
632 let two = int(&mut vm, 2);
633 push(&mut vm, &[array, one]).expect("push 1");
634 push(&mut vm, &[array, two]).expect("push 2");
635 assert_eq!(result_array(&vm, array).len(), 2, "two pushes -> length 2");
636 let popped = pop(&mut vm, &[array]).expect("pop");
638 let popped_slot = popped.as_pointer().expect("Some is a heap pointer");
639 match vm.heap.get(popped_slot) {
640 Some(HeapObject::EnumVariant {
641 variant, payload, ..
642 }) => {
643 assert_eq!(variant, "Some", "a non-empty pop returns Some");
644 assert_eq!(result_i64(&vm, payload[0]), 2, "the last element popped");
645 }
646 _ => panic!("pop must return an Option enum variant"),
647 }
648 pop(&mut vm, &[array]).expect("pop the last element");
650 let none = pop(&mut vm, &[array]).expect("pop an empty array");
651 let none_slot = none.as_pointer().expect("None is a heap pointer");
652 match vm.heap.get(none_slot) {
653 Some(HeapObject::EnumVariant { variant, .. }) => {
654 assert_eq!(variant, "None", "an empty pop returns None");
655 }
656 _ => panic!("pop of an empty array must return None"),
657 }
658 }
659
660 #[test]
661 fn sqrt_of_four_is_two() {
662 let mut vm = bare_vm();
663 let result = sqrt(&mut vm, &[Value::from_f64(4.0)]).expect("sqrt");
664 assert_eq!(result.as_f64(), Some(2.0), "sqrt(4.0) == 2.0");
665 }
666
667 #[test]
668 fn abs_of_a_negative_int_and_a_negative_float() {
669 let mut vm = bare_vm();
670 let neg_three = int(&mut vm, -3);
672 let abs_int = abs(&mut vm, &[neg_three]).expect("abs of an int");
673 assert_eq!(result_i64(&vm, abs_int), 3, "abs(-3) == 3");
674 let abs_float = abs(&mut vm, &[Value::from_f64(-1.5)]).expect("abs of a float");
676 assert_eq!(abs_float.as_f64(), Some(1.5), "abs(-1.5) == 1.5");
677 }
678
679 #[test]
680 fn assert_true_is_a_no_op_and_assert_false_is_a_runtime_error() {
681 let mut vm = bare_vm();
682 let ok = assert(&mut vm, &[Value::bool(true)]).expect("assert(true)");
684 assert!(ok.as_void(), "assert(true) returns void");
685 match assert(&mut vm, &[Value::bool(false)]) {
687 Err(QalaError::Runtime { message, .. }) => {
688 assert!(message.contains("assertion failed"), "got: {message}");
689 }
690 Err(other) => panic!("expected an assertion-failed Runtime error, got {other:?}"),
691 Ok(_) => panic!("assert(false) must error"),
692 }
693 }
694
695 #[test]
696 fn type_of_returns_the_runtime_type_name_for_each_kind() {
697 let mut vm = bare_vm();
698 let i64_value = int(&mut vm, 7);
700 let str_value = string(&mut vm, "hi");
701 let e0 = int(&mut vm, 1);
703 let e1 = int(&mut vm, 2);
704 let i64_array = alloc_array(&mut vm, vec![e0, e1]).expect("alloc array");
705 let shape_struct = vm
707 .heap
708 .alloc(HeapObject::Struct {
709 type_name: "Shape".to_string(),
710 fields: Vec::new(),
711 })
712 .expect("alloc struct");
713 let shape_variant = vm
714 .heap
715 .alloc(HeapObject::EnumVariant {
716 type_name: "Shape".to_string(),
717 variant: "Circle".to_string(),
718 payload: Vec::new(),
719 })
720 .expect("alloc variant");
721 let cases: Vec<(Value, &str)> = vec![
722 (i64_value, "i64"),
723 (Value::from_f64(1.5), "f64"),
724 (Value::bool(true), "bool"),
725 (str_value, "str"),
726 (Value::byte(65), "byte"),
727 (Value::void(), "void"),
728 (i64_array, "[i64]"),
729 (Value::pointer(shape_struct), "Shape"),
730 (Value::pointer(shape_variant), "Shape::Circle"),
731 ];
732 for (value, expected) in cases {
733 let result = type_of(&mut vm, &[value]).expect("type_of");
734 assert_eq!(result_str(&vm, result), expected, "type_of mismatch");
735 }
736 }
737
738 #[test]
739 fn print_and_println_append_to_the_console() {
740 let mut vm = bare_vm();
741 let line = string(&mut vm, "first line");
743 println(&mut vm, &[line]).expect("println");
744 assert_eq!(vm.console, vec!["first line\n".to_string()]);
745 let a = string(&mut vm, "a");
747 let b = string(&mut vm, "b");
748 print(&mut vm, &[a]).expect("print a");
749 print(&mut vm, &[b]).expect("print b");
750 assert_eq!(
751 vm.console,
752 vec!["first line\n".to_string(), "a".to_string(), "b".to_string(),],
753 "println entries end with newline; print entries do not"
754 );
755 }
756
757 #[test]
758 fn println_adds_newline_print_does_not() {
759 let mut vm = bare_vm();
762 let a = string(&mut vm, "a");
763 let b = string(&mut vm, "b");
764 let c = string(&mut vm, "c");
765 println(&mut vm, &[a]).expect("println a");
766 println(&mut vm, &[b]).expect("println b");
767 print(&mut vm, &[c]).expect("print c");
768 let joined: String = vm.console.concat();
769 assert_eq!(
770 joined, "a\nb\nc",
771 "two println then print joined is a\\nb\\nc"
772 );
773 }
774
775 #[test]
776 fn open_returns_a_file_handle_and_close_marks_it_closed() {
777 let mut vm = bare_vm();
778 let path = string(&mut vm, "data.txt");
779 let handle = open(&mut vm, &[path]).expect("open");
780 let slot = handle.as_pointer().expect("open returns a heap pointer");
781 match vm.heap.get(slot) {
783 Some(HeapObject::FileHandle { path, closed, .. }) => {
784 assert_eq!(path, "data.txt", "the handle carries the path");
785 assert!(!closed, "a freshly opened handle is open");
786 }
787 _ => panic!("open must return a FileHandle"),
788 }
789 let void = close(&mut vm, &[handle]).expect("close");
791 assert!(void.as_void(), "close returns void");
792 match vm.heap.get(slot) {
793 Some(HeapObject::FileHandle { closed, .. }) => {
794 assert!(closed, "close marks the handle closed");
795 }
796 _ => panic!("the handle is still a FileHandle after close"),
797 }
798 }
799
800 #[test]
801 fn read_all_returns_ok_with_the_handle_content() {
802 let mut vm = bare_vm();
803 let path = string(&mut vm, "data.txt");
804 let handle = open(&mut vm, &[path]).expect("open");
805 let result = read_all(&mut vm, &[handle]).expect("read_all");
808 let slot = result.as_pointer().expect("Ok is a heap pointer");
809 match vm.heap.get(slot) {
810 Some(HeapObject::EnumVariant {
811 type_name,
812 variant,
813 payload,
814 }) => {
815 assert_eq!(type_name, "Result", "read_all returns a Result");
816 assert_eq!(variant, "Ok", "the mock read succeeds");
817 assert_eq!(
818 result_str(&vm, payload[0]),
819 "",
820 "the v1 mock content is empty"
821 );
822 }
823 _ => panic!("read_all must return a Result enum variant"),
824 }
825 }
826
827 #[test]
828 fn map_applies_a_user_callback_to_each_element() {
829 let src = "fn double(x: i64) -> i64 is pure { return x * 2 }\n\
831 fn main() is io {}\n";
832 let program = compile(src);
833 let double = fn_id(&program, "double");
834 let mut vm = Vm::new(program, src.to_string());
835 let e0 = int(&mut vm, 1);
836 let e1 = int(&mut vm, 2);
837 let e2 = int(&mut vm, 3);
838 let array = alloc_array(&mut vm, vec![e0, e1, e2]).expect("alloc array");
839 let mapped = map(&mut vm, &[array, Value::function(double)]).expect("map");
840 let items = result_array(&vm, mapped);
841 let values: Vec<i64> = items.iter().map(|v| result_i64(&vm, *v)).collect();
842 assert_eq!(values, vec![2, 4, 6], "map(double) doubles every element");
843 }
844
845 #[test]
846 fn filter_keeps_the_elements_the_callback_accepts() {
847 let src = "fn is_even(x: i64) -> bool is pure { return x % 2 == 0 }\n\
849 fn main() is io {}\n";
850 let program = compile(src);
851 let is_even = fn_id(&program, "is_even");
852 let mut vm = Vm::new(program, src.to_string());
853 let mut elements = Vec::new();
854 for n in 1..=6 {
855 elements.push(int(&mut vm, n));
856 }
857 let array = alloc_array(&mut vm, elements).expect("alloc array");
858 let filtered = filter(&mut vm, &[array, Value::function(is_even)]).expect("filter");
859 let items = result_array(&vm, filtered);
860 let values: Vec<i64> = items.iter().map(|v| result_i64(&vm, *v)).collect();
861 assert_eq!(values, vec![2, 4, 6], "filter(is_even) keeps the evens");
862 }
863
864 #[test]
865 fn reduce_folds_an_array_with_the_callback() {
866 let src = "fn add(a: i64, b: i64) -> i64 is pure { return a + b }\n\
868 fn main() is io {}\n";
869 let program = compile(src);
870 let add = fn_id(&program, "add");
871 let mut vm = Vm::new(program, src.to_string());
872 let mut elements = Vec::new();
873 for n in 1..=4 {
874 elements.push(int(&mut vm, n));
875 }
876 let array = alloc_array(&mut vm, elements).expect("alloc array");
877 let initial = int(&mut vm, 0);
878 let total = reduce(&mut vm, &[array, initial, Value::function(add)]).expect("reduce");
879 assert_eq!(result_i64(&vm, total), 10, "reduce(+) of 1..=4 is 10");
880 }
881
882 #[test]
883 fn dispatch_routes_each_fn_id_and_rejects_an_unknown_one() {
884 let mut vm = bare_vm();
885 let via_dispatch = dispatch(&mut vm, 40002, &[Value::from_f64(9.0)]).expect("sqrt");
887 assert_eq!(via_dispatch.as_f64(), Some(3.0), "dispatch(40002) is sqrt");
888 match dispatch(&mut vm, 49999, &[]) {
890 Err(QalaError::Runtime { message, .. }) => {
891 assert!(
892 message.contains("unknown stdlib function"),
893 "got: {message}"
894 );
895 }
896 Err(other) => panic!("expected an unknown-stdlib Runtime error, got {other:?}"),
897 Ok(_) => panic!("an unknown fn-id must error"),
898 }
899 match dispatch(&mut vm, 5, &[]) {
901 Err(QalaError::Runtime { message, .. }) => {
902 assert!(message.contains("not a stdlib id"), "got: {message}");
903 }
904 Err(other) => panic!("expected an invariant Runtime error, got {other:?}"),
905 Ok(_) => panic!("a user fn-id must not dispatch as stdlib"),
906 }
907 }
908
909 #[test]
910 fn a_wrong_argument_count_is_a_runtime_error_not_a_panic() {
911 let mut vm = bare_vm();
912 match sqrt(&mut vm, &[]) {
914 Err(QalaError::Runtime { message, .. }) => {
915 assert!(message.contains("expects 1 argument"), "got: {message}");
916 }
917 Err(other) => panic!("expected an arity Runtime error, got {other:?}"),
918 Ok(_) => panic!("sqrt with no argument must error"),
919 }
920 match len(&mut vm, &[Value::bool(true)]) {
922 Err(QalaError::Runtime { message, .. }) => {
923 assert!(
924 message.contains("expected an array or string"),
925 "got: {message}"
926 );
927 }
928 Err(other) => panic!("expected a wrong-type Runtime error, got {other:?}"),
929 Ok(_) => panic!("len of a bool must error"),
930 }
931 }
932
933 #[test]
936 fn map_reentrant_a_nested_map_inside_the_callback_works() {
937 let src = "fn double(x: i64) -> i64 is pure { return x * 2 }\n\
944 fn double_row(row: [i64]) -> [i64] is pure {\n \
945 return map(row, double)\n}\n\
946 fn main() -> i64 is pure {\n \
947 let grid = [[1, 2], [3, 4]]\n \
948 let doubled = map(grid, double_row)\n \
949 return doubled[1][1]\n}\n";
950 let program = compile(src);
951 let mut vm = Vm::new(program, src.to_string());
952 vm.run().expect("a nested-map program runs clean");
953 assert_eq!(
954 program_result(&vm),
955 "8",
956 "the nested map doubled [3,4] to [6,8]; [1][1] is 8"
957 );
958 }
959
960 #[test]
961 fn fibonacci_smoke_computes_the_correct_numeric_result() {
962 let src = "fn fib(n: i64) -> i64 is pure {\n \
965 if n <= 1 { return n }\n \
966 return fib(n - 1) + fib(n - 2)\n}\n\
967 fn main() -> i64 is pure {\n return fib(10)\n}\n";
968 let program = compile(src);
969 let mut vm = Vm::new(program, src.to_string());
970 vm.run().expect("the fibonacci program runs clean");
971 assert_eq!(program_result(&vm), "55", "fib(10) must be 55");
972 }
973
974 fn example_source(name: &str) -> String {
976 let path = format!(
977 "{}/../../playground/public/examples/{name}.qala",
978 env!("CARGO_MANIFEST_DIR"),
979 );
980 std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {path}: {e}"))
981 }
982
983 fn run_example(name: &str) -> Vm {
986 let src = example_source(name);
987 let program = compile(&src);
988 let mut vm = Vm::new(program, src);
989 vm.run()
990 .unwrap_or_else(|e| panic!("{name}.qala did not run to completion: {e:?}"));
991 vm
992 }
993
994 #[test]
995 fn six_examples_run_to_completion() {
996 let hello = run_example("hello");
1002 assert!(
1003 hello
1004 .console
1005 .iter()
1006 .any(|l| l.trim_end_matches('\n') == "hello, world!"),
1007 "hello.qala console: {:?}",
1008 hello.console,
1009 );
1010
1011 let fibonacci = run_example("fibonacci");
1014 assert!(
1015 fibonacci
1016 .console
1017 .iter()
1018 .any(|l| l.trim_end_matches('\n') == "fib(0) = 0"),
1019 "fibonacci.qala console: {:?}",
1020 fibonacci.console,
1021 );
1022 assert!(
1023 fibonacci
1024 .console
1025 .iter()
1026 .any(|l| l.trim_end_matches('\n') == "fib(10) = 55"),
1027 "fibonacci.qala must print fib(10) = 55, console: {:?}",
1028 fibonacci.console,
1029 );
1030 assert!(
1031 fibonacci
1032 .console
1033 .iter()
1034 .any(|l| l.trim_end_matches('\n') == "fib(14) = 377"),
1035 "fibonacci.qala must print fib(14) = 377, console: {:?}",
1036 fibonacci.console,
1037 );
1038
1039 let effects = run_example("effects");
1041 assert!(
1042 effects
1043 .console
1044 .iter()
1045 .any(|l| l.trim_end_matches('\n') == "7 squared: 49"),
1046 "effects.qala console: {:?}",
1047 effects.console,
1048 );
1049
1050 let pattern_matching = run_example("pattern-matching");
1053 assert!(
1054 !pattern_matching.console.is_empty(),
1055 "pattern-matching.qala must print something",
1056 );
1057 assert!(
1058 pattern_matching
1059 .console
1060 .iter()
1061 .any(|l| l.trim_end_matches('\n') == "positive"),
1062 "pattern-matching.qala classify(42) must print positive, console: {:?}",
1063 pattern_matching.console,
1064 );
1065 assert!(
1066 pattern_matching
1067 .console
1068 .iter()
1069 .any(|l| l.trim_end_matches('\n') == "negative"),
1070 "pattern-matching.qala classify(-7) must print negative, console: {:?}",
1071 pattern_matching.console,
1072 );
1073 assert!(
1074 pattern_matching
1075 .console
1076 .iter()
1077 .any(|l| l.trim_end_matches('\n') == "zero"),
1078 "pattern-matching.qala classify(0) must print zero, console: {:?}",
1079 pattern_matching.console,
1080 );
1081
1082 let pipeline = run_example("pipeline");
1086 assert!(
1087 pipeline.console.iter().any(|l| l.contains("= 22")),
1088 "pipeline.qala must print the pipeline result 22, console: {:?}",
1089 pipeline.console,
1090 );
1091 assert!(
1092 pipeline
1093 .console
1094 .iter()
1095 .any(|l| l.trim_end_matches('\n') == "20"),
1096 "pipeline.qala filter+map must print the doubled even 20, console: {:?}",
1097 pipeline.console,
1098 );
1099
1100 let defer_demo = run_example("defer-demo");
1104 assert!(
1105 defer_demo.console.iter().any(|l| l.starts_with("got: ")),
1106 "defer-demo.qala must print a got: line, console: {:?}",
1107 defer_demo.console,
1108 );
1109 assert!(
1111 defer_demo.leak_log.is_empty(),
1112 "defer-demo.qala closes its handle via defer -- no leak, got: {:?}",
1113 defer_demo.leak_log,
1114 );
1115 }
1116}