Skip to content

feat(codegen)!: centralize generated rule lifecycle - #301

Merged
tinovyatkin merged 2 commits into
mainfrom
issue-277-centralize-rule-lifecycle
Aug 4, 2026
Merged

feat(codegen)!: centralize generated rule lifecycle#301
tinovyatkin merged 2 commits into
mainfrom
issue-277-centralize-rule-lifecycle

Conversation

@tinovyatkin

@tinovyatkin tinovyatkin commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Closes #277.

Summary

  • Add a hidden runtime macro that owns generated parser rule dispatch, entry,
    recovery, adaptive retry, and exit lifecycle behavior.
  • Emit compact ordinary and left-recursive invocations while keeping token
    matches, rule calls, predictions, semantic actions, and attributes inline.
  • Add snapshots for both compact generated forms and retain the existing
    lifecycle, recovery, listener, action, attribute, and generated-only fixtures.
  • Advance the generated-code API from revision 5 to 6, continue accepting
    revisions 1 through 5, and regenerate all checked-in recognizers.

Generated source

Recognizer Before After Reduction
ANTLRv4 parser lines 13,509 11,298 2,211 (16.4%)
ANTLRv4 parser bytes 750,574 656,488 94,086 (12.5%)
Rust parser lines 52,668 45,573 7,095 (13.5%)
Rust parser bytes 3,371,694 3,121,370 250,324 (7.4%)

Performance

Measured against origin/main at a50acb66:

Measurement Baseline This change
Cold generator cargo check (two fresh alternating target dirs) 11.33 s 10.95 s
Incremental frontend rebuild (5 runs) 1.222 +/- 0.032 s 1.184 +/- 0.015 s
Release antlr4-rust-gen binary 11,601,408 bytes 11,582,688 bytes
Kotlin parse-only average (5 paired 1,000-iteration runs) 0.558 ms 0.555 ms

No measurement shows a material regression.

Validation

  • cargo test --locked --workspace --all-features
  • cargo clippy --locked --workspace --all-targets --all-features -- -D warnings
  • ANTLR runtime testsuite: 357 passed, 0 failed, 0 skipped
  • Kotlin parity: 9/9 protected snippets
  • JavaScript parity: 6/6 token streams and parse trees
  • TypeScript parity: 5/5 token streams and parse trees
  • ANTLRv4 Stage 0 -> 1 -> 2 fixed point and pinned frontend corpus
  • Rust recognizer and XPath lexer regeneration checks
  • cargo fmt --all -- --check
  • git diff --check

Summary by CodeRabbit

  • New Features

    • Added shared runtime handling for generated parser rule lifecycles, including recursion, retries, synchronization, recovery, and listener events.
    • Updated generated parser compatibility to revision 6.
  • Bug Fixes

    • Improved consistency of rule dispatch, error recovery, and deeply nested parser behavior.
  • Documentation

    • Updated compatibility documentation to describe revision 6 and supported runtime revisions.

Move generated parser dispatch, entry, recovery, retry, and exit scaffolding behind a doc-hidden runtime macro. Keep grammar-specific matches, rule calls, predictions, semantic actions, and attribute handling inline in each compact invocation while sharing ordinary and left-recursive lifecycle semantics.

Bump the generated-code API to revision 6 because new recognizers require the lifecycle macro. Continue accepting revisions 1 through 5, update compatibility diagnostics and snapshots, and regenerate the ANTLRv4, Rust, and XPath recognizers.

The ANTLRv4 parser drops from 13,509 to 11,298 lines and from 750,574 to 656,488 bytes. Cold and incremental package checks show no regression, the release generator binary is 18,720 bytes smaller, and Kotlin parse-only timing remains flat.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@tinovyatkin, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 46 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: da059c0f-5b15-4f29-a6ef-e1666425148b

📥 Commits

Reviewing files that changed from the base of the PR and between 4312f58 and 9580990.

📒 Files selected for processing (3)
  • crates/antlr-rust-codegen/src/generator/tests.rs
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/parser.rs
  • crates/antlr-rust-runtime/src/parser.rs
📝 Walkthrough

Walkthrough

The generator now emits compact ordinary and left-recursive rule wrappers that use a runtime lifecycle macro. The runtime owns entry, listener, stack, retry, synchronization, recovery, and completion handling. The code-generation API advances from revision 5 to 6.

Changes

Generated rule lifecycle

