Skip to content

feat(runtime): support custom parser construction in parse drivers - #350

Merged
tinovyatkin merged 3 commits into
mainfrom
issue-349-typed-hook-entry-points
Aug 17, 2026
Merged

feat(runtime): support custom parser construction in parse drivers#350
tinovyatkin merged 3 commits into
mainfrom
issue-349-typed-hook-entry-points

Conversation

@tinovyatkin

@tinovyatkin tinovyatkin commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Closes #349.

Summary

  • add parse_with_parser_constructor and
    parse_stream_with_parser_constructor to the runtime-owned generated entry
    point surface
  • retain the concrete semantic-hook type in generated ParseOutput aliases,
    with NoSemanticHooks as the backward-compatible default
  • let constructor-aware outputs stay retained, convert through
    .into_parsed_file(), or cross the validated-tree boundary with .validate()
  • steer hook-requiring callers to the constructor-aware path in generated
    rustdoc and the JavaScript/TypeScript integration guides
  • correct Kotlin guide examples to read tree text through ParsedFile or the
    generated parser facade
  • exercise the default-hook constructor path through the checked-in TOML
    recognizer and route the JavaScript/TypeScript parity dumpers through the
    documented helper

The generated-code API remains revision 14. This is an additive extension to
the existing runtime macro expansion: generated source and its macro invocation
are unchanged, and revision 12-14 recognizers gain the helpers when linked
against the updated runtime.

