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 }