1. Added a tree walker interpreter 2. Added tests to support evaluatormaster
parent
b0cbe5d4f7
commit
22479487ca
@ -0,0 +1,135 @@
|
||||
use crate::parser::ast::Node;
|
||||
pub mod tree_walker;
|
||||
|
||||
pub trait Evaluator {
|
||||
fn eval(&self, node: Node) -> Option<Object>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Object {
|
||||
Integer(i64),
|
||||
Boolean(bool),
|
||||
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::Null => "NULL".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
evaluator::{tree_walker::TreeWalker, Evaluator, Object},
|
||||
lexer::Lexer,
|
||||
parser::{ast::Node, Parser},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn eval_integer_expression() {
|
||||
let test_cases = [
|
||||
("5", Some(Object::Integer(5))),
|
||||
("10", Some(Object::Integer(10))),
|
||||
];
|
||||
|
||||
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_boolean_expression() {
|
||||
let test_cases = [
|
||||
("true", Some(Object::Boolean(true))),
|
||||
("false", Some(Object::Boolean(false))),
|
||||
("3 < 4", Some(Object::Boolean(true))),
|
||||
("3 > 4", Some(Object::Boolean(false))),
|
||||
("3 == 4", Some(Object::Boolean(false))),
|
||||
("3 != 4", Some(Object::Boolean(true))),
|
||||
("(1 < 2 ) == true", Some(Object::Boolean(true))),
|
||||
("(1 < 2 ) == false", Some(Object::Boolean(false))),
|
||||
("(1 > 2 ) == true", Some(Object::Boolean(false))),
|
||||
("(1 > 2 ) == false", Some(Object::Boolean(true))),
|
||||
];
|
||||
|
||||
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_bang_operator() {
|
||||
let test_cases = [
|
||||
("!5", Some(Object::Boolean(false))),
|
||||
("!!true", Some(Object::Boolean(true))),
|
||||
("!!false", Some(Object::Boolean(false))),
|
||||
("!!5", Some(Object::Boolean(true))),
|
||||
("!true", Some(Object::Boolean(false))),
|
||||
("!false", Some(Object::Boolean(true))),
|
||||
];
|
||||
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_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))),
|
||||
];
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
use crate::{
|
||||
evaluator::{Evaluator, Object, FALSE, NULL, TRUE},
|
||||
lexer::TokenType,
|
||||
parser::ast::{Expression, ExpressionStatement, Node, Statement},
|
||||
};
|
||||
|
||||
pub struct TreeWalker;
|
||||
|
||||
impl TreeWalker {
|
||||
pub fn new() -> impl Evaluator {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Evaluator for TreeWalker {
|
||||
fn eval(&self, node: Node) -> Option<Object> {
|
||||
match node {
|
||||
Node::Program(p) => self.eval_statements(p.statements),
|
||||
Node::Statement(stmt) => match stmt {
|
||||
Statement::ExpressionStatement(ExpressionStatement { expression, .. }) => {
|
||||
self.eval(Node::Expression(expression))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
Node::Expression(expr) => match expr {
|
||||
Expression::IntegerLiteral(il) => Some(Object::Integer(il.value)),
|
||||
Expression::BooleanExpression(b) => Some(Object::Boolean(b.value)),
|
||||
Expression::PrefixExpression(p) => {
|
||||
let expr = self.eval(Node::Expression(*p.right))?;
|
||||
self.eval_prefix_expression(p.operator, expr)
|
||||
}
|
||||
Expression::InfixExpression(ie) => {
|
||||
let left = self.eval(Node::Expression(*ie.left))?;
|
||||
let right = self.eval(Node::Expression(*ie.right))?;
|
||||
|
||||
self.eval_infix_expression(left, ie.operator, right)
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TreeWalker {
|
||||
fn eval_statements(&self, stmts: Vec<Statement>) -> Option<Object> {
|
||||
let mut out: Option<Object> = None;
|
||||
for stmt in stmts {
|
||||
out = self.eval(Node::Statement(stmt))
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn eval_prefix_expression(&self, operator: TokenType, expr: Object) -> Option<Object> {
|
||||
match operator {
|
||||
TokenType::Bang => Some(self.eval_bang_operator_expression(expr)),
|
||||
TokenType::Minus => Some(self.eval_minus_prefix_operator_expression(expr)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_infix_expression(
|
||||
&self,
|
||||
left: Object,
|
||||
operator: TokenType,
|
||||
right: Object,
|
||||
) -> Option<Object> {
|
||||
Some(match (left, right) {
|
||||
(Object::Integer(l), Object::Integer(r)) => match operator {
|
||||
TokenType::Plus => Object::Integer(l + r),
|
||||
TokenType::Minus => Object::Integer(l - r),
|
||||
TokenType::Asterisk => Object::Integer(l * r),
|
||||
TokenType::Slash => Object::Integer(l / r),
|
||||
TokenType::Equals => Object::Boolean(l == r),
|
||||
TokenType::NotEquals => Object::Boolean(l != r),
|
||||
TokenType::GreaterThan => Object::Boolean(l > r),
|
||||
TokenType::LessThan => Object::Boolean(l < r),
|
||||
_ => NULL,
|
||||
},
|
||||
_ => match operator {
|
||||
TokenType::Equals => Object::Boolean(left == right),
|
||||
TokenType::NotEquals => Object::Boolean(left != right),
|
||||
_ => NULL,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn eval_bang_operator_expression(&self, expr: Object) -> Object {
|
||||
match expr {
|
||||
TRUE => FALSE,
|
||||
FALSE => TRUE,
|
||||
NULL => TRUE,
|
||||
_ => FALSE,
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_minus_prefix_operator_expression(&self, expr: Object) -> Object {
|
||||
match expr {
|
||||
Object::Integer(v) => Object::Integer(-v),
|
||||
_ => NULL,
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue