From 494504bbf2c6c90c39335ca85be94f301aba9408 Mon Sep 17 00:00:00 2001 From: Sergiy Yevtushenko Date: Sat, 6 Jun 2026 21:06:10 +0200 Subject: [PATCH 1/4] chore: prepare release 0.6.3 --- CHANGELOG.md | 6 ++++++ README.md | 6 +++--- docs/VISITOR-TUTORIAL.md | 2 +- peglib-core/pom.xml | 2 +- peglib-formatter/pom.xml | 2 +- peglib-incremental/pom.xml | 2 +- peglib-maven-plugin/pom.xml | 2 +- peglib-playground/pom.xml | 2 +- peglib-runtime/pom.xml | 2 +- pom.xml | 2 +- 10 files changed, 17 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2b15d9..1f6acb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.6.3] - 2026-06-06 + +### Fixed + +- (placeholder — release in progress) + ## [0.6.2] - 2026-06-06 ### Fixed diff --git a/README.md b/README.md index 486acc2..f71d206 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ A PEG (Parsing Expression Grammar) parser library for Java. Tokens-first lex-then-parse architecture, flat int[] CST, visitor pattern, true incremental reparse. -Maven Central: `org.pragmatica-lite:peglib:0.6.2` +Maven Central: `org.pragmatica-lite:peglib:0.6.3` Migrating from 0.5.x? See [`docs/MIGRATION-0.5-TO-0.6.md`](docs/MIGRATION-0.5-TO-0.6.md). Design rationale: [`docs/ARCHITECTURE-0.6.0.md`](docs/ARCHITECTURE-0.6.0.md). @@ -29,7 +29,7 @@ Design rationale: [`docs/ARCHITECTURE-0.6.0.md`](docs/ARCHITECTURE-0.6.0.md). org.pragmatica-lite peglib - 0.6.2 + 0.6.3 ``` @@ -284,7 +284,7 @@ pre-compiled classes — no `fromGrammar` cost at runtime: org.pragmatica-lite peglib-maven-plugin - 0.6.2 + 0.6.3 generate-v6 diff --git a/docs/VISITOR-TUTORIAL.md b/docs/VISITOR-TUTORIAL.md index 94be2f6..cfbfd38 100644 --- a/docs/VISITOR-TUTORIAL.md +++ b/docs/VISITOR-TUTORIAL.md @@ -93,7 +93,7 @@ Configure `peglib-maven-plugin` in your `pom.xml`: org.pragmatica-lite peglib-maven-plugin - 0.6.2 + 0.6.3 generate-v6 diff --git a/peglib-core/pom.xml b/peglib-core/pom.xml index 7c3a4e0..c0894c8 100644 --- a/peglib-core/pom.xml +++ b/peglib-core/pom.xml @@ -7,7 +7,7 @@ org.pragmatica-lite peglib-parent - 0.6.2 + 0.6.3 ../pom.xml diff --git a/peglib-formatter/pom.xml b/peglib-formatter/pom.xml index 8f7c6b4..1cc4c12 100644 --- a/peglib-formatter/pom.xml +++ b/peglib-formatter/pom.xml @@ -7,7 +7,7 @@ org.pragmatica-lite peglib-parent - 0.6.2 + 0.6.3 ../pom.xml diff --git a/peglib-incremental/pom.xml b/peglib-incremental/pom.xml index 3fd59b9..760efad 100644 --- a/peglib-incremental/pom.xml +++ b/peglib-incremental/pom.xml @@ -7,7 +7,7 @@ org.pragmatica-lite peglib-parent - 0.6.2 + 0.6.3 ../pom.xml diff --git a/peglib-maven-plugin/pom.xml b/peglib-maven-plugin/pom.xml index f80cd19..ca869d7 100644 --- a/peglib-maven-plugin/pom.xml +++ b/peglib-maven-plugin/pom.xml @@ -7,7 +7,7 @@ org.pragmatica-lite peglib-parent - 0.6.2 + 0.6.3 ../pom.xml diff --git a/peglib-playground/pom.xml b/peglib-playground/pom.xml index d41ebb3..33dda5e 100644 --- a/peglib-playground/pom.xml +++ b/peglib-playground/pom.xml @@ -7,7 +7,7 @@ org.pragmatica-lite peglib-parent - 0.6.2 + 0.6.3 ../pom.xml diff --git a/peglib-runtime/pom.xml b/peglib-runtime/pom.xml index 77895c2..52c32ff 100644 --- a/peglib-runtime/pom.xml +++ b/peglib-runtime/pom.xml @@ -7,7 +7,7 @@ org.pragmatica-lite peglib-parent - 0.6.2 + 0.6.3 ../pom.xml diff --git a/pom.xml b/pom.xml index e7df621..8b97333 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.pragmatica-lite peglib-parent - 0.6.2 + 0.6.3 pom Peglib Parent From b631b0e942a1dcedc63bc2c5932aac87376c2dc5 Mon Sep 17 00:00:00 2001 From: Sergiy Yevtushenko Date: Sat, 6 Jun 2026 21:31:07 +0200 Subject: [PATCH 2/4] docs: resolve 0.6.2 deploy-time anomaly as Central publish-queue wait --- docs/HANDOVER.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/HANDOVER.md b/docs/HANDOVER.md index 8fa358d..a40d265 100644 --- a/docs/HANDOVER.md +++ b/docs/HANDOVER.md @@ -28,7 +28,7 @@ Skips moved 4 → 2 (remaining: `LexerGeneratorTest` parity 1, `TriviaAdversaria - **PR #36 merged** → `main` at `5c8126d`, tagged `v0.6.2` (2026-06-06). - **Deployed to Maven Central**, deployment `8ac8bbfd-51ec-4cc6-ab4e-25f596320c75`, auto-published — all 7 artifacts live at `repo1.maven.org/maven2/org/pragmatica-lite/*/0.6.2/`. -- **Anomaly for next release**: deploy total 24:56 min — `peglib-playground` alone took 24:06 (build/test/shade long pole; rest of reactor normal). Investigate before 0.6.3. +- **Deploy-time note (investigated, resolved — no action)**: 0.6.2 deploy total 24:56 min looked like a `peglib-playground` problem (24:06 in reactor summary) but the log shows playground's own work finished in seconds; the time was `central-publishing-maven-plugin`'s `Waiting until Deployment ... is published` poll — Maven Central's server-side publish queue — attributed to the last reactor module. 0.6.1 took ~4-5 min through the same mechanism. Keep `waitUntil=published` (sync confirmation is worth the wait on a rare operation). - **Remaining backlog unchanged**: jbct v6 API migration (jbct repo), token pool (H), lexer modes (I), JBCT plugin bump (K), upstream JBCT formatter convergence bug (`-Djbct.skip=true` still required). --- From b43522ed4871e2ecabdb2b9c6f6e36911c0fe2c2 Mon Sep 17 00:00:00 2001 From: Sergiy Yevtushenko Date: Sat, 6 Jun 2026 21:40:07 +0200 Subject: [PATCH 3/4] fix: restore pending-trivia snapshot on CutFailure in legacy combinators; zero test skips --- CHANGELOG.md | 10 +- .../org/pragmatica/peg/parser/PegEngine.java | 28 ++++- .../peg/perf/TriviaAdversarialTest.java | 111 +++++++++++++++--- .../peg/v6/generator/LexerGeneratorTest.java | 1 - 4 files changed, 125 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f6acb3..ad814a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- (placeholder — release in progress) +- Legacy interpreter (`PegEngine`): `Optional`, `ZeroOrMore`, `OneOrMore`, and bounded + repetition swallowed the pending-trivia snapshot on the `CutFailure` path — the cut + failure propagated without restoring `entryPendingSnapshot`/`iterPendingSnapshot`, + unlike the regular-failure path. All four combinators now restore symmetrically. + Previously `@Disabled` cut-failure test re-enabled plus 4 new combinator tests. +- Re-enabled `LexerGeneratorTest.parity_triviaClassification_lineAndBlockComments` — + the folded `%whitespace` per-kind trivia fix (0.6.2) resolved both reasons for the + skip (block-comment-in-Choice routing and whitespace-run coalescing). The full test + suite now runs with **zero skips** (1424 tests across 7 modules). ## [0.6.2] - 2026-06-06 diff --git a/peglib-core/src/main/java/org/pragmatica/peg/parser/PegEngine.java b/peglib-core/src/main/java/org/pragmatica/peg/parser/PegEngine.java index fb4fef8..64f63b5 100644 --- a/peglib-core/src/main/java/org/pragmatica/peg/parser/PegEngine.java +++ b/peglib-core/src/main/java/org/pragmatica/peg/parser/PegEngine.java @@ -1912,8 +1912,13 @@ private ParseResult parseZeroOrMoreWithMode(ParsingContext ctx, }else { result = parseExpressionWithMode(ctx, zom.expression(), ruleName, mode); } - // CutFailure must propagate - don't just break + // CutFailure must propagate - don't just break. Restore the + // pending-leading buffer first so trivia state matches the + // regular-failure path below (symmetry fix). if (result instanceof ParseResult.CutFailure) { + if (!triviaPostPass) { + ctx.restorePendingLeadingTrivia(iterPendingSnapshot); + } return result; } if (result.isFailure()) { @@ -1991,8 +1996,13 @@ private ParseResult parseOneOrMoreWithMode(ParsingContext ctx, }else { result = parseExpressionWithMode(ctx, oom.expression(), ruleName, mode); } - // CutFailure must propagate - don't just break + // CutFailure must propagate - don't just break. Restore the + // pending-leading buffer first so trivia state matches the + // regular-failure path below (symmetry fix). if (result instanceof ParseResult.CutFailure) { + if (!triviaPostPass) { + ctx.restorePendingLeadingTrivia(iterPendingSnapshot); + } return result; } if (result.isFailure()) { @@ -2038,8 +2048,13 @@ private ParseResult parseOptionalWithMode(ParsingContext ctx, if (result.isSuccess()) { return result; } - // CutFailure must propagate - don't treat as success + // CutFailure must propagate - don't treat as success. Restore the + // pending-leading buffer before propagating so trivia state stays + // consistent with the regular-failure path below (symmetry fix). if (result instanceof ParseResult.CutFailure) { + if (!triviaPostPass) { + ctx.restorePendingLeadingTrivia(entryPendingSnapshot); + } return result; } // Optional always succeeds - return empty node on no match @@ -2102,8 +2117,13 @@ private ParseResult parseRepetitionWithMode(ParsingContext ctx, }else { result = parseExpressionWithMode(ctx, rep.expression(), ruleName, mode); } - // CutFailure must propagate - don't just break + // CutFailure must propagate - don't just break. Restore the + // pending-leading buffer first so trivia state matches the + // regular-failure path below (symmetry fix). if (result instanceof ParseResult.CutFailure) { + if (!triviaPostPass) { + ctx.restorePendingLeadingTrivia(iterPendingSnapshot); + } return result; } if (result.isFailure()) { diff --git a/peglib-core/src/test/java/org/pragmatica/peg/perf/TriviaAdversarialTest.java b/peglib-core/src/test/java/org/pragmatica/peg/perf/TriviaAdversarialTest.java index f57be53..5b0585b 100644 --- a/peglib-core/src/test/java/org/pragmatica/peg/perf/TriviaAdversarialTest.java +++ b/peglib-core/src/test/java/org/pragmatica/peg/perf/TriviaAdversarialTest.java @@ -1,6 +1,5 @@ package org.pragmatica.peg.perf; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.pragmatica.lang.Result; @@ -487,29 +486,25 @@ void notPredicate_failingBody_outerStateRestored() { } /** - * Target #6 — Optional CutFailure asymmetry. - * In {@link org.pragmatica.peg.parser.PegEngine#parseOptionalWithMode} - * (line 1936): on CutFailure the code returns the failure WITHOUT - * restoring the entry pending snapshot. Regular failure DOES restore - * (line 1941). Predates Step 1 docs. + * Target #6 — Optional CutFailure symmetry (fixed). + * {@link org.pragmatica.peg.parser.PegEngine#parseOptionalWithMode} now + * restores the entry pending-trivia snapshot on the CutFailure path, + * matching the regular-failure path. The same symmetric fix was applied to + * the repetition combinators (parseZeroOrMoreWithMode, + * parseOneOrMoreWithMode, parseRepetitionWithMode), which had the identical + * asymmetry on their per-iteration snapshots. */ @Nested - class OptionalCutFailurePending { + class OptionalCutFailureSymmetry { /** - * Optional containing a Cut that fails. Outer parse fails because - * the cut prevents recovery. Pending state asymmetry can only be - * observed via cross-implementation comparison or instrumentation. - * - *

