A Python framework for parsing finite state machine DSL and generating executable code in multiple target languages.
Project description
pyfcstm
pyfcstm is a powerful Python framework designed for parsing a Finite State Machine (FSM) Domain-Specific Language (DSL) and generating executable code in multiple target languages based on user-defined templates. It focuses on modeling Hierarchical State Machines (Harel Statecharts) and automating code generation, making it ideal for developing embedded systems, protocol implementations, and complex control logic.
Core Features
pyfcstm aims to provide a complete solution from conceptual design to code implementation. Its core strengths include:
| Feature | Description | Advantage | Documentation Pointer |
|---|---|---|---|
| FSM DSL | A concise and readable DSL syntax for defining states, nesting, transitions, events, conditions, and effects. | Focus on state machine logic, not programming language details. | DSL Syntax Tutorial |
| Hierarchical State Machines | Supports nested states and composite state lifecycles (enter, during, exit). |
Capable of modeling complex real-time systems and protocols, enhancing maintainability. | DSL Syntax Tutorial - State Definitions |
| Expression System | Built-in mathematical and logical expression parser supporting variable definition, conditional guards, and state effects (effect). |
Allows defining the state machine's internal data and behavior at the DSL level. | DSL Syntax Tutorial - Expression System |
| Templated Code Generation | Based on the Jinja2 template engine, rendering the state machine model into target code (e.g., C/C++, Python, Rust). | Extremely high flexibility, supporting code generation for virtually any programming language. | Template Tutorial |
| Cross-Language Support | Easily enables state machine code generation for embedded or high-performance languages like C/C++ through the template system. | Suitable for scenarios where state machine logic needs to be deployed across different platforms or languages. | Template Tutorial - Expression Styles |
| PlantUML Integration | Directly converts DSL files into PlantUML code for generating state diagram visualizations. | Facilitates design review and documentation generation. | CLI Guide - plantuml |
Installation
You can easily install pyfcstm using the pip command line from the official PyPI site:
pip install pyfcstm
More Information: Please refer to the Installation Documentation for detailed steps and environment checks.
Quick Start
1. Using the Command Line Interface (CLI)
pyfcstm provides two main command-line subcommands: plantuml for visualization and generate for code generation.
Generate PlantUML State Diagram
Use the plantuml subcommand to convert a DSL file into PlantUML format, which can then be used to generate a state
diagram:
# Assuming your DSL code is saved in test_dsl_code.fcstm
pyfcstm plantuml -i test_dsl_code.fcstm -o traffic_light.puml
Tip: The generated .puml file can be rendered online at PlantUML Server
or locally using the PlantUML tool.
Templated Code Generation
Use the generate subcommand, along with a template directory, to generate target language code:
# -i: Input DSL file
# -t: Path to the template directory
# -o: Output directory for the generated code
pyfcstm generate -i test_dsl_code.fcstm -t template_dir/ -o generated_code_dir/
Note: You can add the --clear flag to clear the output directory before generation.
2. Using the Python API
You can also use pyfcstm's core API directly within your Python projects for custom parsing and rendering workflows.
from pyfcstm.dsl import parse_with_grammar_entry
from pyfcstm.model.model import parse_dsl_node_to_state_machine
from pyfcstm.render import StateMachineCodeRenderer
# 1. Define or load the DSL code
dsl_code = """
def int a = 0;
def int b = 0x0;
def int round_count = 0; // define variables
state TrafficLight {
// ... state and transition definitions ...
state Red {
during {
a = 0x1 << 2;
}
}
state Yellow;
state Green;
[*] -> Red :: Start effect {
b = 0x1;
};
// ... more transitions ...
}
"""
if __name__ == '__main__':
# 2. Parse the DSL code to generate an Abstract Syntax Tree (AST)
ast_node = parse_with_grammar_entry(dsl_code, entry_name='state_machine_dsl')
# 3. Convert the AST into a State Machine Model
model = parse_dsl_node_to_state_machine(ast_node)
# 4. Initialize the renderer and specify the template directory
renderer = StateMachineCodeRenderer(
template_dir='../fsm_generation_template'
)
# 5. Render the model to generate code in the specified directory
renderer.render(model, 'test_output_x')
3. Example DSL Code (Traffic Light Example)
The following Traffic Light state machine example, included in the original README.md, demonstrates the core
syntax of the pyfcstm DSL:
def int a = 0;
def int b = 0x0;
def int round_count = 0; // define variables
state TrafficLight {
>> during before {
a = 0;
}
>> during before abstract FFT;
>> during before abstract TTT;
>> during after {
a = 0xff;
b = 0x1;
}
!InService -> [*] :: Error;
state InService {
enter {
a = 0;
b = 0;
round_count = 0;
}
enter abstract InServiceAbstractEnter /*
Abstract Operation When Entering State 'InService'
TODO: Should be Implemented In Generated Code Framework
*/
// for non-leaf state, either 'before' or 'after' aspect keyword should be used for during block
during before abstract InServiceBeforeEnterChild /*
Abstract Operation Before Entering Child States of State 'InService'
TODO: Should be Implemented In Generated Code Framework
*/
during after abstract InServiceAfterEnterChild /*
Abstract Operation After Entering Child States of State 'InService'
TODO: Should be Implemented In Generated Code Framework
*/
exit abstract InServiceAbstractExit /*
Abstract Operation When Leaving State 'InService'
TODO: Should be Implemented In Generated Code Framework
*/
state Red {
during { // no aspect keywords ('before', 'after') should be used for during block of leaf state
a = 0x1 << 2;
}
}
state Yellow;
state Green;
[*] -> Red :: Start effect {
b = 0x1;
};
Red -> Green effect {
b = 0x3;
};
Green -> Yellow effect {
b = 0x2;
};
Yellow -> Red : if [a >= 10] effect {
b = 0x1;
round_count = round_count + 1;
};
Green -> Yellow : /Idle.E2;
Yellow -> Yellow : /E2;
}
state Idle;
[*] -> InService;
InService -> Idle :: Maintain;
Idle -> Idle :: E2;
Idle -> [*];
}
DSL Syntax Overview
The pyfcstm DSL syntax is inspired by UML Statecharts and supports the following key elements:
| Element | Keyword | Description | Example | Documentation Pointer |
|---|---|---|---|---|
| Variable Definition | def int/float |
Defines integer or float variables for the state machine's internal data. | def int counter = 0; |
Variable Definitions |
| State | state |
Defines a state, supporting Leaf States and Composite States (nesting). | state Running { ... } |
State Definitions |
| Transition | -> |
Defines transitions between states, supporting Entry ([*]) and Exit ([*]) transitions. |
Red -> Green; |
Transition Definitions |
| Forced Transition | ! |
Defines a forced transition, which bypasses the source state's exit action. |
!InService -> [*] :: Error; |
Transition Definitions |
| Event | :: or : |
The event that triggers a transition, supporting Local Events (::) and Global Events (: or /). |
Red -> Green :: Timer; |
Transition Definitions |
| Guard Condition | if [...] |
A condition that must be true for the transition to occur. | Yellow -> Red : if [a >= 10]; |
Expression System |
| Effect | effect { ... } |
Operations (variable assignments) executed when the transition occurs. | effect { b = 0x1; } |
Operational Statements |
| Lifecycle Actions | enter, during, exit |
Actions executed when a state is entered, active, or exited. | enter { a = 0; } |
Lifecycle Actions |
| Abstract Action | abstract |
Declares an abstract function that must be implemented in the generated code framework. | enter abstract Init; |
Lifecycle Actions |
| Aspect Action | >> during |
Special during action for composite states, executed before (before) or after (after) the leaf state's during actions. |
>> during before { ... } |
Lifecycle Actions |
| Pseudo State | pseudo state |
Special leaf state that will not apply the aspect actions of the ancestor states. | pseudo state LeafState; |
Pseudo States |
Key DSL Concepts
Hierarchical State Management
The DSL inherently supports state nesting, allowing for the creation of complex, yet organized, state machines. A
composite state's lifecycle actions (enter, during, exit) are executed relative to its substates.
The >> during before/after aspect actions provide a powerful mechanism for Aspect-Oriented Programming (AOP)
within the state machine, enabling logic to be injected before or after the substate's transitions or actions.
Event Scoping
Transitions can be triggered by events with different scopes:
- Local Event (
::): The event is scoped to the source state's namespace. E.g.,StateA -> StateB :: EventXmeans the event isStateA.EventX. - Global Event (
: /): The event is scoped from the root of the state machine. E.g.,StateA -> StateB : /GlobalEventmeans the event isGlobalEvent. - Chain ID (
:): The event is scoped relative to the current state's parent. E.g.,StateA -> StateB : Parent.EventY.
Code Generation Template System
The core value of pyfcstm lies in its highly flexible template system, which allows users complete control over the structure and content of the generated code.
Template Directory Structure
The template directory follows the convention over configuration principle, containing template files and a configuration file:
template_directory/
├── config.yaml # Core configuration file, defining rendering rules, globals, and filters
├── *.j2 # Jinja2 template files for dynamic code generation
├── *.c # Static files, copied directly to the output directory
└── ... # Directory structure is preserved
More Information: See Template System Architecture Details for a deep dive into the structure.
Core Configuration (config.yaml)
The config.yaml file is the "brain" of the template system, defining:
expr_styles: Defines expression rendering rules for different target languages (e.g., C, Python), enabling cross-language expression conversion. This is crucial for translating DSL expressions likea >= 10into the correct syntax for C (a >= 10) or Python (a >= 10).globals: Defines global variables and functions (including importing external Python functions) accessible in all templates. This allows for reusable logic and constants across the generated code.filters: Defines custom filters for data transformation within templates. For example, a filter could be used to convert a state name to a valid C function name (e.g.,{{ state.name \| to_c_func_name }}).ignores: Defines files or directories to be ignored during the code generation process, usingpathspecfor git-like pattern matching.
More Information: See Configuration File Deep Analysis for detailed configuration options.
Template Rendering
In the .j2 template files, you have access to the complete State Machine Model Object and can use Jinja2 syntax
combined with custom filters and global functions to generate code.
Key Model Objects:
model: The root state machine object.state: A state object, with properties likename,is_leaf_state,transitions, andparent.transition: A transition object, with properties likefrom_state,to_state,guard, andeffects.
Example Template Snippet (Jinja2):
{% for state in model.walk_states() %}
void {{ state.name }}_enter() {
// Concrete enter actions
{% for op in state.enter_operations %}
{{ op.var_name }} = {{ op.expr | expr_render(style='c') }};
{% endfor %}
// Abstract enter actions
{% for abstract_func in state.enter_abstract_functions %}
{{ abstract_func.name }}(); // {{ abstract_func.doc }}
{% endfor %}
}
{% endfor %}
More Information: Please refer to the Template Syntax Deep Analysis for a comprehensive guide on template development.
Contribution & Support
pyfcstm is an open-source project, and contributions in all forms are welcome:
- Report Bugs: If you find any issues, please submit them on GitHub Issues.
- Submit Pull Requests: Code improvements, new features, or documentation updates are highly appreciated.
- Suggest Features: Feel free to discuss any feature suggestions or improvement ideas in the Issues section.
Documentation: https://pyfcstm.readthedocs.io/ Source Code: https://github.com/HansBug/pyfcstm License: MIT License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyfcstm-0.2.1.tar.gz.
File metadata
- Download URL: pyfcstm-0.2.1.tar.gz
- Upload date:
- Size: 96.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f0e5184efb79dbb4d0f56a614f4d7974f814feb53ed96423ff4e023b4f1e3ecd
|
|
| MD5 |
372c784045ec2addb1951833f65e0a89
|
|
| BLAKE2b-256 |
c6da980727f932ce32d57851e6440e1b709e2bd9338387f39f6a854ab00d5bb3
|
File details
Details for the file pyfcstm-0.2.1-py3-none-any.whl.
File metadata
- Download URL: pyfcstm-0.2.1-py3-none-any.whl
- Upload date:
- Size: 99.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
647468a6a92eed2d257051e125a57051a7a991725e170a2d07edf9974ca2c41d
|
|
| MD5 |
f07ef18e0b2dc03295dbfe3aaf9215d4
|
|
| BLAKE2b-256 |
76ca905317a5780f22b7e3bd20e7ef7c348e5e46d5a872385af5b005dfef3d8f
|