Skip to content

Add parse-bench AST parity gate and fix Java > vs >> precpred#110

Merged
tinovyatkin merged 4 commits into
mainfrom
fix/parse-bench-ast-check-java-precpred
Jul 18, 2026
Merged

Add parse-bench AST parity gate and fix Java > vs >> precpred#110
tinovyatkin merged 4 commits into
mainfrom
fix/parse-bench-ast-check-java-precpred

Conversation

@tinovyatkin

Copy link
Copy Markdown
Contributor

Summary

  • parse-bench --ast-check: before timing, dump each fixture’s parse tree from rust-antlr and go-antlr in the Kotlin-parity Rule/Term/Err format; require byte-identical dumps and no error nodes (Rust also rejects non-zero number_of_syntax_errors).
  • Fix Java precpred(_ctx, 11) failures on constructs like ListenableFuture<LookupResult> inside switch cases: left-recursive loop-enter no longer forces enter on multi-token operator prefixes that shadow a lower-precedence single-token op (Java > vs >>/>>>).
  • CI: AST parity step for kotlin,java,trino. C# Mono divergence tracked in parse-bench: investigate Rust vs Go AST divergence on C# Mono fixtures #109.

Root cause (Java)

At RHS precedence of <, one-token lookahead treated > as unconditional loop-enter via shift (>>). Operator selection then picked relational >, precpred(11) failed, and the parse reported syntax errors. Go’s StarLoopEntry adaptive predict exits instead, so the outer loop takes relational correctly.

Fix keeps the single-token enter fast path (+, *, ., low-prec >, …) and only defers when the current precedence is multi-token-only for a symbol that is single-token at precedence 0.

