1 module tame.queue;
2 import std.traits : hasMember, hasIndirections;
3 
4 /*
5  * Simple queue implemented as a singly linked list with a tail pointer.
6  *
7  * Needed in some D:YAML code that needs a queue-like structure without too much
8  * reallocation that goes with an array.
9  *
10  * Allocations are non-GC and are damped by a free-list based on the nodes
11  * that are removed. Note that elements lifetime must be managed
12  * outside.
13  */
14 struct Queue(T) if (!hasMember!(T, "__xdtor")) {
15 private:
16 
17 	// Linked list node containing one element and pointer to the next node.
18 	struct Node {
19 		T value;
20 		Node* next;
21 	}
22 
23 	// Start of the linked list - first element added in time (end of the queue).
24 	Node* first_;
25 	// Last element of the linked list - last element added in time (start of the queue).
26 	Node* last_;
27 	// free-list
28 	Node* stock;
29 
30 	// Length of the queue.
31 	size_t len;
32 
33 	// allocate a new node or recycle one from the stock.
34 	Node* make(T value, Node* theNext = null) @trusted nothrow @nogc {
35 		import std.experimental.allocator : make;
36 		import std.experimental.allocator.mallocator : Mallocator;
37 
38 		Node* result;
39 		if (stock !is null) {
40 			result = stock;
41 			stock = result.next;
42 			result.value = value;
43 			result.next = theNext;
44 		} else {
45 			result = Mallocator.instance.make!Node(value, theNext);
46 			// GC can dispose T managed member if it thinks they are no used...
47 			static if (hasIndirections!T) {
48 				import core.memory : GC;
49 
50 				GC.addRange(result, Node.sizeof);
51 			}
52 		}
53 		return result;
54 	}
55 
56 	// free the stock of available free nodes.
57 	void freeStock() @trusted @nogc nothrow {
58 		import std.experimental.allocator.mallocator : Mallocator;
59 
60 		while (stock) {
61 			Node* toFree = stock;
62 			stock = stock.next;
63 			static if (hasIndirections!T) {
64 				import core.memory : GC;
65 
66 				GC.removeRange(toFree);
67 			}
68 			Mallocator.instance.deallocate((cast(ubyte*)toFree)[0 .. Node.sizeof]);
69 		}
70 	}
71 
72 public:
73 
74 	@disable void opAssign(ref Queue);
75 	@disable bool opEquals(ref Queue);
76 	@disable int opCmp(ref Queue);
77 
78 @safe nothrow:
79 	this(this) @nogc {
80 		auto node = first_;
81 		first_ = null;
82 		last_ = null;
83 		while (node !is null) {
84 			Node* newLast = make(node.value);
85 			if (last_ !is null)
86 				last_.next = newLast;
87 			if (first_ is null)
88 				first_ = newLast;
89 			last_ = newLast;
90 			node = node.next;
91 		}
92 	}
93 
94 	~this() {
95 		freeStock();
96 		stock = first_;
97 		freeStock();
98 	}
99 
100 	/// Returns a forward range iterating over this queue.
101 	auto range() {
102 		static struct Result {
103 			private Node* cursor;
104 
105 		@safe pure nothrow @nogc:
106 			void popFront() {
107 				cursor = cursor.next;
108 			}
109 
110 			ref T front()
111 			in (cursor) {
112 				return cursor.value;
113 			}
114 
115 			bool empty() {
116 				return cursor is null;
117 			}
118 		}
119 
120 		return Result(first_);
121 	}
122 
123 	/// Push a new item to the queue.
124 	void push(T item) @nogc {
125 		Node* newLast = make(item);
126 		if (last_ !is null)
127 			last_.next = newLast;
128 		if (first_ is null)
129 			first_ = newLast;
130 		last_ = newLast;
131 		++len;
132 	}
133 
134 	/// Insert a new item putting it to specified index in the linked list.
135 	void insert(T item, const size_t idx)
136 	in (idx <= len) {
137 		if (idx == 0) {
138 			first_ = make(item, first_);
139 			++len;
140 		}  // Adding before last added element, so we can just push.
141 		else if (idx == len) {
142 			push(item);
143 		} else {
144 			// Get the element before one we're inserting.
145 			Node* current = first_;
146 			foreach (i; 1 .. idx)
147 				current = current.next;
148 
149 			assert(current);
150 			// Insert a new node after current, and put current.next behind it.
151 			current.next = make(item, current.next);
152 			++len;
153 		}
154 	}
155 
156 	/// Returns: The next element in the queue and remove it.
157 	T pop()
158 	in (!empty, "Trying to pop an element from an empty queue") {
159 		T result = peek();
160 
161 		Node* oldStock = stock;
162 		Node* old = first_;
163 		first_ = first_.next;
164 
165 		// start the stock from the popped element
166 		stock = old;
167 		old.next = null;
168 		// add the existing "old" stock to the new first stock element
169 		if (oldStock !is null)
170 			stock.next = oldStock;
171 
172 		if (--len == 0) {
173 			assert(first_ is null);
174 			last_ = null;
175 		}
176 
177 		return result;
178 	}
179 
180 pure @nogc:
181 	/// Returns: The next element in the queue.
182 	ref inout(T) peek() inout
183 	in (!empty, "Trying to peek at an element in an empty queue")
184 	do {
185 		return first_.value;
186 	}
187 
188 	/// Returns: true of the queue empty, false otherwise.
189 	bool empty() const {
190 		return first_ is null;
191 	}
192 
193 	/// Returns: The number of elements in the queue.
194 	size_t length() const {
195 		return len;
196 	}
197 }
198 
199 @safe nothrow unittest {
200 	auto queue = Queue!int();
201 	assert(queue.empty);
202 	foreach (i; 0 .. 65) {
203 		queue.push(5);
204 		assert(queue.pop() == 5);
205 		assert(queue.empty);
206 		assert(queue.len == 0);
207 	}
208 
209 	auto arr = [1, -1, 2, -2, 3, -3, 4, -4, 5, -5];
210 	foreach (i; arr) {
211 		queue.push(i);
212 	}
213 
214 	arr = 42 ~ arr[0 .. 3] ~ 42 ~ arr[3 .. $] ~ 42;
215 	queue.insert(42, 3);
216 	queue.insert(42, 0);
217 	queue.insert(42, queue.length);
218 
219 	int[] arr2;
220 	while (!queue.empty) {
221 		arr2 ~= queue.pop();
222 	}
223 
224 	assert(arr == arr2);
225 }