The SRT Language

Textual state machines are created using the SRT ("Statemachine RT") language. It is a small domain-specific language which allows you to create state machines using a textual syntax. It also allows to embedd C++ code snippets, just like graphical state machines do.

Learning SRT is easy thanks to smart content assist in the SRT editor that can automatically insert correct SRT language constructs. Read more about content assist here.

In the sections below the constructs of the SRT language are presented one by one. Use this as a reference guide for the SRT language.

State machine

Each .srt file has a single state machine element as its root element. A state machine can be given a name, but it's optional since a state machine is never referenced from within the .srt file.

// State machine with default name
statemachine 'State Machine' {

};

// State machine without name
statemachine {

};

State

States can be declared inside a state machine or inside a composite state using the state keyword. Multiple states can be declared using a comma-separated list of state names.

state s1, s2, s3;
state 'Wait for ever';

Composite State

A composite state is a state that contains other states and/or pseudo states.

state Composite {
    entrypoint ep1;
    exitpoint ex1;
    state Nested;    
};

State Entry and Exit

Code snippets can be specified as entry and/or exit behavior for a state. Use the entry and/or exit keyword inside the state declaration followed by a code snippet.

statemachine {
    state S {
        entry
        `
         e++;
        `;
        exit
        `
         e--;
        `;
    };
};

Redefined and Excluded State

For an inherited state machine an inherited state can be redefined or excluded. Declare such a state by using one of the keywords redefine or exclude before the state name. Note that in this case the state name must match a state in an inherited state machine.

statemachine {
    state redefine State1 {
        entry
        `
         // Redefined entry
        `;
    };
    state exclude State2;
};

Entry and Exit Points

Entry and exit points are so called pseudo states. They define named location points through which a composite state can be entered or exited. Entry and exit points can therefore only be declared inside a composite state, and not directly in a state machine. You can declare multiple entry/exit points using a comma-separated list of names after the entrypoint or exitpoint keywords.

Note that an entry or exit point is often referenced from outside the composite state where they are declared: an entry point as the target of a transition and an exit point as the source of a transition. Such references must include the state name as a qualifier.

state S {
    entrypoint ep1;
    exitpoint ex1;
    state Nested;    
    ep1 -> Nested;
    Nested -> ex1 on timer.timeout;
};
state T;

initial -> S.ep1; // Qualified entrypoint name as target
S.ex1 -> T; // Qualified exitpoint name as source

Except for entry/exit points, there is not a need in SRT to ever qualify names in references.

Choice Point

A choice point is a pseudo state where a dynamic decision is taken to decide which of the outgoing transitions from the choice point to execute next. Choice points can be referenced as the source or target of transitions and therefore need to have a name. You can declare multiple choice points using a comma-separated list of names after the choice keyword.

Outgoing transitions from a choice point should have a guard condition, and they should be mutually exclusive and cover all possibilities to avoid that execution stops at the choice point. A good way to ensure this is to use an 'else' guard for one of the outgoing transitions.

statemachine {
    choice C1, C2;
    state S, T;
    initial -> C1; 
    C1 -> S when `return x < 0;`;
    C1 -> T when `else`; 
};

Junction Point

A junction point is a pseudo state where one or many transitions either converge or split the execution when the state machine transitions from one state to another. In case of splitting, each outgoing transition should have a guard condition. Such guard conditions are evaluated to find out which of the outgoing transitions to take. A missing guard condition is treated like a true-returning guard condition. Note that contrary to a choice point this evaluation happens before leaving the source state, and the source state will not be left unless one of the outgoing transitions from the junction point has a fulfilled guard condition. This means that unlike a choice point execution cannot get stuck at a junction point.

Since junction points can be referenced as the source or target of transitions they need to have a name. You can declare multiple junction points using a comma-separated list of names after the junction keyword.

statemachine {
    junction J1, J2;
    state S, T, U;
    initial -> J1;
    // Example of splitting the execution path  
    J1 -> S when `return x < 0;`;
    J1 -> T when `else`; 
    // Example of converging execution paths
    trans1: S -> J2 on timing.timeout;
    trans2: T -> J2 on timing.timeout;
    trans_common: J2 -> U;
};

Deep History

Every composite state has an implicitly declared deep history psuedo state with the name "history*" which can be referenced as the target of a transition. The meaning of this is to re-enter the previously active state, and all its active substates recursively.

state X {
    state S;
    S -> history* on timing.timeout;
};

Transition

A transition connects a source state or pseudo state with a target state or pseudo state. The core SRT syntax for a transition is the name of the source followed by an arrow (->) followed by the name of the target.

A transition may have a name, but its optional. However, if you want to redefine a transition defined in an inherited capsule state machine, then it needs a name so it can be referenced. A name can also make it easier to understand the state machine, since it can give some hint on when the transition can execute and/or what it does. The transition name is specified before the source and is separated from it by a colon (:).

statemachine {
    state S, T;
    S -> T on myport.myevent; // Unnamed transition
    onTimeout: S -> T on timing.timeout; // Named transition
    'error occurred': S -> T on myport.error; // Transition with an informal name
};

