Skip to content

Commit

Permalink
Modularize try statement parsing (#390)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
abhijeetbhagat authored May 11, 2020
1 parent 4e5b8ee commit 2851eb0
Show file tree
Hide file tree
Showing 4 changed files with 289 additions and 27 deletions.
99 changes: 99 additions & 0 deletions boa/src/syntax/parser/statement/try_stm/catch.rs
Original file line number Diff line number Diff line change
@@ -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<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 TokenParser for Catch {
type Output = (Option<Node>, Option<Node>);

fn parse(self, cursor: &mut Cursor<'_>) -> Result<Self::Output, ParseError> {
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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)
}
}
47 changes: 47 additions & 0 deletions boa/src/syntax/parser/statement/try_stm/finally.rs
Original file line number Diff line number Diff line change
@@ -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<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 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)?)
}
}
46 changes: 19 additions & 27 deletions boa/src/syntax/parser/statement/try_stm/mod.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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>(
Expand Down
124 changes: 124 additions & 0 deletions boa/src/syntax/parser/statement/try_stm/tests.rs
Original file line number Diff line number Diff line change
@@ -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) {}");
}

0 comments on commit 2851eb0

Please sign in to comment.