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 modules) {
55     import std.algorithm: map;
56     import std.array: join;
57     import std.typecons: Yes, No;  // needed for Module.toString in the mixin
58 
59     enum modulesList = modules.value.map!(a => a.toString).join(", ");
60     mixin(`alias AllFunctions = AllFunctions!(`, modulesList, `);`);
61 }
62 
63 
64 template AllFunctions(Modules...) if(allSatisfy!(isString, Modules)) {
65     import std.meta: staticMap;
66     enum module_(string name) = Module(name);
67     alias AllFunctions = staticMap!(Functions, staticMap!(module_, Modules));
68 }
69 
70 template AllFunctions(Modules...) if(allSatisfy!(isModule, Modules)) {
71     import std.meta: staticMap;
72     alias AllFunctions = staticMap!(Functions, Modules);
73 }
74 
75 
76 template Functions(Module module_) {
77     mixin(`import dmodule = ` ~ module_.name ~ `;`);
78     alias Functions = Functions!(dmodule, module_.alwaysExport);
79 }
80 
81 
82 template Functions(alias module_, Flag!"alwaysExport" alwaysExport = No.alwaysExport)
83     if(!is(typeof(module_) == string))
84 {
85 
86     import std.meta: Filter, staticMap;
87 
88     template Function(string memberName) {
89         static if(__traits(compiles, I!(__traits(getMember, module_, memberName)))) {
90             alias member = I!(__traits(getMember, module_, memberName));
91 
92             static if(isExportFunction!(member, alwaysExport)) {
93                 alias Function = FunctionSymbol!(memberName, module_, moduleName!member, member);
94             } else {
95                 alias Function = void;
96             }
97         } else {
98             alias Function = void;
99         }
100     }
101 
102     template notVoid(A...) if(A.length == 1) {
103         alias T = A[0];
104         enum notVoid = !is(T == void);
105     }
106 
107     alias Functions = Filter!(notVoid, staticMap!(Function, __traits(allMembers, module_)));
108 }
109 
110 template FunctionSymbol(string N, alias M, string MN, alias S) {
111     alias name = N;
112     alias module_ = M;
113     alias moduleName = MN;
114     alias symbol = S;
115 }
116 
117 template AllAggregates(Modules modules) {
118     import std.algorithm: map;
119     import std.array: join;
120     import std.typecons: Yes, No;  // needed for Module.toString in the mixin
121 
122     enum modulesList = modules.value.map!(a => a.toString).join(", ");
123     mixin(`alias AllAggregates = AllAggregates!(`, modulesList, `);`);
124 }
125 
126 template AllAggregates(ModuleNames...) if(allSatisfy!(isString, ModuleNames)) {
127     import std.meta: staticMap;
128 
129     enum module_(string name) = Module(name);
130     enum Modules = staticMap!(module_, ModuleNames);
131 
132     alias AllAggregates = AllAggregates!(staticMap!(module_, ModuleNames));
133 }
134 
135 template AllAggregates(Modules...) if(allSatisfy!(isModule, Modules)) {
136 
137     import std.meta: NoDuplicates, Filter;
138     import std.traits: isCopyable, Unqual;
139     import std.datetime: Date, DateTime;
140 
141     // definitions
142     alias aggregates = AggregateDefinitionsInModules!Modules;
143 
144     // return and parameter types
145     alias functionTypes = FunctionTypesInModules!Modules;
146 
147     alias copyables = Filter!(isCopyable, NoDuplicates!(aggregates, functionTypes));
148 
149     template notAlreadyWrapped(T) {
150         alias Type = Unqual!T;
151         enum notAlreadyWrapped = !is(Type == Date) && !is(Type == DateTime);
152     }
153 
154     alias notWrapped = Filter!(notAlreadyWrapped, copyables);
155     alias public_ = Filter!(isPublicSymbol, notWrapped);
156 
157     alias AllAggregates = public_;
158 }
159 
160 private template AggregateDefinitionsInModules(Modules...) if(allSatisfy!(isModule, Modules)) {
161     import std.meta: staticMap;
162     alias AggregateDefinitionsInModules = staticMap!(AggregateDefinitionsInModule, Modules);
163 }
164 
165 private template AggregateDefinitionsInModule(Module module_) {
166 
167     mixin(`import dmodule  = ` ~ module_.name ~ `;`);
168     import std.meta: Filter, staticMap, NoDuplicates, AliasSeq;
169 
170     alias Member(string memberName) = Symbol!(dmodule, memberName);
171     alias members = staticMap!(Member, __traits(allMembers, dmodule));
172     alias aggregates = Filter!(isUserAggregate, members);
173     alias recursives = staticMap!(RecursiveAggregates, aggregates);
174     alias all = AliasSeq!(aggregates, recursives);
175     alias AggregateDefinitionsInModule = NoDuplicates!all;
176 }
177 
178 
179 // All return and parameter types of the functions in the given modules
180 private template FunctionTypesInModules(Modules...) if(allSatisfy!(isModule, Modules)) {
181     import std.meta: staticMap;
182     alias FunctionTypesInModules = staticMap!(FunctionTypesInModule, Modules);
183 }
184 
185 
186 // All return and parameter types of the functions in the given module
187 private template FunctionTypesInModule(Module module_) {
188 
189     mixin(`import dmodule  = ` ~ module_.name ~ `;`);
190     import autowrap.reflection: isExportFunction;
191     import std.traits: ReturnType, Parameters;
192     import std.meta: Filter, staticMap, AliasSeq, NoDuplicates;
193 
194     alias Member(string memberName) = Symbol!(dmodule, memberName);
195     alias members = staticMap!(Member, __traits(allMembers, dmodule));
196     template isWantedExportFunction(T...) if(T.length == 1) {
197         import std.traits: isSomeFunction;
198         alias F = T[0];
199         static if(isSomeFunction!F)
200             enum isWantedExportFunction = isExportFunction!(F, module_.alwaysExport);
201         else
202             enum isWantedExportFunction = false;
203     }
204     alias functions = Filter!(isWantedExportFunction, members);
205 
206     // all return types of all functions
207     alias returns = NoDuplicates!(Filter!(isUserAggregate, staticMap!(PrimordialType, staticMap!(ReturnType, functions))));
208     // recurse on the types in `returns` to also wrap the aggregate types of the members
209     alias recursiveReturns = NoDuplicates!(staticMap!(RecursiveAggregates, returns));
210     // all of the parameters types of all of the functions
211     alias params = NoDuplicates!(Filter!(isUserAggregate, staticMap!(PrimordialType, staticMap!(Parameters, functions))));
212     // recurse on the types in `params` to also wrap the aggregate types of the members
213     alias recursiveParams = NoDuplicates!(staticMap!(RecursiveAggregates, returns));
214     // chain all types
215     alias functionTypes = AliasSeq!(returns, recursiveReturns, params, recursiveParams);
216 
217     alias FunctionTypesInModule = NoDuplicates!(Filter!(isUserAggregate, functionTypes));
218 }
219 
220 
221 private template RecursiveAggregates(T) {
222     mixin RecursiveAggregateImpl!(T, RecursiveAggregateHelper);
223     alias RecursiveAggregates = RecursiveAggregateImpl;
224 }
225 
226 // Only exists because if RecursiveAggregate recurses using itself dmd complains.
227 // So instead, we ping-pong between identical templates.
228 private template RecursiveAggregateHelper(T) {
229     mixin RecursiveAggregateImpl!(T, RecursiveAggregates);
230     alias RecursiveAggregateHelper = RecursiveAggregateImpl;
231 }
232 
233 /**
234    Only exists because if RecursiveAggregate recurses using itself dmd complains.
235    Instead there's a canonical implementation and we ping-pong between two
236    templates that mix this in.
237  */
238 private mixin template RecursiveAggregateImpl(T, alias Other) {
239     import std.meta: staticMap, Filter, AliasSeq, NoDuplicates;
240     import std.traits: isInstanceOf, Unqual;
241     import std.typecons: Typedef, TypedefType;
242     import std.datetime: Date;
243 
244     static if(isInstanceOf!(Typedef, T)) {
245         alias RecursiveAggregateImpl = TypedefType!T;
246     } else static if (is(T == Date)) {
247         alias RecursiveAggregateImpl = Date;
248     } else static if(isUserAggregate!T) {
249         alias AggMember(string memberName) = Symbol!(T, memberName);
250         alias members = staticMap!(AggMember, __traits(allMembers, T));
251         enum isNotMe(U) = !is(Unqual!T == Unqual!U);
252 
253         alias types = staticMap!(Type, members);
254         alias primordials = staticMap!(PrimordialType, types);
255         alias userAggregates = Filter!(isUserAggregate, primordials);
256         alias aggregates = NoDuplicates!(Filter!(isNotMe, userAggregates));
257 
258         static if(aggregates.length == 0)
259             alias RecursiveAggregateImpl = T;
260         else
261             alias RecursiveAggregateImpl = AliasSeq!(aggregates, staticMap!(Other, aggregates));
262     } else
263         alias RecursiveAggregateImpl = T;
264 }
265 
266 
267 // must be a global template for staticMap
268 private template Type(T...) if(T.length == 1) {
269     import std.traits: isSomeFunction;
270     import std.meta: AliasSeq;
271 
272     static if(isSomeFunction!(T[0]))
273         alias Type = AliasSeq!();
274     else static if(is(T[0]))
275         alias Type = T[0];
276     else
277         alias Type = typeof(T[0]);
278 }
279 
280 // if a type is a struct or a class
281 package template isUserAggregate(A...) if(A.length == 1) {
282     import std.datetime;
283     import std.traits: Unqual, isInstanceOf;
284     import std.typecons: Tuple;
285     alias T = A[0];
286 
287     enum isUserAggregate =
288         !is(Unqual!T == DateTime) &&
289         !isInstanceOf!(Tuple, T) &&
290         (is(T == struct) || is(T == class));
291 }
292 
293 
294 version(TestingAutowrap) {
295     import std.datetime: DateTime;
296     static assert(!isUserAggregate!DateTime);
297 }
298 
299 version(TestingAutowrap) {
300     import std.typecons: Tuple;
301     static assert(!isUserAggregate!(Tuple!(int, double)));
302 }
303 
304 // Given a parent (module, struct, ...) and a memberName, alias the actual member,
305 // or void if not possible
306 package template Symbol(alias parent, string memberName) {
307     static if(__traits(compiles, I!(__traits(getMember, parent, memberName))))
308         alias Symbol = I!(__traits(getMember, parent, memberName));
309     else
310         alias Symbol = void;
311 }
312 
313 
314 // T -> T, T[] -> T, T[][] -> T, T* -> T
315 template PrimordialType(T) if(isArray!T) {
316 
317     import std.range.primitives: ElementEncodingType;
318     import std.traits: Unqual;
319 
320     static if(isArray!(ElementEncodingType!T))
321         alias PrimordialType = PrimordialType!(ElementEncodingType!T);
322     else
323         alias PrimordialType = Unqual!(ElementEncodingType!T);
324 }
325 
326 
327 // T -> T, T[] -> T, T[][] -> T, T* -> T
328 template PrimordialType(T) if(!isArray!T) {
329 
330     import std.traits: isPointer, PointerTarget, Unqual;
331 
332     static if(isPointer!T) {
333         static if(isPointer!(PointerTarget!T))
334             alias PrimordialType = PrimordialType!(PointerTarget!T);
335         else
336             alias PrimordialType = Unqual!(PointerTarget!T);
337     } else
338         alias PrimordialType = Unqual!T;
339 }
340 
341 
342 version(TestingAutowrap) {
343     static assert(is(PrimordialType!int == int));
344     static assert(is(PrimordialType!(int[]) == int));
345     static assert(is(PrimordialType!(int[][]) == int));
346     static assert(is(PrimordialType!(double[][]) == double));
347     static assert(is(PrimordialType!(string[][]) == char));
348     static assert(is(PrimordialType!(int*) == int));
349     static assert(is(PrimordialType!(int**) == int));
350 }
351 
352 
353 package template isExportFunction(alias F, Flag!"alwaysExport" alwaysExport = No.alwaysExport) {
354     import std.traits: isFunction;
355 
356     version(AutowrapAlwaysExport) {
357         static if(isFunction!F) {
358             enum linkage = __traits(getLinkage, F);
359             enum isExportFunction = linkage != "C" && linkage != "C++";
360         } else
361             enum isExportFunction = false;
362     } else version(AutowrapAlwaysExportC) {
363         static if(isFunction!F) {
364             enum linkage = __traits(getLinkage, F);
365             enum isExportFunction = linkage == "C" || linkage == "C++";
366         } else
367             enum isExportFunction = false;
368 
369     } else {
370         enum isExportFunction = isFunction!F && isExportSymbol!(F, alwaysExport);
371     }
372 }
373 
374 
375 private template isExportSymbol(alias S, Flag!"alwaysExport" alwaysExport = No.alwaysExport) {
376     version(AutowrapAlwaysExport)
377         enum isExportSymbol = isPublicSymbol!S;
378     else
379         enum isExportSymbol = isPublicSymbol!S && (alwaysExport || __traits(getProtection, S) == "export");
380 }
381 
382 private template isPublicSymbol(alias S) {
383     enum isPublicSymbol = __traits(getProtection, S) == "export" || __traits(getProtection, S) == "public";
384 }
385 
386 
387 template PublicFieldNames(T) {
388     import std.meta: Filter, AliasSeq;
389     import std.traits: FieldNameTuple;
390 
391     enum isPublic(string fieldName) = __traits(getProtection, __traits(getMember, T, fieldName)) == "public";
392     alias publicFields = Filter!(isPublic, FieldNameTuple!T);
393 
394     // FIXME - See #54
395     static if(is(T == class))
396         alias PublicFieldNames = AliasSeq!();
397     else
398         alias PublicFieldNames = publicFields;
399 }
400 
401 
402 template PublicFieldTypes(T) {
403     import std.meta: staticMap;
404 
405     alias fieldType(string name) = typeof(__traits(getMember, T, name));
406 
407     alias PublicFieldTypes = staticMap!(fieldType, PublicFieldNames!T);
408 }
409 
410 
411 template Properties(functions...) {
412     import std.meta: Filter;
413     alias Properties = Filter!(isProperty, functions);
414 }
415 
416 
417 template isProperty(alias F) {
418     import std.traits: functionAttributes, FunctionAttribute;
419     enum isProperty = functionAttributes!F & FunctionAttribute.property;
420 }
421 
422 
423 template isStatic(alias F) {
424     import std.traits: hasStaticMember;
425     enum isStatic = hasStaticMember!(__traits(parent, F), __traits(identifier, F));
426 }
427 
428 @safe pure unittest {
429     static struct Struct {
430         int foo();
431         static int bar();
432     }
433 
434     static assert(!isStatic!(Struct.foo));
435     static assert( isStatic!(Struct.bar));
436 }
437 
438 
439 // From a function symbol to an AliasSeq of `Parameter`
440 template FunctionParameters(alias F) {
441     import std.traits: Parameters, ParameterIdentifierTuple, ParameterDefaults;
442     import std.meta: staticMap, aliasSeqOf;
443     import std.range: iota;
444 
445     alias parameter(size_t i) = Parameter!(
446         Parameters!F[i],
447         ParameterIdentifierTuple!F[i],
448         ParameterDefaults!F[i]
449     );
450 
451     alias FunctionParameters = staticMap!(parameter, aliasSeqOf!(Parameters!F.length.iota));
452 }
453 
454 
455 template Parameter(T, string id, D...) if(D.length == 1) {
456     alias Type = T;
457     enum identifier = id;
458 
459     static if(is(D[0] == void))
460         alias Default = void;
461     else
462         enum Default = D[0];
463 }
464 
465 template isParameter(alias T) {
466     import std.traits: TemplateOf;
467     enum isParameter = __traits(isSame, TemplateOf!T, Parameter);
468 }