| 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 |
} |
|---|