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