use crate::error::Error;
use ecma_lex_cat::token::{Token, TokenKind};
use ecma_syntax_cat::identifier::{Identifier, PrivateIdentifier};
use ecma_syntax_cat::span::{Position, Span};
pub enum Peek<'a> {
Eof,
Token(&'a Token),
}
#[must_use]
pub fn peek(tokens: &[Token], pos: usize) -> Peek<'_> {
tokens
.get(pos)
.filter(|t| !matches!(t.value(), TokenKind::Eof))
.map_or(Peek::Eof, Peek::Token)
}
#[must_use]
pub fn span_at(tokens: &[Token], pos: usize) -> Span {
tokens.get(pos).map_or(Span::synthetic(), Token::span)
}
#[must_use]
#[allow(dead_code)]
pub fn span_before(tokens: &[Token], pos: usize) -> Span {
pos.checked_sub(1).and_then(|prev| tokens.get(prev)).map_or(
Span::new(Position::synthetic(), Position::synthetic()),
Token::span,
)
}
pub fn expect_kind(
tokens: &[Token],
pos: usize,
expected: &TokenKind,
name: &'static str,
) -> Result<usize, Error> {
match peek(tokens, pos) {
Peek::Eof => Err(Error::UnexpectedEof { expected: name }),
Peek::Token(tok) => {
if tok.value() == expected {
Ok(pos + 1)
} else {
Err(Error::UnexpectedToken {
at: tok.span(),
expected: name,
found: format!("{}", tok.value()),
})
}
}
}
}
#[must_use]
pub fn is_kind(tokens: &[Token], pos: usize, kind: &TokenKind) -> bool {
matches!(peek(tokens, pos), Peek::Token(tok) if tok.value() == kind)
}
#[must_use]
#[allow(dead_code)]
pub fn consume_if(tokens: &[Token], pos: usize, kind: &TokenKind) -> Option<usize> {
is_kind(tokens, pos, kind).then_some(pos + 1)
}
pub fn eat_semicolon(tokens: &[Token], pos: usize) -> Result<usize, Error> {
match peek(tokens, pos) {
Peek::Eof => Ok(pos),
Peek::Token(tok) => match tok.value() {
TokenKind::Semicolon => Ok(pos + 1),
TokenKind::RBrace => Ok(pos),
_other => Err(Error::UnexpectedToken {
at: tok.span(),
expected: "`;` or `}`",
found: format!("{}", tok.value()),
}),
},
}
}
pub fn expect_identifier(tokens: &[Token], pos: usize) -> Result<(Identifier, usize), Error> {
match peek(tokens, pos) {
Peek::Eof => Err(Error::UnexpectedEof {
expected: "identifier",
}),
Peek::Token(tok) => match tok.value() {
TokenKind::Identifier(name) => {
let id = Identifier::new(name.clone())?;
Ok((id, pos + 1))
}
_other => Err(Error::UnexpectedToken {
at: tok.span(),
expected: "identifier",
found: format!("{}", tok.value()),
}),
},
}
}
pub fn expect_identifier_or_keyword(
tokens: &[Token],
pos: usize,
) -> Result<(Identifier, usize), Error> {
match peek(tokens, pos) {
Peek::Eof => Err(Error::UnexpectedEof {
expected: "identifier",
}),
Peek::Token(tok) => {
let text_option = identifier_text(tok.value());
match text_option {
Some(text) => {
let id = Identifier::new(text)?;
Ok((id, pos + 1))
}
None => Err(Error::UnexpectedToken {
at: tok.span(),
expected: "identifier",
found: format!("{}", tok.value()),
}),
}
}
}
}
#[allow(clippy::too_many_lines)] #[allow(clippy::match_same_arms)] fn identifier_text(kind: &TokenKind) -> Option<String> {
match kind {
TokenKind::Identifier(name) => Some(name.clone()),
TokenKind::KwLet => Some("let".to_owned()),
TokenKind::KwAwait => Some("await".to_owned()),
TokenKind::KwYield => Some("yield".to_owned()),
TokenKind::KwStatic => Some("static".to_owned()),
TokenKind::KwImplements
| TokenKind::KwInterface
| TokenKind::KwPackage
| TokenKind::KwPrivate
| TokenKind::KwProtected
| TokenKind::KwPublic => None,
TokenKind::KwBreak
| TokenKind::KwCase
| TokenKind::KwCatch
| TokenKind::KwClass
| TokenKind::KwConst
| TokenKind::KwContinue
| TokenKind::KwDebugger
| TokenKind::KwDefault
| TokenKind::KwDelete
| TokenKind::KwDo
| TokenKind::KwElse
| TokenKind::KwEnum
| TokenKind::KwExport
| TokenKind::KwExtends
| TokenKind::KwFalse
| TokenKind::KwFinally
| TokenKind::KwFor
| TokenKind::KwFunction
| TokenKind::KwIf
| TokenKind::KwImport
| TokenKind::KwIn
| TokenKind::KwInstanceof
| TokenKind::KwNew
| TokenKind::KwNull
| TokenKind::KwReturn
| TokenKind::KwSuper
| TokenKind::KwSwitch
| TokenKind::KwThis
| TokenKind::KwThrow
| TokenKind::KwTrue
| TokenKind::KwTry
| TokenKind::KwTypeof
| TokenKind::KwVar
| TokenKind::KwVoid
| TokenKind::KwWhile
| TokenKind::KwWith => None,
TokenKind::PrivateIdentifier(_)
| TokenKind::Number(_)
| TokenKind::BigInt(_)
| TokenKind::String(_)
| TokenKind::RegExp { .. }
| TokenKind::TemplateNoSubstitution(_)
| TokenKind::TemplateHead(_)
| TokenKind::TemplateMiddle(_)
| TokenKind::TemplateTail(_)
| TokenKind::LParen
| TokenKind::RParen
| TokenKind::LBracket
| TokenKind::RBracket
| TokenKind::LBrace
| TokenKind::RBrace
| TokenKind::Comma
| TokenKind::Semicolon
| TokenKind::Colon
| TokenKind::Dot
| TokenKind::OptionalChain
| TokenKind::Spread
| TokenKind::Arrow
| TokenKind::Question
| TokenKind::EqEq
| TokenKind::EqEqEq
| TokenKind::BangEq
| TokenKind::BangEqEq
| TokenKind::Lt
| TokenKind::LtEq
| TokenKind::Gt
| TokenKind::GtEq
| TokenKind::Plus
| TokenKind::Minus
| TokenKind::Star
| TokenKind::Slash
| TokenKind::Percent
| TokenKind::StarStar
| TokenKind::PlusPlus
| TokenKind::MinusMinus
| TokenKind::Amp
| TokenKind::Pipe
| TokenKind::Caret
| TokenKind::Tilde
| TokenKind::LtLt
| TokenKind::GtGt
| TokenKind::GtGtGt
| TokenKind::AmpAmp
| TokenKind::PipePipe
| TokenKind::QQ
| TokenKind::Bang
| TokenKind::Eq
| TokenKind::PlusEq
| TokenKind::MinusEq
| TokenKind::StarEq
| TokenKind::SlashEq
| TokenKind::PercentEq
| TokenKind::StarStarEq
| TokenKind::LtLtEq
| TokenKind::GtGtEq
| TokenKind::GtGtGtEq
| TokenKind::AmpEq
| TokenKind::PipeEq
| TokenKind::CaretEq
| TokenKind::AmpAmpEq
| TokenKind::PipePipeEq
| TokenKind::QQEq
| TokenKind::Eof => None,
}
}
pub fn expect_private_identifier(
tokens: &[Token],
pos: usize,
) -> Result<(PrivateIdentifier, usize), Error> {
match peek(tokens, pos) {
Peek::Eof => Err(Error::UnexpectedEof {
expected: "private identifier",
}),
Peek::Token(tok) => match tok.value() {
TokenKind::PrivateIdentifier(name) => {
let id = PrivateIdentifier::new(name.clone())?;
Ok((id, pos + 1))
}
_other => Err(Error::UnexpectedToken {
at: tok.span(),
expected: "private identifier",
found: format!("{}", tok.value()),
}),
},
}
}