speclogician 0.0.0b1__py3-none-any.whl → 0.0.0.dev1__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.
- speclogician/agent/funcs.py +29 -0
- speclogician/cmd/agent_cmd.py +89 -0
- speclogician/cmd/data_cmd.py +24 -0
- speclogician/cmd/model_cmd.py +42 -0
- speclogician/cmd/overlay_cmd.py +30 -0
- speclogician/cmd/scenario_cmd.py +61 -0
- speclogician/cmd/state_cmd.py +52 -0
- speclogician/data/artifact.py +8 -50
- speclogician/data/container.py +18 -384
- speclogician/data/mapping.py +18 -17
- speclogician/data/refs.py +12 -11
- speclogician/data/reports.py +11 -0
- speclogician/data/traces.py +15 -6
- speclogician/llms/llmtools.py +102 -0
- speclogician/llms/overlay.py +264 -0
- speclogician/main.py +36 -102
- speclogician/modeling/__init__.py +0 -31
- speclogician/modeling/component.py +4 -60
- speclogician/modeling/conflict.py +5 -19
- speclogician/modeling/domain.py +93 -280
- speclogician/modeling/model.py +206 -0
- speclogician/modeling/predicates.py +20 -22
- speclogician/modeling/report.py +33 -0
- speclogician/modeling/scenario.py +119 -87
- speclogician/sl_cmd.py +76 -0
- speclogician/state/change.py +98 -378
- speclogician/state/state.py +183 -399
- speclogician/tui/box.tcss +10 -0
- speclogician/tui/tui.py +131 -0
- speclogician/utils/__init__.py +1 -70
- speclogician/utils/imx.py +195 -0
- speclogician/utils/load.py +25 -147
- speclogician/utils/prompt.md +1 -325
- speclogician-0.0.0.dev1.dist-info/METADATA +21 -0
- speclogician-0.0.0.dev1.dist-info/RECORD +43 -0
- speclogician/commands/__init__.py +0 -15
- speclogician/commands/cmd_ch.py +0 -616
- speclogician/commands/cmd_find.py +0 -256
- speclogician/commands/cmd_view.py +0 -202
- speclogician/commands/runner.py +0 -149
- speclogician/commands/utils.py +0 -101
- speclogician/demos/.DS_Store +0 -0
- speclogician/demos/cmd_demo.py +0 -278
- speclogician/demos/loader.py +0 -135
- speclogician/demos/model.py +0 -27
- speclogician/demos/runner.py +0 -51
- speclogician/logic/__init__.py +0 -11
- speclogician/logic/api/__init__.py +0 -29
- speclogician/logic/api/client.py +0 -606
- speclogician/logic/api/decomp.py +0 -67
- speclogician/logic/api/scenario.py +0 -102
- speclogician/logic/api/traces.py +0 -59
- speclogician/logic/lib/__init__.py +0 -19
- speclogician/logic/lib/complement.py +0 -107
- speclogician/logic/lib/domain_model.py +0 -59
- speclogician/logic/lib/predicates.py +0 -151
- speclogician/logic/lib/scenarios.py +0 -369
- speclogician/logic/lib/traces.py +0 -114
- speclogician/logic/lib/transitions.py +0 -104
- speclogician/logic/main.py +0 -246
- speclogician/logic/strings.py +0 -194
- speclogician/logic/utils.py +0 -135
- speclogician/modeling/complement.py +0 -104
- speclogician/modeling/spec.py +0 -306
- speclogician/modeling/spec_stats.py +0 -39
- speclogician/presentation/api.py +0 -244
- speclogician/presentation/builders/_links.py +0 -44
- speclogician/presentation/builders/container.py +0 -53
- speclogician/presentation/builders/data_artifact.py +0 -42
- speclogician/presentation/builders/domain.py +0 -54
- speclogician/presentation/builders/instances_list.py +0 -38
- speclogician/presentation/builders/predicate.py +0 -51
- speclogician/presentation/builders/recommendations.py +0 -41
- speclogician/presentation/builders/scenario.py +0 -41
- speclogician/presentation/builders/scenario_complement.py +0 -82
- speclogician/presentation/builders/smart_find.py +0 -39
- speclogician/presentation/builders/spec.py +0 -39
- speclogician/presentation/builders/state_diff.py +0 -150
- speclogician/presentation/builders/state_instance.py +0 -42
- speclogician/presentation/builders/state_instance_summary.py +0 -84
- speclogician/presentation/builders/trace.py +0 -58
- speclogician/presentation/ctx.py +0 -38
- speclogician/presentation/models/container.py +0 -44
- speclogician/presentation/models/data_artifact.py +0 -33
- speclogician/presentation/models/domain.py +0 -50
- speclogician/presentation/models/instances_list.py +0 -23
- speclogician/presentation/models/predicate.py +0 -60
- speclogician/presentation/models/recommendations.py +0 -34
- speclogician/presentation/models/scenario.py +0 -31
- speclogician/presentation/models/scenario_complement.py +0 -40
- speclogician/presentation/models/smart_find.py +0 -34
- speclogician/presentation/models/spec.py +0 -32
- speclogician/presentation/models/state_diff.py +0 -34
- speclogician/presentation/models/state_instance.py +0 -31
- speclogician/presentation/models/state_instance_summary.py +0 -102
- speclogician/presentation/models/trace.py +0 -42
- speclogician/presentation/preview/__init__.py +0 -13
- speclogician/presentation/preview/cli.py +0 -50
- speclogician/presentation/preview/fixtures/__init__.py +0 -205
- speclogician/presentation/preview/fixtures/artifact_container.py +0 -150
- speclogician/presentation/preview/fixtures/data_artifact.py +0 -144
- speclogician/presentation/preview/fixtures/domain_model.py +0 -162
- speclogician/presentation/preview/fixtures/instances_list.py +0 -162
- speclogician/presentation/preview/fixtures/predicate.py +0 -184
- speclogician/presentation/preview/fixtures/scenario.py +0 -84
- speclogician/presentation/preview/fixtures/scenario_complement.py +0 -81
- speclogician/presentation/preview/fixtures/smart_find.py +0 -140
- speclogician/presentation/preview/fixtures/spec.py +0 -95
- speclogician/presentation/preview/fixtures/state_diff.py +0 -158
- speclogician/presentation/preview/fixtures/state_instance.py +0 -128
- speclogician/presentation/preview/fixtures/state_instance_summary.py +0 -80
- speclogician/presentation/preview/fixtures/trace.py +0 -206
- speclogician/presentation/preview/registry.py +0 -42
- speclogician/presentation/renderers/__init__.py +0 -24
- speclogician/presentation/renderers/container.py +0 -136
- speclogician/presentation/renderers/data_artifact.py +0 -144
- speclogician/presentation/renderers/domain.py +0 -123
- speclogician/presentation/renderers/instances_list.py +0 -120
- speclogician/presentation/renderers/predicate.py +0 -180
- speclogician/presentation/renderers/recommendations.py +0 -90
- speclogician/presentation/renderers/scenario.py +0 -94
- speclogician/presentation/renderers/scenario_complement.py +0 -59
- speclogician/presentation/renderers/smart_find.py +0 -307
- speclogician/presentation/renderers/spec.py +0 -105
- speclogician/presentation/renderers/state_diff.py +0 -102
- speclogician/presentation/renderers/state_instance.py +0 -82
- speclogician/presentation/renderers/state_instance_summary.py +0 -143
- speclogician/presentation/renderers/trace.py +0 -122
- speclogician/shell/app.py +0 -170
- speclogician/shell/shell_ch.py +0 -263
- speclogician/shell/shell_view.py +0 -153
- speclogician/state/change_result.py +0 -32
- speclogician/state/diff.py +0 -191
- speclogician/state/inst.py +0 -574
- speclogician/state/recommendation.py +0 -13
- speclogician/state/recommender.py +0 -577
- speclogician/state/state_stats.py +0 -133
- speclogician/tui/__init__.py +0 -0
- speclogician/tui/app.py +0 -257
- speclogician/tui/app.tcss +0 -160
- speclogician/tui/demo.py +0 -45
- speclogician/tui/images/speclogician-full.png +0 -0
- speclogician/tui/images/speclogician-minimal.png +0 -0
- speclogician/tui/main_screen.py +0 -454
- speclogician/tui/splash_screen.py +0 -51
- speclogician/tui/stats_screen.py +0 -125
- speclogician/utils/testing.py +0 -151
- speclogician-0.0.0b1.dist-info/METADATA +0 -116
- speclogician-0.0.0b1.dist-info/RECORD +0 -139
- /speclogician/{presentation → agent}/__init__.py +0 -0
- /speclogician/{presentation/builders → cmd}/__init__.py +0 -0
- /speclogician/{presentation/models → llms}/__init__.py +0 -0
- {speclogician-0.0.0b1.dist-info → speclogician-0.0.0.dev1.dist-info}/WHEEL +0 -0
speclogician/tui/tui.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# tui.py
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
from textual.app import App, ComposeResult
|
|
8
|
+
from textual.widgets import (
|
|
9
|
+
Footer, Header, TabbedContent, TabPane,Static, ListView, ListItem, Label
|
|
10
|
+
)
|
|
11
|
+
from textual.reactive import reactive
|
|
12
|
+
from textual.containers import VerticalScroll
|
|
13
|
+
|
|
14
|
+
from ..modeling.model import TestFormalization, TLState, Scenario
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
class FileItem (ListItem):
|
|
18
|
+
def __init__(self, label: str) -> None:
|
|
19
|
+
super().__init__()
|
|
20
|
+
self.label = label
|
|
21
|
+
|
|
22
|
+
def compose( self ) -> ComposeResult:
|
|
23
|
+
yield Label(Path(self.label).name)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DetailView(Static):
|
|
27
|
+
"""
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
tf : reactive[TestFormalization | None] = reactive(None, always_update=True)
|
|
31
|
+
|
|
32
|
+
def watch_tf(self, _, tf:TestFormalization|None) -> None:
|
|
33
|
+
"""
|
|
34
|
+
"""
|
|
35
|
+
if tf is None: return
|
|
36
|
+
if not hasattr(self, 'view'): return
|
|
37
|
+
|
|
38
|
+
self.view.update(tf.__rich__())
|
|
39
|
+
|
|
40
|
+
def compose (self) -> ComposeResult:
|
|
41
|
+
"""
|
|
42
|
+
"""
|
|
43
|
+
with VerticalScroll():
|
|
44
|
+
self.view = Static("N/A")
|
|
45
|
+
|
|
46
|
+
yield self.view
|
|
47
|
+
|
|
48
|
+
class PredicateList(Static):
|
|
49
|
+
"""
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
pred_list : reactive[ScenarioPredList | None] = reactive(None, always_update=True)
|
|
53
|
+
|
|
54
|
+
def compose(self) -> ComposeResult:
|
|
55
|
+
"""
|
|
56
|
+
"""
|
|
57
|
+
self.view =
|
|
58
|
+
|
|
59
|
+
yield self.view
|
|
60
|
+
|
|
61
|
+
class SpecLogicianApp(App):
|
|
62
|
+
"""
|
|
63
|
+
"""
|
|
64
|
+
CSS_PATH = "box.tcss"
|
|
65
|
+
|
|
66
|
+
def __init__ (self, tl_state : TLState, **kwargs):
|
|
67
|
+
super().__init__ (**kwargs)
|
|
68
|
+
|
|
69
|
+
self.tl_state = tl_state
|
|
70
|
+
|
|
71
|
+
def compose(self) -> ComposeResult:
|
|
72
|
+
"""
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
yield Header()
|
|
76
|
+
|
|
77
|
+
with TabbedContent(initial="scenarios"):
|
|
78
|
+
|
|
79
|
+
with TabPane("Scenarios", id="scenarios"):
|
|
80
|
+
self.items = ListView (
|
|
81
|
+
*[FileItem(t.filepath) for t in self.tl_state.tfs()],
|
|
82
|
+
classes="box",
|
|
83
|
+
id="items"
|
|
84
|
+
)
|
|
85
|
+
yield self.items
|
|
86
|
+
|
|
87
|
+
self.detail = DetailView("hello", id="detail", classes="box")
|
|
88
|
+
yield self.detail
|
|
89
|
+
|
|
90
|
+
with TabPane("Domain Modle", id="model"):
|
|
91
|
+
self.domain_model = Static(self.tl_state.rich_model())
|
|
92
|
+
yield self.domain_model
|
|
93
|
+
|
|
94
|
+
with TabPane("Concrete Scenarios", id="concrete"):
|
|
95
|
+
self.concrete_scenarios = PredicateList()
|
|
96
|
+
self.concrete_scenarios.pred_list = self.tl_state.concrete()
|
|
97
|
+
yield self.concrete_scenarios
|
|
98
|
+
|
|
99
|
+
with TabPane("Predicate Scenarios", id="predicates"):
|
|
100
|
+
self.predicate_scenarios = PredicateList()
|
|
101
|
+
self.predciate_scenarios.pred_list = self.tl_state.predicates()
|
|
102
|
+
yield self.predicate_scenarios
|
|
103
|
+
|
|
104
|
+
with TabPane("Abstract Scenarios", id="abstract"):
|
|
105
|
+
self.abstract_predicates = PredicateList()
|
|
106
|
+
self.abstract_predicates.pred_list = self.tl_state.abstract()
|
|
107
|
+
yield self.abstract_predicates()
|
|
108
|
+
|
|
109
|
+
yield Footer()
|
|
110
|
+
|
|
111
|
+
# Initialize with first model
|
|
112
|
+
if len(self.tl_state.tfs()) > 0:
|
|
113
|
+
self.detail.tf = self.tl_state.tf_by_index(0)
|
|
114
|
+
|
|
115
|
+
def action_show_tab(self, tab: str) -> None:
|
|
116
|
+
"""Switch to a new tab."""
|
|
117
|
+
self.get_child_by_type(TabbedContent).active = tab
|
|
118
|
+
|
|
119
|
+
def on_list_view_highlighted(self, event: ListView.Highlighted) -> None:
|
|
120
|
+
""" """
|
|
121
|
+
self.detail.tf = self.tl_state.tf_by_filepath(event.item.label)
|
|
122
|
+
|
|
123
|
+
def on_list_view_selected(self, event: ListView.Selected) -> None:
|
|
124
|
+
""" """
|
|
125
|
+
self.detail.tf = self.tl_state.tf_by_index(event.index)
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
sample_state = "../data/broadridge/integration/split_tests/tf_state.json"
|
|
129
|
+
state = TLState.fromJSON(Path(sample_state).read_text())
|
|
130
|
+
app = TestLogicianApp(state)
|
|
131
|
+
app.run()
|
speclogician/utils/__init__.py
CHANGED
|
@@ -4,75 +4,6 @@
|
|
|
4
4
|
# utils/__init__.py
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import os
|
|
10
|
-
import json
|
|
11
|
-
import typer
|
|
12
|
-
from typing import Any, TypeAlias
|
|
13
|
-
from enum import Enum, StrEnum
|
|
14
7
|
from rich.console import Console
|
|
15
|
-
from rich.theme import Theme
|
|
16
|
-
|
|
17
|
-
IMANDRA_KEY_ENV = "IMANDRA_UNI_KEY"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
console = Console(
|
|
21
|
-
force_terminal=True,
|
|
22
|
-
color_system="truecolor",
|
|
23
|
-
theme=Theme({"info": "cyan", "ok": "green", "warn": "yellow", "err": "red"})
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
class IMLValidity (StrEnum):
|
|
27
|
-
VALID = 'valid'
|
|
28
|
-
INVALID = 'invalid'
|
|
29
|
-
UNKNOWN = 'unknown'
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
JSONScalar: TypeAlias = str | int | float | bool | None
|
|
33
|
-
JSONValue: TypeAlias = JSONScalar | list["JSONValue"] | dict[str, "JSONValue"]
|
|
34
|
-
JSONObject: TypeAlias = dict[str, JSONValue]
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def emit_json(obj: JSONObject) -> None:
|
|
38
|
-
"""
|
|
39
|
-
Print JSON (never exits).
|
|
40
|
-
Use this only at the CLI boundary or for debugging.
|
|
41
|
-
"""
|
|
42
|
-
typer.echo(json.dumps(obj, indent=2, default=_json_default))
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def _json_default(o: Any) -> Any:
|
|
46
|
-
# Enums (BaseStatus, PredicateType, etc)
|
|
47
|
-
if isinstance(o, Enum):
|
|
48
|
-
return o.value
|
|
49
|
-
# datetime, Path, etc (best-effort string)
|
|
50
|
-
try:
|
|
51
|
-
return str(o)
|
|
52
|
-
except Exception:
|
|
53
|
-
return repr(o)
|
|
54
|
-
|
|
55
|
-
def require_imandra_key() -> None:
|
|
56
|
-
"""
|
|
57
|
-
Ensure IMANDRA_UNI_KEY is present in the environment.
|
|
58
|
-
Exit the CLI with a clear message if not.
|
|
59
|
-
"""
|
|
60
|
-
key = os.getenv(IMANDRA_KEY_ENV)
|
|
61
|
-
|
|
62
|
-
if not key:
|
|
63
|
-
console.print(
|
|
64
|
-
"[red]✖ ImandraX API key missing[/red]\n\n"
|
|
65
|
-
"SpecLogician requires an Imandra Universe API key to run.\n\n"
|
|
66
|
-
"Please set the environment variable:\n\n"
|
|
67
|
-
f" export {IMANDRA_KEY_ENV}=<your-api-key>\n\n"
|
|
68
|
-
"You can obtain a key from Imandra Universe."
|
|
69
|
-
)
|
|
70
|
-
raise typer.Exit(code=2)
|
|
71
8
|
|
|
72
|
-
|
|
73
|
-
"require_imandra_key",
|
|
74
|
-
"emit_json",
|
|
75
|
-
"_json_default",
|
|
76
|
-
"IMLValidity",
|
|
77
|
-
"console"
|
|
78
|
-
]
|
|
9
|
+
console = Console()
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# imx.py
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import sys
|
|
9
|
+
from typing import Literal, TypedDict, assert_never
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
from imandrax_api_models import DecomposeRes, InstanceRes, VerifyRes
|
|
13
|
+
from imandrax_api_models.client import (
|
|
14
|
+
ImandraXAsyncClient,
|
|
15
|
+
get_imandrax_async_client,
|
|
16
|
+
get_imandrax_client,
|
|
17
|
+
)
|
|
18
|
+
from imandrax_api_models.context_utils import (
|
|
19
|
+
format_decomp_res,
|
|
20
|
+
format_eval_res,
|
|
21
|
+
#format_vg_res,
|
|
22
|
+
)
|
|
23
|
+
from iml_query.processing import (
|
|
24
|
+
extract_decomp_reqs,
|
|
25
|
+
extract_instance_reqs,
|
|
26
|
+
extract_verify_reqs,
|
|
27
|
+
)
|
|
28
|
+
from iml_query.processing.decomp import DecompReqArgs
|
|
29
|
+
from iml_query.tree_sitter_utils import get_parser
|
|
30
|
+
|
|
31
|
+
from enum import StrEnum
|
|
32
|
+
class IMX_Status(StrEnum):
|
|
33
|
+
"""
|
|
34
|
+
"""
|
|
35
|
+
UNKNOWN = 'Unkown'
|
|
36
|
+
ADMITTED = 'Admitted'
|
|
37
|
+
ERROR = 'Error'
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def check_model(model : str) -> bool:
|
|
41
|
+
"""
|
|
42
|
+
Check that the model is admitted to ImandraX
|
|
43
|
+
"""
|
|
44
|
+
c = get_imandrax_client()
|
|
45
|
+
eval_res = c.eval_model(src=model, with_vgs=False, with_decomps=False)
|
|
46
|
+
|
|
47
|
+
return format_eval_res(eval_res)
|
|
48
|
+
|
|
49
|
+
def run_eval(model : str, eval_str : str) -> str:
|
|
50
|
+
"""
|
|
51
|
+
Run evaluation
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
c = get_imandrax_client()
|
|
55
|
+
eval_res = c.eval_src(model)
|
|
56
|
+
|
|
57
|
+
return ""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class VGItem(TypedDict):
|
|
61
|
+
kind: Literal["verify", "instance"]
|
|
62
|
+
src: str
|
|
63
|
+
start_point: tuple[int, int]
|
|
64
|
+
end_point: tuple[int, int]
|
|
65
|
+
|
|
66
|
+
def _collect_vgs(iml: str) -> list[VGItem]:
|
|
67
|
+
tree = get_parser().parse(iml.encode("utf-8"))
|
|
68
|
+
iml, tree, verify_reqs, verify_req_ranges = extract_verify_reqs(iml, tree)
|
|
69
|
+
iml, tree, instance_reqs, instance_req_ranges = extract_instance_reqs(iml, tree)
|
|
70
|
+
|
|
71
|
+
# Collect
|
|
72
|
+
vg_items: list[VGItem] = []
|
|
73
|
+
for req, req_range in zip(verify_reqs, verify_req_ranges, strict=True):
|
|
74
|
+
vg_items.append(
|
|
75
|
+
{
|
|
76
|
+
"kind": "verify",
|
|
77
|
+
"src": req["src"],
|
|
78
|
+
"start_point": (req_range.start_point[0], req_range.start_point[1]),
|
|
79
|
+
"end_point": (req_range.end_point[0], req_range.end_point[1]),
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
for req, req_range in zip(instance_reqs, instance_req_ranges, strict=False):
|
|
83
|
+
vg_items.append(
|
|
84
|
+
{
|
|
85
|
+
"kind": "instance",
|
|
86
|
+
"src": req["src"],
|
|
87
|
+
"start_point": (req_range.start_point[0], req_range.start_point[1]),
|
|
88
|
+
"end_point": (req_range.end_point[0], req_range.end_point[1]),
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
vg_items.sort(key=lambda x: x["start_point"])
|
|
92
|
+
return vg_items
|
|
93
|
+
|
|
94
|
+
def check_instance (model : str, instance_query : str) -> bool:
|
|
95
|
+
"""
|
|
96
|
+
Run the instance request and return True/False if the instance is Sat
|
|
97
|
+
"""
|
|
98
|
+
async def _async_check_vg() -> list[VerifyRes | InstanceRes]:
|
|
99
|
+
iml = model + "\n" + instance_query
|
|
100
|
+
vgs = _collect_vgs(iml)
|
|
101
|
+
|
|
102
|
+
vg_with_idx: list[tuple[int, VGItem]] = [
|
|
103
|
+
(i, vg) for (i, vg) in enumerate(vgs, 1)
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
async def _check_vg(
|
|
107
|
+
vg: VGItem,
|
|
108
|
+
i: int,
|
|
109
|
+
c: ImandraXAsyncClient,
|
|
110
|
+
) -> VerifyRes | InstanceRes:
|
|
111
|
+
match vg["kind"]:
|
|
112
|
+
case "verify":
|
|
113
|
+
res = await c.verify_src(src=vg["src"])
|
|
114
|
+
case "instance":
|
|
115
|
+
res = await c.instance_src(src=vg["src"])
|
|
116
|
+
case _:
|
|
117
|
+
assert_never(vg["kind"])
|
|
118
|
+
#print(f"{i}: {vg['kind']} ({vg['src']})")
|
|
119
|
+
#print(format_vg_res(res))
|
|
120
|
+
return res
|
|
121
|
+
|
|
122
|
+
async with get_imandrax_async_client() as c:
|
|
123
|
+
eval_res = await c.eval_model(src=iml)
|
|
124
|
+
#print(format_eval_res(eval_res, iml))
|
|
125
|
+
if eval_res.has_errors:
|
|
126
|
+
print("Error(s) found in IML file. Exiting.")
|
|
127
|
+
sys.exit(1)
|
|
128
|
+
return
|
|
129
|
+
#print("\n" + "=" * 5 + "VG" + "=" * 5 + "\n")
|
|
130
|
+
tasks = [_check_vg(vg, i, c) for (i, vg) in vg_with_idx]
|
|
131
|
+
return await asyncio.gather(*tasks)
|
|
132
|
+
|
|
133
|
+
vg_res_list = asyncio.run(_async_check_vg())
|
|
134
|
+
|
|
135
|
+
if len(vg_res_list) == 0:
|
|
136
|
+
raise Exception(f"Failed to get response")
|
|
137
|
+
|
|
138
|
+
return vg_res_list[0].res_type == 'sat'
|
|
139
|
+
|
|
140
|
+
class DecompItem(TypedDict):
|
|
141
|
+
req_args: DecompReqArgs
|
|
142
|
+
start_point: tuple[int, int]
|
|
143
|
+
end_point: tuple[int, int]
|
|
144
|
+
|
|
145
|
+
def _collect_decomps(iml: str) -> list[DecompItem]:
|
|
146
|
+
tree = get_parser().parse(iml.encode("utf-8"))
|
|
147
|
+
iml, tree, decomp_reqs, ranges = extract_decomp_reqs(iml, tree)
|
|
148
|
+
|
|
149
|
+
decomp_items: list[DecompItem] = [
|
|
150
|
+
DecompItem(
|
|
151
|
+
req_args=req,
|
|
152
|
+
start_point=range_.start_point,
|
|
153
|
+
end_point=range_.end_point,
|
|
154
|
+
)
|
|
155
|
+
for req, range_ in zip(decomp_reqs, ranges, strict=True)
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
decomp_items.sort(key=lambda x: x["start_point"])
|
|
159
|
+
return decomp_items
|
|
160
|
+
|
|
161
|
+
def run_decomp(model : str, decomp_request : str):
|
|
162
|
+
"""
|
|
163
|
+
Run the decomp request
|
|
164
|
+
"""
|
|
165
|
+
async def _async_check_decomp() -> list[DecomposeRes]:
|
|
166
|
+
iml = model + "\n" + decomp_request
|
|
167
|
+
decomps = _collect_decomps(iml)
|
|
168
|
+
|
|
169
|
+
decomp_with_idx: list[tuple[int, DecompItem]] = [
|
|
170
|
+
(i, decomp) for (i, decomp) in enumerate(decomps, 1)
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
async def _check_decomp(
|
|
174
|
+
decomp: DecompItem, i: int, c: ImandraXAsyncClient
|
|
175
|
+
) -> DecomposeRes:
|
|
176
|
+
#print(f"{i}: decompose {decomp['req_args']['name']}")
|
|
177
|
+
res = await c.decompose(**decomp["req_args"])
|
|
178
|
+
#print(format_decomp_res(res))
|
|
179
|
+
return res
|
|
180
|
+
|
|
181
|
+
async with get_imandrax_async_client() as c:
|
|
182
|
+
eval_res = await c.eval_model(src=iml)
|
|
183
|
+
#print(format_eval_res(eval_res, iml))
|
|
184
|
+
if eval_res.has_errors:
|
|
185
|
+
typer.echo("Error(s) found in IML file. Exiting.")
|
|
186
|
+
sys.exit(1)
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
#print("\n" + "=" * 5 + "Decomp" + "=" * 5 + "\n")
|
|
190
|
+
tasks = [_check_decomp(decomp, i, c) for (i, decomp) in decomp_with_idx]
|
|
191
|
+
return await asyncio.gather(*tasks)
|
|
192
|
+
|
|
193
|
+
decomp_res_list = asyncio.run(_async_check_decomp())
|
|
194
|
+
|
|
195
|
+
return decomp_res_list[0]
|
speclogician/utils/load.py
CHANGED
|
@@ -1,166 +1,44 @@
|
|
|
1
1
|
#
|
|
2
2
|
# Imandra Inc.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
4
|
+
# load.py
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
-
import json
|
|
8
7
|
import os
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import typer
|
|
12
|
-
from typing import NoReturn
|
|
8
|
+
import sys
|
|
13
9
|
from rich.prompt import Prompt
|
|
10
|
+
from pathlib import Path
|
|
14
11
|
|
|
15
12
|
from ..state.state import State
|
|
13
|
+
from ..llms.overlay import Overlays
|
|
16
14
|
from .__init__ import console
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
raise typer.Exit(code=exit_code)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def load_state(path: Path | None = None, json: bool = False) -> State:
|
|
26
|
-
"""
|
|
27
|
-
Load a State either from an explicit JSON path or from the local directory.
|
|
28
|
-
|
|
29
|
-
- If `path` is provided:
|
|
30
|
-
* if it exists: load JSON from it
|
|
31
|
-
* if it doesn't exist: ask to create it (unless json)
|
|
32
|
-
- If `path` is None:
|
|
33
|
-
* load from current directory via State.from_dir()
|
|
34
|
-
* if missing: ask to create it (unless json)
|
|
35
|
-
|
|
36
|
-
If json=True:
|
|
37
|
-
- emit JSON on success/error and Exit (no prompts).
|
|
38
|
-
"""
|
|
39
|
-
cwd = Path(os.getcwd())
|
|
40
|
-
|
|
41
|
-
def create_new_state(target: Path | None) -> State:
|
|
42
|
-
"""
|
|
43
|
-
Create a new state and persist it.
|
|
44
|
-
NOTE: Your State.save() appears to accept a directory path; we use:
|
|
45
|
-
- cwd when target is None
|
|
46
|
-
- target.parent when target is a file path
|
|
47
|
-
"""
|
|
48
|
-
state = State()
|
|
49
|
-
|
|
50
|
-
if target is None:
|
|
51
|
-
state.save(str(cwd))
|
|
52
|
-
return state
|
|
53
|
-
|
|
54
|
-
target = target.expanduser()
|
|
55
|
-
target.parent.mkdir(parents=True, exist_ok=True)
|
|
56
|
-
state.save(str(target.parent))
|
|
57
|
-
return state
|
|
58
|
-
|
|
59
|
-
# -------------------------
|
|
60
|
-
# Case 1: explicit file path
|
|
61
|
-
# -------------------------
|
|
62
|
-
if path is not None:
|
|
63
|
-
path = path.expanduser()
|
|
64
|
-
|
|
65
|
-
if path.exists():
|
|
66
|
-
try:
|
|
67
|
-
if path.is_dir():
|
|
68
|
-
new_state = State.from_dir(dirpath=path)
|
|
69
|
-
else:
|
|
70
|
-
new_state = State.from_json(path.read_text())
|
|
71
|
-
except Exception as e:
|
|
72
|
-
if json:
|
|
73
|
-
_emit_json(
|
|
74
|
-
{
|
|
75
|
-
"ok": False,
|
|
76
|
-
"error": "failed_to_load_state_json",
|
|
77
|
-
"path": str(path),
|
|
78
|
-
"message": str(e),
|
|
79
|
-
},
|
|
80
|
-
exit_code=2,
|
|
81
|
-
)
|
|
82
|
-
console.print(f"[red]Failed to load state JSON:[/] {path}\n{e}")
|
|
83
|
-
raise
|
|
84
|
-
|
|
85
|
-
if json:
|
|
86
|
-
_emit_json(
|
|
87
|
-
{"ok": True, "source": "state_json", "path": str(path)},
|
|
88
|
-
exit_code=0,
|
|
89
|
-
)
|
|
90
|
-
return new_state
|
|
91
|
-
|
|
92
|
-
# File doesn't exist
|
|
93
|
-
if json:
|
|
94
|
-
_emit_json(
|
|
95
|
-
{
|
|
96
|
-
"ok": False,
|
|
97
|
-
"error": "state_json_not_found",
|
|
98
|
-
"path": str(path),
|
|
99
|
-
"message": "State JSON file does not exist.",
|
|
100
|
-
},
|
|
101
|
-
exit_code=2,
|
|
102
|
-
)
|
|
103
|
-
|
|
16
|
+
def load_state():
|
|
17
|
+
""" """
|
|
18
|
+
new_state = State.from_dir(dirpath=os.getcwd())
|
|
19
|
+
if new_state is None:
|
|
104
20
|
answer = Prompt.ask(
|
|
105
|
-
prompt=
|
|
106
|
-
choices=["Yes", "No"],
|
|
21
|
+
prompt="⚠️ Couldnt find a state in current directory. Create a new one?",
|
|
22
|
+
choices = ["Yes", "No"],
|
|
107
23
|
console=console,
|
|
108
24
|
case_sensitive=False,
|
|
109
|
-
default="Yes"
|
|
110
|
-
|
|
111
|
-
if answer.lower() == "no":
|
|
112
|
-
console.print("Goodbye!")
|
|
113
|
-
raise typer.Exit(0)
|
|
25
|
+
default="Yes"
|
|
26
|
+
)
|
|
114
27
|
|
|
28
|
+
if answer == 'No':
|
|
29
|
+
console.print("Goodbye!")
|
|
30
|
+
sys.exit(0)
|
|
31
|
+
|
|
115
32
|
console.print("Creating a new state!")
|
|
116
|
-
return create_new_state(path)
|
|
117
33
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
except Exception as e:
|
|
124
|
-
if json:
|
|
125
|
-
_emit_json(
|
|
126
|
-
{
|
|
127
|
-
"ok": False,
|
|
128
|
-
"error": "failed_to_load_state_from_dir",
|
|
129
|
-
"dir": str(cwd),
|
|
130
|
-
"message": str(e),
|
|
131
|
-
},
|
|
132
|
-
exit_code=2,
|
|
133
|
-
)
|
|
134
|
-
console.print(f"[red]Failed to load state from directory:[/] {cwd}\n{e}")
|
|
135
|
-
raise
|
|
136
|
-
|
|
137
|
-
if new_state is not None:
|
|
138
|
-
if json:
|
|
139
|
-
_emit_json({"ok": True, "source": "dir", "dir": str(cwd)}, exit_code=0)
|
|
34
|
+
state = State()
|
|
35
|
+
state.save(os.getcwd())
|
|
36
|
+
return state
|
|
37
|
+
|
|
38
|
+
else:
|
|
140
39
|
return new_state
|
|
141
40
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
"ok": False,
|
|
147
|
-
"error": "state_not_found_in_dir",
|
|
148
|
-
"dir": str(cwd),
|
|
149
|
-
"message": "No state found in current directory.",
|
|
150
|
-
},
|
|
151
|
-
exit_code=2,
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
answer = Prompt.ask(
|
|
155
|
-
prompt="⚠️ Couldn't find a state in current directory. Create a new one?",
|
|
156
|
-
choices=["Yes", "No"],
|
|
157
|
-
console=console,
|
|
158
|
-
case_sensitive=False,
|
|
159
|
-
default="Yes",
|
|
160
|
-
)
|
|
161
|
-
if answer.lower() == "no":
|
|
162
|
-
console.print("Goodbye!")
|
|
163
|
-
raise typer.Exit(0)
|
|
164
|
-
|
|
165
|
-
console.print("Creating a new state!")
|
|
166
|
-
return create_new_state(None)
|
|
41
|
+
def load_overlays():
|
|
42
|
+
"""
|
|
43
|
+
"""
|
|
44
|
+
return Overlays.from_dir(str(Path(os.path.abspath(__file__)).parent / "../../overlays"))
|