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.

Syntax families

Family

Main forms

Details

Program boundary

def int x = 0; / one root state

Top-level program forms

States

state A; / state A { ... } / pseudo state P;

State forms

Transitions

plain, event, guard, guard+effect, combo, forced, entry, exit

Transition forms

Events

:: Local / : Chain / : /RootEvent

Events and scopes

Operation blocks

assignment, block-local temporary, if / else if / else, empty statement

Operation blocks

Expressions

init, numeric, condition, ternary, operator precedence

Expression reference

Lifecycle and aspects

enter / during / exit; abstract; ref; >> during

Lifecycle forms, Aspect forms

Imports

import "path" as Alias and mapping block

Import forms

Diagnostics wording

target-specific warnings, especially C/C++ deployment-profile risks

Diagnostics and target-risk wording

Lexical and comment forms

Lexical forms

Form

Syntax / tokens

Notes

Identifier

[a-zA-Z_][a-zA-Z0-9_]*

Used for variables, states, events, action names, aliases, and path parts.

String

Single or double quoted strings

Import paths and named labels use strings; common escape sequences are lexed.

Comments

/* ... */, // ..., # ...

Multiline comments may become abstract-action documentation in specific lifecycle forms.

Keywords

def, state, pseudo, event, import, enter, during, exit, abstract, ref

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 int and float.

  • 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:

Preamble forms

Rule

Syntax

Meaning

constant_definition

name = init_expression;

Defines a constant-like preamble value for import assembly.

initial_assignment

name := init_expression;

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

State forms

Form

Syntax

Boundary

Leaf state

state Name;

Stoppable runtime state.

Named leaf state

state Name named "Label";

Adds display metadata.

Composite state

state Name { ... }

Owns child declarations; must choose an initial child.

Pseudo state

pseudo state Name;

Routing helper; should be leaf-like and action-free for combo relay use.

Pseudo composite syntax

pseudo state Name { ... }

Parser shape exists, but model validation rejects non-leaf pseudo states with E_PSEUDO_NOT_LEAF.

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

Transition families

Family

Syntax shape

Effect allowed?

Notes

Initial transition

[*] -> Target; or with entry combo trigger

Yes

Selects initial child for a composite.

Normal transition

Source -> Target;

Yes

Source and target resolve in owner scope.

Exit transition

Source -> [*];

Yes

Leaves through the composite exit marker.

Event transition

Source -> Target :: Local; or : EventPath

Yes

Ordinary event form without guard syntax.

Guard transition

Source -> Target : if [condition];

Yes

Guard expression is condition-only.

Guard plus effect

Source -> Target : if [condition] effect { ... }

Yes

Event syntax is not part of this ordinary form.

Combo trigger

[guard] alias or Event + [guard] terms through combo rules

Yes for normal/entry combo expansion

Used for explicit event-plus-guard, guard alias, or multiple-term triggers.

Forced transition

!State -> Target ...; or !* -> Target ...;

No

Expands over selected sources.

Forced exit transition

!State -> [*] ...; or !* -> [*] ...;

No

Forced form targeting exit marker.

Combo details:

  • Local combo uses :: and local event terms.

  • Chain/root combo uses : and chain_id event 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] + Start are 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:

  • !State expands 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 effect blocks; 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;
}
Combo transition expansion state diagram

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.

Combo expansion assertions

Authored trigger term

Expanded edge

Assertion

Request

Waiting -> __combo_* :: Request

The first hop consumes the event and does not run the original effect.

[ready > 0]

__combo_* -> __combo_* : if [ready > 0]

The guard term becomes a relay-edge guard; if it fails, the chain does not advance.

Confirm

__combo_* -> Accepted :: Confirm effect { ... }

The final hop reaches the target state and is the only hop carrying the original effect.

Pseudo relay state

state.name starts with __combo_

The node must remain pure routing: pseudo, action-free, and not an aspect execution point.

Provenance fields

combo_origins / combo_transitions

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:

  • states contains is_pseudo=true states whose names start with __combo_;

  • combo_origins[].terms records the authored Request, [ready > 0], and Confirm terms with source spans;

  • prefix combo_transitions entries have empty effect; the final hop has accepted = 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;
