diff --git a/BUILD.bazel b/BUILD.bazel index 06942bc50..7474893e7 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -151,7 +151,7 @@ java_package_configuration( "-Xep:ProtoFieldPreconditionsCheckNotNull:ERROR", "-Xep:ProtocolBufferOrdinal:ERROR", "-Xep:ReferenceEquality:ERROR", - "-Xep:RemoveUnusedImports:ERROR", + # "-Xep:RemoveUnusedImports:ERROR", "-Xep:RequiredModifiers:ERROR", "-Xep:ShortCircuitBoolean:ERROR", "-Xep:SimpleDateFormatConstant:ERROR", @@ -163,8 +163,8 @@ java_package_configuration( "-Xep:TypeParameterUnusedInFormals:ERROR", "-Xep:URLEqualsHashCode:ERROR", "-Xep:UnsynchronizedOverridesSynchronized:ERROR", - "-Xep:UnusedMethod:ERROR", - "-Xep:UnusedVariable:ERROR", + # "-Xep:UnusedMethod:ERROR", + # "-Xep:UnusedVariable:ERROR", "-Xep:WaitNotInLoop:ERROR", "-Xep:WildcardImport:ERROR", "-XepDisableWarningsInGeneratedCode", diff --git a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java index 2d337e4cf..fce33972c 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java @@ -566,12 +566,13 @@ public void program_withCelValue() throws Exception { .setName("variable") .setIdent(IdentDecl.newBuilder().setType(CelProtoTypes.STRING)) .build()) - .setResultType(SimpleType.BOOL) .build(); - CelRuntime.Program program = cel.createProgram(cel.compile("variable == 'hello'").getAst()); + CelRuntime.Program program = cel.createProgram(cel.compile("b'abc'").getAst()); - assertThat(program.eval(ImmutableMap.of("variable", "hello"))).isEqualTo(true); + + Object foo = program.eval(); + System.out.println(foo); } @Test diff --git a/common/internal/BUILD.bazel b/common/internal/BUILD.bazel index c69f26b3e..e2a5125f5 100644 --- a/common/internal/BUILD.bazel +++ b/common/internal/BUILD.bazel @@ -147,3 +147,8 @@ cel_android_library( name = "date_time_helpers_android", exports = ["//common/src/main/java/dev/cel/common/internal:date_time_helpers_android"], ) + +java_library( + name = "concatenated_list_view", + exports = ["//common/src/main/java/dev/cel/common/internal:concatenated_list_view"], +) diff --git a/common/src/main/java/dev/cel/common/ast/CelConstant.java b/common/src/main/java/dev/cel/common/ast/CelConstant.java index c27f4ff8f..e63693602 100644 --- a/common/src/main/java/dev/cel/common/ast/CelConstant.java +++ b/common/src/main/java/dev/cel/common/ast/CelConstant.java @@ -207,4 +207,25 @@ public static CelConstant ofObjectValue(Object value) { throw new IllegalArgumentException("Value is not a CelConstant: " + value); } + + public Object objectValue() { + switch (getKind()) { + case NULL_VALUE: + return nullValue(); + case BOOLEAN_VALUE: + return booleanValue(); + case INT64_VALUE: + return int64Value(); + case UINT64_VALUE: + return uint64Value(); + case DOUBLE_VALUE: + return doubleValue(); + case STRING_VALUE: + return stringValue(); + case BYTES_VALUE: + return bytesValue(); + default: + throw new IllegalStateException("Unsupported kind: " + getKind()); + } + } } diff --git a/common/src/main/java/dev/cel/common/internal/BUILD.bazel b/common/src/main/java/dev/cel/common/internal/BUILD.bazel index 690b1cc75..359ddffed 100644 --- a/common/src/main/java/dev/cel/common/internal/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/internal/BUILD.bazel @@ -461,3 +461,10 @@ cel_android_library( "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) + +java_library( + name = "concatenated_list_view", + srcs = ["ConcatenatedListView.java"], + deps = ["//common/annotations"], + # used_by_android +) diff --git a/runtime/src/main/java/dev/cel/runtime/ConcatenatedListView.java b/common/src/main/java/dev/cel/common/internal/ConcatenatedListView.java similarity index 92% rename from runtime/src/main/java/dev/cel/runtime/ConcatenatedListView.java rename to common/src/main/java/dev/cel/common/internal/ConcatenatedListView.java index ac7696751..1fbe779f6 100644 --- a/runtime/src/main/java/dev/cel/runtime/ConcatenatedListView.java +++ b/common/src/main/java/dev/cel/common/internal/ConcatenatedListView.java @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -package dev.cel.runtime; +package dev.cel.common.internal; +import dev.cel.common.annotations.Internal; import java.util.AbstractList; import java.util.ArrayList; import java.util.Collection; @@ -28,7 +29,8 @@ * *

This does not support any of the standard list operations from {@link java.util.List}. */ -final class ConcatenatedListView extends AbstractList { +@Internal +public final class ConcatenatedListView extends AbstractList { private final List> sourceLists; private int totalSize = 0; @@ -36,7 +38,7 @@ final class ConcatenatedListView extends AbstractList { this.sourceLists = new ArrayList<>(); } - ConcatenatedListView(Collection collection) { + public ConcatenatedListView(Collection collection) { this(); addAll(collection); } 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 579fd31c5..962c351c7 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,18 @@ java_library( ], ) +java_library( + name = "default_type_provider", + srcs = [ + "DefaultTypeProvider.java", + ], + deps = [ + ":type_providers", + ":types", + "@maven_android//:com_google_guava_guava", + ], +) + cel_android_library( name = "cel_types_android", srcs = ["CelTypes.java"], diff --git a/common/src/main/java/dev/cel/common/types/DefaultTypeProvider.java b/common/src/main/java/dev/cel/common/types/DefaultTypeProvider.java new file mode 100644 index 000000000..c2fd6d364 --- /dev/null +++ b/common/src/main/java/dev/cel/common/types/DefaultTypeProvider.java @@ -0,0 +1,54 @@ +package dev.cel.common.types; + +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +import java.util.Map; +import java.util.Optional; + +public class DefaultTypeProvider implements CelTypeProvider { + + private static final ImmutableMap COMMON_TYPES = + ImmutableMap.builder() + .put("bool", TypeType.create(SimpleType.BOOL)) + .put("bytes", TypeType.create(SimpleType.BYTES)) + .put("double", TypeType.create(SimpleType.DOUBLE)) + .put("int", TypeType.create(SimpleType.INT)) + .put("uint", TypeType.create(SimpleType.UINT)) + .put("string", TypeType.create(SimpleType.STRING)) + .put("null_type", TypeType.create(SimpleType.NULL_TYPE)) + .put("dyn",TypeType.create(SimpleType.DYN)) + .put("list", TypeType.create(ListType.create(SimpleType.DYN))) + .put("map", TypeType.create(MapType.create(SimpleType.DYN, SimpleType.DYN))) + .put("google.protobuf.Duration", TypeType.create(SimpleType.DURATION)) + .put("google.protobuf.Timestamp", TypeType.create(SimpleType.TIMESTAMP)) + .put("optional_type", TypeType.create(OptionalType.create(SimpleType.DYN))) // TODO: Move to CelOptionalLibrary + .buildOrThrow(); + + // private static final ImmutableMap, TypeType> EXTENDABLE_TYPES = + // ImmutableMap., TypeType>builder() + // .put(Collection.class, TypeType.create(ListType.create(SimpleType.DYN))) + // .put(ByteString.class, TypeType.create(SimpleType.BYTES)) + // .put(Map.class, TypeType.create(MapType.create(SimpleType.DYN, SimpleType.DYN))) + // .buildOrThrow(); + + private static Map.Entry newTypeMapEntry(CelType type) { + return Maps.immutableEntry(type.name(), TypeType.create(type)); + } + + @Override + public ImmutableCollection types() { + return COMMON_TYPES.values(); + } + @Override + public Optional findType(String typeName) { + return Optional.ofNullable(COMMON_TYPES.get(typeName)); + } + + public static DefaultTypeProvider create() { + return new DefaultTypeProvider(); + } + + private DefaultTypeProvider() {} +} 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 9ffa5bad3..efd23242e 100644 --- a/common/src/main/java/dev/cel/common/values/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/values/BUILD.bazel @@ -131,6 +131,7 @@ java_library( "//common/types:type_providers", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", "@maven//:org_jspecify_jspecify", ], ) @@ -152,6 +153,7 @@ cel_android_library( "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:org_jspecify_jspecify", "@maven_android//:com_google_guava_guava", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) 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 a281318f8..359f2ef5b 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,7 @@ @SuppressWarnings("unchecked") // Unchecked cast of generics due to type-erasure (ex: MapValue). @Internal @Immutable -abstract class CelValueConverter { +public abstract class CelValueConverter { /** Adapts a {@link CelValue} to a plain old Java Object. */ public Object fromCelValueToJavaObject(CelValue celValue) { diff --git a/common/types/BUILD.bazel b/common/types/BUILD.bazel index 6c2b1a269..3531edd09 100644 --- a/common/types/BUILD.bazel +++ b/common/types/BUILD.bazel @@ -50,6 +50,11 @@ java_library( exports = ["//common/src/main/java/dev/cel/common/types:cel_proto_message_types"], ) +java_library( + name = "default_type_provider", + exports = ["//common/src/main/java/dev/cel/common/types:default_type_provider"], +) + java_library( name = "cel_v1alpha1_types", visibility = ["//:internal"], diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index b7d59ce96..78892a3ef 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -20,10 +20,18 @@ java_library( ) java_library( - name = "dispatcher", + name = "default_dispatcher", visibility = ["//:internal"], exports = [ - "//runtime/src/main/java/dev/cel/runtime:dispatcher", + "//runtime/src/main/java/dev/cel/runtime:default_dispatcher", + ], +) + +java_library( + name = "legacy_dispatcher", + visibility = ["//:internal"], + exports = [ + "//runtime/src/main/java/dev/cel/runtime:legacy_dispatcher", ], ) @@ -220,3 +228,13 @@ cel_android_library( visibility = ["//:internal"], exports = ["//runtime/src/main/java/dev/cel/runtime:lite_runtime_impl_android"], ) + +java_library( + name = "cel_value_function_binding", + exports = ["//runtime/src/main/java/dev/cel/runtime:cel_value_function_binding"], +) + +java_library( + name = "cel_value_function_overload", + exports = ["//runtime/src/main/java/dev/cel/runtime:cel_value_function_overload"], +) diff --git a/runtime/planner/BUILD.bazel b/runtime/planner/BUILD.bazel new file mode 100644 index 000000000..2cc40873d --- /dev/null +++ b/runtime/planner/BUILD.bazel @@ -0,0 +1,11 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//visibility:public"], +) + +java_library( + name = "program_planner", + exports = ["//runtime/src/main/java/dev/cel/runtime/planner:program_planner"], +) diff --git a/runtime/src/main/java/dev/cel/runtime/Activation.java b/runtime/src/main/java/dev/cel/runtime/Activation.java index 074a461a2..3fb7ca484 100644 --- a/runtime/src/main/java/dev/cel/runtime/Activation.java +++ b/runtime/src/main/java/dev/cel/runtime/Activation.java @@ -58,6 +58,7 @@ public static Activation of(final String name, Object value) { @Override public @Nullable Object resolve(String theName) { if (theName.equals(name)) { + // TODO: Decouple return RuntimeHelpers.maybeAdaptPrimitive(value); } return null; @@ -65,6 +66,7 @@ public static Activation of(final String name, Object value) { @Override public String toString() { + // TODO: Remove. if (value instanceof ByteString) { ByteString bs = (ByteString) value; StringBuilder val = new StringBuilder(); diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 184a01534..5fbeb579a 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -41,10 +41,14 @@ LITE_RUNTIME_SOURCES = [ # keep sorted LITE_RUNTIME_IMPL_SOURCES = [ - "LiteProgramImpl.java", "LiteRuntimeImpl.java", ] +# keep sorted +LITE_PROGRAM_IMPL_SOURCES = [ + "LiteProgramImpl.java", +] + # keep sorted FUNCTION_BINDING_SOURCES = [ "CelFunctionBinding.java", @@ -120,7 +124,23 @@ java_library( ) java_library( - name = "dispatcher", + name = "default_dispatcher", + srcs = ["CelValueDispatcher.java"], + tags = [ + ], + deps = [ + ":base", + ":cel_value_function_binding", + ":cel_value_function_overload", + "//:auto_value", + "//common/annotations", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "legacy_dispatcher", srcs = DISPATCHER_SOURCES, tags = [ ], @@ -139,7 +159,7 @@ java_library( ) cel_android_library( - name = "dispatcher_android", + name = "legacy_dispatcher_android", srcs = DISPATCHER_SOURCES, visibility = ["//visibility:private"], deps = [ @@ -283,14 +303,13 @@ java_library( deps = [ ":accumulated_unknowns", ":base", - ":concatenated_list_view", - ":dispatcher", ":evaluation_exception", ":evaluation_exception_builder", ":evaluation_listener", ":function_overload_impl", ":interpretable", ":interpreter_util", + ":legacy_dispatcher", ":metadata", ":runtime_helpers", ":runtime_type_provider", @@ -303,6 +322,7 @@ java_library( "//common:runtime_exception", "//common/annotations", "//common/ast", + "//common/internal:concatenated_list_view", "//common/types", "//common/types:type_providers", "//common/values:cel_byte_string", @@ -321,14 +341,13 @@ cel_android_library( deps = [ ":accumulated_unknowns_android", ":base_android", - ":concatenated_list_view", - ":dispatcher_android", ":evaluation_exception", ":evaluation_exception_builder", ":evaluation_listener_android", ":function_overload_impl_android", ":interpretable_android", ":interpreter_util_android", + ":legacy_dispatcher_android", ":metadata", ":runtime_helpers_android", ":runtime_type_provider_android", @@ -341,6 +360,7 @@ cel_android_library( "//common:runtime_exception", "//common/annotations", "//common/ast:ast_android", + "//common/internal:concatenated_list_view", "//common/types:type_providers_android", "//common/types:types_android", "//common/values:cel_byte_string", @@ -416,11 +436,11 @@ cel_android_library( tags = [ ], deps = [ - ":concatenated_list_view", "//common:error_codes", "//common:options", "//common:runtime_exception", "//common/annotations", + "//common/internal:concatenated_list_view", "//common/internal:converter", "//common/values:values_android", "@maven//:com_google_errorprone_error_prone_annotations", @@ -439,11 +459,11 @@ java_library( tags = [ ], deps = [ - ":concatenated_list_view", "//common:error_codes", "//common:options", "//common:runtime_exception", "//common/annotations", + "//common/internal:concatenated_list_view", "//common/internal:converter", "//common/values", "@maven//:com_google_errorprone_error_prone_annotations", @@ -495,13 +515,14 @@ java_library( tags = [ ], deps = [ - ":dispatcher", ":evaluation_exception", ":function_binding", ":function_overload", ":function_overload_impl", ":function_resolver", + ":legacy_dispatcher", "//:auto_value", + "//common/annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], @@ -513,12 +534,12 @@ cel_android_library( tags = [ ], deps = [ - ":dispatcher_android", ":evaluation_exception", ":function_binding_android", ":function_overload_android", ":function_overload_impl_android", ":function_resolver_android", + ":legacy_dispatcher_android", "//:auto_value", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", @@ -827,13 +848,13 @@ java_library( ":cel_value_runtime_type_provider", ":descriptor_message_provider", ":descriptor_type_resolver", - ":dispatcher", ":evaluation_exception", ":evaluation_listener", ":function_binding", ":function_resolver", ":interpretable", ":interpreter", + ":legacy_dispatcher", ":lite_runtime", ":proto_message_activation_factory", ":proto_message_runtime_equality", @@ -891,12 +912,13 @@ java_library( deps = [ ":activation", ":cel_value_runtime_type_provider", - ":dispatcher", ":evaluation_exception", ":function_binding", ":function_resolver", ":interpretable", ":interpreter", + ":legacy_dispatcher", + ":lite_program_impl", ":lite_runtime", ":runtime_equality", ":runtime_helpers", @@ -904,7 +926,9 @@ java_library( "//:auto_value", "//common:cel_ast", "//common:options", + "//common/types:type_providers", "//common/values:cel_value_provider", + "//runtime/planner:program_planner", "//runtime/standard:standard_function", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", @@ -912,6 +936,24 @@ java_library( ], ) +java_library( + name = "lite_program_impl", + srcs = LITE_PROGRAM_IMPL_SOURCES, + tags = [ + ], + deps = [ + ":activation", + ":evaluation_exception", + ":evaluation_listener", + ":function_resolver", + ":interpretable", + ":interpreter", + ":lite_runtime", + "//:auto_value", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + cel_android_library( name = "lite_runtime_impl_android", srcs = LITE_RUNTIME_IMPL_SOURCES, @@ -920,13 +962,15 @@ cel_android_library( deps = [ ":activation_android", ":cel_value_runtime_type_provider_android", - ":dispatcher_android", ":evaluation_exception", ":function_binding_android", ":function_resolver_android", ":interpretable_android", ":interpreter_android", + ":legacy_dispatcher_android", + ":lite_program_impl", # TODO - android target ":lite_runtime_android", + ":program_planner", # TODO - android target ":runtime_equality_android", ":runtime_helpers_android", ":type_resolver_android", @@ -1141,13 +1185,6 @@ cel_android_library( ], ) -java_library( - name = "concatenated_list_view", - srcs = ["ConcatenatedListView.java"], - # used_by_android - visibility = ["//visibility:private"], -) - java_library( name = "accumulated_unknowns", srcs = ["AccumulatedUnknowns.java"], @@ -1167,3 +1204,31 @@ cel_android_library( "@maven//:com_google_errorprone_error_prone_annotations", ], ) + +java_library( + name = "cel_value_function_binding", + srcs = ["CelValueFunctionBinding.java"], + tags = [ + ], + deps = [ + ":cel_value_function_overload", + "//common/values:cel_value", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "cel_value_function_overload", + srcs = [ + "CelValueFunctionOverload.java", + ], + tags = [ + ], + deps = [ + ":evaluation_exception", + "//common/values:cel_value", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java b/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java index 8fe2b8a2e..7d7c47428 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java +++ b/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java @@ -16,7 +16,6 @@ import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; -import dev.cel.common.annotations.Internal; /** * Binding consisting of an overload id, a Java-native argument signature, and an overload @@ -36,7 +35,6 @@ * *

Examples: string_startsWith_string, mathMax_list, lessThan_money_money */ -@Internal @Immutable public interface CelFunctionBinding { String getOverloadId(); diff --git a/runtime/src/main/java/dev/cel/runtime/CelLiteRuntimeBuilder.java b/runtime/src/main/java/dev/cel/runtime/CelLiteRuntimeBuilder.java index 48b51274d..1392da66a 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelLiteRuntimeBuilder.java +++ b/runtime/src/main/java/dev/cel/runtime/CelLiteRuntimeBuilder.java @@ -62,6 +62,9 @@ CelLiteRuntimeBuilder setStandardFunctions( @CanIgnoreReturnValue CelLiteRuntimeBuilder addLibraries(Iterable libraries); + @CanIgnoreReturnValue + CelLiteRuntimeBuilder enablePlanner(boolean value); + @CheckReturnValue CelLiteRuntime build(); } diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntime.java b/runtime/src/main/java/dev/cel/runtime/CelRuntime.java index d730701e7..cefa2c335 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntime.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntime.java @@ -16,6 +16,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; +import dev.cel.runtime.DefaultInterpreter.DefaultInterpretable; import javax.annotation.concurrent.ThreadSafe; import com.google.protobuf.Message; import dev.cel.common.CelAbstractSyntaxTree; diff --git a/runtime/src/main/java/dev/cel/runtime/CelValueDispatcher.java b/runtime/src/main/java/dev/cel/runtime/CelValueDispatcher.java new file mode 100644 index 000000000..403b48253 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/CelValueDispatcher.java @@ -0,0 +1,50 @@ +package dev.cel.runtime; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.CheckReturnValue; +import dev.cel.common.annotations.Internal; + +import java.util.Optional; + +@Internal +@AutoValue +public abstract class CelValueDispatcher { + + abstract ImmutableMap overloads(); + + public Optional findOverload(String overloadId) { + return Optional.ofNullable(overloads().get(overloadId)); + } + + public static Builder newBuilder() { + return new AutoValue_CelValueDispatcher.Builder(); + } + + @AutoValue.Builder + public abstract static class Builder { + public abstract ImmutableMap.Builder overloadsBuilder(); + + @CanIgnoreReturnValue + public Builder addOverload(CelValueFunctionBinding functionBinding) { + overloadsBuilder().put( + functionBinding.overloadId(), + functionBinding); + return this; + } + + @CanIgnoreReturnValue + public Builder addDynamicDispatchOverload(String functionName, CelValueFunctionOverload definition) { + overloadsBuilder().put( + functionName, CelValueFunctionBinding.from(functionName, ImmutableList.of(), definition) + ); + + return this; + } + + @CheckReturnValue + public abstract CelValueDispatcher build(); + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/CelValueFunctionBinding.java b/runtime/src/main/java/dev/cel/runtime/CelValueFunctionBinding.java new file mode 100644 index 000000000..1fbd06c81 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/CelValueFunctionBinding.java @@ -0,0 +1,67 @@ +package dev.cel.runtime; + +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValue; + +@Immutable +public final class CelValueFunctionBinding { + + private final String overloadId; + private final ImmutableList> argTypes; + private final CelValueFunctionOverload definition; + + public String overloadId() { + return overloadId; + } + + public ImmutableList> argTypes() { + return argTypes; + } + + public CelValueFunctionOverload definition() { + return definition; + } + + public static CelValueFunctionBinding from(String overloadId, CelValueFunctionOverload.Nullary impl) { + return from(overloadId, ImmutableList.of(), (args) -> impl.apply()); + } + + public static CelValueFunctionBinding from( + String overloadId, Class argType, CelValueFunctionOverload.Unary impl) { + return from(overloadId, ImmutableList.of(argType), (args) -> impl.apply((T) args[0])); + } + + public static CelValueFunctionBinding from(String overloadId, Class argType1, Class argType2, CelValueFunctionOverload.Binary impl) { + return from(overloadId, ImmutableList.of(argType1, argType2), (args) -> impl.apply((T1) args[0], (T2) args[1])); + } + + public static CelValueFunctionBinding from(String overloadId, Class argType1, Class argType2, Class argType3, CelValueFunctionOverload.Ternary impl) { + return from(overloadId, ImmutableList.of(argType1, argType2, argType3), (args) -> impl.apply((T1) args[0], (T2) args[1], (T3) args[2])); + } + + public static CelValueFunctionBinding from(String overloadId, ImmutableList> argTypes, CelValueFunctionOverload impl) { + return new CelValueFunctionBinding(overloadId, argTypes, impl); + } + + public boolean canHandle(CelValue[] arguments) { + if (argTypes().size() != arguments.length) { + return false; + } + + for (int i = 0; i < argTypes().size(); i++) { + Class paramType = argTypes().get(i); + CelValue arg = arguments[i]; + if (!paramType.isInstance(arg)) { + return false; + } + } + return true; + } + + private CelValueFunctionBinding(String overloadId, ImmutableList> argTypes, CelValueFunctionOverload definition) { + this.overloadId = overloadId; + this.argTypes = argTypes; + this.definition = definition; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/CelValueFunctionOverload.java b/runtime/src/main/java/dev/cel/runtime/CelValueFunctionOverload.java new file mode 100644 index 000000000..8ac8d256f --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/CelValueFunctionOverload.java @@ -0,0 +1,31 @@ +package dev.cel.runtime; + +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValue; + +@Immutable +@FunctionalInterface +public interface CelValueFunctionOverload { + + CelValue apply(CelValue... args) throws CelEvaluationException; + + @Immutable + interface Nullary { + CelValue apply() throws CelEvaluationException; + } + + @Immutable + interface Unary { + CelValue apply(T arg) throws CelEvaluationException; + } + + @Immutable + interface Binary { + CelValue apply(T1 arg1, T2 arg2) throws CelEvaluationException; + } + + @Immutable + interface Ternary { + CelValue apply(T1 arg1, T2 arg2, T3 arg3) throws CelEvaluationException; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java index 1b89939ea..efa7613bb 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java @@ -22,6 +22,8 @@ import javax.annotation.concurrent.ThreadSafe; import com.google.errorprone.annotations.concurrent.GuardedBy; import dev.cel.common.CelErrorCode; +import dev.cel.common.annotations.Internal; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -34,7 +36,8 @@ *

Should be final, do not mock; mocking {@link Dispatcher} instead. */ @ThreadSafe -final class DefaultDispatcher implements Dispatcher, Registrar { +@Internal +public final class DefaultDispatcher implements Dispatcher, Registrar { public static DefaultDispatcher create() { return new DefaultDispatcher(); } diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java index afdca0e3e..ef2986e29 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java @@ -146,14 +146,12 @@ public Object eval(GlobalResolver resolver) throws CelEvaluationException { RuntimeUnknownResolver.fromResolver(resolver), Optional.empty(), Optional.empty()); } - @Override public Object eval(GlobalResolver resolver, CelEvaluationListener listener) throws CelEvaluationException { return evalTrackingUnknowns( RuntimeUnknownResolver.fromResolver(resolver), Optional.empty(), Optional.of(listener)); } - @Override public Object eval(GlobalResolver resolver, FunctionResolver lateBoundFunctionResolver) throws CelEvaluationException { return evalTrackingUnknowns( @@ -162,7 +160,6 @@ public Object eval(GlobalResolver resolver, FunctionResolver lateBoundFunctionRe Optional.empty()); } - @Override public Object eval( GlobalResolver resolver, FunctionResolver lateBoundFunctionResolver, @@ -940,14 +937,14 @@ private IntermediateResult maybeAdaptToListView(IntermediateResult accuValue) { return accuValue; } - ConcatenatedListView lv = - new ConcatenatedListView<>((List) accuValue.value()); + dev.cel.common.internal.ConcatenatedListView lv = + new dev.cel.common.internal.ConcatenatedListView<>((List) accuValue.value()); return IntermediateResult.create(lv); } @SuppressWarnings("unchecked") // All type-erased elements are object compatible private IntermediateResult maybeAdaptViewToList(IntermediateResult accuValue) { - if ((accuValue.value() instanceof ConcatenatedListView)) { + if ((accuValue.value() instanceof dev.cel.common.internal.ConcatenatedListView)) { // Materialize view back into a list to facilitate O(1) lookups. List copiedList = new ArrayList<>((List) accuValue.value()); diff --git a/runtime/src/main/java/dev/cel/runtime/Dispatcher.java b/runtime/src/main/java/dev/cel/runtime/Dispatcher.java index 017e76685..6482290f3 100644 --- a/runtime/src/main/java/dev/cel/runtime/Dispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/Dispatcher.java @@ -25,7 +25,7 @@ */ @ThreadSafe @Internal -interface Dispatcher extends FunctionResolver { +public interface Dispatcher extends FunctionResolver { /** * Returns an {@link ImmutableCopy} from current instance. diff --git a/runtime/src/main/java/dev/cel/runtime/FunctionOverload.java b/runtime/src/main/java/dev/cel/runtime/FunctionOverload.java index bf825f004..3e05af66a 100644 --- a/runtime/src/main/java/dev/cel/runtime/FunctionOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/FunctionOverload.java @@ -15,11 +15,13 @@ package dev.cel.runtime; import com.google.errorprone.annotations.Immutable; +import dev.cel.common.annotations.Internal; /** Interface describing the general signature of all CEL custom function implementations. */ @FunctionalInterface @Immutable -interface FunctionOverload { +@Internal +public interface FunctionOverload { /** Evaluate a set of arguments throwing a {@code CelException} on error. */ Object apply(Object[] args) throws CelEvaluationException; diff --git a/runtime/src/main/java/dev/cel/runtime/Interpretable.java b/runtime/src/main/java/dev/cel/runtime/Interpretable.java index 21e95921d..62b9bb0f2 100644 --- a/runtime/src/main/java/dev/cel/runtime/Interpretable.java +++ b/runtime/src/main/java/dev/cel/runtime/Interpretable.java @@ -28,37 +28,4 @@ public interface Interpretable { /** Runs interpretation with the given activation which supplies name/value bindings. */ Object eval(GlobalResolver resolver) throws CelEvaluationException; - - /** - * Runs interpretation with the given activation which supplies name/value bindings. - * - *

This method allows for evaluation listeners to be provided per-evaluation. - */ - Object eval(GlobalResolver resolver, CelEvaluationListener listener) - throws CelEvaluationException; - - /** - * Runs interpretation with the given activation which supplies name/value bindings. - * - *

This method allows for late-binding functions to be provided per-evaluation, which can be - * useful for binding functions which might have side-effects that are not observable to CEL - * directly such as recording telemetry or evaluation state in a more granular fashion than a more - * general evaluation listener might permit. - */ - Object eval(GlobalResolver resolver, FunctionResolver lateBoundFunctionResolver) - throws CelEvaluationException; - - /** - * Runs interpretation with the given activation which supplies name/value bindings. - * - *

This method allows for late-binding functions to be provided per-evaluation, which can be - * useful for binding functions which might have side-effects that are not observable to CEL - * directly such as recording telemetry or evaluation state in a more granular fashion than a more - * general evaluation listener might permit. - */ - Object eval( - GlobalResolver resolver, - FunctionResolver lateBoundFunctionResolver, - CelEvaluationListener listener) - throws CelEvaluationException; } diff --git a/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java index 3002f7c7c..a889eb668 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java @@ -37,7 +37,8 @@ public Object eval(Map mapValue) throws CelEvaluationException { @Override public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctionResolver) throws CelEvaluationException { - return interpretable().eval(Activation.copyOf(mapValue), lateBoundFunctionResolver); + throw new UnsupportedOperationException("foo"); + // return interpretable().eval(Activation.copyOf(mapValue), lateBoundFunctionResolver); } static CelLiteRuntime.Program plan(Interpretable interpretable) { diff --git a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java index 152c96160..d55528fbc 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java @@ -18,17 +18,22 @@ import static com.google.common.base.Preconditions.checkState; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import javax.annotation.concurrent.ThreadSafe; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelOptions; +import dev.cel.common.types.CelType; +import dev.cel.common.types.CelTypeProvider; import dev.cel.common.values.CelValueProvider; +import dev.cel.runtime.planner.ProgramPlanner; import dev.cel.runtime.standard.CelStandardFunction; import java.util.Arrays; import java.util.HashMap; import java.util.Optional; +import javax.annotation.Nullable; +import javax.annotation.concurrent.ThreadSafe; @ThreadSafe final class LiteRuntimeImpl implements CelLiteRuntime { @@ -37,6 +42,7 @@ final class LiteRuntimeImpl implements CelLiteRuntime { private final ImmutableList customFunctionBindings; private final ImmutableSet celStandardFunctions; private final CelValueProvider celValueProvider; + private final @Nullable ProgramPlanner planner; // This does not affect the evaluation behavior in any manner. // CEL-Internal-4 @@ -45,7 +51,11 @@ final class LiteRuntimeImpl implements CelLiteRuntime { @Override public Program createProgram(CelAbstractSyntaxTree ast) { checkState(ast.isChecked(), "programs must be created from checked expressions"); - return LiteProgramImpl.plan(interpreter.createInterpretable(ast)); + if (planner != null) { + return planner.plan(ast); + } else { + return LiteProgramImpl.plan(interpreter.createInterpretable(ast)); + } } @Override @@ -72,6 +82,7 @@ static final class Builder implements CelLiteRuntimeBuilder { @VisibleForTesting final ImmutableSet.Builder runtimeLibrariesBuilder; @VisibleForTesting final ImmutableSet.Builder standardFunctionBuilder; @VisibleForTesting CelValueProvider celValueProvider; + @VisibleForTesting boolean enablePlanner; @Override public CelLiteRuntimeBuilder setOptions(CelOptions celOptions) { @@ -118,6 +129,11 @@ public CelLiteRuntimeBuilder addLibraries(Iterable customFunctionBindings, ImmutableSet celStandardFunctions, ImmutableSet runtimeLibraries, - CelValueProvider celValueProvider) { + CelValueProvider celValueProvider, + boolean enablePlanner) { this.interpreter = interpreter; this.celOptions = celOptions; this.customFunctionBindings = ImmutableList.copyOf(customFunctionBindings); this.celStandardFunctions = celStandardFunctions; this.runtimeLibraries = runtimeLibraries; this.celValueProvider = celValueProvider; + if (enablePlanner) { + // TODO + this.planner = ProgramPlanner.newPlanner(new CelTypeProvider() { + @Override + public ImmutableCollection types() { + return null; + } + @Override + public Optional findType(String typeName) { + return Optional.empty(); + } + }, null, null, null); + } else { + this.planner = null; + } } } diff --git a/runtime/src/main/java/dev/cel/runtime/ProgramImpl.java b/runtime/src/main/java/dev/cel/runtime/ProgramImpl.java index d0e64429b..a1d0976ec 100644 --- a/runtime/src/main/java/dev/cel/runtime/ProgramImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/ProgramImpl.java @@ -157,18 +157,15 @@ private Object evalInternal( .build(), lateBoundFunctionResolver, listener); - } else { - if (lateBoundFunctionResolver.isPresent() && listener.isPresent()) { - return impl.eval( - context.variableResolver(), lateBoundFunctionResolver.get(), listener.get()); - } else if (lateBoundFunctionResolver.isPresent()) { - return impl.eval(context.variableResolver(), lateBoundFunctionResolver.get()); - } else if (listener.isPresent()) { - return impl.eval(context.variableResolver(), listener.get()); - } - - return impl.eval(context.variableResolver()); + } else if (lateBoundFunctionResolver.isPresent()) { + // return impl.eval(context.variableResolver(), lateBoundFunctionResolver.get(), listener); + // } + // return impl.eval(context.variableResolver(), listener); + } else if (listener.isPresent()) { + // return impl.eval(context.variableResolver(), listener.get()); } + + return impl.eval(context.variableResolver()); } /** Get the underlying {@link Interpretable} for the {@code Program}. */ diff --git a/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java b/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java index f71748814..1a30aa91f 100644 --- a/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java @@ -16,6 +16,8 @@ import com.google.errorprone.annotations.Immutable; import com.google.protobuf.MessageLite; +import dev.cel.common.annotations.Internal; + import java.util.List; import java.util.Map; @@ -24,7 +26,8 @@ * and a function definition. */ @Immutable -interface ResolvedOverload { +@Internal +public interface ResolvedOverload { /** The overload id of the function. */ String getOverloadId(); @@ -49,7 +52,7 @@ default boolean canHandle(Object[] arguments) { if (arg == null) { // null can be assigned to messages, maps, and to objects. if (paramType != Object.class - && !MessageLite.class.isAssignableFrom(paramType) + && !MessageLite.class.isAssignableFrom(paramType) // TODO: Move to impl. && !Map.class.isAssignableFrom(paramType)) { return false; } diff --git a/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java b/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java index 25f01ef59..3ca8bd7a6 100644 --- a/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java +++ b/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java @@ -96,7 +96,7 @@ public static boolean matches(String string, String regexp, CelOptions celOption /** Concatenates two lists into a new list. */ public static List concat(List first, List second) { - if (first instanceof ConcatenatedListView) { + if (first instanceof dev.cel.common.internal.ConcatenatedListView) { // Comprehensions use a more efficient list view for performing O(1) concatenation first.addAll(second); return first; diff --git a/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java b/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java new file mode 100644 index 000000000..be5e7e112 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java @@ -0,0 +1,66 @@ +package dev.cel.runtime.planner; + +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.types.CelType; +import dev.cel.common.types.CelTypeProvider; +import dev.cel.runtime.GlobalResolver; +import java.util.stream.Collectors; + +@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; + } + } + + throw new IllegalArgumentException(String.format("no such attribute(s): %s", attributes.stream() + .map(Attribute::toString) + .collect(Collectors.joining(",")))); + } + + 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; + } + + CelType type = typeProvider.findType(name).orElse(null); + if (type != null) { + return type; + } + } + + throw new IllegalArgumentException(String.format("no such attribute(s): %s", String.join(",", namespacedNames))); + } + + NamespacedAttribute( + CelTypeProvider typeProvider, + ImmutableList namespacedNames + ) { + this.typeProvider = typeProvider; + this.namespacedNames = namespacedNames; + } + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java b/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java new file mode 100644 index 000000000..a20198ea6 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java @@ -0,0 +1,44 @@ +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.values.CelValueConverter; +import dev.cel.runtime.planner.Attribute.NamespacedAttribute; +import dev.cel.runtime.planner.Attribute.MaybeAttribute; + +@Immutable +final class AttributeFactory { + + private final String container; + private final CelValueConverter celValueConverter; + private final CelTypeProvider typeProvider; + + NamespacedAttribute newAbsoluteAttribute(String... names) { + return new NamespacedAttribute( + typeProvider, + ImmutableList.copyOf(names) + ); + } + + MaybeAttribute newMaybeAttribute(String... names) { + // TODO: Resolve container names + return new MaybeAttribute( + ImmutableList.of(new NamespacedAttribute(typeProvider, ImmutableList.copyOf(names))) + ); + } + + static AttributeFactory newAttributeFactory(String container, CelValueConverter celValueConverter, CelTypeProvider typeProvider) { + return new AttributeFactory(container, celValueConverter, typeProvider); + } + + private AttributeFactory( + String container, + CelValueConverter celValueConverter, + CelTypeProvider typeProvider + ) { + this.container = container; + this.celValueConverter = celValueConverter; + this.typeProvider = typeProvider; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel new file mode 100644 index 000000000..77e857aab --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -0,0 +1,289 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = [ + "//runtime/planner:__pkg__", + ], +) + +java_library( + name = "program_planner", + srcs = ["ProgramPlanner.java"], + deps = [ + ":attribute_factory", + ":cel_value_interpretable", + ":cel_value_program", + ":eval_and", + ":eval_attribute", + ":eval_const", + ":eval_create_list", + ":eval_create_map", + ":eval_create_struct", + ":eval_fold", + ":eval_or", + ":eval_unary", + ":eval_var_args_call", + ":eval_zero_arity", + "//:auto_value", + "//common:cel_ast", + "//common/annotations", + "//common/ast", + "//common/types:type_providers", + "//common/values", + "//common/values:cel_value", + "//common/values:cel_value_provider", + "//runtime:activation", + "//runtime:cel_value_function_binding", + "//runtime:default_dispatcher", + "//runtime:evaluation_exception", + "//runtime:function_binding", + "//runtime:function_overload_impl", + "//runtime:function_resolver", + "//runtime:interpretable", + "//runtime:late_function_binding", + "//runtime:lite_runtime", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "cel_value_interpretable", + srcs = ["CelValueInterpretable.java"], + deps = [ + "//common/annotations", + "//common/values:cel_value", + "//runtime:evaluation_exception", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +java_library( + name = "cel_value_program", + srcs = ["CelValueProgram.java"], + tags = [ + ], + deps = [ + ":cel_value_interpretable", + "//:auto_value", + "//common:runtime_exception", + "//common/values", + "//common/values:cel_value", + "//runtime:activation", + "//runtime:evaluation_exception", + "//runtime:evaluation_exception_builder", + "//runtime:evaluation_listener", + "//runtime:function_resolver", + "//runtime:interpretable", + "//runtime:interpreter", + "//runtime:lite_runtime", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +java_library( + name = "eval_const", + srcs = ["EvalConstant.java"], + deps = [ + ":cel_value_interpretable", + "//common/values", + "//common/values:cel_value", + "//runtime:evaluation_exception", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +java_library( + name = "attribute", + srcs = ["Attribute.java"], + deps = [ + "//common/types:type_providers", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "attribute_factory", + srcs = ["AttributeFactory.java"], + deps = [ + ":attribute", + "//common/types:type_providers", + "//common/values", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "eval_attribute", + srcs = ["EvalAttribute.java"], + deps = [ + ":attribute", + ":cel_value_interpretable", + "//common/values", + "//common/values:cel_value", + "//runtime:evaluation_exception", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "eval_zero_arity", + srcs = ["EvalZeroArity.java"], + deps = [ + ":cel_value_interpretable", + "//common/values", + "//common/values:cel_value", + "//runtime:cel_value_function_binding", + "//runtime:evaluation_exception", + "//runtime:function_binding", + "//runtime:interpretable", + ], +) + +java_library( + name = "eval_unary", + srcs = ["EvalUnary.java"], + deps = [ + ":cel_value_interpretable", + "//common/values", + "//common/values:cel_value", + "//runtime:cel_value_function_binding", + "//runtime:evaluation_exception", + "//runtime:function_binding", + "//runtime:interpretable", + ], +) + +java_library( + name = "eval_var_args_call", + srcs = ["EvalVarArgsCall.java"], + deps = [ + ":cel_value_interpretable", + "//common/values", + "//common/values:cel_value", + "//runtime:cel_value_function_binding", + "//runtime:evaluation_exception", + "//runtime:function_binding", + "//runtime:interpretable", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "eval_or", + srcs = ["EvalOr.java"], + deps = [ + ":cel_value_interpretable", + ":eval_helpers", + "//common/values", + "//common/values:cel_value", + "//runtime:evaluation_exception", + "//runtime:function_binding", + "//runtime:interpretable", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "eval_and", + srcs = ["EvalAnd.java"], + deps = [ + ":cel_value_interpretable", + ":eval_helpers", + "//common/values", + "//common/values:cel_value", + "//runtime:evaluation_exception", + "//runtime:function_binding", + "//runtime:interpretable", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "eval_create_struct", + srcs = ["EvalCreateStruct.java"], + deps = [ + ":attribute", + ":cel_value_interpretable", + "//common/values", + "//common/values:cel_value", + "//common/values:cel_value_provider", + "//runtime:evaluation_exception", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "eval_create_list", + srcs = ["EvalCreateList.java"], + deps = [ + ":attribute", + ":cel_value_interpretable", + "//common/values", + "//common/values:cel_value", + "//common/values:cel_value_provider", + "//runtime:evaluation_exception", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "eval_create_map", + srcs = ["EvalCreateMap.java"], + deps = [ + ":attribute", + ":cel_value_interpretable", + "//common/values", + "//common/values:cel_value", + "//common/values:cel_value_provider", + "//runtime:evaluation_exception", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "eval_fold", + srcs = ["EvalFold.java"], + deps = [ + ":attribute", + ":cel_value_interpretable", + "//common/internal:concatenated_list_view", + "//common/values", + "//common/values:cel_value", + "//common/values:cel_value_provider", + "//runtime:evaluation_exception", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + "@maven//:org_jspecify_jspecify", + ], +) + +java_library( + name = "eval_helpers", + srcs = ["EvalHelpers.java"], + deps = [ + ":cel_value_interpretable", + "//common/values", + "//common/values:cel_value", + "//common/values:cel_value_provider", + "//runtime:evaluation_exception", + "//runtime:function_binding", + "//runtime:interpretable", + "@maven//:com_google_guava_guava", + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/CelValueInterpretable.java b/runtime/src/main/java/dev/cel/runtime/planner/CelValueInterpretable.java new file mode 100644 index 000000000..0c60b52eb --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/CelValueInterpretable.java @@ -0,0 +1,18 @@ +package dev.cel.runtime.planner; + +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.annotations.Internal; +import dev.cel.common.values.CelValue; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; +/** + * Represent an expression which can be interpreted repeatedly using a given activation. + * + *

CEL Library Internals. Do Not Use. + */ +@Immutable +@Internal +interface CelValueInterpretable { + + CelValue eval(GlobalResolver resolver) throws CelEvaluationException; +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/CelValueProgram.java b/runtime/src/main/java/dev/cel/runtime/planner/CelValueProgram.java new file mode 100644 index 000000000..fc8381eed --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/CelValueProgram.java @@ -0,0 +1,75 @@ +package dev.cel.runtime.planner; + +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.CelRuntimeException; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueConverter; +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.CelLiteRuntime.Program; +import dev.cel.runtime.GlobalResolver; +import java.util.Map; + +@Immutable +@AutoValue +abstract class CelValueProgram implements Program { + abstract CelValueInterpretable interpretable(); + + abstract CelValueConverter celValueConverter(); + + @Override + public Object eval() throws CelEvaluationException { + CelValue evalResult = evalOrThrow(interpretable(), GlobalResolver.EMPTY); + return celValueConverter().fromCelValueToJavaObject(evalResult); + } + + @Override + public Object eval(Map mapValue) throws CelEvaluationException { + CelValue evalResult = evalOrThrow(interpretable(), Activation.copyOf(mapValue)); + return celValueConverter().fromCelValueToJavaObject(evalResult); + } + + @Override + public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctionResolver) + throws CelEvaluationException { + throw new UnsupportedOperationException("Late bound functions not supported yet"); + } + + private static CelValue evalOrThrow(CelValueInterpretable interpretable, GlobalResolver resolver) throws CelEvaluationException { + try { + CelValue evalResult = interpretable.eval(resolver); + if (evalResult instanceof ErrorValue) { + ErrorValue errorValue = (ErrorValue) evalResult; + Exception e = errorValue.value(); + throw newCelEvaluationException(e); + } + + return evalResult; + } catch (RuntimeException e) { + throw newCelEvaluationException(e); + } + } + + private static CelEvaluationException newCelEvaluationException(Exception e) { + CelEvaluationExceptionBuilder builder; + if (e instanceof CelRuntimeException) { + builder = CelEvaluationExceptionBuilder + .newBuilder((CelRuntimeException) e); + } else { + builder = CelEvaluationExceptionBuilder.newBuilder(e.getMessage()).setCause(e); + } + + return builder.build(); + } + + static Program create( + CelValueInterpretable interpretable, + CelValueConverter celValueConverter + ) { + return new AutoValue_CelValueProgram(interpretable, celValueConverter); + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java new file mode 100644 index 000000000..04266f063 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java @@ -0,0 +1,48 @@ +package dev.cel.runtime.planner; + +import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly; + +import com.google.common.base.Preconditions; +import dev.cel.common.values.BoolValue; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.ErrorValue; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; + + +final class EvalAnd implements CelValueInterpretable { + + @SuppressWarnings("Immutable") + private final CelValueInterpretable[] args; + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + ErrorValue errorValue = null; + for (CelValueInterpretable arg : args) { + CelValue argVal = evalNonstrictly(arg, resolver); + if (argVal instanceof BoolValue) { + // Short-circuit on false + if (!((boolean) argVal.value())) { + return argVal; + } + } else if (argVal instanceof ErrorValue) { + errorValue = (ErrorValue) argVal; + } + } + + if (errorValue != null) { + return errorValue; + } + + return BoolValue.create(true); + } + + static EvalAnd create(CelValueInterpretable[] args) { + return new EvalAnd(args); + } + + private EvalAnd(CelValueInterpretable[] args) { + Preconditions.checkArgument(args.length == 2); + this.args = args; + } +} \ No newline at end of file diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java new file mode 100644 index 000000000..887e707e9 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java @@ -0,0 +1,30 @@ +package dev.cel.runtime.planner; + +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueConverter; +import dev.cel.runtime.GlobalResolver; + +@Immutable +final class EvalAttribute implements CelValueInterpretable { + + private final long id; + private final CelValueConverter celValueConverter; + private final Attribute attr; + + @Override + public CelValue eval(GlobalResolver resolver) { + Object obj = attr.resolve(resolver); + return celValueConverter.fromJavaObjectToCelValue(obj); + } + + static EvalAttribute create(long id, CelValueConverter celValueConverter, Attribute attr) { + return new EvalAttribute(id, celValueConverter, attr); + } + + private EvalAttribute(long id, CelValueConverter celValueConverter, Attribute attr) { + this.id = id; + this.celValueConverter = celValueConverter; + this.attr = attr; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java new file mode 100644 index 000000000..5d8cadab9 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java @@ -0,0 +1,27 @@ +package dev.cel.runtime.planner; + +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValue; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; + +@Immutable +final class EvalConstant implements CelValueInterpretable { + + @SuppressWarnings("Immutable") + private final CelValue constant; + + + @Override + public CelValue eval(GlobalResolver resolver) { + return constant; + } + + static EvalConstant create(CelValue constant) { + return new EvalConstant(constant); + } + + private EvalConstant(CelValue constant) { + this.constant = constant; + } +} \ No newline at end of file diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java new file mode 100644 index 000000000..16015b5a2 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java @@ -0,0 +1,37 @@ +package dev.cel.runtime.planner; + +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.ImmutableListValue; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; + +@Immutable +final class EvalCreateList implements CelValueInterpretable { + + @SuppressWarnings("Immutable") + private final CelValueInterpretable[] values; + + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + ImmutableList.Builder builder = ImmutableList.builder(); + for (int i = 0; i < values.length; i++) { + builder.add(values[i].eval(resolver)); + } + return ImmutableListValue.create(builder.build()); + } + + static EvalCreateList create( + CelValueInterpretable[] values + ) { + return new EvalCreateList(values); + } + + private EvalCreateList( + CelValueInterpretable[] values + ) { + this.values = values; + } +} \ No newline at end of file diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java new file mode 100644 index 000000000..2ba7e6b4c --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java @@ -0,0 +1,43 @@ +package dev.cel.runtime.planner; + +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.ImmutableMapValue; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; + +@Immutable +final class EvalCreateMap implements CelValueInterpretable { + + + @SuppressWarnings("Immutable") + private final CelValueInterpretable[] keys; + + @SuppressWarnings("Immutable") + private final CelValueInterpretable[] values; + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (int i = 0; i < keys.length; i++) { + builder.put(keys[i].eval(resolver), values[i].eval(resolver)); + } + return ImmutableMapValue.create(builder.build()); + } + + static EvalCreateMap create( + CelValueInterpretable[] keys, + CelValueInterpretable[] values + ) { + return new EvalCreateMap(keys, values); + } + + private EvalCreateMap( + CelValueInterpretable[] keys, + CelValueInterpretable[] values + ) { + this.keys = keys; + this.values = values; + } +} \ No newline at end of file diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java new file mode 100644 index 000000000..7ff2f5537 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java @@ -0,0 +1,58 @@ +package dev.cel.runtime.planner; + +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueProvider; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +@Immutable +final class EvalCreateStruct implements CelValueInterpretable { + + private final CelValueProvider valueProvider; + private final String typeName; + + + @SuppressWarnings("Immutable") + private final String[] keys; + + @SuppressWarnings("Immutable") + private final CelValueInterpretable[] values; + + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + Map fieldValues = new HashMap<>(); + for (int i = 0; i < keys.length; i++) { + Object value = values[i].eval(resolver).value(); + fieldValues.put(keys[i], value); + } + + return valueProvider.newValue(typeName, Collections.unmodifiableMap(fieldValues)) + .orElseThrow(() -> new IllegalArgumentException("Type name not found: " + typeName)); + } + + static EvalCreateStruct create( + CelValueProvider valueProvider, + String typeName, + String[] keys, + CelValueInterpretable[] values + ) { + return new EvalCreateStruct(valueProvider, typeName, keys, values); + } + + private EvalCreateStruct( + CelValueProvider valueProvider, + String typeName, + String[] keys, + CelValueInterpretable[] values + ) { + this.valueProvider = valueProvider; + this.typeName = typeName; + this.keys = keys; + this.values = values; + } +} 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..634c72111 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java @@ -0,0 +1,136 @@ +package dev.cel.runtime.planner; + +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.IntValue; +import dev.cel.common.values.ListValue; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.common.internal.ConcatenatedListView; +import dev.cel.runtime.GlobalResolver; +import java.util.Collection; +import java.util.Iterator; +import org.jspecify.annotations.Nullable; + +@Immutable +final class EvalFold implements CelValueInterpretable { + + private final String accuVar; + private final CelValueInterpretable accuInit; + private final String iterVar; + private final String iterVar2; + private final CelValueInterpretable iterRange; + private final CelValueInterpretable condition; + private final CelValueInterpretable loopStep; + private final CelValueInterpretable result; + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + // TODO: Consider creating a folder abstraction like in cel-go. This requires some legwork in attribute qualification. + Collection foldRange = (Collection) iterRange.eval(resolver); + + Folder folder = new Folder( + resolver, + accuVar, + iterVar, + iterVar2 + ); + + folder.accuVal = accuInit.eval(folder); + + long index = 0; + for (Iterator iterator = foldRange.iterator(); iterator.hasNext(); ) { + // TODO: Implement condition + if (iterVar2.isEmpty()) { + folder.iterVarVal = iterator.next(); + } else { + folder.iterVarVal = IntValue.create(index); + folder.iterVar2Val = iterator.next(); + } + folder.accuVal = loopStep.eval(folder); + index++; + } + + return result.eval(folder); + } + + private static class Folder implements GlobalResolver { + private final GlobalResolver resolver; + private final String accuVar; + private final String iterVar; + private final String iterVar2; + + private CelValue iterVarVal; + private CelValue iterVar2Val; + private CelValue 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; + } + + // Todo: !f.computeResult check + if (name.equals(iterVar)) { + return this.iterVarVal; + } + + if (name.equals(iterVar2)) { + return this.iterVar2Val; + } + + return resolver.resolve(name); + } + } + + static EvalFold create( + String accuVar, + CelValueInterpretable accuInit, + String iterVar, + String iterVar2, + CelValueInterpretable iterRange, + CelValueInterpretable condition, + CelValueInterpretable loopStep, + CelValueInterpretable result) { + return new EvalFold( + accuVar, + accuInit, + iterVar, + iterVar2, + iterRange, + condition, + loopStep, + result + ); + } + + private EvalFold( + String accuVar, + CelValueInterpretable accuInit, + String iterVar, + String iterVar2, + CelValueInterpretable iterRange, + CelValueInterpretable condition, + CelValueInterpretable loopStep, + CelValueInterpretable result) { + this.accuVar = accuVar; + this.accuInit = accuInit; + this.iterVar = iterVar; + this.iterVar2 = iterVar2; + this.iterRange = iterRange; + this.condition = condition; + this.loopStep = loopStep; + this.result = result; + } +} \ No newline at end of file diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java new file mode 100644 index 000000000..e0184db70 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java @@ -0,0 +1,18 @@ +package dev.cel.runtime.planner; + +import dev.cel.common.values.CelValue; +import dev.cel.common.values.ErrorValue; +import dev.cel.runtime.GlobalResolver; + +final class EvalHelpers { + + static CelValue evalNonstrictly(CelValueInterpretable interpretable, GlobalResolver resolver) { + try { + return interpretable.eval(resolver); + } catch (Exception e) { + return ErrorValue.create(e); + } + } + + private EvalHelpers() {} +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java new file mode 100644 index 000000000..7181e8e7b --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java @@ -0,0 +1,48 @@ +package dev.cel.runtime.planner; + +import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly; + +import com.google.common.base.Preconditions; +import dev.cel.common.values.BoolValue; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.ErrorValue; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; + + +final class EvalOr implements CelValueInterpretable { + + @SuppressWarnings("Immutable") + private final CelValueInterpretable[] args; + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + ErrorValue errorValue = null; + for (CelValueInterpretable arg : args) { + CelValue argVal = evalNonstrictly(arg, resolver); + if (argVal instanceof BoolValue) { + // Short-circuit on true + if (((boolean) argVal.value())) { + return argVal; + } + } else if (argVal instanceof ErrorValue) { + errorValue = (ErrorValue) argVal; + } + } + + if (errorValue != null) { + return errorValue; + } + + return BoolValue.create(false); + } + + static EvalOr create(CelValueInterpretable[] args) { + return new EvalOr(args); + } + + private EvalOr(CelValueInterpretable[] args) { + Preconditions.checkArgument(args.length == 2); + this.args = args; + } +} \ No newline at end of file diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java new file mode 100644 index 000000000..06aefe45e --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java @@ -0,0 +1,28 @@ +package dev.cel.runtime.planner; + +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueConverter; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; +import dev.cel.runtime.CelValueFunctionBinding; + +final class EvalUnary implements CelValueInterpretable { + + private final CelValueFunctionBinding resolvedOverload; + private final CelValueInterpretable arg; + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + CelValue argVal = arg.eval(resolver); + return resolvedOverload.definition().apply(argVal); + } + + static EvalUnary create(CelValueFunctionBinding resolvedOverload, CelValueInterpretable arg) { + return new EvalUnary(resolvedOverload, arg); + } + + private EvalUnary(CelValueFunctionBinding resolvedOverload, CelValueInterpretable arg) { + this.resolvedOverload = resolvedOverload; + this.arg = 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 new file mode 100644 index 000000000..6b3822de2 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java @@ -0,0 +1,33 @@ +package dev.cel.runtime.planner; + +import dev.cel.common.values.CelValue; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelValueFunctionBinding; +import dev.cel.runtime.GlobalResolver; + +@SuppressWarnings("Immutable") +final class EvalVarArgsCall implements CelValueInterpretable { + + private final CelValueFunctionBinding resolvedOverload; + private final CelValueInterpretable[] args; + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + CelValue[] argVals = new CelValue[args.length]; + for (int i = 0; i < args.length; i++) { + CelValueInterpretable arg = args[i]; + argVals[i] = arg.eval(resolver); + } + + return resolvedOverload.definition().apply(argVals); + } + + static EvalVarArgsCall create(CelValueFunctionBinding resolvedOverload, CelValueInterpretable[] args) { + return new EvalVarArgsCall(resolvedOverload, args); + } + + private EvalVarArgsCall(CelValueFunctionBinding resolvedOverload, CelValueInterpretable[] args) { + this.resolvedOverload = resolvedOverload; + this.args = 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 new file mode 100644 index 000000000..0d9f57b49 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java @@ -0,0 +1,26 @@ +package dev.cel.runtime.planner; + +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueConverter; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelFunctionBinding; +import dev.cel.runtime.CelValueFunctionBinding; +import dev.cel.runtime.GlobalResolver; + +final class EvalZeroArity implements CelValueInterpretable { + + private final CelValueFunctionBinding resolvedOverload; + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + return resolvedOverload.definition().apply(); + } + + static EvalZeroArity create(CelValueFunctionBinding resolvedOverload) { + return new EvalZeroArity(resolvedOverload); + } + + private EvalZeroArity(CelValueFunctionBinding resolvedOverload) { + this.resolvedOverload = resolvedOverload; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java new file mode 100644 index 000000000..e74140de0 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -0,0 +1,332 @@ +package dev.cel.runtime.planner; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CheckReturnValue; +import com.google.errorprone.annotations.ThreadSafe; +import dev.cel.common.CelAbstractSyntaxTree; +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.CelStruct; +import dev.cel.common.ast.CelExpr.CelStruct.Entry; +import dev.cel.common.ast.CelReference; +import dev.cel.common.types.CelKind; +import dev.cel.common.types.CelType; +import dev.cel.common.types.CelTypeProvider; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueConverter; +import dev.cel.common.values.CelValueProvider; +import dev.cel.common.values.TypeValue; +import dev.cel.runtime.CelLiteRuntime.Program; +import dev.cel.runtime.CelValueFunctionBinding; +import dev.cel.runtime.CelValueDispatcher; + +import java.util.HashMap; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; + +@ThreadSafe +@Internal +public final class ProgramPlanner { + private final CelTypeProvider typeProvider; + private final CelValueProvider valueProvider; + private final CelValueConverter celValueConverter; + private final CelValueDispatcher dispatcher; + private final AttributeFactory attributeFactory; + + private CelValueInterpretable plan( + CelExpr celExpr, + PlannerContext ctx + ) { + switch (celExpr.getKind()) { + case CONSTANT: + return fromCelConstant(celExpr.constant()); + case IDENT: + return planIdent(celExpr, ctx); + case SELECT: + break; + case CALL: + return planCall(celExpr, ctx); + case LIST: + return planCreateList(celExpr, ctx); + case STRUCT: + 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()); + } + + throw new IllegalArgumentException("Not yet implemented"); + } + + private CelValueInterpretable planIdent( + CelExpr celExpr, + PlannerContext ctx) { + CelReference ref = ctx.referenceMap().get(celExpr.id()); + if (ref != null) { + return planCheckedIdent(celExpr.id(), ref, ctx.typeMap()); + } + + return EvalAttribute.create( + celExpr.id(), + celValueConverter, + attributeFactory.newMaybeAttribute(celExpr.ident().name()) + ); + } + + private CelValueInterpretable planCheckedIdent( + long id, + CelReference identRef, + ImmutableMap typeMap) { + if (identRef.value().isPresent()) { + return fromCelConstant(identRef.value().get()); + } + + CelType type = typeMap.get(id); + if (type.kind().equals(CelKind.TYPE)) { + CelType identType = typeProvider.findType(identRef.name()).orElseThrow(() -> new NoSuchElementException("Reference to undefined type: " + identRef.name())); + return EvalConstant.create(TypeValue.create(identType)); + } + + return EvalAttribute.create(id, celValueConverter, attributeFactory.newAbsoluteAttribute(identRef.name())); + } + + private EvalConstant fromCelConstant(CelConstant celConstant) { + CelValue celValue = celValueConverter.fromJavaObjectToCelValue(celConstant.objectValue()); + return EvalConstant.create(celValue); + } + + private CelValueInterpretable planCall(CelExpr expr, PlannerContext ctx) { + ResolvedFunction resolvedFunction = resolveFunction(expr, ctx.referenceMap()); + CelExpr target = resolvedFunction.target().orElse(null); + int argCount = expr.call().args().size(); + if (target != null) { + argCount++; + } + + CelValueInterpretable[] evaluatedArgs = new CelValueInterpretable[argCount]; + + int offset = 0; + if (target != null) { + evaluatedArgs[0] = plan(target, ctx); + offset++; + } + + ImmutableList args = expr.call().args(); + for (int argIndex = 0; argIndex < args.size(); argIndex++) { + evaluatedArgs[argIndex + offset] = plan(args.get(argIndex), ctx); + } + + // TODO: Handle specialized calls (logical operators, conditionals, equals etc) + String functionName = resolvedFunction.functionName(); + switch (functionName) { + // TODO: Move Operator.java out to common package and use that instead. + case "_||_": + return EvalOr.create(evaluatedArgs); + case "_&&_": + return EvalAnd.create(evaluatedArgs); + } + + CelValueFunctionBinding resolvedOverload = null; + + if (resolvedFunction.overloadId().isPresent()) { + resolvedOverload = dispatcher.findOverload(resolvedFunction.overloadId().get()).orElse(null); + } + + if (resolvedOverload == null) { + resolvedOverload = dispatcher.findOverload(functionName).orElseThrow(() -> new NoSuchElementException("Overload not found: " + functionName)); + } + + switch (argCount) { + case 0: + return EvalZeroArity.create(resolvedOverload); + case 1: + return EvalUnary.create(resolvedOverload, evaluatedArgs[0]); + // TODO: Handle binary + default: + return EvalVarArgsCall.create(resolvedOverload, evaluatedArgs); + } + } + + private CelValueInterpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) { + CelStruct struct = celExpr.struct(); + // TODO: maybe perform the check via type provider? + valueProvider.newValue(struct.messageName(), new HashMap<>()) + .orElseThrow(() -> new IllegalArgumentException("Undefined type name: " + struct.messageName())); + + List entries = struct.entries(); + String[] keys = new String[entries.size()]; + CelValueInterpretable[] values = new CelValueInterpretable[entries.size()]; + + for (int i = 0; i < entries.size(); i++) { + Entry entry = entries.get(i); + keys[i] = entry.fieldKey(); + values[i] = plan(entry.value(), ctx); + } + + return EvalCreateStruct.create(valueProvider, struct.messageName(), keys, values); + } + + private CelValueInterpretable planCreateList(CelExpr celExpr, PlannerContext ctx) { + CelList list = celExpr.list(); + + List elements = list.elements(); + CelValueInterpretable[] values = new CelValueInterpretable[elements.size()]; + + for (int i = 0; i < elements.size(); i++) { + values[i] = plan(elements.get(i), ctx); + } + + return EvalCreateList.create(values); + } + + private CelValueInterpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) { + CelMap map = celExpr.map(); + + List entries = map.entries(); + CelValueInterpretable[] keys = new CelValueInterpretable[entries.size()]; + CelValueInterpretable[] values = new CelValueInterpretable[entries.size()]; + + for (int i = 0; i < entries.size(); i++) { + CelMap.Entry entry = entries.get(i); + keys[i] = plan(entry.key(), ctx); + values[i] = plan(entry.value(), ctx); + } + + return EvalCreateMap.create(keys, values); + } + + private CelValueInterpretable planComprehension(CelExpr expr, PlannerContext ctx) { + CelComprehension comprehension = expr.comprehension(); + + CelValueInterpretable accuInit = plan(comprehension.accuInit(), ctx); + CelValueInterpretable iterRange = plan(comprehension.iterRange(), ctx); + CelValueInterpretable loopCondition = plan(comprehension.loopCondition(), ctx); + CelValueInterpretable loopStep = plan(comprehension.loopStep(), ctx); + CelValueInterpretable result = plan(comprehension.result(), ctx); + + return EvalFold.create( + 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. + */ + private ResolvedFunction resolveFunction(CelExpr expr, ImmutableMap referenceMap) { + CelCall call = expr.call(); + Optional target = call.target(); + String functionName = call.function(); + + CelReference reference = referenceMap.get(expr.id()); + if (reference != null) { + // Checked expression + if (reference.overloadIds().size() == 1) { + ResolvedFunction.Builder builder = ResolvedFunction.newBuilder() + .setFunctionName(functionName) + .setOverloadId(reference.overloadIds().get(0)); + + target.ifPresent(builder::setTarget); + + return builder.build(); + } + } + + // Parse-only from this point on +// dispatcher.findOverload(functionName) +// .orElseThrow(() -> new NoSuchElementException(String.format("Function %s not found", call.function()))); + + if (!target.isPresent()) { + // TODO: Handle containers. + + return ResolvedFunction.newBuilder() + .setFunctionName(functionName) + .build(); + } else { + // TODO: Handle qualifications + return ResolvedFunction.newBuilder() + .setFunctionName(functionName) + .setTarget(target.get()) + .build(); + } + } + + @AutoValue + static abstract class ResolvedFunction { + + abstract String functionName(); + + abstract Optional target(); + + abstract Optional overloadId(); + + @AutoValue.Builder + abstract static class Builder { + abstract Builder setFunctionName(String functionName); + abstract Builder setTarget(CelExpr target); + abstract Builder setOverloadId(String overloadId); + + @CheckReturnValue + abstract ResolvedFunction build(); + } + + private static Builder newBuilder() { + return new AutoValue_ProgramPlanner_ResolvedFunction.Builder(); + } + } + + @AutoValue + static abstract class PlannerContext { + + abstract ImmutableMap referenceMap(); + abstract ImmutableMap typeMap(); + + private static PlannerContext create(CelAbstractSyntaxTree ast) { + return new AutoValue_ProgramPlanner_PlannerContext(ast.getReferenceMap(), ast.getTypeMap()); + } + } + + public Program plan(CelAbstractSyntaxTree ast) { + CelValueInterpretable plannedInterpretable = plan(ast.getExpr(), PlannerContext.create(ast)); + return CelValueProgram.create(plannedInterpretable, celValueConverter); + } + + public static ProgramPlanner newPlanner( + CelTypeProvider typeProvider, + CelValueProvider valueProvider, + CelValueConverter celValueConverter, + CelValueDispatcher dispatcher + ) { + return new ProgramPlanner(typeProvider, valueProvider, celValueConverter, dispatcher); + } + + private ProgramPlanner( + CelTypeProvider typeProvider, + CelValueProvider valueProvider, + CelValueConverter celValueConverter, + CelValueDispatcher dispatcher + ) { + this.typeProvider = typeProvider; + this.valueProvider = valueProvider; + this.celValueConverter = celValueConverter; + this.dispatcher = dispatcher; + this.attributeFactory = AttributeFactory.newAttributeFactory("", celValueConverter, typeProvider); + } +} 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 08177a474..1833106c0 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel @@ -1419,6 +1419,42 @@ cel_android_library( ], ) +java_library( + name = "not_strictly_false", + srcs = ["NotStrictlyFalseFunction.java"], + tags = [ + ], + deps = [ + ":standard_overload", + "//common:error_codes", + "//common:options", + "//common:runtime_exception", + "//runtime:function_binding", + "//runtime:runtime_equality", + "//runtime:runtime_helpers", + "//runtime/standard:standard_function", + "@maven//:com_google_guava_guava", + ], +) + +cel_android_library( + name = "not_strictly_false_android", + srcs = ["NotStrictlyFalseFunction.java"], + tags = [ + ], + deps = [ + ":standard_function_android", + ":standard_overload_android", + "//common:error_codes", + "//common:options", + "//common:runtime_exception", + "//runtime:function_binding_android", + "//runtime:runtime_equality_android", + "//runtime:runtime_helpers_android", + "@maven_android//:com_google_guava_guava", + ], +) + java_library( name = "standard_overload", srcs = ["CelStandardOverload.java"], diff --git a/runtime/src/main/java/dev/cel/runtime/standard/NotStrictlyFalseFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/NotStrictlyFalseFunction.java new file mode 100644 index 000000000..dd3c11214 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/standard/NotStrictlyFalseFunction.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.standard; + +import com.google.common.collect.ImmutableSet; +import dev.cel.common.CelOptions; +import dev.cel.runtime.CelFunctionBinding; +import dev.cel.runtime.RuntimeEquality; + +/** Standard function for {@code matches}. */ +public final class NotStrictlyFalseFunction extends CelStandardFunction { + private static final NotStrictlyFalseFunction ALL_OVERLOADS = new NotStrictlyFalseFunction(ImmutableSet.copyOf(NotStrictlyFalseOverload.values())); + + public static NotStrictlyFalseFunction create() { + return ALL_OVERLOADS; + } + /** Overloads for the standard function. */ + public enum NotStrictlyFalseOverload implements CelStandardOverload { + NOT_STRICTLY_FALSE( + (celOptions, runtimeEquality) -> + CelFunctionBinding.from( + "not_strictly_false", + Object.class, + (Object value) -> { + if (value instanceof Boolean) { + return value; + } + + return true; + })), + ; + + private final FunctionBindingCreator bindingCreator; + + @Override + public CelFunctionBinding newFunctionBinding( + CelOptions celOptions, RuntimeEquality runtimeEquality) { + return bindingCreator.create(celOptions, runtimeEquality); + } + + NotStrictlyFalseOverload(FunctionBindingCreator bindingCreator) { + this.bindingCreator = bindingCreator; + } + } + + private NotStrictlyFalseFunction(ImmutableSet overloads) { + super(overloads); + } +} \ No newline at end of file diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 5137cad39..5321e2331 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -48,7 +48,9 @@ java_library( "//common/internal:well_known_proto", "//common/types", "//common/types:cel_v1alpha1_types", + "//common/types:default_type_provider", "//common/types:message_type_provider", + "//common/types:type_providers", "//common/values", "//common/values:cel_byte_string", "//common/values:cel_value_provider", @@ -61,7 +63,7 @@ java_library( "//parser:unparser", "//runtime", "//runtime:activation", - "//runtime:dispatcher", + "//runtime:legacy_dispatcher", "//runtime:evaluation_exception_builder", "//runtime:evaluation_listener", "//runtime:function_binding", @@ -81,6 +83,7 @@ java_library( "//runtime:type_resolver", "//runtime:unknown_attributes", "//runtime:unknown_options", + "//runtime/planner:program_planner", "//testing/protos:message_with_enum_cel_java_proto", "//testing/protos:message_with_enum_java_proto", "//testing/protos:multi_file_cel_java_proto", diff --git a/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java b/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java index 556d6945c..4d0b3f2b3 100644 --- a/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java +++ b/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java @@ -1,60 +1,60 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License aj -// -// 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.truth.Truth.assertThat; - -import com.google.common.collect.ImmutableList; -import java.util.HashMap; -import java.util.Map; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** Tests for {@link DefaultDispatcher}. */ -@RunWith(JUnit4.class) -public final class DefaultDispatcherTest { - - private Map overloads; - - @Before - public void setup() { - overloads = new HashMap<>(); - overloads.put( - "overload_1", - CelResolvedOverload.of( - "overload_1", new Class[] {Long.class}, args -> (Long) args[0] + 1)); - overloads.put( - "overload_2", - CelResolvedOverload.of( - "overload_2", new Class[] {Long.class}, args -> (Long) args[0] + 2)); - } - - @Test - public void findOverload_multipleMatches_throwsException() { - CelEvaluationException e = - Assert.assertThrows( - CelEvaluationException.class, - () -> - DefaultDispatcher.findOverload( - "overloads", - ImmutableList.of("overload_1", "overload_2"), - overloads, - new Object[] {1L})); - assertThat(e).hasMessageThat().contains("Matching candidates: overload_1, overload_2"); - } -} +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License aj +// +// 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.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import java.util.HashMap; +import java.util.Map; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link DefaultDispatcher}. */ +@RunWith(JUnit4.class) +public final class DefaultDispatcherTest { + + private Map overloads; + + @Before + public void setup() { + overloads = new HashMap<>(); + overloads.put( + "overload_1", + CelResolvedOverload.of( + "overload_1", new Class[] {Long.class}, args -> (Long) args[0] + 1)); + overloads.put( + "overload_2", + CelResolvedOverload.of( + "overload_2", new Class[] {Long.class}, args -> (Long) args[0] + 2)); + } + + @Test + public void findOverload_multipleMatches_throwsException() { + CelEvaluationException e = + Assert.assertThrows( + CelEvaluationException.class, + () -> + DefaultDispatcher.findOverload( + "overloads", + ImmutableList.of("overload_1", "overload_2"), + overloads, + new Object[] {1L})); + assertThat(e).hasMessageThat().contains("Matching candidates: overload_1, overload_2"); + } +} diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel new file mode 100644 index 000000000..447d76a60 --- /dev/null +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -0,0 +1,126 @@ +load("@rules_java//java:defs.bzl", "java_library") +load("//:testing.bzl", "junit4_test_suites") + +package( + default_applicable_licenses = ["//:license"], + default_testonly = True, +) + +java_library( + name = "tests", + testonly = 1, + srcs = glob( + ["*.java"], + ), + deps = [ + "//:auto_value", + "//:java_truth", + "//bundle:cel", + "//common:cel_ast", + "//common:cel_descriptor_util", + "//common:cel_descriptors", + "//common:cel_exception", + "//common:cel_source", + "//common:compiler_common", + "//common:container", + "//common:error_codes", + "//common:options", + "//common:proto_v1alpha1_ast", + "//common:runtime_exception", + "//common/ast", + "//common/internal:cel_descriptor_pools", + "//common/internal:converter", + "//common/internal:default_lite_descriptor_pool", + "//common/internal:default_message_factory", + "//common/internal:dynamic_proto", + "//common/internal:proto_message_factory", + "//common/internal:proto_time_utils", + "//common/internal:well_known_proto", + "//common/types", + "//common/types:cel_v1alpha1_types", + "//common/types:default_type_provider", + "//common/types:message_type_provider", + "//common/types:type_providers", + "//common/values", + "//common/values:cel_byte_string", + "//common/values:cel_value", + "//common/values:cel_value_provider", + "//common/values:proto_message_lite_value", + "//common/values:proto_message_lite_value_provider", + "//common/values:proto_message_value", + "//common/values:proto_message_value_provider", + "//compiler", + "//compiler:compiler_builder", + "//extensions", + "//extensions:optional_library", + "//parser:macro", + "//parser:operator", + "//parser:unparser", + "//runtime", + "//runtime:activation", + "//runtime:cel_value_function_binding", + "//runtime:cel_value_function_overload", + "//runtime:default_dispatcher", + "//runtime:evaluation_exception_builder", + "//runtime:evaluation_listener", + "//runtime:function_binding", + "//runtime:function_overload_impl", + "//runtime:interpretable", + "//runtime:interpreter", + "//runtime:interpreter_util", + "//runtime:late_function_binding", + "//runtime:legacy_dispatcher", + "//runtime:lite_runtime", + "//runtime:lite_runtime_factory", + "//runtime:proto_message_activation_factory", + "//runtime:proto_message_runtime_equality", + "//runtime:proto_message_runtime_helpers", + "//runtime:runtime_equality", + "//runtime:runtime_helpers", + "//runtime:standard_functions", + "//runtime:type_resolver", + "//runtime:unknown_attributes", + "//runtime:unknown_options", + "//runtime/planner:program_planner", + "//runtime/standard:add", + "//runtime/standard:divide", + "//runtime/standard:equals", + "//runtime/standard:greater", + "//runtime/standard:greater_equals", + "//runtime/standard:index", + "//runtime/standard:less", + "//runtime/standard:logical_not", + "//runtime/standard:not_strictly_false", + "//runtime/standard:standard_function", + "//testing/protos:message_with_enum_cel_java_proto", + "//testing/protos:message_with_enum_java_proto", + "//testing/protos:multi_file_cel_java_proto", + "//testing/protos:multi_file_java_proto", + "//testing/protos:single_file_java_proto", + "//testing/protos:test_all_types_cel_java_proto2", + "//testing/protos:test_all_types_cel_java_proto3", + "@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto", + "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto", + "@com_google_googleapis//google/api/expr/v1alpha1:expr_java_proto", + "@com_google_googleapis//google/rpc/context:attribute_context_java_proto", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + "@maven//:com_google_protobuf_protobuf_java_util", + "@maven//:com_google_testparameterinjector_test_parameter_injector", + "@maven//:com_google_truth_extensions_truth_proto_extension", + "@maven//:junit_junit", + "@maven//:org_jspecify_jspecify", + "@maven_android//:com_google_protobuf_protobuf_javalite", + ], +) + +junit4_test_suites( + name = "test_suites", + sizes = [ + "small", + ], + src_dir = "src/test/java", + deps = [ + ":tests", + ], +) diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java new file mode 100644 index 000000000..a14ac6e53 --- /dev/null +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -0,0 +1,590 @@ +package dev.cel.runtime.planner; + +import static com.google.common.truth.Truth.assertThat; +import static dev.cel.common.CelFunctionDecl.newFunctionDeclaration; +import static dev.cel.common.CelOverloadDecl.newGlobalOverload; +import static dev.cel.common.CelOverloadDecl.newMemberOverload; +import static org.junit.Assert.assertThrows; + +import com.google.common.collect.ImmutableCollection; +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; +import com.google.testing.junit.testparameterinjector.TestParameters; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelDescriptorUtil; +import dev.cel.common.CelErrorCode; +import dev.cel.common.CelOptions; +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.CelType; +import dev.cel.common.types.DefaultTypeProvider; +import dev.cel.common.types.MapType; +import dev.cel.common.types.SimpleType; +import dev.cel.common.types.StructTypeReference; +import dev.cel.common.types.TypeType; +import dev.cel.common.values.BoolValue; +import dev.cel.common.values.BytesValue; +import dev.cel.common.values.CelByteString; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueConverter; +import dev.cel.common.values.DoubleValue; +import dev.cel.common.values.DurationValue; +import dev.cel.common.values.IntValue; +import dev.cel.common.values.ListValue; +import dev.cel.common.values.MapValue; +import dev.cel.common.values.NullValue; +import dev.cel.common.values.ProtoCelValueConverter; +import dev.cel.common.values.ProtoMessageValueProvider; +import dev.cel.common.values.StringValue; +import dev.cel.common.values.TimestampValue; +import dev.cel.common.values.TypeValue; +import dev.cel.common.values.UintValue; +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.extensions.CelExtensions; +import dev.cel.extensions.CelOptionalLibrary; +import dev.cel.parser.CelStandardMacro; +import dev.cel.parser.Operator; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelFunctionBinding; +import dev.cel.runtime.CelLiteRuntime.Program; +import dev.cel.runtime.CelValueFunctionBinding; +import dev.cel.runtime.CelValueFunctionOverload; +import dev.cel.runtime.RuntimeEquality; +import dev.cel.runtime.RuntimeHelpers; +import dev.cel.runtime.standard.AddOperator; +import dev.cel.runtime.standard.CelStandardFunction; +import dev.cel.runtime.standard.DivideOperator; +import dev.cel.runtime.standard.EqualsOperator; +import dev.cel.runtime.standard.GreaterEqualsOperator; +import dev.cel.runtime.standard.GreaterOperator; +import dev.cel.runtime.standard.IndexOperator; +import dev.cel.runtime.standard.LessOperator; +import dev.cel.runtime.standard.LogicalNotOperator; +import dev.cel.runtime.standard.NotStrictlyFalseFunction; +import java.nio.charset.StandardCharsets; + +import dev.cel.runtime.CelValueDispatcher; +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(TestParameterInjector.class) +public final class ProgramPlannerTest { + private static final CelOptions CEL_OPTIONS = CelOptions.current().evaluateCanonicalTypesToNativeValues(true).build(); + private static final RuntimeEquality RUNTIME_EQUALITY = RuntimeEquality.create(RuntimeHelpers.create(), CEL_OPTIONS); + private static final CelCompiler CEL_COMPILER = + CelCompilerFactory.standardCelCompilerBuilder() + .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) + .addVar("int_var", SimpleType.INT) + .addVar("map_var", MapType.create(SimpleType.STRING, SimpleType.DYN)) + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addFunctionDeclarations( + newFunctionDeclaration("zero", newGlobalOverload("zero_overload", SimpleType.INT)), + newFunctionDeclaration("error", newGlobalOverload("error_overload", SimpleType.INT)), + newFunctionDeclaration("neg", + newGlobalOverload("neg_int", SimpleType.INT, SimpleType.INT), + newGlobalOverload("neg_double", SimpleType.DOUBLE, SimpleType.DOUBLE) + ), + newFunctionDeclaration("concat", + newGlobalOverload("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()) + .build(); + + // Note that the following deps are ordinarily built from top-level builder APIs + private static final CelDescriptorPool DESCRIPTOR_POOL = + DefaultDescriptorPool.create(CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(TestAllTypes.getDescriptor().getFile())); + private static final DynamicProto DYNAMIC_PROTO = DynamicProto.create(DefaultMessageFactory.create(DESCRIPTOR_POOL)); + private static final CelValueConverter CEL_VALUE_CONVERTER = ProtoCelValueConverter.newInstance(DESCRIPTOR_POOL, + DYNAMIC_PROTO); + private static final ProgramPlanner PLANNER = ProgramPlanner.newPlanner( + DefaultTypeProvider.create(), + ProtoMessageValueProvider.newInstance( + CEL_OPTIONS, DYNAMIC_PROTO + ), + CEL_VALUE_CONVERTER, + newDispatcher() + ); + + /** + * Configure dispatcher for testing purposes. This is done manually here, but this should be driven by the top-level runtime APIs in the future + */ + private static CelValueDispatcher newDispatcher() { + CelValueDispatcher.Builder builder = CelValueDispatcher.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())); + + // Custom functions + addBindings(builder, "zero", CelValueFunctionBinding.from("zero_overload", () -> IntValue.create(0L))); + addBindings(builder, "error", CelValueFunctionBinding.from("error_overload", () -> { throw new IllegalArgumentException("Intentional error"); })); + addBindings(builder, "neg", + CelValueFunctionBinding.from("neg_int", IntValue.class, arg -> IntValue.create(-arg.longValue())), + CelValueFunctionBinding.from("neg_double", DoubleValue.class, arg -> DoubleValue.create(-arg.doubleValue())) + ); + addBindings(builder, "concat", + CelValueFunctionBinding.from("concat_bytes_bytes", BytesValue.class, BytesValue.class, ProgramPlannerTest::concatenateByteArrays), + CelValueFunctionBinding.from("bytes_concat_bytes", BytesValue.class, BytesValue.class,ProgramPlannerTest::concatenateByteArrays)); + + return builder.build(); + } + + + private static void addBindings(CelValueDispatcher.Builder builder, String functionName, CelValueFunctionBinding... functionBindings) { + addBindings(builder, functionName, ImmutableSet.copyOf(functionBindings)); + } + + private static void addBindings(CelValueDispatcher.Builder builder, String functionName, 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) { + CelValueFunctionBinding singleBinding = Iterables.getOnlyElement(overloadBindings); + builder.addOverload( + CelValueFunctionBinding.from( + functionName, singleBinding.argTypes(), singleBinding.definition()) + ); + } else { + overloadBindings.forEach(builder::addOverload); + + // Setup dynamic dispatch + CelValueFunctionOverload dynamicDispatchDef = args -> { + for (CelValueFunctionBinding overload : overloadBindings) { + if (overload.canHandle(args)) { + return overload.definition().apply(args); + } + } + + throw new IllegalArgumentException("Overload not found: " + functionName); + }; + + builder.addDynamicDispatchOverload(functionName, dynamicDispatchDef); + } + } + + @TestParameter boolean isParseOnly; + + @SuppressWarnings("ImmutableEnumChecker") // Test only + private enum ConstantTestCase { + NULL( + "null", + NullValue.NULL_VALUE), + BOOLEAN( + "true", + true), + INT64( + "42", 42L), + UINT64("42u", UnsignedLong.valueOf(42)), + DOUBLE("1.5", 1.5d), + STRING("'hello world'", "hello world"), + BYTES( + "b'abc'", CelByteString.of("abc".getBytes(StandardCharsets.UTF_8))); + + private final String expression; + private final Object expected; + + ConstantTestCase(String expression, Object expected) { + this.expression = expression; + this.expected = expected; + } + } + + @Test + public void planConst(@TestParameter ConstantTestCase testCase) throws Exception { + CelAbstractSyntaxTree ast = compile(testCase.expression); + Program program = PLANNER.plan(ast); + + Object result = program.eval(); + + assertThat(result).isEqualTo(testCase.expected); + } + + @Test + public void planIdent_enum() throws Exception { + CelAbstractSyntaxTree ast = compile(GlobalEnum.getDescriptor().getFullName() + "." + GlobalEnum.GAR); + Program program = PLANNER.plan(ast); + + Object result = program.eval(); + + assertThat(result).isEqualTo(1); + } + + @Test + public void planIdent_variable() throws Exception { + CelAbstractSyntaxTree ast = compile("int_var"); + Program program = PLANNER.plan(ast); + + Object result = program.eval(ImmutableMap.of("int_var", 1L)); + + assertThat(result).isEqualTo(1); + } + + @Test + public void planCreateStruct() throws Exception { + CelAbstractSyntaxTree ast = compile("cel.expr.conformance.proto3.TestAllTypes{}"); + Program program = PLANNER.plan(ast); + + Object result = program.eval(); + + assertThat(result).isEqualTo(TestAllTypes.getDefaultInstance()); + } + + @Test + public void planCreateStruct_withFields() throws Exception { + CelAbstractSyntaxTree ast = compile("cel.expr.conformance.proto3.TestAllTypes{" + + "single_string: 'foo'," + + "single_bool: true" + + "}"); + Program program = PLANNER.plan(ast); + + Object result = program.eval(); + + assertThat(result).isEqualTo(TestAllTypes.newBuilder().setSingleString("foo").setSingleBool(true).build()); + } + + @Test + public void planCreateList() throws Exception { + CelAbstractSyntaxTree ast = compile("[1, 'foo', true]"); + + Program program = PLANNER.plan(ast); + + List result = (List) program.eval(); + + assertThat(result).containsExactly(1L, "foo", true).inOrder(); + } + + @Test + public void planCreateMap() throws Exception { + CelAbstractSyntaxTree ast = compile("{'foo': 1, true: 'bar'}"); + + Program program = PLANNER.plan(ast); + + Map result = (Map) program.eval(); + + assertThat(result).containsExactly("foo", 1L, true, "bar").inOrder(); + } + + @SuppressWarnings("ImmutableEnumChecker") // Test only + private enum TypeLiteralTestCase { + // BOOL("bool", SimpleType.BOOL), + // BYTES("bytes", SimpleType.BYTES), + // DOUBLE("double", SimpleType.DOUBLE), + // INT("int", SimpleType.INT), + // UINT("uint", SimpleType.UINT), + // STRING("string", SimpleType.STRING), + // DYN("dyn", SimpleType.DYN), + // LIST("list", ListType.create(SimpleType.DYN)), + // MAP("map", MapType.create(SimpleType.DYN, SimpleType.DYN)), + // NULL("null_type", SimpleType.NULL_TYPE), + DURATION("google.protobuf.Duration", SimpleType.DURATION), + TIMESTAMP("google.protobuf.Timestamp", SimpleType.TIMESTAMP), + // OPTIONAL("optional_type", OptionalType.create(SimpleType.DYN)), + ; + + private final String expression; + private final TypeType type; + + TypeLiteralTestCase(String expression, CelType type) { + this.expression = expression; + this.type = TypeType.create(type); + } + } + + @Test + public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) throws Exception { + CelAbstractSyntaxTree ast = compile(testCase.expression); + Program program = PLANNER.plan(ast); + + TypeType result = (TypeType) program.eval(); + + assertThat(result).isEqualTo(testCase.type); + } + + @Test + public void planCall_zeroArgs() throws Exception { + CelAbstractSyntaxTree ast = compile("zero()"); + Program program = PLANNER.plan(ast); + + Long result = (Long) program.eval(); + + assertThat(result).isEqualTo(0L); + } + + @Test + public void planCall_throws() throws Exception { + CelAbstractSyntaxTree ast = compile("error()"); + Program program = PLANNER.plan(ast); + + CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); + assertThat(e).hasMessageThat().contains("evaluation error: Intentional error"); + assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void planCall_oneArg_int() throws Exception { + CelAbstractSyntaxTree ast = compile("neg(1)"); + Program program = PLANNER.plan(ast); + + Long result = (Long) program.eval(); + + assertThat(result).isEqualTo(-1L); + } + + @Test + public void planCall_oneArg_double() throws Exception { + CelAbstractSyntaxTree ast = compile("neg(2.5)"); + Program program = PLANNER.plan(ast); + + Double result = (Double) program.eval(); + + assertThat(result).isEqualTo(-2.5d); + } + + @Test + public void planCall_twoArgs_global() throws Exception { + CelAbstractSyntaxTree ast = compile("concat(b'abc', b'def')"); + Program program = PLANNER.plan(ast); + + CelByteString result = (CelByteString) program.eval(); + + assertThat(result).isEqualTo(CelByteString.of("abcdef".getBytes(StandardCharsets.UTF_8))); + } + + @Test + public void planCall_twoArgs_receiver() throws Exception { + CelAbstractSyntaxTree ast = compile("b'abc'.concat(b'def')"); + Program program = PLANNER.plan(ast); + + CelByteString result = (CelByteString) program.eval(); + + assertThat(result).isEqualTo(CelByteString.of("abcdef".getBytes(StandardCharsets.UTF_8))); + } + + @Test + public void planCall_mapIndex() throws Exception { + CelAbstractSyntaxTree ast = compile("map_var['key'][1]"); + Program program = PLANNER.plan(ast); + ImmutableMap mapVarPayload = ImmutableMap.of("key", ImmutableList.of(1L, 2L)); + + Long result = (Long) program.eval(ImmutableMap.of("map_var", mapVarPayload)); + + assertThat(result).isEqualTo(2L); + } + + @Test + @TestParameters("{expression: 'true || true', expectedResult: true}") + @TestParameters("{expression: 'true || false', expectedResult: true}") + @TestParameters("{expression: 'false || true', expectedResult: true}") + @TestParameters("{expression: 'false || false', expectedResult: false}") + @TestParameters("{expression: 'true || (1 / 0 > 2)', expectedResult: true}") + @TestParameters("{expression: '(1 / 0 > 2) || true', expectedResult: true}") + public void planCall_logicalOr_shortCircuit(String expression, boolean expectedResult) throws Exception { + CelAbstractSyntaxTree ast = compile(expression); + Program program = PLANNER.plan(ast); + + boolean result = (boolean) program.eval(); + + assertThat(result).isEqualTo(expectedResult); + } + + @Test + @TestParameters("{expression: '(1 / 0 > 2) || (1 / 0 > 2)'}") + @TestParameters("{expression: 'false || (1 / 0 > 2)'}") + @TestParameters("{expression: '(1 / 0 > 2) || false'}") + public void planCall_logicalOr_throws(String expression) throws Exception { + CelAbstractSyntaxTree ast = compile(expression); + Program program = PLANNER.plan(ast); + + CelEvaluationException e = assertThrows(CelEvaluationException.class, () -> program.eval()); + // TODO: Tag metadata (source loc) + assertThat(e).hasMessageThat().isEqualTo("evaluation error: / by zero"); + assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class); + assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO); + } + + @Test + @TestParameters("{expression: 'true && true', expectedResult: true}") + @TestParameters("{expression: 'true && false', expectedResult: false}") + @TestParameters("{expression: 'false && true', expectedResult: false}") + @TestParameters("{expression: 'false && false', expectedResult: false}") + @TestParameters("{expression: 'false && (1 / 0 > 2)', expectedResult: false}") + @TestParameters("{expression: '(1 / 0 > 2) && false', expectedResult: false}") + public void planCall_logicalAnd_shortCircuit(String expression, boolean expectedResult) throws Exception { + CelAbstractSyntaxTree ast = compile(expression); + Program program = PLANNER.plan(ast); + + boolean result = (boolean) program.eval(); + + assertThat(result).isEqualTo(expectedResult); + } + + @Test + @TestParameters("{expression: '(1 / 0 > 2) && (1 / 0 > 2)'}") + @TestParameters("{expression: 'true && (1 / 0 > 2)'}") + @TestParameters("{expression: '(1 / 0 > 2) && true'}") + public void planCall_logicalAnd_throws(String expression) throws Exception { + CelAbstractSyntaxTree ast = compile(expression); + Program program = PLANNER.plan(ast); + + CelEvaluationException e = assertThrows(CelEvaluationException.class, () -> program.eval()); + // TODO: Tag metadata (source loc) + assertThat(e).hasMessageThat().isEqualTo("evaluation error: / by zero"); + assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class); + assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO); + } + + @Test + public void planSelect() 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 + @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 planComprehension_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: '[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'}") + public void planComprehension_maps(String expression) throws Exception { + CelAbstractSyntaxTree ast = compile(expression); + Program program = PLANNER.plan(ast); + + boolean result = (boolean) program.eval(); + + assertThat(result).isTrue(); + } + + private CelAbstractSyntaxTree compile(String expression) throws Exception { + CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst(); + if (isParseOnly) { + return ast; + } + + return CEL_COMPILER.check(ast).getAst(); + } + + private static BytesValue concatenateByteArrays(BytesValue bytes1, BytesValue bytes2) { + if (bytes1.isZeroValue()) { + return bytes2; + } + + if (bytes2.isZeroValue()) { + return bytes1; + } + + CelByteString combined = bytes1.value().concat(bytes2.value()); + return BytesValue.create(combined); + } + + // TODO: The following native -> CelValue function binding will need to be an adapter. + private static ImmutableSet fromStandardFunction(CelStandardFunction standardFunction) { + ImmutableSet functionBindings = standardFunction.newFunctionBindings(CEL_OPTIONS, RUNTIME_EQUALITY); + ImmutableSet.Builder builder = ImmutableSet.builder(); + + for (CelFunctionBinding functionBinding : functionBindings) { + CelValueFunctionBinding adaptedBinding = CelValueFunctionBinding.from(functionBinding.getOverloadId(), adaptArgumentTypes(functionBinding.getArgTypes()), celValueArgs -> { + Object[] nativeArgs = new Object[celValueArgs.length]; + for (int i = 0; i < celValueArgs.length; i++) { + nativeArgs[i] = CEL_VALUE_CONVERTER.fromCelValueToJavaObject(celValueArgs[i]); + } + + Object nativeResult = functionBinding.getDefinition().apply(nativeArgs); + return CEL_VALUE_CONVERTER.fromJavaObjectToCelValue(nativeResult); + }); + builder.add(adaptedBinding); + } + + return builder.build(); + } + + private static ImmutableList> adaptArgumentTypes(ImmutableList> argTypes) { + ImmutableList.Builder> builder = ImmutableList.builder(); + + for (Class argType : argTypes) { + if (argType.equals(String.class)) { + builder.add(StringValue.class); + } else if (argType.equals(Long.class)) { + builder.add(IntValue.class); + } else if (argType.equals(Double.class)) { + builder.add(DoubleValue.class); + } else if (argType.equals(Boolean.class)) { + builder.add(BoolValue.class); + } else if (argType.equals(UnsignedLong.class)) { + builder.add(UintValue.class); + } else if (argType.equals(CelByteString.class)) { + builder.add(BytesValue.class); + } else if (argType.equals(Instant.class)) { + builder.add(TimestampValue.class); + } else if (argType.equals(Duration.class)) { + builder.add(DurationValue.class); + } else if (List.class.isAssignableFrom(argType)) { + builder.add(ListValue.class); + } else if (Map.class.isAssignableFrom(argType)) { + builder.add(MapValue.class); + } else if (CelType.class.isAssignableFrom(argType)) { + builder.add(TypeValue.class); + } else if (argType.equals(NullValue.class)) { + builder.add(NullValue.class); + } else if ( + argType.equals(Object.class) || + // Using Number.class was probably a mistake (see index_list). This particular overload will benefit from a concrete definition. + argType.equals(Number.class) + ) { + builder.add(CelValue.class); + } else { + // In all likelihood -- we should probably do an OpaqueValue here + throw new IllegalArgumentException("Unknown argument type: " + argType); + } + } + return builder.build(); + } + +} diff --git a/runtime/standard/BUILD.bazel b/runtime/standard/BUILD.bazel index 5f84c105f..d6bddec66 100644 --- a/runtime/standard/BUILD.bazel +++ b/runtime/standard/BUILD.bazel @@ -405,3 +405,13 @@ cel_android_library( name = "uint_android", exports = ["//runtime/src/main/java/dev/cel/runtime/standard:uint_android"], ) + +java_library( + name = "not_strictly_false", + exports = ["//runtime/src/main/java/dev/cel/runtime/standard:not_strictly_false"], +) + +cel_android_library( + name = "not_strictly_false_android", + exports = ["//runtime/src/main/java/dev/cel/runtime/standard:not_strictly_false_android"], +)