From e680c7f52b509a23bfbc174618a3d5faca85a373 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Sat, 12 Aug 2023 17:34:39 +0200 Subject: [PATCH 1/6] feat: Add lexing for while loops --- src/lexer/mod.rs | 15 +++++++++++++++ src/lexer/token.rs | 2 ++ 2 files changed, 17 insertions(+) diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index fff119e..9501116 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -110,6 +110,7 @@ impl Lexer { "if" => Token::If, "else" => Token::Else, "return" => Token::Return, + "while" => Token::While, _ => Token::Ident(ident_string), }; } @@ -220,6 +221,10 @@ mod tests { {"foo": "bar"} true && false || true && false; 12 <= 12 && 12 >= 12; + + while (true) { + return false; + } "#; let mut lexer = Lexer::new(input); @@ -336,6 +341,16 @@ mod tests { Token::Int(String::from("12")), Token::Semicolon, // + Token::While, + Token::LParen, + Token::True, + Token::RParen, + Token::LSquirly, + Token::Return, + Token::False, + Token::Semicolon, + Token::RSquirly, + // Token::Eof, ]; diff --git a/src/lexer/token.rs b/src/lexer/token.rs index 95a7641..37aac15 100644 --- a/src/lexer/token.rs +++ b/src/lexer/token.rs @@ -48,6 +48,7 @@ pub enum Token { If, Else, Return, + While, } impl Display for Token { @@ -86,6 +87,7 @@ impl Display for Token { Token::If => write!(f, "if"), Token::Else => write!(f, "else"), Token::Return => write!(f, "return"), + Token::While => write!(f, "while"), } } } From 411805ee49af6e7f8aeb260362376f27c3174d8d Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Sat, 12 Aug 2023 17:35:24 +0200 Subject: [PATCH 2/6] feat: Add parsing for while loops --- Cargo.lock | 2 +- src/parser/ast.rs | 16 +- src/parser/mod.rs | 758 ++---------------------------------- src/parser/parser_errors.rs | 50 +++ src/parser/parser_tests.rs | 727 ++++++++++++++++++++++++++++++++++ 5 files changed, 820 insertions(+), 733 deletions(-) create mode 100644 src/parser/parser_errors.rs create mode 100644 src/parser/parser_tests.rs diff --git a/Cargo.lock b/Cargo.lock index 70e55ba..efc8e9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,7 +125,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chimpanzee" -version = "0.0.0" +version = "0.1.0" dependencies = [ "byteorder", "chrono", diff --git a/src/parser/ast.rs b/src/parser/ast.rs index 3faa19b..cedd85c 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -297,7 +297,7 @@ impl Display for BlockStatement { } impl BlockStatement { - fn parse(parser: &mut Parser) -> Self { + pub(crate) fn parse(parser: &mut Parser) -> Self { parser.next_token(); let mut statements: Vec = Vec::new(); while !parser.current_token_is(&Token::RSquirly) && !parser.current_token_is(&Token::Eof) { @@ -406,6 +406,7 @@ pub enum Statement { Let(LetStatement), Return(ReturnStatement), Expression(Expression), + While(WhileStatement), } impl Display for Statement { @@ -414,6 +415,7 @@ impl Display for Statement { Statement::Let(statement) => write!(f, "{statement}"), Statement::Return(statement) => write!(f, "{statement}"), Statement::Expression(expression) => write!(f, "{expression}"), + Statement::While(statement) => write!(f, "{statement}"), } } } @@ -477,6 +479,18 @@ impl Display for ReturnStatement { } } +#[derive(PartialEq, Debug, Clone)] +pub struct WhileStatement { + pub condition: Expression, + pub body: BlockStatement, +} + +impl Display for WhileStatement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "while {} {{\n{}}}", self.condition, self.body) + } +} + #[derive(PartialEq, Debug, Clone)] pub struct ArrayLiteral { pub elements: Vec, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index d1b92a1..488ef0f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,4 +1,6 @@ pub mod ast; +pub mod parser_errors; +mod parser_tests; use crate::{ lexer::{token::Token, Lexer}, @@ -6,56 +8,11 @@ use crate::{ Expression, Identifier, LetStatement, Precedence, Program, ReturnStatement, Statement, }, }; -use std::{ - error::Error, - fmt::{Display, Formatter}, -}; - -#[allow(clippy::module_name_repetitions)] -#[derive(Debug)] -pub struct ParserErrors { - pub errors: Vec, -} - -impl Error for ParserErrors {} - -impl Default for ParserErrors { - fn default() -> Self { - Self::new() - } -} - -impl Display for ParserErrors { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - writeln!(f, "Parser errors:")?; - for err in &self.errors { - writeln!(f, "\t{err}")?; - } - Ok(()) - } -} - -impl ParserErrors { - pub fn new() -> ParserErrors { - ParserErrors { errors: vec![] } - } - pub fn add_error(&mut self, err: String) { - self.errors.push(err); - } - - pub fn add_errors(&mut self, mut errors: Vec) { - self.errors.append(&mut errors); - } - - pub fn is_empty(&self) -> bool { - self.errors.is_empty() - } - - pub fn len(&self) -> usize { - self.errors.len() - } -} +use self::{ + ast::{BlockStatement, WhileStatement}, + parser_errors::ParserErrors, +}; pub struct Parser { lexer: Lexer, @@ -104,6 +61,7 @@ impl Parser { match self.current_token { Token::Let => self.parse_let_statement().map(Statement::Let), Token::Return => self.parse_return_statement().map(Statement::Return), + Token::While => self.parse_while_statement().map(Statement::While), _ => self.parse_expression_statement().map(Statement::Expression), } } @@ -164,6 +122,26 @@ impl Parser { Some(ReturnStatement { return_value }) } + fn parse_while_statement(&mut self) -> Option { + self.next_token(); + + let condition = match Expression::parse(self, Precedence::Lowest) { + Ok(x) => x, + Err(s) => { + self.push_error(s); + return None; + } + }; + + if !self.expect_peek(&Token::LSquirly) { + return None; + } + + let body = BlockStatement::parse(self); + + Some(WhileStatement { condition, body }) + } + fn parse_expression_statement(&mut self) -> Option { let expression = Expression::parse(self, Precedence::Lowest); if self.peek_token_is(&Token::Semicolon) { @@ -232,685 +210,3 @@ pub fn parse(input: &str) -> Program { let mut parser = Parser::new(lexer); parser.parse_program() } - -#[cfg(test)] -mod tests { - - use crate::{ - lexer::token::Token, - parser::ast::{ - BlockStatement, Expression, Identifier, LetStatement, Primitive, ReturnStatement, - Statement, - }, - }; - - use super::*; - - #[test] - fn test_let_statements() { - let input = r#"let x = 5; - let y = true; - let foobar = y; - "#; - - let program = generate_program(input); - let expected_statemets = vec![ - Statement::Let(LetStatement { - name: Identifier { - token: Token::Ident("x".to_string()), - value: "x".to_string(), - }, - value: Expression::Primitive(Primitive::IntegerLiteral(5)), - }), - Statement::Let(LetStatement { - name: Identifier { - token: Token::Ident("y".to_string()), - value: "y".to_string(), - }, - value: Expression::Primitive(Primitive::BooleanLiteral(true)), - }), - Statement::Let(LetStatement { - name: Identifier { - token: Token::Ident("foobar".to_string()), - value: "foobar".to_string(), - }, - value: Expression::Identifier(Identifier { - token: Token::Ident("y".to_string()), - value: "y".to_string(), - }), - }), - ]; - - assert_eq!(program.statements.len(), expected_statemets.len()); - - for (i, expected) in expected_statemets.iter().enumerate() { - println!("{} | {} | {} ", i, expected, program.statements[i]); - assert_eq!(program.statements[i], *expected); - } - } - - #[test] - fn test_return_statements() { - let input = r#" - return 5; - return true; - return y; - "#; - - let program = generate_program(input); - let expected = vec![ - Statement::Return(ReturnStatement { - return_value: Expression::Primitive(Primitive::IntegerLiteral(5)), - }), - Statement::Return(ReturnStatement { - return_value: Expression::Primitive(Primitive::BooleanLiteral(true)), - }), - Statement::Return(ReturnStatement { - return_value: Expression::Identifier(Identifier { - token: Token::Ident("y".to_string()), - value: "y".to_string(), - }), - }), - ]; - - assert_eq!(program.statements.len(), 3); - - for (i, expected) in expected.iter().enumerate() { - assert_eq!(program.statements[i], *expected); - } - } - - fn check_parse_errors(parser: &Parser) { - let len = parser.errors.len(); - - if len > 0 { - println!("Parser has {} errors", parser.errors.len()); - println!("Parser errors: {:?}", parser.errors); - } - assert_eq!(len, 0); - } - - #[test] - fn test_errors() { - let input = r#" - let x 5; - let = 10; - let 838383; - let x = 838383; - "#; - - let lexer = Lexer::new(input); - let mut parser = Parser::new(lexer); - - parser.parse_program(); - - assert_ne!(parser.errors.len(), 0); - } - - #[test] - fn test_identifier_expression() { - let input = "foobar;"; - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - - let statement = &program.statements[0]; - assert_eq!( - statement, - &Statement::Expression(Expression::Identifier(Identifier { - token: Token::Ident("foobar".to_string()), - value: "foobar".to_string(), - })) - ); - } - - #[test] - fn test_integer_literal_expression() { - let input = "5;"; - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - - let statement = &program.statements[0]; - assert_eq!( - statement, - &Statement::Expression(Expression::Primitive(Primitive::IntegerLiteral(5))) - ); - } - - #[test] - fn test_parsing_prefix_expressions() { - let tests = vec![ - ("!5", "!", "5"), - ("-15", "-", "15"), - ("!true;", "!", "true"), - ("!false;", "!", "false"), - ]; - - for (input, operator, value) in tests { - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => check_prefix_expression(exp, operator, value), - _ => panic!("It is not an expression statement"), - }; - } - } - - #[test] - fn test_parsing_infix_expressions() { - let tests = vec![ - ("5 + 5;", "5", "+", "5"), - ("5 - 5;", "5", "-", "5"), - ("5 * 5;", "5", "*", "5"), - ("5 / 5;", "5", "/", "5"), - ("5 > 5;", "5", ">", "5"), - ("5 >= 5;", "5", ">=", "5"), - ("5 < 5;", "5", "<", "5"), - ("5 <= 5;", "5", "<=", "5"), - ("5 == 5;", "5", "==", "5"), - ("5 != 5;", "5", "!=", "5"), - ("true == true", "true", "==", "true"), - ("true != false", "true", "!=", "false"), - ("false == false", "false", "==", "false"), - ("false && true", "false", "&&", "true"), - ("true || false", "true", "||", "false"), - ]; - - for (input, left, operator, right) in tests { - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => check_infix_expression(exp, left, operator, right), - _ => panic!("It is not an expression statement"), - } - } - } - - #[test] - fn test_operator_precedence_parsing() { - let test = vec![ - ("-a * b", "((-a) * b)"), - ("!-a", "(!(-a))"), - ("a + b + c", "((a + b) + c)"), - ("a + b - c", "((a + b) - c)"), - ("a * b * c", "((a * b) * c)"), - ("a * b / c", "((a * b) / c)"), - ("a + b / c", "(a + (b / c))"), - ("a + b * c + d / e - f", "(((a + (b * c)) + (d / e)) - f)"), - ("3 + 4; -5 * 5", "(3 + 4)\n((-5) * 5)"), - ("5 > 4 == 3 < 4", "((5 > 4) == (3 < 4))"), - ("5 < 4 != 3 > 4", "((5 < 4) != (3 > 4))"), - ( - "3 + 4 * 5 == 3 * 1 + 4 * 5", - "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))", - ), - ("true", "true"), - ("false", "false"), - ("3 > 5 == false", "((3 > 5) == false)"), - ("3 < 5 == true", "((3 < 5) == true)"), - ("1 + (2 + 3) + 4", "((1 + (2 + 3)) + 4)"), - ("(5 + 5) * 2", "((5 + 5) * 2)"), - ("2 / (5 + 5)", "(2 / (5 + 5))"), - ("-(5 + 5)", "(-(5 + 5))"), - ("!(true == true)", "(!(true == true))"), - ("a + add(b * c) + d", "((a + add((b * c))) + d)"), - ( - "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", - "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))", - ), - ( - "add(a + b + c * d / f + g)", - "add((((a + b) + ((c * d) / f)) + g))", - ), - ( - "a * [1, 2, 3, 4][b * c] * d", - "((a * ([1, 2, 3, 4][(b * c)])) * d)", - ), - ( - "add(a * b[2], b[1], 2 * [1, 2][1])", - "add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))", - ), - ]; - - for (input, expected) in test { - let program = generate_program(input); - print!("{program}"); - assert_ne!(program.statements.len(), 0); - assert_eq!(program.to_string(), format!("{expected}\n")); - } - } - - #[test] - fn test_boolean_expression() { - let tests = vec![("true;", true), ("false;", false)]; - - for (input, expected) in tests { - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => check_primitive_literal(exp, &expected.to_string()), - _ => panic!("It is not an expression statement"), - } - } - } - - #[test] - fn test_if_statement() { - let (input, condition, consequence, alternative) = ("if (x < y) { x }", "x < y", "x", None); - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => { - check_conditional_expression(exp, condition, consequence, alternative); - } - _ => panic!("It is not an expression statement"), - } - } - - #[test] - fn test_if_else_statement() { - let (input, condition, consequence, alternative) = - ("if (x < y) { x } else {y}", "x < y", "x", Some("y")); - let lexer = Lexer::new(input); - let mut parser = Parser::new(lexer); - let program = parser.parse_program(); - - check_parse_errors(&parser); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => { - check_conditional_expression(exp, condition, consequence, alternative); - } - _ => panic!("It is not an expression statement"), - } - } - - #[test] - fn test_function_literal_parsing() { - let input = "fn(x, y) { x + y; }"; - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => check_function_literal(exp, vec!["x", "y"], "(x + y)"), - _ => panic!("It is not an expression statement"), - } - } - - #[test] - fn test_parse_funtion_arguments() { - let tests = vec![ - ("fn() {}", Vec::new()), - ("fn(x) {}", vec!["x"]), - ("fn(x,y,z) {}", vec!["x", "y", "z"]), - ]; - - for (input, expected) in tests { - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => check_function_literal(exp, expected, ""), - _ => panic!("It is not an expression statement"), - } - } - } - - #[test] - fn test_function_call_parsing() { - let (input, name, argumnets) = ( - "add(1, 2 * 3, 4 + 5);", - "add", - vec!["1", "(2 * 3)", "(4 + 5)"], - ); - - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => check_function_call(exp, name, argumnets), - _ => panic!("It is not an expression statement"), - } - } - - #[test] - fn test_function_call_parameter_parsing() { - let tests = vec![ - ("add();", "add", vec![]), - ("add(1);", "add", vec!["1"]), - ( - "add(1, 2 * 3, 4 + 5);", - "add", - vec!["1", "(2 * 3)", "(4 + 5)"], - ), - ]; - - for (input, name, argumnets) in tests { - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => check_function_call(exp, name, argumnets), - _ => panic!("It is not an expression statement"), - } - } - } - - #[test] - fn test_string_literal_expression() { - let input = "\"hello world\";"; - - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => check_primitive_literal(exp, "hello world"), - _ => panic!("It is not an expression statement"), - } - } - - #[test] - fn test_array_literal() { - let input = "[1,2*2,3+3]"; - - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - let expressions = match &program.statements[0] { - Statement::Expression(exp) => match exp { - Expression::ArrayLiteral(a) => &a.elements, - _ => panic!("It is not an array literal"), - }, - _ => panic!("It is not an expression statement"), - }; - - assert_eq!(expressions.len(), 3); - check_primitive_literal(&expressions[0], "1"); - check_infix_expression(&expressions[1], "2", "*", "2"); - check_infix_expression(&expressions[2], "3", "+", "3"); - } - - #[test] - fn test_parsing_index_expression_complete() { - let input = "myArray[1+1]"; - - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => match exp { - Expression::IndexExpression(i) => { - assert_eq!(i.left.to_string(), "myArray"); - check_infix_expression(&i.index, "1", "+", "1"); - } - _ => panic!("It is not an index expression"), - }, - _ => panic!("It is not an expression statement"), - } - } - - #[test] - fn test_parsing_index_expression_string_conversion() { - let tests = vec![ - ("myArray[1]", "myArray", "1"), - ("myArray[\"hello\"]", "myArray", "\"hello\""), - ("[1,2,3,4][2]", "[1, 2, 3, 4]", "2"), - ("test()[call()]", "test()", "call()"), - ]; - - for (input, left, index) in tests { - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => check_index_expression(exp, left, index), - - _ => panic!("It is not an expression statement"), - } - } - } - - #[test] - fn test_parsing_hash_map_literal_string_keys() { - let input = "{\"one\": 1, \"two\": 2, \"three\": 3}"; - - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => match exp { - Expression::HashMapLiteral(h) => { - assert_eq!(h.pairs.len(), 3); - let expected = vec![("one", "1"), ("two", "2"), ("three", "3")]; - for (i, (key, value)) in expected.iter().enumerate() { - let pair = h.pairs.get(i).unwrap(); - check_primitive_literal(&pair.0, key); - check_primitive_literal(&pair.1, value); - } - } - _ => panic!("It is not an hash literal"), - }, - _ => panic!("It is not an expression statement"), - } - } - - #[test] - fn test_parsing_empty_hash_map() { - let input = "{}"; - - let program = generate_program(input); - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => match exp { - Expression::HashMapLiteral(h) => { - assert_eq!(h.pairs.len(), 0); - } - _ => panic!("It is not an hash literal"), - }, - _ => panic!("It is not an expression statement"), - } - } - - #[test] - fn test_parsing_hash_map_literal_integer_values() { - let input = "{\"one\": 1 + 34, \"two\": 2/5, \"three\": 3-1}"; - - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => match exp { - Expression::HashMapLiteral(h) => { - assert_eq!(h.pairs.len(), 3); - let expected = vec![ - ("\"one\"", "(1 + 34)"), - ("\"two\"", "(2 / 5)"), - ("\"three\"", "(3 - 1)"), - ]; - for (i, (key, value)) in expected.iter().enumerate() { - let pair = h.pairs.get(i).unwrap(); - assert_eq!(pair.0.to_string(), **key); - assert_eq!(pair.1.to_string(), **value); - } - } - _ => panic!("It is not an hash literal"), - }, - _ => panic!("It is not an expression statement"), - } - } - - #[test] - fn test_parsing_hash_map_literal_mixed_keys() { - let input = "{1:true, 2: \"Hi\", \"three\": 3-1}"; - - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => match exp { - Expression::HashMapLiteral(h) => { - assert_eq!(h.pairs.len(), 3); - let expected = vec![("1", "true"), ("2", "\"Hi\""), ("\"three\"", "(3 - 1)")]; - for (i, (key, value)) in expected.iter().enumerate() { - let pair = h.pairs.get(i).unwrap(); - assert_eq!(pair.0.to_string(), **key); - assert_eq!(pair.1.to_string(), **value); - } - } - _ => panic!("It is not an hash literal"), - }, - _ => panic!("It is not an expression statement"), - } - } - - #[test] - fn test_parsing_function_literal_with_name() { - let input = "let myFunction = fn(){};"; - - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match program.statements[0].clone() { - Statement::Let(l) => match l.value { - Expression::FunctionLiteral(f) => { - assert_eq!(f.name, Some("myFunction".to_string())); - } - _ => panic!("It is not a function literal"), - }, - _ => panic!("It is not a let statement"), - } - } - - #[test] - fn test_parsing_function_literal_without_name() { - let input = "fn(){};"; - - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match program.statements[0].clone() { - Statement::Expression(exp) => match exp { - Expression::FunctionLiteral(f) => { - assert!(f.name.is_none()); - } - _ => panic!("It is not a function literal"), - }, - _ => panic!("It is not an expression"), - } - } - - fn generate_program(input: &str) -> Program { - let lexer = Lexer::new(input); - let mut parser = Parser::new(lexer); - let program = parser.parse_program(); - - check_parse_errors(&parser); - program - } - - fn check_identifier(exp: &Identifier, value: &str) { - assert_eq!(exp.value, value); - } - - fn check_prefix_expression(exp: &Expression, operator: &str, right: &str) { - match exp { - Expression::Prefix(p) => { - assert_eq!(p.token.to_string(), operator); - assert_eq!(p.right.to_string(), right); - } - _ => panic!("It is not an prefix operator"), - } - } - - fn check_primitive_literal(exp: &Expression, value: &str) { - match exp { - Expression::Primitive(p) => match p { - Primitive::IntegerLiteral(i) => assert_eq!(i.to_string(), value), - Primitive::BooleanLiteral(b) => assert_eq!(b.to_string(), value), - Primitive::StringLiteral(s) => assert_eq!(s, value), - }, - _ => panic!("It is not a literal"), - } - } - - fn check_infix_expression(exp: &Expression, left: &str, operator: &str, right: &str) { - match exp { - Expression::Infix(p) => { - check_primitive_literal(p.left.as_ref(), left); - assert_eq!(operator, p.token.to_string()); - check_primitive_literal(p.right.as_ref(), right); - } - _ => panic!("It is not an infix expression"), - } - } - - fn check_conditional_expression( - exp: &Expression, - condition: &str, - consequence: &str, - alternative: Option<&str>, - ) { - match exp { - Expression::Conditional(p) => { - assert_eq!(format!("({condition})"), p.condition.as_ref().to_string()); - check_block_statement(&p.consequence, consequence); - match alternative { - Some(a) => check_block_statement(p.alternative.as_ref().unwrap(), a), - None => assert!(p.alternative.is_none()), - } - } - _ => panic!("It is not a conditional expression"), - } - } - - fn check_block_statement(statement: &BlockStatement, expected: &str) { - if expected.is_empty() { - assert_eq!(statement.to_string(), ""); // Empty block statement does not contain a - // newline - } else { - assert_eq!(statement.to_string(), format!("{expected}\n")); - } - } - - fn check_function_literal(exp: &Expression, params: Vec<&str>, body: &str) { - match exp { - Expression::FunctionLiteral(p) => { - assert_eq!(p.parameters.len(), params.len()); - for (i, param) in params.iter().enumerate() { - check_identifier(&p.parameters[i], param); - } - check_block_statement(&p.body, body); - } - _ => panic!("It is not a function literal"), - } - } - - fn check_function_call(exp: &Expression, name: &str, arguments: Vec<&str>) { - match exp { - Expression::FunctionCall(p) => { - assert_eq!(p.function.to_string(), name); - assert_eq!(p.arguments.len(), arguments.len()); - for (i, arg) in arguments.iter().enumerate() { - assert_eq!(p.arguments[i].to_string(), arg.to_owned().to_string()); - } - } - _ => panic!("It is not a function call"), - } - } - - fn check_index_expression(exp: &Expression, left: &str, index: &str) { - match exp { - Expression::IndexExpression(p) => { - assert_eq!(p.left.to_string(), left); - assert_eq!(p.index.to_string(), index); - } - _ => panic!("It is not an index expression"), - } - } -} diff --git a/src/parser/parser_errors.rs b/src/parser/parser_errors.rs new file mode 100644 index 0000000..53741ce --- /dev/null +++ b/src/parser/parser_errors.rs @@ -0,0 +1,50 @@ +use std::{ + error::Error, + fmt::{Display, Formatter}, +}; + +#[allow(clippy::module_name_repetitions)] +#[derive(Debug)] +pub struct ParserErrors { + pub errors: Vec, +} + +impl Error for ParserErrors {} + +impl Default for ParserErrors { + fn default() -> Self { + Self::new() + } +} + +impl Display for ParserErrors { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + writeln!(f, "Parser errors:")?; + for err in &self.errors { + writeln!(f, "\t{err}")?; + } + Ok(()) + } +} + +impl ParserErrors { + pub fn new() -> ParserErrors { + ParserErrors { errors: vec![] } + } + + pub fn add_error(&mut self, err: String) { + self.errors.push(err); + } + + pub fn add_errors(&mut self, mut errors: Vec) { + self.errors.append(&mut errors); + } + + pub fn is_empty(&self) -> bool { + self.errors.is_empty() + } + + pub fn len(&self) -> usize { + self.errors.len() + } +} diff --git a/src/parser/parser_tests.rs b/src/parser/parser_tests.rs new file mode 100644 index 0000000..145d17e --- /dev/null +++ b/src/parser/parser_tests.rs @@ -0,0 +1,727 @@ +#[cfg(test)] +mod tests { + + use crate::{ + lexer::{token::Token, Lexer}, + parser::{ + ast::{ + BlockStatement, Expression, Identifier, InfixOperator, LetStatement, Primitive, + Program, ReturnStatement, Statement, WhileStatement, + }, + Parser, + }, + }; + + #[test] + fn test_let_statements() { + let input = r#"let x = 5; + let y = true; + let foobar = y; + "#; + + let program = generate_program(input); + let expected_statemets = vec![ + Statement::Let(LetStatement { + name: Identifier { + token: Token::Ident("x".to_string()), + value: "x".to_string(), + }, + value: Expression::Primitive(Primitive::IntegerLiteral(5)), + }), + Statement::Let(LetStatement { + name: Identifier { + token: Token::Ident("y".to_string()), + value: "y".to_string(), + }, + value: Expression::Primitive(Primitive::BooleanLiteral(true)), + }), + Statement::Let(LetStatement { + name: Identifier { + token: Token::Ident("foobar".to_string()), + value: "foobar".to_string(), + }, + value: Expression::Identifier(Identifier { + token: Token::Ident("y".to_string()), + value: "y".to_string(), + }), + }), + ]; + + assert_eq!(program.statements.len(), expected_statemets.len()); + + for (i, expected) in expected_statemets.iter().enumerate() { + println!("{} | {} | {} ", i, expected, program.statements[i]); + assert_eq!(program.statements[i], *expected); + } + } + + #[test] + fn test_return_statements() { + let input = r#" + return 5; + return true; + return y; + "#; + + let program = generate_program(input); + let expected = vec![ + Statement::Return(ReturnStatement { + return_value: Expression::Primitive(Primitive::IntegerLiteral(5)), + }), + Statement::Return(ReturnStatement { + return_value: Expression::Primitive(Primitive::BooleanLiteral(true)), + }), + Statement::Return(ReturnStatement { + return_value: Expression::Identifier(Identifier { + token: Token::Ident("y".to_string()), + value: "y".to_string(), + }), + }), + ]; + + assert_eq!(program.statements.len(), 3); + + for (i, expected) in expected.iter().enumerate() { + assert_eq!(program.statements[i], *expected); + } + } + + fn check_parse_errors(parser: &Parser) { + let len = parser.errors.len(); + + if len > 0 { + println!("Parser has {} errors", parser.errors.len()); + println!("Parser errors: {:?}", parser.errors); + } + assert_eq!(len, 0); + } + + #[test] + fn test_errors() { + let input = r#" + let x 5; + let = 10; + let 838383; + let x = 838383; + "#; + + let lexer = Lexer::new(input); + let mut parser = Parser::new(lexer); + + parser.parse_program(); + + assert_ne!(parser.errors.len(), 0); + } + + #[test] + fn test_identifier_expression() { + let input = "foobar;"; + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + + let statement = &program.statements[0]; + assert_eq!( + statement, + &Statement::Expression(Expression::Identifier(Identifier { + token: Token::Ident("foobar".to_string()), + value: "foobar".to_string(), + })) + ); + } + + #[test] + fn test_integer_literal_expression() { + let input = "5;"; + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + + let statement = &program.statements[0]; + assert_eq!( + statement, + &Statement::Expression(Expression::Primitive(Primitive::IntegerLiteral(5))) + ); + } + + #[test] + fn test_parsing_prefix_expressions() { + let tests = vec![ + ("!5", "!", "5"), + ("-15", "-", "15"), + ("!true;", "!", "true"), + ("!false;", "!", "false"), + ]; + + for (input, operator, value) in tests { + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => check_prefix_expression(exp, operator, value), + _ => panic!("It is not an expression statement"), + }; + } + } + + #[test] + fn test_parsing_infix_expressions() { + let tests = vec![ + ("5 + 5;", "5", "+", "5"), + ("5 - 5;", "5", "-", "5"), + ("5 * 5;", "5", "*", "5"), + ("5 / 5;", "5", "/", "5"), + ("5 > 5;", "5", ">", "5"), + ("5 >= 5;", "5", ">=", "5"), + ("5 < 5;", "5", "<", "5"), + ("5 <= 5;", "5", "<=", "5"), + ("5 == 5;", "5", "==", "5"), + ("5 != 5;", "5", "!=", "5"), + ("true == true", "true", "==", "true"), + ("true != false", "true", "!=", "false"), + ("false == false", "false", "==", "false"), + ("false && true", "false", "&&", "true"), + ("true || false", "true", "||", "false"), + ]; + + for (input, left, operator, right) in tests { + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => check_infix_expression(exp, left, operator, right), + _ => panic!("It is not an expression statement"), + } + } + } + + #[test] + fn test_operator_precedence_parsing() { + let test = vec![ + ("-a * b", "((-a) * b)"), + ("!-a", "(!(-a))"), + ("a + b + c", "((a + b) + c)"), + ("a + b - c", "((a + b) - c)"), + ("a * b * c", "((a * b) * c)"), + ("a * b / c", "((a * b) / c)"), + ("a + b / c", "(a + (b / c))"), + ("a + b * c + d / e - f", "(((a + (b * c)) + (d / e)) - f)"), + ("3 + 4; -5 * 5", "(3 + 4)\n((-5) * 5)"), + ("5 > 4 == 3 < 4", "((5 > 4) == (3 < 4))"), + ("5 < 4 != 3 > 4", "((5 < 4) != (3 > 4))"), + ( + "3 + 4 * 5 == 3 * 1 + 4 * 5", + "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))", + ), + ("true", "true"), + ("false", "false"), + ("3 > 5 == false", "((3 > 5) == false)"), + ("3 < 5 == true", "((3 < 5) == true)"), + ("1 + (2 + 3) + 4", "((1 + (2 + 3)) + 4)"), + ("(5 + 5) * 2", "((5 + 5) * 2)"), + ("2 / (5 + 5)", "(2 / (5 + 5))"), + ("-(5 + 5)", "(-(5 + 5))"), + ("!(true == true)", "(!(true == true))"), + ("a + add(b * c) + d", "((a + add((b * c))) + d)"), + ( + "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", + "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))", + ), + ( + "add(a + b + c * d / f + g)", + "add((((a + b) + ((c * d) / f)) + g))", + ), + ( + "a * [1, 2, 3, 4][b * c] * d", + "((a * ([1, 2, 3, 4][(b * c)])) * d)", + ), + ( + "add(a * b[2], b[1], 2 * [1, 2][1])", + "add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))", + ), + ]; + + for (input, expected) in test { + let program = generate_program(input); + print!("{program}"); + assert_ne!(program.statements.len(), 0); + assert_eq!(program.to_string(), format!("{expected}\n")); + } + } + + #[test] + fn test_boolean_expression() { + let tests = vec![("true;", true), ("false;", false)]; + + for (input, expected) in tests { + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => check_primitive_literal(exp, &expected.to_string()), + _ => panic!("It is not an expression statement"), + } + } + } + + #[test] + fn test_if_statement() { + let (input, condition, consequence, alternative) = ("if (x < y) { x }", "x < y", "x", None); + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => { + check_conditional_expression(exp, condition, consequence, alternative); + } + _ => panic!("It is not an expression statement"), + } + } + + #[test] + fn test_if_else_statement() { + let (input, condition, consequence, alternative) = + ("if (x < y) { x } else {y}", "x < y", "x", Some("y")); + let lexer = Lexer::new(input); + let mut parser = Parser::new(lexer); + let program = parser.parse_program(); + + check_parse_errors(&parser); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => { + check_conditional_expression(exp, condition, consequence, alternative); + } + _ => panic!("It is not an expression statement"), + } + } + + #[test] + fn test_function_literal_parsing() { + let input = "fn(x, y) { x + y; }"; + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => check_function_literal(exp, vec!["x", "y"], "(x + y)"), + _ => panic!("It is not an expression statement"), + } + } + + #[test] + fn test_parse_funtion_arguments() { + let tests = vec![ + ("fn() {}", Vec::new()), + ("fn(x) {}", vec!["x"]), + ("fn(x,y,z) {}", vec!["x", "y", "z"]), + ]; + + for (input, expected) in tests { + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => check_function_literal(exp, expected, ""), + _ => panic!("It is not an expression statement"), + } + } + } + + #[test] + fn test_function_call_parsing() { + let (input, name, argumnets) = ( + "add(1, 2 * 3, 4 + 5);", + "add", + vec!["1", "(2 * 3)", "(4 + 5)"], + ); + + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => check_function_call(exp, name, argumnets), + _ => panic!("It is not an expression statement"), + } + } + + #[test] + fn test_function_call_parameter_parsing() { + let tests = vec![ + ("add();", "add", vec![]), + ("add(1);", "add", vec!["1"]), + ( + "add(1, 2 * 3, 4 + 5);", + "add", + vec!["1", "(2 * 3)", "(4 + 5)"], + ), + ]; + + for (input, name, argumnets) in tests { + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => check_function_call(exp, name, argumnets), + _ => panic!("It is not an expression statement"), + } + } + } + + #[test] + fn test_string_literal_expression() { + let input = "\"hello world\";"; + + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => check_primitive_literal(exp, "hello world"), + _ => panic!("It is not an expression statement"), + } + } + + #[test] + fn test_array_literal() { + let input = "[1,2*2,3+3]"; + + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + let expressions = match &program.statements[0] { + Statement::Expression(exp) => match exp { + Expression::ArrayLiteral(a) => &a.elements, + _ => panic!("It is not an array literal"), + }, + _ => panic!("It is not an expression statement"), + }; + + assert_eq!(expressions.len(), 3); + check_primitive_literal(&expressions[0], "1"); + check_infix_expression(&expressions[1], "2", "*", "2"); + check_infix_expression(&expressions[2], "3", "+", "3"); + } + + #[test] + fn test_parsing_index_expression_complete() { + let input = "myArray[1+1]"; + + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => match exp { + Expression::IndexExpression(i) => { + assert_eq!(i.left.to_string(), "myArray"); + check_infix_expression(&i.index, "1", "+", "1"); + } + _ => panic!("It is not an index expression"), + }, + _ => panic!("It is not an expression statement"), + } + } + + #[test] + fn test_parsing_index_expression_string_conversion() { + let tests = vec![ + ("myArray[1]", "myArray", "1"), + ("myArray[\"hello\"]", "myArray", "\"hello\""), + ("[1,2,3,4][2]", "[1, 2, 3, 4]", "2"), + ("test()[call()]", "test()", "call()"), + ]; + + for (input, left, index) in tests { + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => check_index_expression(exp, left, index), + + _ => panic!("It is not an expression statement"), + } + } + } + + #[test] + fn test_parsing_hash_map_literal_string_keys() { + let input = "{\"one\": 1, \"two\": 2, \"three\": 3}"; + + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => match exp { + Expression::HashMapLiteral(h) => { + assert_eq!(h.pairs.len(), 3); + let expected = vec![("one", "1"), ("two", "2"), ("three", "3")]; + for (i, (key, value)) in expected.iter().enumerate() { + let pair = h.pairs.get(i).unwrap(); + check_primitive_literal(&pair.0, key); + check_primitive_literal(&pair.1, value); + } + } + _ => panic!("It is not an hash literal"), + }, + _ => panic!("It is not an expression statement"), + } + } + + #[test] + fn test_parsing_empty_hash_map() { + let input = "{}"; + + let program = generate_program(input); + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => match exp { + Expression::HashMapLiteral(h) => { + assert_eq!(h.pairs.len(), 0); + } + _ => panic!("It is not an hash literal"), + }, + _ => panic!("It is not an expression statement"), + } + } + + #[test] + fn test_parsing_hash_map_literal_integer_values() { + let input = "{\"one\": 1 + 34, \"two\": 2/5, \"three\": 3-1}"; + + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => match exp { + Expression::HashMapLiteral(h) => { + assert_eq!(h.pairs.len(), 3); + let expected = vec![ + ("\"one\"", "(1 + 34)"), + ("\"two\"", "(2 / 5)"), + ("\"three\"", "(3 - 1)"), + ]; + for (i, (key, value)) in expected.iter().enumerate() { + let pair = h.pairs.get(i).unwrap(); + assert_eq!(pair.0.to_string(), **key); + assert_eq!(pair.1.to_string(), **value); + } + } + _ => panic!("It is not an hash literal"), + }, + _ => panic!("It is not an expression statement"), + } + } + + #[test] + fn test_parsing_hash_map_literal_mixed_keys() { + let input = "{1:true, 2: \"Hi\", \"three\": 3-1}"; + + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => match exp { + Expression::HashMapLiteral(h) => { + assert_eq!(h.pairs.len(), 3); + let expected = vec![("1", "true"), ("2", "\"Hi\""), ("\"three\"", "(3 - 1)")]; + for (i, (key, value)) in expected.iter().enumerate() { + let pair = h.pairs.get(i).unwrap(); + assert_eq!(pair.0.to_string(), **key); + assert_eq!(pair.1.to_string(), **value); + } + } + _ => panic!("It is not an hash literal"), + }, + _ => panic!("It is not an expression statement"), + } + } + + #[test] + fn test_parsing_function_literal_with_name() { + let input = "let myFunction = fn(){};"; + + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match program.statements[0].clone() { + Statement::Let(l) => match l.value { + Expression::FunctionLiteral(f) => { + assert_eq!(f.name, Some("myFunction".to_string())); + } + _ => panic!("It is not a function literal"), + }, + _ => panic!("It is not a let statement"), + } + } + + #[test] + fn test_parsing_function_literal_without_name() { + let input = "fn(){};"; + + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match program.statements[0].clone() { + Statement::Expression(exp) => match exp { + Expression::FunctionLiteral(f) => { + assert!(f.name.is_none()); + } + _ => panic!("It is not a function literal"), + }, + _ => panic!("It is not an expression"), + } + } + + #[test] + fn test_parsing_while_statements() { + let input = "while(x < 3){ + let x = x + 3; + }"; + + let expected = WhileStatement { + condition: Expression::Infix(InfixOperator { + token: Token::LT, + left: Box::new(Expression::Identifier(Identifier { + token: Token::Ident("x".to_string()), + value: "x".to_string(), + })), + right: Box::new(Expression::Primitive(Primitive::IntegerLiteral(3))), + }), + body: BlockStatement { + statements: vec![Statement::Let(LetStatement { + name: Identifier { + token: Token::Ident("x".to_string()), + value: "x".to_string(), + }, + value: Expression::Infix(InfixOperator { + token: Token::Plus, + left: Box::new(Expression::Identifier(Identifier { + token: Token::Ident("x".to_string()), + value: "x".to_string(), + })), + right: Box::new(Expression::Primitive(Primitive::IntegerLiteral(3))), + }), + })], + }, + }; + + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + + match program.statements[0].clone() { + Statement::While(smt) => { + assert_eq!(smt, expected); + } + _ => panic!("It is not an expression"), + } + } + + fn generate_program(input: &str) -> Program { + let lexer = Lexer::new(input); + let mut parser = Parser::new(lexer); + let program = parser.parse_program(); + + check_parse_errors(&parser); + program + } + + fn check_identifier(exp: &Identifier, value: &str) { + assert_eq!(exp.value, value); + } + + fn check_prefix_expression(exp: &Expression, operator: &str, right: &str) { + match exp { + Expression::Prefix(p) => { + assert_eq!(p.token.to_string(), operator); + assert_eq!(p.right.to_string(), right); + } + _ => panic!("It is not an prefix operator"), + } + } + + fn check_primitive_literal(exp: &Expression, value: &str) { + match exp { + Expression::Primitive(p) => match p { + Primitive::IntegerLiteral(i) => assert_eq!(i.to_string(), value), + Primitive::BooleanLiteral(b) => assert_eq!(b.to_string(), value), + Primitive::StringLiteral(s) => assert_eq!(s, value), + }, + _ => panic!("It is not a literal"), + } + } + + fn check_infix_expression(exp: &Expression, left: &str, operator: &str, right: &str) { + match exp { + Expression::Infix(p) => { + check_primitive_literal(p.left.as_ref(), left); + assert_eq!(operator, p.token.to_string()); + check_primitive_literal(p.right.as_ref(), right); + } + _ => panic!("It is not an infix expression"), + } + } + + fn check_conditional_expression( + exp: &Expression, + condition: &str, + consequence: &str, + alternative: Option<&str>, + ) { + match exp { + Expression::Conditional(p) => { + assert_eq!(format!("({condition})"), p.condition.as_ref().to_string()); + check_block_statement(&p.consequence, consequence); + match alternative { + Some(a) => check_block_statement(p.alternative.as_ref().unwrap(), a), + None => assert!(p.alternative.is_none()), + } + } + _ => panic!("It is not a conditional expression"), + } + } + + fn check_block_statement(statement: &BlockStatement, expected: &str) { + if expected.is_empty() { + assert_eq!(statement.to_string(), ""); // Empty block statement does not contain a + // newline + } else { + assert_eq!(statement.to_string(), format!("{expected}\n")); + } + } + + fn check_function_literal(exp: &Expression, params: Vec<&str>, body: &str) { + match exp { + Expression::FunctionLiteral(p) => { + assert_eq!(p.parameters.len(), params.len()); + for (i, param) in params.iter().enumerate() { + check_identifier(&p.parameters[i], param); + } + check_block_statement(&p.body, body); + } + _ => panic!("It is not a function literal"), + } + } + + fn check_function_call(exp: &Expression, name: &str, arguments: Vec<&str>) { + match exp { + Expression::FunctionCall(p) => { + assert_eq!(p.function.to_string(), name); + assert_eq!(p.arguments.len(), arguments.len()); + for (i, arg) in arguments.iter().enumerate() { + assert_eq!(p.arguments[i].to_string(), arg.to_owned().to_string()); + } + } + _ => panic!("It is not a function call"), + } + } + + fn check_index_expression(exp: &Expression, left: &str, index: &str) { + match exp { + Expression::IndexExpression(p) => { + assert_eq!(p.left.to_string(), left); + assert_eq!(p.index.to_string(), index); + } + _ => panic!("It is not an index expression"), + } + } +} From 981bcbac42ab06aa4834e9ee612f60cab22187fd Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Sat, 12 Aug 2023 17:44:27 +0200 Subject: [PATCH 3/6] feat: evaluator now handles while loops --- src/interpreter/evaluator.rs | 559 +--------------------------- src/interpreter/evaluator_tests.rs | 567 +++++++++++++++++++++++++++++ src/interpreter/mod.rs | 1 + 3 files changed, 579 insertions(+), 548 deletions(-) create mode 100644 src/interpreter/evaluator_tests.rs diff --git a/src/interpreter/evaluator.rs b/src/interpreter/evaluator.rs index f598c72..426ccc2 100644 --- a/src/interpreter/evaluator.rs +++ b/src/interpreter/evaluator.rs @@ -73,6 +73,17 @@ impl Evaluator { self.env.borrow_mut().set(x.name.to_string(), value); NULL } + Statement::While(stm) => { + let mut result = NULL; + while Self::is_truthy(&self.eval_expression(stm.condition.clone())) { + result = self.eval_block_statemet(stm.body.clone()); + match result { + Object::RETURN(_) | Object::ERROR(_) => return result, + _ => (), + } + } + result + } } } @@ -351,551 +362,3 @@ impl Evaluator { Object::HASHMAP(hashmap) } } -#[cfg(test)] -mod tests { - - use super::*; - use crate::{lexer::Lexer, parser::Parser}; - use std::collections::HashMap; - - #[test] - fn test_eval_integer_expression() { - let tests = vec![ - ("5", 5), - ("10", 10), - ("-5", -5), - ("-10", -10), - ("5 + 5 + 5 + 5 - 10", 10), - ("2 * 2 * 2 * 2 * 2", 32), - ("-50 + 100 + -50", 0), - ("5 * 2 + 10", 20), - ("5 + 2 * 10", 25), - ("20 + 2 * -10", 0), - ("50 / 2 * 2 + 10", 60), - ("2 * (5 + 10)", 30), - ("3 * 3 * 3 + 10", 37), - ("3 * (3 * 3) + 10", 37), - ("(5 + 10 * 2 + 15 / 3) * 2 + -10", 50), - ]; - - for (input, expected) in tests { - let evaluated = test_eval(input); - test_integer_object(evaluated, expected); - } - } - - #[test] - fn test_eval_boolean_expression() { - let tests = vec![ - ("true", true), - ("false", false), - ("1 < 2", true), - ("1 > 2", false), - ("1 < 1", false), - ("1 > 1", false), - ("1 <= 2", true), - ("1 >= 2", false), - ("1 <= 1", true), - ("1 >= 1", true), - ("1 == 1", true), - ("1 != 1", false), - ("1 == 2", false), - ("1 != 2", true), - // - ("true == true", true), - ("false == false", true), - ("true == false", false), - ("true != false", true), - ("false != true", true), - // - ("false && true", false), - ("true && false", false), - ("false && false", false), - ("true && true", true), - // - ("false || true", true), - ("true || false", true), - ("false || false", false), - ("true || true", true), - // - ("(1 < 2) == true", true), - ("(1 < 2) == false", false), - ("(1 > 2) == true", false), - ("(1 > 2) == false", true), - ]; - - for (input, expected) in tests { - let evaluated = test_eval(input); - test_boolean_object(evaluated, expected); - } - } - - #[test] - fn test_bang_operator() { - let tests = vec![ - ("!true", false), - ("!false", true), - ("!5", false), - ("!!true", true), - ("!!false", false), - ("!!5", true), - ]; - - for (input, expected) in tests { - let evaluated = test_eval(input); - test_boolean_object(evaluated, expected); - } - } - - #[test] - fn test_if_else_expression() { - let tests = vec![ - ("if (true) { 10 }", Some(10)), - ("if (false) { 10 }", None), - ("if (1) { 10 }", Some(10)), - ("if (1 < 2) { 10 }", Some(10)), - ("if (1 > 2) { 10 }", None), - ("if (1 > 2) { 10 } else { 20 }", Some(20)), - ("if (1 < 2) { 10 } else { 20 }", Some(10)), - ]; - - for (input, expected) in tests { - let evaluated = test_eval(input); - if let Some(expected) = expected { - test_integer_object(evaluated, expected); - } else { - test_null_object(evaluated); - } - } - } - - #[test] - fn test_return_statements() { - let tests = vec![ - ("return 10;", 10), - ("return 10; 9;", 10), - ("return 2 * 5; 9;", 10), - ("9; return 2 * 5; 9;", 10), - ("if (10 > 1) { return 10; }", 10), - ("if (10 > 1) { if (10 > 1) { return 10; } return 1; }", 10), - ]; - - for (input, expected) in tests { - let evaluated = test_eval(input); - test_integer_object(evaluated, expected); - } - } - - #[test] - fn test_error_handling() { - let tests = vec![ - ("5 + true;", "type mismatch: INTEGER + BOOLEAN"), - ("5 + true; 5;", "type mismatch: INTEGER + BOOLEAN"), - ("-true", "unknown operator: -true"), - ("true + false;", "unknown operator: BOOLEAN + BOOLEAN"), - ("5; true + false; 5", "unknown operator: BOOLEAN + BOOLEAN"), - ( - "if (10 > 1) { true + false; }", - "unknown operator: BOOLEAN + BOOLEAN", - ), - ( - r#" - if (10 > 1) { - if (10 > 1) { - return true + false; - } - return 1; - }"#, - "unknown operator: BOOLEAN + BOOLEAN", - ), - ("foobar", "identifier not found: foobar"), - (r#""Hello" - "World""#, "unknown operator: STRING - STRING"), - ( - r#"{"name": "Monkey"}[fn(x) { x }];"#, - "unusable as hash key: FUNCTION", - ), - ]; - - for (input, expected) in tests { - let evaluated = test_eval(input); - test_error_object(evaluated, expected.to_string()); - } - } - - #[test] - fn test_let_stateemtns() { - let tests = vec![ - ("let a = 5; a;", Some(5)), - ("let a = 5 * 5; a;", Some(25)), - ("let a = 5; let b = a; b;", Some(5)), - ("let a = 5; let b = a; let c = a + b + 5; c;", Some(15)), - ("let a = 5;", None), - ]; - - for (input, expected) in tests { - let evaluated = test_eval(input); - match expected { - Some(expected) => test_integer_object(evaluated, expected), - None => test_null_object(evaluated), - } - } - } - - #[test] - fn test_function_object() { - let input = "fn(x) { x + 2; };"; - - let evaluated = test_eval(input); - - match evaluated { - Object::FUNCTION(x) => { - assert_eq!(x.parameters.len(), 1); - assert_eq!(x.parameters[0].to_string(), "x"); - assert_eq!(x.body.to_string(), "(x + 2)\n"); - } - _ => panic!("The object is not a function"), - } - } - - #[test] - fn test_function_application() { - let tests = vec![ - ("let identity = fn(x) { x; }; identity(5);", 5), - ("let identity = fn(x) { return x; }; identity(5);", 5), - ("let double = fn(x) { x * 2; }; double(5);", 10), - ("let add = fn(x, y) { x + y; }; add(5, 11);", 16), - ( - "let add = fn(x, y) { x + y; }; add(5 + 5, add(10, 10));", - 30, - ), - ("fn(x) { x; }(5)", 5), - ]; - for (input, expected) in tests { - let evaluated = test_eval(input); - test_integer_object(evaluated, expected); - } - } - - #[test] - fn test_closures() { - let input = r#" - let newAdder = fn(x) { - fn(y) { x + y }; - }; - - let addTwo = newAdder(2); - addTwo(2);"#; - - test_integer_object(test_eval(input), 4); - } - - #[test] - fn test_string_literal() { - let input = "\"Hello World!\""; - - let evaluated = test_eval(input); - - test_string_object(evaluated, "Hello World!".to_string()); - } - - #[test] - fn test_string_concatenationm() { - let input = "\"Hello\" + \" \" + \"World!\""; - - let evaluated = test_eval(input); - - test_string_object(evaluated, "Hello World!".to_string()); - } - - #[test] - fn test_builttin_len_function() { - let tests_striung = vec![ - (r#"len("")"#, 0), - (r#"len("four")"#, 4), - (r#"len("hello world")"#, 11), - (r#"len([1,2,3,4,5])"#, 5), - ]; - - for (input, expected) in tests_striung { - test_integer_object(test_eval(input), expected); - } - } - - #[test] - fn test_builttin_len_function_errors() { - let tests_striung = vec![ - (r#"len(1)"#, "argument to `len` not supported, got INTEGER"), - ( - r#"len("one", "two")"#, - "wrong number of arguments. got=2, want=1", - ), - ]; - - for (input, expected) in tests_striung { - test_error_object(test_eval(input), expected.to_string()); - } - } - - #[test] - fn test_array_literals() { - let input = "[1, 2 * 2, 3 + 3]"; - - let evaluated = test_eval(input); - - match evaluated { - Object::ARRAY(x) => { - assert_eq!(x.len(), 3); - test_integer_object(x[0].clone(), 1); - test_integer_object(x[1].clone(), 4); - test_integer_object(x[2].clone(), 6); - } - _ => panic!("The object is not an array"), - } - } - - #[test] - fn test_array_index_expression() { - let tests = vec![ - ("[1, 2, 3][0]", Some(1)), - ("[1, 2, 3][1]", Some(2)), - ("[1, 2, 3][2]", Some(3)), - ("let i = 0; [1][i];", Some(1)), - ("[1, 2, 3][1 + 1];", Some(3)), - ("let myArray = [1, 2, 3]; myArray[2];", Some(3)), - ( - "let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", - Some(6), - ), - ( - "let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]", - Some(2), - ), - ("[1, 2, 3][3]", None), - ("[1, 2, 3][-1]", None), - ]; - - for (input, expected) in tests { - match expected { - Some(x) => test_integer_object(test_eval(input), x), - None => test_null_object(test_eval(input)), - } - } - } - - #[test] - fn test_first_function() { - let tests = vec![ - ("first([1, 2, 3])", Some(1)), - ("first([1])", Some(1)), - ("first([])", None), - ("first(1)", None), - ("first([1, 2, 3], [4, 5, 6])", None), - ]; - - for (input, expected) in tests { - println!("{input}"); - match expected { - Some(x) => test_integer_object(test_eval(input), x), - None => test_null_object(test_eval(input)), - } - } - } - - #[test] - fn test_last_function() { - let tests = vec![ - ("last([1, 2, 3])", Some(3)), - ("last([1])", Some(1)), - ("last([])", None), - ("last(1)", None), - ("last([1, 2, 3], [4, 5, 6])", None), - ]; - - for (input, expected) in tests { - println!("{input}"); - match expected { - Some(x) => test_integer_object(test_eval(input), x), - None => test_null_object(test_eval(input)), - } - } - } - - #[test] - fn test_rest_function() { - let tests = vec![ - ("rest([1, 2, 3])", Some(vec![2, 3])), - ("rest([1])", Some(Vec::new())), - ("rest([])", None), - ("rest(1)", None), - ("rest([1, 2, 3], [4, 5, 6])", None), - ]; - - for (input, expected) in tests { - println!("{input}"); - match expected { - Some(x) => { - let evaluated = test_eval(input); - test_array_object(evaluated, x); - } - None => test_null_object(test_eval(input)), - } - } - } - - #[test] - fn test_push_function() { - let tests = vec![ - ("push([], 1)", Some(vec![1])), - ("push([1], 2)", Some(vec![1, 2])), - ("push([1,2], 3)", Some(vec![1, 2, 3])), - ("push(1, 1)", None), - ("push([1,2], 3, 4)", None), - ]; - - for (input, expected) in tests { - println!("{input}"); - match expected { - Some(x) => test_array_object(test_eval(input), x), - None => test_null_object(test_eval(input)), - } - } - } - - #[test] - fn test_array_functions_together() { - let input = r#" - let map = fn(arr, f) { - let iter = fn(arr, accumulated) { - if (len(arr) == 0) { - accumulated - } else { - iter(rest(arr), push(accumulated, f(first(arr)))); - } - }; - iter(arr, []); - }; - let a = [1, 2, 3, 4]; - let double = fn(x) { x * 2 }; - map(a, double); - "#; - - let expected = vec![2, 4, 6, 8]; - - test_array_object(test_eval(input), expected); - } - - #[test] - fn test_evaluate_hash_literals() { - let input = r#" - let two = "two"; - { - "one": 10 - 9, - two: 1 + 1, - "thr" + "ee": 6 / 2, - 4: 4, - true: 5, - false: 6 - } - "#; - - let mut expected = HashMap::new(); - expected.insert(Object::STRING("one".to_string()), Object::INTEGER(1)); - expected.insert(Object::STRING("two".to_string()), Object::INTEGER(2)); - expected.insert(Object::STRING("three".to_string()), Object::INTEGER(3)); - expected.insert(Object::INTEGER(4), Object::INTEGER(4)); - expected.insert(Object::BOOLEAN(true), Object::INTEGER(5)); - expected.insert(Object::BOOLEAN(false), Object::INTEGER(6)); - - let evaluated = test_eval(input); - match evaluated { - Object::HASHMAP(hash) => { - assert_eq!(hash.len(), expected.len()); - - for (expected_key, expected_value) in expected { - match hash.get(&expected_key) { - Some(value) => assert_eq!(value, &expected_value), - None => panic!("No pair for given key in Pairs"), - } - } - } - _ => panic!("The object is not a hash"), - } - } - - #[test] - fn test_hash_index_expressions() { - let tests = vec![ - (r#"{"foo": 5}["foo"]"#, Some(5)), - (r#"{"foo": 5}["bar"]"#, None), - (r#"let key = "foo"; {"foo": 5}[key]"#, Some(5)), - (r#"{}["foo"]"#, None), - (r#"{5: 5}[5]"#, Some(5)), - (r#"{true: 5}[true]"#, Some(5)), - (r#"{false: 5}[false]"#, Some(5)), - ]; - - for (input, expected) in tests { - println!("{input}"); - match expected { - Some(x) => test_integer_object(test_eval(input), x), - None => test_null_object(test_eval(input)), - } - } - } - - fn test_eval(input: &str) -> Object { - let lexer = Lexer::new(input); - let mut parser = Parser::new(lexer); - let program = parser.parse_program(); - let mut evaluator = Evaluator::new(); - evaluator.eval(program) - } - - fn test_integer_object(object: Object, expected: i64) { - match object { - Object::INTEGER(x) => assert_eq!(x, expected), - _ => panic!("The object is not an integer"), - } - } - - fn test_boolean_object(object: Object, expected: bool) { - match object { - Object::BOOLEAN(x) => assert_eq!(x, expected), - _ => panic!("The object is not a boolean"), - } - } - - fn test_null_object(object: Object) { - match object { - Object::NULL | Object::ERROR(_) => (), - - _ => panic!("The object is not null"), - } - } - - fn test_error_object(object: Object, expected: String) { - match object { - Object::ERROR(x) => assert_eq!(x, expected), - _ => panic!("The object is not an error"), - } - } - - fn test_string_object(object: Object, expected: String) { - match object { - Object::STRING(s) => assert_eq!(format!("{s}"), expected), - _ => panic!("The object is not an string"), - } - } - - fn test_array_object(object: Object, expected: Vec) { - match object { - Object::ARRAY(x) => { - assert_eq!(x.len(), expected.len()); - for (i, v) in x.iter().enumerate() { - test_integer_object(v.clone(), expected[i]); - } - } - _ => panic!("The object is not an array"), - } - } -} diff --git a/src/interpreter/evaluator_tests.rs b/src/interpreter/evaluator_tests.rs new file mode 100644 index 0000000..1fe55df --- /dev/null +++ b/src/interpreter/evaluator_tests.rs @@ -0,0 +1,567 @@ +#[cfg(test)] +mod tests { + + use crate::{interpreter::evaluator::Evaluator, lexer::Lexer, object::Object, parser::Parser}; + use std::collections::HashMap; + + #[test] + fn test_eval_integer_expression() { + let tests = vec![ + ("5", 5), + ("10", 10), + ("-5", -5), + ("-10", -10), + ("5 + 5 + 5 + 5 - 10", 10), + ("2 * 2 * 2 * 2 * 2", 32), + ("-50 + 100 + -50", 0), + ("5 * 2 + 10", 20), + ("5 + 2 * 10", 25), + ("20 + 2 * -10", 0), + ("50 / 2 * 2 + 10", 60), + ("2 * (5 + 10)", 30), + ("3 * 3 * 3 + 10", 37), + ("3 * (3 * 3) + 10", 37), + ("(5 + 10 * 2 + 15 / 3) * 2 + -10", 50), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input); + test_integer_object(evaluated, expected); + } + } + + #[test] + fn test_eval_boolean_expression() { + let tests = vec![ + ("true", true), + ("false", false), + ("1 < 2", true), + ("1 > 2", false), + ("1 < 1", false), + ("1 > 1", false), + ("1 <= 2", true), + ("1 >= 2", false), + ("1 <= 1", true), + ("1 >= 1", true), + ("1 == 1", true), + ("1 != 1", false), + ("1 == 2", false), + ("1 != 2", true), + // + ("true == true", true), + ("false == false", true), + ("true == false", false), + ("true != false", true), + ("false != true", true), + // + ("false && true", false), + ("true && false", false), + ("false && false", false), + ("true && true", true), + // + ("false || true", true), + ("true || false", true), + ("false || false", false), + ("true || true", true), + // + ("(1 < 2) == true", true), + ("(1 < 2) == false", false), + ("(1 > 2) == true", false), + ("(1 > 2) == false", true), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input); + test_boolean_object(evaluated, expected); + } + } + + #[test] + fn test_bang_operator() { + let tests = vec![ + ("!true", false), + ("!false", true), + ("!5", false), + ("!!true", true), + ("!!false", false), + ("!!5", true), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input); + test_boolean_object(evaluated, expected); + } + } + + #[test] + fn test_if_else_expression() { + let tests = vec![ + ("if (true) { 10 }", Some(10)), + ("if (false) { 10 }", None), + ("if (1) { 10 }", Some(10)), + ("if (1 < 2) { 10 }", Some(10)), + ("if (1 > 2) { 10 }", None), + ("if (1 > 2) { 10 } else { 20 }", Some(20)), + ("if (1 < 2) { 10 } else { 20 }", Some(10)), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input); + if let Some(expected) = expected { + test_integer_object(evaluated, expected); + } else { + test_null_object(evaluated); + } + } + } + + #[test] + fn test_return_statements() { + let tests = vec![ + ("return 10;", 10), + ("return 10; 9;", 10), + ("return 2 * 5; 9;", 10), + ("9; return 2 * 5; 9;", 10), + ("if (10 > 1) { return 10; }", 10), + ("if (10 > 1) { if (10 > 1) { return 10; } return 1; }", 10), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input); + test_integer_object(evaluated, expected); + } + } + + #[test] + fn test_error_handling() { + let tests = vec![ + ("5 + true;", "type mismatch: INTEGER + BOOLEAN"), + ("5 + true; 5;", "type mismatch: INTEGER + BOOLEAN"), + ("-true", "unknown operator: -true"), + ("true + false;", "unknown operator: BOOLEAN + BOOLEAN"), + ("5; true + false; 5", "unknown operator: BOOLEAN + BOOLEAN"), + ( + "if (10 > 1) { true + false; }", + "unknown operator: BOOLEAN + BOOLEAN", + ), + ( + r#" + if (10 > 1) { + if (10 > 1) { + return true + false; + } + return 1; + }"#, + "unknown operator: BOOLEAN + BOOLEAN", + ), + ("foobar", "identifier not found: foobar"), + (r#""Hello" - "World""#, "unknown operator: STRING - STRING"), + ( + r#"{"name": "Monkey"}[fn(x) { x }];"#, + "unusable as hash key: FUNCTION", + ), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input); + test_error_object(evaluated, expected.to_string()); + } + } + + #[test] + fn test_let_stateemtns() { + let tests = vec![ + ("let a = 5; a;", Some(5)), + ("let a = 5 * 5; a;", Some(25)), + ("let a = 5; let b = a; b;", Some(5)), + ("let a = 5; let b = a; let c = a + b + 5; c;", Some(15)), + ("let a = 5;", None), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input); + match expected { + Some(expected) => test_integer_object(evaluated, expected), + None => test_null_object(evaluated), + } + } + } + + #[test] + fn test_function_object() { + let input = "fn(x) { x + 2; };"; + + let evaluated = test_eval(input); + + match evaluated { + Object::FUNCTION(x) => { + assert_eq!(x.parameters.len(), 1); + assert_eq!(x.parameters[0].to_string(), "x"); + assert_eq!(x.body.to_string(), "(x + 2)\n"); + } + _ => panic!("The object is not a function"), + } + } + + #[test] + fn test_function_application() { + let tests = vec![ + ("let identity = fn(x) { x; }; identity(5);", 5), + ("let identity = fn(x) { return x; }; identity(5);", 5), + ("let double = fn(x) { x * 2; }; double(5);", 10), + ("let add = fn(x, y) { x + y; }; add(5, 11);", 16), + ( + "let add = fn(x, y) { x + y; }; add(5 + 5, add(10, 10));", + 30, + ), + ("fn(x) { x; }(5)", 5), + ]; + for (input, expected) in tests { + let evaluated = test_eval(input); + test_integer_object(evaluated, expected); + } + } + + #[test] + fn test_closures() { + let input = r#" + let newAdder = fn(x) { + fn(y) { x + y }; + }; + + let addTwo = newAdder(2); + addTwo(2);"#; + + test_integer_object(test_eval(input), 4); + } + + #[test] + fn test_string_literal() { + let input = "\"Hello World!\""; + + let evaluated = test_eval(input); + + test_string_object(evaluated, "Hello World!".to_string()); + } + + #[test] + fn test_string_concatenationm() { + let input = "\"Hello\" + \" \" + \"World!\""; + + let evaluated = test_eval(input); + + test_string_object(evaluated, "Hello World!".to_string()); + } + + #[test] + fn test_builttin_len_function() { + let tests_striung = vec![ + (r#"len("")"#, 0), + (r#"len("four")"#, 4), + (r#"len("hello world")"#, 11), + (r#"len([1,2,3,4,5])"#, 5), + ]; + + for (input, expected) in tests_striung { + test_integer_object(test_eval(input), expected); + } + } + + #[test] + fn test_builttin_len_function_errors() { + let tests_striung = vec![ + (r#"len(1)"#, "argument to `len` not supported, got INTEGER"), + ( + r#"len("one", "two")"#, + "wrong number of arguments. got=2, want=1", + ), + ]; + + for (input, expected) in tests_striung { + test_error_object(test_eval(input), expected.to_string()); + } + } + + #[test] + fn test_array_literals() { + let input = "[1, 2 * 2, 3 + 3]"; + + let evaluated = test_eval(input); + + match evaluated { + Object::ARRAY(x) => { + assert_eq!(x.len(), 3); + test_integer_object(x[0].clone(), 1); + test_integer_object(x[1].clone(), 4); + test_integer_object(x[2].clone(), 6); + } + _ => panic!("The object is not an array"), + } + } + + #[test] + fn test_array_index_expression() { + let tests = vec![ + ("[1, 2, 3][0]", Some(1)), + ("[1, 2, 3][1]", Some(2)), + ("[1, 2, 3][2]", Some(3)), + ("let i = 0; [1][i];", Some(1)), + ("[1, 2, 3][1 + 1];", Some(3)), + ("let myArray = [1, 2, 3]; myArray[2];", Some(3)), + ( + "let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", + Some(6), + ), + ( + "let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]", + Some(2), + ), + ("[1, 2, 3][3]", None), + ("[1, 2, 3][-1]", None), + ]; + + for (input, expected) in tests { + match expected { + Some(x) => test_integer_object(test_eval(input), x), + None => test_null_object(test_eval(input)), + } + } + } + + #[test] + fn test_first_function() { + let tests = vec![ + ("first([1, 2, 3])", Some(1)), + ("first([1])", Some(1)), + ("first([])", None), + ("first(1)", None), + ("first([1, 2, 3], [4, 5, 6])", None), + ]; + + for (input, expected) in tests { + println!("{input}"); + match expected { + Some(x) => test_integer_object(test_eval(input), x), + None => test_null_object(test_eval(input)), + } + } + } + + #[test] + fn test_last_function() { + let tests = vec![ + ("last([1, 2, 3])", Some(3)), + ("last([1])", Some(1)), + ("last([])", None), + ("last(1)", None), + ("last([1, 2, 3], [4, 5, 6])", None), + ]; + + for (input, expected) in tests { + println!("{input}"); + match expected { + Some(x) => test_integer_object(test_eval(input), x), + None => test_null_object(test_eval(input)), + } + } + } + + #[test] + fn test_rest_function() { + let tests = vec![ + ("rest([1, 2, 3])", Some(vec![2, 3])), + ("rest([1])", Some(Vec::new())), + ("rest([])", None), + ("rest(1)", None), + ("rest([1, 2, 3], [4, 5, 6])", None), + ]; + + for (input, expected) in tests { + println!("{input}"); + match expected { + Some(x) => { + let evaluated = test_eval(input); + test_array_object(evaluated, x); + } + None => test_null_object(test_eval(input)), + } + } + } + + #[test] + fn test_push_function() { + let tests = vec![ + ("push([], 1)", Some(vec![1])), + ("push([1], 2)", Some(vec![1, 2])), + ("push([1,2], 3)", Some(vec![1, 2, 3])), + ("push(1, 1)", None), + ("push([1,2], 3, 4)", None), + ]; + + for (input, expected) in tests { + println!("{input}"); + match expected { + Some(x) => test_array_object(test_eval(input), x), + None => test_null_object(test_eval(input)), + } + } + } + + #[test] + fn test_array_functions_together() { + let input = r#" + let map = fn(arr, f) { + let iter = fn(arr, accumulated) { + if (len(arr) == 0) { + accumulated + } else { + iter(rest(arr), push(accumulated, f(first(arr)))); + } + }; + iter(arr, []); + }; + let a = [1, 2, 3, 4]; + let double = fn(x) { x * 2 }; + map(a, double); + "#; + + let expected = vec![2, 4, 6, 8]; + + test_array_object(test_eval(input), expected); + } + + #[test] + fn test_evaluate_hash_literals() { + let input = r#" + let two = "two"; + { + "one": 10 - 9, + two: 1 + 1, + "thr" + "ee": 6 / 2, + 4: 4, + true: 5, + false: 6 + } + "#; + + let mut expected = HashMap::new(); + expected.insert(Object::STRING("one".to_string()), Object::INTEGER(1)); + expected.insert(Object::STRING("two".to_string()), Object::INTEGER(2)); + expected.insert(Object::STRING("three".to_string()), Object::INTEGER(3)); + expected.insert(Object::INTEGER(4), Object::INTEGER(4)); + expected.insert(Object::BOOLEAN(true), Object::INTEGER(5)); + expected.insert(Object::BOOLEAN(false), Object::INTEGER(6)); + + let evaluated = test_eval(input); + match evaluated { + Object::HASHMAP(hash) => { + assert_eq!(hash.len(), expected.len()); + + for (expected_key, expected_value) in expected { + match hash.get(&expected_key) { + Some(value) => assert_eq!(value, &expected_value), + None => panic!("No pair for given key in Pairs"), + } + } + } + _ => panic!("The object is not a hash"), + } + } + + #[test] + fn test_hash_index_expressions() { + let tests = vec![ + (r#"{"foo": 5}["foo"]"#, Some(5)), + (r#"{"foo": 5}["bar"]"#, None), + (r#"let key = "foo"; {"foo": 5}[key]"#, Some(5)), + (r#"{}["foo"]"#, None), + (r#"{5: 5}[5]"#, Some(5)), + (r#"{true: 5}[true]"#, Some(5)), + (r#"{false: 5}[false]"#, Some(5)), + ]; + + for (input, expected) in tests { + println!("{input}"); + match expected { + Some(x) => test_integer_object(test_eval(input), x), + None => test_null_object(test_eval(input)), + } + } + } + + #[test] + fn test_while_statements() { + let tests = vec![ + ("let a = 0; while (a < 10) { let a = a + 1; }; a", Some(10)), + ( + "let a = 100; while (a < 10) { let a = a + 1; }; a", + Some(100), + ), + ("while (false) { 1 }", None), + ]; + + for (input, expected) in tests { + println!("{input}"); + match expected { + Some(x) => test_integer_object(test_eval(input), x), + None => test_null_object(test_eval(input)), + } + } + } + + fn test_eval(input: &str) -> Object { + let lexer = Lexer::new(input); + let mut parser = Parser::new(lexer); + let program = parser.parse_program(); + let mut evaluator = Evaluator::new(); + evaluator.eval(program) + } + + fn test_integer_object(object: Object, expected: i64) { + match object { + Object::INTEGER(x) => assert_eq!(x, expected), + _ => panic!("The object is not an integer"), + } + } + + fn test_boolean_object(object: Object, expected: bool) { + match object { + Object::BOOLEAN(x) => assert_eq!(x, expected), + _ => panic!("The object is not a boolean"), + } + } + + fn test_null_object(object: Object) { + match object { + Object::NULL | Object::ERROR(_) => (), + + _ => panic!("The object is not null"), + } + } + + fn test_error_object(object: Object, expected: String) { + match object { + Object::ERROR(x) => assert_eq!(x, expected), + _ => panic!("The object is not an error"), + } + } + + fn test_string_object(object: Object, expected: String) { + match object { + Object::STRING(s) => assert_eq!(format!("{s}"), expected), + _ => panic!("The object is not an string"), + } + } + + fn test_array_object(object: Object, expected: Vec) { + match object { + Object::ARRAY(x) => { + assert_eq!(x.len(), expected.len()); + for (i, v) in x.iter().enumerate() { + test_integer_object(v.clone(), expected[i]); + } + } + _ => panic!("The object is not an array"), + } + } +} diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 86a53ae..ce3595d 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -1 +1,2 @@ pub mod evaluator; +mod evaluator_tests; From 16f397f95213948cd96d5f9114490a5c7bae465a Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Mon, 14 Aug 2023 16:22:17 +0200 Subject: [PATCH 4/6] feat: add while statements to the compiler/vm --- src/compiler/compiler_tests.rs | 36 +++++++++++++++ src/compiler/mod.rs | 22 +++++++++- src/parser/parser_tests.rs | 41 +++++++++++------ src/repl/mod.rs | 2 +- src/vm/vm_tests.rs | 80 +++++++++++++++++++++++++++++++++- 5 files changed, 164 insertions(+), 17 deletions(-) diff --git a/src/compiler/compiler_tests.rs b/src/compiler/compiler_tests.rs index 3fbabd1..dfab1a2 100644 --- a/src/compiler/compiler_tests.rs +++ b/src/compiler/compiler_tests.rs @@ -1188,4 +1188,40 @@ pub mod tests { run_compiler(tests); } + + #[test] + fn test_while_statements() { + let input = r#" + while (true){ + ("yes"); + } + "# + .to_string(); + + println!("{input}"); + println!("{:?}", parse(&input)); + println!("{}", parse(&input)); + + let tests = vec![CompilerTestCase { + input: r#" + while (true){ + puts("yes"); + } + "# + .to_string(), + expected_constants: vec![Object::STRING("yes".to_string())], + expected_instructions: flatten_instructions(vec![ + Opcode::True.make(vec![]), // 000 + Opcode::JumpNotTruthy.make(vec![15]), // 001 + Opcode::GetBuiltin.make(vec![5]), // 004 + Opcode::Constant.make(vec![0]), // 006 + Opcode::Call.make(vec![1]), // 009 + Opcode::Pop.make(vec![]), // 011 + Opcode::Jump.make(vec![0]), // 012 + // 015 + ]), + }]; + + run_compiler(tests); + } } diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index e970968..d084c40 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -17,7 +17,7 @@ use crate::{ }, parser::ast::{ BlockStatement, Conditional, Expression, FunctionLiteral, InfixOperator, Primitive, - Program, Statement, + Program, Statement, WhileStatement, }, }; @@ -168,6 +168,9 @@ impl Compiler { self.compile_expression(r.return_value)?; self.emit(Opcode::ReturnValue, vec![]); } + Statement::While(wh) => { + self.compile_while_statement(wh)?; + } } Ok(()) @@ -380,6 +383,23 @@ impl Compiler { Ok(()) } + fn compile_while_statement(&mut self, wh: WhileStatement) -> Result<(), String> { + let condition_pos = self.current_instructions().data.len(); + self.compile_expression(wh.condition)?; + + let jump_not_truthy_pos = self.emit(Opcode::JumpNotTruthy, vec![9999]); // We emit a dummy value for the jump offset + // and we will fix it later + self.compile_block_statement(wh.body)?; + + self.emit(Opcode::Jump, vec![condition_pos as i32]); // We emit a dummy value for the jump offset + // and we will fix it later + + let after_body_pos = self.current_instructions().data.len(); + self.change_operand(jump_not_truthy_pos, after_body_pos as i32)?; + + Ok(()) + } + fn last_instruction_is(&self, opcode: Opcode) -> bool { match self.scopes[self.scope_index].last_instruction { Some(ref last) => last.opcode == opcode, diff --git a/src/parser/parser_tests.rs b/src/parser/parser_tests.rs index 145d17e..9bf2ff9 100644 --- a/src/parser/parser_tests.rs +++ b/src/parser/parser_tests.rs @@ -5,8 +5,8 @@ mod tests { lexer::{token::Token, Lexer}, parser::{ ast::{ - BlockStatement, Expression, Identifier, InfixOperator, LetStatement, Primitive, - Program, ReturnStatement, Statement, WhileStatement, + BlockStatement, Expression, FunctionCall, Identifier, InfixOperator, LetStatement, + Primitive, Program, ReturnStatement, Statement, WhileStatement, }, Parser, }, @@ -575,6 +575,7 @@ mod tests { fn test_parsing_while_statements() { let input = "while(x < 3){ let x = x + 3; + puts(x); }"; let expected = WhileStatement { @@ -587,24 +588,38 @@ mod tests { right: Box::new(Expression::Primitive(Primitive::IntegerLiteral(3))), }), body: BlockStatement { - statements: vec![Statement::Let(LetStatement { - name: Identifier { - token: Token::Ident("x".to_string()), - value: "x".to_string(), - }, - value: Expression::Infix(InfixOperator { - token: Token::Plus, - left: Box::new(Expression::Identifier(Identifier { + statements: vec![ + Statement::Let(LetStatement { + name: Identifier { token: Token::Ident("x".to_string()), value: "x".to_string(), - })), - right: Box::new(Expression::Primitive(Primitive::IntegerLiteral(3))), + }, + value: Expression::Infix(InfixOperator { + token: Token::Plus, + left: Box::new(Expression::Identifier(Identifier { + token: Token::Ident("x".to_string()), + value: "x".to_string(), + })), + right: Box::new(Expression::Primitive(Primitive::IntegerLiteral(3))), + }), }), - })], + Statement::Expression(Expression::FunctionCall(FunctionCall { + function: Box::new(Expression::Identifier(Identifier { + token: Token::Ident("puts".to_string()), + value: "puts".to_string(), + })), + arguments: vec![Expression::Identifier(Identifier { + token: Token::Ident("x".to_string()), + value: "x".to_string(), + })], + })), + ], }, }; + println!("Input:\n{input}"); let program = generate_program(input); + println!("Parsed:\n{program}"); assert_eq!(program.statements.len(), 1); diff --git a/src/repl/mod.rs b/src/repl/mod.rs index d3ce150..ad606b5 100644 --- a/src/repl/mod.rs +++ b/src/repl/mod.rs @@ -11,7 +11,7 @@ use crate::{ builtins::BuiltinFunction, {Object, NULL}, }, - parser::{Parser, ParserErrors}, + parser::{parser_errors::ParserErrors, Parser}, repl::errors::{CompilerError, LexerErrors, RuntimeError}, vm::{GLOBALS_SIZE, VM}, }; diff --git a/src/vm/vm_tests.rs b/src/vm/vm_tests.rs index 917ff4c..498198b 100644 --- a/src/vm/vm_tests.rs +++ b/src/vm/vm_tests.rs @@ -1132,12 +1132,13 @@ mod tests { input: r#" let f = fn(a){ let a = 1; + let a = a + 1; a }; f(1) "# .to_string(), - expected: Object::INTEGER(1), + expected: Object::INTEGER(2), }, VmTestCase { input: r#" @@ -1179,6 +1180,19 @@ mod tests { .to_string(), expected: Object::INTEGER(11), }, + VmTestCase { + input: r#" + let a = 10; + let f = fn(a){ + let a = 1; + let a = a + 1; + a + }; + f(1) + a + "# + .to_string(), + expected: Object::INTEGER(12), + }, VmTestCase { input: r#" let a = 10; @@ -1199,6 +1213,7 @@ mod tests { let a = 10; let f = fn(){ let a = 1; + let a = a + 1; let h = fn(){ let a = 2; a @@ -1208,10 +1223,71 @@ mod tests { f() + a "# .to_string(), - expected: Object::INTEGER(13), + expected: Object::INTEGER(14), + }, + ]; + + run_vm_tests(tests); + } + + #[test] + fn test_while_statements_without_break_or_continue() { + let tests = vec![ + VmTestCase { + input: r#" + let a = 1; + while (a < 100){ + let a = a + 1; + } + a + "# + .to_string(), + expected: Object::INTEGER(100), + }, + VmTestCase { + input: r#" + let a = 1; + while (a < 0){ + let a = 100; + } + a + "# + .to_string(), + expected: Object::INTEGER(1), + }, + VmTestCase { + input: r#" + let a = 1; + while(false){ + let a = 100; + } + a + "# + .to_string(), + expected: Object::INTEGER(1), }, ]; run_vm_tests(tests); } + + #[test] + fn test_while_clean_up() { + // This tests makes sure that a while statement clears the stack correctly (which is + // different from the conditional behavior) + let tests = vec![VmTestCase { + input: r#" + let a = 0; + while (a < 10000){ + let a = a + 1; + puts(a); + } + a + "# + .to_string(), + expected: Object::INTEGER(10000), + }]; + + run_vm_tests(tests); + } } From 99f2265812919fd4df64ad0124c20c8a882384c7 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Mon, 14 Aug 2023 17:01:00 +0200 Subject: [PATCH 5/6] feat: add while loops to the formatter --- src/formatter/formatter_tests.rs | 36 ++++++++++++++++++++++++++++++++ src/formatter/mod.rs | 10 +++++++++ 2 files changed, 46 insertions(+) diff --git a/src/formatter/formatter_tests.rs b/src/formatter/formatter_tests.rs index 088f9e9..2236795 100644 --- a/src/formatter/formatter_tests.rs +++ b/src/formatter/formatter_tests.rs @@ -536,6 +536,42 @@ let a = 10; let expected = r#"puts("Hello, Monkey!"); let arr = [1, 2, 3]; let length = len(arr); +"#; + + assert_eq!(format(input), expected); + } + + #[test] + fn test_while() { + let input = r#" + let a = 1; + while (a<3){ +let a = a + 1, +puts(21); + } + let a = fn (x){ + let a = 1; + while (x > 0){ + let a = a * 2; + } + a + }; + a(12); + "#; + + let expected = r#"let a = 1; +while (a < 3) { + let a = a + 1; + puts(21); +} +let a = fn (x) { + let a = 1; + while (x > 0) { + let a = a * 2; + } + a +}; +a(12); "#; assert_eq!(format(input), expected); diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs index 8a1b794..216508c 100644 --- a/src/formatter/mod.rs +++ b/src/formatter/mod.rs @@ -112,6 +112,16 @@ impl Formatter { self.push(";"); } } + Statement::While(wh) => { + self.push("while ("); + self.visit_expression(&wh.condition); + self.push(") {\n"); + self.indent += 1; + self.visit_block_statement(&wh.body); + self.indent -= 1; + self.push_indent(); + self.push("}"); + } } self.push("\n"); self.last_expression = None; From 01522e81a24bf675f45515bb1ce557273e9e2d86 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Mon, 14 Aug 2023 18:35:37 +0200 Subject: [PATCH 6/6] docs: Update README.md section on loops --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e0953f7..99b30b1 100644 --- a/README.md +++ b/README.md @@ -288,7 +288,15 @@ if (a == 1) { #### Loops -For now loops are not supported. To achieve the same result as a loop, use recursion. In the future loops might be supported. +While loops have been implemented, but for now keywords such as `break` and `continue` have not yet been implemented. + +```monkey +let a = 1; +while (a < 4) { + puts(a); + let a = a + 1; +} +``` ### Comments