1 module stdx.collection.slist; 2 3 import stdx.collection.common; 4 5 debug(CollectionSList) 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 SList(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 31 this(T v, Node *n) 32 { 33 debug(CollectionSList) writefln("SList.Node.ctor: Constructing node" ~ 34 " with payload: %s", v); 35 _payload = v; 36 _next = n; 37 } 38 39 ~this() 40 { 41 debug(CollectionSList) writefln("SList.Node.dtor: Destroying node" ~ 42 " with payload: %s", _payload); 43 } 44 } 45 46 Node *_head; 47 48 alias MutableAlloc = AffixAllocator!(IAllocator, size_t); 49 Mutable!MutableAlloc _ouroborosAllocator; 50 51 /// Returns the actual allocator from ouroboros 52 @trusted ref auto allocator(this _)() 53 { 54 assert(!_ouroborosAllocator.isNull); 55 return _ouroborosAllocator.get(); 56 } 57 58 /// Constructs the ouroboros allocator from allocator if the ouroboros 59 //allocator wasn't previously set 60 @trusted bool setAllocator(IAllocator allocator) 61 { 62 if (_ouroborosAllocator.isNull) 63 { 64 _ouroborosAllocator = Mutable!(MutableAlloc)(allocator, 65 MutableAlloc(allocator)); 66 return true; 67 } 68 return false; 69 } 70 71 @trusted IAllocator getAllocator(this _)() 72 { 73 return _ouroborosAllocator.isNull ? null : allocator().parent; 74 } 75 76 @trusted void addRef(QualNode, this Qualified)(QualNode node) 77 { 78 assert(node !is null); 79 debug(CollectionSList) 80 { 81 writefln("SList.addRef: Node %s has refcount: %s; will be: %s", 82 node._payload, *prefCount(node), *prefCount(node) + 1); 83 } 84 static if (is(Qualified == immutable) || is(Qualified == const)) 85 { 86 atomicOp!"+="(*prefCount(node), 1); 87 } 88 else 89 { 90 ++*prefCount(node); 91 } 92 } 93 94 @trusted void delRef(ref Node *node) 95 { 96 assert(node !is null); 97 size_t *pref = prefCount(node); 98 debug(CollectionSList) writefln("SList.delRef: Node %s has refcount: %s; will be: %s", 99 node._payload, *pref, *pref - 1); 100 if (*pref == 0) 101 { 102 debug(CollectionSList) writefln("SList.delRef: Deleting node %s", node._payload); 103 allocator.dispose(node); 104 node = null; 105 } 106 else 107 { 108 --*pref; 109 } 110 } 111 112 @trusted auto prefCount(QualNode, this Qualified)(QualNode node) 113 { 114 assert(node !is null); 115 static if (is(Qualified == immutable) || is(Qualified == const)) 116 { 117 return cast(shared size_t*)(&allocator.prefix(cast(void[Node.sizeof])(*node))); 118 } 119 else 120 { 121 return cast(size_t*)(&allocator.prefix(cast(void[Node.sizeof])(*node))); 122 } 123 } 124 125 static string immutableInsert(string stuff) 126 { 127 return "" 128 ~"auto tmpAlloc = Mutable!(MutableAlloc)(allocator, MutableAlloc(allocator));" 129 ~"_ouroborosAllocator = (() @trusted => cast(immutable)(tmpAlloc))();" 130 ~"Node *tmpNode;" 131 ~"Node *tmpHead;" 132 ~"foreach (item; " ~ stuff ~ ")" 133 ~"{" 134 ~"Node *newNode;" 135 ~"() @trusted { newNode =" 136 ~"tmpAlloc.get().make!(Node)(item, null);" 137 ~"}();" 138 ~"(tmpHead ? tmpNode._next : tmpHead) = newNode;" 139 ~"tmpNode = newNode;" 140 ~"}" 141 ~"_head = (() @trusted => cast(immutable Node*)(tmpHead))();"; 142 } 143 144 public: 145 this(this _)(IAllocator allocator) 146 { 147 debug(CollectionSList) 148 { 149 writefln("SList.ctor: begin"); 150 scope(exit) writefln("SList.ctor: end"); 151 } 152 setAllocator(allocator); 153 } 154 155 this(U, this Qualified)(U[] values...) 156 if (isImplicitlyConvertible!(U, T)) 157 { 158 this(theAllocator, values); 159 } 160 161 this(U, this Qualified)(IAllocator allocator, U[] values...) 162 if (isImplicitlyConvertible!(U, T)) 163 { 164 debug(CollectionSList) 165 { 166 writefln("SList.ctor: begin"); 167 scope(exit) writefln("SList.ctor: end"); 168 } 169 static if (is(Qualified == immutable) || is(Qualified == const)) 170 { 171 mixin(immutableInsert("values")); 172 } 173 else 174 { 175 setAllocator(allocator); 176 insert(0, values); 177 } 178 } 179 180 this(Stuff, this Qualified)(Stuff stuff) 181 if (isInputRange!Stuff 182 && isImplicitlyConvertible!(ElementType!Stuff, T) 183 && !is(Stuff == T[])) 184 { 185 this(theAllocator, stuff); 186 } 187 188 this(Stuff, this Qualified)(IAllocator allocator, Stuff stuff) 189 if (isInputRange!Stuff 190 && isImplicitlyConvertible!(ElementType!Stuff, T) 191 && !is(Stuff == T[])) 192 { 193 debug(CollectionSList) 194 { 195 writefln("SList.ctor: begin"); 196 scope(exit) writefln("SList.ctor: end"); 197 } 198 static if (is(Qualified == immutable) || is(Qualified == const)) 199 { 200 mixin(immutableInsert("stuff")); 201 } 202 else 203 { 204 setAllocator(allocator); 205 insert(0, stuff); 206 } 207 } 208 209 this(this) 210 { 211 debug(CollectionSList) 212 { 213 writefln("SList.postblit: begin"); 214 scope(exit) writefln("SList.postblit: end"); 215 } 216 if (_head !is null) 217 { 218 addRef(_head); 219 debug(CollectionSList) writefln("SList.postblit: Node %s has refcount: %s", 220 _head._payload, *prefCount(_head)); 221 } 222 } 223 224 // Immutable ctors 225 private this(NodeQual, OuroQual, this Qualified)(NodeQual _newHead, 226 OuroQual ouroborosAllocator) 227 if (is(typeof(_head) : typeof(_newHead)) 228 && (is(Qualified == immutable) || is(Qualified == const))) 229 { 230 _head = _newHead; 231 _ouroborosAllocator = ouroborosAllocator; 232 if (_head !is null) 233 { 234 addRef(_head); 235 debug(CollectionSList) writefln("SList.ctor immutable: Node %s has " 236 ~ "refcount: %s", _head._payload, *prefCount(_head)); 237 } 238 } 239 240 @trusted ~this() 241 { 242 debug(CollectionSList) 243 { 244 writefln("SList.dtor: Begin for instance %s of type %s", 245 cast(size_t)(&this), typeof(this).stringof); 246 scope(exit) writefln("SList.dtor: End for instance %s of type %s", 247 cast(size_t)(&this), typeof(this).stringof); 248 } 249 destroyUnused(); 250 } 251 252 void destroyUnused() 253 { 254 debug(CollectionSList) 255 { 256 writefln("SList.destoryUnused: begin"); 257 scope(exit) writefln("SList.destoryUnused: end"); 258 } 259 while (_head !is null && *prefCount(_head) == 0) 260 { 261 debug(CollectionSList) writefln("SList.destoryUnused: One ref with head at %s", 262 _head._payload); 263 Node *tmpNode = _head; 264 _head = _head._next; 265 delRef(tmpNode); 266 } 267 268 if (_head !is null && *prefCount(_head) > 0) 269 { 270 // We reached a copy, so just remove the head ref, thus deleting 271 // the copy in constant time (we are undoing the postblit) 272 debug(CollectionSList) writefln("SList.destoryUnused: Multiple refs with head at %s", 273 _head._payload); 274 delRef(_head); 275 } 276 } 277 278 bool isUnique(this _)() 279 { 280 debug(CollectionSList) 281 { 282 writefln("SList.isUnique: begin"); 283 scope(exit) writefln("SList.isUnique: end"); 284 } 285 286 Node *tmpNode = (() @trusted => cast(Node*)_head)(); 287 while (tmpNode !is null) 288 { 289 if (*prefCount(tmpNode) > 0) 290 { 291 return false; 292 } 293 tmpNode = tmpNode._next; 294 } 295 return true; 296 } 297 298 bool empty(this _)() 299 { 300 return _head is null; 301 } 302 303 ref auto front(this _)() 304 { 305 assert(!empty, "SList.front: List is empty"); 306 return _head._payload; 307 } 308 309 void popFront() 310 { 311 debug(CollectionSList) 312 { 313 writefln("SList.popFront: begin"); 314 scope(exit) writefln("SList.popFront: end"); 315 } 316 assert(!empty, "SList.popFront: List is empty"); 317 318 Node *tmpNode = _head; 319 _head = _head._next; 320 if (*prefCount(tmpNode) > 0 && _head !is null) 321 { 322 // If we have another copy of the list then the refcount 323 // must increase, otherwise it will remain the same 324 // This condition is needed because the recounting is zero based 325 addRef(_head); 326 } 327 delRef(tmpNode); 328 } 329 330 Qualified tail(this Qualified)() 331 { 332 debug(CollectionSList) 333 { 334 writefln("SList.tail: begin"); 335 scope(exit) writefln("SList.tail: end"); 336 } 337 assert(!empty, "SList.tail: List is empty"); 338 339 static if (is(Qualified == immutable) || is(Qualified == const)) 340 { 341 return typeof(this)(_head._next, _ouroborosAllocator); 342 } 343 else 344 { 345 return .tail(this); 346 } 347 } 348 349 ref Qualified save(this Qualified)() 350 { 351 debug(CollectionSList) 352 { 353 writefln("SList.save: begin"); 354 scope(exit) writefln("SList.save: end"); 355 } 356 return this; 357 } 358 359 typeof(this) dup() 360 { 361 debug(CollectionSList) 362 { 363 writefln("SList.dup: begin"); 364 scope(exit) writefln("SList.dup: end"); 365 } 366 IAllocator alloc = getAllocator(); 367 if (alloc is null) 368 { 369 alloc = theAllocator; 370 } 371 return typeof(this)(alloc, this); 372 } 373 374 size_t insert(Stuff)(size_t pos, Stuff stuff) 375 if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) 376 { 377 debug(CollectionSList) 378 { 379 writefln("SList.insert: begin"); 380 scope(exit) writefln("SList.insert: end"); 381 } 382 // Ensure we have an allocator. If it was already set, this will do nothing 383 setAllocator(theAllocator); 384 385 size_t result; 386 Node *tmpNode; 387 Node *tmpHead; 388 foreach (item; stuff) 389 { 390 Node *newNode; 391 () @trusted { newNode = allocator.make!(Node)(item, null); }(); 392 (tmpHead ? tmpNode._next : tmpHead) = newNode; 393 tmpNode = newNode; 394 ++result; 395 } 396 397 if (!tmpNode) 398 { 399 return 0; 400 } 401 402 Node *needle = _head; 403 Node *needlePrev = null; 404 while (pos) 405 { 406 needlePrev = needle; 407 needle = needle._next; 408 --pos; 409 } 410 411 tmpNode._next = needle; 412 if (needlePrev is null) 413 { 414 _head = tmpHead; 415 } 416 else 417 { 418 needlePrev._next = tmpHead; 419 } 420 return result; 421 } 422 423 size_t insert(Stuff)(size_t pos, Stuff[] stuff...) 424 if (isImplicitlyConvertible!(Stuff, T)) 425 { 426 return insert(pos, stuff); 427 } 428 429 size_t insertBack(Stuff)(Stuff stuff) 430 if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) 431 { 432 debug(CollectionSList) 433 { 434 writefln("SList.insertBack: begin"); 435 scope(exit) writefln("SList.insertBack: end"); 436 } 437 // Ensure we have an allocator. If it was already set, this will do nothing 438 setAllocator(theAllocator); 439 440 size_t result; 441 Node *tmpNode; 442 Node *tmpHead; 443 foreach (item; stuff) 444 { 445 Node *newNode; 446 () @trusted { newNode = allocator.make!(Node)(item, null); }(); 447 (tmpHead ? tmpNode._next : tmpHead) = newNode; 448 tmpNode = newNode; 449 ++result; 450 } 451 452 if (!tmpNode) 453 { 454 return 0; 455 } 456 457 if (_head is null) 458 { 459 _head = tmpHead; 460 } 461 else 462 { 463 Node *endNode; 464 for (endNode = _head; endNode._next !is null; endNode = endNode._next) { } 465 endNode._next = tmpHead; 466 } 467 468 return result; 469 } 470 471 size_t insertBack(Stuff)(Stuff[] stuff...) 472 if (isImplicitlyConvertible!(Stuff, T)) 473 { 474 return insertBack(stuff); 475 } 476 477 auto ref opBinary(string op, U)(auto ref U rhs) 478 if (op == "~" && 479 (is (U == typeof(this)) 480 || is (U : T) 481 || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T)) 482 )) 483 { 484 debug(CollectionSList) 485 { 486 writefln("SList.opBinary!~: begin"); 487 scope(exit) writefln("SList.opBinary!~: end"); 488 } 489 490 IAllocator alloc = getAllocator(); 491 if (alloc is null) 492 { 493 static if (is(U == typeof(this))) 494 { 495 alloc = rhs.getAllocator(); 496 } 497 else 498 { 499 alloc = null; 500 } 501 if (alloc is null) 502 { 503 alloc = theAllocator; 504 } 505 } 506 typeof(this) newList = typeof(this)(alloc, rhs); 507 newList.insert(0, this); 508 return newList; 509 } 510 511 auto ref opAssign()(auto ref typeof(this) rhs) 512 { 513 debug(CollectionSList) 514 { 515 writefln("SList.opAssign: begin"); 516 scope(exit) writefln("SList.opAssign: end"); 517 } 518 519 if (rhs._head !is null && _head is rhs._head) 520 { 521 return this; 522 } 523 524 if (rhs._head !is null) 525 { 526 rhs.addRef(rhs._head); 527 debug(CollectionSList) writefln("SList.opAssign: Node %s has refcount: %s", 528 rhs._head._payload, *prefCount(rhs._head)); 529 } 530 destroyUnused(); 531 _head = rhs._head; 532 _ouroborosAllocator = rhs._ouroborosAllocator; 533 534 return this; 535 } 536 537 auto ref opOpAssign(string op, U)(auto ref U rhs) 538 if (op == "~" && 539 (is (U == typeof(this)) 540 || is (U : T) 541 || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T)) 542 )) 543 { 544 debug(CollectionSList) 545 { 546 writefln("SList.opOpAssign!~: %s begin", typeof(this).stringof); 547 scope(exit) writefln("SList.opOpAssign!~: %s end", typeof(this).stringof); 548 } 549 550 insertBack(rhs); 551 return this; 552 } 553 554 void remove(size_t idx = 0) 555 { 556 assert(!empty, "SList.remove: List is empty"); 557 if (idx == 0) 558 { 559 popFront(); 560 return; 561 } 562 563 Node *tmpNode = _head; 564 while(--idx != 0) 565 { 566 tmpNode = tmpNode._next; 567 } 568 Node *toDel = tmpNode._next; 569 assert(toDel !is null, "SList.remove: Index out of bounds"); 570 tmpNode._next = tmpNode._next._next; 571 delRef(toDel); 572 } 573 574 debug(CollectionSList) void printRefCount(this _)() 575 { 576 writefln("SList.printRefCount: begin"); 577 scope(exit) writefln("SList.printRefCount: end"); 578 579 Node *tmpNode = (() @trusted => cast(Node*)_head)(); 580 while (tmpNode !is null) 581 { 582 writefln("SList.printRefCount: Node %s has ref count %s", 583 tmpNode._payload, *prefCount(tmpNode)); 584 tmpNode = tmpNode._next; 585 } 586 } 587 } 588 589 version(unittest) private @safe void testImmutability(IAllocator allocator) 590 { 591 auto s = immutable SList!(int)(allocator, 1, 2, 3); 592 auto s2 = s; 593 auto s3 = s2.save(); 594 595 assert(s2.front == 1); 596 static assert(!__traits(compiles, s2.front = 4)); 597 static assert(!__traits(compiles, s2.popFront())); 598 599 auto s4 = s2.tail; 600 assert(s4.front == 2); 601 static assert(!__traits(compiles, s4 = s4.tail)); 602 } 603 604 version(unittest) private @safe void testConstness(IAllocator allocator) 605 { 606 auto s = const SList!(int)(allocator, 1, 2, 3); 607 auto s2 = s; 608 auto s3 = s2.save(); 609 610 assert(s2.front == 1); 611 static assert(!__traits(compiles, s2.front = 4)); 612 static assert(!__traits(compiles, s2.popFront())); 613 614 auto s4 = s2.tail; 615 assert(s4.front == 2); 616 static assert(!__traits(compiles, s4 = s4.tail)); 617 } 618 619 @trusted unittest 620 { 621 import std.conv; 622 SCAlloc statsCollectorAlloc; 623 auto _allocator = allocatorObject(statsCollectorAlloc); 624 625 () @safe { 626 testImmutability(_allocator); 627 testConstness(_allocator); 628 auto bytesUsed = _allocator.impl.bytesUsed; 629 assert(bytesUsed == 0, "SList ref count leaks memory; leaked " 630 ~ to!string(bytesUsed) ~ " bytes"); 631 }(); 632 633 //() @nogc { 634 //_allocator.allocate(1); 635 //}(); 636 637 //() @nogc { 638 //import std.experimental.allocator.building_blocks.affix_allocator; 639 //import std.stdio; 640 //auto a = AffixAllocator!(Mallocator, size_t).instance; 641 //auto ia = allocatorObject(a); 642 //pragma(msg, typeof(a)); 643 //auto b = ia.allocate(1); 644 //pragma(msg, typeof(ia.impl.prefix(b))); 645 //}(); 646 } 647 648 version(unittest) private @safe void testConcatAndAppend(IAllocator allocator) 649 { 650 import std.algorithm.comparison : equal; 651 652 auto sl = SList!(int)(allocator, 1, 2, 3); 653 SList!(int) sl2 = SList!int(allocator); 654 655 auto sl3 = sl ~ sl2; 656 assert(equal(sl3, [1, 2, 3])); 657 658 auto sl4 = sl3; 659 sl3 = sl3 ~ 4; 660 assert(equal(sl3, [1, 2, 3, 4])); 661 sl3 = sl3 ~ [5]; 662 assert(equal(sl3, [1, 2, 3, 4, 5])); 663 assert(equal(sl4, [1, 2, 3])); 664 665 sl4 = sl3; 666 sl3 ~= 6; 667 assert(equal(sl3, [1, 2, 3, 4, 5, 6])); 668 sl3 ~= [7]; 669 assert(equal(sl3, [1, 2, 3, 4, 5, 6, 7])); 670 assert(equal(sl4, [1, 2, 3, 4, 5, 6, 7])); 671 672 sl3 ~= sl3; 673 assert(equal(sl3, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7])); 674 assert(equal(sl4, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7])); 675 676 SList!int sl5 = SList!int(allocator); 677 sl5 ~= [1, 2, 3]; 678 assert(equal(sl5, [1, 2, 3])); 679 } 680 681 @trusted unittest 682 { 683 import std.conv; 684 SCAlloc statsCollectorAlloc; 685 auto _allocator = allocatorObject(statsCollectorAlloc); 686 687 () @safe { 688 testConcatAndAppend(_allocator); 689 auto bytesUsed = _allocator.impl.bytesUsed; 690 assert(bytesUsed == 0, "SList ref count leaks memory; leaked " 691 ~ to!string(bytesUsed) ~ " bytes"); 692 }(); 693 } 694 695 version(unittest) private @safe void testSimple(IAllocator allocator) 696 { 697 import std.algorithm.comparison : equal; 698 import std.algorithm.searching : canFind; 699 import std.range.primitives : walkLength; 700 701 auto sl = SList!int(allocator); 702 assert(sl.empty); 703 assert(sl.isUnique); 704 705 size_t pos = 0; 706 sl.insert(pos, 1, 2, 3); 707 assert(sl.front == 1); 708 assert(equal(sl, sl)); 709 assert(equal(sl, [1, 2, 3])); 710 711 sl.popFront(); 712 assert(sl.front == 2); 713 assert(equal(sl, [2, 3])); 714 715 sl.insert(pos, [4, 5, 6]); 716 sl.insert(pos, 7); 717 sl.insert(pos, [8]); 718 assert(equal(sl, [8, 7, 4, 5, 6, 2, 3])); 719 720 sl.insertBack(0, 1); 721 sl.insertBack([-1, -2]); 722 assert(equal(sl, [8, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2])); 723 724 sl.front = 9; 725 assert(equal(sl, [9, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2])); 726 727 auto slTail = sl.tail; 728 assert(slTail.front == 7); 729 slTail.front = 8; 730 assert(slTail.front == 8); 731 assert(sl.tail.front == 8); 732 assert(!sl.isUnique); 733 734 assert(canFind(sl, 2)); 735 assert(!canFind(sl, -10)); 736 737 sl.remove(); 738 assert(equal(sl, [8, 4, 5, 6, 2, 3, 0, 1, -1, -2])); 739 sl.remove(2); 740 assert(equal(sl, [8, 4, 6, 2, 3, 0, 1, -1, -2])); 741 sl.remove(walkLength(sl) - 1); 742 assert(equal(sl, [8, 4, 6, 2, 3, 0, 1, -1])); 743 pos = 1; 744 sl.insert(pos, 10); 745 assert(equal(sl, [8, 10, 4, 6, 2, 3, 0, 1, -1])); 746 } 747 748 @trusted unittest 749 { 750 import std.conv; 751 SCAlloc statsCollectorAlloc; 752 auto _allocator = allocatorObject(statsCollectorAlloc); 753 754 () @safe { 755 testSimple(_allocator); 756 auto bytesUsed = _allocator.impl.bytesUsed; 757 assert(bytesUsed == 0, "SList ref count leaks memory; leaked " 758 ~ to!string(bytesUsed) ~ " bytes"); 759 }(); 760 } 761 762 version(unittest) private @safe void testSimpleImmutable(IAllocator allocator) 763 { 764 import std.algorithm.comparison : equal; 765 import std.algorithm.searching : canFind; 766 import std.range.primitives : walkLength; 767 768 auto sl = SList!(immutable int)(allocator); 769 assert(sl.empty); 770 771 size_t pos = 0; 772 sl.insert(pos, 1, 2, 3); 773 assert(sl.front == 1); 774 assert(equal(sl, sl)); 775 assert(equal(sl, [1, 2, 3])); 776 777 sl.popFront(); 778 assert(sl.front == 2); 779 assert(equal(sl, [2, 3])); 780 assert(sl.tail.front == 3); 781 782 sl.insert(pos, [4, 5, 6]); 783 sl.insert(pos, 7); 784 sl.insert(pos, [8]); 785 assert(equal(sl, [8, 7, 4, 5, 6, 2, 3])); 786 787 sl.insertBack(0, 1); 788 sl.insertBack([-1, -2]); 789 assert(equal(sl, [8, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2])); 790 791 // Cannot modify immutable values 792 static assert(!__traits(compiles, sl.front = 9)); 793 794 assert(canFind(sl, 2)); 795 assert(!canFind(sl, -10)); 796 797 sl.remove(); 798 assert(equal(sl, [7, 4, 5, 6, 2, 3, 0, 1, -1, -2])); 799 sl.remove(2); 800 assert(equal(sl, [7, 4, 6, 2, 3, 0, 1, -1, -2])); 801 sl.remove(walkLength(sl) - 1); 802 assert(equal(sl, [7, 4, 6, 2, 3, 0, 1, -1])); 803 } 804 805 @trusted unittest 806 { 807 import std.conv; 808 SCAlloc statsCollectorAlloc; 809 auto _allocator = allocatorObject(statsCollectorAlloc); 810 811 () @safe { 812 testSimpleImmutable(_allocator); 813 auto bytesUsed = _allocator.impl.bytesUsed; 814 assert(bytesUsed == 0, "SList ref count leaks memory; leaked " 815 ~ to!string(bytesUsed) ~ " bytes"); 816 }(); 817 } 818 819 version(unittest) private @safe void testCopyAndRef(IAllocator allocator) 820 { 821 import std.algorithm.comparison : equal; 822 823 auto slFromList = SList!int(allocator, 1, 2, 3); 824 auto slFromRange = SList!int(allocator, slFromList); 825 assert(equal(slFromList, slFromRange)); 826 827 slFromList.popFront(); 828 assert(equal(slFromList, [2, 3])); 829 assert(equal(slFromRange, [1, 2, 3])); 830 831 SList!int slInsFromRange = SList!int(allocator); 832 size_t pos = 0; 833 slInsFromRange.insert(pos, slFromList); 834 slFromList.popFront(); 835 assert(equal(slFromList, [3])); 836 assert(equal(slInsFromRange, [2, 3])); 837 838 SList!int slInsBackFromRange = SList!int(allocator); 839 slInsBackFromRange.insert(pos, slFromList); 840 slFromList.popFront(); 841 assert(slFromList.empty); 842 assert(equal(slInsBackFromRange, [3])); 843 844 auto slFromRef = slInsFromRange; 845 auto slFromDup = slInsFromRange.dup; 846 assert(slInsFromRange.front == 2); 847 slFromRef.front = 5; 848 assert(slInsFromRange.front == 5); 849 assert(slFromDup.front == 2); 850 } 851 852 @trusted unittest 853 { 854 import std.conv; 855 SCAlloc statsCollectorAlloc; 856 auto _allocator = allocatorObject(statsCollectorAlloc); 857 858 () @safe { 859 testCopyAndRef(_allocator); 860 auto bytesUsed = _allocator.impl.bytesUsed; 861 assert(bytesUsed == 0, "SList ref count leaks memory; leaked " 862 ~ to!string(bytesUsed) ~ " bytes"); 863 }(); 864 } 865 866 version(unittest) private @safe void testWithStruct(IAllocator allocator) 867 { 868 import std.algorithm.comparison : equal; 869 870 auto list = SList!int(allocator, 1, 2, 3); 871 { 872 auto listOfLists = SList!(SList!int)(allocator, list); 873 assert(equal(listOfLists.front, [1, 2, 3])); 874 listOfLists.front.front = 2; 875 assert(equal(listOfLists.front, [2, 2, 3])); 876 size_t pos = 0; 877 static assert(!__traits(compiles, listOfLists.insert(pos, 1))); 878 879 auto immListOfLists = immutable SList!(SList!int)(allocator, list); 880 assert(immListOfLists.front.front == 2); 881 static assert(!__traits(compiles, immListOfLists.front.front = 2)); 882 } 883 assert(equal(list, [2, 2, 3])); 884 } 885 886 @trusted unittest 887 { 888 import std.conv; 889 SCAlloc statsCollectorAlloc; 890 auto _allocator = allocatorObject(statsCollectorAlloc); 891 892 () @safe { 893 testWithStruct(_allocator); 894 auto bytesUsed = _allocator.impl.bytesUsed; 895 assert(bytesUsed == 0, "SList ref count leaks memory; leaked " 896 ~ to!string(bytesUsed) ~ " bytes"); 897 }(); 898 } 899 900 version(unittest) private @safe void testWithClass(IAllocator allocator) 901 { 902 class MyClass 903 { 904 int x; 905 this(int x) { this.x = x; } 906 } 907 908 MyClass c = new MyClass(10); 909 { 910 auto sl = SList!MyClass(allocator, c); 911 assert(sl.front.x == 10); 912 assert(sl.front is c); 913 sl.front.x = 20; 914 } 915 assert(c.x == 20); 916 } 917 918 @trusted unittest 919 { 920 import std.conv; 921 SCAlloc statsCollectorAlloc; 922 auto _allocator = allocatorObject(statsCollectorAlloc); 923 924 () @safe { 925 testWithClass(_allocator); 926 auto bytesUsed = _allocator.impl.bytesUsed; 927 assert(bytesUsed == 0, "SList ref count leaks memory; leaked " 928 ~ to!string(bytesUsed) ~ " bytes"); 929 }(); 930 }