summaryrefslogtreecommitdiff
path: root/boost/python/slice.hpp
blob: 19f316a1e788a0e85037b23bf74920617b9c843c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
#ifndef BOOST_PYTHON_SLICE_JDB20040105_HPP
#define BOOST_PYTHON_SLICE_JDB20040105_HPP

// Copyright (c) 2004 Jonathan Brandmeyer
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file 
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <boost/python/detail/prefix.hpp>
#include <boost/config.hpp>
#include <boost/python/object.hpp>
#include <boost/python/extract.hpp>
#include <boost/python/converter/pytype_object_mgr_traits.hpp>

#include <boost/iterator/iterator_traits.hpp>

#include <iterator>
#include <algorithm>

namespace boost { namespace python {

namespace detail
{
  class BOOST_PYTHON_DECL slice_base : public object
  {
   public:
      // Get the Python objects associated with the slice.  In principle, these 
      // may be any arbitrary Python type, but in practice they are usually 
      // integers.  If one or more parameter is ommited in the Python expression 
      // that created this slice, than that parameter is None here, and compares 
      // equal to a default-constructed boost::python::object.
      // If a user-defined type wishes to support slicing, then support for the 
      // special meaning associated with negative indices is up to the user.
      object start() const;
      object stop() const;
      object step() const;
        
   protected:
      explicit slice_base(PyObject*, PyObject*, PyObject*);

      BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(slice_base, object)
  };
}

class slice : public detail::slice_base
{
    typedef detail::slice_base base;
 public:
    // Equivalent to slice(::)
    slice() : base(0,0,0) {}

    // Each argument must be slice_nil, or implicitly convertable to object.
    // They should normally be integers.
    template<typename Integer1, typename Integer2>
    slice( Integer1 start, Integer2 stop)
        : base( object(start).ptr(), object(stop).ptr(), 0 )
    {}
    
    template<typename Integer1, typename Integer2, typename Integer3>
    slice( Integer1 start, Integer2 stop, Integer3 stride)
        : base( object(start).ptr(), object(stop).ptr(), object(stride).ptr() )
    {}
        
    // The following algorithm is intended to automate the process of 
    // determining a slice range when you want to fully support negative
    // indices and non-singular step sizes.  Its functionallity is simmilar to 
    // PySlice_GetIndicesEx() in the Python/C API, but tailored for C++ users.
    // This template returns a slice::range struct that, when used in the 
    // following iterative loop, will traverse a slice of the function's
    // arguments.
    // while (start != end) { 
    //     do_foo(...); 
    //     std::advance( start, step); 
    // }
    // do_foo(...); // repeat exactly once more.
    
    // Arguments: a [begin, end) pair of STL-conforming random-access iterators.
        
    // Return: slice::range, where start and stop define a _closed_ interval
    // that covers at most [begin, end-1] of the provided arguments, and a step 
    // that is non-zero.
    
    // Throws: error_already_set() if any of the indices are neither None nor 
    //   integers, or the slice has a step value of zero.
    // std::invalid_argument if the resulting range would be empty.  Normally, 
    //   you should catch this exception and return an empty sequence of the
    //   appropriate type.
    
    // Performance: constant time for random-access iterators.
    
    // Rationale: 
    //   closed-interval: If an open interval were used, then for a non-singular
    //     value for step, the required state for the end iterator could be 
    //     beyond the one-past-the-end postion of the specified range.  While 
    //     probably harmless, the behavior of STL-conforming iterators is 
    //     undefined in this case.
    //   exceptions on zero-length range: It is impossible to define a closed 
    //     interval over an empty range, so some other form of error checking 
    //     would have to be used by the user to prevent undefined behavior.  In
    //     the case where the user fails to catch the exception, it will simply
    //     be translated to Python by the default exception handling mechanisms.

    template<typename RandomAccessIterator>
    struct range
    {
        RandomAccessIterator start;
        RandomAccessIterator stop;
        typename iterator_difference<RandomAccessIterator>::type step;
    };
    
