1 /**
2  * Dynamic Array
3  * Copyright: © 2015 Economic Modeling Specialists, Intl.
4  * Authors: Brian Schott
5  * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6  */
7 
8 module containers.dynamicarray;
9 
10 private import containers.internal.node : shouldAddGCRange;
11 private import std.experimental.allocator.mallocator : Mallocator;
12 
13 /**
14  * Array that is able to grow itself when items are appended to it. Uses
15  * malloc/free/realloc to manage its storage.
16  *
17  * Params:
18  *     T = the array element type
19  *     Allocator = the allocator to use. Defaults to `Mallocator`.
20  *     supportGC = true if the container should support holding references to
21  *         GC-allocated memory.
22  */
23 struct DynamicArray(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange!T)
24 {
25 	this(this) @disable;
26 
27 	private import std.experimental.allocator.common : stateSize;
28 
29 	static if (stateSize!Allocator != 0)
30 	{
31 		/// No default construction if an allocator must be provided.
32 		this() @disable;
33 
34 		/**
35 		 * Use the given `allocator` for allocations.
36 		 */
37 		this(Allocator allocator)
38 		in
39 		{
40 			assert(allocator !is null, "Allocator must not be null");
41 		}
42 		body
43 		{
44 			this.allocator = allocator;
45 		}
46 	}
47 
48 	~this()
49 	{
50 		import std.experimental.allocator.mallocator : Mallocator;
51 		import containers.internal.node : shouldAddGCRange;
52 
53 		if (arr is null)
54 			return;
55 		foreach (ref item; arr[0 .. l])
56 		{
57 			static if (is(T == class))
58 				destroy(item);
59 			else
60 				typeid(T).destroy(&item);
61 		}
62 		static if (shouldAddGCRange!T)
63 		{
64 			import core.memory : GC;
65 			GC.removeRange(arr.ptr);
66 		}
67 		allocator.deallocate(arr);
68 	}
69 
70 	/// Slice operator overload
71 	pragma(inline, true)
72 	auto opSlice(this This)() @nogc
73 	{
74 		return opSlice!(This)(0, l);
75 	}
76 
77 	/// ditto
78 	pragma(inline, true)
79 	auto opSlice(this This)(size_t a, size_t b) @nogc
80 	{
81 		alias ET = ContainerElementType!(This, T);
82 		return cast(ET[]) arr[a .. b];
83 	}
84 
85 	/// Index operator overload
86 	pragma(inline, true)
87 	auto opIndex(this This)(size_t i) @nogc
88 	{
89 		return opSlice!(This)(i, i + 1)[0];
90 	}
91 
92 	/**
93 	 * Inserts the given value into the end of the array.
94 	 */
95 	void insert(T value)
96 	{
97 		import std.experimental.allocator.mallocator : Mallocator;
98 		import containers.internal.node : shouldAddGCRange;
99 
100 		if (arr.length == 0)
101 		{
102 			arr = cast(typeof(arr)) allocator.allocate(T.sizeof * 4);
103 			static if (useGC)
104 			{
105 				import core.memory: GC;
106 				GC.addRange(arr.ptr, arr.length * T.sizeof);
107 			}
108 		}
109 		else if (l >= arr.length)
110 		{
111 			immutable size_t c = arr.length > 512 ? arr.length + 1024 : arr.length << 1;
112 			void[] a = cast(void[]) arr;
113 			allocator.reallocate(a, c * T.sizeof);
114 			arr = cast(typeof(arr)) a;
115 			static if (useGC)
116 			{
117 				import core.memory: GC;
118 				GC.removeRange(arr.ptr);
119 				GC.addRange(arr.ptr, arr.length * T.sizeof);
120 			}
121 		}
122 		arr[l++] = value;
123 	}
124 
125 	/// ditto
126 	alias put = insert;
127 
128 	void remove(const size_t i)
129 	{
130 		if (i < this.l)
131 		{
132 			static if (is(T == class))
133 				destroy(arr[i]);
134 			else
135 				typeid(T).destroy(&arr[i]);
136 
137 			auto next = i + 1;
138 			while (next < this.l)
139 			{
140 				arr[next - 1] = arr[next];
141 				++next;
142 			}
143 
144 			--l;
145 		}
146 		else
147 		{
148 			import core.exception : RangeError;
149 			throw new RangeError("Out of range index used to remove element");
150 		}
151 	}
152 
153 	/// Index assignment support
154 	void opIndexAssign(T value, size_t i) @nogc
155 	{
156 		arr[i] = value;
157 	}
158 
159 	/// Slice assignment support
160 	void opSliceAssign(T value) @nogc
161 	{
162 		arr[0 .. l] = value;
163 	}
164 
165 	/// ditto
166 	void opSliceAssign(T value, size_t i, size_t j) @nogc
167 	{
168 		arr[i .. j] = value;
169 	}
170 
171 	/// Returns: the number of items in the array
172 	size_t length() const nothrow pure @property @safe @nogc { return l; }
173 
174 	/// Returns: whether or not the DynamicArray is empty.
175 	bool empty() const nothrow pure @property @safe @nogc { return l == 0; }
176 
177 	/**
178 	 * Returns: a slice to the underlying array.
179 	 *
180 	 * As the memory of the array may be freed, access to this array is
181 	 * highly unsafe.
182 	 */
183 	auto ptr(this This)() @nogc @property
184 	{
185 		alias ET = ContainerElementType!(This, T);
186 		return cast(ET*) arr.ptr;
187 	}
188 
189 	/// Returns: the front element of the DynamicArray.
190 	auto ref T front() pure @property
191 	{
192 		return arr[0];
193 	}
194 
195 	/// Returns: the back element of the DynamicArray.
196 	auto ref T back() pure @property
197 	{
198 		return arr[l - 1];
199 	}
200 
201 private:
202 
203 	import containers.internal.storage_type : ContainerStorageType;
204 	import containers.internal.element_type : ContainerElementType;
205 	import containers.internal.mixins : AllocatorState;
206 
207 	enum bool useGC = supportGC && shouldAddGCRange!T;
208 	mixin AllocatorState!Allocator;
209 	ContainerStorageType!(T)[] arr;
210 	size_t l;
211 }
212 
213 unittest
214 {
215 	import std.algorithm : equal;
216 	import std.range : iota;
217 	DynamicArray!int ints;
218 	assert(ints.empty);
219 	foreach (i; 0 .. 100)
220 	{
221 		ints.insert(i);
222 		assert(ints.front == 0);
223 		assert(ints.back == i);
224 	}
225 
226 	assert (equal(ints[], iota(100)));
227 	assert (ints.length == 100);
228 	ints[0] = 100;
229 	assert (ints[0] == 100);
230 	ints[0 .. 5] = 20;
231 	foreach (i; ints[0 .. 5])
232 		assert (i == 20);
233 	ints[] = 432;
234 	foreach (i; ints[])
235 		assert (i == 432);
236 
237 	auto arr = ints.ptr;
238 	arr[0] = 1337;
239 	assert(arr[0] == 1337);
240 	assert(ints[0] == 1337);
241 }
242 
243 version(unittest)
244 {
245 	class Cls
246 	{
247 		int* a;
248 
249 		this(int* a)
250 		{
251 			this.a = a;
252 		}
253 
254 		~this()
255 		{
256 			++(*a);
257 		}
258 	}
259 }
260 
261 unittest
262 {
263 	int a = 0;
264 	{
265 		DynamicArray!(Cls) arr;
266 		arr.insert(new Cls( & a));
267 	}
268 	assert(a == 1);
269 }
270 
271 unittest
272 {
273 	import std.exception : assertThrown;
274 	import core.exception : RangeError;
275 	DynamicArray!int empty;
276 	assertThrown!RangeError(empty.remove(1337));
277 	assert(empty.length == 0);
278 
279 	DynamicArray!int one;
280 	one.insert(0);
281 	assert(one.length == 1);
282 	assertThrown!RangeError(one.remove(1337));
283 	assert(one.length == 1);
284 	one.remove(0);
285 	assert(one.length == 0);
286 
287 	DynamicArray!int two;
288 	two.insert(0);
289 	two.insert(1);
290 	assert(two.length == 2);
291 	assertThrown!RangeError(two.remove(1337));
292 	assert(two.length == 2);
293 	two.remove(0);
294 	assert(two.length == 1);
295 	assert(two[0] == 1);
296 	two.remove(0);
297 	assert(two.length == 0);
298 
299 	two.insert(0);
300 	two.insert(1);
301 	assert(two.length == 2);
302 
303 	two.remove(1);
304 	assert(two.length == 1);
305 	assert(two[0] == 0);
306 	assertThrown!RangeError(two.remove(1));
307 	assert(two.length == 1);
308 	assert(two[0] == 0);
309 	two.remove(0);
310 	assert(two.length == 0);
311 }
312 
313 unittest
314 {
315 	int a = 0;
316 	DynamicArray!(Cls, Mallocator, true) arr;
317 	arr.insert(new Cls(&a));
318 
319 	arr.remove(0);
320 	assert(a == 1);
321 }
322 
323 unittest
324 {
325 	DynamicArray!(int*, Mallocator, true) arr;
326 	arr.insert(new int(1));
327 }