Skip to content

feat(codegen): centralize parse-driver and entry-point scaffolding in the runtime - #332

Merged
tinovyatkin merged 3 commits into
mainfrom
codegen-runtime-parse-driver
Aug 10, 2026
Merged

feat(codegen): centralize parse-driver and entry-point scaffolding in the runtime#332
tinovyatkin merged 3 commits into
mainfrom
codegen-runtime-parse-driver

Conversation

@tinovyatkin

@tinovyatkin tinovyatkin commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Closes #320.

Summary

Generated parsers no longer re-declare the 113-line parse-driver core and the 200–550-line entry-point block; each module now emits two thin runtime-macro invocations plus its grammar-specific knobs. The generated-code API revision is incremented to 11.

  • Driver coreparse_rule, parse_rule_precedence, parse_rule_precedence_from_generated, parse_rule_precedence_inner, parse_interpreted_rule, parse_interpreted_rule_precedence expand from the new runtime __antlr4_rust_parser_driver! macro. Generated code supplies only a fallback(parser, rule_index, precedence) binder block composing its ParserRuntimeOptions, and an adaptive_direct flag.
  • Embedded-actions axis is uniform — the driver always routes deferred actions through the module's run_action (an empty method for grammars without action states), replacing the divergent for action in actions { … } vs let _ = actions; template line.
  • Entry pointsparse / parse_validated / parse_with_parser / parse_stream / parse_stream_validated / parse_stream_with_parser, the <Grammar>ParserParseOutput alias of the new runtime GeneratedParseOutput<R, P>, and the validate() bridge (via the doc-hidden __GeneratedParserValidate trait) expand from __antlr4_rust_parser_entry_points!. Entry-rule doc lists remain generated on the parser type rustdoc.
  • Adaptive-retry axis is a const knobGeneratedRuleError moves to the runtime with AdaptiveRetry always present; the five-field state bundle becomes AdaptiveAtnRetryState<const RULES: usize> (RULES = 0 without residual adaptive routing; retry_pending() constant-folds to false), and rule bodies carry a uniform retry [adaptive]; clause handled by a new __antlr4_rust_generated_rule! arm.
  • Lexer entry pointslex / lex_stream are runtime generic functions re-exported by generated lexer modules.
  • The driver's entry-ordering invariants (fail-loud surfacing before Ok, diagnostics → abort → semantic-miss draining, shared allow_generated_fallback gate) previously pinned per grammar are now pinned once by a runtime test over the macro source; generator tests assert the module wiring.
  • Compatibility: arms 1–10 are retained (old generated source is self-contained; nothing it needs was removed). Compatibility test, snapshots, README, and docs/migration.md are updated; all checked-in recognizers are regenerated at revision 11.

Generated-source size (lines / bytes)

Module Before After
toml_parser.rs 2,693 / 151,933 2,464 / 141,165 (−7.1% bytes)
rust_parser.rs 30,526 / 2,278,259 30,297 / 2,268,215
antlr_v4_parser.rs 7,128 / 451,066 6,899 / 440,410
toml_lexer.rs 202 / 227,897 158 / 225,906
rust_lexer.rs 284 / 360,962 240 / 358,971
antlr_v4_lexer.rs 337 / 2,751,084 293 / 2,749,090
x_path_lexer.rs 173 / 11,215 129 / 9,223

Compile-time and binary-size deltas were not measured; the scaffolding was already generic over L/H, so the wins are source size and single ownership of driver semantics.

Tested

Check Result
cargo test -p antlr-rust-codegen --lib 896 passed
cargo test -p antlr-rust-codegen --tests (CLI integration, incl. revision handshake, frozen revision-9 fixture, adaptive-routing fixture compiling AdaptiveAtnRetryState<N> at N>0 end to end) 95 passed
Runtime lib tests (incl. new parser_driver_entry_ordering_invariants) 384 passed
toml / rs / g4 parser crate tests 13 / 48 / 12 passed
ANTLR runtime testsuite sweep 357/357 passed
Kotlin parity (tests/kotlin-parity/run.sh) all parse trees match
cargo clippy --locked --workspace --all-targets --all-features -- -D warnings clean
cargo fmt --check, doc-tests, pre-commit hk suite clean

