• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 The Abseil Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #ifndef ABSL_STRINGS_INTERNAL_CORD_REP_BTREE_NAVIGATOR_H_
16 #define ABSL_STRINGS_INTERNAL_CORD_REP_BTREE_NAVIGATOR_H_
17 
18 #include <cassert>
19 #include <iostream>
20 
21 #include "absl/strings/internal/cord_internal.h"
22 #include "absl/strings/internal/cord_rep_btree.h"
23 
24 namespace absl {
25 ABSL_NAMESPACE_BEGIN
26 namespace cord_internal {
27 
28 // CordRepBtreeNavigator is a bi-directional navigator allowing callers to
29 // navigate all the (leaf) data edges in a CordRepBtree instance.
30 //
31 // A CordRepBtreeNavigator instance is by default empty. Callers initialize a
32 // navigator instance by calling one of `InitFirst()`, `InitLast()` or
33 // `InitOffset()`, which establishes a current position. Callers can then
34 // navigate using the `Next`, `Previous`, `Skip` and `Seek` methods.
35 //
36 // The navigator instance does not take or adopt a reference on the provided
37 // `tree` on any of the initialization calls. Callers are responsible for
38 // guaranteeing the lifecycle of the provided tree. A navigator instance can
39 // be reset to the empty state by calling `Reset`.
40 //
41 // A navigator only keeps positional state on the 'current data edge', it does
42 // explicitly not keep any 'offset' state. The class does accept and return
43 // offsets in the `Read()`, `Skip()` and 'Seek()` methods as these would
44 // otherwise put a big burden on callers. Callers are expected to maintain
45 // (returned) offset info if they require such granular state.
46 class CordRepBtreeNavigator {
47  public:
48   // The logical position as returned by the Seek() and Skip() functions.
49   // Returns the current leaf edge for the desired seek or skip position and
50   // the offset of that position inside that edge.
51   struct Position {
52     CordRep* edge;
53     size_t offset;
54   };
55 
56   // The read result as returned by the Read() function.
57   // `tree` contains the resulting tree which is identical to the result
58   // of calling CordRepBtree::SubTree(...) on the tree being navigated.
59   // `n` contains the number of bytes used from the last navigated to
60   // edge of the tree.
61   struct ReadResult {
62     CordRep* tree;
63     size_t n;
64   };
65 
66   // Returns true if this instance is not empty.
67   explicit operator bool() const;
68 
69   // Returns the tree for this instance or nullptr if empty.
70   CordRepBtree* btree() const;
71 
72   // Returns the data edge of the current position.
73   // Requires this instance to not be empty.
74   CordRep* Current() const;
75 
76   // Resets this navigator to `tree`, returning the first data edge in the tree.
77   CordRep* InitFirst(CordRepBtree* tree);
78 
79   // Resets this navigator to `tree`, returning the last data edge in the tree.
80   CordRep* InitLast(CordRepBtree* tree);
81 
82   // Resets this navigator to `tree` returning the data edge at position
83   // `offset` and the relative offset of `offset` into that data edge.
84   // Returns `Position.edge = nullptr` if the provided offset is greater
85   // than or equal to the length of the tree, in which case the state of
86   // the navigator instance remains unchanged.
87   Position InitOffset(CordRepBtree* tree, size_t offset);
88 
89   // Navigates to the next data edge.
90   // Returns the next data edge or nullptr if there is no next data edge, in
91   // which case the current position remains unchanged.
92   CordRep* Next();
93 
94   // Navigates to the previous data edge.
95   // Returns the previous data edge or nullptr if there is no previous data
96   // edge, in which case the current position remains unchanged.
97   CordRep* Previous();
98 
99   // Navigates to the data edge at position `offset`. Returns the navigated to
100   // data edge in `Position.edge` and the relative offset of `offset` into that
101   // data edge in `Position.offset`. Returns `Position.edge = nullptr` if the
102   // provide offset is greater than or equal to the tree's length.
103   Position Seek(size_t offset);
104 
105   // Reads `n` bytes of data starting at offset `edge_offset` of the current
106   // data edge, and returns the result in `ReadResult.tree`. `ReadResult.n`
107   // contains the 'bytes used` from the last / current data edge in the tree.
108   // This allows users that mix regular navigation (using string views) and
109   // 'read into cord' navigation to keep track of the current state, and which
110   // bytes have been consumed from a navigator.
111   // This function returns `ReadResult.tree = nullptr` if the requested length
112   // exceeds the length of the tree starting at the current data edge.
113   ReadResult Read(size_t edge_offset, size_t n);
114 
115   // Skips `n` bytes forward from the current data edge, returning the navigated
116   // to data edge in `Position.edge` and `Position.offset` containing the offset
117   // inside that data edge. Note that the state of the navigator is left
118   // unchanged if `n` is smaller than the length of the current data edge.
119   Position Skip(size_t n);
120 
121   // Resets this instance to the default / empty state.
122   void Reset();
123 
124  private:
125   // Slow path for Next() if Next() reached the end of a leaf node. Backtracks
126   // up the stack until it finds a node that has a 'next' position available,
127   // and then does a 'front dive' towards the next leaf node.
128   CordRep* NextUp();
129 
130   // Slow path for Previous() if Previous() reached the beginning of a leaf
131   // node. Backtracks up the stack until it finds a node that has a 'previous'
132   // position available, and then does a 'back dive' towards the previous leaf
133   // node.
134   CordRep* PreviousUp();
135 
136   // Generic implementation of InitFirst() and InitLast().
137   template <CordRepBtree::EdgeType edge_type>
138   CordRep* Init(CordRepBtree* tree);
139 
140   // `height_` contains the height of the current tree, or -1 if empty.
141   int height_ = -1;
142 
143   // `index_` and `node_` contain the navigation state as the 'path' to the
144   // current data edge which is at `node_[0]->Edge(index_[0])`. The contents
145   // of these are undefined until the instance is initialized (`height_ >= 0`).
146   uint8_t index_[CordRepBtree::kMaxHeight];
147   CordRepBtree* node_[CordRepBtree::kMaxHeight];
148 };
149 
150 // Returns true if this instance is not empty.
151 inline CordRepBtreeNavigator::operator bool() const { return height_ >= 0; }
152 
btree()153 inline CordRepBtree* CordRepBtreeNavigator::btree() const {
154   return height_ >= 0 ? node_[height_] : nullptr;
155 }
156 
Current()157 inline CordRep* CordRepBtreeNavigator::Current() const {
158   assert(height_ >= 0);
159   return node_[0]->Edge(index_[0]);
160 }
161 
Reset()162 inline void CordRepBtreeNavigator::Reset() { height_ = -1; }
163 
InitFirst(CordRepBtree * tree)164 inline CordRep* CordRepBtreeNavigator::InitFirst(CordRepBtree* tree) {
165   return Init<CordRepBtree::kFront>(tree);
166 }
167 
InitLast(CordRepBtree * tree)168 inline CordRep* CordRepBtreeNavigator::InitLast(CordRepBtree* tree) {
169   return Init<CordRepBtree::kBack>(tree);
170 }
171 
172 template <CordRepBtree::EdgeType edge_type>
Init(CordRepBtree * tree)173 inline CordRep* CordRepBtreeNavigator::Init(CordRepBtree* tree) {
174   assert(tree != nullptr);
175   assert(tree->size() > 0);
176   int height = height_ = tree->height();
177   size_t index = tree->index(edge_type);
178   node_[height] = tree;
179   index_[height] = static_cast<uint8_t>(index);
180   while (--height >= 0) {
181     tree = tree->Edge(index)->btree();
182     node_[height] = tree;
183     index = tree->index(edge_type);
184     index_[height] = static_cast<uint8_t>(index);
185   }
186   return node_[0]->Edge(index);
187 }
188 
Seek(size_t offset)189 inline CordRepBtreeNavigator::Position CordRepBtreeNavigator::Seek(
190     size_t offset) {
191   assert(btree() != nullptr);
192   int height = height_;
193   CordRepBtree* edge = node_[height];
194   if (ABSL_PREDICT_FALSE(offset >= edge->length)) return {nullptr, 0};
195   CordRepBtree::Position index = edge->IndexOf(offset);
196   index_[height] = static_cast<uint8_t>(index.index);
197   while (--height >= 0) {
198     edge = edge->Edge(index.index)->btree();
199     node_[height] = edge;
200     index = edge->IndexOf(index.n);
201     index_[height] = static_cast<uint8_t>(index.index);
202   }
203   return {edge->Edge(index.index), index.n};
204 }
205 
InitOffset(CordRepBtree * tree,size_t offset)206 inline CordRepBtreeNavigator::Position CordRepBtreeNavigator::InitOffset(
207     CordRepBtree* tree, size_t offset) {
208   assert(tree != nullptr);
209   if (ABSL_PREDICT_FALSE(offset >= tree->length)) return {nullptr, 0};
210   height_ = tree->height();
211   node_[height_] = tree;
212   return Seek(offset);
213 }
214 
Next()215 inline CordRep* CordRepBtreeNavigator::Next() {
216   CordRepBtree* edge = node_[0];
217   return index_[0] == edge->back() ? NextUp() : edge->Edge(++index_[0]);
218 }
219 
Previous()220 inline CordRep* CordRepBtreeNavigator::Previous() {
221   CordRepBtree* edge = node_[0];
222   return index_[0] == edge->begin() ? PreviousUp() : edge->Edge(--index_[0]);
223 }
224 
NextUp()225 inline CordRep* CordRepBtreeNavigator::NextUp() {
226   assert(index_[0] == node_[0]->back());
227   CordRepBtree* edge;
228   size_t index;
229   int height = 0;
230   do {
231     if (++height > height_) return nullptr;
232     edge = node_[height];
233     index = index_[height] + 1;
234   } while (index == edge->end());
235   index_[height] = static_cast<uint8_t>(index);
236   do {
237     node_[--height] = edge = edge->Edge(index)->btree();
238     index_[height] = static_cast<uint8_t>(index = edge->begin());
239   } while (height > 0);
240   return edge->Edge(index);
241 }
242 
PreviousUp()243 inline CordRep* CordRepBtreeNavigator::PreviousUp() {
244   assert(index_[0] == node_[0]->begin());
245   CordRepBtree* edge;
246   size_t index;
247   int height = 0;
248   do {
249     if (++height > height_) return nullptr;
250     edge = node_[height];
251     index = index_[height];
252   } while (index == edge->begin());
253   index_[height] = static_cast<uint8_t>(--index);
254   do {
255     node_[--height] = edge = edge->Edge(index)->btree();
256     index_[height] = static_cast<uint8_t>(index = edge->back());
257   } while (height > 0);
258   return edge->Edge(index);
259 }
260 
261 }  // namespace cord_internal
262 ABSL_NAMESPACE_END
263 }  // namespace absl
264 
265 #endif  // ABSL_STRINGS_INTERNAL_CORD_REP_BTREE_NAVIGATOR_H_
266