packages feed

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