1 module autowrap.reflection;
2 
3 
4 import std.meta: allSatisfy;
5 import std.traits: isArray, Unqual;
6 import std.typecons: Flag, No;
7 
8 private alias I(alias T) = T;
9 private enum isString(alias T) = is(typeof(T) == string);
10 enum isModule(alias T) = is(Unqual!(typeof(T)) == Module);
11 
12 /**
13    A module to automatically wrap.
14    Usually not needed since a string will do, but is useful when trying to export
15    all functions from a module by using Module("mymodule", Yes.alwaysExport)
16    instead of "mymodule"
17  */
18 struct Module {
19     import std.typecons: Flag, No;
20 
21     string name;
22     Flag!"alwaysExport" alwaysExport = No.alwaysExport;
23 
24     string toString() @safe pure const {
25         import std.conv: text;
26         import std.string: capitalize;
27         return text(`Module("`, name, `", `, text(alwaysExport).capitalize, `.alwaysExport)`);
28     }
29 }
30 
31 
32 template AllFunctions(Modules...) if(allSatisfy!(isString, Modules)) {
33     import std.meta: staticMap;
34     enum module_(string name) = Module(name);
35     alias AllFunctions = staticMap!(Functions, staticMap!(module_, Modules));
36 }
37 
38 template AllFunctions(Modules...) if(allSatisfy!(isModule, Modules)) {
39     import std.meta: staticMap;
40     alias AllFunctions = staticMap!(Functions, Modules);
41 }
42 
43 
44 template Functions(Module module_) {
45     mixin(`import dmodule = ` ~ module_.name ~ `;`);
46     alias Functions = Functions!(dmodule, module_.alwaysExport);
47 }
48 
49 
50 template Functions(alias module_, Flag!"alwaysExport" alwaysExport = No.alwaysExport)
51     if(!is(typeof(module_) == string))
52 {
53 
54     import std.meta: Filter, staticMap;
55 
56     template Function(string memberName) {
57         static if(__traits(compiles, I!(__traits(getMember, module_, memberName)))) {
58             alias member = I!(__traits(getMember, module_, memberName));
59 
60             static if(isExportFunction!(member, alwaysExport))
61                 alias Function = FunctionSymbol!(memberName, member);
62             else
63                 alias Function = void;
64         } else
65             alias Function = void;
66     }
67 
68     template notVoid(A...) if(A.length == 1) {
69         alias T = A[0];
70         enum notVoid = !is(T == void);
71     }
72 
73     alias Functions = Filter!(notVoid, staticMap!(Function, __traits(allMembers, module_)));
74 }
75 
76 template FunctionSymbol(string N, alias S) {
77     alias name = N;
78     alias symbol = S;
79 }
80 
81 template AllAggregates(ModuleNames...) if(allSatisfy!(isString, ModuleNames)) {
82     import std.meta: staticMap;
83 
84     enum module_(string name) = Module(name);
85     enum Modules = staticMap!(module_, ModuleNames);
86 
87     alias AllAggregates = AllAggregates!(staticMap!(module_, ModuleNames));
88 }
89 
90 template AllAggregates(Modules...) if(allSatisfy!(isModule, Modules)) {
91 
92     import std.meta: NoDuplicates, Filter;
93     import std.traits: isCopyable, Unqual;
94     import std.datetime: Date, DateTime;
95 
96     // definitions
97     alias aggregates = AggregateDefinitionsInModules!Modules;
98 
99     // return and parameter types
100     alias functionTypes = FunctionTypesInModules!Modules;
101 
102     alias copyables = Filter!(isCopyable, NoDuplicates!(aggregates, functionTypes));
103 
104     template notAlreadyWrapped(T) {
105         alias Type = Unqual!T;
106         enum notAlreadyWrapped = !is(Type == Date) && !is(Type == DateTime);
107     }
108 
109     alias AllAggregates = Filter!(notAlreadyWrapped, copyables);
110 }
111 
112 private template AggregateDefinitionsInModules(Modules...) if(allSatisfy!(isModule, Modules)) {
113     import std.meta: staticMap;
114     alias AggregateDefinitionsInModules = staticMap!(AggregateDefinitionsInModule, Modules);
115 }
116 
117 private template AggregateDefinitionsInModule(Module module_) {
118 
119     mixin(`import dmodule  = ` ~ module_.name ~ `;`);
120     import std.meta: Filter, staticMap, NoDuplicates, AliasSeq;
121 
122     alias Member(string memberName) = Symbol!(dmodule, memberName);
123     alias members = staticMap!(Member, __traits(allMembers, dmodule));
124     alias aggregates = Filter!(isUserAggregate, members);
125     alias recursives = staticMap!(RecursiveAggregates, aggregates);
126     alias all = AliasSeq!(aggregates, recursives);
127     alias AggregateDefinitionsInModule = NoDuplicates!all;
128 }
129 
130 
131 // All return and parameter types of the functions in the given modules
132 private template FunctionTypesInModules(Modules...) if(allSatisfy!(isModule, Modules)) {
133     import std.meta: staticMap;
134     alias FunctionTypesInModules = staticMap!(FunctionTypesInModule, Modules);
135 }
136 
137 
138 // All return and parameter types of the functions in the given module
139 private template FunctionTypesInModule(Module module_) {
140 
141     mixin(`import dmodule  = ` ~ module_.name ~ `;`);
142     import autowrap.reflection: isExportFunction;
143     import std.traits: ReturnType, Parameters;
144     import std.meta: Filter, staticMap, AliasSeq, NoDuplicates;
145 
146     alias Member(string memberName) = Symbol!(dmodule, memberName);
147     alias members = staticMap!(Member, __traits(allMembers, dmodule));
148     enum isWantedExportFunction(alias F) = isExportFunction!(F, module_.alwaysExport);
149     alias functions = Filter!(isWantedExportFunction, members);
150 
151     // all return types of all functions
152     alias returns = NoDuplicates!(Filter!(isUserAggregate, staticMap!(PrimordialType, staticMap!(ReturnType, functions))));
153     // recurse on the types in `returns` to also wrap the aggregate types of the members
154     alias recursiveReturns = NoDuplicates!(staticMap!(RecursiveAggregates, returns));
155     // all of the parameters types of all of the functions
156     alias params = NoDuplicates!(Filter!(isUserAggregate, staticMap!(PrimordialType, staticMap!(Parameters, functions))));
157     // recurse on the types in `params` to also wrap the aggregate types of the members
158     alias recursiveParams = NoDuplicates!(staticMap!(RecursiveAggregates, returns));
159     // chain all types
160     alias functionTypes = AliasSeq!(returns, recursiveReturns, params, recursiveParams);
161 
162     alias FunctionTypesInModule = NoDuplicates!(Filter!(isUserAggregate, functionTypes));
163 }
164 
165 
166 private template RecursiveAggregates(T) {
167     mixin RecursiveAggregateImpl!(T, RecursiveAggregateHelper);
168     alias RecursiveAggregates = RecursiveAggregateImpl;
169 }
170 
171 // Only exists because if RecursiveAggregate recurses using itself dmd complains.
172 // So instead, we ping-pong between identical templates.
173 private template RecursiveAggregateHelper(T) {
174     mixin RecursiveAggregateImpl!(T, RecursiveAggregates);
175     alias RecursiveAggregateHelper = RecursiveAggregateImpl;
176 }
177 
178 /**
179    Only exists because if RecursiveAggregate recurses using itself dmd complains.
180    Instead there's a canonical implementation and we ping-pong between two
181    templates that mix this in.
182  */
183 private mixin template RecursiveAggregateImpl(T, alias Other) {
184     import std.meta: staticMap, Filter, AliasSeq, NoDuplicates;
185     import std.traits: isInstanceOf, Unqual;
186     import std.typecons: Typedef, TypedefType;
187     import std.datetime: Date;
188 
189     static if(isInstanceOf!(Typedef, T)) {
190         alias RecursiveAggregateImpl = TypedefType!T;
191     } else static if (is(T == Date)) {
192         alias RecursiveAggregateImpl = Date;
193     } else static if(isUserAggregate!T) {
194         alias AggMember(string memberName) = Symbol!(T, memberName);
195         alias members = staticMap!(AggMember, __traits(allMembers, T));
196         enum isNotMe(U) = !is(Unqual!T == Unqual!U);
197 
198         alias types = staticMap!(Type, members);
199         alias primordials = staticMap!(PrimordialType, types);
200         alias userAggregates = Filter!(isUserAggregate, primordials);
201         alias aggregates = NoDuplicates!(Filter!(isNotMe, userAggregates));
202 
203         static if(aggregates.length == 0)
204             alias RecursiveAggregateImpl = T;
205         else
206             alias RecursiveAggregateImpl = AliasSeq!(aggregates, staticMap!(Other, aggregates));
207     } else
208         alias RecursiveAggregatesImpl = T;
209 }
210 
211 
212 private template Type(T...) if(T.length == 1) {
213     static if(is(T[0]))
214         alias Type = T[0];
215     else
216         alias Type = typeof(T[0]);
217 }
218 
219 // if a type is a struct or a class
220 package template isUserAggregate(A...) if(A.length == 1) {
221     import std.datetime;
222     import std.traits: Unqual, isInstanceOf;
223     import std.typecons: Tuple;
224     alias T = A[0];
225 
226     enum isUserAggregate =
227         !is(Unqual!T == DateTime) &&
228         !isInstanceOf!(Tuple, T) &&
229         (is(T == struct) || is(T == class));
230 }
231 
232 @("DateTime is not a user aggregate")
233 @safe pure unittest {
234     import std.datetime: DateTime;
235     static assert(!isUserAggregate!DateTime);
236 }
237 
238 @("Tuple is not a user aggregate")
239 @safe pure unittest {
240     import std.typecons: Tuple;
241     static assert(!isUserAggregate!(Tuple!(int, double)));
242 }
243 
244 // Given a parent (module, struct, ...) and a memberName, alias the actual member,
245 // or void if not possible
246 package template Symbol(alias parent, string memberName) {
247     static if(__traits(compiles, I!(__traits(getMember, parent, memberName))))
248         alias Symbol = I!(__traits(getMember, parent, memberName));
249     else
250         alias Symbol = void;
251 }
252 
253 
254 // T -> T, T[] -> T, T[][] -> T
255 private template PrimordialType(T) if(isArray!T) {
256     import std.range.primitives: ElementType;
257     static if(isArray!(ElementType!T))
258         alias PrimordialType = PrimordialType!(ElementType!T);
259     else
260         alias PrimordialType = ElementType!T;
261 }
262 
263 
264 // T -> T, T[] -> T, T[][] -> T
265 private template PrimordialType(T) if(!isArray!T) {
266     alias PrimordialType = T;
267 }
268 
269 
270 @("PrimordialType")
271 unittest {
272     static assert(is(PrimordialType!int == int));
273     static assert(is(PrimordialType!(int[]) == int));
274     static assert(is(PrimordialType!(int[][]) == int));
275     static assert(is(PrimordialType!(double[][]) == double));
276     static assert(is(PrimordialType!(string[][]) == dchar));
277 }
278 
279 
280 package template isExportFunction(alias F, Flag!"alwaysExport" alwaysExport = No.alwaysExport) {
281     import std.traits: isFunction;
282 
283     version(AutowrapAlwaysExport) {
284         enum linkage = __traits(getLinkage, F);
285         enum isExportFunction = isFunction!F && linkage != "C" && linkage != "C++";
286     } else
287           enum isExportFunction = isFunction!F && isExportSymbol!(F, alwaysExport);
288 }
289 
290 
291 private template isExportSymbol(alias S, Flag!"alwaysExport" alwaysExport = No.alwaysExport) {
292     import std.traits: isFunction;
293 
294     version(AutowrapAlwaysExport)
295         enum isExportSymbol = true;
296     else
297         enum isExportSymbol = (alwaysExport || __traits(getProtection, S) == "export");
298 
299 }