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