skullbrain-parser 0.1.0

Parses skullbrain tokens into a concrete syntax tree (CST).
Documentation
use skullbrain_diagnostics::{EmitDiagnostics, ParseError};
use skullbrain_lexer::TokenKind;

use crate::{
    state::{ConcreteSyntaxTreeBuilder, PeekSequence},
    Diagnostics, ParserState, SyntaxKind,
};

impl<'input, D: Diagnostics, C: ConcreteSyntaxTreeBuilder> ParserState<'input, D, C> {
    pub(super) fn parse_import_expression(&mut self) {
        self.start_node(SyntaxKind::Import);

        self.parse_token(SyntaxKind::Keyword);

        if self.try_possibilities_after_import_keyword() {
            self.recover();

            if self.try_possibilities_after_import_keyword() {
                self.emit_missing_import_source_before_diagnostic();
            }
        }

        self.finish_node();
    }

    fn try_possibilities_after_import_keyword(&mut self) -> bool {
        self.skip_trivia();

        match self.peek() {
            None => {
                self.emit_missing_import_source_at_diagnostic();
                false
            }
            Some(item) if possible_import_source_token(item.kind()) => {
                let kind = if item.kind().is_ident() {
                    SyntaxKind::Ident
                } else {
                    SyntaxKind::Literal
                };
                
                self.start_node(SyntaxKind::ImportSource);
                self.parse_token(kind);

                self.finish_node();
                self.try_possibilities_after_import_source();
                false
            }
            Some(_) => {
                if self.peek_sequence(|peekable| Self::peek_sequence_for_imported_items(peekable)) {
                    self.emit_missing_import_source_before_diagnostic();

                    self.parse_imported_items();
                    false
                } else {
                    true
                }
            }
        }
    }

    fn try_possibilities_after_import_source(&mut self) {
        self.skip_trivia();

        match self.peek() {
            None => (),
            Some(item) if is_alias(item.kind()) => {
                self.parse_alias_and_remaining();
            }
            Some(_) => {
                if self.peek_sequence(|peekable| Self::peek_sequence_for_imported_items(peekable)) {
                    self.parse_imported_items();
                }
            }
        }
    }
}

impl<'input, D: Diagnostics, C: ConcreteSyntaxTreeBuilder> ParserState<'input, D, C> {
    fn peek_sequence_for_imported_items(peekable: &mut PeekSequence) -> bool {
        Self::peek_sequence_for_brace(
            peekable,
            Self::peek_sequence_for_seperated_by_comma(Self::peek_sequence_for_import_item),
        )
    }
}
impl<'input, D: Diagnostics, C: ConcreteSyntaxTreeBuilder> ParserState<'input, D, C> {
    fn parse_alias_and_remaining(&mut self) {
        self.parse_token(SyntaxKind::ImportAlias);
        self.skip_trivia();
        self.parse_imported_items();
    }

    fn parse_imported_items(&mut self) {
        self.start_node(SyntaxKind::ImportItem);

        if self.peek_sequence(|peekable| Self::peek_sequence_for_ident(peekable)) {
            self.parse_ident();
        } else {
            self.parse_brace_aggressive(
                //Self::peek_sequence_for_seperated_by_comma(Self::peek_sequence_for_import_item),
                |state| {
                    state.parse_seperated_by_comma(Self::peek_sequence_for_import_item, |state| {
                        state.parse_import_item()
                    });
                },
            );
        }

        self.finish_node();
    }

    fn parse_import_item(&mut self) {
        self.parse_ident();
    }

    fn peek_sequence_for_import_item(peekable: &mut PeekSequence) -> bool {
        peekable
            .peek_next()
            .map_or(false, |item| item.kind().is_ident())
    }
}

impl<'input, D: Diagnostics, C: ConcreteSyntaxTreeBuilder> ParserState<'input, D, C> {
    fn emit_missing_import_source_at_diagnostic(&mut self) {
        let diagnostic_details = self.diagnostic_details();
        let error = ParseError::missing_import_source_at();
        self.emit_diagnostic(error, diagnostic_details);
    }

    fn emit_missing_import_source_before_diagnostic(&mut self) {
        let diagnostic_details = self.diagnostic_details();
        let error = ParseError::missing_import_source_before();
        self.emit_diagnostic(error, diagnostic_details);
    }
}

fn possible_import_source_token(kind: &TokenKind) -> bool {
    kind.literial_ref().is_some_and(|k| k.is_str()) || kind.is_ident()
}

fn is_alias(kind: &TokenKind) -> bool {
    kind.declaration_ref().is_some_and(|k| k.is_alias())
}

#[cfg(test)]
mod test {
    use crate::test::create_parsing_test;

    #[test]
    fn test_import() {
        create_parsing_test("import/*.skull", |state| {
            state.parse_import_expression();
        });
    }
}