handle expressions + conditionals + return statemetnts + function calls
This commit is contained in:
+4
-2
@@ -1,3 +1,5 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::token::Token;
|
use crate::token::Token;
|
||||||
|
|
||||||
pub trait NodeTrait {
|
pub trait NodeTrait {
|
||||||
@@ -162,7 +164,7 @@ impl StatementTrait for ExpressionStatement {
|
|||||||
fn statement_node(&self) {}
|
fn statement_node(&self) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct Identifier {
|
pub struct Identifier {
|
||||||
pub token: Token,
|
pub token: Token,
|
||||||
pub value: String,
|
pub value: String,
|
||||||
@@ -329,7 +331,7 @@ impl NodeTrait for BlockStatement {
|
|||||||
pub struct FunctionLiteral {
|
pub struct FunctionLiteral {
|
||||||
pub token: Token,
|
pub token: Token,
|
||||||
pub parameters: Vec<Identifier>,
|
pub parameters: Vec<Identifier>,
|
||||||
pub body: BlockStatement,
|
pub body: Rc<BlockStatement>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NodeTrait for FunctionLiteral {
|
impl NodeTrait for FunctionLiteral {
|
||||||
|
|||||||
+328
-37
@@ -1,75 +1,366 @@
|
|||||||
use crate::ast::Program;
|
use std::cell::RefCell;
|
||||||
use crate::ast::{self};
|
use std::rc::Rc;
|
||||||
use crate::object::{self, Null, Object};
|
|
||||||
|
use crate::ast::{self, NodeTrait};
|
||||||
|
use crate::object::{self, Environment, Null, Object, ObjectType};
|
||||||
|
|
||||||
const NULL: Null = Null;
|
const NULL: Null = Null;
|
||||||
const BOOL_TRUE: object::Boolean = object::Boolean { value: true };
|
const BOOL_TRUE: object::Boolean = object::Boolean { value: true };
|
||||||
const BOOL_FALSE: object::Boolean = object::Boolean { value: false };
|
const BOOL_FALSE: object::Boolean = object::Boolean { value: false };
|
||||||
|
|
||||||
fn eval(node: Box<dyn ast::NodeTrait>) -> Option<Box<dyn object::Object>> {
|
pub fn eval_program(program: ast::Program) -> Option<Box<dyn Object>> {
|
||||||
let node = node.as_ref();
|
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>() {
|
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>() {
|
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>() {
|
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);
|
||||||
}
|
}
|
||||||
if let Some(expr) = node.as_any().downcast_ref::<ast::Expression>() {
|
return Some(Box::new(object::ReturnValue { value }));
|
||||||
return eval_expression(expr);
|
|
||||||
}
|
}
|
||||||
if let Some(expr) = node.as_any().downcast_ref::<ast::PrefixExpression>() {
|
if let Some(block) = node.as_any().downcast_ref::<ast::BlockStatement>() {
|
||||||
let right = &eval(expr.right);
|
return eval_block_statement(block, env);
|
||||||
return eval_prefix_expression(expr.operator, right);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval_program(program: Program) -> Option<Box<dyn object::Object>> {
|
fn eval_block_statement(
|
||||||
let mut res: Option<Box<dyn object::Object>> = None;
|
block: &ast::BlockStatement,
|
||||||
for stmt in program.statements {
|
env: Rc<RefCell<Environment>>,
|
||||||
res = eval(stmt)
|
) -> 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 {
|
match expression {
|
||||||
|
ast::Expression::Identifier(ident) => eval_identifier(ident, env),
|
||||||
ast::Expression::IntegerLiteral(int) => {
|
ast::Expression::IntegerLiteral(int) => {
|
||||||
Some(Box::new(object::Integer { value: int.value }))
|
Some(Box::new(object::Integer { value: int.value }))
|
||||||
}
|
}
|
||||||
ast::Expression::Boolean(b) => Some(Box::new(native_bool_to_bool_object(b.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(¶m.value, arg.clone_box());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rc::new(RefCell::new(env))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_prefix_expression(
|
fn eval_prefix_expression(
|
||||||
operator: String,
|
operator: &str,
|
||||||
right: &Option<Box<dyn object::Object>>,
|
right: Box<dyn Object>,
|
||||||
) -> Option<Box<dyn object::Object>> {
|
) -> Option<Box<dyn Object>> {
|
||||||
match operator.as_str() {
|
match operator {
|
||||||
"!" => eval_bang_operator_expression(right),
|
"!" => 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(
|
fn eval_bang_operator_expression(right: Box<dyn Object>) -> Option<Box<dyn Object>> {
|
||||||
right: &Option<Box<dyn object::Object>>,
|
match right.object_type() {
|
||||||
) -> Option<Box<dyn object::Object>> {
|
ObjectType::BOOLEAN => {
|
||||||
let bool_true = Box::new(BOOL_TRUE);
|
if let Some(b) = right.as_any().downcast_ref::<object::Boolean>() {
|
||||||
let bool_false = Box::new(BOOL_FALSE);
|
Some(Box::new(native_bool_to_bool_object(!b.value)))
|
||||||
let null = Box::new(NULL);
|
} else {
|
||||||
|
Some(Box::new(NULL))
|
||||||
match right..unwrap() {
|
|
||||||
bool_true => Some(bool_false),
|
|
||||||
bool_false => Some(bool_true),
|
|
||||||
null => Some(bool_true),
|
|
||||||
_ => Some(bool_false),
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
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 {
|
fn native_bool_to_bool_object(input: bool) -> object::Boolean {
|
||||||
if input { BOOL_TRUE } else { BOOL_FALSE }
|
if input { BOOL_TRUE } else { BOOL_FALSE }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {
|
pub enum ObjectType {
|
||||||
INTEGER,
|
INTEGER,
|
||||||
BOOLEAN,
|
BOOLEAN,
|
||||||
NULL,
|
NULL,
|
||||||
|
RETURN_VALUE,
|
||||||
|
ERROR,
|
||||||
|
FUNCTION,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Object {
|
pub trait Object {
|
||||||
fn object_type(&self) -> ObjectType;
|
fn object_type(&self) -> ObjectType;
|
||||||
fn inspect(&self) -> String;
|
fn inspect(&self) -> String;
|
||||||
|
fn clone_box(&self) -> Box<dyn Object>;
|
||||||
|
fn as_any(&self) -> &dyn std::any::Any;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Integer {
|
pub struct Integer {
|
||||||
@@ -21,6 +34,14 @@ impl Object for Integer {
|
|||||||
fn inspect(&self) -> String {
|
fn inspect(&self) -> String {
|
||||||
self.value.to_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 {
|
pub struct Boolean {
|
||||||
@@ -35,6 +56,14 @@ impl Object for Boolean {
|
|||||||
fn inspect(&self) -> String {
|
fn inspect(&self) -> String {
|
||||||
self.value.to_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;
|
pub struct Null;
|
||||||
@@ -47,4 +76,129 @@ impl Object for Null {
|
|||||||
fn inspect(&self) -> String {
|
fn inspect(&self) -> String {
|
||||||
"null".to_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
@@ -1,3 +1,5 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{self},
|
ast::{self},
|
||||||
lexer::{self, LexerTraits},
|
lexer::{self, LexerTraits},
|
||||||
@@ -378,7 +380,7 @@ impl Parser {
|
|||||||
if !self.expect_peek(TokenType::Lbrace) {
|
if !self.expect_peek(TokenType::Lbrace) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let body = self.parse_block_statement()?;
|
let body = Rc::new(self.parse_block_statement()?);
|
||||||
Some(ast::Expression::FunctionLiteral(ast::FunctionLiteral {
|
Some(ast::Expression::FunctionLiteral(ast::FunctionLiteral {
|
||||||
token,
|
token,
|
||||||
parameters,
|
parameters,
|
||||||
|
|||||||
Reference in New Issue
Block a user