Tests

  • cargo test --locked --workspace --all-features
  • cargo clippy --locked --workspace --all-targets --all-features -- -D warnings
  • cargo fmt --all -- --check
  • RUSTDOCFLAGS='-D warnings -A rustdoc::private-intra-doc-links' cargo doc --locked --workspace --all-features --no-deps
  • uv tool run rumdl==0.2.34 check README.md docs/*.md
  • cargo check --manifest-path tests/javascript-parity/dumper/Cargo.toml
  • cargo check --manifest-path tests/typescript-parity/dumper/Cargo.toml
  • tests/javascript-parity/run.sh ... (6/6 snippets match)
  • tests/typescript-parity/run.sh ... (5/5 snippets match)

The typed-hook generated-project fixture exercises text and stream inputs for
all three result modes: retained parser, owned ParsedFile, and validated tree.
The TOML test covers both new entry points in the in-repo coverage build.

Reviewer Notes

  • the original six entry points remain source-compatible; the default
    parse_stream_with_parser now delegates to the constructor-aware core with
    Parser::new
  • strict rustdoc without the targeted allow still stops on the pre-existing
    private link to GENERATED_RULE_STACK_CHECK_INTERVAL in parser.rs
  • Claude's first two exact-head reviews found no blockers; their directly
    related documentation, coverage, and parity suggestions are included in the
    second and third commits

Summary by CodeRabbit

  • New Features

    • Added parser entry points that support custom lexer and parser construction, including text- and stream-based inputs.
    • Added access to both parsed results and the parser instance for syntax-error inspection.
    • Added convenient conversion of parse results into validated parsed files and syntax trees.
    • Added support for semantic hooks during parsing.
  • Documentation

    • Added README examples covering custom parser construction, semantic hooks, error inspection, and stream parsing.
  • Tests

    • Expanded coverage for custom parser construction, parsing outputs, semantic hooks, and syntax-error handling.

Generated parse entry points always called Parser::new, which prevented grammars with typed semantic hooks from using the shared lexer-to-parser driver.

Add text and stream constructor-aware entry points that retain the concrete hooked parser in GeneratedParseOutput. Make generated ParseOutput aliases generic over the hook type with a backward-compatible default, and add an into_parsed_file conversion alongside validation.

Cover all parsed, validated, and retained-parser result modes with a hook-lowered superClass fixture, update API snapshots, and document the JavaScript and TypeScript hook paths. The generated-code API revision stays unchanged because the emitted macro invocation and generated-source/runtime contract are unchanged.

Closes #349
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown

Copy/Paste Detection

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

Show duplications

Found a 66 line (387 tokens) duplication in the following files:

  • Starting at line 26 of tests/javascript-parity/dumper/src/main.rs
  • Starting at line 26 of tests/typescript-parity/dumper/src/main.rs
use javascript_parser_base::JavaScriptParserBase;

fn dump_tree<S: AsRef<str>>(
    out: &mut dyn Write,
    tree: Node<'_>,
    rule_names: &[S],
    depth: usize,
) -> io::Result<()> {
    let pad = "  ".repeat(depth);
    match tree.kind() {
        NodeKind::Rule => {
            let rule = tree.as_rule().expect("rule node kind checked");
            let name = rule_names
                .get(rule.rule_index())
                .map_or("<?>", AsRef::as_ref);
            writeln!(
                out,
                "{pad}Rule({name}, children={})",
                rule.child_count()
            )?;
            for child in rule.children() {
                dump_tree(out, child, rule_names, depth + 1)?;
            }
        }
        NodeKind::Terminal => writeln!(
            out,
            "{pad}Term({:?})",
            tree.as_terminal().expect("terminal node kind checked").text()
        )?,
        NodeKind::Error => writeln!(
            out,
            "{pad}Err({:?})",
            tree.as_error().expect("error node kind checked").text()
        )?,
    }
    Ok(())
}

fn main() -> ExitCode {
    let mut args = env::args().skip(1);
    let mut input: Option<PathBuf> = None;
    let mut tokens_only = false;
    while let Some(arg) = args.next() {
        match arg.as_str() {
            "--input" => input = args.next().map(PathBuf::from),
            "--tokens" => tokens_only = true,
            other => {
                eprintln!("unknown argument: {other}");
                return ExitCode::from(2);
            }
        }
    }
    let Some(input) = input else {
        eprintln!("missing --input <path>");
        return ExitCode::from(2);
    };
    let source = match fs::read_to_string(&input) {
        Ok(source) => source,
        Err(error) => {
            eprintln!("failed to read {}: {error}", input.display());
            return ExitCode::FAILURE;
        }
    };

    if tokens_only {
        let lexer = JavaScriptLexer::with_typed_hooks(
```rust

---

Found a 25 line (126 tokens) duplication in the following files:
* Starting at line 93 of tests/javascript-parity/dumper/src/main.rs
* Starting at line 93 of tests/typescript-parity/dumper/src/main.rs

```rust
            JavaScriptLexerBase::with_strict_default(false),
        );
        let mut stream = CommonTokenStream::new(lexer);
        stream.fill();
        let errors = stream.drain_source_errors();
        if !errors.is_empty() {
            for error in errors {
                eprintln!("line {}:{} {}", error.line, error.column, error.message);
            }
            return ExitCode::FAILURE;
        }
        for token in stream.tokens() {
            if token.token_type() != TOKEN_EOF {
                println!(
                    "{}\t{}\t{:?}",
                    token.token_type(),
                    token.channel(),
                    token.text_or_empty()
                );
            }
        }
        return ExitCode::SUCCESS;
    }

    let output = match java_script_parser::parse_with_parser_constructor(

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

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

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

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

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

---

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

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

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

  • Starting at line 126 of tests/javascript-parity/dumper/src/main.rs
  • Starting at line 126 of tests/typescript-parity/dumper/src/main.rs
        JavaScriptParser::program,
    ) {
        Ok(output) => output,
        Err(error) => {
            eprintln!("parse failed: {error}");
            return ExitCode::FAILURE;
        }
    };
    let tree = output.result;
    let parser = output.parser;
    if parser.number_of_syntax_errors() != 0 {
        eprintln!(
            "parse produced {} syntax error(s)",
            parser.number_of_syntax_errors()
        );
        return ExitCode::FAILURE;
    }
    if let Err(error) = dump_tree(
        &mut io::stdout().lock(),
        parser.node(tree),
```rust

@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 027d9cd8-d2ef-4a9f-a066-537b0f021786

📥 Commits

Reviewing files that changed from the base of the PR and between 2d336ca and 96838be.

⛔ Files ignored due to path filters (4)
  • docs/javascript-build.md is excluded by !**/docs/**
  • docs/kotlin-build.md is excluded by !**/docs/**
  • docs/migration.md is excluded by !**/docs/**
  • docs/typescript-build.md is excluded by !**/docs/**
📒 Files selected for processing (5)
  • README.md
  • crates/antlr-rust-runtime/src/generated.rs
  • crates/antlr-rust-toml-parser/src/lib.rs
  • tests/javascript-parity/dumper/src/main.rs
  • tests/typescript-parity/dumper/src/main.rs

Included review availability: Your plan includes up to 1 review per rolling hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The runtime adds typed parser-constructor entry points for text and stream parsing. Parse outputs retain semantic-hook types and support conversion into ParsedFile. Tests, parity integrations, and documentation cover the new APIs.

Changes

Typed parser-constructor support

Layer / File(s) Summary
Parse-output contracts and conversion
crates/antlr-rust-runtime/src/generated.rs
Generated parse outputs now carry a semantic-hooks type and provide grammar-specific conversion into ParsedFile.
Constructor-aware parsing
crates/antlr-rust-runtime/src/generated.rs
Text and stream entry points accept caller-provided parser constructors. Existing stream parsing uses the new conversion path.
API coverage and integrations
crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/*, crates/antlr-rust-toml-parser/src/lib.rs, tests/*/dumper/src/main.rs, README.md
Tests and parity tools use constructor-aware parsing. Coverage includes typed hooks, validation, syntax errors, parse-tree access, and stream input. Documentation describes the new entry points.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: 🟡 Moderate · up to 96838