Reviewer notes

  • The renders_parse_convenience_without_replacing_manual_constructor, generated_* and adaptive generator tests now assert macro invocations / new field paths; the ordering invariants moved to the runtime test named above.
  • generated_parser_api (CLI test support) synthesizes the entry-points surface from the invocation the same way it already did for the facade macro, so the API-inventory snapshots keep listing parse*/validate and record the ParseOutput alias as type instead of struct.
  • The xpath lexer regeneration deliberately does not add a decisions.json (it was never checked in for that module).

Summary by CodeRabbit

  • New Features
    • Added standardized parsing APIs for regular, streaming, validated, and parser-returning workflows.
    • Added standalone lexer helpers for tokenizing inputs and streams.
    • Improved adaptive retry, fallback handling, diagnostics, validation, and error reporting.
  • Compatibility
    • Updated generated-code compatibility to API revision 11, supporting revisions 1–10.
  • Bug Fixes
    • Improved parser error precedence and adaptive fallback behavior.
    • Updated generated-source checksums and compatibility documentation.

… the runtime

Every generated parser repeated a 113-line parse-driver core and a
200-550-line entry-point block that were byte-identical across grammars
modulo the type-name prefix and exactly one divergent line per feature
axis. Generated modules now emit two thin macro invocations instead, so
the driver semantics have a single runtime-owned copy and the two
hand-maintained template variants disappear.

Runtime (generated-code API revision 10 -> 11):

- `__antlr4_rust_parser_driver!` expands the six driver methods
  (`parse_rule`, `parse_rule_precedence`,
  `parse_rule_precedence_from_generated`, `parse_rule_precedence_inner`,
  `parse_interpreted_rule`, `parse_interpreted_rule_precedence`).
  Generated code supplies only the interpreted-fallback binder block
  that composes its grammar-specific `ParserRuntimeOptions`, plus an
  `adaptive_direct` flag. Action dispatch is uniform: the driver always
  routes deferred actions through the module's `run_action` (an empty
  method for grammars without action states), replacing the divergent
  `for action in actions { ... }` vs `let _ = actions;` template line.
- `__antlr4_rust_parser_entry_points!` expands the `parse` /
  `parse_validated` / `parse_with_parser` / `parse_stream` /
  `parse_stream_validated` / `parse_stream_with_parser` functions, the
  `<Grammar>ParserParseOutput` alias of the new runtime
  `GeneratedParseOutput<R, P>`, and the validation bridge behind the
  doc-hidden `__GeneratedParserValidate` trait, keeping `validate()`
  and the field/accessor surface source compatible.
- `GeneratedRuleError` moves to the runtime as a grammar-agnostic enum
  whose `AdaptiveRetry` variant always exists; the adaptive-ATN retry
  state bundle becomes `AdaptiveAtnRetryState<const RULES: usize>`
  enabled by a const knob (`RULES = 0` when a grammar has no residual
  adaptive routing; `retry_pending()` then constant-folds to `false`),
  and rule bodies carry a uniform `retry [adaptive];` clause handled by
  a new arm of `__antlr4_rust_generated_rule!`.
- Lexer `lex` / `lex_stream` become runtime generic functions that
  generated lexer modules re-export.

The driver's entry-ordering invariants (fail-loud surfacing before Ok,
diagnostics before abort before semantic-miss draining, shared
`allow_generated_fallback` gate) were previously pinned per grammar by
generator rendered-text tests; they are now pinned once by a runtime
test over the macro source, and the generator tests assert the module
wiring instead. The accepted-revision arms retain 1-10 (older generated
source is self-contained and every surface it needs still exists); the
frozen revision-9 fixture continues to compile.

