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,30 @@
1
+ # codegen/validate/items/__init__.py
2
+ """Validator items package."""
3
+
4
+ from .base_validator import BaseValidator
5
+ from .state_validator import StateValidator
6
+ from .event_validator import EventValidator
7
+ from .transition_validator import TransitionValidator
8
+ from .role_function_validator import RoleFunctionValidator
9
+ from .variable_validator import VariableValidator
10
+ from .flag_validator import FlagValidator
11
+ from .queue_validator import QueueValidator
12
+ from .interrupt_validator import InterruptValidator
13
+ from .timer_validator import TimerValidator
14
+ from .custom_type_validator import CustomTypeValidator
15
+ from .cell_validator import CellValidator
16
+
17
+ __all__ = [
18
+ 'BaseValidator',
19
+ 'StateValidator',
20
+ 'EventValidator',
21
+ 'TransitionValidator',
22
+ 'RoleFunctionValidator',
23
+ 'VariableValidator',
24
+ 'FlagValidator',
25
+ 'QueueValidator',
26
+ 'InterruptValidator',
27
+ 'TimerValidator',
28
+ 'CustomTypeValidator',
29
+ 'CellValidator',
30
+ ]
@@ -0,0 +1,25 @@
1
+ # codegen/validate/items/base_validator.py
2
+ """\nValidator base class\n"""
3
+
4
+ import sys
5
+ import os
6
+ from typing import List
7
+
8
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
9
+
10
+ from ..models import ValidationIssue, ValidationContext
11
+
12
+
13
+ class BaseValidator:
14
+ """Validator base class"""
15
+
16
+ category: str = ""
17
+ rules = {}
18
+
19
+ def validate(self, context: ValidationContext) -> List[ValidationIssue]:
20
+ issues = []
21
+ for code, rule_func in self.rules.items():
22
+ result = rule_func(context)
23
+ if result:
24
+ issues.extend(result)
25
+ return issues
@@ -0,0 +1,275 @@
1
+ # codegen/validate/items/cell_validator.py
2
+ """Cell-level validator (v2.2) - integrated into the validate layer.
3
+
4
+ Rules:
5
+ CELL_EMPTY_CONDITION (Warning) condition empty AND target empty
6
+ CELL_DUPLICATE_LABEL (Error) same label within one cell
7
+ CELL_DANGLING_RELATION (Error) relation members referencing missing labels
8
+ CELL_UNREACHABLE_TRANSITION (Warning) transition after a Commit+else
9
+ CELL_OVERLAP_POSSIBLE (Warning) duplicate condition strings in one cell
10
+ CELL_DUPLICATE_TARGET (Info) multiple transitions targeting the same state
11
+ CELL_EXCLUSIVE_NO_RETURN (Info) exclusive member without early_return
12
+ CELL_EMPTY_TARGET (Warning) target empty AND early_return False
13
+ """
14
+
15
+ import logging
16
+ from typing import List
17
+
18
+ from ..models import (
19
+ ValidationIssue, ValidationSeverity, ValidationContext,
20
+ )
21
+ from ..data.validation_rules import VALIDATION_RULES
22
+ from .base_validator import BaseValidator
23
+
24
+ logger = logging.getLogger("validate.cell_validator")
25
+
26
+
27
+ class CellValidator(BaseValidator):
28
+ """Validates each (source, event) cell of a StateMachine."""
29
+
30
+ category = "cell"
31
+
32
+ def __init__(self):
33
+ self.rules_data = VALIDATION_RULES.get(self.category, {})
34
+ self.rules = {
35
+ "CELL_EMPTY_CONDITION": self._rule_empty_condition,
36
+ "CELL_DUPLICATE_LABEL": self._rule_duplicate_label,
37
+ "CELL_DANGLING_RELATION": self._rule_dangling_relation,
38
+ "CELL_UNREACHABLE_TRANSITION": self._rule_unreachable,
39
+ "CELL_OVERLAP_POSSIBLE": self._rule_overlap,
40
+ "CELL_DUPLICATE_TARGET": self._rule_duplicate_target,
41
+ "CELL_EXCLUSIVE_NO_RETURN": self._rule_exclusive_no_return,
42
+ "CELL_EMPTY_TARGET": self._rule_empty_target,
43
+ }
44
+
45
+ # ------------------------------------------------------------------
46
+ # Helpers
47
+ # ------------------------------------------------------------------
48
+ def _severity(self, code: str) -> ValidationSeverity:
49
+ rule = self.rules_data.get(code, {})
50
+ return ValidationSeverity.from_string(rule.get("severity", "info"))
51
+
52
+ def _make_issue(self, code: str, message: str,
53
+ target: str = "") -> ValidationIssue:
54
+ return ValidationIssue(
55
+ category=self.category,
56
+ code=code,
57
+ message=message,
58
+ severity=self._severity(code),
59
+ target=target,
60
+ )
61
+
62
+ def _iter_cells(self, context: ValidationContext):
63
+ """Return all (source, event) keys that have transitions or relations."""
64
+ sm = context.state_machine
65
+ if sm is None:
66
+ return []
67
+ keys = set()
68
+ for t in sm.transitions:
69
+ keys.add((t.source, t.event or ""))
70
+ for (s, e) in sm.get_cell_keys():
71
+ keys.add((s, e))
72
+ return sorted(keys)
73
+
74
+ def _loc(self, source: str, event: str) -> str:
75
+ return f"{source}/{event or 'Completion'}"
76
+
77
+ # ------------------------------------------------------------------
78
+ # Rule 1: CELL_EMPTY_CONDITION
79
+ # ------------------------------------------------------------------
80
+ def _rule_empty_condition(self, context) -> List[ValidationIssue]:
81
+ sm = context.state_machine
82
+ if sm is None:
83
+ return []
84
+ issues = []
85
+ for (source, event) in self._iter_cells(context):
86
+ for t in sm.get_transitions_for_cell(source, event):
87
+ cond = (getattr(t, "condition", "") or "").strip()
88
+ target = (getattr(t, "target", "") or "").strip()
89
+ if not cond and not target:
90
+ label = getattr(t, "label", "") or "(no label)"
91
+ issues.append(self._make_issue(
92
+ "CELL_EMPTY_CONDITION",
93
+ f"Transition '{label}' has no condition and no target",
94
+ self._loc(source, event),
95
+ ))
96
+ return issues
97
+
98
+ # ------------------------------------------------------------------
99
+ # Rule 2: CELL_DUPLICATE_LABEL
100
+ # ------------------------------------------------------------------
101
+ def _rule_duplicate_label(self, context) -> List[ValidationIssue]:
102
+ sm = context.state_machine
103
+ if sm is None:
104
+ return []
105
+ issues = []
106
+ for (source, event) in self._iter_cells(context):
107
+ seen = set()
108
+ for t in sm.get_transitions_for_cell(source, event):
109
+ label = getattr(t, "label", "") or ""
110
+ if not label:
111
+ continue
112
+ if label in seen:
113
+ issues.append(self._make_issue(
114
+ "CELL_DUPLICATE_LABEL",
115
+ f"Duplicate transition label '{label}'",
116
+ self._loc(source, event),
117
+ ))
118
+ else:
119
+ seen.add(label)
120
+ return issues
121
+
122
+ # ------------------------------------------------------------------
123
+ # Rule 3: CELL_DANGLING_RELATION
124
+ # ------------------------------------------------------------------
125
+ def _rule_dangling_relation(self, context) -> List[ValidationIssue]:
126
+ sm = context.state_machine
127
+ if sm is None:
128
+ return []
129
+ issues = []
130
+ for (source, event) in self._iter_cells(context):
131
+ transitions = sm.get_transitions_for_cell(source, event)
132
+ relations = sm.get_relations_for_cell(source, event)
133
+ if not relations:
134
+ continue
135
+ valid = {getattr(t, "label", "") or "" for t in transitions}
136
+ valid.discard("")
137
+ for r in relations:
138
+ for m in (r.members or []):
139
+ if m not in valid:
140
+ issues.append(self._make_issue(
141
+ "CELL_DANGLING_RELATION",
142
+ f"Relation references missing label '{m}'",
143
+ self._loc(source, event),
144
+ ))
145
+ return issues
146
+
147
+ # ------------------------------------------------------------------
148
+ # Rule 4: CELL_UNREACHABLE_TRANSITION
149
+ # ------------------------------------------------------------------
150
+ def _rule_unreachable(self, context) -> List[ValidationIssue]:
151
+ sm = context.state_machine
152
+ if sm is None:
153
+ return []
154
+ issues = []
155
+ for (source, event) in self._iter_cells(context):
156
+ blocker = False
157
+ for t in sm.get_transitions_for_cell(source, event):
158
+ label = getattr(t, "label", "") or "(no label)"
159
+ if blocker:
160
+ issues.append(self._make_issue(
161
+ "CELL_UNREACHABLE_TRANSITION",
162
+ f"Transition '{label}' is unreachable "
163
+ f"(a prior Commit transition always fires)",
164
+ self._loc(source, event),
165
+ ))
166
+ continue
167
+ er = getattr(t, "early_return", False)
168
+ cond = (getattr(t, "condition", "") or "").strip()
169
+ has_else = getattr(t, "has_else", True)
170
+ if er and (not cond or has_else):
171
+ blocker = True
172
+ return issues
173
+
174
+ # ------------------------------------------------------------------
175
+ # Rule 5: CELL_OVERLAP_POSSIBLE
176
+ # ------------------------------------------------------------------
177
+ def _rule_overlap(self, context) -> List[ValidationIssue]:
178
+ sm = context.state_machine
179
+ if sm is None:
180
+ return []
181
+ issues = []
182
+ for (source, event) in self._iter_cells(context):
183
+ seen = {}
184
+ for t in sm.get_transitions_for_cell(source, event):
185
+ cond = (getattr(t, "condition", "") or "").strip()
186
+ if not cond:
187
+ continue
188
+ label = getattr(t, "label", "") or "(no label)"
189
+ if cond in seen:
190
+ issues.append(self._make_issue(
191
+ "CELL_OVERLAP_POSSIBLE",
192
+ f"Transitions '{seen[cond]}' and '{label}' "
193
+ f"share the same condition '{cond}'",
194
+ self._loc(source, event),
195
+ ))
196
+ else:
197
+ seen[cond] = label
198
+ return issues
199
+
200
+ # ------------------------------------------------------------------
201
+ # Rule 6: CELL_DUPLICATE_TARGET
202
+ # ------------------------------------------------------------------
203
+ def _rule_duplicate_target(self, context) -> List[ValidationIssue]:
204
+ sm = context.state_machine
205
+ if sm is None:
206
+ return []
207
+ issues = []
208
+ for (source, event) in self._iter_cells(context):
209
+ by_target = {}
210
+ for t in sm.get_transitions_for_cell(source, event):
211
+ target = (getattr(t, "target", "") or "").strip()
212
+ if not target:
213
+ continue
214
+ label = getattr(t, "label", "") or "(no label)"
215
+ by_target.setdefault(target, []).append(label)
216
+ for target, labels in by_target.items():
217
+ if len(labels) > 1:
218
+ issues.append(self._make_issue(
219
+ "CELL_DUPLICATE_TARGET",
220
+ f"Multiple transitions target '{target}': "
221
+ f"{', '.join(labels)}",
222
+ self._loc(source, event),
223
+ ))
224
+ return issues
225
+
226
+ # ------------------------------------------------------------------
227
+ # Rule 7: CELL_EXCLUSIVE_NO_RETURN
228
+ # ------------------------------------------------------------------
229
+ def _rule_exclusive_no_return(self, context) -> List[ValidationIssue]:
230
+ sm = context.state_machine
231
+ if sm is None:
232
+ return []
233
+ issues = []
234
+ for (source, event) in self._iter_cells(context):
235
+ transitions = sm.get_transitions_for_cell(source, event)
236
+ relations = sm.get_relations_for_cell(source, event)
237
+ by_label = {getattr(t, "label", "") or "": t for t in transitions}
238
+ for r in relations:
239
+ if r.kind != "exclusive":
240
+ continue
241
+ for m in (r.members or []):
242
+ t = by_label.get(m)
243
+ if t is None:
244
+ continue
245
+ if not getattr(t, "early_return", False):
246
+ issues.append(self._make_issue(
247
+ "CELL_EXCLUSIVE_NO_RETURN",
248
+ f"Exclusive member '{m}' does not have "
249
+ f"early_return=True",
250
+ self._loc(source, event),
251
+ ))
252
+ return issues
253
+
254
+ # ------------------------------------------------------------------
255
+ # Rule 8: CELL_EMPTY_TARGET
256
+ # ------------------------------------------------------------------
257
+ def _rule_empty_target(self, context) -> List[ValidationIssue]:
258
+ sm = context.state_machine
259
+ if sm is None:
260
+ return []
261
+ issues = []
262
+ for (source, event) in self._iter_cells(context):
263
+ for t in sm.get_transitions_for_cell(source, event):
264
+ target = (getattr(t, "target", "") or "").strip()
265
+ er = getattr(t, "early_return", False)
266
+ cond = (getattr(t, "condition", "") or "").strip()
267
+ if not target and not er and cond:
268
+ label = getattr(t, "label", "") or "(no label)"
269
+ issues.append(self._make_issue(
270
+ "CELL_EMPTY_TARGET",
271
+ f"Transition '{label}' has no target "
272
+ f"and no early_return",
273
+ self._loc(source, event),
274
+ ))
275
+ return issues
@@ -0,0 +1,75 @@
1
+ # codegen/validate/items/custom_type_validator.py
2
+ """
3
+ Custom type validation
4
+ """
5
+
6
+ import sys
7
+ import os
8
+ from typing import List
9
+
10
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
11
+
12
+ from ..logger import logger
13
+ from ..models import ValidationIssue, ValidationSeverity, ValidationContext
14
+ from ..data.validation_rules import VALIDATION_RULES
15
+ from .base_validator import BaseValidator
16
+
17
+
18
+ class CustomTypeValidator(BaseValidator):
19
+ """Custom type validator class"""
20
+
21
+ category = "custom_type"
22
+
23
+ def __init__(self):
24
+ logger.debug("CustomTypeValidator.__init__ started")
25
+ self.rules_data = VALIDATION_RULES.get(self.category, {})
26
+ self.rules = {
27
+ 'TYPE_DUPLICATE_NAME': self._check_duplicate_names,
28
+ 'TYPE_NO_MEMBERS': self._check_no_members,
29
+ }
30
+ logger.debug(f"CustomTypeValidator.__init__ completed: {len(self.rules)} rules")
31
+
32
+ def validate(self, context: ValidationContext) -> List[ValidationIssue]:
33
+ logger.debug(f"CustomTypeValidator.validate started")
34
+ issues = []
35
+ for code, rule_func in self.rules.items():
36
+ try:
37
+ result = rule_func(context)
38
+ if result:
39
+ issues.extend(result)
40
+ except Exception as e:
41
+ logger.error(f"Rule '{code}' failed: {e}", exc_info=True)
42
+ return issues
43
+
44
+ def _create_issue(self, code: str, **kwargs) -> ValidationIssue:
45
+ rule = self.rules_data.get(code, {})
46
+ message = rule.get('message', '').format(**kwargs)
47
+ severity = ValidationSeverity.from_string(rule.get('severity', 'info'))
48
+ return ValidationIssue(
49
+ category=self.category,
50
+ code=code,
51
+ message=message,
52
+ severity=severity,
53
+ suggestion=rule.get('suggestion', ''),
54
+ target=kwargs.get('name', ''),
55
+ )
56
+
57
+ def _check_duplicate_names(self, context):
58
+ issues = []
59
+ seen = set()
60
+ for custom_type in context.custom_types:
61
+ name = getattr(custom_type, 'name', '')
62
+ if name in seen:
63
+ issues.append(self._create_issue('TYPE_DUPLICATE_NAME', name=name))
64
+ else:
65
+ seen.add(name)
66
+ return issues
67
+
68
+ def _check_no_members(self, context):
69
+ issues = []
70
+ for custom_type in context.custom_types:
71
+ members = getattr(custom_type, 'members', [])
72
+ name = getattr(custom_type, 'name', '')
73
+ if not members:
74
+ issues.append(self._create_issue('TYPE_NO_MEMBERS', name=name))
75
+ return issues
@@ -0,0 +1,78 @@
1
+ # codegen/validate/items/event_validator.py
2
+ """
3
+ Event validation
4
+
5
+ [v1.8 section 11.2 #6]
6
+ - Unified logger output with StateValidator
7
+ - Added start / completion logs to __init__ / validate
8
+ - Wrapped rule execution in try/except; on failure, log via logger.error
9
+ """
10
+
11
+ import sys
12
+ import os
13
+ from typing import List
14
+
15
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
16
+
17
+ from ..logger import logger
18
+ from ..models import ValidationIssue, ValidationSeverity, ValidationContext
19
+ from ..data.validation_rules import VALIDATION_RULES
20
+ from .base_validator import BaseValidator
21
+
22
+
23
+ class EventValidator(BaseValidator):
24
+ """Event validation class"""
25
+
26
+ category = "event"
27
+
28
+ def __init__(self):
29
+ logger.debug("EventValidator.__init__ started")
30
+ self.rules_data = VALIDATION_RULES.get(self.category, {})
31
+ self.rules = {
32
+ 'EVENT_UNUSED': self._check_unused_events,
33
+ 'EVENT_NO_TRANSITION': self._check_events_without_transitions,
34
+ }
35
+ logger.debug(f"EventValidator.__init__ completed: {len(self.rules)} rules")
36
+
37
+ def validate(self, context: ValidationContext) -> List[ValidationIssue]:
38
+ logger.debug("EventValidator.validate started")
39
+ issues = []
40
+ for code, rule_func in self.rules.items():
41
+ try:
42
+ result = rule_func(context)
43
+ if result:
44
+ issues.extend(result)
45
+ except Exception as e:
46
+ logger.error(f"Rule '{code}' failed: {e}", exc_info=True)
47
+ logger.debug(f"EventValidator.validate completed: {len(issues)} issues")
48
+ return issues
49
+
50
+ def _create_issue(self, code: str, **kwargs) -> ValidationIssue:
51
+ rule = self.rules_data.get(code, {})
52
+ message = rule.get('message', '').format(**kwargs)
53
+ severity = ValidationSeverity.from_string(rule.get('severity', 'info'))
54
+
55
+ return ValidationIssue(
56
+ category=self.category,
57
+ code=code,
58
+ message=message,
59
+ severity=severity,
60
+ suggestion=rule.get('suggestion', ''),
61
+ target=kwargs.get('name', ''),
62
+ )
63
+
64
+ def _check_unused_events(self, context):
65
+ issues = []
66
+ used_events = set(t.event for t in context.transitions)
67
+ for name in context.events.keys():
68
+ if name not in used_events:
69
+ issues.append(self._create_issue('EVENT_UNUSED', name=name))
70
+ return issues
71
+
72
+ def _check_events_without_transitions(self, context):
73
+ issues = []
74
+ for name in context.events.keys():
75
+ has_transition = any(t.event == name for t in context.transitions)
76
+ if not has_transition:
77
+ issues.append(self._create_issue('EVENT_NO_TRANSITION', name=name))
78
+ return issues
@@ -0,0 +1,74 @@
1
+ # codegen/validate/items/flag_validator.py
2
+ """\nFlag validation\n"""
3
+
4
+ import sys
5
+ import os
6
+ from typing import List
7
+
8
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
9
+
10
+ from ..logger import logger
11
+ from ..models import ValidationIssue, ValidationSeverity, ValidationContext
12
+ from ..data.validation_rules import VALIDATION_RULES
13
+ from .base_validator import BaseValidator
14
+
15
+
16
+ class FlagValidator(BaseValidator):
17
+ """Flag validation class"""
18
+
19
+ category = "flag"
20
+
21
+ def __init__(self):
22
+ logger.debug("FlagValidator.__init__ started")
23
+ self.rules_data = VALIDATION_RULES.get(self.category, {})
24
+ self.rules = {
25
+ 'FLAG_DUPLICATE_NAME': self._check_duplicate_names,
26
+ 'FLAG_INVALID_RANGE': self._check_invalid_range,
27
+ }
28
+ logger.debug(f"FlagValidator.__init__ completed: {len(self.rules)} rules")
29
+
30
+ def validate(self, context: ValidationContext) -> List[ValidationIssue]:
31
+ logger.debug(f"FlagValidator.validate started")
32
+ issues = []
33
+ for code, rule_func in self.rules.items():
34
+ try:
35
+ result = rule_func(context)
36
+ if result:
37
+ issues.extend(result)
38
+ except Exception as e:
39
+ logger.error(f"Rule '{code}' failed: {e}", exc_info=True)
40
+ return issues
41
+
42
+ def _create_issue(self, code: str, **kwargs) -> ValidationIssue:
43
+ rule = self.rules_data.get(code, {})
44
+ message = rule.get('message', '').format(**kwargs)
45
+ severity = ValidationSeverity.from_string(rule.get('severity', 'info'))
46
+ return ValidationIssue(
47
+ category=self.category,
48
+ code=code,
49
+ message=message,
50
+ severity=severity,
51
+ suggestion=rule.get('suggestion', ''),
52
+ target=kwargs.get('name', ''),
53
+ )
54
+
55
+ def _check_duplicate_names(self, context):
56
+ issues = []
57
+ seen = set()
58
+ for flag in context.flags:
59
+ name = getattr(flag, 'name', '')
60
+ if name in seen:
61
+ issues.append(self._create_issue('FLAG_DUPLICATE_NAME', name=name))
62
+ else:
63
+ seen.add(name)
64
+ return issues
65
+
66
+ def _check_invalid_range(self, context):
67
+ issues = []
68
+ for flag in context.flags:
69
+ min_value = getattr(flag, 'min_value', 0)
70
+ max_value = getattr(flag, 'max_value', 0)
71
+ name = getattr(flag, 'name', '')
72
+ if min_value > max_value:
73
+ issues.append(self._create_issue('FLAG_INVALID_RANGE', name=name))
74
+ return issues
@@ -0,0 +1,75 @@
1
+ # codegen/validate/items/interrupt_validator.py
2
+ """\nInterrupt validation\n"""
3
+
4
+ import sys
5
+ import os
6
+ from typing import List
7
+
8
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
9
+
10
+ from ..logger import logger
11
+ from ..models import ValidationIssue, ValidationSeverity, ValidationContext
12
+ from ..data.validation_rules import VALIDATION_RULES
13
+ from .base_validator import BaseValidator
14
+
15
+
16
+ class InterruptValidator(BaseValidator):
17
+ """Interrupt validation class"""
18
+
19
+ category = "interrupt"
20
+
21
+ def __init__(self):
22
+ logger.debug("InterruptValidator.__init__ started")
23
+ self.rules_data = VALIDATION_RULES.get(self.category, {})
24
+ self.rules = {
25
+ 'INTERRUPT_DUPLICATE_NAME': self._check_duplicate_names,
26
+ 'INTERRUPT_UNDEFINED_EVENT': self._check_undefined_event,
27
+ }
28
+ logger.debug(f"InterruptValidator.__init__ completed: {len(self.rules)} rules")
29
+
30
+ def validate(self, context: ValidationContext) -> List[ValidationIssue]:
31
+ logger.debug(f"InterruptValidator.validate started")
32
+ issues = []
33
+ for code, rule_func in self.rules.items():
34
+ try:
35
+ result = rule_func(context)
36
+ if result:
37
+ issues.extend(result)
38
+ except Exception as e:
39
+ logger.error(f"Rule '{code}' failed: {e}", exc_info=True)
40
+ return issues
41
+
42
+ def _create_issue(self, code: str, **kwargs) -> ValidationIssue:
43
+ rule = self.rules_data.get(code, {})
44
+ message = rule.get('message', '').format(**kwargs)
45
+ severity = ValidationSeverity.from_string(rule.get('severity', 'info'))
46
+ return ValidationIssue(
47
+ category=self.category,
48
+ code=code,
49
+ message=message,
50
+ severity=severity,
51
+ suggestion=rule.get('suggestion', ''),
52
+ target=kwargs.get('name', ''),
53
+ )
54
+
55
+ def _check_duplicate_names(self, context):
56
+ issues = []
57
+ seen = set()
58
+ for interrupt in context.interrupts:
59
+ name = getattr(interrupt, 'name', '')
60
+ if name in seen:
61
+ issues.append(self._create_issue('INTERRUPT_DUPLICATE_NAME', name=name))
62
+ else:
63
+ seen.add(name)
64
+ return issues
65
+
66
+ def _check_undefined_event(self, context):
67
+ issues = []
68
+ event_names = set(context.events.keys())
69
+ for interrupt in context.interrupts:
70
+ event_list = getattr(interrupt, 'event_names', [])
71
+ name = getattr(interrupt, 'name', '')
72
+ for event_name in event_list:
73
+ if event_name and event_name not in event_names:
74
+ issues.append(self._create_issue('INTERRUPT_UNDEFINED_EVENT', name=name))
75
+ return issues