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
codegen/sample_data.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# codegen/sample_data.py
|
|
2
|
+
"""
|
|
3
|
+
Sample data for code generation tests.
|
|
4
|
+
Provides test data independent of the GUI.
|
|
5
|
+
|
|
6
|
+
[v1.5 fix]
|
|
7
|
+
- Transition action= migrated to pre_actions=[] (bug #87)
|
|
8
|
+
The action field is defined as "compat / unused" in v1.4 sec 2.2,
|
|
9
|
+
and role_function_generator only references pre_actions,
|
|
10
|
+
so action= does not reflect calls in code generation.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import sys
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
17
|
+
|
|
18
|
+
from statable.model import State, Event, Transition, StateType, EventKind, RoleFunction
|
|
19
|
+
from statable.state_machine import StateMachine
|
|
20
|
+
from statable.global_defs import (
|
|
21
|
+
GlobalDefinitions, SystemVariable, EventFlag,
|
|
22
|
+
CustomTypeDef, StructMemberDef,
|
|
23
|
+
EventQueueDef, InterruptHandlerDef, InterruptAction,
|
|
24
|
+
DevicePlaceholderDef, TimerBaseDef, TimerDerivedDef
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SampleDataGenerator:
|
|
29
|
+
"""Sample data generator class"""
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
self.state_machine = None
|
|
33
|
+
self.global_defs = None
|
|
34
|
+
|
|
35
|
+
def create_sample_state_machine(self):
|
|
36
|
+
sm = StateMachine()
|
|
37
|
+
|
|
38
|
+
sm.add_state(State(name="INIT", type=StateType.INITIAL, description="Initial state"))
|
|
39
|
+
sm.add_state(State(name="IDLE", type=StateType.NORMAL, description="Idle state"))
|
|
40
|
+
sm.add_state(State(name="RUNNING", type=StateType.NORMAL, description="Running state"))
|
|
41
|
+
sm.add_state(State(name="ERROR", type=StateType.NORMAL, description="Error state"))
|
|
42
|
+
sm.set_initial("INIT")
|
|
43
|
+
|
|
44
|
+
sm.add_event(Event(name="POWER_ON", kind=EventKind.SIGNAL, description="Power-on event"))
|
|
45
|
+
sm.add_event(Event(name="START", kind=EventKind.SIGNAL, description="Start event"))
|
|
46
|
+
sm.add_event(Event(name="STOP", kind=EventKind.SIGNAL, description="Stop event"))
|
|
47
|
+
sm.add_event(Event(name="ERROR_DETECTED", kind=EventKind.SIGNAL, description="Error detected event"))
|
|
48
|
+
|
|
49
|
+
# ==============================================================
|
|
50
|
+
# [v1.5 fix] action= -> pre_actions=[]
|
|
51
|
+
# ==============================================================
|
|
52
|
+
sm.add_transition(Transition(
|
|
53
|
+
source="INIT",
|
|
54
|
+
event="POWER_ON",
|
|
55
|
+
target="IDLE",
|
|
56
|
+
pre_actions=["PowerOn"],
|
|
57
|
+
title="Power on",
|
|
58
|
+
))
|
|
59
|
+
sm.add_transition(Transition(
|
|
60
|
+
source="IDLE",
|
|
61
|
+
event="START",
|
|
62
|
+
target="RUNNING",
|
|
63
|
+
condition="StartOk",
|
|
64
|
+
pre_actions=["Start"],
|
|
65
|
+
title="Start",
|
|
66
|
+
))
|
|
67
|
+
sm.add_transition(Transition(
|
|
68
|
+
source="RUNNING",
|
|
69
|
+
event="STOP",
|
|
70
|
+
target="IDLE",
|
|
71
|
+
pre_actions=["Stop"],
|
|
72
|
+
title="Stop",
|
|
73
|
+
))
|
|
74
|
+
sm.add_transition(Transition(
|
|
75
|
+
source="RUNNING",
|
|
76
|
+
event="ERROR_DETECTED",
|
|
77
|
+
target="ERROR",
|
|
78
|
+
pre_actions=["HandleError"],
|
|
79
|
+
title="Error handling",
|
|
80
|
+
))
|
|
81
|
+
|
|
82
|
+
sm.add_role_function(RoleFunction(
|
|
83
|
+
name="PowerOn", return_type="void",
|
|
84
|
+
description="Power-on processing",
|
|
85
|
+
))
|
|
86
|
+
sm.add_role_function(RoleFunction(
|
|
87
|
+
name="StartOk", return_type="bool",
|
|
88
|
+
description="Start condition check",
|
|
89
|
+
))
|
|
90
|
+
sm.add_role_function(RoleFunction(
|
|
91
|
+
name="Start", return_type="void",
|
|
92
|
+
description="Start processing",
|
|
93
|
+
))
|
|
94
|
+
sm.add_role_function(RoleFunction(
|
|
95
|
+
name="Stop", return_type="void",
|
|
96
|
+
description="Stop processing",
|
|
97
|
+
))
|
|
98
|
+
sm.add_role_function(RoleFunction(
|
|
99
|
+
name="HandleError", return_type="void",
|
|
100
|
+
description="Error handling",
|
|
101
|
+
))
|
|
102
|
+
sm.add_role_function(RoleFunction(
|
|
103
|
+
name="ProcessData", return_type="int",
|
|
104
|
+
arg1_type="uint8_t*", arg1_name="data",
|
|
105
|
+
arg2_type="uint16_t", arg2_name="len",
|
|
106
|
+
description="Data processing",
|
|
107
|
+
))
|
|
108
|
+
|
|
109
|
+
self.state_machine = sm
|
|
110
|
+
return sm
|
|
111
|
+
|
|
112
|
+
def create_sample_global_defs(self):
|
|
113
|
+
gd = GlobalDefinitions()
|
|
114
|
+
|
|
115
|
+
gd.variables = [
|
|
116
|
+
SystemVariable(name="battery_voltage", type="uint16", unit="mV",
|
|
117
|
+
group="Power", description="Battery voltage"),
|
|
118
|
+
SystemVariable(name="system_tick", type="uint32", unit="ms",
|
|
119
|
+
group="Timer", description="System timer"),
|
|
120
|
+
SystemVariable(name="temperature", type="int16", unit="0.1C",
|
|
121
|
+
group="Sensor", description="Temperature sensor value"),
|
|
122
|
+
SystemVariable(name="data_buffer", type="uint8",
|
|
123
|
+
group="Data", description="Data buffer",
|
|
124
|
+
array_size=64),
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
gd.flags = [
|
|
128
|
+
EventFlag(name="EVT_POWER_ON_REQ", min_value=0, max_value=1,
|
|
129
|
+
group="System", description="Power-on request"),
|
|
130
|
+
EventFlag(name="EVT_START_REQ", min_value=0, max_value=1,
|
|
131
|
+
group="System", description="Start request"),
|
|
132
|
+
EventFlag(name="EVT_STOP_REQ", min_value=0, max_value=1,
|
|
133
|
+
group="System", description="Stop request"),
|
|
134
|
+
EventFlag(name="EVT_ERROR_FLAG", min_value=0, max_value=1,
|
|
135
|
+
group="Error", description="Error flag"),
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
gd.custom_types = [
|
|
139
|
+
CustomTypeDef(name="SystemStatus", description="System status management struct", members=[
|
|
140
|
+
StructMemberDef(name="power_on", data_type="bool", bit_width=1,
|
|
141
|
+
description="Power-on state"),
|
|
142
|
+
StructMemberDef(name="initialized", data_type="bool", bit_width=1,
|
|
143
|
+
description="Initialization completed flag"),
|
|
144
|
+
StructMemberDef(name="error_code", data_type="uint8",
|
|
145
|
+
description="Error code"),
|
|
146
|
+
StructMemberDef(name="mode", data_type="uint8",
|
|
147
|
+
description="Operation mode"),
|
|
148
|
+
]),
|
|
149
|
+
CustomTypeDef(name="SensorData", description="Sensor data struct", members=[
|
|
150
|
+
StructMemberDef(name="temperature", data_type="int16",
|
|
151
|
+
description="Temperature value"),
|
|
152
|
+
StructMemberDef(name="humidity", data_type="uint8",
|
|
153
|
+
description="Humidity value"),
|
|
154
|
+
StructMemberDef(name="pressure", data_type="uint16",
|
|
155
|
+
description="Pressure value"),
|
|
156
|
+
]),
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
gd.event_queues = [
|
|
160
|
+
EventQueueDef(
|
|
161
|
+
name="MainEventQueue",
|
|
162
|
+
size=16,
|
|
163
|
+
element_type="EVENT_t",
|
|
164
|
+
event_ids=["POWER_ON", "START", "STOP", "ERROR_DETECTED"],
|
|
165
|
+
priority_enabled=False,
|
|
166
|
+
interrupt_safe=True,
|
|
167
|
+
rtos_enabled=False,
|
|
168
|
+
description="Main event queue",
|
|
169
|
+
),
|
|
170
|
+
EventQueueDef(
|
|
171
|
+
name="HighPriorityQueue",
|
|
172
|
+
size=8,
|
|
173
|
+
element_type="EVENT_t",
|
|
174
|
+
event_ids=["ERROR_DETECTED"],
|
|
175
|
+
priority_enabled=True,
|
|
176
|
+
interrupt_safe=True,
|
|
177
|
+
rtos_enabled=False,
|
|
178
|
+
description="High-priority event queue",
|
|
179
|
+
),
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
gd.interrupts = [
|
|
183
|
+
InterruptHandlerDef(
|
|
184
|
+
name="UART_RX",
|
|
185
|
+
description="UART receive interrupt",
|
|
186
|
+
event_names=["START", "STOP"],
|
|
187
|
+
is_timer=False,
|
|
188
|
+
actions=[
|
|
189
|
+
InterruptAction(condition="", action="EVT_START_REQ = 1"),
|
|
190
|
+
InterruptAction(
|
|
191
|
+
condition="ctx->data.battery_voltage > 3000",
|
|
192
|
+
action="EVT_STOP_REQ = 1",
|
|
193
|
+
),
|
|
194
|
+
],
|
|
195
|
+
),
|
|
196
|
+
InterruptHandlerDef(
|
|
197
|
+
name="TimerTick",
|
|
198
|
+
description="Timer interrupt",
|
|
199
|
+
event_names=[],
|
|
200
|
+
is_timer=True,
|
|
201
|
+
actions=[
|
|
202
|
+
InterruptAction(condition="", action="ctx->data.system_tick++"),
|
|
203
|
+
],
|
|
204
|
+
),
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
gd.placeholders = [
|
|
208
|
+
DevicePlaceholderDef(name="UART0", description="UART communication port"),
|
|
209
|
+
DevicePlaceholderDef(name="ADC0", description="AD converter"),
|
|
210
|
+
]
|
|
211
|
+
|
|
212
|
+
gd.timer_base = TimerBaseDef(
|
|
213
|
+
variable_name="g_system_tick",
|
|
214
|
+
unit="1ms",
|
|
215
|
+
data_type="volatile uint32_t",
|
|
216
|
+
interrupt_name="TimerTick",
|
|
217
|
+
derived=[
|
|
218
|
+
TimerDerivedDef(period_name="10ms", multiplier=10,
|
|
219
|
+
variable_name="g_tick_10ms", data_type="uint8_t"),
|
|
220
|
+
TimerDerivedDef(period_name="100ms", multiplier=100,
|
|
221
|
+
variable_name="g_tick_100ms", data_type="uint8_t"),
|
|
222
|
+
],
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
gd.extra_timers = [
|
|
226
|
+
TimerBaseDef(
|
|
227
|
+
variable_name="g_high_speed_tick",
|
|
228
|
+
unit="100us",
|
|
229
|
+
data_type="volatile uint32_t",
|
|
230
|
+
interrupt_name="HighSpeedTimerTick",
|
|
231
|
+
derived=[
|
|
232
|
+
TimerDerivedDef(period_name="1ms", multiplier=10,
|
|
233
|
+
variable_name="g_hs_tick_1ms",
|
|
234
|
+
data_type="uint16_t"),
|
|
235
|
+
],
|
|
236
|
+
),
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
self.global_defs = gd
|
|
240
|
+
return gd
|
|
241
|
+
|
|
242
|
+
def get_sample_data(self):
|
|
243
|
+
if self.state_machine is None:
|
|
244
|
+
self.create_sample_state_machine()
|
|
245
|
+
if self.global_defs is None:
|
|
246
|
+
self.create_sample_global_defs()
|
|
247
|
+
return self.state_machine, self.global_defs
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
_sample_data_instance = None
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def get_sample_data_instance():
|
|
254
|
+
global _sample_data_instance
|
|
255
|
+
if _sample_data_instance is None:
|
|
256
|
+
_sample_data_instance = SampleDataGenerator()
|
|
257
|
+
return _sample_data_instance
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def get_sample_state_machine():
|
|
261
|
+
return get_sample_data_instance().create_sample_state_machine()
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def get_sample_global_defs():
|
|
265
|
+
return get_sample_data_instance().create_sample_global_defs()
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def get_sample_data():
|
|
269
|
+
return get_sample_data_instance().get_sample_data()
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
# codegen/state_actions_generator.py
|
|
2
|
+
"""
|
|
3
|
+
State actions generation module (v2.7.0)
|
|
4
|
+
|
|
5
|
+
Generates per-layer Entry / Exit / Do dispatch code:
|
|
6
|
+
|
|
7
|
+
- statable_state_actions_<Layer>.h
|
|
8
|
+
void <Layer>_Entry(STATE_<Layer>_t state, SystemContext_t *ctx);
|
|
9
|
+
void <Layer>_Exit (STATE_<Layer>_t state, SystemContext_t *ctx);
|
|
10
|
+
void <Layer>_Do (STATE_<Layer>_t state, SystemContext_t *ctx);
|
|
11
|
+
|
|
12
|
+
- statable_state_actions_<Layer>.c
|
|
13
|
+
- Entry / Exit / Do dispatch tables (ordered init, C89-compatible)
|
|
14
|
+
- Dispatchers
|
|
15
|
+
- Per-state functions with [[STABLE_USER_CODE_..._custom]] markers
|
|
16
|
+
|
|
17
|
+
Design notes
|
|
18
|
+
------------
|
|
19
|
+
- Table order MUST match STATE_<Layer>_t enum order.
|
|
20
|
+
- Each per-state function contains:
|
|
21
|
+
* GUI-edited actions (regenerated)
|
|
22
|
+
* User custom code (preserved)
|
|
23
|
+
- Role function calls receive NULL for the transition pointer
|
|
24
|
+
(state actions are not tied to a specific transition).
|
|
25
|
+
|
|
26
|
+
Version: 1.0 (2026-09-26 / initial)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import sys
|
|
30
|
+
import os
|
|
31
|
+
import logging
|
|
32
|
+
from string import Template
|
|
33
|
+
from typing import Dict, List, Optional
|
|
34
|
+
|
|
35
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
36
|
+
|
|
37
|
+
from statable.model import State, ActionStep
|
|
38
|
+
from statable.state_machine import StateMachine
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
from .type_mapper import CTypeMapper
|
|
42
|
+
from .naming_convention import CNamingConvention
|
|
43
|
+
from .code_templates import CodeTemplates
|
|
44
|
+
except ImportError:
|
|
45
|
+
from type_mapper import CTypeMapper
|
|
46
|
+
from naming_convention import CNamingConvention
|
|
47
|
+
from code_templates import CodeTemplates
|
|
48
|
+
|
|
49
|
+
logger = logging.getLogger(__name__)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _extract_rf_name(action) -> str:
|
|
53
|
+
"""[v2.7.0] Extract role_function name from ActionStep or str."""
|
|
54
|
+
if action is None:
|
|
55
|
+
return ""
|
|
56
|
+
if isinstance(action, str):
|
|
57
|
+
return action
|
|
58
|
+
return getattr(action, "role_function", "") or ""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class StateActionsGenerator:
|
|
62
|
+
"""State actions (Entry / Exit / Do) code generator."""
|
|
63
|
+
|
|
64
|
+
HEADER_TEMPLATES = {
|
|
65
|
+
'file_comment': '''/**
|
|
66
|
+
* @file statable_state_actions_{layer}.h
|
|
67
|
+
* @brief State actions (Entry / Exit / Do) for {layer} layer
|
|
68
|
+
*
|
|
69
|
+
* @note Code auto-generated by StaTable
|
|
70
|
+
* - Manual editing is not recommended
|
|
71
|
+
* - To modify, use StaTable
|
|
72
|
+
*/''',
|
|
73
|
+
'guard_start': '#ifndef STATABLE_STATE_ACTIONS_{LAYER}_H\n'
|
|
74
|
+
'#define STATABLE_STATE_ACTIONS_{LAYER}_H\n',
|
|
75
|
+
'guard_end': '#endif /* STATABLE_STATE_ACTIONS_{LAYER}_H */',
|
|
76
|
+
'include_common': '#include "statable_types_common.h"',
|
|
77
|
+
'include_layer': '#include "statable_types_{layer}.h"',
|
|
78
|
+
'decl_entry': 'void {layer}_Entry(STATE_{layer}_t state, SystemContext_t *ctx);',
|
|
79
|
+
'decl_exit': 'void {layer}_Exit (STATE_{layer}_t state, SystemContext_t *ctx);',
|
|
80
|
+
'decl_do': 'void {layer}_Do (STATE_{layer}_t state, SystemContext_t *ctx);',
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
SOURCE_TEMPLATES = {
|
|
84
|
+
'file_comment': '''/**
|
|
85
|
+
* @file statable_state_actions_{layer}.c
|
|
86
|
+
* @brief State actions (Entry / Exit / Do) for {layer} layer
|
|
87
|
+
*
|
|
88
|
+
* @note Code auto-generated by StaTable
|
|
89
|
+
* - Manual editing is not recommended
|
|
90
|
+
* - To modify, use StaTable
|
|
91
|
+
* - C89-compatible: no designated initializers used.
|
|
92
|
+
* Table order MUST match STATE_{layer}_t enum order.
|
|
93
|
+
*/''',
|
|
94
|
+
'include_own': '#include "statable_state_actions_{layer}.h"',
|
|
95
|
+
'type_typedef': 'typedef void (*{layer}_StateFunc_t)(SystemContext_t *ctx);',
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
def __init__(self):
|
|
99
|
+
self.mapper = CTypeMapper()
|
|
100
|
+
self.naming = CNamingConvention()
|
|
101
|
+
self.templates = CodeTemplates()
|
|
102
|
+
self.layer_name: str = ""
|
|
103
|
+
|
|
104
|
+
def set_layer(self, layer_name: str):
|
|
105
|
+
self.layer_name = layer_name
|
|
106
|
+
|
|
107
|
+
def _log_debug(self, message, level='debug'):
|
|
108
|
+
log_func = getattr(logger, level, logger.debug)
|
|
109
|
+
log_func(message)
|
|
110
|
+
|
|
111
|
+
# ------------------------------------------------------------------
|
|
112
|
+
# Name helpers
|
|
113
|
+
# ------------------------------------------------------------------
|
|
114
|
+
def _state_type(self) -> str:
|
|
115
|
+
return (f"STATE_{self.layer_name}_t"
|
|
116
|
+
if self.layer_name else "STATE_t")
|
|
117
|
+
|
|
118
|
+
def _state_max(self) -> str:
|
|
119
|
+
return (f"STATE_{self.layer_name}_MAX"
|
|
120
|
+
if self.layer_name else "STATE_MAX")
|
|
121
|
+
|
|
122
|
+
def _state_enum(self, state_name: str) -> str:
|
|
123
|
+
s = self.naming.to_pascal_case(state_name) if state_name else "Unknown"
|
|
124
|
+
return (f"STATE_{self.layer_name}_{s}"
|
|
125
|
+
if self.layer_name else f"STATE_{s}")
|
|
126
|
+
|
|
127
|
+
def _func_type(self) -> str:
|
|
128
|
+
return (f"{self.layer_name}_StateFunc_t"
|
|
129
|
+
if self.layer_name else "StateFunc_t")
|
|
130
|
+
|
|
131
|
+
def _state_suffix(self, state_name: str) -> str:
|
|
132
|
+
"""C-safe suffix for per-state function names."""
|
|
133
|
+
return self.naming.to_pascal_case(state_name) if state_name else "Unknown"
|
|
134
|
+
|
|
135
|
+
def _rf_call(self, rf_name: str) -> str:
|
|
136
|
+
"""Generate RoleFunc_<NS>_<Name>(NULL, ctx) or fallback."""
|
|
137
|
+
if not rf_name:
|
|
138
|
+
return ""
|
|
139
|
+
if rf_name.startswith("RoleFunc_"):
|
|
140
|
+
full = rf_name
|
|
141
|
+
elif "." in rf_name:
|
|
142
|
+
ns, name = rf_name.split(".", 1)
|
|
143
|
+
full = (f"RoleFunc_{self.naming.to_pascal_case(ns)}_"
|
|
144
|
+
f"{self.naming.to_pascal_case(name)}")
|
|
145
|
+
elif self.layer_name:
|
|
146
|
+
full = (f"RoleFunc_{self.layer_name}_"
|
|
147
|
+
f"{self.naming.to_pascal_case(rf_name)}")
|
|
148
|
+
else:
|
|
149
|
+
full = f"RoleFunc_{self.naming.to_pascal_case(rf_name)}"
|
|
150
|
+
return f"(void){full}(NULL, ctx)"
|
|
151
|
+
|
|
152
|
+
def _fire_event_call(self, event_name: str) -> str:
|
|
153
|
+
"""Generate FIRE_EVENT_<Layer>(ctx, EVENT_<Layer>_<EVT>).
|
|
154
|
+
|
|
155
|
+
The FIRE_EVENT_<Layer> macro is defined as:
|
|
156
|
+
#define FIRE_EVENT_Driver(ctx, evt) ...
|
|
157
|
+
so `ctx` must be passed explicitly.
|
|
158
|
+
|
|
159
|
+
The event enum is named EVENT_<Layer>_<UPPER_SNAKE>.
|
|
160
|
+
"""
|
|
161
|
+
if not event_name:
|
|
162
|
+
return ""
|
|
163
|
+
# event_name is expected to be "Layer.EVENT" or "EVENT"
|
|
164
|
+
if "." in event_name:
|
|
165
|
+
layer, evt = event_name.split(".", 1)
|
|
166
|
+
else:
|
|
167
|
+
layer, evt = self.layer_name, event_name
|
|
168
|
+
evt_upper = self.naming.to_upper_snake(evt)
|
|
169
|
+
return f"FIRE_EVENT_{layer}(ctx, EVENT_{layer}_{evt_upper})"
|
|
170
|
+
|
|
171
|
+
# ------------------------------------------------------------------
|
|
172
|
+
# Per-action code emission
|
|
173
|
+
# ------------------------------------------------------------------
|
|
174
|
+
def _action_line(self, action: ActionStep, indent: str = " ") -> str:
|
|
175
|
+
"""Emit one ActionStep as a C statement (with optional condition)."""
|
|
176
|
+
atype = getattr(action, "action_type", "role") or "role"
|
|
177
|
+
cond = (getattr(action, "condition", "") or "").strip()
|
|
178
|
+
|
|
179
|
+
if atype == "fire_event":
|
|
180
|
+
call = self._fire_event_call(
|
|
181
|
+
getattr(action, "event_name", "") or "")
|
|
182
|
+
if not call:
|
|
183
|
+
return ""
|
|
184
|
+
elif atype == "custom":
|
|
185
|
+
# Custom is not emitted from GUI actions
|
|
186
|
+
return ""
|
|
187
|
+
else:
|
|
188
|
+
rf = _extract_rf_name(action)
|
|
189
|
+
call = self._rf_call(rf)
|
|
190
|
+
if not call:
|
|
191
|
+
return ""
|
|
192
|
+
|
|
193
|
+
if cond:
|
|
194
|
+
return (f"{indent}if ({cond}) {{\n"
|
|
195
|
+
f"{indent} {call};\n"
|
|
196
|
+
f"{indent}}}\n")
|
|
197
|
+
return f"{indent}{call};\n"
|
|
198
|
+
|
|
199
|
+
def _emit_actions(self, actions: List[ActionStep]) -> str:
|
|
200
|
+
"""Emit all GUI-edited actions for one kind (Entry / Exit / Do)."""
|
|
201
|
+
if not actions:
|
|
202
|
+
return ""
|
|
203
|
+
parts = [" /* --- GUI-edited actions (regenerated) --- */\n"]
|
|
204
|
+
for a in actions:
|
|
205
|
+
line = self._action_line(a)
|
|
206
|
+
if line:
|
|
207
|
+
parts.append(line)
|
|
208
|
+
parts.append(" /* --- end GUI-edited actions --- */\n\n")
|
|
209
|
+
return "".join(parts)
|
|
210
|
+
|
|
211
|
+
# ------------------------------------------------------------------
|
|
212
|
+
# Per-state function
|
|
213
|
+
# ------------------------------------------------------------------
|
|
214
|
+
def _state_func_name(self, kind: str, state_name: str) -> str:
|
|
215
|
+
"""<Layer>_<Kind>_<State>"""
|
|
216
|
+
return (f"{self.layer_name}_{kind}_"
|
|
217
|
+
f"{self._state_suffix(state_name)}")
|
|
218
|
+
|
|
219
|
+
def _custom_marker_name(self, kind: str, state_name: str) -> str:
|
|
220
|
+
return (f"{self.layer_name}_{kind}_"
|
|
221
|
+
f"{self._state_suffix(state_name)}_custom")
|
|
222
|
+
|
|
223
|
+
def _generate_state_func(self, kind: str, state: State,
|
|
224
|
+
actions: List[ActionStep]) -> str:
|
|
225
|
+
"""Generate one per-state function (Entry / Exit / Do)."""
|
|
226
|
+
func_name = self._state_func_name(kind, state.name)
|
|
227
|
+
marker = self._custom_marker_name(kind, state.name)
|
|
228
|
+
|
|
229
|
+
parts = []
|
|
230
|
+
parts.append(f"static void {func_name}(SystemContext_t *ctx)\n")
|
|
231
|
+
parts.append("{\n")
|
|
232
|
+
parts.append(self._emit_actions(actions))
|
|
233
|
+
parts.append(
|
|
234
|
+
f" /* --- user custom code (preserved) --- */\n")
|
|
235
|
+
parts.append(
|
|
236
|
+
f" /* [[STABLE_USER_CODE_START:{marker}]] */\n")
|
|
237
|
+
parts.append(" (void)ctx;\n")
|
|
238
|
+
parts.append(
|
|
239
|
+
f" /* [[STABLE_USER_CODE_END:{marker}]] */\n")
|
|
240
|
+
parts.append("}\n")
|
|
241
|
+
return "".join(parts)
|
|
242
|
+
|
|
243
|
+
# ------------------------------------------------------------------
|
|
244
|
+
# Entry / Exit / Do groups
|
|
245
|
+
# ------------------------------------------------------------------
|
|
246
|
+
def _collect_actions_by_kind(self, sm: StateMachine,
|
|
247
|
+
kind: str) -> Dict[str, List[ActionStep]]:
|
|
248
|
+
"""Return {state_name: [ActionStep, ...]} for the given kind."""
|
|
249
|
+
result: Dict[str, List[ActionStep]] = {}
|
|
250
|
+
for state in sm.states.values():
|
|
251
|
+
if kind == "Entry":
|
|
252
|
+
actions = list(getattr(state, "entry", []) or [])
|
|
253
|
+
elif kind == "Exit":
|
|
254
|
+
actions = list(getattr(state, "exit", []) or [])
|
|
255
|
+
elif kind == "Do":
|
|
256
|
+
actions = list(getattr(state, "do_actions", []) or [])
|
|
257
|
+
else:
|
|
258
|
+
actions = []
|
|
259
|
+
result[state.name] = actions
|
|
260
|
+
return result
|
|
261
|
+
|
|
262
|
+
# ------------------------------------------------------------------
|
|
263
|
+
# Forward declarations
|
|
264
|
+
# ------------------------------------------------------------------
|
|
265
|
+
def _generate_forward_decls(self, sm: StateMachine) -> str:
|
|
266
|
+
parts = []
|
|
267
|
+
for kind in ("Entry", "Exit", "Do"):
|
|
268
|
+
parts.append(
|
|
269
|
+
f"/* ---- {kind} forward declarations ---- */\n")
|
|
270
|
+
for state in sm.states.values():
|
|
271
|
+
func_name = self._state_func_name(kind, state.name)
|
|
272
|
+
parts.append(
|
|
273
|
+
f"static void {func_name}(SystemContext_t *ctx);\n")
|
|
274
|
+
parts.append("\n")
|
|
275
|
+
return "".join(parts)
|
|
276
|
+
|
|
277
|
+
# ------------------------------------------------------------------
|
|
278
|
+
# Dispatch table (ordered init, C89)
|
|
279
|
+
# ------------------------------------------------------------------
|
|
280
|
+
def _generate_table(self, kind: str, sm: StateMachine) -> str:
|
|
281
|
+
"""Generate one ordered-init dispatch table."""
|
|
282
|
+
layer = self.layer_name or "System"
|
|
283
|
+
func_type = self._func_type()
|
|
284
|
+
state_max = self._state_max()
|
|
285
|
+
table_name = f"g_{layer}_{kind}Table"
|
|
286
|
+
|
|
287
|
+
parts = []
|
|
288
|
+
parts.append(
|
|
289
|
+
f"static const {func_type} {table_name}[{state_max}] = {{\n")
|
|
290
|
+
|
|
291
|
+
# Ordered by enum definition order.
|
|
292
|
+
# StateMachine.states is a dict (insertion order); we rely on
|
|
293
|
+
# the same iteration order used by enum generation.
|
|
294
|
+
for state in sm.states.values():
|
|
295
|
+
func_name = self._state_func_name(kind, state.name)
|
|
296
|
+
parts.append(
|
|
297
|
+
f" {func_name}, /* {self._state_enum(state.name)} */\n")
|
|
298
|
+
parts.append("};\n")
|
|
299
|
+
return "".join(parts)
|
|
300
|
+
|
|
301
|
+
# ------------------------------------------------------------------
|
|
302
|
+
# Dispatcher
|
|
303
|
+
# ------------------------------------------------------------------
|
|
304
|
+
def _generate_dispatcher(self, kind: str, sm: StateMachine) -> str:
|
|
305
|
+
layer = self.layer_name or "System"
|
|
306
|
+
func_type = self._func_type()
|
|
307
|
+
state_type = self._state_type()
|
|
308
|
+
state_max = self._state_max()
|
|
309
|
+
table_name = f"g_{layer}_{kind}Table"
|
|
310
|
+
|
|
311
|
+
parts = []
|
|
312
|
+
parts.append(f"void {layer}_{kind}({state_type} state, "
|
|
313
|
+
f"SystemContext_t *ctx)\n")
|
|
314
|
+
parts.append("{\n")
|
|
315
|
+
parts.append(f" if ((unsigned)state < (unsigned){state_max}) {{\n")
|
|
316
|
+
parts.append(f" {func_type} fn = {table_name}[state];\n")
|
|
317
|
+
parts.append(" if (fn != NULL) {\n")
|
|
318
|
+
parts.append(" fn(ctx);\n")
|
|
319
|
+
parts.append(" }\n")
|
|
320
|
+
parts.append(" }\n")
|
|
321
|
+
parts.append("}\n")
|
|
322
|
+
return "".join(parts)
|
|
323
|
+
|
|
324
|
+
# ------------------------------------------------------------------
|
|
325
|
+
# Public API
|
|
326
|
+
# ------------------------------------------------------------------
|
|
327
|
+
def generate_header(self, sm: StateMachine) -> str:
|
|
328
|
+
"""Generate statable_state_actions_<Layer>.h"""
|
|
329
|
+
layer = self.layer_name or "System"
|
|
330
|
+
T = self.HEADER_TEMPLATES
|
|
331
|
+
guard = T['guard_start'].replace("{LAYER}", layer.upper())
|
|
332
|
+
|
|
333
|
+
parts = []
|
|
334
|
+
parts.append(T['file_comment'].format(layer=layer))
|
|
335
|
+
parts.append("\n\n")
|
|
336
|
+
parts.append(guard)
|
|
337
|
+
parts.append("\n")
|
|
338
|
+
parts.append(T['include_common'])
|
|
339
|
+
parts.append("\n")
|
|
340
|
+
parts.append(T['include_layer'].format(layer=layer))
|
|
341
|
+
parts.append("\n\n")
|
|
342
|
+
parts.append(
|
|
343
|
+
"/* Entry / Exit / Do dispatch (state actions) */\n")
|
|
344
|
+
parts.append(T['decl_entry'].format(layer=layer))
|
|
345
|
+
parts.append("\n")
|
|
346
|
+
parts.append(T['decl_exit'].format(layer=layer))
|
|
347
|
+
parts.append("\n")
|
|
348
|
+
parts.append(T['decl_do'].format(layer=layer))
|
|
349
|
+
parts.append("\n\n")
|
|
350
|
+
parts.append(T['guard_end'].replace("{LAYER}", layer.upper()))
|
|
351
|
+
parts.append("\n")
|
|
352
|
+
return "".join(parts)
|
|
353
|
+
|
|
354
|
+
def generate_source(self, sm: StateMachine) -> str:
|
|
355
|
+
"""Generate statable_state_actions_<Layer>.c"""
|
|
356
|
+
layer = self.layer_name or "System"
|
|
357
|
+
T = self.SOURCE_TEMPLATES
|
|
358
|
+
|
|
359
|
+
parts = []
|
|
360
|
+
parts.append(T['file_comment'].format(layer=layer))
|
|
361
|
+
parts.append("\n\n")
|
|
362
|
+
parts.append(T['include_own'].format(layer=layer))
|
|
363
|
+
parts.append("\n\n")
|
|
364
|
+
parts.append(T['type_typedef'].format(layer=layer))
|
|
365
|
+
parts.append("\n\n")
|
|
366
|
+
|
|
367
|
+
# Forward declarations
|
|
368
|
+
parts.append(self._generate_forward_decls(sm))
|
|
369
|
+
parts.append("\n")
|
|
370
|
+
|
|
371
|
+
# Tables
|
|
372
|
+
for kind in ("Entry", "Exit", "Do"):
|
|
373
|
+
parts.append(
|
|
374
|
+
f"/* ---- {kind} dispatch table ---- */\n")
|
|
375
|
+
parts.append(self._generate_table(kind, sm))
|
|
376
|
+
parts.append("\n")
|
|
377
|
+
|
|
378
|
+
# Dispatchers
|
|
379
|
+
for kind in ("Entry", "Exit", "Do"):
|
|
380
|
+
parts.append(
|
|
381
|
+
f"/* ---- {kind} dispatcher ---- */\n")
|
|
382
|
+
parts.append(self._generate_dispatcher(kind, sm))
|
|
383
|
+
parts.append("\n")
|
|
384
|
+
|
|
385
|
+
# Per-state functions
|
|
386
|
+
for kind in ("Entry", "Exit", "Do"):
|
|
387
|
+
actions_by_state = self._collect_actions_by_kind(sm, kind)
|
|
388
|
+
parts.append(
|
|
389
|
+
f"/* ---- {kind} functions (user-editable) ---- */\n")
|
|
390
|
+
for state in sm.states.values():
|
|
391
|
+
parts.append(self._generate_state_func(
|
|
392
|
+
kind, state, actions_by_state.get(state.name, [])))
|
|
393
|
+
parts.append("\n")
|
|
394
|
+
|
|
395
|
+
return "".join(parts)
|