speclogician 0.0.0b1__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 (139) hide show
  1. speclogician/__init__.py +0 -0
  2. speclogician/commands/__init__.py +15 -0
  3. speclogician/commands/cmd_ch.py +616 -0
  4. speclogician/commands/cmd_find.py +256 -0
  5. speclogician/commands/cmd_view.py +202 -0
  6. speclogician/commands/runner.py +149 -0
  7. speclogician/commands/utils.py +101 -0
  8. speclogician/data/__init__.py +0 -0
  9. speclogician/data/artifact.py +63 -0
  10. speclogician/data/container.py +402 -0
  11. speclogician/data/mapping.py +88 -0
  12. speclogician/data/refs.py +24 -0
  13. speclogician/data/traces.py +26 -0
  14. speclogician/demos/.DS_Store +0 -0
  15. speclogician/demos/cmd_demo.py +278 -0
  16. speclogician/demos/loader.py +135 -0
  17. speclogician/demos/model.py +27 -0
  18. speclogician/demos/runner.py +51 -0
  19. speclogician/logic/__init__.py +11 -0
  20. speclogician/logic/api/__init__.py +29 -0
  21. speclogician/logic/api/client.py +606 -0
  22. speclogician/logic/api/decomp.py +67 -0
  23. speclogician/logic/api/scenario.py +102 -0
  24. speclogician/logic/api/traces.py +59 -0
  25. speclogician/logic/lib/__init__.py +19 -0
  26. speclogician/logic/lib/complement.py +107 -0
  27. speclogician/logic/lib/domain_model.py +59 -0
  28. speclogician/logic/lib/predicates.py +151 -0
  29. speclogician/logic/lib/scenarios.py +369 -0
  30. speclogician/logic/lib/traces.py +114 -0
  31. speclogician/logic/lib/transitions.py +104 -0
  32. speclogician/logic/main.py +246 -0
  33. speclogician/logic/strings.py +194 -0
  34. speclogician/logic/utils.py +135 -0
  35. speclogician/main.py +139 -0
  36. speclogician/modeling/__init__.py +31 -0
  37. speclogician/modeling/complement.py +104 -0
  38. speclogician/modeling/component.py +71 -0
  39. speclogician/modeling/conflict.py +26 -0
  40. speclogician/modeling/domain.py +349 -0
  41. speclogician/modeling/predicates.py +59 -0
  42. speclogician/modeling/scenario.py +162 -0
  43. speclogician/modeling/spec.py +306 -0
  44. speclogician/modeling/spec_stats.py +39 -0
  45. speclogician/presentation/__init__.py +0 -0
  46. speclogician/presentation/api.py +244 -0
  47. speclogician/presentation/builders/__init__.py +0 -0
  48. speclogician/presentation/builders/_links.py +44 -0
  49. speclogician/presentation/builders/container.py +53 -0
  50. speclogician/presentation/builders/data_artifact.py +42 -0
  51. speclogician/presentation/builders/domain.py +54 -0
  52. speclogician/presentation/builders/instances_list.py +38 -0
  53. speclogician/presentation/builders/predicate.py +51 -0
  54. speclogician/presentation/builders/recommendations.py +41 -0
  55. speclogician/presentation/builders/scenario.py +41 -0
  56. speclogician/presentation/builders/scenario_complement.py +82 -0
  57. speclogician/presentation/builders/smart_find.py +39 -0
  58. speclogician/presentation/builders/spec.py +39 -0
  59. speclogician/presentation/builders/state_diff.py +150 -0
  60. speclogician/presentation/builders/state_instance.py +42 -0
  61. speclogician/presentation/builders/state_instance_summary.py +84 -0
  62. speclogician/presentation/builders/trace.py +58 -0
  63. speclogician/presentation/ctx.py +38 -0
  64. speclogician/presentation/models/__init__.py +0 -0
  65. speclogician/presentation/models/container.py +44 -0
  66. speclogician/presentation/models/data_artifact.py +33 -0
  67. speclogician/presentation/models/domain.py +50 -0
  68. speclogician/presentation/models/instances_list.py +23 -0
  69. speclogician/presentation/models/predicate.py +60 -0
  70. speclogician/presentation/models/recommendations.py +34 -0
  71. speclogician/presentation/models/scenario.py +31 -0
  72. speclogician/presentation/models/scenario_complement.py +40 -0
  73. speclogician/presentation/models/smart_find.py +34 -0
  74. speclogician/presentation/models/spec.py +32 -0
  75. speclogician/presentation/models/state_diff.py +34 -0
  76. speclogician/presentation/models/state_instance.py +31 -0
  77. speclogician/presentation/models/state_instance_summary.py +102 -0
  78. speclogician/presentation/models/trace.py +42 -0
  79. speclogician/presentation/preview/__init__.py +13 -0
  80. speclogician/presentation/preview/cli.py +50 -0
  81. speclogician/presentation/preview/fixtures/__init__.py +205 -0
  82. speclogician/presentation/preview/fixtures/artifact_container.py +150 -0
  83. speclogician/presentation/preview/fixtures/data_artifact.py +144 -0
  84. speclogician/presentation/preview/fixtures/domain_model.py +162 -0
  85. speclogician/presentation/preview/fixtures/instances_list.py +162 -0
  86. speclogician/presentation/preview/fixtures/predicate.py +184 -0
  87. speclogician/presentation/preview/fixtures/scenario.py +84 -0
  88. speclogician/presentation/preview/fixtures/scenario_complement.py +81 -0
  89. speclogician/presentation/preview/fixtures/smart_find.py +140 -0
  90. speclogician/presentation/preview/fixtures/spec.py +95 -0
  91. speclogician/presentation/preview/fixtures/state_diff.py +158 -0
  92. speclogician/presentation/preview/fixtures/state_instance.py +128 -0
  93. speclogician/presentation/preview/fixtures/state_instance_summary.py +80 -0
  94. speclogician/presentation/preview/fixtures/trace.py +206 -0
  95. speclogician/presentation/preview/registry.py +42 -0
  96. speclogician/presentation/renderers/__init__.py +24 -0
  97. speclogician/presentation/renderers/container.py +136 -0
  98. speclogician/presentation/renderers/data_artifact.py +144 -0
  99. speclogician/presentation/renderers/domain.py +123 -0
  100. speclogician/presentation/renderers/instances_list.py +120 -0
  101. speclogician/presentation/renderers/predicate.py +180 -0
  102. speclogician/presentation/renderers/recommendations.py +90 -0
  103. speclogician/presentation/renderers/scenario.py +94 -0
  104. speclogician/presentation/renderers/scenario_complement.py +59 -0
  105. speclogician/presentation/renderers/smart_find.py +307 -0
  106. speclogician/presentation/renderers/spec.py +105 -0
  107. speclogician/presentation/renderers/state_diff.py +102 -0
  108. speclogician/presentation/renderers/state_instance.py +82 -0
  109. speclogician/presentation/renderers/state_instance_summary.py +143 -0
  110. speclogician/presentation/renderers/trace.py +122 -0
  111. speclogician/py.typed +0 -0
  112. speclogician/shell/app.py +170 -0
  113. speclogician/shell/shell_ch.py +263 -0
  114. speclogician/shell/shell_view.py +153 -0
  115. speclogician/state/__init__.py +0 -0
  116. speclogician/state/change.py +428 -0
  117. speclogician/state/change_result.py +32 -0
  118. speclogician/state/diff.py +191 -0
  119. speclogician/state/inst.py +574 -0
  120. speclogician/state/recommendation.py +13 -0
  121. speclogician/state/recommender.py +577 -0
  122. speclogician/state/state.py +465 -0
  123. speclogician/state/state_stats.py +133 -0
  124. speclogician/tui/__init__.py +0 -0
  125. speclogician/tui/app.py +257 -0
  126. speclogician/tui/app.tcss +160 -0
  127. speclogician/tui/demo.py +45 -0
  128. speclogician/tui/images/speclogician-full.png +0 -0
  129. speclogician/tui/images/speclogician-minimal.png +0 -0
  130. speclogician/tui/main_screen.py +454 -0
  131. speclogician/tui/splash_screen.py +51 -0
  132. speclogician/tui/stats_screen.py +125 -0
  133. speclogician/utils/__init__.py +78 -0
  134. speclogician/utils/load.py +166 -0
  135. speclogician/utils/prompt.md +325 -0
  136. speclogician/utils/testing.py +151 -0
  137. speclogician-0.0.0b1.dist-info/METADATA +116 -0
  138. speclogician-0.0.0b1.dist-info/RECORD +139 -0
  139. speclogician-0.0.0b1.dist-info/WHEEL +4 -0
