.LF File Format¶
Introduction¶
The .lf
file format is a human-readable format for representing core model's semantics
for state machines and all their properties. Its intended use case is the
creation and manipulation of state machines using very small and light-weight
tools using a text editor and a command line interface to read, validate and
upload state machines to the core.
.lf files¶
A .lf
file holds a single top-level state, all its hierarchical 'children'
states, as well as all ports, barriers, actions, parameters and results. The
name of the file directly acts as the name of that state, e.g. a file called
Wiggle.lf
is interpreted as a hierarchical state machine called 'Wiggle'.
State machines can also contain script code, e.g. for actions. Scripts are stored
within .lf
files as well, but they are enclosed in special tags(@{
as
the opening tag and }@
as the closing tag) to simplify
parsing of .lf
files as well as offer the possibility to use any script
language within these tags. The text inside these special tags is parsed as
a single string literal. This enables embedding other code, such as HTML-based
context menus.
An example of a state machine representing a simple timer module is given below:
Timer {
port done service("timer").operation("wait").status == "success"
parameterType {
float duration;
}
entry @{
service("timer").operation("wait").call(parameter.duration)
}@
clientData {
type : "app";
}
}
Note that the script language (e.g. inside the entry or exit function) is Lua.
In the following sections, we will go over the grammar of the individual elements.
Elements of a Statemachine¶
A statemachine consists of several elements. The following figure shows an overview of them. For a simple state only the name of the statemachine and the ports are mandatory, all the other elements can be used if necessary. If you want to build a DESK App, a few more elements are necessary which are explained later in the tutorial.

