Skip to content

[codex] Expose parser state from generated parse helper - #50

Merged
tinovyatkin merged 2 commits into
mainfrom
codex/issue-49-parse-with-parser
Jun 23, 2026
Merged

[codex] Expose parser state from generated parse helper#50
tinovyatkin merged 2 commits into
mainfrom
codex/issue-49-parse-with-parser

Conversation

@tinovyatkin

@tinovyatkin tinovyatkin commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add a generated parser-specific parse output type plus parse_with_parser(...) so callers can keep parser state after the entry rule runs
  • keep existing parse(...) API intact by delegating through the new helper and returning only the parse result
  • document the state-preserving helper in the README and Kotlin build guide

Review follow-up

  • make the generated output type name parser-specific, e.g. KotlinParserParseOutput, so a parser named ParseOutput cannot collide with the helper type
  • replace separate helper-function where L: TokenSource clauses with inline L: TokenSource bounds; the bound is still required for generated code to compile

Validation

  • cargo +1.95.0 test --locked
  • cargo +1.95.0 clippy --locked --all-targets --all-features -- -D warnings
  • rustfmt +1.95.0 --edition 2024 --check src/bin/antlr4-rust-gen.rs
  • git diff --check
  • RUSTUP_TOOLCHAIN=1.95.0 tests/kotlin-parity/run.sh --antlr-jar /tmp/antlr-cleanroom/tools/antlr-4.13.2-complete.jar --grammars-v4 /tmp/antlr-cleanroom/grammars-v4 --python /tmp/antlr-cleanroom/venv/bin/python

Closes #49

Summary by CodeRabbit

  • New Features

    • Added parse_with_parser function to access parser state, error counts, and token streams after parsing.
  • Documentation

    • Updated README.md with Rust example demonstrating the new parser access functionality.
    • Updated Kotlin build documentation with examples showing how to capture parser state and error information.

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5f4def8e-35ac-4e22-83fa-a5a72c12322c

📥 Commits

Reviewing files that changed from the base of the PR and between b093882 and 5fdf0c2.

📒 Files selected for processing (1)
  • src/bin/antlr4-rust-gen.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/bin/antlr4-rust-gen.rs

📝 Walkthrough

Walkthrough

The generator's render_parser_parse_convenience function now emits a ParseOutput<R, L> struct and a parse_with_parser function that returns both the parse result and the constructed parser instance. The existing parse function is updated to delegate to parse_with_parser and extract only the result. Unit tests and documentation in README and docs/kotlin-build.md are updated accordingly.

Changes

parse_with_parser generated API

Layer / File(s) Summary
Generator emits ParseOutput and parse_with_parser
src/bin/antlr4-rust-gen.rs
render_parser_parse_convenience emits pub struct ParseOutput<R, L> with result and parser fields, and a pub fn parse_with_parser that wires the lexer, CommonTokenStream, and parser before returning Ok(ParseOutput { result, parser }). The existing parse function is changed to delegate to parse_with_parser and return only .result. Unit tests assert the generated ParseOutput struct, parse_with_parser signature, lexer/token stream/parser wiring, and Ok(ParseOutput { result, parser }) construction, plus collision-safe naming when the parser type is itself ParseOutput.
README and kotlin-build docs
README.md, docs/kotlin-build.md
Both files gain a Rust code snippet demonstrating parse_with_parser, capturing the returned parser to call number_of_syntax_errors() and into_token_stream(), and printing error/token counts alongside the parsed tree text.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • ophi-dev/antlr-rust-runtime#37: Adds token_stream/into_token_stream methods to the parser, which are the exact accessors the new parse_with_parser API exposes to callers via the returned ParseOutput.parser.

Poem

🐇 Hop hop, the parser stays near,
No longer dropped before you can peer!
parse_with_parser returns the whole set —
result and parser, best duo yet.
Errors and tokens, now yours to inspect,
One call, full output — perfectly decked! 🌿

🚥 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 summarizes the main change: exposing parser state from generated parse helper functions.
Linked Issues check ✅ Passed The PR fully addresses issue #49 by implementing the desired parse_with_parser API that preserves parser state for diagnostics and token access.
Out of Scope Changes check ✅ Passed All changes directly support the stated objective of exposing parser state: code generator updates, new API documentation, and maintained backward compatibility.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 codex/issue-49-parse-with-parser

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 Jun 22, 2026

Copy link
Copy Markdown

Copy/Paste Detection

