Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@ 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.4.2] - 2026-05-04

Standalone-parser fix. No API changes for the interpreter / incremental / formatter paths.

### Fixed

- **Generated parsers are now truly standalone.** Previous releases' generated `RuleId` interface declared `extends org.pragmatica.peg.action.RuleId` and the emitted `parseRuleAt` signature used `Class<? extends org.pragmatica.peg.action.RuleId>`. This contradicted the documented contract on `ParserGenerator` ("The generated parser depends only on pragmatica-lite:core") — downstream projects without peglib on their compile classpath could not build the emitted source. The link was vestigial: the generated `withAction(Class<? extends RuleId>, ...)` API uses string-based dispatch (`ruleIdClass.getSimpleName()`) and does not need parent-type linkage. (Commit `b72c97f`)
- **Generated parsers no longer reference any FQCN in emitted source.** Audit removed `org.pragmatica.peg.action.RuleId`, `java.util.ArrayDeque`, and other fully-qualified names from `sb.append(...)` emission templates. Proper imports are now emitted in `generateImports` / `generateCstImports` and simple names are used throughout the emitted source. (Commit `b72c97f`)

### Test changes

- `RuleIdEmissionTest` updated to assert standalone shape (`public sealed interface RuleId {`) and the absence of `org.pragmatica.peg.action.RuleId` in emitted source. The test that previously verified the parent-type link via reflection now verifies the local `RuleId` hierarchy instead.

### Notes for downstream

- **Regenerate parsers** to pick up the standalone fix.
- Generated parsers continue to depend only on `pragmatica-lite:core` (for `Result`, `Option`, `Cause`).
- The `peglib-incremental` module's interpreter-based `parseRuleAt` API is unchanged — that path still uses `org.pragmatica.peg.action.RuleId` (interpreter-only, not generator-emitted).

## [0.4.1] - 2026-05-04

Performance — 3.88× interpreter speedup, 3× incremental edit speedup, no API changes.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Quick links:
<dependency>
<groupId>org.pragmatica-lite</groupId>
<artifactId>peglib</artifactId>
<version>0.4.1</version>
<version>0.4.2</version>
</dependency>
```

Expand Down Expand Up @@ -395,7 +395,7 @@ The `peglib-maven-plugin` module (separate artifact, sibling to `peglib`) wraps
<plugin>
<groupId>org.pragmatica-lite</groupId>
<artifactId>peglib-maven-plugin</artifactId>
<version>0.4.1</version>
<version>0.4.2</version>
<executions>
<execution>
<goals>
Expand Down
531 changes: 531 additions & 0 deletions docs/incremental/UNSAFE-GENERATOR-SPIKE.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion peglib-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.pragmatica-lite</groupId>
<artifactId>peglib-parent</artifactId>
<version>0.4.1</version>
<version>0.4.2</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,14 @@ private void generateImports(StringBuilder sb) {
import org.pragmatica.lang.Option;
import org.pragmatica.lang.Result;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;

""");
}
Expand Down Expand Up @@ -1424,7 +1426,7 @@ ParseResult asRegularFailure() {
* 0.2.6 — emit the Actions dispatch field + setter + nested SemanticValues.
* Used only by the AST path (generate()). Lambdas attached here override any
* inline grammar actions for the matching rule class. Generated parsers
* continue to depend only on pragmatica-lite:core plus peglib's RuleId type.
* continue to depend only on pragmatica-lite:core.
*/
private void generateActionsField(StringBuilder sb) {
sb.append("""
Expand Down Expand Up @@ -1520,12 +1522,14 @@ private void generateCstImports(StringBuilder sb) {
import org.pragmatica.lang.Option;
import org.pragmatica.lang.Result;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;

""");
}
Expand All @@ -1545,11 +1549,10 @@ private void generateRuleIdInterface(StringBuilder sb) {
var rules = grammar.rules();
sb.append(" // === Rule ID Types ===\n\n");
// Nested records are implicitly permitted - no need for explicit permits clause.
// Extends the library's RuleId so callers can pass RuleId classes through the
// peglib Actions API (0.2.6) and, forward-compat, parseRuleAt (0.3.0).
sb.append(" public sealed interface RuleId extends org.pragmatica.peg.action.RuleId {\n");
// Standalone marker interface — generated parsers do not depend on peglib at runtime.
sb.append(" public sealed interface RuleId {\n");
sb.append(" int ordinal();\n");
sb.append(" @Override String name();\n\n");
sb.append(" String name();\n\n");
// Generate record for each rule
int ordinal = 0;
for (var rule : rules) {
Expand Down Expand Up @@ -2017,7 +2020,7 @@ public void setPackratEnabled(boolean enabled) {
// first (the stack is unwound by the time recovery runs), then
// the live stack, then the global default char-set.
sb.append("""
private final java.util.ArrayDeque<String> recoveryOverrideStack = new java.util.ArrayDeque<>();
private final ArrayDeque<String> recoveryOverrideStack = new ArrayDeque<>();
private Option<String> pendingFailureRecoveryOverride = Option.none();

private void pushRecoveryOverride(String terminator) {
Expand Down Expand Up @@ -2401,13 +2404,13 @@ private void generateCstParseRuleAt(StringBuilder sb) {
public record PartialParse(CstNode node, int endOffset) {}

// Dispatch table: RuleId marker class -> rule method.
private final Map<Class<? extends org.pragmatica.peg.action.RuleId>,
java.util.function.Supplier<CstParseResult>> ruleDispatch = buildRuleDispatch();
private final Map<Class<? extends RuleId>,
Supplier<CstParseResult>> ruleDispatch = buildRuleDispatch();

private Map<Class<? extends org.pragmatica.peg.action.RuleId>,
java.util.function.Supplier<CstParseResult>> buildRuleDispatch() {
var m = new java.util.HashMap<Class<? extends org.pragmatica.peg.action.RuleId>,
java.util.function.Supplier<CstParseResult>>();
private Map<Class<? extends RuleId>,
Supplier<CstParseResult>> buildRuleDispatch() {
var m = new HashMap<Class<? extends RuleId>,
Supplier<CstParseResult>>();
""");
for (var rule : grammar.rules()) {
var ruleClassName = toClassName(rule.name());
Expand All @@ -2419,7 +2422,7 @@ java.util.function.Supplier<CstParseResult>> buildRuleDispatch() {
.append(");\n");
}
sb.append("""
return java.util.Map.copyOf(m);
return Map.copyOf(m);
}

/**
Expand All @@ -2432,7 +2435,7 @@ java.util.function.Supplier<CstParseResult>> buildRuleDispatch() {
* @since 0.3.0
*/
public Result<PartialParse> parseRuleAt(
Class<? extends org.pragmatica.peg.action.RuleId> ruleId,
Class<? extends RuleId> ruleId,
String input, int offset) {
if (ruleId == null) {
return Result.failure(new ParseError(SourceLocation.START, "Rule id class is null"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
/**
* Verifies that {@link org.pragmatica.peg.generator.ParserGenerator} emits:
* <ul>
* <li>a nested {@code sealed interface RuleId extends
* org.pragmatica.peg.action.RuleId}</li>
* <li>a nested standalone {@code sealed interface RuleId} (no extension of
* a peglib base type — generated parsers must compile with no peglib
* runtime dependency)</li>
* <li>a parameter-less record per grammar rule, with class names matching
* the sanitized rule name (so {@code Class.getSimpleName()} lines up with
* {@link Actions} dispatch)</li>
Expand All @@ -26,20 +27,21 @@
* </ul>
*
* <p>These are the shape guarantees the 0.3.0 {@code parseRuleAt} signature
* will rely on.
* relies on, scoped to the locally-emitted {@code RuleId} interface.
*/
class RuleIdEmissionTest {

@Test
void generatedCstParser_emitsSealedRuleIdExtendingLibraryBase() {
void generatedCstParser_emitsStandaloneSealedRuleId() {
var grammar = """
Number <- < [0-9]+ >
Sum <- Number '+' Number
%whitespace <- [ ]*
""";
var source = PegParser.generateCstParser(grammar, "emit.cst", "CstParser").unwrap();

assertThat(source).contains("public sealed interface RuleId extends org.pragmatica.peg.action.RuleId");
assertThat(source).contains("public sealed interface RuleId {");
assertThat(source).doesNotContain("org.pragmatica.peg.action.RuleId");
assertThat(source).contains("record Number() implements RuleId");
assertThat(source).contains("record Sum() implements RuleId");
}
Expand All @@ -53,7 +55,8 @@ void generatedAstParser_emitsRuleIdAndWithActionApi() {
""";
var source = PegParser.generateParser(grammar, "emit.ast", "AstParser").unwrap();

assertThat(source).contains("public sealed interface RuleId extends org.pragmatica.peg.action.RuleId");
assertThat(source).contains("public sealed interface RuleId {");
assertThat(source).doesNotContain("org.pragmatica.peg.action.RuleId");
assertThat(source).contains("record Number() implements RuleId");
assertThat(source).contains("record Sum() implements RuleId");
assertThat(source).contains("public AstParser withAction(Class<? extends RuleId> ruleIdClass");
Expand Down Expand Up @@ -91,8 +94,6 @@ void generatedAstParser_lambdaOverridesInlineAction() throws Exception {
var numberClass = Class.forName("emit.lambda.LambdaParser$RuleId$Number", true, parserClass.getClassLoader());
var semanticValuesClass = Class.forName("emit.lambda.LambdaParser$SemanticValues", true, parserClass.getClassLoader());
assertThat(ruleIdIface.isAssignableFrom(numberClass)).isTrue();
// Generated RuleId must extend library's RuleId — enforces API shape for parseRuleAt.
assertThat(RuleId.class.isAssignableFrom(ruleIdIface)).isTrue();

// Attach a lambda that returns matched-int-times-10 so it is distinguishable from the inline action (sv.toInt()).
var withAction = parserClass.getMethod("withAction", Class.class, Function.class);
Expand Down
2 changes: 1 addition & 1 deletion peglib-formatter/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.pragmatica-lite</groupId>
<artifactId>peglib-parent</artifactId>
<version>0.4.1</version>
<version>0.4.2</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion peglib-incremental/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.pragmatica-lite</groupId>
<artifactId>peglib-parent</artifactId>
<version>0.4.1</version>
<version>0.4.2</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion peglib-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.pragmatica-lite</groupId>
<artifactId>peglib-parent</artifactId>
<version>0.4.1</version>
<version>0.4.2</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion peglib-playground/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.pragmatica-lite</groupId>
<artifactId>peglib-parent</artifactId>
<version>0.4.1</version>
<version>0.4.2</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>org.pragmatica-lite</groupId>
<artifactId>peglib-parent</artifactId>
<version>0.4.1</version>
<version>0.4.2</version>
<packaging>pom</packaging>

<name>Peglib Parent</name>
Expand Down
Loading