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,150 @@
|
|
|
1
|
+
# statable/shared/role_function_library.py
|
|
2
|
+
"""
|
|
3
|
+
Shared role function library
|
|
4
|
+
|
|
5
|
+
Version History
|
|
6
|
+
---------------
|
|
7
|
+
v1.0 - Initial shared library.
|
|
8
|
+
v1.5 - Added reserved signature fields
|
|
9
|
+
(return_type / arg1_type / arg1_name / arg2_type / arg2_name).
|
|
10
|
+
These are not used by the code generator (its C signature is
|
|
11
|
+
fixed), and they are not exposed in the GUI. They are kept
|
|
12
|
+
here only so that XML I/O round-trips old project files
|
|
13
|
+
(tests/data/v22_features_test3.xml) without canonical
|
|
14
|
+
differences. See xml_io.py v3.8.2.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from typing import List, Dict, Optional
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class RoleFunction:
|
|
23
|
+
"""Shared role function definition.
|
|
24
|
+
|
|
25
|
+
namespace: layer name / feature group name (e.g., "Driver")
|
|
26
|
+
- Referenceable as `Driver.Init`
|
|
27
|
+
- Empty string means no layer
|
|
28
|
+
|
|
29
|
+
[v1.5]
|
|
30
|
+
Reserved signature fields are present but not used:
|
|
31
|
+
* return_type / arg1_type / arg1_name / arg2_type / arg2_name
|
|
32
|
+
Their only purpose is XML round-trip preservation.
|
|
33
|
+
"""
|
|
34
|
+
name: str
|
|
35
|
+
namespace: str = ""
|
|
36
|
+
description: str = ""
|
|
37
|
+
title: str = ""
|
|
38
|
+
# [v1.5] Reserved signature fields (not exposed in GUI,
|
|
39
|
+
# not consumed by codegen; preserved for XML round-trip).
|
|
40
|
+
return_type: str = ""
|
|
41
|
+
arg1_type: str = ""
|
|
42
|
+
arg1_name: str = ""
|
|
43
|
+
arg2_type: str = ""
|
|
44
|
+
arg2_name: str = ""
|
|
45
|
+
# [v1.4] GUI symbol tracking
|
|
46
|
+
used_global_vars: List[str] = field(default_factory=list)
|
|
47
|
+
used_events: List[str] = field(default_factory=list)
|
|
48
|
+
used_literals: List[str] = field(default_factory=list)
|
|
49
|
+
|
|
50
|
+
def __post_init__(self):
|
|
51
|
+
if not self.title:
|
|
52
|
+
self.title = self.qualified_name
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def qualified_name(self) -> str:
|
|
56
|
+
"""GUI display name: 'Driver.Init' or 'Init'"""
|
|
57
|
+
if self.namespace:
|
|
58
|
+
return f"{self.namespace}.{self.name}"
|
|
59
|
+
return self.name
|
|
60
|
+
|
|
61
|
+
def to_dict(self) -> dict:
|
|
62
|
+
return {
|
|
63
|
+
'name': self.name,
|
|
64
|
+
'namespace': self.namespace,
|
|
65
|
+
'description': self.description,
|
|
66
|
+
'title': self.title,
|
|
67
|
+
# [v1.5] Reserved signature fields
|
|
68
|
+
'return_type': self.return_type,
|
|
69
|
+
'arg1_type': self.arg1_type,
|
|
70
|
+
'arg1_name': self.arg1_name,
|
|
71
|
+
'arg2_type': self.arg2_type,
|
|
72
|
+
'arg2_name': self.arg2_name,
|
|
73
|
+
# [v1.4] symbol references
|
|
74
|
+
'used_global_vars': list(self.used_global_vars),
|
|
75
|
+
'used_events': list(self.used_events),
|
|
76
|
+
'used_literals': list(self.used_literals),
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def from_dict(cls, data: dict) -> 'RoleFunction':
|
|
81
|
+
return cls(
|
|
82
|
+
name=data.get('name', ''),
|
|
83
|
+
namespace=data.get('namespace', ''),
|
|
84
|
+
description=data.get('description', ''),
|
|
85
|
+
title=data.get('title', ''),
|
|
86
|
+
return_type=data.get('return_type', ''),
|
|
87
|
+
arg1_type=data.get('arg1_type', ''),
|
|
88
|
+
arg1_name=data.get('arg1_name', ''),
|
|
89
|
+
arg2_type=data.get('arg2_type', ''),
|
|
90
|
+
arg2_name=data.get('arg2_name', ''),
|
|
91
|
+
used_global_vars=list(data.get('used_global_vars', [])),
|
|
92
|
+
used_events=list(data.get('used_events', [])),
|
|
93
|
+
used_literals=list(data.get('used_literals', [])),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class RoleFunctionLibrary:
|
|
98
|
+
"""Role function library shared across the project"""
|
|
99
|
+
|
|
100
|
+
def __init__(self):
|
|
101
|
+
self.role_functions: Dict[str, RoleFunction] = {}
|
|
102
|
+
|
|
103
|
+
def _key(self, rf: RoleFunction) -> str:
|
|
104
|
+
"""Unique key in the library: 'Driver.Init'"""
|
|
105
|
+
return rf.qualified_name
|
|
106
|
+
|
|
107
|
+
def add(self, rf: RoleFunction):
|
|
108
|
+
key = self._key(rf)
|
|
109
|
+
if key in self.role_functions:
|
|
110
|
+
raise ValueError(f"Role function '{key}' already exists")
|
|
111
|
+
self.role_functions[key] = rf
|
|
112
|
+
|
|
113
|
+
def remove(self, name: str):
|
|
114
|
+
"""name can be either qualified_name or a bare name"""
|
|
115
|
+
if name in self.role_functions:
|
|
116
|
+
del self.role_functions[name]
|
|
117
|
+
return
|
|
118
|
+
# Search by bare name
|
|
119
|
+
for key, rf in list(self.role_functions.items()):
|
|
120
|
+
if rf.name == name:
|
|
121
|
+
del self.role_functions[key]
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
def get(self, name: str) -> Optional[RoleFunction]:
|
|
125
|
+
"""name can be either qualified_name or a bare name"""
|
|
126
|
+
if name in self.role_functions:
|
|
127
|
+
return self.role_functions[name]
|
|
128
|
+
for rf in self.role_functions.values():
|
|
129
|
+
if rf.name == name:
|
|
130
|
+
return rf
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
def list_all(self) -> List[RoleFunction]:
|
|
134
|
+
return list(self.role_functions.values())
|
|
135
|
+
|
|
136
|
+
def to_dict(self) -> dict:
|
|
137
|
+
return {
|
|
138
|
+
'role_functions': [rf.to_dict() for rf in self.role_functions.values()]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@classmethod
|
|
142
|
+
def from_dict(cls, data: dict) -> 'RoleFunctionLibrary':
|
|
143
|
+
lib = cls()
|
|
144
|
+
for item in data.get('role_functions', []):
|
|
145
|
+
rf = RoleFunction.from_dict(item)
|
|
146
|
+
try:
|
|
147
|
+
lib.add(rf)
|
|
148
|
+
except ValueError:
|
|
149
|
+
pass # Duplicates ignored
|
|
150
|
+
return lib
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# statable/state_machine.py
|
|
2
|
+
from typing import Dict, List, Optional, Tuple
|
|
3
|
+
|
|
4
|
+
from .model import (
|
|
5
|
+
State, Event, Transition, RoleFunction,
|
|
6
|
+
ActionStep, TransitionRelation,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class StateMachine:
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self.states: Dict[str, State] = {}
|
|
13
|
+
self.events: Dict[str, Event] = {}
|
|
14
|
+
self.transitions: List[Transition] = []
|
|
15
|
+
self.role_functions: Dict[str, RoleFunction] = {}
|
|
16
|
+
self.initial_state: Optional[str] = None
|
|
17
|
+
|
|
18
|
+
# Layer settings
|
|
19
|
+
self.layer_priority: int = 5
|
|
20
|
+
self.layer_description: str = ""
|
|
21
|
+
self.layer_name: str = ""
|
|
22
|
+
|
|
23
|
+
# === v2.2: per-cell metadata ===
|
|
24
|
+
# Key: (source_state_name, event_name)
|
|
25
|
+
# Note: event_name may be "" for completion transitions.
|
|
26
|
+
self.cell_actions: Dict[Tuple[str, str], List[ActionStep]] = {}
|
|
27
|
+
self.cell_relations: Dict[Tuple[str, str], List[TransitionRelation]] = {}
|
|
28
|
+
|
|
29
|
+
# ------------------------------------------------------------------
|
|
30
|
+
# Existing API (unchanged)
|
|
31
|
+
# ------------------------------------------------------------------
|
|
32
|
+
def add_state(self, state: State):
|
|
33
|
+
if state.name in self.states:
|
|
34
|
+
raise ValueError(f"State '{state.name}' already exists")
|
|
35
|
+
self.states[state.name] = state
|
|
36
|
+
|
|
37
|
+
def add_event(self, event: Event):
|
|
38
|
+
if event.name in self.events:
|
|
39
|
+
raise ValueError(f"Event '{event.name}' already exists")
|
|
40
|
+
self.events[event.name] = event
|
|
41
|
+
|
|
42
|
+
def remove_event(self, name: str):
|
|
43
|
+
if name in self.events:
|
|
44
|
+
del self.events[name]
|
|
45
|
+
self.transitions = [t for t in self.transitions if t.event != name]
|
|
46
|
+
# v2.2: drop cell metadata that referenced this event
|
|
47
|
+
for key in [k for k in self.cell_actions if k[1] == name]:
|
|
48
|
+
del self.cell_actions[key]
|
|
49
|
+
for key in [k for k in self.cell_relations if k[1] == name]:
|
|
50
|
+
del self.cell_relations[key]
|
|
51
|
+
|
|
52
|
+
def add_transition(self, trans: Transition):
|
|
53
|
+
if trans.source not in self.states:
|
|
54
|
+
raise ValueError(f"Source state '{trans.source}' not defined")
|
|
55
|
+
if trans.target and trans.target not in self.states:
|
|
56
|
+
raise ValueError(f"Target state '{trans.target}' not defined")
|
|
57
|
+
if trans.event and trans.event not in self.events:
|
|
58
|
+
raise ValueError(f"Event '{trans.event}' not defined")
|
|
59
|
+
self.transitions.append(trans)
|
|
60
|
+
|
|
61
|
+
def remove_transition(self, trans: Transition):
|
|
62
|
+
if trans in self.transitions:
|
|
63
|
+
self.transitions.remove(trans)
|
|
64
|
+
|
|
65
|
+
def set_initial(self, state_name: str):
|
|
66
|
+
if state_name not in self.states:
|
|
67
|
+
raise ValueError(f"State '{state_name}' not defined")
|
|
68
|
+
self.initial_state = state_name
|
|
69
|
+
|
|
70
|
+
def add_role_function(self, rf: RoleFunction):
|
|
71
|
+
if rf.name in self.role_functions:
|
|
72
|
+
raise ValueError(f"Role function '{rf.name}' already exists")
|
|
73
|
+
self.role_functions[rf.name] = rf
|
|
74
|
+
|
|
75
|
+
def remove_role_function(self, name: str):
|
|
76
|
+
if name in self.role_functions:
|
|
77
|
+
del self.role_functions[name]
|
|
78
|
+
|
|
79
|
+
def get_transitions_for_cell(self, source: str, event: str) -> List[Transition]:
|
|
80
|
+
return [t for t in self.transitions
|
|
81
|
+
if t.source == source and t.event == event]
|
|
82
|
+
|
|
83
|
+
def get_transitions_for_event(self, event: str) -> List[Transition]:
|
|
84
|
+
return [t for t in self.transitions if t.event == event]
|
|
85
|
+
|
|
86
|
+
# ------------------------------------------------------------------
|
|
87
|
+
# v2.2: cell action / relation accessors
|
|
88
|
+
# ------------------------------------------------------------------
|
|
89
|
+
def get_actions_for_cell(self, source: str, event: str) -> List[ActionStep]:
|
|
90
|
+
return self.cell_actions.get((source, event), [])
|
|
91
|
+
|
|
92
|
+
def set_actions_for_cell(self, source: str, event: str,
|
|
93
|
+
actions: List[ActionStep]) -> None:
|
|
94
|
+
key = (source, event)
|
|
95
|
+
if actions:
|
|
96
|
+
self.cell_actions[key] = list(actions)
|
|
97
|
+
else:
|
|
98
|
+
self.cell_actions.pop(key, None)
|
|
99
|
+
|
|
100
|
+
def get_relations_for_cell(self, source: str, event: str
|
|
101
|
+
) -> List[TransitionRelation]:
|
|
102
|
+
return self.cell_relations.get((source, event), [])
|
|
103
|
+
|
|
104
|
+
def set_relations_for_cell(self, source: str, event: str,
|
|
105
|
+
relations: List[TransitionRelation]) -> None:
|
|
106
|
+
key = (source, event)
|
|
107
|
+
if relations:
|
|
108
|
+
self.cell_relations[key] = list(relations)
|
|
109
|
+
else:
|
|
110
|
+
self.cell_relations.pop(key, None)
|
|
111
|
+
|
|
112
|
+
def get_cell_keys(self) -> List[Tuple[str, str]]:
|
|
113
|
+
"""All (source, event) keys that have actions or relations."""
|
|
114
|
+
keys = set(self.cell_actions.keys()) | set(self.cell_relations.keys())
|
|
115
|
+
return sorted(keys)
|
|
116
|
+
|
|
117
|
+
def remove_cell_metadata(self, source: str, event: str) -> None:
|
|
118
|
+
"""Remove all cell-level actions / relations for the given cell."""
|
|
119
|
+
key = (source, event)
|
|
120
|
+
self.cell_actions.pop(key, None)
|
|
121
|
+
self.cell_relations.pop(key, None)
|