diff --git a/docs/api/fexpr/__invert__.rst b/docs/api/fexpr/__invert__.rst index 76eab0bca8..df14cd274f 100644 --- a/docs/api/fexpr/__invert__.rst +++ b/docs/api/fexpr/__invert__.rst @@ -1,6 +1,6 @@ .. xmethod:: datatable.FExpr.__invert__ - :src: src/core/expr/fexpr.cc PyFExpr::nb__invert__ + :src: src/core/expr/funary/unary_invert.cc PyFExpr::nb__invert__ __invert__(x) -- diff --git a/docs/api/fexpr/__neg__.rst b/docs/api/fexpr/__neg__.rst index 5b843f2d40..64d1f11023 100644 --- a/docs/api/fexpr/__neg__.rst +++ b/docs/api/fexpr/__neg__.rst @@ -1,6 +1,6 @@ .. xmethod:: datatable.FExpr.__neg__ - :src: src/core/expr/fexpr.cc PyFExpr::nb__neg__ + :src: src/core/expr/funary/unary_minus.cc PyFExpr::nb__neg__ __neg__(x) -- diff --git a/docs/api/fexpr/__pos__.rst b/docs/api/fexpr/__pos__.rst index 753681c68f..720c898eef 100644 --- a/docs/api/fexpr/__pos__.rst +++ b/docs/api/fexpr/__pos__.rst @@ -1,6 +1,6 @@ .. xmethod:: datatable.FExpr.__pos__ - :src: src/core/expr/fexpr.cc PyFExpr::nb__pos__ + :src: src/core/expr/funary/unary_plus.cc PyFExpr::nb__pos__ __pos__(x) -- diff --git a/src/core/expr/fexpr.cc b/src/core/expr/fexpr.cc index dfd59391cc..e975c310b4 100644 --- a/src/core/expr/fexpr.cc +++ b/src/core/expr/fexpr.cc @@ -171,13 +171,6 @@ oobj PyFExpr::m__getitem__(py::robj item) { //----- Basic arithmetics ------------------------------------------------------ - -static oobj make_unexpr(dt::expr::Op op, const PyObject* self) { - return robj(Expr_Type).call({ - oint(static_cast(op)), - otuple{oobj(self)}}); -} - static oobj make_binexpr(dt::expr::Op op, robj lhs, robj rhs) { return robj(Expr_Type).call({ oint(static_cast(op)), @@ -215,21 +208,6 @@ bool PyFExpr::nb__bool__() { " f.B != 0\n"; } -oobj PyFExpr::nb__invert__() { - return make_unexpr(dt::expr::Op::UINVERT, this); -} - -oobj PyFExpr::nb__neg__() { - return make_unexpr(dt::expr::Op::UMINUS, this); -} - -oobj PyFExpr::nb__pos__() { - return make_unexpr(dt::expr::Op::UPLUS, this); -} - - - - //----- Other methods ---------------------------------------------------------- oobj PyFExpr::extend(const XArgs& args) { diff --git a/src/core/expr/fexpr.h b/src/core/expr/fexpr.h index b292bbb1d8..fd0ed2a23a 100644 --- a/src/core/expr/fexpr.h +++ b/src/core/expr/fexpr.h @@ -163,6 +163,9 @@ class PyFExpr : public py::XObject { static py::oobj nb__sub__ (py::robj, py::robj); static py::oobj nb__mul__ (py::robj, py::robj); static py::oobj nb__floordiv__(py::robj, py::robj); + static py::oobj nb__invert__ (py::robj); + static py::oobj nb__neg__ (py::robj); + static py::oobj nb__pos__ (py::robj); static py::oobj nb__truediv__ (py::robj, py::robj); static py::oobj nb__mod__ (py::robj, py::robj); static py::oobj nb__pow__ (py::robj, py::robj, py::robj); @@ -172,9 +175,6 @@ class PyFExpr : public py::XObject { static py::oobj nb__lshift__ (py::robj, py::robj); static py::oobj nb__rshift__ (py::robj, py::robj); bool nb__bool__(); - py::oobj nb__invert__(); - py::oobj nb__neg__(); - py::oobj nb__pos__(); py::oobj len(); // [DEPRECATED] py::oobj re_match(const py::XArgs&); // [DEPRECATED] diff --git a/src/core/expr/funary/umaker.cc b/src/core/expr/funary/umaker.cc index 0bd9867f8e..15b6d45e22 100644 --- a/src/core/expr/funary/umaker.cc +++ b/src/core/expr/funary/umaker.cc @@ -60,11 +60,6 @@ static const umaker_ptr& get_umaker(Op opcode, SType stype) { umaker_ptr resolve_op(Op opcode, SType stype) { switch (opcode) { - // Basic - case Op::UPLUS: return resolve_op_uplus(stype); - case Op::UMINUS: return resolve_op_uminus(stype); - case Op::UINVERT: return resolve_op_uinvert(stype); - // Math: trigonometric case Op::SIN: return resolve_op_sin(stype); case Op::COS: return resolve_op_cos(stype); diff --git a/src/core/expr/funary/umaker.h b/src/core/expr/funary/umaker.h index 30031729a9..c13ae48ef2 100644 --- a/src/core/expr/funary/umaker.h +++ b/src/core/expr/funary/umaker.h @@ -81,11 +81,6 @@ using umaker_ptr = std::unique_ptr; // Main resolver, calls individual-op resolvers below umaker_ptr resolve_op(Op, SType); -// Basic -umaker_ptr resolve_op_uplus(SType); -umaker_ptr resolve_op_uminus(SType); -umaker_ptr resolve_op_uinvert(SType); - // Trigonometric umaker_ptr resolve_op_sin(SType); umaker_ptr resolve_op_cos(SType); diff --git a/src/core/expr/funary/unary_invert.cc b/src/core/expr/funary/unary_invert.cc new file mode 100644 index 0000000000..c616311b51 --- /dev/null +++ b/src/core/expr/funary/unary_invert.cc @@ -0,0 +1,92 @@ +//------------------------------------------------------------------------------ +// Copyright 2022-2023 H2O.ai +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//------------------------------------------------------------------------------ +#include "column/const.h" +#include "column/func_unary.h" +#include "expr/fexpr_func_unary.h" +#include "python/xargs.h" +#include "stype.h" +namespace dt { +namespace expr { + + +class FExpr_UInvert : public FExpr_FuncUnary { + public: + using FExpr_FuncUnary::FExpr_FuncUnary; + + + std::string name() const override { + return "uinvert"; + } + + template + static inline T op_invert(T x) { + return ~x; + } + + static inline int8_t op_invert_bool(int8_t x) { + return !x; + } + /** + * Unary operator `~` acts as logical NOT on a boolean column, + * and as a bitwise inverse on integer columns. Integer promotions + * are not applied. The operator is not applicable to floating-point + * or string columns. + */ + Column evaluate1(Column&& col) const override{ + SType stype = col.stype(); + + switch (stype) { + case SType::VOID: + return Column(new ConstNa_ColumnImpl(col.nrows(), SType::VOID)); + case SType::BOOL: + return Column(new FuncUnary1_ColumnImpl( + std::move(col), op_invert_bool, col.nrows(), SType::BOOL + )); + case SType::INT8: + return Column(new FuncUnary1_ColumnImpl( + std::move(col), op_invert, col.nrows(), SType::INT8 + )); + case SType::INT16: + return Column(new FuncUnary1_ColumnImpl( + std::move(col), op_invert, col.nrows(), SType::INT16 + )); + case SType::INT32: + return Column(new FuncUnary1_ColumnImpl( + std::move(col), op_invert, col.nrows(), SType::INT32 + )); + case SType::INT64: + return Column(new FuncUnary1_ColumnImpl( + std::move(col), op_invert, col.nrows(), SType::INT64 + )); + default: + throw TypeError() << "Cannot apply unary `operator ~` to a column with " + "stype `" << stype << "`"; + } + } +}; + +py::oobj PyFExpr::nb__invert__(py::robj lhs) { + return PyFExpr::make( + new FExpr_UInvert(as_fexpr(lhs))); +} + +}} // dt::expr diff --git a/src/core/expr/funary/unary_minus.cc b/src/core/expr/funary/unary_minus.cc new file mode 100644 index 0000000000..86c671d6cd --- /dev/null +++ b/src/core/expr/funary/unary_minus.cc @@ -0,0 +1,86 @@ +//------------------------------------------------------------------------------ +// Copyright 2022-2023 H2O.ai +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//------------------------------------------------------------------------------ +#include "column/const.h" +#include "column/func_unary.h" +#include "expr/fexpr_func_unary.h" +#include "python/xargs.h" +#include "stype.h" +namespace dt { +namespace expr { + + +class FExpr_UnaryMinus : public FExpr_FuncUnary { + public: + using FExpr_FuncUnary::FExpr_FuncUnary; + + + std::string name() const override { + return "uminus"; + } + + template + static inline T op_minus(T x) { + return -x; + } + + Column evaluate1(Column&& col) const override{ + SType stype = col.stype(); + + switch (stype) { + case SType::VOID: + return Column(new ConstNa_ColumnImpl(col.nrows(), SType::VOID)); + case SType::BOOL: + case SType::INT8: + case SType::INT16: + col.cast_inplace(SType::INT32); + return Column(new FuncUnary1_ColumnImpl( + std::move(col), op_minus, col.nrows(), SType::INT32 + )); + case SType::INT32: + return Column(new FuncUnary1_ColumnImpl( + std::move(col), op_minus, col.nrows(), SType::INT32 + )); + case SType::INT64: + return Column(new FuncUnary1_ColumnImpl( + std::move(col), op_minus, col.nrows(), SType::INT64 + )); + case SType::FLOAT32: + return Column(new FuncUnary1_ColumnImpl( + std::move(col), op_minus, col.nrows(), SType::FLOAT32 + )); + case SType::FLOAT64: + return Column(new FuncUnary1_ColumnImpl( + std::move(col), op_minus, col.nrows(), SType::FLOAT64 + )); + default: + throw TypeError() << "Cannot apply unary `operator -` to a column with " + "stype `" << stype << "`"; + } + } +}; + +py::oobj PyFExpr::nb__neg__(py::robj src) { + return PyFExpr::make( + new FExpr_UnaryMinus(as_fexpr(src))); +} + +}} // dt::expr diff --git a/src/core/expr/funary/unary_plus.cc b/src/core/expr/funary/unary_plus.cc new file mode 100644 index 0000000000..26061ccae5 --- /dev/null +++ b/src/core/expr/funary/unary_plus.cc @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +// Copyright 2022-2023 H2O.ai +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//------------------------------------------------------------------------------ +#include "column/const.h" +#include "expr/fexpr_column.h" +#include "expr/fexpr_func_unary.h" +#include "python/xargs.h" +#include "stype.h" +namespace dt { +namespace expr { + + +class FExpr_UPlus : public FExpr_FuncUnary { + public: + using FExpr_FuncUnary::FExpr_FuncUnary; + + + std::string name() const override { + return "uplus"; + } + /** + * Unary operator `+` upcasts each numeric column to INT32, but + * otherwise keeps unmodified. The operator cannot be applied to + * string columns. + */ + Column evaluate1(Column&& col) const override{ + SType stype = col.stype(); + + switch (stype) { + case SType::VOID: + return Column(new ConstNa_ColumnImpl(col.nrows(), SType::VOID)); + case SType::BOOL: + case SType::INT8: + case SType::INT16: + col.cast_inplace(SType::INT32); + return std::move(col); + case SType::INT32: + case SType::INT64: + case SType::FLOAT32: + case SType::FLOAT64: + return std::move(col); + + default: + throw TypeError() << "Cannot apply unary `operator +` to a column with " + "stype `" << stype << "`"; + } + } +}; + +py::oobj PyFExpr::nb__pos__(py::robj src) { + return PyFExpr::make( + new FExpr_UPlus(as_fexpr(src))); +} + +}} // dt::expr diff --git a/src/core/python/xobject.h b/src/core/python/xobject.h index 22ba4f25eb..2cb7f30275 100644 --- a/src/core/python/xobject.h +++ b/src/core/python/xobject.h @@ -519,6 +519,17 @@ PyObject* _safe_unary(PyObject* self) noexcept { } } +template +PyObject* _safe_uunary(PyObject* self) noexcept { + auto cl = dt::CallLogger::unaryfn(self, OP); + try { + return METH(py::robj(self)).release(); + } catch (const std::exception& e) { + exception_to_python(e); + return nullptr; + } +} + template PyObject* _safe_binary(PyObject* self, PyObject* other) noexcept { @@ -763,12 +774,12 @@ PyObject* _safe_cmp(PyObject* x, PyObject* y, int op) noexcept { */ #define METHOD__NEG__(METH) \ - py::_safe_unary, \ + py::_safe_uunary, \ py::XTypeMaker::nb_negative_tag #define METHOD__POS__(METH) \ - py::_safe_unary, \ + py::_safe_uunary, \ py::XTypeMaker::nb_positive_tag @@ -778,7 +789,7 @@ PyObject* _safe_cmp(PyObject* x, PyObject* y, int op) noexcept { #define METHOD__INVERT__(METH) \ - py::_safe_unary, \ + py::_safe_uunary, \ py::XTypeMaker::nb_invert_tag