diff --git a/pybindgen/converter_functions.py b/pybindgen/converter_functions.py index 6492378..00eac0e 100644 --- a/pybindgen/converter_functions.py +++ b/pybindgen/converter_functions.py @@ -4,7 +4,7 @@ python to C or C to python. These can be useful in certain specialized contexts, such as converting list elements. """ -from typehandlers.base import ReverseWrapperBase +from typehandlers.base import ReverseWrapperBase, ForwardWrapperBase class PythonToCConverter(ReverseWrapperBase): @@ -77,3 +77,61 @@ class PythonToCConverter(ReverseWrapperBase): def get_prototype(self): return "int %s(PyObject *value, %s *address)" % (self.c_function_name, self.return_value.ctype) + + + +class CToPythonConverter(ForwardWrapperBase): + ''' + Utility function that converts a C value to a PyObject*. + ''' + + def __init__(self, value_type, c_function_name): + """ + value_type -- a ReturnValue object handling the value type; + class_ -- the class (CppClass object) + attribute_name -- name of attribute + getter -- None, or name of a method of the class used to get the value + """ + super(CToPythonConverter, self).__init__(value_type, [], parse_error_return="return 0;", error_return="return 0;") + self.c_function_name = c_function_name + self.unblock_threads = False + + def generate(self, code_sink): + + save_return_value_value = self.return_value.value + self.return_value.value = "*cvalue" + try: + self.return_value.convert_c_to_python(self) + finally: + self.return_value.value = save_return_value_value + + code_sink.writeln(self.get_prototype()) + code_sink.writeln("{") + code_sink.indent() + + + params = self.build_params.get_parameters() + if params: + if params == ['""']: + self.after_call.write_code('Py_INCREF(Py_None);') + self.after_call.write_code('py_retval = Py_None;') + else: + assert params[0][0] == '"' + params[0] = "(char *) " + params[0] + self.after_call.write_code('py_retval = Py_BuildValue(%s);' % + (', '.join(params),)) + + ## cleanup and return + self.after_call.write_cleanup() + self.after_call.write_code('return py_retval;') + + self.declarations.get_code_sink().flush_to(code_sink) + code_sink.writeln() + self.before_parse.sink.flush_to(code_sink) + self.before_call.sink.flush_to(code_sink) + self.after_call.sink.flush_to(code_sink) + code_sink.unindent() + code_sink.writeln("}") + + def get_prototype(self): + return "PyObject* %s(%s *cvalue)" % (self.c_function_name, self.return_value.ctype) diff --git a/pybindgen/cppclass.py b/pybindgen/cppclass.py index 4b7daf3..d62c9d1 100644 --- a/pybindgen/cppclass.py +++ b/pybindgen/cppclass.py @@ -451,7 +451,6 @@ class CppClass(object): self._wrapper_registry = None self.binary_comparison_operators = set() self.binary_numeric_operators = dict() - self.pynumbermethods = None ## list of CppClasses from which a value of this class can be ## implicitly generated; corresponds to a @@ -590,7 +589,7 @@ class CppClass(object): if not isinstance(operator, str): raise TypeError("expected operator name as string") if operator not in ['==', '!=', '<', '<=', '>', '>=']: - raise NotImplementedError("The operator %r is invalid or not yet supported by PyBindGen" % (operator,)) + raise ValueError("The operator %r is invalid or not yet supported by PyBindGen" % (operator,)) self.binary_comparison_operators.add(operator) def add_binary_numeric_operator(self, operator, result_cppclass=None, @@ -608,7 +607,7 @@ class CppClass(object): if not isinstance(operator, str): raise TypeError("expected operator name as string") if operator not in ['+', '-', '*', '/']: - raise NotImplementedError("The operator %r is invalid or not yet supported by PyBindGen" % (operator,)) + raise ValueError("The operator %r is invalid or not yet supported by PyBindGen" % (operator,)) try: l = self.binary_numeric_operators[operator] except KeyError: @@ -620,7 +619,9 @@ class CppClass(object): left_cppclass = self if right_cppclass is None: right_cppclass = self - l.append((result_cppclass, left_cppclass, right_cppclass)) + op = (result_cppclass, left_cppclass, right_cppclass) + if op not in l: + l.append(op) def add_class(self, *args, **kwargs): """ @@ -1489,8 +1490,73 @@ typedef struct { if self.slots.get("tp_richcompare", "NULL") == "NULL": self.slots["tp_richcompare"] = self._generate_tp_richcompare(code_sink) + + if self.binary_numeric_operators: + self.slots["tp_as_number"] = self._generate_number_methods(code_sink) self._generate_type_structure(code_sink, docstring) + + def _generate_number_methods(self, code_sink): + number_methods_var_name = "%s__py_number_methods" % (self.mangled_full_name,) + + pynumbermethods = PyNumberMethods() + pynumbermethods.slots['variable'] = number_methods_var_name + + # iterate over all types and request generation of the + # convertion functions for that type (so that those functions + # are not generated in the middle of one of the wrappers we + # are about to generate) + root_module = self.module.get_root() + for dummy_op_symbol, op_types in self.binary_numeric_operators.iteritems(): + for (retval, left, right) in op_types: + root_module.generate_c_to_python_type_converter(retval.ThisClassReturn(retval.full_name), code_sink) + root_module.generate_python_to_c_type_converter(left.ThisClassReturn(left.full_name), code_sink) + root_module.generate_python_to_c_type_converter(right.ThisClassReturn(right.full_name), code_sink) + + def try_wrap_operator(op_symbol, slot_name): + try: + op_types = self.binary_numeric_operators[op_symbol] + except KeyError: + return + wrapper_name = "%s__%s" % (self.mangled_full_name, slot_name) + pynumbermethods.slots[slot_name] = wrapper_name + code_sink.writeln(("static PyObject*\n" + "%s (PyObject *py_left, PyObject *py_right)\n" + "{") % wrapper_name) + code_sink.indent() + for (retval, left, right) in op_types: + retval_converter = root_module.generate_c_to_python_type_converter(retval.ThisClassReturn(retval.full_name), code_sink) + left_converter = root_module.generate_python_to_c_type_converter(left.ThisClassReturn(left.full_name), code_sink) + right_converter = root_module.generate_python_to_c_type_converter(right.ThisClassReturn(right.full_name), code_sink) + + code_sink.writeln("{") + code_sink.indent() + + code_sink.writeln("%s left;" % left.full_name) + code_sink.writeln("%s right;" % right.full_name) + + code_sink.writeln("if (%s(py_left, &left) && %s(py_right, &right)) {" % (left_converter, right_converter)) + code_sink.indent() + code_sink.writeln("%s result = (left %s right);" % (retval.full_name, op_symbol)) + code_sink.writeln("return %s(&result);" % retval_converter) + code_sink.unindent() + code_sink.writeln("}") + + code_sink.unindent() + code_sink.writeln("}") + + code_sink.writeln("Py_INCREF(Py_NotImplemented);") + code_sink.writeln("return Py_NotImplemented;") + code_sink.unindent() + code_sink.writeln("}") + + try_wrap_operator('+', 'nb_add') + try_wrap_operator('-', 'nb_subtract') + try_wrap_operator('*', 'nb_multiply') + try_wrap_operator('/', 'nb_divide') + + pynumbermethods.generate(code_sink) + return '&' + number_methods_var_name def _generate_type_structure(self, code_sink, docstring): """generate the type structure""" diff --git a/pybindgen/module.py b/pybindgen/module.py index ff816f1..9618bd4 100644 --- a/pybindgen/module.py +++ b/pybindgen/module.py @@ -51,7 +51,7 @@ from typehandlers.codesink import MemoryCodeSink, CodeSink, FileCodeSink, NullCo from cppclass import CppClass from enum import Enum from container import Container -from converter_functions import PythonToCConverter +from converter_functions import PythonToCConverter, CToPythonConverter import utils import warnings import traceback @@ -878,6 +878,45 @@ class Module(ModuleBase): return converter_function_name + def get_c_to_python_type_converter_function_name(self, value_type): + """ + Internal API, do not use. + """ + assert isinstance(value_type, ReturnValue) + ctype = value_type.ctype + mangled_ctype = utils.mangle_name(ctype) + converter_function_name = "_wrap_convert_c2py__%s" % mangled_ctype + return converter_function_name + + def generate_c_to_python_type_converter(self, value_type, code_sink): + """ + Generates a c-to-python converter function for a given type + and returns the name of the generated function. If called + multiple times with the same name only the first time is the + converter function generated. + + Use: this method is to be considered pybindgen internal, used + by code generation modules. + + @type value_type: L{ReturnValue} + @type code_sink: L{CodeSink} + @returns: name of the converter function + """ + assert isinstance(value_type, ReturnValue) + converter_function_name = self.get_c_to_python_type_converter_function_name(value_type) + try: + self.declare_one_time_definition(converter_function_name) + except KeyError: + return converter_function_name + else: + converter = CToPythonConverter(value_type, converter_function_name) + self.header.writeln("\n%s;\n" % converter.get_prototype()) + code_sink.writeln() + converter.generate(code_sink) + code_sink.writeln() + return converter_function_name + + class SubModule(ModuleBase): def __init__(self, name, parent, docstring=None, cpp_namespace=None): """ diff --git a/pybindgen/pytypeobject.py b/pybindgen/pytypeobject.py index fdde696..7d9d013 100644 --- a/pybindgen/pytypeobject.py +++ b/pybindgen/pytypeobject.py @@ -105,52 +105,52 @@ class PyTypeObject(object): class PyNumberMethods(object): TEMPLATE = ( - 'PyNumberMethods %(variable)s = {\n' - '(binaryfunc) %(nb_add)s;\n' - '(binaryfunc) %(nb_subtract)s;\n' - '(binaryfunc) %(nb_multiply)s;\n' - '(binaryfunc) %(nb_divide)s;\n' - '(binaryfunc) %(nb_remainder)s;\n' - '(binaryfunc) %(nb_divmod)s;\n' - '(ternaryfunc) %(nb_power)s;\n' - '(unaryfunc) %(nb_negative)s;\n' - '(unaryfunc) %(nb_positive)s;\n' - '(unaryfunc) %(nb_absolute)s;\n' - '(inquiry) %(nb_nonzero)s;\n' - '(unaryfunc) %(nb_invert)s;\n' - '(binaryfunc) %(nb_lshift)s;\n' - '(binaryfunc) %(nb_rshift)s;\n' - '(binaryfunc) %(nb_and)s;\n' - '(binaryfunc) %(nb_xor)s;\n' - '(binaryfunc) %(nb_or)s;\n' - '(coercion) %(nb_coerce)s;\n' - '(unaryfunc) %(nb_int)s;\n' - '(unaryfunc) %(nb_long)s;\n' - '(unaryfunc) %(nb_float)s;\n' - '(unaryfunc) %(nb_oct)s;\n' - '(unaryfunc) %(nb_hex)s;\n' + 'static PyNumberMethods %(variable)s = {\n' + '(binaryfunc) %(nb_add)s,\n' + '(binaryfunc) %(nb_subtract)s,\n' + '(binaryfunc) %(nb_multiply)s,\n' + '(binaryfunc) %(nb_divide)s,\n' + '(binaryfunc) %(nb_remainder)s,\n' + '(binaryfunc) %(nb_divmod)s,\n' + '(ternaryfunc) %(nb_power)s,\n' + '(unaryfunc) %(nb_negative)s,\n' + '(unaryfunc) %(nb_positive)s,\n' + '(unaryfunc) %(nb_absolute)s,\n' + '(inquiry) %(nb_nonzero)s,\n' + '(unaryfunc) %(nb_invert)s,\n' + '(binaryfunc) %(nb_lshift)s,\n' + '(binaryfunc) %(nb_rshift)s,\n' + '(binaryfunc) %(nb_and)s,\n' + '(binaryfunc) %(nb_xor)s,\n' + '(binaryfunc) %(nb_or)s,\n' + '(coercion) %(nb_coerce)s,\n' + '(unaryfunc) %(nb_int)s,\n' + '(unaryfunc) %(nb_long)s,\n' + '(unaryfunc) %(nb_float)s,\n' + '(unaryfunc) %(nb_oct)s,\n' + '(unaryfunc) %(nb_hex)s,\n' '/* Added in release 2.0 */\n' - '(binaryfunc) %(nb_inplace_add)s;\n' - '(binaryfunc) %(nb_inplace_subtract)s;\n' - '(binaryfunc) %(nb_inplace_multiply)s;\n' - '(binaryfunc) %(nb_inplace_divide)s;\n' - '(binaryfunc) %(nb_inplace_remainder)s;\n' - '(ternaryfunc) %(nb_inplace_power)s;\n' - '(binaryfunc) %(nb_inplace_lshift)s;\n' - '(binaryfunc) %(nb_inplace_rshift)s;\n' - '(binaryfunc) %(nb_inplace_and)s;\n' - '(binaryfunc) %(nb_inplace_xor)s;\n' - '(binaryfunc) %(nb_inplace_or)s;\n' + '(binaryfunc) %(nb_inplace_add)s,\n' + '(binaryfunc) %(nb_inplace_subtract)s,\n' + '(binaryfunc) %(nb_inplace_multiply)s,\n' + '(binaryfunc) %(nb_inplace_divide)s,\n' + '(binaryfunc) %(nb_inplace_remainder)s,\n' + '(ternaryfunc) %(nb_inplace_power)s,\n' + '(binaryfunc) %(nb_inplace_lshift)s,\n' + '(binaryfunc) %(nb_inplace_rshift)s,\n' + '(binaryfunc) %(nb_inplace_and)s,\n' + '(binaryfunc) %(nb_inplace_xor)s,\n' + '(binaryfunc) %(nb_inplace_or)s,\n' '\n' '/* Added in release 2.2 */\n' '/* The following require the Py_TPFLAGS_HAVE_CLASS flag */\n' - '(binaryfunc) %(nb_floor_divide)s;\n' - '(binaryfunc) %(nb_true_divide)s;\n' - '(binaryfunc) %(nb_inplace_floor_divide);\n' - '(binaryfunc) %(nb_inplace_true_divide);\n' + '(binaryfunc) %(nb_floor_divide)s,\n' + '(binaryfunc) %(nb_true_divide)s,\n' + '(binaryfunc) %(nb_inplace_floor_divide)s,\n' + '(binaryfunc) %(nb_inplace_true_divide)s,\n' '\n' '/* Added in release 2.5 */\n' - '(unaryfunc) %(nb_index)s;\n' + '(unaryfunc) %(nb_index)s,\n' '\n' '};\n' ) diff --git a/tests/footest.py b/tests/footest.py index 3734621..a901f5e 100644 --- a/tests/footest.py +++ b/tests/footest.py @@ -1020,7 +1020,32 @@ class TestFoo(unittest.TestCase): self.assert_(t2 != t3) self.assert_(not(t2 == t3)) - - + def test_numeric_operators(self): + t1 = foo.Tupl() + + t1.x = 4 + t1.y = 6 + + t2 = foo.Tupl() + t2.x = 2 + t2.y = 3 + + r = t1 + t2 + self.assertEqual(r.x, t1.x + t2.x) + self.assertEqual(r.y, t1.y + t2.y) + + r = t1 - t2 + self.assertEqual(r.x, t1.x - t2.x) + self.assertEqual(r.y, t1.y - t2.y) + + r = t1 * t2 + self.assertEqual(r.x, t1.x * t2.x) + self.assertEqual(r.y, t1.y * t2.y) + + r = t1 / t2 + self.assertEqual(r.x, t1.x / t2.x) + self.assertEqual(r.y, t1.y / t2.y) + + if __name__ == '__main__': unittest.main()