From e747ba156227a83ddc34b6ae841b12ca8cc9e85f Mon Sep 17 00:00:00 2001 From: YannAhlgrim Date: Sun, 12 Jul 2026 15:24:53 +0200 Subject: [PATCH] handle expressions + conditionals + return statemetnts + function calls --- src/ast/mod.rs | 6 +- src/evaluator/mod.rs | 367 ++++++++++++++++++++++++++++++++++++----- src/evaluator/tests.rs | 225 +++++++++++++++++++++++++ src/object/mod.rs | 154 +++++++++++++++++ src/parser/mod.rs | 4 +- 5 files changed, 715 insertions(+), 41 deletions(-) create mode 100644 src/evaluator/tests.rs diff --git a/src/ast/mod.rs b/src/ast/mod.rs index b52cb42..bd1ba36 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use crate::token::Token; pub trait NodeTrait { @@ -162,7 +164,7 @@ impl StatementTrait for ExpressionStatement { fn statement_node(&self) {} } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct Identifier { pub token: Token, pub value: String, @@ -329,7 +331,7 @@ impl NodeTrait for BlockStatement { pub struct FunctionLiteral { pub token: Token, pub parameters: Vec, - pub body: BlockStatement, + pub body: Rc, } impl NodeTrait for FunctionLiteral { diff --git a/src/evaluator/mod.rs b/src/evaluator/mod.rs index 58953d0..1c8f7ef 100644 --- a/src/evaluator/mod.rs +++ b/src/evaluator/mod.rs @@ -1,75 +1,366 @@ -use crate::ast::Program; -use crate::ast::{self}; -use crate::object::{self, Null, Object}; +use std::cell::RefCell; +use std::rc::Rc; + +use crate::ast::{self, NodeTrait}; +use crate::object::{self, Environment, Null, Object, ObjectType}; const NULL: Null = Null; const BOOL_TRUE: object::Boolean = object::Boolean { value: true }; const BOOL_FALSE: object::Boolean = object::Boolean { value: false }; -fn eval(node: Box) -> Option> { - let node = node.as_ref(); +pub fn eval_program(program: ast::Program) -> Option> { + let env = Rc::new(RefCell::new(Environment::new())); + let mut result: Option> = None; + + for stmt in program.statements { + result = eval(stmt.as_ref(), Rc::clone(&env)); + + if let Some(ref obj) = result { + if obj.object_type() == ObjectType::RETURN_VALUE { + return Some(unwrap_return_value(obj.clone_box())); + } + if obj.object_type() == ObjectType::ERROR { + return result; + } + } + } + + result +} + +fn eval(node: &dyn NodeTrait, env: Rc>) -> Option> { if let Some(stmt) = node.as_any().downcast_ref::() { - return eval_expression(&stmt.expression); + return eval_expression(&stmt.expression, env); } if let Some(stmt) = node.as_any().downcast_ref::() { - return None; + let value = eval_expression(&stmt.value, Rc::clone(&env))?; + if is_error(value.as_ref()) { + return Some(value); + } + env.borrow_mut().set(&stmt.name.value, value); + return Some(Box::new(NULL)); } if let Some(stmt) = node.as_any().downcast_ref::() { - return eval_expression(&stmt.return_value); + let value = eval_expression(&stmt.return_value, Rc::clone(&env))?; + if is_error(value.as_ref()) { + return Some(value); + } + return Some(Box::new(object::ReturnValue { value })); } - if let Some(expr) = node.as_any().downcast_ref::() { - return eval_expression(expr); - } - if let Some(expr) = node.as_any().downcast_ref::() { - let right = &eval(expr.right); - return eval_prefix_expression(expr.operator, right); + if let Some(block) = node.as_any().downcast_ref::() { + return eval_block_statement(block, env); } + None } -pub fn eval_program(program: Program) -> Option> { - let mut res: Option> = None; - for stmt in program.statements { - res = eval(stmt) +fn eval_block_statement( + block: &ast::BlockStatement, + env: Rc>, +) -> Option> { + let mut result: Option> = None; + + for stmt in &block.statements { + result = eval(stmt.as_ref(), Rc::clone(&env)); + + if let Some(ref obj) = result { + let obj_type = obj.object_type(); + if obj_type == ObjectType::RETURN_VALUE || obj_type == ObjectType::ERROR { + return result; + } + } } - res + + result } -fn eval_expression(expression: &ast::Expression) -> Option> { +fn eval_expression( + expression: &ast::Expression, + env: Rc>, +) -> Option> { match expression { + ast::Expression::Identifier(ident) => eval_identifier(ident, env), ast::Expression::IntegerLiteral(int) => { Some(Box::new(object::Integer { value: int.value })) } ast::Expression::Boolean(b) => Some(Box::new(native_bool_to_bool_object(b.value))), - _ => None, + ast::Expression::PrefixExpression(expr) => { + let right = eval_expression(expr.right.as_ref(), Rc::clone(&env))?; + if is_error(right.as_ref()) { + return Some(right); + } + eval_prefix_expression(&expr.operator, right) + } + ast::Expression::InfixExpression(expr) => { + let left = eval_expression(expr.left.as_ref(), Rc::clone(&env))?; + if is_error(left.as_ref()) { + return Some(left); + } + let right = eval_expression(expr.right.as_ref(), Rc::clone(&env))?; + if is_error(right.as_ref()) { + return Some(right); + } + eval_infix_expression(&expr.operator, left, right) + } + ast::Expression::IfExpression(expr) => eval_if_expression(expr, env), + ast::Expression::FunctionLiteral(func) => Some(Box::new(object::Function { + parameters: func.parameters.clone(), + body: Rc::clone(&func.body), + env: Rc::clone(&env), + })), + ast::Expression::CallExpression(call) => { + let function = eval_expression(call.function.as_ref(), Rc::clone(&env))?; + if is_error(function.as_ref()) { + return Some(function); + } + let args = eval_expressions(&call.arguments, Rc::clone(&env))?; + if args.len() == 1 && is_error(args[0].as_ref()) { + return Some(args.into_iter().next().unwrap()); + } + apply_function(function, args) + } } } +fn eval_identifier( + ident: &ast::Identifier, + env: Rc>, +) -> Option> { + if let Some(value) = env.borrow().get(&ident.value) { + return Some(value); + } + Some(new_error(format!("identifier not found: {}", ident.value))) +} + +fn eval_expressions( + expressions: &[ast::Expression], + env: Rc>, +) -> Option>> { + let mut result = Vec::new(); + + for expr in expressions { + let evaluated = eval_expression(expr, Rc::clone(&env))?; + if is_error(evaluated.as_ref()) { + return Some(vec![evaluated]); + } + result.push(evaluated); + } + + Some(result) +} + +fn apply_function( + function: Box, + args: Vec>, +) -> Option> { + if let Some(func) = function.as_any().downcast_ref::() { + let extended_env = extend_function_env(func, args); + let evaluated = eval_block_statement(&func.body, Rc::clone(&extended_env)); + + if let Some(obj) = evaluated { + if obj.object_type() == ObjectType::RETURN_VALUE { + return Some(unwrap_return_value(obj)); + } + return Some(obj); + } + return Some(Box::new(NULL)); + } + + Some(new_error(format!( + "not a function: {:?}", + function.object_type() + ))) +} + +fn extend_function_env( + func: &object::Function, + args: Vec>, +) -> Rc> { + let mut env = Environment::from_outer(Rc::clone(&func.env)); + + for (i, param) in func.parameters.iter().enumerate() { + if let Some(arg) = args.get(i) { + env.set(¶m.value, arg.clone_box()); + } + } + + Rc::new(RefCell::new(env)) +} + fn eval_prefix_expression( - operator: String, - right: &Option>, -) -> Option> { - match operator.as_str() { + operator: &str, + right: Box, +) -> Option> { + match operator { "!" => eval_bang_operator_expression(right), - _ => Some(Box::new(NULL)), + "-" => eval_minus_prefix_operator_expression(right), + _ => Some(new_error(format!("unknown operator: {}{:?}", operator, right.object_type()))), } } -fn eval_bang_operator_expression( - right: &Option>, -) -> Option> { - let bool_true = Box::new(BOOL_TRUE); - let bool_false = Box::new(BOOL_FALSE); - let null = Box::new(NULL); - - match right..unwrap() { - bool_true => Some(bool_false), - bool_false => Some(bool_true), - null => Some(bool_true), - _ => Some(bool_false), +fn eval_bang_operator_expression(right: Box) -> Option> { + match right.object_type() { + ObjectType::BOOLEAN => { + if let Some(b) = right.as_any().downcast_ref::() { + Some(Box::new(native_bool_to_bool_object(!b.value))) + } else { + Some(Box::new(NULL)) + } + } + ObjectType::NULL => Some(Box::new(BOOL_TRUE)), + _ => Some(Box::new(BOOL_FALSE)), } } +fn eval_minus_prefix_operator_expression(right: Box) -> Option> { + if let Some(int) = right.as_any().downcast_ref::() { + return Some(Box::new(object::Integer { value: -int.value })); + } + Some(new_error(format!( + "unknown operator: -{:?}", + right.object_type() + ))) +} + +fn eval_infix_expression( + operator: &str, + left: Box, + right: Box, +) -> Option> { + if left.object_type() == ObjectType::INTEGER && right.object_type() == ObjectType::INTEGER { + return eval_integer_infix_expression(operator, left, right); + } + + if left.object_type() == ObjectType::BOOLEAN && right.object_type() == ObjectType::BOOLEAN { + return eval_boolean_infix_expression(operator, left, right); + } + + if left.object_type() != right.object_type() { + return Some(new_error(format!( + "type mismatch: {:?} {} {:?}", + left.object_type(), + operator, + right.object_type() + ))); + } + + Some(new_error(format!( + "unknown operator: {:?} {} {:?}", + left.object_type(), + operator, + right.object_type() + ))) +} + +fn eval_integer_infix_expression( + operator: &str, + left: Box, + right: Box, +) -> Option> { + let left_val = left.as_any().downcast_ref::().unwrap().value; + let right_val = right + .as_any() + .downcast_ref::() + .unwrap() + .value; + + match operator { + "+" => Some(Box::new(object::Integer { + value: left_val + right_val, + })), + "-" => Some(Box::new(object::Integer { + value: left_val - right_val, + })), + "*" => Some(Box::new(object::Integer { + value: left_val * right_val, + })), + "/" => Some(Box::new(object::Integer { + value: left_val / right_val, + })), + "<" => Some(Box::new(native_bool_to_bool_object(left_val < right_val))), + ">" => Some(Box::new(native_bool_to_bool_object(left_val > right_val))), + "==" => Some(Box::new(native_bool_to_bool_object(left_val == right_val))), + "!=" => Some(Box::new(native_bool_to_bool_object(left_val != right_val))), + _ => Some(new_error(format!( + "unknown operator: {:?} {} {:?}", + left.object_type(), + operator, + right.object_type() + ))), + } +} + +fn eval_boolean_infix_expression( + operator: &str, + left: Box, + right: Box, +) -> Option> { + let left_val = left.as_any().downcast_ref::().unwrap().value; + let right_val = right + .as_any() + .downcast_ref::() + .unwrap() + .value; + + match operator { + "==" => Some(Box::new(native_bool_to_bool_object(left_val == right_val))), + "!=" => Some(Box::new(native_bool_to_bool_object(left_val != right_val))), + _ => Some(new_error(format!( + "unknown operator: {:?} {} {:?}", + left.object_type(), + operator, + right.object_type() + ))), + } +} + +fn eval_if_expression( + expr: &ast::IfExpression, + env: Rc>, +) -> Option> { + let condition = eval_expression(expr.condition.as_ref(), Rc::clone(&env))?; + if is_error(condition.as_ref()) { + return Some(condition); + } + + if is_truthy(condition.as_ref()) { + eval_block_statement(&expr.consequence, env) + } else if let Some(ref alternative) = expr.alternative { + eval_block_statement(alternative, env) + } else { + Some(Box::new(NULL)) + } +} + +fn is_truthy(obj: &dyn Object) -> bool { + match obj.object_type() { + ObjectType::NULL => false, + ObjectType::BOOLEAN => { + let b = obj.as_any().downcast_ref::().unwrap(); + b.value + } + _ => true, + } +} + +fn unwrap_return_value(obj: Box) -> Box { + if let Some(ret) = obj.as_any().downcast_ref::() { + return ret.value.clone_box(); + } + obj +} + +fn is_error(obj: &dyn Object) -> bool { + obj.object_type() == ObjectType::ERROR +} + +fn new_error(message: String) -> Box { + Box::new(object::Error { message }) +} + fn native_bool_to_bool_object(input: bool) -> object::Boolean { if input { BOOL_TRUE } else { BOOL_FALSE } } + +#[cfg(test)] +mod tests; diff --git a/src/evaluator/tests.rs b/src/evaluator/tests.rs new file mode 100644 index 0000000..221ff30 --- /dev/null +++ b/src/evaluator/tests.rs @@ -0,0 +1,225 @@ +use super::*; +use crate::lexer; +use crate::parser::new_parser; + +fn test_eval(input: &str) -> Option> { + let lexer = lexer::new(input.to_string()); + let mut parser = new_parser(lexer); + let program = parser.parse_program(); + eval_program(program) +} + +fn test_integer_object(obj: &dyn Object, expected: i64) { + assert_eq!(obj.object_type(), ObjectType::INTEGER); + let int = obj.as_any().downcast_ref::().unwrap(); + assert_eq!(int.value, expected); +} + +fn test_boolean_object(obj: &dyn Object, expected: bool) { + assert_eq!(obj.object_type(), ObjectType::BOOLEAN); + let b = obj.as_any().downcast_ref::().unwrap(); + assert_eq!(b.value, expected); +} + +fn test_null_object(obj: &dyn Object) { + assert_eq!(obj.object_type(), ObjectType::NULL); +} + +#[test] +fn test_eval_integer_expression() { + let tests = vec![("5", 5), ("10", 10), ("-5", -5), ("-10", -10)]; + + for (input, expected) in tests { + let evaluated = test_eval(input).unwrap(); + test_integer_object(evaluated.as_ref(), expected); + } +} + +#[test] +fn test_eval_boolean_expression() { + let tests = vec![ + ("true", true), + ("false", false), + ("1 < 2", true), + ("1 > 2", false), + ("1 < 1", false), + ("1 > 1", false), + ("1 == 1", true), + ("1 != 1", false), + ("1 == 2", false), + ("1 != 2", true), + ("true == true", true), + ("false == false", true), + ("true == false", false), + ("true != false", true), + ("false != true", true), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input).unwrap(); + test_boolean_object(evaluated.as_ref(), expected); + } +} + +#[test] +fn test_bang_operator() { + let tests = vec![ + ("!true", false), + ("!false", true), + ("!5", false), + ("!!true", true), + ("!!false", false), + ("!!5", true), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input).unwrap(); + test_boolean_object(evaluated.as_ref(), expected); + } +} + +#[test] +fn test_eval_integer_arithmetic() { + let tests = vec![ + ("5 + 5", 10), + ("5 - 5", 0), + ("5 * 5", 25), + ("5 / 5", 1), + ("5 + 5 + 5 + 5 - 10", 10), + ("2 * 2 * 2 * 2 * 2", 32), + ("-50 + 100 + -50", 0), + ("5 * 2 + 10", 20), + ("5 + 2 * 10", 25), + ("20 + 2 * -10", 0), + ("50 / 2 * 2 + 10", 60), + ("2 * (5 + 10)", 30), + ("3 * 3 * 3 + 10", 37), + ("3 * (3 * 3) + 10", 37), + ("(5 + 10 * 2 + 15 / 3) * 2 + -10", 50), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input).unwrap(); + test_integer_object(evaluated.as_ref(), expected); + } +} + +#[test] +fn test_if_else_expressions() { + let tests = vec![ + ("if (true) { 10 }", Some(10)), + ("if (false) { 10 }", None), + ("if (1) { 10 }", Some(10)), + ("if (1 < 2) { 10 }", Some(10)), + ("if (1 > 2) { 10 }", None), + ("if (1 > 2) { 10 } else { 20 }", Some(20)), + ("if (1 < 2) { 10 } else { 20 }", Some(10)), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input).unwrap(); + match expected { + Some(value) => test_integer_object(evaluated.as_ref(), value), + None => test_null_object(evaluated.as_ref()), + } + } +} + +#[test] +fn test_return_statements() { + let tests = vec![ + ("return 10;", 10), + ("return 10; 9;", 10), + ("return 2 * 5; 9;", 10), + ("9; return 2 * 5; 9;", 10), + ( + "if (10 > 1) { if (10 > 1) { return 10; } return 1; }", + 10, + ), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input).unwrap(); + test_integer_object(evaluated.as_ref(), expected); + } +} + +#[test] +fn test_let_statements() { + let tests = vec![ + ("let a = 5; a;", 5), + ("let a = 5 * 5; a;", 25), + ("let a = 5; let b = a; b;", 5), + ("let a = 5; let b = a; let c = a + b + 5; c;", 15), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input).unwrap(); + test_integer_object(evaluated.as_ref(), expected); + } +} + +#[test] +fn test_function_object() { + let input = "fn(x) { x + 2; };"; + let evaluated = test_eval(input).unwrap(); + + assert_eq!(evaluated.object_type(), ObjectType::FUNCTION); + let func = evaluated.as_any().downcast_ref::().unwrap(); + assert_eq!(func.parameters.len(), 1); + assert_eq!(func.parameters[0].value, "x"); + assert_eq!(func.body.string(), "(x + 2)"); +} + +#[test] +fn test_function_application() { + let tests = vec![ + ("let identity = fn(x) { x; }; identity(5);", 5), + ("let identity = fn(x) { return x; }; identity(5);", 5), + ("let double = fn(x) { x * 2; }; double(5);", 10), + ("let add = fn(x, y) { x + y; }; add(5, 5);", 10), + ("let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20), + ("fn(x) { x; }(5)", 5), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input).unwrap(); + test_integer_object(evaluated.as_ref(), expected); + } +} + +#[test] +fn test_enclosing_environments() { + let input = " + let first = 10; + let second = 10; + let third = 10; + let ourFunction = fn(first) { + let second = 20; + first + second + third; + }; + ourFunction(20) + first + second; + "; + let evaluated = test_eval(input).unwrap(); + test_integer_object(evaluated.as_ref(), 70); +} + +#[test] +fn test_error_handling() { + let tests = vec![ + ("5 + true;", "type mismatch: INTEGER + BOOLEAN"), + ("5 + true; 5;", "type mismatch: INTEGER + BOOLEAN"), + ("-true", "unknown operator: -BOOLEAN"), + ("true + false;", "unknown operator: BOOLEAN + BOOLEAN"), + ("5; true + false; 5", "unknown operator: BOOLEAN + BOOLEAN"), + ("if (10 > 1) { true + false; }", "unknown operator: BOOLEAN + BOOLEAN"), + ("foobar", "identifier not found: foobar"), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input).unwrap(); + assert_eq!(evaluated.object_type(), ObjectType::ERROR); + let err = evaluated.as_any().downcast_ref::().unwrap(); + assert!(err.message.contains(expected), "expected '{}' to contain '{}'", err.message, expected); + } +} diff --git a/src/object/mod.rs b/src/object/mod.rs index cec9bd5..d25e7e4 100644 --- a/src/object/mod.rs +++ b/src/object/mod.rs @@ -1,12 +1,25 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +use crate::ast::{self, NodeTrait}; + +#[derive(Debug, PartialEq, Eq)] +#[allow(non_camel_case_types)] pub enum ObjectType { INTEGER, BOOLEAN, NULL, + RETURN_VALUE, + ERROR, + FUNCTION, } pub trait Object { fn object_type(&self) -> ObjectType; fn inspect(&self) -> String; + fn clone_box(&self) -> Box; + fn as_any(&self) -> &dyn std::any::Any; } pub struct Integer { @@ -21,6 +34,14 @@ impl Object for Integer { fn inspect(&self) -> String { self.value.to_string() } + + fn clone_box(&self) -> Box { + Box::new(Integer { value: self.value }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } } pub struct Boolean { @@ -35,6 +56,14 @@ impl Object for Boolean { fn inspect(&self) -> String { self.value.to_string() } + + fn clone_box(&self) -> Box { + Box::new(Boolean { value: self.value }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } } pub struct Null; @@ -47,4 +76,129 @@ impl Object for Null { fn inspect(&self) -> String { "null".to_string() } + + fn clone_box(&self) -> Box { + Box::new(Null) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +pub struct ReturnValue { + pub value: Box, +} + +impl Object for ReturnValue { + fn object_type(&self) -> ObjectType { + ObjectType::RETURN_VALUE + } + + fn inspect(&self) -> String { + self.value.inspect() + } + + fn clone_box(&self) -> Box { + Box::new(ReturnValue { + value: self.value.clone_box(), + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +pub struct Error { + pub message: String, +} + +impl Object for Error { + fn object_type(&self) -> ObjectType { + ObjectType::ERROR + } + + fn inspect(&self) -> String { + format!("ERROR: {}", self.message) + } + + fn clone_box(&self) -> Box { + Box::new(Error { + message: self.message.clone(), + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +pub struct Function { + pub parameters: Vec, + pub body: Rc, + pub env: Rc>, +} + +impl Object for Function { + fn object_type(&self) -> ObjectType { + ObjectType::FUNCTION + } + + fn inspect(&self) -> String { + let params: Vec = self.parameters.iter().map(|p| p.string()).collect(); + format!( + "fn({}) {{\n{}\n}}", + params.join(", "), + self.body.string() + ) + } + + fn clone_box(&self) -> Box { + Box::new(Function { + parameters: self.parameters.clone(), + body: Rc::clone(&self.body), + env: Rc::clone(&self.env), + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +#[derive(Default)] +pub struct Environment { + store: HashMap>, + outer: Option>>, +} + +impl Environment { + pub fn new() -> Self { + Self { + store: HashMap::new(), + outer: None, + } + } + + pub fn from_outer(outer: Rc>) -> Self { + Self { + store: HashMap::new(), + outer: Some(outer), + } + } + + pub fn get(&self, name: &str) -> Option> { + if let Some(value) = self.store.get(name) { + return Some(value.clone_box()); + } + if let Some(ref outer) = self.outer { + return outer.borrow().get(name); + } + None + } + + pub fn set(&mut self, name: &str, value: Box) { + self.store.insert(name.to_string(), value); + } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 749f06a..a2f7dca 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use crate::{ ast::{self}, lexer::{self, LexerTraits}, @@ -378,7 +380,7 @@ impl Parser { if !self.expect_peek(TokenType::Lbrace) { return None; } - let body = self.parse_block_statement()?; + let body = Rc::new(self.parse_block_statement()?); Some(ast::Expression::FunctionLiteral(ast::FunctionLiteral { token, parameters,