"""
State Machine DSL Abstract Syntax Tree (AST) Nodes.
This module defines the Abstract Syntax Tree (AST) building blocks used by the
State Machine Domain Specific Language (DSL). The classes provide a structured
representation of DSL constructs such as states, transitions, expressions,
and executable actions. Instances of these classes are typically produced by
a parser and can be serialized back to DSL syntax via ``str()``.
The main public components include:
* :class:`ASTNode` - Root base class for all AST nodes.
* :class:`Expr` and :class:`Literal` families - Expression nodes and literals.
* :class:`StateDefinition` - State declarations with nested structure.
* :class:`TransitionDefinition` and :class:`ForceTransitionDefinition` - State transitions.
* Import-related nodes such as :class:`ImportStatement`,
:class:`ImportDefMapping`, and :class:`ImportEventMapping`.
* :class:`StateMachineDSLProgram` - Root program container.
* Statement and action blocks such as :class:`OperationAssignment`,
:class:`EnterStatement`, :class:`ExitStatement`, and :class:`DuringStatement`.
.. note::
The AST classes are data-only and focus on structure. There is no evaluation
or execution logic in this module.
Example::
>>> from pyfcstm.dsl.node import (
... StateDefinition, TransitionDefinition, ChainID,
... StateMachineDSLProgram, DefAssignment, Integer
... )
>>> def_var = DefAssignment("counter", "int", Integer("0"))
>>> trans = TransitionDefinition("idle", "active", ChainID(["idle", "start"]), None, [])
>>> root = StateDefinition("idle", transitions=[trans])
>>> program = StateMachineDSLProgram([def_var], root)
>>> print(program)
def int counter = 0;
state idle {
idle -> active :: start;
}
"""
import io
import json
import math
import os
from abc import ABC
from dataclasses import dataclass, field
from textwrap import indent
from typing import List, Union, Optional, Any
from hbutils.design import SingletonMark
from ..utils.validate import Span
__all__ = [
"ASTNode",
"Identifier",
"ChainID",
"Expr",
"Literal",
"Boolean",
"Integer",
"HexInt",
"Float",
"Constant",
"Name",
"Paren",
"UnaryOp",
"BinaryOp",
"ConditionalOp",
"UFunc",
"Statement",
"ConstantDefinition",
"InitialAssignment",
"DefAssignment",
"ImportMappingStatement",
"ImportDefSelector",
"ImportDefExactSelector",
"ImportDefSetSelector",
"ImportDefPatternSelector",
"ImportDefFallbackSelector",
"ImportDefTargetTemplate",
"ImportDefMapping",
"ImportEventMapping",
"ImportStatement",
"OperationalDeprecatedAssignment",
"Preamble",
"Operation",
"Condition",
"TransitionDefinition",
"ForceTransitionDefinition",
"StateDefinition",
"OperationalStatement",
"OperationAssignment",
"OperationIfBranch",
"OperationIf",
"EventDefinition",
"StateMachineDSLProgram",
"INIT_STATE",
"EXIT_STATE",
"ALL",
"EnterStatement",
"EnterOperations",
"EnterAbstractFunction",
"EnterRefFunction",
"ExitStatement",
"ExitOperations",
"ExitAbstractFunction",
"ExitRefFunction",
"DuringStatement",
"DuringOperations",
"DuringAbstractFunction",
"DuringRefFunction",
"DuringAspectStatement",
"DuringAspectOperations",
"DuringAspectAbstractFunction",
"DuringAspectRefFunction",
]
[docs]
@dataclass
class ASTNode(ABC):
"""
Abstract base class for all AST nodes in the state machine DSL.
This class provides a common ancestor for all nodes in the Abstract Syntax Tree,
making it convenient to type-check and traverse mixed node collections.
:rtype: ASTNode
"""
pass
[docs]
@dataclass
class Identifier(ASTNode):
"""
Abstract base class for identifiers in the state machine DSL.
Identifiers are used to reference variables, states, and other named elements
in the state machine definition.
:rtype: Identifier
"""
pass
[docs]
@dataclass
class ChainID(Identifier):
"""
Represents a chained identifier (e.g., ``a.b.c``) in the state machine DSL.
:param path: List of string components that make up the chained identifier
:type path: List[str]
:param is_absolute: Whether the identifier is absolute (starts with ``/``)
:type is_absolute: bool
:rtype: ChainID
Example::
>>> chain_id = ChainID(['state1', 'event'])
>>> str(chain_id)
'state1.event'
>>> abs_id = ChainID(['root', 'event'], is_absolute=True)
>>> str(abs_id)
'/root.event'
"""
path: List[str]
is_absolute: bool = False
[docs]
def __str__(self) -> str:
"""
Convert the :class:`ChainID` to its string representation.
:return: String representation of the chained identifier
:rtype: str
"""
pth = ".".join(self.path)
if self.is_absolute:
pth = f"/{pth}"
return pth
[docs]
@dataclass
class Expr(ASTNode):
"""
Abstract base class for expressions in the state machine DSL.
Expressions represent computations that produce values, which can be used in
conditions, assignments, and other contexts within the state machine.
:rtype: Expr
"""
pass
[docs]
@dataclass
class Literal(Expr):
"""
Base class for literal values in expressions.
Literal values are constants directly expressed in the code, such as numbers,
booleans, or predefined constants.
:param raw: The raw string representation of the literal
:type raw: str
:rtype: Literal
"""
raw: str
@property
def value(self) -> Any:
"""
Get the actual value of the literal.
:return: The evaluated value of the literal
:rtype: Any
"""
return self._value()
def _value(self) -> Any:
"""
Internal method to evaluate the literal's value.
:return: The evaluated value of the literal
:rtype: Any
:raises NotImplementedError: This method must be implemented by subclasses
"""
raise NotImplementedError # pragma: no cover
[docs]
def __str__(self) -> str:
"""
Convert the literal to its string representation.
:return: String representation of the literal's value
:rtype: str
"""
return str(self._value())
[docs]
@dataclass
class Integer(Literal):
"""
Represents an integer literal in the state machine DSL.
:param raw: The raw string representation of the integer
:type raw: str
:rtype: Integer
Example::
>>> int_val = Integer("42")
>>> int_val.value
42
"""
def _value(self) -> int:
"""
Convert the raw string to an integer value.
:return: The integer value
:rtype: int
"""
return int(self.raw)
[docs]
@dataclass
class HexInt(Literal):
"""
Represents a hexadecimal integer literal in the state machine DSL.
:param raw: The raw string representation of the hexadecimal integer (e.g., ``"0xFF"``)
:type raw: str
:rtype: HexInt
Example::
>>> hex_val = HexInt("0xFF")
>>> hex_val.value
255
"""
def _value(self) -> int:
"""
Convert the raw hexadecimal string to an integer value.
:return: The integer value
:rtype: int
"""
return int(self.raw, 16)
[docs]
def __str__(self) -> str:
"""
Convert the hexadecimal integer to its string representation.
:return: Lowercase string representation of the hexadecimal value
:rtype: str
"""
return self.raw.lower()
[docs]
@dataclass
class Float(Literal):
"""
Represents a floating-point literal in the state machine DSL.
:param raw: The raw string representation of the float
:type raw: str
:rtype: Float
Example::
>>> float_val = Float("3.14")
>>> float_val.value
3.14
"""
def _value(self) -> float:
"""
Convert the raw string to a float value.
:return: The float value
:rtype: float
"""
return float(self.raw)
[docs]
def __str__(self) -> str:
"""
Convert the float to its string representation.
:return: String representation of the float
:rtype: str
"""
return self.raw
[docs]
@dataclass
class Boolean(Literal):
"""
Represents a boolean literal in the state machine DSL.
:param raw: The raw string representation of the boolean (``"true"`` or ``"false"``)
:type raw: str
:rtype: Boolean
Example::
>>> bool_val = Boolean("true")
>>> bool_val.value
True
"""
[docs]
def __post_init__(self) -> None:
"""
Normalize the raw value to lowercase after initialization.
"""
self.raw = self.raw.lower()
def _value(self) -> bool:
"""
Convert the raw string to a boolean value.
:return: The boolean value
:rtype: bool
"""
return json.loads(self.raw)
[docs]
@dataclass
class Constant(Literal):
"""
Represents a named mathematical constant in the state machine DSL.
:param raw: The name of the constant (e.g., ``"pi"``, ``"E"``)
:type raw: str
:rtype: Constant
Example::
>>> pi_const = Constant("pi")
>>> pi_const.value
3.141592653589793
"""
__KNOWN_CONSTANTS__ = {
"E": math.e,
"pi": math.pi,
"tau": math.tau,
}
def _value(self) -> float:
"""
Get the value of the named constant.
:return: The constant's value
:rtype: float
"""
return self.__KNOWN_CONSTANTS__[self.raw]
[docs]
def __str__(self) -> str:
"""
Convert the constant to its string representation.
:return: The name of the constant
:rtype: str
"""
return f"{self.raw}"
[docs]
@dataclass
class Name(Expr):
"""
Represents a named reference in the state machine DSL.
Names are used to reference variables, states, or other named elements.
:param name: The identifier name
:type name: str
:rtype: Name
Example::
>>> var_name = Name("counter")
>>> str(var_name)
'counter'
"""
name: str
[docs]
def __str__(self) -> str:
"""
Convert the name to its string representation.
:return: The name as a string
:rtype: str
"""
return self.name
[docs]
@dataclass
class Paren(Expr):
"""
Represents a parenthesized expression in the state machine DSL.
Parentheses are used to control the order of operations in expressions.
:param expr: The expression within the parentheses
:type expr: Expr
:rtype: Paren
Example::
>>> inner_expr = BinaryOp(Name("a"), "+", Name("b"))
>>> paren_expr = Paren(inner_expr)
>>> str(paren_expr)
'(a + b)'
"""
expr: Expr
[docs]
def __str__(self) -> str:
"""
Convert the parenthesized expression to its string representation.
:return: The expression surrounded by parentheses
:rtype: str
"""
return f"({self.expr})"
[docs]
@dataclass
class UnaryOp(Expr):
"""
Represents a unary operation in the state machine DSL.
Unary operations apply a single operator to an expression.
:param op: The unary operator (e.g., ``"!"``, ``"not"``, ``"-"``)
:type op: str
:param expr: The expression to which the operator is applied
:type expr: Expr
:rtype: UnaryOp
Example::
>>> not_expr = UnaryOp("not", Name("condition"))
>>> str(not_expr)
'!condition'
"""
__aliases__ = {
"not": "!",
}
op: str
expr: Expr
[docs]
def __post_init__(self) -> None:
"""
Replace any operator aliases with their canonical form.
"""
self.op = self.__aliases__.get(self.op, self.op)
[docs]
def __str__(self) -> str:
"""
Convert the unary operation to its string representation.
:return: String representation of the unary operation
:rtype: str
"""
return f"{self.op}{self.expr}"
[docs]
@dataclass
class BinaryOp(Expr):
"""
Represents a binary operation in the state machine DSL.
Binary operations apply an operator to two expressions.
:param expr1: The left-hand expression
:type expr1: Expr
:param op: The binary operator (e.g., ``"+"``, ``"-"``, ``"and"``, ``"or"``)
:type op: str
:param expr2: The right-hand expression
:type expr2: Expr
:rtype: BinaryOp
Example::
>>> add_expr = BinaryOp(Name("a"), "+", Name("b"))
>>> str(add_expr)
'a + b'
>>> and_expr = BinaryOp(Name("x"), "and", Name("y"))
>>> str(and_expr)
'x && y'
"""
__aliases__ = {
"and": "&&",
"or": "||",
}
expr1: Expr
op: str
expr2: Expr
[docs]
def __post_init__(self) -> None:
"""
Replace any operator aliases with their canonical form.
"""
self.op = self.__aliases__.get(self.op, self.op)
[docs]
def __str__(self) -> str:
"""
Convert the binary operation to its string representation.
:return: String representation of the binary operation
:rtype: str
"""
return f"{self.expr1} {self.op} {self.expr2}"
[docs]
@dataclass
class ConditionalOp(Expr):
"""
Represents a conditional (ternary) operation in the state machine DSL.
The conditional operation evaluates a condition and returns one of two expressions
based on whether the condition is true or false.
:param cond: The condition expression
:type cond: Expr
:param value_true: The expression to evaluate if the condition is true
:type value_true: Expr
:param value_false: The expression to evaluate if the condition is false
:type value_false: Expr
:rtype: ConditionalOp
Example::
>>> cond_op = ConditionalOp(Name("x"), Integer("1"), Integer("0"))
>>> str(cond_op)
'(x) ? 1 : 0'
"""
cond: Expr
value_true: Expr
value_false: Expr
[docs]
def __str__(self) -> str:
"""
Convert the conditional operation to its string representation.
:return: String representation of the conditional operation
:rtype: str
"""
return f"({self.cond}) ? {self.value_true} : {self.value_false}"
[docs]
@dataclass
class UFunc(Expr):
"""
Represents a unary function call in the state machine DSL.
Unary functions apply a named function to a single expression.
:param func: The function name
:type func: str
:param expr: The expression to which the function is applied
:type expr: Expr
:rtype: UFunc
Example::
>>> func_call = UFunc("abs", Name("x"))
>>> str(func_call)
'abs(x)'
"""
func: str
expr: Expr
[docs]
def __str__(self) -> str:
"""
Convert the function call to its string representation.
:return: String representation of the function call
:rtype: str
"""
return f"{self.func}({self.expr})"
[docs]
@dataclass
class Statement(ASTNode):
"""
Abstract base class for statements in the state machine DSL.
Statements represent actions or declarations in the state machine definition.
:rtype: Statement
"""
pass
[docs]
@dataclass
class OperationalStatement(Statement):
"""
Abstract base class for statements inside operation blocks.
Operation statements are the executable statements that may appear inside
``effect { ... }``, ``enter { ... }``, ``during { ... }``, and ``exit { ... }``
blocks.
:rtype: OperationalStatement
"""
pass
def _render_operational_statement_block(
statements: List["OperationalStatement"],
) -> str:
"""
Render an operation statement list as an indented block body.
:param statements: Statements to render.
:type statements: List[OperationalStatement]
:return: Rendered block body without surrounding braces.
:rtype: str
"""
with io.StringIO() as sf:
for statement in statements:
print(indent(str(statement), prefix=" "), file=sf)
return sf.getvalue()
[docs]
@dataclass
class ConstantDefinition(Statement):
"""
Represents a constant definition statement in the state machine DSL.
:param name: The name of the constant
:type name: str
:param expr: The expression defining the constant's value
:type expr: Expr
:rtype: ConstantDefinition
Example::
>>> const_def = ConstantDefinition("MAX_COUNT", Integer("100"))
>>> str(const_def)
'MAX_COUNT = 100;'
"""
name: str
expr: Expr
[docs]
def __str__(self) -> str:
"""
Convert the constant definition to its string representation.
:return: String representation of the constant definition
:rtype: str
"""
return f"{self.name} = {self.expr};"
[docs]
@dataclass
class InitialAssignment(Statement):
"""
Represents an initial assignment statement in the state machine DSL.
Initial assignments are used to set initial values for variables.
:param name: The name of the variable
:type name: str
:param expr: The expression defining the variable's initial value
:type expr: Expr
:rtype: InitialAssignment
Example::
>>> init_assign = InitialAssignment("counter", Integer("0"))
>>> str(init_assign)
'counter := 0;'
"""
name: str
expr: Expr
[docs]
def __str__(self) -> str:
"""
Convert the initial assignment to its string representation.
:return: String representation of the initial assignment
:rtype: str
"""
return f"{self.name} := {self.expr};"
[docs]
@dataclass
class DefAssignment(Statement):
"""
Represents a definition assignment statement in the state machine DSL.
Definition assignments are used to declare and initialize typed variables.
:param name: The name of the variable
:type name: str
:param type: The type of the variable
:type type: str
:param expr: The expression defining the variable's value
:type expr: Expr
:rtype: DefAssignment
Example::
>>> def_assign = DefAssignment("counter", "int", Integer("0"))
>>> str(def_assign)
'def int counter = 0;'
"""
name: str
type: str
expr: Expr
_span: Optional[Span] = field(default=None, repr=False, compare=False)
[docs]
def __str__(self) -> str:
"""
Convert the definition assignment to its string representation.
:return: String representation of the definition assignment
:rtype: str
"""
return f"def {self.type} {self.name} = {self.expr};"
[docs]
@dataclass
class OperationalDeprecatedAssignment(Statement):
"""
Represents a deprecated form of operational assignment in the state machine DSL.
:param name: The name of the variable
:type name: str
:param expr: The expression defining the variable's value
:type expr: Expr
:rtype: OperationalDeprecatedAssignment
Example::
>>> op_assign = OperationalDeprecatedAssignment(
... "counter", BinaryOp(Name("counter"), "+", Integer("1"))
... )
>>> str(op_assign)
'counter := counter + 1;'
"""
name: str
expr: Expr
[docs]
def __str__(self) -> str:
"""
Convert the operational deprecated assignment to its string representation.
:return: String representation of the operational deprecated assignment
:rtype: str
"""
return f"{self.name} := {self.expr};"
[docs]
@dataclass
class Condition(ASTNode):
"""
Represents a condition in the state machine DSL.
Conditions are used in transitions and other contexts to determine when actions should occur.
:param expr: The expression defining the condition
:type expr: Expr
:rtype: Condition
Example::
>>> cond = Condition(BinaryOp(Name("counter"), ">=", Integer("10")))
>>> str(cond)
'counter >= 10'
"""
expr: Expr
[docs]
def __str__(self) -> str:
"""
Convert the condition to its string representation.
:return: String representation of the condition
:rtype: str
"""
return f"{self.expr}"
[docs]
@dataclass
class Preamble(ASTNode):
"""
Represents a preamble section in the state machine DSL.
The preamble contains constant definitions and initial assignments that set up
the state machine's environment.
:param stats: List of statements in the preamble
:type stats: List[Union[ConstantDefinition, InitialAssignment]]
:rtype: Preamble
Example::
>>> const_def = ConstantDefinition("MAX", Integer("100"))
>>> init_assign = InitialAssignment("counter", Integer("0"))
>>> preamble = Preamble([const_def, init_assign])
>>> print(str(preamble))
MAX = 100;
counter := 0;
"""
stats: List[Union[ConstantDefinition, InitialAssignment]]
[docs]
def __str__(self) -> str:
"""
Convert the preamble to its string representation.
:return: String representation of the preamble
:rtype: str
"""
return os.linesep.join(map(str, self.stats))
[docs]
@dataclass
class Operation(ASTNode):
"""
Represents an operation block in the state machine DSL.
Operations are sequences of assignments that modify the state machine's variables.
:param stats: List of operational assignments
:type stats: List[OperationalDeprecatedAssignment]
:rtype: Operation
Example::
>>> op1 = OperationalDeprecatedAssignment(
... "counter", BinaryOp(Name("counter"), "+", Integer("1"))
... )
>>> op2 = OperationalDeprecatedAssignment("flag", Boolean("true"))
>>> operation = Operation([op1, op2])
>>> print(str(operation))
counter := counter + 1;
flag := true;
"""
stats: List[OperationalDeprecatedAssignment]
[docs]
def __str__(self) -> str:
"""
Convert the operation to its string representation.
:return: String representation of the operation
:rtype: str
"""
return os.linesep.join(map(str, self.stats))
class _StateSingletonMark(SingletonMark):
"""
A singleton marker class for special states in the state machine DSL.
:param mark: The marker name
:type mark: str
:rtype: _StateSingletonMark
"""
def __repr__(self) -> str:
"""
Convert the singleton mark to its string representation.
:return: The marker name
:rtype: str
"""
return self.mark
INIT_STATE = _StateSingletonMark("INIT_STATE")
"""
Special singleton marker representing the initial state in a state machine.
This is used to define transitions from the initial pseudo-state.
"""
EXIT_STATE = _StateSingletonMark("EXIT_STATE")
"""
Special singleton marker representing the exit state in a state machine.
This is used to define transitions to the final pseudo-state.
"""
ALL = _StateSingletonMark("ALL")
"""
Special singleton marker representing all states in a state machine.
This is used to define transitions or actions that apply to all states.
"""
[docs]
@dataclass
class ImportMappingStatement(ASTNode):
"""
Abstract base class for mapping statements inside an import block.
Import mapping statements configure how imported variables and events should
be exposed or remapped when the imported module is assembled into the host
state tree.
:rtype: ImportMappingStatement
"""
pass
[docs]
@dataclass
class ImportDefSelector(ASTNode):
"""
Abstract base class for ``def`` mapping source selectors in import blocks.
Source selectors determine which imported variable names a ``def`` mapping
rule applies to.
:rtype: ImportDefSelector
"""
pass
[docs]
@dataclass
class ImportDefExactSelector(ImportDefSelector):
"""
Represents an exact source variable selector in an import ``def`` mapping.
:param name: Exact variable name to match
:type name: str
:rtype: ImportDefExactSelector
"""
name: str
[docs]
def __str__(self) -> str:
"""
Convert the selector to its DSL representation.
:return: String representation of the selector
:rtype: str
"""
return self.name
[docs]
@dataclass
class ImportDefSetSelector(ImportDefSelector):
"""
Represents a set-based source selector in an import ``def`` mapping.
:param names: Exact variable names listed in the selector set
:type names: List[str]
:rtype: ImportDefSetSelector
"""
names: List[str]
[docs]
def __str__(self) -> str:
"""
Convert the selector to its DSL representation.
:return: String representation of the selector
:rtype: str
"""
return "{%s}" % ", ".join(self.names)
[docs]
@dataclass
class ImportDefPatternSelector(ImportDefSelector):
"""
Represents a wildcard-based source selector in an import ``def`` mapping.
The selector is intentionally preserved as raw DSL text rather than being
decomposed into finer-grained AST nodes. This keeps the DSL layer permissive
enough for patterns whose literal segments would not fit the plain ``ID``
token constraints, such as suffixes that start with digits.
:param pattern: Raw selector pattern text
:type pattern: str
:rtype: ImportDefPatternSelector
"""
pattern: str
[docs]
def __str__(self) -> str:
"""
Convert the selector to its DSL representation.
:return: String representation of the selector
:rtype: str
"""
return self.pattern
[docs]
@dataclass
class ImportDefFallbackSelector(ImportDefSelector):
"""
Represents the fallback ``*`` selector in an import ``def`` mapping.
:rtype: ImportDefFallbackSelector
"""
[docs]
def __str__(self) -> str:
"""
Convert the selector to its DSL representation.
:return: Wildcard text
:rtype: str
"""
return "*"
[docs]
@dataclass
class ImportDefTargetTemplate(ASTNode):
"""
Represents the right-hand target template of an import ``def`` mapping.
The template is stored as raw DSL text at the AST layer. Placeholder and
wildcard semantics are validated and interpreted in later assembly phases.
:param template: Raw template text
:type template: str
:rtype: ImportDefTargetTemplate
"""
template: str
[docs]
def __str__(self) -> str:
"""
Convert the template to its DSL representation.
:return: String representation of the target template
:rtype: str
"""
return self.template
[docs]
@dataclass
class ImportDefMapping(ImportMappingStatement):
"""
Represents a variable mapping rule inside an import block.
:param selector: Source selector of the mapping rule
:type selector: ImportDefSelector
:param target_template: Target template of the mapping rule
:type target_template: ImportDefTargetTemplate
:rtype: ImportDefMapping
"""
selector: ImportDefSelector
target_template: ImportDefTargetTemplate
[docs]
def __str__(self) -> str:
"""
Convert the mapping rule to its DSL representation.
:return: String representation of the mapping rule
:rtype: str
"""
return f"def {self.selector} -> {self.target_template};"
[docs]
@dataclass
class ImportEventMapping(ImportMappingStatement):
"""
Represents an event mapping rule inside an import block.
:param source_event: Source event path inside the imported module
:type source_event: ChainID
:param target_event: Target event path in the host state tree
:type target_event: ChainID
:param extra_name: Optional display-name override for the target event
:type extra_name: Optional[str]
:rtype: ImportEventMapping
"""
source_event: ChainID
target_event: ChainID
extra_name: Optional[str] = None
[docs]
def __str__(self) -> str:
"""
Convert the event mapping to its DSL representation.
:return: String representation of the event mapping
:rtype: str
"""
with io.StringIO() as sf:
print(f"event {self.source_event} -> {self.target_event}", file=sf, end="")
if self.extra_name is not None:
print(f" named {self.extra_name!r}", file=sf, end="")
print(";", file=sf, end="")
return sf.getvalue()
[docs]
@dataclass
class ImportStatement(ASTNode):
"""
Represents a single import statement inside a composite state.
:param source_path: Import source path string from the DSL
:type source_path: str
:param alias: Local alias name of the imported root state
:type alias: str
:param extra_name: Optional display-name override for the imported state
:type extra_name: Optional[str]
:param mappings: Mapping rules declared inside the import block
:type mappings: List[ImportMappingStatement]
:rtype: ImportStatement
"""
source_path: str
alias: str
extra_name: Optional[str] = None
mappings: List[ImportMappingStatement] = None
[docs]
def __post_init__(self) -> None:
"""
Initialize default empty lists for optional parameters.
"""
self.mappings = self.mappings or []
[docs]
def __str__(self) -> str:
"""
Convert the import statement to its DSL representation.
:return: String representation of the import statement
:rtype: str
"""
with io.StringIO() as sf:
print(f"import {self.source_path!r} as {self.alias}", file=sf, end="")
if self.extra_name is not None:
print(f" named {self.extra_name!r}", file=sf, end="")
if self.mappings:
print(" {", file=sf)
for mapping in self.mappings:
print(indent(str(mapping), prefix=" "), file=sf)
print("}", file=sf, end="")
else:
print(";", file=sf, end="")
return sf.getvalue()
[docs]
@dataclass
class TransitionDefinition(ASTNode):
"""
Represents a transition definition in the state machine DSL.
Transitions define how the state machine moves from one state to another in response
to events and conditions.
:param from_state: The source state name or :data:`INIT_STATE` singleton
:type from_state: Union[str, _StateSingletonMark]
:param to_state: The target state name or :data:`EXIT_STATE` singleton
:type to_state: Union[str, _StateSingletonMark]
:param event_id: Optional event identifier that triggers the transition
:type event_id: Optional[ChainID]
:param condition_expr: Optional condition expression that must be true for the transition
:type condition_expr: Optional[Expr]
:param post_operations: List of operation statements to perform after the transition
:type post_operations: List[OperationalStatement]
:rtype: TransitionDefinition
Example::
>>> init_trans = TransitionDefinition(INIT_STATE, "idle", None, None, [])
>>> event_trans = TransitionDefinition(
... "idle", "active", ChainID(["idle", "start"]), None, []
... )
>>> op = OperationAssignment("counter", Integer("0"))
>>> cond_trans = TransitionDefinition(
... "active", "idle", None,
... BinaryOp(Name("counter"), ">", Integer("10")),
... [op],
... )
"""
from_state: Union[str, _StateSingletonMark]
to_state: Union[str, _StateSingletonMark]
event_id: Optional[ChainID]
condition_expr: Optional[Expr]
post_operations: List["OperationalStatement"]
_span: Optional[Span] = field(default=None, repr=False, compare=False)
[docs]
def __str__(self) -> str:
"""
Convert the transition definition to its string representation.
:return: String representation of the transition definition
:rtype: str
"""
with io.StringIO() as sf:
print(
"[*]" if self.from_state is INIT_STATE else self.from_state,
file=sf,
end="",
)
print(" -> ", file=sf, end="")
print(
"[*]" if self.to_state is EXIT_STATE else self.to_state, file=sf, end=""
)
if self.event_id is not None:
if not self.event_id.is_absolute and (
(self.from_state is INIT_STATE and len(self.event_id.path) == 1)
or (
self.from_state is not INIT_STATE
and len(self.event_id.path) == 2
and self.event_id.path[0] == self.from_state
)
):
print(f" :: {self.event_id.path[-1]}", file=sf, end="")
else:
print(f" : {self.event_id}", file=sf, end="")
elif self.condition_expr is not None:
print(f" : if [{self.condition_expr}]", file=sf, end="")
if len(self.post_operations) > 0:
print(" effect {", file=sf)
print(
_render_operational_statement_block(self.post_operations),
file=sf,
end="",
)
print("}", file=sf, end="")
else:
print(";", file=sf, end="")
return sf.getvalue()
[docs]
@dataclass
class ForceTransitionDefinition(ASTNode):
"""
Represents a forced transition definition in the state machine DSL.
Forced transitions override normal transitions and are used for special cases
like error handling or interrupts.
:param from_state: The source state name or :data:`ALL` singleton
:type from_state: Union[str, _StateSingletonMark]
:param to_state: The target state name or :data:`EXIT_STATE` singleton
:type to_state: Union[str, _StateSingletonMark]
:param event_id: Optional event identifier that triggers the transition
:type event_id: Optional[ChainID]
:param condition_expr: Optional condition expression that must be true for the transition
:type condition_expr: Optional[Expr]
:rtype: ForceTransitionDefinition
Example::
>>> force_trans = ForceTransitionDefinition(ALL, "error", None, None)
>>> str(force_trans)
'! * -> error;'
"""
from_state: Union[str, _StateSingletonMark]
to_state: Union[str, _StateSingletonMark]
event_id: Optional[ChainID]
condition_expr: Optional[Expr]
_span: Optional[Span] = field(default=None, repr=False, compare=False)
[docs]
def __str__(self) -> str:
"""
Convert the force transition definition to its string representation.
:return: String representation of the force transition definition
:rtype: str
"""
with io.StringIO() as sf:
print("! ", file=sf, end="")
print("*" if self.from_state is ALL else self.from_state, file=sf, end="")
print(" -> ", file=sf, end="")
print(
"[*]" if self.to_state is EXIT_STATE else self.to_state, file=sf, end=""
)
if self.event_id is not None:
if not self.event_id.is_absolute and (
(self.from_state is ALL and len(self.event_id.path) == 1)
or (
self.from_state is not ALL
and len(self.event_id.path) == 2
and self.event_id.path[0] == self.from_state
)
):
print(f" :: {self.event_id.path[-1]}", file=sf, end="")
else:
print(f" : {self.event_id}", file=sf, end="")
elif self.condition_expr is not None:
print(f" : if [{self.condition_expr}]", file=sf, end="")
print(";", file=sf, end="")
return sf.getvalue()
[docs]
@dataclass
class StateDefinition(ASTNode):
"""
Represents a state definition in the state machine DSL.
States are the fundamental building blocks of state machines, containing
transitions, substates, and actions to be performed on entry, during, and exit.
:param name: The name of the state
:type name: str
:param extra_name: Optional additional name for the state
:type extra_name: Optional[str]
:param events: List of events defined within this state
:type events: List[EventDefinition]
:param imports: List of import statements declared within this state
:type imports: List[ImportStatement]
:param substates: List of nested state definitions
:type substates: List[StateDefinition]
:param transitions: List of transitions from this state
:type transitions: List[TransitionDefinition]
:param enters: List of actions to perform when entering the state
:type enters: List[EnterStatement]
:param durings: List of actions to perform while in the state
:type durings: List[DuringStatement]
:param exits: List of actions to perform when exiting the state
:type exits: List[ExitStatement]
:param during_aspects: List of aspect-specific actions to perform while in the state
:type during_aspects: List[DuringAspectStatement]
:param force_transitions: List of forced transitions from this state
:type force_transitions: List[ForceTransitionDefinition]
:param is_pseudo: Whether this is a pseudo state
:type is_pseudo: bool
:rtype: StateDefinition
Example::
>>> simple_state = StateDefinition("idle")
>>> str(simple_state)
'state idle;'
>>> trans = TransitionDefinition(
... "idle", "active", ChainID(["idle", "start"]), None, []
... )
>>> state_with_trans = StateDefinition("idle", transitions=[trans])
"""
name: str
extra_name: Optional[str] = None
events: List["EventDefinition"] = None
imports: List["ImportStatement"] = None
substates: List["StateDefinition"] = None
transitions: List[TransitionDefinition] = None
enters: List["EnterStatement"] = None
durings: List["DuringStatement"] = None
exits: List["ExitStatement"] = None
during_aspects: List["DuringAspectStatement"] = None
force_transitions: List["ForceTransitionDefinition"] = None
is_pseudo: bool = False
_span: Optional[Span] = field(default=None, repr=False, compare=False)
[docs]
def __post_init__(self) -> None:
"""
Initialize default empty lists for optional parameters.
"""
self.events = self.events or []
self.imports = self.imports or []
self.substates = self.substates or []
self.transitions = self.transitions or []
self.force_transitions = self.force_transitions or []
self.enters = self.enters or []
self.durings = self.durings or []
self.exits = self.exits or []
self.during_aspects = self.during_aspects or []
[docs]
def __str__(self) -> str:
"""
Convert the state definition to its string representation.
:return: String representation of the state definition
:rtype: str
"""
with io.StringIO() as sf:
if self.is_pseudo:
print("pseudo ", file=sf, end="")
print(f"state {self.name}", file=sf, end="")
if self.extra_name is not None:
print(f" named {self.extra_name!r}", file=sf, end="")
if (
not self.substates
and not self.transitions
and not self.force_transitions
and not self.events
and not self.imports
and not self.enters
and not self.durings
and not self.exits
and not self.during_aspects
):
print(f";", file=sf, end="")
else:
print(f" {{", file=sf)
for enter_item in self.enters:
print(indent(str(enter_item), prefix=" "), file=sf)
for during_item in self.durings:
print(indent(str(during_item), prefix=" "), file=sf)
for exit_item in self.exits:
print(indent(str(exit_item), prefix=" "), file=sf)
for during_aspect_item in self.during_aspects:
print(indent(str(during_aspect_item), prefix=" "), file=sf)
for import_item in self.imports:
print(indent(str(import_item), prefix=" "), file=sf)
for substate in self.substates:
print(indent(str(substate), prefix=" "), file=sf)
for event in self.events:
print(indent(str(event), prefix=" "), file=sf)
for force_transition in self.force_transitions:
print(indent(str(force_transition), prefix=" "), file=sf)
for transition in self.transitions:
print(indent(str(transition), prefix=" "), file=sf)
print(f"}}", file=sf, end="")
return sf.getvalue()
[docs]
@dataclass
class OperationAssignment(OperationalStatement):
"""
Represents an operation assignment in the state machine DSL.
Operation assignments are used to modify variables during transitions or state actions.
:param name: The name of the variable
:type name: str
:param expr: The expression defining the new value
:type expr: Expr
:rtype: OperationAssignment
Example::
>>> op_assign = OperationAssignment(
... "counter", BinaryOp(Name("counter"), "+", Integer("1"))
... )
>>> str(op_assign)
'counter = counter + 1;'
"""
name: str
expr: Expr
[docs]
def __str__(self) -> str:
"""
Convert the operation assignment to its string representation.
:return: String representation of the operation assignment
:rtype: str
"""
return f"{self.name} = {self.expr};"
[docs]
@dataclass
class OperationIfBranch(ASTNode):
"""
Represents a single branch inside an operation-block ``if`` statement.
:param condition: Branch condition, or ``None`` for the final ``else`` branch.
:type condition: Optional[Expr]
:param statements: Statements executed when the branch is selected.
:type statements: List[OperationalStatement]
:rtype: OperationIfBranch
"""
condition: Optional[Expr]
statements: List[OperationalStatement]
[docs]
@dataclass
class OperationIf(OperationalStatement):
"""
Represents an ``if / else if / else`` statement inside an operation block.
:param branches: Ordered branch list. The last branch may have
``condition=None`` to represent ``else``.
:type branches: List[OperationIfBranch]
:rtype: OperationIf
"""
branches: List[OperationIfBranch]
[docs]
def __str__(self) -> str:
"""
Convert the operation ``if`` statement to its DSL representation.
:return: String representation of the operation ``if`` statement.
:rtype: str
"""
with io.StringIO() as sf:
first_branch = self.branches[0]
print(f"if [{first_branch.condition}] {{", file=sf)
for index, branch in enumerate(self.branches):
if index > 0:
if branch.condition is None:
print("} else {", file=sf)
else:
print(f"}} else if [{branch.condition}] {{", file=sf)
print(
_render_operational_statement_block(branch.statements),
file=sf,
end="",
)
print("}", file=sf, end="")
return sf.getvalue()
[docs]
@dataclass
class EventDefinition(ASTNode):
"""
Represents an event definition in the state machine DSL.
Events are signals that can trigger transitions or other actions within the state machine.
:param name: The name of the event
:type name: str
:param extra_name: Optional additional name for the event
:type extra_name: Optional[str]
:rtype: EventDefinition
Example::
>>> event = EventDefinition("start")
>>> str(event)
'event start;'
>>> named_event = EventDefinition("start", "Start Event")
>>> str(named_event)
'event start named "Start Event";'
"""
name: str
extra_name: Optional[str] = None
[docs]
def __str__(self) -> str:
"""
Convert the event definition to its string representation.
:return: String representation of the event definition
:rtype: str
"""
with io.StringIO() as sf:
print(f"event {self.name}", file=sf, end="")
if self.extra_name is not None:
print(f" named {self.extra_name!r}", file=sf, end="")
print(";", file=sf, end="")
return sf.getvalue()
[docs]
@dataclass
class StateMachineDSLProgram(ASTNode):
"""
Represents a complete state machine DSL program.
A program consists of variable definitions and a root state that contains
the entire state machine hierarchy.
:param definitions: List of variable definitions
:type definitions: List[DefAssignment]
:param root_state: The root state of the state machine
:type root_state: StateDefinition
:rtype: StateMachineDSLProgram
Example::
>>> def_var = DefAssignment("counter", "int", Integer("0"))
>>> root = StateDefinition("root")
>>> program = StateMachineDSLProgram([def_var], root)
>>> print(str(program))
def int counter = 0;
state root;
"""
definitions: List[DefAssignment]
root_state: StateDefinition
[docs]
def __str__(self) -> str:
"""
Convert the state machine program to its string representation.
:return: String representation of the state machine program
:rtype: str
"""
with io.StringIO() as f:
for definition in self.definitions:
print(definition, file=f)
print(self.root_state, file=f, end="")
return f.getvalue()
[docs]
@dataclass
class EnterStatement(ASTNode):
"""
Abstract base class for enter statements in the state machine DSL.
Enter statements define actions to be performed when entering a state.
:rtype: EnterStatement
"""
pass
[docs]
@dataclass
class EnterOperations(EnterStatement):
"""
Represents a block of operations to perform when entering a state.
:param operations: List of operation statements
:type operations: List[OperationalStatement]
:param name: Optional name for the operation block
:type name: Optional[str]
:rtype: EnterOperations
Example::
>>> op = OperationAssignment("counter", Integer("0"))
>>> enter_ops = EnterOperations([op])
>>> print(str(enter_ops))
enter {
counter = 0;
}
"""
operations: List[OperationalStatement]
name: Optional[str] = None
[docs]
def __str__(self) -> str:
"""
Convert the enter operations to their string representation.
:return: String representation of the enter operations
:rtype: str
"""
with io.StringIO() as f:
if self.name:
print(f"enter {self.name} {{", file=f)
else:
print(f"enter {{", file=f)
print(_render_operational_statement_block(self.operations), file=f, end="")
print("}", file=f, end="")
return f.getvalue()
[docs]
@dataclass
class EnterAbstractFunction(EnterStatement):
"""
Represents an abstract function to call when entering a state.
Abstract functions are placeholders for implementation-specific behavior.
:param name: Optional name of the function
:type name: Optional[str]
:param doc: Optional documentation for the function
:type doc: Optional[str]
:rtype: EnterAbstractFunction
Example::
>>> enter_func = EnterAbstractFunction("initState", "Initialize the state")
>>> print(str(enter_func))
enter abstract initState /*
Initialize the state
*/
"""
name: Optional[str]
doc: Optional[str]
[docs]
def __str__(self) -> str:
"""
Convert the enter abstract function to its string representation.
:return: String representation of the enter abstract function
:rtype: str
"""
with io.StringIO() as f:
if self.name:
print(f"enter abstract {self.name}", file=f, end="")
else:
print(f"enter abstract", file=f, end="")
if self.doc is not None:
print(" /*", file=f)
print(indent(self.doc, prefix=" "), file=f)
print("*/", file=f, end="")
else:
print(";", file=f, end="")
return f.getvalue()
[docs]
@dataclass
class EnterRefFunction(EnterStatement):
"""
Represents a reference function to call when entering a state.
Reference functions point to existing functions defined elsewhere.
:param name: Optional name of the function
:type name: Optional[str]
:param ref: Chain identifier referencing the function
:type ref: ChainID
:rtype: EnterRefFunction
Example::
>>> ref_func = EnterRefFunction("init", ChainID(["common", "initialize"]))
>>> str(ref_func)
'enter init ref common.initialize;'
"""
name: Optional[str]
ref: ChainID
[docs]
def __str__(self) -> str:
"""
Convert the enter reference function to its string representation.
:return: String representation of the enter reference function
:rtype: str
"""
if self.name:
return f"enter {self.name} ref {self.ref};"
else:
return f"enter ref {self.ref};"
[docs]
@dataclass
class ExitStatement(ASTNode):
"""
Abstract base class for exit statements in the state machine DSL.
Exit statements define actions to be performed when exiting a state.
:rtype: ExitStatement
"""
pass
[docs]
@dataclass
class ExitOperations(ExitStatement):
"""
Represents a block of operations to perform when exiting a state.
:param operations: List of operation statements
:type operations: List[OperationalStatement]
:param name: Optional name for the operation block
:type name: Optional[str]
:rtype: ExitOperations
Example::
>>> op = OperationAssignment("active", Boolean("false"))
>>> exit_ops = ExitOperations([op])
>>> print(str(exit_ops))
exit {
active = false;
}
"""
operations: List[OperationalStatement]
name: Optional[str] = None
[docs]
def __str__(self) -> str:
"""
Convert the exit operations to their string representation.
:return: String representation of the exit operations
:rtype: str
"""
with io.StringIO() as f:
if self.name:
print(f"exit {self.name} {{", file=f)
else:
print(f"exit {{", file=f)
print(_render_operational_statement_block(self.operations), file=f, end="")
print("}", file=f, end="")
return f.getvalue()
[docs]
@dataclass
class ExitAbstractFunction(ExitStatement):
"""
Represents an abstract function to call when exiting a state.
Abstract functions are placeholders for implementation-specific behavior.
:param name: Optional name of the function
:type name: Optional[str]
:param doc: Optional documentation for the function
:type doc: Optional[str]
:rtype: ExitAbstractFunction
Example::
>>> exit_func = ExitAbstractFunction("cleanupState", "Clean up resources")
>>> print(str(exit_func))
exit abstract cleanupState /*
Clean up resources
*/
"""
name: Optional[str]
doc: Optional[str]
[docs]
def __str__(self) -> str:
"""
Convert the exit abstract function to its string representation.
:return: String representation of the exit abstract function
:rtype: str
"""
with io.StringIO() as f:
if self.name:
print(f"exit abstract {self.name}", file=f, end="")
else:
print(f"exit abstract", file=f, end="")
if self.doc is not None:
print(" /*", file=f)
print(indent(self.doc, prefix=" "), file=f)
print("*/", file=f, end="")
else:
print(";", file=f, end="")
return f.getvalue()
[docs]
@dataclass
class ExitRefFunction(ExitStatement):
"""
Represents a reference function to call when exiting a state.
Reference functions point to existing functions defined elsewhere.
:param name: Optional name of the function
:type name: Optional[str]
:param ref: Chain identifier referencing the function
:type ref: ChainID
:rtype: ExitRefFunction
Example::
>>> ref_func = ExitRefFunction("cleanup", ChainID(["common", "cleanup"]))
>>> str(ref_func)
'exit cleanup ref common.cleanup;'
"""
name: Optional[str]
ref: ChainID
[docs]
def __str__(self) -> str:
"""
Convert the exit reference function to its string representation.
:return: String representation of the exit reference function
:rtype: str
"""
if self.name:
return f"exit {self.name} ref {self.ref};"
else:
return f"exit ref {self.ref};"
[docs]
@dataclass
class DuringStatement(ASTNode):
"""
Abstract base class for during statements in the state machine DSL.
During statements define actions to be performed while in a state.
:rtype: DuringStatement
"""
pass
[docs]
@dataclass
class DuringOperations(DuringStatement):
"""
Represents a block of operations to perform while in a state.
:param aspect: Optional aspect name (e.g., ``"entry"``, ``"do"``, ``"exit"``)
:type aspect: Optional[str]
:param operations: List of operation statements
:type operations: List[OperationalStatement]
:param name: Optional name for the operation block
:type name: Optional[str]
:rtype: DuringOperations
Example::
>>> op = OperationAssignment("counter", BinaryOp(Name("counter"), "+", Integer("1")))
>>> during_ops = DuringOperations("do", [op])
>>> print(str(during_ops))
during do {
counter = counter + 1;
}
"""
aspect: Optional[str]
operations: List[OperationalStatement]
name: Optional[str] = None
[docs]
def __str__(self) -> str:
"""
Convert the during operations to their string representation.
:return: String representation of the during operations
:rtype: str
"""
with io.StringIO() as f:
if self.name:
if self.aspect:
print(f"during {self.aspect} {self.name} {{", file=f)
else:
print(f"during {self.name} {{", file=f)
else:
if self.aspect:
print(f"during {self.aspect} {{", file=f)
else:
print(f"during {{", file=f)
print(_render_operational_statement_block(self.operations), file=f, end="")
print("}", file=f, end="")
return f.getvalue()
[docs]
@dataclass
class DuringAbstractFunction(DuringStatement):
"""
Represents an abstract function to call while in a state.
Abstract functions are placeholders for implementation-specific behavior.
:param name: Optional name of the function
:type name: Optional[str]
:param aspect: Optional aspect name (e.g., ``"entry"``, ``"do"``, ``"exit"``)
:type aspect: Optional[str]
:param doc: Optional documentation for the function
:type doc: Optional[str]
:rtype: DuringAbstractFunction
Example::
>>> during_func = DuringAbstractFunction("processData", "do", "Process incoming data")
>>> print(str(during_func))
during do abstract processData /*
Process incoming data
*/
"""
name: Optional[str]
aspect: Optional[str]
doc: Optional[str]
[docs]
def __str__(self) -> str:
"""
Convert the during abstract function to its string representation.
:return: String representation of the during abstract function
:rtype: str
"""
with io.StringIO() as f:
if self.name:
if self.aspect:
print(f"during {self.aspect} abstract {self.name}", file=f, end="")
else:
print(f"during abstract {self.name}", file=f, end="")
else:
if self.aspect:
print(f"during {self.aspect} abstract", file=f, end="")
else:
print(f"during abstract", file=f, end="")
if self.doc is not None:
print(" /*", file=f)
print(indent(self.doc, prefix=" "), file=f)
print("*/", file=f, end="")
else:
print(";", file=f, end="")
return f.getvalue()
[docs]
@dataclass
class DuringRefFunction(DuringStatement):
"""
Represents a reference function to call while in a state.
Reference functions point to existing functions defined elsewhere.
:param name: Optional name of the function
:type name: Optional[str]
:param aspect: Optional aspect name (e.g., ``"entry"``, ``"do"``, ``"exit"``)
:type aspect: Optional[str]
:param ref: Chain identifier referencing the function
:type ref: ChainID
:rtype: DuringRefFunction
Example::
>>> ref_func = DuringRefFunction("process", "do", ChainID(["common", "process"]))
>>> str(ref_func)
'during do process ref common.process;'
"""
name: Optional[str]
aspect: Optional[str]
ref: ChainID
[docs]
def __str__(self) -> str:
"""
Convert the during reference function to its string representation.
:return: String representation of the during reference function
:rtype: str
"""
if self.name:
if self.aspect:
return f"during {self.aspect} {self.name} ref {self.ref};"
else:
return f"during {self.name} ref {self.ref};"
else:
if self.aspect:
return f"during {self.aspect} ref {self.ref};"
else:
return f"during ref {self.ref};"
[docs]
@dataclass
class DuringAspectStatement(ASTNode):
"""
Abstract base class for during aspect statements in the state machine DSL.
During aspect statements define aspect-specific actions to be performed while in a state.
:rtype: DuringAspectStatement
"""
pass
[docs]
@dataclass
class DuringAspectOperations(DuringAspectStatement):
"""
Represents a block of aspect-specific operations to perform while in a state.
:param aspect: The aspect name (e.g., ``"before"``, ``"after"``)
:type aspect: str
:param operations: List of operation statements
:type operations: List[OperationalStatement]
:param name: Optional name for the operation block
:type name: Optional[str]
:rtype: DuringAspectOperations
Example::
>>> op = OperationAssignment("counter", BinaryOp(Name("counter"), "+", Integer("1")))
>>> during_ops = DuringAspectOperations("before", [op])
>>> print(str(during_ops))
>> during before {
counter = counter + 1;
}
"""
aspect: str
operations: List[OperationalStatement]
name: Optional[str] = None
[docs]
def __str__(self) -> str:
"""
Convert the during aspect operations to their string representation.
:return: String representation of the during aspect operations
:rtype: str
"""
with io.StringIO() as f:
if self.name:
print(f">> during {self.aspect} {self.name} {{", file=f)
else:
print(f">> during {self.aspect} {{", file=f)
print(_render_operational_statement_block(self.operations), file=f, end="")
print("}", file=f, end="")
return f.getvalue()
[docs]
@dataclass
class DuringAspectAbstractFunction(DuringAspectStatement):
"""
Represents an abstract function to call for a specific aspect while in a state.
Abstract functions are placeholders for implementation-specific behavior.
:param name: Optional name of the function
:type name: Optional[str]
:param aspect: The aspect name (e.g., ``"before"``, ``"after"``)
:type aspect: str
:param doc: Optional documentation for the function
:type doc: Optional[str]
:rtype: DuringAspectAbstractFunction
Example::
>>> during_func = DuringAspectAbstractFunction(
... "processData", "before", "Process incoming data"
... )
>>> print(str(during_func))
>> during before abstract processData /*
Process incoming data
*/
"""
name: Optional[str]
aspect: str
doc: Optional[str]
[docs]
def __str__(self) -> str:
"""
Convert the during aspect abstract function to its string representation.
:return: String representation of the during aspect abstract function
:rtype: str
"""
with io.StringIO() as f:
if self.name:
print(f">> during {self.aspect} abstract {self.name}", file=f, end="")
else:
print(f">> during {self.aspect} abstract", file=f, end="")
if self.doc is not None:
print(" /*", file=f)
print(indent(self.doc, prefix=" "), file=f)
print("*/", file=f, end="")
else:
print(";", file=f, end="")
return f.getvalue()
[docs]
@dataclass
class DuringAspectRefFunction(DuringAspectStatement):
"""
Represents a reference function to call for a specific aspect while in a state.
Reference functions point to existing functions defined elsewhere.
:param name: Optional name of the function
:type name: Optional[str]
:param aspect: The aspect name (e.g., ``"before"``, ``"after"``)
:type aspect: str
:param ref: Chain identifier referencing the function
:type ref: ChainID
:rtype: DuringAspectRefFunction
Example::
>>> ref_func = DuringAspectRefFunction(
... "process", "before", ChainID(["common", "process"])
... )
>>> str(ref_func)
'>> during before process ref common.process;'
"""
name: Optional[str]
aspect: str
ref: ChainID
[docs]
def __str__(self) -> str:
"""
Convert the during aspect reference function to its string representation.
:return: String representation of the during aspect reference function
:rtype: str
"""
if self.name:
return f">> during {self.aspect} {self.name} ref {self.ref};"
else:
return f">> during {self.aspect} ref {self.ref};"