Which generated element came from which block (ADR-0004 decision 5).
The compile runs one way and nothing decompiles a chart back into blocks. What has to survive the trip is correspondence, and this is it.
Keyed two ways, because it answers two different questions
Runtime highlighting starts from a running session, whose active
configuration is a list of state ids
(Statifier.active_leaf_states/1). That is by_state_id, and
StatifierBlocks.Compiler.StateId's totality is what makes it complete.
Error routing starts from an upstream finding, and upstream findings
carry no element reference at all: every diagnostic in statifier's
pipeline is {reason, message, location} where location is a
%Statifier.Parser.Location{}, and the offending ids ride inside the
closed reason tuple as data. So the second key is a byte span over
the generated SCXML, and mapping a finding is owner_at/2 on
location.start_offset.
A span key is strictly more general than an id-based one. It routes findings about transitions, which have no id of their own in SCXML, and findings whose reason tuple carries no id at all - and because the compiler records the span of every attribute value as well as every element, it routes a bad expression to the config field the author typed it into.
The map is total over the emission
Every span the compiler emits has an owner; there is no unowned
scaffolding. Chart-level elements belonging to no particular block - the
<scxml> element itself - are attributed to the root block, which
ADR-0001 decision 1 guarantees exists. Totality is what makes decision
9's mapping a total function rather than one with an :unmapped arm
every consumer must handle and none can act on. An unmapped offset
inside the generated bytes is a compiler bug, and owner_at/2 still
returns an error tuple for it rather than raising, because a caller may
hand it an offset from a different chart.
Serialization
to_json/1 writes the map under ADR-0001 decision 8's canonical rules,
so a host can store it beside the SCXML, diff it, and read it from
non-Elixir tooling. It is not part of the document and carries no
schema_version of its own; it is versioned by the compilation record's
compiler version.
Summary
Types
role is nil for the block's own state and a role name for one it
minted (ADR-0004 decision 3). config_key is set when the span was
emitted verbatim from that config field, which is what makes a finding
the author's rather than a bug in this package.
Byte offsets into the generated SCXML. Exclusive end, as upstream.
Functions
Reads back what to_json/1 wrote.
Builds an owner. opts carries :role and :config_key, both nil by
default.
The owner of the innermost span containing offset.
The owner of the generated state state_id, for mapping a running
session's Statifier.active_leaf_states/1 back onto blocks.
The blocks a running configuration is inside, in the order the configuration was given, with any state this map does not know dropped.
Canonical JSON, ADR-0001 decision 8's rules. Deterministic.
Types
@type owner() :: %{ block_id: StatifierBlocks.Block.id(), role: String.t() | nil, config_key: String.t() | nil }
role is nil for the block's own state and a role name for one it
minted (ADR-0004 decision 3). config_key is set when the span was
emitted verbatim from that config field, which is what makes a finding
the author's rather than a bug in this package.
@type span() :: {start_offset :: non_neg_integer(), end_offset :: non_neg_integer()}
Byte offsets into the generated SCXML. Exclusive end, as upstream.
Functions
Reads back what to_json/1 wrote.
@spec owner( StatifierBlocks.Block.id(), keyword() ) :: owner()
Builds an owner. opts carries :role and :config_key, both nil by
default.
@spec owner_at(t(), non_neg_integer()) :: {:ok, owner()} | {:error, {:unmapped_offset, non_neg_integer()}}
The owner of the innermost span containing offset.
Innermost is by width: spans nest, so the narrowest span containing an offset is unique, and an attribute value's span is always strictly inside its element's.
The owner of the generated state state_id, for mapping a running
session's Statifier.active_leaf_states/1 back onto blocks.
The blocks a running configuration is inside, in the order the configuration was given, with any state this map does not know dropped.
The drop is deliberate rather than lenient: a state id this map has never heard of is a state from a different chart, and a highlighting caller wants the blocks it can highlight rather than an error over the whole list.
Canonical JSON, ADR-0001 decision 8's rules. Deterministic.