Disabled because it surfaces a latent bug rather than asserting - * correct behaviour — there is no straightforward black-box assertion - * that distinguishes "snapshot restored" from "snapshot not restored" - * when the parse subsequently fails. Documented as Bug 2 in the - * findings doc; minimum reproducer captured here. + * Optional containing a Cut that fails. The cut commits to the + * alternative, so the Optional must NOT swallow the failure — the outer + * parse fails. The pending-trivia snapshot is restored on the cut path, + * keeping trivia state consistent with the regular-failure path. */ @Test - @Disabled("Bug 2 (latent): parseOptionalWithMode line 1936 returns CutFailure without restoring entryPendingSnapshot. No black-box surface manifestation found yet — observability requires instrumented context. See TRIVIA-ADVERSARIAL-FINDINGS.md.") - void optional_withInternalCutFailure_pendingSnapshotNotRestored() { + void optional_withInternalCutFailure_propagatesAndRestoresSnapshot() { var grammar = """ Item <- Pre Tail? Pre <- < 'a' > @@ -518,10 +513,88 @@ void optional_withInternalCutFailure_pendingSnapshotNotRestored() { """; var parser = PegParser.fromGrammar(grammar).unwrap(); // "a b X" — Tail matches 'b', cut commits, then 'c' fails. - // Optional should NOT swallow the cut; outer parse should fail. + // Optional must NOT swallow the cut; outer parse must fail. var result = parser.parseCst("a b X"); assertThat(result.isFailure()).isTrue(); } + + /** + * Companion success case for the same grammar: the Optional Tail + * matches fully, so the parse succeeds and all three spaces are + * accounted for as trivia. This pins that the pending-trivia buffer is + * not corrupted on the success path, complementing the cut-failure + * restoration above. + */ + @Test + void optional_withInternalCutSuccess_triviaIntact() { + var grammar = """ + Item <- Pre Tail? + Pre <- < 'a' > + Tail <- 'b' ^ 'c' + %whitespace <- [ ]+ + """; + var parser = PegParser.fromGrammar(grammar).unwrap(); + // "a b c" — Tail matches fully; 2 inter-word spaces are trivia. + var result = parser.parseCst("a b c"); + assertThat(result.isSuccess()).isTrue(); + assertThat(collectAllTriviaText(result.unwrap())).isEqualTo(" "); + } + + /** + * ZeroOrMore body contains a Cut that fails on a later iteration. The + * cut must propagate out of the star (it must not silently terminate + * the loop as a normal no-more-matches), and the per-iteration pending + * snapshot is restored on the cut path. + */ + @Test + void zeroOrMore_withInternalCutFailure_propagates() { + var grammar = """ + Item <- Pre Tail* + Pre <- < 'a' > + Tail <- 'b' ^ 'c' + %whitespace <- [ ]+ + """; + var parser = PegParser.fromGrammar(grammar).unwrap(); + // "a b c b X" — first Tail matches, second Tail's cut commits then + // 'c' fails. Star must not swallow the cut. + var result = parser.parseCst("a b c b X"); + assertThat(result.isFailure()).isTrue(); + } + + /** + * OneOrMore body contains a Cut that fails on a later iteration. Same + * propagation and snapshot-restore requirement as ZeroOrMore. + */ + @Test + void oneOrMore_withInternalCutFailure_propagates() { + var grammar = """ + Item <- Pre Tail+ + Pre <- < 'a' > + Tail <- 'b' ^ 'c' + %whitespace <- [ ]+ + """; + var parser = PegParser.fromGrammar(grammar).unwrap(); + var result = parser.parseCst("a b c b X"); + assertThat(result.isFailure()).isTrue(); + } + + /** + * Bounded repetition {@code {n,m}} body contains a Cut that fails. The + * cut must propagate out of the bounded loop and the per-iteration + * pending snapshot is restored on the cut path. + */ + @Test + void boundedRepetition_withInternalCutFailure_propagates() { + var grammar = """ + Item <- Pre Tail{1,3} + Pre <- < 'a' > + Tail <- 'b' ^ 'c' + %whitespace <- [ ]+ + """; + var parser = PegParser.fromGrammar(grammar).unwrap(); + var result = parser.parseCst("a b c b X"); + assertThat(result.isFailure()).isTrue(); + } } /** diff --git a/peglib-core/src/test/java/org/pragmatica/peg/v6/generator/LexerGeneratorTest.java b/peglib-core/src/test/java/org/pragmatica/peg/v6/generator/LexerGeneratorTest.java index 9acc312..cadadb4 100644 --- a/peglib-core/src/test/java/org/pragmatica/peg/v6/generator/LexerGeneratorTest.java +++ b/peglib-core/src/test/java/org/pragmatica/peg/v6/generator/LexerGeneratorTest.java @@ -188,7 +188,6 @@ void parity_caseInsensitiveLiterals() { "True false TRUE foo"); } - @org.junit.jupiter.api.Disabled("Block-comment alternative inside Choice doesn't route through compileDelimitedBlock; lexer driver also coalesces %whitespace runs into a single token. Defer to later phase.") @Test void parity_triviaClassification_lineAndBlockComments() { // Phase A.6 — generated lexer mirrors the engine's content-based trivia From e3576a91b85a726f7ae12a4dff0a9084f38bc9d3 Mon Sep 17 00:00:00 2001 From: Sergiy Yevtushenko Date: Sun, 7 Jun 2026 07:20:28 +0200 Subject: [PATCH 4/4] docs: add 0.6.3 history row and correct release date --- CHANGELOG.md | 2 +- README.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad814a1..d9982f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.6.3] - 2026-06-06 +## [0.6.3] - 2026-06-07 ### Fixed diff --git a/README.md b/README.md index f71d206..f920b30 100644 --- a/README.md +++ b/README.md @@ -343,6 +343,7 @@ Full history in [`CHANGELOG.md`](CHANGELOG.md). | Version | Date | What | |---|---|---| +| **0.6.3** | 2026-06-07 | Patch release. Legacy interpreter cut-failure symmetry: `Optional`/`ZeroOrMore`/`OneOrMore`/bounded repetition now restore the pending-trivia snapshot on `CutFailure`. Test suite reaches zero skips (1424 tests). | | **0.6.2** | 2026-06-06 | Patch release. Shift operators (`<<`/`>>`/`>>>`) in field/local-var initializer context fixed via inline expansion of DFA-skipped lexer rules; loud `SkippedRuleReferenced` guard. Per-iteration `%whitespace` tokenization (folded form emits per-kind trivia; grammar-split workaround reverted). Selfhost fixture now parses with 0 diagnostics. | | **0.6.1** | 2026-05-12 | Patch release. Doc-comment trivia kinds (`KIND_DOC_LINE_COMMENT`, `KIND_DOC_BLOCK_COMMENT`), per-rule `%recover` runtime, `%checkpoint` directive parsing, named captures and back-references restored, `MIXED`-rule char-level fallback, diagnostic cap honored. | | **0.6.0** | 2026-05-11 | Clean-slate redesign. Tokens-first lex-then-parse, flat `int[]` CST, visitor pattern, always-on recovery, true partial reparse. ~12x faster than 0.5.x; parity-class with `javac`. **BREAKING** — see [migration guide](docs/MIGRATION-0.5-TO-0.6.md). |