Skip to content

Commit

Permalink
Merge 5f19656 into 795a70e
Browse files Browse the repository at this point in the history
  • Loading branch information
Razican authored Apr 10, 2020
2 parents 795a70e + 5f19656 commit 40ed44e
Show file tree
Hide file tree
Showing 8 changed files with 2,194 additions and 1,686 deletions.
Empty file.
125 changes: 118 additions & 7 deletions boa/src/syntax/parser/cursor.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
//! Cursor implementation for the parser.
use crate::syntax::ast::token::Token;
use super::ParseError;
use crate::syntax::ast::{
punc::Punctuator,
token::{Token, TokenKind},
};

/// Token cursor.
///
Expand Down Expand Up @@ -45,19 +49,28 @@ impl<'a> Cursor<'a> {
token
}

/// Moves the cursor to the next token after skipping tokens based on the predicate.
pub(super) fn next_skip<P>(&mut self, mut skip: P) -> Option<&'a Token>
/// Skips all the tokens until it finds a token that makes the `skip` function return `true`.
///
/// Calling `next()` after this will return the token that shouldn't have been skipped.
pub(super) fn skip<P>(&mut self, mut skip: P)
where
P: FnMut(&Token) -> bool,
{
while let Some(token) = self.tokens.get(self.pos) {
self.pos += 1;

if !skip(token) {
return Some(token);
return;
}
self.pos += 1;
}
None
}

/// Moves the cursor to the next token after skipping tokens based on the predicate.
pub(super) fn next_skip<P>(&mut self, skip: P) -> Option<&'a Token>
where
P: FnMut(&Token) -> bool,
{
self.skip(skip);
self.next()
}

/// Peeks the next token without moving the cursor.
Expand All @@ -66,6 +79,8 @@ impl<'a> Cursor<'a> {
}

/// Peeks the next token after skipping tokens based on the predicate.
///
/// It will not change the cursor position.
pub(super) fn peek_skip<P>(&self, mut skip: P) -> Option<&'a Token>
where
P: FnMut(&Token) -> bool,
Expand Down Expand Up @@ -99,4 +114,100 @@ impl<'a> Cursor<'a> {
self.tokens.get(self.pos - 1)
}
}

/// Returns an error if the next token is not of kind `kind`.
///
/// Note: it will consume the next token.
pub(super) fn expect(
&mut self,
kind: TokenKind,
routine: Option<&'static str>,
) -> Result<(), ParseError> {
let next_token = self.next().ok_or(ParseError::AbruptEnd)?;
if next_token.kind == kind {
Ok(())
} else {
Err(ParseError::Expected(
vec![kind],
next_token.clone(),
routine,
))
}
}

/// Returns an error if the next token is not the punctuator `p`.
///
/// Note: it will consume the next token.
pub(super) fn expect_punc(
&mut self,
p: Punctuator,
routine: Option<&'static str>,
) -> Result<(), ParseError> {
self.expect(TokenKind::Punctuator(p), routine)
}

/// It will check if the next token is a semicolon.
///
/// It will automatically insert a semicolon if needed, as specified in the [spec][spec].
///
/// [spec]: https://tc39.es/ecma262/#sec-automatic-semicolon-insertion
pub(super) fn expect_semicolon(
&mut self,
routine: Option<&'static str>,
) -> Result<(), ParseError> {
match self.peek(0) {
Some(tk) => match tk.kind {
TokenKind::Punctuator(Punctuator::Semicolon) => {
let _ = self.next();
Ok(())
}
TokenKind::LineTerminator | TokenKind::Punctuator(Punctuator::CloseBlock) => Ok(()),
// TODO: The previous token is ) and the inserted semicolon would then be parsed as
// the terminating semicolon of a do-while statement (13.7.2).
_ => Err(ParseError::Expected(
vec![TokenKind::Punctuator(Punctuator::Semicolon)],
tk.clone(),
routine,
)),
},
None => Ok(()),
}
}

