|
23 | 23 | ) |
24 | 24 | from ..kast.outer import KFlatModule |
25 | 25 | from ..prelude.kbool import andBool |
26 | | -from ..utils import ensure_dir_path, not_none, single |
| 26 | +from ..utils import ensure_dir_path, not_none |
| 27 | +from .minimize import KCFGMinimizer |
27 | 28 |
|
28 | 29 | if TYPE_CHECKING: |
29 | | - from collections.abc import Callable, Iterable, Mapping, MutableMapping |
| 30 | + from collections.abc import Iterable, Mapping, MutableMapping |
30 | 31 | from pathlib import Path |
31 | 32 | from types import TracebackType |
32 | 33 | from typing import Any |
@@ -957,183 +958,6 @@ def split_on_constraints(self, source_id: NodeIdLike, constraints: Iterable[KInn |
957 | 958 | self.create_split(source.id, zip(branch_node_ids, csubsts, strict=True)) |
958 | 959 | return branch_node_ids |
959 | 960 |
|
960 | | - def lift_edge(self, b_id: NodeIdLike) -> None: |
961 | | - """Lift an edge up another edge directly preceding it. |
962 | | -
|
963 | | - `A --M steps--> B --N steps--> C` becomes `A --(M + N) steps--> C`. Node `B` is removed. |
964 | | -
|
965 | | - Args: |
966 | | - b_id: the identifier of the central node `B` of a sequence of edges `A --> B --> C`. |
967 | | -
|
968 | | - Raises: |
969 | | - AssertionError: If the edges in question are not in place. |
970 | | - """ |
971 | | - # Obtain edges `A -> B`, `B -> C` |
972 | | - a_to_b = single(self.edges(target_id=b_id)) |
973 | | - b_to_c = single(self.edges(source_id=b_id)) |
974 | | - # Remove the node `B`, effectively removing the entire initial structure |
975 | | - self.remove_node(b_id) |
976 | | - # Create edge `A -> C` |
977 | | - self.create_edge(a_to_b.source.id, b_to_c.target.id, a_to_b.depth + b_to_c.depth, a_to_b.rules + b_to_c.rules) |
978 | | - |
979 | | - def lift_edges(self) -> bool: |
980 | | - """Perform all possible edge lifts across the KCFG. |
981 | | -
|
982 | | - The KCFG is transformed to an equivalent in which no further edge lifts are possible. |
983 | | -
|
984 | | - Given the KCFG design, it is not possible for one edge lift to either disallow another or |
985 | | - allow another that was not previously possible. Therefore, this function is guaranteed to |
986 | | - lift all possible edges without having to loop. |
987 | | -
|
988 | | - Returns: |
989 | | - An indicator of whether or not at least one edge lift was performed. |
990 | | - """ |
991 | | - edges_to_lift = sorted( |
992 | | - [ |
993 | | - node.id |
994 | | - for node in self.nodes |
995 | | - if len(self.edges(source_id=node.id)) > 0 and len(self.edges(target_id=node.id)) > 0 |
996 | | - ] |
997 | | - ) |
998 | | - for node_id in edges_to_lift: |
999 | | - self.lift_edge(node_id) |
1000 | | - return len(edges_to_lift) > 0 |
1001 | | - |
1002 | | - def lift_split_edge(self, b_id: NodeIdLike) -> None: |
1003 | | - """Lift a split up an edge directly preceding it. |
1004 | | -
|
1005 | | - `A --M steps--> B --[cond_1, ..., cond_N]--> [C_1, ..., C_N]` becomes |
1006 | | - `A --[cond_1, ..., cond_N]--> [A #And cond_1 --M steps--> C_1, ..., A #And cond_N --M steps--> C_N]`. |
1007 | | - Node `B` is removed. |
1008 | | -
|
1009 | | - Args: |
1010 | | - b_id: The identifier of the central node `B` of the structure `A --> B --> [C_1, ..., C_N]`. |
1011 | | -
|
1012 | | - Raises: |
1013 | | - AssertionError: If the structure in question is not in place. |
1014 | | - AssertionError: If any of the `cond_i` contain variables not present in `A`. |
1015 | | - """ |
1016 | | - # Obtain edge `A -> B`, split `[cond_I, C_I | I = 1..N ]` |
1017 | | - a_to_b = single(self.edges(target_id=b_id)) |
1018 | | - a = a_to_b.source |
1019 | | - split_from_b = single(self.splits(source_id=b_id)) |
1020 | | - ci, csubsts = list(split_from_b.splits.keys()), list(split_from_b.splits.values()) |
1021 | | - # Ensure split can be lifted soundly (i.e., that it does not introduce fresh variables) |
1022 | | - assert ( |
1023 | | - len(split_from_b.source_vars.difference(a.free_vars)) == 0 |
1024 | | - and len(split_from_b.target_vars.difference(split_from_b.source_vars)) == 0 |
1025 | | - ) |
1026 | | - # Create CTerms and CSubsts corresponding to the new targets of the split |
1027 | | - new_cterms_with_constraints = [ |
1028 | | - (CTerm(a.cterm.config, a.cterm.constraints + csubst.constraints), csubst.constraint) for csubst in csubsts |
1029 | | - ] |
1030 | | - # Generate substitutions for new targets, which all exist by construction |
1031 | | - new_csubsts = [ |
1032 | | - not_none(a.cterm.match_with_constraint(cterm)).add_constraint(constraint) |
1033 | | - for (cterm, constraint) in new_cterms_with_constraints |
1034 | | - ] |
1035 | | - # Remove the node `B`, effectively removing the entire initial structure |
1036 | | - self.remove_node(b_id) |
1037 | | - # Create the nodes `[ A #And cond_I | I = 1..N ]`. |
1038 | | - ai: list[NodeIdLike] = [self.create_node(cterm).id for (cterm, _) in new_cterms_with_constraints] |
1039 | | - # Create the edges `[A #And cond_1 --M steps--> C_I | I = 1..N ]` |
1040 | | - for i in range(len(ai)): |
1041 | | - self.create_edge(ai[i], ci[i], a_to_b.depth, a_to_b.rules) |
1042 | | - # Create the split `A --[cond_1, ..., cond_N]--> [A #And cond_1, ..., A #And cond_N] |
1043 | | - self.create_split(a.id, zip(ai, new_csubsts, strict=True)) |
1044 | | - |
1045 | | - def lift_split_split(self, b_id: NodeIdLike) -> None: |
1046 | | - """Lift a split up a split directly preceding it, joining them into a single split. |
1047 | | -
|
1048 | | - `A --[..., cond_B, ...]--> [..., B, ...]` with `B --[cond_1, ..., cond_N]--> [C_1, ..., C_N]` becomes |
1049 | | - `A --[..., cond_B #And cond_1, ..., cond_B #And cond_N, ...]--> [..., C_1, ..., C_N, ...]`. |
1050 | | - Node `B` is removed. |
1051 | | -
|
1052 | | - Args: |
1053 | | - b_id: the identifier of the node `B` of the structure |
1054 | | - `A --[..., cond_B, ...]--> [..., B, ...]` with `B --[cond_1, ..., cond_N]--> [C_1, ..., C_N]`. |
1055 | | -
|
1056 | | - Raises: |
1057 | | - AssertionError: If the structure in question is not in place. |
1058 | | - """ |
1059 | | - # Obtain splits `A --[..., cond_B, ...]--> [..., B, ...]` and |
1060 | | - # `B --[cond_1, ..., cond_N]--> [C_1, ..., C_N]-> [C_1, ..., C_N]` |
1061 | | - split_from_a, split_from_b = single(self.splits(target_id=b_id)), single(self.splits(source_id=b_id)) |
1062 | | - splits_from_a, splits_from_b = split_from_a.splits, split_from_b.splits |
1063 | | - a = split_from_a.source |
1064 | | - ci = list(splits_from_b.keys()) |
1065 | | - # Ensure split can be lifted soundly (i.e., that it does not introduce fresh variables) |
1066 | | - assert ( |
1067 | | - len(split_from_b.source_vars.difference(a.free_vars)) == 0 |
1068 | | - and len(split_from_b.target_vars.difference(split_from_b.source_vars)) == 0 |
1069 | | - ) |
1070 | | - # Get the substitution for `B`, at the same time removing 'B' from the targets of `A`. |
1071 | | - csubst_b = splits_from_a.pop(self._resolve(b_id)) |
1072 | | - # Generate substitutions for additional targets `C_I`, which all exist by construction; |
1073 | | - # the constraints are cumulative, resulting in `cond_B #And cond_I` |
1074 | | - additional_csubsts = [ |
1075 | | - not_none(a.cterm.match_with_constraint(self.node(ci).cterm)) |
1076 | | - .add_constraint(csubst_b.constraint) |
1077 | | - .add_constraint(csubst.constraint) |
1078 | | - for ci, csubst in splits_from_b.items() |
1079 | | - ] |
1080 | | - # Create the targets of the new split |
1081 | | - ci = list(splits_from_b.keys()) |
1082 | | - new_splits = zip( |
1083 | | - list(splits_from_a.keys()) + ci, list(splits_from_a.values()) + additional_csubsts, strict=True |
1084 | | - ) |
1085 | | - # Remove the node `B`, thereby removing the two splits as well |
1086 | | - self.remove_node(b_id) |
1087 | | - # Create the new split `A --[..., cond_B #And cond_1, ..., cond_B #And cond_N, ...]--> [..., C_1, ..., C_N, ...]` |
1088 | | - self.create_split(a.id, new_splits) |
1089 | | - |
1090 | | - def lift_splits(self) -> bool: |
1091 | | - """Perform all possible split liftings. |
1092 | | -
|
1093 | | - The KCFG is transformed to an equivalent in which no further split lifts are possible. |
1094 | | -
|
1095 | | - Returns: |
1096 | | - An indicator of whether or not at least one split lift was performed. |
1097 | | - """ |
1098 | | - |
1099 | | - def _lift_split(finder: Callable, lifter: Callable) -> bool: |
1100 | | - result = False |
1101 | | - while True: |
1102 | | - splits_to_lift = sorted( |
1103 | | - [ |
1104 | | - node.id |
1105 | | - for node in self.nodes |
1106 | | - if (splits := self.splits(source_id=node.id)) != [] |
1107 | | - and (sources := finder(target_id=node.id)) != [] |
1108 | | - and (source := single(sources).source) |
1109 | | - and (split := single(splits)) |
1110 | | - and len(split.source_vars.difference(source.free_vars)) == 0 |
1111 | | - and len(split.target_vars.difference(split.source_vars)) == 0 |
1112 | | - ] |
1113 | | - ) |
1114 | | - for id in splits_to_lift: |
1115 | | - lifter(id) |
1116 | | - result = True |
1117 | | - if len(splits_to_lift) == 0: |
1118 | | - break |
1119 | | - return result |
1120 | | - |
1121 | | - def _fold_lift(result: bool, finder_lifter: tuple[Callable, Callable]) -> bool: |
1122 | | - return _lift_split(finder_lifter[0], finder_lifter[1]) or result |
1123 | | - |
1124 | | - return reduce(_fold_lift, [(self.edges, self.lift_split_edge), (self.splits, self.lift_split_split)], False) |
1125 | | - |
1126 | | - def minimize(self) -> None: |
1127 | | - """Minimize KCFG by repeatedly performing the lifting transformations. |
1128 | | -
|
1129 | | - The KCFG is transformed to an equivalent in which no further lifting transformations are possible. |
1130 | | - The loop is designed so that each transformation is performed once in each iteration. |
1131 | | - """ |
1132 | | - repeat = True |
1133 | | - while repeat: |
1134 | | - repeat = self.lift_edges() |
1135 | | - repeat = self.lift_splits() or repeat |
1136 | | - |
1137 | 961 | def add_alias(self, alias: str, node_id: NodeIdLike) -> None: |
1138 | 962 | if '@' in alias: |
1139 | 963 | raise ValueError('Alias may not contain "@"') |
@@ -1308,6 +1132,9 @@ def write_cfg_data(self) -> None: |
1308 | 1132 | self._deleted_nodes.clear() |
1309 | 1133 | self._created_nodes.clear() |
1310 | 1134 |
|
| 1135 | + def minimize(self) -> None: |
| 1136 | + KCFGMinimizer(self).minimize() |
| 1137 | + |
1311 | 1138 | @staticmethod |
1312 | 1139 | def read_cfg_data(cfg_dir: Path) -> KCFG: |
1313 | 1140 | store = KCFGStore(cfg_dir) |
|
0 commit comments