From 542b2cc005c46ab7f09758c24f5a6c6ea95ff178 Mon Sep 17 00:00:00 2001 From: Paul Lancaster Date: Fri, 12 Jun 2020 19:09:02 +0100 Subject: [PATCH] Switch impl (#451) Co-authored-by: Iban Eguia --- .vscode/launch.json | 11 +- boa/src/exec/block/mod.rs | 19 +- boa/src/exec/break_node/mod.rs | 16 ++ boa/src/exec/break_node/tests.rs | 17 ++ boa/src/exec/expression/mod.rs | 4 +- boa/src/exec/iteration/mod.rs | 69 +++++- boa/src/exec/iteration/tests.rs | 101 +++++++++ boa/src/exec/mod.rs | 22 +- boa/src/exec/return_smt/mod.rs | 4 +- boa/src/exec/statement_list.rs | 22 +- boa/src/exec/switch/mod.rs | 32 ++- boa/src/exec/switch/tests.rs | 200 ++++++++++++++++++ boa/src/syntax/ast/node/return_smt.rs | 10 +- boa/src/syntax/ast/node/switch.rs | 12 ++ boa/src/syntax/ast/token.rs | 2 +- .../expression/assignment/arrow_function.rs | 1 + .../parser/expression/left_hand_side/call.rs | 1 + .../expression/left_hand_side/member.rs | 1 + boa/src/syntax/parser/function/tests.rs | 30 ++- .../syntax/parser/statement/block/tests.rs | 4 +- boa/src/syntax/parser/statement/mod.rs | 50 ++++- .../syntax/parser/statement/return_stm/mod.rs | 4 +- boa/src/syntax/parser/statement/switch/mod.rs | 93 +++++++- .../syntax/parser/statement/switch/tests.rs | 98 +++++++++ boa/src/syntax/parser/tests.rs | 2 +- 25 files changed, 756 insertions(+), 69 deletions(-) create mode 100644 boa/src/exec/break_node/mod.rs create mode 100644 boa/src/exec/break_node/tests.rs create mode 100644 boa/src/exec/iteration/tests.rs create mode 100644 boa/src/exec/switch/tests.rs diff --git a/.vscode/launch.json b/.vscode/launch.json index c1f754200d..99f2493327 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -36,16 +36,15 @@ "symbolSearchPath": "https://msdl.microsoft.com/download/symbols" }, { - "name": "(Windows) Run Test Debugger", + "name": "(Windows) Launch Debug", "type": "cppvsdbg", "request": "launch", - "program": "${workspaceFolder}/target/debug/boa-ea5ed1ef3ee0cbe1.exe", + "program": "${workspaceRoot}/target/debug/foo.exe", "args": [], "stopAtEntry": false, - "cwd": "${workspaceFolder}", + "cwd": "${workspaceRoot}", "environment": [], - "externalConsole": true, - "preLaunchTask": "Cargo Test Build", - } + "externalConsole": true + }, ] } \ No newline at end of file diff --git a/boa/src/exec/block/mod.rs b/boa/src/exec/block/mod.rs index deec6e432b..c642caa432 100644 --- a/boa/src/exec/block/mod.rs +++ b/boa/src/exec/block/mod.rs @@ -1,6 +1,6 @@ //! Block statement execution. -use super::{Executable, Interpreter}; +use super::{Executable, Interpreter, InterpreterState}; use crate::{ builtins::value::{ResultValue, Value}, environment::lexical_environment::new_declarative_environment, @@ -22,9 +22,20 @@ impl Executable for Block { for statement in self.statements() { obj = statement.run(interpreter)?; - // early return - if interpreter.is_return { - break; + match interpreter.get_current_state() { + InterpreterState::Return => { + // Early return. + break; + } + InterpreterState::Break(_label) => { + // TODO, break to a label. + + // Early break. + break; + } + _ => { + // Continue execution + } } } diff --git a/boa/src/exec/break_node/mod.rs b/boa/src/exec/break_node/mod.rs new file mode 100644 index 0000000000..11fdd4d5a6 --- /dev/null +++ b/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()) + } +} diff --git a/boa/src/exec/break_node/tests.rs b/boa/src/exec/break_node/tests.rs new file mode 100644 index 0000000000..2cbb2db032 --- /dev/null +++ b/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())) + ); +} diff --git a/boa/src/exec/expression/mod.rs b/boa/src/exec/expression/mod.rs index 4062bfc71f..80d65236f5 100644 --- a/boa/src/exec/expression/mod.rs +++ b/boa/src/exec/expression/mod.rs @@ -1,6 +1,6 @@ //! Expression execution. -use super::{Executable, Interpreter}; +use super::{Executable, Interpreter, InterpreterState}; use crate::{ builtins::{ object::{INSTANCE_PROTOTYPE, PROTOTYPE}, @@ -48,7 +48,7 @@ impl Executable for Call { let fnct_result = interpreter.call(&func, &mut this, &v_args); // unset the early return flag - interpreter.is_return = false; + interpreter.set_current_state(InterpreterState::Executing); fnct_result } diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index ad91ffdec8..d0acad30dd 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -1,6 +1,6 @@ //! Iteration node execution. -use super::{Executable, Interpreter}; +use super::{Executable, Interpreter, InterpreterState}; use crate::{ builtins::value::{ResultValue, Value}, environment::lexical_environment::new_declarative_environment, @@ -9,6 +9,9 @@ use crate::{ }; use std::borrow::Borrow; +#[cfg(test)] +mod tests; + impl Executable for ForLoop { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { // Create the block environment. @@ -30,7 +33,23 @@ impl Executable for ForLoop { .transpose()? .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() { final_expr.run(interpreter)?; @@ -49,6 +68,21 @@ impl Executable for WhileLoop { let mut result = Value::undefined(); while self.cond().run(interpreter)?.borrow().is_true() { 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) } @@ -57,8 +91,39 @@ impl Executable for WhileLoop { impl Executable for DoWhileLoop { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { 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() { 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) } diff --git a/boa/src/exec/iteration/tests.rs b/boa/src/exec/iteration/tests.rs new file mode 100644 index 0000000000..458546364e --- /dev/null +++ b/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"); +} diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 4d8f25ed6c..407b6a1c9e 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -2,6 +2,7 @@ mod array; mod block; +mod break_node; mod conditional; mod declaration; mod exception; @@ -45,6 +46,12 @@ pub trait Executable { fn run(&self, interpreter: &mut Interpreter) -> ResultValue; } +#[derive(Debug, Eq, PartialEq)] +pub(crate) enum InterpreterState { + Executing, + Return, + Break(Option), +} #[derive(Clone, Copy, Debug, PartialEq)] pub enum PreferredType { String, @@ -55,8 +62,8 @@ pub enum PreferredType { /// A Javascript intepreter #[derive(Debug)] pub struct Interpreter { - /// Wether it's running a return statement. - is_return: bool, + current_state: InterpreterState, + /// realm holds both the global object and the environment pub realm: Realm, } @@ -65,8 +72,8 @@ impl Interpreter { /// Creates a new interpreter. pub fn new(realm: Realm) -> Self { Self { + current_state: InterpreterState::Executing, realm, - is_return: false, } } @@ -485,6 +492,14 @@ impl Interpreter { _ => 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 { @@ -539,6 +554,7 @@ impl Executable for Node { Ok(interpreter.realm().environment.get_this_binding()) } Node::Try(ref try_node) => try_node.run(interpreter), + Node::Break(ref break_node) => break_node.run(interpreter), ref i => unimplemented!("{:?}", i), } } diff --git a/boa/src/exec/return_smt/mod.rs b/boa/src/exec/return_smt/mod.rs index 5a75d3610d..3fc0a523a8 100644 --- a/boa/src/exec/return_smt/mod.rs +++ b/boa/src/exec/return_smt/mod.rs @@ -1,4 +1,4 @@ -use super::{Executable, Interpreter}; +use super::{Executable, Interpreter, InterpreterState}; use crate::{ builtins::value::{ResultValue, Value}, syntax::ast::node::Return, @@ -11,7 +11,7 @@ impl Executable for Return { None => Ok(Value::undefined()), }; // Set flag for return - interpreter.is_return = true; + interpreter.set_current_state(InterpreterState::Return); result } } diff --git a/boa/src/exec/statement_list.rs b/boa/src/exec/statement_list.rs index 4863232171..56862034fe 100644 --- a/boa/src/exec/statement_list.rs +++ b/boa/src/exec/statement_list.rs @@ -1,6 +1,6 @@ //! Statement list execution. -use super::{Executable, Interpreter}; +use super::{Executable, Interpreter, InterpreterState}; use crate::{ builtins::value::{ResultValue, Value}, syntax::ast::node::StatementList, @@ -11,12 +11,24 @@ impl Executable for StatementList { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { let _timer = BoaProfiler::global().start_event("StatementList", "exec"); let mut obj = Value::null(); + interpreter.set_current_state(InterpreterState::Executing); for (i, item) in self.statements().iter().enumerate() { let val = item.run(interpreter)?; - // early return - if interpreter.is_return { - obj = val; - break; + match interpreter.get_current_state() { + InterpreterState::Return => { + // Early return. + obj = val; + break; + } + InterpreterState::Break(_label) => { + // TODO, break to a label. + + // Early break. + break; + } + _ => { + // Continue execution + } } if i + 1 == self.statements().len() { obj = val; diff --git a/boa/src/exec/switch/mod.rs b/boa/src/exec/switch/mod.rs index d5e5e93e48..ea20226041 100644 --- a/boa/src/exec/switch/mod.rs +++ b/boa/src/exec/switch/mod.rs @@ -1,24 +1,46 @@ -use super::{Executable, Interpreter}; +use super::{Executable, Interpreter, InterpreterState}; use crate::{ builtins::value::{ResultValue, Value}, syntax::ast::node::Switch, }; +#[cfg(test)] +mod tests; + impl Executable for Switch { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { let default = self.default(); let val = self.val().run(interpreter)?; let mut result = Value::null(); 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() { let cond = case.condition(); let block = case.body(); - if val.strict_equals(&cond.run(interpreter)?) { + if fall_through || val.strict_equals(&cond.run(interpreter)?) { 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 let Some(default) = default { diff --git a/boa/src/exec/switch/tests.rs b/boa/src/exec/switch/tests.rs new file mode 100644 index 0000000000..2c6f0ad3b1 --- /dev/null +++ b/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); + } +} diff --git a/boa/src/syntax/ast/node/return_smt.rs b/boa/src/syntax/ast/node/return_smt.rs index 2cf64fa4f3..468c555994 100644 --- a/boa/src/syntax/ast/node/return_smt.rs +++ b/boa/src/syntax/ast/node/return_smt.rs @@ -24,25 +24,31 @@ use serde::{Deserialize, Serialize}; /// [spec]: https://tc39.es/ecma262/#prod-ReturnStatement /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(transparent))] #[derive(Clone, Debug, Trace, Finalize, PartialEq)] pub struct Return { expr: Option>, + label: Option>, } impl Return { + pub fn label(&self) -> Option<&str> { + self.label.as_ref().map(Box::as_ref) + } + pub fn expr(&self) -> Option<&Node> { self.expr.as_ref().map(Box::as_ref) } /// Creates a `Return` AST node. - pub fn new(expr: OE) -> Self + pub fn new(expr: OE, label: L) -> Self where E: Into, OE: Into>, + L: Into>>, { Self { expr: expr.into().map(E::into).map(Box::new), + label: label.into(), } } } diff --git a/boa/src/syntax/ast/node/switch.rs b/boa/src/syntax/ast/node/switch.rs index f4de8c0743..e8e5149637 100644 --- a/boa/src/syntax/ast/node/switch.rs +++ b/boa/src/syntax/ast/node/switch.rs @@ -24,6 +24,18 @@ impl Case { pub fn body(&self) -> &StatementList { &self.body } + + /// Creates a `Case` AST node. + pub fn new(condition: C, body: B) -> Self + where + C: Into, + B: Into, + { + Self { + condition: condition.into(), + body: body.into(), + } + } } /// The `switch` statement evaluates an expression, matching the expression's value to a case diff --git a/boa/src/syntax/ast/token.rs b/boa/src/syntax/ast/token.rs index f29e501a68..c374aa1122 100644 --- a/boa/src/syntax/ast/token.rs +++ b/boa/src/syntax/ast/token.rs @@ -31,7 +31,7 @@ pub struct Token { /// The token kind, which contains the actual data of the token. pub(crate) kind: TokenKind, /// The token position in the original source code. - span: Span, + pub(crate) span: Span, } impl Token { diff --git a/boa/src/syntax/parser/expression/assignment/arrow_function.rs b/boa/src/syntax/parser/expression/assignment/arrow_function.rs index a802ace594..199864c606 100644 --- a/boa/src/syntax/parser/expression/assignment/arrow_function.rs +++ b/boa/src/syntax/parser/expression/assignment/arrow_function.rs @@ -119,6 +119,7 @@ impl TokenParser for ConciseBody { } _ => Ok(StatementList::from(vec![Return::new( ExpressionBody::new(self.allow_in, false).parse(cursor)?, + None, ) .into()])), } diff --git a/boa/src/syntax/parser/expression/left_hand_side/call.rs b/boa/src/syntax/parser/expression/left_hand_side/call.rs index c1a5b95891..ff25395334 100644 --- a/boa/src/syntax/parser/expression/left_hand_side/call.rs +++ b/boa/src/syntax/parser/expression/left_hand_side/call.rs @@ -8,6 +8,7 @@ //! [spec]: https://tc39.es/ecma262/#prod-CallExpression use super::arguments::Arguments; + use crate::{ syntax::{ ast::{ diff --git a/boa/src/syntax/parser/expression/left_hand_side/member.rs b/boa/src/syntax/parser/expression/left_hand_side/member.rs index 91a6f04a43..5fabff6885 100644 --- a/boa/src/syntax/parser/expression/left_hand_side/member.rs +++ b/boa/src/syntax/parser/expression/left_hand_side/member.rs @@ -6,6 +6,7 @@ //! [spec]: https://tc39.es/ecma262/#prod-MemberExpression use super::arguments::Arguments; + use crate::{ syntax::{ ast::{ diff --git a/boa/src/syntax/parser/function/tests.rs b/boa/src/syntax/parser/function/tests.rs index 7ca527a121..52e7f922f6 100644 --- a/boa/src/syntax/parser/function/tests.rs +++ b/boa/src/syntax/parser/function/tests.rs @@ -14,7 +14,7 @@ fn check_basic() { vec![FunctionDecl::new( Box::from("foo"), vec![FormalParameter::new("a", None, false)], - vec![Return::new(Identifier::from("a")).into()], + vec![Return::new(Identifier::from("a"), None).into()], ) .into()], ); @@ -28,7 +28,7 @@ fn check_basic_semicolon_insertion() { vec![FunctionDecl::new( Box::from("foo"), vec![FormalParameter::new("a", None, false)], - vec![Return::new(Identifier::from("a")).into()], + vec![Return::new(Identifier::from("a"), None).into()], ) .into()], ); @@ -42,7 +42,7 @@ fn check_empty_return() { vec![FunctionDecl::new( Box::from("foo"), vec![FormalParameter::new("a", None, false)], - vec![Return::new::>(None).into()], + vec![Return::new::, Option<_>>(None, None).into()], ) .into()], ); @@ -56,7 +56,7 @@ fn check_empty_return_semicolon_insertion() { vec![FunctionDecl::new( Box::from("foo"), vec![FormalParameter::new("a", None, false)], - vec![Return::new::>(None).into()], + vec![Return::new::, Option<_>>(None, None).into()], ) .into()], ); @@ -115,11 +115,10 @@ fn check_arrow() { FormalParameter::new("a", None, false), FormalParameter::new("b", None, false), ], - vec![Return::new(BinOp::new( - NumOp::Add, - Identifier::from("a"), - Identifier::from("b"), - )) + vec![Return::new( + BinOp::new(NumOp::Add, Identifier::from("a"), Identifier::from("b")), + None, + ) .into()], ) .into()], @@ -136,11 +135,10 @@ fn check_arrow_semicolon_insertion() { FormalParameter::new("a", None, false), FormalParameter::new("b", None, false), ], - vec![Return::new(BinOp::new( - NumOp::Add, - Identifier::from("a"), - Identifier::from("b"), - )) + vec![Return::new( + BinOp::new(NumOp::Add, Identifier::from("a"), Identifier::from("b")), + None, + ) .into()], ) .into()], @@ -157,7 +155,7 @@ fn check_arrow_epty_return() { FormalParameter::new("a", None, false), FormalParameter::new("b", None, false), ], - vec![Return::new::>(None).into()], + vec![Return::new::, Option<_>>(None, None).into()], ) .into()], ); @@ -173,7 +171,7 @@ fn check_arrow_empty_return_semicolon_insertion() { FormalParameter::new("a", None, false), FormalParameter::new("b", None, false), ], - vec![Return::new::>(None).into()], + vec![Return::new::, Option<_>>(None, None).into()], ) .into()], ); diff --git a/boa/src/syntax/parser/statement/block/tests.rs b/boa/src/syntax/parser/statement/block/tests.rs index e0c1797c75..c14b7c81a9 100644 --- a/boa/src/syntax/parser/statement/block/tests.rs +++ b/boa/src/syntax/parser/statement/block/tests.rs @@ -51,7 +51,7 @@ fn non_empty() { FunctionDecl::new( "hello".to_owned().into_boxed_str(), vec![], - vec![Return::new(Const::from(10)).into()], + vec![Return::new(Const::from(10), None).into()], ) .into(), VarDeclList::from(vec![VarDecl::new( @@ -77,7 +77,7 @@ fn hoisting() { FunctionDecl::new( "hello".to_owned().into_boxed_str(), vec![], - vec![Return::new(Const::from(10)).into()], + vec![Return::new(Const::from(10), None).into()], ) .into(), VarDeclList::from(vec![VarDecl::new( diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index 3853ba843f..461ab4c672 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -57,6 +57,7 @@ use crate::{ /// - `WithStatement` /// - `LabelledStatement` /// - `ThrowStatement` +/// - `SwitchStatement` /// - `TryStatement` /// - `DebuggerStatement` /// @@ -184,7 +185,7 @@ pub(super) struct StatementList { allow_yield: AllowYield, allow_await: AllowAwait, allow_return: AllowReturn, - break_when_closingbrase: bool, + break_when_closingbraces: bool, } impl StatementList { @@ -193,7 +194,7 @@ impl StatementList { allow_yield: Y, allow_await: A, allow_return: R, - break_when_closingbrase: bool, + break_when_closingbraces: bool, ) -> Self where Y: Into, @@ -204,9 +205,48 @@ impl StatementList { allow_yield: allow_yield.into(), allow_await: allow_await.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 { + 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()) + } } impl TokenParser for StatementList { @@ -219,14 +259,14 @@ impl TokenParser for StatementList { loop { match cursor.peek(0) { Some(token) if token.kind == TokenKind::Punctuator(Punctuator::CloseBlock) => { - if self.break_when_closingbrase { + if self.break_when_closingbraces { break; } else { return Err(ParseError::unexpected(token.clone(), None)); } } None => { - if self.break_when_closingbrase { + if self.break_when_closingbraces { return Err(ParseError::AbruptEnd); } else { break; diff --git a/boa/src/syntax/parser/statement/return_stm/mod.rs b/boa/src/syntax/parser/statement/return_stm/mod.rs index c1b0c6cf08..293b138ef1 100644 --- a/boa/src/syntax/parser/statement/return_stm/mod.rs +++ b/boa/src/syntax/parser/statement/return_stm/mod.rs @@ -55,13 +55,13 @@ impl TokenParser for ReturnStatement { _ => {} } - return Ok(Return::new::>(None)); + return Ok(Return::new::, Option<_>>(None, None)); } let expr = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; cursor.expect_semicolon(false, "return statement")?; - Ok(Return::new(expr)) + Ok(Return::new(expr, None)) } } diff --git a/boa/src/syntax/parser/statement/switch/mod.rs b/boa/src/syntax/parser/statement/switch/mod.rs index 8a64086cf1..c0aac5663a 100644 --- a/boa/src/syntax/parser/statement/switch/mod.rs +++ b/boa/src/syntax/parser/statement/switch/mod.rs @@ -3,18 +3,22 @@ mod tests; use crate::{ syntax::{ - ast::{ - node::{Case, Switch}, - Keyword, Node, Punctuator, - }, + ast::{node, node::Switch, Keyword, Node, Punctuator, TokenKind}, parser::{ - expression::Expression, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, - TokenParser, + expression::Expression, statement::StatementList, AllowAwait, AllowReturn, AllowYield, + Cursor, ParseError, Token, TokenParser, }, }, 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. /// /// More information: @@ -95,14 +99,81 @@ impl CaseBlock { } impl TokenParser for CaseBlock { - type Output = (Box<[Case]>, Option); + type Output = (Box<[node::Case]>, Option); fn parse(self, cursor: &mut Cursor<'_>) -> Result { - cursor.expect(Punctuator::OpenBlock, "switch case block")?; + let mut cases = Vec::::new(); + let mut default: Option = 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 - // CaseClauses[?Yield, ?Await, ?Return]optDefaultClause[?Yield, ?Await, ?Return]CaseClauses[?Yield, ?Await, ?Return]opt + let statement_list = StatementList::new( + 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)) } } diff --git a/boa/src/syntax/parser/statement/switch/tests.rs b/boa/src/syntax/parser/statement/switch/tests.rs index 8b13789179..568d94a770 100644 --- a/boa/src/syntax/parser/statement/switch/tests.rs +++ b/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; + } + "#, + ); +} diff --git a/boa/src/syntax/parser/tests.rs b/boa/src/syntax/parser/tests.rs index c5e83d03d2..cf41a996c1 100644 --- a/boa/src/syntax/parser/tests.rs +++ b/boa/src/syntax/parser/tests.rs @@ -79,7 +79,7 @@ fn hoisting() { FunctionDecl::new( Box::from("hello"), vec![], - vec![Return::new(Const::from(10)).into()], + vec![Return::new(Const::from(10), None).into()], ) .into(), VarDeclList::from(vec![VarDecl::new(