1 module mir.pybind.conv;
2 
3 import std.stdio;
4 import std..string : toStringz;
5 import std.traits : staticMap;
6 import std.typecons : isTuple, Tuple;
7 import std.conv : to;
8 import std.format : format;
9 
10 import mir.ndslice : isSlice, SliceKind, Slice, Structure;
11 import mir.ndslice.connect.cpython : toPythonBuffer, fromPythonBuffer, PythonBufferErrorCode, PyBuf_indirect, PyBuf_format, PyBuf_writable, bufferinfo;
12 
13 import deimos.python.Python;
14 // FIXME: remove this line (need more version specification?)
15 extern (C) PyObject* PyUnicode_FromStringAndSize(const(char*) u, Py_ssize_t len);
16 extern (C) const(char*) PyUnicode_AsUTF8AndSize(PyObject *unicode, Py_ssize_t *size);
17 extern (C) const(char*) PyUnicode_AsUTF8(PyObject *unicode);
18 
19 import mir.pybind.format : formatTypes;
20 
21 
22 /// D type to PyObject conversion
23 auto toPyObject(double x) { return PyFloat_FromDouble(x); }
24 
25 /// ditto
26 auto toPyObject(long x) { return PyLong_FromLongLong(x); }
27 
28 /// ditto
29 auto toPyObject(ulong x) { return PyLong_FromUnsignedLongLong(x); }
30 
31 /// ditto
32 static if (!is(ulong == size_t))
33     auto toPyObject(size_t x) { return PyLong_FromSize_t(x); }
34 
35 /// ditto
36 static if (!is(long == ptrdiff_t))
37     auto toPyObject(ptrdiff_t x) { return PyLong_FromSsize_t(x); }
38 
39 /// ditto
40 auto toPyObject(bool b) { return PyBool_FromLong(b ? 1 : 0); }
41 
42 /// ditto
43 auto toPyObject(string s) { return PyUnicode_FromStringAndSize(s.ptr, s.length); }
44 
45 /// ditto
46 auto toPyObject(PyObject* p) { return p; }
47 
48 /**
49    default PyObject_GetBuffer flag
50    TODO supportuser-defined flag
51  */
52 enum PyBuf_full = PyBuf_indirect | PyBuf_format | PyBuf_writable;
53 
54 /// numpy.ndarray.descr.type_num
55 enum NpyType : int
56 {
57     npy_bool=0,
58     npy_byte, npy_ubyte,
59     npy_short, npy_ushort,
60     npy_int, npy_uint,
61     npy_long, npy_ulong,
62     npy_longlong, npy_ulonglong,
63     npy_float, npy_double, npy_longdouble,
64     npy_cfloat, npy_cdouble, npy_clongdouble,
65     npy_object=17,
66     npy_string, npy_unicode,
67     npy_void,
68     /**
69      * new 1.6 types appended, may be integrated
70      * into the above in 2.0.
71      */
72     npy_datetime, npy_timedelta, npy_half,
73 
74     npy_ntypes,
75     npy_notype,
76     /// npy_char npy_attr_deprecate("use npy_string"),
77     npy_userdef=256,  /* leave room for characters */
78 
79     /** the number of types not including the new 1.6 types */
80     npy_ntypes_abi_compatible=21
81 };
82 
83 /// e.g., bool -> npy_bool
84 template toNpyType(T)
85 {
86     mixin("enum toNpyType = NpyType.npy_" ~ T.stringof ~ ";");
87 }
88 
89 /// mir.ndslice.Slice to PyObject conversion
90 auto toPyObject(T, size_t n, SliceKind k)(Slice!(T*, n, k) x)
91 {
92     // import mir.ndslice.connect.cpython : cannot_create_f_contiguous_buffer;
93     bufferinfo buf;
94     Structure!n str;
95     auto err = toPythonBuffer(x, buf, PyBuf_full, str);
96     if (err != PythonBufferErrorCode.success) {
97         PyErr_SetString(PyExc_RuntimeError,
98                         "unable to convert Slice object into Py_buffer");
99     }
100     return PyMemoryView_FromBuffer(cast(deimos.python.object.Py_buffer*) &buf);
101     // FIXME use Array API
102     // auto p = PyArray_SimpleNew(n, cast(npy_intp*) x.shape.ptr, toNpyType!T);
103 }
104 
105 /// std.typecons.Tuple to PyObject conversion
106 auto toPyObject(T)(T xs) if (isTuple!T)
107 {
108     enum N = T.length;
109     auto p = PyTuple_New(N);
110     if (p == null) {
111         PyErr_SetString(PyExc_RuntimeError,
112                         ("unable to allocate " ~ N.to!string ~ " tuple elements").toStringz);
113     }
114     static foreach (i; 0 .. T.length) {{
115         auto pi = toPyObject(xs[i]);
116         PyTuple_SetItem(p, i, pi);
117      }}
118     return p;
119 }
120 
121 /// PyObject to D object conversion for python basic types
122 auto fromPyObject(ref long x, PyObject* o)
123 {
124     x = PyLong_AsLongLong(o);
125     return "";
126 }
127 
128 /// ditto
129 auto fromPyObject(ref ulong x, PyObject* o)
130 {
131     x = PyLong_AsUnsignedLongLong(o);
132     return "";
133 }
134 
135 /// ditto
136 auto fromPyObject(ref double x, PyObject* o)
137 {
138     x = PyFloat_AsDouble(o);
139     return "";
140 }
141 
142 /// ditto
143 auto fromPyObject(ref bool x, PyObject* o)
144 {
145     x = PyObject_IsTrue(o) == 1;
146     return "";
147 }
148 
149 /// ditto
150 auto fromPyObject(ref char[] x, PyObject* o)
151 {
152     import std..string : fromStringz;
153     // TODO find better way
154     // FIXME need incref here?
155     Py_ssize_t len;
156     auto ptr = cast(char*) PyUnicode_AsUTF8AndSize(o, &len);
157     if (ptr == null) return "invalid UTF8 string";
158     x = ptr[0 .. len];
159     return "";
160 }
161 
162 /// PyObject to D object conversion for mir slice
163 auto fromPyObject(T)(ref T x, PyObject* o) if (isSlice!T)
164 {
165     bufferinfo buf;
166     if (PyObject_CheckReadBuffer(o) == -1)
167     {
168         return "unable to read buffer";
169     }
170     PyObject_GetBuffer(o, cast(Py_buffer*) &buf, PyBuf_full);
171     scope(exit) PyBuffer_Release(cast(Py_buffer*) &buf);
172     auto err = fromPythonBuffer(x, buf);
173     if (err != PythonBufferErrorCode.success)
174     {
175         return "fail to execute mir.ndslice.connect.cpython.fromPythonBuffer (PythonBufferErrorCode: "
176             ~ err.to!string ~ ")";
177     }
178     return "";
179 }
180 
181 /// PyObject to D object conversion for std.typecons.tuple
182 auto fromPyObject(T)(ref T xs, PyObject* os) if (isTuple!T)
183 {
184     import deimos.python.tupleobject;
185     if (!PyTuple_Check(os))
186     {
187         return "non-tuple python object is passed to std.typecons.Tuple argument";
188     }
189     if (PyTuple_Size(os) != xs.length)
190     {
191         return "lengths of tuples are not matched: python %s vs D %s".format(
192             PyTuple_Size(os), xs.length);
193     }
194     foreach (i, ref x; xs)
195     {
196         auto o = PyTuple_GetItem(os, i);
197         x.fromPyObject(Py_XINCREF(o));
198     }
199     return "";
200 }
201 
202 /**
203    D function to PyObject conversion
204  */
205 extern(C)
206 PyObject* toPyFunction(alias dFunction)(PyObject* mod, PyObject* args)
207 {
208     import std.stdio;
209     import std.conv : to;
210     import std.traits : Parameters, ReturnType;
211     import std.meta : staticMap;
212     import std.typecons : Tuple, tuple;
213     import std..string : toStringz, replace;
214     import mir.pybind.format : formatTypes;
215 
216     alias Ps = Parameters!dFunction;
217     Tuple!Ps params;
218 
219     PyObject*[Ps.length] pyobjs;
220     mixin(
221         {
222             string fmt = "auto fmt = \"";
223             string tup = "auto pyrefs = tuple(";
224             foreach (i; 0 .. Ps.length) {
225                 fmt ~= "O";
226                 tup ~= "&pyobjs[" ~ i.to!string ~ "],";
227             }
228             return tup[0 .. $-1] ~ ");" ~ fmt ~ "\".toStringz;";
229         }());
230 
231     // TODO keyword args
232 
233     // TODO typecheck error message (maybe pyrefs.length is shorter?)
234     // enum f = formatTypes!Ps;
235     // void*[f.count] _refs;
236     // if (!PyArg_ParseTuple(args, f.str.toStringz, null))
237     // {
238     //     return null; // parsing failure
239     // }
240 
241     // extract PyObject* s from args
242     if (!PyArg_ParseTuple(args, fmt, pyrefs.expand))
243     {
244         return null; // parsing failure
245     }
246 
247     static foreach (i; 0 .. Ps.length)
248     {
249         {
250             const msg = params[i].fromPyObject(pyobjs[i]);
251             if (msg != "")
252             {
253                 auto e = "incompatible object, expected type: "
254                     ~ Ps[i].stringof ~ ", message: " ~ msg;
255                 PyErr_SetString(PyExc_RuntimeError, e.toStringz);
256             }
257         }
258     }
259 
260     static if (is(ReturnType!dFunction == void))
261     {
262         dFunction(params.expand);
263         return Py_None();
264     }
265     else
266     {
267         return toPyObject(dFunction(params.expand));
268     }
269 }