# `Statifier.Compiler.Expressions`
[🔗](https://github.com/riddler/statifier-ex/blob/v2.0.0/lib/statifier/compiler/expressions.ex#L1)

The expression-compilation seam: compiles raw predicator source - a
`cond`, an `expr`, or a `<content>` text body - into the single
`Machine.expr()` sum type, on its own, before either of its two consumers
(the transition pass's transitions, the executable-content pass's
content/donedata) so neither lands a raw-string field and changes its type
afterwards.

ADR-0014 item 1 commits to spans, not point positions: every compile entry
point in this module calls a `_with_spans/1` variant, never a
`_with_positions/1` one - `compile/3` calls `Predicator.compile_with_spans/1`,
and `compile_program/3` calls `Predicator.compile_program_with_spans/1`. The
program path's point-position stopgap is closed. Item 2 already
settled the compiled shape - `%Predicator.Compiled{}` threads its own
`positions` table alongside `instructions`, so there is no separate table for
this module to carry or drop.

# `owner_ref`

```elixir
@type owner_ref() ::
  {:transition, non_neg_integer()}
  | {:content, non_neg_integer()}
  | {:donedata, non_neg_integer()}
  | {:data, non_neg_integer()}
  | {:global_script, non_neg_integer()}
  | {:invoke, non_neg_integer(), non_neg_integer()}
```

Identifies the node a compiled expression belongs to, in the same
ADR-0012 constraint-3 index space `Statifier.Compiler.Error` names an
owner by: a transition's `t_index`, a content node's `c_index`, a final
state's own index for its `<donedata>`, or a top-level `<script>`'s
document-order position. Nothing calls `compile/3` with a real index
yet - the transition pass wires transitions, the executable-content pass
wires content and donedata - so this phase's own tests exercise the type
directly with placeholder indexes.

`{:global_script, non_neg_integer()}` (ADR-0026 Decision 7) is
a top-level `<script>` compiled via `compile_program/3` into
`Machine.global_scripts` - it has no `c_index`, `t_index`, or state index
to name it by, since it is not addressed through the block runner's
`contents` tuple at all (`Machine`'s own "why `global_scripts` is
different" moduledoc section). Reusing `{:content, c_index}` for it would
put a false index into the ADR-0012 index space, naming a block-runner
slot that does not exist for this node; the integer here is instead the
script's own position among `document.scripts`, in document order.

`{:invoke, state_index, invoke_index}` names one `<invoke>` element's own
attributes and children (`type`/`typeexpr`, `src`/`srcexpr`, a `<param>`,
a namelist entry, `<content>`) - `state_index` is the owning state, and
`invoke_index` is that invocation's position in the state's own `invoke`
list, mirroring `{:donedata_param, state_index, param_index}`'s shape
(`Statifier.Event.Cause.origin/0`).

# `compile`

```elixir
@spec compile(
  source :: String.t(),
  owner :: owner_ref(),
  location :: Statifier.Parser.Location.t()
) ::
  {:ok, Statifier.Machine.expr()} | {:error, Statifier.Compiler.Error.t()}
```

Compiles `source` into a `Machine.expr()`.

`owner` identifies the node `source` came from (for the error case only -
a successful compile carries no owner, since nothing downstream needs one
until evaluation fails). `location` is the document `Location` a failure
is reported against: the attribute *value* span
(`attribute_locations[:cond]` / `[:expr]`) when the author wrote the
attribute, the owning node's own `location` otherwise - the caller's
choice, not this function's.

On success, `compile_with_spans/1`'s `%Predicator.Compiled{}` is stored
whole - its `positions` table is a span table (ADR-0014 item 1), never
reduced to point positions and never re-supplied via a `:positions`
keyword (item 2: passing both to `evaluate/3` raises).

On failure, `compile_with_spans/1` returns
`{:error, %Predicator.Errors.ParseError{}}` directly (verified against the
installed predicator 8.0.0 dependency:
`deps/predicator/lib/predicator.ex:902-915`'s `build_compiled_result/1`
private clause, and `deps/predicator/lib/predicator/errors/parse_error.ex`).
The struct carries the parser's bare `:message` with no location text
appended, `{line, column}` in `:position`, and the failing token's extent in
`:span` - present in every compile mode, not only `_with_spans`, since the
span comes from the token stream rather than from the `spans:` option.

# `compile_program`

```elixir
@spec compile_program(
  source :: String.t(),
  owner :: owner_ref(),
  location :: Statifier.Parser.Location.t()
) :: {:ok, Statifier.Machine.program()} | {:error, Statifier.Compiler.Error.t()}
```

Compiles a predicator *statement program* - a `<script>` body - into a
`Machine.program()`, the sibling type `Machine.program()`'s typedoc
describes: a program is never an arm of `Machine.expr()`, because
`Predicator.evaluate/3` rejects statement programs outright
(`deps/predicator/lib/predicator.ex:213-217`), so this function is a
parallel entry point rather than a third clause of `compile/3`.

`owner` and `location` carry the same meaning `compile/3`'s docs give
them - the owning node for the error case, and the `Location` a failure
is reported against.

On success, `Predicator.compile_program_with_spans/1`'s
`%Predicator.Compiled{}` is stored whole, exactly as `compile/3` stores
`compile_with_spans/1`'s result. `compiled.positions` maps each
instruction's 0-based index to the source span of the AST node that
emitted it; the instruction that terminates a statement - `store` for an
assignment, `pop` for a bare expression statement - carries that
statement's own source extent (upstream's own words,
`deps/predicator/lib/predicator.ex:856-895`'s doc for
`compile_program_with_spans/1`).

On failure, the error is the same `%Predicator.Errors.ParseError{}` shape
`compile/3` returns, handed straight through with no re-parse. A program
parse failure still carries a `:span`: it comes from the token stream that
`compile_program_with_spans/1` shares with every other compile entry
point (`deps/predicator/lib/predicator.ex:902-915`'s `build_compiled_result/1`
private clause), not from the `spans:` option - so a span is present in
every compile mode, because it comes from the token stream.

`compile_program/3` used to compile with a point-position variant instead, a
stopgap ADR-0014 item 1 sanctioned for exactly this case: "If cond wiring
must begin before 4.0 ships, it threads the identical seam with
`compile_with_positions/1` / `:positions` as a stopgap, storing the table in
the same field" - "the seam is the commitment, the width follows the pin."
Predicator `~> 9.0` closed that stopgap by shipping
`compile_program_with_spans/1`, and this function widened onto it.

# `inline_value`

```elixir
@spec inline_value(text :: String.t()) :: Statifier.Machine.expr()
```

Constant-folds a `<data>` element's child text into a `{:static, value}`
`Machine.expr()`. Spec
5.3.2 is explicit that `<data>`'s children are "an in-line specification
of the **value** of the data object", not an expression that reads the
datamodel, and B.2.1 gives the ECMAScript datamodel a
parse-then-fall-back-to-string ladder for exactly this reason: "if the
content is a valid JSON... create the corresponding ECMAScript object...
Otherwise the Processor MUST treat the content as a space-normalized
string literal". This is the predicator analogue, one rung shorter than
B.2.1's (predicator's own literal syntax stands in for JSON; there is no
XML rung under ADR-0004):

