Browse Source

label statement implementation (#549)

Co-authored-by: Halid Odat <halidodat@gmail.com>
pull/739/head
Jason Williams 4 years ago committed by GitHub
parent
commit
bb57270b85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      .vscode/tasks.json
  2. 4
      boa/src/exec/break_node/mod.rs
  3. 2
      boa/src/exec/break_node/tests.rs
  4. 56
      boa/src/exec/iteration/mod.rs
  5. 37
      boa/src/exec/iteration/tests.rs
  6. 4
      boa/src/exec/mod.rs
  7. 2
      boa/src/exec/tests.rs
  8. 22
      boa/src/syntax/ast/node/iteration.rs
  9. 66
      boa/src/syntax/parser/statement/labelled_stm/mod.rs
  10. 29
      boa/src/syntax/parser/statement/mod.rs

13
.vscode/tasks.json vendored

@ -35,27 +35,30 @@
}, },
"presentation": { "presentation": {
"clear": true "clear": true
} },
"problemMatcher": []
}, },
{ {
"type": "process", "type": "process",
"label": "Get Tokens", "label": "Get Tokens",
"command": "cargo", "command": "cargo",
"args": ["run", "--", "-t=Debug", "./tests/js/test.js"], "args": ["run", "--bin", "boa", "--", "-t=Debug", "./tests/js/test.js"],
"group": "build", "group": "build",
"presentation": { "presentation": {
"clear": true "clear": true
} },
"problemMatcher": []
}, },
{ {
"type": "process", "type": "process",
"label": "Get AST", "label": "Get AST",
"command": "cargo", "command": "cargo",
"args": ["run", "--", "-a=Debug", "./tests/js/test.js"], "args": ["run", "--bin", "boa", "--", "-a=Debug", "./tests/js/test.js"],
"group": "build", "group": "build",
"presentation": { "presentation": {
"clear": true "clear": true
} },
"problemMatcher": []
}, },
{ {
"type": "process", "type": "process",

4
boa/src/exec/break_node/mod.rs

@ -11,7 +11,7 @@ impl Executable for Break {
fn run(&self, interpreter: &mut Context) -> Result<Value> { fn run(&self, interpreter: &mut Context) -> Result<Value> {
interpreter interpreter
.executor() .executor()
.set_current_state(InterpreterState::Break(self.label().map(String::from))); .set_current_state(InterpreterState::Break(self.label().map(Box::from)));
Ok(Value::undefined()) Ok(Value::undefined())
} }
@ -21,7 +21,7 @@ impl Executable for Continue {
fn run(&self, interpreter: &mut Context) -> Result<Value> { fn run(&self, interpreter: &mut Context) -> Result<Value> {
interpreter interpreter
.executor() .executor()
.set_current_state(InterpreterState::Continue(self.label().map(String::from))); .set_current_state(InterpreterState::Continue(self.label().map(Box::from)));
Ok(Value::undefined()) Ok(Value::undefined())
} }

2
boa/src/exec/break_node/tests.rs

@ -11,6 +11,6 @@ fn check_post_state() {
assert_eq!( assert_eq!(
engine.executor().get_current_state(), engine.executor().get_current_state(),
&InterpreterState::Break(Some("label".to_string())) &InterpreterState::Break(Some("label".into()))
); );
} }

56
boa/src/exec/iteration/mod.rs

@ -10,6 +10,27 @@ use crate::{
#[cfg(test)] #[cfg(test)]
mod tests; 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 { impl Executable for ForLoop {
fn run(&self, interpreter: &mut Context) -> Result<Value> { fn run(&self, interpreter: &mut Context) -> Result<Value> {
// Create the block environment. // Create the block environment.
@ -34,22 +55,14 @@ impl Executable for ForLoop {
let result = self.body().run(interpreter)?; let result = self.body().run(interpreter)?;
match interpreter.executor().get_current_state() { match interpreter.executor().get_current_state() {
InterpreterState::Break(_label) => { InterpreterState::Break(label) => {
// TODO break to label. handle_state_with_labels!(self, label, interpreter, break);
// Loops 'consume' breaks.
interpreter
.executor()
.set_current_state(InterpreterState::Executing);
break; break;
} }
InterpreterState::Continue(_label) => { InterpreterState::Continue(label) => {
// TODO continue to label. handle_state_with_labels!(self, label, interpreter, continue);
interpreter
.executor()
.set_current_state(InterpreterState::Executing);
// after breaking out of the block, continue execution of the loop
} }
InterpreterState::Return => { InterpreterState::Return => {
return Ok(result); return Ok(result);
} }
@ -76,21 +89,12 @@ impl Executable for WhileLoop {
while self.cond().run(interpreter)?.to_boolean() { while self.cond().run(interpreter)?.to_boolean() {
result = self.expr().run(interpreter)?; result = self.expr().run(interpreter)?;
match interpreter.executor().get_current_state() { match interpreter.executor().get_current_state() {
InterpreterState::Break(_label) => { InterpreterState::Break(label) => {
// TODO break to label. handle_state_with_labels!(self, label, interpreter, break);
// Loops 'consume' breaks.
interpreter
.executor()
.set_current_state(InterpreterState::Executing);
break; break;
} }
InterpreterState::Continue(_label) => { InterpreterState::Continue(label) => {
// TODO continue to label. handle_state_with_labels!(self, label, interpreter, continue)
interpreter
.executor()
.set_current_state(InterpreterState::Executing);
// after breaking out of the block, continue execution of the loop
} }
InterpreterState::Return => { InterpreterState::Return => {
return Ok(result); return Ok(result);

37
boa/src/exec/iteration/tests.rs

@ -59,7 +59,7 @@ fn for_loop_return() {
} }
} }
} }
foo(); foo();
"#; "#;
@ -191,3 +191,38 @@ fn do_while_loop_continue() {
"#; "#;
assert_eq!(&exec(scenario), "[ 1, 2 ]"); 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");
}

4
boa/src/exec/mod.rs

@ -36,8 +36,8 @@ pub trait Executable {
pub(crate) enum InterpreterState { pub(crate) enum InterpreterState {
Executing, Executing,
Return, Return,
Break(Option<String>), Break(Option<Box<str>>),
Continue(Option<String>), Continue(Option<Box<str>>),
} }
/// A Javascript intepreter /// A Javascript intepreter

2
boa/src/exec/tests.rs

@ -1330,7 +1330,7 @@ fn assign_to_object_decl() {
let mut engine = Context::new(); let mut engine = Context::new();
const ERR_MSG: &str = 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); assert_eq!(forward(&mut engine, "{a: 3} = {a: 5};"), ERR_MSG);
} }

22
boa/src/syntax/ast/node/iteration.rs

@ -21,6 +21,7 @@ use serde::{Deserialize, Serialize};
pub struct ForLoop { pub struct ForLoop {
#[cfg_attr(feature = "serde", serde(flatten))] #[cfg_attr(feature = "serde", serde(flatten))]
inner: Box<InnerForLoop>, inner: Box<InnerForLoop>,
label: Option<Box<str>>,
} }
impl ForLoop { impl ForLoop {
@ -34,6 +35,7 @@ impl ForLoop {
{ {
Self { Self {
inner: Box::new(InnerForLoop::new(init, condition, final_expr, body)), inner: Box::new(InnerForLoop::new(init, condition, final_expr, body)),
label: None,
} }
} }
@ -76,6 +78,14 @@ impl ForLoop {
write!(f, "}}") write!(f, "}}")
} }
pub fn label(&self) -> Option<&str> {
self.label.as_ref().map(Box::as_ref)
}
pub fn set_label(&mut self, label: Box<str>) {
self.label = Some(label);
}
} }
impl fmt::Display for ForLoop { impl fmt::Display for ForLoop {
@ -154,6 +164,7 @@ impl InnerForLoop {
pub struct WhileLoop { pub struct WhileLoop {
cond: Box<Node>, cond: Box<Node>,
expr: Box<Node>, expr: Box<Node>,
label: Option<Box<str>>,
} }
impl WhileLoop { impl WhileLoop {
@ -165,6 +176,10 @@ impl WhileLoop {
&self.expr &self.expr
} }
pub fn label(&self) -> Option<&str> {
self.label.as_ref().map(Box::as_ref)
}
/// Creates a `WhileLoop` AST node. /// Creates a `WhileLoop` AST node.
pub fn new<C, B>(condition: C, body: B) -> Self pub fn new<C, B>(condition: C, body: B) -> Self
where where
@ -174,6 +189,7 @@ impl WhileLoop {
Self { Self {
cond: Box::new(condition.into()), cond: Box::new(condition.into()),
expr: Box::new(body.into()), expr: Box::new(body.into()),
label: None,
} }
} }
@ -212,6 +228,7 @@ impl From<WhileLoop> for Node {
pub struct DoWhileLoop { pub struct DoWhileLoop {
body: Box<Node>, body: Box<Node>,
cond: Box<Node>, cond: Box<Node>,
label: Option<Box<str>>,
} }
impl DoWhileLoop { impl DoWhileLoop {
@ -223,6 +240,10 @@ impl DoWhileLoop {
&self.cond &self.cond
} }
pub fn label(&self) -> Option<&str> {
self.label.as_ref().map(Box::as_ref)
}
/// Creates a `DoWhileLoop` AST node. /// Creates a `DoWhileLoop` AST node.
pub fn new<B, C>(body: B, condition: C) -> Self pub fn new<B, C>(body: B, condition: C) -> Self
where where
@ -232,6 +253,7 @@ impl DoWhileLoop {
Self { Self {
body: Box::new(body.into()), body: Box::new(body.into()),
cond: Box::new(condition.into()), cond: Box::new(condition.into()),
label: None,
} }
} }

66
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<Y, A, R>(allow_yield: Y, allow_await: A, allow_return: R) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
R: Into<AllowReturn>,
{
Self {
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
allow_return: allow_return.into(),
}
}
}
impl<R> TokenParser<R> for LabelledStatement
where
R: Read,
{
type Output = Node;
fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> {
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<str>) {
if let Node::ForLoop(ref mut for_loop) = stmt {
for_loop.set_label(name)
}
}

29
boa/src/syntax/parser/statement/mod.rs

@ -14,6 +14,7 @@ mod declaration;
mod expression; mod expression;
mod if_stm; mod if_stm;
mod iteration; mod iteration;
mod labelled_stm;
mod return_stm; mod return_stm;
mod switch; mod switch;
mod throw; mod throw;
@ -37,11 +38,12 @@ use self::{
use super::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser}; use super::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser};
use crate::syntax::lexer::TokenKind; use crate::syntax::lexer::{InputElement, TokenKind};
use crate::{ use crate::{
syntax::ast::{node, Keyword, Node, Punctuator}, syntax::ast::{node, Keyword, Node, Punctuator},
BoaProfiler, BoaProfiler,
}; };
use labelled_stm::LabelledStatement;
use std::io::Read; use std::io::Read;
@ -170,7 +172,28 @@ where
.parse(cursor) .parse(cursor)
.map(Node::from) .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), _ => ExpressionStatement::new(self.allow_yield, self.allow_await).parse(cursor),
} }
} }
@ -367,7 +390,7 @@ where
/// - [ECMAScript specification][spec] /// - [ECMAScript specification][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-LabelIdentifier /// [spec]: https://tc39.es/ecma262/#prod-LabelIdentifier
type LabelIdentifier = BindingIdentifier; pub(super) type LabelIdentifier = BindingIdentifier;
/// Binding identifier parsing. /// Binding identifier parsing.
/// ///

Loading…
Cancel
Save