This document describes the design of the Finite State Machine (FSM) API in Cure.
FSMs in Cure are first-class language constructs that compile to BEAM gen_statem processes. They provide:
Cure FSMs use a simple, declarative syntax:
module MyModule do
# Define a payload record to track FSM data
record MyPayload do
field1: Type1
field2: Type2
end
# Define FSM with initial payload values
fsm MyPayload{field1: initial_value1, field2: initial_value2} do
State1 --> |event1| State2
State1 --> |event2| State3
State2 --> |event3| State1
end
end
Key Points:
:State1, :State2, etc.)|event| syntaxfsm declarationFrom the Std.Fsm module:
type FsmName = Atom # Registered FSM name (e.g., :traffic_light)
type FsmError = Atom # Error types (:invalid_state, :not_found, etc.)
type EventName = Atom # Event identifier (e.g., :timer, :coin)
type StateName = Atom # State identifier (e.g., :Red, :Green)
start_fsm(module: Atom): PidSpawns a new FSM process using the FSM definition from the given module.
fsm declaration# This would start the FSM defined in the TrafficLightFSM module
let fsm_pid = start_fsm(:TrafficLightFSM)
Note: This is a legacy function. The recommended approach is fsm_spawn/2.
fsm_cast(target: Any, event: Any): AnySends an event asynchronously to an FSM instance (fire-and-forget).
(eventname, eventdata) where event_data is a listok)import Std.Pair [pair/2]
# Send event with empty data
let empty_list = []
let event = pair(:coin, empty_list)
fsm_cast(fsm_pid, event)
# Or to a named FSM
let event2 = pair(:push, [])
fsm_cast(:my_turnstile, event2)
fsm_advertise(pid: Any, name: Atom): AnyRegisters a name for an FSM process, allowing it to be referenced by name instead of Pid.
Example:let adv_result = fsm_advertise(fsm_pid, :main_turnstile)
# Now you can use :main_turnstile instead of fsm_pid
fsm_state(target: Any): AnyQueries the current state of an FSM instance.
:Red, :Locked)let current_state = fsm_state(:traffic_light)
# Returns atom like :Red, :Green, or :Yellow
fsmspawn(fsmtype: Atom, initial_data: Any): AnySpawns an FSM instance with a specific type and initial data.
let initial_data = TrafficPayload{cycles_completed: 0, timer_events: 0, emergency_stops: 0}
let fsm_pid = fsm_spawn(:TrafficPayload, initial_data)
fsm_stop(pid: Any): AnyGracefully stops an FSM instance.
Example:fsm_stop(fsm_pid)
fsm_info(pid: Any): AnyGet detailed information about an FSM instance including state, data, and event history.
Example:let info = fsm_info(fsm_pid)
fsmisalive(pid: Any): AnyCheck if an FSM process is still alive.
Returns: Boolean-like value indicating if the FSM is running Example:let alive = fsm_is_alive(fsm_pid)
The FSM process should handle:
{:event, Event}: Trigger a state transition{:get_state, Pid}: Query current state (reply to sender){:register, FsmName}: Register a name for this FSMThe Cure compiler should:
fsm blocks and extract transition table{state, event} to handler functionsHere's a complete working example from examples/06fsmtraffic_light.cure:
module TrafficLightFSM do
export [main/0]
import Std.Fsm [fsm_spawn/2, fsm_cast/2, fsm_advertise/2, fsm_state/1]
import Std.Io [println/1]
import Std.Pair [pair/2]
# Payload record - tracks traffic light statistics
record TrafficPayload do
cycles_completed: Int
timer_events: Int
emergency_stops: Int
end
# Define the TrafficLight FSM
# Initial state is Red (first state in transitions)
fsm TrafficPayload{cycles_completed: 0, timer_events: 0, emergency_stops: 0} do
Red --> |timer| Green
Red --> |emergency| Red
Green --> |timer| Yellow
Green --> |emergency| Red
Yellow --> |timer| Red
Yellow --> |emergency| Red
end
# Main demonstration
def main(): Int =
# Initialize FSM with starting data
let initial_data = TrafficPayload{cycles_completed: 0, timer_events: 0, emergency_stops: 0}
let fsm_pid = fsm_spawn(:TrafficPayload, initial_data)
# Give the FSM a friendly name
let adv_result = fsm_advertise(fsm_pid, :traffic_light)
# Check initial state (should be Red)
let state0 = fsm_state(:traffic_light)
# Send timer event: Red -> Green
let event1 = pair(:timer, [])
let cast1 = fsm_cast(:traffic_light, event1)
let state1 = fsm_state(:traffic_light) # Now Green
# Send timer event: Green -> Yellow
let event2 = pair(:timer, [])
let cast2 = fsm_cast(:traffic_light, event2)
let state2 = fsm_state(:traffic_light) # Now Yellow
# Send emergency event: Yellow -> Red
let event3 = pair(:emergency, [])
let cast3 = fsm_cast(:traffic_light, event3)
let state3 = fsm_state(:traffic_light) # Back to Red
0
end
This example demonstrates:
fsm_spawn/2fsm_advertise/2pair/2 and fsm_cast/2fsm_state/1Potential additions to the FSM API:
fsm_call/2: Synchronous event sending with replyState --> |timeout(1000)| NextStateState --> |event| when condition State2