1 /**
2    Dynamic arrays with deterministic memory usage
3    akin to C++'s std::vector or Rust's std::vec::Vec
4  */
5 module automem.vector;
6 
7 
8 import automem.traits: isAllocator, isGlobal;
9 import std.range.primitives: isInputRange;
10 import stdx.allocator: theAllocator;
11 import stdx.allocator.mallocator: Mallocator;
12 
13 /**
14    Create a vector from a variadic list of elements, inferring the type of
15    the elements and the allocator
16  */
17 auto vector(A = typeof(theAllocator), E)
18            (E[] elements...)
19     if(isAllocator!A && isGlobal!A)
20 {
21     return Vector!(E, A)(elements);
22 }
23 
24 /// ditto
25 auto vector(A = typeof(theAllocator), E)
26            (A allocator, E[] elements...)
27     if(isAllocator!A && !isGlobal!A)
28 {
29     return Vector!(E, A)(allocator, elements);
30 }
31 
32 /**
33    Create a vector from an input range, inferring the type of the elements
34    and the allocator.
35  */
36 auto vector(A = typeof(theAllocator), R)
37            (R range)
38     if(isAllocator!A && isGlobal!A && isInputRange!R)
39 {
40     import std.range.primitives: ElementType;
41     return Vector!(ElementType!R, A)(range);
42 }
43 
44 
45 /// ditto
46 auto vector(A = typeof(theAllocator), R)
47            (A allocator, R range)
48     if(isAllocator!A && !isGlobal!A && isInputRange!R)
49 {
50     import std.range.primitives: ElementType;
51     return Vector!(ElementType!R, A)(range);
52 }
53 
54 /**
55    A dynamic array with deterministic memory usage
56    akin to C++'s std::vector or Rust's std::vec::Vec
57  */
58 struct Vector(E, Allocator = typeof(theAllocator)) if(isAllocator!Allocator) {
59 
60     import automem.traits: isGlobal, isSingleton, isTheAllocator;
61     import std.traits: Unqual;
62 
63     static if(isGlobal!Allocator) {
64 
65         this(E[] elements...) {
66             fromElements(elements);
67         }
68 
69         this(R)(R range) if(isInputRangeOf!(R, E)) {
70             this = range;
71         }
72 
73     } else {
74 
75         this(Allocator allocator, E[] elements...) {
76             _allocator = allocator;
77             fromElements(elements);
78         }
79 
80         this(R)(Allocator allocator, R range) if(isInputRangeOf!(R, E)) {
81             _allocator = allocator;
82             this = range;
83         }
84     }
85 
86     this(this) scope {
87         auto oldElements = _elements;
88         _elements = createVector(_elements.length);
89         _elements[0 .. length.toSizeT] = oldElements[0 .. length.toSizeT];
90     }
91 
92     ~this() scope {
93         import stdx.allocator: dispose;
94         () @trusted { _allocator.dispose(_elements); }();
95     }
96 
97     /// Returns the first element
98     inout(E) front() inout {
99         return _elements[0];
100     }
101 
102     /// Returns the last element
103     inout(E) back() inout {
104         return _elements[(length - 1).toSizeT];
105     }
106 
107     /// Pops the front element off
108     void popFront() {
109         foreach(i; 0 .. length - 1)
110             _elements[i.toSizeT] = _elements[i.toSizeT + 1];
111 
112         popBack;
113     }
114 
115     /// Pops the last element off
116     void popBack() {
117         --_length;
118     }
119 
120     /// If the vector is empty
121     bool empty() const {
122         return length == 0;
123     }
124 
125     /// The current length of the vector
126     @property long length() const {
127         return _length;
128     }
129 
130     /// Set the length of the vector
131     @property void length(long newLength) {
132         if(capacity < newLength) reserve(newLength);
133         _length = newLength;
134     }
135 
136     /// The current memory capacity of the vector
137     long capacity() const {
138         return _elements.length;
139     }
140 
141     /// Clears the vector, resulting in an empty one
142     void clear() {
143         _length = 0;
144     }
145 
146     /// Reserve memory to avoid allocations when appending
147     void reserve(long newLength) {
148         expandMemory(newLength);
149     }
150 
151     /// Shrink to fit the current length. Returns if shrunk.
152     bool shrink() scope {
153         return shrink(length);
154     }
155 
156     /// Shrink to fit the new length given. Returns if shrunk.
157     bool shrink(long newLength) scope {
158         import stdx.allocator: shrinkArray;
159 
160         const delta = capacity - newLength;
161         const shrunk = () @trusted { return _allocator.shrinkArray(_elements, delta.toSizeT); }();
162         _length = newLength;
163 
164         return shrunk;
165     }
166 
167     /// Access the ith element. Can throw RangeError.
168     ref inout(E) opIndex(long i) inout {
169         if(i < 0 || i >= length)
170             throw boundsException;
171         return _elements[i.toSizeT];
172     }
173 
174     /// Returns a new vector after appending to the given vector.
175     Vector opBinary(string s, T)(auto ref T other) const if(s == "~" && is(Unqual!T == Vector)) {
176         import std.range: chain;
177         return Vector(chain(this[], other[]));
178     }
179 
180     /// Assigns from a range.
181     void opAssign(R)(R range) scope if(isForwardRangeOf!(R, E)) {
182         import std.range.primitives: walkLength, save;
183 
184         expand(range.save.walkLength);
185 
186         long i = 0;
187         foreach(element; range)
188             _elements[toSizeT(i++)] = element;
189     }
190 
191     /// Append to the vector
192     void opOpAssign(string op)
193                    (E other)
194         scope
195         if(op == "~")
196     {
197         expand(length + 1);
198         _elements[(length - 1).toSizeT] = other;
199     }
200 
201     /// Append to the vector
202     void opOpAssign(string op, R)
203                    (R range)
204         scope
205         if(op == "~" && isForwardRangeOf!(R, E))
206     {
207         import std.range.primitives: walkLength, save;
208 
209         long index = length;
210         expand(length + range.save.walkLength);
211 
212         foreach(element; range)
213             _elements[toSizeT(index++)] = element;
214     }
215 
216     /// Returns a slice
217     scope auto opSlice(this This)() {
218         return _elements[0 .. length.toSizeT];
219     }
220 
221     /// Returns a slice
222     scope auto opSlice(this This)(long start, long end) {
223         if(start < 0 || start >= length)
224             throw boundsException;
225 
226         if(end < 0 || end >= length)
227             throw boundsException;
228 
229         return _elements[start.toSizeT .. end.toSizeT];
230     }
231 
232     long opDollar() const {
233         return length;
234     }
235 
236     /// Assign all elements to the given value
237     void opSliceAssign(E value) {
238         _elements[] = value;
239     }
240 
241     /// Assign all elements in the given range to the given value
242     void opSliceAssign(E value, long start, long end) {
243         if(start < 0 || start >= length)
244             throw boundsException;
245 
246         if(end < 0 || end >= length)
247             throw boundsException;
248 
249         _elements[start.toSizeT .. end.toSizeT] = value;
250     }
251 
252     /// Assign all elements using the given operation and the given value
253     void opSliceOpAssign(string op)(E value) scope {
254         foreach(ref elt; _elements)
255             mixin(`elt ` ~ op ~ `= value;`);
256     }
257 
258     /// Assign all elements in the given range  using the given operation and the given value
259     void opSliceOpAssign(string op)(E value, long start, long end) scope {
260         if(start < 0 || start >= length)
261             throw boundsException;
262 
263         if(end < 0 || end >= length)
264             throw boundsException;
265 
266         foreach(ref elt; _elements[start.toSizeT .. end.toSizeT])
267             mixin(`elt ` ~ op ~ `= value;`);
268     }
269 
270 private:
271 
272     E[] _elements;
273     long _length;
274 
275     static if(isSingleton!Allocator)
276         alias _allocator = Allocator.instance;
277     else static if(isTheAllocator!Allocator)
278         alias _allocator = theAllocator;
279     else
280         Allocator _allocator;
281 
282     E[] createVector(long length) {
283         import stdx.allocator: makeArray;
284         return () @trusted { return _allocator.makeArray!E(length.toSizeT); }();
285     }
286 
287     void fromElements(E[] elements) {
288         _elements = createVector(elements.length);
289         _elements[] = elements[];
290         _length = elements.length;
291     }
292 
293     void expand(long newLength) scope {
294         expandMemory(newLength);
295         _length = newLength;
296     }
297 
298     void expandMemory(long newLength) scope {
299         import stdx.allocator: expandArray;
300 
301         if(newLength > capacity) {
302             if(length == 0)
303                 _elements = createVector(newLength);
304             else {
305                 const newCapacity = (newLength * 3) / 2;
306                 const delta = newCapacity - capacity;
307                 () @trusted { _allocator.expandArray(_elements, delta.toSizeT); }();
308             }
309         }
310     }
311 }
312 
313 private static immutable boundsException = new BoundsException("Out of bounds index");
314 
315 class BoundsException: Exception {
316     import std.exception: basicExceptionCtors;
317 
318     mixin basicExceptionCtors;
319 }
320 
321 private template isInputRangeOf(R, E) {
322     import std.range.primitives: isInputRange, ElementType;
323     import std.traits: Unqual;
324 
325     enum isInputRangeOf = isInputRange!R && is(Unqual!(ElementType!R) == E);
326 }
327 
328 private template isForwardRangeOf(R, E) {
329     import std.range.primitives: isForwardRange, ElementType;
330     import std.traits: Unqual;
331 
332     enum isForwardRangeOf = isForwardRange!R && is(Unqual!(ElementType!R) == E);
333 }
334 
335 
336 private size_t toSizeT(long length) @safe @nogc pure nothrow {
337     static if(size_t.sizeof < long.sizeof)
338         assert(length < cast(long) size_t.max);
339     return cast(size_t) length;
340 }