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
|
@@ -0,0 +1,907 @@
|
|
|
1
|
+
# codegen/transition_generator.py
|
|
2
|
+
"""
|
|
3
|
+
State transition function generation module (v2.6 multi-transition + nesting).
|
|
4
|
+
|
|
5
|
+
Version: 2.6 (2026-09-20 / MISRA 17.3 final fix)
|
|
6
|
+
- Fix (MISRA C:2012 Rule 17.3): the LOG_ERROR call in the generated
|
|
7
|
+
StateMachine_GetNextEvent_<Layer> body was reported as an implicit
|
|
8
|
+
function declaration by cppcheck even after statable_types_common.h
|
|
9
|
+
(which defines LOG_ERROR as a no-op variadic macro) was included in
|
|
10
|
+
the .c file. cppcheck apparently does not expand the variadic macro
|
|
11
|
+
and treats the call site as an undeclared function.
|
|
12
|
+
The call has therefore been removed and replaced with a comment;
|
|
13
|
+
consecutive_count is still used by the surrounding code so no
|
|
14
|
+
unused-variable warning is introduced.
|
|
15
|
+
|
|
16
|
+
Version: 2.5 (2026-09-20 / MISRA fixes + _handled emission fix)
|
|
17
|
+
Version: 2.4 (2026-09-20 / condition-aware role function calls)
|
|
18
|
+
Version: 2.2.1 (2026-09-19)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import sys
|
|
22
|
+
import os
|
|
23
|
+
import logging
|
|
24
|
+
from string import Template
|
|
25
|
+
from typing import Dict, List, Optional
|
|
26
|
+
|
|
27
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
28
|
+
|
|
29
|
+
from statable.model import State, Event, Transition
|
|
30
|
+
from statable.state_machine import StateMachine
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
from .type_mapper import CTypeMapper
|
|
34
|
+
from .naming_convention import CNamingConvention
|
|
35
|
+
from .code_templates import CodeTemplates
|
|
36
|
+
except ImportError:
|
|
37
|
+
from type_mapper import CTypeMapper
|
|
38
|
+
from naming_convention import CNamingConvention
|
|
39
|
+
from code_templates import CodeTemplates
|
|
40
|
+
|
|
41
|
+
logger = logging.getLogger(__name__)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def ensure_list(value) -> List[str]:
|
|
45
|
+
"""Normalize str/None/list to List[str]."""
|
|
46
|
+
if value is None:
|
|
47
|
+
return []
|
|
48
|
+
if isinstance(value, str):
|
|
49
|
+
s = value.strip()
|
|
50
|
+
return [s] if s else []
|
|
51
|
+
if isinstance(value, list):
|
|
52
|
+
return [str(x) for x in value if str(x).strip()]
|
|
53
|
+
return [str(value)]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class TransitionGenerator:
|
|
57
|
+
"""State transition function generator (v2.6 multi-transition support)"""
|
|
58
|
+
|
|
59
|
+
SHORT_CELL_NAMES = True
|
|
60
|
+
TABLE_MIN_COL_WIDTH = 14
|
|
61
|
+
DEFAULT_TABLE_TYPE = 'array'
|
|
62
|
+
DEFAULT_GENERATION_STYLE = 'table_driven'
|
|
63
|
+
|
|
64
|
+
CELL_TEMPLATES = {
|
|
65
|
+
'header': Template(
|
|
66
|
+
'/**\n'
|
|
67
|
+
' * @brief Cell transition: $state_enum -[$event_enum]-> (multi)\n'
|
|
68
|
+
' * @note Transition count: $transition_count\n'
|
|
69
|
+
' */\n'
|
|
70
|
+
),
|
|
71
|
+
'signature': Template(
|
|
72
|
+
'static $state_type $func_name(\n'
|
|
73
|
+
' const $context_type *transition,\n'
|
|
74
|
+
' SystemContext_t *ctx\n'
|
|
75
|
+
')\n'
|
|
76
|
+
'{\n'
|
|
77
|
+
),
|
|
78
|
+
'body_open': Template(
|
|
79
|
+
' $state_type next_state = transition->from_state;\n'
|
|
80
|
+
' bool _handled = false;\n'
|
|
81
|
+
),
|
|
82
|
+
'body_open_simple': Template(
|
|
83
|
+
' $state_type next_state = transition->from_state;\n'
|
|
84
|
+
),
|
|
85
|
+
'cell_actions_header': Template(
|
|
86
|
+
'\n'
|
|
87
|
+
' /* ===== Cell actions ($trigger) ===== */\n'
|
|
88
|
+
),
|
|
89
|
+
'cell_action_call': Template(' $call;\n'),
|
|
90
|
+
'body_close': (
|
|
91
|
+
'\n'
|
|
92
|
+
' return next_state;\n'
|
|
93
|
+
'}\n'
|
|
94
|
+
),
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
CELL_PROTO_TEMPLATES = {
|
|
98
|
+
'section_comment': '/* ===== Cell transition function forward declarations ===== */\n',
|
|
99
|
+
'prototype': Template(
|
|
100
|
+
'static $state_type $func_name(\n'
|
|
101
|
+
' const $context_type *transition,\n'
|
|
102
|
+
' SystemContext_t *ctx);\n'
|
|
103
|
+
),
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
TABLE_TEMPLATES = {
|
|
107
|
+
'func_ptr_comment': '/* Transition function pointer type */\n',
|
|
108
|
+
'func_ptr_typedef': Template(
|
|
109
|
+
'typedef $state_type (*$func_type)(\n'
|
|
110
|
+
' const $context_type *, SystemContext_t *);\n'
|
|
111
|
+
),
|
|
112
|
+
'table_comment': Template(
|
|
113
|
+
'\n'
|
|
114
|
+
'/* ============================================================== */\n'
|
|
115
|
+
'/* State transition table: rows=$row_desc, cols=$col_desc */\n'
|
|
116
|
+
'/* Global variable (externally accessible) */\n'
|
|
117
|
+
'/* ============================================================== */\n'
|
|
118
|
+
),
|
|
119
|
+
'table_open': Template(
|
|
120
|
+
'const $func_type $table_name\n'
|
|
121
|
+
' [$state_max][$event_max] = {\n'
|
|
122
|
+
),
|
|
123
|
+
'table_close': '};\n',
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
TABLE_HEADER_TEMPLATES = {
|
|
127
|
+
'header_comment': Template(
|
|
128
|
+
'/**\n'
|
|
129
|
+
' * @brief Transition table ($layer layer)\n'
|
|
130
|
+
' * @note rows=$row_desc, cols=$col_desc\n'
|
|
131
|
+
' */\n'
|
|
132
|
+
),
|
|
133
|
+
'func_ptr_typedef': Template(
|
|
134
|
+
'typedef $state_type (*$func_type)(\n'
|
|
135
|
+
' const $context_type *, SystemContext_t *);\n'
|
|
136
|
+
),
|
|
137
|
+
'extern_decl': Template(
|
|
138
|
+
'extern const $func_type $table_name\n'
|
|
139
|
+
' [$state_max][$event_max];\n'
|
|
140
|
+
),
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
DICT_TEMPLATES = {
|
|
144
|
+
'struct_comment': (
|
|
145
|
+
'/* Transition function dictionary entry */\n'
|
|
146
|
+
'/* Used for debugging, logging, and dynamic dispatch */\n'
|
|
147
|
+
),
|
|
148
|
+
'struct_open': (
|
|
149
|
+
'typedef struct {\n'
|
|
150
|
+
' const char *name;\n'
|
|
151
|
+
),
|
|
152
|
+
'struct_field_from': Template(' $state_type from_state;\n'),
|
|
153
|
+
'struct_field_event': Template(' $event_type event;\n'),
|
|
154
|
+
'struct_field_target': Template(' $state_type default_target;\n'),
|
|
155
|
+
'struct_field_condition': ' const char *condition;\n',
|
|
156
|
+
'struct_field_func': Template(' $func_type func;\n'),
|
|
157
|
+
'struct_close': Template('}} $dict_type;\n'),
|
|
158
|
+
'dict_comment': '\n/* Transition function dictionary */\n',
|
|
159
|
+
'dict_open': Template('static const $dict_type $dict_name[] = {\n'),
|
|
160
|
+
'dict_entry': Template(
|
|
161
|
+
' { "$trans_name",\n'
|
|
162
|
+
' $state_enum, $event_enum, $target_enum,\n'
|
|
163
|
+
' "$condition", $func_name },\n'
|
|
164
|
+
),
|
|
165
|
+
'dict_close': '};\n',
|
|
166
|
+
'dict_size': Template(
|
|
167
|
+
'\n'
|
|
168
|
+
'#define $size_macro \\\n'
|
|
169
|
+
' (sizeof($dict_name) / sizeof($dict_name[0]))\n'
|
|
170
|
+
),
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
PROCESS_TEMPLATES = {
|
|
174
|
+
'comment': Template(
|
|
175
|
+
'/**\n'
|
|
176
|
+
' * @brief $layer layer state transition processing\n'
|
|
177
|
+
' * @param current_state Current state\n'
|
|
178
|
+
' * @param event Event that occurred\n'
|
|
179
|
+
' * @param ctx System context pointer\n'
|
|
180
|
+
' * @return State after transition\n'
|
|
181
|
+
' */\n'
|
|
182
|
+
),
|
|
183
|
+
'signature': Template(
|
|
184
|
+
'$state_type $func_name(\n'
|
|
185
|
+
' $state_type current_state,\n'
|
|
186
|
+
' $event_type event,\n'
|
|
187
|
+
' SystemContext_t *ctx\n'
|
|
188
|
+
')\n'
|
|
189
|
+
),
|
|
190
|
+
'body': Template(
|
|
191
|
+
'{\n'
|
|
192
|
+
' $context_type transition = {\n'
|
|
193
|
+
' .from_state = current_state,\n'
|
|
194
|
+
' .event = event,\n'
|
|
195
|
+
' };\n'
|
|
196
|
+
' $func_type func = $table_name[current_state][event];\n'
|
|
197
|
+
' $state_type next_state = current_state;\n'
|
|
198
|
+
' if (func != NULL) {\n'
|
|
199
|
+
' next_state = func(&transition, ctx);\n'
|
|
200
|
+
' }\n'
|
|
201
|
+
' /* [v2.7.0] State actions: Exit(old) / Entry(new) */\n'
|
|
202
|
+
' if (next_state != current_state) {\n'
|
|
203
|
+
' ${layer}_Exit(current_state, ctx);\n'
|
|
204
|
+
' ${layer}_Entry(next_state, ctx);\n'
|
|
205
|
+
' }\n'
|
|
206
|
+
' return next_state;\n'
|
|
207
|
+
'}\n'
|
|
208
|
+
),
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
# [v2.6 / MISRA 17.3] LOG_ERROR removed from the generated body.
|
|
212
|
+
# cppcheck does not expand the variadic LOG_ERROR macro from
|
|
213
|
+
# statable_types_common.h and reports it as an implicit function.
|
|
214
|
+
GET_NEXT_TEMPLATES = {
|
|
215
|
+
'comment': Template(
|
|
216
|
+
'/**\n'
|
|
217
|
+
' * @brief Get next event for $layer layer\n'
|
|
218
|
+
' * @param ctx System context pointer\n'
|
|
219
|
+
' * @return Next event ($event_none if no pending event)\n'
|
|
220
|
+
' */\n'
|
|
221
|
+
),
|
|
222
|
+
'signature': Template('$event_type $func_name(SystemContext_t *ctx)\n'),
|
|
223
|
+
'body': Template(
|
|
224
|
+
'{\n'
|
|
225
|
+
' static uint8_t consecutive_count = 0;\n'
|
|
226
|
+
' $event_type evt = $event_none;\n'
|
|
227
|
+
'\n'
|
|
228
|
+
' /* [F-3 S2] Read-then-clear own layer\'s pending slot. */\n'
|
|
229
|
+
' STATABLE_ENTER_CRITICAL();\n'
|
|
230
|
+
' if (ctx->pending_event_valid_$layer) {\n'
|
|
231
|
+
' evt = ($event_type)ctx->pending_event_$layer;\n'
|
|
232
|
+
' ctx->pending_event_valid_$layer = false;\n'
|
|
233
|
+
' }\n'
|
|
234
|
+
' STATABLE_EXIT_CRITICAL();\n'
|
|
235
|
+
'\n'
|
|
236
|
+
' if (evt != $event_none) {\n'
|
|
237
|
+
' consecutive_count++;\n'
|
|
238
|
+
' if (consecutive_count > MAX_CONSECUTIVE_PENDING_EVENTS) {\n'
|
|
239
|
+
' /* Pending event chain too long: force reset */\n'
|
|
240
|
+
' consecutive_count = 0;\n'
|
|
241
|
+
' return $event_none;\n'
|
|
242
|
+
' }\n'
|
|
243
|
+
' return evt;\n'
|
|
244
|
+
' }\n'
|
|
245
|
+
'\n'
|
|
246
|
+
' /* [C-52/C-54] Drain this layer\'s queue (protected). */\n'
|
|
247
|
+
' STATABLE_ENTER_CRITICAL();\n'
|
|
248
|
+
' if (ctx->queue_$layer.count > 0U) {\n'
|
|
249
|
+
' evt = ($event_type)ctx->queue_$layer.buffer[ctx->queue_$layer.head];\n'
|
|
250
|
+
' ctx->queue_$layer.head = (uint16_t)((ctx->queue_$layer.head + 1U) % STATABLE_LAYER_QUEUE_SIZE);\n'
|
|
251
|
+
' ctx->queue_$layer.count--;\n'
|
|
252
|
+
' STATABLE_EXIT_CRITICAL();\n'
|
|
253
|
+
' consecutive_count = 0;\n'
|
|
254
|
+
' return evt;\n'
|
|
255
|
+
' }\n'
|
|
256
|
+
' STATABLE_EXIT_CRITICAL();\n'
|
|
257
|
+
'\n'
|
|
258
|
+
' consecutive_count = 0;\n'
|
|
259
|
+
' return $event_none;\n'
|
|
260
|
+
'}\n'
|
|
261
|
+
),
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
def __init__(self):
|
|
265
|
+
self.mapper = CTypeMapper()
|
|
266
|
+
self.naming = CNamingConvention()
|
|
267
|
+
self.templates = CodeTemplates()
|
|
268
|
+
self.strings = self.templates.STRINGS
|
|
269
|
+
self.formats = self.templates.FORMATS
|
|
270
|
+
self.layer_name: str = ""
|
|
271
|
+
self._current_state_machine: Optional[StateMachine] = None
|
|
272
|
+
|
|
273
|
+
self.table_generators: Dict[str, callable] = {
|
|
274
|
+
'array': self._generate_table_array,
|
|
275
|
+
'switch': self._generate_table_switch,
|
|
276
|
+
'dictionary': self._generate_table_dictionary,
|
|
277
|
+
}
|
|
278
|
+
self.process_generators: Dict[str, callable] = {
|
|
279
|
+
'table_driven': self._generate_process_table_driven,
|
|
280
|
+
'switch_case': self._generate_process_switch_case,
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
def set_layer(self, layer_name: str):
|
|
284
|
+
self.layer_name = layer_name
|
|
285
|
+
|
|
286
|
+
def _log_debug(self, message, level='debug'):
|
|
287
|
+
log_func = getattr(logger, level, logger.debug)
|
|
288
|
+
log_func(message)
|
|
289
|
+
|
|
290
|
+
def _state_enum(self, state_name: str) -> str:
|
|
291
|
+
s = self.naming.to_pascal_case(state_name) if state_name else "Unknown"
|
|
292
|
+
return f"STATE_{self.layer_name}_{s}" if self.layer_name else f"STATE_{s}"
|
|
293
|
+
|
|
294
|
+
def _event_enum(self, event_name: str) -> str:
|
|
295
|
+
e = self.naming.to_upper_snake(event_name) if event_name else "NONE"
|
|
296
|
+
return f"EVENT_{self.layer_name}_{e}" if self.layer_name else f"EVENT_{e}"
|
|
297
|
+
|
|
298
|
+
def _state_type(self) -> str:
|
|
299
|
+
return f"STATE_{self.layer_name}_t" if self.layer_name else "STATE_t"
|
|
300
|
+
|
|
301
|
+
def _event_type(self) -> str:
|
|
302
|
+
return f"EVENT_{self.layer_name}_t" if self.layer_name else "EVENT_t"
|
|
303
|
+
|
|
304
|
+
def _context_type(self) -> str:
|
|
305
|
+
return f"TransitionContext_{self.layer_name}_t" if self.layer_name else "TransitionContext_t"
|
|
306
|
+
|
|
307
|
+
def _func_type(self) -> str:
|
|
308
|
+
return f"TransitionFunc_{self.layer_name}_t" if self.layer_name else "TransitionFunc_t"
|
|
309
|
+
|
|
310
|
+
def _table_name(self) -> str:
|
|
311
|
+
return f"transition_table_{self.layer_name}" if self.layer_name else "transition_matrix"
|
|
312
|
+
|
|
313
|
+
def _state_max(self) -> str:
|
|
314
|
+
return f"STATE_{self.layer_name}_MAX" if self.layer_name else "STATE_MAX"
|
|
315
|
+
|
|
316
|
+
def _event_max(self) -> str:
|
|
317
|
+
return f"EVENT_{self.layer_name}_MAX" if self.layer_name else "EVENT_MAX"
|
|
318
|
+
|
|
319
|
+
def _cell_func_name(self, state_name: str, event_name: str) -> str:
|
|
320
|
+
s = self.naming.to_pascal_case(state_name) if state_name else "Unknown"
|
|
321
|
+
e = self.naming.to_upper_snake(event_name) if event_name else "NONE"
|
|
322
|
+
if self.SHORT_CELL_NAMES:
|
|
323
|
+
return f"t_{s}_{e}"
|
|
324
|
+
return f"transition_{self.layer_name}_{s}_{e}" if self.layer_name \
|
|
325
|
+
else f"transition_{s}_{e}"
|
|
326
|
+
|
|
327
|
+
# ==================================================================
|
|
328
|
+
# Role function call generation (context-aware)
|
|
329
|
+
# ==================================================================
|
|
330
|
+
def _resolve_role_func_name(self, func_name: str) -> str:
|
|
331
|
+
if not func_name or not func_name.strip():
|
|
332
|
+
return ""
|
|
333
|
+
if func_name.startswith("RoleFunc_"):
|
|
334
|
+
return func_name
|
|
335
|
+
if "." in func_name:
|
|
336
|
+
ns, name = func_name.split(".", 1)
|
|
337
|
+
ns_pascal = self.naming.to_pascal_case(ns)
|
|
338
|
+
name_pascal = self.naming.to_pascal_case(name)
|
|
339
|
+
return f"RoleFunc_{ns_pascal}_{name_pascal}"
|
|
340
|
+
pascal = self.naming.to_pascal_case(func_name)
|
|
341
|
+
if self.layer_name:
|
|
342
|
+
return f"RoleFunc_{self.layer_name}_{pascal}"
|
|
343
|
+
return f"RoleFunc_{pascal}"
|
|
344
|
+
|
|
345
|
+
def _role_func_call_expr(self, func_name: str) -> str:
|
|
346
|
+
full = self._resolve_role_func_name(func_name)
|
|
347
|
+
if not full:
|
|
348
|
+
return "/* empty role function reference */"
|
|
349
|
+
return f"{full}(transition, ctx)"
|
|
350
|
+
|
|
351
|
+
def _role_func_call_action(self, func_name: str) -> str:
|
|
352
|
+
full = self._resolve_role_func_name(func_name)
|
|
353
|
+
if not full:
|
|
354
|
+
return "/* empty role function reference */"
|
|
355
|
+
return f"(void){full}(transition, ctx)"
|
|
356
|
+
|
|
357
|
+
def _role_func_call_bare(self, func_name: str) -> str:
|
|
358
|
+
return self._resolve_role_func_name(func_name)
|
|
359
|
+
|
|
360
|
+
def _extract_rf_name(self, action) -> str:
|
|
361
|
+
"""[v2.7.0] Extract role function name from ActionStep or str.
|
|
362
|
+
|
|
363
|
+
state.entry / state.exit are List[ActionStep] since v2.7.0,
|
|
364
|
+
but may still hold plain strings from legacy data.
|
|
365
|
+
"""
|
|
366
|
+
if action is None:
|
|
367
|
+
return ""
|
|
368
|
+
if isinstance(action, str):
|
|
369
|
+
return action
|
|
370
|
+
return getattr(action, "role_function", "") or ""
|
|
371
|
+
|
|
372
|
+
def _role_func_call(self, func_name: str) -> str:
|
|
373
|
+
return self._role_func_call_action(func_name)
|
|
374
|
+
|
|
375
|
+
# ==================================================================
|
|
376
|
+
# State entry / exit call generation
|
|
377
|
+
# ==================================================================
|
|
378
|
+
def _build_exit_call(self, state_name: str) -> str:
|
|
379
|
+
if not state_name or self._current_state_machine is None:
|
|
380
|
+
return ''
|
|
381
|
+
state = self._current_state_machine.states.get(state_name)
|
|
382
|
+
if state is None:
|
|
383
|
+
return ''
|
|
384
|
+
# [v2.7.0] state.exit is List[ActionStep]; extract role_function
|
|
385
|
+
exit_list = getattr(state, 'exit', []) or []
|
|
386
|
+
if not exit_list:
|
|
387
|
+
return ''
|
|
388
|
+
lines = []
|
|
389
|
+
for action in exit_list:
|
|
390
|
+
fn = self._extract_rf_name(action)
|
|
391
|
+
if not fn:
|
|
392
|
+
continue
|
|
393
|
+
call = self._role_func_call_action(fn)
|
|
394
|
+
if call.startswith("/*"):
|
|
395
|
+
continue
|
|
396
|
+
lines.append(f' {call}; /* state exit */\n')
|
|
397
|
+
return ''.join(lines)
|
|
398
|
+
|
|
399
|
+
def _build_entry_call(self, state_name: str) -> str:
|
|
400
|
+
if not state_name or self._current_state_machine is None:
|
|
401
|
+
return ''
|
|
402
|
+
state = self._current_state_machine.states.get(state_name)
|
|
403
|
+
if state is None:
|
|
404
|
+
return ''
|
|
405
|
+
# [v2.7.0] state.entry is List[ActionStep]; extract role_function
|
|
406
|
+
entry_list = getattr(state, 'entry', []) or []
|
|
407
|
+
if not entry_list:
|
|
408
|
+
return ''
|
|
409
|
+
lines = []
|
|
410
|
+
for action in entry_list:
|
|
411
|
+
fn = self._extract_rf_name(action)
|
|
412
|
+
if not fn:
|
|
413
|
+
continue
|
|
414
|
+
call = self._role_func_call_action(fn)
|
|
415
|
+
if call.startswith("/*"):
|
|
416
|
+
continue
|
|
417
|
+
lines.append(f' {call}; /* state entry */\n')
|
|
418
|
+
return ''.join(lines)
|
|
419
|
+
|
|
420
|
+
# ==================================================================
|
|
421
|
+
# Transition block generation
|
|
422
|
+
# ==================================================================
|
|
423
|
+
def _build_transition_block(self, trans, idx: int,
|
|
424
|
+
use_handled: bool = True) -> str:
|
|
425
|
+
condition = getattr(trans, 'condition', '')
|
|
426
|
+
early_return = getattr(trans, 'early_return', False)
|
|
427
|
+
target = getattr(trans, 'target', '')
|
|
428
|
+
else_target = getattr(trans, 'else_target', '')
|
|
429
|
+
pre_actions = ensure_list(getattr(trans, 'pre_actions', []))
|
|
430
|
+
else_actions = ensure_list(getattr(trans, 'else_actions', []))
|
|
431
|
+
has_else = getattr(trans, 'has_else', True)
|
|
432
|
+
label = getattr(trans, 'label', '') or f"T{idx + 1}"
|
|
433
|
+
|
|
434
|
+
has_else_body = has_else and bool(else_target or else_actions)
|
|
435
|
+
cond_expr = condition if condition else "1"
|
|
436
|
+
commit_str = "Commit" if early_return else "Tentative"
|
|
437
|
+
|
|
438
|
+
emit_handled = early_return and use_handled
|
|
439
|
+
|
|
440
|
+
exit_calls = self._build_exit_call(trans.source)
|
|
441
|
+
entry_calls = self._build_entry_call(target)
|
|
442
|
+
else_entry_calls = self._build_entry_call(else_target)
|
|
443
|
+
|
|
444
|
+
pre_lines = ''.join(
|
|
445
|
+
f' {self._role_func_call_action(a)};\n'
|
|
446
|
+
for a in pre_actions
|
|
447
|
+
)
|
|
448
|
+
else_lines = ''.join(
|
|
449
|
+
f' {self._role_func_call_action(a)};\n'
|
|
450
|
+
for a in else_actions
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
target_line = (
|
|
454
|
+
f' next_state = {self._state_enum(target)};\n'
|
|
455
|
+
if target else ''
|
|
456
|
+
)
|
|
457
|
+
else_target_line = (
|
|
458
|
+
f' next_state = {self._state_enum(else_target)};\n'
|
|
459
|
+
if else_target else ' /* else target not set */\n'
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
lines = []
|
|
463
|
+
lines.append(f'\n /* ===== Transition[{label}] ({commit_str}) ===== */\n')
|
|
464
|
+
|
|
465
|
+
if emit_handled:
|
|
466
|
+
lines.append(f' if ((!_handled) && ({cond_expr})) {{\n')
|
|
467
|
+
else:
|
|
468
|
+
lines.append(f' if ({cond_expr}) {{\n')
|
|
469
|
+
|
|
470
|
+
lines.append(exit_calls)
|
|
471
|
+
lines.append(pre_lines)
|
|
472
|
+
lines.append(target_line)
|
|
473
|
+
lines.append(entry_calls)
|
|
474
|
+
if emit_handled:
|
|
475
|
+
lines.append(' _handled = true;\n')
|
|
476
|
+
|
|
477
|
+
if has_else_body:
|
|
478
|
+
if emit_handled:
|
|
479
|
+
lines.append(
|
|
480
|
+
f' }} else if ((!_handled) && (!({cond_expr}))) {{\n'
|
|
481
|
+
)
|
|
482
|
+
else:
|
|
483
|
+
lines.append(' } else {\n')
|
|
484
|
+
|
|
485
|
+
lines.append(exit_calls)
|
|
486
|
+
lines.append(else_lines)
|
|
487
|
+
lines.append(else_target_line)
|
|
488
|
+
lines.append(else_entry_calls)
|
|
489
|
+
if emit_handled:
|
|
490
|
+
lines.append(' _handled = true;\n')
|
|
491
|
+
|
|
492
|
+
lines.append(' }\n')
|
|
493
|
+
return ''.join(lines)
|
|
494
|
+
|
|
495
|
+
def _build_transitions_block(self, state, transitions, relations,
|
|
496
|
+
use_handled: bool = True) -> str:
|
|
497
|
+
parts = []
|
|
498
|
+
|
|
499
|
+
by_label = {}
|
|
500
|
+
for t in transitions:
|
|
501
|
+
lbl = getattr(t, 'label', '') or ''
|
|
502
|
+
if lbl:
|
|
503
|
+
by_label[lbl] = t
|
|
504
|
+
|
|
505
|
+
mentioned = set()
|
|
506
|
+
|
|
507
|
+
def collect(r):
|
|
508
|
+
for m in (getattr(r, 'members', None) or []):
|
|
509
|
+
mentioned.add(m)
|
|
510
|
+
for c in (getattr(r, 'children', None) or []):
|
|
511
|
+
collect(c)
|
|
512
|
+
|
|
513
|
+
for r in relations:
|
|
514
|
+
collect(r)
|
|
515
|
+
|
|
516
|
+
for r in relations:
|
|
517
|
+
parts.append(self._emit_relation(
|
|
518
|
+
r, by_label, indent_level=1, use_handled=use_handled,
|
|
519
|
+
))
|
|
520
|
+
|
|
521
|
+
for t in transitions:
|
|
522
|
+
lbl = getattr(t, 'label', '') or ''
|
|
523
|
+
if lbl and lbl in mentioned:
|
|
524
|
+
continue
|
|
525
|
+
parts.append(self._build_transition_block(
|
|
526
|
+
t, 0, use_handled=use_handled,
|
|
527
|
+
))
|
|
528
|
+
|
|
529
|
+
return ''.join(parts)
|
|
530
|
+
|
|
531
|
+
def _collect_child_labels(self, rel) -> set:
|
|
532
|
+
labels = set()
|
|
533
|
+
for child in (getattr(rel, 'children', None) or []):
|
|
534
|
+
labels.update(getattr(child, 'members', None) or [])
|
|
535
|
+
labels.update(self._collect_child_labels(child))
|
|
536
|
+
return labels
|
|
537
|
+
|
|
538
|
+
def _emit_relation(self, rel, by_label, indent_level: int = 1,
|
|
539
|
+
use_handled: bool = True) -> str:
|
|
540
|
+
parts = []
|
|
541
|
+
pad = ' ' * indent_level
|
|
542
|
+
|
|
543
|
+
shared_cond = (getattr(rel, 'shared_condition', '') or '').strip()
|
|
544
|
+
has_cond = bool(shared_cond)
|
|
545
|
+
|
|
546
|
+
if has_cond:
|
|
547
|
+
parts.append(
|
|
548
|
+
f'\n{pad}/* ===== Group (shared_condition) ===== */\n')
|
|
549
|
+
parts.append(f'{pad}if ({shared_cond}) {{\n')
|
|
550
|
+
inner_indent = indent_level + 1
|
|
551
|
+
else:
|
|
552
|
+
inner_indent = indent_level
|
|
553
|
+
|
|
554
|
+
child_labels = self._collect_child_labels(rel)
|
|
555
|
+
|
|
556
|
+
for label in (getattr(rel, 'members', None) or []):
|
|
557
|
+
if label in child_labels:
|
|
558
|
+
continue
|
|
559
|
+
t = by_label.get(label)
|
|
560
|
+
if t is None:
|
|
561
|
+
continue
|
|
562
|
+
block = self._build_transition_block(
|
|
563
|
+
t, 0, use_handled=use_handled,
|
|
564
|
+
)
|
|
565
|
+
extra = inner_indent - 1
|
|
566
|
+
if extra > 0:
|
|
567
|
+
block = self._indent_block(block, extra_indent=extra)
|
|
568
|
+
parts.append(block)
|
|
569
|
+
|
|
570
|
+
for child in (getattr(rel, 'children', None) or []):
|
|
571
|
+
parts.append(self._emit_relation(
|
|
572
|
+
child, by_label,
|
|
573
|
+
indent_level=inner_indent,
|
|
574
|
+
use_handled=use_handled,
|
|
575
|
+
))
|
|
576
|
+
|
|
577
|
+
if has_cond:
|
|
578
|
+
parts.append(f'{pad}}}\n')
|
|
579
|
+
|
|
580
|
+
return ''.join(parts)
|
|
581
|
+
|
|
582
|
+
def _indent_block(self, block: str, extra_indent: int = 1) -> str:
|
|
583
|
+
pad = ' ' * extra_indent
|
|
584
|
+
lines = block.split('\n')
|
|
585
|
+
return '\n'.join(
|
|
586
|
+
pad + line if line.strip() else line for line in lines
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
def generate_transition_cell_functions(self, state_machine: StateMachine) -> str:
|
|
590
|
+
self._current_state_machine = state_machine
|
|
591
|
+
|
|
592
|
+
cell_blocks = []
|
|
593
|
+
for state in state_machine.states.values():
|
|
594
|
+
for event in state_machine.events.values():
|
|
595
|
+
transitions = state_machine.get_transitions_for_cell(
|
|
596
|
+
state.name, event.name)
|
|
597
|
+
if not transitions:
|
|
598
|
+
continue
|
|
599
|
+
cell_blocks.append(
|
|
600
|
+
self._build_cell_function(state, event, transitions)
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
self._current_state_machine = None
|
|
604
|
+
return '\n'.join(cell_blocks)
|
|
605
|
+
|
|
606
|
+
def _build_cell_function(self, state, event, transitions) -> str:
|
|
607
|
+
T = self.CELL_TEMPLATES
|
|
608
|
+
|
|
609
|
+
sm = self._current_state_machine
|
|
610
|
+
cell_actions = sm.get_actions_for_cell(state.name, event.name) if sm else []
|
|
611
|
+
cell_relations = sm.get_relations_for_cell(state.name, event.name) if sm else []
|
|
612
|
+
|
|
613
|
+
early_return_count = sum(
|
|
614
|
+
1 for t in transitions if getattr(t, 'early_return', False)
|
|
615
|
+
)
|
|
616
|
+
needs_handled = early_return_count >= 2
|
|
617
|
+
|
|
618
|
+
parts = []
|
|
619
|
+
|
|
620
|
+
parts.append(T['header'].substitute(
|
|
621
|
+
state_enum=self._state_enum(state.name),
|
|
622
|
+
event_enum=self._event_enum(event.name),
|
|
623
|
+
transition_count=len(transitions),
|
|
624
|
+
))
|
|
625
|
+
|
|
626
|
+
parts.append(T['signature'].substitute(
|
|
627
|
+
state_type=self._state_type(),
|
|
628
|
+
func_name=self._cell_func_name(state.name, event.name),
|
|
629
|
+
context_type=self._context_type(),
|
|
630
|
+
))
|
|
631
|
+
|
|
632
|
+
if needs_handled:
|
|
633
|
+
parts.append(T['body_open'].substitute(state_type=self._state_type()))
|
|
634
|
+
else:
|
|
635
|
+
parts.append(T['body_open_simple'].substitute(
|
|
636
|
+
state_type=self._state_type()))
|
|
637
|
+
|
|
638
|
+
pre_actions_for_cell = [
|
|
639
|
+
a for a in cell_actions
|
|
640
|
+
if a.trigger in ("before_transitions", "always")
|
|
641
|
+
]
|
|
642
|
+
if pre_actions_for_cell:
|
|
643
|
+
parts.append(T['cell_actions_header'].substitute(
|
|
644
|
+
trigger="before_transitions"))
|
|
645
|
+
for a in pre_actions_for_cell:
|
|
646
|
+
parts.append(T['cell_action_call'].substitute(
|
|
647
|
+
call=self._role_func_call_action(a.role_function)
|
|
648
|
+
))
|
|
649
|
+
|
|
650
|
+
parts.append(self._build_transitions_block(
|
|
651
|
+
state, transitions, cell_relations,
|
|
652
|
+
use_handled=needs_handled,
|
|
653
|
+
))
|
|
654
|
+
|
|
655
|
+
after_actions = [a for a in cell_actions
|
|
656
|
+
if a.trigger == "after_transitions"]
|
|
657
|
+
if after_actions:
|
|
658
|
+
parts.append(T['cell_actions_header'].substitute(
|
|
659
|
+
trigger="after_transitions"))
|
|
660
|
+
for a in after_actions:
|
|
661
|
+
parts.append(T['cell_action_call'].substitute(
|
|
662
|
+
call=self._role_func_call_action(a.role_function)
|
|
663
|
+
))
|
|
664
|
+
|
|
665
|
+
parts.append(T['body_close'])
|
|
666
|
+
return ''.join(parts)
|
|
667
|
+
|
|
668
|
+
def generate_transition_cell_prototypes(self, state_machine: StateMachine) -> str:
|
|
669
|
+
T = self.CELL_PROTO_TEMPLATES
|
|
670
|
+
parts = [T['section_comment']]
|
|
671
|
+
for state in state_machine.states.values():
|
|
672
|
+
for event in state_machine.events.values():
|
|
673
|
+
transitions = state_machine.get_transitions_for_cell(
|
|
674
|
+
state.name, event.name)
|
|
675
|
+
if not transitions:
|
|
676
|
+
continue
|
|
677
|
+
parts.append(T['prototype'].substitute(
|
|
678
|
+
state_type=self._state_type(),
|
|
679
|
+
func_name=self._cell_func_name(state.name, event.name),
|
|
680
|
+
context_type=self._context_type(),
|
|
681
|
+
))
|
|
682
|
+
return ''.join(parts)
|
|
683
|
+
|
|
684
|
+
def generate_transition_table(self, state_machine: StateMachine,
|
|
685
|
+
table_type: str = None) -> str:
|
|
686
|
+
if table_type is None:
|
|
687
|
+
table_type = self.DEFAULT_TABLE_TYPE
|
|
688
|
+
generator = self.table_generators.get(table_type)
|
|
689
|
+
if generator is None or table_type != 'array':
|
|
690
|
+
generator = self.table_generators['array']
|
|
691
|
+
return generator(state_machine)
|
|
692
|
+
|
|
693
|
+
def _generate_table_array(self, state_machine: StateMachine) -> str:
|
|
694
|
+
T = self.TABLE_TEMPLATES
|
|
695
|
+
parts = []
|
|
696
|
+
states = list(state_machine.states.values())
|
|
697
|
+
events = list(state_machine.events.values())
|
|
698
|
+
func_type = self._func_type()
|
|
699
|
+
table_name = self._table_name()
|
|
700
|
+
|
|
701
|
+
cell_values = []
|
|
702
|
+
for state in states:
|
|
703
|
+
row = []
|
|
704
|
+
for event in events:
|
|
705
|
+
transitions = state_machine.get_transitions_for_cell(
|
|
706
|
+
state.name, event.name)
|
|
707
|
+
row.append(self._cell_func_name(state.name, event.name)
|
|
708
|
+
if transitions else "NULL")
|
|
709
|
+
cell_values.append(row)
|
|
710
|
+
|
|
711
|
+
state_col_width = max((len(s.name) for s in states), default=5)
|
|
712
|
+
event_headers = []
|
|
713
|
+
col_widths = []
|
|
714
|
+
for event_idx, event in enumerate(events):
|
|
715
|
+
event_disp = event.name if event.name else "NONE"
|
|
716
|
+
event_headers.append(event_disp)
|
|
717
|
+
w = max(len(event_disp), self.TABLE_MIN_COL_WIDTH)
|
|
718
|
+
for state_idx in range(len(states)):
|
|
719
|
+
w = max(w, len(cell_values[state_idx][event_idx]))
|
|
720
|
+
col_widths.append(w)
|
|
721
|
+
|
|
722
|
+
parts.append(T['func_ptr_comment'])
|
|
723
|
+
parts.append(T['func_ptr_typedef'].substitute(
|
|
724
|
+
state_type=self._state_type(),
|
|
725
|
+
func_type=func_type,
|
|
726
|
+
context_type=self._context_type(),
|
|
727
|
+
))
|
|
728
|
+
parts.append(T['table_comment'].substitute(
|
|
729
|
+
row_desc="state", col_desc="event"))
|
|
730
|
+
parts.append(T['table_open'].substitute(
|
|
731
|
+
func_type=func_type, table_name=table_name,
|
|
732
|
+
state_max=self._state_max(), event_max=self._event_max(),
|
|
733
|
+
))
|
|
734
|
+
|
|
735
|
+
header_state_pad = " " * (state_col_width + 2)
|
|
736
|
+
header_cells = [event_headers[i].ljust(col_widths[i])
|
|
737
|
+
for i in range(len(events))]
|
|
738
|
+
parts.append(
|
|
739
|
+
" /*" + " " + header_state_pad + " | " +
|
|
740
|
+
" | ".join(header_cells) + " */\n"
|
|
741
|
+
)
|
|
742
|
+
sep = " /* " + "-" * (state_col_width + 2) + "+"
|
|
743
|
+
for w in col_widths:
|
|
744
|
+
sep += "-" * w + "+"
|
|
745
|
+
sep = sep[:-1] + "*/\n"
|
|
746
|
+
parts.append(sep)
|
|
747
|
+
|
|
748
|
+
for state_idx, state in enumerate(states):
|
|
749
|
+
state_padded = state.name.ljust(state_col_width + 2)
|
|
750
|
+
cells = [cell_values[state_idx][event_idx].ljust(col_widths[event_idx])
|
|
751
|
+
for event_idx in range(len(events))]
|
|
752
|
+
row = f" /* {state_padded}*/ {{ "
|
|
753
|
+
row += ", ".join(cells)
|
|
754
|
+
row += " },\n"
|
|
755
|
+
parts.append(row)
|
|
756
|
+
|
|
757
|
+
parts.append(T['table_close'])
|
|
758
|
+
return ''.join(parts)
|
|
759
|
+
|
|
760
|
+
def _generate_table_switch(self, sm): return self._generate_table_array(sm)
|
|
761
|
+
def _generate_table_dictionary(self, sm): return self._generate_table_array(sm)
|
|
762
|
+
|
|
763
|
+
def generate_transition_table_header(self, state_machine: StateMachine) -> str:
|
|
764
|
+
T = self.TABLE_HEADER_TEMPLATES
|
|
765
|
+
parts = []
|
|
766
|
+
parts.append(T['header_comment'].substitute(
|
|
767
|
+
layer=self.layer_name or "system",
|
|
768
|
+
row_desc="state", col_desc="event",
|
|
769
|
+
))
|
|
770
|
+
parts.append(T['func_ptr_typedef'].substitute(
|
|
771
|
+
state_type=self._state_type(),
|
|
772
|
+
func_type=self._func_type(),
|
|
773
|
+
context_type=self._context_type(),
|
|
774
|
+
))
|
|
775
|
+
parts.append("")
|
|
776
|
+
parts.append(T['extern_decl'].substitute(
|
|
777
|
+
func_type=self._func_type(),
|
|
778
|
+
table_name=self._table_name(),
|
|
779
|
+
state_max=self._state_max(),
|
|
780
|
+
event_max=self._event_max(),
|
|
781
|
+
))
|
|
782
|
+
return ''.join(parts)
|
|
783
|
+
|
|
784
|
+
def generate_function_dictionary(self, state_machine: StateMachine) -> str:
|
|
785
|
+
T = self.DICT_TEMPLATES
|
|
786
|
+
parts = []
|
|
787
|
+
func_type = self._func_type()
|
|
788
|
+
state_type = self._state_type()
|
|
789
|
+
event_type = self._event_type()
|
|
790
|
+
dict_type = f"TransitionDictEntry_{self.layer_name}_t" if self.layer_name else "TransitionDictEntry_t"
|
|
791
|
+
dict_name = f"transition_dict_{self.layer_name}" if self.layer_name else "transition_dict"
|
|
792
|
+
size_macro = f"TRANSITION_DICT_{self.layer_name.upper()}_SIZE" if self.layer_name else "TRANSITION_DICT_SIZE"
|
|
793
|
+
|
|
794
|
+
parts.append(T['struct_comment'])
|
|
795
|
+
parts.append(T['struct_open'])
|
|
796
|
+
parts.append(T['struct_field_from'].substitute(state_type=state_type))
|
|
797
|
+
parts.append(T['struct_field_event'].substitute(event_type=event_type))
|
|
798
|
+
parts.append(T['struct_field_target'].substitute(state_type=state_type))
|
|
799
|
+
parts.append(T['struct_field_condition'])
|
|
800
|
+
parts.append(T['struct_field_func'].substitute(func_type=func_type))
|
|
801
|
+
parts.append(T['struct_close'].substitute(dict_type=dict_type))
|
|
802
|
+
|
|
803
|
+
parts.append(T['dict_comment'])
|
|
804
|
+
parts.append(T['dict_open'].substitute(
|
|
805
|
+
dict_type=dict_type, dict_name=dict_name))
|
|
806
|
+
|
|
807
|
+
for state in state_machine.states.values():
|
|
808
|
+
for event in state_machine.events.values():
|
|
809
|
+
transitions = state_machine.get_transitions_for_cell(
|
|
810
|
+
state.name, event.name)
|
|
811
|
+
if not transitions:
|
|
812
|
+
continue
|
|
813
|
+
for idx, trans in enumerate(transitions):
|
|
814
|
+
base_name = (
|
|
815
|
+
f"{self.layer_name}_{state.name}_{event.name or 'NONE'}"
|
|
816
|
+
if self.layer_name
|
|
817
|
+
else f"{state.name}_{event.name or 'NONE'}"
|
|
818
|
+
)
|
|
819
|
+
trans_name = (f"{base_name}_{idx}"
|
|
820
|
+
if len(transitions) > 1 else base_name)
|
|
821
|
+
target = trans.target or state.name
|
|
822
|
+
condition = trans.condition.replace('"', '\\"') if trans.condition else ""
|
|
823
|
+
parts.append(T['dict_entry'].substitute(
|
|
824
|
+
trans_name=trans_name,
|
|
825
|
+
state_enum=self._state_enum(state.name),
|
|
826
|
+
event_enum=self._event_enum(event.name),
|
|
827
|
+
target_enum=self._state_enum(target),
|
|
828
|
+
condition=condition,
|
|
829
|
+
func_name=self._cell_func_name(state.name, event.name),
|
|
830
|
+
))
|
|
831
|
+
parts.append(T['dict_close'])
|
|
832
|
+
parts.append(T['dict_size'].substitute(
|
|
833
|
+
size_macro=size_macro, dict_name=dict_name))
|
|
834
|
+
return ''.join(parts)
|
|
835
|
+
|
|
836
|
+
def generate_process_function(self, state_machine: StateMachine,
|
|
837
|
+
generation_style: str = None) -> str:
|
|
838
|
+
if generation_style is None:
|
|
839
|
+
generation_style = self.DEFAULT_GENERATION_STYLE
|
|
840
|
+
generator = self.process_generators.get(generation_style)
|
|
841
|
+
if generator is None or generation_style != 'table_driven':
|
|
842
|
+
generator = self.process_generators['table_driven']
|
|
843
|
+
return generator(state_machine)
|
|
844
|
+
|
|
845
|
+
def _generate_process_table_driven(self, state_machine: StateMachine) -> str:
|
|
846
|
+
T = self.PROCESS_TEMPLATES
|
|
847
|
+
func_name = (f"StateMachine_Process_{self.layer_name}"
|
|
848
|
+
if self.layer_name else "StateMachine_Process")
|
|
849
|
+
parts = []
|
|
850
|
+
parts.append(T['comment'].substitute(layer=self.layer_name or "system"))
|
|
851
|
+
parts.append(T['signature'].substitute(
|
|
852
|
+
state_type=self._state_type(),
|
|
853
|
+
func_name=func_name,
|
|
854
|
+
event_type=self._event_type(),
|
|
855
|
+
))
|
|
856
|
+
parts.append(T['body'].substitute(
|
|
857
|
+
context_type=self._context_type(),
|
|
858
|
+
func_type=self._func_type(),
|
|
859
|
+
table_name=self._table_name(),
|
|
860
|
+
state_type=self._state_type(),
|
|
861
|
+
layer=self.layer_name or "System",
|
|
862
|
+
))
|
|
863
|
+
return ''.join(parts)
|
|
864
|
+
|
|
865
|
+
def _generate_process_switch_case(self, sm):
|
|
866
|
+
return self._generate_process_table_driven(sm)
|
|
867
|
+
|
|
868
|
+
def generate_get_next_event_function(self, state_machine: StateMachine) -> str:
|
|
869
|
+
T = self.GET_NEXT_TEMPLATES
|
|
870
|
+
func_name = (f"StateMachine_GetNextEvent_{self.layer_name}"
|
|
871
|
+
if self.layer_name else "StateMachine_GetNextEvent")
|
|
872
|
+
event_none = self._event_enum("")
|
|
873
|
+
parts = []
|
|
874
|
+
parts.append(T['comment'].substitute(
|
|
875
|
+
layer=self.layer_name or "system",
|
|
876
|
+
event_none=event_none))
|
|
877
|
+
parts.append(T['signature'].substitute(
|
|
878
|
+
event_type=self._event_type(),
|
|
879
|
+
func_name=func_name))
|
|
880
|
+
# [C-52] Pass layer name; falls back to layer_name attr
|
|
881
|
+
layer = self.layer_name or ""
|
|
882
|
+
parts.append(T['body'].substitute(
|
|
883
|
+
event_type=self._event_type(),
|
|
884
|
+
event_none=event_none,
|
|
885
|
+
layer=layer))
|
|
886
|
+
return ''.join(parts)
|
|
887
|
+
|
|
888
|
+
def generate_all(self, state_machine: StateMachine) -> Dict[str, str]:
|
|
889
|
+
return {
|
|
890
|
+
'cell_prototypes': self.generate_transition_cell_prototypes(state_machine),
|
|
891
|
+
'cell_functions': self.generate_transition_cell_functions(state_machine),
|
|
892
|
+
'transition_table': self.generate_transition_table(state_machine),
|
|
893
|
+
'transition_table_header': self.generate_transition_table_header(state_machine),
|
|
894
|
+
'function_dict': self.generate_function_dictionary(state_machine),
|
|
895
|
+
'process_func': self.generate_process_function(state_machine),
|
|
896
|
+
'get_next_event': self.generate_get_next_event_function(state_machine),
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
def generate_all_transitions(self, state_machine, table_type='array',
|
|
900
|
+
process_type='table_driven'):
|
|
901
|
+
result = self.generate_all(state_machine)
|
|
902
|
+
return '\n'.join([
|
|
903
|
+
result['cell_prototypes'],
|
|
904
|
+
result['transition_table'],
|
|
905
|
+
result['cell_functions'],
|
|
906
|
+
result['process_func'],
|
|
907
|
+
])
|