The next code block displays these elements in lingua franka. The order of the elements inside the state does not matter. The structure of the child states is similar to the parent state. All the elements can be used, except of the clientData block.
App {
port Success child("child_state_2").port("success")
port Error child("child_state_1").port("error") or child("child_state_2").port("error")
clientData { ... }
parameterType { ... }
resultType { ... }
variableType { ... }
entry @{ ... }@
action ..condition.. @{ ... }@
action ..condition.. @{ ... }@
exit @{ ... }@
--> child_state_1 {
port success ..condition.. -> child_state_2
port error ..condition..
parameterType { ... }
resultType { ... }
variableType { ... }
entry @{ ... }@
action ..condition.. @{ ... }@
action ..condition.. @{ ... }@
exit @{ ... }@
} where { ... }
child_state_2 {
port success ..condition..
port error ..condition..
parameterType { ... }
resultType { ... }
variableType { ... }
entry @{ ... }@
action ..condition.. @{ ... }@
action ..condition.. @{ ... }@
exit @{ ... }@
} where { ... }
} where { ... }
Root States¶
A single .lf
file represents a single root state, whose name is given by
the filename (without the .lf
suffix). Top-level statements within the file
define the various structural members of the root state, including a hierarchy
of children states.
The grammar for a root state (i.e. for a complete .lf
file) is given as follows:
<root-state> ::= <state-name> <state-block>
The rules for <state-name>
and <state-block>
will be defined in the next section.
States¶
A state is a named entity in a hierarchical state machine that can contain other states or barriers. It can be connected to sibling states via ports, and contain actions that are executed upon certain triggers.
In core model, the state holds a reference to a single child state or barrier which it
treats as its "first" child, i.e. upon executing the state, this "first" child
element gets activated. In .lf
files, this attribute is not expressed at the
parent state, but the "first" child element is marked by a -->
arrow,
marking it as the entry state.
A state can be a link state and reference a library which it links to. Such a link state cannot contain states, barriers, nor actions as these elements are inherited from the linked library. The only elements the link state can contain are ports without condition that must also be present within the library. Linking can occur at the top level or in child states.
<state> ::= <entry-symbol> <state-name> <state-block>
<link> ::= <entry-symbol> <state-name> "<-" <library-name> <link-block>
<entry-symbol> ::= epsilon | "-->"
<parameter-value> ::= epsilon | "where" <value>
<state-block> ::= "{" <state-contents> "}" <parameter-value>
<state-contents> ::= <state-elem> *
<state-elem> ::= <state> | <link> | <barrier> | <action> | <port> | <client-data> |
<parameter> | <result>
<link-block> ::= "{" <link-contents> "}" <parameter-value>
<link-contents> ::= <link-elem> *
<link-elem> ::= <link-port> | <client-data>
<state-name> ::= <identifier>
<library-name> ::= <identifier>
<identifier> ::= <alpha> <identifier-component>
<identifier-component> ::= <alpha-num> *
<alpha> ::= A-Za-z
<alpha-num> ::= <alpha> | 0-9 | "_"
Valid state or library identifiers start with a letter and are followed by any number of letters, numbers or underscore. Identifiers are case-sensitive.
The epsilon
represents an empty symbol.
The rules for <barrier>
, <action>
, <port>
, <link-port>
,
<parameter>
, <value>
, <result>
and <client-data>
are given in the following sections.
Barriers¶
Barriers are child elements of states and represent either:
a synchronization point where incoming port connections from multiple states can be merged onto a single destination state. Execution waits for all incoming ports to activate and transitions to the destination state. This is akin to a
join
operation in multi-threaded programs.a branching point where a single incoming port is mapped onto a number of destination states. In this case, execution starts the execution of the destination states in parallel. This is similar to a
fork
operation in multi-threaded programs. Barriers can also be entry nodes of the containing state, i.e. they are the first element to activate when the parent activates.
The grammar for barriers is defined as follows:
<barrier> ::= <entry-symbol> "barrier" <barrier-name> <barrier-block>
<barrier-name> ::= <identifier>
<barrier-block> ::= "{" <barrier-connections> "}"
<barrier-connections> ::= <barrier-connection> *
<barrier-connection> ::= "->" <state-name>
Actions¶
Actions are functions written in Lua that are executed based on trigger
conditions during the execution of the state machines. Special cases are the
entry
and exit
actions, which are executed upon entering or leaving a
state. More general actions
have an additional condition expression which
must evaluate to true to be executed.
The grammar for actions is defined as follows:
<action> ::= <entry-action> | <exit-action> | <condition-action>
<entry-action> ::= 'entry' <script>
<exit-action> ::= 'exit' <script>
<condition-action> ::= 'action' <value> <script>
<script> ::= '@{' String '}@' | '"' EscapedString '"'
In this grammar and the following ones in this document, String
represents
any string, and EscapedString
represents any string where quotes are
escaped (\"
).
The <value>
in the <condition-action>
must evaluate to a <bool-value>
.
Ports¶
Ports are named entities that allow the transition from the port's parent (the source state) to the port's destination (the destination state). A port also has a condition attached that must evaluate to true for the port (and thus the transition) to be activated. Ports are not necessarily connected to a destination state.
The grammar for ports is defined as follows:
<port> ::= "port" <port-name> <value> <connection>
<port-name> ::= String
<connection> ::= epsilon | "->" <destination-name>
<destination-name> ::= <state-name>
The <value>
in the <condition-action>
must evaluate to a <bool-value>
.
Link Ports¶
Libraries also can have ports, and instantiations of libraries ("link" states) inherit the library's ports such that they can be connected. As such, link ports are less complex since they do not contain the condition expression. The condition is already defined within the library.
The grammar for link ports is thus defined as follows, using the same rules as the regular port grammar:
<link-port> ::= "port" <port-name> <connection>
It is therefore possible to define a link port without connecting it to a
destination state or barrier. In this case, the statement merely acts as an
explicit statement that the linked library must provide the corresponding port
<port-name>
.
Parameter, Result and Variable¶
States can have parameters, results and variables, which are simple or composite data structures representing a configuration, outcome or private data of a state, respectively. Parameters, results and variables are strongly typed. Parameters and results can be augmented with metadata for specifying default values, value ranges, units but also information about which widgets to use for allowing the user to specify them. Variables can be used to store state private data which is persistent even if a (child) state is deactivated.
Therefore the definition of parameters is split into two segments: types and values. The parameter types for a state are defined inside the state itself while the values are specified inside the parent section.
Parameter, Result and Variable Types¶
A parameter/result/variable type can consist of the following entities:
basic types: float, int, bool, string
array types
struct types
Comments: everything from the '--' sign until the end of the line is considered a comment and will not be parsed.
The following grammar defines the structure of parameter/result types:
<type> ::= <base-type> | <array> | <struct>
<base-type> ::= 'int' | 'float' | 'bool' | 'string'
<array> ::= '[]' <type> | '[' Integer ']' <type>
<field> ::= <type> <identifier> ';'
<fields> ::= <field> *
<struct> ::= '{' <fields> '}'
The following example shows a simple parameter type that contains a pose matrix and linear and angular velocities:
{
[4][4]float goal_pose;
-- this is a comment
{
[3]float linear;
[3]float angular;
} velocities;
}
Parameter, Result, Variable Values¶
In the preceding section, the grammar for parameter, result and variable types was given. The grammar for assigning values to these parameters is a separate aspect and defined as follows:
<value> ::= <int-value> | <float-value> | <string-value> | <bool-value> |
<nil-value> | <array-value> | <struct-value> | <exp> |
"(" <value> ")"
<int-value> ::= Integer
<float-value> ::= Float
<string-value> ::= "'" String "'"
<bool-value> ::= "true" | "false"
<nil-value> ::= "nil"
<array-value> ::= "[" <elements> "]"
<struct-value> ::= "{" <field-values> "}"
<elements> ::= <value> | <value> "," <elements>
<field-values> ::= <field-value> *
<field-value> ::= <identifier> ":" <value> ";"
<exp> ::= <fun-app> | <access-path> | <exp> "[" Integer "]"
<fun-app> ::= <access-path> "(" <elements> ")" | <access-path> "(" ")" |
<value> <symbol> <value>
<symbol> ::= <special-char> <symbol> | <special-char>
<special-char> ::= "*" | "/" | "-" | "+" | "<" | ">" | "=" | "~" | "%" | "#"
<access-path> ::= <identifier> "." <access-path> | <identifier>
Note that omission of the "where" clause results in an empty struct value.
An instantiation of the above parameter type follows as:
{
goal_pose : [[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0]];
velocities : { linear : [ 1.0, 0.0, 0.0 ]; angular : [ 0.0, 0.0, 0.0 ]; };
}
Client Data¶
States can hold arbitrary client data in a key-value store. This allows for clients to store e.g. human-readable state names, the inclusion of app's context menus etc. While client data is stored in the core, no semantics are attached to them, i.e. their interpretation resides fully client-side.
The grammar for client-data is defined as follows:
<client-data> ::= "clientData" "{" <client-data-block> "}"
<client-data-block> ::= <key-value> *
<key-value> ::= <key> ":" <value> ";"
<key> ::= String
<value> ::= '@{' String '}@' | '"' EscapedString '"'