From 299c1de56469193c4ba1fa96e00513c8d3decf90 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 14 May 2025 17:12:29 -0700 Subject: [PATCH] Add late bound function resolver to lite runtime's program interface PiperOrigin-RevId: 758893486 --- runtime/BUILD.bazel | 12 ++------ .../src/main/java/dev/cel/runtime/BUILD.bazel | 24 ++++++++++++++-- .../java/dev/cel/runtime/CelLiteRuntime.java | 10 +++++++ .../main/java/dev/cel/runtime/CelRuntime.java | 7 +---- .../dev/cel/runtime/DefaultInterpreter.java | 8 ++++++ .../java/dev/cel/runtime/Interpretable.java | 11 ++++++++ .../java/dev/cel/runtime/LiteProgramImpl.java | 10 +++++++ .../dev/cel/runtime/CelLiteRuntimeTest.java | 28 +++++++++++++++++++ testing/src/test/resources/protos/BUILD.bazel | 1 - 9 files changed, 92 insertions(+), 19 deletions(-) diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index 80c61dc10..2dd630293 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -51,25 +51,19 @@ java_library( java_library( name = "function_binding", - exports = [ - "//runtime/src/main/java/dev/cel/runtime:function_binding", - ], + exports = ["//runtime/src/main/java/dev/cel/runtime:function_binding"], ) cel_android_library( name = "function_binding_android", - exports = [ - "//runtime/src/main/java/dev/cel/runtime:function_binding_android", - ], + exports = ["//runtime/src/main/java/dev/cel/runtime:function_binding_android"], ) java_library( name = "function_overload_impl", # used_by_android visibility = ["//:internal"], - exports = [ - "//runtime/src/main/java/dev/cel/runtime:function_overload_impl", - ], + exports = ["//runtime/src/main/java/dev/cel/runtime:function_overload_impl"], ) java_library( diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index dea69bad0..ebd23a856 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -448,7 +448,6 @@ java_library( # keep sorted RUNTIME_SOURCES = [ - "CelFunctionResolver.java", "CelLateFunctionBindings.java", "CelResolvedOverload.java", "CelRuntime.java", @@ -603,6 +602,19 @@ cel_android_library( ], ) +java_library( + name = "function_resolver", + srcs = ["CelFunctionResolver.java"], + # used_by_android + tags = [ + ], + deps = [ + ":function_overload_impl", + "@maven//:com_google_code_findbugs_annotations", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + java_library( name = "function_overload", srcs = [ @@ -652,6 +664,7 @@ java_library( ":function_binding", ":function_overload", ":function_overload_impl", + ":function_resolver", ":interpretable", ":interpreter", ":lite_runtime", @@ -689,6 +702,7 @@ java_library( deps = [ ":evaluation_exception", ":function_binding", + ":function_resolver", ":standard_functions", "//:auto_value", "//common:cel_ast", @@ -710,7 +724,9 @@ java_library( ":cel_value_runtime_type_provider", ":dispatcher", ":evaluation_exception", + ":evaluation_listener", ":function_binding", + ":function_resolver", ":interpretable", ":interpreter", ":lite_runtime", @@ -738,7 +754,9 @@ cel_android_library( ":cel_value_runtime_type_provider_android", ":dispatcher_android", ":evaluation_exception", + ":evaluation_listener_android", ":function_binding_android", + ":function_resolver", ":interpretable_android", ":interpreter_android", ":lite_runtime_android", @@ -923,8 +941,7 @@ java_library( cel_android_library( name = "evaluation_listener_android", srcs = ["CelEvaluationListener.java"], - tags = [ - ], + visibility = ["//visibility:private"], deps = [ "//common/ast:ast_android", "@maven//:com_google_code_findbugs_annotations", @@ -940,6 +957,7 @@ cel_android_library( deps = [ ":evaluation_exception", ":function_binding_android", + ":function_resolver", ":standard_functions_android", "//:auto_value", "//common:cel_ast_android", diff --git a/runtime/src/main/java/dev/cel/runtime/CelLiteRuntime.java b/runtime/src/main/java/dev/cel/runtime/CelLiteRuntime.java index 9f8516575..0603db958 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelLiteRuntime.java +++ b/runtime/src/main/java/dev/cel/runtime/CelLiteRuntime.java @@ -36,8 +36,18 @@ public interface CelLiteRuntime { /** Creates an evaluable {@code Program} instance which is thread-safe and immutable. */ @Immutable interface Program { + + /** Evaluate the expression without any variables. */ Object eval() throws CelEvaluationException; + /** Evaluate the expression using a {@code mapValue} as the source of input variables. */ Object eval(Map mapValue) throws CelEvaluationException; + + /** + * Evaluate a compiled program with {@code mapValue} and late-bound functions {@code + * lateBoundFunctionResolver}. + */ + Object eval(Map mapValue, CelFunctionResolver lateBoundFunctionResolver) + throws CelEvaluationException; } } diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntime.java b/runtime/src/main/java/dev/cel/runtime/CelRuntime.java index a28808721..95b4d7ca5 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntime.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntime.java @@ -43,13 +43,11 @@ public interface CelRuntime { @Immutable abstract class Program implements CelLiteRuntime.Program { - /** Evaluate the expression without any variables. */ @Override public Object eval() throws CelEvaluationException { return evalInternal(Activation.EMPTY); } - /** Evaluate the expression using a {@code mapValue} as the source of input variables. */ @Override public Object eval(Map mapValue) throws CelEvaluationException { return evalInternal(Activation.copyOf(mapValue)); @@ -77,10 +75,7 @@ public Object eval(CelVariableResolver resolver, CelFunctionResolver lateBoundFu CelEvaluationListener.noOpListener()); } - /** - * Evaluate a compiled program with {@code mapValue} and late-bound functions {@code - * lateBoundFunctionResolver}. - */ + @Override public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctionResolver) throws CelEvaluationException { return evalInternal( diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java index afb81adc2..395559ef1 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java @@ -147,6 +147,14 @@ public Object eval(GlobalResolver resolver, CelEvaluationListener listener) RuntimeUnknownResolver.fromResolver(resolver), Optional.empty(), listener); } + @Override + public Object eval(GlobalResolver resolver, FunctionResolver lateBoundFunctionResolver) + throws CelEvaluationException { + return eval(resolver, + lateBoundFunctionResolver, + CelEvaluationListener.noOpListener()); + } + @Override public Object eval( GlobalResolver resolver, diff --git a/runtime/src/main/java/dev/cel/runtime/Interpretable.java b/runtime/src/main/java/dev/cel/runtime/Interpretable.java index 82701eaa2..21e95921d 100644 --- a/runtime/src/main/java/dev/cel/runtime/Interpretable.java +++ b/runtime/src/main/java/dev/cel/runtime/Interpretable.java @@ -37,6 +37,17 @@ public interface Interpretable { 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. * diff --git a/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java index 8c5989323..f7de52aa9 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java @@ -34,6 +34,16 @@ public Object eval(Map mapValue) throws CelEvaluationException { return interpretable().eval(Activation.copyOf(mapValue)); } + @Override + public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctionResolver) + throws CelEvaluationException { + return interpretable() + .eval( + Activation.copyOf(mapValue), + lateBoundFunctionResolver, + CelEvaluationListener.noOpListener()); + } + static CelLiteRuntime.Program plan(Interpretable interpretable) { return new AutoValue_LiteProgramImpl(interpretable); } diff --git a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java index b4bfb650f..9c236f0ea 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java @@ -41,6 +41,8 @@ import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelFunctionDecl; +import dev.cel.common.CelOverloadDecl; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.types.SimpleType; import dev.cel.common.types.StructTypeReference; @@ -605,4 +607,30 @@ public void nestedMessage_fromImportedProto() throws Exception { assertThat(result).isEqualTo("foo"); } + + @Test + public void eval_withLateBoundFunction() throws Exception { + CelCompiler celCompiler = + CelCompilerFactory.standardCelCompilerBuilder() + .addFunctionDeclarations( + CelFunctionDecl.newFunctionDeclaration( + "lateBoundFunc", + CelOverloadDecl.newGlobalOverload( + "lateBoundFunc_string", SimpleType.STRING, SimpleType.STRING))) + .build(); + CelLiteRuntime celRuntime = CelLiteRuntimeFactory.newLiteRuntimeBuilder().build(); + CelAbstractSyntaxTree ast = celCompiler.compile("lateBoundFunc('hello')").getAst(); + + String result = + (String) + celRuntime + .createProgram(ast) + .eval( + ImmutableMap.of(), + CelLateFunctionBindings.from( + CelFunctionBinding.from( + "lateBoundFunc_string", String.class, arg -> arg + " world"))); + + assertThat(result).isEqualTo("hello world"); + } } diff --git a/testing/src/test/resources/protos/BUILD.bazel b/testing/src/test/resources/protos/BUILD.bazel index cd80405c8..14f18d3e2 100644 --- a/testing/src/test/resources/protos/BUILD.bazel +++ b/testing/src/test/resources/protos/BUILD.bazel @@ -65,7 +65,6 @@ java_lite_proto_cel_library( java_lite_proto_cel_library_impl( name = "multi_file_cel_java_proto", - debug = True, java_descriptor_class_suffix = "CelDescriptor", java_proto_library_dep = ":multi_file_java_proto", deps = [