1 /++ 2 It is sometimes necessary to create a function which is an exact copy of 3 another function. Or sometimes it is necessary to introduce a few 4 variations, while carrying all the other aspects. Because of function 5 attributes, parameter storage classes and user-defined attributes, this 6 requires building a string mixin. In addition, the mixed-in code must refer 7 only to local names, if it is to work across module boundaires. 8 9 This module facilitates the creation of such mixins. 10 11 Copyright: Copyright Jean-Louis Leroy 2017-2020 12 13 License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0). 14 15 Authors: Jean-Louis Leroy 16 +/ 17 18 module bolts.experimental.refraction; 19 20 import std.array; 21 import std.format; 22 import std.meta; 23 import std.algorithm.iteration : map; 24 import std.range : iota; 25 import std.traits; 26 27 template ParameterAttribute(alias F, int i, int j) 28 { 29 static if (is(typeof(F) P == __parameters)) { 30 alias ParameterAttribute = Alias!(__traits(getAttributes, P[i..i+1])[j]); 31 } 32 } 33 34 /** 35 Return a Function object that captures all the aspects of Fun, using the 36 value of localSymbol to represent the return and parameter types, and the 37 UDAs. 38 39 Params: 40 fun = a function 41 localSymbol = a string mixin that represents fun in the caller's context 42 43 */ 44 45 immutable(Function) refract(alias fun, string localSymbol)() 46 if (is(typeof(fun) == function)) 47 { 48 Function model = { 49 name: __traits(identifier, fun), 50 localSymbol: localSymbol, 51 returnType: "std.traits.ReturnType!("~localSymbol~")", 52 parameters: refractParameterList!(fun, localSymbol), 53 udas: __traits(getAttributes, fun) 54 .length.iota.map!( 55 formatIndex!("@(__traits(getAttributes, %s)[%%d])".format(localSymbol))).array, 56 attributes: functionAttributes!(fun), 57 static_: __traits(isStaticFunction, fun) && isAggregate!(__traits(parent, fun)), 58 body_: ";", 59 }; 60 61 return model; 62 } 63 64 private enum isScope(alias T) = is(T == module) || is(T == struct) 65 || is(T == class) || is(T == interface); 66 67 immutable(Function) refract( 68 alias Scope, string localSymbol, string name, uint index)() 69 if (isScope!Scope) 70 { 71 return refract!( 72 __traits(getOverloads, Scope, name)[index], 73 `__traits(getOverloads, %s, "%s")[%d]`.format(localSymbol, name, index)) 74 .setIndex(index); 75 } 76 77 private enum True(T...) = true; 78 79 immutable(Function)[] refract(alias Scope, string localSymbol, alias Predicate = True)() 80 if (isScope!Scope) 81 { 82 Function[] functions; 83 84 static foreach (member; __traits(allMembers, Scope)) { 85 static foreach (index, fun; __traits(getOverloads, Scope, member)) { 86 static if (Predicate!fun) { 87 functions ~= refract!(Scope, localSymbol, member, index); 88 } 89 } 90 } 91 92 return functions; 93 } 94 95 /// 96 unittest 97 { 98 pure @nogc int answer(lazy string question); 99 alias F = answer; // typically F is a template argument 100 static assert( 101 refract!(F, "F").mixture == 102 "pure @nogc @system std.traits.ReturnType!(F) answer(lazy std.traits.Parameters!(F)[0] _0);"); 103 } 104 105 /// 106 unittest 107 { 108 import std.format; 109 import std.traits; 110 111 interface GrandTour 112 { 113 pure int foo() immutable; 114 @nogc @trusted nothrow ref int foo(out real, return ref int, lazy int) const; 115 @safe shared scope void bar(scope Object); 116 } 117 118 class Mock(Interface) : Interface 119 { 120 static foreach (member; __traits(allMembers, Interface)) { 121 static foreach (fun; __traits(getOverloads, Interface, member)) { 122 mixin({ 123 enum Model = refract!(fun, "fun"); 124 if (is(ReturnType!fun == void)) { 125 return Model.setBody("{}").mixture; 126 } else if (Model.attributes & FunctionAttribute.ref_) { 127 return Model.setBody(q{{ 128 static %s rv; 129 return rv; 130 }}.format(Model.returnType)).mixture; 131 } else { 132 return Model.setBody(q{{ 133 return %s.init; 134 }}.format(Model.returnType)).mixture; 135 } 136 }()); 137 } 138 } 139 } 140 141 GrandTour mock = new Mock!GrandTour; 142 real x; 143 int i, l; 144 mock.foo(x, i, l++) = 1; 145 assert(mock.foo(x, i, l++) == 1); 146 assert(l == 0); 147 } 148 149 private enum isAggregate(T...) = 150 is(T[0] == struct) || is(T[0] == union) || is(T[0] == class) 151 || is(T[0] == interface); 152 153 /** 154 A struct capturing all the properties of a function. 155 */ 156 157 private mixin template replaceAttribute(string Name) 158 { 159 alias Struct = typeof(this); 160 mixin( 161 "Struct copy = {", 162 { 163 string[] mixture; 164 foreach (member; __traits(allMembers, Struct)) { 165 if (__traits(getOverloads, Struct, member).length == 0) { 166 //pragma(msg, member); 167 mixture ~= member ~ ":" ~ (member == Name ? "value" : member); 168 } 169 } 170 return mixture.join(",\n"); 171 }(), 172 "};" 173 ); 174 } 175 176 177 immutable struct Function 178 { 179 180 /** 181 A string that evaluates to a function symbol. 182 */ 183 184 string localSymbol; 185 186 /** 187 Function name. Initial value: `__traits(identifier, fun)`. 188 */ 189 190 string name; 191 192 /** 193 Return a new Function object with the name attribute set to value. 194 */ 195 196 immutable(Function) setName(string value) 197 { 198 mixin replaceAttribute!"name"; 199 return copy; 200 } 201 202 /// 203 unittest 204 { 205 pure @nogc int answer(); 206 mixin(refract!(answer, "answer").setName("ultimateAnswer").mixture); 207 static assert( 208 __traits(getAttributes, ultimateAnswer) == 209 __traits(getAttributes, answer)); 210 } 211 212 /** 213 Index of function in a set of overloads. Valid only if Function represents 214 a function (not a function pointer), and was refracted from a module. 215 */ 216 217 uint index; 218 219 /** 220 Return a new Function object with the index attribute set to value. 221 */ 222 223 immutable(Function) setIndex(uint value) 224 { 225 mixin replaceAttribute!"index"; 226 return copy; 227 } 228 229 /** 230 Return a string representing the parameter. 231 */ 232 233 /** 234 Return type. Initial value: `std.traits.ReturnType!fun`. 235 */ 236 237 string returnType; 238 239 /** 240 Return a new Function object with the returnType attribute set to value. 241 */ 242 243 immutable(Function) setReturnType(string value) 244 { 245 mixin replaceAttribute!"returnType"; 246 return copy; 247 } 248 249 /// 250 unittest 251 { 252 pure int answer(); 253 mixin( 254 refract!(answer, "answer").setName("realAnswer") 255 .setReturnType("real") 256 .mixture); 257 static assert(is(typeof(realAnswer()) == real)); 258 } 259 260 /** 261 Function parameters. Initial value: from the refracted function. 262 */ 263 264 Parameter[] parameters; 265 266 /** 267 Return a new Function object with the parameters attribute set to value. 268 */ 269 270 immutable(Function) setParameters(immutable(Parameter)[] value) 271 { 272 mixin replaceAttribute!"parameters"; 273 return copy; 274 } 275 276 /// 277 unittest 278 { 279 int answer(); 280 mixin( 281 refract!(answer, "answer").setName("answerQuestion") 282 .setParameters([ Parameter().setName("question").setType("string")]) 283 .mixture); 284 int control(string); 285 static assert(is(Parameters!answerQuestion == Parameters!control)); 286 } 287 288 /** 289 Return a new Function object with a parameter attribute containing the 290 same parameters, with new parameters inserted parameters at the specified 291 index. 292 */ 293 294 Function insertParameters(uint index, immutable(Parameter)[] inserted...) 295 { 296 auto value = index == parameters.length ? parameters ~ inserted 297 : index == 0 ? inserted ~ parameters 298 : parameters[0..index] ~ inserted ~ parameters[index..$]; 299 mixin replaceAttribute!"parameters"; 300 return copy; 301 } 302 303 /** 304 Function body. Initial value: `;`. 305 */ 306 307 string body_; 308 309 /** 310 Return a new Function object with the body_ attribute set to value. 311 */ 312 313 immutable(Function) setBody(string value) 314 { 315 mixin replaceAttribute!"body_"; 316 return copy; 317 } 318 319 /// 320 unittest 321 { 322 pure int answer(); 323 mixin( 324 refract!(answer, "answer").setName("theAnswer") 325 .setBody("{ return 42; }") 326 .mixture); 327 static assert(theAnswer() == 42); 328 } 329 330 /** 331 Function attributes. 332 Initial value: `__traits(getAttributes, fun)`. 333 */ 334 335 ulong attributes; 336 337 /** 338 Set `attributes`. Return `this`. 339 */ 340 341 immutable(Function) setAttributes(uint value) 342 { 343 mixin replaceAttribute!"attributes"; 344 return copy; 345 } 346 347 /// 348 unittest 349 { 350 nothrow int answer(); 351 enum model = refract!(answer, "answer"); 352 with (FunctionAttribute) 353 { 354 mixin( 355 model 356 .setName("pureAnswer") 357 .setAttributes(model.attributes | pure_) 358 .mixture); 359 static assert(functionAttributes!pureAnswer & pure_); 360 static assert(functionAttributes!pureAnswer & nothrow_); 361 } 362 } 363 364 /** 365 If `true`, prefix generated function with `static`. Initial value: `true` 366 if the refracted function is a static *member* function. 367 */ 368 369 bool static_; 370 371 /** 372 Return a new Function object with the static_ attribute set to value. 373 */ 374 375 immutable(Function) setStatic(bool value) 376 { 377 mixin replaceAttribute!"static_"; 378 return copy; 379 } 380 381 /// 382 unittest 383 { 384 struct Question 385 { 386 static int answer() { return 42; } 387 } 388 mixin( 389 refract!(Question.answer, "Question.answer") 390 .setStatic(false) 391 .setBody("{ return Question.answer; }") 392 .mixture); 393 static assert(answer() == 42); 394 } 395 396 /** 397 User defined attributes. 398 Initial value: 399 `bolts.experimental.refraction.ParameterAttribute!(fun, parameterIndex..., attributeIndex...)`. 400 */ 401 402 string[] udas; 403 404 /** 405 Return a new Function object with the udas attribute set to value. 406 */ 407 408 immutable(Function) setUdas(immutable(string)[] value) 409 { 410 mixin replaceAttribute!"udas"; 411 return copy; 412 } 413 414 /// 415 unittest 416 { 417 import std.typecons : tuple; 418 @(666) int answer(); 419 420 mixin( 421 refract!(answer, "answer") 422 .setName("answerIs42") 423 .setUdas(["@(42)"]) 424 .mixture); 425 static assert(__traits(getAttributes, answerIs42).length == 1); 426 static assert(__traits(getAttributes, answerIs42)[0] == 42); 427 } 428 429 /** 430 Return a string representing the entire function definition or declaration. 431 */ 432 433 string mixture() 434 { 435 return join( 436 udas ~ 437 attributeMixtureArray() ~ 438 [ 439 returnType, 440 name ~ "(" ~ parameterListMixtureArray.join(", ") ~ ")", 441 ], " ") ~ 442 body_; 443 } 444 445 string[] parameterListMixtureArray() 446 { 447 return map!(p => p.mixture)(parameters).array; 448 } 449 450 /** 451 Return the argument list as an array of strings. 452 */ 453 454 const(string)[] argumentMixtureArray() 455 { 456 return parameters.map!(p => p.name).array; 457 } 458 459 /// 460 unittest 461 { 462 int add(int a, int b); 463 static assert(refract!(add, "add").argumentMixtureArray == [ "_0", "_1" ]); 464 } 465 466 /** 467 Return the argument list as a string. 468 */ 469 470 string argumentMixture() 471 { 472 return argumentMixtureArray.join(", "); 473 } 474 475 /// 476 unittest 477 { 478 int add(int a, int b); 479 static assert(refract!(add, "add").argumentMixture == "_0, _1"); 480 } 481 482 /** 483 Return the attribute list as an array of strings. 484 */ 485 486 string[] attributeMixtureArray() 487 { 488 with (FunctionAttribute) 489 { 490 return [] 491 ~ (static_ ? ["static"] : []) 492 ~ (attributes & pure_ ? ["pure"] : []) 493 ~ (attributes & nothrow_ ? ["nothrow"] : []) 494 ~ (attributes & property ? ["@property"] : []) 495 ~ (attributes & trusted ? ["@trusted"] : []) 496 ~ (attributes & safe ? ["@safe"] : []) 497 ~ (attributes & nogc ? ["@nogc"] : []) 498 ~ (attributes & system ? ["@system"] : []) 499 ~ (attributes & const_ ? ["const"] : []) 500 ~ (attributes & immutable_ ? ["immutable"] : []) 501 ~ (attributes & inout_ ? ["inout"] : []) 502 ~ (attributes & shared_ ? ["shared"] : []) 503 ~ (attributes & return_ ? ["return"] : []) 504 ~ (attributes & scope_ ? ["scope"] : []) 505 ~ (attributes & ref_ ? ["ref"] : []) 506 ; 507 } 508 } 509 510 /// 511 unittest 512 { 513 nothrow pure int answer(); 514 enum model = refract!(answer, "answer"); 515 static assert( 516 model.attributeMixtureArray == ["pure", "nothrow", "@system"]); 517 } 518 519 /** 520 Return the attribute list as a string. 521 */ 522 523 string attributeMixture() 524 { 525 return attributeMixtureArray.join(" "); 526 } 527 528 /// 529 unittest 530 { 531 nothrow pure int answer(); 532 enum model = refract!(answer, "answer"); 533 static assert(model.attributeMixture == "pure nothrow @system"); 534 } 535 } 536 537 /** 538 A struct capturing all the properties of a function parameter. 539 */ 540 541 immutable struct Parameter 542 { 543 /** 544 Parameter name. Initial value: `_i`, where `i` is the position of the 545 parameter. 546 */ 547 548 string name; 549 550 /** 551 Set the parameter name. 552 */ 553 554 immutable(Parameter) setName(string value) 555 { 556 mixin replaceAttribute!"name"; 557 return copy; 558 } 559 560 /** 561 Parameter type. Initial value: `std.traits.Parameter!fun[i]`, where `fun` 562 is the refracted function and `i` is the position of the parameter. 563 */ 564 565 string type; 566 567 /** 568 Return a new Parameter object with the type attribute set to value. 569 */ 570 571 immutable(Parameter) setType(string value) 572 { 573 mixin replaceAttribute!"type"; 574 return copy; 575 } 576 577 /** 578 Parameter storage classes. Initial value: 579 `[__traits(getParameterStorageClasses, fun, i)]`, where where `fun` is the 580 refracted function and `i` is the position of the parameter. 581 */ 582 583 string[] storage; 584 585 /** 586 Return a new Parameter object with the storage attribute set to value. 587 */ 588 589 immutable(Parameter) setStorage(immutable(string)[] value) 590 { 591 mixin replaceAttribute!"storage"; 592 return copy; 593 } 594 595 /** 596 Parameter UDAs. Initial value: 597 `[@(bolts.experimental.refraction.ParameterAttribute!(fun,i, j...))]`, where 598 where `fun` is the refracted function, `i` is the position of the 599 parameter, and `j...` are the positions of the UDAs. 600 */ 601 602 string[] udas; 603 604 /** 605 Return a new Parameter object with the udas attribute set to value. 606 */ 607 608 immutable(Parameter) setUdas(immutable(string)[] value) 609 { 610 mixin replaceAttribute!"udas"; 611 return copy; 612 } 613 614 string mixture() 615 { 616 return join(udas ~ storage ~ [ type, name ], " "); 617 } 618 } 619 620 immutable(Parameter) refractParameter(alias Fun, string mixture, uint index)() 621 { 622 static if (is(typeof(Fun) parameters == __parameters)) { 623 alias parameter = parameters[index .. index + 1]; 624 static if (__traits(compiles, __traits(getAttributes, parameter))) { 625 enum udaFormat = "@(bolts.experimental.refraction.ParameterAttribute!(%s, %d, %%d))".format( 626 mixture, index); 627 enum udas = __traits(getAttributes, parameter).length.iota.map!( 628 formatIndex!udaFormat).array; 629 } else { 630 enum udas = []; 631 } 632 633 Parameter p = 634 { 635 type: `std.traits.Parameters!(%s)[%d]`.format(mixture, index), 636 name: "_%d".format(index), 637 storage: [__traits(getParameterStorageClasses, Fun, index)], 638 udas: udas, 639 }; 640 } 641 return p; 642 } 643 644 private immutable(Parameter)[] refractParameterList(alias Fun, string mixture)() 645 { 646 Parameter[] result; 647 static if (is(typeof(Fun) parameters == __parameters)) { 648 static foreach (i; 0 .. parameters.length) { 649 result ~= refractParameter!(Fun, mixture, i); 650 } 651 } 652 return result; 653 } 654 655 private string formatIndex(string f)(ulong i) 656 { 657 return format!f(i); 658 }