1 // Scintilla source code edit control 2 /** @file SplitVector.h 3 ** Main data structure for holding arrays that handle insertions 4 ** and deletions efficiently. 5 **/ 6 // Copyright 1998-2007 by Neil Hodgson <[email protected]> 7 // The License.txt file describes the conditions under which this software may be distributed. 8 9 #ifndef SPLITVECTOR_H 10 #define SPLITVECTOR_H 11 12 namespace Scintilla { 13 14 template <typename T> 15 class SplitVector { 16 protected: 17 std::vector<T> body; 18 T empty; /// Returned as the result of out-of-bounds access. 19 ptrdiff_t lengthBody; 20 ptrdiff_t part1Length; 21 ptrdiff_t gapLength; /// invariant: gapLength == body.size() - lengthBody 22 ptrdiff_t growSize; 23 24 /// Move the gap to a particular position so that insertion and 25 /// deletion at that point will not require much copying and 26 /// hence be fast. GapTo(ptrdiff_t position)27 void GapTo(ptrdiff_t position) noexcept { 28 if (position != part1Length) { 29 if (position < part1Length) { 30 // Moving the gap towards start so moving elements towards end 31 std::move_backward( 32 body.data() + position, 33 body.data() + part1Length, 34 body.data() + gapLength + part1Length); 35 } else { // position > part1Length 36 // Moving the gap towards end so moving elements towards start 37 std::move( 38 body.data() + part1Length + gapLength, 39 body.data() + gapLength + position, 40 body.data() + part1Length); 41 } 42 part1Length = position; 43 } 44 } 45 46 /// Check that there is room in the buffer for an insertion, 47 /// reallocating if more space needed. RoomFor(ptrdiff_t insertionLength)48 void RoomFor(ptrdiff_t insertionLength) { 49 if (gapLength <= insertionLength) { 50 while (growSize < static_cast<ptrdiff_t>(body.size() / 6)) 51 growSize *= 2; 52 ReAllocate(body.size() + insertionLength + growSize); 53 } 54 } 55 Init()56 void Init() { 57 body.clear(); 58 body.shrink_to_fit(); 59 lengthBody = 0; 60 part1Length = 0; 61 gapLength = 0; 62 growSize = 8; 63 } 64 65 public: 66 /// Construct a split buffer. SplitVector()67 SplitVector() : empty(), lengthBody(0), part1Length(0), gapLength(0), growSize(8) { 68 } 69 70 // Deleted so SplitVector objects can not be copied. 71 SplitVector(const SplitVector &) = delete; 72 SplitVector(SplitVector &&) = delete; 73 void operator=(const SplitVector &) = delete; 74 void operator=(SplitVector &&) = delete; 75 ~SplitVector()76 ~SplitVector() { 77 } 78 GetGrowSize()79 ptrdiff_t GetGrowSize() const noexcept { 80 return growSize; 81 } 82 SetGrowSize(ptrdiff_t growSize_)83 void SetGrowSize(ptrdiff_t growSize_) noexcept { 84 growSize = growSize_; 85 } 86 87 /// Reallocate the storage for the buffer to be newSize and 88 /// copy existing contents to the new buffer. 89 /// Must not be used to decrease the size of the buffer. ReAllocate(ptrdiff_t newSize)90 void ReAllocate(ptrdiff_t newSize) { 91 if (newSize < 0) 92 throw std::runtime_error("SplitVector::ReAllocate: negative size."); 93 94 if (newSize > static_cast<ptrdiff_t>(body.size())) { 95 // Move the gap to the end 96 GapTo(lengthBody); 97 gapLength += newSize - static_cast<ptrdiff_t>(body.size()); 98 // RoomFor implements a growth strategy but so does vector::resize so 99 // ensure vector::resize allocates exactly the amount wanted by 100 // calling reserve first. 101 body.reserve(newSize); 102 body.resize(newSize); 103 } 104 } 105 106 /// Retrieve the element at a particular position. 107 /// Retrieving positions outside the range of the buffer returns empty or 0. ValueAt(ptrdiff_t position)108 const T& ValueAt(ptrdiff_t position) const noexcept { 109 if (position < part1Length) { 110 if (position < 0) { 111 return empty; 112 } else { 113 return body[position]; 114 } 115 } else { 116 if (position >= lengthBody) { 117 return empty; 118 } else { 119 return body[gapLength + position]; 120 } 121 } 122 } 123 124 /// Set the element at a particular position. 125 /// Setting positions outside the range of the buffer performs no assignment 126 /// but asserts in debug builds. 127 template <typename ParamType> SetValueAt(ptrdiff_t position,ParamType && v)128 void SetValueAt(ptrdiff_t position, ParamType&& v) noexcept { 129 if (position < part1Length) { 130 PLATFORM_ASSERT(position >= 0); 131 if (position < 0) { 132 ; 133 } else { 134 body[position] = std::forward<ParamType>(v); 135 } 136 } else { 137 PLATFORM_ASSERT(position < lengthBody); 138 if (position >= lengthBody) { 139 ; 140 } else { 141 body[gapLength + position] = std::forward<ParamType>(v); 142 } 143 } 144 } 145 146 /// Retrieve the element at a particular position. 147 /// The position must be within bounds or an assertion is triggered. 148 const T &operator[](ptrdiff_t position) const noexcept { 149 PLATFORM_ASSERT(position >= 0 && position < lengthBody); 150 if (position < part1Length) { 151 return body[position]; 152 } else { 153 return body[gapLength + position]; 154 } 155 } 156 157 /// Retrieve reference to the element at a particular position. 158 /// This, instead of the const variant, can be used to mutate in-place. 159 /// The position must be within bounds or an assertion is triggered. 160 T &operator[](ptrdiff_t position) noexcept { 161 PLATFORM_ASSERT(position >= 0 && position < lengthBody); 162 if (position < part1Length) { 163 return body[position]; 164 } else { 165 return body[gapLength + position]; 166 } 167 } 168 169 /// Retrieve the length of the buffer. Length()170 ptrdiff_t Length() const noexcept { 171 return lengthBody; 172 } 173 174 /// Insert a single value into the buffer. 175 /// Inserting at positions outside the current range fails. Insert(ptrdiff_t position,T v)176 void Insert(ptrdiff_t position, T v) { 177 PLATFORM_ASSERT((position >= 0) && (position <= lengthBody)); 178 if ((position < 0) || (position > lengthBody)) { 179 return; 180 } 181 RoomFor(1); 182 GapTo(position); 183 body[part1Length] = std::move(v); 184 lengthBody++; 185 part1Length++; 186 gapLength--; 187 } 188 189 /// Insert a number of elements into the buffer setting their value. 190 /// Inserting at positions outside the current range fails. InsertValue(ptrdiff_t position,ptrdiff_t insertLength,T v)191 void InsertValue(ptrdiff_t position, ptrdiff_t insertLength, T v) { 192 PLATFORM_ASSERT((position >= 0) && (position <= lengthBody)); 193 if (insertLength > 0) { 194 if ((position < 0) || (position > lengthBody)) { 195 return; 196 } 197 RoomFor(insertLength); 198 GapTo(position); 199 std::fill(body.data() + part1Length, body.data() + part1Length + insertLength, v); 200 lengthBody += insertLength; 201 part1Length += insertLength; 202 gapLength -= insertLength; 203 } 204 } 205 206 /// Add some new empty elements. 207 /// InsertValue is good for value objects but not for unique_ptr objects 208 /// since they can only be moved from once. 209 /// Callers can write to the returned pointer to transform inputs without copies. InsertEmpty(ptrdiff_t position,ptrdiff_t insertLength)210 T *InsertEmpty(ptrdiff_t position, ptrdiff_t insertLength) { 211 PLATFORM_ASSERT((position >= 0) && (position <= lengthBody)); 212 if (insertLength > 0) { 213 if ((position < 0) || (position > lengthBody)) { 214 return nullptr; 215 } 216 RoomFor(insertLength); 217 GapTo(position); 218 for (ptrdiff_t elem = part1Length; elem < part1Length + insertLength; elem++) { 219 T emptyOne = {}; 220 body[elem] = std::move(emptyOne); 221 } 222 lengthBody += insertLength; 223 part1Length += insertLength; 224 gapLength -= insertLength; 225 } 226 return body.data() + position; 227 } 228 229 /// Ensure at least length elements allocated, 230 /// appending zero valued elements if needed. EnsureLength(ptrdiff_t wantedLength)231 void EnsureLength(ptrdiff_t wantedLength) { 232 if (Length() < wantedLength) { 233 InsertEmpty(Length(), wantedLength - Length()); 234 } 235 } 236 237 /// Insert text into the buffer from an array. InsertFromArray(ptrdiff_t positionToInsert,const T s[],ptrdiff_t positionFrom,ptrdiff_t insertLength)238 void InsertFromArray(ptrdiff_t positionToInsert, const T s[], ptrdiff_t positionFrom, ptrdiff_t insertLength) { 239 PLATFORM_ASSERT((positionToInsert >= 0) && (positionToInsert <= lengthBody)); 240 if (insertLength > 0) { 241 if ((positionToInsert < 0) || (positionToInsert > lengthBody)) { 242 return; 243 } 244 RoomFor(insertLength); 245 GapTo(positionToInsert); 246 std::copy(s + positionFrom, s + positionFrom + insertLength, body.data() + part1Length); 247 lengthBody += insertLength; 248 part1Length += insertLength; 249 gapLength -= insertLength; 250 } 251 } 252 253 /// Delete one element from the buffer. Delete(ptrdiff_t position)254 void Delete(ptrdiff_t position) { 255 PLATFORM_ASSERT((position >= 0) && (position < lengthBody)); 256 DeleteRange(position, 1); 257 } 258 259 /// Delete a range from the buffer. 260 /// Deleting positions outside the current range fails. 261 /// Cannot be noexcept as vector::shrink_to_fit may be called and it may throw. DeleteRange(ptrdiff_t position,ptrdiff_t deleteLength)262 void DeleteRange(ptrdiff_t position, ptrdiff_t deleteLength) { 263 PLATFORM_ASSERT((position >= 0) && (position + deleteLength <= lengthBody)); 264 if ((position < 0) || ((position + deleteLength) > lengthBody)) { 265 return; 266 } 267 if ((position == 0) && (deleteLength == lengthBody)) { 268 // Full deallocation returns storage and is faster 269 Init(); 270 } else if (deleteLength > 0) { 271 GapTo(position); 272 lengthBody -= deleteLength; 273 gapLength += deleteLength; 274 } 275 } 276 277 /// Delete all the buffer contents. DeleteAll()278 void DeleteAll() { 279 DeleteRange(0, lengthBody); 280 } 281 282 /// Retrieve a range of elements into an array GetRange(T * buffer,ptrdiff_t position,ptrdiff_t retrieveLength)283 void GetRange(T *buffer, ptrdiff_t position, ptrdiff_t retrieveLength) const noexcept { 284 // Split into up to 2 ranges, before and after the split then use memcpy on each. 285 ptrdiff_t range1Length = 0; 286 if (position < part1Length) { 287 const ptrdiff_t part1AfterPosition = part1Length - position; 288 range1Length = retrieveLength; 289 if (range1Length > part1AfterPosition) 290 range1Length = part1AfterPosition; 291 } 292 std::copy(body.data() + position, body.data() + position + range1Length, buffer); 293 buffer += range1Length; 294 position = position + range1Length + gapLength; 295 const ptrdiff_t range2Length = retrieveLength - range1Length; 296 std::copy(body.data() + position, body.data() + position + range2Length, buffer); 297 } 298 299 /// Compact the buffer and return a pointer to the first element. 300 /// Also ensures there is an empty element beyond logical end in case its 301 /// passed to a function expecting a NUL terminated string. BufferPointer()302 T *BufferPointer() { 303 RoomFor(1); 304 GapTo(lengthBody); 305 T emptyOne = {}; 306 body[lengthBody] = std::move(emptyOne); 307 return body.data(); 308 } 309 310 /// Return a pointer to a range of elements, first rearranging the buffer if 311 /// needed to make that range contiguous. RangePointer(ptrdiff_t position,ptrdiff_t rangeLength)312 T *RangePointer(ptrdiff_t position, ptrdiff_t rangeLength) noexcept { 313 if (position < part1Length) { 314 if ((position + rangeLength) > part1Length) { 315 // Range overlaps gap, so move gap to start of range. 316 GapTo(position); 317 return body.data() + position + gapLength; 318 } else { 319 return body.data() + position; 320 } 321 } else { 322 return body.data() + position + gapLength; 323 } 324 } 325 326 /// Return the position of the gap within the buffer. GapPosition()327 ptrdiff_t GapPosition() const noexcept { 328 return part1Length; 329 } 330 }; 331 332 } 333 334 #endif 335