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.
Files changed (117) hide show
  1. codegen/__init__.py +38 -0
  2. codegen/c_code_generator.py +1752 -0
  3. codegen/code_merger.py +371 -0
  4. codegen/code_templates.py +509 -0
  5. codegen/config.py +167 -0
  6. codegen/enum_generator.py +377 -0
  7. codegen/event_queue_generator.py +303 -0
  8. codegen/interrupt_generator.py +345 -0
  9. codegen/naming_convention.py +171 -0
  10. codegen/osal_generator.py +282 -0
  11. codegen/role_function_generator.py +1244 -0
  12. codegen/sample_data.py +269 -0
  13. codegen/state_actions_generator.py +395 -0
  14. codegen/struct_generator.py +721 -0
  15. codegen/timer_generator.py +299 -0
  16. codegen/transition_generator.py +907 -0
  17. codegen/type_mapper.py +69 -0
  18. codegen/validate/__init__.py +38 -0
  19. codegen/validate/change_actions.py +90 -0
  20. codegen/validate/change_applier.py +416 -0
  21. codegen/validate/clipboard_manager.py +47 -0
  22. codegen/validate/data/__init__.py +30 -0
  23. codegen/validate/data/action_definitions.py +200 -0
  24. codegen/validate/data/keywords.py +46 -0
  25. codegen/validate/data/prompt_templates.py +176 -0
  26. codegen/validate/data/validation_rules.py +205 -0
  27. codegen/validate/items/__init__.py +30 -0
  28. codegen/validate/items/base_validator.py +25 -0
  29. codegen/validate/items/cell_validator.py +275 -0
  30. codegen/validate/items/custom_type_validator.py +75 -0
  31. codegen/validate/items/event_validator.py +78 -0
  32. codegen/validate/items/flag_validator.py +74 -0
  33. codegen/validate/items/interrupt_validator.py +75 -0
  34. codegen/validate/items/queue_validator.py +73 -0
  35. codegen/validate/items/role_function_validator.py +175 -0
  36. codegen/validate/items/state_validator.py +88 -0
  37. codegen/validate/items/timer_validator.py +120 -0
  38. codegen/validate/items/transition_validator.py +113 -0
  39. codegen/validate/items/variable_validator.py +132 -0
  40. codegen/validate/logger.py +52 -0
  41. codegen/validate/models.py +177 -0
  42. codegen/validate/prompt_generator.py +724 -0
  43. codegen/validate/response_parser.py +154 -0
  44. codegen/validate/response_validator.py +275 -0
  45. codegen/validate/validation_dialog.py +322 -0
  46. codegen/validate/validator.py +94 -0
  47. codegen/variable_generator.py +419 -0
  48. statable/__init__.py +71 -0
  49. statable/__main__.py +49 -0
  50. statable/cli.py +251 -0
  51. statable/global_defs.py +256 -0
  52. statable/mermaid_gen.py +144 -0
  53. statable/model.py +426 -0
  54. statable/parser.py +64 -0
  55. statable/sample_data.py +435 -0
  56. statable/shared/__init__.py +24 -0
  57. statable/shared/condition_library.py +63 -0
  58. statable/shared/literal_library.py +66 -0
  59. statable/shared/role_function_library.py +150 -0
  60. statable/state_machine.py +121 -0
  61. statable/xml_io.py +1164 -0
  62. statable-3.1.0.dist-info/METADATA +151 -0
  63. statable-3.1.0.dist-info/RECORD +117 -0
  64. statable-3.1.0.dist-info/WHEEL +5 -0
  65. statable-3.1.0.dist-info/entry_points.txt +2 -0
  66. statable-3.1.0.dist-info/top_level.txt +3 -0
  67. statable_gui/__init__.py +23 -0
  68. statable_gui/action_edit_dialog.py +214 -0
  69. statable_gui/code_generation_dialog.py +486 -0
  70. statable_gui/code_generation_settings_dialog.py +522 -0
  71. statable_gui/common_widgets.py +537 -0
  72. statable_gui/condition_builder_dialog.py +500 -0
  73. statable_gui/condition_edit_dialog.py +123 -0
  74. statable_gui/condition_template_dialog.py +58 -0
  75. statable_gui/config.py +21 -0
  76. statable_gui/dialogs.py +439 -0
  77. statable_gui/event_definition_dialog.py +605 -0
  78. statable_gui/event_delivery_settings_dialog.py +191 -0
  79. statable_gui/event_queue_dialog.py +245 -0
  80. statable_gui/global_defs.py +167 -0
  81. statable_gui/global_defs_dialog.py +796 -0
  82. statable_gui/i18n/__init__.py +118 -0
  83. statable_gui/interrupt_handler_edit_dialog.py +810 -0
  84. statable_gui/layer_settings_dialog.py +180 -0
  85. statable_gui/libcntrl/__init__.py +23 -0
  86. statable_gui/libcntrl/condition_library.py +13 -0
  87. statable_gui/libcntrl/literal_library.py +13 -0
  88. statable_gui/libcntrl/literal_management_dialog.py +185 -0
  89. statable_gui/libcntrl/role_function_edit_dialog.py +145 -0
  90. statable_gui/libcntrl/role_function_library.py +13 -0
  91. statable_gui/literal_definition_dialog.py +62 -0
  92. statable_gui/logger.py +83 -0
  93. statable_gui/main_window.py +1380 -0
  94. statable_gui/matrix_table.py +413 -0
  95. statable_gui/preference_keys.py +18 -0
  96. statable_gui/preferences.py +74 -0
  97. statable_gui/role_function_dialog.py +244 -0
  98. statable_gui/sample_data.py +11 -0
  99. statable_gui/state_actions_dialog.py +398 -0
  100. statable_gui/symbol_picker.py +170 -0
  101. statable_gui/traceball.py +37 -0
  102. statable_gui/transition_editor_direct/__init__.py +15 -0
  103. statable_gui/transition_editor_direct/actions_tab.py +856 -0
  104. statable_gui/transition_editor_direct/code_widget.py +344 -0
  105. statable_gui/transition_editor_direct/coverage_analyzer.py +163 -0
  106. statable_gui/transition_editor_direct/dialog.py +335 -0
  107. statable_gui/transition_editor_direct/draft.py +270 -0
  108. statable_gui/transition_editor_direct/edit_dialogs.py +160 -0
  109. statable_gui/transition_editor_direct/overview_tab.py +173 -0
  110. statable_gui/transition_editor_direct/palette_widget.py +233 -0
  111. statable_gui/transition_editor_direct/relations_edit_dialog.py +156 -0
  112. statable_gui/transition_editor_direct/relations_tab.py +230 -0
  113. statable_gui/transition_editor_direct/system_global_dialog.py +52 -0
  114. statable_gui/transition_editor_direct/transition_actions_dialog.py +109 -0
  115. statable_gui/transition_editor_direct/transitions_tab.py +620 -0
  116. statable_gui/validation_dialog.py +285 -0
  117. statable_gui/widgets.py +1315 -0
