Browse Source

Switch impl (#451)

Co-authored-by: Iban Eguia <razican@protonmail.ch>
pull/489/head
Paul Lancaster 5 years ago committed by GitHub
parent
commit
542b2cc005
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      .vscode/launch.json
  2. 17
      boa/src/exec/block/mod.rs
  3. 16
      boa/src/exec/break_node/mod.rs
  4. 17
      boa/src/exec/break_node/tests.rs
  5. 4
      boa/src/exec/expression/mod.rs
  6. 69
      boa/src/exec/iteration/mod.rs
  7. 101
      boa/src/exec/iteration/tests.rs
  8. 22
      boa/src/exec/mod.rs
  9. 4
      boa/src/exec/return_smt/mod.rs
  10. 18
      boa/src/exec/statement_list.rs
  11. 32
      boa/src/exec/switch/mod.rs
  12. 200
      boa/src/exec/switch/tests.rs
  13. 10
      boa/src/syntax/ast/node/return_smt.rs
  14. 12
      boa/src/syntax/ast/node/switch.rs
  15. 2
      boa/src/syntax/ast/token.rs
  16. 1
      boa/src/syntax/parser/expression/assignment/arrow_function.rs
  17. 1
      boa/src/syntax/parser/expression/left_hand_side/call.rs
  18. 1
      boa/src/syntax/parser/expression/left_hand_side/member.rs
  19. 30
      boa/src/syntax/parser/function/tests.rs
  20. 4
      boa/src/syntax/parser/statement/block/tests.rs
  21. 50
      boa/src/syntax/parser/statement/mod.rs
  22. 4
      boa/src/syntax/parser/statement/return_stm/mod.rs
  23. 93
      boa/src/syntax/parser/statement/switch/mod.rs
  24. 98
      boa/src/syntax/parser/statement/switch/tests.rs
  25. 2
      boa/src/syntax/parser/tests.rs

11
.vscode/launch.json vendored

@ -36,16 +36,15 @@
"symbolSearchPath": "https://msdl.microsoft.com/download/symbols" "symbolSearchPath": "https://msdl.microsoft.com/download/symbols"
}, },
{ {
"name": "(Windows) Run Test Debugger", "name": "(Windows) Launch Debug",
"type": "cppvsdbg", "type": "cppvsdbg",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/target/debug/boa-ea5ed1ef3ee0cbe1.exe", "program": "${workspaceRoot}/target/debug/foo.exe",
"args": [], "args": [],
"stopAtEntry": false, "stopAtEntry": false,
"cwd": "${workspaceFolder}", "cwd": "${workspaceRoot}",
"environment": [], "environment": [],
"externalConsole": true, "externalConsole": true
"preLaunchTask": "Cargo Test Build", },
}
] ]
} }

17
boa/src/exec/block/mod.rs

@ -1,6 +1,6 @@
//! Block statement execution. //! Block statement execution.
use super::{Executable, Interpreter}; use super::{Executable, Interpreter, InterpreterState};
use crate::{ use crate::{
builtins::value::{ResultValue, Value}, builtins::value::{ResultValue, Value},
environment::lexical_environment::new_declarative_environment, environment::lexical_environment::new_declarative_environment,
@ -22,10 +22,21 @@ impl Executable for Block {
for statement in self.statements() { for statement in self.statements() {
obj = statement.run(interpreter)?; obj = statement.run(interpreter)?;
// early return match interpreter.get_current_state() {
if interpreter.is_return { InterpreterState::Return => {
// Early return.
break; break;
} }
InterpreterState::Break(_label) => {
// TODO, break to a label.
// Early break.
break;
}
_ => {
// Continue execution
}
}
} }
// pop the block env // pop the block env

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

@ -0,0 +1,16 @@
use super::{Executable, Interpreter, InterpreterState};
use crate::{
builtins::value::{ResultValue, Value},
syntax::ast::node::Break,
};
#[cfg(test)]
mod tests;
impl Executable for Break {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
interpreter.set_current_state(InterpreterState::Break(self.label().map(String::from)));
Ok(Value::undefined())
}
}

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

@ -0,0 +1,17 @@
use super::{Interpreter, InterpreterState};
use crate::{exec::Executable, syntax::ast::node::Break, Realm};
#[test]
fn check_post_state() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let brk: Break = Break::new("label");
brk.run(&mut engine).unwrap();
assert_eq!(
engine.get_current_state(),
&InterpreterState::Break(Some("label".to_string()))
);
}

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

