1 module evx.meta.list;
2 
3 private {//imports
4 	import std.typetuple;
5 	import std.typecons;
6 
7 	import evx.meta.predicate;
8 }
9 
10 /**
11     construct a list, which automatically flattens
12 */
13 alias Cons (T...) = T;
14 
15 /**
16     generate a sequence of contiguous natural numbers
17 */
18 alias Iota (size_t n) = staticIota!(0,n);
19 /**
20     ditto
21 */
22 alias Iota (size_t l, size_t r) = staticIota!(l,r);
23 ///
24 unittest {
25     static assert (Iota!4 == Cons!(0,1,2,3));
26     static assert (Iota!(2,6) == Cons!(2,3,4,5));
27 }
28 
29 /**
30     repeat an argument a given number of times
31 */
32 alias Repeat (size_t n, T...) = Cons!(T, Repeat!(n-1, T));
33 /**
34     ditto
35 */
36 alias Repeat (size_t n : 0, T...) = Cons!();
37 ///
38 unittest {
39     static assert (Repeat!(4, 1) == Cons!(1,1,1,1));
40     static assert (is (Repeat!(3, int, char) == Cons!(int, char, int, char, int, char)));
41 }
42 
43 /** 
44     reverse the order of a list
45 */
46 alias Reverse = std.typetuple.Reverse;
47 ///
48 unittest {
49     static assert (Reverse!(1,2,3) == Cons!(3,2,1));
50 }
51 
52 /**
53     swap the two elements in a compile-time list indexed by i and j
54 */
55 template Swap (uint i, uint j, T...)
56 {
57     import std.algorithm: min, max;
58 
59     enum a = min (i,j);
60     enum b = max (i,j);
61 
62     alias Swap = Cons!(T[0..a], T[b], T[a+1..b], T[a], T[b+1..$]);
63 }
64 ///
65 unittest {
66     static assert (is (Swap!(0,3, int, bool, char, byte) == Cons!(byte, bool, char, int)));
67     static assert (Swap!(0,3, 0,1,2,3) == Cons!(3,1,2,0));
68 }
69 
70 ////
71 /**
72     map a template over a list
73 */
74 template Map (alias F, T...)
75 {
76 	static if (T.length == 0)
77 	{
78 		alias Map = Cons!();
79 	}
80 	else
81 	{
82 		alias Map = Cons!(F!(Unpack!(T[0])), Map!(F, T[1..$]));
83 	}
84 }
85 ///
86 unittest {
87     alias ArrayOf (T) = T[];
88 
89     static assert (is (Map!(ArrayOf, int, char, string) == Cons!(int[], char[], string[])));
90     static assert (Map!(Iota, 1, 2, 3) == Cons!(0, 0,1, 0,1,2));
91 }
92 
93 /**
94     map each element of a list to its position in the list
95 */
96 alias Ordinal (T...) = Iota!(T.length);
97 ///
98 unittest {
99     static assert (Ordinal!(Cons!(int, char, string)) == Cons!(0,1,2));
100 }
101 
102 /**
103     pair each element's position in the list with the element
104 */
105 alias Enumerate (T...) = Zip!(Pack!(Ordinal!T), Pack!T);
106 ///
107 unittest {
108     static assert (is (Enumerate!('a', 'b', 'c') == Cons!(Pack!(0, 'a'), Pack!(1, 'b'), Pack!(2, 'c'))));
109 }
110 
111 /**
112     sort a list by <
113 */
114 template Sort (T...)
115 {
116 	alias less_than (T...) = First!(T[0] < T[1]);
117 
118 	alias Sort = SortBy!(less_than, T);
119 }
120 ///
121 unittest {
122 	static assert (Sort!(5,4,2,7,4,3,1) == Cons!(1,2,3,4,4,5,7));
123 }
124 /** sort a list by a custom comparison
125 */
126 template SortBy (alias compare, T...)
127 {
128 	static if (T.length > 1)
129 	{
130 		alias Remaining = Cons!(T[0..$/2], T[$/2 +1..$]);
131 		enum is_before (U...) = compare!(U[0], T[$/2]);
132 
133 		alias SortBy = Cons!(
134 			SortBy!(compare, Filter!(is_before, Remaining)),
135 			T[$/2],
136 			SortBy!(compare, Filter!(Not!is_before, Remaining)),
137 		);
138 	}
139 	else alias SortBy = T;
140 }
141 ///
142 unittest {
143 	static assert (is (
144         SortBy!(λ!q{(T,U) = T.sizeof < U.sizeof},
145             int, char, long, short
146         ) == Cons!(
147             char, short, int, long
148         )
149     ));
150 }
151 
152 ////
153 /**
154     evaluates true if all items in the list satisfy the condition
155 */
156 template All (alias cond, T...)
157 {
158 	static if (T.length == 0)
159 	{
160 		enum All = true;
161 	}
162 	else
163 	{
164 		enum All = cond!(Unpack!(T[0])) && All!(cond, T[1..$]);
165 	}
166 }
167 ///
168 unittest {
169     import std.traits : isIntegral;
170 
171     static assert (All!(isIntegral, int, long, short));
172     static assert (not (All!(isIntegral, int, float, short)));
173 }
174 
175 /**
176     evaluates true if any items in the list satisfy the condition
177 */
178 template Any (alias cond, T...)
179 {
180 	static if (T.length == 0)
181 	{
182 		enum Any = false;
183 	}
184 	else
185 	{
186 		enum Any = cond!(Unpack!(T[0])) || Any!(cond, T[1..$]);
187 	}
188 }
189 ///
190 unittest {
191     import std.traits : isIntegral;
192 
193     static assert (Any!(isIntegral, float, long, string));
194     static assert (not (Any!(isIntegral, string, float, void)));
195 }
196 
197 /**
198     from a given list, produce a new list containing only items which satisfy a condition
199 */
200 template Filter (alias cond, T...)
201 {
202 	static if (T.length == 0)
203 	{
204 		alias Filter = Cons!();
205 	}
206 	else
207 	{
208 		static if (cond!(Unpack!(T[0])))
209 			alias Filter = Cons!(T[0], Filter!(cond, T[1..$]));
210 		else alias Filter = Filter!(cond, T[1..$]);
211 	}
212 }
213 ///
214 unittest {
215     import std.traits: isIntegral;
216 
217     static assert (is (Filter!(isIntegral, float, int, string, short) == Cons!(int, short)));
218 }
219 
220 /** from a given list, produce a new list containing only unique items
221 */
222 alias NoDuplicates = std.typetuple.NoDuplicates;
223 ///
224 unittest {
225     static assert (is (NoDuplicates!(int, char, int, string, int) == Cons!(int, char, string)));
226 }
227 
228 /** reduce a list to a single item using a binary template
229 */
230 template Reduce (alias f, T...)
231 {
232 	static if (T.length == 2)
233 		alias Reduce = f!T;
234 	else 
235 		alias Reduce = f!(T[0], Reduce!(f, T[1..$]));
236 }
237 ///
238 unittest {
239     import std.traits: Select;
240 
241     alias Smallest (T,U) = Select!(T.sizeof < U.sizeof, T, U);
242 
243     static assert (is (Reduce!(Smallest, int, short, double, byte, string) == byte));
244 }
245 
246 /** from a given list, produce a list of all partial reductions, sweeping from left to right
247 */
248 template Scan (alias f, T...)
249 {
250 	template Sweep (size_t i)
251 	{
252 		static if (i == 0)
253 			alias Sweep = Cons!(T[i]);
254 		else
255 			alias Sweep = Reduce!(f, T[0..i+1]);
256 	}
257 
258 	alias Scan = Map!(Sweep, Ordinal!T);
259 }
260 ///
261 unittest {
262 	static assert (Scan!(Sum, 0,1,2,3,4,5) == Cons!(0,1,3,6,10,15));
263 }
264 
265 /**
266     sum a list
267 */
268 alias Sum (T...) = Reduce!(λ!q{(long a, long b) = a + b}, T);
269 ///
270 unittest {
271 	static assert (Sum!(0,1,2,3,4,5) == 15);
272 }
273 
274 ////
275 /**
276     find the index of the first occurence of an item in a list, or -1
277 */
278 alias IndexOf = staticIndexOf;
279 ///
280 unittest {
281     static assert (IndexOf!(2, 1,2,3) == 1);
282 }
283 
284 /**
285     evaluates true if a list contains a given item
286 */
287 enum Contains (T...) = IndexOf!(T[0], T[1..$]) > -1;
288 ///
289 unittest {
290     static assert (Contains!(1, 0,1,2,3));
291     static assert (not (Contains!(1, 7,8,9,'A')));
292 }
293 
294 ////
295 /**
296     from a list of n packs of length m, produce a list of m packs of length n
297 */
298 template Zip (Packs...)
299 {
300 	enum n = Packs.length;
301 	enum length = Packs[0].length;
302 
303 	enum CheckLength (uint i) = Packs[i].length == Packs[0].length;
304 
305 	static assert (All!(Map!(CheckLength, Iota!n)));
306 
307 	template ToPack (uint i)
308 	{
309 		alias ExtractAt (uint j) = Cons!(Packs[j].Unpack[i]);
310 
311 		alias ToPack = Pack!(Map!(ExtractAt, Iota!n));
312 	}
313 
314 	alias Zip = Map!(ToPack, Iota!length);
315 }
316 ///
317 unittest {
318     static assert (is (
319         Zip!(
320             Pack!(  0,  1,  2 ),
321             Pack!( 'a','b','c' ),
322         ) == Cons!(
323             Pack!( 0, 'a' ),
324             Pack!( 1, 'b' ), 
325             Pack!( 2, 'c' )
326         )
327     ));
328     
329     // Map and Filter automatically unpack Packs before passing them through to predicates
330     enum Binary (T,U) = T.stringof ~ U.stringof;
331 
332     static assert (
333         Map!(Binary, Zip!(
334             Pack!( int, string, char ),
335             Pack!( string, int, char ),
336         )) == Cons!(
337             `intstring`, `stringint`, `charchar`
338         )
339     );
340 
341     enum binary (T,U) = is (T == U);
342 
343     static assert (is (
344         Filter!(binary,
345             Zip!(
346                 Pack!( int, string, char ),
347                 Pack!( int, string, bool ),
348             )
349         ) == Cons!(
350             Pack!(int, int),
351             Pack!(string, string)
352         )
353     ));
354 }
355 
356 /**
357     a pack is a list which doesn't automatically flatten
358 */
359 struct Pack (T...)
360 {
361 	alias Unpack = T;
362 	alias Unpack this;
363 	enum length = T.length;
364 }
365 ///
366 unittest {
367     static assert (
368         Cons!(
369             Cons!(1,2), Cons!(3)
370         )
371         == Cons!(1,2,3)
372     );
373 
374     static assert (is (
375         Pack!(
376             Pack!(1,2), Pack!(3)
377         )
378         == Pack!(
379             Pack!(1,2), Pack!(3)
380         )
381     ));
382 }
383 
384 /**
385     unpack a packed list, and pass non-pack parameters through unmodified
386 */
387 template Unpack (T...)
388 {
389 	static if (is (T[0] == Pack!U, U...))
390 		alias Unpack = Cons!(T[0].Unpack);
391 	else
392 		alias Unpack = T;
393 }
394 ///
395 unittest {
396     static assert (Unpack!(Pack!(1,2)) == Cons!(1,2));
397     static assert (Unpack!(1,2) == Cons!(1,2));
398 }
399 
400 /**
401     get the first element of a list
402 */
403 template First (T...)
404 {
405 	static if (is (typeof((){enum U = T[0];})))
406 		enum First = T[0];
407 	else 
408 		alias First = T[0];
409 }
410 ///
411 unittest {
412     static assert (First!('a', 'b', 'c') == 'a');
413 }
414 
415 /**
416     get the second element of a list
417 */
418 template Second (T...)
419 {
420 	static if (is (typeof((){enum U = T[1];})))
421 		enum Second = T[1];
422 	else
423 		alias Second = T[1];
424 }
425 ///
426 unittest {
427     static assert (Second!('a', 'b', 'c') == 'b');
428 }
429 
430 /**
431     split a list into n-lists and interleave their items into a new list
432 */
433 template InterleaveNLists (uint n, T...)
434 if (T.length % n == 0)
435 {
436 	template Group (uint i)
437 	{
438 		alias Item (uint j) = Cons!(T[($/n)*j + i]);
439 
440 		alias Group = Map!(Item, Iota!n);
441 	}
442 
443 	alias InterleaveNLists = Map!(Group, Iota!(T.length/n));
444 }
445 ///
446 unittest {
447 	static assert (InterleaveNLists!(2, 0,1,2,3,4,5) == Cons!(0,3,1,4,2,5));
448 	static assert (InterleaveNLists!(3, 0,1,2,3,4,5) == Cons!(0,2,4,1,3,5));
449 }
450 
451 /**
452     partition a list into equivalence classes of each element's order modulo n
453 */
454 alias DeinterleaveNLists (uint n, T...) = InterleaveNLists!(T.length/n, T);
455 ///
456 unittest {
457 	static assert (DeinterleaveNLists!(2, 0,3,1,4,2,5) == Cons!(0,1,2,3,4,5));
458 	static assert (DeinterleaveNLists!(3, 0,2,4,1,3,5) == Cons!(0,1,2,3,4,5));
459 }
460 
461 /**
462     shorthand for (de)interleaving templates
463 */
464 alias Interleave (T...) = InterleaveNLists!(2,T);
465 /**
466     ditto
467 */
468 alias Deinterleave (T...) = DeinterleaveNLists!(2,T);
469 
470 /**
471     map each item in a list to the given member in each item
472 */
473 template Extract (string member, T...)
474 {
475 	static if (T.length == 0)
476 		alias Extract = Cons!();
477 	else mixin(q{
478 		alias Extract = Cons!(T[0].}~(member)~q{, Extract!(member, T[1..$]));
479 	});
480 }
481 ///
482 unittest {
483     static assert (Extract!(`sizeof`, byte, short, int, long) == Cons!(1,2,4,8));
484 }
485 
486 /**
487     get all items in a list of zero-parameter templates which successfully compiles
488 */
489 template MatchAll (patterns...)
490 {
491 	template compiles (alias pattern)
492 	{
493 		void attempt ()()
494 		{
495 			alias attempt = pattern!();
496 		}
497 
498 		enum compiles = is (typeof(attempt ()));
499 	}
500 
501 	alias MatchAll = Filter!(compiles, patterns);
502 }
503 ///
504 unittest {
505     void a ()() {NONSENSE;}
506     byte b ()() {return 0;}
507     char c ()() {}
508     long d ()() {int x; ++x; return x;}
509 
510     static assert (MatchAll!(a,b,c,d).stringof == q{tuple(b()(), d()())});
511 }