1 /**
2    Better versions of Phobos's template contraints that emit
3    error messages when they fail
4    Can be used with concepts.models.models
5  */
6 module concepts.range;
7 
8 import std.range.primitives: front, popFront, empty, put, save, back, popBack;
9 
10 
11 void checkInputRange(R)(inout int = 0) {
12     R r = R.init;     // can define a range object
13     if (r.empty) {}   // can test for empty
14     r.popFront;       // can invoke popFront()
15     auto h = r.front; // can get the front of the range
16 }
17 
18 /**
19 Returns $(D true) if $(D R) is an input range. An input range must
20 define the primitives $(D empty), $(D popFront), and $(D front). The
21 following code should compile for any input range.
22 
23 ----
24 R r;              // can define a range object
25 if (r.empty) {}   // can test for empty
26 r.popFront();     // can invoke popFront()
27 auto h = r.front; // can get the front of the range of non-void type
28 ----
29 
30 The following are rules of input ranges are assumed to hold true in all
31 Phobos code. These rules are not checkable at compile-time, so not conforming
32 to these rules when writing ranges or range based code will result in
33 undefined behavior.
34 
35 $(UL
36     $(LI `r.empty` returns `false` if and only if there is more data
37     available in the range.)
38     $(LI `r.empty` evaluated multiple times, without calling
39     `r.popFront`, or otherwise mutating the range object or the
40     underlying data, yields the same result for every evaluation.)
41     $(LI `r.front` returns the current element in the range.
42     It may return by value or by reference.)
43     $(LI `r.front` can be legally evaluated if and only if evaluating
44     `r.empty` has, or would have, equaled `false`.)
45     $(LI `r.front` evaluated multiple times, without calling
46     `r.popFront`, or otherwise mutating the range object or the
47     underlying data, yields the same result for every evaluation.)
48     $(LI `r.popFront` advances to the next element in the range.)
49     $(LI `r.popFront` can be called if and only if evaluating `r.empty`
50     has, or would have, equaled `false`.)
51 )
52 
53 Also, note that Phobos code assumes that the primitives `r.front` and
54 `r.empty` are $(BIGOH 1) time complexity wise or "cheap" in terms of
55 running time. $(BIGOH) statements in the documentation of range functions
56 are made with this assumption.
57 
58 Params:
59     R = type to be tested
60 
61 Returns:
62     true if R is an InputRange, false if not
63  */
64 enum isInputRange(R) = is(typeof(checkInputRange!R));
65 
66 ///
67 @safe pure unittest {
68 
69     import concepts.models: models;
70 
71     struct A {}
72     struct B
73     {
74         void popFront();
75         @property bool empty();
76         @property int front();
77     }
78     static assert(!isInputRange!A);
79     static assert( isInputRange!B);
80     static assert( isInputRange!(int[]));
81     static assert( isInputRange!(char[]));
82     static assert(!isInputRange!(char[4]));
83     static assert( isInputRange!(inout(int)[]));
84 }
85 
86 void checkOutputRange(R, E)(inout int = 0) {
87     R r = R.init;
88     E e = E.init;
89     put(r, e);
90 }
91 
92 
93 /++
94 Returns $(D true) if $(D R) is an output range for elements of type
95 $(D E). An output range is defined functionally as a range that
96 supports the operation $(D put(r, e)) as defined above.
97  +/
98 enum isOutputRange(R, E) = is(typeof(checkOutputRange!(R, E)));
99 
100 
101 ///
102 @safe pure unittest
103 {
104     void myprint(in char[] s) { }
105     static assert(isOutputRange!(typeof(&myprint), char));
106 
107     static assert( isOutputRange!(char[],  char));
108     static assert( isOutputRange!(dchar[], wchar));
109     static assert( isOutputRange!(dchar[], dchar));
110 }
111 
112 @safe pure unittest
113 {
114     import std.array;
115     import std.stdio : writeln;
116 
117     auto app = appender!string();
118     string s;
119     static assert( isOutputRange!(Appender!string, string));
120     static assert( isOutputRange!(Appender!string*, string));
121     static assert(!isOutputRange!(Appender!string, int));
122     static assert( isOutputRange!(wchar[], wchar));
123     static assert( isOutputRange!(dchar[], char));
124     static assert( isOutputRange!(dchar[], string));
125     static assert( isOutputRange!(dchar[], wstring));
126     static assert( isOutputRange!(dchar[], dstring));
127 
128     static assert(!isOutputRange!(const(int)[], int));
129     static assert(!isOutputRange!(inout(int)[], int));
130 }
131 
132 
133 void checkForwardRange(R)(inout int = 0) {
134 
135     checkInputRange!R;
136 
137     R r1 = R.init;
138     // NOTE: we cannot check typeof(r1.save) directly
139     // because typeof may not check the right type there, and
140     // because we want to ensure the range can be copied.
141     auto s1 = r1.save;
142     static assert (is(typeof(s1) == R));
143 }
144 
145 
146 /**
147 Returns $(D true) if $(D R) is a forward range. A forward range is an
148 input range $(D r) that can save "checkpoints" by saving $(D r.save)
149 to another value of type $(D R). Notable examples of input ranges that
150 are $(I not) forward ranges are file/socket ranges; copying such a
151 range will not save the position in the stream, and they most likely
152 reuse an internal buffer as the entire stream does not sit in
153 memory. Subsequently, advancing either the original or the copy will
154 advance the stream, so the copies are not independent.
155 
156 The following code should compile for any forward range.
157 
158 ----
159 static assert(isInputRange!R);
160 R r1;
161 auto s1 = r1.save;
162 static assert (is(typeof(s1) == R));
163 ----
164 
165 Saving a range is not duplicating it; in the example above, $(D r1)
166 and $(D r2) still refer to the same underlying data. They just
167 navigate that data independently.
168 
169 The semantics of a forward range (not checkable during compilation)
170 are the same as for an input range, with the additional requirement
171 that backtracking must be possible by saving a copy of the range
172 object with $(D save) and using it later.
173  */
174 enum isForwardRange(R) = is(typeof(checkForwardRange!R));
175 
176 ///
177 @safe pure unittest
178 {
179     static assert(!isForwardRange!(int));
180     static assert( isForwardRange!(int[]));
181     static assert( isForwardRange!(inout(int)[]));
182 }
183 
184 // FIXME - infinite loop
185 // @("BUG 14544")
186 // @safe pure unittest
187 // {
188 //     import concepts.models: models;
189 
190 //     @models!(R14544, isForwardRange)
191 //     struct R14544
192 //     {
193 //         int front() { return 0;}
194 //         void popFront() {}
195 //         bool empty() { return false; }
196 //         R14544 save() {return this;}
197 //     }
198 
199 //     static assert(isForwardRange!R14544);
200 // }
201 
202 
203 void checkBidirectionalRange(R)(inout int = 0) {
204     R r = R.init;
205     r.popBack;
206     auto t = r.back;
207     auto w = r.front;
208     static assert(is(typeof(t) == typeof(w)));
209 }
210 
211 /**
212 Returns $(D true) if $(D R) is a bidirectional range. A bidirectional
213 range is a forward range that also offers the primitives $(D back) and
214 $(D popBack). The following code should compile for any bidirectional
215 range.
216 
217 The semantics of a bidirectional range (not checkable during
218 compilation) are assumed to be the following ($(D r) is an object of
219 type $(D R)):
220 
221 $(UL $(LI $(D r.back) returns (possibly a reference to) the last
222 element in the range. Calling $(D r.back) is allowed only if calling
223 $(D r.empty) has, or would have, returned $(D false).))
224  */
225 enum isBidirectionalRange(R) = is(typeof(checkBidirectionalRange!R));
226 
227 ///
228 @safe pure unittest
229 {
230     alias R = int[];
231     R r = [0,1];
232     static assert(isForwardRange!R);           // is forward range
233     r.popBack();                               // can invoke popBack
234     auto t = r.back;                           // can get the back of the range
235     auto w = r.front;
236     static assert(is(typeof(t) == typeof(w))); // same type for front and back
237 }
238 
239 @safe pure unittest
240 {
241     struct A {}
242     struct B
243     {
244         void popFront();
245         @property bool empty();
246         @property int front();
247     }
248     struct C
249     {
250         @property bool empty();
251         @property C save();
252         void popFront();
253         @property int front();
254         void popBack();
255         @property int back();
256     }
257     static assert(!isBidirectionalRange!(A));
258     static assert(!isBidirectionalRange!(B));
259     static assert( isBidirectionalRange!(C));
260     static assert( isBidirectionalRange!(int[]));
261     static assert( isBidirectionalRange!(char[]));
262     static assert( isBidirectionalRange!(inout(int)[]));
263 }
264 
265 void checkRandomAccessRange(R)(inout int = 0) {
266 
267     import std.traits: isNarrowString;
268     import std.range: hasLength, isInfinite;
269 
270     if(isBidirectionalRange!R) checkBidirectionalRange!R;
271     if(isForwardRange!R) checkForwardRange!R;
272 
273     static assert(isBidirectionalRange!R ||
274                   isForwardRange!R && isInfinite!R);
275     R r = R.init;
276     auto e = r[1];
277     auto f = r.front;
278     static assert(is(typeof(e) == typeof(f)));
279     static assert(!isNarrowString!R);
280     static assert(hasLength!R || isInfinite!R);
281 
282     static if (is(typeof(r[$])))
283     {
284         static assert(is(typeof(f) == typeof(r[$])));
285 
286         static if (!isInfinite!R)
287             static assert(is(typeof(f) == typeof(r[$ - 1])));
288     }
289 }
290 
291 
292 /**
293 Returns $(D true) if $(D R) is a random-access range. A random-access
294 range is a bidirectional range that also offers the primitive $(D
295 opIndex), OR an infinite forward range that offers $(D opIndex). In
296 either case, the range must either offer $(D length) or be
297 infinite. The following code should compile for any random-access
298 range.
299 
300 The semantics of a random-access range (not checkable during
301 compilation) are assumed to be the following ($(D r) is an object of
302 type $(D R)): $(UL $(LI $(D r.opIndex(n)) returns a reference to the
303 $(D n)th element in the range.))
304 
305 Although $(D char[]) and $(D wchar[]) (as well as their qualified
306 versions including $(D string) and $(D wstring)) are arrays, $(D
307 isRandomAccessRange) yields $(D false) for them because they use
308 variable-length encodings (UTF-8 and UTF-16 respectively). These types
309 are bidirectional ranges only.
310  */
311 enum isRandomAccessRange(R) = is(typeof(checkRandomAccessRange!R));
312 
313 ///
314 @safe pure unittest
315 {
316     import std.traits : isNarrowString;
317     import std.range: isInfinite, hasLength;
318 
319     alias R = int[];
320 
321     // range is finite and bidirectional or infinite and forward.
322     static assert(isBidirectionalRange!R ||
323                   isForwardRange!R && isInfinite!R);
324 
325     R r = [0,1];
326     auto e = r[1]; // can index
327     auto f = r.front;
328     static assert(is(typeof(e) == typeof(f))); // same type for indexed and front
329     static assert(!isNarrowString!R); // narrow strings cannot be indexed as ranges
330     static assert(hasLength!R || isInfinite!R); // must have length or be infinite
331 
332     // $ must work as it does with arrays if opIndex works with $
333     static if (is(typeof(r[$])))
334     {
335         static assert(is(typeof(f) == typeof(r[$])));
336 
337         // $ - 1 doesn't make sense with infinite ranges but needs to work
338         // with finite ones.
339         static if (!isInfinite!R)
340             static assert(is(typeof(f) == typeof(r[$ - 1])));
341     }
342 }
343 
344 // FIXME - infinite loop
345 // @safe pure unittest
346 // {
347 //     import concepts.models: models;
348 
349 //     struct A {}
350 //     struct B
351 //     {
352 //         void popFront();
353 //         @property bool empty();
354 //         @property int front();
355 //     }
356 //     struct C
357 //     {
358 //         void popFront();
359 //         @property bool empty();
360 //         @property int front();
361 //         void popBack();
362 //         @property int back();
363 //     }
364 
365 //     @models!(D, isRandomAccessRange)
366 //     struct D
367 //     {
368 //         @property bool empty();
369 //         @property D save();
370 //         @property int front();
371 //         void popFront();
372 //         @property int back();
373 //         void popBack();
374 //         ref int opIndex(uint);
375 //         @property size_t length();
376 //         alias opDollar = length;
377 //         //int opSlice(uint, uint);
378 //     }
379 //     struct E
380 //     {
381 //         bool empty();
382 //         E save();
383 //         int front();
384 //         void popFront();
385 //         int back();
386 //         void popBack();
387 //         ref int opIndex(uint);
388 //         size_t length();
389 //         alias opDollar = length;
390 //         //int opSlice(uint, uint);
391 //     }
392 //     static assert(!isRandomAccessRange!(A));
393 //     static assert(!isRandomAccessRange!(B));
394 //     static assert(!isRandomAccessRange!(C));
395 //     static assert( isRandomAccessRange!(D));
396 //     static assert( isRandomAccessRange!(E));
397 //     static assert( isRandomAccessRange!(int[]));
398 //     static assert( isRandomAccessRange!(inout(int)[]));
399 // }
400 
401 @safe pure unittest
402 {
403     // Test fix for bug 6935.
404     struct R
405     {
406         @disable this();
407 
408         @property bool empty() const { return false; }
409         @property int front() const { return 0; }
410         void popFront() {}
411 
412         @property R save() { return this; }
413 
414         @property int back() const { return 0; }
415         void popBack(){}
416 
417         int opIndex(size_t n) const { return 0; }
418         @property size_t length() const { return 0; }
419         alias opDollar = length;
420 
421         void put(int e){  }
422     }
423     static assert(isInputRange!R);
424     static assert(isForwardRange!R);
425     static assert(isBidirectionalRange!R);
426     static assert(isRandomAccessRange!R);
427     static assert(isOutputRange!(R, int));
428 }