diff --git a/common/exceptions/BUILD.bazel b/common/exceptions/BUILD.bazel
index 6ffb2dcb9..96e07ac65 100644
--- a/common/exceptions/BUILD.bazel
+++ b/common/exceptions/BUILD.bazel
@@ -40,3 +40,9 @@ java_library(
# used_by_android
exports = ["//common/src/main/java/dev/cel/common/exceptions:invalid_argument"],
)
+
+java_library(
+ name = "iteration_budget_exceeded",
+ # used_by_android
+ exports = ["//common/src/main/java/dev/cel/common/exceptions:iteration_budget_exceeded"],
+)
diff --git a/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel b/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel
index 6bd1ad9ca..203866928 100644
--- a/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel
+++ b/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel
@@ -85,3 +85,16 @@ java_library(
"//common/annotations",
],
)
+
+java_library(
+ name = "iteration_budget_exceeded",
+ srcs = ["CelIterationLimitExceededException.java"],
+ # used_by_android
+ tags = [
+ ],
+ deps = [
+ "//common:error_codes",
+ "//common:runtime_exception",
+ "//common/annotations",
+ ],
+)
diff --git a/common/src/main/java/dev/cel/common/exceptions/CelIterationLimitExceededException.java b/common/src/main/java/dev/cel/common/exceptions/CelIterationLimitExceededException.java
new file mode 100644
index 000000000..ef0f1d8e3
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/exceptions/CelIterationLimitExceededException.java
@@ -0,0 +1,31 @@
+// Copyright 2025 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.exceptions;
+
+import dev.cel.common.CelErrorCode;
+import dev.cel.common.CelRuntimeException;
+import dev.cel.common.annotations.Internal;
+import java.util.Locale;
+
+/** Indicates that the iteration budget for a comprehension has been exceeded. */
+@Internal
+public final class CelIterationLimitExceededException extends CelRuntimeException {
+
+ public CelIterationLimitExceededException(int budget) {
+ super(
+ String.format(Locale.US, "Iteration budget exceeded: %d", budget),
+ CelErrorCode.ITERATION_BUDGET_EXCEEDED);
+ }
+}
diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel
index 7760d96b8..07bfdebbc 100644
--- a/runtime/BUILD.bazel
+++ b/runtime/BUILD.bazel
@@ -255,3 +255,11 @@ java_library(
visibility = ["//:internal"],
exports = ["//runtime/src/main/java/dev/cel/runtime:metadata"],
)
+
+java_library(
+ name = "concatenated_list_view",
+ visibility = ["//:internal"],
+ exports = [
+ "//runtime/src/main/java/dev/cel/runtime:concatenated_list_view",
+ ],
+)
diff --git a/runtime/src/main/java/dev/cel/runtime/AccumulatedUnknowns.java b/runtime/src/main/java/dev/cel/runtime/AccumulatedUnknowns.java
index 77435a042..d27de2da2 100644
--- a/runtime/src/main/java/dev/cel/runtime/AccumulatedUnknowns.java
+++ b/runtime/src/main/java/dev/cel/runtime/AccumulatedUnknowns.java
@@ -2,7 +2,7 @@
//
// 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 aj
+// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel
index 847092cc3..b55aec00f 100644
--- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel
+++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel
@@ -1142,7 +1142,9 @@ java_library(
name = "concatenated_list_view",
srcs = ["ConcatenatedListView.java"],
# used_by_android
- visibility = ["//visibility:private"],
+ tags = [
+ ],
+ deps = ["//common/annotations"],
)
java_library(
diff --git a/runtime/src/main/java/dev/cel/runtime/ConcatenatedListView.java b/runtime/src/main/java/dev/cel/runtime/ConcatenatedListView.java
index ac7696751..c15e76f77 100644
--- a/runtime/src/main/java/dev/cel/runtime/ConcatenatedListView.java
+++ b/runtime/src/main/java/dev/cel/runtime/ConcatenatedListView.java
@@ -2,7 +2,7 @@
//
// 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 aj
+// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
@@ -14,6 +14,7 @@
package dev.cel.runtime;
+import dev.cel.common.annotations.Internal;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
@@ -27,8 +28,12 @@
* comprehensions that dispatch `add_list` to concat N lists together).
*
*
This does not support any of the standard list operations from {@link java.util.List}.
+ *
+
+ *
CEL Library Internals. Do Not Use.
*/
-final class ConcatenatedListView extends AbstractList {
+@Internal
+public final class ConcatenatedListView extends AbstractList {
private final List> sourceLists;
private int totalSize = 0;
@@ -36,7 +41,7 @@ final class ConcatenatedListView extends AbstractList {
this.sourceLists = new ArrayList<>();
}
- ConcatenatedListView(Collection extends E> collection) {
+ public ConcatenatedListView(Collection extends E> collection) {
this();
addAll(collection);
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java b/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java
index f20c9aadd..cc011ed34 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java
@@ -20,7 +20,7 @@
/** Represents a resolvable symbol or path (such as a variable or a field selection). */
@Immutable
interface Attribute {
- Object resolve(GlobalResolver ctx);
+ Object resolve(GlobalResolver ctx, ExecutionFrame frame);
Attribute addQualifier(Qualifier qualifier);
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel
index 45936203a..b99b41fd5 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel
+++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel
@@ -22,7 +22,9 @@ java_library(
":eval_create_list",
":eval_create_map",
":eval_create_struct",
+ ":eval_fold",
":eval_or",
+ ":eval_test_only",
":eval_unary",
":eval_var_args_call",
":eval_zero_arity",
@@ -35,6 +37,7 @@ java_library(
"//common:cel_ast",
"//common:container",
"//common:operator",
+ "//common:options",
"//common/annotations",
"//common/ast",
"//common/types",
@@ -57,11 +60,14 @@ java_library(
srcs = ["PlannedProgram.java"],
deps = [
":error_metadata",
+ ":execution_frame",
":planned_interpretable",
":strict_error_exception",
"//:auto_value",
+ "//common:options",
"//common:runtime_exception",
"//common/values",
+ "//runtime",
"//runtime:activation",
"//runtime:evaluation_exception",
"//runtime:evaluation_exception_builder",
@@ -76,11 +82,10 @@ java_library(
name = "eval_const",
srcs = ["EvalConstant.java"],
deps = [
+ ":execution_frame",
":planned_interpretable",
"//common/values",
"//common/values:cel_byte_string",
- "//runtime:evaluation_listener",
- "//runtime:function_resolver",
"//runtime:interpretable",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
@@ -109,6 +114,7 @@ java_library(
],
deps = [
":eval_helpers",
+ ":execution_frame",
":planned_interpretable",
":qualifier",
"//common:container",
@@ -131,6 +137,16 @@ java_library(
],
)
+java_library(
+ name = "presence_test_qualifier",
+ srcs = ["PresenceTestQualifier.java"],
+ deps = [
+ ":attribute",
+ ":qualifier",
+ "//common/values",
+ ],
+)
+
java_library(
name = "string_qualifier",
srcs = ["StringQualifier.java"],
@@ -146,24 +162,36 @@ java_library(
srcs = ["EvalAttribute.java"],
deps = [
":attribute",
+ ":execution_frame",
":interpretable_attribute",
":qualifier",
- "//runtime:evaluation_listener",
- "//runtime:function_resolver",
"//runtime:interpretable",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
],
)
+java_library(
+ name = "eval_test_only",
+ srcs = ["EvalTestOnly.java"],
+ deps = [
+ ":execution_frame",
+ ":interpretable_attribute",
+ ":presence_test_qualifier",
+ ":qualifier",
+ "//runtime:evaluation_exception",
+ "//runtime:interpretable",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ ],
+)
+
java_library(
name = "eval_zero_arity",
srcs = ["EvalZeroArity.java"],
deps = [
+ ":execution_frame",
":planned_interpretable",
"//runtime:evaluation_exception",
- "//runtime:evaluation_listener",
- "//runtime:function_resolver",
"//runtime:interpretable",
"//runtime:resolved_overload",
],
@@ -174,10 +202,9 @@ java_library(
srcs = ["EvalUnary.java"],
deps = [
":eval_helpers",
+ ":execution_frame",
":planned_interpretable",
"//runtime:evaluation_exception",
- "//runtime:evaluation_listener",
- "//runtime:function_resolver",
"//runtime:interpretable",
"//runtime:resolved_overload",
],
@@ -188,10 +215,9 @@ java_library(
srcs = ["EvalVarArgsCall.java"],
deps = [
":eval_helpers",
+ ":execution_frame",
":planned_interpretable",
"//runtime:evaluation_exception",
- "//runtime:evaluation_listener",
- "//runtime:function_resolver",
"//runtime:interpretable",
"//runtime:resolved_overload",
],
@@ -202,10 +228,9 @@ java_library(
srcs = ["EvalOr.java"],
deps = [
":eval_helpers",
+ ":execution_frame",
":planned_interpretable",
"//common/values",
- "//runtime:evaluation_listener",
- "//runtime:function_resolver",
"//runtime:interpretable",
"@maven//:com_google_guava_guava",
],
@@ -216,10 +241,9 @@ java_library(
srcs = ["EvalAnd.java"],
deps = [
":eval_helpers",
+ ":execution_frame",
":planned_interpretable",
"//common/values",
- "//runtime:evaluation_listener",
- "//runtime:function_resolver",
"//runtime:interpretable",
"@maven//:com_google_guava_guava",
],
@@ -229,10 +253,9 @@ java_library(
name = "eval_conditional",
srcs = ["EvalConditional.java"],
deps = [
+ ":execution_frame",
":planned_interpretable",
"//runtime:evaluation_exception",
- "//runtime:evaluation_listener",
- "//runtime:function_resolver",
"//runtime:interpretable",
"@maven//:com_google_guava_guava",
],
@@ -242,13 +265,12 @@ java_library(
name = "eval_create_struct",
srcs = ["EvalCreateStruct.java"],
deps = [
+ ":execution_frame",
":planned_interpretable",
"//common/types",
"//common/values",
"//common/values:cel_value_provider",
"//runtime:evaluation_exception",
- "//runtime:evaluation_listener",
- "//runtime:function_resolver",
"//runtime:interpretable",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
@@ -259,10 +281,9 @@ java_library(
name = "eval_create_list",
srcs = ["EvalCreateList.java"],
deps = [
+ ":execution_frame",
":planned_interpretable",
"//runtime:evaluation_exception",
- "//runtime:evaluation_listener",
- "//runtime:function_resolver",
"//runtime:interpretable",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
@@ -273,20 +294,46 @@ java_library(
name = "eval_create_map",
srcs = ["EvalCreateMap.java"],
deps = [
+ ":execution_frame",
":planned_interpretable",
"//runtime:evaluation_exception",
- "//runtime:evaluation_listener",
- "//runtime:function_resolver",
"//runtime:interpretable",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
],
)
+java_library(
+ name = "eval_fold",
+ srcs = ["EvalFold.java"],
+ deps = [
+ ":execution_frame",
+ ":planned_interpretable",
+ "//runtime:concatenated_list_view",
+ "//runtime:evaluation_exception",
+ "//runtime:interpretable",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ "@maven//:com_google_guava_guava",
+ "@maven//:org_jspecify_jspecify",
+ ],
+)
+
+java_library(
+ name = "execution_frame",
+ srcs = ["ExecutionFrame.java"],
+ deps = [
+ "//common:options",
+ "//common/exceptions:iteration_budget_exceeded",
+ "//runtime:interpretable",
+ "@maven//:org_jspecify_jspecify",
+ ],
+)
+
java_library(
name = "eval_helpers",
srcs = ["EvalHelpers.java"],
deps = [
+ ":execution_frame",
":planned_interpretable",
":strict_error_exception",
"//common:error_codes",
@@ -319,6 +366,8 @@ java_library(
name = "planned_interpretable",
srcs = ["PlannedInterpretable.java"],
deps = [
+ ":execution_frame",
+ "//runtime:evaluation_exception",
"//runtime:interpretable",
"@maven//:com_google_errorprone_error_prone_annotations",
],
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java
index a3a39ce8a..b09191e9f 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java
@@ -18,8 +18,6 @@
import com.google.common.base.Preconditions;
import dev.cel.common.values.ErrorValue;
-import dev.cel.runtime.CelEvaluationListener;
-import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.GlobalResolver;
final class EvalAnd extends PlannedInterpretable {
@@ -28,10 +26,10 @@ final class EvalAnd extends PlannedInterpretable {
private final PlannedInterpretable[] args;
@Override
- public Object eval(GlobalResolver resolver) {
+ public Object eval(GlobalResolver resolver, ExecutionFrame frame) {
ErrorValue errorValue = null;
for (PlannedInterpretable arg : args) {
- Object argVal = evalNonstrictly(arg, resolver);
+ Object argVal = evalNonstrictly(arg, resolver, frame);
if (argVal instanceof Boolean) {
// Short-circuit on false
if (!((boolean) argVal)) {
@@ -53,27 +51,6 @@ public Object eval(GlobalResolver resolver) {
return true;
}
- @Override
- public Object eval(GlobalResolver resolver, CelEvaluationListener listener) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- @Override
- public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- @Override
- public Object eval(
- GlobalResolver resolver,
- CelFunctionResolver lateBoundFunctionResolver,
- CelEvaluationListener listener) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
static EvalAnd create(long exprId, PlannedInterpretable[] args) {
return new EvalAnd(exprId, args);
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java
index 826f7e1fa..fdd7ad2a3 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java
@@ -15,8 +15,6 @@
package dev.cel.runtime.planner;
import com.google.errorprone.annotations.Immutable;
-import dev.cel.runtime.CelEvaluationListener;
-import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.GlobalResolver;
@Immutable
@@ -25,36 +23,15 @@ final class EvalAttribute extends InterpretableAttribute {
private final Attribute attr;
@Override
- public Object eval(GlobalResolver resolver) {
- Object resolved = attr.resolve(resolver);
+ public Object eval(GlobalResolver resolver, ExecutionFrame frame) {
+ Object resolved = attr.resolve(resolver, frame);
if (resolved instanceof MissingAttribute) {
- ((MissingAttribute) resolved).resolve(resolver);
+ ((MissingAttribute) resolved).resolve(resolver, frame);
}
return resolved;
}
- @Override
- public Object eval(GlobalResolver resolver, CelEvaluationListener listener) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- @Override
- public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- @Override
- public Object eval(
- GlobalResolver resolver,
- CelFunctionResolver lateBoundFunctionResolver,
- CelEvaluationListener listener) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
@Override
public EvalAttribute addQualifier(long exprId, Qualifier qualifier) {
Attribute newAttribute = attr.addQualifier(qualifier);
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java
index 4445d3e71..74482d629 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java
@@ -16,23 +16,20 @@
import com.google.common.base.Preconditions;
import dev.cel.runtime.CelEvaluationException;
-import dev.cel.runtime.CelEvaluationListener;
-import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
final class EvalConditional extends PlannedInterpretable {
@SuppressWarnings("Immutable")
- private final Interpretable[] args;
+ private final PlannedInterpretable[] args;
@Override
- public Object eval(GlobalResolver resolver) throws CelEvaluationException {
- Interpretable condition = args[0];
- Interpretable truthy = args[1];
- Interpretable falsy = args[2];
+ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException {
+ PlannedInterpretable condition = args[0];
+ PlannedInterpretable truthy = args[1];
+ PlannedInterpretable falsy = args[2];
// TODO: Handle unknowns
- Object condResult = condition.eval(resolver);
+ Object condResult = condition.eval(resolver, frame);
if (!(condResult instanceof Boolean)) {
throw new IllegalArgumentException(
String.format("Expected boolean value, found :%s", condResult));
@@ -40,38 +37,17 @@ public Object eval(GlobalResolver resolver) throws CelEvaluationException {
// TODO: Handle exhaustive eval
if ((boolean) condResult) {
- return truthy.eval(resolver);
+ return truthy.eval(resolver, frame);
}
- return falsy.eval(resolver);
+ return falsy.eval(resolver, frame);
}
- @Override
- public Object eval(GlobalResolver resolver, CelEvaluationListener listener) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- @Override
- public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- @Override
- public Object eval(
- GlobalResolver resolver,
- CelFunctionResolver lateBoundFunctionResolver,
- CelEvaluationListener listener) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- static EvalConditional create(long exprId, Interpretable[] args) {
+ static EvalConditional create(long exprId, PlannedInterpretable[] args) {
return new EvalConditional(exprId, args);
}
- private EvalConditional(long exprId, Interpretable[] args) {
+ private EvalConditional(long exprId, PlannedInterpretable[] args) {
super(exprId);
Preconditions.checkArgument(args.length == 3);
this.args = args;
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java
index 408d04046..74d2811ea 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java
@@ -18,8 +18,6 @@
import com.google.errorprone.annotations.Immutable;
import dev.cel.common.values.CelByteString;
import dev.cel.common.values.NullValue;
-import dev.cel.runtime.CelEvaluationListener;
-import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.GlobalResolver;
@Immutable
@@ -40,25 +38,7 @@ final class EvalConstant extends PlannedInterpretable {
private final Object constant;
@Override
- public Object eval(GlobalResolver resolver) {
- return constant;
- }
-
- @Override
- public Object eval(GlobalResolver resolver, CelEvaluationListener listener) {
- return constant;
- }
-
- @Override
- public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) {
- return constant;
- }
-
- @Override
- public Object eval(
- GlobalResolver resolver,
- CelFunctionResolver lateBoundFunctionResolver,
- CelEvaluationListener listener) {
+ public Object eval(GlobalResolver resolver, ExecutionFrame frame) {
return constant;
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java
index 4ec275eef..e519b968c 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java
@@ -17,53 +17,29 @@
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.Immutable;
import dev.cel.runtime.CelEvaluationException;
-import dev.cel.runtime.CelEvaluationListener;
-import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
@Immutable
final class EvalCreateList extends PlannedInterpretable {
// Array contents are not mutated
@SuppressWarnings("Immutable")
- private final Interpretable[] values;
+ private final PlannedInterpretable[] values;
@Override
- public Object eval(GlobalResolver resolver) throws CelEvaluationException {
+ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException {
ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize(values.length);
- for (Interpretable value : values) {
- builder.add(value.eval(resolver));
+ for (PlannedInterpretable value : values) {
+ builder.add(value.eval(resolver, frame));
}
return builder.build();
}
- @Override
- public Object eval(GlobalResolver resolver, CelEvaluationListener listener) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- @Override
- public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- @Override
- public Object eval(
- GlobalResolver resolver,
- CelFunctionResolver lateBoundFunctionResolver,
- CelEvaluationListener listener) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- static EvalCreateList create(long exprId, Interpretable[] values) {
+ static EvalCreateList create(long exprId, PlannedInterpretable[] values) {
return new EvalCreateList(exprId, values);
}
- private EvalCreateList(long exprId, Interpretable[] values) {
+ private EvalCreateList(long exprId, PlannedInterpretable[] values) {
super(exprId);
this.values = values;
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java
index 38d690303..abdba90db 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java
@@ -18,59 +18,34 @@
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.Immutable;
import dev.cel.runtime.CelEvaluationException;
-import dev.cel.runtime.CelEvaluationListener;
-import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
@Immutable
final class EvalCreateMap extends PlannedInterpretable {
// Array contents are not mutated
@SuppressWarnings("Immutable")
- private final Interpretable[] keys;
+ private final PlannedInterpretable[] keys;
// Array contents are not mutated
@SuppressWarnings("Immutable")
- private final Interpretable[] values;
+ private final PlannedInterpretable[] values;
@Override
- public Object eval(GlobalResolver resolver) throws CelEvaluationException {
+ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException {
ImmutableMap.Builder builder =
ImmutableMap.builderWithExpectedSize(keys.length);
for (int i = 0; i < keys.length; i++) {
- builder.put(keys[i].eval(resolver), values[i].eval(resolver));
+ builder.put(keys[i].eval(resolver, frame), values[i].eval(resolver, frame));
}
return builder.buildOrThrow();
}
-
- @Override
- public Object eval(GlobalResolver resolver, CelEvaluationListener listener) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- @Override
- public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- @Override
- public Object eval(
- GlobalResolver resolver,
- CelFunctionResolver lateBoundFunctionResolver,
- CelEvaluationListener listener) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- static EvalCreateMap create(long exprId, Interpretable[] keys, Interpretable[] values) {
+ static EvalCreateMap create(long exprId, PlannedInterpretable[] keys, PlannedInterpretable[] values) {
return new EvalCreateMap(exprId, keys, values);
}
- private EvalCreateMap(long exprId, Interpretable[] keys, Interpretable[] values) {
+ private EvalCreateMap(long exprId, PlannedInterpretable[] keys, PlannedInterpretable[] values) {
super(exprId);
Preconditions.checkArgument(keys.length == values.length);
this.keys = keys;
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java
index 7553add80..f1d6f75e5 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java
@@ -19,10 +19,7 @@
import dev.cel.common.values.CelValueProvider;
import dev.cel.common.values.StructValue;
import dev.cel.runtime.CelEvaluationException;
-import dev.cel.runtime.CelEvaluationListener;
-import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -39,13 +36,13 @@ final class EvalCreateStruct extends PlannedInterpretable {
// Array contents are not mutated
@SuppressWarnings("Immutable")
- private final Interpretable[] values;
+ private final PlannedInterpretable[] values;
@Override
- public Object eval(GlobalResolver resolver) throws CelEvaluationException {
+ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException {
Map fieldValues = new HashMap<>();
for (int i = 0; i < keys.length; i++) {
- Object value = values[i].eval(resolver);
+ Object value = values[i].eval(resolver, frame);
fieldValues.put(keys[i], value);
}
@@ -62,33 +59,12 @@ public Object eval(GlobalResolver resolver) throws CelEvaluationException {
return value;
}
- @Override
- public Object eval(GlobalResolver resolver, CelEvaluationListener listener) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- @Override
- public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- @Override
- public Object eval(
- GlobalResolver resolver,
- CelFunctionResolver lateBoundFunctionResolver,
- CelEvaluationListener listener) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
static EvalCreateStruct create(
long exprId,
CelValueProvider valueProvider,
StructType structType,
String[] keys,
- Interpretable[] values) {
+ PlannedInterpretable[] values) {
return new EvalCreateStruct(exprId, valueProvider, structType, keys, values);
}
@@ -97,7 +73,7 @@ private EvalCreateStruct(
CelValueProvider valueProvider,
StructType structType,
String[] keys,
- Interpretable[] values) {
+ PlannedInterpretable[] values) {
super(exprId);
this.valueProvider = valueProvider;
this.structType = structType;
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java
new file mode 100644
index 000000000..49047f3a4
--- /dev/null
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java
@@ -0,0 +1,186 @@
+// Copyright 2025 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.runtime.planner;
+
+import com.google.common.collect.ImmutableList;
+import com.google.errorprone.annotations.Immutable;
+import dev.cel.runtime.CelEvaluationException;
+import dev.cel.runtime.ConcatenatedListView;
+import dev.cel.runtime.GlobalResolver;
+import java.util.Collection;
+import java.util.Map;
+import org.jspecify.annotations.Nullable;
+
+@Immutable
+final class EvalFold extends PlannedInterpretable {
+
+ private final String accuVar;
+ private final PlannedInterpretable accuInit;
+ private final String iterVar;
+ private final String iterVar2;
+ private final PlannedInterpretable iterRange;
+ private final PlannedInterpretable condition;
+ private final PlannedInterpretable loopStep;
+ private final PlannedInterpretable result;
+
+ static EvalFold create(
+ long exprId,
+ String accuVar,
+ PlannedInterpretable accuInit,
+ String iterVar,
+ String iterVar2,
+ PlannedInterpretable iterRange,
+ PlannedInterpretable loopCondition,
+ PlannedInterpretable loopStep,
+ PlannedInterpretable result) {
+ return new EvalFold(
+ exprId, accuVar, accuInit, iterVar, iterVar2, iterRange, loopCondition, loopStep, result);
+ }
+
+ private EvalFold(
+ long exprId,
+ String accuVar,
+ PlannedInterpretable accuInit,
+ String iterVar,
+ String iterVar2,
+ PlannedInterpretable iterRange,
+ PlannedInterpretable condition,
+ PlannedInterpretable loopStep,
+ PlannedInterpretable result) {
+ super(exprId);
+ this.accuVar = accuVar;
+ this.accuInit = accuInit;
+ this.iterVar = iterVar;
+ this.iterVar2 = iterVar2;
+ this.iterRange = iterRange;
+ this.condition = condition;
+ this.loopStep = loopStep;
+ this.result = result;
+ }
+
+ @Override
+ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException {
+ Object iterRangeRaw = iterRange.eval(resolver, frame);
+ Folder folder = new Folder(resolver, accuVar, iterVar, iterVar2);
+ folder.accuVal = maybeWrapAccumulator(accuInit.eval(folder, frame));
+
+ Object result;
+ if (iterRangeRaw instanceof Map) {
+ result = evalMap((Map, ?>) iterRangeRaw, folder, frame);
+ } else if (iterRangeRaw instanceof Collection) {
+ result = evalList((Collection>) iterRangeRaw, folder, frame);
+ } else {
+ throw new IllegalArgumentException("Unexpected iter_range type: " + iterRangeRaw.getClass());
+ }
+
+ return maybeUnwrapAccumulator(result);
+ }
+
+ private Object evalMap(Map, ?> iterRange, Folder folder, ExecutionFrame frame)
+ throws CelEvaluationException {
+ for (Map.Entry, ?> entry : iterRange.entrySet()) {
+ frame.incrementIterations();
+
+ folder.iterVarVal = entry.getKey();
+ if (!iterVar2.isEmpty()) {
+ folder.iterVar2Val = entry.getValue();
+ }
+
+ boolean cond = (boolean) condition.eval(folder, frame);
+ if (!cond) {
+ return result.eval(folder, frame);
+ }
+
+ folder.accuVal = loopStep.eval(folder, frame);
+ }
+ return result.eval(folder, frame);
+ }
+
+ private Object evalList(Collection> iterRange, Folder folder, ExecutionFrame frame)
+ throws CelEvaluationException {
+ int index = 0;
+ for (Object item : iterRange) {
+ frame.incrementIterations();
+
+ if (iterVar2.isEmpty()) {
+ folder.iterVarVal = item;
+ } else {
+ folder.iterVarVal = (long) index;
+ folder.iterVar2Val = item;
+ }
+
+ boolean cond = (boolean) condition.eval(folder, frame);
+ if (!cond) {
+ return result.eval(folder, frame);
+ }
+
+ folder.accuVal = loopStep.eval(folder, frame);
+ index++;
+ }
+ return result.eval(folder, frame);
+ }
+
+ private static Object maybeWrapAccumulator(Object val) {
+ if (val instanceof Collection) {
+ return new ConcatenatedListView<>((Collection>) val);
+ }
+ // TODO: Introduce mutable map support (for comp v2)
+ return val;
+ }
+
+ private static Object maybeUnwrapAccumulator(Object val) {
+ if (val instanceof ConcatenatedListView) {
+ return ImmutableList.copyOf((ConcatenatedListView>) val);
+ }
+
+ // TODO: Introduce mutable map support (for comp v2)
+ return val;
+ }
+
+ private static class Folder implements GlobalResolver {
+ private final GlobalResolver resolver;
+ private final String accuVar;
+ private final String iterVar;
+ private final String iterVar2;
+
+ private Object iterVarVal;
+ private Object iterVar2Val;
+ private Object accuVal;
+
+ private Folder(GlobalResolver resolver, String accuVar, String iterVar, String iterVar2) {
+ this.resolver = resolver;
+ this.accuVar = accuVar;
+ this.iterVar = iterVar;
+ this.iterVar2 = iterVar2;
+ }
+
+ @Override
+ public @Nullable Object resolve(String name) {
+ if (name.equals(accuVar)) {
+ return accuVal;
+ }
+
+ if (name.equals(iterVar)) {
+ return this.iterVarVal;
+ }
+
+ if (name.equals(iterVar2)) {
+ return this.iterVar2Val;
+ }
+
+ return resolver.resolve(name);
+ }
+ }
+}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java
index 3b5bda1bc..8d2805469 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java
@@ -21,9 +21,10 @@
final class EvalHelpers {
- static Object evalNonstrictly(PlannedInterpretable interpretable, GlobalResolver resolver) {
+ static Object evalNonstrictly(
+ PlannedInterpretable interpretable, GlobalResolver resolver, ExecutionFrame frame) {
try {
- return interpretable.eval(resolver);
+ return interpretable.eval(resolver, frame);
} catch (StrictErrorException e) {
// Intercept the strict exception to get a more localized expr ID for error reporting purposes
// Example: foo [1] && strict_err [2] -> ID 2 is propagated.
@@ -33,9 +34,10 @@ static Object evalNonstrictly(PlannedInterpretable interpretable, GlobalResolver
}
}
- static Object evalStrictly(PlannedInterpretable interpretable, GlobalResolver resolver) {
+ static Object evalStrictly(
+ PlannedInterpretable interpretable, GlobalResolver resolver, ExecutionFrame frame) {
try {
- return interpretable.eval(resolver);
+ return interpretable.eval(resolver, frame);
} catch (CelRuntimeException e) {
throw new StrictErrorException(e, interpretable.exprId());
} catch (Exception e) {
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java
index f287bdd59..8c8f5954d 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java
@@ -18,8 +18,6 @@
import com.google.common.base.Preconditions;
import dev.cel.common.values.ErrorValue;
-import dev.cel.runtime.CelEvaluationListener;
-import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.GlobalResolver;
final class EvalOr extends PlannedInterpretable {
@@ -28,10 +26,10 @@ final class EvalOr extends PlannedInterpretable {
private final PlannedInterpretable[] args;
@Override
- public Object eval(GlobalResolver resolver) {
+ public Object eval(GlobalResolver resolver, ExecutionFrame frame) {
ErrorValue errorValue = null;
for (PlannedInterpretable arg : args) {
- Object argVal = evalNonstrictly(arg, resolver);
+ Object argVal = evalNonstrictly(arg, resolver, frame);
if (argVal instanceof Boolean) {
// Short-circuit on true
if (((boolean) argVal)) {
@@ -53,27 +51,6 @@ public Object eval(GlobalResolver resolver) {
return false;
}
- @Override
- public Object eval(GlobalResolver resolver, CelEvaluationListener listener) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- @Override
- public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- @Override
- public Object eval(
- GlobalResolver resolver,
- CelFunctionResolver lateBoundFunctionResolver,
- CelEvaluationListener listener) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
static EvalOr create(long exprId, PlannedInterpretable[] args) {
return new EvalOr(exprId, args);
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalTestOnly.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalTestOnly.java
new file mode 100644
index 000000000..30ecdbd83
--- /dev/null
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalTestOnly.java
@@ -0,0 +1,45 @@
+// Copyright 2025 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.runtime.planner;
+
+import com.google.errorprone.annotations.Immutable;
+import dev.cel.runtime.CelEvaluationException;
+import dev.cel.runtime.GlobalResolver;
+
+@Immutable
+final class EvalTestOnly extends InterpretableAttribute {
+
+ private final InterpretableAttribute attr;
+
+ @Override
+ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException {
+ return attr.eval(resolver, frame);
+ }
+
+ @Override
+ public EvalTestOnly addQualifier(long exprId, Qualifier qualifier) {
+ PresenceTestQualifier presenceTestQualifier = PresenceTestQualifier.create(qualifier.value());
+ return new EvalTestOnly(exprId(), attr.addQualifier(exprId, presenceTestQualifier));
+ }
+
+ static EvalTestOnly create(long exprId, InterpretableAttribute attr) {
+ return new EvalTestOnly(exprId, attr);
+ }
+
+ private EvalTestOnly(long exprId, InterpretableAttribute attr) {
+ super(exprId);
+ this.attr = attr;
+ }
+}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java
index 13b59d11e..d1a33017b 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java
@@ -18,8 +18,6 @@
import static dev.cel.runtime.planner.EvalHelpers.evalStrictly;
import dev.cel.runtime.CelEvaluationException;
-import dev.cel.runtime.CelEvaluationListener;
-import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.CelResolvedOverload;
import dev.cel.runtime.GlobalResolver;
@@ -29,35 +27,16 @@ final class EvalUnary extends PlannedInterpretable {
private final PlannedInterpretable arg;
@Override
- public Object eval(GlobalResolver resolver) throws CelEvaluationException {
+ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException {
Object argVal =
- resolvedOverload.isStrict() ? evalStrictly(arg, resolver) : evalNonstrictly(arg, resolver);
+ resolvedOverload.isStrict()
+ ? evalStrictly(arg, resolver, frame)
+ : evalNonstrictly(arg, resolver, frame);
Object[] arguments = new Object[] {argVal};
return resolvedOverload.getDefinition().apply(arguments);
}
- @Override
- public Object eval(GlobalResolver resolver, CelEvaluationListener listener) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- @Override
- public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- @Override
- public Object eval(
- GlobalResolver resolver,
- CelFunctionResolver lateBoundFunctionResolver,
- CelEvaluationListener listener) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
static EvalUnary create(
long exprId, CelResolvedOverload resolvedOverload, PlannedInterpretable arg) {
return new EvalUnary(exprId, resolvedOverload, arg);
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java
index a2a4c0acc..da2979ad1 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java
@@ -18,8 +18,6 @@
import static dev.cel.runtime.planner.EvalHelpers.evalStrictly;
import dev.cel.runtime.CelEvaluationException;
-import dev.cel.runtime.CelEvaluationListener;
-import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.CelResolvedOverload;
import dev.cel.runtime.GlobalResolver;
@@ -30,40 +28,19 @@ final class EvalVarArgsCall extends PlannedInterpretable {
private final PlannedInterpretable[] args;
@Override
- public Object eval(GlobalResolver resolver) throws CelEvaluationException {
+ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException {
Object[] argVals = new Object[args.length];
for (int i = 0; i < args.length; i++) {
PlannedInterpretable arg = args[i];
argVals[i] =
resolvedOverload.isStrict()
- ? evalStrictly(arg, resolver)
- : evalNonstrictly(arg, resolver);
+ ? evalStrictly(arg, resolver, frame)
+ : evalNonstrictly(arg, resolver, frame);
}
return resolvedOverload.getDefinition().apply(argVals);
}
- @Override
- public Object eval(GlobalResolver resolver, CelEvaluationListener listener) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- @Override
- public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- @Override
- public Object eval(
- GlobalResolver resolver,
- CelFunctionResolver lateBoundFunctionResolver,
- CelEvaluationListener listener) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
static EvalVarArgsCall create(
long exprId, CelResolvedOverload resolvedOverload, PlannedInterpretable[] args) {
return new EvalVarArgsCall(exprId, resolvedOverload, args);
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java
index 628e4a70f..6bda7619d 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java
@@ -15,8 +15,6 @@
package dev.cel.runtime.planner;
import dev.cel.runtime.CelEvaluationException;
-import dev.cel.runtime.CelEvaluationListener;
-import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.CelResolvedOverload;
import dev.cel.runtime.GlobalResolver;
@@ -26,31 +24,10 @@ final class EvalZeroArity extends PlannedInterpretable {
private final CelResolvedOverload resolvedOverload;
@Override
- public Object eval(GlobalResolver resolver) throws CelEvaluationException {
+ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException {
return resolvedOverload.getDefinition().apply(EMPTY_ARRAY);
}
- @Override
- public Object eval(GlobalResolver resolver, CelEvaluationListener listener) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- @Override
- public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- @Override
- public Object eval(
- GlobalResolver resolver,
- CelFunctionResolver lateBoundFunctionResolver,
- CelEvaluationListener listener) {
- // TODO: Implement support
- throw new UnsupportedOperationException("Not yet supported");
- }
-
static EvalZeroArity create(long exprId, CelResolvedOverload resolvedOverload) {
return new EvalZeroArity(exprId, resolvedOverload);
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ExecutionFrame.java b/runtime/src/main/java/dev/cel/runtime/planner/ExecutionFrame.java
new file mode 100644
index 000000000..a436d397a
--- /dev/null
+++ b/runtime/src/main/java/dev/cel/runtime/planner/ExecutionFrame.java
@@ -0,0 +1,51 @@
+// Copyright 2025 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.runtime.planner;
+
+import dev.cel.common.CelOptions;
+import dev.cel.common.exceptions.CelIterationLimitExceededException;
+import dev.cel.runtime.GlobalResolver;
+import org.jspecify.annotations.Nullable;
+
+/** Tracks execution context within a planned program. */
+final class ExecutionFrame implements GlobalResolver {
+
+ private final GlobalResolver delegate;
+ private final int comprehensionIterationLimit;
+ private int iterationCount;
+
+ @Override
+ public @Nullable Object resolve(String name) {
+ return delegate.resolve(name);
+ }
+
+ void incrementIterations() {
+ if (comprehensionIterationLimit < 0) {
+ return;
+ }
+ if (++iterationCount > comprehensionIterationLimit) {
+ throw new CelIterationLimitExceededException(comprehensionIterationLimit);
+ }
+ }
+
+ static ExecutionFrame create(GlobalResolver delegate, CelOptions celOptions) {
+ return new ExecutionFrame(delegate, celOptions.comprehensionMaxIterations());
+ }
+
+ private ExecutionFrame(GlobalResolver delegate, int limit) {
+ this.delegate = delegate;
+ this.comprehensionIterationLimit = limit;
+ }
+}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java
index 542067349..40a9f6203 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java
@@ -28,10 +28,10 @@ final class MaybeAttribute implements Attribute {
private final ImmutableList attributes;
@Override
- public Object resolve(GlobalResolver ctx) {
+ public Object resolve(GlobalResolver ctx, ExecutionFrame frame) {
MissingAttribute maybeError = null;
for (NamespacedAttribute attr : attributes) {
- Object value = attr.resolve(ctx);
+ Object value = attr.resolve(ctx, frame);
if (value == null) {
continue;
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java
index bbb4e0422..596d1bae4 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java
@@ -24,7 +24,7 @@ final class MissingAttribute implements Attribute {
private final ImmutableSet missingAttributes;
@Override
- public Object resolve(GlobalResolver ctx) {
+ public Object resolve(GlobalResolver ctx, ExecutionFrame frame) {
throw CelAttributeNotFoundException.forFieldResolution(missingAttributes);
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java
index b90ac0824..d513bc7ba 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java
@@ -34,7 +34,7 @@ final class NamespacedAttribute implements Attribute {
private final CelTypeProvider typeProvider;
@Override
- public Object resolve(GlobalResolver ctx) {
+ public Object resolve(GlobalResolver ctx, ExecutionFrame frame) {
for (String name : namespacedNames) {
Object value = ctx.resolve(name);
if (value != null) {
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java b/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java
index 87a1a7dc4..5ce3208f8 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java
@@ -15,12 +15,18 @@
package dev.cel.runtime.planner;
import com.google.errorprone.annotations.Immutable;
-import dev.cel.runtime.Interpretable;
+import dev.cel.runtime.CelEvaluationException;
+import dev.cel.runtime.GlobalResolver;
@Immutable
-abstract class PlannedInterpretable implements Interpretable {
+abstract class PlannedInterpretable {
private final long exprId;
+ /** Runs interpretation with the given activation which supplies name/value bindings. */
+ abstract Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException;
+
+ // TODO: Implement support for late-bound functions and evaluation listener
+
long exprId() {
return exprId;
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java
index d1214fab0..646ad6c85 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java
@@ -16,6 +16,7 @@
import com.google.auto.value.AutoValue;
import com.google.errorprone.annotations.Immutable;
+import dev.cel.common.CelOptions;
import dev.cel.common.CelRuntimeException;
import dev.cel.common.values.ErrorValue;
import dev.cel.runtime.Activation;
@@ -33,6 +34,8 @@ abstract class PlannedProgram implements Program {
abstract ErrorMetadata metadata();
+ abstract CelOptions options();
+
@Override
public Object eval() throws CelEvaluationException {
return evalOrThrow(interpretable(), GlobalResolver.EMPTY);
@@ -52,7 +55,8 @@ public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctio
private Object evalOrThrow(PlannedInterpretable interpretable, GlobalResolver resolver)
throws CelEvaluationException {
try {
- Object evalResult = interpretable.eval(resolver);
+ ExecutionFrame frame = ExecutionFrame.create(resolver, options());
+ Object evalResult = interpretable.eval(resolver, frame);
if (evalResult instanceof ErrorValue) {
ErrorValue errorValue = (ErrorValue) evalResult;
throw newCelEvaluationException(errorValue.exprId(), errorValue.value());
@@ -78,7 +82,8 @@ private CelEvaluationException newCelEvaluationException(long exprId, Exception
return builder.setMetadata(metadata(), exprId).build();
}
- static Program create(PlannedInterpretable interpretable, ErrorMetadata metadata) {
- return new AutoValue_PlannedProgram(interpretable, metadata);
+ static Program create(
+ PlannedInterpretable interpretable, ErrorMetadata metadata, CelOptions options) {
+ return new AutoValue_PlannedProgram(interpretable, metadata, options);
}
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PresenceTestQualifier.java b/runtime/src/main/java/dev/cel/runtime/planner/PresenceTestQualifier.java
new file mode 100644
index 000000000..973182b9b
--- /dev/null
+++ b/runtime/src/main/java/dev/cel/runtime/planner/PresenceTestQualifier.java
@@ -0,0 +1,53 @@
+// Copyright 2025 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.runtime.planner;
+
+import static dev.cel.runtime.planner.MissingAttribute.newMissingAttribute;
+
+import dev.cel.common.values.SelectableValue;
+import java.util.Map;
+
+/** A qualifier for presence testing a field or a map key. */
+final class PresenceTestQualifier implements Qualifier {
+
+ @SuppressWarnings("Immutable")
+ private final Object value;
+
+ @Override
+ public Object value() {
+ return value;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked") // SelectableValue cast is safe
+ public Object qualify(Object obj) {
+ if (obj instanceof SelectableValue) {
+ return ((SelectableValue) obj).find(value).isPresent();
+ } else if (obj instanceof Map) {
+ Map, ?> map = (Map, ?>) obj;
+ return map.containsKey(value);
+ }
+
+ return newMissingAttribute(value.toString());
+ }
+
+ static PresenceTestQualifier create(Object value) {
+ return new PresenceTestQualifier(value);
+ }
+
+ private PresenceTestQualifier(Object value) {
+ this.value = value;
+ }
+}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java
index be197649f..1559b8482 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java
@@ -21,11 +21,13 @@
import javax.annotation.concurrent.ThreadSafe;
import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.CelContainer;
+import dev.cel.common.CelOptions;
import dev.cel.common.Operator;
import dev.cel.common.annotations.Internal;
import dev.cel.common.ast.CelConstant;
import dev.cel.common.ast.CelExpr;
import dev.cel.common.ast.CelExpr.CelCall;
+import dev.cel.common.ast.CelExpr.CelComprehension;
import dev.cel.common.ast.CelExpr.CelList;
import dev.cel.common.ast.CelExpr.CelMap;
import dev.cel.common.ast.CelExpr.CelSelect;
@@ -60,6 +62,8 @@ public final class ProgramPlanner {
private final DefaultDispatcher dispatcher;
private final AttributeFactory attributeFactory;
private final CelContainer container;
+ private final CelOptions options;
+
/**
* Plans a {@link Program} from the provided parsed-only or type-checked {@link
@@ -75,7 +79,7 @@ public Program plan(CelAbstractSyntaxTree ast) throws CelEvaluationException {
ErrorMetadata errorMetadata =
ErrorMetadata.create(ast.getSource().getPositionsMap(), ast.getSource().getDescription());
- return PlannedProgram.create(plannedInterpretable, errorMetadata);
+ return PlannedProgram.create(plannedInterpretable, errorMetadata, options);
}
private PlannedInterpretable plan(CelExpr celExpr, PlannerContext ctx) {
@@ -94,10 +98,12 @@ private PlannedInterpretable plan(CelExpr celExpr, PlannerContext ctx) {
return planCreateStruct(celExpr, ctx);
case MAP:
return planCreateMap(celExpr, ctx);
+ case COMPREHENSION:
+ return planComprehension(celExpr, ctx);
case NOT_SET:
throw new UnsupportedOperationException("Unsupported kind: " + celExpr.getKind());
default:
- throw new IllegalArgumentException("Not yet implemented kind: " + celExpr.getKind());
+ throw new UnsupportedOperationException("Unexpected kind: " + celExpr.getKind());
}
}
@@ -114,7 +120,7 @@ private PlannedInterpretable planSelect(CelExpr celExpr, PlannerContext ctx) {
}
if (select.testOnly()) {
- throw new UnsupportedOperationException("Presence tests not supported yet");
+ attribute = EvalTestOnly.create(celExpr.id(), attribute);
}
Qualifier qualifier = StringQualifier.create(select.field());
@@ -280,6 +286,27 @@ private PlannedInterpretable planCreateMap(CelExpr celExpr, PlannerContext ctx)
return EvalCreateMap.create(celExpr.id(), keys, values);
}
+ private PlannedInterpretable planComprehension(CelExpr expr, PlannerContext ctx) {
+ CelComprehension comprehension = expr.comprehension();
+
+ PlannedInterpretable accuInit = plan(comprehension.accuInit(), ctx);
+ PlannedInterpretable iterRange = plan(comprehension.iterRange(), ctx);
+ PlannedInterpretable loopCondition = plan(comprehension.loopCondition(), ctx);
+ PlannedInterpretable loopStep = plan(comprehension.loopStep(), ctx);
+ PlannedInterpretable result = plan(comprehension.result(), ctx);
+
+ return EvalFold.create(
+ expr.id(),
+ comprehension.accuVar(),
+ accuInit,
+ comprehension.iterVar(),
+ comprehension.iterVar2(),
+ iterRange,
+ loopCondition,
+ loopStep,
+ result);
+ }
+
/**
* resolveFunction determines the call target, function name, and overload name (when unambiguous)
* from the given call expr.
@@ -427,9 +454,10 @@ public static ProgramPlanner newPlanner(
CelValueProvider valueProvider,
DefaultDispatcher dispatcher,
CelValueConverter celValueConverter,
- CelContainer container) {
+ CelContainer container,
+ CelOptions options) {
return new ProgramPlanner(
- typeProvider, valueProvider, dispatcher, celValueConverter, container);
+ typeProvider, valueProvider, dispatcher, celValueConverter, container, options);
}
private ProgramPlanner(
@@ -437,11 +465,13 @@ private ProgramPlanner(
CelValueProvider valueProvider,
DefaultDispatcher dispatcher,
CelValueConverter celValueConverter,
- CelContainer container) {
+ CelContainer container,
+ CelOptions options) {
this.typeProvider = typeProvider;
this.valueProvider = valueProvider;
this.dispatcher = dispatcher;
this.container = container;
+ this.options = options;
this.attributeFactory =
AttributeFactory.newAttributeFactory(container, typeProvider, celValueConverter);
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java
index 7357d8147..a913849f6 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java
@@ -31,8 +31,8 @@ final class RelativeAttribute implements Attribute {
private final ImmutableList qualifiers;
@Override
- public Object resolve(GlobalResolver ctx) {
- Object obj = EvalHelpers.evalStrictly(operand, ctx);
+ public Object resolve(GlobalResolver ctx, ExecutionFrame frame) {
+ Object obj = EvalHelpers.evalStrictly(operand, ctx, frame);
obj = celValueConverter.toRuntimeValue(obj);
for (Qualifier qualifier : qualifiers) {
diff --git a/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java b/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java
index 39723083e..7d7243384 100644
--- a/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java
+++ b/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java
@@ -2,7 +2,7 @@
//
// 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 aj
+// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
diff --git a/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java b/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java
index fbcfbd813..255360ee1 100644
--- a/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java
+++ b/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java
@@ -2,7 +2,7 @@
//
// 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 aj
+// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel
index 9e5855f54..01df7c9ee 100644
--- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel
+++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel
@@ -38,6 +38,7 @@ java_library(
"//compiler",
"//compiler:compiler_builder",
"//extensions",
+ "//parser:macro",
"//runtime",
"//runtime:dispatcher",
"//runtime:function_binding",
@@ -48,6 +49,7 @@ java_library(
"//runtime/planner:program_planner",
"//runtime/standard:add",
"//runtime/standard:divide",
+ "//runtime/standard:dyn",
"//runtime/standard:equals",
"//runtime/standard:greater",
"//runtime/standard:greater_equals",
diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java
index 60b629f7b..968fdcc94 100644
--- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java
+++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java
@@ -65,6 +65,7 @@
import dev.cel.expr.conformance.proto3.TestAllTypes;
import dev.cel.expr.conformance.proto3.TestAllTypes.NestedMessage;
import dev.cel.extensions.CelExtensions;
+import dev.cel.parser.CelStandardMacro;
import dev.cel.runtime.CelEvaluationException;
import dev.cel.runtime.CelFunctionBinding;
import dev.cel.runtime.CelFunctionOverload;
@@ -76,6 +77,7 @@
import dev.cel.runtime.standard.AddOperator;
import dev.cel.runtime.standard.CelStandardFunction;
import dev.cel.runtime.standard.DivideOperator;
+import dev.cel.runtime.standard.DynFunction;
import dev.cel.runtime.standard.EqualsOperator;
import dev.cel.runtime.standard.GreaterEqualsOperator;
import dev.cel.runtime.standard.GreaterOperator;
@@ -114,10 +116,16 @@ public final class ProgramPlannerTest {
private static final ProgramPlanner PLANNER =
ProgramPlanner.newPlanner(
- TYPE_PROVIDER, VALUE_PROVIDER, newDispatcher(), CEL_VALUE_CONVERTER, CEL_CONTAINER);
+ TYPE_PROVIDER,
+ VALUE_PROVIDER,
+ newDispatcher(),
+ CEL_VALUE_CONVERTER,
+ CEL_CONTAINER,
+ CEL_OPTIONS);
private static final CelCompiler CEL_COMPILER =
CelCompilerFactory.standardCelCompilerBuilder()
+ .setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName()))
.addVar("map_var", MapType.create(SimpleType.STRING, SimpleType.DYN))
.addVar("int_var", SimpleType.INT)
@@ -141,7 +149,7 @@ public final class ProgramPlannerTest {
newMemberOverload(
"bytes_concat_bytes", SimpleType.BYTES, SimpleType.BYTES, SimpleType.BYTES)))
.addMessageTypes(TestAllTypes.getDescriptor())
- .addLibraries(CelExtensions.optional())
+ .addLibraries(CelExtensions.optional(), CelExtensions.comprehensions())
.setContainer(CEL_CONTAINER)
.build();
@@ -175,6 +183,7 @@ private static DefaultDispatcher newDispatcher() {
builder,
Operator.NOT_STRICTLY_FALSE.getFunction(),
fromStandardFunction(NotStrictlyFalseFunction.create()));
+ addBindings(builder, "dyn", fromStandardFunction(DynFunction.create()));
// Custom functions
addBindings(
@@ -656,7 +665,7 @@ public void plan_select_safeTraversal() throws Exception {
CelAbstractSyntaxTree ast = compile("msg.single_nested_message.bb");
Program program = PLANNER.plan(ast);
- Object result = program.eval(ImmutableMap.of("msg", TestAllTypes.newBuilder().build()));
+ Object result = program.eval(ImmutableMap.of("msg", TestAllTypes.getDefaultInstance()));
assertThat(result).isEqualTo(0L);
}
@@ -742,6 +751,105 @@ public void plan_select_stringQualificationFail_throws() throws Exception {
+ " performed on messages or maps.");
}
+ @Test
+ public void plan_select_presenceTest(@TestParameter PresenceTestCase testCase) throws Exception {
+ CelAbstractSyntaxTree ast = compile(testCase.expression);
+ Program program = PLANNER.plan(ast);
+
+ boolean result =
+ (boolean)
+ program.eval(
+ ImmutableMap.of("msg", testCase.inputParam, "map_var", testCase.inputParam));
+
+ assertThat(result).isEqualTo(testCase.expected);
+ }
+
+ @Test
+ public void plan_select_badPresenceTest_throws() throws Exception {
+ CelAbstractSyntaxTree ast = compile("has(dyn([]).invalid)");
+ Program program = PLANNER.plan(ast);
+
+ CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval);
+ assertThat(e)
+ .hasMessageThat()
+ .contains(
+ "Error resolving field 'invalid'. Field selections must be performed on messages or"
+ + " maps.");
+ }
+
+ @Test
+ @TestParameters("{expression: '[1,2,3].exists(x, x > 0) == true'}")
+ @TestParameters("{expression: '[1,2,3].exists(x, x < 0) == false'}")
+ @TestParameters("{expression: '[1,2,3].exists(i, v, i >= 0 && v > 0) == true'}")
+ @TestParameters("{expression: '[1,2,3].exists(i, v, i < 0 || v < 0) == false'}")
+ @TestParameters("{expression: '[1,2,3].map(x, x + 1) == [2,3,4]'}")
+ public void plan_comprehension_lists(String expression) throws Exception {
+ CelAbstractSyntaxTree ast = compile(expression);
+ Program program = PLANNER.plan(ast);
+
+ boolean result = (boolean) program.eval();
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ @TestParameters("{expression: '{\"a\": 1, \"b\": 2}.exists(k, k == \"a\")'}")
+ @TestParameters("{expression: '{\"a\": 1, \"b\": 2}.exists(k, k == \"c\") == false'}")
+ @TestParameters("{expression: '{\"a\": \"b\", \"c\": \"c\"}.exists(k, v, k == v)'}")
+ @TestParameters("{expression: '{\"a\": 1, \"b\": 2}.exists(k, v, v == 3) == false'}")
+ public void plan_comprehension_maps(String expression) throws Exception {
+ CelAbstractSyntaxTree ast = compile(expression);
+ Program program = PLANNER.plan(ast);
+
+ boolean result = (boolean) program.eval();
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ @TestParameters("{expression: '[1, 2, 3, 4, 5, 6].map(x, x)'}")
+ @TestParameters("{expression: '[1, 2, 3].map(x, [1, 2].map(y, x + y))'}")
+ public void plan_comprehension_iterationLimit_throws(String expression) throws Exception {
+ CelOptions options = CelOptions.current().comprehensionMaxIterations(5).build();
+ ProgramPlanner planner =
+ ProgramPlanner.newPlanner(
+ TYPE_PROVIDER,
+ ProtoMessageValueProvider.newInstance(options, DYNAMIC_PROTO),
+ newDispatcher(),
+ CEL_VALUE_CONVERTER,
+ CEL_CONTAINER,
+ options);
+ CelAbstractSyntaxTree ast = compile(expression);
+
+ Program program = planner.plan(ast);
+
+ CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval);
+ assertThat(e).hasMessageThat().contains("Iteration budget exceeded: 5");
+ assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.ITERATION_BUDGET_EXCEEDED);
+ }
+
+ @Test
+ public void plan_comprehension_iterationLimit_success() throws Exception {
+ CelOptions options = CelOptions.current().comprehensionMaxIterations(10).build();
+ ProgramPlanner planner =
+ ProgramPlanner.newPlanner(
+ TYPE_PROVIDER,
+ ProtoMessageValueProvider.newInstance(options, DYNAMIC_PROTO),
+ newDispatcher(),
+ CEL_VALUE_CONVERTER,
+ CEL_CONTAINER,
+ options);
+ CelAbstractSyntaxTree ast = compile("[1, 2, 3].map(x, [1, 2].map(y, x + y))");
+
+ Program program = planner.plan(ast);
+
+ Object result = program.eval();
+ assertThat(result)
+ .isEqualTo(
+ ImmutableList.of(
+ ImmutableList.of(2L, 3L), ImmutableList.of(3L, 4L), ImmutableList.of(4L, 5L)));
+ }
+
private CelAbstractSyntaxTree compile(String expression) throws Exception {
CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst();
if (isParseOnly) {
@@ -814,4 +922,31 @@ private enum TypeLiteralTestCase {
this.type = TypeType.create(type);
}
}
+
+ @SuppressWarnings("Immutable") // Test only
+ private enum PresenceTestCase {
+ PROTO_FIELD_PRESENT(
+ "has(msg.single_string)", TestAllTypes.newBuilder().setSingleString("foo").build(), true),
+ PROTO_FIELD_ABSENT("has(msg.single_string)", TestAllTypes.getDefaultInstance(), false),
+ PROTO_NESTED_FIELD_PRESENT(
+ "has(msg.single_nested_message.bb)",
+ TestAllTypes.newBuilder()
+ .setSingleNestedMessage(NestedMessage.newBuilder().setBb(42).build())
+ .build(),
+ true),
+ PROTO_NESTED_FIELD_ABSENT(
+ "has(msg.single_nested_message.bb)", TestAllTypes.getDefaultInstance(), false),
+ PROTO_MAP_KEY_PRESENT("has(map_var.foo)", ImmutableMap.of("foo", "1"), true),
+ PROTO_MAP_KEY_ABSENT("has(map_var.bar)", ImmutableMap.of(), false);
+
+ private final String expression;
+ private final Object inputParam;
+ private final Object expected;
+
+ PresenceTestCase(String expression, Object inputParam, Object expected) {
+ this.expression = expression;
+ this.inputParam = inputParam;
+ this.expected = expected;
+ }
+ }
}