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