skullbrain-parser 0.1.0

Parses skullbrain tokens into a concrete syntax tree (CST).
Documentation
use crate::{state::ConcreteSyntaxTreeBuilder, Diagnostics, ParserState, SyntaxKind};
use skullbrain_diagnostics::{DiagnosticDetails, EmitDiagnostics, ParseError};
use skullbrain_lexer::{ModifierKind, TokenKind};

impl<'input, D: Diagnostics, C: ConcreteSyntaxTreeBuilder> ParserState<'input, D, C> {
    pub(super) fn parse_optional_modifiers(&mut self) {
        if self.peek().is_none_or(|i| i.kind().is_not_modifier()) {
            return;
        }

        self.start_node(SyntaxKind::Modifiers);

        let mut context = Context::default();
        let mut diagnostic_details = self.diagnostic_details();

        while let Some(item) = self.peek() {
            match item.kind() {
                TokenKind::Modifier(kind) => {
                    context.update_for(kind);

                    diagnostic_details.span.end = item.range().end;
                    let Some(item) = self.next() else {
                        unreachable!()
                    };

                    self.token(SyntaxKind::Keyword, item);
                    self.skip_trivia();
                }
                _ => break,
            }
        }

        self.emit_diagnostic_for_context(diagnostic_details, context);
        self.finish_node();
    }

    fn emit_diagnostic_for_context(
        &mut self,
        diagnostic_details: DiagnosticDetails,
        context: Context,
    ) {
        if context.public == SeenState::Duplicate {
            self.emit_diagnostic(
                ParseError::duplicate_modifier(ModifierKind::VisibilityPublic),
                diagnostic_details.clone(),
            );
        }

        if context.instance_access == SeenState::Duplicate {
            self.emit_diagnostic(
                ParseError::duplicate_modifier(ModifierKind::InstanceAccess),
                diagnostic_details.clone(),
            );
        }
    }
}

/// Tracks which modifiers have been encountered during parsing.
#[derive(Default)]
struct Context {
    public: SeenState,
    instance_access: SeenState,
}

#[derive(Default, PartialEq)]
enum SeenState {
    #[default]
    NotSeen,
    SeenOnce,
    Duplicate,
}

impl Context {
    fn update_for(&mut self, kind: &ModifierKind) {
        let state = match kind {
            ModifierKind::VisibilityPublic => &mut self.public,
            ModifierKind::InstanceAccess => &mut self.instance_access,
        };

        match state {
            SeenState::NotSeen => *state = SeenState::SeenOnce,
            SeenState::SeenOnce => *state = SeenState::Duplicate,
            SeenState::Duplicate => (),
        };
    }
}

mod test {
    use crate::state::ConcreteSyntaxTreeBuilder;

    #[test]
    fn test_modifiers() {
        crate::test::create_parsing_test("modifiers/*.skull", |state| {
            // Only for testing purposes
            state.start_node(crate::SyntaxKind::Unknown);
            state.parse_optional_modifiers();
            state.finish_node();
        });
    }
}