From 305b375975bd26b5ed3a307770d4435f8440e2a0 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 16 Apr 2024 11:59:47 -0700 Subject: [PATCH] Compute Max IDs per subtree in NavigableExpr PiperOrigin-RevId: 625408805 --- .../dev/cel/common/navigation/BUILD.bazel | 2 +- .../common/navigation/BaseNavigableExpr.java | 9 + .../common/navigation/CelNavigableExpr.java | 17 +- .../navigation/CelNavigableExprVisitor.java | 15 +- .../navigation/ExprHeightCalculator.java | 128 --------- .../navigation/ExprPropertyCalculator.java | 156 +++++++++++ .../CelNavigableExprVisitorTest.java | 262 ++++++++++++++++++ 7 files changed, 453 insertions(+), 136 deletions(-) delete mode 100644 common/src/main/java/dev/cel/common/navigation/ExprHeightCalculator.java create mode 100644 common/src/main/java/dev/cel/common/navigation/ExprPropertyCalculator.java diff --git a/common/src/main/java/dev/cel/common/navigation/BUILD.bazel b/common/src/main/java/dev/cel/common/navigation/BUILD.bazel index 8d01f82d5..55ae48f72 100644 --- a/common/src/main/java/dev/cel/common/navigation/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/navigation/BUILD.bazel @@ -14,7 +14,7 @@ java_library( "CelNavigableAst.java", "CelNavigableExpr.java", "CelNavigableExprVisitor.java", - "ExprHeightCalculator.java", + "ExprPropertyCalculator.java", "TraversalOrder.java", ], tags = [ diff --git a/common/src/main/java/dev/cel/common/navigation/BaseNavigableExpr.java b/common/src/main/java/dev/cel/common/navigation/BaseNavigableExpr.java index af6653b7c..1699b4a96 100644 --- a/common/src/main/java/dev/cel/common/navigation/BaseNavigableExpr.java +++ b/common/src/main/java/dev/cel/common/navigation/BaseNavigableExpr.java @@ -40,6 +40,12 @@ public long id() { /** Represents the count of transitive parents. Depth of an AST's root is 0. */ public abstract int depth(); + /** + * Represents the maximum ID of the tree. Note that if the underlying expression tree held by this + * navigable expression is mutated, its max ID becomes stale and must be recomputed. + */ + public abstract long maxId(); + /** * Represents the maximum count of children from any of its branches. Height of a leaf node is 0. * For example, the height of the call node 'func' in expression `(1 + 2 + 3).func(4 + 5)` is 3. @@ -125,6 +131,9 @@ default ExprKind.Kind getKind() { @CanIgnoreReturnValue Builder setHeight(int value); + @CanIgnoreReturnValue + Builder setMaxId(long value); + @CheckReturnValue T build(); } diff --git a/common/src/main/java/dev/cel/common/navigation/CelNavigableExpr.java b/common/src/main/java/dev/cel/common/navigation/CelNavigableExpr.java index e994c15a0..69453b900 100644 --- a/common/src/main/java/dev/cel/common/navigation/CelNavigableExpr.java +++ b/common/src/main/java/dev/cel/common/navigation/CelNavigableExpr.java @@ -16,6 +16,7 @@ import com.google.auto.value.AutoValue; import dev.cel.common.ast.CelExpr; +import dev.cel.common.navigation.ExprPropertyCalculator.ExprProperty; import java.util.Optional; import java.util.stream.Stream; @@ -28,10 +29,17 @@ // redundant override: Overriding is required to specify the return type to a concrete type. @SuppressWarnings({"unchecked", "RedundantOverride"}) public abstract class CelNavigableExpr extends BaseNavigableExpr { + /** Constructs a new instance of {@link CelNavigableExpr} from {@link CelExpr}. */ public static CelNavigableExpr fromExpr(CelExpr expr) { - ExprHeightCalculator exprHeightCalculator = new ExprHeightCalculator<>(expr); - return builder().setExpr(expr).setHeight(exprHeightCalculator.getHeight(expr.id())).build(); + ExprPropertyCalculator exprHeightCalculator = new ExprPropertyCalculator<>(expr); + ExprProperty exprProperty = exprHeightCalculator.getProperty(expr.id()); + + return builder() + .setExpr(expr) + .setHeight(exprProperty.height()) + .setMaxId(exprProperty.maxId()) + .build(); } @Override @@ -74,7 +82,7 @@ public Builder builderFromInstance() { /** Create a new builder to construct a {@link CelNavigableExpr} instance. */ public static Builder builder() { - return new AutoValue_CelNavigableExpr.Builder().setDepth(0).setHeight(0); + return new AutoValue_CelNavigableExpr.Builder().setDepth(0).setHeight(0).setMaxId(0); } /** Builder to configure {@link CelNavigableExpr}. */ @@ -91,6 +99,9 @@ public abstract static class Builder @Override public abstract Builder setDepth(int value); + @Override + public abstract Builder setMaxId(long value); + @Override public abstract Builder setHeight(int value); } diff --git a/common/src/main/java/dev/cel/common/navigation/CelNavigableExprVisitor.java b/common/src/main/java/dev/cel/common/navigation/CelNavigableExprVisitor.java index eecf789d5..73cfecd0b 100644 --- a/common/src/main/java/dev/cel/common/navigation/CelNavigableExprVisitor.java +++ b/common/src/main/java/dev/cel/common/navigation/CelNavigableExprVisitor.java @@ -15,6 +15,7 @@ package dev.cel.common.navigation; import dev.cel.common.ast.Expression; +import dev.cel.common.navigation.ExprPropertyCalculator.ExprProperty; import java.util.List; import java.util.stream.Stream; @@ -23,12 +24,14 @@ final class CelNavigableExprVisitor streamBuilder; - private final ExprHeightCalculator exprPropertyCalculator; + private final ExprPropertyCalculator exprPropertyCalculator; private final TraversalOrder traversalOrder; private final int maxDepth; private CelNavigableExprVisitor( - int maxDepth, ExprHeightCalculator exprPropertyCalculator, TraversalOrder traversalOrder) { + int maxDepth, + ExprPropertyCalculator exprPropertyCalculator, + TraversalOrder traversalOrder) { this.maxDepth = maxDepth; this.exprPropertyCalculator = exprPropertyCalculator; this.traversalOrder = traversalOrder; @@ -78,7 +81,8 @@ static > Stream collect( */ static > Stream collect( T navigableExpr, int maxDepth, TraversalOrder traversalOrder) { - ExprHeightCalculator exprHeightCalculator = new ExprHeightCalculator<>(navigableExpr.expr()); + ExprPropertyCalculator exprHeightCalculator = + new ExprPropertyCalculator<>(navigableExpr.expr()); CelNavigableExprVisitor visitor = new CelNavigableExprVisitor<>(maxDepth, exprHeightCalculator, traversalOrder); @@ -174,11 +178,14 @@ private void visitExprList(List createListExpr, T parent) { } private T newNavigableChild(T parent, E expr) { + ExprProperty exprProperty = exprPropertyCalculator.getProperty(expr.id()); + return parent .builderFromInstance() .setExpr(expr) .setDepth(parent.depth() + 1) - .setHeight(exprPropertyCalculator.getHeight(expr.id())) + .setHeight(exprProperty.height()) + .setMaxId(exprProperty.maxId()) .setParent(parent) .build(); } diff --git a/common/src/main/java/dev/cel/common/navigation/ExprHeightCalculator.java b/common/src/main/java/dev/cel/common/navigation/ExprHeightCalculator.java deleted file mode 100644 index c2cb81101..000000000 --- a/common/src/main/java/dev/cel/common/navigation/ExprHeightCalculator.java +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package dev.cel.common.navigation; - -import static java.lang.Math.max; - -import dev.cel.common.ast.Expression; -import dev.cel.common.ast.Expression.CreateMap; -import dev.cel.common.ast.Expression.CreateStruct; -import java.util.HashMap; -import java.util.List; - -/** Package-private class to assist computing the height of expression nodes. */ -final class ExprHeightCalculator { - // Store hashmap instead of immutable map for performance, such that this helper class can be - // instantiated faster. - private final HashMap idToHeight; - - ExprHeightCalculator(E expr) { - this.idToHeight = new HashMap<>(); - visit(expr); - } - - int getHeight(Long exprId) { - if (!idToHeight.containsKey(exprId)) { - throw new IllegalStateException("Height not found for expression id: " + exprId); - } - - return idToHeight.get(exprId); - } - - private int visit(E expr) { - int height = 1; - switch (expr.getKind()) { - case CALL: - height += visit(expr.call()); - break; - case CREATE_LIST: - height += visit(expr.createList()); - break; - case SELECT: - height += visit(expr.select()); - break; - case CREATE_STRUCT: - height += visitStruct(expr.createStruct()); - break; - case CREATE_MAP: - height += visitMap(expr.createMap()); - break; - case COMPREHENSION: - height += visit(expr.comprehension()); - break; - default: - // This is a leaf node - height = 0; - break; - } - - idToHeight.put(expr.id(), height); - return height; - } - - private int visit(Expression.Call call) { - int targetHeight = 0; - if (call.target().isPresent()) { - targetHeight = visit(call.target().get()); - } - - int argumentHeight = visitExprList(call.args()); - return max(targetHeight, argumentHeight); - } - - private int visit(Expression.CreateList createList) { - return visitExprList(createList.elements()); - } - - private int visit(Expression.Select selectExpr) { - return visit(selectExpr.operand()); - } - - private int visit(Expression.Comprehension comprehension) { - int maxHeight = 0; - maxHeight = max(visit(comprehension.iterRange()), maxHeight); - maxHeight = max(visit(comprehension.accuInit()), maxHeight); - maxHeight = max(visit(comprehension.loopCondition()), maxHeight); - maxHeight = max(visit(comprehension.loopStep()), maxHeight); - maxHeight = max(visit(comprehension.result()), maxHeight); - - return maxHeight; - } - - private int visitStruct(Expression.CreateStruct> struct) { - int maxHeight = 0; - for (CreateStruct.Entry entry : struct.entries()) { - maxHeight = max(visit(entry.value()), maxHeight); - } - return maxHeight; - } - - private int visitMap(Expression.CreateMap> map) { - int maxHeight = 0; - for (CreateMap.Entry entry : map.entries()) { - maxHeight = max(visit(entry.key()), maxHeight); - maxHeight = max(visit(entry.value()), maxHeight); - } - return maxHeight; - } - - private int visitExprList(List createListExpr) { - int maxHeight = 0; - for (E expr : createListExpr) { - maxHeight = max(visit(expr), maxHeight); - } - return maxHeight; - } -} diff --git a/common/src/main/java/dev/cel/common/navigation/ExprPropertyCalculator.java b/common/src/main/java/dev/cel/common/navigation/ExprPropertyCalculator.java new file mode 100644 index 000000000..63097a1e3 --- /dev/null +++ b/common/src/main/java/dev/cel/common/navigation/ExprPropertyCalculator.java @@ -0,0 +1,156 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.common.navigation; + +import static java.lang.Math.max; + +import com.google.auto.value.AutoValue; +import dev.cel.common.ast.Expression; +import dev.cel.common.ast.Expression.CreateMap; +import dev.cel.common.ast.Expression.CreateStruct; +import java.util.HashMap; +import java.util.List; + +/** Package-private class to assist computing the height and the max ID of expression nodes. */ +final class ExprPropertyCalculator { + // Store hashmap instead of immutable map for performance, such that this helper class can be + // instantiated faster. + private final HashMap idToProperty; + + ExprPropertyCalculator(E celExpr) { + this.idToProperty = new HashMap<>(); + visit(celExpr); + } + + /** + * Retrieves the property containing the expression's maximum ID and the height of the subtree. + * + * @throws IllegalArgumentException If the provided expression ID does not exist. + */ + ExprProperty getProperty(Long exprId) { + if (!idToProperty.containsKey(exprId)) { + throw new IllegalArgumentException("Property not found for expression id: " + exprId); + } + + return idToProperty.get(exprId); + } + + private ExprProperty visit(E expr) { + int baseHeight = 1; + ExprProperty visitedProperty; + switch (expr.getKind()) { + case CALL: + visitedProperty = visit(expr.call()); + break; + case CREATE_LIST: + visitedProperty = visit(expr.createList()); + break; + case SELECT: + visitedProperty = visit(expr.select()); + break; + case CREATE_STRUCT: + visitedProperty = visitStruct(expr.createStruct()); + break; + case CREATE_MAP: + visitedProperty = visitMap(expr.createMap()); + break; + case COMPREHENSION: + visitedProperty = visit(expr.comprehension()); + break; + default: + // This is a leaf node + baseHeight = 0; + visitedProperty = ExprProperty.create(baseHeight, expr.id()); + break; + } + + ExprProperty exprProperty = + ExprProperty.create( + baseHeight + visitedProperty.height(), max(visitedProperty.maxId(), expr.id())); + idToProperty.put(expr.id(), exprProperty); + + return exprProperty; + } + + private ExprProperty visit(Expression.Call call) { + ExprProperty visitedTarget = ExprProperty.create(0, 0); + if (call.target().isPresent()) { + visitedTarget = visit(call.target().get()); + } + + ExprProperty visitedArgument = visitExprList(call.args()); + return ExprProperty.merge(visitedArgument, visitedTarget); + } + + private ExprProperty visit(Expression.CreateList createList) { + return visitExprList(createList.elements()); + } + + private ExprProperty visit(Expression.Select selectExpr) { + return visit(selectExpr.operand()); + } + + private ExprProperty visit(Expression.Comprehension comprehension) { + ExprProperty visitedProperty = visit(comprehension.iterRange()); + visitedProperty = ExprProperty.merge(visitedProperty, visit(comprehension.accuInit())); + visitedProperty = ExprProperty.merge(visitedProperty, visit(comprehension.loopCondition())); + visitedProperty = ExprProperty.merge(visitedProperty, visit(comprehension.loopStep())); + visitedProperty = ExprProperty.merge(visitedProperty, visit(comprehension.result())); + + return visitedProperty; + } + + private ExprProperty visitStruct(Expression.CreateStruct> struct) { + ExprProperty visitedProperty = ExprProperty.create(0, 0); + for (CreateStruct.Entry entry : struct.entries()) { + visitedProperty = ExprProperty.merge(visitedProperty, visit(entry.value())); + } + return visitedProperty; + } + + private ExprProperty visitMap(Expression.CreateMap> map) { + ExprProperty visitedProperty = ExprProperty.create(0, 0); + for (CreateMap.Entry entry : map.entries()) { + visitedProperty = ExprProperty.merge(visitedProperty, visit(entry.key())); + visitedProperty = ExprProperty.merge(visitedProperty, visit(entry.value())); + } + return visitedProperty; + } + + private ExprProperty visitExprList(List createListExpr) { + ExprProperty visitedProperty = ExprProperty.create(0, 0); + for (E expr : createListExpr) { + visitedProperty = ExprProperty.merge(visitedProperty, visit(expr)); + } + return visitedProperty; + } + + /** Value class to store the height and the max ID at a specific expression ID. */ + @AutoValue + abstract static class ExprProperty { + abstract Integer height(); + + abstract Long maxId(); + + /** Merges the two {@link ExprProperty}, taking their maximum values from the properties. */ + private static ExprProperty merge(ExprProperty e1, ExprProperty e2) { + return create(max(e1.height(), e2.height()), max(e1.maxId(), e2.maxId())); + } + + private static ExprProperty create(int height, long maxId) { + return new AutoValue_ExprPropertyCalculator_ExprProperty(height, maxId); + } + } +} diff --git a/common/src/test/java/dev/cel/common/navigation/CelNavigableExprVisitorTest.java b/common/src/test/java/dev/cel/common/navigation/CelNavigableExprVisitorTest.java index d7c04375e..4af7a7e83 100644 --- a/common/src/test/java/dev/cel/common/navigation/CelNavigableExprVisitorTest.java +++ b/common/src/test/java/dev/cel/common/navigation/CelNavigableExprVisitorTest.java @@ -127,6 +127,26 @@ public void add_preOrder_heightSet() throws Exception { assertThat(allNodeHeights).containsExactly(2, 1, 0, 0, 0).inOrder(); // +, +, 1, a, 2 } + @Test + public void add_preOrder_maxIdsSet() throws Exception { + CelCompiler compiler = + CelCompilerFactory.standardCelCompilerBuilder().addVar("a", SimpleType.INT).build(); + // Tree shape (brackets are IDs): + // + [4] + // + [2] 2 [5] + // 1 [1] a [3] + CelAbstractSyntaxTree ast = compiler.compile("1 + a + 2").getAst(); + CelNavigableAst navigableAst = CelNavigableAst.fromAst(ast); + + ImmutableList allNodeMaxIds = + navigableAst + .getRoot() + .allNodes(TraversalOrder.PRE_ORDER) + .map(CelNavigableExpr::maxId) + .collect(toImmutableList()); + assertThat(allNodeMaxIds).containsExactly(5L, 3L, 1L, 3L, 5L).inOrder(); // +, +, 1, a, 2 + } + @Test public void add_postOrder_heightSet() throws Exception { CelCompiler compiler = @@ -147,6 +167,26 @@ public void add_postOrder_heightSet() throws Exception { assertThat(allNodeHeights).containsExactly(0, 0, 1, 0, 2).inOrder(); // 1, a, +, 2, + } + @Test + public void add_postOrder_maxIdsSet() throws Exception { + CelCompiler compiler = + CelCompilerFactory.standardCelCompilerBuilder().addVar("a", SimpleType.INT).build(); + // Tree shape (brackets are IDs): + // + [4] + // + [2] 2 [5] + // 1 [1] a [3] + CelAbstractSyntaxTree ast = compiler.compile("1 + a + 2").getAst(); + CelNavigableAst navigableAst = CelNavigableAst.fromAst(ast); + + ImmutableList allNodeMaxIds = + navigableAst + .getRoot() + .allNodes(TraversalOrder.POST_ORDER) + .map(CelNavigableExpr::maxId) + .collect(toImmutableList()); + assertThat(allNodeMaxIds).containsExactly(1L, 3L, 3L, 5L, 5L).inOrder(); // 1, a, +, 2, + + } + @Test public void add_fromLeaf_heightSetForParents() throws Exception { CelCompiler compiler = @@ -176,6 +216,35 @@ public void add_fromLeaf_heightSetForParents() throws Exception { assertThat(heights.build()).containsExactly(3, 2, 1, 0); } + @Test + public void add_fromLeaf_maxIdsSetForParents() throws Exception { + CelCompiler compiler = + CelCompilerFactory.standardCelCompilerBuilder().addVar("a", SimpleType.INT).build(); + // Tree shape: + // + + // + 3 + // + 2 + // a 1 + CelAbstractSyntaxTree ast = compiler.compile("1 + a + 2 + 3").getAst(); + CelNavigableAst navigableAst = CelNavigableAst.fromAst(ast); + + ImmutableList.Builder heights = ImmutableList.builder(); + CelNavigableExpr navigableExpr = + navigableAst + .getRoot() + .allNodes() + .filter(node -> node.expr().identOrDefault().name().equals("a")) + .findAny() + .get(); + heights.add(navigableExpr.maxId()); + while (navigableExpr.parent().isPresent()) { + navigableExpr = navigableExpr.parent().get(); + heights.add(navigableExpr.maxId()); + } + + assertThat(heights.build()).containsExactly(3L, 3L, 5L, 7L).inOrder(); + } + @Test public void add_children_heightSet(@TestParameter TraversalOrder traversalOrder) throws Exception { @@ -198,6 +267,30 @@ public void add_children_heightSet(@TestParameter TraversalOrder traversalOrder) assertThat(allNodeHeights).containsExactly(2, 0).inOrder(); // + (2), 2 (0) regardless of order } + @Test + public void add_children_maxIdsSet(@TestParameter TraversalOrder traversalOrder) + throws Exception { + CelCompiler compiler = + CelCompilerFactory.standardCelCompilerBuilder().addVar("a", SimpleType.INT).build(); + // Tree shape: + // + + // + 3 + // + 2 + // a 1 + CelAbstractSyntaxTree ast = compiler.compile("1 + a + 2 + 3").getAst(); + CelNavigableAst navigableAst = CelNavigableAst.fromAst(ast); + + ImmutableList allNodeHeights = + navigableAst + .getRoot() + .children(traversalOrder) + .map(CelNavigableExpr::maxId) + .collect(toImmutableList()); + assertThat(allNodeHeights) + .containsExactly(5L, 7L) + .inOrder(); // + (5), 3 (7) regardless of order + } + @Test public void add_filterConstants_allNodesReturned() throws Exception { CelCompiler compiler = @@ -581,6 +674,27 @@ public void messageConstruction_postOrder_heightSet() throws Exception { assertThat(allNodes).containsExactly(0, 1).inOrder(); } + @Test + public void messageConstruction_maxIdsSet(@TestParameter TraversalOrder traversalOrder) + throws Exception { + CelCompiler compiler = + CelCompilerFactory.standardCelCompilerBuilder() + .addMessageTypes(TestAllTypes.getDescriptor()) + .setContainer("dev.cel.testing.testdata.proto3") + .build(); + CelAbstractSyntaxTree ast = compiler.compile("TestAllTypes{single_int64: 1}").getAst(); + CelNavigableAst navigableAst = CelNavigableAst.fromAst(ast); + + ImmutableList allNodes = + navigableAst + .getRoot() + .allNodes(traversalOrder) + .map(CelNavigableExpr::maxId) + .collect(toImmutableList()); + + assertThat(allNodes).containsExactly(3L, 3L).inOrder(); + } + @Test public void mapConstruction_allNodesReturned() throws Exception { CelCompiler compiler = CelCompilerFactory.standardCelCompilerBuilder().build(); @@ -659,6 +773,38 @@ public void mapConstruction_postOrder_heightSet() throws Exception { assertThat(allNodes).containsExactly(0, 0, 1).inOrder(); } + @Test + public void mapConstruction_preOrder_maxIdsSet() throws Exception { + CelCompiler compiler = CelCompilerFactory.standardCelCompilerBuilder().build(); + CelAbstractSyntaxTree ast = compiler.compile("{'key': 2}").getAst(); + CelNavigableAst navigableAst = CelNavigableAst.fromAst(ast); + + ImmutableList allNodes = + navigableAst + .getRoot() + .allNodes(TraversalOrder.PRE_ORDER) + .map(CelNavigableExpr::maxId) + .collect(toImmutableList()); + + assertThat(allNodes).containsExactly(4L, 3L, 4L).inOrder(); + } + + @Test + public void mapConstruction_postOrder_maxIdsSet() throws Exception { + CelCompiler compiler = CelCompilerFactory.standardCelCompilerBuilder().build(); + CelAbstractSyntaxTree ast = compiler.compile("{'key': 2}").getAst(); + CelNavigableAst navigableAst = CelNavigableAst.fromAst(ast); + + ImmutableList allNodes = + navigableAst + .getRoot() + .allNodes(TraversalOrder.POST_ORDER) + .map(CelNavigableExpr::maxId) + .collect(toImmutableList()); + + assertThat(allNodes).containsExactly(3L, 4L, 4L).inOrder(); + } + @Test public void emptyMapConstruction_allNodesReturned() throws Exception { CelCompiler compiler = CelCompilerFactory.standardCelCompilerBuilder().build(); @@ -834,6 +980,44 @@ public void comprehension_postOrder_heightSet() throws Exception { assertThat(allNodes).containsExactly(0, 1, 0, 0, 1, 2, 0, 0, 1, 0, 3).inOrder(); } + @Test + public void comprehension_preOrder_maxIdsSet() throws Exception { + CelCompiler compiler = + CelCompilerFactory.standardCelCompilerBuilder() + .setStandardMacros(CelStandardMacro.EXISTS) + .build(); + CelAbstractSyntaxTree ast = compiler.compile("[true].exists(i, i)").getAst(); + CelNavigableAst navigableAst = CelNavigableAst.fromAst(ast); + + ImmutableList allNodes = + navigableAst + .getRoot() + .allNodes(TraversalOrder.PRE_ORDER) + .map(CelNavigableExpr::maxId) + .collect(toImmutableList()); + + assertThat(allNodes).containsExactly(13L, 2L, 2L, 6L, 9L, 8L, 7L, 11L, 10L, 5L, 12L).inOrder(); + } + + @Test + public void comprehension_postOrder_maxIdsSet() throws Exception { + CelCompiler compiler = + CelCompilerFactory.standardCelCompilerBuilder() + .setStandardMacros(CelStandardMacro.EXISTS) + .build(); + CelAbstractSyntaxTree ast = compiler.compile("[true].exists(i, i)").getAst(); + CelNavigableAst navigableAst = CelNavigableAst.fromAst(ast); + + ImmutableList allNodes = + navigableAst + .getRoot() + .allNodes(TraversalOrder.POST_ORDER) + .map(CelNavigableExpr::maxId) + .collect(toImmutableList()); + + assertThat(allNodes).containsExactly(2L, 2L, 6L, 7L, 8L, 9L, 10L, 5L, 11L, 12L, 13L).inOrder(); + } + @Test public void comprehension_allNodes_parentsPopulated() throws Exception { CelCompiler compiler = @@ -1072,6 +1256,66 @@ public void callExpr_postOrder_heightSet() throws Exception { assertThat(allNodes).containsExactly(0, 0, 1, 0, 2, 0, 3, 0, 0, 1, 0, 2, 0, 4).inOrder(); } + @Test + public void callExpr_preOrder_maxIdsSet() throws Exception { + CelCompiler compiler = + CelCompilerFactory.standardCelCompilerBuilder() + .addFunctionDeclarations( + newFunctionDeclaration( + "test", + newMemberOverload( + "test_overload", + SimpleType.STRING, + SimpleType.STRING, + SimpleType.INT, + SimpleType.UINT))) + .build(); + CelAbstractSyntaxTree ast = + compiler.compile("('a' + 'b' + 'c' + 'd').test((1 + 2 + 3), 6u)").getAst(); + CelNavigableAst navigableAst = CelNavigableAst.fromAst(ast); + + ImmutableList allNodes = + navigableAst + .getRoot() + .allNodes(TraversalOrder.PRE_ORDER) + .map(CelNavigableExpr::maxId) + .collect(toImmutableList()); + + assertThat(allNodes) + .containsExactly(14L, 7L, 5L, 3L, 1L, 3L, 5L, 7L, 13L, 11L, 9L, 11L, 13L, 14L) + .inOrder(); + } + + @Test + public void callExpr_postOrder_maxIdsSet() throws Exception { + CelCompiler compiler = + CelCompilerFactory.standardCelCompilerBuilder() + .addFunctionDeclarations( + newFunctionDeclaration( + "test", + newMemberOverload( + "test_overload", + SimpleType.STRING, + SimpleType.STRING, + SimpleType.INT, + SimpleType.UINT))) + .build(); + CelAbstractSyntaxTree ast = + compiler.compile("('a' + 'b' + 'c' + 'd').test((1 + 2 + 3), 6u)").getAst(); + CelNavigableAst navigableAst = CelNavigableAst.fromAst(ast); + + ImmutableList allNodes = + navigableAst + .getRoot() + .allNodes(TraversalOrder.POST_ORDER) + .map(CelNavigableExpr::maxId) + .collect(toImmutableList()); + + assertThat(allNodes) + .containsExactly(1L, 3L, 3L, 5L, 5L, 7L, 7L, 9L, 11L, 11L, 13L, 13L, 14L, 14L) + .inOrder(); + } + @Test public void createList_children_heightSet(@TestParameter TraversalOrder traversalOrder) throws Exception { @@ -1090,6 +1334,24 @@ public void createList_children_heightSet(@TestParameter TraversalOrder traversa assertThat(allNodeHeights).containsExactly(0, 0, 1, 2).inOrder(); } + @Test + public void createList_children_maxIdsSet(@TestParameter TraversalOrder traversalOrder) + throws Exception { + CelCompiler compiler = + CelCompilerFactory.standardCelCompilerBuilder().addVar("a", SimpleType.INT).build(); + CelAbstractSyntaxTree ast = compiler.compile("[1, a, (2 + 2), (3 + 4 + 5)]").getAst(); + + CelNavigableAst navigableAst = CelNavigableAst.fromAst(ast); + + ImmutableList allNodeHeights = + navigableAst + .getRoot() + .children(traversalOrder) + .map(CelNavigableExpr::maxId) + .collect(toImmutableList()); + assertThat(allNodeHeights).containsExactly(2L, 3L, 6L, 11L).inOrder(); + } + @Test public void maxRecursionLimitReached_throws() throws Exception { StringBuilder sb = new StringBuilder();