The OpenD Programming Language

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: isGlobal;
9 import std.range.primitives: isInputRange;
10 import std.experimental.allocator: theAllocator;
11 import std.experimental.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             // reallocating is ok if only allocating now
83             () @trusted { this = range; }();
84         }
85 
86     } else static if(isCopyable!Allocator) {
87 
88         this(Allocator allocator, E[] elements...) {
89             _allocator = allocator;
90             fromElements(elements);
91         }
92 
93         this(R)(Allocator allocator, R range) if(isInputRangeOf!(R, E)) {
94             _allocator = allocator;
95             this = range;
96         }
97     } else {
98 
99         this(R)(R range) if(isInputRangeOf!(R, E)) {
100             this = range;
101         }
102 
103     }
104 
105     this(this) scope {
106 
107         auto oldElements = _elements;
108         _elements = createVector(_elements.length);
109 
110         static if(isElementMutable)
111             _elements[0 .. length.toSizeT] = oldElements[0 .. length.toSizeT];
112         else
113             () @trusted {
114                 cast(MutE[])(_elements)[0 .. length.toSizeT] = oldElements[0 .. length.toSizeT];
115             }();
116     }
117 
118     ~this() {
119         free;
120     }
121 
122     /// Frees the memory and returns to .init
123     void free() scope {
124         import std.experimental.allocator: dispose;
125 
126         // dispose is @system for theAllocator
127         () @trusted {
128             static if(is(E == immutable))
129                 auto elements = cast(MutE[]) _elements;
130             else
131                 alias elements = _elements;
132 
133             _allocator.dispose(elements);
134         }();
135 
136         clear;
137     }
138 
139     static if(isElementMutable) {
140         /// Pops the front element off
141         void popFront() {
142             foreach(i; 0 .. length - 1)
143                 _elements[i.toSizeT] = _elements[i.toSizeT + 1];
144 
145             popBack;
146         }
147     }
148 
149     /// Pops the last element off
150     void popBack() {
151         --_length;
152     }
153 
154     /// If the vector is empty
155     bool empty() const {
156         return length == 0;
157     }
158 
159     /// The current length of the vector
160     @property long length() const {
161         return _length;
162     }
163 
164     /// Set the length of the vector
165     @property void length(long newLength) {
166         if(capacity < newLength) reserve(newLength);
167         _length = newLength;
168     }
169 
170     /// The current memory capacity of the vector
171     long capacity() const {
172         return _elements.length;
173     }
174 
175     /// Clears the vector, resulting in an empty one
176     void clear() {
177         _length = 0;
178     }
179 
180     /// Reserve memory to avoid allocations when appending
181     void reserve(long newLength) {
182         expandMemory(newLength);
183     }
184 
185     static if(isElementMutable) {
186 
187         /// Shrink to fit the current length. Returns if shrunk.
188         bool shrink() scope {
189             return shrink(length);
190         }
191 
192         /**
193            Shrink to fit the new length given. Returns if shrunk.
194            Cannot be made @safe due to reallocation causing pointers
195            to dangle.
196         */
197         bool shrink(long newLength) scope {
198             import std.experimental.allocator: shrinkArray;
199 
200             const delta = capacity - newLength;
201             const shrunk = _allocator.shrinkArray(_elements, delta.toSizeT);
202             _length = newLength;
203 
204             return shrunk;
205         }
206     }
207 
208     /// Access the ith element. Can throw RangeError.
209     ref inout(E) opIndex(long i) return scope inout {
210         if(i < 0 || i >= length)
211             mixin(throwBoundsException);
212         return _elements[i.toSizeT];
213     }
214 
215     /// Returns a new vector after appending to the given vector.
216     Vector opBinary(string s, T)(auto ref T other) const
217         if(s == "~" && is(Unqual!T == Vector))
218     {
219         import std.range: chain;
220         // opSlice is @system , but it's ok here because we're not
221         // returning the slice but concatenating.
222         return Vector(chain(() @trusted { return this[]; }(),
223                             () @trusted { return other[]; }()));
224     }
225 
226     /// Assigns from a range.
227     void opAssign(R)(R range) scope if(isForwardRangeOf!(R, E)) {
228         import std.range.primitives: walkLength, save;
229 
230         expand(range.save.walkLength);
231 
232         long i = 0;
233         foreach(element; range)
234             _elements[toSizeT(i++)] = element;
235     }
236 
237     // make it an output range
238 
239 
240     /// Append to the vector
241     void opOpAssign(string op)
242                    (E other)
243         scope
244         if(op == "~")
245     {
246         put(other);
247     }
248 
249     void put(E other) {
250 
251         expand(length + 1);
252 
253         const lastIndex = (length - 1).toSizeT;
254 
255         static if(isElementMutable)
256             _elements[lastIndex] = other;
257         else {
258             assert(_elements[lastIndex] == E.init,
259                    "Assigning to non default initialised non mutable member");
260 
261             () @trusted { mutableElements[lastIndex] = other; }();
262         }
263     }
264 
265     /// Append to the vector from a range
266     void opOpAssign(string op, R)
267                    (scope R range)
268         scope
269         if(op == "~" && isForwardRangeOf!(R, E))
270     {
271         put(range);
272     }
273 
274     void put(R)(scope R range) if(isLengthRangeOf!(R, E)) {
275         import std.range.primitives: walkLength, save;
276 
277         long index = length;
278 
279         static if(hasLength!R)
280             const rangeLength = range.length;
281         else
282             const rangeLength = range.save.walkLength;
283 
284         expand(length + rangeLength);
285 
286         foreach(element; range) {
287             const safeIndex = toSizeT(index++);
288             static if(!isElementMutable) {
289                 assert(_elements[safeIndex] == E.init,
290                        "Assigning to non default initialised non mutable member");
291             }
292 
293             static if(isElementMutable)
294                 _elements[safeIndex] = element;
295             else {
296                 assert(_elements[safeIndex] == E.init,
297                        "Assigning to non default initialised non mutable member");
298                 () @trusted { mutableElements[safeIndex] = element; }();
299             }
300         }
301     }
302 
303     /**
304        Return a forward range of the vector contents.
305        Negative `end` values work like in Python.
306      */
307     auto range(this This)(in long start = 0, long end = -1) return scope
308         in(start >= 0)
309         in(end <= length)
310         do
311     {
312         import std.range.primitives: isForwardRange;
313 
314         static struct Range {
315             private This* self;
316             private long index;
317             private long end;
318 
319             Range save() {
320                 return this;
321             }
322 
323             auto front() {
324                 return (*self)[index];
325             }
326 
327             void popFront() {
328                 ++index;
329             }
330 
331             bool empty() const {
332                 const comp = end < 0 ? length + end + 1 : end;
333                 return index >= comp;
334             }
335 
336             auto length() const {
337                 return self.length;
338             }
339         }
340 
341         static assert(isForwardRange!Range);
342 
343         // FIXME - why isn't &this @safe?
344         return Range(() @trusted { return &this; }(),
345                      start,
346                      end);
347     }
348 
349     /**
350        Returns a slice.
351        @system because the pointer in the slice might dangle.
352      */
353     auto opSlice(this This)() @system scope return {
354         return _elements[0 .. length.toSizeT];
355     }
356 
357     /**
358        Returns a slice.
359        @system because the pointer in the slice might dangle.
360      */
361     auto opSlice(this This)(long start, long end) @system scope return {
362         if(start < 0 || start >= length)
363             mixin(throwBoundsException);
364 
365         if(end < 0 || end > length)
366             mixin(throwBoundsException);
367 
368         return _elements[start.toSizeT .. end.toSizeT];
369     }
370 
371     long opDollar() const {
372         return length;
373     }
374 
375     static if(isElementMutable) {
376         /// Assign all elements to the given value
377         void opSliceAssign(E value) {
378             _elements[] = value;
379         }
380     }
381 
382 
383     static if(isElementMutable) {
384         /// Assign all elements in the given range to the given value
385         void opSliceAssign(E value, long start, long end) {
386             if(start < 0 || start >= length)
387                 mixin(throwBoundsException);
388 
389             if(end < 0 || end >= length)
390                 mixin(throwBoundsException);
391 
392             _elements[start.toSizeT .. end.toSizeT] = value;
393         }
394     }
395 
396     static if(isElementMutable) {
397         /// Assign all elements using the given operation and the given value
398         void opSliceOpAssign(string op)(E value) scope {
399             foreach(ref elt; _elements)
400                 mixin(`elt ` ~ op ~ `= value;`);
401         }
402     }
403 
404     static if(isElementMutable) {
405         /// Assign all elements in the given range  using the given operation and the given value
406         void opSliceOpAssign(string op)(E value, long start, long end) scope {
407             if(start < 0 || start >= length)
408                 mixin(throwBoundsException);
409 
410             if(end < 0 || end >= length)
411                 mixin(throwBoundsException);
412 
413             foreach(ref elt; _elements[start.toSizeT .. end.toSizeT])
414                 mixin(`elt ` ~ op ~ `= value;`);
415         }
416     }
417 
418     bool opCast(U)() const scope if(is(U == bool)) {
419         return length > 0;
420     }
421 
422     static if(is(Unqual!E == char)) {
423 
424         /**
425            Return a null-terminated C string
426            @system since appending is not @safe.
427          */
428         auto stringz(this This)() return scope @system {
429             if(capacity == length) reserve(length + 1);
430 
431             static if(isElementMutable) {
432                 _elements[length.toSizeT] = 0;
433             } else {
434                 assert(_elements[length.toSizeT] == E.init || _elements[length.toSizeT] == 0,
435                        "Assigning to non default initialised non mutable member");
436 
437                 () @trusted { mutableElements[length.toSizeT] = 0; }();
438             }
439 
440             return &_elements[0];
441         }
442     }
443 
444     auto ptr(this This)() return scope {
445         return &_elements[0];
446     }
447 
448     bool opEquals(R)(R range) scope const
449         if(isInputRangeOf!(R, E))
450     {
451         import std.array: empty, popFront, front;
452 
453         if(length == 0 && range.empty) return true;
454 
455         foreach(i; 0 .. length) {
456             if(range.empty) return false;
457             if(range.front != this[i]) return false;
458             range.popFront;
459         }
460 
461         return range.empty;
462     }
463 
464     bool opEquals(OtherAllocator)(auto ref scope const(Vector!(E, OtherAllocator)) other) const {
465         return this == other.range;
466     }
467 
468 private:
469 
470     E[] _elements;
471     long _length;
472 
473     static if(isSingleton!Allocator)
474         alias _allocator = Allocator.instance;
475     else static if(isTheAllocator!Allocator)
476         alias _allocator = theAllocator;
477     else
478         Allocator _allocator;
479 
480     E[] createVector(long length) scope {
481         import std.experimental.allocator: makeArray;
482         // theAllocator.makeArray is @system
483         return () @trusted { return _allocator.makeArray!E(length.toSizeT); }();
484     }
485 
486     void fromElements(E[] elements) {
487 
488         _elements = createVector(elements.length);
489 
490         static if(isElementMutable)
491             _elements[] = elements[];
492         else
493             () @trusted { (cast(MutE[]) _elements)[] = elements[]; }();
494 
495         _length = elements.length;
496     }
497 
498     void expand(long newLength) scope {
499         expandMemory(newLength);
500         _length = newLength;
501     }
502 
503 
504     // @system since reallocating can cause pointers to dangle
505     void expandMemory(long newLength) scope @system {
506         import std.experimental.allocator: expandArray;
507 
508         if(newLength > capacity) {
509             if(length == 0)
510                 _elements = createVector(newLength);
511             else {
512                 const newCapacity = (newLength * 3) / 2;
513                 const delta = newCapacity - capacity;
514                 _allocator.expandArray(mutableElements, delta.toSizeT);
515             }
516         }
517     }
518 
519     ref MutE[] mutableElements() scope return @system {
520         auto ptr = &_elements;
521         return *(cast(MutE[]*) ptr);
522     }
523 }
524 
525 
526 static if (__VERSION__ >= 2082) { // version identifier D_Exceptions was added in 2.082
527     version (D_Exceptions)
528         private enum haveExceptions = true;
529     else
530         private enum haveExceptions = false;
531 } else {
532     version (D_BetterC)
533         private enum haveExceptions = false;
534     else
535         private enum haveExceptions = true;
536 }
537 
538 
539 static if (haveExceptions) {
540     private static immutable boundsException = new BoundsException("Out of bounds index");
541     private enum throwBoundsException = q{throw boundsException;};
542     class BoundsException: Exception {
543         import std.exception: basicExceptionCtors;
544 
545         mixin basicExceptionCtors;
546     }
547 } else {
548     private enum throwBoundsException = q{assert(0, "Out of bounds index");};
549 }
550 
551 
552 private template isInputRangeOf(R, E) {
553     import std.range.primitives: isInputRange;
554     enum isInputRangeOf = isInputRange!R && canAssignFrom!(R, E);
555 }
556 
557 private template isForwardRangeOf(R, E) {
558     import std.range.primitives: isForwardRange;
559     enum isForwardRangeOf = isForwardRange!R && canAssignFrom!(R, E);
560 }
561 
562 
563 private enum hasLength(R) = is(typeof({
564     import std.traits: isIntegral;
565     auto length = R.init.length;
566     static assert(isIntegral!(typeof(length)));
567 }));
568 
569 
570 private enum isLengthRangeOf(R, E) = isForwardRangeOf!(R, E) || hasLength!R;
571 
572 private template canAssignFrom(R, E) {
573     enum canAssignFrom = is(typeof({
574         import automem.vector: frontNoAutoDecode;
575         E element = R.init.frontNoAutoDecode;
576     }));
577 }
578 
579 private size_t toSizeT(long length) @safe @nogc pure nothrow {
580     static if(size_t.sizeof < long.sizeof)
581         assert(length < cast(long) size_t.max);
582     return cast(size_t) length;
583 }
584 
585 // Because autodecoding is fun
586 private template ElementType(R) {
587     import std.traits: isSomeString;
588 
589     static if(isSomeString!R) {
590         alias ElementType = typeof(R.init[0]);
591     } else {
592         import std.range.primitives: ElementType_ = ElementType;
593         alias ElementType = ElementType_!R;
594     }
595 }
596 
597 @("ElementType")
598 @safe pure unittest {
599     import automem.vector: ElementType;
600     static assert(is(ElementType!(int[]) == int));
601     static assert(is(ElementType!(char[]) == char));
602     static assert(is(ElementType!(wchar[]) == wchar));
603     static assert(is(ElementType!(dchar[]) == dchar));
604 }
605 
606 
607 // More fun with autodecoding
608 private auto frontNoAutoDecode(R)(R range) {
609     import std.traits: isSomeString;
610 
611     static if(isSomeString!R)
612         return range[0];
613     else {
614         import std.range.primitives: front;
615         return range.front;
616     }
617 }
618 
619 
620 void checkAllocator(T)() {
621     import std.experimental.allocator: dispose, shrinkArray, makeArray, expandArray;
622     import std.traits: hasMember;
623 
624     static if(hasMember!(T, "instance"))
625         alias allocator = T.instance;
626     else
627         T allocator;
628 
629     void[] bytes;
630     allocator.dispose(bytes);
631 
632     int[] ints = allocator.makeArray!int(42);
633 
634     allocator.shrinkArray(ints, size_t.init);
635     allocator.expandArray(ints, size_t.init);
636 }
637 enum isAllocator(T) = is(typeof(checkAllocator!T));
638 
639 
640 @("isAllocator")
641 @safe @nogc pure unittest {
642     import std.experimental.allocator.mallocator: Mallocator;
643     import test_allocator: TestAllocator;
644 
645     static assert( isAllocator!Mallocator);
646     static assert( isAllocator!TestAllocator);
647     static assert(!isAllocator!int);
648     static assert( isAllocator!(typeof(theAllocator)));
649 }