diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 5839afb18ea..7592103972a 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -35,27 +35,30 @@ }, "presentation": { "clear": true - } + }, + "problemMatcher": [] }, { "type": "process", "label": "Get Tokens", "command": "cargo", - "args": ["run", "--", "-t=Debug", "./tests/js/test.js"], + "args": ["run", "--bin", "boa", "--", "-t=Debug", "./tests/js/test.js"], "group": "build", "presentation": { "clear": true - } + }, + "problemMatcher": [] }, { "type": "process", "label": "Get AST", "command": "cargo", - "args": ["run", "--", "-a=Debug", "./tests/js/test.js"], + "args": ["run", "--bin", "boa", "--", "-a=Debug", "./tests/js/test.js"], "group": "build", "presentation": { "clear": true - } + }, + "problemMatcher": [] }, { "type": "process", diff --git a/boa/src/exec/break_node/mod.rs b/boa/src/exec/break_node/mod.rs index 17d16eddf30..6d8071c316b 100644 --- a/boa/src/exec/break_node/mod.rs +++ b/boa/src/exec/break_node/mod.rs @@ -11,7 +11,7 @@ impl Executable for Break { fn run(&self, interpreter: &mut Context) -> Result { interpreter .executor() - .set_current_state(InterpreterState::Break(self.label().map(String::from))); + .set_current_state(InterpreterState::Break(self.label().map(Box::from))); Ok(Value::undefined()) } @@ -21,7 +21,7 @@ impl Executable for Continue { fn run(&self, interpreter: &mut Context) -> Result { interpreter .executor() - .set_current_state(InterpreterState::Continue(self.label().map(String::from))); + .set_current_state(InterpreterState::Continue(self.label().map(Box::from))); Ok(Value::undefined()) } diff --git a/boa/src/exec/break_node/tests.rs b/boa/src/exec/break_node/tests.rs index 02d2557e9e4..8444a40ad28 100644 --- a/boa/src/exec/break_node/tests.rs +++ b/boa/src/exec/break_node/tests.rs @@ -11,6 +11,6 @@ fn check_post_state() { assert_eq!( engine.executor().get_current_state(), - &InterpreterState::Break(Some("label".to_string())) + &InterpreterState::Break(Some("label".into())) ); } diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 29d04b2c027..a6720788590 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -10,6 +10,27 @@ use crate::{ #[cfg(test)] mod tests; +// Checking labels for break and continue is the same operation for `ForLoop`, `While` and `DoWhile` +macro_rules! handle_state_with_labels { + ($self:ident, $label:ident, $interpreter:ident, $state:tt) => {{ + if let Some(brk_label) = $label { + if let Some(stmt_label) = $self.label() { + // Break from where we are, keeping "continue" set as the state + if stmt_label != brk_label.as_ref() { + break; + } + } else { + // if a label is set but the current block has no label, break + break; + } + } + + $interpreter + .executor() + .set_current_state(InterpreterState::Executing); + }}; +} + impl Executable for ForLoop { fn run(&self, interpreter: &mut Context) -> Result { // Create the block environment. @@ -34,22 +55,14 @@ impl Executable for ForLoop { let result = self.body().run(interpreter)?; match interpreter.executor().get_current_state() { - InterpreterState::Break(_label) => { - // TODO break to label. - - // Loops 'consume' breaks. - interpreter - .executor() - .set_current_state(InterpreterState::Executing); + InterpreterState::Break(label) => { + handle_state_with_labels!(self, label, interpreter, break); break; } - InterpreterState::Continue(_label) => { - // TODO continue to label. - interpreter - .executor() - .set_current_state(InterpreterState::Executing); - // after breaking out of the block, continue execution of the loop + InterpreterState::Continue(label) => { + handle_state_with_labels!(self, label, interpreter, continue); } + InterpreterState::Return => { return Ok(result); } @@ -76,21 +89,12 @@ impl Executable for WhileLoop { while self.cond().run(interpreter)?.to_boolean() { result = self.expr().run(interpreter)?; match interpreter.executor().get_current_state() { - InterpreterState::Break(_label) => { - // TODO break to label. - - // Loops 'consume' breaks. - interpreter - .executor() - .set_current_state(InterpreterState::Executing); + InterpreterState::Break(label) => { + handle_state_with_labels!(self, label, interpreter, break); break; } - InterpreterState::Continue(_label) => { - // TODO continue to label. - interpreter - .executor() - .set_current_state(InterpreterState::Executing); - // after breaking out of the block, continue execution of the loop + InterpreterState::Continue(label) => { + handle_state_with_labels!(self, label, interpreter, continue) } InterpreterState::Return => { return Ok(result); diff --git a/boa/src/exec/iteration/tests.rs b/boa/src/exec/iteration/tests.rs index 864665b3af4..571fe1defef 100644 --- a/boa/src/exec/iteration/tests.rs +++ b/boa/src/exec/iteration/tests.rs @@ -59,7 +59,7 @@ fn for_loop_return() { } } } - + foo(); "#; @@ -191,3 +191,38 @@ fn do_while_loop_continue() { "#; assert_eq!(&exec(scenario), "[ 1, 2 ]"); } + +#[test] +fn for_loop_break_label() { + let scenario = r#" + var str = ""; + + outer: for (let i = 0; i < 5; i++) { + inner: for (let b = 0; b < 5; b++) { + if (b === 2) { + break outer; + } + str = str + b; + } + str = str + i; + } + str + "#; + assert_eq!(&exec(scenario), "\"01\"") +} + +#[test] +fn for_loop_continue_label() { + let scenario = r#" + var count = 0; + label: for (let x = 0; x < 10;) { + while (true) { + x++; + count++; + continue label; + } + } + count + "#; + assert_eq!(&exec(scenario), "10"); +} diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 7f7b9905638..5d12878414d 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -36,8 +36,8 @@ pub trait Executable { pub(crate) enum InterpreterState { Executing, Return, - Break(Option), - Continue(Option), + Break(Option>), + Continue(Option>), } /// A Javascript intepreter diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index 246fbe6c59a..2e3f3211dcd 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -1330,7 +1330,7 @@ fn assign_to_object_decl() { let mut engine = Context::new(); const ERR_MSG: &str = - "Uncaught \"SyntaxError\": \"expected token \';\', got \':\' in expression statement at line 1, col 3\""; + "Uncaught \"SyntaxError\": \"unexpected token '=', primary expression at line 1, col 8\""; assert_eq!(forward(&mut engine, "{a: 3} = {a: 5};"), ERR_MSG); } diff --git a/boa/src/syntax/ast/node/iteration.rs b/boa/src/syntax/ast/node/iteration.rs index d84a12499f3..8b2dcdfc8de 100644 --- a/boa/src/syntax/ast/node/iteration.rs +++ b/boa/src/syntax/ast/node/iteration.rs @@ -21,6 +21,7 @@ use serde::{Deserialize, Serialize}; pub struct ForLoop { #[cfg_attr(feature = "serde", serde(flatten))] inner: Box, + label: Option>, } impl ForLoop { @@ -34,6 +35,7 @@ impl ForLoop { { Self { inner: Box::new(InnerForLoop::new(init, condition, final_expr, body)), + label: None, } } @@ -76,6 +78,14 @@ impl ForLoop { write!(f, "}}") } + + pub fn label(&self) -> Option<&str> { + self.label.as_ref().map(Box::as_ref) + } + + pub fn set_label(&mut self, label: Box) { + self.label = Some(label); + } } impl fmt::Display for ForLoop { @@ -154,6 +164,7 @@ impl InnerForLoop { pub struct WhileLoop { cond: Box, expr: Box, + label: Option>, } impl WhileLoop { @@ -165,6 +176,10 @@ impl WhileLoop { &self.expr } + pub fn label(&self) -> Option<&str> { + self.label.as_ref().map(Box::as_ref) + } + /// Creates a `WhileLoop` AST node. pub fn new(condition: C, body: B) -> Self where @@ -174,6 +189,7 @@ impl WhileLoop { Self { cond: Box::new(condition.into()), expr: Box::new(body.into()), + label: None, } } @@ -212,6 +228,7 @@ impl From for Node { pub struct DoWhileLoop { body: Box, cond: Box, + label: Option>, } impl DoWhileLoop { @@ -223,6 +240,10 @@ impl DoWhileLoop { &self.cond } + pub fn label(&self) -> Option<&str> { + self.label.as_ref().map(Box::as_ref) + } + /// Creates a `DoWhileLoop` AST node. pub fn new(body: B, condition: C) -> Self where @@ -232,6 +253,7 @@ impl DoWhileLoop { Self { body: Box::new(body.into()), cond: Box::new(condition.into()), + label: None, } } diff --git a/boa/src/syntax/parser/statement/labelled_stm/mod.rs b/boa/src/syntax/parser/statement/labelled_stm/mod.rs new file mode 100644 index 00000000000..231a5c0b8a1 --- /dev/null +++ b/boa/src/syntax/parser/statement/labelled_stm/mod.rs @@ -0,0 +1,66 @@ +use std::io::Read; + +use super::{LabelIdentifier, Statement}; +use crate::{ + syntax::ast::Node, + syntax::{ + ast::Punctuator, + parser::{ + cursor::Cursor, error::ParseError, AllowAwait, AllowReturn, AllowYield, TokenParser, + }, + }, + BoaProfiler, +}; +/// Labelled Statement Parsing +/// +/// More information +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label +/// [spec]: https://tc39.es/ecma262/#sec-labelled-statements +#[derive(Debug, Clone, Copy)] +pub(super) struct LabelledStatement { + allow_yield: AllowYield, + allow_await: AllowAwait, + allow_return: AllowReturn, +} + +impl LabelledStatement { + pub(super) fn new(allow_yield: Y, allow_await: A, allow_return: R) -> Self + where + Y: Into, + A: Into, + R: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + allow_return: allow_return.into(), + } + } +} + +impl TokenParser for LabelledStatement +where + R: Read, +{ + type Output = Node; + + fn parse(self, cursor: &mut Cursor) -> Result { + let _timer = BoaProfiler::global().start_event("Label", "Parsing"); + let name = LabelIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?; + cursor.expect(Punctuator::Colon, "Labelled Statement")?; + let mut stmt = + Statement::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?; + + set_label_for_node(&mut stmt, name); + Ok(stmt) + } +} + +fn set_label_for_node(stmt: &mut Node, name: Box) { + if let Node::ForLoop(ref mut for_loop) = stmt { + for_loop.set_label(name) + } +} diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index 6b93bdc7e88..08b419f4d11 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -14,6 +14,7 @@ mod declaration; mod expression; mod if_stm; mod iteration; +mod labelled_stm; mod return_stm; mod switch; mod throw; @@ -37,11 +38,12 @@ use self::{ use super::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser}; -use crate::syntax::lexer::TokenKind; +use crate::syntax::lexer::{InputElement, TokenKind}; use crate::{ syntax::ast::{node, Keyword, Node, Punctuator}, BoaProfiler, }; +use labelled_stm::LabelledStatement; use std::io::Read; @@ -170,7 +172,28 @@ where .parse(cursor) .map(Node::from) } - // TODO: https://tc39.es/ecma262/#prod-LabelledStatement + TokenKind::Identifier(_) => { + // Labelled Statement check + cursor.set_goal(InputElement::Div); + let tok = cursor.peek(1)?; + if tok.is_some() + && matches!( + tok.unwrap().kind(), + TokenKind::Punctuator(Punctuator::Colon) + ) + { + return LabelledStatement::new( + self.allow_yield, + self.allow_await, + self.allow_return, + ) + .parse(cursor) + .map(Node::from); + } + + ExpressionStatement::new(self.allow_yield, self.allow_await).parse(cursor) + } + _ => ExpressionStatement::new(self.allow_yield, self.allow_await).parse(cursor), } } @@ -367,7 +390,7 @@ where /// - [ECMAScript specification][spec] /// /// [spec]: https://tc39.es/ecma262/#prod-LabelIdentifier -type LabelIdentifier = BindingIdentifier; +pub(super) type LabelIdentifier = BindingIdentifier; /// Binding identifier parsing. ///