use skullbrain_diagnostics::{DiagnosticDetails, EmitDiagnostics, ParseError};
use skullbrain_lexer::{PunctuationKind, TokenKind};
use crate::{
state::{ConcreteSyntaxTreeBuilder, PeekSequence},
Diagnostics, ParserState, SyntaxKind,
};
#[derive(Default)]
struct Context<'input> {
missing_open_delimiter: Option<DiagnosticDetails<'input>>,
missing_close_delimiter: Option<(DiagnosticDetails<'input>, bool)>,
}
impl<'input, D: Diagnostics, C: ConcreteSyntaxTreeBuilder> ParserState<'input, D, C> {
pub(crate) fn parse_brace_aggressive(&mut self, parse_content: impl FnOnce(&mut Self)) {
self.parse_delimited_aggressive(
SyntaxKind::DelimitedRange,
(
TokenKind::Punctuation(PunctuationKind::OpenBrace),
SyntaxKind::OpenBrace,
),
(
TokenKind::Punctuation(PunctuationKind::CloseBrace),
SyntaxKind::CloseBrace,
),
parse_content,
)
}
pub(crate) fn parse_parenthesis_aggressive(&mut self, parse_content: impl FnOnce(&mut Self)) {
self.parse_delimited_aggressive(
SyntaxKind::DelimitedRange,
(
TokenKind::Punctuation(PunctuationKind::OpenParen),
SyntaxKind::OpenParen,
),
(
TokenKind::Punctuation(PunctuationKind::CloseParen),
SyntaxKind::CloseParen,
),
parse_content,
)
}
pub(crate) fn parse_delimited_aggressive(
&mut self,
range_kind: SyntaxKind,
open: (TokenKind, SyntaxKind),
close: (TokenKind, SyntaxKind),
parse_content: impl FnOnce(&mut Self),
) {
self.start_node(range_kind);
let mut context = Context::default();
self.skip_trivia();
self.has_opening_delimiter(&mut context, &open);
self.push_delimited(close.0.clone());
if self.try_possibilities(&mut context, &close) {
self.skip_trivia();
let close_tok = &((&close).0);
if self.peek_raw().is_some_and(|i| !i.kind().is(close_tok)) {
parse_content(self);
}
if self.try_possibilities(&mut context, &close) {
let details = self.diagnostic_details();
context.missing_close_delimiter = Some((details, true));
}
}
self.resume_normal_tokenstream();
self.finish_node();
self.try_emit_diagnostics_with_context(open.0, close.0, context)
}
fn try_emit_diagnostics_with_context(
&mut self,
open: TokenKind,
close: TokenKind,
context: Context<'_>,
) {
match (
context.missing_open_delimiter,
context.missing_close_delimiter,
) {
(None, None) => (),
(Some(diagnostic_details), None) => {
let error = ParseError::expected_token_before(open);
self.emit_diagnostic(error, diagnostic_details);
}
(None, Some((diagnostic_details, is_before))) => {
let error = if is_before {
ParseError::expected_token_before(close)
} else {
ParseError::expected_token_after(close)
};
self.emit_diagnostic(error, diagnostic_details);
}
(Some(d1), Some((d2, _))) => {
let diagnostic_details = DiagnosticDetails {
span: d1.span.start..d2.span.end,
..d1
};
let error = ParseError::wrap_with(open, close);
self.emit_diagnostic(error, diagnostic_details);
}
}
}
fn has_opening_delimiter(
&mut self,
context: &mut Context<'input>,
open: &(TokenKind, SyntaxKind),
) {
let open_token = &open.0;
match self.peek_raw() {
Some(item) if item.kind().is(&open_token) => {
self.parse_raw_token(open.1);
}
Some(_) => {
self.recover();
match self.peek_raw() {
Some(item) if item.kind().is(&open_token) => {
self.parse_raw_token(open.1);
}
_ => {
let details = self.diagnostic_details();
context.missing_open_delimiter = Some(details);
}
}
}
_ => {
let details = self.diagnostic_details();
context.missing_open_delimiter = Some(details);
}
}
}
fn try_possibilities(
&mut self,
context: &mut Context<'input>,
(close_tok, close_kind): &(TokenKind, SyntaxKind),
) -> bool {
self.skip_trivia();
match self.peek_raw() {
None => {
let details = self.diagnostic_details();
context.missing_close_delimiter = Some((details, false));
false
}
Some(i) if i.kind().is(&close_tok) => {
let Some(i) = self.next_raw() else {
unreachable!()
};
self.token(close_kind.clone(), i);
false
}
Some(_) => true,
}
}
}
impl<'input, D: Diagnostics, C: ConcreteSyntaxTreeBuilder> ParserState<'input, D, C> {
pub(crate) fn peek_sequence_for_brace<'a>(
peekable: &mut PeekSequence,
should_parse_content: impl FnOnce(&mut PeekSequence) -> bool,
) -> bool {
Self::peek_sequence_for_delimited(
(PunctuationKind::OpenBrace, PunctuationKind::CloseBrace),
peekable,
should_parse_content,
)
}
pub(crate) fn peek_sequence_for_parenthesis(
peekable: &mut PeekSequence,
should_parse_content: impl FnOnce(&mut PeekSequence) -> bool,
) -> bool {
Self::peek_sequence_for_delimited(
(PunctuationKind::OpenParen, PunctuationKind::CloseParen),
peekable,
should_parse_content,
)
}
fn peek_sequence_for_delimited(
(open, close): (PunctuationKind, PunctuationKind),
peekable: &mut PeekSequence,
should_parse_content: impl FnOnce(&mut PeekSequence) -> bool,
) -> bool {
let contains_open_delimiter = peekable.peek_next_is(&TokenKind::Punctuation(open));
let content = should_parse_content(peekable);
let contains_close_delimiter = peekable.peek_next_is(&TokenKind::Punctuation(close));
(contains_open_delimiter || contains_close_delimiter) && content
}
}
#[cfg(test)]
mod test {
use crate::{
primitive::is_trivia, state::ConcreteSyntaxTreeBuilder, test::create_parsing_test,
Diagnostics, ParserState, SyntaxKind,
};
#[test]
fn test_delimited_parser() {
create_parsing_test("delimited/*.skull", |state| {
state.parse_brace_aggressive(parse_content);
});
}
fn parse_content(state: &mut ParserState<impl Diagnostics, impl ConcreteSyntaxTreeBuilder>) {
loop {
match state.peek() {
None => break,
Some(item) if is_trivia(item.kind()) => {
let skipped_trivia = state.skip_trivia();
debug_assert!(skipped_trivia);
}
Some(item) if item.kind().is_unknown() => {
state.recover();
}
Some(_) => {
let Some(item) = state.next() else {
unreachable!()
};
state.token(SyntaxKind::Ident, item);
}
}
}
}
}