Statifier.Evaluator.SystemVariables (Statifier v2.0.0)

Copy Markdown View Source

Spec 5.10's system variables, as the plain maps Statifier.MachineState.datamodel carries them in. Two functions, so that neither Statifier.MachineState nor Statifier.Interpreter grows spec 5.10 knowledge of its own - MachineState.new/2 calls initial/2 once, and MachineState.put_event/2 calls event/1 on every write.

Every writer here spells "declared, no value yet" as :undefined directly, never nil - nil is reserved for predicator's own null (ADR-0037, docs/adr/0037-unbound-spelled-undefined-at-the-writer.md). Writing the spec-correct "not bound" answer at the source means a Statifier.Evaluator.context/1 wrap needs no normalization pass to produce it.

Summary

Functions

_event's value for event - spec 5.10.1's fields, read straight off Statifier.Event. sendid/origin/origintype/invokeid are String.t() | nil on Statifier.Event (a datamodel null can never be one of them, so nil there is unambiguous - Statifier.Event's moduledoc makes the same argument); absent/1 translates a nil to :undefined here, where they cross into the datamodel (the same translation initial/2 reuses for _name), so _event's own fields spell "declared, no value yet" the way every other datamodel writer does. data is not translated - Statifier.EventData.coerce/1 already spells :undefined for "no data" and nil for a null payload, so it passes through verbatim.

All four system variables (spec 5.10) as they stand before any event. Called once, by MachineState.new/2.

The SCXML Event I/O Processor's type URI (spec 6.2.5 / C.1). Public so the session's target router names the same string this module keys _ioprocessors by, rather than a second copy of it.

The address C.1.1 asks for: a value external entities can use to reach this session, which C.1 also makes the delivered event's origin and 5.10.1 requires to work as a <send target>. _sessionid stays the bare session id.

Functions

event(event)

@spec event(event :: Statifier.Event.t()) :: map()

_event's value for event - spec 5.10.1's fields, read straight off Statifier.Event. sendid/origin/origintype/invokeid are String.t() | nil on Statifier.Event (a datamodel null can never be one of them, so nil there is unambiguous - Statifier.Event's moduledoc makes the same argument); absent/1 translates a nil to :undefined here, where they cross into the datamodel (the same translation initial/2 reuses for _name), so _event's own fields spell "declared, no value yet" the way every other datamodel writer does. data is not translated - Statifier.EventData.coerce/1 already spells :undefined for "no data" and nil for a null payload, so it passes through verbatim.

initial(machine, session_id)

@spec initial(machine :: Statifier.Machine.t(), session_id :: String.t()) :: map()

All four system variables (spec 5.10) as they stand before any event. Called once, by MachineState.new/2.

_sessionid, _name, and _ioprocessors are session-lifetime and are never rewritten afterward - _sessionid stays stable for the session's whole lifetime (ADR-0008). _event is different: it is seeded here to :undefined and thereafter written only by MachineState.put_event/2.

_name binds machine.name (String.t() | nil on Statifier.Machine, since a <scxml> element may omit the optional name attribute). Spec 5.10 only says the Processor "MUST bind the variable _name ... to the value of the 'name' attribute", and is silent on an absent attribute; an absent optional attribute is exactly "declared, no value yet", so a nil machine.name runs through absent/1 the same as an absent _event field does, and _name reads as :undefined rather than as a datamodel null.

Why _event is seeded rather than left absent

Spec 5.10's system variables are declared for the session's whole lifetime; _event merely has no value until an event is being processed. A datamodel is a plain map, so the only way to say "declared, no value yet" is to bind the key to :undefined directly. Leaving the key out instead says something different and wrong - "no such variable" - which under Statifier.Evaluator.context/1's on_unbound: :error (ADR-0014 item 5) makes every pre-event _event reference an UndefinedVariableError rather than the undefined value the spec wants.

The W3C corpus is the evidence for which reading is right: test319 asserts that _event compares equal to undefined before any event has been processed, and takes its <else> branch to pass. Under the ECMAScript datamodel those tests were written against, an undeclared identifier throws ReferenceError - so the test can only pass if _event is declared and holds undefined, which is exactly what seeding reproduces.

scxml_event_processor()

@spec scxml_event_processor() :: String.t()

The SCXML Event I/O Processor's type URI (spec 6.2.5 / C.1). Public so the session's target router names the same string this module keys _ioprocessors by, rather than a second copy of it.

scxml_location(session_id)

@spec scxml_location(session_id :: String.t()) :: String.t()

The address C.1.1 asks for: a value external entities can use to reach this session, which C.1 also makes the delivered event's origin and 5.10.1 requires to work as a <send target>. _sessionid stays the bare session id.