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 }