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