pyfcstm 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyfcstm/__init__.py +0 -0
- pyfcstm/__main__.py +4 -0
- pyfcstm/config/__init__.py +0 -0
- pyfcstm/config/meta.py +20 -0
- pyfcstm/dsl/__init__.py +6 -0
- pyfcstm/dsl/error.py +226 -0
- pyfcstm/dsl/grammar/Grammar.g4 +190 -0
- pyfcstm/dsl/grammar/Grammar.interp +168 -0
- pyfcstm/dsl/grammar/Grammar.tokens +118 -0
- pyfcstm/dsl/grammar/GrammarLexer.interp +214 -0
- pyfcstm/dsl/grammar/GrammarLexer.py +523 -0
- pyfcstm/dsl/grammar/GrammarLexer.tokens +118 -0
- pyfcstm/dsl/grammar/GrammarListener.py +521 -0
- pyfcstm/dsl/grammar/GrammarParser.py +4373 -0
- pyfcstm/dsl/grammar/__init__.py +3 -0
- pyfcstm/dsl/listener.py +440 -0
- pyfcstm/dsl/node.py +1581 -0
- pyfcstm/dsl/parse.py +155 -0
- pyfcstm/entry/__init__.py +1 -0
- pyfcstm/entry/base.py +126 -0
- pyfcstm/entry/cli.py +12 -0
- pyfcstm/entry/dispatch.py +46 -0
- pyfcstm/entry/generate.py +83 -0
- pyfcstm/entry/plantuml.py +67 -0
- pyfcstm/model/__init__.py +3 -0
- pyfcstm/model/base.py +51 -0
- pyfcstm/model/expr.py +764 -0
- pyfcstm/model/model.py +1392 -0
- pyfcstm/render/__init__.py +3 -0
- pyfcstm/render/env.py +36 -0
- pyfcstm/render/expr.py +180 -0
- pyfcstm/render/func.py +77 -0
- pyfcstm/render/render.py +279 -0
- pyfcstm/utils/__init__.py +6 -0
- pyfcstm/utils/binary.py +38 -0
- pyfcstm/utils/doc.py +64 -0
- pyfcstm/utils/jinja2.py +121 -0
- pyfcstm/utils/json.py +125 -0
- pyfcstm/utils/text.py +91 -0
- pyfcstm/utils/validate.py +102 -0
- pyfcstm-0.0.1.dist-info/LICENSE +165 -0
- pyfcstm-0.0.1.dist-info/METADATA +205 -0
- pyfcstm-0.0.1.dist-info/RECORD +46 -0
- pyfcstm-0.0.1.dist-info/WHEEL +5 -0
- pyfcstm-0.0.1.dist-info/entry_points.txt +2 -0
- pyfcstm-0.0.1.dist-info/top_level.txt +1 -0
pyfcstm/dsl/node.py
ADDED
@@ -0,0 +1,1581 @@
|
|
1
|
+
"""
|
2
|
+
State Machine DSL AST Module
|
3
|
+
|
4
|
+
This module defines the Abstract Syntax Tree (AST) classes for a State Machine Domain Specific Language.
|
5
|
+
It provides a comprehensive set of classes to represent various elements of state machines including:
|
6
|
+
|
7
|
+
- States and transitions
|
8
|
+
- Expressions and operations
|
9
|
+
- Conditions and assignments
|
10
|
+
- Event handling with enter, during, and exit actions
|
11
|
+
|
12
|
+
The AST classes enable parsing, manipulation, and generation of state machine definitions
|
13
|
+
in a structured way, supporting both simple and hierarchical state machines.
|
14
|
+
"""
|
15
|
+
|
16
|
+
import io
|
17
|
+
import json
|
18
|
+
import math
|
19
|
+
import os
|
20
|
+
from abc import ABC
|
21
|
+
from dataclasses import dataclass
|
22
|
+
from textwrap import indent
|
23
|
+
|
24
|
+
from hbutils.design import SingletonMark
|
25
|
+
|
26
|
+
__all__ = [
|
27
|
+
'ASTNode',
|
28
|
+
'Identifier',
|
29
|
+
'ChainID',
|
30
|
+
'Expr',
|
31
|
+
'Literal',
|
32
|
+
'Boolean',
|
33
|
+
'Integer',
|
34
|
+
'HexInt',
|
35
|
+
'Float',
|
36
|
+
'Constant',
|
37
|
+
'Name',
|
38
|
+
'Paren',
|
39
|
+
'UnaryOp',
|
40
|
+
'BinaryOp',
|
41
|
+
'ConditionalOp',
|
42
|
+
'UFunc',
|
43
|
+
'Statement',
|
44
|
+
'ConstantDefinition',
|
45
|
+
'InitialAssignment',
|
46
|
+
'DefAssignment',
|
47
|
+
'OperationalDeprecatedAssignment',
|
48
|
+
'Preamble',
|
49
|
+
'Operation',
|
50
|
+
'Condition',
|
51
|
+
'TransitionDefinition',
|
52
|
+
'ForceTransitionDefinition',
|
53
|
+
'StateDefinition',
|
54
|
+
'OperationAssignment',
|
55
|
+
'StateMachineDSLProgram',
|
56
|
+
'INIT_STATE',
|
57
|
+
'EXIT_STATE',
|
58
|
+
'ALL',
|
59
|
+
'EnterStatement',
|
60
|
+
'EnterOperations',
|
61
|
+
'EnterAbstractFunction',
|
62
|
+
'ExitStatement',
|
63
|
+
'ExitOperations',
|
64
|
+
'ExitAbstractFunction',
|
65
|
+
'DuringStatement',
|
66
|
+
'DuringOperations',
|
67
|
+
'DuringAbstractFunction',
|
68
|
+
'DuringAspectStatement',
|
69
|
+
'DuringAspectOperations',
|
70
|
+
'DuringAspectAbstractFunction',
|
71
|
+
]
|
72
|
+
|
73
|
+
from typing import List, Union, Optional
|
74
|
+
|
75
|
+
|
76
|
+
@dataclass
|
77
|
+
class ASTNode(ABC):
|
78
|
+
"""
|
79
|
+
Abstract base class for all AST nodes in the state machine DSL.
|
80
|
+
|
81
|
+
This class serves as the foundation for all nodes in the Abstract Syntax Tree,
|
82
|
+
providing a common type for all elements in the state machine definition.
|
83
|
+
|
84
|
+
:rtype: ASTNode
|
85
|
+
"""
|
86
|
+
pass
|
87
|
+
|
88
|
+
|
89
|
+
@dataclass
|
90
|
+
class Identifier(ASTNode):
|
91
|
+
"""
|
92
|
+
Abstract base class for identifiers in the state machine DSL.
|
93
|
+
|
94
|
+
Identifiers are used to reference variables, states, and other named elements
|
95
|
+
in the state machine definition.
|
96
|
+
|
97
|
+
:rtype: Identifier
|
98
|
+
"""
|
99
|
+
pass
|
100
|
+
|
101
|
+
|
102
|
+
@dataclass
|
103
|
+
class ChainID(Identifier):
|
104
|
+
"""
|
105
|
+
Represents a chained identifier (e.g., a.b.c) in the state machine DSL.
|
106
|
+
|
107
|
+
:param path: List of string components that make up the chained identifier
|
108
|
+
:type path: List[str]
|
109
|
+
|
110
|
+
:rtype: ChainID
|
111
|
+
|
112
|
+
Example::
|
113
|
+
|
114
|
+
>>> chain_id = ChainID(['state1', 'event'])
|
115
|
+
>>> str(chain_id)
|
116
|
+
'state1.event'
|
117
|
+
"""
|
118
|
+
path: List[str]
|
119
|
+
is_absolute: bool = False
|
120
|
+
|
121
|
+
def __str__(self):
|
122
|
+
"""
|
123
|
+
Convert the ChainID to its string representation.
|
124
|
+
|
125
|
+
:return: String representation of the chained identifier
|
126
|
+
:rtype: str
|
127
|
+
"""
|
128
|
+
pth = '.'.join(self.path)
|
129
|
+
if self.is_absolute:
|
130
|
+
pth = f'/{pth}'
|
131
|
+
return pth
|
132
|
+
|
133
|
+
|
134
|
+
@dataclass
|
135
|
+
class Expr(ASTNode):
|
136
|
+
"""
|
137
|
+
Abstract base class for expressions in the state machine DSL.
|
138
|
+
|
139
|
+
Expressions represent computations that produce values, which can be used in
|
140
|
+
conditions, assignments, and other contexts within the state machine.
|
141
|
+
|
142
|
+
:rtype: Expr
|
143
|
+
"""
|
144
|
+
pass
|
145
|
+
|
146
|
+
|
147
|
+
@dataclass
|
148
|
+
class Literal(Expr):
|
149
|
+
"""
|
150
|
+
Base class for literal values in expressions.
|
151
|
+
|
152
|
+
Literal values are constants directly expressed in the code, such as numbers,
|
153
|
+
booleans, or predefined constants.
|
154
|
+
|
155
|
+
:param raw: The raw string representation of the literal
|
156
|
+
:type raw: str
|
157
|
+
|
158
|
+
:rtype: Literal
|
159
|
+
"""
|
160
|
+
raw: str
|
161
|
+
|
162
|
+
@property
|
163
|
+
def value(self):
|
164
|
+
"""
|
165
|
+
Get the actual value of the literal.
|
166
|
+
|
167
|
+
:return: The evaluated value of the literal
|
168
|
+
:rtype: Any
|
169
|
+
"""
|
170
|
+
return self._value()
|
171
|
+
|
172
|
+
def _value(self):
|
173
|
+
"""
|
174
|
+
Internal method to evaluate the literal's value.
|
175
|
+
|
176
|
+
:return: The evaluated value of the literal
|
177
|
+
:rtype: Any
|
178
|
+
:raises NotImplementedError: This method must be implemented by subclasses
|
179
|
+
"""
|
180
|
+
raise NotImplementedError # pragma: no cover
|
181
|
+
|
182
|
+
def __str__(self):
|
183
|
+
"""
|
184
|
+
Convert the literal to its string representation.
|
185
|
+
|
186
|
+
:return: String representation of the literal's value
|
187
|
+
:rtype: str
|
188
|
+
"""
|
189
|
+
return str(self._value())
|
190
|
+
|
191
|
+
|
192
|
+
@dataclass
|
193
|
+
class Integer(Literal):
|
194
|
+
"""
|
195
|
+
Represents an integer literal in the state machine DSL.
|
196
|
+
|
197
|
+
:param raw: The raw string representation of the integer
|
198
|
+
:type raw: str
|
199
|
+
|
200
|
+
:rtype: Integer
|
201
|
+
|
202
|
+
Example::
|
203
|
+
|
204
|
+
>>> int_val = Integer("42")
|
205
|
+
>>> int_val.value
|
206
|
+
42
|
207
|
+
"""
|
208
|
+
|
209
|
+
def _value(self):
|
210
|
+
"""
|
211
|
+
Convert the raw string to an integer value.
|
212
|
+
|
213
|
+
:return: The integer value
|
214
|
+
:rtype: int
|
215
|
+
"""
|
216
|
+
return int(self.raw)
|
217
|
+
|
218
|
+
|
219
|
+
@dataclass
|
220
|
+
class HexInt(Literal):
|
221
|
+
"""
|
222
|
+
Represents a hexadecimal integer literal in the state machine DSL.
|
223
|
+
|
224
|
+
:param raw: The raw string representation of the hexadecimal integer (e.g., "0xFF")
|
225
|
+
:type raw: str
|
226
|
+
|
227
|
+
:rtype: HexInt
|
228
|
+
|
229
|
+
Example::
|
230
|
+
|
231
|
+
>>> hex_val = HexInt("0xFF")
|
232
|
+
>>> hex_val.value
|
233
|
+
255
|
234
|
+
"""
|
235
|
+
|
236
|
+
def _value(self):
|
237
|
+
"""
|
238
|
+
Convert the raw hexadecimal string to an integer value.
|
239
|
+
|
240
|
+
:return: The integer value
|
241
|
+
:rtype: int
|
242
|
+
"""
|
243
|
+
return int(self.raw, 16)
|
244
|
+
|
245
|
+
def __str__(self):
|
246
|
+
"""
|
247
|
+
Convert the hexadecimal integer to its string representation.
|
248
|
+
|
249
|
+
:return: Lowercase string representation of the hexadecimal value
|
250
|
+
:rtype: str
|
251
|
+
"""
|
252
|
+
return self.raw.lower()
|
253
|
+
|
254
|
+
|
255
|
+
@dataclass
|
256
|
+
class Float(Literal):
|
257
|
+
"""
|
258
|
+
Represents a floating-point literal in the state machine DSL.
|
259
|
+
|
260
|
+
:param raw: The raw string representation of the float
|
261
|
+
:type raw: str
|
262
|
+
|
263
|
+
:rtype: Float
|
264
|
+
|
265
|
+
Example::
|
266
|
+
|
267
|
+
>>> float_val = Float("3.14")
|
268
|
+
>>> float_val.value
|
269
|
+
3.14
|
270
|
+
"""
|
271
|
+
|
272
|
+
def _value(self):
|
273
|
+
"""
|
274
|
+
Convert the raw string to a float value.
|
275
|
+
|
276
|
+
:return: The float value
|
277
|
+
:rtype: float
|
278
|
+
"""
|
279
|
+
return float(self.raw)
|
280
|
+
|
281
|
+
def __str__(self):
|
282
|
+
"""
|
283
|
+
Convert the float to its string representation.
|
284
|
+
|
285
|
+
:return: String representation of the float
|
286
|
+
:rtype: str
|
287
|
+
"""
|
288
|
+
return self.raw
|
289
|
+
|
290
|
+
|
291
|
+
@dataclass
|
292
|
+
class Boolean(Literal):
|
293
|
+
"""
|
294
|
+
Represents a boolean literal in the state machine DSL.
|
295
|
+
|
296
|
+
:param raw: The raw string representation of the boolean ("true" or "false")
|
297
|
+
:type raw: str
|
298
|
+
|
299
|
+
:rtype: Boolean
|
300
|
+
|
301
|
+
Example::
|
302
|
+
|
303
|
+
>>> bool_val = Boolean("true")
|
304
|
+
>>> bool_val.value
|
305
|
+
True
|
306
|
+
"""
|
307
|
+
|
308
|
+
def __post_init__(self):
|
309
|
+
"""
|
310
|
+
Normalize the raw value to lowercase after initialization.
|
311
|
+
"""
|
312
|
+
self.raw = self.raw.lower()
|
313
|
+
|
314
|
+
def _value(self):
|
315
|
+
"""
|
316
|
+
Convert the raw string to a boolean value.
|
317
|
+
|
318
|
+
:return: The boolean value
|
319
|
+
:rtype: bool
|
320
|
+
"""
|
321
|
+
return json.loads(self.raw)
|
322
|
+
|
323
|
+
|
324
|
+
@dataclass
|
325
|
+
class Constant(Literal):
|
326
|
+
"""
|
327
|
+
Represents a named mathematical constant in the state machine DSL.
|
328
|
+
|
329
|
+
:param raw: The name of the constant (e.g., "pi", "E")
|
330
|
+
:type raw: str
|
331
|
+
|
332
|
+
:rtype: Constant
|
333
|
+
|
334
|
+
Example::
|
335
|
+
|
336
|
+
>>> pi_const = Constant("pi")
|
337
|
+
>>> pi_const.value
|
338
|
+
3.141592653589793
|
339
|
+
"""
|
340
|
+
__KNOWN_CONSTANTS__ = {
|
341
|
+
'E': math.e,
|
342
|
+
'pi': math.pi,
|
343
|
+
'tau': math.tau,
|
344
|
+
}
|
345
|
+
|
346
|
+
def _value(self):
|
347
|
+
"""
|
348
|
+
Get the value of the named constant.
|
349
|
+
|
350
|
+
:return: The constant's value
|
351
|
+
:rtype: float
|
352
|
+
"""
|
353
|
+
return self.__KNOWN_CONSTANTS__[self.raw]
|
354
|
+
|
355
|
+
def __str__(self):
|
356
|
+
"""
|
357
|
+
Convert the constant to its string representation.
|
358
|
+
|
359
|
+
:return: The name of the constant
|
360
|
+
:rtype: str
|
361
|
+
"""
|
362
|
+
return f'{self.raw}'
|
363
|
+
|
364
|
+
|
365
|
+
@dataclass
|
366
|
+
class Name(Expr):
|
367
|
+
"""
|
368
|
+
Represents a named reference in the state machine DSL.
|
369
|
+
|
370
|
+
Names are used to reference variables, states, or other named elements.
|
371
|
+
|
372
|
+
:param name: The identifier name
|
373
|
+
:type name: str
|
374
|
+
|
375
|
+
:rtype: Name
|
376
|
+
|
377
|
+
Example::
|
378
|
+
|
379
|
+
>>> var_name = Name("counter")
|
380
|
+
>>> str(var_name)
|
381
|
+
'counter'
|
382
|
+
"""
|
383
|
+
name: str
|
384
|
+
|
385
|
+
def __str__(self):
|
386
|
+
"""
|
387
|
+
Convert the name to its string representation.
|
388
|
+
|
389
|
+
:return: The name as a string
|
390
|
+
:rtype: str
|
391
|
+
"""
|
392
|
+
return self.name
|
393
|
+
|
394
|
+
|
395
|
+
@dataclass
|
396
|
+
class Paren(Expr):
|
397
|
+
"""
|
398
|
+
Represents a parenthesized expression in the state machine DSL.
|
399
|
+
|
400
|
+
Parentheses are used to control the order of operations in expressions.
|
401
|
+
|
402
|
+
:param expr: The expression within the parentheses
|
403
|
+
:type expr: Expr
|
404
|
+
|
405
|
+
:rtype: Paren
|
406
|
+
|
407
|
+
Example::
|
408
|
+
|
409
|
+
>>> inner_expr = BinaryOp(Name("a"), "+", Name("b"))
|
410
|
+
>>> paren_expr = Paren(inner_expr)
|
411
|
+
>>> str(paren_expr)
|
412
|
+
'(a + b)'
|
413
|
+
"""
|
414
|
+
expr: Expr
|
415
|
+
|
416
|
+
def __str__(self):
|
417
|
+
"""
|
418
|
+
Convert the parenthesized expression to its string representation.
|
419
|
+
|
420
|
+
:return: The expression surrounded by parentheses
|
421
|
+
:rtype: str
|
422
|
+
"""
|
423
|
+
return f'({self.expr})'
|
424
|
+
|
425
|
+
|
426
|
+
@dataclass
|
427
|
+
class UnaryOp(Expr):
|
428
|
+
"""
|
429
|
+
Represents a unary operation in the state machine DSL.
|
430
|
+
|
431
|
+
Unary operations apply a single operator to an expression.
|
432
|
+
|
433
|
+
:param op: The unary operator (e.g., "!", "not", "-")
|
434
|
+
:type op: str
|
435
|
+
:param expr: The expression to which the operator is applied
|
436
|
+
:type expr: Expr
|
437
|
+
|
438
|
+
:rtype: UnaryOp
|
439
|
+
|
440
|
+
Example::
|
441
|
+
|
442
|
+
>>> not_expr = UnaryOp("not", Name("condition"))
|
443
|
+
>>> str(not_expr)
|
444
|
+
'!condition'
|
445
|
+
"""
|
446
|
+
__aliases__ = {
|
447
|
+
'not': '!',
|
448
|
+
}
|
449
|
+
|
450
|
+
op: str
|
451
|
+
expr: Expr
|
452
|
+
|
453
|
+
def __post_init__(self):
|
454
|
+
"""
|
455
|
+
Replace any operator aliases with their canonical form.
|
456
|
+
"""
|
457
|
+
self.op = self.__aliases__.get(self.op, self.op)
|
458
|
+
|
459
|
+
def __str__(self):
|
460
|
+
"""
|
461
|
+
Convert the unary operation to its string representation.
|
462
|
+
|
463
|
+
:return: String representation of the unary operation
|
464
|
+
:rtype: str
|
465
|
+
"""
|
466
|
+
return f'{self.op}{self.expr}'
|
467
|
+
|
468
|
+
|
469
|
+
@dataclass
|
470
|
+
class BinaryOp(Expr):
|
471
|
+
"""
|
472
|
+
Represents a binary operation in the state machine DSL.
|
473
|
+
|
474
|
+
Binary operations apply an operator to two expressions.
|
475
|
+
|
476
|
+
:param expr1: The left-hand expression
|
477
|
+
:type expr1: Expr
|
478
|
+
:param op: The binary operator (e.g., "+", "-", "and", "or")
|
479
|
+
:type op: str
|
480
|
+
:param expr2: The right-hand expression
|
481
|
+
:type expr2: Expr
|
482
|
+
|
483
|
+
:rtype: BinaryOp
|
484
|
+
|
485
|
+
Example::
|
486
|
+
|
487
|
+
>>> add_expr = BinaryOp(Name("a"), "+", Name("b"))
|
488
|
+
>>> str(add_expr)
|
489
|
+
'a + b'
|
490
|
+
>>> and_expr = BinaryOp(Name("x"), "and", Name("y"))
|
491
|
+
>>> str(and_expr)
|
492
|
+
'x && y'
|
493
|
+
"""
|
494
|
+
__aliases__ = {
|
495
|
+
'and': '&&',
|
496
|
+
'or': '||',
|
497
|
+
}
|
498
|
+
|
499
|
+
expr1: Expr
|
500
|
+
op: str
|
501
|
+
expr2: Expr
|
502
|
+
|
503
|
+
def __post_init__(self):
|
504
|
+
"""
|
505
|
+
Replace any operator aliases with their canonical form.
|
506
|
+
"""
|
507
|
+
self.op = self.__aliases__.get(self.op, self.op)
|
508
|
+
|
509
|
+
def __str__(self):
|
510
|
+
"""
|
511
|
+
Convert the binary operation to its string representation.
|
512
|
+
|
513
|
+
:return: String representation of the binary operation
|
514
|
+
:rtype: str
|
515
|
+
"""
|
516
|
+
return f'{self.expr1} {self.op} {self.expr2}'
|
517
|
+
|
518
|
+
|
519
|
+
@dataclass
|
520
|
+
class ConditionalOp(Expr):
|
521
|
+
"""
|
522
|
+
Represents a conditional (ternary) operation in the state machine DSL.
|
523
|
+
|
524
|
+
The conditional operation evaluates a condition and returns one of two expressions
|
525
|
+
based on whether the condition is true or false.
|
526
|
+
|
527
|
+
:param cond: The condition expression
|
528
|
+
:type cond: Expr
|
529
|
+
:param value_true: The expression to evaluate if the condition is true
|
530
|
+
:type value_true: Expr
|
531
|
+
:param value_false: The expression to evaluate if the condition is false
|
532
|
+
:type value_false: Expr
|
533
|
+
|
534
|
+
:rtype: ConditionalOp
|
535
|
+
|
536
|
+
Example::
|
537
|
+
|
538
|
+
>>> cond_op = ConditionalOp(Name("x"), Integer("1"), Integer("0"))
|
539
|
+
>>> str(cond_op)
|
540
|
+
'(x) ? 1 : 0'
|
541
|
+
"""
|
542
|
+
cond: Expr
|
543
|
+
value_true: Expr
|
544
|
+
value_false: Expr
|
545
|
+
|
546
|
+
def __str__(self):
|
547
|
+
"""
|
548
|
+
Convert the conditional operation to its string representation.
|
549
|
+
|
550
|
+
:return: String representation of the conditional operation
|
551
|
+
:rtype: str
|
552
|
+
"""
|
553
|
+
return f'({self.cond}) ? {self.value_true} : {self.value_false}'
|
554
|
+
|
555
|
+
|
556
|
+
@dataclass
|
557
|
+
class UFunc(Expr):
|
558
|
+
"""
|
559
|
+
Represents a unary function call in the state machine DSL.
|
560
|
+
|
561
|
+
Unary functions apply a named function to a single expression.
|
562
|
+
|
563
|
+
:param func: The function name
|
564
|
+
:type func: str
|
565
|
+
:param expr: The expression to which the function is applied
|
566
|
+
:type expr: Expr
|
567
|
+
|
568
|
+
:rtype: UFunc
|
569
|
+
|
570
|
+
Example::
|
571
|
+
|
572
|
+
>>> func_call = UFunc("abs", Name("x"))
|
573
|
+
>>> str(func_call)
|
574
|
+
'abs(x)'
|
575
|
+
"""
|
576
|
+
func: str
|
577
|
+
expr: Expr
|
578
|
+
|
579
|
+
def __str__(self):
|
580
|
+
"""
|
581
|
+
Convert the function call to its string representation.
|
582
|
+
|
583
|
+
:return: String representation of the function call
|
584
|
+
:rtype: str
|
585
|
+
"""
|
586
|
+
return f'{self.func}({self.expr})'
|
587
|
+
|
588
|
+
|
589
|
+
@dataclass
|
590
|
+
class Statement(ASTNode):
|
591
|
+
"""
|
592
|
+
Abstract base class for statements in the state machine DSL.
|
593
|
+
|
594
|
+
Statements represent actions or declarations in the state machine definition.
|
595
|
+
|
596
|
+
:rtype: Statement
|
597
|
+
"""
|
598
|
+
pass
|
599
|
+
|
600
|
+
|
601
|
+
@dataclass
|
602
|
+
class ConstantDefinition(Statement):
|
603
|
+
"""
|
604
|
+
Represents a constant definition statement in the state machine DSL.
|
605
|
+
|
606
|
+
:param name: The name of the constant
|
607
|
+
:type name: str
|
608
|
+
:param expr: The expression defining the constant's value
|
609
|
+
:type expr: Expr
|
610
|
+
|
611
|
+
:rtype: ConstantDefinition
|
612
|
+
|
613
|
+
Example::
|
614
|
+
|
615
|
+
>>> const_def = ConstantDefinition("MAX_COUNT", Integer("100"))
|
616
|
+
>>> str(const_def)
|
617
|
+
'MAX_COUNT = 100;'
|
618
|
+
"""
|
619
|
+
name: str
|
620
|
+
expr: Expr
|
621
|
+
|
622
|
+
def __str__(self):
|
623
|
+
"""
|
624
|
+
Convert the constant definition to its string representation.
|
625
|
+
|
626
|
+
:return: String representation of the constant definition
|
627
|
+
:rtype: str
|
628
|
+
"""
|
629
|
+
return f'{self.name} = {self.expr};'
|
630
|
+
|
631
|
+
|
632
|
+
@dataclass
|
633
|
+
class InitialAssignment(Statement):
|
634
|
+
"""
|
635
|
+
Represents an initial assignment statement in the state machine DSL.
|
636
|
+
|
637
|
+
Initial assignments are used to set initial values for variables.
|
638
|
+
|
639
|
+
:param name: The name of the variable
|
640
|
+
:type name: str
|
641
|
+
:param expr: The expression defining the variable's initial value
|
642
|
+
:type expr: Expr
|
643
|
+
|
644
|
+
:rtype: InitialAssignment
|
645
|
+
|
646
|
+
Example::
|
647
|
+
|
648
|
+
>>> init_assign = InitialAssignment("counter", Integer("0"))
|
649
|
+
>>> str(init_assign)
|
650
|
+
'counter := 0;'
|
651
|
+
"""
|
652
|
+
name: str
|
653
|
+
expr: Expr
|
654
|
+
|
655
|
+
def __str__(self):
|
656
|
+
"""
|
657
|
+
Convert the initial assignment to its string representation.
|
658
|
+
|
659
|
+
:return: String representation of the initial assignment
|
660
|
+
:rtype: str
|
661
|
+
"""
|
662
|
+
return f'{self.name} := {self.expr};'
|
663
|
+
|
664
|
+
|
665
|
+
@dataclass
|
666
|
+
class DefAssignment(Statement):
|
667
|
+
"""
|
668
|
+
Represents a definition assignment statement in the state machine DSL.
|
669
|
+
|
670
|
+
Definition assignments are used to declare and initialize typed variables.
|
671
|
+
|
672
|
+
:param name: The name of the variable
|
673
|
+
:type name: str
|
674
|
+
:param type: The type of the variable
|
675
|
+
:type type: str
|
676
|
+
:param expr: The expression defining the variable's value
|
677
|
+
:type expr: Expr
|
678
|
+
|
679
|
+
:rtype: DefAssignment
|
680
|
+
|
681
|
+
Example::
|
682
|
+
|
683
|
+
>>> def_assign = DefAssignment("counter", "int", Integer("0"))
|
684
|
+
>>> str(def_assign)
|
685
|
+
'def int counter = 0;'
|
686
|
+
"""
|
687
|
+
name: str
|
688
|
+
type: str
|
689
|
+
expr: Expr
|
690
|
+
|
691
|
+
def __str__(self):
|
692
|
+
"""
|
693
|
+
Convert the definition assignment to its string representation.
|
694
|
+
|
695
|
+
:return: String representation of the definition assignment
|
696
|
+
:rtype: str
|
697
|
+
"""
|
698
|
+
return f'def {self.type} {self.name} = {self.expr};'
|
699
|
+
|
700
|
+
|
701
|
+
@dataclass
|
702
|
+
class OperationalDeprecatedAssignment(Statement):
|
703
|
+
"""
|
704
|
+
Represents a deprecated form of operational assignment in the state machine DSL.
|
705
|
+
|
706
|
+
:param name: The name of the variable
|
707
|
+
:type name: str
|
708
|
+
:param expr: The expression defining the variable's value
|
709
|
+
:type expr: Expr
|
710
|
+
|
711
|
+
:rtype: OperationalDeprecatedAssignment
|
712
|
+
|
713
|
+
Example::
|
714
|
+
|
715
|
+
>>> op_assign = OperationalDeprecatedAssignment("counter", BinaryOp(Name("counter"), "+", Integer("1")))
|
716
|
+
>>> str(op_assign)
|
717
|
+
'counter := counter + 1;'
|
718
|
+
"""
|
719
|
+
name: str
|
720
|
+
expr: Expr
|
721
|
+
|
722
|
+
def __str__(self):
|
723
|
+
"""
|
724
|
+
Convert the operational deprecated assignment to its string representation.
|
725
|
+
|
726
|
+
:return: String representation of the operational deprecated assignment
|
727
|
+
:rtype: str
|
728
|
+
"""
|
729
|
+
return f'{self.name} := {self.expr};'
|
730
|
+
|
731
|
+
|
732
|
+
@dataclass
|
733
|
+
class Condition(ASTNode):
|
734
|
+
"""
|
735
|
+
Represents a condition in the state machine DSL.
|
736
|
+
|
737
|
+
Conditions are used in transitions and other contexts to determine when actions should occur.
|
738
|
+
|
739
|
+
:param expr: The expression defining the condition
|
740
|
+
:type expr: Expr
|
741
|
+
|
742
|
+
:rtype: Condition
|
743
|
+
|
744
|
+
Example::
|
745
|
+
|
746
|
+
>>> cond = Condition(BinaryOp(Name("counter"), ">=", Integer("10")))
|
747
|
+
>>> str(cond)
|
748
|
+
'counter >= 10'
|
749
|
+
"""
|
750
|
+
expr: Expr
|
751
|
+
|
752
|
+
def __str__(self):
|
753
|
+
"""
|
754
|
+
Convert the condition to its string representation.
|
755
|
+
|
756
|
+
:return: String representation of the condition
|
757
|
+
:rtype: str
|
758
|
+
"""
|
759
|
+
return f'{self.expr}'
|
760
|
+
|
761
|
+
|
762
|
+
@dataclass
|
763
|
+
class Preamble(ASTNode):
|
764
|
+
"""
|
765
|
+
Represents a preamble section in the state machine DSL.
|
766
|
+
|
767
|
+
The preamble contains constant definitions and initial assignments that set up
|
768
|
+
the state machine's environment.
|
769
|
+
|
770
|
+
:param stats: List of statements in the preamble
|
771
|
+
:type stats: List[Union[ConstantDefinition, InitialAssignment]]
|
772
|
+
|
773
|
+
:rtype: Preamble
|
774
|
+
|
775
|
+
Example::
|
776
|
+
|
777
|
+
>>> const_def = ConstantDefinition("MAX", Integer("100"))
|
778
|
+
>>> init_assign = InitialAssignment("counter", Integer("0"))
|
779
|
+
>>> preamble = Preamble([const_def, init_assign])
|
780
|
+
>>> print(str(preamble))
|
781
|
+
MAX = 100;
|
782
|
+
counter := 0;
|
783
|
+
"""
|
784
|
+
stats: List[Union[ConstantDefinition, InitialAssignment]]
|
785
|
+
|
786
|
+
def __str__(self):
|
787
|
+
"""
|
788
|
+
Convert the preamble to its string representation.
|
789
|
+
|
790
|
+
:return: String representation of the preamble
|
791
|
+
:rtype: str
|
792
|
+
"""
|
793
|
+
return os.linesep.join(map(str, self.stats))
|
794
|
+
|
795
|
+
|
796
|
+
@dataclass
|
797
|
+
class Operation(ASTNode):
|
798
|
+
"""
|
799
|
+
Represents an operation block in the state machine DSL.
|
800
|
+
|
801
|
+
Operations are sequences of assignments that modify the state machine's variables.
|
802
|
+
|
803
|
+
:param stats: List of operational assignments
|
804
|
+
:type stats: List[OperationalDeprecatedAssignment]
|
805
|
+
|
806
|
+
:rtype: Operation
|
807
|
+
|
808
|
+
Example::
|
809
|
+
|
810
|
+
>>> op1 = OperationalDeprecatedAssignment("counter", BinaryOp(Name("counter"), "+", Integer("1")))
|
811
|
+
>>> op2 = OperationalDeprecatedAssignment("flag", Boolean("true"))
|
812
|
+
>>> operation = Operation([op1, op2])
|
813
|
+
>>> print(str(operation))
|
814
|
+
counter := counter + 1;
|
815
|
+
flag := true;
|
816
|
+
"""
|
817
|
+
stats: List[OperationalDeprecatedAssignment]
|
818
|
+
|
819
|
+
def __str__(self):
|
820
|
+
"""
|
821
|
+
Convert the operation to its string representation.
|
822
|
+
|
823
|
+
:return: String representation of the operation
|
824
|
+
:rtype: str
|
825
|
+
"""
|
826
|
+
return os.linesep.join(map(str, self.stats))
|
827
|
+
|
828
|
+
|
829
|
+
class _StateSingletonMark(SingletonMark):
|
830
|
+
"""
|
831
|
+
A singleton marker class for special states in the state machine DSL.
|
832
|
+
|
833
|
+
:param mark: The marker name
|
834
|
+
:type mark: str
|
835
|
+
|
836
|
+
:rtype: _StateSingletonMark
|
837
|
+
"""
|
838
|
+
|
839
|
+
def __repr__(self):
|
840
|
+
"""
|
841
|
+
Convert the singleton mark to its string representation.
|
842
|
+
|
843
|
+
:return: The marker name
|
844
|
+
:rtype: str
|
845
|
+
"""
|
846
|
+
return self.mark
|
847
|
+
|
848
|
+
|
849
|
+
INIT_STATE = _StateSingletonMark('INIT_STATE')
|
850
|
+
"""
|
851
|
+
Special singleton marker representing the initial state in a state machine.
|
852
|
+
|
853
|
+
This is used to define transitions from the initial pseudo-state.
|
854
|
+
"""
|
855
|
+
|
856
|
+
EXIT_STATE = _StateSingletonMark('EXIT_STATE')
|
857
|
+
"""
|
858
|
+
Special singleton marker representing the exit state in a state machine.
|
859
|
+
|
860
|
+
This is used to define transitions to the final pseudo-state.
|
861
|
+
"""
|
862
|
+
|
863
|
+
ALL = _StateSingletonMark('ALL')
|
864
|
+
"""
|
865
|
+
Special singleton marker representing all states in a state machine.
|
866
|
+
|
867
|
+
This is used to define transitions or actions that apply to all states.
|
868
|
+
"""
|
869
|
+
|
870
|
+
|
871
|
+
@dataclass
|
872
|
+
class TransitionDefinition(ASTNode):
|
873
|
+
"""
|
874
|
+
Represents a transition definition in the state machine DSL.
|
875
|
+
|
876
|
+
Transitions define how the state machine moves from one state to another in response
|
877
|
+
to events and conditions.
|
878
|
+
|
879
|
+
:param from_state: The source state name or INIT_STATE singleton
|
880
|
+
:type from_state: Union[str, _StateSingletonMark]
|
881
|
+
:param to_state: The target state name or EXIT_STATE singleton
|
882
|
+
:type to_state: Union[str, _StateSingletonMark]
|
883
|
+
:param event_id: Optional event identifier that triggers the transition
|
884
|
+
:type event_id: Optional[ChainID]
|
885
|
+
:param condition_expr: Optional condition expression that must be true for the transition
|
886
|
+
:type condition_expr: Optional[Expr]
|
887
|
+
:param post_operations: List of operations to perform after the transition
|
888
|
+
:type post_operations: List[OperationAssignment]
|
889
|
+
|
890
|
+
:rtype: TransitionDefinition
|
891
|
+
|
892
|
+
Example::
|
893
|
+
|
894
|
+
>>> # Transition from initial state to "idle" state
|
895
|
+
>>> init_trans = TransitionDefinition(INIT_STATE, "idle", None, None, [])
|
896
|
+
>>> # Transition from "idle" to "active" on "start" event
|
897
|
+
>>> event_trans = TransitionDefinition("idle", "active", ChainID(["idle", "start"]), None, [])
|
898
|
+
>>> # Transition with condition and operations
|
899
|
+
>>> op = OperationAssignment("counter", Integer("0"))
|
900
|
+
>>> cond_trans = TransitionDefinition("active", "idle", None, BinaryOp(Name("counter"), ">", Integer("10")), [op])
|
901
|
+
"""
|
902
|
+
from_state: Union[str, _StateSingletonMark]
|
903
|
+
to_state: Union[str, _StateSingletonMark]
|
904
|
+
event_id: Optional[ChainID]
|
905
|
+
condition_expr: Optional[Expr]
|
906
|
+
post_operations: List['OperationAssignment']
|
907
|
+
|
908
|
+
def __str__(self):
|
909
|
+
"""
|
910
|
+
Convert the transition definition to its string representation.
|
911
|
+
|
912
|
+
:return: String representation of the transition definition
|
913
|
+
:rtype: str
|
914
|
+
"""
|
915
|
+
with io.StringIO() as sf:
|
916
|
+
print('[*]' if self.from_state is INIT_STATE else self.from_state, file=sf, end='')
|
917
|
+
print(' -> ', file=sf, end='')
|
918
|
+
print('[*]' if self.to_state is EXIT_STATE else self.to_state, file=sf, end='')
|
919
|
+
|
920
|
+
if self.event_id is not None:
|
921
|
+
if not self.event_id.is_absolute and \
|
922
|
+
((self.from_state is INIT_STATE and len(self.event_id.path) == 1) or
|
923
|
+
(self.from_state is not INIT_STATE and len(self.event_id.path) == 2 and
|
924
|
+
self.event_id.path[0] == self.from_state)):
|
925
|
+
print(f' :: {self.event_id.path[-1]}', file=sf, end='')
|
926
|
+
else:
|
927
|
+
print(f' : {self.event_id}', file=sf, end='')
|
928
|
+
elif self.condition_expr is not None:
|
929
|
+
print(f' : if [{self.condition_expr}]', file=sf, end='')
|
930
|
+
|
931
|
+
if len(self.post_operations) > 0:
|
932
|
+
print(' effect {', file=sf)
|
933
|
+
for operation in self.post_operations:
|
934
|
+
print(f' {operation}', file=sf)
|
935
|
+
print('}', file=sf, end='')
|
936
|
+
else:
|
937
|
+
print(';', file=sf, end='')
|
938
|
+
|
939
|
+
return sf.getvalue()
|
940
|
+
|
941
|
+
|
942
|
+
@dataclass
|
943
|
+
class ForceTransitionDefinition(ASTNode):
|
944
|
+
"""
|
945
|
+
Represents a forced transition definition in the state machine DSL.
|
946
|
+
|
947
|
+
Forced transitions override normal transitions and are used for special cases
|
948
|
+
like error handling or interrupts.
|
949
|
+
|
950
|
+
:param from_state: The source state name or ALL singleton
|
951
|
+
:type from_state: Union[str, _StateSingletonMark]
|
952
|
+
:param to_state: The target state name or EXIT_STATE singleton
|
953
|
+
:type to_state: Union[str, _StateSingletonMark]
|
954
|
+
:param event_id: Optional event identifier that triggers the transition
|
955
|
+
:type event_id: Optional[ChainID]
|
956
|
+
:param condition_expr: Optional condition expression that must be true for the transition
|
957
|
+
:type condition_expr: Optional[Expr]
|
958
|
+
|
959
|
+
:rtype: ForceTransitionDefinition
|
960
|
+
|
961
|
+
Example::
|
962
|
+
|
963
|
+
>>> # Force transition from any state to "error" state
|
964
|
+
>>> force_trans = ForceTransitionDefinition(ALL, "error", None, None)
|
965
|
+
>>> str(force_trans)
|
966
|
+
'! * -> error;'
|
967
|
+
"""
|
968
|
+
from_state: Union[str, _StateSingletonMark]
|
969
|
+
to_state: Union[str, _StateSingletonMark]
|
970
|
+
event_id: Optional[ChainID]
|
971
|
+
condition_expr: Optional[Expr]
|
972
|
+
|
973
|
+
def __str__(self):
|
974
|
+
"""
|
975
|
+
Convert the force transition definition to its string representation.
|
976
|
+
|
977
|
+
:return: String representation of the force transition definition
|
978
|
+
:rtype: str
|
979
|
+
"""
|
980
|
+
with io.StringIO() as sf:
|
981
|
+
print('! ', file=sf, end='')
|
982
|
+
print('*' if self.from_state is ALL else self.from_state, file=sf, end='')
|
983
|
+
print(' -> ', file=sf, end='')
|
984
|
+
print('[*]' if self.to_state is EXIT_STATE else self.to_state, file=sf, end='')
|
985
|
+
|
986
|
+
if self.event_id is not None:
|
987
|
+
if not self.event_id.is_absolute and \
|
988
|
+
((self.from_state is ALL and len(self.event_id.path) == 1) or
|
989
|
+
(self.from_state is not ALL and len(self.event_id.path) == 2 and
|
990
|
+
self.event_id.path[0] == self.from_state)):
|
991
|
+
print(f' :: {self.event_id.path[-1]}', file=sf, end='')
|
992
|
+
else:
|
993
|
+
print(f' : {self.event_id}', file=sf, end='')
|
994
|
+
elif self.condition_expr is not None:
|
995
|
+
print(f' : if [{self.condition_expr}]', file=sf, end='')
|
996
|
+
|
997
|
+
print(';', file=sf, end='')
|
998
|
+
return sf.getvalue()
|
999
|
+
|
1000
|
+
|
1001
|
+
@dataclass
|
1002
|
+
class StateDefinition(ASTNode):
|
1003
|
+
"""
|
1004
|
+
Represents a state definition in the state machine DSL.
|
1005
|
+
|
1006
|
+
States are the fundamental building blocks of state machines, containing
|
1007
|
+
transitions, substates, and actions to be performed on entry, during, and exit.
|
1008
|
+
|
1009
|
+
:param name: The name of the state
|
1010
|
+
:type name: str
|
1011
|
+
:param substates: List of nested state definitions
|
1012
|
+
:type substates: List[StateDefinition]
|
1013
|
+
:param transitions: List of transitions from this state
|
1014
|
+
:type transitions: List[TransitionDefinition]
|
1015
|
+
:param enters: List of actions to perform when entering the state
|
1016
|
+
:type enters: List[EnterStatement]
|
1017
|
+
:param durings: List of actions to perform while in the state
|
1018
|
+
:type durings: List[DuringStatement]
|
1019
|
+
:param exits: List of actions to perform when exiting the state
|
1020
|
+
:type exits: List[ExitStatement]
|
1021
|
+
|
1022
|
+
:rtype: StateDefinition
|
1023
|
+
|
1024
|
+
Example::
|
1025
|
+
|
1026
|
+
>>> # Simple state with no internal elements
|
1027
|
+
>>> simple_state = StateDefinition("idle", [], [], [], [], [])
|
1028
|
+
>>> str(simple_state)
|
1029
|
+
'state idle;'
|
1030
|
+
|
1031
|
+
>>> # State with transitions
|
1032
|
+
>>> trans = TransitionDefinition("idle", "active", ChainID(["idle", "start"]), None, [])
|
1033
|
+
>>> state_with_trans = StateDefinition("idle", [], [trans], [], [], [])
|
1034
|
+
"""
|
1035
|
+
name: str
|
1036
|
+
substates: List['StateDefinition'] = None
|
1037
|
+
transitions: List[TransitionDefinition] = None
|
1038
|
+
enters: List['EnterStatement'] = None
|
1039
|
+
durings: List['DuringStatement'] = None
|
1040
|
+
exits: List['ExitStatement'] = None
|
1041
|
+
during_aspects: List['DuringAspectStatement'] = None
|
1042
|
+
force_transitions: List['ForceTransitionDefinition'] = None
|
1043
|
+
|
1044
|
+
def __post_init__(self):
|
1045
|
+
self.substates = self.substates or []
|
1046
|
+
self.transitions = self.transitions or []
|
1047
|
+
self.force_transitions = self.force_transitions or []
|
1048
|
+
self.enters = self.enters or []
|
1049
|
+
self.durings = self.durings or []
|
1050
|
+
self.exits = self.exits or []
|
1051
|
+
self.during_aspects = self.during_aspects or []
|
1052
|
+
|
1053
|
+
def __str__(self):
|
1054
|
+
"""
|
1055
|
+
Convert the state definition to its string representation.
|
1056
|
+
|
1057
|
+
:return: String representation of the state definition
|
1058
|
+
:rtype: str
|
1059
|
+
"""
|
1060
|
+
with io.StringIO() as sf:
|
1061
|
+
if not self.substates and not self.transitions and \
|
1062
|
+
not self.enters and not self.durings and not self.exits and not self.during_aspects:
|
1063
|
+
print(f'state {self.name};', file=sf, end='')
|
1064
|
+
else:
|
1065
|
+
print(f'state {self.name} {{', file=sf)
|
1066
|
+
for enter_item in self.enters:
|
1067
|
+
print(indent(str(enter_item), prefix=' '), file=sf)
|
1068
|
+
for during_item in self.durings:
|
1069
|
+
print(indent(str(during_item), prefix=' '), file=sf)
|
1070
|
+
for exit_item in self.exits:
|
1071
|
+
print(indent(str(exit_item), prefix=' '), file=sf)
|
1072
|
+
for during_aspect_item in self.during_aspects:
|
1073
|
+
print(indent(str(during_aspect_item), prefix=' '), file=sf)
|
1074
|
+
for substate in self.substates:
|
1075
|
+
print(indent(str(substate), prefix=' '), file=sf)
|
1076
|
+
for transition in self.transitions:
|
1077
|
+
print(indent(str(transition), prefix=' '), file=sf)
|
1078
|
+
print(f'}}', file=sf, end='')
|
1079
|
+
|
1080
|
+
return sf.getvalue()
|
1081
|
+
|
1082
|
+
|
1083
|
+
@dataclass
|
1084
|
+
class OperationAssignment(Statement):
|
1085
|
+
"""
|
1086
|
+
Represents an operation assignment in the state machine DSL.
|
1087
|
+
|
1088
|
+
Operation assignments are used to modify variables during transitions or state actions.
|
1089
|
+
|
1090
|
+
:param name: The name of the variable
|
1091
|
+
:type name: str
|
1092
|
+
:param expr: The expression defining the new value
|
1093
|
+
:type expr: Expr
|
1094
|
+
|
1095
|
+
:rtype: OperationAssignment
|
1096
|
+
|
1097
|
+
Example::
|
1098
|
+
|
1099
|
+
>>> op_assign = OperationAssignment("counter", BinaryOp(Name("counter"), "+", Integer("1")))
|
1100
|
+
>>> str(op_assign)
|
1101
|
+
'counter = counter + 1;'
|
1102
|
+
"""
|
1103
|
+
name: str
|
1104
|
+
expr: Expr
|
1105
|
+
|
1106
|
+
def __str__(self):
|
1107
|
+
"""
|
1108
|
+
Convert the operation assignment to its string representation.
|
1109
|
+
|
1110
|
+
:return: String representation of the operation assignment
|
1111
|
+
:rtype: str
|
1112
|
+
"""
|
1113
|
+
return f'{self.name} = {self.expr};'
|
1114
|
+
|
1115
|
+
|
1116
|
+
@dataclass
|
1117
|
+
class StateMachineDSLProgram(ASTNode):
|
1118
|
+
"""
|
1119
|
+
Represents a complete state machine DSL program.
|
1120
|
+
|
1121
|
+
A program consists of variable definitions and a root state that contains
|
1122
|
+
the entire state machine hierarchy.
|
1123
|
+
|
1124
|
+
:param definitions: List of variable definitions
|
1125
|
+
:type definitions: List[DefAssignment]
|
1126
|
+
:param root_state: The root state of the state machine
|
1127
|
+
:type root_state: StateDefinition
|
1128
|
+
|
1129
|
+
:rtype: StateMachineDSLProgram
|
1130
|
+
|
1131
|
+
Example::
|
1132
|
+
|
1133
|
+
>>> def_var = DefAssignment("counter", "int", Integer("0"))
|
1134
|
+
>>> root = StateDefinition("root", [], [], [], [], [])
|
1135
|
+
>>> program = StateMachineDSLProgram([def_var], root)
|
1136
|
+
>>> print(str(program))
|
1137
|
+
def int counter = 0;
|
1138
|
+
state root;
|
1139
|
+
"""
|
1140
|
+
definitions: List[DefAssignment]
|
1141
|
+
root_state: StateDefinition
|
1142
|
+
|
1143
|
+
def __str__(self):
|
1144
|
+
"""
|
1145
|
+
Convert the state machine program to its string representation.
|
1146
|
+
|
1147
|
+
:return: String representation of the state machine program
|
1148
|
+
:rtype: str
|
1149
|
+
"""
|
1150
|
+
with io.StringIO() as f:
|
1151
|
+
for definition in self.definitions:
|
1152
|
+
print(definition, file=f)
|
1153
|
+
print(self.root_state, file=f, end='')
|
1154
|
+
return f.getvalue()
|
1155
|
+
|
1156
|
+
|
1157
|
+
@dataclass
|
1158
|
+
class EnterStatement(ASTNode):
|
1159
|
+
"""
|
1160
|
+
Abstract base class for enter statements in the state machine DSL.
|
1161
|
+
|
1162
|
+
Enter statements define actions to be performed when entering a state.
|
1163
|
+
|
1164
|
+
:rtype: EnterStatement
|
1165
|
+
"""
|
1166
|
+
pass
|
1167
|
+
|
1168
|
+
|
1169
|
+
@dataclass
|
1170
|
+
class EnterOperations(EnterStatement):
|
1171
|
+
"""
|
1172
|
+
Represents a block of operations to perform when entering a state.
|
1173
|
+
|
1174
|
+
:param operations: List of operation assignments
|
1175
|
+
:type operations: List[OperationAssignment]
|
1176
|
+
|
1177
|
+
:rtype: EnterOperations
|
1178
|
+
|
1179
|
+
Example::
|
1180
|
+
|
1181
|
+
>>> op = OperationAssignment("counter", Integer("0"))
|
1182
|
+
>>> enter_ops = EnterOperations([op])
|
1183
|
+
>>> print(str(enter_ops))
|
1184
|
+
enter {
|
1185
|
+
counter = 0;
|
1186
|
+
}
|
1187
|
+
"""
|
1188
|
+
operations: List[OperationAssignment]
|
1189
|
+
name: Optional[str] = None
|
1190
|
+
|
1191
|
+
def __str__(self):
|
1192
|
+
"""
|
1193
|
+
Convert the enter operations to their string representation.
|
1194
|
+
|
1195
|
+
:return: String representation of the enter operations
|
1196
|
+
:rtype: str
|
1197
|
+
"""
|
1198
|
+
with io.StringIO() as f:
|
1199
|
+
if self.name:
|
1200
|
+
print(f'enter {self.name} {{', file=f)
|
1201
|
+
else:
|
1202
|
+
print(f'enter {{', file=f)
|
1203
|
+
for operation in self.operations:
|
1204
|
+
print(f' {operation}', file=f)
|
1205
|
+
print('}', file=f, end='')
|
1206
|
+
return f.getvalue()
|
1207
|
+
|
1208
|
+
|
1209
|
+
@dataclass
|
1210
|
+
class EnterAbstractFunction(EnterStatement):
|
1211
|
+
"""
|
1212
|
+
Represents an abstract function to call when entering a state.
|
1213
|
+
|
1214
|
+
Abstract functions are placeholders for implementation-specific behavior.
|
1215
|
+
|
1216
|
+
:param name: Optional name of the function
|
1217
|
+
:type name: Optional[str]
|
1218
|
+
:param doc: Optional documentation for the function
|
1219
|
+
:type doc: Optional[str]
|
1220
|
+
|
1221
|
+
:rtype: EnterAbstractFunction
|
1222
|
+
|
1223
|
+
Example::
|
1224
|
+
|
1225
|
+
>>> enter_func = EnterAbstractFunction("initState", "Initialize the state")
|
1226
|
+
>>> print(str(enter_func))
|
1227
|
+
enter abstract initState /*
|
1228
|
+
Initialize the state
|
1229
|
+
*/
|
1230
|
+
"""
|
1231
|
+
name: Optional[str]
|
1232
|
+
doc: Optional[str]
|
1233
|
+
|
1234
|
+
def __str__(self):
|
1235
|
+
"""
|
1236
|
+
Convert the enter abstract function to its string representation.
|
1237
|
+
|
1238
|
+
:return: String representation of the enter abstract function
|
1239
|
+
:rtype: str
|
1240
|
+
"""
|
1241
|
+
with io.StringIO() as f:
|
1242
|
+
if self.name:
|
1243
|
+
print(f'enter abstract {self.name}', file=f, end='')
|
1244
|
+
else:
|
1245
|
+
print(f'enter abstract', file=f, end='')
|
1246
|
+
|
1247
|
+
if self.doc is not None:
|
1248
|
+
print(' /*', file=f)
|
1249
|
+
print(indent(self.doc, prefix=' '), file=f)
|
1250
|
+
print('*/', file=f, end='')
|
1251
|
+
else:
|
1252
|
+
print(';', file=f, end='')
|
1253
|
+
|
1254
|
+
return f.getvalue()
|
1255
|
+
|
1256
|
+
|
1257
|
+
@dataclass
|
1258
|
+
class ExitStatement(ASTNode):
|
1259
|
+
"""
|
1260
|
+
Abstract base class for exit statements in the state machine DSL.
|
1261
|
+
|
1262
|
+
Exit statements define actions to be performed when exiting a state.
|
1263
|
+
|
1264
|
+
:rtype: ExitStatement
|
1265
|
+
"""
|
1266
|
+
pass
|
1267
|
+
|
1268
|
+
|
1269
|
+
@dataclass
|
1270
|
+
class ExitOperations(ExitStatement):
|
1271
|
+
"""
|
1272
|
+
Represents a block of operations to perform when exiting a state.
|
1273
|
+
|
1274
|
+
:param operations: List of operation assignments
|
1275
|
+
:type operations: List[OperationAssignment]
|
1276
|
+
|
1277
|
+
:rtype: ExitOperations
|
1278
|
+
|
1279
|
+
Example::
|
1280
|
+
|
1281
|
+
>>> op = OperationAssignment("active", Boolean("false"))
|
1282
|
+
>>> exit_ops = ExitOperations([op])
|
1283
|
+
>>> print(str(exit_ops))
|
1284
|
+
exit {
|
1285
|
+
active = false;
|
1286
|
+
}
|
1287
|
+
"""
|
1288
|
+
operations: List[OperationAssignment]
|
1289
|
+
name: Optional[str] = None
|
1290
|
+
|
1291
|
+
def __str__(self):
|
1292
|
+
"""
|
1293
|
+
Convert the exit operations to their string representation.
|
1294
|
+
|
1295
|
+
:return: String representation of the exit operations
|
1296
|
+
:rtype: str
|
1297
|
+
"""
|
1298
|
+
with io.StringIO() as f:
|
1299
|
+
if self.name:
|
1300
|
+
print(f'exit {self.name} {{', file=f)
|
1301
|
+
else:
|
1302
|
+
print(f'exit {{', file=f)
|
1303
|
+
|
1304
|
+
for operation in self.operations:
|
1305
|
+
print(f' {operation}', file=f)
|
1306
|
+
print('}', file=f, end='')
|
1307
|
+
return f.getvalue()
|
1308
|
+
|
1309
|
+
|
1310
|
+
@dataclass
|
1311
|
+
class ExitAbstractFunction(ExitStatement):
|
1312
|
+
"""
|
1313
|
+
Represents an abstract function to call when exiting a state.
|
1314
|
+
|
1315
|
+
Abstract functions are placeholders for implementation-specific behavior.
|
1316
|
+
|
1317
|
+
:param name: Optional name of the function
|
1318
|
+
:type name: Optional[str]
|
1319
|
+
:param doc: Optional documentation for the function
|
1320
|
+
:type doc: Optional[str]
|
1321
|
+
|
1322
|
+
:rtype: ExitAbstractFunction
|
1323
|
+
|
1324
|
+
Example::
|
1325
|
+
|
1326
|
+
>>> exit_func = ExitAbstractFunction("cleanupState", "Clean up resources")
|
1327
|
+
>>> print(str(exit_func))
|
1328
|
+
exit abstract cleanupState /*
|
1329
|
+
Clean up resources
|
1330
|
+
*/
|
1331
|
+
"""
|
1332
|
+
name: Optional[str]
|
1333
|
+
doc: Optional[str]
|
1334
|
+
|
1335
|
+
def __str__(self):
|
1336
|
+
"""
|
1337
|
+
Convert the exit abstract function to its string representation.
|
1338
|
+
|
1339
|
+
:return: String representation of the exit abstract function
|
1340
|
+
:rtype: str
|
1341
|
+
"""
|
1342
|
+
with io.StringIO() as f:
|
1343
|
+
if self.name:
|
1344
|
+
print(f'exit abstract {self.name}', file=f, end='')
|
1345
|
+
else:
|
1346
|
+
print(f'exit abstract', file=f, end='')
|
1347
|
+
|
1348
|
+
if self.doc is not None:
|
1349
|
+
print(' /*', file=f)
|
1350
|
+
print(indent(self.doc, prefix=' '), file=f)
|
1351
|
+
print('*/', file=f, end='')
|
1352
|
+
else:
|
1353
|
+
print(';', file=f, end='')
|
1354
|
+
|
1355
|
+
return f.getvalue()
|
1356
|
+
|
1357
|
+
|
1358
|
+
@dataclass
|
1359
|
+
class DuringStatement(ASTNode):
|
1360
|
+
"""
|
1361
|
+
Abstract base class for during statements in the state machine DSL.
|
1362
|
+
|
1363
|
+
During statements define actions to be performed while in a state.
|
1364
|
+
|
1365
|
+
:rtype: DuringStatement
|
1366
|
+
"""
|
1367
|
+
pass
|
1368
|
+
|
1369
|
+
|
1370
|
+
@dataclass
|
1371
|
+
class DuringOperations(DuringStatement):
|
1372
|
+
"""
|
1373
|
+
Represents a block of operations to perform while in a state.
|
1374
|
+
|
1375
|
+
:param aspect: Optional aspect name (e.g., "entry", "do", "exit")
|
1376
|
+
:type aspect: Optional[str]
|
1377
|
+
:param operations: List of operation assignments
|
1378
|
+
:type operations: List[OperationAssignment]
|
1379
|
+
|
1380
|
+
:rtype: DuringOperations
|
1381
|
+
|
1382
|
+
Example::
|
1383
|
+
|
1384
|
+
>>> op = OperationAssignment("counter", BinaryOp(Name("counter"), "+", Integer("1")))
|
1385
|
+
>>> during_ops = DuringOperations("do", [op])
|
1386
|
+
>>> print(str(during_ops))
|
1387
|
+
during do {
|
1388
|
+
counter = counter + 1;
|
1389
|
+
}
|
1390
|
+
"""
|
1391
|
+
aspect: Optional[str]
|
1392
|
+
operations: List[OperationAssignment]
|
1393
|
+
name: Optional[str] = None
|
1394
|
+
|
1395
|
+
def __str__(self):
|
1396
|
+
"""
|
1397
|
+
Convert the during operations to their string representation.
|
1398
|
+
|
1399
|
+
:return: String representation of the during operations
|
1400
|
+
:rtype: str
|
1401
|
+
"""
|
1402
|
+
with io.StringIO() as f:
|
1403
|
+
if self.name:
|
1404
|
+
if self.aspect:
|
1405
|
+
print(f'during {self.aspect} {self.name} {{', file=f)
|
1406
|
+
else:
|
1407
|
+
print(f'during {self.name} {{', file=f)
|
1408
|
+
else:
|
1409
|
+
if self.aspect:
|
1410
|
+
print(f'during {self.aspect} {{', file=f)
|
1411
|
+
else:
|
1412
|
+
print(f'during {{', file=f)
|
1413
|
+
for operation in self.operations:
|
1414
|
+
print(f' {operation}', file=f)
|
1415
|
+
print('}', file=f, end='')
|
1416
|
+
return f.getvalue()
|
1417
|
+
|
1418
|
+
|
1419
|
+
@dataclass
|
1420
|
+
class DuringAbstractFunction(DuringStatement):
|
1421
|
+
"""
|
1422
|
+
Represents an abstract function to call while in a state.
|
1423
|
+
|
1424
|
+
Abstract functions are placeholders for implementation-specific behavior.
|
1425
|
+
|
1426
|
+
:param name: Optional name of the function
|
1427
|
+
:type name: Optional[str]
|
1428
|
+
:param aspect: Optional aspect name (e.g., "entry", "do", "exit")
|
1429
|
+
:type aspect: Optional[str]
|
1430
|
+
:param doc: Optional documentation for the function
|
1431
|
+
:type doc: Optional[str]
|
1432
|
+
|
1433
|
+
:rtype: DuringAbstractFunction
|
1434
|
+
|
1435
|
+
Example::
|
1436
|
+
|
1437
|
+
>>> during_func = DuringAbstractFunction("processData", "do", "Process incoming data")
|
1438
|
+
>>> print(str(during_func))
|
1439
|
+
during do abstract processData /*
|
1440
|
+
Process incoming data
|
1441
|
+
*/
|
1442
|
+
"""
|
1443
|
+
name: Optional[str]
|
1444
|
+
aspect: Optional[str]
|
1445
|
+
doc: Optional[str]
|
1446
|
+
|
1447
|
+
def __str__(self):
|
1448
|
+
"""
|
1449
|
+
Convert the during abstract function to its string representation.
|
1450
|
+
|
1451
|
+
:return: String representation of the during abstract function
|
1452
|
+
:rtype: str
|
1453
|
+
"""
|
1454
|
+
with io.StringIO() as f:
|
1455
|
+
if self.name:
|
1456
|
+
if self.aspect:
|
1457
|
+
print(f'during {self.aspect} abstract {self.name}', file=f, end='')
|
1458
|
+
else:
|
1459
|
+
print(f'during abstract {self.name}', file=f, end='')
|
1460
|
+
else:
|
1461
|
+
if self.aspect:
|
1462
|
+
print(f'during {self.aspect} abstract', file=f, end='')
|
1463
|
+
else:
|
1464
|
+
print(f'during abstract', file=f, end='')
|
1465
|
+
|
1466
|
+
if self.doc is not None:
|
1467
|
+
print(' /*', file=f)
|
1468
|
+
print(indent(self.doc, prefix=' '), file=f)
|
1469
|
+
print('*/', file=f, end='')
|
1470
|
+
else:
|
1471
|
+
print(';', file=f, end='')
|
1472
|
+
|
1473
|
+
return f.getvalue()
|
1474
|
+
|
1475
|
+
|
1476
|
+
@dataclass
|
1477
|
+
class DuringAspectStatement(ASTNode):
|
1478
|
+
"""
|
1479
|
+
Abstract base class for during aspect statements in the state machine DSL.
|
1480
|
+
|
1481
|
+
During aspect statements define aspect-specific actions to be performed while in a state.
|
1482
|
+
|
1483
|
+
:rtype: DuringAspectStatement
|
1484
|
+
"""
|
1485
|
+
pass
|
1486
|
+
|
1487
|
+
|
1488
|
+
@dataclass
|
1489
|
+
class DuringAspectOperations(DuringAspectStatement):
|
1490
|
+
"""
|
1491
|
+
Represents a block of aspect-specific operations to perform while in a state.
|
1492
|
+
|
1493
|
+
:param aspect: The aspect name (e.g., "entry", "do", "exit")
|
1494
|
+
:type aspect: str
|
1495
|
+
:param operations: List of operation assignments
|
1496
|
+
:type operations: List[OperationAssignment]
|
1497
|
+
:param name: Optional name for the operation block
|
1498
|
+
:type name: Optional[str]
|
1499
|
+
|
1500
|
+
:rtype: DuringAspectOperations
|
1501
|
+
|
1502
|
+
Example::
|
1503
|
+
|
1504
|
+
>>> op = OperationAssignment("counter", BinaryOp(Name("counter"), "+", Integer("1")))
|
1505
|
+
>>> during_ops = DuringAspectOperations("before", [op])
|
1506
|
+
>>> print(str(during_ops))
|
1507
|
+
>> during before {
|
1508
|
+
counter = counter + 1;
|
1509
|
+
}
|
1510
|
+
"""
|
1511
|
+
aspect: str
|
1512
|
+
operations: List[OperationAssignment]
|
1513
|
+
name: Optional[str] = None
|
1514
|
+
|
1515
|
+
def __str__(self):
|
1516
|
+
"""
|
1517
|
+
Convert the during aspect operations to their string representation.
|
1518
|
+
|
1519
|
+
:return: String representation of the during aspect operations
|
1520
|
+
:rtype: str
|
1521
|
+
"""
|
1522
|
+
with io.StringIO() as f:
|
1523
|
+
if self.name:
|
1524
|
+
print(f'>> during {self.aspect} {self.name} {{', file=f)
|
1525
|
+
else:
|
1526
|
+
print(f'>> during {self.aspect} {{', file=f)
|
1527
|
+
for operation in self.operations:
|
1528
|
+
print(f' {operation}', file=f)
|
1529
|
+
print('}', file=f, end='')
|
1530
|
+
return f.getvalue()
|
1531
|
+
|
1532
|
+
|
1533
|
+
@dataclass
|
1534
|
+
class DuringAspectAbstractFunction(DuringAspectStatement):
|
1535
|
+
"""
|
1536
|
+
Represents an abstract function to call for a specific aspect while in a state.
|
1537
|
+
|
1538
|
+
Abstract functions are placeholders for implementation-specific behavior.
|
1539
|
+
|
1540
|
+
:param name: Optional name of the function
|
1541
|
+
:type name: Optional[str]
|
1542
|
+
:param aspect: The aspect name (e.g., "before", "after")
|
1543
|
+
:type aspect: str
|
1544
|
+
:param doc: Optional documentation for the function
|
1545
|
+
:type doc: Optional[str]
|
1546
|
+
|
1547
|
+
:rtype: DuringAspectAbstractFunction
|
1548
|
+
|
1549
|
+
Example::
|
1550
|
+
|
1551
|
+
>>> during_func = DuringAspectAbstractFunction("processData", "before", "Process incoming data")
|
1552
|
+
>>> print(str(during_func))
|
1553
|
+
>> during before abstract processData /*
|
1554
|
+
Process incoming data
|
1555
|
+
*/
|
1556
|
+
"""
|
1557
|
+
name: Optional[str]
|
1558
|
+
aspect: str
|
1559
|
+
doc: Optional[str]
|
1560
|
+
|
1561
|
+
def __str__(self):
|
1562
|
+
"""
|
1563
|
+
Convert the during aspect abstract function to its string representation.
|
1564
|
+
|
1565
|
+
:return: String representation of the during aspect abstract function
|
1566
|
+
:rtype: str
|
1567
|
+
"""
|
1568
|
+
with io.StringIO() as f:
|
1569
|
+
if self.name:
|
1570
|
+
print(f'>> during {self.aspect} abstract {self.name}', file=f, end='')
|
1571
|
+
else:
|
1572
|
+
print(f'>> during {self.aspect} abstract', file=f, end='')
|
1573
|
+
|
1574
|
+
if self.doc is not None:
|
1575
|
+
print(' /*', file=f)
|
1576
|
+
print(indent(self.doc, prefix=' '), file=f)
|
1577
|
+
print('*/', file=f, end='')
|
1578
|
+
else:
|
1579
|
+
print(';', file=f, end='')
|
1580
|
+
|
1581
|
+
return f.getvalue()
|