FCSTM simulation first run

This page is the short first-run path for the FCSTM simulator. It shows the minimum concepts you need, one reproducible batch command, and where to go next for task recipes or execution-order details.

What the simulator checks

The simulator executes the parsed model directly, before you generate target code. In a simulation run:

  • a leaf state can be the current stoppable state;

  • a composite state contains child states and chooses an initial child;

  • a pseudo state is an automatic routing state, not a normal stopping point;

  • lifecycle actions such as enter, during and exit update variables while transitions move between states.

For full execution-order details, see Execution semantics explanation. For concrete simulator commands, see Simulation tasks. For exact command, export and Python API facts, see Simulation reference.

Run one batch transcript

Batch mode uses the same command processor as the interactive REPL. Commands are separated by semicolons, so a short transcript is enough to verify initial entry, available events, event-driven transition, current state display and history output:

pyfcstm simulate -i ../cli/simple_machine.fcstm \
  -e "cycle; events; cycle Start; current; cycle Stop; history 3" \
  --no-color

The documentation keeps this transcript generated from a real demo script:

────────────────────────────────────────────────────────────
>>> cycle
────────────────────────────────────────────────────────────
Cycle: 1
Current State: SimpleMachine.Idle
Variables:
  counter = 0

────────────────────────────────────────────────────────────
>>> events
────────────────────────────────────────────────────────────
Available Events:
  • Start (SimpleMachine.Idle.Start)

────────────────────────────────────────────────────────────
>>> cycle Start
────────────────────────────────────────────────────────────
Cycle: 2
Current State: SimpleMachine.Running
Variables:
  counter = 0

────────────────────────────────────────────────────────────
>>> current
────────────────────────────────────────────────────────────
Cycle: 2
Current State: SimpleMachine.Running
Variables:
  counter = 0

────────────────────────────────────────────────────────────
>>> cycle Stop
────────────────────────────────────────────────────────────
Cycle: 3
Current State: SimpleMachine.Stopped
Variables:
  counter = 1

────────────────────────────────────────────────────────────
>>> history 3
────────────────────────────────────────────────────────────
 Cycle          State          counter
---------------------------------------
   1     SimpleMachine.Idle       0
   2    SimpleMachine.Running     0
   3    SimpleMachine.Stopped     1

The first cycle initializes the machine at SimpleMachine.Idle. The events command then shows that Start is available from the current state. After cycle Start the current state is SimpleMachine.Running; cycle Stop reaches SimpleMachine.Stopped and the short history table shows the three recorded cycles.

Try one Python runtime loop

If you are embedding pyfcstm in Python tests or tools, the runtime API follows the same model: parse DSL, build the state-machine model, construct SimulationRuntime, and call cycle(). Each call returns a CycleResult. Its legacy value is currently None, while input_events, consumed_events and unconsumed_events tell you which canonical event paths were supplied, used or left unused in that cycle.

Minimal Python simulation loop
#!/usr/bin/env python3
from pyfcstm.dsl import parse_with_grammar_entry
from pyfcstm.model import parse_dsl_node_to_state_machine
from pyfcstm.simulate import SimulationRuntime

dsl_code = """
def int counter = 0;

state System {
    [*] -> Idle;

    state Idle {
        during {
            counter = counter + 1;
        }
    }

    state Active {
        during {
            counter = counter + 10;
        }
    }

    Idle -> Active : if [counter >= 5];
    Active -> Idle : if [counter >= 50];
}
"""

# Parse and create state machine
ast = parse_with_grammar_entry(dsl_code, 'state_machine_dsl')
sm = parse_dsl_node_to_state_machine(ast)

# Create runtime
runtime = SimulationRuntime(sm)

# Execute cycles
print(f"Initial: state={'.'.join(runtime.current_state.path)}, counter={runtime.vars['counter']}")

for i in range(1, 8):
    runtime.cycle()
    print(f"Cycle {i}: state={'.'.join(runtime.current_state.path)}, counter={runtime.vars['counter']}")

Output:

Initial: state=System, counter=0
Cycle 1: state=System.Idle, counter=1
Cycle 2: state=System.Idle, counter=2
Cycle 3: state=System.Idle, counter=3
Cycle 4: state=System.Idle, counter=4
Cycle 5: state=System.Idle, counter=5
Cycle 6: state=System.Active, counter=15
Cycle 7: state=System.Active, counter=25

The example only prints state and variables. If you need the exact CycleResult fields, event input forms, export formats or public runtime exceptions, use Simulation reference.

Old topic map

The previous long simulation guide mixed tutorial, how-to and explanation material. The main topics now live here:

Simulation topic destinations

Old topic

New location

Python API loops and event injection

Simulation tasks

Batch mode, REPL commands, export and output formats

Simulation tasks

Hot start task usage

Simulation tasks

Abstract handlers

Simulation tasks

Configuration settings and command-line facts

Simulation reference

Testing, debugging and best-practice notes

Simulation tasks

Business examples and long semantic walkthroughs

Execution semantics explanation

Execution order, composite entry, aspect actions and pseudo states

Execution semantics explanation

DSL syntax used by examples

DSL reference

DSL semantic background

DSL semantics explanation

The legacy example resources remain in this directory so old links and source-output pairs stay stable. The migration log records each resource and the final cleanup audit explains why these compatibility resources remain.