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
36 changes: 36 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,42 @@ 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.3.0] - 2026-04-22

Infrastructure-only minor-bump release. No new user-facing features beyond the `parseRuleAt` API. Sets up the project structure and API additions that `peglib-incremental` (0.3.1) and `peglib-formatter` (0.3.3) will build against.

### Changed

- **Root pom converted to a multi-module parent.** Previous structure was a single-module `org.pragmatica-lite:peglib` with sibling directories holding their own poms. New structure:
```
peglib-parent (root pom, packaging=pom)
├── peglib-core (the primary artifact: org.pragmatica-lite:peglib:0.3.0)
├── peglib-incremental (shell module, reserved for 0.3.1)
├── peglib-formatter (shell module, reserved for 0.3.3)
├── peglib-maven-plugin (was sibling; now reactor module)
└── peglib-playground (was sibling; now reactor module)
```
- **Maven coordinate preserved.** `org.pragmatica-lite:peglib:0.3.0` still resolves to the same jar content consumers got in 0.2.x — the artifact simply lives in a sub-directory. Downstream consumers pinning the `peglib` coordinate need no changes.
- `peglib-maven-plugin` and `peglib-playground` modules now inherit from `peglib-parent` — their poms gain `<parent>` references; artifactIds unchanged.

### Added

- `Parser#parseRuleAt(Class<? extends RuleId> ruleId, String input, int offset)` — partial-parse entry point. Parses a specific rule against input starting at the given offset; returns `Result<PartialParse>` wrapping the resulting CST subtree and its end offset. Implemented by `PegEngine` (interpreter) and by generated parsers via an identity map keyed on the `RuleId` marker classes the generator has been emitting since 0.2.6. This is the API `peglib-incremental` (0.3.1) depends on per `docs/incremental/SPEC.md` §5.6.
- `org.pragmatica.peg.parser.PartialParse` record `(CstNode node, int endOffset)`.
- `peglib-incremental` and `peglib-formatter` shell modules — empty placeholders with just a `package-info.java` each. Reservations for 0.3.1 and 0.3.3.
- `docs/PARTIAL-PARSE.md` — `parseRuleAt` API reference.
- README updated with a Module Layout section and `parseRuleAt` usage snippet.

### Tests

- Root reactor: 663 → **674 passing** in `peglib-core`, +11 new in `ParseRuleAtTest` (interpreter + generator parity + `PartialParse` record). Plus 5 in `peglib-maven-plugin`, 22 in `peglib-playground` — aggregate **701 tests, 0 failures, 1 skipped** (pre-existing `RoundTripTest`).
- All 22-file corpus parity suites stay 22/22 under the new structure.
- `GeneratorFlagInertnessTest` stays 3/3 green — `parseRuleAt` emission lands in both config-vs-no-config sides.

### Notes

- Non-CST (Object-returning) generator path uses a different `ParseResult` type; `parseRuleAt` is scoped to the CST generator path (used by `peglib-incremental`). Per-path rationale in `docs/PARTIAL-PARSE.md`.

## [0.2.9] - 2026-04-22

