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 : RCIAllocator, RCISharedAllocator, 13 allocatorObject, sharedAllocatorObject; 14 15 private alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed); 16 } 17 18 /// 19 struct DList(T) 20 { 21 import std.experimental.allocator : RCIAllocator, RCISharedAllocator, 22 theAllocator, processAllocator, make, dispose, stateSize; 23 import std.experimental.allocator.building_blocks.affix_allocator; 24 import std.traits : isImplicitlyConvertible; 25 import std.range.primitives : isInputRange, ElementType; 26 import std.variant : Algebraic; 27 import std.conv : emplace; 28 import core.atomic : atomicOp; 29 import std.algorithm.mutation : move; 30 31 private: 32 struct Node 33 { 34 T _payload; 35 Node *_next; 36 Node *_prev; 37 38 this(T v, Node *n, Node *p) 39 { 40 debug(CollectionDList) writefln("DList.Node.ctor: Constructing node" ~ 41 " with payload: %s", v); 42 _payload = v; 43 _next = n; 44 _prev = p; 45 } 46 47 ~this() 48 { 49 debug(CollectionDList) writefln("DList.Node.dtor: Destroying node" ~ 50 " with payload: %s", _payload); 51 } 52 } 53 54 // State { 55 Node *_head; 56 mixin(allocatorHandler); 57 // } 58 59 @nogc nothrow pure @trusted 60 void addRef(QualNode, this Q)(QualNode node) 61 { 62 assert(node !is null); 63 cast(void) _allocator.opPrefix!("+=")(cast(void[Node.sizeof])(*node), 1); 64 } 65 66 void delRef(ref Node *node) 67 { 68 // Will be optimized away, but the type system infers T's safety 69 if (0) { T t = T.init; } 70 71 assert(node !is null); 72 () @trusted { 73 if (opCmpPrefix!"=="(node, 0)) 74 { 75 dispose(_allocator, node); 76 node = null; 77 } 78 else 79 { 80 cast(void) _allocator.opPrefix!("-=")(cast(void[Node.sizeof])(*node), 1); 81 } 82 }(); 83 } 84 85 pragma(inline, true) 86 @nogc nothrow pure @trusted 87 size_t opCmpPrefix(string op)(const Node *node, size_t val) const 88 if ((op == "==") || (op == "<=") || (op == "<") || (op == ">=") || (op == ">")) 89 { 90 return _allocator.opCmpPrefix!op(cast(void[Node.sizeof])(*node), val); 91 } 92 93 static string immutableInsert(string stuff) 94 { 95 return q{ 96 _allocator = immutable AllocatorHandler(allocator); 97 Node *tmpNode; 98 Node *tmpHead; 99 foreach (item; } ~ stuff ~ q{ ) 100 { 101 Node *newNode; 102 () @trusted { newNode = 103 _allocator.make!(Node)(item, null, null); 104 }(); 105 if (tmpHead is null) 106 { 107 tmpHead = tmpNode = newNode; 108 } 109 else 110 { 111 tmpNode._next = newNode; 112 newNode._prev = tmpNode; 113 addRef(newNode._prev); 114 tmpNode = newNode; 115 } 116 } 117 _head = (() @trusted => cast(immutable Node*)(tmpHead))(); 118 }; 119 } 120 121 public: 122 /** 123 * Constructs a qualified doubly linked list that will use the provided 124 * allocator object. For `immutable` objects, a `RCISharedAllocator` must 125 * be supplied. 126 * 127 * Params: 128 * allocator = a $(REF RCIAllocator, std,experimental,allocator) or 129 * $(REF RCISharedAllocator, std,experimental,allocator) 130 * allocator object 131 * 132 * Complexity: $(BIGOH 1) 133 */ 134 this(A, this Q)(A allocator) 135 if (!is(Q == shared) 136 && (is(A == RCISharedAllocator) || !is(Q == immutable)) 137 && (is(A == RCIAllocator) || is(A == RCISharedAllocator))) 138 { 139 debug(CollectionDList) 140 { 141 writefln("DList.ctor: begin"); 142 scope(exit) writefln("DList.ctor: end"); 143 } 144 static if (is(Q == immutable) || is(Q == const)) 145 { 146 T[] empty; 147 this(allocator, empty); 148 } 149 else 150 { 151 setAllocator(allocator); 152 } 153 } 154 155 /// 156 @safe unittest 157 { 158 auto dl = DList!int(theAllocator); 159 auto cdl = const DList!int(processAllocator); 160 auto idl = immutable DList!int(processAllocator); 161 } 162 163 /** 164 * Constructs a qualified doubly linked list out of a number of items. 165 * Because no allocator was provided, the list will use the 166 * $(REF, GCAllocator, std,experimental,allocator). 167 * 168 * Params: 169 * values = a variable number of items, either in the form of a 170 * list or as a built-in array 171 * 172 * Complexity: $(BIGOH m), where `m` is the number of items. 173 */ 174 this(U, this Q)(U[] values...) 175 if (isImplicitlyConvertible!(U, T)) 176 { 177 this(defaultAllocator!(typeof(this)), values); 178 } 179 180 /// 181 static if (is(T == int)) 182 @safe unittest 183 { 184 import std.algorithm.comparison : equal; 185 186 // Create a list from a list of ints 187 { 188 auto dl = DList!int(1, 2, 3); 189 assert(equal(dl, [1, 2, 3])); 190 } 191 // Create a list from an array of ints 192 { 193 auto dl = DList!int([1, 2, 3]); 194 assert(equal(dl, [1, 2, 3])); 195 } 196 // Create a list from a list from an input range 197 { 198 auto dl = DList!int(1, 2, 3); 199 auto dl2 = DList!int(dl); 200 assert(equal(dl2, [1, 2, 3])); 201 } 202 } 203 204 /** 205 * Constructs a qualified doubly linked list out of a number of items 206 * that will use the provided allocator object. 207 * For `immutable` objects, a `RCISharedAllocator` must be supplied. 208 * 209 * Params: 210 * allocator = a $(REF RCIAllocator, std,experimental,allocator) or 211 * $(REF RCISharedAllocator, std,experimental,allocator) 212 * allocator object 213 * values = a variable number of items, either in the form of a 214 * list or as a built-in array 215 * 216 * Complexity: $(BIGOH m), where `m` is the number of items. 217 */ 218 this(A, U, this Q)(A allocator, U[] values...) 219 if (!is(Q == shared) 220 && (is(A == RCISharedAllocator) || !is(Q == immutable)) 221 && (is(A == RCIAllocator) || is(A == RCISharedAllocator)) 222 && isImplicitlyConvertible!(U, T)) 223 { 224 debug(CollectionDList) 225 { 226 writefln("DList.ctor: begin"); 227 scope(exit) writefln("DList.ctor: end"); 228 } 229 static if (is(Q == immutable) || is(Q == const)) 230 { 231 mixin(immutableInsert("values")); 232 } 233 else 234 { 235 setAllocator(allocator); 236 insert(0, values); 237 } 238 } 239 240 /** 241 * Constructs a qualified doubly linked list out of an 242 * $(REF_ALTTEXT input range, isInputRange, std,range,primitives). 243 * Because no allocator was provided, the list will use the 244 * $(REF, GCAllocator, std,experimental,allocator). 245 * 246 * Params: 247 * stuff = an input range of elements that are implitictly convertible 248 * to `T` 249 * 250 * Complexity: $(BIGOH m), where `m` is the number of elements in the range. 251 */ 252 this(Stuff, this Q)(Stuff stuff) 253 if (isInputRange!Stuff 254 && isImplicitlyConvertible!(ElementType!Stuff, T) 255 && !is(Stuff == T[])) 256 { 257 this(defaultAllocator!(typeof(this)), stuff); 258 } 259 260 /** 261 * Constructs a qualified doubly linked list out of an 262 * $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 263 * that will use the provided allocator object. 264 * For `immutable` objects, a `RCISharedAllocator` must be supplied. 265 * 266 * Params: 267 * allocator = a $(REF RCIAllocator, std,experimental,allocator) or 268 * $(REF RCISharedAllocator, std,experimental,allocator) 269 * allocator object 270 * stuff = an input range of elements that are implitictly convertible 271 * to `T` 272 * 273 * Complexity: $(BIGOH m), where `m` is the number of elements in the range. 274 */ 275 this(A, Stuff, this Q)(A allocator, Stuff stuff) 276 if (!is(Q == shared) 277 && (is(A == RCISharedAllocator) || !is(Q == immutable)) 278 && (is(A == RCIAllocator) || is(A == RCISharedAllocator)) 279 && isInputRange!Stuff 280 && isImplicitlyConvertible!(ElementType!Stuff, T) 281 && !is(Stuff == T[])) 282 { 283 debug(CollectionDList) 284 { 285 writefln("DList.ctor: begin"); 286 scope(exit) writefln("DList.ctor: end"); 287 } 288 static if (is(Q == immutable) || is(Q == const)) 289 { 290 mixin(immutableInsert("stuff")); 291 } 292 else 293 { 294 setAllocator(allocator); 295 insert(0, stuff); 296 } 297 } 298 299 this(this) 300 { 301 debug(CollectionDList) 302 { 303 writefln("DList.postblit: begin"); 304 scope(exit) writefln("DList.postblit: end"); 305 } 306 _allocator.bootstrap(); 307 if (_head !is null) 308 { 309 addRef(_head); 310 debug(CollectionDList) writefln("DList.postblit: Node %s has refcount: %s", 311 _head._payload, *prefCount(_head)); 312 } 313 } 314 315 // Immutable ctors 316 // Very important to pass the allocator by ref! (Related to postblit bug) 317 private this(NodeQual, AllocQual, this Q)(NodeQual _newHead, ref AllocQual _newAllocator) 318 if (is(typeof(_head) : typeof(_newHead)) 319 && (is(Q == immutable) || is(Q == const))) 320 { 321 _head = _newHead; 322 // Needs a bootstrap 323 // bootstrap is the equivalent of incRef 324 _newAllocator.bootstrap(); 325 _allocator = _newAllocator; 326 if (_head !is null) 327 { 328 addRef(_head); 329 debug(CollectionDList) writefln("DList.ctor immutable: Node %s has " 330 ~ "refcount: %s", _head._payload, *prefCount(_head)); 331 } 332 } 333 334 ~this() 335 { 336 debug(CollectionDList) 337 { 338 writefln("DList.dtor: Begin for instance %s of type %s", 339 cast(size_t)(&this), typeof(this).stringof); 340 scope(exit) writefln("DList.dtor: End for instance %s of type %s", 341 cast(size_t)(&this), typeof(this).stringof); 342 } 343 if (_head !is null) 344 { 345 delRef(_head); 346 if (_head !is null 347 && ((_head._prev !is null) || (_head._next !is null))) 348 { 349 // If it was a single node list, only delRef must be used 350 // in order to avoid premature/double freeing 351 destroyUnused(_head); 352 } 353 } 354 } 355 356 static if (is(T == int)) 357 nothrow pure @safe unittest 358 { 359 auto s = DList!int(1, 2, 3); 360 361 // Infer safety 362 static assert(!__traits(compiles, () @safe { DList!Unsafe(Unsafe(1)); })); 363 static assert(!__traits(compiles, () @safe { auto s = const DList!Unsafe(Unsafe(1)); })); 364 static assert(!__traits(compiles, () @safe { auto s = immutable DList!Unsafe(Unsafe(1)); })); 365 366 static assert(!__traits(compiles, () @safe { DList!UnsafeDtor(UnsafeDtor(1)); })); 367 static assert(!__traits(compiles, () @safe { auto s = const DList!UnsafeDtor(UnsafeDtor(1)); })); 368 static assert(!__traits(compiles, () @safe { auto s = immutable DList!UnsafeDtor(UnsafeDtor(1)); })); 369 370 // Infer purity 371 static assert(!__traits(compiles, () pure { DList!Impure(Impure(1)); })); 372 static assert(!__traits(compiles, () pure { auto s = const DList!Impure(Impure(1)); })); 373 static assert(!__traits(compiles, () pure { auto s = immutable DList!Impure(Impure(1)); })); 374 375 static assert(!__traits(compiles, () pure { DList!ImpureDtor(ImpureDtor(1)); })); 376 static assert(!__traits(compiles, () pure { auto s = const DList!ImpureDtor(ImpureDtor(1)); })); 377 static assert(!__traits(compiles, () pure { auto s = immutable DList!ImpureDtor(ImpureDtor(1)); })); 378 379 // Infer throwability 380 static assert(!__traits(compiles, () nothrow { DList!Throws(Throws(1)); })); 381 static assert(!__traits(compiles, () nothrow { auto s = const DList!Throws(Throws(1)); })); 382 static assert(!__traits(compiles, () nothrow { auto s = immutable DList!Throws(Throws(1)); })); 383 384 static assert(!__traits(compiles, () nothrow { DList!ThrowsDtor(ThrowsDtor(1)); })); 385 static assert(!__traits(compiles, () nothrow { auto s = const DList!ThrowsDtor(ThrowsDtor(1)); })); 386 static assert(!__traits(compiles, () nothrow { auto s = immutable DList!ThrowsDtor(ThrowsDtor(1)); })); 387 } 388 389 private void destroyUnused(Node *startNode) 390 { 391 debug(CollectionDList) 392 { 393 writefln("DList.destoryUnused: begin"); 394 scope(exit) writefln("DList.destoryUnused: end"); 395 } 396 397 // Will be optimized away, but the type system infers T's safety 398 if (0) { T t = T.init; } 399 400 if (startNode is null) return; 401 402 Node *tmpNode = startNode; 403 bool isCycle = true; 404 while (tmpNode !is null) 405 { 406 if (((tmpNode._next is null || tmpNode._prev is null) 407 && opCmpPrefix!"=="(tmpNode, 0)) 408 || (tmpNode._next !is null && tmpNode._prev !is null 409 && opCmpPrefix!"=="(tmpNode, 1))) 410 { 411 // The last node should always have rc == 0 (only one ref, 412 // from prev._next) 413 // The first node should always have rc == 0 (only one ref, 414 // from next._prev), since we don't take into account 415 // the head ref (that was deleted either by the dtor or by pop) 416 // Nodes within the cycle should always have rc == 1 417 tmpNode = tmpNode._next; 418 } 419 else 420 { 421 isCycle = false; 422 break; 423 } 424 } 425 426 tmpNode = startNode._prev; 427 while (isCycle && tmpNode !is null) 428 { 429 if (((tmpNode._next is null || tmpNode._prev is null) 430 && opCmpPrefix!"=="(tmpNode, 0)) 431 || (tmpNode._next !is null && tmpNode._prev !is null 432 && opCmpPrefix!"=="(tmpNode, 1))) 433 { 434 tmpNode = tmpNode._prev; 435 } 436 else 437 { 438 isCycle = false; 439 break; 440 } 441 } 442 443 if (isCycle) 444 { 445 // We can safely deallocate memory 446 // We could be in the middle of the list so we need to go both 447 // forwards and backwards 448 tmpNode = startNode._next; 449 while (tmpNode !is null) 450 { 451 Node *oldNode = tmpNode; 452 tmpNode = tmpNode._next; 453 () @trusted { dispose(_allocator, oldNode); }(); 454 } 455 tmpNode = startNode; 456 while (tmpNode !is null) 457 { 458 Node *oldNode = tmpNode; 459 tmpNode = tmpNode._prev; 460 () @trusted { dispose(_allocator, oldNode); }(); 461 } 462 } 463 } 464 465 /** 466 * Check whether there are no more references to this list instance. 467 * 468 * Returns: 469 * `true` if this is the only reference to this list instance; 470 * `false` otherwise. 471 * 472 * Complexity: $(BIGOH n). 473 */ 474 bool isUnique() const 475 { 476 debug(CollectionDList) 477 { 478 writefln("DList.isUnique: begin"); 479 scope(exit) writefln("DList.isUnique: end"); 480 } 481 482 if (empty) 483 { 484 return true; 485 } 486 487 Node *tmpNode = (() @trusted => cast(Node*)_head)(); 488 489 // Rewind to the beginning of the list 490 while (tmpNode !is null && tmpNode._prev !is null) 491 { 492 tmpNode = tmpNode._prev; 493 } 494 495 // For a single node list, head should have rc == 0 496 if (tmpNode._next is null && opCmpPrefix!">"(tmpNode, 0)) 497 { 498 return false; 499 } 500 501 while (tmpNode !is null) 502 { 503 if (tmpNode._next is null || tmpNode._prev is null) 504 { 505 // The first and last node should have rc == 0 unless the _head 506 // is pointing to them, in which case rc must be 1 507 if (((tmpNode is _head) && opCmpPrefix!">"(tmpNode, 1)) 508 || ((tmpNode !is _head) && opCmpPrefix!">"(tmpNode, 0))) 509 { 510 return false; 511 } 512 } 513 else if (((tmpNode is _head) && opCmpPrefix!">"(tmpNode, 2)) 514 || ((tmpNode !is _head) && opCmpPrefix!">"(tmpNode, 1))) 515 { 516 // Any other node should have rc == 1 unless the _head 517 // is pointing to it, in which case rc must be 2 518 return false; 519 } 520 tmpNode = tmpNode._next; 521 } 522 return true; 523 } 524 525 /// 526 static if (is(T == int)) 527 @safe unittest 528 { 529 auto dl = DList!int(24, 42); 530 assert(dl.isUnique); 531 { 532 auto dl2 = dl; 533 assert(!dl.isUnique); 534 dl2.front = 0; 535 assert(dl.front == 0); 536 } // dl2 goes out of scope 537 assert(dl.isUnique); 538 } 539 540 /** 541 * Check if the list is empty. 542 * 543 * Returns: 544 * `true` if there are no nodes in the list; `false` otherwise. 545 * 546 * Complexity: $(BIGOH 1). 547 */ 548 @nogc nothrow pure @safe 549 bool empty() const 550 { 551 return _head is null; 552 } 553 554 /// 555 static if (is(T == int)) 556 @safe unittest 557 { 558 DList!int dl; 559 assert(dl.empty); 560 size_t pos = 0; 561 dl.insert(pos, 1); 562 assert(!dl.empty); 563 } 564 565 /** 566 * Provide access to the first element in the list. The user must check 567 * that the list isn't `empty`, prior to calling this function. 568 * 569 * Returns: 570 * a reference to the first element. 571 * 572 * Complexity: $(BIGOH 1). 573 */ 574 ref auto front(this _)() 575 { 576 assert(!empty, "DList.front: List is empty"); 577 return _head._payload; 578 } 579 580 /// 581 static if (is(T == int)) 582 @safe unittest 583 { 584 auto dl = DList!int(1, 2, 3); 585 assert(dl.front == 1); 586 dl.front = 0; 587 assert(dl.front == 0); 588 } 589 590 /** 591 * Advance to the next element in the list. The user must check 592 * that the list isn't `empty`, prior to calling this function. 593 * 594 * If this was the last element in the list and there are no more 595 * references to the current list, then the list and all it's elements 596 * will be destroyed; this will call `T`'s dtor, if one is defined, 597 * and will collect the resources. 598 * 599 * Complexity: usually $(BIGOH 1), worst case $(BIGOH n). 600 */ 601 void popFront() 602 { 603 debug(CollectionDList) 604 { 605 writefln("DList.popFront: begin"); 606 scope(exit) writefln("DList.popFront: end"); 607 } 608 assert(!empty, "DList.popFront: List is empty"); 609 Node *tmpNode = _head; 610 _head = _head._next; 611 if (_head !is null) 612 { 613 addRef(_head); 614 delRef(tmpNode); 615 } 616 else 617 { 618 delRef(tmpNode); 619 if (tmpNode !is null 620 && ((tmpNode._prev !is null) || (tmpNode._next !is null))) 621 { 622 // If it was a single node list, only delRef must be used 623 // in order to avoid premature/double freeing 624 destroyUnused(tmpNode); 625 } 626 } 627 } 628 629 /// 630 static if (is(T == int)) 631 @safe unittest 632 { 633 auto a = [1, 2, 3]; 634 auto dl = DList!int(a); 635 size_t i = 0; 636 while (!dl.empty) 637 { 638 assert(dl.front == a[i++]); 639 dl.popFront; 640 } 641 assert(dl.empty); 642 } 643 644 /** 645 * Go to the previous element in the list. The user must check 646 * that the list isn't `empty`, prior to calling this function. 647 * 648 * If this was the first element in the list and there are no more 649 * references to the current list, then the list and all it's elements 650 * will be destroyed; this will call `T`'s dtor, if one is defined, 651 * and will collect the resources. 652 * 653 * Complexity: usually $(BIGOH 1), worst case $(BIGOH n). 654 */ 655 void popPrev() 656 { 657 debug(CollectionDList) 658 { 659 writefln("DList.popPrev: begin"); 660 scope(exit) writefln("DList.popPrev: end"); 661 } 662 assert(!empty, "DList.popPrev: List is empty"); 663 Node *tmpNode = _head; 664 _head = _head._prev; 665 if (_head !is null) { 666 addRef(_head); 667 delRef(tmpNode); 668 } 669 else 670 { 671 delRef(tmpNode); 672 if (tmpNode !is null 673 && ((tmpNode._prev !is null) || (tmpNode._next !is null))) 674 { 675 // If it was a single node list, only delRef must be used 676 // in order to avoid premature/double freeing 677 destroyUnused(tmpNode); 678 } 679 } 680 } 681 682 /// 683 static if (is(T == int)) 684 @safe unittest 685 { 686 auto dl = DList!int([1, 2, 3]); 687 dl.popFront; 688 assert(dl.front == 2); 689 dl.popPrev; 690 assert(dl.front == 1); 691 dl.popPrev; 692 assert(dl.empty); 693 } 694 695 /** 696 * Advance to the next element in the list. The user must check 697 * that the list isn't `empty`, prior to calling this function. 698 * 699 * This must be used in order to iterate through a `const` or `immutable` 700 * list. For a mutable list this is equivalent to calling `popFront`. 701 * 702 * Returns: 703 * a list that starts with the next element in the original list 704 * 705 * Complexity: $(BIGOH 1). 706 */ 707 Qualified tail(this Qualified)() 708 { 709 debug(CollectionDList) 710 { 711 writefln("DList.popFront: begin"); 712 scope(exit) writefln("DList.popFront: end"); 713 } 714 assert(!empty, "DList.popFront: List is empty"); 715 716 static if (is(Qualified == immutable) || is(Qualified == const)) 717 { 718 return typeof(this)(_head._next, _allocator); 719 } 720 else 721 { 722 return .tail(this); 723 } 724 } 725 726 /// 727 static if (is(T == int)) 728 @safe unittest 729 { 730 auto idl = immutable DList!int([1, 2, 3]); 731 assert(idl.tail.front == 2); 732 } 733 734 /** 735 * Eagerly iterate over each element in the list and call `fun` over each 736 * element. This should be used to iterate through `const` and `immutable` 737 * lists. 738 * 739 * Normally, the entire list is iterated. If partial iteration (early stopping) 740 * is desired, `fun` needs to return a value of type 741 * $(REF Flag, std,typecons)`!"each"` (`Yes.each` to continue iteration, or 742 * `No.each` to stop). 743 * 744 * Params: 745 * fun = unary function to apply on each element of the list. 746 * 747 * Returns: 748 * `Yes.each` if it has iterated through all the elements in the list, 749 * or `No.each` otherwise. 750 * 751 * Complexity: $(BIGOH n). 752 */ 753 template each(alias fun) 754 { 755 import std.typecons : Flag, Yes, No; 756 import std.functional : unaryFun; 757 import stdx.collections.slist : SList; 758 759 Flag!"each" each(this Q)() 760 if (is (typeof(unaryFun!fun(T.init)))) 761 { 762 alias fn = unaryFun!fun; 763 764 auto sl = SList!(const DList!T)(this); 765 while (!sl.empty && !sl.front.empty) 766 { 767 static if (!is(typeof(fn(T.init)) == Flag!"each")) 768 { 769 cast(void) fn(sl.front.front); 770 } 771 else 772 { 773 if (fn(sl.front.front) == No.each) 774 return No.each; 775 } 776 sl ~= sl.front.tail; 777 sl.popFront; 778 } 779 return Yes.each; 780 } 781 } 782 783 /// 784 static if (is(T == int)) 785 @safe unittest 786 { 787 import std.typecons : Flag, Yes, No; 788 789 auto idl = immutable DList!int([1, 2, 3]); 790 791 static bool foo(int x) { return x > 0; } 792 793 assert(idl.each!foo == Yes.each); 794 } 795 796 /** 797 * Perform a shallow copy of the list. 798 * 799 * Returns: 800 * a new reference to the current list. 801 * 802 * Complexity: $(BIGOH 1). 803 */ 804 ref Qualified save(this Qualified)() 805 { 806 debug(CollectionDList) 807 { 808 writefln("DList.save: begin"); 809 scope(exit) writefln("DList.save: end"); 810 } 811 return this; 812 } 813 814 /// 815 static if (is(T == int)) 816 @safe unittest 817 { 818 auto a = [1, 2, 3]; 819 auto dl = DList!int(a); 820 size_t i = 0; 821 822 auto tmp = dl.save; 823 while (!tmp.empty) 824 { 825 assert(tmp.front == a[i++]); 826 tmp.popFront; 827 } 828 assert(tmp.empty); 829 assert(!dl.empty); 830 } 831 832 /** 833 * Perform a copy of the list. This will create a new list that will copy 834 * the elements of the current list. This will `NOT` call `dup` on the 835 * elements of the list, regardless if `T` defines it or not. 836 * 837 * Returns: 838 * a new list. 839 * 840 * Complexity: $(BIGOH n). 841 */ 842 typeof(this) dup() 843 { 844 debug(CollectionDList) 845 { 846 writefln("DList.dup: begin"); 847 scope(exit) writefln("DList.dup: end"); 848 } 849 850 DList!T result; 851 result._allocator = _allocator; 852 853 // TODO: this should rewind the list 854 static if (is(Q == immutable) || is(Q == const)) 855 { 856 auto tmp = this; 857 while(!tmp.empty) 858 { 859 result ~= tmp.front; 860 tmp = tmp.tail; 861 } 862 } 863 else 864 { 865 result.insert(0, this); 866 } 867 return result; 868 } 869 870 /// 871 static if (is(T == int)) 872 @safe unittest 873 { 874 import std.algorithm.comparison : equal; 875 876 auto dl = DList!int(1, 2, 3); 877 auto dlDup = dl.dup; 878 assert(equal(dl, dlDup)); 879 dlDup.front = 0; 880 assert(!equal(dl, dlDup)); 881 assert(dlDup.front == 0); 882 assert(dl.front == 1); 883 } 884 885 /** 886 * Inserts the elements of an 887 * $(REF_ALTTEXT input range, isInputRange, std,range,primitives), or a 888 * variable number of items, at the given `pos`. 889 * 890 * If no allocator was provided when the list was created, the 891 * $(REF, GCAllocator, std,experimental,allocator) will be used. 892 * 893 * Params: 894 * pos = a positive integral 895 * stuff = an input range of elements that are implitictly convertible 896 * to `T`; a variable number of items either in the form of a 897 * list or as a built-in array 898 * 899 * Returns: 900 * the number of elements inserted 901 * 902 * Complexity: $(BIGOH pos + m), where `m` is the number of elements in the range. 903 */ 904 size_t insert(Stuff)(size_t pos, Stuff stuff) 905 if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) 906 { 907 debug(CollectionDList) 908 { 909 writefln("DList.insert: begin"); 910 scope(exit) writefln("DList.insert: end"); 911 } 912 913 // Will be optimized away, but the type system infers T's safety 914 if (0) { T t = T.init; } 915 916 // Ensure we have an allocator. If it was already set, this will do nothing 917 auto a = threadAllocatorObject(); 918 setAllocator(a); 919 920 size_t result; 921 Node *tmpNode; 922 Node *tmpHead; 923 foreach (item; stuff) 924 { 925 Node *newNode; 926 () @trusted { newNode = _allocator.make!(Node)(item, null, null); }(); 927 if (tmpHead is null) 928 { 929 tmpHead = tmpNode = newNode; 930 } 931 else 932 { 933 tmpNode._next = newNode; 934 newNode._prev = tmpNode; 935 addRef(newNode._prev); 936 tmpNode = newNode; 937 } 938 ++result; 939 } 940 941 if (!tmpNode) 942 { 943 return 0; 944 } 945 946 if (!_head) assert(pos == 0); 947 948 size_t initPos = pos; 949 Node *needle = _head; 950 while (pos && needle._next !is null) 951 { 952 needle = needle._next; 953 --pos; 954 } 955 956 // Check if we need to insert at the back of the list 957 if (initPos != 0 && needle._next is null && pos >= 1) 958 { 959 // We need to insert at the back of the list 960 assert(pos == 1, "Index out of range"); 961 needle._next = tmpHead; 962 tmpHead._prev = needle; 963 addRef(needle); 964 return result; 965 } 966 assert(pos == 0, "Index out of range"); 967 968 tmpNode._next = needle; 969 if (needle !is null) 970 { 971 addRef(needle); 972 if (needle._prev !is null) 973 { 974 tmpHead._prev = needle._prev; 975 needle._prev._next = tmpHead; 976 // Inc ref only when tmpHead will be the new head of the list 977 if (initPos == 0) 978 { 979 addRef(tmpHead); 980 } 981 982 // Delete extra ref, since we already added the ref earlier 983 // through tmpNode._next 984 delRef(needle); 985 } 986 if (initPos == 0) 987 { 988 // Pass the ref to the new head 989 delRef(needle); 990 } 991 assert(needle !is null); 992 needle._prev = tmpNode; 993 if (tmpHead == tmpNode) 994 { 995 addRef(tmpHead); 996 } 997 else 998 { 999 addRef(needle._prev); 1000 } 1001 } 1002 1003 if (initPos == 0) 1004 { 1005 _head = tmpHead; 1006 } 1007 return result; 1008 } 1009 1010 /// ditto 1011 size_t insert(Stuff)(size_t pos, Stuff[] stuff...) 1012 if (isImplicitlyConvertible!(Stuff, T)) 1013 { 1014 return insert(pos, stuff); 1015 } 1016 1017 /// 1018 static if (is(T == int)) 1019 @safe unittest 1020 { 1021 import std.algorithm.comparison : equal; 1022 1023 auto d = DList!int(4, 5); 1024 DList!int dl; 1025 assert(dl.empty); 1026 1027 size_t pos = 0; 1028 pos += dl.insert(pos, 1); 1029 pos += dl.insert(pos, [2, 3]); 1030 assert(equal(dl, [1, 2, 3])); 1031 1032 // insert from an input range 1033 pos += dl.insert(pos, d); 1034 assert(equal(dl, [1, 2, 3, 4, 5])); 1035 d.front = 0; 1036 assert(equal(dl, [1, 2, 3, 4, 5])); 1037 } 1038 1039 /** 1040 * Inserts the elements of an 1041 * $(REF_ALTTEXT input range, isInputRange, std,range,primitives), or a 1042 * variable number of items, at the end of the list. 1043 * 1044 * If no allocator was provided when the list was created, the 1045 * $(REF, GCAllocator, std,experimental,allocator) will be used. 1046 * 1047 * Params: 1048 * stuff = an input range of elements that are implitictly convertible 1049 * to `T`; a variable number of items either in the form of a 1050 * list or as a built-in array 1051 * 1052 * Returns: 1053 * the number of elements inserted 1054 * 1055 * Complexity: $(BIGOH pos + m), where `m` is the number of elements in the range. 1056 */ 1057 size_t insertBack(Stuff)(Stuff stuff) 1058 if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) 1059 { 1060 debug(CollectionDList) 1061 { 1062 writefln("DList.insertBack: begin"); 1063 scope(exit) writefln("DList.insertBack: end"); 1064 } 1065 1066 // Will be optimized away, but the type system infers T's safety 1067 if (0) { T t = T.init; } 1068 1069 // Ensure we have an allocator. If it was already set, this will do nothing 1070 auto a = threadAllocatorObject(); 1071 setAllocator(a); 1072 1073 size_t result; 1074 Node *tmpNode; 1075 Node *tmpHead; 1076 foreach (item; stuff) 1077 { 1078 Node *newNode; 1079 () @trusted { newNode = _allocator.make!(Node)(item, null, null); }(); 1080 if (tmpHead is null) 1081 { 1082 tmpHead = tmpNode = newNode; 1083 } 1084 else 1085 { 1086 tmpNode._next = newNode; 1087 newNode._prev = tmpNode; 1088 addRef(newNode._prev); 1089 tmpNode = newNode; 1090 } 1091 ++result; 1092 } 1093 1094 if (!tmpNode) 1095 { 1096 return 0; 1097 } 1098 1099 if (_head is null) 1100 { 1101 _head = tmpHead; 1102 } 1103 else 1104 { 1105 Node *endNode; 1106 for (endNode = _head; endNode._next !is null; endNode = endNode._next) { } 1107 endNode._next = tmpHead; 1108 // don't addRef(tmpHead) since the ref will pass from tmpHead to 1109 // endNode._next when tmpHead's scope ends 1110 tmpHead._prev = endNode; 1111 addRef(endNode); 1112 } 1113 1114 return result; 1115 } 1116 1117 /// ditto 1118 size_t insertBack(Stuff)(Stuff[] stuff...) 1119 if (isImplicitlyConvertible!(Stuff, T)) 1120 { 1121 return insertBack(stuff); 1122 } 1123 1124 /// 1125 static if (is(T == int)) 1126 @safe unittest 1127 { 1128 import std.algorithm.comparison : equal; 1129 1130 auto d = DList!int(4, 5); 1131 DList!int dl; 1132 assert(dl.empty); 1133 1134 dl.insertBack(1); 1135 dl.insertBack([2, 3]); 1136 assert(equal(dl, [1, 2, 3])); 1137 1138 // insert from an input range 1139 dl.insertBack(d); 1140 assert(equal(dl, [1, 2, 3, 4, 5])); 1141 d.front = 0; 1142 assert(equal(dl, [1, 2, 3, 4, 5])); 1143 } 1144 1145 /** 1146 * Create a new list that results from the concatenation of this list 1147 * with `rhs`. 1148 * 1149 * Params: 1150 * rhs = can be an element that is implicitly convertible to `T`, an 1151 * input range of such elements, or another doubly linked list 1152 * 1153 * Returns: 1154 * the newly created list 1155 * 1156 * Complexity: $(BIGOH n + m), where `m` is the number of elements in `rhs`. 1157 */ 1158 auto ref opBinary(string op, U)(auto ref U rhs) 1159 if (op == "~" && 1160 //(is (U : const typeof(this)) 1161 (is (U == typeof(this)) 1162 || is (U : T) 1163 || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T)) 1164 )) 1165 { 1166 debug(CollectionDList) 1167 { 1168 writefln("DList.opBinary!~: begin"); 1169 scope(exit) writefln("DList.opBinary!~: end"); 1170 } 1171 1172 auto newList = this.dup(); 1173 newList.insertBack(rhs); 1174 return newList; 1175 } 1176 1177 /// 1178 static if (is(T == int)) 1179 @safe unittest 1180 { 1181 import std.algorithm.comparison : equal; 1182 1183 auto dl = DList!int(1); 1184 auto dl2 = dl ~ 2; 1185 1186 assert(equal(dl2, [1, 2])); 1187 dl.front = 0; 1188 assert(equal(dl2, [1, 2])); 1189 } 1190 1191 /** 1192 * Assign `rhs` to this list. The current list will now become another 1193 * reference to `rhs`, unless `rhs` is `null`, in which case the current 1194 * list will become empty. If `rhs` refers to the current list nothing will 1195 * happen. 1196 * 1197 * All the previous list elements that have no more references to them 1198 * will be destroyed; this leads to a $(BIGOH n) complexity. 1199 * 1200 * Params: 1201 * rhs = a reference to a doubly linked list 1202 * 1203 * Returns: 1204 * a reference to this list 1205 * 1206 * Complexity: $(BIGOH n). 1207 */ 1208 auto ref opAssign()(auto ref typeof(this) rhs) 1209 { 1210 debug(CollectionDList) 1211 { 1212 writefln("DList.opAssign: begin"); 1213 scope(exit) writefln("DList.opAssign: end"); 1214 } 1215 1216 if (rhs._head !is null && _head is rhs._head) 1217 { 1218 return this; 1219 } 1220 1221 if (rhs._head !is null) 1222 { 1223 rhs.addRef(rhs._head); 1224 debug(CollectionDList) writefln("DList.opAssign: Node %s has refcount: %s", 1225 rhs._head._payload, *localPrefCount(rhs._head)); 1226 } 1227 1228 if (_head !is null) 1229 { 1230 Node *tmpNode = _head; 1231 delRef(tmpNode); 1232 if (tmpNode !is null 1233 && ((tmpNode._prev !is null) || (tmpNode._next !is null))) 1234 { 1235 // If it was a single node list, only delRef must be used 1236 // in order to avoid premature/double freeing 1237 destroyUnused(tmpNode); 1238 } 1239 } 1240 _head = rhs._head; 1241 _allocator = rhs._allocator; 1242 return this; 1243 } 1244 1245 /// 1246 static if (is(T == int)) 1247 @safe unittest 1248 { 1249 import std.algorithm.comparison : equal; 1250 1251 auto dl = DList!int(1); 1252 auto dl2 = DList!int(1, 2); 1253 1254 dl = dl2; // this will free the old dl 1255 assert(equal(dl, [1, 2])); 1256 dl.front = 0; 1257 assert(equal(dl2, [0, 2])); 1258 } 1259 1260 /** 1261 * Append the elements of `rhs` at the end of the list. 1262 * 1263 * If no allocator was provided when the list was created, the 1264 * $(REF, GCAllocator, std,experimental,allocator) will be used. 1265 * 1266 * Params: 1267 * rhs = can be an element that is implicitly convertible to `T`, an 1268 * input range of such elements, or another doubly linked list 1269 * 1270 * Returns: 1271 * a reference to this list 1272 * 1273 * Complexity: $(BIGOH n + m), where `m` is the number of elements in `rhs`. 1274 */ 1275 auto ref opOpAssign(string op, U)(auto ref U rhs) 1276 if (op == "~" && 1277 (is (U == typeof(this)) 1278 || is (U : T) 1279 || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T)) 1280 )) 1281 { 1282 debug(CollectionDList) 1283 { 1284 writefln("DList.opOpAssign!~: %s begin", typeof(this).stringof); 1285 scope(exit) writefln("DList.opOpAssign!~: %s end", typeof(this).stringof); 1286 } 1287 1288 insertBack(rhs); 1289 return this; 1290 } 1291 1292 /// 1293 static if (is(T == int)) 1294 @safe unittest 1295 { 1296 import std.algorithm.comparison : equal; 1297 1298 auto d = DList!int(4, 5); 1299 DList!int dl; 1300 assert(dl.empty); 1301 1302 dl ~= 1; 1303 dl ~= [2, 3]; 1304 assert(equal(dl, [1, 2, 3])); 1305 1306 // append an input range 1307 dl ~= d; 1308 assert(equal(dl, [1, 2, 3, 4, 5])); 1309 d.front = 0; 1310 assert(equal(dl, [1, 2, 3, 4, 5])); 1311 } 1312 1313 /** 1314 * Remove the current element from the list. If there are no 1315 * more references to the current element, then it will be destroyed. 1316 */ 1317 void remove() 1318 { 1319 debug(CollectionDList) 1320 { 1321 writefln("DList.remove: begin"); 1322 scope(exit) writefln("DList.remove: end"); 1323 } 1324 assert(!empty, "DList.remove: List is empty"); 1325 1326 Node *tmpNode = _head; 1327 _head = _head._next; 1328 if (_head !is null) 1329 { 1330 //addRef(_head); 1331 _head._prev = tmpNode._prev; 1332 delRef(tmpNode); // Remove tmpNode._next._prev ref 1333 tmpNode._next = null; 1334 //delRef(_head); 1335 if (tmpNode._prev !is null) 1336 { 1337 addRef(_head); 1338 tmpNode._prev._next = _head; 1339 delRef(tmpNode); // Remove tmpNode._prev._next ref 1340 tmpNode._prev = null; 1341 } 1342 } 1343 else if (tmpNode._prev !is null) 1344 { 1345 _head = tmpNode._prev; 1346 //addRef(_head); 1347 tmpNode._prev = null; 1348 //delRef(_head); 1349 _head._next = null; 1350 delRef(tmpNode); 1351 } 1352 delRef(tmpNode); // Remove old head ref 1353 if (tmpNode !is null 1354 && ((tmpNode._prev !is null) || (tmpNode._next !is null))) 1355 { 1356 // If it was a single node list, only delRef must be used 1357 // in order to avoid premature/double freeing 1358 destroyUnused(tmpNode); 1359 } 1360 } 1361 1362 /// 1363 static if (is(T == int)) 1364 @safe unittest 1365 { 1366 import std.algorithm.comparison : equal; 1367 1368 auto dl = DList!int(1, 2, 3); 1369 auto dl2 = dl; 1370 1371 assert(equal(dl, [1, 2, 3])); 1372 dl.popFront; 1373 dl.remove(); 1374 assert(equal(dl, [3])); 1375 assert(equal(dl2, [1, 3])); 1376 dl.popPrev; 1377 assert(equal(dl, [1, 3])); 1378 } 1379 1380 debug(CollectionDList) 1381 void printRefCount(Node *sn = null) 1382 { 1383 import std.stdio; 1384 writefln("DList.printRefCount: begin"); 1385 scope(exit) writefln("DList.printRefCount: end"); 1386 1387 Node *tmpNode; 1388 if (sn is null) 1389 tmpNode = _head; 1390 else 1391 tmpNode = sn; 1392 1393 while (tmpNode !is null && tmpNode._prev !is null) 1394 { 1395 // Rewind to the beginning of the list 1396 tmpNode = tmpNode._prev; 1397 } 1398 while (tmpNode !is null) 1399 { 1400 writefln("DList.printRefCount: Node %s has ref count %s", 1401 tmpNode._payload, *localPrefCount(tmpNode)); 1402 tmpNode = tmpNode._next; 1403 } 1404 } 1405 } 1406 1407 version (unittest) private nothrow pure @safe 1408 void testInit(RCIAllocator allocator) 1409 { 1410 import std.algorithm.comparison : equal; 1411 1412 DList!int dl = DList!int(allocator); 1413 assert(dl.empty); 1414 assert(dl.isUnique); 1415 int[] empty; 1416 assert(equal(dl, empty)); 1417 1418 DList!int dl2 = DList!int(allocator, 1); 1419 assert(equal(dl2, [1])); 1420 1421 DList!int dl3 = DList!int(allocator, 1, 2); 1422 assert(equal(dl3, [1, 2])); 1423 1424 DList!int dl4 = DList!int(allocator, [1]); 1425 assert(equal(dl4, [1])); 1426 1427 DList!int dl5 = DList!int(allocator, [1, 2]); 1428 assert(equal(dl5, [1, 2])); 1429 } 1430 1431 @safe unittest 1432 { 1433 import std.conv; 1434 SCAlloc statsCollectorAlloc; 1435 { 1436 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1437 () nothrow pure @safe { 1438 testInit(_allocator); 1439 }(); 1440 } 1441 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1442 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1443 ~ to!string(bytesUsed) ~ " bytes"); 1444 } 1445 1446 version (unittest) private nothrow pure @safe 1447 void testInsert(RCIAllocator allocator) 1448 { 1449 import std.algorithm.comparison : equal; 1450 import std.range.primitives : walkLength; 1451 1452 DList!int dl = DList!int(allocator, 1); 1453 size_t pos = 0; 1454 dl.insert(pos, 2); 1455 assert(equal(dl, [2, 1])); 1456 1457 DList!int dl2 = DList!int(allocator, 1); 1458 dl2.insert(pos, 2, 3); 1459 assert(equal(dl2, [2, 3, 1])); 1460 1461 DList!int dl3 = DList!int(allocator, 1, 2); 1462 dl3.insert(pos, 3); 1463 assert(equal(dl3, [3, 1, 2])); 1464 1465 DList!int dl4 = DList!int(allocator, 1, 2); 1466 dl4.insert(pos, 3, 4); 1467 assert(equal(dl4, [3, 4, 1, 2])); 1468 1469 DList!int dl5 = DList!int(allocator, 1, 2); 1470 dl5.popFront(); 1471 dl5.insert(pos, 3); 1472 assert(equal(dl5, [3, 2])); 1473 dl5.popPrev(); 1474 assert(equal(dl5, [1, 3, 2])); 1475 1476 DList!int dl6 = DList!int(allocator, 1, 2); 1477 dl6.popFront(); 1478 dl6.insert(pos, 3, 4); 1479 assert(equal(dl6, [3, 4, 2])); 1480 dl6.popPrev(); 1481 assert(equal(dl6, [1, 3, 4, 2])); 1482 dl6.insertBack(5); 1483 assert(equal(dl6, [1, 3, 4, 2, 5])); 1484 dl6.insertBack(6, 7); 1485 assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7])); 1486 dl6.insertBack([8]); 1487 assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8])); 1488 dl6.insertBack([9, 10]); 1489 assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8, 9, 10])); 1490 int[] empty; 1491 dl6.insertBack(empty); 1492 assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8, 9, 10])); 1493 dl6.insert(pos, empty); 1494 assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8, 9, 10])); 1495 1496 DList!int dl7 = DList!int(allocator, 1); 1497 assert(equal(dl7, [1])); 1498 dl7.insert(pos, 2); 1499 assert(equal(dl7, [2, 1])); 1500 pos = walkLength(dl7); 1501 dl7.insert(pos, 3); 1502 assert(equal(dl7, [2, 1, 3])); 1503 dl7.insert(pos, 4); 1504 assert(equal(dl7, [2, 1, 4, 3])); 1505 } 1506 1507 @safe unittest 1508 { 1509 import std.conv; 1510 SCAlloc statsCollectorAlloc; 1511 { 1512 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1513 () nothrow pure @safe { 1514 testInsert(_allocator); 1515 }(); 1516 } 1517 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1518 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1519 ~ to!string(bytesUsed) ~ " bytes"); 1520 } 1521 1522 version (unittest) private nothrow pure @safe 1523 void testRemove(RCIAllocator allocator) 1524 { 1525 import std.algorithm.comparison : equal; 1526 1527 DList!int dl = DList!int(allocator, 1); 1528 size_t pos = 0; 1529 dl.remove(); 1530 assert(dl.empty); 1531 assert(dl.isUnique); 1532 assert(!dl._allocator.isNull); 1533 1534 dl.insert(pos, 2); 1535 auto dl2 = dl; 1536 auto dl3 = dl; 1537 assert(!dl.isUnique); 1538 1539 dl.popFront(); 1540 assert(dl.empty); 1541 assert(!dl._allocator.isNull); 1542 1543 dl2.popPrev(); 1544 assert(dl2.empty); 1545 assert(dl3.isUnique); 1546 1547 auto dl4 = dl3; 1548 assert(!dl3.isUnique); 1549 dl4.remove(); 1550 assert(dl4.empty); 1551 assert(!dl3.empty); 1552 assert(dl3.isUnique); 1553 } 1554 1555 @safe unittest 1556 { 1557 import std.conv; 1558 SCAlloc statsCollectorAlloc; 1559 { 1560 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1561 () nothrow pure @safe { 1562 testRemove(_allocator); 1563 }(); 1564 } 1565 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1566 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1567 ~ to!string(bytesUsed) ~ " bytes"); 1568 } 1569 1570 version (unittest) private nothrow pure @safe 1571 void testCopyAndRef(RCIAllocator allocator) 1572 { 1573 import std.algorithm.comparison : equal; 1574 1575 auto dlFromList = DList!int(allocator, 1, 2, 3); 1576 auto dlFromRange = DList!int(allocator, dlFromList); 1577 assert(equal(dlFromList, dlFromRange)); 1578 1579 dlFromList.popFront(); 1580 assert(equal(dlFromList, [2, 3])); 1581 assert(equal(dlFromRange, [1, 2, 3])); 1582 1583 DList!int dlInsFromRange = DList!int(allocator); 1584 size_t pos = 0; 1585 dlInsFromRange.insert(pos, dlFromList); 1586 dlFromList.popFront(); 1587 assert(equal(dlFromList, [3])); 1588 assert(equal(dlInsFromRange, [2, 3])); 1589 1590 DList!int dlInsBackFromRange = DList!int(allocator); 1591 dlInsBackFromRange.insert(pos, dlFromList); 1592 dlFromList.popFront(); 1593 assert(dlFromList.empty); 1594 assert(equal(dlInsBackFromRange, [3])); 1595 1596 auto dlFromRef = dlInsFromRange; 1597 auto dlFromDup = dlInsFromRange.dup; 1598 assert(dlInsFromRange.front == 2); 1599 dlFromRef.front = 5; 1600 assert(dlInsFromRange.front == 5); 1601 assert(dlFromDup.front == 2); 1602 } 1603 1604 @safe unittest 1605 { 1606 import std.conv; 1607 SCAlloc statsCollectorAlloc; 1608 { 1609 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1610 () nothrow pure @safe { 1611 testCopyAndRef(_allocator); 1612 }(); 1613 } 1614 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1615 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1616 ~ to!string(bytesUsed) ~ " bytes"); 1617 } 1618 1619 @safe unittest 1620 { 1621 import std.algorithm.comparison : equal; 1622 1623 SCAlloc statsCollectorAlloc; 1624 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1625 1626 DList!int dl = DList!int(_allocator, 1, 2, 3); 1627 auto before = statsCollectorAlloc.bytesUsed; 1628 { 1629 DList!int dl2 = dl; 1630 dl2.popFront(); 1631 assert(equal(dl2, [2, 3])); 1632 } 1633 assert(before == statsCollectorAlloc.bytesUsed); 1634 assert(equal(dl, [1, 2, 3])); 1635 dl.tail(); 1636 } 1637 1638 version(unittest) private nothrow pure @safe 1639 void testImmutability(RCISharedAllocator allocator) 1640 { 1641 auto s = immutable DList!(int)(allocator, 1, 2, 3); 1642 auto s2 = s; 1643 auto s3 = s2.save(); 1644 1645 assert(s2.front == 1); 1646 static assert(!__traits(compiles, s2.front = 4)); 1647 static assert(!__traits(compiles, s2.popFront())); 1648 1649 auto s4 = s2.tail; 1650 assert(s4.front == 2); 1651 static assert(!__traits(compiles, s4 = s4.tail)); 1652 } 1653 1654 version(unittest) private nothrow pure @safe 1655 void testConstness(RCISharedAllocator allocator) 1656 { 1657 auto s = const DList!(int)(allocator, 1, 2, 3); 1658 auto s2 = s; 1659 auto s3 = s2.save(); 1660 1661 assert(s2.front == 1); 1662 static assert(!__traits(compiles, s2.front = 4)); 1663 static assert(!__traits(compiles, s2.popFront())); 1664 1665 auto s4 = s2.tail; 1666 assert(s4.front == 2); 1667 static assert(!__traits(compiles, s4 = s4.tail)); 1668 } 1669 1670 @safe unittest 1671 { 1672 import std.conv; 1673 import std.experimental.allocator : processAllocator; 1674 SCAlloc statsCollectorAlloc; 1675 { 1676 // TODO: StatsCollector need to be made shareable 1677 //auto _allocator = sharedAllocatorObject(&statsCollectorAlloc); 1678 () nothrow pure @safe { 1679 testConstness(processAllocatorObject()); 1680 testImmutability(processAllocatorObject()); 1681 }(); 1682 } 1683 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1684 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1685 ~ to!string(bytesUsed) ~ " bytes"); 1686 } 1687 1688 version(unittest) private nothrow pure @safe 1689 void testConcatAndAppend(RCIAllocator allocator) 1690 { 1691 import std.algorithm.comparison : equal; 1692 1693 auto dl = DList!(int)(allocator, 1, 2, 3); 1694 DList!(int) dl2 = DList!int(allocator); 1695 1696 auto dl3 = dl ~ dl2; 1697 assert(equal(dl3, [1, 2, 3])); 1698 1699 auto dl4 = dl3; 1700 dl3 = dl3 ~ 4; 1701 assert(equal(dl3, [1, 2, 3, 4])); 1702 dl3 = dl3 ~ [5]; 1703 assert(equal(dl3, [1, 2, 3, 4, 5])); 1704 assert(equal(dl4, [1, 2, 3])); 1705 1706 dl4 = dl3; 1707 dl3 ~= 6; 1708 assert(equal(dl3, [1, 2, 3, 4, 5, 6])); 1709 dl3 ~= [7]; 1710 assert(equal(dl3, [1, 2, 3, 4, 5, 6, 7])); 1711 assert(equal(dl4, [1, 2, 3, 4, 5, 6, 7])); 1712 1713 dl3 ~= dl3; 1714 assert(equal(dl3, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7])); 1715 assert(equal(dl4, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7])); 1716 1717 DList!int dl5 = DList!int(allocator); 1718 dl5 ~= [1, 2, 3]; 1719 assert(equal(dl5, [1, 2, 3])); 1720 } 1721 1722 @safe unittest 1723 { 1724 import std.conv; 1725 SCAlloc statsCollectorAlloc; 1726 { 1727 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1728 () nothrow pure @safe { 1729 testConcatAndAppend(_allocator); 1730 }(); 1731 } 1732 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1733 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1734 ~ to!string(bytesUsed) ~ " bytes"); 1735 } 1736 1737 version(unittest) private nothrow pure @safe 1738 void testAssign(RCIAllocator allocator) 1739 { 1740 import std.algorithm.comparison : equal; 1741 1742 auto dl = DList!int(allocator, 1, 2, 3); 1743 assert(equal(dl, [1, 2, 3])); 1744 { 1745 auto dl2 = DList!int(allocator, 4, 5, 6); 1746 dl = dl2; 1747 assert(equal(dl, dl2)); 1748 } 1749 assert(equal(dl, [4, 5, 6])); 1750 dl.popPrev(); 1751 assert(dl.empty); 1752 } 1753 1754 @safe unittest 1755 { 1756 import std.conv; 1757 SCAlloc statsCollectorAlloc; 1758 { 1759 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1760 () nothrow pure @safe { 1761 testAssign(_allocator); 1762 }(); 1763 } 1764 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1765 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1766 ~ to!string(bytesUsed) ~ " bytes"); 1767 } 1768 1769 version(unittest) private nothrow pure @safe 1770 void testWithStruct(RCIAllocator allocator, RCISharedAllocator sharedAlloc) 1771 { 1772 import std.algorithm.comparison : equal; 1773 1774 auto list = DList!int(allocator, 1, 2, 3); 1775 { 1776 auto listOfLists = DList!(DList!int)(allocator, list); 1777 size_t pos = 0; 1778 assert(equal(listOfLists.front, [1, 2, 3])); 1779 listOfLists.front.front = 2; 1780 assert(equal(listOfLists.front, [2, 2, 3])); 1781 static assert(!__traits(compiles, listOfLists.insert(pos, 1))); 1782 1783 auto immListOfLists = immutable DList!(DList!int)(sharedAlloc, list); 1784 assert(immListOfLists.front.front == 2); 1785 static assert(!__traits(compiles, immListOfLists.front.front = 2)); 1786 } 1787 assert(equal(list, [2, 2, 3])); 1788 } 1789 1790 @safe unittest 1791 { 1792 import std.conv; 1793 import std.experimental.allocator : processAllocator; 1794 SCAlloc statsCollectorAlloc; 1795 { 1796 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1797 () nothrow pure @safe { 1798 testWithStruct(_allocator, processAllocatorObject()); 1799 }(); 1800 } 1801 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1802 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1803 ~ to!string(bytesUsed) ~ " bytes"); 1804 } 1805 1806 version(unittest) private nothrow pure @safe 1807 void testWithClass(RCIAllocator allocator) 1808 { 1809 class MyClass 1810 { 1811 int x; 1812 this(int x) { this.x = x; } 1813 } 1814 1815 MyClass c = new MyClass(10); 1816 { 1817 auto dl = DList!MyClass(allocator, c); 1818 assert(dl.front.x == 10); 1819 assert(dl.front is c); 1820 dl.front.x = 20; 1821 } 1822 assert(c.x == 20); 1823 } 1824 1825 @safe unittest 1826 { 1827 import std.conv; 1828 SCAlloc statsCollectorAlloc; 1829 { 1830 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1831 () nothrow pure @safe { 1832 testWithClass(_allocator); 1833 }(); 1834 } 1835 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1836 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1837 ~ to!string(bytesUsed) ~ " bytes"); 1838 }