skullbrain-parser 0.1.0

Parses skullbrain tokens into a concrete syntax tree (CST).
Documentation
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);

                    // Handle trivia first
                    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);

        // Reset the error-span
        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;
            }
        }
    }
}