use super::*;
impl<'a> Parser<'a> {
pub(super) fn new(tokens: &'a [Token], source: &'a str, name: &'a str) -> Self {
let first = tokens.first();
let mut p = Self {
tokens,
source,
name,
pos: 0,
import_exprs: Vec::new(),
prev_end: first.map_or(0, |t| t.offset),
prev_line: first.map_or(1, |t| t.line),
amending: false,
new_types: Vec::new(),
type_alias_parameters: std::collections::HashMap::new(),
local_type_aliases: std::collections::HashSet::new(),
module_local_type_aliases: std::collections::HashSet::new(),
depth: 0,
};
p.skip_semicolons();
p
}
pub(super) fn skip_semicolons(&mut self) {
while self.pos + 1 < self.tokens.len()
&& matches!(self.tokens[self.pos].kind, TokenKind::Semicolon)
{
self.pos += 1;
}
}
pub(super) fn peek(&self) -> &TokenKind {
&self.tokens[self.pos].kind
}
pub(super) fn peek_tok(&self) -> &Token {
&self.tokens[self.pos]
}
pub(super) fn peek_nth(&self, n: usize) -> &TokenKind {
let mut i = self.pos;
let mut remaining = n;
while remaining > 0 && i + 1 < self.tokens.len() {
i += 1;
if !matches!(self.tokens[i].kind, TokenKind::Semicolon) {
remaining -= 1;
}
}
&self.tokens[i].kind
}
pub(super) fn advance(&mut self) -> &'a Token {
let tokens = self.tokens;
let tok = &tokens[self.pos];
self.prev_end = tok.end;
self.prev_line = tok.line;
if self.pos + 1 < tokens.len() {
self.pos += 1;
self.skip_semicolons();
}
tok
}
pub(super) fn at_eof(&self) -> bool {
matches!(self.peek(), TokenKind::Eof)
}
pub(super) fn preceded_by_semicolon(&self) -> bool {
self.pos > 0 && matches!(self.tokens[self.pos - 1].kind, TokenKind::Semicolon)
}
pub(super) fn newline_before(&self) -> bool {
let tok = self.peek_tok();
match self.source.get(self.prev_end..tok.offset) {
Some(gap) => gap.contains('\n'),
None => tok.line != self.prev_line,
}
}
pub(super) fn on_same_line(&self) -> bool {
!self.newline_before() && !self.preceded_by_semicolon()
}
pub(super) fn token_text(&self, tok: &Token) -> String {
if matches!(tok.kind, TokenKind::Eof) {
return "EOF".to_string();
}
match self.source.get(tok.offset..tok.end) {
Some(text) if !text.is_empty() => text.to_string(),
_ => format!("{:?}", tok.kind),
}
}
pub(super) fn is_quoted(&self, tok: &Token) -> bool {
self.source.as_bytes().get(tok.offset) == Some(&b'`')
}
pub(super) fn keyword_at(&self, tok: &Token) -> Option<String> {
match &tok.kind {
TokenKind::Ident(name) => (!self.is_quoted(tok)
&& RESERVED_WORDS.contains(&name.as_str()))
.then(|| name.clone()),
other => keyword_text(other).map(String::from),
}
}
pub(super) fn error_at(&self, offset: usize, message: impl Into<String>) -> Error {
Error::parse(self.name, self.source, offset, message.into())
}
pub(super) fn parse_error(&self, message: impl Into<String>) -> Error {
self.error_at(self.peek_tok().offset, message)
}
pub(super) fn missing_offset(&self) -> usize {
if self.at_eof() || self.newline_before() {
self.prev_end
} else {
self.peek_tok().offset
}
}
pub(super) fn unexpected_token(&self, expected: &str) -> Error {
let text = self.token_text(self.peek_tok());
self.error_at(
self.missing_offset(),
format!("Unexpected token `{text}`. Expected `{expected}`."),
)
}
pub(super) fn unexpected_token2(&self, expected1: &str, expected2: &str) -> Error {
let text = self.token_text(self.peek_tok());
self.error_at(
self.missing_offset(),
format!("Unexpected token `{text}`. Expected `{expected1}` or `{expected2}`."),
)
}
pub(super) fn expect(&mut self, kind: &TokenKind, text: &str) -> Result<&'a Token> {
if std::mem::discriminant(self.peek()) == std::mem::discriminant(kind) {
Ok(self.advance())
} else {
Err(self.unexpected_token(text))
}
}
pub(super) fn expect2(
&mut self,
kind: &TokenKind,
text: &str,
alternative: &str,
) -> Result<&'a Token> {
if std::mem::discriminant(self.peek()) == std::mem::discriminant(kind) {
Ok(self.advance())
} else {
Err(self.unexpected_token2(alternative, text))
}
}
pub(super) fn keyword_error(&self, keyword: &str) -> Error {
self.parse_error(format!(
"Keyword `{keyword}` is not allowed here.\n\n\
If you must use this name as identifier, enclose it in backticks."
))
}
pub(super) fn expect_ident(&mut self) -> Result<String> {
let tok = self.peek_tok();
if let Some(keyword) = self.keyword_at(tok) {
return Err(self.keyword_error(&keyword));
}
match &tok.kind {
TokenKind::Ident(name) => {
let name = name.clone();
self.advance();
Ok(name)
}
_ => Err(self.unexpected_token("identifier")),
}
}
pub(super) fn expect_field_ident(&mut self) -> Result<String> {
if matches!(self.peek(), TokenKind::Ident(name) if name == "outer")
&& !self.is_quoted(self.peek_tok())
{
self.advance();
return Ok("outer".to_string());
}
self.expect_ident()
}
pub(super) fn expect_string_constant(&mut self) -> Result<String> {
match self.peek() {
TokenKind::StringLit(s) => {
let s = s.clone();
self.advance();
Ok(s)
}
TokenKind::InterpolatedString(_) => {
Err(self.parse_error("String constant cannot have interpolated values."))
}
_ => Err(self.unexpected_token("\"")),
}
}
pub(super) fn parse_qualified_ident(&mut self) -> Result<String> {
let mut name = self.expect_ident()?;
while matches!(self.peek(), TokenKind::Dot) {
self.advance();
name.push('.');
name.push_str(&self.expect_ident()?);
}
Ok(name)
}
}
pub(super) const RESERVED_WORDS: &[&str] = &[
"_",
"case",
"delete",
"nothing",
"out",
"outer",
"override",
"protected",
"record",
"switch",
"unknown",
"vararg",
];
pub(super) fn keyword_text(kind: &TokenKind) -> Option<&'static str> {
Some(match kind {
TokenKind::KwAmends => "amends",
TokenKind::KwImport => "import",
TokenKind::KwAs => "as",
TokenKind::KwLocal => "local",
TokenKind::KwConst => "const",
TokenKind::KwFixed => "fixed",
TokenKind::KwHidden => "hidden",
TokenKind::KwNew => "new",
TokenKind::KwExtends => "extends",
TokenKind::KwAbstract => "abstract",
TokenKind::KwOpen => "open",
TokenKind::KwExternal => "external",
TokenKind::KwClass => "class",
TokenKind::KwTypeAlias => "typealias",
TokenKind::KwFunction => "function",
TokenKind::KwThis => "this",
TokenKind::KwSuper => "super",
TokenKind::KwModule => "module",
TokenKind::KwImportStar => "import*",
TokenKind::KwIf => "if",
TokenKind::KwElse => "else",
TokenKind::KwWhen => "when",
TokenKind::KwIs => "is",
TokenKind::KwLet => "let",
TokenKind::KwThrow => "throw",
TokenKind::KwTrace => "trace",
TokenKind::KwRead => "read",
TokenKind::KwReadOrNull => "read?",
TokenKind::KwReadGlob => "read*",
TokenKind::KwFor => "for",
TokenKind::KwIn => "in",
TokenKind::BoolLit(true) => "true",
TokenKind::BoolLit(false) => "false",
TokenKind::Null => "null",
_ => return None,
})
}