From e680c7f52b509a23bfbc174618a3d5faca85a373 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Sat, 12 Aug 2023 17:34:39 +0200 Subject: [PATCH 01/19] 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 02/19] 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 03/19] 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 04/19] 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 05/19] 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 06/19] 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 From 46b91fe014808f55f4e81295c51430dc4775c381 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Wed, 16 Aug 2023 09:54:11 +0200 Subject: [PATCH 07/19] docs: Update installation instructions and split monkey syntax Installation instructions have now been added to the README. The monkey syntax section has now been moved to its own file under the docs directory. --- README.md | 262 ++++--------------------------------------------- docs/MONKEY.md | 248 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 268 insertions(+), 242 deletions(-) create mode 100644 docs/MONKEY.md diff --git a/README.md b/README.md index 99b30b1..2d5d05c 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ There are some issues that I want to fix before I can call this implementation c To start the REPL, run the following command: ```bash -cargo run --release --bin monkey +monkey ``` ### File interpreter @@ -24,7 +24,7 @@ cargo run --release --bin monkey To run a Monkey file, run the following command: ```bash -cargo run --release --bin monkey -- +monkey ``` ### Other modes @@ -32,7 +32,7 @@ cargo run --release --bin monkey -- You can also test the compiler, parser and lexer in the same way, adding the following flag after the path to the file: ```bash ---mode +monkey --mode ``` Where `` can be `compiler`, `parser`, `lexer` or `interpreter`. @@ -40,7 +40,7 @@ Where `` can be `compiler`, `parser`, `lexer` or `interpreter`. Example: ```bash -cargo run --release --bin monkey -- --mode compiler +monkey --mode compiler ``` ### Formatter @@ -49,7 +49,7 @@ A monkey formatter is also available, with the binary `monkeyfmt`. I will format To use it you only need to run the following command: ```bash -cargo run --release --bin monkeyfmt -- +monkeyfmt ``` Adding the `-r` flag after the file name will replace the contents of the file with the @@ -61,254 +61,32 @@ formatted code. If the flag is not activated, the formatted code will be printed To see the help, run the following command: ```bash -cargo run --release --bin monkey -- --help +monkey --help ``` -## Monkey syntax +## Installation -### Types +### Crates.io -The monkey language supports the following types: +`Chimpanzee` is available as a cargo crate, which means that you can install it +by simple using: -- Integers -- Booleans -- Strings -- Arrays -- Hashes -- Functions (yes, functions are a type in Monkey) - -#### Integers - -Integers are 64-bit signed integers. They are written as follows: - -```monkey -let a = 1; -let b = 2; -``` - -##### Operators - -Integers support the following operators: - -- `+`: addition -- `-`: subtraction -- `*`: multiplication -- `/`: division (integer division) -- `==`: equality -- `!=`: inequality -- `<`: less than -- `>`: greater than -- `<=`: less than or equal to -- `>=`: greater than or equal to - -#### Booleans - -Booleans are either `true` or `false`. They are written as follows: - -```monkey -let a = true; -let b = false; -``` - -##### Operators - -Booleans support the following operators: - -- `==`: equality -- `!=`: inequality -- `!`: negation -- `&&`: and -- `||`: or - -#### Strings - -Strings are sequences of characters. They are written as follows: - -```monkey -let a = "Hello, world!"; -``` - -##### String interpolation - -Strings can be interpolated using the `+` operator. The following example shows how to interpolate a string: - -```monkey -let a = "Hello " + "world!"; -``` - -###### Built-in functions - -Strings have the following built-in functions: - -- `len()`: returns the length of the string - -#### Arrays - -Arrays are sequences of values. They are written as follows: - -```monkey -let a = [1, "two", [1,2,3]]; -``` - -They can contain any type of value, including other arrays and functions. - -##### Indexing - -Arrays can be indexed using the `[]` operator. The index must be an integer. The index starts at 0. The following example shows how to index an array: - -```monkey -let a = [1,2,3]; -let b = a[0]; // b = 1 -``` - -##### Built-in functions - -Arrays have the following built-in functions: - -- `len(array)`: returns the length of the array -- `first(array)`: returns the first element of the array -- `last(array)`: returns the last element of the array -- `rest(array)`: returns a new array containing all elements except the first -- `push(array, value)`: returns a new array containing all elements of the original array and the new value (at the end) - -#### Hashes - -Hashes are key-value pairs. They are written as follows: - -```monkey -let a = {"one": 1, "two": 2}; -``` - -The keys can be: `Integer` , `Boolean` or `String`. The values can be any type of value, including other hashes and functions. - -##### Indexing - -Hashes can be indexed using the `[]` operator. The index must be a key. The following example shows how to index a hash: - -```monkey -let a = {"one": 1, "two": 2}; -let b = a["one"]; // b = 1 -``` - -##### Built-in functions - -For now hashes have no built-in functions. In the future the following built-in functions will be supported: - -- `keys(hash)`: returns an array containing all keys of the hash -- `values(hash)`: returns an array containing all values of the hash -- `add(hash, key, value)`: returns a new hash containing all key-value pairs of the original hash and the new key-value pair - -#### Functions - -The function syntax is as follows: - -```monkey -let add = fn(a, b) { - return a + b; -}; -``` - -Functions are first-class citizens in Monkey. This means that they can be assigned to variables, passed as arguments to other functions, and returned from other functions. -One example is the map function: - -```monkey -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); -``` - -#### Return - -Functions can return a value using the `return` keyword. The following example shows how to return a value from a function: - -```monkey -let add = fn(a, b) { - return a + b; -}; -``` - -Note that the `return` keyword is optional, Monkey allows implicit returns. The following example shows how to use an implicit return: - -```monkey -let add = fn(a, b) { - a + b; -}; -``` - -### Variables - -Variables are declared using the `let` keyword. The following example shows how to declare a variable: - -```monkey -let a = 1; -``` - -Shadowing is supported. The following example shows how to shadow a variable: - -```monkey -let a = 1; -let a = 2; -``` - -### Control flow - -#### If-else - -The if-else syntax is as follows: - -```monkey - -if (condition) { - // code -} else { - // code -} -``` - -The following example shows how to use if-else: - -```monkey -let a = 1; -if (a == 1) { - return "a is 1"; -} else { - return "a is not 1"; -} +```bash +cargo install chimpanzee ``` -#### Loops +### From source -While loops have been implemented, but for now keywords such as `break` and `continue` have not yet been implemented. +To install it from source you bust clone the repo. Once you have clone it you build the project -```monkey -let a = 1; -while (a < 4) { - puts(a); - let a = a + 1; -} +```bash +cargo build --release ``` -### Comments - -For now comments are not supported ( not a huge loss :) ) +> This step can take some time, the expected time is less that 2 minutes, but it can be even longer. -### Built-in functions +In the directory `target/directory` the two executables will be now available: `monkey` and `monkeyfmt`. -Monkey has the following built-in functions: +## Monkey language -- `puts(value)`: prints the value to the console -- `len(value)` -- `first(array)` -- `last(array)` -- `rest(array)` -- `push(array, value)` +Information about the monkey language is available in the MONKEY file. diff --git a/docs/MONKEY.md b/docs/MONKEY.md new file mode 100644 index 0000000..2b65419 --- /dev/null +++ b/docs/MONKEY.md @@ -0,0 +1,248 @@ +# Monkey syntax + +## Types + +The monkey language supports the following types: + +- Integers +- Booleans +- Strings +- Arrays +- Hashes +- Functions (yes, functions are a type in Monkey) + +### Integers + +Integers are 64-bit signed integers. They are written as follows: + +```monkey +let a = 1; +let b = 2; +``` + +#### Operators + +Integers support the following operators: + +- `+`: addition +- `-`: subtraction +- `*`: multiplication +- `/`: division (integer division) +- `==`: equality +- `!=`: inequality +- `<`: less than +- `>`: greater than +- `<=`: less than or equal to +- `>=`: greater than or equal to + +### Booleans + +Booleans are either `true` or `false`. They are written as follows: + +```monkey +let a = true; +let b = false; +``` + +#### Operators + +Booleans support the following operators: + +- `==`: equality +- `!=`: inequality +- `!`: negation +- `&&`: and +- `||`: or + +### Strings + +Strings are sequences of characters. They are written as follows: + +```monkey +let a = "Hello, world!"; +``` + +#### String interpolation + +Strings can be interpolated using the `+` operator. The following example shows how to interpolate a string: + +```monkey +let a = "Hello " + "world!"; +``` + +##### Built-in functions + +Strings have the following built-in functions: + +- `len()`: returns the length of the string + +### Arrays + +Arrays are sequences of values. They are written as follows: + +```monkey +let a = [1, "two", [1,2,3]]; +``` + +They can contain any type of value, including other arrays and functions. + +#### Indexing + +Arrays can be indexed using the `[]` operator. The index must be an integer. The index starts at 0. The following example shows how to index an array: + +```monkey +let a = [1,2,3]; +let b = a[0]; // b = 1 +``` + +#### Built-in functions + +Arrays have the following built-in functions: + +- `len(array)`: returns the length of the array +- `first(array)`: returns the first element of the array +- `last(array)`: returns the last element of the array +- `rest(array)`: returns a new array containing all elements except the first +- `push(array, value)`: returns a new array containing all elements of the original array and the new value (at the end) + +### Hashes + +Hashes are key-value pairs. They are written as follows: + +```monkey +let a = {"one": 1, "two": 2}; +``` + +The keys can be: `Integer` , `Boolean` or `String`. The values can be any type of value, including other hashes and functions. + +#### Indexing + +Hashes can be indexed using the `[]` operator. The index must be a key. The following example shows how to index a hash: + +```monkey +let a = {"one": 1, "two": 2}; +let b = a["one"]; // b = 1 +``` + +#### Built-in functions + +For now hashes have no built-in functions. In the future the following built-in functions will be supported: + +- `keys(hash)`: returns an array containing all keys of the hash +- `values(hash)`: returns an array containing all values of the hash +- `add(hash, key, value)`: returns a new hash containing all key-value pairs of the original hash and the new key-value pair + +### Functions + +The function syntax is as follows: + +```monkey +let add = fn(a, b) { + return a + b; +}; +``` + +Functions are first-class citizens in Monkey. This means that they can be assigned to variables, passed as arguments to other functions, and returned from other functions. +One example is the map function: + +```monkey +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); +``` + +### Return + +Functions can return a value using the `return` keyword. The following example shows how to return a value from a function: + +```monkey +let add = fn(a, b) { + return a + b; +}; +``` + +Note that the `return` keyword is optional, Monkey allows implicit returns. The following example shows how to use an implicit return: + +```monkey +let add = fn(a, b) { + a + b; +}; +``` + +## Variables + +Variables are declared using the `let` keyword. The following example shows how to declare a variable: + +```monkey +let a = 1; +``` + +Shadowing is supported. The following example shows how to shadow a variable: + +```monkey +let a = 1; +let a = 2; +``` + +## Control flow + +### If-else + +The if-else syntax is as follows: + +```monkey + +if (condition) { + // code +} else { + // code +} +``` + +The following example shows how to use if-else: + +```monkey +let a = 1; +if (a == 1) { + return "a is 1"; +} else { + return "a is not 1"; +} +``` + +### Loops + +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 + +For now comments are not supported ( not a huge loss :) ) + +## Built-in functions + +Monkey has the following built-in functions: + +- `puts(value)`: prints the value to the console +- `len(value)` +- `first(array)` +- `last(array)` +- `rest(array)` +- `push(array, value)` From 2d738854c5a24754d4b92d8485fe5579c1447a2d Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Fri, 18 Aug 2023 17:35:53 +0200 Subject: [PATCH 08/19] docs: Add link to the monkey language docs in the README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d5d05c..2ef2401 100644 --- a/README.md +++ b/README.md @@ -89,4 +89,4 @@ In the directory `target/directory` the two executables will be now available: ` ## Monkey language -Information about the monkey language is available in the MONKEY file. +Information about the monkey language is available in the [MONKEY file](docs/MONKEY.md). From 66ab70ca4f8b6c8579f97dadf181604f6db83f13 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Sun, 20 Aug 2023 10:48:33 +0200 Subject: [PATCH 09/19] feat: break and continue tokens --- src/lexer/mod.rs | 10 ++++++++++ src/lexer/token.rs | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index 9501116..f1f5d43 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -111,6 +111,8 @@ impl Lexer { "else" => Token::Else, "return" => Token::Return, "while" => Token::While, + "break" => Token::Break, + "continue" => Token::Continue, _ => Token::Ident(ident_string), }; } @@ -225,6 +227,9 @@ mod tests { while (true) { return false; } + + break; + continue; "#; let mut lexer = Lexer::new(input); @@ -351,6 +356,11 @@ mod tests { Token::Semicolon, Token::RSquirly, // + Token::Break, + Token::Semicolon, + Token::Continue, + Token::Semicolon, + // Token::Eof, ]; diff --git a/src/lexer/token.rs b/src/lexer/token.rs index 37aac15..358c325 100644 --- a/src/lexer/token.rs +++ b/src/lexer/token.rs @@ -49,6 +49,8 @@ pub enum Token { Else, Return, While, + Break, + Continue } impl Display for Token { @@ -88,6 +90,8 @@ impl Display for Token { Token::Else => write!(f, "else"), Token::Return => write!(f, "return"), Token::While => write!(f, "while"), + Token::Break => write!(f, "break"), + Token::Continue => write!(f, "continue") } } } From acb1e5162acad773f52334ffdc1c031eb8e79780 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Mon, 21 Aug 2023 00:56:23 +0200 Subject: [PATCH 10/19] feat: add break and continue expressions to the parser --- src/compiler/mod.rs | 1 + src/formatter/mod.rs | 1 + src/interpreter/evaluator.rs | 1 + src/parser/ast.rs | 34 +++++++++++++++++++ src/parser/parser_tests.rs | 64 ++++++++++++++++++++++++++++++++++-- 5 files changed, 99 insertions(+), 2 deletions(-) diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index d084c40..641e1c6 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -237,6 +237,7 @@ impl Compiler { self.emit(Opcode::Call, vec![args_length]); } + _ => unimplemented!(), } Ok(()) diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs index 216508c..48bd6c8 100644 --- a/src/formatter/mod.rs +++ b/src/formatter/mod.rs @@ -228,6 +228,7 @@ impl Formatter { self.visit_expression(&index.index); self.push("]"); } + _ => {} } self.last_expression = Some(exp.clone()); diff --git a/src/interpreter/evaluator.rs b/src/interpreter/evaluator.rs index 426ccc2..bb510a5 100644 --- a/src/interpreter/evaluator.rs +++ b/src/interpreter/evaluator.rs @@ -139,6 +139,7 @@ impl Evaluator { self.eval_index_expression(index_expression) } Expression::HashMapLiteral(hashmap) => self.eval_hashmap_literal(hashmap), + _ => unimplemented!(), } } diff --git a/src/parser/ast.rs b/src/parser/ast.rs index cedd85c..be98bef 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -28,6 +28,7 @@ pub enum Expression { ArrayLiteral(ArrayLiteral), HashMapLiteral(HashMapLiteral), IndexExpression(IndexExpression), + ControlFlow(ControlFlow), } impl Display for Expression { @@ -43,6 +44,7 @@ impl Display for Expression { Expression::ArrayLiteral(x) => write!(f, "{x}"), Expression::IndexExpression(x) => write!(f, "{x}"), Expression::HashMapLiteral(x) => write!(f, "{x}"), + Expression::ControlFlow(x) => write!(f, "{x}"), } } } @@ -60,6 +62,10 @@ impl Expression { Token::Function => FunctionLiteral::parse(parser).map(Expression::FunctionLiteral), Token::LSquare => ArrayLiteral::parse(parser).map(Expression::ArrayLiteral), Token::LSquirly => HashMapLiteral::parse(parser).map(Expression::HashMapLiteral), + Token::Break | Token::Continue => { + ControlFlow::parse(parser).map(Expression::ControlFlow) + } + _ => Err(format!( "There is no prefix parser for the token {}", parser.current_token @@ -586,6 +592,34 @@ impl HashMapLiteral { } } +#[derive(PartialEq, Debug, Clone)] +pub enum ControlFlow { + Break, + Continue, +} + +impl Display for ControlFlow { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ControlFlow::Break => write!(f, "break;"), + ControlFlow::Continue => write!(f, "continue;"), + } + } +} + +impl ControlFlow { + fn parse(parser: &mut Parser) -> Result { + match parser.current_token { + Token::Break => Ok(Self::Break), + Token::Continue => Ok(Self::Continue), + _ => Err(format!( + "Expected a control flow keyword (break, continue), got {}", + parser.current_token + )), + } + } +} + #[derive(PartialEq, PartialOrd, Clone, Copy)] pub enum Precedence { Lowest = 0, diff --git a/src/parser/parser_tests.rs b/src/parser/parser_tests.rs index 9bf2ff9..3f256c9 100644 --- a/src/parser/parser_tests.rs +++ b/src/parser/parser_tests.rs @@ -5,8 +5,9 @@ mod tests { lexer::{token::Token, Lexer}, parser::{ ast::{ - BlockStatement, Expression, FunctionCall, Identifier, InfixOperator, LetStatement, - Primitive, Program, ReturnStatement, Statement, WhileStatement, + BlockStatement, Conditional, ControlFlow, Expression, FunctionCall, Identifier, + InfixOperator, LetStatement, Primitive, Program, ReturnStatement, Statement, + WhileStatement, }, Parser, }, @@ -631,6 +632,65 @@ mod tests { } } + #[test] + fn test_parse_while_control_flow_statements() { + let input = "while(x < 3){ + if (x == 2){ + break; + } else { + continue; + } + }"; + + 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::Expression(Expression::Conditional( + Conditional { + condition: Box::new(Expression::Infix(InfixOperator { + token: Token::Equal, + left: Box::new(Expression::Identifier(Identifier { + token: Token::Ident("x".to_string()), + value: "x".to_string(), + })), + right: Box::new(Expression::Primitive(Primitive::IntegerLiteral(2))), + })), + consequence: BlockStatement { + statements: vec![Statement::Expression(Expression::ControlFlow( + ControlFlow::Break, + ))], + }, + alternative: Some(BlockStatement { + statements: vec![Statement::Expression(Expression::ControlFlow( + ControlFlow::Continue, + ))], + }), + }, + ))], + }, + }; + + println!("Input:\n{input}"); + let program = generate_program(input); + println!("Parsed:\n{program}"); + + 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); From 1c6445d96a12444cc1f4e304ccaf6bc00300cba5 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Fri, 25 Aug 2023 22:22:56 +0200 Subject: [PATCH 11/19] refactor: change control flow to a statement and not an expression --- src/formatter/mod.rs | 5 ++++- src/interpreter/evaluator.rs | 6 +++++- src/interpreter/evaluator_tests.rs | 2 +- src/parser/ast.rs | 9 +++------ src/parser/mod.rs | 11 ++++++++++- src/parser/parser_tests.rs | 8 ++------ 6 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs index 48bd6c8..56f9363 100644 --- a/src/formatter/mod.rs +++ b/src/formatter/mod.rs @@ -122,6 +122,10 @@ impl Formatter { self.push_indent(); self.push("}"); } + Statement::ControlFlow(cf) => { + self.push(cf.to_string().as_str()); + self.push(";"); + } } self.push("\n"); self.last_expression = None; @@ -228,7 +232,6 @@ impl Formatter { self.visit_expression(&index.index); self.push("]"); } - _ => {} } self.last_expression = Some(exp.clone()); diff --git a/src/interpreter/evaluator.rs b/src/interpreter/evaluator.rs index bb510a5..43fb2a0 100644 --- a/src/interpreter/evaluator.rs +++ b/src/interpreter/evaluator.rs @@ -84,6 +84,9 @@ impl Evaluator { } result } + + _ => unimplemented!(), // I have decided not to implement the rest of the expressions, + // I will focus on the compiler } } @@ -139,7 +142,8 @@ impl Evaluator { self.eval_index_expression(index_expression) } Expression::HashMapLiteral(hashmap) => self.eval_hashmap_literal(hashmap), - _ => unimplemented!(), + _ => unimplemented!(), // I have decided not to implement the rest of the expressions, + // I will focus on the compiler } } diff --git a/src/interpreter/evaluator_tests.rs b/src/interpreter/evaluator_tests.rs index 1fe55df..b9f5b27 100644 --- a/src/interpreter/evaluator_tests.rs +++ b/src/interpreter/evaluator_tests.rs @@ -520,7 +520,7 @@ mod tests { fn test_integer_object(object: Object, expected: i64) { match object { Object::INTEGER(x) => assert_eq!(x, expected), - _ => panic!("The object is not an integer"), + x => panic!("The object is not an integer, it is {:#?}", x), } } diff --git a/src/parser/ast.rs b/src/parser/ast.rs index be98bef..9bd2779 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -28,7 +28,6 @@ pub enum Expression { ArrayLiteral(ArrayLiteral), HashMapLiteral(HashMapLiteral), IndexExpression(IndexExpression), - ControlFlow(ControlFlow), } impl Display for Expression { @@ -44,7 +43,6 @@ impl Display for Expression { Expression::ArrayLiteral(x) => write!(f, "{x}"), Expression::IndexExpression(x) => write!(f, "{x}"), Expression::HashMapLiteral(x) => write!(f, "{x}"), - Expression::ControlFlow(x) => write!(f, "{x}"), } } } @@ -62,9 +60,6 @@ impl Expression { Token::Function => FunctionLiteral::parse(parser).map(Expression::FunctionLiteral), Token::LSquare => ArrayLiteral::parse(parser).map(Expression::ArrayLiteral), Token::LSquirly => HashMapLiteral::parse(parser).map(Expression::HashMapLiteral), - Token::Break | Token::Continue => { - ControlFlow::parse(parser).map(Expression::ControlFlow) - } _ => Err(format!( "There is no prefix parser for the token {}", @@ -413,6 +408,7 @@ pub enum Statement { Return(ReturnStatement), Expression(Expression), While(WhileStatement), + ControlFlow(ControlFlow), } impl Display for Statement { @@ -422,6 +418,7 @@ impl Display for Statement { Statement::Return(statement) => write!(f, "{statement}"), Statement::Expression(expression) => write!(f, "{expression}"), Statement::While(statement) => write!(f, "{statement}"), + Statement::ControlFlow(statement) => write!(f, "{statement}"), } } } @@ -608,7 +605,7 @@ impl Display for ControlFlow { } impl ControlFlow { - fn parse(parser: &mut Parser) -> Result { + pub fn parse(parser: &mut Parser) -> Result { match parser.current_token { Token::Break => Ok(Self::Break), Token::Continue => Ok(Self::Continue), diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 488ef0f..163d8ec 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -10,7 +10,7 @@ use crate::{ }; use self::{ - ast::{BlockStatement, WhileStatement}, + ast::{BlockStatement, ControlFlow, WhileStatement}, parser_errors::ParserErrors, }; @@ -62,6 +62,9 @@ impl Parser { 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), + Token::Break | Token::Continue => self + .parse_control_flow_statement() + .map(Statement::ControlFlow), _ => self.parse_expression_statement().map(Statement::Expression), } } @@ -142,6 +145,12 @@ impl Parser { Some(WhileStatement { condition, body }) } + fn parse_control_flow_statement(&mut self) -> Option { + let ctrlflow = ControlFlow::parse(self).ok(); + self.next_token(); + ctrlflow + } + fn parse_expression_statement(&mut self) -> Option { let expression = Expression::parse(self, Precedence::Lowest); if self.peek_token_is(&Token::Semicolon) { diff --git a/src/parser/parser_tests.rs b/src/parser/parser_tests.rs index 3f256c9..9b69d12 100644 --- a/src/parser/parser_tests.rs +++ b/src/parser/parser_tests.rs @@ -663,14 +663,10 @@ mod tests { right: Box::new(Expression::Primitive(Primitive::IntegerLiteral(2))), })), consequence: BlockStatement { - statements: vec![Statement::Expression(Expression::ControlFlow( - ControlFlow::Break, - ))], + statements: vec![Statement::ControlFlow(ControlFlow::Break)], }, alternative: Some(BlockStatement { - statements: vec![Statement::Expression(Expression::ControlFlow( - ControlFlow::Continue, - ))], + statements: vec![Statement::ControlFlow(ControlFlow::Continue)], }), }, ))], From 9c5a0326aa0d098ada57e70f7997658ca5c4f8fc Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Fri, 25 Aug 2023 22:23:39 +0200 Subject: [PATCH 12/19] feat: add break + continue to the compiler --- src/compiler/compiler_tests.rs | 141 +++++++++++++++++++++-- src/compiler/mod.rs | 201 ++++++++++++++++++++++++--------- 2 files changed, 280 insertions(+), 62 deletions(-) diff --git a/src/compiler/compiler_tests.rs b/src/compiler/compiler_tests.rs index dfab1a2..902776c 100644 --- a/src/compiler/compiler_tests.rs +++ b/src/compiler/compiler_tests.rs @@ -1191,17 +1191,6 @@ pub mod 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){ @@ -1224,4 +1213,134 @@ pub mod tests { run_compiler(tests); } + + #[test] + fn test_break_in_while() { + let tests = vec![CompilerTestCase { + input: r#" + while (true){ + break; + } + "# + .to_string(), + expected_constants: vec![], + expected_instructions: flatten_instructions(vec![ + Opcode::True.make(vec![]), // 000 + Opcode::JumpNotTruthy.make(vec![10]), // 001 + Opcode::Jump.make(vec![10]), // 004 + Opcode::Jump.make(vec![0]), // 007 + // 010 + ]), + }]; + + run_compiler(tests); + } + + #[test] + fn test_nested_breaks_in_while() { + let tests = vec![CompilerTestCase { + input: r#" + while (true){ + while (true){ + break; + } + break; + } + "# + .to_string(), + expected_constants: vec![], + expected_instructions: flatten_instructions(vec![ + Opcode::True.make(vec![]), // 000 + Opcode::JumpNotTruthy.make(vec![20]), // 001 + Opcode::True.make(vec![]), // 004 + Opcode::JumpNotTruthy.make(vec![14]), // 005 + Opcode::Jump.make(vec![14]), // 008 + Opcode::Jump.make(vec![4]), // 011 + Opcode::Jump.make(vec![20]), // 014 + Opcode::Jump.make(vec![0]), // 017 + // 020 + ]), + }]; + + run_compiler(tests); + } + #[test] + fn test_continue_in_while() { + let tests = vec![CompilerTestCase { + input: r#" + while (true){ + continue; + } + "# + .to_string(), + expected_constants: vec![], + expected_instructions: flatten_instructions(vec![ + Opcode::True.make(vec![]), // 000 + Opcode::JumpNotTruthy.make(vec![10]), // 001 + Opcode::Jump.make(vec![0]), // 004 + Opcode::Jump.make(vec![0]), // 007 + // 010 + ]), + }]; + + run_compiler(tests); + } + + #[test] + fn test_nested_continue_in_while() { + let tests = vec![CompilerTestCase { + input: r#" + while (true){ + while (true){ + continue; + } + continue; + } + "# + .to_string(), + expected_constants: vec![], + expected_instructions: flatten_instructions(vec![ + Opcode::True.make(vec![]), // 000 + Opcode::JumpNotTruthy.make(vec![20]), // 001 + Opcode::True.make(vec![]), // 004 + Opcode::JumpNotTruthy.make(vec![14]), // 005 + Opcode::Jump.make(vec![4]), // 008 + Opcode::Jump.make(vec![4]), // 011 + Opcode::Jump.make(vec![0]), // 014 + Opcode::Jump.make(vec![0]), // 017 + // 020 + ]), + }]; + + run_compiler(tests); + } + + #[test] + fn test_continue_and_break_in_while() { + let tests = vec![CompilerTestCase { + input: r#" + while (true){ + while (true){ + continue; + } + break; + } + "# + .to_string(), + expected_constants: vec![], + expected_instructions: flatten_instructions(vec![ + Opcode::True.make(vec![]), // 000 + Opcode::JumpNotTruthy.make(vec![20]), // 001 + Opcode::True.make(vec![]), // 004 + Opcode::JumpNotTruthy.make(vec![14]), // 005 + Opcode::Jump.make(vec![4]), // 008 + Opcode::Jump.make(vec![4]), // 011 + Opcode::Jump.make(vec![20]), // 014 + Opcode::Jump.make(vec![0]), // 017 + // 020 + ]), + }]; + + run_compiler(tests); + } } diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 641e1c6..4d96145 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -16,8 +16,8 @@ use crate::{ {CompiledFunction, Object}, }, parser::ast::{ - BlockStatement, Conditional, Expression, FunctionLiteral, InfixOperator, Primitive, - Program, Statement, WhileStatement, + BlockStatement, Conditional, ControlFlow, Expression, FunctionLiteral, InfixOperator, + LetStatement, Primitive, Program, Statement, WhileStatement, }, }; @@ -34,6 +34,7 @@ struct CompilerScope { instructions: Instructions, last_instruction: Option, previous_instruction: Option, + loop_scope: Option>>, } impl Default for CompilerScope { @@ -48,8 +49,51 @@ impl CompilerScope { instructions: Instructions::default(), last_instruction: None, previous_instruction: None, + loop_scope: None, } } + + fn enter_loop_scope(&mut self, start_position: usize) { + let loop_scope = LoopScope::new_enclosed(self.loop_scope.clone(), start_position); + self.loop_scope = Some(Rc::new(RefCell::new(loop_scope))); + } + + fn leave_loop_scope(&mut self) -> Option>> { + let outer = self.loop_scope.clone(); + self.loop_scope = self + .loop_scope + .clone() + .unwrap() + .as_ref() + .borrow() + .outer + .clone(); + outer + } +} + +struct LoopScope { + outer: Option>>, + start_position: usize, + breaks: Vec, +} + +impl LoopScope { + pub fn new_enclosed(outer: Option>>, start_position: usize) -> Self { + Self { + outer, + start_position, + breaks: vec![], + } + } + + pub fn add_break(&mut self, pos: usize) { + self.breaks.push(pos); + } + + pub fn breaks(&self) -> Vec { + self.breaks.clone() + } } pub struct Compiler { @@ -115,54 +159,7 @@ impl Compiler { self.emit(Opcode::Pop, vec![]); } Statement::Let(s) => { - // This step is extremely important. If it is not done then when shadowing variables - // and using the previous value we get an error. Because we would have assigned - // a new index to the symbol and the GetGlobal instruction would get a NULL - // value instead of the previous value. (corresponds to issue #8) - let symbol = match self.symbol_table.resolve(&s.name.value) { - Some(symbol) => match symbol.scope { - SymbolScope::Global => { - // A Local variable should never replace a global one - if self.symbol_table.has_outer() { - // This means that the symbol will - // be local and not global, and thus not - // replace the global one - self.symbol_table.define(s.name.value) - } else { - symbol - } - } - SymbolScope::Local => symbol, - - // We only want to do in in the case of "normal" variable assignation. - // The special cases should not be touched, since the program should not - // have access to them, only the compiler/vm - _ => self.symbol_table.define(s.name.value), - }, - None => self.symbol_table.define(s.name.value), - }; - - self.compile_expression(s.value)?; - - match symbol.scope { - SymbolScope::Global => { - self.emit(Opcode::SetGlobal, vec![symbol.index as i32]); - } - SymbolScope::Local => { - self.emit(Opcode::SetLocal, vec![symbol.index as i32]); - } - SymbolScope::Free => { - unreachable!( - "Free symbols should not be set, the compiler should panic before this" - ) - } - SymbolScope::Builtin => { - unreachable!("Builtin symbols should not be set, the compiler should panic before this") - } - SymbolScope::Function => { - unreachable!("Function symbols should not be set, the compiler should panic before this") - } - } + self.compiler_let_statement(s)?; } Statement::Return(r) => { self.compile_expression(r.return_value)?; @@ -171,6 +168,65 @@ impl Compiler { Statement::While(wh) => { self.compile_while_statement(wh)?; } + + Statement::ControlFlow(ctrflow) => self.compile_control_flow_statement(ctrflow)?, + } + + Ok(()) + } + + fn compiler_let_statement(&mut self, s: LetStatement) -> Result<(), String> { + // This step is extremely important. If it is not done then when shadowing variables + // and using the previous value we get an error. Because we would have assigned + // a new index to the symbol and the GetGlobal instruction would get a NULL + // value instead of the previous value. (corresponds to issue #8) + let symbol = match self.symbol_table.resolve(&s.name.value) { + Some(symbol) => match symbol.scope { + SymbolScope::Global => { + // A Local variable should never replace a global one + if self.symbol_table.has_outer() { + // This means that the symbol will + // be local and not global, and thus not + // replace the global one + self.symbol_table.define(s.name.value) + } else { + symbol + } + } + SymbolScope::Local => symbol, + + // We only want to do in in the case of "normal" variable assignation. + // The special cases should not be touched, since the program should not + // have access to them, only the compiler/vm + _ => self.symbol_table.define(s.name.value), + }, + None => self.symbol_table.define(s.name.value), + }; + + self.compile_expression(s.value)?; + + match symbol.scope { + SymbolScope::Global => { + self.emit(Opcode::SetGlobal, vec![symbol.index as i32]); + } + SymbolScope::Local => { + self.emit(Opcode::SetLocal, vec![symbol.index as i32]); + } + SymbolScope::Free => { + unreachable!( + "Free symbols should not be set, the compiler should panic before this" + ) + } + SymbolScope::Builtin => { + unreachable!( + "Builtin symbols should not be set, the compiler should panic before this" + ) + } + SymbolScope::Function => { + unreachable!( + "Function symbols should not be set, the compiler should panic before this" + ) + } } Ok(()) @@ -237,7 +293,6 @@ impl Compiler { self.emit(Opcode::Call, vec![args_length]); } - _ => unimplemented!(), } Ok(()) @@ -386,6 +441,8 @@ impl Compiler { fn compile_while_statement(&mut self, wh: WhileStatement) -> Result<(), String> { let condition_pos = self.current_instructions().data.len(); + self.scopes[self.scope_index].enter_loop_scope(condition_pos); + 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 @@ -398,6 +455,48 @@ impl Compiler { let after_body_pos = self.current_instructions().data.len(); self.change_operand(jump_not_truthy_pos, after_body_pos as i32)?; + for break_pos in self.scopes[self.scope_index] + .loop_scope + .clone() // TODO: Improve this + .unwrap() + .as_ref() + .borrow() + .breaks() + { + self.change_operand(break_pos, after_body_pos as i32)?; + } + + self.scopes[self.scope_index].leave_loop_scope(); + + Ok(()) + } + + fn compile_control_flow_statement(&mut self, ctrflow: ControlFlow) -> Result<(), String> { + match ctrflow { + ControlFlow::Break => { + let pos = self.emit(Opcode::Jump, vec![9999]); // We emit a dummy value for the jump offset + // and we will fix it later + self.scopes[self.scope_index] + .loop_scope + .clone() + .unwrap() + .as_ref() + .borrow_mut() + .add_break(pos); + } + ControlFlow::Continue => { + let while_initial_pos = self.scopes[self.scope_index] + .loop_scope + .as_ref() + .unwrap() + .borrow() + .start_position + .clone(); + + self.emit(Opcode::Jump, vec![while_initial_pos as i32]); + } + } + Ok(()) } From 05c66f94ba48c02b853f27ab7c31bd276ad07d9b Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Fri, 25 Aug 2023 22:44:23 +0200 Subject: [PATCH 13/19] tests: add test for break/continue in vm --- src/vm/vm_tests.rs | 235 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) diff --git a/src/vm/vm_tests.rs b/src/vm/vm_tests.rs index 498198b..cefc1b0 100644 --- a/src/vm/vm_tests.rs +++ b/src/vm/vm_tests.rs @@ -1290,4 +1290,239 @@ mod tests { run_vm_tests(tests); } + + #[test] + fn test_break_from_while() { + let tests = vec![ + VmTestCase { + input: r#" + let a = 0; + while (a < 10) { + if (a == 5) { + break; + } + let a = a + 1; + }; + a"# + .to_string(), + expected: Object::INTEGER(5), + }, + VmTestCase { + input: r#" + let a = 0; + let c = 0; + while (a < 10) { + let b = 0; + while (b < 10) { + if (b == 5) { + break; + } + let b = b + 1; + let c = c + 1; + } + let a = a + 1; + }; + c"# + .to_string(), + expected: Object::INTEGER(50), + }, + VmTestCase { + input: r#" + let a = 0; + let c = 0; + while (a < 10) { + if (a == 5) { + break; + } + + let b = 0; + while (b < 10) { + if (b == 5) { + break; + } + let b = b + 1; + let c = c + 1; + } + let a = a + 1; + }; + c"# + .to_string(), + expected: Object::INTEGER(25), + }, + // The next tests will take care of the possible interference between the break and a function + VmTestCase { + input: r#" + let f = fn (a) { + let c = 0; + while (a < 10) { + if (a == 5) { + break; + } + + let b = 0; + while (b < 10) { + if (b == 5) { + break; + } + let b = b + 1; + let c = c + 1; + } + let a = a + 1; + } + c + }; + f(0)"# + .to_string(), + expected: Object::INTEGER(25), + }, + VmTestCase { + input: r#" + let a = 0; + let c = 0; + while (a < 10) { + if (a == 5) { + break; + } + + let f = fn () { + let c = 0; + let b = 0; + while (b < 10) { + if (b == 5) { + break; + } + let b = b + 1; + let c = c + 1; + } + c + } + + let a = a + 1; + let c = c + f(); + }; + c"# + .to_string(), + expected: Object::INTEGER(25), + }, + ]; + + run_vm_tests(tests); + } + + #[test] + fn test_continue_from_while() { + let tests = vec![ + VmTestCase { + input: r#" + let a = 0; + let c = 0; + while (a < 10) { + let a = a + 1; + if (a == 5) { + let c = c + 2; + continue; + } + let c = c + 1; + }; + c"# + .to_string(), + expected: Object::INTEGER(11), + }, + VmTestCase { + input: r#" + let a = 0; + let c = 0; + while (a < 10) { + let b = 0; + while (b < 10) { + let b = b + 1; + if (b == 5) { + let c = c + 3; + continue; + } + let c = c + 1; + } + let a = a + 1; + }; + c"# + .to_string(), + expected: Object::INTEGER(120), + }, + // The next tests will take care of the possible interference between the continue and a function + VmTestCase { + input: r#" + let f = fn (a) { + let c = 0; + while (a < 10) { + let b = 0; + while (b < 10) { + let b = b + 1; + if (b == 5) { + let c = c + 3; + continue; + } + let c = c + 1; + } + let a = a + 1; + } + c + }; + f(0)"# + .to_string(), + expected: Object::INTEGER(120), + }, + VmTestCase { + input: r#" + let a = 0; + let c = 0; + while (a < 10) { + let f = fn () { + let c = 0; + let b = 0; + while (b < 10) { + let b = b + 1; + if (b == 5) { + let c = c + 3; + continue; + } + let c = c + 1; + } + c + } + + let a = a + 1; + let c = c + f(); + }; + c"# + .to_string(), + expected: Object::INTEGER(120), + }, + ]; + + run_vm_tests(tests); + } + + #[test] + fn test_continue_and_break_in_while() { + let tests = vec![VmTestCase { + input: r#" + let a = 0; + let c = 0; + while (a < 10) { + let a = a + 1; + if (a == 5) { + let c = c + 3; + continue; + } + if (a == 7) { + break; + } + let c = c + 1; + } + c"# + .to_string(), + expected: Object::INTEGER(8), + }]; + run_vm_tests(tests); + } } From 346e74d682ac55e2841ca7edc01964503c1ad6dd Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Fri, 25 Aug 2023 23:40:01 +0200 Subject: [PATCH 14/19] chore: lint compiler and evaluator --- src/compiler/mod.rs | 9 +++------ src/interpreter/evaluator.rs | 2 ++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 4d96145..59b9352 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -169,7 +169,7 @@ impl Compiler { self.compile_while_statement(wh)?; } - Statement::ControlFlow(ctrflow) => self.compile_control_flow_statement(ctrflow)?, + Statement::ControlFlow(ctrflow) => self.compile_control_flow_statement(&ctrflow), } Ok(()) @@ -471,7 +471,7 @@ impl Compiler { Ok(()) } - fn compile_control_flow_statement(&mut self, ctrflow: ControlFlow) -> Result<(), String> { + fn compile_control_flow_statement(&mut self, ctrflow: &ControlFlow) { match ctrflow { ControlFlow::Break => { let pos = self.emit(Opcode::Jump, vec![9999]); // We emit a dummy value for the jump offset @@ -490,14 +490,11 @@ impl Compiler { .as_ref() .unwrap() .borrow() - .start_position - .clone(); + .start_position; self.emit(Opcode::Jump, vec![while_initial_pos as i32]); } } - - Ok(()) } fn last_instruction_is(&self, opcode: Opcode) -> bool { diff --git a/src/interpreter/evaluator.rs b/src/interpreter/evaluator.rs index 43fb2a0..530f340 100644 --- a/src/interpreter/evaluator.rs +++ b/src/interpreter/evaluator.rs @@ -55,6 +55,7 @@ impl Evaluator { result } + #[allow(clippy::match_wildcard_for_single_variants, unreachable_patterns)] fn eval_statement(&mut self, statement: Statement) -> Object { match statement { Statement::Expression(x) => self.eval_expression(x), @@ -90,6 +91,7 @@ impl Evaluator { } } + #[allow(clippy::match_wildcard_for_single_variants, unreachable_patterns)] fn eval_expression(&mut self, expression: Expression) -> Object { match expression { Expression::Primitive(x) => Self::eval_primitive_expression(x), From ad3868735f12df997c6e4372efbc24d4f60ec5e7 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Tue, 29 Aug 2023 18:04:32 +0200 Subject: [PATCH 15/19] docs: Update MONKEY.md with break and continue keywords --- docs/MONKEY.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/MONKEY.md b/docs/MONKEY.md index 2b65419..288e436 100644 --- a/docs/MONKEY.md +++ b/docs/MONKEY.md @@ -222,7 +222,7 @@ if (a == 1) { ### Loops -While loops have been implemented, but for now keywords such as `break` and `continue` have not yet been implemented. +While loops have been implemented. ```monkey let a = 1; @@ -232,6 +232,19 @@ while (a < 4) { } ``` +You can also use `break` and `continue` inside a loop. + +```monkey +let a = 1; +while (a < 4) { + if (a == 2) { + break; + } + puts(a); + let a = a + 1; +} +``` + ## Comments For now comments are not supported ( not a huge loss :) ) From 46a35ef54f459e381bd20796bf7ca0be22df4f20 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Tue, 29 Aug 2023 18:10:28 +0200 Subject: [PATCH 16/19] refactor(src): Rename ControlFlow to LoopStatemnts The previous name could be confused with else-if statements. --- src/compiler/mod.rs | 10 +++++----- src/formatter/mod.rs | 2 +- src/parser/ast.rs | 14 +++++++------- src/parser/mod.rs | 8 ++++---- src/parser/parser_tests.rs | 6 +++--- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 59b9352..e735617 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -16,7 +16,7 @@ use crate::{ {CompiledFunction, Object}, }, parser::ast::{ - BlockStatement, Conditional, ControlFlow, Expression, FunctionLiteral, InfixOperator, + BlockStatement, Conditional, LoopStatements, Expression, FunctionLiteral, InfixOperator, LetStatement, Primitive, Program, Statement, WhileStatement, }, }; @@ -169,7 +169,7 @@ impl Compiler { self.compile_while_statement(wh)?; } - Statement::ControlFlow(ctrflow) => self.compile_control_flow_statement(&ctrflow), + Statement::LoopStatements(ctrflow) => self.compile_control_flow_statement(&ctrflow), } Ok(()) @@ -471,9 +471,9 @@ impl Compiler { Ok(()) } - fn compile_control_flow_statement(&mut self, ctrflow: &ControlFlow) { + fn compile_control_flow_statement(&mut self, ctrflow: &LoopStatements) { match ctrflow { - ControlFlow::Break => { + LoopStatements::Break => { let pos = self.emit(Opcode::Jump, vec![9999]); // We emit a dummy value for the jump offset // and we will fix it later self.scopes[self.scope_index] @@ -484,7 +484,7 @@ impl Compiler { .borrow_mut() .add_break(pos); } - ControlFlow::Continue => { + LoopStatements::Continue => { let while_initial_pos = self.scopes[self.scope_index] .loop_scope .as_ref() diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs index 56f9363..594f4d8 100644 --- a/src/formatter/mod.rs +++ b/src/formatter/mod.rs @@ -122,7 +122,7 @@ impl Formatter { self.push_indent(); self.push("}"); } - Statement::ControlFlow(cf) => { + Statement::LoopStatements(cf) => { self.push(cf.to_string().as_str()); self.push(";"); } diff --git a/src/parser/ast.rs b/src/parser/ast.rs index 9bd2779..1e06455 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -408,7 +408,7 @@ pub enum Statement { Return(ReturnStatement), Expression(Expression), While(WhileStatement), - ControlFlow(ControlFlow), + LoopStatements(LoopStatements), } impl Display for Statement { @@ -418,7 +418,7 @@ impl Display for Statement { Statement::Return(statement) => write!(f, "{statement}"), Statement::Expression(expression) => write!(f, "{expression}"), Statement::While(statement) => write!(f, "{statement}"), - Statement::ControlFlow(statement) => write!(f, "{statement}"), + Statement::LoopStatements(statement) => write!(f, "{statement}"), } } } @@ -590,21 +590,21 @@ impl HashMapLiteral { } #[derive(PartialEq, Debug, Clone)] -pub enum ControlFlow { +pub enum LoopStatements { Break, Continue, } -impl Display for ControlFlow { +impl Display for LoopStatements { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ControlFlow::Break => write!(f, "break;"), - ControlFlow::Continue => write!(f, "continue;"), + LoopStatements::Break => write!(f, "break;"), + LoopStatements::Continue => write!(f, "continue;"), } } } -impl ControlFlow { +impl LoopStatements { pub fn parse(parser: &mut Parser) -> Result { match parser.current_token { Token::Break => Ok(Self::Break), diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 163d8ec..6821db5 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -10,7 +10,7 @@ use crate::{ }; use self::{ - ast::{BlockStatement, ControlFlow, WhileStatement}, + ast::{BlockStatement, LoopStatements, WhileStatement}, parser_errors::ParserErrors, }; @@ -64,7 +64,7 @@ impl Parser { Token::While => self.parse_while_statement().map(Statement::While), Token::Break | Token::Continue => self .parse_control_flow_statement() - .map(Statement::ControlFlow), + .map(Statement::LoopStatements), _ => self.parse_expression_statement().map(Statement::Expression), } } @@ -145,8 +145,8 @@ impl Parser { Some(WhileStatement { condition, body }) } - fn parse_control_flow_statement(&mut self) -> Option { - let ctrlflow = ControlFlow::parse(self).ok(); + fn parse_control_flow_statement(&mut self) -> Option { + let ctrlflow = LoopStatements::parse(self).ok(); self.next_token(); ctrlflow } diff --git a/src/parser/parser_tests.rs b/src/parser/parser_tests.rs index 9b69d12..8b45e44 100644 --- a/src/parser/parser_tests.rs +++ b/src/parser/parser_tests.rs @@ -5,7 +5,7 @@ mod tests { lexer::{token::Token, Lexer}, parser::{ ast::{ - BlockStatement, Conditional, ControlFlow, Expression, FunctionCall, Identifier, + BlockStatement, Conditional, LoopStatements, Expression, FunctionCall, Identifier, InfixOperator, LetStatement, Primitive, Program, ReturnStatement, Statement, WhileStatement, }, @@ -663,10 +663,10 @@ mod tests { right: Box::new(Expression::Primitive(Primitive::IntegerLiteral(2))), })), consequence: BlockStatement { - statements: vec![Statement::ControlFlow(ControlFlow::Break)], + statements: vec![Statement::LoopStatements(LoopStatements::Break)], }, alternative: Some(BlockStatement { - statements: vec![Statement::ControlFlow(ControlFlow::Continue)], + statements: vec![Statement::LoopStatements(LoopStatements::Continue)], }), }, ))], From ddc7eeae47ade62ecec3a551a85110038ed956f2 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Tue, 29 Aug 2023 18:22:22 +0200 Subject: [PATCH 17/19] refactor: complete rename from #46a35ef --- src/compiler/mod.rs | 8 ++++---- src/parser/ast.rs | 6 +++--- src/parser/mod.rs | 4 ++-- src/parser/parser_tests.rs | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index e735617..22a7a26 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -16,8 +16,8 @@ use crate::{ {CompiledFunction, Object}, }, parser::ast::{ - BlockStatement, Conditional, LoopStatements, Expression, FunctionLiteral, InfixOperator, - LetStatement, Primitive, Program, Statement, WhileStatement, + BlockStatement, Conditional, Expression, FunctionLiteral, InfixOperator, LetStatement, + LoopStatements, Primitive, Program, Statement, WhileStatement, }, }; @@ -169,7 +169,7 @@ impl Compiler { self.compile_while_statement(wh)?; } - Statement::LoopStatements(ctrflow) => self.compile_control_flow_statement(&ctrflow), + Statement::LoopStatements(ctrflow) => self.compile_loop_statement(&ctrflow), } Ok(()) @@ -471,7 +471,7 @@ impl Compiler { Ok(()) } - fn compile_control_flow_statement(&mut self, ctrflow: &LoopStatements) { + fn compile_loop_statement(&mut self, ctrflow: &LoopStatements) { match ctrflow { LoopStatements::Break => { let pos = self.emit(Opcode::Jump, vec![9999]); // We emit a dummy value for the jump offset diff --git a/src/parser/ast.rs b/src/parser/ast.rs index 1e06455..b8ba6aa 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -598,8 +598,8 @@ pub enum LoopStatements { impl Display for LoopStatements { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - LoopStatements::Break => write!(f, "break;"), - LoopStatements::Continue => write!(f, "continue;"), + LoopStatements::Break => write!(f, "break"), + LoopStatements::Continue => write!(f, "continue"), } } } @@ -610,7 +610,7 @@ impl LoopStatements { Token::Break => Ok(Self::Break), Token::Continue => Ok(Self::Continue), _ => Err(format!( - "Expected a control flow keyword (break, continue), got {}", + "Expected a loop statement keyword (break, continue), got {}", parser.current_token )), } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6821db5..28129a3 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -63,7 +63,7 @@ impl Parser { Token::Return => self.parse_return_statement().map(Statement::Return), Token::While => self.parse_while_statement().map(Statement::While), Token::Break | Token::Continue => self - .parse_control_flow_statement() + .parse_loop_statement() .map(Statement::LoopStatements), _ => self.parse_expression_statement().map(Statement::Expression), } @@ -145,7 +145,7 @@ impl Parser { Some(WhileStatement { condition, body }) } - fn parse_control_flow_statement(&mut self) -> Option { + fn parse_loop_statement(&mut self) -> Option { let ctrlflow = LoopStatements::parse(self).ok(); self.next_token(); ctrlflow diff --git a/src/parser/parser_tests.rs b/src/parser/parser_tests.rs index 8b45e44..b83b877 100644 --- a/src/parser/parser_tests.rs +++ b/src/parser/parser_tests.rs @@ -633,7 +633,7 @@ mod tests { } #[test] - fn test_parse_while_control_flow_statements() { + fn test_parse_while_loop_statements() { let input = "while(x < 3){ if (x == 2){ break; From 6b78dbc50b6121409ee1a7d9a4aedfa501c5cdb1 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Tue, 29 Aug 2023 18:23:22 +0200 Subject: [PATCH 18/19] test: add formatter tests for break/continue --- src/formatter/formatter_tests.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/formatter/formatter_tests.rs b/src/formatter/formatter_tests.rs index 2236795..8a2c2ed 100644 --- a/src/formatter/formatter_tests.rs +++ b/src/formatter/formatter_tests.rs @@ -553,7 +553,9 @@ puts(21); let a = 1; while (x > 0){ let a = a * 2; + break; } + continue; a }; a(12); @@ -568,7 +570,9 @@ let a = fn (x) { let a = 1; while (x > 0) { let a = a * 2; + break; } + continue; a }; a(12); From 7004800ef2b9fe60d81d3cea9748fe92bb773e07 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Tue, 29 Aug 2023 19:33:56 +0200 Subject: [PATCH 19/19] refactor: improve #46a35ef renames --- src/compiler/mod.rs | 12 ++++++------ src/parser/ast.rs | 12 ++++++------ src/parser/mod.rs | 8 ++++---- src/parser/parser_tests.rs | 6 +++--- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 22a7a26..367d57a 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -17,7 +17,7 @@ use crate::{ }, parser::ast::{ BlockStatement, Conditional, Expression, FunctionLiteral, InfixOperator, LetStatement, - LoopStatements, Primitive, Program, Statement, WhileStatement, + LoopStatement, Primitive, Program, Statement, WhileStatement, }, }; @@ -169,7 +169,7 @@ impl Compiler { self.compile_while_statement(wh)?; } - Statement::LoopStatements(ctrflow) => self.compile_loop_statement(&ctrflow), + Statement::LoopStatements(smt) => self.compile_loop_statement(&smt), } Ok(()) @@ -471,9 +471,9 @@ impl Compiler { Ok(()) } - fn compile_loop_statement(&mut self, ctrflow: &LoopStatements) { - match ctrflow { - LoopStatements::Break => { + fn compile_loop_statement(&mut self, smt: &LoopStatement) { + match smt { + LoopStatement::Break => { let pos = self.emit(Opcode::Jump, vec![9999]); // We emit a dummy value for the jump offset // and we will fix it later self.scopes[self.scope_index] @@ -484,7 +484,7 @@ impl Compiler { .borrow_mut() .add_break(pos); } - LoopStatements::Continue => { + LoopStatement::Continue => { let while_initial_pos = self.scopes[self.scope_index] .loop_scope .as_ref() diff --git a/src/parser/ast.rs b/src/parser/ast.rs index b8ba6aa..b287945 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -408,7 +408,7 @@ pub enum Statement { Return(ReturnStatement), Expression(Expression), While(WhileStatement), - LoopStatements(LoopStatements), + LoopStatements(LoopStatement), } impl Display for Statement { @@ -590,21 +590,21 @@ impl HashMapLiteral { } #[derive(PartialEq, Debug, Clone)] -pub enum LoopStatements { +pub enum LoopStatement { Break, Continue, } -impl Display for LoopStatements { +impl Display for LoopStatement { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - LoopStatements::Break => write!(f, "break"), - LoopStatements::Continue => write!(f, "continue"), + LoopStatement::Break => write!(f, "break"), + LoopStatement::Continue => write!(f, "continue"), } } } -impl LoopStatements { +impl LoopStatement { pub fn parse(parser: &mut Parser) -> Result { match parser.current_token { Token::Break => Ok(Self::Break), diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 28129a3..0d781fb 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -10,7 +10,7 @@ use crate::{ }; use self::{ - ast::{BlockStatement, LoopStatements, WhileStatement}, + ast::{BlockStatement, LoopStatement, WhileStatement}, parser_errors::ParserErrors, }; @@ -145,10 +145,10 @@ impl Parser { Some(WhileStatement { condition, body }) } - fn parse_loop_statement(&mut self) -> Option { - let ctrlflow = LoopStatements::parse(self).ok(); + fn parse_loop_statement(&mut self) -> Option { + let smt = LoopStatement::parse(self).ok(); self.next_token(); - ctrlflow + smt } fn parse_expression_statement(&mut self) -> Option { diff --git a/src/parser/parser_tests.rs b/src/parser/parser_tests.rs index b83b877..8485c65 100644 --- a/src/parser/parser_tests.rs +++ b/src/parser/parser_tests.rs @@ -5,7 +5,7 @@ mod tests { lexer::{token::Token, Lexer}, parser::{ ast::{ - BlockStatement, Conditional, LoopStatements, Expression, FunctionCall, Identifier, + BlockStatement, Conditional, LoopStatement, Expression, FunctionCall, Identifier, InfixOperator, LetStatement, Primitive, Program, ReturnStatement, Statement, WhileStatement, }, @@ -663,10 +663,10 @@ mod tests { right: Box::new(Expression::Primitive(Primitive::IntegerLiteral(2))), })), consequence: BlockStatement { - statements: vec![Statement::LoopStatements(LoopStatements::Break)], + statements: vec![Statement::LoopStatements(LoopStatement::Break)], }, alternative: Some(BlockStatement { - statements: vec![Statement::LoopStatements(LoopStatements::Continue)], + statements: vec![Statement::LoopStatements(LoopStatement::Continue)], }), }, ))],