@ -1,6 +1,6 @@
//! Expression execution. //! Expression execution.
use super::{Executable, Interpreter}; use super::{Executable, Interpreter, InterpreterState};
use crate::{ use crate::{
builtins::{ builtins::{
object::{INSTANCE_PROTOTYPE, PROTOTYPE}, object::{INSTANCE_PROTOTYPE, PROTOTYPE},
@ -48,7 +48,7 @@ impl Executable for Call {
let fnct_result = interpreter.call(&func, &mut this, &v_args); let fnct_result = interpreter.call(&func, &mut this, &v_args);
// unset the early return flag // unset the early return flag
interpreter.is_return = false; interpreter.set_current_state(InterpreterState::Executing);
fnct_result fnct_result
} }

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

@ -1,6 +1,6 @@
//! Iteration node execution. //! Iteration node execution.
use super::{Executable, Interpreter}; use super::{Executable, Interpreter, InterpreterState};
use crate::{ use crate::{
builtins::value::{ResultValue, Value}, builtins::value::{ResultValue, Value},
environment::lexical_environment::new_declarative_environment, environment::lexical_environment::new_declarative_environment,
@ -9,6 +9,9 @@ use crate::{
}; };
use std::borrow::Borrow; use std::borrow::Borrow;
#[cfg(test)]
mod tests;
impl Executable for ForLoop { impl Executable for ForLoop {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
// Create the block environment. // Create the block environment.
@ -30,7 +33,23 @@ impl Executable for ForLoop {
.transpose()? .transpose()?
.unwrap_or(true) .unwrap_or(true)
{ {
self.body().run(interpreter)?; let result = self.body().run(interpreter)?;
match interpreter.get_current_state() {
InterpreterState::Break(_label) => {
// TODO break to label.
// Loops 'consume' breaks.
interpreter.set_current_state(InterpreterState::Executing);
break;
}
InterpreterState::Return => {
return Ok(result);
}
_ => {
// Continue execution.
}
}
if let Some(final_expr) = self.final_expr() { if let Some(final_expr) = self.final_expr() {
final_expr.run(interpreter)?; final_expr.run(interpreter)?;
@ -49,6 +68,21 @@ impl Executable for WhileLoop {
let mut result = Value::undefined(); let mut result = Value::undefined();
while self.cond().run(interpreter)?.borrow().is_true() { while self.cond().run(interpreter)?.borrow().is_true() {
result = self.expr().run(interpreter)?; result = self.expr().run(interpreter)?;
match interpreter.get_current_state() {
InterpreterState::Break(_label) => {
// TODO break to label.
// Loops 'consume' breaks.
interpreter.set_current_state(InterpreterState::Executing);
break;
}
InterpreterState::Return => {
return Ok(result);
}
_ => {
// Continue execution.
}
}
} }
Ok(result) Ok(result)
} }
@ -57,8 +91,39 @@ impl Executable for WhileLoop {
impl Executable for DoWhileLoop { impl Executable for DoWhileLoop {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
let mut result = self.body().run(interpreter)?; let mut result = self.body().run(interpreter)?;
match interpreter.get_current_state() {
InterpreterState::Break(_label) => {
// TODO break to label.
// Loops 'consume' breaks.
interpreter.set_current_state(InterpreterState::Executing);
return Ok(result);
}
InterpreterState::Return => {
return Ok(result);
}
_ => {
// Continue execution.
}
}
while self.cond().run(interpreter)?.borrow().is_true() { while self.cond().run(interpreter)?.borrow().is_true() {
result = self.body().run(interpreter)?; result = self.body().run(interpreter)?;
match interpreter.get_current_state() {
InterpreterState::Break(_label) => {
// TODO break to label.
// Loops 'consume' breaks.
interpreter.set_current_state(InterpreterState::Executing);
break;
}
InterpreterState::Return => {
return Ok(result);
}
_ => {
// Continue execution.
}
}
} }
Ok(result) Ok(result)
} }

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

@ -0,0 +1,101 @@
use crate::exec;
#[test]
fn while_loop_late_break() {
// Ordering with statement before the break.
let scenario = r#"
let a = 1;
while (a < 5) {
a++;
if (a == 3) {
break;
}
}
a;
"#;
assert_eq!(&exec(scenario), "3");
}
#[test]
fn while_loop_early_break() {
// Ordering with statements after the break.
let scenario = r#"
let a = 1;
while (a < 5) {
if (a == 3) {
break;
}
a++;
}
a;
"#;
assert_eq!(&exec(scenario), "3");
}
#[test]
fn for_loop_break() {
let scenario = r#"
let a = 1;
for (; a < 5; a++) {
if (a == 3) {
break;
}
}
a;
"#;
assert_eq!(&exec(scenario), "3");
}
#[test]
fn for_loop_return() {
let scenario = r#"
function foo() {
for (let a = 1; a < 5; a++) {
if (a == 3) {
return a;
}
}
}
foo();
"#;
assert_eq!(&exec(scenario), "3");
}
#[test]
fn do_loop_late_break() {
// Ordering with statement before the break.
let scenario = r#"
let a = 1;
do {
a++;
if (a == 3) {
break;
}
} while (a < 5);
a;
"#;
assert_eq!(&exec(scenario), "3");
}
#[test]
fn do_loop_early_break() {
// Ordering with statements after the break.
let scenario = r#"
let a = 1;
do {
if (a == 3) {
break;
}
a++;
} while (a < 5);
a;
"#;
assert_eq!(&exec(scenario), "3");
}

22
boa/src/exec/mod.rs

@ -2,6 +2,7 @@
mod array; mod array;
mod block; mod block;
mod break_node;
mod conditional; mod conditional;
mod declaration; mod declaration;
mod exception; mod exception;
@ -45,6 +46,12 @@ pub trait Executable {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue; fn run(&self, interpreter: &mut Interpreter) -> ResultValue;
} }
#[derive(Debug, Eq, PartialEq)]
pub(crate) enum InterpreterState {
Executing,
Return,
Break(Option<String>),
}
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum PreferredType { pub enum PreferredType {
String, String,
@ -55,8 +62,8 @@ pub enum PreferredType {
/// A Javascript intepreter /// A Javascript intepreter
#[derive(Debug)] #[derive(Debug)]
pub struct Interpreter { pub struct Interpreter {
/// Wether it's running a return statement. current_state: InterpreterState,
is_return: bool,
/// realm holds both the global object and the environment /// realm holds both the global object and the environment
pub realm: Realm, pub realm: Realm,
} }
@ -65,8 +72,8 @@ impl Interpreter {
/// Creates a new interpreter. /// Creates a new interpreter.
pub fn new(realm: Realm) -> Self { pub fn new(realm: Realm) -> Self {
Self { Self {
current_state: InterpreterState::Executing,
realm, realm,
is_return: false,
} }
} }
@ -485,6 +492,14 @@ impl Interpreter {
_ => panic!("TypeError: invalid assignment to {}", node), _ => panic!("TypeError: invalid assignment to {}", node),
} }
} }
pub(crate) fn set_current_state(&mut self, new_state: InterpreterState) {
self.current_state = new_state
}
pub(crate) fn get_current_state(&self) -> &InterpreterState {
&self.current_state
}
} }
impl Executable for Node { impl Executable for Node {
@ -539,6 +554,7 @@ impl Executable for Node {
Ok(interpreter.realm().environment.get_this_binding()) Ok(interpreter.realm().environment.get_this_binding())
} }
Node::Try(ref try_node) => try_node.run(interpreter), Node::Try(ref try_node) => try_node.run(interpreter),
Node::Break(ref break_node) => break_node.run(interpreter),
ref i => unimplemented!("{:?}", i), ref i => unimplemented!("{:?}", i),
} }
} }

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

@ -1,4 +1,4 @@
use super::{Executable, Interpreter}; use super::{Executable, Interpreter, InterpreterState};
use crate::{ use crate::{
builtins::value::{ResultValue, Value}, builtins::value::{ResultValue, Value},
syntax::ast::node::Return, syntax::ast::node::Return,
@ -11,7 +11,7 @@ impl Executable for Return {
None => Ok(Value::undefined()), None => Ok(Value::undefined()),
}; };
// Set flag for return // Set flag for return
interpreter.is_return = true; interpreter.set_current_state(InterpreterState::Return);
result result
} }
} }

18
boa/src/exec/statement_list.rs

@ -1,6 +1,6 @@
//! Statement list execution. //! Statement list execution.
use super::{Executable, Interpreter}; use super::{Executable, Interpreter, InterpreterState};
use crate::{ use crate::{
builtins::value::{ResultValue, Value}, builtins::value::{ResultValue, Value},
syntax::ast::node::StatementList, syntax::ast::node::StatementList,
@ -11,13 +11,25 @@ impl Executable for StatementList {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
let _timer = BoaProfiler::global().start_event("StatementList", "exec"); let _timer = BoaProfiler::global().start_event("StatementList", "exec");
let mut obj = Value::null(); let mut obj = Value::null();
interpreter.set_current_state(InterpreterState::Executing);
for (i, item) in self.statements().iter().enumerate() { for (i, item) in self.statements().iter().enumerate() {
let val = item.run(interpreter)?; let val = item.run(interpreter)?;
// early return match interpreter.get_current_state() {
if interpreter.is_return { InterpreterState::Return => {
// Early return.
obj = val; obj = val;
break; break;
} }
InterpreterState::Break(_label) => {
// TODO, break to a label.
// Early break.
break;
}
_ => {
// Continue execution
}
}
if i + 1 == self.statements().len() { if i + 1 == self.statements().len() {
obj = val; obj = val;
} }

32
boa/src/exec/switch/mod.rs

@ -1,24 +1,46 @@
use super::{Executable, Interpreter}; use super::{Executable, Interpreter, InterpreterState};
use crate::{ use crate::{
builtins::value::{ResultValue, Value}, builtins::value::{ResultValue, Value},
syntax::ast::node::Switch, syntax::ast::node::Switch,
}; };
#[cfg(test)]
mod tests;
impl Executable for Switch { impl Executable for Switch {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
let default = self.default(); let default = self.default();
let val = self.val().run(interpreter)?; let val = self.val().run(interpreter)?;
let mut result = Value::null(); let mut result = Value::null();
let mut matched = false; let mut matched = false;
interpreter.set_current_state(InterpreterState::Executing);
// If a case block does not end with a break statement then subsequent cases will be run without
// checking their conditions until a break is encountered.
let mut fall_through: bool = false;
for case in self.cases().iter() { for case in self.cases().iter() {
let cond = case.condition(); let cond = case.condition();
let block = case.body(); let block = case.body();
if val.strict_equals(&cond.run(interpreter)?) { if fall_through || val.strict_equals(&cond.run(interpreter)?) {
matched = true; matched = true;
block.run(interpreter)?; let result = block.run(interpreter)?;
match interpreter.get_current_state() {
InterpreterState::Return => {
// Early return.
return Ok(result);
}
InterpreterState::Break(_label) => {
// Break statement encountered so therefore end switch statement.
interpreter.set_current_state(InterpreterState::Executing);
break;
}
_ => {
// Continuing execution / falling through to next case statement(s).
fall_through = true;
}
}
} }
// TODO: break out of switch on a break statement.
} }
if !matched { if !matched {
if let Some(default) = default { if let Some(default) = default {

200
boa/src/exec/switch/tests.rs

@ -0,0 +1,200 @@
use crate::exec;
#[test]
fn single_case_switch() {
let scenario = r#"
let a = 10;
switch (a) {
case 10:
a = 20;
break;
}
a;
"#;
assert_eq!(&exec(scenario), "20");
}
#[test]
fn no_cases_switch() {
let scenario = r#"
let a = 10;
switch (a) {
}
a;
"#;
assert_eq!(&exec(scenario), "10");
}
#[test]
fn no_true_case_switch() {
let scenario = r#"
let a = 10;
switch (a) {
case 5:
a = 15;
break;
}
a;
"#;
assert_eq!(&exec(scenario), "10");
}
#[test]
fn two_case_switch() {
let scenario = r#"
let a = 10;
switch (a) {
case 5:
a = 15;
break;
case 10:
a = 20;
break;
}
a;
"#;
assert_eq!(&exec(scenario), "20");
}
#[test]
fn two_case_no_break_switch() {
let scenario = r#"
let a = 10;
let b = 10;
switch (a) {
case 10:
a = 150;
case 20:
b = 150;
break;
}
a + b;
"#;
assert_eq!(&exec(scenario), "300");
}
#[test]
fn three_case_partial_fallthrough() {
let scenario = r#"
let a = 10;
let b = 10;
switch (a) {
case 10:
a = 150;
case 20:
b = 150;
break;
case 15:
b = 1000;
break;
}
a + b;
"#;
assert_eq!(&exec(scenario), "300");
}
#[test]
fn default_taken_switch() {
let scenario = r#"
let a = 10;
switch (a) {
case 5:
a = 150;
break;
default:
a = 70;
}
a;
"#;
assert_eq!(&exec(scenario), "70");
}
#[test]
fn default_not_taken_switch() {
let scenario = r#"
let a = 5;
switch (a) {
case 5:
a = 150;
break;
default:
a = 70;
}
a;
"#;
assert_eq!(&exec(scenario), "150");
}
#[test]
fn string_switch() {
let scenario = r#"
let a = "hello";
switch (a) {
case "hello":
a = "world";
break;
default:
a = "hi";
}
a;
"#;
assert_eq!(&exec(scenario), "world");
}
#[test]
fn bigger_switch_example() {
let expected = ["Mon", "Tue", "Wed", "Thurs", "Fri", "Sat", "Sun"];
for (i, val) in expected.iter().enumerate() {
let scenario = format!(
r#"
let a = {};
let b = "unknown";
switch (a) {{
case 0:
b = "Mon";
break;
case 1:
b = "Tue";
break;
case 2:
b = "Wed";
break;
case 3:
b = "Thurs";
break;
case 4:
b = "Fri";
break;
case 5:
b = "Sat";
break;
case 6:
b = "Sun";
break;
}}
b;
"#,
i
);
assert_eq!(&exec(&scenario), val);
}
}

10
boa/src/syntax/ast/node/return_smt.rs

@ -24,25 +24,31 @@ use serde::{Deserialize, Serialize};
/// [spec]: https://tc39.es/ecma262/#prod-ReturnStatement /// [spec]: https://tc39.es/ecma262/#prod-ReturnStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] #[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct Return { pub struct Return {
expr: Option<Box<Node>>, expr: Option<Box<Node>>,
label: Option<Box<str>>,
} }
impl Return { impl Return {
pub fn label(&self) -> Option<&str> {
self.label.as_ref().map(Box::as_ref)
}
pub fn expr(&self) -> Option<&Node> { pub fn expr(&self) -> Option<&Node> {
self.expr.as_ref().map(Box::as_ref) self.expr.as_ref().map(Box::as_ref)
} }
/// Creates a `Return` AST node. /// Creates a `Return` AST node.
pub fn new<E, OE>(expr: OE) -> Self pub fn new<E, OE, L>(expr: OE, label: L) -> Self
where where
E: Into<Node>, E: Into<Node>,
OE: Into<Option<E>>, OE: Into<Option<E>>,
L: Into<Option<Box<str>>>,
{ {
Self { Self {
expr: expr.into().map(E::into).map(Box::new), expr: expr.into().map(E::into).map(Box::new),
label: label.into(),
} }
} }
} }

12
boa/src/syntax/ast/node/switch.rs

@ -24,6 +24,18 @@ impl Case {
pub fn body(&self) -> &StatementList { pub fn body(&self) -> &StatementList {
&self.body &self.body
} }
/// Creates a `Case` AST node.
pub fn new<C, B>(condition: C, body: B) -> Self
where
C: Into<Node>,
B: Into<StatementList>,
{
Self {
condition: condition.into(),
body: body.into(),
}
}
} }
/// The `switch` statement evaluates an expression, matching the expression's value to a case /// The `switch` statement evaluates an expression, matching the expression's value to a case

2
boa/src/syntax/ast/token.rs

@ -31,7 +31,7 @@ pub struct Token {
/// The token kind, which contains the actual data of the token. /// The token kind, which contains the actual data of the token.
pub(crate) kind: TokenKind, pub(crate) kind: TokenKind,
/// The token position in the original source code. /// The token position in the original source code.
span: Span, pub(crate) span: Span,
} }
impl Token { impl Token {

1
boa/src/syntax/parser/expression/assignment/arrow_function.rs

@ -119,6 +119,7 @@ impl TokenParser for ConciseBody {
} }
_ => Ok(StatementList::from(vec![Return::new( _ => Ok(StatementList::from(vec![Return::new(
ExpressionBody::new(self.allow_in, false).parse(cursor)?, ExpressionBody::new(self.allow_in, false).parse(cursor)?,
None,
) )
.into()])), .into()])),
} }

1
boa/src/syntax/parser/expression/left_hand_side/call.rs

@ -8,6 +8,7 @@
//! [spec]: https://tc39.es/ecma262/#prod-CallExpression //! [spec]: https://tc39.es/ecma262/#prod-CallExpression
use super::arguments::Arguments; use super::arguments::Arguments;
use crate::{ use crate::{
syntax::{ syntax::{
ast::{ ast::{

1
boa/src/syntax/parser/expression/left_hand_side/member.rs

@ -6,6 +6,7 @@
//! [spec]: https://tc39.es/ecma262/#prod-MemberExpression //! [spec]: https://tc39.es/ecma262/#prod-MemberExpression
use super::arguments::Arguments; use super::arguments::Arguments;
use crate::{ use crate::{
syntax::{ syntax::{
ast::{ ast::{

30
boa/src/syntax/parser/function/tests.rs

@ -14,7 +14,7 @@ fn check_basic() {
vec![FunctionDecl::new( vec![FunctionDecl::new(
Box::from("foo"), Box::from("foo"),
vec![FormalParameter::new("a", None, false)], vec![FormalParameter::new("a", None, false)],
vec![Return::new(Identifier::from("a")).into()], vec![Return::new(Identifier::from("a"), None).into()],
) )
.into()], .into()],
); );
@ -28,7 +28,7 @@ fn check_basic_semicolon_insertion() {
vec![FunctionDecl::new( vec![FunctionDecl::new(
Box::from("foo"), Box::from("foo"),
vec![FormalParameter::new("a", None, false)], vec![FormalParameter::new("a", None, false)],
vec![Return::new(Identifier::from("a")).into()], vec![Return::new(Identifier::from("a"), None).into()],
) )
.into()], .into()],
); );
@ -42,7 +42,7 @@ fn check_empty_return() {
vec![FunctionDecl::new( vec![FunctionDecl::new(
Box::from("foo"), Box::from("foo"),
vec![FormalParameter::new("a", None, false)], vec![FormalParameter::new("a", None, false)],
vec![Return::new::<Node, Option<Node>>(None).into()], vec![Return::new::<Node, Option<Node>, Option<_>>(None, None).into()],
) )
.into()], .into()],
); );
@ -56,7 +56,7 @@ fn check_empty_return_semicolon_insertion() {
vec![FunctionDecl::new( vec![FunctionDecl::new(
Box::from("foo"), Box::from("foo"),
vec![FormalParameter::new("a", None, false)], vec![FormalParameter::new("a", None, false)],
vec![Return::new::<Node, Option<Node>>(None).into()], vec![Return::new::<Node, Option<Node>, Option<_>>(None, None).into()],
) )
.into()], .into()],
); );
@ -115,11 +115,10 @@ fn check_arrow() {
FormalParameter::new("a", None, false), FormalParameter::new("a", None, false),
FormalParameter::new("b", None, false), FormalParameter::new("b", None, false),
], ],
vec![Return::new(BinOp::new( vec![Return::new(
NumOp::Add, BinOp::new(NumOp::Add, Identifier::from("a"), Identifier::from("b")),
Identifier::from("a"), None,
Identifier::from("b"), )
))
.into()], .into()],
) )
.into()], .into()],
@ -136,11 +135,10 @@ fn check_arrow_semicolon_insertion() {
FormalParameter::new("a", None, false), FormalParameter::new("a", None, false),
FormalParameter::new("b", None, false), FormalParameter::new("b", None, false),
], ],
vec![Return::new(BinOp::new( vec![Return::new(
NumOp::Add, BinOp::new(NumOp::Add, Identifier::from("a"), Identifier::from("b")),
Identifier::from("a"), None,
Identifier::from("b"), )
))
.into()], .into()],
) )
.into()], .into()],
@ -157,7 +155,7 @@ fn check_arrow_epty_return() {
FormalParameter::new("a", None, false), FormalParameter::new("a", None, false),
FormalParameter::new("b", None, false), FormalParameter::new("b", None, false),
], ],
vec![Return::new::<Node, Option<_>>(None).into()], vec![Return::new::<Node, Option<_>, Option<_>>(None, None).into()],
) )
.into()], .into()],
); );
@ -173,7 +171,7 @@ fn check_arrow_empty_return_semicolon_insertion() {
FormalParameter::new("a", None, false), FormalParameter::new("a", None, false),
FormalParameter::new("b", None, false), FormalParameter::new("b", None, false),
], ],
vec![Return::new::<Node, Option<_>>(None).into()], vec![Return::new::<Node, Option<_>, Option<_>>(None, None).into()],
) )
.into()], .into()],
); );

