Skip to content

Commit e6a0d1d

Browse files
authored
Refactor rule parsing (#4699)
* Remove unnecessary cases * Make pattern matching logic more concise * Process three cases of simplification rules: `AppRule`, `CeilRule`, `EqualsRule`
1 parent 19a16cd commit e6a0d1d

File tree

2 files changed

+160
-116
lines changed

2 files changed

+160
-116
lines changed

pyk/src/pyk/kore/rule.py

Lines changed: 156 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,44 @@
1-
"""Parse KORE axioms into rewrite rules.
2-
3-
Based on the [LLVM Backend's implementation](https://github.com/runtimeverification/llvm-backend/blob/d5eab4b0f0e610bc60843ebb482f79c043b92702/lib/ast/pattern_matching.cpp).
4-
"""
5-
61
from __future__ import annotations
72

3+
import logging
84
from abc import ABC
95
from dataclasses import dataclass
10-
from typing import TYPE_CHECKING, final
6+
from typing import TYPE_CHECKING, Generic, TypeVar, final
117

128
from .prelude import inj
13-
from .syntax import And, App, Axiom, Ceil, Equals, EVar, Implies, In, Not, Rewrites, SortVar, String, Top
9+
from .syntax import (
10+
DV,
11+
And,
12+
App,
13+
Axiom,
14+
Ceil,
15+
Equals,
16+
EVar,
17+
Implies,
18+
In,
19+
Not,
20+
Pattern,
21+
Rewrites,
22+
SortApp,
23+
SortVar,
24+
String,
25+
Top,
26+
)
1427

1528
if TYPE_CHECKING:
1629
from typing import Final
1730

18-
from .syntax import Definition, Pattern
31+
from .syntax import Definition
1932

2033
Attrs = dict[str, tuple[Pattern, ...]]
2134

2235

36+
P = TypeVar('P', bound=Pattern)
37+
38+
39+
_LOGGER: Final = logging.getLogger(__name__)
40+
41+
2342
# There's a simplification rule with irregular form in the prelude module INJ.
2443
# This rule is skipped in Rule.extract_all.
2544
_S1, _S2, _S3, _R = (SortVar(name) for name in ['S1', 'S2', 'S3', 'R'])
@@ -56,33 +75,35 @@ def from_axiom(axiom: Axiom) -> Rule:
5675
if isinstance(axiom.pattern, Rewrites):
5776
return RewriteRule.from_axiom(axiom)
5877

59-
if 'simplification' in axiom.attrs_by_key:
60-
return SimpliRule.from_axiom(axiom)
78+
if 'simplification' not in axiom.attrs_by_key:
79+
return FunctionRule.from_axiom(axiom)
6180

62-
return FunctionRule.from_axiom(axiom)
81+
match axiom.pattern:
82+
case Implies(right=Equals(left=App())):
83+
return AppRule.from_axiom(axiom)
84+
case Implies(right=Equals(left=Ceil())):
85+
return CeilRule.from_axiom(axiom)
86+
case Implies(right=Equals(left=Equals())):
87+
return EqualsRule.from_axiom(axiom)
88+
case _:
89+
raise ValueError(f'Cannot parse simplification rule: {axiom.text}')
6390

6491
@staticmethod
6592
def extract_all(defn: Definition) -> list[Rule]:
66-
return [Rule.from_axiom(axiom) for axiom in defn.axioms if Rule._is_rule(axiom)]
67-
68-
@staticmethod
69-
def _is_rule(axiom: Axiom) -> bool:
70-
if axiom == _INJ_AXIOM:
71-
return False
72-
73-
if any(attr in axiom.attrs_by_key for attr in _SKIPPED_ATTRS):
74-
return False
93+
def is_rule(axiom: Axiom) -> bool:
94+
if axiom == _INJ_AXIOM:
95+
return False
7596

76-
match axiom.pattern:
77-
case Implies(right=Equals(left=Ceil())):
78-
# Ceil rule
97+
if any(attr in axiom.attrs_by_key for attr in _SKIPPED_ATTRS):
7998
return False
8099

81-
return True
100+
return True
101+
102+
return [Rule.from_axiom(axiom) for axiom in defn.axioms if is_rule(axiom)]
82103

83104

84105
@final
85-
@dataclass
106+
@dataclass(frozen=True)
86107
class RewriteRule(Rule):
87108
lhs: App
88109
rhs: App
@@ -95,8 +116,7 @@ class RewriteRule(Rule):
95116

96117
@staticmethod
97118
def from_axiom(axiom: Axiom) -> RewriteRule:
98-
lhs, req, ctx = RewriteRule._extract_lhs(axiom)
99-
rhs, ens = RewriteRule._extract_rhs(axiom)
119+
lhs, rhs, req, ens, ctx = RewriteRule._extract(axiom)
100120
priority = _extract_priority(axiom)
101121
uid = _extract_uid(axiom)
102122
label = _extract_label(axiom)
@@ -112,51 +132,35 @@ def from_axiom(axiom: Axiom) -> RewriteRule:
112132
)
113133

