sl@0: #ifndef BOOST_PYTHON_SLICE_JDB20040105_HPP sl@0: #define BOOST_PYTHON_SLICE_JDB20040105_HPP sl@0: sl@0: // Copyright (c) 2004 Jonathan Brandmeyer sl@0: // Use, modification and distribution are subject to the sl@0: // Boost Software License, Version 1.0. (See accompanying file sl@0: // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) sl@0: sl@0: #include sl@0: #include sl@0: #include sl@0: #include sl@0: #include sl@0: sl@0: #include sl@0: sl@0: #include sl@0: #include sl@0: sl@0: namespace boost { namespace python { sl@0: sl@0: namespace detail sl@0: { sl@0: class BOOST_PYTHON_DECL slice_base : public object sl@0: { sl@0: public: sl@0: // Get the Python objects associated with the slice. In principle, these sl@0: // may be any arbitrary Python type, but in practice they are usually sl@0: // integers. If one or more parameter is ommited in the Python expression sl@0: // that created this slice, than that parameter is None here, and compares sl@0: // equal to a default-constructed boost::python::object. sl@0: // If a user-defined type wishes to support slicing, then support for the sl@0: // special meaning associated with negative indicies is up to the user. sl@0: object start() const; sl@0: object stop() const; sl@0: object step() const; sl@0: sl@0: protected: sl@0: explicit slice_base(PyObject*, PyObject*, PyObject*); sl@0: sl@0: BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(slice_base, object) sl@0: }; sl@0: } sl@0: sl@0: class slice : public detail::slice_base sl@0: { sl@0: typedef detail::slice_base base; sl@0: public: sl@0: // Equivalent to slice(::) sl@0: slice() : base(0,0,0) {} sl@0: sl@0: // Each argument must be slice_nil, or implicitly convertable to object. sl@0: // They should normally be integers. sl@0: template sl@0: slice( Integer1 start, Integer2 stop) sl@0: : base( object(start).ptr(), object(stop).ptr(), 0 ) sl@0: {} sl@0: sl@0: template sl@0: slice( Integer1 start, Integer2 stop, Integer3 stride) sl@0: : base( object(start).ptr(), object(stop).ptr(), object(stride).ptr() ) sl@0: {} sl@0: sl@0: // The following algorithm is intended to automate the process of sl@0: // determining a slice range when you want to fully support negative sl@0: // indicies and non-singular step sizes. Its functionallity is simmilar to sl@0: // PySlice_GetIndicesEx() in the Python/C API, but tailored for C++ users. sl@0: // This template returns a slice::range struct that, when used in the sl@0: // following iterative loop, will traverse a slice of the function's sl@0: // arguments. sl@0: // while (start != end) { sl@0: // do_foo(...); sl@0: // std::advance( start, step); sl@0: // } sl@0: // do_foo(...); // repeat exactly once more. sl@0: sl@0: // Arguments: a [begin, end) pair of STL-conforming random-access iterators. sl@0: sl@0: // Return: slice::range, where start and stop define a _closed_ interval sl@0: // that covers at most [begin, end-1] of the provided arguments, and a step sl@0: // that is non-zero. sl@0: sl@0: // Throws: error_already_set() if any of the indices are neither None nor sl@0: // integers, or the slice has a step value of zero. sl@0: // std::invalid_argument if the resulting range would be empty. Normally, sl@0: // you should catch this exception and return an empty sequence of the sl@0: // appropriate type. sl@0: sl@0: // Performance: constant time for random-access iterators. sl@0: sl@0: // Rationale: sl@0: // closed-interval: If an open interval were used, then for a non-singular sl@0: // value for step, the required state for the end iterator could be sl@0: // beyond the one-past-the-end postion of the specified range. While sl@0: // probably harmless, the behavior of STL-conforming iterators is sl@0: // undefined in this case. sl@0: // exceptions on zero-length range: It is impossible to define a closed sl@0: // interval over an empty range, so some other form of error checking sl@0: // would have to be used by the user to prevent undefined behavior. In sl@0: // the case where the user fails to catch the exception, it will simply sl@0: // be translated to Python by the default exception handling mechanisms. sl@0: sl@0: template sl@0: struct range sl@0: { sl@0: RandomAccessIterator start; sl@0: RandomAccessIterator stop; sl@0: typename iterator_difference::type step; sl@0: }; sl@0: sl@0: template sl@0: slice::range sl@0: get_indicies( const RandomAccessIterator& begin, sl@0: const RandomAccessIterator& end) const sl@0: { sl@0: // This is based loosely on PySlice_GetIndicesEx(), but it has been sl@0: // carefully crafted to ensure that these iterators never fall out of sl@0: // the range of the container. sl@0: slice::range ret; sl@0: sl@0: typedef typename iterator_difference::type difference_type; sl@0: difference_type max_dist = boost::detail::distance(begin, end); sl@0: sl@0: object slice_start = this->start(); sl@0: object slice_stop = this->stop(); sl@0: object slice_step = this->step(); sl@0: sl@0: // Extract the step. sl@0: if (slice_step == object()) { sl@0: ret.step = 1; sl@0: } sl@0: else { sl@0: ret.step = extract( slice_step); sl@0: if (ret.step == 0) { sl@0: PyErr_SetString( PyExc_IndexError, "step size cannot be zero."); sl@0: throw_error_already_set(); sl@0: } sl@0: } sl@0: sl@0: // Setup the start iterator. sl@0: if (slice_start == object()) { sl@0: if (ret.step < 0) { sl@0: ret.start = end; sl@0: --ret.start; sl@0: } sl@0: else sl@0: ret.start = begin; sl@0: } sl@0: else { sl@0: difference_type i = extract( slice_start); sl@0: if (i >= max_dist && ret.step > 0) sl@0: throw std::invalid_argument( "Zero-length slice"); sl@0: if (i >= 0) { sl@0: ret.start = begin; sl@0: BOOST_USING_STD_MIN(); sl@0: std::advance( ret.start, min BOOST_PREVENT_MACRO_SUBSTITUTION(i, max_dist-1)); sl@0: } sl@0: else { sl@0: if (i < -max_dist && ret.step < 0) sl@0: throw std::invalid_argument( "Zero-length slice"); sl@0: ret.start = end; sl@0: // Advance start (towards begin) not farther than begin. sl@0: std::advance( ret.start, (-i < max_dist) ? i : -max_dist ); sl@0: } sl@0: } sl@0: sl@0: // Set up the stop iterator. This one is a little trickier since slices sl@0: // define a [) range, and we are returning a [] range. sl@0: if (slice_stop == object()) { sl@0: if (ret.step < 0) { sl@0: ret.stop = begin; sl@0: } sl@0: else { sl@0: ret.stop = end; sl@0: std::advance( ret.stop, -1); sl@0: } sl@0: } sl@0: else { sl@0: difference_type i = extract(slice_stop); sl@0: // First, branch on which direction we are going with this. sl@0: if (ret.step < 0) { sl@0: if (i+1 >= max_dist || i == -1) sl@0: throw std::invalid_argument( "Zero-length slice"); sl@0: sl@0: if (i >= 0) { sl@0: ret.stop = begin; sl@0: std::advance( ret.stop, i+1); sl@0: } sl@0: else { // i is negative, but more negative than -1. sl@0: ret.stop = end; sl@0: std::advance( ret.stop, (-i < max_dist) ? i : -max_dist); sl@0: } sl@0: } sl@0: else { // stepping forward sl@0: if (i == 0 || -i >= max_dist) sl@0: throw std::invalid_argument( "Zero-length slice"); sl@0: sl@0: if (i > 0) { sl@0: ret.stop = begin; sl@0: std::advance( ret.stop, (std::min)( i-1, max_dist-1)); sl@0: } sl@0: else { // i is negative, but not more negative than -max_dist sl@0: ret.stop = end; sl@0: std::advance( ret.stop, i-1); sl@0: } sl@0: } sl@0: } sl@0: sl@0: // Now the fun part, handling the possibilites surrounding step. sl@0: // At this point, step has been initialized, ret.stop, and ret.step sl@0: // represent the widest possible range that could be traveled sl@0: // (inclusive), and final_dist is the maximum distance covered by the sl@0: // slice. sl@0: typename iterator_difference::type final_dist = sl@0: boost::detail::distance( ret.start, ret.stop); sl@0: sl@0: // First case, if both ret.start and ret.stop are equal, then step sl@0: // is irrelevant and we can return here. sl@0: if (final_dist == 0) sl@0: return ret; sl@0: sl@0: // Second, if there is a sign mismatch, than the resulting range and sl@0: // step size conflict: std::advance( ret.start, ret.step) goes away from sl@0: // ret.stop. sl@0: if ((final_dist > 0) != (ret.step > 0)) sl@0: throw std::invalid_argument( "Zero-length slice."); sl@0: sl@0: // Finally, if the last step puts us past the end, we move ret.stop sl@0: // towards ret.start in the amount of the remainder. sl@0: // I don't remember all of the oolies surrounding negative modulii, sl@0: // so I am handling each of these cases separately. sl@0: if (final_dist < 0) { sl@0: difference_type remainder = -final_dist % -ret.step; sl@0: std::advance( ret.stop, remainder); sl@0: } sl@0: else { sl@0: difference_type remainder = final_dist % ret.step; sl@0: std::advance( ret.stop, -remainder); sl@0: } sl@0: sl@0: return ret; sl@0: } sl@0: sl@0: public: sl@0: // This declaration, in conjunction with the specialization of sl@0: // object_manager_traits<> below, allows C++ functions accepting slice sl@0: // arguments to be called from from Python. These constructors should never sl@0: // be used in client code. sl@0: BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(slice, detail::slice_base) sl@0: }; sl@0: sl@0: sl@0: namespace converter { sl@0: sl@0: template<> sl@0: struct object_manager_traits sl@0: : pytype_object_manager_traits<&PySlice_Type, slice> sl@0: { sl@0: }; sl@0: sl@0: } // !namesapce converter sl@0: sl@0: } } // !namespace ::boost::python sl@0: sl@0: sl@0: #endif // !defined BOOST_PYTHON_SLICE_JDB20040105_HPP