1 /** 2 * Metaprogramming 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <vladimir@thecybershadow.net> 12 */ 13 14 module ae.utils.meta; 15 16 public import ae.utils.meta.reference; 17 public import ae.utils.meta.x; 18 public import ae.utils.meta.proxy; 19 public import ae.utils.meta.binding_v1; 20 public import ae.utils.meta.binding; 21 22 // ************************************************************************ 23 24 import std.algorithm; 25 import std.range; 26 import std.string; 27 import std.traits; 28 import std.typetuple; 29 30 /** 31 * Same as TypeTuple, but meant to be used with values. 32 * 33 * Example: 34 * foreach (char channel; ValueTuple!('r', 'g', 'b')) 35 * { 36 * // the loop is unrolled at compile-time 37 * // "channel" is a compile-time value, and can be used in string mixins 38 * } 39 */ 40 template ValueTuple(T...) 41 { 42 alias T ValueTuple; 43 } 44 45 template RangeTupleImpl(size_t N, R...) 46 { 47 static if (N==R.length) 48 alias R RangeTupleImpl; 49 else 50 alias RangeTupleImpl!(N, ValueTuple!(R, R.length)) RangeTupleImpl; 51 } 52 53 /// Generate a tuple containing integers from 0 to N-1. 54 /// Useful for static loop unrolling. (staticIota) 55 template RangeTuple(size_t N) 56 { 57 alias RangeTupleImpl!(N, ValueTuple!()) RangeTuple; 58 } 59 60 /// Expand an array to a tuple. 61 /// The array value must be known during compilation. 62 template ArrayToTuple(alias arr, Elements...) 63 { 64 static if (arr.length) 65 alias ArrayToTuple = ArrayToTuple!(arr[1..$], ValueTuple!(Elements, arr[0])); 66 else 67 alias ArrayToTuple = Elements; 68 } 69 70 unittest 71 { 72 alias X = ArrayToTuple!"abc"; 73 static assert(X[0] == 'a' && X[2] == 'c'); 74 static assert([X] == "abc"); 75 } 76 77 /// Expand a static array to a tuple. 78 /// Unlike ArrayToTuple, the array may be a runtime variable. 79 template expand(alias arr, size_t offset = 0) 80 if (isStaticArray!(typeof(arr))) 81 { 82 static if (arr.length == offset) 83 alias expand = AliasSeq!(); 84 else 85 { 86 @property ref getValue() { return arr[offset]; } 87 alias expand = AliasSeq!(getValue, expand!(arr, offset+1)); 88 } 89 } 90 91 unittest 92 { 93 int[3] arr = [1, 2, 3]; 94 void test(int a, int b, int c) {} 95 test(expand!arr); 96 } 97 98 /// Return something to foreach over optimally. 99 /// If A is known at compile-time, return a tuple, 100 /// so the foreach is unrolled at compile-time. 101 /// Otherwise, return A for a regular runtime foreach. 102 template CTIterate(alias A) 103 { 104 static if (is(typeof(ArrayToTuple!A))) 105 enum CTIterate = ArrayToTuple!A; 106 else 107 alias CTIterate = A; 108 } 109 110 unittest 111 { 112 foreach (c; CTIterate!"abc") {} 113 string s; 114 foreach (c; CTIterate!s) {} 115 } 116 117 /// Like std.typecons.Tuple, but a template mixin. 118 /// Unlike std.typecons.Tuple, names may not be omitted - but repeating types may be. 119 /// Example: FieldList!(ubyte, "r", "g", "b", ushort, "a"); 120 mixin template FieldList(Fields...) 121 { 122 mixin(GenFieldList!(void, Fields)); 123 } 124 125 template GenFieldList(T, Fields...) 126 { 127 static if (Fields.length == 0) 128 enum GenFieldList = ""; 129 else 130 { 131 static if (is(typeof(Fields[0]) == string)) 132 enum GenFieldList = T.stringof ~ " " ~ Fields[0] ~ ";\n" ~ GenFieldList!(T, Fields[1..$]); 133 else 134 enum GenFieldList = GenFieldList!(Fields[0], Fields[1..$]); 135 } 136 } 137 138 unittest 139 { 140 struct S 141 { 142 mixin FieldList!(ubyte, "r", "g", "b", ushort, "a"); 143 } 144 S s; 145 static assert(is(typeof(s.r) == ubyte)); 146 static assert(is(typeof(s.g) == ubyte)); 147 static assert(is(typeof(s.b) == ubyte)); 148 static assert(is(typeof(s.a) == ushort)); 149 } 150 151 /// Return true if all of T's fields are the same type. 152 @property bool isHomogenous(T)() 153 { 154 foreach (i, f; T.init.tupleof) 155 if (!is(typeof(T.init.tupleof[i]) == typeof(T.init.tupleof[0]))) 156 return false; 157 return true; 158 } 159 160 template isValueOfTypeInTuple(X, T...) 161 { 162 static if (T.length==0) 163 enum bool isValueOfTypeInTuple = false; 164 else 165 static if (T.length==1) 166 enum bool isValueOfTypeInTuple = is(typeof(T[0]) : X); 167 else 168 enum bool isValueOfTypeInTuple = isValueOfTypeInTuple!(X, T[0..$/2]) || isValueOfTypeInTuple!(X, T[$/2..$]); 169 } 170 171 unittest 172 { 173 static assert( isValueOfTypeInTuple!(int, ValueTuple!("a", 42))); 174 static assert(!isValueOfTypeInTuple!(int, ValueTuple!("a", 42.42))); 175 static assert(!isValueOfTypeInTuple!(int, ValueTuple!())); 176 177 static assert(!isValueOfTypeInTuple!(int, "a", int, Object)); 178 static assert( isValueOfTypeInTuple!(int, "a", int, Object, 42)); 179 } 180 181 template findValueOfTypeInTuple(X, T...) 182 { 183 static if (T.length==0) 184 static assert(false, "Can't find value of type " ~ X.stringof ~ " in specified tuple"); 185 else 186 static if (is(typeof(T[0]) : X)) 187 enum findValueOfTypeInTuple = T[0]; 188 else 189 enum findValueOfTypeInTuple = findValueOfTypeInTuple!(X, T[1..$]); 190 } 191 192 unittest 193 { 194 static assert(findValueOfTypeInTuple!(int, ValueTuple!("a", 42))==42); 195 static assert(findValueOfTypeInTuple!(int, "a", int, Object, 42)==42); 196 } 197 198 /// One past the biggest element of the enum T. 199 /// Example: string[enumLength!E] arr; 200 template enumLength(T) 201 if (is(T==enum)) 202 { 203 enum enumLength = cast(T)(cast(size_t)T.max + 1); 204 } 205 206 deprecated alias EnumLength = enumLength; 207 208 /// A range that iterates over all members of an enum. 209 @property auto enumIota(T)() { return iota(T.init, enumLength!T); } 210 211 unittest 212 { 213 enum E { a, b, c } 214 static assert(equal(enumIota!E, [E.a, E.b, E.c])); 215 } 216 217 // ************************************************************************ 218 219 // http://d.puremagic.com/issues/show_bug.cgi?id=7805 220 static template stringofArray(Args...) 221 { 222 static string[] stringofArray() 223 { 224 string[] args; 225 foreach (i, _ ; typeof(Args)) 226 args ~= Args[i].stringof; 227 return args; 228 } 229 } 230 231 /// Returns the index of fun's parameter with the name 232 /// matching "names", or asserts if the parameter is not found. 233 /// "names" can contain multiple names separated by slashes. 234 static size_t findParameter(alias fun, string names)() 235 { 236 foreach (name; names.split("/")) 237 foreach (i, param; ParameterIdentifierTuple!fun) 238 if (param == name) 239 return i; 240 assert(false, "Function " ~ __traits(identifier, fun) ~ " doesn't have a parameter called " ~ name); 241 } 242 243 /// ditto 244 // Workaround for no "static alias" template parameters 245 static size_t findParameter()(string[] searchedNames, string soughtNames, string funName) 246 { 247 foreach (soughtName; soughtNames.split("/")) 248 { 249 auto targetIndex = searchedNames.countUntil(soughtName); 250 if (targetIndex >= 0) 251 return targetIndex; 252 } 253 assert(false, "No argument %s in %s's parameters (%s)".format(soughtNames, funName, searchedNames).idup); 254 } 255 256 /// Generates a function which passes its arguments to a struct, which is 257 /// returned. Preserves field names (as parameter names) and default values. 258 template structFun(S) 259 { 260 string gen() 261 { 262 enum identifierAt(int n) = __traits(identifier, S.tupleof[n]); 263 enum names = [staticMap!(identifierAt, RangeTuple!(S.tupleof.length))]; 264 265 return 266 "S structFun(\n" ~ 267 S.tupleof.length.iota.map!(n => 268 " typeof(S.init.tupleof[%d]) %s = S.init.tupleof[%d],\n".format(n, names[n], n) 269 ).join() ~ 270 `) { return S(` ~ names.join(", ") ~ "); }"; 271 } 272 273 mixin(gen()); 274 } 275 276 unittest 277 { 278 static struct Test 279 { 280 string a; 281 int b = 42; 282 } 283 284 Test test = structFun!Test("banana"); 285 assert(test.a is "banana"); 286 assert(test.b == 42); 287 } 288 289 // ************************************************************************ 290 291 // Using a compiler with UDA support? 292 enum HAVE_UDA = __traits(compiles, __traits(getAttributes, Object)); 293 294 static if (HAVE_UDA) 295 { 296 /* 297 template hasAttribute(T, alias D) 298 { 299 enum bool hasAttribute = isValueOfTypeInTuple!(T, __traits(getAttributes, D)); 300 } 301 */ 302 303 /// Detects types and values of the given type 304 template hasAttribute(Args...) 305 if (Args.length == 2) 306 { 307 // alias attribute = Args[0]; 308 // alias symbol = Args[1]; 309 310 import std.typetuple : staticIndexOf; 311 import std.traits : staticMap; 312 313 static if (is(Args[0])) 314 { 315 template isTypeOrValueInTuple(T, Args...) 316 { 317 static if (!Args.length) 318 enum isTypeOrValueInTuple = false; 319 else 320 static if (is(Args[0] == T)) 321 enum isTypeOrValueInTuple = true; 322 else 323 static if (is(typeof(Args[0]) == T)) 324 enum isTypeOrValueInTuple = true; 325 else 326 enum isTypeOrValueInTuple = isTypeOrValueInTuple!(T, Args[1..$]); 327 } 328 329 enum bool hasAttribute = isTypeOrValueInTuple!(Args[0], __traits(getAttributes, Args[1])); 330 } 331 else 332 enum bool hasAttribute = staticIndexOf!(Args[0], __traits(getAttributes, Args[1])) != -1; 333 } 334 335 template getAttribute(T, alias D) 336 { 337 enum T getAttribute = findValueOfTypeInTuple!(T, __traits(getAttributes, D)); 338 } 339 340 unittest 341 { 342 struct Attr { int i; } 343 344 struct S 345 { 346 @Attr int a; 347 @Attr(5) int b; 348 @("test") int c; 349 } 350 351 static assert(hasAttribute!(Attr, S.a)); 352 static assert(hasAttribute!(Attr, S.b)); 353 static assert(hasAttribute!(string, S.c)); 354 static assert(hasAttribute!("test", S.c)); 355 } 356 } 357 else 358 { 359 template hasAttribute(T, alias D) 360 { 361 enum bool hasAttribute = false; 362 } 363 364 template getAttribute(T, alias D) 365 { 366 static assert(false, "This D compiler has no UDA support."); 367 } 368 } 369 370 // ************************************************************************ 371 372 /// Generate constructors that simply call the parent class constructors. 373 /// Based on http://forum.dlang.org/post/i3hpj0$2vc6$1@digitalmars.com 374 mixin template GenerateContructorProxies() 375 { 376 mixin(() { 377 import std.conv : text; 378 import std.string : join; 379 import std.traits : ParameterTypeTuple, fullyQualifiedName; 380 381 alias T = typeof(super); 382 383 string s; 384 static if (__traits(hasMember, T, "__ctor")) 385 foreach (ctor; __traits(getOverloads, T, "__ctor")) 386 { 387 string[] declarationList, usageList; 388 foreach (i, param; ParameterTypeTuple!(typeof(&ctor))) 389 { 390 auto varName = "v" ~ text(i); 391 declarationList ~= fullyQualifiedName!param ~ " " ~ varName; 392 usageList ~= varName; 393 } 394 s ~= "this(" ~ declarationList.join(", ") ~ ") { super(" ~ usageList.join(", ") ~ "); }\n"; 395 } 396 return s; 397 } ()); 398 } 399 400 unittest 401 { 402 class A 403 { 404 int i, j; 405 this() { } 406 this(int i) { this.i = i; } 407 this(int i, int j ) { this.i = i; this.j = j; } 408 } 409 410 class B : A 411 { 412 mixin GenerateContructorProxies; 413 } 414 415 A a; 416 417 a = new B(); 418 assert(a.i == 0); 419 a = new B(17); 420 assert(a.i == 17); 421 a = new B(17, 42); 422 assert(a.j == 42); 423 } 424 425 // ************************************************************************ 426 427 /// Generate a @property function which creates/returns 428 /// a thread-local singleton of a class with the given arguments. 429 430 @property T singleton(T, args...)() 431 if (is(typeof(new T(args)))) 432 { 433 static T instance; 434 if (!instance) 435 instance = new T(args); 436 return instance; 437 } 438 439 unittest 440 { 441 static class C 442 { 443 static int n = 0; 444 445 this() { n++; } 446 this(int x) { n += x; } 447 448 void fun() {} 449 } 450 451 alias singleton!C c0; 452 c0.fun(); 453 c0.fun(); 454 assert(C.n == 1); 455 456 alias singleton!(C, 5) c1; 457 c1.fun(); 458 c1.fun(); 459 assert(C.n == 6); 460 } 461 462 // ************************************************************************ 463 464 /// Were we built with -debug? 465 debug 466 enum isDebug = true; 467 else 468 enum isDebug = false; 469 470 deprecated alias IsDebug = isDebug; 471 472 /// Is a specific version on? 473 template isVersion(string versionName) 474 { 475 mixin(`version (` ~ versionName ~ `) enum isVersion = true; else enum isVersion = false;`); 476 } 477 478 // ************************************************************************ 479 480 /// Identity function. 481 auto ref T identity(T)(auto ref T value) { return value; } 482 483 /// Shorter synonym for std.traits.Identity. 484 /// Can be used to UFCS-chain static methods and nested functions. 485 alias I(alias A) = A; 486 487 // ************************************************************************ 488 489 /// Get f's ancestor which represents its "this" pointer. 490 /// Skips template and mixin ancestors until it finds a struct or class. 491 template thisOf(alias f) 492 { 493 alias p = Identity!(__traits(parent, f)); 494 static if (is(p == class) || is(p == struct) || is(p == union)) 495 alias thisOf = p; 496 else 497 alias thisOf = thisOf!p; 498 } 499 500 // ************************************************************************ 501 502 /// Return the number of bits used to store the value part, i.e. 503 /// T.sizeof*8 for integer parts and the mantissa size for 504 /// floating-point types. 505 template valueBits(T) 506 { 507 static if (is(T : ulong)) 508 enum valueBits = T.sizeof * 8; 509 else 510 static if (is(T : real)) 511 enum valueBits = T.mant_dig; 512 else 513 static assert(false, "Don't know how many value bits there are in " ~ T.stringof); 514 } 515 516 static assert(valueBits!uint == 32); 517 static assert(valueBits!double == 53); 518 519 /// Expand to a built-in numeric type of the same kind 520 /// (signed integer / unsigned integer / floating-point) 521 /// with at least the indicated number of bits of precision. 522 template ResizeNumericType(T, uint bits) 523 { 524 static if (is(T : ulong)) 525 static if (isSigned!T) 526 alias ResizeNumericType = SignedBitsType!bits; 527 else 528 alias ResizeNumericType = UnsignedBitsType!bits; 529 else 530 static if (is(T : real)) 531 { 532 static if (bits <= float.mant_dig) 533 alias ResizeNumericType = float; 534 else 535 static if (bits <= double.mant_dig) 536 alias ResizeNumericType = double; 537 else 538 static if (bits <= real.mant_dig) 539 alias ResizeNumericType = real; 540 else 541 static assert(0, "No floating-point type big enough to fit " ~ bits.stringof ~ " bits"); 542 } 543 else 544 static assert(false, "Don't know how to resize type: " ~ T.stringof); 545 } 546 547 static assert(is(ResizeNumericType!(float, double.mant_dig) == double)); 548 549 /// Expand to a built-in numeric type of the same kind 550 /// (signed integer / unsigned integer / floating-point) 551 /// with at least additionalBits more bits of precision. 552 alias ExpandNumericType(T, uint additionalBits) = 553 ResizeNumericType!(T, valueBits!T + additionalBits); 554 555 /// Unsigned integer type big enough to fit N bits of precision. 556 template UnsignedBitsType(uint bits) 557 { 558 static if (bits <= 8) 559 alias ubyte UnsignedBitsType; 560 else 561 static if (bits <= 16) 562 alias ushort UnsignedBitsType; 563 else 564 static if (bits <= 32) 565 alias uint UnsignedBitsType; 566 else 567 static if (bits <= 64) 568 alias ulong UnsignedBitsType; 569 else 570 static assert(0, "No integer type big enough to fit " ~ bits.stringof ~ " bits"); 571 } 572 573 template SignedBitsType(uint bits) 574 { 575 alias Signed!(UnsignedBitsType!bits) SignedBitsType; 576 } 577 578 /// Evaluates to array of strings with name for each field. 579 @property string[] structFields(T)() 580 if (is(T == struct) || is(T == class)) 581 { 582 import std.string : split; 583 584 string[] fields; 585 foreach (i, f; T.init.tupleof) 586 { 587 string field = T.tupleof[i].stringof; 588 field = field.split(".")[$-1]; 589 fields ~= field; 590 } 591 return fields; 592 }