All checked-in recognizers are regenerated at revision 11. Generated
source shrinks by ~230 lines per parser and ~44 per lexer (toml parser
151,933 -> 141,165 bytes; rust parser 2,278,259 -> 2,268,215; g4 parser
451,066 -> 440,410), and grammars that do exercise the adaptive-retry
bundle now compile against the shared contract (covered by the
adaptive-routing CLI fixture end to end).

Closes #320
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown

Copy/Paste Detection

Found 35 duplication(s) across 15 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 15324 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 16729 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 4285 of crates/antlr-rust-codegen/src/generator/tests.rs
* Starting at line 17070 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 16658 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 17625 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 4149 of crates/antlr-rust-codegen/src/generator/tests.rs
* Starting at line 16778 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 18146 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 18280 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 4187 of crates/antlr-rust-codegen/src/generator/tests.rs
* Starting at line 4410 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 4098 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 17742 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 16324 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 16349 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 4150 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 16851 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 16779 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 16851 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 26 line (125 tokens) duplication in the following files:

  • Starting at line 791 of crates/antlr-rust-runtime/src/generated.rs
  • Starting at line 962 of crates/antlr-rust-runtime/src/generated.rs
            $input: $crate::char_stream::CharStream,
            $hooks: $crate::parser::SemanticHooks,
        {
            pub fn metadata() -> &'static $crate::generated::GrammarMetadata {
                $metadata()
            }

            /// Adds a listener for lexer diagnostics.
            pub fn add_error_listener<T>(&mut self, listener: T)
            where
                T: for<'a> $crate::errors::ErrorListener<dyn $crate::recognizer::Recognizer + 'a>
                    + ::core::marker::Send
                    + 'static,
            {
                $crate::recognizer::Recognizer::add_error_listener(&mut self.$base, listener);
            }

            /// Removes every lexer error listener, including the default console listener.
            pub fn remove_error_listeners(&mut self) {
                $crate::recognizer::Recognizer::remove_error_listeners(&mut self.$base);
            }

            /// Routes every token through ATN interpretation instead of the compiled
            /// lexer DFA, so the learned-DFA trace (`lexer_dfa_string`) observes each
            /// match.
            pub fn set_force_interpreted(&mut self, force_interpreted: bool) {
```rust

---

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

```rust
            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,

Found a 34 line (119 tokens) duplication in the following files:

  • Starting at line 10909 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 10984 of crates/antlr-rust-runtime/src/parser.rs
                        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);
```rust

---

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

```rust
        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);

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

  • Starting at line 4158 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 4368 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 16787 of crates/antlr-rust-runtime/src/parser.rs
        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))
```rust

---

Found a 15 line (113 tokens) duplication in the following files:
* Starting at line 18205 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 18483 of crates/antlr-rust-runtime/src/parser.rs

```rust
    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"),

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

  • Starting at line 4285 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 4359 of crates/antlr-rust-codegen/src/generator/tests.rs
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))
```rust

---

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

```rust
            (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");

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

  • Starting at line 15377 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 15460 of crates/antlr-rust-runtime/src/parser.rs
        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),
```rust

---

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

```rust
    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))

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

  • Starting at line 15199 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 17325 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 17742 of crates/antlr-rust-runtime/src/parser.rs
    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))
```rust

---

Found a 27 line (110 tokens) duplication in the following files:
* Starting at line 3512 of crates/antlr-rust-codegen/src/generator/tests.rs
* Starting at line 3624 of crates/antlr-rust-codegen/src/generator/tests.rs

```rust
            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!(

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

  • Starting at line 17875 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 21820 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);
        let matched = parser.match_token(1).expect("token 1 should match");
```rust

---

Found a 13 line (109 tokens) duplication in the following files:
* Starting at line 17875 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 21845 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);

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

  • Starting at line 4098 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 15199 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 17325 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))
```rust

---

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

```rust
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))

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

  • Starting at line 8699 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 9090 of crates/antlr-rust-runtime/src/parser.rs
    ) -> 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;
```rust

---

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

```rust
    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)],

Found a 17 line (107 tokens) duplication in the following files:

  • Starting at line 7758 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 8453 of crates/antlr-rust-runtime/src/parser.rs
        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);
```rust