Forced transition expansion state diagram

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.

Forced expansion assertions

Authored form

Expansion meaning

Inspect assertion

!* -> ErrorHandler :: CriticalError;

Expand from all applicable sources in the current owner scope.

forced_transitions[].from_path is * and expansion_count gives the number of generated edges.

!Running -> SafeMode :: EmergencyStop;

Expand from the Running boundary and related child exits.

Expanded edges keep forced_origin so diagnostics and diagrams can trace back to the declaration.

No effect block

Forced declarations cannot write effect.

Expanded edges have empty effect; shared behavior belongs in target enter or explicit normal edges.

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

Event scope forms

Form

Syntax

Meaning

Event declaration

event Name; or event Name named "Label";

Declares an event owned by the containing state.

Source-local event

:: Name

Event in the source state’s local namespace.

Chain event

: Name or : Parent.Event

Event path relative to an owning scope.

Root event

: /Name or : /Path.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.

Operation statements

Statement

Syntax

Notes

Assignment

name = num_expression;

Updates a persistent variable or introduces a block-local temporary.

Conditional block

if [condition] { ... } else if [condition] { ... } else { ... }

Conditions use cond_expression.

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

Numeric expression facts

Category

Forms

Notes

Literals

decimal integer, hexadecimal integer, float

Float tokens support decimal/exponent forms.

Constants

pi, E, tau

Math constants for initializers and numeric expressions.

Variables

ID

Runtime numeric variable or block-local temporary.

Unary

+x, -x

Prefix numeric sign.

Power

x ** y

Right associative.

Multiplicative

*, /, %

Numeric arithmetic.

Additive

+, -

Numeric arithmetic.

Shift / bitwise

<<, >>, &, ^, |

Numeric bitwise operators; target warnings may apply for C/C++ profiles.

Function call

sin(x), sqrt(x), abs(x), sign(x), and lexer-listed math functions

Unary math functions only.

C-style ternary

(cond) ? num_expr : num_expr

Condition must be parenthesized before ?.

Condition expression facts

Category

Forms

Notes

Boolean literals

true / false variants

Lexer accepts common case variants.

Not

!cond or not cond

Prefix condition negation.

Numeric comparison

<, >, <=, >=, ==, !=

Bridges numeric expressions into conditions.

Condition equality

cond == cond, cond != cond, cond iff cond

Condition-level equality and equivalence.

Boolean composition

&& / and, || / or, xor

Do not use ^ for boolean xor; ^ is numeric bitwise xor.

Implication

=> or implies

Right associative; do not use -> as implication.

C-style ternary

(cond) ? cond : cond

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

Lifecycle action forms

Stage

Concrete

Named concrete

Abstract

Doc-comment abstract

Ref

enter

enter { ... }

enter Name { ... }

enter abstract Name;

enter abstract Name /* doc */ or enter abstract /* doc */

enter ref Path; or enter Name ref Path;

during

during { ... }

during Name { ... }

during abstract Name;

during abstract Name /* doc */ or during abstract /* doc */

during ref Path; or during Name ref Path;

during before

during before { ... }

during before Name { ... }

during before abstract Name;

during before abstract Name /* doc */ or during before abstract /* doc */

during before ref Path; or during before Name ref Path;

during after

during after { ... }

during after Name { ... }

during after abstract Name;

during after abstract Name /* doc */ or during after abstract /* doc */

during after ref Path; or during after Name ref Path;

exit

exit { ... }

exit Name { ... }

exit abstract Name;

exit abstract Name /* doc */ or exit abstract /* doc */

exit ref Path; or exit Name ref Path;

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.

Aspect facts

Form

Example shape

Boundary

Concrete aspect

>> during before { ... }

Runs for descendant leaf-state active cycles.

Named aspect

>> during after Trace { ... }

Gives generated hooks a stable name.

Abstract aspect

>> during before abstract Trace;

Generated code calls user-provided behavior.

Ref aspect

>> during after ref Path;

Reuses a named action.

Combo pseudo relay

N/A

Aspect actions do not execute inside combo pseudo relay chains.

Import forms

Import syntax facts

Form

Syntax

Notes

Basic import

import "file.fcstm" as Alias;

Adds imported root as child Alias.

Named import

import "file.fcstm" as Alias named "Label";

Adds display metadata.

Import block

import "file.fcstm" as Alias { ... }

Contains mapping statements.

Def fallback selector

def * -> target;

Fallback variable mapping.

Def set selector

def {a, b} -> target;

Maps a set of variables.

Def pattern selector

def sensor_* -> sensor_$1;

Pattern selector is compact and whitespace-sensitive; $1 is the first wildcard capture.

Def exact selector

def value -> renamed;

Maps one variable.

Target template

ID, compact template, or *

$0 / ${0} mean the full imported name; $1 / ${1} mean the first wildcard capture. Bare * keeps the imported name.

Event mapping

event Source.Path -> Target.Path;

May include named "Label".

Directory entry

import "./dir/main.fcstm" as Subsystem;

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.

Diagnostics wording facts

Area

Codes / source

Wording rule

Combo expansion

W_COMBO_*, I_COMBO_PSEUDO_NAME_EXTENDED, E_COMBO_PSEUDO_NAME_COLLISION

Explain pseudo relay purity and name-extension behavior without implying aspects run inside relays.

Pseudo state shape

E_PSEUDO_NOT_LEAF

Parser shape is not the same as model validity.

Numeric literal / operation risk

W_NUMERIC_* and numeric analyzer

Describe as C/C++ deployment-profile risk for c, c_poll, cpp, and cpp_poll unless another target has its own evidence.

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.

DSL capability coverage

feature_id

Family

Fact source

Tutorial coverage

How-to coverage

Reference coverage

Explanation coverage

Example / verification

EN/ZH

dsl-lexical-comments

lexical

GrammarLexer.g4 comments / strings / IDs

N/A: tutorial hides token table

N/A: task pages use snippets

Lexical and comment forms

N/A: syntax token facts

reference table review

synced

dsl-top-level-root

top-level

state_machine_dsl / root state_definition

Write your first FCSTM DSL model

Write a small valid model

Top-level program forms

Why variables come before one root state

first_thermostat.fcstm inspect

synced

dsl-top-level-def

top-level

def_assignment / init_expression

Write your first FCSTM DSL model

Use expressions safely

Top-level program forms

Guard, effect, and expression separation

first_thermostat.fcstm inspect

synced

dsl-import-preamble

import

preamble_program / constant_definition / initial_assignment

N/A: tutorial skips imports

Assemble imports

Import preamble forms

Import assembly semantics

parse_preamble("limit = 3;") / parser-helper only

synced

dsl-state-leaf-composite

state

state_definition leaf/composite branches

Write your first FCSTM DSL model

Organize states and resolve targets

State forms

Ownership tree and name resolution

first_thermostat.fcstm inspect

synced

dsl-state-pseudo

state

PSEUDO STATE / E_PSEUDO_NOT_LEAF

N/A: tutorial links advanced routing

Organize states and resolve targets

State forms

Pseudo and combo relay semantics

pseudo_state_demo.fcstm inspect

synced

dsl-state-target-resolution

state

model state lookup / transition ownership

Write your first FCSTM DSL model

Organize states and resolve targets

State forms

Ownership tree and name resolution

scope snippets / model validation

synced

dsl-transition-initial

transition

entryTransitionDefinition

Write your first FCSTM DSL model

Write a small valid model

Transition forms

Composite entry and initial transitions

first_thermostat.fcstm inspect

synced

dsl-transition-plain-event

transition

normalTransitionDefinition / event terms

Write your first FCSTM DSL model

Write guards, effects, and operation blocks / Write event scopes

Transition forms

Event scopes as ownership signals

event_scoping_complete.fcstm inspect

synced

dsl-transition-guard-effect

transition

COLON IF / EFFECT operation block

Write your first FCSTM DSL model

Write guards, effects, and operation blocks

Transition forms

Guard, effect, and expression separation

operation_blocks_complete.fcstm inspect

synced

dsl-transition-combo

transition

combo_transition_trigger / entry_combo_transition_trigger

N/A: tutorial links advanced transition

Write combo transitions

Transition forms / Combo expansion quasi-spec

