Skip to content

State block grammar

Leon Starr edited this page Jul 24, 2026 · 3 revisions

The bulk of an xsm file is a sequence of state blocks, one per state, each one closed by an unindented --.

Here are two states from the Door lifecycle so we can look at the structure:

state OPENING
activity
transitions
    Door opened > OPEN
    Passenger close > CLOSING
--
state OPEN
activity
    my Bank .= /R4/R2/R1/Bank
    Blocked? delay = my Bank.Block clear time :
        delay = my Bank.Passenger load time
    Time to close -> me @delay
transitions
    Passenger close > Cancel open delay
    Time to close > HOLDING OPEN
--

The relevant grammar is:

// State block
state_block = state_header activity transitions? block_end
state_header = "state" SP state_name signature? (SP DELETION)? EOL*
signature = '()' / '(' SP? parameter_set SP? ')'
parameter_set = parameter (',' SP parameter)*
parameter = parameter_name SP? ':' SP? type_name
parameter_name = name
type_name = name
DELETION = r'!\*'
activity = activity_header body_line* // A block of activity text (not parsed further)
activity_header = "activity" EOL
transitions = transition_header transition*
transition_header = "transitions" EOL*
transition = INDENT event_name (SP '>' SP state_name)? EOL*
state_name = name

So a state block is four things in a fixed order:

  1. A state header — the unindented state keyword, the state name, an optional signature and an optional !* deletion marker. See State names, Signatures and Deletion states.
  2. An activity — the unindented activity keyword followed by the action language body. Required, even when the body is empty. See Activities.
  3. Transitions — optional. The unindented transitions keyword followed by indented transition lines. See Transitions.
  4. The block end — an unindented --.

The activity keyword is not optional

This trips people up, so it's worth stating plainly: state_block = state_header activity transitions? block_end has no ? after activity. Every state must have the activity line even if there is nothing underneath it. The OPENING state above does exactly that, and so does MOVING in the Cabin lifecycle. Leave the keyword out and the parse fails with a complaint about expecting 'activity'.

The transitions section, by contrast, really is optional. A state with no transitions is a final state — CANNOT CLOSE in the Door lifecycle is one.

Ordering

The state blocks may appear in any order and the parser preserves the order you wrote them in. There's no significance to which one comes first; the starting state is established by Initial transitions, not by position in the file.

What the parser gives you

A list of StateBlock_a named tuples in the states field of StateModel_a. Each one has a state (a StateSpec_a with name, deletion and signature), an activity (a list of raw text lines) and a transitions list of Transition_a named tuples.

Clone this wiki locally