From 66ab70ca4f8b6c8579f97dadf181604f6db83f13 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Sun, 20 Aug 2023 10:48:33 +0200 Subject: [PATCH 01/11] 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 02/11] 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 03/11] 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 04/11] 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 05/11] 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 06/11] 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 07/11] 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 08/11] 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 09/11] 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 10/11] 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 11/11] 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)], }), }, ))],