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, isCopyable;
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 static if(isCopyable!Allocator) {
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     } else {
97 
98         this(R)(R range) if(isInputRangeOf!(R, E)) {
99             this = range;
100         }
101 
102     }
103 
104     this(this) scope {
105         auto oldElements = _elements;
106         _elements = createVector(_elements.length);
107         () @trusted {
108             cast(MutE[])(_elements)[0 .. length.toSizeT] = oldElements[0 .. length.toSizeT];
109         }();
110     }
111 
112     ~this() {
113         free;
114     }
115 
116     /// Frees the memory and returns to .init
117     void free() scope {
118         import stdx.allocator: dispose;
119         () @trusted { _allocator.dispose(cast(void[]) _elements); }();
120         clear;
121     }
122 
123     /// Returns the first element
124     inout(E) front() inout {
125         return _elements[0];
126     }
127 
128     /// Returns the last element
129     inout(E) back() inout {
130         return _elements[(length - 1).toSizeT];
131     }
132 
133     static if(isElementMutable) {
134         /// Pops the front element off
135         void popFront() {
136             foreach(i; 0 .. length - 1)
137                 _elements[i.toSizeT] = _elements[i.toSizeT + 1];
138 
139             popBack;
140         }
141     }
142 
143     /// Pops the last element off
144     void popBack() {
145         --_length;
146     }
147 
148     /// If the vector is empty
149     bool empty() const {
150         return length == 0;
151     }
152 
153     /// The current length of the vector
154     @property long length() const {
155         return _length;
156     }
157 
158     /// Set the length of the vector
159     @property void length(long newLength) {
160         if(capacity < newLength) reserve(newLength);
161         _length = newLength;
162     }
163 
164     /// The current memory capacity of the vector
165     long capacity() const {
166         return _elements.length;
167     }
168 
169     /// Clears the vector, resulting in an empty one
170     void clear() {
171         _length = 0;
172     }
173 
174     /// Reserve memory to avoid allocations when appending
175     void reserve(long newLength) {
176         expandMemory(newLength);
177     }
178 
179     static if(isElementMutable) {
180 
181         /// Shrink to fit the current length. Returns if shrunk.
182         bool shrink() scope {
183             return shrink(length);
184         }
185 
186         /// Shrink to fit the new length given. Returns if shrunk.
187         bool shrink(long newLength) scope @trusted {
188             import stdx.allocator: shrinkArray;
189 
190             const delta = capacity - newLength;
191             const shrunk = _allocator.shrinkArray(_elements, delta.toSizeT);
192             _length = newLength;
193 
194             return shrunk;
195         }
196     }
197 
198     /// Access the ith element. Can throw RangeError.
199     ref inout(E) opIndex(long i) inout {
200         if(i < 0 || i >= length)
201             mixin(throwBoundsException);
202         return _elements[i.toSizeT];
203     }
204 
205     /// Returns a new vector after appending to the given vector.
206     Vector opBinary(string s, T)(auto ref T other) const if(s == "~" && is(Unqual!T == Vector)) {
207         import std.range: chain;
208         return Vector(chain(this[], other[]));
209     }
210 
211     /// Assigns from a range.
212     void opAssign(R)(R range) scope if(isForwardRangeOf!(R, E)) {
213         import std.range.primitives: walkLength, save;
214 
215         expand(range.save.walkLength);
216 
217         long i = 0;
218         foreach(element; range)
219             _elements[toSizeT(i++)] = element;
220     }
221 
222     // make it an output range
223 
224 
225     /// Append to the vector
226     void opOpAssign(string op)
227                    (E other)
228         scope
229         if(op == "~")
230     {
231         put(other);
232     }
233 
234     void put(E other) {
235 
236         expand(length + 1);
237 
238         const lastIndex = (length - 1).toSizeT;
239         static if(!isElementMutable) {
240             assert(_elements[lastIndex] == E.init,
241                    "Assigning to non default initialised non mutable member");
242         }
243 
244         () @trusted { mutableElements[lastIndex] = other; }();
245     }
246 
247     /// Append to the vector from a range
248     void opOpAssign(string op, R)
249                    (scope R range)
250         scope
251         if(op == "~" && isForwardRangeOf!(R, E))
252     {
253         put(range);
254     }
255 
256     void put(R)(scope R range) if(isForwardRangeOf!(R, E)) {
257         import std.range.primitives: walkLength, save;
258 
259         long index = length;
260         expand(length + () @trusted { return range.save.walkLength; }());
261 
262         foreach(element; range) {
263             const safeIndex = toSizeT(index++);
264             static if(!isElementMutable) {
265                 assert(_elements[safeIndex] == E.init,
266                        "Assigning to non default initialised non mutable member");
267             }
268             () @trusted { mutableElements[safeIndex] = element; }();
269         }
270     }
271 
272     /// Returns a slice
273     auto opSlice(this This)() scope return {
274         return _elements[0 .. length.toSizeT];
275     }
276 
277     /// Returns a slice
278     auto opSlice(this This)(long start, long end) scope return {
279         if(start < 0 || start >= length)
280             mixin(throwBoundsException);
281 
282         if(end < 0 || end >= length)
283             mixin(throwBoundsException);
284 
285         return _elements[start.toSizeT .. end.toSizeT];
286     }
287 
288     long opDollar() const {
289         return length;
290     }
291 
292     static if(isElementMutable) {
293         /// Assign all elements to the given value
294         void opSliceAssign(E value) {
295             _elements[] = value;
296         }
297     }
298 
299 
300     static if(isElementMutable) {
301         /// Assign all elements in the given range to the given value
302         void opSliceAssign(E value, long start, long end) {
303             if(start < 0 || start >= length)
304                 mixin(throwBoundsException);
305 
306             if(end < 0 || end >= length)
307                 mixin(throwBoundsException);
308 
309             _elements[start.toSizeT .. end.toSizeT] = value;
310         }
311     }
312 
313     static if(isElementMutable) {
314         /// Assign all elements using the given operation and the given value
315         void opSliceOpAssign(string op)(E value) scope {
316             foreach(ref elt; _elements)
317                 mixin(`elt ` ~ op ~ `= value;`);
318         }
319     }
320 
321     static if(isElementMutable) {
322         /// Assign all elements in the given range  using the given operation and the given value
323         void opSliceOpAssign(string op)(E value, long start, long end) scope {
324             if(start < 0 || start >= length)
325                 mixin(throwBoundsException);
326 
327             if(end < 0 || end >= length)
328                 mixin(throwBoundsException);
329 
330             foreach(ref elt; _elements[start.toSizeT .. end.toSizeT])
331                 mixin(`elt ` ~ op ~ `= value;`);
332         }
333     }
334 
335     bool opCast(U)() const scope if(is(U == bool)) {
336         return length > 0;
337     }
338 
339     static if(is(Unqual!E == char)) {
340         // return a null-terminated C string
341         auto stringz(this This)() return scope {
342             if(capacity == length) reserve(length + 1);
343 
344             static if(!isElementMutable) {
345                 assert(_elements[length.toSizeT] == E.init || _elements[length.toSizeT] == 0,
346                        "Assigning to non default initialised non mutable member");
347             }
348 
349             () @trusted { mutableElements[length.toSizeT] = 0; }();
350 
351             return &_elements[0];
352         }
353     }
354 
355     auto ptr(this This)() return scope {
356         return &_elements[0];
357     }
358 
359 private:
360 
361     E[] _elements;
362     long _length;
363 
364     static if(isSingleton!Allocator)
365         alias _allocator = Allocator.instance;
366     else static if(isTheAllocator!Allocator)
367         alias _allocator = theAllocator;
368     else
369         Allocator _allocator;
370 
371     E[] createVector(long length) scope {
372         import stdx.allocator: makeArray;
373         return () @trusted { return _allocator.makeArray!E(length.toSizeT); }();
374     }
375 
376     void fromElements(E[] elements) {
377 
378         _elements = createVector(elements.length);
379         () @trusted { (cast(MutE[]) _elements)[] = elements[]; }();
380         _length = elements.length;
381     }
382 
383     void expand(long newLength) scope {
384         expandMemory(newLength);
385         _length = newLength;
386     }
387 
388     void expandMemory(long newLength) scope {
389         import stdx.allocator: expandArray;
390 
391         if(newLength > capacity) {
392             if(length == 0)
393                 _elements = createVector(newLength);
394             else {
395                 const newCapacity = (newLength * 3) / 2;
396                 const delta = newCapacity - capacity;
397                 () @trusted { _allocator.expandArray(mutableElements, delta.toSizeT); }();
398             }
399         }
400     }
401 
402     ref MutE[] mutableElements() scope return @system {
403         auto ptr = &_elements;
404         return *(cast(MutE[]*) ptr);
405     }
406 }
407 
408 static if (__VERSION__ >= 2082) // version identifier D_Exceptions was added in 2.082
409 {
410     version (D_Exceptions)
411         private enum haveExceptions = true;
412     else
413         private enum haveExceptions = false;
414 }
415 else
416 {
417     version (D_BetterC)
418         private enum haveExceptions = false;
419     else
420         private enum haveExceptions = true;
421 }
422 
423 static if (haveExceptions)
424 {
425     private static immutable boundsException = new BoundsException("Out of bounds index");
426     private enum throwBoundsException = q{throw boundsException;};
427     class BoundsException: Exception {
428         import std.exception: basicExceptionCtors;
429 
430         mixin basicExceptionCtors;
431     }
432 }
433 else
434 {
435     private enum throwBoundsException = q{assert(0, "Out of bounds index");};
436 }
437 
438 private template isInputRangeOf(R, E) {
439     import std.range.primitives: isInputRange;
440     enum isInputRangeOf = isInputRange!R && canAssignFrom!(R, E);
441 }
442 
443 private template isForwardRangeOf(R, E) {
444     import std.range.primitives: isForwardRange;
445     enum isForwardRangeOf = isForwardRange!R && canAssignFrom!(R, E);
446 }
447 
448 
449 private template canAssignFrom(R, E) {
450     enum canAssignFrom = is(typeof({
451         import automem.vector: frontNoAutoDecode;
452         E element = R.init.frontNoAutoDecode;
453     }));
454 }
455 private size_t toSizeT(long length) @safe @nogc pure nothrow {
456     static if(size_t.sizeof < long.sizeof)
457         assert(length < cast(long) size_t.max);
458     return cast(size_t) length;
459 }
460 
461 // Because autodecoding is fun
462 private template ElementType(R) {
463     import std.traits: isSomeString;
464 
465     static if(isSomeString!R) {
466         alias ElementType = typeof(R.init[0]);
467     } else {
468         import std.range.primitives: ElementType_ = ElementType;
469         alias ElementType = ElementType_!R;
470     }
471 }
472 
473 @("ElementType")
474 @safe pure unittest {
475     import automem.vector: ElementType;
476     static assert(is(ElementType!(int[]) == int));
477     static assert(is(ElementType!(char[]) == char));
478     static assert(is(ElementType!(wchar[]) == wchar));
479     static assert(is(ElementType!(dchar[]) == dchar));
480 }
481 
482 
483 // More fun with autodecoding
484 private auto frontNoAutoDecode(R)(R range) {
485     import std.traits: isSomeString;
486 
487     static if(isSomeString!R)
488         return range[0];
489     else {
490         import std.range.primitives: front;
491         return range.front;
492     }
493 }