Finite-state machine builder on top of Yog.
Choreo.FSM lets you define states, transitions, and render classic
state-machine diagrams. It supports normal states, initial states, and
final (accepting) states.
When to use
Use Choreo.FSM when modelling stateful behaviour — protocol handlers,
UI flows, game states, or embedded-system controllers. It verifies
determinism, finds dead states, and checks whether any input sequence
leads to acceptance.
Further reading
Quick Start
fsm =
Choreo.FSM.new()
|> Choreo.FSM.add_initial_state(:idle, label: "Idle")
|> Choreo.FSM.add_state(:running, label: "Running")
|> Choreo.FSM.add_final_state(:done, label: "Done")
|> Choreo.FSM.add_transition(:idle, :running, label: "start")
|> Choreo.FSM.add_transition(:running, :done, label: "finish")
|> Choreo.FSM.add_transition(:running, :idle, label: "pause")
dot = Choreo.FSM.to_dot(fsm)Diagram
Themes
Use :default, :dark, or a custom Choreo.Theme struct:
dot = Choreo.FSM.to_dot(fsm, theme: :dark)
Summary
Functions
Adds a final (accepting) state to the FSM.
Adds an initial state to the FSM.
Adds a state to the FSM.
Adds a transition (directed edge) between two states.
Returns the complement FSM: final states become normal, and normal states
become final. Initial states keep their :initial type.
Returns all final state IDs.
Returns the set of initial state IDs.
Creates a new empty FSM.
Prunes unreachable and dead states, returning a smaller equivalent FSM.
Removes a state from the set of final states.
Removes a state from the set of initial states.
Returns all state IDs in the FSM.
Renders the FSM to DOT format.
Returns all transitions as {from, to, label} tuples.
Types
@type t() :: %Choreo.FSM{ edge_meta: %{optional(Yog.Multi.Graph.edge_id()) => map()}, graph: Yog.Multi.Graph.t(), meta: map() }
Functions
@spec add_final_state(t(), Yog.node_id(), keyword()) :: t()
Adds a final (accepting) state to the FSM.
Final states are rendered as double circles.
Options
:label(String.t/0):shape(atom/0):fillcolor(String.t/0):fontcolor(String.t/0):style(String.t/0):penwidth:image(String.t/0)
Examples
iex> fsm = Choreo.FSM.new() |> Choreo.FSM.add_final_state(:done)
iex> :done in Choreo.FSM.final_states(fsm)
trueDiagram
@spec add_initial_state(t(), Yog.node_id(), keyword()) :: t()
Adds an initial state to the FSM.
Initial states are rendered with a filled entry-point dot and an incoming arrow in DOT output.
Options
:label(String.t/0):shape(atom/0):fillcolor(String.t/0):fontcolor(String.t/0):style(String.t/0):penwidth:image(String.t/0)
Examples
iex> fsm = Choreo.FSM.new() |> Choreo.FSM.add_initial_state(:idle)
iex> :idle in Choreo.FSM.initial_states(fsm)
trueDiagram
@spec add_state(t(), Yog.node_id(), keyword()) :: t()
Adds a state to the FSM.
Options
:type:label(String.t/0):shape(atom/0):fillcolor(String.t/0):fontcolor(String.t/0):style(String.t/0):penwidth:image(String.t/0)
Examples
iex> fsm = Choreo.FSM.new() |> Choreo.FSM.add_state(:idle, label: "Idle")
iex> fsm.graph.nodes[:idle].label
"Idle"Diagram
@spec add_transition(t(), Yog.node_id(), Yog.node_id(), keyword()) :: t()
Adds a transition (directed edge) between two states.
Multiple transitions are allowed per (from, to) pair (parallel edges),
as long as they have unique labels from the source state.
Options
:label(String.t/0):guard(String.t/0)
Examples
iex> fsm =
...> Choreo.FSM.new()
...> |> Choreo.FSM.add_state(:a)
...> |> Choreo.FSM.add_state(:b)
...> |> Choreo.FSM.add_transition(:a, :b, label: "go")
iex> Yog.Multi.edges_between(fsm.graph, :a, :b) != []
trueDiagram
Returns the complement FSM: final states become normal, and normal states
become final. Initial states keep their :initial type.
Examples
iex> fsm =
...> Choreo.FSM.new()
...> |> Choreo.FSM.add_initial_state(:a)
...> |> Choreo.FSM.add_final_state(:b)
iex> comp = Choreo.FSM.complement(fsm)
iex> :a in Choreo.FSM.final_states(comp)
true
iex> :b in Choreo.FSM.final_states(comp)
falseDiagram
@spec final_states(t()) :: [Yog.node_id()]
Returns all final state IDs.
Examples
iex> fsm = Choreo.FSM.new() |> Choreo.FSM.add_final_state(:done)
iex> :done in Choreo.FSM.final_states(fsm)
true
@spec initial_states(t()) :: MapSet.t(Yog.node_id())
Returns the set of initial state IDs.
Examples
iex> fsm = Choreo.FSM.new() |> Choreo.FSM.add_initial_state(:idle)
iex> :idle in Choreo.FSM.initial_states(fsm)
true
Creates a new empty FSM.
Options
:directed— whether the underlying graph is directed (default:true)
Examples
iex> fsm = Choreo.FSM.new()
iex> fsm.graph.kind == :directed
true
Prunes unreachable and dead states, returning a smaller equivalent FSM.
- Unreachable states — no path from any initial state
- Dead states — no path to any final state
Examples
iex> fsm =
...> Choreo.FSM.new()
...> |> Choreo.FSM.add_initial_state(:a)
...> |> Choreo.FSM.add_state(:b)
...> |> Choreo.FSM.add_state(:trap)
...> |> Choreo.FSM.add_final_state(:c)
...> |> Choreo.FSM.add_transition(:a, :c, label: "go")
iex> pruned = Choreo.FSM.prune(fsm)
iex> :a in Choreo.FSM.states(pruned)
true
iex> :b in Choreo.FSM.states(pruned)
false
iex> :trap in Choreo.FSM.states(pruned)
falseDiagram
@spec remove_final_state(t(), Yog.node_id()) :: t()
Removes a state from the set of final states.
Examples
iex> fsm = Choreo.FSM.new() |> Choreo.FSM.add_final_state(:done) |> Choreo.FSM.remove_final_state(:done)
iex> :done in Choreo.FSM.final_states(fsm)
false
@spec remove_initial_state(t(), Yog.node_id()) :: t()
Removes a state from the set of initial states.
Examples
iex> fsm = Choreo.FSM.new() |> Choreo.FSM.add_initial_state(:idle) |> Choreo.FSM.remove_initial_state(:idle)
iex> :idle in Choreo.FSM.initial_states(fsm)
false
@spec states(t()) :: [Yog.node_id()]
Returns all state IDs in the FSM.
Examples
iex> fsm = Choreo.FSM.new() |> Choreo.FSM.add_state(:a) |> Choreo.FSM.add_state(:b)
iex> Enum.sort(Choreo.FSM.states(fsm))
[:a, :b]
Renders the FSM to DOT format.
Options
:theme—:default,:dark, or aChoreo.Themestruct
Examples
iex> fsm = Choreo.FSM.new() |> Choreo.FSM.add_state(:a)
iex> dot = Choreo.FSM.to_dot(fsm)
iex> String.contains?(dot, "digraph")
true
iex> String.contains?(dot, "a")
true
@spec transitions(t()) :: [{Yog.node_id(), Yog.node_id(), String.t()}]
Returns all transitions as {from, to, label} tuples.
Examples
iex> fsm =
...> Choreo.FSM.new()
...> |> Choreo.FSM.add_state(:a)
...> |> Choreo.FSM.add_state(:b)
...> |> Choreo.FSM.add_transition(:a, :b, label: "go")
iex> Choreo.FSM.transitions(fsm)
[{:a, :b, "go"}]