This PR changes generated parsing APIs, and formatting, workspace tests, and clippy still need confirmed results before merge. No concrete defect or failed check is identified, but the PR is not fully merge-ready until that validation is complete.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant ParserEntryPoint
  participant Lexer
  participant CommonTokenStream
  participant TypedParser
  Caller->>ParserEntryPoint: provide input, lexer, parser constructor, and entry rule
  ParserEntryPoint->>Lexer: lex input
  Lexer->>CommonTokenStream: provide tokens
  ParserEntryPoint->>TypedParser: construct parser with typed hooks
  ParserEntryPoint->>TypedParser: invoke entry rule
  TypedParser-->>Caller: return GeneratedParseOutput
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR adds parser-constructor closures for text and stream drivers, supports typed hooks, and adds coverage that satisfies issue #349.
Out of Scope Changes check ✅ Passed The documented runtime, test, consumer, and README changes directly support custom parser construction and typed-hook usage.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding custom parser construction support to runtime parse drivers.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-349-typed-hook-entry-points

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@claude

claude Bot commented Aug 17, 2026

Copy link
Copy Markdown

Code review — PR #350

Reviewing head 96838bed.

  • Gather context (diff vs origin/main, CLAUDE.md scope)
  • Summarize changes
  • CLAUDE.md compliance passes (×2) — no findings
  • Bug/logic passes (×2) — in progress
  • Local verification: cargo test -p antlr-rust-toml-parser --lib (14 passed, incl. the new constructor_entry_points_cover_the_checked_in_parser)
  • Local verification: typed-hook generated-project fixture + rustdoc link check
  • Validate candidate findings
  • Post consolidated review

View job run · branch issue-349-typed-hook-entry-points

@codspeed-hq

codspeed-hq Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 17 untouched benchmarks


Comparing issue-349-typed-hook-entry-points (96838be) with main (8b768a6)

Open in CodSpeed

@codecov

codecov Bot commented Aug 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.87500% with 2 lines in your changes missing coverage. Please review.

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

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown

📊 Source Code Metrics (this PR vs main)

File Cyclomatic Cognitive Functions LLOC MI
crates/antlr-rust-runtime/src/generated.rs 129 (main: 126) 🔴 33 ⚪ 61 (main: 60) 🔴 152 (main: 151) 🔴 0 ⚪
crates/antlr-rust-toml-parser/src/lib.rs 16 (main: 15) 🔴 1 ⚪ 14 (main: 13) 🔴 39 (main: 33) 🔴 21.55 (main: 24.14) 🔴

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

Address Claude's non-blocking review notes by routing Kotlin text access through ParsedFile or the parser facade, showing the current defaulted hook parameter in the migration alias, and linking the README hook example to the full JavaScript setup guide.
@tinovyatkin

Copy link
Copy Markdown
Contributor Author

Addressed all three optional documentation follow-ups in 09b1dd1c:

  • corrected the Kotlin guide's ParsedFile and NodeId text access examples
  • updated the migration alias example to include the defaulted hooks parameter
  • linked the README's Java parser-constructor example to the complete
    JavaScript typed-hook setup guide

The PR description is updated, and the exact-head checks/reviews are running
again.

Exercise the constructor-aware text and stream entry points through the checked-in TOML recognizer so runtime coverage observes the macro paths directly.

Route the JavaScript and TypeScript parity dumpers through the documented typed-hook driver, clarify diagnostics and conversion behavior, and keep the migration history distinct from the current widened alias.
@tinovyatkin

Copy link
Copy Markdown
Contributor Author

Addressed the five exact-head suggestions in 96838bed:

  • documented that into_parsed_file() consumes the retained parser and keeps
    recovered parses
  • added checked-in TOML coverage for both constructor-aware entry points,
    including .into_parsed_file() and .validate()
  • migrated the JavaScript and TypeScript parity dumpers to
    parse_with_parser_constructor
  • clarified where lexer diagnostics are handled with the constructor-aware
    driver
  • made the README example use the JavaScript names from the linked guide

I also restored the historical revision-11 alias spelling and moved the current
three-parameter alias into the current-runtime paragraph.

The explicit six-case generated-project fixture remains deliberate because it
keeps each text/stream and retained/parsed/validated behavior independently
observable. The older README destructuring example remains as the compatibility
example referenced by the migration notes, and custom token channels remain
out of scope for this issue.

Local verification includes the full workspace suite, exact CI clippy, both
standalone dumper builds, and full JS (6/6) plus TS (5/5) parity runs. The PR
description is current and the final exact-head checks are running.

@tinovyatkin
tinovyatkin merged commit b371dcd into main Aug 17, 2026
13 of 14 checks passed
@tinovyatkin
tinovyatkin deleted the issue-349-typed-hook-entry-points branch August 17, 2026 23:01
@ophiarch ophiarch Bot mentioned this pull request Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generated entry points can't install typed hooks — superClass grammars are locked out of the 0.33 parse drivers

1 participant