HiPipe  0.7.0
C++17 data pipeline with Python bindings.
range.hpp
1 /****************************************************************************
2  * hipipe library
3  * Copyright (c) 2017, Cognexa Solutions s.r.o.
4  * Copyright (c) 2018, Iterait a.s.
5  * Author(s) Filip Matzner
6  *
7  * This file is distributed under the MIT License.
8  * See the accompanying file LICENSE.txt for the complete license agreement.
9  ****************************************************************************/
10 
11 #pragma once
12 #include <hipipe/build_config.hpp>
13 #ifdef HIPIPE_BUILD_PYTHON
14 
15 #include <hipipe/core/python/utility/pyboost_is_registered.hpp>
16 
17 #include <boost/python.hpp>
18 #include <range/v3/core.hpp>
19 
20 #include <memory>
21 #include <stdexcept>
22 #include <string>
23 #include <typeinfo>
24 #include <vector>
25 
26 namespace hipipe::python {
27 
30 struct stop_iteration_exception : public std::runtime_error {
31  stop_iteration_exception()
32  : std::runtime_error{"stop iteration"}
33  { }
34 };
35 
37 inline void stop_iteration_translator(const stop_iteration_exception& x)
38 {
39  PyErr_SetNone(PyExc_StopIteration);
40 }
41 
60 template<typename Rng>
61 class range {
62 private:
63  std::shared_ptr<Rng> rng_ptr_;
64 
65  // register __len__ function if it is supported
66  CPP_template(int dummy = 0)(requires ranges::sized_range<const Rng>)
67  static void register_len(boost::python::class_<range<Rng>>& cls)
68  {
69  cls.def("__len__", &range<Rng>::len<>);
70  }
71  CPP_template(int dummy = 0)(requires !ranges::sized_range<const Rng>)
72  static void register_len(boost::python::class_<range<Rng>>&)
73  {
74  }
75 
76  // register __getitem__ function if it is supported
77  CPP_template(int dummy = 0)(requires ranges::random_access_range<const Rng>)
78  static void register_getitem(boost::python::class_<range<Rng>>& cls)
79  {
80  cls.def("__getitem__", &range<Rng>::getitem<>);
81  }
82  CPP_template(int dummy = 0)(requires !ranges::random_access_range<const Rng>)
83  static void register_getitem(boost::python::class_<range<Rng>>&)
84  {
85  }
86 
87  // function to register the type of this class in boost::python
88  // makes sure the type is registered only once
89  static void register_to_python()
90  {
91  namespace py = boost::python;
92 
93  if (!utility::is_registered<range<Rng>>()) {
94  std::string this_name = std::string("hipipe_") + typeid(range<Rng>).name();
95  py::class_<range<Rng>> cls{this_name.c_str(), py::no_init};
96  cls.def("__iter__", &range<Rng>::iter);
97  register_len(cls);
98  register_getitem(cls);
99 
100  py::class_<range<Rng>::iterator>{(this_name + "_iterator").c_str(), py::no_init}
101  .def("__next__", &range<Rng>::iterator::next)
102  .def("__iter__", &range<Rng>::iterator::iter);
103  };
104  }
105 
106 public:
107  class iterator {
108  private:
109  std::shared_ptr<Rng> rng_ptr_;
110  ranges::iterator_t<Rng> position_;
111  bool first_iteration_;
112 
113  public:
114  iterator() = default;
115 
116  // Construct iterator from a range.
117  explicit iterator(range& rng)
118  : rng_ptr_{rng.rng_ptr_},
119  position_{ranges::begin(*rng_ptr_)},
120  first_iteration_{true}
121  {
122  }
123 
124  // Return a copy of this iterator.
125  iterator iter()
126  {
127  return *this;
128  }
129 
130  // Return the next element in the range.
131  //
132  // \throws stop_iteration_exception if there are no more elements.
133  auto next()
134  {
135  // do not increment the iterator in the first iteration, just return *begin()
136  if (!first_iteration_ && position_ != ranges::end(*rng_ptr_)) ++position_;
137  first_iteration_ = false;
138  if (position_ == ranges::end(*rng_ptr_)) throw stop_iteration_exception();
139  return *position_;
140  }
141  };
142 
144  range() = default;
145 
147  explicit range(Rng rng)
148  : rng_ptr_{std::make_shared<Rng>(std::move(rng))}
149  {
150  register_to_python();
151  }
152 
154  range(std::shared_ptr<Rng> rng_ptr)
155  : rng_ptr_{std::move(rng_ptr)}
156  {
157  register_to_python();
158  }
159 
160  // Get python iterator.
161  iterator iter()
162  {
163  return iterator{*this};
164  }
165 
166  // Get an item or a slice.
167  //
168  // Note that when slicing, the data get copied.
169  CPP_template(int dummy = 0)(requires ranges::sized_range<const Rng>)
170  boost::python::object getitem(PyObject* idx_py) const
171  {
172  // handle slices
173  if (PySlice_Check(idx_py)) {
174  PySliceObject* slice = static_cast<PySliceObject*>(static_cast<void*>(idx_py));
175  if (slice->step != Py_None) {
176  throw std::logic_error("HiPipe python range does not support slice steps.");
177  }
178 
179  auto handle_index = [this](PyObject* idx_py, long def_val) {
180  long idx = def_val;
181  if (idx_py != Py_None) {
182  idx = boost::python::extract<long>(idx_py);
183  // reverse negative index
184  if (idx < 0) idx += this->len();
185  // if it is still negative, clip
186  if (idx < 0) idx = 0;
187  // handle index larger then len
188  if (idx > this->len()) idx = len();
189  }
190  return idx;
191  };
192  long start = handle_index(slice->start, 0);
193  long stop = handle_index(slice->stop, len());
194  if (start > stop) start = stop;
195 
196  using slice_data_type = std::vector<ranges::range_value_t<Rng>>;
197  slice_data_type slice_data{rng_ptr_->begin() + start, rng_ptr_->begin() + stop};
198  return boost::python::object{range<slice_data_type>{std::move(slice_data)}};
199  }
200 
201  // handle indices
202  long idx = boost::python::extract<long>(idx_py);
203  if (idx < 0) idx += len();
204  return boost::python::object{ranges::at(*rng_ptr_, idx)};
205  }
206 
207  // Get the size of the range.
208  CPP_template(int dummy = 0)(requires ranges::sized_range<const Rng>)
209  long len() const
210  {
211  return ranges::size(*rng_ptr_);
212  }
213 
214 }; // class range
215 
216 } // end namespace hipipe::python
217 
218 #endif // HIPIPE_BUILD_PYTHON
hipipe::utility::CPP_template
CPP_template(class Rng)(requires ranges
Unzips a range of tuples to a tuple of std::vectors.
Definition: tuple.hpp:255