1 module stdx.collections.array; 2 3 import stdx.collections.common; 4 5 debug(CollectionArray) import std.stdio; 6 7 version(unittest) 8 { 9 import std.experimental.allocator.mallocator; 10 import std.experimental.allocator.building_blocks.stats_collector; 11 import std.experimental.allocator : RCIAllocator, allocatorObject; 12 13 private alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed); 14 } 15 16 struct Array(T) 17 { 18 import std.experimental.allocator : RCIAllocator, RCISharedAllocator, 19 theAllocator, processAllocator, make, dispose; 20 import std.experimental.allocator.building_blocks.affix_allocator; 21 import std.traits : isImplicitlyConvertible, Unqual, isArray; 22 import std.range.primitives : isInputRange, isInfinite, 23 ElementType, hasLength; 24 import std.variant : Algebraic; 25 import std.conv : emplace; 26 import core.atomic : atomicOp; 27 28 //private: 29 T[] _payload; 30 Unqual!T[] _support; 31 32 static enum double capacityFactor = 3.0 / 2; 33 static enum initCapacity = 3; 34 35 alias LocalAllocT = AffixAllocator!(RCIAllocator, size_t); 36 alias SharedAllocT = AffixAllocator!(RCISharedAllocator, size_t); 37 alias MutableAlloc = Algebraic!(LocalAllocT, SharedAllocT); 38 Mutable!MutableAlloc _ouroborosAllocator; 39 40 /// Returns the actual allocator from ouroboros 41 @trusted ref auto allocator(this Q)() 42 { 43 static if (is(Q == immutable) || is(Q == const)) 44 { 45 assert(_ouroborosAllocator.get.peek!(SharedAllocT) !is null); 46 return _ouroborosAllocator.get.get!(SharedAllocT); 47 } 48 else 49 { 50 assert(_ouroborosAllocator.get.peek!(LocalAllocT) !is null); 51 return _ouroborosAllocator.get.get!(LocalAllocT); 52 } 53 } 54 55 /// Constructs the ouroboros allocator from allocator if the ouroboros 56 //allocator wasn't previously set 57 @trusted bool setAllocator(RCIAllocator allocator) 58 { 59 if (_ouroborosAllocator.get.peek!(LocalAllocT) is null) 60 { 61 _ouroborosAllocator = Mutable!(MutableAlloc)(allocator, 62 MutableAlloc(LocalAllocT(allocator))); 63 return true; 64 } 65 return false; 66 } 67 68 mixin template setSharedAllocator(RCISharedAllocator allocator) 69 { 70 _ouroborosAllocator = Mutable!(MutableAlloc)(allocator, 71 MutableAlloc(SharedAllocT(allocator))); 72 } 73 74 public @trusted auto ref getAllocator(this Q)() 75 { 76 static if (is(Q == immutable) || is(Q == const)) 77 { 78 return _ouroborosAllocator.get.peek!(SharedAllocT) is null ? 79 RCISharedAllocator(null) : allocator().parent; 80 } 81 else 82 { 83 return _ouroborosAllocator.get.peek!(LocalAllocT) is null ? 84 RCIAllocator(null) : allocator().parent; 85 } 86 } 87 88 @trusted void addRef(SupportQual, this Q)(SupportQual support) 89 { 90 assert(support !is null); 91 debug(CollectionArray) 92 { 93 writefln("Array.addRef: Array %s has refcount: %s; will be: %s", 94 support, *prefCount(support), *prefCount(support) + 1); 95 } 96 static if (is(Q == immutable) || is(Q == const)) 97 { 98 atomicOp!"+="(*prefCount(support), 1); 99 } 100 else 101 { 102 ++*prefCount(support); 103 } 104 } 105 106 @trusted void delRef(Unqual!T[] support) 107 { 108 assert(support !is null); 109 size_t *pref = prefCount(support); 110 debug(CollectionArray) writefln("Array.delRef: Array %s has refcount: %s; will be: %s", 111 support, *pref, *pref - 1); 112 if (*pref == 0) 113 { 114 debug(CollectionArray) writefln("Array.delRef: Deleting array %s", support); 115 allocator.dispose(support); 116 } 117 else 118 { 119 --*pref; 120 } 121 } 122 123 @trusted auto prefCount(SupportQual, this Q)(SupportQual support) 124 { 125 assert(support !is null); 126 static if (is(Q == immutable) || is(Q == const)) 127 { 128 return cast(shared size_t*)(&allocator.prefix(support)); 129 } 130 else 131 { 132 return cast(size_t*)(&allocator.prefix(support)); 133 } 134 } 135 136 static string immutableInsert(StuffType)(string stuff) 137 { 138 static if (hasLength!StuffType) 139 { 140 auto stuffLengthStr = "" 141 ~"size_t stuffLength = " ~ stuff ~ ".length;"; 142 } 143 else 144 { 145 auto stuffLengthStr = "" 146 ~"import std.range.primitives : walkLength;" 147 ~"size_t stuffLength = walkLength(" ~ stuff ~ ");"; 148 } 149 150 return "" 151 ~ stuffLengthStr 152 ~"auto tmpAlloc = Mutable!(MutableAlloc)(allocator, MutableAlloc(allocator));" 153 ~"_ouroborosAllocator = (() @trusted => cast(immutable)(tmpAlloc))();" 154 ~"auto tmpSupport = cast(Unqual!T[])(tmpAlloc.get().allocate(stuffLength * T.sizeof));" 155 ~"assert(stuffLength == 0 || (stuffLength > 0 && tmpSupport !is 156 null));" 157 ~"for (size_t i = 0; i < tmpSupport.length; ++i)" 158 ~"{" 159 ~"emplace(&tmpSupport[i]);" 160 ~"}" 161 ~"size_t i = 0;" 162 ~"foreach (item; " ~ stuff ~ ")" 163 ~"{" 164 ~"tmpSupport[i++] = item;" 165 ~"}" 166 ~"_support = cast(typeof(_support))(tmpSupport);" 167 ~"_payload = cast(T[])(_support[0 .. stuffLength]);"; 168 } 169 170 void destroyUnused() 171 { 172 debug(CollectionArray) 173 { 174 writefln("Array.destoryUnused: begin"); 175 scope(exit) writefln("Array.destoryUnused: end"); 176 } 177 if (_support !is null) 178 { 179 delRef(_support); 180 } 181 } 182 183 public: 184 this(A, this Q)(A allocator) 185 if (((is(Q == immutable) || is(Q == const)) && is(A == RCISharedAllocator)) 186 || (!(is(Q == immutable) || is(Q == const)) && is(A == RCIAllocator))) 187 { 188 debug(CollectionArray) 189 { 190 writefln("Array.ctor: begin"); 191 scope(exit) writefln("Array.ctor: end"); 192 } 193 static if (is(Q == immutable) || is(Q == const)) 194 { 195 mixin setSharedAllocator!(allocator); 196 } 197 else 198 { 199 setAllocator(allocator); 200 } 201 } 202 203 this(U, this Q)(U[] values...) 204 if (isImplicitlyConvertible!(U, T)) 205 { 206 static if (is(Q == immutable) || is(Q == const)) 207 { 208 this(processAllocator, values); 209 } 210 else 211 { 212 this(theAllocator, values); 213 } 214 } 215 216 this(A, U, this Q)(A allocator, U[] values...) 217 if ((((is(Q == immutable) || is(Q == const)) && is(A == RCISharedAllocator)) 218 || (!(is(Q == immutable) || is(Q == const)) && is(A == RCIAllocator))) 219 && isImplicitlyConvertible!(U, T)) 220 { 221 debug(CollectionArray) 222 { 223 writefln("Array.ctor: begin"); 224 scope(exit) writefln("Array.ctor: end"); 225 } 226 static if (is(Q == immutable) || is(Q == const)) 227 { 228 mixin setSharedAllocator!(allocator); 229 mixin(immutableInsert!(typeof(values))("values")); 230 assert(!_ouroborosAllocator.isNull); 231 } 232 else 233 { 234 setAllocator(allocator); 235 insert(0, values); 236 } 237 } 238 239 this(Stuff, this Q)(Stuff stuff) 240 if (isInputRange!Stuff && !isInfinite!Stuff 241 && isImplicitlyConvertible!(ElementType!Stuff, T) 242 && !is(Stuff == T[])) 243 { 244 static if (is(Q == immutable) || is(Q == const)) 245 { 246 this(processAllocator, stuff); 247 } 248 else 249 { 250 this(theAllocator, stuff); 251 } 252 } 253 254 this(A, Stuff, this Q)(A allocator, Stuff stuff) 255 if ((((is(Q == immutable) || is(Q == const)) && is(A == RCISharedAllocator)) 256 || (!(is(Q == immutable) || is(Q == const)) && is(A == RCIAllocator))) 257 && isInputRange!Stuff 258 && isImplicitlyConvertible!(ElementType!Stuff, T) 259 && !is(Stuff == T[])) 260 { 261 debug(CollectionArray) 262 { 263 writefln("Array.ctor: begin"); 264 scope(exit) writefln("Array.ctor: end"); 265 } 266 static if (is(Q == immutable) || is(Q == const)) 267 { 268 mixin setSharedAllocator!(allocator); 269 mixin(immutableInsert!(typeof(stuff))("stuff")); 270 assert(!_ouroborosAllocator.isNull); 271 } 272 else 273 { 274 setAllocator(allocator); 275 insert(0, stuff); 276 } 277 } 278 279 this(this) 280 { 281 debug(CollectionArray) 282 { 283 writefln("Array.postblit: begin"); 284 scope(exit) writefln("Array.postblit: end"); 285 } 286 if (_support !is null) 287 { 288 addRef(_support); 289 debug(CollectionArray) writefln("Array.postblit: Array %s has refcount: %s", 290 _support, *prefCount(_support)); 291 } 292 } 293 294 // Immutable ctors 295 private this(SuppQual, PaylQual, OuroQual, this Qualified)(SuppQual support, 296 PaylQual payload, OuroQual ouroborosAllocator) 297 //if (is(typeof(_support) : typeof(support)) 298 //&& (is(Qualified == immutable) || is(Qualified == const))) 299 { 300 _support = support; 301 _payload = payload; 302 _ouroborosAllocator = ouroborosAllocator; 303 if (_support !is null) 304 { 305 addRef(_support); 306 debug(CollectionArray) writefln("Array.ctor immutable: Array %s has " 307 ~ "refcount: %s", _support, *prefCount(_support)); 308 } 309 } 310 311 @trusted ~this() 312 { 313 debug(CollectionArray) 314 { 315 writefln("Array.dtor: Begin for instance %s of type %s", 316 cast(size_t)(&this), typeof(this).stringof); 317 scope(exit) writefln("Array.dtor: End for instance %s of type %s", 318 cast(size_t)(&this), typeof(this).stringof); 319 } 320 destroyUnused(); 321 } 322 323 private @trusted size_t slackFront() const 324 { 325 return _payload.ptr - _support.ptr; 326 } 327 328 private @trusted size_t slackBack() const 329 { 330 return _support.ptr + _support.length - _payload.ptr - _payload.length; 331 } 332 333 size_t length() const 334 { 335 return _payload.length; 336 } 337 338 void forceLength(size_t len) 339 { 340 assert(len <= capacity); 341 _payload = cast(T[])(_support[slackFront .. len]); 342 } 343 344 alias opDollar = length; 345 346 @trusted size_t capacity() const 347 { 348 return length + slackBack; 349 } 350 351 void reserve(size_t n) 352 { 353 debug(CollectionArray) 354 { 355 writefln("Array.reserve: begin"); 356 scope(exit) writefln("Array.reserve: end"); 357 } 358 setAllocator(theAllocator); 359 360 if (n <= capacity) { return; } 361 if (_support && *prefCount(_support) == 0) 362 { 363 void[] buf = _support; 364 if (allocator.expand(buf, (n - capacity) * T.sizeof)) 365 { 366 _support = cast(Unqual!T[])(buf); 367 // TODO: emplace extended buf 368 return; 369 } 370 else 371 { 372 //assert(0, "Array.reserve: Failed to expand array."); 373 } 374 } 375 376 auto tmpSupport = cast(Unqual!T[])(allocator.allocate(n * T.sizeof)); 377 assert(tmpSupport !is null); 378 for (size_t i = 0; i < tmpSupport.length; ++i) 379 { 380 emplace(&tmpSupport[i]); 381 } 382 tmpSupport[0 .. _payload.length] = _payload[]; 383 __dtor(); 384 _support = tmpSupport; 385 _payload = cast(T[])(_support[0 .. _payload.length]); 386 assert(capacity >= n); 387 } 388 389 size_t insert(Stuff)(size_t pos, Stuff stuff) 390 if (!isArray!(typeof(stuff)) && isInputRange!Stuff && !isInfinite!Stuff 391 && isImplicitlyConvertible!(ElementType!Stuff, T)) 392 { 393 debug(CollectionArray) 394 { 395 writefln("Array.insert: begin"); 396 scope(exit) writefln("Array.insert: end"); 397 } 398 setAllocator(theAllocator); 399 400 static if (hasLength!Stuff) 401 { 402 size_t stuffLength = stuff.length; 403 } 404 else 405 { 406 import std.range.primitives : walkLength; 407 size_t stuffLength = walkLength(stuff); 408 } 409 if (stuffLength == 0) return 0; 410 411 auto tmpSupport = cast(Unqual!T[])(allocator.allocate(stuffLength * T.sizeof)); 412 assert(stuffLength == 0 || (stuffLength > 0 && tmpSupport !is null)); 413 for (size_t i = 0; i < tmpSupport.length; ++i) 414 { 415 emplace(&tmpSupport[i]); 416 } 417 418 size_t i = 0; 419 foreach (item; stuff) 420 { 421 tmpSupport[i++] = item; 422 } 423 size_t result = insert(pos, tmpSupport); 424 allocator.dispose(tmpSupport); 425 return result; 426 } 427 428 size_t insert(Stuff)(size_t pos, Stuff[] stuff...) 429 if (isImplicitlyConvertible!(Stuff, T)) 430 { 431 debug(CollectionArray) 432 { 433 writefln("Array.insert: begin"); 434 scope(exit) writefln("Array.insert: end"); 435 } 436 assert(pos <= _payload.length); 437 setAllocator(theAllocator); 438 439 if (stuff.length == 0) return 0; 440 if (stuff.length > slackBack) 441 { 442 double newCapacity = capacity ? capacity * capacityFactor : stuff.length; 443 while (newCapacity < capacity + stuff.length) 444 { 445 newCapacity = newCapacity * capacityFactor; 446 } 447 reserve(cast(size_t)(newCapacity)); 448 } 449 //_support[pos + stuff.length .. _payload.length + stuff.length] = 450 //_support[pos .. _payload.length]; 451 for (size_t i = _payload.length + stuff.length - 1; i >= pos + 452 stuff.length; --i) 453 { 454 // Avoids underflow if payload is empty 455 _support[i] = _support[i - stuff.length]; 456 } 457 _support[pos .. pos + stuff.length] = stuff[]; 458 _payload = cast(T[])(_support[0 .. _payload.length + stuff.length]); 459 return stuff.length; 460 } 461 462 bool isUnique(this _)() 463 { 464 debug(CollectionArray) 465 { 466 writefln("Array.isUnique: begin"); 467 scope(exit) writefln("Array.isUnique: end"); 468 } 469 470 if (_support !is null) 471 { 472 return *prefCount(_support) == 0; 473 } 474 return true; 475 } 476 477 bool empty(this _)() 478 { 479 return length == 0; 480 } 481 482 ref auto front(this _)() 483 { 484 assert(!empty, "Array.front: Array is empty"); 485 return _payload[0]; 486 } 487 488 void popFront() 489 { 490 debug(CollectionArray) 491 { 492 writefln("Array.popFront: begin"); 493 scope(exit) writefln("Array.popFront: end"); 494 } 495 assert(!empty, "Array.popFront: Array is empty"); 496 _payload = _payload[1 .. $]; 497 } 498 499 Qualified tail(this Qualified)() 500 { 501 debug(CollectionArray) 502 { 503 writefln("Array.tail: begin"); 504 scope(exit) writefln("Array.tail: end"); 505 } 506 assert(!empty, "Array.tail: Array is empty"); 507 508 static if (is(Qualified == immutable) || is(Qualified == const)) 509 { 510 return this[1 .. $]; 511 } 512 else 513 { 514 return .tail(this); 515 } 516 } 517 518 ref auto save(this _)() 519 { 520 debug(CollectionArray) 521 { 522 writefln("Array.save: begin"); 523 scope(exit) writefln("Array.save: end"); 524 } 525 return this; 526 } 527 528 typeof(this) dup() 529 { 530 debug(CollectionArray) 531 { 532 writefln("Array.dup: begin"); 533 scope(exit) writefln("Array.dup: end"); 534 } 535 RCIAllocator alloc = getAllocator(); 536 if (alloc.isNull) 537 { 538 alloc = theAllocator; 539 } 540 return typeof(this)(alloc, this); 541 } 542 543 Qualified opSlice(this Qualified)() 544 { 545 debug(CollectionArray) 546 { 547 writefln("Array.opSlice(): begin"); 548 scope(exit) writefln("Array.opSlice(): end"); 549 } 550 return this.save; 551 } 552 553 Qualified opSlice(this Qualified)(size_t start, size_t end) 554 in 555 { 556 assert(start <= end && end <= length, 557 "Array.opSlice(s, e): Invalid bounds: Ensure start <= end <= length"); 558 } 559 body 560 { 561 debug(CollectionArray) 562 { 563 writefln("Array.opSlice(s, e): begin"); 564 scope(exit) writefln("Array.opSlice(s, e): end"); 565 } 566 return typeof(this)(_support, _payload[start .. end], _ouroborosAllocator); 567 } 568 569 ref auto opIndex(this _)(size_t idx) 570 in 571 { 572 assert(idx <= length, "Array.opIndex: Index out of bounds"); 573 } 574 body 575 { 576 return _payload[idx]; 577 } 578 579 ref auto opIndexUnary(string op)(size_t idx) 580 in 581 { 582 assert(idx <= length, "Array.opIndexUnary!" ~ op ~ ": Index out of bounds"); 583 } 584 body 585 { 586 mixin("return " ~ op ~ "_payload[idx];"); 587 } 588 589 ref auto opIndexAssign(U)(U elem, size_t idx) 590 if (isImplicitlyConvertible!(U, T)) 591 in 592 { 593 assert(idx <= length, "Array.opIndexAssign: Index out of bounds"); 594 } 595 body 596 { 597 return _payload[idx] = elem; 598 } 599 600 ref auto opIndexOpAssign(string op, U)(U elem, size_t idx) 601 if (isImplicitlyConvertible!(U, T)) 602 in 603 { 604 assert(idx <= length, "Array.opIndexOpAssign!" ~ op ~ ": Index out of bounds"); 605 } 606 body 607 { 608 mixin("return _payload[idx]" ~ op ~ "= elem;"); 609 } 610 611 auto ref opBinary(string op, U)(auto ref U rhs) 612 if (op == "~" && 613 (is (U == typeof(this)) 614 || is (U : T) 615 || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T)) 616 )) 617 { 618 debug(CollectionArray) 619 { 620 writefln("Array.opBinary!~: begin"); 621 scope(exit) writefln("Array.opBinary!~: end"); 622 } 623 624 //TODO: should work for immutable, const as well 625 626 typeof(this) newArray = this.dup(); 627 newArray.insert(length, rhs); 628 return newArray; 629 } 630 631 auto ref opAssign()(auto ref typeof(this) rhs) 632 { 633 debug(CollectionArray) 634 { 635 writefln("Array.opAssign: begin"); 636 scope(exit) writefln("Array.opAssign: end"); 637 } 638 639 if (rhs._support !is null && _support is rhs._support) 640 { 641 return this; 642 } 643 644 if (rhs._support !is null) 645 { 646 rhs.addRef(rhs._support); 647 debug(CollectionArray) writefln("Array.opAssign: Array %s has refcount: %s", 648 rhs._payload, *prefCount(rhs._support)); 649 } 650 //__dtor(); 651 destroyUnused(); 652 _support = rhs._support; 653 _payload = rhs._payload; 654 _ouroborosAllocator = rhs._ouroborosAllocator; 655 return this; 656 } 657 658 auto ref opOpAssign(string op, U)(auto ref U rhs) 659 if (op == "~" && 660 (is (U == typeof(this)) 661 || is (U : T) 662 || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T)) 663 )) 664 { 665 debug(CollectionArray) 666 { 667 writefln("Array.opOpAssign!~: %s begin", typeof(this).stringof); 668 scope(exit) writefln("Array.opOpAssign!~: %s end", typeof(this).stringof); 669 } 670 insert(length, rhs); 671 return this; 672 } 673 } 674 675 version(unittest) private @trusted void testConcatAndAppend(RCIAllocator allocator) 676 { 677 import std.algorithm.comparison : equal; 678 679 auto a = Array!(int)(allocator, 1, 2, 3); 680 Array!(int) a2 = Array!(int)(allocator); 681 682 auto a3 = a ~ a2; 683 assert(equal(a3, [1, 2, 3])); 684 685 auto a4 = a3; 686 a3 = a3 ~ 4; 687 assert(equal(a3, [1, 2, 3, 4])); 688 a3 = a3 ~ [5]; 689 assert(equal(a3, [1, 2, 3, 4, 5])); 690 assert(equal(a4, [1, 2, 3])); 691 692 a4 = a3; 693 a3 ~= 6; 694 assert(equal(a3, [1, 2, 3, 4, 5, 6])); 695 a3 ~= [7]; 696 697 a3 ~= a3; 698 assert(equal(a3, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7])); 699 700 Array!int a5 = Array!(int)(allocator); 701 a5 ~= [1, 2, 3]; 702 assert(equal(a5, [1, 2, 3])); 703 auto a6 = a5; 704 a5 = a5; 705 a5[0] = 10; 706 assert(equal(a5, a6)); 707 } 708 709 @trusted unittest 710 { 711 import std.conv; 712 SCAlloc statsCollectorAlloc; 713 auto _allocator = allocatorObject(&statsCollectorAlloc); 714 715 () @safe { 716 testConcatAndAppend(_allocator); 717 }(); 718 auto bytesUsed = statsCollectorAlloc.bytesUsed; 719 assert(bytesUsed == 0, "Array ref count leaks memory; leaked " 720 ~ to!string(bytesUsed) ~ " bytes"); 721 } 722 723 version(unittest) private @trusted void testSimple(RCIAllocator allocator) 724 { 725 import std.algorithm.comparison : equal; 726 import std.algorithm.searching : canFind; 727 728 auto a = Array!int(allocator); 729 assert(a.empty); 730 assert(a.isUnique); 731 732 size_t pos = 0; 733 a.insert(pos, 1, 2, 3); 734 assert(a.front == 1); 735 assert(equal(a, a)); 736 assert(equal(a, [1, 2, 3])); 737 738 a.popFront(); 739 assert(a.front == 2); 740 assert(equal(a, [2, 3])); 741 742 a.insert(pos, [4, 5, 6]); 743 a.insert(pos, 7); 744 a.insert(pos, [8]); 745 assert(equal(a, [8, 7, 4, 5, 6, 2, 3])); 746 747 a.insert(a.length, 0, 1); 748 a.insert(a.length, [-1, -2]); 749 assert(equal(a, [8, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2])); 750 751 a.front = 9; 752 assert(equal(a, [9, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2])); 753 754 auto aTail = a.tail; 755 assert(aTail.front == 7); 756 aTail.front = 8; 757 assert(aTail.front == 8); 758 assert(a.tail.front == 8); 759 assert(!a.isUnique); 760 761 assert(canFind(a, 2)); 762 assert(!canFind(a, -10)); 763 } 764 765 @trusted unittest 766 { 767 import std.conv; 768 SCAlloc statsCollectorAlloc; 769 auto _allocator = allocatorObject(&statsCollectorAlloc); 770 771 () @safe { 772 testSimple(_allocator); 773 }(); 774 auto bytesUsed = statsCollectorAlloc.bytesUsed; 775 assert(bytesUsed == 0, "Array ref count leaks memory; leaked " 776 ~ to!string(bytesUsed) ~ " bytes"); 777 } 778 779 version(unittest) private @trusted void testSimpleImmutable(RCIAllocator allocator) 780 { 781 import std.algorithm.comparison : equal; 782 import std.algorithm.searching : canFind; 783 784 auto a = Array!(immutable int)(allocator); 785 assert(a.empty); 786 787 size_t pos = 0; 788 a.insert(pos, 1, 2, 3); 789 assert(a.front == 1); 790 assert(equal(a, a)); 791 assert(equal(a, [1, 2, 3])); 792 793 a.popFront(); 794 assert(a.front == 2); 795 assert(equal(a, [2, 3])); 796 assert(a.tail.front == 3); 797 798 a.insert(pos, [4, 5, 6]); 799 a.insert(pos, 7); 800 a.insert(pos, [8]); 801 assert(equal(a, [8, 7, 4, 5, 6, 2, 3])); 802 803 a.insert(a.length, 0, 1); 804 a.insert(a.length, [-1, -2]); 805 assert(equal(a, [8, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2])); 806 807 // Cannot modify immutable values 808 static assert(!__traits(compiles, a.front = 9)); 809 810 assert(canFind(a, 2)); 811 assert(!canFind(a, -10)); 812 } 813 814 @trusted unittest 815 { 816 import std.conv; 817 SCAlloc statsCollectorAlloc; 818 auto _allocator = allocatorObject(&statsCollectorAlloc); 819 820 () @safe { 821 testSimpleImmutable(_allocator); 822 }(); 823 auto bytesUsed = statsCollectorAlloc.bytesUsed; 824 assert(bytesUsed == 0, "Array ref count leaks memory; leaked " 825 ~ to!string(bytesUsed) ~ " bytes"); 826 } 827 828 version(unittest) private @trusted void testCopyAndRef(RCIAllocator allocator) 829 { 830 import std.algorithm.comparison : equal; 831 832 auto aFromList = Array!int(allocator, 1, 2, 3); 833 auto aFromRange = Array!int(allocator, aFromList); 834 assert(equal(aFromList, aFromRange)); 835 836 aFromList.popFront(); 837 assert(equal(aFromList, [2, 3])); 838 assert(equal(aFromRange, [1, 2, 3])); 839 840 size_t pos = 0; 841 Array!int aInsFromRange = Array!int(allocator); 842 aInsFromRange.insert(pos, aFromList); 843 aFromList.popFront(); 844 assert(equal(aFromList, [3])); 845 assert(equal(aInsFromRange, [2, 3])); 846 847 Array!int aInsBackFromRange = Array!int(allocator); 848 aInsBackFromRange.insert(pos, aFromList); 849 aFromList.popFront(); 850 assert(aFromList.empty); 851 assert(equal(aInsBackFromRange, [3])); 852 853 auto aFromRef = aInsFromRange; 854 auto aFromDup = aInsFromRange.dup; 855 assert(aInsFromRange.front == 2); 856 aFromRef.front = 5; 857 assert(aInsFromRange.front == 5); 858 assert(aFromDup.front == 2); 859 } 860 861 @trusted unittest 862 { 863 import std.conv; 864 SCAlloc statsCollectorAlloc; 865 auto _allocator = allocatorObject(&statsCollectorAlloc); 866 867 () @safe { 868 testCopyAndRef(_allocator); 869 }(); 870 auto bytesUsed = statsCollectorAlloc.bytesUsed; 871 assert(bytesUsed == 0, "Array ref count leaks memory; leaked " 872 ~ to!string(bytesUsed) ~ " bytes"); 873 } 874 875 version(unittest) private @trusted void testImmutability(RCIAllocator allocator) 876 { 877 auto a = immutable Array!(int)(allocator, 1, 2, 3); 878 auto a2 = a; 879 auto a3 = a2.save(); 880 881 assert(a2.front == 1); 882 assert(a2[0] == a2.front); 883 static assert(!__traits(compiles, a2.front = 4)); 884 static assert(!__traits(compiles, a2.popFront())); 885 886 auto a4 = a2.tail; 887 assert(a4.front == 2); 888 static assert(!__traits(compiles, a4 = a4.tail)); 889 } 890 891 version(unittest) private @trusted void testConstness(RCIAllocator allocator) 892 { 893 auto a = const Array!(int)(allocator, 1, 2, 3); 894 auto a2 = a; 895 auto a3 = a2.save(); 896 897 assert(a2.front == 1); 898 assert(a2[0] == a2.front); 899 static assert(!__traits(compiles, a2.front = 4)); 900 static assert(!__traits(compiles, a2.popFront())); 901 902 auto a4 = a2.tail; 903 assert(a4.front == 2); 904 static assert(!__traits(compiles, a4 = a4.tail)); 905 } 906 907 @trusted unittest 908 { 909 import std.conv; 910 SCAlloc statsCollectorAlloc; 911 auto _allocator = allocatorObject(&statsCollectorAlloc); 912 913 () @safe { 914 testImmutability(_allocator); 915 testConstness(_allocator); 916 }(); 917 auto bytesUsed = statsCollectorAlloc.bytesUsed; 918 assert(bytesUsed == 0, "Array ref count leaks memory; leaked " 919 ~ to!string(bytesUsed) ~ " bytes"); 920 } 921 922 version(unittest) private @trusted void testWithStruct(RCIAllocator allocator) 923 { 924 import std.algorithm.comparison : equal; 925 import std.stdio; 926 927 auto array = Array!int(allocator, 1, 2, 3); 928 { 929 auto arrayOfArrays = Array!(Array!int)(allocator, array); 930 assert(equal(arrayOfArrays.front, [1, 2, 3])); 931 arrayOfArrays.front.front = 2; 932 assert(equal(arrayOfArrays.front, [2, 2, 3])); 933 static assert(!__traits(compiles, arrayOfArrays.insert(1))); 934 935 auto immArrayOfArrays = immutable Array!(Array!int)(allocator, array); 936 assert(immArrayOfArrays.front.front == 2); 937 static assert(!__traits(compiles, immArrayOfArrays.front.front = 2)); 938 } 939 assert(equal(array, [2, 2, 3])); 940 } 941 942 @trusted unittest 943 { 944 import std.conv; 945 SCAlloc statsCollectorAlloc; 946 auto _allocator = allocatorObject(&statsCollectorAlloc); 947 948 () @safe { 949 testWithStruct(_allocator); 950 }(); 951 auto bytesUsed = statsCollectorAlloc.bytesUsed; 952 assert(bytesUsed == 0, "Array ref count leaks memory; leaked " 953 ~ to!string(bytesUsed) ~ " bytes"); 954 } 955 956 version(unittest) private @trusted void testWithClass(RCIAllocator allocator) 957 { 958 class MyClass 959 { 960 int x; 961 this(int x) { this.x = x; } 962 } 963 964 MyClass c = new MyClass(10); 965 { 966 auto a = Array!MyClass(allocator, c); 967 assert(a.front.x == 10); 968 assert(a.front is c); 969 a.front.x = 20; 970 } 971 assert(c.x == 20); 972 } 973 974 @trusted unittest 975 { 976 import std.conv; 977 SCAlloc statsCollectorAlloc; 978 auto _allocator = allocatorObject(&statsCollectorAlloc); 979 980 () @safe { 981 testWithClass(_allocator); 982 }(); 983 auto bytesUsed = statsCollectorAlloc.bytesUsed; 984 assert(bytesUsed == 0, "Array ref count leaks memory; leaked " 985 ~ to!string(bytesUsed) ~ " bytes"); 986 } 987 988 version(unittest) private @trusted void testOpOverloads(RCIAllocator allocator) 989 { 990 auto a = Array!int(allocator, 1, 2, 3, 4); 991 assert(a[0] == 1); // opIndex 992 993 // opIndexUnary 994 ++a[0]; 995 assert(a[0] == 2); 996 --a[0]; 997 assert(a[0] == 1); 998 a[0]++; 999 assert(a[0] == 2); 1000 a[0]--; 1001 assert(a[0] == 1); 1002 1003 // opIndexAssign 1004 a[0] = 2; 1005 assert(a[0] == 2); 1006 1007 // opIndexOpAssign 1008 a[0] /= 2; 1009 assert(a[0] == 1); 1010 a[0] *= 2; 1011 assert(a[0] == 2); 1012 a[0] -= 1; 1013 assert(a[0] == 1); 1014 a[0] += 1; 1015 assert(a[0] == 2); 1016 } 1017 1018 @trusted unittest 1019 { 1020 import std.conv; 1021 SCAlloc statsCollectorAlloc; 1022 auto _allocator = allocatorObject(&statsCollectorAlloc); 1023 1024 () @safe { 1025 testOpOverloads(_allocator); 1026 }(); 1027 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1028 assert(bytesUsed == 0, "Array ref count leaks memory; leaked " 1029 ~ to!string(bytesUsed) ~ " bytes"); 1030 } 1031 1032 version(unittest) private @trusted void testSlice(RCIAllocator allocator) 1033 { 1034 import std.algorithm.comparison : equal; 1035 1036 auto a = Array!int(allocator, 1, 2, 3, 4); 1037 auto b = a[]; 1038 assert(equal(a, b)); 1039 b[1] = 5; 1040 assert(a[1] == 5); 1041 1042 size_t startPos = 2; 1043 auto c = b[startPos .. $]; 1044 assert(equal(c, [3, 4])); 1045 c[0] = 5; 1046 assert(equal(a, b)); 1047 assert(equal(a, [1, 5, 5, 4])); 1048 assert(a.capacity == b.capacity && b.capacity == c.capacity + startPos); 1049 1050 c ~= 5; 1051 assert(equal(c, [5, 4, 5])); 1052 assert(equal(a, b)); 1053 assert(equal(a, [1, 5, 5, 4])); 1054 } 1055 1056 @trusted unittest 1057 { 1058 import std.conv; 1059 SCAlloc statsCollectorAlloc; 1060 auto _allocator = allocatorObject(&statsCollectorAlloc); 1061 1062 () @safe { 1063 testSlice(_allocator); 1064 }(); 1065 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1066 assert(bytesUsed == 0, "Array ref count leaks memory; leaked " 1067 ~ to!string(bytesUsed) ~ " bytes"); 1068 } 1069 1070 //@trusted unittest { 1071 //import stdx.collections.slist; 1072 //import std.stdio; 1073 1074 //{ 1075 //auto a = Array!(SList!int)(SList!int(1)); 1076 //writefln("Array: %s", *a.prefCount(a._support)); 1077 //writefln("SList: %s", *a.front.prefCount(a.front._head)); 1078 //{ 1079 //auto b = a; 1080 //writefln("Array: %s", *a.prefCount(a._support)); 1081 //writefln("SList: %s", *a.front.prefCount(a.front._head)); 1082 //size_t i = 0; 1083 //auto sl = a.front; 1084 ////while(!a.front.empty) 1085 ////{ 1086 ////writefln("[%s] %s", i, a.front.front); 1087 ////a.front.popFront; 1088 ////} 1089 //while(!sl.empty) 1090 //{ 1091 //writefln("[%s] %s", i, sl.front); 1092 //sl.popFront; 1093 //} 1094 //writefln("At end of scope"); 1095 //} 1096 //writefln("After end of scope"); 1097 //writefln("Array: %s", *a.prefCount(a._support)); 1098 //writefln("SList: %s", *a.front.prefCount(a.front._head)); 1099 //} 1100 1101 //import std.conv; 1102 //writefln("HERE"); 1103 //auto bytesUsed = _allocator.bytesUsed; 1104 //assert(bytesUsed == 0, "Array ref count leaks memory; leaked " 1105 //~ to!string(bytesUsed) ~ " bytes"); 1106 /*}*/