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