The OpenD Programming Language

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)));