Skip to content

refactor(codegen): share generated tree walker engine - #341

Merged
tinovyatkin merged 1 commit into
mainfrom
issue-324-runtime-walk-engine
Aug 12, 2026
Merged

refactor(codegen): share generated tree walker engine#341
tinovyatkin merged 1 commit into
mainfrom
issue-324-runtime-walk-engine

Conversation

@tinovyatkin

@tinovyatkin tinovyatkin commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

  • move the iterative generated listener tree-walk engine into antlr-rust-runtime
  • keep generated plain and validated walkers API-compatible while emitting only grammar-specific callback dispatch
  • bump the generated-code API to revision 13, retain revision-12 runtime compatibility, and regenerate all checked-in recognizers
  • add direct runtime ordering/error tests and a CodSpeed listener-walk benchmark

Net effect

This is a modest consolidation, not a large feature or a large total-LOC
reduction.

  • The overall repository diff is +1,843 / -1,626 lines (+217 net).
    That includes the new shared runtime implementation, compatibility support,
    tests, benchmark, documentation, and generated-file churn.
  • The three checked-in generated parser artifacts themselves are 102 lines
    and 31,617 bytes smaller
    .
  • A release antlr4-rust-gen binary linking all three parsers is 121,680
    bytes smaller
    , including 114,688 fewer __TEXT bytes.
  • Listener-walk performance is unchanged at the measured median.

The main long-term benefit is that traversal behavior now has one
grammar-agnostic runtime implementation instead of being maintained in every
generated plain and validated walker. Future generated parsers pay only for
their grammar-specific dispatch.

Measurements

Measurement Before After Change
Generated parser lines 37,875 37,773 -102 (-0.269%)
Generated parser bytes 2,720,367 2,688,750 -31,617 (-1.162%)
antlr4-rust-gen file bytes 37,720,592 37,598,912 -121,680 (-0.323%)
antlr4-rust-gen __TEXT 29,868,032 29,753,344 -114,688 (-0.384%)
Listener-walk median 2.749 us 2.749 us no regression
Listener-walk mean 2.800 us 2.785 us -0.536%

A trait-object callback prototype measured 3.208 us versus a 2.749 us
baseline (+16.7%), so the final runtime engine is generic over the callback
adapter and keeps the dispatch methods inlined. The benchmark black-boxes both
the tree input and walk result to prevent dead-code elimination.