114134
@staticmethod
115-
def _extract_lhs(axiom: Axiom) -> tuple[App, Pattern | None, EVar | None]:
116-
req: Pattern | None = None
117-
# Cases 0-5 of get_left_hand_side
118-
# Cases 5-10 of get_requires
135+
def _extract(axiom: Axiom) -> tuple[App, App, Pattern | None, Pattern | None, EVar | None]:
119136
match axiom.pattern:
120-
case Rewrites(left=And(ops=(Top(), lhs))):
121-
pass
122-
case Rewrites(left=And(ops=(Equals(left=req), lhs))):
123-
pass
124-
case Rewrites(left=And(ops=(lhs, Top()))):
125-
pass
126-
case Rewrites(left=And(ops=(lhs, Equals(left=req)))):
127-
pass
128-
case Rewrites(left=And(ops=(Not(), And(ops=(Top(), lhs))))):
129-
pass
130-
case Rewrites(left=And(ops=(Not(), And(ops=(Equals(left=req), lhs))))):
137+
case Rewrites(left=And(ops=(_lhs, _req)), right=_rhs):
131138
pass
132139
case _:
133-
raise ValueError(f'Cannot extract LHS from axiom: {axiom.text}')
140+
raise ValueError(f'Cannot extract rewrite rule from axiom: {axiom.text}')
134141

135142
ctx: EVar | None = None
136-
match lhs:
137-
case App("Lbl'-LT-'generatedTop'-GT-'") as app:
143+
match _lhs:
144+
case App("Lbl'-LT-'generatedTop'-GT-'") as lhs:
138145
pass
139-
case And(_, (App("Lbl'-LT-'generatedTop'-GT-'") as app, EVar("Var'Hash'Configuration") as ctx)):
146+
case And(_, (App("Lbl'-LT-'generatedTop'-GT-'") as lhs, EVar("Var'Hash'Configuration") as ctx)):
140147
pass
148+
case _:
149+
raise ValueError(f'Cannot extract LHS configuration from axiom: {axiom.text}')
141150

142-
return app, req, ctx
143-
144-
@staticmethod
145-
def _extract_rhs(axiom: Axiom) -> tuple[App, Pattern | None]:
146-
# Case 2 of get_right_hand_side:
147-
# 2: rhs(\rewrites(_, \and(X, Y))) = get_builtin(\and(X, Y))
148-
# Below is a special case without get_builtin
149-
match axiom.pattern:
150-
case Rewrites(right=And(ops=(App("Lbl'-LT-'generatedTop'-GT-'") as rhs, Top() | Equals() as _ens))):
151+
req = _extract_condition(_req)
152+
rhs, ens = _extract_rhs(_rhs)
153+
match rhs:
154+
case App("Lbl'-LT-'generatedTop'-GT-'"):
151155
pass
152156
case _:
153-
raise ValueError(f'Cannot extract RHS from axiom: {axiom.text}')
154-
ens = _extract_ensures(_ens)
155-
return rhs, ens
157+
raise ValueError(f'Cannot extract RHS configuration from axiom: {axiom.text}')
158+
159+
return lhs, rhs, req, ens, ctx
156160

157161

158162
@final
159-
@dataclass
163+
@dataclass(frozen=True)
160164
class FunctionRule(Rule):
161165
lhs: App
162166
rhs: Pattern
@@ -166,9 +170,7 @@ class FunctionRule(Rule):
166170

167171
@staticmethod
168172
def from_axiom(axiom: Axiom) -> FunctionRule:
169-
args, req = FunctionRule._extract_args(axiom)
170-
app, rhs, ens = FunctionRule._extract_rhs(axiom)
171-
lhs = app.let(args=args)
173+
lhs, rhs, req, ens = FunctionRule._extract(axiom)
172174
priority = _extract_priority(axiom)
173175
return FunctionRule(
174176
lhs=lhs,
@@ -179,93 +181,133 @@ def from_axiom(axiom: Axiom) -> FunctionRule:
179181
)
180182

