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