|
18 | 18 | import static java.lang.Math.max; |
19 | 19 | import static java.util.stream.Collectors.toCollection; |
20 | 20 |
|
| 21 | +import com.google.auto.value.AutoOneOf; |
21 | 22 | import com.google.auto.value.AutoValue; |
22 | 23 | import com.google.common.base.Preconditions; |
23 | 24 | import com.google.common.base.Strings; |
24 | 25 | import com.google.common.collect.HashBasedTable; |
25 | 26 | import com.google.common.collect.ImmutableMap; |
| 27 | +import com.google.common.collect.Streams; |
26 | 28 | import com.google.common.collect.Table; |
27 | 29 | import com.google.errorprone.annotations.Immutable; |
28 | 30 | import dev.cel.common.CelAbstractSyntaxTree; |
|
44 | 46 | import java.util.Arrays; |
45 | 47 | import java.util.Collection; |
46 | 48 | import java.util.HashMap; |
| 49 | +import java.util.Iterator; |
47 | 50 | import java.util.LinkedHashMap; |
48 | 51 | import java.util.List; |
49 | 52 | import java.util.Map.Entry; |
50 | 53 | import java.util.NoSuchElementException; |
51 | 54 | import java.util.Optional; |
| 55 | +import java.util.function.Function; |
52 | 56 | import java.util.function.Predicate; |
53 | 57 | import java.util.stream.Collectors; |
54 | 58 |
|
@@ -552,6 +556,140 @@ public CelMutableAst replaceSubtree( |
552 | 556 | return CelMutableAst.of(mutatedRoot, newAstSource); |
553 | 557 | } |
554 | 558 |
|
| 559 | + /** |
| 560 | + * Replaces a subtree in the given AST with the specified {@link SubtreeReplacement}. |
| 561 | + * |
| 562 | + * <p>This operation is intended for AST optimization purposes. |
| 563 | + * |
| 564 | + * <p>This is a very dangerous operation. Callers must re-typecheck the mutated AST and |
| 565 | + * additionally verify that the resulting AST is semantically valid. |
| 566 | + * |
| 567 | + * <p>All expression IDs will be renumbered in a stable manner to ensure there's no ID collision |
| 568 | + * between the nodes. The renumbering occurs even if the subtree was not replaced. |
| 569 | + * |
| 570 | + * @param ast Original AST to mutate. |
| 571 | + * @param replacement Subtree replacement containing the target node ID and the new expression or |
| 572 | + * AST. |
| 573 | + */ |
| 574 | + public CelMutableAst replaceSubtree(CelMutableAst ast, SubtreeReplacement replacement) { |
| 575 | + Preconditions.checkNotNull(ast); |
| 576 | + Preconditions.checkNotNull(replacement); |
| 577 | + switch (replacement.replacement().kind()) { |
| 578 | + case EXPR: |
| 579 | + return replaceSubtree(ast, replacement.replacement().expr(), replacement.exprIdToReplace()); |
| 580 | + case AST: |
| 581 | + return replaceSubtree(ast, replacement.replacement().ast(), replacement.exprIdToReplace()); |
| 582 | + } |
| 583 | + throw new IllegalArgumentException( |
| 584 | + "Unsupported replacement kind: " + replacement.replacement().kind()); |
| 585 | + } |
| 586 | + |
| 587 | + /** |
| 588 | + * Repeatedly applies AST mutations using the provided AST-level rewriter until no further |
| 589 | + * replacements match (fixed point reached) or the mutator's iteration limit is exhausted. |
| 590 | + * |
| 591 | + * <p>This operation is intended for AST optimization purposes. |
| 592 | + * |
| 593 | + * <p>This is a very dangerous operation. Callers must re-typecheck the mutated AST and |
| 594 | + * additionally verify that the resulting AST is semantically valid. |
| 595 | + * |
| 596 | + * <p>All expression IDs will be renumbered in a stable manner to ensure there's no ID collision |
| 597 | + * between the nodes. |
| 598 | + * |
| 599 | + * @param ast Initial mutable AST to mutate. |
| 600 | + * @param astRewriter Function returning a {@link SubtreeReplacement} or {@code Optional.empty()} |
| 601 | + * when no further rewrites are possible. |
| 602 | + * @return Mutated {@link CelMutableAst} at fixed point. |
| 603 | + * @throws IllegalStateException If the iteration count exceeds {@code iterationLimit}. |
| 604 | + */ |
| 605 | + public CelMutableAst mutateUntilFixedPoint( |
| 606 | + CelMutableAst ast, |
| 607 | + Function<CelNavigableMutableAst, Optional<SubtreeReplacement>> astRewriter) { |
| 608 | + Preconditions.checkNotNull(ast); |
| 609 | + Preconditions.checkNotNull(astRewriter); |
| 610 | + CelMutableAst mutableAst = ast; |
| 611 | + for (long i = 0; i < iterationLimit; i++) { |
| 612 | + CelNavigableMutableAst navAst = CelNavigableMutableAst.fromAst(mutableAst); |
| 613 | + Optional<SubtreeReplacement> replacement = astRewriter.apply(navAst); |
| 614 | + if (!replacement.isPresent()) { |
| 615 | + return mutableAst; |
| 616 | + } |
| 617 | + mutableAst = replaceSubtree(mutableAst, replacement.get()); |
| 618 | + } |
| 619 | + throw new IllegalStateException("Max iteration count reached."); |
| 620 | + } |
| 621 | + |
| 622 | + /** |
| 623 | + * Traverses nodes using the specified {@link TraversalOrder} and repeatedly rewrites matching |
| 624 | + * subtrees until a fixed point is reached. |
| 625 | + * |
| 626 | + * <p>This operation is intended for AST optimization purposes. |
| 627 | + * |
| 628 | + * <p>This is a very dangerous operation. Callers must re-typecheck the mutated AST and |
| 629 | + * additionally verify that the resulting AST is semantically valid. |
| 630 | + * |
| 631 | + * <p>All expression IDs will be renumbered in a stable manner to ensure there's no ID collision |
| 632 | + * between the nodes. |
| 633 | + * |
| 634 | + * @param ast Initial mutable AST to mutate. |
| 635 | + * @param traversalOrder Order in which nodes are visited per iteration pass. |
| 636 | + * @param nodeRewriter Function returning a {@link SubtreeReplacement} or {@code |
| 637 | + * Optional.empty()}. |
| 638 | + * @return Mutated {@link CelMutableAst} at fixed point. |
| 639 | + * @throws IllegalStateException If the iteration count exceeds {@code iterationLimit}. |
| 640 | + */ |
| 641 | + public CelMutableAst mutateUntilFixedPoint( |
| 642 | + CelMutableAst ast, |
| 643 | + TraversalOrder traversalOrder, |
| 644 | + Function<CelNavigableMutableExpr, Optional<SubtreeReplacement>> nodeRewriter) { |
| 645 | + Preconditions.checkNotNull(traversalOrder); |
| 646 | + Preconditions.checkNotNull(nodeRewriter); |
| 647 | + return mutateUntilFixedPoint( |
| 648 | + ast, |
| 649 | + navAst -> |
| 650 | + navAst |
| 651 | + .getRoot() |
| 652 | + .allNodes(traversalOrder) |
| 653 | + .flatMap(node -> Streams.stream(nodeRewriter.apply(node))) |
| 654 | + .findFirst()); |
| 655 | + } |
| 656 | + |
| 657 | + /** |
| 658 | + * Traverses nodes using the specified {@link TraversalOrder}, applies the node matcher, and |
| 659 | + * substitutes matching nodes with the returned replacement expression (targeting {@code |
| 660 | + * node.id()}). |
| 661 | + * |
| 662 | + * <p>This operation is intended for AST optimization purposes. |
| 663 | + * |
| 664 | + * <p>This is a very dangerous operation. Callers must re-typecheck the mutated AST and |
| 665 | + * additionally verify that the resulting AST is semantically valid. |
| 666 | + * |
| 667 | + * <p>All expression IDs will be renumbered in a stable manner to ensure there's no ID collision |
| 668 | + * between the nodes. |
| 669 | + * |
| 670 | + * @param ast Initial mutable AST to mutate. |
| 671 | + * @param traversalOrder Order in which nodes are visited per iteration pass. |
| 672 | + * @param nodeMatcher Predicate to filter candidate nodes. |
| 673 | + * @param nodeRewriter Function producing the new {@link CelMutableExpr} for matched nodes. |
| 674 | + * @return Mutated {@link CelMutableAst} at fixed point. |
| 675 | + * @throws IllegalStateException If the iteration count exceeds {@code iterationLimit}. |
| 676 | + */ |
| 677 | + public CelMutableAst mutateUntilFixedPoint( |
| 678 | + CelMutableAst ast, |
| 679 | + TraversalOrder traversalOrder, |
| 680 | + Predicate<CelNavigableMutableExpr> nodeMatcher, |
| 681 | + Function<CelNavigableMutableExpr, Optional<CelMutableExpr>> nodeRewriter) { |
| 682 | + Preconditions.checkNotNull(nodeMatcher); |
| 683 | + Preconditions.checkNotNull(nodeRewriter); |
| 684 | + return mutateUntilFixedPoint( |
| 685 | + ast, |
| 686 | + traversalOrder, |
| 687 | + node -> |
| 688 | + nodeMatcher.test(node) |
| 689 | + ? nodeRewriter.apply(node).map(newExpr -> SubtreeReplacement.of(node.id(), newExpr)) |
| 690 | + : Optional.empty()); |
| 691 | + } |
| 692 | + |
555 | 693 | private CelMutableExpr mangleIdentsInComprehensionExpr( |
556 | 694 | CelMutableExpr root, |
557 | 695 | CelMutableExpr comprehensionExpr, |
@@ -983,4 +1121,53 @@ private static MangledComprehensionName of( |
983 | 1121 | iterVarName, iterVar2Name, resultName); |
984 | 1122 | } |
985 | 1123 | } |
| 1124 | + |
| 1125 | + /** |
| 1126 | + * Represents a planned subtree replacement containing the target node ID to replace and either a |
| 1127 | + * {@link CelMutableExpr} or {@link CelMutableAst}. |
| 1128 | + */ |
| 1129 | + @AutoValue |
| 1130 | + public abstract static class SubtreeReplacement { |
| 1131 | + |
| 1132 | + public abstract long exprIdToReplace(); |
| 1133 | + |
| 1134 | + public abstract Replacement replacement(); |
| 1135 | + |
| 1136 | + public static SubtreeReplacement of(long exprIdToReplace, CelMutableExpr replacementExpr) { |
| 1137 | + return new AutoValue_AstMutator_SubtreeReplacement( |
| 1138 | + exprIdToReplace, Replacement.ofExpr(replacementExpr)); |
| 1139 | + } |
| 1140 | + |
| 1141 | + public static SubtreeReplacement of(long exprIdToReplace, CelMutableAst replacementAst) { |
| 1142 | + return new AutoValue_AstMutator_SubtreeReplacement( |
| 1143 | + exprIdToReplace, Replacement.ofAst(replacementAst)); |
| 1144 | + } |
| 1145 | + |
| 1146 | + /** Discriminated union of either a {@link CelMutableExpr} or a {@link CelMutableAst}. */ |
| 1147 | + @AutoOneOf(Replacement.Kind.class) |
| 1148 | + public abstract static class Replacement { |
| 1149 | + |
| 1150 | + public abstract CelMutableExpr expr(); |
| 1151 | + |
| 1152 | + public abstract CelMutableAst ast(); |
| 1153 | + |
| 1154 | + public abstract Kind kind(); |
| 1155 | + |
| 1156 | + public static Replacement ofExpr(CelMutableExpr expr) { |
| 1157 | + return AutoOneOf_AstMutator_SubtreeReplacement_Replacement.expr( |
| 1158 | + Preconditions.checkNotNull(expr)); |
| 1159 | + } |
| 1160 | + |
| 1161 | + public static Replacement ofAst(CelMutableAst ast) { |
| 1162 | + return AutoOneOf_AstMutator_SubtreeReplacement_Replacement.ast( |
| 1163 | + Preconditions.checkNotNull(ast)); |
| 1164 | + } |
| 1165 | + |
| 1166 | + /** Kind of {@link Replacement}. */ |
| 1167 | + public enum Kind { |
| 1168 | + EXPR, |
| 1169 | + AST |
| 1170 | + } |
| 1171 | + } |
| 1172 | + } |
986 | 1173 | } |
0 commit comments