use crate::interpreter::{Interpreter, Value}; use crate::lexer::Lexer; use crate::parser::Parser; /// Full pipeline: source → tokens → ast → interpret → last expression value. /// Wraps in `let __result = ;` so we can read the value back. fn eval_expr(input: &str) -> Value { let wrapped = format!("let __result = {};", input); let (tokens, _) = Lexer::new(&wrapped).tokenize(); let mut parser = Parser::new(tokens); let (stmts, errors) = parser.parse(); assert!(errors.is_empty(), "Parse errors: {:?}", errors); let mut interpreter = Interpreter::new(); interpreter.interpret(stmts).expect("Runtime error"); interpreter.env.borrow().get("__result").expect("No __result in env") } /// Full pipeline for multiple statements. Returns the interpreter for env inspection. fn run(input: &str) -> Interpreter { let (tokens, _) = Lexer::new(input).tokenize(); let mut parser = Parser::new(tokens); let (stmts, errors) = parser.parse(); assert!(errors.is_empty(), "Parse errors: {:?}", errors); let mut interpreter = Interpreter::new(); interpreter.interpret(stmts).expect("Runtime error"); interpreter } /// Helper: get a variable from the interpreter's environment. fn get_var(interp: &Interpreter, name: &str) -> Value { interp.env.borrow().get(name).unwrap_or(Value::Nil) } /// Helper: assert a number value. fn assert_num(val: &Value, expected: f64) { match val { Value::Number(n) => assert!((n - expected).abs() < f64::EPSILON, "Expected {}, got {}", expected, n), _ => panic!("Expected Number({}), got {:?}", expected, val), } } /// Helper: assert a bool value. fn assert_bool(val: &Value, expected: bool) { match val { Value::Bool(b) => assert_eq!(*b, expected), _ => panic!("Expected Bool({}), got {:?}", expected, val), } } /// Helper: assert nil. fn assert_nil(val: &Value) { match val { Value::Nil => {} _ => panic!("Expected Nil, got {:?}", val), } } // ========================================================================= // Literals // ========================================================================= #[test] fn eval_number() { assert_num(&eval_expr("42"), 42.0); } #[test] fn eval_string() { match eval_expr("\"hello\"") { Value::String(s) => assert_eq!(s, "hello"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_true() { assert_bool(&eval_expr("true"), true); } #[test] fn eval_false() { assert_bool(&eval_expr("false"), false); } #[test] fn eval_nil() { assert_nil(&eval_expr("nil")); } // ========================================================================= // Arithmetic // ========================================================================= #[test] fn eval_add() { assert_num(&eval_expr("1 + 2"), 3.0); } #[test] fn eval_sub() { assert_num(&eval_expr("5 - 3"), 2.0); } #[test] fn eval_mul() { assert_num(&eval_expr("4 * 3"), 12.0); } #[test] fn eval_div() { assert_num(&eval_expr("10 / 4"), 2.5); } #[test] fn eval_mod() { assert_num(&eval_expr("7 % 3"), 1.0); } #[test] fn eval_complex_arithmetic() { assert_num(&eval_expr("1 + 2 * 3"), 7.0); assert_num(&eval_expr("(1 + 2) * 3"), 9.0); } // ========================================================================= // String concatenation // ========================================================================= #[test] fn eval_string_concat() { match eval_expr("\"hello \" + \"world\"") { Value::String(s) => assert_eq!(s, "hello world"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_single_quoted_string() { match eval_expr("'hello'") { Value::String(s) => assert_eq!(s, "hello"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_mixed_quotes_concat() { match eval_expr("\"a\" + 'b'") { Value::String(s) => assert_eq!(s, "ab"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_single_quote_containing_double() { match eval_expr("'he\"llo'") { Value::String(s) => assert_eq!(s, "he\"llo"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_string_plus_number() { match eval_expr("\"a\" + 42") { Value::String(s) => assert_eq!(s, "a42"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_number_plus_string() { match eval_expr("42 + \"a\"") { Value::String(s) => assert_eq!(s, "42a"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_string_plus_bool() { match eval_expr("\"t\" + true") { Value::String(s) => assert_eq!(s, "ttrue"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_bool_plus_string() { match eval_expr("false + \"x\"") { Value::String(s) => assert_eq!(s, "falsex"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_string_plus_nil() { match eval_expr("\"x\" + nil") { Value::String(s) => assert_eq!(s, "xnil"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_plusequal_string_concat() { let interp = run("let s = \"hello\"; s += \" world\"; s += 42;"); match get_var(&interp, "s") { Value::String(s) => assert_eq!(s, "hello world42"), v => panic!("Expected String, got {:?}", v), } } // ========================================================================= // Comparison // ========================================================================= #[test] fn eval_equal_numbers() { assert_bool(&eval_expr("1 == 1"), true); assert_bool(&eval_expr("1 == 2"), false); } #[test] fn eval_not_equal() { assert_bool(&eval_expr("1 != 2"), true); assert_bool(&eval_expr("1 != 1"), false); } #[test] fn eval_greater_less() { assert_bool(&eval_expr("5 > 3"), true); assert_bool(&eval_expr("3 > 5"), false); assert_bool(&eval_expr("5 >= 5"), true); assert_bool(&eval_expr("3 < 5"), true); assert_bool(&eval_expr("5 < 3"), false); assert_bool(&eval_expr("3 <= 3"), true); } #[test] fn eval_equal_strings() { assert_bool(&eval_expr("\"a\" == \"a\""), true); assert_bool(&eval_expr("\"a\" == \"b\""), false); } #[test] fn eval_equal_bools() { assert_bool(&eval_expr("true == true"), true); assert_bool(&eval_expr("true == false"), false); } #[test] fn eval_nil_equals_nil() { assert_bool(&eval_expr("nil == nil"), true); } // ========================================================================= // Logical operators // ========================================================================= #[test] fn eval_logical_and_short_circuit() { assert_bool(&eval_expr("true && true"), true); assert_bool(&eval_expr("true && false"), false); assert_bool(&eval_expr("false && true"), false); // Short-circuit: false && returns false (the left value) match eval_expr("false && 999") { Value::Bool(false) => {} v => panic!("Expected Bool(false), got {:?}", v), } } #[test] fn eval_logical_or_short_circuit() { assert_bool(&eval_expr("true || false"), true); assert_bool(&eval_expr("false || true"), true); assert_bool(&eval_expr("false || false"), false); // Short-circuit: true || returns true (the left value) match eval_expr("true || 999") { Value::Bool(true) => {} v => panic!("Expected Bool(true), got {:?}", v), } } // ========================================================================= // Ternary // ========================================================================= #[test] fn eval_ternary_true_condition() { assert_num(&eval_expr("true ? 1 : 2"), 1.0); } #[test] fn eval_ternary_false_condition() { assert_num(&eval_expr("false ? 1 : 2"), 2.0); } #[test] fn eval_ternary_truthy_condition() { // Non-nil, non-false values are truthy assert_num(&eval_expr("1 ? 42 : 99"), 42.0); } #[test] fn eval_ternary_nil_condition() { assert_num(&eval_expr("nil ? 1 : 2"), 2.0); } #[test] fn eval_ternary_nested() { let interp = run("let x = 5; let r = x > 3 ? (x > 10 ? 100 : 50) : 0;"); assert_num(&get_var(&interp, "r"), 50.0); } #[test] fn eval_ternary_with_computed_branches() { let interp = run("let x = 10; let r = x > 5 ? x * 2 : x / 2;"); assert_num(&get_var(&interp, "r"), 20.0); } #[test] fn eval_ternary_short_circuit_then() { // When condition is false, then_branch is not evaluated let interp = run("let x = 0; let r = false ? (x = 999) : (x = 42);"); assert_num(&get_var(&interp, "x"), 42.0); } #[test] fn eval_ternary_short_circuit_else() { // When condition is true, else_branch is not evaluated let interp = run("let x = 0; let r = true ? (x = 42) : (x = 999);"); assert_num(&get_var(&interp, "x"), 42.0); } // ========================================================================= // Unary // ========================================================================= #[test] fn eval_negation() { assert_num(&eval_expr("-5"), -5.0); assert_num(&eval_expr("--5"), 5.0); } #[test] fn eval_not() { assert_bool(&eval_expr("!true"), false); assert_bool(&eval_expr("!false"), true); assert_bool(&eval_expr("!nil"), true); } // ========================================================================= // Assignment and variables // ========================================================================= #[test] fn eval_variable_definition_and_use() { let interp = run("let x = 10;"); assert_num(&get_var(&interp, "x"), 10.0); } #[test] fn eval_simple_assignment() { let interp = run("let x = 5; x = 10;"); assert_num(&get_var(&interp, "x"), 10.0); } #[test] fn eval_compound_plus_equal() { let interp = run("let x = 5; x += 3;"); assert_num(&get_var(&interp, "x"), 8.0); } #[test] fn eval_compound_minus_equal() { let interp = run("let x = 10; x -= 3;"); assert_num(&get_var(&interp, "x"), 7.0); } #[test] fn eval_compound_star_equal() { let interp = run("let x = 4; x *= 3;"); assert_num(&get_var(&interp, "x"), 12.0); } #[test] fn eval_compound_slash_equal() { let interp = run("let x = 10; x /= 2;"); assert_num(&get_var(&interp, "x"), 5.0); } #[test] fn eval_compound_percent_equal() { let interp = run("let x = 7; x %= 3;"); assert_num(&get_var(&interp, "x"), 1.0); } // ========================================================================= // Const // ========================================================================= #[test] fn eval_const_definition() { let interp = run("const x = 42;"); assert_num(&get_var(&interp, "x"), 42.0); } #[test] fn error_const_reassignment() { let result = std::panic::catch_unwind(|| { run("const x = 5; x = 10;"); }); assert!(result.is_err(), "Expected error for reassigning const"); } #[test] fn error_const_compound_assignment() { let result = std::panic::catch_unwind(|| { run("const x = 5; x += 1;"); }); assert!(result.is_err(), "Expected error for compound assignment to const"); } #[test] fn eval_const_shadowing() { // Inner block can shadow outer const with its own binding let interp = run("const x = 10; { let x = 20; }"); assert_num(&get_var(&interp, "x"), 10.0); // Outer unchanged } #[test] fn eval_const_object_mutation() { // const prevents rebinding, not property mutation let interp = run("const obj = { a: 1 }; obj.a = 2;"); match get_var(&interp, "obj") { Value::Object(obj) => { let obj = obj.borrow(); assert_num(obj.get("a").unwrap(), 2.0); } v => panic!("Expected Object, got {:?}", v), } } #[test] fn eval_const_array_mutation() { // const prevents rebinding, not index mutation let interp = run("const arr = [1, 2]; arr[0] = 99;"); match get_var(&interp, "arr") { Value::Array(arr) => { assert_num(&arr.borrow()[0], 99.0); } v => panic!("Expected Array, got {:?}", v), } } // ========================================================================= // Scope // ========================================================================= #[test] fn eval_block_scope_isolation() { let interp = run("{ let x = 42; }"); let val = get_var(&interp, "x"); assert_nil(&val); // x should not exist outside the block } #[test] fn eval_block_can_access_outer() { let interp = run("let x = 10; { let y = x + 1; }"); assert_num(&get_var(&interp, "x"), 10.0); } #[test] fn eval_shadowing() { let interp = run("let x = 10; { let x = 20; }"); assert_num(&get_var(&interp, "x"), 10.0); // Outer x unchanged } #[test] fn eval_assign_outer_from_inner() { let interp = run("let x = 10; { x = 20; }"); assert_num(&get_var(&interp, "x"), 20.0); } // ========================================================================= // Control flow: if // ========================================================================= #[test] fn eval_if_true_branch() { let interp = run("let x = 0; if (true) { x = 1; }"); assert_num(&get_var(&interp, "x"), 1.0); } #[test] fn eval_if_false_branch_skipped() { let interp = run("let x = 0; if (false) { x = 1; }"); assert_num(&get_var(&interp, "x"), 0.0); } #[test] fn eval_if_else_true() { let interp = run("let x = 0; if (true) { x = 1; } else { x = 2; }"); assert_num(&get_var(&interp, "x"), 1.0); } #[test] fn eval_if_else_false() { let interp = run("let x = 0; if (false) { x = 1; } else { x = 2; }"); assert_num(&get_var(&interp, "x"), 2.0); } #[test] fn eval_if_truthy() { // Non-nil, non-false values are truthy let interp = run("let x = 0; if (1) { x = 42; }"); assert_num(&get_var(&interp, "x"), 42.0); } // ========================================================================= // Control flow: while // ========================================================================= #[test] fn eval_while_loop() { let interp = run("let i = 0; while (i < 5) { i = i + 1; }"); assert_num(&get_var(&interp, "i"), 5.0); } #[test] fn eval_while_never_runs() { let interp = run("let x = 0; while (false) { x = 1; }"); assert_num(&get_var(&interp, "x"), 0.0); } // ========================================================================= // Control flow: for // ========================================================================= #[test] fn eval_for_loop() { let interp = run("let sum = 0; for (let i = 0; i < 5; i = i + 1) { sum = sum + i; }"); assert_num(&get_var(&interp, "sum"), 10.0); // 0+1+2+3+4 } #[test] fn eval_for_empty_clauses() { // Infinite loop with break — just test it parses and doesn't crash on one iter let interp = run("let x = 0; for (;;) { x = 1; break; }"); assert_num(&get_var(&interp, "x"), 1.0); } // ========================================================================= // Control flow: break / continue // ========================================================================= #[test] fn eval_break() { let interp = run("let i = 0; while (i < 100) { if (i == 5) { break; } i = i + 1; }"); assert_num(&get_var(&interp, "i"), 5.0); } #[test] fn eval_continue() { let interp = run("let sum = 0; let j = 0; while (j < 5) { j = j + 1; if (j == 3) { continue; } sum = sum + j; }"); assert_num(&get_var(&interp, "sum"), 12.0); // 1+2+4+5 = 12 (skips 3) } #[test] fn eval_break_in_for() { let interp = run("let found = -1; for (let k = 0; k < 100; k = k + 1) { if (k * k > 50) { found = k; break; } }"); assert_num(&get_var(&interp, "found"), 8.0); // 8*8=64 > 50 } // ========================================================================= // Functions // ========================================================================= #[test] fn eval_function_call() { let interp = run("fn add(a, b) { return a + b; } let result = add(3, 4);"); assert_num(&get_var(&interp, "result"), 7.0); } #[test] fn eval_function_no_return() { let interp = run("fn foo() { 42; } let result = foo();"); assert_nil(&get_var(&interp, "result")); // No explicit return → nil } #[test] fn eval_recursion() { let interp = run("fn fib(n) { if (n <= 1) { return n; } return fib(n-1) + fib(n-2); } let f = fib(10);"); assert_num(&get_var(&interp, "f"), 55.0); } #[test] fn eval_nested_function_calls() { let interp = run("fn double(x) { return x * 2; } let r = double(double(3));"); assert_num(&get_var(&interp, "r"), 12.0); } // ========================================================================= // Closures // ========================================================================= #[test] fn eval_simple_closure() { let interp = run(" fn make_adder(x) { return fn(y) { return x + y; }; } let add10 = make_adder(10); let r = add10(5); "); assert_num(&get_var(&interp, "r"), 15.0); } #[test] fn eval_closure_captures_by_value_semantics() { // Each call to make_adder creates an independent closure let interp = run(" fn make_adder(x) { return fn(y) { return x + y; }; } let add5 = make_adder(5); let add10 = make_adder(10); let r1 = add5(0); let r2 = add10(0); "); assert_num(&get_var(&interp, "r1"), 5.0); assert_num(&get_var(&interp, "r2"), 10.0); } // ========================================================================= // Arrays // ========================================================================= #[test] fn eval_array_creation() { match eval_expr("[1, 2, 3]") { Value::Array(arr) => { let arr = arr.borrow(); assert_eq!(arr.len(), 3); assert_num(&arr[0], 1.0); assert_num(&arr[1], 2.0); assert_num(&arr[2], 3.0); } v => panic!("Expected Array, got {:?}", v), } } #[test] fn eval_empty_array() { match eval_expr("[]") { Value::Array(arr) => assert_eq!(arr.borrow().len(), 0), v => panic!("Expected Array, got {:?}", v), } } #[test] fn eval_array_index_access() { let interp = run("let arr = [10, 20, 30]; let x = arr[0]; let y = arr[2];"); assert_num(&get_var(&interp, "x"), 10.0); assert_num(&get_var(&interp, "y"), 30.0); } #[test] fn eval_array_index_assignment() { let interp = run("let arr = [1, 2, 3]; arr[0] = 99;"); match get_var(&interp, "arr") { Value::Array(arr) => assert_num(&arr.borrow()[0], 99.0), v => panic!("Expected Array, got {:?}", v), } } #[test] fn eval_array_compound_index_assignment() { let interp = run("let arr = [1, 2, 3]; arr[0] += 10;"); match get_var(&interp, "arr") { Value::Array(arr) => assert_num(&arr.borrow()[0], 11.0), v => panic!("Expected Array, got {:?}", v), } } #[test] fn eval_nested_arrays() { let interp = run("let arr = [[1, 2], [3, 4]]; let x = arr[1][0];"); assert_num(&get_var(&interp, "x"), 3.0); } #[test] fn eval_array_equality() { assert_bool(&eval_expr("[1, 2, 3] == [1, 2, 3]"), true); assert_bool(&eval_expr("[1, 2] == [3, 4]"), false); assert_bool(&eval_expr("[1] == [1, 2]"), false); } // ========================================================================= // Object equality // ========================================================================= #[test] fn eval_object_equality_same() { let interp = run("let a = {x: 1, y: 2}; let b = {x: 1, y: 2}; let r = a == b;"); assert_bool(&get_var(&interp, "r"), true); } #[test] fn eval_object_equality_different_values() { let interp = run("let a = {x: 1}; let b = {x: 2}; let r = a == b;"); assert_bool(&get_var(&interp, "r"), false); } #[test] fn eval_object_equality_different_keys() { let interp = run("let a = {x: 1}; let b = {y: 1}; let r = a == b;"); assert_bool(&get_var(&interp, "r"), false); } #[test] fn eval_object_equality_different_size() { let interp = run("let a = {x: 1}; let b = {x: 1, y: 2}; let r = a == b;"); assert_bool(&get_var(&interp, "r"), false); } #[test] fn eval_object_equality_empty() { let interp = run("let a = {}; let b = {}; let r = a == b;"); assert_bool(&get_var(&interp, "r"), true); } // ========================================================================= // Strings as indexable // ========================================================================= #[test] fn eval_string_index() { match eval_expr("\"hello\"[0]") { Value::String(s) => assert_eq!(s, "h"), v => panic!("Expected String for 'h', got {:?}", v), } } #[test] fn eval_string_index_last() { match eval_expr("\"abc\"[2]") { Value::String(s) => assert_eq!(s, "c"), v => panic!("Expected String for 'c', got {:?}", v), } } // ========================================================================= // Objects // ========================================================================= #[test] fn eval_object_creation() { match eval_expr("{ name: \"aster\", version: 1 }") { Value::Object(obj) => { let obj = obj.borrow(); match obj.get("name") { Some(Value::String(s)) => assert_eq!(s, "aster"), v => panic!("Expected String 'aster', got {:?}", v), } match obj.get("version") { Some(Value::Number(n)) => assert!((n - 1.0).abs() < f64::EPSILON), v => panic!("Expected Number 1, got {:?}", v), } } v => panic!("Expected Object, got {:?}", v), } } #[test] fn eval_object_property_access() { let interp = run("let obj = { name: \"aster\" }; let n = obj.name;"); match get_var(&interp, "n") { Value::String(s) => assert_eq!(s, "aster"), v => panic!("Expected String 'aster', got {:?}", v), } } #[test] fn eval_object_property_assignment() { let interp = run("let obj = { name: \"old\" }; obj.name = \"new\";"); match get_var(&interp, "obj") { Value::Object(obj) => { match obj.borrow().get("name") { Some(Value::String(s)) => assert_eq!(s, "new"), v => panic!("Expected String 'new', got {:?}", v), } } v => panic!("Expected Object, got {:?}", v), } } #[test] fn eval_object_compound_property_assignment() { let interp = run("let obj = { age: 26 }; obj.age -= 2;"); match get_var(&interp, "obj") { Value::Object(obj) => { assert_num(obj.borrow().get("age").unwrap(), 24.0); } v => panic!("Expected Object, got {:?}", v), } } // ========================================================================= // Built-in functions // ========================================================================= #[test] fn eval_builtin_clock() { // clock() returns a number (Unix timestamp in seconds) match eval_expr("clock()") { Value::Number(n) => assert!(n > 0.0, "clock should return positive number"), v => panic!("Expected Number from clock(), got {:?}", v), } } #[test] fn eval_builtin_os_clock() { match eval_expr("os.clock()") { Value::Number(n) => assert!(n > 0.0), v => panic!("Expected Number from os.clock(), got {:?}", v), } } #[test] fn eval_print_returns_nil() { // print() returns nil assert_nil(&eval_expr("print(\"test\")")); } // ========================================================================= // Runtime errors // ========================================================================= #[test] fn error_undefined_variable() { let result = std::panic::catch_unwind(|| { run("let x = y;"); }); // run() asserts no errors, so this should panic assert!(result.is_err(), "Expected error for undefined variable"); } #[test] fn error_division_by_zero() { let result = std::panic::catch_unwind(|| { eval_expr("1 / 0"); }); assert!(result.is_err(), "Expected error for division by zero"); } #[test] fn error_modulo_by_zero() { let result = std::panic::catch_unwind(|| { eval_expr("1 % 0"); }); assert!(result.is_err(), "Expected error for modulo by zero"); } #[test] fn error_type_mismatch_negate_string() { let result = std::panic::catch_unwind(|| { eval_expr("-\"hello\""); }); assert!(result.is_err(), "Expected error for negating a string"); } #[test] fn eval_number_plus_string_concatenates() { match eval_expr("1 + \"hello\"") { Value::String(s) => assert_eq!(s, "1hello"), v => panic!("Expected String, got {:?}", v), } } #[test] fn error_call_non_function() { let result = std::panic::catch_unwind(|| { eval_expr("42()"); }); assert!(result.is_err(), "Expected error for calling non-function"); } #[test] fn error_index_out_of_bounds() { let result = std::panic::catch_unwind(|| { eval_expr("[1, 2][5]"); }); assert!(result.is_err(), "Expected error for index out of bounds"); } #[test] fn error_property_on_non_object() { let result = std::panic::catch_unwind(|| { eval_expr("42.name"); }); assert!(result.is_err(), "Expected error for property on non-object"); } #[test] fn error_index_on_non_array() { let result = std::panic::catch_unwind(|| { eval_expr("42[0]"); }); assert!(result.is_err(), "Expected error for index on non-array"); } // ========================================================================= // Object index access: obj[key] // ========================================================================= #[test] fn eval_object_index_get() { assert_num(&eval_expr("{a: 42}[\"a\"]"), 42.0); } #[test] fn eval_object_index_get_nested() { assert_num(&eval_expr("{inner: {val: 7}}[\"inner\"][\"val\"]"), 7.0); } #[test] fn eval_object_index_get_variable_key() { let interp = run("let obj = {x: 10, y: 20}; let k = \"y\"; let result = obj[k];"); assert_num(&get_var(&interp, "result"), 20.0); } #[test] fn eval_object_index_get_undefined() { let result = std::panic::catch_unwind(|| { eval_expr("{}[\"x\"]"); }); assert!(result.is_err(), "Expected error for undefined property"); } #[test] fn eval_object_index_set_existing() { let interp = run("let obj = {a: 1}; obj[\"a\"] = 99;"); match get_var(&interp, "obj") { Value::Object(map) => { let map = map.borrow(); assert_num(map.get("a").unwrap(), 99.0); } v => panic!("Expected Object, got {:?}", v), } } #[test] fn eval_object_index_set_new_key() { let interp = run("let obj = {}; obj[\"name\"] = \"Aster\";"); match get_var(&interp, "obj") { Value::Object(map) => { let map = map.borrow(); match map.get("name").unwrap() { Value::String(s) => assert_eq!(s, "Aster"), v => panic!("Expected String, got {:?}", v), } } v => panic!("Expected Object, got {:?}", v), } } #[test] fn eval_object_index_compound_assign() { let interp = run("let obj = {count: 10}; obj[\"count\"] += 5;"); match get_var(&interp, "obj") { Value::Object(map) => { let map = map.borrow(); assert_num(map.get("count").unwrap(), 15.0); } v => panic!("Expected Object, got {:?}", v), } } #[test] fn error_object_index_non_string() { let result = std::panic::catch_unwind(|| { eval_expr("{a: 1}[42]"); }); assert!(result.is_err(), "Expected error for non-string object index"); } #[test] fn error_object_index_set_non_string() { let result = std::panic::catch_unwind(|| { eval_expr("let obj = {a: 1}; obj[true] = 2;"); }); assert!(result.is_err(), "Expected error for non-string object index set"); } // ========================================================================= // Builtins: len // ========================================================================= #[test] fn eval_len_string() { assert_num(&eval_expr("len(\"hello\")"), 5.0); } #[test] fn eval_len_empty_string() { assert_num(&eval_expr("len(\"\")"), 0.0); } #[test] fn eval_len_array() { assert_num(&eval_expr("len([1, 2, 3])"), 3.0); } #[test] fn eval_len_empty_array() { assert_num(&eval_expr("len([])"), 0.0); } #[test] fn eval_len_object() { assert_num(&eval_expr("len({a: 1, b: 2})"), 2.0); } #[test] fn eval_len_empty_object() { assert_num(&eval_expr("len({})"), 0.0); } #[test] fn error_len_no_args() { let result = std::panic::catch_unwind(|| { eval_expr("len()"); }); assert!(result.is_err(), "Expected error for len() with no args"); } #[test] fn error_len_number() { let result = std::panic::catch_unwind(|| { eval_expr("len(42)"); }); assert!(result.is_err(), "Expected error for len(42)"); } // ========================================================================= // Builtins: typeof // ========================================================================= #[test] fn eval_typeof_number() { match eval_expr("typeof(42)") { Value::String(s) => assert_eq!(s, "number"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_typeof_string() { match eval_expr("typeof(\"hello\")") { Value::String(s) => assert_eq!(s, "string"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_typeof_bool() { match eval_expr("typeof(true)") { Value::String(s) => assert_eq!(s, "bool"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_typeof_nil() { match eval_expr("typeof(nil)") { Value::String(s) => assert_eq!(s, "nil"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_typeof_object() { match eval_expr("typeof({a: 1})") { Value::String(s) => assert_eq!(s, "object"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_typeof_array() { match eval_expr("typeof([1, 2])") { Value::String(s) => assert_eq!(s, "array"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_typeof_function() { let interp = run("fn f() {}"); // f is a variable, not an expression — use get_var match get_var(&interp, "f") { Value::Function(_) => {} // just verify it's a function v => panic!("Expected Function, got {:?}", v), } } // ========================================================================= // Builtins: push / pop // ========================================================================= #[test] fn eval_push_returns_value() { assert_num(&eval_expr("push([1, 2], 3)"), 3.0); } #[test] fn eval_push_modifies_array() { let interp = run("let arr = [1, 2]; push(arr, 3);"); match get_var(&interp, "arr") { Value::Array(arr) => { let arr = arr.borrow(); assert_eq!(arr.len(), 3); assert_num(&arr[0], 1.0); assert_num(&arr[1], 2.0); assert_num(&arr[2], 3.0); } v => panic!("Expected Array, got {:?}", v), } } #[test] fn eval_push_string_to_array() { let interp = run("let arr = [\"a\"]; push(arr, \"b\");"); match get_var(&interp, "arr") { Value::Array(arr) => { let arr = arr.borrow(); assert_eq!(arr.len(), 2); } v => panic!("Expected Array, got {:?}", v), } } #[test] fn eval_pop_returns_last() { assert_num(&eval_expr("pop([1, 2, 3])"), 3.0); } #[test] fn eval_pop_modifies_array() { let interp = run("let arr = [1, 2, 3]; pop(arr);"); match get_var(&interp, "arr") { Value::Array(arr) => { let arr = arr.borrow(); assert_eq!(arr.len(), 2); assert_num(&arr[0], 1.0); assert_num(&arr[1], 2.0); } v => panic!("Expected Array, got {:?}", v), } } #[test] fn error_pop_empty_array() { let result = std::panic::catch_unwind(|| { eval_expr("pop([])"); }); assert!(result.is_err(), "Expected error for pop([])"); } #[test] fn error_push_non_array() { let result = std::panic::catch_unwind(|| { eval_expr("push(42, 1)"); }); assert!(result.is_err(), "Expected error for push on non-array"); } #[test] fn error_pop_non_array() { let result = std::panic::catch_unwind(|| { eval_expr("pop(42)"); }); assert!(result.is_err(), "Expected error for pop on non-array"); } // ========================================================================= // For-In loops // ========================================================================= #[test] fn eval_forin_array_elements() { // Sum elements via for-in let interp = run("let sum = 0; let arr = [1, 2, 3]; for (x in arr) { sum = sum + x; }"); assert_num(&get_var(&interp, "sum"), 6.0); } #[test] fn eval_forin_empty_array() { let interp = run("let count = 0; let arr = []; for (x in arr) { count = count + 1; }"); assert_num(&get_var(&interp, "count"), 0.0); } #[test] fn eval_forin_object_keys() { let interp = run("let keys = \"\"; let obj = {a: 1, b: 2}; for (k in obj) { keys = keys + k; }"); match get_var(&interp, "keys") { Value::String(s) => { // Object iteration order is not guaranteed, so check length assert_eq!(s.len(), 2); assert!(s.contains('a')); assert!(s.contains('b')); } v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_forin_string_chars() { let interp = run("let result = \"\"; for (c in \"ab\") { result = result + c; }"); match get_var(&interp, "result") { Value::String(s) => assert_eq!(s, "ab"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_forin_break() { let interp = run("let sum = 0; for (x in [1, 2, 3, 4]) { if (x == 3) { break; } sum = sum + x; }"); assert_num(&get_var(&interp, "sum"), 3.0); // 1 + 2 } #[test] fn eval_forin_continue() { let interp = run("let sum = 0; for (x in [1, 2, 3]) { if (x == 2) { continue; } sum = sum + x; }"); assert_num(&get_var(&interp, "sum"), 4.0); // 1 + 3 } #[test] fn eval_forin_loop_var_doesnt_leak() { // Loop variable should not be accessible outside the loop let interp = run("for (x in [1]) { let _ = x; }"); // x should not exist in the outer scope match get_var(&interp, "x") { Value::Nil => {} // Expected: x is not defined v => panic!("Expected Nil (undefined), got {:?}", v), } } #[test] fn eval_forin_with_object_array() { // Iterate over an array of objects let interp = run(" let arr = [{v: 10}, {v: 20}]; let total = 0; for (obj in arr) { total = total + obj.v; } "); assert_num(&get_var(&interp, "total"), 30.0); } #[test] fn error_forin_non_iterable() { let result = std::panic::catch_unwind(|| { run("for (x in 42) { let _ = x; }"); // Can't iterate a number }); assert!(result.is_err(), "Expected error for for-in on non-iterable"); } // ========================================================================= // String builtins // ========================================================================= #[test] fn eval_split_by_comma() { match eval_expr("split(\"a,b,c\", \",\")") { Value::Array(arr) => { let arr = arr.borrow(); assert_eq!(arr.len(), 3); match &arr[0] { Value::String(s) => assert_eq!(s, "a"), v => panic!("{:?}", v) } } v => panic!("Expected Array, got {:?}", v), } } #[test] fn eval_split_by_empty_delim_returns_chars() { match eval_expr("split(\"ab\", \"\")") { Value::Array(arr) => { let arr = arr.borrow(); assert_eq!(arr.len(), 2); } v => panic!("Expected Array, got {:?}", v), } } #[test] fn eval_split_no_match_returns_whole_string() { match eval_expr("split(\"hello\", \",\")") { Value::Array(arr) => { let arr = arr.borrow(); assert_eq!(arr.len(), 1); } v => panic!("Expected Array, got {:?}", v), } } #[test] fn eval_trim_spaces() { match eval_expr("trim(\" hello \")") { Value::String(s) => assert_eq!(s, "hello"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_trim_no_whitespace() { match eval_expr("trim(\"hello\")") { Value::String(s) => assert_eq!(s, "hello"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_substring_start() { match eval_expr("substring(\"hello\", 1)") { Value::String(s) => assert_eq!(s, "ello"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_substring_start_and_length() { match eval_expr("substring(\"hello\", 1, 3)") { Value::String(s) => assert_eq!(s, "ell"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_substring_start_beyond_length() { match eval_expr("substring(\"hi\", 10)") { Value::String(s) => assert_eq!(s, ""), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_replace_single() { match eval_expr("replace(\"hello world\", \"world\", \"aster\")") { Value::String(s) => assert_eq!(s, "hello aster"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_replace_multiple() { match eval_expr("replace(\"a,a,a\", \"a\", \"b\")") { Value::String(s) => assert_eq!(s, "b,b,b"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_replace_no_match() { match eval_expr("replace(\"hello\", \"x\", \"y\")") { Value::String(s) => assert_eq!(s, "hello"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_contains_true() { assert_bool(&eval_expr("contains(\"hello\", \"ell\")"), true); } #[test] fn eval_contains_false() { assert_bool(&eval_expr("contains(\"hello\", \"xyz\")"), false); } #[test] fn eval_upper() { match eval_expr("upper(\"hello\")") { Value::String(s) => assert_eq!(s, "HELLO"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_lower() { match eval_expr("lower(\"HELLO\")") { Value::String(s) => assert_eq!(s, "hello"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_starts_with_true() { assert_bool(&eval_expr("starts_with(\"hello\", \"he\")"), true); } #[test] fn eval_starts_with_false() { assert_bool(&eval_expr("starts_with(\"hello\", \"lo\")"), false); } #[test] fn eval_ends_with_true() { assert_bool(&eval_expr("ends_with(\"hello\", \"lo\")"), true); } #[test] fn eval_ends_with_false() { assert_bool(&eval_expr("ends_with(\"hello\", \"he\")"), false); } #[test] fn error_split_wrong_arg_count() { let result = std::panic::catch_unwind(|| { eval_expr("split(\"a\")"); }); assert!(result.is_err(), "Expected error for split with 1 arg"); } #[test] fn error_trim_non_string() { let result = std::panic::catch_unwind(|| { eval_expr("trim(42)"); }); assert!(result.is_err(), "Expected error for trim(42)"); } // ========================================================================= // Array properties & methods (dot notation) // ========================================================================= #[test] fn eval_array_length_property() { assert_num(&eval_expr("[1, 2, 3].length"), 3.0); } #[test] fn eval_empty_array_length() { assert_num(&eval_expr("[].length"), 0.0); } #[test] fn eval_array_length_via_variable() { let interp = run("let arr = [10, 20, 30, 40]; let len = arr.length;"); assert_num(&get_var(&interp, "len"), 4.0); } #[test] fn eval_array_push_method_returns_value() { let interp = run("let arr = [1, 2]; let result = arr.push(3);"); assert_num(&get_var(&interp, "result"), 3.0); } #[test] fn eval_array_push_method_modifies_array() { let interp = run("let arr = [1, 2]; arr.push(3);"); match get_var(&interp, "arr") { Value::Array(arr) => { let arr = arr.borrow(); assert_eq!(arr.len(), 3); assert_num(&arr[2], 3.0); } v => panic!("Expected Array, got {:?}", v), } } #[test] fn eval_array_push_method_chained() { // push returns the pushed value, so chaining .push().push() won't work // but push returns the pushed value, verify that independently let interp = run("let arr = []; let a = arr.push(1); let b = arr.push(2); let c = arr.push(3);"); assert_num(&get_var(&interp, "a"), 1.0); assert_num(&get_var(&interp, "b"), 2.0); assert_num(&get_var(&interp, "c"), 3.0); match get_var(&interp, "arr") { Value::Array(arr) => { let arr = arr.borrow(); assert_eq!(arr.len(), 3); } v => panic!("Expected Array, got {:?}", v), } } #[test] fn eval_array_pop_method_returns_last() { assert_num(&eval_expr("[1, 2, 3].pop()"), 3.0); } #[test] fn eval_array_pop_method_modifies_array() { let interp = run("let arr = [1, 2, 3]; let popped = arr.pop();"); assert_num(&get_var(&interp, "popped"), 3.0); match get_var(&interp, "arr") { Value::Array(arr) => { let arr = arr.borrow(); assert_eq!(arr.len(), 2); } v => panic!("Expected Array, got {:?}", v), } } #[test] fn error_array_pop_empty() { let result = std::panic::catch_unwind(|| { eval_expr("[].pop()"); }); assert!(result.is_err(), "Expected error for pop() on empty array"); } #[test] fn error_array_unknown_property() { let result = std::panic::catch_unwind(|| { eval_expr("[1, 2].foo"); }); assert!(result.is_err(), "Expected error for unknown array property"); } #[test] fn error_non_array_push_method() { let result = std::panic::catch_unwind(|| { eval_expr("42.push(1)"); }); assert!(result.is_err(), "Expected error for .push() on non-array"); } // ========================================================================= // String properties & methods (dot notation) // ========================================================================= #[test] fn eval_string_length_property() { assert_num(&eval_expr("\"hello\".length"), 5.0); } #[test] fn eval_empty_string_length() { assert_num(&eval_expr("\"\".length"), 0.0); } #[test] fn eval_string_length_via_variable() { let interp = run("let s = \"hi there\"; let len = s.length;"); assert_num(&get_var(&interp, "len"), 8.0); } #[test] fn eval_string_upper_method() { match eval_expr("\"hello\".upper()") { Value::String(s) => assert_eq!(s, "HELLO"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_string_lower_method() { match eval_expr("\"HELLO\".lower()") { Value::String(s) => assert_eq!(s, "hello"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_string_trim_method() { match eval_expr("\" hi \".trim()") { Value::String(s) => assert_eq!(s, "hi"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_string_contains_method_true() { assert_bool(&eval_expr("\"hello\".contains(\"ell\")"), true); } #[test] fn eval_string_contains_method_false() { assert_bool(&eval_expr("\"hello\".contains(\"xyz\")"), false); } #[test] fn eval_string_starts_with_method() { assert_bool(&eval_expr("\"hello\".starts_with(\"he\")"), true); assert_bool(&eval_expr("\"hello\".starts_with(\"lo\")"), false); } #[test] fn eval_string_ends_with_method() { assert_bool(&eval_expr("\"hello\".ends_with(\"lo\")"), true); assert_bool(&eval_expr("\"hello\".ends_with(\"he\")"), false); } #[test] fn eval_string_substring_method() { match eval_expr("\"hello\".substring(1, 3)") { Value::String(s) => assert_eq!(s, "ell"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_string_replace_method() { match eval_expr("\"hello world\".replace(\"world\", \"aster\")") { Value::String(s) => assert_eq!(s, "hello aster"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_string_split_method() { match eval_expr("\"a,b,c\".split(\",\")") { Value::Array(arr) => { let arr = arr.borrow(); assert_eq!(arr.len(), 3); } v => panic!("Expected Array, got {:?}", v), } } #[test] fn eval_string_method_chaining() { match eval_expr("\" hello \".trim().upper()") { Value::String(s) => assert_eq!(s, "HELLO"), v => panic!("Expected String, got {:?}", v), } } #[test] fn error_string_unknown_property() { let result = std::panic::catch_unwind(|| { eval_expr("\"hi\".foo"); }); assert!(result.is_err(), "Expected error for unknown string property"); } // ========================================================================= // Module system (require) // ========================================================================= #[test] fn eval_require_math_module() { let interp = run("let math = require(\"tests/fixtures/math.ast\");"); let math = get_var(&interp, "math"); match math { Value::Object(obj) => { let obj = obj.borrow(); assert!(obj.contains_key("add"), "math should contain 'add'"); assert!(obj.contains_key("mul"), "math should contain 'mul'"); assert!(obj.contains_key("version"), "math should contain 'version'"); } v => panic!("Expected Object, got {:?}", v), } } #[test] fn eval_require_call_exported_function() { let interp = run(r#" let math = require("tests/fixtures/math.ast"); let result = math.add(10, 32); "#); assert_num(&get_var(&interp, "result"), 42.0); } #[test] fn eval_require_cache_returns_same_object() { let interp = run(r#" let a = require("tests/fixtures/math.ast"); let b = require("tests/fixtures/math.ast"); let same = a == b; "#); assert_bool(&get_var(&interp, "same"), true); } #[test] fn eval_require_module_isolation() { // 模块看不到调用者的变量,但模块内部定义可以正常工作 let interp = run(r#" let secret = 99; let g = require("tests/fixtures/greet.ast"); let msg = g.greet("Aster"); "#); match get_var(&interp, "msg") { Value::String(s) => assert_eq!(s, "Hello!"), v => panic!("Expected String, got {:?}", v), } } #[test] fn eval_require_module_with_builtins() { // 模块内部可以使用内置函数(因为 builtins_env 是模块 env 的祖先) let interp = run(r#" let m = require("tests/fixtures/math.ast"); let r = m.add(5, 7); "#); assert_num(&get_var(&interp, "r"), 12.0); } #[test] fn eval_require_error_file_not_found() { let result = std::panic::catch_unwind(|| { run("let x = require(\"nonexistent_file_xyz.ast\");"); }); assert!(result.is_err(), "Expected error for missing module file"); } #[test] fn eval_require_error_no_args() { let result = std::panic::catch_unwind(|| { run("let x = require();"); }); assert!(result.is_err(), "Expected error for require() with no args"); } #[test] fn eval_require_error_non_string_arg() { let result = std::panic::catch_unwind(|| { run("let x = require(42);"); }); assert!(result.is_err(), "Expected error for require() with non-string arg"); } #[test] fn eval_require_error_broken_syntax() { let result = std::panic::catch_unwind(|| { run("let x = require(\"tests/fixtures/broken_syntax.ast\");"); }); assert!(result.is_err(), "Expected error for module with syntax error"); } #[test] fn eval_require_empty_module() { // 空模块应该返回空对象 let interp = run("let empty = require(\"tests/fixtures/sub/mod.ast\");"); match get_var(&interp, "empty") { Value::Object(obj) => { let obj = obj.borrow(); assert!(obj.contains_key("sub"), "empty module should contain 'sub'"); } v => panic!("Expected Object, got {:?}", v), } }