@@ -0,0 +1,721 @@
1
+ # codegen/struct_generator.py
2
+ """
3
+ C struct code generation module (multi-layer state machine support)
4
+
5
+ Generated structs:
6
+ 1. custom_type : user-defined types (unchanged)
7
+ 2. system_data : global variables (unchanged)
8
+ 3. event_flags : event flags (unchanged)
9
+ 4. system_context : SystemContext_t (pending_event added)
10
+ 5. transition_context : common TransitionContext_t (new base type)
11
+ 6. layer_transition_context : TransitionContext_<Layer>_t (new, per-layer)
12
+ 7. pending_event_macros : FIRE_EVENT / MAX_CONSECUTIVE_PENDING_EVENTS macros (new)
13
+
14
+ Design policy:
15
+ - Templates are data-tabled using string.Template
16
+ - pending_event is uint16_t (holds each layer's enum value generically)
17
+ - Provides both per-layer TransitionContext_<Layer>_t and
18
+ the common base type TransitionContext_t
19
+ """
20
+
21
+ import sys
22
+ import os
23
+ import logging
24
+ from string import Template
25
+ from typing import Dict, Callable, List, Any
26
+
27
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
28
+
29
+ from statable.global_defs import StructMemberDef, CustomTypeDef, GlobalDefinitions
30
+
31
+ try:
32
+ from .type_mapper import CTypeMapper
33
+ from .naming_convention import CNamingConvention
34
+ from .code_templates import CodeTemplates
35
+ except ImportError:
36
+ from type_mapper import CTypeMapper
37
+ from naming_convention import CNamingConvention
38
+ from code_templates import CodeTemplates
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+
43
+ class CStructGenerator:
44
+ """C struct code generation class (multi-layer state machine support)"""
45
+
46
+ # ==================================================================
47
+ # [Data Table 1] Common struct templates (SystemContext_t etc.)
48
+ # ==================================================================
49
+ CONTEXT_TEMPLATES = {
50
+ # --- Section comment ---
51
+ 'section_comment': Template(
52
+ '/* $title */\n'
53
+ '/* $description */\n'
54
+ ),
55
+
56
+ # --- struct start ---
57
+ 'struct_start': (
58
+ 'typedef struct {\n'
59
+ ),
60
+
61
+ # --- data / flags members ---
62
+ 'member_data': Template(
63
+ ' $system_data_type data; /* global variables */\n'
64
+ ),
65
+ 'member_flags': Template(
66
+ ' $event_flags_type flags; /* event flags */\n'
67
+ ),
68
+
69
+ # --- pending_event members ---
70
+ 'member_pending_event': Template(
71
+ ' uint16_t pending_event_$layer; /* [F-3 S2] pending event */\n'
72
+ ),
73
+ 'member_pending_event_valid': Template(
74
+ ' bool pending_event_valid_$layer; /* [F-3 S2] pending valid flag */\n'
75
+ ),
76
+ # [C-52] one queue per layer
77
+ 'member_queue': Template(
78
+ ' EventQueueState_t queue_$layer; /* [C-52] per-layer queue */\n'
79
+ ),
80
+
81
+ # --- struct end ---
82
+ 'struct_end': Template(
83
+ '} $type_name;\n'
84
+ ),
85
+ }
86
+
87
+ # ==================================================================
88
+ # [Data Table 2] Common macro templates
89
+ # ==================================================================
90
+ MACRO_TEMPLATES = {
91
+ # --- Section comment ---
92
+ 'section_comment': (
93
+ '\n'
94
+ '/* ============================================================== */\n'
95
+ '/* Pending event control */\n'
96
+ '/* ============================================================== */\n'
97
+ ),
98
+
99
+ # --- FIRE_EVENT macro ---
100
+ 'fire_event_macro': (
101
+ '\n'
102
+ '/* Event fire macro */\n'
103
+ '/* Used within role functions: FIRE_EVENT(ctx, EVENT_XXX_YYY); */\n'
104
+ '#define FIRE_EVENT(ctx, evt) do { \\\n'
105
+ ' (ctx)->pending_event = (uint16_t)(evt); \\\n'
106
+ ' (ctx)->pending_event_valid = true; \\\n'
107
+ '} while(0)\n'
108
+ ),
109
+
110
+ # --- MAX_CONSECUTIVE_PENDING_EVENTS macro ---
111
+ 'max_consecutive_macro': (
112
+ '\n'
113
+ '/* Upper limit for consecutive pending event processing */\n'
114
+ '/* Infinite loop prevention. Can be overridden at build time via -D */\n'
115
+ '#ifndef MAX_CONSECUTIVE_PENDING_EVENTS\n'
116
+ '#define MAX_CONSECUTIVE_PENDING_EVENTS 16\n'
117
+ '#endif\n'
118
+ ),
119
+ # [C-52] per-layer queue size + init macro
120
+ 'queue_section_comment': (
121
+ '\n'
122
+ '/* ============================================================== */\n'
123
+ '/* Layer event queue (C-52) */\n'
124
+ '/* ============================================================== */\n'
125
+ ),
126
+ 'queue_size_macro': (
127
+ '\n'
128
+ '/* Per-layer queue capacity. Override via -D at build time. */\n'
129
+ '#ifndef STATABLE_LAYER_QUEUE_SIZE\n'
130
+ '#define STATABLE_LAYER_QUEUE_SIZE 16\n'
131
+ '#endif\n'
132
+ ),
133
+ 'queue_state_type': (
134
+ '\n'
135
+ '/* Ring buffer state (one instance per layer, embedded in */\n'
136
+ '/* SystemContext_t). Protected by STATABLE_ENTER/EXIT_ */\n'
137
+ '/* CRITICAL hooks (see below). */\n'
138
+ 'typedef struct {\n'
139
+ ' uint16_t buffer[STATABLE_LAYER_QUEUE_SIZE];\n'
140
+ ' volatile uint16_t head;\n'
141
+ ' volatile uint16_t tail;\n'
142
+ ' volatile uint16_t count;\n'
143
+ ' volatile uint32_t dropped; /* [C-54] full-drop count */\n'
144
+ '} EventQueueState_t;\n'
145
+ ),
146
+ # [C-54] Critical section hooks (default no-op)
147
+ 'critical_hooks': (
148
+ '\n'
149
+ '/* ============================================================== */\n'
150
+ '/* Critical section hooks (C-54) */\n'
151
+ '/* ============================================================== */\n'
152
+ '/* Called around per-layer queue operations to make them safe */\n'
153
+ '/* against ISR preemption. Default: no-op (single-context). */\n'
154
+ '/* Override at build time via -D (see OSAL porting guide). */\n'
155
+ '#ifndef STATABLE_ENTER_CRITICAL\n'
156
+ '#define STATABLE_ENTER_CRITICAL() do { } while (0)\n'
157
+ '#endif\n'
158
+ '#ifndef STATABLE_EXIT_CRITICAL\n'
159
+ '#define STATABLE_EXIT_CRITICAL() do { } while (0)\n'
160
+ '#endif\n'
161
+ ),
162
+ }
163
+
164
+ # ==================================================================
165
+ # [Data Table 3] Common TransitionContext_t template
166
+ # ==================================================================
167
+ COMMON_TRANSITION_CONTEXT_TEMPLATES = {
168
+ 'comment': (
169
+ '/* Generic transition context (for common role functions across layers) */\n'
170
+ '/* Same layout as each layer\'s TransitionContext_<Layer>_t */\n'
171
+ ),
172
+ 'struct_start': (
173
+ 'typedef struct {\n'
174
+ ),
175
+ 'member_from_state': (
176
+ ' uint16_t from_state; /* source state (cast from layer enum) */\n'
177
+ ),
178
+ 'member_event': (
179
+ ' uint16_t event; /* event (cast from layer enum) */\n'
180
+ ),
181
+ 'struct_end': (
182
+ '} TransitionContext_t;\n'
183
+ ),
184
+ }
185
+
186
+ # ==================================================================
187
+ # [Data Table 4] Per-layer TransitionContext_<Layer>_t
188
+ # ==================================================================
189
+ LAYER_TRANSITION_CONTEXT_TEMPLATES = {
190
+ 'comment': Template(
191
+ '/* Transition context for $layer layer */\n'
192
+ ),
193
+ 'struct_start': (
194
+ 'typedef struct {\n'
195
+ ),
196
+ 'member_from_state': Template(
197
+ ' $state_type from_state; /* source state */\n'
198
+ ),
199
+ 'member_event': Template(
200
+ ' $event_type event; /* event */\n'
201
+ ),
202
+ 'struct_end': Template(
203
+ '} $type_name;\n'
204
+ ),
205
+ }
206
+
207
+ # ==================================================================
208
+ # [Data Table 5] Member type detection
209
+ # ==================================================================
210
+ MEMBER_TYPE_DETECTORS = {
211
+ 'bit_field': lambda m: getattr(m, 'bit_width', 0) > 0,
212
+ 'array': lambda m: getattr(m, 'array_size', 0) > 0,
213
+ 'normal': lambda m: True,
214
+ }
215
+
216
+ # ==================================================================
217
+ # [Data Table 6] Member generation templates
218
+ # ==================================================================
219
+ MEMBER_TEMPLATES = {
220
+ 'bit_field': Template(
221
+ '$indent$c_type $name : $width;\n'
222
+ ),
223
+ 'array': Template(
224
+ '$indent$c_type $name[$size];\n'
225
+ ),
226
+ 'normal': Template(
227
+ '$indent$c_type $name;\n'
228
+ ),
229
+ }
230
+
231
+ # ==================================================================
232
+ # Constructor
233
+ # ==================================================================
234
+ def __init__(self):
235
+ self.mapper = CTypeMapper()
236
+ self.naming = CNamingConvention()
237
+ self.templates = CodeTemplates()
238
+ self.strings = self.templates.STRINGS
239
+ self.formats = self.templates.FORMATS
240
+ self.layer_name: str = ""
241
+ # [C-52] all layers of the current project
242
+ self._all_layers: list = []
243
+
244
+ def set_layer(self, layer_name: str):
245
+ """Set layer name (for per-layer TransitionContext generation)"""
246
+ self.layer_name = layer_name
247
+ logger.debug(f"CStructGenerator.set_layer: layer_name='{layer_name}'")
248
+
249
+ def set_layers(self, layers):
250
+ """[C-52] Record all (name, sm) pairs for queue members."""
251
+ self._all_layers = list(layers or [])
252
+
253
+ def _log_debug(self, message: str, level: str = 'debug'):
254
+ log_func = getattr(logger, level, logger.debug)
255
+ log_func(message)
256
+
257
+ # ==================================================================
258
+ # Helpers
259
+ # ==================================================================
260
+ def _detect_member_type(self, member) -> str:
261
+ for member_type, detector in self.MEMBER_TYPE_DETECTORS.items():
262
+ if detector(member):
263
+ return member_type
264
+ return 'normal'
265
+
266
+ def _generate_member(self, member) -> str:
267
+ """Generate one member"""
268
+ member_type = self._detect_member_type(member)
269
+ template = self.MEMBER_TEMPLATES[member_type]
270
+ member_name = self.naming.sanitize_identifier(getattr(member, 'name', 'unnamed'))
271
+ c_type = self.mapper.map_type(getattr(member, 'data_type', 'void'))
272
+ indent = self.strings['indent_1']
273
+
274
+ if member_type == 'bit_field':
275
+ return template.substitute(
276
+ indent=indent, c_type=c_type,
277
+ name=member_name,
278
+ width=getattr(member, 'bit_width', 0),
279
+ )
280
+ elif member_type == 'array':
281
+ return template.substitute(
282
+ indent=indent, c_type=c_type,
283
+ name=member_name,
284
+ size=getattr(member, 'array_size', 0),
285
+ )
286
+ else:
287
+ return template.substitute(
288
+ indent=indent, c_type=c_type, name=member_name,
289
+ )
290
+
291
+ # ==================================================================
292
+ # 1. Custom type
293
+ # ==================================================================
294
+ def _generate_custom_type(self, struct_def: CustomTypeDef) -> str:
295
+ self._log_debug(f"Generating custom type: {getattr(struct_def, 'name', 'unknown')}")
296
+ lines = []
297
+
298
+ # Comment
299
+ if getattr(struct_def, 'description', ''):
300
+ lines.append(f"/* {struct_def.description} */")
301
+ if getattr(struct_def, 'title', '') and struct_def.title != getattr(struct_def, 'name', ''):
302
+ lines.append(f"/* Title: {struct_def.title} */")
303
+
304
+ # struct body
305
+ lines.append("typedef struct {")
306
+ for member in getattr(struct_def, 'members', []):
307
+ if getattr(member, 'description', ''):
308
+ indent = self.strings['indent_1']
309
+ lines.append(f"{indent}/* {member.description} */")
310
+ if getattr(member, 'title', '') and member.title != getattr(member, 'name', ''):
311
+ indent = self.strings['indent_1']
312
+ lines.append(f"{indent}/* Title: {member.title} */")
313
+ lines.append(self._generate_member(member).rstrip('\n'))
314
+
315
+ type_name = self.naming.create_type_name(getattr(struct_def, 'name', 'Unknown'))
316
+ lines.append(f"}} {type_name};")
317
+
318
+ return '\n'.join(lines)
319
+
320
+ # ==================================================================
321
+ # 2. SystemData_t
322
+ # ==================================================================
323
+ def _generate_system_data(self, global_defs: GlobalDefinitions) -> str:
324
+ self._log_debug("Generating system data struct")
325
+ lines = []
326
+
327
+ # Comment
328
+ comment = self.templates.STRUCT_COMMENTS.get('system_data', {})
329
+ lines.append(f"/* {comment.get('title', '')} */")
330
+ lines.append(f"/* {comment.get('description', '')} */")
331
+
332
+ # struct body
333
+ lines.append("typedef struct {")
334
+ current_group = None
335
+ indent = self.strings['indent_1']
336
+
337
+ for var in getattr(global_defs, 'variables', []):
338
+ # Group comment
339
+ if getattr(var, 'group', '') and var.group != current_group:
340
+ if current_group is not None:
341
+ lines.append("")
342
+ lines.append(f"{indent}/* === {var.group} === */")
343
+ current_group = var.group
344
+
345
+ # Description / unit
346
+ comments = []
347
+ if getattr(var, 'description', ''):
348
+ comments.append(var.description)
349
+ if getattr(var, 'unit', ''):
350
+ comments.append(f"[{var.unit}]")
351
+ if comments:
352
+ lines.append(f"{indent}/* {' '.join(comments)} */")
353
+
354
+ # Variable body
355
+ var_name = self.naming.sanitize_identifier(getattr(var, 'name', 'unnamed'))
356
+ c_type = self.mapper.map_type(getattr(var, 'type', 'void'))
357
+
358
+ if getattr(var, 'array_size', 0) > 0:
359
+ lines.append(f"{indent}{c_type} {var_name}[{var.array_size}];")
360
+ else:
361
+ lines.append(f"{indent}{c_type} {var_name};")
362
+
363
+ lines.append(f"}} {self.templates.TYPE_NAMES['system_data']};")
364
+ return '\n'.join(lines)
365
+
366
+ # ==================================================================
367
+ # 3. EventFlags_t
368
+ # ==================================================================
369
+ def _generate_event_flags(self, global_defs: GlobalDefinitions) -> str:
370
+ self._log_debug("Generating event flags struct")
371
+ lines = []
372
+
373
+ # Comment
374
+ comment = self.templates.STRUCT_COMMENTS.get('event_flags', {})
375
+ lines.append(f"/* {comment.get('title', '')} */")
376
+ lines.append(f"/* {comment.get('description', '')} */")
377
+
378
+ # struct body
379
+ lines.append("typedef struct {")
380
+ current_group = None
381
+ indent = self.strings['indent_1']
382
+
383
+ for flag in getattr(global_defs, 'flags', []):
384
+ # Group comment
385
+ if getattr(flag, 'group', '') and flag.group != current_group:
386
+ if current_group is not None:
387
+ lines.append("")
388
+ lines.append(f"{indent}/* === {flag.group} === */")
389
+ current_group = flag.group
390
+
391
+ # Description
392
+ if getattr(flag, 'description', ''):
393
+ lines.append(f"{indent}/* {flag.description} */")
394
+
395
+ # Flag body
396
+ flag_name = self.naming.sanitize_identifier(getattr(flag, 'name', 'unnamed'))
397
+ lines.append(f"{indent}uint8_t {flag_name};")
398
+
399
+ lines.append(f"}} {self.templates.TYPE_NAMES['event_flags']};")
400
+ return '\n'.join(lines)
401
+
402
+ # ==================================================================
403
+ # 4. SystemContext_t (pending_event added)
404
+ # ==================================================================
405
+ def _generate_system_context(self, global_defs: GlobalDefinitions) -> str:
406
+ """
407
+ Generate SystemContext_t (with pending_event / pending_event_valid).
408
+
409
+ Example output:
410
+ /* System context struct */
411
+ /* Integrates global variables and event flags */
412
+ typedef struct {
413
+ SystemData_t data; /* global variables */
414
+ EventFlags_t flags; /* event flags */
415
+ uint16_t pending_event; /* pending event */
416
+ bool pending_event_valid; /* pending event valid flag */
417
+ } SystemContext_t;
418
+ """
419
+ self._log_debug("Generating system context struct (with pending_event)")
420
+ T = self.CONTEXT_TEMPLATES
421
+ lines = []
422
+
423
+ # Comment
424
+ comment = self.templates.STRUCT_COMMENTS.get('system_context', {})
425
+ lines.append(T['section_comment'].substitute(
426
+ title=comment.get('title', 'System context struct'),
427
+ description=comment.get('description', 'Integrates global variables and event flags'),
428
+ ).rstrip('\n'))
429
+
430
+ # struct body
431
+ lines.append(T['struct_start'].rstrip('\n'))
432
+ lines.append(T['member_data'].substitute(
433
+ system_data_type=self.templates.TYPE_NAMES['system_data'],
434
+ ).rstrip('\n'))
435
+ lines.append(T['member_flags'].substitute(
436
+ event_flags_type=self.templates.TYPE_NAMES['event_flags'],
437
+ ).rstrip('\n'))
438
+
439
+ # [F-3 Step 2] per-layer pending_event slots
440
+ for layer_name, sm in self._all_layers:
441
+ layer = getattr(sm, 'layer_name', '') or layer_name
442
+ if layer:
443
+ lines.append(
444
+ T['member_pending_event'].substitute(
445
+ layer=layer).rstrip('\n'))
446
+ lines.append(
447
+ T['member_pending_event_valid'].substitute(
448
+ layer=layer).rstrip('\n'))
449
+
450
+ # [C-52] per-layer queues
451
+ for layer_name, sm in self._all_layers:
452
+ layer = getattr(sm, 'layer_name', '') or layer_name
453
+ if layer:
454
+ lines.append(
455
+ T['member_queue'].substitute(layer=layer).rstrip('\n'))
456
+
457
+ # struct end
458
+ lines.append(T['struct_end'].substitute(
459
+ type_name=self.templates.TYPE_NAMES['system_context'],
460
+ ).rstrip('\n'))
461
+
462
+ return '\n'.join(lines)
463
+
464
+ # ==================================================================
465
+ # 5. Common TransitionContext_t (base type)
466
+ # ==================================================================
467
+ def generate_common_transition_context(self) -> str:
468
+ """
469
+ Generate the common base type TransitionContext_t.
470
+
471
+ Example output:
472
+ /* Generic transition context (for common role functions across layers) */
473
+ /* Same layout as each layer's TransitionContext_<Layer>_t */
474
+ typedef struct {
475
+ uint16_t from_state; /* source state (cast from layer enum) */
476
+ uint16_t event; /* event (cast from layer enum) */
477
+ } TransitionContext_t;
478
+ """
479
+ self._log_debug("Generating common TransitionContext_t")
480
+ T = self.COMMON_TRANSITION_CONTEXT_TEMPLATES
481
+ lines = [
482
+ T['comment'].rstrip('\n'),
483
+ T['struct_start'].rstrip('\n'),
484
+ T['member_from_state'].rstrip('\n'),
485
+ T['member_event'].rstrip('\n'),
486
+ T['struct_end'].rstrip('\n'),
487
+ ]
488
+ return '\n'.join(lines)
489
+
490
+ # ==================================================================
491
+ # 6. Per-layer TransitionContext_<Layer>_t
492
+ # ==================================================================
493
+ def generate_layer_transition_context(self, state_type: str,
494
+ event_type: str) -> str:
495
+ """
496
+ Generate per-layer TransitionContext_<Layer>_t.
497
+
498
+ Args:
499
+ state_type: layer state type (e.g. 'STATE_Driver_t')
500
+ event_type: layer event type (e.g. 'EVENT_Driver_t')
501
+
502
+ Example output:
503
+ /* Transition context for Driver layer */
504
+ typedef struct {
505
+ STATE_Driver_t from_state; /* source state */
506
+ EVENT_Driver_t event; /* event */
507
+ } TransitionContext_Driver_t;
508
+ """
509
+ if not self.layer_name:
510
+ self._log_debug("generate_layer_transition_context: layer_name is empty, "
511
+ "returning empty string")
512
+ return ""
513
+
514
+ self._log_debug(f"Generating layer TransitionContext for '{self.layer_name}'")
515
+ T = self.LAYER_TRANSITION_CONTEXT_TEMPLATES
516
+ type_name = f"TransitionContext_{self.layer_name}_t"
517
+
518
+ lines = [
519
+ T['comment'].substitute(layer=self.layer_name).rstrip('\n'),
520
+ T['struct_start'].rstrip('\n'),
521
+ T['member_from_state'].substitute(state_type=state_type).rstrip('\n'),
522
+ T['member_event'].substitute(event_type=event_type).rstrip('\n'),
523
+ T['struct_end'].substitute(type_name=type_name).rstrip('\n'),
524
+ ]
525
+ return '\n'.join(lines)
526
+
527
+ # ==================================================================
528
+ # 7. Pending event macros (FIRE_EVENT / MAX_CONSECUTIVE_PENDING_EVENTS)
529
+ # ==================================================================
530
+ def generate_pending_event_macros(self) -> str:
531
+ """
532
+ Generate pending event control macros.
533
+
534
+ Example output:
535
+ /* ============================================================== */
536
+ /* Pending event control */
537
+ /* ============================================================== */
538
+
539
+ /* Event fire macro */
540
+ /* Used within role functions: FIRE_EVENT(ctx, EVENT_XXX_YYY); */
541
+ #define FIRE_EVENT(ctx, evt) do { \\
542
+ (ctx)->pending_event = (uint16_t)(evt); \\
543
+ (ctx)->pending_event_valid = true; \\
544
+ } while(0)
545
+
546
+ /* Upper limit for consecutive pending event processing */
547
+ /* Infinite loop prevention. Can be overridden at build time via -D */
548
+ #ifndef MAX_CONSECUTIVE_PENDING_EVENTS
549
+ #define MAX_CONSECUTIVE_PENDING_EVENTS 16
550
+ #endif
551
+ """
552
+ self._log_debug("Generating pending event macros")
553
+ T = self.MACRO_TEMPLATES
554
+ parts = [T['section_comment']]
555
+ # [F-3 Step 2] FIRE_EVENT(ctx, evt) is removed.
556
+ parts.append(
557
+ '\n'
558
+ '/* [F-3 Step 2] FIRE_EVENT(ctx, evt) is removed. */\n'
559
+ '/* Use FIRE_EVENT_<Layer>(ctx, evt) with an explicit layer. */\n'
560
+ )
561
+ _bs = chr(92) # backslash
562
+ _tpl = (
563
+ '\n'
564
+ '/* Fire event for {layer} layer (protected) */\n'
565
+ '#define FIRE_EVENT_{layer}(ctx, evt) do {{ {bs}\n'
566
+ ' STATABLE_ENTER_CRITICAL(); {bs}\n'
567
+ ' (ctx)->pending_event_{layer} = (uint16_t)(evt); {bs}\n'
568
+ ' (ctx)->pending_event_valid_{layer} = true; {bs}\n'
569
+ ' STATABLE_EXIT_CRITICAL(); {bs}\n'
570
+ '}} while (0)\n'
571
+ )
572
+ seen = set()
573
+ for layer_name, sm in self._all_layers:
574
+ layer = getattr(sm, 'layer_name', '') or layer_name
575
+ if not layer or layer in seen:
576
+ continue
577
+ seen.add(layer)
578
+ parts.append(_tpl.format(layer=layer, bs=_bs))
579
+ parts.append(T['max_consecutive_macro'])
580
+ return ''.join(parts)
581
+
582
+ # ================================================================
583
+ # [C-52] Per-layer queue: types + macros
584
+ # ================================================================
585
+ def generate_layer_queue_types(self) -> str:
586
+ """EventQueueState_t + size macro (emitted once)."""
587
+ if not self._all_layers:
588
+ return ""
589
+ T = self.MACRO_TEMPLATES
590
+ return ''.join([
591
+ T['queue_section_comment'],
592
+ T['critical_hooks'],
593
+ T['queue_size_macro'],
594
+ T['queue_state_type'],
595
+ ])
596
+
597
+ def generate_layer_queue_macros(self) -> str:
598
+ """FIRE_EVENT_QUEUE_<Layer> / INIT_EVENT_QUEUE_<Layer> macros."""
599
+ if not self._all_layers:
600
+ return ""
601
+ parts = []
602
+ seen = set()
603
+ for layer_name, sm in self._all_layers:
604
+ layer = getattr(sm, 'layer_name', '') or layer_name
605
+ if not layer or layer in seen:
606
+ continue
607
+ seen.add(layer)
608
+ parts.append(
609
+ f'\n'
610
+ f'/* Queue send (C-54: protected by hooks; drops+counts if full) */\n'
611
+ f'#define FIRE_EVENT_QUEUE_{layer}(ctx, evt) do {{ \\\n'
612
+ f' STATABLE_ENTER_CRITICAL(); \\\n'
613
+ f' EventQueueState_t *q_ = &(ctx)->queue_{layer}; \\\n'
614
+ f' if (q_->count < STATABLE_LAYER_QUEUE_SIZE) {{ \\\n'
615
+ f' q_->buffer[q_->tail] = (uint16_t)(evt); \\\n'
616
+ f' q_->tail = (uint16_t)((q_->tail + 1U) % STATABLE_LAYER_QUEUE_SIZE); \\\n'
617
+ f' q_->count++; \\\n'
618
+ f' }} else {{ \\\n'
619
+ f' q_->dropped++; \\\n'
620
+ f' }} \\\n'
621
+ f' STATABLE_EXIT_CRITICAL(); \\\n'
622
+ f'}} while (0)\n'
623
+ )
624
+ parts.append(
625
+ f'\n'
626
+ f'/* Queue init (C-54: protected) */\n'
627
+ f'#define INIT_EVENT_QUEUE_{layer}(ctx) do {{ \\\n'
628
+ f' STATABLE_ENTER_CRITICAL(); \\\n'
629
+ f' (ctx)->queue_{layer}.head = 0U; \\\n'
630
+ f' (ctx)->queue_{layer}.tail = 0U; \\\n'
631
+ f' (ctx)->queue_{layer}.count = 0U; \\\n'
632
+ f' (ctx)->queue_{layer}.dropped = 0U; \\\n'
633
+ f' STATABLE_EXIT_CRITICAL(); \\\n'
634
+ f'}} while (0)\n'
635
+ )
636
+ return ''.join(parts)
637
+ def generate_all_structs(self, global_defs: GlobalDefinitions) -> str:
638
+ """Generate all structs in bulk
639
+ (custom_type + system_data + event_flags + system_context).
640
+
641
+ [v2.2.1 fix] Section header now emits a valid C block comment
642
+ using section_line_start / section_line_end.
643
+ """
644
+ lines = []
645
+
646
+ def _section_header(title_key: str) -> str:
647
+ start = self.strings['section_line_start']
648
+ end = self.strings['section_line_end']
649
+ title = self.templates.SECTION_HEADERS.get(title_key, '')
650
+ return f"{start}\n * {title}\n{end}"
651
+
652
+ # custom_types
653
+ if getattr(global_defs, 'custom_types', []):
654
+ lines.append(_section_header('custom_types'))
655
+ lines.append("")
656
+ for custom_type in global_defs.custom_types:
657
+ lines.append(self._generate_custom_type(custom_type))
658
+ lines.append("")
659
+
660
+ # system_structs
661
+ lines.append(_section_header('system_structs'))
662
+ lines.append("")
663
+ lines.append(self._generate_system_data(global_defs))
664
+ lines.append("")
665
+ lines.append(self._generate_event_flags(global_defs))
666
+ lines.append("")
667
+ lines.append(self._generate_system_context(global_defs))
668
+
669
+ return '\n'.join(lines)
670
+
671
+ def generate_all(self, global_defs: GlobalDefinitions) -> Dict[str, str]:
672
+ """
673
+ Return all generated artifacts as a dictionary.
674
+
675
+ Returns:
676
+ {
677
+ 'custom_types': str, # user-defined types
678
+ 'system_data': str, # SystemData_t
679
+ 'event_flags': str, # EventFlags_t
680
+ 'system_context': str, # SystemContext_t (with pending_event)
681
+ 'common_transition_context': str, # common TransitionContext_t (base)
682
+ 'pending_event_macros': str, # FIRE_EVENT / MAX_CONSECUTIVE
683
+ }
684
+ """
685
+ self._log_debug("=== generate_all START (struct_generator) ===")
686
+ result = {
687
+ 'custom_types': '\n\n'.join(
688
+ self._generate_custom_type(ct)
689
+ for ct in getattr(global_defs, 'custom_types', [])
690
+ ),
691
+ 'system_data': self._generate_system_data(global_defs),
692
+ 'event_flags': self._generate_event_flags(global_defs),
693
+ 'system_context': self._generate_system_context(global_defs),
694
+ 'common_transition_context': self.generate_common_transition_context(),
695
+ 'pending_event_macros': self.generate_pending_event_macros(),
696
+ }
697
+ self._log_debug("=== generate_all END (struct_generator) ===")
698
+ return result
699
+
700
+ # ==================================================================
701
+ # Backward-compatible API
702
+ # ==================================================================
703
+ def generate_struct(self, struct_type: str, item) -> str:
704
+ """Backward compat: generate a single struct"""
705
+ if struct_type == 'custom_type':
706
+ return self._generate_custom_type(item)
707
+ elif struct_type == 'system_data':
708
+ return self._generate_system_data(item)
709
+ elif struct_type == 'event_flags':
710
+ return self._generate_event_flags(item)
711
+ elif struct_type == 'system_context':
712
+ return self._generate_system_context(item)
713
+ elif struct_type == 'common_transition_context':
714
+ return self.generate_common_transition_context()
715
+ elif struct_type == 'pending_event_macros':
716
+ return self.generate_pending_event_macros()
717
+ elif struct_type == 'layer_queue_types':
718
+ return self.generate_layer_queue_types()
719
+ elif struct_type == 'layer_queue_macros':
720
+ return self.generate_layer_queue_macros()
721
+ raise ValueError(f"Unknown struct type: {struct_type}")