diff --git a/codelab/BUILD b/codelab/BUILD index 69c2825e2..9449b7f73 100644 --- a/codelab/BUILD +++ b/codelab/BUILD @@ -44,18 +44,21 @@ cc_library( "nobuilder", ], deps = [ - "//eval/public:activation", - "//eval/public:builtin_func_registrar", - "//eval/public:cel_expr_builder_factory", - "//eval/public:cel_expression", - "//eval/public:cel_options", - "//eval/public:cel_value", + "//common:ast", + "//common:minimal_descriptor_pool", + "//common:value", + "//compiler", + "//compiler:compiler_factory", + "//compiler:standard_library", "//internal:status_macros", - "//parser", + "//runtime", + "//runtime:activation", + "//runtime:runtime_builder", + "//runtime:runtime_options", + "//runtime:standard_runtime_builder_factory", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", - "@com_google_cel_spec//proto/cel/expr:syntax_cc_proto", "@com_google_protobuf//:protobuf", ], ) @@ -76,22 +79,26 @@ cc_library( srcs = ["exercise2.cc"], hdrs = ["exercise2.h"], deps = [ - ":cel_compiler", + "//checker:type_checker_builder", + "//checker:validation_result", + "//common:ast", + "//common:decl", + "//common:minimal_descriptor_pool", + "//common:type", + "//common:value", "//compiler", "//compiler:compiler_factory", "//compiler:standard_library", - "//eval/public:activation", - "//eval/public:builtin_func_registrar", - "//eval/public:cel_expr_builder_factory", - "//eval/public:cel_expression", - "//eval/public:cel_options", - "//eval/public:cel_value", "//internal:status_macros", + "//runtime", + "//runtime:activation", + "//runtime:bind_proto_to_activation", + "//runtime:runtime_builder", + "//runtime:runtime_options", + "//runtime:standard_runtime_builder_factory", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", - "@com_google_cel_spec//proto/cel/expr:checked_cc_proto", - "@com_google_cel_spec//proto/cel/expr:syntax_cc_proto", "@com_google_googleapis//google/rpc/context:attribute_context_cc_proto", "@com_google_protobuf//:protobuf", ], @@ -125,42 +132,30 @@ cc_test( ) cc_library( - name = "cel_compiler", - hdrs = ["cel_compiler.h"], + name = "checked_expr_conversion_example", + hdrs = ["checked_expr_conversion_example.h"], deps = [ - "//checker:validation_result", + "//common:ast", "//common:ast_proto", - "//compiler", "//internal:status_macros", - "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", - "@com_google_absl//absl/strings", "@com_google_cel_spec//proto/cel/expr:checked_cc_proto", ], ) cc_test( - name = "cel_compiler_test", - srcs = ["cel_compiler_test.cc"], + name = "checked_expr_conversion_example_test", + srcs = ["checked_expr_conversion_example_test.cc"], deps = [ - ":cel_compiler", - "//common:decl", - "//common:type", + ":checked_expr_conversion_example", + "//checker:validation_result", "//compiler", "//compiler:compiler_factory", "//compiler:standard_library", - "//eval/public:activation", - "//eval/public:activation_bind_helper", - "//eval/public:builtin_func_registrar", - "//eval/public:cel_expr_builder_factory", - "//eval/public:cel_function_adapter", - "//eval/public:cel_value", - "//eval/public/testing:matchers", "//internal:testing", - "@com_google_absl//absl/log:absl_check", "@com_google_absl//absl/status", "@com_google_absl//absl/status:status_matchers", - "@com_google_googleapis//google/rpc/context:attribute_context_cc_proto", + "@com_google_cel_spec//proto/cel/expr:checked_cc_proto", "@com_google_protobuf//:protobuf", ], ) @@ -170,22 +165,28 @@ cc_library( srcs = ["exercise4.cc"], hdrs = ["exercise4.h"], deps = [ - ":cel_compiler", + "//checker:type_checker_builder", + "//checker:validation_result", + "//common:ast", + "//common:decl", + "//common:minimal_descriptor_pool", + "//common:type", + "//common:value", "//compiler", "//compiler:compiler_factory", "//compiler:standard_library", - "//eval/public:activation", - "//eval/public:activation_bind_helper", - "//eval/public:builtin_func_registrar", - "//eval/public:cel_expr_builder_factory", - "//eval/public:cel_expression", - "//eval/public:cel_options", - "//eval/public:cel_value", "//internal:status_macros", + "//runtime", + "//runtime:activation", + "//runtime:bind_proto_to_activation", + "//runtime:function_adapter", + "//runtime:runtime_builder", + "//runtime:runtime_options", + "//runtime:standard_runtime_builder_factory", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", - "@com_google_cel_spec//proto/cel/expr:checked_cc_proto", + "@com_google_absl//absl/types:optional", "@com_google_googleapis//google/rpc/context:attribute_context_cc_proto", "@com_google_protobuf//:protobuf", ], @@ -270,6 +271,7 @@ cc_library( deps = [ ":network_functions", "//checker:validation_result", + "//common:ast", "//common:decl", "//common:minimal_descriptor_pool", "//common:type", diff --git a/codelab/README.md b/codelab/README.md index 96f7598ba..d8daff550 100644 --- a/codelab/README.md +++ b/codelab/README.md @@ -1,42 +1,59 @@ # What is CEL? -Common Expression Language (CEL) is an expression language that’s fast, portable, and safe to execute in performance-critical applications. CEL is designed to be embedded in an application, with application-specific extensions, and is ideal for extending declarative configurations that your applications might already use. + +Common Expression Language (CEL) is an expression language that’s fast, +portable, and safe to execute in performance-critical applications. CEL is +designed to be embedded in an application, with application-specific extensions, +and is ideal for extending declarative configurations that your applications +might already use. ## What is covered in this Codelab? -This codelab is aimed at developers who would like to learn CEL to use services that already support CEL. This Codelab covers common use cases. This codelab doesn't cover how to integrate CEL into your own project. For a more in-depth look at the language, semantics, and features see the [CEL Language Definition on GitHub](https://github.com/google/cel-spec). + +This codelab is aimed at developers who would like to learn CEL to use services +that already support CEL. This Codelab covers common use cases. This codelab +doesn't cover how to integrate CEL into your own project. For a more in-depth +look at the language, semantics, and features see the +[CEL Language Definition on GitHub](https://github.com/google/cel-spec). Some key areas covered are: -* [Hello, World: Using CEL to evaluate a String](#hello-world) -* [Creating variables](#creating-variables) -* [Commutative logical AND/OR](#logical-andor) -* [Adding custom functions](#custom-functions) +* [Hello, World: Using CEL to evaluate a String](#hello-world) +* [Creating variables](#creating-variables) +* [Commutative logical AND/OR](#logical-andor) +* [Adding custom functions](#custom-functions) ### Prerequisites -This codelab builds upon a basic understanding of Protocol Buffers and C++. -If you're not familiar with Protocol Buffers, the first exercise will give you a sense of how CEL works, but because the more advanced examples use Protocol Buffers as the input into CEL, they may be harder to understand. Consider working through one of these tutorials, first. See the devsite for [Protocol Buffers](https://protobuf.dev). +This codelab builds upon a basic understanding of Protocol Buffers and C++. -Notes on portability: Protocol Buffers are not required to use CEL -generally, but the C++ implementation has a hard dependency on the library -and some APIs reference protobuf types directly. Automated builds test -against gcc9 and clang11 on linux. We accept requests for portability -fixes for other OSes and compilers, but don't actively maintain support at -this time. A simple Docker file is provided as a reference for a known good -environment configuration for running the codelab solutions. +If you're not familiar with Protocol Buffers, the first exercise will give you a +sense of how CEL works, but because the more advanced examples use Protocol +Buffers as the input into CEL, they may be harder to understand. Consider +working through one of these tutorials, first. See the devsite for +[Protocol Buffers](https://protobuf.dev). + +Notes on portability: Protocol Buffers are not required to use CEL generally, +but the C++ implementation has a hard dependency on the library and some APIs +reference protobuf types directly. Automated builds test against gcc10 and +clang14 on linux, a recent visual code version on Windows, and a recent xcode +version on MacOS. We accept requests for portability fixes for other OSes and +compilers, but don't actively maintain support at this time. A simple Docker +file is provided as a reference for a known good environment configuration for +running the codelab solutions. What you'll need: -- Git -- Bazel -- C/C++ Compiler (GCC, Clang, Visual Studio). -- Optional: bazelisk is a wrapper around bazel that simplifies version - management. If using, substitute all bazel commands below with `bazelisk`. +- Git +- Bazel +- C/C++ Compiler (GCC, Clang, Visual Studio). +- Optional: bazelisk is a wrapper around bazel that simplifies version + management. If using, substitute all bazel commands below with `bazelisk`. ## GitHub Setup GitHub Repo: -The code for this codelab lives in the `codelab` folder of the cel-cpp repo. The solution is available in the `codelab/solution` folder of the same repo. +The code for this codelab lives in the `codelab` folder of the cel-cpp repo. The +solutions are available in the `codelab/solution` folder of the same repo. Clone and cd into the repo: @@ -52,68 +69,74 @@ bazel build //codelab:all ``` ## Hello, World -In the tried and true tradition of all programming languages, let's start with "Hello, World!". + +In the tried and true tradition of all programming languages, let's start with +"Hello, World!". Update exercise1.cc with the following: Using declarations: ```c++ -using ::google::api::expr::parser::Parse; -using ::google::api::expr::runtime::Activation; -using ::google::api::expr::runtime::CelExpression; -using ::google::api::expr::runtime::CelExpressionBuilder; -using ::google::api::expr::runtime::CelValue; -using ::google::api::expr::runtime::CreateCelExpressionBuilder; -using ::google::api::expr::runtime::InterpreterOptions; -using ::google::api::expr::runtime::RegisterBuiltinFunctions; +using ::cel::Activation; +using ::cel::Compiler; +using ::cel::CompilerBuilder; +using ::cel::Program; +using ::cel::Runtime; +using ::cel::RuntimeBuilder; +using ::cel::RuntimeOptions; +using ::cel::ValidationResult; +using ::cel::Value; ``` Implementation: ```c++ -absl::StatusOr ParseAndEvaluate(absl::string_view cel_expr) -{ +absl::StatusOr ParseAndEvaluate(absl::string_view cel_expr) { // === Start Codelab === - // Setup a default environment for building expressions. - InterpreterOptions options; - std::unique_ptr builder = - CreateCelExpressionBuilder(options); - + // Setup a default compiler for compiling expressions. + CEL_ASSIGN_OR_RETURN( + std::unique_ptr compiler_builder, + cel::NewCompilerBuilder(cel::GetMinimalDescriptorPool())); CEL_RETURN_IF_ERROR( - RegisterBuiltinFunctions(builder->GetRegistry(), options)); - - // Parse the expression. This is fine for codelabs, but this skips the type - // checking phase. It won't check that functions and variables are available - // in the environment, and it won't handle certain ambiguous identifier - // expressions (e.g. container lookup vs namespaced name, packaged function - // vs. receiver call style function). - ParsedExpr parsed_expr; - CEL_ASSIGN_OR_RETURN(parsed_expr, Parse(cel_expr)); - - // The evaluator uses a proto Arena for incidental allocations during - // evaluation. + compiler_builder->AddLibrary(cel::StandardCompilerLibrary())); + CEL_ASSIGN_OR_RETURN(std::unique_ptr compiler, + std::move(compiler_builder)->Build()); + + // Compile the expression. + CEL_ASSIGN_OR_RETURN(ValidationResult validation_result, + compiler->Compile(cel_expr)); + if (!validation_result.IsValid()) { + return absl::InvalidArgumentError(validation_result.FormatError()); + } + + // Setup a standard runtime for evaluating expressions. + RuntimeOptions options; + CEL_ASSIGN_OR_RETURN( + RuntimeBuilder runtime_builder, + cel::CreateStandardRuntimeBuilder(cel::GetMinimalDescriptorPool(), + options)); + CEL_ASSIGN_OR_RETURN(std::unique_ptr runtime, + std::move(runtime_builder).Build()); + + // Build the executable program from the compiled AST. + CEL_ASSIGN_OR_RETURN(std::unique_ptr ast, + validation_result.ReleaseAst()); + CEL_ASSIGN_OR_RETURN(std::unique_ptr program, + runtime->CreateProgram(std::move(ast))); + + // The evaluator uses a proto Arena for allocations during evaluation. proto2::Arena arena; - // The activation provides variables and functions that are bound into the + // The activation provides variables and functions bound into the // expression environment. In this example, there's no context expected, so - // we just provide an empty one to the evaluator. + // we provide an empty activation. Activation activation; - // Build the expression plan. This assumes that the source expression AST and - // the expression builder outlives the CelExpression object. - CEL_ASSIGN_OR_RETURN(std::unique_ptr expression_plan, - builder->CreateExpression(&parsed_expr.expr(), - &parsed_expr.source_info())); - - // Actually run the expression plan. We don't support any environment - // variables at the moment so just use an empty activation. - CEL_ASSIGN_OR_RETURN(CelValue result, - expression_plan->Evaluate(activation, &arena)); - - // Convert the result to a c++ string. CelValues may reference instances from - // either the input expression, or objects allocated on the arena, so we need - // to pass ownership (in this case by copying to a new instance and returning - // that). + // Run the program. + CEL_ASSIGN_OR_RETURN(Value result, + program->Evaluate(&arena, activation)); + + // Convert the result to a C++ string. return ConvertResult(result); // === End Codelab === } @@ -129,151 +152,176 @@ You can add additional test cases or experiment with different return types. Hello, World! Now, let's break down what's happening. - ### Setup the Environment -CEL applications evaluate an expression against an environment. -The standard CEL environment supports all of the types, operators, functions, and macros defined within the language spec. The environment can be customized by providing options to disable macros, declare custom variables and functions, etc. +CEL applications compile and evaluate an expression against an environment. + +The standard CEL environment supports all of the types, operators, functions, +and macros defined within the language spec. The compiler and runtime objects +can be customized with other variables, types, and functions. -An ExpressionBuilder maintains C++ evaluation environment. This creates a builder with the standard environment. +A `CompilerBuilder` configures the compilation environment, while a +`RuntimeBuilder` configures the execution environment: ```c++ -#include "eval/public/builtin_func_registrar.h" -#include "eval/public/cel_expr_builder_factory.h" -#include "eval/public/cel_options.h" +#include "compiler/compiler_factory.h" +#include "compiler/standard_library.h" +#include "runtime/runtime_options.h" +#include "runtime/standard_runtime_builder_factory.h" ... -// Setup a default environment for building expressions. - -// Breaking behavior changes and optional features are controlled by -// InterpreterOptions. -InterpreterOptions options; - -// Environment used for planning and evaluating expressions is managed by an -// ExpressionBuilder. -std::unique_ptr builder = - CreateCelExpressionBuilder(options); - -// Add standard function bindings e.g. for +,-,==,||,&& operators. -// Custom functions (implementing the CelFunction interface) can be added to the -// registry similarly. +// Create a compiler configured with the standard CEL library. +CEL_ASSIGN_OR_RETURN( + std::unique_ptr compiler_builder, + cel::NewCompilerBuilder(cel::GetMinimalDescriptorPool())); CEL_RETURN_IF_ERROR( - RegisterBuiltinFunctions(builder->GetRegistry(), options)); + compiler_builder->AddLibrary(cel::StandardCompilerLibrary())); +CEL_ASSIGN_OR_RETURN(std::unique_ptr compiler, + std::move(compiler_builder)->Build()); + +// Create a standard runtime. +cel::RuntimeOptions options; +CEL_ASSIGN_OR_RETURN( + cel::RuntimeBuilder runtime_builder, + cel::CreateStandardRuntimeBuilder(cel::GetMinimalDescriptorPool(), + options)); +CEL_ASSIGN_OR_RETURN(std::unique_ptr runtime, + std::move(runtime_builder).Build()); ``` -### Parse -After the environment is configured, you can parse and check the expressions: +### Compile + +After the compiler is configured, you can compile source expressions into a +checked `cel::Ast`: ```c++ -#include "google/api/expr/syntax.proto.h" -#include "parser/parser.h" +#include "checker/validation_result.h" // ... -ASSIGN_OR_RETURN(google::api::expr::ParsedExpr parsed_expr, google::api::expr::parser::Parse(cel_expr)); +CEL_ASSIGN_OR_RETURN(cel::ValidationResult validation_result, + compiler->Compile(cel_expr)); +if (!validation_result.IsValid()) { + return absl::InvalidArgumentError(validation_result.FormatError()); +} ``` -The C++ parser is a stand-alone utility. It's not aware of the evaluation environment and does not perform any semantic checks on the expression. A status is returned if the input string isn't a syntactically valid CEL expression or if it exceeds the configured complexity limits (see cel::ParserOptions and default limits). +`compiler->Compile(...)` parses and type-checks the expression in one step, +returning a `cel::ValidationResult`. If the expression is valid +(`validation_result.IsValid()`), the resulting `cel::Ast` can be extracted using +`validation_result.ReleaseAst()`. + +> Note: If you need to interface with external systems that store or transmit +> serialized protobuf ASTs (`google::api::expr::CheckedExpr`), conversion +> examples are available in `checked_expr_conversion_example.h` +> (`cel::AstToCheckedExpr` and `cel::CreateAstFromCheckedExpr`). ### Evaluate -After the expressions have been parsed and checked into an AST representation, it can be converted into an evaluable program whose function bindings and evaluation modes can be customized depending on the stack you are using. -Once a CEL expression is planned, it can be evaluated against an evaluation context (an activation). The evaluation result will be either a value or an error state. -The InterpreterOptions to create the expression plan are honored at evaluation. C++ uses the proto representation of either a parsed `google.api.expr.ParsedExpr` or parsed and type-checked `google.api.expr.CheckedExpr` AST directly. -Once a CEL program is planned (represented by a `google::api::expr::runtime::CelExpression`), it can be evaluated against an `google::api::expr::runtime::Activation`. The Activation provides per-evaluation bindings for variables and functions in the expression's environment. + +Once a `cel::Ast` is compiled, `runtime->CreateProgram(...)` creates an +executable `cel::Program`. The program is then evaluated against a +`cel::Activation`, which provides per-evaluation bindings for variables and +functions in the environment: ```c++ +#include "runtime/activation.h" #include "third_party/protobuf/arena.h" -#include "eval/public/activation.h" -#include "eval/public/cel_expression.h" -#include "eval/public/cel_value.h" -#include "internal/status_macros.h" -#include "parser/parser.h" ... -// The evaluator uses a proto Arena for incidental allocations during -// evaluation. +// Build the executable program from the compiled AST. +CEL_ASSIGN_OR_RETURN(std::unique_ptr ast, + validation_result.ReleaseAst()); +CEL_ASSIGN_OR_RETURN(std::unique_ptr program, + runtime->CreateProgram(std::move(ast))); + +// Run the program against an activation and proto Arena. +cel::Activation activation; proto2::Arena arena; -// The activation provides variables and functions that are bound into the -// expression environment. In this example, there's no context expected, so -// we just provide an empty one to the evaluator. -Activation activation; - -// Build the expression plan. This assumes that the source expression AST and -// the expression builder outlives the CelExpression object. -CEL_ASSIGN_OR_RETURN(std::unique_ptr expression_plan, - builder->CreateExpression(&parsed_expr.expr(), - &parsed_expr.source_info())); - -// Actually run the expression plan. We don't support any environment -// variables at the moment so just use an empty activation. -CEL_ASSIGN_OR_RETURN(CelValue result, - expression_plan->Evaluate(activation, &arena)); - -// Convert the result to a C++ string. CelValues may reference instances from -// either the input expression, or objects allocated on the arena, so we need -// to pass ownership (in this case by copying to a new instance and returning -// that). +CEL_ASSIGN_OR_RETURN(cel::Value result, + program->Evaluate(&arena, activation)); + return ConvertResult(result); ``` ## Creating variables -Most CEL applications will declare variables that can be referenced within expressions. Variables declarations specify a name and a type. A variable's type may either be a CEL builtin type, a protocol buffer well-known type, or any protobuf message type so long as its descriptor is also provided to CEL. -At runtime, the hosting program binds instances of variables to the evaluation context (using the variable name as a key). +Most CEL applications declare variables that can be referenced within +expressions. Variable declarations specify a name and a type. A variable's type +may be a CEL built-in type, a protocol buffer well-known type, or any protobuf +message type so long as its descriptor is registered. + +At runtime, the hosting program binds variable values into `cel::Activation`. -For the C++ evaluator at runtime, the values are managed by the `google::api::expr::runtime::CelValue` type, a variant over the C++ representations of supported CEL types. +For the C++ evaluator at runtime, values are represented by `cel::Value`. Update exercise2.cc: ```c++ -// The Variables exercise shows how to declare and use variables in expressions. -// There are two overloads for preparing an expression either granularly for -// individual variables or using a helper to bind a context proto. - -// The first overload shows manually populating individual variables in the -// evaluation environment. This allows cel_expr to reference 'bool_var'. -absl::StatusOr ParseAndEvaluate(absl::string_view cel_expr, - bool bool_var) { - Activation activation; +absl::StatusOr CompileAndEvaluateWithBoolVar(absl::string_view cel_expr, + bool bool_var) { + CEL_ASSIGN_OR_RETURN(std::unique_ptr compiler, + MakeCelCompiler()); + CEL_ASSIGN_OR_RETURN(cel::ValidationResult validation_result, + compiler->Compile(cel_expr)); + if (!validation_result.IsValid()) { + return absl::InvalidArgumentError(validation_result.FormatError()); + } + + cel::Activation activation; proto2::Arena arena; // === Start Codelab === - activation.InsertValue("bool_var", CelValue::CreateBool(bool_var)); - // === End Codelab === - - return ParseAndEvaluate(cel_expr, activation, &arena); + activation.InsertOrAssignValue("bool_var", cel::BoolValue(bool_var)); + CEL_ASSIGN_OR_RETURN(std::unique_ptr ast, + validation_result.ReleaseAst()); + return EvalAst(std::move(ast), activation, &arena); } ``` -Run the following to check your work. You should have fixed the first two test cases in exercise2_test.cc. +Run the following to check your work. You should have fixed the first two test +cases in exercise2_test.cc. ``` bazel test //codelab:exercise2_test ``` -The second overload uses a protocol buffer message to represent the environment variables. For this use case, there is a helper to automatically bind in fields from a top level message (see `google::api::expr::runtime::BindProtoToActivation`). In this example, we assume that unset fields should be bound to default values. +The second overload uses a protocol buffer message (`AttributeContext`) to +represent the environment variables. For this use case, +`cel::BindProtoToActivation` automatically binds fields from a top-level message +into the activation (`#include +"runtime/bind_proto_to_activation.h"`): ```c++ -#include "eval/public/activation_bind_helper.h" +#include "runtime/bind_proto_to_activation.h" // ... -using ::google::api::expr::runtime::ProtoUnsetFieldOptions; -// ... -absl::StatusOr ParseAndEvaluate(absl::string_view cel_expr, - const AttributeContext& context) { - Activation activation; - google::protobuf::Arena arena; +absl::StatusOr CompileAndEvaluateWithContext( + absl::string_view cel_expr, const AttributeContext& context) { + // ... compile cel_expr ... + cel::Activation activation; + proto2::Arena arena; // === Start Codelab === - - CEL_RETURN_IF_ERROR(BindProtoToActivation( - &context, &arena, &activation, ProtoUnsetFieldOptions::kBindDefault)); + CEL_RETURN_IF_ERROR(cel::BindProtoToActivation( + context, cel::BindProtoUnsetFieldBehavior::kBindDefaultValue, + proto2::DescriptorPool::generated_pool(), + proto2::MessageFactory::generated_factory(), &arena, &activation)); // === End Codelab === - return ParseAndEvaluate(cel_expr, activation, &arena); + CEL_ASSIGN_OR_RETURN(std::unique_ptr ast, + validation_result.ReleaseAst()); + return EvalAst(std::move(ast), activation, &arena); } ``` -Note: You can experiment with unset values and the alternative bind option for BindProtoToActivation. With ProtoUnsetFieldOptions::kSkip unset values will not be bound at all, and accesses in expressions will cause errors. +Note: You can experiment with unset values using +`cel::BindProtoUnsetFieldBehavior::kBindDefaultValue` or `kSkip`. With `kSkip`, +unset protobuf fields will not be bound at all, and accesses in expressions will +result in errors. ## Logical And/Or -One of CEL's more distinctive features is its use of commutative logical operators. Either side of a conditional branch can short-circuit the evaluation, even in the face of errors or partial input. -Note: If you are skipping ahead, copy the solution for exercise2 -- we'll be using it to test the behavior of some simple expressions. -exercise3_test.cc lists truth tables for simple expressions using the 'or', 'and', and 'ternary' operators. +One of CEL's more distinctive features is its use of commutative logical +operators. Either side of a conditional branch can short-circuit the evaluation, +even in the face of errors or partial input. Note: If you are skipping ahead, +copy the solution for exercise2 -- we'll be using it to test the behavior of +some simple expressions. + +exercise3_test.cc lists truth tables for simple expressions using the 'or', +'and', and 'ternary' operators. Running the following should result in some failing expectations. @@ -309,7 +357,8 @@ TEST(Exercise3Var, LogicalOr) { } ``` -Updating the two failing cases "true || (1 / 0 > 2)" and "(1 / 0 > 2) || true" should fix this test: +Updating the two failing cases "true || (1 / 0 > 2)" and "(1 / 0 > 2) || true" +should fix this test: ```c++ // ... @@ -323,6 +372,15 @@ Updating the two failing cases "true || (1 / 0 > 2)" and "(1 / 0 > 2) || true" s IsOkAndHolds(true)); ``` -You can examine the other tests for other cases for corresponding behavior for the 'and' and ternary operators. +You can examine the other tests for other cases for corresponding behavior for +the 'and' and ternary operators. + +CEL finds an evaluation order which gives results whenever possible, ignoring +errors or even missing data that might occur in other evaluation orders. +Applications like IAM conditions rely on this property to minimize the cost of +evaluation, deferring the gathering of expensive inputs when a result can be +reached without them. + +## Adding custom functions {#custom-functions} -CEL finds an evaluation order which gives results whenever possible, ignoring errors or even missing data that might occur in other evaluation orders. Applications like IAM conditions rely on this property to minimize the cost of evaluation, deferring the gathering of expensive inputs when a result can be reached without them. +See `exercise4.h` and `exercise4.cc`. Documentation to be added later. diff --git a/codelab/cel_compiler.h b/codelab/cel_compiler.h deleted file mode 100644 index 0ff2f699b..000000000 --- a/codelab/cel_compiler.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2022 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. - -#ifndef THIRD_PARTY_CEL_CPP_CODELAB_COMPILER_H_ -#define THIRD_PARTY_CEL_CPP_CODELAB_COMPILER_H_ - -#include "cel/expr/checked.pb.h" -#include "absl/status/status.h" -#include "absl/status/statusor.h" -#include "absl/strings/string_view.h" -#include "checker/validation_result.h" -#include "common/ast_proto.h" -#include "compiler/compiler.h" -#include "internal/status_macros.h" - -namespace cel_codelab { - -// Helper for compiling expression and converting to proto. -// -// Simplifies error handling for brevity in the codelab. -inline absl::StatusOr CompileToCheckedExpr( - const cel::Compiler& compiler, absl::string_view expr) { - CEL_ASSIGN_OR_RETURN(cel::ValidationResult result, compiler.Compile(expr)); - - if (!result.IsValid() || result.GetAst() == nullptr) { - return absl::InvalidArgumentError(result.FormatError()); - } - - cel::expr::CheckedExpr pb; - CEL_RETURN_IF_ERROR(cel::AstToCheckedExpr(*result.GetAst(), &pb)); - return pb; -}; - -} // namespace cel_codelab - -#endif // THIRD_PARTY_CEL_CPP_CODELAB_COMPILER_H_ diff --git a/codelab/cel_compiler_test.cc b/codelab/cel_compiler_test.cc deleted file mode 100644 index 635b4d54d..000000000 --- a/codelab/cel_compiler_test.cc +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2022 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. - -#include "codelab/cel_compiler.h" - -#include -#include - -#include "google/rpc/context/attribute_context.pb.h" -#include "absl/log/absl_check.h" -#include "absl/status/status.h" -#include "absl/status/status_matchers.h" -#include "common/decl.h" -#include "common/type.h" -#include "compiler/compiler.h" -#include "compiler/compiler_factory.h" -#include "compiler/standard_library.h" -#include "eval/public/activation.h" -#include "eval/public/activation_bind_helper.h" -#include "eval/public/builtin_func_registrar.h" -#include "eval/public/cel_expr_builder_factory.h" -#include "eval/public/cel_function_adapter.h" -#include "eval/public/cel_value.h" -#include "eval/public/testing/matchers.h" -#include "internal/testing.h" -#include "google/protobuf/arena.h" -#include "google/protobuf/descriptor.h" -#include "google/protobuf/message.h" - -namespace cel_codelab { -namespace { - -using ::absl_testing::IsOk; -using ::absl_testing::StatusIs; -using ::cel::BoolType; -using ::cel::MakeFunctionDecl; -using ::cel::MakeOverloadDecl; -using ::cel::MakeVariableDecl; -using ::cel::StringType; -using ::google::api::expr::runtime::Activation; -using ::google::api::expr::runtime::BindProtoToActivation; -using ::google::api::expr::runtime::CelValue; -using ::google::api::expr::runtime::CreateCelExpressionBuilder; -using ::google::api::expr::runtime::FunctionAdapter; -using ::google::api::expr::runtime::RegisterBuiltinFunctions; -using ::google::api::expr::runtime::test::IsCelBool; -using ::google::rpc::context::AttributeContext; -using ::testing::HasSubstr; - -std::unique_ptr MakeDefaultCompilerBuilder() { - google::protobuf::LinkMessageReflection(); - auto builder = - cel::NewCompilerBuilder(google::protobuf::DescriptorPool::generated_pool()); - ABSL_CHECK_OK(builder.status()); - - ABSL_CHECK_OK((*builder)->AddLibrary(cel::StandardCompilerLibrary())); - ABSL_CHECK_OK((*builder)->GetCheckerBuilder().AddContextDeclaration( - "google.rpc.context.AttributeContext")); - - return std::move(builder).value(); -} - -TEST(DefaultCompiler, Basic) { - ASSERT_OK_AND_ASSIGN(auto compiler, MakeDefaultCompilerBuilder()->Build()); - EXPECT_THAT(compiler->Compile("1 < 2").status(), IsOk()); -} - -TEST(DefaultCompiler, AddFunctionDecl) { - auto builder = MakeDefaultCompilerBuilder(); - ASSERT_OK_AND_ASSIGN( - cel::FunctionDecl decl, - MakeFunctionDecl("IpMatch", - MakeOverloadDecl("IpMatch_string_string", BoolType(), - StringType(), StringType()))); - EXPECT_THAT(builder->GetCheckerBuilder().AddFunction(decl), IsOk()); - - ASSERT_OK_AND_ASSIGN(auto compiler, builder->Build()); - - EXPECT_THAT(CompileToCheckedExpr( - *compiler, "IpMatch('255.255.255.255', '255.255.255.255')") - .status(), - IsOk()); - EXPECT_THAT( - CompileToCheckedExpr(*compiler, "IpMatch('255.255.255.255', 123436)") - .status(), - StatusIs(absl::StatusCode::kInvalidArgument, - HasSubstr("no matching overload"))); -} - -TEST(DefaultCompiler, EndToEnd) { - google::protobuf::Arena arena; - - auto compiler_builder = MakeDefaultCompilerBuilder(); - ASSERT_OK_AND_ASSIGN( - cel::FunctionDecl func_decl, - MakeFunctionDecl("MyFunc", MakeOverloadDecl("MyFunc", BoolType()))); - ASSERT_THAT(compiler_builder->GetCheckerBuilder().AddFunction(func_decl), - IsOk()); - - ASSERT_THAT(compiler_builder->GetCheckerBuilder().AddVariable( - MakeVariableDecl("my_var", BoolType())), - IsOk()); - - ASSERT_OK_AND_ASSIGN(auto compiler, compiler_builder->Build()); - - ASSERT_OK_AND_ASSIGN( - auto expr, - CompileToCheckedExpr( - *compiler, - "(my_var || MyFunc()) && request.host == 'www.google.com'")); - - auto builder = - CreateCelExpressionBuilder(google::protobuf::DescriptorPool::generated_pool(), - google::protobuf::MessageFactory::generated_factory()); - ASSERT_THAT(RegisterBuiltinFunctions(builder->GetRegistry()), IsOk()); - ASSERT_THAT(FunctionAdapter::CreateAndRegister( - "MyFunc", false, [](google::protobuf::Arena*) { return true; }, - builder->GetRegistry()), - IsOk()); - - ASSERT_OK_AND_ASSIGN(auto plan, builder->CreateExpression(&expr)); - - AttributeContext context; - context.mutable_request()->set_host("www.google.com"); - Activation activation; - ASSERT_THAT(BindProtoToActivation(&context, &arena, &activation), IsOk()); - activation.InsertValue("my_var", CelValue::CreateBool(false)); - - ASSERT_OK_AND_ASSIGN(CelValue result, plan->Evaluate(activation, &arena)); - - EXPECT_THAT(result, IsCelBool(true)); -} - -} // namespace -} // namespace cel_codelab diff --git a/codelab/checked_expr_conversion_example.h b/codelab/checked_expr_conversion_example.h new file mode 100644 index 000000000..fc3783ddd --- /dev/null +++ b/codelab/checked_expr_conversion_example.h @@ -0,0 +1,59 @@ +// Copyright 2026 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. + +#ifndef THIRD_PARTY_CEL_CPP_CODELAB_CHECKED_EXPR_CONVERSION_EXAMPLE_H_ +#define THIRD_PARTY_CEL_CPP_CODELAB_CHECKED_EXPR_CONVERSION_EXAMPLE_H_ + +#include + +#include "cel/expr/checked.pb.h" +#include "absl/status/statusor.h" +#include "common/ast.h" +#include "common/ast_proto.h" +#include "internal/status_macros.h" + +namespace cel_codelab { + +// Examples demonstrating how to convert between protobuf-based AST +// representations (cel::expr::CheckedExpr) and modern runtime AST +// representations (cel::Ast). +// +// When evaluating expressions with modern `cel::Runtime`, compilation via +// `cel::Compiler::Compile` returns a `cel::ValidationResult` holding a +// `cel::Ast` that can be passed directly to `runtime->CreateProgram(...)`. +// +// However, when working with external services or storage layers that persist +// or transmit serialized protobuf ASTs (`cel::expr::CheckedExpr`), +// conversion functions in `common/ast_proto.h` can be used +// to move between `cel::Ast` and `cel::expr::CheckedExpr`. + +// Example: Convert a runtime `cel::Ast` to a `cel::expr::CheckedExpr` +// proto. +inline absl::StatusOr ConvertAstToCheckedExpr( + const cel::Ast& ast) { + cel::expr::CheckedExpr checked_expr; + CEL_RETURN_IF_ERROR(cel::AstToCheckedExpr(ast, &checked_expr)); + return checked_expr; +} + +// Example: Convert a `cel::expr::CheckedExpr` proto to a runtime +// `cel::Ast`. +inline absl::StatusOr> ConvertCheckedExprToAst( + const cel::expr::CheckedExpr& checked_expr) { + return cel::CreateAstFromCheckedExpr(checked_expr); +} + +} // namespace cel_codelab + +#endif // THIRD_PARTY_CEL_CPP_CODELAB_CHECKED_EXPR_CONVERSION_EXAMPLE_H_ diff --git a/codelab/checked_expr_conversion_example_test.cc b/codelab/checked_expr_conversion_example_test.cc new file mode 100644 index 000000000..96469dae9 --- /dev/null +++ b/codelab/checked_expr_conversion_example_test.cc @@ -0,0 +1,60 @@ +// Copyright 2026 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. + +#include "codelab/checked_expr_conversion_example.h" + +#include +#include + +#include "cel/expr/checked.pb.h" +#include "absl/status/status.h" +#include "absl/status/status_matchers.h" +#include "checker/validation_result.h" +#include "compiler/compiler.h" +#include "compiler/compiler_factory.h" +#include "compiler/standard_library.h" +#include "internal/testing.h" +#include "google/protobuf/descriptor.h" + +namespace cel_codelab { +namespace { + +using ::absl_testing::IsOk; + +TEST(CheckedExprConversionExampleTest, ConvertAstToCheckedExprAndBack) { + ASSERT_OK_AND_ASSIGN( + std::unique_ptr builder, + cel::NewCompilerBuilder(google::protobuf::DescriptorPool::generated_pool())); + ASSERT_THAT(builder->AddLibrary(cel::StandardCompilerLibrary()), IsOk()); + ASSERT_OK_AND_ASSIGN(std::unique_ptr compiler, + std::move(builder)->Build()); + + ASSERT_OK_AND_ASSIGN(cel::ValidationResult validation_result, + compiler->Compile("1 + 2 == 3")); + ASSERT_TRUE(validation_result.IsValid()); + ASSERT_OK_AND_ASSIGN(std::unique_ptr ast, + validation_result.ReleaseAst()); + + // Convert cel::Ast to CheckedExpr proto + ASSERT_OK_AND_ASSIGN(cel::expr::CheckedExpr checked_expr, + ConvertAstToCheckedExpr(*ast)); + + // Convert CheckedExpr proto back to cel::Ast + ASSERT_OK_AND_ASSIGN(std::unique_ptr roundtrip_ast, + ConvertCheckedExprToAst(checked_expr)); + EXPECT_NE(roundtrip_ast, nullptr); +} + +} // namespace +} // namespace cel_codelab diff --git a/codelab/exercise1.cc b/codelab/exercise1.cc index de7ccf6e0..4b446e000 100644 --- a/codelab/exercise1.cc +++ b/codelab/exercise1.cc @@ -16,67 +16,69 @@ #include #include +#include -#include "cel/expr/syntax.pb.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" -#include "eval/public/activation.h" -#include "eval/public/builtin_func_registrar.h" -#include "eval/public/cel_expr_builder_factory.h" -#include "eval/public/cel_expression.h" -#include "eval/public/cel_options.h" -#include "eval/public/cel_value.h" +#include "common/minimal_descriptor_pool.h" +#include "common/value.h" +#include "compiler/compiler.h" +#include "compiler/compiler_factory.h" +#include "compiler/standard_library.h" #include "internal/status_macros.h" -#include "parser/parser.h" +#include "runtime/activation.h" +#include "runtime/runtime.h" +#include "runtime/runtime_options.h" +#include "runtime/standard_runtime_builder_factory.h" #include "google/protobuf/arena.h" namespace cel_codelab { namespace { -using ::google::api::expr::runtime::Activation; -using ::google::api::expr::runtime::CelValue; - -// Convert the CelResult to a C++ string if it is string typed. Otherwise, -// return invalid argument error. This takes a copy to avoid lifecycle concerns -// (the evaluator may represent strings as stringviews backed by the input -// expression). -absl::StatusOr ConvertResult(const CelValue& value) { - if (CelValue::StringHolder inner_value; value.GetValue(&inner_value)) { - return std::string(inner_value.value()); - } else { - return absl::InvalidArgumentError(absl::StrCat( - "expected string result got '", CelValue::TypeName(value.type()), "'")); +// Convert the cel::Value to a C++ string if it is string typed. Otherwise, +// return invalid argument error. +absl::StatusOr ConvertResult(const cel::Value& value) { + if (value.IsString()) { + return value.GetString().ToString(); } + return absl::InvalidArgumentError( + absl::StrCat("expected string result got '", value.GetTypeName(), "'")); } + } // namespace absl::StatusOr ParseAndEvaluate(absl::string_view cel_expr) { // === Start Codelab === - // Parse the expression using ::google::api::expr::parser::Parse; - // This will return a cel::expr::ParsedExpr message. + // 1. Setup a default compiler for compiling expressions: + // Use cel::NewCompilerBuilder(cel::GetMinimalDescriptorPool()) and add + // cel::StandardCompilerLibrary(). Build the cel::Compiler. + + // 2. Compile the expression using compiler->Compile(cel_expr). + // Check that the resulting validation_result.IsValid(). - // Setup a default environment for building expressions. - // std::unique_ptr builder = - // CreateCelExpressionBuilder(options); + // 3. Setup a standard runtime for evaluating expressions: + // Use cel::CreateStandardRuntimeBuilder(cel::GetMinimalDescriptorPool(), + // options) and build the cel::Runtime. - // Register standard functions. - // CEL_RETURN_IF_ERROR( - // RegisterBuiltinFunctions(builder->GetRegistry(), options)); + // 4. Create an executable program from the compiled AST: + // Use CEL_ASSIGN_OR_RETURN(std::unique_ptr ast, + // validation_result.ReleaseAst()) and + // runtime->CreateProgram(std::move(ast)). - // The evaluator uses a proto Arena for incidental allocations during - // evaluation. + // The evaluator uses a proto Arena for allocations during evaluation. google::protobuf::Arena arena; - // The activation provides variables and functions that are bound into the + // The activation provides variables and functions bound into the // expression environment. In this example, there's no context expected, so - // we just provide an empty one to the evaluator. - Activation activation; + // we provide an empty activation. + cel::Activation activation; + (void)arena; + (void)activation; - // Using the CelExpressionBuilder and the ParseExpr, create an execution plan - // (google::api::expr::runtime::CelExpression), evaluate, and return the - // result. Use the provided helper function ConvertResult to copy the value - // for return. + // 5. Evaluate the program and convert the result: + // Call program->Evaluate(&arena, activation) and pass the resulting + // cel::Value to ConvertResult. return absl::UnimplementedError("Not yet implemented"); // === End Codelab === } diff --git a/codelab/exercise10.cc b/codelab/exercise10.cc index 37eaa7642..c618a5988 100644 --- a/codelab/exercise10.cc +++ b/codelab/exercise10.cc @@ -95,8 +95,9 @@ absl::StatusOr CompileAndEvaluateExercise10(absl::string_view expression, return absl::InvalidArgumentError(checked->FormatError()); } + CEL_ASSIGN_OR_RETURN(std::unique_ptr ast, checked->ReleaseAst()); absl::StatusOr> program = - (*runtime)->CreateProgram(checked->ReleaseAst().value()); + (*runtime)->CreateProgram(std::move(ast)); if (!program.ok()) { return std::move(program).status(); diff --git a/codelab/exercise2.cc b/codelab/exercise2.cc index 373f63365..66004480a 100644 --- a/codelab/exercise2.cc +++ b/codelab/exercise2.cc @@ -15,25 +15,30 @@ #include "codelab/exercise2.h" #include +#include -#include "cel/expr/checked.pb.h" -#include "cel/expr/syntax.pb.h" #include "google/rpc/context/attribute_context.pb.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" -#include "codelab/cel_compiler.h" +#include "checker/type_checker_builder.h" +#include "checker/validation_result.h" +#include "common/ast.h" +#include "common/decl.h" +#include "common/minimal_descriptor_pool.h" +#include "common/type.h" +#include "common/value.h" #include "compiler/compiler.h" #include "compiler/compiler_factory.h" #include "compiler/standard_library.h" -#include "eval/public/activation.h" -#include "eval/public/builtin_func_registrar.h" -#include "eval/public/cel_expr_builder_factory.h" -#include "eval/public/cel_expression.h" -#include "eval/public/cel_options.h" -#include "eval/public/cel_value.h" #include "internal/status_macros.h" +#include "runtime/activation.h" +#include "runtime/bind_proto_to_activation.h" +#include "runtime/runtime.h" +#include "runtime/runtime_builder.h" +#include "runtime/runtime_options.h" +#include "runtime/standard_runtime_builder_factory.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" #include "google/protobuf/message.h" @@ -41,15 +46,6 @@ namespace cel_codelab { namespace { -using ::cel::expr::CheckedExpr; -using ::google::api::expr::runtime::Activation; -using ::google::api::expr::runtime::CelError; -using ::google::api::expr::runtime::CelExpression; -using ::google::api::expr::runtime::CelExpressionBuilder; -using ::google::api::expr::runtime::CelValue; -using ::google::api::expr::runtime::CreateCelExpressionBuilder; -using ::google::api::expr::runtime::InterpreterOptions; -using ::google::api::expr::runtime::RegisterBuiltinFunctions; using ::google::rpc::context::AttributeContext; absl::StatusOr> MakeCelCompiler() { @@ -65,45 +61,41 @@ absl::StatusOr> MakeCelCompiler() { // === Start Codelab === // Add 'AttributeContext' as a context message to the type checker and a // boolean variable 'bool_var'. Relevant functions are on the - // TypeCheckerBuilder class (see CompilerBuilder::GetCheckerBuilder). + // cel::TypeCheckerBuilder class (see builder->GetCheckerBuilder()). // // We're reusing the same compiler for both evaluation paths here for brevity, // but it's likely a better fit to configure a separate compiler per use case. // === End Codelab === - return builder->Build(); + return std::move(builder)->Build(); } -// Parse a cel expression and evaluate it against the given activation and -// arena. -absl::StatusOr EvalCheckedExpr(const CheckedExpr& checked_expr, - const Activation& activation, - google::protobuf::Arena* arena) { - // Setup a default environment for building expressions. - InterpreterOptions options; - std::unique_ptr builder = CreateCelExpressionBuilder( - google::protobuf::DescriptorPool::generated_pool(), - google::protobuf::MessageFactory::generated_factory(), options); - CEL_RETURN_IF_ERROR( - RegisterBuiltinFunctions(builder->GetRegistry(), options)); - - // Note, the expression_plan below is reusable for different inputs, but we - // create one just in time for evaluation here. - CEL_ASSIGN_OR_RETURN(std::unique_ptr expression_plan, - builder->CreateExpression(&checked_expr)); - - CEL_ASSIGN_OR_RETURN(CelValue result, - expression_plan->Evaluate(activation, arena)); - - if (bool value; result.GetValue(&value)) { - return value; - } else if (const CelError * value; result.GetValue(&value)) { - return *value; - } else { - return absl::InvalidArgumentError(absl::StrCat( - "expected 'bool' result got '", result.DebugString(), "'")); +// Evaluate a runtime cel::Ast against the given activation and arena. +absl::StatusOr EvalAst(std::unique_ptr ast, + const cel::Activation& activation, + google::protobuf::Arena* arena) { + cel::RuntimeOptions options; + CEL_ASSIGN_OR_RETURN(cel::RuntimeBuilder runtime_builder, + cel::CreateStandardRuntimeBuilder( + google::protobuf::DescriptorPool::generated_pool(), options)); + CEL_ASSIGN_OR_RETURN(std::unique_ptr runtime, + std::move(runtime_builder).Build()); + + CEL_ASSIGN_OR_RETURN(std::unique_ptr program, + runtime->CreateProgram(std::move(ast))); + + CEL_ASSIGN_OR_RETURN(cel::Value result, program->Evaluate(arena, activation)); + + if (result.IsBool()) { + return result.GetBool().NativeValue(); } + if (result.IsError()) { + return result.GetError().ToStatus(); + } + return absl::InvalidArgumentError( + absl::StrCat("expected 'bool' result got '", result.GetTypeName(), "'")); } + } // namespace absl::StatusOr CompileAndEvaluateWithBoolVar(absl::string_view cel_expr, @@ -111,16 +103,21 @@ absl::StatusOr CompileAndEvaluateWithBoolVar(absl::string_view cel_expr, CEL_ASSIGN_OR_RETURN(std::unique_ptr compiler, MakeCelCompiler()); - CEL_ASSIGN_OR_RETURN(CheckedExpr checked_expr, - CompileToCheckedExpr(*compiler, cel_expr)); + CEL_ASSIGN_OR_RETURN(cel::ValidationResult validation_result, + compiler->Compile(cel_expr)); + if (!validation_result.IsValid()) { + return absl::InvalidArgumentError(validation_result.FormatError()); + } - Activation activation; + cel::Activation activation; google::protobuf::Arena arena; // === Start Codelab === - // Update the activation to bind the bool argument to 'bool_var' + // Update the activation to bind the bool argument to 'bool_var': + // activation.InsertOrAssignValue("bool_var", cel::BoolValue(bool_var)); // === End Codelab === - - return EvalCheckedExpr(checked_expr, activation, &arena); + CEL_ASSIGN_OR_RETURN(std::unique_ptr ast, + validation_result.ReleaseAst()); + return EvalAst(std::move(ast), activation, &arena); } absl::StatusOr CompileAndEvaluateWithContext( @@ -128,16 +125,22 @@ absl::StatusOr CompileAndEvaluateWithContext( CEL_ASSIGN_OR_RETURN(std::unique_ptr compiler, MakeCelCompiler()); - CEL_ASSIGN_OR_RETURN(CheckedExpr checked_expr, - CompileToCheckedExpr(*compiler, cel_expr)); + CEL_ASSIGN_OR_RETURN(cel::ValidationResult validation_result, + compiler->Compile(cel_expr)); + if (!validation_result.IsValid()) { + return absl::InvalidArgumentError(validation_result.FormatError()); + } - Activation activation; + cel::Activation activation; google::protobuf::Arena arena; // === Start Codelab === - // Update the activation to bind the AttributeContext. + // Update the activation to bind the AttributeContext using + // cel::BindProtoToActivation (in + // "runtime/bind_proto_to_activation.h"). // === End Codelab === - - return EvalCheckedExpr(checked_expr, activation, &arena); + CEL_ASSIGN_OR_RETURN(std::unique_ptr ast, + validation_result.ReleaseAst()); + return EvalAst(std::move(ast), activation, &arena); } } // namespace cel_codelab diff --git a/codelab/exercise2.h b/codelab/exercise2.h index d4836dc2b..7e0b3b8c7 100644 --- a/codelab/exercise2.h +++ b/codelab/exercise2.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef THIRD_PARTY_CEL_CPP_CODELAB_EXERCISE1_H_ -#define THIRD_PARTY_CEL_CPP_CODELAB_EXERCISE1_H_ +#ifndef THIRD_PARTY_CEL_CPP_CODELAB_EXERCISE2_H_ +#define THIRD_PARTY_CEL_CPP_CODELAB_EXERCISE2_H_ #include "google/rpc/context/attribute_context.pb.h" #include "absl/status/statusor.h" @@ -37,4 +37,4 @@ absl::StatusOr CompileAndEvaluateWithContext( } // namespace cel_codelab -#endif // THIRD_PARTY_CEL_CPP_CODELAB_EXERCISE1_H_ +#endif // THIRD_PARTY_CEL_CPP_CODELAB_EXERCISE2_H_ diff --git a/codelab/exercise4.cc b/codelab/exercise4.cc index cf02a88bd..31374248a 100644 --- a/codelab/exercise4.cc +++ b/codelab/exercise4.cc @@ -15,25 +15,32 @@ #include "codelab/exercise4.h" #include +#include -#include "cel/expr/checked.pb.h" #include "google/rpc/context/attribute_context.pb.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" -#include "codelab/cel_compiler.h" +#include "absl/types/optional.h" +#include "checker/type_checker_builder.h" +#include "checker/validation_result.h" +#include "common/ast.h" +#include "common/decl.h" +#include "common/minimal_descriptor_pool.h" +#include "common/type.h" +#include "common/value.h" #include "compiler/compiler.h" #include "compiler/compiler_factory.h" #include "compiler/standard_library.h" -#include "eval/public/activation.h" -#include "eval/public/activation_bind_helper.h" -#include "eval/public/builtin_func_registrar.h" -#include "eval/public/cel_expr_builder_factory.h" -#include "eval/public/cel_expression.h" -#include "eval/public/cel_options.h" -#include "eval/public/cel_value.h" #include "internal/status_macros.h" +#include "runtime/activation.h" +#include "runtime/bind_proto_to_activation.h" +#include "runtime/function_adapter.h" +#include "runtime/runtime.h" +#include "runtime/runtime_builder.h" +#include "runtime/runtime_options.h" +#include "runtime/standard_runtime_builder_factory.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" #include "google/protobuf/message.h" @@ -41,21 +48,25 @@ namespace cel_codelab { namespace { -using ::cel::expr::CheckedExpr; -using ::google::api::expr::runtime::Activation; -using ::google::api::expr::runtime::BindProtoToActivation; -using ::google::api::expr::runtime::CelError; -using ::google::api::expr::runtime::CelExpressionBuilder; -using ::google::api::expr::runtime::CelValue; -using ::google::api::expr::runtime::CreateCelExpressionBuilder; -using ::google::api::expr::runtime::InterpreterOptions; -using ::google::api::expr::runtime::RegisterBuiltinFunctions; using ::google::rpc::context::AttributeContext; +absl::StatusOr ContainsExtensionFunction( + const cel::MapValue& map, const cel::StringValue& key, + const cel::Value& value, const google::protobuf::DescriptorPool* descriptor_pool, + google::protobuf::MessageFactory* message_factory, google::protobuf::Arena* arena) { + (void)map; + (void)key; + (void)value; + (void)descriptor_pool; + (void)message_factory; + (void)arena; + // Implement the contains function: check if key is in map and if its value + // matches `value`. + return absl::UnimplementedError("Not yet implemented"); +} + absl::StatusOr> MakeConfiguredCompiler() { - // Setup for handling for protobuf types. - // Using the generated descriptor pool is simpler to configure, but often - // adds more types than necessary. + // Setup for handling protobuf types. google::protobuf::LinkMessageReflection(); CEL_ASSIGN_OR_RETURN( std::unique_ptr builder, @@ -67,48 +78,54 @@ absl::StatusOr> MakeConfiguredCompiler() { // Codelab part 1: // Add a declaration for the map.contains(string, V) function. - // Hint: use cel::MakeFunctionDecl and cel::TypeCheckerBuilder::MergeFunction. - return builder->Build(); + // Hint: use cel::MakeFunctionDecl and cel::MakeMemberOverloadDecl. + return std::move(builder)->Build(); } class Evaluator { public: - Evaluator() { - builder_ = CreateCelExpressionBuilder( - google::protobuf::DescriptorPool::generated_pool(), - google::protobuf::MessageFactory::generated_factory(), options_); - } + Evaluator() = default; absl::Status SetupEvaluatorEnvironment() { - CEL_RETURN_IF_ERROR(RegisterBuiltinFunctions(builder_->GetRegistry())); + cel::RuntimeOptions options; + CEL_ASSIGN_OR_RETURN( + cel::RuntimeBuilder runtime_builder, + cel::CreateStandardRuntimeBuilder( + google::protobuf::DescriptorPool::generated_pool(), options)); // Codelab part 2: // Register the map.contains(string, value) function. - // Hint: use `CelFunctionAdapter::CreateAndRegister` to adapt from a free - // function ContainsExtensionFunction. + // Hint: use `TernaryFunctionAdapter::RegisterMemberOverload` to adapt + // from a free function ContainsExtensionFunction. + CEL_ASSIGN_OR_RETURN(runtime_, std::move(runtime_builder).Build()); return absl::OkStatus(); } - absl::StatusOr Evaluate(const CheckedExpr& expr, + absl::StatusOr Evaluate(std::unique_ptr ast, const AttributeContext& context) { - Activation activation; - CEL_RETURN_IF_ERROR(BindProtoToActivation(&context, &arena_, &activation)); - CEL_ASSIGN_OR_RETURN(auto plan, builder_->CreateExpression(&expr)); - CEL_ASSIGN_OR_RETURN(CelValue result, plan->Evaluate(activation, &arena_)); - - if (bool value; result.GetValue(&value)) { - return value; - } else if (const CelError * value; result.GetValue(&value)) { - return *value; - } else { - return absl::InvalidArgumentError( - absl::StrCat("unexpected return type: ", result.DebugString())); + cel::Activation activation; + CEL_RETURN_IF_ERROR(cel::BindProtoToActivation( + context, cel::BindProtoUnsetFieldBehavior::kBindDefaultValue, + google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), &arena_, &activation)); + + CEL_ASSIGN_OR_RETURN(std::unique_ptr program, + runtime_->CreateProgram(std::move(ast))); + CEL_ASSIGN_OR_RETURN(cel::Value result, + program->Evaluate(&arena_, activation)); + + if (result.IsBool()) { + return result.GetBool().NativeValue(); } + if (result.IsError()) { + return result.GetError().ToStatus(); + } + return absl::InvalidArgumentError( + absl::StrCat("unexpected return type: ", result.GetTypeName())); } private: google::protobuf::Arena arena_; - std::unique_ptr builder_; - InterpreterOptions options_; + std::unique_ptr runtime_; }; } // namespace @@ -118,15 +135,20 @@ absl::StatusOr EvaluateWithExtensionFunction( // Prepare a checked expression. CEL_ASSIGN_OR_RETURN(std::unique_ptr compiler, MakeConfiguredCompiler()); - CEL_ASSIGN_OR_RETURN(auto checked_expr, - CompileToCheckedExpr(*compiler, expr)); + CEL_ASSIGN_OR_RETURN(cel::ValidationResult validation_result, + compiler->Compile(expr)); + if (!validation_result.IsValid()) { + return absl::InvalidArgumentError(validation_result.FormatError()); + } // Prepare an evaluation environment. Evaluator evaluator; CEL_RETURN_IF_ERROR(evaluator.SetupEvaluatorEnvironment()); - // Evaluate a checked expression against a particular activation - return evaluator.Evaluate(checked_expr, context); + // Evaluate the checked AST against a particular activation. + CEL_ASSIGN_OR_RETURN(std::unique_ptr ast, + validation_result.ReleaseAst()); + return evaluator.Evaluate(std::move(ast), context); } } // namespace cel_codelab diff --git a/codelab/solutions/BUILD b/codelab/solutions/BUILD index a1597e182..abe0f6139 100644 --- a/codelab/solutions/BUILD +++ b/codelab/solutions/BUILD @@ -24,18 +24,19 @@ cc_library( srcs = ["exercise1.cc"], hdrs = ["//codelab:exercise1.h"], deps = [ - "//eval/public:activation", - "//eval/public:builtin_func_registrar", - "//eval/public:cel_expr_builder_factory", - "//eval/public:cel_expression", - "//eval/public:cel_options", - "//eval/public:cel_value", + "//common:minimal_descriptor_pool", + "//common:value", + "//compiler", + "//compiler:compiler_factory", + "//compiler:standard_library", "//internal:status_macros", - "//parser", + "//runtime", + "//runtime:activation", + "//runtime:runtime_options", + "//runtime:standard_runtime_builder_factory", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", - "@com_google_cel_spec//proto/cel/expr:syntax_cc_proto", "@com_google_protobuf//:protobuf", ], ) @@ -56,25 +57,23 @@ cc_library( hdrs = ["//codelab:exercise2.h"], deps = [ "//checker:type_checker_builder", - "//codelab:cel_compiler", + "//checker:validation_result", "//common:decl", + "//common:minimal_descriptor_pool", "//common:type", + "//common:value", "//compiler", "//compiler:compiler_factory", "//compiler:standard_library", - "//eval/public:activation", - "//eval/public:activation_bind_helper", - "//eval/public:builtin_func_registrar", - "//eval/public:cel_expr_builder_factory", - "//eval/public:cel_expression", - "//eval/public:cel_options", - "//eval/public:cel_value", "//internal:status_macros", + "//runtime", + "//runtime:activation", + "//runtime:bind_proto_to_activation", + "//runtime:runtime_options", + "//runtime:standard_runtime_builder_factory", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", - "@com_google_cel_spec//proto/cel/expr:checked_cc_proto", - "@com_google_cel_spec//proto/cel/expr:syntax_cc_proto", "@com_google_googleapis//google/rpc/context:attribute_context_cc_proto", "@com_google_protobuf//:protobuf", ], @@ -110,26 +109,26 @@ cc_library( srcs = ["exercise4.cc"], hdrs = ["//codelab:exercise4.h"], deps = [ - "//codelab:cel_compiler", + "//checker:type_checker_builder", + "//checker:validation_result", "//common:decl", + "//common:minimal_descriptor_pool", "//common:type", + "//common:value", "//compiler", "//compiler:compiler_factory", "//compiler:standard_library", - "//eval/public:activation", - "//eval/public:activation_bind_helper", - "//eval/public:builtin_func_registrar", - "//eval/public:cel_expr_builder_factory", - "//eval/public:cel_expression", - "//eval/public:cel_function_adapter", - "//eval/public:cel_options", - "//eval/public:cel_value", "//internal:status_macros", + "//runtime", + "//runtime:activation", + "//runtime:bind_proto_to_activation", + "//runtime:function_adapter", + "//runtime:runtime_options", + "//runtime:standard_runtime_builder_factory", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_absl//absl/types:optional", - "@com_google_cel_spec//proto/cel/expr:checked_cc_proto", "@com_google_googleapis//google/rpc/context:attribute_context_cc_proto", "@com_google_protobuf//:protobuf", ], @@ -154,6 +153,7 @@ cc_library( deps = [ "//checker:validation_result", "//codelab:network_functions", + "//common:ast", "//common:decl", "//common:minimal_descriptor_pool", "//common:type", diff --git a/codelab/solutions/exercise1.cc b/codelab/solutions/exercise1.cc index aef6c0efe..88046a801 100644 --- a/codelab/solutions/exercise1.cc +++ b/codelab/solutions/exercise1.cc @@ -16,90 +16,83 @@ #include #include +#include -#include "cel/expr/syntax.pb.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" -#include "eval/public/activation.h" -#include "eval/public/builtin_func_registrar.h" -#include "eval/public/cel_expr_builder_factory.h" -#include "eval/public/cel_expression.h" -#include "eval/public/cel_options.h" -#include "eval/public/cel_value.h" +#include "common/minimal_descriptor_pool.h" +#include "common/value.h" +#include "compiler/compiler.h" +#include "compiler/compiler_factory.h" +#include "compiler/standard_library.h" #include "internal/status_macros.h" -#include "parser/parser.h" +#include "runtime/activation.h" +#include "runtime/runtime.h" +#include "runtime/runtime_options.h" +#include "runtime/standard_runtime_builder_factory.h" #include "google/protobuf/arena.h" namespace cel_codelab { namespace { -using ::cel::expr::ParsedExpr; -using ::google::api::expr::parser::Parse; -using ::google::api::expr::runtime::Activation; -using ::google::api::expr::runtime::CelExpression; -using ::google::api::expr::runtime::CelExpressionBuilder; -using ::google::api::expr::runtime::CelValue; -using ::google::api::expr::runtime::CreateCelExpressionBuilder; -using ::google::api::expr::runtime::InterpreterOptions; -using ::google::api::expr::runtime::RegisterBuiltinFunctions; - -// Convert the CelResult to a C++ string if it is string typed. Otherwise, -// return invalid argument error. This takes a copy to avoid lifecycle concerns -// (the evaluator may represent strings as stringviews backed by the input -// expression). -absl::StatusOr ConvertResult(const CelValue& value) { - if (CelValue::StringHolder inner_value; value.GetValue(&inner_value)) { - return std::string(inner_value.value()); - } else { - return absl::InvalidArgumentError(absl::StrCat( - "expected string result got '", CelValue::TypeName(value.type()), "'")); +// Convert the cel::Value to a C++ string if it is string typed. Otherwise, +// return invalid argument error. +absl::StatusOr ConvertResult(const cel::Value& value) { + if (value.IsString()) { + return value.GetString().ToString(); } + return absl::InvalidArgumentError( + absl::StrCat("expected string result got '", value.GetTypeName(), "'")); } + } // namespace absl::StatusOr ParseAndEvaluate(absl::string_view cel_expr) { // === Start Codelab === - // Setup a default environment for building expressions. - InterpreterOptions options; - std::unique_ptr builder = - CreateCelExpressionBuilder(options); - + // Setup a default compiler for compiling expressions. + CEL_ASSIGN_OR_RETURN( + std::unique_ptr compiler_builder, + cel::NewCompilerBuilder(cel::GetMinimalDescriptorPool())); CEL_RETURN_IF_ERROR( - RegisterBuiltinFunctions(builder->GetRegistry(), options)); + compiler_builder->AddLibrary(cel::StandardCompilerLibrary())); + CEL_ASSIGN_OR_RETURN(std::unique_ptr compiler, + std::move(compiler_builder)->Build()); - // Parse the expression. This is fine for codelabs, but this skips the type - // checking phase. It won't check that functions and variables are available - // in the environment, and it won't handle certain ambiguous identifier - // expressions (e.g. container lookup vs namespaced name, packaged function - // vs. receiver call style function). - ParsedExpr parsed_expr; - CEL_ASSIGN_OR_RETURN(parsed_expr, Parse(cel_expr)); + // Compile the expression. + CEL_ASSIGN_OR_RETURN(cel::ValidationResult validation_result, + compiler->Compile(cel_expr)); + if (!validation_result.IsValid()) { + return absl::InvalidArgumentError(validation_result.FormatError()); + } - // The evaluator uses a proto Arena for incidental allocations during - // evaluation. + // Setup a standard runtime for evaluating expressions. + cel::RuntimeOptions options; + CEL_ASSIGN_OR_RETURN(cel::RuntimeBuilder runtime_builder, + cel::CreateStandardRuntimeBuilder( + cel::GetMinimalDescriptorPool(), options)); + CEL_ASSIGN_OR_RETURN(std::unique_ptr runtime, + std::move(runtime_builder).Build()); + + // Build the executable program from the compiled AST. + CEL_ASSIGN_OR_RETURN(std::unique_ptr ast, + validation_result.ReleaseAst()); + CEL_ASSIGN_OR_RETURN(std::unique_ptr program, + runtime->CreateProgram(std::move(ast))); + + // The evaluator uses a proto Arena for allocations during evaluation. google::protobuf::Arena arena; - // The activation provides variables and functions that are bound into the + // The activation provides variables and functions bound into the // expression environment. In this example, there's no context expected, so - // we just provide an empty one to the evaluator. - Activation activation; - - // Build the expression plan. This assumes that the source expression AST and - // the expression builder outlive the CelExpression object. - CEL_ASSIGN_OR_RETURN(std::unique_ptr expression_plan, - builder->CreateExpression(&parsed_expr.expr(), - &parsed_expr.source_info())); + // we provide an empty activation. + cel::Activation activation; - // Actually run the expression plan. We don't support any environment - // variables at the moment so just use an empty activation. - CEL_ASSIGN_OR_RETURN(CelValue result, - expression_plan->Evaluate(activation, &arena)); + // Run the program. + CEL_ASSIGN_OR_RETURN(cel::Value result, + program->Evaluate(&arena, activation)); - // Convert the result to a c++ string. CelValues may reference instances from - // either the input expression, or objects allocated on the arena, so we need - // to pass ownership (in this case by copying to a new instance and returning - // that). + // Convert the result to a C++ string. return ConvertResult(result); // === End Codelab === } diff --git a/codelab/solutions/exercise10.cc b/codelab/solutions/exercise10.cc index 0d2c197d6..e82f06d2a 100644 --- a/codelab/solutions/exercise10.cc +++ b/codelab/solutions/exercise10.cc @@ -105,8 +105,9 @@ absl::StatusOr CompileAndEvaluateExercise10(absl::string_view expression, return absl::InvalidArgumentError(checked->FormatError()); } + CEL_ASSIGN_OR_RETURN(std::unique_ptr ast, checked->ReleaseAst()); absl::StatusOr> program = - (*runtime)->CreateProgram(checked->ReleaseAst().value()); + (*runtime)->CreateProgram(std::move(ast)); if (!program.ok()) { return std::move(program).status(); diff --git a/codelab/solutions/exercise2.cc b/codelab/solutions/exercise2.cc index d07645aed..2037e4f11 100644 --- a/codelab/solutions/exercise2.cc +++ b/codelab/solutions/exercise2.cc @@ -15,29 +15,28 @@ #include "codelab/exercise2.h" #include +#include -#include "cel/expr/checked.pb.h" -#include "cel/expr/syntax.pb.h" #include "google/rpc/context/attribute_context.pb.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "checker/type_checker_builder.h" -#include "codelab/cel_compiler.h" +#include "checker/validation_result.h" #include "common/decl.h" +#include "common/minimal_descriptor_pool.h" #include "common/type.h" +#include "common/value.h" #include "compiler/compiler.h" #include "compiler/compiler_factory.h" #include "compiler/standard_library.h" -#include "eval/public/activation.h" -#include "eval/public/activation_bind_helper.h" -#include "eval/public/builtin_func_registrar.h" -#include "eval/public/cel_expr_builder_factory.h" -#include "eval/public/cel_expression.h" -#include "eval/public/cel_options.h" -#include "eval/public/cel_value.h" #include "internal/status_macros.h" +#include "runtime/activation.h" +#include "runtime/bind_proto_to_activation.h" +#include "runtime/runtime.h" +#include "runtime/runtime_options.h" +#include "runtime/standard_runtime_builder_factory.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" #include "google/protobuf/message.h" @@ -45,16 +44,6 @@ namespace cel_codelab { namespace { -using ::cel::expr::CheckedExpr; -using ::google::api::expr::runtime::Activation; -using ::google::api::expr::runtime::CelError; -using ::google::api::expr::runtime::CelExpression; -using ::google::api::expr::runtime::CelExpressionBuilder; -using ::google::api::expr::runtime::CelValue; -using ::google::api::expr::runtime::CreateCelExpressionBuilder; -using ::google::api::expr::runtime::InterpreterOptions; -using ::google::api::expr::runtime::ProtoUnsetFieldOptions; -using ::google::api::expr::runtime::RegisterBuiltinFunctions; using ::google::rpc::context::AttributeContext; absl::StatusOr> MakeCelCompiler() { @@ -75,39 +64,38 @@ absl::StatusOr> MakeCelCompiler() { AttributeContext::descriptor()->full_name())); // === End Codelab === - return builder->Build(); + return std::move(builder)->Build(); } -// Parse a cel expression and evaluate it against the given activation and -// arena. -absl::StatusOr EvalCheckedExpr(const CheckedExpr& checked_expr, - const Activation& activation, - google::protobuf::Arena* arena) { - // Setup a default environment for building expressions. - InterpreterOptions options; - std::unique_ptr builder = CreateCelExpressionBuilder( - google::protobuf::DescriptorPool::generated_pool(), - google::protobuf::MessageFactory::generated_factory(), options); - CEL_RETURN_IF_ERROR( - RegisterBuiltinFunctions(builder->GetRegistry(), options)); - - // Note, the expression_plan below is reusable for different inputs, but we +// Evaluate a runtime cel::Ast against the given activation and arena. +absl::StatusOr EvalAst(std::unique_ptr ast, + const cel::Activation& activation, + google::protobuf::Arena* arena) { + // Setup a default standard runtime. + cel::RuntimeOptions options; + CEL_ASSIGN_OR_RETURN(cel::RuntimeBuilder runtime_builder, + cel::CreateStandardRuntimeBuilder( + google::protobuf::DescriptorPool::generated_pool(), options)); + CEL_ASSIGN_OR_RETURN(std::unique_ptr runtime, + std::move(runtime_builder).Build()); + + // Note, the program plan below is reusable across different inputs, but we // create one just in time for evaluation here. - CEL_ASSIGN_OR_RETURN(std::unique_ptr expression_plan, - builder->CreateExpression(&checked_expr)); - - CEL_ASSIGN_OR_RETURN(CelValue result, - expression_plan->Evaluate(activation, arena)); - - if (bool value; result.GetValue(&value)) { - return value; - } else if (const CelError * value; result.GetValue(&value)) { - return *value; - } else { - return absl::InvalidArgumentError(absl::StrCat( - "expected 'bool' result got '", result.DebugString(), "'")); + CEL_ASSIGN_OR_RETURN(std::unique_ptr program, + runtime->CreateProgram(std::move(ast))); + + CEL_ASSIGN_OR_RETURN(cel::Value result, program->Evaluate(arena, activation)); + + if (result.IsBool()) { + return result.GetBool().NativeValue(); + } + if (result.IsError()) { + return result.GetError().ToStatus(); } + return absl::InvalidArgumentError( + absl::StrCat("expected 'bool' result got '", result.GetTypeName(), "'")); } + } // namespace absl::StatusOr CompileAndEvaluateWithBoolVar(absl::string_view cel_expr, @@ -115,16 +103,21 @@ absl::StatusOr CompileAndEvaluateWithBoolVar(absl::string_view cel_expr, CEL_ASSIGN_OR_RETURN(std::unique_ptr compiler, MakeCelCompiler()); - CEL_ASSIGN_OR_RETURN(CheckedExpr checked_expr, - CompileToCheckedExpr(*compiler, cel_expr)); + CEL_ASSIGN_OR_RETURN(cel::ValidationResult validation_result, + compiler->Compile(cel_expr)); + if (!validation_result.IsValid()) { + return absl::InvalidArgumentError(validation_result.FormatError()); + } - Activation activation; + cel::Activation activation; google::protobuf::Arena arena; // === Start Codelab === - activation.InsertValue("bool_var", CelValue::CreateBool(bool_var)); + activation.InsertOrAssignValue("bool_var", cel::BoolValue(bool_var)); // === End Codelab === - return EvalCheckedExpr(checked_expr, activation, &arena); + CEL_ASSIGN_OR_RETURN(std::unique_ptr ast, + validation_result.ReleaseAst()); + return EvalAst(std::move(ast), activation, &arena); } absl::StatusOr CompileAndEvaluateWithContext( @@ -132,17 +125,24 @@ absl::StatusOr CompileAndEvaluateWithContext( CEL_ASSIGN_OR_RETURN(std::unique_ptr compiler, MakeCelCompiler()); - CEL_ASSIGN_OR_RETURN(CheckedExpr checked_expr, - CompileToCheckedExpr(*compiler, cel_expr)); + CEL_ASSIGN_OR_RETURN(cel::ValidationResult validation_result, + compiler->Compile(cel_expr)); + if (!validation_result.IsValid()) { + return absl::InvalidArgumentError(validation_result.FormatError()); + } - Activation activation; + cel::Activation activation; google::protobuf::Arena arena; // === Start Codelab === - CEL_RETURN_IF_ERROR(BindProtoToActivation( - &context, &arena, &activation, ProtoUnsetFieldOptions::kBindDefault)); + CEL_RETURN_IF_ERROR(cel::BindProtoToActivation( + context, cel::BindProtoUnsetFieldBehavior::kBindDefaultValue, + google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), &arena, &activation)); // === End Codelab === - return EvalCheckedExpr(checked_expr, activation, &arena); + CEL_ASSIGN_OR_RETURN(std::unique_ptr ast, + validation_result.ReleaseAst()); + return EvalAst(std::move(ast), activation, &arena); } } // namespace cel_codelab diff --git a/codelab/solutions/exercise4.cc b/codelab/solutions/exercise4.cc index 244fdac05..6e9821962 100644 --- a/codelab/solutions/exercise4.cc +++ b/codelab/solutions/exercise4.cc @@ -15,29 +15,30 @@ #include "codelab/exercise4.h" #include +#include -#include "cel/expr/checked.pb.h" #include "google/rpc/context/attribute_context.pb.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" -#include "codelab/cel_compiler.h" +#include "checker/type_checker_builder.h" +#include "checker/validation_result.h" #include "common/decl.h" +#include "common/minimal_descriptor_pool.h" #include "common/type.h" +#include "common/value.h" #include "compiler/compiler.h" #include "compiler/compiler_factory.h" #include "compiler/standard_library.h" -#include "eval/public/activation.h" -#include "eval/public/activation_bind_helper.h" -#include "eval/public/builtin_func_registrar.h" -#include "eval/public/cel_expr_builder_factory.h" -#include "eval/public/cel_expression.h" -#include "eval/public/cel_function_adapter.h" -#include "eval/public/cel_options.h" -#include "eval/public/cel_value.h" #include "internal/status_macros.h" +#include "runtime/activation.h" +#include "runtime/bind_proto_to_activation.h" +#include "runtime/function_adapter.h" +#include "runtime/runtime.h" +#include "runtime/runtime_options.h" +#include "runtime/standard_runtime_builder_factory.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" #include "google/protobuf/message.h" @@ -45,40 +46,24 @@ namespace cel_codelab { namespace { -using ::cel::expr::CheckedExpr; -using ::google::api::expr::runtime::Activation; -using ::google::api::expr::runtime::BindProtoToActivation; -using ::google::api::expr::runtime::CelError; -using ::google::api::expr::runtime::CelExpressionBuilder; -using ::google::api::expr::runtime::CelMap; -using ::google::api::expr::runtime::CelValue; -using ::google::api::expr::runtime::CreateCelExpressionBuilder; -using ::google::api::expr::runtime::FunctionAdapter; -using ::google::api::expr::runtime::InterpreterOptions; -using ::google::api::expr::runtime::RegisterBuiltinFunctions; using ::google::rpc::context::AttributeContext; -// Handle the parametric type overload with a single generic CelValue overload. -absl::StatusOr ContainsExtensionFunction(google::protobuf::Arena* arena, - const CelMap* map, - CelValue::StringHolder key, - const CelValue& value) { - absl::optional entry = (*map)[CelValue::CreateString(key)]; +absl::StatusOr ContainsExtensionFunction( + const cel::MapValue& map, const cel::StringValue& key, + const cel::Value& value, const google::protobuf::DescriptorPool* descriptor_pool, + google::protobuf::MessageFactory* message_factory, google::protobuf::Arena* arena) { + CEL_ASSIGN_OR_RETURN(absl::optional entry, + map.Find(key, descriptor_pool, message_factory, arena)); if (!entry.has_value()) { return false; } - if (value.IsInt64() && entry->IsInt64()) { - return value.Int64OrDie() == entry->Int64OrDie(); - } else if (value.IsString() && entry->IsString()) { - return value.StringOrDie().value() == entry->StringOrDie().value(); - } - return false; + CEL_ASSIGN_OR_RETURN(cel::Value equal, entry->Equal(value, descriptor_pool, + message_factory, arena)); + return equal.IsBool() && equal.GetBool().NativeValue(); } absl::StatusOr> MakeConfiguredCompiler() { - // Setup for handling for protobuf types. - // Using the generated descriptor pool is simpler to configure, but often - // adds more types than necessary. + // Setup for handling protobuf types. google::protobuf::LinkMessageReflection(); CEL_ASSIGN_OR_RETURN( std::unique_ptr builder, @@ -99,59 +84,66 @@ absl::StatusOr> MakeConfiguredCompiler() { cel::MakeFunctionDecl( "contains", cel::MakeMemberOverloadDecl( - "map_contains_string_string", cel::BoolType(), + "map_contains_string_value", cel::BoolType(), cel::MapType(checker_builder.arena(), cel::StringType(), cel::TypeParamType("V")), cel::StringType(), cel::TypeParamType("V")))); // Note: we use MergeFunction instead of AddFunction because we are adding // an overload to an already declared function with the same name. CEL_RETURN_IF_ERROR(checker_builder.MergeFunction(decl)); - return builder->Build(); + return std::move(builder)->Build(); } class Evaluator { public: - Evaluator() { - builder_ = CreateCelExpressionBuilder( - google::protobuf::DescriptorPool::generated_pool(), - google::protobuf::MessageFactory::generated_factory(), options_); - } + Evaluator() = default; absl::Status SetupEvaluatorEnvironment() { - CEL_RETURN_IF_ERROR(RegisterBuiltinFunctions(builder_->GetRegistry())); + cel::RuntimeOptions options; + CEL_ASSIGN_OR_RETURN( + cel::RuntimeBuilder runtime_builder, + cel::CreateStandardRuntimeBuilder( + google::protobuf::DescriptorPool::generated_pool(), options)); // Codelab part 2: - // Register the map.contains(string, string) function. - // Hint: use `FunctionAdapter::CreateAndRegister` to adapt from a free - // function ContainsExtensionFunction. - using AdapterT = FunctionAdapter, const CelMap*, - CelValue::StringHolder, CelValue>; - CEL_RETURN_IF_ERROR(AdapterT::CreateAndRegister( - "contains", /*receiver_style=*/true, &ContainsExtensionFunction, - builder_->GetRegistry())); + // Register the map.contains(string, value) function. + // Hint: use `TernaryFunctionAdapter::RegisterMemberOverload` to adapt + // from a free function ContainsExtensionFunction. + using AdapterT = + cel::TernaryFunctionAdapter, const cel::MapValue&, + const cel::StringValue&, const cel::Value&>; + CEL_RETURN_IF_ERROR( + AdapterT::RegisterMemberOverload("contains", &ContainsExtensionFunction, + runtime_builder.function_registry())); + CEL_ASSIGN_OR_RETURN(runtime_, std::move(runtime_builder).Build()); return absl::OkStatus(); } - absl::StatusOr Evaluate(const CheckedExpr& expr, + absl::StatusOr Evaluate(std::unique_ptr ast, const AttributeContext& context) { - Activation activation; - CEL_RETURN_IF_ERROR(BindProtoToActivation(&context, &arena_, &activation)); - CEL_ASSIGN_OR_RETURN(auto plan, builder_->CreateExpression(&expr)); - CEL_ASSIGN_OR_RETURN(CelValue result, plan->Evaluate(activation, &arena_)); - - if (bool value; result.GetValue(&value)) { - return value; - } else if (const CelError* value; result.GetValue(&value)) { - return *value; - } else { - return absl::InvalidArgumentError( - absl::StrCat("unexpected return type: ", result.DebugString())); + cel::Activation activation; + CEL_RETURN_IF_ERROR(cel::BindProtoToActivation( + context, cel::BindProtoUnsetFieldBehavior::kBindDefaultValue, + google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), &arena_, &activation)); + + CEL_ASSIGN_OR_RETURN(std::unique_ptr program, + runtime_->CreateProgram(std::move(ast))); + CEL_ASSIGN_OR_RETURN(cel::Value result, + program->Evaluate(&arena_, activation)); + + if (result.IsBool()) { + return result.GetBool().NativeValue(); } + if (result.IsError()) { + return result.GetError().ToStatus(); + } + return absl::InvalidArgumentError( + absl::StrCat("unexpected return type: ", result.GetTypeName())); } private: google::protobuf::Arena arena_; - std::unique_ptr builder_; - InterpreterOptions options_; + std::unique_ptr runtime_; }; } // namespace @@ -161,15 +153,20 @@ absl::StatusOr EvaluateWithExtensionFunction( // Prepare a checked expression. CEL_ASSIGN_OR_RETURN(std::unique_ptr compiler, MakeConfiguredCompiler()); - CEL_ASSIGN_OR_RETURN(auto checked_expr, - CompileToCheckedExpr(*compiler, expr)); + CEL_ASSIGN_OR_RETURN(cel::ValidationResult validation_result, + compiler->Compile(expr)); + if (!validation_result.IsValid()) { + return absl::InvalidArgumentError(validation_result.FormatError()); + } // Prepare an evaluation environment. Evaluator evaluator; CEL_RETURN_IF_ERROR(evaluator.SetupEvaluatorEnvironment()); - // Evaluate a checked expression against a particular activation - return evaluator.Evaluate(checked_expr, context); + // Evaluate the checked AST against a particular activation. + CEL_ASSIGN_OR_RETURN(std::unique_ptr ast, + validation_result.ReleaseAst()); + return evaluator.Evaluate(std::move(ast), context); } } // namespace cel_codelab