root/trunk/infrastructure/pyd/func_wrap.d

Revision 120, 9.5 kB (checked in by KirkMcDonald, 1 year ago)

* Pyd now requires D 2.003 or later.
* Pyd now compiles with D 2.003.
* Resolved long-standing string-copying annoyance (thanks to const).

Line 
1 /*
2 Copyright 2006, 2007 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.func_wrap;
23
24 import python;
25
26 import pyd.class_wrap;
27 import pyd.dg_convert;
28 import pyd.exception;
29 import pyd.make_object;
30 import pyd.lib_abstract :
31     toString,
32     ParameterTypeTuple,
33     ReturnType
34 ;
35
36 //import meta.Default;
37 //import meta.Nameof;
38
39 //import std.string;
40 //import std.traits;
41 //import std.stdio;
42
43 // Builds a callable Python object from a delegate or function pointer.
44 void PydWrappedFunc_Ready(T)() {
45     alias wrapped_class_type!(T) type;
46     alias wrapped_class_object!(T) obj;
47     if (!is_wrapped!(T)) {
48         type.ob_type = PyType_Type_p;
49         type.tp_basicsize = obj.sizeof;
50         type.tp_name = "PydFunc";
51         type.tp_flags = Py_TPFLAGS_DEFAULT;
52
53         type.tp_call = &wrapped_func_call!(T).call;
54
55         PyType_Ready(&type);
56         is_wrapped!(T) = true;
57         //wrapped_classes[typeid(T)] = true;
58     }
59 }
60
61 void setWrongArgsError(int gotArgs, uint minArgs, uint maxArgs, string funcName="") {
62     char[] str;
63     if (funcName == "") {
64         str ~= "function takes ";
65     } else {
66         str ~= funcName ~ "() takes ";
67     }
68
69     char[] argStr(int args) {
70         char[] temp = toString(args) ~ " argument";
71         if (args > 1) {
72             temp ~= "s";
73         }
74         return temp;
75     }
76
77     if (minArgs == maxArgs) {
78         if (minArgs == 0) {
79             str ~= "no arguments";
80         } else {
81             str ~= "exactly " ~ argStr(minArgs);
82         }
83     }
84     else if (gotArgs < minArgs) {
85         str ~= "at least " ~ argStr(minArgs);
86     } else {
87         str ~= "at most " ~ argStr(maxArgs);
88     }
89     str ~= " (" ~ toString(gotArgs) ~ " given)";
90
91     PyErr_SetString(PyExc_TypeError, (str ~ \0).ptr);
92 }
93
94 // Calls callable alias fn with PyTuple args.
95 ReturnType!(fn_t) applyPyTupleToAlias(alias fn, fn_t, uint MIN_ARGS) (PyObject* args) {
96     alias ParameterTypeTuple!(fn_t) T;
97     const uint MAX_ARGS = T.length;
98     alias ReturnType!(fn_t) RT;
99
100     int argCount = 0;
101     // This can make it more convenient to call this with 0 args.
102     if (args !is null) {
103         argCount = PyObject_Length(args);
104     }
105
106     // Sanity check!
107     if (argCount < MIN_ARGS || argCount > MAX_ARGS) {
108         setWrongArgsError(argCount, MIN_ARGS, MAX_ARGS);
109         handle_exception();
110     }
111
112     static if (MIN_ARGS == 0) {
113         if (argCount == 0) {
114             return fn();
115         }
116     }
117     T t;
118     foreach(i, arg; t) {
119         const uint argNum = i+1;
120         if (i < argCount) {
121             t[i] = d_type!(typeof(arg))(PyTuple_GetItem(args, i));
122         }
123         static if (argNum >= MIN_ARGS && argNum <= MAX_ARGS) {
124             if (argNum == argCount) {
125                 return fn(t[0 .. argNum]);
126                 break;
127             }
128         }
129     }
130     // This should never get here.
131     throw new Exception("applyPyTupleToAlias reached end! argCount = " ~ toString(argCount));
132     static if (!is(RT == void))
133         return RT.init;
134 }
135
136 // wraps applyPyTupleToAlias to return a PyObject*
137 PyObject* pyApplyToAlias(alias fn, fn_t, uint MIN_ARGS) (PyObject* args) {
138     static if (is(ReturnType!(fn_t) == void)) {
139         applyPyTupleToAlias!(fn, fn_t, MIN_ARGS)(args);
140         Py_INCREF(Py_None);
141         return Py_None;
142     } else {
143         return _py( applyPyTupleToAlias!(fn, fn_t, MIN_ARGS)(args) );
144     }
145 }
146
147 ReturnType!(dg_t) applyPyTupleToDelegate(dg_t) (dg_t dg, PyObject* args) {
148     alias ParameterTypeTuple!(dg_t) T;
149     const uint ARGS = T.length;
150     alias ReturnType!(dg_t) RT;
151
152     int argCount = 0;
153     // This can make it more convenient to call this with 0 args.
154     if (args !is null) {
155         argCount = PyObject_Length(args);
156     }
157
158     // Sanity check!
159     if (argCount != ARGS) {
160         setWrongArgsError(argCount, ARGS, ARGS);
161         handle_exception();
162     }
163
164     static if (ARGS == 0) {
165         if (argCount == 0) {
166             return dg();
167         }
168     }
169     T t;
170     foreach(i, arg; t) {
171         t[i] = d_type!(typeof(arg))(PyTuple_GetItem(args, i));
172     }
173     return dg(t);
174 }
175
176 // wraps applyPyTupleToDelegate to return a PyObject*
177 PyObject* pyApplyToDelegate(dg_t) (dg_t dg, PyObject* args) {
178     static if (is(ReturnType!(dg_t) == void)) {
179         applyPyTupleToDelegate(dg, args);
180         Py_INCREF(Py_None);
181         return Py_None;
182     } else {
183         return _py( applyPyTupleToDelegate(dg, args) );
184     }
185 }
186
187 template wrapped_func_call(fn_t) {
188     const uint ARGS = ParameterTypeTuple!(fn_t).length;
189     alias ReturnType!(fn_t) RT;
190     // The entry for the tp_call slot of the PydFunc types.
191     // (Or: What gets called when you pass a delegate or function pointer to
192     // Python.)
193     extern(C)
194     PyObject* call(PyObject* self, PyObject* args, PyObject* kwds) {
195         if (self is null) {
196             PyErr_SetString(PyExc_TypeError, "Wrapped method didn't get a function pointer.");
197             return null;
198         }
199
200         fn_t fn = (cast(wrapped_class_object!(fn_t)*)self).d_obj;
201
202         return exception_catcher(delegate PyObject*() {
203             return pyApplyToDelegate(fn, args);
204         });
205     }
206 }
207
208 // Wraps a function alias with a PyCFunction.
209 template function_wrap(alias real_fn, uint MIN_ARGS, fn_t=typeof(&real_fn)) {
210     alias ParameterTypeTuple!(fn_t) Info;
211     const uint MAX_ARGS = Info.length;
212     alias ReturnType!(fn_t) RT;
213
214     extern (C)
215     PyObject* func(PyObject* self, PyObject* args) {
216         return exception_catcher(delegate PyObject*() {
217             return pyApplyToAlias!(real_fn, fn_t, MIN_ARGS)(args);
218         });
219     }
220 }
221
222 // Wraps a member function alias with a PyCFunction.
223 template method_wrap(C, alias real_fn, fn_t=typeof(&real_fn)) {
224     alias ParameterTypeTuple!(fn_t) Info;
225     const uint ARGS = Info.length;
226     alias ReturnType!(fn_t) RT;
227     extern(C)
228     PyObject* func(PyObject* self, PyObject* args) {
229         return exception_catcher(delegate PyObject*() {
230             // Didn't pass a "self" parameter! Ack!
231             if (self is null) {
232                 PyErr_SetString(PyExc_TypeError, "Wrapped method didn't get a 'self' parameter.");
233                 return null;
234             }
235             C instance = WrapPyObject_AsObject!(C)(self);//(cast(wrapped_class_object!(C)*)self).d_obj;
236             if (instance is null) {
237                 PyErr_SetString(PyExc_ValueError, "Wrapped class instance is null!");
238                 return null;
239             }
240             fn_to_dg!(fn_t) dg = dg_wrapper!(C, fn_t)(instance, cast(fn_t)&real_fn);
241             return pyApplyToDelegate(dg, args);
242         });
243     }
244 }
245
246 //-----------------------------------------------------------------------------
247 // And now the reverse operation: wrapping a Python callable with a delegate.
248 // These rely on a whole collection of nasty templates, but the result is both
249 // flexible and pretty fast.
250 // (Sadly, wrapping a Python callable with a regular function is not quite
251 // possible.)
252 //-----------------------------------------------------------------------------
253 // The steps involved when calling this function are as follows:
254 // 1) An instance of PydWrappedFunc is made, and the callable placed within.
255 // 2) The delegate type Dg is broken into its constituent parts.
256 // 3) These parts are used to get the proper overload of PydWrappedFunc.fn
257 // 4) A delegate to PydWrappedFunc.fn is returned.
258 // 5) When fn is called, it attempts to cram the arguments into the callable.
259 //    If Python objects to this, an exception is raised. Note that this means
260 //    any error in converting the callable to a given delegate can only be
261 //    detected at runtime.
262
263 Dg PydCallable_AsDelegate(Dg) (PyObject* c) {
264     return _pycallable_asdgT!(Dg).func(c);
265 }
266
267 private template _pycallable_asdgT(Dg) {
268     alias ParameterTypeTuple!(Dg) Info;
269     alias ReturnType!(Dg) Tr;
270
271     Dg func(PyObject* c) {
272         auto f = new PydWrappedFunc(c);
273
274         return &f.fn!(Tr, Info);
275     }
276 }
277
278 private
279 class PydWrappedFunc {
280     PyObject* callable;
281
282     this(PyObject* c) { callable = c; Py_INCREF(c); }
283     ~this() { Py_DECREF(callable); }
284
285     Tr fn(Tr, T ...) (T t) {
286         PyObject* ret = call(t);
287         if (ret is null) handle_exception();
288         scope(exit) Py_DECREF(ret);
289         return d_type!(Tr)(ret);
290     }
291
292     PyObject* call(T ...) (T t) {
293         const uint ARGS = T.length;
294         PyObject* pyt = PyTuple_FromItems(t);
295         if (pyt is null) return null;
296         scope(exit) Py_DECREF(pyt);
297         return PyObject_CallObject(callable, pyt);
298     }
299 }
Note: See TracBrowser for help on using the browser.