diff --git a/Cargo.toml b/Cargo.toml index 629c937..89f2c11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,4 @@ edition = "2018" [dependencies] lazy_static = "1.4.0" -itertools = "0.8.2" - +itertools = "0.8.2" \ No newline at end of file diff --git a/src/evaluator/mod.rs b/src/evaluator/mod.rs new file mode 100644 index 0000000..b1d08fd --- /dev/null +++ b/src/evaluator/mod.rs @@ -0,0 +1,135 @@ +use crate::parser::ast::Node; +pub mod tree_walker; + +pub trait Evaluator { + fn eval(&self, node: Node) -> Option; +} + +#[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); + } + } +} diff --git a/src/evaluator/tree_walker/mod.rs b/src/evaluator/tree_walker/mod.rs new file mode 100644 index 0000000..898b745 --- /dev/null +++ b/src/evaluator/tree_walker/mod.rs @@ -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 { + 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) -> Option { + let mut out: Option = None; + for stmt in stmts { + out = self.eval(Node::Statement(stmt)) + } + out + } + + fn eval_prefix_expression(&self, operator: TokenType, expr: Object) -> Option { + 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 { + 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, + } + } +} diff --git a/src/main.rs b/src/main.rs index dee141f..dfffd20 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ #[macro_use] extern crate lazy_static; +mod evaluator; mod lexer; mod parser; mod repl; diff --git a/src/parser/ast/mod.rs b/src/parser/ast/mod.rs index 28b9f61..9ec420f 100644 --- a/src/parser/ast/mod.rs +++ b/src/parser/ast/mod.rs @@ -16,6 +16,13 @@ pub struct Program { pub statements: Vec, } +#[derive(Debug)] +pub enum Node { + Program(Program), + Statement(Statement), + Expression(Expression), +} + impl Display for Program { fn fmt(&self, f: &mut Formatter) -> FmtResult { let mut out = String::new(); @@ -143,7 +150,7 @@ impl Display for ReturnStatement { #[derive(Debug, PartialEq)] pub struct ExpressionStatement { token: Token, - expression: Expression, + pub expression: Expression, } impl ExpressionStatement { @@ -287,7 +294,7 @@ impl Display for Identifier { #[derive(Debug, PartialEq)] pub struct IntegerLiteral { - value: i64, + pub value: i64, } impl IntegerLiteral { @@ -310,8 +317,8 @@ impl IntegerLiteral { #[derive(Debug, PartialEq)] pub struct PrefixExpression { - operator: TokenType, - right: Box, + pub operator: TokenType, + pub right: Box, } impl PrefixExpression { @@ -344,9 +351,9 @@ impl Display for PrefixExpression { #[derive(Debug, PartialEq)] pub struct InfixExpression { - left: Box, - operator: TokenType, - right: Box, + pub left: Box, + pub operator: TokenType, + pub right: Box, } impl InfixExpression { @@ -381,7 +388,7 @@ impl Display for InfixExpression { #[derive(Debug, PartialEq)] pub struct BooleanExpression { token: TokenType, - value: bool, + pub value: bool, } impl BooleanExpression { diff --git a/src/repl.rs b/src/repl.rs index 9ea2f98..32bb924 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -1,6 +1,7 @@ use crate::{ + evaluator::{tree_walker::TreeWalker, Evaluator}, lexer::Lexer, - parser::{Error as ParserError, Parser}, + parser::{ast::Node, Error as ParserError, Parser}, }; use std::io::{self, BufRead, Result as IoResult, Write}; @@ -29,11 +30,13 @@ fn start(mut ip: R, mut out: W) { print_parser_errors(&mut out, &parser.errors).unwrap(); continue; } - if let Some(program) = program { - for stmt in &program.statements { - out.write_fmt(format_args!("{}\n", stmt)).unwrap(); - } - }; + let program = program.unwrap(); + let evaluator = TreeWalker::new(); + let obj = evaluator.eval(Node::Program(program)); + if let Some(node) = obj { + out.write_fmt(format_args!("{}\n", node.inspect())).unwrap(); + out.flush().unwrap(); + } } }