blob: 7962feac5760bde5571c321d6871ae13c2d52603 [file] [log] [blame]
// 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_