Layer / File(s) Summary
Revision 6 compatibility contract
README.md, crates/antlr-rust-runtime/src/lib.rs, crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/cli.rs
The runtime and compatibility tests support code-generation API revision 6. The documentation describes the updated generated-source contract.
Runtime rule lifecycle macro
crates/antlr-rust-runtime/src/parser.rs
__antlr4_rust_generated_rule! centralizes ordinary and recursive rule entry, execution, retry, synchronization, recovery, and completion handling.
Compact generated rule integration
crates/antlr-rust-codegen/src/parser/render/rules.rs, crates/antlr-rust-codegen/src/parser/routing.rs
Generated rules delegate lifecycle handling to the runtime macro. Ordinary and left-recursive entry methods use separate lifecycle modes. Embedded code receives dynamic indentation.
Generated output validation
crates/antlr-rust-codegen/src/generator/tests.rs, crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/parser.rs, third_party/antlr-v4-grammar/self-hosted.sha256
Tests validate compact rule output, retry handling, embedded initialization ordering, synchronization behavior, deep nesting, and updated generated-source hashes.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GeneratedParser
  participant __antlr4_rust_generated_rule
  participant ParserState
  participant Recovery
  GeneratedParser->>__antlr4_rust_generated_rule: invoke ordinary or recursive rule
  __antlr4_rust_generated_rule->>ParserState: enter rule, listener, and stack lifecycle
  __antlr4_rust_generated_rule->>GeneratedParser: run setup and grammar-specific body
  __antlr4_rust_generated_rule->>Recovery: handle retry, synchronization, or recovery
  Recovery-->>__antlr4_rust_generated_rule: recovery outcome
  __antlr4_rust_generated_rule->>ParserState: finalize rule and parse tree state
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The implementation and tests address issue #277, but excluded snapshots, generated recognizers, and migration documentation prevent full verification. Review the excluded snapshots, generated recognizers, and docs/migration.md to verify compact fixtures, regeneration, compatibility documentation, and reported outputs.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary change: centralizing generated parser rule lifecycle handling.
Out of Scope Changes check ✅ Passed The changes support issue #277 through runtime centralization, compatibility updates, tests, documentation, and regenerated recognizer checksums.
Docstring Coverage ✅ Passed Docstring coverage is 91.67% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-277-centralize-rule-lifecycle

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Copy/Paste Detection

Found 34 duplication(s) across 7 changed non-generated Rust file(s) (threshold: 100 tokens).

Show duplications

Found a 21 line (226 tokens) duplication in the following files:

  • Starting at line 15133 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 16538 of crates/antlr-rust-runtime/src/parser.rs
            (9, AtnStateKind::RuleStop),
        ] {
            assert_eq!(atn.add_state(kind, Some(0)).expect("state").index(), state);
        }
        atn.set_left_recursive_rule(0)
            .expect("left-recursive rule start");
        atn.set_precedence_rule_decision(2)
            .expect("precedence decision");
        atn.set_loop_back_state(8, 7).expect("loop-back state");
        atn.set_rule_to_start_state(vec![0])
            .expect("rule start states");
        atn.set_rule_to_stop_state(vec![9])
            .expect("rule stop states");
        for state in [1, 2, 3] {
            atn.add_decision_state(state).expect("decision state");
        }
        for (source, target) in [(0, 1), (2, 3), (2, 8), (7, 2), (8, 9)] {
            atn.add_transition(source, ParserTransitionSpec::Epsilon { target })
                .expect("epsilon transition");
        }
        for (source, target, label) in [(1, 2, 1), (1, 2, 2), (4, 6, 4), (5, 6, 3), (6, 7, 1)] {
```rust

---

Found a 44 line (215 tokens) duplication in the following files:
* Starting at line 4167 of crates/antlr-rust-codegen/src/generator/tests.rs
* Starting at line 16879 of crates/antlr-rust-runtime/src/parser.rs

```rust
fn plus_loop_atn() -> ParserAtn {
    let mut atn = ParserAtnBuilder::new(2);
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStart, Some(0))
            .expect("state")
            .index(),
        0
    );
    assert_eq!(
        atn.add_state(AtnStateKind::PlusBlockStart, Some(0))
            .expect("state")
            .index(),
        1
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        2
    );
    assert_eq!(
        atn.add_state(AtnStateKind::BlockEnd, Some(0))
            .expect("state")
            .index(),
        3
    );
    assert_eq!(
        atn.add_state(AtnStateKind::PlusLoopBack, Some(0))
            .expect("state")
            .index(),
        4
    );
    assert_eq!(
        atn.add_state(AtnStateKind::LoopEnd, Some(0))
            .expect("state")
            .index(),
        5
    );
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStop, Some(0))
            .expect("state")
            .index(),
        6
    );

Found a 25 line (193 tokens) duplication in the following files:

  • Starting at line 16467 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 17434 of crates/antlr-rust-runtime/src/parser.rs
        let mut atn = ParserAtnBuilder::new(1);
        for (state_number, kind) in [
            (0, AtnStateKind::RuleStart),
            (1, AtnStateKind::StarLoopEntry),
            (2, AtnStateKind::Basic),
            (3, AtnStateKind::Basic),
            (4, AtnStateKind::StarLoopBack),
            (5, AtnStateKind::LoopEnd),
            (6, AtnStateKind::RuleStop),
        ] {
            assert_eq!(
                atn.add_state(kind, Some(0)).expect("state").index(),
                state_number
            );
        }
        atn.set_rule_to_start_state(vec![0])
            .expect("rule start states");
        atn.set_rule_to_stop_state(vec![6])
            .expect("rule stop states");
        atn.add_decision_state(1).expect("decision state");
        atn.set_loop_back_state(5, 4).expect("loop back state");
        atn.add_transition(0, ParserTransitionSpec::Epsilon { target: 1 })
            .expect("entry transition");
        atn.add_transition(1, ParserTransitionSpec::Epsilon { target: 2 })
            .expect("loop body");
