# `Ichor.Actions`
[🔗](https://github.com/joetjen/ichor_runtime/blob/v0.5.0/lib/ichor/actions.ex#L1)

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 (an
`Ichor.Capture.raw_captures/0` list, in true source order at every
level -- never a plain map, which Elixir/Erlang make no cross-version
iteration-order guarantee about), 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.

# `capture_shapes`

```elixir
@type capture_shapes() :: %{optional(atom()) =&gt; MapSet.t(atom())}
```

# `captures`

```elixir
@type captures() :: %{optional(atom()) =&gt; Ichor.Capture.t() | [Ichor.Capture.t()]}
```

# `context`

```elixir
@type context() :: term()
```

# `finalize`
*optional* 

```elixir
@callback finalize(context :: context()) :: :ok | {:error, [Ichor.Error.t()]}
```

# `handle_rule`
*optional* 

```elixir
@callback handle_rule(rule :: atom(), captures :: captures(), context :: context()) ::
  {:ok, term(), context()} | {:error, Ichor.Error.t()}
```

# `handle_token`
*optional* 

```elixir
@callback handle_token(token :: atom(), text :: String.t(), context :: context()) ::
  {:ok, term()} | {:error, Ichor.Error.t()}
```

# `eval_all`

```elixir
@spec eval_all(captures(), context()) ::
  {:ok, %{optional(atom()) =&gt; term()}, context()} | {:error, Ichor.Error.t()}
```

Eagerly invokes every capture's `eval`, in true source order (each
`%Capture{}`'s own `position`, not `captures`' incidental map
iteration order -- see `Ichor.Capture`'s moduledoc for why those two
can differ), 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.

A `%Capture{}` built by hand rather than via `dispatch_rule/5`
(directly constructing this map for a test, say) defaults every
`position` to `0` -- ties between those fall back to whatever order
`captures` itself happens to enumerate in, same as this function's
behavior before `position` existed. Give hand-built captures distinct
`position`s if you need this function's ordering guarantee to hold
for them too.

# `evaluate`

```elixir
@spec evaluate(
  atom(),
  Ichor.Capture.raw_captures(),
  module(),
  context(),
  capture_shapes(),
  keyword()
) ::
  {: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).

## Options

  * `:locator` -- the parse's `Grammar.Source.Locator`. With it, every
    capture the parser recorded the place of (`Ichor.Capture.at/3`)
    gets its `span`, a default `Ichor.Node` gets its own, and an
    action's error with no position is placed (below). Without it,
    every span is `nil` and errors are returned as the actions gave
    them.
  * `:range` -- the token range `{from, to}` the root rule matched;
    the whole stream by default. A sequence of top-level matches
    gives each one its own.

## An action's error says where

An error a `handle_rule/3` or `handle_token/3` returns with no line
is placed where that rule or token matched, and shown with the line
it is on and a caret; nothing else about it changes. The action best
placed to say where is the one that refused, so an error that already
has a position -- placed by a nested rule's own action, or by the
action itself -- keeps it. `finalize/1`'s errors belong to no rule
and are left as they are.

# `evaluate_node`

```elixir
@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.

A node carries no positions, so every capture under it has a `nil`
span, and an error from it is returned as the action gave it.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
