// Copyright 2021 The Abseil 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 // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef ABSL_STRINGS_INTERNAL_CORD_REP_BTREE_NAVIGATOR_H_ #define ABSL_STRINGS_INTERNAL_CORD_REP_BTREE_NAVIGATOR_H_ #include #include #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_rep_btree.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace cord_internal { // CordRepBtreeNavigator is a bi-directional navigator allowing callers to // navigate all the (leaf) data edges in a CordRepBtree instance. // // A CordRepBtreeNavigator instance is by default empty. Callers initialize a // navigator instance by calling one of `InitFirst()`, `InitLast()` or // `InitOffset()`, which establishes a current position. Callers can then // navigate using the `Next`, `Previous`, `Skip` and `Seek` methods. // // The navigator instance does not take or adopt a reference on the provided // `tree` on any of the initialization calls. Callers are responsible for // guaranteeing the lifecycle of the provided tree. A navigator instance can // be reset to the empty state by calling `Reset`. // // A navigator only keeps positional state on the 'current data edge', it does // explicitly not keep any 'offset' state. The class does accept and return // offsets in the `Read()`, `Skip()` and 'Seek()` methods as these would // otherwise put a big burden on callers. Callers are expected to maintain // (returned) offset info if they require such granular state. class CordRepBtreeNavigator { public: // The logical position as returned by the Seek() and Skip() functions. // Returns the current leaf edge for the desired seek or skip position and // the offset of that position inside that edge. struct Position { CordRep* edge; size_t offset; }; // The read result as returned by the Read() function. // `tree` contains the resulting tree which is identical to the result // of calling CordRepBtree::SubTree(...) on the tree being navigated. // `n` contains the number of bytes used from the last navigated to // edge of the tree. struct ReadResult { CordRep* tree; size_t n; }; // Returns true if this instance is not empty. explicit operator bool() const; // Returns the tree for this instance or nullptr if empty. CordRepBtree* btree() const; // Returns the data edge of the current position. // Requires this instance to not be empty. CordRep* Current() const; // Resets this navigator to `tree`, returning the first data edge in the tree. CordRep* InitFirst(CordRepBtree* tree); // Resets this navigator to `tree`, returning the last data edge in the tree. CordRep* InitLast(CordRepBtree* tree); // Resets this navigator to `tree` returning the data edge at position // `offset` and the relative offset of `offset` into that data edge. // Returns `Position.edge = nullptr` if the provided offset is greater // than or equal to the length of the tree, in which case the state of // the navigator instance remains unchanged. Position InitOffset(CordRepBtree* tree, size_t offset); // Navigates to the next data edge. // Returns the next data edge or nullptr if there is no next data edge, in // which case the current position remains unchanged. CordRep* Next(); // Navigates to the previous data edge. // Returns the previous data edge or nullptr if there is no previous data // edge, in which case the current position remains unchanged. CordRep* Previous(); // Navigates to the data edge at position `offset`. Returns the navigated to // data edge in `Position.edge` and the relative offset of `offset` into that // data edge in `Position.offset`. Returns `Position.edge = nullptr` if the // provide offset is greater than or equal to the tree's length. Position Seek(size_t offset); // Reads `n` bytes of data starting at offset `edge_offset` of the current // data edge, and returns the result in `ReadResult.tree`. `ReadResult.n` // contains the 'bytes used` from the last / current data edge in the tree. // This allows users that mix regular navigation (using string views) and // 'read into cord' navigation to keep track of the current state, and which // bytes have been consumed from a navigator. // This function returns `ReadResult.tree = nullptr` if the requested length // exceeds the length of the tree starting at the current data edge. ReadResult Read(size_t edge_offset, size_t n); // Skips `n` bytes forward from the current data edge, returning the navigated // to data edge in `Position.edge` and `Position.offset` containing the offset // inside that data edge. Note that the state of the navigator is left // unchanged if `n` is smaller than the length of the current data edge. Position Skip(size_t n); // Resets this instance to the default / empty state. void Reset(); private: // Slow path for Next() if Next() reached the end of a leaf node. Backtracks // up the stack until it finds a node that has a 'next' position available, // and then does a 'front dive' towards the next leaf node. CordRep* NextUp(); // Slow path for Previous() if Previous() reached the beginning of a leaf // node. Backtracks up the stack until it finds a node that has a 'previous' // position available, and then does a 'back dive' towards the previous leaf // node. CordRep* PreviousUp(); // Generic implementation of InitFirst() and InitLast(). template CordRep* Init(CordRepBtree* tree); // `height_` contains the height of the current tree, or -1 if empty. int height_ = -1; // `index_` and `node_` contain the navigation state as the 'path' to the // current data edge which is at `node_[0]->Edge(index_[0])`. The contents // of these are undefined until the instance is initialized (`height_ >= 0`). uint8_t index_[CordRepBtree::kMaxDepth]; CordRepBtree* node_[CordRepBtree::kMaxDepth]; }; // Returns true if this instance is not empty. inline CordRepBtreeNavigator::operator bool() const { return height_ >= 0; } inline CordRepBtree* CordRepBtreeNavigator::btree() const { return height_ >= 0 ? node_[height_] : nullptr; } inline CordRep* CordRepBtreeNavigator::Current() const { assert(height_ >= 0); return node_[0]->Edge(index_[0]); } inline void CordRepBtreeNavigator::Reset() { height_ = -1; } inline CordRep* CordRepBtreeNavigator::InitFirst(CordRepBtree* tree) { return Init(tree); } inline CordRep* CordRepBtreeNavigator::InitLast(CordRepBtree* tree) { return Init(tree); } template inline CordRep* CordRepBtreeNavigator::Init(CordRepBtree* tree) { assert(tree != nullptr); assert(tree->size() > 0); assert(tree->height() <= CordRepBtree::kMaxHeight); int height = height_ = tree->height(); size_t index = tree->index(edge_type); node_[height] = tree; index_[height] = static_cast(index); while (--height >= 0) { tree = tree->Edge(index)->btree(); node_[height] = tree; index = tree->index(edge_type); index_[height] = static_cast(index); } return node_[0]->Edge(index); } inline CordRepBtreeNavigator::Position CordRepBtreeNavigator::Seek( size_t offset) { assert(btree() != nullptr); int height = height_; CordRepBtree* edge = node_[height]; if (ABSL_PREDICT_FALSE(offset >= edge->length)) return {nullptr, 0}; CordRepBtree::Position index = edge->IndexOf(offset); index_[height] = static_cast(index.index); while (--height >= 0) { edge = edge->Edge(index.index)->btree(); node_[height] = edge; index = edge->IndexOf(index.n); index_[height] = static_cast(index.index); } return {edge->Edge(index.index), index.n}; } inline CordRepBtreeNavigator::Position CordRepBtreeNavigator::InitOffset( CordRepBtree* tree, size_t offset) { assert(tree != nullptr); assert(tree->height() <= CordRepBtree::kMaxHeight); if (ABSL_PREDICT_FALSE(offset >= tree->length)) return {nullptr, 0}; height_ = tree->height(); node_[height_] = tree; return Seek(offset); } inline CordRep* CordRepBtreeNavigator::Next() { CordRepBtree* edge = node_[0]; return index_[0] == edge->back() ? NextUp() : edge->Edge(++index_[0]); } inline CordRep* CordRepBtreeNavigator::Previous() { CordRepBtree* edge = node_[0]; return index_[0] == edge->begin() ? PreviousUp() : edge->Edge(--index_[0]); } inline CordRep* CordRepBtreeNavigator::NextUp() { assert(index_[0] == node_[0]->back()); CordRepBtree* edge; size_t index; int height = 0; do { if (++height > height_) return nullptr; edge = node_[height]; index = index_[height] + 1; } while (index == edge->end()); index_[height] = static_cast(index); do { node_[--height] = edge = edge->Edge(index)->btree(); index_[height] = static_cast(index = edge->begin()); } while (height > 0); return edge->Edge(index); } inline CordRep* CordRepBtreeNavigator::PreviousUp() { assert(index_[0] == node_[0]->begin()); CordRepBtree* edge; size_t index; int height = 0; do { if (++height > height_) return nullptr; edge = node_[height]; index = index_[height]; } while (index == edge->begin()); index_[height] = static_cast(--index); do { node_[--height] = edge = edge->Edge(index)->btree(); index_[height] = static_cast(index = edge->back()); } while (height > 0); return edge->Edge(index); } } // namespace cord_internal ABSL_NAMESPACE_END } // namespace absl #endif // ABSL_STRINGS_INTERNAL_CORD_REP_BTREE_NAVIGATOR_H_