From 2851eb02a7ce7b7d061aafb4d56abab65a8e6206 Mon Sep 17 00:00:00 2001 From: abhi Date: Mon, 11 May 2020 22:51:26 +0530 Subject: [PATCH] Modularize try statement parsing (#390) * Fix catch parsing - move the cursor to next token * Refactor catch and finally parsing into separate modules * Refactor catchparam parsing into separate module and add more tests * Refactoring - use ? instead of match --- .../syntax/parser/statement/try_stm/catch.rs | 99 ++++++++++++++ .../parser/statement/try_stm/finally.rs | 47 +++++++ .../syntax/parser/statement/try_stm/mod.rs | 46 +++---- .../syntax/parser/statement/try_stm/tests.rs | 124 ++++++++++++++++++ 4 files changed, 289 insertions(+), 27 deletions(-) create mode 100644 boa/src/syntax/parser/statement/try_stm/catch.rs create mode 100644 boa/src/syntax/parser/statement/try_stm/finally.rs diff --git a/boa/src/syntax/parser/statement/try_stm/catch.rs b/boa/src/syntax/parser/statement/try_stm/catch.rs new file mode 100644 index 0000000000..addf8a8880 --- /dev/null +++ b/boa/src/syntax/parser/statement/try_stm/catch.rs @@ -0,0 +1,99 @@ +use crate::syntax::{ + ast::{keyword::Keyword, node::Node, punc::Punctuator}, + parser::{ + statement::{block::Block, BindingIdentifier}, + AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, ParseResult, TokenParser, + }, +}; + +/// Catch parsing +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch +/// [spec]: https://tc39.es/ecma262/#prod-Catch +#[derive(Debug, Clone, Copy)] +pub(super) struct Catch { + allow_yield: AllowYield, + allow_await: AllowAwait, + allow_return: AllowReturn, +} + +impl Catch { + /// Creates a new `Catch` block parser. + 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 Catch { + type Output = (Option, Option); + + fn parse(self, cursor: &mut Cursor<'_>) -> Result { + cursor.expect(Keyword::Catch, "try statement")?; + let catch_param = if cursor.next_if(Punctuator::OpenParen).is_some() { + let catch_param = + CatchParameter::new(self.allow_yield, self.allow_await).parse(cursor)?; + cursor.expect(Punctuator::CloseParen, "catch in try statement")?; + Some(catch_param) + } else { + None + }; + + // Catch block + Ok(( + Some(Block::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?), + catch_param, + )) + } +} + +/// CatchParameter parsing +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch +/// [spec]: https://tc39.es/ecma262/#prod-CatchParameter +#[derive(Debug, Clone, Copy)] +pub(super) struct CatchParameter { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl CatchParameter { + /// Creates a new `CatchParameter` parser. + pub(super) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for CatchParameter { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + // TODO: should accept BindingPattern + BindingIdentifier::new(self.allow_yield, self.allow_await) + .parse(cursor) + .map(Node::local) + } +} diff --git a/boa/src/syntax/parser/statement/try_stm/finally.rs b/boa/src/syntax/parser/statement/try_stm/finally.rs new file mode 100644 index 0000000000..5a7037beca --- /dev/null +++ b/boa/src/syntax/parser/statement/try_stm/finally.rs @@ -0,0 +1,47 @@ +use crate::syntax::{ + ast::{keyword::Keyword, node::Node}, + parser::{ + statement::block::Block, AllowAwait, AllowReturn, AllowYield, Cursor, ParseResult, + TokenParser, + }, +}; + +/// Finally parsing +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch +/// [spec]: https://tc39.es/ecma262/#prod-Finally +#[derive(Debug, Clone, Copy)] +pub(super) struct Finally { + allow_yield: AllowYield, + allow_await: AllowAwait, + allow_return: AllowReturn, +} + +impl Finally { + /// Creates a new `Finally` block parser. + 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 Finally { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + cursor.expect(Keyword::Finally, "try statement")?; + Ok(Block::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?) + } +} diff --git a/boa/src/syntax/parser/statement/try_stm/mod.rs b/boa/src/syntax/parser/statement/try_stm/mod.rs index 5cdcd9c3e0..aa015c0081 100644 --- a/boa/src/syntax/parser/statement/try_stm/mod.rs +++ b/boa/src/syntax/parser/statement/try_stm/mod.rs @@ -1,13 +1,15 @@ +mod catch; +mod finally; + #[cfg(test)] mod tests; +use self::catch::Catch; +use self::finally::Finally; use super::block::Block; use crate::syntax::{ - ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, - parser::{ - statement::BindingIdentifier, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, - ParseResult, TokenParser, - }, + ast::{keyword::Keyword, node::Node, token::TokenKind}, + parser::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, }; /// Try...catch statement parsing @@ -66,33 +68,23 @@ impl TokenParser for TryStatement { )); } - // CATCH let (catch, param) = if next_token.kind == TokenKind::Keyword(Keyword::Catch) { - // Catch binding - cursor.expect(Punctuator::OpenParen, "catch in try statement")?; - // TODO: CatchParameter - BindingPattern - let catch_param = BindingIdentifier::new(self.allow_yield, self.allow_await) - .parse(cursor) - .map(Node::local)?; - cursor.expect(Punctuator::CloseParen, "catch in try statement")?; - - // Catch block - ( - Some( - Block::new(self.allow_yield, self.allow_await, self.allow_return) - .parse(cursor)?, - ), - Some(catch_param), - ) + Catch::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)? } else { (None, None) }; - // FINALLY - let finally_block = if cursor.next_if(Keyword::Finally).is_some() { - Some(Block::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?) - } else { - None + let next_token = cursor.peek(0); + let finally_block = match next_token { + Some(token) => match token.kind { + TokenKind::Keyword(Keyword::Finally) => Some( + Finally::new(self.allow_yield, self.allow_await, self.allow_return) + .parse(cursor)?, + ), + _ => None, + }, + + None => None, }; Ok(Node::try_node::<_, _, _, _, Node, Node, Node>( diff --git a/boa/src/syntax/parser/statement/try_stm/tests.rs b/boa/src/syntax/parser/statement/try_stm/tests.rs index 8b13789179..cecf9bb144 100644 --- a/boa/src/syntax/parser/statement/try_stm/tests.rs +++ b/boa/src/syntax/parser/statement/try_stm/tests.rs @@ -1 +1,125 @@ +use crate::syntax::{ + ast::node::Node, + parser::tests::{check_invalid, check_parser}, +}; +#[test] +fn check_inline_with_empty_try_catch() { + check_parser( + "try { } catch(e) {}", + vec![Node::try_node::<_, _, _, _, Node, Node, Node>( + Node::block(vec![]), + Node::block(vec![]), + Node::local("e"), + None, + )], + ); +} + +#[test] +fn check_inline_with_var_decl_inside_try() { + check_parser( + "try { var x = 1; } catch(e) {}", + vec![Node::try_node::<_, _, _, _, Node, Node, Node>( + Node::block(vec![Node::var_decl(vec![( + String::from("x"), + Some(Node::const_node(1)), + )])]), + Node::block(vec![]), + Node::local("e"), + None, + )], + ); +} + +#[test] +fn check_inline_with_var_decl_inside_catch() { + check_parser( + "try { var x = 1; } catch(e) { var x = 1; }", + vec![Node::try_node::<_, _, _, _, Node, Node, Node>( + Node::block(vec![Node::var_decl(vec![( + String::from("x"), + Some(Node::const_node(1)), + )])]), + Node::block(vec![Node::var_decl(vec![( + String::from("x"), + Some(Node::const_node(1)), + )])]), + Node::local("e"), + None, + )], + ); +} + +#[test] +fn check_inline_with_empty_try_catch_finally() { + check_parser( + "try {} catch(e) {} finally {}", + vec![Node::try_node::<_, _, _, _, Node, Node, Node>( + Node::block(vec![]), + Node::block(vec![]), + Node::local("e"), + Node::block(vec![]), + )], + ); +} + +#[test] +fn check_inline_with_empty_try_finally() { + check_parser( + "try {} finally {}", + vec![Node::try_node::<_, _, _, _, Node, Node, Node>( + Node::block(vec![]), + None, + None, + Node::block(vec![]), + )], + ); +} + +#[test] +fn check_inline_with_empty_try_var_decl_in_finally() { + check_parser( + "try {} finally { var x = 1; }", + vec![Node::try_node::<_, _, _, _, Node, Node, Node>( + Node::block(vec![]), + None, + None, + Node::block(vec![Node::var_decl(vec![( + String::from("x"), + Some(Node::const_node(1)), + )])]), + )], + ); +} + +#[test] +fn check_inline_empty_try_paramless_catch() { + check_parser( + "try {} catch { var x = 1; }", + vec![Node::try_node::<_, _, _, _, Node, Node, Node>( + Node::block(vec![]), + Node::block(vec![Node::var_decl(vec![( + String::from("x"), + Some(Node::const_node(1)), + )])]), + None, + None, + )], + ); +} + +#[test] +fn check_inline_invalid_catch() { + check_invalid("try {} catch"); +} + +#[test] +fn check_inline_invalid_catch_without_closing_paren() { + check_invalid("try {} catch(e {}"); +} + +#[test] +fn check_inline_invalid_catch_parameter() { + check_invalid("try {} catch(1) {}"); +}