Turns a compiled %Statifier.Machine{} plus caller-supplied context into
the session.start definition message (docs/wire-format.md) - the
message that makes every later index (state index, t_index,
c_index, d_index) resolvable to a source location without a compiler
on the reading end.
Pure, no process.
The Machine does not retain the SCXML source text and has no
id -> index reverse map beyond the partial id_to_index
(deps/statifier/lib/statifier/machine.ex:29), so the state table is built
by walking machine.states (a tuple in document order) rather than by any
lookup, and source is accepted from the caller rather than derived
(decision 7 of the plan).
The Content.Script/Content.Assign location gotcha
Every executable-content node kind carries a :location field holding its
own %Statifier.Parser.Location{} span, with two exceptions this module
handles explicitly rather than by a generic fallback:
Statifier.Machine.Content.Scripthas no:locationfield at all - its span lives on:node_locationinstead (deps/statifier/lib/statifier/machine/content/script.ex:48).Statifier.Machine.Content.Assignhas both fields, but its:locationis aString.t()- the raw, uncompiledlocationattribute (a path expression such as"foo.bar"), not a location span. Its span is on:node_location(deps/statifier/lib/statifier/machine/content/assign.ex:42-46).
A naive Map.get(node, :location) || Map.get(node, :node_location)
handles Script correctly (its :location key does not exist, so
Map.get/2 returns nil and the fallback wins) but silently picks the
wrong value for Assign - its :location is a truthy string, so the ||
never reaches :node_location, and a string ends up where a location
object belongs. content_location/1 below dispatches on the struct
itself instead, naming Assign and Script as the two kinds whose span
is :node_location and treating every other kind's :location as the
span it already is.
attribute_locations: key presence is the contract
A state row and a transition row each carry an attribute_locations
object alongside the element-level location, built from the Machine
field of the same name (Statifier.Machine.State,
Statifier.Machine.Transition). It maps an attribute name to that
attribute's own value span, and its value is not the interesting part:
an entry exists only for an attribute the author actually wrote, so
Map.has_key?(row["attribute_locations"], "type") is the "was type
written" question that type's own value cannot answer once lowering has
applied the :external default. The map is carried verbatim from the
Document node through the Machine, so that contract survives the whole
trip to the wire.
The object is always present and is %{} for an element that wrote no
attributes at all, for the synthesized initial transition no author wrote,
and for a Machine compiled by an engine old enough not to populate the
field. Those three are indistinguishable on the wire and deliberately so:
a consumer that finds no entry for the attribute it wants falls back to
the row's element-level location, which is exactly the granularity the
format offered before this field existed.
cond_location stays where it is. It is not folded into this object
because it carries a fallback the raw map does not - the transition's own
location when cond was written without a recorded span - so the two
answer different questions. Prefer attribute_locations["cond"] for new
work; read cond_location when the fallback is what is wanted.
Summary
Functions
Builds the session.start message for machine, stamped session: session and seq: 0.
The session.start schema version this module produces - 1 (ADR-0005's initial value).
Types
Functions
@spec build(Statifier.Machine.t(), session :: String.t(), opts()) :: {:ok, StatifierUI.Trace.Message.t()} | {:error, term()}
Builds the session.start message for machine, stamped session: session and seq: 0.
Returns {:error, {:invalid_fixtures, opts[:fixtures]}} when :fixtures
is supplied and is not a map, and {:error, {:invalid_source, opts[:source]}} when :source is supplied and is not a binary. Nothing
else can fail: every other field comes from a compiled Machine, which is
well-formed by construction.
@spec version() :: pos_integer()
The session.start schema version this module produces - 1 (ADR-0005's initial value).