1 
2 //          Copyright Ferdinand Majerech 2011-2014.
3 // Distributed under the Boost Software License, Version 1.0.
4 //    (See accompanying file LICENSE_1_0.txt or copy at
5 //          http://www.boost.org/LICENSE_1_0.txt)
6 
7 module dyaml.queue;
8 
9 
10 /// Queue collection.
11 import core.stdc.stdlib;
12 import core.memory;
13 
14 import std.container;
15 import std.traits;
16 
17 
18 package:
19 
20 /// Simple queue implemented as a singly linked list with a tail pointer.
21 /// 
22 /// Needed in some D:YAML code that needs a queue-like structure without too much
23 /// reallocation that goes with an array.
24 /// 
25 /// This should be replaced once Phobos has a decent queue/linked list.
26 /// 
27 /// Uses manual allocation through malloc/free.
28 /// 
29 /// Also has some features uncommon for a queue, e.g. iteration. Couldn't bother with
30 /// implementing a range, as this is used only as a placeholder until Phobos gets a
31 /// decent replacement.
32 struct Queue(T)
33     if(!hasMember!(T, "__dtor"))
34 {
35     private:
36         /// Linked list node containing one element and pointer to the next node.
37         struct Node
38         {
39             T payload_;
40             Node* next_ = null;
41         }
42 
43         /// Start of the linked list - first element added in time (end of the queue).
44         Node* first_ = null;
45         /// Last element of the linked list - last element added in time (start of the queue).
46         Node* last_ = null;
47         /// Cursor pointing to the current node in iteration.
48         Node* cursor_ = null;
49 
50         /// The first element of a linked list of freed Nodes available for recycling.
51         Node* freeList_ = null;
52 
53         /// Length of the queue.
54         size_t length_ = 0;
55 
56     public:
57         @disable void opAssign(ref Queue);
58         @disable bool opEquals(ref Queue);
59         @disable int opCmp(ref Queue);
60 
61         /// Destroy the queue, deallocating all its elements.
62         @trusted nothrow ~this()
63         {
64             while(!empty) { pop(); }
65             while(freeList_ !is null)
66             {
67                 auto toFree = freeList_;
68                 freeList_   = toFree.next_;
69                 free(toFree);
70             }
71             cursor_ = last_ = first_ = null;
72             length_ = 0;
73         }
74 
75         /// Start iterating over the queue.
76         void startIteration() @safe pure nothrow @nogc
77         {
78             cursor_ = first_;
79         }
80 
81         /// Get next element in the queue.
82         ref const(T) next() @safe pure nothrow @nogc
83         in
84         {
85             assert(!empty);
86             assert(cursor_ !is null);
87         }
88         body
89         {
90             const previous = cursor_;
91             cursor_ = cursor_.next_; 
92             return previous.payload_;
93         }
94 
95         /// Are we done iterating?
96         bool iterationOver() @safe pure nothrow const @nogc
97         {
98             return cursor_ is null;
99         }
100 
101         /// Push new item to the queue.
102         void push(T item) @trusted nothrow
103         {
104             Node* newLast = newNode(item, null);
105             if(last_ !is null) { last_.next_ = newLast; }
106             if(first_ is null) { first_      = newLast; }
107             last_ = newLast;
108             ++length_;
109         }
110 
111         /// Insert a new item putting it to specified index in the linked list.
112         void insert(T item, const size_t idx) @trusted nothrow
113         in
114         {
115             assert(idx <= length_);
116         }
117         body
118         {
119             if(idx == 0)
120             {
121                 first_ = newNode(item, first_);
122                 ++length_;
123             }
124             // Adding before last added element, so we can just push.
125             else if(idx == length_) { push(item); }
126             else
127             {
128                 // Get the element before one we're inserting.
129                 Node* current = first_;
130                 foreach(i; 1 .. idx) { current = current.next_; }
131 
132                 // Insert a new node after current, and put current.next_ behind it.
133                 current.next_ = newNode(item, current.next_);
134                 ++length_;
135             }
136         }
137 
138         /// Return the next element in the queue and remove it.
139         T pop() @trusted nothrow
140         in
141         {
142             assert(!empty, "Trying to pop an element from an empty queue");
143         }
144         body
145         {
146             T result     = peek();
147             Node* popped = first_;
148             first_       = first_.next_;
149 
150             Node* oldFree   = freeList_;
151             freeList_       = popped;
152             freeList_.next_ = oldFree;
153             if(--length_ == 0)
154             {
155                 assert(first_ is null);
156                 last_ = null;
157             }
158 
159             return result;
160         }
161 
162         /// Return the next element in the queue.
163         ref inout(T) peek() @safe pure nothrow inout @nogc 
164         in
165         {
166             assert(!empty, "Trying to peek at an element in an empty queue");
167         }
168         body
169         {
170             return first_.payload_;
171         }
172 
173         /// Is the queue empty?
174         bool empty() @safe pure nothrow const @nogc
175         {
176             return first_ is null;
177         }
178 
179         /// Return number of elements in the queue.
180         size_t length() @safe pure nothrow const @nogc
181         {
182             return length_;
183         }
184 
185 private:
186         /// Get a new (or recycled) node with specified item and next node pointer.
187         ///
188         /// Tries to reuse a node from freeList_, allocates a new node if not possible.
189         Node* newNode(ref T item, Node* next) @trusted nothrow
190         {
191             if(freeList_ !is null)
192             {
193                 auto node = freeList_;
194                 freeList_ = freeList_.next_;
195                 *node     = Node(item, next);
196                 return node;
197             }
198             return allocate!Node(item, next);
199         }
200 }
201 
202 
203 private:
204 
205 /// Allocate a struct, passing arguments to its constructor or default initializer.
206 T* allocate(T, Args...)(Args args) @system nothrow
207 {
208     T* ptr = cast(T*)malloc(T.sizeof);
209     *ptr = T(args);
210     // The struct might contain references to GC-allocated memory, so tell the GC about it.
211     static if(hasIndirections!T) { GC.addRange(cast(void*)ptr, T.sizeof); }
212     return ptr;
213 }
214 
215 /// Deallocate struct pointed at by specified pointer.
216 void free(T)(T* ptr) @system nothrow
217 {
218     // GC doesn't need to care about any references in this struct anymore.
219     static if(hasIndirections!T) { GC.removeRange(cast(void*)ptr); }
220     core.stdc.stdlib.free(ptr);
221 }
222 
223 unittest
224 {
225     auto queue = Queue!int();
226     assert(queue.empty);
227     foreach(i; 0 .. 65)
228     {
229         queue.push(5);
230         assert(queue.pop() == 5);
231         assert(queue.empty);
232         assert(queue.length_ == 0);
233     }
234 
235     int[] array = [1, -1, 2, -2, 3, -3, 4, -4, 5, -5];
236     foreach(i; array)
237     {
238         queue.push(i);
239     }
240 
241     array = 42 ~ array[0 .. 3] ~ 42 ~ array[3 .. $] ~ 42;
242     queue.insert(42, 3);
243     queue.insert(42, 0);
244     queue.insert(42, queue.length);
245 
246     int[] array2;
247     while(!queue.empty)
248     {
249         array2 ~= queue.pop();
250     }
251 
252     assert(array == array2);
253 }