```rust

---

Found a 39 line (188 tokens) duplication in the following files:
* Starting at line 4031 of crates/antlr-rust-codegen/src/generator/tests.rs
* Starting at line 16587 of crates/antlr-rust-runtime/src/parser.rs

```rust
fn block_decision_atn() -> ParserAtn {
    let mut atn = ParserAtnBuilder::new(2);
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStart, Some(0))
            .expect("state")
            .index(),
        0
    );
    assert_eq!(
        atn.add_state(AtnStateKind::BlockStart, Some(0))
            .expect("state")
            .index(),
        1
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        2
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        3
    );
    assert_eq!(
        atn.add_state(AtnStateKind::BlockEnd, Some(0))
            .expect("state")
            .index(),
        4
    );
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStop, Some(0))
            .expect("state")
            .index(),
        5
    );
    atn.set_end_state(1, 4).expect("block end state");

Found a 27 line (145 tokens) duplication in the following files:

  • Starting at line 17955 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 18089 of crates/antlr-rust-runtime/src/parser.rs
    fn generated_match_token_recovers_missing_token_from_context_follow() {
        let atn = generated_match_recovery_atn();
        let data = RecognizerData::new(
            "Mini.g4",
            Vocabulary::new(
                [None, Some("'X'"), Some("'Y'")],
                [None, Some("X"), Some("Y")],
                [None::<&str>, None, None],
            ),
        );
        let mut parser = BaseParser::new(
            CommonTokenStream::new(Source {
                tokens: vec![TestToken::eof("parser-test", 3, 1, 3)],
                index: 0,
            }),
            data,
        );
        parser.rule_context_stack = vec![
            RuleContextFrame {
                rule_index: 0,
                invoking_state: 0,
            },
            RuleContextFrame {
                rule_index: 1,
                invoking_state: 1,
            },
        ];
```rust

---

Found a 26 line (142 tokens) duplication in the following files:
* Starting at line 4069 of crates/antlr-rust-codegen/src/generator/tests.rs
* Starting at line 4292 of crates/antlr-rust-codegen/src/generator/tests.rs

```rust
    atn.set_end_state(1, 4).expect("block end state");
    atn.add_transition(0, ParserTransitionSpec::Epsilon { target: 1 })
        .expect("transition");
    atn.add_transition(1, ParserTransitionSpec::Epsilon { target: 2 })
        .expect("transition");
    atn.add_transition(1, ParserTransitionSpec::Epsilon { target: 3 })
        .expect("transition");
    atn.add_transition(
        2,
        ParserTransitionSpec::Atom {
            target: 4,
            label: 1,
        },
    )
    .expect("transition");
    atn.add_transition(
        3,
        ParserTransitionSpec::Atom {
            target: 4,
            label: 2,
        },
    )
    .expect("transition");
    atn.add_transition(4, ParserTransitionSpec::Epsilon { target: 5 })
        .expect("transition");
    atn.add_decision_state(1).expect("decision state");

Found a 26 line (128 tokens) duplication in the following files:

  • Starting at line 3980 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 17551 of crates/antlr-rust-runtime/src/parser.rs
fn linear_rule_atn() -> ParserAtn {
    let mut atn = ParserAtnBuilder::new(2);
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStart, Some(0))
            .expect("state")
            .index(),
        0
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        1
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        2
    );
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStop, Some(0))
            .expect("state")
            .index(),
        3
    );
