Files
pybindgen/pybindgen/overloading.py
T

301 lines
13 KiB
Python

"""
C wrapper wrapper
"""
from typehandlers.base import TypeConfigurationError, CodeGenerationError, NotSupportedError
from typehandlers.base import ForwardWrapperBase
import utils
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'
self.static_decl = True
# self.enable_implicit_conversions = True
def add(self, wrapper):
"""
Add a wrapper to the overloaded wrapper
wrapper -- a Wrapper object
"""
assert isinstance(wrapper, ForwardWrapperBase)
flags = None
for existing_wrapper in self.wrappers:
existing_flags = set(existing_wrapper.get_py_method_def_flags())
if flags is None:
flags = existing_flags
else:
assert flags == existing_flags
if flags is not None and (flags != set(wrapper.get_py_method_def_flags())):
## Try to coerce all wrappers into
## METH_KEYWORDS|METH_VARARGS mode, then try again
modified = False
if wrapper.force_parse != ForwardWrapperBase.PARSE_TUPLE_AND_KEYWORDS:
wrapper.force_parse = ForwardWrapperBase.PARSE_TUPLE_AND_KEYWORDS
modified = True
for existing_wrapper in self.wrappers:
if existing_wrapper.force_parse != ForwardWrapperBase.PARSE_TUPLE_AND_KEYWORDS:
existing_wrapper.force_parse = ForwardWrapperBase.PARSE_TUPLE_AND_KEYWORDS
modified = True
if modified:
return self.add(wrapper)
raise NotSupportedError("attempting to overload methods of different kinds"
" (adding %r vs existing %r)" %
(set(wrapper.get_py_method_def_flags()), flags))
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)
continue ### XXXX: implicit conversions are now handled
### differently; the code is embedded in
### CppClassParameter and CppClassRefParameter.
# if not self.enable_implicit_conversions:
# continue
# ## 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, CppClassRefParameter)):
# continue
# if isinstance(param, (CppClassRefParameter)) and not param.is_const:
# 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
# param = wrapper_clone.parameters[pos]
# ref = (isinstance(param, CppClassRefParameter) and ' &' or '')
# const = (param.is_const and 'const ' or '')
# param.cpp_class = cpp_class
# param.ctype = '%s%s%s' % (const, cpp_class.full_name, ref)
# 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 \
and not getattr(self.all_wrappers[0], 'NEEDS_OVERLOADING_INTERFACE', False):
## special case when there's only one wrapper; keep
## simple things simple
#self.all_wrappers[0].generate(code_sink)
utils.call_with_error_handling(self.all_wrappers[0].generate,
(code_sink,), {}, self.all_wrappers[0])
self.wrapper_function_name = self.all_wrappers[0].wrapper_actual_name
assert self.wrapper_function_name is not None
else:
## multiple overloaded wrappers case..
flags = self.all_wrappers[0].get_py_method_def_flags()
## 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"])
try:
utils.call_with_error_handling(
wrapper.generate, args=(code_sink, wrapper_name),
kwargs=dict(extra_wrapper_params=["PyObject **return_exception"]),
wrapper=wrapper)
except utils.SkipWrapper:
continue
delegate_wrappers.append(wrapper.wrapper_actual_name)
## if all wrappers did not generate, then the overload
## aggregator wrapper should not be generated either..
if not delegate_wrappers:
raise utils.SkipWrapper
## Generate the 'main wrapper' that calls the other ones
code_sink.writeln()
if self.static_decl:
code_sink.writeln("static " + self.RETURN_TYPE)
else:
code_sink.writeln(self.RETURN_TYPE)
main_args = ['%s *self' % self.pystruct]
if 'METH_VARARGS' in flags:
main_args.append('PyObject *args')
if 'METH_KEYWORDS' in flags:
main_args.append('PyObject *kwargs')
code_sink.writeln("%s(%s)" % (self.wrapper_function_name, ', '.join(main_args)))
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
args = ['self']
if 'METH_VARARGS' in flags:
args.append('args')
if 'METH_KEYWORDS' in flags:
args.append('kwargs')
args.append('&exceptions[%i]' % number)
code_sink.writeln("retval = %s(%s);" % (delegate_wrapper, ', '.join(args)))
## 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 \
and not getattr(self.all_wrappers[0], 'NEEDS_OVERLOADING_INTERFACE', False):
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:
try:
assert set(func.get_py_method_def_flags()) == set(flags),\
("Expected PyMethodDef flags %r, got %r"
% (flags, func.get_py_method_def_flags()))
except (TypeConfigurationError,
CodeGenerationError,
NotSupportedError):
pass
docstring = None # FIXME
return "{\"%s\", (PyCFunction) %s, %s, %s }," % \
(name, self.wrapper_function_name, '|'.join(flags),
(docstring is None and "NULL" or ('"'+docstring+'"')))
def generate_declaration(self, code_sink):
self._compute_all_wrappers()
if len(self.all_wrappers) > 1:
for i, wrapper in enumerate(self.all_wrappers):
wrapper.overload_index = i
wrapper.force_parse = wrapper.PARSE_TUPLE_AND_KEYWORDS
wrapper.generate_declaration(
code_sink,
extra_wrapper_parameters=["PyObject **return_exception"])
self.all_wrappers[0].overload_index = None
self.all_wrappers[0].generate_declaration(code_sink)
def reset_code_generation_state(self):
self._compute_all_wrappers()
for wrapper in self.all_wrappers:
wrapper.reset_code_generation_state()
from cppclass import CppClassParameter, CppClassRefParameter