PyFCSTM DSL Syntax Tutorial
Overview
The PyFCSTM Domain Specific Language (DSL) provides a comprehensive syntax for defining hierarchical finite state machines with expressions, conditions, and lifecycle actions. This tutorial covers all language constructs, semantic rules, and best practices for writing correct DSL programs.
Language Structure
Program Organization
A complete DSL program consists of optional variable definitions followed by a single root state definition:
program ::= def_assignment* state_definition EOF
The top-level structure ensures every state machine has exactly one root state that may contain nested substates and transitions.
Variable Definitions
Syntax
Variable definitions declare typed variables with initial values using the def keyword:
def_assignment ::= 'def' ('int'|'float') ID '=' init_expression ';'
Correct Usage
Integer Variables:
def int counter = 0;def int max_attempts = 5;def int flags = 0xFF;
Float Variables:
def float temperature = 25.5;def float pi_value = pi;def float ratio = 3.14 * 2;
Semantic Rules
Variable definitions must follow these semantic constraints:
Unique Names: Each variable name must be unique within the program scope
Type Consistency: Initial expressions must evaluate to values compatible with the declared type
Expression Validity: Initial expressions can only reference mathematical constants and literals
Common Errors
Incorrect Usage:
Duplicate definitions:
def int x = 1; def float x = 2.0;(semantic error)Undefined references:
def int y = unknown_var;(semantic error)Type mismatches: Variables must use appropriate types for their intended values
State Definitions
Syntax Types
The DSL supports two types of state definitions:
state_definition ::= leafStateDefinition | compositeStateDefinition
leafStateDefinition ::= 'state' ID ';'
compositeStateDefinition ::= 'state' ID '{' state_inner_statement* '}'
Leaf States
Leaf states represent terminal states with no internal structure:
Correct Usage:
state Idle;state Running;state Error;
Leaf states are the simplest form and typically represent final operational states.
Composite States
Composite states contain nested substates, transitions, and lifecycle actions:
Correct Usage:
state Machine {
state Off;
state On {
state Slow;
state Fast;
}
[*] -> Off;
Off -> On: if [power_switch == 1];
On -> Off: if [power_switch == 0];
}
Semantic Rules
State definitions must adhere to these semantic constraints:
Unique Names: State names must be unique within their containing scope
Entry Transitions: Composite states must have at least one entry transition (
[*] -> state)State References: All transition targets must reference existing states
Hierarchical Consistency: Nested states follow proper parent-child relationships
Common Errors
Incorrect Usage:
Missing entry transitions in composite states (semantic error)
Duplicate state names within the same scope (semantic error)
Self-referential state hierarchies (structural error)
Transition Definitions
Transition Types
The DSL supports three types of transitions with distinct syntax patterns:
transition_definition ::= entryTransitionDefinition | normalTransitionDefinition | exitTransitionDefinition
Entry Transitions
Entry transitions define the initial state when entering a composite state:
Syntax: [*] -> target_state [: chain_id|:: event_name] [if [condition]] [effect { operations }] ';'
Correct Usage:
[*] -> Idle;[*] -> Running: startup_event;[*] -> Active: if [initialized == 0x1];[*] -> Ready effect { counter = 0; }
Normal Transitions
Normal transitions connect two named states:
Syntax: from_state -> to_state [: chain_id|:: event_name] [if [condition]] [effect { operations }] ';'
Correct Usage:
Idle -> Running;Slow -> Fast : speed_up;Slow -> Fast :: speed_up;(this is different from the above one, will explain it later)Active -> Inactive: if [timeout > 100];Processing -> Complete effect { result = output; }
Exit Transitions
Exit transitions define how to leave a composite state:
Syntax: from_state -> [*] [: chain_id|:: event_name] [if [condition]] [effect { operations }] ';'
Correct Usage:
Error -> [*];Complete -> [*]: finish_event;Running -> [*]: if [shutdown_requested];Active -> [*] effect { cleanup_flag = 0x1; }
Chain Identifiers
Chain identifiers provide hierarchical event naming using dot notation:
Correct Usage:
State1 -> State2: event_name;Parent -> Child: parent.child.event;Active -> Idle: system.shutdown.requested;
It’s worth noting that when using:
state Root {
[*] -> A;
A -> B : E;
B -> C : E;
}
This means that the events triggering both A -> B and B -> C transitions will be the same event, specifically the event E under the Root namespace, i.e., Root.E. If you wish to distinguish between these two events, meaning you don’t want to trigger two different transitions based on the same event, you should write:
state Root {
[*] -> A;
A -> B : A.E;
B -> C : B.E;
}
In this case, the two events are using Root.A.E and Root.B.E respectively, achieving the distinction.
Additionally, to simplify this expression, we provide an extra syntactic sugar:
state Root {
[*] -> A;
A -> B :: E;
B -> C :: E;
}
When using the :: symbol, it means using the event under the namespace of the source state, so this syntax sugar expression is completely equivalent to the method above. Specifically, for [*] -> A, using the :: symbol is equivalent to using :.
Semantic Rules
Transitions must satisfy these semantic constraints:
State Existence: Both source and target states must exist in the current scope
Variable Validity: All variables in conditions and effects must be declared
Expression Types: Guard conditions must evaluate to boolean values
Entry Requirements: Composite states require at least one entry transition
Common Errors
Incorrect Usage:
References to undefined states (semantic error)
Missing entry transitions for composite states (semantic error)
Invalid variable references in conditions or effects (semantic error)
Malformed chain identifiers with invalid dot notation (syntax error)
Expression System
Expression Hierarchy
The DSL supports comprehensive expression types for mathematical and logical operations:
init_expression ::= conditional_expression
num_expression ::= conditional_expression
cond_expression ::= conditional_expression
Literal Values
Integer Literals:
Decimal:
42,0,123Hexadecimal:
0xFF,0x2A,0x100
Float Literals:
Standard:
3.14,2.5,0.1Scientific:
1e6,2.5e-3,1E10Mathematical constants:
pi,E,tau
Boolean Literals:
True values:
true,True,TRUEFalse values:
false,False,FALSE
Operators
Arithmetic Operators (by precedence):
Parentheses:
()Unary:
+,-Power:
**Multiplication/Division:
*,/,%Addition/Subtraction:
+,-
Comparison Operators:
Relational:
<,>,<=,>=Equality:
==,!=
Logical Operators:
Unary:
!,notBinary:
&&,||,and,or
Bitwise Operators:
Bitwise:
&,|,^Shift:
<<,>>
Mathematical Functions
The DSL provides extensive mathematical function support:
Trigonometric Functions:
Basic:
sin(x),cos(x),tan(x)Inverse:
asin(x),acos(x),atan(x)Hyperbolic:
sinh(x),cosh(x),tanh(x)
Exponential and Logarithmic:
exp(x),log(x),log10(x),log2(x)
Other Mathematical Functions:
sqrt(x),abs(x),ceil(x),floor(x),round(x)
Conditional Expressions
Conditional expressions use ternary operator syntax:
Syntax: (condition) ? true_value : false_value
Correct Usage:
(x > 0) ? 1 : -1(temperature > 25.0) ? "hot" : "cold"(count == 0) ? initial_value : current_value
It is worth noting that you must use a () outside the condition explicitly. This is quite different from common coding languages.
Semantic Rules
Expressions must follow these semantic constraints:
Variable Declarations: All referenced variables must be declared
Type Consistency: Operations must be performed on compatible types
Function Arguments: Mathematical functions require appropriate argument types
Boolean Context: Conditional guards must evaluate to boolean values
Common Errors
Incorrect Usage:
Undefined variable references (semantic error)
Type mismatches in operations (semantic error)
Invalid function arguments (semantic error)
Malformed conditional expressions (syntax error)
Lifecycle Actions
Action Types
States support three lifecycle phases with corresponding action definitions:
enter_definition ::= enterOperations | enterAbstractFunc
during_definition ::= duringOperations | duringAbstractFunc
exit_definition ::= exitOperations | exitAbstractFunc
Enter Actions
Enter actions execute when a state is entered:
Concrete Operations:
enter {
counter = 0;
status_flag = 0x1;
}
Abstract Functions:
enter abstract initialize_system;
enter abstract setup_resources /*
Initialize system resources
*/
It is worth noting that the /* xxx */ part of the abstract enter function is a part of the enter declaration, and it can be reflected in the code generation stage.
During Actions
During actions execute while a state is active, with optional aspects for composite states:
Before Aspect (Composite States Only):
during before {
update_counters = update_counters + 1;
}
After Aspect (Composite States Only):
during after {
cleanup_temporary = 0x1;
}
Regular During Actions (Leaf State Only):
during {
heartbeat_counter = heartbeat_counter + 1;
}
Abstract During Actions:
during abstract abstract_during;
during abstract abstract_during_with_comment /*
This is the comment
*/
It is worth noting that the /* xxx */ part of the abstract during function is a part of the during declaration, and it can be reflected in the code generation stage.
Exit Actions
Exit actions execute when leaving a state:
Concrete Operations:
exit {
save_state = current_value;
cleanup_flag = 0x1;
}
Abstract Functions:
exit abstract cleanup_resources;
exit abstract finalize_operations /*
Clean up before exit
*/
It is worth noting that the /* xxx */ part of the abstract exit function is a part of the exit declaration, and it can be reflected in the code generation stage.
Semantic Rules
Lifecycle actions must adhere to these constraints:
Variable Validity: All referenced variables must be declared
Aspect Restrictions:
beforeandafteraspects only apply to composite statesAssignment Targets: Only declared variables can be assigned values
Expression Types: Assignment expressions must be type-compatible
Common Errors
Incorrect Usage:
Undefined variable assignments (semantic error)
Invalid aspect usage on leaf states (semantic error)
Type mismatches in assignments (semantic error)
Malformed abstract function declarations (syntax error)
Lifecycle Flows of State Machine
Operational Statements
Assignment Types
The DSL supports different assignment contexts with distinct operators:
Operation Assignment:
Syntax:
variable = expression;Usage: Standard assignment in effect blocks and lifecycle actions
Correct Usage
In Effect Blocks:
State1 -> State2 effect {
counter = counter + 1;
status = 1;
result = sin(angle) * radius;
}
In Lifecycle Actions:
enter {
initialization_flag = 0x1;
start_time = current_time;
}
Semantic Rules
Operational statements must satisfy:
Variable Existence: Target variables must be declared
Expression Validity: Right-hand expressions must be well-formed
Type Compatibility: Assignments must respect variable types
Scope Validity: Variables must be accessible in the current context
Common Errors
Incorrect Usage:
Assignment to undeclared variables (semantic error)
Type mismatches in assignments (semantic error)
Invalid expression syntax on right-hand side (syntax error)
Semantic Validation Rules
Comprehensive Validation
The DSL parser performs extensive semantic validation during the parsing process:
Variable Validation:
Unique variable names across the program
All referenced variables must be declared
Type consistency in assignments and expressions
State Validation:
Unique state names within each scope
Valid state references in all transitions
Required entry transitions for composite states
Expression Validation:
Well-formed mathematical and logical expressions
Valid function calls with appropriate arguments
Proper operator precedence and associativity
Structural Validation:
Proper nesting of composite states
Valid lifecycle action placement
Correct transition connectivity
Error Handling
The parser provides detailed error messages for common mistakes:
Syntax Errors:
Malformed expressions and statements
Missing punctuation and keywords
Invalid token sequences
Semantic Errors:
Undefined variable or state references
Type mismatches in operations
Structural inconsistencies
Advanced Patterns and Best Practices
Hierarchical State Organization
Organize complex state machines using hierarchical decomposition:
state SystemController {
state PowerManagement {
state LowPower;
state NormalPower;
state HighPower;
}
state OperationalMode {
state Idle;
state Processing {
state DataInput;
state Computation;
state OutputGeneration;
}
}
}
Event-Driven Transitions
Use chain identifiers to create clear event hierarchies:
ProcessingState -> IdleState: user.input.complete;
IdleState -> ProcessingState: system.timer.expired;
ErrorState -> RecoveryState: error.recovery.initiated;
Conditional Logic Patterns
Implement complex decision logic using conditional expressions:
Active -> Standby: if [(battery_level < 20) && (charging_state == 0x0)];
Normal -> Emergency: if [temperature > critical_temp || pressure > max_pressure];
Resource Management
Use lifecycle actions for proper resource management:
state ResourceManager {
enter {
resource_count = 0;
allocation_flag = 0x1;
}
during {
heartbeat_counter = heartbeat_counter + 1;
}
exit {
cleanup_flag = 0x1;
resource_count = 0;
}
}
Integration with Code Generation
AST Export Interface
All DSL constructs implement the AST export interface for code generation:
The parsed DSL programs can be converted back to AST nodes for code generation and analysis purposes. This enables round-trip conversion and integration with various target platforms.
PlantUML Export
State machines can be exported to PlantUML format for visualization:
The PlantUML export provides graphical representation of state machine structure, transitions, and relationships, facilitating documentation and design review processes.
Here is a full example of FCSTM model DSL code
1def int a = 0;
2def int b = 0x0;
3def int round_count = 0;
4state TrafficLight {
5 state InService {
6 enter {
7 a = 0;
8 b = 0;
9 round_count = 0;
10 }
11 enter abstract InServiceAbstractEnter /*
12 Abstract Operation When Entering State 'InService'
13 TODO: Should be Implemented In Generated Code Framework
14 */
15 during before abstract InServiceBeforeEnterChild /*
16 Abstract Operation Before Entering Child States of State 'InService'
17 TODO: Should be Implemented In Generated Code Framework
18 */
19 during after abstract InServiceAfterEnterChild /*
20 Abstract Operation After Entering Child States of State 'InService'
21 TODO: Should be Implemented In Generated Code Framework
22 */
23 exit abstract InServiceAbstractExit /*
24 Abstract Operation When Leaving State 'InService'
25 TODO: Should be Implemented In Generated Code Framework
26 */
27 state Red {
28 during {
29 a = 0x1 << 2;
30 }
31 }
32 state Yellow;
33 state Green;
34 [*] -> Red :: Start effect {
35 b = 0x1;
36 }
37 Red -> Green effect {
38 b = 0x3;
39 }
40 Green -> Yellow effect {
41 b = 0x2;
42 }
43 Yellow -> Red : if [a >= 10] effect {
44 b = 0x1;
45 round_count = round_count + 1;
46 }
47 }
48 state Idle;
49 [*] -> InService;
50 InService -> Idle :: Maintain;
51 Idle -> [*];
52}
It can be exported to PlantUML code, and get visualized as the following
Notes
This tutorial provides comprehensive coverage of the PyFCSTM DSL syntax based on the ANTLR grammar definition, AST node types, semantic validation rules, and extensive test cases. The language supports hierarchical state machines with rich expression capabilities, lifecycle management, and comprehensive validation to ensure correct state machine definitions.
For implementation details, refer to the grammar definition, parsing pipeline, and model system documentation. The test suite provides additional examples and validation patterns for complex use cases.
Comments and Documentation
Comment Styles
The DSL supports multiple comment formats for documentation:
Line Comments:
C++ style:
// This is a commentPython style:
# This is also a commentDocumentation Best Practices
Comments should be used to:
Explain complex state machine logic
Document variable purposes and constraints
Clarify transition conditions and effects
Provide context for abstract function implementations