| // 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 <cassert> |
| #include <iostream> |
| |
| #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 <CordRepBtree::EdgeType edge_type> |
| 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<CordRepBtree::kFront>(tree); |
| } |
| |
| inline CordRep* CordRepBtreeNavigator::InitLast(CordRepBtree* tree) { |
| return Init<CordRepBtree::kBack>(tree); |
| } |
| |
| template <CordRepBtree::EdgeType edge_type> |
| 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<uint8_t>(index); |
| while (--height >= 0) { |
| tree = tree->Edge(index)->btree(); |
| node_[height] = tree; |
| index = tree->index(edge_type); |
| index_[height] = static_cast<uint8_t>(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<uint8_t>(index.index); |
| while (--height >= 0) { |
| edge = edge->Edge(index.index)->btree(); |
| node_[height] = edge; |
| index = edge->IndexOf(index.n); |
| index_[height] = static_cast<uint8_t>(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<uint8_t>(index); |
| do { |
| node_[--height] = edge = edge->Edge(index)->btree(); |
| index_[height] = static_cast<uint8_t>(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<uint8_t>(--index); |
| do { |
| node_[--height] = edge = edge->Edge(index)->btree(); |
| index_[height] = static_cast<uint8_t>(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_ |