Test plan

  • cargo test --lib left_recursive_loop (includes new shadowing unit test)
  • cargo test --lib parser::tests
  • python3 tools/parse-bench/test_run.py
  • Minimal Java repro + bazel-sky-value-retriever.java: Rust dump matches Go, 0 error nodes
  • All tools/parse-bench/fixtures/java/*.java dump clean under rust-antlr
  • kotlin dump still clean after the loop-enter change
  • CI parse-bench workflow (AST parity + timing)

Follow-up

Parse-bench can now dump Rule/Term/Err trees from rust-antlr and go-antlr
and require byte-identical, error-free ASTs before timing (--ast-check).

Fix left-recursive loop-enter so multi-token operator prefixes that shadow
a lower-precedence single-token op (Java > vs >>) defer to StarLoopEntry
adaptive predict instead of forcing enter and failing precpred.

CI runs AST parity on kotlin, java, and trino. C# Mono fixture divergence
is tracked in #109.
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 27 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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

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

How do review limits work?

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

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

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c160fdf1-309c-4d72-8974-abbda1d08c38

📥 Commits

Reviewing files that changed from the base of the PR and between 8f09dfc and ad4f553.

📒 Files selected for processing (3)
  • src/parser.rs
  • tools/parse-bench/run.py
  • tools/parse-bench/test_run.py

Walkthrough

The parser now classifies left-recursive operators by token-prefix length and defers multi-token operators when they shadow lower-precedence single-token operators. Benchmark runners for Rust and Go can emit structured parse-tree dumps. The benchmark script compares dumps, rejects error nodes and mismatches, and exposes this through --ast-check. Documentation and CI commands enable the parity check for selected languages and fixtures.

Poem

I’m a rabbit with trees in my paws,
Matching Rust and Go without flaws.
> hops, >> waits,
While parity checks guard the gates.
Clean dumps bounce through the night! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title captures both main changes: parse-bench AST parity gating and the Java precedence fix.
Description check ✅ Passed The description directly summarizes the AST parity gate, the Java precedence fix, and the CI/test impact.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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 Jul 18, 2026

Copy link
Copy Markdown

Copy/Paste Detection

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

Show duplications

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

  • Starting at line 14155 of src/parser.rs
  • Starting at line 14288 of 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,
            },
        ];

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

  • Starting at line 13175 of src/parser.rs
  • Starting at line 13247 of 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))

Found a 33 line (126 tokens) duplication in the following files:

  • Starting at line 8126 of src/parser.rs
  • Starting at line 8164 of src/parser.rs
                    if self.fast_parser_predicate_matches(predicate_context, transition, index) {
                        let boundary = left_recursive_boundary(atn, state, target);
                        outcomes.extend(
                            self.recognize_state_fast(
                                atn,
                                FastRecognizeRequest {
                                    state_number: target,
                                    stop_state,
                                    index,
                                    rule_start_index,
                                    decision_start_index: next_decision_start_index,
                                    precedence,
                                    depth: depth + 1,
                                    recovery_symbols: Rc::clone(&epsilon_recovery_symbols),
                                    recovery_state: epsilon_recovery_state,
                                },
                                FastRecognizeScratch {
                                    predicate_context,
                                    visiting,
                                    memo,
                                    expected,
                                },
                            )
                            .into_iter()
                            .map(|mut outcome| {
                                if let Some(rule_index) = boundary {
                                    let boundary = self.arena_boundary_node(rule_index);
                                    self.defer_fast_outcome_node(&mut outcome, boundary);
                                }
                                outcome
                            }),
                        );
                    } else {

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

  • Starting at line 8091 of src/parser.rs
  • Starting at line 8127 of src/parser.rs
  • Starting at line 8165 of src/parser.rs
                    let boundary = left_recursive_boundary(atn, state, target);
                    outcomes.extend(
                        self.recognize_state_fast(
                            atn,
                            FastRecognizeRequest {
                                state_number: target,
                                stop_state,
                                index,
                                rule_start_index,
                                decision_start_index: next_decision_start_index,
                                precedence,
                                depth: depth + 1,
                                recovery_symbols: Rc::clone(&epsilon_recovery_symbols),
                                recovery_state: epsilon_recovery_state,
                            },
                            FastRecognizeScratch {
                                predicate_context,
                                visiting,
                                memo,
                                expected,
                            },
                        )
                        .into_iter()
                        .map(|mut outcome| {
                            if let Some(rule_index) = boundary {
                                let boundary = self.arena_boundary_node(rule_index);
                                self.defer_fast_outcome_node(&mut outcome, boundary);
                            }
                            outcome
                        }),
                    );
                }

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

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

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

  • Starting at line 9045 of src/parser.rs
  • Starting at line 9118 of 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,
                                    precedence,
                                    depth: depth + 1,
                                    recovery_symbols: epsilon_recovery_symbols.clone(),
                                    recovery_state: epsilon_recovery_state,
                                },
                                visiting,
                                memo,
                                expected,
                            )
                            .into_iter()
                            .map(|mut outcome| {
                                prepend_decision(&mut outcome, decision);

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

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

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

  • Starting at line 12181 of src/parser.rs
  • Starting at line 12264 of 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),

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

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

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

  • Starting at line 12046 of src/parser.rs
  • Starting at line 13612 of src/parser.rs
  • Starting at line 13813 of 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))

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

  • Starting at line 13946 of src/parser.rs
  • Starting at line 16063 of 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");

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

  • Starting at line 13946 of src/parser.rs
  • Starting at line 16088 of 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);

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

  • Starting at line 6998 of src/parser.rs
  • Starting at line 7386 of 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;

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

  • Starting at line 16342 of src/parser.rs
  • Starting at line 16366 of src/parser.rs
    fn outcome_ties_keep_later_non_recursive_alternative() {
        let arena = RecognitionArena::default();
        let first = RecognizeOutcome {
            index: 1,
            consumed_eof: false,
            alt_number: 0,
            member_values: BTreeMap::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 16 line (105 tokens) duplication in the following files:

  • Starting at line 6199 of src/parser.rs
  • Starting at line 6765 of src/parser.rs
        let start_state = atn.rule_to_start_state().get(rule_index).ok_or_else(|| {
            AntlrError::Unsupported(format!("rule {rule_index} has no start state"))
        })?;
        let stop_state = atn
            .rule_to_stop_state()
            .get(rule_index)
            .filter(|state| *state != usize::MAX)
            .ok_or_else(|| {
                AntlrError::Unsupported(format!("rule {rule_index} has no stop state"))
            })?;

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

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

  • Starting at line 5934 of src/parser.rs
  • Starting at line 5958 of 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;
            };

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces an AST parity verification mechanism between the Rust and Go ANTLR implementations in the parse benchmark tool via a new --ast-check flag. Additionally, it refactors the Rust parser's left-recursive loop enter prediction to correctly handle and defer multi-token prefixes that shadow lower-precedence single-token operators (such as Java's >> vs >). Feedback on the changes suggests simplifying the match arms in state_operator_token_prefix_complete by refactoring the Transition::Precedence arm to use boolean logic instead of a match guard, which also removes a redundant pattern from the catch-all arm.

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/parser.rs Outdated
Comment on lines +3044 to +3058
Transition::Precedence {
target,
precedence: transition_precedence,
} if *transition_precedence >= precedence => {
state_operator_token_prefix_complete(atn, *target, precedence, visited)
}
Transition::Predicate { target, .. } => {
state_operator_token_prefix_complete(atn, *target, precedence, visited)
}
Transition::Atom { .. }
| Transition::Range { .. }
| Transition::Set { .. }
| Transition::NotSet { .. }
| Transition::Wildcard { .. }
| Transition::Precedence { .. } => false,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The Transition::Precedence match arm can be simplified by removing the guard and using boolean logic inside the arm. This allows us to remove the redundant Transition::Precedence pattern from the catch-all arm, making the code more idiomatic and maintainable.

Suggested change
Transition::Precedence {
target,
precedence: transition_precedence,
} if *transition_precedence >= precedence => {
state_operator_token_prefix_complete(atn, *target, precedence, visited)
}
Transition::Predicate { target, .. } => {
state_operator_token_prefix_complete(atn, *target, precedence, visited)
}
Transition::Atom { .. }
| Transition::Range { .. }
| Transition::Set { .. }
| Transition::NotSet { .. }
| Transition::Wildcard { .. }
| Transition::Precedence { .. } => false,
Transition::Precedence {
target,
precedence: transition_precedence,
} => {
*transition_precedence >= precedence
&& state_operator_token_prefix_complete(atn, *target, precedence, visited)
}
Transition::Predicate { target, .. } => {
state_operator_token_prefix_complete(atn, *target, precedence, visited)
}
Transition::Atom { .. }
| Transition::Range { .. }
| Transition::Set { .. }
| Transition::NotSet { .. }
| Transition::Wildcard { .. } => false,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 8f09dfc: the precedence arm now uses the requested boolean condition and the redundant fallback pattern is removed. Validation passed with cargo +1.97.1 clippy --locked --all-targets --all-features -- -D warnings, cargo +1.97.1 test --locked, cargo test --locked --lib left_recursive_loop, and python3 tools/parse-bench/test_run.py.

@tinovyatkin

Copy link
Copy Markdown
Contributor Author

@codex review

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/parser.rs (1)

2864-2871: 🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift

Preserve rule continuations and independent reachability flags.

Transition::Rule => true can classify a token matched inside a helper rule as complete even when its caller still requires operator tokens. Predicates after the first token are also treated as unconditional, while max discards predicate-dependent reachability when a multi-token path exists. These cases can incorrectly return Some(true) and bypass the adaptive exit alternative.

Carry rule-return continuations and represent single-token, multi-token, and predicate-dependent results as combinable flags. Add coverage for a rule-wrapped prefix and a predicate-gated single-token alternative sharing a prefix with a multi-token alternative.

As per coding guidelines, “Model behavior from ANTLR metadata and ATN structure instead” and keep runtime modules grammar-agnostic.

Also applies to: 3019-3063, 3085-3100, 3121-3122, 3187-3190, 3229-3236, 5371-5389

🤖 Prompt for 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.

In `@src/parser.rs` around lines 2864 - 2871, Update the operator reachability
analysis around OperatorSymbolReachability and its Transition::Rule handling to
preserve caller continuations, propagate predicate-dependent status beyond the
first token, and combine independent single-token, multi-token, and
predicate-dependent flags instead of letting max discard them. Base decisions on
ANTLR metadata and ATN structure without grammar-specific logic, ensuring
rule-wrapped prefixes and predicate-gated single-token alternatives sharing a
multi-token prefix do not incorrectly return Some(true) or bypass the adaptive
exit alternative; add coverage for both cases.

Source: Coding guidelines

🤖 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 `@tools/parse-bench/run.py`:
- Around line 575-583: Unify tree-dump escaping by making the Rust Terminal and
Error formatting paths use the same encoding contract as rustDebugStr(),
including Unicode cases such as leading combining marks. Update rustDebugStr()
or the Rust formatter as needed so equivalent trees produce byte-for-byte
identical output, and add coverage for these Unicode escape cases across both
dumpers.
- Around line 685-697: Update the generated dump-tree paths around
dump_tree_{spec.name} and the corresponding logic near the second reported
location to inspect lexer diagnostics as well as parser syntax errors. Capture
the lexer’s error count from the generated lexer or token stream, and return an
error whenever either lexer errors or parser syntax errors are nonzero,
preserving the existing diagnostic failure behavior.

---

Outside diff comments:
In `@src/parser.rs`:
- Around line 2864-2871: Update the operator reachability analysis around
OperatorSymbolReachability and its Transition::Rule handling to preserve caller
continuations, propagate predicate-dependent status beyond the first token, and
combine independent single-token, multi-token, and predicate-dependent flags
instead of letting max discard them. Base decisions on ANTLR metadata and ATN
structure without grammar-specific logic, ensuring rule-wrapped prefixes and
predicate-gated single-token alternatives sharing a multi-token prefix do not
incorrectly return Some(true) or bypass the adaptive exit alternative; add
coverage for both cases.
🪄 Autofix (Beta)

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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ed2eb61f-486d-4f4e-be3b-cd6b2b7b7d0b

📥 Commits

Reviewing files that changed from the base of the PR and between 837cb54 and 8f09dfc.

📒 Files selected for processing (6)
  • .github/workflows/parse-bench.yml
  • README.md
  • src/parser.rs
  • tools/parse-bench/README.md
  • tools/parse-bench/run.py
  • tools/parse-bench/test_run.py

Comment thread tools/parse-bench/run.py Outdated
Comment thread tools/parse-bench/run.py

@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: 8f09dfcb40

ℹ️ 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 tools/parse-bench/run.py Outdated
Comment on lines +1071 to +1074
if r < 0x20 || r == 0x7f {{
fmt.Fprintf(&b, `\\u{{%x}}`, r)
}} else {{
b.WriteRune(r)

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 Match Rust debug escaping for Unicode text

When a fixture contains non-printing Unicode in token text, such as U+00A0 or U+2028 in a comment/string/identifier, the Rust dump uses {:?} and emits \u{...}, but this Go helper writes the raw rune because it only escapes C0 controls and DEL. That makes --ast-check report a byte-level Rust/Go AST diff even though the parse trees and token text are equivalent, so the Go formatter needs to mirror Rust's string debug escaping more completely.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 396465f. Rather than trying to track Rust debug-printability tables, both generated runners now encode every scalar as lowercase \u{...}, so valid Unicode including leading combining marks, NBSP, and U+2028 is byte-stable across languages. The generated-source regression and all 13 Kotlin/Java/Trino AST parity fixtures pass.

@tinovyatkin

Copy link
Copy Markdown
Contributor Author

Addressed CodeRabbit’s outside-diff operator-reachability finding in 396465f4c.

The analysis now carries helper-rule return continuations after a matched prefix token, combines unconditional single-token, unconditional multi-token, and predicate-dependent reachability independently, and conservatively delegates recursive helper cycles to adaptive prediction. Added focused regressions for a rule-wrapped > prefix and a predicate-gated single-token alternative sharing > with a multi-token alternative.

Validation on this head:

  • cargo test --locked --all-targets --all-features --no-fail-fast (375 passed)
  • cargo clippy --locked --all-targets --all-features -- -D warnings
  • runtime testsuite: 357 passed, 0 failed, 0 skipped
  • Kotlin/Java/Trino Rust-vs-Go AST parity: 13/13 fixtures
  • same-host Java/Kotlin A-B-A benchmark vs origin/main: worst minimum-time ratio 1.017x; worst median average-time ratio 1.013x (well below 1.15x)

@tinovyatkin

Copy link
Copy Markdown
Contributor Author

@codex review

@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: 396465f4c5

ℹ️ 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/parser.rs Outdated
@tinovyatkin

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: ad4f553e9d

ℹ️ 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".

@tinovyatkin
tinovyatkin merged commit f95b0d2 into main Jul 18, 2026
11 checks passed
@tinovyatkin
tinovyatkin deleted the fix/parse-bench-ast-check-java-precpred branch July 18, 2026 14:09
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.

1 participant