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 }