Skip to content

Commit 0734e45

Browse files
authored
Factor out CTermShow class for simpler node printing (#4808)
~Blocked on: #4819 This is a refactoring to how our display of nodes/KCFGs works, ideally to make it easier to thread options through to them. We're specifically trying to capture all the common options for how `CTerm` should be displayed in one place, and remove those options from other places. This came about as I was trying to implement some asks from the KMIR team on changing the display in the TUI viewer, and realized that currently all the options are threaded through in different ways and controlled at different points. This is a breaking change in pyk API, and so corresponding changes into downstream repos will need to be added to the update-deps PRs. Another big change that happens here: the `kcfg` directory no longer needs a `KPrint` because we use a `kast.pretty.PrettyPrinter` directly instead. Before, it was using `KPrint.pretty_print`, which was just constructing a `PrettyPrinter` then using it. So we skip the intermediate class. That means that several classes which took in a `KPrint` now just take in a `KDefinition`. Changes in particular: - A new class `CTermShow` is introduced, which enables printing out `CTerm` using a `PrettyPrinter`, via `CTermShow.show_config`, `CTermShow.show_constraints` and `CTermShow.show`. It takes several options controlling how printing can occur, including: - `extra_unparsing_modules`, `patch_symbol_table`, `unalias`, and `sort_ac_collections`: all the same as `PrettyPrinter`. - `minimize`: collapse unused cell-variables into `...`, and the recursively collapse the `...` as much as possible in the configuration. - `break_cell_collections`: enable line-breaking collections (`List`, `Set`, `Map`) which are stored directly in cells, as usually you want one item printer per line in this case. - `omit_labels`: a set of labels to always replace occurrences of with `...`, so they do not get printed. - `NodePrinter` (and `APRProofNodePrinter`) now take a `CTermShow` instead of a `KPrint`, which also means they don't take the options controlling printing directly (like `minimize`). These options must be set when building teh `CTermShow` that is passed in for the `NodePrinter` to use. - Similarly, `KCFGShow` (and `APRProofShow`) now take a `KDefinition` instead of a `KPrint` and build a `PrettyPrinter` directly. This means that the `NodePrinter` (and thus `CTermShow`) is responsible for setting options related to node printing like `sort_collections` ahead of time. - The `KCFGViewer` (and `APRProofViewer`) are now taking directly a `KDefinition` instead of a `KPrint`, and similar to the `*Show` counterparts offloading decisions about how to print items to how the `CTermShow` is setup in the client application. - Tests are updated with the new pattern (of constructing a `CTermShow` ahead of time and passing it in), which provides an example of what's needed downstream. In particular, before we were mocking an entire `KPrint` in the unit tests, but now we only need to mock a `CTermShow`, which is much more compact. We rename the file from `mock_kprint.py` to `mock.py`, so it can be for all mocks.
1 parent 9b9b5d5 commit 0734e45

15 files changed

+261
-106
lines changed

pyk/src/pyk/__main__.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from .cli.utils import LOG_FORMAT, loglevel
1717
from .coverage import get_rule_by_id, strip_coverage_logger
1818
from .cterm import CTerm
19+
from .cterm.show import CTermShow
1920
from .cterm.symbolic import cterm_symbolic
2021
from .kast import KAst
2122
from .kast.att import KAtt
@@ -275,8 +276,10 @@ def explore_context() -> Iterator[KCFGExplore]:
275276
if type(failure_info) is APRFailureInfo:
276277
print('\n'.join(failure_info.print()))
277278
if options.show_kcfg and type(proof) is APRProof:
278-
node_printer = APRProofNodePrinter(proof, kprove, full_printer=True, minimize=False)
279-
show = APRProofShow(kprove, node_printer=node_printer)
279+
node_printer = APRProofNodePrinter(
280+
proof, CTermShow(PrettyPrinter(kprove.definition).print, minimize=False), full_printer=True
281+
)
282+
show = APRProofShow(kprove.definition, node_printer=node_printer)
280283
print('\n'.join(show.show(proof, minimize=False)))
281284
sys.exit(len([p.id for p in proofs if not p.passed]))
282285

pyk/src/pyk/cterm/show.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
from __future__ import annotations
2+
3+
import logging
4+
from dataclasses import dataclass
5+
from typing import TYPE_CHECKING
6+
7+
from ..kast.inner import KApply, KSort, KToken, flatten_label, top_down
8+
from ..kast.manip import free_vars, minimize_term
9+
from ..kast.prelude.k import DOTS
10+
from .cterm import CTerm
11+
12+
if TYPE_CHECKING:
13+
from collections.abc import Callable, Iterable
14+
from typing import Final
15+
16+
from ..kast.inner import KInner
17+
18+
_LOGGER: Final = logging.getLogger(__name__)
19+
20+
21+
@dataclass
22+
class CTermShow:
23+
_printer: Callable[[KInner], str]
24+
_minimize: bool
25+
_break_cell_collections: bool
26+
_omit_labels: tuple[str, ...]
27+
28+
def __init__(
29+
self,
30+
printer: Callable[[KInner], str],
31+
minimize: bool = True,
32+
break_cell_collections: bool = True,
33+
omit_labels: Iterable[str] = (),
34+
):
35+
self._printer = printer
36+
self._minimize = minimize
37+
self._break_cell_collections = break_cell_collections
38+
self._omit_labels = tuple(omit_labels)
39+
40+
def print_lines(self, kast: KInner) -> list[str]:
41+
return self._printer(kast).split('\n')
42+
43+
def let(
44+
self,
45+
minimize: bool | None = None,
46+
break_cell_collections: bool | None = None,
47+
omit_labels: Iterable[str] | None = None,
48+
) -> CTermShow:
49+
return CTermShow(
50+
self._printer,
51+
minimize=(self._minimize if minimize is None else minimize),
52+
break_cell_collections=(
53+
self._break_cell_collections if break_cell_collections is None else break_cell_collections
54+
),
55+
omit_labels=(self._omit_labels if omit_labels is None else omit_labels),
56+
)
57+
58+
def show(self, cterm: CTerm) -> list[str]:
59+
ret_strs: list[str] = []
60+
ret_strs.extend(self.show_config(cterm))
61+
ret_strs.extend(self.show_constraints(cterm))
62+
return ret_strs
63+
64+
def show_config(self, cterm: CTerm) -> list[str]:
65+
if self._break_cell_collections:
66+
cterm = CTerm(top_down(self._break_cells_visitor, cterm.config), cterm.constraints)
67+
if self._omit_labels:
68+
cterm = CTerm(top_down(self._omit_labels_visitor, cterm.config), cterm.constraints)
69+
if self._minimize:
70+
cterm = CTerm(minimize_term(cterm.config, keep_vars=free_vars(cterm.constraint)), cterm.constraints)
71+
return self.print_lines(cterm.config)
72+
73+
def show_constraints(self, cterm: CTerm) -> list[str]:
74+
ret_strs: list[str] = []
75+
for constraint in cterm.constraints:
76+
constraint_strs = self.print_lines(constraint)
77+
if len(constraint_strs) > 0:
78+
constraint_strs = [f'#And {cstr}' for cstr in constraint_strs]
79+
ret_strs.append(constraint_strs[0])
80+
ret_strs.extend([f' {constraint_str}' for constraint_str in constraint_strs[1:]])
81+
return ret_strs
82+
83+
def _break_cells_visitor(self, kast: KInner) -> KInner:
84+
if (
85+
type(kast) is KApply
86+
and kast.is_cell
87+
and len(kast.args) == 1
88+
and type(kast.args[0]) is KApply
89+
and kast.args[0].label.name in {'_Set_', '_List_', '_Map_'}
90+
):
91+
items = flatten_label(kast.args[0].label.name, kast.args[0])
92+
printed = KToken('\n'.join(map(self._printer, items)), KSort(kast.label.name[1:-1]))
93+
return KApply(kast.label, [printed])
94+
return kast
95+
96+
def _omit_labels_visitor(self, kast: KInner) -> KInner:
97+
if type(kast) == KApply and kast.label.name in self._omit_labels:
98+
return DOTS
99+
return kast

pyk/src/pyk/kcfg/show.py

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from graphviz import Digraph
77

8+
from ..cterm.show import CTermShow
89
from ..kast.inner import KApply, KRewrite, top_down
910
from ..kast.manip import (
1011
flatten_label,
@@ -19,6 +20,7 @@
1920
from ..kast.outer import KRule
2021
from ..kast.prelude.k import DOTS
2122
from ..kast.prelude.ml import mlAnd
23+
from ..kast.pretty import PrettyPrinter
2224
from ..utils import add_indent, ensure_dir_path
2325
from .kcfg import KCFG
2426

@@ -30,31 +32,26 @@
3032
from ..cterm import CSubst
3133
from ..kast import KInner
3234
from ..kast.outer import KDefinition, KFlatModule, KImport, KSentence
33-
from ..ktool.kprint import KPrint
3435
from .kcfg import NodeIdLike
3536

3637
_LOGGER: Final = logging.getLogger(__name__)
3738

3839

3940
class NodePrinter:
40-
kprint: KPrint
41+
cterm_show: CTermShow
4142
full_printer: bool
4243
minimize: bool
4344

44-
def __init__(self, kprint: KPrint, full_printer: bool = False, minimize: bool = False):
45-
self.kprint = kprint
45+
def __init__(self, cterm_show: CTermShow, full_printer: bool = False):
46+
self.cterm_show = cterm_show
4647
self.full_printer = full_printer
47-
self.minimize = minimize
4848

4949
def print_node(self, kcfg: KCFG, node: KCFG.Node) -> list[str]:
5050
attrs = self.node_attrs(kcfg, node)
5151
attr_str = ' (' + ', '.join(attrs) + ')' if attrs else ''
5252
node_strs = [f'{node.id}{attr_str}']
5353
if self.full_printer:
54-
kast = node.cterm.kast
55-
if self.minimize:
56-
kast = minimize_term(kast)
57-
node_strs.extend(' ' + line for line in self.kprint.pretty_print(kast).split('\n'))
54+
node_strs.extend(' ' + line for line in self.cterm_show.show(node.cterm))
5855
return node_strs
5956

6057
def node_attrs(self, kcfg: KCFG, node: KCFG.Node) -> list[str]:
@@ -74,12 +71,12 @@ def node_attrs(self, kcfg: KCFG, node: KCFG.Node) -> list[str]:
7471

7572

7673
class KCFGShow:
77-
kprint: KPrint
74+
pretty_printer: PrettyPrinter
7875
node_printer: NodePrinter
7976

80-
def __init__(self, kprint: KPrint, node_printer: NodePrinter | None = None):
81-
self.kprint = kprint
82-
self.node_printer = node_printer if node_printer is not None else NodePrinter(kprint)
77+
def __init__(self, defn: KDefinition, node_printer: NodePrinter | None = None):
78+
self.pretty_printer = PrettyPrinter(defn)
79+
self.node_printer = node_printer if node_printer else NodePrinter(CTermShow(self.pretty_printer.print))
8380

8481
def node_short_info(self, kcfg: KCFG, node: KCFG.Node) -> list[str]:
8582
return self.node_printer.print_node(kcfg, node)
@@ -140,7 +137,7 @@ def _print_csubst(
140137
csubst: CSubst, subst_first: bool = False, indent: int = 4, minimize: bool = False
141138
) -> list[str]:
142139
_constraint_strs = [
143-
self.kprint.pretty_print(ml_pred_to_bool(constraint, unsafe=True)) for constraint in csubst.constraints
140+
self.pretty_printer.print(ml_pred_to_bool(constraint, unsafe=True)) for constraint in csubst.constraints
144141
]
145142
constraint_strs = _multi_line_print('constraint', _constraint_strs, 'true')
146143
if len(csubst.subst.minimize()) > 0 and minimize:
@@ -149,7 +146,7 @@ def _print_csubst(
149146
_subst_strs = [
150147
line
151148
for k, v in csubst.subst.minimize().items()
152-
for line in f'{k} <- {self.kprint.pretty_print(v)}'.split('\n')
149+
for line in f'{k} <- {self.pretty_printer.print(v)}'.split('\n')
153150
]
154151
subst_strs = _multi_line_print('subst', _subst_strs, '.Subst')
155152
if subst_first:
@@ -325,7 +322,6 @@ def show(
325322
node_deltas: Iterable[tuple[NodeIdLike, NodeIdLike]] = (),
326323
to_module: bool = False,
327324
minimize: bool = True,
328-
sort_collections: bool = False,
329325
omit_cells: Iterable[str] = (),
330326
module_name: str | None = None,
331327
) -> list[str]:
@@ -344,7 +340,7 @@ def show(
344340
res_lines.append('')
345341
res_lines.append(f'Node {node_id}:')
346342
res_lines.append('')
347-
res_lines.append(self.kprint.pretty_print(kast, sort_collections=sort_collections))
343+
res_lines.append(self.pretty_printer.print(kast))
348344
res_lines.append('')
349345

350346
for node_id_1, node_id_2 in node_deltas:
@@ -358,7 +354,7 @@ def show(
358354
res_lines.append('')
359355
res_lines.append(f'State Delta {node_id_1} => {node_id_2}:')
360356
res_lines.append('')
361-
res_lines.append(self.kprint.pretty_print(config_delta, sort_collections=sort_collections))
357+
res_lines.append(self.pretty_printer.print(config_delta))
362358
res_lines.append('')
363359

364360
if not (nodes_printed):
@@ -368,7 +364,7 @@ def show(
368364

369365
if to_module:
370366
module = self.to_module(cfg, module_name, omit_cells=omit_cells)
371-
res_lines.append(self.kprint.pretty_print(module, sort_collections=sort_collections))
367+
res_lines.append(self.pretty_printer.print(module))
372368

373369
return res_lines
374370

@@ -396,7 +392,7 @@ def _short_label(label: str) -> str:
396392

397393
for cover in kcfg.covers():
398394
label = ', '.join(
399-
f'{k} |-> {self.kprint.pretty_print(v)}' for k, v in cover.csubst.subst.minimize().items()
395+
f'{k} |-> {self.pretty_printer.print(v)}' for k, v in cover.csubst.subst.minimize().items()
400396
)
401397
label = _short_label(label)
402398
attrs = {'class': 'abstraction', 'style': 'dashed'}
@@ -405,7 +401,7 @@ def _short_label(label: str) -> str:
405401
for split in kcfg.splits():
406402
for target_id, csubst in split.splits.items():
407403
label = '\n#And'.join(
408-
f'{self.kprint.pretty_print(v)}' for v in split.source.cterm.constraints + csubst.constraints
404+
f'{self.pretty_printer.print(v)}' for v in split.source.cterm.constraints + csubst.constraints
409405
)
410406
graph.edge(tail_name=split.source.id, head_name=target_id, label=f' {label} ')
411407

@@ -438,15 +434,15 @@ def dump(self, cfgid: str, cfg: KCFG, dump_dir: Path, dot: bool = False) -> None
438434

439435
config = node.cterm.config
440436
if not node_file.exists():
441-
node_file.write_text(self.kprint.pretty_print(config))
437+
node_file.write_text(self.pretty_printer.print(config))
442438
_LOGGER.info(f'Wrote node file {cfgid}: {node_file}')
443439
config = minimize_term(config)
444440
if not node_minimized_file.exists():
445-
node_minimized_file.write_text(self.kprint.pretty_print(config))
441+
node_minimized_file.write_text(self.pretty_printer.print(config))
446442
_LOGGER.info(f'Wrote node file {cfgid}: {node_minimized_file}')
447443
if not node_constraint_file.exists():
448444
constraint = mlAnd(node.cterm.constraints)
449-
node_constraint_file.write_text(self.kprint.pretty_print(constraint))
445+
node_constraint_file.write_text(self.pretty_printer.print(constraint))
450446
_LOGGER.info(f'Wrote node file {cfgid}: {node_constraint_file}')
451447

452448
edges_dir = dump_dir / 'edges'
@@ -457,11 +453,11 @@ def dump(self, cfgid: str, cfg: KCFG, dump_dir: Path, dot: bool = False) -> None
457453

458454
config = push_down_rewrites(KRewrite(edge.source.cterm.config, edge.target.cterm.config))
459455
if not edge_file.exists():
460-
edge_file.write_text(self.kprint.pretty_print(config))
456+
edge_file.write_text(self.pretty_printer.print(config))
461457
_LOGGER.info(f'Wrote edge file {cfgid}: {edge_file}')
462458
config = minimize_term(config)
463459
if not edge_minimized_file.exists():
464-
edge_minimized_file.write_text(self.kprint.pretty_print(config))
460+
edge_minimized_file.write_text(self.pretty_printer.print(config))
465461
_LOGGER.info(f'Wrote edge file {cfgid}: {edge_minimized_file}')
466462

467463
covers_dir = dump_dir / 'covers'
@@ -471,12 +467,12 @@ def dump(self, cfgid: str, cfg: KCFG, dump_dir: Path, dot: bool = False) -> None
471467
cover_constraint_file = covers_dir / f'constraint_{cover.source.id}_{cover.target.id}.txt'
472468

473469
subst_equalities = flatten_label(
474-
'#And', cover.csubst.pred(sort_with=self.kprint.definition, constraints=False)
470+
'#And', cover.csubst.pred(sort_with=self.pretty_printer.definition, constraints=False)
475471
)
476472

477473
if not cover_file.exists():
478-
cover_file.write_text('\n'.join(self.kprint.pretty_print(se) for se in subst_equalities))
474+
cover_file.write_text('\n'.join(self.pretty_printer.print(se) for se in subst_equalities))
479475
_LOGGER.info(f'Wrote cover file {cfgid}: {cover_file}')
480476
if not cover_constraint_file.exists():
481-
cover_constraint_file.write_text(self.kprint.pretty_print(cover.csubst.constraint))
477+
cover_constraint_file.write_text(self.pretty_printer.print(cover.csubst.constraint))
482478
_LOGGER.info(f'Wrote cover file {cfgid}: {cover_constraint_file}')

0 commit comments

Comments
 (0)