Pseudo and combo relay semantics

combo_transitions.fcstm inspect

synced

dsl-transition-forced

transition

transition_force_definition

N/A: tutorial links advanced transition

Write forced transitions

Transition forms / Forced expansion quasi-spec

Forced transition expansion

forced_transitions.fcstm inspect

synced

dsl-event-scopes

event

event_definition / chain_id

Write your first FCSTM DSL model

Write event scopes

Events and scopes

Event scopes as ownership signals

event_scoping_complete.fcstm inspect

synced

dsl-operation-assignment-temp

operation

operation_assignment / local temp tracking

Write your first FCSTM DSL model

Write guards, effects, and operation blocks

Operation blocks

Guard, effect, and expression separation

operation_blocks_complete.fcstm inspect

synced

dsl-operation-conditionals

operation

if_statement / empty statement

N/A: tutorial keeps blocks small

Write guards, effects, and operation blocks

Operation blocks

Guard, effect, and expression separation

operation_blocks_complete.fcstm inspect

synced

dsl-expression-init

expression

init_expression

Write your first FCSTM DSL model

Use expressions safely

Expression reference

Guard, effect, and expression separation

top-level initializer snippets

synced

dsl-expression-runtime

expression

num_expression / math functions / bitwise

Write your first FCSTM DSL model

Use expressions safely

Expression reference

Guard, effect, and expression separation

expression_condition_ternary.fcstm inspect

synced

dsl-expression-condition

expression

cond_expression / comparison / boolean ops

Write your first FCSTM DSL model

Use expressions safely

Expression reference

Guard, effect, and expression separation

expression_condition_ternary.fcstm inspect

synced

dsl-expression-ternary

expression

conditionalCStyleExprNum / conditionalCStyleCondNum

N/A: tutorial keeps arithmetic simple

Use expressions safely

Expression reference

Guard, effect, and expression separation

expression_condition_ternary.fcstm inspect

synced

dsl-lifecycle-concrete

lifecycle

enter / during / exit operation forms

Write your first FCSTM DSL model

Write lifecycle hooks, refs, and abstract hooks

Lifecycle forms

Lifecycle, abstract hooks, and refs

first_thermostat.fcstm inspect

synced

dsl-lifecycle-named-abstract-ref

lifecycle

named / abstract / doc-comment / ref branches

N/A: tutorial links advanced hooks

Write lifecycle hooks, refs, and abstract hooks

Lifecycle forms

Lifecycle, abstract hooks, and refs

abstract_reference_demo.fcstm inspect

synced

dsl-aspect-forms

aspect

during_aspect_definition

N/A: tutorial only links

Use during aspects

Aspect forms

During before/after and aspects

hierarchy_execution.fcstm inspect

synced

dsl-import-basic-alias

import

import_statement header

N/A: tutorial skips imports

Assemble imports

Import forms

Import assembly semantics

import_host_basic.fcstm inspect

synced

dsl-import-mapping

import

def_mapping_statement / event_mapping_statement

N/A: tutorial skips imports

Assemble imports

Import forms

Import assembly semantics

import_host_mapped.fcstm inspect

synced

dsl-import-directory-boundary

import

import path resolution in model/imports.py

N/A: tutorial skips imports

Assemble imports

Import forms

Import assembly semantics

import_host_directory.fcstm inspect

synced

dsl-diagnostics-target-risk

diagnostics

pyfcstm/diagnostics/codes.yaml / analyzers

Write your first FCSTM DSL model

Diagnose and repair DSL errors

Diagnostics and target-risk wording

Guard, effect, and expression separation

risk wording line audit

synced

Fact-check notes

  • Grammar facts come from GrammarParser.g4 and GrammarLexer.g4.

  • AST shape and export details come from pyfcstm/dsl/node.py and pyfcstm/dsl/listener.py.

  • Import assembly facts come from pyfcstm/model/imports.py.

  • Target-risk diagnostics come from pyfcstm/diagnostics/codes.yaml and pyfcstm/diagnostics/analyzers/.

  • LLM-facing syntax guidance is in pyfcstm/llm/fcstm_grammar_guide.md. This page does not modify that packaged guide.