Validation

  • cargo fmt -- --check
  • cargo test --locked --workspace --all-features
  • cargo clippy --locked --workspace --all-targets --all-features -- -D warnings
  • full ANTLR runtime testsuite: 357 passed, 0 failed, 0 skipped
  • all seven upstream Listeners/* descriptors
  • TOML and Rust generated-recognizer checks
  • ANTLR grammar frontend Stage 1/Stage 2 byte-identical fixed-point check
  • generated-code API compatibility test and snapshots

Closes #324.

@github-actions

Copy link
Copy Markdown

Copy/Paste Detection

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

Show duplications

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

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

---

Found a 26 line (125 tokens) duplication in the following files:
* Starting at line 794 of crates/antlr-rust-runtime/src/generated.rs
* Starting at line 965 of crates/antlr-rust-runtime/src/generated.rs

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

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

  • Starting at line 4159 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 4369 of crates/antlr-rust-codegen/src/generator/tests.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 22 line (112 tokens) duplication in the following files:
* Starting at line 4286 of crates/antlr-rust-codegen/src/generator/tests.rs
* Starting at line 4360 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 27 line (110 tokens) duplication in the following files:

  • Starting at line 3513 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 3625 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 17 line (105 tokens) duplication in the following files:
* Starting at line 183 of crates/antlr-rust-runtime/src/generated.rs
* Starting at line 280 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 3253 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 3420 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 28 line (102 tokens) duplication in the following files:
* Starting at line 3306 of crates/antlr-rust-codegen/src/generator/tests.rs
* Starting at line 3466 of crates/antlr-rust-codegen/src/generator/tests.rs

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

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

  • Starting at line 4259 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 4411 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

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The runtime now provides walk_generated for iterative parse-tree traversal. Generated plain and validated walkers delegate to this engine through callback adapters. The generated-code compatibility contract advances from revision 12 to revision 13, with updated tests, documentation, and TOML traversal benchmarks.

Changes

Generated tree walking consolidation

Layer / File(s) Summary
Runtime traversal engine
crates/antlr-rust-runtime/src/generated.rs
Adds GeneratedWalkCallbacks, the callback adapter macro, and walk_generated. Tests cover traversal order, invocation states, error nodes, and callback short-circuiting.
Generated walker delegation
crates/antlr-rust-codegen/src/parser/surface/traversal.rs, crates/antlr-rust-codegen/src/parser/surface/contexts.rs, crates/antlr-rust-codegen/src/generator/tests.rs
Plain and validated walkers delegate traversal to the runtime while retaining grammar-specific dispatch and validated error-node behavior.
Codegen API revision 13
crates/antlr-rust-runtime/src/lib.rs, crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/cli.rs, README.md
Updates the runtime compatibility revision, diagnostic expectations, and generated-code compatibility documentation.
Benchmark and generated artifact validation
benches/Cargo.toml, benches/benches/grammar_frontend.rs, third_party/antlr-v4-grammar/self-hosted.sha256
Adds TOML parsing and generated listener traversal benchmarks and updates self-hosted generated-file checksums.

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

Sequence Diagram(s)

sequenceDiagram
  participant GeneratedTreeWalker
  participant RuntimeWalkGenerated
  participant ListenerCallbacks
  GeneratedTreeWalker->>RuntimeWalkGenerated: pass parse tree and invocation states
  RuntimeWalkGenerated->>ListenerCallbacks: emit enter, terminal, error, and exit callbacks
  ListenerCallbacks-->>RuntimeWalkGenerated: return callback result
  RuntimeWalkGenerated-->>GeneratedTreeWalker: return traversal result
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The shared runtime walker, generated dispatch, behavior tests, benchmark, and API revision are shown, but excluded generated files and snapshots prevent full verification. Include or otherwise verify the excluded generated recognizers and compatibility snapshots, including the files filtered by generated and snapshot rules.
✅ Passed checks (3 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed The benchmark, documentation, checksum, tests, code generation, and runtime changes all support the linked issue objectives.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: moving the generated tree-walking engine into shared runtime code.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-324-runtime-walk-engine

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.

@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown

Code review — PR #341

  • Gather PR context and diff
  • Collect relevant CLAUDE.md / AGENTS.md rules
  • Summarize changes
  • Parallel review: CLAUDE.md compliance ×2, bug hunt ×2 (1/4 back)
  • Validate candidate findings
  • Post consolidated review

Verified so far: revision bump is complete across all 7 checked-in recognizers; new walk snapshots match hand-traced expected output; walk semantics match the old inline walker and upstream ParseTreeWalker.java.

View job run · branch issue-324-runtime-walk-engine

@codspeed-hq

codspeed-hq Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 16 untouched benchmarks
🆕 1 new benchmark

Performance Changes

Benchmark BASE HEAD Efficiency
🆕 generated_listener_walk N/A 77.3 µs N/A

Comparing issue-324-runtime-walk-engine (ecb4492) with main (c732098)

Open in CodSpeed

@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 `@benches/benches/grammar_frontend.rs`:
- Around line 146-152: Update generated_listener_walk to wrap both parsed.tree()
and the result of TomlTreeWalker::walk in std::hint::black_box, preserving the
existing NoopTomlListener and successful-walk handling while preventing
traversal work from being optimized away.
🪄 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: f62ef982-9d49-45de-9769-476bb14c8219

📥 Commits

Reviewing files that changed from the base of the PR and between c732098 and e9a9175.

⛔ Files ignored due to path filters (14)
  • benches/Cargo.lock is excluded by !**/*.lock
  • 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/snapshots/antlr4_runtime__generated__tests__generated_walk_order_and_invocation_states.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__generated__tests__generated_walk_short_circuit.snap is excluded by !**/*.snap
  • 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 (10)
  • README.md
  • benches/Cargo.toml
  • benches/benches/grammar_frontend.rs
  • crates/antlr-rust-codegen/src/generator/tests.rs
  • crates/antlr-rust-codegen/src/parser/surface/contexts.rs
  • crates/antlr-rust-codegen/src/parser/surface/traversal.rs
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/cli.rs
  • crates/antlr-rust-runtime/src/generated.rs
  • crates/antlr-rust-runtime/src/lib.rs
  • third_party/antlr-v4-grammar/self-hosted.sha256

Comment thread benches/benches/grammar_frontend.rs
@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@tinovyatkin
tinovyatkin force-pushed the issue-324-runtime-walk-engine branch from e9a9175 to 61106c8 Compare August 12, 2026 13:52
@github-actions

github-actions Bot commented Aug 12, 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 ⚪ 48 ⚪ 207 ⚪ 1336 (main: 1332) 🔴 0 ⚪
crates/antlr-rust-runtime/src/generated.rs 126 (main: 102) 🔴 33 (main: 14) 🔴 60 (main: 51) 🔴 151 (main: 101) 🔴 0 ⚪
crates/antlr-rust-codegen/src/parser/surface/contexts.rs 25 ⚪ 22 ⚪ 4 ⚪ 69 ⚪ 16.53 (main: 16.57) 🔴
crates/antlr-rust-codegen/src/parser/surface/traversal.rs 6 ⚪ 2 ⚪ 3 ⚪ 36 ⚪ 18.40 (main: 17.73) 🟢
crates/antlr-rust-runtime/src/lib.rs 4 ⚪ 3 ⚪ 1 ⚪ 7 ⚪ 29.44 (main: 29.52) 🔴

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

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 61106c864e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/antlr-rust-runtime/src/lib.rs
Move iterative listener traversal into antlr-rust-runtime and keep only
grammar-specific callback dispatch in generated parsers. Preserve public
walker and listener APIs, visitation ordering, invocation-state threading,
and callback error propagation.

Use generic inlined callbacks after a dynamic-dispatch prototype regressed
the hardened direct listener-walk benchmark by 16.7%. Add direct runtime
coverage and a CodSpeed listener-walk benchmark that black-boxes its input and
result.

Bump the generated-code API to revision 13 while retaining revision-12 runtime
compatibility, and regenerate the checked-in G4, Rust, TOML, and XPath
recognizers together with compatibility documentation and snapshots.
@tinovyatkin
tinovyatkin force-pushed the issue-324-runtime-walk-engine branch from 61106c8 to ecb4492 Compare August 12, 2026 13:59
@tinovyatkin
tinovyatkin merged commit 2a550f0 into main Aug 12, 2026
15 of 17 checks passed
@tinovyatkin
tinovyatkin deleted the issue-324-runtime-walk-engine branch August 12, 2026 15:15
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: move the listener tree-walk engine into the runtime

1 participant