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]
|
[dependencies]
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
itertools = "0.8.2"
|
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]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
|
||||||
|
mod evaluator;
|
||||||
mod lexer;
|
mod lexer;
|
||||||
mod parser;
|
mod parser;
|
||||||
mod repl;
|
mod repl;
|
||||||
|
|
|
@ -16,6 +16,13 @@ pub struct Program {
|
||||||
pub statements: Vec<Statement>,
|
pub statements: Vec<Statement>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Node {
|
||||||
|
Program(Program),
|
||||||
|
Statement(Statement),
|
||||||
|
Expression(Expression),
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for Program {
|
impl Display for Program {
|
||||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
|
@ -143,7 +150,7 @@ impl Display for ReturnStatement {
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct ExpressionStatement {
|
pub struct ExpressionStatement {
|
||||||
token: Token,
|
token: Token,
|
||||||
expression: Expression,
|
pub expression: Expression,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExpressionStatement {
|
impl ExpressionStatement {
|
||||||
|
@ -287,7 +294,7 @@ impl Display for Identifier {
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct IntegerLiteral {
|
pub struct IntegerLiteral {
|
||||||
value: i64,
|
pub value: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntegerLiteral {
|
impl IntegerLiteral {
|
||||||
|
@ -310,8 +317,8 @@ impl IntegerLiteral {
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct PrefixExpression {
|
pub struct PrefixExpression {
|
||||||
operator: TokenType,
|
pub operator: TokenType,
|
||||||
right: Box<Expression>,
|
pub right: Box<Expression>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrefixExpression {
|
impl PrefixExpression {
|
||||||
|
@ -344,9 +351,9 @@ impl Display for PrefixExpression {
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct InfixExpression {
|
pub struct InfixExpression {
|
||||||
left: Box<Expression>,
|
pub left: Box<Expression>,
|
||||||
operator: TokenType,
|
pub operator: TokenType,
|
||||||
right: Box<Expression>,
|
pub right: Box<Expression>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InfixExpression {
|
impl InfixExpression {
|
||||||
|
@ -381,7 +388,7 @@ impl Display for InfixExpression {
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct BooleanExpression {
|
pub struct BooleanExpression {
|
||||||
token: TokenType,
|
token: TokenType,
|
||||||
value: bool,
|
pub value: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BooleanExpression {
|
impl BooleanExpression {
|
||||||
|
|
15
src/repl.rs
15
src/repl.rs
|
@ -1,6 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
evaluator::{tree_walker::TreeWalker, Evaluator},
|
||||||
lexer::Lexer,
|
lexer::Lexer,
|
||||||
parser::{Error as ParserError, Parser},
|
parser::{ast::Node, Error as ParserError, Parser},
|
||||||
};
|
};
|
||||||
use std::io::{self, BufRead, Result as IoResult, Write};
|
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();
|
print_parser_errors(&mut out, &parser.errors).unwrap();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(program) = program {
|
let program = program.unwrap();
|
||||||
for stmt in &program.statements {
|
let evaluator = TreeWalker::new();
|
||||||
out.write_fmt(format_args!("{}\n", stmt)).unwrap();
|
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