1 /* Use this file as a template to start implementing a module that
2    also declares object types. All occurrences of 'Xxo' should be changed
3    to something reasonable for your objects. After that, all other
4    occurrences of 'xx' should be changed to something reasonable for your
5    module. If your module is named foo your source file should be named
6    foo.c or foomodule.c.
7 
8    You will probably want to delete all references to 'x_attr' and add
9    your own types of attributes instead.  Maybe you want to name your
10    local variables other than 'self'.  If your object type is needed in
11    other files, you'll have to create a file "foobarobject.h"; see
12    floatobject.h for an example.
13 
14    This module roughly corresponds to::
15 
16       class Xxo:
17          """A class that explicitly stores attributes in an internal dict"""
18 
19           def __init__(self):
20               # In the C class, "_x_attr" is not accessible from Python code
21               self._x_attr = {}
22               self._x_exports = 0
23 
24           def __getattr__(self, name):
25               return self._x_attr[name]
26 
27           def __setattr__(self, name, value):
28               self._x_attr[name] = value
29 
30           def __delattr__(self, name):
31               del self._x_attr[name]
32 
33           @property
34           def x_exports(self):
35               """Return the number of times an internal buffer is exported."""
36               # Each Xxo instance has a 10-byte buffer that can be
37               # accessed via the buffer interface (e.g. `memoryview`).
38               return self._x_exports
39 
40           def demo(o, /):
41               if isinstance(o, str):
42                   return o
43               elif isinstance(o, Xxo):
44                   return o
45               else:
46                   raise Error('argument must be str or Xxo')
47 
48       class Error(Exception):
49           """Exception raised by the xxlimited module"""
50 
51       def foo(i: int, j: int, /):
52           """Return the sum of i and j."""
53           # Unlike this pseudocode, the C function will *only* work with
54           # integers and perform C long int arithmetic
55           return i + j
56 
57       def new():
58           return Xxo()
59 
60       def Str(str):
61           # A trivial subclass of a built-in type
62           pass
63    */
64 
65 #define Py_LIMITED_API 0x030b0000
66 
67 #include "Python.h"
68 #include <string.h>
69 
70 #define BUFSIZE 10
71 
72 // Module state
73 typedef struct {
74     PyObject *Xxo_Type;    // Xxo class
75     PyObject *Error_Type;       // Error class
76 } xx_state;
77 
78 
79 /* Xxo objects */
80 
81 // Instance state
82 typedef struct {
83     PyObject_HEAD
84     PyObject            *x_attr;           /* Attributes dictionary */
85     char                x_buffer[BUFSIZE]; /* buffer for Py_buffer */
86     Py_ssize_t          x_exports;         /* how many buffer are exported */
87 } XxoObject;
88 
89 // XXX: no good way to do this yet
90 // #define XxoObject_Check(v)      Py_IS_TYPE(v, Xxo_Type)
91 
92 static XxoObject *
newXxoObject(PyObject * module)93 newXxoObject(PyObject *module)
94 {
95     xx_state *state = PyModule_GetState(module);
96     if (state == NULL) {
97         return NULL;
98     }
99     XxoObject *self;
100     self = PyObject_GC_New(XxoObject, (PyTypeObject*)state->Xxo_Type);
101     if (self == NULL) {
102         return NULL;
103     }
104     self->x_attr = NULL;
105     memset(self->x_buffer, 0, BUFSIZE);
106     self->x_exports = 0;
107     return self;
108 }
109 
110 /* Xxo finalization */
111 
112 static int
Xxo_traverse(PyObject * self_obj,visitproc visit,void * arg)113 Xxo_traverse(PyObject *self_obj, visitproc visit, void *arg)
114 {
115     // Visit the type
116     Py_VISIT(Py_TYPE(self_obj));
117 
118     // Visit the attribute dict
119     XxoObject *self = (XxoObject *)self_obj;
120     Py_VISIT(self->x_attr);
121     return 0;
122 }
123 
124 static int
Xxo_clear(XxoObject * self)125 Xxo_clear(XxoObject *self)
126 {
127     Py_CLEAR(self->x_attr);
128     return 0;
129 }
130 
131 static void
Xxo_finalize(PyObject * self_obj)132 Xxo_finalize(PyObject *self_obj)
133 {
134     XxoObject *self = (XxoObject *)self_obj;
135     Py_CLEAR(self->x_attr);
136 }
137 
138 static void
Xxo_dealloc(PyObject * self)139 Xxo_dealloc(PyObject *self)
140 {
141     PyObject_GC_UnTrack(self);
142     Xxo_finalize(self);
143     PyTypeObject *tp = Py_TYPE(self);
144     freefunc free = PyType_GetSlot(tp, Py_tp_free);
145     free(self);
146     Py_DECREF(tp);
147 }
148 
149 
150 /* Xxo attribute handling */
151 
152 static PyObject *
Xxo_getattro(XxoObject * self,PyObject * name)153 Xxo_getattro(XxoObject *self, PyObject *name)
154 {
155     if (self->x_attr != NULL) {
156         PyObject *v = PyDict_GetItemWithError(self->x_attr, name);
157         if (v != NULL) {
158             Py_INCREF(v);
159             return v;
160         }
161         else if (PyErr_Occurred()) {
162             return NULL;
163         }
164     }
165     return PyObject_GenericGetAttr((PyObject *)self, name);
166 }
167 
168 static int
Xxo_setattro(XxoObject * self,PyObject * name,PyObject * v)169 Xxo_setattro(XxoObject *self, PyObject *name, PyObject *v)
170 {
171     if (self->x_attr == NULL) {
172         // prepare the attribute dict
173         self->x_attr = PyDict_New();
174         if (self->x_attr == NULL) {
175             return -1;
176         }
177     }
178     if (v == NULL) {
179         // delete an attribute
180         int rv = PyDict_DelItem(self->x_attr, name);
181         if (rv < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) {
182             PyErr_SetString(PyExc_AttributeError,
183                 "delete non-existing Xxo attribute");
184             return -1;
185         }
186         return rv;
187     }
188     else {
189         // set an attribute
190         return PyDict_SetItem(self->x_attr, name, v);
191     }
192 }
193 
194 /* Xxo methods */
195 
196 static PyObject *
Xxo_demo(XxoObject * self,PyTypeObject * defining_class,PyObject ** args,Py_ssize_t nargs,PyObject * kwnames)197 Xxo_demo(XxoObject *self, PyTypeObject *defining_class,
198          PyObject **args, Py_ssize_t nargs, PyObject *kwnames)
199 {
200     if (kwnames != NULL && PyObject_Length(kwnames)) {
201         PyErr_SetString(PyExc_TypeError, "demo() takes no keyword arguments");
202         return NULL;
203     }
204     if (nargs != 1) {
205         PyErr_SetString(PyExc_TypeError, "demo() takes exactly 1 argument");
206         return NULL;
207     }
208 
209     PyObject *o = args[0];
210 
211     /* Test if the argument is "str" */
212     if (PyUnicode_Check(o)) {
213         Py_INCREF(o);
214         return o;
215     }
216 
217     /* test if the argument is of the Xxo class */
218     if (PyObject_TypeCheck(o, defining_class)) {
219         Py_INCREF(o);
220         return o;
221     }
222 
223     Py_INCREF(Py_None);
224     return Py_None;
225 }
226 
227 static PyMethodDef Xxo_methods[] = {
228     {"demo",            _PyCFunction_CAST(Xxo_demo),
229      METH_METHOD | METH_FASTCALL | METH_KEYWORDS, PyDoc_STR("demo(o) -> o")},
230     {NULL,              NULL}           /* sentinel */
231 };
232 
233 /* Xxo buffer interface */
234 
235 static int
Xxo_getbuffer(XxoObject * self,Py_buffer * view,int flags)236 Xxo_getbuffer(XxoObject *self, Py_buffer *view, int flags)
237 {
238     int res = PyBuffer_FillInfo(view, (PyObject*)self,
239                                (void *)self->x_buffer, BUFSIZE,
240                                0, flags);
241     if (res == 0) {
242         self->x_exports++;
243     }
244     return res;
245 }
246 
247 static void
Xxo_releasebuffer(XxoObject * self,Py_buffer * view)248 Xxo_releasebuffer(XxoObject *self, Py_buffer *view)
249 {
250     self->x_exports--;
251 }
252 
253 static PyObject *
Xxo_get_x_exports(XxoObject * self,void * c)254 Xxo_get_x_exports(XxoObject *self, void *c)
255 {
256     return PyLong_FromSsize_t(self->x_exports);
257 }
258 
259 /* Xxo type definition */
260 
261 PyDoc_STRVAR(Xxo_doc,
262              "A class that explicitly stores attributes in an internal dict");
263 
264 static PyGetSetDef Xxo_getsetlist[] = {
265     {"x_exports", (getter) Xxo_get_x_exports, NULL, NULL},
266     {NULL},
267 };
268 
269 
270 static PyType_Slot Xxo_Type_slots[] = {
271     {Py_tp_doc, (char *)Xxo_doc},
272     {Py_tp_traverse, Xxo_traverse},
273     {Py_tp_clear, Xxo_clear},
274     {Py_tp_finalize, Xxo_finalize},
275     {Py_tp_dealloc, Xxo_dealloc},
276     {Py_tp_getattro, Xxo_getattro},
277     {Py_tp_setattro, Xxo_setattro},
278     {Py_tp_methods, Xxo_methods},
279     {Py_bf_getbuffer, Xxo_getbuffer},
280     {Py_bf_releasebuffer, Xxo_releasebuffer},
281     {Py_tp_getset, Xxo_getsetlist},
282     {0, 0},  /* sentinel */
283 };
284 
285 static PyType_Spec Xxo_Type_spec = {
286     .name = "xxlimited.Xxo",
287     .basicsize = sizeof(XxoObject),
288     .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
289     .slots = Xxo_Type_slots,
290 };
291 
292 
293 /* Str type definition*/
294 
295 static PyType_Slot Str_Type_slots[] = {
296     {0, 0},  /* sentinel */
297 };
298 
299 static PyType_Spec Str_Type_spec = {
300     .name = "xxlimited.Str",
301     .basicsize = 0,
302     .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
303     .slots = Str_Type_slots,
304 };
305 
306 
307 /* Function of two integers returning integer (with C "long int" arithmetic) */
308 
309 PyDoc_STRVAR(xx_foo_doc,
310 "foo(i,j)\n\
311 \n\
312 Return the sum of i and j.");
313 
314 static PyObject *
xx_foo(PyObject * module,PyObject * args)315 xx_foo(PyObject *module, PyObject *args)
316 {
317     long i, j;
318     long res;
319     if (!PyArg_ParseTuple(args, "ll:foo", &i, &j))
320         return NULL;
321     res = i+j; /* XXX Do something here */
322     return PyLong_FromLong(res);
323 }
324 
325 
326 /* Function of no arguments returning new Xxo object */
327 
328 static PyObject *
xx_new(PyObject * module,PyObject * Py_UNUSED (unused))329 xx_new(PyObject *module, PyObject *Py_UNUSED(unused))
330 {
331     XxoObject *rv;
332 
333     rv = newXxoObject(module);
334     if (rv == NULL)
335         return NULL;
336     return (PyObject *)rv;
337 }
338 
339 
340 
341 /* List of functions defined in the module */
342 
343 static PyMethodDef xx_methods[] = {
344     {"foo",             xx_foo,         METH_VARARGS,
345         xx_foo_doc},
346     {"new",             xx_new,         METH_NOARGS,
347         PyDoc_STR("new() -> new Xx object")},
348     {NULL,              NULL}           /* sentinel */
349 };
350 
351 
352 /* The module itself */
353 
354 PyDoc_STRVAR(module_doc,
355 "This is a template module just for instruction.");
356 
357 static int
xx_modexec(PyObject * m)358 xx_modexec(PyObject *m)
359 {
360     xx_state *state = PyModule_GetState(m);
361 
362     state->Error_Type = PyErr_NewException("xxlimited.Error", NULL, NULL);
363     if (state->Error_Type == NULL) {
364         return -1;
365     }
366     if (PyModule_AddType(m, (PyTypeObject*)state->Error_Type) < 0) {
367         return -1;
368     }
369 
370     state->Xxo_Type = PyType_FromModuleAndSpec(m, &Xxo_Type_spec, NULL);
371     if (state->Xxo_Type == NULL) {
372         return -1;
373     }
374     if (PyModule_AddType(m, (PyTypeObject*)state->Xxo_Type) < 0) {
375         return -1;
376     }
377 
378     // Add the Str type. It is not needed from C code, so it is only
379     // added to the module dict.
380     // It does not inherit from "object" (PyObject_Type), but from "str"
381     // (PyUnincode_Type).
382     PyObject *Str_Type = PyType_FromModuleAndSpec(
383         m, &Str_Type_spec, (PyObject *)&PyUnicode_Type);
384     if (Str_Type == NULL) {
385         return -1;
386     }
387     if (PyModule_AddType(m, (PyTypeObject*)Str_Type) < 0) {
388         return -1;
389     }
390     Py_DECREF(Str_Type);
391 
392     return 0;
393 }
394 
395 static PyModuleDef_Slot xx_slots[] = {
396     {Py_mod_exec, xx_modexec},
397     {0, NULL}
398 };
399 
400 static int
xx_traverse(PyObject * module,visitproc visit,void * arg)401 xx_traverse(PyObject *module, visitproc visit, void *arg)
402 {
403     xx_state *state = PyModule_GetState(module);
404     Py_VISIT(state->Xxo_Type);
405     Py_VISIT(state->Error_Type);
406     return 0;
407 }
408 
409 static int
xx_clear(PyObject * module)410 xx_clear(PyObject *module)
411 {
412     xx_state *state = PyModule_GetState(module);
413     Py_CLEAR(state->Xxo_Type);
414     Py_CLEAR(state->Error_Type);
415     return 0;
416 }
417 
418 static struct PyModuleDef xxmodule = {
419     PyModuleDef_HEAD_INIT,
420     .m_name = "xxlimited",
421     .m_doc = module_doc,
422     .m_size = sizeof(xx_state),
423     .m_methods = xx_methods,
424     .m_slots = xx_slots,
425     .m_traverse = xx_traverse,
426     .m_clear = xx_clear,
427     /* m_free is not necessary here: xx_clear clears all references,
428      * and the module state is deallocated along with the module.
429      */
430 };
431 
432 
433 /* Export function for the module (*must* be called PyInit_xx) */
434 
435 PyMODINIT_FUNC
PyInit_xxlimited(void)436 PyInit_xxlimited(void)
437 {
438     return PyModuleDef_Init(&xxmodule);
439 }
440