DSL reference
Scope
This is the fact-oriented FCSTM DSL reference. It is checked against the current
split grammar files, especially pyfcstm/dsl/grammar/GrammarParser.g4 and
pyfcstm/dsl/grammar/GrammarLexer.g4. Use it for exact forms and boundaries.
Use Write your first FCSTM DSL model for a learning path,
DSL task guide for task recipes, and
DSL semantics explanation for semantic background.
Syntax quick index
Start here when you need to find the exact form. Examples in this reference are
fragments unless they are introduced as checked files under
docs/source/tutorials/dsl. The task guide links each major form to a complete
example and verification command.
Family |
Main forms |
Details |
|---|---|---|
Program boundary |
|
|
States |
|
|
Transitions |
plain, event, guard, guard+effect, combo, forced, entry, exit |
|
Events |
|
|
Operation blocks |
assignment, block-local temporary, |
|
Expressions |
init, numeric, condition, ternary, operator precedence |
|
Lifecycle and aspects |
|
|
Imports |
|
|
Diagnostics wording |
target-specific warnings, especially C/C++ deployment-profile risks |
Lexical and comment forms
Form |
Syntax / tokens |
Notes |
|---|---|---|
Identifier |
|
Used for variables, states, events, action names, aliases, and path parts. |
String |
Single or double quoted strings |
Import paths and |
Comments |
|
Multiline comments may become abstract-action documentation in specific lifecycle forms. |
Keywords |
|
Keywords are reserved by lexer rules. |
Compact import tokens |
selector patterns and target templates |
Compact forms are tokenized in import-specific lexer modes and are whitespace-sensitive. |
Top-level program forms
A normal DSL entry has zero or more persistent variable declarations followed by one root state:
Fragment:
def int counter = 0;
def float threshold = 3.5;
state Root {
[*] -> Idle;
state Idle;
}
Facts:
Persistent variable types are
intandfloat.Declarations must appear before the single root
state.Initializers use
init_expression. This subset accepts literals, math constants, arithmetic, bitwise operators, and unary math functions, but it does not accept runtime variable references or C-style ternary expressions.The root may be leaf or composite, but practical models usually use composite root state.
Import preamble forms
The import assembly pipeline also parses a preamble entry point:
Rule |
Syntax |
Meaning |
|---|---|---|
|
|
Defines a constant-like preamble value for import assembly. |
|
|
Provides an initial assignment in the import preamble context. |
These forms are not ordinary top-level def declarations. They exist so
imported modules can expose assembly-time constants or initial values before the
model is rewritten into the host.
Minimal parser-helper verification:
from pyfcstm.dsl.parse import parse_preamble
print(parse_preamble("limit = 3;"))
print(parse_preamble("speed := 5;"))
This is a parser-helper fact, not a normal user entry point for complete
.fcstm files. User-facing import examples should still use concrete import
files such as import "./line/main.fcstm" as Line;.
State forms
Form |
Syntax |
Boundary |
|---|---|---|
Leaf state |
|
Stoppable runtime state. |
Named leaf state |
|
Adds display metadata. |
Composite state |
|
Owns child declarations; must choose an initial child. |
Pseudo state |
|
Routing helper; should be leaf-like and action-free for combo relay use. |
Pseudo composite syntax |
|
Parser shape exists, but model validation rejects non-leaf pseudo states with |
Some path-bearing forms use dotted identifiers through chain_id, for
example event scopes, import mappings, or action references. Transition
from_state and to_state endpoints are different: they are plain
identifiers resolved in the owning state scope, not dotted paths. To reach a
nested leaf, put the transition inside the composite that owns that leaf, or
transition to the composite and let its initial transition select the child.
Transition forms
Family |
Syntax shape |
Effect allowed? |
Notes |
|---|---|---|---|
Initial transition |
|
Yes |
Selects initial child for a composite. |
Normal transition |
|
Yes |
Source and target resolve in owner scope. |
Exit transition |
|
Yes |
Leaves through the composite exit marker. |
Event transition |
|
Yes |
Ordinary event form without guard syntax. |
Guard transition |
|
Yes |
Guard expression is condition-only. |
Guard plus effect |
|
Yes |
Event syntax is not part of this ordinary form. |
Combo trigger |
|
Yes for normal/entry combo expansion |
Used for explicit event-plus-guard, guard alias, or multiple-term triggers. |
Forced transition |
|
No |
Expands over selected sources. |
Forced exit transition |
|
No |
Forced form targeting exit marker. |
Combo details:
Local combo uses
::and local event terms.Chain/root combo uses
:andchain_idevent terms.Entry combo triggers are accepted on initial transitions.
: [condition]is the combo guard alias for a single guard trigger;: if [condition]is the ordinary guard spelling.Leading guard combo terms such as
: [enabled] + Startare accepted.Duplicate event terms and constant guards are diagnostics targets.
Combo pseudo relay states are generated routing helpers. They must not be treated as business states or aspect-action execution points.
Forced transition details:
!Stateexpands from a named source state.!*expands from all applicable source states in the owner scope.Forced forms can carry one local, chain/root, or guard trigger.
Forced forms cannot carry combo
+trigger chains.Forced forms cannot have
effectblocks; put side effects on explicit normal transitions if needed.
Combo expansion quasi-spec
Combo transitions represent an ordered trigger chain in one cycle. They are not ordinary event suffixes glued to ordinary guard suffixes; model construction expands them into pseudo relay states.
Author-written example from combo_transitions.fcstm:
Waiting -> Accepted :: Request + [ready > 0] + Confirm effect {
accepted = accepted + 1;
}
Conceptually, that shape expands like this. Real generated names use the
reserved __combo_ prefix plus a hash; the names below are explanatory only
and must not be hand-written into business models.
Waiting -> __combo_waiting_request :: Request;
__combo_waiting_request -> __combo_waiting_ready : if [ready > 0];
__combo_waiting_ready -> Accepted :: Confirm effect {
accepted = accepted + 1;
}
Waiting, Accepted, Retrying, and Booted are authored
business states. Nodes with the __combo_ prefix are generated pseudo
relay states. The authored business intent from Waiting to Accepted
becomes three hops: consume Request, test ready > 0, then consume
Confirm and run the original effect.
Authored trigger term |
Expanded edge |
Assertion |
|---|---|---|
|
|
The first hop consumes the event and does not run the original effect. |
|
|
The guard term becomes a relay-edge guard; if it fails, the chain does not advance. |
|
|
The final hop reaches the target state and is the only hop carrying the original effect. |
Pseudo relay state |
|
The node must remain pure routing: pseudo, action-free, and not an aspect execution point. |
Provenance fields |
|
Diagnostics can point back to authored trigger terms instead of only to generated nodes. |
Verify with:
pyfcstm inspect -i docs/source/tutorials/dsl/combo_transitions.fcstm --format json
Expected facts:
statescontainsis_pseudo=truestates whose names start with__combo_;combo_origins[].termsrecords the authoredRequest,[ready > 0], andConfirmterms with source spans;prefix
combo_transitionsentries have emptyeffect; the final hop hasaccepted = accepted + 1;;the generated relay names do not appear in authored source and should not be depended on.
Common error:
// Bad ordinary syntax: an event suffix followed by an ordinary guard suffix.
Waiting -> Accepted :: Request if [ready > 0];
// Good combo syntax: use a guard term in the combo trigger.
Waiting -> Accepted :: Request + [ready > 0];
Forced expansion quasi-spec
Forced transitions expand over a source set. They are not combo chains. One declaration is copied into multiple ordinary transitions, which is useful for modeling many states reacting to the same emergency event or guard.
Author-written example from forced_transitions.fcstm:
!* -> ErrorHandler :: CriticalError;
!Running -> SafeMode :: EmergencyStop;
System directly owns Initializing, Running, SafeMode, and
ErrorHandler. !* expands in that owner scope; !Running also
contributes exits from the Running boundary and related child paths. The
inspected model therefore contains more ordinary edges than the two authored
forced declarations.
Authored form |
Expansion meaning |
Inspect assertion |
|---|---|---|
|
Expand from all applicable sources in the current owner scope. |
|
|
Expand from the |
Expanded edges keep |
No effect block |
Forced declarations cannot write |
Expanded edges have empty |
No combo chain |
Forced declarations cannot use |
Use explicit normal combo transitions when a source needs ordered trigger terms. |
Verify with:
pyfcstm inspect -i docs/source/tutorials/dsl/forced_transitions.fcstm --format json
The JSON report should contain a forced_transitions summary and multiple
expanded edges with forced_origin. Once expanded, those edges are ordinary
transitions for lifecycle ordering: source exit and target enter still
run according to runtime semantics.
Events and scopes
Form |
Syntax |
Meaning |
|---|---|---|
Event declaration |
|
Declares an event owned by the containing state. |
Source-local event |
|
Event in the source state’s local namespace. |
Chain event |
|
Event path relative to an owning scope. |
Root event |
|
Absolute event path from root. |
chain_id is / optional followed by one or more dotted identifiers. Use
local events for source-private signals, chain paths for containing protocols,
and root paths for globally owned events.
Operation blocks
Operation blocks appear in effects and lifecycle bodies.
Statement |
Syntax |
Notes |
|---|---|---|
Assignment |
|
Updates a persistent variable or introduces a block-local temporary. |
Conditional block |
|
Conditions use |
Empty statement |
|
Accepted as a no-op statement. |
A block-local temporary is local to the current operation block and can only be
read after assignment in that block. Persistent variables must be declared in the
top-level def list.
Expression reference
Category |
Forms |
Notes |
|---|---|---|
Literals |
decimal integer, hexadecimal integer, float |
Float tokens support decimal/exponent forms. |
Constants |
|
Math constants for initializers and numeric expressions. |
Variables |
|
Runtime numeric variable or block-local temporary. |
Unary |
|
Prefix numeric sign. |
Power |
|
Right associative. |
Multiplicative |
|
Numeric arithmetic. |
Additive |
|
Numeric arithmetic. |
Shift / bitwise |
|
Numeric bitwise operators; target warnings may apply for C/C++ profiles. |
Function call |
|
Unary math functions only. |
C-style ternary |
|
Condition must be parenthesized before |
Category |
Forms |
Notes |
|---|---|---|
Boolean literals |
|
Lexer accepts common case variants. |
Not |
|
Prefix condition negation. |
Numeric comparison |
|
Bridges numeric expressions into conditions. |
Condition equality |
|
Condition-level equality and equivalence. |
Boolean composition |
|
Do not use |
Implication |
|
Right associative; do not use |
C-style ternary |
|
Condition result ternary. |
Operator precedence follows the grammar rule order from tight to loose:
parentheses/literals/functions, unary signs, power, multiplicative, additive,
shift, bitwise & / ^ / |, comparisons, condition equality/iff,
and, xor, or, implication, and ternary forms.
Lifecycle forms
Stage |
Concrete |
Named concrete |
Abstract |
Doc-comment abstract |
Ref |
|---|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
A ref points to a named lifecycle action path, not to a state or event.
Doc-comment abstract forms use the multiline comment as documentation metadata;
the optional Name shown above is prose notation, not a literal Name?
token.
Aspect forms
Aspect actions are written with >> during before or >> during after.
They support the same concrete, named, abstract, doc-comment abstract, and ref
families as lifecycle during before/after forms.
Form |
Example shape |
Boundary |
|---|---|---|
Concrete aspect |
|
Runs for descendant leaf-state active cycles. |
Named aspect |
|
Gives generated hooks a stable name. |
Abstract aspect |
|
Generated code calls user-provided behavior. |
Ref aspect |
|
Reuses a named action. |
Combo pseudo relay |
N/A |
Aspect actions do not execute inside combo pseudo relay chains. |
Import forms
Form |
Syntax |
Notes |
|---|---|---|
Basic import |
|
Adds imported root as child |
Named import |
|
Adds display metadata. |
Import block |
|
Contains mapping statements. |
Def fallback selector |
|
Fallback variable mapping. |
Def set selector |
|
Maps a set of variables. |
Def pattern selector |
|
Pattern selector is compact and whitespace-sensitive; |
Def exact selector |
|
Maps one variable. |
Target template |
|
|
Event mapping |
|
May include |
Directory entry |
|
Use an explicit file; bare directory import is unsupported. |
File resolution, recursive loading, conflict detection, mapping precedence, and model assembly are implemented after parsing in Python import/model code.
Diagnostics and target-risk wording
Diagnostics come from syntax parsing, model validation, inspect analyzers, and optional verification phases. User-facing DSL docs must preserve the target scope of each diagnostic.
Area |
Codes / source |
Wording rule |
|---|---|---|
Combo expansion |
|
Explain pseudo relay purity and name-extension behavior without implying aspects run inside relays. |
Pseudo state shape |
|
Parser shape is not the same as model validity. |
Numeric literal / operation risk |
|
Describe as C/C++ deployment-profile risk for |
Python generated runtime |
No generic carry-over from C/C++ warnings |
Do not claim Python generated code has the same fixed-width or undefined-behavior risk unless a Python-specific diagnostic says so. |
Use Diagnostics code reference for code-level wording.
Coverage appendix
The next matrix is for maintainers and reviewers. It is intentionally placed after the user-facing syntax facts so normal readers can look up DSL forms without first crossing a migration audit table.
DSL coverage matrix
N/A means that the page type intentionally does not own that leaf ability.
Every row still has a reference or explanation landing point.
feature_id |
Family |
Fact source |
Tutorial coverage |
How-to coverage |
Reference coverage |
Explanation coverage |
Example / verification |
EN/ZH |
|---|---|---|---|---|---|---|---|---|
|
lexical |
|
N/A: tutorial hides token table |
N/A: task pages use snippets |
N/A: syntax token facts |
reference table review |
synced |
|
|
top-level |
|
|
synced |
||||
|
top-level |
|
|
synced |
||||
|
import |
|
N/A: tutorial skips imports |
|
synced |
|||
|
state |
|
|
synced |
||||
|
state |
|
N/A: tutorial links advanced routing |
|
synced |
|||
|
state |
model state lookup / transition ownership |
scope snippets / model validation |
synced |
||||
|
transition |
|
|
synced |
||||
|
transition |
|
Write guards, effects, and operation blocks / Write event scopes |
|
synced |
|||
|
transition |
|
|
synced |
||||
|
transition |
|
N/A: tutorial links advanced transition |
|
synced |
|||
|
transition |
|
N/A: tutorial links advanced transition |
|
synced |
|||
|
event |
|
|
synced |
||||
|
operation |
|
|
synced |
||||
|
operation |
|
N/A: tutorial keeps blocks small |
|
synced |
|||
|
expression |
|
top-level initializer snippets |
synced |
||||
|
expression |
|
|
synced |
||||
|
expression |
|
|
synced |
||||
|
expression |
|
N/A: tutorial keeps arithmetic simple |
|
synced |
|||
|
lifecycle |
|
|
synced |
||||
|
lifecycle |
named / |
N/A: tutorial links advanced hooks |
|
synced |
|||
|
aspect |
|
N/A: tutorial only links |
|
synced |
|||
|
import |
|
N/A: tutorial skips imports |
|
synced |
|||
|
import |
|
N/A: tutorial skips imports |
|
synced |
|||
|
import |
import path resolution in |
N/A: tutorial skips imports |
|
synced |
|||
|
diagnostics |
|
risk wording line audit |
synced |
Fact-check notes
Grammar facts come from
GrammarParser.g4andGrammarLexer.g4.AST shape and export details come from
pyfcstm/dsl/node.pyandpyfcstm/dsl/listener.py.Import assembly facts come from
pyfcstm/model/imports.py.Target-risk diagnostics come from
pyfcstm/diagnostics/codes.yamlandpyfcstm/diagnostics/analyzers/.LLM-facing syntax guidance is in
pyfcstm/llm/fcstm_grammar_guide.md. This page does not modify that packaged guide.