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 alias String = StringA!(typeof(theAllocator));
15 alias StringM = StringA!Mallocator;
16 
17 
18 template StringA(A = typeof(theAllocator)) if(isAllocator!A) {
19     alias StringA = Vector!(immutable char, A);
20 }
21 
22 /**
23    Create a vector from a variadic list of elements, inferring the type of
24    the elements and the allocator
25  */
26 auto vector(A = typeof(theAllocator), E)
27            (E[] elements...)
28     if(isAllocator!A && isGlobal!A)
29 {
30     return Vector!(E, A)(elements);
31 }
32 
33 /// ditto
34 auto vector(A = typeof(theAllocator), E)
35            (A allocator, E[] elements...)
36     if(isAllocator!A && !isGlobal!A)
37 {
38     return Vector!(E, A)(allocator, elements);
39 }
40 
41 /**
42    Create a vector from an input range, inferring the type of the elements
43    and the allocator.
44  */
45 auto vector(A = typeof(theAllocator), R)
46            (R range)
47     if(isAllocator!A && isGlobal!A && isInputRange!R)
48 {
49     import automem.vector: ElementType;
50     return Vector!(ElementType!R, A)(range);
51 }
52 
53 
54 /// ditto
55 auto vector(A = typeof(theAllocator), R)
56            (A allocator, R range)
57     if(isAllocator!A && !isGlobal!A && isInputRange!R)
58 {
59     import automem.vector: ElementType;
60     return Vector!(ElementType!R, A)(allocator, range);
61 }
62 
63 /**
64    A dynamic array with deterministic memory usage
65    akin to C++'s std::vector or Rust's std::vec::Vec
66  */
67 struct Vector(E, Allocator = typeof(theAllocator)) if(isAllocator!Allocator) {
68 
69     import automem.traits: isGlobal, isSingleton, isTheAllocator;
70     import std.traits: Unqual;
71 
72     alias MutE = Unqual!E;
73     enum isElementMutable = !is(E == immutable) && !is(E == const);
74 
75     static if(isGlobal!Allocator) {
76 
77         this(E[] elements...) {
78             fromElements(elements);
79         }
80 
81         this(R)(R range) if(isInputRangeOf!(R, E)) {
82             this = range;
83         }
84 
85     } else {
86 
87         this(Allocator allocator, E[] elements...) {
88             _allocator = allocator;
89             fromElements(elements);
90         }
91 
92         this(R)(Allocator allocator, R range) if(isInputRangeOf!(R, E)) {
93             _allocator = allocator;
94             this = range;
95         }
96     }
97 
98     this(this) scope {
99         auto oldElements = _elements;
100         _elements = createVector(_elements.length);
101         () @trusted {
102             cast(MutE[])(_elements)[0 .. length.toSizeT] = oldElements[0 .. length.toSizeT];
103         }();
104     }
105 
106     ~this() {
107         free;
108     }
109 
110     /// Frees the memory and returns to .init
111     void free() scope {
112         import stdx.allocator: dispose;
113         () @trusted { _allocator.dispose(cast(void[]) _elements); }();
114         clear;
115     }
116 
117     /// Returns the first element
118     inout(E) front() inout {
119         return _elements[0];
120     }
121 
122     /// Returns the last element
123     inout(E) back() inout {
124         return _elements[(length - 1).toSizeT];
125     }
126 
127     static if(isElementMutable) {
128         /// Pops the front element off
129         void popFront() {
130             foreach(i; 0 .. length - 1)
131                 _elements[i.toSizeT] = _elements[i.toSizeT + 1];
132 
133             popBack;
134         }
135     }
136 
137     /// Pops the last element off
138     void popBack() {
139         --_length;
140     }
141 
142     /// If the vector is empty
143     bool empty() const {
144         return length == 0;
145     }
146 
147     /// The current length of the vector
148     @property long length() const {
149         return _length;
150     }
151 
152     /// Set the length of the vector
153     @property void length(long newLength) {
154         if(capacity < newLength) reserve(newLength);
155         _length = newLength;
156     }
157 
158     /// The current memory capacity of the vector
159     long capacity() const {
160         return _elements.length;
161     }
162 
163     /// Clears the vector, resulting in an empty one
164     void clear() {
165         _length = 0;
166     }
167 
168     /// Reserve memory to avoid allocations when appending
169     void reserve(long newLength) {
170         expandMemory(newLength);
171     }
172 
173     static if(isElementMutable) {
174 
175         /// Shrink to fit the current length. Returns if shrunk.
176         bool shrink() scope {
177             return shrink(length);
178         }
179 
180         /// Shrink to fit the new length given. Returns if shrunk.
181         bool shrink(long newLength) scope @trusted {
182             import stdx.allocator: shrinkArray;
183 
184             const delta = capacity - newLength;
185             const shrunk = _allocator.shrinkArray(_elements, delta.toSizeT);
186             _length = newLength;
187 
188             return shrunk;
189         }
190     }
191 
192     /// Access the ith element. Can throw RangeError.
193     ref inout(E) opIndex(long i) inout {
194         if(i < 0 || i >= length)
195             throw boundsException;
196         return _elements[i.toSizeT];
197     }
198 
199     /// Returns a new vector after appending to the given vector.
200     Vector opBinary(string s, T)(auto ref T other) const if(s == "~" && is(Unqual!T == Vector)) {
201         import std.range: chain;
202         return Vector(chain(this[], other[]));
203     }
204 
205     /// Assigns from a range.
206     void opAssign(R)(R range) scope if(isForwardRangeOf!(R, E)) {
207         import std.range.primitives: walkLength, save;
208 
209         expand(range.save.walkLength);
210 
211         long i = 0;
212         foreach(element; range)
213             _elements[toSizeT(i++)] = element;
214     }
215 
216     /// Append to the vector
217     void opOpAssign(string op)
218                    (E other)
219         scope
220         if(op == "~")
221     {
222         expand(length + 1);
223 
224         const lastIndex = (length - 1).toSizeT;
225         static if(!isElementMutable) {
226             assert(_elements[lastIndex] == E.init,
227                    "Assigning to non default initialised non mutable member");
228         }
229 
230         () @trusted { mutableElements[lastIndex] = other; }();
231     }
232 
233     /// Append to the vector from a range
234     void opOpAssign(string op, R)
235                    (scope R range)
236         scope
237         if(op == "~" && isForwardRangeOf!(R, E))
238     {
239         import std.range.primitives: walkLength, save;
240 
241         long index = length;
242         expand(length + () @trusted { return range.save.walkLength; }());
243 
244         foreach(element; range) {
245             const safeIndex = toSizeT(index++);
246             static if(!isElementMutable) {
247                 assert(_elements[safeIndex] == E.init,
248                        "Assigning to non default initialised non mutable member");
249             }
250             () @trusted { mutableElements[safeIndex] = element; }();
251         }
252     }
253 
254     /// Returns a slice
255     auto opSlice(this This)() scope return {
256         return _elements[0 .. length.toSizeT];
257     }
258 
259     /// Returns a slice
260     auto opSlice(this This)(long start, long end) scope return {
261         if(start < 0 || start >= length)
262             throw boundsException;
263 
264         if(end < 0 || end >= length)
265             throw boundsException;
266 
267         return _elements[start.toSizeT .. end.toSizeT];
268     }
269 
270     long opDollar() const {
271         return length;
272     }
273 
274     static if(isElementMutable) {
275         /// Assign all elements to the given value
276         void opSliceAssign(E value) {
277             _elements[] = value;
278         }
279     }
280 
281 
282     static if(isElementMutable) {
283         /// Assign all elements in the given range to the given value
284         void opSliceAssign(E value, long start, long end) {
285             if(start < 0 || start >= length)
286                 throw boundsException;
287 
288             if(end < 0 || end >= length)
289                 throw boundsException;
290 
291             _elements[start.toSizeT .. end.toSizeT] = value;
292         }
293     }
294 
295     static if(isElementMutable) {
296         /// Assign all elements using the given operation and the given value
297         void opSliceOpAssign(string op)(E value) scope {
298             foreach(ref elt; _elements)
299                 mixin(`elt ` ~ op ~ `= value;`);
300         }
301     }
302 
303     static if(isElementMutable) {
304         /// Assign all elements in the given range  using the given operation and the given value
305         void opSliceOpAssign(string op)(E value, long start, long end) scope {
306             if(start < 0 || start >= length)
307                 throw boundsException;
308 
309             if(end < 0 || end >= length)
310                 throw boundsException;
311 
312             foreach(ref elt; _elements[start.toSizeT .. end.toSizeT])
313                 mixin(`elt ` ~ op ~ `= value;`);
314         }
315     }
316 
317     bool opCast(U)() const scope if(is(U == bool)) {
318         return length > 0;
319     }
320 
321     static if(is(Unqual!E == char)) {
322         // return a null-terminated C string
323         auto stringz(this This)() return scope {
324             if(capacity == length) reserve(length + 1);
325 
326             static if(!isElementMutable) {
327                 assert(_elements[length] == E.init || _elements[length] == 0,
328                        "Assigning to non default initialised non mutable member");
329             }
330 
331             () @trusted { mutableElements[length] = 0; }();
332 
333             return &_elements[0];
334         }
335     }
336 
337 private:
338 
339     E[] _elements;
340     long _length;
341 
342     static if(isSingleton!Allocator)
343         alias _allocator = Allocator.instance;
344     else static if(isTheAllocator!Allocator)
345         alias _allocator = theAllocator;
346     else
347         Allocator _allocator;
348 
349     E[] createVector(long length) {
350         import stdx.allocator: makeArray;
351         return () @trusted { return _allocator.makeArray!E(length.toSizeT); }();
352     }
353 
354     void fromElements(E[] elements) {
355 
356         _elements = createVector(elements.length);
357         () @trusted { (cast(MutE[]) _elements)[] = elements[]; }();
358         _length = elements.length;
359     }
360 
361     void expand(long newLength) scope {
362         expandMemory(newLength);
363         _length = newLength;
364     }
365 
366     void expandMemory(long newLength) scope {
367         import stdx.allocator: expandArray;
368 
369         if(newLength > capacity) {
370             if(length == 0)
371                 _elements = createVector(newLength);
372             else {
373                 const newCapacity = (newLength * 3) / 2;
374                 const delta = newCapacity - capacity;
375                 () @trusted { _allocator.expandArray(mutableElements, delta.toSizeT); }();
376             }
377         }
378     }
379 
380     ref MutE[] mutableElements() scope return @system {
381         auto ptr = &_elements;
382         return *(cast(MutE[]*) ptr);
383     }
384 }
385 
386 private static immutable boundsException = new BoundsException("Out of bounds index");
387 
388 class BoundsException: Exception {
389     import std.exception: basicExceptionCtors;
390 
391     mixin basicExceptionCtors;
392 }
393 
394 private template isInputRangeOf(R, E) {
395     import std.range.primitives: isInputRange;
396     enum isInputRangeOf = isInputRange!R && canAssignFrom!(R, E);
397 }
398 
399 private template isForwardRangeOf(R, E) {
400     import std.range.primitives: isForwardRange;
401     enum isForwardRangeOf = isForwardRange!R && canAssignFrom!(R, E);
402 }
403 
404 
405 private template canAssignFrom(R, E) {
406     enum canAssignFrom = is(typeof({
407         import automem.vector: frontNoAutoDecode;
408         E element = R.init.frontNoAutoDecode;
409     }));
410 }
411 private size_t toSizeT(long length) @safe @nogc pure nothrow {
412     static if(size_t.sizeof < long.sizeof)
413         assert(length < cast(long) size_t.max);
414     return cast(size_t) length;
415 }
416 
417 // Because autodecoding is fun
418 private template ElementType(R) {
419     import std.traits: isSomeString;
420 
421     static if(isSomeString!R) {
422         alias ElementType = typeof(R.init[0]);
423     } else {
424         import std.range.primitives: ElementType_ = ElementType;
425         alias ElementType = ElementType_!R;
426     }
427 }
428 
429 @("ElementType")
430 @safe pure unittest {
431     import automem.vector: ElementType;
432     static assert(is(ElementType!(int[]) == int));
433     static assert(is(ElementType!(char[]) == char));
434     static assert(is(ElementType!(wchar[]) == wchar));
435     static assert(is(ElementType!(dchar[]) == dchar));
436 }
437 
438 
439 // More fun with autodecoding
440 private auto frontNoAutoDecode(R)(R range) {
441     import std.traits: isSomeString;
442 
443     static if(isSomeString!R)
444         return range[0];
445     else {
446         import std.range.primitives: front;
447         return range.front;
448     }
449 }