statable 3.1.0__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 (117) hide show
  1. codegen/__init__.py +38 -0
  2. codegen/c_code_generator.py +1752 -0
  3. codegen/code_merger.py +371 -0
  4. codegen/code_templates.py +509 -0
  5. codegen/config.py +167 -0
  6. codegen/enum_generator.py +377 -0
  7. codegen/event_queue_generator.py +303 -0
  8. codegen/interrupt_generator.py +345 -0
  9. codegen/naming_convention.py +171 -0
  10. codegen/osal_generator.py +282 -0
  11. codegen/role_function_generator.py +1244 -0
  12. codegen/sample_data.py +269 -0
  13. codegen/state_actions_generator.py +395 -0
  14. codegen/struct_generator.py +721 -0
  15. codegen/timer_generator.py +299 -0
  16. codegen/transition_generator.py +907 -0
  17. codegen/type_mapper.py +69 -0
  18. codegen/validate/__init__.py +38 -0
  19. codegen/validate/change_actions.py +90 -0
  20. codegen/validate/change_applier.py +416 -0
  21. codegen/validate/clipboard_manager.py +47 -0
  22. codegen/validate/data/__init__.py +30 -0
  23. codegen/validate/data/action_definitions.py +200 -0
  24. codegen/validate/data/keywords.py +46 -0
  25. codegen/validate/data/prompt_templates.py +176 -0
  26. codegen/validate/data/validation_rules.py +205 -0
  27. codegen/validate/items/__init__.py +30 -0
  28. codegen/validate/items/base_validator.py +25 -0
  29. codegen/validate/items/cell_validator.py +275 -0
  30. codegen/validate/items/custom_type_validator.py +75 -0
  31. codegen/validate/items/event_validator.py +78 -0
  32. codegen/validate/items/flag_validator.py +74 -0
  33. codegen/validate/items/interrupt_validator.py +75 -0
  34. codegen/validate/items/queue_validator.py +73 -0
  35. codegen/validate/items/role_function_validator.py +175 -0
  36. codegen/validate/items/state_validator.py +88 -0
  37. codegen/validate/items/timer_validator.py +120 -0
  38. codegen/validate/items/transition_validator.py +113 -0
  39. codegen/validate/items/variable_validator.py +132 -0
  40. codegen/validate/logger.py +52 -0
  41. codegen/validate/models.py +177 -0
  42. codegen/validate/prompt_generator.py +724 -0
  43. codegen/validate/response_parser.py +154 -0
  44. codegen/validate/response_validator.py +275 -0
  45. codegen/validate/validation_dialog.py +322 -0
  46. codegen/validate/validator.py +94 -0
  47. codegen/variable_generator.py +419 -0
  48. statable/__init__.py +71 -0
  49. statable/__main__.py +49 -0
  50. statable/cli.py +251 -0
  51. statable/global_defs.py +256 -0
  52. statable/mermaid_gen.py +144 -0
  53. statable/model.py +426 -0
  54. statable/parser.py +64 -0
  55. statable/sample_data.py +435 -0
  56. statable/shared/__init__.py +24 -0
  57. statable/shared/condition_library.py +63 -0
  58. statable/shared/literal_library.py +66 -0
  59. statable/shared/role_function_library.py +150 -0
  60. statable/state_machine.py +121 -0
  61. statable/xml_io.py +1164 -0
  62. statable-3.1.0.dist-info/METADATA +151 -0
  63. statable-3.1.0.dist-info/RECORD +117 -0
  64. statable-3.1.0.dist-info/WHEEL +5 -0
  65. statable-3.1.0.dist-info/entry_points.txt +2 -0
  66. statable-3.1.0.dist-info/top_level.txt +3 -0
  67. statable_gui/__init__.py +23 -0
  68. statable_gui/action_edit_dialog.py +214 -0
  69. statable_gui/code_generation_dialog.py +486 -0
  70. statable_gui/code_generation_settings_dialog.py +522 -0
  71. statable_gui/common_widgets.py +537 -0
  72. statable_gui/condition_builder_dialog.py +500 -0
  73. statable_gui/condition_edit_dialog.py +123 -0
  74. statable_gui/condition_template_dialog.py +58 -0
  75. statable_gui/config.py +21 -0
  76. statable_gui/dialogs.py +439 -0
  77. statable_gui/event_definition_dialog.py +605 -0
  78. statable_gui/event_delivery_settings_dialog.py +191 -0
  79. statable_gui/event_queue_dialog.py +245 -0
  80. statable_gui/global_defs.py +167 -0
  81. statable_gui/global_defs_dialog.py +796 -0
  82. statable_gui/i18n/__init__.py +118 -0
  83. statable_gui/interrupt_handler_edit_dialog.py +810 -0
  84. statable_gui/layer_settings_dialog.py +180 -0
  85. statable_gui/libcntrl/__init__.py +23 -0
  86. statable_gui/libcntrl/condition_library.py +13 -0
  87. statable_gui/libcntrl/literal_library.py +13 -0
  88. statable_gui/libcntrl/literal_management_dialog.py +185 -0
  89. statable_gui/libcntrl/role_function_edit_dialog.py +145 -0
  90. statable_gui/libcntrl/role_function_library.py +13 -0
  91. statable_gui/literal_definition_dialog.py +62 -0
  92. statable_gui/logger.py +83 -0
  93. statable_gui/main_window.py +1380 -0
  94. statable_gui/matrix_table.py +413 -0
  95. statable_gui/preference_keys.py +18 -0
  96. statable_gui/preferences.py +74 -0
  97. statable_gui/role_function_dialog.py +244 -0
  98. statable_gui/sample_data.py +11 -0
  99. statable_gui/state_actions_dialog.py +398 -0
  100. statable_gui/symbol_picker.py +170 -0
  101. statable_gui/traceball.py +37 -0
  102. statable_gui/transition_editor_direct/__init__.py +15 -0
  103. statable_gui/transition_editor_direct/actions_tab.py +856 -0
  104. statable_gui/transition_editor_direct/code_widget.py +344 -0
  105. statable_gui/transition_editor_direct/coverage_analyzer.py +163 -0
  106. statable_gui/transition_editor_direct/dialog.py +335 -0
  107. statable_gui/transition_editor_direct/draft.py +270 -0
  108. statable_gui/transition_editor_direct/edit_dialogs.py +160 -0
  109. statable_gui/transition_editor_direct/overview_tab.py +173 -0
  110. statable_gui/transition_editor_direct/palette_widget.py +233 -0
  111. statable_gui/transition_editor_direct/relations_edit_dialog.py +156 -0
  112. statable_gui/transition_editor_direct/relations_tab.py +230 -0
  113. statable_gui/transition_editor_direct/system_global_dialog.py +52 -0
  114. statable_gui/transition_editor_direct/transition_actions_dialog.py +109 -0
  115. statable_gui/transition_editor_direct/transitions_tab.py +620 -0
  116. statable_gui/validation_dialog.py +285 -0
  117. statable_gui/widgets.py +1315 -0
