1 module python.conv.python_to_d;
2 
3 
4 import python.raw: PyObject, pyListCheck, pyTupleCheck, PyTuple_Size, pyCallableCheck;
5 import python.type: isUserAggregate, isTuple;
6 import std.traits: Unqual, isIntegral, isFloatingPoint, isAggregateType, isArray,
7     isStaticArray, isAssociativeArray, isPointer, PointerTarget, isSomeChar, isSomeString,
8     isDelegate, isFunctionPointer;
9 import std.range: isInputRange;
10 import std.datetime: DateTime, Date;
11 import core.time: Duration;
12 
13 
14 T to(T)(PyObject* value) @trusted if(isIntegral!T && !is(T == enum)) {
15     import python.raw: PyLong_AsLong;
16 
17     const ret = PyLong_AsLong(value);
18     if(ret > T.max || ret < T.min) throw new Exception("Overflow");
19 
20     return cast(T) ret;
21 }
22 
23 
24 T to(T)(PyObject* value) @trusted if(isFloatingPoint!T) {
25     import python.raw: PyFloat_AsDouble;
26     auto ret = PyFloat_AsDouble(value);
27     return cast(T) ret;
28 }
29 
30 
31 private template UserAggregateReturnType(T) {
32     import std.traits: isCopyable;
33 
34     static if(isCopyable!T)
35         alias UserAggregateReturnType = T;
36      else
37         alias UserAggregateReturnType = T*;
38 }
39 
40 // Returns T if T is copyable, T* otherwise
41 UserAggregateReturnType!T to(T)(PyObject* value)
42     @trusted
43     if(isUserAggregate!T && (is(T == struct) || is(T == union)))
44 {
45     import std.traits: Unqual, isCopyable;
46 
47     alias RetType = UserAggregateReturnType!T;
48 
49     static if(isCopyable!T) {
50         Unqual!T ret;
51         toStructImpl(value, &ret);
52     } else {
53         auto ret = new Unqual!T;
54         toStructImpl(value, ret);
55     }
56 
57     return maybeCast!RetType(ret);
58 }
59 
60 
61 private void toStructImpl(T)(PyObject* value, T* ret) {
62     import autowrap.common: AlwaysTry;
63     import python.type: PythonClass;
64     import std.traits: fullyQualifiedName, isCopyable, isPointer;
65 
66     auto pyclass = cast(PythonClass!T*) value;
67 
68     static foreach(i; 0 .. typeof(*ret).tupleof.length) {{
69         static if(AlwaysTry || __traits(compiles, pyclass.getField!i.to!(typeof(T.tupleof[i])))) {
70 
71             auto pythonField = pyclass.getField!i;
72             auto converted = pythonField.to!(typeof(T.tupleof[i]));
73 
74             static if(isCopyable!(typeof((*ret).tupleof[i])))
75                 (*ret).tupleof[i] = converted;
76             else {
77                 static assert(isPointer!(typeof(converted)));
78                 toStructImpl(pyclass.getField!i, &((*ret).tupleof[i]));
79             }
80         } else
81             pragma(msg, "WARNING: cannot convert struct field #", i, " of ", fullyQualifiedName!T);
82     }}
83 }
84 
85 
86 T to(T)(PyObject* value) @trusted if(isUserAggregate!T && !is(T == struct) && !is(T == union))
87 {
88     import python.type: PythonClass, userAggregateInit, gFactory;
89     import std.traits: Unqual;
90     import std..string: fromStringz;
91     import std.conv: text;
92 
93     assert(value !is null, "Cannot convert null PyObject to `" ~ T.stringof ~ "`");
94 
95     auto pyclass = cast(PythonClass!T*) value;
96     const runtimeType = value.ob_type.tp_name.fromStringz.text;
97     auto creator = runtimeType in gFactory;
98     return creator
99         ? cast(T) (*creator)(value)
100         : userAggregateInit!T;
101 }
102 
103 
104 T to(T)(PyObject* value) if(isPointer!T && isUserAggregate!(PointerTarget!T)) {
105     auto ret = new Unqual!(PointerTarget!T);
106     toStructImpl(value, ret);
107     return ret;
108 }
109 
110 
111 // FIXME - not sure why a separate implementation is needed for non user aggregates
112 T to(T)(PyObject* value)
113     if(isPointer!T && !isUserAggregate!(PointerTarget!T) &&
114        !isFunctionPointer!T && !isDelegate!T &&
115        !is(Unqual!(PointerTarget!T) == void))
116 {
117     import python.raw: pyUnicodeCheck;
118     import std.traits: Unqual, PointerTarget;
119     import std..string: toStringz;
120 
121     enum isStringz = is(PointerTarget!T == const(char)) || is(PointerTarget!T == immutable(char));
122 
123     if(isStringz && pyUnicodeCheck(value))
124         return value.to!string.toStringz.maybeCast!T;
125     else {
126         auto ret = new Unqual!(PointerTarget!T);
127         *ret = value.to!(Unqual!(PointerTarget!T));
128         return ret.maybeCast!T;
129     }
130 }
131 
132 
133 T to(T)(PyObject* value) if(isPointer!T && is(Unqual!(PointerTarget!T) == void))
134 {
135     import python.raw: pyBytesCheck, PyBytes_AsString;
136     import std.exception: enforce;
137 
138     enforce(pyBytesCheck(value), "Can only convert Python bytes object to void*");
139     return cast(void*) PyBytes_AsString(value);
140 }
141 
142 
143 T to(T)(PyObject* value) if(is(Unqual!T == DateTime)) {
144     import python.raw;
145 
146     return DateTime(pyDateTimeYear(value),
147                     pyDateTimeMonth(value),
148                     pyDateTimeDay(value),
149                     pyDateTimeHour(value),
150                     pyDateTimeMinute(value),
151                     pyDateTimeSecond(value));
152 
153 }
154 
155 
156 T to(T)(PyObject* value) if(is(Unqual!T == Date)) {
157     import python.raw;
158 
159     return Date(pyDateTimeYear(value),
160                 pyDateTimeMonth(value),
161                 pyDateTimeDay(value));
162 }
163 
164 
165 T to(T)(PyObject* value) if(isArray!T && !isSomeString!T)
166     in(pyListCheck(value) || pyTupleCheck(value))
167 {
168     import python.raw: PyList_Size, PyList_GetItem, PyTuple_Size, PyTuple_GetItem;
169     import std.range: ElementEncodingType;
170     import std.traits: Unqual, isDynamicArray;
171     import std.exception: enforce;
172     import std.conv: text;
173 
174     alias ElementType = Unqual!(ElementEncodingType!T);
175 
176     // This is needed to deal with array of const or immutable
177     static if(isDynamicArray!T)
178         alias ArrayType = ElementType[];
179     else
180         alias ArrayType = Unqual!T;
181 
182     // deal with void[] here since otherwise we won't be able to iterate over it
183     static if(is(ElementType == void))
184         alias RetType = ubyte[];
185     else
186         alias RetType = ArrayType;
187 
188     RetType ret;
189 
190     static if(__traits(compiles, ret.length = 1)) {
191         const valueLength = {
192             if(pyListCheck(value))
193                 return PyList_Size(value);
194             else if(pyTupleCheck(value))
195                 return PyTuple_Size(value);
196             else
197                 assert(0);
198         }();
199         assert(valueLength >= 0, text("Invalid length ", valueLength));
200         ret.length = valueLength;
201     }
202 
203     foreach(i, ref elt; ret) {
204         auto pythonItem = {
205             if(pyListCheck(value))
206                 return PyList_GetItem(value, i);
207             else if(pyTupleCheck(value))
208                 return PyTuple_GetItem(value, i);
209             else
210                 assert(0);
211         }();
212         elt = pythonItem.to!(typeof(elt));
213     }
214 
215     return maybeCast!T(ret);
216 }
217 
218 
219 T to(T)(PyObject* value) if(isSomeString!T) {
220 
221     import python.raw: pyUnicodeGetSize, pyUnicodeCheck,
222         pyBytesAsString, pyObjectUnicode, pyUnicodeAsUtf8String, Py_ssize_t;
223     import std.conv: to;
224 
225     value = pyObjectUnicode(value);
226 
227     const length = pyUnicodeGetSize(value);
228     if(length == 0) return T.init;
229 
230     auto str = pyUnicodeAsUtf8String(value);
231     if(str is null) throw new Exception("Tried to convert a non-string Python value to D string");
232 
233     auto ptr = pyBytesAsString(str);
234     assert(length == 0 || ptr !is null);
235 
236     auto slice = ptr[0 .. length];
237 
238     return slice.to!T;
239 }
240 
241 
242 T to(T)(PyObject* value) if(is(Unqual!T == bool)) {
243     import python.raw: pyTrue;
244     return value is pyTrue;
245 }
246 
247 
248 T to(T)(PyObject* value) if(isAssociativeArray!T)
249 {
250     import python.raw: pyDictCheck, PyDict_Keys, PyList_Size, PyList_GetItem, PyDict_GetItem;
251 
252     assert(pyDictCheck(value));
253 
254     // this enum is to get K and V whilst avoiding auto-decoding, which is why we're not using
255     // std.traits
256     enum _ = is(T == V[K], V, K);
257     alias KeyType = Unqual!K;
258     alias ValueType = Unqual!V;
259 
260     ValueType[KeyType] ret;
261 
262     auto keys = PyDict_Keys(value);
263 
264     foreach(i; 0 .. PyList_Size(keys)) {
265         auto k = PyList_GetItem(keys, i);
266         auto v = PyDict_GetItem(value, k);
267         auto dk = k.to!KeyType;
268         auto dv = v.to!ValueType;
269 
270         ret[dk] = dv;
271     }
272 
273     return ret;
274 }
275 
276 
277 T to(T)(PyObject* value) if(isTuple!T)
278     in(pyTupleCheck(value))
279     in(PyTuple_Size(value) == T.length)
280     do
281 {
282     import python.raw: pyTupleCheck, PyTuple_Size, PyTuple_GetItem;
283 
284     T ret;
285 
286     static foreach(i; 0 .. T.length) {
287         ret[i] = PyTuple_GetItem(value, i).to!(typeof(ret[i]));
288     }
289 
290     return ret;
291 }
292 
293 
294 T to(T)(PyObject* value) if(is(Unqual!T == char) || is(Unqual!T == wchar) || is(Unqual!T == dchar)) {
295     auto str = value.to!string;
296     return str[0];
297 }
298 
299 
300 T to(T)(PyObject* value) if(isDelegate!T) {
301     return value.toDlangFunction!T;
302 }
303 
304 T to(T)(PyObject* value) if(isFunctionPointer!T) {
305     throw new Exception("Conversion of Python functions to D function pointers not yet implemented");
306 }
307 
308 
309 private T toDlangFunction(T)(PyObject* value)
310     in(pyCallableCheck(value))
311     do
312 {
313     import python.raw: PyObject_CallObject;
314     import python.conv.d_to_python: toPython;
315     import python.conv.python_to_d: to;
316     import std.traits: ReturnType, Unqual;
317     static import std.traits;
318     import std.meta: staticMap;
319     import std.typecons: Tuple;
320     import std.format: format;
321     import std.conv: text;
322 
323     alias UnqualParams = staticMap!(Unqual, std.traits.Parameters!T);
324 
325     enum code =
326         q{
327             // FIXME: the @trusted here is due to conversions to @safe
328             // D delegates
329             return (%s) @trusted {
330                 try {
331                     Tuple!UnqualParams dArgsTuple;
332                     static foreach(i; 0 .. UnqualParams.length) {
333                         dArgsTuple[i] = mixin(`arg` ~ i.text);
334                     }
335                     auto pyArgs = dArgsTuple.toPython;
336                     auto pyResult = PyObject_CallObject(value, pyArgs);
337                     static if(!is(ReturnType!T == void))
338                         return pyResult.to!(ReturnType!T);
339                     else
340                         return;
341                 } catch(Exception e) {
342                     import python.raw: PyErr_SetString, PyExc_RuntimeError;
343                     import std..string: toStringz;
344                     PyErr_SetString(PyExc_RuntimeError, e.msg.toStringz);
345                 }
346                 assert(0);
347 
348             };
349         }.format(
350             parametersRecipe!T("T"),
351         )
352     ;
353 
354     // pragma(msg, code);
355     mixin(code);
356 }
357 
358 private string parametersRecipe(alias F)(in string symbol)
359     in(__ctfe)
360     do
361 {
362 
363     import std.array: join;
364     import std.traits: Parameters;
365 
366     string[] parameters;
367 
368     static foreach(i; 0 .. Parameters!F.length) {
369         parameters ~= parameterRecipe!(F, i)(symbol);
370     }
371 
372     return parameters.join(", ");
373 }
374 
375 
376 private string parameterRecipe(alias F, size_t i)(in string symbol)
377     in(__ctfe)
378     do
379 {
380     import std.array: join;
381     import std.conv: text;
382     import std.traits: ParameterDefaults;
383 
384     const string[] storageClasses = [ __traits(getParameterStorageClasses, F, i) ];
385 
386     static string defaultValue(alias default_)() {
387         static if(is(default_ == void))
388             return "";
389         else
390             return text(" = ", default_);
391     }
392 
393 
394     return
395         text(storageClasses.join(" "), " ",
396              `std.traits.Parameters!(`, symbol, `)[`, i, `] `,
397              `arg`, i,
398              defaultValue!(ParameterDefaults!F[i]),
399             );
400 }
401 
402 
403 
404 T to(T)(PyObject* value) if(is(Unqual!T == Duration)) {
405     import python.raw: PyDateTime_Delta;
406     import core.time: days, seconds, usecs;
407 
408     const delta = cast(PyDateTime_Delta*) value;
409 
410     return delta.days.days + delta.seconds.seconds + delta.microseconds.usecs;
411 }
412 
413 
414 T to(T)(PyObject* value) if(is(T == enum)) {
415     import std.traits: OriginalType;
416     return cast(T) value.to!(OriginalType!T);
417 }
418 
419 
420 // Usually this is needed in the presence of `const` or `immutable`
421 private auto maybeCast(Wanted, Actual)(auto ref Actual value) {
422     static if(is(Wanted == Actual))
423         return value;
424     // the presence of `opCast` might mean this isn't
425     // always possible
426     else static if(__traits(compiles, cast(Wanted) value))
427         return cast(Wanted) value;
428     else {
429         Wanted ret = value;
430         return ret;
431     }
432 }