Skip to content

Support mutual (indirect) left-recursion that ANTLR rejects #151

Description

@tinovyatkin

Context

ANTLR 4 supports direct left-recursion: a rule that references itself as its own left corner (e : e '*' e | e '+' e | INT ;) is rewritten internally into a precedence-climbing form. This is the well-known ALL(*) precedence-rule handling.

It does not support mutual (indirect) left-recursion — a cycle through two or more rules, e.g.:

a : b '+' a | INT ;
b : a '.' b | ID ;

Here a can reach a left-recursive position through b and vice versa. ANTLR rejects this ("rule ... is left-recursive" / mutually left-recursive error) because its precedence rewrite only recognizes the direct self-reference shape. The grammar is expressible in ANTLR syntax; the tool just refuses it.

Because this is a grammar-source-shape question, it is blocked on the .g4-as-source effort (#141): the current metadata-first flow never sees the offending rules — the ANTLR tool rejects them before any .interp exists.

Research question

Once we own grammar source, can antlr-rust-runtime accept a mutually-left-recursive grammar that ANTLR rejects, and generate a correct parser for it?

Two families of approach to evaluate:

  1. Transform to an equivalent accepted form. Detect the recursion cycle and apply a semantics-preserving rewrite (indirect → direct left-recursion elimination, or grammar-level substitution to collapse the cycle into a single precedence rule) so the existing precedence machinery can handle it. Lowest-risk if a general, provably-equivalent transform exists.
  2. Handle the cycle directly in prediction. Extend the recognizer to climb precedence across a rule cycle rather than a single rule. Higher-risk, larger runtime surface, but avoids reshaping the user's grammar/tree.

The evaluation should establish which class of mutual recursion is tractable (there are known-hard cases) and where we must still decline.

Potential benefit vs the ANTLR limitation

  • Expressiveness. Some languages are more naturally written with mutually-recursive rules (e.g. expression/type grammars where terms and types refer to each other left-recursively). Today authors must hand-refactor into a shape ANTLR accepts, which distorts the grammar and the resulting tree/labels away from how the language is actually specified.
  • Fidelity to published grammars. A spec grammar written in the natural mutual form could be consumed closer to as-published, instead of forcing a target-specific rewrite.
  • A concrete capability ANTLR doesn't have. "Accepts grammars the reference tool rejects" is a differentiator — if correctness is provable. It must never mean "accepts and silently miscompiles."

Required rigor

  • Correctness is the whole game. For any accepted mutual-recursion grammar, define the intended associativity/precedence semantics explicitly (the user must be able to state it, as they can for direct precedence rules via alt order), and prove the generated parser matches it. Ambiguous cycles with no well-defined precedence must be rejected with a clear diagnostic, not guessed.
  • Where approach (1) is used, differentially validate the transformed grammar against the intended semantics over valid and invalid inputs; parse trees must reflect the declared precedence/associativity.
  • Full ANTLR runtime testsuite must still pass with zero skips (this must not perturb direct-left-recursion handling, which already works and is conformance-covered).
  • If we cannot prove a general solution, ship a detector that recognizes mutual left-recursion and emits a precise, actionable error (strictly better than ANTLR's generic rejection) — that alone is a usability win and a safe first increment.

Acceptance criteria (staged)

  1. Detect + diagnose: identify mutual-left-recursion cycles from grammar source and report them precisely (rules in the cycle, why rejected). Safe, ships first.
  2. Accept a proven subclass: define the subclass we can transform/handle with provable precedence semantics; generate a correct parser; full parity + zero testsuite skips.
  3. Document exactly which mutual-recursion shapes are supported vs declined.

Notes

Research-track — feasibility before commitment; the honest outcome may be "stage 1 only" if a general correct transform proves intractable. Related: #141 (source input, hard dependency), direct-left-recursion precedence handling (must not regress). Origin: discussion on antlr-ng#99 about what working directly from .g4 unlocks beyond a metadata file.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestquestionFurther information is requested

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions