v0.3.3 — peglib-formatter framework
Final release in the 0.2.3 → 0.3.3 roadmap. New peglib-formatter module ships a Wadler-Lindig pretty-printer framework for grammar-driven formatters, closing peglib as a complete language-tool substrate: parse → incremental reparse → format.
Doc algebra
Sealed Doc interface with seven records (Text, Line, Softline, Group, Indent, Concat, Empty) + static builders in Docs. Canonical Wadler algebra implemented in ~200 LOC.
Fluent DSL
var formatter = Formatter.builder()
.rule("Block", (ctx, children) ->
group(text("{"), line(), indent(4, children.concat()), line(), text("}")))
.rule("ArgList", (ctx, children) ->
children.joinWith(text(","), line()))
.defaultIndent(4)
.maxLineWidth(100)
.build();
Result<String> output = formatter.format(cst);Rule lookup by CstNode#rule() name. Children are pre-formatted before the user's rule combinator runs.
Demo formatters
Three end-to-end demos with idempotency fuzz tests:
- JSON — objects with braces + indented members; arrays similar.
- SQL-like — clause-per-line, keywords upper-cased.
- Arithmetic — precedence-aware with minimal parentheses.
Each passes format(format(x)) == format(x) on fuzz inputs.
Limitations
Trivia preservation is best-effort in v1. Depends on 0.2.4's trivia attribution foundation, which still has a deferred rule-exit pos-rewind for trailing intra-rule trivia. Format output may not byte-equal source for all inputs today; full round-trip lands when the trivia foundation is completed.
Module layout
peglib-formatter depends on peglib-core. Installable independently or via the peglib-parent reactor.
Breaking changes
None. Strictly additive.