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 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 /// 65 unittest 66 { 67 pure @nogc int answer(lazy string question); 68 alias F = answer; // typically F is a template argument 69 static assert( 70 refract!(F, "F").mixture == 71 "pure @nogc @system std.traits.ReturnType!(F) answer(lazy std.traits.Parameters!(F)[0] _0);"); 72 } 73 74 /// 75 unittest 76 { 77 import std.format; 78 import std.traits; 79 80 interface GrandTour 81 { 82 pure int foo() immutable; 83 @nogc @trusted nothrow ref int foo(out real, return ref int, lazy int) const; 84 @safe shared scope void bar(scope Object); 85 } 86 87 class Mock(Interface) : Interface 88 { 89 static foreach (member; __traits(allMembers, Interface)) { 90 static foreach (fun; __traits(getOverloads, Interface, member)) { 91 mixin({ 92 enum Model = refract!(fun, "fun"); 93 if (is(ReturnType!fun == void)) { 94 return Model.setBody("{}").mixture; 95 } else if (Model.attributes & FunctionAttribute.ref_) { 96 return Model.setBody(q{{ 97 static %s rv; 98 return rv; 99 }}.format(Model.returnType)).mixture; 100 } else { 101 return Model.setBody(q{{ 102 return %s.init; 103 }}.format(Model.returnType)).mixture; 104 } 105 }()); 106 } 107 } 108 } 109 110 GrandTour mock = new Mock!GrandTour; 111 real x; 112 int i, l; 113 mock.foo(x, i, l++) = 1; 114 assert(mock.foo(x, i, l++) == 1); 115 assert(l == 0); 116 } 117 118 private enum isAggregate(T...) = 119 is(T[0] == struct) || is(T[0] == union) || is(T[0] == class) 120 || is(T[0] == interface); 121 122 /** 123 A struct capturing all the properties of a function. 124 */ 125 126 struct Function 127 { 128 129 /** 130 A string that evaluates to a function symbol. 131 */ 132 133 string localSymbol; 134 135 /** 136 Function name. Initial value: `__traits(identifier, fun)`. 137 */ 138 139 string name; 140 141 /** 142 Set the function name. 143 */ 144 145 Function setName(string value) 146 { 147 name = value; 148 return this; 149 } 150 151 /// 152 unittest 153 { 154 pure @nogc int answer(); 155 mixin(refract!(answer, "answer").setName("ultimateAnswer").mixture); 156 static assert( 157 __traits(getAttributes, ultimateAnswer) == 158 __traits(getAttributes, answer)); 159 } 160 161 /** 162 Return type. Initial value: `std.traits.ReturnType!fun`. 163 */ 164 165 string returnType; 166 167 /** 168 Set the return type. 169 */ 170 171 Function setReturnType(string value) 172 { 173 returnType = value; 174 return this; 175 } 176 177 /// 178 unittest 179 { 180 pure int answer(); 181 mixin( 182 refract!(answer, "answer").setName("realAnswer") 183 .setReturnType("real") 184 .mixture); 185 static assert(is(typeof(realAnswer()) == real)); 186 } 187 188 /** 189 Function parameters. Initial value: from the refracted function. 190 */ 191 192 Parameter[] parameters; 193 194 /** 195 Set the parameter list. 196 */ 197 198 Function setParameters(Parameter[] value) 199 { 200 parameters = value; 201 return this; 202 } 203 204 /// 205 unittest 206 { 207 int answer(); 208 mixin( 209 refract!(answer, "answer").setName("answerQuestion") 210 .setParameters([ Parameter().setName("question").setType("string")]) 211 .mixture); 212 int control(string); 213 static assert(is(Parameters!answerQuestion == Parameters!control)); 214 } 215 216 /** 217 Function body. Initial value: `;`. 218 */ 219 220 string body_; 221 222 /** 223 Set the function body. 224 */ 225 226 Function setBody(string value) 227 { 228 body_ = value; 229 return this; 230 } 231 232 /// 233 unittest 234 { 235 pure int answer(); 236 mixin( 237 refract!(answer, "answer").setName("theAnswer") 238 .setBody("{ return 42; }") 239 .mixture); 240 static assert(theAnswer() == 42); 241 } 242 243 /** 244 Function attributes. 245 Initial value: `__traits(getAttributes, fun)`. 246 */ 247 248 ulong attributes; 249 250 /** 251 Set `attributes`. Return `this`. 252 */ 253 254 Function setAttributes(uint value) 255 { 256 attributes = value; 257 return this; 258 } 259 260 /// 261 unittest 262 { 263 nothrow int answer(); 264 enum model = refract!(answer, "answer"); 265 with (FunctionAttribute) 266 { 267 mixin( 268 model 269 .setName("pureAnswer") 270 .setAttributes(model.attributes | pure_) 271 .mixture); 272 static assert(functionAttributes!pureAnswer & pure_); 273 static assert(functionAttributes!pureAnswer & nothrow_); 274 } 275 } 276 277 /** 278 If `true`, prefix generated function with `static`. Initial value: `true` 279 if the refracted function is a static *member* function. 280 */ 281 282 bool static_; 283 284 /** 285 Set the `static_` attribute. Return `this`. 286 */ 287 288 Function setStatic(bool value) 289 { 290 static_ = value; 291 return this; 292 } 293 294 /// 295 unittest 296 { 297 struct Question 298 { 299 static int answer() { return 42; } 300 } 301 mixin( 302 refract!(Question.answer, "Question.answer") 303 .setStatic(false) 304 .setBody("{ return Question.answer; }") 305 .mixture); 306 static assert(answer() == 42); 307 } 308 309 /** 310 User defined attributes. 311 Initial value: 312 `bolts.experimental.refraction.ParameterAttribute!(fun, parameterIndex..., attributeIndex...)`. 313 */ 314 315 string[] udas; 316 317 /** 318 Set the `udas` attribute. Return `this`. 319 */ 320 321 Function setUdas(string[] value) 322 { 323 udas = value; 324 return this; 325 } 326 327 /// 328 unittest 329 { 330 import std.typecons : tuple; 331 @(666) int answer(); 332 333 mixin( 334 refract!(answer, "answer") 335 .setName("answerIs42") 336 .setUdas(["@(42)"]) 337 .mixture); 338 static assert(__traits(getAttributes, answerIs42).length == 1); 339 static assert(__traits(getAttributes, answerIs42)[0] == 42); 340 } 341 342 /** 343 Return a string representing the entire function definition or declaration. 344 */ 345 346 string mixture() 347 { 348 return join( 349 udas ~ 350 attributeMixtureArray() ~ 351 [ 352 returnType, 353 name ~ "(" ~ parameterListMixtureArray.join(", ") ~ ")", 354 ], " ") ~ 355 body_; 356 } 357 358 const string[] parameterListMixtureArray() 359 { 360 return map!(p => p.mixture)(parameters).array; 361 } 362 363 /** 364 Return the argument list as an array of strings. 365 */ 366 367 const const(string)[] argumentMixtureArray() 368 { 369 return parameters.map!(p => p.name).array; 370 } 371 372 /// 373 unittest 374 { 375 int add(int a, int b); 376 static assert(refract!(add, "add").argumentMixtureArray == [ "_0", "_1" ]); 377 } 378 379 /** 380 Return the argument list as a string. 381 */ 382 383 const string argumentMixture() 384 { 385 return argumentMixtureArray.join(", "); 386 } 387 388 /// 389 unittest 390 { 391 int add(int a, int b); 392 static assert(refract!(add, "add").argumentMixture == "_0, _1"); 393 } 394 395 /** 396 Return the attribute list as an array of strings. 397 */ 398 399 const string[] attributeMixtureArray() 400 { 401 with (FunctionAttribute) 402 { 403 return [] 404 ~ (static_ ? ["static"] : []) 405 ~ (attributes & pure_ ? ["pure"] : []) 406 ~ (attributes & nothrow_ ? ["nothrow"] : []) 407 ~ (attributes & property ? ["@property"] : []) 408 ~ (attributes & trusted ? ["@trusted"] : []) 409 ~ (attributes & safe ? ["@safe"] : []) 410 ~ (attributes & nogc ? ["@nogc"] : []) 411 ~ (attributes & system ? ["@system"] : []) 412 ~ (attributes & const_ ? ["const"] : []) 413 ~ (attributes & immutable_ ? ["immutable"] : []) 414 ~ (attributes & inout_ ? ["inout"] : []) 415 ~ (attributes & shared_ ? ["shared"] : []) 416 ~ (attributes & return_ ? ["return"] : []) 417 ~ (attributes & scope_ ? ["scope"] : []) 418 ~ (attributes & ref_ ? ["ref"] : []) 419 ; 420 } 421 } 422 423 /// 424 unittest 425 { 426 nothrow pure int answer(); 427 enum model = refract!(answer, "answer"); 428 static assert( 429 model.attributeMixtureArray == ["pure", "nothrow", "@system"]); 430 } 431 432 /** 433 Return the attribute list as a string. 434 */ 435 436 const string attributeMixture() 437 { 438 return attributeMixtureArray.join(" "); 439 } 440 441 /// 442 unittest 443 { 444 nothrow pure int answer(); 445 enum model = refract!(answer, "answer"); 446 static assert(model.attributeMixture == "pure nothrow @system"); 447 } 448 } 449 450 /** 451 A struct capturing all the properties of a function parameter. 452 */ 453 454 struct Parameter 455 { 456 /** 457 Parameter name. Initial value: `_i`, where `i` is the position of the 458 parameter. 459 */ 460 461 string name; 462 463 /** 464 Set the parameter name. 465 */ 466 467 Parameter setName(string value) 468 { 469 name = value; 470 return this; 471 } 472 473 /** 474 Parameter type. Initial value: `std.traits.Parameter!fun[i]`, where `fun` 475 is the refracted function and `i` is the position of the parameter. 476 */ 477 478 string type; 479 480 /** 481 Set the parameter type. 482 */ 483 484 Parameter setType(string value) 485 { 486 type = value; 487 return this; 488 } 489 490 /** 491 Parameter storage classes. Initial value: 492 `[__traits(getParameterStorageClasses, fun, i)]`, where where `fun` is the 493 refracted function and `i` is the position of the parameter. 494 */ 495 496 string[] storage; 497 498 /** 499 Set the parameter storage classes. 500 */ 501 502 Parameter setStorage(string[] value) 503 { 504 storage = value; 505 return this; 506 } 507 508 /** 509 Parameter UDAs. Initial value: 510 `[@(bolts.experimental.refraction.ParameterAttribute!(fun,i, j...))]`, where 511 where `fun` is the refracted function, `i` is the position of the 512 parameter, and `j...` are the positions of the UDAs. 513 */ 514 515 string[] udas; 516 517 /** 518 Set the parameter udas. 519 */ 520 521 Parameter setUdas(string[] value) 522 { 523 udas = value; 524 return this; 525 } 526 527 /** 528 Return a string representing the parameter. 529 */ 530 531 const string mixture() 532 { 533 return join(udas ~ storage ~ [ type, name ], " "); 534 } 535 } 536 537 Parameter refractParameter(alias Fun, string mixture, uint index)() 538 { 539 static if (is(typeof(Fun) parameters == __parameters)) { 540 alias parameter = parameters[index .. index + 1]; 541 static if (__traits(compiles, __traits(getAttributes, parameter))) { 542 enum udaFormat = "@(bolts.experimental.refraction.ParameterAttribute!(%s, %d, %%d))".format( 543 mixture, index); 544 enum udas = __traits(getAttributes, parameter).length.iota.map!( 545 formatIndex!udaFormat).array; 546 } else { 547 enum udas = []; 548 } 549 550 Parameter p = 551 { 552 name: "_%d".format(index), 553 type: `std.traits.Parameters!(%s)[%d]`.format(mixture, index), 554 storage: [__traits(getParameterStorageClasses, Fun, index)], 555 udas: udas, 556 }; 557 } 558 return p; 559 } 560 561 private Parameter[] refractParameterList(alias Fun, string mixture)() 562 { 563 Parameter[] result; 564 static if (is(typeof(Fun) parameters == __parameters)) { 565 static foreach (i; 0 .. parameters.length) { 566 result ~= refractParameter!(Fun, mixture, i); 567 } 568 } 569 return result; 570 } 571 572 private string formatIndex(string f)(ulong i) 573 { 574 return format!f(i); 575 }