use alloc::{borrow::Cow, vec::Vec};
use super::AstParser;
use crate::{
ast::{Comment, CommentKind, ErrorExpr, Expr, Trivia},
error::{Error, ErrorKind, Position, Span},
token::{Token, TokenKind},
};
pub(super) trait ParserCore<'a> {
fn peek_kind(&mut self) -> TokenKind;
fn peek_span(&mut self) -> Span;
fn peek_two_kinds(&mut self) -> (TokenKind, TokenKind);
fn next_token(&mut self) -> Token<'a>;
fn consume_comma(&mut self) -> (Option<Span>, bool);
fn drain_trivia(&mut self) -> Trivia<'a>;
fn collect_leading_trivia(&mut self) -> Trivia<'a>;
fn error(span: Span, kind: ErrorKind) -> Error;
fn error_expr_from(&mut self, err: Error, errors: &mut Vec<Error>) -> Expr<'a>;
fn expected(msg: &'static str, ctx: Option<&'static str>) -> ErrorKind;
fn error_for_error_token(tok: &Token<'_>) -> Error;
fn recover_until(&mut self, sync: &[TokenKind]);
fn consume_closing(
&mut self,
expected: TokenKind,
error_kind: ErrorKind,
errors: &mut Vec<Error>,
) -> Span;
fn at_closing_or_eof(&mut self, closing: TokenKind) -> bool;
fn report_missing_comma(&mut self, context: &'static str, errors: &mut Vec<Error>);
fn handle_missing_comma(
&mut self,
context: &'static str,
closing: TokenKind,
errors: &mut Vec<Error>,
) -> bool;
fn eof_span(&self) -> Span;
fn span_at_end(span: &Span) -> Span;
}
impl<'a> ParserCore<'a> for AstParser<'a> {
#[inline]
fn peek_kind(&mut self) -> TokenKind {
if let Some(tok) = self.lookahead.as_ref() {
return tok.kind;
}
while let Some(tok) = self.tokens.peek() {
if tok.kind.is_trivia() {
if let Some(tok) = self.tokens.next() {
self.trivia_buffer.push(tok);
}
} else {
return tok.kind;
}
}
TokenKind::Eof
}
fn peek_span(&mut self) -> Span {
if let Some(tok) = self.lookahead.as_ref() {
return tok.span;
}
while let Some(tok) = self.tokens.peek() {
if tok.kind.is_trivia() {
if let Some(tok) = self.tokens.next() {
self.trivia_buffer.push(tok);
}
} else {
return tok.span;
}
}
self.eof_span()
}
fn peek_two_kinds(&mut self) -> (TokenKind, TokenKind) {
if self.lookahead.is_none() {
while let Some(tok) = self.tokens.peek() {
if tok.kind.is_trivia() {
if let Some(tok) = self.tokens.next() {
self.trivia_buffer.push(tok);
}
} else {
break;
}
}
if let Some(tok) = self.tokens.next() {
self.lookahead = Some(tok);
}
}
let first_kind = self.lookahead.as_ref().map_or(TokenKind::Eof, |t| t.kind);
while let Some(tok) = self.tokens.peek() {
if tok.kind.is_trivia() {
if let Some(tok) = self.tokens.next() {
self.trivia_buffer.push(tok);
}
} else {
break;
}
}
let second_kind = self.tokens.peek().map_or(TokenKind::Eof, |t| t.kind);
(first_kind, second_kind)
}
#[inline]
fn next_token(&mut self) -> Token<'a> {
if let Some(tok) = self.lookahead.take() {
return tok;
}
while let Some(tok) = self.tokens.peek() {
if tok.kind.is_trivia() {
if let Some(tok) = self.tokens.next() {
self.trivia_buffer.push(tok);
}
} else {
break;
}
}
self.tokens.next().unwrap_or_else(|| Token {
kind: TokenKind::Eof,
text: "",
span: self.eof_span(),
})
}
#[inline]
fn consume_comma(&mut self) -> (Option<Span>, bool) {
if self.peek_kind() == TokenKind::Comma {
let comma_tok = self.next_token();
(Some(comma_tok.span), true)
} else {
(None, false)
}
}
#[inline]
fn drain_trivia(&mut self) -> Trivia<'a> {
if self.trivia_buffer.is_empty() {
return Trivia::empty();
}
let start_span = self.trivia_buffer.first().map(|t| t.span);
let end_span = self.trivia_buffer.last().map(|t| t.span);
let span = match (start_span, end_span) {
(Some(s), Some(e)) => Some(Span {
start: s.start,
end: e.end,
start_offset: s.start_offset,
end_offset: e.end_offset,
}),
_ => None,
};
let mut first_whitespace_range: Option<(usize, usize)> = None;
let mut comments = Vec::with_capacity(self.trivia_buffer.len().min(4));
for tok in self.trivia_buffer.drain(..) {
match tok.kind {
TokenKind::Whitespace => {
if first_whitespace_range.is_none() {
first_whitespace_range = Some((tok.span.start_offset, tok.span.end_offset));
}
}
TokenKind::LineComment => {
comments.push(Comment {
span: tok.span,
text: Cow::Borrowed(tok.text),
kind: CommentKind::Line,
});
}
TokenKind::BlockComment => {
comments.push(Comment {
span: tok.span,
text: Cow::Borrowed(tok.text),
kind: CommentKind::Block,
});
}
_ => {}
}
}
let whitespace = if let Some((start, end)) = first_whitespace_range {
Cow::Borrowed(&self.source[start..end])
} else {
Cow::Borrowed("")
};
Trivia {
span,
whitespace,
comments,
}
}
#[inline]
fn collect_leading_trivia(&mut self) -> Trivia<'a> {
let _ = self.peek_kind();
self.drain_trivia()
}
fn error(span: Span, kind: ErrorKind) -> Error {
Error::with_span(kind, span)
}
#[inline(never)]
fn error_expr_from(&mut self, err: Error, errors: &mut Vec<Error>) -> Expr<'a> {
let span = *err.span();
let error = err.clone();
errors.push(err);
self.recover_until(&[
TokenKind::Comma,
TokenKind::RParen,
TokenKind::RBracket,
TokenKind::RBrace,
]);
Expr::Error(ErrorExpr { span, error })
}
fn expected(msg: &'static str, ctx: Option<&'static str>) -> ErrorKind {
ErrorKind::Expected {
expected: Cow::Borrowed(msg),
context: ctx,
}
}
#[inline(never)]
fn error_for_error_token(tok: &Token<'_>) -> Error {
debug_assert_eq!(tok.kind, TokenKind::Error);
if tok.text.starts_with("/*") {
Self::error(tok.span, ErrorKind::UnclosedBlockComment)
} else if tok.text.starts_with('"') || tok.text.starts_with("r#") {
Self::error(tok.span, ErrorKind::ExpectedStringEnd)
} else if tok.text.starts_with("0x")
|| tok.text.starts_with("0X")
|| tok.text.starts_with("0b")
|| tok.text.starts_with("0B")
|| tok.text.starts_with("0o")
|| tok.text.starts_with("0O")
{
let prefix_len = 2;
let error_span = Span {
start: Position {
line: tok.span.start.line,
col: tok.span.start.col + prefix_len,
},
end: tok.span.end,
start_offset: tok.span.start_offset + prefix_len,
end_offset: tok.span.end_offset,
};
let invalid_char = tok.text[prefix_len..].chars().next().unwrap_or('?');
Self::error(error_span, ErrorKind::UnexpectedChar(invalid_char))
} else {
Self::error(
tok.span,
ErrorKind::UnexpectedChar(tok.text.chars().next().unwrap_or('?')),
)
}
}
#[inline(never)]
fn recover_until(&mut self, sync: &[TokenKind]) {
self.trivia_buffer.clear();
loop {
let kind = self.peek_kind();
if kind == TokenKind::Eof || sync.contains(&kind) {
break;
}
let _ = self.next_token();
}
}
fn consume_closing(
&mut self,
expected: TokenKind,
error_kind: ErrorKind,
errors: &mut Vec<Error>,
) -> Span {
if self.peek_kind() == expected {
return self.next_token().span;
}
errors.push(Self::error(self.peek_span(), error_kind));
self.recover_until(&[expected, TokenKind::Eof]);
if self.peek_kind() == expected {
return self.next_token().span;
}
self.eof_span()
}
#[inline]
fn at_closing_or_eof(&mut self, closing: TokenKind) -> bool {
let kind = self.peek_kind();
kind == closing || kind == TokenKind::Eof
}
fn report_missing_comma(&mut self, context: &'static str, errors: &mut Vec<Error>) {
errors.push(Self::error(
self.peek_span(),
Self::expected("comma", Some(context)),
));
}
fn handle_missing_comma(
&mut self,
context: &'static str,
closing: TokenKind,
errors: &mut Vec<Error>,
) -> bool {
if self.at_closing_or_eof(closing) {
false
} else {
self.report_missing_comma(context, errors);
true
}
}
fn eof_span(&self) -> Span {
let len = self.source.len();
let pos = Position::from_src_end(self.source);
Span {
start: pos,
end: pos,
start_offset: len,
end_offset: len,
}
}
#[inline]
fn span_at_end(span: &Span) -> Span {
Span {
start: span.end,
end: span.end,
start_offset: span.end_offset,
end_offset: span.end_offset,
}
}
}