# `Statifier.Interpreter.ExitEntry`
[🔗](https://github.com/riddler/statifier-ex/blob/v2.0.0/lib/statifier/interpreter/exit_entry.ex#L1)

Appendix D's exit and entry blocks, ported function for function
(ADR-0002) - the half of the algorithm that changes the configuration.
`Statifier.Interpreter.Selection` answers "which transitions fire and
what would they leave"; this module actually leaves and enters states.

`machine_state` stands in for the pseudocode's globals exactly as
`Statifier.Interpreter.Selection`'s moduledoc states for the same reason
(`docs/observability.md` constraint 1) - every function here that reads a
global takes `machine_state` as its first argument.

## Return shapes

`exit_states/2` and `enter_states/2` are the two functions here that both
mutate the position and emit effects, so both return
`{MachineState.t(), [Effect.t()]}` - the shape `docs/observability.md`
fixes for `microstep/1`. `Statifier.Interpreter.microstep/2` is their
caller for the ordinary exit/entry path. Effect order within the returned
list is emission order: the trace effect first, then each block's effects
in the order the blocks ran.

`run_onexit_blocks/2` and `donedata/2` are public for a second caller:
`Statifier.Interpreter.exit_interpreter/1`'s termination walk needs
exactly the same per-state onexit body and the same `<donedata>` folding
rule that `depart/2` and `raise_parent_completion/3` already use here, so
this module owns both rather than the interpreter carrying a second
copy.

## Ordering

Exit order and entry order are never hand-sorted here: `exit_states/2`
pipes `Statifier.Interpreter.Selection.compute_exit_set/2`'s `MapSet`
through `Statifier.Machine.exit_order/2` (descending index), and
`enter_states/2` mirrors it through `Statifier.Machine.document_order/2`
(ascending index).

## The content seam

Every call site that would run executable content - `<onexit>`,
`<onentry>`, an `<initial>` transition's content, default history
content - goes through the private `execute_block/3` seam, which
delegates to `Statifier.Interpreter.Content.execute_block/3`: that module
owns the block runner and the `Trace.ContentExecuted` emission that wraps
it. This module owns only *where and in what order* blocks run, which is
what the exit/entry pseudocode defines.

## History recording

`exit_states/2` records history in a full first pass over the exit set,
reading the configuration exactly as it stood before any state exited -
*before* the first `onexit` runs and before any state is removed. The
pseudocode writes two consecutive `for s in statesToExit` loops for
exactly this reason: recording inside the delete loop would let a
later-exiting state's deep history see a configuration its own siblings
have already been removed from.

One mechanical deviation in the recorded value itself: the pseudocode's
`configuration.toList().filter(f)` produces an ordered list, while
`machine_state.history_values` holds a `MapSet`. Order is dropped rather
than preserved because nothing reads it - `add_descendant_states_to_enter/3`
re-enters a restored value through the same descendant and ancestor walks
any other target set goes through, and `enter_states/2` sorts the whole
entry set into document order afterwards regardless.

## The entry set

`compute_entry_set/2`, `add_descendant_states_to_enter/3`, and
`add_ancestor_states_to_enter/4` are pure queries: none of them touch
`machine_state.configuration`, none of them emit an effect. The
pseudocode's three `statesToEnter` / `statesForDefaultEntry` /
`defaultHistoryContent` out-parameters become one accumulator of type
`entry_set()`, threaded in and returned last - the minimal mechanical
deviation Elixir's lack of out-parameters forces. `default_history_content`
stores the default transition's `t_index` rather than its `[c_index]` list:
strictly more information, and what the content seam actually needs to
build a `Content.owner()`.

# `entry_set`

```elixir
@type entry_set() ::
  {states_to_enter :: MapSet.t(non_neg_integer()),
   states_for_default_entry :: MapSet.t(non_neg_integer()),
   default_history_content :: %{
     optional(non_neg_integer()) =&gt; non_neg_integer()
   }}
```

The pseudocode's three `computeEntrySet` out-parameters, threaded as one
accumulator: the states a transition set will enter, the subset of those
entered via a compound state's `<initial>` declaration rather than as an
explicit target (so default-entry content runs only for them), and a map
from a history state's parent to the `t_index` of the default transition
whose content runs because that history was unrecorded.

# `add_ancestor_states_to_enter`

```elixir
@spec add_ancestor_states_to_enter(
  machine_state :: Statifier.MachineState.t(),
  state_index :: non_neg_integer(),
  ancestor :: non_neg_integer() | nil,
  acc :: entry_set()
) :: entry_set()
```

`addAncestorStatesToEnter` (Appendix D) - every proper ancestor of
`state_index` up to but excluding `ancestor` is added, each parallel
ancestor also pulling in its uncovered regions.

`Machine.proper_ancestors/2` returns *all* ancestors up to the `:scxml`
root (no two-argument bound), so the pseudocode's
`getProperAncestors(state, ancestor)` is expressed as
`Enum.take_while(&(&1 != ancestor))` over the one-argument helper. A
`nil` `ancestor` (a targetless transition's domain, or a direct call with
no bound) therefore correctly takes every ancestor, since `nil` never
appears in `proper_ancestors/2`'s result.

# `add_descendant_states_to_enter`

```elixir
@spec add_descendant_states_to_enter(
  machine_state :: Statifier.MachineState.t(),
  state_index :: non_neg_integer(),
  acc :: entry_set()
) :: entry_set()
```

`addDescendantStatesToEnter` (Appendix D) - `state_index` and, depending on
its kind, the descendants entering with it.

Four cases, matching the pseudocode's own branches:

- **history** (`enter_history_target/3`): a recorded value in
  `machine_state.history_values` restores those states; an unrecorded one
  registers `default_history_content` and follows the history's default
  transition's targets instead.
- **compound**: `state_index` is added, flagged in
  `states_for_default_entry`, and its initial targets are entered
  (`enter_initial_targets/3`). When the document wrote an `<initial>`
  element, the targets come from
  `Machine.transition(machine, state.initial_transition).targets` - the
  pseudocode's `state.initial.transition.target` - because that transition
  is also what carries the default-entry content `run_default_entry/3`
  runs on the way in. **Mechanical deviation**:
  `Statifier.Compiler.resolve_initial/3` only populates
  `initial_transition` for a written `<initial>` element; a state
  defaulted through the `initial` attribute or the first-child fallback
  carries its resolved default in `State.initial` instead, with no
  synthesized transition (there being no `<initial>` content to run in
  that case either). `enter_initial_targets/3` reads `initial_transition`
  when set and falls back to `State.initial` otherwise, rather than
  asserting non-nil.
- **parallel**: `state_index` is added and each child region not already
  covered by a `states_to_enter` descendant is entered
  (`enter_uncovered_regions/3`).
- **atomic / final / anything else**: `state_index` is added and nothing
  else happens.

# `cancel_invocations_for_state`

```elixir
@spec cancel_invocations_for_state(
  machine_state :: Statifier.MachineState.t(),
  state_index :: non_neg_integer()
) :: {Statifier.MachineState.t(), [Statifier.Effect.t()]}
```

`for inv in s.invoke: cancelInvoke(inv)` (Appendix D) - one
`{:cancel_invoke, %Effect.CancelInvoke{}}` per live invocation of
`state_index`: walk its compiled `invoke` list in document order, and for
each `{state_index, invoke_index}` found in `machine_state.active_invocations`
emit a cancel and delete the entry. An invocation whose arguments failed
(ADR-0031), and one whose resolved `type` was unsupported (6.4.1), never
reached `active_invocations`, so neither produces a cancel.

Two callers, both after that state's own `onexit` blocks and before it
leaves the configuration - both the pseudocode's own order in `exitStates`
and `exitInterpreter`, and spec 6.4's "the cancel operation MUST act as if
it were the final `<onexit>` handler in the invoking state": `depart/2`
(this module's own `exit_states/2` path) and
`Statifier.Interpreter.exit_interpreter/1` (the termination walk), the
same second-caller shape `run_onexit_blocks/2` above already has.

# `compute_entry_set`

```elixir
@spec compute_entry_set(
  machine_state :: Statifier.MachineState.t(),
  transitions :: [Statifier.Machine.Transition.t()]
) :: entry_set()
```

`computeEntrySet` (Appendix D) - the entry-set bookkeeping every
`enabled_transitions` transition contributes, folded into one
`entry_set()` accumulator.

Per transition, in the pseudocode's own order: each *written* target
(`transition.targets`, not the resolved history/effective set) through
`add_descendant_states_to_enter/3`, then the transition's domain
(`Selection.get_transition_domain/2`), then each *effective* target
(`Selection.get_effective_target_states/2` - already resolves both
history branches, consumed rather than re-resolved here) through
`add_ancestor_states_to_enter/4` bounded by that domain. The
written-versus-effective distinction is the pseudocode's own and is
preserved here rather than collapsed into one walk.

# `donedata`

```elixir
@spec donedata(
  machine_state :: Statifier.MachineState.t(),
  state_index :: non_neg_integer()
) ::
  {Statifier.MachineState.t(), term()}
```

`returnDoneEvent`'s `s.donedata` argument (Appendix D) - `state_index`'s
`<donedata>`, folded to the value a raised or returned `done.*` event
carries as `data`.

- No `<donedata>` at all, or a `%Donedata{expr: nil, params: []}` (no
  `<content>` or `<param>` child at all) - `{machine_state, :undefined}`:
  "no data", the same spelling every other absent-payload writer uses
  (`docs/adr/0037-unbound-spelled-undefined-at-the-writer.md`).
- `{:static, text}` - `<content>`'s text body, which can only originate
  there (`Statifier.Compiler.build_content_expr/2`). Coerced through
  `Statifier.EventData.coerce({:text, text})` (Decision 3 of the plan on
  this module) so `<content>21</content>` becomes the integer `21`
  (W3C test529), not the string `"21"`.
- `{:compiled, _, _}` - `<content expr>`. Evaluated against a fresh
  `Statifier.Evaluator.context/1`; success is coerced through
  `EventData.coerce({:value, v})` (identity - an evaluated expression
  already produced a legal data value, so it is never re-run through the
  text ladder). Failure raises `error.execution` via
  `MachineState.raise_platform/4` and returns `:undefined` donedata -
  **not** the empty string spec 5.6 names for `<content>` generally
  (ADR-0021 records that deviation and its scope), and not `nil` either:
  a failed evaluation produced no data, it did not produce a null value
  (W3C test528).
- `expr: nil` with a non-empty `params` list - `<param>` children, folded
  against one `Statifier.Evaluator.context/1` built once for the whole
  fold. Each param is evaluated in document order; a failure raises its
  own `error.execution` (spec 5.7: "If the evaluation of 'expr' produces
  an error, or if neither 'location' nor 'expr' is present, or if the
  value of 'location' is not a valid location for a data value in the
  underlying data model, the SCXML Processor MUST place the error
  'error.execution' in the internal event queue and MUST ignore the name
  and value") and is dropped rather than aborting the remaining params.
  The surviving `{name, value}` pairs, still in document order, go
  through `EventData.coerce({:params, pairs})`, which returns `:undefined`
  for an empty result (`Statifier.EventData`'s own moduledoc, Decision 5) -
  so a `<donedata>` whose only param fails produces `:undefined` data, the
  same shape as no donedata at all, not `%{}`. The `<content>` arm above
  and this params arm are mutually exclusive by validator check
  (`Statifier.Validator.Checks.Donedata`), so there is no case where both
  are non-empty.

Two callers: `raise_parent_completion/3` (a non-top-level final's
`done.state.*`) and `Statifier.Interpreter.exit_interpreter/1` (a
top-level final's terminal `{:done, _}` effect) - the same folding rule
either way. `<donedata>` carries no `c_index`
(`lib/statifier/machine/content.ex:25-28`), so
`Statifier.Interpreter.Content.raise_execution_error/4`'s `{:content,
c_index, owner}` origin shape does not apply here; the raise below uses
`{:state, state_index}` instead, the same origin shape
`raise_parent_completion/3` already stamps on the `done.state.*` event
itself - for both the `<content>` arm and each failing `<param>`.

# `enter_states`

```elixir
@spec enter_states(
  machine_state :: Statifier.MachineState.t(),
  enabled_transitions :: [Statifier.Machine.Transition.t()]
) :: {Statifier.MachineState.t(), [Statifier.Effect.t()]}
```

`enterStates` (Appendix D) - the entry-ordered set `compute_entry_set/2`
computes, added to the configuration with `onentry`, default-entry, and
default-history content run per state, then completion events raised.

Body, in the pseudocode's own order:

1. `compute_entry_set/2` over `enabled_transitions`.
2. `entry_order` = `Machine.document_order/2` over `states_to_enter` -
   ascending index, the mirror of `exit_states/2`'s `exit_order`.
3. `entry_order` is reduced through `arrive/3`, each state added to the
   configuration, its `onentry` blocks run, its default-entry / default-
   history content run when flagged/registered, then its completion
   events raised.
4. `Effect.trace(pre_entry_state, Effect.Trace.EntrySet, indexes:
   entry_order, configuration: machine_state.configuration)` is emitted
   last: `macrostep`/`microstep`/`round` are stamped against
   `pre_entry_state`, captured as `machine_state` stood before step 3's
   `arrive/3` reduce (ADR-0012, mirroring `exit_states/2`), while
   `configuration` is read from the post-entry `machine_state` because
   "the configuration after this entry set was applied" does not exist
   until step 3 has run. The payload's position in the returned
   list is unchanged: it is still first, since the list is concatenated
   at the end either way.

# `exit_states`

```elixir
@spec exit_states(
  machine_state :: Statifier.MachineState.t(),
  enabled_transitions :: [Statifier.Machine.Transition.t()]
) :: {Statifier.MachineState.t(), [Statifier.Effect.t()]}
```

`exitStates` (Appendix D) - the exit-ordered set of states
`enabled_transitions` leave, with history recorded before any state
exits and `onexit` content run per state in exit order.

Body, in the pseudocode's own order:

1. `exit_set` = `Selection.compute_exit_set/2` over `enabled_transitions`
   - an *unordered* `MapSet`.
2. `states_to_invoke` loses every member of `exit_set`, over that same
   unordered set - `statesToInvoke.delete(s)` (Appendix D), which runs
   before the exit-order sort and before `record_history_values/2`.
3. `exit_set` is sorted into exit order via `Machine.exit_order/2` into
   `states_to_exit`: `compute_exit_set/2` returns unordered, and ordering
   it is this function's job, not `Selection`'s.
4. `record_history_values/2` runs its whole first pass over
   `states_to_exit`, reading the untouched configuration.
5. `states_to_exit` is reduced through `depart/2`, each state running its
   `onexit` blocks and then leaving the configuration.
6. `Effect.trace(pre_exit_state, Effect.Trace.ExitSet, indexes:
   states_to_exit, configuration: machine_state.configuration)` is
   emitted last: `macrostep`/`microstep`/`round` are stamped against
   `pre_exit_state`, captured as `machine_state` stood after step 2's
   `states_to_invoke` update and before the departure reduce (ADR-0012 -
   `test/fixtures/adr_judge/0012_trace_prestate_captured.diff` is the
   sanctioned shape), while `configuration` is read from the
   post-departure `machine_state` because "the configuration after this
   exit set was applied" does not exist until step 5 has run.
   The payload's position in the returned list is unchanged: it is still
   first, since the list is concatenated at the end either way.

# `in_final_state?`

```elixir
@spec in_final_state?(
  machine_state :: Statifier.MachineState.t(),
  state_index :: non_neg_integer()
) ::
  boolean()
```

`isInFinalState` (Appendix D) - whether `state_index` is, itself or
through its active descendants, "in a final state": a compound state
answers true when some active child is a `:final`; a parallel answers
true only when *every* region does, recursively; anything else (an
atomic `:final` included) answers false. Ported verbatim.

# `run_onexit_blocks`

```elixir
@spec run_onexit_blocks(
  machine_state :: Statifier.MachineState.t(),
  state_index :: non_neg_integer()
) ::
  {Statifier.MachineState.t(), [Statifier.Effect.t()]}
```

`exitStates`'s per-state `for content in s.onexit: executeContent(content)`
(Appendix D) - `state_index`'s `onexit` blocks, in document order, each
through the `execute_block/3` seam with `{:onexit, state_index, ordinal}`
- `ordinal` is the block's position in the state's own `onexit` list,
exactly what `Machine.Content.owner()` documents it to be.

Two callers: `depart/2` (this module's own `exit_states/2` path) and
`Statifier.Interpreter.exit_interpreter/1` (the termination block, which
runs every active state's `onexit` blocks the same way, in exit order,
without the rest of `exitStates`'s history-recording and configuration
bookkeeping).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
