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,345 @@
|
|
|
1
|
+
# codegen/interrupt_generator.py
|
|
2
|
+
"""\nInterrupt handler ISR generation module (H3: ISR context support)\n\nFeatures:\n 1. Automatic ctx pointer insertion (SystemContext_t *ctx = &g_ctx;)\n 2. Action Namespace.Name -> RoleFunc_<NS>_<Name>(NULL, ctx) conversion\n 3. Legacy identifier(args) -> RoleFunc_<Layer>_<Pascal>(NULL, ctx) conversion\n 4. User marker [[STABLE_USER_CODE_START:<Marker>]]\n 5. Automatic extraction of used_role_functions / used_variables\n 6. Entry / exit logging\n\nMarker naming convention:\n handler.name = \"TIMER0\" -> ISR_TIMER0 / Marker \"TIMER0\"\n handler.name = \"UART_RX\" -> ISR_UARTRX / Marker \"UARTRX\"\n"""
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Dict, Callable, List, Any, Optional, Tuple
|
|
9
|
+
|
|
10
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
11
|
+
|
|
12
|
+
from statable.global_defs import (
|
|
13
|
+
InterruptHandlerDef, InterruptAction, GlobalDefinitions,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from .naming_convention import CNamingConvention
|
|
18
|
+
from .code_templates import CodeTemplates
|
|
19
|
+
except ImportError:
|
|
20
|
+
from naming_convention import CNamingConvention
|
|
21
|
+
from code_templates import CodeTemplates
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# C reserved words (excluded to avoid mis-converting function-call forms)
|
|
27
|
+
_C_KEYWORDS = {
|
|
28
|
+
'if', 'else', 'for', 'while', 'do', 'switch', 'case',
|
|
29
|
+
'default', 'break', 'continue', 'return', 'goto',
|
|
30
|
+
'sizeof', 'typedef', 'struct', 'union', 'enum',
|
|
31
|
+
'static', 'const', 'volatile', 'extern', 'inline',
|
|
32
|
+
'void', 'int', 'char', 'short', 'long', 'float', 'double',
|
|
33
|
+
'signed', 'unsigned', 'bool', 'true', 'false', 'NULL',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class InterruptGenerator:
|
|
38
|
+
"""Interrupt handler ISR generation class (H3: context support)"""
|
|
39
|
+
|
|
40
|
+
# ================================================================
|
|
41
|
+
# Class constants
|
|
42
|
+
# ================================================================
|
|
43
|
+
AUTO_INSERT_CTX = True
|
|
44
|
+
CTX_VAR_NAME = 'ctx'
|
|
45
|
+
G_CTX_NAME = 'g_ctx'
|
|
46
|
+
|
|
47
|
+
def __init__(self):
|
|
48
|
+
self.naming = CNamingConvention()
|
|
49
|
+
self.templates = CodeTemplates()
|
|
50
|
+
self.strings = self.templates.STRINGS
|
|
51
|
+
self.formats = self.templates.FORMATS
|
|
52
|
+
self.layer_name: str = ''
|
|
53
|
+
|
|
54
|
+
# ============================================================
|
|
55
|
+
# Generation step
|
|
56
|
+
# ============================================================
|
|
57
|
+
self.isr_steps = [
|
|
58
|
+
{'action': 'comment'},
|
|
59
|
+
{'action': 'signature'},
|
|
60
|
+
{'action': 'open'},
|
|
61
|
+
{'action': 'context'},
|
|
62
|
+
{'action': 'enter_log'},
|
|
63
|
+
{'action': 'actions'},
|
|
64
|
+
{'action': 'user_section'},
|
|
65
|
+
{'action': 'exit_log'},
|
|
66
|
+
{'action': 'close'},
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
self.step_executors: Dict[str, Callable] = {
|
|
70
|
+
'comment': self._execute_comment_step,
|
|
71
|
+
'signature': self._execute_signature_step,
|
|
72
|
+
'open': self._execute_open_step,
|
|
73
|
+
'context': self._execute_context_step,
|
|
74
|
+
'enter_log': self._execute_enter_log_step,
|
|
75
|
+
'actions': self._execute_actions_step,
|
|
76
|
+
'user_section': self._execute_user_section_step,
|
|
77
|
+
'exit_log': self._execute_exit_log_step,
|
|
78
|
+
'close': self._execute_close_step,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
def set_layer(self, layer_name: str):
|
|
82
|
+
"""Set layer name (used for RoleFunc name generation in legacy identifier(args) form)"""
|
|
83
|
+
self.layer_name = layer_name or ''
|
|
84
|
+
|
|
85
|
+
def _log_debug(self, message, level='debug'):
|
|
86
|
+
log_func = getattr(logger, level, logger.debug)
|
|
87
|
+
log_func(message)
|
|
88
|
+
|
|
89
|
+
# ================================================================
|
|
90
|
+
# Name generation
|
|
91
|
+
# ================================================================
|
|
92
|
+
def _get_isr_function_name(self, handler) -> str:
|
|
93
|
+
"""ISR_<PascalCase(handler.name)>"""
|
|
94
|
+
name = getattr(handler, 'name', '') or 'unnamed'
|
|
95
|
+
return f"ISR_{self.naming.to_pascal_case(name)}"
|
|
96
|
+
|
|
97
|
+
def _get_marker_name(self, handler) -> str:
|
|
98
|
+
"""PascalCase(handler.name)"""
|
|
99
|
+
name = getattr(handler, 'name', '') or 'unnamed'
|
|
100
|
+
return self.naming.to_pascal_case(name)
|
|
101
|
+
|
|
102
|
+
def _get_handler_display_name(self, handler) -> str:
|
|
103
|
+
"""Log display name (original name as-is)"""
|
|
104
|
+
return getattr(handler, 'name', '') or 'unnamed'
|
|
105
|
+
|
|
106
|
+
# ================================================================
|
|
107
|
+
# Action parsing
|
|
108
|
+
# ================================================================
|
|
109
|
+
def _parse_action(self, action_text: str) -> Tuple[str, List[str]]:
|
|
110
|
+
"""\n Convert action string to C code\n\n Args:\n action_text: Input action string\n\n Returns:\n (c_code, used_role_functions)\n\n Conversion rules:\n 1. Namespace.Name [(args)] -> RoleFunc_Namespace_Name(NULL, ctx)\n 2. identifier(args) -> RoleFunc_<Layer>_PascalId(NULL, ctx)\n 3. otherwise -> as-is\n """
|
|
111
|
+
if not action_text:
|
|
112
|
+
return "", []
|
|
113
|
+
text = action_text.strip()
|
|
114
|
+
if not text:
|
|
115
|
+
return "", []
|
|
116
|
+
|
|
117
|
+
used: List[str] = []
|
|
118
|
+
|
|
119
|
+
# --- 1. Namespace.Name form ---
|
|
120
|
+
m = re.fullmatch(
|
|
121
|
+
r'([A-Z]\w*)\.([A-Za-z_]\w*)\s*(\(.*\))?',
|
|
122
|
+
text, re.DOTALL,
|
|
123
|
+
)
|
|
124
|
+
if m:
|
|
125
|
+
ns = m.group(1)
|
|
126
|
+
name = m.group(2)
|
|
127
|
+
pascal = self.naming.to_pascal_case(name)
|
|
128
|
+
qualified = f"{ns}.{name}"
|
|
129
|
+
used.append(qualified)
|
|
130
|
+
return f"RoleFunc_{ns}_{pascal}(NULL, ctx)", used
|
|
131
|
+
|
|
132
|
+
# --- 2. Function call form ---
|
|
133
|
+
m = re.fullmatch(
|
|
134
|
+
r'([A-Za-z_]\w*)\s*(\(.*\))',
|
|
135
|
+
text, re.DOTALL,
|
|
136
|
+
)
|
|
137
|
+
if m:
|
|
138
|
+
fname = m.group(1)
|
|
139
|
+
# Names already starting with RoleFunc_ / ISR_ are kept as-is
|
|
140
|
+
if fname.startswith('RoleFunc_') or fname.startswith('ISR_'):
|
|
141
|
+
return text, used
|
|
142
|
+
# C keywords kept as-is
|
|
143
|
+
if fname in _C_KEYWORDS:
|
|
144
|
+
return text, used
|
|
145
|
+
# Convert to RoleFunc name (arguments discarded)
|
|
146
|
+
pascal = self.naming.to_pascal_case(fname)
|
|
147
|
+
if self.layer_name:
|
|
148
|
+
call = f"RoleFunc_{self.layer_name}_{pascal}(NULL, ctx)"
|
|
149
|
+
qualified = f"{self.layer_name}.{pascal}"
|
|
150
|
+
else:
|
|
151
|
+
call = f"RoleFunc_{pascal}(NULL, ctx)"
|
|
152
|
+
qualified = pascal
|
|
153
|
+
used.append(qualified)
|
|
154
|
+
return call, used
|
|
155
|
+
|
|
156
|
+
# --- 3. Otherwise ---
|
|
157
|
+
return text, used
|
|
158
|
+
|
|
159
|
+
def _parse_action_with_semicolon(
|
|
160
|
+
self, action: InterruptAction,
|
|
161
|
+
) -> Tuple[str, List[str]]:
|
|
162
|
+
"""Convert condition-attached action to a single line of C code"""
|
|
163
|
+
body, used = self._parse_action(getattr(action, 'action', ''))
|
|
164
|
+
if not body:
|
|
165
|
+
return "", used
|
|
166
|
+
# Normalize trailing semicolon
|
|
167
|
+
body = body.rstrip(';').rstrip()
|
|
168
|
+
condition = (getattr(action, 'condition', '') or '').strip()
|
|
169
|
+
if condition:
|
|
170
|
+
return f"if ({condition}) {{ {body}; }}", used
|
|
171
|
+
return f"{body};", used
|
|
172
|
+
|
|
173
|
+
# ================================================================
|
|
174
|
+
# Used symbol extraction
|
|
175
|
+
# ================================================================
|
|
176
|
+
def extract_used_symbols(
|
|
177
|
+
self, handler: InterruptHandlerDef,
|
|
178
|
+
) -> Tuple[List[str], List[str]]:
|
|
179
|
+
"""\n Extract used role functions and used variables from handler\n\n Returns:\n (used_role_functions, used_variables)\n used_role_functions: [\"Driver.Init\", \"Application.HandleTick\"]\n used_variables: [\"counter\", \"EVT_START\"]\n """
|
|
180
|
+
used_rfs: List[str] = []
|
|
181
|
+
used_vars: List[str] = []
|
|
182
|
+
seen_rf = set()
|
|
183
|
+
seen_var = set()
|
|
184
|
+
|
|
185
|
+
for action in getattr(handler, 'actions', []):
|
|
186
|
+
text = getattr(action, 'action', '') or ''
|
|
187
|
+
_, rfs = self._parse_action(text)
|
|
188
|
+
for r in rfs:
|
|
189
|
+
if r and r not in seen_rf:
|
|
190
|
+
seen_rf.add(r)
|
|
191
|
+
used_rfs.append(r)
|
|
192
|
+
|
|
193
|
+
# ctx->data.X / ctx->flags.X
|
|
194
|
+
for m in re.finditer(
|
|
195
|
+
r'ctx->(?:data|flags)\.([A-Za-z_]\w*)', text,
|
|
196
|
+
):
|
|
197
|
+
v = m.group(1)
|
|
198
|
+
if v and v not in seen_var:
|
|
199
|
+
seen_var.add(v)
|
|
200
|
+
used_vars.append(v)
|
|
201
|
+
|
|
202
|
+
return used_rfs, used_vars
|
|
203
|
+
|
|
204
|
+
def update_handler_symbols(self, handler: InterruptHandlerDef):
|
|
205
|
+
"""Recompute handler's used_role_functions / used_variables and write back"""
|
|
206
|
+
used_rfs, used_vars = self.extract_used_symbols(handler)
|
|
207
|
+
handler.used_role_functions = used_rfs
|
|
208
|
+
handler.used_variables = used_vars
|
|
209
|
+
|
|
210
|
+
# ================================================================
|
|
211
|
+
# Step execution
|
|
212
|
+
# ================================================================
|
|
213
|
+
def _execute_comment_step(self, step, context):
|
|
214
|
+
handler = context['handler']
|
|
215
|
+
display = self._get_handler_display_name(handler)
|
|
216
|
+
used_rfs = context.get('used_role_functions', [])
|
|
217
|
+
description = getattr(handler, 'description', '') or ''
|
|
218
|
+
|
|
219
|
+
lines = [
|
|
220
|
+
"/**",
|
|
221
|
+
f" * @brief {display} interrupt handler",
|
|
222
|
+
]
|
|
223
|
+
if description:
|
|
224
|
+
lines.append(f" * @note {description}")
|
|
225
|
+
if used_rfs:
|
|
226
|
+
lines.append(" * @note Used role functions:")
|
|
227
|
+
for r in used_rfs:
|
|
228
|
+
lines.append(f" * - {r}")
|
|
229
|
+
lines.append(" */")
|
|
230
|
+
return lines
|
|
231
|
+
|
|
232
|
+
def _execute_signature_step(self, step, context):
|
|
233
|
+
handler = context['handler']
|
|
234
|
+
func_name = self._get_isr_function_name(handler)
|
|
235
|
+
return [f"void {func_name}(void)"]
|
|
236
|
+
|
|
237
|
+
def _execute_open_step(self, step, context):
|
|
238
|
+
return ["{"]
|
|
239
|
+
|
|
240
|
+
def _execute_context_step(self, step, context):
|
|
241
|
+
if not self.AUTO_INSERT_CTX:
|
|
242
|
+
return []
|
|
243
|
+
return [
|
|
244
|
+
"",
|
|
245
|
+
" /* ===== Context reference (auto-generated) ===== */",
|
|
246
|
+
f" SystemContext_t *{self.CTX_VAR_NAME} = &{self.G_CTX_NAME};",
|
|
247
|
+
f" (void){self.CTX_VAR_NAME};",
|
|
248
|
+
]
|
|
249
|
+
|
|
250
|
+
def _execute_enter_log_step(self, step, context):
|
|
251
|
+
handler = context['handler']
|
|
252
|
+
display = self._get_handler_display_name(handler)
|
|
253
|
+
return [
|
|
254
|
+
"",
|
|
255
|
+
" /* ===== Entry log ===== */",
|
|
256
|
+
f' LOG_DEBUG("Enter ISR: {display}");',
|
|
257
|
+
]
|
|
258
|
+
|
|
259
|
+
def _execute_actions_step(self, step, context):
|
|
260
|
+
handler = context['handler']
|
|
261
|
+
actions = getattr(handler, 'actions', []) or []
|
|
262
|
+
if not actions:
|
|
263
|
+
return []
|
|
264
|
+
|
|
265
|
+
lines = [
|
|
266
|
+
"",
|
|
267
|
+
" /* ===== Actions (auto-generated) ===== */",
|
|
268
|
+
]
|
|
269
|
+
has_action = False
|
|
270
|
+
for action in actions:
|
|
271
|
+
body, _ = self._parse_action_with_semicolon(action)
|
|
272
|
+
if body:
|
|
273
|
+
lines.append(f" {body}")
|
|
274
|
+
has_action = True
|
|
275
|
+
if not has_action:
|
|
276
|
+
lines.append(" /* (action undefined) */")
|
|
277
|
+
return lines
|
|
278
|
+
|
|
279
|
+
def _execute_user_section_step(self, step, context):
|
|
280
|
+
handler = context['handler']
|
|
281
|
+
marker = self._get_marker_name(handler)
|
|
282
|
+
return [
|
|
283
|
+
"",
|
|
284
|
+
" /* ===== User extension area ===== */",
|
|
285
|
+
f" /* [[STABLE_USER_CODE_START:{marker}]] */",
|
|
286
|
+
" /* Add user code here */",
|
|
287
|
+
f" /* [[STABLE_USER_CODE_END:{marker}]] */",
|
|
288
|
+
]
|
|
289
|
+
|
|
290
|
+
def _execute_exit_log_step(self, step, context):
|
|
291
|
+
handler = context['handler']
|
|
292
|
+
display = self._get_handler_display_name(handler)
|
|
293
|
+
return [
|
|
294
|
+
"",
|
|
295
|
+
" /* ===== Exit log ===== */",
|
|
296
|
+
f' LOG_DEBUG("Exit ISR: {display}");',
|
|
297
|
+
]
|
|
298
|
+
|
|
299
|
+
def _execute_close_step(self, step, context):
|
|
300
|
+
return ["}"]
|
|
301
|
+
|
|
302
|
+
# ================================================================
|
|
303
|
+
# Public API
|
|
304
|
+
# ================================================================
|
|
305
|
+
def generate_isr(
|
|
306
|
+
self, handler: InterruptHandlerDef,
|
|
307
|
+
update_handler: bool = False,
|
|
308
|
+
) -> str:
|
|
309
|
+
"""\n Generate ISR\n\n Args:\n handler: Interrupt handler definition\n update_handler: If True, write used_* back to handler\n """
|
|
310
|
+
self._log_debug(f"Generating ISR for: {handler.name}")
|
|
311
|
+
|
|
312
|
+
used_rfs, used_vars = self.extract_used_symbols(handler)
|
|
313
|
+
|
|
314
|
+
if update_handler:
|
|
315
|
+
handler.used_role_functions = used_rfs
|
|
316
|
+
handler.used_variables = used_vars
|
|
317
|
+
|
|
318
|
+
context = {
|
|
319
|
+
'handler': handler,
|
|
320
|
+
'used_role_functions': used_rfs,
|
|
321
|
+
'used_variables': used_vars,
|
|
322
|
+
}
|
|
323
|
+
lines: List[str] = []
|
|
324
|
+
for step in self.isr_steps:
|
|
325
|
+
executor = self.step_executors.get(step['action'])
|
|
326
|
+
if executor is None:
|
|
327
|
+
continue
|
|
328
|
+
result = executor(step, context)
|
|
329
|
+
if result:
|
|
330
|
+
lines.extend(result)
|
|
331
|
+
return '\n'.join(lines)
|
|
332
|
+
|
|
333
|
+
def generate_all_isrs(
|
|
334
|
+
self, global_defs: GlobalDefinitions,
|
|
335
|
+
update_handlers: bool = False,
|
|
336
|
+
) -> str:
|
|
337
|
+
"""Generate all ISRs"""
|
|
338
|
+
lines: List[str] = []
|
|
339
|
+
for handler in getattr(global_defs, 'interrupts', []):
|
|
340
|
+
lines.append(self.generate_isr(
|
|
341
|
+
handler, update_handler=update_handlers,
|
|
342
|
+
))
|
|
343
|
+
lines.append("")
|
|
344
|
+
lines.append("")
|
|
345
|
+
return '\n'.join(lines).rstrip() + '\n'
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# codegen/naming_convention.py
|
|
2
|
+
"""
|
|
3
|
+
C language naming convention module.
|
|
4
|
+
|
|
5
|
+
Version: 2.2.5 (2026-09-19)
|
|
6
|
+
- Fix: create_type_name() is now idempotent.
|
|
7
|
+
Names already ending in '_t' are preserved (avoids '_t_t' doubling
|
|
8
|
+
such as 'SystemStatus_t' -> 'SystemStatusT_t').
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
import sys
|
|
13
|
+
import os
|
|
14
|
+
|
|
15
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from .code_templates import CodeTemplates
|
|
19
|
+
except ImportError:
|
|
20
|
+
from code_templates import CodeTemplates
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CNamingConvention:
|
|
24
|
+
"""Class managing C naming conventions"""
|
|
25
|
+
|
|
26
|
+
CONVERSION_PATTERNS = {
|
|
27
|
+
'upper_snake': {
|
|
28
|
+
'patterns': [
|
|
29
|
+
(r'(.)([A-Z][a-z]+)', r'\1_\2'),
|
|
30
|
+
(r'([a-z0-9])([A-Z])', r'\1_\2'),
|
|
31
|
+
],
|
|
32
|
+
'transform': str.upper,
|
|
33
|
+
},
|
|
34
|
+
'lower_snake': {
|
|
35
|
+
'patterns': [
|
|
36
|
+
(r'(.)([A-Z][a-z]+)', r'\1_\2'),
|
|
37
|
+
(r'([a-z0-9])([A-Z])', r'\1_\2'),
|
|
38
|
+
],
|
|
39
|
+
'transform': str.lower,
|
|
40
|
+
},
|
|
41
|
+
'camel': {
|
|
42
|
+
'split': r'[_-]',
|
|
43
|
+
'first': str.lower,
|
|
44
|
+
'rest': str.capitalize,
|
|
45
|
+
},
|
|
46
|
+
'pascal': {
|
|
47
|
+
'split': r'[_-]',
|
|
48
|
+
'first': str.capitalize,
|
|
49
|
+
'rest': str.capitalize,
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
C_KEYWORDS = {
|
|
54
|
+
'auto', 'break', 'case', 'char', 'const', 'continue',
|
|
55
|
+
'default', 'do', 'double', 'else', 'enum', 'extern',
|
|
56
|
+
'float', 'for', 'goto', 'if', 'inline', 'int', 'long',
|
|
57
|
+
'register', 'restrict', 'return', 'short', 'signed',
|
|
58
|
+
'sizeof', 'static', 'struct', 'switch', 'typedef',
|
|
59
|
+
'union', 'unsigned', 'void', 'volatile', 'while',
|
|
60
|
+
'_Bool', '_Complex', '_Imaginary'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
IDENTIFIER_RULES = {
|
|
64
|
+
'variable': 'to_lower_snake',
|
|
65
|
+
'function': 'to_pascal_case',
|
|
66
|
+
'type': 'to_pascal_case',
|
|
67
|
+
'enum': 'to_upper_snake',
|
|
68
|
+
'macro': 'to_upper_snake',
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
def __init__(self):
|
|
72
|
+
self.templates = CodeTemplates()
|
|
73
|
+
self.strings = self.templates.STRINGS
|
|
74
|
+
self.formats = self.templates.FORMATS
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def to_snake_case(cls, name, upper=False):
|
|
78
|
+
config = cls.CONVERSION_PATTERNS['upper_snake' if upper else 'lower_snake']
|
|
79
|
+
result = name
|
|
80
|
+
for pattern, replacement in config['patterns']:
|
|
81
|
+
result = re.sub(pattern, replacement, result)
|
|
82
|
+
return config['transform'](result)
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def to_upper_snake(cls, name):
|
|
86
|
+
return cls.to_snake_case(name, upper=True)
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def to_lower_snake(cls, name):
|
|
90
|
+
return cls.to_snake_case(name, upper=False)
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def to_camel_case(cls, name):
|
|
94
|
+
config = cls.CONVERSION_PATTERNS['camel']
|
|
95
|
+
parts = re.split(config['split'], name)
|
|
96
|
+
if not parts:
|
|
97
|
+
return ""
|
|
98
|
+
result = config['first'](parts[0])
|
|
99
|
+
for part in parts[1:]:
|
|
100
|
+
if part:
|
|
101
|
+
result += config['rest'](part)
|
|
102
|
+
return result
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def to_pascal_case(cls, name):
|
|
106
|
+
"""Convert to PascalCase"""
|
|
107
|
+
config = cls.CONVERSION_PATTERNS['pascal']
|
|
108
|
+
parts = re.split(config['split'], name)
|
|
109
|
+
if not parts:
|
|
110
|
+
return ""
|
|
111
|
+
|
|
112
|
+
result = ""
|
|
113
|
+
for part in parts:
|
|
114
|
+
if part:
|
|
115
|
+
# If camelCase, capitalize first letter
|
|
116
|
+
result += part[0].upper() + part[1:]
|
|
117
|
+
return result
|
|
118
|
+
|
|
119
|
+
@classmethod
|
|
120
|
+
def sanitize_identifier(cls, name):
|
|
121
|
+
if not name:
|
|
122
|
+
return "_unnamed"
|
|
123
|
+
if name[0].isdigit():
|
|
124
|
+
name = '_' + name
|
|
125
|
+
name = re.sub(r'[^a-zA-Z0-9_]', '_', name)
|
|
126
|
+
if name in cls.C_KEYWORDS:
|
|
127
|
+
name = name + '_'
|
|
128
|
+
return name
|
|
129
|
+
|
|
130
|
+
@classmethod
|
|
131
|
+
def create_identifier(cls, name, kind='variable'):
|
|
132
|
+
method_name = cls.IDENTIFIER_RULES.get(kind, 'to_lower_snake')
|
|
133
|
+
method = getattr(cls, method_name)
|
|
134
|
+
return cls.sanitize_identifier(method(name))
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
def create_type_name(cls, name):
|
|
138
|
+
"""Create a C type name with '_t' suffix (idempotent).
|
|
139
|
+
|
|
140
|
+
[v2.2.5 fix]
|
|
141
|
+
If the name already ends with '_t', it is preserved as-is
|
|
142
|
+
so that references match. Otherwise PascalCase + '_t' is applied.
|
|
143
|
+
|
|
144
|
+
Examples:
|
|
145
|
+
'SystemStatus' -> 'SystemStatus_t'
|
|
146
|
+
'SystemStatus_t' -> 'SystemStatus_t' (not 'SystemStatusT_t')
|
|
147
|
+
'sensor_data' -> 'SensorData_t'
|
|
148
|
+
'sensor_data_t' -> 'sensor_data_t' (preserved)
|
|
149
|
+
'' -> 'Unknown_t'
|
|
150
|
+
"""
|
|
151
|
+
if not name:
|
|
152
|
+
return "Unknown_t"
|
|
153
|
+
if name.endswith('_t'):
|
|
154
|
+
return name
|
|
155
|
+
return cls.to_pascal_case(name) + "_t"
|
|
156
|
+
|
|
157
|
+
@classmethod
|
|
158
|
+
def create_enum_value(cls, prefix, name):
|
|
159
|
+
return cls.sanitize_identifier(prefix) + "_" + cls.to_upper_snake(name)
|
|
160
|
+
|
|
161
|
+
@classmethod
|
|
162
|
+
def create_function_name(cls, module, action):
|
|
163
|
+
return cls.to_pascal_case(module) + "_" + cls.to_pascal_case(action)
|
|
164
|
+
|
|
165
|
+
@classmethod
|
|
166
|
+
def create_variable_name(cls, name):
|
|
167
|
+
return cls.sanitize_identifier(cls.to_lower_snake(name))
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def create_macro_name(cls, name):
|
|
171
|
+
return cls.to_upper_snake(name)
|