/// Consume the next token skipping line terminators.
pub(super) fn next_skip_lineterminator(&mut self) -> Option<&'a Token> {
self.next_skip(|tk| tk.kind == TokenKind::LineTerminator)
}

/// Peek the next token skipping line terminators.
pub(super) fn peek_skip_lineterminator(&mut self) -> Option<&'a Token> {
self.peek_skip(|tk| tk.kind == TokenKind::LineTerminator)
}

/// Advance the cursor to the next token and retrieve it, only if it's of `kind` type.
///
/// When the next token is a `kind` token, get the token, otherwise return `None`. This
/// function skips line terminators.
pub(super) fn next_if_skip_lineterminator(&mut self, kind: TokenKind) -> Option<&'a Token> {
let next_token = self.peek_skip_lineterminator()?;

if next_token.kind == kind {
self.next_skip_lineterminator()
} else {
None
}
}

/// Advance the cursor to the next token and retrieve it, only if it's of `kind` type.
///
/// When the next token is a `kind` token, get the token, otherwise return `None`.
pub(super) fn next_if(&mut self, kind: TokenKind) -> Option<&'a Token> {
let next_token = self.peek(0)?;

if next_token.kind == kind {
self.next()
} else {
None
}
}
}
110 changes: 110 additions & 0 deletions boa/src/syntax/parser/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//! Error and result implementation for the parser.
use crate::syntax::ast::{
keyword::Keyword,
node::Node,
pos::Position,
token::{Token, TokenKind},
};
use std::fmt;

/// Result of a parsing operation.
pub type ParseResult = Result<Node, ParseError>;

/// `ParseError` is an enum which represents errors encounted during parsing an expression
#[derive(Debug, Clone)]
pub enum ParseError {
/// When it expected a certain kind of token, but got another as part of something
Expected(Vec<TokenKind>, Token, Option<&'static str>),
/// When it expected a certain expression, but got another
ExpectedExpr(&'static str, Node, Position),
/// When it didn't expect this keyword
UnexpectedKeyword(Keyword, Position),
/// When a token is unexpected
Unexpected(Token, Option<&'static str>),
/// When there is an abrupt end to the parsing
AbruptEnd,
/// Out of range error, attempting to set a position where there is no token
RangeError,
/// Catch all General Error
General(&'static str, Option<Position>),
}

impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseError::Expected(expected, actual, routine) => write!(
f,
"Expected {}, got '{}'{} at line {}, col {}",
if expected.len() == 1 {
format!(
"token '{}'",
expected.first().map(TokenKind::to_string).unwrap()
)
} else {
format!(
"one of {}",
expected
.iter()
.enumerate()
.map(|(i, t)| {
format!(
"{}'{}'",
if i == 0 {
""
} else if i == expected.len() - 1 {
" or "
} else {
", "
},
t
)
})
.collect::<String>()
)
},
actual,
if let Some(routine) = routine {
format!(" in {}", routine)
} else {
String::new()
},
actual.pos.line_number,
actual.pos.column_number
),
ParseError::ExpectedExpr(expected, actual, pos) => write!(
f,
"Expected expression '{}', got '{}' at line {}, col {}",
expected, actual, pos.line_number, pos.column_number
),
ParseError::UnexpectedKeyword(keyword, pos) => write!(
f,
"Unexpected keyword: '{}' at line {}, col {}",
keyword, pos.line_number, pos.column_number
),
ParseError::Unexpected(tok, msg) => write!(
f,
"Unexpected Token '{}'{} at line {}, col {}",
tok,
if let Some(m) = msg {
format!(", {}", m)
} else {
String::new()
},
tok.pos.line_number,
tok.pos.column_number
),
ParseError::AbruptEnd => write!(f, "Abrupt End"),
ParseError::General(msg, pos) => write!(
f,
"{}{}",
msg,
if let Some(pos) = pos {
format!(" at line {}, col {}", pos.line_number, pos.column_number)
} else {
String::new()
}
),
ParseError::RangeError => write!(f, "RangeError!"),
}
}
}
Loading

0 comments on commit 40ed44e

Please sign in to comment.