diff --git a/.bazelrc b/.bazelrc
new file mode 100644
index 000000000..8724ec1da
--- /dev/null
+++ b/.bazelrc
@@ -0,0 +1,20 @@
+common --enable_bzlmod
+# Use built-in protoc
+common --incompatible_enable_proto_toolchain_resolution
+common --@com_google_protobuf//bazel/toolchains:prefer_prebuilt_protoc=true
+
+# Ensure that we don't accidentally build protobuf or gRPC
+common --per_file_copt=external/.*protobuf.*@--PROTOBUF_WAS_NOT_SUPPOSED_TO_BE_BUILT
+common --host_per_file_copt=external/.*protobuf.*@--PROTOBUF_WAS_NOT_SUPPOSED_TO_BE_BUILT
+common --per_file_copt=external/.*grpc.*@--GRPC_WAS_NOT_SUPPOSED_TO_BE_BUILT
+common --host_per_file_copt=external/.*grpc.*@--GRPC_WAS_NOT_SUPPOSED_TO_BE_BUILT
+build --java_runtime_version=remotejdk_11
+build --java_language_version=11
+
+
+# Hide Java 8 deprecation warnings.
+common --javacopt=-Xlint:-options
+
+# Remove flag once https://github.com/google/cel-spec/issues/508 and rules_jvm_external is fixed.
+common --incompatible_autoload_externally=proto_library,cc_proto_library,java_proto_library,java_test
+
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 000000000..a3a0f42f2
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,39 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: "[BUG] "
+labels: ''
+
+---
+
+---
+name: Bug report
+about: Create a report to help us improve
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Check which components this affects:
+
+- [ ] parser
+- [ ] checker
+- [ ] runtime
+
+Sample expression and input that reproduces the issue:
+```cel
+// sample expression string
+```
+
+Test setup:
+```java
+// test case in java
+```
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 000000000..59a173b91
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,23 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: ''
+
+---
+
+**Feature request checklist**
+
+- [ ] There are no issues that match the desired change
+- [ ] The change is large enough it can't be addressed with a simple Pull Request
+- [ ] If this is a bug, please file a [Bug Report](./ISSUE_TEMPLATE).
+
+**Change**
+Summary of the proposed change and some details about what problem
+it helps solve.
+
+**Example**
+Replace this text with an example indicating the desired functionality.
+
+**Alternatives considered**
+Briefly list alternative designs, or an example of the functionality to be replaced.
diff --git a/.github/workflows/cross_artifact_dependencies_check.sh b/.github/workflows/cross_artifact_dependencies_check.sh
new file mode 100755
index 000000000..0802a299a
--- /dev/null
+++ b/.github/workflows/cross_artifact_dependencies_check.sh
@@ -0,0 +1,85 @@
+#!/bin/bash
+# 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.
+
+set -o pipefail
+
+TARGETS=(
+ "//publish:cel"
+ "//publish:cel_common"
+ "//publish:cel_compiler"
+ "//publish:cel_runtime"
+ "//publish:cel_protobuf"
+ "//publish:cel_v1alpha1"
+)
+
+echo "------------------------------------------------"
+echo "Checking for duplicates..."
+echo "------------------------------------------------"
+
+JDK8_FLAGS="--java_language_version=8 --java_runtime_version=8"
+bazel build $JDK8_FLAGS "${TARGETS[@]}" || { echo "Bazel build failed"; exit 1; }
+
+(
+ for target in "${TARGETS[@]}"; do
+ # Locate the jar
+ jar_path=$(bazel cquery "$target" --output=files 2>/dev/null | grep '\-project.jar$')
+
+ if [[ -z "$jar_path" ]]; then
+ echo "Error: Could not find -project.jar for target $target" >&2
+ exit 1
+ fi
+
+ # Fix relative paths if running from a subdir.
+ if [[ ! -f "$jar_path" ]]; then
+ if [[ -f "../../$jar_path" ]]; then
+ jar_path="../../$jar_path"
+ else
+ echo "Error: File not found at $jar_path" >&2
+ exit 1
+ fi
+ fi
+
+ echo "Inspecting: $target" >&2
+
+ # Extract classes and append the target name to the end of the line
+ # Format: dev/cel/expr/Expr.class //publish:cel_compiler
+ jar tf "$jar_path" | grep "\.class$" | awk -v tgt="$target" '{print $0, tgt}'
+ done
+) | awk '
+ # $1 is the Class Name, $2 is the Target Name
+ seen[$1] {
+ print "β DUPLICATE FOUND: " $1
+ print " Present in: " seen[$1]
+ print " And in: " $2
+ dupe=1
+ next
+ }
+ { seen[$1] = $2 }
+
+ END { if (dupe) exit 2 }
+'
+
+EXIT_CODE=$?
+
+if [ $EXIT_CODE -eq 0 ]; then
+ echo "β Success: No duplicate classes found."
+elif [ $EXIT_CODE -eq 2 ]; then
+ echo "β Failure: Duplicate classes detected."
+else
+ echo "π₯ Error: An unexpected error occurred (e.g., missing jar files). Exit Code: $EXIT_CODE"
+fi
+
+exit $EXIT_CODE
+
diff --git a/.github/workflows/unwanted_deps.sh b/.github/workflows/unwanted_deps.sh
new file mode 100755
index 000000000..c483f9730
--- /dev/null
+++ b/.github/workflows/unwanted_deps.sh
@@ -0,0 +1,49 @@
+#!/bin/bash
+# Copyright 2024 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License 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.
+
+# Script ran as part of Github CEL-Java CI to verify that the runtime jar does not contain unwanted dependencies.
+
+function checkUnwantedDeps {
+ target="$1"
+ unwanted_dep="$2"
+
+ query="bazel query 'deps(${target})' --notool_deps --noimplicit_deps --output graph"
+ deps=$(eval $query)
+
+ if echo "$deps" | grep "$unwanted_dep" > /dev/null; then
+ echo -e "$target contains unwanted dependency: $unwanted_dep!\n"
+ echo "$(echo "$deps" | grep "$unwanted_dep")"
+ exit 1
+ fi
+}
+
+# Do not include generated CEL protos in the jar
+checkUnwantedDeps '//publish:cel_runtime' '@cel_spec'
+
+# cel_runtime does not support protolite
+checkUnwantedDeps '//publish:cel_runtime' 'protobuf_java_util'
+checkUnwantedDeps '//publish:cel' 'protobuf_java_util'
+
+# cel_runtime shouldn't depend on antlr
+checkUnwantedDeps '//publish:cel_runtime' '@maven//:org_antlr_antlr4_runtime'
+
+# cel_runtime shouldn't depend on the protobuf_lite runtime
+checkUnwantedDeps '//publish:cel_runtime' '@maven_android//:com_google_protobuf_protobuf_javalite'
+checkUnwantedDeps '//publish:cel' '@maven_android//:com_google_protobuf_protobuf_javalite'
+
+# cel_runtime_android shouldn't depend on the full protobuf runtime or antlr
+checkUnwantedDeps '//publish:cel_runtime_android' '@maven//:com_google_protobuf_protobuf_java'
+checkUnwantedDeps '//publish:cel_runtime_android' '@maven//:org_antlr_antlr4_runtime'
+exit 0
diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml
new file mode 100644
index 000000000..94effd33b
--- /dev/null
+++ b/.github/workflows/workflow.yml
@@ -0,0 +1,129 @@
+name: CEL-Java CI
+run-name: Workflow started by ${{ github.actor }}.
+on:
+ push:
+ branches:
+ - 'main'
+ pull_request:
+ branches:
+ - 'main'
+
+# Cancel previous workflows on the PR when there are multiple fast commits.
+# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#concurrency
+concurrency:
+ group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ Bazel-Build-Java8:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - run: echo "π The job was automatically triggered by a ${{ github.event_name }} event."
+ - run: echo "π§ Job is running on a ${{ runner.os }} server!"
+ - run: echo "π The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
+ - name: Check out repository code
+ uses: actions/checkout@v6
+ - name: Setup Bazel
+ uses: bazel-contrib/setup-bazel@0.18.0
+ with:
+ # Avoid downloading Bazel every time.
+ bazelisk-cache: true
+ # Store build cache per workflow.
+ disk-cache: ${{ github.workflow }}
+ # Share repository cache between workflows.
+ repository-cache: true
+ # Prevent PRs from polluting cache
+ cache-save: ${{ github.event_name != 'pull_request' }}
+ - name: Bazel Output Version
+ run: bazelisk --version
+ - name: Java 8 Build
+ run: bazel build ... --java_language_version=8 --java_runtime_version=8 --build_tag_filters=-conformance_maven
+ - name: Unwanted Dependencies
+ run: .github/workflows/unwanted_deps.sh
+ - name: Cross-artifact Duplicate Classes Check
+ run: .github/workflows/cross_artifact_dependencies_check.sh
+ - run: echo "π This job's status is ${{ job.status }}."
+
+ Bazel-Tests:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - run: echo "π The job was automatically triggered by a ${{ github.event_name }} event."
+ - run: echo "π§ Job is running on a ${{ runner.os }} server!"
+ - run: echo "π The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
+ - name: Check out repository code
+ uses: actions/checkout@v6
+ - name: Setup Bazel
+ uses: bazel-contrib/setup-bazel@0.18.0
+ with:
+ # Avoid downloading Bazel every time.
+ bazelisk-cache: true
+ # Store build cache per workflow.
+ disk-cache: ${{ github.workflow }}
+ # Share repository cache between workflows.
+ repository-cache: true
+ # Prevent PRs from polluting cache
+ cache-save: ${{ github.event_name != 'pull_request' }}
+ - name: Bazel Output Version
+ run: bazelisk --version
+ - name: Bazel Test
+ # Exclude codelab exercises as they are intentionally made to fail
+ # Exclude maven conformance tests. They are only executed when there's version change.
+ run: bazelisk test ... --deleted_packages=//codelab/src/test/codelab --test_output=errors --test_tag_filters=-conformance_maven --build_tag_filters=-conformance_maven
+ - run: echo "π This job's status is ${{ job.status }}."
+
+ # -- Start of Maven Conformance Tests (Ran only when there's version changes) --
+ Maven-Conformance:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - run: echo "π The job was automatically triggered by a ${{ github.event_name }} event."
+ - run: echo "π§ Job is running on a ${{ runner.os }} server!"
+ - run: echo "π The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
+ - name: Check out repository code
+ uses: actions/checkout@v6
+ - name: Get changed files
+ id: changed_file
+ uses: tj-actions/changed-files@v47
+ with:
+ files: publish/cel_version.bzl
+ - name: Setup Bazel
+ if: steps.changed_file.outputs.any_changed == 'true'
+ uses: bazel-contrib/setup-bazel@0.18.0
+ with:
+ # Avoid downloading Bazel every time.
+ bazelisk-cache: true
+ # Store build cache per workflow.
+ disk-cache: ${{ github.workflow }}
+ # Share repository cache between workflows.
+ repository-cache: true
+ # Never write to the cache, strictly read-only
+ cache-save: false
+ - name: Verify Version Consistency
+ if: steps.changed_file.outputs.any_changed == 'true'
+ run: |
+ CEL_VERSION=$(grep 'CEL_VERSION =' publish/cel_version.bzl | cut -d '"' -f 2)
+
+ MODULE_VERSION=$(grep 'CEL_VERSION =' MODULE.bazel | cut -d '"' -f 2)
+
+ if [ -z "$CEL_VERSION" ] || [ -z "$MODULE_VERSION" ]; then
+ echo "β Error: Could not extract one or both version strings."
+ exit 1
+ fi
+
+ echo "Version in publish/cel_version.bzl: ${CEL_VERSION}"
+ echo "Version in MODULE.bazel: ${MODULE_VERSION}"
+
+ if [ "$CEL_VERSION" != "$MODULE_VERSION" ]; then
+ echo "β Error: Version mismatch between files!"
+ exit 1
+ fi
+
+ echo "β Versions match."
+ - name: Run Conformance Maven Test on Version Change
+ if: steps.changed_file.outputs.any_changed == 'true'
+ run: bazelisk test //conformance/src/test/java/dev/cel/conformance:conformance_maven --test_output=errors
+ - run: echo "π This job's status is ${JOB_STATUS}."
+ env:
+ JOB_STATUS: ${{ job.status }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..3a2e0015d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,35 @@
+# Bazel
+bazel-bin
+bazel-examples
+bazel-genfiles
+bazel-cel-java
+bazel-out
+bazel-testlogs
+
+# IntelliJ IDEA
+.idea
+*.iml
+*.ipr
+*.iws
+.ijwb
+
+# Eclipse
+.classpath
+.project
+.settings
+.gitignore
+bin
+
+# OS X
+.DS_Store
+
+# Maven (examples)
+target
+
+# Temporary output dir for artifacts
+mvn-artifacts
+
+*.swp
+*.lock
+.eclipse
+.vscode
diff --git a/BUILD.bazel b/BUILD.bazel
new file mode 100644
index 000000000..024908625
--- /dev/null
+++ b/BUILD.bazel
@@ -0,0 +1,206 @@
+# 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.
+
+# Includes package-wide build definitions for maven imported java targets
+# that needs to be defined separately.
+
+load(
+ "@bazel_tools//tools/jdk:default_java_toolchain.bzl",
+ "BASE_JDK9_JVM_OPTS",
+ "DEFAULT_JAVACOPTS",
+ "DEFAULT_TOOLCHAIN_CONFIGURATION",
+ "default_java_toolchain",
+)
+load(
+ "@rules_java//java:defs.bzl",
+ "java_binary",
+ "java_library",
+ "java_package_configuration",
+ "java_plugin",
+)
+load("@rules_license//rules:license.bzl", "license")
+
+licenses(["notice"]) # Apache License 2.0
+
+exports_files(["LICENSE"])
+
+package(default_visibility = ["//visibility:public"])
+
+license(
+ name = "license",
+ package_name = "cel",
+)
+
+# Auto-value requires java_plugin to run the annotation processor
+
+java_plugin(
+ name = "auto_value_plugin",
+ processor_class = "com.google.auto.value.processor.AutoValueProcessor",
+ deps = [
+ "@maven//:com_google_auto_value_auto_value",
+ "@maven//:com_google_auto_value_auto_value_annotations",
+ ],
+)
+
+java_plugin(
+ name = "auto_builder_plugin",
+ processor_class = "com.google.auto.value.processor.AutoBuilderProcessor",
+ deps = [
+ "@maven//:com_google_auto_value_auto_value",
+ "@maven//:com_google_auto_value_auto_value_annotations",
+ ],
+)
+
+java_plugin(
+ name = "auto_one_of_plugin",
+ processor_class = "com.google.auto.value.processor.AutoOneOfProcessor",
+ deps = [
+ "@maven//:com_google_auto_value_auto_value",
+ "@maven//:com_google_auto_value_auto_value_annotations",
+ ],
+)
+
+java_library(
+ name = "auto_value",
+ exported_plugins = [
+ ":auto_value_plugin",
+ ":auto_builder_plugin",
+ ":auto_one_of_plugin",
+ ],
+ neverlink = 1,
+ exports = [
+ "@maven//:com_google_auto_value_auto_value_annotations",
+ ],
+)
+
+# Truth requires both the core package and the extension to be exported.
+
+java_library(
+ name = "java_truth",
+ testonly = 1,
+ exports = [
+ "@maven//:com_google_truth_extensions_truth_java8_extension",
+ "@maven//:com_google_truth_truth",
+ ],
+)
+
+default_java_toolchain(
+ name = "repository_default_toolchain",
+ configuration = DEFAULT_TOOLCHAIN_CONFIGURATION,
+ javacopts = DEFAULT_JAVACOPTS,
+ jvm_opts = BASE_JDK9_JVM_OPTS,
+ package_configuration = [
+ ":error_prone",
+ ],
+ source_version = "11",
+ target_version = "11",
+)
+
+# This associates a set of javac flags with a set of packages
+java_package_configuration(
+ name = "error_prone",
+ # keep sorted
+ javacopts = [
+ "-Xep:AmbiguousMethodReference:ERROR",
+ "-Xep:BadAnnotationImplementation:ERROR",
+ "-Xep:BadComparable:ERROR",
+ "-Xep:BoxedPrimitiveConstructor:ERROR",
+ "-Xep:CannotMockFinalClass:ERROR",
+ "-Xep:CheckReturnValue:OFF",
+ "-Xep:ClassCanBeStatic:ERROR",
+ "-Xep:ClassNewInstance:ERROR",
+ "-Xep:DefaultCharset:ERROR",
+ "-Xep:DoubleCheckedLocking:ERROR",
+ "-Xep:ElementsCountedInLoop:ERROR",
+ "-Xep:EqualsHashCode:ERROR",
+ "-Xep:EqualsIncompatibleType:ERROR",
+ "-Xep:Finally:ERROR",
+ "-Xep:FloatingPointLiteralPrecision:ERROR",
+ "-Xep:FragmentInjection:ERROR",
+ "-Xep:FragmentNotInstantiable:ERROR",
+ "-Xep:FunctionalInterfaceClash:ERROR",
+ "-Xep:FutureReturnValueIgnored:ERROR",
+ "-Xep:GetClassOnEnum:ERROR",
+ "-Xep:ImmutableAnnotationChecker:ERROR",
+ "-Xep:ImmutableEnumChecker:ERROR",
+ "-Xep:IncompatibleModifiers:ERROR",
+ "-Xep:InjectOnConstructorOfAbstractClass:ERROR",
+ "-Xep:InputStreamSlowMultibyteRead:ERROR",
+ "-Xep:IterableAndIterator:ERROR",
+ "-Xep:JUnit3FloatingPointComparisonWithoutDelta:ERROR",
+ "-Xep:JUnitAmbiguousTestClass:ERROR",
+ "-Xep:Java8ApiChecker:ERROR",
+ "-Xep:LiteralClassName:ERROR",
+ "-Xep:MissingCasesInEnumSwitch:ERROR",
+ "-Xep:MissingFail:ERROR",
+ "-Xep:MissingOverride:ERROR",
+ "-Xep:MutableConstantField:ERROR",
+ "-Xep:NarrowingCompoundAssignment:ERROR",
+ "-Xep:NonAtomicVolatileUpdate:ERROR",
+ "-Xep:NonOverridingEquals:ERROR",
+ "-Xep:NullableConstructor:ERROR",
+ "-Xep:NullablePrimitive:ERROR",
+ "-Xep:NullableVoid:ERROR",
+ "-Xep:OperatorPrecedence:ERROR",
+ "-Xep:OverridesGuiceInjectableMethod:ERROR",
+ "-Xep:PreconditionsInvalidPlaceholder:ERROR",
+ "-Xep:ProtoFieldPreconditionsCheckNotNull:ERROR",
+ "-Xep:ProtocolBufferOrdinal:ERROR",
+ "-Xep:ReferenceEquality:ERROR",
+ "-Xep:RemoveUnusedImports:ERROR",
+ "-Xep:RequiredModifiers:ERROR",
+ "-Xep:ShortCircuitBoolean:ERROR",
+ "-Xep:SimpleDateFormatConstant:ERROR",
+ "-Xep:StaticGuardedByInstance:ERROR",
+ "-Xep:StringEquality:ERROR",
+ "-Xep:SynchronizeOnNonFinalField:ERROR",
+ "-Xep:TruthConstantAsserts:ERROR",
+ "-Xep:TypeParameterShadowing:ERROR",
+ "-Xep:TypeParameterUnusedInFormals:ERROR",
+ "-Xep:URLEqualsHashCode:ERROR",
+ "-Xep:UnsynchronizedOverridesSynchronized:ERROR",
+ "-Xep:UnusedMethod:ERROR",
+ "-Xep:UnusedVariable:ERROR",
+ "-Xep:WaitNotInLoop:ERROR",
+ "-Xep:WildcardImport:ERROR",
+ "-XepDisableWarningsInGeneratedCode",
+ "-XepExcludedPaths:.*/bazel-out/.*",
+ ],
+ packages = ["error_prone_packages"],
+)
+
+# This is a regular package_group, which is used to specify a set of packages to apply flags to
+package_group(
+ name = "error_prone_packages",
+ packages = [
+ "//...",
+ ],
+)
+
+java_binary(
+ name = "antlr4_tool",
+ main_class = "org.antlr.v4.Tool",
+ runtime_deps = ["@antlr4_jar//jar"],
+)
+
+# These two package groups are to allow proper bidrectional sync with g3
+package_group(
+ name = "internal",
+ packages = ["//..."],
+)
+
+package_group(
+ name = "android_allow_list",
+ packages = ["//..."],
+)
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..7fb48ef20
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,93 @@
+# Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of
+experience, education, socio-economic status, nationality, personal appearance,
+race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, or to ban temporarily or permanently any
+contributor for other behaviors that they deem inappropriate, threatening,
+offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+This Code of Conduct also applies outside the project spaces when the Project
+Steward has a reasonable belief that an individual's behavior may have a
+negative impact on the project or its community.
+
+## Conflict Resolution
+
+We do not believe that all conflict is bad; healthy debate and disagreement
+often yield positive results. However, it is never okay to be disrespectful or
+to engage in behavior that violates the projectβs code of conduct.
+
+If you see someone violating the code of conduct, you are encouraged to address
+the behavior directly with those involved. Many issues can be resolved quickly
+and easily, and this gives people more control over the outcome of their
+dispute. If you are unable to resolve the matter for any reason, or if the
+behavior is threatening or harassing, report it. We are dedicated to providing
+an environment where participants feel welcome and safe.
+
+Reports should be directed to *cel-conduct@google.com*, the Project Steward(s)
+for *Common Expression Language (CEL)*. It is the Project Stewardβs duty to
+receive and address reported violations of the code of conduct. They will then
+work with a committee consisting of representatives from the Open Source
+Programs Office and the Google Open Source Strategy team. If for any reason you
+are uncomfortable reaching out to the Project Steward, please email
+opensource@google.com.
+
+We will investigate every complaint, but you may not receive a direct response.
+We will use our discretion in determining when and how to follow up on reported
+incidents, which may range from not taking action to permanent expulsion from
+the project and project-sponsored spaces. We will notify the accused of the
+report and provide them an opportunity to discuss it before any action is taken.
+The identity of the reporter will be omitted from the details of the report
+supplied to the accused. In potentially harmful situations, such as ongoing
+harassment or threats to anyone's safety, we may take action without notice.
+
+## Attribution
+
+This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
+available at
+https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..47634bc4c
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,33 @@
+# How to Contribute
+
+We'd love to accept your patches and contributions to this project.
+
+## Before you begin
+
+### Sign our Contributor License Agreement
+
+Contributions to this project must be accompanied by a
+[Contributor License Agreement](https://cla.developers.google.com/about) (CLA).
+You (or your employer) retain the copyright to your contribution; this simply
+gives us permission to use and redistribute your contributions as part of the
+project.
+
+If you or your current employer have already signed the Google CLA (even if it
+was for a different project), you probably don't need to do it again.
+
+Visit to see your current agreements or to
+sign a new one.
+
+### Review our Community Guidelines
+
+This project follows [Google's Open Source Community
+Guidelines](https://opensource.google/conduct/).
+
+## Contribution process
+
+### Code Reviews
+
+All submissions, including submissions by project members, require review. We
+use GitHub pull requests for this purpose. Consult
+[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
+information on using pull requests.
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 000000000..d64569567
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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
+
+ http://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.
diff --git a/MODULE.bazel b/MODULE.bazel
new file mode 100644
index 000000000..fcaf041ba
--- /dev/null
+++ b/MODULE.bazel
@@ -0,0 +1,140 @@
+# Copyright 2025 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https:#www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module(
+ name = "cel_java",
+)
+
+bazel_dep(name = "bazel_skylib", version = "1.9.0")
+bazel_dep(name = "rules_jvm_external", version = "6.10")
+bazel_dep(name = "protobuf", version = "33.4", repo_name = "com_google_protobuf") # see https://github.com/bazelbuild/rules_android/issues/373
+bazel_dep(name = "googleapis", version = "0.0.0-20260223-edfe7983", repo_name = "com_google_googleapis")
+bazel_dep(name = "rules_pkg", version = "1.2.0")
+bazel_dep(name = "rules_license", version = "1.0.0")
+bazel_dep(name = "rules_proto", version = "7.1.0")
+bazel_dep(name = "rules_java", version = "9.3.0")
+bazel_dep(name = "rules_android", version = "0.7.1")
+bazel_dep(name = "rules_shell", version = "0.6.1")
+bazel_dep(name = "googleapis-java", version = "1.0.0")
+bazel_dep(name = "cel-spec", version = "0.25.1", repo_name = "cel_spec")
+bazel_dep(name = "rules_go", version = "0.50.1")
+
+# Required by cel-spec to satisfy gazelle transitive dependency
+go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk")
+go_sdk.download(version = "1.23.0")
+
+switched_rules = use_extension("@com_google_googleapis//:extensions.bzl", "switched_rules")
+switched_rules.use_languages(java = True)
+use_repo(switched_rules, "com_google_googleapis_imports")
+
+maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
+
+GUAVA_VERSION = "33.5.0"
+
+TRUTH_VERSION = "1.4.4"
+
+PROTOBUF_JAVA_VERSION = "4.33.5"
+
+CEL_VERSION = "0.13.1"
+
+# Compile only artifacts
+[
+ maven.artifact(
+ artifact = artifact,
+ group = group,
+ neverlink = True,
+ version = version,
+ )
+ for group, artifact, version in [coord.split(":") for coord in [
+ "com.google.code.findbugs:annotations:3.0.1",
+ "com.google.errorprone:error_prone_annotations:2.42.0",
+ ]]
+]
+
+# Test only artifacts
+[
+ maven.artifact(
+ testonly = True,
+ artifact = artifact,
+ group = group,
+ version = version,
+ )
+ for group, artifact, version in [coord.split(":") for coord in [
+ "org.mockito:mockito-core:4.11.0",
+ "io.github.classgraph:classgraph:4.8.179",
+ "com.google.testparameterinjector:test-parameter-injector:1.18",
+ "com.google.guava:guava-testlib:" + GUAVA_VERSION + "-jre",
+ "com.google.truth.extensions:truth-java8-extension:" + TRUTH_VERSION,
+ "com.google.truth.extensions:truth-proto-extension:" + TRUTH_VERSION,
+ "com.google.truth.extensions:truth-liteproto-extension:" + TRUTH_VERSION,
+ "com.google.truth:truth:" + TRUTH_VERSION,
+ ]]
+]
+
+maven.install(
+ name = "maven",
+ # keep sorted
+ artifacts = [
+ "com.google.auto.value:auto-value:1.11.0",
+ "com.google.auto.value:auto-value-annotations:1.11.0",
+ "com.google.guava:guava:" + GUAVA_VERSION + "-jre",
+ "com.google.protobuf:protobuf-java:" + PROTOBUF_JAVA_VERSION,
+ "com.google.protobuf:protobuf-java-util:" + PROTOBUF_JAVA_VERSION,
+ "com.google.re2j:re2j:1.8",
+ "info.picocli:picocli:4.7.7",
+ "org.antlr:antlr4-runtime:4.13.2",
+ "org.freemarker:freemarker:2.3.34",
+ "org.jspecify:jspecify:1.0.0",
+ "org.threeten:threeten-extra:1.8.0",
+ "org.yaml:snakeyaml:2.5",
+ ],
+ repositories = [
+ "https://maven.google.com",
+ "https://repo1.maven.org/maven2",
+ ],
+)
+maven.install(
+ name = "maven_android",
+ # keep sorted
+ artifacts = [
+ "com.google.guava:guava:" + GUAVA_VERSION + "-android",
+ "com.google.protobuf:protobuf-javalite:" + PROTOBUF_JAVA_VERSION,
+ ],
+ repositories = [
+ "https://maven.google.com",
+ "https://repo1.maven.org/maven2",
+ ],
+)
+
+# Conformance test only
+
+maven.install(
+ name = "maven_conformance",
+ artifacts = [
+ "dev.cel:cel:" + CEL_VERSION,
+ "dev.cel:compiler:" + CEL_VERSION,
+ "dev.cel:runtime:" + CEL_VERSION,
+ ],
+ repositories = [
+ "https://maven.google.com",
+ "https://repo1.maven.org/maven2",
+ "https://central.sonatype.com/repository/maven-snapshots/",
+ ],
+)
+use_repo(maven, "maven", "maven_android", "maven_conformance")
+
+non_module_dependencies = use_extension("//:repositories.bzl", "non_module_dependencies")
+use_repo(non_module_dependencies, "antlr4_jar")
+use_repo(non_module_dependencies, "bazel_common")
+use_repo(non_module_dependencies, "cel_policy")
diff --git a/README.md b/README.md
index f9a2b8294..e2424a85c 100644
--- a/README.md
+++ b/README.md
@@ -28,19 +28,78 @@ scripting language is too resource intensive.
---
+* [Quick Start](#quick-start)
* [Overview](#overview)
* [Environment Setup](#environment-setup)
- * [Parse and Check](#parse-and-check)
- * [Macros](#macros)
- * [Evaluate](#evaluate)
- * [Errors](#Errors)
-* [Examples](examples/README.md)
+ * [Parsing](#parsing)
+ * [Checking](#checking)
+ * [Macros](#macros)
+ * [Evaluation](#evaluation)
+ * [Errors](#errors)
+ * [Extensions](#extensions)
* [Install](#install)
* [Common Questions](#common-questions)
* [License](#license)
---
+## Quick Start
+
+### Install
+
+CEL-Java is available in Maven Central Repository. [Download the JARs here][8] or add the following to your build dependencies:
+
+**Maven (pom.xml)**:
+
+```xml
+
+ dev.cel
+ cel
+ 0.13.1
+
+```
+
+**Gradle**
+
+```gradle
+implementation 'dev.cel:cel:0.13.1'
+```
+
+Then run this example:
+
+```java
+import dev.cel.common.CelAbstractSyntaxTree;
+import dev.cel.common.CelValidationException;
+import dev.cel.common.types.SimpleType;
+import dev.cel.compiler.CelCompiler;
+import dev.cel.compiler.CelCompilerFactory;
+import dev.cel.runtime.CelEvaluationException;
+import dev.cel.runtime.CelRuntime;
+import dev.cel.runtime.CelRuntimeFactory;
+import java.util.Map;
+
+public class HelloWorld {
+ // Construct the compilation and runtime environments.
+ // These instances are immutable and thus trivially thread-safe and amenable to caching.
+ private static final CelCompiler CEL_COMPILER =
+ CelCompilerFactory.standardCelCompilerBuilder().addVar("my_var", SimpleType.STRING).build();
+ private static final CelRuntime CEL_RUNTIME =
+ CelRuntimeFactory.standardCelRuntimeBuilder().build();
+
+ public void run() throws CelValidationException, CelEvaluationException {
+ // Compile the expression into an Abstract Syntax Tree.
+ CelAbstractSyntaxTree ast = CEL_COMPILER.compile("my_var + '!'").getAst();
+
+ // Plan an executable program instance.
+ CelRuntime.Program program = CEL_RUNTIME.createProgram(ast);
+
+ // Evaluate the program with an input variable.
+ String result = (String) program.eval(Map.of("my_var", "Hello World"));
+ System.out.println(result); // 'Hello World!'
+ }
+}
+```
+
## Overview
Determine the variables and functions you want to provide to CEL. Parse and
@@ -49,22 +108,91 @@ against some input. Checking is optional, but strongly encouraged.
### Environment Setup
-This section will be completed once parser and type-checker has been added.
+Configuration for the entire CEL stack can be done all at once via the
+`CelFactory.standardCelBuilder()`, or can be composed into compilation and
+evaluation via the `CelCompilerFactory` and `CelRuntimeFactory`.
+
+The simplest form of CEL usage is as follows:
+
+```java
+Cel cel = CelFactory.standardCelBuilder().build();
+```
+
+More commonly, your application will want to configure type-checking separately
+from the runtime. Use `CelCompilerFactory` to construct a compilation
+environment and declare the types, macros, variables, and functions to use with
+your CEL application:
+
+```java
+// Example environment for the following expression:
+// resource.name.startsWith('/groups/' + group)
+CelCompiler cel = CelCompilerFactory.standardCelCompilerBuilder()
+ .setStandardMacros(CelStandardMacro.HAS)
+ .setContainer("google.rpc.context.AttributeContext")
+ .addMessageTypes(AttributeContext.getDescriptor())
+ .addVar("resource",
+ StructTypeReference.create("google.rpc.context.AttributeContext.Resource"))
+ .addVar("group", SimpleType.STRING)
+ .build();
+```
+
+More information about the features which are supported on the builder may be
+found in the [`CelCompilerBuilder`][9].
+
+### Parsing
+
+Some CEL use cases only require parsing of an expression in order to be useful.
+For example, one example might want to check whether the expression contains any
+nested comprehensions, or possibly to pass the parsed expression to a C++ or Go
+binary for evaluation. Presently, Java does not support parse-only evaluation.
+
+```java
+CelValidationResult parseResult =
+ cel.parse("resource.name.startsWith('/groups/' + group)");
+try {
+ return parseResult.getAst();
+} catch (CelValidationException e) {
+ // Handle exception...
+}
+```
+
+### Checking
+
+Type-checking is performed on `CelAbstractSyntaxTree` values to ensure that the
+expression is well formed and all variable and function references are defined.
-### Parse and Check
+Type-checking can be performed immediately after parsing an expression:
-Parsing and type-checking support are currently not available in Java but will
-be added in the near future. In the interim, you may consider
-leveraging [Go implementation of CEL][4]
-to produce a type-checked expression to evaluate it in CEL-Java.
+```java
+try {
+ CelValidationResult parseResult =
+ cel.parse("resource.name.startsWith('/groups/' + group)");
+ CelValidationResult checkResult = cel.check(parseResult.getAst());
+ return checkResult.getAst();
+} catch (CelValidationException e) {
+ // Handle exception...
+}
+```
+
+Or, the parse and type-check can be combined into the `compile` call. This is
+likely the more common need.
+
+```java
+CelValidationResult compileResult =
+ cel.compile("resource.name.startsWith('/groups/' + group)");
+try {
+ return compileResult.getAst();
+} catch (CelValidationException e) {
+ // Handle exception...
+}
+```
#### Macros
-Macros are optional but enabled by default. Macros were introduced to support
-optional CEL features that might not be desired in all use cases without the
-syntactic burden and complexity such features might desire if they were part of
-the core CEL syntax. Macros are expanded at parse time and their expansions are
-type-checked at check time.
+Macros were introduced to support optional CEL features that might not be
+desired in all use cases without the syntactic burden and complexity such
+features might desire if they were part of the core CEL syntax. Macros are
+expanded at parse time and their expansions are type-checked at check time.
For example, when macros are enabled it is possible to support bounded iteration
/ fold operators. The macros `all`, `exists`, `exists_one`, `filter`, and `map`
@@ -88,28 +216,37 @@ has(message.field)
Both cases traditionally require special syntax at the language level, but these
features are exposed via macros in CEL.
-### Evaluate
-
-Now, evaluate for fun and profit. The evaluation is thread-safe and side-effect
-free. Many different inputs can be sent to the same `cel.Program` and if fields
-are present in the input, but not referenced in the expression, they are
-ignored.
+Refer to the [CEL Specification][10] for full listings of available macros. To
+leverage them, simply set the desired macros via `setStandardMacros` on the
+builder:
```java
-import dev.cel.common.CelAbstractSyntaxTree;
-import dev.cel.runtime.CelRuntimeFactory;
+CelCompiler.standardCelBuilder()
+ .setStandardMacros(CelStandardMacro.STANDARD_MACROS)
+```
-CelRuntime celRuntime = CelRuntimeFactory.standardCelRuntimeBuilder().build();
-CelAbstractSyntaxTree ast = CelAbstractSyntaxTree.fromCheckedExpr(checkedExpr);
+### Evaluation
+Expressions can be evaluated using once they are type-checked/compiled by
+creating a `CelRuntime.Program` from a `CelAbstractSyntaxTree`:
+
+```java
+CelRuntime celRuntime = CelRuntimeFactory.standardCelRuntimeBuilder().build();
try {
- CelRuntime.Program program=celRuntime.createProgram(ast);
- Object evaluatedResult=program.eval(parameterValues);
-} catch(CelEvaluationException e) {
- throw new IllegalArgumentException("Evaluation error has occurred.",e);
+ CelRuntime.Program program = celRuntime.createProgram(compileResult.getAst());
+ return program.eval(
+ ImmutableMap.of(
+ "resource", Resource.newBuilder().setName("/groups/").build(),
+ "group", "admin"
+ ));
+} catch (CelEvaluationException e) {
+ // Handle evaluation exceptions ...
}
```
+The evaluation is thread-safe and side effect free thus many different inputs can
+be sent to the same `cel.Program`.
+
#### Partial State
In distributed apps it is not uncommon to have edge caches and central services.
@@ -127,7 +264,7 @@ robust to evaluation against dynamic data types such as JSON inputs.
In the following truth-table, the symbols `` and `` represent error or
unknown values, with the `?` indicating that the branch is not taken due to
-short-circuiting. When the result is `` this means that the both args are
+short-circuiting. When the result is `` this means that both the args are
possibly relevant to the result.
| Expression | Result |
@@ -149,14 +286,42 @@ possibly relevant to the result.
### Errors
-This section will be completed once parser and type-checker has been added.
+Parse and check errors have friendly error messages with pointers to where the
+issues occur in source:
+
+```sh
+ERROR: :1:40: undefined field 'undefined'
+ | TestAllTypes{single_int32: 1, undefined: 2}
+ | .......................................^`,
+```
+
+Both the parsed and checked expressions contain source position information
+about each node that appears in the output AST. This information can be used
+to determine error locations at evaluation time as well.
-## Install
+### Extensions
+
+CEL-Java offers a suite of [canonical extensions][11] to support commonly
+needed features that falls outside the CEL specification.
+
+Examples:
+
+```java
+// String manipulation
+'hello hello'.replace('he', 'we') // returns 'wello wello'
+'hello hello hello'.split(' ') // returns ['hello', 'hello', 'hello']
-CEL-Go supports `modules` and uses semantic versioning. For more info see
-the [Go Modules](https://github.com/golang/go/wiki/Modules) docs.
+// Math extensions
+math.greatest(-42.0, -21.5, -100.0) // -21.5
+math.least(-42.0, -21.5, -100.0) // -100.0
-And of course, there is always the option to build from source directly.
+// Proto extensions
+proto.getExt(msg, google.expr.proto2.test.int32_ext) // returns int value
+
+// Local bindings
+cel.bind(a, 'hello',
+ cel.bind(b, 'world', a + b + b + a)) // "helloworldworldhello"
+```
## Common Questions
@@ -189,36 +354,36 @@ runtime bindings and error handling to do the right thing.
### Where can I learn more about the language?
* See the [CEL Spec][1] for the specification and conformance test suite.
-* Ask for support on the [CEL Go Discuss][2] Google group.
+* Ask for support on the [CEL Java Discuss][2] Google group.
### How can I contribute?
* See [CONTRIBUTING.md](./CONTRIBUTING.md) to get started.
-* Use [GitHub Issues][4] to request features or report bugs.
-*
+* Use [GitHub Issues][7] to request features or report bugs.
+
### Dependencies
Java 8 or newer is required.
-| Library | Version | Details |
-|-----------------------|---------|----------------------|
-| [Guava][2] | TBD | N/A |
-| [RE2/J][3] | TBD | N/A |
-| [Protocol Buffers][4] | TBD | Full or lite runtime |
-| [ANTLR4][5] | TBD | Java runtime |
+| Library | Details |
+|-----------------------|----------------------|
+| [Guava][3] | N/A |
+| [RE2/J][4] | N/A |
+| [Protocol Buffers][5] | Full or lite runtime |
+| [ANTLR4][6] | Java runtime |
## License
Released under the [Apache License](LICENSE).
-Disclaimer: This is not an official Google product.
-
-[1]: https://github.com/google/cel-spec
-
+[1]: https://github.com/cel-expr/cel-spec
[2]: https://groups.google.com/forum/#!forum/cel-java-discuss
-
-[3]: https://github.com/google/cel-cpp
-
-[4]: https://github.com/google/cel-go
-
-[5]: https://github.com/google/cel-java/issues
\ No newline at end of file
+[3]: https://github.com/google/guava
+[4]: https://github.com/google/re2j
+[5]: https://github.com/protocolbuffers/protobuf/tree/master/java
+[6]: https://github.com/antlr/antlr4/tree/master/runtime/Java
+[7]: https://github.com/cel-expr/cel-java/issues
+[8]: https://search.maven.org/search?q=g:dev.cel
+[9]: https://github.com/cel-expr/cel-java/blob/main/compiler/src/main/java/dev/cel/compiler/CelCompilerBuilder.java
+[10]: https://github.com/cel-expr/cel-spec/blob/master/doc/langdef.md#macros
+[11]: https://github.com/cel-expr/cel-java/blob/main/extensions/src/main/java/dev/cel/extensions/README.md
diff --git a/antlr.bzl b/antlr.bzl
new file mode 100644
index 000000000..3361366f2
--- /dev/null
+++ b/antlr.bzl
@@ -0,0 +1,203 @@
+# Copyright 2023 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.
+
+"""Build rules to create Java code from an ANTLR4 grammar."""
+
+def antlr4_java_lexer(name, src, package, visibility = None, imports = None, compatible_with = []):
+ """Generates the java source corresponding to an antlr4 lexer definition.
+
+ Args:
+ name: The name of the build rule.
+ src: The antlr4 g4 file containing the lexer rules.
+ package: The Java package to place the output in.
+ visibility: The standard Bazel visibility attribute.
+ imports: A list of antlr4 source imports to use when building the lexer.
+ compatible_with: The standard Bazel compatible_with attribute.
+ """
+ suffixes = ("%s.java", "%s.tokens")
+ imports = imports or []
+
+ import_srcs = []
+ for imp in imports:
+ import_srcs.append(imp)
+ if not imp.endswith(".g4"):
+ continue
+ import_srcs.append("%s.tokens" % imp[:-3])
+ file_prefix = src[:-3] if src.endswith(".g4") else src
+ outs = _make_outs(file_prefix, suffixes)
+ native.genrule(
+ name = name,
+ srcs = [src] + import_srcs,
+ visibility = visibility,
+ outs = outs,
+ tags = _make_tags(package, outs),
+ cmd = ("mkdir $$$$.tmp ; " + "cp $(SRCS) $$$$.tmp/ ; " + "cd $$$$.tmp ; " +
+ ("../$(location //:antlr4_tool) " + src +
+ " -package " + package + " -Werror ; ") + "cd .. ; " + "".join(
+ [
+ " cp $$$$.tmp/%s $(@D)/ ;" % filepath
+ for filepath in _make_outs(file_prefix, suffixes)
+ ],
+ ) + "rm -rf $$$$.tmp"),
+ heuristic_label_expansion = 0,
+ tools = [
+ "//:antlr4_tool",
+ ],
+ compatible_with = compatible_with,
+ )
+
+def antlr4_java_parser(
+ name,
+ src,
+ package,
+ visibility = None,
+ imports = None,
+ listener = True,
+ visitor = False,
+ compatible_with = []):
+ """Generates the java source corresponding to an antlr4 parser definition.
+
+ Args:
+ name: The name of the build rule.
+ src: The antlr4 g4 file containing the parser rules.
+ package: The Java package to place the output in.
+ visibility: The standard Blaze visibility attribute.
+ imports: A list of antlr4 source imports to use when building the parser.
+ listener: Whether or not to include listener generated files.
+ visitor: Whether or not to include visitor generated files.
+ compatible_with: The standard Blaze compatible_with attribute.
+ """
+ suffixes = ("%s.java", "%s.tokens")
+ visitor_flag = " "
+ if visitor:
+ visitor_flag = " -visitor"
+ suffixes += ("%sBaseVisitor.java", "%sVisitor.java")
+ listener_flag = " "
+ if listener:
+ suffixes += ("%sBaseListener.java", "%sListener.java")
+ else:
+ listener_flag = " -no-listener"
+ imports = imports or []
+ import_srcs = []
+ for imp in imports:
+ import_srcs.append(imp)
+ if not imp.endswith(".g4"):
+ continue
+ import_srcs.append("%s.tokens" % imp[:-3])
+ file_prefix = src[:-3] if src.endswith(".g4") else src
+ outs = _make_outs(file_prefix, suffixes)
+
+ native.genrule(
+ name = name,
+ srcs = [src] + import_srcs,
+ visibility = visibility,
+ outs = outs,
+ tags = _make_tags(package, outs),
+ cmd = ("mkdir $$$$.tmp ; " + "cp $(SRCS) $$$$.tmp/ ; " + "cd $$$$.tmp ; " +
+ ("../$(location //:antlr4_tool) " + src +
+ visitor_flag + listener_flag + " -package " + package + " ; ") +
+ "cd .. ; " + (
+ "".join([
+ " cp $$$$.tmp/%s $(@D)/ ;" % filepath
+ for filepath in _make_outs(
+ file_prefix,
+ suffixes,
+ )
+ ])
+ ) + "rm -rf $$$$.tmp"),
+ heuristic_label_expansion = 0,
+ tools = [
+ "//:antlr4_tool",
+ ],
+ compatible_with = compatible_with,
+ )
+
+def antlr4_java_combined(
+ name,
+ src,
+ package,
+ visibility = None,
+ imports = None,
+ listener = True,
+ visitor = False,
+ compatible_with = []):
+ """Generates the java source corresponding to an antlr4 grammar definition.
+
+ This genrule assumes that 'src' starts with 'grammar' and not
+ '(lexer|parser) grammar'
+
+ Args:
+ name: The name of the build rule.
+ src: The antlr4 g4 file containing the rules.
+ package: The Java package to place the output in.
+ visibility: The standard Blaze visibility attribute.
+ imports: A list of antlr4 source imports to use when building the parser.
+ listener: Whether or not to include listener generated files.
+ visitor: Whether or not to include visitor generated files.
+ compatible_with: The standard Blaze compatible_with attribute.
+ """
+ suffixes = ("%sLexer.java", "%sParser.java", "%s.tokens")
+ visitor_flag = " "
+ if visitor:
+ visitor_flag = " -visitor"
+ suffixes += ("%sBaseVisitor.java", "%sVisitor.java")
+ listener_flag = " "
+ if listener:
+ suffixes += ("%sBaseListener.java", "%sListener.java")
+ else:
+ listener_flag = " -no-listener"
+ imports = imports or []
+ import_srcs = []
+ for imp in imports:
+ import_srcs.append(imp)
+ if not imp.endswith(".g4"):
+ continue
+ import_srcs.append("%s.tokens" % imp[:-3])
+ file_prefix = src[:-3] if src.endswith(".g4") else src
+ outs = _make_outs(file_prefix, suffixes)
+
+ native.genrule(
+ name = name,
+ srcs = [src] + import_srcs,
+ visibility = visibility,
+ outs = outs,
+ tags = _make_tags(package, outs),
+ cmd = ("mkdir $$$$.tmp ; " + "cp $(SRCS) $$$$.tmp/ ; " + "cd $$$$.tmp ; " +
+ ("../$(location //:antlr4_tool) " + src +
+ visitor_flag + listener_flag + " -package " + package + " ; ") +
+ "cd .. ; " + (
+ "".join([
+ " cp $$$$.tmp/%s $(@D)/ ;" % filepath
+ for filepath in _make_outs(
+ file_prefix,
+ suffixes,
+ )
+ ])
+ ) + "rm -rf $$$$.tmp"),
+ heuristic_label_expansion = 0,
+ tools = [
+ "//:antlr4_tool",
+ ],
+ compatible_with = compatible_with,
+ )
+
+def _make_outs(file_prefix, suffixes):
+ return [file_suffix % file_prefix for file_suffix in suffixes]
+
+def _make_tags(package, outs):
+ tags = []
+ for file in outs:
+ if file.endswith(".java"):
+ tags.append("generated_java_class=%s.%s" % (package, file[:-5]))
+ return tags
diff --git a/bundle/BUILD.bazel b/bundle/BUILD.bazel
new file mode 100644
index 000000000..1eaf0bec8
--- /dev/null
+++ b/bundle/BUILD.bazel
@@ -0,0 +1,41 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
+package(
+ default_applicable_licenses = ["//:license"],
+ default_visibility = ["//visibility:public"],
+)
+
+java_library(
+ name = "cel",
+ exports = [
+ "//bundle/src/main/java/dev/cel/bundle:cel",
+ "//bundle/src/main/java/dev/cel/bundle:cel_factory",
+ ],
+)
+
+java_library(
+ name = "environment",
+ exports = ["//bundle/src/main/java/dev/cel/bundle:environment"],
+)
+
+java_library(
+ name = "environment_exception",
+ exports = ["//bundle/src/main/java/dev/cel/bundle:environment_exception"],
+)
+
+java_library(
+ name = "environment_yaml_parser",
+ exports = ["//bundle/src/main/java/dev/cel/bundle:environment_yaml_parser"],
+)
+
+java_library(
+ name = "environment_exporter",
+ exports = ["//bundle/src/main/java/dev/cel/bundle:environment_exporter"],
+)
+
+java_library(
+ name = "cel_impl",
+ testonly = 1,
+ visibility = ["//:internal"],
+ exports = ["//bundle/src/main/java/dev/cel/bundle:cel_impl"],
+)
diff --git a/bundle/src/main/java/dev/cel/bundle/BUILD.bazel b/bundle/src/main/java/dev/cel/bundle/BUILD.bazel
new file mode 100644
index 000000000..716442849
--- /dev/null
+++ b/bundle/src/main/java/dev/cel/bundle/BUILD.bazel
@@ -0,0 +1,197 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
+package(
+ default_applicable_licenses = ["//:license"],
+ default_visibility = [
+ "//bundle:__pkg__",
+ "//publish:__pkg__",
+ ],
+)
+
+CEL_SOURCES = [
+ "Cel.java",
+ "CelBuilder.java",
+]
+
+java_library(
+ name = "cel",
+ srcs = CEL_SOURCES,
+ tags = [
+ ],
+ deps = [
+ "//checker:checker_legacy_environment",
+ "//checker:proto_type_mask",
+ "//checker:standard_decl",
+ "//common:compiler_common",
+ "//common:container",
+ "//common:options",
+ "//common/types:type_providers",
+ "//common/values:cel_value_provider",
+ "//compiler:compiler_builder",
+ "//parser:macro",
+ "//runtime",
+ "//runtime:function_binding",
+ "//runtime:standard_functions",
+ "@cel_spec//proto/cel/expr:checked_java_proto",
+ "@maven//:com_google_code_findbugs_annotations",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ "@maven//:com_google_protobuf_protobuf_java",
+ ],
+)
+
+java_library(
+ name = "cel_factory",
+ srcs = ["CelFactory.java"],
+ tags = [
+ ],
+ deps = [
+ ":cel",
+ ":cel_impl",
+ "//checker",
+ "//common:options",
+ "//compiler",
+ "//compiler:compiler_builder",
+ "//parser",
+ "//runtime",
+ "//runtime:runtime_planner_impl",
+ ],
+)
+
+java_library(
+ name = "cel_impl",
+ srcs = ["CelImpl.java"],
+ tags = [
+ ],
+ deps = [
+ ":cel",
+ "//checker:checker_builder",
+ "//checker:proto_type_mask",
+ "//checker:standard_decl",
+ "//checker:type_provider_legacy",
+ "//common:cel_ast",
+ "//common:cel_source",
+ "//common:compiler_common",
+ "//common:container",
+ "//common:options",
+ "//common/internal:env_visitor",
+ "//common/internal:file_descriptor_converter",
+ "//common/types:cel_proto_types",
+ "//common/types:type_providers",
+ "//common/values:cel_value_provider",
+ "//compiler:compiler_builder",
+ "//parser:macro",
+ "//parser:parser_builder",
+ "//runtime",
+ "//runtime:function_binding",
+ "//runtime:runtime_planner_impl",
+ "//runtime:standard_functions",
+ "@cel_spec//proto/cel/expr:checked_java_proto",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ "@maven//:com_google_guava_guava",
+ "@maven//:com_google_protobuf_protobuf_java",
+ ],
+)
+
+java_library(
+ name = "environment",
+ srcs = [
+ "CelEnvironment.java",
+ ],
+ tags = [
+ ],
+ deps = [
+ ":environment_exception",
+ ":required_fields_checker",
+ "//:auto_value",
+ "//bundle:cel",
+ "//checker:proto_type_mask",
+ "//checker:standard_decl",
+ "//common:compiler_common",
+ "//common:container",
+ "//common:options",
+ "//common:source",
+ "//common/types",
+ "//common/types:type_providers",
+ "//compiler:compiler_builder",
+ "//extensions",
+ "//parser:macro",
+ "//runtime",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ "@maven//:com_google_guava_guava",
+ ],
+)
+
+java_library(
+ name = "environment_exception",
+ srcs = [
+ "CelEnvironmentException.java",
+ ],
+ tags = [
+ ],
+ deps = ["//common:cel_exception"],
+)
+
+java_library(
+ name = "environment_yaml_parser",
+ srcs = [
+ "CelEnvironmentYamlParser.java",
+ "CelEnvironmentYamlSerializer.java",
+ ],
+ tags = [
+ ],
+ deps = [
+ ":environment",
+ ":environment_exception",
+ "//common:compiler_common",
+ "//common:container",
+ "//common/formats:file_source",
+ "//common/formats:parser_context",
+ "//common/formats:yaml_helper",
+ "//common/formats:yaml_parser_context_impl",
+ "//common/internal",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ "@maven//:com_google_guava_guava",
+ "@maven//:org_jspecify_jspecify",
+ "@maven//:org_yaml_snakeyaml",
+ ],
+)
+
+java_library(
+ name = "environment_exporter",
+ srcs = [
+ "CelEnvironmentExporter.java",
+ ],
+ tags = [
+ ],
+ deps = [
+ ":environment",
+ "//:auto_value",
+ "//bundle:cel",
+ "//checker:checker_builder",
+ "//checker:standard_decl",
+ "//common:compiler_common",
+ "//common:options",
+ "//common/internal:env_visitor",
+ "//common/types:cel_proto_types",
+ "//common/types:type_providers",
+ "//compiler:compiler_builder",
+ "//extensions",
+ "//extensions:extension_library",
+ "//parser:macro",
+ "@cel_spec//proto/cel/expr:checked_java_proto",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ "@maven//:com_google_guava_guava",
+ ],
+)
+
+java_library(
+ name = "required_fields_checker",
+ srcs = [
+ "RequiredFieldsChecker.java",
+ ],
+ visibility = ["//visibility:private"],
+ deps = [
+ "//:auto_value",
+ "@maven//:com_google_guava_guava",
+ ],
+)
diff --git a/bundle/src/main/java/dev/cel/bundle/Cel.java b/bundle/src/main/java/dev/cel/bundle/Cel.java
new file mode 100644
index 000000000..677a4af2c
--- /dev/null
+++ b/bundle/src/main/java/dev/cel/bundle/Cel.java
@@ -0,0 +1,25 @@
+// 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.
+
+package dev.cel.bundle;
+
+import javax.annotation.concurrent.ThreadSafe;
+import dev.cel.compiler.CelCompiler;
+import dev.cel.runtime.CelRuntime;
+
+/** Cel interface for parse, type-check, and evaluation of CEL programs. */
+@ThreadSafe
+public interface Cel extends CelCompiler, CelRuntime {
+ CelBuilder toCelBuilder();
+}
diff --git a/bundle/src/main/java/dev/cel/bundle/CelBuilder.java b/bundle/src/main/java/dev/cel/bundle/CelBuilder.java
new file mode 100644
index 000000000..f603b479f
--- /dev/null
+++ b/bundle/src/main/java/dev/cel/bundle/CelBuilder.java
@@ -0,0 +1,339 @@
+// 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.
+
+package dev.cel.bundle;
+
+import dev.cel.expr.Decl;
+import dev.cel.expr.Type;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import com.google.protobuf.DescriptorProtos.FileDescriptorSet;
+import com.google.protobuf.Descriptors.Descriptor;
+import com.google.protobuf.Descriptors.FileDescriptor;
+import com.google.protobuf.ExtensionRegistry;
+import com.google.protobuf.Message;
+import dev.cel.checker.CelStandardDeclarations;
+import dev.cel.checker.ProtoTypeMask;
+import dev.cel.checker.TypeProvider;
+import dev.cel.common.CelContainer;
+import dev.cel.common.CelFunctionDecl;
+import dev.cel.common.CelOptions;
+import dev.cel.common.CelVarDecl;
+import dev.cel.common.types.CelType;
+import dev.cel.common.types.CelTypeProvider;
+import dev.cel.common.values.CelValueProvider;
+import dev.cel.compiler.CelCompilerLibrary;
+import dev.cel.parser.CelMacro;
+import dev.cel.parser.CelStandardMacro;
+import dev.cel.runtime.CelFunctionBinding;
+import dev.cel.runtime.CelRuntimeLibrary;
+import dev.cel.runtime.CelStandardFunctions;
+import java.util.function.Function;
+
+/** Interface for building an instance of Cel. */
+public interface CelBuilder {
+
+ /** Set the {@code CelOptions} used to enable fixes and feautres for this CEL instance. */
+ @CanIgnoreReturnValue
+ CelBuilder setOptions(CelOptions options);
+
+ /** Set the {@link CelStandardMacro} values for use with this instance. */
+ @CanIgnoreReturnValue
+ CelBuilder setStandardMacros(CelStandardMacro... macros);
+
+ /** Set the {@link CelStandardMacro} values for use with this instance. */
+ @CanIgnoreReturnValue
+ CelBuilder setStandardMacros(Iterable macros);
+
+ /**
+ * Registers the given macros, replacing any previous macros with the same key.
+ *
+ *
Use this to register a set of user-defined custom macro implementation for the parser. For
+ * registering macros defined as part of CEL standard library, use {@link #setStandardMacros}
+ * instead.
+ *
+ *
Custom macros should not use the same function names as the ones found in {@link
+ * CelStandardMacro} (ex: has, all, exists, etc.). Build method will throw if both standard macros
+ * and custom macros are set with the same name.
+ */
+ @CanIgnoreReturnValue
+ CelBuilder addMacros(CelMacro... macros);
+
+ /**
+ * Registers the given macros, replacing any previous macros with the same key.
+ *
+ *
Use this to register a set of user-defined custom macro implementation for the parser. For
+ * registering macros defined as part of CEL standard library, use {@link #setStandardMacros}
+ * instead.
+ *
+ *
Custom macros should not use the same function names as the ones found in {@link
+ * CelStandardMacro} (ex: has, all, exists, etc.). Build method will throw if both standard macros
+ * and custom macros are set with the same name.
+ */
+ @CanIgnoreReturnValue
+ CelBuilder addMacros(Iterable macros);
+
+ /** Retrieves the currently configured {@link CelContainer} in the builder. */
+ CelContainer container();
+
+ /**
+ * Set the {@link CelContainer} to use as the namespace for resolving CEL expression variables and
+ * functions.
+ */
+ @CanIgnoreReturnValue
+ CelBuilder setContainer(CelContainer container);
+
+ /** Add a variable declaration with a given {@code name} and {@link Type}. */
+ @CanIgnoreReturnValue
+ CelBuilder addVar(String name, Type type);
+
+ /** Add a variable declaration with a given {@code name} and {@link CelType}. */
+ @CanIgnoreReturnValue
+ CelBuilder addVar(String name, CelType type);
+
+ /** Add variable and function {@code declarations} to the CEL environment. */
+ @CanIgnoreReturnValue
+ CelBuilder addDeclarations(Decl... declarations);
+
+ /** Add variable and function {@code declarations} to the CEL environment. */
+ @CanIgnoreReturnValue
+ CelBuilder addDeclarations(Iterable declarations);
+
+ /** Add function declaration {@code CelFunctionDecl} to the CEL environment. */
+ @CanIgnoreReturnValue
+ CelBuilder addFunctionDeclarations(CelFunctionDecl... celFunctionDecls);
+
+ /** Add function declaration {@code CelFunctionDecl} to the CEL environment. */
+ @CanIgnoreReturnValue
+ CelBuilder addFunctionDeclarations(Iterable celFunctionDecls);
+
+ /** Add variable declaration {@code CelVarDecl} to the CEL environment. */
+ @CanIgnoreReturnValue
+ CelBuilder addVarDeclarations(CelVarDecl... celVarDecls);
+
+ /** Add variable declaration {@code CelVarDecl} to the CEL environment. */
+ @CanIgnoreReturnValue
+ CelBuilder addVarDeclarations(Iterable celVarDecls);
+
+ /**
+ * Add one or more {@link ProtoTypeMask} values. The {@code ProtoTypeMask} values will be used to
+ * compute a set of {@code Decl} values using a protobuf message's fields as the names and types
+ * of the variables if {@link ProtoTypeMask#fieldsAreVariableDeclarations} is {@code true}.
+ *
+ *
Note, this feature may not work with custom {@link TypeProvider} implementations out of the
+ * box, as it requires the implementation of {@link TypeProvider#lookupFieldNames} to return the
+ * set of all fields declared on the protobuf type.
+ */
+ @CanIgnoreReturnValue
+ CelBuilder addProtoTypeMasks(ProtoTypeMask... typeMasks);
+
+ /**
+ * Add one or more {@link ProtoTypeMask} values. The {@code ProtoTypeMask} values will be used to
+ * compute a set of {@code Decl} values using a protobuf message's fields as the names and types
+ * of the variables if {@link ProtoTypeMask#fieldsAreVariableDeclarations} is {@code true}.
+ *
+ *
Note, this feature may not work with custom {@link TypeProvider} implementations out of the
+ * box, as it requires the implementation of {@link TypeProvider#lookupFieldNames} to return the
+ * set of all fields declared on the protobuf type.
+ */
+ @CanIgnoreReturnValue
+ CelBuilder addProtoTypeMasks(Iterable typeMasks);
+
+ /**
+ * Add one or more {@link CelFunctionBinding} objects to the CEL runtime.
+ *
+ *
Functions with duplicate overload ids will be replaced in favor of the new overload.
+ */
+ @CanIgnoreReturnValue
+ CelBuilder addFunctionBindings(CelFunctionBinding... bindings);
+
+ /**
+ * Bind a collection of {@link CelFunctionBinding} objects to the runtime.
+ *
+ *
Functions with duplicate overload ids will be replaced in favor of the new overload.
+ */
+ @CanIgnoreReturnValue
+ CelBuilder addFunctionBindings(Iterable bindings);
+
+ /** Adds bindings for functions that are allowed to be late-bound (resolved at execution time). */
+ @CanIgnoreReturnValue
+ CelBuilder addLateBoundFunctions(String... lateBoundFunctionNames);
+
+ /** Adds bindings for functions that are allowed to be late-bound (resolved at execution time). */
+ @CanIgnoreReturnValue
+ CelBuilder addLateBoundFunctions(Iterable lateBoundFunctionNames);
+
+ /** Set the expected {@code resultType} for the type-checked expression. */
+ @CanIgnoreReturnValue
+ CelBuilder setResultType(CelType resultType);
+
+ /**
+ * Set the expected {@code resultType} in proto format described in checked.proto for the
+ * type-checked expression.
+ */
+ @CanIgnoreReturnValue
+ CelBuilder setProtoResultType(Type resultType);
+
+ /**
+ * Set a custom type factory for the runtime.
+ *
+ *
Note: it is valid to combine type factory methods within the runtime. Only the options which
+ * have been configured will be used.
+ *
+ *
The type creation search order is as follows:
+ *
+ *
+ * Custom type factory ({@link #setTypeFactory})
+ * Custom descriptor set {{@link #addMessageTypes})
+ *
+ */
+ @CanIgnoreReturnValue
+ CelBuilder setTypeFactory(Function typeFactory);
+
+ /**
+ * Sets the {@code celValueProvider} for resolving values during evaluation. The provided value
+ * provider will be used first before falling back to the built-in {@link
+ * dev.cel.common.values.ProtoMessageValueProvider} for resolving protobuf messages.
+ *
+ *
Note that {@link CelOptions#enableCelValue()} must be enabled or this method will be a
+ * no-op.
+ */
+ @CanIgnoreReturnValue
+ CelBuilder setValueProvider(CelValueProvider celValueProvider);
+
+ /**
+ * Set the {@code typeProvider} for use with type-checking expressions.
+ *
+ * @deprecated Use {@link #setTypeProvider(CelTypeProvider)} instead.
+ */
+ @CanIgnoreReturnValue
+ @Deprecated
+ CelBuilder setTypeProvider(TypeProvider typeProvider);
+
+ /** Set the {@code celTypeProvider} for use with type-checking expressions. */
+ @CanIgnoreReturnValue
+ CelBuilder setTypeProvider(CelTypeProvider celTypeProvider);
+
+ /**
+ * Add message {@link Descriptor}s to the use for type-checking and object creation at
+ * interpretation time.
+ *
+ *
This method may be safely combined with {@link #setTypeFactory}, {@link #setTypeProvider},
+ * and {@link #addFileTypes} calls.
+ *
+ *
If either a {@code typeFactory} or {@code typeProvider} are configured, these classes will
+ * take precedence over any dynamic resolution of data related to the descriptors.
+ */
+ @CanIgnoreReturnValue
+ CelBuilder addMessageTypes(Descriptor... descriptors);
+
+ /**
+ * Add message {@link Descriptor}s to the use for type-checking and object creation at
+ * interpretation time.
+ *
+ *
This method may be safely combined with {@link #setTypeFactory}, {@link #setTypeProvider},
+ * and {@link #addFileTypes} calls.
+ *
+ *
If either a {@code typeFactory} or {@code typeProvider} are configured, these classes will
+ * take precedence over any dynamic resolution of data related to the descriptors.
+ */
+ @CanIgnoreReturnValue
+ CelBuilder addMessageTypes(Iterable descriptors);
+
+ /**
+ * Add {@link FileDescriptor}s to the use for type-checking, and for object creation at
+ * interpretation time.
+ *
+ *
This method may be safely combined with {@link #setTypeFactory}, {@link #setTypeProvider},
+ * and {@link #addMessageTypes} calls.
+ *
+ *
If either a {@code typeFactory} or {@code typeProvider} are configured, these classes will
+ * take precedence over any dynamic resolution of data related to the descriptors.
+ */
+ @CanIgnoreReturnValue
+ CelBuilder addFileTypes(FileDescriptor... fileDescriptors);
+
+ /**
+ * Add {@link FileDescriptor}s to the use for type-checking, and for object creation at
+ * interpretation time.
+ *
+ *
This method may be safely combined with {@link #setTypeFactory}, {@link #setTypeProvider},
+ * and {@link #addMessageTypes} calls.
+ *
+ *
If either a {@code typeFactory} or {@code typeProvider} are configured, these classes will
+ * take precedence over any dynamic resolution of data related to the descriptors.
+ */
+ @CanIgnoreReturnValue
+ CelBuilder addFileTypes(Iterable fileDescriptors);
+
+ /**
+ * Add all of the {@link FileDescriptor}s in a {@code FileDescriptorSet} to the use for
+ * type-checking, and for object creation at interpretation time.
+ *
+ *
This method may be safely combined with {@link #setTypeFactory}, {@link #setTypeProvider},
+ * and {@link #addMessageTypes} calls.
+ *
+ *
If either a {@code typeFactory} or {@code typeProvider} are configured, these classes will
+ * take precedence over any dynamic resolution of data related to the descriptors.
+ */
+ @CanIgnoreReturnValue
+ CelBuilder addFileTypes(FileDescriptorSet fileDescriptorSet);
+
+ /** Enable or disable the standard CEL library functions and variables */
+ @CanIgnoreReturnValue
+ CelBuilder setStandardEnvironmentEnabled(boolean value);
+
+ /** Adds one or more libraries for parsing and type-checking. */
+ @CanIgnoreReturnValue
+ CelBuilder addCompilerLibraries(CelCompilerLibrary... libraries);
+
+ /** Adds a collection of libraries for parsing and type-checking. */
+ @CanIgnoreReturnValue
+ CelBuilder addCompilerLibraries(Iterable libraries);
+
+ /** Adds one or more libraries for runtime. */
+ @CanIgnoreReturnValue
+ CelBuilder addRuntimeLibraries(CelRuntimeLibrary... libraries);
+
+ /** Adds a collection of libraries for runtime. */
+ @CanIgnoreReturnValue
+ CelBuilder addRuntimeLibraries(Iterable libraries);
+
+ /**
+ * Override the standard declarations for the type-checker. This can be used to subset the
+ * standard environment to only expose the desired declarations to the type-checker. {@link
+ * #setStandardEnvironmentEnabled(boolean)} must be set to false for this to take effect.
+ */
+ @CanIgnoreReturnValue
+ CelBuilder setStandardDeclarations(CelStandardDeclarations standardDeclarations);
+
+ /**
+ * Override the standard functions for the runtime. This can be used to subset the standard
+ * environment to only expose the desired function overloads to the runtime.
+ *
+ *
{@link #setStandardEnvironmentEnabled(boolean)} must be set to false for this to take
+ * effect.
+ */
+ @CanIgnoreReturnValue
+ CelBuilder setStandardFunctions(CelStandardFunctions standardFunctions);
+
+ /**
+ * Sets a proto ExtensionRegistry to assist with unpacking Any messages containing a proto2
+ extension field.
+ */
+ @CanIgnoreReturnValue
+ CelBuilder setExtensionRegistry(ExtensionRegistry extensionRegistry);
+
+ /** Construct a new {@code Cel} instance from the provided configuration. */
+ Cel build();
+}
diff --git a/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java b/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java
new file mode 100644
index 000000000..ccbaef61b
--- /dev/null
+++ b/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java
@@ -0,0 +1,1112 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dev.cel.bundle;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import com.google.errorprone.annotations.CheckReturnValue;
+import dev.cel.bundle.CelEnvironment.LibrarySubset.FunctionSelector;
+import dev.cel.checker.CelStandardDeclarations;
+import dev.cel.checker.CelStandardDeclarations.StandardFunction;
+import dev.cel.checker.CelStandardDeclarations.StandardOverload;
+import dev.cel.checker.ProtoTypeMask;
+import dev.cel.common.CelContainer;
+import dev.cel.common.CelFunctionDecl;
+import dev.cel.common.CelOptions;
+import dev.cel.common.CelOverloadDecl;
+import dev.cel.common.CelVarDecl;
+import dev.cel.common.Source;
+import dev.cel.common.types.CelType;
+import dev.cel.common.types.CelTypeProvider;
+import dev.cel.common.types.ListType;
+import dev.cel.common.types.MapType;
+import dev.cel.common.types.OptionalType;
+import dev.cel.common.types.SimpleType;
+import dev.cel.common.types.TypeParamType;
+import dev.cel.common.types.TypeType;
+import dev.cel.compiler.CelCompiler;
+import dev.cel.compiler.CelCompilerBuilder;
+import dev.cel.compiler.CelCompilerLibrary;
+import dev.cel.extensions.CelExtensions;
+import dev.cel.parser.CelStandardMacro;
+import dev.cel.runtime.CelRuntime;
+import dev.cel.runtime.CelRuntimeBuilder;
+import dev.cel.runtime.CelRuntimeLibrary;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.function.ObjIntConsumer;
+
+/**
+ * CelEnvironment is a native representation of a CEL environment for compiler and runtime. This
+ * object is amenable to being serialized into YAML, textproto or other formats as needed.
+ */
+@AutoValue
+public abstract class CelEnvironment {
+
+ @VisibleForTesting
+ static final ImmutableMap CEL_EXTENSION_CONFIG_MAP =
+ ImmutableMap.of(
+ "bindings", CanonicalCelExtension.BINDINGS,
+ "encoders", CanonicalCelExtension.ENCODERS,
+ "lists", CanonicalCelExtension.LISTS,
+ "math", CanonicalCelExtension.MATH,
+ "optional", CanonicalCelExtension.OPTIONAL,
+ "protos", CanonicalCelExtension.PROTOS,
+ "regex", CanonicalCelExtension.REGEX,
+ "sets", CanonicalCelExtension.SETS,
+ "strings", CanonicalCelExtension.STRINGS,
+ "two-var-comprehensions", CanonicalCelExtension.COMPREHENSIONS);
+
+ private static final ImmutableMap> LIMIT_HANDLERS =
+ ImmutableMap.of(
+ "cel.limit.expression_code_points",
+ CelOptions.Builder::maxExpressionCodePointSize,
+ "cel.limit.parse_error_recovery",
+ CelOptions.Builder::maxParseErrorRecoveryLimit,
+ "cel.limit.parse_recursion_depth",
+ CelOptions.Builder::maxParseRecursionDepth);
+
+ private static final ImmutableMap FEATURE_HANDLERS =
+ ImmutableMap.of(
+ "cel.feature.macro_call_tracking",
+ CelOptions.Builder::populateMacroCalls,
+ "cel.feature.backtick_escape_syntax",
+ CelOptions.Builder::enableQuotedIdentifierSyntax,
+ "cel.feature.cross_type_numeric_comparisons",
+ CelOptions.Builder::enableHeterogeneousNumericComparisons);
+
+ /** Environment source in textual format (ex: textproto, YAML). */
+ public abstract Optional source();
+
+ /** Name of the environment. */
+ public abstract String name();
+
+ /** Container, which captures default namespace and aliases for value resolution. */
+ public abstract Optional container();
+
+ /**
+ * An optional description of the environment (example: location of the file containing the config
+ * content).
+ */
+ public abstract String description();
+
+ /** Converts this {@code CelEnvironment} object into a builder. */
+ public abstract Builder toBuilder();
+
+ /**
+ * Canonical extensions to enable in the environment, such as Optional, String and Math
+ * extensions.
+ */
+ public abstract ImmutableSet extensions();
+
+ /** New variable declarations to add in the compilation environment. */
+ public abstract ImmutableSet variables();
+
+ /** New function declarations to add in the compilation environment. */
+ public abstract ImmutableSet functions();
+
+ /** Standard library subset (which macros, functions to include/exclude) */
+ public abstract Optional standardLibrarySubset();
+
+ /** Feature flags to enable in the environment. */
+ public abstract ImmutableSet features();
+
+ /** Limits to set in the environment. */
+ public abstract ImmutableSet limits();
+
+ /** Context variable to enable in the environment. */
+ public abstract Optional contextVariable();
+
+ /** Builder for {@link CelEnvironment}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ public abstract ImmutableSet.Builder extensionsBuilder();
+
+ // For testing only, to empty out the source.
+ abstract Builder setSource(Optional source);
+
+ public abstract Builder setSource(Source source);
+
+ public abstract Builder setName(String name);
+
+ public abstract Builder setDescription(String description);
+
+ public abstract Builder setContainer(CelContainer container);
+
+ @CanIgnoreReturnValue
+ public Builder setContainer(String container) {
+ return setContainer(CelContainer.ofName(container));
+ }
+
+ @CanIgnoreReturnValue
+ public Builder addExtensions(ExtensionConfig... extensions) {
+ checkNotNull(extensions);
+ return addExtensions(Arrays.asList(extensions));
+ }
+
+ @CanIgnoreReturnValue
+ public Builder addExtensions(Iterable extensions) {
+ checkNotNull(extensions);
+ this.extensionsBuilder().addAll(extensions);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setVariables(VariableDecl... variables) {
+ return setVariables(ImmutableSet.copyOf(variables));
+ }
+
+ public abstract Builder setVariables(ImmutableSet variables);
+
+ @CanIgnoreReturnValue
+ public Builder setFunctions(FunctionDecl... functions) {
+ return setFunctions(ImmutableSet.copyOf(functions));
+ }
+
+ public abstract Builder setFunctions(ImmutableSet functions);
+
+ public abstract Builder setStandardLibrarySubset(LibrarySubset stdLibrarySubset);
+
+ @CanIgnoreReturnValue
+ public Builder setFeatures(FeatureFlag... featureFlags) {
+ return setFeatures(ImmutableSet.copyOf(featureFlags));
+ }
+
+ public abstract Builder setFeatures(ImmutableSet featureFlags);
+
+ @CanIgnoreReturnValue
+ public Builder setLimits(Limit... limits) {
+ return setLimits(ImmutableSet.copyOf(limits));
+ }
+
+ public abstract Builder setLimits(ImmutableSet limits);
+
+ public abstract Builder setContextVariable(ContextVariable contextVariable);
+
+ abstract CelEnvironment autoBuild();
+
+ @CheckReturnValue
+ public final CelEnvironment build() {
+ CelEnvironment env = autoBuild();
+ LibrarySubset librarySubset = env.standardLibrarySubset().orElse(null);
+ if (librarySubset != null) {
+ if (!librarySubset.includedMacros().isEmpty()
+ && !librarySubset.excludedMacros().isEmpty()) {
+ throw new IllegalArgumentException(
+ "Invalid subset: cannot both include and exclude macros");
+ }
+ if (!librarySubset.includedFunctions().isEmpty()
+ && !librarySubset.excludedFunctions().isEmpty()) {
+ throw new IllegalArgumentException(
+ "Invalid subset: cannot both include and exclude functions");
+ }
+ }
+ return env;
+ }
+ }
+
+ /** Creates a new builder to construct a {@link CelEnvironment} instance. */
+ public static Builder newBuilder() {
+ return new AutoValue_CelEnvironment.Builder()
+ .setName("")
+ .setDescription("")
+ .setVariables(ImmutableSet.of())
+ .setFunctions(ImmutableSet.of())
+ .setFeatures(ImmutableSet.of())
+ .setLimits(ImmutableSet.of());
+ }
+
+ /** Extends the provided {@link CelCompiler} environment with this configuration. */
+ public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)
+ throws CelEnvironmentException {
+ celOptions = applyEnvironmentOptions(celOptions);
+ try {
+ CelTypeProvider celTypeProvider = celCompiler.getTypeProvider();
+ CelCompilerBuilder compilerBuilder =
+ celCompiler
+ .toCompilerBuilder()
+ .setOptions(celOptions)
+ .setTypeProvider(celTypeProvider)
+ .addVarDeclarations(
+ variables().stream()
+ .map(v -> v.toCelVarDecl(celTypeProvider))
+ .collect(toImmutableList()))
+ .addFunctionDeclarations(
+ functions().stream()
+ .map(f -> f.toCelFunctionDecl(celTypeProvider))
+ .collect(toImmutableList()));
+
+ container().ifPresent(compilerBuilder::setContainer);
+
+ addAllCompilerExtensions(compilerBuilder, celOptions);
+
+ applyStandardLibrarySubset(compilerBuilder);
+
+ contextVariable()
+ .ifPresent(
+ cv ->
+ compilerBuilder.addProtoTypeMasks(
+ ProtoTypeMask.ofAllFields(cv.typeName()).withFieldsAsVariableDeclarations()));
+
+ return compilerBuilder.build();
+ } catch (RuntimeException e) {
+ throw new CelEnvironmentException(e.getMessage(), e);
+ }
+ }
+
+ /** Extends the provided {@link Cel} environment with this configuration. */
+ public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException {
+ celOptions = applyEnvironmentOptions(celOptions);
+ try {
+ // Casting is necessary to only extend the compiler here
+ CelCompiler celCompiler = extend((CelCompiler) cel, celOptions);
+
+ CelRuntime celRuntime = extendRuntime(cel, celOptions);
+
+ return CelFactory.combine(celCompiler, celRuntime);
+ } catch (RuntimeException e) {
+ throw new CelEnvironmentException(e.getMessage(), e);
+ }
+ }
+
+ private CelOptions applyEnvironmentOptions(CelOptions celOptions) {
+ CelOptions.Builder optionsBuilder = celOptions.toBuilder();
+ for (FeatureFlag featureFlag : features()) {
+ BooleanOptionConsumer consumer = FEATURE_HANDLERS.get(featureFlag.name());
+ if (consumer == null) {
+ throw new IllegalArgumentException("Unknown feature flag: " + featureFlag.name());
+ }
+ consumer.accept(optionsBuilder, featureFlag.enabled());
+ }
+ for (Limit limit : limits()) {
+ int value = limit.value() < 0 ? -1 : limit.value();
+ ObjIntConsumer consumer = LIMIT_HANDLERS.get(limit.name());
+ if (consumer == null) {
+ throw new IllegalArgumentException("Unknown limit: " + limit.name());
+ }
+ consumer.accept(optionsBuilder, value);
+ }
+ return optionsBuilder.build();
+ }
+
+ private void addAllCompilerExtensions(
+ CelCompilerBuilder celCompilerBuilder, CelOptions celOptions) {
+ // TODO: Add capability to accept user defined exceptions
+ for (ExtensionConfig extensionConfig : extensions()) {
+ CanonicalCelExtension extension = getExtensionOrThrow(extensionConfig.name());
+ if (extension.compilerExtensionProvider() != null) {
+ CelCompilerLibrary celCompilerLibrary =
+ extension
+ .compilerExtensionProvider()
+ .getCelCompilerLibrary(celOptions, extensionConfig.version());
+ celCompilerBuilder.addLibraries(celCompilerLibrary);
+ }
+ }
+ }
+
+ private CelRuntime extendRuntime(CelRuntime celRuntime, CelOptions celOptions) {
+ CelRuntimeBuilder celRuntimeBuilder = celRuntime.toRuntimeBuilder();
+ celRuntimeBuilder.setOptions(celOptions);
+ // TODO: Add capability to accept user defined exceptions
+ for (ExtensionConfig extensionConfig : extensions()) {
+ CanonicalCelExtension extension = getExtensionOrThrow(extensionConfig.name());
+ if (extension.runtimeExtensionProvider() != null) {
+ CelRuntimeLibrary celRuntimeLibrary =
+ extension
+ .runtimeExtensionProvider()
+ .getCelRuntimeLibrary(celOptions, extensionConfig.version());
+ celRuntimeBuilder.addLibraries(celRuntimeLibrary);
+ }
+ }
+ return celRuntimeBuilder.build();
+ }
+
+ private void applyStandardLibrarySubset(CelCompilerBuilder compilerBuilder) {
+ if (!standardLibrarySubset().isPresent()) {
+ return;
+ }
+
+ LibrarySubset librarySubset = standardLibrarySubset().get();
+ if (librarySubset.disabled()) {
+ compilerBuilder.setStandardEnvironmentEnabled(false);
+ return;
+ }
+
+ if (librarySubset.macrosDisabled()) {
+ compilerBuilder.setStandardMacros(ImmutableList.of());
+ } else if (!librarySubset.includedMacros().isEmpty()) {
+ compilerBuilder.setStandardMacros(
+ librarySubset.includedMacros().stream()
+ .flatMap(name -> getStandardMacrosOrThrow(name).stream())
+ .collect(toImmutableSet()));
+ } else if (!librarySubset.excludedMacros().isEmpty()) {
+ ImmutableSet set =
+ librarySubset.excludedMacros().stream()
+ .flatMap(name -> getStandardMacrosOrThrow(name).stream())
+ .collect(toImmutableSet());
+ compilerBuilder.setStandardMacros(
+ CelStandardMacro.STANDARD_MACROS.stream()
+ .filter(macro -> !set.contains(macro))
+ .collect(toImmutableSet()));
+ }
+
+ if (!librarySubset.includedFunctions().isEmpty()) {
+ ImmutableSet includedFunctions = librarySubset.includedFunctions();
+ compilerBuilder
+ .setStandardEnvironmentEnabled(false)
+ .setStandardDeclarations(
+ CelStandardDeclarations.newBuilder()
+ .filterFunctions(
+ (function, overload) ->
+ FunctionSelector.matchesAny(function, overload, includedFunctions))
+ .build());
+ } else if (!librarySubset.excludedFunctions().isEmpty()) {
+ ImmutableSet excludedFunctions = librarySubset.excludedFunctions();
+ compilerBuilder
+ .setStandardEnvironmentEnabled(false)
+ .setStandardDeclarations(
+ CelStandardDeclarations.newBuilder()
+ .filterFunctions(
+ (function, overload) ->
+ !FunctionSelector.matchesAny(function, overload, excludedFunctions))
+ .build());
+ }
+ }
+
+ private static ImmutableSet getStandardMacrosOrThrow(String macroName) {
+ ImmutableSet.Builder builder = ImmutableSet.builder();
+ for (CelStandardMacro macro : CelStandardMacro.STANDARD_MACROS) {
+ if (macro.getFunction().equals(macroName)) {
+ builder.add(macro);
+ }
+ }
+ ImmutableSet macros = builder.build();
+ if (macros.isEmpty()) {
+ throw new IllegalArgumentException("unrecognized standard macro `" + macroName + "'");
+ }
+ return macros;
+ }
+
+ private static CanonicalCelExtension getExtensionOrThrow(String extensionName) {
+ CanonicalCelExtension extension = CEL_EXTENSION_CONFIG_MAP.get(extensionName);
+ if (extension == null) {
+ throw new IllegalArgumentException("Unrecognized extension: " + extensionName);
+ }
+
+ return extension;
+ }
+
+ /** Represents a context variable declaration. */
+ @AutoValue
+ public abstract static class ContextVariable {
+ /** Fully qualified type name of the context variable. */
+ public abstract String typeName();
+
+ public static ContextVariable create(String typeName) {
+ return new AutoValue_CelEnvironment_ContextVariable(typeName);
+ }
+ }
+
+ /** Represents a policy variable declaration. */
+ @AutoValue
+ public abstract static class VariableDecl {
+
+ /** Fully qualified variable name. */
+ public abstract String name();
+
+ /** The type of the variable. */
+ public abstract TypeDecl type();
+
+ public abstract Optional description();
+
+ /** Builder for {@link VariableDecl}. */
+ @AutoValue.Builder
+ public abstract static class Builder implements RequiredFieldsChecker {
+
+ public abstract Optional name();
+
+ public abstract Optional type();
+
+ public abstract VariableDecl.Builder setName(String name);
+
+ public abstract VariableDecl.Builder setType(TypeDecl typeDecl);
+
+ public abstract VariableDecl.Builder setDescription(String name);
+
+ @Override
+ public ImmutableList requiredFields() {
+ return ImmutableList.of(
+ RequiredField.of("name", this::name), RequiredField.of("type", this::type));
+ }
+
+ /** Builds a new instance of {@link VariableDecl}. */
+ public abstract VariableDecl build();
+ }
+
+ public static VariableDecl.Builder newBuilder() {
+ return new AutoValue_CelEnvironment_VariableDecl.Builder();
+ }
+
+ /** Creates a new builder to construct a {@link VariableDecl} instance. */
+ public static VariableDecl create(String name, TypeDecl type) {
+ return newBuilder().setName(name).setType(type).build();
+ }
+
+ /** Converts this policy variable declaration into a {@link CelVarDecl}. */
+ public CelVarDecl toCelVarDecl(CelTypeProvider celTypeProvider) {
+ return CelVarDecl.newVarDeclaration(name(), type().toCelType(celTypeProvider));
+ }
+ }
+
+ /** Represents a policy function declaration. */
+ @AutoValue
+ public abstract static class FunctionDecl {
+
+ public abstract String name();
+
+ public abstract Optional description();
+
+ public abstract ImmutableSet overloads();
+
+ /** Builder for {@link FunctionDecl}. */
+ @AutoValue.Builder
+ public abstract static class Builder implements RequiredFieldsChecker {
+
+ public abstract Optional name();
+
+ public abstract Optional> overloads();
+
+ public abstract FunctionDecl.Builder setName(String name);
+
+ public abstract FunctionDecl.Builder setDescription(String description);
+
+ public abstract FunctionDecl.Builder setOverloads(ImmutableSet overloads);
+
+ @Override
+ public ImmutableList requiredFields() {
+ return ImmutableList.of(
+ RequiredField.of("name", this::name), RequiredField.of("overloads", this::overloads));
+ }
+
+ /** Builds a new instance of {@link FunctionDecl}. */
+ public abstract FunctionDecl build();
+ }
+
+ /** Creates a new builder to construct a {@link FunctionDecl} instance. */
+ public static FunctionDecl.Builder newBuilder() {
+ return new AutoValue_CelEnvironment_FunctionDecl.Builder();
+ }
+
+ /** Creates a new {@link FunctionDecl} with the provided function name and its overloads. */
+ public static FunctionDecl create(String name, ImmutableSet overloads) {
+ return newBuilder().setName(name).setOverloads(overloads).build();
+ }
+
+ /** Converts this policy function declaration into a {@link CelFunctionDecl}. */
+ public CelFunctionDecl toCelFunctionDecl(CelTypeProvider celTypeProvider) {
+ return CelFunctionDecl.newFunctionDeclaration(
+ name(),
+ overloads().stream()
+ .map(o -> o.toCelOverloadDecl(celTypeProvider))
+ .collect(toImmutableList()));
+ }
+ }
+
+ /** Represents an overload declaration on a policy function. */
+ @AutoValue
+ public abstract static class OverloadDecl {
+
+ /**
+ * A unique overload ID. Required. This should follow the typical naming convention used in CEL
+ * (e.g: targetType_func_argType1_argType...)
+ */
+ public abstract String id();
+
+ /** Target of the function overload if it's a receiver style (example: foo in `foo.f(...)`) */
+ public abstract Optional target();
+
+ /** List of function overload type values. */
+ public abstract ImmutableList arguments();
+
+ /** Examples for the overload. */
+ public abstract ImmutableList examples();
+
+ /** Return type of the overload. Required. */
+ public abstract TypeDecl returnType();
+
+ /** Builder for {@link OverloadDecl}. */
+ @AutoValue.Builder
+ public abstract static class Builder implements RequiredFieldsChecker {
+
+ public abstract Optional id();
+
+ public abstract Optional returnType();
+
+ public abstract OverloadDecl.Builder setId(String overloadId);
+
+ public abstract OverloadDecl.Builder setTarget(TypeDecl target);
+
+ // This should stay package-private to encourage add/set methods to be used instead.
+ abstract ImmutableList.Builder argumentsBuilder();
+
+ abstract ImmutableList.Builder examplesBuilder();
+
+ public abstract OverloadDecl.Builder setArguments(ImmutableList args);
+
+ @CanIgnoreReturnValue
+ public OverloadDecl.Builder addExamples(Iterable examples) {
+ this.examplesBuilder().addAll(checkNotNull(examples));
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public OverloadDecl.Builder addExamples(String... examples) {
+ return addExamples(Arrays.asList(examples));
+ }
+
+ @CanIgnoreReturnValue
+ public OverloadDecl.Builder addArguments(Iterable args) {
+ this.argumentsBuilder().addAll(checkNotNull(args));
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public OverloadDecl.Builder addArguments(TypeDecl... args) {
+ return addArguments(Arrays.asList(args));
+ }
+
+ public abstract OverloadDecl.Builder setReturnType(TypeDecl returnType);
+
+ @Override
+ public ImmutableList requiredFields() {
+ return ImmutableList.of(
+ RequiredField.of("id", this::id), RequiredField.of("return", this::returnType));
+ }
+
+ /** Builds a new instance of {@link OverloadDecl}. */
+ @CheckReturnValue
+ public abstract OverloadDecl build();
+ }
+
+ /** Creates a new builder to construct a {@link OverloadDecl} instance. */
+ public static OverloadDecl.Builder newBuilder() {
+ return new AutoValue_CelEnvironment_OverloadDecl.Builder().setArguments(ImmutableList.of());
+ }
+
+ /** Converts this policy function overload into a {@link CelOverloadDecl}. */
+ public CelOverloadDecl toCelOverloadDecl(CelTypeProvider celTypeProvider) {
+ CelOverloadDecl.Builder builder =
+ CelOverloadDecl.newBuilder()
+ .setIsInstanceFunction(false)
+ .setOverloadId(id())
+ .setResultType(returnType().toCelType(celTypeProvider));
+
+ target()
+ .ifPresent(
+ t ->
+ builder
+ .setIsInstanceFunction(true)
+ .addParameterTypes(t.toCelType(celTypeProvider)));
+
+ for (TypeDecl type : arguments()) {
+ builder.addParameterTypes(type.toCelType(celTypeProvider));
+ }
+
+ return builder.build();
+ }
+ }
+
+ /**
+ * Represents an abstract type declaration used to declare functions and variables in a policy.
+ */
+ @AutoValue
+ public abstract static class TypeDecl {
+
+ public abstract String name();
+
+ public abstract ImmutableList params();
+
+ public abstract boolean isTypeParam();
+
+ /** Builder for {@link TypeDecl}. */
+ @AutoValue.Builder
+ public abstract static class Builder implements RequiredFieldsChecker {
+
+ public abstract Optional name();
+
+ public abstract TypeDecl.Builder setName(String name);
+
+ // This should stay package-private to encourage add/set methods to be used instead.
+ abstract ImmutableList.Builder paramsBuilder();
+
+ public abstract TypeDecl.Builder setParams(ImmutableList typeDecls);
+
+ @CanIgnoreReturnValue
+ public TypeDecl.Builder addParams(TypeDecl... params) {
+ return addParams(Arrays.asList(params));
+ }
+
+ @CanIgnoreReturnValue
+ public TypeDecl.Builder addParams(Iterable params) {
+ this.paramsBuilder().addAll(checkNotNull(params));
+ return this;
+ }
+
+ public abstract TypeDecl.Builder setIsTypeParam(boolean isTypeParam);
+
+ @Override
+ public ImmutableList requiredFields() {
+ return ImmutableList.of(RequiredField.of("type_name", this::name));
+ }
+
+ @CheckReturnValue
+ public abstract TypeDecl build();
+ }
+
+ /** Creates a new {@link TypeDecl} with the provided name. */
+ public static TypeDecl create(String name) {
+ return newBuilder().setName(name).build();
+ }
+
+ public static TypeDecl.Builder newBuilder() {
+ return new AutoValue_CelEnvironment_TypeDecl.Builder().setIsTypeParam(false);
+ }
+
+ /** Converts this type declaration into a {@link CelType}. */
+ public CelType toCelType(CelTypeProvider celTypeProvider) {
+ switch (name()) {
+ case "list":
+ if (params().size() != 1) {
+ throw new IllegalArgumentException(
+ "List type has unexpected param count: " + params().size());
+ }
+
+ CelType elementType = params().get(0).toCelType(celTypeProvider);
+ return ListType.create(elementType);
+ case "map":
+ if (params().size() != 2) {
+ throw new IllegalArgumentException(
+ "Map type has unexpected param count: " + params().size());
+ }
+
+ CelType keyType = params().get(0).toCelType(celTypeProvider);
+ CelType valueType = params().get(1).toCelType(celTypeProvider);
+ return MapType.create(keyType, valueType);
+ case "type":
+ checkState(
+ params().size() == 1, "Expected 1 parameter for type, got %s", params().size());
+ return TypeType.create(params().get(0).toCelType(celTypeProvider));
+ default:
+ if (isTypeParam()) {
+ return TypeParamType.create(name());
+ }
+
+ if (name().equals("dyn")) {
+ return SimpleType.DYN;
+ }
+
+ CelType simpleType = SimpleType.findByName(name()).orElse(null);
+ if (simpleType != null) {
+ return simpleType;
+ }
+
+ if (name().equals(OptionalType.NAME)) {
+ checkState(
+ params().size() == 1,
+ "Optional type must have exactly 1 parameter. Found %s",
+ params().size());
+ return OptionalType.create(params().get(0).toCelType(celTypeProvider));
+ }
+
+ return celTypeProvider
+ .findType(name())
+ .orElseThrow(() -> new IllegalArgumentException("Undefined type name: " + name()));
+ }
+ }
+ }
+
+ /** Represents a feature flag that can be enabled in the environment. */
+ @AutoValue
+ public abstract static class FeatureFlag {
+ /** Normalized name of the feature flag. */
+ public abstract String name();
+
+ /** Whether the feature is enabled or disabled. */
+ public abstract boolean enabled();
+
+ public static FeatureFlag create(String name, boolean enabled) {
+ return new AutoValue_CelEnvironment_FeatureFlag(name, enabled);
+ }
+ }
+
+ /**
+ * Represents a configurable limit in the environment.
+ *
+ *