use crate::ast::*; use crate::error::RuntimeError; use crate::interpreter::Signal; use super::{Value, Env, Function}; use std::collections::HashMap; use std::rc::Rc; use std::cell::RefCell; pub struct Interpreter { pub env: Rc>, } impl Interpreter { pub fn new() -> Self { let env = Rc::new(RefCell::new(Env::new(None))); super::builtins::register_all(&env); Self { env } } pub fn interpret(&mut self, statements: Vec) -> Result<(), RuntimeError> { for stmt in statements { self.execute(stmt)?; } Ok(()) } fn execute(&mut self, stmt: Stmt) -> Result { match stmt { Stmt::Let { name, initializer, mutable } => { let val = self.evaluate(initializer)?; self.env.borrow_mut().define(name, val, mutable); Ok(Signal::None) } Stmt::ExprStmt(expr) => { self.evaluate(expr)?; Ok(Signal::None) } Stmt::Block(stmts) => { let previous = Rc::clone(&self.env); self.env = Rc::new(RefCell::new(Env::new(Some(previous)))); let mut signal = Signal::None; for s in stmts { signal = self.execute(s)?; if !matches!(signal, Signal::None) { break; } } let parent = self.env.borrow().parent.as_ref().unwrap().clone(); self.env = parent; Ok(signal) } Stmt::If { condition, then_branch, else_branch } => { let cond_val = self.evaluate(condition)?; if self.is_truthy(&cond_val) { self.execute(*then_branch) } else if let Some(else_branch) = else_branch { self.execute(*else_branch) } else { Ok(Signal::None) } } Stmt::While { condition, body } => { loop { let cond_val = self.evaluate(condition.clone())?; if !self.is_truthy(&cond_val) { break; } match self.execute(*body.clone())? { Signal::Break => break, Signal::Continue => continue, sig @ Signal::Return(_) => return Ok(sig), Signal::None => {} } } Ok(Signal::None) } Stmt::For { initializer, condition, step, body } => { if let Some(init) = initializer { self.execute(*init)?; } loop { if let Some(cond) = &condition { let cond_val = self.evaluate(cond.clone())?; if !self.is_truthy(&cond_val) { break; } } match self.execute(*body.clone())? { Signal::Break => break, Signal::Continue => { if let Some(step) = &step { self.evaluate(step.clone())?; } continue; } sig @ Signal::Return(_) => return Ok(sig), Signal::None => {} } if let Some(step) = &step { self.evaluate(step.clone())?; } } Ok(Signal::None) } Stmt::ForIn { var_name, iterable, body } => { let iter_val = self.evaluate(iterable)?; let items: Vec = match &iter_val { Value::Array(arr) => arr.borrow().clone(), Value::Object(obj) => obj.borrow().keys().map(|k| Value::String(k.clone())).collect(), Value::String(s) => s.chars().map(|c| Value::String(c.to_string())).collect(), _ => { return Err(RuntimeError::RuntimeError { message: "for-in requires an array, object, or string".into(), token: None, }); } }; let mut signal = Signal::None; for item in items { // Create a new scope for each iteration so the loop variable // is isolated and break/continue clean up correctly. let previous = Rc::clone(&self.env); self.env = Rc::new(RefCell::new(Env::new(Some(previous)))); self.env.borrow_mut().define(var_name.clone(), item, true); signal = self.execute(*body.clone())?; // Restore parent scope before checking signal let parent = self.env.borrow().parent.as_ref().unwrap().clone(); self.env = parent; match signal { Signal::Break => { signal = Signal::None; break; } Signal::Continue => { signal = Signal::None; continue; } sig @ Signal::Return(_) => return Ok(sig), Signal::None => {} } } Ok(signal) } Stmt::Function { name, params, body } => { let func = Value::Function(Rc::new(Function { params, body, env: Rc::clone(&self.env), name: Some(name.clone()), })); self.env.borrow_mut().define(name, func, true); Ok(Signal::None) } Stmt::Return(expr_opt) => { if let Some(expr) = expr_opt { Ok(Signal::Return(self.evaluate(expr)?)) } else { Ok(Signal::Return(Value::Nil)) } } Stmt::Break => Ok(Signal::Break), Stmt::Continue => Ok(Signal::Continue), } } fn evaluate(&mut self, expr: Expr) -> Result { match expr { Expr::Literal(lit) => Ok(match lit { crate::ast::expr::Literal::Number(n) => Value::Number(n), crate::ast::expr::Literal::String(s) => Value::String(s), crate::ast::expr::Literal::Bool(b) => Value::Bool(b), crate::ast::expr::Literal::Nil => Value::Nil, }), Expr::Variable(name) => { match self.env.borrow().get(&name) { Some(val) => Ok(val), None => Err(RuntimeError::RuntimeError { message: format!("Undefined variable '{}'", name), token: None, }), } } Expr::Assign { name, op, value } => { let rhs = self.evaluate(*value)?; let val = match op { AssignOp::Equal => rhs, _ => { let current = self.env.borrow().get(&name).ok_or_else(|| RuntimeError::RuntimeError { message: format!("Undefined variable '{}'", name), token: None })?; self.apply_assign_op(current.clone(), rhs, op)? } }; match self.env.borrow_mut().assign(&name, val.clone()) { Ok(true) => {} Ok(false) => { return Err(RuntimeError::RuntimeError { message: format!("Undefined variable '{}'", name), token: None, }); } Err(msg) => { return Err(RuntimeError::RuntimeError { message: msg, token: None, }); } } Ok(val) } Expr::Get { object, name } => { let obj = self.evaluate(*object)?; match obj { Value::Object(map) => { match map.borrow().get(&name) { Some(val) => Ok(val.clone()), None => Err(RuntimeError::RuntimeError { message: format!("Undefined property '{}'", name), token: None, }), } } _ => Err(RuntimeError::RuntimeError { message: "Only objects have properties".to_string(), token: None, }), } } Expr::Set { object, name, op, value } => { let obj = self.evaluate(*object)?; let rhs = self.evaluate(*value)?; match obj { Value::Object(map) => { let val = match op { AssignOp::Equal => rhs, _ => { let map_borrow = map.borrow(); let current = map_borrow.get(&name).ok_or_else(|| RuntimeError::RuntimeError { message: format!("Property '{}' does not exist", name), token: None })?; self.apply_assign_op(current.clone(), rhs, op)? } }; map.borrow_mut().insert(name, val.clone()); Ok(val) } _ => Err(RuntimeError::RuntimeError { message: "Only objects have properties".to_string(), token: None, }), } } Expr::IndexGet { array, index } => { let target = self.evaluate(*array)?; let idx_val = self.evaluate(*index)?; // Object index access: obj["key"] if let Value::Object(map) = &target { let key = match &idx_val { Value::String(s) => s.clone(), _ => return Err(RuntimeError::RuntimeError { message: "Object index must be a string".into(), token: None, }), }; return map.borrow().get(&key).cloned().ok_or_else(|| RuntimeError::RuntimeError { message: format!("Undefined property '{}'", key), token: None, }); } let i = self.as_array_index(&idx_val)?; match target { Value::Array(vec) => { let vec = vec.borrow(); vec.get(i).cloned().ok_or_else(|| RuntimeError::RuntimeError { message: format!("Index {} out of bounds (len {})", i, vec.len()), token: None, }) } Value::String(s) => { let chars: Vec = s.chars().collect(); chars.get(i).map(|&c| Value::String(c.to_string())).ok_or_else(|| RuntimeError::RuntimeError { message: format!("Index {} out of bounds (len {})", i, chars.len()), token: None, }) } _ => Err(RuntimeError::RuntimeError { message: "Index access on non-array, non-string, non-object value".to_string(), token: None, }), } } Expr::IndexSet { array, index, op, value } => { let target = self.evaluate(*array)?; let idx_val = self.evaluate(*index)?; let rhs = self.evaluate(*value)?; // Object index assignment: obj["key"] = value if let Value::Object(map) = &target { let key = match &idx_val { Value::String(s) => s.clone(), _ => return Err(RuntimeError::RuntimeError { message: "Object index must be a string".into(), token: None, }), }; let val = match op { AssignOp::Equal => rhs, _ => { let map_borrow = map.borrow(); let current = map_borrow.get(&key).ok_or_else(|| RuntimeError::RuntimeError { message: format!("Property '{}' does not exist", key), token: None, })?; self.apply_assign_op(current.clone(), rhs, op)? } }; map.borrow_mut().insert(key, val.clone()); return Ok(val); } let i = self.as_array_index(&idx_val)?; match target { Value::Array(vec) => { let mut vec = vec.borrow_mut(); if i >= vec.len() { return Err(RuntimeError::RuntimeError { message: format!("Index {} out of bounds (len {})", i, vec.len()), token: None, }); } let val = match op { AssignOp::Equal => rhs, _ => { let current = vec[i].clone(); self.apply_assign_op(current, rhs, op)? } }; vec[i] = val.clone(); Ok(val) } _ => Err(RuntimeError::RuntimeError { message: "Index assignment on non-array, non-object value".to_string(), token: None, }), } } Expr::ObjectLiteral { properties } => { let mut map = HashMap::new(); for (key, value_expr) in properties { let value = self.evaluate(value_expr)?; map.insert(key, value); } Ok(Value::Object(Rc::new(RefCell::new(map)))) } Expr::ArrayLiteral { elements } => { let mut arr = Vec::new(); for e in elements { arr.push(self.evaluate(e)?); } Ok(Value::Array(Rc::new(RefCell::new(arr)))) } Expr::Unary { op, right } => { let val = self.evaluate(*right)?; match op { crate::ast::expr::UnaryOp::Negate => match val { Value::Number(n) => Ok(Value::Number(-n)), _ => Err(RuntimeError::RuntimeError { message: "Unary '-' on non-number".to_string(), token: None, }), }, crate::ast::expr::UnaryOp::Not => Ok(Value::Bool(!self.is_truthy(&val))), } } Expr::Binary { left, op, right } => { let l = self.evaluate(*left)?; let r = self.evaluate(*right)?; match op { crate::ast::expr::BinaryOp::Add => { // If either operand is a string, coerce both to string and concatenate. if matches!(l, Value::String(_)) || matches!(r, Value::String(_)) { Ok(Value::String(format!("{}{}", l, r))) } else { match (l, r) { (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a+b)), _ => Err(RuntimeError::RuntimeError { message: "Invalid '+' operands".to_string(), token: None, }), } } } crate::ast::expr::BinaryOp::Sub => match (l,r) { (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a-b)), _ => Err(RuntimeError::RuntimeError { message: "Invalid '-' operands".to_string(), token: None, }), }, crate::ast::expr::BinaryOp::Mul => match (l,r) { (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a*b)), _ => Err(RuntimeError::RuntimeError { message: "Invalid '*' operands".to_string(), token: None, }), }, crate::ast::expr::BinaryOp::Div => match (l,r) { (Value::Number(_), Value::Number(b)) if b == 0.0 => { Err(RuntimeError::RuntimeError { message: "Division by zero".to_string(), token: None, }) } (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a/b)), _ => Err(RuntimeError::RuntimeError { message: "Invalid '/' operands".to_string(), token: None, }), }, crate::ast::expr::BinaryOp::Mod => match (l,r) { (Value::Number(_), Value::Number(b)) if b == 0.0 => { Err(RuntimeError::RuntimeError { message: "Modulo by zero".to_string(), token: None, }) } (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a%b)), _ => Err(RuntimeError::RuntimeError { message: "Invalid '%' operands".to_string(), token: None, }), }, crate::ast::expr::BinaryOp::Greater => Ok(Value::Bool(self.as_number(&l)? > self.as_number(&r)?)), crate::ast::expr::BinaryOp::GreaterEqual => Ok(Value::Bool(self.as_number(&l)? >= self.as_number(&r)?)), crate::ast::expr::BinaryOp::Less => Ok(Value::Bool(self.as_number(&l)? < self.as_number(&r)?)), crate::ast::expr::BinaryOp::LessEqual => Ok(Value::Bool(self.as_number(&l)? <= self.as_number(&r)?)), crate::ast::expr::BinaryOp::Equal => Ok(Value::Bool(self.is_equal(&l,&r))), crate::ast::expr::BinaryOp::NotEqual => Ok(Value::Bool(!self.is_equal(&l,&r))), } } Expr::Logical { left, op, right } => { let l = self.evaluate(*left)?; match op { crate::ast::expr::LogicalOp::And => { Ok(if !self.is_truthy(&l) { l } else { self.evaluate(*right)? }) } crate::ast::expr::LogicalOp::Or => { Ok(if self.is_truthy(&l) { l } else { self.evaluate(*right)? }) } } } Expr::Ternary { condition, then_branch, else_branch } => { let cond = self.evaluate(*condition)?; if self.is_truthy(&cond) { self.evaluate(*then_branch) } else { self.evaluate(*else_branch) } } Expr::Call { callee, arguments } => { let func = self.evaluate(*callee)?; let mut args = Vec::new(); for e in arguments { args.push(self.evaluate(e)?); } self.call_function(func, args) } Expr::Lambda { params, body } => { Ok(Value::Function(Rc::new(Function { params, body, env: Rc::clone(&self.env), name: None, }))) } } } fn call_function(&mut self, func_val: Value, args: Vec) -> Result { match func_val { Value::NativeFunction(native_fn) => { native_fn(args) } Value::Function(f) => { let env = Rc::new(RefCell::new(Env::new(Some(Rc::clone(&f.env))))); if let Some(name) = &f.name { env.borrow_mut().define(name.clone(), Value::Function(Rc::clone(&f)), true); } for (i,param) in f.params.iter().enumerate() { let val = args.get(i).cloned().unwrap_or(Value::Nil); env.borrow_mut().define(param.clone(), val, true); } let previous = Rc::clone(&self.env); self.env = env; let mut ret = Value::Nil; for stmt in &f.body { match self.execute(stmt.clone())? { Signal::Return(val) => { ret = val; break; } Signal::None => {} Signal::Break | Signal::Continue => { return Err(RuntimeError::RuntimeError { message: "break/continue outside of loop".to_string(), token: None, }); } } } self.env = previous; Ok(ret) } _ => { Err(RuntimeError::RuntimeError { message: "Attempt to call non-function".to_string(), token: None, }) } } } fn is_truthy(&self, val: &Value) -> bool { match val { Value::Nil => false, Value::Bool(b) => *b, _ => true, } } fn is_equal(&self, a: &Value, b: &Value) -> bool { match (a,b) { (Value::Nil, Value::Nil) => true, (Value::Bool(x), Value::Bool(y)) => x==y, (Value::Number(x), Value::Number(y)) => x==y, (Value::String(x), Value::String(y)) => x==y, (Value::Array(x), Value::Array(y)) => { let x = x.borrow(); let y = y.borrow(); if x.len() != y.len() { return false; } x.iter().zip(y.iter()).all(|(a,b)| self.is_equal(a,b)) } (Value::Object(x), Value::Object(y)) => { let x = x.borrow(); let y = y.borrow(); if x.len() != y.len() { return false; } x.iter().all(|(k, v)| { y.get(k).map_or(false, |yv| self.is_equal(v, yv)) }) } _ => false, } } fn apply_assign_op(&self, left: Value, right: Value, op: AssignOp) -> Result { match op { AssignOp::Equal => Ok(right), AssignOp::PlusEqual => { if let Value::String(s) = &left { Ok(Value::String(format!("{}{}", s, right))) } else { let left_num = self.as_number(&left)?; let right_num = self.as_number(&right)?; Ok(Value::Number(left_num + right_num)) } } AssignOp::MinusEqual => { let left_num = self.as_number(&left)?; let right_num = self.as_number(&right)?; Ok(Value::Number(left_num - right_num)) } AssignOp::StarEqual => { let left_num = self.as_number(&left)?; let right_num = self.as_number(&right)?; Ok(Value::Number(left_num * right_num)) } AssignOp::SlashEqual => { let left_num = self.as_number(&left)?; let right_num = self.as_number(&right)?; Ok(Value::Number(left_num / right_num)) } AssignOp::PercentEqual => { let left_num = self.as_number(&left)?; let right_num = self.as_number(&right)?; Ok(Value::Number(left_num % right_num)) } } } fn as_number(&self, val: &Value) -> Result { if let Value::Number(n) = val { Ok(*n) } else { Err(RuntimeError::RuntimeError { message: "Expected number".to_string(), token: None, }) } } fn as_array_index(&self, val: &Value) -> Result { if let Value::Number(n) = val { if *n < 0.0 || n.fract() != 0.0 { return Err(RuntimeError::RuntimeError { message: format!("Index must be a non-negative integer, got {}", n), token: None, }); } Ok(*n as usize) } else { Err(RuntimeError::RuntimeError { message: "Index must be a number".to_string(), token: None, }) } } } // ============================================================================ // 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 = ;` 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_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"); } }