Initial Transition

If the source of a transition is the initial keyword, then the transition is the first one to execute when the capsule starts executing its state machine. There should be at most one such initial transition in a state machine, and for a top state machine (i.e. a state machine that is not nested inside a composite state) it's mandatory to have an initial transition.

statemachine {    
    state S;
    initial -> S; // Initial transition in top state machine
    state C {
        state Inner;
        Initial: initial -> Inner; // Named initial transition in nested state machine
    };
};

Transition Trigger and Guard

If the source of a transition is a state, then the transition needs to have at least one trigger. A trigger specifies a port in the capsule, and an event of the protocol that types that port. When the specified event arrives at the specified port, the trigger may cause the transition to execute. However, both the trigger and the transition may have a guard condition that must be fulfilled for that to happen.

Transition triggers are specified using a comma-separated list after the keyword on that comes after the transition target. Guards for transitions are specified in code snippets using the when keyword. Guards for triggers are specified within square brackets after the trigger.

statemachine {
    state S;
    S -> S on timing.timeout, myPort.myevent [`return x < 10;`] // Transition with 2 triggers (the 2nd has a guard)
    when `return first == true;` // Transition guard
    `cout << "Triggered!";`;
};

Redefined and Excluded Transitions

For an inherited state machine an inherited transition can be redefined or excluded. Declare such a transition by using one of the keywords redefine or exclude before the transition name. Note that in this case the transition name must match a transition in an inherited state machine. That is, you can only redefine or exclude transitions that have been given a name in the inherited state machine.

statemachine {
    redefine trans1: State1 -> State1; 
    exclude trans2: State1 -> State2;
};

Note that the SRT language requires to specify source and target even when excluding a state, but it doesn't matter which source and target is used in this case, since the transition anyway will be excluded.

Common Syntax Rules

Some syntax rules are common for several kinds of elements.

Comment

Both single-line comments starting with // and multi-line comments enclosed in /* ... */ are supported.

/* 
 * This is my state machine
 */
statemachine 'State Machine' {    
    state S1, /* The final state */ final;
    trans: S1 -> final on myPort.myevent;
    //disabled
};

When commenting individual model elements, it's good practise to place the comment just before the element (as for state "final" in the above example). Thereby the comment can be shown in hover tooltips on references to the element.

Name

All elements that can be referenced from within an .srt file must have a name. For example, a state must have a name, as otherwise it would not be possible to reference it as the source or target of a transition. Note that some elements for which a name is not required in a graphical state machine, such as a choice or junction point, need to have a name in SRT. When you convert a graphical state machine to SRT such unnamed elements will get a name assigned automatically. You can choose to replace such automatic names with more meaningful names if you want.

For some elements a name is optional. For example, a state machine can have a name, but since state machines are never referenced in SRT a name is not mandatory. The name of a transition is also optional, but if you want to redefine the transition it needs to have a name, so that the redefining transition can reference it.

A valid name consists of a letter (or underscore) followed by zero to many letters (or underscores) or numbers. Certain names cannot be used since they are reserved keywords. But you can use any name if you enclose it in single quotes. Such an informal name can contain any character, including spaces, which means you can use a small sentence in an informal language such as English for describing the element. This can sometimes improve the readability of your state machine. Note that you need to use single quotes also when referencing an informal name.

// Regular names
state my_state;
// Informal names (enclosed in quotes)
state 'informal name'; // Name with special character (space)
state 'state'; // Name is a keyword
state '3state'; // Name starts with number

// Referencing names of states in a transition
'a transition' : my_state -> 'informal name' on timing.timeout;

Keyword

The following names are keywords in the SRT language and cannot be used for user-defined elements (unless you enclose them in single quotes):

state 
statemachine 
initial 
on 
when 
entry 
exit 
history* 
redefine 
exclude 
junction 
choice 
entrypoint 
exitpoint    

Code Snippet

Code snippets can be specified at various places in a textual state machine. For example, the effect of a transition or the guard of a trigger is specified using a code snippet.

In the SRT syntax code snippets are arbitrary C++ code enclosed in backticks (```). For increased readability it can be good to put code snippets on their own lines (especially for multi-line code snippets).

statemachine {
    state S;
    initial -> S
    `cout << "Started";`; // Transition effect code
    state C {
        entry
        `
         // C is entered
         cout << "C enter";
        `; // State entry code (multi-line)
    };
};    

Property

A few, rarely used, properties which for a graphically defined state machine are set using the Properties view, do not have a specific syntax in the SRT language. Instead, a general-purpose property syntax is supported for a few elements in a state machine where such properties can be set. Only properties that have an affect on C++ code generation are considered:

statemachine {
    state S1, S2;
    trans: S1 -> S2 on [[rt::properties(frequent)]] myPort.rtBound; // Property on trigger
    trans2: [[rt::properties(const_rtdata=false)]] S1 -> S2 on timing.timeout; // Property on transition
};