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