1
0
Fork 0
monkey-interpreter/src/evaluator/mod.rs

217 lines
6.4 KiB
Rust

use {
crate::parser::ast::Node,
std::fmt::{Display, Formatter, Result as FmtResult},
};
pub mod tree_walker;
pub trait Evaluator {
fn eval(&self, node: Node) -> Option<Object>;
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Object {
Integer(i64),
Boolean(bool),
ReturnValue(Box<Object>),
Error(String),
Null,
}
const NULL: Object = Object::Null;
const TRUE: Object = Object::Boolean(true);
const FALSE: Object = Object::Boolean(false);
impl Object {
pub fn inspect(&self) -> String {
match self {
Object::Integer(v) => v.to_string(),
Object::Boolean(v) => v.to_string(),
Object::ReturnValue(ret) => ret.inspect(),
Object::Error(s) => s.to_string(),
Object::Null => "NULL".into(),
}
}
}
impl Display for Object {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
f.write_str(match self {
Object::Integer(_) => "INTEGER",
Object::Boolean(_) => "BOOLEAN",
Object::ReturnValue(_) => "RETURN_VALUE",
Object::Error(_) => "ERROR",
Object::Null => "NULL",
})
}
}
#[cfg(test)]
mod tests {
use crate::{
evaluator::{tree_walker::TreeWalker, Evaluator, Object, FALSE, NULL, TRUE},
lexer::Lexer,
parser::{ast::Node, Parser},
};
fn run_test_cases(test_cases: &[(&str, Option<Object>)]) {
for test in test_cases.iter() {
let lexer = Lexer::new(test.0);
let mut parser = Parser::new(lexer);
let program = parser.parse_program();
assert!(program.is_some());
let program = program.unwrap();
let evaluator = TreeWalker::new();
let eval = evaluator.eval(Node::Program(program));
assert_eq!(eval, test.1);
}
}
#[test]
fn eval_integer_expression() {
let test_cases = [
("5", Some(Object::Integer(5))),
("10", Some(Object::Integer(10))),
];
run_test_cases(&test_cases);
}
#[test]
fn eval_boolean_expression() {
let test_cases = [
("true", Some(TRUE)),
("false", Some(FALSE)),
("3 < 4", Some(TRUE)),
("3 > 4", Some(FALSE)),
("3 == 4", Some(FALSE)),
("3 != 4", Some(TRUE)),
("(1 < 2 ) == true", Some(TRUE)),
("(1 < 2 ) == false", Some(FALSE)),
("(1 > 2 ) == true", Some(FALSE)),
("(1 > 2 ) == false", Some(TRUE)),
];
run_test_cases(&test_cases);
}
#[test]
fn eval_bang_operator() {
let test_cases = [
("!5", Some(FALSE)),
("!!true", Some(TRUE)),
("!!false", Some(FALSE)),
("!!5", Some(TRUE)),
("!true", Some(FALSE)),
("!false", Some(TRUE)),
];
run_test_cases(&test_cases);
}
#[test]
fn eval_integer_literal_expression() {
let test_cases = [
("5", Some(Object::Integer(5))),
("10", Some(Object::Integer(10))),
("-5", Some(Object::Integer(-5))),
("-10", Some(Object::Integer(-10))),
("5 + 5 + 5 + 5 - 10", Some(Object::Integer(10))),
("2 * 2 * 2 * 2 * 2", Some(Object::Integer(32))),
("-50 + 100 + -50", Some(Object::Integer(0))),
("5 * 2 + 10 ", Some(Object::Integer(20))),
("5 + 2 * 10", Some(Object::Integer(25))),
("20 + 2 * -10", Some(Object::Integer(0))),
("50 / 2 * 2 + 10", Some(Object::Integer(60))),
("2 * (5 + 10)", Some(Object::Integer(30))),
("3 * (3 * 3) + 10", Some(Object::Integer(37))),
("(5 + 10 * 2 + 15 / 3) * 2 + -10", Some(Object::Integer(50))),
];
run_test_cases(&test_cases);
}
#[test]
fn eval_if_else_expression() {
let test_cases = [
// (
// "if (x > 10) {
// puts(\"everything okay\");
// } else {
// puts(\"x is too low!\");
// potato();
// }",
// Object::Null,
// ),
("if(true) {10}", Some(Object::Integer(10))),
("if(false) {10}", Some(NULL)),
("if (1) {10}", Some(Object::Integer(10))),
("if(1 < 2) {10}", Some(Object::Integer(10))),
("if (1 > 2) {10}", Some(NULL)),
("if(1 > 2) {10} else {20}", Some(Object::Integer(20))),
("if (1 < 2) {10} else {20}", Some(Object::Integer(10))),
];
run_test_cases(&test_cases);
}
#[test]
fn eval_return_statements() {
let test_cases = [
("return 10; ", Some(Object::Integer(10))),
("return 10; 9;", Some(Object::Integer(10))),
("return 2 * 5; 9;", Some(Object::Integer(10))),
("9; return 2 * 5; 9;", Some(Object::Integer(10))),
(
"if(10 > 1) {
if(10 > 2) {
return 10;
}
return 1;
}",
Some(Object::Integer(10)),
),
];
run_test_cases(&test_cases);
}
#[test]
fn error_handling() {
let test_cases = [
(
"-true",
Some(Object::Error("unknown operator: -BOOLEAN".into())),
),
(
"true + false;",
Some(Object::Error("unknown operator: BOOLEAN + BOOLEAN".into())),
),
(
"5 + true;",
Some(Object::Error("type mismatch: INTEGER + BOOLEAN".into())),
),
(
"5 + true; 5",
Some(Object::Error("type mismatch: INTEGER + BOOLEAN".into())),
),
(
"5; true + false; 5",
Some(Object::Error("unknown operator: BOOLEAN + BOOLEAN".into())),
),
(
"if (10>1){true + false;}",
Some(Object::Error("unknown operator: BOOLEAN + BOOLEAN".into())),
),
(
"if(10 > 1) {
if(10 > 2) {
return true + false;
}
return 1;
}",
Some(Object::Error("unknown operator: BOOLEAN + BOOLEAN".into())),
),
];
run_test_cases(&test_cases);
}
}