StatifierBlocks.Core.Parallel (StatifierBlocks v0.1.0)

Copy Markdown View Source

core.parallel: one slot per lane, with no ordering between the lanes (ADR-0002 decision 10).

The second config-parameterized type. config carries an ordered "lanes" list of bare lane names, and each lane name becomes the slot "lane_" <> name:

config = %{"lanes" => ["capture", "receipt"]}

slots(config)
#=> [{"lane_capture", :any, "capture"}, {"lane_receipt", :any, "receipt"}]

A lane stores its bare name and the slot prefixes it, where StatifierBlocks.Core.Branch's arms store the whole slot name. The asymmetry is not this type's choice: it is what the ADR-0001 worked example stores, and the stored bytes win over any tidier scheme.

Lane slots are :any - a declared-but-empty lane is an ordinary intermediate state, not a finding - and the order of the lanes in config is presentation order only. Nothing about a parallel block gives its lanes a sequence; that is the whole point of it, and it is why palette_entry/0 declares layout: :columns so the lanes render side by side (ADR-0005 decision 10).

produces is :unknown for the same reason core.branch's is: joining the lanes' outputs is a type lattice, and ADR-0003 decision 4 refuses to build one.

Summary

Functions

A compound state wrapping one <parallel> whose regions are the lanes (ADR-0004 decision 2), each region sequencing its own steps and finishing at its own <final>.

One :any slot per well-formed lane, in config order.

Functions

emit(block, context)

A compound state wrapping one <parallel> whose regions are the lanes (ADR-0004 decision 2), each region sequencing its own steps and finishing at its own <final>.

<state id="s_PAR" initial="s_PAR__run">
  <transition event="done.state.s_PAR__run" target="s_PAR__done"/>
  <parallel id="s_PAR__run">
    <state id="s_PAR__lane_capture" initial="...">...<final id="s_PAR__done_lane_capture"/></state>
    <state id="s_PAR__lane_receipt" ...>
  </parallel>
  <final id="s_PAR__done"/>
</state>

A <parallel> is done when every region is, so the outer state needs one transition and no join logic of its own.

The two role families are lane_<name> and done_lane_<name>. They cannot collide with each other whatever a lane is called, because they differ in their first token rather than their last - a lane named capture_done mints lane_capture_done and done_lane_capture_done, and neither is any other lane's id.

A parallel with no lanes emits no <parallel> at all: an empty one is not valid SCXML, and "run nothing concurrently" is done the moment it starts, so the block compiles to a state whose initial is its <final>.

slots(config)

One :any slot per well-formed lane, in config order.

Total for any config: a lane name that could not make a usable slot name is skipped rather than raised on, keeping ADR-0002 decision 6's stability rule true mid-edit.