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 }