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,322 @@
|
|
|
1
|
+
# statable_gui/validation_dialog.py
|
|
2
|
+
"""\nValidation / AI integration dialog\nPySide6 compatible\n"""
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
10
|
+
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'codegen'))
|
|
11
|
+
|
|
12
|
+
from PySide6.QtWidgets import (
|
|
13
|
+
QDialog, QVBoxLayout, QHBoxLayout, QPushButton,
|
|
14
|
+
QLabel, QTextEdit, QTreeWidget, QTreeWidgetItem,
|
|
15
|
+
QMessageBox, QTabWidget, QWidget, QGroupBox,
|
|
16
|
+
QFormLayout, QComboBox, QSplitter
|
|
17
|
+
)
|
|
18
|
+
from PySide6.QtCore import Qt
|
|
19
|
+
|
|
20
|
+
from .validator import CodeGenerationValidator
|
|
21
|
+
from .prompt_generator import AIPromptGenerator
|
|
22
|
+
from .response_parser import AIResponseParser
|
|
23
|
+
from .change_applier import ChangeApplier
|
|
24
|
+
from .clipboard_manager import ClipboardManager
|
|
25
|
+
from .response_validator import ResponseValidator
|
|
26
|
+
from .models import ValidationResult
|
|
27
|
+
|
|
28
|
+
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
29
|
+
logger = logging.getLogger("validation_dialog")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ValidationDialog(QDialog):
|
|
33
|
+
"""Validation / AI integration dialog"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, sm, gd, parent=None):
|
|
36
|
+
super().__init__(parent)
|
|
37
|
+
self.sm = sm
|
|
38
|
+
self.gd = gd
|
|
39
|
+
self.validator = CodeGenerationValidator()
|
|
40
|
+
self.prompt_generator = AIPromptGenerator()
|
|
41
|
+
self.response_parser = AIResponseParser()
|
|
42
|
+
self.response_validator = ResponseValidator()
|
|
43
|
+
self.clipboard = ClipboardManager()
|
|
44
|
+
self.validation_result = None
|
|
45
|
+
self.parsed_changes = []
|
|
46
|
+
self.validation_outcome = None
|
|
47
|
+
|
|
48
|
+
self.setWindowTitle("Pre-generation validation / AI diagnosis")
|
|
49
|
+
self.setMinimumSize(900, 700)
|
|
50
|
+
|
|
51
|
+
self._setup_ui()
|
|
52
|
+
self._run_validation()
|
|
53
|
+
|
|
54
|
+
def _setup_ui(self):
|
|
55
|
+
"""Build UI"""
|
|
56
|
+
main_layout = QVBoxLayout(self)
|
|
57
|
+
|
|
58
|
+
# Tab widget
|
|
59
|
+
self.tab_widget = QTabWidget()
|
|
60
|
+
main_layout.addWidget(self.tab_widget)
|
|
61
|
+
|
|
62
|
+
# ===== Validation result tab =====
|
|
63
|
+
self.validation_tab = QWidget()
|
|
64
|
+
self._setup_validation_tab()
|
|
65
|
+
self.tab_widget.addTab(self.validation_tab, "1) Validation result")
|
|
66
|
+
|
|
67
|
+
# ===== AI prompt tab =====
|
|
68
|
+
self.prompt_tab = QWidget()
|
|
69
|
+
self._setup_prompt_tab()
|
|
70
|
+
self.tab_widget.addTab(self.prompt_tab, "2) AI prompt")
|
|
71
|
+
|
|
72
|
+
# ===== AI answer tab =====
|
|
73
|
+
self.response_tab = QWidget()
|
|
74
|
+
self._setup_response_tab()
|
|
75
|
+
self.tab_widget.addTab(self.response_tab, "3) AI answer intake")
|
|
76
|
+
|
|
77
|
+
# ===== Change list tab =====
|
|
78
|
+
self.changes_tab = QWidget()
|
|
79
|
+
self._setup_changes_tab()
|
|
80
|
+
self.tab_widget.addTab(self.changes_tab, "4) Change list / apply")
|
|
81
|
+
|
|
82
|
+
# Close button
|
|
83
|
+
button_layout = QHBoxLayout()
|
|
84
|
+
close_btn = QPushButton("Close")
|
|
85
|
+
close_btn.clicked.connect(self.accept)
|
|
86
|
+
button_layout.addStretch()
|
|
87
|
+
button_layout.addWidget(close_btn)
|
|
88
|
+
main_layout.addLayout(button_layout)
|
|
89
|
+
|
|
90
|
+
def _setup_validation_tab(self):
|
|
91
|
+
"""Set up the validation result tab"""
|
|
92
|
+
layout = QVBoxLayout(self.validation_tab)
|
|
93
|
+
|
|
94
|
+
# Summary label
|
|
95
|
+
self.summary_label = QLabel("")
|
|
96
|
+
self.summary_label.setStyleSheet("font-weight: bold; font-size: 14px;")
|
|
97
|
+
layout.addWidget(self.summary_label)
|
|
98
|
+
|
|
99
|
+
# Problem list
|
|
100
|
+
self.issue_tree = QTreeWidget()
|
|
101
|
+
self.issue_tree.setHeaderLabels(["Severity", "Category", "Message", "Target", "Suggested fix"])
|
|
102
|
+
self.issue_tree.setColumnWidth(0, 80)
|
|
103
|
+
self.issue_tree.setColumnWidth(1, 100)
|
|
104
|
+
self.issue_tree.setColumnWidth(2, 300)
|
|
105
|
+
self.issue_tree.setColumnWidth(3, 100)
|
|
106
|
+
self.issue_tree.setColumnWidth(4, 300)
|
|
107
|
+
layout.addWidget(self.issue_tree)
|
|
108
|
+
|
|
109
|
+
# Re-validate button
|
|
110
|
+
revalidate_btn = QPushButton("Re-validate")
|
|
111
|
+
revalidate_btn.clicked.connect(self._run_validation)
|
|
112
|
+
layout.addWidget(revalidate_btn)
|
|
113
|
+
|
|
114
|
+
def _setup_prompt_tab(self):
|
|
115
|
+
"""Set up the AI prompt tab"""
|
|
116
|
+
layout = QVBoxLayout(self.prompt_tab)
|
|
117
|
+
|
|
118
|
+
# Description label
|
|
119
|
+
info_label = QLabel(
|
|
120
|
+
"Perform AI diagnosis with the following steps:\n"
|
|
121
|
+
"1. Click \"Copy\" button to copy the prompt to the clipboard\n"
|
|
122
|
+
"2. Paste into ChatGPT or similar and ask\n"
|
|
123
|
+
"3. Copy the AI answer\n"
|
|
124
|
+
"4. Paste the answer in the \"AI answer intake\" tab"
|
|
125
|
+
)
|
|
126
|
+
info_label.setWordWrap(True)
|
|
127
|
+
layout.addWidget(info_label)
|
|
128
|
+
|
|
129
|
+
# Copy button
|
|
130
|
+
copy_btn = QPushButton("Copy prompt")
|
|
131
|
+
copy_btn.clicked.connect(self._copy_prompt)
|
|
132
|
+
layout.addWidget(copy_btn)
|
|
133
|
+
|
|
134
|
+
# Prompt preview
|
|
135
|
+
self.prompt_preview = QTextEdit()
|
|
136
|
+
self.prompt_preview.setReadOnly(True)
|
|
137
|
+
layout.addWidget(self.prompt_preview)
|
|
138
|
+
|
|
139
|
+
def _setup_response_tab(self):
|
|
140
|
+
"""Set up the AI answer tab"""
|
|
141
|
+
layout = QVBoxLayout(self.response_tab)
|
|
142
|
+
|
|
143
|
+
# Paste button
|
|
144
|
+
paste_btn = QPushButton("Paste from clipboard")
|
|
145
|
+
paste_btn.clicked.connect(self._paste_response)
|
|
146
|
+
layout.addWidget(paste_btn)
|
|
147
|
+
|
|
148
|
+
# Answer input
|
|
149
|
+
self.response_edit = QTextEdit()
|
|
150
|
+
self.response_edit.setPlaceholderText("Paste the AI answer here")
|
|
151
|
+
layout.addWidget(self.response_edit)
|
|
152
|
+
|
|
153
|
+
# Parse button
|
|
154
|
+
parse_btn = QPushButton("Parse the answer and generate a change list")
|
|
155
|
+
parse_btn.clicked.connect(self._parse_response)
|
|
156
|
+
layout.addWidget(parse_btn)
|
|
157
|
+
|
|
158
|
+
def _setup_changes_tab(self):
|
|
159
|
+
"""Set up the change list tab"""
|
|
160
|
+
layout = QVBoxLayout(self.changes_tab)
|
|
161
|
+
|
|
162
|
+
# Change list
|
|
163
|
+
self.change_tree = QTreeWidget()
|
|
164
|
+
self.change_tree.setHeaderLabels([
|
|
165
|
+
"Selection", "Action", "Parameter", "Reason",
|
|
166
|
+
"Priority", "Confidence", "Status",
|
|
167
|
+
])
|
|
168
|
+
self.change_tree.setColumnWidth(0, 50)
|
|
169
|
+
self.change_tree.setColumnWidth(1, 150)
|
|
170
|
+
self.change_tree.setColumnWidth(2, 400)
|
|
171
|
+
self.change_tree.setColumnWidth(3, 250)
|
|
172
|
+
self.change_tree.setColumnWidth(4, 80)
|
|
173
|
+
self.change_tree.setColumnWidth(5, 80)
|
|
174
|
+
self.change_tree.setColumnWidth(6, 240)
|
|
175
|
+
layout.addWidget(self.change_tree)
|
|
176
|
+
|
|
177
|
+
# Apply button
|
|
178
|
+
apply_btn = QPushButton("Apply selected changes")
|
|
179
|
+
apply_btn.clicked.connect(self._apply_changes)
|
|
180
|
+
layout.addWidget(apply_btn)
|
|
181
|
+
|
|
182
|
+
def _run_validation(self):
|
|
183
|
+
"""Run validation"""
|
|
184
|
+
logger.debug("_run_validation started")
|
|
185
|
+
self.validation_result = self.validator.validate(self.sm, self.gd)
|
|
186
|
+
|
|
187
|
+
# Summary update
|
|
188
|
+
summary = (f"Error: {self.validation_result.error_count} | "
|
|
189
|
+
f"Warning: {self.validation_result.warning_count} | "
|
|
190
|
+
f"Info: {self.validation_result.info_count}")
|
|
191
|
+
self.summary_label.setText(summary)
|
|
192
|
+
|
|
193
|
+
# Problem list update
|
|
194
|
+
self.issue_tree.clear()
|
|
195
|
+
severity_colors = {
|
|
196
|
+
'error': Qt.red,
|
|
197
|
+
'warning': Qt.yellow,
|
|
198
|
+
'info': Qt.blue,
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
for issue in self.validation_result.issues:
|
|
202
|
+
item = QTreeWidgetItem([
|
|
203
|
+
issue.severity.value.upper(),
|
|
204
|
+
issue.category,
|
|
205
|
+
issue.message,
|
|
206
|
+
issue.target,
|
|
207
|
+
issue.suggestion,
|
|
208
|
+
])
|
|
209
|
+
# Severity-based coloring
|
|
210
|
+
color = severity_colors.get(issue.severity.value, Qt.black)
|
|
211
|
+
item.setForeground(0, color)
|
|
212
|
+
self.issue_tree.addTopLevelItem(item)
|
|
213
|
+
|
|
214
|
+
# Prompt generation
|
|
215
|
+
prompt = self.prompt_generator.generate_diagnosis_prompt(
|
|
216
|
+
self.sm, self.gd, self.validation_result
|
|
217
|
+
)
|
|
218
|
+
self.prompt_preview.setPlainText(prompt)
|
|
219
|
+
|
|
220
|
+
logger.debug(f"_run_validation completed: {summary}")
|
|
221
|
+
|
|
222
|
+
def _copy_prompt(self):
|
|
223
|
+
"""Copy prompt to clipboard"""
|
|
224
|
+
prompt = self.prompt_generator.generate_diagnosis_prompt(
|
|
225
|
+
self.sm, self.gd, self.validation_result
|
|
226
|
+
)
|
|
227
|
+
if self.clipboard.copy_to_clipboard(prompt):
|
|
228
|
+
QMessageBox.information(self, "Copy complete",
|
|
229
|
+
"Prompt copied to clipboard.\n"
|
|
230
|
+
"Please paste into ChatGPT or similar and ask.")
|
|
231
|
+
else:
|
|
232
|
+
QMessageBox.warning(self, "Error", "Failed to copy to clipboard.")
|
|
233
|
+
|
|
234
|
+
def _paste_response(self):
|
|
235
|
+
"""Paste AI answer from clipboard"""
|
|
236
|
+
text = self.clipboard.get_from_clipboard()
|
|
237
|
+
if text:
|
|
238
|
+
self.response_edit.setPlainText(text)
|
|
239
|
+
else:
|
|
240
|
+
QMessageBox.warning(self, "Warning", "Clipboard is empty.")
|
|
241
|
+
|
|
242
|
+
def _parse_response(self):
|
|
243
|
+
"""Parse AI answer"""
|
|
244
|
+
text = self.response_edit.toPlainText()
|
|
245
|
+
if not text.strip():
|
|
246
|
+
QMessageBox.warning(self, "Warning", "AI answer is empty.")
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
parsed = self.response_parser.parse(text)
|
|
250
|
+
self.validation_outcome = self.response_validator.validate(
|
|
251
|
+
parsed, self.sm, self.gd)
|
|
252
|
+
outcome = self.validation_outcome
|
|
253
|
+
self.parsed_changes = list(outcome.valid_requests)
|
|
254
|
+
|
|
255
|
+
# Show change list (valid requests only)
|
|
256
|
+
self.change_tree.clear()
|
|
257
|
+
for change in self.parsed_changes:
|
|
258
|
+
item = QTreeWidgetItem()
|
|
259
|
+
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
|
260
|
+
item.setCheckState(0, Qt.Checked)
|
|
261
|
+
item.setText(1, change.action.value)
|
|
262
|
+
item.setText(2, json.dumps(change.params, ensure_ascii=False))
|
|
263
|
+
item.setText(3, change.reason)
|
|
264
|
+
item.setText(4, change.priority)
|
|
265
|
+
item.setText(5, f"{change.confidence:.2f}")
|
|
266
|
+
item.setText(6, "OK")
|
|
267
|
+
self.change_tree.addTopLevelItem(item)
|
|
268
|
+
|
|
269
|
+
# Append invalid requests as unchecked, disabled rows
|
|
270
|
+
for req, reason in outcome.invalid_requests:
|
|
271
|
+
item = QTreeWidgetItem()
|
|
272
|
+
# Non-checkable, non-selectable: an excluded
|
|
273
|
+
# proposal must not participate in 'apply selected'.
|
|
274
|
+
item.setFlags(Qt.ItemIsEnabled)
|
|
275
|
+
item.setText(0, "")
|
|
276
|
+
item.setText(1, req.action.value)
|
|
277
|
+
item.setText(2, json.dumps(req.params, ensure_ascii=False))
|
|
278
|
+
item.setText(3, req.reason)
|
|
279
|
+
item.setText(4, req.priority)
|
|
280
|
+
item.setText(5, f"{req.confidence:.2f}")
|
|
281
|
+
item.setText(6, f"EXCLUDED: {reason}")
|
|
282
|
+
self.change_tree.addTopLevelItem(item)
|
|
283
|
+
|
|
284
|
+
# Switch the tab to the change list
|
|
285
|
+
self.tab_widget.setCurrentIndex(3)
|
|
286
|
+
|
|
287
|
+
n_ok = len(outcome.valid_requests)
|
|
288
|
+
n_ng = len(outcome.invalid_requests)
|
|
289
|
+
msg = f"Parsed {len(parsed)} change(s).\n"
|
|
290
|
+
msg += f"Valid: {n_ok} Excluded: {n_ng}\n"
|
|
291
|
+
if outcome.warnings:
|
|
292
|
+
msg += "\nWarnings:\n" + "\n".join(
|
|
293
|
+
f" * {w}" for w in outcome.warnings)
|
|
294
|
+
QMessageBox.information(self, "Parse complete", msg)
|
|
295
|
+
|
|
296
|
+
def _apply_changes(self):
|
|
297
|
+
"""Apply selected changes"""
|
|
298
|
+
selected_changes = []
|
|
299
|
+
for i in range(self.change_tree.topLevelItemCount()):
|
|
300
|
+
item = self.change_tree.topLevelItem(i)
|
|
301
|
+
if item.checkState(0) == Qt.Checked:
|
|
302
|
+
selected_changes.append(self.parsed_changes[i])
|
|
303
|
+
|
|
304
|
+
if not selected_changes:
|
|
305
|
+
QMessageBox.warning(self, "Warning", "No changes selected for applying.")
|
|
306
|
+
return
|
|
307
|
+
|
|
308
|
+
applier = ChangeApplier(self.sm, self.gd)
|
|
309
|
+
result = applier.apply_all(selected_changes)
|
|
310
|
+
|
|
311
|
+
QMessageBox.information(self, "Apply complete",
|
|
312
|
+
f"{result['applied']} change(s) applied.\n"
|
|
313
|
+
f"Failed: {result['failed']} item(s)")
|
|
314
|
+
|
|
315
|
+
# Re-validate
|
|
316
|
+
self._run_validation()
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def show_validation_dialog(sm, gd, parent=None):
|
|
320
|
+
"""Helper function to show the validation dialog"""
|
|
321
|
+
dialog = ValidationDialog(sm, gd, parent)
|
|
322
|
+
dialog.exec()
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# codegen/validate/validator.py
|
|
2
|
+
"""Validation main class."""
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
from typing import List, Dict, Type
|
|
7
|
+
|
|
8
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
|
9
|
+
|
|
10
|
+
# Logger referenced directly
|
|
11
|
+
from .logger import logger
|
|
12
|
+
|
|
13
|
+
# Model
|
|
14
|
+
from .models import ValidationResult, ValidationIssue, ValidationContext
|
|
15
|
+
|
|
16
|
+
# Validators
|
|
17
|
+
from .items.state_validator import StateValidator
|
|
18
|
+
from .items.event_validator import EventValidator
|
|
19
|
+
from .items.transition_validator import TransitionValidator
|
|
20
|
+
from .items.role_function_validator import RoleFunctionValidator
|
|
21
|
+
from .items.variable_validator import VariableValidator
|
|
22
|
+
from .items.flag_validator import FlagValidator
|
|
23
|
+
from .items.queue_validator import QueueValidator
|
|
24
|
+
from .items.interrupt_validator import InterruptValidator
|
|
25
|
+
from .items.timer_validator import TimerValidator
|
|
26
|
+
from .items.custom_type_validator import CustomTypeValidator
|
|
27
|
+
from .items.cell_validator import CellValidator
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CodeGenerationValidator:
|
|
31
|
+
"""Code generation validation main class"""
|
|
32
|
+
|
|
33
|
+
VALIDATORS: Dict[str, Type] = {
|
|
34
|
+
'state': StateValidator,
|
|
35
|
+
'event': EventValidator,
|
|
36
|
+
'transition': TransitionValidator,
|
|
37
|
+
'role_function': RoleFunctionValidator,
|
|
38
|
+
'variable': VariableValidator,
|
|
39
|
+
'flag': FlagValidator,
|
|
40
|
+
'queue': QueueValidator,
|
|
41
|
+
'interrupt': InterruptValidator,
|
|
42
|
+
'timer': TimerValidator,
|
|
43
|
+
'custom_type': CustomTypeValidator,
|
|
44
|
+
'cell': CellValidator,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
def __init__(self):
|
|
48
|
+
logger.debug("CodeGenerationValidator.__init__ started")
|
|
49
|
+
self._validators = {}
|
|
50
|
+
for category, validator_class in self.VALIDATORS.items():
|
|
51
|
+
logger.debug(f"Creating validator: {category} -> {validator_class.__name__}")
|
|
52
|
+
self._validators[category] = validator_class()
|
|
53
|
+
logger.debug(f"CodeGenerationValidator.__init__ completed: "
|
|
54
|
+
f"{len(self._validators)} validators")
|
|
55
|
+
|
|
56
|
+
def validate(self, state_machine, global_defs) -> ValidationResult:
|
|
57
|
+
logger.debug(f"validate started: sm_id={id(state_machine)}, gd_id={id(global_defs)}")
|
|
58
|
+
|
|
59
|
+
context = ValidationContext(
|
|
60
|
+
state_machine=state_machine,
|
|
61
|
+
global_defs=global_defs,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
result = ValidationResult()
|
|
65
|
+
|
|
66
|
+
for category, validator in self._validators.items():
|
|
67
|
+
logger.debug(f"Running validator: {category}")
|
|
68
|
+
try:
|
|
69
|
+
issues = validator.validate(context)
|
|
70
|
+
result.issues.extend(issues)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.error(f"Validator '{category}' failed: {e}", exc_info=True)
|
|
73
|
+
|
|
74
|
+
logger.debug(f"validate completed: errors={result.error_count}, "
|
|
75
|
+
f"warnings={result.warning_count}, infos={result.info_count}")
|
|
76
|
+
return result
|
|
77
|
+
|
|
78
|
+
def validate_category(self, category: str, state_machine, global_defs
|
|
79
|
+
) -> List[ValidationIssue]:
|
|
80
|
+
logger.debug(f"validate_category: {category}")
|
|
81
|
+
validator = self._validators.get(category)
|
|
82
|
+
if validator is None:
|
|
83
|
+
logger.warning(f"Unknown category: {category}")
|
|
84
|
+
return []
|
|
85
|
+
|
|
86
|
+
context = ValidationContext(
|
|
87
|
+
state_machine=state_machine,
|
|
88
|
+
global_defs=global_defs,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return validator.validate(context)
|
|
92
|
+
|
|
93
|
+
def get_categories(self) -> List[str]:
|
|
94
|
+
return list(self._validators.keys())
|