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 }