1 /* 2 Copyright (c) 2006 Kirk McDonald 3 4 Permission is hereby granted, free of charge, to any person obtaining a copy of 5 this software and associated documentation files (the "Software"), to deal in 6 the Software without restriction, including without limitation the rights to 7 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 of the Software, and to permit persons to whom the Software is furnished to do 9 so, subject to the following conditions: 10 11 The above copyright notice and this permission notice shall be included in all 12 copies or substantial portions of the Software. 13 14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 SOFTWARE. 21 */ 22 module pyd.op_wrap; 23 24 import deimos.python.Python; 25 26 import std.algorithm: startsWith, endsWith; 27 import std.traits; 28 import std.exception: enforce; 29 import std..string: format; 30 import std.conv: to; 31 import util.typeinfo; 32 33 import pyd.references; 34 import pyd.class_wrap; 35 import pyd.func_wrap; 36 import pyd.exception; 37 import pyd.make_object; 38 39 // wrap a binary operator overload, handling __op__, __rop__, or 40 // __op__ and __rop__ as necessary. 41 // use new style operator overloading (ie check which arg is actually self). 42 // _lop.C is a tuple w length 0 or 1 containing a BinaryOperatorX instance. 43 // same for _rop.C. 44 template binop_wrap(T, _lop, _rop) { 45 alias _lop.C lop; 46 alias _rop.C rop; 47 alias PydTypeObject!T wtype; 48 static if(lop.length) { 49 alias lop[0] lop0; 50 alias lop0.Inner!T.FN lfn; 51 alias dg_wrapper!(T, typeof(&lfn)) get_dgl; 52 alias ParameterTypeTuple!(lfn)[0] LOtherT; 53 alias ReturnType!(lfn) LRet; 54 } 55 static if(rop.length) { 56 alias rop[0] rop0; 57 alias rop0.Inner!T.FN rfn; 58 alias dg_wrapper!(T, typeof(&rfn)) get_dgr; 59 alias ParameterTypeTuple!(rfn)[0] ROtherT; 60 alias ReturnType!(rfn) RRet; 61 } 62 enum mode = (lop.length?"l":"")~(rop.length?"r":""); 63 extern(C) 64 PyObject* func(PyObject* o1, PyObject* o2) { 65 return exception_catcher(delegate PyObject*() { 66 enforce(is_wrapped!(T)); 67 68 static if(mode == "lr") { 69 if (PyObject_IsInstance(o1, cast(PyObject*)&wtype)) { 70 goto op; 71 }else if(PyObject_IsInstance(o2, cast(PyObject*)&wtype)) { 72 goto rop; 73 }else{ 74 enforce(false, format( 75 "unsupported operand type(s) for %s: '%s' and '%s'", 76 lop[0].op, to!string(o1.ob_type.tp_name), 77 to!string(o2.ob_type.tp_name), 78 )); 79 } 80 } 81 static if(mode.startsWith("l")) { 82 op: 83 auto dgl = get_dgl(get_d_reference!T(o1), &lfn); 84 static if(lop[0].op.endsWith("=")) { 85 dgl(python_to_d!LOtherT(o2)); 86 // why? 87 // http://stackoverflow.com/questions/11897597/implementing-nb-inplace-add-results-in-returning-a-read-only-buffer-object 88 // .. still don't know 89 Py_INCREF(o1); 90 return o1; 91 }else static if (is(LRet == void)) { 92 dgl(python_to_d!LOtherT(o2)); 93 return Py_INCREF(Py_None()); 94 } else { 95 return d_to_python(dgl(python_to_d!LOtherT(o2))); 96 } 97 } 98 static if(mode.endsWith("r")) { 99 rop: 100 auto dgr = get_dgr(get_d_reference!T(o2), &rfn); 101 static if (is(RRet == void)) { 102 dgr(python_to_d!ROtherT(o1)); 103 return Py_INCREF(Py_None()); 104 } else { 105 return d_to_python(dgr(python_to_d!LOtherT(o1))); 106 } 107 } 108 }); 109 } 110 } 111 112 template binopasg_wrap(T, alias fn) { 113 alias PydTypeObject!T wtype; 114 alias dg_wrapper!(T, typeof(&fn)) get_dg; 115 alias ParameterTypeTuple!(fn)[0] OtherT; 116 alias ReturnType!(fn) Ret; 117 118 extern(C) 119 PyObject* func(PyObject* self, PyObject* o2) { 120 auto dg = get_dg(get_d_reference!T(self), &fn); 121 dg(python_to_d!OtherT(o2)); 122 // why? 123 // http://stackoverflow.com/questions/11897597/implementing-nb-inplace-add-results-in-returning-a-read-only-buffer-object 124 // .. still don't know 125 Py_INCREF(self); 126 return self; 127 } 128 } 129 130 // pow is special. its stupid slot is a ternary function. 131 template powop_wrap(T, _lop, _rop) { 132 alias _lop.C lop; 133 alias _rop.C rop; 134 alias PydTypeObject!T wtype; 135 static if(lop.length) { 136 alias lop[0] lop0; 137 alias lop0.Inner!T.FN lfn; 138 alias dg_wrapper!(T, typeof(&lfn)) get_dgl; 139 alias ParameterTypeTuple!(lfn)[0] LOtherT; 140 alias ReturnType!(lfn) LRet; 141 } 142 static if(rop.length) { 143 alias rop[0] rop0; 144 alias rop0.Inner!T.FN rfn; 145 alias dg_wrapper!(T, typeof(&rfn)) get_dgr; 146 alias ParameterTypeTuple!(rfn)[0] ROtherT; 147 alias ReturnType!(rfn) RRet; 148 } 149 enum mode = (lop.length?"l":"")~(rop.length?"r":""); 150 extern(C) 151 PyObject* func(PyObject* o1, PyObject* o2, PyObject* o3) { 152 return exception_catcher(delegate PyObject*() { 153 enforce(is_wrapped!(T)); 154 155 static if(mode == "lr") { 156 if (PyObject_IsInstance(o1, cast(PyObject*)&wtype)) { 157 goto op; 158 }else if(PyObject_IsInstance(o2, cast(PyObject*)&wtype)) { 159 goto rop; 160 }else{ 161 enforce(false, format( 162 "unsupported operand type(s) for %s: '%s' and '%s'", 163 opl.op, o1.ob_type.tp_name, o2.ob_type.tp_name, 164 )); 165 } 166 } 167 static if(mode.startsWith("l")) { 168 op: 169 auto dgl = get_dgl(get_d_reference!T(o1), &lfn); 170 static if (is(LRet == void)) { 171 dgl(python_to_d!LOtherT(o2)); 172 return Py_INCREF(Py_None()); 173 } else { 174 return d_to_python(dgl(python_to_d!LOtherT(o2))); 175 } 176 } 177 static if(mode.endsWith("r")) { 178 rop: 179 auto dgr = get_dgr(get_d_reference!T(o2), &rfn); 180 static if (is(RRet == void)) { 181 dgr(python_to_d!ROtherT(o1)); 182 return Py_INCREF(Py_None()); 183 } else { 184 return d_to_python(dgr(python_to_d!LOtherT(o1))); 185 } 186 } 187 }); 188 } 189 } 190 191 template powopasg_wrap(T, alias fn) { 192 alias PydTypeObject!T wtype; 193 alias dg_wrapper!(T, typeof(&fn)) get_dg; 194 alias ParameterTypeTuple!(fn)[0] OtherT; 195 alias ReturnType!(fn) Ret; 196 197 extern(C) 198 PyObject* func(PyObject* self, PyObject* o2, PyObject* o3) { 199 auto dg = get_dg(get_d_reference!T(self), &fn); 200 dg(python_to_d!OtherT(o2)); 201 // why? 202 // http://stackoverflow.com/questions/11897597/implementing-nb-inplace-add-results-in-returning-a-read-only-buffer-object 203 // .. still don't know 204 Py_INCREF(self); 205 return self; 206 } 207 } 208 209 template opcall_wrap(T, alias fn) { 210 static assert(constCompatible(constness!T, constness!(typeof(fn))), 211 format("constness mismatch instance: %s function: %s", 212 T.stringof, typeof(fn).stringof)); 213 alias PydTypeObject!T wtype; 214 alias dg_wrapper!(T, typeof(&fn)) get_dg; 215 alias ParameterTypeTuple!(fn)[0] OtherT; 216 alias ReturnType!(fn) Ret; 217 218 extern(C) 219 PyObject* func(PyObject* self, PyObject* args, PyObject* kwargs) { 220 return exception_catcher(delegate PyObject*() { 221 // Didn't pass a "self" parameter! Ack! 222 if (self is null) { 223 PyErr_SetString(PyExc_TypeError, "OpCall didn't get a 'self' parameter."); 224 return null; 225 } 226 T instance = get_d_reference!T(self); 227 if (instance is null) { 228 PyErr_SetString(PyExc_ValueError, "Wrapped class instance is null!"); 229 return null; 230 } 231 auto dg = get_dg(instance, &fn); 232 return pyApplyToDelegate(dg, args); 233 }); 234 } 235 } 236 237 //----------------// 238 // Implementation // 239 //----------------// 240 241 template opfunc_unary_wrap(T, alias opfn) { 242 extern(C) 243 PyObject* func(PyObject* self) { 244 // method_dgwrap takes care of exception handling 245 return method_dgwrap!(T, opfn).func(self, null); 246 } 247 } 248 249 template opiter_wrap(T, alias fn){ 250 alias ParameterTypeTuple!fn params; 251 extern(C) 252 PyObject* func(PyObject* self) { 253 alias memberfunc_to_func!(T,fn).func func; 254 return exception_catcher(delegate PyObject*() { 255 T t = python_to_d!T(self); 256 auto dg = dg_wrapper(t, &fn); 257 return d_to_python(dg()); 258 }); 259 } 260 } 261 262 template opindex_wrap(T, alias fn) { 263 alias ParameterTypeTuple!fn Params; 264 alias dg_wrapper!(T, typeof(&fn)) get_dg; 265 266 // Multiple arguments are converted into tuples, and thus become a standard 267 // wrapped member function call. A single argument is passed directly. 268 static if (Params.length == 1) { 269 alias Params[0] KeyT; 270 extern(C) 271 PyObject* func(PyObject* self, PyObject* key) { 272 return exception_catcher(delegate PyObject*() { 273 auto dg = get_dg(get_d_reference!T(self), &fn); 274 return d_to_python(dg(python_to_d!KeyT(key))); 275 }); 276 } 277 } else { 278 alias method_dgwrap!(T, fn) opindex_methodT; 279 extern(C) 280 PyObject* func(PyObject* self, PyObject* key) { 281 Py_ssize_t args; 282 if (!PyTuple_CheckExact(key)) { 283 args = 1; 284 } else { 285 args = PySequence_Length(key); 286 } 287 if (Params.length != args) { 288 setWrongArgsError(args, Params.length, Params.length); 289 return null; 290 } 291 return opindex_methodT.func(self, key); 292 } 293 } 294 } 295 296 template opindexassign_wrap(T, alias fn) { 297 alias ParameterTypeTuple!(fn) Params; 298 299 static if (Params.length > 2) { 300 alias method_dgwrap!(T, fn) fn_wrap; 301 extern(C) 302 int func(PyObject* self, PyObject* key, PyObject* val) { 303 Py_ssize_t args; 304 if (!PyTuple_CheckExact(key)) { 305 args = 2; 306 } else { 307 args = PySequence_Length(key) + 1; 308 } 309 if (Params.length != args) { 310 setWrongArgsError(args, Params.length, Params.length); 311 return -1; 312 } 313 // Build a new tuple with the value at the front. 314 PyObject* temp = PyTuple_New(Params.length); 315 if (temp is null) return -1; 316 scope(exit) Py_DECREF(temp); 317 PyTuple_SetItem(temp, 0, val); 318 for (int i=1; i<Params.length; ++i) { 319 Py_INCREF(PyTuple_GetItem(key, i-1)); 320 PyTuple_SetItem(temp, i, PyTuple_GetItem(key, i-1)); 321 } 322 fnwrap.func(self, temp); 323 return 0; 324 } 325 } else { 326 alias dg_wrapper!(T, typeof(&fn)) get_dg; 327 alias Params[0] ValT; 328 alias Params[1] KeyT; 329 330 extern(C) 331 int func(PyObject* self, PyObject* key, PyObject* val) { 332 return exception_catcher(delegate int() { 333 auto dg = get_dg(get_d_reference!T(self), &fn); 334 dg(python_to_d!ValT(val), python_to_d!KeyT(key)); 335 return 0; 336 }); 337 } 338 } 339 } 340 341 template inop_wrap(T, _lop, _rop) { 342 alias _rop.C rop; 343 static if(rop.length) { 344 alias rop[0] rop0; 345 alias rop0.Inner!T.FN rfn; 346 alias dg_wrapper!(T, typeof(&rfn)) get_dgr; 347 alias ParameterTypeTuple!(rfn)[0] ROtherT; 348 } 349 350 extern(C) 351 int func(PyObject* o1, PyObject* o2) { 352 return exception_catcher(delegate int() { 353 auto dg = get_dgr(get_d_reference!T(o1), &rfn); 354 return dg(python_to_d!ROtherT(o2)); 355 }); 356 } 357 } 358 359 template opcmp_wrap(T, alias fn) { 360 static assert(constCompatible(constness!T, constness!(typeof(fn))), 361 format("constness mismatch instance: %s function: %s", 362 T.stringof, typeof(fn).stringof)); 363 alias ParameterTypeTuple!(fn) Info; 364 alias Info[0] OtherT; 365 extern(C) 366 int func(PyObject* self, PyObject* other) { 367 return exception_catcher(delegate int() { 368 int result = get_d_reference!T(self).opCmp(python_to_d!OtherT(other)); 369 // The Python API reference specifies that tp_compare must return 370 // -1, 0, or 1. The D spec says opCmp may return any integer value, 371 // and just compares it with zero. 372 if (result < 0) return -1; 373 if (result == 0) return 0; 374 if (result > 0) return 1; 375 assert(0); 376 }); 377 } 378 } 379 380 template rich_opcmp_wrap(T, alias fn) { 381 static assert(constCompatible(constness!T, constness!(typeof(fn))), 382 format("constness mismatch instance: %s function: %s", 383 T.stringof, typeof(fn).stringof)); 384 alias ParameterTypeTuple!(fn) Info; 385 alias dg_wrapper!(T, typeof(&fn)) get_dg; 386 alias Info[0] OtherT; 387 extern(C) 388 PyObject* func(PyObject* self, PyObject* other, int op) { 389 return exception_catcher(delegate PyObject*() { 390 auto dg = get_dg(get_d_reference!T(self), &fn); 391 auto dother = python_to_d!OtherT(other); 392 int result = dg(dother); 393 bool pyresult; 394 switch(op) { 395 case Py_LT: 396 pyresult = (result < 0); 397 break; 398 case Py_LE: 399 pyresult = (result <= 0); 400 break; 401 case Py_EQ: 402 pyresult = (result == 0); 403 break; 404 case Py_NE: 405 pyresult = (result != 0); 406 break; 407 case Py_GT: 408 pyresult = (result > 0); 409 break; 410 case Py_GE: 411 pyresult = (result >= 0); 412 break; 413 default: 414 assert(0); 415 } 416 if (pyresult) return Py_INCREF(Py_True); 417 else return Py_INCREF(Py_False); 418 }); 419 } 420 } 421 422 //----------// 423 // Dispatch // 424 //----------// 425 template length_wrap(T, alias fn) { 426 alias dg_wrapper!(T, typeof(&fn)) get_dg; 427 extern(C) 428 Py_ssize_t func(PyObject* self) { 429 return exception_catcher(delegate Py_ssize_t() { 430 auto dg = get_dg(get_d_reference!T(self), &fn); 431 return dg(); 432 }); 433 } 434 } 435 436 template opslice_wrap(T,alias fn) { 437 alias dg_wrapper!(T, typeof(&fn)) get_dg; 438 extern(C) 439 PyObject* func(PyObject* self, Py_ssize_t i1, Py_ssize_t i2) { 440 return exception_catcher(delegate PyObject*() { 441 auto dg = get_dg(get_d_reference!T(self), &fn); 442 return d_to_python(dg(i1, i2)); 443 }); 444 } 445 } 446 447 template opsliceassign_wrap(T, alias fn) { 448 alias ParameterTypeTuple!fn Params; 449 alias Params[0] AssignT; 450 alias dg_wrapper!(T, typeof(&fn)) get_dg; 451 452 extern(C) 453 int func(PyObject* self, Py_ssize_t i1, Py_ssize_t i2, PyObject* o) { 454 return exception_catcher(delegate int() { 455 auto dg = get_dg(get_d_reference!T(self), &fn); 456 dg(python_to_d!AssignT(o), i1, i2); 457 return 0; 458 }); 459 } 460 } 461