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