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 }