diff --git a/common/BUILD.bazel b/common/BUILD.bazel index 815d8a1da..7b5c7af4c 100644 --- a/common/BUILD.bazel +++ b/common/BUILD.bazel @@ -126,3 +126,8 @@ java_library( name = "operator", exports = ["//common/src/main/java/dev/cel/common:operator"], ) + +cel_android_library( + name = "operator_android", + exports = ["//common/src/main/java/dev/cel/common:operator_android"], +) 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/BUILD.bazel b/common/src/main/java/dev/cel/common/BUILD.bazel index a5fffe049..333ec4b78 100644 --- a/common/src/main/java/dev/cel/common/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/BUILD.bazel @@ -360,3 +360,11 @@ java_library( ], deps = ["@maven//:com_google_guava_guava"], ) + +cel_android_library( + name = "operator_android", + srcs = ["Operator.java"], + tags = [ + ], + deps = ["@maven_android//:com_google_guava_guava"], +) 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/common/src/main/java/dev/cel/common/types/BUILD.bazel b/common/src/main/java/dev/cel/common/types/BUILD.bazel index a35a897b8..b1401dc9d 100644 --- a/common/src/main/java/dev/cel/common/types/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/types/BUILD.bazel @@ -183,6 +183,19 @@ java_library( ], ) +java_library( + name = "message_lite_type_provider", + srcs = [ + "ProtoMessageLiteTypeProvider.java", + ], + tags = [ + ], + deps = [ + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + java_library( name = "default_type_provider", srcs = [ diff --git a/common/src/main/java/dev/cel/common/types/ProtoMessageLiteTypeProvider.java b/common/src/main/java/dev/cel/common/types/ProtoMessageLiteTypeProvider.java new file mode 100644 index 000000000..119d70751 --- /dev/null +++ b/common/src/main/java/dev/cel/common/types/ProtoMessageLiteTypeProvider.java @@ -0,0 +1,115 @@ +// 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.types; + +import static com.google.common.collect.ImmutableMap.toImmutableMap; + +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableMap; +import dev.cel.protobuf.CelLiteDescriptor; +import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor; +import dev.cel.protobuf.CelLiteDescriptor.MessageLiteDescriptor; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +/** TODO */ +public final class ProtoMessageLiteTypeProvider implements CelTypeProvider { + private static final ImmutableMap PROTO_TYPE_TO_CEL_TYPE = + ImmutableMap.builder() + .put(FieldLiteDescriptor.Type.DOUBLE, SimpleType.DOUBLE) + .put(FieldLiteDescriptor.Type.FLOAT, SimpleType.DOUBLE) + .put(FieldLiteDescriptor.Type.INT64, SimpleType.INT) + .put(FieldLiteDescriptor.Type.INT32, SimpleType.INT) + .put(FieldLiteDescriptor.Type.SFIXED32, SimpleType.INT) + .put(FieldLiteDescriptor.Type.SFIXED64, SimpleType.INT) + .put(FieldLiteDescriptor.Type.SINT32, SimpleType.INT) + .put(FieldLiteDescriptor.Type.SINT64, SimpleType.INT) + .put(FieldLiteDescriptor.Type.BOOL, SimpleType.BOOL) + .put(FieldLiteDescriptor.Type.STRING, SimpleType.STRING) + .put(FieldLiteDescriptor.Type.BYTES, SimpleType.BYTES) + .put(FieldLiteDescriptor.Type.FIXED32, SimpleType.UINT) + .put(FieldLiteDescriptor.Type.FIXED64, SimpleType.UINT) + .put(FieldLiteDescriptor.Type.UINT32, SimpleType.UINT) + .put(FieldLiteDescriptor.Type.UINT64, SimpleType.UINT) + .buildOrThrow(); + + private final ImmutableMap allTypes; + + @Override + public ImmutableCollection types() { + return null; + } + + @Override + public Optional findType(String typeName) { + return Optional.empty(); + } + + public static ProtoMessageLiteTypeProvider newInstance( + Set celLiteDescriptors) { + return new ProtoMessageLiteTypeProvider(celLiteDescriptors); + } + + private ProtoMessageLiteTypeProvider(Set celLiteDescriptors) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (CelLiteDescriptor descriptor : celLiteDescriptors) { + for (Entry entry : + descriptor.getProtoTypeNamesToDescriptors().entrySet()) { + builder.put(entry.getKey(), createMessageType(entry.getValue())); + } + } + + this.allTypes = builder.buildOrThrow(); + } + + private static ProtoMessageType createMessageType(MessageLiteDescriptor messageLiteDescriptor) { + ImmutableMap fields = + messageLiteDescriptor.getFieldDescriptors().stream() + .collect(toImmutableMap(FieldLiteDescriptor::getFieldName, Function.identity())); + + return new ProtoMessageType( + messageLiteDescriptor.getProtoTypeName(), + fields.keySet(), + new FieldResolver(fields), + extensionFieldName -> { + throw new UnsupportedOperationException( + "Proto extensions are not yet supported in MessageLite."); + }); + } + + private static class FieldResolver implements StructType.FieldResolver { + private final ImmutableMap fields; + + @Override + public Optional findField(String fieldName) { + FieldLiteDescriptor fieldDescriptor = fields.get(fieldName); + if (fieldDescriptor == null) { + return Optional.empty(); + } + + FieldLiteDescriptor.Type fieldType = fieldDescriptor.getProtoFieldType(); + switch (fieldDescriptor.getProtoFieldType()) { + default: + return Optional.of(PROTO_TYPE_TO_CEL_TYPE.get(fieldType)); + } + } + + private FieldResolver(ImmutableMap fields) { + this.fields = fields; + } + } +} diff --git a/common/src/main/java/dev/cel/common/values/BUILD.bazel b/common/src/main/java/dev/cel/common/values/BUILD.bazel index e9b4be4f1..c52a22c5f 100644 --- a/common/src/main/java/dev/cel/common/values/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/values/BUILD.bazel @@ -57,6 +57,7 @@ java_library( tags = [ ], deps = [ + ":values", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], @@ -68,6 +69,7 @@ cel_android_library( tags = [ ], deps = [ + "//common/values:values_android", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", ], @@ -79,6 +81,7 @@ java_library( tags = [ ], deps = [ + "//common/values", "//common/values:cel_value_provider", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -92,6 +95,7 @@ cel_android_library( ], deps = [ "//common/values:cel_value_provider_android", + "//common/values:values_android", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", ], @@ -213,7 +217,9 @@ java_library( "//common/annotations", "//common/internal:dynamic_proto", "//common/internal:proto_message_factory", + "//common/values", "//common/values:base_proto_cel_value_converter", + "//common/values:cel_value_provider", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_protobuf_protobuf_java", ], @@ -284,7 +290,9 @@ java_library( "//common/annotations", "//common/internal:cel_lite_descriptor_pool", "//common/internal:default_lite_descriptor_pool", + "//common/values", "//common/values:base_proto_cel_value_converter", + "//common/values:cel_value_provider", "//protobuf:cel_lite_descriptor", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -304,6 +312,8 @@ cel_android_library( "//common/internal:cel_lite_descriptor_pool_android", "//common/internal:default_lite_descriptor_pool_android", "//common/values:base_proto_cel_value_converter_android", + "//common/values:cel_value_provider_android", + "//common/values:values_android", "//protobuf:cel_lite_descriptor", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", diff --git a/common/src/main/java/dev/cel/common/values/BaseProtoMessageValueProvider.java b/common/src/main/java/dev/cel/common/values/BaseProtoMessageValueProvider.java index f42a16179..f9b7a6ce4 100644 --- a/common/src/main/java/dev/cel/common/values/BaseProtoMessageValueProvider.java +++ b/common/src/main/java/dev/cel/common/values/BaseProtoMessageValueProvider.java @@ -25,7 +25,4 @@ */ @Internal @Immutable -public abstract class BaseProtoMessageValueProvider implements CelValueProvider { - - public abstract BaseProtoCelValueConverter protoCelValueConverter(); -} +public abstract class BaseProtoMessageValueProvider implements CelValueProvider {} diff --git a/common/src/main/java/dev/cel/common/values/CelValueConverter.java b/common/src/main/java/dev/cel/common/values/CelValueConverter.java index b58790eaf..ae0b40ef7 100644 --- a/common/src/main/java/dev/cel/common/values/CelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/CelValueConverter.java @@ -33,7 +33,13 @@ @SuppressWarnings("unchecked") // Unchecked cast of generics due to type-erasure (ex: MapValue). @Internal @Immutable -abstract class CelValueConverter { +public class CelValueConverter { + + private static final CelValueConverter DEFAULT_INSTANCE = new CelValueConverter(); + + public static CelValueConverter getDefaultInstance() { + return DEFAULT_INSTANCE; + } /** Adapts a {@link CelValue} to a plain old Java Object. */ public Object unwrap(CelValue celValue) { diff --git a/common/src/main/java/dev/cel/common/values/CelValueProvider.java b/common/src/main/java/dev/cel/common/values/CelValueProvider.java index 717834660..20ae865e7 100644 --- a/common/src/main/java/dev/cel/common/values/CelValueProvider.java +++ b/common/src/main/java/dev/cel/common/values/CelValueProvider.java @@ -27,4 +27,8 @@ public interface CelValueProvider { * a wrapper. */ Optional newValue(String structType, Map fields); + + default CelValueConverter celValueConverter() { + return CelValueConverter.getDefaultInstance(); + } } diff --git a/common/src/main/java/dev/cel/common/values/CombinedCelValueProvider.java b/common/src/main/java/dev/cel/common/values/CombinedCelValueProvider.java index 8fe62cb7b..6aff39a45 100644 --- a/common/src/main/java/dev/cel/common/values/CombinedCelValueProvider.java +++ b/common/src/main/java/dev/cel/common/values/CombinedCelValueProvider.java @@ -30,6 +30,7 @@ @Immutable public final class CombinedCelValueProvider implements CelValueProvider { private final ImmutableList celValueProviders; + private final CelValueConverter celValueConverter; /** Combines the provided first and second {@link CelValueProvider}. */ public static CombinedCelValueProvider combine(CelValueProvider... providers) { @@ -49,12 +50,18 @@ public Optional newValue(String structType, Map fields) return Optional.empty(); } + @Override + public CelValueConverter celValueConverter() { + return celValueConverter; + } + /** Returns the underlying {@link CelValueProvider}s in order. */ public ImmutableList valueProviders() { return celValueProviders; } private CombinedCelValueProvider(ImmutableList providers) { - celValueProviders = checkNotNull(providers); + this.celValueProviders = checkNotNull(providers); + this.celValueConverter = providers.get(0).celValueConverter(); } } diff --git a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java index 08f30b9d1..9400ae961 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java @@ -82,7 +82,13 @@ public Object toRuntimeValue(Object value) { } if (value instanceof MessageOrBuilder) { - MessageOrBuilder message = (MessageOrBuilder) value; + Message message; + if (value instanceof Message.Builder) { + message = ((Message.Builder) value).build(); + } else { + message = (Message) value; + } + // Attempt to convert the proto from a dynamic message into a concrete message if possible. if (message instanceof DynamicMessage) { message = dynamicProto.maybeAdaptDynamicMessage((DynamicMessage) message); diff --git a/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java b/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java index 3fbb0ad75..e064e1f48 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java @@ -28,6 +28,7 @@ import com.google.protobuf.CodedInputStream; import com.google.protobuf.ExtensionRegistryLite; import com.google.protobuf.MessageLite; +import com.google.protobuf.MessageLiteOrBuilder; import com.google.protobuf.WireFormat; import dev.cel.common.annotations.Internal; import dev.cel.common.internal.CelLiteDescriptorPool; @@ -163,8 +164,13 @@ Object getDefaultCelValue(String protoTypeName, String fieldName) { @SuppressWarnings("LiteProtoToString") // No alternative identifier to use. Debug only info is OK. public Object toRuntimeValue(Object value) { checkNotNull(value); - if (value instanceof MessageLite) { - MessageLite msg = (MessageLite) value; + if (value instanceof MessageLiteOrBuilder) { + MessageLite msg; + if (value instanceof MessageLite.Builder) { + msg = ((MessageLite.Builder) value).build(); + } else { + msg = (MessageLite) value; + } MessageLiteDescriptor descriptor = descriptorPool diff --git a/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValueProvider.java b/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValueProvider.java index 041d850a6..b3ee2ef04 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValueProvider.java +++ b/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValueProvider.java @@ -32,12 +32,12 @@ */ @Immutable @Beta -public class ProtoMessageLiteValueProvider extends BaseProtoMessageValueProvider { +public class ProtoMessageLiteValueProvider implements CelValueProvider { private final CelLiteDescriptorPool descriptorPool; private final ProtoLiteCelValueConverter protoLiteCelValueConverter; @Override - public BaseProtoCelValueConverter protoCelValueConverter() { + public CelValueConverter celValueConverter() { return protoLiteCelValueConverter; } diff --git a/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java b/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java index 5bf2927ab..a05658c8f 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java +++ b/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java @@ -34,13 +34,13 @@ */ @Immutable @Internal -public class ProtoMessageValueProvider extends BaseProtoMessageValueProvider { +public class ProtoMessageValueProvider implements CelValueProvider { private final ProtoAdapter protoAdapter; private final ProtoMessageFactory protoMessageFactory; private final ProtoCelValueConverter protoCelValueConverter; @Override - public BaseProtoCelValueConverter protoCelValueConverter() { + public CelValueConverter celValueConverter() { return protoCelValueConverter; } diff --git a/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueProviderTest.java b/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueProviderTest.java index bbe116c80..95e0e031c 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueProviderTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueProviderTest.java @@ -49,6 +49,6 @@ public void newValue_emptyFields_success() { @Test public void getProtoLiteCelValueConverter() { - assertThat(VALUE_PROVIDER.protoCelValueConverter()).isNotNull(); + assertThat(VALUE_PROVIDER.celValueConverter()).isNotNull(); } } diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index 7760d96b8..1dd2ab7be 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -16,6 +16,7 @@ java_library( "//runtime/src/main/java/dev/cel/runtime:descriptor_message_provider", "//runtime/src/main/java/dev/cel/runtime:function_overload", "//runtime/src/main/java/dev/cel/runtime:runtime_type_provider", + "//runtime/src/main/java/dev/cel/runtime:variable_resolver", ], ) @@ -255,3 +256,16 @@ java_library( visibility = ["//:internal"], exports = ["//runtime/src/main/java/dev/cel/runtime:metadata"], ) + +java_library( + name = "variable_resolver", + exports = ["//runtime/src/main/java/dev/cel/runtime:variable_resolver"], +) + +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..722496ad9 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -475,10 +475,9 @@ RUNTIME_SOURCES = [ "CelRuntime.java", "CelRuntimeBuilder.java", "CelRuntimeFactory.java", + "CelRuntimeImpl.java", "CelRuntimeLegacyImpl.java", "CelRuntimeLibrary.java", - "CelVariableResolver.java", - "HierarchicalVariableResolver.java", "ProgramImpl.java", "UnknownContext.java", ] @@ -570,6 +569,8 @@ java_library( name = "metadata", srcs = ["Metadata.java"], # used_by_android + tags = [ + ], deps = [ "//common/annotations", "@maven//:com_google_errorprone_error_prone_annotations", @@ -579,11 +580,13 @@ java_library( java_library( name = "interpretable", srcs = INTERPRABLE_SOURCES, + tags = [ + ], deps = [ ":evaluation_exception", - ":evaluation_listener", ":function_resolver", "//common/annotations", + "//runtime:evaluation_listener", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:org_jspecify_jspecify", ], @@ -726,6 +729,7 @@ java_library( ], deps = [ ":function_overload", + ":resolved_overload", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], @@ -738,6 +742,7 @@ cel_android_library( ], deps = [ ":function_overload_android", + ":resolved_overload_android", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", ], @@ -815,10 +820,12 @@ java_library( ":runtime_type_provider", ":standard_functions", ":unknown_attributes", + ":variable_resolver", "//:auto_value", "//common:cel_ast", "//common:cel_descriptor_util", "//common:cel_descriptors", + "//common:container", "//common:options", "//common/annotations", "//common/internal:cel_descriptor_pools", @@ -826,8 +833,17 @@ java_library( "//common/internal:dynamic_proto", "//common/internal:proto_message_factory", "//common/types:cel_types", + "//common/types:default_type_provider", + "//common/types:message_type_provider", + "//common/types:type_providers", + "//common/values", "//common/values:cel_value_provider", + "//common/values:combined_cel_value_provider", "//common/values:proto_message_value_provider", + "//runtime:proto_message_runtime_helpers", + "//runtime:resolved_overload", + "//runtime:runtime_helpers", + "//runtime/planner:program_planner", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -879,6 +895,7 @@ java_library( "//common/values:cel_value_provider", "//runtime/standard:standard_function", "@maven//:com_google_code_findbugs_annotations", + "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], ) @@ -893,6 +910,7 @@ java_library( ":interpretable", ":program", "//:auto_value", + "//runtime:variable_resolver", "@maven//:com_google_errorprone_error_prone_annotations", ], ) @@ -906,6 +924,7 @@ cel_android_library( ":function_resolver_android", ":interpretable_android", ":program_android", + ":variable_resolver", "//:auto_value", "@maven//:com_google_errorprone_error_prone_annotations", ], @@ -919,6 +938,7 @@ cel_android_library( deps = [ ":cel_value_runtime_type_provider_android", ":dispatcher_android", + ":evaluation_exception", ":function_binding_android", ":interpreter_android", ":lite_program_impl_android", @@ -930,8 +950,12 @@ cel_android_library( "//:auto_value", "//common:cel_ast_android", "//common:options", + "//common/src/main/java/dev/cel/common/values:cel_value_android", + "//common/types:type_providers_android", "//common/values:cel_value_provider_android", + "//common/values:values_android", "//runtime/standard:standard_function_android", + "//third_party/java/jsr305_annotations", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -1033,11 +1057,8 @@ java_library( "//common:runtime_exception", "//common/annotations", "//common/values", - "//common/values:base_proto_cel_value_converter", - "//common/values:base_proto_message_value_provider", "//common/values:cel_value", "//common/values:cel_value_provider", - "//common/values:combined_cel_value_provider", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", @@ -1142,7 +1163,9 @@ java_library( name = "concatenated_list_view", srcs = ["ConcatenatedListView.java"], # used_by_android - visibility = ["//visibility:private"], + tags = [ + ], + deps = ["//common/annotations"], ) java_library( @@ -1203,6 +1226,8 @@ java_library( deps = [ ":evaluation_exception", ":function_resolver", + ":variable_resolver", + "//runtime:variable_resolver", "@maven//:com_google_errorprone_error_prone_annotations", ], ) @@ -1215,6 +1240,7 @@ cel_android_library( deps = [ ":evaluation_exception", ":function_resolver_android", + ":variable_resolver", "//:auto_value", "@maven//:com_google_errorprone_error_prone_annotations", ], @@ -1245,3 +1271,16 @@ cel_android_library( "@maven_android//:com_google_guava_guava", ], ) + +java_library( + name = "variable_resolver", + srcs = [ + "CelVariableResolver.java", + "HierarchicalVariableResolver.java", + ], + # used_by_android + tags = [ + ], + deps = [ + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/CelEvaluationExceptionBuilder.java b/runtime/src/main/java/dev/cel/runtime/CelEvaluationExceptionBuilder.java index 6aaed4da7..6adbcb672 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelEvaluationExceptionBuilder.java +++ b/runtime/src/main/java/dev/cel/runtime/CelEvaluationExceptionBuilder.java @@ -83,10 +83,16 @@ public static CelEvaluationExceptionBuilder newBuilder(String message, Object... */ @Internal public static CelEvaluationExceptionBuilder newBuilder(CelRuntimeException celRuntimeException) { + // TODO: Temporary until migration is complete. Throwable cause = celRuntimeException.getCause(); - return new CelEvaluationExceptionBuilder(cause.getMessage()) - .setCause(cause) - .setErrorCode(celRuntimeException.getErrorCode()); + String message = + cause == null + ? celRuntimeException.getMessage() + : celRuntimeException.getCause().getMessage(); + + return new CelEvaluationExceptionBuilder(message) + .setErrorCode(celRuntimeException.getErrorCode()) + .setCause(cause); } private CelEvaluationExceptionBuilder(String message) { diff --git a/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java b/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java index 79b0f3f54..ae938ba4b 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java +++ b/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java @@ -14,8 +14,13 @@ package dev.cel.runtime; +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.Immutable; +import java.util.Collection; /** * Binding consisting of an overload id, a Java-native argument signature, and an overload @@ -35,7 +40,6 @@ * *

Examples: string_startsWith_string, mathMax_list, lessThan_money_money */ - @Immutable public interface CelFunctionBinding { String getOverloadId(); @@ -70,4 +74,20 @@ static CelFunctionBinding from( return new FunctionBindingImpl( overloadId, ImmutableList.copyOf(argTypes), impl, /* isStrict= */ true); } + + /** TODO */ + static ImmutableSet groupOverloads( + String functionName, CelFunctionBinding... overloadBindings) { + return groupOverloads(functionName, ImmutableList.copyOf(overloadBindings)); + } + + /** TODO */ + static ImmutableSet groupOverloads( + String functionName, Collection overloadBindings) { + checkArgument(!Strings.isNullOrEmpty(functionName), "Function name cannot be null or empty"); + checkArgument(!overloadBindings.isEmpty(), "You must provide at least one binding."); + + return FunctionBindingImpl.groupOverloadsToFunction( + functionName, ImmutableSet.copyOf(overloadBindings)); + } } diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntime.java b/runtime/src/main/java/dev/cel/runtime/CelRuntime.java index a42b7f969..71fef3173 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntime.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntime.java @@ -42,9 +42,6 @@ interface Program extends dev.cel.runtime.Program { /** Evaluate the expression using {@code message} fields as the source of input variables. */ Object eval(Message message) throws CelEvaluationException; - /** Evaluate a compiled program with a custom variable {@code resolver}. */ - Object eval(CelVariableResolver resolver) throws CelEvaluationException; - /** * Evaluate a compiled program with a custom variable {@code resolver} and late-bound functions * {@code lateBoundFunctionResolver}. diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeFactory.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeFactory.java index e19f9d765..61f29a790 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeFactory.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeFactory.java @@ -15,6 +15,7 @@ package dev.cel.runtime; import dev.cel.common.CelOptions; +import dev.cel.common.annotations.Beta; /** Helper class to construct new {@code CelRuntime} instances. */ public final class CelRuntimeFactory { @@ -31,5 +32,16 @@ public static CelRuntimeBuilder standardCelRuntimeBuilder() { .setStandardEnvironmentEnabled(true); } + @Beta + public static CelRuntimeBuilder plannerCelRuntimeBuilder() { + return CelRuntimeImpl.newBuilder() + .setStandardFunctions(CelStandardFunctions.newBuilder().build()) + .setOptions( + CelOptions.current() + .enableTimestampEpoch(true) + .enableHeterogeneousNumericComparisons(true) + .build()); + } + private CelRuntimeFactory() {} } diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java new file mode 100644 index 000000000..051a10702 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java @@ -0,0 +1,385 @@ +// 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; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.Immutable; +import com.google.protobuf.DescriptorProtos; +import com.google.protobuf.Descriptors; +import com.google.protobuf.ExtensionRegistry; +import com.google.protobuf.Message; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelContainer; +import dev.cel.common.CelDescriptorUtil; +import dev.cel.common.CelDescriptors; +import dev.cel.common.CelOptions; +import dev.cel.common.annotations.Internal; +import dev.cel.common.internal.CelDescriptorPool; +import dev.cel.common.internal.DefaultDescriptorPool; +import dev.cel.common.internal.DefaultMessageFactory; +import dev.cel.common.internal.DynamicProto; +import dev.cel.common.types.CelTypeProvider; +import dev.cel.common.types.DefaultTypeProvider; +import dev.cel.common.types.ProtoMessageTypeProvider; +import dev.cel.common.values.CelValueConverter; +import dev.cel.common.values.CelValueProvider; +import dev.cel.common.values.CombinedCelValueProvider; +import dev.cel.common.values.ProtoMessageValueProvider; +import dev.cel.runtime.planner.ProgramPlanner; +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import org.jspecify.annotations.Nullable; + +@AutoValue +@Internal +@Immutable +abstract class CelRuntimeImpl implements CelRuntime { + + abstract ProgramPlanner planner(); + + abstract CelOptions options(); + + abstract ImmutableSet functionBindings(); + + abstract ImmutableSet fileDescriptors(); + + // Callers must guarantee that a custom runtime library is immutable. CEL provided ones are + // immutable by default. + @SuppressWarnings("Immutable") + @AutoValue.CopyAnnotations + abstract ImmutableSet runtimeLibraries(); + + abstract CelStandardFunctions standardFunctions(); + + abstract @Nullable CelValueProvider valueProvider(); + + // Extension registry is unmodifiable. Just not marked as such from Protobuf's implementation. + @SuppressWarnings("Immutable") + @AutoValue.CopyAnnotations + abstract @Nullable ExtensionRegistry extensionRegistry(); + + public Program createProgram(CelAbstractSyntaxTree ast) throws CelEvaluationException { + return toRuntimeProgram(planner().plan(ast)); + } + + public Program toRuntimeProgram(dev.cel.runtime.Program program) { + return new Program() { + + @Override + public Object eval() throws CelEvaluationException { + return program.eval(); + } + + @Override + public Object eval(Map mapValue) throws CelEvaluationException { + return program.eval(mapValue); + } + + @Override + public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctionResolver) + throws CelEvaluationException { + return program.eval(mapValue, lateBoundFunctionResolver); + } + + @Override + public Object eval(Message message) throws CelEvaluationException { + throw new UnsupportedOperationException("Not yet supported."); + } + + @Override + public Object eval(CelVariableResolver resolver) throws CelEvaluationException { + return program.eval(resolver); + } + + @Override + public Object eval( + CelVariableResolver resolver, CelFunctionResolver lateBoundFunctionResolver) + throws CelEvaluationException { + throw new UnsupportedOperationException("Not yet supported."); + } + + @Override + public Object trace(CelEvaluationListener listener) throws CelEvaluationException { + throw new UnsupportedOperationException("Trace is not yet supported."); + } + + @Override + public Object trace(Map mapValue, CelEvaluationListener listener) + throws CelEvaluationException { + throw new UnsupportedOperationException("Trace is not yet supported."); + } + + @Override + public Object trace(Message message, CelEvaluationListener listener) + throws CelEvaluationException { + throw new UnsupportedOperationException("Trace is not yet supported."); + } + + @Override + public Object trace(CelVariableResolver resolver, CelEvaluationListener listener) + throws CelEvaluationException { + throw new UnsupportedOperationException("Trace is not yet supported."); + } + + @Override + public Object trace( + CelVariableResolver resolver, + CelFunctionResolver lateBoundFunctionResolver, + CelEvaluationListener listener) + throws CelEvaluationException { + throw new UnsupportedOperationException("Trace is not yet supported."); + } + + @Override + public Object trace( + Map mapValue, + CelFunctionResolver lateBoundFunctionResolver, + CelEvaluationListener listener) + throws CelEvaluationException { + throw new UnsupportedOperationException("Trace is not yet supported."); + } + + @Override + public Object advanceEvaluation(UnknownContext context) throws CelEvaluationException { + throw new UnsupportedOperationException("Unsupported operation."); + } + }; + } + + public abstract Builder toRuntimeBuilder(); + + static Builder newBuilder() { + return new AutoValue_CelRuntimeImpl.Builder(); + } + + @AutoValue.Builder + abstract static class Builder implements CelRuntimeBuilder { + + public abstract Builder setPlanner(ProgramPlanner planner); + + public abstract Builder setOptions(CelOptions options); + + public abstract Builder setStandardFunctions(CelStandardFunctions standardFunctions); + + public abstract Builder setExtensionRegistry(ExtensionRegistry extensionRegistry); + + public abstract Builder setValueProvider(CelValueProvider celValueProvider); + + abstract CelOptions options(); + + abstract CelValueProvider valueProvider(); + + abstract CelStandardFunctions standardFunctions(); + + abstract ImmutableSet.Builder functionBindingsBuilder(); + + abstract ImmutableSet.Builder fileDescriptorsBuilder(); + + abstract ImmutableSet.Builder runtimeLibrariesBuilder(); + + @CanIgnoreReturnValue + public Builder addFunctionBindings(CelFunctionBinding... bindings) { + checkNotNull(bindings); + return addFunctionBindings(Arrays.asList(bindings)); + } + + @CanIgnoreReturnValue + public Builder addFunctionBindings(Iterable bindings) { + checkNotNull(bindings); + this.functionBindingsBuilder().addAll(bindings); + return this; + } + + @CanIgnoreReturnValue + public Builder addMessageTypes(Descriptors.Descriptor... descriptors) { + checkNotNull(descriptors); + return addMessageTypes(Arrays.asList(descriptors)); + } + + @CanIgnoreReturnValue + public Builder addMessageTypes(Iterable descriptors) { + checkNotNull(descriptors); + return addFileTypes(CelDescriptorUtil.getFileDescriptorsForDescriptors(descriptors)); + } + + @CanIgnoreReturnValue + public Builder addFileTypes(DescriptorProtos.FileDescriptorSet fileDescriptorSet) { + checkNotNull(fileDescriptorSet); + return addFileTypes( + CelDescriptorUtil.getFileDescriptorsFromFileDescriptorSet(fileDescriptorSet)); + } + + @CanIgnoreReturnValue + public Builder addFileTypes(Descriptors.FileDescriptor... fileDescriptors) { + checkNotNull(fileDescriptors); + return addFileTypes(Arrays.asList(fileDescriptors)); + } + + @CanIgnoreReturnValue + public Builder addFileTypes(Iterable fileDescriptors) { + checkNotNull(fileDescriptors); + this.fileDescriptorsBuilder().addAll(fileDescriptors); + return this; + } + + @CanIgnoreReturnValue + public Builder addLibraries(CelRuntimeLibrary... libraries) { + checkNotNull(libraries); + return this.addLibraries(Arrays.asList(libraries)); + } + + @CanIgnoreReturnValue + public Builder addLibraries(Iterable libraries) { + checkNotNull(libraries); + this.runtimeLibrariesBuilder().addAll(libraries); + return this; + } + + public Builder setTypeFactory(Function typeFactory) { + throw new UnsupportedOperationException("Unsupported. Use a custom value provider instead."); + } + + public Builder setStandardEnvironmentEnabled(boolean value) { + throw new UnsupportedOperationException( + "Unsupported. Subset the environment using setStandardFunctions instead."); + } + + /** Throws if an unsupported flag in CelOptions is toggled. */ + private static void assertAllowedCelOptions(CelOptions celOptions) { + String prefix = "Misconfigured CelOptions: "; + if (!celOptions.enableUnsignedLongs()) { + throw new IllegalArgumentException(prefix + "enableUnsignedLongs cannot be disabled."); + } + if (!celOptions.unwrapWellKnownTypesOnFunctionDispatch()) { + throw new IllegalArgumentException( + prefix + "unwrapWellKnownTypesOnFunctionDispatch cannot be disabled."); + } + + // Disallowed options in favor of subsetting + String subsettingError = "Subset the environment instead using setStandardFunctions method."; + if (!celOptions.enableStringConcatenation()) { + throw new IllegalArgumentException( + prefix + "enableStringConcatenation cannot be disabled. " + subsettingError); + } + + if (!celOptions.enableStringConversion()) { + throw new IllegalArgumentException( + prefix + "enableStringConversion cannot be disabled. " + subsettingError); + } + + if (!celOptions.enableListConcatenation()) { + throw new IllegalArgumentException( + prefix + "enableListConcatenation cannot be disabled. " + subsettingError); + } + + if (!celOptions.enableTimestampEpoch()) { + throw new IllegalArgumentException( + prefix + "enableTimestampEpoch cannot be disabled. " + subsettingError); + } + + if (!celOptions.enableHeterogeneousNumericComparisons()) { + throw new IllegalArgumentException( + prefix + + "enableHeterogeneousNumericComparisons cannot be disabled. " + + subsettingError); + } + } + + abstract CelRuntimeImpl autoBuild(); + + private static DefaultDispatcher newDispatcher( + CelStandardFunctions standardFunctions, + ImmutableSet customFunctionBindings, + RuntimeEquality runtimeEquality, + CelOptions options) { + DefaultDispatcher.Builder builder = DefaultDispatcher.newBuilder(); + for (CelFunctionBinding binding : + standardFunctions.newFunctionBindings(runtimeEquality, options)) { + builder.addOverload( + binding.getOverloadId(), + binding.getArgTypes(), + binding.isStrict(), + binding.getDefinition()); + } + + for (CelFunctionBinding binding : customFunctionBindings) { + builder.addOverload( + binding.getOverloadId(), + binding.getArgTypes(), + binding.isStrict(), + binding.getDefinition()); + } + + return builder.build(); + } + + public CelRuntime build() { + assertAllowedCelOptions(options()); + + CelDescriptors celDescriptors = + CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(fileDescriptorsBuilder().build()); + + CelDescriptorPool descriptorPool = DefaultDescriptorPool.create(celDescriptors); + DefaultMessageFactory defaultMessageFactory = DefaultMessageFactory.create(descriptorPool); + DynamicProto dynamicProto = DynamicProto.create(defaultMessageFactory); + CelValueProvider protoMessageValueProvider = + ProtoMessageValueProvider.newInstance(options(), dynamicProto); + CelValueConverter celValueConverter = protoMessageValueProvider.celValueConverter(); + if (valueProvider() != null) { + protoMessageValueProvider = + CombinedCelValueProvider.combine(protoMessageValueProvider, valueProvider()); + } + + RuntimeEquality runtimeEquality = + RuntimeEquality.create( + ProtoMessageRuntimeHelpers.create(dynamicProto, options()), options()); + ImmutableSet runtimeLibraries = runtimeLibrariesBuilder().build(); + // Add libraries, such as extensions + for (CelRuntimeLibrary celLibrary : runtimeLibraries) { + if (celLibrary instanceof CelInternalRuntimeLibrary) { + ((CelInternalRuntimeLibrary) celLibrary) + .setRuntimeOptions(this, runtimeEquality, options()); + } else { + celLibrary.setRuntimeOptions(this); + } + } + + CelTypeProvider combinedTypeProvider = + new CelTypeProvider.CombinedCelTypeProvider( + new ProtoMessageTypeProvider(celDescriptors), DefaultTypeProvider.getInstance()); + + DefaultDispatcher dispatcher = + newDispatcher( + standardFunctions(), functionBindingsBuilder().build(), runtimeEquality, options()); + + ProgramPlanner planner = + ProgramPlanner.newPlanner( + combinedTypeProvider, + protoMessageValueProvider, + dispatcher, + celValueConverter, + CelContainer.newBuilder().build(), // TODO: Accept CEL container + options()); + setPlanner(planner); + + return autoBuild(); + } + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java b/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java index 989d3b7be..380e1781d 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java +++ b/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java @@ -21,11 +21,9 @@ import dev.cel.common.CelErrorCode; import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; -import dev.cel.common.values.BaseProtoCelValueConverter; -import dev.cel.common.values.BaseProtoMessageValueProvider; import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueConverter; import dev.cel.common.values.CelValueProvider; -import dev.cel.common.values.CombinedCelValueProvider; import dev.cel.common.values.SelectableValue; import java.util.Map; import java.util.NoSuchElementException; @@ -36,31 +34,10 @@ final class CelValueRuntimeTypeProvider implements RuntimeTypeProvider { private final CelValueProvider valueProvider; - private final BaseProtoCelValueConverter protoCelValueConverter; - private static final BaseProtoCelValueConverter DEFAULT_CEL_VALUE_CONVERTER = - new BaseProtoCelValueConverter() {}; + private final CelValueConverter protoCelValueConverter; static CelValueRuntimeTypeProvider newInstance(CelValueProvider valueProvider) { - BaseProtoCelValueConverter converter = DEFAULT_CEL_VALUE_CONVERTER; - - // Find the underlying ProtoCelValueConverter. - // This is required because DefaultInterpreter works with a resolved protobuf messages directly - // in evaluation flow. - // A new runtime should not directly depend on protobuf, thus this will not be needed in the - // future. - if (valueProvider instanceof BaseProtoMessageValueProvider) { - converter = ((BaseProtoMessageValueProvider) valueProvider).protoCelValueConverter(); - } else if (valueProvider instanceof CombinedCelValueProvider) { - converter = - ((CombinedCelValueProvider) valueProvider) - .valueProviders().stream() - .filter(p -> p instanceof BaseProtoMessageValueProvider) - .map(p -> ((BaseProtoMessageValueProvider) p).protoCelValueConverter()) - .findFirst() - .orElse(DEFAULT_CEL_VALUE_CONVERTER); - } - - return new CelValueRuntimeTypeProvider(valueProvider, converter); + return new CelValueRuntimeTypeProvider(valueProvider, valueProvider.celValueConverter()); } @Override @@ -152,7 +129,7 @@ private static void throwInvalidFieldSelection(String fieldName) { } private CelValueRuntimeTypeProvider( - CelValueProvider valueProvider, BaseProtoCelValueConverter protoCelValueConverter) { + CelValueProvider valueProvider, CelValueConverter protoCelValueConverter) { this.valueProvider = checkNotNull(valueProvider); this.protoCelValueConverter = checkNotNull(protoCelValueConverter); } 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 collection) { + public ConcatenatedListView(Collection collection) { this(); addAll(collection); } diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java index 35ce243b3..13741972b 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java @@ -19,6 +19,7 @@ import com.google.auto.value.AutoBuilder; import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; @@ -127,7 +128,7 @@ public abstract static class Builder { @CanIgnoreReturnValue public Builder addOverload( String overloadId, - List> argTypes, + ImmutableList> argTypes, boolean isStrict, CelFunctionOverload overload) { checkNotNull(overloadId); @@ -136,13 +137,38 @@ public Builder addOverload( checkNotNull(overload); overloadsBuilder() - .put(overloadId, CelResolvedOverload.of(overloadId, overload, isStrict, argTypes)); + .put( + overloadId, + CelResolvedOverload.of( + overloadId, + args -> guardedOp(overloadId, args, argTypes, isStrict, overload), + isStrict, + argTypes)); return this; } public abstract DefaultDispatcher build(); } + /** Creates an invocation guard around the overload definition. */ + private static Object guardedOp( + String functionName, + Object[] args, + ImmutableList> argTypes, + boolean isStrict, + CelFunctionOverload overload) + throws CelEvaluationException { + if (!CelResolvedOverload.canHandle(args, argTypes, isStrict)) { + if (argTypes.size() == 0) { + // TEMPORARY HACK. MAKE THIS UNDERSTAND DYNAMIC DISPATCH. + return overload.apply(args); + } + throw new IllegalArgumentException("No matching overload for function: " + functionName); + } + + return overload.apply(args); + } + DefaultDispatcher(ImmutableMap overloads) { this.overloads = overloads; } diff --git a/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java b/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java index b554ce41a..a2c3edf01 100644 --- a/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java @@ -15,6 +15,8 @@ package dev.cel.runtime; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.errorprone.annotations.Immutable; @Immutable @@ -58,4 +60,49 @@ public boolean isStrict() { this.definition = definition; this.isStrict = isStrict; } + + static ImmutableSet groupOverloadsToFunction( + String functionName, ImmutableSet overloadBindings) { + if (overloadBindings.size() == 1) { + CelFunctionBinding singleBinding = Iterables.getOnlyElement(overloadBindings); + FunctionBindingImpl functionBindingImpl = + new FunctionBindingImpl( + functionName, + singleBinding.getArgTypes(), + singleBinding.getDefinition(), + singleBinding.isStrict()); + + return ImmutableSet.of(singleBinding, functionBindingImpl); + } + + ImmutableSet.Builder builder = ImmutableSet.builder(); + for (CelFunctionBinding binding : overloadBindings) { + // Skip adding overload ids that is exactly the same as the function name + // example: matches standard function has "matches" and "matches_string" as overloads. + if (!binding.getOverloadId().equals(functionName)) { + builder.add(binding); + } + } + + // Setup dynamic dispatch + CelFunctionOverload dynamicDispatchDef = + args -> { + for (CelFunctionBinding overload : overloadBindings) { + // TODO: Avoid checking twice (See CelRuntimeImpl) + // TODO: Pull canHandle to somewhere else? + if (CelResolvedOverload.canHandle(args, overload.getArgTypes(), overload.isStrict())) { + return overload.getDefinition().apply(args); + } + } + + throw new IllegalArgumentException("No matching overload for function: " + functionName); + }; + + boolean allOverloadsStrict = overloadBindings.stream().allMatch(CelFunctionBinding::isStrict); + builder.add( + new FunctionBindingImpl( + functionName, ImmutableList.of(), dynamicDispatchDef, allOverloadsStrict)); + + return builder.build(); + } } diff --git a/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java index e54f848b7..b6057269f 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java @@ -40,6 +40,11 @@ public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctio return interpretable().eval(Activation.copyOf(mapValue), lateBoundFunctionResolver); } + @Override + public Object eval(CelVariableResolver resolver) throws CelEvaluationException { + throw new UnsupportedOperationException("Unsupported"); + } + static Program plan(Interpretable interpretable) { return new AutoValue_LiteProgramImpl(interpretable); } diff --git a/runtime/src/main/java/dev/cel/runtime/Program.java b/runtime/src/main/java/dev/cel/runtime/Program.java index 3eb074317..88eeef10a 100644 --- a/runtime/src/main/java/dev/cel/runtime/Program.java +++ b/runtime/src/main/java/dev/cel/runtime/Program.java @@ -33,4 +33,7 @@ public interface Program { */ Object eval(Map mapValue, CelFunctionResolver lateBoundFunctionResolver) throws CelEvaluationException; + + /** Evaluate a compiled program with a custom variable {@code resolver}. */ + Object eval(CelVariableResolver resolver) throws CelEvaluationException; } 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 0ce487ceb..cc011ed34 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java @@ -14,64 +14,13 @@ package dev.cel.runtime.planner; - -import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; -import dev.cel.common.types.CelTypeProvider; -import dev.cel.common.types.TypeType; import dev.cel.runtime.GlobalResolver; +/** Represents a resolvable symbol or path (such as a variable or a field selection). */ @Immutable interface Attribute { - Object resolve(GlobalResolver ctx); - - final class MaybeAttribute implements Attribute { - private final ImmutableList attributes; - - @Override - public Object resolve(GlobalResolver ctx) { - for (Attribute attr : attributes) { - Object value = attr.resolve(ctx); - if (value != null) { - return value; - } - } - - // TODO: Handle unknowns - throw new UnsupportedOperationException("Unknown attributes is not supported yet"); - } - - MaybeAttribute(ImmutableList attributes) { - this.attributes = attributes; - } - } - - final class NamespacedAttribute implements Attribute { - private final ImmutableList namespacedNames; - private final CelTypeProvider typeProvider; - - @Override - public Object resolve(GlobalResolver ctx) { - for (String name : namespacedNames) { - Object value = ctx.resolve(name); - if (value != null) { - // TODO: apply qualifiers - return value; - } - - TypeType type = typeProvider.findType(name).map(TypeType::create).orElse(null); - if (type != null) { - return type; - } - } - - // TODO: Handle unknowns - throw new UnsupportedOperationException("Unknown attributes is not supported yet"); - } + Object resolve(GlobalResolver ctx, ExecutionFrame frame); - NamespacedAttribute(CelTypeProvider typeProvider, ImmutableList namespacedNames) { - this.typeProvider = typeProvider; - this.namespacedNames = namespacedNames; - } - } + Attribute addQualifier(Qualifier qualifier); } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java b/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java index bda7c46a6..632c6cd91 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java @@ -15,35 +15,46 @@ package dev.cel.runtime.planner; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.Immutable; import dev.cel.common.CelContainer; import dev.cel.common.types.CelTypeProvider; -import dev.cel.runtime.planner.Attribute.MaybeAttribute; -import dev.cel.runtime.planner.Attribute.NamespacedAttribute; +import dev.cel.common.values.CelValueConverter; @Immutable final class AttributeFactory { - private final CelContainer unusedContainer; + private final CelContainer container; private final CelTypeProvider typeProvider; + private final CelValueConverter celValueConverter; NamespacedAttribute newAbsoluteAttribute(String... names) { - return new NamespacedAttribute(typeProvider, ImmutableList.copyOf(names)); + return new NamespacedAttribute(typeProvider, celValueConverter, ImmutableSet.copyOf(names)); } - MaybeAttribute newMaybeAttribute(String... names) { - // TODO: Resolve container names + RelativeAttribute newRelativeAttribute(PlannedInterpretable operand) { + return new RelativeAttribute(operand, celValueConverter); + } + + MaybeAttribute newMaybeAttribute(String name) { return new MaybeAttribute( - ImmutableList.of(new NamespacedAttribute(typeProvider, ImmutableList.copyOf(names)))); + this, + ImmutableList.of( + new NamespacedAttribute( + typeProvider, celValueConverter, container.resolveCandidateNames(name)))); } static AttributeFactory newAttributeFactory( - CelContainer celContainer, CelTypeProvider typeProvider) { - return new AttributeFactory(celContainer, typeProvider); + CelContainer celContainer, + CelTypeProvider typeProvider, + CelValueConverter celValueConverter) { + return new AttributeFactory(celContainer, typeProvider, celValueConverter); } - private AttributeFactory(CelContainer container, CelTypeProvider typeProvider) { - this.unusedContainer = container; + private AttributeFactory( + CelContainer container, CelTypeProvider typeProvider, CelValueConverter celValueConverter) { + this.container = container; this.typeProvider = typeProvider; + this.celValueConverter = celValueConverter; } } 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 e7d7b8bdd..cb7194147 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -22,25 +22,31 @@ 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", + ":interpretable_attribute", ":planned_interpretable", ":planned_program", + ":qualifier", + ":string_qualifier", "//:auto_value", "//common:cel_ast", "//common:container", "//common:operator", + "//common:options", "//common/annotations", "//common/ast", "//common/types", "//common/types:type_providers", + "//common/values", "//common/values:cel_value_provider", "//runtime:dispatcher", "//runtime:evaluation_exception", "//runtime:evaluation_exception_builder", - "//runtime:interpretable", "//runtime:program", "//runtime:resolved_overload", "@maven//:com_google_code_findbugs_annotations", @@ -52,11 +58,15 @@ java_library( java_library( name = "planned_program", srcs = ["PlannedProgram.java"], + tags = [ + ], deps = [ ":error_metadata", + ":execution_frame", ":planned_interpretable", ":strict_error_exception", "//:auto_value", + "//common:options", "//common:runtime_exception", "//common/values", "//runtime:activation", @@ -65,6 +75,7 @@ java_library( "//runtime:function_resolver", "//runtime:interpretable", "//runtime:program", + "//runtime:variable_resolver", "@maven//:com_google_errorprone_error_prone_annotations", ], ) @@ -73,55 +84,116 @@ 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", ], ) +java_library( + name = "interpretable_attribute", + srcs = ["InterpretableAttribute.java"], + deps = [ + ":planned_interpretable", + ":qualifier", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + java_library( name = "attribute", srcs = [ "Attribute.java", "AttributeFactory.java", + "MaybeAttribute.java", + "MissingAttribute.java", + "NamespacedAttribute.java", + "RelativeAttribute.java", ], deps = [ + ":eval_helpers", + ":execution_frame", + ":planned_interpretable", + ":qualifier", "//common:container", + "//common/exceptions:attribute_not_found", "//common/types", "//common/types:type_providers", + "//common/values", + "//common/values:cel_value", "//runtime:interpretable", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], ) +java_library( + name = "qualifier", + srcs = ["Qualifier.java"], + deps = [ + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +java_library( + name = "presence_test_qualifier", + srcs = ["PresenceTestQualifier.java"], + deps = [ + ":attribute", + ":qualifier", + "//common/values", + ], +) + +java_library( + name = "string_qualifier", + srcs = ["StringQualifier.java"], + deps = [ + ":qualifier", + "//common/exceptions:attribute_not_found", + "//common/values", + ], +) + java_library( name = "eval_attribute", srcs = ["EvalAttribute.java"], deps = [ ":attribute", - ":planned_interpretable", - "//runtime:evaluation_listener", - "//runtime:function_resolver", + ":execution_frame", + ":interpretable_attribute", + ":qualifier", "//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", ], @@ -132,10 +204,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", ], @@ -146,10 +217,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", ], @@ -160,10 +230,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", ], @@ -174,10 +243,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", ], @@ -187,10 +255,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", ], @@ -200,13 +267,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", @@ -217,10 +283,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", @@ -231,20 +296,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", @@ -277,6 +368,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 6e20d4f59..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,38 +15,27 @@ 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 -final class EvalAttribute extends PlannedInterpretable { +final class EvalAttribute extends InterpretableAttribute { private final Attribute attr; @Override - public Object eval(GlobalResolver resolver) { - return attr.resolve(resolver); - } + public Object eval(GlobalResolver resolver, ExecutionFrame frame) { + Object resolved = attr.resolve(resolver, frame); + if (resolved instanceof MissingAttribute) { + ((MissingAttribute) resolved).resolve(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"); + return resolved; } @Override - public Object eval( - GlobalResolver resolver, - CelFunctionResolver lateBoundFunctionResolver, - CelEvaluationListener listener) { - throw new UnsupportedOperationException("Not yet supported"); + public EvalAttribute addQualifier(long exprId, Qualifier qualifier) { + Attribute newAttribute = attr.addQualifier(qualifier); + return create(exprId, newAttribute); } static EvalAttribute create(long exprId, Attribute attr) { 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..55be92471 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/ExecutionFrame.java @@ -0,0 +1,54 @@ +// 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 java.util.concurrent.atomic.AtomicInteger; +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 final AtomicInteger iterationCount; + + @Override + public @Nullable Object resolve(String name) { + return delegate.resolve(name); + } + + void incrementIterations() { + if (comprehensionIterationLimit < 0) { + return; + } + int iterations = iterationCount.incrementAndGet(); + if (iterations > 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; + this.iterationCount = new AtomicInteger(0); + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/InterpretableAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/InterpretableAttribute.java new file mode 100644 index 000000000..547380c11 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/InterpretableAttribute.java @@ -0,0 +1,27 @@ +// 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; + +@Immutable +abstract class InterpretableAttribute extends PlannedInterpretable { + + abstract InterpretableAttribute addQualifier(long exprId, Qualifier qualifier); + + InterpretableAttribute(long exprId) { + super(exprId); + } +} \ No newline at end of file diff --git a/runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java new file mode 100644 index 000000000..40a9f6203 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java @@ -0,0 +1,81 @@ +// 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.GlobalResolver; + +/** + * An attribute that attempts to resolve a variable against a list of potential namespaced + * attributes. This is used during parsed-only evaluation. + */ +@Immutable +final class MaybeAttribute implements Attribute { + private final AttributeFactory attrFactory; + private final ImmutableList attributes; + + @Override + public Object resolve(GlobalResolver ctx, ExecutionFrame frame) { + MissingAttribute maybeError = null; + for (NamespacedAttribute attr : attributes) { + Object value = attr.resolve(ctx, frame); + if (value == null) { + continue; + } + + if (value instanceof MissingAttribute) { + maybeError = (MissingAttribute) value; + // When the variable is missing in a maybe attribute, defer erroring. + // The variable may exist in other namespaced attributes. + continue; + } + + return value; + } + + return maybeError; + } + + @Override + public Attribute addQualifier(Qualifier qualifier) { + Object strQualifier = qualifier.value(); + ImmutableList.Builder augmentedNamesBuilder = ImmutableList.builder(); + ImmutableList.Builder attributesBuilder = ImmutableList.builder(); + for (NamespacedAttribute attr : attributes) { + if (strQualifier instanceof String && attr.qualifiers().isEmpty()) { + for (String varName : attr.candidateVariableNames()) { + augmentedNamesBuilder.add(varName + "." + strQualifier); + } + } + + attributesBuilder.add(attr.addQualifier(qualifier)); + } + ImmutableList augmentedNames = augmentedNamesBuilder.build(); + ImmutableList.Builder namespacedAttributeBuilder = ImmutableList.builder(); + if (!augmentedNames.isEmpty()) { + namespacedAttributeBuilder.add( + attrFactory.newAbsoluteAttribute(augmentedNames.toArray(new String[0]))); + } + + namespacedAttributeBuilder.addAll(attributesBuilder.build()); + return new MaybeAttribute(attrFactory, namespacedAttributeBuilder.build()); + } + + MaybeAttribute(AttributeFactory attrFactory, ImmutableList attributes) { + this.attrFactory = attrFactory; + this.attributes = attributes; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java new file mode 100644 index 000000000..596d1bae4 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java @@ -0,0 +1,47 @@ +// 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.ImmutableSet; +import dev.cel.common.exceptions.CelAttributeNotFoundException; +import dev.cel.runtime.GlobalResolver; + +/** Represents a missing attribute that is surfaced while resolving a struct field or a map key. */ +final class MissingAttribute implements Attribute { + + private final ImmutableSet missingAttributes; + + @Override + public Object resolve(GlobalResolver ctx, ExecutionFrame frame) { + throw CelAttributeNotFoundException.forFieldResolution(missingAttributes); + } + + @Override + public Attribute addQualifier(Qualifier qualifier) { + throw new UnsupportedOperationException("Unsupported operation"); + } + + static MissingAttribute newMissingAttribute(String... attributeNames) { + return newMissingAttribute(ImmutableSet.copyOf(attributeNames)); + } + + static MissingAttribute newMissingAttribute(ImmutableSet attributeNames) { + return new MissingAttribute(attributeNames); + } + + private MissingAttribute(ImmutableSet missingAttributes) { + this.missingAttributes = 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 new file mode 100644 index 000000000..d513bc7ba --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java @@ -0,0 +1,126 @@ +// 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.common.collect.ImmutableSet; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.types.CelType; +import dev.cel.common.types.CelTypeProvider; +import dev.cel.common.types.EnumType; +import dev.cel.common.types.TypeType; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueConverter; +import dev.cel.runtime.GlobalResolver; +import java.util.NoSuchElementException; + +@Immutable +final class NamespacedAttribute implements Attribute { + private final ImmutableSet namespacedNames; + private final ImmutableList qualifiers; + private final CelValueConverter celValueConverter; + private final CelTypeProvider typeProvider; + + @Override + public Object resolve(GlobalResolver ctx, ExecutionFrame frame) { + for (String name : namespacedNames) { + Object value = ctx.resolve(name); + if (value != null) { + if (!qualifiers.isEmpty()) { + return applyQualifiers(value, celValueConverter, qualifiers); + } else { + return value; + } + } + + CelType type = typeProvider.findType(name).orElse(null); + if (type != null) { + if (qualifiers.isEmpty()) { + // Resolution of a fully qualified type name: foo.bar.baz + return TypeType.create(type); + } else { + // This is potentially a fully qualified reference to an enum value + if (type instanceof EnumType && qualifiers.size() == 1) { + EnumType enumType = (EnumType) type; + String strQualifier = (String) qualifiers.get(0).value(); + return enumType + .findNumberByName(strQualifier) + .orElseThrow( + () -> + new NoSuchElementException( + String.format( + "Field %s was not found on enum %s", + enumType.name(), strQualifier))); + } + } + + throw new IllegalStateException( + "Unexpected type resolution when there were remaining qualifiers: " + type.name()); + } + } + + return MissingAttribute.newMissingAttribute(namespacedNames); + } + + ImmutableList qualifiers() { + return qualifiers; + } + + ImmutableSet candidateVariableNames() { + return namespacedNames; + } + + @Override + public NamespacedAttribute addQualifier(Qualifier qualifier) { + return new NamespacedAttribute( + typeProvider, + celValueConverter, + namespacedNames, + ImmutableList.builder().addAll(qualifiers).add(qualifier).build()); + } + + private static Object applyQualifiers( + Object value, CelValueConverter celValueConverter, ImmutableList qualifiers) { + Object obj = celValueConverter.toRuntimeValue(value); + + for (Qualifier qualifier : qualifiers) { + obj = qualifier.qualify(obj); + } + + if (obj instanceof CelValue) { + obj = celValueConverter.unwrap((CelValue) obj); + } + + return obj; + } + + NamespacedAttribute( + CelTypeProvider typeProvider, + CelValueConverter celValueConverter, + ImmutableSet namespacedNames) { + this(typeProvider, celValueConverter, namespacedNames, ImmutableList.of()); + } + + private NamespacedAttribute( + CelTypeProvider typeProvider, + CelValueConverter celValueConverter, + ImmutableSet namespacedNames, + ImmutableList qualifiers) { + this.typeProvider = typeProvider; + this.celValueConverter = celValueConverter; + this.namespacedNames = namespacedNames; + this.qualifiers = qualifiers; + } +} 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..b9a74b60b 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java @@ -16,12 +16,14 @@ 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; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelEvaluationExceptionBuilder; import dev.cel.runtime.CelFunctionResolver; +import dev.cel.runtime.CelVariableResolver; import dev.cel.runtime.GlobalResolver; import dev.cel.runtime.Program; import java.util.Map; @@ -33,6 +35,8 @@ abstract class PlannedProgram implements Program { abstract ErrorMetadata metadata(); + abstract CelOptions options(); + @Override public Object eval() throws CelEvaluationException { return evalOrThrow(interpretable(), GlobalResolver.EMPTY); @@ -49,10 +53,16 @@ public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctio throw new UnsupportedOperationException("Late bound functions not supported yet"); } + @Override + public Object eval(CelVariableResolver resolver) throws CelEvaluationException { + return evalOrThrow(interpretable(), ((name) -> resolver.find(name).orElse(null))); + } + 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 +88,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 252695ed7..807230f2a 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -18,14 +18,16 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.CheckReturnValue; -import javax.annotation.concurrent.ThreadSafe; +import com.google.errorprone.annotations.Immutable; 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; @@ -37,12 +39,12 @@ import dev.cel.common.types.CelTypeProvider; import dev.cel.common.types.StructType; import dev.cel.common.types.TypeType; +import dev.cel.common.values.CelValueConverter; import dev.cel.common.values.CelValueProvider; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelEvaluationExceptionBuilder; import dev.cel.runtime.CelResolvedOverload; import dev.cel.runtime.DefaultDispatcher; -import dev.cel.runtime.Interpretable; import dev.cel.runtime.Program; import java.util.NoSuchElementException; import java.util.Optional; @@ -51,15 +53,16 @@ * {@code ProgramPlanner} resolves functions, types, and identifiers at plan time given a * parsed-only or a type-checked expression. */ -@ThreadSafe +@Immutable @Internal public final class ProgramPlanner { - private final CelTypeProvider typeProvider; private final CelValueProvider valueProvider; 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 +78,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) { @@ -84,6 +87,8 @@ private PlannedInterpretable plan(CelExpr celExpr, PlannerContext ctx) { return planConstant(celExpr.constant()); case IDENT: return planIdent(celExpr, ctx); + case SELECT: + return planSelect(celExpr, ctx); case CALL: return planCall(celExpr, ctx); case LIST: @@ -92,11 +97,34 @@ 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()); + } + } + + private PlannedInterpretable planSelect(CelExpr celExpr, PlannerContext ctx) { + CelSelect select = celExpr.select(); + PlannedInterpretable operand = plan(select.operand(), ctx); + + InterpretableAttribute attribute; + if (operand instanceof EvalAttribute) { + attribute = (EvalAttribute) operand; + } else { + attribute = + EvalAttribute.create(celExpr.id(), attributeFactory.newRelativeAttribute(operand)); } + + if (select.testOnly()) { + attribute = EvalTestOnly.create(celExpr.id(), attribute); + } + + Qualifier qualifier = StringQualifier.create(select.field()); + + return attribute.addQualifier(celExpr.id(), qualifier); } private PlannedInterpretable planConstant(CelConstant celConstant) { @@ -188,6 +216,7 @@ private PlannedInterpretable planCall(CelExpr expr, PlannerContext ctx) { } } + // TODO: Handle all specialized calls (logical operators, conditionals, equals etc) CelResolvedOverload resolvedOverload = null; if (resolvedFunction.overloadId().isPresent()) { resolvedOverload = dispatcher.findOverload(resolvedFunction.overloadId().get()).orElse(null); @@ -217,7 +246,7 @@ private PlannedInterpretable planCreateStruct(CelExpr celExpr, PlannerContext ct ImmutableList entries = struct.entries(); String[] keys = new String[entries.size()]; - Interpretable[] values = new Interpretable[entries.size()]; + PlannedInterpretable[] values = new PlannedInterpretable[entries.size()]; for (int i = 0; i < entries.size(); i++) { Entry entry = entries.get(i); @@ -257,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. @@ -403,19 +453,26 @@ public static ProgramPlanner newPlanner( CelTypeProvider typeProvider, CelValueProvider valueProvider, DefaultDispatcher dispatcher, - CelContainer container) { - return new ProgramPlanner(typeProvider, valueProvider, dispatcher, container); + CelValueConverter celValueConverter, + CelContainer container, + CelOptions options) { + return new ProgramPlanner( + typeProvider, valueProvider, dispatcher, celValueConverter, container, options); } private ProgramPlanner( CelTypeProvider typeProvider, CelValueProvider valueProvider, DefaultDispatcher dispatcher, - CelContainer container) { + CelValueConverter celValueConverter, + CelContainer container, + CelOptions options) { this.typeProvider = typeProvider; this.valueProvider = valueProvider; this.dispatcher = dispatcher; this.container = container; - this.attributeFactory = AttributeFactory.newAttributeFactory(container, typeProvider); + this.options = options; + this.attributeFactory = + AttributeFactory.newAttributeFactory(container, typeProvider, celValueConverter); } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/Qualifier.java b/runtime/src/main/java/dev/cel/runtime/planner/Qualifier.java new file mode 100644 index 000000000..82e48e95a --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/Qualifier.java @@ -0,0 +1,28 @@ +// 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; + +/** + * Represents a qualification step (such as a field selection or map key lookup) applied to an + * intermediate value during attribute resolution. + */ +@Immutable +interface Qualifier { + Object value(); + + Object qualify(Object value); +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java new file mode 100644 index 000000000..a913849f6 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java @@ -0,0 +1,70 @@ +// 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.common.values.CelValueConverter; +import dev.cel.runtime.GlobalResolver; + +/** + * An attribute resolved relative to a base expression (operand) by applying a sequence of + * qualifiers. + */ +@Immutable +final class RelativeAttribute implements Attribute { + + private final PlannedInterpretable operand; + private final CelValueConverter celValueConverter; + private final ImmutableList qualifiers; + + @Override + public Object resolve(GlobalResolver ctx, ExecutionFrame frame) { + Object obj = EvalHelpers.evalStrictly(operand, ctx, frame); + obj = celValueConverter.toRuntimeValue(obj); + + for (Qualifier qualifier : qualifiers) { + obj = qualifier.qualify(obj); + } + + // TODO: Handle unknowns + + return obj; + } + + @Override + public Attribute addQualifier(Qualifier qualifier) { + return new RelativeAttribute( + this.operand, + celValueConverter, + ImmutableList.builderWithExpectedSize(qualifiers.size() + 1) + .addAll(this.qualifiers) + .add(qualifier) + .build()); + } + + RelativeAttribute(PlannedInterpretable operand, CelValueConverter celValueConverter) { + this(operand, celValueConverter, ImmutableList.of()); + } + + private RelativeAttribute( + PlannedInterpretable operand, + CelValueConverter celValueConverter, + ImmutableList qualifiers) { + this.operand = operand; + this.celValueConverter = celValueConverter; + this.qualifiers = qualifiers; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/StringQualifier.java b/runtime/src/main/java/dev/cel/runtime/planner/StringQualifier.java new file mode 100644 index 000000000..4ceaa0e51 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/StringQualifier.java @@ -0,0 +1,61 @@ +// 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.exceptions.CelAttributeNotFoundException; +import dev.cel.common.values.SelectableValue; +import java.util.Map; + +/** A qualifier that accesses fields or map keys using a string identifier. */ +final class StringQualifier implements Qualifier { + + private final String value; + + @Override + public String value() { + return value; + } + + @Override + @SuppressWarnings("unchecked") // Qualifications on maps/structs must be a string + public Object qualify(Object obj) { + if (obj instanceof SelectableValue) { + return ((SelectableValue) obj).select(value); + } else if (obj instanceof Map) { + Map map = (Map) obj; + if (!map.containsKey(value)) { + throw CelAttributeNotFoundException.forMissingMapKey(value); + } + + Object mapVal = map.get(value); + + if (mapVal == null) { + throw CelAttributeNotFoundException.of( + String.format("Map value cannot be null for key: %s", value)); + } + return map.get(value); + } + + throw CelAttributeNotFoundException.forFieldResolution(value); + } + + static StringQualifier create(String value) { + return new StringQualifier(value); + } + + private StringQualifier(String value) { + this.value = value; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java index 783ddd81a..82bb308d1 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java @@ -14,6 +14,7 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.ADD; import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; import com.google.common.collect.ImmutableSet; @@ -170,6 +171,6 @@ public CelFunctionBinding newFunctionBinding( } private AddOperator(ImmutableSet overloads) { - super(overloads); + super(ADD.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel index 041d07dea..ed189014f 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel @@ -48,6 +48,7 @@ java_library( ":arithmetic_helpers", ":standard_function", ":standard_overload", + "//common:operator", "//common:options", "//common:runtime_exception", "//common/internal:date_time_helpers", @@ -70,6 +71,7 @@ cel_android_library( ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//common:runtime_exception", "//common/internal:date_time_helpers_android", @@ -92,6 +94,7 @@ java_library( ":arithmetic_helpers", ":standard_function", ":standard_overload", + "//common:operator", "//common:options", "//common:runtime_exception", "//common/internal:date_time_helpers", @@ -113,6 +116,7 @@ cel_android_library( ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//common:runtime_exception", "//common/internal:date_time_helpers_android", @@ -364,6 +368,7 @@ java_library( ], deps = [ ":standard_overload", + "//common:operator", "//common:options", "//runtime:function_binding", "//runtime:runtime_equality", @@ -380,6 +385,7 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -742,6 +748,7 @@ java_library( ], deps = [ ":standard_overload", + "//common:operator", "//common:options", "//common/internal:comparison_functions", "//common/internal:proto_time_utils", @@ -763,6 +770,7 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//common/internal:comparison_functions_android", "//common/internal:proto_time_utils_android", @@ -782,6 +790,7 @@ java_library( ], deps = [ ":standard_overload", + "//common:operator", "//common:options", "//common/internal:comparison_functions", "//common/internal:proto_time_utils", @@ -803,6 +812,7 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//common/internal:comparison_functions_android", "//common/internal:proto_time_utils_android", @@ -822,6 +832,7 @@ java_library( ], deps = [ ":standard_overload", + "//common:operator", "//common:options", "//runtime:function_binding", "//runtime:runtime_equality", @@ -838,6 +849,7 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -852,6 +864,7 @@ java_library( ], deps = [ ":standard_overload", + "//common:operator", "//common:options", "//runtime:function_binding", "//runtime:runtime_equality", @@ -869,6 +882,7 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -924,6 +938,7 @@ java_library( ], deps = [ ":standard_overload", + "//common:operator", "//common:options", "//common/internal:comparison_functions", "//common/internal:proto_time_utils", @@ -945,6 +960,7 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//common/internal:comparison_functions_android", "//common/internal:proto_time_utils_android", @@ -964,6 +980,7 @@ java_library( ], deps = [ ":standard_overload", + "//common:operator", "//common:options", "//common/internal:comparison_functions", "//common/internal:proto_time_utils", @@ -985,6 +1002,7 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//common/internal:comparison_functions_android", "//common/internal:proto_time_utils_android", @@ -1004,6 +1022,7 @@ java_library( ], deps = [ ":standard_overload", + "//common:operator", "//common:options", "//runtime:function_binding", "//runtime:runtime_equality", @@ -1020,6 +1039,7 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -1071,6 +1091,7 @@ java_library( deps = [ ":arithmetic_helpers", ":standard_overload", + "//common:operator", "//common:options", "//common:runtime_exception", "//runtime:function_binding", @@ -1090,6 +1111,7 @@ cel_android_library( ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//common:runtime_exception", "//runtime:function_binding_android", @@ -1107,6 +1129,7 @@ java_library( deps = [ ":arithmetic_helpers", ":standard_overload", + "//common:operator", "//common:options", "//common:runtime_exception", "//runtime:function_binding", @@ -1126,6 +1149,7 @@ cel_android_library( ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//common:runtime_exception", "//runtime:function_binding_android", @@ -1143,6 +1167,7 @@ java_library( deps = [ ":arithmetic_helpers", ":standard_overload", + "//common:operator", "//common:options", "//common:runtime_exception", "//runtime:function_binding", @@ -1162,6 +1187,7 @@ cel_android_library( ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//common:runtime_exception", "//runtime:function_binding_android", @@ -1179,6 +1205,7 @@ java_library( deps = [ ":arithmetic_helpers", ":standard_overload", + "//common:operator", "//common:options", "//common:runtime_exception", "//runtime:function_binding", @@ -1198,6 +1225,7 @@ cel_android_library( ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//common:runtime_exception", "//runtime:function_binding_android", @@ -1214,6 +1242,7 @@ java_library( ], deps = [ ":standard_overload", + "//common:operator", "//common:options", "//runtime:function_binding", "//runtime:runtime_equality", @@ -1230,6 +1259,7 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -1426,6 +1456,7 @@ java_library( ], deps = [ ":standard_overload", + "//common:operator", "//common:options", "//runtime:function_binding", "//runtime:internal_function_binder", @@ -1443,6 +1474,7 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//runtime:function_binding_android", "//runtime:internal_function_binder_android", diff --git a/runtime/src/main/java/dev/cel/runtime/standard/BoolFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/BoolFunction.java index 5d9d3919c..60cc8b156 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BoolFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/BoolFunction.java @@ -87,6 +87,6 @@ public CelFunctionBinding newFunctionBinding( } private BoolFunction(ImmutableSet overloads) { - super(overloads); + super("bool", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/BytesFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/BytesFunction.java index e2a5230ce..7e3ab2b2f 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BytesFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/BytesFunction.java @@ -76,6 +76,6 @@ public CelFunctionBinding newFunctionBinding( } private BytesFunction(ImmutableSet overloads) { - super(overloads); + super("bytes", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/CelStandardFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/CelStandardFunction.java index bde6fa6b5..117730e62 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/CelStandardFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/CelStandardFunction.java @@ -14,8 +14,10 @@ package dev.cel.runtime.standard; -import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.ImmutableSet.toImmutableSet; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.Immutable; import dev.cel.common.CelOptions; @@ -28,20 +30,23 @@ */ @Immutable public abstract class CelStandardFunction { + private final String name; private final ImmutableSet overloads; public ImmutableSet newFunctionBindings( CelOptions celOptions, RuntimeEquality runtimeEquality) { - ImmutableSet.Builder builder = ImmutableSet.builder(); - for (CelStandardOverload overload : overloads) { - builder.add(overload.newFunctionBinding(celOptions, runtimeEquality)); - } + ImmutableSet overloadBindings = + overloads.stream() + .map(overload -> overload.newFunctionBinding(celOptions, runtimeEquality)) + .collect(toImmutableSet()); - return builder.build(); + return CelFunctionBinding.groupOverloads(name, overloadBindings); } - CelStandardFunction(ImmutableSet overloads) { - checkState(!overloads.isEmpty(), "At least 1 overload must be provided."); + CelStandardFunction(String name, ImmutableSet overloads) { + checkArgument(!Strings.isNullOrEmpty(name), "Function name must be provided."); + checkArgument(!overloads.isEmpty(), "At least 1 overload must be provided."); this.overloads = overloads; + this.name = name; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/ContainsFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/ContainsFunction.java index 21f8390b9..bf3f9f7a7 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/ContainsFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/ContainsFunction.java @@ -58,6 +58,6 @@ public CelFunctionBinding newFunctionBinding( } private ContainsFunction(ImmutableSet overloads) { - super(overloads); + super("contains", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DivideOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/DivideOperator.java index b9fdad33c..a9c36b0ff 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/DivideOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/DivideOperator.java @@ -14,6 +14,7 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.DIVIDE; import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; import com.google.common.collect.ImmutableSet; @@ -90,6 +91,6 @@ public CelFunctionBinding newFunctionBinding( } private DivideOperator(ImmutableSet overloads) { - super(overloads); + super(DIVIDE.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DoubleFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/DoubleFunction.java index 508df1983..b541bd379 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/DoubleFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/DoubleFunction.java @@ -87,6 +87,6 @@ public CelFunctionBinding newFunctionBinding( } private DoubleFunction(ImmutableSet overloads) { - super(overloads); + super("double", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java index 72f96f785..436ffd1d4 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java @@ -88,6 +88,6 @@ public CelFunctionBinding newFunctionBinding( } private DurationFunction(ImmutableSet overloads) { - super(overloads); + super("duration", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DynFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/DynFunction.java index d3cb34de9..da855de82 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/DynFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/DynFunction.java @@ -57,6 +57,6 @@ public CelFunctionBinding newFunctionBinding( } private DynFunction(ImmutableSet overloads) { - super(overloads); + super("dyn", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/EndsWithFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/EndsWithFunction.java index eeda546fe..7f4e8c035 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/EndsWithFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/EndsWithFunction.java @@ -59,6 +59,6 @@ public CelFunctionBinding newFunctionBinding( } private EndsWithFunction(ImmutableSet overloads) { - super(overloads); + super("endsWith", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/EqualsOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/EqualsOperator.java index ae78decbd..c505e069e 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/EqualsOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/EqualsOperator.java @@ -14,6 +14,8 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.EQUALS; + import com.google.common.collect.ImmutableSet; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; @@ -58,6 +60,6 @@ public CelFunctionBinding newFunctionBinding( } private EqualsOperator(ImmutableSet overloads) { - super(overloads); + super(EQUALS.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java index 04e046062..bb0fb0d79 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java @@ -89,6 +89,6 @@ public CelFunctionBinding newFunctionBinding( } private GetDateFunction(ImmutableSet overloads) { - super(overloads); + super("getDate", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java index b6f146aa9..e35888fb5 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java @@ -90,6 +90,6 @@ public CelFunctionBinding newFunctionBinding( } private GetDayOfMonthFunction(ImmutableSet overloads) { - super(overloads); + super("getDayOfMonth", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java index cede6c1e1..e2fa02961 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java @@ -106,6 +106,6 @@ public CelFunctionBinding newFunctionBinding( } private GetDayOfWeekFunction(ImmutableSet overloads) { - super(overloads); + super("getDayOfWeek", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java index 966c70bc6..4c0e62637 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java @@ -91,6 +91,6 @@ public CelFunctionBinding newFunctionBinding( } private GetDayOfYearFunction(ImmutableSet overloads) { - super(overloads); + super("getDayOfYear", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java index a91fffb72..925f61307 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java @@ -90,6 +90,6 @@ public CelFunctionBinding newFunctionBinding( } private GetFullYearFunction(ImmutableSet overloads) { - super(overloads); + super("getFullYear", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java index 94fb6391e..afe16556f 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java @@ -101,6 +101,6 @@ public CelFunctionBinding newFunctionBinding( } private GetHoursFunction(ImmutableSet overloads) { - super(overloads); + super("getHours", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java index 672226eaf..32b17cc88 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java @@ -112,6 +112,6 @@ public CelFunctionBinding newFunctionBinding( } private GetMillisecondsFunction(ImmutableSet overloads) { - super(overloads); + super("getMilliseconds", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java index 5f75d14ef..5f701f1e1 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java @@ -102,6 +102,6 @@ public CelFunctionBinding newFunctionBinding( } private GetMinutesFunction(ImmutableSet overloads) { - super(overloads); + super("getMinutes", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java index dc4e66889..a8a9ded68 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java @@ -89,6 +89,6 @@ public CelFunctionBinding newFunctionBinding( } private GetMonthFunction(ImmutableSet overloads) { - super(overloads); + super("getMonth", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java index 70fe96ec4..80030f50f 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java @@ -114,6 +114,6 @@ public CelFunctionBinding newFunctionBinding( } private GetSecondsFunction(ImmutableSet overloads) { - super(overloads); + super("getSeconds", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java index be41e0f5f..c04b4398f 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java @@ -14,6 +14,8 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.GREATER_EQUALS; + import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import com.google.protobuf.ByteString; @@ -197,6 +199,6 @@ public CelFunctionBinding newFunctionBinding( } private GreaterEqualsOperator(ImmutableSet overloads) { - super(overloads); + super(GREATER_EQUALS.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java index 21b59eebb..80edb8a9b 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java @@ -14,6 +14,8 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.GREATER; + import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import com.google.protobuf.ByteString; @@ -190,6 +192,6 @@ public CelFunctionBinding newFunctionBinding( } private GreaterOperator(ImmutableSet overloads) { - super(overloads); + super(GREATER.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/InOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/InOperator.java index 9a2c99f43..bf80cc6ff 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/InOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/InOperator.java @@ -14,6 +14,8 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.IN; + import com.google.common.collect.ImmutableSet; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; @@ -70,6 +72,6 @@ public CelFunctionBinding newFunctionBinding( } private InOperator(ImmutableSet overloads) { - super(overloads); + super(IN.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/IndexOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/IndexOperator.java index d1b88a88f..f48e8fbf5 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/IndexOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/IndexOperator.java @@ -14,6 +14,8 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.INDEX; + import com.google.common.collect.ImmutableSet; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; @@ -65,6 +67,6 @@ public CelFunctionBinding newFunctionBinding( } private IndexOperator(ImmutableSet overloads) { - super(overloads); + super(INDEX.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java index fda307a9d..248131865 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java @@ -130,6 +130,6 @@ public CelFunctionBinding newFunctionBinding( } private IntFunction(ImmutableSet overloads) { - super(overloads); + super("int", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java index e09fcce5e..7688acbc1 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java @@ -14,6 +14,8 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.LESS_EQUALS; + import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import com.google.protobuf.ByteString; @@ -194,6 +196,6 @@ public CelFunctionBinding newFunctionBinding( } private LessEqualsOperator(ImmutableSet overloads) { - super(overloads); + super(LESS_EQUALS.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java index 4afbc20a2..a53a13a92 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java @@ -14,6 +14,8 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.LESS; + import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import com.google.protobuf.ByteString; @@ -190,6 +192,6 @@ public CelFunctionBinding newFunctionBinding( } private LessOperator(ImmutableSet overloads) { - super(overloads); + super(LESS.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/LogicalNotOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/LogicalNotOperator.java index a4be6385b..6c2ec8efd 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/LogicalNotOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/LogicalNotOperator.java @@ -14,6 +14,8 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.LOGICAL_NOT; + import com.google.common.collect.ImmutableSet; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; @@ -57,6 +59,6 @@ public CelFunctionBinding newFunctionBinding( } private LogicalNotOperator(ImmutableSet overloads) { - super(overloads); + super(LOGICAL_NOT.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java index 7c24f65a2..62df04110 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java @@ -84,6 +84,6 @@ public CelFunctionBinding newFunctionBinding( } private MatchesFunction(ImmutableSet overloads) { - super(overloads); + super("matches", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/ModuloOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/ModuloOperator.java index e7246851c..01e02764d 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/ModuloOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/ModuloOperator.java @@ -14,6 +14,7 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.MODULO; import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; import com.google.common.collect.ImmutableSet; @@ -85,6 +86,6 @@ public CelFunctionBinding newFunctionBinding( } private ModuloOperator(ImmutableSet overloads) { - super(overloads); + super(MODULO.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/MultiplyOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/MultiplyOperator.java index 7e9e2f352..c55a4c968 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/MultiplyOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/MultiplyOperator.java @@ -14,6 +14,7 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.MULTIPLY; import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; import com.google.common.collect.ImmutableSet; @@ -103,6 +104,6 @@ public CelFunctionBinding newFunctionBinding( } private MultiplyOperator(ImmutableSet overloads) { - super(overloads); + super(MULTIPLY.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/NegateOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/NegateOperator.java index 2e3f094f5..cc072fdec 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/NegateOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/NegateOperator.java @@ -14,6 +14,7 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.NEGATE; import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; import com.google.common.collect.ImmutableSet; @@ -72,6 +73,6 @@ public CelFunctionBinding newFunctionBinding( } private NegateOperator(ImmutableSet overloads) { - super(overloads); + super(NEGATE.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/NotEqualsOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/NotEqualsOperator.java index 5a0475f29..27b17676e 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/NotEqualsOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/NotEqualsOperator.java @@ -14,6 +14,8 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.NOT_EQUALS; + import com.google.common.collect.ImmutableSet; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; @@ -60,6 +62,6 @@ public CelFunctionBinding newFunctionBinding( } private NotEqualsOperator(ImmutableSet overloads) { - super(overloads); + super(NOT_EQUALS.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/NotStrictlyFalseFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/NotStrictlyFalseFunction.java index af7d1a3c2..8e0ceead4 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/NotStrictlyFalseFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/NotStrictlyFalseFunction.java @@ -14,6 +14,8 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.NOT_STRICTLY_FALSE; + import com.google.common.collect.ImmutableSet; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; @@ -63,6 +65,6 @@ public CelFunctionBinding newFunctionBinding( } private NotStrictlyFalseFunction(ImmutableSet overloads) { - super(overloads); + super(NOT_STRICTLY_FALSE.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/SizeFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/SizeFunction.java index 9f79605ed..a8f787665 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/SizeFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/SizeFunction.java @@ -98,6 +98,6 @@ public CelFunctionBinding newFunctionBinding( } private SizeFunction(ImmutableSet overloads) { - super(overloads); + super("size", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/StartsWithFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/StartsWithFunction.java index 6626d6912..457dd3cf1 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/StartsWithFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/StartsWithFunction.java @@ -58,6 +58,6 @@ public CelFunctionBinding newFunctionBinding( } private StartsWithFunction(ImmutableSet overloads) { - super(overloads); + super("startsWith", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java index c491bfe76..9311962ca 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java @@ -134,6 +134,6 @@ public CelFunctionBinding newFunctionBinding( } private StringFunction(ImmutableSet overloads) { - super(overloads); + super("string", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java index 4b0ecd3ae..8a7595f92 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java @@ -14,6 +14,7 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.SUBTRACT; import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; import com.google.common.collect.ImmutableSet; @@ -157,6 +158,6 @@ public CelFunctionBinding newFunctionBinding( } private SubtractOperator(ImmutableSet overloads) { - super(overloads); + super(SUBTRACT.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java index 34fb15768..27c495034 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java @@ -107,6 +107,6 @@ public CelFunctionBinding newFunctionBinding( } private TimestampFunction(ImmutableSet overloads) { - super(overloads); + super("timestamp", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/UintFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/UintFunction.java index 89424e7fc..d120c6b75 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/UintFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/UintFunction.java @@ -158,6 +158,6 @@ public CelFunctionBinding newFunctionBinding( } private UintFunction(ImmutableSet overloads) { - super(overloads); + super("uint", overloads); } } diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 1541e9c84..522bdbd55 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -21,6 +21,7 @@ java_library( "CelLiteInterpreterTest.java", "CelValueInterpreterTest.java", "InterpreterTest.java", + "PlannerInterpreterTest.java", ] + ANDROID_TESTS, ), deps = [ @@ -133,6 +134,21 @@ java_library( ], ) +java_library( + name = "planner_interpreter_test", + testonly = 1, + srcs = [ + "PlannerInterpreterTest.java", + ], + deps = [ + "//extensions", + "//runtime", + "//testing:base_interpreter_test", + "@maven//:com_google_testparameterinjector_test_parameter_injector", + "@maven//:junit_junit", + ], +) + cel_android_local_test( name = "android_tests", srcs = ANDROID_TESTS, @@ -153,6 +169,7 @@ cel_android_local_test( "//runtime:lite_runtime_android", "//runtime:lite_runtime_factory_android", "//runtime:lite_runtime_impl_android", + "//runtime:program_android", "//runtime:standard_functions_android", "//runtime:unknown_attributes_android", "//runtime/src/main/java/dev/cel/runtime:program_android", @@ -200,6 +217,7 @@ junit4_test_suites( ":cel_lite_interpreter_test", ":cel_value_interpreter_test", ":interpreter_test", + ":planner_interpreter_test", ":tests", ], ) diff --git a/runtime/src/test/java/dev/cel/runtime/CelRuntimeFactoryTest.java b/runtime/src/test/java/dev/cel/runtime/CelRuntimeFactoryTest.java index 28676ebcb..3abf90f7e 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelRuntimeFactoryTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelRuntimeFactoryTest.java @@ -27,4 +27,10 @@ public final class CelRuntimeFactoryTest { public void standardCelRuntimeBuilder() { assertThat(CelRuntimeFactory.standardCelRuntimeBuilder().build()).isNotNull(); } + + @Test + public void plannerCelRuntimeBuilder() { + CelRuntime runtime = CelRuntimeFactory.plannerCelRuntimeBuilder().build(); + assertThat(runtime).isNotNull(); + } } 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/PlannerInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java new file mode 100644 index 000000000..fc39a70bd --- /dev/null +++ b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java @@ -0,0 +1,32 @@ +// 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; + +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import dev.cel.extensions.CelExtensions; +import dev.cel.testing.BaseInterpreterTest; +import org.junit.runner.RunWith; + +@RunWith(TestParameterInjector.class) +public class PlannerInterpreterTest extends BaseInterpreterTest { + + public PlannerInterpreterTest() { + super( + CelRuntimeFactory.plannerCelRuntimeBuilder() + .addLibraries(CelExtensions.optional()) + .addFileTypes(TEST_FILE_DESCRIPTORS) + .build()); + } +} 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 9e3a79ed9..512b214fd 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -33,10 +33,13 @@ java_library( "//common/values", "//common/values:cel_byte_string", "//common/values:cel_value_provider", + "//common/values:proto_message_value", "//common/values:proto_message_value_provider", "//compiler", "//compiler:compiler_builder", "//extensions", + "//extensions:optional_library", + "//parser:macro", "//runtime", "//runtime:dispatcher", "//runtime:function_binding", @@ -47,6 +50,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 bb580cbb3..fac962093 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -25,7 +25,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; import com.google.common.primitives.UnsignedLong; import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; @@ -36,7 +35,6 @@ import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; import dev.cel.common.CelSource; -import dev.cel.common.Operator; import dev.cel.common.ast.CelExpr; import dev.cel.common.internal.CelDescriptorPool; import dev.cel.common.internal.DefaultDescriptorPool; @@ -51,20 +49,24 @@ import dev.cel.common.types.OptionalType; import dev.cel.common.types.ProtoMessageTypeProvider; import dev.cel.common.types.SimpleType; +import dev.cel.common.types.StructTypeReference; import dev.cel.common.types.TypeType; import dev.cel.common.values.CelByteString; +import dev.cel.common.values.CelValueConverter; import dev.cel.common.values.CelValueProvider; import dev.cel.common.values.NullValue; +import dev.cel.common.values.ProtoCelValueConverter; import dev.cel.common.values.ProtoMessageValueProvider; import dev.cel.compiler.CelCompiler; import dev.cel.compiler.CelCompilerFactory; import dev.cel.expr.conformance.proto3.GlobalEnum; import dev.cel.expr.conformance.proto3.TestAllTypes; +import dev.cel.expr.conformance.proto3.TestAllTypes.NestedMessage; import dev.cel.extensions.CelExtensions; +import dev.cel.extensions.CelOptionalLibrary; +import dev.cel.parser.CelStandardMacro; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelFunctionBinding; -import dev.cel.runtime.CelFunctionOverload; -import dev.cel.runtime.CelResolvedOverload; import dev.cel.runtime.DefaultDispatcher; import dev.cel.runtime.Program; import dev.cel.runtime.RuntimeEquality; @@ -72,6 +74,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; @@ -84,7 +87,7 @@ @RunWith(TestParameterInjector.class) public final class ProgramPlannerTest { - // Note that the following deps will be built from top-level builder APIs + // Note that the following deps are ordinarily built from top-level builder APIs private static final CelOptions CEL_OPTIONS = CelOptions.current().build(); private static final CelTypeProvider TYPE_PROVIDER = new CombinedCelTypeProvider( @@ -99,17 +102,34 @@ public final class ProgramPlannerTest { private static final DynamicProto DYNAMIC_PROTO = DynamicProto.create(DefaultMessageFactory.create(DESCRIPTOR_POOL)); private static final CelValueProvider VALUE_PROVIDER = - ProtoMessageValueProvider.newInstance(CelOptions.DEFAULT, DYNAMIC_PROTO); + ProtoMessageValueProvider.newInstance(CEL_OPTIONS, DYNAMIC_PROTO); + private static final CelValueConverter CEL_VALUE_CONVERTER = + ProtoCelValueConverter.newInstance(DESCRIPTOR_POOL, DYNAMIC_PROTO); private static final CelContainer CEL_CONTAINER = - CelContainer.newBuilder().setName("cel.expr.conformance.proto3").build(); + CelContainer.newBuilder() + .setName("cel.expr.conformance.proto3") + .addAbbreviations("really.long.abbr") + .build(); private static final ProgramPlanner PLANNER = - ProgramPlanner.newPlanner(TYPE_PROVIDER, VALUE_PROVIDER, newDispatcher(), CEL_CONTAINER); + ProgramPlanner.newPlanner( + 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) .addVar("dyn_var", SimpleType.DYN) + .addVar("really.long.abbr.ident", SimpleType.DYN) + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .setContainer(CEL_CONTAINER) .addFunctionDeclarations( newFunctionDeclaration("zero", newGlobalOverload("zero_overload", SimpleType.INT)), newFunctionDeclaration("error", newGlobalOverload("error_overload", SimpleType.INT)), @@ -127,9 +147,8 @@ public final class ProgramPlannerTest { "concat_bytes_bytes", SimpleType.BYTES, SimpleType.BYTES, SimpleType.BYTES), newMemberOverload( "bytes_concat_bytes", SimpleType.BYTES, SimpleType.BYTES, SimpleType.BYTES))) + .addLibraries(CelOptionalLibrary.INSTANCE, CelExtensions.comprehensions()) .addMessageTypes(TestAllTypes.getDescriptor()) - .addLibraries(CelExtensions.optional()) - .setContainer(CEL_CONTAINER) .build(); /** @@ -140,135 +159,95 @@ private static DefaultDispatcher newDispatcher() { DefaultDispatcher.Builder builder = DefaultDispatcher.newBuilder(); // Subsetted StdLib - addBindings( - builder, Operator.INDEX.getFunction(), fromStandardFunction(IndexOperator.create())); - addBindings( - builder, - Operator.LOGICAL_NOT.getFunction(), - fromStandardFunction(LogicalNotOperator.create())); - addBindings(builder, Operator.ADD.getFunction(), fromStandardFunction(AddOperator.create())); - addBindings( - builder, Operator.GREATER.getFunction(), fromStandardFunction(GreaterOperator.create())); - addBindings( - builder, - Operator.GREATER_EQUALS.getFunction(), - fromStandardFunction(GreaterEqualsOperator.create())); - addBindings(builder, Operator.LESS.getFunction(), fromStandardFunction(LessOperator.create())); - addBindings( - builder, Operator.DIVIDE.getFunction(), fromStandardFunction(DivideOperator.create())); - addBindings( - builder, Operator.EQUALS.getFunction(), fromStandardFunction(EqualsOperator.create())); - addBindings( - builder, - Operator.NOT_STRICTLY_FALSE.getFunction(), - fromStandardFunction(NotStrictlyFalseFunction.create())); + addStandardFuncBindings(builder, IndexOperator.create()); + addStandardFuncBindings(builder, LogicalNotOperator.create()); + addStandardFuncBindings(builder, AddOperator.create()); + addStandardFuncBindings(builder, GreaterOperator.create()); + addStandardFuncBindings(builder, GreaterEqualsOperator.create()); + addStandardFuncBindings(builder, LessOperator.create()); + addStandardFuncBindings(builder, DivideOperator.create()); + addStandardFuncBindings(builder, EqualsOperator.create()); + addStandardFuncBindings(builder, NotStrictlyFalseFunction.create()); + addStandardFuncBindings(builder, DynFunction.create()); // Custom functions - addBindings( + addCustomBindings( builder, - "zero", - CelFunctionBinding.from("zero_overload", ImmutableList.of(), (unused) -> 0L)); - addBindings( + CelFunctionBinding.groupOverloads( + "zero", CelFunctionBinding.from("zero_overload", ImmutableList.of(), (unused) -> 0L))); + + addCustomBindings( builder, - "error", - CelFunctionBinding.from( - "error_overload", - ImmutableList.of(), - (unused) -> { - throw new IllegalArgumentException("Intentional error"); - })); - addBindings( + CelFunctionBinding.groupOverloads( + "error", + CelFunctionBinding.from( + "error_overload", + ImmutableList.of(), + (unused) -> { + throw new IllegalArgumentException("Intentional error"); + }))); + + addCustomBindings( builder, - "neg", - CelFunctionBinding.from("neg_int", Long.class, arg -> -arg), - CelFunctionBinding.from("neg_double", Double.class, arg -> -arg)); - addBindings( + CelFunctionBinding.groupOverloads( + "neg", + CelFunctionBinding.from("neg_int", Long.class, arg -> -arg), + CelFunctionBinding.from("neg_double", Double.class, arg -> -arg))); + + addCustomBindings( builder, - "cel.expr.conformance.proto3.power", - CelFunctionBinding.from( - "power_int_int", - Long.class, - Long.class, - (value, power) -> (long) Math.pow(value, power))); - addBindings( + CelFunctionBinding.groupOverloads( + "cel.expr.conformance.proto3.power", + CelFunctionBinding.from( + "power_int_int", + Long.class, + Long.class, + (value, power) -> (long) Math.pow(value, power)))); + + addCustomBindings( builder, - "concat", - CelFunctionBinding.from( - "concat_bytes_bytes", - CelByteString.class, - CelByteString.class, - ProgramPlannerTest::concatenateByteArrays), - CelFunctionBinding.from( - "bytes_concat_bytes", - CelByteString.class, - CelByteString.class, - ProgramPlannerTest::concatenateByteArrays)); + CelFunctionBinding.groupOverloads( + "concat", + CelFunctionBinding.from( + "concat_bytes_bytes", + CelByteString.class, + CelByteString.class, + ProgramPlannerTest::concatenateByteArrays), + CelFunctionBinding.from( + "bytes_concat_bytes", + CelByteString.class, + CelByteString.class, + ProgramPlannerTest::concatenateByteArrays))); return builder.build(); } - private static void addBindings( - DefaultDispatcher.Builder builder, - String functionName, - CelFunctionBinding... functionBindings) { - addBindings(builder, functionName, ImmutableSet.copyOf(functionBindings)); + private static void addStandardFuncBindings( + DefaultDispatcher.Builder builder, CelStandardFunction standardFunction) { + standardFunction + .newFunctionBindings(CEL_OPTIONS, RUNTIME_EQUALITY) + .forEach( + binding -> + builder.addOverload( + binding.getOverloadId(), + binding.getArgTypes(), + binding.isStrict(), + binding.getDefinition())); } - private static void addBindings( - DefaultDispatcher.Builder builder, - String functionName, - ImmutableCollection overloadBindings) { + private static void addCustomBindings( + DefaultDispatcher.Builder builder, ImmutableCollection overloadBindings) { if (overloadBindings.isEmpty()) { throw new IllegalArgumentException("Invalid bindings"); } - // TODO: Runtime top-level APIs currently does not allow grouping overloads with - // the function name. This capability will have to be added. - if (overloadBindings.size() == 1) { - CelFunctionBinding singleBinding = Iterables.getOnlyElement(overloadBindings); - builder.addOverload( - functionName, - singleBinding.getArgTypes(), - singleBinding.isStrict(), - args -> guardedOp(functionName, args, singleBinding)); - } else { - overloadBindings.forEach( - overload -> - builder.addOverload( - overload.getOverloadId(), - overload.getArgTypes(), - overload.isStrict(), - args -> guardedOp(functionName, args, overload))); - - // Setup dynamic dispatch - CelFunctionOverload dynamicDispatchDef = - args -> { - for (CelFunctionBinding overload : overloadBindings) { - if (CelResolvedOverload.canHandle( - args, overload.getArgTypes(), overload.isStrict())) { - return overload.getDefinition().apply(args); - } - } - - throw new IllegalArgumentException( - "No matching overload for function: " + functionName); - }; - - boolean allOverloadsStrict = overloadBindings.stream().allMatch(CelFunctionBinding::isStrict); - builder.addOverload( - functionName, ImmutableList.of(), /* isStrict= */ allOverloadsStrict, dynamicDispatchDef); - } - } - /** Creates an invocation guard around the overload definition. */ - private static Object guardedOp( - String functionName, Object[] args, CelFunctionBinding singleBinding) - throws CelEvaluationException { - if (!CelResolvedOverload.canHandle( - args, singleBinding.getArgTypes(), singleBinding.isStrict())) { - throw new IllegalArgumentException("No matching overload for function: " + functionName); - } - - return singleBinding.getDefinition().apply(args); + overloadBindings.forEach( + overload -> + builder.addOverload( + overload.getOverloadId(), + overload.getArgTypes(), + overload.isStrict(), + overload.getDefinition())); } @TestParameter boolean isParseOnly; @@ -296,10 +275,6 @@ public void plan_constant(@TestParameter ConstantTestCase testCase) throws Excep @Test public void plan_ident_enum() throws Exception { - if (isParseOnly) { - // TODO Skip for now, requires attribute qualification - return; - } CelAbstractSyntaxTree ast = compile(GlobalEnum.getDescriptor().getFullName() + "." + GlobalEnum.GAR); Program program = PLANNER.plan(ast); @@ -321,14 +296,6 @@ public void plan_ident_variable() throws Exception { @Test public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) throws Exception { - if (isParseOnly) { - if (testCase.equals(TypeLiteralTestCase.DURATION) - || testCase.equals(TypeLiteralTestCase.TIMESTAMP) - || testCase.equals(TypeLiteralTestCase.PROTO_MESSAGE_TYPE)) { - // TODO Skip for now, requires attribute qualification - return; - } - } CelAbstractSyntaxTree ast = compile(testCase.expression); Program program = PLANNER.plan(ast); @@ -337,6 +304,16 @@ public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) t assertThat(result).isEqualTo(testCase.type); } + @Test + public void plan_ident_withContainer() throws Exception { + CelAbstractSyntaxTree ast = compile("abbr.ident"); + Program program = PLANNER.plan(ast); + + Object result = program.eval(ImmutableMap.of("really.long.abbr.ident", 1L)); + + assertThat(result).isEqualTo(1); + } + @Test @SuppressWarnings("unchecked") // test only public void plan_createList() throws Exception { @@ -597,6 +574,239 @@ public void plan_call_withContainer(String expression) throws Exception { assertThat(result).isEqualTo(8); } + @Test + public void plan_select_protoMessageField() throws Exception { + CelAbstractSyntaxTree ast = compile("msg.single_string"); + Program program = PLANNER.plan(ast); + + String result = + (String) + program.eval( + ImmutableMap.of("msg", TestAllTypes.newBuilder().setSingleString("foo").build())); + + assertThat(result).isEqualTo("foo"); + } + + @Test + public void plan_select_nestedProtoMessage() throws Exception { + CelAbstractSyntaxTree ast = compile("msg.single_nested_message"); + NestedMessage nestedMessage = NestedMessage.newBuilder().setBb(42).build(); + Program program = PLANNER.plan(ast); + + Object result = + program.eval( + ImmutableMap.of( + "msg", TestAllTypes.newBuilder().setSingleNestedMessage(nestedMessage).build())); + + assertThat(result).isEqualTo(nestedMessage); + } + + @Test + public void plan_select_nestedProtoMessageField() throws Exception { + CelAbstractSyntaxTree ast = compile("msg.single_nested_message.bb"); + Program program = PLANNER.plan(ast); + + Object result = + program.eval( + ImmutableMap.of( + "msg", + TestAllTypes.newBuilder() + .setSingleNestedMessage(NestedMessage.newBuilder().setBb(42)) + .build())); + + assertThat(result).isEqualTo(42); + } + + @Test + 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.getDefaultInstance())); + + assertThat(result).isEqualTo(0L); + } + + @Test + public void plan_select_onCreateStruct() throws Exception { + CelAbstractSyntaxTree ast = + compile("cel.expr.conformance.proto3.TestAllTypes{ single_string: 'foo'}.single_string"); + Program program = PLANNER.plan(ast); + + Object result = program.eval(); + + assertThat(result).isEqualTo("foo"); + } + + @Test + public void plan_select_onCreateMap() throws Exception { + CelAbstractSyntaxTree ast = compile("{'foo':'bar'}.foo"); + Program program = PLANNER.plan(ast); + + Object result = program.eval(); + + assertThat(result).isEqualTo("bar"); + } + + @Test + public void plan_select_onMapVariable() throws Exception { + CelAbstractSyntaxTree ast = compile("map_var.foo"); + Program program = PLANNER.plan(ast); + + Object result = program.eval(ImmutableMap.of("map_var", ImmutableMap.of("foo", 42L))); + + assertThat(result).isEqualTo(42L); + } + + @Test + public void plan_select_mapVarInputMissing_throws() throws Exception { + CelAbstractSyntaxTree ast = compile("map_var.foo"); + Program program = PLANNER.plan(ast); + String errorMessage = "evaluation error at :7: Error resolving "; + if (isParseOnly) { + errorMessage += + "fields 'cel.expr.conformance.proto3.map_var, cel.expr.conformance.map_var," + + " cel.expr.map_var, cel.map_var, map_var'"; + } else { + errorMessage += "field 'map_var'"; + } + + CelEvaluationException e = + assertThrows(CelEvaluationException.class, () -> program.eval(ImmutableMap.of())); + + assertThat(e).hasMessageThat().contains(errorMessage); + } + + @Test + public void plan_select_mapVarKeyMissing_throws() throws Exception { + CelAbstractSyntaxTree ast = compile("map_var.foo"); + Program program = PLANNER.plan(ast); + + CelEvaluationException e = + assertThrows( + CelEvaluationException.class, + () -> program.eval(ImmutableMap.of("map_var", ImmutableMap.of()))); + assertThat(e) + .hasMessageThat() + .contains("evaluation error at :7: key 'foo' is not present in map"); + } + + @Test + public void plan_select_stringQualificationFail_throws() throws Exception { + CelAbstractSyntaxTree ast = compile("map_var.foo"); + Program program = PLANNER.plan(ast); + + CelEvaluationException e = + assertThrows( + CelEvaluationException.class, + () -> program.eval(ImmutableMap.of("map_var", "bogus string"))); + + assertThat(e) + .hasMessageThat() + .isEqualTo( + "evaluation error at :7: Error resolving field 'foo'. Field selections must be" + + " 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) { @@ -618,11 +828,6 @@ private static CelByteString concatenateByteArrays(CelByteString bytes1, CelByte return bytes1.concat(bytes2); } - private static ImmutableSet fromStandardFunction( - CelStandardFunction standardFunction) { - return standardFunction.newFunctionBindings(CEL_OPTIONS, RUNTIME_EQUALITY); - } - @SuppressWarnings("ImmutableEnumChecker") // Test only private enum ConstantTestCase { NULL("null", NullValue.NULL_VALUE), @@ -669,4 +874,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; + } + } }