use std::iter::Peekable;
use crate::{
diagnostics::DiagBox,
lex::{Lex, Token, TokenKind},
utils::{FileId, Location, Span},
};
use super::errors::{ExpectedToken, UnexpectedEnd};
pub trait Parse<L, T, K>
where
L: Lex<T, K> + Iterator<Item = Result<T, DiagBox>> + 'static,
T: Token<K>,
K: TokenKind + 'static,
{
fn lexer(&mut self) -> &mut Peekable<L>;
fn file_id(&self) -> FileId;
fn cursor(&self) -> usize;
fn set_cursor(&mut self, target: usize);
fn match_take(&mut self, tok_kind: K) -> Option<T> {
match self.peek() {
Ok(peek) if peek.kind_ref() == &tok_kind => Some(self.take().unwrap()),
Err(_) => None,
_ => None,
}
}
fn require(&mut self, expected_type: K) -> Result<T, DiagBox> {
let found_tok = self.take()?;
if found_tok.kind_ref() == &expected_type {
Ok(found_tok)
} else {
Err(ExpectedToken(expected_type, found_tok.location()).into())
}
}
fn next_tok_boundary(&mut self) -> usize {
let cursor = self.cursor();
self.lexer().peek().map_or(cursor, |tok| {
tok.as_ref().map_or(cursor, |tok| tok.span().start())
})
}
fn peek(&mut self) -> Result<&T, DiagBox> {
let next = self.next_tok_boundary();
let location = self.location(next);
if self.lexer().peek().is_some_and(|v| v.is_err()) {
return Err(self.take().unwrap_err());
}
match self.lexer().peek() {
Some(Ok(tok)) => Ok(tok),
None => Err(UnexpectedEnd(location).into()),
_ => unreachable!(),
}
}
fn soft_peek(&mut self) -> Result<Option<&T>, DiagBox> {
if self.lexer().peek().is_some_and(|v| v.is_err()) {
return Err(self.take().unwrap_err());
}
match self.lexer().peek() {
Some(Ok(tok)) => Ok(Some(tok)),
None => Ok(None),
_ => unreachable!(),
}
}
fn take(&mut self) -> Result<T, DiagBox> {
let next = self.next_tok_boundary();
let location = self.location(next);
match self.lexer().next() {
Some(Ok(tok)) => {
let boundary = self.next_tok_boundary();
self.set_cursor(boundary);
Ok(tok)
}
Some(Err(err)) => Err(err),
None => Err(UnexpectedEnd(location).into()),
}
}
fn span(&self, start: usize) -> Span {
Span::new(start, self.cursor())
}
fn location(&self, start: usize) -> Location {
Location::new(self.file_id(), self.span(start))
}
}