@@ -0,0 +1,435 @@
1
+ """Sample data generation (business logic layer).
2
+
3
+ Version History
4
+ ---------------
5
+ v2.2 - entry / exit: List[str]; cell actions and relations added.
6
+ v3.8 - RoleFunction.used_global_vars / used_events / used_literals
7
+ are available (optional).
8
+ v3.12 - Explicit namespace="Application" added to all sample role
9
+ functions. Previously they all used namespace="", which
10
+ meant the namespace combo box in the GUI had no candidates
11
+ coming from the role function list.
12
+
13
+ Design note
14
+ -----------
15
+ In StaTable the role-function namespace must match the
16
+ *layer name* (tab name) for the declaration to be emitted
17
+ by codegen/role_function_generator.py. Feature-group style
18
+ namespaces ("Sensor", "Error", ...) are not supported by
19
+ the current layer model. See SPEC_OVERVIEW §3.2.7
20
+ "Design Principle (v2.3)".
21
+
22
+ The sample below therefore uses namespace="Application",
23
+ which matches the initial tab name created by MainWindow.
24
+ """
25
+
26
+ from .model import (
27
+ State, Event, Transition, StateType, EventKind,
28
+ RoleFunction, EventDeliveryType, EventSourceLayer,
29
+ ActionStep, TransitionRelation,
30
+ )
31
+ from .state_machine import StateMachine
32
+ from .global_defs import (
33
+ GlobalDefinitions, SystemVariable, EventFlag,
34
+ InterruptHandlerDef, InterruptAction,
35
+ DevicePlaceholderDef, TimerBaseDef, TimerDerivedDef,
36
+ EventQueueDef, CustomTypeDef, StructMemberDef,
37
+ )
38
+
39
+
40
+ # [v3.12] Layer name used by the sample state machine.
41
+ # Must match the initial tab name created by MainWindow.
42
+ _SAMPLE_LAYER = "Application"
43
+
44
+
45
+ def create_sample_state_machine() -> StateMachine:
46
+ sm = StateMachine()
47
+ sm.layer_name = _SAMPLE_LAYER
48
+
49
+ # ==================================================================
50
+ # States (v2.2: entry / exit are List[str])
51
+ # ==================================================================
52
+ sm.add_state(State("Idle",
53
+ entry=["Idle_Entry"],
54
+ description="Initial state"))
55
+ sm.add_state(State("Active",
56
+ do="Active_Do",
57
+ description="Running"))
58
+ sm.add_state(State("Error",
59
+ entry=["Error_Entry"],
60
+ exit=["Error_Exit"],
61
+ description="Error state"))
62
+ sm.add_state(State("Halt",
63
+ type=StateType.FINAL,
64
+ description="Stopped state"))
65
+
66
+ sm.set_initial("Idle")
67
+
68
+ # ==================================================================
69
+ # Events
70
+ # ==================================================================
71
+ sm.add_event(Event(name="START", id=1, description="Startup request",
72
+ delivery_type=EventDeliveryType.DIRECT,
73
+ source_layer=EventSourceLayer.MIDDLEWARE,
74
+ title="Startup request"))
75
+ sm.add_event(Event(name="STOP", id=2, description="Stop request",
76
+ delivery_type=EventDeliveryType.DIRECT,
77
+ source_layer=EventSourceLayer.MIDDLEWARE,
78
+ title="Stop request"))
79
+ sm.add_event(Event(name="ERROR", id=3, description="Error notification",
80
+ delivery_type=EventDeliveryType.QUEUE,
81
+ source_layer=EventSourceLayer.DRIVER,
82
+ data_type="uint8_t", data_name="err_code",
83
+ title="Error notification"))
84
+ sm.add_event(Event(name="TIMER0_OVERFLOW", id=4,
85
+ description="1ms timer expired",
86
+ delivery_type=EventDeliveryType.DOUBLE,
87
+ source_layer=EventSourceLayer.DRIVER,
88
+ title="Timer expired"))
89
+ sm.add_event(Event(name="", id=0, kind=EventKind.SIGNAL,
90
+ description="Completion transition",
91
+ delivery_type=EventDeliveryType.DIRECT,
92
+ source_layer=EventSourceLayer.MIDDLEWARE,
93
+ title="Completion transition"))
94
+
95
+ # ==================================================================
96
+ # Role functions
97
+ #
98
+ # [v3.12] All sample functions belong to the Application layer.
99
+ # namespace is now explicitly set so that the namespace
100
+ # combo box in RoleFunctionDialog / SettingsPanel has
101
+ # at least one candidate even before the user adds
102
+ # additional tabs.
103
+ #
104
+ # Call sites (pre_actions / entry / exit / ActionStep.
105
+ # role_function) keep using the bare names below; the
106
+ # code generator resolves them via the bare-name
107
+ # fallback in _get_call_sites_for_func().
108
+ # ==================================================================
109
+ sm.add_role_function(RoleFunction(
110
+ name="Sensor_Init",
111
+ namespace=_SAMPLE_LAYER,
112
+ description="Sensor initialization",
113
+ return_type="int",
114
+ arg1_type="uint8_t", arg1_name="channel",
115
+ arg2_type="uint32_t", arg2_name="timeout_ms",
116
+ title="Sensor initialization",
117
+ ))
118
+ sm.add_role_function(RoleFunction(
119
+ name="Error_Log",
120
+ namespace=_SAMPLE_LAYER,
121
+ description="Error log output",
122
+ return_type="void",
123
+ arg1_type="int", arg1_name="err_code",
124
+ arg2_type="int", arg2_name="level",
125
+ title="Error log output",
126
+ ))
127
+ sm.add_role_function(RoleFunction(
128
+ name="Start_Init",
129
+ namespace=_SAMPLE_LAYER,
130
+ description="Startup initialization",
131
+ return_type="void",
132
+ title="Startup initialization",
133
+ ))
134
+ sm.add_role_function(RoleFunction(
135
+ name="Stop_Cleanup",
136
+ namespace=_SAMPLE_LAYER,
137
+ description="Stop cleanup",
138
+ return_type="void",
139
+ title="Stop cleanup",
140
+ ))
141
+ sm.add_role_function(RoleFunction(
142
+ name="Retry_Increment",
143
+ namespace=_SAMPLE_LAYER,
144
+ description="Increment retry counter",
145
+ return_type="void",
146
+ title="Increment retry counter",
147
+ ))
148
+ sm.add_role_function(RoleFunction(
149
+ name="Ignore_Event",
150
+ namespace=_SAMPLE_LAYER,
151
+ description="Ignore the current event",
152
+ return_type="void",
153
+ title="Ignore the current event",
154
+ ))
155
+ sm.add_role_function(RoleFunction(
156
+ name="Idle_Entry",
157
+ namespace=_SAMPLE_LAYER,
158
+ description="Idle state entry action",
159
+ return_type="void",
160
+ title="Idle entry",
161
+ ))
162
+ sm.add_role_function(RoleFunction(
163
+ name="Error_Entry",
164
+ namespace=_SAMPLE_LAYER,
165
+ description="Error state entry action",
166
+ return_type="void",
167
+ title="Error entry",
168
+ ))
169
+ sm.add_role_function(RoleFunction(
170
+ name="Error_Exit",
171
+ namespace=_SAMPLE_LAYER,
172
+ description="Error state exit action",
173
+ return_type="void",
174
+ title="Error exit",
175
+ ))
176
+
177
+ # ==================================================================
178
+ # Transitions
179
+ # ==================================================================
180
+
181
+ # Idle --START--> Active
182
+ sm.add_transition(Transition(
183
+ source="Idle",
184
+ event="START",
185
+ condition="",
186
+ pre_actions=["Start_Init"],
187
+ target="Active",
188
+ has_else=False,
189
+ early_return=True,
190
+ label="T1",
191
+ title="Startup",
192
+ ))
193
+
194
+ # Active --STOP--> Idle
195
+ sm.add_transition(Transition(
196
+ source="Active",
197
+ event="STOP",
198
+ condition="",
199
+ pre_actions=["Stop_Cleanup"],
200
+ target="Idle",
201
+ has_else=False,
202
+ early_return=True,
203
+ label="T1",
204
+ title="Stop",
205
+ ))
206
+
207
+ # Active --ERROR--> Error (err_code != 0)
208
+ sm.add_transition(Transition(
209
+ source="Active",
210
+ event="ERROR",
211
+ condition="err_code != 0",
212
+ pre_actions=["Error_Log"],
213
+ target="Error",
214
+ has_else=False,
215
+ early_return=True,
216
+ label="T1",
217
+ title="To error",
218
+ ))
219
+
220
+ # Active --ERROR--> Active (err_code == 0) [Tentative]
221
+ sm.add_transition(Transition(
222
+ source="Active",
223
+ event="ERROR",
224
+ condition="err_code == 0",
225
+ pre_actions=["Ignore_Event"],
226
+ target="Active",
227
+ has_else=False,
228
+ early_return=False,
229
+ label="T2",
230
+ title="Ignore",
231
+ ))
232
+
233
+ # Error --(Completion)--> Active (retry)
234
+ sm.add_transition(Transition(
235
+ source="Error",
236
+ event="",
237
+ condition="retry_count < 3",
238
+ pre_actions=["Retry_Increment"],
239
+ target="Active",
240
+ has_else=False,
241
+ early_return=True,
242
+ label="T1",
243
+ title="Retry",
244
+ ))
245
+
246
+ # Error --(Completion)--> Halt (give up)
247
+ sm.add_transition(Transition(
248
+ source="Error",
249
+ event="",
250
+ condition="retry_count >= 3",
251
+ pre_actions=[],
252
+ target="Halt",
253
+ has_else=False,
254
+ early_return=True,
255
+ label="T2",
256
+ title="Give up",
257
+ ))
258
+
259
+ # ==================================================================
260
+ # Cell actions (v2.2)
261
+ # ==================================================================
262
+
263
+ # Idle + START: sensor init before condition check
264
+ sm.set_actions_for_cell("Idle", "START", [
265
+ ActionStep(role_function="Sensor_Init",
266
+ trigger="before_transitions",
267
+ title="Initialize sensor on startup"),
268
+ ])
269
+
270
+ # Active + ERROR: log before and after
271
+ sm.set_actions_for_cell("Active", "ERROR", [
272
+ ActionStep(role_function="Error_Log",
273
+ trigger="before_transitions",
274
+ title="Log before evaluating error"),
275
+ ActionStep(role_function="Error_Log",
276
+ trigger="after_transitions",
277
+ title="Log after evaluating error"),
278
+ ])
279
+
280
+ # Error + (Completion): log before retry evaluation
281
+ sm.set_actions_for_cell("Error", "", [
282
+ ActionStep(role_function="Error_Log",
283
+ trigger="before_transitions",
284
+ title="Log before retry evaluation"),
285
+ ])
286
+
287
+ # Active + STOP: cleanup after stop
288
+ sm.set_actions_for_cell("Active", "STOP", [
289
+ ActionStep(role_function="Stop_Cleanup",
290
+ trigger="after_transitions",
291
+ title="Cleanup after stop"),
292
+ ])
293
+
294
+ # ==================================================================
295
+ # Cell relations (v2.2)
296
+ # ==================================================================
297
+
298
+ # Active + ERROR: T1 and T2 evaluated sequentially
299
+ sm.set_relations_for_cell("Active", "ERROR", [
300
+ TransitionRelation(
301
+ kind="sequential",
302
+ members=["T1", "T2"],
303
+ shared_condition="",
304
+ note="T1 (error) first, then T2 (ignore)",
305
+ ),
306
+ ])
307
+
308
+ # Error + (Completion): T1 and T2 mutually exclusive
309
+ sm.set_relations_for_cell("Error", "", [
310
+ TransitionRelation(
311
+ kind="exclusive",
312
+ members=["T1", "T2"],
313
+ shared_condition="",
314
+ note="At most one fires (retry or halt)",
315
+ ),
316
+ ])
317
+
318
+ return sm
319
+
320
+
321
+ def create_sample_global_defs() -> GlobalDefinitions:
322
+ defs = GlobalDefinitions()
323
+
324
+ defs.custom_types.append(CustomTypeDef(
325
+ name="SystemStatus_t",
326
+ description="System status struct",
327
+ title="System status",
328
+ members=[
329
+ StructMemberDef(name="power_on", data_type="uint8_t", bit_width=1,
330
+ description="Power ON flag", title="Power ON"),
331
+ StructMemberDef(name="mode", data_type="uint8_t", bit_width=3,
332
+ description="Mode", title="Mode"),
333
+ StructMemberDef(name="status", data_type="uint8_t", bit_width=4,
334
+ description="Status", title="Status"),
335
+ ]
336
+ ))
337
+
338
+ defs.custom_types.append(CustomTypeDef(
339
+ name="DataPacket_t",
340
+ description="Data packet struct",
341
+ title="Data packet",
342
+ members=[
343
+ StructMemberDef(name="data", data_type="uint8_t",
344
+ description="Data array", title="Data array",
345
+ array_size=64),
346
+ StructMemberDef(name="length", data_type="uint16_t",
347
+ description="Data length", title="Data length"),
348
+ ]
349
+ ))
350
+
351
+ defs.variables.append(SystemVariable(
352
+ name="battery_voltage", type="uint16_t", unit="mV",
353
+ default_value="0", group="Power",
354
+ description="Battery voltage", title="Battery voltage"))
355
+ defs.variables.append(SystemVariable(
356
+ name="payload", type="uint8_t", unit="bytes",
357
+ default_value="", group="Communication",
358
+ description="Receive data buffer", title="Receive buffer",
359
+ array_size=64))
360
+ defs.variables.append(SystemVariable(
361
+ name="system_status", type="SystemStatus_t",
362
+ group="System", description="System status",
363
+ title="System status"))
364
+ defs.variables.append(SystemVariable(
365
+ name="retry_count", type="uint8_t", unit="",
366
+ default_value="0", group="Error",
367
+ description="Retry counter", title="Retry counter"))
368
+
369
+ defs.flags.append(EventFlag(
370
+ name="EVT_START_REQ", min_value=0, max_value=1,
371
+ group="SystemEvents", description="Startup request",
372
+ title="Startup request flag"))
373
+ defs.flags.append(EventFlag(
374
+ name="EVT_MODE", min_value=0, max_value=3,
375
+ group="SystemEvents", description="Mode indication",
376
+ title="Mode indication flag"))
377
+
378
+ defs.interrupts.append(InterruptHandlerDef(
379
+ name="TIMER0", description="1ms period timer",
380
+ event_names=["TIMER0_OVERFLOW"], is_timer=True,
381
+ actions=[
382
+ InterruptAction(condition="g_tick_100ms >= 5",
383
+ action="StateMachine_EnqueueEvent(EVENT_TICK);"),
384
+ InterruptAction(condition="",
385
+ action="g_system_tick++;\nUpdateDerivedTimers();"),
386
+ ],
387
+ title="Timer0Interrupt"
388
+ ))
389
+
390
+ defs.placeholders.append(DevicePlaceholderDef(
391
+ name="TIMER0_IRQ_FLAG",
392
+ description="Timer0 interrupt flag clear register",
393
+ title="Timer0 IRQFlag"))
394
+
395
+ defs.timer_base = TimerBaseDef(
396
+ variable_name="g_system_tick", unit="1ms",
397
+ data_type="volatile uint32_t",
398
+ derived=[
399
+ TimerDerivedDef(period_name="10ms", multiplier=10,
400
+ variable_name="g_tick_10ms",
401
+ data_type="uint8_t", title="10msTimer"),
402
+ TimerDerivedDef(period_name="100ms", multiplier=100,
403
+ variable_name="g_tick_100ms",
404
+ data_type="uint8_t", title="100msTimer"),
405
+ TimerDerivedDef(period_name="1s", multiplier=1000,
406
+ variable_name="g_tick_1s",
407
+ data_type="uint16_t", title="1sTimer"),
408
+ ],
409
+ title="System timer base",
410
+ interrupt_name="TIMER0"
411
+ )
412
+
413
+ defs.extra_timers.append(TimerBaseDef(
414
+ variable_name="g_high_speed_tick", unit="100us",
415
+ data_type="volatile uint32_t",
416
+ derived=[
417
+ TimerDerivedDef(period_name="1ms", multiplier=10,
418
+ variable_name="g_hs_1ms",
419
+ data_type="uint16_t",
420
+ title="High-speed 1ms timer"),
421
+ ],
422
+ title="High-speed timer base",
423
+ interrupt_name="TIMER1"
424
+ ))
425
+
426
+ defs.event_queues.append(EventQueueDef(
427
+ name="UartQueue", size=16, element_type="uint8_t",
428
+ event_ids=["ERROR"], priority_enabled=False,
429
+ interrupt_safe=True, rtos_enabled=False,
430
+ description="UART receive queue", title="UART receive queue"
431
+ ))
432
+
433
+ defs.add_timer_variables()
434
+
435
+ return defs
@@ -0,0 +1,24 @@
1
+ # statable/shared/__init__.py
2
+ """
3
+ Shared project libraries.
4
+
5
+ These modules have no PySide6 dependency and can be used from the SDK
6
+ without installing the GUI extras (statable[gui]).
7
+
8
+ [v3.0 / G-1a]
9
+ Moved from statable_gui.libcntrl to allow SDK-only installs.
10
+ Backward-compat shims remain at statable_gui.libcntrl.* for a while.
11
+ """
12
+
13
+ from .role_function_library import RoleFunctionLibrary, RoleFunction
14
+ from .condition_library import ConditionLibrary, ConditionTemplate
15
+ from .literal_library import LiteralLibrary, LiteralDefinition
16
+
17
+ __all__ = [
18
+ "RoleFunctionLibrary",
19
+ "RoleFunction",
20
+ "ConditionLibrary",
21
+ "ConditionTemplate",
22
+ "LiteralLibrary",
23
+ "LiteralDefinition",
24
+ ]
@@ -0,0 +1,63 @@
1
+ # statable/shared/condition_library.py
2
+ """\nShared transition condition library\n"""
3
+
4
+ from dataclasses import dataclass
5
+ from typing import Dict, List
6
+
7
+
8
+ @dataclass
9
+ class ConditionTemplate:
10
+ """Shared transition condition template"""
11
+ name: str # Template name
12
+ condition: str # Condition expression (including literals)
13
+ description: str = ""
14
+
15
+ def to_dict(self) -> dict:
16
+ return {
17
+ 'name': self.name,
18
+ 'condition': self.condition,
19
+ 'description': self.description,
20
+ }
21
+
22
+ @classmethod
23
+ def from_dict(cls, data: dict) -> 'ConditionTemplate':
24
+ return cls(
25
+ name=data.get('name', ''),
26
+ condition=data.get('condition', ''),
27
+ description=data.get('description', ''),
28
+ )
29
+
30
+
31
+ class ConditionLibrary:
32
+ """Transition condition library shared"""
33
+
34
+ def __init__(self):
35
+ self.condition_templates: Dict[str, ConditionTemplate] = {}
36
+
37
+ def add(self, ct: ConditionTemplate):
38
+ if ct.name in self.condition_templates:
39
+ raise ValueError(f"Condition template '{ct.name}' already exists")
40
+ self.condition_templates[ct.name] = ct
41
+
42
+ def remove(self, name: str):
43
+ if name in self.condition_templates:
44
+ del self.condition_templates[name]
45
+
46
+ def get(self, name: str) -> ConditionTemplate:
47
+ return self.condition_templates.get(name)
48
+
49
+ def list_all(self) -> List[ConditionTemplate]:
50
+ return list(self.condition_templates.values())
51
+
52
+ def to_dict(self) -> dict:
53
+ return {
54
+ 'condition_templates': [ct.to_dict() for ct in self.condition_templates.values()]
55
+ }
56
+
57
+ @classmethod
58
+ def from_dict(cls, data: dict) -> 'ConditionLibrary':
59
+ lib = cls()
60
+ for item in data.get('condition_templates', []):
61
+ ct = ConditionTemplate.from_dict(item)
62
+ lib.add(ct)
63
+ return lib
@@ -0,0 +1,66 @@
1
+ # statable/shared/literal_library.py
2
+ """\nShared literal library\n"""
3
+
4
+ from dataclasses import dataclass
5
+ from typing import Dict, List
6
+
7
+
8
+ @dataclass
9
+ class LiteralDefinition:
10
+ """Shared literal definition"""
11
+ name: str # Unique literal name
12
+ value: str # Value
13
+ literal_type: str = "int" # int / float / string / bool
14
+ description: str = ""
15
+
16
+ def to_dict(self) -> dict:
17
+ return {
18
+ 'name': self.name,
19
+ 'value': self.value,
20
+ 'literal_type': self.literal_type,
21
+ 'description': self.description,
22
+ }
23
+
24
+ @classmethod
25
+ def from_dict(cls, data: dict) -> 'LiteralDefinition':
26
+ return cls(
27
+ name=data.get('name', ''),
28
+ value=data.get('value', ''),
29
+ literal_type=data.get('literal_type', 'int'),
30
+ description=data.get('description', ''),
31
+ )
32
+
33
+
34
+ class LiteralLibrary:
35
+ """Literal library shared across project"""
36
+
37
+ def __init__(self):
38
+ self.literals: Dict[str, LiteralDefinition] = {}
39
+
40
+ def add(self, lit: LiteralDefinition):
41
+ if lit.name in self.literals:
42
+ raise ValueError(f"Literal '{lit.name}' already exists")
43
+ self.literals[lit.name] = lit
44
+
45
+ def remove(self, name: str):
46
+ if name in self.literals:
47
+ del self.literals[name]
48
+
49
+ def get(self, name: str) -> LiteralDefinition:
50
+ return self.literals.get(name)
51
+
52
+ def list_all(self) -> List[LiteralDefinition]:
53
+ return list(self.literals.values())
54
+
55
+ def to_dict(self) -> dict:
56
+ return {
57
+ 'literals': [lit.to_dict() for lit in self.literals.values()]
58
+ }
59
+
60
+ @classmethod
61
+ def from_dict(cls, data: dict) -> 'LiteralLibrary':
62
+ lib = cls()
63
+ for item in data.get('literals', []):
64
+ lit = LiteralDefinition.from_dict(item)
65
+ lib.add(lit)
66
+ return lib