feat: 添加标准测试
This commit is contained in:
@@ -487,3 +487,684 @@ impl Interpreter {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Tests
|
||||
// ============================================================================
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::lexer::Lexer;
|
||||
use crate::parser::Parser;
|
||||
|
||||
/// Full pipeline: source → tokens → ast → interpret → last expression value.
|
||||
/// Wraps in `let __result = <expr>;` 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),
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 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 && <anything> 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 || <anything> returns true (the left value)
|
||||
match eval_expr("true || 999") {
|
||||
Value::Bool(true) => {}
|
||||
v => panic!("Expected Bool(true), got {:?}", v),
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 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);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 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);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 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_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 error_type_mismatch_add_number_and_string() {
|
||||
let result = std::panic::catch_unwind(|| {
|
||||
eval_expr("1 + \"hello\"");
|
||||
});
|
||||
assert!(result.is_err(), "Expected error for adding number + string");
|
||||
}
|
||||
|
||||
#[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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user