1 /**
2  * ae.utils.range
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.utils.range;
15 
16 import std.range.primitives;
17 
18 import ae.utils.meta : isDebug;
19 
20 /// An equivalent of an array range, but which maintains
21 /// a start and end pointer instead of a start pointer
22 /// and length. This allows .popFront to be faster.
23 /// Optionally, omits bounds checking for even more speed.
24 // TODO: Can we make CHECKED implicit, controlled by
25 //       -release, like regular arrays?
26 // TODO: Does this actually make a difference in practice?
27 //       Run some benchmarks...
28 struct FastArrayRange(T, bool CHECKED=isDebug)
29 {
30 	T* ptr, end;
31 
32 	this(T[] arr)
33 	{
34 		ptr = arr.ptr;
35 		end = ptr + arr.length;
36 	}
37 
38 	@property T front()
39 	{
40 		static if (CHECKED)
41 			assert(!empty);
42 		return *ptr;
43 	}
44 
45 	void popFront()
46 	{
47 		static if (CHECKED)
48 			assert(!empty);
49 		ptr++;
50 	}
51 
52 	@property bool empty() { return ptr==end; }
53 
54 	@property ref typeof(this) save() { return this; }
55 
56 	T opIndex(size_t index)
57 	{
58 		static if (CHECKED)
59 			assert(index < end-ptr);
60 		return ptr[index];
61 	}
62 
63 	T[] opSlice()
64 	{
65 		return ptrSlice(ptr, end);
66 	}
67 
68 	T[] opSlice(size_t from, size_t to)
69 	{
70 		static if (CHECKED)
71 			assert(from <= to && to <= end-ptr);
72 		return ptr[from..to];
73 	}
74 }
75 
76 auto fastArrayRange(T)(T[] arr) { return FastArrayRange!T(arr); }
77 
78 T[] ptrSlice(T)(T* a, T* b)
79 {
80 	return a[0..b-a];
81 }
82 
83 unittest
84 {
85 	FastArrayRange!ubyte r;
86 	auto x = r.save;
87 }
88 
89 // ************************************************************************
90 
91 /// Presents a null-terminated pointer (C-like string) as a range.
92 struct NullTerminated(E)
93 {
94 	E* ptr;
95 	bool empty() { return !*ptr; }
96 	ref E front() { return *ptr; }
97 	void popFront() { ptr++; }
98 	auto save() { return this; }
99 }
100 auto nullTerminated(E)(E* ptr)
101 {
102 	return NullTerminated!E(ptr);
103 }
104 
105 unittest
106 {
107 	void test(S)(S s)
108 	{
109 		import std.utf, std.algorithm.comparison;
110 		assert(equal(s.byCodeUnit, s.ptr.nullTerminated));
111 	}
112 	// String literals are null-terminated
113 	test("foo");
114 	test("foo"w);
115 	test("foo"d);
116 }
117 
118 // ************************************************************************
119 
120 /// Apply a predicate over each consecutive pair.
121 template pairwise(alias pred)
122 {
123 	import std.range : zip, dropOne;
124 	import std.algorithm.iteration : map;
125 	import std.functional : binaryFun;
126 
127 	auto pairwise(R)(R r)
128 	{
129 		return zip(r, r.dropOne).map!(pair => binaryFun!pred(pair[0], pair[1]));
130 	}
131 }
132 
133 ///
134 unittest
135 {
136 	import std.algorithm.comparison : equal;
137 	assert(equal(pairwise!"a+b"([1, 2, 3]), [3, 5]));
138 	assert(equal(pairwise!"b-a"([1, 2, 3]), [1, 1]));
139 }
140 
141 // ************************************************************************
142 
143 struct InfiniteIota(T)
144 {
145 	T front;
146 	enum empty = false;
147 	void popFront() { front++; }
148 	T opIndex(T offset) { return front + offset; }
149 	InfiniteIota save() { return this; }
150 }
151 InfiniteIota!T infiniteIota(T)() { return InfiniteIota!T.init; }
152 
153 // ************************************************************************
154 
155 /// Empty range of type E.
156 struct EmptyRange(E)
157 {
158 	@property E front() { assert(false); }
159 	void popFront() { assert(false); }
160 	@property E back() { assert(false); }
161 	void popBack() { assert(false); }
162 	E opIndex(size_t) { assert(false); }
163 	enum empty = true;
164 	enum save = typeof(this).init;
165 	enum size_t length = 0;
166 }
167 
168 /// ditto
169 EmptyRange!E emptyRange(E)() { return EmptyRange!E.init; }
170 
171 static assert(isInputRange!(EmptyRange!uint));
172 static assert(isForwardRange!(EmptyRange!uint));
173 static assert(isBidirectionalRange!(EmptyRange!uint));
174 static assert(isRandomAccessRange!(EmptyRange!uint));
175 
176 // ************************************************************************
177 
178 /// Like `only`, but evaluates the argument lazily, i.e. when the
179 /// range's "front" is evaluated.
180 /// DO NOT USE before this bug is fixed:
181 /// https://issues.dlang.org/show_bug.cgi?id=11044
182 auto onlyLazy(E)(lazy E value)
183 {
184 	struct Lazy
185 	{
186 		bool empty = false;
187 		@property E front() { assert(!empty); return value; }
188 		void popFront() { assert(!empty); empty = true; }
189 		alias back = front;
190 		alias popBack = popFront;
191 		@property size_t length() { return empty ? 0 : 1; }
192 		E opIndex(size_t i) { assert(!empty); assert(i == 0); return value; }
193 		@property typeof(this) save() { return this; }
194 	}
195 	return Lazy();
196 }
197 
198 static assert(isInputRange!(typeof(onlyLazy(1))));
199 static assert(isForwardRange!(typeof(onlyLazy(1))));
200 static assert(isBidirectionalRange!(typeof(onlyLazy(1))));
201 static assert(isRandomAccessRange!(typeof(onlyLazy(1))));
202 
203 unittest
204 {
205 	import std.algorithm.comparison;
206 	import std.range;
207 
208 	int i;
209 	auto r = onlyLazy(i);
210 	i = 1; assert(equal(r, 1.only));
211 	i = 2; assert(equal(r, 2.only));
212 }
213 
214 // ************************************************************************
215 
216 /// Defer range construction until first empty/front call.
217 auto lazyInitRange(R)(R delegate() constructor)
218 {
219 	bool initialized;
220 	R r = void;
221 
222 	ref R getRange()
223 	{
224 		if (!initialized)
225 		{
226 			r = constructor();
227 			initialized = true;
228 		}
229 		return r;
230 	}
231 
232 	struct LazyRange
233 	{
234 		bool empty() { return getRange().empty; }
235 		auto ref front() { return getRange().front; }
236 		void popFront() { return getRange().popFront; }
237 	}
238 	return LazyRange();
239 }
240 
241 ///
242 unittest
243 {
244 	import std.algorithm.iteration;
245 	import std.range;
246 
247 	int[] todo, done;
248 	chain(
249 		only({ todo = [1, 2, 3]; }),
250 		// eager will fail: todo.map!(n => { done ~= n; }),
251 		lazyInitRange(() => todo.map!(n => { done ~= n; })),
252 	).each!(dg => dg());
253 	assert(done == [1, 2, 3]);
254 }