StatifierBlocks.SourceView (StatifierBlocks v0.23.0)

Copy Markdown View Source

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.

t()

Functions

Compiles document against palette and returns the listing.

Every span in view the block block_id owns, in document order.

Types

status()

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

t()

@type t() :: %StatifierBlocks.SourceView{
  findings: [StatifierBlocks.Compiler.Finding.t()],
  line_count: non_neg_integer(),
  lines: [StatifierBlocks.SourceView.Line.t()],
  stale?: boolean(),
  status: status()
}

Functions

build(document, palette, opts \\ [])

Compiles document against palette and returns the listing.

opts is the compiler's own option list, plus one option of this module's own. Every key but :previous is forwarded verbatim to StatifierBlocks.Compiler.compile/3, because the listing is a read of what one compile produced and a caller reading a chart it did not compile the way its host does is reading the wrong chart: terminate:, child_use:, known_invoke_types: and datamodel: each change the emitted bytes, so each changes the listing. Nothing here validates the list - the compiler is the authority on its own options.

  • :declare - the host's raw {id, expr} declaration list, the compiler's own :declare option. Defaults to [].
  • :previous - the value this call replaces, and the one key this module keeps for itself. When the compile fails and the previous value was :ready, that listing comes back with stale? set rather than being thrown away - see the moduledoc.

spans_of(source_view, block_id)

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.