rtk-0.11: CHANGELOG.md
# Changelog
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.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.11] - 2026-06-12
Highlights: RTK is now self-hosting by default — grammar files are parsed
with the front end RTK generated from its own grammar description,
`test-grammars/grammar.pg` is the authoritative definition of the grammar
language, and the hand-written front end is a reference oracle selected by
`--use-handwritten`. Errors are structured, positioned diagnostics rendered
in GNU one-line style; generated ASTs carry equality-transparent source
positions. A reworked test architecture — golden snapshots, dual-front-end
equivalence, a compile gate over every snapshot — enforces all of it.
Quasi-quoters gained a `$$` escape and honest context errors; packaging
gained PVP bounds; CI a pinned, cached toolchain.
### Added
- Early-warning CI job (`ghc-latest`): builds with `-Werror` and runs the
cabal test suites once against the newest GHC series the CI runner image
ships (currently 9.14), relaxing only rtk's own bounds on the GHC boot
libraries. Warnings a new GHC promotes into `-Wall` (or turns on by
default) now surface on every push instead of at the next toolchain bump;
the pinned primary job stays authoritative. rtk's own code is kept
warning-clean portably: pattern matches instead of `head` (9.8 warns on
every `head`/`tail` use, `-Wx-partial`), and the no-op `Typeable`
derivings dropped (9.14 promotes `-Wderiving-typeable` into `-Wall`;
explicit `Typeable` deriving has been meaningless since GHC 7.10). The
compiled-in generated snapshot gets a scoped, GHC-gated `-Wno-x-partial`
(generated code is not edited to placate
linters - the lexer generator emitting `drop 1` instead of `tail` is
tracked as follow-up work, since it churns every golden)
- Provenance banner: every generated artifact (`<Name>Lexer.x`,
`<Name>Parser.y`, `<Name>QQ.hs`) now opens with `-- Generated by RTK from
grammar '<Name>'. Do not edit by hand.` The banner deliberately names the
grammar rather than the grammar file (a path would make the output - and
the golden snapshots - depend on where the grammar lives) and carries no
rtk version (a version would couple every release bump to a full golden
churn)
- `make test-compile-goldens`, wired into CI after the cabal test suites:
runs `alex -g` and `happy --ghc` over every checked-in golden
`<Name>Lexer.x`/`<Name>Parser.y` pair and typechecks the result with
`ghc -fno-code`. The golden suite diffs text only and sat green while the
debug-test and t1 snapshots did not compile; the gate closes that
test-architecture hole (QQ goldens join once the regex-posix dependency
is dropped, task 8b)
- Source positions in generated ASTs: every constructor that generated
parsers build (except the quasi-quotation-only `Anti_*` splice
constructors) now stores the position of its alternative's first symbol
in a leading `RtkPos` field. `RtkPos` is transparent for equality and
ordering (`==` always holds, `compare` is always `EQ`), so ASTs that
differ only in source positions compare equal — in particular a
quasi-quote parsed at compile time still matches the same construct
parsed at run time, and quasi-quote *patterns* wildcard every position
field while *expressions* may embed them. Payload-carrying `%token`
bindings now bind the whole positioned token, with generated `tkVal_*`
extractors recovering the payload in semantic actions; a generated
`RtkPosOf` class projects the position of any symbol (tokens,
nonterminals, lists, optionals). Generated code stays dependency-free.
The self-hosted front end now maps the rule constructors' positions into
`getIRulePos` (previously `Nothing` under `--use-generated`), so
pipeline diagnostics carry real `FILE:LINE:COL:` positions with both
front ends, and the dual-front-end AST equality suite compares positions
too. The hand-written parser's position for the `'.' id ':' …` rule form
moved from the identifier to the leading dot (a rule's position is where
the rule starts), aligning it with first-symbol capture; no corpus
grammar uses that form
- `--debug-rule RULENAME` is back, this time with a real implementation
(it was removed together with the other never-implemented placeholder
flags): it traces one rule through the pipeline, printing only that
rule's representation after each stage — its mentions in the token
stream (with positions), the matching `IRule`s after parsing and after
string normalization (making literal-to-`!tok_*` rewrites visible), and
the matching rule groups/lexical rules after clause normalization and
constructor filling — instead of full-grammar dumps. `--expand-rule`
shows a rule's final expanded form; this flag shows its evolution. When
the rule is missing at a stage the trace says so and lists up to five
case-insensitive near matches (normalization renames things — `Rule_N`,
`ListElem_*`, `tok_*`); a rule found at no stage at all fails the run
with exit code 1, so typos don't go unnoticed in scripts. Composes with
`--debug-stage` (stop early) and works under `--use-generated`, where
the token stage is internal to the generated front end and the trace
starts after parsing
- Self-hosting milestone (Prototype 2 closed): `rtk --use-generated` parses
grammar files with the lexer/parser RTK generated from
`test-grammars/grammar.pg`. The generated modules are compiled into rtk
straight from the checked-in golden snapshot (`test/golden/grammar/`, the
bootstrap stage produced by the previous rtk binary, kept current by `make
accept-golden`), and `src/generated/ASTAdapter.hs` converts the generated
AST to the hand-written `InitialGrammar`; everything after parsing is the
same shared pipeline. The golden suite now runs every grammar in
`test-grammars/` through BOTH front ends and requires byte-identical
artifacts, and the unit suite asserts AST equality (positions stripped)
for every grammar. The fixed point holds: `rtk --use-generated
test-grammars/grammar.pg out/` regenerates `test/golden/grammar/` exactly.
Accepted divergences (documented in BOOTSTRAP.md): errors carry positions
in the message text rather than structured diagnostics, `getIRulePos` is
not captured (both converge in task 7b), no nested `/* /* */ */` comments
(#25), no concatenation of adjacent `"""…"""` blocks. The harness also
surfaced two constructs the hand-written parser accepts beyond
grammar.pg's own definition of the language — empty alternatives
(`Gd = | ExpI ;` in haskell.pg) and redundant parentheses kept as
semantic grouping (`(ImportStatement)*` in java.pg, `(A B) C` in t1.pg) —
so those three grammars are pinned in the test suites
(`frontEndDivergentGrammars`) with a guard that fails once they stop
diverging; resolving which front end defines the language is follow-up
work
- Generated parsers, lexers and quasi-quoters report errors with `Either`
instead of throwing (the deferred "Stage G" of the diagnostics
migration): generated parsers use `%monad { Either String }`, so
`parse<Name> :: [PosToken] -> Either String <AST>` mirrors the shape of
the hand-written grammar parser; generated lexers expose
`scanTokens :: String -> Either String [PosToken]` (`alexScanTokens`
stays as a throwing compatibility wrapper); and the generated
quasi-quoters route lexer, parser and unknown-metavariable failures
through `fail` in `TH.Q`, so a bad quasi-quote is now a positioned GHC
compile error at the splice site instead of a runtime crash during
Template Haskell expansion. Generated code stays dependency-free
- Duplicate rule definitions are rejected: defining the same rule name twice
in a grammar is now a normalization error carrying both source positions
(e.g. `g.pg:3:1: error: in rule 'Foo': rule 'Foo' is defined more than
once (first definition at line 2, column 1)`), instead of the definitions
being silently merged into one rule group with extra alternatives
(closes #20). `test-grammars/debug-test.pg`, the one grammar that relied
on this, now defines `IfStatement` once with `|` alternatives
- A `$$` escape in quasi-quote bodies: `$$name` now produces the literal
text `$name` instead of being rewritten as a metavariable (or rejected).
Previously the generated quasi-quoter rewrote `$ident` everywhere in a
quote body — including inside the quoted language's own string literals —
mangling programs. Each `$$` pair directly before a metavariable stands
for one literal `$` (so `$$$x` is a literal `$` followed by the
metavariable `$x`). The unknown-metavariable error now names the
offending `$name`, lists the known shortcuts and points at the `$$`
escape (see `docs/why-qq-limitations.md`)
- Hackage packaging hygiene: PVP version bounds on all dependencies and on
the `alex`/`happy` build tools, `Tested-With` now lists GHC 9.4.7 and
9.6.4 (the versions actually exercised locally and in CI), and the
packages required by RTK-generated code are documented in the README and
the cabal description
- Structured diagnostics: grammar errors are now `Diagnostic` values
(message plus optional source position and context) threaded through
`Either`, instead of being thrown with `error`. The grammar-processing
pipeline functions (`scanTokens`, `parse`, `normalizeTopLevelClauses`,
`genX`/`genY`/`genAST`/`genQ`) are total for user-caused failures
- One-line GNU-style error reporting: `rtk` prints `FILE:LINE:COL: error:
MESSAGE` to stderr and exits 1 for a bad grammar, with no Haskell exception
or call stack (closes #23 and #31)
- A lifted (`,`) clause under `*`/`+`/`?` (e.g. `Foo = ,Bar* ;`) is now
rejected with a clear error during normalization, instead of silently
generating broken code
- A reference to an unknown rule now names both the unknown rule and the type
that references it
- Source positions on tokens: the lexer now returns `PosToken` values
(token plus line/column), both in the hand-written lexer and in all
generated lexers
- Parse errors report line, column and a human-readable description of the
unexpected token (e.g. `Parse error at line 2, column 1: unexpected
identifier 'Foo'`); generated parsers report positions too instead of
dumping the remaining token list
- Errors at end of input carry the position where the input ended, in both
the hand-written and generated parsers
- Grammar normalization errors name the offending rule and its source
position (e.g. `Grammar error in rule 'Foo' (at line 2, column 1): ...`)
- Lexer-generation errors name the lexical rule they occur in
### Changed
- **RTK is now self-hosting by default — the headline of this release.**
Grammar files are parsed with the front end RTK generated from its own
grammar description, and `test-grammars/grammar.pg` is the authoritative
definition of the grammar language. The hand-written `Lexer.x`/`Parser.y`
are demoted to a reference oracle selected with the new
`--use-handwritten` flag (`--use-generated` remains accepted and is now a
no-op); the equivalence harness keeps holding both front ends to identical
artifacts and equal ASTs for every corpus grammar, and the bootstrap fixed
point now holds for a *default* invocation: `rtk test-grammars/grammar.pg
out/` reproduces `test/golden/grammar/` byte-for-byte. Changes to the
grammar language land in grammar.pg (plus regenerated goldens) first; the
hand-written files follow only to keep the harness green. See BOOTSTRAP.md
- Front-end error parity (prerequisite of the default flip): generated
lexers and parsers now encode failures as machine-splittable
`LINE:COL:message` (previously prose like `Parse error at line L, column
C: …`), the shared `Diagnostics.diagnosticFromPositioned` splits the
encoding into a positioned diagnostic, and rtk renders the same GNU-style
`FILE:LINE:COL: error: …` line under both front ends — for lexical errors
the stderr line is identical character for character; for parse errors the
position is identical and only the token wording differs (generated
parsers render tokens generically). Generated quasi-quoters re-render the
encoding human-readably (`line L, column C: …`) in their `fail` path, as
do the standalone test drivers. Every golden `<Name>Lexer.x`/`<Name>Parser.y`
changed uniformly with the new error templates
- The two historic front-end divergences are resolved and the
pinned-divergence list (`TestSupport.frontEndDivergentGrammars`) is empty:
the hand-written reference parser now defines the same language as
grammar.pg — it rejects empty alternatives (`R = | X ;`) and lifts
redundant parenthesis groups exactly like grammar.pg's
`Clause5 = '(' ,Clause ')'` (single-leaf groups collapse, pure-sequence
groups merge at the head of a sequence, alternation groups merge as the
first alternative); haskell.pg's `Gd = | ExpI ;` was rewritten as the
equivalent `Gd = ExpI? ;`. java.pg, t1.pg and haskell.pg artifacts changed
accordingly (fewer proxy sub-rules, e.g. java.pg's `(ImportStatement)*`
now generates a plain list rule)
- The core pipeline data types (`InitialGrammar`, `IClause`,
`NormalGrammar`, …) moved from the footer of the hand-written `Parser.y`
into the new front-end-agnostic `Syntax` module; a demoted parser no
longer owns the project's core types. Pure move, no behavior change
- `--debug-tokens` in the default (generated) front end now dumps the
generated lexer's token stream (it previously printed nothing under
`--use-generated`)
- The Java grammar parses exactly ONE `ModifierList` per declaration. The
class/interface/enum/`@interface` rules no longer begin with their own
nullable list; `TypeDeclRest` (ex-`NestedTypeDeclaration`, now used at the
top level too) carries everything after the modifiers, and the list lives
on the enclosing rule: `TypeDeclaration = OptDocComment ModifierList
TypeDeclRest`, `FieldDeclaration = OptDocComment ModifierList
(MemberDeclaration | TypeDeclRest | StaticInitializer) | ';'`. The nested
nullable lists used to produce 14 shift/reduce conflicts (after the outer
list, every modifier/annotation token chose between extending it and
epsilon-starting the inner one) whose shift resolution parsed
`public class A {}` with `public` on the OUTER list and a confusing,
always-empty inner list; that always-empty `ModifierList` field is gone
from the class/interface/enum/annotation AST constructors, and the
generated quasi-quoter renames `nestedTypeDeclaration` to `typeDeclRest`.
Together with the restored dangling-else conflict (see Fixed) the
generated `JavaParser.y` is down to 18 shift/reduce + 0 reduce/reduce
conflicts (from 32 + 0), and java.pg now opens with a complete inventory
of all 18 - each family with its automaton items and why the shift is the
correct Java reading (dangling else; catch/finally attach to the nearest
try; member id vs empty TypeParameters; greedy CompoundName `.`;
bracket-list `[` shifts; `<` commits to type arguments; the QQ bootstrap
dummy bracket) - replacing the stale "~14"/"~13"/"32" conflict comments.
The commons-lang corpus success sets are unchanged (14/16 main/tests
files), so the parser blacklists stay as they are
- The makefile test pipeline now runs alex with `-g` (the GHC backend), so
generated lexers in `test-out/` compile as compact string-encoded tables
instead of ~half-a-million-line pattern matches. GHC's compile of the
Java lexer drops from minutes to seconds with identical token streams
(verified against the `test-lex-java` goldens). The library's own lexers
already used `-g` via cabal's preprocessor; this closes the gap for the
makefile-driven tests (#27)
- The `$Type:var` splice alternative is now attached to a minimal set of
rules of a shared-type group instead of to every rule. The normalizer
builds the group's lift graph (rule A → rule B when B has an alternative
that is exactly the single nonterminal A, ignoring nullable clauses) and
greedily picks attach points whose unit-closure covers every rule the
grammar demands from some position; a splice token reduces at an attach
point and climbs the chain to the level its position requires. For
java.pg's 18-rule Expression chain the single attach point is
`PrimaryNoPostfix`, which removes the 806 reduce/reduce conflicts
(of 883) the per-rule alternatives used to cause, along with the three
"rule ... is unused" happy warnings, and makes the parse of a splice
independent of conflict-resolution accidents. Types declared by a
single rule are unaffected. A
group whose demanded rules cannot be covered from one attach point gets
several; if their closures overlap, the overlap can reintroduce
reduce/reduce conflicts between the splice reductions (inherent to such
grammars; normalization has no warning channel to report them)
### Removed
- The `--debug-format` option: its only honest format was the `pretty`
default - `compact` merely stripped newlines into an unreadable single
line (and the advertised `json`/`tree` formats had already been removed
as never-implemented). Debug output is always pretty-printed now;
`DEBUG_OPTIONS.md` and `test-debug-options.sh` follow
- The orphan `PrintGrammar` module: a dead pretty-printing draft predating
the current pipeline, listed in the cabal file but imported by nothing
(pretty-printing returns properly as a tracked feature)
- The makefile's `Windows_NT` branch: its `dist/build/rtk/rtk.exe` was the
cabal-v1 path, dead for years - the makefile is Unix-only and now says so
- The superseded textual bootstrap comparison: `compare-bootstrap.sh`, the
`make test-bootstrap` target and its informational CI step. Behavioral
equivalence of the two front ends is enforced by the golden/unit harness
on every test run; textual identity of the generated `.x`/`.y` with the
hand-written ones was never the goal
- Unimplemented CLI options that were advertised in `--help` but had no
effect: `--debug-rule`, `--compare-stages`, `--memory-stats`,
`--debug-output-dir`, `--debug-log`, `--interactive`, the placeholder
`json`/`tree` debug formats, and the `--use-generated` stub that only
printed an error
### Fixed
- Generated quasi-quoters' `quoteType`/`quoteDec` no longer lie: they used
to silently return dummies (`TH.ListT` / `[]`), so a quote in a type or
declaration context compiled into nonsense. Both now `fail` in `TH.Q`,
making the misuse a GHC compile error at the splice site ("this
quasi-quoter cannot be used in a type/declaration context")
- Writing into a missing output directory no longer escapes as a raw
`IOException`: `rtk g.pg /tmp/does/not/exist` now creates the directory
and succeeds; IO failures that remain (no permission, a file where the
directory should be) are rendered as the usual one-line GNU-style
diagnostic and exit 1
- A literal backslash inside a `[...]` character class is now emitted
Alex-escaped (`\` → `\\`) by the lexer generator; it used to pass through
raw, where Alex set syntax reads it as an escape, forcing grammars onto
the `[\x5C]` hex spelling (issue #95's smaller finding). The `\n \t \r \f
\v` pairs that token post-processing preserves keep passing through bare.
java.pg drops its hex workarounds for a `backslash` charset macro and a
plain `[^\"\\\n\r]` negated class; generated Java token streams are
byte-identical (the `test-lex-java` goldens are untouched). One
grammar-lexer limitation remains, documented at the macro definition: a
class ending in a backslash must also end its source line, because `\]`
lexes as an escape pair
- `GenAST`'s unreachable backstop for lifted clauses claimed "lifted rules
are not yet implemented" - a missing feature it is not; reworded to the
`Internal error (GenAST): …` convention (Normalize rejects or filters
every lifted position before AST generation)
- A type declared only through rule annotations (`Thing : Item = …` with no
rule named `Thing`) could not be referenced: a plain `S = Thing ;`, a list
element `Thing* ~ ','`, or merely the QQ start wrapper (which references
every public type by name) failed generation with `reference to unknown
rule 'Thing'`. rtk now synthesizes the cover rule that grammar authors
wrote by hand for this (java.pg's
`Expression : Expression = AssignmentExpression ;`), in lifted form —
`Thing : Thing = ,Item1 | ,Item2 ;` — so the type gets a nonterminal
without an extra AST constructor. Synthesis is demand-driven (a grammar
that never references the bare type is generated byte-identically) and
happens on the parsed grammar before normalization, so the cover takes
part in the splice attach-point machinery like a hand-written one: the
`$Thing:var` alternative lands on one annotated rule and climbs to the
type through the cover, and the covered type gets a top-level quoter.
LALR conflicts of the synthesized cover are exactly those of its
hand-written equivalent (overlapping FIRST sets among the annotated
rules remain the grammar's own, and happy still reports them). The
remaining unresolvable case — referencing a lexical rule's *value* type,
which declares no syntax type — keeps a diagnostic that now explains the
type-named-rule convention. New corpus grammar `test-grammars/i14.pg`
pins all reference shapes in the golden suite and compile gate, and
`make test-i14-qq` (wired into CI) exercises quoters and splices against
the covered types (#14)
- A grammar whose start rule is a repetition (`Start = Item* ;`) generated a
parser that did not compile: the QQ start-wrapper alternatives extended
the start group as a `data` declaration while the repetition typed the
same nonterminal as the alias `[Item]` (plus a dummy-token shift/reduce
conflict against the empty list). Such an alias start group now skips the
QQ entry-point machinery entirely — no dummy tokens, wrapper alternatives
or top-level quoters, since an alias has no constructor to project a quote
through — while element-level `$Type:var` splices keep working. Groups
merely *referenced* by a wrapper alternative (grammar.pg's
`RuleList = Rule*`) were always well-typed and are unchanged. The
debug-test golden, uncompilable until now, is regenerated (#34)
- Repetition over a terminal silently generated uncompilable code:
`Foo = 'x'* ;` emitted a degenerate self-recursive `data Foo` against
list-building parser actions, and `Foo = num* ;` an invalid lowercase
`data num`. Decided as by-design and rejected during normalization with a
positioned diagnostic: a list element must be a syntax rule, because the
element type hosts the list's splice constructor — wrap the terminal in a
syntax rule and repeat that rule instead. A lifted `(,X)` element under
`*`/`+` is rejected the same way instead of crashing the generator (#28)
- The synthesized QQ start wrapper is now emitted directly adjacent to the
rule it wraps: list-element proxy rules could separate the two same-named
rule blocks in the generated `.y`, which happy rejects outright
("Multiple rules for 'A'", t1.pg's golden was affected)
- A brace-less nested `if` parses again: `IfStatement`'s then-branch was
`StatementWithoutIf`, so valid Java like `if (a) if (b) f(); else g();`
was rejected with "unexpected 'if'". The then-branch is a full `Statement`
and `StatementWithoutIf` is inlined away (`Statement` absorbs its
alternatives; nothing else referenced it). This re-surfaces the classic
dangling-else shift/reduce conflict, which happy resolves by shifting:
`else` binds to the NEAREST `if`, exactly the JLS 14.5 rule (verified on
the printed AST; regression coverage via
`test-grammars/java/test-nested-if.java` / `make test-java-nested-if` and
a quasi-quotation construction case). The exclusion never even avoided
that conflict - it already existed through loop bodies in then-position,
e.g. `if (a) while (b) if (c) f(); else g();` - it only rejected the
direct nested form
- The Java grammar's last structural LALR ambiguity - deciding between a
type and an expression after a `CompoundName` at statement start or after
`(` - is resolved JLS-style (`happy` now reports 0 reduce/reduce
conflicts on the generated `JavaParser.y`, down from 2). Types and
declarators take only empty bracket pairs (`Dims`/`NonEmptyDims`,
anchored on the unreduced name so `[` is a plain shift); only array
creation takes sizing expressions; `CastExpression` uses the JLS trick
(`'(' Expression ')' UnaryExpressionNotPlusMinus` for reference casts
plus explicit primitive/generic/array alternatives); and array access is
anchored on `CompoundName` in `PrimaryNoPostfix`. `a[0] = 1;`,
`x = (a);`, `x = (Foo) y;`, `x = (List<String>) y;`, `((Map) c).get(k);`
and `new int[3][];` now parse; JLS-invalid `int[3] x;` declarations are
now rejected. Known residual limitation (shared by LALR Java parsers
generally): `(a < b)` as a parenthesized primary mis-commits to a
generic cast on `<` (conditions like `if (a < b)` are unaffected)
- `make test-p` works again: the target had a hand-rolled recipe that
compiled the driver with bare `ghc` outside the cabal package environment,
failing with `Could not find module 'Data.Generics'` in clean
environments. It now uses the generic `make-test-rule` like its sibling
targets (`cabal exec -- ghc --make -itest-out ...`); the driver binary is
consequently named `test-out/p-main` instead of `test-out/p-rtk`. The
compile failure had masked a latent bug in the driver itself:
`p-main.hs`'s `subst` bound `e1 = subst id e1 i` etc., self-referential
recursive lets that shadow the pattern variables and diverge (`<<loop>>`)
on the first fold expression, and it had no base case. The lets now bind
fresh names and a catch-all clause terminates the recursion, so the
driver prints the substituted AST
- `'\f'` and `'\v'` escapes in grammar string and `[...]` regex literals now
reach the generated lexer as bare Alex escapes; previously token
post-processing (`unBackQuote`) stripped them to the literal letters
`f`/`v` even though `GenX.isAlexEscape` was ready to emit them, so the two
escape sets disagreed. The preserved set (`\n \t \r \f \v`) is now pinned
to `isAlexEscape` by a unit test
- `test-grammars/haskell.pg` is self-consistent again: minimal `Pat` and
`QOp` rules were added for the previously dangling references, so RTK
generation no longer aborts on it (progress on issue #30; the grammar is
still far from full Haskell). The haskell special cases in the test suites
were dropped and golden snapshots added
- A grammar whose first rule is lexical (or has a data-type annotation
different from the rule name) no longer crashes with `fromJust: Nothing`;
it reports that the first rule must be a syntax rule, or resolves the
annotated type correctly
- Internal `fromJust` calls in code generation replaced with descriptive
internal-error messages
- Invalid clauses in lexical-rule macros now abort generation with an error
instead of writing the error text into the generated lexer
- User-facing errors no longer print a GHC call stack
- `--debug-stage` now exits with a success status after stopping at the
requested stage instead of reporting failure via `error`
- `--profile-stages` timings now force each stage's result to normal form,
so per-stage durations are no longer skewed by lazy evaluation
### Documentation
- Replaced the README "Grammar Format" example, which used Happy-style
semantic actions that RTK cannot parse, with a verified `.pg` example
- Removed the stale `Claude.MD` (a case-colliding near-duplicate of
`CLAUDE.md`; its Quick Reference table was folded into `CLAUDE.md`) and the
stray root `test-simple-return.java` duplicate
## [0.10] - 2025-12-03
### Added
- MIT license with generated code exemption
- Full Java grammar support with comprehensive parsing tests
- Quasi-quotation support for embedding parsed syntax in Haskell
- Debug options for grammar development and troubleshooting
- Bootstrap self-hosting capability (RTK can parse its own grammar format)
### Fixed
- Alex escape sequence generation in GenX.hs
- Java grammar lexer patterns for complete test coverage
## [0.9] - Initial Development
### Added
- Core grammar specification format (.pg files)
- Alex lexer generation (GenX.hs)
- Happy parser generation (GenY.hs)
- AST generation (GenAST.hs)
- Quasi-quotation generation (GenQ.hs)
- Grammar normalization and transformation