Repository: runtimeverification/k
Pull request #4808: Factor out CTermShow class for simpler node printing
Author: login: ehildenb
Labels: name: automerge
Created: 2025-05-06T15:32:29Z | Merged: 2025-05-20T19:41:31Z

~Blocked on: https://github.com/runtimeverification/k/pull/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.


Diff:
diff --git a/pyk/src/pyk/__main__.py b/pyk/src/pyk/__main__.py
index ea15761c87e..6570b223fe9 100644
--- a/pyk/src/pyk/__main__.py
+++ b/pyk/src/pyk/__main__.py
@@ -16,6 +16,7 @@
 from .cli.utils import LOG_FORMAT, loglevel
 from .coverage import get_rule_by_id, strip_coverage_logger
 from .cterm import CTerm
+from .cterm.show import CTermShow
 from .cterm.symbolic import cterm_symbolic
 from .kast import KAst
 from .kast.att import KAtt
@@ -275,8 +276,10 @@ def explore_context() -> Iterator[KCFGExplore]:
             if type(failure_info) is APRFailureInfo:
                 print('\n'.join(failure_info.print()))
         if options.show_kcfg and type(proof) is APRProof:
-            node_printer = APRProofNodePrinter(proof, kprove, full_printer=True, minimize=False)
-            show = APRProofShow(kprove, node_printer=node_printer)
+            node_printer = APRProofNodePrinter(
+                proof, CTermShow(PrettyPrinter(kprove.definition).print, minimize=False), full_printer=True
+            )
+            show = APRProofShow(kprove.definition, node_printer=node_printer)
             print('\n'.join(show.show(proof, minimize=False)))
     sys.exit(len([p.id for p in proofs if not p.passed]))
 
diff --git a/pyk/src/pyk/cterm/show.py b/pyk/src/pyk/cterm/show.py
new file mode 100644
index 00000000000..c817f2a3ef6
--- /dev/null
+++ b/pyk/src/pyk/cterm/show.py
@@ -0,0 +1,99 @@
+from __future__ import annotations
+
+import logging
+from dataclasses import dataclass
+from typing import TYPE_CHECKING
+
+from ..kast.inner import KApply, KSort, KToken, flatten_label, top_down
+from ..kast.manip import free_vars, minimize_term
+from ..kast.prelude.k import DOTS
+from .cterm import CTerm
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Iterable
+    from typing import Final
+
+    from ..kast.inner import KInner
+
+_LOGGER: Final = logging.getLogger(__name__)
+
+
+@dataclass
+class CTermShow:
+    _printer: Callable[[KInner], str]
+    _minimize: bool
+    _break_cell_collections: bool
+    _omit_labels: tuple[str, ...]
+
+    def __init__(
+        self,
+        printer: Callable[[KInner], str],
+        minimize: bool = True,
+        break_cell_collections: bool = True,
+        omit_labels: Iterable[str] = (),
+    ):
+        self._printer = printer
+        self._minimize = minimize
+        self._break_cell_collections = break_cell_collections
+        self._omit_labels = tuple(omit_labels)
+
+    def print_lines(self, kast: KInner) -> list[str]:
+        return self._printer(kast).split('\n')
+
+    def let(
+        self,
+        minimize: bool | None = None,
+        break_cell_collections: bool | None = None,
+        omit_labels: Iterable[str] | None = None,
+    ) -> CTermShow:
+        return CTermShow(
+            self._printer,
+            minimize=(self._minimize if minimize is None else minimize),
+            break_cell_collections=(
+                self._break_cell_collections if break_cell_collections is None else break_cell_collections
+            ),
+            omit_labels=(self._omit_labels if omit_labels is None else omit_labels),
+        )
+
+    def show(self, cterm: CTerm) -> list[str]:
+        ret_strs: list[str] = []
+        ret_strs.extend(self.show_config(cterm))
+        ret_strs.extend(self.show_constraints(cterm))
+        return ret_strs
+
+    def show_config(self, cterm: CTerm) -> list[str]:
+        if self._break_cell_collections:
+            cterm = CTerm(top_down(self._break_cells_visitor, cterm.config), cterm.constraints)
+        if self._omit_labels:
+            cterm = CTerm(top_down(self._omit_labels_visitor, cterm.config), cterm.constraints)
+        if self._minimize:
+            cterm = CTerm(minimize_term(cterm.config, keep_vars=free_vars(cterm.constraint)), cterm.constraints)
+        return self.print_lines(cterm.config)
+
+    def show_constraints(self, cterm: CTerm) -> list[str]:
+        ret_strs: list[str] = []
+        for constraint in cterm.constraints:
+            constraint_strs = self.print_lines(constraint)
+            if len(constraint_strs) > 0:
+                constraint_strs = [f'#And {cstr}' for cstr in constraint_strs]
+                ret_strs.append(constraint_strs[0])
+                ret_strs.extend([f'  {constraint_str}' for constraint_str in constraint_strs[1:]])
+        return ret_strs
+
+    def _break_cells_visitor(self, kast: KInner) -> KInner:
+        if (
+            type(kast) is KApply
+            and kast.is_cell
+            and len(kast.args) == 1
+            and type(kast.args[0]) is KApply
+            and kast.args[0].label.name in {'_Set_', '_List_', '_Map_'}
+        ):
+            items = flatten_label(kast.args[0].label.name, kast.args[0])
+            printed = KToken('\n'.join(map(self._printer, items)), KSort(kast.label.name[1:-1]))
+            return KApply(kast.label, [printed])
+        return kast
+
+    def _omit_labels_visitor(self, kast: KInner) -> KInner:
+        if type(kast) == KApply and kast.label.name in self._omit_labels:
+            return DOTS
+        return kast
diff --git a/pyk/src/pyk/kcfg/show.py b/pyk/src/pyk/kcfg/show.py
index 7d7bba8a93f..818e45107c2 100644
--- a/pyk/src/pyk/kcfg/show.py
+++ b/pyk/src/pyk/kcfg/show.py
@@ -5,6 +5,7 @@
 
 from graphviz import Digraph
 
+from ..cterm.show import CTermShow
 from ..kast.inner import KApply, KRewrite, top_down
 from ..kast.manip import (
     flatten_label,
@@ -19,6 +20,7 @@
 from ..kast.outer import KRule
 from ..kast.prelude.k import DOTS
 from ..kast.prelude.ml import mlAnd
+from ..kast.pretty import PrettyPrinter
 from ..utils import add_indent, ensure_dir_path
 from .kcfg import KCFG
 
@@ -30,31 +32,26 @@
     from ..cterm import CSubst
     from ..kast import KInner
     from ..kast.outer import KDefinition, KFlatModule, KImport, KSentence
-    from ..ktool.kprint import KPrint
     from .kcfg import NodeIdLike
 
 _LOGGER: Final = logging.getLogger(__name__)
 
 
 class NodePrinter:
-    kprint: KPrint
+    cterm_show: CTermShow
     full_printer: bool
     minimize: bool
 
-    def __init__(self, kprint: KPrint, full_printer: bool = False, minimize: bool = False):
-        self.kprint = kprint
+    def __init__(self, cterm_show: CTermShow, full_printer: bool = False):
+        self.cterm_show = cterm_show
         self.full_printer = full_printer
-        self.minimize = minimize
 
     def print_node(self, kcfg: KCFG, node: KCFG.Node) -> list[str]:
         attrs = self.node_attrs(kcfg, node)
         attr_str = ' (' + ', '.join(attrs) + ')' if attrs else ''
         node_strs = [f'{node.id}{attr_str}']
         if self.full_printer:
-            kast = node.cterm.kast
-            if self.minimize:
-                kast = minimize_term(kast)
-            node_strs.extend('  ' + line for line in self.kprint.pretty_print(kast).split('\n'))
+            node_strs.extend('  ' + line for line in self.cterm_show.show(node.cterm))
         return node_strs
 
     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]:
 
 
 class KCFGShow:
-    kprint: KPrint
+    pretty_printer: PrettyPrinter
     node_printer: NodePrinter
 
-    def __init__(self, kprint: KPrint, node_printer: NodePrinter | None = None):
-        self.kprint = kprint
-        self.node_printer = node_printer if node_printer is not None else NodePrinter(kprint)
+    def __init__(self, defn: KDefinition, node_printer: NodePrinter | None = None):
+        self.pretty_printer = PrettyPrinter(defn)
+        self.node_printer = node_printer if node_printer else NodePrinter(CTermShow(self.pretty_printer.print))
 
     def node_short_info(self, kcfg: KCFG, node: KCFG.Node) -> list[str]:
         return self.node_printer.print_node(kcfg, node)
@@ -140,7 +137,7 @@ def _print_csubst(
             csubst: CSubst, subst_first: bool = False, indent: int = 4, minimize: bool = False
         ) -> list[str]:
             _constraint_strs = [
-                self.kprint.pretty_print(ml_pred_to_bool(constraint, unsafe=True)) for constraint in csubst.constraints
+                self.pretty_printer.print(ml_pred_to_bool(constraint, unsafe=True)) for constraint in csubst.constraints
             ]
             constraint_strs = _multi_line_print('constraint', _constraint_strs, 'true')
             if len(csubst.subst.minimize()) > 0 and minimize:
