handle expressions + conditionals + return statemetnts + function calls

This commit is contained in:
YannAhlgrim
2026-07-12 15:24:53 +02:00
parent 5869d8acc2
commit e747ba1562
5 changed files with 715 additions and 41 deletions
+4 -2
View File
@@ -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<Identifier>,
pub body: BlockStatement,
pub body: Rc<BlockStatement>,
}
impl NodeTrait for FunctionLiteral {
+329 -38
View File
@@ -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<dyn ast::NodeTrait>) -> Option<Box<dyn object::Object>> {
let node = node.as_ref();
pub fn eval_program(program: ast::Program) -> Option<Box<dyn Object>> {
let env = Rc::new(RefCell::new(Environment::new()));
let mut result: Option<Box<dyn Object>> = 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<RefCell<Environment>>) -> Option<Box<dyn Object>> {
if let Some(stmt) = node.as_any().downcast_ref::<ast::ExpressionStatement>() {
return eval_expression(&stmt.expression);
return eval_expression(&stmt.expression, env);
}
if let Some(stmt) = node.as_any().downcast_ref::<ast::LetStatement>() {
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::<ast::ReturnStatement>() {
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::<ast::Expression>() {
return eval_expression(expr);
}
if let Some(expr) = node.as_any().downcast_ref::<ast::PrefixExpression>() {
let right = &eval(expr.right);
return eval_prefix_expression(expr.operator, right);
if let Some(block) = node.as_any().downcast_ref::<ast::BlockStatement>() {
return eval_block_statement(block, env);
}
None
}
pub fn eval_program(program: Program) -> Option<Box<dyn object::Object>> {
let mut res: Option<Box<dyn object::Object>> = None;
for stmt in program.statements {
res = eval(stmt)
fn eval_block_statement(
block: &ast::BlockStatement,
env: Rc<RefCell<Environment>>,
) -> Option<Box<dyn Object>> {
let mut result: Option<Box<dyn Object>> = 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<Box<dyn object::Object>> {
fn eval_expression(
expression: &ast::Expression,
env: Rc<RefCell<Environment>>,
) -> Option<Box<dyn Object>> {
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<RefCell<Environment>>,
) -> Option<Box<dyn Object>> {
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<RefCell<Environment>>,
) -> Option<Vec<Box<dyn Object>>> {
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<dyn Object>,
args: Vec<Box<dyn Object>>,
) -> Option<Box<dyn Object>> {
if let Some(func) = function.as_any().downcast_ref::<object::Function>() {
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<Box<dyn Object>>,
) -> Rc<RefCell<Environment>> {
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(&param.value, arg.clone_box());
}
}
Rc::new(RefCell::new(env))
}
fn eval_prefix_expression(
operator: String,
right: &Option<Box<dyn object::Object>>,
) -> Option<Box<dyn object::Object>> {
match operator.as_str() {
operator: &str,
right: Box<dyn Object>,
) -> Option<Box<dyn Object>> {
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<Box<dyn object::Object>>,
) -> Option<Box<dyn object::Object>> {
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<dyn Object>) -> Option<Box<dyn Object>> {
match right.object_type() {
ObjectType::BOOLEAN => {
if let Some(b) = right.as_any().downcast_ref::<object::Boolean>() {
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<dyn Object>) -> Option<Box<dyn Object>> {
if let Some(int) = right.as_any().downcast_ref::<object::Integer>() {
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<dyn Object>,
right: Box<dyn Object>,
) -> Option<Box<dyn Object>> {
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<dyn Object>,
right: Box<dyn Object>,
) -> Option<Box<dyn Object>> {
let left_val = left.as_any().downcast_ref::<object::Integer>().unwrap().value;
let right_val = right
.as_any()
.downcast_ref::<object::Integer>()
.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<dyn Object>,
right: Box<dyn Object>,
) -> Option<Box<dyn Object>> {
let left_val = left.as_any().downcast_ref::<object::Boolean>().unwrap().value;
let right_val = right
.as_any()
.downcast_ref::<object::Boolean>()
.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<RefCell<Environment>>,
) -> Option<Box<dyn Object>> {
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::<object::Boolean>().unwrap();
b.value
}
_ => true,
}
}
fn unwrap_return_value(obj: Box<dyn Object>) -> Box<dyn Object> {
if let Some(ret) = obj.as_any().downcast_ref::<object::ReturnValue>() {
return ret.value.clone_box();
}
obj
}
fn is_error(obj: &dyn Object) -> bool {
obj.object_type() == ObjectType::ERROR
}
fn new_error(message: String) -> Box<dyn Object> {
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;
+225
View File
@@ -0,0 +1,225 @@
use super::*;
use crate::lexer;
use crate::parser::new_parser;
fn test_eval(input: &str) -> Option<Box<dyn Object>> {
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::<object::Integer>().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::<object::Boolean>().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::<object::Function>().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::<object::Error>().unwrap();
assert!(err.message.contains(expected), "expected '{}' to contain '{}'", err.message, expected);
}
}
+154
View File
@@ -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<dyn Object>;
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<dyn Object> {
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<dyn Object> {
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<dyn Object> {
Box::new(Null)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
pub struct ReturnValue {
pub value: Box<dyn Object>,
}
impl Object for ReturnValue {
fn object_type(&self) -> ObjectType {
ObjectType::RETURN_VALUE
}
fn inspect(&self) -> String {
self.value.inspect()
}
fn clone_box(&self) -> Box<dyn Object> {
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<dyn Object> {
Box::new(Error {
message: self.message.clone(),
})
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
pub struct Function {
pub parameters: Vec<ast::Identifier>,
pub body: Rc<ast::BlockStatement>,
pub env: Rc<RefCell<Environment>>,
}
impl Object for Function {
fn object_type(&self) -> ObjectType {
ObjectType::FUNCTION
}
fn inspect(&self) -> String {
let params: Vec<String> = self.parameters.iter().map(|p| p.string()).collect();
format!(
"fn({}) {{\n{}\n}}",
params.join(", "),
self.body.string()
)
}
fn clone_box(&self) -> Box<dyn Object> {
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<String, Box<dyn Object>>,
outer: Option<Rc<RefCell<Environment>>>,
}
impl Environment {
pub fn new() -> Self {
Self {
store: HashMap::new(),
outer: None,
}
}
pub fn from_outer(outer: Rc<RefCell<Environment>>) -> Self {
Self {
store: HashMap::new(),
outer: Some(outer),
}
}
pub fn get(&self, name: &str) -> Option<Box<dyn Object>> {
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<dyn Object>) {
self.store.insert(name.to_string(), value);
}
}
+3 -1
View File
@@ -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,