1#![cfg_attr(feature = "no_std", no_std)]
2
3#[cfg(feature = "no_std")]
4extern crate alloc;
5
6#[cfg(feature = "no_std")]
7use alloc::{string::String, vec::Vec};
8
9pub mod error;
10pub mod error_messages;
11pub mod value;
12pub mod lexer;
13pub mod parser;
14pub mod math;
15pub mod memory;
16pub mod naming;
17pub mod ops;
18pub mod precheck;
19pub mod builtins;
20pub mod host;
21pub mod methods;
22pub mod suggest;
23pub mod check;
24#[cfg(feature = "bop-std")]
34pub mod stdlib;
35
36mod evaluator;
37
38pub use error::BopError;
39pub use error::BopWarning;
40pub use parser::{Stmt, count_instructions};
41pub use value::Value;
42
43pub use evaluator::pattern_matches;
47
48pub use evaluator::resolve_type_in;
55
56pub use evaluator::TypeResolveFn;
58
59pub use evaluator::ReplSession;
66
67#[derive(Debug, Clone)]
71pub struct BopLimits {
72 pub max_steps: u64,
74 pub max_memory: usize,
76}
77
78impl BopLimits {
79 pub fn standard() -> Self {
80 Self {
81 max_steps: 10_000,
82 max_memory: 10 * 1024 * 1024, }
84 }
85
86 pub fn demo() -> Self {
87 Self {
88 max_steps: 1_000,
89 max_memory: 1024 * 1024, }
91 }
92}
93
94impl Default for BopLimits {
95 fn default() -> Self {
96 Self::standard()
97 }
98}
99
100pub trait BopHost {
104 fn call(&mut self, name: &str, args: &[Value], line: u32) -> Option<Result<Value, BopError>>;
106
107 fn on_print(&mut self, message: &str) {
109 let _ = message;
110 }
111
112 fn function_hint(&self) -> &str {
114 ""
115 }
116
117 fn on_tick(&mut self) -> Result<(), BopError> {
119 Ok(())
120 }
121
122 fn resolve_module(&mut self, name: &str) -> Option<Result<String, BopError>> {
139 let _ = name;
140 None
141 }
142}
143
144pub fn run<H: BopHost>(source: &str, host: &mut H, limits: &BopLimits) -> Result<(), BopError> {
148 let tokens = lexer::lex(source)?;
149 let stmts = parser::parse(tokens)?;
150 let eval = evaluator::Evaluator::new(host, limits.clone());
151 eval.run(&stmts)
152}
153
154pub fn parse(source: &str) -> Result<Vec<Stmt>, BopError> {
156 let tokens = lexer::lex(source)?;
157 parser::parse(tokens)
158}
159
160pub fn parse_with_warnings(
171 source: &str,
172) -> Result<(Vec<Stmt>, Vec<error::BopWarning>), BopError> {
173 let stmts = parse(source)?;
174 let warnings = check::check_program(&stmts);
175 Ok((stmts, warnings))
176}
177
178pub fn parse_with_warnings_and_resolver<R>(
189 source: &str,
190 mut resolver: R,
191) -> Result<(Vec<Stmt>, Vec<error::BopWarning>), BopError>
192where
193 R: FnMut(&str) -> Option<Result<String, BopError>>,
194{
195 let stmts = parse(source)?;
196 let warnings = check::check_program_with_resolver(&stmts, &mut resolver);
197 Ok((stmts, warnings))
198}
199
200#[cfg(test)]
203mod tests {
204 use super::*;
205 use std::cell::RefCell;
206
207 struct TestHost {
210 prints: RefCell<Vec<String>>,
211 }
212
213 impl TestHost {
214 fn new() -> Self {
215 Self {
216 prints: RefCell::new(Vec::new()),
217 }
218 }
219
220 fn last_print(&self) -> String {
221 self.prints.borrow().last().cloned().expect("no print output")
222 }
223 }
224
225 impl BopHost for TestHost {
226 fn call(&mut self, _name: &str, _args: &[Value], _line: u32) -> Option<Result<Value, BopError>> {
227 None
228 }
229
230 fn on_print(&mut self, message: &str) {
231 self.prints.borrow_mut().push(message.to_string());
232 }
233 }
234
235 fn test_limits() -> BopLimits {
238 BopLimits::standard()
239 }
240
241 fn say(code: &str) -> String {
243 let mut host = TestHost::new();
245 run(code, &mut host, &test_limits()).unwrap();
246 host.last_print()
247 }
248
249 fn run_err(code: &str) -> String {
251 let mut host = TestHost::new();
252 run(code, &mut host, &test_limits()).unwrap_err().message
253 }
254
255 fn parse_err(code: &str) -> String {
257 parse(code).unwrap_err().message
258 }
259
260 fn run_err_with_limits(code: &str, limits: BopLimits) -> String {
262 let mut host = TestHost::new();
263 run(code, &mut host, &limits).unwrap_err().message
264 }
265
266 fn tight_limits() -> BopLimits {
268 BopLimits {
269 max_steps: 500,
270 max_memory: 64 * 1024,
271 }
272 }
273
274 #[test]
277 fn add_numbers() {
278 assert_eq!(say("print(1 + 2)"), "3");
279 }
280
281 #[test]
282 fn subtract() {
283 assert_eq!(say("print(10 - 3)"), "7");
284 }
285
286 #[test]
287 fn multiply() {
288 assert_eq!(say("print(4 * 5)"), "20");
289 }
290
291 #[test]
292 fn divide_float() {
293 assert_eq!(say("print(7 / 2)"), "3.5");
294 }
295
296 #[test]
297 fn divide_whole() {
298 assert_eq!(say("print(6 / 2)"), "3");
299 }
300
301 #[test]
302 fn modulo() {
303 assert_eq!(say("print(10 % 3)"), "1");
304 }
305
306 #[test]
307 fn precedence() {
308 assert_eq!(say("print(2 + 3 * 4)"), "14");
309 }
310
311 #[test]
312 fn parentheses() {
313 assert_eq!(say("print((2 + 3) * 4)"), "20");
314 }
315
316 #[test]
317 fn unary_neg() {
318 assert_eq!(say("print(-5)"), "-5");
319 }
320
321 #[test]
322 fn unary_not() {
323 assert_eq!(say("print(!true)"), "false");
324 }
325
326 #[test]
329 fn string_concat() {
330 assert_eq!(say(r#"print("hello" + " " + "world")"#), "hello world");
331 }
332
333 #[test]
334 fn string_repeat() {
335 assert_eq!(say(r#"print("ab" * 3)"#), "ababab");
336 }
337
338 #[test]
339 fn string_interpolation() {
340 assert_eq!(
341 say(r#"let name = "bop"
342print("hi {name}!")"#),
343 "hi bop!"
344 );
345 }
346
347 #[test]
348 fn string_auto_coerce_in_add() {
349 assert_eq!(say(r#"print("val=" + 42)"#), "val=42");
350 }
351
352 #[test]
355 fn equality() {
356 assert_eq!(say("print(1 == 1)"), "true");
357 assert_eq!(say("print(1 == 2)"), "false");
358 assert_eq!(say("print(1 != 2)"), "true");
359 }
360
361 #[test]
362 fn ordering() {
363 assert_eq!(say("print(3 < 5)"), "true");
364 assert_eq!(say("print(5 <= 5)"), "true");
365 assert_eq!(say("print(6 > 5)"), "true");
366 assert_eq!(say("print(5 >= 6)"), "false");
367 }
368
369 #[test]
370 fn logical_and_or() {
371 assert_eq!(say("print(true && false)"), "false");
372 assert_eq!(say("print(true || false)"), "true");
373 }
374
375 #[test]
376 fn short_circuit_and() {
377 assert_eq!(say("print(false && x)"), "false");
378 }
379
380 #[test]
381 fn short_circuit_or() {
382 assert_eq!(say("print(true || x)"), "true");
383 }
384
385 #[test]
388 fn let_and_use() {
389 assert_eq!(say("let x = 10\nprint(x)"), "10");
390 }
391
392 #[test]
393 fn assign() {
394 assert_eq!(say("let x = 1\nx = 5\nprint(x)"), "5");
395 }
396
397 #[test]
398 fn compound_assign() {
399 assert_eq!(say("let x = 10\nx += 5\nprint(x)"), "15");
400 assert_eq!(say("let x = 10\nx -= 3\nprint(x)"), "7");
401 assert_eq!(say("let x = 4\nx *= 3\nprint(x)"), "12");
402 assert_eq!(say("let x = 10\nx /= 4\nprint(x)"), "2.5");
403 assert_eq!(say("let x = 10\nx %= 3\nprint(x)"), "1");
404 }
405
406 #[test]
407 fn undefined_variable_error() {
408 assert!(run_err("print(nope)").contains("not found"));
409 }
410
411 #[test]
412 fn assign_undeclared_error() {
413 assert!(run_err("x = 5").contains("doesn't exist"));
414 }
415
416 #[test]
419 fn if_true_branch() {
420 assert_eq!(say("if true { print(\"yes\") } else { print(\"no\") }"), "yes");
421 }
422
423 #[test]
424 fn if_false_branch() {
425 assert_eq!(say("if false { print(\"yes\") } else { print(\"no\") }"), "no");
426 }
427
428 #[test]
429 fn if_else_if() {
430 assert_eq!(
431 say(r#"let x = 2
432if x == 1 { print("one") } else if x == 2 { print("two") } else { print("other") }"#),
433 "two"
434 );
435 }
436
437 #[test]
438 fn if_expression() {
439 assert_eq!(say("let x = if true { 1 } else { 2 }\nprint(x)"), "1");
440 }
441
442 #[test]
445 fn while_loop() {
446 assert_eq!(say("let i = 0\nwhile i < 5 { i += 1 }\nprint(i)"), "5");
447 }
448
449 #[test]
450 fn while_break() {
451 assert_eq!(
452 say("let i = 0\nwhile true { i += 1\nif i == 3 { break } }\nprint(i)"),
453 "3"
454 );
455 }
456
457 #[test]
458 fn while_continue() {
459 assert_eq!(
460 say(r#"let sum = 0
461let i = 0
462while i < 10 {
463 i += 1
464 if i % 2 == 0 { continue }
465 sum += i
466}
467print(sum)"#),
468 "25"
469 );
470 }
471
472 #[test]
475 fn for_over_array() {
476 assert_eq!(
477 say(r#"let sum = 0
478for x in [10, 20, 30] { sum += x }
479print(sum)"#),
480 "60"
481 );
482 }
483
484 #[test]
485 fn for_over_range() {
486 assert_eq!(
487 say("let sum = 0\nfor i in range(5) { sum += i }\nprint(sum)"),
488 "10"
489 );
490 }
491
492 #[test]
493 fn for_over_string() {
494 assert_eq!(
495 say(r#"let out = ""
496for ch in "abc" { out += ch + "-" }
497print(out)"#),
498 "a-b-c-"
499 );
500 }
501
502 #[test]
503 fn for_with_break() {
504 assert_eq!(
505 say("let last = 0\nfor i in range(100) { if i == 3 { break }\nlast = i }\nprint(last)"),
506 "2"
507 );
508 }
509
510 #[test]
513 fn repeat_loop() {
514 assert_eq!(say("let n = 0\nrepeat 4 { n += 1 }\nprint(n)"), "4");
515 }
516
517 #[test]
518 fn repeat_zero() {
519 assert_eq!(say("let n = 99\nrepeat 0 { n = 0 }\nprint(n)"), "99");
520 }
521
522 #[test]
525 fn fn_basic() {
526 assert_eq!(say("fn double(x) { return x * 2 }\nprint(double(5))"), "10");
527 }
528
529 #[test]
530 fn fn_implicit_return_none() {
531 assert_eq!(
532 say(r#"fn noop() { let x = 1 }
533print(noop().type())"#),
534 "none"
535 );
536 }
537
538 #[test]
539 fn fn_multiple_params() {
540 assert_eq!(say("fn add(a, b) { return a + b }\nprint(add(3, 7))"), "10");
541 }
542
543 #[test]
544 fn fn_scope_isolation() {
545 assert!(
546 run_err(
547 r#"let secret = 42
548fn peek() { return secret }
549peek()"#
550 )
551 .contains("not found")
552 );
553 }
554
555 #[test]
556 fn fn_wrong_arg_count() {
557 assert!(run_err("fn f(a, b) { return a }\nf(1)").contains("expects 2"));
558 }
559
560 #[test]
561 fn fn_recursion() {
562 assert_eq!(
563 say(r#"fn fib(n) {
564 if n <= 1 { return n }
565 return fib(n - 1) + fib(n - 2)
566}
567print(fib(10))"#),
568 "55"
569 );
570 }
571
572 #[test]
575 fn lambda_basic() {
576 assert_eq!(
577 say(r#"let double = fn(x) { return x * 2 }
578print(double(5))"#),
579 "10"
580 );
581 }
582
583 #[test]
584 fn lambda_captures_value() {
585 assert_eq!(
586 say(r#"let n = 5
587let add_n = fn(x) { return x + n }
588print(add_n(3))"#),
589 "8"
590 );
591 }
592
593 #[test]
594 fn lambda_captures_are_snapshot() {
595 assert_eq!(
599 say(r#"let n = 5
600let add_n = fn(x) { return x + n }
601n = 100
602print(add_n(3))"#),
603 "8"
604 );
605 }
606
607 #[test]
608 fn lambda_returned_from_fn() {
609 assert_eq!(
613 say(r#"fn make_adder(n) { return fn(x) { return x + n } }
614let add5 = make_adder(5)
615let add10 = make_adder(10)
616print(add5(3))
617print(add10(3))"#),
618 "13"
619 );
620 }
621
622 #[test]
623 fn named_fn_is_first_class_value() {
624 assert_eq!(
625 say(r#"fn double(x) { return x * 2 }
626let f = double
627print(f(7))"#),
628 "14"
629 );
630 }
631
632 #[test]
633 fn fn_stored_in_array_and_called_via_index() {
634 assert_eq!(
635 say(r#"fn add(x, y) { return x + y }
636fn mul(x, y) { return x * y }
637let ops = [add, mul]
638print(ops[0](2, 3))
639print(ops[1](2, 3))"#),
640 "6"
641 );
642 }
643
644 #[test]
645 fn higher_order_apply() {
646 assert_eq!(
649 say(r#"fn apply(f, x) { return f(x) }
650fn square(n) { return n * n }
651print(apply(square, 4))
652print(apply(fn(n) { return n + 1 }, 4))"#),
653 "5"
654 );
655 }
656
657 #[test]
658 fn lambda_self_reference_via_named_fn() {
659 assert_eq!(
664 say(r#"fn countdown(n) {
665 if n <= 0 { return "done" }
666 return countdown(n - 1)
667}
668print(countdown(3))"#),
669 "done"
670 );
671 }
672
673 #[test]
674 fn type_of_fn_is_fn() {
675 assert_eq!(say("fn f() { }\nprint(f.type())"), "fn");
676 assert_eq!(say("let g = fn() { }\nprint(g.type())"), "fn");
677 }
678
679 #[test]
680 fn calling_non_callable_value_errors() {
681 assert!(run_err("let x = 5\nx(1)").contains("not a function"));
682 }
683
684 #[test]
685 fn lambda_captures_nested_scope() {
686 assert_eq!(
690 say(r#"let a = 1
691if true {
692 let b = 2
693 let f = fn() { return a + b }
694 print(f())
695}"#),
696 "3"
697 );
698 }
699
700 #[test]
701 fn iife() {
702 assert_eq!(say("print((fn(x) { return x * 3 })(4))"), "12");
705 }
706
707 #[test]
710 fn array_literal_and_index() {
711 assert_eq!(say("let a = [10, 20, 30]\nprint(a[1])"), "20");
712 }
713
714 #[test]
715 fn array_negative_index() {
716 assert_eq!(say("let a = [10, 20, 30]\nprint(a[-1])"), "30");
717 }
718
719 #[test]
720 fn array_assign_index() {
721 assert_eq!(say("let a = [1, 2, 3]\na[1] = 99\nprint(a[1])"), "99");
722 }
723
724 #[test]
725 fn array_push_pop() {
726 assert_eq!(
727 say(r#"let a = [1, 2]
728a.push(3)
729print(a.len())"#),
730 "3"
731 );
732 assert_eq!(
733 say(r#"let a = [1, 2, 3]
734let last = a.pop()
735print(last)"#),
736 "3"
737 );
738 }
739
740 #[test]
741 fn array_has() {
742 assert_eq!(say("print([1, 2, 3].has(2))"), "true");
743 assert_eq!(say("print([1, 2, 3].has(9))"), "false");
744 }
745
746 #[test]
747 fn array_index_of() {
748 assert_eq!(say("print([10, 20, 30].index_of(20))"), "1");
749 assert_eq!(say("print([10, 20, 30].index_of(99))"), "-1");
750 }
751
752 #[test]
753 fn array_slice() {
754 assert_eq!(say("print([1, 2, 3, 4, 5].slice(1, 4))"), "[2, 3, 4]");
755 }
756
757 #[test]
758 fn array_join() {
759 assert_eq!(say(r#"print([1, 2, 3].join("-"))"#), "1-2-3");
760 }
761
762 #[test]
763 fn array_sort() {
764 assert_eq!(say("let a = [3, 1, 2]\na.sort()\nprint(a)"), "[1, 2, 3]");
765 }
766
767 #[test]
768 fn array_reverse() {
769 assert_eq!(say("let a = [1, 2, 3]\na.reverse()\nprint(a)"), "[3, 2, 1]");
770 }
771
772 #[test]
773 fn array_insert_remove() {
774 assert_eq!(
775 say(r#"let a = [1, 3]
776a.insert(1, 2)
777print(a)"#),
778 "[1, 2, 3]"
779 );
780 assert_eq!(
781 say(r#"let a = [1, 2, 3]
782let removed = a.remove(1)
783print(removed)"#),
784 "2"
785 );
786 }
787
788 #[test]
789 fn array_concat() {
790 assert_eq!(say("print([1, 2] + [3, 4])"), "[1, 2, 3, 4]");
791 }
792
793 #[test]
794 fn array_out_of_bounds() {
795 assert!(run_err("let a = [1]\nprint(a[5])").contains("out of bounds"));
796 }
797
798 #[test]
801 fn string_len() {
802 assert_eq!(say(r#"print("hello".len())"#), "5");
803 }
804
805 #[test]
806 fn string_contains() {
807 assert_eq!(say(r#"print("abcdef".contains("cd"))"#), "true");
808 assert_eq!(say(r#"print("abcdef".contains("zz"))"#), "false");
809 }
810
811 #[test]
812 fn string_starts_ends_with() {
813 assert_eq!(say(r#"print("hello".starts_with("he"))"#), "true");
814 assert_eq!(say(r#"print("hello".ends_with("lo"))"#), "true");
815 }
816
817 #[test]
818 fn string_split() {
819 assert_eq!(say(r#"print("a,b,c".split(","))"#), r#"["a", "b", "c"]"#);
820 }
821
822 #[test]
823 fn string_replace() {
824 assert_eq!(
825 say(r#"print("hello world".replace("world", "bop"))"#),
826 "hello bop"
827 );
828 }
829
830 #[test]
831 fn string_upper_lower_trim() {
832 assert_eq!(say(r#"print("Hello".upper())"#), "HELLO");
833 assert_eq!(say(r#"print("Hello".lower())"#), "hello");
834 assert_eq!(say(r#"print(" hi ".trim())"#), "hi");
835 }
836
837 #[test]
838 fn string_slice() {
839 assert_eq!(say(r#"print("hello".slice(1, 4))"#), "ell");
840 }
841
842 #[test]
843 fn string_index_of() {
844 assert_eq!(say(r#"print("hello".index_of("ll"))"#), "2");
845 assert_eq!(say(r#"print("hello".index_of("zz"))"#), "-1");
846 }
847
848 #[test]
849 fn string_index_char() {
850 assert_eq!(say(r#"print("abc"[1])"#), "b");
851 }
852
853 #[test]
856 fn dict_literal_and_access() {
857 assert_eq!(
858 say(r#"let d = {"name": "bop", "hp": 100}
859print(d["name"])"#),
860 "bop"
861 );
862 }
863
864 #[test]
865 fn dict_assign_key() {
866 assert_eq!(
867 say(r#"let d = {"a": 1}
868d["b"] = 2
869print(d["b"])"#),
870 "2"
871 );
872 }
873
874 #[test]
875 fn dict_methods() {
876 assert_eq!(
877 say(r#"let d = {"x": 1, "y": 2}
878print(d.len())"#),
879 "2"
880 );
881 assert_eq!(say(r#"print({"a": 1, "b": 2}.has("a"))"#), "true");
882 assert_eq!(say(r#"print({"a": 1, "b": 2}.has("z"))"#), "false");
883 }
884
885 #[test]
886 fn dict_keys_values() {
887 assert_eq!(say(r#"print({"a": 1, "b": 2}.keys())"#), r#"["a", "b"]"#);
888 assert_eq!(say(r#"print({"a": 1, "b": 2}.values())"#), "[1, 2]");
889 }
890
891 #[test]
894 fn builtin_range_1arg() {
895 assert_eq!(say("print(range(5))"), "[0, 1, 2, 3, 4]");
896 }
897
898 #[test]
899 fn builtin_range_2args() {
900 assert_eq!(say("print(range(2, 5))"), "[2, 3, 4]");
901 }
902
903 #[test]
904 fn builtin_range_3args() {
905 assert_eq!(say("print(range(0, 10, 3))"), "[0, 3, 6, 9]");
906 }
907
908 #[test]
909 fn builtin_range_reverse() {
910 assert_eq!(say("print(range(5, 0))"), "[5, 4, 3, 2, 1]");
911 }
912
913 #[test]
914 fn builtin_str() {
915 assert_eq!(say(r#"print(42.to_str())"#), "42");
916 assert_eq!(say(r#"print(true.to_str())"#), "true");
917 }
918
919 #[test]
920 fn builtin_int() {
921 assert_eq!(say("print(3.7.to_int())"), "3");
922 assert_eq!(say("print((-2.9).to_int())"), "-2");
923 }
924
925 #[test]
926 fn builtin_type() {
927 assert_eq!(say("print(42.type())"), "int");
930 assert_eq!(say("print(42.0.type())"), "number");
931 assert_eq!(say(r#"print("hi".type())"#), "string");
932 assert_eq!(say("print(true.type())"), "bool");
933 assert_eq!(say("print(none.type())"), "none");
934 assert_eq!(say("print([].type())"), "array");
935 }
936
937 #[test]
938 fn builtin_abs_min_max() {
939 assert_eq!(say("print((-5).abs())"), "5");
940 assert_eq!(say("print(3.min(7))"), "3");
941 assert_eq!(say("print(3.max(7))"), "7");
942 }
943
944 #[test]
945 fn builtin_len() {
946 assert_eq!(say(r#"print("hello".len())"#), "5");
947 assert_eq!(say("print([1, 2, 3].len())"), "3");
948 }
949
950 #[test]
951 fn builtin_inspect() {
952 assert_eq!(say(r#"print("hi".inspect())"#), r#""hi""#);
953 assert_eq!(say("print(42.inspect())"), "42");
954 }
955
956 #[test]
957 fn builtin_print_multi_args() {
958 let mut host = TestHost::new();
959 run(r#"print("a", "b", "c")"#, &mut host, &test_limits()).unwrap();
960 assert_eq!(host.prints.borrow().as_slice(), &["a b c"]);
961 }
962
963 #[test]
964 fn builtin_rand_deterministic() {
965 let a = say("print(rand(100))");
966 let b = say("print(rand(100))");
967 assert_eq!(a, b);
968 }
969
970 #[test]
973 fn error_division_by_zero() {
974 assert!(run_err("print(1 / 0)").contains("Division by zero"));
975 }
976
977 #[test]
978 fn error_type_mismatch_subtract() {
979 let msg = run_err(r#"print("a" - 1)"#);
980 assert!(msg.contains("Can't use `-`"));
981 }
982
983 #[test]
984 fn error_unknown_function() {
985 assert!(run_err("nope()").contains("not found"));
986 }
987
988 #[test]
989 fn error_infinite_loop_protection() {
990 let msg = run_err("while true { }");
991 assert!(msg.contains("too many steps"));
992 }
993
994 #[test]
995 fn error_break_outside_loop() {
996 assert!(run_err("break").contains("outside of a loop"));
997 }
998
999 #[test]
1000 fn error_continue_outside_loop() {
1001 assert!(run_err("continue").contains("outside of a loop"));
1002 }
1003
1004 #[test]
1007 fn parse_error_missing_rparen() {
1008 assert!(parse_err("print(1").contains("Expected `)`"));
1009 }
1010
1011 #[test]
1012 fn parse_error_missing_rbrace() {
1013 assert!(parse_err("if true {").contains("Expected `}`"));
1014 }
1015
1016 #[test]
1019 fn empty_program() {
1020 let mut host = TestHost::new();
1021 run("", &mut host, &test_limits()).unwrap();
1022 assert!(host.prints.borrow().is_empty());
1023 }
1024
1025 #[test]
1026 fn trailing_comma_in_array() {
1027 assert_eq!(say("print([1, 2, 3,])"), "[1, 2, 3]");
1028 }
1029
1030 #[test]
1031 fn trailing_comma_in_dict() {
1032 assert_eq!(say(r#"print({"a": 1,}.len())"#), "1");
1033 }
1034
1035 #[test]
1036 fn none_value() {
1037 assert_eq!(say("print(none)"), "none");
1038 assert_eq!(say("print(none == none)"), "true");
1039 }
1040
1041 #[test]
1042 fn equality_across_types() {
1043 assert_eq!(say("print(1 == true)"), "false");
1044 assert_eq!(say(r#"print(0 == "")"#), "false");
1045 assert_eq!(say("print(none == false)"), "false");
1046 }
1047
1048 #[test]
1049 fn dict_equality() {
1050 assert_eq!(say(r#"print({"a": 1, "b": 2} == {"b": 2, "a": 1})"#), "true");
1051 assert_eq!(say(r#"print({"a": 1} == {"a": 2})"#), "false");
1052 assert_eq!(say(r#"print({"a": 1} == {"b": 1})"#), "false");
1053 assert_eq!(say(r#"print({"a": 1} == {"a": 1, "b": 2})"#), "false");
1054 assert_eq!(say(r#"print({"a": {"x": 1}} == {"a": {"x": 1}})"#), "true");
1055 }
1056
1057 #[test]
1058 fn nested_array_access() {
1059 assert_eq!(say("let m = [[1, 2], [3, 4]]\nprint(m[1][0])"), "3");
1060 }
1061
1062 #[test]
1063 fn method_chain() {
1064 assert_eq!(say(r#"print(" HELLO ".trim().lower())"#), "hello");
1065 }
1066
1067 #[test]
1070 fn parse_error_carries_column_info() {
1071 let err = parse_err_full("let 42");
1075 assert_eq!(err.line, Some(1), "err: {:?}", err);
1076 assert_eq!(err.column, Some(5), "err: {:?}", err);
1077 assert!(err.message.contains("Expected a name"));
1078 }
1079
1080 #[test]
1081 fn parse_error_renders_with_snippet_and_carat() {
1082 let src = "let 42";
1083 let err = parse_err_full(src);
1084 let rendered = err.render(src);
1085 assert!(rendered.contains("--> line 1:5"), "rendered:\n{}", rendered);
1086 assert!(rendered.contains("let 42"));
1087 assert!(rendered.contains(" ^"), "rendered:\n{}", rendered);
1089 }
1090
1091 #[test]
1092 fn parse_error_on_line_2_points_at_line_2() {
1093 let src = "let x = 1\nlet = 2";
1094 let err = parse_err_full(src);
1095 assert_eq!(err.line, Some(2), "err: {:?}", err);
1096 let rendered = err.render(src);
1097 assert!(
1098 rendered.contains("let = 2"),
1099 "rendered:\n{}",
1100 rendered
1101 );
1102 }
1103
1104 #[test]
1105 fn runtime_error_renders_without_column() {
1106 let src = "let x = 1 / 0";
1109 let err = run_err_full(src);
1110 assert_eq!(err.line, Some(1));
1111 assert!(err.column.is_none());
1112 let rendered = err.render(src);
1113 assert!(rendered.contains("--> line 1"));
1114 assert!(rendered.contains("let x = 1 / 0"));
1115 assert!(!rendered.contains("^"), "rendered:\n{}", rendered);
1117 }
1118
1119 fn parse_err_full(code: &str) -> BopError {
1122 parse(code).unwrap_err()
1123 }
1124
1125 fn run_err_full(code: &str) -> BopError {
1127 let mut host = TestHost::new();
1128 run(code, &mut host, &test_limits()).unwrap_err()
1129 }
1130
1131 #[test]
1134 fn typo_variable_suggests_closest_local() {
1135 let err = run_err_full(
1136 r#"let length = 5
1137print(lenght)"#,
1138 );
1139 assert!(err.message.contains("not found"), "err: {:?}", err);
1140 assert_eq!(
1141 err.friendly_hint.as_deref(),
1142 Some("Did you mean `length`?")
1143 );
1144 }
1145
1146 #[test]
1147 fn typo_variable_falls_back_when_nothing_close() {
1148 let err = run_err_full("print(xylophone_constant)");
1151 assert_eq!(
1152 err.friendly_hint.as_deref(),
1153 Some("Did you forget to create it with `let`?")
1154 );
1155 }
1156
1157 #[test]
1158 fn typo_function_suggests_user_fn() {
1159 let err = run_err_full(
1160 r#"fn greet(name) { print("hi " + name) }
1161gret("world")"#,
1162 );
1163 assert!(err.message.contains("not found"));
1164 assert_eq!(
1165 err.friendly_hint.as_deref(),
1166 Some("Did you mean `greet`?")
1167 );
1168 }
1169
1170 #[test]
1171 fn typo_builtin_suggests_core_name() {
1172 let err = run_err_full("rang(5)");
1174 assert_eq!(
1175 err.friendly_hint.as_deref(),
1176 Some("Did you mean `range`?")
1177 );
1178 }
1179
1180 #[test]
1181 fn typo_struct_field_at_access_suggests_declared() {
1182 let err = run_err_full(
1183 r#"struct Point { x, y }
1184let p = Point { x: 1, y: 2 }
1185print(p.z)"#,
1186 );
1187 assert!(err.message.contains("has no field `z`"));
1188 assert_eq!(
1192 err.friendly_hint.as_deref(),
1193 Some("Did you mean `x`?")
1194 );
1195 }
1196
1197 #[test]
1198 fn typo_struct_field_at_construction_suggests_declared() {
1199 let err = run_err_full(
1200 r#"struct Point { x, y }
1201let p = Point { x: 1, ya: 2 }"#,
1202 );
1203 assert!(err.message.contains("has no field `ya`"));
1204 assert_eq!(
1205 err.friendly_hint.as_deref(),
1206 Some("Did you mean `y`?")
1207 );
1208 }
1209
1210 #[test]
1211 fn typo_enum_variant_suggests_declared() {
1212 let err = run_err_full(
1213 r#"enum Shape { Circle(r), Rectangle { w, h } }
1214let s = Shape::Circel(5)"#,
1215 );
1216 assert!(err.message.contains("has no variant `Circel`"));
1217 assert_eq!(
1218 err.friendly_hint.as_deref(),
1219 Some("Did you mean `Circle`?")
1220 );
1221 }
1222
1223 #[test]
1224 fn typo_hint_renders_in_source_snippet() {
1225 let src = r#"let length = 5
1226print(lenght)"#;
1227 let err = run_err_full(src);
1228 let rendered = err.render(src);
1229 assert!(
1230 rendered.contains("hint: Did you mean `length`?"),
1231 "rendered:\n{}",
1232 rendered
1233 );
1234 }
1235
1236 #[test]
1237 fn comments_in_code() {
1238 assert_eq!(
1243 say(r#"// this is a comment
1244let x = 42 // inline comment
1245print(x)"#),
1246 "42"
1247 );
1248 }
1249
1250 fn count(code: &str) -> u32 {
1253 let stmts = parse(code).unwrap();
1254 count_instructions(&stmts)
1255 }
1256
1257 #[test]
1258 fn count_simple_calls() {
1259 assert_eq!(count("print(1)"), 1);
1260 assert_eq!(count("print(1); print(2); print(3)"), 3);
1261 }
1262
1263 #[test]
1264 fn count_repeat() {
1265 assert_eq!(count("repeat 7 { print(1) }"), 2);
1266 }
1267
1268 #[test]
1269 fn count_if() {
1270 assert_eq!(count("if true { print(1) }"), 2);
1271 assert_eq!(count("if true { print(1) } else { print(2) }"), 3);
1272 }
1273
1274 #[test]
1275 fn count_while() {
1276 assert_eq!(count("while true { print(1) }"), 2);
1277 }
1278
1279 #[test]
1280 fn count_fn_skips_body() {
1281 assert_eq!(count("fn go() { print(1); print(2); print(3) }\ngo()"), 2);
1282 }
1283
1284 #[test]
1285 fn count_format_independent() {
1286 let one_line = count("repeat 7 { print(1) }");
1287 let multi_line = count("repeat 7 {\n print(1)\n}");
1288 assert_eq!(one_line, multi_line);
1289 assert_eq!(one_line, 2);
1290 }
1291
1292 #[test]
1293 fn count_nested() {
1294 assert_eq!(count("repeat 7 { if true { print(1) } }"), 3);
1295 }
1296
1297 #[test]
1298 fn count_empty_program() {
1299 assert_eq!(count(""), 0);
1300 }
1301
1302 #[test]
1305 fn if_block_scope() {
1306 assert!(
1307 run_err(
1308 r#"if true { let inner = 1 }
1309print(inner)"#
1310 )
1311 .contains("not found")
1312 );
1313 }
1314
1315 #[test]
1316 fn for_loop_var_scoped() {
1317 assert!(
1318 run_err(
1319 r#"for item in [1, 2] { let x = item }
1320print(item)"#
1321 )
1322 .contains("not found")
1323 );
1324 }
1325
1326 #[test]
1329 fn fizzbuzz() {
1330 assert_eq!(
1331 say(r#"let result = []
1332for i in range(1, 16) {
1333 if i % 15 == 0 {
1334 result.push("FizzBuzz")
1335 } else if i % 3 == 0 {
1336 result.push("Fizz")
1337 } else if i % 5 == 0 {
1338 result.push("Buzz")
1339 } else {
1340 result.push(i.to_str())
1341 }
1342}
1343print(result.join(", "))"#),
1344 "1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz"
1345 );
1346 }
1347
1348 #[test]
1349 fn nested_function_calls() {
1350 assert_eq!(
1351 say(r#"fn square(n) { return n * n }
1352fn sum_squares(a, b) { return square(a) + square(b) }
1353print(sum_squares(3, 4))"#),
1354 "25"
1355 );
1356 }
1357
1358 #[test]
1359 fn array_manipulation_program() {
1360 assert_eq!(
1361 say(r#"let data = [5, 2, 8, 1, 9, 3]
1362data.sort()
1363let top3 = data.slice(3, 6)
1364print(top3.join(", "))"#),
1365 "5, 8, 9"
1366 );
1367 }
1368
1369 #[test]
1372 fn truthy_values() {
1373 assert_eq!(say("print(if 1 { \"yes\" } else { \"no\" })"), "yes");
1374 assert_eq!(say(r#"print(if "x" { "yes" } else { "no" })"#), "yes");
1375 assert_eq!(say("print(if [1] { \"yes\" } else { \"no\" })"), "yes");
1376 }
1377
1378 #[test]
1379 fn falsy_values() {
1380 assert_eq!(say("print(if 0 { \"yes\" } else { \"no\" })"), "no");
1381 assert_eq!(say("print(if false { \"yes\" } else { \"no\" })"), "no");
1382 assert_eq!(say("print(if none { \"yes\" } else { \"no\" })"), "no");
1383 assert_eq!(say(r#"print(if "" { "yes" } else { "no" })"#), "no");
1384 }
1385
1386 #[test]
1389 fn display_whole_number_as_int() {
1390 assert_eq!(say("print(5.0)"), "5");
1391 }
1392
1393 #[test]
1394 fn display_float_with_decimals() {
1395 assert_eq!(say("print(3.14)"), "3.14");
1396 }
1397
1398 #[test]
1401 fn safety_infinite_loop_halts() {
1402 let msg = run_err_with_limits("while true { }", tight_limits());
1403 assert!(msg.contains("too many steps"), "got: {}", msg);
1404 }
1405
1406 #[test]
1407 fn safety_memory_bomb_string_doubling() {
1408 let msg = run_err_with_limits(
1409 r#"let s = "aaaaaaaaaa"
1410repeat 100 { s = s + s }"#,
1411 tight_limits(),
1412 );
1413 assert!(msg.contains("Memory limit"), "got: {}", msg);
1414 }
1415
1416 #[test]
1417 fn safety_memory_bomb_array_growth() {
1418 let msg = run_err_with_limits(
1419 r#"let arr = []
1420repeat 500 {
1421 arr.push("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
1422}"#,
1423 tight_limits(),
1424 );
1425 assert!(
1426 msg.contains("Memory limit") || msg.contains("too many steps"),
1427 "got: {}", msg
1428 );
1429 }
1430
1431 #[test]
1432 fn safety_deep_recursion_halts() {
1433 let handle = std::thread::Builder::new()
1443 .stack_size(8 * 1024 * 1024)
1444 .spawn(|| run_err_with_limits("fn f() { f() }\nf()", tight_limits()))
1445 .expect("spawn recursion test thread");
1446 let msg = handle.join().expect("recursion test thread panicked");
1447 assert!(
1448 msg.contains("nested function calls") || msg.contains("recursion"),
1449 "got: {}", msg
1450 );
1451 }
1452
1453 #[test]
1454 fn safety_deep_parse_nesting() {
1455 let code = "(".repeat(200) + "1" + &")".repeat(200);
1456 let msg = parse(&code).unwrap_err().message;
1457 assert!(msg.contains("nested too deeply"), "got: {}", msg);
1458 }
1459
1460 #[test]
1461 fn safety_string_repeat_bomb() {
1462 let msg = run_err_with_limits(r#"let s = "x" * 999999"#, tight_limits());
1463 assert!(msg.contains("Memory limit"), "got: {}", msg);
1464 }
1465
1466 #[test]
1467 fn safety_string_concat_bomb() {
1468 let msg = run_err_with_limits(
1469 r#"let s = "x" * 1000
1470repeat 100 { s = s + s }"#,
1471 tight_limits(),
1472 );
1473 assert!(msg.contains("Memory limit"), "got: {}", msg);
1474 }
1475
1476 #[test]
1477 fn safety_array_concat_bomb() {
1478 let msg = run_err_with_limits(
1479 r#"let a = range(100)
1480repeat 50 { a = a + a }"#,
1481 tight_limits(),
1482 );
1483 assert!(
1484 msg.contains("Memory limit") || msg.contains("too many steps"),
1485 "got: {}", msg
1486 );
1487 }
1488
1489 #[test]
1490 fn safety_for_in_large_string() {
1491 let msg = run_err_with_limits(
1492 r#"let s = "x" * 10000
1493for c in s { }"#,
1494 tight_limits(),
1495 );
1496 assert!(
1497 msg.contains("too many steps") || msg.contains("Memory limit"),
1498 "got: {}", msg
1499 );
1500 }
1501
1502 #[test]
1503 fn safety_demo_limits_step_bound() {
1504 let msg = run_err_with_limits(
1505 "let i = 0\nwhile true { i = i + 1 }",
1506 BopLimits::demo(),
1507 );
1508 assert!(msg.contains("too many steps"), "got: {}", msg);
1509 }
1510
1511 #[test]
1512 fn safety_demo_limits_memory_bound() {
1513 let msg = run_err_with_limits(
1514 r#"let s = "x" * 1100000
1515print(s)"#,
1516 BopLimits::demo(),
1517 );
1518 assert!(msg.contains("Memory limit"), "got: {}", msg);
1519 }
1520
1521 #[test]
1522 fn safety_nested_loop_step_bound() {
1523 let msg = run_err_with_limits("repeat 100 { repeat 100 { let x = 1 } }", tight_limits());
1524 assert!(msg.contains("too many steps"), "got: {}", msg);
1525 }
1526
1527 #[test]
1528 fn safety_string_split_bomb() {
1529 let msg = run_err_with_limits(
1530 r#"let s = "abababababab" * 2000
1531let parts = s.split("a")
1532let x = 1"#,
1533 tight_limits(),
1534 );
1535 assert!(
1536 msg.contains("Memory limit") || msg.contains("too many steps"),
1537 "got: {}", msg
1538 );
1539 }
1540
1541 #[test]
1542 fn safety_join_bomb() {
1543 let msg = run_err_with_limits(
1544 r#"let a = []
1545repeat 400 { a.push("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") }
1546let s = a.join("")
1547let x = 1"#,
1548 tight_limits(),
1549 );
1550 assert!(
1551 msg.contains("Memory limit") || msg.contains("too many steps"),
1552 "got: {}", msg
1553 );
1554 }
1555
1556 #[test]
1557 fn safety_range_hard_cap() {
1558 let msg = run_err_with_limits(
1559 r#"let a = range(100000)
1560let x = 1"#,
1561 tight_limits(),
1562 );
1563 assert!(
1564 msg.contains("Memory limit") || msg.contains("too many steps"),
1565 "got: {}", msg
1566 );
1567 }
1568
1569 #[test]
1570 fn safety_array_method_doubling() {
1571 let msg = run_err_with_limits(
1572 r#"let a = []
1573repeat 400 { a.push("aaaaaaaaaaaaaaaaaaaaaa") }
1574a.reverse()
1575let x = 1"#,
1576 tight_limits(),
1577 );
1578 assert!(
1579 msg.contains("Memory limit") || msg.contains("too many steps"),
1580 "got: {}", msg
1581 );
1582 }
1583
1584 #[test]
1585 fn safety_preflight_catches() {
1586 let limits = BopLimits {
1587 max_steps: 500,
1588 max_memory: 32 * 1024,
1589 };
1590 let msg = run_err_with_limits(r#"let s = "x" * 40000"#, limits);
1591 assert!(msg.contains("Memory limit"), "got: {}", msg);
1592 }
1593
1594 #[test]
1595 fn safety_bounded_overshoot() {
1596 let limits = BopLimits {
1597 max_steps: 500,
1598 max_memory: 64 * 1024,
1599 };
1600 let mut host = TestHost::new();
1601 let result = run(
1602 r#"let s = "abababab" * 1000
1603let parts = s.split("a")"#,
1604 &mut host,
1605 &limits,
1606 );
1607 assert!(result.is_ok(), "Expected success (bounded overshoot), got error");
1608 }
1609
1610 #[test]
1611 fn safety_dict_growth_tracked() {
1612 let msg = run_err_with_limits(
1613 r#"let d = {}
1614repeat 400 {
1615 d[d.len().to_str()] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1616}
1617let x = 1"#,
1618 tight_limits(),
1619 );
1620 assert!(
1621 msg.contains("Memory limit") || msg.contains("too many steps"),
1622 "got: {}", msg
1623 );
1624 }
1625
1626 struct CustomHost {
1629 prints: Vec<String>,
1630 }
1631
1632 impl BopHost for CustomHost {
1633 fn call(&mut self, name: &str, args: &[Value], line: u32) -> Option<Result<Value, BopError>> {
1634 match name {
1635 "greet" => {
1636 if args.len() != 1 {
1637 return Some(Err(BopError {
1638 line: Some(line),
1639 column: None,
1640 message: "greet() needs 1 argument".into(),
1641 friendly_hint: None,
1642 is_fatal: false,
1643 is_try_return: false,
1644 }));
1645 }
1646 Some(Ok(Value::new_str(format!("Hello, {}!", args[0]))))
1647 }
1648 _ => None,
1649 }
1650 }
1651
1652 fn on_print(&mut self, message: &str) {
1653 self.prints.push(message.to_string());
1654 }
1655
1656 fn function_hint(&self) -> &str {
1657 "Available: greet(name)"
1658 }
1659 }
1660
1661 #[test]
1662 fn host_custom_builtin() {
1663 let mut host = CustomHost { prints: vec![] };
1664 run(r#"print(greet("world"))"#, &mut host, &BopLimits::standard()).unwrap();
1665 assert_eq!(host.prints, vec!["Hello, world!"]);
1666 }
1667
1668 #[test]
1669 fn host_function_hint() {
1670 let mut host = CustomHost { prints: vec![] };
1671 let err = run("unknown()", &mut host, &BopLimits::standard()).unwrap_err();
1672 assert!(err.message.contains("not found"));
1673 }
1674
1675 #[test]
1678 fn match_literal_arms() {
1679 assert_eq!(
1680 say(r#"let x = 2
1681let out = match x {
1682 1 => "one",
1683 2 => "two",
1684 3 => "three",
1685 _ => "other",
1686}
1687print(out)"#),
1688 "two"
1689 );
1690 }
1691
1692 #[test]
1693 fn match_falls_through_to_wildcard() {
1694 assert_eq!(
1695 say(r#"let x = 42
1696print(match x {
1697 1 => "one",
1698 _ => "other",
1699})"#),
1700 "other"
1701 );
1702 }
1703
1704 #[test]
1705 fn match_no_arm_errors() {
1706 let err = run_err(r#"let x = 5
1707match x { 1 => "a", 2 => "b" }"#);
1708 assert!(err.contains("No match arm matched"), "got: {}", err);
1709 }
1710
1711 #[test]
1712 fn match_binding_captures_scrutinee() {
1713 assert_eq!(
1714 say(r#"print(match 42 {
1715 x => x + 1,
1716})"#),
1717 "43"
1718 );
1719 }
1720
1721 #[test]
1722 fn match_guard_accepts() {
1723 assert_eq!(
1724 say(r#"print(match 7 {
1725 n if n > 10 => "big",
1726 n if n > 0 => "small",
1727 _ => "zero or less",
1728})"#),
1729 "small"
1730 );
1731 }
1732
1733 #[test]
1734 fn match_guard_rejects_continues() {
1735 assert_eq!(
1736 say(r#"print(match 5 {
1737 n if n < 0 => "neg",
1738 n if n > 100 => "huge",
1739 _ => "mid",
1740})"#),
1741 "mid"
1742 );
1743 }
1744
1745 #[test]
1746 fn match_or_pattern() {
1747 assert_eq!(
1748 say(r#"let x = 3
1749print(match x {
1750 1 | 2 | 3 => "small",
1751 _ => "other",
1752})"#),
1753 "small"
1754 );
1755 }
1756
1757 #[test]
1758 fn match_enum_unit_variant() {
1759 assert_eq!(
1760 say(r#"enum E { A, B, C }
1761print(match E::B {
1762 E::A => "a",
1763 E::B => "b",
1764 E::C => "c",
1765})"#),
1766 "b"
1767 );
1768 }
1769
1770 #[test]
1771 fn match_enum_tuple_binds() {
1772 assert_eq!(
1773 say(r#"enum Shape { Circle(r), Square(s), Empty }
1774let s = Shape::Circle(5)
1775print(match s {
1776 Shape::Circle(r) => r * 2,
1777 Shape::Square(s) => s * s,
1778 Shape::Empty => 0,
1779})"#),
1780 "10"
1781 );
1782 }
1783
1784 #[test]
1785 fn match_enum_struct_variant_binds() {
1786 assert_eq!(
1787 say(r#"enum Shape { Rect { w, h }, Empty }
1788let r = Shape::Rect { w: 4, h: 3 }
1789print(match r {
1790 Shape::Rect { w, h } => w * h,
1791 Shape::Empty => 0,
1792})"#),
1793 "12"
1794 );
1795 }
1796
1797 #[test]
1798 fn match_struct_destructure() {
1799 assert_eq!(
1800 say(r#"struct Point { x, y }
1801let p = Point { x: 7, y: 3 }
1802print(match p {
1803 Point { x, y } => x + y,
1804})"#),
1805 "10"
1806 );
1807 }
1808
1809 #[test]
1810 fn match_struct_partial_with_rest() {
1811 assert_eq!(
1815 say(r#"struct Triple { a, b, c }
1816let t = Triple { a: 1, b: 2, c: 3 }
1817print(match t {
1818 Triple { b, .. } => b,
1819})"#),
1820 "2"
1821 );
1822 }
1823
1824 #[test]
1825 fn match_nested_pattern() {
1826 assert_eq!(
1828 say(r#"enum FileError { NotFound(path), Permission(path), Other }
1829enum Result { Ok(value), Err(error) }
1830let r = Result::Err(FileError::NotFound("/etc/passwd"))
1831print(match r {
1832 Result::Ok(v) => v,
1833 Result::Err(FileError::NotFound(p)) => p,
1834 Result::Err(FileError::Permission(p)) => p,
1835 Result::Err(FileError::Other) => "other",
1836})"#),
1837 "/etc/passwd"
1838 );
1839 }
1840
1841 #[test]
1842 fn match_array_exact() {
1843 assert_eq!(
1844 say(r#"let a = [1, 2, 3]
1845print(match a {
1846 [] => "empty",
1847 [x] => "one",
1848 [x, y] => "two",
1849 [x, y, z] => x + y + z,
1850 _ => "long",
1851})"#),
1852 "6"
1853 );
1854 }
1855
1856 #[test]
1857 fn match_array_with_rest() {
1858 assert_eq!(
1859 say(r#"let a = [10, 20, 30, 40, 50]
1860print(match a {
1861 [head, ..rest] => rest,
1862 _ => [],
1863})"#),
1864 "[20, 30, 40, 50]"
1865 );
1866 }
1867
1868 #[test]
1869 fn match_array_with_ignored_rest() {
1870 assert_eq!(
1871 say(r#"let a = [10, 20, 30]
1872print(match a {
1873 [first, ..] => first,
1874 _ => 0,
1875})"#),
1876 "10"
1877 );
1878 }
1879
1880 #[test]
1881 fn match_binding_scope_limited_to_arm() {
1882 assert!(
1883 run_err(r#"let v = 5
1884match v { x => print(x) }
1885print(x)"#)
1886 .contains("not found")
1887 );
1888 }
1889
1890 #[test]
1891 fn match_negative_literal() {
1892 assert_eq!(
1893 say(r#"print(match -3 {
1894 -3 => "neg three",
1895 _ => "other",
1896})"#),
1897 "neg three"
1898 );
1899 }
1900
1901 #[test]
1902 fn match_string_literal() {
1903 assert_eq!(
1904 say(r#"let s = "hello"
1905print(match s {
1906 "hi" => 1,
1907 "hello" => 2,
1908 _ => 0,
1909})"#),
1910 "2"
1911 );
1912 }
1913
1914 #[test]
1915 fn match_bool_none() {
1916 assert_eq!(
1917 say(r#"print(match true {
1918 true => "t",
1919 false => "f",
1920})"#),
1921 "t"
1922 );
1923 assert_eq!(
1924 say(r#"print(match none {
1925 none => "n",
1926 _ => "other",
1927})"#),
1928 "n"
1929 );
1930 }
1931
1932 #[test]
1935 fn try_unwraps_ok_variant() {
1936 assert_eq!(
1937 say(r#"enum Result { Ok(v), Err(e) }
1938fn doit() {
1939 let v = try Result::Ok(42)
1940 return v
1941}
1942print(doit())"#),
1943 "42"
1944 );
1945 }
1946
1947 #[test]
1948 fn try_propagates_err_variant() {
1949 assert_eq!(
1952 say(r#"enum Result { Ok(v), Err(e) }
1953fn doit() {
1954 let v = try Result::Err("boom")
1955 return Result::Ok(v)
1956}
1957let r = doit()
1958print(match r {
1959 Result::Ok(v) => v,
1960 Result::Err(e) => e,
1961})"#),
1962 "boom"
1963 );
1964 }
1965
1966 #[test]
1967 fn try_sentinel_uses_flag_not_message_string() {
1968 let mut host = TestHost::new();
1987 let err = run(
1988 "print(1 / 0)",
1989 &mut host,
1990 &test_limits(),
1991 )
1992 .unwrap_err();
1993 assert_eq!(err.message, "Division by zero");
1994 assert!(!err.is_try_return, "got: {:?}", err);
1998 assert!(!err.is_fatal, "got: {:?}", err);
2000 }
2001
2002 #[test]
2003 fn try_chains_through_nested_calls() {
2004 assert_eq!(
2007 say(r#"enum Result { Ok(v), Err(e) }
2008fn leaf() { return Result::Err("leaf-err") }
2009fn middle() {
2010 let v = try leaf()
2011 return Result::Ok(v + 1)
2012}
2013fn top() {
2014 let v = try middle()
2015 return Result::Ok(v * 2)
2016}
2017print(match top() {
2018 Result::Ok(v) => v,
2019 Result::Err(e) => e,
2020})"#),
2021 "leaf-err"
2022 );
2023 }
2024
2025 #[test]
2026 fn user_result_with_unit_ok_coexists_with_builtin() {
2027 assert_eq!(
2033 say(r#"enum Result { Ok, Err(e) }
2034fn doit() {
2035 let v = try Result::Ok
2036 return v.type()
2037}
2038print(doit())"#),
2039 "none"
2040 );
2041 }
2042
2043 #[test]
2044 fn try_inside_lambda_returns_from_lambda_only() {
2045 assert_eq!(
2048 say(r#"enum Result { Ok(v), Err(e) }
2049let f = fn() {
2050 let v = try Result::Err("inner")
2051 return Result::Ok(v)
2052}
2053let r = f()
2054print("after lambda")
2055print(match r {
2056 Result::Ok(_) => "ok",
2057 Result::Err(e) => e,
2058})"#),
2059 "inner"
2060 );
2061 }
2062
2063 #[test]
2064 fn try_at_top_level_on_err_value_errors() {
2065 let msg = run_err(
2066 r#"enum Result { Ok(v), Err(e) }
2067let r = try Result::Err("boom")"#,
2068 );
2069 assert!(msg.contains("top-level"), "got: {}", msg);
2070 }
2071
2072 #[test]
2073 fn try_on_non_result_errors() {
2074 let msg = run_err(
2075 r#"fn doit() {
2076 let v = try 42
2077 return v
2078}
2079doit()"#,
2080 );
2081 assert!(msg.contains("Result-shaped"), "got: {}", msg);
2082 }
2083
2084 #[test]
2085 fn try_ok_tuple_wrong_arity_errors() {
2086 let msg = run_err(
2093 r#"enum Result { Ok(a, b), Err(e) }
2094fn doit() {
2095 let v = try Result::Ok(1, 2)
2096 return v
2097}
2098doit()"#,
2099 );
2100 assert!(
2101 msg.contains("Ok variant must carry exactly one"),
2102 "got: {}",
2103 msg
2104 );
2105 }
2106
2107 #[test]
2108 fn try_in_for_loop_short_circuits() {
2109 assert_eq!(
2111 say(r#"enum Result { Ok(v), Err(e) }
2112fn lookup(i) {
2113 if i == 2 { return Result::Err("stop") }
2114 return Result::Ok(i * 10)
2115}
2116fn sum_until_err() {
2117 let total = 0
2118 for i in range(5) {
2119 let v = try lookup(i)
2120 total = total + v
2121 }
2122 return Result::Ok(total)
2123}
2124print(match sum_until_err() {
2125 Result::Ok(v) => v,
2126 Result::Err(e) => e,
2127})"#),
2128 "stop"
2129 );
2130 }
2131
2132 #[test]
2133 fn try_threaded_through_nested_fn_composition() {
2134 assert_eq!(
2138 say(r#"enum Result { Ok(v), Err(e) }
2139fn compute(input) {
2140 if input < 0 { return Result::Err("negative") }
2141 return Result::Ok(input * 2)
2142}
2143fn with_try(x) {
2144 let doubled = try compute(x)
2145 return Result::Ok(doubled + 1)
2146}
2147print(match with_try(5) { Result::Ok(v) => v, Result::Err(_) => -1 })
2148print(match with_try(-1) { Result::Ok(_) => "ok", Result::Err(e) => e })"#),
2149 "negative"
2150 );
2151 }
2152
2153 #[test]
2156 fn try_call_wraps_successful_return_in_ok() {
2157 assert_eq!(
2162 say(r#"let r = try_call(fn() { return 42 })
2163print(match r {
2164 Result::Ok(v) => v,
2165 Result::Err(_) => -1,
2166})"#),
2167 "42"
2168 );
2169 }
2170
2171 #[test]
2172 fn try_call_wraps_non_fatal_error_in_err() {
2173 assert_eq!(
2177 say(r#"let r = try_call(fn() { return 1 / 0 })
2178print(match r {
2179 Result::Ok(_) => "ok",
2180 Result::Err(e) => e.message,
2181})"#),
2182 "Division by zero"
2183 );
2184 }
2185
2186 #[test]
2187 fn try_call_runtime_error_carries_line_number() {
2188 assert_eq!(
2191 say(r#"let r = try_call(fn() {
2192 let x = 1
2193 return x / 0
2194})
2195print(match r {
2196 Result::Ok(_) => -1,
2197 Result::Err(e) => e.line,
2198})"#),
2199 "3"
2200 );
2201 }
2202
2203 #[test]
2204 fn try_call_step_limit_error_is_fatal_and_bypasses_wrap() {
2205 let tight = BopLimits {
2209 max_steps: 200,
2210 max_memory: 1 << 20,
2211 };
2212 let mut host = TestHost::new();
2213 let err = run(
2214 r#"let r = try_call(fn() {
2215 while true { }
2216})
2217print("should never run")"#,
2218 &mut host,
2219 &tight,
2220 )
2221 .unwrap_err();
2222 assert!(err.is_fatal, "expected fatal: {}", err.message);
2223 assert!(
2224 err.message.contains("too many steps"),
2225 "got: {}",
2226 err.message
2227 );
2228 assert!(host.prints.borrow().is_empty());
2231 }
2232
2233 #[test]
2234 fn try_call_plays_with_try_operator_to_chain_errors() {
2235 assert_eq!(
2240 say(r#"fn risky(x) {
2241 let arr = [1, 2]
2242 return arr[x] // out-of-bounds when x > 1
2243}
2244let r = try_call(fn() { return risky(5) })
2245print(match r {
2246 Result::Ok(_) => "ok",
2247 Result::Err(e) => e.message,
2248})"#),
2249 "Index 5 is out of bounds (array has 2 items)"
2250 );
2251 }
2252
2253 #[test]
2254 fn try_call_errors_on_wrong_arg_count() {
2255 let msg = run_err("try_call()");
2256 assert!(
2257 msg.contains("try_call` expects 1"),
2258 "got: {}",
2259 msg
2260 );
2261 }
2262
2263 #[test]
2264 fn try_call_errors_on_non_function_arg() {
2265 let msg = run_err("try_call(42)");
2266 assert!(
2267 msg.contains("try_call` expects a function"),
2268 "got: {}",
2269 msg
2270 );
2271 }
2272
2273 #[test]
2274 fn try_call_result_ok_is_matchable_even_without_declared_type() {
2275 assert_eq!(
2281 say(r#"let r = try_call(fn() { return "yay" })
2282print(match r {
2283 Result::Ok(v) => v + "!",
2284 Result::Err(_) => "bad",
2285})"#),
2286 "yay!"
2287 );
2288 }
2289
2290 #[test]
2291 fn try_call_nested_outer_sees_ok_of_inner_err() {
2292 assert_eq!(
2296 say(r#"let r = try_call(fn() {
2297 let inner = try_call(fn() { return 1 / 0 })
2298 return inner
2299})
2300print(match r {
2301 Result::Ok(Result::Err(e)) => e.message,
2302 Result::Ok(Result::Ok(_)) => "inner ok?",
2303 Result::Err(_) => "outer caught",
2304})"#),
2305 "Division by zero"
2306 );
2307 }
2308
2309 #[test]
2312 fn int_literal_produces_int_value() {
2313 assert_eq!(say("print(42.type())"), "int");
2314 assert_eq!(say("print((-3).type())"), "int");
2315 assert_eq!(say("print(0.type())"), "int");
2316 }
2317
2318 #[test]
2319 fn float_literal_produces_number_value() {
2320 assert_eq!(say("print(42.0.type())"), "number");
2321 assert_eq!(say("print(3.14.type())"), "number");
2322 assert_eq!(say("print((-0.5).type())"), "number");
2323 }
2324
2325 #[test]
2326 fn int_int_arithmetic_stays_int() {
2327 assert_eq!(say("print((1 + 2).type())"), "int");
2328 assert_eq!(say("print(1 + 2)"), "3");
2329 assert_eq!(say("print(10 - 4)"), "6");
2330 assert_eq!(say("print(3 * 4)"), "12");
2331 assert_eq!(say("print(10 % 3)"), "1");
2332 }
2333
2334 #[test]
2335 fn division_slash_always_returns_number() {
2336 assert_eq!(say("print((10 / 3).type())"), "number");
2340 assert_eq!(say("print(10 / 4)"), "2.5");
2341 assert_eq!(say("print((10 / 5).type())"), "number");
2342 }
2343
2344 #[test]
2345 fn int_division_via_int_of_quotient() {
2346 assert_eq!(say("print((10 / 3).to_int().type())"), "int");
2351 assert_eq!(say("print((10 / 3).to_int())"), "3");
2352 assert_eq!(say("print((-7 / 2).to_int())"), "-3");
2353 assert_eq!(say("print((10 / -3).to_int())"), "-3");
2354 }
2355
2356 #[test]
2357 fn int_number_mixed_widens_to_number() {
2358 assert_eq!(say("print((1 + 2.0).type())"), "number");
2359 assert_eq!(say("print(1 + 2.0)"), "3");
2360 assert_eq!(say("print(3 * 0.5)"), "1.5");
2361 assert_eq!(say("print((2.0 - 1).type())"), "number");
2362 }
2363
2364 #[test]
2365 fn int_comparison_uses_exact_integer_ordering() {
2366 assert_eq!(say("print(10 < 20)"), "true");
2367 assert_eq!(say("print(10 == 10)"), "true");
2368 assert_eq!(say("print(1 == 1.0)"), "true");
2371 assert_eq!(say("print(2 > 1.5)"), "true");
2372 }
2373
2374 #[test]
2375 fn division_by_zero_errors() {
2376 let msg = run_err("print(10 / 0)");
2377 assert!(msg.contains("Division by zero"), "got: {}", msg);
2378 }
2379
2380 #[test]
2381 fn int_overflow_on_add_errors() {
2382 let msg = run_err("print(9223372036854775807 + 1)");
2385 assert!(msg.contains("Integer overflow"), "got: {}", msg);
2386 }
2387
2388 #[test]
2389 fn int_overflow_on_neg_of_i64_min_errors() {
2390 let msg = run_err(
2394 "let x = -9223372036854775807 - 1\nprint(-x)",
2395 );
2396 assert!(msg.contains("overflow"), "got: {}", msg);
2397 }
2398
2399 #[test]
2400 fn int_builtin_converts_to_int() {
2401 assert_eq!(say("print(3.7.to_int())"), "3");
2402 assert_eq!(say("print(3.7.to_int().type())"), "int");
2403 assert_eq!(say(r#"print("42".to_int())"#), "42");
2404 assert_eq!(say(r#"print("42".to_int().type())"#), "int");
2405 assert_eq!(say(r#"print("3.7".to_int())"#), "3");
2407 }
2408
2409 #[test]
2410 fn float_builtin_converts_to_number() {
2411 assert_eq!(say("print(42.to_float())"), "42");
2412 assert_eq!(say("print(42.to_float().type())"), "number");
2413 assert_eq!(say(r#"print("3.14".to_float())"#), "3.14");
2414 }
2415
2416 #[test]
2417 fn len_returns_int() {
2418 assert_eq!(say(r#"print("hi".len().type())"#), "int");
2419 assert_eq!(say("print([1, 2, 3].len().type())"), "int");
2420 }
2421
2422 #[test]
2423 fn range_produces_int_elements() {
2424 assert_eq!(say("print((range(3)[0]).type())"), "int");
2425 }
2426
2427 #[test]
2428 fn array_index_accepts_int_and_float() {
2429 assert_eq!(say("let a = [10, 20]\nprint(a[0])"), "10");
2432 assert_eq!(say("let a = [10, 20]\nprint(a[0.0])"), "10");
2433 }
2434
2435 #[test]
2436 fn int_match_literal_pattern() {
2437 assert_eq!(
2438 say(r#"let x = 2
2439print(match x {
2440 1 => "one",
2441 2 => "two",
2442 _ => "other",
2443})"#),
2444 "two"
2445 );
2446 }
2447
2448 #[test]
2449 fn repeat_accepts_int() {
2450 assert_eq!(
2451 say(r#"let n = 0
2452repeat 5 { n = n + 1 }
2453print(n)"#),
2454 "5"
2455 );
2456 }
2457
2458 #[test]
2459 fn int_overflow_literal_parse_errors() {
2460 let msg = parse_err("let x = 99999999999999999999");
2463 assert!(msg.contains("out of range"), "got: {}", msg);
2464 }
2465
2466 struct ModuleHost {
2473 prints: RefCell<Vec<String>>,
2474 modules: std::collections::HashMap<String, String>,
2475 resolve_counts: RefCell<std::collections::HashMap<String, u32>>,
2476 }
2477
2478 impl ModuleHost {
2479 fn new(modules: &[(&str, &str)]) -> Self {
2480 let mut map = std::collections::HashMap::new();
2481 for (name, source) in modules {
2482 map.insert((*name).to_string(), (*source).to_string());
2483 }
2484 Self {
2485 prints: RefCell::new(Vec::new()),
2486 modules: map,
2487 resolve_counts: RefCell::new(std::collections::HashMap::new()),
2488 }
2489 }
2490
2491 fn prints(&self) -> Vec<String> {
2492 self.prints.borrow().clone()
2493 }
2494
2495 fn resolve_count(&self, name: &str) -> u32 {
2496 *self
2497 .resolve_counts
2498 .borrow()
2499 .get(name)
2500 .unwrap_or(&0)
2501 }
2502 }
2503
2504 impl BopHost for ModuleHost {
2505 fn call(
2506 &mut self,
2507 _name: &str,
2508 _args: &[Value],
2509 _line: u32,
2510 ) -> Option<Result<Value, BopError>> {
2511 None
2512 }
2513
2514 fn on_print(&mut self, message: &str) {
2515 self.prints.borrow_mut().push(message.to_string());
2516 }
2517
2518 fn resolve_module(&mut self, name: &str) -> Option<Result<String, BopError>> {
2519 *self
2520 .resolve_counts
2521 .borrow_mut()
2522 .entry(name.to_string())
2523 .or_insert(0) += 1;
2524 self.modules.get(name).cloned().map(Ok)
2525 }
2526 }
2527
2528 #[test]
2529 fn import_brings_let_binding_into_scope() {
2530 let mut host = ModuleHost::new(&[("math", "let pi = 3")]);
2531 run(
2532 r#"use math
2533print(pi)"#,
2534 &mut host,
2535 &BopLimits::standard(),
2536 )
2537 .unwrap();
2538 assert_eq!(host.prints(), vec!["3"]);
2539 }
2540
2541 #[test]
2542 fn import_brings_fn_into_scope() {
2543 let mut host = ModuleHost::new(&[(
2544 "math",
2545 r#"fn square(n) { return n * n }
2546let pi = 3"#,
2547 )]);
2548 run(
2549 r#"use math
2550print(square(5))
2551print(pi)"#,
2552 &mut host,
2553 &BopLimits::standard(),
2554 )
2555 .unwrap();
2556 assert_eq!(host.prints(), vec!["25", "3"]);
2557 }
2558
2559 #[test]
2560 fn import_dotted_path_passes_through_to_host() {
2561 let mut host = ModuleHost::new(&[("std.math", "let e = 2")]);
2562 run(
2563 r#"use std.math
2564print(e)"#,
2565 &mut host,
2566 &BopLimits::standard(),
2567 )
2568 .unwrap();
2569 assert_eq!(host.prints(), vec!["2"]);
2570 assert_eq!(host.resolve_count("std.math"), 1);
2572 }
2573
2574 #[test]
2575 fn import_module_not_found_errors() {
2576 let mut host = ModuleHost::new(&[]);
2577 let err = run("use nope", &mut host, &BopLimits::standard())
2578 .unwrap_err();
2579 assert!(
2580 err.message.contains("Module `nope` not found"),
2581 "got: {}",
2582 err.message
2583 );
2584 }
2585
2586 #[test]
2587 fn import_cache_resolves_once() {
2588 let mut host = ModuleHost::new(&[("m", "let x = 1")]);
2591 run(
2592 r#"use m
2593use m
2594print(x)"#,
2595 &mut host,
2596 &BopLimits::standard(),
2597 )
2598 .unwrap();
2599 assert_eq!(host.prints(), vec!["1"]);
2600 assert_eq!(host.resolve_count("m"), 1);
2601 }
2602
2603 #[test]
2604 fn import_module_can_import_other_modules() {
2605 let mut host = ModuleHost::new(&[
2606 ("a", "use b\nlet doubled_pi = pi + pi"),
2607 ("b", "let pi = 3"),
2608 ]);
2609 run(
2610 r#"use a
2611print(doubled_pi)"#,
2612 &mut host,
2613 &BopLimits::standard(),
2614 )
2615 .unwrap();
2616 assert_eq!(host.prints(), vec!["6"]);
2617 }
2618
2619 #[test]
2620 fn import_circular_detected() {
2621 let mut host = ModuleHost::new(&[
2622 ("a", "use b\nlet x = 1"),
2623 ("b", "use a\nlet y = 2"),
2624 ]);
2625 let err = run("use a", &mut host, &BopLimits::standard())
2626 .unwrap_err();
2627 assert!(
2628 err.message.contains("Circular import"),
2629 "got: {}",
2630 err.message
2631 );
2632 }
2633
2634 #[test]
2635 fn glob_use_shadowing_is_a_warning_first_wins() {
2636 let mut host = ModuleHost::new(&[("m", "let x = 99")]);
2643 run(
2644 r#"let x = 1
2645use m
2646print(x)"#,
2647 &mut host,
2648 &BopLimits::standard(),
2649 )
2650 .expect("run ok");
2651 assert_eq!(host.prints(), vec!["1".to_string()]);
2652 }
2653
2654 #[test]
2655 fn use_selective_form_pulls_only_listed_names() {
2656 let mut host = ModuleHost::new(&[("m", "let a = 1\nlet b = 2\nlet c = 3")]);
2657 run(
2658 r#"use m.{a, c}
2659print(a)
2660print(c)"#,
2661 &mut host,
2662 &BopLimits::standard(),
2663 )
2664 .expect("run ok");
2665 assert_eq!(host.prints(), vec!["1".to_string(), "3".to_string()]);
2666 }
2667
2668 #[test]
2669 fn use_selective_unknown_name_errors() {
2670 let mut host = ModuleHost::new(&[("m", "let a = 1")]);
2671 let err = run(
2672 r#"use m.{b}"#,
2673 &mut host,
2674 &BopLimits::standard(),
2675 )
2676 .unwrap_err();
2677 assert!(
2678 err.message.contains("isn't exported"),
2679 "got: {}",
2680 err.message
2681 );
2682 }
2683
2684 #[test]
2685 fn use_alias_binds_module_value() {
2686 let mut host = ModuleHost::new(&[(
2687 "m",
2688 "let pi = 3\nfn double(n) { return n + n }",
2689 )]);
2690 run(
2691 r#"use m as m
2692print(m.pi)
2693print(m.double(7))"#,
2694 &mut host,
2695 &BopLimits::standard(),
2696 )
2697 .expect("run ok");
2698 assert_eq!(host.prints(), vec!["3".to_string(), "14".to_string()]);
2699 }
2700
2701 #[test]
2702 fn use_alias_selective_form() {
2703 let mut host = ModuleHost::new(&[("m", "let a = 1\nlet b = 2\nlet c = 3")]);
2704 run(
2705 r#"use m.{a, c} as m
2706print(m.a)
2707print(m.c)"#,
2708 &mut host,
2709 &BopLimits::standard(),
2710 )
2711 .expect("run ok");
2712 assert_eq!(host.prints(), vec!["1".to_string(), "3".to_string()]);
2713 }
2714
2715 #[test]
2716 fn use_alias_rejects_missing_module_field() {
2717 let mut host = ModuleHost::new(&[("m", "let a = 1")]);
2718 let err = run(
2719 r#"use m as m
2720print(m.b)"#,
2721 &mut host,
2722 &BopLimits::standard(),
2723 )
2724 .unwrap_err();
2725 assert!(
2726 err.message.contains("isn't exported"),
2727 "got: {}",
2728 err.message
2729 );
2730 }
2731
2732 #[test]
2733 fn glob_skips_underscore_prefixed_exports() {
2734 let mut host = ModuleHost::new(&[("m", "let public = 1\nlet _private = 2")]);
2737 let err = run(
2738 r#"use m
2739print(_private)"#,
2740 &mut host,
2741 &BopLimits::standard(),
2742 )
2743 .unwrap_err();
2744 assert!(
2747 err.message.contains("_private"),
2748 "expected `_private not found`, got: {}",
2749 err.message
2750 );
2751 }
2752
2753 #[test]
2754 fn selective_form_can_reach_underscore_prefixed_names() {
2755 let mut host = ModuleHost::new(&[("m", "let _private = 42")]);
2758 run(
2759 r#"use m.{_private}
2760print(_private)"#,
2761 &mut host,
2762 &BopLimits::standard(),
2763 )
2764 .expect("run ok");
2765 assert_eq!(host.prints(), vec!["42".to_string()]);
2766 }
2767
2768 #[test]
2769 fn alias_exposes_underscore_prefixed_names() {
2770 let mut host = ModuleHost::new(&[("m", "let _private = 7")]);
2774 run(
2775 r#"use m as m
2776print(m._private)"#,
2777 &mut host,
2778 &BopLimits::standard(),
2779 )
2780 .expect("run ok");
2781 assert_eq!(host.prints(), vec!["7".to_string()]);
2782 }
2783
2784 #[test]
2785 fn alias_namespaced_struct_literal() {
2786 let mut host = ModuleHost::new(&[(
2787 "g",
2788 "struct Entity { id, hp }\nfn spawn(id) { return Entity { id: id, hp: 100 } }",
2789 )]);
2790 run(
2791 r#"use g as g
2792let e = g.Entity { id: 1, hp: 50 }
2793print(e.id)
2794print(e.hp)"#,
2795 &mut host,
2796 &BopLimits::standard(),
2797 )
2798 .expect("run ok");
2799 assert_eq!(host.prints(), vec!["1".to_string(), "50".to_string()]);
2800 }
2801
2802 #[test]
2803 fn alias_namespaced_variant_ctor() {
2804 let mut host = ModuleHost::new(&[(
2805 "r",
2806 "enum Result { Ok(v), Err(e) }",
2807 )]);
2808 run(
2809 r#"use r as r
2810let v = r.Result::Ok(42)
2811match v {
2812 r.Result::Ok(n) => print(n),
2813 r.Result::Err(_) => print("err"),
2814}"#,
2815 &mut host,
2816 &BopLimits::standard(),
2817 )
2818 .expect("run ok");
2819 assert_eq!(host.prints(), vec!["42".to_string()]);
2820 }
2821
2822 #[test]
2823 fn alias_module_value_is_a_module_type() {
2824 let mut host = ModuleHost::new(&[("m", "let x = 1")]);
2825 run(
2826 r#"use m as mm
2827print(mm.type())"#,
2828 &mut host,
2829 &BopLimits::standard(),
2830 )
2831 .expect("run ok");
2832 assert_eq!(host.prints(), vec!["module".to_string()]);
2833 }
2834
2835 #[test]
2838 fn struct_decl_and_construct() {
2839 assert_eq!(
2840 say(r#"struct Point { x, y }
2841let p = Point { x: 3, y: 4 }
2842print(p.x)
2843print(p.y)"#),
2844 "4"
2845 );
2846 }
2847
2848 #[test]
2849 fn struct_display_shows_type_name_and_fields() {
2850 assert_eq!(
2851 say(r#"struct Point { x, y }
2852let p = Point { x: 3, y: 4 }
2853print(p)"#),
2854 "Point { x: 3, y: 4 }"
2855 );
2856 }
2857
2858 #[test]
2859 fn struct_fields_respect_declaration_order() {
2860 assert_eq!(
2864 say(r#"struct Point { x, y }
2865let p = Point { y: 4, x: 3 }
2866print(p)"#),
2867 "Point { x: 3, y: 4 }"
2868 );
2869 }
2870
2871 #[test]
2872 fn struct_equality_is_structural() {
2873 assert_eq!(
2874 say(r#"struct Point { x, y }
2875let a = Point { x: 1, y: 2 }
2876let b = Point { x: 1, y: 2 }
2877print(a == b)"#),
2878 "true"
2879 );
2880 assert_eq!(
2881 say(r#"struct Point { x, y }
2882let a = Point { x: 1, y: 2 }
2883let b = Point { x: 1, y: 3 }
2884print(a == b)"#),
2885 "false"
2886 );
2887 }
2888
2889 #[test]
2890 fn struct_different_types_never_equal() {
2891 assert_eq!(
2892 say(r#"struct A { x }
2893struct B { x }
2894let a = A { x: 1 }
2895let b = B { x: 1 }
2896print(a == b)"#),
2897 "false"
2898 );
2899 }
2900
2901 #[test]
2902 fn struct_type_name_is_struct() {
2903 assert_eq!(
2907 say(r#"struct Foo { a }
2908print(Foo { a: 1 }.type())"#),
2909 "struct"
2910 );
2911 }
2912
2913 #[test]
2914 fn struct_missing_field_errors() {
2915 let err = run_err(r#"struct Point { x, y }
2916let p = Point { x: 1 }"#);
2917 assert!(err.contains("Missing field"), "got: {}", err);
2918 }
2919
2920 #[test]
2921 fn struct_extra_field_errors() {
2922 let err = run_err(r#"struct Point { x, y }
2923let p = Point { x: 1, y: 2, z: 3 }"#);
2924 assert!(err.contains("has no field"), "got: {}", err);
2925 }
2926
2927 #[test]
2928 fn struct_duplicate_field_errors() {
2929 let err = run_err(r#"struct Point { x, y }
2930let p = Point { x: 1, x: 2, y: 3 }"#);
2931 assert!(err.contains("specified twice"), "got: {}", err);
2932 }
2933
2934 #[test]
2935 fn struct_undeclared_type_errors() {
2936 let err = run_err(r#"let p = Nope { x: 1 }"#);
2937 assert!(err.contains("not declared"), "got: {}", err);
2938 }
2939
2940 #[test]
2941 fn struct_field_access_missing_errors() {
2942 let err = run_err(r#"struct Point { x, y }
2943let p = Point { x: 1, y: 2 }
2944print(p.z)"#);
2945 assert!(err.contains("no field"), "got: {}", err);
2946 }
2947
2948 #[test]
2949 fn struct_field_access_on_non_struct_errors() {
2950 let err = run_err("let x = 42\nprint(x.value)");
2951 assert!(err.contains("Can't read field"), "got: {}", err);
2952 }
2953
2954 #[test]
2955 fn struct_duplicate_decl_errors() {
2956 let err = run_err(r#"struct Foo { x }
2957struct Foo { y }"#);
2958 assert!(err.contains("already declared"), "got: {}", err);
2959 }
2960
2961 #[test]
2962 fn struct_nested() {
2963 assert_eq!(
2964 say(r#"struct Inner { v }
2965struct Outer { name, inner }
2966let o = Outer { name: "nest", inner: Inner { v: 42 } }
2967print(o.inner.v)"#),
2968 "42"
2969 );
2970 }
2971
2972 #[test]
2973 fn struct_in_array_and_iteration() {
2974 assert_eq!(
2975 say(r#"struct Item { name, qty }
2976let cart = [Item { name: "apple", qty: 3 }, Item { name: "banana", qty: 2 }]
2977let total = 0
2978for i in cart { total += i.qty }
2979print(total)"#),
2980 "5"
2981 );
2982 }
2983
2984 #[test]
2985 fn struct_literal_disallowed_in_if_condition_parses() {
2986 let err = run_err("if Foo { print(\"hi\") }");
2992 assert!(err.contains("not found"), "got: {}", err);
2993 }
2994
2995 #[test]
2996 fn struct_literal_disallowed_in_for_iterable() {
2997 assert_eq!(
3003 say("let arr = [1, 2, 3]\nlet sum = 0\nfor x in arr { sum += x }\nprint(sum)"),
3004 "6"
3005 );
3006 }
3007
3008 #[test]
3009 fn struct_literal_ok_in_let_rhs() {
3010 assert_eq!(
3014 say(r#"struct P { x }
3015let p = P { x: 7 }
3016print(p.x)"#),
3017 "7"
3018 );
3019 }
3020
3021 #[test]
3022 fn struct_field_assign_basic() {
3023 assert_eq!(
3024 say(r#"struct Point { x, y }
3025let p = Point { x: 1, y: 2 }
3026p.x = 99
3027print(p.x)
3028print(p.y)"#),
3029 "2"
3030 );
3031 }
3032
3033 #[test]
3034 fn struct_field_compound_assign() {
3035 assert_eq!(
3036 say(r#"struct Counter { n }
3037let c = Counter { n: 10 }
3038c.n += 5
3039c.n *= 2
3040print(c.n)"#),
3041 "30"
3042 );
3043 }
3044
3045 #[test]
3046 fn struct_field_assign_unknown_field_errors() {
3047 let err = run_err(r#"struct P { x }
3048let p = P { x: 1 }
3049p.y = 99"#);
3050 assert!(err.contains("no field"), "got: {}", err);
3051 }
3052
3053 #[test]
3054 fn struct_field_assign_on_non_struct_errors() {
3055 let err = run_err(r#"let x = 5
3056x.field = 1"#);
3057 assert!(err.contains("Can't assign to field"), "got: {}", err);
3058 }
3059
3060 #[test]
3061 fn struct_field_assign_chain_via_intermediate_var() {
3062 assert_eq!(
3066 say(r#"struct Inner { v }
3067struct Outer { inner }
3068let o = Outer { inner: Inner { v: 1 } }
3069let i = o.inner
3070i.v = 99
3071o.inner = i
3072print(o.inner.v)"#),
3073 "99"
3074 );
3075 }
3076
3077 #[test]
3080 fn enum_unit_variant_basic() {
3081 assert_eq!(
3082 say(r#"enum Shape { Empty, Circle(r), Square(s) }
3083let s = Shape::Empty
3084print(s)"#),
3085 "Shape::Empty"
3086 );
3087 }
3088
3089 #[test]
3090 fn enum_tuple_variant() {
3091 assert_eq!(
3092 say(r#"enum Shape { Empty, Circle(r), Pair(x, y) }
3093let p = Shape::Pair(3, 4)
3094print(p)"#),
3095 "Shape::Pair(3, 4)"
3096 );
3097 }
3098
3099 #[test]
3100 fn enum_struct_variant() {
3101 assert_eq!(
3102 say(r#"enum Shape { Rectangle { width, height }, Empty }
3103let r = Shape::Rectangle { width: 4, height: 3 }
3104print(r)
3105print(r.width)
3106print(r.height)"#),
3107 "3"
3108 );
3109 }
3110
3111 #[test]
3112 fn enum_equality_same_variant() {
3113 assert_eq!(
3114 say(r#"enum E { A, B(x) }
3115print(E::A == E::A)
3116print(E::B(1) == E::B(1))
3117print(E::B(1) == E::B(2))
3118print(E::A == E::B(1))"#),
3119 "false"
3120 );
3121 }
3122
3123 #[test]
3124 fn enum_different_types_not_equal() {
3125 assert_eq!(
3126 say(r#"enum A { X }
3127enum B { X }
3128print(A::X == B::X)"#),
3129 "false"
3130 );
3131 }
3132
3133 #[test]
3134 fn enum_variant_mismatch_unit_given_args() {
3135 let err = run_err(r#"enum E { A }
3136let x = E::A(1)"#);
3137 assert!(err.contains("no payload"), "got: {}", err);
3138 }
3139
3140 #[test]
3141 fn enum_variant_mismatch_tuple_arity() {
3142 let err = run_err(r#"enum E { P(x, y) }
3143let p = E::P(1)"#);
3144 assert!(err.contains("expects 2 argument"), "got: {}", err);
3145 }
3146
3147 #[test]
3148 fn enum_variant_mismatch_struct_missing_field() {
3149 let err = run_err(r#"enum E { R { w, h } }
3150let r = E::R { w: 1 }"#);
3151 assert!(err.contains("Missing field"), "got: {}", err);
3152 }
3153
3154 #[test]
3155 fn enum_variant_mismatch_struct_extra_field() {
3156 let err = run_err(r#"enum E { R { w, h } }
3157let r = E::R { w: 1, h: 2, extra: 3 }"#);
3158 assert!(err.contains("no field"), "got: {}", err);
3159 }
3160
3161 #[test]
3162 fn enum_undeclared_variant_errors() {
3163 let err = run_err(r#"enum E { A }
3164let x = E::Z"#);
3165 assert!(err.contains("no variant"), "got: {}", err);
3166 }
3167
3168 #[test]
3169 fn enum_undeclared_type_errors() {
3170 let err = run_err("let x = Nope::V");
3171 assert!(err.contains("not declared"), "got: {}", err);
3172 }
3173
3174 #[test]
3175 fn enum_struct_variant_field_access() {
3176 assert_eq!(
3177 say(r#"enum Shape { Rect { w, h }, Empty }
3178let r = Shape::Rect { w: 10, h: 3 }
3179print(r.w * r.h)"#),
3180 "30"
3181 );
3182 }
3183
3184 #[test]
3185 fn enum_used_in_if_condition() {
3186 assert_eq!(
3191 say(r#"enum E { V }
3192if E::V == E::V {
3193 print("yes")
3194} else {
3195 print("no")
3196}"#),
3197 "yes"
3198 );
3199 }
3200
3201 #[test]
3202 fn enum_type_name_is_enum() {
3203 assert_eq!(
3204 say(r#"enum E { V }
3205print((E::V).type())"#),
3206 "enum"
3207 );
3208 }
3209
3210 #[test]
3211 fn enum_in_array_of_values() {
3212 assert_eq!(
3213 say(r#"enum Color { Red, Green, Blue }
3214let palette = [Color::Red, Color::Green, Color::Blue]
3215print(palette)"#),
3216 "[Color::Red, Color::Green, Color::Blue]"
3217 );
3218 }
3219
3220 #[test]
3221 fn enum_duplicate_decl_errors() {
3222 let err = run_err(r#"enum E { A }
3223enum E { B }"#);
3224 assert!(err.contains("already declared"), "got: {}", err);
3225 }
3226
3227 #[test]
3230 fn method_on_struct_basic() {
3231 assert_eq!(
3232 say(r#"struct Point { x, y }
3233fn Point.sum(self) { return self.x + self.y }
3234let p = Point { x: 3, y: 4 }
3235print(p.sum())"#),
3236 "7"
3237 );
3238 }
3239
3240 #[test]
3241 fn method_with_extra_args() {
3242 assert_eq!(
3243 say(r#"struct Counter { n }
3244fn Counter.add(self, delta) { return Counter { n: self.n + delta } }
3245let c = Counter { n: 10 }
3246let c2 = c.add(5)
3247print(c2.n)
3248print(c.n)"#),
3249 "10"
3250 );
3251 }
3252
3253 #[test]
3254 fn method_does_not_mutate_receiver() {
3255 assert_eq!(
3259 say(r#"struct Counter { n }
3260fn Counter.bump(self) { self.n = self.n + 1 }
3261let c = Counter { n: 5 }
3262c.bump()
3263print(c.n)"#),
3264 "5"
3265 );
3266 }
3267
3268 #[test]
3269 fn method_on_enum_dispatches_on_type() {
3270 assert_eq!(
3271 say(r#"enum Shape { Circle(r), Rect { w, h }, Empty }
3272fn Shape.name(self) { return "shape" }
3273print(Shape::Circle(3).name())
3274print(Shape::Rect { w: 4, h: 3 }.name())
3275print(Shape::Empty.name())"#),
3276 "shape"
3277 );
3278 }
3279
3280 #[test]
3281 fn method_overrides_builtin() {
3282 assert_eq!(
3285 say(r#"struct Wrapper { data }
3286fn Wrapper.len(self) { return 99 }
3287let w = Wrapper { data: [1, 2, 3] }
3288print(w.len())"#),
3289 "99"
3290 );
3291 }
3292
3293 #[test]
3294 fn method_unknown_on_struct_errors() {
3295 let err = run_err(r#"struct P { x }
3296let p = P { x: 1 }
3297p.nope()"#);
3298 assert!(err.contains(".nope()"), "got: {}", err);
3299 }
3300
3301 #[test]
3302 fn method_wrong_arg_count_errors() {
3303 let err = run_err(r#"struct P { x }
3304fn P.set(self, v) { return P { x: v } }
3305let p = P { x: 1 }
3306p.set(1, 2)"#);
3307 assert!(err.contains("expects"), "got: {}", err);
3308 }
3309
3310 #[test]
3311 fn method_chain_user_defined() {
3312 assert_eq!(
3313 say(r#"struct Adder { n }
3314fn Adder.then(self, m) { return Adder { n: self.n + m } }
3315let result = Adder { n: 1 }.then(2).then(3).then(4)
3316print(result.n)"#),
3317 "10"
3318 );
3319 }
3320
3321 #[test]
3322 fn method_self_is_clone() {
3323 assert_eq!(
3327 say(r#"struct P { x }
3328fn P.identity(self) { return self }
3329let a = P { x: 7 }
3330let b = a.identity()
3331print(a == b)
3332print(b.x)"#),
3333 "7"
3334 );
3335 }
3336
3337 #[test]
3338 fn method_on_enum_reads_payload_field() {
3339 assert_eq!(
3340 say(r#"enum Shape { Circle(r), Rect { w, h } }
3341fn Shape.label(self, prefix) {
3342 return prefix + "-shape"
3343}
3344let c = Shape::Circle(5)
3345print(c.label("small"))"#),
3346 "small-shape"
3347 );
3348 }
3349
3350 #[test]
3351 fn enum_duplicate_variant_errors() {
3352 let err = run_err(r#"enum E { A, A }"#);
3353 assert!(err.contains("duplicate variant"), "got: {}", err);
3354 }
3355
3356 #[test]
3357 fn struct_empty() {
3358 assert_eq!(
3359 say(r#"struct Unit { }
3360let u = Unit { }
3361print(u)"#),
3362 "Unit {}"
3363 );
3364 }
3365
3366 #[test]
3367 fn import_module_does_not_see_importer_scope() {
3368 let mut host = ModuleHost::new(&[("m", "fn leak() { return outer }")]);
3371 let err = run(
3372 r#"let outer = 42
3373use m
3374print(leak())"#,
3375 &mut host,
3376 &BopLimits::standard(),
3377 )
3378 .unwrap_err();
3379 assert!(
3380 err.message.contains("outer"),
3381 "expected 'outer' not-found error, got: {}",
3382 err.message
3383 );
3384 }
3385
3386 #[test]
3394 fn const_declares_an_immutable_binding() {
3395 assert_eq!(say("const PI = 3\nprint(PI)"), "3");
3396 }
3397
3398 #[test]
3399 fn const_can_reference_another_const() {
3400 assert_eq!(
3401 say("const PI = 3\nconst DIAMETER = PI * 2\nprint(DIAMETER)"),
3402 "6"
3403 );
3404 }
3405
3406 #[test]
3407 fn const_reassignment_is_refused_at_parse_time() {
3408 let err = run_err("const PI = 3\nPI = 4");
3409 assert!(
3410 err.contains("can't reassign") && err.contains("constant"),
3411 "expected const-reassignment error, got: {err}"
3412 );
3413 }
3414
3415 #[test]
3416 fn let_name_must_start_lowercase() {
3417 let err = run_err("let Foo = 1");
3418 assert!(err.to_lowercase().contains("value"), "got: {err}");
3419 }
3420
3421 #[test]
3422 fn let_with_all_caps_suggests_const() {
3423 let err = run_err("let MAX = 1");
3424 assert!(
3425 err.contains("const"),
3426 "expected hint to suggest `const`, got: {err}"
3427 );
3428 }
3429
3430 #[test]
3431 fn struct_name_must_start_uppercase() {
3432 let err = run_err("struct entity { id }");
3433 assert!(err.to_lowercase().contains("type"), "got: {err}");
3434 }
3435
3436 #[test]
3437 fn enum_variants_start_uppercase() {
3438 let err = run_err("enum Event { spawn, damage }");
3439 assert!(err.to_lowercase().contains("type"), "got: {err}");
3440 }
3441
3442 #[test]
3443 fn enum_with_all_caps_variants_is_allowed() {
3444 assert_eq!(
3447 say("enum Dir { N, E, S, W }\nlet d = Dir::E\nprint(\"ok\")"),
3448 "ok"
3449 );
3450 }
3451
3452 #[test]
3453 fn fn_name_must_start_lowercase() {
3454 let err = run_err("fn Greet() { return 1 }");
3455 assert!(err.to_lowercase().contains("value"), "got: {err}");
3456 }
3457
3458 #[test]
3459 fn fn_params_must_start_lowercase() {
3460 let err = run_err("fn greet(Name) { return Name }");
3461 assert!(err.to_lowercase().contains("value"), "got: {err}");
3462 }
3463
3464 #[test]
3465 fn for_loop_var_must_start_lowercase() {
3466 let err = run_err("for I in range(3) { print(I) }");
3467 assert!(err.to_lowercase().contains("value"), "got: {err}");
3468 }
3469
3470 #[test]
3471 fn const_name_must_be_all_caps() {
3472 let err = run_err("const Pi = 3");
3473 assert!(
3474 err.to_lowercase().contains("constant"),
3475 "got: {err}"
3476 );
3477 }
3478
3479 #[test]
3480 fn underscore_prefix_is_allowed_for_all_buckets() {
3481 assert_eq!(
3483 say(r#"
3484 let _hidden = 1
3485 const _DEBUG = true
3486 struct _Internal { _counter }
3487 let s = _Internal { _counter: _hidden }
3488 print(s._counter)
3489 "#),
3490 "1"
3491 );
3492 }
3493
3494 #[test]
3499 fn two_modules_can_declare_same_type_name_with_different_shapes() {
3500 let mut host = crate::host::StringModuleHost::new([
3506 ("paint", "enum Color { Red, Blue }"),
3507 ("other", "enum Color { Red, Green, Yellow }"),
3508 ]);
3509 run(
3510 r#"use paint as p
3511use other as o
3512let a = p.Color::Red
3513let b = o.Color::Red
3514print(a == b)
3515print(a == a)
3516print(a)
3517print(b)"#,
3518 &mut host,
3519 &BopLimits::standard(),
3520 )
3521 .unwrap();
3522 assert_eq!(host.output(), "false\ntrue\nColor::Red\nColor::Red");
3528 }
3529
3530 #[test]
3531 fn namespaced_pattern_matches_only_same_module_value() {
3532 let mut host = crate::host::StringModuleHost::new([
3537 ("paint", "enum Color { Red, Blue }"),
3538 ("other", "enum Color { Red, Green, Yellow }"),
3539 ]);
3540 run(
3541 r#"use paint as p
3542use other as o
3543fn label(c) {
3544 return match c {
3545 p.Color::Red => "paint red",
3546 o.Color::Red => "other red",
3547 _ => "other",
3548 }
3549}
3550print(label(p.Color::Red))
3551print(label(o.Color::Red))
3552print(label(o.Color::Green))"#,
3553 &mut host,
3554 &BopLimits::standard(),
3555 )
3556 .unwrap();
3557 assert_eq!(
3558 host.output(),
3559 "paint red\nother red\nother"
3560 );
3561 }
3562
3563 #[test]
3564 fn string_module_host_runs_use_end_to_end() {
3565 let mut host = crate::host::StringModuleHost::new([
3570 ("greetings", "fn hello(name) { print(\"hi \" + name) }"),
3571 ]);
3572 run(
3573 "use greetings\nhello(\"bop\")",
3574 &mut host,
3575 &BopLimits::standard(),
3576 )
3577 .unwrap();
3578 assert_eq!(host.output(), "hi bop");
3579 }
3580
3581 #[test]
3584 fn runtime_error_carries_column_for_undefined_ident() {
3585 let err = run_err_full("let x = 1\nprint(undefined)");
3591 assert_eq!(err.line, Some(2), "line");
3592 assert!(
3593 err.column.is_some(),
3594 "expected runtime error to carry column info, got None"
3595 );
3596 }
3597
3598 #[test]
3599 fn runtime_error_column_renders_with_carat() {
3600 let src = "let x = 1\nprint(undefined)";
3604 let err = run_err_full(src);
3605 let rendered = err.render(src);
3606 assert!(
3607 rendered.contains("--> line 2:"),
3608 "rendered should include line+col header, got:\n{}",
3609 rendered
3610 );
3611 assert!(
3612 rendered.contains("^"),
3613 "rendered should draw a carat, got:\n{}",
3614 rendered
3615 );
3616 }
3617
3618 #[test]
3619 fn resolve_from_map_returns_none_for_unknown_modules() {
3620 let resolver = crate::host::resolve_from_map([("m", "let x = 1")]);
3625 assert!(resolver("m").is_some());
3626 assert!(resolver("other").is_none());
3627 }
3628
3629 fn repl_eval(
3632 session: &mut ReplSession,
3633 src: &str,
3634 host: &mut TestHost,
3635 ) -> Result<Option<Value>, BopError> {
3636 session.eval(src, host, &test_limits())
3637 }
3638
3639 #[test]
3640 fn session_let_binding_survives_between_evals() {
3641 let mut session = ReplSession::new();
3642 let mut host = TestHost::new();
3643 repl_eval(&mut session, "let x = 5", &mut host).unwrap();
3644 repl_eval(&mut session, "print(x)", &mut host).unwrap();
3645 assert_eq!(host.last_print(), "5");
3646 }
3647
3648 #[test]
3649 fn session_mutated_let_reflects_in_next_eval() {
3650 let mut session = ReplSession::new();
3651 let mut host = TestHost::new();
3652 repl_eval(&mut session, "let counter = 0", &mut host).unwrap();
3653 repl_eval(&mut session, "counter = counter + 1", &mut host).unwrap();
3654 repl_eval(&mut session, "counter = counter + 1", &mut host).unwrap();
3655 repl_eval(&mut session, "print(counter)", &mut host).unwrap();
3656 assert_eq!(host.last_print(), "2");
3657 }
3658
3659 #[test]
3660 fn session_fn_declared_on_one_eval_callable_next() {
3661 let mut session = ReplSession::new();
3662 let mut host = TestHost::new();
3663 repl_eval(
3664 &mut session,
3665 "fn double(x) { return x + x }",
3666 &mut host,
3667 )
3668 .unwrap();
3669 repl_eval(&mut session, "print(double(21))", &mut host).unwrap();
3670 assert_eq!(host.last_print(), "42");
3671 }
3672
3673 #[test]
3674 fn session_struct_and_method_survive() {
3675 let mut session = ReplSession::new();
3680 let mut host = TestHost::new();
3681 repl_eval(
3682 &mut session,
3683 "struct Point { x, y }\nfn Point.sum(self) { return self.x + self.y }",
3684 &mut host,
3685 )
3686 .unwrap();
3687 repl_eval(
3688 &mut session,
3689 "let p = Point { x: 3, y: 4 }\nprint(p.sum())",
3690 &mut host,
3691 )
3692 .unwrap();
3693 assert_eq!(host.last_print(), "7");
3694 }
3695
3696 #[test]
3697 fn session_bare_expression_returns_value() {
3698 let mut session = ReplSession::new();
3702 let mut host = TestHost::new();
3703 assert!(repl_eval(&mut session, "let x = 5", &mut host)
3704 .unwrap()
3705 .is_none());
3706 let v = repl_eval(&mut session, "x + 1", &mut host).unwrap();
3707 match v {
3708 Some(Value::Int(n)) => assert_eq!(n, 6),
3709 other => panic!("expected Int(6), got: {:?}", other),
3710 }
3711 }
3712
3713 #[test]
3714 fn session_errors_preserve_earlier_effects() {
3715 let mut session = ReplSession::new();
3719 let mut host = TestHost::new();
3720 let err = repl_eval(
3721 &mut session,
3722 "let kept = 1\nlet bad = undefined\nlet skipped = 3",
3723 &mut host,
3724 );
3725 assert!(err.is_err(), "expected runtime error");
3726 assert!(session.get("kept").is_some());
3728 assert!(session.get("skipped").is_none());
3729 }
3730
3731 #[test]
3732 fn session_normalises_scope_depth_after_block_error() {
3733 let mut session = ReplSession::new();
3738 let mut host = TestHost::new();
3739 let _ = repl_eval(
3740 &mut session,
3741 "if true {\n let y = undefined\n}",
3742 &mut host,
3743 );
3744 repl_eval(&mut session, "let after = 7", &mut host).unwrap();
3747 let v = repl_eval(&mut session, "after", &mut host).unwrap();
3748 match v {
3749 Some(Value::Int(n)) => assert_eq!(n, 7),
3750 other => panic!("expected Int(7), got: {:?}", other),
3751 }
3752 }
3753
3754 #[test]
3755 fn session_binding_names_surfaces_lets_and_fns() {
3756 let mut session = ReplSession::new();
3760 let mut host = TestHost::new();
3761 repl_eval(&mut session, "let alpha = 1", &mut host).unwrap();
3762 repl_eval(&mut session, "fn beta() { return 2 }", &mut host).unwrap();
3763 let names = session.binding_names();
3764 assert!(names.contains(&"alpha".to_string()));
3765 assert!(names.contains(&"beta".to_string()));
3766 }
3767
3768 #[test]
3769 fn session_use_carries_imports_across_evals() {
3770 struct ModHost {
3775 prints: std::cell::RefCell<Vec<String>>,
3776 }
3777 impl BopHost for ModHost {
3778 fn call(
3779 &mut self,
3780 _: &str,
3781 _: &[Value],
3782 _: u32,
3783 ) -> Option<Result<Value, BopError>> {
3784 None
3785 }
3786 fn on_print(&mut self, message: &str) {
3787 self.prints.borrow_mut().push(message.to_string());
3788 }
3789 fn resolve_module(&mut self, name: &str) -> Option<Result<String, BopError>> {
3790 match name {
3791 "m" => Some(Ok("fn greet() { return \"hi\" }".to_string())),
3792 _ => None,
3793 }
3794 }
3795 }
3796 let mut host = ModHost {
3797 prints: std::cell::RefCell::new(Vec::new()),
3798 };
3799 let mut session = ReplSession::new();
3800 session.eval("use m", &mut host, &test_limits()).unwrap();
3801 session
3802 .eval("print(greet())", &mut host, &test_limits())
3803 .unwrap();
3804 let prints = host.prints.borrow();
3805 assert_eq!(prints.last().map(|s| s.as_str()), Some("hi"));
3806 }
3807}