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,419 @@
|
|
|
1
|
+
# codegen/variable_generator.py
|
|
2
|
+
"""
|
|
3
|
+
Variable and flag generation module (multi-layer state machine support)
|
|
4
|
+
|
|
5
|
+
[v1.5 fix]
|
|
6
|
+
- _generate_normal_init initializes custom struct variables with memset
|
|
7
|
+
(v1.4 used `ctx->data.system_status = 0;` which caused compile errors)
|
|
8
|
+
|
|
9
|
+
[v1.6 sec 9.8 #92 fix]
|
|
10
|
+
- Fix access macro field name
|
|
11
|
+
DATA_COUNTER(ctx) ((ctx)->data.COUNTER) -> bug
|
|
12
|
+
DATA_COUNTER(ctx) ((ctx)->data.counter) -> fixed
|
|
13
|
+
Macro name is uppercase (to_upper_snake); field name uses sanitize_identifier.
|
|
14
|
+
|
|
15
|
+
[v2.0 fix]
|
|
16
|
+
- Correctly recognize standard types with `_t` suffix (uint32_t / uint8_t, etc.)
|
|
17
|
+
Old: DEFAULT_INIT_VALUES had 'uint32' but not 'uint32_t'
|
|
18
|
+
-> standard types were treated as custom types, generating redundant memset
|
|
19
|
+
New: added _t-suffixed types to DEFAULT_INIT_VALUES
|
|
20
|
+
_normalize_type_for_init strips volatile / const qualifiers before lookup
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import sys
|
|
24
|
+
import os
|
|
25
|
+
import logging
|
|
26
|
+
from string import Template
|
|
27
|
+
from typing import Dict, Callable, List, Any, Optional
|
|
28
|
+
|
|
29
|
+
_this_dir = os.path.dirname(os.path.abspath(__file__))
|
|
30
|
+
_parent_dir = os.path.dirname(_this_dir)
|
|
31
|
+
for _p in (_this_dir, _parent_dir):
|
|
32
|
+
if _p not in sys.path:
|
|
33
|
+
sys.path.insert(0, _p)
|
|
34
|
+
|
|
35
|
+
from statable.global_defs import SystemVariable, EventFlag, GlobalDefinitions
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
from .type_mapper import CTypeMapper
|
|
39
|
+
from .naming_convention import CNamingConvention
|
|
40
|
+
from .code_templates import CodeTemplates
|
|
41
|
+
except ImportError:
|
|
42
|
+
from type_mapper import CTypeMapper
|
|
43
|
+
from naming_convention import CNamingConvention
|
|
44
|
+
from code_templates import CodeTemplates
|
|
45
|
+
|
|
46
|
+
logger = logging.getLogger(__name__)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class VariableGenerator:
|
|
50
|
+
"""Variable and flag generation class (multi-layer state machine support)"""
|
|
51
|
+
|
|
52
|
+
INIT_TEMPLATES = {
|
|
53
|
+
'comment': (
|
|
54
|
+
'/**\n'
|
|
55
|
+
' * @brief System context initialization\n'
|
|
56
|
+
' * @param ctx System context pointer\n'
|
|
57
|
+
' */\n'
|
|
58
|
+
),
|
|
59
|
+
'signature': Template(
|
|
60
|
+
'void $func_name(SystemContext_t *ctx)\n'
|
|
61
|
+
),
|
|
62
|
+
'function_open': '{\n',
|
|
63
|
+
'null_check': Template(
|
|
64
|
+
' /* NULL check */\n'
|
|
65
|
+
' if (ctx == NULL) {\n'
|
|
66
|
+
' $log_error("NULL pointer: ctx");\n'
|
|
67
|
+
' return;\n'
|
|
68
|
+
' }\n'
|
|
69
|
+
),
|
|
70
|
+
'entry_log': Template(
|
|
71
|
+
' $log_debug("Enter $func_name");\n'
|
|
72
|
+
),
|
|
73
|
+
'variables_comment': (
|
|
74
|
+
' /* Initialize global variables */\n'
|
|
75
|
+
),
|
|
76
|
+
'flags_comment': (
|
|
77
|
+
' /* Initialize event flags */\n'
|
|
78
|
+
),
|
|
79
|
+
'pending_event_comment': (
|
|
80
|
+
' /* Initialize pending event */\n'
|
|
81
|
+
),
|
|
82
|
+
'pending_event_init': (
|
|
83
|
+
' ctx->pending_event = 0;\n'
|
|
84
|
+
' ctx->pending_event_valid = false;\n'
|
|
85
|
+
),
|
|
86
|
+
'exit_log': Template(
|
|
87
|
+
' $log_debug("Exit $func_name");\n'
|
|
88
|
+
),
|
|
89
|
+
'function_close': '}\n',
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
INIT_FUNCTION_STEPS = [
|
|
93
|
+
{'action': 'template', 'key': 'comment'},
|
|
94
|
+
{'action': 'template', 'key': 'signature',
|
|
95
|
+
'format': {'func_name': '{func_name}'}},
|
|
96
|
+
{'action': 'template', 'key': 'function_open'},
|
|
97
|
+
{'action': 'template', 'key': 'null_check',
|
|
98
|
+
'format': {'log_error': '{log_error}'}},
|
|
99
|
+
{'action': 'blank'},
|
|
100
|
+
{'action': 'template', 'key': 'entry_log',
|
|
101
|
+
'format': {'func_name': '{func_name}', 'log_debug': '{log_debug}'}},
|
|
102
|
+
{'action': 'blank'},
|
|
103
|
+
{'action': 'template', 'key': 'variables_comment'},
|
|
104
|
+
{'action': 'loop', 'source': 'variables', 'generator': 'init'},
|
|
105
|
+
{'action': 'blank'},
|
|
106
|
+
{'action': 'template', 'key': 'flags_comment'},
|
|
107
|
+
{'action': 'loop', 'source': 'flags', 'generator': 'init'},
|
|
108
|
+
{'action': 'blank'},
|
|
109
|
+
# [F-3 Step 2] pending_event init removed: per-layer
|
|
110
|
+
# slots are reset by SystemContext_InitQueues().
|
|
111
|
+
{'action': 'blank'},
|
|
112
|
+
{'action': 'template', 'key': 'exit_log',
|
|
113
|
+
'format': {'func_name': '{func_name}', 'log_debug': '{log_debug}'}},
|
|
114
|
+
{'action': 'template', 'key': 'function_close'},
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
# ================================================================
|
|
118
|
+
# v2.0 fix: add standard types with _t suffix
|
|
119
|
+
# Consistent with type_mapper.py TYPE_MAPPING:
|
|
120
|
+
# 'uint32' -> 'uint32_t' is converted, so
|
|
121
|
+
# generated code must treat 'uint32_t' as a standard type
|
|
122
|
+
# ================================================================
|
|
123
|
+
DEFAULT_INIT_VALUES = {
|
|
124
|
+
# ---- Signed integers ----
|
|
125
|
+
'int': '0', 'int8': '0', 'int16': '0', 'int32': '0', 'int64': '0',
|
|
126
|
+
'int8_t': '0', 'int16_t': '0', 'int32_t': '0', 'int64_t': '0',
|
|
127
|
+
'short': '0', 'long': '0',
|
|
128
|
+
# ---- Unsigned integers ----
|
|
129
|
+
'uint': '0', 'uint8': '0', 'uint16': '0', 'uint32': '0', 'uint64': '0',
|
|
130
|
+
'uint8_t': '0', 'uint16_t': '0', 'uint32_t': '0', 'uint64_t': '0',
|
|
131
|
+
'unsigned': '0', 'unsigned int': '0', 'size_t': '0',
|
|
132
|
+
# ---- Floating point ----
|
|
133
|
+
'float': '0.0f', 'double': '0.0',
|
|
134
|
+
# ---- Boolean / character ----
|
|
135
|
+
'bool': 'false', '_Bool': 'false',
|
|
136
|
+
'char': '0', 'string': 'NULL',
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# Type qualifiers (may appear at the beginning)
|
|
140
|
+
_TYPE_QUALIFIERS = ('volatile', 'const', 'static')
|
|
141
|
+
|
|
142
|
+
# v1.6 sec 9.8 #92: separate macro name and field name
|
|
143
|
+
MACRO_TEMPLATES = {
|
|
144
|
+
'data_macro': Template(
|
|
145
|
+
'#define DATA_$macro_name(ctx) ((ctx)->data.$field_name)\n'
|
|
146
|
+
),
|
|
147
|
+
'flag_macro': Template(
|
|
148
|
+
'#define FLAG_$macro_name(ctx) ((ctx)->flags.$field_name)\n'
|
|
149
|
+
),
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
INIT_CODE_TEMPLATES = {
|
|
153
|
+
'array_init': Template(
|
|
154
|
+
' memset(ctx->data.$var_name, 0, sizeof(ctx->data.$var_name));\n'
|
|
155
|
+
),
|
|
156
|
+
'struct_init': Template(
|
|
157
|
+
' memset(&ctx->data.$var_name, 0, sizeof(ctx->data.$var_name));\n'
|
|
158
|
+
),
|
|
159
|
+
'normal_init': Template(
|
|
160
|
+
' ctx->data.$var_name = $init_value;\n'
|
|
161
|
+
),
|
|
162
|
+
'flag_init': Template(
|
|
163
|
+
' ctx->flags.$flag_name = 0;\n'
|
|
164
|
+
),
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
VARIABLE_TYPE_DETECTORS = {
|
|
168
|
+
'array': lambda v: getattr(v, 'array_size', 0) > 0,
|
|
169
|
+
'normal': lambda v: True,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
def __init__(self):
|
|
173
|
+
self.mapper = CTypeMapper()
|
|
174
|
+
self.naming = CNamingConvention()
|
|
175
|
+
self.templates = CodeTemplates()
|
|
176
|
+
self.strings = self.templates.STRINGS
|
|
177
|
+
self.formats = self.templates.FORMATS
|
|
178
|
+
|
|
179
|
+
self.step_executors = {
|
|
180
|
+
'template': self._execute_template_step,
|
|
181
|
+
'blank': self._execute_blank_step,
|
|
182
|
+
'loop': self._execute_loop_step,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
def _log_debug(self, message, level='debug'):
|
|
186
|
+
log_func = getattr(logger, level, logger.debug)
|
|
187
|
+
log_func(message)
|
|
188
|
+
|
|
189
|
+
def _detect_variable_type(self, var) -> str:
|
|
190
|
+
for var_type, detector in self.VARIABLE_TYPE_DETECTORS.items():
|
|
191
|
+
if detector(var):
|
|
192
|
+
return var_type
|
|
193
|
+
return 'normal'
|
|
194
|
+
|
|
195
|
+
def _build_context(self, global_defs: GlobalDefinitions) -> dict:
|
|
196
|
+
return {
|
|
197
|
+
'func_name': self.templates.FUNCTION_NAMES['system_context_init'],
|
|
198
|
+
'log_debug': self.strings['log_debug'],
|
|
199
|
+
'log_error': self.strings['log_error'],
|
|
200
|
+
'variables': getattr(global_defs, 'variables', []),
|
|
201
|
+
'flags': getattr(global_defs, 'flags', []),
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
def _replace_placeholders(self, template: str, params: dict) -> str:
|
|
205
|
+
result = template
|
|
206
|
+
for key, value in params.items():
|
|
207
|
+
result = result.replace('$' + key, str(value))
|
|
208
|
+
return result
|
|
209
|
+
|
|
210
|
+
def _resolve_value(self, value, context: dict) -> str:
|
|
211
|
+
if isinstance(value, str) and value.startswith('{') and value.endswith('}'):
|
|
212
|
+
key = value[1:-1]
|
|
213
|
+
return str(context.get(key, value))
|
|
214
|
+
return str(value)
|
|
215
|
+
|
|
216
|
+
def _execute_template_step(self, step: dict, context: dict) -> List[str]:
|
|
217
|
+
template_key = step.get('key', '')
|
|
218
|
+
template = self.INIT_TEMPLATES.get(template_key, '')
|
|
219
|
+
if not template:
|
|
220
|
+
return []
|
|
221
|
+
|
|
222
|
+
format_params = step.get('format', {})
|
|
223
|
+
resolved = {}
|
|
224
|
+
for k, v in format_params.items():
|
|
225
|
+
resolved[k] = self._resolve_value(v, context)
|
|
226
|
+
|
|
227
|
+
if isinstance(template, Template):
|
|
228
|
+
return [template.substitute(resolved).rstrip('\n')]
|
|
229
|
+
|
|
230
|
+
return [self._replace_placeholders(template, resolved).rstrip('\n')]
|
|
231
|
+
|
|
232
|
+
def _execute_blank_step(self, step: dict, context: dict) -> List[str]:
|
|
233
|
+
return [""]
|
|
234
|
+
|
|
235
|
+
def _execute_loop_step(self, step: dict, context: dict) -> List[str]:
|
|
236
|
+
source_key = step.get('source', '')
|
|
237
|
+
generator_key = step.get('generator', '')
|
|
238
|
+
items = context.get(source_key, [])
|
|
239
|
+
results = []
|
|
240
|
+
for item in items:
|
|
241
|
+
if generator_key == 'init':
|
|
242
|
+
results.append(self._generate_init_code(item))
|
|
243
|
+
elif generator_key == 'macro':
|
|
244
|
+
results.append(self._generate_access_macro(item))
|
|
245
|
+
return [r for r in results if r]
|
|
246
|
+
|
|
247
|
+
# ================================================================
|
|
248
|
+
# v1.6 sec 9.8 #92: macro generation (macro name = uppercase,
|
|
249
|
+
# field name = original)
|
|
250
|
+
# ================================================================
|
|
251
|
+
def _generate_data_macro(self, var) -> str:
|
|
252
|
+
"""
|
|
253
|
+
Generate data access macro.
|
|
254
|
+
|
|
255
|
+
[v1.6 fix]
|
|
256
|
+
Old: DATA_COUNTER(ctx) ((ctx)->data.COUNTER) -> uppercase field (bug)
|
|
257
|
+
New: DATA_COUNTER(ctx) ((ctx)->data.counter) -> original field
|
|
258
|
+
"""
|
|
259
|
+
raw_name = getattr(var, 'name', 'unnamed')
|
|
260
|
+
macro_name = self.naming.to_upper_snake(raw_name)
|
|
261
|
+
field_name = self.naming.sanitize_identifier(raw_name)
|
|
262
|
+
return self.MACRO_TEMPLATES['data_macro'].substitute(
|
|
263
|
+
macro_name=macro_name, field_name=field_name
|
|
264
|
+
).rstrip('\n')
|
|
265
|
+
|
|
266
|
+
def _generate_flag_macro(self, flag) -> str:
|
|
267
|
+
"""
|
|
268
|
+
Generate flag access macro.
|
|
269
|
+
|
|
270
|
+
[v1.6 fix]
|
|
271
|
+
Old: FLAG_EVT_INIT_DONE(ctx) ((ctx)->flags.EVT_INIT_DONE) -> bug
|
|
272
|
+
New: FLAG_EVT_INIT_DONE(ctx) ((ctx)->flags.EVT_INIT_DONE) -> matches
|
|
273
|
+
* Field name may not be sanitize_identifier-ed on the struct side
|
|
274
|
+
"""
|
|
275
|
+
raw_name = getattr(flag, 'name', 'unnamed')
|
|
276
|
+
macro_name = self.naming.to_upper_snake(raw_name)
|
|
277
|
+
field_name = self.naming.sanitize_identifier(raw_name)
|
|
278
|
+
return self.MACRO_TEMPLATES['flag_macro'].substitute(
|
|
279
|
+
macro_name=macro_name, field_name=field_name
|
|
280
|
+
).rstrip('\n')
|
|
281
|
+
|
|
282
|
+
def _generate_access_macro(self, item) -> str:
|
|
283
|
+
item_class = item.__class__.__name__
|
|
284
|
+
if item_class == 'SystemVariable':
|
|
285
|
+
return self._generate_data_macro(item)
|
|
286
|
+
elif item_class == 'EventFlag':
|
|
287
|
+
return self._generate_flag_macro(item)
|
|
288
|
+
return ""
|
|
289
|
+
|
|
290
|
+
# ================================================================
|
|
291
|
+
# v2.0 addition: normalize type name
|
|
292
|
+
# ================================================================
|
|
293
|
+
def _normalize_type_for_init(self, var_type: str) -> str:
|
|
294
|
+
"""
|
|
295
|
+
Normalize type name so it can be looked up in DEFAULT_INIT_VALUES.
|
|
296
|
+
|
|
297
|
+
Examples:
|
|
298
|
+
'volatile uint32_t' -> 'uint32_t'
|
|
299
|
+
'uint32_t' -> 'uint32_t'
|
|
300
|
+
'const uint8_t' -> 'uint8_t'
|
|
301
|
+
'MyCustomType' -> 'MyCustomType' (unchanged)
|
|
302
|
+
"""
|
|
303
|
+
if not var_type:
|
|
304
|
+
return ''
|
|
305
|
+
tokens = var_type.strip().split()
|
|
306
|
+
# Strip leading qualifiers
|
|
307
|
+
while tokens and tokens[0] in self._TYPE_QUALIFIERS:
|
|
308
|
+
tokens.pop(0)
|
|
309
|
+
# Sign qualifiers such as 'unsigned int' are preserved
|
|
310
|
+
normalized = ' '.join(tokens)
|
|
311
|
+
return normalized
|
|
312
|
+
|
|
313
|
+
# ================================================================
|
|
314
|
+
# Initialization code generation
|
|
315
|
+
# ================================================================
|
|
316
|
+
def _generate_array_init(self, var) -> str:
|
|
317
|
+
var_name = self.naming.sanitize_identifier(getattr(var, 'name', 'unnamed'))
|
|
318
|
+
return self.INIT_CODE_TEMPLATES['array_init'].substitute(
|
|
319
|
+
var_name=var_name
|
|
320
|
+
).rstrip('\n')
|
|
321
|
+
|
|
322
|
+
def _generate_normal_init(self, var) -> str:
|
|
323
|
+
"""
|
|
324
|
+
[v1.5 fix]
|
|
325
|
+
- Custom types (not in DEFAULT_INIT_VALUES) use memset
|
|
326
|
+
- Primitive types use direct assignment (= 0 etc.)
|
|
327
|
+
|
|
328
|
+
[v2.0 fix]
|
|
329
|
+
- Correctly recognize standard types with _t suffix (uint32_t etc.)
|
|
330
|
+
- Strip volatile / const qualifiers before lookup
|
|
331
|
+
- Prevents redundant memset generation
|
|
332
|
+
"""
|
|
333
|
+
var_name = self.naming.sanitize_identifier(getattr(var, 'name', 'unnamed'))
|
|
334
|
+
raw_type = getattr(var, 'type', 'void')
|
|
335
|
+
normalized_type = self._normalize_type_for_init(raw_type)
|
|
336
|
+
|
|
337
|
+
# v2.0: custom type check (using normalized type name)
|
|
338
|
+
if normalized_type not in self.DEFAULT_INIT_VALUES:
|
|
339
|
+
self._log_debug(
|
|
340
|
+
f"_generate_normal_init: '{var_name}' "
|
|
341
|
+
f"(type='{raw_type}' -> '{normalized_type}') "
|
|
342
|
+
f"is a custom type -> using memset"
|
|
343
|
+
)
|
|
344
|
+
return self.INIT_CODE_TEMPLATES['struct_init'].substitute(
|
|
345
|
+
var_name=var_name
|
|
346
|
+
).rstrip('\n')
|
|
347
|
+
|
|
348
|
+
# Standard type: = 0 / = false / = 0.0f etc.
|
|
349
|
+
init_value = getattr(var, 'default_value', '') or \
|
|
350
|
+
self.DEFAULT_INIT_VALUES.get(normalized_type, '0')
|
|
351
|
+
return self.INIT_CODE_TEMPLATES['normal_init'].substitute(
|
|
352
|
+
var_name=var_name, init_value=init_value
|
|
353
|
+
).rstrip('\n')
|
|
354
|
+
|
|
355
|
+
def _generate_flag_init(self, flag) -> str:
|
|
356
|
+
flag_name = self.naming.sanitize_identifier(getattr(flag, 'name', 'unnamed'))
|
|
357
|
+
return self.INIT_CODE_TEMPLATES['flag_init'].substitute(
|
|
358
|
+
flag_name=flag_name
|
|
359
|
+
).rstrip('\n')
|
|
360
|
+
|
|
361
|
+
def _generate_init_code(self, item) -> str:
|
|
362
|
+
item_class = item.__class__.__name__
|
|
363
|
+
if item_class == 'SystemVariable':
|
|
364
|
+
var_type = self._detect_variable_type(item)
|
|
365
|
+
if var_type == 'array':
|
|
366
|
+
return self._generate_array_init(item)
|
|
367
|
+
else:
|
|
368
|
+
return self._generate_normal_init(item)
|
|
369
|
+
elif item_class == 'EventFlag':
|
|
370
|
+
return self._generate_flag_init(item)
|
|
371
|
+
return ""
|
|
372
|
+
|
|
373
|
+
# ================================================================
|
|
374
|
+
# Public API
|
|
375
|
+
# ================================================================
|
|
376
|
+
def generate_init_function(self, global_defs: GlobalDefinitions) -> str:
|
|
377
|
+
self._log_debug("=== generate_init_function START ===")
|
|
378
|
+
lines = []
|
|
379
|
+
context = self._build_context(global_defs)
|
|
380
|
+
|
|
381
|
+
for step in self.INIT_FUNCTION_STEPS:
|
|
382
|
+
executor = self.step_executors.get(step.get('action', ''))
|
|
383
|
+
if executor:
|
|
384
|
+
lines.extend(executor(step, context))
|
|
385
|
+
|
|
386
|
+
result = '\n'.join(lines)
|
|
387
|
+
self._log_debug(f"=== generate_init_function END: {len(result)} chars ===")
|
|
388
|
+
return result
|
|
389
|
+
|
|
390
|
+
def generate_all_macros(self, global_defs: GlobalDefinitions) -> str:
|
|
391
|
+
self._log_debug("=== generate_all_macros START ===")
|
|
392
|
+
lines = []
|
|
393
|
+
for var in getattr(global_defs, 'variables', []):
|
|
394
|
+
lines.append(self._generate_data_macro(var))
|
|
395
|
+
for flag in getattr(global_defs, 'flags', []):
|
|
396
|
+
lines.append(self._generate_flag_macro(flag))
|
|
397
|
+
result = '\n'.join(lines)
|
|
398
|
+
self._log_debug(f"=== generate_all_macros END: {len(lines)} macros ===")
|
|
399
|
+
return result
|
|
400
|
+
|
|
401
|
+
def generate_variable(self, var_type: str, item) -> str:
|
|
402
|
+
if var_type == 'macro':
|
|
403
|
+
return self._generate_access_macro(item)
|
|
404
|
+
elif var_type == 'init':
|
|
405
|
+
return self._generate_init_code(item)
|
|
406
|
+
elif var_type == 'global':
|
|
407
|
+
return self._generate_normal_init(item)
|
|
408
|
+
elif var_type == 'flag':
|
|
409
|
+
return self._generate_flag_init(item)
|
|
410
|
+
raise ValueError(f"Unknown variable type: {var_type}")
|
|
411
|
+
|
|
412
|
+
def generate_all(self, global_defs: GlobalDefinitions) -> Dict[str, str]:
|
|
413
|
+
self._log_debug("=== generate_all START (variable_generator) ===")
|
|
414
|
+
result = {
|
|
415
|
+
'init_function': self.generate_init_function(global_defs),
|
|
416
|
+
'macros': self.generate_all_macros(global_defs),
|
|
417
|
+
}
|
|
418
|
+
self._log_debug("=== generate_all END (variable_generator) ===")
|
|
419
|
+
return result
|
statable/__init__.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# statable package
|
|
2
|
+
"""
|
|
3
|
+
StaTable data model layer
|
|
4
|
+
|
|
5
|
+
Re-exports main classes for convenient external use.
|
|
6
|
+
|
|
7
|
+
[v1.5 added]
|
|
8
|
+
- Re-export only lightweight-dependency modules to avoid circular imports
|
|
9
|
+
- xml_io / sample_data are intentionally excluded
|
|
10
|
+
(they have heavy dependencies and can cause circular references)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
# model.py (no dependencies) -- load first
|
|
14
|
+
from .model import (
|
|
15
|
+
State,
|
|
16
|
+
Event,
|
|
17
|
+
Transition,
|
|
18
|
+
StateType,
|
|
19
|
+
EventKind,
|
|
20
|
+
RoleFunction,
|
|
21
|
+
EventDeliveryType,
|
|
22
|
+
EventSourceLayer,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# state_machine.py (depends on model)
|
|
26
|
+
from .state_machine import StateMachine
|
|
27
|
+
|
|
28
|
+
# global_defs.py (no dependencies)
|
|
29
|
+
from .global_defs import (
|
|
30
|
+
GlobalDefinitions,
|
|
31
|
+
SystemVariable,
|
|
32
|
+
EventFlag,
|
|
33
|
+
InterruptHandlerDef,
|
|
34
|
+
InterruptAction,
|
|
35
|
+
DevicePlaceholderDef,
|
|
36
|
+
TimerBaseDef,
|
|
37
|
+
TimerDerivedDef,
|
|
38
|
+
EventQueueDef,
|
|
39
|
+
CustomTypeDef,
|
|
40
|
+
StructMemberDef,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
__version__ = "3.1.0"
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
# model
|
|
47
|
+
'State',
|
|
48
|
+
'Event',
|
|
49
|
+
'Transition',
|
|
50
|
+
'StateType',
|
|
51
|
+
'EventKind',
|
|
52
|
+
'RoleFunction',
|
|
53
|
+
'EventDeliveryType',
|
|
54
|
+
'EventSourceLayer',
|
|
55
|
+
# state_machine
|
|
56
|
+
'StateMachine',
|
|
57
|
+
# global_defs
|
|
58
|
+
'GlobalDefinitions',
|
|
59
|
+
'SystemVariable',
|
|
60
|
+
'EventFlag',
|
|
61
|
+
'InterruptHandlerDef',
|
|
62
|
+
'InterruptAction',
|
|
63
|
+
'DevicePlaceholderDef',
|
|
64
|
+
'TimerBaseDef',
|
|
65
|
+
'TimerDerivedDef',
|
|
66
|
+
'EventQueueDef',
|
|
67
|
+
'CustomTypeDef',
|
|
68
|
+
'StructMemberDef',
|
|
69
|
+
# version
|
|
70
|
+
'__version__',
|
|
71
|
+
]
|
statable/__main__.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Entry point for `python -m statable`.
|
|
2
|
+
|
|
3
|
+
Launches the StaTable GUI, or prints a friendly error if the
|
|
4
|
+
GUI extras are not installed.
|
|
5
|
+
|
|
6
|
+
pip install statable[gui] # to enable GUI
|
|
7
|
+
python -m statable # launch
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main() -> int:
|
|
15
|
+
try:
|
|
16
|
+
from PySide6.QtWidgets import QApplication
|
|
17
|
+
except ImportError:
|
|
18
|
+
sys.stderr.write(
|
|
19
|
+
"error: The StaTable GUI requires PySide6.\n"
|
|
20
|
+
" Install with: pip install \"statable[gui]\"\n"
|
|
21
|
+
" For CLI-only usage: statable-cli --help\n"
|
|
22
|
+
)
|
|
23
|
+
return 1
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from statable_gui.main_window import MainWindow
|
|
27
|
+
except ImportError as exc:
|
|
28
|
+
sys.stderr.write(
|
|
29
|
+
f"error: Failed to import GUI module: {exc}\n"
|
|
30
|
+
" The GUI files may be missing from the install.\n"
|
|
31
|
+
)
|
|
32
|
+
return 1
|
|
33
|
+
|
|
34
|
+
app = QApplication(sys.argv)
|
|
35
|
+
|
|
36
|
+
# [v3.1] Apply saved language preference (en / zh_CN)
|
|
37
|
+
try:
|
|
38
|
+
from statable_gui.i18n import install_translator
|
|
39
|
+
install_translator(app)
|
|
40
|
+
except Exception:
|
|
41
|
+
pass # i18n is optional; fall back to English
|
|
42
|
+
|
|
43
|
+
window = MainWindow()
|
|
44
|
+
window.show()
|
|
45
|
+
return app.exec()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
sys.exit(main())
|