1 /* -*- Mode: C; c-basic-offset: 4 -*-
2  * vim: tabstop=4 shiftwidth=4 expandtab
3  *
4  * Copyright (C) 2015 Christoph Reiter <reiter.christoph@gmail.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <Python.h>
21 #include <glib.h>
22 #include "pygi-resulttuple.h"
23 #include "pygi-util.h"
24 
25 static char repr_format_key[] = "__repr_format";
26 static char tuple_indices_key[] = "__tuple_indices";
27 
28 #define PYGI_USE_FREELIST
29 
30 #ifdef PYPY_VERSION
31 #undef PYGI_USE_FREELIST
32 #endif
33 
34 #ifdef PYGI_USE_FREELIST
35 /* A free list similar to the one used for the CPython tuple. Difference
36  * is that zero length tuples aren't cached (as we don't need them)
37  * and that the freelist is smaller as we don't free it with the cyclic GC
38  * as CPython does. This wastes 21kB max.
39  */
40 #define PyGIResultTuple_MAXSAVESIZE 10
41 #define PyGIResultTuple_MAXFREELIST 100
42 static PyObject *free_list[PyGIResultTuple_MAXSAVESIZE];
43 static int numfree[PyGIResultTuple_MAXSAVESIZE];
44 #endif
45 
46 PYGI_DEFINE_TYPE ("gi._gi.ResultTuple", PyGIResultTuple_Type, PyTupleObject)
47 
48 /**
49  * ResultTuple.__repr__() implementation.
50  * Takes the _ResultTuple.__repr_format format string and applies the tuple
51  * values to it.
52  */
53 static PyObject*
resulttuple_repr(PyObject * self)54 resulttuple_repr(PyObject *self) {
55     PyObject *format,  *repr, *format_attr;
56 
57     format_attr = PyUnicode_FromString (repr_format_key);
58     format = PyTuple_Type.tp_getattro (self, format_attr);
59     Py_DECREF (format_attr);
60     if (format == NULL)
61         return NULL;
62     repr = PyUnicode_Format (format, self);
63     Py_DECREF (format);
64     return repr;
65 }
66 
67 /**
68  * PyGIResultTuple_Type.tp_getattro implementation.
69  * Looks up the tuple index in _ResultTuple.__tuple_indices and returns the
70  * tuple item.
71  */
72 static PyObject*
resulttuple_getattro(PyObject * self,PyObject * name)73 resulttuple_getattro(PyObject *self, PyObject *name) {
74     PyObject *mapping, *index, *mapping_attr, *item;
75 
76     mapping_attr = PyUnicode_FromString (tuple_indices_key);
77     mapping = PyTuple_Type.tp_getattro (self, mapping_attr);
78     Py_DECREF (mapping_attr);
79     if (mapping == NULL)
80         return NULL;
81     g_assert (PyDict_Check (mapping));
82     index = PyDict_GetItem (mapping, name);
83 
84     if (index != NULL) {
85         item = PyTuple_GET_ITEM (self, PyLong_AsSsize_t (index));
86         Py_INCREF (item);
87     } else {
88         item = PyTuple_Type.tp_getattro (self, name);
89     }
90     Py_DECREF (mapping);
91 
92     return item;
93 }
94 
95 /**
96  * ResultTuple.__reduce__() implementation.
97  * Always returns (tuple, tuple(self))
98  * Needed so that pickling doesn't depend on our tuple subclass and unpickling
99  * works without it. As a result unpickle will give back in a normal tuple.
100  */
101 static PyObject *
resulttuple_reduce(PyObject * self)102 resulttuple_reduce(PyObject *self)
103 {
104     PyObject *tuple = PySequence_Tuple (self);
105     if (tuple == NULL)
106         return NULL;
107     return Py_BuildValue ("(O, (N))", &PyTuple_Type, tuple);
108 }
109 
110 /**
111  * Extends __dir__ with the extra attributes accessible through
112  * resulttuple_getattro()
113  */
114 static PyObject *
resulttuple_dir(PyObject * self)115 resulttuple_dir(PyObject *self)
116 {
117     PyObject *mapping_attr;
118     PyObject *items = NULL;
119     PyObject *mapping = NULL;
120     PyObject *mapping_values = NULL;
121     PyObject *result = NULL;
122 
123     mapping_attr = PyUnicode_FromString (tuple_indices_key);
124     mapping = PyTuple_Type.tp_getattro (self, mapping_attr);
125     Py_DECREF (mapping_attr);
126     if (mapping == NULL)
127         goto error;
128     items = PyObject_Dir ((PyObject*)Py_TYPE (self));
129     if (items == NULL)
130         goto error;
131     mapping_values = PyDict_Keys (mapping);
132     if (mapping_values == NULL)
133         goto error;
134     result = PySequence_InPlaceConcat (items, mapping_values);
135 
136 error:
137     Py_XDECREF (items);
138     Py_XDECREF (mapping);
139     Py_XDECREF (mapping_values);
140 
141     return result;
142 }
143 
144 /**
145  * resulttuple_new_type:
146  * @args: one list object containing tuple item names and None
147  *
148  * Exposes pygi_resulttuple_new_type() as ResultTuple._new_type()
149  * to allow creation of result types for unit tests.
150  *
151  * Returns: A new PyTypeObject which is a subclass of PyGIResultTuple_Type
152  *    or %NULL in case of an error.
153  */
154 static PyObject *
resulttuple_new_type(PyObject * self,PyObject * args)155 resulttuple_new_type(PyObject *self, PyObject *args) {
156     PyObject *tuple_names, *new_type;
157 
158     if (!PyArg_ParseTuple (args, "O:ResultTuple._new_type", &tuple_names))
159         return NULL;
160 
161     if (!PyList_Check (tuple_names)) {
162         PyErr_SetString (PyExc_TypeError, "not a list");
163         return NULL;
164     }
165 
166     new_type = (PyObject *)pygi_resulttuple_new_type (tuple_names);
167     return new_type;
168 }
169 
170 static PyMethodDef resulttuple_methods[] = {
171     {"__reduce__", (PyCFunction)resulttuple_reduce, METH_NOARGS},
172     {"__dir__", (PyCFunction)resulttuple_dir, METH_NOARGS},
173     {"_new_type", (PyCFunction)resulttuple_new_type,
174      METH_VARARGS | METH_STATIC},
175     {NULL, NULL, 0},
176 };
177 
178 /**
179  * pygi_resulttuple_new_type:
180  * @tuple_names: A python list containing str or None items.
181  *
182  * Similar to namedtuple() creates a new tuple subclass which
183  * allows to access items by name and have a pretty __repr__.
184  * Each item in the passed name list corresponds to an item with
185  * the same index in the tuple class. If the name is None the item/index
186  * is unnamed.
187  *
188  * Returns: A new PyTypeObject which is a subclass of PyGIResultTuple_Type
189  *    or %NULL in case of an error.
190  */
191 PyTypeObject*
pygi_resulttuple_new_type(PyObject * tuple_names)192 pygi_resulttuple_new_type(PyObject *tuple_names) {
193     PyTypeObject *new_type;
194     PyObject *class_dict, *format_string, *empty_format, *named_format,
195         *format_list, *sep, *index_dict, *slots, *paren_format, *new_type_args,
196         *paren_string;
197     Py_ssize_t len, i;
198 
199     g_assert (PyList_Check (tuple_names));
200 
201     class_dict = PyDict_New ();
202 
203     /* To save some memory don't use an instance dict */
204     slots = PyTuple_New (0);
205     PyDict_SetItemString (class_dict, "__slots__", slots);
206     Py_DECREF (slots);
207 
208     format_list = PyList_New (0);
209     index_dict = PyDict_New ();
210 
211     empty_format = PyUnicode_FromString ("%r");
212     named_format = PyUnicode_FromString ("%s=%%r");
213     len = PyList_Size (tuple_names);
214     for (i = 0; i < len; i++) {
215         PyObject *item, *named_args, *named_build, *index;
216         item = PyList_GET_ITEM (tuple_names, i);
217         if (item == Py_None) {
218             PyList_Append (format_list, empty_format);
219         } else {
220             named_args = Py_BuildValue ("(O)", item);
221             named_build = PyUnicode_Format (named_format, named_args);
222             Py_DECREF (named_args);
223             PyList_Append (format_list, named_build);
224             Py_DECREF (named_build);
225             index = PyLong_FromSsize_t (i);
226             PyDict_SetItem (index_dict, item, index);
227             Py_DECREF (index);
228         }
229     }
230     Py_DECREF (empty_format);
231     Py_DECREF (named_format);
232 
233     sep = PyUnicode_FromString (", ");
234     format_string = PyObject_CallMethod (sep, "join", "O", format_list);
235     Py_DECREF (sep);
236     Py_DECREF (format_list);
237     paren_format = PyUnicode_FromString ("(%s)");
238     paren_string = PyUnicode_Format (paren_format, format_string);
239     Py_DECREF (paren_format);
240     Py_DECREF (format_string);
241 
242     PyDict_SetItemString (class_dict, repr_format_key, paren_string);
243     Py_DECREF (paren_string);
244 
245     PyDict_SetItemString (class_dict, tuple_indices_key, index_dict);
246     Py_DECREF (index_dict);
247 
248     new_type_args = Py_BuildValue ("s(O)O", "_ResultTuple",
249                                    &PyGIResultTuple_Type, class_dict);
250     new_type = (PyTypeObject *)PyType_Type.tp_new (&PyType_Type,
251                                                    new_type_args, NULL);
252     Py_DECREF (new_type_args);
253     Py_DECREF (class_dict);
254 
255     if (new_type != NULL) {
256         /* disallow subclassing as that would break the free list caching
257          * since we assume that all subclasses use PyTupleObject */
258         new_type->tp_flags &= ~Py_TPFLAGS_BASETYPE;
259     }
260 
261     return new_type;
262 }
263 
264 
265 /**
266  * pygi_resulttuple_new:
267  * @subclass: A PyGIResultTuple_Type subclass which will be the type of the
268  *    returned instance.
269  * @len: Length of the returned tuple
270  *
271  * Like PyTuple_New(). Return an uninitialized tuple of the given @length.
272  *
273  * Returns: An instance of @subclass or %NULL on error.
274  */
275 PyObject *
pygi_resulttuple_new(PyTypeObject * subclass,Py_ssize_t len)276 pygi_resulttuple_new(PyTypeObject *subclass, Py_ssize_t len) {
277 #ifdef PYGI_USE_FREELIST
278     PyObject *self;
279     Py_ssize_t i;
280 
281     /* Check the free list for a tuple object with the needed size;
282      * clear it and change the class to ours.
283      */
284     if (len > 0 && len < PyGIResultTuple_MAXSAVESIZE) {
285         self = free_list[len];
286         if (self != NULL) {
287             free_list[len] = PyTuple_GET_ITEM (self, 0);
288             numfree[len]--;
289             for (i=0; i < len; i++) {
290                 PyTuple_SET_ITEM (self, i, NULL);
291             }
292             Py_TYPE (self) = subclass;
293             Py_INCREF (subclass);
294             _Py_NewReference (self);
295             PyObject_GC_Track (self);
296             return self;
297         }
298     }
299 #endif
300 
301     /* For zero length tuples and in case the free list is empty, alloc
302      * as usual.
303      */
304     return subclass->tp_alloc (subclass, len);
305 }
306 
307 #ifdef PYGI_USE_FREELIST
resulttuple_dealloc(PyObject * self)308 static void resulttuple_dealloc(PyObject *self) {
309     Py_ssize_t i, len;
310 
311     PyObject_GC_UnTrack (self);
312     Py_TRASHCAN_SAFE_BEGIN (self)
313 
314     /* Free the tuple items and, if there is space, save the tuple object
315      * pointer to the front of the free list for its size. Otherwise free it.
316      */
317     len = Py_SIZE (self);
318     if (len > 0) {
319         for (i=0; i < len; i++) {
320             Py_XDECREF (PyTuple_GET_ITEM (self, i));
321         }
322 
323         if (len < PyGIResultTuple_MAXSAVESIZE && numfree[len] < PyGIResultTuple_MAXFREELIST) {
324             PyTuple_SET_ITEM (self, 0, free_list[len]);
325             numfree[len]++;
326             free_list[len] = self;
327             goto done;
328         }
329     }
330 
331     Py_TYPE (self)->tp_free (self);
332 
333 done:
334     Py_TRASHCAN_SAFE_END (self)
335 }
336 #endif
337 
338 /**
339  * pygi_resulttuple_register_types:
340  * @module: A Python modules to which ResultTuple gets added to.
341  *
342  * Initializes the ResultTuple class and adds it to the passed @module.
343  *
344  * Returns: -1 on error, 0 on success.
345  */
pygi_resulttuple_register_types(PyObject * module)346 int pygi_resulttuple_register_types(PyObject *module) {
347 
348     PyGIResultTuple_Type.tp_base = &PyTuple_Type;
349     PyGIResultTuple_Type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
350     PyGIResultTuple_Type.tp_repr = (reprfunc)resulttuple_repr;
351     PyGIResultTuple_Type.tp_getattro = (getattrofunc)resulttuple_getattro;
352     PyGIResultTuple_Type.tp_methods = resulttuple_methods;
353 #ifdef PYGI_USE_FREELIST
354     PyGIResultTuple_Type.tp_dealloc = (destructor)resulttuple_dealloc;
355 #endif
356 
357     if (PyType_Ready (&PyGIResultTuple_Type) < 0)
358         return -1;
359 
360     Py_INCREF (&PyGIResultTuple_Type);
361     if (PyModule_AddObject (module, "ResultTuple",
362                             (PyObject *)&PyGIResultTuple_Type) < 0) {
363         Py_DECREF (&PyGIResultTuple_Type);
364         return -1;
365     }
366 
367     return 0;
368 }
369