1 //  (C) Copyright Joel de Guzman 2003.
2 //  Distributed under the Boost Software License, Version 1.0. (See
3 //  accompanying file LICENSE_1_0.txt or copy at
4 //  http://www.boost.org/LICENSE_1_0.txt)
5 
6 #ifndef INDEXING_SUITE_JDG20036_HPP
7 # define INDEXING_SUITE_JDG20036_HPP
8 
9 # include <boost/python/class.hpp>
10 # include <boost/python/def_visitor.hpp>
11 # include <boost/python/register_ptr_to_python.hpp>
12 # include <boost/python/suite/indexing/detail/indexing_suite_detail.hpp>
13 # include <boost/python/return_internal_reference.hpp>
14 # include <boost/python/iterator.hpp>
15 # include <boost/mpl/or.hpp>
16 # include <boost/mpl/not.hpp>
17 # include <boost/python/detail/type_traits.hpp>
18 
19 namespace boost { namespace python {
20 
21     // indexing_suite class. This class is the facade class for
22     // the management of C++ containers intended to be integrated
23     // to Python. The objective is make a C++ container look and
24     // feel and behave exactly as we'd expect a Python container.
25     // By default indexed elements are returned by proxy. This can be
26     // disabled by supplying *true* in the NoProxy template parameter.
27     //
28     // Derived classes provide the hooks needed by the indexing_suite
29     // to do its job:
30     //
31     //      static data_type&
32     //      get_item(Container& container, index_type i);
33     //
34     //      static object
35     //      get_slice(Container& container, index_type from, index_type to);
36     //
37     //      static void
38     //      set_item(Container& container, index_type i, data_type const& v);
39     //
40     //      static void
41     //      set_slice(
42     //         Container& container, index_type from,
43     //         index_type to, data_type const& v
44     //      );
45     //
46     //      template <class Iter>
47     //      static void
48     //      set_slice(Container& container, index_type from,
49     //          index_type to, Iter first, Iter last
50     //      );
51     //
52     //      static void
53     //      delete_item(Container& container, index_type i);
54     //
55     //      static void
56     //      delete_slice(Container& container, index_type from, index_type to);
57     //
58     //      static size_t
59     //      size(Container& container);
60     //
61     //      template <class T>
62     //      static bool
63     //      contains(Container& container, T const& val);
64     //
65     //      static index_type
66     //      convert_index(Container& container, PyObject* i);
67     //
68     //      static index_type
69     //      adjust_index(index_type current, index_type from,
70     //          index_type to, size_type len
71     //      );
72     //
73     // Most of these policies are self explanatory. convert_index and
74     // adjust_index, however, deserves some explanation.
75     //
76     // convert_index converts an Python index into a C++ index that the
77     // container can handle. For instance, negative indexes in Python, by
78     // convention, indexes from the right (e.g. C[-1] indexes the rightmost
79     // element in C). convert_index should handle the necessary conversion
80     // for the C++ container (e.g. convert -1 to C.size()-1). convert_index
81     // should also be able to convert the type of the index (A dynamic Python
82     // type) to the actual type that the C++ container expects.
83     //
84     // When a container expands or contracts, held indexes to its elements
85     // must be adjusted to follow the movement of data. For instance, if
86     // we erase 3 elements, starting from index 0 from a 5 element vector,
87     // what used to be at index 4 will now be at index 1:
88     //
89     //      [a][b][c][d][e] ---> [d][e]
90     //                   ^           ^
91     //                   4           1
92     //
93     // adjust_index takes care of the adjustment. Given a current index,
94     // the function should return the adjusted index when data in the
95     // container at index from..to is replaced by *len* elements.
96     //
97 
98     template <
99           class Container
100         , class DerivedPolicies
101         , bool NoProxy = false
102         , bool NoSlice = false
103         , class Data = typename Container::value_type
104         , class Index = typename Container::size_type
105         , class Key = typename Container::value_type
106     >
107     class indexing_suite
108         : public def_visitor<
109             indexing_suite<
110               Container
111             , DerivedPolicies
112             , NoProxy
113             , NoSlice
114             , Data
115             , Index
116             , Key
117         > >
118     {
119     private:
120 
121         typedef mpl::or_<
122             mpl::bool_<NoProxy>
123           , mpl::not_<is_class<Data> >
124           , typename mpl::or_<
125                 detail::is_same<Data, std::string>
126               , detail::is_same<Data, std::complex<float> >
127               , detail::is_same<Data, std::complex<double> >
128               , detail::is_same<Data, std::complex<long double> > >::type>
129         no_proxy;
130 
131         typedef detail::container_element<Container, Index, DerivedPolicies>
132             container_element_t;
133 
134         typedef return_internal_reference<> return_policy;
135 
136         typedef typename mpl::if_<
137             no_proxy
138           , iterator<Container>
139           , iterator<Container, return_policy> >::type
140         def_iterator;
141 
142         typedef typename mpl::if_<
143             no_proxy
144           , detail::no_proxy_helper<
145                 Container
146               , DerivedPolicies
147               , container_element_t
148               , Index>
149           , detail::proxy_helper<
150                 Container
151               , DerivedPolicies
152               , container_element_t
153               , Index> >::type
154         proxy_handler;
155 
156         typedef typename mpl::if_<
157             mpl::bool_<NoSlice>
158           , detail::no_slice_helper<
159                 Container
160               , DerivedPolicies
161               , proxy_handler
162               , Data
163               , Index>
164           , detail::slice_helper<
165                 Container
166               , DerivedPolicies
167               , proxy_handler
168               , Data
169               , Index> >::type
170         slice_handler;
171 
172     public:
173 
174         template <class Class>
visit(Class & cl) const175         void visit(Class& cl) const
176         {
177             // Hook into the class_ generic visitation .def function
178             proxy_handler::register_container_element();
179 
180             cl
181                 .def("__len__", base_size)
182                 .def("__setitem__", &base_set_item)
183                 .def("__delitem__", &base_delete_item)
184                 .def("__getitem__", &base_get_item)
185                 .def("__contains__", &base_contains)
186                 .def("__iter__", def_iterator())
187             ;
188 
189             DerivedPolicies::extension_def(cl);
190         }
191 
192         template <class Class>
193         static void
extension_def(Class & cl)194         extension_def(Class& cl)
195         {
196             // default.
197             // no more extensions
198         }
199 
200     private:
201 
202         static object
base_get_item(back_reference<Container &> container,PyObject * i)203         base_get_item(back_reference<Container&> container, PyObject* i)
204         {
205             if (PySlice_Check(i))
206                 return slice_handler::base_get_slice(
207                     container.get(), static_cast<PySliceObject*>(static_cast<void*>(i)));
208 
209             return proxy_handler::base_get_item_(container, i);
210         }
211 
212         static void
base_set_item(Container & container,PyObject * i,PyObject * v)213         base_set_item(Container& container, PyObject* i, PyObject* v)
214         {
215             if (PySlice_Check(i))
216             {
217                  slice_handler::base_set_slice(container,
218                      static_cast<PySliceObject*>(static_cast<void*>(i)), v);
219             }
220             else
221             {
222                 extract<Data&> elem(v);
223                 // try if elem is an exact Data
224                 if (elem.check())
225                 {
226                     DerivedPolicies::
227                         set_item(container,
228                             DerivedPolicies::
229                                 convert_index(container, i), elem());
230                 }
231                 else
232                 {
233                     //  try to convert elem to Data
234                     extract<Data> elem(v);
235                     if (elem.check())
236                     {
237                         DerivedPolicies::
238                             set_item(container,
239                                 DerivedPolicies::
240                                     convert_index(container, i), elem());
241                     }
242                     else
243                     {
244                         PyErr_SetString(PyExc_TypeError, "Invalid assignment");
245                         throw_error_already_set();
246                     }
247                 }
248             }
249         }
250 
251         static void
base_delete_item(Container & container,PyObject * i)252         base_delete_item(Container& container, PyObject* i)
253         {
254             if (PySlice_Check(i))
255             {
256                 slice_handler::base_delete_slice(
257                     container, static_cast<PySliceObject*>(static_cast<void*>(i)));
258                 return;
259             }
260 
261             Index index = DerivedPolicies::convert_index(container, i);
262             proxy_handler::base_erase_index(container, index, mpl::bool_<NoSlice>());
263             DerivedPolicies::delete_item(container, index);
264         }
265 
266         static size_t
base_size(Container & container)267         base_size(Container& container)
268         {
269             return DerivedPolicies::size(container);
270         }
271 
272         static bool
base_contains(Container & container,PyObject * key)273         base_contains(Container& container, PyObject* key)
274         {
275             extract<Key const&> x(key);
276             //  try if key is an exact Key type
277             if (x.check())
278             {
279                 return DerivedPolicies::contains(container, x());
280             }
281             else
282             {
283                 //  try to convert key to Key type
284                 extract<Key> x(key);
285                 if (x.check())
286                     return DerivedPolicies::contains(container, x());
287                 else
288                     return false;
289             }
290         }
291     };
292 
293 }} // namespace boost::python
294 
295 #endif // INDEXING_SUITE_JDG20036_HPP
296