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
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/3sorts 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.
Summary
Functions
Serializes emission to SCXML bytes and the provenance map over them.
Serializes emission to SCXML bytes, discarding the provenance map.
Functions
@spec serialize(StatifierBlocks.Emission.t()) :: {binary(), StatifierBlocks.Provenance.t()}
Serializes emission to SCXML bytes and the provenance map over them.
@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.