1 /**
2  * Singly-linked list.
3  * Copyright: © 2015 Economic Modeling Specialists, Intl.
4  * Authors: Brian Schott
5  * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6  */
7 
8 module containers.slist;
9 
10 private import containers.internal.node : shouldAddGCRange;
11 private import std.experimental.allocator.mallocator : Mallocator;
12 
13 /**
14  * Single-linked allocator-backed list.
15  * Params:
16  *     T = the element type
17  *     Allocator = the allocator to use. Defaults to `Mallocator`.
18  *     supportGC = true if the container should support holding references to
19  *         GC-allocated memory.
20  */
21 struct SList(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange!T)
22 {
23 	/// Disable copying.
24 	this(this) @disable;
25 
26 	private import std.experimental.allocator.common : stateSize;
27 
28 	static if (stateSize!Allocator != 0)
29 	{
30 		/// No default construction if an allocator must be provided.
31 		this() @disable;
32 
33 		/**
34 		 * Use the given `allocator` for allocations.
35 		 */
36 		this(Allocator allocator)
37 		in
38 		{
39 			assert(allocator !is null, "Allocator must not be null");
40 		}
41 		body
42 		{
43 			this.allocator = allocator;
44 		}
45 	}
46 
47 	~this()
48 	{
49 		Node* current = _front;
50 		Node* prev = null;
51 		while (current !is null)
52 		{
53 			prev = current;
54 			current = current.next;
55 			typeid(Node).destroy(prev);
56 			static if (useGC)
57 			{
58 				import core.memory : GC;
59 				GC.removeRange(prev);
60 			}
61 			allocator.dispose(prev);
62 		}
63 		_front = null;
64 	}
65 
66 	/**
67 	 * Returns: the most recently inserted item
68 	 */
69 	auto front(this This)() @property
70 	in
71 	{
72 		assert (!empty);
73 	}
74 	body
75 	{
76 		alias ET = ContainerElementType!(This, T);
77 		return cast(ET) _front.value;
78 	}
79 
80 	/**
81 	 * Removes and returns the first item in the list.
82 	 */
83 	T moveFront()
84 	in
85 	{
86 		assert (!empty);
87 	}
88 	body
89 	{
90 		Node* f = _front;
91 		_front = f.next;
92 		T r = f.value;
93 		static if (useGC)
94 		{
95 			import core.memory : GC;
96 			GC.removeRange(f);
97 		}
98 		allocator.dispose(f);
99 		--_length;
100 		return r;
101 	}
102 
103 	/**
104 	 * Removes the first item in the list.
105 	 */
106 	void popFront()
107 	{
108 		Node* f = _front;
109 		_front = f.next;
110 		static if (useGC)
111 		{
112 			import core.memory : GC;
113 			GC.removeRange(f);
114 		}
115 		allocator.dispose(f);
116 		--_length;
117 	}
118 
119 	/**
120 	 * Returns: true if this list is empty
121 	 */
122 	bool empty() inout pure nothrow @property @safe @nogc
123 	{
124 		return _front is null;
125 	}
126 
127 	/**
128 	 * Returns: the number of items in the list
129 	 */
130 	size_t length() inout pure nothrow @property @safe @nogc
131 	{
132 		return _length;
133 	}
134 
135 	/**
136 	 * Inserts an item at the front of the list.
137 	 * Params: t = the item to insert into the list
138 	 */
139 	void insertFront(T t) @trusted
140 	{
141 		_front = make!Node(allocator, _front, t);
142 		static if (useGC)
143 		{
144 			import core.memory : GC;
145 			GC.addRange(_front, Node.sizeof);
146 		}
147 		_length++;
148 	}
149 
150 	/// ditto
151 	alias insert = insertFront;
152 
153 	/// ditto
154 	alias put = insertFront;
155 
156 	/// Supports $(B list ~= item) syntax
157 	void opOpAssign(string op)(T t) if (op == "~")
158 	{
159 		put(t);
160 	}
161 
162 	/**
163 	 * Removes the first instance of value found in the list.
164 	 * Returns: true if a value was removed.
165 	 */
166 	bool remove(V)(V value) @trusted
167 	{
168 		Node* prev = null;
169 		Node* cur = _front;
170 		while (cur !is null)
171 		{
172 			if (cur.value == value)
173 			{
174 				if (prev !is null)
175 					prev.next = cur.next;
176 				if (_front is cur)
177 					_front = cur.next;
178 				static if (shouldAddGCRange!T)
179 				{
180 					import core.memory : GC;
181 					GC.removeRange(cur);
182 				}
183 				allocator.dispose(cur);
184 				_length--;
185 				return true;
186 			}
187 			prev = cur;
188 			cur = cur.next;
189 		}
190 		return false;
191 	}
192 
193 	/**
194 	 * Forward range interface
195 	 */
196 	auto range(this This)()
197 	{
198 		return Range!(This)(_front);
199 	}
200 
201 	/// ditto
202 	alias opSlice = range;
203 
204 	/**
205 	 * Removes all elements from the range
206 	 */
207 	void clear()
208 	{
209 		Node* prev = null;
210 		Node* cur = _front;
211 		while (cur !is null)
212 		{
213 			prev = cur;
214 			cur = prev.next;
215 			static if (shouldAddGCRange!T)
216 			{
217 				import core.memory : GC;
218 				GC.removeRange(prev);
219 			}
220 			allocator.dispose(prev);
221 		}
222 		_front = null;
223 		_length = 0;
224 	}
225 
226 private:
227 
228 	import std.experimental.allocator : make, dispose;
229 	import containers.internal.node : shouldAddGCRange;
230 	import containers.internal.element_type : ContainerElementType;
231 	import containers.internal.mixins : AllocatorState;
232 
233 	enum bool useGC = supportGC && shouldAddGCRange!T;
234 
235 	static struct Range(ThisT)
236 	{
237 	public:
238 		ET front() pure nothrow @property @trusted @nogc
239 		{
240 			return cast(typeof(return)) current.value;
241 		}
242 
243 		void popFront() pure nothrow @safe @nogc
244 		{
245 			current = current.next;
246 		}
247 
248 		bool empty() const pure nothrow @property @safe @nogc
249 		{
250 			return current is null;
251 		}
252 
253 	private:
254 		alias ET = ContainerElementType!(ThisT, T);
255 		const(Node)* current;
256 	}
257 
258 	static struct Node
259 	{
260 		Node* next;
261 		T value;
262 	}
263 
264 	mixin AllocatorState!Allocator;
265 	Node* _front;
266 	size_t _length;
267 }
268 
269 unittest
270 {
271 	import std..string : format;
272 	import std.algorithm : canFind;
273 	SList!int intList;
274 	foreach (i; 0 .. 100)
275 		intList.put(i);
276 	assert (intList.length == 100, "%d".format(intList.length));
277 	assert (intList.remove(10));
278 	assert (!intList.remove(10));
279 	assert (intList.length == 99);
280 	assert (intList.range.canFind(9));
281 	assert (!intList.range.canFind(10));
282 	SList!string l;
283 	l ~= "abcde";
284 	l ~= "fghij";
285 	assert (l.length == 2);
286 }