Examples

Two small languages running in this page, and two large ones you can go and read. One parses chess notation, the other the grammars that constrain what a language model may say. Of the large ones, one composes existing grammar plugins into a configuration language, and the other builds a full programming language and its toolchain.

chess

PGN and SAN — the notation chess games are written in

Runtime
TypeScript · also ported to Go
Built with
@tabnas/parser
Source
github.com/tabnas/chess
The input, in full
[Event "Casual game"]
[Site "London ENG"]
[Date "1851.06.21"]
[White "Adolf Anderssen"]
[Black "Lionel Kieseritzky"]
[Result "1-0"]

1. e4 e5 2. f4 exf4 3. Bc4 Qh4+ 4. Kf1 b5 5. Bxb5 Nf6 6. Nf3 Qh6
7. d3 Nh5 8. Nh4 Qg5 9. Nf5 c6 10. g4 Nf6 11. Rg1 cxb5 12. h4 Qg6
13. h5 Qg5 14. Qf3 Ng8 15. Bxf4 Qf6 16. Nc3 Bc5 17. Nd5 Qxb2 18. Bd6 Bxg1
19. e5 Qxa1+ 20. Ke2 Na6 21. Nxg7+ Kd8 22. Qf6+ Nxf6 23. Be7# 1-0
[Event "Casual game"] [Site "London ENG"] [Date "1851.06.21"] [White "Adolf Anderssen"] [Black "Lionel Kieseritzky"] [Result "1-0"] 1. e4 e5 2. f4 exf4 3. Bc4 Qh4+ 4. Kf1 b5 5. Bxb5 Nf6 6. Nf3 Qh6 7. d3 Nh5 8. Nh4 Qg5 9. Nf5 c6 10. g4 Nf6 11. Rg1 cxb5 12. h4 Qg6 13. h5 Qg5 14. Qf3 Ng8 15. Bxf4 Qf6 16. Nc3 Bc5 17. Nd5 Qxb2 18. Bd6 Bxg1 19. e5 Qxa1+ 20. Ke2 Na6 21. Nxg7+ Kd8 22. Qf6+ Nxf6 23. Be7# 1-0

Anderssen–Kieseritzky, London, 21 June 1851 — the Immortal Game. Use the controls, the arrow keys, or click any move. The notation below the board is editable: change it and the board re-parses as you type, so you can watch the grammar accept and reject. The board, the move list and the parser are one 37 kB script with no other requests.

That text is the whole input: a PGN game, parsed by @tabnas/chess into tag pairs, moves and a result. Everything on the board comes from the parse — the players and date above it are the tag pairs, and each move comes back as structure rather than as a string: Nxg7+ is a knight, a capture, a destination of g7 and a check. That is what makes the move list clickable and lets the board follow along.

It is also a good example of where a parser stops. Nf3 names a piece and a square but not which knight; only the position knows, and the grammar has no position. So the plugin does not pretend to have a board, and the component supplies the missing half: a legal move generator that resolves each parsed move against the position it is played in. Parsing the notation and playing the game are two jobs, and only one of them is the engine's.

gbnf

GBNF — the grammars that constrain what a model may say

Runtime
TypeScript · also ported to Go, C ABI, Python
Built with
@tabnas/parser @tabnas/bnf
Source
github.com/tabnas/gbnf

Both boxes are editable, and the verdicts re-run as you type. Every character counts: GBNF is scannerless, so yes  (90%) is rejected for the second space. Break the grammar itself and you get a compile error instead of verdicts, which is the distinction the tool exists to keep.

GBNF is llama.cpp's notation for constrained decoding: at each step the sampler masks every token that would leave the grammar's language, so the model cannot emit malformed output. XGrammar reads it too, and therefore so do vLLM and SGLang, along with KoboldCpp, LocalAI and node-llama-cpp.

In all of them the grammar runs inside generation and nowhere else. None can answer does this string match my grammar? without loading a model, which leaves two questions open. Text produced without the grammar was never checked against it. And a grammar can compile, constrain a model perfectly, and still accept a different language than its author meant. That is what this parses GBNF for: the grammar becomes an ordinary tabnas grammar, so you can run known-good samples through it before a model ever sees it.

It is also the front end that pushed the engine furthest. GBNF describes input one character at a time while tabnas is a tokenising parser, so the compiled grammar has to switch off every default matcher and empty the ignore set. Those lexer settings are part of the accepted language rather than a tuning choice, which is why the package owns them and the shared @tabnas/bnf compiler owns everything downstream of the IR.

aontu

A JSON structure unifier, in TypeScript

Runtime
TypeScript · also ported to Go
Built with
@tabnas/jsonic@tabnas/expr@tabnas/directive@tabnas/multisource@tabnas/path@tabnas/debug
Source
github.com/rjrodger/aontu

Aontu unifies JSON structures: you write schemas and unification rules, and it folds many partial configurations into one consistent result. The language is a purpose-specific dialect of CUE — compact enough to write inline (`a:1 b:$.a`), with file evaluation and a REPL.

It is the clearest example of what the plugin model is for. Aontu defines no parser of its own. It starts from the jsonic grammar and layers on the pieces it needs: expressions for computed values, directives for `@`-forms, multisource so one document can pull in others, and path tracking so a reference like `$.a` knows where it is. The language is assembled out of grammar plugins rather than written from scratch.

boru

A typed, word-based query language, in Go

Runtime
Go
Built with
@tabnas/parser@tabnas/jsonic@tabnas/abnf@tabnas/csv@tabnas/expr
Source
github.com/boru-lang/boru

A boru program is a sequence of words, and a word takes its arguments where you write them: `add 1 2` reads left to right like an ordinary call, while binary operations also read naturally infix (`10 sub 3`). It aims to keep the composability of concatenative, stack-based languages without making you track the stack in your head.

The language is substantial — hierarchical types with typed signatures driving dispatch, lists and maps and records, hygienic macros, concurrency — and the toolchain around it is too: parser, type checker, formatter and LSP server all ship in one CLI. That whole front end is built on the tabnas engine in Go, which makes boru the answer to whether this scales past config formats.

The grammars themselves

The grammar packages are worth reading as examples in their own right, because most of them are short and each one extends another. The clearest chain is JSON: json is a handful of rules, jsonc adds comments to them, and jsonic relaxes the quoting from there. For a grammar compiled from a specification rather than hand-written, see proto, which parses the Protocol Buffers IDL from an ABNF grammar.

The full list is on the releases page, and the docs build two grammars from nothing — one standalone, one on top of an existing plugin.

Built something?

If you've used tabnas for anything — a config dialect, a log format, a query language, an experiment that didn't work — it's worth saying so in Discussions. Real usage is the most useful thing a project this early can get, and this page is short precisely because there isn't much of it yet.