1 /**
2  * Dynamic Array
3  * Copyright: © 2015 Economic Modeling Specialists, Intl.
4  * Authors: Brian Schott
5  * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6  */
7 
8 module spasm.rt.array;
9 import spasm.rt.memory;
10 import spasm.rt.gc : SpasmGCAllocator;
11 
12 @safe:
13 nothrow:
14 
15 /* an optimized version of DynamicArray when T is an pointer
16  *
17  * to reduce template bloat all PointerArray!T* are backed by a
18  * DynamicArray!(void*)
19  */
20 struct PointerArray(T, Allocator = SpasmGCAllocator) if (is(T : U*, U)) {
21   static if (!is(Allocator == SpasmGCAllocator)) {
22     private DynamicArray!(void*, Allocator) array = void;
23     this(ref Allocator allocator) {
24       array = DynamicArray!(void*, Allocator)(allocator);
25     }
26   } else {
27     private DynamicArray!(void*, Allocator) array;
28   }
29 
30   alias array this;
31 
32   /// Slice operator overload
33   pragma(inline, true) auto opSlice(this This)() @nogc
34   {
35     return opSlice!(This)(0, l);
36   }
37 
38   /// ditto
39   pragma(inline, true) auto opSlice(this This)(size_t a, size_t b) @nogc @trusted
40   {
41     alias ET = ContainerElementType!(This, T);
42     return cast(ET[]) array.arr[a .. b];
43   }
44 
45   /// Index operator overload
46   pragma(inline, true) auto opIndex(this This)(size_t i) @nogc
47   {
48     return cast(T)array[i];
49   }
50 
51   void insertBack(T value) {
52     array.insertBack(cast(void*)value);
53   }
54 
55   void shrinkTo(int idx) {
56     array.shrinkTo(idx);
57   }
58 
59   alias insert = insertBack;
60 
61   /// ditto
62   alias insertAnywhere = insertBack;
63 
64   /// ditto
65   alias put = insertBack;
66 
67   /// Index assignment support
68   void opIndexAssign(T value, size_t i) @nogc
69   {
70     array.arr[i] = cast(void*)value;
71   }
72 
73   /// Slice assignment support
74   void opSliceAssign(T value) @nogc
75   {
76     array.arr[0 .. l] = cast(void*)value;
77   }
78 
79   /// ditto
80   void opSliceAssign(T value, size_t i, size_t j) @nogc
81   {
82     array.arr[i .. j] = cast(void*)value;
83   }
84   auto ref T front() pure @property @trusted
85   {
86     return cast(T)array.front();
87   }
88 
89   /// Returns: the back element of the DynamicArray.
90   auto ref T back() pure @property @trusted
91   {
92     return cast(T)array.back();
93   }
94 
95   size_t length() const nothrow pure @property @safe @nogc
96   {
97     return array.l;
98   }
99   /// Ditto
100   alias opDollar = length;
101 
102   void remove(const size_t i) {
103     array.remove(i);
104   }
105 }
106 /**
107  * Array that is able to grow itself when items are appended to it. Uses
108  * malloc/free/realloc to manage its storage.
109  *
110  * Params:
111  *     T = the array element type
112  *     Allocator = the allocator to use. Defaults to `Mallocator`.
113  */
114 
115 struct DynamicArray(T, Allocator = SpasmGCAllocator)
116 {
117   static if (!is(Allocator == SpasmGCAllocator)) {
118     Allocator* allocator;
119     @trusted this(ref Allocator allocator) {
120       this.allocator = &allocator;
121     }
122   }
123 
124 	this(this) @disable;
125 
126 	private import stdx.allocator.common : stateSize;
127 
128 	static if (is(typeof((T[] a, const T[] b) => a[0 .. b.length] = b[0 .. $])))
129 	{
130 		/// Either `const(T)` or `T`.
131 		alias AppendT = const(T);
132 
133 		/// Either `const(typeof(this))` or `typeof(this)`.
134 		alias AppendTypeOfThis = const(typeof(this));
135 	}
136 	else
137 	{
138 		alias AppendT = T;
139 		alias AppendTypeOfThis = typeof(this);
140 	}
141 
142 	@trusted ~this()
143 	{
144 		if (arr is null)
145 			return;
146 
147 		static if ((is(T == struct) || is(T == union))
148 			&& __traits(hasMember, T, "__xdtor"))
149 		{
150 			foreach (ref item; arr[0 .. l])
151 			{
152 				item.__xdtor();
153 			}
154 		}
155 		allocator.deallocate(arr);
156 	}
157 
158 	/// Slice operator overload
159 	pragma(inline, true)
160 	auto opSlice(this This)() @nogc
161 	{
162 		return opSlice!(This)(0, l);
163 	}
164 
165 	/// ditto
166 	pragma(inline, true)
167 	auto opSlice(this This)(size_t a, size_t b) @nogc
168 	{
169 		alias ET = ContainerElementType!(This, T);
170 		return cast(ET[]) arr[a .. b];
171 	}
172 
173 	/// Index operator overload
174 	pragma(inline, true)
175 	auto opIndex(this This)(size_t i) @nogc
176 	{
177 		return opSlice!(This)(i, i + 1)[0];
178 	}
179 
180 	/**
181 	 * Inserts the given value into the end of the array.
182 	 */
183 	@trusted void insertBack(T value)
184 	{
185 		if (arr.length == 0)
186 		{
187 			arr = allocator.make!(typeof(arr))(4);
188 		}
189 		else if (l >= arr.length)
190 		{
191 			immutable size_t c = arr.length > 512 ? arr.length + 1024 : arr.length << 1;
192 			void[] a = cast(void[]) arr;
193       import stdx.allocator.common : reallocate;
194 			allocator.reallocate(a, c * T.sizeof);
195 			arr = cast(typeof(arr)) a;
196 		}
197 		import std.traits: hasElaborateAssign, hasElaborateDestructor;
198 		static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T))
199 		{
200 			// If a destructor is run before blit or assignment involves
201 			// more than just a blit, ensure that arr[l] is in a valid
202 			// state before assigning to it.
203 			import core.stdc.string : memcpy, memset;
204 			const init = typeid(T).initializer();
205 			if (init.ptr is null) // null pointer means initialize to 0s
206 				(() @trusted => memset(arr.ptr + l, 0, T.sizeof))();
207 			else
208 				(() @trusted => memcpy(arr.ptr + l, init.ptr, T.sizeof))();
209 		}
210 		emplace(arr[l++], value);
211 	}
212 
213 	/// ditto
214 	alias insert = insertBack;
215 
216 	/// ditto
217 	alias insertAnywhere = insertBack;
218 
219 	/// ditto
220 	alias put = insertBack;
221 
222 	/**
223 	 * ~= operator overload
224 	 */
225 	scope ref typeof(this) opOpAssign(string op)(T value) if (op == "~")
226 	{
227 		insert(value);
228 		return this;
229 	}
230 
231 	/**
232 	* ~= operator overload for an array of items
233 	*/
234 	scope ref typeof(this) opOpAssign(string op, bool checkForOverlap = true)(AppendT[] rhs)
235 		if (op == "~" && !is(T == AppendT[]))
236 	{
237 		// Disabling checkForOverlap when this function is called from opBinary!"~"
238 		// is not just for efficiency, but to avoid circular function calls that
239 		// would prevent inference of @nogc, etc.
240 		static if (checkForOverlap)
241 		if ((() @trusted => arr.ptr <= rhs.ptr && arr.ptr + arr.length > rhs.ptr)())
242 		{
243 			// Special case where rhs is a slice of this array.
244 			this = this ~ rhs;
245 			return this;
246 		}
247 		reserve(l + rhs.length);
248 		import std.traits: hasElaborateAssign, hasElaborateDestructor;
249 		static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T))
250 		{
251 			foreach (ref value; rhs)
252 				emplace(arr[l++], value);
253 		}
254 		else
255 		{
256 			arr[l .. l + rhs.length] = rhs[0 .. rhs.length];
257 			l += rhs.length;
258 		}
259 		return this;
260 	}
261 
262 	/// ditto
263 	scope ref typeof(this) opOpAssign(string op)(ref AppendTypeOfThis rhs)
264 		if (op == "~")
265 	{
266 		return this ~= rhs.arr[0 .. rhs.l];
267 	}
268 
269 	/**
270 	 * ~ operator overload
271 	 */
272 	typeof(this) opBinary(string op)(ref AppendTypeOfThis other) if (op == "~")
273 	{
274 		typeof(this) ret;
275 		ret.reserve(l + other.l);
276 		ret.opOpAssign!("~", false)(arr[0 .. l]);
277 		ret.opOpAssign!("~", false)(other.arr[0 .. other.l]);
278 		return ret;
279 	}
280 
281 	/// ditto
282 	typeof(this) opBinary(string op)(AppendT[] values) if (op == "~")
283 	{
284 		typeof(this) ret;
285 		ret.reserve(l + values.length);
286 		ret.opOpAssign!("~", false)(arr[0 .. l]);
287 		ret.opOpAssign!("~", false)(values);
288 		return ret;
289 	}
290 
291 	/**
292 	 * Ensures sufficient capacity to accommodate `n` elements.
293 	 */
294 	@trusted void reserve(size_t n)
295 	{
296 		if (arr.length >= n)
297 			return;
298 		if (arr.ptr is null)
299 		{
300 			size_t c = 4;
301 			if (c < n)
302 				c = n;
303 			arr = allocator.make!(typeof(arr))(c);
304 		}
305 		else
306 		{
307 			size_t c = arr.length > 512 ? arr.length + 1024 : arr.length << 1;
308 			if (c < n)
309 				c = n;
310 			void[] a = cast(void[]) arr;
311       import stdx.allocator.common : reallocate;
312       allocator.reallocate(a, c * T.sizeof);
313 			arr = cast(typeof(arr)) a;
314 		}
315 	}
316 
317 	static if (is(typeof({T value;}))) // default construction is allowed
318 	{
319 		/**
320 		 * Change the array length.
321 		 * When growing, initialize new elements to the default value.
322 		 */
323 		void resize(size_t n)
324 		{
325 			resize(n, T.init);
326 		}
327 	}
328 
329 	/**
330 	 * Change the array length.
331 	 * When growing, initialize new elements to the given value.
332 	 */
333 	void resize(size_t n, T value)
334 	{
335 		if (arr.length < n)
336 			reserve(n);
337 
338 		if (l < n) // Growing?
339 		{
340 			import std.traits: hasElaborateAssign, hasElaborateDestructor;
341 			static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T))
342 			{
343 				foreach (i; l..n)
344 					emplace(arr[l], value);
345 			}
346 			else
347 				arr[l..n] = value;
348 		}
349 		else
350 		{
351 			static if ((is(T == struct) || is(T == union))
352 				&& __traits(hasMember, T, "__xdtor"))
353 			{
354 				foreach (i; n..l)
355 					arr[i].__xdtor();
356 			}
357 		}
358 
359 		l = n;
360 	}
361 
362 	/**
363 	 * Remove the item at the given index from the array.
364 	 */
365 	void remove(const size_t i)
366 	{
367     assert(i < this.l);
368     auto next = i + 1;
369     while (next < this.l)
370     {
371       arr[next - 1] = arr[next];
372       ++next;
373     }
374 
375     --l;
376     static if ((is(T == struct) || is(T == union)) && __traits(hasMember, T, "__xdtor"))
377     {
378       arr[l].__xdtor();
379     }
380 	}
381 
382 	/**
383 	 * Removes the last element from the array.
384 	 */
385 	void removeBack()
386 	{
387     assert(l > 0);
388     --l;
389     static if ((is(T == struct) || is(T == union)) && __traits(hasMember, T, "__xdtor"))
390     {
391       arr[l].__xdtor();
392     }
393 	}
394 
395   void shrinkTo(const size_t nl) {
396     assert(l >= nl);
397     static if ((is(T == struct) || is(T == union)) && __traits(hasMember, T, "__xdtor"))
398     {
399       foreach_reverse(i; nl..l)
400         arr[i].__xdtor();
401     }
402 
403     l = nl;
404   }
405 
406 	/// Index assignment support
407 	void opIndexAssign(T value, size_t i) @nogc
408 	{
409 		arr[i] = value;
410 	}
411 
412 	/// Slice assignment support
413 	void opSliceAssign(T value) @nogc
414 	{
415 		arr[0 .. l] = value;
416 	}
417 
418 	/// ditto
419 	void opSliceAssign(T value, size_t i, size_t j) @nogc
420 	{
421 		arr[i .. j] = value;
422 	}
423 
424 	/// Returns: the number of items in the array
425 	size_t length() const nothrow pure @property @safe @nogc { return l; }
426 
427 	/// Ditto
428 	alias opDollar = length;
429 
430 	/// Returns: whether or not the DynamicArray is empty.
431 	bool empty() const nothrow pure @property @safe @nogc { return l == 0; }
432 
433 	/**
434 	 * Returns: a slice to the underlying array.
435 	 *
436 	 * As the memory of the array may be freed, access to this array is
437 	 * highly unsafe.
438 	 */
439 	auto ptr(this This)() @nogc @property
440 	{
441 		alias ET = ContainerElementType!(This, T);
442 		return cast(ET) arr.ptr;
443 	}
444 
445 	/// Returns: the front element of the DynamicArray.
446 	auto ref T front() pure @property
447 	{
448 		return arr[0];
449 	}
450 
451 	/// Returns: the back element of the DynamicArray.
452 	auto ref T back() pure @property
453 	{
454 		return arr[l - 1];
455 	}
456 
457 private:
458 
459 	@trusted static void emplace(ref ContainerStorageType!T target, ref AppendT source)
460 	{
461 		(cast(void[])((&target)[0..1]))[] = cast(void[])((&source)[0..1]);
462 		// static if (__traits(hasMember, T, "__xpostblit"))
463 		// 	target.__xpostblit();
464 	}
465 
466 	enum bool useGC = false;
467 	ContainerStorageType!(T)[] arr;
468 	size_t l;
469 }
470 
471 template removePred(alias pred) {
472   auto removePred(T)(ref DynamicArray!T arr) {
473     size_t target = 0;
474     auto len = arr.l < arr.arr.length ? arr.l : arr.arr.length;
475     foreach (i; 0 .. len)
476     {
477       if (pred(arr.arr[i]))
478       {
479         static if ((is(T == struct) || is(T == union)) && __traits(hasMember, T, "__xdtor"))
480         {
481           arr.arr[l].__xdtor();
482         }
483         continue;
484       }
485       else
486       {
487         arr.arr[target] = arr.arr[i];
488         target++;
489       }
490     }
491     arr.l = target;
492   }
493 }
494 
495 template ContainerStorageType(T)
496 {
497 	import std.traits : hasElaborateCopyConstructor, hasElaborateDestructor,
498 		hasElaborateAssign, isBasicType, isDynamicArray, isPointer, Unqual;
499 	import std.typecons : Rebindable;
500 	static if (is (T == const) || is (T == immutable))
501 	{
502 		static if (isBasicType!T || isDynamicArray!T || isPointer!T)
503 			alias ContainerStorageType = Unqual!T;
504 		else static if (is (T == class) || is (T == interface))
505 			alias ContainerStorageType = Rebindable!T;
506 		else static if (is (T == struct))
507 		{
508 			alias U = Unqual!T;
509 			static if (hasElaborateAssign!U || hasElaborateCopyConstructor!U || hasElaborateDestructor!U)
510 				static assert (false, "Cannot store " ~ T.stringof ~ " because of postblit, opAssign, or ~this");
511 			else
512 				alias ContainerStorageType = U;
513 		}
514 		else
515 			static assert (false, "Don't know how to handle type " ~ T.stringof);
516 	}
517 	else
518 		alias ContainerStorageType = T;
519 }
520 
521 ///
522 unittest
523 {
524 	static assert (is (ContainerStorageType!(int) == int));
525 	static assert (is (ContainerStorageType!(const int) == int));
526 }
527 
528 ///
529 unittest
530 {
531 	import std.typecons : Rebindable;
532 	static assert (is (ContainerStorageType!(Object) == Object));
533 	static assert (is (ContainerStorageType!(const(Object)) == Rebindable!(const(Object))));
534 }
535 
536 ///
537 unittest
538 {
539 	struct A { int foo; }
540 	struct B { void opAssign(typeof(this)) { this.foo *= 2; }  int foo;}
541 
542 	// A can be stored easily because it is plain data
543 	static assert (is (ContainerStorageType!(A) == A));
544 	static assert (is (ContainerStorageType!(const(A)) == A));
545 
546 	// const(B) cannot be stored in the container because of its
547 	// opAssign. Casting away const could lead to some very unexpected
548 	// behavior.
549 	static assert (!is (typeof(ContainerStorageType!(const(B)))));
550 	// Mutable B is not a problem
551 	static assert (is (ContainerStorageType!(B) == B));
552 
553 	// Arrays can be stored because the entire pointer-length pair is moved as
554 	// a unit.
555 	static assert (is (ContainerStorageType!(const(int[])) == const(int)[]));
556 }
557 
558 ///
559 unittest
560 {
561 	static assert (is (ContainerStorageType!(const(int*)) == const(int)*));
562 }
563 
564 template ContainerElementType(ContainerType, ElementType)
565 {
566 	import std.traits : isMutable, hasIndirections, PointerTarget, isPointer, Unqual;
567 
568 	template ET(bool isConst, T)
569 	{
570 		static if (isPointer!ElementType)
571 		{
572 			enum PointerIsConst = is(ElementType == const);
573 			enum PointerIsImmutable = is(ElementType == immutable);
574 			enum DataIsConst = is(PointerTarget!(ElementType) == const);
575 			enum DataIsImmutable = is(PointerTarget!(ElementType) == immutable);
576 			static if (isConst)
577 			{
578 				static if (PointerIsConst)
579 					alias ET = ElementType;
580 				else static if (PointerIsImmutable)
581 					alias ET = ElementType;
582 				else
583 					alias ET = const(PointerTarget!(ElementType))*;
584 			}
585 			else
586 			{
587 				static assert(DataIsImmutable, "An immutable container cannot reference const or mutable data");
588 				static if (PointerIsConst)
589 					alias ET = immutable(PointerTarget!ElementType)*;
590 				else
591 					alias ET = ElementType;
592 			}
593 		}
594 		else
595 		{
596 			static if (isConst)
597 			{
598 				static if (is(ElementType == immutable))
599 					alias ET = ElementType;
600 				else
601 					alias ET = const(Unqual!ElementType);
602 			}
603 			else
604 				alias ET = immutable(Unqual!ElementType);
605 		}
606 	}
607 
608 	static if (isMutable!ContainerType)
609 		alias ContainerElementType = ElementType;
610 	else
611 	{
612 		static if (hasIndirections!ElementType)
613 			alias ContainerElementType = ET!(is(ContainerType == const), ElementType);
614 		else
615 			alias ContainerElementType = ElementType;
616 	}
617 }
618 
619 mixin template AllocatorState(Allocator)
620 {
621   static if (stateSize!Allocator == 0)
622     alias allocator = Allocator.instance;
623   else
624     Allocator allocator;
625 }
626 
627 struct StringAppender(Allocator = SpasmGCAllocator) {
628   static if (!is(Allocator == SpasmGCAllocator)) {
629     DynamicArray!(char, Allocator) arr = void;
630     this(ref Allocator allocator) {
631       arr = DynamicArray!(char, Allocator)(allocator);
632     }
633   } else {
634     DynamicArray!(char, Allocator) arr;
635   }
636   alias arr this;
637   void put(string s) {
638     foreach(c; s)
639       arr.put(c);
640   }
641   void put(char c) {
642     arr.put(c);
643   }
644   void put(char[] cs) {
645     foreach(c; cs)
646       arr.put(c);
647   }
648 }
649 
650 @trusted string text(Allocator, T...)(ref Allocator allocator, T t) {
651   auto app = StringAppender!(Allocator)(allocator);
652   write(app, t);
653   auto end = app.length;
654   return cast(string)app[0..end];
655 }
656 
657 @trusted string text(T...)(T t) {
658   StringAppender!() app;
659   write(app, t);
660   auto end = app.length;
661   return cast(string)app[0..end];
662 }
663 
664 char[] unsignedToTempString()(ulong value, return scope char[] buf, uint radix = 10) @safe
665 {
666   if (radix < 2) // not a valid radix, just return an empty string
667     return buf[$ .. $];
668 
669   size_t i = buf.length;
670   do
671   {
672     if (value < radix)
673     {
674       ubyte x = cast(ubyte) value;
675       buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a');
676       break;
677     }
678     else
679     {
680       ubyte x = cast(ubyte)(value % radix);
681       value = value / radix;
682       buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a');
683     }
684   }
685   while (value);
686   return buf[i .. $];
687 }
688 
689 private struct TempStringNoAlloc
690 {
691   // need to handle 65 bytes for radix of 2 with negative sign.
692   private char[65] _buf = 0;
693   private ubyte _len;
694   auto get() return
695   {
696     return _buf[$ - _len .. $];
697   }
698 
699   alias get this;
700 }
701 
702 auto unsignedToTempString()(ulong value, uint radix = 10) @safe
703 {
704   TempStringNoAlloc result = void;
705   result._len = unsignedToTempString(value, result._buf, radix).length & 0xff;
706   return result;
707 }
708 
709 char[] signedToTempString(long value, return scope char[] buf, uint radix = 10) @safe
710 {
711   bool neg = value < 0;
712   if (neg)
713     value = cast(ulong)-value;
714   auto r = unsignedToTempString(value, buf, radix);
715   if (neg)
716   {
717     // about to do a slice without a bounds check
718     auto trustedSlice(return char[] r) @trusted
719     {
720       assert(r.ptr > buf.ptr);
721       return (r.ptr - 1)[0 .. r.length + 1];
722     }
723 
724     r = trustedSlice(r);
725     r[0] = '-';
726   }
727   return r;
728 }
729 
730 auto signedToTempString(long value, uint radix = 10) @safe
731 {
732   bool neg = value < 0;
733   if (neg)
734     value = cast(ulong)-value;
735   auto r = unsignedToTempString(value, radix);
736   if (neg)
737   {
738     r._len++;
739     r.get()[0] = '-';
740   }
741   return r;
742 }
743 
744 import std.traits : isIntegral;
745 import std.range.primitives : isOutputRange;
746 // TODO: std.range.put doesn't do scope on second args therefor compiler thinks buf escapes. resolve it and we can avoid the @trusted
747 @trusted
748 void toTextRange(T, W)(T value, auto ref W writer)
749     if (isIntegral!T && isOutputRange!(W, char))
750 {
751   import core.internal.string : SignedStringBuf,
752     UnsignedStringBuf;
753   import std.range.primitives : put;
754 
755   if (value < 0)
756   {
757     SignedStringBuf buf = void;
758     put(writer, signedToTempString(value, buf, 10));
759   }
760   else
761   {
762     UnsignedStringBuf buf = void;
763     put(writer, unsignedToTempString(value, buf, 10));
764   }
765 }
766 
767 void write(Sink, S...)(auto ref Sink sink, S args) {
768   import std.traits : isBoolean, isIntegral, isAggregateType, isSomeString, isSomeChar;
769 
770   foreach (arg; args)
771   {
772     alias A = typeof(arg);
773     static if (isAggregateType!A || is(A == enum))
774     {
775       sink.put(arg.toString());
776     }
777     else static if (isSomeString!A)
778     {
779       sink.put(arg);
780     }
781     else static if (isIntegral!A)
782     {
783       toTextRange(arg, sink);
784     }
785     else static if (isBoolean!A)
786     {
787       sink.put(arg ? "true" : "false");
788     }
789     else static if (isSomeChar!A)
790     {
791       sink.put(arg);
792     }
793     else
794     {
795       static assert(0);
796     }
797   }
798 }