ExMachine.Statechart (ex_machine v0.1.3)
View SourceModule for building and validating statechart definitions.
A Statechart is a compiled representation of a hierarchical state machine definition. It provides the foundation for creating and running state machines with complex hierarchical structures, transitions, actions, and guards.
Statechart Structure
A compiled statechart contains:
states
- A flattened map of all states in the hierarchy with their full paths
The statechart compiler takes a nested State
definition and:
- Validates the state machine structure
- Flattens the hierarchy into addressable state paths
- Validates all transitions reference valid states
- Ensures there are no duplicate state names in the same scope
- Verifies initial states are valid
State Addressing
States in a statechart are addressed using dot notation paths:
"root"
- The root state"root.playing"
- A top-level state called "playing""root.playing.normal_speed"
- A nested state "normal_speed" inside "playing"
Configuration
The active configuration represents which states are currently active. Due to hierarchical composition, multiple states can be active simultaneously.
For example, if a media player is in "normal speed" mode:
- Configuration:
[["normal_speed", "playing", "root"]]
- Active states: "normal_speed", "playing", and "root"
Building a Statechart
# Define your state machine structure
definition = %State{
initial: "idle",
substates: %{
"idle" => %State{transitions: %{"start" => "running"}},
"running" => %State{transitions: %{"stop" => "idle"}}
}
}
# Compile into a statechart
statechart = Statechart.build(definition)
Validation
The build process performs comprehensive validation:
- All referenced states must exist
- Initial states must be valid substates
- No duplicate state names in the same scope
- Transitions must reference valid target states
- State hierarchy must be well-formed
If validation fails, specific exceptions are raised describing the problem.
Usage with using macro
You can also define statecharts using the __using__
macro:
defmodule MyMachine do
use ExMachine.Statechart
def definition do
%State{
# ... your state definition
}
end
end
Summary
Functions
Build and return a Statechart
struct that contains the compiled and
validated version of the statechart in definition
argument,
ready to be executed in a Machine.
Return the Least Common Compound Ancestor of states
list.
Returns the ancestors of state
(parent of state, parent of parent, etc),
in form of a list of string, ordered from nearest parent to (and always)
the "root" state.
Returns the ancestors of state
(parent of state, parent of parent, etc),
in form of a list of string, ordered from nearest parent to (and excluded)
the until
state.
Returns the descendants of state
, in form of an unordered MapSet of string,
containing all the descendants (children, children of children, etc) of state
.
Returns a list of states that must be entered when the machine is entering
in the state target
, considering the lcca
Return the list of enter actions for each state in list states_list
,
if defined for the state, in the same exact order of states_list
Return the list of exit actions for each state in list states_list
,
if defined for the state, in the same exact order of states_list
Returns a list of states that must be exited when the machine is exiting
from the state source
, considering the lcca
Return list of initial states from argument state
deep to a leaf state.
Return a transition map if state
have a transition defined
to handle event
, otherwise nil
Types
@type t() :: %ExMachine.Statechart{states: map()}
Functions
Build and return a Statechart
struct that contains the compiled and
validated version of the statechart in definition
argument,
ready to be executed in a Machine.
During compilation, Statechart verifies that the definition is valid and raise an exception if there is a problem.
Examples
iex> eng = Statechart.build(%State{initial: "s1", substates: %{ "s1" => %State{}}})
iex> Enum.count(eng.states)
2
iex> Statechart.build("invalid")
** (ExMachine.Statechart.InvalidDefinition) Definition "invalid" is not valid
iex> defs = %State{initial: "invalid_state", substates: %{ "s1" => %State{}}}
iex> Statechart.build(defs)
** (ExMachine.Statechart.NotValidInitial) Initial state "invalid_state" is not valid or not a descendant of composite state "root"
Return the Least Common Compound Ancestor of states
list.
LCCA of a list states
is the lowest (i.e. deepest) state in the state
hierarchy that has all state in states
as descendants.
In other words LCCA is the state s
such that s
is a ancestor of all
states on states
list and no descendants of s
has this property.
Return nil
if in the states
list is present the root state because can't
exist a state that is parent of root state.
Note that since we are speaking of ancestor (parent or parent
of a parent, etc.) the LCCA is never a member of state
list.
Returns the ancestors of state
(parent of state, parent of parent, etc),
in form of a list of string, ordered from nearest parent to (and always)
the "root" state.
Examples
iex> defs = %State{initial: "s1", substates: %{ "s1" => %State{ initial: "s11", substates: %{ "s11" => %State{}}}}}
iex> eng = Statechart.build(defs)
iex> Statechart.get_ancestors(eng, "s11")
["s1", "root"]
Returns the ancestors of state
(parent of state, parent of parent, etc),
in form of a list of string, ordered from nearest parent to (and excluded)
the until
state.
Examples
iex> defs = %State{initial: "s1", substates: %{ "s1" => %State{ initial: "s11", substates: %{ "s11" => %State{}}}}}
iex> eng = Statechart.build(defs)
iex> Statechart.get_ancestors_until(eng, "s11", "root")
["s1"]
Returns the descendants of state
, in form of an unordered MapSet of string,
containing all the descendants (children, children of children, etc) of state
.
Examples
iex> defs = %State{initial: "s1", substates: %{ "s1" => %State{ initial: "s11", substates: %{ "s11" => %State{}}}}}
iex> eng = Statechart.build(defs)
iex> Statechart.get_descendants(eng, "root")
MapSet.new(["s1", "s11"])
Returns a list of states that must be entered when the machine is entering
in the state target
, considering the lcca
Return the list of enter actions for each state in list states_list
,
if defined for the state, in the same exact order of states_list
Return the list of exit actions for each state in list states_list
,
if defined for the state, in the same exact order of states_list
Returns a list of states that must be exited when the machine is exiting
from the state source
, considering the lcca
Return list of initial states from argument state
deep to a leaf state.
The function uses :initial
key in the %State{}
definition,
unless it encounter a history state (to be implemented)
## Examples
iex> defs = %State{initial: "s1", substates: %{ "s1" => %State{ initial: "s11", substates: %{ "s11" => %State{}}}}}
iex> eng = Statechart.build(defs)
iex> Statechart.get_initials(eng, "root")
["root", "s1", "s11"]
iex> Statechart.get_initials(eng, "s1")
["s1", "s11"]
Return a transition map if state
have a transition defined
to handle event
, otherwise nil