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,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
|