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