Skip to content

Repository files navigation

ebnf-viewer

Give it a W3C EBNF grammar and an input, and it shows you how that input is parsed: the parse forest, the Earley chart the parser actually built, railroad diagrams of the grammar, and what is wrong with the grammar itself.

It is a grammar comprehension and debugging tool. It does not generate parsers.

ebnf-viewer ./examples/json          # opens the workspace in your browser

Status: early. v0.1.0 is a working walking skeleton, not a finished product. The engine is real — Earley with a Scott-style SPPF, so ambiguous and left-recursive grammars are handled properly — but the performance apparatus described in docs/DESIGN.md is not built yet. See What is and is not here.


Why

Most grammar tooling answers "did it parse?" — this answers "how did it parse, and why not?"

  • Real grammars are ambiguous and you usually find out much too late. This tool parses with Earley and keeps every derivation in an SPPF, so it can point at the exact span that has two parses and show you both.
  • Real grammars are left-recursive, because that is how you write an expression grammar. Earley takes left recursion natively; nothing has to be rewritten to suit the tool.
  • When a parse fails, "unexpected token at offset 412" is not an answer. This reports the furthest position reached, the set of things that could have continued, and which constructs were still open — the { on line 2 that never closed.

W3C EBNF is scannerless: terminals are characters, so there is no lexer hiding between you and the grammar. What you see in the tree is what the grammar actually said.

Install

Not published to npm yet. From a clone:

pnpm install
pnpm build
node packages/cli/bin/ebnf-viewer.mjs ./examples/json

Requires Node ≥ 20.19.

Use

ebnf-viewer [dir]                            serve the workspace and open a browser
ebnf-viewer parse <grammar> <input>          print the parse tree
ebnf-viewer why   <grammar> <input>          explain why a parse failed
ebnf-viewer check <dir>                      grammar diagnostics; non-zero exit on error
ebnf-viewer init  [dir]                      write a starter grammar and sample

Flags: --start <Rule>, --port <n>, --no-open, --format text|json, --max-items <n>. Exit codes: 0 ok, 1 usage, 2 grammar has errors, 3 input failed to parse, 4 internal.

Parse a file

$ ebnf-viewer parse examples/expr/expr.ebnf examples/expr/sample.txt
Expr [0,11) "(1+2)*3-4/2"
  Expr [0,7) "(1+2)*3"
    Term [0,7) "(1+2)*3"
      Term [0,5) "(1+2)"
        Factor [0,5) "(1+2)"
          '(' [0,1) "("
          Expr [1,4) "1+2"
          ...

Note [0-9]+ appears in the tree as [0-9]+ — the synthetic rules that lowering creates to feed Earley never reach you.

See an ambiguity

$ ebnf-viewer parse examples/expr/expr-ambiguous.ebnf examples/expr/sample-ambiguous.txt
Expr [0,5) "1+2*3" amb×2
  ...
2 parses
1 ambiguity observed
  A001 Expr [0,5) ×2: "1+2*3"
    `Expr` derives "1+2*3" through 2 different alternatives (1, 2).

Find out why it failed

$ ebnf-viewer why examples/json/json.ebnf broken.json
broken.json:1:16: parse failed
1 | {"name":"ebnf" "stars":3}
                   ^
  found    '"'
  expected ',' | '}' | #x9, #xA, #xD or #x20
  unclosed object opened at 0 (line 1, col 1)
  unclosed members opened at 1 (line 1, col 2)

Check a grammar

$ ebnf-viewer check examples/diagnostics
broken.ebnf:3:25: error E301: undefined nonterminal 'Footer'
  Start   ::= Header Body Footer
                          ^^^^^^
  hint: add a production 'Footer ::= ...' or remove the reference

broken.ebnf:15:1: error E502: rule 'Loop' is not productive: it cannot derive any finite string
  hint: every alternative of 'Loop' reaches 'Loop' again; add a base case

broken.ebnf:19:1: warning W403: unit rule cycle: Unit -> Alias -> Unit
broken.ebnf:15:1: warning W411: rule 'Loop' is directly left-recursive: Loop -> Loop
  hint: left recursion is legal and fully supported by the Earley engine

The workspace

ebnf-viewer <dir> serves a three-column UI on 127.0.0.1: grammar, input, and a result column with five views. Hovering anything highlights the corresponding span in the other two columns. Edit a grammar file in your own editor and the browser updates.

View What it is for
Tree the parse forest, collapsed into a readable tree; ambiguous nodes carry a badge and you can pin which derivation to show
Diagnostics everything static analysis found, clickable to the source span
Railroad each rule as a syntax diagram, cross-linked to the grammar text
Chart the Earley sets themselves — which items were predicted, scanned and completed at each position
Ambiguity each ambiguous span with its shortest witness

What is and is not here

Working:

  • W3C EBNF frontend: ::=, |, juxtaposition, ? * +, ( ), '...' / "...", [a-z], [^...], #xNN, /* */, and A - B exclusion for character sets. Panic recovery, so one typo does not cost you the rest of the file. W3C spec constraint notes ([VC: ...]) are treated as trivia instead of being silently absorbed into the previous rule.
  • Codepoint alphabet throughout, so [#x10000-#x10FFFF] means what it says.
  • Earley recognizer with Aycock–Horspool nullable pre-completion, atomic string literals, and a zero-width iteration guard; Scott-style binarized SPPF with family dedup, reachability sweep and canonical numbering.
  • Ambiguity detection and reporting; ParseCount that says infinite with a witness cycle rather than lying.
  • Static analysis: undefined, duplicate, unused, unreachable, non-productive, nullable, unit cycles, and left recursion with the actual cycle path (reported as information, since Earley handles it).
  • Failure explanation with expected set, unclosed constructs, and partial literal matches.
  • Railroad SVG rendering, byte-stable, with no DOM measurement.
  • CLI, local server with watch mode, and the web workspace.

Not built yet (designed in docs/DESIGN.md, deliberately deferred):

  • The performance apparatus: struct-of-arrays arenas, Leo optimization, budget ladder with graceful degradation, browser worker, row paging. Large inputs will get slow before they get correct.
  • The journal-based step debugger (time travel, breakpoints, whyNot). The Chart view is the minimum slice.
  • Railroad path-taken overlay, the xref graph, and SVG export.
  • General A - B over nonterminal languages. Character-set exclusion is exact; anything else is approximated as the left operand and reported as W505.
  • Grammar editing in the browser, .ebnftrace artifacts, static build.

Design

docs/DESIGN.md is the full architecture — the parsing engine specification, the diagnostic catalogue, the trace model, and the roadmap. It is considerably more thorough than this MVP and is what the MVP is being grown into.

Develop

pnpm install
pnpm typecheck
pnpm test          # 87 tests over the named engine fixtures
pnpm build

The test suite is the interesting part: it pins the cases that catch silent tree bugs — Scott's hidden left recursion, S ::= S S | 'a' over aaaa giving exactly Catalan(3) = 5 parses, nullable iteration bodies terminating, and a property test that no synthetic symbol name ever escapes into anything a user can see.

License

MIT

About

Visualize how an arbitrary W3C EBNF grammar parses an input: parse forest, Earley chart, railroad diagrams and grammar diagnostics.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages