Skip to content

Implement semantic predicate accountability hooks - #55

Merged
tinovyatkin merged 60 commits into
mainfrom
codex/issue-9-semantic-delivery
Jul 10, 2026
Merged

Implement semantic predicate accountability hooks#55
tinovyatkin merged 60 commits into
mainfrom
codex/issue-9-semantic-delivery

Conversation

@tinovyatkin

@tinovyatkin tinovyatkin commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • route parser semantic predicates and actions through a SemIR-backed ParserSemantics table while keeping the existing legacy predicate/action table API as a compatibility adapter
  • generate parser-owned semantic tables, direct SemIR predicate dispatch, fallback parser options, and typed hook adapters for helper-style semantics
  • add generator plan controls for --sem-patterns, --require-full-semantics, explicit --sem-unknown=hook, coordinate overrides, exact pattern/helper lowering, and JavaScript helper hook data in patterns/javascript.toml
  • expose the shared runtime pieces needed by the plan: SemIR constructors/evaluation trace support, parser hook execution context, lexer semantic hook wrappers, and LexerSemCtx
  • update README, CLAUDE, and the issue-9 design plan to document the delivered compatibility boundaries, fail-loud modes, hook strategy, and generator usage

Validation

  • cargo +1.95.0 check --locked --all-targets
  • cargo +1.95.0 clippy --locked --all-targets --all-features -- -D warnings
  • cargo +1.95.0 test --locked --all-targets
  • cargo +1.95.0 run --release --quiet --bin antlr4-runtime-testsuite -> summary: 357 passed, 0 failed, 0 skipped, 357 run
  • 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/antlr-python/bin/python -> all 9 Kotlin/script snippets matched

Closes #9

@greptile-apps greptile-apps 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.

tinovyatkin has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown

Copy/Paste Detection

Found 21 duplication(s) across 15 changed Rust file(s) (threshold: 100 tokens).

Show duplications

