mirror of https://github.com/boa-dev/boa.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
176 lines
5.4 KiB
176 lines
5.4 KiB
//! Cursor implementation for the parser. |
|
mod buffered_lexer; |
|
|
|
use super::ParseError; |
|
use crate::syntax::{ |
|
ast::Punctuator, |
|
lexer::{InputElement, Lexer, Position, Token, TokenKind}, |
|
}; |
|
use buffered_lexer::BufferedLexer; |
|
use std::io::Read; |
|
|
|
/// The result of a peek for a semicolon. |
|
#[derive(Debug)] |
|
pub(super) enum SemicolonResult<'s> { |
|
Found(Option<&'s Token>), |
|
NotFound(&'s Token), |
|
} |
|
|
|
/// Token cursor. |
|
/// |
|
/// This internal structure gives basic testable operations to the parser. |
|
#[derive(Debug)] |
|
pub(super) struct Cursor<R> { |
|
buffered_lexer: BufferedLexer<R>, |
|
} |
|
|
|
impl<R> Cursor<R> |
|
where |
|
R: Read, |
|
{ |
|
/// Creates a new cursor with the given reader. |
|
#[inline] |
|
pub(super) fn new(reader: R) -> Self { |
|
Self { |
|
buffered_lexer: Lexer::new(reader).into(), |
|
} |
|
} |
|
|
|
#[inline] |
|
pub(super) fn set_goal(&mut self, elm: InputElement) { |
|
self.buffered_lexer.set_goal(elm) |
|
} |
|
|
|
#[inline] |
|
pub(super) fn lex_regex(&mut self, start: Position) -> Result<Token, ParseError> { |
|
self.buffered_lexer.lex_regex(start) |
|
} |
|
|
|
#[inline] |
|
pub(super) fn lex_template(&mut self, start: Position) -> Result<Token, ParseError> { |
|
self.buffered_lexer.lex_template(start) |
|
} |
|
|
|
#[inline] |
|
pub(super) fn next(&mut self) -> Result<Option<Token>, ParseError> { |
|
self.buffered_lexer.next(true) |
|
} |
|
|
|
#[inline] |
|
pub(super) fn peek(&mut self, skip_n: usize) -> Result<Option<&Token>, ParseError> { |
|
self.buffered_lexer.peek(skip_n, true) |
|
} |
|
|
|
#[inline] |
|
pub(super) fn strict_mode(&self) -> bool { |
|
self.buffered_lexer.strict_mode() |
|
} |
|
|
|
#[inline] |
|
pub(super) fn set_strict_mode(&mut self, strict_mode: bool) { |
|
self.buffered_lexer.set_strict_mode(strict_mode) |
|
} |
|
|
|
/// Returns an error if the next token is not of kind `kind`. |
|
#[inline] |
|
pub(super) fn expect<K>(&mut self, kind: K, context: &'static str) -> Result<Token, ParseError> |
|
where |
|
K: Into<TokenKind>, |
|
{ |
|
let next_token = self.next()?.ok_or(ParseError::AbruptEnd)?; |
|
let kind = kind.into(); |
|
|
|
if next_token.kind() == &kind { |
|
Ok(next_token) |
|
} else { |
|
Err(ParseError::expected(vec![kind], next_token, context)) |
|
} |
|
} |
|
|
|
/// It will peek for the next token, to see if it's 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 |
|
#[inline] |
|
pub(super) fn peek_semicolon(&mut self) -> Result<SemicolonResult<'_>, ParseError> { |
|
match self.buffered_lexer.peek(0, false)? { |
|
Some(tk) => match tk.kind() { |
|
TokenKind::Punctuator(Punctuator::Semicolon) |
|
| TokenKind::LineTerminator |
|
| TokenKind::Punctuator(Punctuator::CloseBlock) => { |
|
Ok(SemicolonResult::Found(Some(tk))) |
|
} |
|
_ => Ok(SemicolonResult::NotFound(tk)), |
|
}, |
|
None => Ok(SemicolonResult::Found(None)), |
|
} |
|
} |
|
|
|
/// Consumes the next token if it is a semicolon, or returns a `ParseError` if it's not. |
|
/// |
|
/// It will automatically insert a semicolon if needed, as specified in the [spec][spec]. |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-automatic-semicolon-insertion |
|
#[inline] |
|
pub(super) fn expect_semicolon(&mut self, context: &'static str) -> Result<(), ParseError> { |
|
match self.peek_semicolon()? { |
|
SemicolonResult::Found(Some(tk)) => match *tk.kind() { |
|
TokenKind::Punctuator(Punctuator::Semicolon) | TokenKind::LineTerminator => { |
|
let _ = self.buffered_lexer.next(false)?; |
|
Ok(()) |
|
} |
|
_ => Ok(()), |
|
}, |
|
SemicolonResult::Found(None) => Ok(()), |
|
SemicolonResult::NotFound(tk) => Err(ParseError::expected( |
|
vec![TokenKind::Punctuator(Punctuator::Semicolon)], |
|
tk.clone(), |
|
context, |
|
)), |
|
} |
|
} |
|
|
|
/// It will make sure that the peeked token (skipping n tokens) is not a line terminator. |
|
/// |
|
/// It expects that the token stream does not end here. |
|
/// |
|
/// This is just syntatic sugar for a .peek(skip_n) call followed by a check that the result is not a line terminator or None. |
|
#[inline] |
|
pub(super) fn peek_expect_no_lineterminator( |
|
&mut self, |
|
skip_n: usize, |
|
context: &'static str, |
|
) -> Result<&Token, ParseError> { |
|
if let Some(t) = self.buffered_lexer.peek(skip_n, false)? { |
|
if t.kind() == &TokenKind::LineTerminator { |
|
Err(ParseError::unexpected(t.clone(), context)) |
|
} else { |
|
Ok(t) |
|
} |
|
} else { |
|
Err(ParseError::AbruptEnd) |
|
} |
|
} |
|
|
|
/// 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`. |
|
/// |
|
/// No next token also returns None. |
|
#[inline] |
|
pub(super) fn next_if<K>(&mut self, kind: K) -> Result<Option<Token>, ParseError> |
|
where |
|
K: Into<TokenKind>, |
|
{ |
|
Ok(if let Some(token) = self.peek(0)? { |
|
if token.kind() == &kind.into() { |
|
self.next()? |
|
} else { |
|
None |
|
} |
|
} else { |
|
None |
|
}) |
|
} |
|
}
|
|
|