How a parsed Aether AST turns into an actual result -- a sandboxed
program's final value, a config's map/struct, a query's result set, or
transpiled output. One mechanism, used differently per grammar: a
grammar's Actions module only implements the rules/tokens it needs
custom behavior for (@optional_callbacks); everything else falls back
to the defaults below.
handle_rule/handle_token are never invoked directly by user code --
Grammar.VM builds the raw capture tree during matching, and this
module's evaluate/5 walks it, calling into the actions module (or the
default fallback) as it goes, threading context throughout.
evaluate_node/3 is the companion entry point for a raw capture node
that didn't come from the original parse -- a grammar with
macro-like features (LISP's own defmacro/expansion is the reference
case) needs to evaluate the result of its own expansion through this
same handle_rule/handle_token dispatch, not skip it. Not
Lisp-specific in principle: any grammar with macro-like features needs
this same re-entry point. Genuinely a runtime concern (called from
generated/hand-written Actions code while actually running a parse),
unlike Ichor.generate/3/Ichor.__using__/1, which only ever run at
compile time -- that split is exactly why this lives here, in
ichor_runtime, rather than alongside those two in ichor proper.
Summary
Functions
Eagerly invokes every capture's eval, in order, threading context
through -- the plain bottom-up fold that's correct for grammars with no
special-form semantics (most config/markup/query grammars, as opposed
to something like LISP's if/quote, which need to not evaluate an
untaken branch at all). A list-valued capture (the same name captured
more than once, e.g. inside a *) resolves to a list of values in the
same order. Halts and returns immediately on the first error.
The single entry point tying Grammar.VM's raw capture tree to a
grammar's Actions module: evaluates the root rule, then runs
finalize/1 over whatever context that produced. Returns the final
context alongside the value -- needed whenever more than one top-level
match must thread context forward (e.g. loading a multi-form source
file one top-level form at a time); callers that only need the value
can just ignore it.
Evaluates a raw capture node (as found on any Ichor.Capture.node, or
built directly by something like a macro's unreify) against
actions_module, starting from context. See this module's own
moduledoc for when you need this instead of an ordinary .eval thunk.
Types
@type captures() :: %{optional(atom()) => Ichor.Capture.t() | [Ichor.Capture.t()]}
@type context() :: term()
Callbacks
@callback finalize(context :: context()) :: :ok | {:error, [Ichor.Error.t()]}
@callback handle_token(token :: atom(), text :: String.t(), context :: context()) :: {:ok, term()} | {:error, Ichor.Error.t()}
Functions
@spec eval_all(captures(), context()) :: {:ok, %{optional(atom()) => term()}, context()} | {:error, Ichor.Error.t()}
Eagerly invokes every capture's eval, in order, threading context
through -- the plain bottom-up fold that's correct for grammars with no
special-form semantics (most config/markup/query grammars, as opposed
to something like LISP's if/quote, which need to not evaluate an
untaken branch at all). A list-valued capture (the same name captured
more than once, e.g. inside a *) resolves to a list of values in the
same order. Halts and returns immediately on the first error.
@spec evaluate(atom(), map(), module(), context(), capture_shapes()) :: {:ok, term(), context()} | {:error, Ichor.Error.t() | [Ichor.Error.t()]}
The single entry point tying Grammar.VM's raw capture tree to a
grammar's Actions module: evaluates the root rule, then runs
finalize/1 over whatever context that produced. Returns the final
context alongside the value -- needed whenever more than one top-level
match must thread context forward (e.g. loading a multi-form source
file one top-level form at a time); callers that only need the value
can just ignore it.
capture_shapes (from Grammar.VM.RuleCompiler.capture_shapes/1) says,
per rule, which capture names sit under a */+/{n,m} -- needed so
e.g. an op: that matched zero times still shows up as [], not as a
missing key indistinguishable from "this rule shape never has an
:op" (see that function's docs for why this can't just be inferred
from the raw tree at dispatch time).
@spec evaluate_node(Ichor.Capture.node_t(), module(), context()) :: {:ok, term(), context()} | {:error, Ichor.Error.t()}
Evaluates a raw capture node (as found on any Ichor.Capture.node, or
built directly by something like a macro's unreify) against
actions_module, starting from context. See this module's own
moduledoc for when you need this instead of an ordinary .eval thunk.