| // Copyright 2017-2022 The Verible Authors. | |
| // | |
| // 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. | |
| // This file contains generic functions and trait classes for operating on tree | |
| // structures. | |
| // | |
| // TreeNode concept | |
| // ---------------- | |
| // | |
| // Class `T` must contain at least the following member in order to fulfill the | |
| // TreeNode concept: | |
| // | |
| // - `Container<T>& Children()`: | |
| // Returns reference to a STL-like container or a range containing the node's | |
| // children. | |
| // The Container must at least support `begin()` and `end()` methods. | |
| // Pointer and iterator stability is not required. | |
| // | |
| // To check whether class `T` fulfills the concept one can use the | |
| // `TreeNodeTraits<T>` traits struct. It is defined only for classes fulfilling | |
| // the TreeNode concept, what makes it suitable for SFINAE tests. For | |
| // readability and consistency with Value and Parent checks (described below) | |
| // there is a member `TreeNodeTraits<T>::available` which is always true. | |
| // | |
| // Optional members: | |
| // | |
| // - `T* Parent()`: | |
| // Returns pointer to a parent node or nullptr when the node is a tree root. | |
| // Types with this member can be detected by checking whether | |
| // `TreeNodeTraits<T>::Parent::available` is true. | |
| // | |
| // - `ValueType& Value()`: | |
| // Returns reference to a value stored in the node. | |
| // Types with this member can be detected by checking whether | |
| // `TreeNodeTraits<T>::Value::available` is true. | |
| // | |
| // - `subnodes_type` (typename): | |
| // Container type which should be used for storing collections of detached | |
| // child nodes. | |
| // It must be move-assignable to, and move-constructible from a value returned | |
| // by `Children()` method. It must provide at least the same STL container's | |
| // interface and iterator/pointer stability guarantees as a return type of | |
| // `Children()` method. | |
| // The use case where this alias is useful is when `Children()` returns a | |
| // container wrapped by some internal wrapper type that is either not | |
| // constructible outside of the node class or has some extra logic that is | |
| // not useful when used outside of a node. The `subnodes_type` in such case | |
| // should be an alias to the container's real type. | |
| // When the type is absent, it is assumed to be the type returned by | |
| // `Children()` method with reference and other modifiers removed. | |
| // It is available through `TreeNodeTraits<T>::Children::container_type`. | |
| #ifndef VERIBLE_COMMON_UTIL_TREE_OPERATIONS_H_ | |
| #define VERIBLE_COMMON_UTIL_TREE_OPERATIONS_H_ | |
| #include <algorithm> | |
| #include <cstddef> | |
| #include <functional> | |
| #include <iosfwd> // IWYU pragma: keep | |
| #include <iterator> | |
| #include <numeric> | |
| #include <set> | |
| #include <utility> | |
| #include <vector> | |
| #include "common/util/logging.h" | |
| #include "common/util/spacer.h" | |
| #include "common/util/type_traits.h" | |
| namespace verible { | |
| namespace tree_operations_internal { | |
| // TreeNodeTraits implementation details: | |
| // Alias to Node::subnodes_type if it exists. | |
| // Intended for use with detected_or_t. | |
| template <typename Node, // | |
| typename Type_ = typename Node::subnodes_type> | |
| using TreeNodeSubnodesType = Type_; | |
| // Defined when `Node` contains `Children()` method which returns reference to | |
| // a STL-like container with nodes. | |
| // TODO(mglb): Add support for children containers storing Node pointers. | |
| template <typename Node, // | |
| typename ChildrenType_ = decltype(std::declval<Node>().Children()), | |
| typename = | |
| std::void_t<decltype(*std::declval<Node>().Children().begin()), | |
| decltype(std::declval<Node>().Children().end())>> | |
| struct TreeNodeChildrenTraits : FeatureTraits { | |
| // Container type which should be used for storing arrays of detached | |
| // child nodes. It must be move-assignable to, and move-constructible from, | |
| // a value returned by `Children()` method. | |
| using container_type = verible::remove_cvref_t< | |
| detected_or_t<ChildrenType_, TreeNodeSubnodesType, Node>>; | |
| }; | |
| // Defined when `Node` contains `Value()` method. | |
| template <typename Node, // | |
| typename Value_ = decltype(std::declval<Node>().Value())> | |
| struct TreeNodeValueTraits : FeatureTraits { | |
| // Type of data returned by `Value()` method, without reference and const. | |
| using type = std::remove_const_t<std::remove_reference_t<Value_>>; | |
| // Reference to a type returned by `Value()` matching its constness. | |
| using reference = std::add_lvalue_reference_t<Value_>; | |
| // Const reference to a type returned by `Value()` | |
| using const_reference = std::add_lvalue_reference_t<std::add_const_t<type>>; | |
| }; | |
| // Defined when `Node` contains `Parent()` method which dereferences to a type | |
| // fulfilling NodeTraits concept. | |
| template <typename Node, // | |
| typename Parent_ = decltype(*std::declval<Node>().Parent()), | |
| typename = std::void_t<TreeNodeChildrenTraits<Parent_>>> | |
| struct TreeNodeParentTraits : FeatureTraits {}; | |
| // BirthRank implementation details: | |
| // BirthRank implementation supporting any container. | |
| // This overload is a linear-time operation. | |
| template <class T> | |
| size_t BirthRank(const T& node, std::input_iterator_tag) { | |
| if (node.Parent() != nullptr) { | |
| size_t index = 0; | |
| for (const auto& child : node.Parent()->Children()) { | |
| if (&node == &child) return index; | |
| ++index; | |
| } | |
| } | |
| return 0; | |
| } | |
| // BirthRank optimized for random access containers. | |
| template <class T> | |
| size_t BirthRank(const T& node, std::random_access_iterator_tag) { | |
| if (node.Parent() != nullptr) { | |
| return std::distance(&*(node.Parent()->Children().begin()), &node); | |
| } | |
| return 0; | |
| } | |
| // Conditional container operations: | |
| // Calls `container.reserve(new_cap)` if container supports this method. | |
| template <class Container> | |
| auto /* void */ ReserveIfSupported(Container& container, | |
| typename Container::size_type new_cap) | |
| -> std::void_t<decltype(container.reserve(new_cap))> { | |
| container.reserve(new_cap); | |
| } | |
| // No-op candidate used when Container doesn't provide `reserve()` method. | |
| template <class Container> | |
| void ReserveIfSupported(Container&, ...) {} | |
| } // namespace tree_operations_internal | |
| // TreeNodeTraits: | |
| // Traits of a type fulfilling TreeNode concept. | |
| // `TreeNodeTraits<T>` is defined for every class `T` fulfilling the TreeNode | |
| // concept. It can be used in SFINAE tests. | |
| template <class Node, // | |
| typename Children_ = | |
| tree_operations_internal::TreeNodeChildrenTraits<Node>> | |
| struct TreeNodeTraits : FeatureTraits { | |
| using Parent = | |
| detected_or_t<UnavailableFeatureTraits, | |
| tree_operations_internal::TreeNodeParentTraits, Node>; | |
| using Value = | |
| detected_or_t<UnavailableFeatureTraits, | |
| tree_operations_internal::TreeNodeValueTraits, Node>; | |
| using Children = Children_; | |
| }; | |
| // Functions operating on tree nodes: | |
| // Returns true when `node` is a leaf, false otherwise. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::available>* = nullptr> | |
| bool is_leaf(const T& node) { | |
| return node.Children().empty(); | |
| } | |
| // Descend through children using indices specified by iterator range. | |
| // Iterator type must dereference to an integral value. | |
| // This works on any internal node, not just the root. | |
| // This implementation fatally exits if any indices are out-of-range. | |
| // The const-ness of the returned reference matches the const-ness of the node | |
| // argument. | |
| template <class T, class Iterator, // | |
| std::enable_if_t<TreeNodeTraits<T>::available>* = nullptr> | |
| T& DescendPath(T& node, Iterator start, Iterator end) { | |
| auto* current_node = &node; | |
| for (auto iter = start; iter != end; ++iter) { | |
| auto& children = current_node->Children(); | |
| const std::size_t index = *iter; | |
| CHECK_LT(index, std::size(children)); | |
| current_node = &*std::next(children.begin(), index); // descend | |
| } | |
| return *current_node; | |
| } | |
| // Returns the node reached by descending through *(Children().begin()). | |
| // The const-ness of the returned reference matches the const-ness of the node | |
| // argumnent. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::available>* = nullptr> | |
| T& LeftmostDescendant(T& node) { | |
| T* leaf = &node; | |
| while (!leaf->Children().empty()) { | |
| leaf = &*leaf->Children().begin(); | |
| } | |
| return *leaf; | |
| } | |
| // Returns the node reached by descending through Children().back(). | |
| // The const-ness of the returned reference matches the const-ness of the node | |
| // argumnent. | |
| // | |
| // Requires `back()` method in the children container. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::available>* = nullptr, | |
| std::void_t<decltype(std::declval<T>().Children().back())>* = nullptr> | |
| T& RightmostDescendant(T& node) { | |
| T* leaf = &node; | |
| while (!leaf->Children().empty()) { | |
| leaf = &leaf->Children().back(); | |
| } | |
| return *leaf; | |
| } | |
| // std::function type representing a printer function in PrintTree. | |
| template <class Node> | |
| using PrintTreePrinterFunction = std::function<std::ostream&( | |
| std::ostream&, typename TreeNodeTraits<Node>::Value::const_reference)>; | |
| // Pretty-print in tree-form. Value() is enclosed in parens, and the whole | |
| // node is enclosed in braces. | |
| // This variant supports a custom value 'printer'. | |
| // | |
| // Requires `Value()` method in the node. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::Value::available>* = nullptr> | |
| std::ostream& PrintTree(const T& node, std::ostream* stream, | |
| const PrintTreePrinterFunction<T>& printer, | |
| size_t indent = 0) { | |
| printer(*stream << Spacer(indent) << "{ (", node.Value()) << ')'; | |
| if (node.Children().empty()) { | |
| *stream << " }"; | |
| } else { | |
| *stream << '\n'; | |
| for (const auto& child : node.Children()) { | |
| PrintTree(child, stream, printer, indent + 2) << '\n'; | |
| } | |
| *stream << Spacer(indent) << '}'; | |
| } | |
| return *stream; | |
| } | |
| // Pretty-print tree, using the default stream printer, which requires that | |
| // operator<<(std::ostream&, const value_type&) is defined. | |
| // | |
| // Requires `Value()` method in the node. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::Value::available>* = nullptr> | |
| std::ostream& PrintTree(const T& node, std::ostream* stream, | |
| size_t indent = 0) { | |
| using ConstValueRef = typename TreeNodeTraits<T>::Value::const_reference; | |
| return PrintTree( | |
| node, stream, | |
| [](std::ostream& s, ConstValueRef v) -> std::ostream& { return s << v; }, | |
| indent); | |
| } | |
| // Stream operator overload that calls PrintTree with given stream and node. | |
| // | |
| // Requires `Value()` method in the node. | |
| template <typename T, // | |
| std::enable_if_t<TreeNodeTraits<T>::Value::available>* = nullptr> | |
| std::ostream& operator<<(std::ostream& stream, const T& node) { | |
| return PrintTree(node, &stream); | |
| } | |
| // Function-application traversals. | |
| // - Apply in pre-order vs. post-order. | |
| // - Apply to attached value at each node vs. the whole node itself. | |
| // Whole node gives access to immediate children (and subtree). | |
| // - Apply function is const vs. mutating. | |
| // All combinations of the above are provided. | |
| // std::function type representing function called on each node in | |
| // Apply(Pre|Post)Order. | |
| template <class Node> | |
| using ApplyOnNodeFunction = | |
| std::function<void(std::add_lvalue_reference_t<Node>)>; | |
| // Visits all tree nodes in pre-order traversal applying function to all nodes. | |
| // Useful for checking invariants between parents and their children. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::available>* = nullptr> | |
| void ApplyPreOrder(T& node, const ApplyOnNodeFunction<T>& f) { | |
| f(node); | |
| for (auto& child : node.Children()) ApplyPreOrder(child, f); | |
| } | |
| // Visits all tree nodes in post-order traversal applying function to all nodes. | |
| // Useful for checking invariants between parents and their children. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::available>* = nullptr> | |
| void ApplyPostOrder(T& node, const ApplyOnNodeFunction<T>& f) { | |
| for (auto& child : node.Children()) ApplyPostOrder(child, f); | |
| f(node); | |
| } | |
| // std::function type representing function called on each node's value in | |
| // Apply(Pre|Post)Order. | |
| template <class Node> | |
| using ApplyOnNodeValueFunction = | |
| std::function<void(typename TreeNodeTraits<Node>::Value::reference)>; | |
| // This variant of ApplyPreOrder expects a function on the underlying | |
| // value_type (const&). | |
| // | |
| // Requires `Value()` method in the node. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::Value::available>* = nullptr> | |
| void ApplyPreOrder(T& node, const ApplyOnNodeValueFunction<T>& f) { | |
| f(node.Value()); | |
| for (auto& child : node.Children()) ApplyPreOrder(child, f); | |
| } | |
| // This variant of ApplyPostOrder expects a function on the underlying | |
| // value_type. | |
| // | |
| // Requires `Value()` method in the node. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::Value::available>* = nullptr> | |
| void ApplyPostOrder( | |
| T& node, | |
| const std::function<void(typename TreeNodeTraits<T>::Value::reference)>& | |
| f) { | |
| for (auto& child : node.Children()) ApplyPostOrder(child, f); | |
| f(node.Value()); | |
| } | |
| // Returns the index of this node relative to parent's children. | |
| // An only-child, first-child, and a tree root will have birth rank 0. | |
| // This function is aware of children container type: it uses `std::distance()` | |
| // to calculate node index in random access containers (which usually performs | |
| // in O(1)), and O(n) loop for other containers. | |
| // | |
| // Requires `Parent()` method in the node. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::Parent::available>* = nullptr> | |
| size_t BirthRank(const T& node) { | |
| using Iterator = decltype(node.Parent()->Children().begin()); | |
| return tree_operations_internal::BirthRank( | |
| node, typename std::iterator_traits<Iterator>::iterator_category()); | |
| } | |
| // Returns the number of parents between this node and the root. | |
| // | |
| // Requires `Parent()` method in the node. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::Parent::available>* = nullptr> | |
| size_t NumAncestors(const T& node) { | |
| size_t depth = 0; | |
| for (const T* p = node.Parent(); p != nullptr; p = p->Parent()) { | |
| ++depth; | |
| } | |
| return depth; | |
| } | |
| // Returns true if 'other' is an ancestor of this node, in other words, | |
| // this node is descended from 'other'. | |
| // This method could have been named IsDescendedFrom(). | |
| // nullptr is never considered an ancestor of any node. | |
| // 'this' node is not considered an ancestor of itself. | |
| // | |
| // Requires `Parent()` method in the node. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::Parent::available>* = nullptr> | |
| bool HasAncestor(const T& node, const T* other) { | |
| if (other == nullptr) return false; | |
| for (const auto* p = node.Parent(); p != nullptr; p = p->Parent()) { | |
| if (p == other) return true; | |
| } | |
| return false; | |
| } | |
| // Overload for nullptr. Always returns false. | |
| // | |
| // Requires `Parent()` method in the node. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::Parent::available>* = nullptr> | |
| constexpr bool HasAncestor(const T& node, std::nullptr_t) { | |
| return false; | |
| } | |
| // Returns reference to the tree root, the greatest ancestor of this node. | |
| // The const-ness of the returned reference matches the const-ness of the node | |
| // argumnent. | |
| // | |
| // Requires `Parent()` method in the node. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::Parent::available>* = nullptr> | |
| T& Root(T& node) { | |
| T* root = &node; | |
| while (root->Parent() != nullptr) { | |
| root = root->Parent(); | |
| } | |
| return *root; | |
| } | |
| // Returns the closest common ancestor to this and the other, else nullptr. | |
| // The const-ness of the returned pointer matches the const-ness of the node | |
| // argumnent. | |
| // | |
| // Requires `Parent()` method in the node. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::Parent::available>* = nullptr> | |
| T* NearestCommonAncestor(T& node_a, T& node_b) { | |
| T* a = &node_a; | |
| T* b = &node_b; | |
| std::set<T*> ancestors_a, ancestors_b; | |
| // In alternation, insert a/b into its respective set of ancestors, | |
| // and check for membership in the other ancestor set. | |
| // Return as soon as one is found in the other's set of ancestors. | |
| while (a != nullptr || b != nullptr) { | |
| if (a != nullptr) { | |
| if (ancestors_b.find(a) != ancestors_b.end()) { | |
| return a; | |
| } | |
| ancestors_a.insert(a); | |
| a = a->Parent(); | |
| } | |
| if (b != nullptr) { | |
| if (ancestors_a.find(b) != ancestors_a.end()) { | |
| return b; | |
| } | |
| ancestors_b.insert(b); | |
| b = b->Parent(); | |
| } | |
| } | |
| // Once this point is reached, there are no common ancestors. | |
| return nullptr; | |
| } | |
| // Returns true if this node has no parent, or it has BirthRank 0. | |
| // | |
| // Requires `Parent()` method in the node. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::Parent::available>* = nullptr> | |
| bool IsFirstChild(const T& node) { | |
| if (node.Parent() == nullptr) return true; | |
| return &*node.Parent()->Children().begin() == &node; | |
| } | |
| // Returns true if this node has no parent, or it is the last child of its | |
| // parent. | |
| // | |
| // Requires `back()` method in the children container. | |
| // Requires `Parent()` method in the node. | |
| template < | |
| class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::Parent::available>* = nullptr, | |
| std::void_t<decltype(std::declval<const T>().Children().back())>* = nullptr> | |
| bool IsLastChild(const T& node) { | |
| if (node.Parent() == nullptr) return true; | |
| return &node.Parent()->Children().back() == &node; | |
| } | |
| // Navigates to the next leaf (node without Children()) in the tree | |
| // (if it exists), else returns nullptr. | |
| // The const-ness of the returned pointer matches the const-ness of the node | |
| // argumnent. | |
| // | |
| // Requires `Parent()` method in the node. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::Parent::available>* = nullptr> | |
| T* NextLeaf(T& node) { | |
| auto* parent = node.Parent(); | |
| if (parent == nullptr) { | |
| // Root node has no next sibling, this is the end(). | |
| return nullptr; | |
| } | |
| // Find the next sibling, if there is one. | |
| auto& siblings = parent->Children(); | |
| const size_t birth_rank = BirthRank(node); | |
| const size_t next_rank = birth_rank + 1; | |
| if (next_rank != std::size(siblings)) { | |
| // More children follow this one. | |
| return &LeftmostDescendant(*std::next(siblings.begin(), next_rank)); | |
| } | |
| // This is the last child of the group. | |
| // Find the nearest parent that has a next child (ascending). | |
| // TODO(fangism): rewrite without recursion | |
| auto* next_ancestor = NextLeaf(*parent); | |
| if (next_ancestor == nullptr) return nullptr; | |
| // next_ancestor is the NearestCommonAncestor() to the original | |
| // node and the resulting node. | |
| return &LeftmostDescendant(*next_ancestor); | |
| } | |
| // Navigates to the previous leaf (node without Children()) in the tree | |
| // (if it exists), else returns nullptr. | |
| // The const-ness of the returned pointer matches the const-ness of the node | |
| // argumnent. | |
| // | |
| // Requires `Parent()` method in the node. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::Parent::available>* = nullptr> | |
| T* PreviousLeaf(T& node) { | |
| auto* parent = node.Parent(); | |
| if (parent == nullptr) { | |
| // Root node has no previous sibling, this is the reverse-end(). | |
| return nullptr; | |
| } | |
| // Find the next sibling, if there is one. | |
| auto& siblings = parent->Children(); | |
| const size_t birth_rank = BirthRank(node); | |
| if (birth_rank > 0) { | |
| // More children precede this one. | |
| return &RightmostDescendant(*std::next(siblings.begin(), birth_rank - 1)); | |
| } | |
| // This is the first child of the group. | |
| // Find the nearest parent that has a previous child (descending). | |
| // TODO(fangism): rewrite without recursion | |
| auto* prev_ancestor = PreviousLeaf(*parent); | |
| if (prev_ancestor == nullptr) return nullptr; | |
| // prev_ancestor is the NearestCommonAncestor() to the original | |
| // node and the resulting node. | |
| return &RightmostDescendant(*prev_ancestor); | |
| } | |
| // Returns the next sibling node if it exists, else nullptr. | |
| // The const-ness of the returned pointer matches the const-ness of the node | |
| // argumnent. | |
| // | |
| // Requires `Parent()` method in the node. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::Parent::available>* = nullptr> | |
| T* NextSibling(T& node) { | |
| if (node.Parent() == nullptr) { | |
| return nullptr; | |
| } | |
| const size_t birth_rank = BirthRank(node); | |
| const size_t next_rank = birth_rank + 1; | |
| if (next_rank == std::size(node.Parent()->Children())) { | |
| return nullptr; // This is the last child of the Parent(). | |
| } | |
| // More children follow this one. | |
| return &*std::next(node.Parent()->Children().begin(), next_rank); | |
| } | |
| // Returns the previous sibling node if it exists, else nullptr. | |
| // The const-ness of the returned pointer matches the const-ness of the node | |
| // argumnent. | |
| // | |
| // Requires `Parent()` method in the node. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::Parent::available>* = nullptr> | |
| T* PreviousSibling(T& node) { | |
| if (node.Parent() == nullptr) { | |
| return nullptr; | |
| } | |
| const size_t birth_rank = BirthRank(node); | |
| if (birth_rank == 0) { | |
| return nullptr; | |
| } | |
| // More children precede this one. | |
| return &*std::next(node.Parent()->Children().begin(), birth_rank - 1); | |
| } | |
| // Removes this node from its parent, and shifts ths siblings that follow this | |
| // node lower in BirthRank(). | |
| // Any iterators that pointed to this node or its later siblings are | |
| // invalidated. | |
| // This node is destroyed in the process. | |
| // This operation is only valid on non-root nodes. | |
| // It is the caller's responsibility to maintain invariants before | |
| // destroying this node. | |
| // | |
| // Requires `Parent()` method in the node. | |
| // Requires `erase()` method in the children container. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::Parent::available && | |
| !std::is_const_v<T>>* = nullptr> | |
| void RemoveSelfFromParent(T& node) { | |
| auto& siblings = ABSL_DIE_IF_NULL(node.Parent())->Children(); | |
| auto self_iter = std::next(siblings.begin(), verible::BirthRank(node)); | |
| CHECK_EQ(&*self_iter, &node); | |
| siblings.erase(self_iter); | |
| } | |
| // Appends one or more sub-trees at this level. | |
| // Variadic template handles one argument at a time. | |
| // This invalidates previous iterators/pointers to sibling children. | |
| // | |
| // Requires `push_back()` method in the children container. | |
| template <class T, typename... AdoptedNodeN> | |
| std::enable_if_t<TreeNodeTraits<T>::available && !std::is_const_v<T> && | |
| (std::is_convertible_v<std::decay_t<AdoptedNodeN>, T> && ...)> | |
| AdoptSubtree(T& node, AdoptedNodeN&&... node_n) { | |
| using tree_operations_internal::ReserveIfSupported; | |
| ReserveIfSupported(node.Children(), | |
| std::size(node.Children()) + sizeof...(node_n)); | |
| (node.Children().push_back(std::forward<AdoptedNodeN>(node_n)), ...); | |
| } | |
| // This node takes/moves subtrees from another node (concatenates). | |
| // There need not be any relationship between this node and the other. | |
| // | |
| // Requires `push_back()` and `clear()` method in the children container. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::available && | |
| !std::is_const_v<T>>* = nullptr> | |
| void AdoptSubtreesFrom(T& node, T* other) { | |
| using tree_operations_internal::ReserveIfSupported; | |
| auto& src_children = other->Children(); | |
| ReserveIfSupported(node.Children(), | |
| std::size(node.Children()) + std::size(src_children)); | |
| for (auto& src_child : src_children) { | |
| node.Children().push_back(std::move(src_child)); | |
| } | |
| other->Children().clear(); | |
| } | |
| // Recursively transform a SrcTree to DstTree. | |
| // The resulting tree is always StructureEqual() to the original tree. | |
| // | |
| // Usage: | |
| // auto tree_other = orig_tree.Transform<OtherType>( | |
| // [](const SrcTree& node) { return ... }); | |
| // | |
| // Requires `push_back()` method in the children container. | |
| template < | |
| class DstTree, class SrcTree, class SrcNodeToDstValueFunc, | |
| class DstValue_ = | |
| std::invoke_result_t<SrcNodeToDstValueFunc, const SrcTree&>, // | |
| std::enable_if_t<TreeNodeTraits<DstTree>::available && | |
| TreeNodeTraits<SrcTree>::available && | |
| std::is_constructible_v<DstTree, DstValue_>>* = nullptr> | |
| DstTree Transform(const SrcTree& src_node, const SrcNodeToDstValueFunc& f) { | |
| using tree_operations_internal::ReserveIfSupported; | |
| // Using invoke() to allow passing SrcTree's method pointers as `f` | |
| DstTree dst_node(std::invoke(f, src_node)); | |
| ReserveIfSupported(dst_node.Children(), std::size(src_node.Children())); | |
| for (const auto& child : src_node.Children()) { | |
| AdoptSubtree(dst_node, Transform<DstTree>(child, f)); | |
| } | |
| return dst_node; | |
| } | |
| // If this node has exactly one child, replace this node with that child | |
| // and return true, otherwise, do nothing and return false. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::available && | |
| !std::is_const_v<T>>* = nullptr> | |
| bool HoistOnlyChild(T& node) { | |
| if (std::size(node.Children()) != 1) return false; | |
| // Can't do this directly, as assignment to node destroys its child | |
| // (`only`) before it is moved. | |
| auto only = std::move(*node.Children().begin()); | |
| node = std::move(only); | |
| return true; | |
| } | |
| // Combines the Nth and (N+1) sibling using a custom function 'joiner' on the | |
| // nodes' values, and the Nth sibling will adopt N+1's children. | |
| // The 'joiner' function does: *left = f(*left, *right); | |
| // The (N+1) sibling will be erased in the process, and every sibling | |
| // thereafter will be shifted back one position. Depending on a children | |
| // container type this can invalidate all interators after position N and | |
| // interators to Nth node children. This applies e.g. for vector-like | |
| // containers, with contiguous storage. | |
| // | |
| // Requires `Value()` method in the node. | |
| // Requires `push_back()` and `erase()` methods in the children container. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::Value::available && | |
| !std::is_const_v<T>>* = nullptr> | |
| void MergeConsecutiveSiblings( | |
| T& node, size_t N, | |
| std::function<void(typename TreeNodeTraits<T>::Value::type*, | |
| typename TreeNodeTraits<T>::Value::const_reference)> | |
| joiner) { | |
| CHECK_LT(N + 1, std::size(node.Children())); | |
| // Combine value into node[N]. | |
| const auto nth_child = std::next(node.Children().begin(), N); | |
| const auto next_child = std::next(nth_child); | |
| joiner(&nth_child->Value(), next_child->Value()); | |
| // Move-concatenate children to node[N]. | |
| verible::AdoptSubtreesFrom(*nth_child, &*next_child); | |
| // Shift-left children_ by 1 beyond N. | |
| node.Children().erase(next_child); // done via move-assignment | |
| } | |
| // Replace all direct children of this node with concatenated grandchildren. | |
| // Retains the value of this node. Discards direct childrens' values. | |
| // | |
| // Requires `insert()` and `erase()` methods in the children container. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::available && | |
| !std::is_const_v<T>>* = nullptr> | |
| void FlattenOnce(T& node) { | |
| const int grandchildren_count = std::transform_reduce( | |
| node.Children().begin(), node.Children().end(), 0, std::plus<>(), | |
| [](const T& gc) { return std::size(gc.Children()); }); | |
| using tree_operations_internal::ReserveIfSupported; | |
| // Build new children list in a standalone container, then move-assign it to | |
| // this node's children container. | |
| using Container = typename TreeNodeTraits<T>::Children::container_type; | |
| Container grandchildren; | |
| ReserveIfSupported(grandchildren, grandchildren_count); | |
| for (auto& child : node.Children()) { | |
| for (auto& grandchild : child.Children()) { | |
| grandchildren.push_back(std::move(grandchild)); | |
| } | |
| } | |
| node.Children() = std::move(grandchildren); | |
| } | |
| // For every child, if that child has grandchildren, replace that child with | |
| // its grandchildren, else preserve that child. | |
| // If new_offsets is provided, populate that array with indices into the | |
| // resulting children that correspond to the start locations of the original | |
| // children's children. This lets the caller reference and operate on | |
| // subranges of the original set of grandchildren. | |
| // | |
| // Requires `push_back()` method in the children container. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::available && | |
| !std::is_const_v<T>>* = nullptr> | |
| void FlattenOnlyChildrenWithChildren( | |
| T& node, std::vector<size_t>* new_offsets = nullptr) { | |
| const int new_children_count = std::transform_reduce( | |
| node.Children().begin(), node.Children().end(), 0, std::plus<>(), | |
| [](const T& gc) { | |
| return std::max<size_t>(std::size(gc.Children()), 1u); | |
| }); | |
| using tree_operations_internal::ReserveIfSupported; | |
| // Build new children list in a standalone container, then move-assign it to | |
| // this node's children container. | |
| using Container = typename TreeNodeTraits<T>::Children::container_type; | |
| Container new_children; | |
| ReserveIfSupported(new_children, new_children_count); | |
| if (new_offsets != nullptr) { | |
| new_offsets->clear(); | |
| new_offsets->reserve(std::size(node.Children())); | |
| } | |
| size_t new_index = 0; | |
| for (auto& child : node.Children()) { | |
| if (new_offsets) new_offsets->push_back(new_index); | |
| if (child.Children().empty()) { | |
| // Use child node | |
| new_children.push_back(std::move(child)); | |
| ++new_index; | |
| } else { | |
| // Use grandchildren | |
| for (auto& grandchild : child.Children()) { | |
| new_children.push_back(std::move(grandchild)); | |
| ++new_index; | |
| } | |
| } | |
| } | |
| node.Children() = std::move(new_children); | |
| } | |
| // Replace the i'th child with its children. This may result in increasing | |
| // the number of direct children of this node. | |
| // | |
| // Requires `insert()` and `erase()` methods in the children container. | |
| template <class T, // | |
| std::enable_if_t<TreeNodeTraits<T>::available && | |
| !std::is_const_v<T>>* = nullptr> | |
| void FlattenOneChild(T& node, size_t i) { | |
| const size_t original_size = std::size(node.Children()); | |
| CHECK_LT(i, original_size); | |
| auto ith_child = std::next(node.Children().begin(), i); | |
| if (is_leaf(*ith_child)) { | |
| // Empty list of grandchildren, just remove the child. | |
| node.Children().erase(ith_child); | |
| return; | |
| } | |
| // Move-insert all grandchildren except the first one after the child. | |
| node.Children().insert( | |
| std::next(ith_child, 1), | |
| std::make_move_iterator(std::next(ith_child->Children().begin(), 1)), | |
| std::make_move_iterator(ith_child->Children().end())); | |
| // Possible reallocation and iterator invalidation above; update iterator. | |
| ith_child = std::next(node.Children().begin(), i); | |
| // Move the first grandchild into the child's place. Can't do this directly, | |
| // as assignment to *ith_child destroys the grandchild before it is moved. | |
| auto first_granchild = std::move(*ith_child->Children().begin()); | |
| *ith_child = std::move(first_granchild); | |
| } | |
| // Construct a path of BirthRank()s from root to this. | |
| // Root node's 'path' is empty. Passing the resulting path to | |
| // root.DescendPath() gets you back to this node. | |
| // PathType can be any container with a `push_back()` interface. | |
| // | |
| // Requires `Parent()` method in the node. | |
| template <class T, class PathType, // | |
| std::enable_if_t<TreeNodeTraits<T>::Parent::available>* = nullptr> | |
| void Path(const T& node, PathType& path) { | |
| if (node.Parent() != nullptr) { | |
| Path(*node.Parent(), path); | |
| path.push_back(verible::BirthRank(node)); | |
| } | |
| } | |
| // Stream-printable representation of the location of a node under its | |
| // greatest ancestor (root). | |
| // Usage: stream << NodePath(node); | |
| // | |
| // Requires `Parent()` method in the node. | |
| struct NodePath { | |
| template <class T, | |
| std::enable_if_t<TreeNodeTraits<T>::Parent::available>* = nullptr> | |
| explicit NodePath(const T& node) { | |
| verible::Path(node, path); | |
| } | |
| std::vector<size_t> path; | |
| }; | |
| std::ostream& operator<<(std::ostream& stream, const NodePath& p); | |
| // Binary operations | |
| // Struct used to point to corresponding two nodes in different trees. | |
| // This could be interpreted as a difference object. | |
| // Both pointers should be nullptr when DeepEqual() finds no differences between | |
| // any corresponding pair of tree nodes, or non-nullptr when some difference is | |
| // found. When they are both non-nullptr, left != right by some user-defined | |
| // comparison function, and their ->Path()s are equivalent (as ensured by | |
| // DeepEqual's simultaneous traversal). | |
| // This is analogous to std::mismatch()'s return type: a pair of non-end() | |
| // iterators pointing to the first difference found in a dual-linear | |
| // traversal. | |
| template <typename LT, typename RT, | |
| std::enable_if_t<TreeNodeTraits<LT>::available && | |
| TreeNodeTraits<RT>::available>* = nullptr> | |
| struct TreeNodePair { | |
| TreeNodePair() {} | |
| TreeNodePair(const LT* l, const RT* r) : left(l), right(r) {} | |
| const LT* left = nullptr; | |
| const RT* right = nullptr; | |
| }; | |
| // Recursively compares two trees node-for-node, checking their values | |
| // and substructure. The value types of the trees being compared need not be | |
| // the same, as long as there exists a comparison function for them. | |
| // The comparison function should be an equality function, i.e. it should | |
| // return true when values are considered equal, otherwise false. | |
| // Traversal order: pre-order (compare parents before children) | |
| // Returns pair of pointers pointing to first encountered difference, | |
| // or pair of nullptr when everything matches. | |
| // If you want more detail about value differences, then capture them in the | |
| // comparison function's closure before returning. | |
| // | |
| // Requires `Value()` method in the node. | |
| template <typename LT, typename RT, | |
| std::enable_if_t<TreeNodeTraits<LT>::Value::available && | |
| TreeNodeTraits<RT>::Value::available>* = nullptr> | |
| TreeNodePair<LT, RT> DeepEqual( | |
| const LT& left, const RT& right, | |
| const std::function< | |
| bool(typename TreeNodeTraits<LT>::Value::const_reference, | |
| typename TreeNodeTraits<RT>::Value::const_reference)>& comp) { | |
| using result_type = TreeNodePair<LT, RT>; | |
| // Node value comparison at current level. | |
| if (!comp(left.Value(), right.Value())) { | |
| return result_type{&left, &right}; | |
| } | |
| // Subtree comparison: check number of children first, returning early if | |
| // different. | |
| const auto& left_children = left.Children(); | |
| const auto& right_children = right.Children(); | |
| if (std::size(left_children) != std::size(right_children)) { | |
| return result_type{&left, &right}; | |
| } | |
| // Number of children match. Find first differing children. | |
| // The iterators returned by std::mismatch() do not propagate the | |
| // deep children nodes that differ, so we must use a lambda with capture. | |
| result_type first_diff; // initially nullptrs | |
| (void)std::mismatch(left_children.begin(), left_children.end(), | |
| right_children.begin(), | |
| [&comp, &first_diff](const LT& l, const RT& r) -> bool { | |
| const auto result = DeepEqual(l, r, comp); | |
| if (result.left == nullptr) { | |
| return true; | |
| } else { | |
| first_diff = result; // Capture first difference. | |
| return false; | |
| } | |
| // When this returns true, no further comparisons will | |
| // be called, so the assignment to first_diff will | |
| // contain the desired result. | |
| }); | |
| // When every subtree matches, first_diff will hold nullptrs. | |
| // Otherwise it will point to the first mismatched nodes. | |
| return first_diff; | |
| } | |
| // Overload for the case when left and right tree types have a defined equality | |
| // operator. This also works when the left and right types are the same. | |
| // | |
| // Requires `Value()` method in the node. | |
| template <typename LT, typename RT, | |
| std::enable_if_t<TreeNodeTraits<LT>::Value::available && | |
| TreeNodeTraits<RT>::Value::available>* = nullptr> | |
| TreeNodePair<LT, RT> DeepEqual(const LT& left, const RT& right) { | |
| using LValueRef = typename TreeNodeTraits<LT>::Value::const_reference; | |
| using RValueRef = typename TreeNodeTraits<RT>::Value::const_reference; | |
| return DeepEqual(left, right, | |
| [](LValueRef l, RValueRef r) { return l == r; }); | |
| } | |
| // If both trees are structurally the same, node-for-node, returns a pair of | |
| // nullptrs. Otherwise, returns with pointers to the first encountered nodes | |
| // that differ in substructure. Traversal order: pre-order. | |
| // Implementation: this is just a degenerate form of DeepEqual, where the value | |
| // comparison is ignored (always returns true). | |
| // | |
| // Requires `Value()` method in the node. | |
| template <typename LT, typename RT, | |
| std::enable_if_t<TreeNodeTraits<LT>::Value::available && | |
| TreeNodeTraits<RT>::Value::available>* = nullptr> | |
| TreeNodePair<LT, RT> StructureEqual(const LT& left, const RT& right) { | |
| using LValueRef = typename TreeNodeTraits<LT>::Value::const_reference; | |
| using RValueRef = typename TreeNodeTraits<RT>::Value::const_reference; | |
| // Ignore node values by always treating them as equal. | |
| return DeepEqual(left, right, [](LValueRef, RValueRef) { return true; }); | |
| } | |
| } // namespace verible | |
| #endif // VERIBLE_COMMON_UTIL_TREE_OPERATIONS_H_ |