compilers · parsing · rust

Crafting a parser, part 5: error recovery and diagnostics

2026-06-192 min readcompilers

A parser that gives up on the first syntax error is fine for a homework interpreter and useless for everything else. Compilers and editors must report many errors per run and keep producing a usable tree. This post is about parsing that bends instead of breaking.

Errors are data, not exceptions

Don't panic! or bail with the first surprise. Push a diagnostic into a list, insert an error node, and keep parsing.

diag.rs
1fn expect(&mut self, kind: TokenKind) -> Token {2    if self.peek().kind == kind {3        self.bump()4    } else {5        self.error(Diagnostic::expected(kind, self.peek())); // record + continue6        Token::missing(self.peek().span)                     // synthetic node7    }8}

Panic-mode recovery

When a rule is hopelessly lost, synchronize: skip tokens until you reach a known boundary — a ;, a }, or the start of the next statement — then resume. You lose one construct, not the rest of the file.

Choose sync points deliberately

Good synchronization tokens are statement and block boundaries. Synchronizing on, say, every + makes a single typo cascade into dozens of nonsense errors. Sync rarely, at structural seams.

Diagnostics that don't lie

The message is half the product. "expected ;" with the span of the previous token beats "syntax error" with no location. Carry the expected set and the found token; render a caret under the source.

Good diagnostic
owns: trust
expected vs foundprecise span + caretone error per root cause
Points at the fix.
Bad diagnostic
owns: confusion
"parse error near line 12"a cascade of follow-onsblames recovery, not the typo
Teaches users to distrust the tool.
🦀
Ferris' hot tip

Deduplicate cascading errors: once you emit a diagnostic, suppress further ones until you have successfully consumed a token. One typo should yield one message, not ten.

Takeaways

  • Record diagnostics and synthesize nodes; never stop at the first error.
  • Panic-mode + structural sync points keep the rest of the file parseable.
  • Spend effort on the message — expected/found with a real span.

Next: ambiguity, lookahead, and the lexer hack.

Comments
Loading comments…