1. trim `text` - `String.trim/1`'s result doubles as this analogue's
   "space-normalized string literal", the fallback of step 5;
2. compile the trimmed text with `Predicator.compile_with_spans/1`;
3. on success, evaluate the compiled result **at compile time**, against
   an *empty* `Predicator.Context.new(%{}, on_unbound: :error)`;
4. on `{:ok, value}`, return `{:static, value}`;
5. on any failure at either step, return `{:static, trimmed_text}`.

The empty, `on_unbound: :error` context in step 3 is what makes the fold
safe: `<data id="x">hello</data>` compiles to a load of `hello`, which
fails against a context that holds nothing and errors rather than
defaulting to `:undefined` - so it falls through to the string literal
`"hello"` instead of silently becoming a datamodel read. `[1, 2, 3]`
compiles and evaluates to the list `[1, 2, 3]` directly, with nothing to
fall back to.

Consequently this function **never** returns a `{:compiled, ...}` arm:
child content can never raise `error.execution` at binding time, which is
why 5.3.2's "empty data element" failure clause never has a child-content
case to apply to under this datamodel.

`Statifier.EventData.coerce/1` runs a near-identical parse-or-string
ladder, and the two are deliberately not merged: this function implements
B.2.1 (datamodel initialization from `<data>` inline content) at compile
time, because a `<data>` body is static by construction, while
`EventData.coerce/1` implements B.2.8.1 (`_event.data` population) at
event time, because a `<param expr>`'s value is only known then. Same
shape, different clause, different time - see that module's moduledoc.

# `static`

```elixir
@spec static(value :: term()) :: Statifier.Machine.expr()
```

Wraps a literal value with no expression to evaluate - the `{:static, v}`
arm of `Machine.expr()` for a plain `<content>text</content>` body -
there is no predicator source and nothing to compile.

---

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