Skip to content

Release 0.5.1#33

Merged
siy merged 26 commits into
mainfrom
release-0.5.1
May 9, 2026
Merged

Release 0.5.1#33
siy merged 26 commits into
mainfrom
release-0.5.1

Conversation

@siy

@siy siy commented May 9, 2026

Copy link
Copy Markdown
Owner

Summary

Release 0.5.1 of peglib — context-independent trivia attribution + StringSpan + perf cleanup arc. 26 commits past main.

Headline outcomes

Trivia rework — long-standing issue closed:

  • triviaPostPass=true is the new default. Context-independent attribution by re-derivation from (input, span) coordinates.
  • parseRuleAt now produces structurally identical body subtree to full reparse (Lever B trivia-context-loss blocker RESOLVED).
  • 5 historical bugs (A through C''') and the 0.5.0 rebasing all preserved as the regression net.

Perf wins on the GC-bound workload:

Fixture Buffer-driven (legacy) Post-pass + StringSpan Δ
Reference (1900 LOC) 19.08 ms 24.88 ms +30% (intrinsic; bounded; affects no real workload)
Selfhost (37k LOC) 825.6 ms 784.7 ms -5% (perf-critical workload now faster than legacy)

StringSpan added (org.pragmatica.peg.tree.StringSpan) — CharSequence view with lazy String materialization. Eliminates substring/byte[] allocation on token capture hot path. New public API; CstNode.Terminal/Token internals migrated.

Engineering arc

8 ships, 4 reverts, 1 deferral across the cleanup arc. All bench-gated; no silent regressions:

  • ✅ Step 4 commits 1-7 — trivia rework foundation, splice-offset, buffer no-op, default flip, Bug C' fix
  • ✅ O(n²) computeSpan fix (line-start table) — 15× / 210× speedup post-default-flip
  • ✅ Cleanup A — strip dead buffer call sites
  • ✅ Cleanup B — thread orphan trailing
  • ✅ Cleanup D — skip rebuild when no trivia change
  • ✅ Cleanup F.1/F.2/F.3 — StringSpan multi-commit (runtime → CharSequence widening → generator)
  • ❌ Cleanup G.1 prefilter, G.2 alloc tightening — micro-optimizations below noise floor
  • ❌ Lever B retry — SafePivotAnalyzer's literal-prefix criterion incompatible with Java25 grammar (+338% Regime B median)
  • ⏸ Skip-postPass-for-full-parses — academic; no real workload benefits

Test counts

  • peglib-core: 805 tests + 1 pre-existing skip
  • peglib-incremental: 125 tests
  • peglib-formatter: 66 tests
  • peglib-maven-plugin: 5 tests
  • peglib-playground: 27 tests
  • Total: 1028 tests, 1 skipped, 0 failures

Breaking changes

  • CstNode.Terminal and CstNode.Token record components changed: text: StringtextSpan: StringSpan. Public .text(): String accessor preserved (lazy materialization). Pattern-matchers using case Terminal t -> t.text() continue to work; component-name deconstruction does not (none found in audit).
  • triviaPostPass default flipped to true. Legacy buffer-driven attribution available via explicit new ParserConfig(..., triviaPostPass=false, ...).
  • CstParseResult.text (generator-emitted) widened from String to CharSequence. Internal type; no public API impact.

Test plan

  • Full reactor mvn install green — local m2 fresh
  • RoundTripTest 22/22 under flag-ON (correctness gate)
  • Phase1ParityTest 22/22 (legacy attribution still works)
  • Bench A/B selfhost faster than legacy
  • StringSpanTest 30 cases green
  • Lever B retry attempted, reverted on bench evidence (no perf regression shipped)

Docs

  • HANDOVER.md §11 — full arc summary including reverts and lessons
  • CHANGELOG.md [0.5.1] section — release notes
  • Move B post-mortem still in docs/incremental/THROUGHPUT-ENGINE-MOVE-B.md

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added public API for post-parse trivia reattribution enabling enhanced input reconstruction
    • New parser configuration flag providing control over trivia attribution strategy
    • Optimized memory usage with deferred string materialization
  • Bug Fixes

    • Fixed orphan trivia placement during parse tree reconstruction
    • Resolved parser performance regression
  • Documentation

    • Updated project handover documentation with detailed technical investigation results

siy added 26 commits May 9, 2026 06:20
…ingSpan, .text() returns String via lazy materialization (Cleanup F.1)
@coderabbitai

coderabbitai Bot commented May 9, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This release (0.5.1) implements context-independent trivia reattribution via a new TriviaPostPass class and deferred StringSpan views. When enabled (new default), the parser defers trivia slot assignment to a post-parse phase instead of inline buffering, with splice-aware variants for incremental parsing. Extensive test coverage validates interpreter/generated parity, round-trip reconstruction, and adversarial edge cases.

Changes

Trivia Post-Pass Attribution Rework

Layer / File(s) Summary
Version & POM Bumps
pom.xml, peglib-*/pom.xml
All Maven module versions bumped from 0.5.0 to 0.5.1 across parent and child POMs.
Release Documentation
CHANGELOG.md, docs/HANDOVER.md, docs/incremental/TRIVIA-ADVERSARIAL-FINDINGS.md
CHANGELOG documents 0.5.1 features (TriviaPostPass API, triviaPostPass flag, StringSpan, fixes, benchmarks); HANDOVER.md extended with complete trivia investigation arc, production implementation steps, performance outcomes, and deferred optimizations; FINDINGS.md reports Step 2 adversarial test results.
Data Types: StringSpan & CstNode
peglib-core/src/main/java/org/pragmatica/peg/tree/StringSpan.java, peglib-core/src/main/java/org/pragmatica/peg/tree/CstNode.java
New StringSpan CharSequence type defers substring materialization with lazy cache; Terminal and Token refactored to use StringSpan textSpan instead of materialized String text, preserving text() accessor for compatibility.
Configuration & Public API
peglib-core/src/main/java/org/pragmatica/peg/parser/ParserConfig.java, peglib-core/src/main/java/org/pragmatica/peg/PegParser.java, peglib-core/src/main/java/org/pragmatica/peg/tree/TriviaPostPass.java
ParserConfig adds boolean triviaPostPass field (default true); PegParser.Builder adds triviaPostPass(boolean) method. New public TriviaPostPass class provides assignTrivia() and scanWhitespace() entry points for context-independent trivia reattribution.
Trivia Buffering: ParsingContext Gating
peglib-core/src/main/java/org/pragmatica/peg/parser/ParsingContext.java
Adds substringSpan() for deferred StringSpan views. When triviaPostPass enabled, pending-leading trivia methods become conditional no-ops, deferring all slot assignment to post-pass phase.
Engine Core: Dual-Mode Trivia Flow
peglib-core/src/main/java/org/pragmatica/peg/parser/PegEngine.java
Caches triviaPostPass flag. When enabled, parseCst() and parseRuleAt() wrap results in applyTriviaPostPass(). Rule-parsing pending-leading buffers, snapshots, and whitespace-to-pending accumulation conditionally gated. Terminal/token CST construction uses StringSpan textSpan when post-pass enabled.
Incremental: TreeSplicer & TriviaRedistribution
peglib-incremental/src/main/java/org/pragmatica/peg/incremental/internal/TreeSplicer.java, peglib-incremental/src/main/java/org/pragmatica/peg/incremental/internal/TriviaRedistribution.java
TreeSplicer.shiftAll updates Token reconstruction to shift textSpan instead of unshifted text. TriviaRedistribution uses textSpan() when recreating Terminal/Token nodes in splice-offset and rebuild-trivia paths.
Benchmarking: JMH Configuration
peglib-core/src/jmh/java/org/pragmatica/peg/bench/Java25ParseBenchmark.java, peglib-core/src/jmh/java/org/pragmatica/peg/bench/PackratStatsProbe.java
Java25ParseBenchmark adds phase1_allStructural_mutableResult_autoSkipPackrat_postPass variant. allOff() baseline explicitly sets triviaPostPass=false. withStructural() overloads refactored to accept triviaPostPass.
Tests: Parity Baselines
peglib-core/src/test/java/org/pragmatica/peg/grammar/LeftRecursionTest.java, peglib-core/src/test/java/org/pragmatica/peg/perf/Phase*ParityTest.java
Phase-1 and Phase-2 parity tests updated to explicitly set triviaPostPass=false to maintain baseline hash comparisons. All phase tests standardized with both tokenFastPath and triviaPostPass configuration.
Tests: Generated Parser & Flag Validation
peglib-core/src/test/java/org/pragmatica/peg/generator/GeneratedParserTriviaPostPassTest.java
Validates generated parser source emission, flag-ON/OFF behavior, standalone-import invariants, trivia capture correctness, and parity between generated and interpreter parsers via runtime compilation and reflection.
Tests: TriviaPostPass Round-Trip & Reconstruction
peglib-core/src/test/java/org/pragmatica/peg/perf/TriviaPostPassFlagTest.java
Validates byte-exact round-trip reconstruction under flag-on, total trivia text preservation, trivia attribution shape divergence between flag modes, splice-offset behavior, and buffer no-op semantics.
Tests: Corpus & parseRuleAt Parity
peglib-core/src/test/java/org/pragmatica/peg/tree/TriviaPostPassTest.java
Validates post-pass reconstruction against corpus fixtures, structural divergence patterns, adversarial parity across hand-crafted grammars, and parseRuleAt parity under post-pass via span text equivalence.
Tests: Splice-Offset Window Semantics
peglib-core/src/test/java/org/pragmatica/peg/tree/TriviaPostPassSpliceOffsetTest.java
Validates the 4-argument assignTrivia(input, cst, grammar, leadingScanFrom) overload, leading-trivia scan window constraints, delegate parity, edge-case behavior, and correct splice-gap attribution.
Tests: StringSpan CharSequence
peglib-core/src/test/java/org/pragmatica/peg/tree/StringSpanTest.java
Validates StringSpan construction, CharSequence operations, materialization idempotence, equality/hash code rules, round-trip correctness, and accessor methods.
Tests: Adversarial Divergence Targets
peglib-core/src/test/java/org/pragmatica/peg/perf/TriviaAdversarialTest.java
Covers seven divergence targets via nested test groups: backtracking, cache hit/miss parity, predicate state symmetry, parseRuleAt context-loss, optional/cut asymmetry, whitespace-purity invariant, and property-based fuzzing.

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly Related PRs

  • siy/java-peglib#13: Both PRs modify ParserConfig and its defaults, introducing new boolean flags consumed by parser/generator; the main PR's triviaPostPass change is related to the earlier perf-rework PR's ParserConfig work.
  • siy/java-peglib#21: Both PRs modify PegEngine's parseRuleAt-related parsing flow; the retrieved PR adds the parseRuleAt API/implementation, and the main PR updates PegEngine's parseRuleAt and its trivia handling/post-pass integration.
  • siy/java-peglib#25: Both PRs modify core parsing classes (PegEngine, ParsingContext); the main PR adds triviaPostPass/StringSpan behavior while the retrieved PR refactors caches/Option usage in the same files.

Poem

🐰 A whisker-twitch of context swept away,
StringSpans defer their strings to brighter day,
Post-pass trivia flows through leaf and tree,
Spliced subtrees stay in harmony!
No more buffer-bound, the parser runs so free!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.16% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Release 0.5.1' concisely summarizes the primary change—a version release with major trivia rework, StringSpan introduction, and performance improvements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release-0.5.1

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

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

🧹 Nitpick comments (5)
docs/HANDOVER.md (1)

690-690: ⚡ Quick win

Final summary line is extremely dense.

Line 690 is a single paragraph containing:

  • Branch and commit count
  • Test counts (805 + 1 skipped for peglib-core)
  • Trivia rework state
  • Cumulative selfhost results (-5%)
  • Reference fixture results (+30%)
  • StringSpan addition
  • Move B post-mortem reference
  • Three next-session options

Consider splitting this into a bulleted list or multiple paragraphs for readability. The information is valuable but difficult to parse in a single 250+ word block.

♻️ Suggested format improvement
- Branch `release-0.5.1` at `0b29c78` — 20 commits past 0.5.1 base. peglib-core 805 tests + 1 pre-existing skipped, all green at the new default (post-pass + StringSpan). **Trivia rework + cleanup state: SHIPPED + bench-validated.** Cumulative selfhost wallclock now **-5% under legacy** (was at parity); reference fixture +30% over legacy (per-NonTerminal scan dominates; future work). 30 new StringSpanTest cases + StringSpan added as `org.pragmatica.peg.tree.StringSpan` (CharSequence view with lazy String materialization). Move B post-mortem: [`docs/incremental/THROUGHPUT-ENGINE-MOVE-B.md`](incremental/THROUGHPUT-ENGINE-MOVE-B.md) §11. Next session: Lever B retry (fallback-rule-bypass blocker remains; trivia-context-loss blocker is RESOLVED) OR Lever C IR unification OR address reference-fixture per-NonTerminal scan cost (cache scanWhitespace, skip NonTerminals with full-coverage children, batch gap-scans).
+**Branch state:** `release-0.5.1` at `0b29c78` — 20 commits past 0.5.1 base.
+
+**Test status:** peglib-core 805 tests + 1 pre-existing skipped, all green at the new default (post-pass + StringSpan).
+
+**Trivia rework + cleanup state: SHIPPED + bench-validated.**
+- Cumulative selfhost wallclock: **-5% under legacy** (was at parity)
+- Reference fixture: +30% over legacy (per-NonTerminal scan dominates; future work)
+- 30 new StringSpanTest cases
+- StringSpan added as `org.pragmatica.peg.tree.StringSpan` (CharSequence view with lazy String materialization)
+
+**Move B post-mortem:** [`docs/incremental/THROUGHPUT-ENGINE-MOVE-B.md`](incremental/THROUGHPUT-ENGINE-MOVE-B.md) §11
+
+**Next session options:**
+- Lever B retry (fallback-rule-bypass blocker remains; trivia-context-loss blocker is RESOLVED)
+- Lever C IR unification
+- Address reference-fixture per-NonTerminal scan cost (cache scanWhitespace, skip NonTerminals with full-coverage children, batch gap-scans)
🤖 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 `@docs/HANDOVER.md` at line 690, The final summary paragraph (the dense block
mentioning Branch `release-0.5.1` at `0b29c78`, peglib-core test counts, Trivia
rework, cumulative selfhost -5%, reference fixture +30%, addition of StringSpan
/ org.pragmatica.peg.tree.StringSpan, and the Move B post-mortem reference
THROUGHPUT-ENGINE-MOVE-B.md plus three next-session options) is too dense; split
it into multiple short paragraphs or a bulleted list so each item is clear:
e.g., one line for branch/commit and test counts, one for Trivia/StringSpan
changes (mention StringSpan class), one for performance results (selfhost and
reference fixture), one linking the Move B post-mortem
(THROUGHPUT-ENGINE-MOVE-B.md), and one listing the three next-session options as
separate bullets.
peglib-core/src/test/java/org/pragmatica/peg/perf/TriviaAdversarialTest.java (2)

656-661: 💤 Low value

Unused helper method.

The firstChild helper is defined but never called within this file. Consider removing it to avoid dead code.

🤖 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 `@peglib-core/src/test/java/org/pragmatica/peg/perf/TriviaAdversarialTest.java`
around lines 656 - 661, The helper method firstChild(CstNode node) is dead code
and should be removed to avoid unused methods; delete the private static method
firstChild that references CstNode and CstNode.NonTerminal (and any imports only
used by it) from TriviaAdversarialTest.java, or if it was intended to be used,
replace its removal by adding calls to firstChild where appropriate—prefer
removing the method if there are no call sites.

690-708: 💤 Low value

Potential resource leak: URLClassLoader not closed.

The URLClassLoader created at line 706 is never closed. While acceptable in test code since the JVM will reclaim resources, consider using try-with-resources for cleanliness, especially if tests are run repeatedly.

♻️ Optional: Use try-with-resources pattern
-    private static Class<?> compileAndLoad(String source, String className) throws Exception {
+    private static Class<?> compileAndLoad(String source, String className) throws Exception {
         Path tempDir = Files.createTempDirectory("peglib-adv-test");
         var packagePath = className.substring(0, className.lastIndexOf('.')).replace('.', '/');
         var simple = className.substring(className.lastIndexOf('.') + 1);
         var pkgDir = tempDir.resolve(packagePath);
         Files.createDirectories(pkgDir);
         var srcFile = pkgDir.resolve(simple + ".java");
         Files.writeString(srcFile, source);
         var compiler = ToolProvider.getSystemJavaCompiler();
         var rc = compiler.run(null, null, null,
             "-d", tempDir.toString(),
             "-cp", System.getProperty("java.class.path"),
             srcFile.toString());
         if (rc != 0) {
             throw new RuntimeException("compile failed: " + className);
         }
-        var cl = new URLClassLoader(new URL[]{tempDir.toUri().toURL()});
-        return cl.loadClass(className);
+        // Note: Closing the loader would unload the class; keep open intentionally
+        `@SuppressWarnings`("resource")
+        var cl = new URLClassLoader(new URL[]{tempDir.toUri().toURL()});
+        return cl.loadClass(className);
     }
🤖 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 `@peglib-core/src/test/java/org/pragmatica/peg/perf/TriviaAdversarialTest.java`
around lines 690 - 708, The URLClassLoader instance created in compileAndLoad is
never closed; update compileAndLoad to ensure the URLClassLoader (variable cl)
is closed after loading the compiled class—e.g., use a try-with-resources around
the URLClassLoader or call cl.close() after obtaining the Class (via
cl.loadClass or Class.forName with the loader) so the class is loaded and the
loader is properly closed before returning.
peglib-core/src/test/java/org/pragmatica/peg/tree/TriviaPostPassSpliceOffsetTest.java (1)

270-305: 💤 Low value

Duplicated helper methods across test files.

The reconstruct, emitBody, emitLeading, emitTrailing helpers are duplicated across TriviaPostPassTest, TriviaPostPassSpliceOffsetTest, and TriviaPostPassFlagTest. Consider extracting these to a shared test utility class if the pattern persists.

🤖 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
`@peglib-core/src/test/java/org/pragmatica/peg/tree/TriviaPostPassSpliceOffsetTest.java`
around lines 270 - 305, Extract the duplicated helpers reconstruct, emitBody,
emitLeading, and emitTrailing into a shared test utility class (e.g.,
TriviaTestUtils) as static methods, move the existing implementations from
TriviaPostPassTest, TriviaPostPassSpliceOffsetTest, and TriviaPostPassFlagTest
into that single class, then update those test classes to call
TriviaTestUtils.reconstruct(...) and the other static helpers instead of
defining their own copies; ensure method signatures remain the same (accepting
CstNode and StringBuilder where used) and update imports accordingly.
peglib-core/src/main/java/org/pragmatica/peg/parser/PegEngine.java (1)

325-329: 💤 Low value

Minor inconsistency: use cached triviaPostPass field instead of config.triviaPostPass().

The comment at lines 41-45 explicitly states the intent to cache the flag to avoid re-reading config, but this method reads from config directly. While not a bug (config is immutable), using the cached field maintains consistency.

♻️ Suggested fix
     private CstNode applyTriviaPostPass(String input, CstNode root, int leadingScanFrom) {
-        if (!config.triviaPostPass()) {
+        if (!triviaPostPass) {
             return root;
         }
         return TriviaPostPass.assignTrivia(input, root, grammar, leadingScanFrom);
     }
🤖 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 `@peglib-core/src/main/java/org/pragmatica/peg/parser/PegEngine.java` around
lines 325 - 329, The method applyTriviaPostPass reads the flag from config
directly; replace that call with the cached boolean field triviaPostPass to
follow the class intent—change the if-check to use triviaPostPass and keep the
rest of the method (calling TriviaPostPass.assignTrivia(input, root, grammar,
leadingScanFrom)) unchanged so the cached value is used consistently instead of
config.triviaPostPass().
🤖 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 `@CHANGELOG.md`:
- Around line 8-10: The changelog entry for the 0.5.1 release shows a dated
header "## [0.5.1] - 2026-05-09" but still contains the line "_Unreleased —
patch cycle following 0.5.0._"; remove or replace that line so the dated section
no longer includes the "Unreleased" label (edit the paragraph under the header
"## [0.5.1] - 2026-05-09" to reflect a released state or remove the sentence
entirely).

In `@docs/HANDOVER.md`:
- Around line 600-603: Update the user-facing documentation to call out the
35.8% regression on small fixtures and give explicit guidance for users: add an
entry to CHANGELOG.md and migration notes summarizing the regression and that
the new behavior is the default but can be disabled via the ParserConfig
constructor (reference ParserConfig and the legacy attribution option),
recommend opt-out guidance such as "use legacy attribution for one-shot parses
under ~2,000 LOC" (or a clarified threshold), and add a short user-facing
paragraph explaining when to prefer the legacy mode vs new default so users
parsing small inputs won't be surprised.

In `@docs/incremental/TRIVIA-ADVERSARIAL-FINDINGS.md`:
- Around line 72-77: Add a language tag to the fenced grammar block so Markdown
lint rule MD040 is satisfied and syntax highlighting improves: update the
untyped fence around the grammar containing Start, Pair, Triple and %whitespace
to include the language identifier (e.g., "peg") immediately after the opening
triple backticks.

In `@peglib-core/src/main/java/org/pragmatica/peg/PegParser.java`:
- Around line 246-247: The builder's default for the instance field
triviaPostPass in PegParser currently initializes it to false causing
PegParser.builder(...).build() to diverge from the historical default (post-pass
enabled); update the builder's initialization so triviaPostPass defaults to true
(or otherwise match the release default) and ensure any related builder
docs/comments (the "Default: off" text near the builder) are updated to reflect
the new default; check and adjust usages/fields referenced in PegParser (e.g.,
the triviaPostPass field and the builder methods that set it) so the built
parser behavior matches the 0.5.1 default.

In `@peglib-core/src/main/java/org/pragmatica/peg/tree/TriviaPostPass.java`:
- Around line 671-688: The parser silently fails when a Repetition has invalid
bounds (min > max); add a validation step in Grammar.validate() that iterates
Repetition expressions and checks rep.min() <= rep.max(). If the check fails,
raise/return a clear semantic error referencing the offending Repetition
(include min and max in the message) so invalid bounds like {5,1} are rejected
at grammar validation rather than causing silent match failures in
matchRepetition.

---

Nitpick comments:
In `@docs/HANDOVER.md`:
- Line 690: The final summary paragraph (the dense block mentioning Branch
`release-0.5.1` at `0b29c78`, peglib-core test counts, Trivia rework, cumulative
selfhost -5%, reference fixture +30%, addition of StringSpan /
org.pragmatica.peg.tree.StringSpan, and the Move B post-mortem reference
THROUGHPUT-ENGINE-MOVE-B.md plus three next-session options) is too dense; split
it into multiple short paragraphs or a bulleted list so each item is clear:
e.g., one line for branch/commit and test counts, one for Trivia/StringSpan
changes (mention StringSpan class), one for performance results (selfhost and
reference fixture), one linking the Move B post-mortem
(THROUGHPUT-ENGINE-MOVE-B.md), and one listing the three next-session options as
separate bullets.

In `@peglib-core/src/main/java/org/pragmatica/peg/parser/PegEngine.java`:
- Around line 325-329: The method applyTriviaPostPass reads the flag from config
directly; replace that call with the cached boolean field triviaPostPass to
follow the class intent—change the if-check to use triviaPostPass and keep the
rest of the method (calling TriviaPostPass.assignTrivia(input, root, grammar,
leadingScanFrom)) unchanged so the cached value is used consistently instead of
config.triviaPostPass().

In
`@peglib-core/src/test/java/org/pragmatica/peg/perf/TriviaAdversarialTest.java`:
- Around line 656-661: The helper method firstChild(CstNode node) is dead code
and should be removed to avoid unused methods; delete the private static method
firstChild that references CstNode and CstNode.NonTerminal (and any imports only
used by it) from TriviaAdversarialTest.java, or if it was intended to be used,
replace its removal by adding calls to firstChild where appropriate—prefer
removing the method if there are no call sites.
- Around line 690-708: The URLClassLoader instance created in compileAndLoad is
never closed; update compileAndLoad to ensure the URLClassLoader (variable cl)
is closed after loading the compiled class—e.g., use a try-with-resources around
the URLClassLoader or call cl.close() after obtaining the Class (via
cl.loadClass or Class.forName with the loader) so the class is loaded and the
loader is properly closed before returning.

In
`@peglib-core/src/test/java/org/pragmatica/peg/tree/TriviaPostPassSpliceOffsetTest.java`:
- Around line 270-305: Extract the duplicated helpers reconstruct, emitBody,
emitLeading, and emitTrailing into a shared test utility class (e.g.,
TriviaTestUtils) as static methods, move the existing implementations from
TriviaPostPassTest, TriviaPostPassSpliceOffsetTest, and TriviaPostPassFlagTest
into that single class, then update those test classes to call
TriviaTestUtils.reconstruct(...) and the other static helpers instead of
defining their own copies; ensure method signatures remain the same (accepting
CstNode and StringBuilder where used) and update imports accordingly.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: b3cbcc5e-3729-4287-9675-f562bf7087c7

📥 Commits

Reviewing files that changed from the base of the PR and between abfd00c and 83f2c3e.

📒 Files selected for processing (36)
  • CHANGELOG.md
  • docs/HANDOVER.md
  • docs/incremental/TRIVIA-ADVERSARIAL-FINDINGS.md
  • peglib-core/pom.xml
  • peglib-core/src/jmh/java/org/pragmatica/peg/bench/Java25ParseBenchmark.java
  • peglib-core/src/jmh/java/org/pragmatica/peg/bench/PackratStatsProbe.java
  • peglib-core/src/main/java/org/pragmatica/peg/PegParser.java
  • peglib-core/src/main/java/org/pragmatica/peg/generator/ParserGenerator.java
  • peglib-core/src/main/java/org/pragmatica/peg/parser/ParserConfig.java
  • peglib-core/src/main/java/org/pragmatica/peg/parser/ParsingContext.java
  • peglib-core/src/main/java/org/pragmatica/peg/parser/PegEngine.java
  • peglib-core/src/main/java/org/pragmatica/peg/tree/CstNode.java
  • peglib-core/src/main/java/org/pragmatica/peg/tree/StringSpan.java
  • peglib-core/src/main/java/org/pragmatica/peg/tree/TriviaPostPass.java
  • peglib-core/src/test/java/org/pragmatica/peg/generator/GeneratedParserTriviaPostPassTest.java
  • peglib-core/src/test/java/org/pragmatica/peg/grammar/LeftRecursionTest.java
  • peglib-core/src/test/java/org/pragmatica/peg/perf/Phase1ParityTest.java
  • peglib-core/src/test/java/org/pragmatica/peg/perf/Phase2AllStructuralParityTest.java
  • peglib-core/src/test/java/org/pragmatica/peg/perf/Phase2ChoiceDispatchAndMarkResetParityTest.java
  • peglib-core/src/test/java/org/pragmatica/peg/perf/Phase2ChoiceDispatchParityTest.java
  • peglib-core/src/test/java/org/pragmatica/peg/perf/Phase2InlineLocationsParityTest.java
  • peglib-core/src/test/java/org/pragmatica/peg/perf/Phase2MarkResetChildrenParityTest.java
  • peglib-core/src/test/java/org/pragmatica/peg/perf/Phase2SelectivePackratEmptySetParityTest.java
  • peglib-core/src/test/java/org/pragmatica/peg/perf/Phase2SelectivePackratParityTest.java
  • peglib-core/src/test/java/org/pragmatica/peg/perf/TriviaAdversarialTest.java
  • peglib-core/src/test/java/org/pragmatica/peg/perf/TriviaPostPassFlagTest.java
  • peglib-core/src/test/java/org/pragmatica/peg/tree/StringSpanTest.java
  • peglib-core/src/test/java/org/pragmatica/peg/tree/TriviaPostPassSpliceOffsetTest.java
  • peglib-core/src/test/java/org/pragmatica/peg/tree/TriviaPostPassTest.java
  • peglib-formatter/pom.xml
  • peglib-incremental/pom.xml
  • peglib-incremental/src/main/java/org/pragmatica/peg/incremental/internal/TreeSplicer.java
  • peglib-incremental/src/main/java/org/pragmatica/peg/incremental/internal/TriviaRedistribution.java
  • peglib-maven-plugin/pom.xml
  • peglib-playground/pom.xml
  • pom.xml

Comment thread CHANGELOG.md
Comment on lines +8 to +10
## [0.5.1] - 2026-05-09

_Unreleased — patch cycle following 0.5.0._

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove the “Unreleased” label from the dated 0.5.1 release section.

Line 10 contradicts the dated heading at Line 8 (2026-05-09) and may confuse consumers parsing release state from the changelog.

- _Unreleased — patch cycle following 0.5.0._
+ _Patch cycle following 0.5.0._
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## [0.5.1] - 2026-05-09
_Unreleased — patch cycle following 0.5.0._
## [0.5.1] - 2026-05-09
_Patch cycle following 0.5.0._
🤖 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 `@CHANGELOG.md` around lines 8 - 10, The changelog entry for the 0.5.1 release
shows a dated header "## [0.5.1] - 2026-05-09" but still contains the line
"_Unreleased — patch cycle following 0.5.0._"; remove or replace that line so
the dated section no longer includes the "Unreleased" label (edit the paragraph
under the header "## [0.5.1] - 2026-05-09" to reflect a released state or remove
the sentence entirely).

Comment thread docs/HANDOVER.md
Comment on lines +600 to +603
| Reference (1900 LOC) | 19.78 ms | 26.87 ms | +35.8% |
| Selfhost (37k LOC) | 934.7 ms | 943.6 ms | +0.95% (within noise) |

Reference is 36% slower than legacy; selfhost is at parity. The post-pass overhead is real on small inputs (one tree-walk) but amortizes for large inputs where buffer save/restore dominated. **Acceptable as default**; opt-out remains available via explicit `ParserConfig` constructor.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Performance regression on small fixtures: verify user communication.

The documented +35.8% slowdown on the 1900-LOC reference fixture (line 600) is a significant user-facing change, even though selfhost is at parity. The doc marks this "acceptable as default" with opt-out available via explicit ParserConfig constructor.

Consider:

  • Is this performance tradeoff prominently documented in user-facing CHANGELOG.md and migration notes?
  • Should the default flip include guidance on when to opt-out (e.g., "use legacy attribution for sub-2000 LOC one-shot parses")?
  • The structural explanation (lines 679-682) is clear for engineers but may not help users decide whether to opt-out.

The transparency here is excellent, but ensure users parsing small inputs aren't surprised by the regression.

🤖 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 `@docs/HANDOVER.md` around lines 600 - 603, Update the user-facing
documentation to call out the 35.8% regression on small fixtures and give
explicit guidance for users: add an entry to CHANGELOG.md and migration notes
summarizing the regression and that the new behavior is the default but can be
disabled via the ParserConfig constructor (reference ParserConfig and the legacy
attribution option), recommend opt-out guidance such as "use legacy attribution
for one-shot parses under ~2,000 LOC" (or a clarified threshold), and add a
short user-facing paragraph explaining when to prefer the legacy mode vs new
default so users parsing small inputs won't be surprised.

Comment on lines +72 to +77
```
Start <- Triple / Pair
Pair <- 'x' 'y'
Triple <- 'x' 'y' 'z'
%whitespace <- [ ]+
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced grammar block.

The code fence is untyped, which triggers markdown lint rule MD040 and reduces syntax highlighting quality.

Proposed doc fix
-```
+```peg
 Start <- Triple / Pair
 Pair <- 'x' 'y'
 Triple <- 'x' 'y' 'z'
 %whitespace <- [ ]+
</details>

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.22.1)</summary>

[warning] 72-72: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/incremental/TRIVIA-ADVERSARIAL-FINDINGS.md around lines 72 - 77, Add a
language tag to the fenced grammar block so Markdown lint rule MD040 is
satisfied and syntax highlighting improves: update the untyped fence around the
grammar containing Start, Pair, Triple and %whitespace to include the language
identifier (e.g., "peg") immediately after the opening triple backticks.


</details>

<!-- fingerprinting:phantom:poseidon:hawk -->

<!-- d98c2f50 -->

<!-- This is an auto-generated comment by CodeRabbit -->

Comment on lines +246 to 247
private boolean triviaPostPass = false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Builder default for triviaPostPass is inconsistent with the release default.

Line 246 sets triviaPostPass to false, and Line 271 documents “Default: off”, but 0.5.1’s default behavior is post-pass enabled. This makes PegParser.builder(...).build() diverge from default parser creation behavior unless callers set it manually.

Suggested fix
-        private boolean triviaPostPass = false;
+        private boolean triviaPostPass = ParserConfig.DEFAULT.triviaPostPass();
...
-         * returns its CST. Default: off.
+         * returns its CST. Default: follows {`@link` ParserConfig#DEFAULT}.

Also applies to: 267-272, 279-297

🤖 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 `@peglib-core/src/main/java/org/pragmatica/peg/PegParser.java` around lines 246
- 247, The builder's default for the instance field triviaPostPass in PegParser
currently initializes it to false causing PegParser.builder(...).build() to
diverge from the historical default (post-pass enabled); update the builder's
initialization so triviaPostPass defaults to true (or otherwise match the
release default) and ensure any related builder docs/comments (the "Default:
off" text near the builder) are updated to reflect the new default; check and
adjust usages/fields referenced in PegParser (e.g., the triviaPostPass field and
the builder methods that set it) so the built parser behavior matches the 0.5.1
default.

Comment on lines +671 to +688
private static int matchRepetition(String input, int pos, int limit, Expression.Repetition rep, Grammar grammar) {
int cursor = pos;
int count = 0;
int max = rep.max()
.isPresent()
? rep.max()
.unwrap()
: Integer.MAX_VALUE;
while (count < max) {
int next = matchExpression(input, cursor, limit, rep.expression(), grammar);
if (next < 0 || next == cursor) break;
cursor = next;
count++ ;
}
return count >= rep.min()
? cursor
: - 1;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if Repetition expression validation exists elsewhere in the codebase
rg -n "Repetition" --type=java -A 5 -B 2 | head -100

Repository: siy/java-peglib

Length of output: 11111


🏁 Script executed:

# Search for Repetition class definition
rg -n "class Repetition" --type=java -A 10

Repository: siy/java-peglib

Length of output: 41


🏁 Script executed:

# Check EdgeCaseTest.java for repetition edge cases
fd -t f "EdgeCaseTest.java" | xargs cat | head -200

Repository: siy/java-peglib

Length of output: 6802


🏁 Script executed:

# Search for any validation of min/max in grammar parsing
rg -n "min.*max|max.*min" --type=java peglib-core/src/main/java | head -30

Repository: siy/java-peglib

Length of output: 839


🏁 Script executed:

# Look at the full GrammarParser repetition parsing logic
rg -n "bounded.*[Rr]epetition|parseRepetition|Repetition.*min.*max" --type=java -B 5 -A 15 peglib-core/src/main/java/org/pragmatica/peg/grammar/GrammarParser.java | head -100

Repository: siy/java-peglib

Length of output: 2244


🏁 Script executed:

# Check GrammarParserTest for the bounded repetition test
rg -n "parse_boundedRepetition" --type=java -A 30 peglib-core/src/test/java/org/pragmatica/peg/grammar/GrammarParserTest.java

Repository: siy/java-peglib

Length of output: 1458


🏁 Script executed:

# Search for any validation of Repetition constraints
rg -n "min.*>|max.*<" --type=java peglib-core/src/main/java/org/pragmatica/peg/grammar/ | head -20

Repository: siy/java-peglib

Length of output: 567


🏁 Script executed:

# Check if there's grammar validation logic that validates repetition constraints
rg -n "validate|Validation|check.*[Rr]epetition" --type=java peglib-core/src/main/java/org/pragmatica/peg/grammar/ | head -20

Repository: siy/java-peglib

Length of output: 2015


🏁 Script executed:

# Look at GrammarResolver which might do semantic validation
rg -n "class GrammarResolver" --type=java -A 20 peglib-core/src/main/java/org/pragmatica/peg/grammar/GrammarResolver.java | head -50

Repository: siy/java-peglib

Length of output: 781


🏁 Script executed:

# Search for any tests that try to create invalid repetitions like {5,1}
rg -n "{[0-9]+,[0-9]+}" --type=java peglib-core/src/test/java/org/pragmatica/peg/ | head -20

Repository: siy/java-peglib

Length of output: 150


🏁 Script executed:

# Look at Grammar.validate() method to see what it validates
rg -n "private static Result<Grammar> validate" --type=java -A 50 peglib-core/src/main/java/org/pragmatica/peg/grammar/Grammar.java

Repository: siy/java-peglib

Length of output: 3390


🏁 Script executed:

# Search for actual test cases with repetition bounds in different formats
rg -n "\\{[0-9]+\\}" --type=java peglib-core/src/test/java/org/pragmatica/peg/ | head -30

Repository: siy/java-peglib

Length of output: 1786


🏁 Script executed:

# Check if there's any mention of invalid repetition bounds in comments or error messages
rg -n "min.*max|bound|invalid.*repetition" --type=java peglib-core/src/main/java/org/pragmatica/peg/ -i | head -20

Repository: siy/java-peglib

Length of output: 2809


🏁 Script executed:

# Check if there's any test specifically for invalid repetition bounds like {5,1}
rg -n "{[0-9]+," --type=java peglib-core/src/test/java/ -A 2 -B 2 | grep -E "(min|max|test|Test)" | head -30

Repository: siy/java-peglib

Length of output: 143


🏁 Script executed:

# Look at the full matchRepetition context to understand the edge case handling better
cat -n peglib-core/src/main/java/org/pragmatica/peg/tree/TriviaPostPass.java | sed -n '671,690p'

Repository: siy/java-peglib

Length of output: 841


🏁 Script executed:

# Check if there are any other tests mentioning edge cases for repetition
rg -n "repetition.*edge|edge.*repetition|min.*max|max.*min" --type=java -i peglib-core/src/test/java/org/pragmatica/peg/ | head -20

Repository: siy/java-peglib

Length of output: 163


Add validation for invalid repetition bounds or document the behavior.

The matchRepetition method implementation is correct, but there's a gap: Grammar.validate() does not check that min ≤ max for Repetition expressions. When min > max occurs (e.g., {5,1}), the parser silently fails to match instead of reporting an error. The min == 0 edge case (ZeroOrMore bounded by max) is already tested in EdgeCaseTest.zeroOrMore_withEmpty_succeeds(). Consider either:

  • Adding validation in Grammar.validate() to reject min > max with a semantic error, or
  • Adding a test case demonstrating the expected behavior for invalid bounds
🤖 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 `@peglib-core/src/main/java/org/pragmatica/peg/tree/TriviaPostPass.java` around
lines 671 - 688, The parser silently fails when a Repetition has invalid bounds
(min > max); add a validation step in Grammar.validate() that iterates
Repetition expressions and checks rep.min() <= rep.max(). If the check fails,
raise/return a clear semantic error referencing the offending Repetition
(include min and max in the message) so invalid bounds like {5,1} are rejected
at grammar validation rather than causing silent match failures in
matchRepetition.

@siy siy merged commit f1eb332 into main May 9, 2026
2 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.

1 participant