---

Found a 17 line (105 tokens) duplication in the following files:
* Starting at line 180 of crates/antlr-rust-runtime/src/generated.rs
* Starting at line 277 of crates/antlr-rust-runtime/src/generated.rs

```rust
            fn __from_node_with_invocation_states(
                node: $crate::RuleNodeView<'a>,
                invocation_states: Option<Vec<isize>>,
            ) -> Self {
                $(
                    let __default = <$attrs>::default();
                    let __attrs = node.generated_attrs::<$attrs>().unwrap_or(&__default);
                )?
                Self {
                    __node: __GeneratedRuleContext::Stored(node),
                    __invocation_states: invocation_states,
                    __state: std::marker::PhantomData,
                    $(
                        $($field: __attrs.$field.clone(),)+
                    )?
                }
            }

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

  • Starting at line 3252 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 3419 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 15312 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 16716 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 3305 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 3465 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 16 line (101 tokens) duplication in the following files:
* Starting at line 4258 of crates/antlr-rust-codegen/src/generator/tests.rs
* Starting at line 4410 of crates/antlr-rust-codegen/src/generator/tests.rs

```rust
    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 })

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

  • Starting at line 7409 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 7433 of crates/antlr-rust-runtime/src/parser.rs
        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;
            };
```rust

@claude

claude Bot commented Aug 10, 2026

Copy link
Copy Markdown

Reviewing PR #332

  • Read the full PR diff against origin/main
  • Review the new runtime macros (__antlr4_rust_parser_driver!, __antlr4_rust_parser_entry_points!, lex/lex_stream)
  • Review the codegen-side changes (render model, routing, fallback, rules)
  • Check the adaptive-retry const-knob refactor (AdaptiveAtnRetryState<RULES>, GeneratedRuleError)
  • Compare regenerated recognizers for behavioral drift
  • Check API-revision handshake / compatibility surface
  • Verify findings by mutation/build where possible
  • Post consolidated review

View job run · branch codegen-runtime-parse-driver

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f1c95730-a28a-4d6b-8851-297ae197c6cb

📥 Commits

Reviewing files that changed from the base of the PR and between f84fafb and 3313634.

📒 Files selected for processing (1)
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/optimizations.rs

📝 Walkthrough

Walkthrough

The runtime now owns shared parser-driver, parser-entry-point, adaptive-retry, and lexer helper infrastructure. Code generation emits wiring and grammar-specific configuration. Compatibility advances to generated-code revision 11.

Changes

Runtime and parser code-generation centralization

Layer / File(s) Summary
Runtime driver and shared contracts
crates/antlr-rust-runtime/src/generated.rs, crates/antlr-rust-runtime/src/parser.rs
Runtime macros now handle parser routing, fallback, diagnostics, actions, entry points, validation, streams, and adaptive retries. Shared parse output and lexer helpers are public.
Generated parser wiring and adaptive state
crates/antlr-rust-codegen/src/parser/..., crates/antlr-rust-codegen/src/generator/tests.rs, crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/{optimizations,support,typed_tree}.rs
Generated parsers use runtime driver and entry-point macros. Adaptive routing uses consolidated adaptive_atn state. Fallback and action dispatch use runtime contracts. Tests verify the generated wiring and options.
Lexer helpers and compatibility revision
crates/antlr-rust-codegen/src/lexer/..., crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/cli.rs, crates/antlr-rust-runtime/src/lib.rs, README.md, third_party/antlr-v4-grammar/self-hosted.sha256
Generated lexers re-export generic runtime helpers. The compatibility revision changes from 10 to 11, with updated diagnostics, tests, documentation, and generated-source checksums.

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

Sequence Diagram(s)

sequenceDiagram
  participant GeneratedParser
  participant RuntimeDriver
  participant GeneratedFallback
  participant Diagnostics
  GeneratedParser->>RuntimeDriver: invoke parser driver macro
  RuntimeDriver->>GeneratedFallback: parse generated or interpreted rule
  GeneratedFallback-->>RuntimeDriver: parse result or GeneratedRuleError
  RuntimeDriver->>Diagnostics: drain and order diagnostics
  Diagnostics-->>GeneratedParser: return parse output or error
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 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 main change: centralizing parser driver and entry-point scaffolding in the runtime.
Linked Issues check ✅ Passed The changes satisfy issue #320 by centralizing runtime scaffolding, preserving APIs and behavior, and updating compatibility tests and documentation.
Out of Scope Changes check ✅ Passed The changes remain within issue #320 scope, including runtime centralization, code generation updates, tests, documentation, and regenerated checksums.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% 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 codegen-runtime-parse-driver

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.

@codspeed-hq

codspeed-hq Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing codegen-runtime-parse-driver (3313634) with main (0d118b3)

Open in CodSpeed

@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.05556% with 133 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/antlr-rust-runtime/src/generated.rs 58.69% 133 Missing ⚠️

📢 Thoughts on this report? Let us know!

@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: 2

🤖 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/tests/antlr4_rust_gen_cli/support.rs`:
- Around line 278-284: Restrict the `output` extraction in the API-surface
parsing logic to lines within the `__antlr4_rust_parser_entry_points!`
invocation block before calling `find_map`. Preserve the existing trimming and
`type {output}` insertion behavior, while ignoring matching text elsewhere in
the module.

