1 /**
2  * Dynamic Array
3  * Copyright: © 2014 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 /**
11  * Array that is able to grow itself when items are appended to it. Uses
12  * reference counting to manage memory and malloc/free/realloc for managing its
13  * storage.
14  * Params:
15  *     T = the array element type
16  *     supportGC = true if the container should support holding references to
17  *         GC-allocated memory.
18  */
19 struct DynamicArray(T, bool supportGC = true)
20 {
21 	import std.traits : hasMember;
22 	this(this) @disable;
23 
24 	~this()
25 	{
26 		import std.experimental.allocator.mallocator : Mallocator;
27 		import containers.internal.node : shouldAddGCRange;
28 
29 		if (arr is null)
30 			return;
31 		foreach (ref item; arr[0 .. l])
32 		{
33 			static if (is(T == class))
34 				destroy(item);
35 			else
36 				typeid(T).destroy(&item);
37 		}
38 		static if (shouldAddGCRange!T)
39 		{
40 			import core.memory : GC;
41 			GC.removeRange(arr.ptr);
42 		}
43 		Mallocator.instance.deallocate(arr);
44 	}
45 
46 	/// Slice operator overload
47 	T[] opSlice() @nogc
48 	{
49 		return arr[0 .. l];
50 	}
51 
52 	/// ditto
53 	T[] opSlice(size_t a, size_t b) @nogc
54 	{
55 		return arr[a .. b];
56 	}
57 
58 	/// Index operator overload
59 	T opIndex(size_t i) @nogc
60 	{
61 		return arr[i];
62 	}
63 
64 	/**
65 	 * Inserts the given value into the end of the array.
66 	 */
67 	void insert(T value)
68 	{
69 		import std.experimental.allocator.mallocator : Mallocator;
70 		import containers.internal.node : shouldAddGCRange;
71 
72 		if (arr.length == 0)
73 		{
74 			arr = cast(T[]) Mallocator.instance.allocate(T.sizeof * 4);
75 			static if (supportGC && shouldAddGCRange!T)
76 			{
77 				import core.memory: GC;
78 				GC.addRange(arr.ptr, arr.length * T.sizeof);
79 			}
80 		}
81 		else if (l >= arr.length)
82 		{
83 			immutable size_t c = arr.length > 512 ? arr.length + 1024 : arr.length << 1;
84 			void[] a = cast(void[]) arr;
85 			Mallocator.instance.reallocate(a, c * T.sizeof);
86 			arr = cast(T[]) a;
87 			static if (supportGC && shouldAddGCRange!T)
88 			{
89 				import core.memory: GC;
90 				GC.removeRange(arr.ptr);
91 				GC.addRange(arr.ptr, arr.length * T.sizeof);
92 			}
93 		}
94 		arr[l++] = value;
95 	}
96 
97 	/// ditto
98 	alias put = insert;
99 
100 	void remove(const size_t i)
101 	{
102 		if (i < this.l)
103 		{
104 			static if (is(T == class))
105 				destroy(arr[i]);
106 			else
107 				typeid(T).destroy(&arr[i]);
108 
109 			auto next = i + 1;
110 			while (next < this.l)
111 			{
112 				arr[next - 1] = arr[next];
113 				++next;
114 			}
115 
116 			--l;
117 		}
118 		else
119 		{
120 			import core.exception : RangeError;
121 			throw new RangeError("Out of range index used to remove element");
122 		}
123 	}
124 
125 	/// Index assignment support
126 	void opIndexAssign(T value, size_t i) @nogc
127 	{
128 		arr[i] = value;
129 	}
130 
131 	/// Slice assignment support
132 	void opSliceAssign(T value) @nogc
133 	{
134 		arr[0 .. l] = value;
135 	}
136 
137 	/// ditto
138 	void opSliceAssign(T value, size_t i, size_t j) @nogc
139 	{
140 		arr[i .. j] = value;
141 	}
142 
143 	/// Returns: the number of items in the array
144 	size_t length() const nothrow pure @property @safe @nogc { return l; }
145 
146 	/**
147 	 * Returns: a slice to the underlying array.
148 	 *
149 	 * As the memory of the array may be freed, access to this array is
150 	 * highly unsafe.
151 	 */
152 	@property T* ptr() @nogc { return arr.ptr; }
153 
154 private:
155 
156 	T[] arr;
157 	size_t l;
158 }
159 
160 unittest
161 {
162 	import std.algorithm : equal;
163 	import std.range : iota;
164 	DynamicArray!int ints;
165 	foreach (i; 0 .. 100)
166 		ints.insert(i);
167 	assert (equal(ints[], iota(100)));
168 	assert (ints.length == 100);
169 	ints[0] = 100;
170 	assert (ints[0] == 100);
171 	ints[0 .. 5] = 20;
172 	foreach (i; ints[0 .. 5])
173 		assert (i == 20);
174 	ints[] = 432;
175 	foreach (i; ints[])
176 		assert (i == 432);
177 
178 	auto arr = ints.ptr;
179 	arr[0] = 1337;
180 	assert(arr[0] == 1337);
181 	assert(ints[0] == 1337);
182 }
183 
184 version(unittest)
185 {
186 	class Cls
187 	{
188 		int* a;
189 
190 		this(int* a)
191 		{
192 			this.a = a;
193 		}
194 
195 		~this()
196 		{
197 			++(*a);
198 		}
199 	}
200 }
201 
202 unittest
203 {
204 	int a = 0;
205 	{
206 		DynamicArray!(Cls) arr;
207 		arr.insert(new Cls( & a));
208 	}
209 	assert(a == 1);
210 }
211 
212 unittest
213 {
214 	import std.exception : assertThrown;
215 	import core.exception : RangeError;
216 	DynamicArray!int empty;
217 	assertThrown!RangeError(empty.remove(1337));
218 	assert(empty.length == 0);
219 
220 	DynamicArray!int one;
221 	one.insert(0);
222 	assert(one.length == 1);
223 	assertThrown!RangeError(one.remove(1337));
224 	assert(one.length == 1);
225 	one.remove(0);
226 	assert(one.length == 0);
227 
228 	DynamicArray!int two;
229 	two.insert(0);
230 	two.insert(1);
231 	assert(two.length == 2);
232 	assertThrown!RangeError(two.remove(1337));
233 	assert(two.length == 2);
234 	two.remove(0);
235 	assert(two.length == 1);
236 	assert(two[0] == 1);
237 	two.remove(0);
238 	assert(two.length == 0);
239 
240 	two.insert(0);
241 	two.insert(1);
242 	assert(two.length == 2);
243 
244 	two.remove(1);
245 	assert(two.length == 1);
246 	assert(two[0] == 0);
247 	assertThrown!RangeError(two.remove(1));
248 	assert(two.length == 1);
249 	assert(two[0] == 0);
250 	two.remove(0);
251 	assert(two.length == 0);
252 }
253 
254 unittest
255 {
256 	int a = 0;
257 	DynamicArray!(Cls,true) arr;
258 	arr.insert(new Cls(&a));
259 
260 	arr.remove(0);
261 	assert(a == 1);
262 }
263 
264 unittest
265 {
266 	DynamicArray!(int*,true) arr;
267 	arr.insert(new int(1));
268 }