    template<typename RandomAccessIterator>
    slice::range<RandomAccessIterator>
    get_indices( const RandomAccessIterator& begin, 
        const RandomAccessIterator& end) const
    {
        // This is based loosely on PySlice_GetIndicesEx(), but it has been 
        // carefully crafted to ensure that these iterators never fall out of
        // the range of the container.
        slice::range<RandomAccessIterator> ret;
        
        typedef typename iterator_difference<RandomAccessIterator>::type difference_type;
        difference_type max_dist = boost::detail::distance(begin, end);

        object slice_start = this->start();
        object slice_stop = this->stop();
        object slice_step = this->step();
        
        // Extract the step.
        if (slice_step == object()) {
            ret.step = 1;
        }
        else {
            ret.step = extract<long>( slice_step);
            if (ret.step == 0) {
                PyErr_SetString( PyExc_IndexError, "step size cannot be zero.");
                throw_error_already_set();
            }
        }
        
        // Setup the start iterator.
        if (slice_start == object()) {
            if (ret.step < 0) {
                ret.start = end;
                --ret.start;
            }
            else
                ret.start = begin;
        }
        else {
            difference_type i = extract<long>( slice_start);
            if (i >= max_dist && ret.step > 0)
                    throw std::invalid_argument( "Zero-length slice");
            if (i >= 0) {
                ret.start = begin;
                BOOST_USING_STD_MIN();
                std::advance( ret.start, min BOOST_PREVENT_MACRO_SUBSTITUTION(i, max_dist-1));
            }
            else {
                if (i < -max_dist && ret.step < 0)
                    throw std::invalid_argument( "Zero-length slice");
                ret.start = end;
                // Advance start (towards begin) not farther than begin.
                std::advance( ret.start, (-i < max_dist) ? i : -max_dist );
            }
        }
        
        // Set up the stop iterator.  This one is a little trickier since slices
        // define a [) range, and we are returning a [] range.
        if (slice_stop == object()) {
            if (ret.step < 0) {
                ret.stop = begin;
            }
            else {
                ret.stop = end;
                std::advance( ret.stop, -1);
            }
        }
        else {
            difference_type i = extract<long>(slice_stop);
            // First, branch on which direction we are going with this.
            if (ret.step < 0) {
                if (i+1 >= max_dist || i == -1)
                    throw std::invalid_argument( "Zero-length slice");
                
                if (i >= 0) {
                    ret.stop = begin;
                    std::advance( ret.stop, i+1);
                }
                else { // i is negative, but more negative than -1.
                    ret.stop = end;
                    std::advance( ret.stop, (-i < max_dist) ? i : -max_dist);
                }
            }
            else { // stepping forward
                if (i == 0 || -i >= max_dist)
                    throw std::invalid_argument( "Zero-length slice");
                
                if (i > 0) {
                    ret.stop = begin;
                    std::advance( ret.stop, (std::min)( i-1, max_dist-1));
                }
                else { // i is negative, but not more negative than -max_dist
                    ret.stop = end;
                    std::advance( ret.stop, i-1);
                }
            }
        }
        
        // Now the fun part, handling the possibilites surrounding step.
        // At this point, step has been initialized, ret.stop, and ret.step
        // represent the widest possible range that could be traveled
        // (inclusive), and final_dist is the maximum distance covered by the
        // slice.
        typename iterator_difference<RandomAccessIterator>::type final_dist = 
            boost::detail::distance( ret.start, ret.stop);
        
        // First case, if both ret.start and ret.stop are equal, then step
        // is irrelevant and we can return here.
        if (final_dist == 0)
            return ret;
        
        // Second, if there is a sign mismatch, than the resulting range and 
        // step size conflict: std::advance( ret.start, ret.step) goes away from
        // ret.stop.
        if ((final_dist > 0) != (ret.step > 0))
            throw std::invalid_argument( "Zero-length slice.");
        
        // Finally, if the last step puts us past the end, we move ret.stop
        // towards ret.start in the amount of the remainder.
        // I don't remember all of the oolies surrounding negative modulii,
        // so I am handling each of these cases separately.
        if (final_dist < 0) {
            difference_type remainder = -final_dist % -ret.step;
            std::advance( ret.stop, remainder);
        }
        else {
            difference_type remainder = final_dist % ret.step;
            std::advance( ret.stop, -remainder);
        }
        
        return ret;
    }

    // Incorrect spelling. DO NOT USE. Only here for backward compatibility.
    // Corrected 2011-06-14.
    template<typename RandomAccessIterator>
    slice::range<RandomAccessIterator>
    get_indicies( const RandomAccessIterator& begin, 
        const RandomAccessIterator& end) const
    {
        return get_indices(begin, end);
    }
        
 public:
    // This declaration, in conjunction with the specialization of 
    // object_manager_traits<> below, allows C++ functions accepting slice 
    // arguments to be called from from Python.  These constructors should never
    // be used in client code.
    BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(slice, detail::slice_base)
};


namespace converter {

template<>
struct object_manager_traits<slice>
    : pytype_object_manager_traits<&PySlice_Type, slice>
{
};
    
} // !namesapce converter

} } // !namespace ::boost::python


#endif // !defined BOOST_PYTHON_SLICE_JDB20040105_HPP