1 /// 2 module stdx.collections.dlist; 3 4 import stdx.collections.common; 5 6 debug(CollectionDList) import std.stdio; 7 8 version(unittest) 9 { 10 import std.experimental.allocator.mallocator; 11 import std.experimental.allocator.building_blocks.stats_collector; 12 import std.experimental.allocator : IAllocator, allocatorObject; 13 14 private alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed); 15 } 16 17 struct DList(T) 18 { 19 import std.experimental.allocator : IAllocator, theAllocator, make, dispose; 20 import std.experimental.allocator.building_blocks.affix_allocator; 21 import std.traits : isImplicitlyConvertible; 22 import std.range.primitives : isInputRange, ElementType; 23 import std.conv : emplace; 24 import core.atomic : atomicOp; 25 26 private: 27 struct Node 28 { 29 T _payload; 30 Node *_next; 31 Node *_prev; 32 33 this(T v, Node *n, Node *p) 34 { 35 debug(CollectionDList) writefln("DList.Node.ctor: Constructing node" ~ 36 " with payload: %s", v); 37 _payload = v; 38 _next = n; 39 _prev = p; 40 } 41 42 ~this() 43 { 44 debug(CollectionDList) writefln("DList.Node.dtor: Destroying node" ~ 45 " with payload: %s", _payload); 46 } 47 } 48 49 Node *_head; 50 51 alias MutableAlloc = AffixAllocator!(IAllocator, size_t); 52 Mutable!MutableAlloc _ouroborosAllocator; 53 54 /// Returns the actual allocator from ouroboros 55 @trusted ref auto allocator(this _)() 56 { 57 assert(!_ouroborosAllocator.isNull); 58 return _ouroborosAllocator.get(); 59 } 60 61 /// Constructs the ouroboros allocator from allocator if the ouroboros 62 //allocator wasn't previously set 63 public @trusted bool setAllocator(IAllocator allocator) 64 { 65 if (_ouroborosAllocator.isNull) 66 { 67 _ouroborosAllocator = Mutable!(MutableAlloc)(allocator, 68 MutableAlloc(allocator)); 69 return true; 70 } 71 return false; 72 } 73 74 public @trusted IAllocator getAllocator(this _)() 75 { 76 return _ouroborosAllocator.isNull ? null : allocator().parent; 77 } 78 79 @trusted void addRef(QualNode, this Qualified)(QualNode node) 80 { 81 assert(node !is null); 82 debug(CollectionDList) 83 { 84 writefln("DList.addRef: Node %s has refcount: %s; will be: %s", 85 node._payload, *prefCount(node), *prefCount(node) + 1); 86 } 87 static if (is(Qualified == immutable) || is(Qualified == const)) 88 { 89 atomicOp!"+="(*prefCount(node), 1); 90 } 91 else 92 { 93 ++*prefCount(node); 94 } 95 } 96 97 @trusted void delRef(ref Node *node) 98 { 99 assert(node !is null); 100 size_t *pref = prefCount(node); 101 debug(CollectionDList) writefln("DList.delRef: Node %s has refcount: %s; will be: %s", 102 node._payload, *pref, *pref - 1); 103 if (*pref == 0) 104 { 105 debug(CollectionDList) writefln("DList.delRef: Deleting node %s", node._payload); 106 allocator.dispose(node); 107 node = null; 108 } 109 else 110 { 111 --*pref; 112 } 113 } 114 115 @trusted auto prefCount(QualNode, this Qualified)(QualNode node) 116 { 117 assert(node !is null); 118 static if (is(Qualified == immutable) || is(Qualified == const)) 119 { 120 return cast(shared size_t*)(&allocator.prefix(cast(void[Node.sizeof])(*node))); 121 } 122 else 123 { 124 return cast(size_t*)(&allocator.prefix(cast(void[Node.sizeof])(*node))); 125 } 126 } 127 128 static string immutableInsert(string stuff) 129 { 130 return "" 131 ~"auto tmpAlloc = Mutable!(MutableAlloc)(allocator, MutableAlloc(allocator));" 132 ~"_ouroborosAllocator = (() @trusted => cast(immutable)(tmpAlloc))();" 133 ~"Node *tmpNode;" 134 ~"Node *tmpHead;" 135 ~"foreach (item; " ~ stuff ~ ")" 136 ~"{" 137 ~"Node *newNode;" 138 ~"() @trusted { newNode =" 139 ~"tmpAlloc.get().make!(Node)(item, null, null);" 140 ~"}();" 141 ~"if (tmpHead is null)" 142 ~"{" 143 ~"tmpHead = tmpNode = newNode;" 144 ~"}" 145 ~"else" 146 ~"{" 147 ~"tmpNode._next = newNode;" 148 ~"newNode._prev = tmpNode;" 149 ~"addRef(newNode._prev);" 150 ~"tmpNode = newNode;" 151 ~"}" 152 ~"}" 153 ~"_head = (() @trusted => cast(immutable Node*)(tmpHead))();"; 154 } 155 156 public: 157 this(this _)(IAllocator allocator) 158 { 159 debug(CollectionDList) 160 { 161 writefln("DList.ctor: begin"); 162 scope(exit) writefln("DList.ctor: end"); 163 } 164 setAllocator(allocator); 165 } 166 167 this(U, this Qualified)(U[] values...) 168 if (isImplicitlyConvertible!(U, T)) 169 { 170 this(theAllocator, values); 171 } 172 173 this(U, this Qualified)(IAllocator allocator, U[] values...) 174 if (isImplicitlyConvertible!(U, T)) 175 { 176 debug(CollectionDList) 177 { 178 writefln("DList.ctor: begin"); 179 scope(exit) writefln("DList.ctor: end"); 180 } 181 static if (is(Qualified == immutable) || is(Qualified == const)) 182 { 183 mixin(immutableInsert("values")); 184 } 185 else 186 { 187 setAllocator(allocator); 188 insert(0, values); 189 } 190 } 191 192 this(Stuff, this Qualified)(Stuff stuff) 193 if (isInputRange!Stuff 194 && isImplicitlyConvertible!(ElementType!Stuff, T) 195 && !is(Stuff == T[])) 196 { 197 this(theAllocator, stuff); 198 } 199 200 this(Stuff, this Qualified)(IAllocator allocator, Stuff stuff) 201 if (isInputRange!Stuff 202 && isImplicitlyConvertible!(ElementType!Stuff, T) 203 && !is(Stuff == T[])) 204 { 205 debug(CollectionDList) 206 { 207 writefln("DList.ctor: begin"); 208 scope(exit) writefln("DList.ctor: end"); 209 } 210 static if (is(Qualified == immutable) || is(Qualified == const)) 211 { 212 mixin(immutableInsert("stuff")); 213 } 214 else 215 { 216 setAllocator(allocator); 217 insert(0, stuff); 218 } 219 } 220 221 this(this) 222 { 223 debug(CollectionDList) 224 { 225 writefln("DList.postblit: begin"); 226 scope(exit) writefln("DList.postblit: end"); 227 } 228 if (_head !is null) 229 { 230 addRef(_head); 231 debug(CollectionDList) writefln("DList.postblit: Node %s has refcount: %s", 232 _head._payload, *prefCount(_head)); 233 } 234 } 235 236 // Immutable ctors 237 private this(NodeQual, OuroQual, this Qualified)(NodeQual _newHead, 238 OuroQual ouroborosAllocator) 239 if (is(typeof(_head) : typeof(_newHead)) 240 && (is(Qualified == immutable) || is(Qualified == const))) 241 { 242 _head = _newHead; 243 _ouroborosAllocator = ouroborosAllocator; 244 if (_head !is null) 245 { 246 addRef(_head); 247 debug(CollectionDList) writefln("DList.ctor immutable: Node %s has " 248 ~ "refcount: %s", _head._payload, *prefCount(_head)); 249 } 250 } 251 252 ~this() 253 { 254 debug(CollectionDList) 255 { 256 writefln("DList.dtor: Begin for instance %s of type %s", 257 cast(size_t)(&this), typeof(this).stringof); 258 scope(exit) writefln("DList.dtor: End for instance %s of type %s", 259 cast(size_t)(&this), typeof(this).stringof); 260 } 261 if (_head !is null) 262 { 263 delRef(_head); 264 if (_head !is null 265 && ((_head._prev !is null) || (_head._next !is null))) 266 { 267 // If it was a single node list, only delRef must be used 268 // in order to avoid premature/double freeing 269 destroyUnused(_head); 270 } 271 } 272 } 273 274 void destroyUnused(Node *startNode) 275 { 276 debug(CollectionDList) 277 { 278 writefln("DList.destoryUnused: begin"); 279 scope(exit) writefln("DList.destoryUnused: end"); 280 } 281 282 if (startNode is null) return; 283 284 Node *tmpNode = startNode; 285 bool isCycle = true; 286 while (tmpNode !is null) 287 { 288 if (((tmpNode._next is null || tmpNode._prev is null) 289 && *prefCount(tmpNode) == 0) 290 || (tmpNode._next !is null && tmpNode._prev !is null 291 && *prefCount(tmpNode) == 1)) 292 { 293 // The last node should always have rc == 0 (only one ref, 294 // from prev._next) 295 // The first node should always have rc == 0 (only one ref, 296 // from next._prev), since we don't take into account 297 // the head ref (that was deleted either by the dtor or by pop) 298 // Nodes within the cycle should always have rc == 1 299 tmpNode = tmpNode._next; 300 } 301 else 302 { 303 isCycle = false; 304 break; 305 } 306 } 307 308 tmpNode = startNode._prev; 309 while (isCycle && tmpNode !is null) 310 { 311 if (((tmpNode._next is null || tmpNode._prev is null) 312 && *prefCount(tmpNode) == 0) 313 || (tmpNode._next !is null && tmpNode._prev !is null 314 && *prefCount(tmpNode) == 1)) 315 { 316 tmpNode = tmpNode._prev; 317 } 318 else 319 { 320 isCycle = false; 321 break; 322 } 323 } 324 325 if (isCycle) 326 { 327 // We can safely deallocate memory 328 tmpNode = startNode._next; 329 while (tmpNode !is null) 330 { 331 Node *oldNode = tmpNode; 332 tmpNode = tmpNode._next; 333 () @trusted { allocator.dispose(oldNode); }(); 334 } 335 tmpNode = startNode; 336 while (tmpNode !is null) 337 { 338 Node *oldNode = tmpNode; 339 tmpNode = tmpNode._prev; 340 () @trusted { allocator.dispose(oldNode); }(); 341 } 342 } 343 } 344 345 bool isUnique(this _)() 346 { 347 debug(CollectionDList) 348 { 349 writefln("DList.isUnique: begin"); 350 scope(exit) writefln("DList.isUnique: end"); 351 } 352 353 if (empty) 354 { 355 return true; 356 } 357 358 Node *tmpNode = (() @trusted => cast(Node*)_head)(); 359 360 // Rewind to the beginning of the list 361 while (tmpNode !is null && tmpNode._prev !is null) 362 { 363 tmpNode = tmpNode._prev; 364 } 365 366 // For a single node list, head should have rc == 0 367 if (tmpNode._next is null && (*prefCount(tmpNode) > 0)) 368 { 369 return false; 370 } 371 372 while (tmpNode !is null) 373 { 374 if (tmpNode._next is null || tmpNode._prev is null) 375 { 376 // The first and last node should have rc == 0 unless the _head 377 // is pointing to them, in which case rc must be 1 378 if (((tmpNode is _head) && (*prefCount(tmpNode) > 1)) 379 || ((tmpNode !is _head) && (*prefCount(tmpNode) > 0))) 380 { 381 return false; 382 } 383 } 384 else if (((tmpNode is _head) && (*prefCount(tmpNode) > 2)) 385 || ((tmpNode !is _head) && (*prefCount(tmpNode) > 1))) 386 { 387 // Any other node should have rc == 1 unless the _head 388 // is pointing to it, in which case rc must be 2 389 return false; 390 } 391 tmpNode = tmpNode._next; 392 } 393 394 return true; 395 } 396 397 bool empty(this _)() 398 { 399 return _head is null; 400 } 401 402 ref auto front(this _)() 403 { 404 assert(!empty, "DList.front: List is empty"); 405 return _head._payload; 406 } 407 408 void popFront() 409 { 410 debug(CollectionDList) 411 { 412 writefln("DList.popFront: begin"); 413 scope(exit) writefln("DList.popFront: end"); 414 } 415 assert(!empty, "DList.popFront: List is empty"); 416 Node *tmpNode = _head; 417 _head = _head._next; 418 if (_head !is null) 419 { 420 addRef(_head); 421 delRef(tmpNode); 422 } 423 else 424 { 425 delRef(tmpNode); 426 if (tmpNode !is null 427 && ((tmpNode._prev !is null) || (tmpNode._next !is null))) 428 { 429 // If it was a single node list, only delRef must be used 430 // in order to avoid premature/double freeing 431 destroyUnused(tmpNode); 432 } 433 } 434 } 435 436 void popPrev() 437 { 438 debug(CollectionDList) 439 { 440 writefln("DList.popPrev: begin"); 441 scope(exit) writefln("DList.popPrev: end"); 442 } 443 assert(!empty, "DList.popPrev: List is empty"); 444 Node *tmpNode = _head; 445 _head = _head._prev; 446 if (_head !is null) { 447 addRef(_head); 448 delRef(tmpNode); 449 } 450 else 451 { 452 delRef(tmpNode); 453 if (tmpNode !is null 454 && ((tmpNode._prev !is null) || (tmpNode._next !is null))) 455 { 456 // If it was a single node list, only delRef must be used 457 // in order to avoid premature/double freeing 458 destroyUnused(tmpNode); 459 } 460 } 461 } 462 463 Qualified tail(this Qualified)() 464 { 465 debug(CollectionDList) 466 { 467 writefln("DList.popFront: begin"); 468 scope(exit) writefln("DList.popFront: end"); 469 } 470 assert(!empty, "DList.popFront: List is empty"); 471 472 static if (is(Qualified == immutable) || is(Qualified == const)) 473 { 474 return typeof(this)(_head._next, _ouroborosAllocator); 475 } 476 else 477 { 478 return .tail(this); 479 } 480 } 481 482 ref Qualified save(this Qualified)() 483 { 484 debug(CollectionDList) 485 { 486 writefln("DList.save: begin"); 487 scope(exit) writefln("DList.save: end"); 488 } 489 return this; 490 } 491 492 typeof(this) dup() 493 { 494 debug(CollectionDList) 495 { 496 writefln("DList.dup: begin"); 497 scope(exit) writefln("DList.dup: end"); 498 } 499 IAllocator alloc = getAllocator(); 500 if (alloc is null) 501 { 502 alloc = theAllocator; 503 } 504 return typeof(this)(alloc, this); 505 } 506 507 size_t insert(Stuff)(size_t pos, Stuff stuff) 508 if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) 509 { 510 debug(CollectionDList) 511 { 512 writefln("DList.insert: begin"); 513 scope(exit) writefln("DList.insert: end"); 514 } 515 setAllocator(theAllocator); 516 517 size_t result; 518 Node *tmpNode; 519 Node *tmpHead; 520 foreach (item; stuff) 521 { 522 Node *newNode; 523 () @trusted { newNode = allocator.make!(Node)(item, null, null); }(); 524 if (tmpHead is null) 525 { 526 tmpHead = tmpNode = newNode; 527 } 528 else 529 { 530 tmpNode._next = newNode; 531 newNode._prev = tmpNode; 532 addRef(newNode._prev); 533 tmpNode = newNode; 534 } 535 ++result; 536 } 537 538 if (!tmpNode) 539 { 540 return 0; 541 } 542 543 if (!_head) assert(pos == 0); 544 545 size_t initPos = pos; 546 Node *needle = _head; 547 while (pos && needle._next !is null) 548 { 549 needle = needle._next; 550 --pos; 551 } 552 553 // Check if we need to insert at the back of the list 554 if (initPos != 0 && needle._next is null && pos >= 1) 555 { 556 // We need to insert at the back of the list 557 assert(pos == 1, "Index out of range"); 558 needle._next = tmpHead; 559 tmpHead._prev = needle; 560 addRef(needle); 561 return result; 562 } 563 assert(pos == 0, "Index out of range"); 564 565 tmpNode._next = needle; 566 if (needle !is null) 567 { 568 addRef(needle); 569 if (needle._prev !is null) 570 { 571 tmpHead._prev = needle._prev; 572 needle._prev._next = tmpHead; 573 // Inc ref only when tmpHead will be the new head of the list 574 if (initPos == 0) 575 { 576 addRef(tmpHead); 577 } 578 579 // Delete extra ref, since we already added the ref earlier 580 // through tmpNode._next 581 delRef(needle); 582 } 583 if (initPos == 0) 584 { 585 // Pass the ref to the new head 586 delRef(needle); 587 } 588 assert(needle !is null); 589 needle._prev = tmpNode; 590 if (tmpHead == tmpNode) 591 { 592 addRef(tmpHead); 593 } 594 else 595 { 596 addRef(needle._prev); 597 } 598 } 599 600 if (initPos == 0) 601 { 602 _head = tmpHead; 603 } 604 return result; 605 } 606 607 size_t insert(Stuff)(size_t pos, Stuff[] stuff...) 608 if (isImplicitlyConvertible!(Stuff, T)) 609 { 610 return insert(pos, stuff); 611 } 612 613 size_t insertBack(Stuff)(Stuff stuff) 614 if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) 615 { 616 debug(CollectionDList) 617 { 618 writefln("DList.insertBack: begin"); 619 scope(exit) writefln("DList.insertBack: end"); 620 } 621 setAllocator(theAllocator); 622 623 size_t result; 624 Node *tmpNode; 625 Node *tmpHead; 626 foreach (item; stuff) 627 { 628 Node *newNode; 629 () @trusted { newNode = allocator.make!(Node)(item, null, null); }(); 630 if (tmpHead is null) 631 { 632 tmpHead = tmpNode = newNode; 633 } 634 else 635 { 636 tmpNode._next = newNode; 637 newNode._prev = tmpNode; 638 addRef(newNode._prev); 639 tmpNode = newNode; 640 } 641 ++result; 642 } 643 644 if (!tmpNode) 645 { 646 return 0; 647 } 648 649 if (_head is null) 650 { 651 _head = tmpHead; 652 } 653 else 654 { 655 Node *endNode; 656 for (endNode = _head; endNode._next !is null; endNode = endNode._next) { } 657 endNode._next = tmpHead; 658 // don't addRef(tmpHead) since the ref will pass from tmpHead to 659 // endNode._next when tmpHead's scope ends 660 tmpHead._prev = endNode; 661 addRef(endNode); 662 } 663 664 return result; 665 } 666 667 size_t insertBack(Stuff)(Stuff[] stuff...) 668 if (isImplicitlyConvertible!(Stuff, T)) 669 { 670 return insertBack(stuff); 671 } 672 673 auto ref opBinary(string op, U)(auto ref U rhs) 674 if (op == "~" && 675 (is (U == typeof(this)) 676 || is (U : T) 677 || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T)) 678 )) 679 { 680 debug(CollectionDList) 681 { 682 writefln("DList.opBinary!~: begin"); 683 scope(exit) writefln("DList.opBinary!~: end"); 684 } 685 686 IAllocator alloc = getAllocator(); 687 if (alloc is null) 688 { 689 static if (is(U == typeof(this))) 690 { 691 alloc = rhs.getAllocator(); 692 } 693 else 694 { 695 alloc = null; 696 } 697 if (alloc is null) 698 { 699 alloc = theAllocator; 700 } 701 } 702 703 typeof(this) newList = typeof(this)(alloc, rhs); 704 newList.insert(0, this); 705 return newList; 706 } 707 708 auto ref opAssign()(auto ref typeof(this) rhs) 709 { 710 debug(CollectionDList) 711 { 712 writefln("DList.opAssign: begin"); 713 scope(exit) writefln("DList.opAssign: end"); 714 } 715 716 if (rhs._head !is null && _head is rhs._head) 717 { 718 return this; 719 } 720 721 if (rhs._head !is null) 722 { 723 rhs.addRef(rhs._head); 724 debug(CollectionDList) writefln("DList.opAssign: Node %s has refcount: %s", 725 rhs._head._payload, *prefCount(rhs._head)); 726 } 727 728 if (_head !is null) 729 { 730 Node *tmpNode = _head; 731 delRef(tmpNode); 732 if (tmpNode !is null 733 && ((tmpNode._prev !is null) || (tmpNode._next !is null))) 734 { 735 // If it was a single node list, only delRef must be used 736 // in order to avoid premature/double freeing 737 destroyUnused(tmpNode); 738 } 739 } 740 _head = rhs._head; 741 _ouroborosAllocator = rhs._ouroborosAllocator; 742 743 return this; 744 } 745 746 auto ref opOpAssign(string op, U)(auto ref U rhs) 747 if (op == "~" && 748 (is (U == typeof(this)) 749 || is (U : T) 750 || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T)) 751 )) 752 { 753 debug(CollectionDList) 754 { 755 writefln("DList.opOpAssign!~: %s begin", typeof(this).stringof); 756 scope(exit) writefln("DList.opOpAssign!~: %s end", typeof(this).stringof); 757 } 758 759 insertBack(rhs); 760 return this; 761 } 762 763 void remove() 764 { 765 debug(CollectionDList) 766 { 767 writefln("DList.remove: begin"); 768 scope(exit) writefln("DList.remove: end"); 769 } 770 assert(!empty, "DList.remove: List is empty"); 771 772 Node *tmpNode = _head; 773 _head = _head._next; 774 if (_head !is null) 775 { 776 //addRef(_head); 777 _head._prev = tmpNode._prev; 778 delRef(tmpNode); // Remove tmpNode._next._prev ref 779 tmpNode._next = null; 780 //delRef(_head); 781 if (tmpNode._prev !is null) 782 { 783 addRef(_head); 784 tmpNode._prev._next = _head; 785 delRef(tmpNode); // Remove tmpNode._prev._next ref 786 tmpNode._prev = null; 787 } 788 } 789 else if (tmpNode._prev !is null) 790 { 791 _head = tmpNode._prev; 792 //addRef(_head); 793 tmpNode._prev = null; 794 //delRef(_head); 795 _head._next = null; 796 delRef(tmpNode); 797 } 798 delRef(tmpNode); // Remove old head ref 799 if (tmpNode !is null 800 && ((tmpNode._prev !is null) || (tmpNode._next !is null))) 801 { 802 // If it was a single node list, only delRef must be used 803 // in order to avoid premature/double freeing 804 destroyUnused(tmpNode); 805 } 806 } 807 808 //debug(CollectionDList) void printRefCount(Node *sn = null) 809 void printRefCount(Node *sn = null) 810 { 811 import std.stdio; 812 writefln("DList.printRefCount: begin"); 813 scope(exit) writefln("DList.printRefCount: end"); 814 815 Node *tmpNode; 816 if (sn is null) 817 tmpNode = _head; 818 else 819 tmpNode = sn; 820 821 while (tmpNode !is null && tmpNode._prev !is null) 822 { 823 // Rewind to the beginning of the list 824 tmpNode = tmpNode._prev; 825 } 826 while (tmpNode !is null) 827 { 828 writefln("DList.printRefCount: Node %s has ref count %s", 829 tmpNode._payload, *prefCount(tmpNode)); 830 tmpNode = tmpNode._next; 831 } 832 } 833 } 834 835 version (unittest) private @trusted void testInit(IAllocator allocator) 836 { 837 import std.algorithm.comparison : equal; 838 839 DList!int dl = DList!int(allocator); 840 assert(dl.empty); 841 assert(dl.isUnique); 842 int[] empty; 843 assert(equal(dl, empty)); 844 845 DList!int dl2 = DList!int(allocator, 1); 846 assert(equal(dl2, [1])); 847 848 DList!int dl3 = DList!int(allocator, 1, 2); 849 assert(equal(dl3, [1, 2])); 850 851 DList!int dl4 = DList!int(allocator, [1]); 852 assert(equal(dl4, [1])); 853 854 DList!int dl5 = DList!int(allocator, [1, 2]); 855 assert(equal(dl5, [1, 2])); 856 } 857 858 @trusted unittest 859 { 860 import std.conv; 861 SCAlloc statsCollectorAlloc; 862 auto _allocator = allocatorObject(statsCollectorAlloc); 863 864 () @safe { 865 testInit(_allocator); 866 auto bytesUsed = _allocator.impl.bytesUsed; 867 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 868 ~ to!string(bytesUsed) ~ " bytes"); 869 }(); 870 } 871 872 version (unittest) private @trusted void testInsert(IAllocator allocator) 873 { 874 import std.algorithm.comparison : equal; 875 import std.range.primitives : walkLength; 876 877 DList!int dl = DList!int(allocator, 1); 878 size_t pos = 0; 879 dl.insert(pos, 2); 880 assert(equal(dl, [2, 1])); 881 882 DList!int dl2 = DList!int(allocator, 1); 883 dl2.insert(pos, 2, 3); 884 assert(equal(dl2, [2, 3, 1])); 885 886 DList!int dl3 = DList!int(allocator, 1, 2); 887 dl3.insert(pos, 3); 888 assert(equal(dl3, [3, 1, 2])); 889 890 DList!int dl4 = DList!int(allocator, 1, 2); 891 dl4.insert(pos, 3, 4); 892 assert(equal(dl4, [3, 4, 1, 2])); 893 894 DList!int dl5 = DList!int(allocator, 1, 2); 895 dl5.popFront(); 896 dl5.insert(pos, 3); 897 assert(equal(dl5, [3, 2])); 898 dl5.popPrev(); 899 assert(equal(dl5, [1, 3, 2])); 900 901 DList!int dl6 = DList!int(allocator, 1, 2); 902 dl6.popFront(); 903 dl6.insert(pos, 3, 4); 904 assert(equal(dl6, [3, 4, 2])); 905 dl6.popPrev(); 906 assert(equal(dl6, [1, 3, 4, 2])); 907 dl6.insertBack(5); 908 assert(equal(dl6, [1, 3, 4, 2, 5])); 909 dl6.insertBack(6, 7); 910 assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7])); 911 dl6.insertBack([8]); 912 assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8])); 913 dl6.insertBack([9, 10]); 914 assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8, 9, 10])); 915 int[] empty; 916 dl6.insertBack(empty); 917 assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8, 9, 10])); 918 dl6.insert(pos, empty); 919 assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8, 9, 10])); 920 921 DList!int dl7 = DList!int(allocator, 1); 922 assert(equal(dl7, [1])); 923 dl7.insert(pos, 2); 924 assert(equal(dl7, [2, 1])); 925 pos = walkLength(dl7); 926 dl7.insert(pos, 3); 927 assert(equal(dl7, [2, 1, 3])); 928 dl7.insert(pos, 4); 929 assert(equal(dl7, [2, 1, 4, 3])); 930 } 931 932 @trusted unittest 933 { 934 import std.conv; 935 SCAlloc statsCollectorAlloc; 936 auto _allocator = allocatorObject(statsCollectorAlloc); 937 938 () @safe { 939 testInsert(_allocator); 940 auto bytesUsed = _allocator.impl.bytesUsed; 941 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 942 ~ to!string(bytesUsed) ~ " bytes"); 943 }(); 944 } 945 946 version (unittest) private @trusted void testRemove(IAllocator allocator) 947 { 948 import std.algorithm.comparison : equal; 949 950 DList!int dl = DList!int(allocator, 1); 951 size_t pos = 0; 952 dl.remove(); 953 assert(dl.empty); 954 assert(dl.isUnique); 955 assert(!dl._ouroborosAllocator.isNull); 956 957 dl.insert(pos, 2); 958 auto dl2 = dl; 959 auto dl3 = dl; 960 assert(!dl.isUnique); 961 962 dl.popFront(); 963 assert(dl.empty); 964 assert(!dl._ouroborosAllocator.isNull); 965 966 dl2.popPrev(); 967 assert(dl2.empty); 968 assert(dl3.isUnique); 969 970 auto dl4 = dl3; 971 assert(!dl3.isUnique); 972 dl4.remove(); 973 assert(dl4.empty); 974 assert(!dl3.empty); 975 assert(dl3.isUnique); 976 } 977 978 @trusted unittest 979 { 980 import std.conv; 981 SCAlloc statsCollectorAlloc; 982 auto _allocator = allocatorObject(statsCollectorAlloc); 983 984 () @safe { 985 testRemove(_allocator); 986 auto bytesUsed = _allocator.impl.bytesUsed; 987 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 988 ~ to!string(bytesUsed) ~ " bytes"); 989 }(); 990 } 991 992 version (unittest) private @trusted void testCopyAndRef(IAllocator allocator) 993 { 994 import std.algorithm.comparison : equal; 995 996 auto dlFromList = DList!int(allocator, 1, 2, 3); 997 auto dlFromRange = DList!int(allocator, dlFromList); 998 assert(equal(dlFromList, dlFromRange)); 999 1000 dlFromList.popFront(); 1001 assert(equal(dlFromList, [2, 3])); 1002 assert(equal(dlFromRange, [1, 2, 3])); 1003 1004 DList!int dlInsFromRange = DList!int(allocator); 1005 size_t pos = 0; 1006 dlInsFromRange.insert(pos, dlFromList); 1007 dlFromList.popFront(); 1008 assert(equal(dlFromList, [3])); 1009 assert(equal(dlInsFromRange, [2, 3])); 1010 1011 DList!int dlInsBackFromRange = DList!int(allocator); 1012 dlInsBackFromRange.insert(pos, dlFromList); 1013 dlFromList.popFront(); 1014 assert(dlFromList.empty); 1015 assert(equal(dlInsBackFromRange, [3])); 1016 1017 auto dlFromRef = dlInsFromRange; 1018 auto dlFromDup = dlInsFromRange.dup; 1019 assert(dlInsFromRange.front == 2); 1020 dlFromRef.front = 5; 1021 assert(dlInsFromRange.front == 5); 1022 assert(dlFromDup.front == 2); 1023 } 1024 1025 @trusted unittest 1026 { 1027 import std.conv; 1028 SCAlloc statsCollectorAlloc; 1029 auto _allocator = allocatorObject(statsCollectorAlloc); 1030 1031 () @safe { 1032 testCopyAndRef(_allocator); 1033 auto bytesUsed = _allocator.impl.bytesUsed; 1034 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1035 ~ to!string(bytesUsed) ~ " bytes"); 1036 }(); 1037 } 1038 1039 @trusted unittest 1040 { 1041 import std.algorithm.comparison : equal; 1042 1043 SCAlloc statsCollectorAlloc; 1044 auto _allocator = allocatorObject(statsCollectorAlloc); 1045 1046 DList!int dl = DList!int(_allocator, 1, 2, 3); 1047 auto before = _allocator.impl.bytesUsed; 1048 { 1049 DList!int dl2 = dl; 1050 dl2.popFront(); 1051 assert(equal(dl2, [2, 3])); 1052 } 1053 assert(before == _allocator.impl.bytesUsed); 1054 assert(equal(dl, [1, 2, 3])); 1055 dl.tail(); 1056 } 1057 1058 version(unittest) private @trusted void testImmutability(IAllocator allocator) 1059 { 1060 auto s = immutable DList!(int)(allocator, 1, 2, 3); 1061 auto s2 = s; 1062 auto s3 = s2.save(); 1063 1064 assert(s2.front == 1); 1065 static assert(!__traits(compiles, s2.front = 4)); 1066 static assert(!__traits(compiles, s2.popFront())); 1067 1068 auto s4 = s2.tail; 1069 assert(s4.front == 2); 1070 static assert(!__traits(compiles, s4 = s4.tail)); 1071 } 1072 1073 version(unittest) private @trusted void testConstness(IAllocator allocator) 1074 { 1075 auto s = const DList!(int)(allocator, 1, 2, 3); 1076 auto s2 = s; 1077 auto s3 = s2.save(); 1078 1079 assert(s2.front == 1); 1080 static assert(!__traits(compiles, s2.front = 4)); 1081 static assert(!__traits(compiles, s2.popFront())); 1082 1083 auto s4 = s2.tail; 1084 assert(s4.front == 2); 1085 static assert(!__traits(compiles, s4 = s4.tail)); 1086 } 1087 1088 @trusted unittest 1089 { 1090 import std.conv; 1091 SCAlloc statsCollectorAlloc; 1092 auto _allocator = allocatorObject(statsCollectorAlloc); 1093 1094 () @safe { 1095 testConstness(_allocator); 1096 testImmutability(_allocator); 1097 auto bytesUsed = _allocator.impl.bytesUsed; 1098 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1099 ~ to!string(bytesUsed) ~ " bytes"); 1100 }(); 1101 } 1102 1103 version(unittest) private @trusted void testConcatAndAppend(IAllocator allocator) 1104 { 1105 import std.algorithm.comparison : equal; 1106 1107 auto dl = DList!(int)(allocator, 1, 2, 3); 1108 DList!(int) dl2 = DList!int(allocator); 1109 1110 auto dl3 = dl ~ dl2; 1111 assert(equal(dl3, [1, 2, 3])); 1112 1113 auto dl4 = dl3; 1114 dl3 = dl3 ~ 4; 1115 assert(equal(dl3, [1, 2, 3, 4])); 1116 dl3 = dl3 ~ [5]; 1117 assert(equal(dl3, [1, 2, 3, 4, 5])); 1118 assert(equal(dl4, [1, 2, 3])); 1119 1120 dl4 = dl3; 1121 dl3 ~= 6; 1122 assert(equal(dl3, [1, 2, 3, 4, 5, 6])); 1123 dl3 ~= [7]; 1124 assert(equal(dl3, [1, 2, 3, 4, 5, 6, 7])); 1125 assert(equal(dl4, [1, 2, 3, 4, 5, 6, 7])); 1126 1127 dl3 ~= dl3; 1128 assert(equal(dl3, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7])); 1129 assert(equal(dl4, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7])); 1130 1131 DList!int dl5 = DList!int(allocator); 1132 dl5 ~= [1, 2, 3]; 1133 assert(equal(dl5, [1, 2, 3])); 1134 } 1135 1136 @trusted unittest 1137 { 1138 import std.conv; 1139 SCAlloc statsCollectorAlloc; 1140 auto _allocator = allocatorObject(statsCollectorAlloc); 1141 1142 () @safe { 1143 testConcatAndAppend(_allocator); 1144 auto bytesUsed = _allocator.impl.bytesUsed; 1145 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1146 ~ to!string(bytesUsed) ~ " bytes"); 1147 }(); 1148 } 1149 1150 version(unittest) private @trusted void testAssign(IAllocator allocator) 1151 { 1152 import std.algorithm.comparison : equal; 1153 1154 auto dl = DList!int(allocator, 1, 2, 3); 1155 assert(equal(dl, [1, 2, 3])); 1156 { 1157 auto dl2 = DList!int(allocator, 4, 5, 6); 1158 dl = dl2; 1159 assert(equal(dl, dl2)); 1160 } 1161 assert(equal(dl, [4, 5, 6])); 1162 dl.popPrev(); 1163 assert(dl.empty); 1164 } 1165 1166 @trusted unittest 1167 { 1168 import std.conv; 1169 SCAlloc statsCollectorAlloc; 1170 auto _allocator = allocatorObject(statsCollectorAlloc); 1171 1172 () @safe { 1173 testAssign(_allocator); 1174 auto bytesUsed = _allocator.impl.bytesUsed; 1175 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1176 ~ to!string(bytesUsed) ~ " bytes"); 1177 }(); 1178 } 1179 1180 version(unittest) private @trusted void testWithStruct(IAllocator allocator) 1181 { 1182 import std.algorithm.comparison : equal; 1183 1184 auto list = DList!int(allocator, 1, 2, 3); 1185 { 1186 auto listOfLists = DList!(DList!int)(allocator, list); 1187 size_t pos = 0; 1188 assert(equal(listOfLists.front, [1, 2, 3])); 1189 listOfLists.front.front = 2; 1190 assert(equal(listOfLists.front, [2, 2, 3])); 1191 static assert(!__traits(compiles, listOfLists.insert(pos, 1))); 1192 1193 auto immListOfLists = immutable DList!(DList!int)(allocator, list); 1194 assert(immListOfLists.front.front == 2); 1195 static assert(!__traits(compiles, immListOfLists.front.front = 2)); 1196 } 1197 assert(equal(list, [2, 2, 3])); 1198 } 1199 1200 @trusted unittest 1201 { 1202 import std.conv; 1203 SCAlloc statsCollectorAlloc; 1204 auto _allocator = allocatorObject(statsCollectorAlloc); 1205 1206 () @safe { 1207 testWithStruct(_allocator); 1208 auto bytesUsed = _allocator.impl.bytesUsed; 1209 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1210 ~ to!string(bytesUsed) ~ " bytes"); 1211 }(); 1212 } 1213 1214 version(unittest) private @trusted void testWithClass(IAllocator allocator) 1215 { 1216 class MyClass 1217 { 1218 int x; 1219 this(int x) { this.x = x; } 1220 } 1221 1222 MyClass c = new MyClass(10); 1223 { 1224 auto dl = DList!MyClass(allocator, c); 1225 assert(dl.front.x == 10); 1226 assert(dl.front is c); 1227 dl.front.x = 20; 1228 } 1229 assert(c.x == 20); 1230 } 1231 1232 @trusted unittest 1233 { 1234 import std.conv; 1235 SCAlloc statsCollectorAlloc; 1236 auto _allocator = allocatorObject(statsCollectorAlloc); 1237 1238 () @safe { 1239 testWithClass(_allocator); 1240 auto bytesUsed = _allocator.impl.bytesUsed; 1241 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1242 ~ to!string(bytesUsed) ~ " bytes"); 1243 }(); 1244 }