use alloc::boxed::Box;
use alloc::format;
use alloc::vec::Vec;
use diag_lang::{Diagnostic, Label, Severity};
use token_lang::{Span, Token, TokenKind};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Checkpoint {
pos: usize,
errors: usize,
}
pub struct Parser<'t, K> {
tokens: &'t [Token<K>],
pos: usize,
eof_span: Span,
errors: Vec<Diagnostic>,
}
impl<'t, K: TokenKind> Parser<'t, K> {
#[must_use]
pub fn new(tokens: &'t [Token<K>]) -> Self {
let eof_span = tokens
.last()
.map_or(Span::empty(0), |t| Span::empty(t.span().end().to_u32()));
let mut parser = Self {
tokens,
pos: 0,
eof_span,
errors: Vec::new(),
};
parser.skip_trivia();
parser
}
fn skip_trivia(&mut self) {
while self.tokens.get(self.pos).is_some_and(Token::is_trivia) {
self.pos += 1;
}
}
#[must_use]
pub fn peek(&self) -> Option<&'t Token<K>> {
self.tokens.get(self.pos)
}
#[must_use]
pub fn peek_kind(&self) -> Option<&'t K> {
self.peek().map(Token::kind)
}
#[must_use]
pub fn span(&self) -> Span {
self.peek().map_or(self.eof_span, Token::span)
}
#[must_use]
pub fn at_end(&self) -> bool {
self.peek().is_none_or(Token::is_eof)
}
#[must_use]
pub fn at(&self, pred: impl FnOnce(&K) -> bool) -> bool {
self.peek_kind().is_some_and(pred)
}
pub fn bump(&mut self) -> Option<&'t Token<K>> {
let token = self.tokens.get(self.pos)?;
self.pos += 1;
self.skip_trivia();
Some(token)
}
pub fn eat(&mut self, pred: impl FnOnce(&K) -> bool) -> Option<&'t Token<K>> {
if self.at(pred) { self.bump() } else { None }
}
pub fn expect(
&mut self,
pred: impl FnOnce(&K) -> bool,
description: &str,
) -> Option<&'t Token<K>> {
match self.eat(pred) {
Some(token) => Some(token),
None => {
self.error(format!("expected {description}"));
None
}
}
}
pub fn error(&mut self, message: impl Into<Box<str>>) {
let span = self.span();
self.error_at(span, message);
}
pub fn error_at(&mut self, span: Span, message: impl Into<Box<str>>) {
self.errors.push(Diagnostic::new(
Severity::Error,
message,
Label::unlabelled(span),
));
}
pub fn recover(&mut self, sync: impl Fn(&K) -> bool) {
while let Some(token) = self.tokens.get(self.pos) {
if token.is_eof() || sync(token.kind()) {
return;
}
self.pos += 1;
self.skip_trivia();
}
}
pub fn repeated<T>(&mut self, mut parse: impl FnMut(&mut Self) -> Option<T>) -> Vec<T> {
let mut items = Vec::new();
loop {
let before = self.pos;
match parse(self) {
Some(item) => {
items.push(item);
if self.pos == before {
break; }
}
None => break,
}
}
items
}
pub fn separated<T>(
&mut self,
mut sep: impl FnMut(&K) -> bool,
mut parse: impl FnMut(&mut Self) -> Option<T>,
) -> Vec<T> {
let mut items = Vec::new();
match parse(self) {
Some(first) => items.push(first),
None => return items,
}
while self.eat(&mut sep).is_some() {
let before = self.pos;
match parse(self) {
Some(item) => {
items.push(item);
if self.pos == before {
break;
}
}
None => break,
}
}
items
}
#[must_use]
pub fn checkpoint(&self) -> Checkpoint {
Checkpoint {
pos: self.pos,
errors: self.errors.len(),
}
}
pub fn rewind(&mut self, checkpoint: Checkpoint) {
self.pos = checkpoint.pos;
self.errors.truncate(checkpoint.errors);
}
#[must_use]
pub fn errors(&self) -> &[Diagnostic] {
&self.errors
}
#[must_use]
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
#[must_use]
pub fn into_errors(self) -> Vec<Diagnostic> {
self.errors
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone, Copy, Debug, PartialEq)]
enum K {
Num,
Plus,
Space,
Eof,
}
impl TokenKind for K {
fn is_trivia(&self) -> bool {
matches!(self, K::Space)
}
fn is_eof(&self) -> bool {
matches!(self, K::Eof)
}
}
fn toks(kinds: &[K]) -> Vec<Token<K>> {
kinds
.iter()
.enumerate()
.map(|(i, k)| Token::new(*k, Span::new(i as u32, i as u32 + 1)))
.collect()
}
#[test]
fn test_new_skips_leading_trivia() {
let t = toks(&[K::Space, K::Space, K::Num]);
let p = Parser::new(&t);
assert!(p.at(|k| matches!(k, K::Num)));
}
#[test]
fn test_bump_skips_trailing_trivia() {
let t = toks(&[K::Num, K::Space, K::Plus]);
let mut p = Parser::new(&t);
p.bump();
assert!(p.at(|k| matches!(k, K::Plus)));
}
#[test]
fn test_navigation_is_total_on_empty_stream() {
let t: Vec<Token<K>> = Vec::new();
let mut p = Parser::new(&t);
assert!(p.peek().is_none());
assert!(p.at_end());
assert!(p.bump().is_none());
assert_eq!(p.span(), Span::empty(0));
}
#[test]
fn test_at_end_true_on_eof_marker() {
let t = toks(&[K::Num, K::Eof]);
let mut p = Parser::new(&t);
assert!(!p.at_end());
p.bump();
assert!(p.at_end());
}
#[test]
fn test_expect_records_error_and_returns_none() {
let t = toks(&[K::Num]);
let mut p = Parser::new(&t);
assert!(p.expect(|k| matches!(k, K::Plus), "`+`").is_none());
assert_eq!(p.errors().len(), 1);
}
#[test]
fn test_recover_stops_at_sync_and_at_eof() {
let t = toks(&[K::Num, K::Num, K::Plus, K::Eof]);
let mut p = Parser::new(&t);
p.recover(|k| matches!(k, K::Plus));
assert!(p.at(|k| matches!(k, K::Plus)));
let t2 = toks(&[K::Num, K::Num, K::Eof]);
let mut p2 = Parser::new(&t2);
p2.recover(|k| matches!(k, K::Plus));
assert!(p2.at_end());
}
#[test]
fn test_checkpoint_rewinds_cursor_and_errors() {
let t = toks(&[K::Num, K::Plus]);
let mut p = Parser::new(&t);
let cp = p.checkpoint();
p.bump();
p.error("nope");
p.rewind(cp);
assert!(!p.has_errors());
assert!(p.at(|k| matches!(k, K::Num)));
}
#[test]
fn test_repeated_stops_without_progress() {
let t = toks(&[K::Num]);
let mut p = Parser::new(&t);
let collected = p.repeated(|_p| Some(1u8));
assert_eq!(collected, [1]);
}
}