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