The generated SCXML as numbered lines of owned spans, for reading.
ADR-0004 decision 5 keys the provenance map two ways, and this is the second key turned around. Error routing asks "which block owns the byte this finding points at"; a reader asks the same question of every byte at once, and gets back a listing in which each run of bytes carries the block that emitted it. Nothing here decompiles anything: the chart is still a build product, the document is still the source of truth, and this is a read of what one compile produced.
One element per line, because the serializer emits none
StatifierBlocks.Compiler.Serializer writes the whole chart as a single
line - a chart's bytes are identity-bearing (st-ADR-0052) and whitespace
the serializer does not need is whitespace that would have to be hashed.
So the lines here are the reader's, not the file's: a line begins at every
<, which the serializer's own escaping makes an exact rule rather than a
heuristic (&, < and > are escaped in text and in attribute values,
so a raw < in the output always opens a tag). indent is the nesting
depth the same walk counts, and it is presentation: no byte of the
generated chart is moved, added or dropped.
That is the property the whole surface rests on. Concatenating every span
of every line, in order, reproduces the compiled bytes exactly, which is
what makes an offset in this listing the same offset
StatifierBlocks.Provenance.owner_at/2 was asked about.
A span is one owner's run of bytes
The cut points are the provenance map's own span endpoints plus the line
starts, so within one span the innermost owner never changes. The owner is
read once per span with owner_at/2 - the innermost containing span,
which is what makes an attribute value's owner its config field rather
than the element around it - and config_key is the field name an author
typed the value into.
A span whose block_id is nil is one no span in the map contains. The
map is total over the emission, so in practice this is the prologue an
element span does not reach; it is drawn as plain text rather than
dropped, because a listing that silently omitted bytes would stop
reproducing the chart.
Staleness rather than a recompile per keystroke
A compile is not cheap and this runs off one, so a caller recomputes only
while something is showing the result - the same discipline
StatifierBlocks.Runtime.FixtureRuns is held to. What that leaves is the
mid-edit document that does not compile at all. Handing back
:compile_error there would empty the panel on the first half-typed
expression; handing back the previous listing without saying so would show
a chart the document no longer produces. So build/3 takes the previous
value as :previous and, when the new compile fails and the previous one
succeeded, returns that listing with stale? set and the new findings
beside it. The panel says which document it is looking at, and no
keystroke recompiles anything.
Unguarded, on purpose
Like StatifierBlocks.Runtime.FixtureRuns, this module names no LiveView
module and carries no Code.ensure_loaded?/1 guard: it is a pure function
of a document and a palette, and the headless suite exercises it directly.
Drawing it is StatifierBlocks.Editor.Drawer's job.
Summary
Types
:pending - nothing has been compiled yet, which is the value a caller
holds before it first asks. :compile_error - the document does not
compile and there is no earlier listing to fall back on (normal mid-edit,
not exceptional); findings carries why. :ready - lines is the
compiled chart, and stale? says whether it is the current document's.
Functions
Compiles document against palette and returns the listing.
Every span in view the block block_id owns, in document order.
Types
@type status() :: :pending | :ready | :compile_error
:pending - nothing has been compiled yet, which is the value a caller
holds before it first asks. :compile_error - the document does not
compile and there is no earlier listing to fall back on (normal mid-edit,
not exceptional); findings carries why. :ready - lines is the
compiled chart, and stale? says whether it is the current document's.
@type t() :: %StatifierBlocks.SourceView{ findings: [StatifierBlocks.Compiler.Finding.t()], line_count: non_neg_integer(), lines: [StatifierBlocks.SourceView.Line.t()], stale?: boolean(), status: status() }
Functions
@spec build(StatifierBlocks.Document.t(), StatifierBlocks.Palette.t(), keyword()) :: t()
Compiles document against palette and returns the listing.
opts:
:declare- forwarded verbatim toStatifierBlocks.Compiler.compile/3as its own:declareoption, the host's raw{id, expr}declaration list. Defaults to[].:previous- the value this call replaces. When the compile fails and the previous value was:ready, that listing comes back withstale?set rather than being thrown away - see the moduledoc.
@spec spans_of(t(), StatifierBlocks.Block.id()) :: [ StatifierBlocks.SourceView.Span.t() ]
Every span in view the block block_id owns, in document order.
What the panel highlights, and the same question the drawer's markup asks per span - here as a function, so a caller that wants the answer without the markup has one.