1 /++ 2 Templates used to check primitives and 3 range primitives for arrays with multi-dimensional like API support. 4 5 Note: 6 UTF strings behaves like common arrays in Mir. 7 `std.uni.byCodePoint` can be used to create a range of characters. 8 9 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) 10 Authors: Ilia Ki, $(HTTP erdani.com, Andrei Alexandrescu), David Simcha, and 11 $(HTTP jmdavisprog.com, Jonathan M Davis). Credit for some of the ideas 12 in building this module goes to 13 $(HTTP fantascienza.net/leonardo/so/, Leonardo Maffi) 14 +/ 15 module mir.primitives; 16 17 import mir.internal.utility; 18 import mir.math.common: optmath; 19 import std.traits; 20 21 @optmath: 22 23 /++ 24 Returns: `true` if `R` has a `length` member that returns an 25 integral type implicitly convertible to `size_t`. 26 27 `R` does not have to be a range. 28 +/ 29 enum bool hasLength(R) = is(typeof( 30 (const R r, inout int = 0) 31 { 32 size_t l = r.length; 33 })); 34 35 /// 36 @safe version(mir_core_test) unittest 37 { 38 static assert(hasLength!(char[])); 39 static assert(hasLength!(int[])); 40 static assert(hasLength!(inout(int)[])); 41 42 struct B { size_t length() const { return 0; } } 43 struct C { @property size_t length() const { return 0; } } 44 static assert(hasLength!(B)); 45 static assert(hasLength!(C)); 46 } 47 48 /++ 49 Returns: `true` if `R` has a `shape` member that returns an static array type of size_t[N]. 50 +/ 51 enum bool hasShape(R) = is(typeof( 52 (const R r, inout int = 0) 53 { 54 auto l = r.shape; 55 alias F = typeof(l); 56 import std.traits; 57 static assert(isStaticArray!F); 58 static assert(is(ForeachType!F == size_t)); 59 })); 60 61 /// 62 @safe version(mir_core_test) unittest 63 { 64 static assert(hasShape!(char[])); 65 static assert(hasShape!(int[])); 66 static assert(hasShape!(inout(int)[])); 67 68 struct B { size_t length() const { return 0; } } 69 struct C { @property size_t length() const { return 0; } } 70 static assert(hasShape!(B)); 71 static assert(hasShape!(C)); 72 } 73 74 /// 75 auto shape(Range)(scope const auto ref Range range) @property 76 if (hasLength!Range || hasShape!Range) 77 { 78 static if (__traits(hasMember, Range, "shape")) 79 { 80 return range.shape; 81 } 82 else 83 { 84 size_t[1] ret; 85 ret[0] = range.length; 86 return ret; 87 } 88 } 89 90 /// 91 version(mir_core_test) unittest 92 { 93 static assert([2, 2, 2].shape == [3]); 94 } 95 96 /// 97 template DimensionCount(T) 98 { 99 import mir.ndslice.slice: Slice, SliceKind; 100 /// Extracts dimension count from a $(LREF Slice). Alias for $(LREF isSlice). 101 static if(is(T : Slice!(Iterator, N, kind), Iterator, size_t N, SliceKind kind)) 102 enum size_t DimensionCount = N; 103 else 104 static if (hasShape!T) 105 enum size_t DimensionCount = typeof(T.init.shape).length; 106 else 107 enum size_t DimensionCount = 1; 108 } 109 110 package(mir) bool anyEmptyShape(size_t N)(scope const auto ref size_t[N] shape) @property 111 { 112 foreach (i; Iota!N) 113 if (shape[i] == 0) 114 return true; 115 return false; 116 } 117 118 /// 119 bool anyEmpty(Range)(scope auto ref Range range) @property 120 if (hasShape!Range || __traits(hasMember, Range, "anyEmpty") || is(ReturnType!((Range r) => r.empty) == bool)) 121 { 122 static if (__traits(hasMember, Range, "anyEmpty")) 123 { 124 return range.anyEmpty; 125 } 126 else 127 static if (__traits(hasMember, Range, "shape")) 128 { 129 return anyEmptyShape(range.shape); 130 } 131 else 132 { 133 return range.empty; 134 } 135 } 136 137 /// 138 size_t elementCount(Range)(scope const auto ref Range range) @property 139 if (hasShape!Range || __traits(hasMember, Range, "elementCount")) 140 { 141 static if (__traits(hasMember, Range, "elementCount")) 142 { 143 return range.elementCount; 144 } 145 else 146 { 147 auto sh = range.shape; 148 size_t ret = sh[0]; 149 foreach(i; Iota!(1, sh.length)) 150 { 151 ret *= sh[i]; 152 } 153 return ret; 154 } 155 } 156 157 deprecated("use elementCount instead") 158 alias elementsCount = elementCount; 159 160 161 /++ 162 Returns the element type of a struct with `.DeepElement` inner alias or a type of common array. 163 Returns `ForeachType` if struct does not have `.DeepElement` member. 164 +/ 165 template DeepElementType(S) 166 if (is(S == struct) || is(S == class) || is(S == interface)) 167 { 168 static if (__traits(hasMember, S, "DeepElement")) 169 alias DeepElementType = S.DeepElement; 170 else 171 alias DeepElementType = ForeachType!S; 172 } 173 174 /// ditto 175 alias DeepElementType(S : T[], T) = T; 176 177 /+ ARRAY PRIMITIVES +/ 178 pragma(inline, true): 179 180 /// 181 bool empty(size_t dim = 0, T)(scope const T[] ar) 182 if (!dim) 183 { 184 return !ar.length; 185 } 186 187 /// 188 version(mir_core_test) unittest 189 { 190 assert((int[]).init.empty); 191 assert(![1].empty!0); // Slice-like API 192 } 193 194 /// 195 ref inout(T) front(size_t dim = 0, T)(scope return inout(T)[] ar) 196 if (!dim && !is(Unqual!T[] == void[])) 197 { 198 assert(ar.length, "Accessing front of an empty array."); 199 return ar[0]; 200 } 201 202 /// 203 version(mir_core_test) unittest 204 { 205 assert(*&[3, 4].front == 3); // access be ref 206 assert([3, 4].front!0 == 3); // Slice-like API 207 } 208 209 210 /// 211 ref inout(T) back(size_t dim = 0, T)(scope return inout(T)[] ar) 212 if (!dim && !is(Unqual!T[] == void[])) 213 { 214 assert(ar.length, "Accessing back of an empty array."); 215 return ar[$ - 1]; 216 } 217 218 /// 219 version(mir_core_test) unittest 220 { 221 assert(*&[3, 4].back == 4); // access be ref 222 assert([3, 4].back!0 == 4); // Slice-like API 223 } 224 225 /// 226 void popFront(size_t dim = 0, T)(scope ref inout(T)[] ar) 227 if (!dim && !is(Unqual!T[] == void[])) 228 { 229 assert(ar.length, "Evaluating popFront() on an empty array."); 230 ar = ar[1 .. $]; 231 } 232 233 /// 234 version(mir_core_test) unittest 235 { 236 auto ar = [3, 4]; 237 ar.popFront; 238 assert(ar == [4]); 239 ar.popFront!0; // Slice-like API 240 assert(ar == []); 241 } 242 243 /// 244 void popBack(size_t dim = 0, T)(scope ref inout(T)[] ar) 245 if (!dim && !is(Unqual!T[] == void[])) 246 { 247 assert(ar.length, "Evaluating popBack() on an empty array."); 248 ar = ar[0 .. $ - 1]; 249 } 250 251 /// 252 version(mir_core_test) unittest 253 { 254 auto ar = [3, 4]; 255 ar.popBack; 256 assert(ar == [3]); 257 ar.popBack!0; // Slice-like API 258 assert(ar == []); 259 } 260 261 /// 262 size_t popFrontN(size_t dim = 0, T)(scope ref inout(T)[] ar, size_t n) 263 if (!dim && !is(Unqual!T[] == void[])) 264 { 265 n = ar.length < n ? ar.length : n; 266 ar = ar[n .. $]; 267 return n; 268 } 269 270 /// 271 version(mir_core_test) unittest 272 { 273 auto ar = [3, 4]; 274 ar.popFrontN(1); 275 assert(ar == [4]); 276 ar.popFrontN!0(10); // Slice-like API 277 assert(ar == []); 278 } 279 280 /// 281 size_t popBackN(size_t dim = 0, T)(scope ref inout(T)[] ar, size_t n) 282 if (!dim && !is(Unqual!T[] == void[])) 283 { 284 n = ar.length < n ? ar.length : n; 285 ar = ar[0 .. $ - n]; 286 return n; 287 } 288 289 /// 290 version(mir_core_test) unittest 291 { 292 auto ar = [3, 4]; 293 ar.popBackN(1); 294 assert(ar == [3]); 295 ar.popBackN!0(10); // Slice-like API 296 assert(ar == []); 297 } 298 299 /// 300 void popFrontExactly(size_t dim = 0, T)(scope ref inout(T)[] ar, size_t n) 301 if (!dim && !is(Unqual!T[] == void[])) 302 { 303 assert(ar.length >= n, "Evaluating *.popFrontExactly(n) on an array with length less then n."); 304 ar = ar[n .. $]; 305 } 306 307 /// 308 version(mir_core_test) unittest 309 { 310 auto ar = [3, 4, 5]; 311 ar.popFrontExactly(2); 312 assert(ar == [5]); 313 ar.popFrontExactly!0(1); // Slice-like API 314 assert(ar == []); 315 } 316 317 /// 318 void popBackExactly(size_t dim = 0, T)(scope ref inout(T)[] ar, size_t n) 319 if (!dim && !is(Unqual!T[] == void[])) 320 { 321 assert(ar.length >= n, "Evaluating *.popBackExactly(n) on an array with length less then n."); 322 ar = ar[0 .. $ - n]; 323 } 324 325 /// 326 version(mir_core_test) unittest 327 { 328 auto ar = [3, 4, 5]; 329 ar.popBackExactly(2); 330 assert(ar == [3]); 331 ar.popBackExactly!0(1); // Slice-like API 332 assert(ar == []); 333 } 334 335 /// 336 size_t length(size_t d : 0, T)(in T[] array) 337 if (d == 0) 338 { 339 return array.length; 340 } 341 342 /// 343 version(mir_core_test) unittest 344 { 345 assert([1, 2].length!0 == 2); 346 assert([1, 2].elementCount == 2); 347 } 348 349 /// 350 inout(T)[] save(T)(scope return inout(T)[] array) 351 { 352 return array; 353 } 354 355 /// 356 version(mir_core_test) unittest 357 { 358 auto a = [1, 2]; 359 assert(a is a.save); 360 } 361 362 /** 363 Returns `true` if `R` is an input range. An input range must 364 define the primitives `empty`, `popFront`, and `front`. The 365 following code should compile for any input range. 366 ---- 367 R r; // can define a range object 368 if (r.empty) {} // can test for empty 369 r.popFront(); // can invoke popFront() 370 auto h = r.front; // can get the front of the range of non-void type 371 ---- 372 The following are rules of input ranges are assumed to hold true in all 373 Phobos code. These rules are not checkable at compile-time, so not conforming 374 to these rules when writing ranges or range based code will result in 375 undefined behavior. 376 $(UL 377 $(LI `r.empty` returns `false` if and only if there is more data 378 available in the range.) 379 $(LI `r.empty` evaluated multiple times, without calling 380 `r.popFront`, or otherwise mutating the range object or the 381 underlying data, yields the same result for every evaluation.) 382 $(LI `r.front` returns the current element in the range. 383 It may return by value or by reference.) 384 $(LI `r.front` can be legally evaluated if and only if evaluating 385 `r.empty` has, or would have, equaled `false`.) 386 $(LI `r.front` evaluated multiple times, without calling 387 `r.popFront`, or otherwise mutating the range object or the 388 underlying data, yields the same result for every evaluation.) 389 $(LI `r.popFront` advances to the next element in the range.) 390 $(LI `r.popFront` can be called if and only if evaluating `r.empty` 391 has, or would have, equaled `false`.) 392 ) 393 Also, note that Phobos code assumes that the primitives `r.front` and 394 `r.empty` are $(BIGOH 1) time complexity wise or "cheap" in terms of 395 running time. $(BIGOH) statements in the documentation of range functions 396 are made with this assumption. 397 Params: 398 R = type to be tested 399 Returns: 400 `true` if R is an input range, `false` if not 401 */ 402 enum bool isInputRange(R) = 403 is(typeof(R.init) == R) 404 && is(ReturnType!((R r) => r.empty) == bool) 405 && is(typeof((return ref R r) => r.front)) 406 && !is(ReturnType!((R r) => r.front) == void) 407 && is(typeof((R r) => r.popFront)); 408 409 /** 410 Returns `true` if `R` is an infinite input range. An 411 infinite input range is an input range that has a statically-defined 412 enumerated member called `empty` that is always `false`, 413 for example: 414 ---- 415 struct MyInfiniteRange 416 { 417 enum bool empty = false; 418 ... 419 } 420 ---- 421 */ 422 423 template isInfinite(R) 424 { 425 static if (isInputRange!R && __traits(compiles, { enum e = R.empty; })) 426 enum bool isInfinite = !R.empty; 427 else 428 enum bool isInfinite = false; 429 } 430 431 432 /** 433 The element type of `R`. `R` does not have to be a range. The 434 element type is determined as the type yielded by `r.front` for an 435 object `r` of type `R`. For example, `ElementType!(T[])` is 436 `T` if `T[]` isn't a narrow string; if it is, the element type is 437 `dchar`. If `R` doesn't have `front`, `ElementType!R` is 438 `void`. 439 */ 440 template ElementType(R) 441 { 442 static if (is(typeof(R.init.front.init) T)) 443 alias ElementType = T; 444 else 445 alias ElementType = void; 446 } 447 448 /++ 449 This is a best-effort implementation of `length` for any kind of 450 range. 451 If `hasLength!Range`, simply returns `range.length` without 452 checking `upTo` (when specified). 453 Otherwise, walks the range through its length and returns the number 454 of elements seen. Performes $(BIGOH n) evaluations of `range.empty` 455 and `range.popFront()`, where `n` is the effective length of $(D 456 range). 457 +/ 458 auto walkLength(Range)(Range range) 459 if (isIterable!Range && !isInfinite!Range) 460 { 461 static if (hasLength!Range) 462 return range.length; 463 else 464 static if (__traits(hasMember, Range, "walkLength")) 465 return range.walkLength; 466 static if (isInputRange!Range) 467 { 468 size_t result; 469 for ( ; !range.empty ; range.popFront() ) 470 ++result; 471 return result; 472 } 473 else 474 { 475 size_t result; 476 foreach (ref e; range) 477 ++result; 478 return result; 479 } 480 } 481 482 /++ 483 Returns `true` if `R` is an output range for elements of type 484 `E`. An output range is defined functionally as a range that 485 supports the operation $(D r.put(e)). 486 +/ 487 enum bool isOutputRange(R, E) = 488 is(typeof(R.init.put(E.init)));