1 /// 2 module stdx.collections.slist; 3 4 import stdx.collections.common; 5 6 debug(CollectionSList) 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 SList(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 // TODO: should this be static struct? 33 struct Node 34 { 35 T _payload; 36 Node *_next; 37 38 this(T v, Node *n) 39 { 40 debug(CollectionSList) writefln("SList.Node.ctor: Constructing node" ~ 41 " with payload: %s", v); 42 _payload = v; 43 _next = n; 44 } 45 46 ~this() 47 { 48 debug(CollectionSList) writefln("SList.Node.dtor: Destroying node" ~ 49 " with payload: %s", _payload); 50 } 51 } 52 53 // State { 54 Node *_head; 55 mixin(allocatorHandler); 56 // } 57 58 @nogc nothrow pure @trusted 59 void addRef(QualNode, this Q)(QualNode node) 60 { 61 assert(node !is null); 62 cast(void) _allocator.opPrefix!("+=")(cast(void[Node.sizeof])(*node), 1); 63 } 64 65 void delRef(ref Node *node) 66 { 67 // Will be optimized away, but the type system infers T's safety 68 if (0) { T t = T.init; } 69 70 assert(node !is null); 71 //if (_allocator.opPrefix!("-=")(cast(void[Node.sizeof])(*node), 1) == 0) 72 //{ 73 //debug(CollectionSList) writefln("SList.delRef: Deleting node %s", node._payload); 74 //dispose(_allocator, node); 75 //node = null; 76 //} 77 () @trusted { 78 if (opCmpPrefix!"=="(node, 0)) 79 { 80 dispose(_allocator, node); 81 node = null; 82 } 83 else 84 { 85 cast(void) _allocator.opPrefix!("-=")(cast(void[Node.sizeof])(*node), 1); 86 } 87 }(); 88 } 89 90 pragma(inline, true) 91 @nogc nothrow pure @trusted 92 size_t opCmpPrefix(string op)(const Node *node, size_t val) const 93 if ((op == "==") || (op == "<=") || (op == "<") || (op == ">=") || (op == ">")) 94 { 95 return _allocator.opCmpPrefix!op(cast(void[Node.sizeof])(*node), val); 96 } 97 98 static string immutableInsert(string stuff) 99 { 100 return q{ 101 _allocator = immutable AllocatorHandler(allocator); 102 Node *tmpNode; 103 Node *tmpHead; 104 foreach (item; } ~ stuff ~ q{ ) 105 { 106 Node *newNode; 107 () @trusted { newNode = 108 _allocator.make!(Node)(item, null); 109 }(); 110 (tmpHead ? tmpNode._next : tmpHead) = newNode; 111 tmpNode = newNode; 112 } 113 _head = (() @trusted => cast(immutable Node*)(tmpHead))(); 114 }; 115 } 116 117 public: 118 /** 119 * Constructs a qualified singly linked list that will use the provided 120 * allocator object. For `immutable` objects, a `RCISharedAllocator` must 121 * be supplied. 122 * 123 * Params: 124 * allocator = a $(REF RCIAllocator, std,experimental,allocator) or 125 * $(REF RCISharedAllocator, std,experimental,allocator) 126 * allocator object 127 * 128 * Complexity: $(BIGOH 1) 129 */ 130 this(A, this Q)(A allocator) 131 if (!is(Q == shared) 132 && (is(A == RCISharedAllocator) || !is(Q == immutable)) 133 && (is(A == RCIAllocator) || is(A == RCISharedAllocator))) 134 { 135 debug(CollectionSList) 136 { 137 writefln("SList.ctor: begin"); 138 scope(exit) writefln("SList.ctor: end"); 139 } 140 static if (is(Q == immutable) || is(Q == const)) 141 { 142 T[] empty; 143 this(allocator, empty); 144 } 145 else 146 { 147 setAllocator(allocator); 148 } 149 } 150 151 /// 152 static if (is(T == int)) 153 @safe unittest 154 { 155 auto sl = SList!int(theAllocator); 156 auto csl = const SList!int(processAllocator); 157 auto isl = immutable SList!int(processAllocator); 158 } 159 160 /** 161 * Constructs a qualified singly linked list out of a number of items. 162 * Because no allocator was provided, the list will use the 163 * $(REF, GCAllocator, std,experimental,allocator). 164 * 165 * Params: 166 * values = a variable number of items, either in the form of a 167 * list or as a built-in array 168 * 169 * Complexity: $(BIGOH m), where `m` is the number of items. 170 */ 171 this(U, this Q)(U[] values...) 172 if (isImplicitlyConvertible!(U, T)) 173 { 174 this(defaultAllocator!(typeof(this)), values); 175 } 176 177 /// 178 static if (is(T == int)) 179 @safe unittest 180 { 181 import std.algorithm.comparison : equal; 182 183 // Create a list from a list of ints 184 { 185 auto sl = SList!int(1, 2, 3); 186 assert(equal(sl, [1, 2, 3])); 187 } 188 // Create a list from an array of ints 189 { 190 auto sl = SList!int([1, 2, 3]); 191 assert(equal(sl, [1, 2, 3])); 192 } 193 // Create a list from a list from an input range 194 { 195 auto sl = SList!int(1, 2, 3); 196 auto sl2 = SList!int(sl); 197 assert(equal(sl2, [1, 2, 3])); 198 } 199 } 200 201 /** 202 * Constructs a qualified singly linked list out of a number of items 203 * that will use the provided allocator object. 204 * For `immutable` objects, a `RCISharedAllocator` must be supplied. 205 * 206 * Params: 207 * allocator = a $(REF RCIAllocator, std,experimental,allocator) or 208 * $(REF RCISharedAllocator, std,experimental,allocator) 209 * allocator object 210 * values = a variable number of items, either in the form of a 211 * list or as a built-in array 212 * 213 * Complexity: $(BIGOH m), where `m` is the number of items. 214 */ 215 this(A, U, this Q)(A allocator, U[] values...) 216 if (!is(Q == shared) 217 && (is(A == RCISharedAllocator) || !is(Q == immutable)) 218 && (is(A == RCIAllocator) || is(A == RCISharedAllocator)) 219 && isImplicitlyConvertible!(U, T)) 220 { 221 debug(CollectionSList) 222 { 223 writefln("SList.ctor: begin"); 224 scope(exit) writefln("SList.ctor: end"); 225 } 226 static if (is(Q == immutable) || is(Q == const)) 227 { 228 mixin(immutableInsert("values")); 229 } 230 else 231 { 232 setAllocator(allocator); 233 insert(0, values); 234 } 235 } 236 237 /** 238 * Constructs a qualified singly linked list out of an 239 * $(REF_ALTTEXT input range, isInputRange, std,range,primitives). 240 * Because no allocator was provided, the list will use the 241 * $(REF, GCAllocator, std,experimental,allocator). 242 * 243 * Params: 244 * stuff = an input range of elements that are implitictly convertible 245 * to `T` 246 * 247 * Complexity: $(BIGOH m), where `m` is the number of elements in the range. 248 */ 249 this(Stuff, this Q)(Stuff stuff) 250 if (isInputRange!Stuff 251 && isImplicitlyConvertible!(ElementType!Stuff, T) 252 && !is(Stuff == T[])) 253 { 254 this(defaultAllocator!(typeof(this)), stuff); 255 } 256 257 /** 258 * Constructs a qualified singly linked list out of an 259 * $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 260 * that will use the provided allocator object. 261 * For `immutable` objects, a `RCISharedAllocator` must be supplied. 262 * 263 * Params: 264 * allocator = a $(REF RCIAllocator, std,experimental,allocator) or 265 * $(REF RCISharedAllocator, std,experimental,allocator) 266 * allocator object 267 * stuff = an input range of elements that are implitictly convertible 268 * to `T` 269 * 270 * Complexity: $(BIGOH m), where `m` is the number of elements in the range. 271 */ 272 this(A, Stuff, this Q)(A allocator, Stuff stuff) 273 if (!is(Q == shared) 274 && (is(A == RCISharedAllocator) || !is(Q == immutable)) 275 && (is(A == RCIAllocator) || is(A == RCISharedAllocator)) 276 && isInputRange!Stuff 277 && isImplicitlyConvertible!(ElementType!Stuff, T) 278 && !is(Stuff == T[])) 279 { 280 debug(CollectionSList) 281 { 282 writefln("SList.ctor: begin"); 283 scope(exit) writefln("SList.ctor: end"); 284 } 285 static if (is(Q == immutable) || is(Q == const)) 286 { 287 mixin(immutableInsert("stuff")); 288 } 289 else 290 { 291 setAllocator(allocator); 292 insert(0, stuff); 293 } 294 } 295 296 this(this) 297 { 298 debug(CollectionSList) 299 { 300 writefln("SList.postblit: begin"); 301 scope(exit) writefln("SList.postblit: end"); 302 } 303 _allocator.bootstrap(); 304 if (_head !is null) 305 { 306 addRef(_head); 307 debug(CollectionSList) writefln("SList.postblit: Node %s has refcount: %s", 308 _head._payload, *prefCount(_head)); 309 } 310 } 311 312 // Immutable ctors 313 // Very important to pass the allocator by ref! (Related to postblit bug) 314 private this(NodeQual, AllocQual, this Q)(NodeQual _newHead, ref AllocQual _newAllocator) 315 if (is(typeof(_head) : typeof(_newHead)) 316 && (is(Q == immutable) || is(Q == const))) 317 { 318 _head = _newHead; 319 // Needs a bootstrap 320 // bootstrap is the equivalent of incRef 321 _newAllocator.bootstrap(); 322 _allocator = _newAllocator; 323 if (_head !is null) 324 { 325 addRef(_head); 326 debug(CollectionSList) writefln("SList.ctor immutable: Node %s has " 327 ~ "refcount: %s", _head._payload, *sharedPrefCount(_head)); 328 } 329 } 330 331 ~this() 332 { 333 debug(CollectionSList) 334 { 335 writefln("SList.dtor: Begin for instance %s of type %s", 336 cast(size_t)(&this), typeof(this).stringof); 337 scope(exit) writefln("SList.dtor: End for instance %s of type %s", 338 cast(size_t)(&this), typeof(this).stringof); 339 } 340 destroyUnused(); 341 } 342 343 static if (is(T == int)) 344 nothrow pure @safe unittest 345 { 346 auto s = SList!int(1, 2, 3); 347 348 // Infer safety 349 static assert(!__traits(compiles, () @safe { SList!Unsafe(Unsafe(1)); })); 350 static assert(!__traits(compiles, () @safe { auto s = const SList!Unsafe(Unsafe(1)); })); 351 static assert(!__traits(compiles, () @safe { auto s = immutable SList!Unsafe(Unsafe(1)); })); 352 353 static assert(!__traits(compiles, () @safe { SList!UnsafeDtor(UnsafeDtor(1)); })); 354 static assert(!__traits(compiles, () @safe { auto s = const SList!UnsafeDtor(UnsafeDtor(1)); })); 355 static assert(!__traits(compiles, () @safe { auto s = immutable SList!UnsafeDtor(UnsafeDtor(1)); })); 356 357 // Infer purity 358 static assert(!__traits(compiles, () pure { SList!Impure(Impure(1)); })); 359 static assert(!__traits(compiles, () pure { auto s = const SList!Impure(Impure(1)); })); 360 static assert(!__traits(compiles, () pure { auto s = immutable SList!Impure(Impure(1)); })); 361 362 static assert(!__traits(compiles, () pure { SList!ImpureDtor(ImpureDtor(1)); })); 363 static assert(!__traits(compiles, () pure { auto s = const SList!ImpureDtor(ImpureDtor(1)); })); 364 static assert(!__traits(compiles, () pure { auto s = immutable SList!ImpureDtor(ImpureDtor(1)); })); 365 366 // Infer throwability 367 static assert(!__traits(compiles, () nothrow { SList!Throws(Throws(1)); })); 368 static assert(!__traits(compiles, () nothrow { auto s = const SList!Throws(Throws(1)); })); 369 static assert(!__traits(compiles, () nothrow { auto s = immutable SList!Throws(Throws(1)); })); 370 371 static assert(!__traits(compiles, () nothrow { SList!ThrowsDtor(ThrowsDtor(1)); })); 372 static assert(!__traits(compiles, () nothrow { auto s = const SList!ThrowsDtor(ThrowsDtor(1)); })); 373 static assert(!__traits(compiles, () nothrow { auto s = immutable SList!ThrowsDtor(ThrowsDtor(1)); })); 374 } 375 376 private void destroyUnused() 377 { 378 debug(CollectionSList) 379 { 380 writefln("SList.destoryUnused: begin"); 381 scope(exit) writefln("SList.destoryUnused: end"); 382 } 383 while (_head !is null && opCmpPrefix!"=="(_head, 0)) 384 { 385 debug(CollectionSList) writefln("SList.destoryUnused: One ref with head at %s", 386 _head._payload); 387 Node *tmpNode = _head; 388 _head = _head._next; 389 delRef(tmpNode); 390 } 391 392 if (_head !is null && opCmpPrefix!">"(_head, 0)) 393 { 394 // We reached a copy, so just remove the head ref, thus deleting 395 // the copy in constant time (we are undoing the postblit) 396 debug(CollectionSList) writefln("SList.destoryUnused: Multiple refs with head at %s", 397 _head._payload); 398 delRef(_head); 399 } 400 } 401 402 /** 403 * Check whether there are no more references to this list instance. 404 * 405 * Returns: 406 * `true` if this is the only reference to this list instance; 407 * `false` otherwise. 408 * 409 * Complexity: $(BIGOH n). 410 */ 411 bool isUnique() const 412 { 413 debug(CollectionSList) 414 { 415 writefln("SList.isUnique: begin"); 416 scope(exit) writefln("SList.isUnique: end"); 417 } 418 419 // TODO: change this to tail-impl for const/imm types 420 Node *tmpNode = (() @trusted => cast(Node*)_head)(); 421 while (tmpNode !is null) 422 { 423 if (opCmpPrefix!">"(tmpNode, 0)) 424 { 425 return false; 426 } 427 tmpNode = tmpNode._next; 428 } 429 return true; 430 } 431 432 /// 433 static if (is(T == int)) 434 @safe unittest 435 { 436 auto sl = SList!int(24, 42); 437 assert(sl.isUnique); 438 { 439 auto sl2 = sl; 440 assert(!sl.isUnique); 441 sl2.front = 0; 442 assert(sl.front == 0); 443 } // sl2 goes out of scope 444 assert(sl.isUnique); 445 } 446 447 /** 448 * Check if the list is empty. 449 * 450 * Returns: 451 * `true` if there are no nodes in the list; `false` otherwise. 452 * 453 * Complexity: $(BIGOH 1). 454 */ 455 @nogc nothrow pure @safe 456 bool empty() const 457 { 458 return _head is null; 459 } 460 461 /// 462 static if (is(T == int)) 463 @safe unittest 464 { 465 SList!int sl; 466 assert(sl.empty); 467 size_t pos = 0; 468 sl.insert(pos, 1); 469 assert(!sl.empty); 470 } 471 472 /** 473 * Provide access to the first element in the list. The user must check 474 * that the list isn't `empty`, prior to calling this function. 475 * 476 * Returns: 477 * a reference to the first element. 478 * 479 * Complexity: $(BIGOH 1). 480 */ 481 ref auto front(this _)() 482 { 483 assert(!empty, "SList.front: List is empty"); 484 return _head._payload; 485 } 486 487 /// 488 static if (is(T == int)) 489 @safe unittest 490 { 491 auto sl = SList!int(1, 2, 3); 492 assert(sl.front == 1); 493 sl.front = 0; 494 assert(sl.front == 0); 495 } 496 497 /** 498 * Advance to the next element in the list. The user must check 499 * that the list isn't `empty`, prior to calling this function. 500 * 501 * If there are no more references to the current element (which is being 502 * consumed), then the current element will be destroyed; this will call 503 * `T`'s dtor, if one is defined, and will collect it's resources. 504 * 505 * Complexity: $(BIGOH 1). 506 */ 507 void popFront() 508 { 509 debug(CollectionSList) 510 { 511 writefln("SList.popFront: begin"); 512 scope(exit) writefln("SList.popFront: end"); 513 } 514 assert(!empty, "SList.popFront: List is empty"); 515 516 Node *tmpNode = _head; 517 _head = _head._next; 518 if (opCmpPrefix!">"(tmpNode, 0) && _head !is null) 519 { 520 // If we have another copy of the list then the refcount 521 // must increase, otherwise it will remain the same 522 // This condition is needed because the recounting is zero based 523 addRef(_head); 524 } 525 delRef(tmpNode); 526 } 527 528 /// 529 static if (is(T == int)) 530 @safe unittest 531 { 532 auto a = [1, 2, 3]; 533 auto sl = SList!int(a); 534 size_t i = 0; 535 while (!sl.empty) 536 { 537 assert(sl.front == a[i++]); 538 sl.popFront; 539 } 540 assert(sl.empty); 541 } 542 543 /** 544 * Advance to the next element in the list. The user must check 545 * that the list isn't `empty`, prior to calling this function. 546 * 547 * This must be used in order to iterate through a `const` or `immutable` 548 * list. For a mutable list this is equivalent to calling `popFront`. 549 * 550 * Returns: 551 * a list that starts with the next element in the original list 552 * 553 * Complexity: $(BIGOH 1). 554 */ 555 Qualified tail(this Qualified)() 556 { 557 debug(CollectionSList) 558 { 559 writefln("SList.tail: begin"); 560 scope(exit) writefln("SList.tail: end"); 561 } 562 assert(!empty, "SList.tail: List is empty"); 563 564 static if (is(Qualified == immutable) || is(Qualified == const)) 565 { 566 return Qualified(_head._next, _allocator); 567 } 568 else 569 { 570 return .tail(this); 571 } 572 } 573 574 /// 575 static if (is(T == int)) 576 @safe unittest 577 { 578 auto isl = immutable SList!int([1, 2, 3]); 579 assert(isl.tail.front == 2); 580 } 581 582 /** 583 * Eagerly iterate over each element in the list and call `fun` over each 584 * element. This should be used to iterate through `const` and `immutable` 585 * lists. 586 * 587 * Normally, the entire list is iterated. If partial iteration (early stopping) 588 * is desired, `fun` needs to return a value of type 589 * $(REF Flag, std,typecons)`!"each"` (`Yes.each` to continue iteration, or 590 * `No.each` to stop). 591 * 592 * Params: 593 * fun = unary function to apply on each element of the list. 594 * 595 * Returns: 596 * `Yes.each` if it has iterated through all the elements in the list, 597 * or `No.each` otherwise. 598 * 599 * Complexity: $(BIGOH n). 600 */ 601 template each(alias fun) 602 { 603 import std.typecons : Flag, Yes, No; 604 import std.functional : unaryFun; 605 606 Flag!"each" each(this Q)() 607 if (is (typeof(unaryFun!fun(T.init)))) 608 { 609 alias fn = unaryFun!fun; 610 611 auto sl = SList!(const SList!T)(this); 612 while (!sl.empty && !sl.front.empty) 613 { 614 static if (!is(typeof(fn(T.init)) == Flag!"each")) 615 { 616 cast(void) fn(sl.front.front); 617 } 618 else 619 { 620 if (fn(sl.front.front) == No.each) 621 return No.each; 622 } 623 sl ~= sl.front.tail; 624 sl.popFront; 625 } 626 return Yes.each; 627 } 628 } 629 630 /// 631 static if (is(T == int)) 632 @safe unittest 633 { 634 import std.typecons : Flag, Yes, No; 635 636 auto isl = immutable SList!int([1, 2, 3]); 637 638 static bool foo(int x) { return x > 0; } 639 640 assert(isl.each!foo == Yes.each); 641 } 642 643 /** 644 * Perform a shallow copy of the list. 645 * 646 * Returns: 647 * a new reference to the current list. 648 * 649 * Complexity: $(BIGOH 1). 650 */ 651 ref Qualified save(this Qualified)() 652 { 653 debug(CollectionSList) 654 { 655 writefln("SList.save: begin"); 656 scope(exit) writefln("SList.save: end"); 657 } 658 return this; 659 } 660 661 /// 662 static if (is(T == int)) 663 @safe unittest 664 { 665 auto a = [1, 2, 3]; 666 auto sl = SList!int(a); 667 size_t i = 0; 668 669 auto tmp = sl.save; 670 while (!tmp.empty) 671 { 672 assert(tmp.front == a[i++]); 673 tmp.popFront; 674 } 675 assert(tmp.empty); 676 assert(!sl.empty); 677 } 678 679 /** 680 * Perform a copy of the list. This will create a new list that will copy 681 * the elements of the current list. This will `NOT` call `dup` on the 682 * elements of the list, regardless if `T` defines it or not. 683 * 684 * Returns: 685 * a new list. 686 * 687 * Complexity: $(BIGOH n). 688 */ 689 SList!T dup(this Q)() 690 { 691 debug(CollectionSList) 692 { 693 writefln("SList.dup: begin"); 694 scope(exit) writefln("SList.dup: end"); 695 } 696 697 SList!T result; 698 result._allocator = _allocator; 699 700 static if (is(Q == immutable) || is(Q == const)) 701 { 702 static void appendEach(ref SList!T sl, const SList!T isl) 703 { 704 if (isl.empty) return; 705 sl ~= isl.front; 706 appendEach(sl, isl.tail); 707 } 708 709 appendEach(result, this); 710 } 711 else 712 { 713 result.insert(0, this); 714 } 715 return result; 716 } 717 718 /// 719 static if (is(T == int)) 720 @safe unittest 721 { 722 import std.algorithm.comparison : equal; 723 724 auto stuff = [1, 2, 3]; 725 auto sl = immutable SList!int(stuff); 726 auto slDup = sl.dup; 727 assert(equal(slDup, stuff)); 728 slDup.front = 0; 729 assert(slDup.front == 0); 730 assert(sl.front == 1); 731 } 732 733 /** 734 * Inserts the elements of an 735 * $(REF_ALTTEXT input range, isInputRange, std,range,primitives), or a 736 * variable number of items, at the given `pos`. 737 * 738 * If no allocator was provided when the list was created, the 739 * $(REF, GCAllocator, std,experimental,allocator) will be used. 740 * 741 * Params: 742 * pos = a positive integer 743 * stuff = an input range of elements that are implitictly convertible 744 * to `T`; a variable number of items either in the form of a 745 * list or as a built-in array 746 * 747 * Returns: 748 * the number of elements inserted 749 * 750 * Complexity: $(BIGOH pos + m), where `m` is the number of elements in the range. 751 */ 752 size_t insert(Stuff)(size_t pos, Stuff stuff) 753 if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) 754 { 755 debug(CollectionSList) 756 { 757 writefln("SList.insert: begin"); 758 scope(exit) writefln("SList.insert: end"); 759 } 760 761 // Will be optimized away, but the type system infers T's safety 762 if (0) { T t = T.init; } 763 764 // Ensure we have an allocator. If it was already set, this will do nothing 765 auto a = threadAllocatorObject(); 766 setAllocator(a); 767 768 size_t result; 769 Node *tmpNode; 770 Node *tmpHead; 771 foreach (item; stuff) 772 { 773 Node *newNode; 774 () @trusted { newNode = _allocator.make!(Node)(item, null); }(); 775 (tmpHead ? tmpNode._next : tmpHead) = newNode; 776 tmpNode = newNode; 777 ++result; 778 } 779 780 if (!tmpNode) 781 { 782 return 0; 783 } 784 785 Node *needle = _head; 786 Node *needlePrev = null; 787 while (pos) 788 { 789 needlePrev = needle; 790 needle = needle._next; 791 --pos; 792 } 793 794 tmpNode._next = needle; 795 if (needlePrev is null) 796 { 797 _head = tmpHead; 798 } 799 else 800 { 801 needlePrev._next = tmpHead; 802 } 803 return result; 804 } 805 806 /// ditto 807 size_t insert(Stuff)(size_t pos, Stuff[] stuff...) 808 if (isImplicitlyConvertible!(Stuff, T)) 809 { 810 return insert(pos, stuff); 811 } 812 813 /// 814 static if (is(T == int)) 815 @safe unittest 816 { 817 import std.algorithm.comparison : equal; 818 819 auto s = SList!int(4, 5); 820 SList!int sl; 821 assert(sl.empty); 822 823 size_t pos = 0; 824 pos += sl.insert(pos, 1); 825 pos += sl.insert(pos, [2, 3]); 826 assert(equal(sl, [1, 2, 3])); 827 828 // insert from an input range 829 pos += sl.insert(pos, s); 830 assert(equal(sl, [1, 2, 3, 4, 5])); 831 s.front = 0; 832 assert(equal(sl, [1, 2, 3, 4, 5])); 833 } 834 835 /** 836 * Inserts the elements of an 837 * $(REF_ALTTEXT input range, isInputRange, std,range,primitives), or a 838 * variable number of items, at the end of the list. 839 * 840 * If no allocator was provided when the list was created, the 841 * $(REF, GCAllocator, std,experimental,allocator) will be used. 842 * 843 * Params: 844 * stuff = an input range of elements that are implitictly convertible 845 * to `T`; a variable number of items either in the form of a 846 * list or as a built-in array 847 * 848 * Returns: 849 * the number of elements inserted 850 * 851 * Complexity: $(BIGOH pos + m), where `m` is the number of elements in the range. 852 */ 853 size_t insertBack(Stuff)(Stuff stuff) 854 if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) 855 { 856 debug(CollectionSList) 857 { 858 writefln("SList.insertBack: begin"); 859 scope(exit) writefln("SList.insertBack: end"); 860 } 861 862 // Will be optimized away, but the type system infers T's safety 863 if (0) { T t = T.init; } 864 865 // Ensure we have an allocator. If it was already set, this will do nothing 866 auto a = threadAllocatorObject(); 867 setAllocator(a); 868 869 size_t result; 870 Node *tmpNode; 871 Node *tmpHead; 872 foreach (item; stuff) 873 { 874 Node *newNode; 875 () @trusted { newNode = _allocator.make!(Node)(item, null); }(); 876 (tmpHead ? tmpNode._next : tmpHead) = newNode; 877 tmpNode = newNode; 878 ++result; 879 } 880 881 if (!tmpNode) 882 { 883 return 0; 884 } 885 886 if (_head is null) 887 { 888 _head = tmpHead; 889 } 890 else 891 { 892 Node *endNode; 893 for (endNode = _head; endNode._next !is null; endNode = endNode._next) { } 894 endNode._next = tmpHead; 895 } 896 897 return result; 898 } 899 900 /// ditto 901 size_t insertBack(Stuff)(Stuff[] stuff...) 902 if (isImplicitlyConvertible!(Stuff, T)) 903 { 904 return insertBack(stuff); 905 } 906 907 /// 908 static if (is(T == int)) 909 @safe unittest 910 { 911 import std.algorithm.comparison : equal; 912 913 auto s = SList!int(4, 5); 914 SList!int sl; 915 assert(sl.empty); 916 917 sl.insertBack(1); 918 sl.insertBack([2, 3]); 919 assert(equal(sl, [1, 2, 3])); 920 921 // insert from an input range 922 sl.insertBack(s); 923 assert(equal(sl, [1, 2, 3, 4, 5])); 924 s.front = 0; 925 assert(equal(sl, [1, 2, 3, 4, 5])); 926 } 927 928 /** 929 * Create a new list that results from the concatenation of this list 930 * with `rhs`. 931 * 932 * Params: 933 * rhs = can be an element that is implicitly convertible to `T`, an 934 * input range of such elements, or another singly linked list 935 * 936 * Returns: 937 * the newly created list 938 * 939 * Complexity: $(BIGOH n + m), where `m` is the number of elements in `rhs`. 940 */ 941 auto ref opBinary(string op, U)(auto ref U rhs) 942 if (op == "~" && 943 //(is (U : const typeof(this)) 944 (is (U == typeof(this)) 945 || is (U : T) 946 || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T)) 947 )) 948 { 949 debug(CollectionSList) 950 { 951 writefln("SList.opBinary!~: begin"); 952 scope(exit) writefln("SList.opBinary!~: end"); 953 } 954 955 auto newList = this.dup(); 956 newList.insertBack(rhs); 957 return newList; 958 } 959 960 /// 961 static if (is(T == int)) 962 @safe unittest 963 { 964 import std.algorithm.comparison : equal; 965 966 auto sl = SList!int(1); 967 auto sl2 = sl ~ 2; 968 969 assert(equal(sl2, [1, 2])); 970 sl.front = 0; 971 assert(equal(sl2, [1, 2])); 972 } 973 974 /** 975 * Assign `rhs` to this list. The current list will now become another 976 * reference to `rhs`, unless `rhs` is `null`, in which case the current 977 * list will become empty. If `rhs` refers to the current list nothing will 978 * happen. 979 * 980 * All the previous list elements that have no more references to them 981 * will be destroyed; this leads to a $(BIGOH n) complexity. 982 * 983 * Params: 984 * rhs = a reference to a singly linked list 985 * 986 * Returns: 987 * a reference to this list 988 * 989 * Complexity: $(BIGOH n). 990 */ 991 auto ref opAssign()(auto ref typeof(this) rhs) 992 { 993 debug(CollectionSList) 994 { 995 writefln("SList.opAssign: begin"); 996 scope(exit) writefln("SList.opAssign: end"); 997 } 998 999 if (rhs._head !is null && _head is rhs._head) 1000 { 1001 return this; 1002 } 1003 1004 if (rhs._head !is null) 1005 { 1006 rhs.addRef(rhs._head); 1007 debug(CollectionSList) writefln("SList.opAssign: Node %s has refcount: %s", 1008 rhs._head._payload, *prefCount(rhs._head)); 1009 } 1010 destroyUnused(); 1011 _head = rhs._head; 1012 _allocator = rhs._allocator; 1013 return this; 1014 } 1015 1016 /// 1017 static if (is(T == int)) 1018 @safe unittest 1019 { 1020 import std.algorithm.comparison : equal; 1021 1022 auto sl = SList!int(1); 1023 auto sl2 = SList!int(1, 2); 1024 1025 sl = sl2; // this will free the old sl 1026 assert(equal(sl, [1, 2])); 1027 sl.front = 0; 1028 assert(equal(sl2, [0, 2])); 1029 } 1030 1031 /** 1032 * Append the elements of `rhs` at the end of the list. 1033 * 1034 * If no allocator was provided when the list was created, the 1035 * $(REF, GCAllocator, std,experimental,allocator) will be used. 1036 * 1037 * Params: 1038 * rhs = can be an element that is implicitly convertible to `T`, an 1039 * input range of such elements, or another singly linked list 1040 * 1041 * Returns: 1042 * a reference to this list 1043 * 1044 * Complexity: $(BIGOH n + m), where `m` is the number of elements in `rhs`. 1045 */ 1046 auto ref opOpAssign(string op, U)(auto ref U rhs) 1047 if (op == "~" && 1048 (is (U == typeof(this)) 1049 || is (U : T) 1050 || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T)) 1051 )) 1052 { 1053 debug(CollectionSList) 1054 { 1055 writefln("SList.opOpAssign!~: %s begin", typeof(this).stringof); 1056 scope(exit) writefln("SList.opOpAssign!~: %s end", typeof(this).stringof); 1057 } 1058 1059 insertBack(rhs); 1060 return this; 1061 } 1062 1063 /// 1064 static if (is(T == int)) 1065 @safe unittest 1066 { 1067 import std.algorithm.comparison : equal; 1068 1069 auto s = SList!int(4, 5); 1070 SList!int sl; 1071 assert(sl.empty); 1072 1073 sl ~= 1; 1074 sl ~= [2, 3]; 1075 assert(equal(sl, [1, 2, 3])); 1076 1077 // append an input range 1078 sl ~= s; 1079 assert(equal(sl, [1, 2, 3, 4, 5])); 1080 s.front = 0; 1081 assert(equal(sl, [1, 2, 3, 4, 5])); 1082 } 1083 1084 /** 1085 * Remove the element at the given `idx` from the list. If there are no 1086 * more references to the given element, then it will be destroyed. 1087 * 1088 * Params: 1089 * idx = a positive integer 1090 */ 1091 void remove(size_t idx = 0) 1092 { 1093 assert(!empty, "SList.remove: List is empty"); 1094 if (idx == 0) 1095 { 1096 popFront(); 1097 return; 1098 } 1099 1100 Node *tmpNode = _head; 1101 while(--idx != 0) 1102 { 1103 tmpNode = tmpNode._next; 1104 } 1105 Node *toDel = tmpNode._next; 1106 assert(toDel !is null, "SList.remove: Index out of bounds"); 1107 tmpNode._next = tmpNode._next._next; 1108 delRef(toDel); 1109 } 1110 1111 /// 1112 static if (is(T == int)) 1113 @safe unittest 1114 { 1115 import std.algorithm.comparison : equal; 1116 1117 auto sl = SList!int(1, 2, 3); 1118 auto sl2 = sl; 1119 auto pos = 1; 1120 1121 assert(equal(sl, [1, 2, 3])); 1122 sl.remove(pos); 1123 assert(equal(sl, [1, 3])); 1124 assert(equal(sl2, [1, 3])); 1125 } 1126 1127 debug(CollectionSList) 1128 void printRefCount() const 1129 { 1130 writefln("SList.printRefCount: begin"); 1131 scope(exit) writefln("SList.printRefCount: end"); 1132 1133 Node *tmpNode = (() @trusted => cast(Node*)_head)(); 1134 while (tmpNode !is null) 1135 { 1136 writefln("SList.printRefCount: Node %s has ref count %s", 1137 tmpNode._payload, *localPrefCount(tmpNode)); 1138 tmpNode = tmpNode._next; 1139 } 1140 } 1141 } 1142 1143 version(unittest) private nothrow pure @safe 1144 void testImmutability(RCISharedAllocator allocator) 1145 { 1146 auto s = immutable SList!(int)(allocator, 1, 2, 3); 1147 auto s2 = s; 1148 auto s3 = s2.save(); 1149 1150 assert(s2.front == 1); 1151 static assert(!__traits(compiles, s2.front = 4)); 1152 static assert(!__traits(compiles, s2.popFront())); 1153 1154 auto s4 = s2.tail; 1155 assert(s4.front == 2); 1156 static assert(!__traits(compiles, s4 = s4.tail)); 1157 } 1158 1159 version(unittest) private nothrow pure @safe 1160 void testConstness(RCISharedAllocator allocator) 1161 { 1162 auto s = const SList!(int)(allocator, 1, 2, 3); 1163 auto s2 = s; 1164 auto s3 = s2.save(); 1165 1166 assert(s2.front == 1); 1167 static assert(!__traits(compiles, s2.front = 4)); 1168 static assert(!__traits(compiles, s2.popFront())); 1169 1170 auto s4 = s2.tail; 1171 assert(s4.front == 2); 1172 static assert(!__traits(compiles, s4 = s4.tail)); 1173 } 1174 1175 @safe unittest 1176 { 1177 import std.conv; 1178 import std.experimental.allocator : processAllocator; 1179 SCAlloc statsCollectorAlloc; 1180 { 1181 // TODO: StatsCollector need to be made shareable 1182 //auto _allocator = sharedAllocatorObject(&statsCollectorAlloc); 1183 () nothrow pure @safe { 1184 testImmutability(processAllocatorObject()); 1185 testConstness(processAllocatorObject()); 1186 }(); 1187 } 1188 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1189 assert(bytesUsed == 0, "SList ref count leaks memory; leaked " 1190 ~ to!string(bytesUsed) ~ " bytes"); 1191 } 1192 1193 version(unittest) private nothrow pure @safe 1194 void testConcatAndAppend(RCIAllocator allocator) 1195 { 1196 import std.algorithm.comparison : equal; 1197 1198 auto sl = SList!(int)(allocator, 1, 2, 3); 1199 SList!(int) sl2 = SList!int(allocator); 1200 1201 auto sl3 = sl ~ sl2; 1202 assert(equal(sl3, [1, 2, 3])); 1203 1204 auto sl4 = sl3; 1205 sl3 = sl3 ~ 4; 1206 assert(equal(sl3, [1, 2, 3, 4])); 1207 sl3 = sl3 ~ [5]; 1208 assert(equal(sl3, [1, 2, 3, 4, 5])); 1209 assert(equal(sl4, [1, 2, 3])); 1210 1211 sl4 = sl3; 1212 sl3 ~= 6; 1213 assert(equal(sl3, [1, 2, 3, 4, 5, 6])); 1214 sl3 ~= [7]; 1215 assert(equal(sl3, [1, 2, 3, 4, 5, 6, 7])); 1216 assert(equal(sl4, [1, 2, 3, 4, 5, 6, 7])); 1217 1218 sl3 ~= sl3; 1219 assert(equal(sl3, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7])); 1220 assert(equal(sl4, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7])); 1221 1222 SList!int sl5 = SList!int(allocator); 1223 sl5 ~= [1, 2, 3]; 1224 assert(equal(sl5, [1, 2, 3])); 1225 } 1226 1227 @safe unittest 1228 { 1229 import std.conv; 1230 SCAlloc statsCollectorAlloc; 1231 { 1232 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1233 () nothrow pure @safe { 1234 testConcatAndAppend(_allocator); 1235 }(); 1236 } 1237 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1238 assert(bytesUsed == 0, "SList ref count leaks memory; leaked " 1239 ~ to!string(bytesUsed) ~ " bytes"); 1240 } 1241 1242 version(unittest) private nothrow pure @safe 1243 void testSimple(RCIAllocator allocator) 1244 { 1245 import std.algorithm.comparison : equal; 1246 import std.algorithm.searching : canFind; 1247 import std.range.primitives : walkLength; 1248 1249 auto sl = SList!int(allocator); 1250 assert(sl.empty); 1251 assert(sl.isUnique); 1252 1253 size_t pos = 0; 1254 sl.insert(pos, 1, 2, 3); 1255 assert(sl.front == 1); 1256 assert(equal(sl, sl)); 1257 assert(equal(sl, [1, 2, 3])); 1258 1259 sl.popFront(); 1260 assert(sl.front == 2); 1261 assert(equal(sl, [2, 3])); 1262 1263 sl.insert(pos, [4, 5, 6]); 1264 sl.insert(pos, 7); 1265 sl.insert(pos, [8]); 1266 assert(equal(sl, [8, 7, 4, 5, 6, 2, 3])); 1267 1268 sl.insertBack(0, 1); 1269 sl.insertBack([-1, -2]); 1270 assert(equal(sl, [8, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2])); 1271 1272 sl.front = 9; 1273 assert(equal(sl, [9, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2])); 1274 1275 auto slTail = sl.tail; 1276 assert(slTail.front == 7); 1277 slTail.front = 8; 1278 assert(slTail.front == 8); 1279 assert(sl.tail.front == 8); 1280 assert(!sl.isUnique); 1281 1282 assert(canFind(sl, 2)); 1283 assert(!canFind(sl, -10)); 1284 1285 sl.remove(); 1286 assert(equal(sl, [8, 4, 5, 6, 2, 3, 0, 1, -1, -2])); 1287 sl.remove(2); 1288 assert(equal(sl, [8, 4, 6, 2, 3, 0, 1, -1, -2])); 1289 sl.remove(walkLength(sl) - 1); 1290 assert(equal(sl, [8, 4, 6, 2, 3, 0, 1, -1])); 1291 pos = 1; 1292 sl.insert(pos, 10); 1293 assert(equal(sl, [8, 10, 4, 6, 2, 3, 0, 1, -1])); 1294 } 1295 1296 @safe unittest 1297 { 1298 import std.conv; 1299 SCAlloc statsCollectorAlloc; 1300 { 1301 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1302 () nothrow pure @safe { 1303 testSimple(_allocator); 1304 }(); 1305 } 1306 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1307 assert(bytesUsed == 0, "SList ref count leaks memory; leaked " 1308 ~ to!string(bytesUsed) ~ " bytes"); 1309 } 1310 1311 version(unittest) private nothrow pure @safe 1312 void testSimpleImmutable(RCIAllocator allocator) 1313 { 1314 import std.algorithm.comparison : equal; 1315 import std.algorithm.searching : canFind; 1316 import std.range.primitives : walkLength; 1317 1318 auto sl = SList!(immutable int)(allocator); 1319 assert(sl.empty); 1320 1321 size_t pos = 0; 1322 sl.insert(pos, 1, 2, 3); 1323 assert(sl.front == 1); 1324 assert(equal(sl, sl)); 1325 assert(equal(sl, [1, 2, 3])); 1326 1327 sl.popFront(); 1328 assert(sl.front == 2); 1329 assert(equal(sl, [2, 3])); 1330 assert(sl.tail.front == 3); 1331 1332 sl.insert(pos, [4, 5, 6]); 1333 sl.insert(pos, 7); 1334 sl.insert(pos, [8]); 1335 assert(equal(sl, [8, 7, 4, 5, 6, 2, 3])); 1336 1337 sl.insertBack(0, 1); 1338 sl.insertBack([-1, -2]); 1339 assert(equal(sl, [8, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2])); 1340 1341 // Cannot modify immutable values 1342 static assert(!__traits(compiles, sl.front = 9)); 1343 1344 assert(canFind(sl, 2)); 1345 assert(!canFind(sl, -10)); 1346 1347 sl.remove(); 1348 assert(equal(sl, [7, 4, 5, 6, 2, 3, 0, 1, -1, -2])); 1349 sl.remove(2); 1350 assert(equal(sl, [7, 4, 6, 2, 3, 0, 1, -1, -2])); 1351 sl.remove(walkLength(sl) - 1); 1352 assert(equal(sl, [7, 4, 6, 2, 3, 0, 1, -1])); 1353 } 1354 1355 @safe unittest 1356 { 1357 import std.conv; 1358 SCAlloc statsCollectorAlloc; 1359 { 1360 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1361 () nothrow pure @safe { 1362 testSimpleImmutable(_allocator); 1363 }(); 1364 } 1365 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1366 assert(bytesUsed == 0, "SList ref count leaks memory; leaked " 1367 ~ to!string(bytesUsed) ~ " bytes"); 1368 } 1369 1370 version(unittest) private nothrow pure @safe 1371 void testCopyAndRef(RCIAllocator allocator) 1372 { 1373 import std.algorithm.comparison : equal; 1374 1375 auto slFromList = SList!int(allocator, 1, 2, 3); 1376 auto slFromRange = SList!int(allocator, slFromList); 1377 assert(equal(slFromList, slFromRange)); 1378 1379 slFromList.popFront(); 1380 assert(equal(slFromList, [2, 3])); 1381 assert(equal(slFromRange, [1, 2, 3])); 1382 1383 SList!int slInsFromRange = SList!int(allocator); 1384 size_t pos = 0; 1385 slInsFromRange.insert(pos, slFromList); 1386 slFromList.popFront(); 1387 assert(equal(slFromList, [3])); 1388 assert(equal(slInsFromRange, [2, 3])); 1389 1390 SList!int slInsBackFromRange = SList!int(allocator); 1391 slInsBackFromRange.insert(pos, slFromList); 1392 slFromList.popFront(); 1393 assert(slFromList.empty); 1394 assert(equal(slInsBackFromRange, [3])); 1395 1396 auto slFromRef = slInsFromRange; 1397 auto slFromDup = slInsFromRange.dup; 1398 assert(slInsFromRange.front == 2); 1399 slFromRef.front = 5; 1400 assert(slInsFromRange.front == 5); 1401 assert(slFromDup.front == 2); 1402 } 1403 1404 @safe unittest 1405 { 1406 import std.conv; 1407 SCAlloc statsCollectorAlloc; 1408 { 1409 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1410 () nothrow pure @safe { 1411 testCopyAndRef(_allocator); 1412 }(); 1413 } 1414 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1415 assert(bytesUsed == 0, "SList ref count leaks memory; leaked " 1416 ~ to!string(bytesUsed) ~ " bytes"); 1417 } 1418 1419 version(unittest) private nothrow pure @safe 1420 void testWithStruct(RCIAllocator allocator, RCISharedAllocator sharedAlloc) 1421 { 1422 import std.algorithm.comparison : equal; 1423 1424 auto list = SList!int(allocator, 1, 2, 3); 1425 { 1426 auto listOfLists = SList!(SList!int)(allocator, list); 1427 assert(equal(listOfLists.front, [1, 2, 3])); 1428 listOfLists.front.front = 2; 1429 assert(equal(listOfLists.front, [2, 2, 3])); 1430 size_t pos = 0; 1431 static assert(!__traits(compiles, listOfLists.insert(pos, 1))); 1432 1433 auto immListOfLists = immutable SList!(SList!int)(sharedAlloc, list); 1434 assert(immListOfLists.front.front == 2); 1435 static assert(!__traits(compiles, immListOfLists.front.front = 2)); 1436 } 1437 assert(equal(list, [2, 2, 3])); 1438 } 1439 1440 @safe unittest 1441 { 1442 import std.conv; 1443 import std.experimental.allocator : processAllocator; 1444 SCAlloc statsCollectorAlloc; 1445 { 1446 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1447 () nothrow pure @safe { 1448 testWithStruct(_allocator, processAllocatorObject()); 1449 }(); 1450 } 1451 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1452 assert(bytesUsed == 0, "SList ref count leaks memory; leaked " 1453 ~ to!string(bytesUsed) ~ " bytes"); 1454 } 1455 1456 version(unittest) private nothrow pure @safe 1457 void testWithClass(RCIAllocator allocator) 1458 { 1459 class MyClass 1460 { 1461 int x; 1462 this(int x) { this.x = x; } 1463 } 1464 1465 MyClass c = new MyClass(10); 1466 { 1467 auto sl = SList!MyClass(allocator, c); 1468 assert(sl.front.x == 10); 1469 assert(sl.front is c); 1470 sl.front.x = 20; 1471 } 1472 assert(c.x == 20); 1473 } 1474 1475 @safe unittest 1476 { 1477 import std.conv; 1478 SCAlloc statsCollectorAlloc; 1479 { 1480 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1481 () nothrow pure @safe { 1482 testWithClass(_allocator); 1483 }(); 1484 } 1485 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1486 assert(bytesUsed == 0, "SList ref count leaks memory; leaked " 1487 ~ to!string(bytesUsed) ~ " bytes"); 1488 }