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] = oldElements[0 .. length];
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];
105     }
106 
107     /// When implemented, pops the front element off
108     void popFront() {
109         throw new Exception("Not implemented yet");
110     }
111 
112     /// Pops the last element off
113     void popBack() {
114         --_length;
115     }
116 
117     /// If the vector is empty
118     bool empty() const {
119         return length == 0;
120     }
121 
122     /// The current length of the vector
123     @property long length() const {
124         return _length;
125     }
126 
127     /// Set the length of the vector
128     @property void length(long newLength) {
129         if(capacity < newLength) reserve(newLength);
130         _length = newLength;
131     }
132 
133     /// The current memory capacity of the vector
134     long capacity() const {
135         return _elements.length;
136     }
137 
138     /// Clears the vector, resulting in an empty one
139     void clear() {
140         _length = 0;
141     }
142 
143     /// Reserve memory to avoid allocations when appending
144     void reserve(long newLength) {
145         expandMemory(newLength);
146     }
147 
148     /// Shrink to fit the current length. Returns if shrunk.
149     bool shrink() scope {
150         return shrink(length);
151     }
152 
153     /// Shrink to fit the new length given. Returns if shrunk.
154     bool shrink(long newLength) scope {
155         import stdx.allocator: shrinkArray;
156 
157         const delta = capacity - newLength;
158         const shrunk = () @trusted { return _allocator.shrinkArray(_elements, delta); }();
159         _length = newLength;
160 
161         return shrunk;
162     }
163 
164     /// Access the ith element. Can throw RangeError.
165     ref inout(E) opIndex(long i) inout {
166         if(i < 0 || i >= length)
167             throw boundsException;
168         return _elements[i];
169     }
170 
171     /// Returns a new vector after appending to the given vector.
172     Vector opBinary(string s, T)(auto ref T other) const if(s == "~" && is(Unqual!T == Vector)) {
173         import std.range: chain;
174         return Vector(chain(this[], other[]));
175     }
176 
177     /// Assigns from a range.
178     void opAssign(R)(R range) scope if(isForwardRangeOf!(R, E)) {
179         import std.range.primitives: walkLength, save;
180 
181         expand(range.save.walkLength);
182 
183         long i = 0;
184         foreach(element; range)
185             _elements[i++] = element;
186     }
187 
188     /// Append to the vector
189     void opOpAssign(string op)
190                    (E other)
191         scope
192         if(op == "~")
193     {
194         expand(length + 1);
195         _elements[length - 1] = other;
196     }
197 
198     /// Append to the vector
199     void opOpAssign(string op, R)
200                    (R range)
201         scope
202         if(op == "~" && isForwardRangeOf!(R, E))
203     {
204         import std.range.primitives: walkLength, save;
205 
206         long index = length;
207         expand(length + range.save.walkLength);
208 
209         foreach(element; range)
210             _elements[index++] = element;
211     }
212 
213     /// Returns a slice
214     scope auto opSlice(this This)() {
215         return _elements[0 .. length];
216     }
217 
218     /// Returns a slice
219     scope auto opSlice(this This)(long start, long end) {
220         if(start < 0 || start >= length)
221             throw boundsException;
222 
223         if(end < 0 || end >= length)
224             throw boundsException;
225 
226         return _elements[start .. end];
227     }
228 
229     long opDollar() const {
230         return length;
231     }
232 
233     /// Assign all elements to the given value
234     void opSliceAssign(E value) {
235         _elements[] = value;
236     }
237 
238     /// Assign all elements in the given range to the given value
239     void opSliceAssign(E value, long start, long end) {
240         if(start < 0 || start >= length)
241             throw boundsException;
242 
243         if(end < 0 || end >= length)
244             throw boundsException;
245 
246         _elements[start .. end] = value;
247     }
248 
249     /// Assign all elements using the given operation and the given value
250     void opSliceOpAssign(string op)(E value) scope {
251         foreach(ref elt; _elements)
252             mixin(`elt ` ~ op ~ `= value;`);
253     }
254 
255     /// Assign all elements in the given range  using the given operation and the given value
256     void opSliceOpAssign(string op)(E value, long start, long end) scope {
257         if(start < 0 || start >= length)
258             throw boundsException;
259 
260         if(end < 0 || end >= length)
261             throw boundsException;
262 
263         foreach(ref elt; _elements[start .. end])
264             mixin(`elt ` ~ op ~ `= value;`);
265     }
266 
267 private:
268 
269     E[] _elements;
270     long _length;
271 
272     static if(isSingleton!Allocator)
273         alias _allocator = Allocator.instance;
274     else static if(isTheAllocator!Allocator)
275         alias _allocator = theAllocator;
276     else
277         Allocator _allocator;
278 
279     E[] createVector(long length) {
280         import stdx.allocator: makeArray;
281         return () @trusted { return _allocator.makeArray!E(length); }();
282     }
283 
284     void fromElements(E[] elements) {
285         _elements = createVector(elements.length);
286         _elements[] = elements[];
287         _length = elements.length;
288     }
289 
290     void expand(long newLength) scope {
291         expandMemory(newLength);
292         _length = newLength;
293     }
294 
295     void expandMemory(long newLength) scope {
296         import stdx.allocator: expandArray;
297 
298         if(newLength > capacity) {
299             if(length == 0)
300                 _elements = createVector(newLength);
301             else {
302                 const newCapacity = (newLength * 3) / 2;
303                 const delta = newCapacity - capacity;
304                 () @trusted { _allocator.expandArray(_elements, delta); }();
305             }
306         }
307     }
308 }
309 
310 private static immutable boundsException = new BoundsException("Out of bounds index");
311 
312 class BoundsException: Exception {
313     import std.exception: basicExceptionCtors;
314 
315     mixin basicExceptionCtors;
316 }
317 
318 private template isInputRangeOf(R, E) {
319     import std.range.primitives: isInputRange, ElementType;
320     import std.traits: Unqual;
321 
322     enum isInputRangeOf = isInputRange!R && is(Unqual!(ElementType!R) == E);
323 }
324 
325 private template isForwardRangeOf(R, E) {
326     import std.range.primitives: isForwardRange, ElementType;
327     import std.traits: Unqual;
328 
329     enum isForwardRangeOf = isForwardRange!R && is(Unqual!(ElementType!R) == E);
330 }