diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 45aa641fa1..aaf4b16496 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -165,6 +165,13 @@ impl Executor for Interpreter { } Ok(result) } + Node::DoWhileLoop(ref body, ref cond) => { + let mut result = self.run(body)?; + while self.run(cond)?.borrow().is_true() { + result = self.run(body)?; + } + Ok(result) + } Node::If(ref cond, ref expr, None) => Ok(if self.run(cond)?.borrow().is_true() { self.run(expr)? } else { diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index b2ffd1f565..6a610fa564 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -247,3 +247,38 @@ fn test_short_circuit_evaluation() { "#; assert_eq!(exec(short_circuit_eval), String::from("1")); } + +#[test] +fn test_do_while_loop() { + let simple_one = r#" + a = 0; + do { + a += 1; + } while (a < 10); + + a + "#; + assert_eq!(exec(simple_one), String::from("10")); + + let multiline_statement = r#" + pow = 0; + b = 1; + do { + pow += 1; + b *= 2; + } while (pow < 8); + b + "#; + assert_eq!(exec(multiline_statement), String::from("256")); + + let body_is_executed_at_least_once = r#" + a = 0; + do + { + a += 1; + } + while (false); + a + "#; + assert_eq!(exec(body_is_executed_at_least_once), String::from("1")); +} diff --git a/boa/src/syntax/ast/node.rs b/boa/src/syntax/ast/node.rs index fe50ba10bb..5fd0007431 100644 --- a/boa/src/syntax/ast/node.rs +++ b/boa/src/syntax/ast/node.rs @@ -34,6 +34,8 @@ pub enum Node { ConstDecl(Vec<(String, Node)>), /// Continue with an optional label. Continue(Option), + /// do [body] while [cond] + DoWhileLoop(Box, Box), /// Create a function with the given name, arguments, and internal AST node. FunctionDecl(Option, Vec, Box), /// Gets the constant field of a value. @@ -209,6 +211,11 @@ impl Node { write!(f, "while ({}) ", cond)?; node.display(f, indentation) } + Self::DoWhileLoop(ref node, ref cond) => { + write!(f, "do")?; + node.display(f, indentation)?; + write!(f, "while ({})", cond) + } Self::If(ref cond, ref node, None) => { write!(f, "if ({}) ", cond)?; node.display(f, indentation) diff --git a/boa/src/syntax/parser/mod.rs b/boa/src/syntax/parser/mod.rs index f9138780a3..22a3232811 100644 --- a/boa/src/syntax/parser/mod.rs +++ b/boa/src/syntax/parser/mod.rs @@ -444,6 +444,7 @@ impl<'a> Parser<'a> { TokenKind::Keyword(Keyword::If) => self.read_if_statement(), TokenKind::Keyword(Keyword::Var) => self.read_variable_statement(), TokenKind::Keyword(Keyword::While) => self.read_while_statement(), + TokenKind::Keyword(Keyword::Do) => self.read_do_while_statement(), TokenKind::Keyword(Keyword::For) => self.read_for_statement(), TokenKind::Keyword(Keyword::Return) => self.read_return_statement(), TokenKind::Keyword(Keyword::Break) => self.read_break_statement(), @@ -633,6 +634,33 @@ impl<'a> Parser<'a> { Ok(Node::WhileLoop(Box::new(cond), Box::new(body))) } + /// https://tc39.es/ecma262/#sec-do-while-statement + fn read_do_while_statement(&mut self) -> ParseResult { + let body = self.read_statement()?; + + let next_token = self + .peek_skip_lineterminator() + .ok_or(ParseError::AbruptEnd)?; + + if next_token.kind != TokenKind::Keyword(Keyword::While) { + return Err(ParseError::Expected( + vec![TokenKind::Keyword(Keyword::While)], + next_token.clone(), + Some("do while statement"), + )); + } + + let _ = self.next_skip_lineterminator(); // skip while token + + self.expect_punc(Punctuator::OpenParen, Some("do while statement"))?; + + let cond = self.read_expression()?; + + self.expect_punc(Punctuator::CloseParen, Some("do while statement"))?; + + Ok(Node::DoWhileLoop(Box::new(body), Box::new(cond))) + } + /// fn read_try_statement(&mut self) -> ParseResult { // TRY diff --git a/boa/src/syntax/parser/tests.rs b/boa/src/syntax/parser/tests.rs index 2c2d630a07..d247af4ca8 100644 --- a/boa/src/syntax/parser/tests.rs +++ b/boa/src/syntax/parser/tests.rs @@ -711,7 +711,24 @@ fn check_function_declarations() { } #[test] +fn check_do_while() { + check_parser( + r#"do { + a += 1; + } while (true)"#, + &[Node::DoWhileLoop( + Box::new(Node::Block(vec![create_bin_op( + BinOp::Assign(AssignOp::Add), + Node::Local(String::from("a")), + Node::Const(Const::Num(1.0)), + )])), + Box::new(Node::Const(Const::Bool(true))), + )], + ); +} + /// Should be parsed as `new Class().method()` instead of `new (Class().method())` +#[test] fn check_construct_call_precedence() { check_parser( "new Date().getTime()",