Skip to content

Add PairsBuilder to construct Pairs without a parser - #1194

Merged
tomtau merged 1 commit into
pest-parser:masterfrom
ChrisJr404:feat/pairs-builder
Aug 20, 2026
Merged

Add PairsBuilder to construct Pairs without a parser#1194
tomtau merged 1 commit into
pest-parser:masterfrom
ChrisJr404:feat/pairs-builder

Conversation

@ChrisJr404

@ChrisJr404 ChrisJr404 commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Closes #469.

Problem

Code that consumes parser output usually takes a Pair<'_, Rule> or Pairs<'_, Rule>. Unit-testing such code today means running a real parse first, just to get tokens of the right shape — which couples the test to the grammar and makes it awkward to exercise edge cases (a specific nesting, a particular tag) in isolation. #469 asks for a simple way to hand-build a Pair for exactly this.

What this adds

A PairsBuilder in pest::iterators that describes the expected token tree directly and turns it into real Pairs:

use pest::iterators::PairsBuilder;

let pairs = PairsBuilder::new("1+2")
    .rule_with(Rule::sum, 0, 3, |inner| {
        inner
            .rule(Rule::number, 0, 1).tag("lhs")
            .rule(Rule::number, 2, 3).tag("rhs")
    })
    .build();

let sum = pairs.peek().unwrap();
assert_eq!(sum.as_rule(), Rule::sum);
  • new(input) — start a builder over the input string.
  • rule(rule, start, end) — append a leaf pair spanning input[start..end] (same [start, end) byte convention as Span).
  • rule_with(rule, start, end, |inner| ...) — append a pair whose inner pairs are built by the closure.
  • tag(tag) — attach a node tag to the most recently appended pair (so find_tagged / as_node_tag work).
  • build() — flatten into the same Start/End queue the parser produces and return Pairs.

Because it reuses the existing queue representation, the resulting pairs behave exactly like parsed ones — as_str, as_span, line_col, into_inner, tokens, and the tag lookups all work unchanged. build() validates every span against the input (ascending range, on UTF-8 char boundaries), mirroring the invariant Span::new enforces, and panics with a descriptive message otherwise.

Notes

  • Purely additive — one new module, exported as pest::iterators::PairsBuilder; nothing else changes.
  • no_std-friendly (only alloc), consistent with the rest of the crate.
  • Tests cover leaves, multiple top-level pairs, nesting, tags, token round-tripping, line_col, multibyte spans, the empty builder, and the panic paths; docs include runnable examples. cargo fmt --check and cargo clippy are clean.

The method names / shape are of course open to bikeshedding — happy to adjust naming or the leaf-vs-rule_with split if you'd prefer different ergonomics.

Summary by CodeRabbit

  • New Features
    • Added a public builder for constructing parser rule pairs without parsing input.
    • Supports nested rules, tags, UTF-8 spans, empty inputs, and line/column calculations.
    • Provides depth-first token output through a simple fluent API.
    • Added documentation examples and coverage for common and invalid span scenarios.

@ChrisJr404
ChrisJr404 requested a review from a team as a code owner August 18, 2026 20:54
@ChrisJr404
ChrisJr404 requested review from tomtau and removed request for a team August 18, 2026 20:54
@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c5a56330-9b01-4602-9b0d-7a591524448e

📥 Commits

Reviewing files that changed from the base of the PR and between d9b29b6 and 3f5b700.

📒 Files selected for processing (2)
  • pest/src/iterators/mod.rs
  • pest/src/iterators/pairs_builder.rs

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

Adds and publicly exports PairsBuilder, which constructs leaf or nested Pairs with tags, validated UTF-8 spans, depth-first tokens, and line indexing. Tests cover construction, nesting, tags, empty input, multibyte spans, positions, and panic conditions.

Changes

PairsBuilder construction

Layer / File(s) Summary
Builder API and node model
pest/src/iterators/pairs_builder.rs
Defines PairsBuilder, internal node storage, and methods for rules, nested children, and tags.
Pair construction and export
pest/src/iterators/mod.rs, pest/src/iterators/pairs_builder.rs
Exports PairsBuilder and builds depth-first Pairs tokens with line indexing and UTF-8 span validation.
Builder behavior validation
pest/src/iterators/pairs_builder.rs
Tests leaves, nesting, token iteration, tags, empty builders, positions, multibyte spans, and panic conditions.

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

Merge Risk: ⚪ Minimal · up to 3f5b7

This additive change introduces a builder for constructing parser-like pairs without changing existing behavior; no actionable merge-blocking risk remains after normal checks and review.

Suggested reviewers: tomtau

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: adding PairsBuilder to construct Pairs without a parser.
Linked Issues check ✅ Passed PairsBuilder directly enables manual Pair construction for tests without invoking a parser, satisfying issue #469.
Out of Scope Changes check ✅ Passed The changes are limited to the requested builder, public re-export, documentation, and related tests.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@tomtau tomtau 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.

thanks! Could you rebase it on top of the latest master?

One other thing I'm thinking is that given 1. this is not used anywhere inside the pest crates, 2. it's meant for testing (without invoking the parser), would it make sense to feature-guard under a "test" feature flag? (I know fails_with and parses_to macros are kind of in the same category, but they are used within pest tests)

Testing code that consumes parser output (a function taking `Pair` or
`Pairs`) currently requires running a real parse just to obtain tokens of
the right shape, which couples such unit tests to the grammar.

`PairsBuilder` lets the expected token tree be described directly: each
pair is given a rule and a `[start, end)` span into the input, with inner
pairs added through a closure and node tags attached via `tag`. `build`
flattens the tree into the same `Start`/`End` queue the parser produces,
so the resulting `Pairs`/`Pair` behave identically (`as_str`, `line_col`,
`into_inner`, `tokens`, tag lookups). Spans are validated against the
input on `build`.

Closes pest-parser#469
@ChrisJr404

Copy link
Copy Markdown
Contributor Author

Rebased onto the latest master, just the one CI toolchain commit on top of the old base, no conflicts.

On the feature flag, good question. A couple of thoughts.

I'd steer away from #[cfg(test)] specifically: that only compiles during pest's own test builds, so downstream crates (which are the real audience here, testing their own code that consumes Pairs) would never see it, which defeats the point of #469.

A regular cargo feature that's off by default is the right mechanism if the goal is keeping it out of the default build. One caveat worth naming: cargo feature unification means that if a crate pulls pest in both as a normal dependency and as a dev-dependency with the feature on, the feature ends up enabled for the normal build too. So it isn't a hard guarantee, it just keeps the code out for anyone who never turns it on anywhere.

My weak preference is to leave it ungated, for two reasons: parses_to and fails_with already ship ungated in the same testing-helper category, and this is a small, purely additive, alloc-only module, so the weight it adds to the default build is tiny. That said I don't feel strongly. If you'd rather have it behind a non-default feature (something like builder or test-utils), say the word and I'll add it to this PR, it's a quick change.

@tomtau
tomtau merged commit f28de05 into pest-parser:master Aug 20, 2026
12 checks passed
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.

Add a simple way to manually generate Pair

2 participants