181183
@staticmethod
182-
def _extract_args(axiom: Axiom) -> tuple[tuple[Pattern, ...], Pattern | None]:
183-
req: Pattern | None = None
184-
# Cases 7-10 of get_left_hand_side
185-
# Cases 0-3 of get_requires
184+
def _extract(axiom: Axiom) -> tuple[App, Pattern, Pattern | None, Pattern | None]:
186185
match axiom.pattern:
187-
case Implies(left=And(ops=(Top(), pat))):
188-
return FunctionRule._get_patterns(pat), req
189-
case Implies(left=And(ops=(Equals(left=req), pat))):
190-
return FunctionRule._get_patterns(pat), req
191-
case Implies(left=And(ops=(Not(), And(ops=(Top(), pat))))):
192-
return FunctionRule._get_patterns(pat), req
193-
case Implies(left=And(ops=(Not(), And(ops=(Equals(left=req), pat))))):
194-
return FunctionRule._get_patterns(pat), req
186+
case Implies(
187+
left=And(
188+
ops=(Not(), And(ops=(_req, _args))) | (_req, _args),
189+
),
190+
right=Equals(left=App() as app, right=_rhs),
191+
):
192+
args = FunctionRule._extract_args(_args)
193+
lhs = app.let(args=args)
194+
req = _extract_condition(_req)
195+
rhs, ens = _extract_rhs(_rhs)
196+
return lhs, rhs, req, ens
195197
case _:
196-
raise ValueError(f'Cannot extract LHS from axiom: {axiom.text}')
198+
raise ValueError(f'Cannot extract function rule from axiom: {axiom.text}')
197199

198200
@staticmethod
199-
def _get_patterns(pattern: Pattern) -> tuple[Pattern, ...]:
200-
# get_patterns(\top()) = []
201-
# get_patterns(\and(\in(_, X), Y)) = X : get_patterns(Y)
201+
def _extract_args(pattern: Pattern) -> tuple[Pattern, ...]:
202202
match pattern:
203203
case Top():
204204
return ()
205-
case And(ops=(In(right=x), y)):
206-
return (x,) + FunctionRule._get_patterns(y)
205+
case And(ops=(In(left=EVar(), right=arg), rest)):
206+
return (arg,) + FunctionRule._extract_args(rest)
207207
case _:
208-
raise AssertionError()
208+
raise ValueError(f'Cannot extract argument list from pattern: {pattern.text}')
209+
210+
211+
class SimpliRule(Rule, Generic[P], ABC):
212+
lhs: P
209213

210214
@staticmethod
211-
def _extract_rhs(axiom: Axiom) -> tuple[App, Pattern, Pattern | None]:
212-
# Case 0 of get_right_hand_side
215+
def _extract(axiom: Axiom, lhs_type: type[P]) -> tuple[P, Pattern, Pattern | None, Pattern | None]:
213216
match axiom.pattern:
214-
case Implies(right=Equals(left=App() as app, right=And(ops=(rhs, Top() | Equals() as _ens)))):
215-
pass
217+
case Implies(left=_req, right=Equals(left=lhs, right=_rhs)):
218+
req = _extract_condition(_req)
219+
rhs, ens = _extract_rhs(_rhs)
220+
if not isinstance(lhs, lhs_type):
221+
raise ValueError(f'Invalid LHS type from simplification axiom: {axiom.text}')
222+
return lhs, rhs, req, ens
216223
case _:
217-
raise ValueError(f'Cannot extract RHS from axiom: {axiom.text}')
218-
ens = _extract_ensures(_ens)
219-
return app, rhs, ens
224+
raise ValueError(f'Cannot extract simplification rule from axiom: {axiom.text}')
220225

221226

222227
@final
223-
@dataclass
224-
class SimpliRule(Rule):
225-
lhs: Pattern
228+
@dataclass(frozen=True)
229+
class AppRule(SimpliRule[App]):
230+
lhs: App
231+
rhs: Pattern
232+
req: Pattern | None
233+
ens: Pattern | None
234+
priority: int
235+
236+
@staticmethod
237+
def from_axiom(axiom: Axiom) -> AppRule:
238+
lhs, rhs, req, ens = SimpliRule._extract(axiom, App)
239+
priority = _extract_priority(axiom)
240+
return AppRule(
241+
lhs=lhs,
242+
rhs=rhs,
243+
req=req,
244+
ens=ens,
245+
priority=priority,
246+
)
247+
248+
249+
@final
250+
@dataclass(frozen=True)
251+
class CeilRule(SimpliRule):
252+
lhs: Ceil
226253
rhs: Pattern
227254
req: Pattern | None
228255
ens: Pattern | None
229256
priority: int
230257

