← Back to Documentation

Cure FSM API Design

Last Updated: October 31, 2025

This document describes the design of the Finite State Machine (FSM) API in Cure.

Overview

FSMs in Cure are first-class language constructs that compile to BEAM gen_statem processes. They provide:

FSM Definition Syntax

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:

Core Types

From 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)

API Functions

start_fsm(module: Atom): Pid

Spawns a new FSM process using the FSM definition from the given module.

Example:
# 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): Any

Sends an event asynchronously to an FSM instance (fire-and-forget).

Example:
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): Any

Registers 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): Any

Queries the current state of an FSM instance.

Example:
let current_state = fsm_state(:traffic_light)
# Returns atom like :Red, :Green, or :Yellow

fsmspawn(fsmtype: Atom, initial_data: Any): Any

Spawns an FSM instance with a specific type and initial data.

Example:
let initial_data = TrafficPayload{cycles_completed: 0, timer_events: 0, emergency_stops: 0}
let fsm_pid = fsm_spawn(:TrafficPayload, initial_data)

Additional API Functions

fsm_stop(pid: Any): Any

Gracefully stops an FSM instance.

Example:
fsm_stop(fsm_pid)

fsm_info(pid: Any): Any

Get detailed information about an FSM instance including state, data, and event history.

Example:
let info = fsm_info(fsm_pid)

fsmisalive(pid: Any): Any

Check 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)

Implementation Notes

Process-Based FSM Runtime

FSM Process Message Protocol

The FSM process should handle:

Compiler Responsibilities

The Cure compiler should:

  1. Parse fsm blocks and extract transition table
  2. Generate FSM definition structure with initial state/payload
  3. Create transition map linking {state, event} to handler functions
  4. Generate process spawning code that initializes FSM state
  5. Implement message loop for handling events and state queries

Complete Example: Traffic Light

Here'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:

Future Enhancements

Potential additions to the FSM API: