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 }