Skip to content
Sinfolke edited this page Oct 25, 2026 · 2 revisions

Error Recovery (Fail Blocks)

Nobody writes perfect input, least of all while typing. A parser that stops dead at the first typo isn't much fun to use, so ISPA lets you say what should happen when things go wrong.

🧪 Heads-up: fail blocks are limited right now. They work, but they're early and not as powerful as we want them to be. Expect them to grow, and expect some rough edges today.

🚧 LR-family parsers (LR(0), LR(1), LALR, LR(*)) don't have proper failure handling yet, so fail blocks apply to the LL(k) parser for now.

What a fail block can do

A fail block can contain:

  • Python-style statements, such as if/else and calls on the context ctx
  • match statements: match <grammar regex>, optionally followed by -> and the ids that receive what the @ units captured

An example

This one comes from ISPA's own grammar. It handles a mistake where someone writes = or => instead of : between an AST field name and its value.

// Assignment arrows belong to other ISPA constructs. In an AST field they
// almost always mean ':': replace the separator without eating its expression.
fail ast_field_separator {
    ctx.error("expected ':' after AST field name; '=' and '=>' are not AST field separators")
    if ctx.at("=") or ctx.at("=>"):
        ctx.replace()
    else:
        ctx.insert()
}

Step by step:

  1. ctx.error(...) reports a helpful message.
  2. ctx.at("=") checks whether the parser is looking at the wrong character.
  3. If so, ctx.replace() swaps it for the expected one. Otherwise ctx.insert() adds the missing one.

Either way, parsing carries on, so you can report several mistakes in one run instead of just the first.

Other LL handlers use ctx.retry to try again after an edit and ctx.sync_tokens("NEWLINE", "DEDENT") to skip to a token boundary. ctx.sync(...) works at source level for foreign payloads. Synchronization is bounded by parser skip/error budgets and does not balance nested delimiters. See parser/python/README.md for a working recovery grammar and API details.

What's not here yet

  • Recovery does not manufacture a valid value for a missing capture. Check getErrors() and getContextErrors() before using the returned tree.
  • Unterminated strings, invalid indentation, and some other lexer errors can still throw. Recovery may stop when its skip or error budget is exhausted.

We're working on it. 💪

Clone this wiki locally