Compiles a grammar file to a plain .ex file, once, ahead of time. This is the recommended way to use Ichor for anything shipping to production — see the tutorial for the full reasoning, or the cheatsheet for a quick-reference version of everything on this page.

mix ichor.gen PATH_TO_GRAMMAR --module MODULE --actions ACTIONS_MODULE --out PATH [--root NAME]

It runs the exact same parse → analyze → codegen pipeline as use Ichor, grammar: ..., actions: ..., but as a one-time command instead of a macro re-run on every mix compile, writing the result to disk as an ordinary module you check in like any other source file.

Options

OptionRequiredMeaning
--moduleYesThe generated module's name, e.g. MyApp.Calculator.
--actionsYesThe Ichor.Actions module the generated run/1,2 dispatches to, e.g. MyApp.Calculator.Actions. Baked in as a literal reference — it doesn't need to exist yet, only when the generated file's own callers run.
--outYesPath to write the generated module to.
--rootNoThe entry rule's name. Only meaningful for non-Aether grammar styles (see below) — Aether always uses its own @root pragma.

Example: a basic grammar

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

Open lib/calculator.ex afterward and it reads like ordinary, hand-written Elixir — tokenize/1, parse/1, run/1,2, plus whatever private helper functions the codegen backend needed, all emitted as def (not defp, unlike use Ichor's macro-expanded output — see "Why every helper is public" below):

# Generated by:
#
#     mix ichor.gen calculator.aether --module Calculator --actions Calculator.Actions --out lib/calculator.ex
#
# Do not edit by hand -- rerun the command above instead.

defmodule Calculator do
  @moduledoc false
  # tokenize/1, parse/1, run/1, run/2, ...
end

Calculator.run("2 + 3 * 4")
#=> {:ok, 14}

Rerun the same command whenever calculator.aether changes — there's no automatic staleness check between the checked-in file and its source grammar, so it's worth wiring into a mix.exs alias of your own (a gen.grammar alias calling "ichor.gen ...", say) rather than typed by hand each time.

Example: keeping ichor out of production

The whole point of generating ahead of time is that the output only ever calls a small, fixed set of support modules by name — no macro, no compile-time dependency on ichor itself. That fixed set is exactly what the independently-published ichor_runtime package is, so a project that only ever consumes pregenerated parsers can mark ichor dev-only:

def deps do
  [
    {:ichor_runtime, "~> 0.2"},
    {:ichor, "~> 0.2", only: :dev, runtime: false}
  ]
end

Contrast with use Ichor: because that macro needs the real Ichor module loaded to expand, ichor has to be a genuine dependency in whatever environment compiles it — Mix won't load an only: :dev dependency under MIX_ENV=prod, so a mix release build (which compiles under :prod by default) fails outright if any module still says use Ichor while ichor is dev-only. mix ichor.gen-generated code has no such constraint.

Example: importing a non-Aether grammar

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)
mix ichor.gen http_request_line.abnf \
    --module HttpRequestLine \
    --actions HttpRequestLine.Actions \
    --out lib/http_request_line.ex \
    --root request_line

--root overrides the source's own first-declared rule as the entry point — here request_line, since ABNF has no @root pragma of its own to say so. Two other differences from Aether source apply to every non-Aether style: 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 written), and an Ichor.Actions module written against the generated parser only ever sees the default fallback capture shape (Ichor.Node/single-capture passthrough) — never a named capture, since none of these formats have 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).

Example: overriding the detected style with @style

An @style line anywhere in the source wins over the extension-based guess — useful for a .txt grammar, or one whose extension doesn't match its actual format:

@style abnf

request-line = method SP request-target SP http-version CRLF
mix ichor.gen request_line.txt \
    --module RequestLine \
    --actions RequestLine.Actions \
    --out lib/request_line.ex

@style accepts abnf, bnf, ebnf, peg, or aether, and is stripped before the source reaches whichever parser actually runs, so it doesn't need to be valid syntax in the target format at all.

Why every helper is public

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 as unused even when a particular grammar's shape leaves some unreferenced (a predefined token like HEX compiles a lexer function whether or not any rule in this grammar uses it). Written out as ordinary source instead, the same defps would trip --warnings-as-errors on an unrelated grammar-shape detail — def sidesteps that.

See also

  • Tutorial §9 — the full worked example, alongside use Ichor and the raw Grammar.VM pipeline for comparison.
  • Cheatsheet — quick reference once you already know which path you want.
  • mix ichor.tokens — inspect a grammar's tokens without generating anything.