// 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. #include "absl/strings/internal/cord_rep_btree_navigator.h" #include #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_rep_btree.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace cord_internal { using ReadResult = CordRepBtreeNavigator::ReadResult; namespace { // Returns a `CordRepSubstring` from `rep` starting at `offset` of size `n`. // If `rep` is already a `CordRepSubstring` instance, an adjusted instance is // created based on the old offset and new offset. // Adopts a reference on `rep`. Rep must be a valid data edge. Returns // nullptr if `n == 0`, `rep` if `n == rep->length`. // Requires `offset < rep->length` and `offset + n <= rep->length`. // TODO(192061034): move to utility library in internal and optimize for small // substrings of larger reps. inline CordRep* Substring(CordRep* rep, size_t offset, size_t n) { assert(n <= rep->length); assert(offset < rep->length); assert(offset <= rep->length - n); assert(CordRepBtree::IsDataEdge(rep)); if (n == 0) return nullptr; if (n == rep->length) return CordRep::Ref(rep); if (rep->tag == SUBSTRING) { offset += rep->substring()->start; rep = rep->substring()->child; } CordRepSubstring* substring = new CordRepSubstring(); substring->length = n; substring->tag = SUBSTRING; substring->start = offset; substring->child = CordRep::Ref(rep); return substring; } inline CordRep* Substring(CordRep* rep, size_t offset) { return Substring(rep, offset, rep->length - offset); } } // namespace CordRepBtreeNavigator::Position CordRepBtreeNavigator::Skip(size_t n) { int height = 0; size_t index = index_[0]; CordRepBtree* node = node_[0]; CordRep* edge = node->Edge(index); // Overall logic: Find an edge of at least the length we need to skip. // We consume all edges which are smaller (i.e., must be 100% skipped). // If we exhausted all edges on the current level, we move one level // up the tree, and repeat until we either find the edge, or until we hit // the top of the tree meaning the skip exceeds tree->length. while (n >= edge->length) { n -= edge->length; while (++index == node->end()) { if (++height > height_) return {nullptr, n}; node = node_[height]; index = index_[height]; } edge = node->Edge(index); } // If we moved up the tree, descend down to the leaf level, consuming all // edges that must be skipped. while (height > 0) { node = edge->btree(); index_[height] = index; node_[--height] = node; index = node->begin(); edge = node->Edge(index); while (n >= edge->length) { n -= edge->length; ++index; assert(index != node->end()); edge = node->Edge(index); } } index_[0] = index; return {edge, n}; } ReadResult CordRepBtreeNavigator::Read(size_t edge_offset, size_t n) { int height = 0; size_t length = edge_offset + n; size_t index = index_[0]; CordRepBtree* node = node_[0]; CordRep* edge = node->Edge(index); assert(edge_offset < edge->length); if (length < edge->length) { return {Substring(edge, edge_offset, n), length}; } // Similar to 'Skip', we consume all edges that are inside the 'length' of // data that needs to be read. If we exhaust the current level, we move one // level up the tree and repeat until we hit the final edge that must be // (partially) read. We consume all edges into `subtree`. CordRepBtree* subtree = CordRepBtree::New(Substring(edge, edge_offset)); size_t subtree_end = 1; do { length -= edge->length; while (++index == node->end()) { index_[height] = index; if (++height > height_) { subtree->set_end(subtree_end); if (length == 0) return {subtree, 0}; CordRep::Unref(subtree); return {nullptr, length}; } if (length != 0) { subtree->set_end(subtree_end); subtree = CordRepBtree::New(subtree); subtree_end = 1; } node = node_[height]; index = index_[height]; } edge = node->Edge(index); if (length >= edge->length) { subtree->length += edge->length; subtree->edges_[subtree_end++] = CordRep::Ref(edge); } } while (length >= edge->length); CordRepBtree* tree = subtree; subtree->length += length; // If we moved up the tree, descend down to the leaf level, consuming all // edges that must be read, adding 'down' nodes to `subtree`. while (height > 0) { node = edge->btree(); index_[height] = index; node_[--height] = node; index = node->begin(); edge = node->Edge(index); if (length != 0) { CordRepBtree* right = CordRepBtree::New(height); right->length = length; subtree->edges_[subtree_end++] = right; subtree->set_end(subtree_end); subtree = right; subtree_end = 0; while (length >= edge->length) { subtree->edges_[subtree_end++] = CordRep::Ref(edge); length -= edge->length; edge = node->Edge(++index); } } } // Add any (partial) edge still remaining at the leaf level. if (length != 0) { subtree->edges_[subtree_end++] = Substring(edge, 0, length); } subtree->set_end(subtree_end); index_[0] = index; return {tree, length}; } } // namespace cord_internal ABSL_NAMESPACE_END } // namespace absl