Skip to content

Commit

Permalink
Improve error recovery in delimited groups when can't parse anything
Browse files Browse the repository at this point in the history
TerminatedBy can be a bit too greedy, i.e. we recovered some unexpected
item definition just because... we recovered at a terminator. However,
the delimited group is a reasonable boundary since it's simple (2-3 kinds
of delimiters) and the opening delimiter must always match the closing
one, so it produces more reliable and self-contained recovered phrases,
so attempting to recover from a null parse in a delimited group makes
more sense.
  • Loading branch information
Xanewok committed Sep 25, 2023
1 parent 4bbad48 commit 24be667
Show file tree
Hide file tree
Showing 10 changed files with 307 additions and 63 deletions.
2 changes: 2 additions & 0 deletions crates/codegen/parser/generator/src/parser_definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ impl ParserDefinitionNodeExtensions for ParserDefinitionNode {
|input| Lexer::leading_trivia(self, input),
TokenKind::#close_token,
Self::#delimiters(),
RecoverFromNoMatch::Yes,
)
)?;
},
Expand Down Expand Up @@ -251,6 +252,7 @@ impl ParserDefinitionNodeExtensions for ParserDefinitionNode {
|input| Lexer::leading_trivia(self, input),
TokenKind::#terminator_token_kind,
Self::#delimiters(),
RecoverFromNoMatch::No,
)
)?;
},
Expand Down
46 changes: 37 additions & 9 deletions crates/codegen/parser/runtime/src/support/recovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ use crate::text_index::TextRangeExtensions as _;
use super::parser_result::SkippedUntil;
use super::ParserContext;

/// An explicit parameter for the [`ParserResult::recover_until_with_nested_delims`] method.
pub enum RecoverFromNoMatch {
Yes,
No,
}

impl RecoverFromNoMatch {
pub fn as_bool(&self) -> bool {
matches!(self, RecoverFromNoMatch::Yes)
}
}

fn opt_parse(
input: &mut ParserContext,
parse: impl Fn(&mut ParserContext) -> ParserResult,
Expand Down Expand Up @@ -34,6 +46,7 @@ impl ParserResult {
leading_trivia: impl Fn(&mut ParserContext) -> ParserResult,
expected: TokenKind,
delims: &[(TokenKind, TokenKind)],
recover_from_no_match: RecoverFromNoMatch,
) -> ParserResult {
let before_recovery = input.position();

Expand All @@ -47,10 +60,23 @@ impl ParserResult {
token
};

let (mut nodes, mut expected_tokens, is_incomplete) = match self {
ParserResult::IncompleteMatch(result) => (result.nodes, result.expected_tokens, true),
enum ParseResultKind {
Match,
Incomplete,
NoMatch,
}

let (mut nodes, mut expected_tokens, result_kind) = match self {
ParserResult::IncompleteMatch(result) => (
result.nodes,
result.expected_tokens,
ParseResultKind::Incomplete,
),
ParserResult::Match(result) if peek_token_after_trivia() != Some(expected) => {
(result.nodes, result.expected_tokens, false)
(result.nodes, result.expected_tokens, ParseResultKind::Match)
}
ParserResult::NoMatch(result) if recover_from_no_match.as_bool() => {
(vec![], result.expected_tokens, ParseResultKind::NoMatch)
}
// No need to recover, so just return as-is.
_ => return self,
Expand All @@ -70,7 +96,7 @@ impl ParserResult {
&& (token == expected || input.closing_delimiters().contains(&token)) =>
{
nodes.extend(leading_trivia);
if !is_incomplete {
if matches!(result_kind, ParseResultKind::Match) {
expected_tokens.push(expected);
}

Expand Down Expand Up @@ -106,11 +132,13 @@ impl ParserResult {
None => {
input.set_position(before_recovery);

if is_incomplete {
return ParserResult::incomplete_match(nodes, expected_tokens);
} else {
return ParserResult::r#match(nodes, expected_tokens);
}
return match result_kind {
ParseResultKind::Match => ParserResult::r#match(nodes, expected_tokens),
ParseResultKind::Incomplete => {
ParserResult::incomplete_match(nodes, expected_tokens)
}
ParseResultKind::NoMatch => ParserResult::no_match(expected_tokens),
};
}
}
}
Expand Down
Loading

0 comments on commit 24be667

Please sign in to comment.