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