# `StatifierBlocks.Compiler.Serializer`
[🔗](https://github.com/riddler/statifier_blocks/blob/v0.44.1/lib/statifier_blocks/compiler/serializer.ex#L1)

Turns an `StatifierBlocks.Emission` tree into SCXML bytes, and records
where every one of them came from (ADR-0004 decisions 5 and 6).

> #### This module is identity-bearing code {: .warning}
>
> `Statifier.Machine.Identity.of_source/2` hashes the SCXML **source
> bytes** (st-ADR-0052), so indentation, attribute order and
> empty-element spelling are all identity-bearing: a change here that
> moves a single byte changes the chart identity of every document this
> package compiles, and `Statifier.Machine.Identity.matches?/2` is what
> st-ADR-0060's resume refuses on. Treat any edit to this module as a
> compiler-version bump (decision 6's third determinism input), and see
> `test/statifier_blocks/compiler/serializer_test.exs`, which enforces
> the sensitivity rather than leaving it as prose.

The rules, all of them fixed so that `{document, palette, compiler
version}` determines the bytes:

  * attributes in sorted order (`StatifierBlocks.Emission.element/3` sorts
    them at construction);
  * **no incidental whitespace at all** - no indentation, no newlines, no
    space between elements. Generated SCXML is not read by hand; it is
    hashed, and the provenance map is what a human reads it through;
  * one canonical empty-element form, `<name/>`, never `<name></name>`;
  * no XML declaration, since it carries no information this package uses
    and would be one more byte sequence to keep stable;
  * `&`, `<`, `>` escaped in text and attribute values, plus `"` in
    attribute values. Escaping `>` is not required by XML but is
    canonical, and canonical is what this module is for.

## Why the spans are recorded here and nowhere else

Decision 5 keys error routing on a **byte span over the generated
SCXML**, because upstream findings carry a `%Statifier.Parser.Location{}`
and no element reference at all. Only the code that writes the bytes
knows where each element and each attribute value landed, so recording
the spans is this module's job and cannot be a second pass over the
output: re-deriving offsets by re-parsing would be a second
implementation of the serializer that must agree with the first.
Recording them costs one accumulator.

`serialize/1` walks the tree once and returns both. It reads the
resolved `owner` `StatifierBlocks.Compiler` stamped onto each element;
an element with no owner contributes bytes and no span, which is a
compiler bug rather than a supported mode - `compiler_test.exs` states
the totality of the map as a property over the artifact.

# `serialize`

```elixir
@spec serialize(StatifierBlocks.Emission.t()) ::
  {binary(), StatifierBlocks.Provenance.t()}
```

Serializes `emission` to SCXML bytes and the provenance map over them.

# `to_binary`

```elixir
@spec to_binary(StatifierBlocks.Emission.t()) :: binary()
```

Serializes `emission` to SCXML bytes, discarding the provenance map.

Every child placeholder must already have been spliced out by the
compiler; one that survives to here is a compiler bug, and this function
raises `ArgumentError` on it rather than writing a hole into
identity-bearing bytes. `StatifierBlocks.Compiler` never lets one reach
this point - it reports an unspliced placeholder as an Emit finding - so
the raise is an assertion, not an error path a caller handles.

---

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