diff --git a/include/boost/python/suite/indexing/slice_handler.hpp b/include/boost/python/suite/indexing/slice_handler.hpp index 38237f99..62a2e80d 100755 --- a/include/boost/python/suite/indexing/slice_handler.hpp +++ b/include/boost/python/suite/indexing/slice_handler.hpp @@ -26,81 +26,29 @@ #include #include -#include +#include #include #include -// #include +#include "slice.hpp" +#include "python_iterator.hpp" namespace indexing { - class slice : public boost::python::object - { - int mStart; - int mStep; - int mStop; - bool mLengthSet; - - void validate () const { - if (!mLengthSet) - { - PyErr_SetString (PyExc_RuntimeError - , "slice access attempted before setLength called"); - boost::python::throw_error_already_set(); - } - } - - public: - slice (boost::python::detail::borrowed_reference ref) - : boost::python::object (ref) - , mStart (0) - , mStep (0) - , mStop (0) - , mLengthSet (false) - { - if (!PySlice_Check (this->ptr())) - { - PyErr_SetString (PyExc_TypeError - , "slice constructor: passed a non-slice object"); - - boost::python::throw_error_already_set(); - } - - // - // *** WARNING *** - // - // The slice object is useless until setLength is called - // - } - - void setLength (int sequenceLength) - { - PySlice_GetIndices ((PySliceObject *) this->ptr() - , sequenceLength - , &mStart - , &mStop - , &mStep); - - mStart = std::max (0, std::min (sequenceLength, mStart)); - mStop = std::max (0, std::min (sequenceLength, mStop)); - - mLengthSet = true; - } - - int start() const { validate(); return mStart; } - int step() const { validate(); return mStep; } - int stop() const { validate(); return mStop; } - }; - template struct slice_handler { + static boost::python::object make_getitem (Policy const &); + static boost::python::object make_setitem (Policy const &); + + private: typedef typename Algorithms::container container; - typedef typename Algorithms::index_param index_param; - typedef typename Algorithms::value_type value_type; typedef typename Algorithms::reference reference; - class postcall_override + static boost::python::list get_slice (container &, slice); + static void set_slice (container &, slice, boost::python::object); + + struct postcall_override { // This class overrides our Policy's postcall function and // result_conveter to handle the list returned from get_slice. @@ -109,69 +57,240 @@ namespace indexing // original postcall to each element of the Python list returned // from get_slice. - Policy mBase; - - public: - postcall_override (Policy const &p) : mBase (p) { - } - - bool precall (PyObject *args) { - return mBase.precall (args); - } - - PyObject* postcall (PyObject *args, PyObject *result) { - int size = PyList_Size (result); - - for (int count = 0; count < size; ++count) - { - mBase.postcall (args, PyList_GetItem (result, count)); - } - - return result; - } - typedef boost::python::default_result_converter result_converter; + + postcall_override (Policy const &p); + + bool precall (PyObject *args); + PyObject* postcall (PyObject *args, PyObject *result); + + private: + Policy mBase; }; + }; - static boost::python::list get_slice (container &c, slice sl) + template struct maybe_insert { + template + static void apply (typename Algorithms::container & + , typename Algorithms::index_param + , typename Algorithms::value_param) { - typedef typename Policy::result_converter converter_type; - typedef typename Algorithms::reference reference; - typename boost::mpl::apply1::type converter; + PyErr_SetString (PyExc_TypeError + , "container does not support item insertion"); - boost::python::list result; - - sl.setLength (Algorithms::size(c)); - - int direction = (sl.step() > 0) ? 1 : ((sl.step() == 0) ? 0 : -1); - - for (int index = sl.start() - ; ((sl.stop() - index) * direction) > 0 - ; index += sl.step()) - { - result.append - (boost::python::handle<> - (converter (Algorithms::get (c, index)))); - } - - return result; + boost::python::throw_error_already_set (); } + }; - static boost::python::object make_getitem (Policy const &policy) + template<> struct maybe_insert { + template + static void apply (typename Algorithms::container &c + , typename Algorithms::index_param i + , typename Algorithms::value_param v) { - return boost::python::make_function - (get_slice, postcall_override (policy)); + Algorithms::insert (c, i, v); + } + }; + + template struct maybe_erase { + template + static void apply (typename Algorithms::container & + , typename Algorithms::index_param + , typename Algorithms::index_param) + { + PyErr_SetString (PyExc_TypeError + , "container does not support item deletion"); + + boost::python::throw_error_already_set (); + } + }; + + template<> struct maybe_erase { + template + static void apply (typename Algorithms::container &c + , typename Algorithms::index_param from + , typename Algorithms::index_param to) + { + Algorithms::erase (c, from, to); } }; } -namespace boost { namespace python { namespace converter { - // Specialized converter to handle PySlice_Type objects - template<> - struct object_manager_traits - : pytype_object_manager_traits<&PySlice_Type, ::indexing::slice> - { - }; -}}} +///////////////////////////////////////////////////////////////////////////// +// postcall_override constructor +///////////////////////////////////////////////////////////////////////////// + +template +indexing::slice_handler +::postcall_override::postcall_override (Policy const &p) + : mBase (p) +{ +} + +///////////////////////////////////////////////////////////////////////////// +// precall forwarder +///////////////////////////////////////////////////////////////////////////// + +template +bool +indexing::slice_handler +::postcall_override::precall (PyObject *args) +{ + return mBase.precall (args); +} + +///////////////////////////////////////////////////////////////////////////// +// Apply base postcall to each element of the list returend by get_slice +///////////////////////////////////////////////////////////////////////////// + +template +PyObject * +indexing::slice_handler +::postcall_override::postcall (PyObject *args, PyObject *result) +{ + int size = PyList_Size (result); + + for (int count = 0; count < size; ++count) + { + mBase.postcall (args, PyList_GetItem (result, count)); + } + + return result; +} + +///////////////////////////////////////////////////////////////////////////// +// Return a function object that implements the slice version of __getitem__ +///////////////////////////////////////////////////////////////////////////// + +template +boost::python::object +indexing::slice_handler +::make_getitem (Policy const &policy) +{ + return boost::python::make_function (get_slice, postcall_override (policy)); +} + +///////////////////////////////////////////////////////////////////////////// +// Return a function object that implements the slice version of __setitem__ +///////////////////////////////////////////////////////////////////////////// + +template +boost::python::object +indexing::slice_handler +::make_setitem (Policy const &policy) +{ + // should we try to get funky with policy::precall? + return boost::python::make_function (set_slice, policy); +} + +///////////////////////////////////////////////////////////////////////////// +// Implementation for the slice version of __getitem__ +///////////////////////////////////////////////////////////////////////////// + +template +boost::python::list +indexing::slice_handler +::get_slice (container &c, slice sl) +{ + typedef typename Policy::result_converter converter_type; + typedef typename Algorithms::reference reference; + typename boost::mpl::apply1::type converter; + + boost::python::list result; + + sl.setLength (Algorithms::size(c)); + + for (int index = sl.start(); sl.inRange (index); index += sl.step()) + { + // Apply the result converter (only) to each element before + // appending. postcall is done in postcall_override + + result.append + (boost::python::handle<> + (converter (Algorithms::get (c, index)))); + } + + return result; +} + +///////////////////////////////////////////////////////////////////////////// +// Implementation for the slice version of __setitem__ +///////////////////////////////////////////////////////////////////////////// + +template +void +indexing::slice_handler +::set_slice (container &c, slice sl, boost::python::object values) +{ + std::auto_ptr iterPtr (make_iterator (values)); + + if (!iterPtr.get()) + { + PyErr_SetString (PyExc_TypeError + , "Type assigned to slice must be a sequence"); + + boost::python::throw_error_already_set(); + } + + typedef typename Algorithms::container_traits traits; + typedef boost::python::extract extractor; + + // Note: any error during this operation will probably leave the + // container partially updated. This can occur (for example) if the + // replacement sequence is of a different length to the original + // slice and the container does not support insertion/deletion. + // This could be prevented if the length of the replacement sequence + // is known in advance (via __len__, for example) but not otherwise. + + sl.setLength (Algorithms::size (c)); // Current length of our container + int index = sl.start(); // Index in our container for update + + // Overwrite and/or insert elements + while (iterPtr->next()) + { + if (sl.inRange (index)) + { + Algorithms::assign (c, index, extractor (iterPtr->current())); + } + + else if (sl.step() != 1) + { + PyErr_SetString (PyExc_ValueError + , "attempt to insert via extended slice"); + + boost::python::throw_error_already_set (); + } + + else + { + // Could optimize this in some cases (i.e. if the length of + // the replacement sequence is known) + + maybe_insert + ::template apply (c, index + , extractor (iterPtr->current())); + } + + index += sl.step(); + } + + // Erase any remaining elements in the slice + if (sl.inRange(index)) + { + if (sl.step() != 1) + { + PyErr_SetString (PyExc_ValueError + , "attempt to erase via extended slice"); + + boost::python::throw_error_already_set (); + } + + else + { + maybe_erase + ::template apply (c, index, sl.stop()); + } + } +} #endif // slice_handler_rmg_20030909_included