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 }