Project Structure Guide
Overview
PyFCSTM is a Python framework for parsing Finite State Machine (FSM) Domain-Specific Language (DSL) and generating executable code in multiple target languages. The framework implements a complete pipeline from DSL text to executable code through a three-stage architecture: DSL Parsing → State Machine Modeling → Code Generation.
The project follows a layered modular design that separates concerns and enables extensibility for new target languages and template formats.
Architecture Layers
The framework is organized into four distinct architectural layers:
- Foundation Layer (
pyfcstm.utils,pyfcstm.config) Provides cross-cutting utilities and project metadata used by all other layers.
- Core Layer (
pyfcstm.dsl,pyfcstm.model) Implements DSL parsing and state machine modeling - the heart of the framework.
- Application Layer (
pyfcstm.render,pyfcstm.entry) Provides code generation engine and command-line interface for end users.
- Extension Layer (
pyfcstm.template) Reserved for custom template extensions and future expansion.
Module Reference
Foundation Layer
- pyfcstm.config - Project Metadata
meta.py: Package metadata (version, author, description, title)Provides
__VERSION__,__TITLE__,__AUTHOR__,__DESCRIPTION__Used by
setup.pyfor package distribution
- pyfcstm.utils - Utility Functions
validate.py: Validation framework withIValidatablebase class andModelValidationErrortext.py: String normalization (normalize(),to_identifier()) for identifier generationsafe.py: Safe sequence identifier generation (sequence_safe())doc.py: Multiline comment formatting (format_multiline_comment())binary.py: Binary file detection utilitiesdecode.py: Auto-decoding withauto_decode()for various encodingsjinja2.py: Jinja2 environment utilities (add_builtins_to_env(),add_settings_for_env())json.py: JSON operation interface withIJsonOpfor serialization
Core Layer
- pyfcstm.dsl - DSL Parsing Pipeline
Converts DSL text into Abstract Syntax Tree (AST) nodes using ANTLR4.
parse.py: Entry point withparse_with_grammar_entry()for parsing DSL code stringslistener.py: ANTLR listener (GrammarParseListener) that walks parse tree and constructs AST nodesnode.py: AST node definitions (dataclasses) for states, transitions, operations, expressionserror.py: DSL parsing error handling (GrammarParseError,SyntaxFailError)grammar/Grammar.g4: ANTLR4 grammar defining lexer and parser rules for FSM DSL syntaxgrammar/GrammarLexer.py,grammar/GrammarParser.py,grammar/GrammarListener.py: Auto-generated ANTLR4 code
- Key Concepts:
Supports hierarchical state definitions with nested composite states
Expression grammar includes numeric operations, bitwise operations, conditionals, function calls
Each AST node has methods for exporting back to DSL or PlantUML format
- pyfcstm.model - State Machine Modeling
Converts AST nodes into structured, queryable state machine models.
model.py: Core state machine model classesStateMachine: Root container with variables, states, and global eventsState: Represents states with parent/child relationships, lifecycle actions (enter/during/exit), transitionsTransition: State transitions with source, target, event, guard conditions, effectsEvent: Named events with scoping (local::vs global:or/)Operation: Variable assignments executed during lifecycle actions or transition effectsVarDefine: Variable definitions with type (int/float) and initial valuesOnStage/OnAspect: Lifecycle action containers for enter/during/exit behaviors
expr.py: Expression system for variables, conditions, and effectsSupports literals, variables, unary/binary operators, bitwise operations, function calls
Conditional expressions with guards for transitions
Expression tree structure renderable to different target languages
base.py: Base classesAstExportableandPlantUMLExportablefor model components
- Key Methods:
walk_states(): Traverse state hierarchyfind_state(): Lookup states by nameExport capabilities for DSL and PlantUML formats
Application Layer
- pyfcstm.render - Code Generation Engine
Transforms state machine models into target code using Jinja2 templates.
render.py: MainStateMachineCodeRendererclassLoads template directory and
config.yamlconfigurationProcesses
.j2Jinja2 templates with state machine model as contextCopies static files directly to output directory
Supports file ignoring via gitignore-style patterns
env.py: Jinja2 environment setup and configurationCreates sandboxed Jinja2 environment with custom globals, filters, tests
Configures template loader and rendering options
expr.py: Expression rendering for different target languagescreate_expr_render_template(): Creates language-specific expression renderersSupports multiple expression styles:
dsl,c,cpp,pythonConverts DSL expressions to target language syntax (e.g.,
&&toandfor Python)Available as
expr_renderfilter in templates:{{ expr | expr_render(style='c') }}
func.py: Custom Jinja2 filters and functionsprocess_item_to_object(): Converts config items to Python objects (imports, templates, values)Supports importing external Python functions into template context
- Template System:
Template directories must contain
config.yamldefiningexpr_styles,globals,filters,ignores.j2files have access to state machine model viamodelvariableStatic files are copied directly preserving directory structure
- pyfcstm.entry - Command-Line Interface
Provides user-facing commands for DSL processing.
cli.py: CLI implementation using Click frameworkMain entry point
pyfcstmcli()registered as console scriptSubcommand aggregation and argument parsing
dispatch.py: Command dispatching logic and version informationplantuml.py: PlantUML diagram generation from state machine modelsConverts DSL to
.pumlformat for visualization
generate.py: Template-based code generationOrchestrates parsing DSL, building model, and rendering with templates
base.py: CLI base functionality and exception handling
Extension Layer
- pyfcstm.template - Template Extensions
Reserved module for custom template extensions and future expansion.
Architecture Diagram
The following diagram illustrates the module relationships and data flow through the framework:
PyFCSTM layered architecture showing module dependencies and data flow
Processing Pipeline
The framework processes FSM definitions through a three-stage pipeline:
- Stage 1: DSL Parsing (
pyfcstm.dsl) User provides DSL code as text (from
.fcstmfiles)ANTLR4 parser tokenizes and parses according to
Grammar.g4rulesGrammarParseListenerwalks parse tree and constructs AST nodesOutput: AST node tree (
node.pydataclasses)
- Stage 2: Model Construction (
pyfcstm.model) AST nodes are converted to state machine model objects
Hierarchical state relationships are established (parent/child)
Transitions, events, and expressions are linked to states
Model validation ensures structural integrity
Output: Queryable
StateMachineobject withState,Transition,Eventinstances
- Stage 3: Code Generation (
pyfcstm.render) Template directory is loaded with
config.yamlconfigurationJinja2 environment is configured with custom filters and expression renderers
Templates receive
modelobject as contextExpression rendering converts DSL expressions to target language syntax
Output: Generated code files in target language
User Interaction Flow
Users interact with the framework through CLI commands (pyfcstm.entry):
PlantUML Generation:
pyfcstm plantuml -i input.fcstm -o output.puml
Flow: DSL → Parser → Model → PlantUML Exporter → .puml file
Code Generation:
pyfcstm generate -i input.fcstm -t template_dir/ -o output_dir/
Flow: DSL → Parser → Model → Template Renderer → Generated code files
Dependency Relationships
The layered architecture enforces clear dependency rules:
Foundation Layer has no dependencies on other layers
Core Layer depends only on Foundation Layer
Application Layer depends on Core and Foundation Layers
Extension Layer depends on Application, Core, and Foundation Layers
This design ensures:
Modularity: Each layer has well-defined responsibilities
Testability: Lower layers can be tested independently
Extensibility: New target languages can be added via templates without modifying core logic
Maintainability: Changes in upper layers don’t affect lower layers
Key Design Patterns
- Visitor Pattern (
pyfcstm.dsl.listener) ANTLR listener walks parse tree and constructs AST nodes
- Template Method Pattern (
pyfcstm.model.base) Base classes define export interfaces implemented by model classes
- Strategy Pattern (
pyfcstm.render.expr) Expression rendering strategies for different target languages
- Facade Pattern (
pyfcstm.entry) CLI provides simplified interface to complex parsing and rendering subsystems