ADR-120: A Grammar’s Vocabulary Is One Page, an Action Is the Block After the Pattern, and Two Borrowed Names Go

Status: Superseded by ADR-296 — its D1-D3 are D32-D34 there Date: September 16, 2026 Target Version: Nikaia 0.0.8 Related: ADR-007 (grammars), ADR-082 (a grammar is entered by a call), ADR-009 (frames), Part II 10.1, 10.6, 10.7 and the new 10.8, language-review.md §3.6

1. Context

The grammar is the most original part of the language and the best-designed: scannerless, so a foreign language can be embedded; typed actions in the rule, where Ohm and pest keep them outside; commit points, so a corrupt line is an error and not a silent skip; frames, so a file can be cut blindly and parsed in parallel, which no other grammar language offers; build-time execution and a trust floor per format. None of that is in question.

What was missing is a page. The specification showed five built-in elements and the examples used fourteen; the sentences that say how whitespace is skipped, what WS is, what => does and what # "…" names lived in example comments, one per file. And two of the built-ins were the engine’s spelling for something the grammar can already say: tag("x") is "x", and digit1 is digit+. A third thing is a wart rather than a gap: the arrow that introduces a rule’s result type introduced its action too, on the same line.

The conventions themselves have precedent and stay: uppercase names are lexical as in ANTLR, WS steers skipping as pest’s WHITESPACE does, => is the cut, and # "…" names a rule for its failure.

2. Decisions

D1 — Part II 10.8 lists every element a grammar may write, one line each, and it is complete.

An element that is not on the page is not in the language. The page is the reference the examples’ comments were standing in for; it is normative, and a built-in the engine offers that the page does not name is not reachable from a .nika grammar.

D2 — A rule’s action is the block after its pattern, with no second arrow.

rule NAME -> &str = s:until(";" | frame_end) { s }
rule pair -> Pair = key:string ":" => val:value { Pair { key, val } }

The one -> a rule writes is its result type, as a function’s is. A bare { after a pattern can only begin the action, because a brace delimiter in the input is written { … } around a pattern and a brace bound starts with an integer; the three are told apart where they stand, as 10.8 says. Every alternative carries its own block, as before.

D3 — tag and digit1 are not spelled.

A literal is "x"; one or more digits is digit+, which yields the text it matched as every character-class repetition does. The engine’s names are not names in a .nika grammar, and a grammar that writes one is refused with the spelling to use.

3. Consequences

4. What this does not decide

5. What is built

Nothing. Grammars write -> { … } for the action, and the vocabulary is the engine’s. The order of work:

  1. the parser: the action block after the pattern, the arrow form refused with the spelling; tag and digit1 refused with theirs;
  2. the emitter writing the engine’s arrow;
  3. the specification’s grammar blocks and the 53 arrows in examples/.

#202 carries it.

This site is open source. Improve this page.