231258
@staticmethod
232-
def from_axiom(axiom: Axiom) -> SimpliRule:
233-
lhs, rhs, req, ens = SimpliRule._extract(axiom)
259+
def from_axiom(axiom: Axiom) -> CeilRule:
260+
lhs, rhs, req, ens = SimpliRule._extract(axiom, Ceil)
234261
priority = _extract_priority(axiom)
235-
return SimpliRule(
262+
return CeilRule(
236263
lhs=lhs,
237264
rhs=rhs,
238265
req=req,
239266
ens=ens,
240267
priority=priority,
241268
)
242269

270+
271+
@final
272+
@dataclass(frozen=True)
273+
class EqualsRule(SimpliRule):
274+
lhs: Equals
275+
rhs: Pattern
276+
req: Pattern | None
277+
ens: Pattern | None
278+
priority: int
279+
243280
@staticmethod
244-
def _extract(axiom: Axiom) -> tuple[Pattern, Pattern, Pattern | None, Pattern | None]:
245-
req: Pattern | None = None
246-
# Cases 11-12 of get_left_hand_side
247-
# Case 0 of get_right_hand_side
248-
match axiom.pattern:
249-
case Implies(left=Top(), right=Equals(left=lhs, right=And(ops=(rhs, Top() | Equals() as _ens)))):
250-
pass
251-
case Implies(left=Equals(left=req), right=Equals(left=lhs, right=And(ops=(rhs, Top() | Equals() as _ens)))):
252-
pass
253-
case Implies(right=Equals(left=Ceil())):
254-
raise ValueError(f'Axiom is a ceil rule: {axiom.text}')
255-
case _:
256-
raise ValueError(f'Cannot extract simplification rule from axiom: {axiom.text}')
257-
ens = _extract_ensures(_ens)
258-
return lhs, rhs, req, ens
281+
def from_axiom(axiom: Axiom) -> EqualsRule:
282+
lhs, rhs, req, ens = SimpliRule._extract(axiom, Equals)
283+
if not isinstance(lhs, Equals):
284+
raise ValueError(f'Cannot extract LHS as Equals from axiom: {axiom.text}')
285+
priority = _extract_priority(axiom)
286+
return EqualsRule(
287+
lhs=lhs,
288+
rhs=rhs,
289+
req=req,
290+
ens=ens,
291+
priority=priority,
292+
)
293+
294+
295+
def _extract_rhs(pattern: Pattern) -> tuple[Pattern, Pattern | None]:
296+
match pattern:
297+
case And(ops=(rhs, _ens)):
298+
return rhs, _extract_condition(_ens)
299+
case _:
300+
raise ValueError(f'Cannot extract RHS from pattern: {pattern.text}')
259301

260302

261-
def _extract_ensures(ens: Top | Equals | None) -> Pattern | None:
262-
match ens:
303+
def _extract_condition(pattern: Pattern) -> Pattern | None:
304+
match pattern:
263305
case Top():
264306
return None
265-
case Equals(left=res):
266-
return res
307+
case Equals(left=cond, right=DV(SortApp('SortBool'), String('true'))):
308+
return cond
267309
case _:
268-
raise AssertionError()
310+
raise ValueError(f'Cannot extract condition from pattern: {pattern.text}')
269311

270312

271313
def _extract_uid(axiom: Axiom) -> str:

pyk/src/tests/integration/kore/test_rule.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
@pytest.fixture(scope='module')
1919
def definition(kompile: Kompiler) -> Definition:
2020
main_file = K_FILES / 'imp.k'
21-
definition_dir = kompile(main_file=main_file)
21+
definition_dir = kompile(main_file=main_file, backend='haskell')
2222
kore_file = definition_dir / 'definition.kore'
2323
kore_text = kore_file.read_text()
2424
definition = KoreParser(kore_text).definition()
@@ -33,4 +33,6 @@ def test_extract_all(definition: Definition) -> None:
3333
cnt = Counter(type(rule).__name__ for rule in rules)
3434
assert cnt['RewriteRule']
3535
assert cnt['FunctionRule']
36-
assert cnt['SimpliRule']
36+
assert cnt['AppRule']
37+
assert cnt['CeilRule']
38+
assert cnt['EqualsRule']

0 commit comments

Comments
 (0)