### Added
Expand Down
39 changes: 37 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,23 @@ A PEG (Parsing Expression Grammar) parser library for Java, inspired by [cpp-peg
- **Source code generation** - Generate standalone parser Java files
- **Java 25** - Uses latest Java features (records, sealed interfaces, pattern matching)

## Module Layout (0.3.0)

Peglib is a multi-module Maven reactor. Pick the module you need; transitive deps stay minimal.

```
peglib-parent (pom)
├── peglib-core # engine, generator, analyzer — the core parser
├── peglib-incremental # cursor-anchored incremental reparsing (shell; spec-driven)
├── peglib-formatter # grammar-driven source formatter (shell)
├── peglib-maven-plugin # codegen + analyzer goals for Maven builds
└── peglib-playground # interactive REPL / web playground
```

The `peglib-core` module directory ships the primary artifact `org.pragmatica-lite:peglib` —
the Maven coordinate is preserved from 0.2.x for downstream compatibility. The other modules
are optional add-ons.

## Quick Start

### Dependency
Expand All @@ -23,7 +40,7 @@ A PEG (Parsing Expression Grammar) parser library for Java, inspired by [cpp-peg
<dependency>
<groupId>org.pragmatica-lite</groupId>
<artifactId>peglib</artifactId>
<version>0.2.9</version>
<version>0.3.0</version>
</dependency>
```

Expand Down Expand Up @@ -63,6 +80,24 @@ Integer result = (Integer) calculator.parse("3 + 5 * 2").unwrap();
// result = 13
```

### Partial parse (0.3.0)

`parseRuleAt` invokes a single rule against a substring of the buffer starting at a
given offset. Intended for cursor-anchored incremental reparsing and grammar-debugging
tooling. See [docs/PARTIAL-PARSE.md](docs/PARTIAL-PARSE.md) for the full API.

```java
record Number() implements org.pragmatica.peg.action.RuleId {}

var parser = PegParser.fromGrammar("""
Number <- < [0-9]+ >
%whitespace <- [ \\t]*
""").unwrap();

var partial = parser.parseRuleAt(Number.class, " 42 ", 2).unwrap();
// partial.endOffset() == 4, partial.node() is the CST for "42"
```

## Grammar Syntax

Peglib uses [PEG](https://bford.info/pub/lang/peg.pdf) syntax compatible with [cpp-peglib](https://github.com/yhirose/cpp-peglib):
Expand Down Expand Up @@ -354,7 +389,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.2.9</version>
<version>0.3.0</version>
<executions>
<execution>
<goals>
Expand Down
2 changes: 2 additions & 0 deletions docs/GRAMMAR-DSL.md
Original file line number Diff line number Diff line change
Expand Up @@ -548,5 +548,7 @@ tooling integrations.
formatting, `ParseResultWithDiagnostics` API
- [Trivia Attribution](TRIVIA-ATTRIBUTION.md) — how whitespace/comments
are attached to CST nodes
- [Partial Parse](PARTIAL-PARSE.md) — the 0.3.0 `parseRuleAt` API for
cursor-anchored partial parsing and incremental reparse
- [`CHANGELOG.md`](../CHANGELOG.md) — per-release history of grammar-DSL
additions
112 changes: 112 additions & 0 deletions docs/PARTIAL-PARSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Partial parse — `parseRuleAt` (0.3.0)

`Parser.parseRuleAt` invokes a specific grammar rule at a given offset in the
input buffer, returning the produced CST subtree plus the absolute offset where
parsing stopped. Unlike `parseCst`, the matched rule is not required to consume
all remaining input — parsing stops when the rule itself finishes.

The feature is primarily consumed by the `peglib-incremental` module for
cursor-anchored reparsing (see [`docs/incremental/SPEC.md`](incremental/SPEC.md) §5.6)
but is also useful for grammar debugging and testing.

## API

```java
// In org.pragmatica.peg.parser

public interface Parser {
// ... existing methods ...

Result<PartialParse> parseRuleAt(Class<? extends RuleId> ruleId,
String input,
int offset);
}

public record PartialParse(CstNode node, int endOffset) {}
```

`RuleId` lives in `org.pragmatica.peg.action`. It's the same marker interface
used by the 0.2.6 programmatic-action API, and — importantly — the exact
interface that generated parsers' nested `sealed interface RuleId` extends.
That means the same `RuleId` class value works against both the interpreter
and a generated parser.

## Resolution rules

The rule name is resolved from `ruleId.name()`. The default implementation
returns `getClass().getSimpleName()`, which matches the sanitized rule-name
convention used by `ParserGenerator`. For custom marker records that override
`name()`, the interpreter instantiates the class (requires a zero-arg
constructor) to invoke the override.

## Semantics

| Input condition | Result |
| ---------------------------------------- | ------------------------------------------------ |
| `ruleId` class unknown to the grammar | `Result.failure(SemanticError)` |
| `offset < 0 \|\| offset > input.length()` | `Result.failure(SemanticError)` |
| Rule matches prefix of `input[offset:]` | `Result.success(PartialParse(node, endOffset))` |
| Rule fails at `offset` | `Result.failure(UnexpectedInput)` from the normal parser error path |

`endOffset` is the absolute offset at which the rule stopped matching. When the
rule (or any nested rule) attaches trailing trivia, that trivia is included in
`endOffset`; the raw matched span is `partial.node().span()`.

The method reuses the packrat cache, trivia capture, and (for the runtime
interpreter) lambda/inline action machinery used by `parseCst`. There is no
state carried between calls — each invocation creates a fresh `ParsingContext`.

## Examples

### Interpreter

```java
record Number() implements RuleId {}

var parser = PegParser.fromGrammar("""
Number <- < [0-9]+ >
%whitespace <- [ \\t]*
""").unwrap();

// offset 0
var p1 = parser.parseRuleAt(Number.class, "42", 0).unwrap();
// p1.endOffset() == 2

// offset 2 in " 42 "
var p2 = parser.parseRuleAt(Number.class, " 42 ", 2).unwrap();
// p2.endOffset() == 4
```

### Generated parser

Generated CST parsers emit a public `parseRuleAt(Class, String, int)` method
that returns the generated parser's own `PartialParse` record. The dispatch
keys are the nested `RuleId` record classes:

```java
var parser = new MyGeneratedParser();
var partial = parser.parseRuleAt(MyGeneratedParser.RuleId.Number.class, input, offset)
.unwrap();
```

The generator emits a private `Map<Class<? extends RuleId>, Supplier<CstParseResult>>`
dispatch table populated once at construction, so each call is a constant-time
lookup.

## Limitations

- **Action replay is not incremental.** Actions run every time `parseRuleAt`
is invoked, the same as any other parse call. Incremental action replay
(avoiding re-running actions over unchanged subtrees) is out of scope.
- **No error recovery.** `parseRuleAt` uses the standard fail-fast path. If
you need `ADVANCED` diagnostics over a partial region, use the full
`parseCstWithDiagnostics` API.

## See also

- [`docs/incremental/SPEC.md`](incremental/SPEC.md) — full incremental reparse spec
- [`GRAMMAR-DSL.md`](GRAMMAR-DSL.md) — grammar syntax reference
- [`RuleId`](../peglib-core/src/main/java/org/pragmatica/peg/action/RuleId.java)
and
[`Actions`](../peglib-core/src/main/java/org/pragmatica/peg/action/Actions.java)
— marker type and programmatic action API
Loading
Loading