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