use skullbrain_diagnostics::{EmitDiagnostics, ParseError};
use skullbrain_lexer::{PunctuationKind, TokenKind};
use crate::{
state::{ConcreteSyntaxTreeBuilder, PeekSequence},
Diagnostics, ParserState, SyntaxKind,
};
impl<'input, D: Diagnostics, C: ConcreteSyntaxTreeBuilder> ParserState<'input, D, C> {
pub(crate) fn parse_seperated_by_comma(
&mut self,
should_parse_content: impl for<'a> FnOnce(&mut PeekSequence<'a, 'input>) -> bool + Clone,
parse_content: impl Fn(&mut Self),
) {
self.parse_seperated_by(
PunctuationKind::Comma,
SyntaxKind::Comma,
should_parse_content,
parse_content,
);
}
pub(crate) fn peek_sequence_for_seperated_by_comma(
should_parse_content: impl FnOnce(&mut PeekSequence) -> bool,
) -> impl FnOnce(&mut PeekSequence) -> bool {
should_parse_content
}
}
struct Context {
encountered_separator_last: Option<bool>,
expected_separator_error_added_last_iteration: bool,
recovered_context: bool,
}
impl Default for Context {
fn default() -> Self {
Self {
encountered_separator_last: None,
expected_separator_error_added_last_iteration: false,
recovered_context: false,
}
}
}
impl<'input, D: Diagnostics, C: ConcreteSyntaxTreeBuilder> ParserState<'input, D, C> {
fn parse_seperated_by(
&mut self,
sep_token: PunctuationKind,
sep_kind: SyntaxKind,
should_parse_content: impl FnOnce(&mut PeekSequence<'_, 'input>) -> bool + Clone,
parse_content: impl Fn(&mut Self),
) {
let mut context = Context::default();
loop {
self.skip_trivia();
match self.peek() {
None => break,
Some(item) => {
if TokenKind::Punctuation(sep_token).is(item.kind()) {
self.found_seperator_as_next_tok(&mut context, sep_kind.clone());
continue;
}
if self.peek_sequence(should_parse_content.clone()) {
self.found_content_parser_as_following_sequence(
&mut context,
sep_token,
&parse_content,
);
continue;
}
let should_break: bool = self.try_to_recover_from_this_unexpected_sequence(
sep_token,
&mut context,
should_parse_content.clone(),
);
if should_break {
break;
}
}
}
}
}
fn found_seperator_as_next_tok(&mut self, context: &mut Context, sep_kind: SyntaxKind) {
context.expected_separator_error_added_last_iteration = false;
if context.encountered_separator_last.is_none_or(|b| b) && !context.recovered_context {
let diagnostic_details = self.diagnostic_details();
let error = ParseError::expected_expression_between_seperators();
self.emit_diagnostic(error, diagnostic_details);
}
context.encountered_separator_last = Some(true);
context.recovered_context = false;
self.parse_token(sep_kind);
}
fn found_content_parser_as_following_sequence(
&mut self,
context: &mut Context,
sep_token: PunctuationKind,
parse_content: impl Fn(&mut Self),
) {
if !context.expected_separator_error_added_last_iteration
&& context.encountered_separator_last.is_some_and(|b| !b)
{
let diagnostic_details = self.diagnostic_details();
let error = ParseError::expected_token_before(TokenKind::Punctuation(sep_token));
self.emit_diagnostic(error, diagnostic_details);
context.expected_separator_error_added_last_iteration = true;
}
context.expected_separator_error_added_last_iteration = false;
context.encountered_separator_last = Some(false);
context.recovered_context = false;
parse_content(self);
}
fn try_to_recover_from_this_unexpected_sequence(
&mut self,
sep_token: PunctuationKind,
context: &mut Context,
should_parse_content: impl FnOnce(&mut PeekSequence<'_, 'input>) -> bool,
) -> bool {
self.recover();
context.recovered_context = true;
let should_break = match self.peek() {
Some(item) => {
if item.kind().is(&TokenKind::Punctuation(sep_token))
|| self.peek_sequence(should_parse_content)
{
false
} else {
true
}
}
None => false,
};
should_break
}
}
#[cfg(test)]
mod tests {
use skullbrain_lexer::TokenKind;
use crate::{
state::{ConcreteSyntaxTreeBuilder, PeekSequence},
test::create_parsing_test,
Diagnostics, ParserState, SyntaxKind,
};
#[test]
fn seperated_by_comma() {
create_parsing_test("seperated_by_comma/*.skull", |state| {
state.start_node(SyntaxKind::Unknown);
state.parse_seperated_by_comma(should_parse_content, parse_content);
state.finish_node();
});
}
const REJECT_KINDS: &[TokenKind] = &[TokenKind::Unknown];
fn parse_content(state: &mut ParserState<impl Diagnostics, impl ConcreteSyntaxTreeBuilder>) {
let Some(item) = state.peek() else {
unreachable!()
};
let kind = item.kind().clone();
if REJECT_KINDS.iter().any(|v| kind.eq(v)) {
state.recover();
} else {
let Some(item) = state.next() else {
unreachable!()
};
assert!(kind.is_ident());
state.token(SyntaxKind::Ident, item);
}
}
fn should_parse_content(state: &mut PeekSequence) -> bool {
const ACCEPT: &[TokenKind] = &[TokenKind::Ident];
state.peek_next().map_or(false, |k| {
let kind = k.kind();
ACCEPT.iter().any(|k| k.is(kind))
})
}
}