1 /**
2    Functions to wrap entities in D modules for Python consumption.
3 
4    These functions are usually not called directly, but from the mixin generated by
5    autowrap.python.boilerplate.pydBoilerplate.
6  */
7 module autowrap.python.wrap;
8 
9 import autowrap.types: isModule;
10 import autowrap.reflection: isUserAggregate;
11 import std.meta: allSatisfy;
12 import std.traits: isArray;
13 
14 
15 private alias I(alias T) = T;
16 private enum isString(alias T) = is(typeof(T) == string);
17 
18 ///  Wrap global functions from multiple modules
19 void wrapAllFunctions(Modules...)() if(allSatisfy!(isModule, Modules)) {
20     import autowrap.common: toSnakeCase, AlwaysTry;
21     import autowrap.reflection: AllFunctions;
22     import pyd.pyd: def, PyName;
23 
24     static foreach(function_; AllFunctions!Modules) {
25         static if(AlwaysTry || __traits(compiles, def!(function_.symbol, PyName!(toSnakeCase(function_.identifier)))()))
26             def!(function_.symbol, PyName!(toSnakeCase(function_.identifier)))();
27         else {
28             pragma(msg, "\nERROR! Autowrap could not wrap function `", function_.identifier, "` for Python\n");
29             // def!(function_.symbol, PyName!(toSnakeCase(function_.identifier)))();
30         }
31     }
32 }
33 
34 
35 /**
36    wrap all aggregates found in the given modules, specified by their name
37    (to avoid importing all of them first).
38 
39    This function wraps all struct and class definitions, and also all struct and class
40    types that are parameters or return types of any functions found.
41  */
42 void wrapAllAggregates(Modules...)() if(allSatisfy!(isModule, Modules)) {
43 
44     import autowrap.common: AlwaysTry;
45     import autowrap.reflection: AllAggregates;
46     import std.traits: fullyQualifiedName;
47 
48     static foreach(aggregate; AllAggregates!Modules) {
49         static if(AlwaysTry || __traits(compiles, wrapAggregate!aggregate))
50             wrapAggregate!aggregate;
51         else {
52             pragma(msg, "\nERROR! Autowrap could not wrap aggregate `", fullyQualifiedName!aggregate, "` for Python\n");
53             // wrapAggregate!aggregate; // uncomment to see the error messages from the compiler
54         }
55     }
56 }
57 
58 
59 /**
60    Wrap aggregate of type T.
61  */
62 auto wrapAggregate(T)() if(isUserAggregate!T) {
63 
64     import autowrap.python.pyd.class_wrap: MemberFunction;
65     import mirror.meta.traits: isProperty, isStaticMemberFunction, PublicFieldNames;
66     import pyd.pyd: wrap_class, Member, Init, StaticDef, Repr, Property;
67     import std.meta: staticMap, Filter, templateNot, AliasSeq;
68     import std.algorithm: startsWith;
69 
70     alias AggMember(string memberName) = Symbol!(T, memberName);
71     alias members = staticMap!(AggMember, __traits(allMembers, T));
72     alias memberFunctions = Filter!(isMemberFunction, members);
73     alias staticMemberFunctions = Filter!(isStaticMemberFunction, memberFunctions);
74     alias nonStaticMemberFunctions = Filter!(templateNot!isStaticMemberFunction, memberFunctions);
75     enum isOperator(alias F) = __traits(identifier, F).startsWith("op");
76     alias regularMemberFunctions = Filter!(templateNot!isOperator, Filter!(templateNot!isProperty, nonStaticMemberFunctions));
77     alias properties = Filter!(isProperty, nonStaticMemberFunctions);
78     // FIXME - See #54
79     static if(is(T == class))
80         alias publicFieldNames = AliasSeq!();
81     else
82         alias publicFieldNames = PublicFieldNames!T;
83 
84     enum isToString(alias F) = __traits(identifier, F) == "toString";
85 
86     wrap_class!(
87         T,
88         staticMap!(Member, publicFieldNames),
89         staticMap!(MemberFunction, regularMemberFunctions),
90         staticMap!(StaticDef, staticMemberFunctions),
91         staticMap!(InitTuple, ConstructorParamTuples!T),
92         staticMap!(Repr, Filter!(isToString, memberFunctions)),
93         staticMap!(Property, properties),
94         OpUnaries!T,
95         OpBinaries!T,
96         OpBinaryRights!T,
97         OpCmps!T,
98         Lengths!T,
99         OpIndices!T,
100         DefOpSlices!T,
101         OpSliceRanges!T,
102         OpOpAssigns!T,
103         OpIndexAssigns!T,
104         OpSliceAssigns!T,
105         OpCalls!T,
106    );
107 }
108 
109 
110 // Given a parent (module, struct, ...) and a memberName, alias the actual member,
111 // or void if not possible
112 private template Symbol(alias parent, string memberName) {
113     static if(__traits(compiles, I!(__traits(getMember, parent, memberName))))
114         alias Symbol = I!(__traits(getMember, parent, memberName));
115     else
116         alias Symbol = void;
117 }
118 
119 
120 private template DefOpSlices(T) {
121     import std.traits: hasMember, Parameters;
122     import std.meta: AliasSeq, Filter, staticMap;
123 
124     static if(hasMember!(T, "opSlice")) {
125         // See testdll for details on this
126         enum hasNoParams(alias F) = Parameters!F.length == 0;
127         alias iters = Filter!(hasNoParams, __traits(getOverloads, T, "opSlice"));
128         alias defIters = staticMap!(DefOpSlice, iters);
129 
130         alias DefOpSlices = AliasSeq!(defIters);
131     } else
132         alias DefOpSlices = AliasSeq!();
133 }
134 
135 private template DefOpSlice(alias F) {
136     import pyd.pyd: Def, PyName;
137     import std.traits: ReturnType, Parameters;
138     alias DefOpSlice = Def!(F, PyName!"__iter__", ReturnType!F function(Parameters!F));
139 }
140 
141 private template OpSliceRanges(T) {
142     import pyd.pyd: OpSlice;
143     import std.traits: hasMember, Parameters, isIntegral;
144     import std.meta: AliasSeq, Filter, allSatisfy, staticMap;
145 
146     static if(hasMember!(T, "opSlice")) {
147         enum hasTwoIntParams(alias F) =
148             allSatisfy!(isIntegral, Parameters!F) && Parameters!F.length == 2;
149         alias twoInts = Filter!(hasTwoIntParams, __traits(getOverloads, T, "opSlice"));
150 
151         static if(twoInts.length > 0) {
152             // pyd is very specific about this for some reason
153             static if(__traits(compiles, OpSlice!().Inner!T))
154                 alias OpSliceRanges =  OpSlice!();
155             else
156                 alias OpSliceRanges = AliasSeq!();
157         } else
158             alias OpSliceRanges = AliasSeq!();
159     } else
160         alias OpSliceRanges = AliasSeq!();
161 }
162 
163 
164 
165 // A tuple, with as many elements as constructors. Each element is a
166 // std.typecons.Tuple of the constructor parameter types.
167 private template ConstructorParamTuples(alias T) {
168     import std.meta: staticMap, AliasSeq;
169     import std.traits: Parameters, hasMember;
170     import std.typecons: Tuple;
171 
172     // If we staticMap with std.traits.Parameters, we end up with a collapsed tuple
173     // i.e. with one constructor that takes int and another that takes int, string,
174     // we'd end up with 3 elements (int, int, string) instead of 2 ((int), (int, string))
175     // so we package them up in a std.typecons.Tuple to avoid flattening
176     // each being an AliasSeq of types for the constructor
177     alias ParametersTuple(alias F) = Tuple!(Parameters!F);
178 
179     static if(hasMember!(T, "__ctor"))
180         alias constructors = AliasSeq!(__traits(getOverloads, T, "__ctor"));
181     else
182         alias constructors = AliasSeq!();
183 
184     // A tuple, with as many elements as constructors. Each element is a
185     // std.typecons.Tuple of the constructor parameter types.
186     alias ConstructorParamTuples = staticMap!(ParametersTuple, constructors);
187 }
188 
189 // Apply pyd's Init to the unpacked types of the parameter Tuple.
190 private template InitTuple(alias Tuple) {
191     import pyd.pyd: Init;
192     alias InitTuple = Init!(Tuple.Types);
193 }
194 
195 
196 private alias OpBinaries(T)     = Operators!(T, "opBinary");
197 private alias OpBinaryRights(T) = Operators!(T, "opBinaryRight");
198 private alias OpUnaries(T)      = Operators!(T, "opUnary");
199 private alias OpOpAssigns(T)    = Operators!(T, "opOpAssign");
200 
201 private template Operators(T, string name) {
202     import std.uni: toUpper;
203     import std.conv: text;
204 
205     private enum pascalName = name[0].toUpper.text ~ name[1..$];
206     static if(pascalName == "OpOpAssign")
207         private enum pydName = "OpAssign";
208     else
209         private enum pydName = pascalName;
210 
211     mixin(`import pyd.pyd: ` ~ pydName ~ `;`);
212     import std.meta: AliasSeq, staticMap, Filter;
213     import std.traits: hasMember;
214 
215     private enum hasOperator(string op) = is(typeof(probeTemplate!(T, name, op)));
216     mixin(`alias toPyd(string op) = ` ~ pydName ~ `!op;`);
217 
218     alias pythonableOperators = AliasSeq!(
219         "+", "-", "*", "/", "%", "^^", "<<", ">>", "&", "^", "|", "in", "~",
220     );
221 
222     static if(hasMember!(T, name)) {
223         private alias dOperatorNames = Filter!(hasOperator, pythonableOperators);
224         alias Operators = staticMap!(toPyd, dOperatorNames);
225     } else
226         alias Operators = AliasSeq!();
227 }
228 
229 
230 private auto probeTemplate(T, string templateName, string op)() {
231     import std.traits: ReturnType, Parameters;
232     import std.meta: Alias;
233 
234     mixin(`alias func = T.` ~ templateName ~ `;`);
235     alias R = ReturnType!(func!op);
236     alias P = Parameters!(func!op);
237 
238     auto obj = T.init;
239 
240     static if(is(R == void))
241         mixin(`obj.` ~ templateName ~ `!op(P.init);`);
242     else
243         mixin(`R ret = obj.` ~ templateName ~ `!op(P.init);`);
244 }
245 
246 
247 private template OpCmps(T) {
248     import pyd.pyd: OpCompare;
249     import std.traits: hasMember;
250     import std.meta: AliasSeq;
251 
252     static if(hasMember!(T, "opCmp")) {
253         static if(__traits(compiles, OpCompare!().Inner!T))
254             alias OpCmps = AliasSeq!(OpCompare!());
255         else
256             alias OpCmps = AliasSeq!();
257     } else
258         alias OpCmps = AliasSeq!();
259 }
260 
261 private template Lengths(T) {
262     import pyd.pyd: Len;
263     import std.meta: AliasSeq;
264 
265     static if(is(typeof(T.init.length)))
266         alias Lengths = Len!(T.length);
267     else
268         alias Lengths = AliasSeq!();
269 }
270 
271 private template OpIndices(T) {
272     import pyd.pyd: OpIndex;
273     import std.meta: AliasSeq;
274 
275     static if(is(typeof(T.init.opIndex(0))))
276         alias OpIndices = OpIndex!();
277     else
278         alias OpIndices = AliasSeq!();
279 }
280 
281 
282 private template OpIndexAssigns(T) {
283     import pyd.pyd: OpIndexAssign;
284     import std.meta: AliasSeq;
285     import std.traits: hasMember;
286 
287     static if(hasMember!(T, "opIndexAssign")) {
288         static if(__traits(compiles, OpIndexAssign!().Inner!T))
289             alias OpIndexAssigns = OpIndexAssign!();
290         else
291             alias OpIndexAssigns = AliasSeq!();
292     } else
293         alias OpIndexAssigns = AliasSeq!();
294 }
295 
296 
297 private template OpSliceAssigns(T) {
298     import pyd.pyd: OpSliceAssign;
299     import std.meta: AliasSeq;
300     import std.traits: hasMember;
301 
302     static if(hasMember!(T, "opSliceAssign")) {
303         static if(__traits(compiles, OpSliceAssign!().Inner!T))
304             alias OpSliceAssigns = OpSliceAssign!();
305         else
306             alias OpSliceAssigns = AliasSeq!();
307     } else
308         alias OpSliceAssigns = AliasSeq!();
309 }
310 
311 
312 private template OpCalls(T) {
313     import pyd.pyd: OpCall;
314     import std.meta: AliasSeq, staticMap;
315     import std.traits: hasMember, Parameters;
316 
317     static if(hasMember!(T, "opCall")) {
318         alias overloads = AliasSeq!(__traits(getOverloads, T, "opCall"));
319         alias opCall(alias F) = OpCall!(Parameters!F);
320         alias OpCalls = staticMap!(opCall, overloads);
321     } else
322         alias OpCalls = AliasSeq!();
323 }
324 
325 
326 // must be a global template
327 private template isMemberFunction(A...) if(A.length == 1) {
328     import std.algorithm: startsWith;
329 
330     alias T = A[0];
331 
332     static if(__traits(compiles, __traits(identifier, T))) {
333         enum name = __traits(identifier, T);
334         enum isMemberFunction =
335             isPublicFunction!T
336             && !name.startsWith("__")
337             && name != "toHash"
338             ;
339     } else
340         enum isMemberFunction = false;
341 }
342 
343 
344 private template isPublicFunction(alias F) {
345     import std.traits: isFunction;
346     enum prot = __traits(getProtection, F);
347     enum isPublicFunction = isFunction!F && (prot == "export" || prot == "public");
348 }