@@ -149,7 +146,7 @@ def _print_csubst(
                 _subst_strs = [
                     line
                     for k, v in csubst.subst.minimize().items()
-                    for line in f'{k} <- {self.kprint.pretty_print(v)}'.split('\n')
+                    for line in f'{k} <- {self.pretty_printer.print(v)}'.split('\n')
                 ]
                 subst_strs = _multi_line_print('subst', _subst_strs, '.Subst')
             if subst_first:
@@ -325,7 +322,6 @@ def show(
         node_deltas: Iterable[tuple[NodeIdLike, NodeIdLike]] = (),
         to_module: bool = False,
         minimize: bool = True,
-        sort_collections: bool = False,
         omit_cells: Iterable[str] = (),
         module_name: str | None = None,
     ) -> list[str]:
@@ -344,7 +340,7 @@ def show(
             res_lines.append('')
             res_lines.append(f'Node {node_id}:')
             res_lines.append('')
-            res_lines.append(self.kprint.pretty_print(kast, sort_collections=sort_collections))
+            res_lines.append(self.pretty_printer.print(kast))
             res_lines.append('')
 
         for node_id_1, node_id_2 in node_deltas:
@@ -358,7 +354,7 @@ def show(
             res_lines.append('')
             res_lines.append(f'State Delta {node_id_1} => {node_id_2}:')
             res_lines.append('')
-            res_lines.append(self.kprint.pretty_print(config_delta, sort_collections=sort_collections))
+            res_lines.append(self.pretty_printer.print(config_delta))
             res_lines.append('')
 
         if not (nodes_printed):
@@ -368,7 +364,7 @@ def show(
 
         if to_module:
             module = self.to_module(cfg, module_name, omit_cells=omit_cells)
-            res_lines.append(self.kprint.pretty_print(module, sort_collections=sort_collections))
+            res_lines.append(self.pretty_printer.print(module))
 
         return res_lines
 
@@ -396,7 +392,7 @@ def _short_label(label: str) -> str:
 
         for cover in kcfg.covers():
             label = ', '.join(
-                f'{k} |-> {self.kprint.pretty_print(v)}' for k, v in cover.csubst.subst.minimize().items()
+                f'{k} |-> {self.pretty_printer.print(v)}' for k, v in cover.csubst.subst.minimize().items()
             )
             label = _short_label(label)
             attrs = {'class': 'abstraction', 'style': 'dashed'}
@@ -405,7 +401,7 @@ def _short_label(label: str) -> str:
         for split in kcfg.splits():
             for target_id, csubst in split.splits.items():
                 label = '\n#And'.join(
-                    f'{self.kprint.pretty_print(v)}' for v in split.source.cterm.constraints + csubst.constraints
+                    f'{self.pretty_printer.print(v)}' for v in split.source.cterm.constraints + csubst.constraints
                 )
                 graph.edge(tail_name=split.source.id, head_name=target_id, label=f'  {label}        ')
 
@@ -438,15 +434,15 @@ def dump(self, cfgid: str, cfg: KCFG, dump_dir: Path, dot: bool = False) -> None
 
             config = node.cterm.config
             if not node_file.exists():
-                node_file.write_text(self.kprint.pretty_print(config))
+                node_file.write_text(self.pretty_printer.print(config))
                 _LOGGER.info(f'Wrote node file {cfgid}: {node_file}')
             config = minimize_term(config)
             if not node_minimized_file.exists():
-                node_minimized_file.write_text(self.kprint.pretty_print(config))
+                node_minimized_file.write_text(self.pretty_printer.print(config))
                 _LOGGER.info(f'Wrote node file {cfgid}: {node_minimized_file}')
             if not node_constraint_file.exists():
                 constraint = mlAnd(node.cterm.constraints)
-                node_constraint_file.write_text(self.kprint.pretty_print(constraint))
+                node_constraint_file.write_text(self.pretty_printer.print(constraint))
                 _LOGGER.info(f'Wrote node file {cfgid}: {node_constraint_file}')
 
         edges_dir = dump_dir / 'edges'
@@ -457,11 +453,11 @@ def dump(self, cfgid: str, cfg: KCFG, dump_dir: Path, dot: bool = False) -> None
 
             config = push_down_rewrites(KRewrite(edge.source.cterm.config, edge.target.cterm.config))
             if not edge_file.exists():
-                edge_file.write_text(self.kprint.pretty_print(config))
+                edge_file.write_text(self.pretty_printer.print(config))
                 _LOGGER.info(f'Wrote edge file {cfgid}: {edge_file}')
             config = minimize_term(config)
             if not edge_minimized_file.exists():
-                edge_minimized_file.write_text(self.kprint.pretty_print(config))
+                edge_minimized_file.write_text(self.pretty_printer.print(config))
                 _LOGGER.info(f'Wrote edge file {cfgid}: {edge_minimized_file}')
 
         covers_dir = dump_dir / 'covers'
@@ -471,12 +467,12 @@ def dump(self, cfgid: str, cfg: KCFG, dump_dir: Path, dot: bool = False) -> None
             cover_constraint_file = covers_dir / f'constraint_{cover.source.id}_{cover.target.id}.txt'
 
             subst_equalities = flatten_label(
-                '#And', cover.csubst.pred(sort_with=self.kprint.definition, constraints=False)
+                '#And', cover.csubst.pred(sort_with=self.pretty_printer.definition, constraints=False)
             )
 
             if not cover_file.exists():
-                cover_file.write_text('\n'.join(self.kprint.pretty_print(se) for se in subst_equalities))
+                cover_file.write_text('\n'.join(self.pretty_printer.print(se) for se in subst_equalities))
                 _LOGGER.info(f'Wrote cover file {cfgid}: {cover_file}')
             if not cover_constraint_file.exists():
-                cover_constraint_file.write_text(self.kprint.pretty_print(cover.csubst.constraint))
+                cover_constraint_file.write_text(self.pretty_printer.print(cover.csubst.constraint))
                 _LOGGER.info(f'Wrote cover file {cfgid}: {cover_constraint_file}')
diff --git a/pyk/src/pyk/kcfg/tui.py b/pyk/src/pyk/kcfg/tui.py
index 6118a216dcf..9a5fe821c44 100644
--- a/pyk/src/pyk/kcfg/tui.py
+++ b/pyk/src/pyk/kcfg/tui.py
@@ -11,9 +11,11 @@
 from textual.widgets import Footer, Static
 
 from ..cterm import CTerm
+from ..cterm.show import CTermShow
 from ..kast.inner import KApply, KRewrite
-from ..kast.manip import flatten_label, minimize_term, push_down_rewrites
+from ..kast.manip import flatten_label, push_down_rewrites
 from ..kast.prelude.kbool import TRUE
+from ..kast.pretty import PrettyPrinter
 from ..utils import ROOT, shorten_hashes, single
 from .kcfg import KCFG
 from .show import KCFGShow
@@ -25,8 +27,8 @@
     from textual.events import Click
 
     from ..cterm import CSubst
-    from ..kast import KInner
-    from ..ktool.kprint import KPrint
+    from ..kast.inner import KInner
+    from ..kast.outer import KDefinition
     from .show import NodePrinter
 
 
@@ -137,7 +139,6 @@ def on_click(self, click: Click) -> None:
 
 class BehaviorView(ScrollableContainer, can_focus=True):
     _kcfg: KCFG
-    _kprint: KPrint
     _minimize: bool
     _node_printer: NodePrinter | None
     _kcfg_nodes: Iterable[GraphChunk]
@@ -149,18 +150,17 @@ def __init__(self) -> None:
     def __init__(
         self,
         kcfg: KCFG,
-        kprint: KPrint,
+        definition: KDefinition,
         minimize: bool = True,
         node_printer: NodePrinter | None = None,
         id: str = '',
     ):
         super().__init__(id=id)
         self._kcfg = kcfg
-        self._kprint = kprint
         self._minimize = minimize
         self._node_printer = node_printer
         self._kcfg_nodes = []
-        kcfg_show = KCFGShow(kprint, node_printer=node_printer)
+        kcfg_show = KCFGShow(definition, node_printer=node_printer)
         for lseg_id, node_lines in kcfg_show.pretty_segments(self._kcfg, minimize=self._minimize):
             self._kcfg_nodes.append(GraphChunk(lseg_id, node_lines))
 
@@ -173,8 +173,9 @@ def on_click(self, click: Click) -> None:
 
 
 class NodeView(Widget):
-    _kprint: KPrint
+    _definition: KDefinition
     _custom_view: Callable[[KCFGElem], Iterable[str]] | None
+    _cterm_show: CTermShow
 
     _element: KCFGElem | None
 
@@ -189,7 +190,7 @@ class NodeView(Widget):
 
     def __init__(
         self,
-        kprint: KPrint,
+        definition: KDefinition,
         id: str = '',
         minimize: bool = True,
         term_on: bool = True,
@@ -197,12 +198,13 @@ def __init__(
         custom_on: bool = False,
         status_on: bool = True,
         custom_view: Callable[[KCFGElem], Iterable[str]] | None = None,
+        cterm_show: CTermShow | None = None,
         proof_status: str = '',
         proof_id: str = '',
         exec_time: float = 0,
     ):
         super().__init__(id=id)
-        self._kprint = kprint
+        self._definition = definition
         self._element = None
         self._minimize = minimize
         self._term_on = term_on
@@ -213,6 +215,7 @@ def __init__(
         self._proof_status = proof_status
         self._proof_id = proof_id
         self._exec_time = exec_time
+        self._cterm_show = cterm_show if cterm_show else CTermShow(PrettyPrinter(definition).print)
 
     def _info_text(self) -> str:
         term_str = '✅' if self._term_on else '❌'
@@ -254,6 +257,8 @@ def toggle_option(self, field: str) -> bool:
         if field == 'custom_on' and self._custom_view is None:
             new_value = False
         setattr(self, field_attr, new_value)
+        if field == 'minimize':
+            self._cterm_show = self._cterm_show.let(minimize=self._minimize)
         self._update()
         return new_value
 
@@ -279,18 +284,16 @@ def _boolify(c: KInner) -> KInner:
                 return c
 
         def _boolify_print(c: KInner) -> str:
-            return self._kprint.pretty_print(_boolify(c))
+            return PrettyPrinter(self._definition).print(_boolify(c))
 
         def _cterm_text(cterm: CTerm) -> tuple[str, str]:
-            config = cterm.config
-            constraints = map(_boolify, cterm.constraints)
-            if self._minimize:
-                config = minimize_term(config)
-            return (self._kprint.pretty_print(config), '\n'.join(self._kprint.pretty_print(c) for c in constraints))
+            config_text = self._cterm_show.show_config(cterm)
+            constraint_text = self._cterm_show.show_constraints(cterm)
+            return '\n'.join(config_text), '\n'.join(constraint_text)
 
         def _csubst_text(csubst: CSubst) -> tuple[str, str]:
             equalities = map(
-                _boolify_print, flatten_label('#And', csubst.pred(sort_with=self._kprint.definition, constraints=False))
+                _boolify_print, flatten_label('#And', csubst.pred(sort_with=self._definition, constraints=False))
             )
             constraints = map(_boolify_print, flatten_label('#And', csubst.constraint))
             return '\n'.join(equalities), '\n'.join(constraints)
@@ -376,10 +379,11 @@ class KCFGViewer(App):
     CSS_PATH = ROOT / 'kcfg/style.css'
 
     _kcfg: KCFG
-    _kprint: KPrint
+    _definition: KDefinition
 
     _node_printer: NodePrinter | None
     _custom_view: Callable[[KCFGElem], Iterable[str]] | None
+    _cterm_show: CTermShow | None
 
     _minimize: bool
 
@@ -389,16 +393,18 @@ class KCFGViewer(App):
     def __init__(
         self,
         kcfg: KCFG,
-        kprint: KPrint,
+        definition: KDefinition,
         node_printer: NodePrinter | None = None,
         custom_view: Callable[[KCFGElem], Iterable[str]] | None = None,
+        cterm_show: CTermShow | None = None,
         minimize: bool = True,
     ) -> None:
         super().__init__()
         self._kcfg = kcfg
-        self._kprint = kprint
+        self._definition = definition
         self._node_printer = node_printer
         self._custom_view = custom_view
+        self._cterm_show = cterm_show
         self._minimize = minimize
         self._hidden_chunks = []
         self._selected_chunk = None
@@ -408,13 +414,14 @@ def __init__(
     def compose(self) -> ComposeResult:
         yield Horizontal(
             Vertical(
-                BehaviorView(self._kcfg, self._kprint, node_printer=self._node_printer, id='behavior'),
+                BehaviorView(self._kcfg, self._definition, node_printer=self._node_printer, id='behavior'),
                 id='navigation',
             ),
             Vertical(
                 NodeView(
-                    self._kprint,
+                    self._definition,
                     custom_view=self._custom_view,
+                    cterm_show=self._cterm_show,
                     proof_id=str(self._kcfg._node_id),
                     id='node-view',
                 ),
diff --git a/pyk/src/pyk/proof/show.py b/pyk/src/pyk/proof/show.py
index 31a990939d8..9483fa15996 100644
--- a/pyk/src/pyk/proof/show.py
+++ b/pyk/src/pyk/proof/show.py
@@ -13,9 +13,10 @@
 
     from graphviz import Digraph
 
+    from ..cterm.show import CTermShow
+    from ..kast.outer import KDefinition
     from ..kcfg import KCFG
     from ..kcfg.kcfg import NodeIdLike
-    from ..ktool.kprint import KPrint
     from .reachability import APRProof
 
 _LOGGER: Final = logging.getLogger(__name__)
@@ -24,8 +25,8 @@
 class APRProofNodePrinter(NodePrinter):
     proof: APRProof
 
-    def __init__(self, proof: APRProof, kprint: KPrint, full_printer: bool = False, minimize: bool = False):
-        super().__init__(kprint, full_printer=full_printer, minimize=minimize)
+    def __init__(self, proof: APRProof, cterm_show: CTermShow, full_printer: bool = False):
+        super().__init__(cterm_show, full_printer=full_printer)
         self.proof = proof
 
     def node_attrs(self, kcfg: KCFG, node: KCFG.Node) -> list[str]:
@@ -52,8 +53,8 @@ def node_attrs(self, kcfg: KCFG, node: KCFG.Node) -> list[str]:
 class APRProofShow:
     kcfg_show: KCFGShow
 
-    def __init__(self, kprint: KPrint, node_printer: NodePrinter | None = None):
-        self.kcfg_show = KCFGShow(kprint, node_printer=node_printer)
+    def __init__(self, definition: KDefinition, node_printer: NodePrinter | None = None):
+        self.kcfg_show = KCFGShow(definition, node_printer=node_printer)
 
     def pretty_segments(self, proof: APRProof, minimize: bool = True) -> Iterable[tuple[str, Iterable[str]]]:
         ret_lines = list(self.kcfg_show.pretty_segments(proof.kcfg, minimize=minimize))
@@ -73,7 +74,6 @@ def show(
         node_deltas: Iterable[tuple[NodeIdLike, NodeIdLike]] = (),
         to_module: bool = False,
         minimize: bool = True,
-        sort_collections: bool = False,
         omit_cells: Iterable[str] = (),
     ) -> list[str]:
         res_lines = self.kcfg_show.show(
@@ -82,7 +82,6 @@ def show(
             node_deltas=node_deltas,
             to_module=to_module,
             minimize=minimize,
-            sort_collections=sort_collections,
             omit_cells=omit_cells,
             module_name=f'SUMMARY-{proof.id.upper().replace("_", "-")}',
         )
diff --git a/pyk/src/pyk/proof/tui.py b/pyk/src/pyk/proof/tui.py
index 417be51dbf1..8d4c121c8de 100644
--- a/pyk/src/pyk/proof/tui.py
+++ b/pyk/src/pyk/proof/tui.py
@@ -13,6 +13,8 @@
 
     from textual.app import ComposeResult
 
+    from ..cterm.show import CTermShow
+    from ..kast.outer import KDefinition
     from ..kcfg.show import NodePrinter
     from ..kcfg.tui import KCFGElem
     from ..ktool.kprint import KPrint
@@ -21,7 +23,7 @@
 
 class APRProofBehaviorView(ScrollableContainer, can_focus=True):
     _proof: APRProof
-    _kprint: KPrint
+    _definition: KDefinition
     _minimize: bool
     _node_printer: NodePrinter | None
     _proof_nodes: Iterable[GraphChunk]
@@ -29,18 +31,18 @@ class APRProofBehaviorView(ScrollableContainer, can_focus=True):
     def __init__(
         self,
         proof: APRProof,
-        kprint: KPrint,
+        definition: KDefinition,
         minimize: bool = True,
         node_printer: NodePrinter | None = None,
         id: str = '',
     ):
         super().__init__(id=id)
         self._proof = proof
-        self._kprint = kprint
+        self._definition = definition
         self._minimize = minimize
         self._node_printer = node_printer
         self._proof_nodes = []
-        proof_show = APRProofShow(kprint, node_printer=node_printer)
+        proof_show = APRProofShow(definition, node_printer=node_printer)
         for lseg_id, node_lines in proof_show.pretty_segments(self._proof, minimize=self._minimize):
             self._proof_nodes.append(GraphChunk(lseg_id, node_lines))
 
@@ -57,9 +59,17 @@ def __init__(
         kprint: KPrint,
         node_printer: NodePrinter | None = None,
         custom_view: Callable[[KCFGElem], Iterable[str]] | None = None,
+        cterm_show: CTermShow | None = None,
         minimize: bool = True,
     ) -> None:
-        super().__init__(proof.kcfg, kprint, node_printer=node_printer, custom_view=custom_view, minimize=minimize)
+        super().__init__(
+            proof.kcfg,
+            kprint.definition,
+            node_printer=node_printer,
+            custom_view=custom_view,
+            cterm_show=cterm_show,
+            minimize=minimize,
+        )
         self._proof = proof
 
     def on_mount(self) -> None:
@@ -68,16 +78,17 @@ def on_mount(self) -> None:
     def compose(self) -> ComposeResult:
         yield Horizontal(
             Vertical(
-                APRProofBehaviorView(self._proof, self._kprint, node_printer=self._node_printer, id='behavior'),
+                APRProofBehaviorView(self._proof, self._definition, node_printer=self._node_printer, id='behavior'),
                 id='navigation',
             ),
             Vertical(
                 NodeView(
-                    self._kprint,
+                    self._definition,
                     custom_view=self._custom_view,
                     proof_id=self._proof.id,
                     proof_status=self._proof.status.value,
                     exec_time=self._proof.exec_time,
+                    cterm_show=self._cterm_show,
                     id='node-view',
                 ),
                 id='display',
diff --git a/pyk/src/tests/integration/proof/test_cell_map.py b/pyk/src/tests/integration/proof/test_cell_map.py
index c897dd97708..563a7db87bf 100644
--- a/pyk/src/tests/integration/proof/test_cell_map.py
+++ b/pyk/src/tests/integration/proof/test_cell_map.py
@@ -7,7 +7,9 @@
 import pytest
 
 from pyk.cterm import CTerm
+from pyk.cterm.show import CTermShow
 from pyk.kast.inner import KApply, KSequence, KToken, KVariable, build_assoc
+from pyk.kast.pretty import PrettyPrinter
 from pyk.kcfg.show import KCFGShow
 from pyk.proof import APRProof, APRProver, ProofStatus
 from pyk.proof.show import APRProofNodePrinter
@@ -140,7 +142,12 @@ def test_all_path_reachability_prove(
         prover = APRProver(kcfg_explore=kcfg_explore, execute_depth=max_depth)
         prover.advance_proof(proof, max_iterations=max_iterations)
 
-        kcfg_show = KCFGShow(kprove, node_printer=APRProofNodePrinter(proof, kprove, full_printer=True))
+        kcfg_show = KCFGShow(
+            kprove.definition,
+            node_printer=APRProofNodePrinter(
+                proof, CTermShow(PrettyPrinter(kprove.definition).print), full_printer=True
+            ),
+        )
         cfg_lines = kcfg_show.show(proof.kcfg)
         _LOGGER.info('\n'.join(cfg_lines))
 
diff --git a/pyk/src/tests/integration/proof/test_custom_step.py b/pyk/src/tests/integration/proof/test_custom_step.py
index 04fd057894e..141f95b3a1a 100644
--- a/pyk/src/tests/integration/proof/test_custom_step.py
+++ b/pyk/src/tests/integration/proof/test_custom_step.py
@@ -6,8 +6,10 @@
 import pytest
 
 from pyk.cterm import CTerm
+from pyk.cterm.show import CTermShow
 from pyk.kast.inner import KApply, KSequence, KVariable
 from pyk.kast.manip import set_cell
+from pyk.kast.pretty import PrettyPrinter
 from pyk.kcfg import KCFGExplore
 from pyk.kcfg.kcfg import Step
 from pyk.kcfg.semantics import DefaultSemantics
@@ -215,5 +217,10 @@ def _run_proof(
         )
         prover.advance_proof(proof, max_iterations=max_iterations)
 
-        kcfg_show = KCFGShow(kprove, node_printer=APRProofNodePrinter(proof, kprove, full_printer=True, minimize=False))
+        kcfg_show = KCFGShow(
+            kprove.definition,
+            node_printer=APRProofNodePrinter(
+                proof, CTermShow(PrettyPrinter(kprove.definition).print, minimize=False), full_printer=True
+            ),
+        )
         return proof.status, kcfg_show.show(proof.kcfg)
diff --git a/pyk/src/tests/integration/proof/test_goto.py b/pyk/src/tests/integration/proof/test_goto.py
index ce1cf03c410..97ddf541871 100644
--- a/pyk/src/tests/integration/proof/test_goto.py
+++ b/pyk/src/tests/integration/proof/test_goto.py
@@ -6,7 +6,9 @@
 
 import pytest
 
+from pyk.cterm.show import CTermShow
 from pyk.kast.inner import KApply, KSequence
+from pyk.kast.pretty import PrettyPrinter
 from pyk.kcfg.semantics import DefaultSemantics
 from pyk.kcfg.show import KCFGShow
 from pyk.proof import APRProof, APRProver, ProofStatus
@@ -113,7 +115,12 @@ def test_all_path_bmc_reachability_prove(
         )
         prover.advance_proof(proof, max_iterations=max_iterations)
 
-        kcfg_show = KCFGShow(kprove, node_printer=APRProofNodePrinter(proof, kprove, full_printer=True))
+        kcfg_show = KCFGShow(
+            kprove.definition,
+            node_printer=APRProofNodePrinter(
+                proof, CTermShow(PrettyPrinter(kprove.definition).print), full_printer=True
+            ),
+        )
         cfg_lines = kcfg_show.show(proof.kcfg)
         _LOGGER.info('\n'.join(cfg_lines))
 
diff --git a/pyk/src/tests/integration/proof/test_imp.py b/pyk/src/tests/integration/proof/test_imp.py
index 5bb6e871d78..65be2ca7d42 100644
--- a/pyk/src/tests/integration/proof/test_imp.py
+++ b/pyk/src/tests/integration/proof/test_imp.py
@@ -8,11 +8,13 @@
 import pytest
 
 from pyk.cterm import CSubst, CTerm
+from pyk.cterm.show import CTermShow
 from pyk.kast.inner import KApply, KSequence, KSort, KToken, KVariable, Subst
 from pyk.kast.manip import minimize_term, sort_ac_collections
 from pyk.kast.prelude.kbool import BOOL, FALSE, andBool, orBool
 from pyk.kast.prelude.kint import intToken
 from pyk.kast.prelude.ml import mlAnd, mlBottom, mlEquals, mlEqualsFalse, mlEqualsTrue, mlTop
+from pyk.kast.pretty import PrettyPrinter
 from pyk.kcfg.semantics import DefaultSemantics
 from pyk.kcfg.show import KCFGShow
 from pyk.proof import APRProver, ProofStatus
@@ -940,7 +942,12 @@ def test_all_path_reachability_prove(
         prover = APRProver(kcfg_explore=kcfg_explore, execute_depth=max_depth, cut_point_rules=cut_rules)
         prover.advance_proof(proof, max_iterations=max_iterations)
 
-        kcfg_show = KCFGShow(kprove, node_printer=APRProofNodePrinter(proof, kprove, full_printer=True))
+        kcfg_show = KCFGShow(
+            kprove.definition,
+            node_printer=APRProofNodePrinter(
+                proof, CTermShow(PrettyPrinter(kprove.definition).print), full_printer=True
+            ),
+        )
         cfg_lines = kcfg_show.show(proof.kcfg)
         _LOGGER.info('\n'.join(cfg_lines))
 
@@ -989,7 +996,12 @@ def test_all_path_reachability_prove_with_kcfg_optims(
         )
         prover.advance_proof(proof, max_iterations=max_iterations)
 
-        kcfg_show = KCFGShow(kprove, node_printer=APRProofNodePrinter(proof, kprove, full_printer=True))
+        kcfg_show = KCFGShow(
+            kprove.definition,
+            node_printer=APRProofNodePrinter(
+                proof, CTermShow(PrettyPrinter(kprove.definition).print), full_printer=True
+            ),
+        )
         cfg_lines = kcfg_show.show(proof.kcfg)
         _LOGGER.info('\n'.join(cfg_lines))
 
@@ -1103,7 +1115,12 @@ def test_all_path_bmc_reachability_prove(
         )
         prover.advance_proof(proof, max_iterations=max_iterations)
 
-        kcfg_show = KCFGShow(kprove, node_printer=APRProofNodePrinter(proof, kprove, full_printer=True))
+        kcfg_show = KCFGShow(
+            kprove.definition,
+            node_printer=APRProofNodePrinter(
+                proof, CTermShow(PrettyPrinter(kprove.definition).print), full_printer=True
+            ),
+        )
         cfg_lines = kcfg_show.show(proof.kcfg)
         _LOGGER.info('\n'.join(cfg_lines))
 
@@ -1538,7 +1555,12 @@ def test_all_path_reachability_prove_parallel(
 
         parallel_advance_proof(proof=proof, max_iterations=max_iterations, create_prover=_create_prover, max_workers=2)
 
-        kcfg_show = KCFGShow(kprove, node_printer=APRProofNodePrinter(proof, kprove, full_printer=True))
+        kcfg_show = KCFGShow(
+            kprove.definition,
+            node_printer=APRProofNodePrinter(
+                proof, CTermShow(PrettyPrinter(kprove.definition).print), full_printer=True
+            ),
+        )
         cfg_lines = kcfg_show.show(proof.kcfg)
         _LOGGER.info('\n'.join(cfg_lines))
 
diff --git a/pyk/src/tests/integration/proof/test_mini_kevm.py b/pyk/src/tests/integration/proof/test_mini_kevm.py
index e961eb73130..2addef3650f 100644
--- a/pyk/src/tests/integration/proof/test_mini_kevm.py
+++ b/pyk/src/tests/integration/proof/test_mini_kevm.py
@@ -7,8 +7,10 @@
 import pytest
 
 from pyk.cterm import CTerm
+from pyk.cterm.show import CTermShow
 from pyk.kast.inner import KApply, KVariable
 from pyk.kast.prelude.kint import intToken
+from pyk.kast.pretty import PrettyPrinter
 from pyk.kcfg.show import KCFGShow
 from pyk.proof import APRProof, APRProver, ProofStatus
 from pyk.proof.show import APRProofNodePrinter
@@ -93,7 +95,12 @@ def test_all_path_reachability_prove(
         )
         prover.advance_proof(proof, max_iterations=max_iterations)
 
-        kcfg_show = KCFGShow(kprove, node_printer=APRProofNodePrinter(proof, kprove, full_printer=True, minimize=False))
+        kcfg_show = KCFGShow(
+            kprove.definition,
+            node_printer=APRProofNodePrinter(
+                proof, CTermShow(PrettyPrinter(kprove.definition).print, minimize=False), full_printer=True
+            ),
+        )
         cfg_lines = kcfg_show.show(proof.kcfg)
         _LOGGER.info('\n'.join(cfg_lines))
 
diff --git a/pyk/src/tests/integration/proof/test_non_det.py b/pyk/src/tests/integration/proof/test_non_det.py
index db3a791bf4c..00c1b7b3304 100644
--- a/pyk/src/tests/integration/proof/test_non_det.py
+++ b/pyk/src/tests/integration/proof/test_non_det.py
@@ -6,6 +6,8 @@
 
 import pytest
 
+from pyk.cterm.show import CTermShow
+from pyk.kast.pretty import PrettyPrinter
 from pyk.kcfg.show import KCFGShow
 from pyk.proof import APRProof, APRProver, ProofStatus
 from pyk.proof.show import APRProofNodePrinter
@@ -85,7 +87,12 @@ def test_all_path_reachability_prove(
         prover = APRProver(kcfg_explore=kcfg_explore, execute_depth=max_depth)
         prover.advance_proof(proof, max_iterations=max_iterations)
 
-        kcfg_show = KCFGShow(kprove, node_printer=APRProofNodePrinter(proof, kprove, full_printer=True))
+        kcfg_show = KCFGShow(
+            kprove.definition,
+            node_printer=APRProofNodePrinter(
+                proof, CTermShow(PrettyPrinter(kprove.definition).print), full_printer=True
+            ),
+        )
         cfg_lines = kcfg_show.show(proof.kcfg)
         _LOGGER.info('\n'.join(cfg_lines))
 
diff --git a/pyk/src/tests/unit/kcfg/test_minimize.py b/pyk/src/tests/unit/kcfg/test_minimize.py
index d2b92b51058..d2708175ee1 100644
--- a/pyk/src/tests/unit/kcfg/test_minimize.py
+++ b/pyk/src/tests/unit/kcfg/test_minimize.py
@@ -4,16 +4,18 @@
 
 import pytest
 
+from pyk.cterm.show import CTermShow
 from pyk.kast.inner import KVariable
+from pyk.kast.outer import KDefinition, KFlatModule
 from pyk.kast.prelude.kint import geInt, intToken, ltInt
 from pyk.kast.prelude.ml import mlEqualsTrue, mlTop
+from pyk.kast.pretty import PrettyPrinter
 from pyk.kcfg import KCFG, KCFGShow
 from pyk.kcfg.minimize import KCFGMinimizer
 from pyk.kcfg.show import NodePrinter
 from pyk.utils import single
 
 from ..kcfg.merge_node_data import KCFG_MERGE_NODE_TEST_DATA
-from ..mock_kprint import MockKPrint
 from ..test_kcfg import edge_dicts, node_dicts, split_dicts, to_csubst_node, x_config, x_node, x_subst
 
 if TYPE_CHECKING:
@@ -337,7 +339,9 @@ def x_lt(n: int) -> KApply:
 
     minimizer.minimize()
 
-    kcfg_show = KCFGShow(MockKPrint(), node_printer=NodePrinter(MockKPrint()))
+    definition = KDefinition('MOCK', [KFlatModule('MOCK', [])], [])
+    cterm_show = CTermShow(PrettyPrinter(definition).print)
+    kcfg_show = KCFGShow(definition, node_printer=NodePrinter(cterm_show))
     actual = '\n'.join(kcfg_show.pretty(cfg)) + '\n'
 
     expected = (
diff --git a/pyk/src/tests/unit/mock_kprint.py b/pyk/src/tests/unit/mock_kprint.py
deleted file mode 100644
index e36664f7485..00000000000
--- a/pyk/src/tests/unit/mock_kprint.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from __future__ import annotations
-
-from functools import cached_property
-from typing import TYPE_CHECKING
-
-from pyk.kast.outer import KDefinition, KFlatModule
-from pyk.ktool.kprint import KPrint
-
-if TYPE_CHECKING:
-    from collections.abc import Callable, Iterable
-
-    from pyk.kast.pretty import SymbolTable
-
-
-class MockKPrint(KPrint):
-    _patch_symbol_table: Callable[[SymbolTable], None] | None
-    _extra_unparsing_modules: Iterable[KFlatModule]
-
-    def __init__(self) -> None:  # TODO should be unnecessary
-        self._patch_symbol_table = None
-        self._extra_unparsing_modules = ()
-
-    @cached_property
-    def definition(self) -> KDefinition:
-        return KDefinition('MOCK', [KFlatModule('MOCK', [])], [])
diff --git a/pyk/src/tests/unit/test_kcfg.py b/pyk/src/tests/unit/test_kcfg.py
index 204d37d2469..f9dc69d1b0c 100644
--- a/pyk/src/tests/unit/test_kcfg.py
+++ b/pyk/src/tests/unit/test_kcfg.py
@@ -5,17 +5,19 @@
 import pytest
 
 from pyk.cterm import CSubst, CTerm
+from pyk.cterm.show import CTermShow
 from pyk.kast.inner import KApply, KRewrite, KToken, KVariable, Subst
 from pyk.kast.manip import no_cell_rewrite_to_dots
+from pyk.kast.outer import KDefinition, KFlatModule
 from pyk.kast.prelude.kint import INT
 from pyk.kast.prelude.ml import mlEquals, mlTop
 from pyk.kast.prelude.utils import token
+from pyk.kast.pretty import PrettyPrinter
 from pyk.kcfg import KCFG, KCFGShow
 from pyk.kcfg.kcfg import KCFGNodeAttr
 from pyk.kcfg.show import NodePrinter
 from pyk.utils import not_none, single
 
-from .mock_kprint import MockKPrint
 from .utils import ge_ml, k, lt_ml
 
 if TYPE_CHECKING:
@@ -821,8 +823,10 @@ def test_pretty_print() -> None:
     )
 
     # When
-    kcfg_show = KCFGShow(MockKPrint(), node_printer=NodePrinter(MockKPrint()))
-    kcfg_show_full_printer = KCFGShow(MockKPrint(), node_printer=NodePrinter(MockKPrint(), full_printer=True))
+    definition = KDefinition('MOCK', [KFlatModule('MOCK', [])], [])
+    cterm_show = CTermShow(PrettyPrinter(definition).print, minimize=False)
+    kcfg_show = KCFGShow(definition, node_printer=NodePrinter(cterm_show))
+    kcfg_show_full_printer = KCFGShow(definition, node_printer=NodePrinter(cterm_show, full_printer=True))
     actual = '\n'.join(kcfg_show.pretty(cfg)) + '\n'
     actual_full_printer = '\n'.join(kcfg_show_full_printer.pretty(cfg)) + '\n'
 


Reviews:
user: login: tothtamas28
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T08:29:19Z
user: login: ehildenb
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T14:11:27Z
user: login: ehildenb
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T14:13:08Z
user: login: ehildenb
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T14:15:17Z
user: login: ehildenb
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T14:16:07Z
user: login: ehildenb
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T15:31:28Z
user: login: ehildenb
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T15:40:48Z
user: login: tothtamas28
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T15:45:41Z
user: login: tothtamas28
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T15:49:38Z
user: login: tothtamas28
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T16:41:26Z
user: login: ehildenb
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T16:18:46Z
user: login: ehildenb
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T16:18:55Z
user: login: ehildenb
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T16:19:01Z
user: login: ehildenb
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T16:19:09Z
user: login: ehildenb
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T16:19:55Z
user: login: ehildenb
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T16:22:34Z
user: login: ehildenb
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T17:19:32Z
user: login: tothtamas28
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T18:23:45Z
user: login: tothtamas28
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T18:25:16Z

Review comments:
diff_hunk: @@ -0,0 +1,112 @@
+from __future__ import annotations
+
+import logging
+from typing import TYPE_CHECKING
+
+from ..kast.inner import KApply, KSort, KToken, flatten_label, top_down
+from ..kast.manip import free_vars, minimize_term
+from ..kast.prelude.k import DOTS
+from ..kast.pretty import PrettyPrinter
+from .cterm import CTerm
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Iterable
+    from typing import Final
+
+    from ..kast.inner import KInner
+    from ..kast.outer import KDefinition, KFlatModule
+    from ..kast.pretty import SymbolTable
+
+_LOGGER: Final = logging.getLogger(__name__)
+
+
+class CTermShow(PrettyPrinter):
path: pyk/src/pyk/cterm/show.py
user: login: tothtamas28
body: IIUC `CTermShow` is for printing `CTerm` instances, whereas `PrettyPrinter` is for printing `KAst` instances, so in this case composition is sufficient I think:

```suggestion
@final
class CTermShow:
    printer: PrettyPrinter
```

Then you can probably remove

```
definition
extra_unparsing_modules
patch_symbol_table
unalias
sort_collections
```

from the `__init__` argument list.
created_at: 2025-05-16T08:00:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,112 @@
+from __future__ import annotations
+
+import logging
+from typing import TYPE_CHECKING
+
+from ..kast.inner import KApply, KSort, KToken, flatten_label, top_down
+from ..kast.manip import free_vars, minimize_term
+from ..kast.prelude.k import DOTS
+from ..kast.pretty import PrettyPrinter
+from .cterm import CTerm
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Iterable
+    from typing import Final
+
+    from ..kast.inner import KInner
+    from ..kast.outer import KDefinition, KFlatModule
+    from ..kast.pretty import SymbolTable
+
+_LOGGER: Final = logging.getLogger(__name__)
+
+
+class CTermShow(PrettyPrinter):
path: pyk/src/pyk/cterm/show.py
user: login: tothtamas28
body: In fact, I'd reduce coupling between these classes a bit further: all this class requires is a `Callable[[KInner], str]]`. So I'd store that as an attribute, and provide something like

```python3
@staticmethod
def from_pretty_printer(printer: PrettyPrinter) -> CTermShow: ...
```

that creates the appropriate closure from the `PrettyPrinter`. Similarly, we can provide

```python3
@staticmethod
def from_formatter(formatter: Formatter) -> CTermShow: ...
```

and other implementations as needed.
created_at: 2025-05-16T08:06:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,31 +32,26 @@
     from ..cterm import CSubst
     from ..kast import KInner
     from ..kast.outer import KDefinition, KFlatModule, KImport, KSentence
-    from ..ktool.kprint import KPrint
     from .kcfg import NodeIdLike
 
 _LOGGER: Final = logging.getLogger(__name__)
 
 
 class NodePrinter:
-    kprint: KPrint
+    cterm_show: CTermShow
path: pyk/src/pyk/kcfg/show.py
user: login: tothtamas28
body: Target for future refactorings: this class should be made abstract, as what details I want to show for a node (and how) varies from definition to definition (and even for a given definition there might be more than a single useful view). Displaying a `CTerm` as in this current implementation is a reasonable default implementation.

Also, the abstract interface should probably define separate abstract functions for providing a list of labels that should be shown in the tree, and for displaying the actual node content.
created_at: 2025-05-16T08:10:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,12 +71,12 @@ def node_attrs(self, kcfg: KCFG, node: KCFG.Node) -> list[str]:
 
 
 class KCFGShow:
-    kprint: KPrint
+    pretty_printer: PrettyPrinter
     node_printer: NodePrinter
path: pyk/src/pyk/kcfg/show.py
user: login: tothtamas28
body: What's the difference between `pretty_printer` and `node_printer`? One is for displaying edges, the other is for nodes?
created_at: 2025-05-16T08:12:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -173,8 +173,9 @@ def on_click(self, click: Click) -> None:
 
 
 class NodeView(Widget):
-    _kprint: KPrint
+    _definition: KDefinition
     _custom_view: Callable[[KCFGElem], Iterable[str]] | None
+    _cterm_show: CTermShow
path: pyk/src/pyk/kcfg/tui.py
user: login: tothtamas28
body: Target for future refactorings: abstract interfaces should be extracted.
created_at: 2025-05-16T08:13:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,112 @@
+from __future__ import annotations
+
+import logging
+from typing import TYPE_CHECKING
+
+from ..kast.inner import KApply, KSort, KToken, flatten_label, top_down
+from ..kast.manip import free_vars, minimize_term
+from ..kast.prelude.k import DOTS
+from ..kast.pretty import PrettyPrinter
+from .cterm import CTerm
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Iterable
+    from typing import Final
+
+    from ..kast.inner import KInner
+    from ..kast.outer import KDefinition, KFlatModule
+    from ..kast.pretty import SymbolTable
+
+_LOGGER: Final = logging.getLogger(__name__)
+
+
+class CTermShow(PrettyPrinter):
+    minimize: bool
+    break_cell_collections: bool
+    omit_labels: tuple[str, ...]
+
+    def __init__(
+        self,
+        definition: KDefinition,
+        extra_unparsing_modules: Iterable[KFlatModule] = (),
+        patch_symbol_table: Callable[[SymbolTable], None] | None = None,
+        unalias: bool = True,
+        sort_collections: bool = True,
+        minimize: bool = True,
+        break_cell_collections: bool = True,
+        omit_labels: Iterable[str] = (),
+    ):
+        super().__init__(
+            definition,
+            extra_unparsing_modules,
+            patch_symbol_table,
+            unalias,
+            sort_collections,
+        )
+        self.minimize = minimize
+        self.break_cell_collections = break_cell_collections
+        self.omit_labels = tuple(omit_labels)
+
+    def let(
+        self,
+        unalias: bool | None = None,
+        sort_collections: bool | None = None,
+        minimize: bool | None = None,
+        break_cell_collections: bool | None = None,
+        omit_labels: Iterable[str] | None = None,
+    ) -> CTermShow:
+        return CTermShow(
+            self.definition,
+            extra_unparsing_modules=self._extra_unparsing_modules,
+            patch_symbol_table=self._patch_symbol_table,
+            unalias=(self._unalias if unalias is None else unalias),
+            sort_collections=(self._sort_collections if sort_collections is None else sort_collections),
+            minimize=(self.minimize if minimize is None else minimize),
+            break_cell_collections=(
+                self.break_cell_collections if break_cell_collections is None else break_cell_collections
+            ),
+            omit_labels=(self.omit_labels if omit_labels is None else omit_labels),
+        )
+
+    def _break_cell_collections(self, kast: KInner) -> KInner:
+        if (
+            type(kast) is KApply
+            and kast.is_cell
+            and len(kast.args) == 1
+            and type(kast.args[0]) is KApply
+            and kast.args[0].label.name in {'_Set_', '_List_', '_Map_'}
+        ):
+            items = flatten_label(kast.args[0].label.name, kast.args[0])
+            printed = KToken('\n'.join(map(self.print, items)), KSort(kast.label.name[1:-1]))
+            return KApply(kast.label, [printed])
+        return kast
+
+    def _hide_labels(self, kast: KInner) -> KInner:
+        if type(kast) == KApply and kast.label.name in self.omit_labels:
+            return DOTS
+        return kast
+
+    def show_config(self, cterm: CTerm) -> list[str]:
+        if self.break_cell_collections:
+            cterm = CTerm(top_down(self._break_cell_collections, cterm.config), cterm.constraints)
+        if self.omit_labels:
+            cterm = CTerm(top_down(self._hide_labels, cterm.config), cterm.constraints)
+        if self.minimize:
+            cterm = CTerm(minimize_term(cterm.config, keep_vars=free_vars(cterm.constraint)), cterm.constraints)
+        return self.print(cterm.config).split('\n')
+
+    def show_constraints(self, cterm: CTerm) -> list[str]:
+        ret_strs: list[str] = []
+        for constraint in cterm.constraints:
+            constraint_strs = self.print(constraint).split('\n')
+            if len(constraint_strs) > 0:
+                constraint_strs = [f'#And {cstr}' for cstr in constraint_strs]
+                ret_strs.append(constraint_strs[0])
+                ret_strs.extend([f'  {constraint_str}' for constraint_str in constraint_strs[1:]])
+        return ret_strs
+
+    def show(self, cterm: CTerm) -> list[str]:
path: pyk/src/pyk/cterm/show.py
user: login: tothtamas28
body: Consider reordering these functions: public functions on top, private helpers after.

```
show
show_config
show_constraints
_break_cell_collections
_hide_labels
```
created_at: 2025-05-16T08:21:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,112 @@
+from __future__ import annotations
+
+import logging
+from typing import TYPE_CHECKING
+
+from ..kast.inner import KApply, KSort, KToken, flatten_label, top_down
+from ..kast.manip import free_vars, minimize_term
+from ..kast.prelude.k import DOTS
+from ..kast.pretty import PrettyPrinter
+from .cterm import CTerm
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Iterable
+    from typing import Final
+
+    from ..kast.inner import KInner
+    from ..kast.outer import KDefinition, KFlatModule
+    from ..kast.pretty import SymbolTable
+
+_LOGGER: Final = logging.getLogger(__name__)
+
+
+class CTermShow(PrettyPrinter):
+    minimize: bool
+    break_cell_collections: bool
+    omit_labels: tuple[str, ...]
+
+    def __init__(
+        self,
+        definition: KDefinition,
+        extra_unparsing_modules: Iterable[KFlatModule] = (),
+        patch_symbol_table: Callable[[SymbolTable], None] | None = None,
+        unalias: bool = True,
+        sort_collections: bool = True,
+        minimize: bool = True,
+        break_cell_collections: bool = True,
+        omit_labels: Iterable[str] = (),
+    ):
+        super().__init__(
+            definition,
+            extra_unparsing_modules,
+            patch_symbol_table,
+            unalias,
+            sort_collections,
+        )
+        self.minimize = minimize
+        self.break_cell_collections = break_cell_collections
+        self.omit_labels = tuple(omit_labels)
+
+    def let(
path: pyk/src/pyk/cterm/show.py
user: login: tothtamas28
body: If `PrettyPrinter` or the `Callable` is made an attribute, then this function can be removed altogether: the user can either set the remaining parameters (`minimize`, `break_cell_collections`, `omit_labels`) directly, or they can be made arguments of the `show*` methods.
created_at: 2025-05-16T08:28:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,31 +32,26 @@
     from ..cterm import CSubst
     from ..kast import KInner
     from ..kast.outer import KDefinition, KFlatModule, KImport, KSentence
-    from ..ktool.kprint import KPrint
     from .kcfg import NodeIdLike
 
 _LOGGER: Final = logging.getLogger(__name__)
 
 
 class NodePrinter:
-    kprint: KPrint
+    cterm_show: CTermShow
path: pyk/src/pyk/kcfg/show.py
user: login: ehildenb
body: The next refactoring does remove the actual node content display (which is fully moved into `CTermShow`). I was hoping to entirely remove the `NodePrinter` hierarchy, but it's somewhat complicated by the fact that `KCFGShow.pretty_segments` and `APRProofShow.pretty_segments` both take a `KCFG` and need to print node attrs, but we want to print different node attrs depending on if it's a `KCFG` or an `APRProof`. So I'm a bit stuck there, but at least we can totally lift the printing out of `NodePrinter` later (and I'll get that change in once this PR is propagated downstream).
created_at: 2025-05-16T14:11:27Z
author_association: MEMBER
diff_hunk: @@ -74,12 +71,12 @@ def node_attrs(self, kcfg: KCFG, node: KCFG.Node) -> list[str]:
 
 
 class KCFGShow:
-    kprint: KPrint
+    pretty_printer: PrettyPrinter
     node_printer: NodePrinter
path: pyk/src/pyk/kcfg/show.py
user: login: ehildenb
body: Ideally, we wouldn't have `NodePrinter` at all. This change is what I could do to lower from `KPrint` to `KDefinition` in this class now. Later refactoring will remove the `print_node` from `NodePrinter`, and then it will be more like `NodeAttrs` instead, which ideally would be inlined directly into `KCFG` class (thus removing the `NodePrinter` heirarchy). For now, I know I can remove `NodePrinter.print_node`, but not sure about inlining into `KCFG`.
created_at: 2025-05-16T14:13:08Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,112 @@
+from __future__ import annotations
+
+import logging
+from typing import TYPE_CHECKING
+
+from ..kast.inner import KApply, KSort, KToken, flatten_label, top_down
+from ..kast.manip import free_vars, minimize_term
+from ..kast.prelude.k import DOTS
+from ..kast.pretty import PrettyPrinter
+from .cterm import CTerm
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Iterable
+    from typing import Final
+
+    from ..kast.inner import KInner
+    from ..kast.outer import KDefinition, KFlatModule
+    from ..kast.pretty import SymbolTable
+
+_LOGGER: Final = logging.getLogger(__name__)
+
+
+class CTermShow(PrettyPrinter):
+    minimize: bool
+    break_cell_collections: bool
+    omit_labels: tuple[str, ...]
+
+    def __init__(
+        self,
+        definition: KDefinition,
+        extra_unparsing_modules: Iterable[KFlatModule] = (),
+        patch_symbol_table: Callable[[SymbolTable], None] | None = None,
+        unalias: bool = True,
+        sort_collections: bool = True,
+        minimize: bool = True,
+        break_cell_collections: bool = True,
+        omit_labels: Iterable[str] = (),
+    ):
+        super().__init__(
+            definition,
+            extra_unparsing_modules,
+            patch_symbol_table,
+            unalias,
+            sort_collections,
+        )
+        self.minimize = minimize
+        self.break_cell_collections = break_cell_collections
+        self.omit_labels = tuple(omit_labels)
+
+    def let(
+        self,
+        unalias: bool | None = None,
+        sort_collections: bool | None = None,
+        minimize: bool | None = None,
+        break_cell_collections: bool | None = None,
+        omit_labels: Iterable[str] | None = None,
+    ) -> CTermShow:
+        return CTermShow(
+            self.definition,
+            extra_unparsing_modules=self._extra_unparsing_modules,
+            patch_symbol_table=self._patch_symbol_table,
+            unalias=(self._unalias if unalias is None else unalias),
+            sort_collections=(self._sort_collections if sort_collections is None else sort_collections),
+            minimize=(self.minimize if minimize is None else minimize),
+            break_cell_collections=(
+                self.break_cell_collections if break_cell_collections is None else break_cell_collections
+            ),
+            omit_labels=(self.omit_labels if omit_labels is None else omit_labels),
+        )
+
+    def _break_cell_collections(self, kast: KInner) -> KInner:
+        if (
+            type(kast) is KApply
+            and kast.is_cell
+            and len(kast.args) == 1
+            and type(kast.args[0]) is KApply
+            and kast.args[0].label.name in {'_Set_', '_List_', '_Map_'}
+        ):
+            items = flatten_label(kast.args[0].label.name, kast.args[0])
+            printed = KToken('\n'.join(map(self.print, items)), KSort(kast.label.name[1:-1]))
+            return KApply(kast.label, [printed])
+        return kast
+
+    def _hide_labels(self, kast: KInner) -> KInner:
+        if type(kast) == KApply and kast.label.name in self.omit_labels:
+            return DOTS
+        return kast
+
+    def show_config(self, cterm: CTerm) -> list[str]:
+        if self.break_cell_collections:
+            cterm = CTerm(top_down(self._break_cell_collections, cterm.config), cterm.constraints)
+        if self.omit_labels:
+            cterm = CTerm(top_down(self._hide_labels, cterm.config), cterm.constraints)
+        if self.minimize:
+            cterm = CTerm(minimize_term(cterm.config, keep_vars=free_vars(cterm.constraint)), cterm.constraints)
+        return self.print(cterm.config).split('\n')
+
+    def show_constraints(self, cterm: CTerm) -> list[str]:
+        ret_strs: list[str] = []
+        for constraint in cterm.constraints:
+            constraint_strs = self.print(constraint).split('\n')
+            if len(constraint_strs) > 0:
+                constraint_strs = [f'#And {cstr}' for cstr in constraint_strs]
+                ret_strs.append(constraint_strs[0])
+                ret_strs.extend([f'  {constraint_str}' for constraint_str in constraint_strs[1:]])
+        return ret_strs
+
+    def show(self, cterm: CTerm) -> list[str]:
path: pyk/src/pyk/cterm/show.py
user: login: ehildenb
body: done
created_at: 2025-05-16T14:15:17Z
author_association: MEMBER
diff_hunk: @@ -173,8 +173,9 @@ def on_click(self, click: Click) -> None:
 
 
 class NodeView(Widget):
-    _kprint: KPrint
+    _definition: KDefinition
     _custom_view: Callable[[KCFGElem], Iterable[str]] | None
+    _cterm_show: CTermShow
path: pyk/src/pyk/kcfg/tui.py
user: login: ehildenb
body: I agree. I'm playing with that a bit on my machine, but I think we need a bit of a planning session around that. Currently, this change is an improvement that gets us some benefit and a slighlty cleaner interface, and I have a few more improvements planned, but I need to propagate it downstream before going too far I think.
created_at: 2025-05-16T14:16:06Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,112 @@
+from __future__ import annotations
+
+import logging
+from typing import TYPE_CHECKING
+
+from ..kast.inner import KApply, KSort, KToken, flatten_label, top_down
+from ..kast.manip import free_vars, minimize_term
+from ..kast.prelude.k import DOTS
+from ..kast.pretty import PrettyPrinter
+from .cterm import CTerm
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Iterable
+    from typing import Final
+
+    from ..kast.inner import KInner
+    from ..kast.outer import KDefinition, KFlatModule
+    from ..kast.pretty import SymbolTable
+
+_LOGGER: Final = logging.getLogger(__name__)
+
+
+class CTermShow(PrettyPrinter):
path: pyk/src/pyk/cterm/show.py
user: login: ehildenb
body: Ok, I've done this. Instead of providing static methods on `CTermShow` instead I've added a `property PrettyPrinter.printer` and `property Formatter.printer`, so it's easy to get the needed `Callable[[KInner], list[str]]` directly out of those classes for constructing the `CTermShow`.
created_at: 2025-05-16T15:31:28Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,112 @@
+from __future__ import annotations
+
+import logging
+from typing import TYPE_CHECKING
+
+from ..kast.inner import KApply, KSort, KToken, flatten_label, top_down
+from ..kast.manip import free_vars, minimize_term
+from ..kast.prelude.k import DOTS
+from ..kast.pretty import PrettyPrinter
+from .cterm import CTerm
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Iterable
+    from typing import Final
+
+    from ..kast.inner import KInner
+    from ..kast.outer import KDefinition, KFlatModule
+    from ..kast.pretty import SymbolTable
+
+_LOGGER: Final = logging.getLogger(__name__)
+
+
+class CTermShow(PrettyPrinter):
+    minimize: bool
+    break_cell_collections: bool
+    omit_labels: tuple[str, ...]
+
+    def __init__(
+        self,
+        definition: KDefinition,
+        extra_unparsing_modules: Iterable[KFlatModule] = (),
+        patch_symbol_table: Callable[[SymbolTable], None] | None = None,
+        unalias: bool = True,
+        sort_collections: bool = True,
+        minimize: bool = True,
+        break_cell_collections: bool = True,
+        omit_labels: Iterable[str] = (),
+    ):
+        super().__init__(
+            definition,
+            extra_unparsing_modules,
+            patch_symbol_table,
+            unalias,
+            sort_collections,
+        )
+        self.minimize = minimize
+        self.break_cell_collections = break_cell_collections
+        self.omit_labels = tuple(omit_labels)
+
+    def let(
path: pyk/src/pyk/cterm/show.py
user: login: ehildenb
body: It would be nice for it to be possible to pass these arguments directyl to teh `show`. But I think overall, it ends up being less code to just set them once at class construction and go from there. Otherwise, all the points that are holding onto a `CTermShow` for printing need to also hold teh arguments `minimize` and others to determine how to print _that time_, instead of the user setting it up once up front.
created_at: 2025-05-16T15:40:47Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,112 @@
+from __future__ import annotations
+
+import logging
+from typing import TYPE_CHECKING
+
+from ..kast.inner import KApply, KSort, KToken, flatten_label, top_down
+from ..kast.manip import free_vars, minimize_term
+from ..kast.prelude.k import DOTS
+from ..kast.pretty import PrettyPrinter
+from .cterm import CTerm
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Iterable
+    from typing import Final
+
+    from ..kast.inner import KInner
+    from ..kast.outer import KDefinition, KFlatModule
+    from ..kast.pretty import SymbolTable
+
+_LOGGER: Final = logging.getLogger(__name__)
+
+
+class CTermShow(PrettyPrinter):
path: pyk/src/pyk/cterm/show.py
user: login: tothtamas28
body: > Instead of providing static methods on `CTermShow` instead I've added a property `PrettyPrinter.printer` and property `Formatter.printer`

1. I think the interface should be `Callable[[KInner], str]` as it is simpler (`Formatter` is already a `Callable[[KInner], str]` so it could even be passed directly). Also, since the `split` has to be done in both providers, it could be done internally `CTermShow` where necessary (by calling a helper `_print_lines` or similar).

2. I think the `staticmethod` approach is cleaner: when reading the code it is immediately obvious what the intention is. On the other hand, if you add `printer` methods, in a way, you introduce a dependency in the wrong direction.
created_at: 2025-05-16T15:45:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,112 @@
+from __future__ import annotations
+
+import logging
+from typing import TYPE_CHECKING
+
+from ..kast.inner import KApply, KSort, KToken, flatten_label, top_down
+from ..kast.manip import free_vars, minimize_term
+from ..kast.prelude.k import DOTS
+from ..kast.pretty import PrettyPrinter
+from .cterm import CTerm
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Iterable
+    from typing import Final
+
+    from ..kast.inner import KInner
+    from ..kast.outer import KDefinition, KFlatModule
+    from ..kast.pretty import SymbolTable
+
+_LOGGER: Final = logging.getLogger(__name__)
+
+
+class CTermShow(PrettyPrinter):
+    minimize: bool
+    break_cell_collections: bool
+    omit_labels: tuple[str, ...]
+
+    def __init__(
+        self,
+        definition: KDefinition,
+        extra_unparsing_modules: Iterable[KFlatModule] = (),
+        patch_symbol_table: Callable[[SymbolTable], None] | None = None,
+        unalias: bool = True,
+        sort_collections: bool = True,
+        minimize: bool = True,
+        break_cell_collections: bool = True,
+        omit_labels: Iterable[str] = (),
+    ):
+        super().__init__(
+            definition,
+            extra_unparsing_modules,
+            patch_symbol_table,
+            unalias,
+            sort_collections,
+        )
+        self.minimize = minimize
+        self.break_cell_collections = break_cell_collections
+        self.omit_labels = tuple(omit_labels)
+
+    def let(
path: pyk/src/pyk/cterm/show.py
user: login: tothtamas28
body: Makes sense.
created_at: 2025-05-16T15:49:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -76,6 +77,10 @@ def format(self, term: KInner) -> str:
             term = add_brackets(self.definition, term)
         return ''.join(self._format(term))
 
+    @property
+    def printer(self) -> Callable[[KInner], list[str]]:
+        return lambda k: self.format(k).split('\n')
path: pyk/src/pyk/kast/formatter.py
user: login: tothtamas28
body: This feels like an implicit dependency in the wrong direction: this method is only introduced so that `CTermShow` can be instantiated with a `Fomatter`.

Instead, consider the `@staticmethod` approach, which makes the dependency between these two classes explicit.
created_at: 2025-05-16T16:33:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -76,6 +76,10 @@ def symbol_table(self) -> SymbolTable:
             self._patch_symbol_table(symb_table)
         return symb_table
 
+    @property
+    def printer(self) -> Callable[[KInner], list[str]]:
+        return lambda k: self.print(k).split('\n')
+
path: pyk/src/pyk/kast/pretty.py
user: login: tothtamas28
body: Same here.
created_at: 2025-05-16T16:33:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,97 @@
+from __future__ import annotations
+
+import logging
+from typing import TYPE_CHECKING
+
+from ..kast.inner import KApply, KSort, KToken, flatten_label, top_down
+from ..kast.manip import free_vars, minimize_term
+from ..kast.prelude.k import DOTS
+from .cterm import CTerm
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Iterable
+    from typing import Final
+
+    from ..kast.inner import KInner
+
+_LOGGER: Final = logging.getLogger(__name__)
+
+
+class CTermShow:
+    _printer: Callable[[KInner], list[str]]
path: pyk/src/pyk/cterm/show.py
user: login: tothtamas28
body: Consider

```suggestion
    _printer: Callable[[KInner], str]
```

Reason:
* It's a simpler interface.
* Both `Formatter` and `PrettyPrniter` produce `str`. (In fact, `Formatter` is already a `Callable[[KInner], str]`)

Then `print` can be replaced by `_print_lines` which does the split.
created_at: 2025-05-16T16:38:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,97 @@
+from __future__ import annotations
+
+import logging
+from typing import TYPE_CHECKING
+
+from ..kast.inner import KApply, KSort, KToken, flatten_label, top_down
+from ..kast.manip import free_vars, minimize_term
+from ..kast.prelude.k import DOTS
+from .cterm import CTerm
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Iterable
+    from typing import Final
+
+    from ..kast.inner import KInner
+
+_LOGGER: Final = logging.getLogger(__name__)
+
+
+class CTermShow:
path: pyk/src/pyk/cterm/show.py
user: login: tothtamas28
body: ```suggestion
@dataclass
class CTermShow:
```
created_at: 2025-05-16T16:38:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,97 @@
+from __future__ import annotations
+
+import logging
+from typing import TYPE_CHECKING
+
+from ..kast.inner import KApply, KSort, KToken, flatten_label, top_down
+from ..kast.manip import free_vars, minimize_term
+from ..kast.prelude.k import DOTS
+from .cterm import CTerm
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Iterable
+    from typing import Final
+
+    from ..kast.inner import KInner
+
+_LOGGER: Final = logging.getLogger(__name__)
+
+
+class CTermShow:
+    _printer: Callable[[KInner], list[str]]
+    minimize: bool
+    break_cell_collections: bool
+    omit_labels: tuple[str, ...]
path: pyk/src/pyk/cterm/show.py
user: login: tothtamas28
body: I'd make these private too.
created_at: 2025-05-16T16:40:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,112 @@
+from __future__ import annotations
+
+import logging
+from typing import TYPE_CHECKING
+
+from ..kast.inner import KApply, KSort, KToken, flatten_label, top_down
+from ..kast.manip import free_vars, minimize_term
+from ..kast.prelude.k import DOTS
+from ..kast.pretty import PrettyPrinter
+from .cterm import CTerm
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Iterable
+    from typing import Final
+
+    from ..kast.inner import KInner
+    from ..kast.outer import KDefinition, KFlatModule
+    from ..kast.pretty import SymbolTable
+
+_LOGGER: Final = logging.getLogger(__name__)
+
+
+class CTermShow(PrettyPrinter):
path: pyk/src/pyk/cterm/show.py
user: login: ehildenb
body: I switched to `Callable[[KInner], str]`, which makes it so that I can just pass `PrettyPrinter.print` and `Formatter.print` directly as teh arguments to `CTermShow` constructor. Because this is pretty straightforward, I didn't add the `@staticmethod def from_...` options (it's not really any shorter).
created_at: 2025-05-20T16:18:46Z
author_association: MEMBER
diff_hunk: @@ -76,6 +77,10 @@ def format(self, term: KInner) -> str:
             term = add_brackets(self.definition, term)
         return ''.join(self._format(term))
 
+    @property
+    def printer(self) -> Callable[[KInner], list[str]]:
+        return lambda k: self.format(k).split('\n')
path: pyk/src/pyk/kast/formatter.py
user: login: ehildenb
body: I removed this.
created_at: 2025-05-20T16:18:55Z
author_association: MEMBER
diff_hunk: @@ -76,6 +76,10 @@ def symbol_table(self) -> SymbolTable:
             self._patch_symbol_table(symb_table)
         return symb_table
 
+    @property
+    def printer(self) -> Callable[[KInner], list[str]]:
+        return lambda k: self.print(k).split('\n')
+
path: pyk/src/pyk/kast/pretty.py
user: login: ehildenb
body: Removed.
created_at: 2025-05-20T16:19:01Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,97 @@
+from __future__ import annotations
+
+import logging
+from typing import TYPE_CHECKING
+
+from ..kast.inner import KApply, KSort, KToken, flatten_label, top_down
+from ..kast.manip import free_vars, minimize_term
+from ..kast.prelude.k import DOTS
+from .cterm import CTerm
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Iterable
+    from typing import Final
+
+    from ..kast.inner import KInner
+
+_LOGGER: Final = logging.getLogger(__name__)
+
+
+class CTermShow:
+    _printer: Callable[[KInner], list[str]]
path: pyk/src/pyk/cterm/show.py
user: login: ehildenb
body: Done.
created_at: 2025-05-20T16:19:09Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,97 @@
+from __future__ import annotations
+
+import logging
+from typing import TYPE_CHECKING
+
+from ..kast.inner import KApply, KSort, KToken, flatten_label, top_down
+from ..kast.manip import free_vars, minimize_term
+from ..kast.prelude.k import DOTS
+from .cterm import CTerm
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Iterable
+    from typing import Final
+
+    from ..kast.inner import KInner
+
+_LOGGER: Final = logging.getLogger(__name__)
+
+
+class CTermShow:
path: pyk/src/pyk/cterm/show.py
user: login: ehildenb
body: Done.
created_at: 2025-05-20T16:19:54Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,97 @@
+from __future__ import annotations
+
+import logging
+from typing import TYPE_CHECKING
+
+from ..kast.inner import KApply, KSort, KToken, flatten_label, top_down
+from ..kast.manip import free_vars, minimize_term
+from ..kast.prelude.k import DOTS
+from .cterm import CTerm
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Iterable
+    from typing import Final
+
+    from ..kast.inner import KInner
+
+_LOGGER: Final = logging.getLogger(__name__)
+
+
+class CTermShow:
+    _printer: Callable[[KInner], list[str]]
+    minimize: bool
+    break_cell_collections: bool
+    omit_labels: tuple[str, ...]
path: pyk/src/pyk/cterm/show.py
user: login: ehildenb
body: Done.
created_at: 2025-05-20T16:22:34Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,112 @@
+from __future__ import annotations
+
+import logging
+from typing import TYPE_CHECKING
+
+from ..kast.inner import KApply, KSort, KToken, flatten_label, top_down
+from ..kast.manip import free_vars, minimize_term
+from ..kast.prelude.k import DOTS
+from ..kast.pretty import PrettyPrinter
+from .cterm import CTerm
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Iterable
+    from typing import Final
+
+    from ..kast.inner import KInner
+    from ..kast.outer import KDefinition, KFlatModule
+    from ..kast.pretty import SymbolTable
+
+_LOGGER: Final = logging.getLogger(__name__)
+
+
+class CTermShow(PrettyPrinter):
path: pyk/src/pyk/cterm/show.py
user: login: ehildenb
body: One benefit of `list[str]` as a return is that we can represent the empty return (no lines returned), whereas `''` always it at least one line returned.
created_at: 2025-05-20T17:19:32Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,112 @@
+from __future__ import annotations
+
+import logging
+from typing import TYPE_CHECKING
+
+from ..kast.inner import KApply, KSort, KToken, flatten_label, top_down
+from ..kast.manip import free_vars, minimize_term
+from ..kast.prelude.k import DOTS
+from ..kast.pretty import PrettyPrinter
+from .cterm import CTerm
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Iterable
+    from typing import Final
+
+    from ..kast.inner import KInner
+    from ..kast.outer import KDefinition, KFlatModule
+    from ..kast.pretty import SymbolTable
+
+_LOGGER: Final = logging.getLogger(__name__)
+
+
+class CTermShow(PrettyPrinter):
path: pyk/src/pyk/cterm/show.py
user: login: tothtamas28
body: > Because this is pretty straightforward, I didn't add the `@staticmethod`

Makes sense.

> I can just pass `PrettyPrinter.print` and `Formatter.print`

For `Formatter`, you can even pass the instance directly. The same can be achieved for `PrettyPrinter` by implementing `__call__` with the right signature.
created_at: 2025-05-20T18:23:45Z
author_association: CONTRIBUTOR

PR comments:
user: login: ehildenb
created_at: 2025-05-06T17:55:08Z
body: On my local version, I'm able to remove the dependency from `kcfg -> ktool`. This relies on removing the use of `KPrint` for rendering nodes and such in the `show/tui` modules.

Original `develop`:

![k-before](https://github.com/user-attachments/assets/767d25c5-30ae-4f05-83dc-dda1769193f1)

And after:

![k](https://github.com/user-attachments/assets/9e3fda47-89ad-4cae-add7-72ed14b56886)

This PR is the  first step in that refactoring.
author_association: MEMBER
user: login: ehildenb
created_at: 2025-05-06T18:06:11Z
body: Downstream repos to open branches into:

- [x] https://github.com/runtimeverification/evm-semantics
  - Update: https://github.com/runtimeverification/evm-semantics/pull/2751
  - To this PR: https://github.com/runtimeverification/evm-semantics/pull/2764
- [x] https://github.com/runtimeverification/mir-semantics
  - Update: https://github.com/runtimeverification/mir-semantics/pull/565
  - To this PR: https://github.com/runtimeverification/mir-semantics/pull/585
- [x] https://github.com/runtimeverification/wasm-semantics
  - Update: https://github.com/runtimeverification/wasm-semantics/pull/724
  - To this PR: https://github.com/runtimeverification/wasm-semantics/pull/725
- [x] https://github.com/runtimeverification/riscv-semantics
  - Update: https://github.com/runtimeverification/riscv-semantics/pull/86
  - To this PR: https://github.com/runtimeverification/riscv-semantics/pull/113
- [x] https://github.com/runtimeverification/skribe
  - Update: https://github.com/runtimeverification/skribe/pull/12
  - To this PR: https://github.com/runtimeverification/skribe/pull/14
- [x] https://github.com/runtimeverification/komet
  - Update: https://github.com/runtimeverification/komet/pull/79
  - To this PR: https://github.com/runtimeverification/komet/pull/80
- [x] https://github.com/runtimeverification/kontrol
  - Update: https://github.com/runtimeverification/kontrol/pull/1008
  - To this PR: https://github.com/runtimeverification/kontrol/pull/1032
author_association: MEMBER
user: login: ehildenb
created_at: 2025-05-12T21:55:11Z
body: Import graphs:

Before:

![k-before](https://github.com/user-attachments/assets/45ace715-3ed7-4fc8-91a1-1c66d832f1bb)

After:

![k](https://github.com/user-attachments/assets/a1fc5659-fcdb-4629-85eb-2f6e4add8c8b)

author_association: MEMBER
user: login: ehildenb
created_at: 2025-05-13T19:58:01Z
body: Adjustments to import graph.

Before:

![k-before](https://github.com/user-attachments/assets/79dd4fe0-4a2f-4ce5-9576-2f5dbf71ce6b)

After:

![k](https://github.com/user-attachments/assets/33b47c11-29f6-4a9f-be73-7cf209f9fce1)

In particular, `kcfg` is no longer importing `ktool`.
author_association: MEMBER
user: login: ehildenb
created_at: 2025-05-15T16:32:15Z
body: I have one more refactoring in mind, which should eliminate the need for eth `NodePrinter` class heirarchy (in favor of customizing `CTermShow` class and passing it around), but that should wait for a follow-up and is a bit more invasive.

Once that refactoring s in, our display heirarchy should be significantly simplified.
author_association: MEMBER
user: login: tothtamas28
created_at: 2025-05-16T09:03:42Z
body: > I have one more refactoring in mind, which should eliminate the need for eth `NodePrinter` class heirarchy (in favor of customizing `CTermShow` class and passing it around), but that should wait for a follow-up and is a bit more invasive.

I see two issues that I think should be addressed when refactoring these classes.

1. Parallel class hierarchies: `KCFGShow`, `KCFGViewer`, `NodePrinter` vs `APRProofShow`, `APRProofViewer`, `APRProofNodePrinter`. My current understanding is that the only difference between these is that when printing a proof, there's a different set of node labels we want to show.

2. Coupling: these printing utilities (`KCFGShow`, `KCFGViewer` and the `APRPRoof` variants) depend on layers of concrete implementations of how a KAST term is printed (`NodePrinter`, which depends on `PrettyPrinter`, which depends on `KDefinition`, ...). Instead, they should define their own interface in terms of abstractions (e.g. a function that takes a `Node` and a `KCFGViewer`-specific set of configuration parameters, and returns a `str` for the node content to be printed). This makes configuration of the tools for a given semantics downstreams much more feasible.
author_association: CONTRIBUTOR
user: login: ehildenb
created_at: 2025-05-16T15:35:31Z
body: 1. I want to tackle this one. I will start by first getting rid of `print_node` in the `NodePrinter` heirarchy. Then it would be nice to handle the `attrs` directly at the `KCFG` level, but this is tricky because the attributes are different based on how you interpret the KCFG (as a raw KCFG or as an `APRProof`).
2. Also agreed. One step/improvement at a time though, this is what I have time for to get KMIR printing to look better, and I want to get it merged downstream all the way first, before introducing more potential breakages.
author_association: MEMBER