Found 9 duplication(s) across 1 changed Rust file(s) (threshold: 100 tokens).

Show duplications

Found a 70 line (285 tokens) duplication in the following files:

  • Starting at line 4887 of src/bin/antlr4-rust-gen.rs
  • Starting at line 5063 of src/bin/antlr4-rust-gen.rs
        let size = ch.len_utf8();
        if line_comment {
            line_comment = ch != '\n';
            index += size;
            continue;
        }
        if block_comment {
            if source.as_bytes().get(index..index + 2) == Some(b"*/") {
                block_comment = false;
                index += 2;
            } else {
                index += size;
            }
            continue;
        }
        if char_set {
            match ch {
                _ if escaped => escaped = false,
                '\\' => escaped = true,
                ']' => char_set = false,
                _ => {}
            }
            index += size;
            continue;
        }
        if escaped {
            escaped = false;
            index += size;
            continue;
        }
        if single_quoted {
            match ch {
                '\\' => escaped = true,
                '\'' => single_quoted = false,
                _ => {}
            }
            index += size;
            continue;
        }
        if double_quoted {
            match ch {
                '\\' => escaped = true,
                '"' => double_quoted = false,
                _ => {}
            }
            index += size;
            continue;
        }
        match ch {
            '/' if source.as_bytes().get(index..index + 2) == Some(b"//") => {
                line_comment = true;
                index += 2;
            }
            '/' if source.as_bytes().get(index..index + 2) == Some(b"/*") => {
                block_comment = true;
                index += 2;
            }
            '\'' => {
                single_quoted = true;
                index += size;
            }
            '"' => {
                double_quoted = true;
                index += size;
            }
            '[' => {
                char_set = true;
                index += size;
            }
            '{' => return Some(index),

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

  • Starting at line 10449 of src/bin/antlr4-rust-gen.rs
  • Starting at line 10573 of src/bin/antlr4-rust-gen.rs
        atn.add_state(AtnState::new(5, AtnStateKind::RuleStop).with_rule_index(0));
        atn.state_mut(0)
            .expect("state 0")
            .add_transition(Transition::Epsilon { target: 1 });
        atn.state_mut(1)
            .expect("state 1")
            .add_transition(Transition::Epsilon { target: 2 });
        atn.state_mut(1)
            .expect("state 1")
            .add_transition(Transition::Epsilon { target: 3 });
        atn.state_mut(2)
            .expect("state 2")
            .add_transition(Transition::Atom {
                target: 4,
                label: 1,
            });
        atn.state_mut(3)
            .expect("state 3")
            .add_transition(Transition::Atom {
                target: 4,
                label: 2,
            });
        atn.state_mut(4)
            .expect("state 4")
            .add_transition(Transition::Epsilon { target: 5 });
        atn.add_decision_state(1);

Found a 43 line (157 tokens) duplication in the following files:

  • Starting at line 2093 of src/bin/antlr4-rust-gen.rs
  • Starting at line 2257 of src/bin/antlr4-rust-gen.rs
    )
    .expect("writing to a string cannot fail");
    // Capture the rule start AFTER `enter_rule`, which advances the cursor past any
    // leading hidden-channel tokens to the first visible token. Capturing before
    // would make `$start`/`$text` in generated actions include a leading hidden
    // prefix (e.g. whitespace), diverging from ANTLR and the rule context start.
    writeln!(
        out,
        "        let __rule_start = antlr4_runtime::IntStream::index(self.base.input());"
    )
    .expect("writing to a string cannot fail");
    // Member-setting `@init` runs on rule entry (before the body) so same-rule
    // predicates and actions observe the state it sets.
    render_generated_init_action_entry(
        out,
        index,
        step_render_context.init_entry_action_statements,
        2,
    );
    // Queue the `@init` action event before the body steps so the buffered replay
    // (`run_generated_action`) runs it ahead of body actions, matching ANTLR's
    // "init before body" order. It sits after `__generated_action_marker`, so a
    // fatal-sync abort that truncates back to the marker discards it too.
    render_generated_init_action(out, index, entry_state, init_action_statements, 2);
    writeln!(out, "        let mut __consumed_eof = false;")
        .expect("writing to a string cannot fail");
    writeln!(
        out,
        "        let mut __sync_error: Option<antlr4_runtime::AntlrError> = None;"
    )
    .expect("writing to a string cannot fail");
    writeln!(
        out,
        "        let __result = (|| -> Result<(), antlr4_runtime::AntlrError> {{"
    )
    .expect("writing to a string cannot fail");
    render_generated_steps(out, &rule.steps, 3, step_render_context);
    writeln!(out, "            Ok(())").expect("writing to a string cannot fail");
    writeln!(out, "        }})();").expect("writing to a string cannot fail");
    writeln!(out, "        match __result {{").expect("writing to a string cannot fail");
    writeln!(out, "            Ok(()) => {{").expect("writing to a string cannot fail");
    writeln!(
        out,

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

  • Starting at line 10448 of src/bin/antlr4-rust-gen.rs
  • Starting at line 10488 of src/bin/antlr4-rust-gen.rs
        atn.add_state(AtnState::new(4, AtnStateKind::BlockEnd).with_rule_index(0));
        atn.add_state(AtnState::new(5, AtnStateKind::RuleStop).with_rule_index(0));
        atn.state_mut(0)
            .expect("state 0")
            .add_transition(Transition::Epsilon { target: 1 });
        atn.state_mut(1)
            .expect("state 1")
            .add_transition(Transition::Epsilon { target: 2 });
        atn.state_mut(1)
            .expect("state 1")
            .add_transition(Transition::Epsilon { target: 3 });
        atn.state_mut(2)
            .expect("state 2")
            .add_transition(Transition::Atom {
                target: 4,
                label: 1,
            });
        atn.state_mut(3)

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

  • Starting at line 10489 of src/bin/antlr4-rust-gen.rs
  • Starting at line 10573 of src/bin/antlr4-rust-gen.rs
        atn.add_state(AtnState::new(5, AtnStateKind::RuleStop).with_rule_index(0));
        atn.state_mut(0)
            .expect("state 0")
            .add_transition(Transition::Epsilon { target: 1 });
        atn.state_mut(1)
            .expect("state 1")
            .add_transition(Transition::Epsilon { target: 2 });
        atn.state_mut(1)
            .expect("state 1")
            .add_transition(Transition::Epsilon { target: 3 });
        atn.state_mut(2)
            .expect("state 2")
            .add_transition(Transition::Atom {
                target: 4,
                label: 1,
            });
        atn.state_mut(4)

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

  • Starting at line 8280 of src/bin/antlr4-rust-gen.rs
  • Starting at line 8424 of src/bin/antlr4-rust-gen.rs
            [GeneratedParserStep::Decision {
                state: 1,
                decision: 0,
                track_alt_number: true,
                allow_semantic_context: false,
                force_context: false,
                fast_path: Some(GeneratedDecisionFastPath {
                    arms: vec![
                        GeneratedDecisionFastArm {
                            alt: 1,
                            intervals: vec![(1, 1)],
                        },
                        GeneratedDecisionFastArm {
                            alt: 2,
                            intervals: vec![(2, 2)],
                        },
                    ],
                }),
                alts: vec![vec![mt(1, 4)], vec![mt(2, 4)]],
            }]

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

  • Starting at line 1593 of src/bin/antlr4-rust-gen.rs
  • Starting at line 1667 of src/bin/antlr4-rust-gen.rs
fn compile_generated_parser_star_loop(
    context: &GeneratedParserCompileContext<'_>,
    state: &antlr4_runtime::atn::AtnState,
    decision: usize,
    stop_state: usize,
    visited: &mut BTreeSet<usize>,
) -> Option<Vec<GeneratedParserStep>> {
    let mut enter = None;
    let mut exit = None;
    for (index, transition) in state.transitions.iter().enumerate() {
        let alt = index + 1;
        let target = transition.target();
        let target_state = context.atn.state(target)?;

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

  • Starting at line 6316 of src/bin/antlr4-rust-gen.rs
  • Starting at line 6476 of src/bin/antlr4-rust-gen.rs
        }
        ActionTemplate::Noop
        | ActionTemplate::Text { .. }
        | ActionTemplate::TextWithPrefix { .. }
        | ActionTemplate::RuleTextWithPrefix { .. }
        | ActionTemplate::StringTree { .. }
        | ActionTemplate::RuleInvocationStack { .. }
        | ActionTemplate::ListenerWalk { .. }
        | ActionTemplate::RuleValue { .. }
        | ActionTemplate::RuleReturnValue { .. }
        | ActionTemplate::SetIntReturn { .. }
        | ActionTemplate::TokenText { .. }
        | ActionTemplate::TokenTextWithPrefix { .. }
        | ActionTemplate::TokenDisplay { .. }
        | ActionTemplate::ExpectedTokenNames { .. }
        | ActionTemplate::Literal { .. }
        | ActionTemplate::MemberValue { .. }

Found a 35 line (103 tokens) duplication in the following files:

  • Starting at line 2157 of src/bin/antlr4-rust-gen.rs
  • Starting at line 2321 of src/bin/antlr4-rust-gen.rs
    writeln!(out, "                        self.base.exit_rule();")
        .expect("writing to a string cannot fail");
    writeln!(
        out,
        "                        self.generated_actions.truncate(__generated_action_marker);"
    )
    .expect("writing to a string cannot fail");
    writeln!(
        out,
        "                        self.base.restore_int_members(__generated_member_checkpoint);"
    )
    .expect("writing to a string cannot fail");
    writeln!(
        out,
        "                        self.base.restore_generated_diagnostics(__generated_diagnostic_marker);"
    )
    .expect("writing to a string cannot fail");
    writeln!(
        out,
        "                        self.base.record_generated_syntax_error();"
    )
    .expect("writing to a string cannot fail");
    writeln!(
        out,
        "                        return Err(GeneratedRuleError::Fatal(__error));"
    )
    .expect("writing to a string cannot fail");
    writeln!(out, "                    }}").expect("writing to a string cannot fail");
    writeln!(
        out,
        "                    self.base.recover_generated_rule(&mut __ctx, atn(), __error);"
    )
    .expect("writing to a string cannot fail");
    writeln!(
        out,

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new parse_with_parser helper function and a ParseOutput struct to the generated ANTLR4 Rust parser, allowing callers to access the parser state (such as syntax diagnostics or the token stream) after running the entry rule. The documentation and tests have been updated to reflect this change. The feedback suggests removing the redundant 'where L: TokenSource' trait bounds on the generated parse and parse_with_parser functions to simplify the generated code, as the compiler can infer this constraint automatically.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/bin/antlr4-rust-gen.rs Outdated
Comment on lines 7943 to 7949
pub fn parse_with_parser<L, R>(
input: impl AsRef<str>,
lexer: impl FnOnce(antlr4_runtime::InputStream) -> L,
entry: impl FnOnce(&mut {type_name}<L>) -> Result<R, antlr4_runtime::AntlrError>,
) -> Result<ParseOutput<R, L>, antlr4_runtime::AntlrError>
where
L: TokenSource,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The where L: TokenSource clause is redundant here. The compiler can infer this constraint from the entry closure's argument type &mut {type_name}<L> and the return type Result<ParseOutput<R, L>, ...>, as both {type_name} and ParseOutput have a where L: TokenSource bound. Removing the explicit where clause would make the generated code slightly cleaner.

This also applies to the parse function.

Suggested change
pub fn parse_with_parser<L, R>(
input: impl AsRef<str>,
lexer: impl FnOnce(antlr4_runtime::InputStream) -> L,
entry: impl FnOnce(&mut {type_name}<L>) -> Result<R, antlr4_runtime::AntlrError>,
) -> Result<ParseOutput<R, L>, antlr4_runtime::AntlrError>
where
L: TokenSource,
pub fn parse_with_parser<L, R>(
input: impl AsRef<str>,
lexer: impl FnOnce(antlr4_runtime::InputStream) -> L,
entry: impl FnOnce(&mut {type_name}<L>) -> Result<R, antlr4_runtime::AntlrError>,
) -> Result<ParseOutput<R, L>, antlr4_runtime::AntlrError>

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

ℹ️ 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 src/bin/antlr4-rust-gen.rs Outdated
/// Keeps the generated parser available after the entry rule runs so callers
/// can inspect diagnostics or recover the parser-owned token stream.
#[derive(Debug)]
pub struct ParseOutput<R, L>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid a fixed helper type name collision

For grammars whose parser name renders to ParseOutput (for example --parser-name ParseOutput or a ParseOutput.interp input), this emits a public helper struct named ParseOutput and then later emits the generated parser struct with the same name in the same module, so the generated parser fails to compile with a duplicate type definition. Please make the helper name collision-proof relative to the generated parser type.

Useful? React with 👍 / 👎.

@tinovyatkin
tinovyatkin merged commit 10ee75b into main Jun 23, 2026
8 checks passed
@tinovyatkin
tinovyatkin deleted the codex/issue-49-parse-with-parser branch June 23, 2026 19:55
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.

Generated parse convenience should expose parser state for diagnostics and tokens

1 participant