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,132 @@
1
+ # codegen/validate/items/variable_validator.py
2
+ """\nVariable validation\n"""
3
+
4
+ import sys
5
+ import os
6
+ from typing import List
7
+
8
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
9
+
10
+ from ..logger import logger
11
+ from ..models import ValidationIssue, ValidationSeverity, ValidationContext
12
+ from ..data.validation_rules import VALIDATION_RULES
13
+ from .base_validator import BaseValidator
14
+
15
+
16
+ class VariableValidator(BaseValidator):
17
+ """Variable validation class"""
18
+
19
+ category = "variable"
20
+
21
+ def __init__(self):
22
+ logger.debug("VariableValidator.__init__ started")
23
+ self.rules_data = VALIDATION_RULES.get(self.category, {})
24
+ self.rules = {
25
+ 'VAR_DUPLICATE_NAME': self._check_duplicate_names,
26
+ 'VAR_INVALID_TYPE': self._check_invalid_type,
27
+ 'VAR_INVALID_ARRAY_SIZE': self._check_invalid_array_size,
28
+ }
29
+ logger.debug(f"VariableValidator.__init__ completed: {len(self.rules)} rules")
30
+
31
+ def validate(self, context: ValidationContext) -> List[ValidationIssue]:
32
+ logger.debug(f"VariableValidator.validate started")
33
+ issues = []
34
+ for code, rule_func in self.rules.items():
35
+ try:
36
+ result = rule_func(context)
37
+ if result:
38
+ issues.extend(result)
39
+ except Exception as e:
40
+ logger.error(f"Rule '{code}' failed: {e}", exc_info=True)
41
+ return issues
42
+
43
+ def _create_issue(self, code: str, **kwargs) -> ValidationIssue:
44
+ rule = self.rules_data.get(code, {})
45
+ message = rule.get('message', '').format(**kwargs)
46
+ severity = ValidationSeverity.from_string(rule.get('severity', 'info'))
47
+ return ValidationIssue(
48
+ category=self.category,
49
+ code=code,
50
+ message=message,
51
+ severity=severity,
52
+ suggestion=rule.get('suggestion', ''),
53
+ target=kwargs.get('name', ''),
54
+ )
55
+
56
+ def _check_duplicate_names(self, context):
57
+ issues = []
58
+ seen = set()
59
+ for var in context.variables:
60
+ name = getattr(var, 'name', '')
61
+ if name in seen:
62
+ issues.append(self._create_issue('VAR_DUPLICATE_NAME', name=name))
63
+ else:
64
+ seen.add(name)
65
+ return issues
66
+
67
+ def _check_invalid_type(self, context):
68
+ """[v2.7.2 fix] Accept stdint.h types and strip qualifiers.
69
+
70
+ Previous behavior:
71
+ - 'uint32_t' / 'uint8_t' were flagged (only legacy aliases
72
+ 'uint32' / 'uint8' were in the set)
73
+ - 'volatile uint32_t' was flagged
74
+ """
75
+ issues = []
76
+ basic_types = {
77
+ # Plain C
78
+ 'int', 'short', 'long', 'unsigned', 'signed',
79
+ 'float', 'double', 'bool', 'char', 'string', 'void',
80
+ # stdint.h (C99)
81
+ 'int8_t', 'int16_t', 'int32_t', 'int64_t',
82
+ 'uint8_t', 'uint16_t', 'uint32_t', 'uint64_t',
83
+ 'intptr_t', 'uintptr_t', 'size_t',
84
+ # Legacy aliases (without _t)
85
+ 'int8', 'int16', 'int32', 'int64',
86
+ 'uint', 'uint8', 'uint16', 'uint32', 'uint64',
87
+ }
88
+ qualifiers = {
89
+ 'volatile', 'const', 'static', 'register',
90
+ 'extern', 'inline', 'unsigned', 'signed',
91
+ 'long', 'short',
92
+ }
93
+
94
+ def normalize(t: str) -> str:
95
+ parts = (t or '').strip().split()
96
+ base = [p for p in parts if p not in qualifiers]
97
+ return ' '.join(base) if base else (t or '').strip()
98
+
99
+ def strip_extras(t: str) -> str:
100
+ t = t.rstrip('*').strip()
101
+ t = t.split('[')[0].strip()
102
+ return t
103
+
104
+ custom_type_names = {
105
+ getattr(ct, 'name', '') for ct in context.custom_types
106
+ }
107
+ for var in context.variables:
108
+ var_type = getattr(var, 'type', '') or ''
109
+ name = getattr(var, 'name', '')
110
+ if not var_type:
111
+ continue
112
+ normalized = normalize(var_type)
113
+ candidates = {
114
+ var_type, normalized,
115
+ strip_extras(var_type), strip_extras(normalized),
116
+ }
117
+ if candidates & basic_types:
118
+ continue
119
+ if candidates & custom_type_names:
120
+ continue
121
+ issues.append(self._create_issue(
122
+ 'VAR_INVALID_TYPE', name=name, type=var_type))
123
+ return issues
124
+
125
+ def _check_invalid_array_size(self, context):
126
+ issues = []
127
+ for var in context.variables:
128
+ array_size = getattr(var, 'array_size', 0)
129
+ name = getattr(var, 'name', '')
130
+ if array_size < 0:
131
+ issues.append(self._create_issue('VAR_INVALID_ARRAY_SIZE', name=name))
132
+ return issues
@@ -0,0 +1,52 @@
1
+ # codegen/validate/logger.py
2
+ """\nLogger setup for validation module\n"""
3
+
4
+ import logging
5
+ import os
6
+ from datetime import datetime
7
+
8
+
9
+ def setup_logger(name: str = "validate", log_dir: str = None) -> logging.Logger:
10
+ """Setup logger"""
11
+ logger = logging.getLogger(name)
12
+
13
+ if logger.handlers:
14
+ return logger
15
+
16
+ logger.setLevel(logging.DEBUG)
17
+
18
+ # Console handler
19
+ console_handler = logging.StreamHandler()
20
+ console_handler.setLevel(logging.DEBUG)
21
+ console_format = logging.Formatter(
22
+ '%(asctime)s - %(name)s - %(levelname)s - [%(funcName)s:%(lineno)d] - %(message)s',
23
+ datefmt='%H:%M:%S'
24
+ )
25
+ console_handler.setFormatter(console_format)
26
+ logger.addHandler(console_handler)
27
+
28
+ # File handler
29
+ if log_dir is None:
30
+ log_dir = os.path.join(
31
+ os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
32
+ 'logs'
33
+ )
34
+
35
+ os.makedirs(log_dir, exist_ok=True)
36
+
37
+ log_file = os.path.join(log_dir, f'validate_{datetime.now().strftime("%Y%m%d")}.log')
38
+ file_handler = logging.FileHandler(log_file, encoding='utf-8')
39
+ file_handler.setLevel(logging.DEBUG)
40
+ file_format = logging.Formatter(
41
+ '%(asctime)s - %(name)s - %(levelname)s - [%(funcName)s:%(lineno)d] - %(message)s'
42
+ )
43
+ file_handler.setFormatter(file_format)
44
+ logger.addHandler(file_handler)
45
+
46
+ logger.debug(f"Logger setup completed: {name} -> {log_file}")
47
+ return logger
48
+
49
+
50
+ # Default logger
51
+ validate_logger = setup_logger()
52
+ logger = validate_logger
@@ -0,0 +1,177 @@
1
+ # codegen/validate/models.py
2
+ """\nValidation data model definitions\n"""
3
+
4
+ from dataclasses import dataclass, field
5
+ from enum import Enum
6
+ from typing import List, Dict, Any, Optional
7
+
8
+
9
+ class ValidationSeverity(Enum):
10
+ """Validation severity"""
11
+ ERROR = "error"
12
+ WARNING = "warning"
13
+ INFO = "info"
14
+
15
+ @classmethod
16
+ def from_string(cls, value: str) -> 'ValidationSeverity':
17
+ """Convert from string"""
18
+ value_map = {
19
+ 'error': cls.ERROR,
20
+ 'warning': cls.WARNING,
21
+ 'info': cls.INFO,
22
+ 'ERROR': cls.ERROR,
23
+ 'WARNING': cls.WARNING,
24
+ 'INFO': cls.INFO,
25
+ 'Error': cls.ERROR,
26
+ 'Warning': cls.WARNING,
27
+ 'Info': cls.INFO,
28
+ }
29
+ return value_map.get(value, cls.INFO)
30
+
31
+
32
+ @dataclass
33
+ class ValidationIssue:
34
+ """Validation issue"""
35
+ category: str
36
+ code: str
37
+ message: str
38
+ severity: ValidationSeverity
39
+ target: str = ""
40
+ suggestion: str = ""
41
+ details: Dict[str, Any] = field(default_factory=dict)
42
+
43
+ def to_dict(self) -> Dict:
44
+ """Convert to dictionary"""
45
+ return {
46
+ 'category': self.category,
47
+ 'code': self.code,
48
+ 'message': self.message,
49
+ 'severity': self.severity.value,
50
+ 'target': self.target,
51
+ 'suggestion': self.suggestion,
52
+ 'details': self.details,
53
+ }
54
+
55
+ @classmethod
56
+ def from_dict(cls, data: Dict) -> 'ValidationIssue':
57
+ """Restore from dictionary"""
58
+ return cls(
59
+ category=data.get('category', ''),
60
+ code=data.get('code', ''),
61
+ message=data.get('message', ''),
62
+ severity=ValidationSeverity.from_string(data.get('severity', 'info')),
63
+ target=data.get('target', ''),
64
+ suggestion=data.get('suggestion', ''),
65
+ details=data.get('details', {}),
66
+ )
67
+
68
+ def __str__(self) -> str:
69
+ return f"[{self.severity.value.upper()}] {self.category}: {self.message}"
70
+
71
+
72
+ @dataclass
73
+ class ValidationResult:
74
+ """Validation result"""
75
+ issues: List[ValidationIssue] = field(default_factory=list)
76
+
77
+ @property
78
+ def passed(self) -> bool:
79
+ return self.error_count == 0
80
+
81
+ @property
82
+ def error_count(self) -> int:
83
+ return sum(1 for i in self.issues if i.severity == ValidationSeverity.ERROR)
84
+
85
+ @property
86
+ def warning_count(self) -> int:
87
+ return sum(1 for i in self.issues if i.severity == ValidationSeverity.WARNING)
88
+
89
+ @property
90
+ def info_count(self) -> int:
91
+ return sum(1 for i in self.issues if i.severity == ValidationSeverity.INFO)
92
+
93
+ def get_errors(self) -> List[ValidationIssue]:
94
+ return [i for i in self.issues if i.severity == ValidationSeverity.ERROR]
95
+
96
+ def get_warnings(self) -> List[ValidationIssue]:
97
+ return [i for i in self.issues if i.severity == ValidationSeverity.WARNING]
98
+
99
+ def get_infos(self) -> List[ValidationIssue]:
100
+ return [i for i in self.issues if i.severity == ValidationSeverity.INFO]
101
+
102
+ def get_by_category(self, category: str) -> List[ValidationIssue]:
103
+ return [i for i in self.issues if i.category == category]
104
+
105
+ def to_dict(self) -> Dict:
106
+ return {
107
+ 'passed': self.passed,
108
+ 'error_count': self.error_count,
109
+ 'warning_count': self.warning_count,
110
+ 'info_count': self.info_count,
111
+ 'issues': [i.to_dict() for i in self.issues],
112
+ }
113
+
114
+ @classmethod
115
+ def from_dict(cls, data: Dict) -> 'ValidationResult':
116
+ return cls(
117
+ issues=[ValidationIssue.from_dict(i) for i in data.get('issues', [])]
118
+ )
119
+
120
+ def __str__(self) -> str:
121
+ return (f"ValidationResult(errors={self.error_count}, "
122
+ f"warnings={self.warning_count}, infos={self.info_count})")
123
+
124
+
125
+ @dataclass
126
+ class ValidationContext:
127
+ """Validation context"""
128
+ state_machine: Any = None
129
+ global_defs: Any = None
130
+
131
+ @property
132
+ def states(self) -> Dict:
133
+ return self.state_machine.states if self.state_machine else {}
134
+
135
+ @property
136
+ def events(self) -> Dict:
137
+ return self.state_machine.events if self.state_machine else {}
138
+
139
+ @property
140
+ def transitions(self) -> List:
141
+ return self.state_machine.transitions if self.state_machine else []
142
+
143
+ @property
144
+ def role_functions(self) -> Dict:
145
+ return self.state_machine.role_functions if self.state_machine else {}
146
+
147
+ @property
148
+ def initial_state(self) -> Optional[str]:
149
+ return self.state_machine.initial_state if self.state_machine else None
150
+
151
+ @property
152
+ def variables(self) -> List:
153
+ return self.global_defs.variables if self.global_defs else []
154
+
155
+ @property
156
+ def flags(self) -> List:
157
+ return self.global_defs.flags if self.global_defs else []
158
+
159
+ @property
160
+ def event_queues(self) -> List:
161
+ return self.global_defs.event_queues if self.global_defs else []
162
+
163
+ @property
164
+ def interrupts(self) -> List:
165
+ return self.global_defs.interrupts if self.global_defs else []
166
+
167
+ @property
168
+ def custom_types(self) -> List:
169
+ return self.global_defs.custom_types if self.global_defs else []
170
+
171
+ @property
172
+ def timer_base(self):
173
+ return self.global_defs.timer_base if self.global_defs else None
174
+
175
+ @property
176
+ def extra_timers(self) -> List:
177
+ return self.global_defs.extra_timers if self.global_defs else []