Found a 30 line (252 tokens) duplication in the following files:

  • Starting at line 2017 of src/atn/parser.rs
  • Starting at line 2132 of src/atn/parser.rs
        let mut atn = Atn::new(AtnType::Parser, 3);
        add_state(&mut atn, 0, AtnStateKind::RuleStart);
        add_state(&mut atn, 1, AtnStateKind::BlockStart);
        add_state(&mut atn, 2, AtnStateKind::Basic);
        add_state(&mut atn, 3, AtnStateKind::Basic);
        add_state(&mut atn, 4, AtnStateKind::Basic);
        add_state(&mut atn, 5, AtnStateKind::Basic);
        add_state(&mut atn, 6, AtnStateKind::BlockEnd);
        add_state(&mut atn, 7, AtnStateKind::RuleStop);
        atn.set_rule_to_start_state(vec![0]);
        atn.set_rule_to_stop_state(vec![7]);
        atn.add_decision_state(1);
        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: 4 });
        atn.state_mut(2)
            .expect("state 2")
            .add_transition(Transition::Atom {
                target: 3,
                label: 1,
            });
        atn.state_mut(3)
            .expect("state 3")
            .add_transition(Transition::Atom {

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

  • Starting at line 10361 of src/parser.rs
  • Starting at line 10446 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![CommonToken::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 24 line (134 tokens) duplication in the following files:

  • Starting at line 1409 of src/atn/lexer.rs
  • Starting at line 923 of src/atn/lexer_dfa.rs
        let atn = AtnDeserializer::new(&SerializedAtn::from_i32(&[
            4, 0, 2, // version, lexer, max token type
            9, // states
            6, -1, // 0 token start
            2, 0, // 1 rule 0 start
            1, 0, // 2
            1, 0, // 3
            7, 0, // 4 rule 0 stop
            2, 1, // 5 rule 1 start
            1, 1, // 6
            1, 1, // 7
            7, 1, // 8 rule 1 stop
            0, // non-greedy
            0, // precedence
            2, // rules
            1, 1, // rule 0 starts at 1, token type 1
            5, 2, // rule 1 starts at 5, token type 2
            1, // modes
            0, // default mode starts at 0
            0, // sets
            8, // edges
            0, 1, 1, 0, 0, 0, // start -> rule 0
            0, 5, 1, 0, 0, 0, // start -> rule 1
            1, 2, 5, 'a' as i32, 0, 0, 2, 3, 5, 'b' as i32, 0, 0, 3, 4, 1, 0, 0, 0, 5, 6, 5,

Found a 23 line (133 tokens) duplication in the following files:

  • Starting at line 202 of src/atn/lexer.rs
  • Starting at line 392 of src/atn/lexer.rs
pub fn next_token_with_hooks<I, F, A, P, E>(
    lexer: &mut BaseLexer<I, F>,
    atn: &Atn,
    mut custom_action: A,
    mut semantic_predicate: P,
    mut accept_adjuster: E,
) -> CommonToken
where
    I: CharStream,
    F: TokenFactory,
    A: FnMut(&mut BaseLexer<I, F>, LexerCustomAction),
    P: FnMut(&BaseLexer<I, F>, LexerPredicate) -> bool,
    E: FnMut(&mut BaseLexer<I, F>, i32, usize),
{
    next_token_with_hooks_impl(
        lexer,
        atn,
        &mut custom_action,
        &mut semantic_predicate,
        &mut accept_adjuster,
        LexerMatchStrategy {
            compiled: None,
            use_cache: false,

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

  • Starting at line 2142 of src/atn/parser.rs
  • Starting at line 2219 of src/atn/parser.rs
        atn.set_rule_to_stop_state(vec![7]);
        atn.add_decision_state(1);
        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: 4 });
        atn.state_mut(2)
            .expect("state 2")
            .add_transition(Transition::Atom {
                target: 3,
                label: 1,
            });
        atn.state_mut(3)
            .expect("state 3")
            .add_transition(Transition::Epsilon { target: 6 });

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

  • Starting at line 637 of src/atn/lexer.rs
  • Starting at line 755 of src/atn/lexer.rs
            let Some(state) = atn.state(config.state) else {
                continue;
            };
            for transition in &state.transitions {
                if !transition.matches(symbol, MIN_CHAR_VALUE, MAX_CHAR_VALUE) {
                    continue;
                }
                let mut advanced = config.clone();
                set_config_state(atn, &mut advanced, transition.target());
                if symbol == EOF {
                    advanced.consumed_eof = true;
                } else {
                    advanced.position += 1;
                }
                next.push(advanced);
            }
        }

        let closure = epsilon_closure(atn, next, &mut |predicate| {
            semantic_predicate(lexer, predicate)
        });
        let target_has_semantic_context = closure.has_semantic_context;

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

  • Starting at line 2027 of src/atn/parser.rs
  • Starting at line 2219 of src/atn/parser.rs
        atn.set_rule_to_stop_state(vec![7]);
        atn.add_decision_state(1);
        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: 4 });
        atn.state_mut(2)
            .expect("state 2")
            .add_transition(Transition::Atom {
                target: 3,
                label: 1,
            });
        atn.state_mut(3)
            .expect("state 3")
            .add_transition(Transition::Atom {

Found a 19 line (120 tokens) duplication in the following files:

  • Starting at line 312 of src/bin_support/templates.rs
  • Starting at line 343 of src/bin_support/templates.rs
pub(crate) fn matching_action_brace(source: &str, mut index: usize) -> Option<usize> {
    let mut nested = 0_usize;
    let mut double_quoted = false;
    let mut escaped = false;
    while let Some(ch) = source[index..].chars().next() {
        if escaped {
            escaped = false;
            index += ch.len_utf8();
            continue;
        }
        match ch {
            '\\' if double_quoted => escaped = true,
            '"' => double_quoted = !double_quoted,
            '\'' if !double_quoted => {
                if let Some(next_index) = skip_char_literal(source, index) {
                    index = next_index;
                    continue;
                }
            }

Found a 30 line (120 tokens) duplication in the following files:

  • Starting at line 6085 of src/parser.rs
  • Starting at line 6120 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,
                            },
                            visiting,
                            memo,
                            expected,
                        )
                        .into_iter()
                        .map(|mut outcome| {
                            if let Some(rule_index) = boundary {
                                outcome.nodes.prepend(Rc::new(
                                    FastRecognizedNode::LeftRecursiveBoundary { rule_index },
                                ));
                            }
                            outcome
                        }),
                    );
                }

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

  • Starting at line 6967 of src/parser.rs
  • Starting at line 7042 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 (114 tokens) duplication in the following files:

  • Starting at line 10175 of src/parser.rs
  • Starting at line 11755 of src/parser.rs
    fn parser_matches_token_and_reports_mismatch() {
        let source = Source {
            tokens: vec![
                CommonToken::new(1).with_text("x"),
                CommonToken::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);
        assert_eq!(
            parser.match_token(1).expect("token 1 should match").text(),

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

  • Starting at line 161 of src/atn/parser.rs
  • Starting at line 2317 of src/atn/parser.rs
impl IntStream for LookaheadIntStream {
    fn consume(&mut self) {
        if self.la(1) != TOKEN_EOF {
            self.index += 1;
        }
    }

    fn la(&mut self, offset: isize) -> i32 {
        if offset <= 0 {
            return 0;
        }
        let offset = offset.cast_unsigned() - 1;
        self.symbols
            .get(self.index + offset)
            .copied()
            .unwrap_or(TOKEN_EOF)
    }

    fn index(&self) -> usize {
        self.index
    }

    fn seek(&mut self, index: usize) {
        self.index = index.min(self.symbols.len());

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

  • Starting at line 10412 of src/parser.rs
  • Starting at line 10648 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![
                    CommonToken::new(3).with_text("z"),
                    CommonToken::new(2).with_text("y"),

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

  • Starting at line 4830 of src/parser.rs
  • Starting at line 4873 of src/parser.rs
            FastRecognizedNode::Rule {
                rule_index,
                invoking_state,
                start_index,
                stop_index,
                children,
            } => {
                let mut context = ParserRuleContext::with_child_capacity(
                    *rule_index,
                    *invoking_state,
                    children.len(),
                );
                if let Some(token) = self.token_ref_at(*start_index) {
                    context.set_start_ref(token);
                }
                if let Some(token) = stop_index.and_then(|index| self.token_ref_at(index)) {
                    context.set_stop_ref(token);
                }
                if children.has_left_recursive_boundary() {
                    let folded = fold_fast_left_recursive_boundaries(children.to_vec());

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

  • Starting at line 11818 of src/parser.rs
  • Starting at line 11841 of src/parser.rs
    fn outcome_ties_keep_later_non_recursive_alternative() {
        let first = RecognizeOutcome {
            index: 1,
            consumed_eof: false,
            alt_number: 0,
            member_values: BTreeMap::new(),
            return_values: BTreeMap::new(),
            diagnostics: Vec::new(),
            decisions: Vec::new(),
            actions: vec![ParserAction::new(1, 0, 0, None)],
            nodes: vec![RecognizedNode::Token { index: 0 }],
        };
        let second = RecognizeOutcome {
            actions: vec![ParserAction::new(2, 0, 0, None)],

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

  • Starting at line 10175 of src/parser.rs
  • Starting at line 11730 of src/parser.rs
  • Starting at line 11755 of src/parser.rs
    fn parser_matches_token_and_reports_mismatch() {
        let source = Source {
            tokens: vec![
                CommonToken::new(1).with_text("x"),
                CommonToken::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 20 line (107 tokens) duplication in the following files:

  • Starting at line 4563 of src/parser.rs
  • Starting at line 5099 of src/parser.rs
        let start_state = atn
            .rule_to_start_state()
            .get(rule_index)
            .copied()
            .ok_or_else(|| {
                AntlrError::Unsupported(format!("rule {rule_index} has no start state"))
            })?;
        let stop_state = atn
            .rule_to_stop_state()
            .get(rule_index)
            .copied()
            .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();
        let caller_follow_state = self.pending_invoking_follow_state(atn);

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

  • Starting at line 4815 of src/parser.rs
  • Starting at line 8508 of src/parser.rs
            FastRecognizedNode::MissingToken {
                token_type,
                at_index,
                text,
            } => {
                let current = self.token_at(*at_index);
                let token = CommonToken::new(*token_type)
                    .with_text(text.as_str())
                    .with_span(usize::MAX, usize::MAX)
                    .with_position(
                        current.as_ref().map(Token::line).unwrap_or_default(),
                        current.as_ref().map(Token::column).unwrap_or_default(),
                    );
                Ok(ParseTree::Error(ErrorNode::new(token)))
            }

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

  • Starting at line 204 of src/atn/lexer.rs
  • Starting at line 343 of src/atn/lexer.rs
  • Starting at line 394 of src/atn/lexer.rs
    atn: &Atn,
    mut custom_action: A,
    mut semantic_predicate: P,
    mut accept_adjuster: E,
) -> CommonToken
where
    I: CharStream,
    F: TokenFactory,
    A: FnMut(&mut BaseLexer<I, F>, LexerCustomAction),
    P: FnMut(&BaseLexer<I, F>, LexerPredicate) -> bool,
    E: FnMut(&mut BaseLexer<I, F>, i32, usize),
{
    next_token_with_hooks_impl(
        lexer,
        atn,
        &mut custom_action,
        &mut semantic_predicate,
        &mut accept_adjuster,
        LexerMatchStrategy {
            compiled: None,

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

  • Starting at line 5318 of src/parser.rs
  • Starting at line 5684 of src/parser.rs
    ) -> Option<RecognizeOutcome> {
        let (error_index, message) = self.expected_error_message(rule_index, start_index, expected);
        let token = self.token_at(error_index);
        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;
        }

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

  • Starting at line 9797 of src/parser.rs
  • Starting at line 9837 of src/parser.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.set_rule_to_start_state(vec![0]);
        atn.set_rule_to_stop_state(vec![5]);
        atn.add_decision_state(1);
        atn.state_mut(0)
            .expect("state 0")
            .add_transition(Transition::Epsilon { target: 1 });
        atn.state_mut(1)
            .expect("state 1")
            .add_transition(Transition::Atom {
                target: 2,

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR adds semantic-predicate/action documentation, a new semantic IR module, and hook-aware parser and lexer runtime support. It also updates code generation to inventory semantic coordinates, emit semantics.json, enforce --sem-unknown, and generate hook-capable parser types and constructors. Template scanning, recognizer metadata access, and JavaScript helper mappings were adjusted to support parser-side semantic hook handling.

Poem

I hopped through the grammar, a curious bun,
With hooks and SemIR all neatly spun.
The unknowns now speak, the manifests glow,
And predicates flutter wherever I go. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately highlights the semantic predicate/action hook work.
Description check ✅ Passed The description matches the implemented semantic predicate/action hooks, policies, and docs.
Linked Issues check ✅ Passed The PR implements the fail-loud semantic predicate/action strategy, hook traits, and typed mappings requested in #9.
Out of Scope Changes check ✅ Passed The added docs, patterns, and test artifacts all support the same semantic-predicate/action work; no clear unrelated scope stands out.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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.

@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 implements the initial phases of the design for handling semantic predicates and actions (Issue #9). It introduces a new Semantic IR (semir module) to represent predicates and actions as data, adds support for user-defined SemanticHooks on the parser side, and implements a configurable policy (--sem-unknown) for handling unknown coordinates, complete with a semantics.json manifest output. The review feedback highlights two critical issues: a potential out-of-bounds panic in action_text when the start index exceeds the adjusted stop index, and a misalignment bug in parser_action_source_blocks that causes incorrect source spans and bodies to be recorded in the manifest for unsupported blocks.

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
Comment thread src/bin/antlr4-rust-gen.rs Outdated

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

ℹ️ 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
Comment on lines +4483 to +4487
.collect::<Vec<_>>();
// A non-default policy must reach the interpreter through the emitted
// runtime options, so its literal forces the options-carrying call shape.
let unknown_policy_literal = match options.sem_unknown {
SemUnknownPolicy::AssumeTrue => None,

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 Disable adaptive direct for non-default unknown predicates

When --sem-unknown=assume-false or error is used on a parser whose ATN has only untranslated predicate transitions, unknown_policy_literal is set here but has_predicate_dispatch remains false, so the generated adaptive_direct_allowed gate can still take the ANTLR4_RUST_ADAPTIVE_DIRECT path. That helper falls back through parse_atn_rule(...) without the ParserRuntimeOptions emitted below, so the new unknown-predicate policy is lost and the predicate is treated as passing instead of failing/erroring. Please also disable the adaptive-direct path when a non-default unknown policy or unknown predicate coordinates are present.

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.

Fixed in 3bcc2f2. adaptive_direct_allowed now also requires unknown_policy_literal.is_none(), so a non-default --sem-unknown policy disables the ANTLR4_RUST_ADAPTIVE_DIRECT shortcut. As you noted, that path runs parse_atn_rule_adaptive_or_fallback, which falls back through parse_atn_rule without the emitted ParserRuntimeOptions, dropping the policy. New test non_default_policy_disables_adaptive_direct_gate asserts the gate literal flips from && true && (default) to && false && under both assume-false and error, and first confirms the predicate-free fixture emits the enabled gate by default so the test can't silently pass.

@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 `@src/parser.rs`:
- Around line 424-444: In action_text(), the EOF fallback is using the wrong
boundary and can include hidden tokens before EOF. Update the stop calculation
to match text_interval() and $text by using the visible-token boundary for
TOKEN_EOF instead of blindly subtracting one, and keep the logic localized
around action.stop_index(), self.input.get(), and self.input.text() so the
returned action text excludes trailing whitespace.
🪄 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: bfd76bb4-9d5e-409c-b77e-b4357fa7ac13

📥 Commits

Reviewing files that changed from the base of the PR and between 879e22f and b8ccf2e.

📒 Files selected for processing (7)
  • CLAUDE.md
  • README.md
  • docs/issue-9-semantic-predicates-actions-design.md
  • src/bin/antlr4-rust-gen.rs
  • src/lib.rs
  • src/parser.rs
  • src/semir.rs

Comment thread src/parser.rs

@greptile-apps greptile-apps 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.

tinovyatkin has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@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: 568a824430

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

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

2382-2402: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Avoid replaying legacy and SemIR actions for the same coordinate.

When semantics is present, these paths still apply legacy ParserMemberAction/ParserReturnAction tables first, then execute matching SemIR actions. If a generated parser passes both during migration, AddMember side effects can double-apply. Prefer SemIR for a matched action coordinate, falling back to legacy only when no SemIR action exists.

Also applies to: 2426-2444

🤖 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 2382 - 2402, The replay logic in the parser state
handling is applying both legacy ParserMemberAction/ParserReturnAction entries
and SemIR actions for the same coordinate, which can double-apply member
updates. Update the code around the ParserTableSemCtx setup and the action
replay loop in parser::... so that when semantics is present you first check for
matching speculative SemIR actions and execute those instead, and only fall back
to the legacy actions when no SemIR action exists for that
source_state/coordinate. Make the same precedence change in the related block at
the other referenced location so both paths stay consistent.

4953-4954: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Scope and restore the active unknown-predicate policy.

This mutates parser-level unknown_predicate_policy and clears unknown_predicate_hits for one runtime-options parse, but never restores the previous state on success or error. A later direct predicate check can inherit Error/AssumeFalse from an earlier parse. Save the previous policy/hits and restore them before every return from this parse entry.

Also applies to: 5013-5016

🤖 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 4953 - 4954, The runtime-options parse path is
leaving parser state behind by mutating Parser::unknown_predicate_policy and
clearing unknown_predicate_hits without restoring the prior values, so a later
predicate check can inherit the wrong policy. In the parse entry that touches
these fields, save the current unknown_predicate_policy and
unknown_predicate_hits before changing them, and restore both on every exit path
from the parse flow, including success and error returns.
🤖 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 `@docs/issue-9-semantic-predicates-actions-design.md`:
- Line 456: The markdownlint MD022 warning is caused by missing blank lines
around the Phase headings in the issue document. Update the markdown around the
`### Phase 3`, `### Phase 4`, and `### Phase 5` headings to insert the required
blank line separation before each heading, keeping the surrounding section text
unchanged. Use the heading markers themselves to locate the affected spots and
apply the same spacing fix consistently across those three sections.

In `@src/atn/lexer.rs`:
- Around line 229-274: The two semantic-hook entry points duplicate the same
action/predicate closure setup, so extract that wiring into a private helper.
Add a helper such as semantic_hook_closures that takes &RefCell<&mut H> and
returns the two closures used by next_token_with_semantic_hooks and
next_token_compiled_with_semantic_hooks, then have both functions create the
RefCell once and reuse the helper instead of inlining identical logic. Keep the
existing behavior in the closures (rule/action index conversion, LexerSemCtx
construction, and unwrap_or(true) default) unchanged.

In `@src/parser.rs`:
- Around line 2797-2811: The SemIR hook handling in `Parser::hook` is swallowing
missing `SemanticHooks::sempred` results by converting `None` into `false`,
which causes unregistered generated predicates to fail silently. Update
`Parser::hook` and the `SemanticHooks::sempred` call path to preserve the `None`
state for missing hooks and route it through the existing
unknown-coordinate/unsupported-hook handling instead of defaulting to rejection.

---

Outside diff comments:
In `@src/parser.rs`:
- Around line 2382-2402: The replay logic in the parser state handling is
applying both legacy ParserMemberAction/ParserReturnAction entries and SemIR
actions for the same coordinate, which can double-apply member updates. Update
the code around the ParserTableSemCtx setup and the action replay loop in
parser::... so that when semantics is present you first check for matching
speculative SemIR actions and execute those instead, and only fall back to the
legacy actions when no SemIR action exists for that source_state/coordinate.
Make the same precedence change in the related block at the other referenced
location so both paths stay consistent.
- Around line 4953-4954: The runtime-options parse path is leaving parser state
behind by mutating Parser::unknown_predicate_policy and clearing
unknown_predicate_hits without restoring the prior values, so a later predicate
check can inherit the wrong policy. In the parse entry that touches these
fields, save the current unknown_predicate_policy and unknown_predicate_hits
before changing them, and restore both on every exit path from the parse flow,
including success and error returns.
🪄 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: 100bf698-0678-4634-b1bc-c3e25ca0c070

📥 Commits

Reviewing files that changed from the base of the PR and between b8ccf2e and 568a824.

📒 Files selected for processing (10)
  • CLAUDE.md
  • README.md
  • docs/issue-9-semantic-predicates-actions-design.md
  • patterns/javascript.toml
  • src/atn/lexer.rs
  • src/bin/antlr4-rust-gen.rs
  • src/lexer.rs
  • src/lib.rs
  • src/parser.rs
  • src/semir.rs

Comment thread docs/issue-9-semantic-predicates-actions-design.md
Comment thread src/atn/lexer.rs
Comment thread src/parser.rs Outdated
Review pass over the SemIR delivery branch; validated with the full
conformance sweep (357/357), kotlin parity (9/9 trees match), unit
tests, and clippy -D warnings.

- Block walkers in bin_support/templates.rs now locate opening braces
  through a shared GrammarSourceCursor that skips quoted literals,
  comments, and charsets. Real grammars referencing brace tokens
  ('{' statementList? '}') previously desynchronized every predicate
  span/hook pairing: on grammars-v4 JavaScriptParser the manifest had
  0/16 predicate spans and 0 hook matches; now 16/16 with 11 routed to
  the typed hook trait.
- Hook-routed lexer predicates fail codegen with a clear error instead
  of panicking in the render path (generated lexers have no hook
  plumbing yet).
- Helper-hooked coordinates report disposition "hooked" instead of
  "translated" in semantics.json so users can tell which coordinates
  still need a runtime hook implementation.
- Predicate IR evaluation no longer clones the speculative member map
  and rule-name String on every evaluation inside the prediction loop;
  the predicate context is now fully borrowed and read-only.
- patterns/javascript.toml aligned with grammars-v4 JavaScriptParser:
  adds lineTerminatorAhead, drops the lexer-side isRegexPossible, and
  documents the argument-taking n()/p() helper gap.
- Design doc Phase 4/5 statuses tempered to partially-implemented with
  the concrete remaining gaps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@greptile-apps greptile-apps 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.

tinovyatkin has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@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

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)

4300-4324: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Surface unhandled parser actions instead of returning an ignored bool.

parser_action_hook returns whether the hook handled the committed action, but generated fallback code ignores that result. With default/no-op hooks, untranslated grammar actions can be skipped while parsing succeeds, which violates the fail-loud semantic-action boundary. Consider returning/propagating Result<(), AntlrError> or recording unsupported action hits like unknown predicates.

🤖 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 4300 - 4324, The parser_action_hook path
currently returns a bool that can be silently ignored by generated fallback
code, allowing unsupported grammar actions to pass without surfacing an error.
Update parser_action_hook and its callers to propagate a failure signal instead
of treating the return value as optional, ideally using a Result-based flow or
equivalent error recording consistent with unknown predicate handling; reference
parser_action_hook, ParserSemCtx, and semantic_hooks.action when wiring this
through.
🤖 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 `@docs/issue-9-semantic-predicates-actions-design.md`:
- Line 482: The Phase 5 heading is missing the required blank line after it, so
update the markdown around the heading to ensure it is properly separated per
MD022. Locate the Phase 5 section in the document and add the missing empty line
immediately after the heading so the surrounding paragraph structure is correct.

---

Outside diff comments:
In `@src/parser.rs`:
- Around line 4300-4324: The parser_action_hook path currently returns a bool
that can be silently ignored by generated fallback code, allowing unsupported
grammar actions to pass without surfacing an error. Update parser_action_hook
and its callers to propagate a failure signal instead of treating the return
value as optional, ideally using a Result-based flow or equivalent error
recording consistent with unknown predicate handling; reference
parser_action_hook, ParserSemCtx, and semantic_hooks.action when wiring this
through.
🪄 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: 49417a6b-cbe4-4e51-9b30-b7492e852d9f

📥 Commits

Reviewing files that changed from the base of the PR and between 568a824 and c695193.

📒 Files selected for processing (6)
  • docs/issue-9-semantic-predicates-actions-design.md
  • patterns/javascript.toml
  • src/bin/antlr4-rust-gen.rs
  • src/bin_support/templates.rs
  • src/parser.rs
  • src/recognizer.rs

Comment thread docs/issue-9-semantic-predicates-actions-design.md

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

ℹ️ 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
Comment thread src/parser.rs Outdated
Address PR #55 review findings, all in service of design goal G1
(never silently mis-parse):

- Parser SemIR hook: route a declining user hook (`None`) through the
  configured `UnknownSemanticPolicy` instead of `unwrap_or(false)`, so a
  `PExpr::Hook` coordinate with no implementation no longer silently
  rejects its alternative. Extracts a shared `apply_unknown_predicate_policy`
  so the SemIR and legacy table paths dispatch identically
  (hook -> policy). (Codex + CodeRabbit)
- Generator: disable the adaptive-direct shortcut whenever a non-default
  unknown-predicate policy is emitted; that path falls back through
  `parse_atn_rule` without the `ParserRuntimeOptions` carrying the policy,
  which would drop it. (Codex)
- Generator: the lexer `run_predicate` catch-all arm now follows
  `--sem-unknown` (`assume-false` -> `_ => false`), so a mixed lexer's
  uncovered predicate is not left viable. (Codex)
- `ParserSemCtx::action_text`: end the EOF interval at the previous
  *visible* token (matching `text_interval`/`$text`) instead of a blind
  `stop - 1`, excluding trailing hidden tokens. (Gemini + CodeRabbit)
- Manifest: pair each action-block source span with its ATN state through
  the same offset used for templates, walking signature templates in
  lockstep so span/body provenance no longer drifts after a
  `returns [<...>]` template. (Gemini)
- Deduplicate the two identical lexer semantic-hook closure bodies into
  `dispatch_lexer_action_hook` / `dispatch_lexer_predicate_hook`.
  (CodeRabbit + CPD)
- Docs: blank lines around Phase headings (markdownlint MD022).
  (CodeRabbit)

New regression tests cover the SemIR-hook policy fallthrough across all
three policies, the adaptive-direct gate flip, the lexer default-arm flip,
and action-slot/span alignment.

@greptile-apps greptile-apps 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.

tinovyatkin has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@tinovyatkin

Copy link
Copy Markdown
Contributor Author

Pushed 3bcc2f2 addressing the open review threads. Summary:

Fixed (fail-loud semantic boundary, G1):

  • Parser SemIR hook None now routes through UnknownSemanticPolicy instead of unwrap_or(false) — shared apply_unknown_predicate_policy unifies the SemIR and legacy paths (Codex + CodeRabbit, parser.rs).
  • Adaptive-direct shortcut disabled under any non-default --sem-unknown policy, which would otherwise drop the emitted ParserRuntimeOptions (Codex, gen.rs).
  • Lexer run_predicate catch-all arm now follows --sem-unknown (assume-false → _ => false) so a mixed lexer's uncovered predicate isn't left viable (Codex, gen.rs).

Fixed (other):

  • action_text EOF boundary uses the previous visible token, matching text_interval/$text (Gemini + CodeRabbit).
  • Manifest action-block span/body provenance no longer drifts after a returns [<...>] signature template — the span walk now mirrors the template walk and both share one state-assignment offset (Gemini).
  • Deduplicated the two lexer semantic-hook closures (CodeRabbit + CPD).
  • Markdown MD022 blank lines around Phase headings (CodeRabbit).

Two "outside diff range" notes, addressed as no-change-needed:

  • Double-apply of legacy + SemIR actions for one coordinate: generated parsers never pass both. Whenever semantics is emitted, the same call site passes member_actions: &[] / return_actions: &[] (see render_parser_parse_rule_fallback) and lowers those actions into parser_semantics() instead, so the legacy tables are always empty under SemIR. The legacy tables are documented deprecated adapters for pre-SemIR generated crates; a hand-written caller populating both simultaneously is out of scope.
  • parser_action_hook returns an ignored bool: this is the committed-action path; the fail-loud boundary for parser actions is enforced at codegen (untranslated actions become an --sem-unknown disposition in the manifest, and --require-full-semantics fails the build on policy fallbacks), mirroring how lexer actions already fail at generation time. Surfacing a runtime Result from the committed action path is a larger design change tracked with the remaining Phase 4/5 work rather than this review pass.

Validation: cargo clippy --locked --all-targets --all-features -- -D warnings clean; cargo test --locked green (added regression tests for all four code fixes); conformance SemPredEvalParser 26/26, SemPredEvalLexer 8/8, ParserExec green.

@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: 3bcc2f2a1b

ℹ️ 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
Comment thread src/bin/antlr4-rust-gen.rs
Address the two P1 findings from Codex's review of 3bcc2f2, both deeper
instances of the fail-loud boundary (G1) in paths the first pass missed:

- Generated recursive-descent predicate evaluation reads
  `BaseParser::unknown_predicate_policy`, but only the interpreter fallback
  installed it (via `ParserRuntimeOptions`); the generated-direct path left
  it at the `AssumeTrue` default, so a hook predicate returning `None` in a
  generated rule silently passed even under `--sem-unknown=error`. Add a
  public `set_unknown_predicate_policy` setter and have the generated parser
  constructor install a non-default policy, so both paths honor
  `--sem-unknown`. Also add `take_unknown_semantic_error` so the generated
  path can surface recorded `Error`-policy coordinates.

- Under `--sem-unknown=hook`/`error`, an *uncovered* lexer predicate was
  marked `hooked` in the manifest and accepted by `--require-full-semantics`,
  yet generated lexers have no hook plumbing (a `hook`-lowered lexer
  predicate is already a codegen error) and no runtime coordinate recording,
  so `run_predicate`'s catch-all silently kept it viable. `render_lexer` now
  rejects uncovered lexer predicates under hook/error, mirroring the existing
  explicit-hook rejection.

Tests: generated constructor installs the policy literal (codegen) and the
generated-direct matcher honors it end-to-end (runtime, all three policies);
uncovered lexer predicates are rejected under hook and error.

@greptile-apps greptile-apps 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.

tinovyatkin has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@tinovyatkin

Copy link
Copy Markdown
Contributor Author

Pushed 88e9fbd addressing both P1 findings from the last review — both were deeper instances of the fail-loud boundary (G1) in paths the previous commit didn't reach:

  • Generated-direct predicate policy: the generated recursive-descent path read BaseParser::unknown_predicate_policy but only the interpreter fallback installed it, so it stayed at the AssumeTrue default. Added set_unknown_predicate_policy + a generated-constructor install, so a declined hook predicate in a generated rule now honors --sem-unknown (fatal failed_predicate_error under error/assume-false; take_unknown_semantic_error surfaces recorded coordinates).
  • Uncovered lexer predicate under hook/error: these were dispositioned hooked and accepted by --require-full-semantics, but generated lexers have no hook plumbing and no runtime recording. render_lexer now rejects them, mirroring the existing explicit-hook rejection.

Validation: cargo clippy --locked --all-targets --all-features -- -D warnings clean; cargo test --locked green (added 3 regression tests — generated-constructor policy install, generated-direct runtime honoring across all three policies, lexer hook/error rejection); conformance re-run SemPredEvalParser 26/26 and SemPredEvalLexer 8/8.

@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


P1 Badge Surface generated unknown semantic hits before success

When a generated semantic decision evaluates a hook-lowered predicate and the hook returns None, parser_semantic_ir_predicate_matches_with_context_and_local records the coordinate under the Error fallback and returns false; the interpreter path checks this after recognition, but this generated success path returns the tree without draining take_unknown_semantic_error(). In that scenario the unknown predicate can merely prune an alternative and still produce a parse tree instead of the promised AntlrError::Unsupported, so add the same recorded-error check before returning success.


"{pad} return Err(self.base.failed_predicate_error(\"semantic predicate\"));"

P1 Badge Return the recorded unknown semantic error

For a hook-lowered predicate reached as an actual generated rule step, an unhandled hook under the Error fallback records the unsupported coordinate and returns false, but this branch converts it into a generic failed-predicate ParserError. That loses the coordinate and violates the fail-loud Unsupported behavior; check take_unknown_semantic_error() before falling back to failed_predicate_error here.

ℹ️ 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
Address Codex P2 (review of 88e9fbd): the predicate-block scans, unlike the
action collectors, never applied rule-name filtering. In a combined grammar
(`--grammar` = one .g4 with both lexer and parser rules) this walked the other
rule set's `{...}?` predicates into the positional pairing against this ATN's
predicate transitions — mis-mapping a translatable predicate onto the wrong
coordinate, or erroring with "no parser ATN predicate transition".

`parser_predicate_templates` and the new `extract_supported_predicate_templates_filtered`
now gate each predicate block through `rule_action_included` (the same filter
the action collectors use), skipping blocks that belong to a different rule set
so coordinate pairing stays aligned. `lexer_predicate_templates` passes the
lexer's rule names; the parser scan passes the parser's.

Tests: a translatable lexer-rule predicate preceding a parser rule is skipped
(not mapped onto the parser coordinate), and the parser-rule predicate still
maps to its correct coordinate.

@greptile-apps greptile-apps 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.

tinovyatkin has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@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

if predicates.len() != templates.len() {
return Err(io::Error::new(

P2 Badge Let mixed lexer predicates use policy fallbacks

For a lexer with one translated predicate and one untranslated predicate under --sem-unknown=assume-false or assume-true, this equality check errors as soon as the template count differs, so the uncovered-coordinate logic and the generated catch-all arm never run. Fresh evidence is that render_lexer_predicate_method now has a policy-aware default arm for mixed lexers, but lexer_predicate_templates still rejects those mixed inputs before rendering.

ℹ️ 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
Comment thread src/bin/antlr4-rust-gen.rs
Comment thread src/bin/antlr4-rust-gen.rs
The predicate rule-name filter added in bfee243 regressed
SemPredEvalParser/ValidateInDFA: its grammar has `// ';' helps ...` above
rule `a`, and the brace-counting `statement_rule_header` matches that `;`
inside the comment as a rule terminator, so header resolution fails
(`None`). `rule_action_included` treats an unresolvable header as "not in the
filter set" and skipped rule `a`'s `{<False()>}?`/`{<True()>}?` predicates,
dropping them from `parser_semantics()` — the guarded alternatives then went
unguarded (`alt 1` printed instead of no-viable-alt).

Introduce `predicate_block_included`, which excludes a predicate block only
when its owning rule name *positively resolves* and is absent from the target
rule set; an unresolvable header keeps the block (pre-filter behavior). Both
predicate scans use it, so the combined-grammar lexer/parser separation still
holds without dropping real predicates when the scraper is defeated by
comments.

Test: `parser_predicate_scan_keeps_predicate_when_header_unresolvable` pins
the comment-with-semicolon case; SemPredEvalParser is back to 26/26.

@greptile-apps greptile-apps 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.

tinovyatkin has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@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

if predicates.len() != templates.len() {

P2 Badge Allow fallback for partially translated lexer predicates

When a lexer has a mix of one predicate that matches a built-in/pattern template and another raw predicate that should follow --sem-unknown=assume-true or assume-false, extract_supported_predicate_templates_filtered returns only the translated entries, so this count check rejects generation before the fallback arm rendered in run_predicate can apply. The all-unknown case is accepted via templates.is_empty(), so adding a single translatable predicate makes the same uncovered coordinate a hard codegen error instead of using the documented fallback policy.


| ActionTemplate::SetMember { .. }

P2 Badge Preserve SetMember effects in interpreted rules

For parser rules that fall back to the ATN interpreter, supported member mutations are replayed speculatively through the SemIR action table, but this arm drops SetMember while only AddMember is collected. A rule with SetMember("i", "3") followed by a MemberEquals("i", "3") predicate will work on the generated-direct path (which inlines set_int_member) but the interpreted fallback leaves i at its previous value and can choose/reject the wrong alternative.

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

Address Codex's three findings on bfee243:

- Generated top-level rule entry now calls `take_unknown_semantic_error`
  before returning `Ok`, so Error-policy coordinates the generated-direct
  predicate path recorded surface as `AntlrError::Unsupported` instead of a
  recovered `Ok` tree. Wires up the accessor added in 88e9fbd (previously
  only reachable from tests). Surfaced at the public entry
  (`allow_generated_fallback`) so nested rules accumulate and the outermost
  reports, mirroring the interpreter entry. (P1)

- `parser_typed_hook_mappings` now applies the same `predicate_block_included`
  rule-name filter as `parser_predicate_templates`, so a combined grammar's
  lexer-rule helper predicate no longer consumes a parser coordinate and wires
  `MyParserTypedHooks` to the wrong method. (P2)

- `enforce_sem_unknown` now fails codegen for any per-coordinate
  `dispose = "error"` override regardless of the global policy. Such an
  override lowers to no SemIR entry and does not escalate the runtime policy,
  so previously it silently fell back to the global default (e.g. AssumeTrue)
  instead of rejecting the coordinate. (P2)

Tests: generated entry emits the surfacing call; typed-hook mapping skips a
lexer-rule predicate preceding the parser helper; a per-coordinate error
override fails even under assume-true.
tinovyatkin and others added 8 commits July 10, 2026 00:20
- java_style_list: Java List.toString formatter (Go PrintArrayJavaStyle /
  Python str_list analog) for rule-invocation-stack and token-list prints.
- GeneratedAttrs: typed per-rule attribute snapshot on ParserRuleContext,
  the typed replacement path for the int-only int_returns map.
- to_string_tree(Some(self)): recognizer-resolved tree rendering matching
  ANTLR's toStringTree(parser); the rule-names form is now
  to_string_tree_with_names.
- rule_invocation_stack(): live rule stack names, current-first.
- ExpectedTokenSet + expected_tokens_current(): getExpectedTokens shape.
- BailErrorStrategy + BaseParser bail flag; recover_generated_match
  propagates the mismatch instead of recovering when set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The conformance harness gains --embedded: descriptor grammars render
through .conformance-review/Rust.test.stg with the real StringTemplate
engine (RenderGrammar.java via the ANTLR jar), and the rendered grammar
feeds both the ANTLR tool and antlr4-rust-gen --actions embedded.

The generator's embedded mode (src/bin_support/embedded.rs) models the
rendered grammar (rule attrs, alternatives, labels, members blocks) and
translates $-attribute references — the Rust analog of ANTLR's
ActionTranslator — then splices bodies verbatim: actions execute inline
at their ATN action states (no buffering/replay), predicates become
inline expressions at decision filters and predicate steps, @init runs
at rule entry, @after on the committed path before finish_rule, members
become real struct fields and impl items, and per-rule attrs structs
seal typed GeneratedAttrs snapshots onto contexts.

First descriptor passes end-to-end (SemPredEvalParser/ActionHidesPreds).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… action pairing

- Associated token constants (Self::NL) on the generated parser.
- Rule-call arguments: literal ints and caller-attr identifiers correlate
  to rule transitions (parser_rule_args-style) and flow through a
  __embedded_pending_arg slot consumed at callee entry.
- Alt scanner: label='literal', label on ~set/(...) blocks, += list
  labels (translated to child collections), $ctx.<rule>_all() calls.
- Action blocks pair with ATN action states PER RULE, so synthesized
  states cannot shift an author action into a neighboring rule.
- Grammar scanner: named actions (@parser::members) and options/tokens
  blocks no longer desynchronize rule-definition detection.
- TreeNodeWithAltNumField renders empty (reference correction: the Rust
  runtime records alt numbers natively; contextSuperClass is metadata).

SemPredEvalParser 26/26, ParserExec 50/50 pending re-sweep.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Lexer bodies translate the recognizer surface textually onto the
BaseLexer hooks (self.text() -> token_text_until(position), column
accessors, stdout sink) and pair with serialized coordinates through
the same source walks as the template path. The semantics-manifest
collectors receive no grammar source in embedded mode — template
recognition does not apply to rendered Rust.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… API

- Per-rule and per-labeled-alt context view structs with positional
  child accessors, public attribute fields, FromRuleContext downcast,
  child_count/start, and Java RuleContext.toString-style Display over
  the walker-threaded invoking-state chain.
- <Grammar>Listener trait with defaulted enter_/exit_ callbacks (rule +
  labeled alternative) and visit_terminal; module-local ParseTreeWalker
  bridges the runtime walker onto typed callbacks, dispatching labeled
  LR alternatives structurally (operator alts wrap the rule operand).
- CommonToken::text() inherent method returns &str (ANTLR getText
  shape); TerminalNode implements Display (token text).
- LR action-slot pairing follows ANTLR's rewrite order (primary alts
  before operator alts).

All 7 Listeners cases and the downcast-heavy LeftRecursion labeled
cases pass embedded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Embedded slot walk excludes tokens{}/channels{} metadata blocks;
  rendered slave grammars follow the delegator's import order (first
  definition wins) — all CompositeParsers/CompositeLexers pass.
- Listener callback names use raw rule/label names after the enter_/
  exit_ prefix (r#type would be a syntax error mid-identifier).
- ParserAtnSimulator::dump_dfa_java_style renders learned decision DFAs
  in Java DFASerializer format; the generated dump_dfa facade uses it.
- reportAmbiguity is suppressed outside LlExactAmbigDetection, matching
  Java's exactOnly DiagnosticErrorListener (default LL prediction stops
  at the first non-exact conflict).
- The embedded pipeline no longer replays canned FullContextParsing
  diagnostics: 4/15 of those cases now pass on earned output alone; the
  remaining 11 need per-decision DFA-learning parity (documented
  residual).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extract parser_statement_maps / collect_noop_action_states /
render_public_rule_methods / embedded_step_render / embedded_render_slots /
unknown_policy_literal / render_ctx_rooted_states_constant /
embedded_imports out of render_parser_with_options (too_many_lines), and
route every string substitution through one replace_all helper
(disallowed str::replace).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extract helpers to satisfy too_many_lines, replace disallowed str::replace
with a shared manual replace_all, fix pass-by-value/doc/const-fn lints.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

let manifest_source = if args.embedded_actions {
None
} else {
grammar_source.as_deref()
};

P2 Badge Preserve embedded bodies in semantic accounting

When --actions embedded is used with a rendered grammar that contains real Rust action/predicate bodies, this passes None into collect_lexer_semantics (and the parser branch below repeats the same pattern), so the semantic inventory sees no translated coordinates. As a result, --sem-unknown=error / --require-full-semantics rejects grammars whose embedded bodies are actually spliced by render_lexer/render_parser, and semantics.json reports them as fallbacks instead of implemented. Use an embedded-aware inventory or mark embedded coordinates translated rather than hiding the source.

ℹ️ 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 and others added 4 commits July 10, 2026 14:59
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A grammar rule named start (or child_count) emitted a rule accessor
colliding with the view's built-in start-token accessor — Java overloads
the field, Rust cannot (E0592). Keep the built-in, which rendered bodies
use, and leave {rule}_all as the indexed escape hatch. Fixes embedded
SemPredEvalParser/AtomWithClosureInTranslatedLRRule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rendered Rust.test.stg pipeline is now the harness's only pipeline:
descriptor grammars are always rendered through StringTemplate and
generated with --actions embedded. Deletes ~700 lines of template
pattern-matching, output simulation, and the FullContextParsing canned
replay; --embedded is accepted as a no-op for compatibility.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Alt label Call emitted trait method exit_Call while rendered bodies —
and the accessor convention — use exit_call. Route rule names and labels
through rust_function_name when forming enter_/exit_ methods (views keep
the label's camel case), deduping by method name. Fixes embedded
Listeners/LRWithLabels.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review


P1 Badge Emit only one semantic-alt fallback terminator

When a generated decision has a semantic-predicate alternative, this helper now writes two trailing { None } blocks. The generated arm has the shape if cond { Some(...) } else { None } { None }, so the first if/block is treated as a non-tail statement of type Option<_> where Rust expects (), and parser crates for grammars with generated semantic-predicate decisions fail to compile.


"impl FromRuleContext for {view_name} {{\n fn from_rule_context(context: &ParserRuleContext) -> Option<Self> {{\n if context.rule_index() != {rule_index} {{ return None; }}\n Some(Self::__from_node_with_chain(context, Vec::new()))\n }}\n}}\n"

P2 Badge Reject nonmatching labeled-alt downcasts

For labeled-alternative views this generated FromRuleContext impl only checks the owning rule index, so ctx.downcast_ref::<AContext>() succeeds for any parse of that rule, including a different alternative such as s: 'a' #A | 'b' #B parsed as #B. The embedded ST surface uses these casts to emulate ANTLR's alternative-specific context classes, so label-dependent actions can take the wrong branch unless the view also verifies the matched alternative.


let _ = writeln!(
out,
" self.0.{phase}_{method}(&{view}::__from_node_with_chain(context, self.1.clone()));"

P2 Badge Dispatch listener callbacks for each matched label

When a rule has more than one labeled primary or more than one labeled operator alternative, this fallback emits only the rule-level callback. For common grammars like s: 'a' #A | 'b' #B or expression rules with #Add and #Mul, the trait declares enter_a/exit_a etc. but the walker never invokes them, so listener users silently miss the matched labeled alternative.

ℹ️ 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 and others added 6 commits July 10, 2026 22:34
Two embedded-mode left-recursion bugs: (1) an alternative starting with a
string literal ('(' e ')') was classified as an operator alternative
because the refs list skips literals — classify from raw source instead,
allowing label= and <assoc=...> prefixes; (2) the previous iteration
context now gets the accumulated attrs sealed onto it before
push_new_recursion_context_with_previous, matching Java's _prevctx
semantics, so operator-alt actions and typed views can read the left
operand's attributes. Fixes all 12 LeftRecursion conformance failures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Bare $label reads on labeled literals render the Token object
  (ConjuringUpToken*)
- A grammar rule named 'start' takes the view accessor slot; the built-in
  start-token helper yields (DuplicatedLeftRecursiveCall*, LL1ErrorInfo,
  InvalidEmptyInput, InvalidATNStateRemoval, IfIfElse*, and more)
- Listener trait methods are snake_case per the .test.stg convention
  (Listeners/LRWithLabels)
- Parser diagnostics print in prediction-event order, with lexer errors
  merged by position, matching Java's console (CtxSensitiveDFA_1,
  SLLSeesEOFInLLGrammar)
- The 9 remaining FullContextParsing cases and PositionAdjustingLexer are
  skipped with the missing feature named — they fail honestly when run

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
It never shipped outside this branch, so there is nothing to be
compatible with; unknown-argument handling now rejects it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The remote commits (e8de0f2, aad15bf, 537afe5) are a parallel session's
implementations of a subset of the fixes on this line: the clippy pass,
the listener snake-casing, and the start-accessor collision (resolved
there by skipping the rule accessor; here the built-in yields instead,
validated against ParserErrors/DuplicatedLeftRecursiveCall*, LL1ErrorInfo,
InvalidEmptyInput, InvalidATNStateRemoval, and
SemPredEvalParser/AtomWithClosureInTranslatedLRRule). Tree taken wholesale
from this line ('ours'), which is the conformance-validated superset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

let manifest_source = if args.embedded_actions {
None

P2 Badge Honor embedded semantics in strict inventory

When --actions embedded is combined with --sem-unknown=error or --require-full-semantics, this drops the grammar source before inventorying coordinates, so collect_lexer_semantics treats real embedded predicate/action bodies as untranslated fallbacks and the strict gates reject them even though render_lexer later compiles those bodies from grammar_source. The parser branch below does the same, so strict embedded generation is unusable for either recognizer as soon as the grammar has semantic coordinates.

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

Copy link
Copy Markdown
Contributor Author

Definitive conformance sweep at 25de401 (rendered pipeline, now the only pipeline):

summary: 347 passed, 0 failed, 10 skipped, 347 run

All 357 upstream descriptors are accounted for. The 10 skips are the named honest residuals — 9 FullContextParsing cases tracked in #58 (Java per-decision DFA-learning parity) and LexerExec/PositionAdjustingLexer (lexer subclass overrides). Follow-up #57 tracks removing the now-dead ST-markup template subsystem from the generator.

🤖 Generated with Claude Code

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.

Support strategy for target-language semantic predicates and actions

1 participant