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
codegen/type_mapper.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# codegen/type_mapper.py
|
|
2
|
+
"""
|
|
3
|
+
C language type mapping module (dictionary-driven)
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import os
|
|
8
|
+
from typing import Set, List
|
|
9
|
+
|
|
10
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from .code_templates import CodeTemplates
|
|
14
|
+
except ImportError:
|
|
15
|
+
from code_templates import CodeTemplates
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CTypeMapper:
|
|
19
|
+
"""Class mapping StaTable types to C language types"""
|
|
20
|
+
|
|
21
|
+
TYPE_MAPPING = {
|
|
22
|
+
'int': 'int', 'int8': 'int8_t', 'int16': 'int16_t',
|
|
23
|
+
'int32': 'int32_t', 'int64': 'int64_t',
|
|
24
|
+
'uint': 'unsigned int', 'uint8': 'uint8_t', 'uint16': 'uint16_t',
|
|
25
|
+
'uint32': 'uint32_t', 'uint64': 'uint64_t',
|
|
26
|
+
'float': 'float', 'double': 'double', 'bool': 'bool',
|
|
27
|
+
'char': 'char', 'string': 'char*', 'void': 'void',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
TYPE_CATEGORIES = {
|
|
31
|
+
'int': 'integer', 'int8': 'integer', 'int16': 'integer',
|
|
32
|
+
'int32': 'integer', 'int64': 'integer',
|
|
33
|
+
'uint': 'integer', 'uint8': 'integer', 'uint16': 'integer',
|
|
34
|
+
'uint32': 'integer', 'uint64': 'integer',
|
|
35
|
+
'float': 'float', 'double': 'float', 'bool': 'boolean',
|
|
36
|
+
'char': 'character', 'string': 'string', 'void': 'void',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
HEADER_REQUIREMENTS = {
|
|
40
|
+
'integer': '#include <stdint.h>',
|
|
41
|
+
'boolean': '#include <stdbool.h>',
|
|
42
|
+
'string': '#include <string.h>',
|
|
43
|
+
'float': '#include <math.h>',
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
def __init__(self):
|
|
47
|
+
self.templates = CodeTemplates()
|
|
48
|
+
self.strings = self.templates.STRINGS
|
|
49
|
+
self.formats = self.templates.FORMATS
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def map_type(cls, sta_type):
|
|
53
|
+
return cls.TYPE_MAPPING.get(sta_type, sta_type)
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def get_type_category(cls, sta_type):
|
|
57
|
+
return cls.TYPE_CATEGORIES.get(sta_type, 'custom')
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def get_required_headers(cls, types):
|
|
61
|
+
headers = set()
|
|
62
|
+
categories = set()
|
|
63
|
+
for type_name in types:
|
|
64
|
+
categories.add(cls.get_type_category(type_name))
|
|
65
|
+
for category in categories:
|
|
66
|
+
header = cls.HEADER_REQUIREMENTS.get(category)
|
|
67
|
+
if header:
|
|
68
|
+
headers.add(header)
|
|
69
|
+
return sorted(headers)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# codegen/validate/__init__.py
|
|
2
|
+
"""\nValidation / AI integration package\n"""
|
|
3
|
+
|
|
4
|
+
from .logger import logger, setup_logger
|
|
5
|
+
from .models import (
|
|
6
|
+
ValidationSeverity,
|
|
7
|
+
ValidationIssue,
|
|
8
|
+
ValidationResult,
|
|
9
|
+
ValidationContext,
|
|
10
|
+
)
|
|
11
|
+
from .validator import CodeGenerationValidator
|
|
12
|
+
from .prompt_generator import AIPromptGenerator
|
|
13
|
+
from .response_parser import AIResponseParser
|
|
14
|
+
from .change_actions import ChangeRequest, ChangeActionType
|
|
15
|
+
from .change_applier import ChangeApplier
|
|
16
|
+
from .clipboard_manager import ClipboardManager
|
|
17
|
+
from .response_validator import (
|
|
18
|
+
ResponseValidator,
|
|
19
|
+
ResponseValidationResult,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
'logger',
|
|
24
|
+
'setup_logger',
|
|
25
|
+
'ValidationSeverity',
|
|
26
|
+
'ValidationIssue',
|
|
27
|
+
'ValidationResult',
|
|
28
|
+
'ValidationContext',
|
|
29
|
+
'CodeGenerationValidator',
|
|
30
|
+
'AIPromptGenerator',
|
|
31
|
+
'AIResponseParser',
|
|
32
|
+
'ChangeRequest',
|
|
33
|
+
'ChangeActionType',
|
|
34
|
+
'ChangeApplier',
|
|
35
|
+
'ClipboardManager',
|
|
36
|
+
'ResponseValidator',
|
|
37
|
+
'ResponseValidationResult',
|
|
38
|
+
]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# codegen/validate/change_actions.py
|
|
2
|
+
"""
|
|
3
|
+
Change action definitions.
|
|
4
|
+
|
|
5
|
+
[v2.2 §12-6 change]
|
|
6
|
+
- Added 7 cell-level AI action types:
|
|
7
|
+
ADD_CELL / REMOVE_CELL
|
|
8
|
+
ADD_ACTION_STEP / REMOVE_ACTION_STEP
|
|
9
|
+
ADD_TRANSITION_RELATION / REMOVE_TRANSITION_RELATION
|
|
10
|
+
SET_EARLY_RETURN
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from typing import Dict, Any, List
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ChangeActionType(Enum):
|
|
19
|
+
"""Change action kind"""
|
|
20
|
+
# --------------------------------------------------------------
|
|
21
|
+
# Legacy actions
|
|
22
|
+
# --------------------------------------------------------------
|
|
23
|
+
SET_INITIAL = "set_initial"
|
|
24
|
+
ADD_TRANSITION = "add_transition"
|
|
25
|
+
ADD_STATE = "add_state"
|
|
26
|
+
ADD_EVENT = "add_event"
|
|
27
|
+
REMOVE_TRANSITION = "remove_transition"
|
|
28
|
+
UPDATE_TRANSITION = "update_transition"
|
|
29
|
+
ADD_ROLE_FUNCTION = "add_role_function"
|
|
30
|
+
REMOVE_ROLE_FUNCTION = "remove_role_function"
|
|
31
|
+
ADD_VARIABLE = "add_variable"
|
|
32
|
+
ADD_FLAG = "add_flag"
|
|
33
|
+
|
|
34
|
+
# --------------------------------------------------------------
|
|
35
|
+
# v2.2 §12-6: Cell-level AI action extensions
|
|
36
|
+
# --------------------------------------------------------------
|
|
37
|
+
ADD_CELL = "add_cell"
|
|
38
|
+
REMOVE_CELL = "remove_cell"
|
|
39
|
+
ADD_ACTION_STEP = "add_action_step"
|
|
40
|
+
REMOVE_ACTION_STEP = "remove_action_step"
|
|
41
|
+
ADD_TRANSITION_RELATION = "add_transition_relation"
|
|
42
|
+
REMOVE_TRANSITION_RELATION = "remove_transition_relation"
|
|
43
|
+
SET_EARLY_RETURN = "set_early_return"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class ChangeRequest:
|
|
48
|
+
"""Change request"""
|
|
49
|
+
action: ChangeActionType
|
|
50
|
+
params: Dict[str, Any] = field(default_factory=dict)
|
|
51
|
+
reason: str = ""
|
|
52
|
+
source: str = "ai"
|
|
53
|
+
# [v2.8.0 Phase B] extended fields
|
|
54
|
+
id: str = ""
|
|
55
|
+
evidence: List[str] = field(default_factory=list)
|
|
56
|
+
priority: str = "medium"
|
|
57
|
+
confidence: float = 1.0
|
|
58
|
+
|
|
59
|
+
def to_dict(self) -> Dict:
|
|
60
|
+
return {
|
|
61
|
+
'id': self.id,
|
|
62
|
+
'action': self.action.value,
|
|
63
|
+
'params': self.params,
|
|
64
|
+
'reason': self.reason,
|
|
65
|
+
'source': self.source,
|
|
66
|
+
'evidence': list(self.evidence),
|
|
67
|
+
'priority': self.priority,
|
|
68
|
+
'confidence': self.confidence,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def from_dict(cls, data: Dict) -> 'ChangeRequest':
|
|
73
|
+
try:
|
|
74
|
+
confidence = float(data.get('confidence', 1.0))
|
|
75
|
+
except (TypeError, ValueError):
|
|
76
|
+
confidence = 1.0
|
|
77
|
+
return cls(
|
|
78
|
+
action=ChangeActionType(data['action']),
|
|
79
|
+
params=data.get('params', {}),
|
|
80
|
+
reason=data.get('reason', ''),
|
|
81
|
+
source=data.get('source', 'ai'),
|
|
82
|
+
id=str(data.get('id', '') or ''),
|
|
83
|
+
evidence=list(data.get('evidence', []) or []),
|
|
84
|
+
priority=str(data.get('priority', 'medium') or 'medium'),
|
|
85
|
+
confidence=confidence,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def __str__(self) -> str:
|
|
89
|
+
return (f"ChangeRequest(action={self.action.value}, "
|
|
90
|
+
f"params={self.params}, reason={self.reason})")
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
# codegen/validate/change_applier.py
|
|
2
|
+
"""
|
|
3
|
+
Change apply engine.
|
|
4
|
+
|
|
5
|
+
[v1.6 change]
|
|
6
|
+
- Added 'remove_role_function' to _handlers
|
|
7
|
+
|
|
8
|
+
[v2.2 §12-6 change]
|
|
9
|
+
- Added 7 cell-level AI action handlers:
|
|
10
|
+
_add_cell / _remove_cell
|
|
11
|
+
_add_action_step / _remove_action_step
|
|
12
|
+
_add_transition_relation / _remove_transition_relation
|
|
13
|
+
_set_early_return
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import sys
|
|
17
|
+
import os
|
|
18
|
+
from typing import List, Dict, Tuple
|
|
19
|
+
|
|
20
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
|
21
|
+
|
|
22
|
+
from .logger import logger
|
|
23
|
+
from .change_actions import ChangeRequest, ChangeActionType
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ChangeApplier:
|
|
27
|
+
"""Apply change requests to a StateMachine / GlobalDefinitions."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, sm, gd):
|
|
30
|
+
logger.debug("ChangeApplier.__init__ started")
|
|
31
|
+
self.sm = sm
|
|
32
|
+
self.gd = gd
|
|
33
|
+
self.applied_changes = []
|
|
34
|
+
self.failed_changes = []
|
|
35
|
+
# Define handler dict by string key
|
|
36
|
+
self._handlers = {
|
|
37
|
+
'set_initial': self._set_initial,
|
|
38
|
+
'add_transition': self._add_transition,
|
|
39
|
+
'add_state': self._add_state,
|
|
40
|
+
'add_event': self._add_event,
|
|
41
|
+
'remove_transition': self._remove_transition,
|
|
42
|
+
'update_transition': self._update_transition,
|
|
43
|
+
'add_role_function': self._add_role_function,
|
|
44
|
+
'remove_role_function': self._remove_role_function,
|
|
45
|
+
'add_variable': self._add_variable,
|
|
46
|
+
'add_flag': self._add_flag,
|
|
47
|
+
# v2.2 §12-6 additions
|
|
48
|
+
'add_cell': self._add_cell,
|
|
49
|
+
'remove_cell': self._remove_cell,
|
|
50
|
+
'add_action_step': self._add_action_step,
|
|
51
|
+
'remove_action_step': self._remove_action_step,
|
|
52
|
+
'add_transition_relation': self._add_transition_relation,
|
|
53
|
+
'remove_transition_relation': self._remove_transition_relation,
|
|
54
|
+
'set_early_return': self._set_early_return,
|
|
55
|
+
}
|
|
56
|
+
logger.debug("ChangeApplier.__init__ completed")
|
|
57
|
+
|
|
58
|
+
def apply(self, change: ChangeRequest) -> Tuple[bool, str]:
|
|
59
|
+
"""Apply change"""
|
|
60
|
+
logger.debug(f"apply: action={change.action}")
|
|
61
|
+
action_value = (change.action.value
|
|
62
|
+
if hasattr(change.action, 'value')
|
|
63
|
+
else str(change.action))
|
|
64
|
+
logger.debug(f"action_value: {action_value}")
|
|
65
|
+
|
|
66
|
+
handler = self._handlers.get(action_value)
|
|
67
|
+
if handler is None:
|
|
68
|
+
logger.warning(f"Unsupported action: {action_value}")
|
|
69
|
+
return False, f"Unsupported action: {action_value}"
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
result = handler(change.params)
|
|
73
|
+
if result[0]:
|
|
74
|
+
self.applied_changes.append(change)
|
|
75
|
+
logger.debug(f"Change applied: {action_value}")
|
|
76
|
+
else:
|
|
77
|
+
self.failed_changes.append((change, result[1]))
|
|
78
|
+
logger.warning(f"Change failed: {result[1]}")
|
|
79
|
+
return result
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.error(f"Change threw exception: {e}", exc_info=True)
|
|
82
|
+
self.failed_changes.append((change, str(e)))
|
|
83
|
+
return False, str(e)
|
|
84
|
+
|
|
85
|
+
def apply_all(self, changes: List[ChangeRequest]) -> Dict:
|
|
86
|
+
"""Apply all changes"""
|
|
87
|
+
logger.debug(f"apply_all: {len(changes)} changes")
|
|
88
|
+
results = []
|
|
89
|
+
for change in changes:
|
|
90
|
+
success, message = self.apply(change)
|
|
91
|
+
results.append({'change': change, 'success': success,
|
|
92
|
+
'message': message})
|
|
93
|
+
summary = {
|
|
94
|
+
'total': len(changes),
|
|
95
|
+
'applied': len(self.applied_changes),
|
|
96
|
+
'failed': len(self.failed_changes),
|
|
97
|
+
'results': results,
|
|
98
|
+
}
|
|
99
|
+
logger.debug(
|
|
100
|
+
f"apply_all completed: applied={summary['applied']}, "
|
|
101
|
+
f"failed={summary['failed']}")
|
|
102
|
+
return summary
|
|
103
|
+
|
|
104
|
+
# ==================================================================
|
|
105
|
+
# Existing handlers (unchanged)
|
|
106
|
+
# ==================================================================
|
|
107
|
+
def _set_initial(self, params: Dict) -> Tuple[bool, str]:
|
|
108
|
+
state = params.get('state', '')
|
|
109
|
+
if state not in self.sm.states:
|
|
110
|
+
return False, f"State '{state}' does not exist"
|
|
111
|
+
self.sm.set_initial(state)
|
|
112
|
+
return True, f"Initial state set to '{state}'"
|
|
113
|
+
|
|
114
|
+
def _add_transition(self, params: Dict) -> Tuple[bool, str]:
|
|
115
|
+
from statable.model import Transition
|
|
116
|
+
source = params.get('source', '')
|
|
117
|
+
event = params.get('event', '')
|
|
118
|
+
target = params.get('target', '')
|
|
119
|
+
if not all([source, event, target]):
|
|
120
|
+
return False, "Transition information is incomplete"
|
|
121
|
+
transition = Transition(
|
|
122
|
+
source=source, event=event, target=target,
|
|
123
|
+
condition=params.get('condition', ''),
|
|
124
|
+
action=params.get('action_name', ''),
|
|
125
|
+
)
|
|
126
|
+
try:
|
|
127
|
+
self.sm.add_transition(transition)
|
|
128
|
+
return True, f"Transition '{source} --[{event}]--> {target}' added"
|
|
129
|
+
except ValueError as e:
|
|
130
|
+
return False, str(e)
|
|
131
|
+
|
|
132
|
+
def _add_state(self, params: Dict) -> Tuple[bool, str]:
|
|
133
|
+
from statable.model import State, StateType
|
|
134
|
+
name = params.get('name', '')
|
|
135
|
+
if not name:
|
|
136
|
+
return False, "State name is not specified"
|
|
137
|
+
if name in self.sm.states:
|
|
138
|
+
return False, f"State '{name}' already exists"
|
|
139
|
+
type_name = params.get('type', 'NORMAL')
|
|
140
|
+
state_type = getattr(StateType, type_name, StateType.NORMAL)
|
|
141
|
+
if not hasattr(StateType, type_name):
|
|
142
|
+
logger.warning(
|
|
143
|
+
f"_add_state: unknown type '{type_name}' -> NORMAL fallback")
|
|
144
|
+
parent = params.get('parent', None) or None
|
|
145
|
+
self.sm.add_state(State(
|
|
146
|
+
name=name, type=state_type, parent=parent,
|
|
147
|
+
description=params.get('description', ''),
|
|
148
|
+
))
|
|
149
|
+
return True, f"State '{name}' added (type={state_type.name})"
|
|
150
|
+
|
|
151
|
+
def _add_event(self, params: Dict) -> Tuple[bool, str]:
|
|
152
|
+
from statable.model import Event, EventKind
|
|
153
|
+
name = params.get('name', '')
|
|
154
|
+
if not name:
|
|
155
|
+
return False, "Event name is not specified"
|
|
156
|
+
if name in self.sm.events:
|
|
157
|
+
return False, f"Event '{name}' already exists"
|
|
158
|
+
kind = getattr(EventKind, params.get('kind', 'SIGNAL'),
|
|
159
|
+
EventKind.SIGNAL)
|
|
160
|
+
self.sm.add_event(Event(name=name, kind=kind,
|
|
161
|
+
description=params.get('description', '')))
|
|
162
|
+
return True, f"Event '{name}' added"
|
|
163
|
+
|
|
164
|
+
def _remove_transition(self, params: Dict) -> Tuple[bool, str]:
|
|
165
|
+
source = params.get('source', '')
|
|
166
|
+
event = params.get('event', '')
|
|
167
|
+
target = params.get('target', '')
|
|
168
|
+
for t in self.sm.transitions:
|
|
169
|
+
if t.source == source and t.event == event and t.target == target:
|
|
170
|
+
self.sm.remove_transition(t)
|
|
171
|
+
return True, f"Transition '{source} --[{event}]--> {target}' deleted"
|
|
172
|
+
return False, "No matching transition found"
|
|
173
|
+
|
|
174
|
+
def _update_transition(self, params: Dict) -> Tuple[bool, str]:
|
|
175
|
+
source = params.get('source', '')
|
|
176
|
+
event = params.get('event', '')
|
|
177
|
+
for t in self.sm.transitions:
|
|
178
|
+
if t.source == source and t.event == event:
|
|
179
|
+
if 'new_target' in params:
|
|
180
|
+
t.target = params['new_target']
|
|
181
|
+
if 'new_condition' in params:
|
|
182
|
+
t.condition = params['new_condition']
|
|
183
|
+
if 'new_action' in params:
|
|
184
|
+
t.action = params['new_action']
|
|
185
|
+
return True, f"Transition '{source} --[{event}]-->' updated"
|
|
186
|
+
return False, "No matching transition found"
|
|
187
|
+
|
|
188
|
+
def _add_role_function(self, params: Dict) -> Tuple[bool, str]:
|
|
189
|
+
from statable.model import RoleFunction
|
|
190
|
+
name = params.get('name', '')
|
|
191
|
+
if not name:
|
|
192
|
+
return False, "Function name is not specified"
|
|
193
|
+
if name in self.sm.role_functions:
|
|
194
|
+
return False, f"Function '{name}' already exists"
|
|
195
|
+
rf = RoleFunction(
|
|
196
|
+
name=name,
|
|
197
|
+
namespace=params.get('namespace', ''),
|
|
198
|
+
return_type=params.get('return_type', 'void'),
|
|
199
|
+
description=params.get('description', ''),
|
|
200
|
+
)
|
|
201
|
+
self.sm.add_role_function(rf)
|
|
202
|
+
return True, f"Role function '{name}' added"
|
|
203
|
+
|
|
204
|
+
def _remove_role_function(self, params: Dict) -> Tuple[bool, str]:
|
|
205
|
+
"""
|
|
206
|
+
Remove a role function.
|
|
207
|
+
|
|
208
|
+
Added in v1.6. Corresponds to ChangeActionType.REMOVE_ROLE_FUNCTION.
|
|
209
|
+
params:
|
|
210
|
+
name: 'HandleErr' or 'Middleware.HandleErr' (either is acceptable)
|
|
211
|
+
"""
|
|
212
|
+
name = params.get('name', '')
|
|
213
|
+
if not name:
|
|
214
|
+
return False, "Function name is not specified"
|
|
215
|
+
# Exact match
|
|
216
|
+
if name in self.sm.role_functions:
|
|
217
|
+
self.sm.remove_role_function(name)
|
|
218
|
+
return True, f"Role function '{name}' deleted"
|
|
219
|
+
|
|
220
|
+
# Match by qualified_name
|
|
221
|
+
for key, rf in list(self.sm.role_functions.items()):
|
|
222
|
+
qn = getattr(rf, 'qualified_name', None) or key
|
|
223
|
+
if qn == name:
|
|
224
|
+
self.sm.remove_role_function(key)
|
|
225
|
+
return True, f"Role function '{name}' deleted"
|
|
226
|
+
|
|
227
|
+
# Match by bare name
|
|
228
|
+
for key, rf in list(self.sm.role_functions.items()):
|
|
229
|
+
if getattr(rf, 'name', '') == name:
|
|
230
|
+
self.sm.remove_role_function(key)
|
|
231
|
+
return True, f"Role function '{name}' deleted"
|
|
232
|
+
return False, f"Function '{name}' not found"
|
|
233
|
+
|
|
234
|
+
def _add_variable(self, params: Dict) -> Tuple[bool, str]:
|
|
235
|
+
from statable.global_defs import SystemVariable
|
|
236
|
+
name = params.get('name', '')
|
|
237
|
+
if not name:
|
|
238
|
+
return False, "Variable 'name' is required"
|
|
239
|
+
# [C-28 fix] Duplicate check
|
|
240
|
+
if any(getattr(v, 'name', '') == name for v in self.gd.variables):
|
|
241
|
+
return False, f"Variable '{name}' already exists"
|
|
242
|
+
var = SystemVariable(
|
|
243
|
+
name=name, type=params.get('type', 'uint8'),
|
|
244
|
+
group=params.get('group', ''),
|
|
245
|
+
description=params.get('description', ''))
|
|
246
|
+
self.gd.variables.append(var)
|
|
247
|
+
return True, f"Variable '{name}' added"
|
|
248
|
+
|
|
249
|
+
def _add_flag(self, params: Dict) -> Tuple[bool, str]:
|
|
250
|
+
from statable.global_defs import EventFlag
|
|
251
|
+
name = params.get('name', '')
|
|
252
|
+
if not name:
|
|
253
|
+
return False, "Flag 'name' is required"
|
|
254
|
+
# [C-28 fix] Duplicate check
|
|
255
|
+
if any(getattr(f, 'name', '') == name for f in self.gd.flags):
|
|
256
|
+
return False, f"Flag '{name}' already exists"
|
|
257
|
+
flag = EventFlag(
|
|
258
|
+
name=name, min_value=params.get('min_value', 0),
|
|
259
|
+
max_value=params.get('max_value', 1),
|
|
260
|
+
group=params.get('group', ''))
|
|
261
|
+
self.gd.flags.append(flag)
|
|
262
|
+
return True, f"Flag '{name}' added"
|
|
263
|
+
|
|
264
|
+
# ==================================================================
|
|
265
|
+
# v2.2 §12-6: Cell-level handlers
|
|
266
|
+
# ==================================================================
|
|
267
|
+
def _add_cell(self, params: Dict) -> Tuple[bool, str]:
|
|
268
|
+
"""Ensure a cell exists for (source, event). Idempotent."""
|
|
269
|
+
source = params.get('source', '')
|
|
270
|
+
event = params.get('event', '')
|
|
271
|
+
if source not in self.sm.states:
|
|
272
|
+
return False, f"Source state '{source}' does not exist"
|
|
273
|
+
# event may be '' for completion transitions
|
|
274
|
+
if event and event not in self.sm.events:
|
|
275
|
+
return False, f"Event '{event}' does not exist"
|
|
276
|
+
# Ensure cell_actions / cell_relations entries exist (may be empty lists)
|
|
277
|
+
self.sm.cell_actions.setdefault((source, event), [])
|
|
278
|
+
self.sm.cell_relations.setdefault((source, event), [])
|
|
279
|
+
return True, f"Cell ({source}, {event or 'Completion'}) ensured"
|
|
280
|
+
|
|
281
|
+
def _remove_cell(self, params: Dict) -> Tuple[bool, str]:
|
|
282
|
+
"""Remove all cell-level metadata (actions + relations)."""
|
|
283
|
+
source = params.get('source', '')
|
|
284
|
+
event = params.get('event', '')
|
|
285
|
+
if source not in self.sm.states:
|
|
286
|
+
return False, f"Source state '{source}' does not exist"
|
|
287
|
+
self.sm.remove_cell_metadata(source, event)
|
|
288
|
+
return True, f"Cell ({source}, {event or 'Completion'}) removed"
|
|
289
|
+
|
|
290
|
+
def _add_action_step(self, params: Dict) -> Tuple[bool, str]:
|
|
291
|
+
from statable.model import ActionStep
|
|
292
|
+
source = params.get('source', '')
|
|
293
|
+
event = params.get('event', '')
|
|
294
|
+
role_function = params.get('role_function', '')
|
|
295
|
+
trigger = params.get('trigger', 'before_transitions')
|
|
296
|
+
|
|
297
|
+
if not role_function:
|
|
298
|
+
return False, "role_function is required"
|
|
299
|
+
if source not in self.sm.states:
|
|
300
|
+
return False, f"Source state '{source}' does not exist"
|
|
301
|
+
if event and event not in self.sm.events:
|
|
302
|
+
return False, f"Event '{event}' does not exist"
|
|
303
|
+
if trigger not in ('before_transitions', 'after_transitions'):
|
|
304
|
+
return False, f"Invalid trigger: {trigger}"
|
|
305
|
+
|
|
306
|
+
existing = list(self.sm.get_actions_for_cell(source, event))
|
|
307
|
+
existing.append(ActionStep(
|
|
308
|
+
role_function=role_function, trigger=trigger,
|
|
309
|
+
title=role_function,
|
|
310
|
+
))
|
|
311
|
+
self.sm.set_actions_for_cell(source, event, existing)
|
|
312
|
+
return True, (f"Action step '{role_function}' ({trigger}) "
|
|
313
|
+
f"added to ({source}, {event or 'Completion'})")
|
|
314
|
+
|
|
315
|
+
def _remove_action_step(self, params: Dict) -> Tuple[bool, str]:
|
|
316
|
+
source = params.get('source', '')
|
|
317
|
+
event = params.get('event', '')
|
|
318
|
+
role_function = params.get('role_function', '')
|
|
319
|
+
|
|
320
|
+
if not role_function:
|
|
321
|
+
return False, "role_function is required"
|
|
322
|
+
if source not in self.sm.states:
|
|
323
|
+
return False, f"Source state '{source}' does not exist"
|
|
324
|
+
|
|
325
|
+
existing = list(self.sm.get_actions_for_cell(source, event))
|
|
326
|
+
filtered = [a for a in existing if a.role_function != role_function]
|
|
327
|
+
if len(filtered) == len(existing):
|
|
328
|
+
return False, f"Action step '{role_function}' not found"
|
|
329
|
+
self.sm.set_actions_for_cell(source, event, filtered)
|
|
330
|
+
return True, (f"Action step '{role_function}' removed from "
|
|
331
|
+
f"({source}, {event or 'Completion'})")
|
|
332
|
+
|
|
333
|
+
def _add_transition_relation(self, params: Dict) -> Tuple[bool, str]:
|
|
334
|
+
from statable.model import TransitionRelation
|
|
335
|
+
source = params.get('source', '')
|
|
336
|
+
event = params.get('event', '')
|
|
337
|
+
kind = params.get('kind', '')
|
|
338
|
+
members = params.get('members', [])
|
|
339
|
+
shared_condition = params.get('shared_condition', '')
|
|
340
|
+
|
|
341
|
+
if source not in self.sm.states:
|
|
342
|
+
return False, f"Source state '{source}' does not exist"
|
|
343
|
+
if event and event not in self.sm.events:
|
|
344
|
+
return False, f"Event '{event}' does not exist"
|
|
345
|
+
if kind not in ('sequential', 'exclusive', 'group'):
|
|
346
|
+
return False, f"Invalid kind: {kind}"
|
|
347
|
+
if not members:
|
|
348
|
+
return False, "members is required"
|
|
349
|
+
if kind == 'group' and not shared_condition:
|
|
350
|
+
return False, "shared_condition is required for kind == 'group'"
|
|
351
|
+
|
|
352
|
+
# Validate that labels exist in this cell
|
|
353
|
+
existing_labels = {getattr(t, 'label', '') or ''
|
|
354
|
+
for t in self.sm.get_transitions_for_cell(source, event)}
|
|
355
|
+
for m in members:
|
|
356
|
+
if m not in existing_labels:
|
|
357
|
+
return False, f"Label '{m}' does not exist in this cell"
|
|
358
|
+
|
|
359
|
+
existing = list(self.sm.get_relations_for_cell(source, event))
|
|
360
|
+
existing.append(TransitionRelation(
|
|
361
|
+
kind=kind, members=list(members),
|
|
362
|
+
shared_condition=shared_condition,
|
|
363
|
+
))
|
|
364
|
+
self.sm.set_relations_for_cell(source, event, existing)
|
|
365
|
+
return True, (f"Relation ({kind}) added to "
|
|
366
|
+
f"({source}, {event or 'Completion'})")
|
|
367
|
+
|
|
368
|
+
def _remove_transition_relation(self, params: Dict) -> Tuple[bool, str]:
|
|
369
|
+
source = params.get('source', '')
|
|
370
|
+
event = params.get('event', '')
|
|
371
|
+
kind = params.get('kind', '')
|
|
372
|
+
shared_condition = params.get('shared_condition', None)
|
|
373
|
+
|
|
374
|
+
if source not in self.sm.states:
|
|
375
|
+
return False, f"Source state '{source}' does not exist"
|
|
376
|
+
|
|
377
|
+
existing = list(self.sm.get_relations_for_cell(source, event))
|
|
378
|
+
filtered = []
|
|
379
|
+
removed = False
|
|
380
|
+
for r in existing:
|
|
381
|
+
if removed:
|
|
382
|
+
filtered.append(r)
|
|
383
|
+
continue
|
|
384
|
+
kind_match = (r.kind == kind)
|
|
385
|
+
cond_match = (shared_condition is None
|
|
386
|
+
or r.shared_condition == shared_condition)
|
|
387
|
+
if kind_match and cond_match:
|
|
388
|
+
removed = True
|
|
389
|
+
continue
|
|
390
|
+
filtered.append(r)
|
|
391
|
+
|
|
392
|
+
if not removed:
|
|
393
|
+
return False, f"No matching relation found (kind={kind})"
|
|
394
|
+
self.sm.set_relations_for_cell(source, event, filtered)
|
|
395
|
+
return True, (f"Relation ({kind}) removed from "
|
|
396
|
+
f"({source}, {event or 'Completion'})")
|
|
397
|
+
|
|
398
|
+
def _set_early_return(self, params: Dict) -> Tuple[bool, str]:
|
|
399
|
+
source = params.get('source', '')
|
|
400
|
+
event = params.get('event', '')
|
|
401
|
+
label = params.get('label', '')
|
|
402
|
+
early_return = params.get('early_return', None)
|
|
403
|
+
|
|
404
|
+
if source not in self.sm.states:
|
|
405
|
+
return False, f"Source state '{source}' does not exist"
|
|
406
|
+
if not label:
|
|
407
|
+
return False, "label is required"
|
|
408
|
+
if early_return is None:
|
|
409
|
+
return False, "early_return is required"
|
|
410
|
+
|
|
411
|
+
for t in self.sm.get_transitions_for_cell(source, event):
|
|
412
|
+
if getattr(t, 'label', '') == label:
|
|
413
|
+
t.early_return = bool(early_return)
|
|
414
|
+
return True, (f"Transition '{label}' early_return set to "
|
|
415
|
+
f"{t.early_return}")
|
|
416
|
+
return False, f"Transition label '{label}' not found in ({source}, {event or 'Completion'})"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# codegen/validate/clipboard_manager.py
|
|
2
|
+
"""Clipboard management (PySide6 only)."""
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
sys.path.append(
|
|
8
|
+
os.path.dirname(
|
|
9
|
+
os.path.dirname(
|
|
10
|
+
os.path.dirname(os.path.abspath(__file__)))))
|
|
11
|
+
|
|
12
|
+
from .logger import logger
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ClipboardManager:
|
|
16
|
+
"""Clipboard management class."""
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def copy_to_clipboard(text: str) -> bool:
|
|
20
|
+
logger.debug(f"copy_to_clipboard: {len(text)} chars")
|
|
21
|
+
try:
|
|
22
|
+
from PySide6.QtWidgets import QApplication
|
|
23
|
+
except ImportError:
|
|
24
|
+
logger.error("PySide6 not available")
|
|
25
|
+
return False
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
QApplication.clipboard().setText(text)
|
|
29
|
+
return True
|
|
30
|
+
except Exception as e:
|
|
31
|
+
logger.error(f"Clipboard set failed: {e}")
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def get_from_clipboard() -> str:
|
|
36
|
+
logger.debug("get_from_clipboard started")
|
|
37
|
+
try:
|
|
38
|
+
from PySide6.QtWidgets import QApplication
|
|
39
|
+
except ImportError:
|
|
40
|
+
logger.error("PySide6 not available")
|
|
41
|
+
return ""
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
return QApplication.clipboard().text()
|
|
45
|
+
except Exception as e:
|
|
46
|
+
logger.error(f"Clipboard get failed: {e}")
|
|
47
|
+
return ""
|