From 3209fd2cafb8bfaa60def32308b0dc4e0b7170bc Mon Sep 17 00:00:00 2001 From: Jonathan Tatum Date: Wed, 27 Aug 2025 14:34:25 -0700 Subject: [PATCH] Extract implementation details for NavigableAst to internal template base class. PiperOrigin-RevId: 800176850 --- common/ast/navigable_ast_internal.h | 173 +++++++++++++++++++++++++--- tools/navigable_ast.cc | 42 +++---- tools/navigable_ast.h | 147 +++++++++-------------- 3 files changed, 231 insertions(+), 131 deletions(-) diff --git a/common/ast/navigable_ast_internal.h b/common/ast/navigable_ast_internal.h index 08d849f88..6759212a1 100644 --- a/common/ast/navigable_ast_internal.h +++ b/common/ast/navigable_ast_internal.h @@ -103,8 +103,8 @@ class NavigableAstRange { explicit NavigableAstRange(SpanType span) : span_(span) {} - Iterator begin() { return Iterator(span_, 0); } - Iterator end() { return Iterator(span_, span_.size()); } + Iterator begin() const { return Iterator(span_, 0); } + Iterator end() const { return Iterator(span_, span_.size()); } explicit operator bool() const { return !span_.empty(); } @@ -112,37 +112,38 @@ class NavigableAstRange { SpanType span_; }; -template +template struct NavigableAstMetadata; // Internal implementation for data-structures handling cross-referencing nodes. // // This is exposed separately to allow building up the AST relationships // without exposing too much mutable state on the client facing classes. -template +template struct NavigableAstNodeData { - AstNode* parent; - const typename AstNode::ExprType* expr; + typename AstTraits::NodeType* parent; + const typename AstTraits::ExprType* expr; ChildKind parent_relation; NodeKind node_kind; - const NavigableAstMetadata* absl_nonnull metadata; + const NavigableAstMetadata* absl_nonnull metadata; size_t index; size_t tree_size; size_t height; int child_index; - std::vector children; + std::vector children; }; -template +template struct NavigableAstMetadata { // The nodes in the AST in preorder. // // unique_ptr is used to guarantee pointer stability in the other tables. - std::vector> nodes; - std::vector postorder; - absl::flat_hash_map id_to_node; - absl::flat_hash_map + std::vector> nodes; + std::vector postorder; + absl::flat_hash_map + id_to_node; + absl::flat_hash_map expr_to_node; }; @@ -161,6 +162,150 @@ struct PreorderTraits { } }; +// Base class for NavigableAstNode and NavigableProtoAstNode. +template +class NavigableAstNodeBase { + private: + using MetadataType = NavigableAstMetadata; + using NodeDataType = NavigableAstNodeData; + using Derived = typename AstTraits::NodeType; + using ExprType = typename AstTraits::ExprType; + + public: + using PreorderRange = NavigableAstRange>; + using PostorderRange = NavigableAstRange>; + + // The parent of this node or nullptr if it is a root. + const Derived* absl_nullable parent() const { return data_.parent; } + + const ExprType* absl_nonnull expr() const { return data_.expr; } + + // The index of this node in the parent's children. -1 if this is a root. + int child_index() const { return data_.child_index; } + + // The type of traversal from parent to this node. + ChildKind parent_relation() const { return data_.parent_relation; } + + // The type of this node, analogous to Expr::ExprKindCase. + NodeKind node_kind() const { return data_.node_kind; } + + // The number of nodes in the tree rooted at this node (including self). + size_t tree_size() const { return data_.tree_size; } + + // The height of this node in the tree (the number of descendants including + // self on the longest path). + size_t height() const { return data_.height; } + + absl::Span children() const { + return absl::MakeConstSpan(data_.children); + } + + // Range over the descendants of this node (including self) using preorder + // semantics. Each node is visited immediately before all of its descendants. + PreorderRange DescendantsPreorder() const { + return PreorderRange(absl::MakeConstSpan(data_.metadata->nodes) + .subspan(data_.index, data_.tree_size)); + } + + // Range over the descendants of this node (including self) using postorder + // semantics. Each node is visited immediately after all of its descendants. + PostorderRange DescendantsPostorder() const { + return PostorderRange(absl::MakeConstSpan(data_.metadata->postorder) + .subspan(data_.index, data_.tree_size)); + } + + private: + friend Derived; + + NavigableAstNodeBase() = default; + NavigableAstNodeBase(const NavigableAstNodeBase&) = delete; + NavigableAstNodeBase& operator=(const NavigableAstNodeBase&) = delete; + + protected: + NodeDataType data_; +}; + +// Shared implementation for NavigableAst and NavigableProtoAst. +// +// AstTraits provides type info for the derived classes that implement building +// the traversal metadata. It provides the following types: +// +// ExprType is the expression node type of the source AST. +// +// AstType is the subclass of NavigableAstBase for the implementation. +// +// NodeType is the subclass of NavigableAstNodeBase for the implementation. +template +class NavigableAstBase { + private: + using MetadataType = NavigableAstMetadata; + using Derived = typename AstTraits::AstType; + using NodeType = typename AstTraits::NodeType; + using ExprType = typename AstTraits::ExprType; + + public: + NavigableAstBase(const NavigableAstBase&) = delete; + NavigableAstBase& operator=(const NavigableAstBase&) = delete; + NavigableAstBase(NavigableAstBase&&) = default; + NavigableAstBase& operator=(NavigableAstBase&&) = default; + + // Return ptr to the AST node with id if present. Otherwise returns nullptr. + // + // If ids are non-unique, the first pre-order node encountered with id is + // returned. + const NodeType* absl_nullable FindId(int64_t id) const { + auto it = metadata_->id_to_node.find(id); + if (it == metadata_->id_to_node.end()) { + return nullptr; + } + return it->second; + } + + // Return ptr to the AST node representing the given Expr protobuf node. + const NodeType* absl_nullable FindExpr( + const ExprType* absl_nonnull expr) const { + auto it = metadata_->expr_to_node.find(expr); + if (it == metadata_->expr_to_node.end()) { + return nullptr; + } + return it->second; + } + + // The root of the AST. + const NodeType& Root() const { return *metadata_->nodes[0]; } + + // Check whether the source AST used unique IDs for each node. + // + // This is typically the case, but older versions of the parsers didn't + // guarantee uniqueness for nodes generated by some macros and ASTs modified + // outside of CEL's parse/type check may not have unique IDs. + bool IdsAreUnique() const { + return metadata_->id_to_node.size() == metadata_->nodes.size(); + } + + // Equality operators test for identity. They are intended to distinguish + // moved-from or uninitialized instances from initialized. + bool operator==(const NavigableAstBase& other) const { + return metadata_ == other.metadata_; + } + + bool operator!=(const NavigableAstBase& other) const { + return metadata_ != other.metadata_; + } + + // Return true if this instance is initialized. + explicit operator bool() const { return metadata_ != nullptr; } + + private: + friend Derived; + + NavigableAstBase() = default; + explicit NavigableAstBase(std::unique_ptr metadata) + : metadata_(std::move(metadata)) {} + + std::unique_ptr metadata_; +}; + } // namespace cel::common_internal #endif // THIRD_PARTY_CEL_CPP_COMMON_AST_NAVIGABLE_AST_INTERNAL_H_ diff --git a/tools/navigable_ast.cc b/tools/navigable_ast.cc index 63fb9e971..4ff9b9f06 100644 --- a/tools/navigable_ast.cc +++ b/tools/navigable_ast.cc @@ -39,6 +39,11 @@ using ::cel::expr::Expr; using ::google::api::expr::runtime::AstTraverse; using ::google::api::expr::runtime::SourcePosition; +using NavigableAstNodeData = + common_internal::NavigableAstNodeData; +using NavigableAstMetadata = + common_internal::NavigableAstMetadata; + NodeKind GetNodeKind(const Expr& expr) { switch (expr.expr_kind_case()) { case Expr::kConstExpr: @@ -67,8 +72,7 @@ NodeKind GetNodeKind(const Expr& expr) { // Get the traversal relationship from parent to the given node. // Note: these depend on the ast_visitor utility's traversal ordering. -ChildKind GetChildKind(const common_internal::NavigableAstNodeData< - NavigableProtoAstNode>& parent_node, +ChildKind GetChildKind(const NavigableAstNodeData& parent_node, size_t child_index) { constexpr size_t kComprehensionRangeArgIndex = google::api::expr::runtime::ITER_RANGE; @@ -122,17 +126,13 @@ class NavigableExprBuilderVisitor : public google::api::expr::runtime::AstVisitorBase { public: NavigableExprBuilderVisitor( - absl::AnyInvocable()> node_factory, - absl::AnyInvocable&(NavigableProtoAstNode&)> - node_data_accessor) + absl::AnyInvocable()> node_factory, + absl::AnyInvocable node_data_accessor) : node_factory_(std::move(node_factory)), node_data_accessor_(std::move(node_data_accessor)), - metadata_(std::make_unique>()) {} + metadata_(std::make_unique()) {} - common_internal::NavigableAstNodeData& NodeDataAt( - size_t index) { + NavigableAstNodeData& NodeDataAt(size_t index) { return node_data_accessor_(*metadata_->nodes[index]); } @@ -171,8 +171,7 @@ class NavigableExprBuilderVisitor size_t idx = parent_stack_.back(); parent_stack_.pop_back(); metadata_->postorder.push_back(metadata_->nodes[idx].get()); - common_internal::NavigableAstNodeData& node = - NodeDataAt(idx); + NavigableAstNodeData& node = NodeDataAt(idx); if (!parent_stack_.empty()) { auto& parent_node_data = NodeDataAt(parent_stack_.back()); parent_node_data.tree_size += node.tree_size; @@ -181,18 +180,14 @@ class NavigableExprBuilderVisitor } } - std::unique_ptr> - Consume() && { + std::unique_ptr Consume() && { return std::move(metadata_); } private: - absl::AnyInvocable()> node_factory_; - absl::AnyInvocable&(NavigableProtoAstNode&)> - node_data_accessor_; - std::unique_ptr> - metadata_; + absl::AnyInvocable()> node_factory_; + absl::AnyInvocable node_data_accessor_; + std::unique_ptr metadata_; std::vector parent_stack_; }; @@ -200,11 +195,8 @@ class NavigableExprBuilderVisitor NavigableProtoAst NavigableProtoAst::Build(const Expr& expr) { NavigableExprBuilderVisitor visitor( - []() { return absl::WrapUnique(new NavigableProtoAstNode()); }, - [](NavigableProtoAstNode& node) - -> common_internal::NavigableAstNodeData& { - return node.data_; - }); + []() { return absl::WrapUnique(new AstNode()); }, + [](AstNode& node) -> NavigableAstNodeData& { return node.data_; }); AstTraverse(&expr, /*source_info=*/nullptr, &visitor); return NavigableProtoAst(std::move(visitor).Consume()); } diff --git a/tools/navigable_ast.h b/tools/navigable_ast.h index 06fe809b1..5bf013cae 100644 --- a/tools/navigable_ast.h +++ b/tools/navigable_ast.h @@ -15,68 +15,70 @@ #ifndef THIRD_PARTY_CEL_CPP_TOOLS_NAVIGABLE_AST_H_ #define THIRD_PARTY_CEL_CPP_TOOLS_NAVIGABLE_AST_H_ -#include -#include -#include -#include -#include #include "cel/expr/syntax.pb.h" -#include "absl/base/nullability.h" -#include "absl/container/flat_hash_map.h" -#include "absl/types/span.h" #include "common/ast/navigable_ast_internal.h" #include "common/ast/navigable_ast_kinds.h" // IWYU pragma: export namespace cel { class NavigableProtoAst; +class NavigableProtoAstNode; + +namespace common_internal { + +struct ProtoAstTraits { + using ExprType = cel::expr::Expr; + using AstType = NavigableProtoAst; + using NodeType = NavigableProtoAstNode; +}; + +} // namespace common_internal // Wrapper around a CEL AST node that exposes traversal information. -class NavigableProtoAstNode { - public: - using ExprType = const cel::expr::Expr; +class NavigableProtoAstNode : public common_internal::NavigableAstNodeBase< + common_internal::ProtoAstTraits> { + private: + using Base = + common_internal::NavigableAstNodeBase; + public: // A const Span like type that provides pre-order traversal for a sub tree. // provides .begin() and .end() returning bidirectional iterators to - // const NavigableProtoAstNode&. - using PreorderRange = common_internal::NavigableAstRange< - common_internal::PreorderTraits>; + // const AstNode&. + using PreorderRange = Base::PreorderRange; // A const Span like type that provides post-order traversal for a sub tree. // provides .begin() and .end() returning bidirectional iterators to - // const NavigableProtoAstNode&. - using PostorderRange = common_internal::NavigableAstRange< - common_internal::PostorderTraits>; + // const AstNode&. + using PostorderRange = Base::PostorderRange; // The parent of this node or nullptr if it is a root. - const NavigableProtoAstNode* absl_nullable parent() const { - return data_.parent; - } + using Base::parent; - const cel::expr::Expr* absl_nonnull expr() const { - return data_.expr; - } + // The ptr to the backing Expr in the source AST. + // + // This may dangle if the source AST is mutated or destroyed. + using Base::expr; // The index of this node in the parent's children. -1 if this is a root. - int child_index() const { return data_.child_index; } + using Base::child_index; // The type of traversal from parent to this node. - ChildKind parent_relation() const { return data_.parent_relation; } + using Base::parent_relation; // The type of this node, analogous to Expr::ExprKindCase. - NodeKind node_kind() const { return data_.node_kind; } + using Base::node_kind; // The number of nodes in the tree rooted at this node (including self). - size_t tree_size() const { return data_.tree_size; } + using Base::tree_size; // The height of this node in the tree (the number of descendants including // self on the longest path). - size_t height() const { return data_.height; } + using Base::height; - absl::Span children() const { - return absl::MakeConstSpan(data_.children); - } + // The children of this node in their natural order. + using Base::children; // Range over the descendants of this node (including self) using preorder // semantics. Each node is visited immediately before all of its descendants. @@ -93,26 +95,16 @@ class NavigableProtoAstNode { // - maps are traversed in order (alternating key, value per entry) // - comprehensions are traversed in the order: range, accu_init, condition, // step, result - PreorderRange DescendantsPreorder() const { - return PreorderRange(absl::MakeConstSpan(data_.metadata->nodes) - .subspan(data_.index, data_.tree_size)); - } + using Base::DescendantsPreorder; // Range over the descendants of this node (including self) using postorder // semantics. Each node is visited immediately after all of its descendants. - PostorderRange DescendantsPostorder() const { - return PostorderRange(absl::MakeConstSpan(data_.metadata->postorder) - .subspan(data_.index, data_.tree_size)); - } + using Base::DescendantsPostorder; private: friend class NavigableProtoAst; NavigableProtoAstNode() = default; - NavigableProtoAstNode(const NavigableProtoAstNode&) = delete; - NavigableProtoAstNode& operator=(const NavigableProtoAstNode&) = delete; - - common_internal::NavigableAstNodeData data_; }; // NavigableExpr provides a view over a CEL AST that allows for generalized @@ -123,10 +115,15 @@ class NavigableProtoAstNode { // // Pointers to AstNodes are owned by this instance and must not outlive it. // -// `NavigableProtoAst` and Navigable nodes are independent of the input Expr and -// may outlive it, but may contain dangling pointers if the input Expr is -// modified or destroyed. -class NavigableProtoAst { +// `NavigableAst` and Navigable nodes are independent of the input Expr and may +// outlive it, but may contain dangling pointers if the input Expr is modified +// or destroyed. +class NavigableProtoAst : public common_internal::NavigableAstBase< + common_internal::ProtoAstTraits> { + private: + using Base = + common_internal::NavigableAstBase; + public: static NavigableProtoAst Build(const cel::expr::Expr& expr); @@ -148,57 +145,23 @@ class NavigableProtoAst { // // If ids are non-unique, the first pre-order node encountered with id is // returned. - const NavigableProtoAstNode* absl_nullable FindId(int64_t id) const { - auto it = metadata_->id_to_node.find(id); - if (it == metadata_->id_to_node.end()) { - return nullptr; - } - return it->second; - } - - // Return ptr to the AST node representing the given Expr protobuf node. - const NavigableProtoAstNode* absl_nullable FindExpr( - const cel::expr::Expr* expr) const { - auto it = metadata_->expr_to_node.find(expr); - if (it == metadata_->expr_to_node.end()) { - return nullptr; - } - return it->second; - } - - // The root of the AST. - const NavigableProtoAstNode& Root() const { return *metadata_->nodes[0]; } - - // Check whether the source AST used unique IDs for each node. + using Base::FindId; + + // Return ptr to the AST node representing the given Expr node. + using Base::FindExpr; + + // Returns the root of the AST. + using Base::Root; + + // Return whether the source AST used unique IDs for each node. // // This is typically the case, but older versions of the parsers didn't // guarantee uniqueness for nodes generated by some macros and ASTs modified // outside of CEL's parse/type check may not have unique IDs. - bool IdsAreUnique() const { - return metadata_->id_to_node.size() == metadata_->nodes.size(); - } - - // Equality operators test for identity. They are intended to distinguish - // moved-from or uninitialized instances from initialized. - bool operator==(const NavigableProtoAst& other) const { - return metadata_ == other.metadata_; - } - - bool operator!=(const NavigableProtoAst& other) const { - return metadata_ != other.metadata_; - } - - // Return true if this instance is initialized. - explicit operator bool() const { return metadata_ != nullptr; } + using Base::IdsAreUnique; private: - using AstMetadata = - common_internal::NavigableAstMetadata; - - explicit NavigableProtoAst(std::unique_ptr metadata) - : metadata_(std::move(metadata)) {} - - std::unique_ptr metadata_; + using Base::Base; }; // Type aliases for backwards compatibility.