diff --git a/class_wrapper.h b/class_wrapper.h index 082af07b..9d3aa7fc 100644 --- a/class_wrapper.h +++ b/class_wrapper.h @@ -2,6 +2,7 @@ # define CLASS_WRAPPER_DWA101000_H_ #include "extclass.h" +#include "operators.h" #include "module.h" #include "py.h" #include "cast.h" @@ -26,6 +27,21 @@ class ClassWrapper void def(const Signature& signature) { m_class->def(signature); } + // export heterogeneous reverse-argument operators + // (type of lhs: 'left', of rhs: 'right') + // usage: foo_class.def(py::operators<(py::op_add | py::op_sub), Foo>(), + // py::left_operand()); + template + void def(operators o1, left_operand o2) + { m_class->def(o1, o2); } + + // export heterogeneous operators (type of lhs: 'left', of rhs: 'right') + // usage: foo_class.def(py::operators<(py::op_add | py::op_sub), Foo>(), + // py::right_operand()); + template + void def(operators o1, right_operand o2) + { m_class->def(o1, o2); } + // define a function that passes Python arguments and keywords // to C++ verbatim (as a 'Tuple const &' and 'Dict const &' // respectively). This is useful for manual argument passing. diff --git a/extclass.cpp b/extclass.cpp index ff8d173b..25398a93 100644 --- a/extclass.cpp +++ b/extclass.cpp @@ -11,6 +11,17 @@ namespace py { +namespace detail +{ + + Tuple extension_class_coerce(PyObject * l, PyObject * r) + { + return py::Tuple(Ptr(new detail::operator_dispatcher(l, l)), + Ptr(new detail::operator_dispatcher(r, 0))); + } + +} // namespace detail + ExtensionInstance* get_extension_instance(PyObject* p) { // The object's type will just be some Class object, @@ -325,4 +336,254 @@ void ExtensionClassBase::set_attribute(const char* name, Ptr x) detail::enable_named_method(this, name); } +namespace detail +{ +PyTypeObject operator_dispatcher::type_object = +{ + PyObject_HEAD_INIT(&PyType_Type) + 0, + "operator_dispatcher", + sizeof(operator_dispatcher), + 0, + (destructor)&operator_dispatcher::dealloc, + 0, + 0, + 0, + &operator_dispatcher::call_cmp, + 0, + &operator_dispatcher::number_methods, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 +}; + +PyNumberMethods operator_dispatcher::number_methods = +{ + &operator_dispatcher::call_add, + &operator_dispatcher::call_sub, + &operator_dispatcher::call_mul, + &operator_dispatcher::call_div, + &operator_dispatcher::call_mod, + &operator_dispatcher::call_divmod, + &operator_dispatcher::call_pow, + 0, + 0, + 0, + 0, + 0, + &operator_dispatcher::call_lshift, + &operator_dispatcher::call_rshift, + &operator_dispatcher::call_and, + &operator_dispatcher::call_xor, + &operator_dispatcher::call_or, + &operator_dispatcher::coerce, + 0, + 0, + 0, + 0, + 0 +}; + +operator_dispatcher::operator_dispatcher(PyObject * o, PyObject * s) +: object(o), self(s) +{ + ob_refcnt = 1; + ob_type = &type_object; +} + +void operator_dispatcher::dealloc(PyObject *self) +{ + delete static_cast(self); +} + +int operator_dispatcher::coerce(PyObject ** l, PyObject ** r) +{ + Py_INCREF(*l); + *r = new operator_dispatcher(*r, 0); + return 0; +} + + +#define py_define_operator(id, symbol) \ + PyObject * operator_dispatcher::call_##id(PyObject * left, PyObject * right) \ + { \ + /* unwrap the arguments from their OperatorDispatcher */ \ + PyObject * self, * other; \ + bool reverse = unwrap_args(left, right, self, other); \ + \ + /* call the function */ \ + PyObject * result = \ + PyEval_CallMethod(self, \ + const_cast(reverse ? "__r" #id "__" : "__" #id "__"), \ + const_cast("(O)"), \ + other); \ + if(result == 0 && PyErr_GivenExceptionMatches(PyErr_Occurred(), PyExc_AttributeError)) \ + { \ + PyErr_Clear(); \ + PyErr_SetString(PyExc_TypeError, "bad operand type(s) for " #symbol); \ + } \ + return result; \ + } + +py_define_operator(add, +) +py_define_operator(sub, -) +py_define_operator(mul, *) +py_define_operator(div, /) +py_define_operator(mod, %) +py_define_operator(divmod, divmod) +py_define_operator(lshift, <<) +py_define_operator(rshift, >>) +py_define_operator(and, &) +py_define_operator(xor, ^) +py_define_operator(or, |) + +/* coercion rules for heterogeneous pow(): + pow(Foo, int): left, right coerced; m: None => reverse = 0 + pow(int, Foo): left, right coerced; m: None => reverse = 1 + pow(Foo, int, int): left, right, m coerced => reverse = 0 + pow(int, Foo, int): left, right, m coerced => reverse = 1 + pow(int, int, Foo): left, right, m coerced => reverse = 2 + pow(Foo, Foo, int): left, right coerced; m coerced twice => reverse = 0 + pow(Foo, int, Foo): left, right, m coerced => reverse = 0 + pow(int, Foo, Foo): left, right, m coerced => reverse = 1 +*/ +PyObject * operator_dispatcher::call_pow(PyObject * left, PyObject * right, PyObject * m) +{ + int reverse; + PyObject * self, * first, * second; + if(m->ob_type == Py_None->ob_type) + { + reverse = unwrap_args(left, right, self, first); + second = m; + } + else + { + reverse = unwrap_pow_args(left, right, m, self, first, second); + } + + /* call the function */ + PyObject * result = + PyEval_CallMethod(self, + const_cast((reverse == 0) ? + "__pow__" : + (reverse == 1) ? + "__rpow__" : + "__rrpow__"), + const_cast("(OO)"), + first, second); + if(result == 0 && + (PyErr_GivenExceptionMatches(PyErr_Occurred(), PyExc_TypeError) || + PyErr_GivenExceptionMatches(PyErr_Occurred(), PyExc_AttributeError))) + { + PyErr_Clear(); + PyErr_SetString(PyExc_TypeError, "bad operand type(s) for pow()"); + } + return result; +} + +int operator_dispatcher::call_cmp(PyObject * left, PyObject * right) +{ + /* unwrap the arguments from their OperatorDispatcher */ + PyObject * self, * other; + bool reverse = unwrap_args(left, right, self, other); + + /* call the function */ + PyObject * result = + PyEval_CallMethod(self, + const_cast(reverse ? "__rcmp__" : "__cmp__"), + const_cast("(O)"), + other); + if(result == 0) + { + PyErr_Clear(); + PyErr_SetString(PyExc_TypeError, "bad operand type(s) for cmp()"); + return 0; + } + else + { + return PY_CONVERSION::from_python(result, Type()); + } +} + +bool operator_dispatcher::unwrap_args(PyObject * left, PyObject * right, + PyObject * & self, PyObject * & other) +{ + if(left->ob_type != &operator_dispatcher::type_object || + right->ob_type != &operator_dispatcher::type_object) + { + std::cerr << "operator_dispatcher::unwrap_args(): internal error (" __FILE__ ", " << + __LINE__ << ")" << std::endl; + exit(1); + } + + PyPtr lwrapper(static_cast(left)); + PyPtr rwrapper(static_cast(right)); + if(lwrapper->self != 0) + { + self = lwrapper->self; + other = rwrapper->object; + return false; + } + else + { + self = rwrapper->self; + other = lwrapper->object; + return true; + } +} + +int operator_dispatcher::unwrap_pow_args(PyObject * left, PyObject * right, PyObject * m, + PyObject * & self, PyObject * & first, PyObject * & second) +{ + if(left->ob_type != &operator_dispatcher::type_object || + right->ob_type != &operator_dispatcher::type_object || + m->ob_type != &operator_dispatcher::type_object) + { + std::cerr << "operator_dispatcher::unwrap_pow_args(): internal error (" __FILE__ ", " << + __LINE__ << ")" << std::endl; + exit(1); + } + + PyPtr lwrapper(static_cast(left)); + PyPtr rwrapper(static_cast(right)); + PyPtr mwrapper(static_cast(m)); + + if(mwrapper->object->ob_type == &operator_dispatcher::type_object) + { + mwrapper = static_cast(mwrapper->object); + } + + if(lwrapper->self != 0) + { + self = lwrapper->self; + first = rwrapper->object; + second = mwrapper->object; + return 0; + } + else if(rwrapper->self != 0) + { + self = rwrapper->self; + first = lwrapper->object; + second = mwrapper->object; + return 1; + } + else + { + self = mwrapper->self; + first = lwrapper->object; + second = rwrapper->object; + return 2; + } +} + + +} // namespace detail + } // namespace py diff --git a/extclass.h b/extclass.h index 97d7d400..5deb5210 100644 --- a/extclass.h +++ b/extclass.h @@ -118,6 +118,41 @@ class ClassRegistry static std::vector static_derived_class_info; }; +namespace detail +{ + struct operator_dispatcher + : public PyObject + { + static PyTypeObject type_object; + static PyNumberMethods number_methods; + + operator_dispatcher(PyObject * o, PyObject * s); + static void dealloc(PyObject *self); + static int coerce(PyObject ** l, PyObject ** r); + static PyObject * call_add(PyObject *, PyObject *); + static PyObject * call_sub(PyObject *, PyObject *); + static PyObject * call_mul(PyObject *, PyObject *); + static PyObject * call_div(PyObject *, PyObject *); + static PyObject * call_mod(PyObject *, PyObject *); + static PyObject * call_divmod(PyObject *, PyObject *); + static PyObject * call_lshift(PyObject *, PyObject *); + static PyObject * call_rshift(PyObject *, PyObject *); + static PyObject * call_and(PyObject *, PyObject *); + static PyObject * call_xor(PyObject *, PyObject *); + static PyObject * call_or(PyObject *, PyObject *); + static PyObject * call_pow(PyObject *, PyObject *, PyObject *); + static int call_cmp(PyObject *, PyObject *); + + static bool unwrap_args(PyObject * left, PyObject * right, + PyObject * & self, PyObject * & other); + static int unwrap_pow_args(PyObject *, PyObject *, PyObject *, + PyObject * &, PyObject * &, PyObject * &); + + PyObject * object; + PyObject * self; + }; +} + } PY_BEGIN_CONVERSION_NAMESPACE @@ -284,6 +319,9 @@ PyObject* to_python(const T& x) return py_extension_class_converters(py::Type()).to_python(x); } +inline PyObject * to_python(py::detail::operator_dispatcher * n) { return n; } + + PY_END_CONVERSION_NAMESPACE namespace py { @@ -302,22 +340,66 @@ class ReadOnlySetattrFunction : public Function String m_name; }; +enum operator_id +{ + op_add = 0x1, + op_sub = 0x2, + op_mul = 0x4, + op_div = 0x8, + op_mod = 0x10, + op_divmod =0x20, + op_pow = 0x40, + op_lshift = 0x80, + op_rshift = 0x100, + op_and = 0x200, + op_xor = 0x400, + op_or = 0x800, + op_neg = 0x1000, + op_pos = 0x2000, + op_abs = 0x4000, + op_invert = 0x8000, + op_int = 0x10000, + op_long = 0x20000, + op_float = 0x40000, + op_str = 0x80000, + op_cmp = 0x100000 +}; + namespace detail { - template - struct DefineConversion - { - static void * upcast_ptr(void * v) - { - return static_cast(static_cast(v)); - } + struct auto_operand {}; - static void * downcast_ptr(void * v) - { - return dynamic_cast(static_cast(v)); - } - }; + template + struct define_operator; + +} + +template +struct operators {}; + +template +struct left_operand {}; + +template +struct right_operand {}; + +namespace detail +{ + + template + struct DefineConversion + { + static void * upcast_ptr(void * v) + { + return static_cast(static_cast(v)); + } + + static void * downcast_ptr(void * v) + { + return dynamic_cast(static_cast(v)); + } + }; } @@ -366,6 +448,299 @@ class ExtensionClass Signature0())))))); } + + // export homogeneous operators (type of both lhs and rhs is 'operator') + // usage: foo_class.def(py::operators<(py::op_add | py::op_sub), Foo>()); + template + void def(operators) + { + register_coerce(); + + if(which & py::op_add) + add_method(new typename detail::define_operator<(which & py::op_add)>:: + operator_function(), + detail::define_operator<(which & py::op_add)>::name()); + + if(which & py::op_sub) + add_method(new typename detail::define_operator<(which & py::op_sub)>:: + operator_function(), + detail::define_operator<(which & py::op_sub)>::name()); + + if(which & py::op_mul) + add_method(new typename detail::define_operator<(which & py::op_mul)>:: + operator_function(), + detail::define_operator<(which & py::op_mul)>::name()); + + if(which & py::op_div) + add_method(new typename detail::define_operator<(which & py::op_div)>:: + operator_function(), + detail::define_operator<(which & py::op_div)>::name()); + + if(which & py::op_mod) + add_method(new typename detail::define_operator<(which & py::op_mod)>:: + operator_function(), + detail::define_operator<(which & py::op_mod)>::name()); + + if(which & py::op_divmod) + add_method(new typename detail::define_operator<(which & py::op_divmod)>:: + operator_function(), + detail::define_operator<(which & py::op_divmod)>::name()); + + if(which & py::op_pow) + add_method(new typename detail::define_operator<(which & py::op_pow)>:: + operator_function(), + detail::define_operator<(which & py::op_pow)>::name()); + + if(which & py::op_lshift) + add_method(new typename detail::define_operator<(which & py::op_lshift)>:: + operator_function(), + detail::define_operator<(which & py::op_lshift)>::name()); + + if(which & py::op_rshift) + add_method(new typename detail::define_operator<(which & py::op_rshift)>:: + operator_function(), + detail::define_operator<(which & py::op_rshift)>::name()); + + if(which & py::op_and) + add_method(new typename detail::define_operator<(which & py::op_and)>:: + operator_function(), + detail::define_operator<(which & py::op_and)>::name()); + + if(which & py::op_xor) + add_method(new typename detail::define_operator<(which & py::op_xor)>:: + operator_function(), + detail::define_operator<(which & py::op_xor)>::name()); + + if(which & py::op_or) + add_method(new typename detail::define_operator<(which & py::op_or)>:: + operator_function(), + detail::define_operator<(which & py::op_or)>::name()); + + if(which & py::op_neg) + add_method(new typename detail::define_operator<(which & py::op_neg)>:: + operator_function(), + detail::define_operator<(which & py::op_neg)>::name()); + + if(which & py::op_pos) + add_method(new typename detail::define_operator<(which & py::op_pos)>:: + operator_function(), + detail::define_operator<(which & py::op_pos)>::name()); + + if(which & py::op_abs) + add_method(new typename detail::define_operator<(which & py::op_abs)>:: + operator_function(), + detail::define_operator<(which & py::op_abs)>::name()); + + if(which & py::op_invert) + add_method(new typename detail::define_operator<(which & py::op_invert)>:: + operator_function(), + detail::define_operator<(which & py::op_invert)>::name()); + + if(which & py::op_int) + add_method(new typename detail::define_operator<(which & py::op_int)>:: + operator_function(), + detail::define_operator<(which & py::op_int)>::name()); + + if(which & py::op_long) + add_method(new typename detail::define_operator<(which & py::op_long)>:: + operator_function(), + detail::define_operator<(which & py::op_long)>::name()); + + if(which & py::op_float) + add_method(new typename detail::define_operator<(which & py::op_float)>:: + operator_function(), + detail::define_operator<(which & py::op_float)>::name()); + + if(which & py::op_cmp) + add_method(new typename detail::define_operator<(which & py::op_cmp)>:: + operator_function(), + detail::define_operator<(which & py::op_cmp)>::name()); + + if(which & py::op_str) + add_method(new typename detail::define_operator<(which & py::op_str)>:: + operator_function(), + detail::define_operator<(which & py::op_str)>::name()); + + } + + // export homogeneous operators (type of both lhs and rhs is 'T const &') + // usage: foo_class.def(py::operators<(py::op_add | py::op_sub)>()); + template + void def(operators) + { + def(operators()); + } + + // export heterogeneous operators (type of lhs: 'left', of rhs: 'right') + // usage: foo_class.def(py::operators<(py::op_add | py::op_sub), Foo>(), + // py::right_operand()); + template + void def(operators, right_operand) + { + register_coerce(); + + if(which & py::op_add) + add_method(new typename detail::define_operator<(which & py::op_add)>:: + operator_function(), + detail::define_operator<(which & py::op_add)>::name()); + + if(which & py::op_sub) + add_method(new typename detail::define_operator<(which & py::op_sub)>:: + operator_function(), + detail::define_operator<(which & py::op_sub)>::name()); + + if(which & py::op_mul) + add_method(new typename detail::define_operator<(which & py::op_mul)>:: + operator_function(), + detail::define_operator<(which & py::op_mul)>::name()); + + if(which & py::op_div) + add_method(new typename detail::define_operator<(which & py::op_div)>:: + operator_function(), + detail::define_operator<(which & py::op_div)>::name()); + + if(which & py::op_mod) + add_method(new typename detail::define_operator<(which & py::op_mod)>:: + operator_function(), + detail::define_operator<(which & py::op_mod)>::name()); + + if(which & py::op_divmod) + add_method(new typename detail::define_operator<(which & py::op_divmod)>:: + operator_function(), + detail::define_operator<(which & py::op_divmod)>::name()); + + if(which & py::op_pow) + add_method(new typename detail::define_operator<(which & py::op_pow)>:: + operator_function(), + detail::define_operator<(which & py::op_pow)>::name()); + + if(which & py::op_lshift) + add_method(new typename detail::define_operator<(which & py::op_lshift)>:: + operator_function(), + detail::define_operator<(which & py::op_lshift)>::name()); + + if(which & py::op_rshift) + add_method(new typename detail::define_operator<(which & py::op_rshift)>:: + operator_function(), + detail::define_operator<(which & py::op_rshift)>::name()); + + if(which & py::op_and) + add_method(new typename detail::define_operator<(which & py::op_and)>:: + operator_function(), + detail::define_operator<(which & py::op_and)>::name()); + + if(which & py::op_xor) + add_method(new typename detail::define_operator<(which & py::op_xor)>:: + operator_function(), + detail::define_operator<(which & py::op_xor)>::name()); + + if(which & py::op_or) + add_method(new typename detail::define_operator<(which & py::op_or)>:: + operator_function(), + detail::define_operator<(which & py::op_or)>::name()); + + if(which & py::op_cmp) + add_method(new typename detail::define_operator<(which & py::op_cmp)>:: + operator_function(), + detail::define_operator<(which & py::op_cmp)>::name()); + + } + + // export heterogeneous operators (type of lhs: 'T const &', of rhs: 'right') + // usage: foo_class.def(py::operators<(py::op_add | py::op_sub)>(), + // py::right_operand()); + template + void def(operators, right_operand r) + { + def(operators(), r); + } + + // export heterogeneous reverse-argument operators + // (type of lhs: 'left', of rhs: 'right') + // usage: foo_class.def(py::operators<(py::op_add | py::op_sub), Foo>(), + // py::left_operand()); + template + void def(operators, left_operand) + { + register_coerce(); + + if(which & py::op_add) + add_method(new typename detail::define_operator<(which & py::op_add)>:: + roperator_function(), + detail::define_operator<(which & py::op_add)>::rname()); + + if(which & py::op_sub) + add_method(new typename detail::define_operator<(which & py::op_sub)>:: + roperator_function(), + detail::define_operator<(which & py::op_sub)>::rname()); + + if(which & py::op_mul) + add_method(new typename detail::define_operator<(which & py::op_mul)>:: + roperator_function(), + detail::define_operator<(which & py::op_mul)>::rname()); + + if(which & py::op_div) + add_method(new typename detail::define_operator<(which & py::op_div)>:: + roperator_function(), + detail::define_operator<(which & py::op_div)>::rname()); + + if(which & py::op_mod) + add_method(new typename detail::define_operator<(which & py::op_mod)>:: + roperator_function(), + detail::define_operator<(which & py::op_mod)>::rname()); + + if(which & py::op_divmod) + add_method(new typename detail::define_operator<(which & py::op_divmod)>:: + roperator_function(), + detail::define_operator<(which & py::op_divmod)>::rname()); + + if(which & py::op_pow) + add_method(new typename detail::define_operator<(which & py::op_pow)>:: + roperator_function(), + detail::define_operator<(which & py::op_pow)>::rname()); + + if(which & py::op_lshift) + add_method(new typename detail::define_operator<(which & py::op_lshift)>:: + roperator_function(), + detail::define_operator<(which & py::op_lshift)>::rname()); + + if(which & py::op_rshift) + add_method(new typename detail::define_operator<(which & py::op_rshift)>:: + roperator_function(), + detail::define_operator<(which & py::op_rshift)>::rname()); + + if(which & py::op_and) + add_method(new typename detail::define_operator<(which & py::op_and)>:: + roperator_function(), + detail::define_operator<(which & py::op_and)>::rname()); + + if(which & py::op_xor) + add_method(new typename detail::define_operator<(which & py::op_xor)>:: + roperator_function(), + detail::define_operator<(which & py::op_xor)>::rname()); + + if(which & py::op_or) + add_method(new typename detail::define_operator<(which & py::op_or)>:: + roperator_function(), + detail::define_operator<(which & py::op_or)>::rname()); + + if(which & py::op_cmp) + add_method(new typename detail::define_operator<(which & py::op_cmp)>:: + roperator_function(), + detail::define_operator<(which & py::op_cmp)>::rname()); + + } + + // export heterogeneous reverse-argument operators + // (type of lhs: 'left', of rhs: 'T const &') + // usage: foo_class.def(py::operators<(py::op_add | py::op_sub)>(), + // py::left_operand()); + template + void def(operators, left_operand l) + { + def(operators(), l); + } + // define a function that passes Python arguments and keywords // to C++ verbatim (as a 'Tuple const &' and 'Dict const &' // respectively). This is useful for manual argument passing. @@ -476,6 +851,8 @@ class ExtensionClass { this->add_constructor_object(InitFunction::create(sig)); } + + void register_coerce(); }; // A simple wrapper over a T which allows us to use ExtensionClass with a @@ -593,6 +970,11 @@ class ExtensionInstance : public Instance // Template function implementations // +namespace detail +{ + Tuple extension_class_coerce(PyObject * l, PyObject * r); +} + template ExtensionClass::ExtensionClass() : ExtensionClassBase(typeid(T).name()) @@ -607,6 +989,15 @@ ExtensionClass::ExtensionClass(const char* name) ClassRegistry::register_class(this); } +template +void ExtensionClass::register_coerce() +{ + Ptr coerce_fct = dict().get_item(String("__coerce__")); + + if(coerce_fct.get() == 0) // not yet defined + this->def(&py::detail::extension_class_coerce, "__coerce__"); +} + template inline std::vector const & diff --git a/extclass_demo.cpp b/extclass_demo.cpp index 94edc6ae..a1bee25e 100644 --- a/extclass_demo.cpp +++ b/extclass_demo.cpp @@ -8,6 +8,7 @@ #include "extclass_demo.h" #include "class_wrapper.h" #include // used for portability on broken compilers +#include // for pow() #include namespace extclass_demo { @@ -633,6 +634,49 @@ struct Fubar { Fubar(int) {} }; +/************************************************************/ +/* */ +/* Int */ +/* this class tests operator export */ +/* */ +/************************************************************/ + +struct Int +{ + explicit Int(int i) : i_(i) { } + + int i() const { return i_; } + + int i_; +}; + +Int operator+(Int const & l, Int const & r) { return Int(l.i_ + r.i_); } +Int operator+(Int const & l, int const & r) { return Int(l.i_ + r); } +Int operator+(int const & l, Int const & r) { return Int(l + r.i_); } + +Int operator-(Int const & l, Int const & r) { return Int(l.i_ - r.i_); } +Int operator-(Int const & l, int const & r) { return Int(l.i_ - r); } +Int operator-(int const & l, Int const & r) { return Int(l - r.i_); } +Int operator-(Int const & r) { return Int(- r.i_); } + +Int mul(Int const & l, Int const & r) { return Int(l.i_ * r.i_); } +Int imul(Int const & l, int const & r) { return Int(l.i_ * r); } +Int rmul(Int const & r, int const & l) { return Int(l * r.i_); } + +Int operator/(Int const & l, Int const & r) { return Int(l.i_ / r.i_); } + +Int operator%(Int const & l, Int const & r) { return Int(l.i_ % r.i_); } + +bool operator<(Int const & l, Int const & r) { return l.i_ < r.i_; } +bool operator<(Int const & l, int const & r) { return l.i_ < r; } +bool operator<(int const & l, Int const & r) { return l < r.i_; } + +Int pow(Int const & l, Int const & r) { return Int(::pow(l.i_, r.i_)); } +Int powmod(Int const & l, Int const & r, Int const & m) { return Int((int)::pow(l.i_, r.i_) % m.i_); } +Int pow(Int const & l, int const & r) { return Int(::pow(l.i_, r)); } + +std::ostream & operator<<(std::ostream & o, Int const & r) { return (o << r.i_); } + /************************************************************/ /* */ /* double tests from Mark Evans() */ @@ -704,6 +748,8 @@ struct EnumOwner namespace py { template class enum_as_int_converters; + +using extclass_demo::pow; } // This is just a way of getting the converters instantiated @@ -883,6 +929,30 @@ void init_module(py::Module& m) m.def_raw(&raw, "raw"); m.def_raw(&raw1, "raw1"); m.def_raw(&raw2, "raw2"); + + py::ClassWrapper int_class(m, "Int"); + int_class.def(py::Constructor()); + int_class.def(&Int::i, "i"); + + // wrap homogeneous operators + int_class.def(py::operators<(py::op_add | py::op_sub | py::op_neg | + py::op_cmp | py::op_str | py::op_divmod | py::op_pow )>()); + // export non-operator functions as homogeneous operators + int_class.def(&mul, "__mul__"); + int_class.def(&powmod, "__pow__"); + + // wrap heterogeneous operators (lhs: Int const &, rhs: int const &) + int_class.def(py::operators<(py::op_add | py::op_sub | py::op_cmp | py::op_pow)>(), + py::right_operand()); + // export non-operator function as heterogeneous operator + int_class.def(&imul, "__mul__"); + + // wrap heterogeneous operators (lhs: int const &, rhs: Int const &) + int_class.def(py::operators<(py::op_add | py::op_sub | py::op_cmp)>(), + py::left_operand()); + // export non-operator function as heterogeneous reverse-argument operator + int_class.def(&rmul, "__rmul__"); + py::ClassWrapper enum_owner(m, "EnumOwner"); enum_owner.def(py::Constructor()); diff --git a/gen_extclass.py b/gen_extclass.py index bbb7b1d5..b5598072 100644 --- a/gen_extclass.py +++ b/gen_extclass.py @@ -123,6 +123,41 @@ class ClassRegistry static std::vector static_derived_class_info; }; +namespace detail +{ + struct operator_dispatcher + : public PyObject + { + static PyTypeObject type_object; + static PyNumberMethods number_methods; + + operator_dispatcher(PyObject * o, PyObject * s); + static void dealloc(PyObject *self); + static int coerce(PyObject ** l, PyObject ** r); + static PyObject * call_add(PyObject *, PyObject *); + static PyObject * call_sub(PyObject *, PyObject *); + static PyObject * call_mul(PyObject *, PyObject *); + static PyObject * call_div(PyObject *, PyObject *); + static PyObject * call_mod(PyObject *, PyObject *); + static PyObject * call_divmod(PyObject *, PyObject *); + static PyObject * call_lshift(PyObject *, PyObject *); + static PyObject * call_rshift(PyObject *, PyObject *); + static PyObject * call_and(PyObject *, PyObject *); + static PyObject * call_xor(PyObject *, PyObject *); + static PyObject * call_or(PyObject *, PyObject *); + static PyObject * call_pow(PyObject *, PyObject *, PyObject *); + static int call_cmp(PyObject *, PyObject *); + + static bool unwrap_args(PyObject * left, PyObject * right, + PyObject * & self, PyObject * & other); + static int unwrap_pow_args(PyObject *, PyObject *, PyObject *, + PyObject * &, PyObject * &, PyObject * &); + + PyObject * object; + PyObject * self; + }; +} + } PY_BEGIN_CONVERSION_NAMESPACE @@ -289,6 +324,9 @@ PyObject* to_python(const T& x) return py_extension_class_converters(py::Type()).to_python(x); } +inline PyObject * to_python(py::detail::operator_dispatcher * n) { return n; } + + PY_END_CONVERSION_NAMESPACE namespace py { @@ -307,22 +345,66 @@ class ReadOnlySetattrFunction : public Function String m_name; }; +enum operator_id +{ + op_add = 0x1, + op_sub = 0x2, + op_mul = 0x4, + op_div = 0x8, + op_mod = 0x10, + op_divmod =0x20, + op_pow = 0x40, + op_lshift = 0x80, + op_rshift = 0x100, + op_and = 0x200, + op_xor = 0x400, + op_or = 0x800, + op_neg = 0x1000, + op_pos = 0x2000, + op_abs = 0x4000, + op_invert = 0x8000, + op_int = 0x10000, + op_long = 0x20000, + op_float = 0x40000, + op_str = 0x80000, + op_cmp = 0x100000 +}; + namespace detail { - template - struct DefineConversion - { - static void * upcast_ptr(void * v) - { - return static_cast(static_cast(v)); - } + struct auto_operand {}; - static void * downcast_ptr(void * v) - { - return dynamic_cast(static_cast(v)); - } - }; + template + struct define_operator; + +} + +template +struct operators {}; + +template +struct left_operand {}; + +template +struct right_operand {}; + +namespace detail +{ + + template + struct DefineConversion + { + static void * upcast_ptr(void * v) + { + return static_cast(static_cast(v)); + } + + static void * downcast_ptr(void * v) + { + return dynamic_cast(static_cast(v)); + } + }; } @@ -371,6 +453,299 @@ class ExtensionClass """, args) + """ + + // export homogeneous operators (type of both lhs and rhs is 'operator') + // usage: foo_class.def(py::operators<(py::op_add | py::op_sub), Foo>()); + template + void def(operators) + { + register_coerce(); + + if(which & py::op_add) + add_method(new typename detail::define_operator<(which & py::op_add)>:: + operator_function(), + detail::define_operator<(which & py::op_add)>::name()); + + if(which & py::op_sub) + add_method(new typename detail::define_operator<(which & py::op_sub)>:: + operator_function(), + detail::define_operator<(which & py::op_sub)>::name()); + + if(which & py::op_mul) + add_method(new typename detail::define_operator<(which & py::op_mul)>:: + operator_function(), + detail::define_operator<(which & py::op_mul)>::name()); + + if(which & py::op_div) + add_method(new typename detail::define_operator<(which & py::op_div)>:: + operator_function(), + detail::define_operator<(which & py::op_div)>::name()); + + if(which & py::op_mod) + add_method(new typename detail::define_operator<(which & py::op_mod)>:: + operator_function(), + detail::define_operator<(which & py::op_mod)>::name()); + + if(which & py::op_divmod) + add_method(new typename detail::define_operator<(which & py::op_divmod)>:: + operator_function(), + detail::define_operator<(which & py::op_divmod)>::name()); + + if(which & py::op_pow) + add_method(new typename detail::define_operator<(which & py::op_pow)>:: + operator_function(), + detail::define_operator<(which & py::op_pow)>::name()); + + if(which & py::op_lshift) + add_method(new typename detail::define_operator<(which & py::op_lshift)>:: + operator_function(), + detail::define_operator<(which & py::op_lshift)>::name()); + + if(which & py::op_rshift) + add_method(new typename detail::define_operator<(which & py::op_rshift)>:: + operator_function(), + detail::define_operator<(which & py::op_rshift)>::name()); + + if(which & py::op_and) + add_method(new typename detail::define_operator<(which & py::op_and)>:: + operator_function(), + detail::define_operator<(which & py::op_and)>::name()); + + if(which & py::op_xor) + add_method(new typename detail::define_operator<(which & py::op_xor)>:: + operator_function(), + detail::define_operator<(which & py::op_xor)>::name()); + + if(which & py::op_or) + add_method(new typename detail::define_operator<(which & py::op_or)>:: + operator_function(), + detail::define_operator<(which & py::op_or)>::name()); + + if(which & py::op_neg) + add_method(new typename detail::define_operator<(which & py::op_neg)>:: + operator_function(), + detail::define_operator<(which & py::op_neg)>::name()); + + if(which & py::op_pos) + add_method(new typename detail::define_operator<(which & py::op_pos)>:: + operator_function(), + detail::define_operator<(which & py::op_pos)>::name()); + + if(which & py::op_abs) + add_method(new typename detail::define_operator<(which & py::op_abs)>:: + operator_function(), + detail::define_operator<(which & py::op_abs)>::name()); + + if(which & py::op_invert) + add_method(new typename detail::define_operator<(which & py::op_invert)>:: + operator_function(), + detail::define_operator<(which & py::op_invert)>::name()); + + if(which & py::op_int) + add_method(new typename detail::define_operator<(which & py::op_int)>:: + operator_function(), + detail::define_operator<(which & py::op_int)>::name()); + + if(which & py::op_long) + add_method(new typename detail::define_operator<(which & py::op_long)>:: + operator_function(), + detail::define_operator<(which & py::op_long)>::name()); + + if(which & py::op_float) + add_method(new typename detail::define_operator<(which & py::op_float)>:: + operator_function(), + detail::define_operator<(which & py::op_float)>::name()); + + if(which & py::op_cmp) + add_method(new typename detail::define_operator<(which & py::op_cmp)>:: + operator_function(), + detail::define_operator<(which & py::op_cmp)>::name()); + + if(which & py::op_str) + add_method(new typename detail::define_operator<(which & py::op_str)>:: + operator_function(), + detail::define_operator<(which & py::op_str)>::name()); + + } + + // export homogeneous operators (type of both lhs and rhs is 'T const &') + // usage: foo_class.def(py::operators<(py::op_add | py::op_sub)>()); + template + void def(operators) + { + def(operators()); + } + + // export heterogeneous operators (type of lhs: 'left', of rhs: 'right') + // usage: foo_class.def(py::operators<(py::op_add | py::op_sub), Foo>(), + // py::right_operand()); + template + void def(operators, right_operand) + { + register_coerce(); + + if(which & py::op_add) + add_method(new typename detail::define_operator<(which & py::op_add)>:: + operator_function(), + detail::define_operator<(which & py::op_add)>::name()); + + if(which & py::op_sub) + add_method(new typename detail::define_operator<(which & py::op_sub)>:: + operator_function(), + detail::define_operator<(which & py::op_sub)>::name()); + + if(which & py::op_mul) + add_method(new typename detail::define_operator<(which & py::op_mul)>:: + operator_function(), + detail::define_operator<(which & py::op_mul)>::name()); + + if(which & py::op_div) + add_method(new typename detail::define_operator<(which & py::op_div)>:: + operator_function(), + detail::define_operator<(which & py::op_div)>::name()); + + if(which & py::op_mod) + add_method(new typename detail::define_operator<(which & py::op_mod)>:: + operator_function(), + detail::define_operator<(which & py::op_mod)>::name()); + + if(which & py::op_divmod) + add_method(new typename detail::define_operator<(which & py::op_divmod)>:: + operator_function(), + detail::define_operator<(which & py::op_divmod)>::name()); + + if(which & py::op_pow) + add_method(new typename detail::define_operator<(which & py::op_pow)>:: + operator_function(), + detail::define_operator<(which & py::op_pow)>::name()); + + if(which & py::op_lshift) + add_method(new typename detail::define_operator<(which & py::op_lshift)>:: + operator_function(), + detail::define_operator<(which & py::op_lshift)>::name()); + + if(which & py::op_rshift) + add_method(new typename detail::define_operator<(which & py::op_rshift)>:: + operator_function(), + detail::define_operator<(which & py::op_rshift)>::name()); + + if(which & py::op_and) + add_method(new typename detail::define_operator<(which & py::op_and)>:: + operator_function(), + detail::define_operator<(which & py::op_and)>::name()); + + if(which & py::op_xor) + add_method(new typename detail::define_operator<(which & py::op_xor)>:: + operator_function(), + detail::define_operator<(which & py::op_xor)>::name()); + + if(which & py::op_or) + add_method(new typename detail::define_operator<(which & py::op_or)>:: + operator_function(), + detail::define_operator<(which & py::op_or)>::name()); + + if(which & py::op_cmp) + add_method(new typename detail::define_operator<(which & py::op_cmp)>:: + operator_function(), + detail::define_operator<(which & py::op_cmp)>::name()); + + } + + // export heterogeneous operators (type of lhs: 'T const &', of rhs: 'right') + // usage: foo_class.def(py::operators<(py::op_add | py::op_sub)>(), + // py::right_operand()); + template + void def(operators, right_operand r) + { + def(operators(), r); + } + + // export heterogeneous reverse-argument operators + // (type of lhs: 'left', of rhs: 'right') + // usage: foo_class.def(py::operators<(py::op_add | py::op_sub), Foo>(), + // py::left_operand()); + template + void def(operators, left_operand) + { + register_coerce(); + + if(which & py::op_add) + add_method(new typename detail::define_operator<(which & py::op_add)>:: + roperator_function(), + detail::define_operator<(which & py::op_add)>::rname()); + + if(which & py::op_sub) + add_method(new typename detail::define_operator<(which & py::op_sub)>:: + roperator_function(), + detail::define_operator<(which & py::op_sub)>::rname()); + + if(which & py::op_mul) + add_method(new typename detail::define_operator<(which & py::op_mul)>:: + roperator_function(), + detail::define_operator<(which & py::op_mul)>::rname()); + + if(which & py::op_div) + add_method(new typename detail::define_operator<(which & py::op_div)>:: + roperator_function(), + detail::define_operator<(which & py::op_div)>::rname()); + + if(which & py::op_mod) + add_method(new typename detail::define_operator<(which & py::op_mod)>:: + roperator_function(), + detail::define_operator<(which & py::op_mod)>::rname()); + + if(which & py::op_divmod) + add_method(new typename detail::define_operator<(which & py::op_divmod)>:: + roperator_function(), + detail::define_operator<(which & py::op_divmod)>::rname()); + + if(which & py::op_pow) + add_method(new typename detail::define_operator<(which & py::op_pow)>:: + roperator_function(), + detail::define_operator<(which & py::op_pow)>::rname()); + + if(which & py::op_lshift) + add_method(new typename detail::define_operator<(which & py::op_lshift)>:: + roperator_function(), + detail::define_operator<(which & py::op_lshift)>::rname()); + + if(which & py::op_rshift) + add_method(new typename detail::define_operator<(which & py::op_rshift)>:: + roperator_function(), + detail::define_operator<(which & py::op_rshift)>::rname()); + + if(which & py::op_and) + add_method(new typename detail::define_operator<(which & py::op_and)>:: + roperator_function(), + detail::define_operator<(which & py::op_and)>::rname()); + + if(which & py::op_xor) + add_method(new typename detail::define_operator<(which & py::op_xor)>:: + roperator_function(), + detail::define_operator<(which & py::op_xor)>::rname()); + + if(which & py::op_or) + add_method(new typename detail::define_operator<(which & py::op_or)>:: + roperator_function(), + detail::define_operator<(which & py::op_or)>::rname()); + + if(which & py::op_cmp) + add_method(new typename detail::define_operator<(which & py::op_cmp)>:: + roperator_function(), + detail::define_operator<(which & py::op_cmp)>::rname()); + + } + + // export heterogeneous reverse-argument operators + // (type of lhs: 'left', of rhs: 'T const &') + // usage: foo_class.def(py::operators<(py::op_add | py::op_sub)>(), + // py::left_operand()); + template + void def(operators, left_operand l) + { + def(operators(), l); + } + // define a function that passes Python arguments and keywords // to C++ verbatim (as a 'Tuple const &' and 'Dict const &' // respectively). This is useful for manual argument passing. @@ -481,6 +856,8 @@ class ExtensionClass { this->add_constructor_object(InitFunction::create(sig)); } + + void register_coerce(); }; // A simple wrapper over a T which allows us to use ExtensionClass with a @@ -579,6 +956,11 @@ class ExtensionInstance : public Instance // Template function implementations // +namespace detail +{ + Tuple extension_class_coerce(PyObject * l, PyObject * r); +} + template ExtensionClass::ExtensionClass() : ExtensionClassBase(typeid(T).name()) @@ -593,6 +975,15 @@ ExtensionClass::ExtensionClass(const char* name) ClassRegistry::register_class(this); } +template +void ExtensionClass::register_coerce() +{ + Ptr coerce_fct = dict().get_item(String("__coerce__")); + + if(coerce_fct.get() == 0) // not yet defined + this->def(&py::detail::extension_class_coerce, "__coerce__"); +} + template inline std::vector const & diff --git a/test_extclass.py b/test_extclass.py index bacd9a3a..9304f9d3 100644 --- a/test_extclass.py +++ b/test_extclass.py @@ -878,6 +878,118 @@ test inheritB2 >>> raw2() 0 +========= test export of operators ========== + + >>> i = Int(2) + >>> j = i+i + >>> j.i() + 4 + >>> j = i-i + >>> j.i() + 0 + >>> j = i*i + >>> j.i() + 4 + >>> i>> cmp(i,i) + 0 + >>> k = Int(5) + >>> j = divmod(k, i) + >>> j[0].i() + 2 + >>> j[1].i() + 1 + >>> j = pow(i, k) + >>> j.i() + 32 + >>> j = pow(i, k, k) + >>> j.i() + 2 + >>> j = -i + >>> j.i() + -2 + >>> str(i) + '2' + >>> j = i/i + Traceback (innermost last): + TypeError: bad operand type(s) for / + >>> j = abs(i) + Traceback (innermost last): + TypeError: bad operand type for abs() + >>> j = i+1 + >>> j.i() + 3 + >>> j = i-1 + >>> j.i() + 1 + >>> j = i*1 + >>> j.i() + 2 + >>> i<1 + 0 + >>> cmp(i,1) + 1 + >>> j = pow(i, 5) + >>> j.i() + 32 + >>> j = pow(i, 5, k) + Traceback (innermost last): + TypeError: bad operand type(s) for pow() + >>> j = pow(i, 5, 5) + Traceback (innermost last): + TypeError: bad operand type(s) for pow() + >>> j = i/1 + Traceback (innermost last): + TypeError: bad operand type(s) for / + >>> j = 1+i + >>> j.i() + 3 + >>> j = 1-i + >>> j.i() + -1 + >>> j = 1*i + >>> j.i() + 2 + >>> 1>> cmp(1,i) + -1 + >>> j = 1/i + Traceback (innermost last): + TypeError: bad operand type(s) for / + >>> pow(1,i) + Traceback (innermost last): + TypeError: bad operand type(s) for pow() + +Test operator export to a subclass + >>> class Foo(Int): + ... def __init__(self, i): + ... Int.__init__(self, i) + ... def __str__(self): + ... return 'Foo: ' + str(self.i()) + ... def __coerce__(self, other): + ... return Int.__coerce__(self, other) + ... + >>> f = Foo(3) + >>> str(f) + 'Foo: 3' + >>> j = f * f + >>> j.i() + 9 + >>> j = f * i + >>> j.i() + 6 + >>> j = f * 5 + >>> j.i() + 15 + >>> j = i * f + >>> j.i() + 6 + >>> j = 5 * f + >>> j.i() + 15 + ========= Prove that the "phantom base class" issue is resolved ==========