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