Added Tree walker evaluator
1. Added a tree walker interpreter 2. Added tests to support evaluator
This commit is contained in:
parent
b0cbe5d4f7
commit
22479487ca
|
@ -7,4 +7,3 @@ edition = "2018"
|
|||
[dependencies]
|
||||
lazy_static = "1.4.0"
|
||||
itertools = "0.8.2"
|
||||
|
||||
|
|
135
src/evaluator/mod.rs
Normal file
135
src/evaluator/mod.rs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
102
src/evaluator/tree_walker/mod.rs
Normal file
102
src/evaluator/tree_walker/mod.rs
Normal file
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
mod evaluator;
|
||||
mod lexer;
|
||||
mod parser;
|
||||
mod repl;
|
||||
|
|
|
@ -16,6 +16,13 @@ pub struct Program {
|
|||
pub statements: Vec<Statement>,
|
||||
}
|
||||
|
||||
#[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<Expression>,
|
||||
pub operator: TokenType,
|
||||
pub right: Box<Expression>,
|
||||
}
|
||||
|
||||
impl PrefixExpression {
|
||||
|
@ -344,9 +351,9 @@ impl Display for PrefixExpression {
|
|||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct InfixExpression {
|
||||
left: Box<Expression>,
|
||||
operator: TokenType,
|
||||
right: Box<Expression>,
|
||||
pub left: Box<Expression>,
|
||||
pub operator: TokenType,
|
||||
pub right: Box<Expression>,
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
13
src/repl.rs
13
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<R: BufRead, W: Write>(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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user