From 54a98f27cd23839737811d625ae4a465b31f1c9a Mon Sep 17 00:00:00 2001 From: Dmitri Plotnikov Date: Thu, 23 Jul 2026 16:29:01 -0700 Subject: [PATCH] [Pratt Parser] Add AstFactoryInterface template Also, add AstFactoryInterface specialization PiperOrigin-RevId: 953018238 --- common/expr_factory.h | 6 + parser/internal/BUILD | 28 ++++ parser/internal/ast_factory.h | 181 +++++++++++++++++++++ parser/internal/ast_factory_interface.h | 104 ++++++++++++ parser/internal/ast_factory_test.cc | 200 ++++++++++++++++++++++++ 5 files changed, 519 insertions(+) create mode 100644 parser/internal/ast_factory.h create mode 100644 parser/internal/ast_factory_interface.h create mode 100644 parser/internal/ast_factory_test.cc diff --git a/common/expr_factory.h b/common/expr_factory.h index 757318545..371361948 100644 --- a/common/expr_factory.h +++ b/common/expr_factory.h @@ -38,6 +38,11 @@ namespace tools { class ProtoToPredicateBuilder; } +namespace parser_internal { +template +class AstFactoryInterface; +} + class ExprFactory { protected: // `IsExprLike` determines whether `T` is some `Expr`. Currently that means @@ -385,6 +390,7 @@ class ExprFactory { friend class ParserMacroExprFactory; friend class OptimizerExprFactory; friend class tools::ProtoToPredicateBuilder; + friend class parser_internal::AstFactoryInterface; ExprFactory() : accu_var_(kAccumulatorVariableName) {} diff --git a/parser/internal/BUILD b/parser/internal/BUILD index c5bb23a4e..55d2aa247 100644 --- a/parser/internal/BUILD +++ b/parser/internal/BUILD @@ -20,6 +20,23 @@ package(default_visibility = ["//visibility:public"]) licenses(["notice"]) +cc_library( + name = "ast_factory_interface", + hdrs = ["ast_factory_interface.h"], +) + +cc_library( + name = "ast_factory", + hdrs = ["ast_factory.h"], + deps = [ + ":ast_factory_interface", + "//common:constant", + "//common:expr", + "//common:expr_factory", + "@com_google_absl//absl/strings:string_view", + ], +) + cc_library( name = "options", hdrs = ["options.h"], @@ -47,6 +64,17 @@ cc_library( ], ) +cc_test( + name = "ast_factory_test", + srcs = ["ast_factory_test.cc"], + deps = [ + ":ast_factory", + "//common:expr", + "//internal:testing", + "@com_google_absl//absl/strings:string_view", + ], +) + cc_test( name = "lexer_test", srcs = ["lexer_test.cc"], diff --git a/parser/internal/ast_factory.h b/parser/internal/ast_factory.h new file mode 100644 index 000000000..c0e8a6b7e --- /dev/null +++ b/parser/internal/ast_factory.h @@ -0,0 +1,181 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_CEL_CPP_PARSER_INTERNAL_AST_FACTORY_H_ +#define THIRD_PARTY_CEL_CPP_PARSER_INTERNAL_AST_FACTORY_H_ + +#include +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "common/constant.h" +#include "common/expr.h" +#include "common/expr_factory.h" +#include "parser/internal/ast_factory_interface.h" + +namespace cel::parser_internal { + +// Explicit specialization of `AstFactoryInterface` for `cel::Expr` AST nodes. + +template <> +class ListNodeBuilder { + public: + explicit ListNodeBuilder(int64_t id) { + expr_.set_id(id); + expr_.mutable_list_expr(); + } + + ListNodeBuilder& Add(cel::Expr element, bool optional = false) { + cel::ListExpr& list_val = expr_.mutable_list_expr(); + cel::ListExprElement expr_element; + expr_element.set_expr(std::move(element)); + expr_element.set_optional(optional); + list_val.mutable_elements().push_back(std::move(expr_element)); + return *this; + } + + cel::Expr Build() { return std::move(expr_); } + + private: + cel::Expr expr_; +}; + +template <> +class MapNodeBuilder { + public: + explicit MapNodeBuilder(int64_t id) { + expr_.set_id(id); + expr_.mutable_map_expr(); + } + + MapNodeBuilder& Add(int64_t id, cel::Expr key, cel::Expr value, + bool optional = false) { + cel::MapExpr& map_val = expr_.mutable_map_expr(); + cel::MapExprEntry entry; + entry.set_id(id); + entry.set_key(std::move(key)); + entry.set_value(std::move(value)); + entry.set_optional(optional); + map_val.mutable_entries().push_back(std::move(entry)); + return *this; + } + + cel::Expr Build() { return std::move(expr_); } + + private: + cel::Expr expr_; +}; + +template <> +class StructNodeBuilder { + public: + explicit StructNodeBuilder(int64_t id, std::string name) { + expr_.set_id(id); + expr_.mutable_struct_expr().set_name(std::move(name)); + } + + StructNodeBuilder& Add(int64_t id, std::string name, cel::Expr value, + bool optional = false) { + cel::StructExpr& struct_val = expr_.mutable_struct_expr(); + cel::StructExprField field; + field.set_id(id); + field.set_name(std::move(name)); + field.set_value(std::move(value)); + field.set_optional(optional); + struct_val.mutable_fields().push_back(std::move(field)); + return *this; + } + + cel::Expr Build() { return std::move(expr_); } + + private: + cel::Expr expr_; +}; + +template <> +class AstFactoryInterface : public cel::ExprFactory { + public: + AstFactoryInterface() = default; + AstFactoryInterface(const AstFactoryInterface&) = delete; + AstFactoryInterface(AstFactoryInterface&&) = delete; + AstFactoryInterface& operator=(const AstFactoryInterface&) = delete; + AstFactoryInterface& operator=(AstFactoryInterface&&) = delete; + + ~AstFactoryInterface() override = default; + + // Node inspection and encapsulation API + int64_t GetId(const cel::Expr& expr) const { return expr.id(); } + + bool IsEmpty(const cel::Expr& expr) const { return expr.id() == 0; } + + bool IsConst(const cel::Expr& expr) const { return expr.has_const_expr(); } + + bool IsIdent(const cel::Expr& expr) const { return expr.has_ident_expr(); } + + absl::string_view GetIdentName(const cel::Expr& expr) const { + return expr.has_ident_expr() ? absl::string_view(expr.ident_expr().name()) + : absl::string_view(); + } + + bool IsSelect(const cel::Expr& expr) const { return expr.has_select_expr(); } + + bool IsPresenceTest(const cel::Expr& expr) const { + return expr.has_select_expr() && expr.select_expr().test_only(); + } + + const cel::Expr* GetSelectOperand(const cel::Expr& expr) const { + return expr.has_select_expr() ? &expr.select_expr().operand() : nullptr; + } + + absl::string_view GetSelectField(const cel::Expr& expr) const { + return expr.has_select_expr() + ? absl::string_view(expr.select_expr().field()) + : absl::string_view(); + } + + // Node creation API + using cel::ExprFactory::NewBoolConst; + using cel::ExprFactory::NewBytesConst; + using cel::ExprFactory::NewCall; + using cel::ExprFactory::NewDoubleConst; + using cel::ExprFactory::NewIdent; + using cel::ExprFactory::NewIntConst; + using cel::ExprFactory::NewMemberCall; + using cel::ExprFactory::NewNullConst; + using cel::ExprFactory::NewPresenceTest; + using cel::ExprFactory::NewSelect; + using cel::ExprFactory::NewStringConst; + using cel::ExprFactory::NewUintConst; + using cel::ExprFactory::NewUnspecified; + + ListNodeBuilder NewListBuilder(int64_t id) { + return ListNodeBuilder(id); + } + + StructNodeBuilder NewStructBuilder(int64_t id, std::string name) { + return StructNodeBuilder(id, std::move(name)); + } + + MapNodeBuilder NewMapBuilder(int64_t id) { + return MapNodeBuilder(id); + } +}; + +using AstFactory = AstFactoryInterface; + +} // namespace cel::parser_internal + +#endif // THIRD_PARTY_CEL_CPP_PARSER_INTERNAL_AST_FACTORY_H_ diff --git a/parser/internal/ast_factory_interface.h b/parser/internal/ast_factory_interface.h new file mode 100644 index 000000000..3b8c9bdfb --- /dev/null +++ b/parser/internal/ast_factory_interface.h @@ -0,0 +1,104 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_CEL_CPP_PARSER_INTERNAL_AST_FACTORY_INTERFACE_H_ +#define THIRD_PARTY_CEL_CPP_PARSER_INTERNAL_AST_FACTORY_INTERFACE_H_ + +#include +#include +#include +#include + +namespace cel::parser_internal { + +// Interface for decoupling parser logic from the underlying AST node +// data structures. +// +// By parameterizing the parser and factory on `ExprNode`, alternative AST node +// representations (such as `cel::Expr`) can be constructed without modifying +// parser rules. +// +// To implement AST construction using an alternative AST structure: +// 1. Define or specify your custom node type `MyNode`. +// 2. Implement a concrete factory specialization `AstFactoryInterface` +// that provides inspection (`GetId`, `IsSelect`, etc.) and creation +// (`NewCall`, `NewListBuilder`, etc.) operations for `MyNode`. +// 3. Instantiate the parser worker with your node type: +// `PrattParserWorker`. + +template +class ListNodeBuilder { + public: + ListNodeBuilder& Add(ExprNode element, bool optional = false); + ExprNode Build(); +}; + +template +class MapNodeBuilder { + public: + MapNodeBuilder& Add(int64_t id, ExprNode key, ExprNode value, + bool optional = false); + ExprNode Build(); +}; + +template +class StructNodeBuilder { + public: + StructNodeBuilder& Add(int64_t id, std::string name, ExprNode value, + bool optional = false); + ExprNode Build(); +}; + +template +class AstFactoryInterface { + public: + AstFactoryInterface() = default; + AstFactoryInterface(const AstFactoryInterface&) = delete; + AstFactoryInterface(AstFactoryInterface&&) = delete; + AstFactoryInterface& operator=(const AstFactoryInterface&) = delete; + AstFactoryInterface& operator=(AstFactoryInterface&&) = delete; + + int64_t GetId(const ExprNode& expr) const; + bool IsEmpty(const ExprNode& expr) const; + bool IsConst(const ExprNode& expr) const; + bool IsIdent(const ExprNode& expr) const; + std::string_view GetIdentName(const ExprNode& expr) const; + bool IsSelect(const ExprNode& expr) const; + bool IsPresenceTest(const ExprNode& expr) const; + const ExprNode* GetSelectOperand(const ExprNode& expr) const; + std::string_view GetSelectField(const ExprNode& expr) const; + + ExprNode NewUnspecified(int64_t id); + ExprNode NewNullConst(int64_t id); + ExprNode NewBoolConst(int64_t id, bool value); + ExprNode NewIntConst(int64_t id, int64_t value); + ExprNode NewUintConst(int64_t id, uint64_t value); + ExprNode NewDoubleConst(int64_t id, double value); + ExprNode NewBytesConst(int64_t id, std::string value); + ExprNode NewStringConst(int64_t id, std::string value); + ExprNode NewIdent(int64_t id, std::string name); + ExprNode NewSelect(int64_t id, ExprNode operand, std::string field); + ExprNode NewPresenceTest(int64_t id, ExprNode operand, std::string field); + ExprNode NewCall(int64_t id, std::string function, + std::vector args); + ExprNode NewMemberCall(int64_t id, std::string function, ExprNode target, + std::vector args); + ListNodeBuilder NewListBuilder(int64_t id); + MapNodeBuilder NewMapBuilder(int64_t id); + StructNodeBuilder NewStructBuilder(int64_t id, std::string name); +}; + +} // namespace cel::parser_internal + +#endif // THIRD_PARTY_CEL_CPP_PARSER_INTERNAL_AST_FACTORY_INTERFACE_H_ diff --git a/parser/internal/ast_factory_test.cc b/parser/internal/ast_factory_test.cc new file mode 100644 index 000000000..a66c8ee6b --- /dev/null +++ b/parser/internal/ast_factory_test.cc @@ -0,0 +1,200 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "parser/internal/ast_factory.h" + +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "common/expr.h" +#include "internal/testing.h" + +namespace cel::parser_internal { +namespace { + +TEST(AstFactoryInterfaceTest, AstFactoryUnspecified) { + AstFactory factory; + cel::Expr expr = factory.NewUnspecified(1); + EXPECT_EQ(factory.GetId(expr), 1); + EXPECT_FALSE(factory.IsEmpty(expr)); + EXPECT_FALSE(factory.IsConst(expr)); + EXPECT_FALSE(factory.IsIdent(expr)); + EXPECT_FALSE(factory.IsSelect(expr)); + + cel::Expr empty_expr = factory.NewUnspecified(0); + EXPECT_TRUE(factory.IsEmpty(empty_expr)); +} + +TEST(AstFactoryInterfaceTest, AstFactoryConstNodes) { + AstFactory factory; + + cel::Expr null_expr = factory.NewNullConst(10); + EXPECT_EQ(factory.GetId(null_expr), 10); + EXPECT_TRUE(factory.IsConst(null_expr)); + ASSERT_TRUE(null_expr.has_const_expr()); + EXPECT_TRUE(null_expr.const_expr().has_null_value()); + + cel::Expr bool_expr = factory.NewBoolConst(11, true); + EXPECT_EQ(factory.GetId(bool_expr), 11); + EXPECT_TRUE(factory.IsConst(bool_expr)); + ASSERT_TRUE(bool_expr.has_const_expr()); + EXPECT_TRUE(bool_expr.const_expr().bool_value()); + + cel::Expr int_expr = factory.NewIntConst(12, -42); + EXPECT_EQ(factory.GetId(int_expr), 12); + EXPECT_TRUE(factory.IsConst(int_expr)); + ASSERT_TRUE(int_expr.has_const_expr()); + EXPECT_EQ(int_expr.const_expr().int_value(), -42); + + cel::Expr uint_expr = factory.NewUintConst(13, 100u); + EXPECT_EQ(factory.GetId(uint_expr), 13); + EXPECT_TRUE(factory.IsConst(uint_expr)); + ASSERT_TRUE(uint_expr.has_const_expr()); + EXPECT_EQ(uint_expr.const_expr().uint_value(), 100u); + + cel::Expr double_expr = factory.NewDoubleConst(14, 3.14159); + EXPECT_EQ(factory.GetId(double_expr), 14); + EXPECT_TRUE(factory.IsConst(double_expr)); + ASSERT_TRUE(double_expr.has_const_expr()); + EXPECT_DOUBLE_EQ(double_expr.const_expr().double_value(), 3.14159); + + cel::Expr bytes_expr = factory.NewBytesConst(15, "bytes_val"); + EXPECT_EQ(factory.GetId(bytes_expr), 15); + EXPECT_TRUE(factory.IsConst(bytes_expr)); + ASSERT_TRUE(bytes_expr.has_const_expr()); + EXPECT_EQ(bytes_expr.const_expr().bytes_value(), "bytes_val"); + + cel::Expr string_expr = factory.NewStringConst(16, "string_val"); + EXPECT_EQ(factory.GetId(string_expr), 16); + EXPECT_TRUE(factory.IsConst(string_expr)); + ASSERT_TRUE(string_expr.has_const_expr()); + EXPECT_EQ(string_expr.const_expr().string_value(), "string_val"); +} + +TEST(AstFactoryInterfaceTest, AstFactoryIdentAndSelect) { + AstFactory factory; + + cel::Expr ident_expr = factory.NewIdent(20, "foo"); + EXPECT_EQ(factory.GetId(ident_expr), 20); + EXPECT_TRUE(factory.IsIdent(ident_expr)); + EXPECT_EQ(factory.GetIdentName(ident_expr), "foo"); + + cel::Expr select_expr = + factory.NewSelect(21, factory.NewIdent(20, "foo"), "bar"); + EXPECT_EQ(factory.GetId(select_expr), 21); + EXPECT_TRUE(factory.IsSelect(select_expr)); + EXPECT_FALSE(factory.IsPresenceTest(select_expr)); + EXPECT_EQ(factory.GetSelectField(select_expr), "bar"); + ASSERT_NE(factory.GetSelectOperand(select_expr), nullptr); + EXPECT_EQ(factory.GetIdentName(*factory.GetSelectOperand(select_expr)), + "foo"); + + cel::Expr presence_expr = + factory.NewPresenceTest(22, factory.NewIdent(20, "foo"), "bar"); + EXPECT_EQ(factory.GetId(presence_expr), 22); + EXPECT_TRUE(factory.IsSelect(presence_expr)); + EXPECT_TRUE(factory.IsPresenceTest(presence_expr)); + EXPECT_EQ(factory.GetSelectField(presence_expr), "bar"); +} + +TEST(AstFactoryInterfaceTest, AstFactoryCalls) { + AstFactory factory; + + std::vector call_args; + call_args.push_back(factory.NewIntConst(30, 1)); + call_args.push_back(factory.NewIntConst(31, 2)); + cel::Expr call_expr = factory.NewCall(32, "_+_", std::move(call_args)); + EXPECT_EQ(factory.GetId(call_expr), 32); + ASSERT_TRUE(call_expr.has_call_expr()); + EXPECT_EQ(call_expr.call_expr().function(), "_+_"); + EXPECT_FALSE(call_expr.call_expr().has_target()); + EXPECT_EQ(call_expr.call_expr().args().size(), 2); + + std::vector member_args; + member_args.push_back(factory.NewStringConst(33, "suffix")); + cel::Expr member_call_expr = factory.NewMemberCall( + 34, "endsWith", factory.NewIdent(35, "str_var"), std::move(member_args)); + EXPECT_EQ(factory.GetId(member_call_expr), 34); + ASSERT_TRUE(member_call_expr.has_call_expr()); + EXPECT_EQ(member_call_expr.call_expr().function(), "endsWith"); + EXPECT_TRUE(member_call_expr.call_expr().has_target()); + EXPECT_EQ(member_call_expr.call_expr().target().ident_expr().name(), + "str_var"); + EXPECT_EQ(member_call_expr.call_expr().args().size(), 1); +} + +TEST(AstFactoryInterfaceTest, AstFactoryList) { + AstFactory factory; + + cel::Expr list_expr = factory.NewListBuilder(42) + .Add(factory.NewIntConst(40, 1), false) + .Add(factory.NewIntConst(41, 2), true) + .Build(); + EXPECT_EQ(factory.GetId(list_expr), 42); + ASSERT_TRUE(list_expr.has_list_expr()); + ASSERT_EQ(list_expr.list_expr().elements().size(), 2); + EXPECT_FALSE(list_expr.list_expr().elements()[0].optional()); + EXPECT_EQ(list_expr.list_expr().elements()[0].expr().const_expr().int_value(), + 1); + EXPECT_TRUE(list_expr.list_expr().elements()[1].optional()); + EXPECT_EQ(list_expr.list_expr().elements()[1].expr().const_expr().int_value(), + 2); +} + +TEST(AstFactoryInterfaceTest, AstFactoryStruct) { + AstFactory factory; + + cel::Expr struct_expr = + factory.NewStructBuilder(54, "MyMessage") + .Add(50, "field1", factory.NewIntConst(51, 100), false) + .Add(52, "field2", factory.NewIntConst(53, 200), true) + .Build(); + EXPECT_EQ(factory.GetId(struct_expr), 54); + ASSERT_TRUE(struct_expr.has_struct_expr()); + EXPECT_EQ(struct_expr.struct_expr().name(), "MyMessage"); + ASSERT_EQ(struct_expr.struct_expr().fields().size(), 2); + EXPECT_EQ(struct_expr.struct_expr().fields()[0].id(), 50); + EXPECT_EQ(struct_expr.struct_expr().fields()[0].name(), "field1"); + EXPECT_FALSE(struct_expr.struct_expr().fields()[0].optional()); + EXPECT_EQ(struct_expr.struct_expr().fields()[1].id(), 52); + EXPECT_EQ(struct_expr.struct_expr().fields()[1].name(), "field2"); + EXPECT_TRUE(struct_expr.struct_expr().fields()[1].optional()); +} + +TEST(AstFactoryInterfaceTest, AstFactoryMap) { + AstFactory factory; + + cel::Expr map_expr = factory.NewMapBuilder(66) + .Add(60, factory.NewStringConst(61, "key1"), + factory.NewIntConst(62, 10), false) + .Add(63, factory.NewStringConst(64, "key2"), + factory.NewIntConst(65, 20), true) + .Build(); + EXPECT_EQ(factory.GetId(map_expr), 66); + ASSERT_TRUE(map_expr.has_map_expr()); + ASSERT_EQ(map_expr.map_expr().entries().size(), 2); + EXPECT_EQ(map_expr.map_expr().entries()[0].id(), 60); + EXPECT_EQ(map_expr.map_expr().entries()[0].key().const_expr().string_value(), + "key1"); + EXPECT_EQ(map_expr.map_expr().entries()[0].value().const_expr().int_value(), + 10); + EXPECT_FALSE(map_expr.map_expr().entries()[0].optional()); + EXPECT_EQ(map_expr.map_expr().entries()[1].id(), 63); + EXPECT_TRUE(map_expr.map_expr().entries()[1].optional()); +} + +} // namespace +} // namespace cel::parser_internal