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 <neilh@scintilla.org>
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