mix ichor.gen (Ichor v0.2.0)

Copy Markdown View Source

Runs the exact same parse -> analyze -> native-codegen pipeline as use Ichor, grammar: ..., actions: ... (Ichor.generate/3), but once, from the command line, writing the result to disk as an ordinary module instead of splicing it into a macro expansion.

$ mix ichor.gen calculator.aether \
    --module Calculator \
    --actions Calculator.Actions \
    --out lib/calculator.ex

The point is moving the expensive part -- parsing the grammar, running Grammar.Analysis, building an LR/GLR table if @engine calls for one, and generating Elixir functions from it -- out of the consuming app's own mix compile (where use Ichor would otherwise redo all of that on every compile) and into a one-time step whose output gets checked in like any other source file.

The generated module still calls a handful of small support modules by name (Ichor.Actions, Ichor.Error, Grammar.Native.Runtime.Parser, Grammar.Native.Runtime.Tokenizer, Grammar.VM.Token, and -- for an @engine lr/glr grammar -- the LR/GLR shift-reduce and GSS runtime) for capture dispatch, error formatting, and token matching, exactly as use Ichor-generated code does. That handful of modules is exactly what ichor_runtime is: a consuming app can depend on ichor_runtime as an ordinary runtime dependency and mark ichor itself only: :dev, runtime: false -- the Aether front-end, the format importers, Grammar.Analysis, the LR/GLR table builder, and the codegen backends themselves (the bulk of the library) genuinely never ship, including in a mix release build.

Every private helper function the codegen backend generates (tokenizer/rule sub-functions, LR/GLR dispatch clauses, ...) is emitted as def, not defp: through use Ichor, these come from macro expansion, which Elixir's compiler doesn't flag for being unused even when a particular grammar's shape leaves some unreferenced (e.g. a predefined token like HEX no rule actually uses); written out as ordinary source, the same functions would trip --warnings-as-errors on an unrelated grammar-shape detail.

Regenerate by rerunning the same command whenever the grammar changes -- there's no automatic staleness check between the checked-in file and its source grammar.

Grammar styles: Aether, ABNF, BNF, ISO EBNF, PEG

PATH_TO_GRAMMAR doesn't have to be .aether source -- ABNF, BNF, ISO EBNF, and PEG grammars work too, via Ichor.GrammarImport. The style is picked automatically from the file extension:

ExtensionStyle
.aether (or anything unrecognized)Aether (default)
.abnfABNF (RFC 5234 + RFC 7405)
.bnfClassical BNF
.ebnfISO EBNF (ISO/IEC 14977)
.pegPEG (Ford/pest/PEG.js convention)

Override the detected style with an @style line anywhere in the source (@style abnf, @style bnf, @style ebnf, @style peg, or @style aether) -- it's stripped before the source reaches whichever parser actually runs, so it doesn't need to be valid syntax in the target format at all.

None of ABNF, BNF, ISO EBNF, or PEG have Aether's own @root pragma, named-capture syntax (name:expr), or @skip auto-splicing convenience. For a non-Aether style:

  • The root is the source's own first-declared rule, unless overridden with --root NAME.
  • The grammar is always @noskip -- whitespace tolerance has to already be written explicitly into the source, matching how real ABNF/BNF/EBNF/PEG grammars are actually written.
  • An Ichor.Actions module written against the generated parser only ever sees the default fallback shape (Ichor.Node/single- capture passthrough) -- never a named capture, since the source format has no syntax for one.
  • For ABNF specifically, RFC 5234 Appendix B's own "core rules" (ALPHA, DIGIT, CRLF, ...) are filled in automatically for whichever of them the source references but never defines itself -- extremely common in real ABNF, which generally assumes those are always available. See Ichor.GrammarImport's own moduledoc for the one sharp edge this can't fully paper over (two genuinely-overlapping core rules both referenced by the same source).

Options

  • --module (required) -- the generated module's name, e.g. MyApp.Calculator.
  • --actions (required) -- the Ichor.Actions module the generated run/1,2 dispatches to, e.g. MyApp.Calculator.Actions. Not required to exist yet -- it's baked in as a literal module reference, resolved when the generated file's own callers run, not when it's generated.
  • --out (required) -- path to write the generated module to.
  • --root (optional, non-Aether styles only) -- the entry rule's name, overriding the source's own first-declared rule.