1 /**
2 License: public domain
3 Authors: Simon Bürger
4 */
5 
6 module jive.array;
7 
8 import jive.internal;
9 import core.exception : RangeError;
10 import core.stdc.string : memmove, memcpy, memset;
11 import std.algorithm;
12 import std.conv : emplace;
13 import std.format;
14 import std.range;
15 import std.traits;
16 
17 
18 /**
19  *  Array of dynamic size.
20  *
21  *  If you add elements, new memory will be allocated automatically as needed.
22  *  Typically there is more memory allocated than is currently in use. There is
23  *  a tradeoff between wasted space and frequency of reallocations. The default
24  *  behaviour is to double the capacity every time the allocated memory is
25  *  filled up. This ensures that pushBack takes O(1) in amortized time. If you
26  *  know the number of elements in advance, you can use reserve to avoid
27  *  reallocations, but this is just an optimization and never necessary.
28  */
29 struct Array(V)
30 {
31 	private V* _ptr = null;			// unused elements are undefined
32 	private size_t _capacity = 0;	// size of buf
33 	private size_t _length = 0;		// used size
34 
35 	/** constructor for given length */
36 	this(size_t size)
37 	{
38 		resize(size);
39 	}
40 
41 	/** constructor for given length and init */
42 	this(size_t size, V val)
43 	{
44 		resize(size, val);
45 	}
46 
47 	/** constructor that gets content from arbitrary range */
48 	this(Stuff)(Stuff data)
49 		if(isInputRange!Stuff && is(ElementType!Stuff:V))
50 	{
51 		static if(hasLength!Stuff)
52 			reserve(_length + data.length);
53 
54 		foreach(ref x; data)
55 			pushBack(x);
56 	}
57 
58 	/** post-blit that does a full copy */
59 	this(this)
60 	{
61 		auto newPtr = jiveMalloc!V(_length);
62 
63 		static if(hasElaborateCopyConstructor!V)
64 		{
65 			for(size_t i = 0; i < _length; ++i)
66 				emplace(newPtr + i, _ptr[i]);
67 		}
68 		else
69 			memcpy(newPtr, _ptr, V.sizeof * _length);
70 		_ptr = newPtr;
71 		_capacity = _length;
72 	}
73 
74 	/** destructor */
75 	~this()
76 	{
77 		static if (hasElaborateDestructor!V)
78 			foreach (ref x; this[])
79 				destroy(x);
80 		jiveFree(_ptr);
81 		_ptr = null; // probably not necessary, just a precaution
82 	}
83 
84 	/** check for emptiness */
85 	bool empty() const pure nothrow @property @safe
86 	{
87 		return _length == 0;
88 	}
89 
90 	/** number of elements */
91 	size_t length() const pure nothrow @property @safe
92 	{
93 		return _length;
94 	}
95 
96 	/** ditto */
97 	size_t opDollar() const pure nothrow @property @safe
98 	{
99 		return _length;
100 	}
101 
102 	/** number of elements this structure can hold without further allocations */
103 	size_t capacity() const pure nothrow @property @safe
104 	{
105 		return _capacity;
106 	}
107 
108 	/** make sure this structure can contain given number of elements without further allocs */
109 	void reserve(size_t newCap, bool overEstimate = false) nothrow @trusted
110 	{
111 		if(newCap <= _capacity)
112 			return;
113 
114 		if(overEstimate)
115 			newCap = max(newCap, 2*_capacity);
116 
117 		auto newPtr = jiveMalloc!V(newCap);
118 		memcpy(newPtr, _ptr, V.sizeof * _length);
119 
120 		static if(hasIndirections!V)
121 			memset(newPtr + length, 0, V.sizeof * (newCap - _length)); // prevent false pointers
122 
123 		jiveFree(_ptr);
124 		_ptr = newPtr;
125 		_capacity = newCap;
126 	}
127 
128 	/** pointer to the first element */
129 	inout(V)* ptr() inout pure nothrow @property @safe
130 	{
131 		return _ptr;
132 	}
133 
134 	/** default range */
135 	inout(V)[] opSlice() inout nothrow pure @trusted
136 	{
137 		return _ptr[0 .. _length];
138 	}
139 
140 	/** subrange */
141 	inout(V)[] opSlice(string file = __FILE__, int line = __LINE__)(size_t a, size_t b) inout pure nothrow @trusted
142 	{
143 		if(boundsChecks && (a > b || b > _length))
144 			assert(false, boundsCheckMsg!(file, line));
145 		return _ptr[a .. b];
146 	}
147 
148 	/** assign all elements to the same value */
149 	void opSliceAssign(V v)
150 	{
151 		this.opSlice()[] = v;
152 	}
153 
154 	/* assign a subset of elements to the same value */
155 	void opSliceAssign(string file = __FILE__, int line = __LINE__)(V v, size_t a, size_t b)
156 	{
157 		this.opSlice!(file, line)(a, b)[] = v;
158 	}
159 
160 	/** indexing */
161 	ref inout(V) opIndex(string file = __FILE__, int line = __LINE__)(size_t i) inout pure nothrow @trusted
162 	{
163 		if(boundsChecks && i >= _length)
164 			assert(false, boundsCheckMsg!(file, line));
165 		return _ptr[i];
166 	}
167 
168 	/** first element, same as this[0] */
169 	ref inout(V) front(string file = __FILE__, int line = __LINE__)() inout pure nothrow
170 	{
171 		return this.opIndex!(file, line)(0);
172 	}
173 
174 	/** last element, same as this[$-1] */
175 	ref inout(V) back(string file = __FILE__, int line = __LINE__)() inout pure nothrow
176 	{
177 		return this.opIndex!(file, line)(_length-1);
178 	}
179 
180 	/** add some new element to the back */
181 	void pushBack(V val) @trusted
182 	{
183 		reserve(_length + 1, true);
184 		moveEmplace(val, _ptr[_length]);
185 		++_length;
186 	}
187 
188 	/** add multiple new elements to the back */
189 	void pushBack(Stuff)(Stuff data)
190 		if(!is(Stuff:V) && isInputRange!Stuff && is(ElementType!Stuff:V))
191 	{
192 		static if(hasLength!Stuff)
193 			reserve(_length + data.length, true);
194 
195 		foreach(ref x; data)
196 			pushBack(x);
197 	}
198 
199 	/** convenience alias for pushBack */
200 	alias pushBack opCatAssign;
201 
202 	/** returns removed element */
203 	V popBack(string file = __FILE__, int line = __LINE__)() @trusted
204 	{
205 		if(boundsChecks && empty)
206 			assert(false, boundsCheckMsg!(file, line));
207 
208 		--_length;
209 		V r = void;
210 		memcpy(&r, _ptr + _length, V.sizeof);
211 		static if(hasIndirections!V)
212 			memset(_ptr + _length, 0, V.sizeof);
213 		return r;
214 	}
215 
216 	/** insert new element at given location. moves all elements behind */
217 	void insert(string file = __FILE__, int line = __LINE__)(size_t i, V data) @trusted
218 	{
219 		if(boundsChecks && i > _length)
220 			assert(false, boundsCheckMsg!(file, line));
221 
222 		reserve(_length + 1, true);
223 		memmove(_ptr + i + 1, _ptr + i, V.sizeof * (_length - i));
224 		++_length;
225 		moveEmplace(data, _ptr[i]);
226 	}
227 
228 	/** remove i'th element. moves all elements behind */
229 	V remove(string file = __FILE__, int line = __LINE__)(size_t i) @trusted
230 	{
231 		if(boundsChecks && i >= _length)
232 			assert(false, boundsCheckMsg!(file, line));
233 
234 		V r = void;
235 		memcpy(&r, _ptr + i, V.sizeof);
236 		--_length;
237 		memmove(_ptr + i, _ptr + i + 1, V.sizeof * (_length - i));
238 		static if(hasIndirections!V)
239 			memset(_ptr + _length, 0, V.sizeof);
240 		return r;
241 	}
242 
243 	/** sets the size to some value. Either cuts of some values (but does not free memory), or fills new ones with V.init */
244 	void resize(size_t size, V v) @trusted
245 	{
246 		// TODO: remove @trusted in case V's destructor is @system
247 
248 		if(size <= _length) // shrink
249 		{
250 			static if(hasElaborateDestructor!V)
251 				for(size_t i = size; i < _length; ++i)
252 					destroy(_ptr[i]);
253 			static if(hasIndirections!V)
254 				memset(_ptr + size, 0, V.sizeof * (_length - size));
255 			_length = size;
256 		}
257 		else // expand
258 		{
259 			reserve(size, false);
260 			for(size_t i = _length; i < size; ++i)
261 				emplace(_ptr + i, v);
262 			_length = size;
263 		}
264 	}
265 
266 	/** ditto */
267 	void resize(size_t size)
268 	{
269 		resize(size, V.init);
270 	}
271 
272 	/** sets the size and fills everything with one value */
273 	void assign(size_t newsize, V v)
274 	{
275 		resize(newsize);
276 		this[] = v;
277 	}
278 
279 	/** remove all content but keep allocated memory (same as resize(0)) */
280 	void clear()
281 	{
282 		resize(0);
283 	}
284 
285 	/** convert to string */
286 	void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const
287 	{
288 		formatValue(sink, this[], fmt);
289 	}
290 
291 	/** ditto */
292 	string toString() const
293 	{
294 		return format("%s", this[]);
295 	}
296 
297 	hash_t toHash() const nothrow @trusted
298 	{
299 		return this[].hashOf;
300 	}
301 
302 	bool opEquals(const ref Array other) const
303 	{
304 		return equal(this[], other[]);
305 	}
306 
307 	static if(__traits(compiles, V.init < V.init))
308 	int opCmp(const ref Array other) const
309 	{
310 		return cmp(this[], other[]);
311 	}
312 }
313 
314 ///
315 /+@nogc+/ nothrow pure @safe unittest
316 {
317 	Array!int a;
318 
319 	a.pushBack(1);
320 	a.pushBack([2,3,4,5]);
321 	assert(a.popBack() == 5);
322 	assert(equal(a[], [1,2,3,4]));
323 
324 	a[] = 0;
325 	a[1..3] = 1;
326 	a.resize(6, 2);
327 	assert(equal(a[], [0,1,1,0,2,2]));
328 }
329 
330 // check for all nice attributes in case the type is nice as well
331 @nogc nothrow pure @safe unittest
332 {
333 	struct S1
334 	{
335 		int x;
336 		//int* p; // FIXME: when GC.addRange/removeRange become pure
337 		this(this) @nogc nothrow pure @safe {}
338 		~this() @nogc nothrow pure @safe {}
339 	}
340 
341 	static assert(hasElaborateDestructor!S1);
342 	static assert(hasElaborateCopyConstructor!S1);
343 	//static assert(hasIndirections!S);
344 
345 	Array!S1 a;
346 	S1 s;
347 	a.pushBack(s);
348 	a.popBack();
349 	a.resize(5);
350 	a.insert(3, s);
351 	a.remove(3);
352 	a.reserve(10);
353 	a[] = s;
354 	a[0..1] = s;
355 	a.pushBack(a[0]);
356 	a.pushBack(a[0..1]); // only valid with the previous reserve!
357 }
358 
359 // check correct invocation of postblit/destructor
360 unittest
361 {
362 	int counter = 0;
363 
364 	struct S
365 	{
366 		bool active;
367 		this(bool active) { this.active = active; if(active) ++counter; }
368 		this(this) { if(active) ++counter; }
369 		~this() { if(active) --counter; }
370 	}
371 
372 	{
373 		Array!S a;
374 		assert(counter == 0);
375 		a.pushBack(S(true));
376 		assert(counter == 1);
377 		a.pushBack(a[0]);
378 		assert(counter == 2);
379 		a.reserve(5);
380 		a.pushBack(a[]);
381 
382 		assert(counter == 4);
383 		a.insert(1, a[1]);
384 		assert(counter == 5);
385 		a.remove(3);
386 		assert(counter == 4);
387 		Array!S b = a;
388 		assert(a[] == b[]);
389 		assert(counter == 8);
390 	}
391 	assert(counter == 0);
392 }
393 
394 // check move-semantics
395 unittest
396 {
397 	struct S3
398 	{
399 		int x;
400 		alias x this;
401 		this(this) { assert(x == 0); }
402 	}
403 
404 	Array!S3 a;
405 	a.pushBack(S3(1));
406 	a.pushBack(S3(3));
407 	a.insert(1, S3(2));
408 	a.popBack();
409 	a[1] = S3(4);
410 	assert(a[] == [S3(1),S3(4)]);
411 }
412 
413 // type with no @safe/pure/etc-attributes at all and also no opCmp
414 unittest
415 {
416 	struct S
417 	{
418 		int* x;
419 		this(this){ }
420 		~this(){ }
421 		bool opEquals(const S b) const { return x is b.x; }
422 	}
423 
424 	static assert(hasIndirections!S);
425 	static assert(hasElaborateDestructor!S);
426 
427 	S s;
428 	Array!S a;
429 	a.pushBack(s);
430 	a.pushBack([s,s]);
431 	a.popBack();
432 	a.reserve(5);
433 	a.insert(1, s);
434 	a.remove(2);
435 	a.resize(3);
436 	assert(a[] == [s,s,s]);
437 	Array!S b = a;
438 }