diff --git a/common/BUILD.bazel b/common/BUILD.bazel index b67069de7..9cb0c2f7b 100644 --- a/common/BUILD.bazel +++ b/common/BUILD.bazel @@ -8,7 +8,65 @@ package( java_library( name = "compiler_common", - exports = ["//common/src/main/java/dev/cel/common:compiler_common"], + deprecation = "Please use the granular targets (e.g. :cel_issue, :cel_validation_result, etc.) instead.", + exports = [ + ":cel_function_decl", + ":cel_issue", + ":cel_overload_decl", + ":cel_validation_exception", + ":cel_validation_result", + ":cel_var_decl", + ], +) + +java_library( + name = "cel_function_decl", + exports = ["//common/src/main/java/dev/cel/common:cel_function_decl"], +) + +java_library( + name = "cel_overload_decl", + exports = ["//common/src/main/java/dev/cel/common:cel_overload_decl"], +) + +java_library( + name = "cel_var_decl", + exports = ["//common/src/main/java/dev/cel/common:cel_var_decl"], +) + +cel_android_library( + name = "cel_var_decl_android", + exports = ["//common/src/main/java/dev/cel/common:cel_var_decl_android"], +) + +java_library( + name = "cel_issue", + exports = ["//common/src/main/java/dev/cel/common:cel_issue"], +) + +cel_android_library( + name = "cel_issue_android", + exports = ["//common/src/main/java/dev/cel/common:cel_issue_android"], +) + +java_library( + name = "cel_validation_exception", + exports = ["//common/src/main/java/dev/cel/common:cel_validation_exception"], +) + +cel_android_library( + name = "cel_validation_exception_android", + exports = ["//common/src/main/java/dev/cel/common:cel_validation_exception_android"], +) + +java_library( + name = "cel_validation_result", + exports = ["//common/src/main/java/dev/cel/common:cel_validation_result"], +) + +cel_android_library( + name = "cel_validation_result_android", + exports = ["//common/src/main/java/dev/cel/common:cel_validation_result_android"], ) java_library( @@ -74,6 +132,11 @@ java_library( exports = ["//common/src/main/java/dev/cel/common:source_location"], ) +cel_android_library( + name = "source_location_android", + exports = ["//common/src/main/java/dev/cel/common:source_location_android"], +) + java_library( name = "cel_source", exports = ["//common/src/main/java/dev/cel/common:cel_source"], diff --git a/common/ast/BUILD.bazel b/common/ast/BUILD.bazel index 1361ad76b..9b7573a7c 100644 --- a/common/ast/BUILD.bazel +++ b/common/ast/BUILD.bazel @@ -53,6 +53,11 @@ java_library( exports = ["//common/src/main/java/dev/cel/common/ast:expr_factory"], ) +cel_android_library( + name = "expr_factory_android", + exports = ["//common/src/main/java/dev/cel/common/ast:expr_factory_android"], +) + java_library( name = "mutable_expr", exports = ["//common/src/main/java/dev/cel/common/ast:mutable_expr"], diff --git a/common/src/main/java/dev/cel/common/BUILD.bazel b/common/src/main/java/dev/cel/common/BUILD.bazel index 11b762220..73475e623 100644 --- a/common/src/main/java/dev/cel/common/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/BUILD.bazel @@ -9,16 +9,6 @@ package( ], ) -# keep sorted -COMPILER_COMMON_SOURCES = [ - "CelFunctionDecl.java", - "CelIssue.java", - "CelOverloadDecl.java", - "CelValidationException.java", - "CelValidationResult.java", - "CelVarDecl.java", -] - # keep sorted SOURCE_SOURCES = [ "Source.java", @@ -67,28 +57,151 @@ java_library( ) java_library( - name = "compiler_common", - srcs = COMPILER_COMMON_SOURCES, + name = "cel_function_decl", + srcs = ["CelFunctionDecl.java"], tags = [ ], deps = [ - ":cel_ast", - ":cel_exception", - ":cel_source", - ":source", - ":source_location", + ":cel_overload_decl", "//:auto_value", "//common/annotations", - "//common/internal:safe_string_formatter", + "@cel_spec//proto/cel/expr:checked_java_proto", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "cel_overload_decl", + srcs = ["CelOverloadDecl.java"], + tags = [ + ], + deps = [ + "//:auto_value", "//common/types:cel_proto_types", "//common/types:type_providers", "@cel_spec//proto/cel/expr:checked_java_proto", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "cel_var_decl", + srcs = ["CelVarDecl.java"], + tags = [ + ], + deps = [ + "//:auto_value", + "//common/types:type_providers", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +cel_android_library( + name = "cel_var_decl_android", + srcs = ["CelVarDecl.java"], + tags = [ + ], + deps = [ + "//:auto_value", + "//common/types:type_providers_android", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +java_library( + name = "cel_issue", + srcs = ["CelIssue.java"], + tags = [ + ], + deps = [ + ":source", + ":source_location", + "//:auto_value", + "//common/internal:safe_string_formatter", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +cel_android_library( + name = "cel_issue_android", + srcs = ["CelIssue.java"], + tags = [ + ], + deps = [ + ":source_android", + ":source_location_android", + "//:auto_value", + "//common/internal:safe_string_formatter", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven_android//:com_google_guava_guava", + ], +) + +java_library( + name = "cel_validation_exception", + srcs = ["CelValidationException.java"], + tags = [ + ], + deps = [ + ":cel_exception", + ":cel_issue", + ":cel_source", + "//common/annotations", + "@maven//:com_google_guava_guava", + ], +) + +cel_android_library( + name = "cel_validation_exception_android", + srcs = ["CelValidationException.java"], + tags = [ + ], + deps = [ + ":cel_exception", + ":cel_issue_android", + ":cel_source_android", + "//common/annotations", + "@maven_android//:com_google_guava_guava", + ], +) + +java_library( + name = "cel_validation_result", + srcs = ["CelValidationResult.java"], + tags = [ + ], + deps = [ + ":cel_ast", + ":cel_issue", + ":cel_source", + ":cel_validation_exception", + "//common/annotations", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", "@maven//:org_jspecify_jspecify", ], ) +cel_android_library( + name = "cel_validation_result_android", + srcs = ["CelValidationResult.java"], + tags = [ + ], + deps = [ + ":cel_ast_android", + ":cel_issue_android", + ":cel_source_android", + ":cel_validation_exception_android", + "//common/annotations", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:org_jspecify_jspecify", + "@maven_android//:com_google_guava_guava", + ], +) + java_library( name = "cel_exception", srcs = ["CelException.java"], @@ -345,7 +458,8 @@ cel_android_library( cel_android_library( name = "source_location_android", srcs = ["CelSourceLocation.java"], - visibility = ["//visibility:private"], + tags = [ + ], deps = [ "//:auto_value", "@maven//:com_google_errorprone_error_prone_annotations", diff --git a/common/src/main/java/dev/cel/common/CelValidationException.java b/common/src/main/java/dev/cel/common/CelValidationException.java index 18bec2fe6..edbd9a0c0 100644 --- a/common/src/main/java/dev/cel/common/CelValidationException.java +++ b/common/src/main/java/dev/cel/common/CelValidationException.java @@ -14,8 +14,8 @@ package dev.cel.common; -import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; +import dev.cel.common.annotations.Internal; import java.util.List; /** Base class for all checked exceptions explicitly thrown by the library during parsing. */ @@ -27,7 +27,7 @@ public final class CelValidationException extends CelException { private final CelSource source; private final ImmutableList errors; - @VisibleForTesting + @Internal public CelValidationException(CelSource source, List errors) { super(safeJoinErrorMessage(source, errors)); this.source = source; @@ -49,8 +49,9 @@ private static String safeJoinErrorMessage(CelSource source, List erro List truncatedErrors = errors.subList(0, MAX_ERRORS_TO_REPORT); return CelIssue.toDisplayString(truncatedErrors, source) - + String.format( - "%n...and %d more errors (truncated)", errors.size() - MAX_ERRORS_TO_REPORT); + + "\n...and " + + (errors.size() - MAX_ERRORS_TO_REPORT) + + " more errors (truncated)"; } /** Returns the {@link CelSource} that was being validated. */ diff --git a/common/src/main/java/dev/cel/common/ast/BUILD.bazel b/common/src/main/java/dev/cel/common/ast/BUILD.bazel index c72857080..14cb75dd9 100644 --- a/common/src/main/java/dev/cel/common/ast/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/ast/BUILD.bazel @@ -156,6 +156,19 @@ java_library( ], ) +cel_android_library( + name = "expr_factory_android", + srcs = EXPR_FACTORY_SOURCES, + tags = [ + ], + deps = [ + ":ast_android", + "//common/annotations", + "//common/values:cel_byte_string", + "@maven_android//:com_google_guava_guava", + ], +) + java_library( name = "mutable_expr", srcs = MUTABLE_EXPR_SOURCES, diff --git a/parser/BUILD.bazel b/parser/BUILD.bazel index 8bd568183..38c7bbea3 100644 --- a/parser/BUILD.bazel +++ b/parser/BUILD.bazel @@ -1,4 +1,5 @@ load("@rules_java//java:defs.bzl", "java_library") +load("//:cel_android_rules.bzl", "cel_android_library") package( default_applicable_licenses = ["//:license"], @@ -22,11 +23,38 @@ java_library( exports = ["//parser/src/main/java/dev/cel/parser:parser_factory"], ) +java_library( + name = "lite_parser_factory", + exports = ["//parser/src/main/java/dev/cel/parser:lite_parser_factory"], +) + +cel_android_library( + name = "lite_parser_factory_android", + exports = ["//parser/src/main/java/dev/cel/parser:lite_parser_factory_android"], +) + +java_library( + name = "parser_base", + visibility = ["//:internal"], + exports = ["//parser/src/main/java/dev/cel/parser:parser_base"], +) + +cel_android_library( + name = "parser_base_android", + visibility = ["//:internal"], + exports = ["//parser/src/main/java/dev/cel/parser:parser_base_android"], +) + java_library( name = "parser_builder", exports = ["//parser/src/main/java/dev/cel/parser:parser_builder"], ) +cel_android_library( + name = "parser_builder_android", + exports = ["//parser/src/main/java/dev/cel/parser:parser_builder_android"], +) + java_library( name = "unparser_visitor", exports = ["//parser/src/main/java/dev/cel/parser:unparser_visitor"], @@ -37,6 +65,11 @@ java_library( exports = ["//parser/src/main/java/dev/cel/parser:macro"], ) +cel_android_library( + name = "macro_android", + exports = ["//parser/src/main/java/dev/cel/parser:macro_android"], +) + java_library( name = "cel_g4_visitors", visibility = ["//:internal"], diff --git a/parser/src/main/java/dev/cel/parser/BUILD.bazel b/parser/src/main/java/dev/cel/parser/BUILD.bazel index 848209380..e0bd48e12 100644 --- a/parser/src/main/java/dev/cel/parser/BUILD.bazel +++ b/parser/src/main/java/dev/cel/parser/BUILD.bazel @@ -1,4 +1,5 @@ load("@rules_java//java:defs.bzl", "java_library") +load("//:cel_android_rules.bzl", "cel_android_library") package( default_applicable_licenses = ["//:license"], @@ -8,18 +9,29 @@ package( ], ) -# keep sorted -PARSER_SOURCES = [ - "CelParserImpl.java", - "Parser.java", -] - # keep sorted ANTLR_PARSER_SOURCES = [ "AntlrParser.java", "ExpressionBalancer.java", ] +# keep sorted +LITE_PARSER_FACTORY_SOURCES = [ + "CelLiteParserFactory.java", + "LiteParserImpl.java", +] + +# keep sorted +PARSER_BASE_SOURCES = [ + "CelParserBase.java", +] + +# keep sorted +PARSER_SOURCES = [ + "CelParserImpl.java", + "Parser.java", +] + # keep sorted PRATT_PARSER_SOURCES = [ "Lexer.java", @@ -60,6 +72,78 @@ java_library( ], ) +java_library( + name = "parser_base", + srcs = PARSER_BASE_SOURCES, + tags = [ + ], + deps = [ + ":macro", + ":parser_builder", + "//common:cel_source", + "//common:cel_validation_result", + "//common:options", + "//common/annotations", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +cel_android_library( + name = "parser_base_android", + srcs = PARSER_BASE_SOURCES, + tags = [ + ], + deps = [ + ":macro_android", + ":parser_builder_android", + "//common:cel_source_android", + "//common:cel_validation_result_android", + "//common:options", + "//common/annotations", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven_android//:com_google_guava_guava", + ], +) + +java_library( + name = "lite_parser_factory", + srcs = LITE_PARSER_FACTORY_SOURCES, + tags = [ + ], + deps = [ + ":macro", + ":parser_base", + ":parser_builder", + ":pratt_parser", + "//common:cel_source", + "//common:cel_validation_result", + "//common:options", + "//common/annotations", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +cel_android_library( + name = "lite_parser_factory_android", + srcs = LITE_PARSER_FACTORY_SOURCES, + tags = [ + ], + deps = [ + ":macro_android", + ":parser_base_android", + ":parser_builder_android", + ":pratt_parser_android", + "//common:cel_source_android", + "//common:cel_validation_result_android", + "//common:options", + "//common/annotations", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven_android//:com_google_guava_guava", + ], +) + java_library( name = "parser", srcs = PARSER_SOURCES, @@ -68,10 +152,11 @@ java_library( deps = [ ":antlr_parser", ":macro", + ":parser_base", ":parser_builder", ":pratt_parser", "//common:cel_source", - "//common:compiler_common", + "//common:cel_validation_result", "//common:options", "//common/annotations", "//common/internal:env_visitor", @@ -88,8 +173,9 @@ java_library( deps = [ ":macro", "//common:cel_ast", + "//common:cel_issue", "//common:cel_source", - "//common:compiler_common", + "//common:cel_validation_result", "//common:operator", "//common:options", "//common:source_location", @@ -111,8 +197,9 @@ java_library( deps = [ ":macro", "//common:cel_ast", + "//common:cel_issue", "//common:cel_source", - "//common:compiler_common", + "//common:cel_validation_result", "//common:operator", "//common:options", "//common:source_location", @@ -123,6 +210,25 @@ java_library( ], ) +cel_android_library( + name = "pratt_parser_android", + srcs = PRATT_PARSER_SOURCES, + deps = [ + ":macro_android", + "//common:cel_ast_android", + "//common:cel_issue_android", + "//common:cel_source_android", + "//common:cel_validation_result_android", + "//common:operator_android", + "//common:options", + "//common:source_location_android", + "//common/ast:ast_android", + "//common/internal:internal_android", + "@maven//:org_jspecify_jspecify", + "@maven_android//:com_google_guava_guava", + ], +) + java_library( name = "parser_builder", srcs = PARSER_BUILDER_SOURCES, @@ -131,7 +237,21 @@ java_library( deps = [ ":macro", "//common:cel_source", - "//common:compiler_common", + "//common:cel_validation_result", + "//common:options", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +cel_android_library( + name = "parser_builder_android", + srcs = PARSER_BUILDER_SOURCES, + tags = [ + ], + deps = [ + ":macro_android", + "//common:cel_source_android", + "//common:cel_validation_result_android", "//common:options", "@maven//:com_google_errorprone_error_prone_annotations", ], @@ -144,7 +264,7 @@ java_library( ], deps = [ "//:auto_value", - "//common:compiler_common", + "//common:cel_issue", "//common:operator", "//common:source_location", "//common/ast", @@ -154,6 +274,23 @@ java_library( ], ) +cel_android_library( + name = "macro_android", + srcs = MACRO_SOURCES, + tags = [ + ], + deps = [ + "//:auto_value", + "//common:cel_issue_android", + "//common:operator_android", + "//common:source_location_android", + "//common/ast:ast_android", + "//common/ast:expr_factory_android", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven_android//:com_google_guava_guava", + ], +) + java_library( name = "unparser", srcs = UNPARSER_SOURCES, diff --git a/parser/src/main/java/dev/cel/parser/CelLiteParserFactory.java b/parser/src/main/java/dev/cel/parser/CelLiteParserFactory.java new file mode 100644 index 000000000..3bfce1c69 --- /dev/null +++ b/parser/src/main/java/dev/cel/parser/CelLiteParserFactory.java @@ -0,0 +1,38 @@ +// 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. + +package dev.cel.parser; + +import dev.cel.common.CelOptions; +import dev.cel.common.annotations.Beta; + +/** + * Factory class for producing light-weight {@link CelParser} instances and builders using the Pratt + * parser. + */ +@Beta +public final class CelLiteParserFactory { + + /** + * Configure a builder to construct a light-weight {@code CelParser} instance. + * + *

Note, the {@link CelOptions#current} are enabled by default with {@link + * CelOptions#enablePrattParser()} explicitly set to {@code true}. + */ + public static CelParserBuilder newLiteParserBuilder() { + return LiteParserImpl.newBuilder(); + } + + private CelLiteParserFactory() {} +} diff --git a/parser/src/main/java/dev/cel/parser/CelMacro.java b/parser/src/main/java/dev/cel/parser/CelMacro.java index cf75714a0..243baf477 100644 --- a/parser/src/main/java/dev/cel/parser/CelMacro.java +++ b/parser/src/main/java/dev/cel/parser/CelMacro.java @@ -158,12 +158,12 @@ public static CelMacro newReceiverVarArgMacro(String function, CelMacroExpander static String formatKey(String function, int argCount, boolean receiverStyle) { checkArgument(!isNullOrEmpty(function)); checkArgument(argCount >= 0); - return String.format("%s:%d:%s", function, argCount, receiverStyle); + return function + ":" + argCount + ":" + receiverStyle; } static String formatVarArgKey(String function, boolean receiverStyle) { checkArgument(!isNullOrEmpty(function)); - return String.format("%s:*:%s", function, receiverStyle); + return function + ":*:" + receiverStyle; } @AutoValue.Builder diff --git a/parser/src/main/java/dev/cel/parser/CelParserBase.java b/parser/src/main/java/dev/cel/parser/CelParserBase.java new file mode 100644 index 000000000..8f528e9f3 --- /dev/null +++ b/parser/src/main/java/dev/cel/parser/CelParserBase.java @@ -0,0 +1,227 @@ +// 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. + +package dev.cel.parser; + +import static com.google.common.base.Preconditions.checkNotNull; + +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.Immutable; +import dev.cel.common.CelOptions; +import dev.cel.common.CelSource; +import dev.cel.common.CelValidationResult; +import dev.cel.common.annotations.Internal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * Abstract base class containing shared logic for CEL parser implementations. + * + *

CEL Library Internals. Do Not Use. + */ +@Immutable +@Internal +abstract class CelParserBase implements CelParser { + + private final ImmutableMap macros; + private final ImmutableMap customMacros; + private final CelOptions options; + private final ImmutableList standardMacros; + + @SuppressWarnings("Immutable") // Interface not marked as immutable, however it should be. + private final ImmutableSet parserLibraries; + + @Override + public final CelValidationResult parse(String expression, String description) { + return parse(CelSource.newBuilder(expression).setDescription(description).build()); + } + + /** Return the options the {@link CelParser} was originally created with. */ + final CelOptions getOptions() { + return options; + } + + final ImmutableMap getMacros() { + return macros; + } + + final ImmutableList getStandardMacros() { + return standardMacros; + } + + final ImmutableSet getParserLibraries() { + return parserLibraries; + } + + final Optional findMacro(String key) { + return Optional.ofNullable(macros.get(key)); + } + + @CanIgnoreReturnValue + protected final > B populateBuilder(B builder) { + checkNotNull(builder); + + return builder + .setOptions(options) + .setStandardMacros(standardMacros) + .addMacros(customMacros.values()) + .addLibraries(parserLibraries); + } + + abstract static class Builder> implements CelParserBuilder { + + private final Set standardMacros; + private final Map macros; + private final Set celParserLibraries; + private CelOptions options; + + @SuppressWarnings("unchecked") // Safe cast for fluent builder chaining in subclasses. + protected B self() { + return (B) this; + } + + @CanIgnoreReturnValue + @Override + public B setStandardMacros(CelStandardMacro... macros) { + checkNotNull(macros); + return setStandardMacros(Arrays.asList(macros)); + } + + @CanIgnoreReturnValue + @Override + public B setStandardMacros(Iterable macros) { + checkNotNull(macros); + this.standardMacros.clear(); + for (CelStandardMacro macro : macros) { + this.standardMacros.add(checkNotNull(macro)); + } + return self(); + } + + @CanIgnoreReturnValue + @Override + public B addMacros(CelMacro... macros) { + checkNotNull(macros); + return addMacros(Arrays.asList(macros)); + } + + @CanIgnoreReturnValue + @Override + public B addMacros(Iterable macros) { + checkNotNull(macros); + for (CelMacro m : macros) { + CelMacro macro = checkNotNull(m); + this.macros.put(macro.getKey(), macro); + } + return self(); + } + + @CanIgnoreReturnValue + @Override + public B addLibraries(CelParserLibrary... libraries) { + checkNotNull(libraries); + return this.addLibraries(Arrays.asList(libraries)); + } + + @CanIgnoreReturnValue + @Override + public B addLibraries(Iterable libraries) { + checkNotNull(libraries); + for (CelParserLibrary library : libraries) { + this.celParserLibraries.add(checkNotNull(library)); + } + return self(); + } + + @CanIgnoreReturnValue + @Override + public B setOptions(CelOptions options) { + this.options = checkNotNull(options); + return self(); + } + + @Override + public CelOptions getOptions() { + return this.options; + } + + // Exists for test assertions in CelParserImplTest. + List getStandardMacros() { + return new ArrayList<>(this.standardMacros); + } + + // Exists for test assertions in CelParserImplTest. + Map getMacros() { + return this.macros; + } + + // Exists for test assertions in CelParserImplTest. + ImmutableSet.Builder getParserLibraries() { + return ImmutableSet.builder().addAll(this.celParserLibraries); + } + + protected ImmutableList buildStandardMacros() { + return ImmutableList.copyOf(standardMacros); + } + + protected ImmutableMap buildCustomMacros() { + return ImmutableMap.copyOf(macros); + } + + protected ImmutableMap buildMacroMap() { + ImmutableMap.Builder macroMapBuilder = ImmutableMap.builder(); + macroMapBuilder.putAll(macros); + for (CelStandardMacro standardMacro : standardMacros) { + CelMacro celMacro = standardMacro.getDefinition(); + macroMapBuilder.put(celMacro.getKey(), celMacro); + } + return macroMapBuilder.buildOrThrow(); + } + + protected ImmutableSet buildLibraries() { + ImmutableSet parserLibrarySet = ImmutableSet.copyOf(celParserLibraries); + parserLibrarySet.forEach(celLibrary -> celLibrary.setParserOptions(this)); + return parserLibrarySet; + } + + protected Builder() { + this.macros = new HashMap<>(); + this.celParserLibraries = new LinkedHashSet<>(); + this.standardMacros = new LinkedHashSet<>(); + this.options = CelOptions.DEFAULT; + } + } + + protected CelParserBase( + ImmutableMap allMacros, + ImmutableMap customMacros, + CelOptions options, + ImmutableList standardMacros, + ImmutableSet parserLibraries) { + this.macros = checkNotNull(allMacros); + this.customMacros = checkNotNull(customMacros); + this.options = checkNotNull(options); + this.standardMacros = checkNotNull(standardMacros); + this.parserLibraries = checkNotNull(parserLibraries); + } +} diff --git a/parser/src/main/java/dev/cel/parser/CelParserImpl.java b/parser/src/main/java/dev/cel/parser/CelParserImpl.java index 8ee9a3457..ecd018653 100644 --- a/parser/src/main/java/dev/cel/parser/CelParserImpl.java +++ b/parser/src/main/java/dev/cel/parser/CelParserImpl.java @@ -15,14 +15,10 @@ package dev.cel.parser; import static com.google.common.base.Preconditions.checkNotNull; -import static java.util.stream.Collectors.toCollection; -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.common.collect.Iterables; -import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CheckReturnValue; import com.google.errorprone.annotations.Immutable; import dev.cel.common.CelOptions; @@ -31,14 +27,6 @@ import dev.cel.common.annotations.Internal; import dev.cel.common.internal.EnvVisitable; import dev.cel.common.internal.EnvVisitor; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; /** * Modernized parser implementation for CEL. @@ -48,188 +36,53 @@ */ @Immutable @Internal -public final class CelParserImpl implements CelParser, EnvVisitable { - - // Common feature flags to be used with all calls. - - // Set of macros configured for parsing. - private final ImmutableMap macros; - - // Specific options for limits on parsing power. - private final CelOptions options; - - private final ImmutableList standardMacros; - - @SuppressWarnings("Immutable") // Interface not marked as immutable, however it should be. - private final ImmutableSet parserLibraries; +public final class CelParserImpl extends CelParserBase implements EnvVisitable { /** Creates a new {@link Builder}. */ - public static CelParserBuilder newBuilder() { + public static Builder newBuilder() { return new Builder().setOptions(CelOptions.DEFAULT); } - @Override - public CelValidationResult parse(String expression, String description) { - return parse(CelSource.newBuilder(expression).setDescription(description).build()); - } - @Override public CelValidationResult parse(CelSource source) { - return Parser.parse(this, source, getOptions()); + return Parser.parse(this, checkNotNull(source), getOptions()); } @Override - public CelParserBuilder toParserBuilder() { - HashSet standardMacroKeys = - standardMacros.stream() - .map(s -> s.getDefinition().getKey()) - .collect(Collectors.toCollection(HashSet::new)); - - return new Builder() - .setOptions(options) - .setStandardMacros(standardMacros) - .addMacros( - // Separate standard macros from the custom macros before constructing the builder - macros.values().stream() - .filter(m -> !standardMacroKeys.contains(m.getKey())) - .collect(toCollection(ArrayList::new))) - .addLibraries(parserLibraries); - } - - Optional findMacro(String key) { - return Optional.ofNullable(macros.get(key)); + public Builder toParserBuilder() { + return populateBuilder(new Builder()); } - ImmutableMap getMacros() { - return macros; - } - - /** Return the options the {@link CelParser} was originally created with. */ - public CelOptions getOptions() { - return options; + @Override + public void accept(EnvVisitor visitor) { + getMacros().forEach((name, macro) -> visitor.visitMacro(macro)); } /** Builder for {@link CelParserImpl}. */ - public static final class Builder implements CelParserBuilder { - - private final List standardMacros; - private final Map macros; - private final ImmutableSet.Builder celParserLibraries; - private CelOptions options; - - @Override - public CelParserBuilder setStandardMacros(CelStandardMacro... macros) { - checkNotNull(macros); - return setStandardMacros(Arrays.asList(macros)); - } - - @Override - public CelParserBuilder setStandardMacros(Iterable macros) { - checkNotNull(macros); - this.standardMacros.clear(); - Iterables.addAll(this.standardMacros, macros); - return this; - } - - @Override - public CelParserBuilder addMacros(CelMacro... macros) { - checkNotNull(macros); - return addMacros(Arrays.asList(macros)); - } - - @Override - public CelParserBuilder addMacros(Iterable macros) { - checkNotNull(macros); - for (CelMacro m : macros) { - CelMacro macro = checkNotNull(m); - this.macros.put(macro.getKey(), macro); - } - return this; - } - - @Override - public CelParserBuilder addLibraries(CelParserLibrary... libraries) { - checkNotNull(libraries); - return this.addLibraries(Arrays.asList(libraries)); - } - - @Override - public CelParserBuilder addLibraries(Iterable libraries) { - checkNotNull(libraries); - this.celParserLibraries.addAll(libraries); - return this; - } - - @CanIgnoreReturnValue - @Override - public Builder setOptions(CelOptions options) { - this.options = checkNotNull(options); - return this; - } - - @Override - public CelOptions getOptions() { - return this.options; - } - - // The following getters exist for asserting immutability for collections held by this builder, - // and shouldn't be exposed to the public. - @VisibleForTesting - List getStandardMacros() { - return this.standardMacros; - } - - @VisibleForTesting - Map getMacros() { - return this.macros; - } - - @VisibleForTesting - ImmutableSet.Builder getParserLibraries() { - return this.celParserLibraries; - } + public static final class Builder extends CelParserBase.Builder { @Override @CheckReturnValue public CelParserImpl build() { - ImmutableSet parserLibrarySet = celParserLibraries.build(); - - // Add libraries, such as extensions - parserLibrarySet.forEach(celLibrary -> celLibrary.setParserOptions(this)); - - ImmutableMap.Builder macroMapBuilder = ImmutableMap.builder(); - macroMapBuilder.putAll(macros); - standardMacros.stream() - .map(CelStandardMacro::getDefinition) - .forEach(celMacro -> macroMapBuilder.put(celMacro.getKey(), celMacro)); + ImmutableSet parserLibrarySet = buildLibraries(); return new CelParserImpl( - macroMapBuilder.buildOrThrow(), - options, - ImmutableList.copyOf(standardMacros), - celParserLibraries.build()); + buildMacroMap(), + buildCustomMacros(), + getOptions(), + buildStandardMacros(), + parserLibrarySet); } - private Builder() { - this.macros = new HashMap<>(); - this.celParserLibraries = ImmutableSet.builder(); - this.standardMacros = new ArrayList<>(); - } + private Builder() {} } private CelParserImpl( ImmutableMap macros, + ImmutableMap customMacros, CelOptions options, ImmutableList standardMacros, ImmutableSet parserLibraries) { - this.macros = macros; - this.options = checkNotNull(options); - this.standardMacros = standardMacros; - this.parserLibraries = parserLibraries; - } - - @Override - public void accept(EnvVisitor visitor) { - macros.forEach((name, macro) -> visitor.visitMacro(macro)); + super(macros, customMacros, options, standardMacros, parserLibraries); } } diff --git a/parser/src/main/java/dev/cel/parser/LiteParserImpl.java b/parser/src/main/java/dev/cel/parser/LiteParserImpl.java new file mode 100644 index 000000000..b88712608 --- /dev/null +++ b/parser/src/main/java/dev/cel/parser/LiteParserImpl.java @@ -0,0 +1,89 @@ +// 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. + +package dev.cel.parser; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.CheckReturnValue; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.CelOptions; +import dev.cel.common.CelSource; +import dev.cel.common.CelValidationResult; +import dev.cel.common.annotations.Internal; + +/** + * Modernized lite parser implementation for CEL using the Pratt parser. + * + *

CEL Library Internals. Do Not Use. Consumers should use {@link CelLiteParserFactory} instead. + */ +@Immutable +@Internal +final class LiteParserImpl extends CelParserBase { + + static Builder newBuilder() { + return new Builder(); + } + + @Override + public CelValidationResult parse(CelSource source) { + return PrattParser.parse(checkNotNull(source), getOptions(), getMacros()); + } + + @Override + public CelParserBuilder toParserBuilder() { + return populateBuilder(new Builder()); + } + + static final class Builder extends CelParserBase.Builder { + + /** Throws if an unsupported flag in CelOptions is toggled. */ + private static void assertAllowedCelOptions(CelOptions celOptions) { + String prefix = "Misconfigured CelOptions: "; + if (!celOptions.enablePrattParser()) { + throw new IllegalArgumentException(prefix + "enablePrattParser cannot be disabled."); + } + } + + @Override + @CheckReturnValue + public CelParser build() { + ImmutableSet parserLibrarySet = buildLibraries(); + assertAllowedCelOptions(getOptions()); + + return new LiteParserImpl( + buildMacroMap(), + buildCustomMacros(), + getOptions(), + buildStandardMacros(), + parserLibrarySet); + } + + private Builder() { + setOptions(CelOptions.current().enablePrattParser(true).build()); + } + } + + private LiteParserImpl( + ImmutableMap macros, + ImmutableMap customMacros, + CelOptions options, + ImmutableList standardMacros, + ImmutableSet parserLibraries) { + super(macros, customMacros, options, standardMacros, parserLibraries); + } +} diff --git a/parser/src/main/java/dev/cel/parser/PrattParser.java b/parser/src/main/java/dev/cel/parser/PrattParser.java index 1fe2a4bab..829ac1dcc 100644 --- a/parser/src/main/java/dev/cel/parser/PrattParser.java +++ b/parser/src/main/java/dev/cel/parser/PrattParser.java @@ -34,6 +34,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import org.jspecify.annotations.Nullable; @@ -41,6 +42,8 @@ /** Pratt parser implementation for CEL. */ final class PrattParser { + private static final Locale LOCALE = Locale.US; + /** Sentinel stored in {@link #positions} for expression ids that have no source position. */ private static final int NO_POSITION = -1; @@ -154,8 +157,10 @@ static CelValidationResult parse( CelIssue.formatError( CelSourceLocation.NONE, String.format( + LOCALE, "expression code point size exceeds limit: size: %d, limit %d", - source.getContent().size(), options.maxExpressionCodePointSize())))); + source.getContent().size(), + options.maxExpressionCodePointSize())))); } PrattParser prattParser = new PrattParser(source, options, macros); CelExpr expr = prattParser.run(); @@ -290,7 +295,9 @@ private long nextId(int position) { reportError( position, String.format( - "expression node limit (%d) exceeded", options.maxParseExpressionNodeCount())); + LOCALE, + "expression node limit (%d) exceeded", + options.maxParseExpressionNodeCount())); nodeLimitExceeded = true; } if (!nodeLimitExceeded && position >= 0) { @@ -375,7 +382,8 @@ private void reportError(CelSourceLocation loc, String msg) { issues.add( CelIssue.formatError( CelSourceLocation.NONE, - String.format("More than %d parse errors.", options.maxParseErrorRecoveryLimit()))); + String.format( + LOCALE, "More than %d parse errors.", options.maxParseErrorRecoveryLimit()))); peekToken = END_TOKEN; } if (errorCount <= options.maxParseErrorRecoveryLimit()) { @@ -401,7 +409,9 @@ private void reportRecursionLimit(int position) { reportError( position, String.format( - "Expression recursion limit exceeded. limit: %d", options.maxParseRecursionDepth())); + LOCALE, + "Expression recursion limit exceeded. limit: %d", + options.maxParseRecursionDepth())); } } diff --git a/parser/src/test/java/dev/cel/parser/BUILD.bazel b/parser/src/test/java/dev/cel/parser/BUILD.bazel index eea155e92..db5a647b7 100644 --- a/parser/src/test/java/dev/cel/parser/BUILD.bazel +++ b/parser/src/test/java/dev/cel/parser/BUILD.bazel @@ -1,4 +1,5 @@ load("@rules_java//java:defs.bzl", "java_library") +load("//:cel_android_rules.bzl", "cel_android_local_test") load("//:testing.bzl", "junit4_test_suites") package( @@ -7,19 +8,25 @@ package( ], ) +ANDROID_TESTS = [ + "CelLiteParserAndroidTest.java", +] + java_library( name = "tests", testonly = True, srcs = glob( ["*Test.java"], - exclude = ["TmpPrattParserTest.java"], + exclude = ["TmpPrattParserTest.java"] + ANDROID_TESTS, ), resources = ["//parser/src/test/resources:baselines"], deps = [ "//:java_truth", "//common:cel_ast", + "//common:cel_issue", "//common:cel_source", - "//common:compiler_common", + "//common:cel_validation_exception", + "//common:cel_validation_result", "//common:operator", "//common:options", "//common:proto_ast", @@ -29,6 +36,7 @@ java_library( "//common/values:cel_byte_string", "//extensions:optional_library", "//parser", + "//parser:lite_parser_factory", "//parser:macro", "//parser:parser_builder", "//parser:parser_factory", @@ -46,6 +54,18 @@ java_library( ], ) +cel_android_local_test( + name = "android_tests", + srcs = ANDROID_TESTS, + test_class = "dev.cel.parser.CelLiteParserAndroidTest", + deps = [ + "//:java_truth", + "//common:cel_validation_result_android", + "//parser:lite_parser_factory_android", + "//parser:parser_builder_android", + ], +) + junit4_test_suites( name = "test_suites", sizes = [ diff --git a/parser/src/test/java/dev/cel/parser/CelLiteParserAndroidTest.java b/parser/src/test/java/dev/cel/parser/CelLiteParserAndroidTest.java new file mode 100644 index 000000000..63e4346d1 --- /dev/null +++ b/parser/src/test/java/dev/cel/parser/CelLiteParserAndroidTest.java @@ -0,0 +1,40 @@ +// 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. + +package dev.cel.parser; + +import static com.google.common.truth.Truth.assertThat; + +import dev.cel.common.CelValidationResult; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class CelLiteParserAndroidTest { + + @Test + public void newLiteParserBuilder_build_isNotNull() { + assertThat(CelLiteParserFactory.newLiteParserBuilder().build()).isNotNull(); + } + + @Test + public void newLiteParserBuilder_parseSmokeTest() { + CelParser parser = CelLiteParserFactory.newLiteParserBuilder().build(); + + CelValidationResult result = parser.parse("1 + 1"); + + assertThat(result.hasError()).isFalse(); + } +} diff --git a/parser/src/test/java/dev/cel/parser/CelLiteParserFactoryTest.java b/parser/src/test/java/dev/cel/parser/CelLiteParserFactoryTest.java new file mode 100644 index 000000000..5b78521c5 --- /dev/null +++ b/parser/src/test/java/dev/cel/parser/CelLiteParserFactoryTest.java @@ -0,0 +1,229 @@ +// 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. + +package dev.cel.parser; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import dev.cel.common.CelOptions; +import dev.cel.common.CelSource; +import dev.cel.common.CelValidationResult; +import dev.cel.common.ast.CelExpr.ExprKind.Kind; +import java.util.Optional; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class CelLiteParserFactoryTest { + + @Test + public void newLiteParserBuilder_build_isNotNull() { + CelParserBuilder builder = CelLiteParserFactory.newLiteParserBuilder(); + + CelParser parser = builder.build(); + + assertThat(parser).isNotNull(); + } + + @Test + public void newLiteParserBuilder_defaultOptions_matchesCurrentWithPrattParserEnabled() { + CelParserBuilder builder = CelLiteParserFactory.newLiteParserBuilder(); + + assertThat(builder.getOptions()) + .isEqualTo(CelOptions.current().enablePrattParser(true).build()); + } + + @Test + public void newLiteParserBuilder_parseSmokeTest() { + CelParser parser = CelLiteParserFactory.newLiteParserBuilder().build(); + + CelValidationResult result = parser.parse("1 + 1"); + + assertThat(result.hasError()).isFalse(); + } + + @Test + public void parse_nullSource_throwsNullPointerException() { + CelParser parser = CelLiteParserFactory.newLiteParserBuilder().build(); + + assertThrows(NullPointerException.class, () -> parser.parse((CelSource) null)); + } + + @Test + public void setStandardMacros_secondCallReplacesPreviousStandardMacros() throws Exception { + CelParser parser = + CelLiteParserFactory.newLiteParserBuilder() + .setStandardMacros(CelStandardMacro.HAS, CelStandardMacro.ALL) + .setStandardMacros(CelStandardMacro.HAS) + .build(); + + CelValidationResult hasResult = parser.parse("has(a.b)"); + CelValidationResult allResult = parser.parse("[1].all(x, x > 0)"); + + assertThat(hasResult.getAst().getExpr().getKind()).isEqualTo(Kind.SELECT); + assertThat(allResult.getAst().getExpr().getKind()).isEqualTo(Kind.CALL); + } + + @Test + public void setStandardMacros_allStandardMacrosSupported() throws Exception { + CelParser parser = + CelLiteParserFactory.newLiteParserBuilder() + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .build(); + + assertThat(parser.parse("has(a.b)").getAst().getExpr().getKind()).isEqualTo(Kind.SELECT); + assertThat(parser.parse("[1].all(x, x > 0)").getAst().getExpr().getKind()) + .isEqualTo(Kind.COMPREHENSION); + assertThat(parser.parse("[1].exists(x, x > 0)").getAst().getExpr().getKind()) + .isEqualTo(Kind.COMPREHENSION); + assertThat(parser.parse("[1].exists_one(x, x > 0)").getAst().getExpr().getKind()) + .isEqualTo(Kind.COMPREHENSION); + assertThat(parser.parse("[1].map(x, x > 0)").getAst().getExpr().getKind()) + .isEqualTo(Kind.COMPREHENSION); + assertThat(parser.parse("[1].map(x, x > 0, x)").getAst().getExpr().getKind()) + .isEqualTo(Kind.COMPREHENSION); + assertThat(parser.parse("[1].filter(x, x > 0)").getAst().getExpr().getKind()) + .isEqualTo(Kind.COMPREHENSION); + } + + @Test + public void addMacros_registersCustomMacro() throws Exception { + CelMacro customMacro = + CelMacro.newGlobalMacro( + "customMacro", + 0, + (exprFactory, target, args) -> Optional.of(exprFactory.newBoolLiteral(true))); + CelParser parser = CelLiteParserFactory.newLiteParserBuilder().addMacros(customMacro).build(); + + CelValidationResult result = parser.parse("customMacro()"); + + assertThat(result.getAst().getExpr().getKind()).isEqualTo(Kind.CONSTANT); + } + + @Test + public void addLibraries_configuresParserBuilder() throws Exception { + CelParserLibrary library = + new CelParserLibrary() { + @Override + public void setParserOptions(CelParserBuilder parserBuilder) { + parserBuilder.addMacros( + CelMacro.newGlobalMacro( + "libMacro", + 0, + (exprFactory, target, args) -> Optional.of(exprFactory.newBoolLiteral(true)))); + } + }; + CelParser parser = CelLiteParserFactory.newLiteParserBuilder().addLibraries(library).build(); + + CelValidationResult result = parser.parse("libMacro()"); + + assertThat(result.getAst().getExpr().getKind()).isEqualTo(Kind.CONSTANT); + } + + @Test + public void toParserBuilder_roundtripPreservesCustomAndStandardMacros() throws Exception { + CelMacro customMacro = + CelMacro.newGlobalMacro( + "customMacro", + 0, + (exprFactory, target, args) -> Optional.of(exprFactory.newBoolLiteral(true))); + CelParser parser = + CelLiteParserFactory.newLiteParserBuilder() + .setStandardMacros(CelStandardMacro.HAS) + .addMacros(customMacro) + .build(); + + CelParser roundtripParser = parser.toParserBuilder().build(); + CelValidationResult hasResult = roundtripParser.parse("has(a.b)"); + CelValidationResult customMacroResult = roundtripParser.parse("customMacro()"); + + assertThat(hasResult.getAst().getExpr().getKind()).isEqualTo(Kind.SELECT); + assertThat(customMacroResult.getAst().getExpr().getKind()).isEqualTo(Kind.CONSTANT); + } + + @Test + public void toParserBuilder_roundtripPreservesOptionsAndLibraries() throws Exception { + CelParserLibrary library = + new CelParserLibrary() { + @Override + public void setParserOptions(CelParserBuilder parserBuilder) { + parserBuilder.addMacros( + CelMacro.newGlobalMacro( + "libMacro", + 0, + (exprFactory, target, args) -> Optional.of(exprFactory.newBoolLiteral(true)))); + } + }; + CelOptions customOptions = + CelOptions.current().enablePrattParser(true).maxExpressionCodePointSize(100).build(); + CelParser parser = + CelLiteParserFactory.newLiteParserBuilder() + .setOptions(customOptions) + .addLibraries(library) + .build(); + + CelParser roundtripParser = parser.toParserBuilder().build(); + + assertThat(roundtripParser.parse("libMacro()").getAst().getExpr().getKind()) + .isEqualTo(Kind.CONSTANT); + } + + @Test + public void toParserBuilder_createsNewBuilder() { + CelParserBuilder originalBuilder = CelLiteParserFactory.newLiteParserBuilder(); + CelParser parser = originalBuilder.build(); + + CelParserBuilder roundtripBuilder = parser.toParserBuilder(); + + assertThat(roundtripBuilder).isNotSameInstanceAs(originalBuilder); + assertThat(roundtripBuilder.build()).isNotNull(); + } + + @Test + public void build_withPrattParserDisabled_throwsIllegalArgumentException() { + CelParserBuilder builder = + CelLiteParserFactory.newLiteParserBuilder() + .setOptions(CelOptions.current().enablePrattParser(false).build()); + + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, builder::build); + + assertThat(exception) + .hasMessageThat() + .isEqualTo("Misconfigured CelOptions: enablePrattParser cannot be disabled."); + } + + @Test + public void build_withLibraryDisablingPrattParser_throwsIllegalArgumentException() { + CelParserLibrary disablingLibrary = + new CelParserLibrary() { + @Override + public void setParserOptions(CelParserBuilder parserBuilder) { + parserBuilder.setOptions( + parserBuilder.getOptions().toBuilder().enablePrattParser(false).build()); + } + }; + CelParserBuilder builder = + CelLiteParserFactory.newLiteParserBuilder().addLibraries(disablingLibrary); + + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, builder::build); + + assertThat(exception) + .hasMessageThat() + .isEqualTo("Misconfigured CelOptions: enablePrattParser cannot be disabled."); + } +}