@@ -0,0 +1,349 @@
1
+ #
2
+ # Imandra Inc.
3
+ #
4
+ # speclogician/modeling/domain.py
5
+ #
6
+
7
+ from typing import Sequence
8
+ from pydantic import BaseModel, Field
9
+
10
+ from .predicates import (
11
+ StatePredicate, ActionPredicate, Transition, PredicateType
12
+ )
13
+ from .__init__ import extract_declared_types
14
+ from enum import StrEnum
15
+
16
+ class BaseStatus(StrEnum):
17
+ UNKNOWN = "unknown"
18
+ INVALID_IML = "invalid_iml"
19
+ VALID = "valid"
20
+
21
+ class DomainModel(BaseModel):
22
+ """
23
+ The domain model (base model with state/action types + other auxillary functions,
24
+ and predicates/transition functions)
25
+ """
26
+
27
+ base_status : BaseStatus = BaseStatus.UNKNOWN # IML status
28
+ base_has_state : bool = False # Model contains `state` type definition
29
+ base_has_action : bool = False # Model contains `action` type definition
30
+ base : str = "" # Includes type definitions and other
31
+
32
+ transitions : list[Transition] = Field(default_factory=list[Transition]) # 'state * action -> state' tansition functions
33
+ state_preds : list[StatePredicate] = Field(default_factory=list[StatePredicate]) # 'state -> bool' predicates
34
+ action_preds : list[ActionPredicate] = Field(default_factory=list[ActionPredicate]) # 'state * action -> bool' predicates
35
+
36
+ # transitions
37
+ num_trans_total : int = 0
38
+ num_trans_matched : int = 0
39
+ num_trans_valid_logic : int = 0
40
+ num_trans_errored : int = 0
41
+
42
+ # state-only predicates
43
+ num_state_preds_total : int = 0
44
+ num_state_preds_matched : int = 0
45
+ num_state_preds_valid_logic : int = 0
46
+ num_state_preds_errored : int = 0
47
+
48
+ # state x action predicates
49
+ num_action_preds_total : int = 0
50
+ num_action_preds_matched : int = 0
51
+ num_action_preds_valid_logic : int = 0
52
+ num_action_preds_errored : int = 0
53
+
54
+ # predicate totals
55
+ num_preds_total : int = 0
56
+ num_preds_matched : int = 0
57
+ num_preds_valid_logic : int = 0
58
+ num_preds_errored : int = 0
59
+
60
+ def assert_has_predicates(
61
+ self,
62
+ *,
63
+ given: Sequence[str],
64
+ when: Sequence[str],
65
+ ) -> None:
66
+ """Ensure all predicates referenced by a scenario exist in the domain model.
67
+
68
+ - `given` must reference state predicates (state -> bool)
69
+ - `when` must reference action predicates (state -> action -> bool)
70
+
71
+ Raises ValueError with a helpful message if anything is missing or mismatched.
72
+ """
73
+ missing_state: list[str] = []
74
+ missing_action: list[str] = []
75
+ wrong_kind_given: list[str] = []
76
+ wrong_kind_when: list[str] = []
77
+
78
+ for name in given:
79
+ p = self.get_pred_by_name(name)
80
+ if p is None:
81
+ missing_state.append(name)
82
+ elif p not in self.state_preds:
83
+ # Exists, but it's an action predicate (or otherwise not a state pred)
84
+ wrong_kind_given.append(name)
85
+
86
+ for name in when:
87
+ p = self.get_pred_by_name(name)
88
+ if p is None:
89
+ missing_action.append(name)
90
+ elif p not in self.action_preds:
91
+ # Exists, but it's a state predicate (or otherwise not an action pred)
92
+ wrong_kind_when.append(name)
93
+
94
+ if missing_state or missing_action or wrong_kind_given or wrong_kind_when:
95
+ parts: list[str] = []
96
+ if missing_state:
97
+ parts.append(f"missing state predicates (given): {missing_state}")
98
+ if wrong_kind_given:
99
+ parts.append(f"not state predicates (given): {wrong_kind_given}")
100
+ if missing_action:
101
+ parts.append(f"missing action predicates (when): {missing_action}")
102
+ if wrong_kind_when:
103
+ parts.append(f"not action predicates (when): {wrong_kind_when}")
104
+
105
+ raise ValueError("Scenario references invalid predicates: " + "; ".join(parts))
106
+
107
+ def assert_has_transitions(self, *, then: Sequence[str]) -> None:
108
+ """Ensure all transitions referenced by a scenario exist in the domain model.
109
+
110
+ `then` is a list of transition function names.
111
+
112
+ Raises ValueError with a helpful message if anything is missing.
113
+ """
114
+ missing: list[str] = []
115
+
116
+ for name in then:
117
+ t = self.get_trans_by_name(name)
118
+ if t is None:
119
+ missing.append(name)
120
+
121
+ if missing:
122
+ raise ValueError(f"Scenario references unknown transitions (then): {missing}")
123
+
124
+ def get_pred_by_name (self, name:str) -> StatePredicate|ActionPredicate|None:
125
+ """ Return a predicate body for the specified name """
126
+ s_pred = next((p for p in self.state_preds if p.name == name), None)
127
+
128
+ if s_pred is None:
129
+ return next((p for p in self.action_preds if p.name == name), None)
130
+ else:
131
+ return s_pred
132
+
133
+ def get_trans_by_name (self, name:str) -> Transition|None:
134
+ """ Return transition body for a specified name """
135
+ return next((p for p in self.transitions if p.name == name), None)
136
+
137
+ def action_pred_names(self) -> list[str]:
138
+ """ List of action predicate names """
139
+ return list(map(lambda x: x.name, self.action_preds))
140
+
141
+ def state_pred_names(self) -> list[str]:
142
+ """ List of available state predicate names """
143
+ return list(map(lambda x: x.name, self.state_preds))
144
+
145
+ def transition_names(self) -> list[str]:
146
+ """ List of available transitions """
147
+ return list(map(lambda x: x.name, self.transitions))
148
+
149
+ def pred_exists(self, name:str) -> bool:
150
+ """
151
+ Does this predicate exist?
152
+ """
153
+ if next ((p for p in self.state_preds if p.name == name), None) is not None:
154
+ return True
155
+ return next ((p for p in self.action_preds if p.name == name), None) is not None
156
+
157
+ def add_predicate(self, name: str, ptype: PredicateType, src_code: str) -> None:
158
+ """Add a new predicate to the domain model."""
159
+
160
+ # Enforce global uniqueness across all predicates
161
+ if any(p.name == name for p in self.state_preds) or \
162
+ any(p.name == name for p in self.action_preds):
163
+ raise ValueError(f"Predicate '{name}' already exists in domain model")
164
+
165
+ if ptype == PredicateType.STATE:
166
+ pred = StatePredicate(
167
+ name=name,
168
+ src_code=src_code,
169
+ )
170
+ self.state_preds.append(pred)
171
+
172
+ elif ptype == PredicateType.ACTION:
173
+ pred = ActionPredicate(
174
+ name=name,
175
+ src_code=src_code,
176
+ )
177
+ self.action_preds.append(pred)
178
+
179
+ else:
180
+ raise ValueError(f"Attempting to add unknown type of predicate: {ptype}")
181
+
182
+ def edit_predicate(self, name: str, src_code: str) -> None:
183
+ """Edit a predicate by name and update last_updated."""
184
+
185
+ matches = [p for p in self.state_preds if p.name == name] + \
186
+ [p for p in self.action_preds if p.name == name]
187
+
188
+ if not matches:
189
+ raise ValueError(f"Predicate '{name}' not found")
190
+
191
+ if len(matches) > 1:
192
+ raise ValueError(f"Predicate '{name}' is ambiguous (found {len(matches)} matches)")
193
+
194
+ pred = matches[0]
195
+ pred.set_src_code(src_code)
196
+
197
+ def rem_predicate(self, name: str) -> None:
198
+ """Remove a predicate (state or action) by name."""
199
+
200
+ removed = False
201
+
202
+ # Remove from state predicates
203
+ for i, p in enumerate(self.state_preds):
204
+ if p.name == name:
205
+ del self.state_preds[i]
206
+ removed = True
207
+ break
208
+
209
+ # Remove from action predicates
210
+ for i, p in enumerate(self.action_preds):
211
+ if p.name == name:
212
+ del self.action_preds[i]
213
+ removed = True
214
+ break
215
+
216
+ if not removed:
217
+ raise ValueError(f"Predicate '{name}' not found in domain model")
218
+
219
+ def add_transition(self, name: str, src_code: str) -> None:
220
+ """Add a new transition function to the domain model."""
221
+
222
+ # Ensure uniqueness
223
+ if any(t.name == name for t in self.transitions):
224
+ raise ValueError(f"Transition '{name}' already exists in domain model")
225
+
226
+ transition = Transition(
227
+ name=name,
228
+ src_code=src_code,
229
+ )
230
+
231
+ self.transitions.append(transition)
232
+
233
+ def edit_transition(self, name: str, src_code: str) -> None:
234
+ """Edit a transition function by name and update last_updated."""
235
+ matches = [t for t in self.transitions if t.name == name]
236
+
237
+ if not matches:
238
+ raise ValueError(f"Transition '{name}' not found in domain model")
239
+
240
+ if len(matches) > 1:
241
+ raise ValueError(
242
+ f"Transition name '{name}' is ambiguous "
243
+ f"({len(matches)} transitions found)"
244
+ )
245
+
246
+ transition = matches[0]
247
+ transition.set_src_code(src_code)
248
+
249
+ def rem_transition(self, name: str) -> None:
250
+ """Remove a transition function by name."""
251
+
252
+ for i, t in enumerate(self.transitions):
253
+ if t.name == name:
254
+ del self.transitions[i]
255
+ return
256
+
257
+ raise ValueError(f"Transition '{name}' not found in domain model")
258
+
259
+ def trans_exists(self, name:str) -> bool:
260
+ """ Return True/False if transitions with specified name exists """
261
+ return next ((t for t in self.transitions if t.name == name), None) is not None
262
+
263
+ def state_specified (self) -> bool:
264
+ """
265
+ Return True if the `state` type is properly specified
266
+ """
267
+ return self.base_has_type('state')
268
+
269
+ def action_specified(self) -> bool:
270
+ """
271
+ Return True if the `action` is properly specified
272
+ """
273
+ return self.base_has_type('action')
274
+
275
+ def base_has_type(
276
+ self,
277
+ type_name : str
278
+ ) -> bool:
279
+ """
280
+ Check whether the base model has a specified type
281
+ """
282
+
283
+ declared_types = extract_declared_types(self.base)
284
+ return type_name in declared_types
285
+
286
+ def set_base (
287
+ self,
288
+ new_base : str) -> None:
289
+ """
290
+ Update the base model
291
+ """
292
+ self.base = new_base
293
+
294
+ return None
295
+
296
+ def make_iml_model (
297
+ self,
298
+ state_pred_names : list[str],
299
+ action_pred_names : list[str],
300
+ transition_names : list[str]
301
+ ) -> str:
302
+ """
303
+ Return base model with required predicates/transitions if needed
304
+ """
305
+
306
+ state_preds_str = ""
307
+ for p in state_pred_names:
308
+ pred = self.get_pred_by_name(p)
309
+ if pred is None:
310
+ continue
311
+ state_preds_str += f"{pred.to_iml()}"
312
+ if len(state_preds_str):
313
+ state_preds_str = f"(* State predicates *)\n{state_preds_str}"
314
+
315
+ action_preds_str = ""
316
+ for p in action_pred_names:
317
+ pred = self.get_pred_by_name(p)
318
+ if pred is None:
319
+ continue
320
+ action_preds_str += f"\n{pred.to_iml()}\n"
321
+ if len(action_preds_str):
322
+ action_preds_str = f"(* Action predicates *)\n{action_preds_str}"
323
+
324
+ trans_str = ""
325
+ for t in transition_names:
326
+ trans = self.get_trans_by_name(t)
327
+ if trans is None:
328
+ continue
329
+ trans_str += f"\n{trans.to_iml()}\n"
330
+ if len(trans_str):
331
+ trans_str = f"(* Transitions *)\n{trans_str}"
332
+
333
+ return f"""
334
+ (* Domain Model *)
335
+
336
+ (* Base *)
337
+ {self.base}
338
+ {state_preds_str}
339
+ {action_preds_str}
340
+ {trans_str}
341
+ """.rstrip("\n")
342
+
343
+ def to_iml(self):
344
+ """
345
+ Create a consolidated IML model with all predicates and transitions
346
+ """
347
+ return self.make_iml_model(
348
+ self.state_pred_names(), self.action_pred_names(), self.transition_names()
349
+ )
@@ -0,0 +1,59 @@
1
+ #
2
+ # Imandra Inc.
3
+ #
4
+ # speclogician/modeling/predicates.py
5
+ #
6
+
7
+ from typing import Literal
8
+ from enum import StrEnum
9
+
10
+ from .component import SrcComponent
11
+
12
+ class PredicateType(StrEnum):
13
+ STATE = "state"
14
+ ACTION = "action"
15
+
16
+ class StatePredicate(SrcComponent):
17
+ """
18
+ Predicate on the state only
19
+ """
20
+ kind: Literal["state_predicate"] = "state_predicate"
21
+
22
+ def __str__ (self):
23
+ """ """
24
+ return f"{self.name} s"
25
+
26
+ def to_iml (self):
27
+ """ Return the IML representation of the StatePredicate """
28
+ return f"let {self.name} (s : state) : bool = \n {self.src_code}\n"
29
+
30
+ class ActionPredicate(SrcComponent):
31
+ """
32
+ Predicate involving a state and an action
33
+ """
34
+ kind: Literal["action_predicate"] = "action_predicate"
35
+
36
+ def __str__(self):
37
+ """
38
+ """
39
+ return f"{self.name} s a"
40
+
41
+ def to_iml(self):
42
+ """
43
+ Return the IML representation of the ActionPredicate
44
+ """
45
+ return f"let {self.name} (s : state) (a : action) : bool = \n {self.src_code}\n"
46
+
47
+ class Transition(SrcComponent):
48
+ """
49
+ Transition functions are used to update the state
50
+ """
51
+ kind: Literal["transition"] = "transition"
52
+ def __str__ (self):
53
+ return f"{self.name} s a"
54
+
55
+ def to_iml (self):
56
+ """
57
+ Return the IML representation of the transition
58
+ """
59
+ return f"let {self.name} (s : state) (a : action) : state = \n {self.src_code}"
@@ -0,0 +1,162 @@
1
+ #
2
+ # Imandra Inc.
3
+ #
4
+ # speclogician/modeling/scenario.py
5
+ #
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import TypeAlias, Sequence
10
+ from pydantic import BaseModel, Field, field_validator, model_validator
11
+
12
+ from .component import ModelComponent, validate_iml_identifier
13
+
14
+
15
+ class ScenarioDelta(BaseModel):
16
+ """We'll use this to edit Scenarios."""
17
+ add: list[str] = Field(default_factory=list)
18
+ remove: list[str] = Field(default_factory=list)
19
+
20
+ @field_validator("add", "remove")
21
+ @classmethod
22
+ def _dedup_and_strip(cls, xs: list[str]) -> list[str]:
23
+ # normalize, drop empties, dedup preserving order
24
+ out: list[str] = []
25
+ seen: set[str] = set()
26
+ for x in xs:
27
+ x = x.strip()
28
+ if not x or x in seen:
29
+ continue
30
+ out.append(x)
31
+ seen.add(x)
32
+ return out
33
+
34
+ @field_validator("add", "remove")
35
+ @classmethod
36
+ def _validate_identifiers(cls, xs: list[str]) -> list[str]:
37
+ # Reuse the canonical IML identifier validation
38
+ for x in xs:
39
+ validate_iml_identifier(x)
40
+ return xs
41
+
42
+ @model_validator(mode="after")
43
+ def _no_overlap(self) -> "ScenarioDelta":
44
+ overlap = set(self.add) & set(self.remove)
45
+ if overlap:
46
+ raise ValueError(f"Names cannot be both added and removed: {sorted(overlap)}")
47
+ return self
48
+
49
+ def is_empty(self) -> bool:
50
+ return not self.add and not self.remove
51
+
52
+ def is_add_only(self) -> bool:
53
+ return not self.remove
54
+
55
+ class Valid(BaseModel):
56
+ """ The scenario's valid """
57
+ pass
58
+
59
+ class Inconsistent(BaseModel):
60
+ """ Scenario is inconsistent - the combination of predicates is unsatisfiable """
61
+ pass
62
+
63
+ class Missing(BaseModel):
64
+ """ Referenced model components are missing """
65
+ missing_preds : list[str] = Field(default_factory=list)
66
+ missing_trans : list[str] = Field(default_factory=list)
67
+
68
+ ComponentStatus : TypeAlias = Valid | Inconsistent | Missing
69
+
70
+ class Scenario(ModelComponent):
71
+ """
72
+ Scenario contains
73
+ """
74
+
75
+ name : str
76
+
77
+ # Predicates and transition functions are components
78
+ component_status : ComponentStatus = Valid()
79
+
80
+ given: Sequence[str] = Field(default_factory=list)
81
+ when: Sequence[str] = Field(default_factory=list)
82
+ then: Sequence[str] = Field(default_factory=list)
83
+
84
+
85
+ # When we check the DomainModel and this is missing,
86
+ # we'll then set these fields
87
+ preds_missing : list[str] = Field(default_factory=list)
88
+ trans_missing : list[str] = Field(default_factory=list)
89
+
90
+ # These contain consistency checks
91
+ # why precisely they're inconsistent
92
+ given_preds_consistent : bool = True
93
+ when_preds_consistent : bool = True
94
+ all_preds_consistent : bool = True
95
+
96
+ def pred_calls_to_iml (self) -> str:
97
+ """
98
+ return &&'ed string of all predicates
99
+ """
100
+
101
+ given_str = "\n&& ".join(map(lambda x: f"{x} s", self.given))
102
+ when_str = "\n&& ".join(map(lambda x: f"{x} s a", self.when))
103
+
104
+ match (len(self.given), len(self.when)):
105
+ case (0, 0):
106
+ return ""
107
+ case (_, 0):
108
+ return given_str
109
+ case (0, _):
110
+ return when_str
111
+ case (_, _):
112
+ return f"{given_str} \n&& {when_str}"
113
+
114
+ def eval_transition (self, state_val:str, act_val:str) -> str:
115
+ """
116
+ Given concrete state and action values, evaluate the set of transitions
117
+ """
118
+
119
+ if len(self.then) == 0:
120
+ return state_val
121
+
122
+ trans_iml = ""
123
+ for t in self.then:
124
+ trans_iml += f"\nlet s = {t} s a in"
125
+
126
+ trans_iml += "\n"
127
+
128
+ return ''
129
+
130
+ def preds_to_iml (self):
131
+ """
132
+ Return IML code of just the predicates (both types) encoded in IML
133
+ """
134
+ given_str = "&& \n".join(map(str, self.given))
135
+ when_str = "&& \n".join(map(str, self.when))
136
+
137
+ s = f"""
138
+ {given_str}
139
+
140
+ {when_str}
141
+ """
142
+ return s
143
+
144
+ def full_model_to_iml(self):
145
+ """
146
+ Create the full model (we omit the 'else' at the end because this is intended to be used with
147
+ other scenarios
148
+ """
149
+ given_str = "&& \n".join(map(str, self.given))
150
+ when_str = "&& \n".join(map(str, self.when))
151
+ then_str = "\n".join(map(str, self.then))
152
+
153
+ s = f"""
154
+ (* Scenario: {self.name} *)
155
+ let given_true =
156
+ {given_str}
157
+ in let when_true =
158
+ {when_str}
159
+ in if given_true && when_true then
160
+ ({then_str})
161
+ """
162
+ return s