In `@crates/antlr-rust-runtime/src/generated.rs`:
- Around line 2355-2431: Update parser_driver_entry_ordering_invariants to
define named constants for each searched macro-body token sequence, including
macro markers and drained-state/action calls, then reuse those constants in
find, contains, and related assertions. Keep the existing ordering checks
unchanged while making expected token strings centralized and easier to update.
🪄 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: 5b70c0dc-5375-4a7b-9427-a18dc7d84449

📥 Commits

Reviewing files that changed from the base of the PR and between 0d118b3 and 8399e8f.

⛔ Files ignored due to path filters (25)
  • 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/src/generator/snapshots/antlr_rust_codegen__generator__tests__generated_parser_optional_state_facade.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/src/generator/snapshots/antlr_rust_codegen__generator__tests__generated_plain_recognizer_facades.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/src/generator/snapshots/antlr_rust_codegen__generator__tests__generated_recognizers_reuse_cached_static_metadata.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/src/generator/snapshots/antlr_rust_codegen__generator__tests__lexer_lex_convenience.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/src/generator/snapshots/antlr_rust_codegen__generator__tests__parser_parse_convenience.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-codegen/tests/antlr4_rust_gen_cli/snapshots/antlr4_rust_gen_cli__parser__inlined_token_accessors_generated_api.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/snapshots/antlr4_rust_gen_cli__transforms__configured_mutual_entry_generated_api.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/snapshots/antlr4_rust_gen_cli__transforms__configured_precedence_entry_generated_api.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/snapshots/antlr4_rust_gen_cli__transforms__inferred_mutual_entries_generated_api.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/snapshots/antlr4_rust_gen_cli__transforms__pruned_unreachable_rule_generated_api.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/snapshots/antlr4_rust_gen_cli__transforms__recursive_eof_entry_component_generated_api.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/snapshots/antlr4_rust_gen_cli__transforms__unreachable_rule_default_generated_api.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/**
  • crates/antlr-rust-toml-parser/src/generated/toml_lexer.rs is excluded by !**/generated/**
  • crates/antlr-rust-toml-parser/src/generated/toml_parser.rs is excluded by !**/generated/**
  • docs/migration.md is excluded by !**/docs/**
📒 Files selected for processing (17)
  • README.md
  • crates/antlr-rust-codegen/src/generator/tests.rs
  • crates/antlr-rust-codegen/src/lexer/render.rs
  • crates/antlr-rust-codegen/src/lexer/render_model.rs
  • crates/antlr-rust-codegen/src/parser/render/fallback.rs
  • crates/antlr-rust-codegen/src/parser/render/mod.rs
  • crates/antlr-rust-codegen/src/parser/render/rules.rs
  • crates/antlr-rust-codegen/src/parser/render_model.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/optimizations.rs
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/support.rs
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/typed_tree.rs
  • crates/antlr-rust-runtime/src/generated.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/tests/antlr4_rust_gen_cli/support.rs Outdated
Comment thread crates/antlr-rust-runtime/src/generated.rs
@github-actions

github-actions Bot commented Aug 10, 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 292 (main: 294) 🟢 48 ⚪ 207 ⚪ 1330 (main: 1353) 🟢 0 ⚪
crates/antlr-rust-runtime/src/generated.rs 96 (main: 75) 🔴 10 (main: 8) 🔴 50 (main: 40) 🔴 92 (main: 61) 🔴 0 ⚪
crates/antlr-rust-codegen/src/parser/routing.rs 34 (main: 37) 🟢 26 (main: 27) 🟢 10 (main: 12) 🟢 93 (main: 97) 🟢 9.57 (main: 7.79) 🟢
crates/antlr-rust-codegen/src/lexer/render_model.rs 20 ⚪ 1 ⚪ 9 ⚪ 22 ⚪ 25.50 (main: 22.98) 🟢
crates/antlr-rust-codegen/src/parser/render/rules.rs 45 (main: 46) 🟢 52 (main: 54) 🟢 5 ⚪ 114 (main: 116) 🟢 7.82 (main: 7.45) 🟢
crates/antlr-rust-codegen/src/parser/render/mod.rs 43 ⚪ 41 ⚪ 3 ⚪ 90 (main: 92) 🟢 9.38 (main: 6.99) 🟢
crates/antlr-rust-codegen/src/parser/render/fallback.rs 7 (main: 9) 🟢 6 (main: 8) 🟢 1 ⚪ 9 (main: 14) 🟢 40.80 (main: 37.51) 🟢
crates/antlr-rust-runtime/src/lib.rs 4 ⚪ 3 ⚪ 1 ⚪ 7 ⚪ 28.88 (main: 28.97) 🔴

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

…acing behavior

Address PR #332 review findings.

The runtime `parser_driver_entry_ordering_invariants` test anchored its
first and third assertions on `self.$base.take_unknown_semantic_error()`,
whose first occurrence is the `let _ =` drain inside the Err-arm abort
branch — not the top-level surfacing check — so deleting the surfacing
block left the test (and the whole generator suite) passing. The test now
whitespace-flattens the macro body and anchors on the binder forms that
are unique to each site (`Some(error)` for the surfacing check,
`Some(semantic_error)` and `Some(abort)` for the Err-arm drains), asserts
the surfacing anchor is unique, and keeps every search string in a named
constant so macro renames are one-line updates. It also re-pins the
boundary-diagnostics dispatch the generator test dropped: the post-tree
`report_generated_parser_diagnostics` between the interpreted fallback
and the surfacing check, and `report_unrecovered_parser_error` between
the Err-arm conversion and its return. Verified by mutation: removing the
surfacing block from `__antlr4_rust_parser_driver!` now fails the test.

A new behavioral CLI case,
`public_entry_surfaces_recorded_semantic_miss_after_clean_parse`, pins
the same invariant end to end where source-text matching cannot: a
generated parser under `--sem-unknown hook` whose untranslated action
records a fail-loud miss during a structurally clean parse must return
`AntlrError::Unsupported` from the public entry (zero syntax errors), and
a reused parser stays clean. The same mutation fails this test with the
miss escaping as a recovered Ok tree.

Also from review: the `generated_parser_api` scrape reads the ParseOutput
alias name only from inside the `__antlr4_rust_parser_entry_points!`
invocation block, so an `output: ` line elsewhere in a module cannot add
a bogus API-surface entry; the rule macro's `retry [$cond => $err]` arm
is documented as back-compat-only for generated-code API revisions <= 10;
`GeneratedParseOutput` documents when `validate()` is available; the
shared `lex` rustdoc attributes `metadata()` to the generated module; and
the README describes revision 10 in the past tense now that 11 is
current.
@tinovyatkin

Copy link
Copy Markdown
Contributor Author

@claude Thanks for the mutation-verified review — Finding 1 was a real gap. All four findings and both nits are addressed in f84fafb:

Finding 1 (fixed, mutation-verified both ways). parser_driver_entry_ordering_invariants now whitespace-flattens the macro body (driver_macro_body()) and anchors on the binder forms unique to each site: Some(error) = self.$base.take_unknown_semantic_error() for the top-level surfacing check (the Err-arm check binds semantic_error, the drains bind _), plus an assert_eq!(matches(...).count(), 1) uniqueness guard so a future second Some(error) binder can't silently re-introduce the ambiguity. The post-fallback assertion reuses the same unique anchor. I repeated your mutation (deleting the whole top-level surfacing block): the test now fails at the surfacing expect.

I also took the "stronger still" suggestion: a new behavioral CLI case public_entry_surfaces_recorded_semantic_miss_after_clean_parse (optimizations.rs) generates a grammar under --sem-unknown hook whose untranslated action records a fail-loud miss during a structurally clean parse, and asserts the public entry returns AntlrError::Unsupported("unhandled semantic action: …") with number_of_syntax_errors() == 0, plus clean parser reuse afterwards. The same mutation fails this test with the miss escaping as a recovered Ok tree (NodeId(2)), so the post-tree path is now pinned behaviorally, not just textually.

Finding 2 (fixed). The runtime test now asserts report_generated_parser_diagnostics(); appears between the interpreted fallback and the surfacing check (the post-tree dispatch), and report_unrecovered_parser_error(&error); appears between the Err-arm conversion and its return.

Finding 3 (fixed). The retry [$cond => $err] arm carries a comment marking it back-compat-only for generated-code API revisions ≤ 10, tied to the accepted-revision arms of __antlr4_rust_require_codegen_api! so a future cleanup sees the dependency.

Finding 4 + nits (fixed). The shared lex rustdoc now attributes metadata() to the generated module; GeneratedParseOutput documents that validate() is available for generated parser types (which wire in their module's validated surface); and the README describes revision 10 in the past tense. The generic MyGrammarLexer::new example in the re-exported lexer docs stays as the accepted trade-off you noted.

CodeRabbit's two inline comments (bounding the output: scan to the invocation block; named constants) are fixed in the same commit.

@claude

claude Bot commented Aug 10, 2026

Copy link
Copy Markdown

Claude finished @tinovyatkin's task in 1m 58s —— View job


Verifying fixes in f84fafb

  • Check commit f84fafb diff for the driver-ordering invariant test changes
  • Verify Finding 1 fix (unique anchors + uniqueness guard) in generated.rs
  • Verify Finding 2 fix (report_generated_parser_diagnostics / report_unrecovered_parser_error ordering assertions)
  • Verify Finding 3 fix (back-compat comment on retry [$cond => $err] arm)
  • Verify Finding 4 + nits (rustdoc updates, README revision-10 phrasing)
  • Verify the support.rs output: scoping fix and new CLI behavioral test
  • Post final verification summary

Verified against source

I re-derived each claim directly against crates/antlr-rust-runtime/src/generated.rs and crates/antlr-rust-runtime/src/parser.rs at f84fafb rather than trusting the commit message alone:

  • Finding 1. Confirmed the actual macro body (__antlr4_rust_parser_driver!, lines 1242–1421) has exactly one occurrence of the literal Some(error) = self.$base.take_unknown_semantic_error() — the top-level surfacing check at line 1371-1372. The other two take_unknown_semantic_error() calls inside the Err-arm abort branch bind _ (line 1332, a drain) and semantic_error (line 1339-1340), and the Err-arm's own take_parse_abort() binds error too (line 1365) but to a different function — so the new TOP_LEVEL_SEMANTIC_SURFACE constant can't collide with either. The assert_eq!(... .count(), 1, ...) uniqueness guard is a good belt-and-suspenders addition. This genuinely closes the gap the original mutation exposed (deleting the surfacing block previously left the old .find()-based test passing because it fell back to matching the drain).
  • Finding 2. Confirmed in source: report_generated_parser_diagnostics() sits between the interpreted-fallback call (line 1357) and the surfacing check (line 1371); report_unrecovered_parser_error(&error) sits between the error.into_error() conversion (line 1345) and the return Err(error) (line 1349). Both match the new assertions exactly.
  • Finding 3. The back-compat comment on the retry [$cond => $err] arm in parser.rs is present and correctly ties itself to __antlr4_rust_require_codegen_api!'s accepted-revision arms.
  • Finding 4 + nits. GeneratedParseOutput's doc now notes validate() availability for generated parser types; the lex rustdoc attributes metadata() to the generated module; README now reads "Revision 10 stopped re-declaring..." (past tense). All confirmed in the diff.
  • support.rs scoping fix. generated_parser_api now does source.split_once("...__antlr4_rust_parser_entry_points! { "), then further truncates at the invocation's closing \n} before scanning for output: , exactly matching CodeRabbit's suggested fix (and CodeRabbit itself re-confirmed this inline).
  • New CLI test. public_entry_surfaces_recorded_semantic_miss_after_clean_parse in optimizations.rs generates a real grammar under --sem-unknown hook, parses a structurally clean "ab" input, and asserts the entry returns AntlrError::Unsupported(.. "unhandled semantic action" ..) with number_of_syntax_errors() == 0, then reuses the parser via set_token_stream and asserts a clean entry (clean()) still succeeds. This is a legitimate end-to-end pin of the same invariant the source-text test only checks statically, and it uses helpers (temporary_directory, run_antlr4_rust_gen, assert_generated_project, utf8) already wildcard-imported from support in this file, so it compiles cleanly with the existing test harness.

Note: I wasn't able to execute cargo test/cargo clippy in this sandbox (build commands require approval I don't have here), so I verified everything by direct source inspection (reading the exact macro body byte-for-byte, checking uniqueness of anchors, and tracing statement order) rather than by running the suite. Given the PR description already reports the full local test/clippy/fmt matrix as clean and this commit is a pure test/doc change with no production-code behavior modified, I'm confident in the verification, but flagging the inspection method for transparency.

All four findings and the two CodeRabbit nits are accurately and correctly addressed in f84fafb — nothing further to fix from my side.

@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: 1

🤖 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/tests/antlr4_rust_gen_cli/optimizations.rs`:
- Around line 535-541: Update the test around the failed SemanticMissLexer item
entry so clean accepts EOF, then invoke clean() immediately after the failed
item() call without resetting the parser via set_token_stream. Remove the
intervening token-stream replacement and retain the assertion that the clean
entry succeeds, ensuring parser state is validated in place.
🪄 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: f5509106-f434-48e2-a715-64d177ee801c

📥 Commits

Reviewing files that changed from the base of the PR and between 8399e8f and f84fafb.

📒 Files selected for processing (5)
  • README.md
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/optimizations.rs
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/support.rs
  • crates/antlr-rust-runtime/src/generated.rs
  • crates/antlr-rust-runtime/src/parser.rs

Comment thread crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/optimizations.rs Outdated
…case

`set_token_stream` fully resets parser-owned state, so the reuse check in
`public_entry_surfaces_recorded_semantic_miss_after_clean_parse` passed
even if the failed entry had left the recorded semantic error behind.
`clean` now matches `EOF` and runs directly after the failed `item()`
entry on the same token stream (the cursor sits at EOF after the clean
parse), so the assertion observes the drain itself rather than the reset.
@tinovyatkin
tinovyatkin merged commit 65cdc82 into main Aug 10, 2026
18 of 19 checks passed
@tinovyatkin
tinovyatkin deleted the codegen-runtime-parse-driver branch August 10, 2026 19:32
@ophiarch ophiarch Bot mentioned this pull request Aug 10, 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 the parse-driver and entry-point scaffolding behind the runtime

1 participant