202 lines
8.5 KiB
Python
202 lines
8.5 KiB
Python
"""
|
|
C wrapper wrapper
|
|
"""
|
|
from typehandlers.base import ForwardWrapperBase
|
|
|
|
def isiterable(obj):
|
|
"""Returns True if an object appears to be iterable"""
|
|
return hasattr(obj, '__iter__') or isinstance(obj, basestring)
|
|
|
|
def vector_counter(vec):
|
|
"""
|
|
>>> list(vector_counter([[1,2], ['a', 'b'], ['x', 'y']]))
|
|
[[1, 'a', 'x'], [1, 'a', 'y'], [1, 'b', 'x'], [1, 'b', 'y'], [2, 'a', 'x'], [2, 'a', 'y'], [2, 'b', 'x'], [2, 'b', 'y']]
|
|
"""
|
|
iters = [iter(l) for l in vec]
|
|
values = [it.next() for it in iters[:-1]] + [vec[-1][0]]
|
|
while 1:
|
|
for idx in xrange(len(iters)-1, -1, -1):
|
|
try:
|
|
values[idx] = iters[idx].next()
|
|
except StopIteration:
|
|
iters[idx] = iter(vec[idx])
|
|
values[idx] = iters[idx].next()
|
|
else:
|
|
break
|
|
else:
|
|
raise StopIteration
|
|
yield list(values)
|
|
|
|
class OverloadedWrapper(object):
|
|
"""
|
|
An object that aggregates a set of wrapper objects; it generates
|
|
a single python wrapper wrapper that supports overloading,
|
|
i.e. tries to parse parameters according to each individual
|
|
Function parameter list, and uses the first wrapper that doesn't
|
|
generate parameter parsing error.
|
|
"""
|
|
|
|
RETURN_TYPE = NotImplemented
|
|
ERROR_RETURN = NotImplemented
|
|
|
|
def __init__(self, wrapper_name):
|
|
"""
|
|
wrapper_name -- C/C++ name of the wrapper
|
|
"""
|
|
self.wrappers = []
|
|
self.all_wrappers = None
|
|
self.wrapper_name = wrapper_name
|
|
self.wrapper_function_name = None
|
|
self.pystruct = 'PyObject'
|
|
|
|
def add(self, wrapper):
|
|
"""
|
|
Add a wrapper to the overloaded wrapper
|
|
wrapper -- a Wrapper object
|
|
"""
|
|
assert isinstance(wrapper, ForwardWrapperBase)
|
|
self.wrappers.append(wrapper)
|
|
|
|
def _compute_all_wrappers(self):
|
|
"""
|
|
Computes all the wrappers that should be generated; this
|
|
includes not only the regular overloaded wrappers but also
|
|
additional wrappers created in runtime to fulfil implicit
|
|
conversion requirements. The resulting list is stored as
|
|
self.all_wrappers
|
|
"""
|
|
self.all_wrappers = []
|
|
for wrapper in self.wrappers:
|
|
self.all_wrappers.append(wrapper)
|
|
## add additional wrappers to support implicit conversion
|
|
if not hasattr(wrapper, "clone"):
|
|
continue
|
|
implicit_conversion_classes = []
|
|
implicit_conversion_positions = []
|
|
for pos, param in enumerate(wrapper.parameters):
|
|
if not isinstance(param, CppClassParameter):
|
|
continue
|
|
conversion_sources = param.cpp_class.get_all_implicit_conversions()
|
|
if conversion_sources:
|
|
conversion_sources.insert(0, param.cpp_class)
|
|
implicit_conversion_classes.append(conversion_sources)
|
|
implicit_conversion_positions.append(pos)
|
|
if not implicit_conversion_classes:
|
|
continue
|
|
## Now generate the addition wrappers, with all possible
|
|
## combinations of implicit conversion source classes...
|
|
combinations = vector_counter(implicit_conversion_classes)
|
|
## skip the first one, as it is the same as in the original wrapper
|
|
combinations.next()
|
|
for combination in combinations:
|
|
wrapper_clone = wrapper.clone()
|
|
for pos, cpp_class in zip(implicit_conversion_positions, combination):
|
|
## now make this parameter handle a different C++ class
|
|
wrapper_clone.parameters[pos].cpp_class = cpp_class
|
|
self.all_wrappers.append(wrapper_clone)
|
|
|
|
|
|
def generate(self, code_sink):
|
|
"""
|
|
Generate all the wrappers plus the 'aggregator' wrapper to a code sink.
|
|
"""
|
|
|
|
self._compute_all_wrappers()
|
|
|
|
if len(self.all_wrappers) == 1:
|
|
## special case when there's only one wrapper; keep
|
|
## simple things simple
|
|
self.all_wrappers[0].generate(code_sink)
|
|
self.wrapper_function_name = self.all_wrappers[0].wrapper_actual_name
|
|
assert self.wrapper_function_name is not None
|
|
else:
|
|
## multiple overloaded wrappers case..
|
|
|
|
## Generate the individual "low level" wrappers that handle a single prototype
|
|
self.wrapper_function_name = self.all_wrappers[0].wrapper_base_name
|
|
delegate_wrappers = []
|
|
for number, wrapper in enumerate(self.all_wrappers):
|
|
## enforce uniform method flags
|
|
wrapper.force_parse = wrapper.PARSE_TUPLE_AND_KEYWORDS
|
|
## an extra parameter 'return_exception' is used to
|
|
## return parse error exceptions to the 'main wrapper'
|
|
error_return = """{
|
|
PyObject *exc_type, *traceback;
|
|
PyErr_Fetch(&exc_type, return_exception, &traceback);
|
|
Py_XDECREF(exc_type);
|
|
Py_XDECREF(traceback);
|
|
}
|
|
%s""" % (self.ERROR_RETURN,)
|
|
wrapper_name = "%s__%i" % (self.wrapper_function_name, number)
|
|
wrapper.set_parse_error_return(error_return)
|
|
code_sink.writeln()
|
|
wrapper.generate(code_sink, wrapper_name,
|
|
extra_wrapper_params=["PyObject **return_exception"])
|
|
delegate_wrappers.append(wrapper_name)
|
|
|
|
## Generate the 'main wrapper' that calls the other ones
|
|
code_sink.writeln()
|
|
code_sink.writeln("static " + self.RETURN_TYPE)
|
|
code_sink.writeln("%s(%s *self,"
|
|
" PyObject *args, PyObject *kwargs)"
|
|
% (self.wrapper_function_name, self.pystruct))
|
|
code_sink.writeln('{')
|
|
code_sink.indent()
|
|
code_sink.writeln(self.RETURN_TYPE + ' retval;')
|
|
code_sink.writeln('PyObject *error_list;')
|
|
code_sink.writeln('PyObject *exceptions[%i] = {0,};' % len(delegate_wrappers))
|
|
for number, delegate_wrapper in enumerate(delegate_wrappers):
|
|
## call the delegate wrapper
|
|
code_sink.writeln("retval = %s(self, args, kwargs, &exceptions[%i]);"
|
|
% (delegate_wrapper, number))
|
|
## if no parse exception, call was successful:
|
|
## free previous exceptions and return the result
|
|
code_sink.writeln("if (!exceptions[%i]) {" % number)
|
|
code_sink.indent()
|
|
for i in xrange(number):
|
|
code_sink.writeln("Py_DECREF(exceptions[%i]);" % i)
|
|
code_sink.writeln("return retval;")
|
|
code_sink.unindent()
|
|
code_sink.writeln("}")
|
|
|
|
## If the following generated code is reached it means
|
|
## that all of our delegate wrappers had parsing errors:
|
|
## raise an appropriate exception, free the previous
|
|
## exceptions, and return NULL
|
|
code_sink.writeln('error_list = PyList_New(%i);' % len(delegate_wrappers))
|
|
for i in xrange(len(delegate_wrappers)):
|
|
code_sink.writeln(
|
|
'PyList_SET_ITEM(error_list, %i, PyObject_Str(exceptions[%i]));'
|
|
% (i, i))
|
|
code_sink.writeln("Py_DECREF(exceptions[%i]);" % i)
|
|
code_sink.writeln('PyErr_SetObject(PyExc_TypeError, error_list);')
|
|
code_sink.writeln("Py_DECREF(error_list);")
|
|
code_sink.writeln(self.ERROR_RETURN)
|
|
code_sink.unindent()
|
|
code_sink.writeln('}')
|
|
|
|
def get_py_method_def(self, name):
|
|
"""
|
|
Returns an array element to use in a PyMethodDef table.
|
|
Should only be called after code generation.
|
|
|
|
name -- python wrapper/method name
|
|
"""
|
|
if len(self.all_wrappers) == 1:
|
|
return self.all_wrappers[0].get_py_method_def(name)
|
|
else:
|
|
flags = self.all_wrappers[0].get_py_method_def_flags()
|
|
## detect inconsistencies in flags; they must all be the same
|
|
if __debug__:
|
|
for func in self.all_wrappers:
|
|
assert func.get_py_method_def_flags() == flags
|
|
docstring = None # FIXME
|
|
return "{\"%s\", (PyCFunction) %s, %s, %s }," % \
|
|
(name, self.wrapper_function_name, '|'.join(flags),
|
|
(docstring is None and "NULL" or ('"'+docstring+'"')))
|
|
|
|
|
|
|
|
from cppclass import CppClassParameter
|
|
|