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.
- codegen/__init__.py +38 -0
- codegen/c_code_generator.py +1752 -0
- codegen/code_merger.py +371 -0
- codegen/code_templates.py +509 -0
- codegen/config.py +167 -0
- codegen/enum_generator.py +377 -0
- codegen/event_queue_generator.py +303 -0
- codegen/interrupt_generator.py +345 -0
- codegen/naming_convention.py +171 -0
- codegen/osal_generator.py +282 -0
- codegen/role_function_generator.py +1244 -0
- codegen/sample_data.py +269 -0
- codegen/state_actions_generator.py +395 -0
- codegen/struct_generator.py +721 -0
- codegen/timer_generator.py +299 -0
- codegen/transition_generator.py +907 -0
- codegen/type_mapper.py +69 -0
- codegen/validate/__init__.py +38 -0
- codegen/validate/change_actions.py +90 -0
- codegen/validate/change_applier.py +416 -0
- codegen/validate/clipboard_manager.py +47 -0
- codegen/validate/data/__init__.py +30 -0
- codegen/validate/data/action_definitions.py +200 -0
- codegen/validate/data/keywords.py +46 -0
- codegen/validate/data/prompt_templates.py +176 -0
- codegen/validate/data/validation_rules.py +205 -0
- codegen/validate/items/__init__.py +30 -0
- codegen/validate/items/base_validator.py +25 -0
- codegen/validate/items/cell_validator.py +275 -0
- codegen/validate/items/custom_type_validator.py +75 -0
- codegen/validate/items/event_validator.py +78 -0
- codegen/validate/items/flag_validator.py +74 -0
- codegen/validate/items/interrupt_validator.py +75 -0
- codegen/validate/items/queue_validator.py +73 -0
- codegen/validate/items/role_function_validator.py +175 -0
- codegen/validate/items/state_validator.py +88 -0
- codegen/validate/items/timer_validator.py +120 -0
- codegen/validate/items/transition_validator.py +113 -0
- codegen/validate/items/variable_validator.py +132 -0
- codegen/validate/logger.py +52 -0
- codegen/validate/models.py +177 -0
- codegen/validate/prompt_generator.py +724 -0
- codegen/validate/response_parser.py +154 -0
- codegen/validate/response_validator.py +275 -0
- codegen/validate/validation_dialog.py +322 -0
- codegen/validate/validator.py +94 -0
- codegen/variable_generator.py +419 -0
- statable/__init__.py +71 -0
- statable/__main__.py +49 -0
- statable/cli.py +251 -0
- statable/global_defs.py +256 -0
- statable/mermaid_gen.py +144 -0
- statable/model.py +426 -0
- statable/parser.py +64 -0
- statable/sample_data.py +435 -0
- statable/shared/__init__.py +24 -0
- statable/shared/condition_library.py +63 -0
- statable/shared/literal_library.py +66 -0
- statable/shared/role_function_library.py +150 -0
- statable/state_machine.py +121 -0
- statable/xml_io.py +1164 -0
- statable-3.1.0.dist-info/METADATA +151 -0
- statable-3.1.0.dist-info/RECORD +117 -0
- statable-3.1.0.dist-info/WHEEL +5 -0
- statable-3.1.0.dist-info/entry_points.txt +2 -0
- statable-3.1.0.dist-info/top_level.txt +3 -0
- statable_gui/__init__.py +23 -0
- statable_gui/action_edit_dialog.py +214 -0
- statable_gui/code_generation_dialog.py +486 -0
- statable_gui/code_generation_settings_dialog.py +522 -0
- statable_gui/common_widgets.py +537 -0
- statable_gui/condition_builder_dialog.py +500 -0
- statable_gui/condition_edit_dialog.py +123 -0
- statable_gui/condition_template_dialog.py +58 -0
- statable_gui/config.py +21 -0
- statable_gui/dialogs.py +439 -0
- statable_gui/event_definition_dialog.py +605 -0
- statable_gui/event_delivery_settings_dialog.py +191 -0
- statable_gui/event_queue_dialog.py +245 -0
- statable_gui/global_defs.py +167 -0
- statable_gui/global_defs_dialog.py +796 -0
- statable_gui/i18n/__init__.py +118 -0
- statable_gui/interrupt_handler_edit_dialog.py +810 -0
- statable_gui/layer_settings_dialog.py +180 -0
- statable_gui/libcntrl/__init__.py +23 -0
- statable_gui/libcntrl/condition_library.py +13 -0
- statable_gui/libcntrl/literal_library.py +13 -0
- statable_gui/libcntrl/literal_management_dialog.py +185 -0
- statable_gui/libcntrl/role_function_edit_dialog.py +145 -0
- statable_gui/libcntrl/role_function_library.py +13 -0
- statable_gui/literal_definition_dialog.py +62 -0
- statable_gui/logger.py +83 -0
- statable_gui/main_window.py +1380 -0
- statable_gui/matrix_table.py +413 -0
- statable_gui/preference_keys.py +18 -0
- statable_gui/preferences.py +74 -0
- statable_gui/role_function_dialog.py +244 -0
- statable_gui/sample_data.py +11 -0
- statable_gui/state_actions_dialog.py +398 -0
- statable_gui/symbol_picker.py +170 -0
- statable_gui/traceball.py +37 -0
- statable_gui/transition_editor_direct/__init__.py +15 -0
- statable_gui/transition_editor_direct/actions_tab.py +856 -0
- statable_gui/transition_editor_direct/code_widget.py +344 -0
- statable_gui/transition_editor_direct/coverage_analyzer.py +163 -0
- statable_gui/transition_editor_direct/dialog.py +335 -0
- statable_gui/transition_editor_direct/draft.py +270 -0
- statable_gui/transition_editor_direct/edit_dialogs.py +160 -0
- statable_gui/transition_editor_direct/overview_tab.py +173 -0
- statable_gui/transition_editor_direct/palette_widget.py +233 -0
- statable_gui/transition_editor_direct/relations_edit_dialog.py +156 -0
- statable_gui/transition_editor_direct/relations_tab.py +230 -0
- statable_gui/transition_editor_direct/system_global_dialog.py +52 -0
- statable_gui/transition_editor_direct/transition_actions_dialog.py +109 -0
- statable_gui/transition_editor_direct/transitions_tab.py +620 -0
- statable_gui/validation_dialog.py +285 -0
- statable_gui/widgets.py +1315 -0
statable/model.py
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
"""
|
|
2
|
+
statable/model.py
|
|
3
|
+
Data model for StaTable.
|
|
4
|
+
|
|
5
|
+
Version History
|
|
6
|
+
---------------
|
|
7
|
+
v1.0 - Initial data model.
|
|
8
|
+
v1.6 - Transition: kw_only=True. Prevents positional-argument
|
|
9
|
+
field-order mistakes (see v1.4 §9.6 #67).
|
|
10
|
+
v2.2 - State.entry / State.exit: str -> List[str].
|
|
11
|
+
ActionStep and TransitionRelation added.
|
|
12
|
+
v2.2.4 - ActionStep / TransitionRelation: kw_only=True.
|
|
13
|
+
v3.7 - Reserved-field annotations added:
|
|
14
|
+
* State.do
|
|
15
|
+
* RoleFunction.return_type / arg1_type / arg1_name
|
|
16
|
+
/ arg2_type / arg2_name
|
|
17
|
+
These fields are not exposed in the GUI and not used by
|
|
18
|
+
the code generator, but are retained in the data model
|
|
19
|
+
and XML I/O for backward compatibility.
|
|
20
|
+
v3.8 - RoleFunction: added used_global_vars / used_events /
|
|
21
|
+
used_literals. These mirror libcntrl.RoleFunction and
|
|
22
|
+
enable the GUI dialog to track symbol references (see
|
|
23
|
+
statable_gui/role_function_dialog.py v3.9). Persisted in
|
|
24
|
+
XML (xml_io.py v3.8); not consumed by the code generator.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from dataclasses import dataclass, field
|
|
30
|
+
from enum import Enum
|
|
31
|
+
from typing import Optional, List
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class StateType(Enum):
|
|
35
|
+
NORMAL = "normal"
|
|
36
|
+
CONCURRENT = "concurrent"
|
|
37
|
+
REGION = "region"
|
|
38
|
+
INITIAL = "initial"
|
|
39
|
+
FINAL = "final"
|
|
40
|
+
CHOICE = "choice"
|
|
41
|
+
JUNCTION = "junction"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class EventKind(Enum):
|
|
45
|
+
SIGNAL = "signal"
|
|
46
|
+
CALL = "call"
|
|
47
|
+
TIME = "time"
|
|
48
|
+
CHANGE = "change"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class EventDeliveryType(Enum):
|
|
52
|
+
"""Event delivery method"""
|
|
53
|
+
DIRECT = "direct"
|
|
54
|
+
QUEUE = "queue"
|
|
55
|
+
DOUBLE = "double"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class EventSourceLayer(Enum):
|
|
59
|
+
"""Event source layer"""
|
|
60
|
+
DRIVER = "driver"
|
|
61
|
+
MIDDLEWARE = "middleware"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _normalize_action_list(value) -> list:
|
|
65
|
+
"""[v2.7.0] Normalize a legacy action list to List[ActionStep].
|
|
66
|
+
|
|
67
|
+
Accepts:
|
|
68
|
+
- None -> []
|
|
69
|
+
- str -> [ActionStep(role_function=str)]
|
|
70
|
+
- list of str -> [ActionStep(role_function=s) ...]
|
|
71
|
+
- list of ActionStep -> as-is
|
|
72
|
+
- list of dict -> [ActionStep.from_dict(d) ...]
|
|
73
|
+
"""
|
|
74
|
+
if value is None:
|
|
75
|
+
return []
|
|
76
|
+
if isinstance(value, str):
|
|
77
|
+
return [ActionStep(role_function=value)] if value.strip() else []
|
|
78
|
+
if isinstance(value, list):
|
|
79
|
+
result = []
|
|
80
|
+
for item in value:
|
|
81
|
+
if isinstance(item, str):
|
|
82
|
+
if item.strip():
|
|
83
|
+
result.append(ActionStep(role_function=item))
|
|
84
|
+
elif isinstance(item, ActionStep):
|
|
85
|
+
result.append(item)
|
|
86
|
+
elif isinstance(item, dict):
|
|
87
|
+
result.append(ActionStep.from_dict(item))
|
|
88
|
+
return result
|
|
89
|
+
return []
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass
|
|
93
|
+
class State:
|
|
94
|
+
"""State definition.
|
|
95
|
+
|
|
96
|
+
[v2.2 change]
|
|
97
|
+
entry / exit: str -> List[str]
|
|
98
|
+
Multiple entry / exit actions are supported.
|
|
99
|
+
For defensive compatibility, str / None are auto-normalized
|
|
100
|
+
in __post_init__.
|
|
101
|
+
|
|
102
|
+
[v3.7 note]
|
|
103
|
+
`do` is a **reserved field**. The code generator does not
|
|
104
|
+
reference it, and it is not exposed in the GUI SettingsPanel
|
|
105
|
+
(the "do function" column was removed in v3.7). It remains
|
|
106
|
+
in the data model and in XML I/O for backward compatibility
|
|
107
|
+
with projects created before v3.7.
|
|
108
|
+
"""
|
|
109
|
+
name: str
|
|
110
|
+
type: StateType = StateType.NORMAL
|
|
111
|
+
parent: Optional[str] = None
|
|
112
|
+
# [v2.7.0] List[str] -> List[ActionStep]. Legacy str / list-of-str
|
|
113
|
+
# values are auto-normalized in __post_init__.
|
|
114
|
+
entry: List[ActionStep] = field(default_factory=list)
|
|
115
|
+
exit: List[ActionStep] = field(default_factory=list)
|
|
116
|
+
# [v2.7.0] do activity: executed every loop while the state is active.
|
|
117
|
+
do_actions: List[ActionStep] = field(default_factory=list)
|
|
118
|
+
# [Reserved] Not exposed in UI / not used by codegen.
|
|
119
|
+
# Persisted in XML for backward compatibility only.
|
|
120
|
+
do: str = ""
|
|
121
|
+
description: str = ""
|
|
122
|
+
|
|
123
|
+
def __post_init__(self):
|
|
124
|
+
# v2.2 + v2.7.0: defensive normalization to List[ActionStep]
|
|
125
|
+
self.entry = _normalize_action_list(self.entry)
|
|
126
|
+
self.exit = _normalize_action_list(self.exit)
|
|
127
|
+
self.do_actions = _normalize_action_list(self.do_actions)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dataclass
|
|
131
|
+
class EventTrigger:
|
|
132
|
+
"""[C-51 Step 3] Structured trigger detail for an Event.
|
|
133
|
+
|
|
134
|
+
Only `type` is required; other fields are type-specific.
|
|
135
|
+
|
|
136
|
+
type:
|
|
137
|
+
"manual" - fires by explicit user code (default)
|
|
138
|
+
"edge" - GPIO edge detection
|
|
139
|
+
"polling" - periodic polling
|
|
140
|
+
"timer" - timer expiry
|
|
141
|
+
"call" - function invocation
|
|
142
|
+
"comparison" - condition polling
|
|
143
|
+
"""
|
|
144
|
+
type: str = "manual"
|
|
145
|
+
source: str = ""
|
|
146
|
+
description: str = ""
|
|
147
|
+
edge: str = "" # edge: rising / falling / both
|
|
148
|
+
debounce_ms: int = 0 # edge
|
|
149
|
+
period_ms: int = 0 # polling / timer
|
|
150
|
+
auto_reload: bool = True # timer
|
|
151
|
+
caller: str = "" # call
|
|
152
|
+
condition: str = "" # comparison
|
|
153
|
+
poll_period_ms: int = 0 # comparison
|
|
154
|
+
|
|
155
|
+
def to_dict(self) -> dict:
|
|
156
|
+
"""Serialize non-empty fields only (order stable)."""
|
|
157
|
+
d = {"type": self.type}
|
|
158
|
+
if self.source: d["source"] = self.source
|
|
159
|
+
if self.description: d["description"] = self.description
|
|
160
|
+
if self.edge: d["edge"] = self.edge
|
|
161
|
+
if self.debounce_ms: d["debounce_ms"] = self.debounce_ms
|
|
162
|
+
if self.period_ms: d["period_ms"] = self.period_ms
|
|
163
|
+
if self.type == "timer" and not self.auto_reload:
|
|
164
|
+
d["auto_reload"] = False
|
|
165
|
+
if self.caller: d["caller"] = self.caller
|
|
166
|
+
if self.condition: d["condition"] = self.condition
|
|
167
|
+
if self.poll_period_ms: d["poll_period_ms"] = self.poll_period_ms
|
|
168
|
+
return d
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def from_dict(cls, d: dict) -> "EventTrigger":
|
|
172
|
+
def _int(v, default=0):
|
|
173
|
+
try:
|
|
174
|
+
return int(v)
|
|
175
|
+
except (TypeError, ValueError):
|
|
176
|
+
return default
|
|
177
|
+
def _bool(v, default=True):
|
|
178
|
+
if isinstance(v, bool):
|
|
179
|
+
return v
|
|
180
|
+
if isinstance(v, str):
|
|
181
|
+
return v.lower() not in ("false", "0", "")
|
|
182
|
+
return bool(v) if v is not None else default
|
|
183
|
+
return cls(
|
|
184
|
+
type=str(d.get("type", "manual") or "manual"),
|
|
185
|
+
source=str(d.get("source", "") or ""),
|
|
186
|
+
description=str(d.get("description", "") or ""),
|
|
187
|
+
edge=str(d.get("edge", "") or ""),
|
|
188
|
+
debounce_ms=_int(d.get("debounce_ms", 0)),
|
|
189
|
+
period_ms=_int(d.get("period_ms", 0)),
|
|
190
|
+
auto_reload=_bool(d.get("auto_reload", True)),
|
|
191
|
+
caller=str(d.get("caller", "") or ""),
|
|
192
|
+
condition=str(d.get("condition", "") or ""),
|
|
193
|
+
poll_period_ms=_int(d.get("poll_period_ms", 0)),
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@dataclass
|
|
198
|
+
class Event:
|
|
199
|
+
"""State transition event"""
|
|
200
|
+
name: str
|
|
201
|
+
id: Optional[int] = None
|
|
202
|
+
kind: EventKind = EventKind.SIGNAL
|
|
203
|
+
params: List[str] = field(default_factory=list)
|
|
204
|
+
priority: int = 0
|
|
205
|
+
description: str = ""
|
|
206
|
+
delivery_type: EventDeliveryType = EventDeliveryType.DIRECT
|
|
207
|
+
source_layer: EventSourceLayer = EventSourceLayer.DRIVER
|
|
208
|
+
data_type: str = ""
|
|
209
|
+
data_name: str = ""
|
|
210
|
+
title: str = ""
|
|
211
|
+
# [C-51 Step 2] Free-text description of when / from where
|
|
212
|
+
# this event fires. Only meaningful for kind in
|
|
213
|
+
# {signal, call, time}; kind=change is handled inside
|
|
214
|
+
# transition conditions. Metadata only; not consumed
|
|
215
|
+
# by codegen. Backward compatible (missing XML attribute
|
|
216
|
+
# loads as "").
|
|
217
|
+
trigger: str = ""
|
|
218
|
+
# [C-51 Step 3] Structured trigger detail. None = not specified
|
|
219
|
+
# (backward-compatible with Step 2, where only `trigger` existed).
|
|
220
|
+
trigger_detail: Optional["EventTrigger"] = None
|
|
221
|
+
|
|
222
|
+
def __post_init__(self):
|
|
223
|
+
if not self.title:
|
|
224
|
+
self.title = f"Event: {self.name}" if self.name else "Event: (completion)"
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@dataclass(kw_only=True)
|
|
228
|
+
class Transition:
|
|
229
|
+
"""State transition definition.
|
|
230
|
+
|
|
231
|
+
[v1.6 change] kw_only=True
|
|
232
|
+
To structurally prevent positional-argument field-order accidents
|
|
233
|
+
(v1.4 section 9.6 #67), only kw_only arguments are accepted.
|
|
234
|
+
|
|
235
|
+
[v2.2 additions]
|
|
236
|
+
early_return: bool = False
|
|
237
|
+
- True : "Commit" - stop evaluating later transitions in the same cell
|
|
238
|
+
- False : "Tentative" - later transitions may overwrite the target
|
|
239
|
+
|
|
240
|
+
label: str = ""
|
|
241
|
+
Stable identifier within a cell (e.g. "T1", "T2").
|
|
242
|
+
Referenced by TransitionRelation.members.
|
|
243
|
+
Persisted in XML.
|
|
244
|
+
"""
|
|
245
|
+
source: str
|
|
246
|
+
event: str
|
|
247
|
+
condition: str = ""
|
|
248
|
+
pre_actions: List[str] = field(default_factory=list)
|
|
249
|
+
target: str = ""
|
|
250
|
+
has_else: bool = True
|
|
251
|
+
else_target: str = ""
|
|
252
|
+
else_actions: List[str] = field(default_factory=list)
|
|
253
|
+
early_return: bool = False
|
|
254
|
+
label: str = ""
|
|
255
|
+
action: str = ""
|
|
256
|
+
transition_type: str = "external"
|
|
257
|
+
title: str = ""
|
|
258
|
+
|
|
259
|
+
def __post_init__(self):
|
|
260
|
+
if not self.title:
|
|
261
|
+
self.title = "(untitled transition)"
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@dataclass(kw_only=True)
|
|
265
|
+
class ActionStep:
|
|
266
|
+
"""Transition-independent action step (v2.2).
|
|
267
|
+
|
|
268
|
+
[v2.2.4 change] kw_only=True
|
|
269
|
+
To structurally prevent positional-argument field-order accidents
|
|
270
|
+
(consistent with v1.5 RoleFunction / v1.6 Transition).
|
|
271
|
+
|
|
272
|
+
trigger:
|
|
273
|
+
"before_transitions" - runs just before evaluating transitions
|
|
274
|
+
"after_transitions" - runs at the end of the cell body
|
|
275
|
+
|
|
276
|
+
Note: legacy "always" is silently mapped to "before_transitions"
|
|
277
|
+
when loading old XML.
|
|
278
|
+
"""
|
|
279
|
+
role_function: str = ""
|
|
280
|
+
trigger: str = "before_transitions"
|
|
281
|
+
title: str = ""
|
|
282
|
+
# [v2.7.0] State action extensions
|
|
283
|
+
condition: str = "" # execution condition (C expression)
|
|
284
|
+
action_type: str = "role" # "role" | "fire_event" | "custom"
|
|
285
|
+
event_name: str = "" # action_type="fire_event"
|
|
286
|
+
|
|
287
|
+
def __post_init__(self):
|
|
288
|
+
if not self.title:
|
|
289
|
+
self.title = self.role_function or "(untitled action)"
|
|
290
|
+
|
|
291
|
+
def to_dict(self) -> dict:
|
|
292
|
+
"""[v2.7.0] Serialize non-empty fields only (order stable)."""
|
|
293
|
+
d = {}
|
|
294
|
+
if self.role_function: d["role_function"] = self.role_function
|
|
295
|
+
if self.trigger and self.trigger != "before_transitions":
|
|
296
|
+
d["trigger"] = self.trigger
|
|
297
|
+
if self.title: d["title"] = self.title
|
|
298
|
+
if self.condition: d["condition"] = self.condition
|
|
299
|
+
if self.action_type and self.action_type != "role":
|
|
300
|
+
d["action_type"] = self.action_type
|
|
301
|
+
if self.event_name: d["event_name"] = self.event_name
|
|
302
|
+
return d
|
|
303
|
+
|
|
304
|
+
@classmethod
|
|
305
|
+
def from_dict(cls, d: dict) -> "ActionStep":
|
|
306
|
+
"""[v2.7.0] Deserialize; tolerant of legacy `name` attribute."""
|
|
307
|
+
role = str(d.get("role_function", "") or d.get("name", "") or "")
|
|
308
|
+
return cls(
|
|
309
|
+
role_function=role,
|
|
310
|
+
trigger=str(d.get("trigger", "before_transitions") or "before_transitions"),
|
|
311
|
+
title=str(d.get("title", "") or ""),
|
|
312
|
+
condition=str(d.get("condition", "") or ""),
|
|
313
|
+
action_type=str(d.get("action_type", "role") or "role"),
|
|
314
|
+
event_name=str(d.get("event_name", "") or ""),
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
@dataclass(kw_only=True)
|
|
319
|
+
class TransitionRelation:
|
|
320
|
+
"""Relation between transitions in one cell (v2.2).
|
|
321
|
+
|
|
322
|
+
[v2.2.4 change] kw_only=True
|
|
323
|
+
To structurally prevent positional-argument field-order accidents
|
|
324
|
+
(consistent with v1.5 RoleFunction / v1.6 Transition).
|
|
325
|
+
|
|
326
|
+
kind:
|
|
327
|
+
"sequential" - evaluate members in order (default)
|
|
328
|
+
"exclusive" - at most one member fires; codegen enforces early return
|
|
329
|
+
"group" - logical grouping; shared_condition is hoisted
|
|
330
|
+
as an outer if (evaluated once)
|
|
331
|
+
|
|
332
|
+
members: Transition.label values (e.g. ["T1", "T2"]).
|
|
333
|
+
shared_condition: only used when kind == "group".
|
|
334
|
+
|
|
335
|
+
[v2.2 §12-5] children: nested sub-relations (recursive).
|
|
336
|
+
"""
|
|
337
|
+
kind: str = "sequential"
|
|
338
|
+
members: List[str] = field(default_factory=list)
|
|
339
|
+
shared_condition: str = ""
|
|
340
|
+
note: str = ""
|
|
341
|
+
children: List["TransitionRelation"] = field(default_factory=list)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
@dataclass(kw_only=True)
|
|
345
|
+
class RoleFunction:
|
|
346
|
+
"""Role function (shared library / state machine local).
|
|
347
|
+
|
|
348
|
+
[v3.7 note]
|
|
349
|
+
The C signature generated for a role function is **fixed** to
|
|
350
|
+
|
|
351
|
+
int RoleFunc_<NS>_<Name>(const Transition_t* transition,
|
|
352
|
+
Context_t* ctx);
|
|
353
|
+
|
|
354
|
+
by `codegen/role_function_generator.py`, which does not
|
|
355
|
+
reference `return_type` / `arg1_*` / `arg2_*` at all.
|
|
356
|
+
Those fields are therefore **reserved**:
|
|
357
|
+
|
|
358
|
+
- They are not exposed in the GUI SettingsPanel or in
|
|
359
|
+
RoleFunctionDialog (v3.7 / v3.8).
|
|
360
|
+
- They are still serialized / deserialized by xml_io.py
|
|
361
|
+
for backward compatibility with projects created
|
|
362
|
+
before v3.7.
|
|
363
|
+
- `SettingsPanel.apply_changes()` and `RoleFunctionDialog.
|
|
364
|
+
get_role_function()` carry existing values forward so
|
|
365
|
+
that an XML round-trip remains lossless.
|
|
366
|
+
|
|
367
|
+
Do not remove these fields without a migration plan; old
|
|
368
|
+
project files may rely on their presence.
|
|
369
|
+
|
|
370
|
+
[v3.8 addition]
|
|
371
|
+
used_global_vars / used_events / used_literals mirror the
|
|
372
|
+
fields of libcntrl.RoleFunction. They let the RoleFunctionDialog
|
|
373
|
+
track which symbols a role function references, without
|
|
374
|
+
changing the generated C signature (which remains fixed as
|
|
375
|
+
above). Persisted in XML; not consumed by the code generator.
|
|
376
|
+
"""
|
|
377
|
+
name: str
|
|
378
|
+
namespace: str = ""
|
|
379
|
+
description: str = ""
|
|
380
|
+
# [Reserved] Not exposed in UI / not used by codegen.
|
|
381
|
+
# Persisted in XML for backward compatibility only.
|
|
382
|
+
return_type: str = "void"
|
|
383
|
+
# [Reserved] Not exposed in UI / not used by codegen.
|
|
384
|
+
arg1_type: str = ""
|
|
385
|
+
# [Reserved] Not exposed in UI / not used by codegen.
|
|
386
|
+
arg1_name: str = ""
|
|
387
|
+
# [Reserved] Not exposed in UI / not used by codegen.
|
|
388
|
+
arg2_type: str = ""
|
|
389
|
+
# [Reserved] Not exposed in UI / not used by codegen.
|
|
390
|
+
arg2_name: str = ""
|
|
391
|
+
title: str = ""
|
|
392
|
+
# [v3.8] GUI symbol tracking (mirrors libcntrl.RoleFunction).
|
|
393
|
+
# Persisted in XML; not consumed by codegen.
|
|
394
|
+
used_global_vars: List[str] = field(default_factory=list)
|
|
395
|
+
used_events: List[str] = field(default_factory=list)
|
|
396
|
+
used_literals: List[str] = field(default_factory=list)
|
|
397
|
+
|
|
398
|
+
def __post_init__(self):
|
|
399
|
+
if not self.title:
|
|
400
|
+
self.title = f"Role function: {self.qualified_name}"
|
|
401
|
+
# v3.8: defensive normalization
|
|
402
|
+
if self.used_global_vars is None:
|
|
403
|
+
self.used_global_vars = []
|
|
404
|
+
if self.used_events is None:
|
|
405
|
+
self.used_events = []
|
|
406
|
+
if self.used_literals is None:
|
|
407
|
+
self.used_literals = []
|
|
408
|
+
|
|
409
|
+
@property
|
|
410
|
+
def qualified_name(self) -> str:
|
|
411
|
+
if self.namespace:
|
|
412
|
+
return f"{self.namespace}.{self.name}"
|
|
413
|
+
return self.name
|
|
414
|
+
|
|
415
|
+
@classmethod
|
|
416
|
+
def from_legacy_name(cls, legacy_name: str,
|
|
417
|
+
layer_names: Optional[List[str]] = None) -> 'RoleFunction':
|
|
418
|
+
if not legacy_name:
|
|
419
|
+
return cls(name="")
|
|
420
|
+
if layer_names:
|
|
421
|
+
for layer in layer_names:
|
|
422
|
+
prefix = f"{layer}_"
|
|
423
|
+
if legacy_name.startswith(prefix):
|
|
424
|
+
return cls(name=legacy_name[len(prefix):],
|
|
425
|
+
namespace=layer)
|
|
426
|
+
return cls(name=legacy_name, namespace="")
|
statable/parser.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# statable/parser.py
|
|
2
|
+
"""
|
|
3
|
+
StaTable parser (unimplemented stub)
|
|
4
|
+
|
|
5
|
+
[v1.8 section 11.2 #9]
|
|
6
|
+
Currently an unimplemented stub.
|
|
7
|
+
XML I/O is handled by `statable/xml_io.py`.
|
|
8
|
+
|
|
9
|
+
[Planned for future implementation]
|
|
10
|
+
- Excel (.xlsx) loading
|
|
11
|
+
Import state transition tables using openpyxl etc.
|
|
12
|
+
- CSV (.csv) loading
|
|
13
|
+
Assume state x event matrix format
|
|
14
|
+
- JSON (.json) loading
|
|
15
|
+
General-purpose format for external tool integration
|
|
16
|
+
|
|
17
|
+
[Notes]
|
|
18
|
+
- This module currently has no callers
|
|
19
|
+
- When implementing, convert to `statable/model.py`'s
|
|
20
|
+
StateMachine / State / Event / Transition
|
|
21
|
+
- It is desirable to target the same interface as the existing `xml_io.py`
|
|
22
|
+
(equivalent to project_to_xml / project_from_xml)
|
|
23
|
+
|
|
24
|
+
[If not implementing]
|
|
25
|
+
This file itself can be deleted without issue.
|
|
26
|
+
Since there are no callers, deletion has no impact.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
# ======================================================================
|
|
30
|
+
# Unimplemented marker
|
|
31
|
+
# ======================================================================
|
|
32
|
+
# The following are placeholders.
|
|
33
|
+
# Do not call until implemented.
|
|
34
|
+
|
|
35
|
+
__all__ = [] # No public API (not implemented)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ParserNotImplementedError(NotImplementedError):
|
|
39
|
+
"""Exception indicating parser.py features not implemented"""
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def parse_excel(filepath: str):
|
|
44
|
+
"""\n Load state transition data from an Excel file (not implemented)\n"""
|
|
45
|
+
raise ParserNotImplementedError(
|
|
46
|
+
"parse_excel is not implemented."
|
|
47
|
+
"Currently, please use statable/xml_io.py."
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def parse_csv(filepath: str):
|
|
52
|
+
"""\n Load state transition data from a CSV file (not implemented)\n"""
|
|
53
|
+
raise ParserNotImplementedError(
|
|
54
|
+
"parse_csv is not implemented."
|
|
55
|
+
"Currently, please use statable/xml_io.py."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def parse_json(filepath: str):
|
|
60
|
+
"""\n Load state transition data from a JSON file (not implemented)\n"""
|
|
61
|
+
raise ParserNotImplementedError(
|
|
62
|
+
"parse_json is not implemented."
|
|
63
|
+
"Currently, please use statable/xml_io.py."
|
|
64
|
+
)
|