Skip to content

State block grammar

Leon Starr edited this page Aug 4, 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
ignore
    Passenger open
        The passenger can hit the open button all they like at this point,
        but it will be ignored since the door is, in fact, opening now.
can't happen
    Door closed
        Door is opening, so it should not detect ‘closed’.
--
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? ignores? cant_happens? 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

// Non transition event responses
ignores = ignore_header response*
ignore_header = "ignore" EOL*
cant_happens = cant_happen_header response*
cant_happen_header = "can't happen" EOL*
response = INDENT event_name (SP tag)? EOL* explanation?
explanation = expl_line+
expl_line = INDENT INDENT tag? expl_text EOL
expl_text = r'[^\n]*'
tag = '<' r'[^>\n]+' '>' SP*

So a state block is six 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. Ignores — optional. The unindented ignore keyword followed by the events accepted and discarded here, each with its explanation. See Ignore.
  5. Can't happens — optional. The unindented can't happen keyword followed by the events that cannot legitimately arrive here, each with its explanation. See Can't Happen.
  6. The block end — an unindented --.

Only the last of these sections is followed by --. The three response sections are parts of one state block, so one block_end closes all of them together, and the order is fixed: an ignore section may not precede transitions, and can't happen always comes last. Explanations belong to Reason tags.

The activity keyword is not optional

This trips people up, so it's worth stating plainly: state_block = state_header activity transitions? ignores? cant_happens? 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 other three sections really are optional. A state with no transitions is a final state — CANNOT CLOSE in the Door lifecycle is one, and it still carries a can't happen section, since a final state can be sent events just like any other.

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), a transitions list of Transition_a named tuples, and ignores and cant_happens lists of Response_a named tuples carrying event, tag and explanation.

Events left out of all three response sections do not appear in the parse result at all. They default to can't happen — see Can't Happen for what that default does and does not tell you.

Clone this wiki locally