```rust

---

Found a 18 line (128 tokens) duplication in the following files:
* Starting at line 16133 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 16158 of crates/antlr-rust-runtime/src/parser.rs

```rust
    fn epsilon_cycle_atn() -> Atn {
        let mut atn = ParserAtnBuilder::new(1);
        for (state_number, kind) in [
            (0, AtnStateKind::RuleStart),
            (1, AtnStateKind::Basic),
            (2, AtnStateKind::RuleStop),
        ] {
            assert_eq!(
                atn.add_state(kind, Some(0)).expect("state").index(),
                state_number
            );
        }
        atn.set_rule_to_start_state(vec![0])
            .expect("rule start states");
        atn.set_rule_to_stop_state(vec![2])
            .expect("rule stop states");
        atn.add_transition(0, ParserTransitionSpec::Epsilon { target: 1 })
            .expect("transition");

Found a 27 line (127 tokens) duplication in the following files:

  • Starting at line 4032 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 16660 of crates/antlr-rust-runtime/src/parser.rs
    let mut atn = ParserAtnBuilder::new(2);
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStart, Some(0))
            .expect("state")
            .index(),
        0
    );
    assert_eq!(
        atn.add_state(AtnStateKind::BlockStart, Some(0))
            .expect("state")
            .index(),
        1
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        2
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        3
    );
    assert_eq!(
        atn.add_state(AtnStateKind::BlockEnd, Some(0))
```rust

---

Found a 27 line (127 tokens) duplication in the following files:
* Starting at line 16588 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 16660 of crates/antlr-rust-runtime/src/parser.rs

```rust
        let mut atn = ParserAtnBuilder::new(2);
        assert_eq!(
            atn.add_state(AtnStateKind::RuleStart, Some(0))
                .expect("state")
                .index(),
            0
        );
        assert_eq!(
            atn.add_state(AtnStateKind::BlockStart, Some(0))
                .expect("state")
                .index(),
            1
        );
        assert_eq!(
            atn.add_state(AtnStateKind::Basic, Some(0))
                .expect("state")
                .index(),
            2
        );
        assert_eq!(
            atn.add_state(AtnStateKind::Basic, Some(0))
                .expect("state")
                .index(),
            3
        );
        assert_eq!(
            atn.add_state(AtnStateKind::BlockEnd, Some(0))

Found a 22 line (125 tokens) duplication in the following files:

  • Starting at line 16614 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 16686 of crates/antlr-rust-runtime/src/parser.rs
            atn.add_state(AtnStateKind::BlockEnd, Some(0))
                .expect("state")
                .index(),
            4
        );
        assert_eq!(
            atn.add_state(AtnStateKind::RuleStop, Some(0))
                .expect("state")
                .index(),
            5
        );
        atn.set_rule_to_start_state(vec![0])
            .expect("rule start states");
        atn.set_rule_to_stop_state(vec![5])
            .expect("rule stop states");
        atn.add_decision_state(1).expect("decision state");
        atn.add_transition(0, ParserTransitionSpec::Epsilon { target: 1 })
            .expect("transition");
        atn.add_transition(
            1,
            ParserTransitionSpec::Atom {
                target: 2,
```rust

---

Found a 34 line (119 tokens) duplication in the following files:
* Starting at line 10718 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 10793 of crates/antlr-rust-runtime/src/parser.rs

```rust
                        outcomes.extend(
                            self.recognize_state(
                                atn,
                                RecognizeRequest {
                                    state_number: *target,
                                    stop_state,
                                    index,
                                    rule_start_index,
                                    decision_start_index: next_decision_start_index,
                                    init_action_rules,
                                    predicates,
                                    semantics,
                                    rule_args,
                                    member_actions,
                                    return_actions,
                                    local_int_arg,
                                    member_values: member_values.clone(),
                                    return_values: return_values.clone(),
                                    rule_alt_number: next_alt_number,
                                    track_alt_numbers,
                                    consumed_eof,
                                    committed_decision: transition_committed,
                                    precedence,
                                    depth: depth + 1,
                                    recovery_symbols: epsilon_recovery_symbols.clone(),
                                    recovery_state: epsilon_recovery_state,
                                },
                                visiting,
                                memo,
                                expected,
                            )
                            .into_iter()
                            .map(|mut outcome| {
                                prepend_decision(&mut outcome, decision);

Found a 13 line (117 tokens) duplication in the following files:

  • Starting at line 162 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 189 of crates/antlr-rust-runtime/src/parser.rs
        ordinary $parser:ident, $state:expr, $rule:expr, $allow_fallback:expr,
        $atn:expr, $fatal:path;
        retry [$($retry:tt)*];
        bind ($ctx:ident, $rule_start:ident, $consumed_eof:ident, $sync_error:ident);
        setup { $($setup:tt)* }
        body { $($body:tt)* }
        success { $($success:tt)* }
        recovery { $($recovery:tt)* }
    ) => {
        $crate::__antlr4_rust_generated_rule! {
            @body
            parser $parser;
            enter $parser.base.enter_rule($state, $rule);
```rust

---

Found a 25 line (115 tokens) duplication in the following files:
* Starting at line 4040 of crates/antlr-rust-codegen/src/generator/tests.rs
* Starting at line 4250 of crates/antlr-rust-codegen/src/generator/tests.rs
* Starting at line 16596 of crates/antlr-rust-runtime/src/parser.rs

```rust
        atn.add_state(AtnStateKind::BlockStart, Some(0))
            .expect("state")
            .index(),
        1
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        2
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        3
    );
    assert_eq!(
        atn.add_state(AtnStateKind::BlockEnd, Some(0))
            .expect("state")
            .index(),
        4
    );
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStop, Some(0))

Found a 15 line (113 tokens) duplication in the following files:

  • Starting at line 18014 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 18292 of crates/antlr-rust-runtime/src/parser.rs
    fn generated_match_token_counts_single_token_deletion_recovery() {
        let atn = generated_match_recovery_atn();
        let data = RecognizerData::new(
            "Mini.g4",
            Vocabulary::new(
                [None, Some("'X'"), Some("'Y'"), Some("'Z'")],
                [None, Some("X"), Some("Y"), Some("Z")],
                [None::<&str>, None, None, None],
            ),
        );
        let mut parser = BaseParser::new(
            CommonTokenStream::new(Source {
                tokens: vec![
                    TestToken::new(3).with_text("z"),
                    TestToken::new(2).with_text("y"),
```rust

---

Found a 22 line (112 tokens) duplication in the following files:
* Starting at line 4167 of crates/antlr-rust-codegen/src/generator/tests.rs
* Starting at line 4241 of crates/antlr-rust-codegen/src/generator/tests.rs

```rust
fn plus_loop_atn() -> ParserAtn {
    let mut atn = ParserAtnBuilder::new(2);
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStart, Some(0))
            .expect("state")
            .index(),
        0
    );
    assert_eq!(
        atn.add_state(AtnStateKind::PlusBlockStart, Some(0))
            .expect("state")
            .index(),
        1
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        2
    );
    assert_eq!(
        atn.add_state(AtnStateKind::BlockEnd, Some(0))

Found a 18 line (112 tokens) duplication in the following files:

  • Starting at line 14943 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 17360 of crates/antlr-rust-runtime/src/parser.rs
            (4, AtnStateKind::Basic, 0),
            (5, AtnStateKind::RuleStop, 0),
            (6, AtnStateKind::RuleStart, 1),
            (7, AtnStateKind::Basic, 1),
            (8, AtnStateKind::RuleStop, 1),
        ] {
            assert_eq!(
                atn.add_state(kind, Some(rule_index))
                    .expect("state")
                    .index(),
                state_number
            );
        }
        atn.set_rule_to_start_state(vec![0, 6])
            .expect("rule start states");
        atn.set_rule_to_stop_state(vec![5, 8])
            .expect("rule stop states");
        atn.add_decision_state(2).expect("decision state");
```rust

---

Found a 12 line (112 tokens) duplication in the following files:
* Starting at line 15186 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 15269 of crates/antlr-rust-runtime/src/parser.rs

```rust
        let mut atn = ParserAtnBuilder::new(1);
        for (state, kind, rule) in [
            (0, AtnStateKind::RuleStart, 0),
            (1, AtnStateKind::StarLoopEntry, 0),
            (2, AtnStateKind::Basic, 0), // ops hub
            (3, AtnStateKind::Basic, 0), // shift prec
            (4, AtnStateKind::Basic, 0), // shift first >
            (5, AtnStateKind::Basic, 0), // shift second >
            (6, AtnStateKind::Basic, 0), // rel prec
            (7, AtnStateKind::Basic, 0), // rel >
            (8, AtnStateKind::LoopEnd, 0),
            (9, AtnStateKind::RuleStop, 0),

Found a 22 line (112 tokens) duplication in the following files:

  • Starting at line 17134 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 17551 of crates/antlr-rust-runtime/src/parser.rs
    fn predicate_after_token_atn() -> Atn {
        let mut atn = ParserAtnBuilder::new(2);
        assert_eq!(
            atn.add_state(AtnStateKind::RuleStart, Some(0))
                .expect("state")
                .index(),
            0
        );
        assert_eq!(
            atn.add_state(AtnStateKind::Basic, Some(0))
                .expect("state")
                .index(),
            1
        );
        assert_eq!(
            atn.add_state(AtnStateKind::Basic, Some(0))
                .expect("state")
                .index(),
            2
        );
        assert_eq!(
            atn.add_state(AtnStateKind::Basic, Some(0))
```rust

---

Found a 22 line (111 tokens) duplication in the following files:
* Starting at line 15008 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 17134 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 17551 of crates/antlr-rust-runtime/src/parser.rs

```rust
    fn left_recursive_loop_with_caller_follow_atn(caller_symbol: i32) -> Atn {
        let mut atn = ParserAtnBuilder::new(2);
        assert_eq!(
            atn.add_state(AtnStateKind::RuleStart, Some(0))
                .expect("state")
                .index(),
            0
        );
        assert_eq!(
            atn.add_state(AtnStateKind::Basic, Some(0))
                .expect("state")
                .index(),
            1
        );
        assert_eq!(
            atn.add_state(AtnStateKind::Basic, Some(0))
                .expect("state")
                .index(),
            2
        );
        assert_eq!(
            atn.add_state(AtnStateKind::RuleStart, Some(1))

Found a 27 line (110 tokens) duplication in the following files:

  • Starting at line 3394 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 3506 of crates/antlr-rust-codegen/src/generator/tests.rs
            decision: 0,
            alts: (1, 2),
            track_alt_number: false,
            allow_semantic_context: true,
            force_context: false,
            plus_loop: false,
            fast_path: None,
            body: &body,
        },
        0,
        GeneratedStepRenderContext {
            current_rule_index: 0,
            embedded: None,
            portable_locals: None,
            decision_routing: DecisionRoutingRender::default(),
            inline_action_statements: &BTreeMap::new(),
            track_alt_numbers: false,
            track_context_alt_numbers: false,
            direct_generated_rule_calls: &[],
            atn_preferred_rule_calls: &[],
            adaptive_atn_preferred_rule_slots: &[],
            adaptive_atn_probe_rule_slots: &[],
        },
    );

    // The whole rendered star-loop captures the leading-predicate-to-exit-alt filtering.
    insta::assert_snapshot!(
```rust

---

Found a 14 line (110 tokens) duplication in the following files:
* Starting at line 17684 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 21629 of crates/antlr-rust-runtime/src/parser.rs

```rust
    fn parser_matches_token_and_reports_mismatch() {
        let source = Source {
            tokens: vec![
                TestToken::new(1).with_text("x"),
                TestToken::eof("parser-test", 1, 1, 1),
            ],
            index: 0,
        };
        let data = RecognizerData::new(
            "Mini.g4",
            Vocabulary::new([None, Some("'x'")], [None, Some("X")], [None::<&str>, None]),
        );
        let mut parser = BaseParser::new(CommonTokenStream::new(source), data);
        let matched = parser.match_token(1).expect("token 1 should match");

Found a 13 line (109 tokens) duplication in the following files:

  • Starting at line 17684 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 21654 of crates/antlr-rust-runtime/src/parser.rs
    fn parser_matches_token_and_reports_mismatch() {
        let source = Source {
            tokens: vec![
                TestToken::new(1).with_text("x"),
                TestToken::eof("parser-test", 1, 1, 1),
            ],
            index: 0,
        };
        let data = RecognizerData::new(
            "Mini.g4",
            Vocabulary::new([None, Some("'x'")], [None, Some("X")], [None::<&str>, None]),
        );
        let mut parser = BaseParser::new(CommonTokenStream::new(source), data);
```rust

---

Found a 22 line (108 tokens) duplication in the following files:
* Starting at line 3980 of crates/antlr-rust-codegen/src/generator/tests.rs
* Starting at line 15008 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 17134 of crates/antlr-rust-runtime/src/parser.rs

```rust
fn linear_rule_atn() -> ParserAtn {
    let mut atn = ParserAtnBuilder::new(2);
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStart, Some(0))
            .expect("state")
            .index(),
        0
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        1
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        2
    );
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStop, Some(0))

Found a 22 line (108 tokens) duplication in the following files:

  • Starting at line 4241 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 16879 of crates/antlr-rust-runtime/src/parser.rs
fn plus_block_decision_atn() -> ParserAtn {
    let mut atn = ParserAtnBuilder::new(2);
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStart, Some(0))
            .expect("state")
            .index(),
        0
    );
    assert_eq!(
        atn.add_state(AtnStateKind::PlusBlockStart, Some(0))
            .expect("state")
            .index(),
        1
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        2
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
```rust

---

Found a 22 line (108 tokens) duplication in the following files:
* Starting at line 8508 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 8899 of crates/antlr-rust-runtime/src/parser.rs

```rust
    ) -> Option<RecognizeOutcome> {
        let (error_index, message) = self.expected_error_message(rule_index, start_index, expected);
        let diagnostic = diagnostic_for_token(self.token_at(error_index), message);
        let mut next_index = error_index;
        loop {
            let symbol = self.token_type_at(next_index);
            if sync_symbols.contains(&symbol) {
                if next_index == error_index {
                    return None;
                }
                break;
            }
            if symbol == TOKEN_EOF {
                break;
            }
            let after = self.consume_index(next_index, symbol);
            if after == next_index {
                break;
            }
            next_index = after;
        }
        let mut nodes = NodeSeqId::EMPTY;

Found a 15 line (108 tokens) duplication in the following files:

  • Starting at line 21925 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 21949 of crates/antlr-rust-runtime/src/parser.rs
    fn outcome_ties_keep_later_non_recursive_alternative() {
        let arena = RecognitionArena::default();
        let first = RecognizeOutcome {
            index: 1,
            consumed_eof: false,
            alt_number: 0,
            member_values: MemberEnv::new(),
            return_values: BTreeMap::new(),
            diagnostics: DiagnosticSeqId::EMPTY,
            decisions: Vec::new(),
            actions: vec![ParserAction::new(1, 0, 0, None)],
            nodes: NodeSeqId::EMPTY,
        };
        let second = RecognizeOutcome {
            actions: vec![ParserAction::new(2, 0, 0, None)],
```rust

---

Found a 17 line (107 tokens) duplication in the following files:
* Starting at line 7567 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 8262 of crates/antlr-rust-runtime/src/parser.rs

```rust
        let report_unrecovered_error = self.is_top_level_entry();
        let start_state = atn.rule_to_start_state().get(rule_index).ok_or_else(|| {
            AntlrError::Unsupported(format!("rule {rule_index} has no start state"))
        })?;
        let stop_state = atn
            .rule_to_stop_state()
            .get(rule_index)
            .filter(|state| *state != usize::MAX)
            .ok_or_else(|| {
                AntlrError::Unsupported(format!("rule {rule_index} has no stop state"))
            })?;

        let start_index = self.current_visible_index();
        self.clear_prediction_diagnostics();
        self.reset_per_parse_caches();
        self.reset_recognition_arena();
        let caller_follow_state = self.pending_invoking_follow_state(atn);

Found a 25 line (104 tokens) duplication in the following files:

  • Starting at line 3134 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 3301 of crates/antlr-rust-codegen/src/generator/tests.rs
            state: 1,
            decision: 0,
            track_alt_number: false,
            allow_semantic_context: false,
            force_context: false,
            fast_path: None,
            alts: &alts,
        },
        0,
        GeneratedStepRenderContext {
            current_rule_index: 0,
            embedded: None,
            portable_locals: None,
            decision_routing: DecisionRoutingRender::default(),
            inline_action_statements: &BTreeMap::new(),
            track_alt_numbers: false,
            track_context_alt_numbers: false,
            direct_generated_rule_calls: &[],
            atn_preferred_rule_calls: &[],
            adaptive_atn_preferred_rule_slots: &[],
            adaptive_atn_probe_rule_slots: &[],
        },
    );

    assert!(rendered.contains("ll1_decision_prediction(atn(), 1)"));
```rust

---

Found a 13 line (104 tokens) duplication in the following files:
* Starting at line 15121 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 16525 of crates/antlr-rust-runtime/src/parser.rs

```rust
    fn labeled_left_recursive_operator_atn() -> Atn {
        let mut atn = ParserAtnBuilder::new(4);
        for (state, kind) in [
            (0, AtnStateKind::RuleStart),
            (1, AtnStateKind::BlockStart),
            (2, AtnStateKind::StarLoopEntry),
            (3, AtnStateKind::StarBlockStart),
            (4, AtnStateKind::Basic),
            (5, AtnStateKind::Basic),
            (6, AtnStateKind::Basic),
            (7, AtnStateKind::StarLoopBack),
            (8, AtnStateKind::LoopEnd),
            (9, AtnStateKind::RuleStop),

Found a 28 line (102 tokens) duplication in the following files:

  • Starting at line 3187 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 3347 of crates/antlr-rust-codegen/src/generator/tests.rs
            state: 1,
            decision: 0,
            track_alt_number: false,
            allow_semantic_context: true,
            force_context: false,
            fast_path: None,
            alts: &alts,
        },
        0,
        GeneratedStepRenderContext {
            current_rule_index: 0,
            embedded: None,
            portable_locals: None,
            decision_routing: DecisionRoutingRender::default(),
            inline_action_statements: &BTreeMap::new(),
            track_alt_numbers: false,
            track_context_alt_numbers: false,
            direct_generated_rule_calls: &[],
            atn_preferred_rule_calls: &[],
            adaptive_atn_preferred_rule_slots: &[],
            adaptive_atn_probe_rule_slots: &[],
        },
    );

    // One decision renders into a fresh String; snapshot the whole emitted control flow (the
    // semantic-context gate, both predicate probes, the alt rewrite, the no-viable fallback)
    // instead of six positive probes plus one negative guard.
    insta::assert_snapshot!(
```rust

---

Found a 17 line (102 tokens) duplication in the following files:
* Starting at line 767 of crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/parser.rs
* Starting at line 834 of crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/parser.rs

```rust
        dir.join("L.g4").as_os_str(),
        OsStr::new("--sem-patterns"),
        dir.join("patterns.toml").as_os_str(),
        OsStr::new("--sem-unknown"),
        OsStr::new("error"),
        OsStr::new("--require-full-semantics"),
        OsStr::new("--out-dir"),
        out.as_os_str(),
    ]);
    assert!(
        output.status.success(),
        "stdout: {}\nstderr: {}",
        utf8(&output.stdout),
        utf8(&output.stderr)
    );

    let lexer = fs::read_to_string(out.join("l.rs")).expect("lexer should be emitted");

Found a 16 line (101 tokens) duplication in the following files:

  • Starting at line 4140 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 4292 of crates/antlr-rust-codegen/src/generator/tests.rs
    atn.set_loop_back_state(3, 4).expect("loop back state");
    atn.add_transition(0, ParserTransitionSpec::Epsilon { target: 1 })
        .expect("transition");
    atn.add_transition(1, ParserTransitionSpec::Epsilon { target: 2 })
        .expect("transition");
    atn.add_transition(1, ParserTransitionSpec::Epsilon { target: 3 })
        .expect("transition");
    atn.add_transition(
        2,
        ParserTransitionSpec::Atom {
            target: 4,
            label: 1,
        },
    )
    .expect("transition");
    atn.add_transition(4, ParserTransitionSpec::Epsilon { target: 1 })
```rust

---

Found a 13 line (100 tokens) duplication in the following files:
* Starting at line 7218 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 7242 of crates/antlr-rust-runtime/src/parser.rs

```rust
        let mut expected = BTreeSet::new();
        for index in (1..self.rule_context_stack.len()).rev() {
            let invoking_state = self.rule_context_stack[index].invoking_state;
            let Ok(state_number) = usize::try_from(invoking_state) else {
                continue;
            };
            let Some(Transition::Rule { follow_state, .. }) = atn
                .state(state_number)
                .and_then(|state| state.transitions().first())
                .map(ParserTransition::data)
            else {
                continue;
            };

@claude

claude Bot commented Aug 4, 2026

Copy link
Copy Markdown

Claude Code is working…

I'll analyze this and get back to you.

View job run

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/antlr-rust-codegen/src/generator/tests.rs`:
- Around line 1697-1710: Update the ordering test around the rendered `@init` body
to locate the containing rule first, then search for its setup and body sections
within that rule’s rendered range rather than using parser-wide find calls. Use
the existing init_at position and the rule-section boundaries produced by
render_generated_rule_section, preserving the assertion that setup precedes
`@init` and `@init` precedes body.

In `@crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/parser.rs`:
- Around line 157-163: Update the generated-parser assertion in the test around
the emitted “nest_parser.rs” content to require the
`__antlr4_rust_generated_rule!` dispatch form, not merely the shared macro name.
Match the dispatch-specific syntax associated with `grow_generated_rule_stack`,
while preserving the existing 10,000-level behavioral parse guard.

In `@crates/antlr-rust-runtime/src/parser.rs`:
- Around line 238-241: Document the immediately-invoked closure at __result,
stating that it returns Result<(), AntlrError>, that return inside body { ... }
aborts only the body and enters recovery rather than returning from the
generated rule method, and that the closure holds one mutable borrow of $parser,
preventing disjoint field borrows across the body boundary.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 70dcde42-e832-43c4-b946-a036b6942511

📥 Commits

Reviewing files that changed from the base of the PR and between a50acb6 and 4312f58.

⛔ Files ignored due to path filters (11)
  • crates/antlr-rust-codegen/src/generator/snapshots/antlr_rust_codegen__generator__tests__compact_left_recursive_rule_lifecycle.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/src/generator/snapshots/antlr_rust_codegen__generator__tests__compact_ordinary_rule_lifecycle.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/src/generator/snapshots/antlr_rust_codegen__generator__tests__generated_module_file_header.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/snapshots/antlr4_rust_gen_cli__cli__generated_codegen_api_checks.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/snapshots/antlr4_rust_gen_cli__cli__generated_codegen_api_mismatch_diagnostic.snap is excluded by !**/*.snap
  • crates/antlr-rust-g4-parser/src/generated/antlr_v4_lexer.rs is excluded by !**/generated/**
  • crates/antlr-rust-g4-parser/src/generated/antlr_v4_parser.rs is excluded by !**/generated/**
  • crates/antlr-rust-rs-parser/src/generated/rust_lexer.rs is excluded by !**/generated/**
  • crates/antlr-rust-rs-parser/src/generated/rust_parser.rs is excluded by !**/generated/**
  • crates/antlr-rust-runtime/src/xpath/generated/x_path_lexer.rs is excluded by !**/generated/**
  • docs/migration.md is excluded by !**/docs/**
📒 Files selected for processing (9)
  • README.md
  • crates/antlr-rust-codegen/src/generator/tests.rs
  • crates/antlr-rust-codegen/src/parser/render/rules.rs
  • crates/antlr-rust-codegen/src/parser/routing.rs
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/cli.rs
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/parser.rs
  • crates/antlr-rust-runtime/src/lib.rs
  • crates/antlr-rust-runtime/src/parser.rs
  • third_party/antlr-v4-grammar/self-hosted.sha256

Comment thread crates/antlr-rust-codegen/src/generator/tests.rs Outdated
Comment thread crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/parser.rs
Comment thread crates/antlr-rust-runtime/src/parser.rs
@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

📊 Source Code Metrics (this PR vs main)

File Cyclomatic Cognitive Functions LLOC MI
crates/antlr-rust-codegen/src/generator/tests.rs 288 (main: 284) 🔴 48 ⚪ 202 (main: 199) 🔴 1345 (main: 1344) 🔴 0 ⚪
crates/antlr-rust-codegen/src/parser/routing.rs 37 (main: 40) 🟢 27 (main: 30) 🟢 12 (main: 13) 🟢 97 (main: 99) 🟢 7.79 (main: 6.58) 🟢
crates/antlr-rust-codegen/src/parser/render/rules.rs 45 (main: 41) 🔴 54 (main: 48) 🔴 5 (main: 4) 🔴 121 (main: 173) 🟢 7.08 (main: 4.54) 🟢
crates/antlr-rust-runtime/src/lib.rs 4 ⚪ 3 ⚪ 1 ⚪ 7 ⚪ 29.76 (main: 29.85) 🔴

Generated by mehen v1.8.0 — the code quality watcher.

Anchor the init-order check on its surrounding lifecycle sections, require the deep-nesting fixture to emit the dispatch arm that owns stack growth, and document the generated body Result boundary.
@tinovyatkin
tinovyatkin merged commit 2c4b985 into main Aug 4, 2026
24 of 25 checks passed
@tinovyatkin
tinovyatkin deleted the issue-277-centralize-rule-lifecycle branch August 4, 2026 23:55
@ophiarch ophiarch Bot mentioned this pull request Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

codegen: centralize generated parser rule lifecycle and recovery scaffolding

1 participant