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.
Files changed (46) hide show
  1. pyfcstm/__init__.py +0 -0
  2. pyfcstm/__main__.py +4 -0
  3. pyfcstm/config/__init__.py +0 -0
  4. pyfcstm/config/meta.py +20 -0
  5. pyfcstm/dsl/__init__.py +6 -0
  6. pyfcstm/dsl/error.py +226 -0
  7. pyfcstm/dsl/grammar/Grammar.g4 +190 -0
  8. pyfcstm/dsl/grammar/Grammar.interp +168 -0
  9. pyfcstm/dsl/grammar/Grammar.tokens +118 -0
  10. pyfcstm/dsl/grammar/GrammarLexer.interp +214 -0
  11. pyfcstm/dsl/grammar/GrammarLexer.py +523 -0
  12. pyfcstm/dsl/grammar/GrammarLexer.tokens +118 -0
  13. pyfcstm/dsl/grammar/GrammarListener.py +521 -0
  14. pyfcstm/dsl/grammar/GrammarParser.py +4373 -0
  15. pyfcstm/dsl/grammar/__init__.py +3 -0
  16. pyfcstm/dsl/listener.py +440 -0
  17. pyfcstm/dsl/node.py +1581 -0
  18. pyfcstm/dsl/parse.py +155 -0
  19. pyfcstm/entry/__init__.py +1 -0
  20. pyfcstm/entry/base.py +126 -0
  21. pyfcstm/entry/cli.py +12 -0
  22. pyfcstm/entry/dispatch.py +46 -0
  23. pyfcstm/entry/generate.py +83 -0
  24. pyfcstm/entry/plantuml.py +67 -0
  25. pyfcstm/model/__init__.py +3 -0
  26. pyfcstm/model/base.py +51 -0
  27. pyfcstm/model/expr.py +764 -0
  28. pyfcstm/model/model.py +1392 -0
  29. pyfcstm/render/__init__.py +3 -0
  30. pyfcstm/render/env.py +36 -0
  31. pyfcstm/render/expr.py +180 -0
  32. pyfcstm/render/func.py +77 -0
  33. pyfcstm/render/render.py +279 -0
  34. pyfcstm/utils/__init__.py +6 -0
  35. pyfcstm/utils/binary.py +38 -0
  36. pyfcstm/utils/doc.py +64 -0
  37. pyfcstm/utils/jinja2.py +121 -0
  38. pyfcstm/utils/json.py +125 -0
  39. pyfcstm/utils/text.py +91 -0
  40. pyfcstm/utils/validate.py +102 -0
  41. pyfcstm-0.0.1.dist-info/LICENSE +165 -0
  42. pyfcstm-0.0.1.dist-info/METADATA +205 -0
  43. pyfcstm-0.0.1.dist-info/RECORD +46 -0
  44. pyfcstm-0.0.1.dist-info/WHEEL +5 -0
  45. pyfcstm-0.0.1.dist-info/entry_points.txt +2 -0
  46. 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()