use skullbrain_lexer::{LiteralKind, OperatorKind, TokenKind};
use std::ops::Range;
use crate::{primitive::is_trivia, SyntaxKind};
use super::{ConcreteSyntaxTreeBuilder, Diagnostics, ParserState, PositionedToken};
const fn is_recovery_point(kind: &TokenKind) -> bool {
match kind {
TokenKind::Punctuation(_) => true,
TokenKind::Ident => true,
TokenKind::Operator(OperatorKind::Assignment) => true,
TokenKind::Declaration(_) => true,
TokenKind::ControlFlow(_) => true,
TokenKind::Literial(LiteralKind::Str { .. }) => true,
_ => false,
}
}
struct RecoveryContext {
error_span: Option<Range<usize>>,
}
impl<'input, D: Diagnostics, C: ConcreteSyntaxTreeBuilder> ParserState<'input, D, C> {
pub(crate) fn recover(&mut self) {
let mut context = RecoveryContext { error_span: None };
self.recovery_loop(&mut context);
}
fn recovery_loop(&mut self, context: &mut RecoveryContext) {
loop {
match self.peek() {
None => break,
Some(item) if is_trivia(item.kind()) => {
self.create_skipped_nodes(context);
let skipped_trivia = self.skip_trivia();
debug_assert!(skipped_trivia);
}
Some(item) if is_recovery_point(item.kind()) => {
if context.error_span.is_some() {
self.create_skipped_nodes(context);
}
break;
}
Some(_) => self.advance_recovery(context),
}
}
}
fn create_skipped_nodes(&mut self, context: &mut RecoveryContext) {
let Some(error_span) = context.error_span.take() else {
unreachable!("Missing skip-trivia call before recover method, next token was trivia.");
};
debug_assert!(!error_span.is_empty());
let positioned_token = PositionedToken::new(TokenKind::Unknown, error_span);
self.token(SyntaxKind::Error, positioned_token);
context.error_span = None;
}
fn advance_recovery(&mut self, context: &mut RecoveryContext) {
let Some(item) = self.next() else {
unreachable!()
};
match &mut context.error_span {
None => context.error_span = Some(item.into_range()),
Some(range) => {
range.end = item.range.end;
}
}
}
}