4
boa/src/syntax/parser/statement/block/tests.rs

@ -51,7 +51,7 @@ fn non_empty() {
FunctionDecl::new( FunctionDecl::new(
"hello".to_owned().into_boxed_str(), "hello".to_owned().into_boxed_str(),
vec![], vec![],
vec![Return::new(Const::from(10)).into()], vec![Return::new(Const::from(10), None).into()],
) )
.into(), .into(),
VarDeclList::from(vec![VarDecl::new( VarDeclList::from(vec![VarDecl::new(
@ -77,7 +77,7 @@ fn hoisting() {
FunctionDecl::new( FunctionDecl::new(
"hello".to_owned().into_boxed_str(), "hello".to_owned().into_boxed_str(),
vec![], vec![],
vec![Return::new(Const::from(10)).into()], vec![Return::new(Const::from(10), None).into()],
) )
.into(), .into(),
VarDeclList::from(vec![VarDecl::new( VarDeclList::from(vec![VarDecl::new(

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

@ -57,6 +57,7 @@ use crate::{
/// - `WithStatement` /// - `WithStatement`
/// - `LabelledStatement` /// - `LabelledStatement`
/// - `ThrowStatement` /// - `ThrowStatement`
/// - `SwitchStatement`
/// - `TryStatement` /// - `TryStatement`
/// - `DebuggerStatement` /// - `DebuggerStatement`
/// ///
@ -184,7 +185,7 @@ pub(super) struct StatementList {
allow_yield: AllowYield, allow_yield: AllowYield,
allow_await: AllowAwait, allow_await: AllowAwait,
allow_return: AllowReturn, allow_return: AllowReturn,
break_when_closingbrase: bool, break_when_closingbraces: bool,
} }
impl StatementList { impl StatementList {
@ -193,7 +194,7 @@ impl StatementList {
allow_yield: Y, allow_yield: Y,
allow_await: A, allow_await: A,
allow_return: R, allow_return: R,
break_when_closingbrase: bool, break_when_closingbraces: bool,
) -> Self ) -> Self
where where
Y: Into<AllowYield>, Y: Into<AllowYield>,
@ -204,8 +205,47 @@ impl StatementList {
allow_yield: allow_yield.into(), allow_yield: allow_yield.into(),
allow_await: allow_await.into(), allow_await: allow_await.into(),
allow_return: allow_return.into(), allow_return: allow_return.into(),
break_when_closingbrase, break_when_closingbraces,
}
}
/// The function parses a node::StatementList using the given break_nodes to know when to terminate.
///
/// This ignores the break_when_closingbraces flag.
///
/// Returns a ParseError::AbruptEnd if end of stream is reached before a break token.
///
/// This is a more general version of the TokenParser parse function for StatementList which can exit based on multiple
/// different tokens. This may eventually replace the parse() function but is currently seperate to allow testing the
/// performance impact of this more general mechanism.
///
/// Note that the last token which causes the parse to finish is not consumed.
pub(crate) fn parse_generalised(
self,
cursor: &mut Cursor<'_>,
break_nodes: &[TokenKind],
) -> Result<node::StatementList, ParseError> {
let mut items = Vec::new();
loop {
match cursor.peek(0) {
Some(token) if break_nodes.contains(&token.kind) => break,
None => return Err(ParseError::AbruptEnd),
_ => {}
}
let item =
StatementListItem::new(self.allow_yield, self.allow_await, self.allow_return)
.parse(cursor)?;
items.push(item);
// move the cursor forward for any consecutive semicolon.
while cursor.next_if(Punctuator::Semicolon).is_some() {}
} }
items.sort_by(Node::hoistable_order);
Ok(items.into())
} }
} }
@ -219,14 +259,14 @@ impl TokenParser for StatementList {
loop { loop {
match cursor.peek(0) { match cursor.peek(0) {
Some(token) if token.kind == TokenKind::Punctuator(Punctuator::CloseBlock) => { Some(token) if token.kind == TokenKind::Punctuator(Punctuator::CloseBlock) => {
if self.break_when_closingbrase { if self.break_when_closingbraces {
break; break;
} else { } else {
return Err(ParseError::unexpected(token.clone(), None)); return Err(ParseError::unexpected(token.clone(), None));
} }
} }
None => { None => {
if self.break_when_closingbrase { if self.break_when_closingbraces {
return Err(ParseError::AbruptEnd); return Err(ParseError::AbruptEnd);
} else { } else {
break; break;

4
boa/src/syntax/parser/statement/return_stm/mod.rs

@ -55,13 +55,13 @@ impl TokenParser for ReturnStatement {
_ => {} _ => {}
} }
return Ok(Return::new::<Node, Option<_>>(None)); return Ok(Return::new::<Node, Option<_>, Option<_>>(None, None));
} }
let expr = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; let expr = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?;
cursor.expect_semicolon(false, "return statement")?; cursor.expect_semicolon(false, "return statement")?;
Ok(Return::new(expr)) Ok(Return::new(expr, None))
} }
} }

93
boa/src/syntax/parser/statement/switch/mod.rs

@ -3,18 +3,22 @@ mod tests;
use crate::{ use crate::{
syntax::{ syntax::{
ast::{ ast::{node, node::Switch, Keyword, Node, Punctuator, TokenKind},
node::{Case, Switch},
Keyword, Node, Punctuator,
},
parser::{ parser::{
expression::Expression, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, expression::Expression, statement::StatementList, AllowAwait, AllowReturn, AllowYield,
TokenParser, Cursor, ParseError, Token, TokenParser,
}, },
}, },
BoaProfiler, BoaProfiler,
}; };
/// The possible TokenKind which indicate the end of a case statement.
const CASE_BREAK_TOKENS: [TokenKind; 3] = [
TokenKind::Punctuator(Punctuator::CloseBlock),
TokenKind::Keyword(Keyword::Case),
TokenKind::Keyword(Keyword::Default),
];
/// Switch statement parsing. /// Switch statement parsing.
/// ///
/// More information: /// More information:
@ -95,14 +99,81 @@ impl CaseBlock {
} }
impl TokenParser for CaseBlock { impl TokenParser for CaseBlock {
type Output = (Box<[Case]>, Option<Node>); type Output = (Box<[node::Case]>, Option<Node>);
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Self::Output, ParseError> { fn parse(self, cursor: &mut Cursor<'_>) -> Result<Self::Output, ParseError> {
cursor.expect(Punctuator::OpenBlock, "switch case block")?; let mut cases = Vec::<node::Case>::new();
let mut default: Option<Node> = None;
cursor.expect(Punctuator::OpenBlock, "switch start case block")?;
loop {
match cursor.expect(Keyword::Case, "switch case: block") {
Ok(_) => {
// Case statement.
let cond =
Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?;
cursor.expect(Punctuator::Colon, "switch case block start")?;
// CaseClauses[?Yield, ?Await, ?Return]opt let statement_list = StatementList::new(
// CaseClauses[?Yield, ?Await, ?Return]optDefaultClause[?Yield, ?Await, ?Return]CaseClauses[?Yield, ?Await, ?Return]opt self.allow_yield,
self.allow_await,
self.allow_return,
true,
)
.parse_generalised(cursor, &CASE_BREAK_TOKENS)?;
cases.push(node::Case::new(cond, statement_list));
}
Err(ParseError::Expected {
expected: _,
found:
Token {
kind: TokenKind::Keyword(Keyword::Default),
span: s,
},
context: _,
}) => {
// Default statement.
if default.is_some() {
// If default has already been defined then it cannot be defined again and to do so is an error.
return Err(ParseError::unexpected(
Token::new(TokenKind::Keyword(Keyword::Default), s),
Some("Second default clause found in switch statement"),
));
}
cursor.expect(Punctuator::Colon, "switch default case block start")?;
let statement_list = StatementList::new(
self.allow_yield,
self.allow_await,
self.allow_return,
true,
)
.parse_generalised(cursor, &CASE_BREAK_TOKENS)?;
default = Some(node::Block::from(statement_list).into());
}
Err(ParseError::Expected {
expected: _,
found:
Token {
kind: TokenKind::Punctuator(Punctuator::CloseBlock),
span: _,
},
context: _,
}) => {
// End of switch block.
break;
}
Err(e) => {
// Unexpected statement.
return Err(e);
}
}
}
unimplemented!("switch case block parsing") Ok((cases.into_boxed_slice(), default))
} }
} }

98
boa/src/syntax/parser/statement/switch/tests.rs

@ -1 +1,99 @@
use crate::syntax::parser::tests::check_invalid;
/// Checks parsing malformed switch with no closeblock.
#[test]
fn check_switch_no_closeblock() {
check_invalid(
r#"
let a = 10;
switch (a) {
case 10:
a = 20;
break;
"#,
);
}
/// Checks parsing malformed switch in which a case is started but not finished.
#[test]
fn check_switch_case_unclosed() {
check_invalid(
r#"
let a = 10;
switch (a) {
case 10:
a = 20;
"#,
);
}
/// Checks parsing malformed switch with 2 defaults.
#[test]
fn check_switch_two_default() {
check_invalid(
r#"
let a = 10;
switch (a) {
default:
a = 20;
break;
default:
a = 30;
break;
}
"#,
);
}
/// Checks parsing malformed switch with no expression.
#[test]
fn check_switch_no_expr() {
check_invalid(
r#"
let a = 10;
switch {
default:
a = 20;
break;
}
"#,
);
}
/// Checks parsing malformed switch with an unknown label.
#[test]
fn check_switch_unknown_label() {
check_invalid(
r#"
let a = 10;
switch (a) {
fake:
a = 20;
break;
}
"#,
);
}
/// Checks parsing malformed switch with two defaults that are seperated by cases.
#[test]
fn check_switch_seperated_defaults() {
check_invalid(
r#"
let a = 10;
switch (a) {
default:
a = 20;
break;
case 10:
a = 60;
break;
default:
a = 30;
break;
}
"#,
);
}

2
boa/src/syntax/parser/tests.rs

@ -79,7 +79,7 @@ fn hoisting() {
FunctionDecl::new( FunctionDecl::new(
Box::from("hello"), Box::from("hello"),
vec![], vec![],
vec![Return::new(Const::from(10)).into()], vec![Return::new(Const::from(10), None).into()],
) )
.into(), .into(),
VarDeclList::from(vec![VarDecl::new( VarDeclList::from(vec![VarDecl::new(

Loading…
Cancel
Save