1
0
Fork 0

Added Return statement evaluator and basic error system

1. Added Return statement parser with tests
2. Added a very basic error reporting system in the evaluator
3. Removed duplicate code
This commit is contained in:
Ishan Jain 2020-01-23 20:53:06 +05:30
parent 62fd586acb
commit 9e04c61310
4 changed files with 197 additions and 168 deletions

View File

@ -1,14 +1,19 @@
use crate::parser::ast::Node; use {
crate::parser::ast::Node,
std::fmt::{Display, Formatter, Result as FmtResult},
};
pub mod tree_walker; pub mod tree_walker;
pub trait Evaluator { pub trait Evaluator {
fn eval(&self, node: Node) -> Option<Object>; fn eval(&self, node: Node) -> Option<Object>;
} }
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub enum Object { pub enum Object {
Integer(i64), Integer(i64),
Boolean(bool), Boolean(bool),
ReturnValue(Box<Object>),
Error(String),
Null, Null,
} }
@ -21,11 +26,25 @@ impl Object {
match self { match self {
Object::Integer(v) => v.to_string(), Object::Integer(v) => v.to_string(),
Object::Boolean(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(), 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)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use crate::{
@ -34,13 +53,7 @@ mod tests {
parser::{ast::Node, Parser}, parser::{ast::Node, Parser},
}; };
#[test] fn run_test_cases(test_cases: &[(&str, Option<Object>)]) {
fn eval_integer_expression() {
let test_cases = [
("5", Some(Object::Integer(5))),
("10", Some(Object::Integer(10))),
];
for test in test_cases.iter() { for test in test_cases.iter() {
let lexer = Lexer::new(test.0); let lexer = Lexer::new(test.0);
let mut parser = Parser::new(lexer); let mut parser = Parser::new(lexer);
@ -53,6 +66,16 @@ mod tests {
} }
} }
#[test]
fn eval_integer_expression() {
let test_cases = [
("5", Some(Object::Integer(5))),
("10", Some(Object::Integer(10))),
];
run_test_cases(&test_cases);
}
#[test] #[test]
fn eval_boolean_expression() { fn eval_boolean_expression() {
let test_cases = [ let test_cases = [
@ -67,17 +90,7 @@ mod tests {
("(1 > 2 ) == true", Some(FALSE)), ("(1 > 2 ) == true", Some(FALSE)),
("(1 > 2 ) == false", Some(TRUE)), ("(1 > 2 ) == false", Some(TRUE)),
]; ];
run_test_cases(&test_cases);
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] #[test]
@ -90,16 +103,7 @@ mod tests {
("!true", Some(FALSE)), ("!true", Some(FALSE)),
("!false", Some(TRUE)), ("!false", Some(TRUE)),
]; ];
for test in test_cases.iter() { run_test_cases(&test_cases);
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] #[test]
@ -121,16 +125,7 @@ mod tests {
("(5 + 10 * 2 + 15 / 3) * 2 + -10", Some(Object::Integer(50))), ("(5 + 10 * 2 + 15 / 3) * 2 + -10", Some(Object::Integer(50))),
]; ];
for test in test_cases.iter() { run_test_cases(&test_cases);
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] #[test]
@ -154,15 +149,68 @@ mod tests {
("if (1 < 2) {10} else {20}", Some(Object::Integer(10))), ("if (1 < 2) {10} else {20}", Some(Object::Integer(10))),
]; ];
for test in test_cases.iter() { run_test_cases(&test_cases);
let lexer = Lexer::new(test.0); }
let mut parser = Parser::new(lexer);
let program = parser.parse_program(); #[test]
assert!(program.is_some()); fn eval_return_statements() {
let program = program.unwrap(); let test_cases = [
let evaluator = TreeWalker::new(); ("return 10; ", Some(Object::Integer(10))),
let eval = evaluator.eval(Node::Program(program)); ("return 10; 9;", Some(Object::Integer(10))),
assert_eq!(eval, test.1); ("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);
} }
} }

View File

@ -1,7 +1,10 @@
// TODO: This is all a mess. Almost certainly because right now, I don't know any better way to do this.
// It's just constantly unwrapping enums from one place and rewrapping it to some other enum(or even the same enum) and returning it
// The error handling story is pretty bad too
use crate::{ use crate::{
evaluator::{Evaluator, Object, FALSE, NULL, TRUE}, evaluator::{Evaluator, Object, FALSE, NULL, TRUE},
lexer::TokenType, lexer::TokenType,
parser::ast::{Expression, ExpressionStatement, Node, Statement}, parser::ast::{BlockStatement, Expression, ExpressionStatement, Node, Program, Statement},
}; };
pub struct TreeWalker; pub struct TreeWalker;
@ -15,12 +18,16 @@ impl TreeWalker {
impl Evaluator for TreeWalker { impl Evaluator for TreeWalker {
fn eval(&self, node: Node) -> Option<Object> { fn eval(&self, node: Node) -> Option<Object> {
match node { match node {
Node::Program(p) => self.eval_statements(p.statements), Node::Program(p) => self.eval_program(p),
Node::Statement(stmt) => match stmt { Node::Statement(stmt) => match stmt {
Statement::ExpressionStatement(ExpressionStatement { expression, .. }) => { Statement::ExpressionStatement(ExpressionStatement { expression, .. }) => {
self.eval(Node::Expression(expression)) self.eval(Node::Expression(expression))
} }
Statement::BlockStatement(bs) => self.eval_statements(bs.statements), Statement::BlockStatement(bs) => self.eval_block_statement(bs),
Statement::Return(ret) => {
let ret_val = self.eval(Node::Expression(ret.value?))?;
Some(Object::ReturnValue(Box::new(ret_val)))
}
_ => None, _ => None,
}, },
Node::Expression(expr) => match expr { Node::Expression(expr) => match expr {
@ -53,10 +60,54 @@ impl Evaluator for TreeWalker {
} }
impl TreeWalker { impl TreeWalker {
fn eval_statements(&self, stmts: Vec<Statement>) -> Option<Object> { fn eval_program(&self, prg: Program) -> Option<Object> {
let mut out: Option<Object> = None; let mut out: Option<Object> = None;
for stmt in stmts { for stmt in prg.statements {
out = self.eval(Node::Statement(stmt)) out = self.eval(Node::Statement(stmt));
// No need to evaluate any more statements from a statements vector once we
// get a return keyword. nothing after in the block matters.
if let Some(out) = out.clone() {
match out {
Object::ReturnValue(v) => return Some(*v),
Object::Error(_) => return Some(out),
_ => {}
}
}
}
out
}
fn eval_block_statement(&self, bs: BlockStatement) -> Option<Object> {
let mut out: Option<Object> = None;
for stmt in bs.statements {
out = self.eval(Node::Statement(stmt));
// TODO: Find a nicer way to do this. :(
// The objective here is,
// If we encounter a node of type ReturnValue, Don't unwrap it. Return it as is
// So, It can evaluated again by the eval function.
// This is helpful when we have a nested structure with multiple return statments.
// something like,
// if (true) {
// if (true) {
// return 10;
// }
// return 1;
// }
// This will return 1 if we unwrap return right here and return the value within.
// But in reality that shouldn't happen. It should be returning 10
// So, We don't unwrap the ReturnValue node when we encounter 10. Just return it as is and it is later eval-ed
// and the correct value is returned
if let Some(out) = out.clone() {
match out {
Object::ReturnValue(v) => {
return Some(Object::ReturnValue(v));
}
Object::Error(_) => return Some(out),
_ => {}
}
}
} }
out out
} }
@ -65,7 +116,10 @@ impl TreeWalker {
match operator { match operator {
TokenType::Bang => Some(self.eval_bang_operator_expression(expr)), TokenType::Bang => Some(self.eval_bang_operator_expression(expr)),
TokenType::Minus => Some(self.eval_minus_prefix_operator_expression(expr)), TokenType::Minus => Some(self.eval_minus_prefix_operator_expression(expr)),
_ => None, _ => Some(Object::Error(format!(
"unknown operator: {}{}",
operator, expr
))),
} }
} }
@ -75,7 +129,7 @@ impl TreeWalker {
operator: TokenType, operator: TokenType,
right: Object, right: Object,
) -> Option<Object> { ) -> Option<Object> {
Some(match (left, right) { Some(match (left.clone(), right.clone()) {
(Object::Integer(l), Object::Integer(r)) => match operator { (Object::Integer(l), Object::Integer(r)) => match operator {
TokenType::Plus => Object::Integer(l + r), TokenType::Plus => Object::Integer(l + r),
TokenType::Minus => Object::Integer(l - r), TokenType::Minus => Object::Integer(l - r),
@ -85,12 +139,16 @@ impl TreeWalker {
TokenType::NotEquals => Object::Boolean(l != r), TokenType::NotEquals => Object::Boolean(l != r),
TokenType::GreaterThan => Object::Boolean(l > r), TokenType::GreaterThan => Object::Boolean(l > r),
TokenType::LessThan => Object::Boolean(l < r), TokenType::LessThan => Object::Boolean(l < r),
_ => NULL, _ => Object::Error(format!("unknown operator: {} {} {}", l, operator, r)),
}, },
(o1 @ _, o2 @ _) if o1.to_string() != o2.to_string() => {
Object::Error(format!("type mismatch: {} {} {}", o1, operator, o2))
}
_ => match operator { _ => match operator {
TokenType::Equals => Object::Boolean(left == right), TokenType::Equals => Object::Boolean(left == right),
TokenType::NotEquals => Object::Boolean(left != right), TokenType::NotEquals => Object::Boolean(left != right),
_ => NULL, _ => Object::Error(format!("unknown operator: {} {} {}", left, operator, right)),
}, },
}) })
} }
@ -107,7 +165,7 @@ impl TreeWalker {
fn eval_minus_prefix_operator_expression(&self, expr: Object) -> Object { fn eval_minus_prefix_operator_expression(&self, expr: Object) -> Object {
match expr { match expr {
Object::Integer(v) => Object::Integer(-v), Object::Integer(v) => Object::Integer(-v),
_ => NULL, v @ _ => Object::Error(format!("unknown operator: -{}", v)),
} }
} }

View File

@ -115,21 +115,19 @@ impl Display for LetStatement {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct ReturnStatement { pub struct ReturnStatement {
return_value: Option<Expression>, pub value: Option<Expression>,
} }
impl ReturnStatement { impl ReturnStatement {
pub fn new(expr: Expression) -> Self { pub fn new(expr: Expression) -> Self {
ReturnStatement { ReturnStatement { value: Some(expr) }
return_value: Some(expr),
}
} }
fn parse(parser: &mut Parser) -> Option<Self> { fn parse(parser: &mut Parser) -> Option<Self> {
let token = parser.lexer.next()?; let token = parser.lexer.next()?;
let expr = Expression::parse(parser, token, ExpressionPriority::Lowest); let expr = Expression::parse(parser, token, ExpressionPriority::Lowest);
parser.expect_peek(TokenType::Semicolon)?; parser.expect_peek(TokenType::Semicolon)?;
Some(ReturnStatement { return_value: expr }) Some(ReturnStatement { value: expr })
} }
} }
@ -137,7 +135,7 @@ impl Display for ReturnStatement {
fn fmt(&self, f: &mut Formatter) -> FmtResult { fn fmt(&self, f: &mut Formatter) -> FmtResult {
let mut out: String = TokenType::Return.to_string(); let mut out: String = TokenType::Return.to_string();
if let Some(v) = &self.return_value { if let Some(v) = &self.value {
out.push(' '); out.push(' ');
let a: String = v.into(); let a: String = v.into();
out.push_str(&a); out.push_str(&a);
@ -660,12 +658,9 @@ mod tests {
))), ))),
}), }),
Statement::Return(ReturnStatement { Statement::Return(ReturnStatement {
return_value: Some(Expression::Identifier(Identifier::new( value: Some(Expression::Identifier(Identifier::new(TokenType::Int, "5"))),
TokenType::Int,
"5",
))),
}), }),
Statement::Return(ReturnStatement { return_value: None }), Statement::Return(ReturnStatement { value: None }),
], ],
}; };
assert_eq!( assert_eq!(

View File

@ -181,6 +181,18 @@ mod tests {
} }
} }
fn check_test_cases(test_cases: &[(&str, Vec<Statement>)]) {
for test in test_cases.iter() {
let lexer = Lexer::new(test.0);
let mut parser = Parser::new(lexer);
let program = parser.parse_program();
check_parser_errors(&parser);
assert_eq!(parser.errors.len(), 0);
assert!(program.is_some());
assert_eq!(program.unwrap().statements, test.1);
}
}
#[test] #[test]
fn let_statements() { fn let_statements() {
let test_cases = [( let test_cases = [(
@ -201,15 +213,7 @@ mod tests {
], ],
)]; )];
for test in test_cases.iter() { check_test_cases(&test_cases);
let lexer = Lexer::new(test.0);
let mut parser = Parser::new(lexer);
let program = parser.parse_program();
check_parser_errors(&parser);
assert_eq!(parser.errors.len(), 0);
assert!(program.is_some());
assert_eq!(program.unwrap().statements, test.1);
}
let fail_case = "let x 5; let 10; let 83838383;"; let fail_case = "let x 5; let 10; let 83838383;";
@ -241,30 +245,12 @@ mod tests {
], ],
)]; )];
for test in test_cases.iter() { check_test_cases(&test_cases);
let lexer = Lexer::new(test.0);
let mut parser = Parser::new(lexer);
let program = parser.parse_program();
check_parser_errors(&parser);
assert_eq!(parser.errors.len(), 0);
assert!(program.is_some());
assert_eq!(program.unwrap().statements, test.1);
}
let lexer = Lexer::new("return 5; return 10; return add(10);");
let mut parser = Parser::new(lexer);
let program = parser.parse_program();
check_parser_errors(&parser);
assert_eq!(parser.errors.len(), 0);
assert!(program.is_some());
let program = program.unwrap();
assert_eq!(program.statements.len(), 3);
assert_eq!(parser.errors.len(), 0);
} }
#[test] #[test]
fn identifier_expression() { fn identifier_expression() {
// TODO: Add more tests for this
let lexer = Lexer::new("foobar;"); let lexer = Lexer::new("foobar;");
let mut parser = Parser::new(lexer); let mut parser = Parser::new(lexer);
let program = parser.parse_program(); let program = parser.parse_program();
@ -303,7 +289,7 @@ mod tests {
#[test] #[test]
fn prefix_expressions() { fn prefix_expressions() {
let prefix_tests = [ let test_cases = [
( (
"!5", "!5",
vec![Statement::ExpressionStatement(ExpressionStatement::new( vec![Statement::ExpressionStatement(ExpressionStatement::new(
@ -372,20 +358,12 @@ mod tests {
), ),
]; ];
for test in prefix_tests.iter() { check_test_cases(&test_cases);
let lexer = Lexer::new(test.0);
let mut parser = Parser::new(lexer);
let program = parser.parse_program();
check_parser_errors(&parser);
assert_eq!(parser.errors.len(), 0);
assert!(program.is_some());
assert_eq!(program.unwrap().statements, test.1);
}
} }
#[test] #[test]
fn parsing_infix_expressions() { fn parsing_infix_expressions() {
let infix_tests = [ let test_cases = [
( (
"5 + 10;", "5 + 10;",
vec![Statement::ExpressionStatement(ExpressionStatement::new( vec![Statement::ExpressionStatement(ExpressionStatement::new(
@ -490,15 +468,7 @@ mod tests {
))], ))],
), ),
]; ];
for test in infix_tests.iter() { check_test_cases(&test_cases);
let lexer = Lexer::new(test.0);
let mut parser = Parser::new(lexer);
let program = parser.parse_program();
check_parser_errors(&parser);
assert_eq!(parser.errors.len(), 0);
assert!(program.is_some());
assert_eq!(program.unwrap().statements, test.1);
}
} }
#[test] #[test]
@ -570,16 +540,7 @@ mod tests {
))], ))],
), ),
]; ];
check_test_cases(&test_cases);
for test in test_cases.iter() {
let lexer = Lexer::new(test.0);
let mut parser = Parser::new(lexer);
let program = parser.parse_program();
check_parser_errors(&parser);
assert_eq!(parser.errors.len(), 0);
assert!(program.is_some());
assert_eq!(program.unwrap().statements, test.1);
}
} }
#[test] #[test]
fn if_expression() { fn if_expression() {
@ -604,15 +565,7 @@ mod tests {
))], ))],
)]; )];
for test in test_cases.iter() { check_test_cases(&test_cases);
let lexer = Lexer::new(test.0);
let mut parser = Parser::new(lexer);
let program = parser.parse_program();
check_parser_errors(&parser);
assert_eq!(parser.errors.len(), 0);
assert!(program.is_some());
assert_eq!(program.unwrap().statements, test.1);
}
} }
#[test] #[test]
fn if_else_expression() { fn if_else_expression() {
@ -641,16 +594,7 @@ mod tests {
)), )),
))], ))],
)]; )];
check_test_cases(&test_cases);
for test in test_cases.iter() {
let lexer = Lexer::new(test.0);
let mut parser = Parser::new(lexer);
let program = parser.parse_program();
check_parser_errors(&parser);
assert_eq!(parser.errors.len(), 0);
assert!(program.is_some());
assert_eq!(program.unwrap().statements, test.1);
}
} }
#[test] #[test]
@ -762,15 +706,7 @@ mod tests {
), ),
]; ];
for test in test_cases.iter() { check_test_cases(&test_cases);
let lexer = Lexer::new(test.0);
let mut parser = Parser::new(lexer);
let program = parser.parse_program();
check_parser_errors(&parser);
assert_eq!(parser.errors.len(), 0);
assert!(program.is_some());
assert_eq!(program.unwrap().statements, test.1);
}
} }
#[test] #[test]
fn call_expression_parsing() { fn call_expression_parsing() {
@ -910,15 +846,7 @@ mod tests {
), ),
]; ];
for test in test_cases.iter() { check_test_cases(&test_cases);
let lexer = Lexer::new(test.0);
let mut parser = Parser::new(lexer);
let program = parser.parse_program();
check_parser_errors(&parser);
assert_eq!(parser.errors.len(), 0);
assert!(program.is_some());
assert_eq!(program.unwrap().statements, test.1);
}
} }
#[test] #[test]
fn call_expression_parsing_string() { fn call_expression_parsing_string() {