StatifierBlocks.Compiler.Serializer (StatifierBlocks v0.1.0)

Copy Markdown View Source

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/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.

Summary

Functions

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

Serializes emission to SCXML bytes, discarding the provenance map.

Functions

serialize(emission)

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

to_binary(emission)

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