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