pyke/mapping.c, key.c: Make the mapping code more intrusive and complete.
[pyke] / mapping.c
CommitLineData
c1756f78
MW
1/* -*-c-*-
2 *
3 * Generic mapping support
4 *
5 * (c) 2019 Straylight/Edgeware
6 */
7
8/*----- Licensing notice --------------------------------------------------*
9 *
10 * This file is part of Pyke: the Python Kit for Extensions.
11 *
12 * Pyke is free software: you can redistribute it and/or modify it under
13 * the terms of the GNU General Public License as published by the Free
14 * Software Foundation; either version 2 of the License, or (at your
15 * option) any later version.
16 *
17 * Pyke is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
20 * for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with Pyke. If not, write to the Free Software Foundation, Inc.,
24 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 */
26
27/*----- Header files ------------------------------------------------------*/
28
29#include "pyke.h"
30
31/*----- Iteration ---------------------------------------------------------*/
32
78daa0e0
MW
33static PyTypeObject *keyiter_pytype, *itemiter_pytype, *valiter_pytype;
34
35union iterstate {
36 void *external;
37 void *internal[4];
38};
c1756f78
MW
39
40typedef struct iter_pyobj {
41 PyObject_HEAD
42 PyObject *map;
78daa0e0 43 union iterstate iter;
c1756f78
MW
44} iter_pyobj;
45#define ITER_MAP(o) (((iter_pyobj *)(o))->map)
78daa0e0
MW
46#define ITER_ITER(o) (&((iter_pyobj *)(o))->iter)
47#define ITER_EXTERNALP(o) \
48 (GMAP_OPS(ITER_MAP(o))->isz > sizeof(union iterstate))
49#define ITER_I(o) (ITER_EXTERNALP(o) ? ITER_ITER(o)->external \
50 : &ITER_ITER(o)->internal)
51
52static void *iter_init(PyObject *me, union iterstate *iter)
53{
54 const gmap_ops *gmops = GMAP_OPS(me);
55 void *i;
56
57 if (gmops->isz <= sizeof(*iter)) i = &iter->internal;
58 else { i = iter->external = PyObject_Malloc(gmops->isz); assert(i); }
59 gmops->iter_init(me, i);
60 return (i);
61}
62
63static void iter_free(PyObject *me, union iterstate *iter)
64 { if (GMAP_OPS(me)->isz > sizeof(*iter)) PyObject_Free(iter->external); }
c1756f78
MW
65
66static void iter_pydealloc(PyObject *me)
78daa0e0
MW
67{
68 PyObject *map = ITER_MAP(me);
69 iter_free(map, ITER_ITER(me));
70 Py_DECREF(map); FREEOBJ(me);
71}
c1756f78 72
78daa0e0 73static PyObject *gmap_mkiter(PyObject *me, PyTypeObject *ty)
c1756f78 74{
78daa0e0 75 iter_pyobj *iter = PyObject_NEW(iter_pyobj, ty);
c1756f78 76
78daa0e0
MW
77 iter->map = me; Py_INCREF(me);
78 iter_init(me, &iter->iter);
79 return ((PyObject *)iter);
c1756f78
MW
80}
81
78daa0e0
MW
82static PyObject *keyiter_pynext(PyObject *me)
83{
84 PyObject *map = ITER_MAP(me);
85 const struct gmap_ops *gmops = GMAP_OPS(map);
86 void *e = gmops->iter_next(map, ITER_I(me));
87
88 if (!e) return (0);
89 else return (gmops->entry_key(map, e));
90}
91
92static const PyTypeObject keyiter_pytype_skel = {
c1756f78 93 PyObject_HEAD_INIT(0) 0, /* Header */
78daa0e0 94 "_KeyIter", /* @tp_name@ */
c1756f78
MW
95 sizeof(iter_pyobj), /* @tp_basicsize@ */
96 0, /* @tp_itemsize@ */
97
98 iter_pydealloc, /* @tp_dealloc@ */
99 0, /* @tp_print@ */
100 0, /* @tp_getattr@ */
101 0, /* @tp_setattr@ */
102 0, /* @tp_compare@ */
103 0, /* @tp_repr@ */
104 0, /* @tp_as_number@ */
105 0, /* @tp_as_sequence@ */
106 0, /* @tp_as_mapping@ */
107 0, /* @tp_hash@ */
108 0, /* @tp_call@ */
109 0, /* @tp_str@ */
110 0, /* @tp_getattro@ */
111 0, /* @tp_setattro@ */
112 0, /* @tp_as_buffer@ */
113 Py_TPFLAGS_DEFAULT | /* @tp_flags@ */
114 Py_TPFLAGS_BASETYPE,
115
116 /* @tp_doc@ */
78daa0e0 117 "Iterates over the keys of a mapping.",
c1756f78
MW
118
119 0, /* @tp_traverse@ */
120 0, /* @tp_clear@ */
121 0, /* @tp_richcompare@ */
122 0, /* @tp_weaklistoffset@ */
123 PyObject_SelfIter, /* @tp_iter@ */
78daa0e0 124 keyiter_pynext, /* @tp_iternext@ */
c1756f78
MW
125 0, /* @tp_methods@ */
126 0, /* @tp_members@ */
127 0, /* @tp_getset@ */
128 0, /* @tp_base@ */
129 0, /* @tp_dict@ */
130 0, /* @tp_descr_get@ */
131 0, /* @tp_descr_set@ */
132 0, /* @tp_dictoffset@ */
133 0, /* @tp_init@ */
134 PyType_GenericAlloc, /* @tp_alloc@ */
135 abstract_pynew, /* @tp_new@ */
136 0, /* @tp_free@ */
137 0 /* @tp_is_gc@ */
138};
139
140static PyObject *valiter_pynext(PyObject *me)
141{
78daa0e0
MW
142 PyObject *map = ITER_MAP(me);
143 const struct gmap_ops *gmops = GMAP_OPS(map);
144 void *e = gmops->iter_next(map, ITER_I(me));
c1756f78 145
78daa0e0
MW
146 if (!e) return (0);
147 else return (gmops->entry_value(map, e));
c1756f78
MW
148}
149
747ddb1b 150static const PyTypeObject valiter_pytype_skel = {
c1756f78 151 PyObject_HEAD_INIT(0) 0, /* Header */
78daa0e0 152 "_ValueIter", /* @tp_name@ */
c1756f78
MW
153 sizeof(iter_pyobj), /* @tp_basicsize@ */
154 0, /* @tp_itemsize@ */
155
156 iter_pydealloc, /* @tp_dealloc@ */
157 0, /* @tp_print@ */
158 0, /* @tp_getattr@ */
159 0, /* @tp_setattr@ */
160 0, /* @tp_compare@ */
161 0, /* @tp_repr@ */
162 0, /* @tp_as_number@ */
163 0, /* @tp_as_sequence@ */
164 0, /* @tp_as_mapping@ */
165 0, /* @tp_hash@ */
166 0, /* @tp_call@ */
167 0, /* @tp_str@ */
168 0, /* @tp_getattro@ */
169 0, /* @tp_setattro@ */
170 0, /* @tp_as_buffer@ */
171 Py_TPFLAGS_DEFAULT | /* @tp_flags@ */
172 Py_TPFLAGS_BASETYPE,
173
174 /* @tp_doc@ */
175 "Iterates over the values of a mapping.",
176
177 0, /* @tp_traverse@ */
178 0, /* @tp_clear@ */
179 0, /* @tp_richcompare@ */
180 0, /* @tp_weaklistoffset@ */
181 PyObject_SelfIter, /* @tp_iter@ */
182 valiter_pynext, /* @tp_iternext@ */
183 0, /* @tp_methods@ */
184 0, /* @tp_members@ */
185 0, /* @tp_getset@ */
186 0, /* @tp_base@ */
187 0, /* @tp_dict@ */
188 0, /* @tp_descr_get@ */
189 0, /* @tp_descr_set@ */
190 0, /* @tp_dictoffset@ */
191 0, /* @tp_init@ */
192 PyType_GenericAlloc, /* @tp_alloc@ */
193 abstract_pynew, /* @tp_new@ */
194 0, /* @tp_free@ */
195 0 /* @tp_is_gc@ */
196};
197
78daa0e0
MW
198static PyObject *itemiter_pynext(PyObject *me)
199{
200 PyObject *map = ITER_MAP(me);
201 const struct gmap_ops *gmops = GMAP_OPS(map);
202 void *e = gmops->iter_next(map, ITER_I(me));
203 PyObject *rc = 0;
204
205 if (e)
206 rc = Py_BuildValue("(NN)",
207 gmops->entry_key(map, e),
208 gmops->entry_value(map, e));
209 return (rc);
210}
211
212static const PyTypeObject itemiter_pytype_skel = {
213 PyObject_HEAD_INIT(0) 0, /* Header */
214 "_ItemIter", /* @tp_name@ */
215 sizeof(iter_pyobj), /* @tp_basicsize@ */
216 0, /* @tp_itemsize@ */
217
218 iter_pydealloc, /* @tp_dealloc@ */
219 0, /* @tp_print@ */
220 0, /* @tp_getattr@ */
221 0, /* @tp_setattr@ */
222 0, /* @tp_compare@ */
223 0, /* @tp_repr@ */
224 0, /* @tp_as_number@ */
225 0, /* @tp_as_sequence@ */
226 0, /* @tp_as_mapping@ */
227 0, /* @tp_hash@ */
228 0, /* @tp_call@ */
229 0, /* @tp_str@ */
230 0, /* @tp_getattro@ */
231 0, /* @tp_setattro@ */
232 0, /* @tp_as_buffer@ */
233 Py_TPFLAGS_DEFAULT | /* @tp_flags@ */
234 Py_TPFLAGS_BASETYPE,
235
236 /* @tp_doc@ */
237 "Iterates over the items of a mapping.",
238
239 0, /* @tp_traverse@ */
240 0, /* @tp_clear@ */
241 0, /* @tp_richcompare@ */
242 0, /* @tp_weaklistoffset@ */
243 PyObject_SelfIter, /* @tp_iter@ */
244 itemiter_pynext, /* @tp_iternext@ */
245 0, /* @tp_methods@ */
246 0, /* @tp_members@ */
247 0, /* @tp_getset@ */
248 0, /* @tp_base@ */
249 0, /* @tp_dict@ */
250 0, /* @tp_descr_get@ */
251 0, /* @tp_descr_set@ */
252 0, /* @tp_dictoffset@ */
253 0, /* @tp_init@ */
254 PyType_GenericAlloc, /* @tp_alloc@ */
255 abstract_pynew, /* @tp_new@ */
256 0, /* @tp_free@ */
257 0 /* @tp_is_gc@ */
258};
259
260/*----- Other mapping protocol support ------------------------------------*/
261
262Py_ssize_t gmap_pysize(PyObject *me)
263{
264 const gmap_ops *gmops = GMAP_OPS(me);
265 union iterstate iter;
266 void *i;
267 Py_ssize_t n = 0;
268
269 i = iter_init(me, &iter);
270 while (gmops->iter_next(me, i)) n++;
271 iter_free(me, &iter);
272 return (n);
273}
274
275PyObject *gmap_pylookup(PyObject *me, PyObject *key)
276{
277 const gmap_ops *gmops = GMAP_OPS(me);
278 void *e = gmops->lookup(me, key, 0);
279 PyObject *rc = 0;
280
281 if (!e) { if (!PyErr_Occurred()) MAPERR(key); else goto end; }
282 rc = gmops->entry_value(me, e);
283end:
284 return (rc);
285}
286
287int gmap_pystore(PyObject *me, PyObject *key, PyObject *value)
288{
289 const gmap_ops *gmops = GMAP_OPS(me);
290 unsigned f;
291 void *e = gmops->lookup(me, key, &f);
292 int rc = -1;
293
294 if (!e) goto end;
295 if (!value)
296 rc = gmops->del_entry(me, e);
297 else {
298 rc = gmops->set_entry(me, e, value);
299 if (rc && !f) gmops->del_entry(me, e);
300 }
301 rc = 0;
302end:
303 return (rc);
304}
305
306int gmap_pyhaskey(PyObject *me, PyObject *key)
307 { return (GMAP_OPS(me)->lookup(me, key, 0) ? 1 : PyErr_Occurred() ? -1 : 0); }
308
87fa2d56 309const PySequenceMethods gmap_pysequence = {
c1756f78
MW
310 0, /* @sq_length@ */
311 0, /* @sq_concat@ */
312 0, /* @sq_repeat@ */
313 0, /* @sq_item@ */
314 0, /* @sq_slice@ */
315 0, /* @sq_ass_item@ */
316 0, /* @sq_ass_slice@ */
78daa0e0 317 gmap_pyhaskey, /* @sq_contains@ */
c1756f78
MW
318 0, /* @sq_inplace_concat@ */
319 0 /* @sq_inplace_repeat@ */
320};
321
c1756f78
MW
322PyObject *gmapmeth_has_key(PyObject *me, PyObject *arg)
323{
324 PyObject *k;
78daa0e0 325 void *e;
c1756f78 326 if (!PyArg_ParseTuple(arg, "O:has_key", &k)) return (0);
78daa0e0
MW
327 e = GMAP_OPS(me)->lookup(me, k, 0);
328 if (e) RETURN_TRUE;
329 else if (!PyErr_Occurred()) RETURN_FALSE;
330 else return (0);
c1756f78
MW
331}
332
138563a5 333PyObject *gmapmeth_keys(PyObject *me)
c1756f78 334{
78daa0e0
MW
335 const gmap_ops *gmops = GMAP_OPS(me);
336 union iterstate iter; void *i = 0, *e;
337 PyObject *l = 0, *k, *rc = 0;
c1756f78
MW
338 int err;
339
78daa0e0
MW
340 if ((l = PyList_New(0)) == 0) goto done;
341 i = iter_init(me, &iter);
342 while ((e = gmops->iter_next(me, i)) != 0) {
343 k = gmops->entry_key(me, e);
344 err = PyList_Append(l, k);
345 Py_DECREF(k);
346 if (err) goto done;
347 }
c1756f78
MW
348 rc = l; l = 0;
349done:
78daa0e0
MW
350 Py_XDECREF(l);
351 if (i) iter_free(me, &iter);
c1756f78
MW
352 return (rc);
353}
354
138563a5 355PyObject *gmapmeth_values(PyObject *me)
c1756f78 356{
78daa0e0
MW
357 const gmap_ops *gmops = GMAP_OPS(me);
358 union iterstate iter; void *i = 0, *e;
359 PyObject *l = 0, *v, *rc = 0;
360 int err;
361
362 if ((l = PyList_New(0)) == 0) goto done;
363 i = iter_init(me, &iter);
364 while ((e = gmops->iter_next(me, i)) != 0) {
365 v = gmops->entry_value(me, e);
366 err = PyList_Append(l, v);
367 Py_DECREF(v);
c1756f78
MW
368 if (err) goto done;
369 }
c1756f78
MW
370 rc = l; l = 0;
371done:
78daa0e0
MW
372 Py_XDECREF(l);
373 if (i) iter_free(me, &iter);
c1756f78
MW
374 return (rc);
375}
376
138563a5 377PyObject *gmapmeth_items(PyObject *me)
c1756f78 378{
78daa0e0
MW
379 const gmap_ops *gmops = GMAP_OPS(me);
380 union iterstate iter; void *i = 0, *e;
381 PyObject *l = 0, *z, *rc = 0;
382 int err;
383
384 if ((l = PyList_New(0)) == 0) goto done;
385 i = iter_init(me, &iter);
386 while ((e = gmops->iter_next(me, i)) != 0) {
387 if ((z = Py_BuildValue("(NN)",
388 gmops->entry_key(me, e),
389 gmops->entry_value(me, e))) == 0)
390 goto done;
391 err = PyList_Append(l, z);
392 Py_XDECREF(z);
c1756f78
MW
393 if (err) goto done;
394 }
c1756f78
MW
395 rc = l; l = 0;
396done:
78daa0e0
MW
397 Py_XDECREF(l);
398 if (i) iter_free(me, &iter);
c1756f78
MW
399 return (rc);
400}
401
138563a5 402PyObject *gmapmeth_iterkeys(PyObject *me)
78daa0e0 403 { return (gmap_mkiter(me, keyiter_pytype)); }
c1756f78 404
138563a5 405PyObject *gmapmeth_itervalues(PyObject *me)
78daa0e0 406 { return (gmap_mkiter(me, valiter_pytype)); }
c1756f78 407
138563a5 408PyObject *gmapmeth_iteritems(PyObject *me)
78daa0e0 409 { return (gmap_mkiter(me, itemiter_pytype)); }
c1756f78 410
78daa0e0
MW
411PyObject *gmap_pyiter(PyObject *me)
412 { return gmap_mkiter(me, keyiter_pytype); }
c1756f78 413
138563a5 414PyObject *gmapmeth_clear(PyObject *me)
c1756f78 415{
78daa0e0
MW
416 const gmap_ops *gmops = GMAP_OPS(me);
417 union iterstate iter;
418 void *i, *e;
419 PyObject *rc = 0;
420
421 i = iter_init(me, &iter);
422 for (;;) {
423 e = gmops->iter_next(me, i); if (!e) break;
424 if (gmops->del_entry(me, e)) goto end;
c1756f78 425 }
78daa0e0 426 iter_free(me, &iter);
c1756f78
MW
427 rc = me; Py_INCREF(me);
428end:
c1756f78
MW
429 return (rc);
430}
431
432static const char *const def_kwlist[] = { "key", "default", 0 };
85d110a4 433#define DEF_KWLIST ((/*unconst*/ char **)def_kwlist)
c1756f78
MW
434
435PyObject *gmapmeth_get(PyObject *me, PyObject *arg, PyObject *kw)
436{
78daa0e0
MW
437 const gmap_ops *gmops = GMAP_OPS(me);
438 PyObject *k, *def = Py_None;
439 void *e;
c1756f78 440
85d110a4 441 if (!PyArg_ParseTupleAndKeywords(arg, kw, "O|O:get", DEF_KWLIST, &k, &def))
c1756f78 442 return (0);
78daa0e0
MW
443 e = gmops->lookup(me, k, 0);
444 if (e) return (gmops->entry_value(me, e));
445 else if (!PyErr_Occurred()) RETURN_OBJ(def);
446 else return (0);
c1756f78
MW
447}
448
449PyObject *gmapmeth_setdefault(PyObject *me, PyObject *arg, PyObject *kw)
450{
78daa0e0
MW
451 const gmap_ops *gmops = GMAP_OPS(me);
452 PyObject *k, *def = Py_None;
453 void *e;
454 unsigned f;
c1756f78 455
85d110a4 456 if (!PyArg_ParseTupleAndKeywords(arg, kw, "O|O:setdefault", DEF_KWLIST,
c1756f78
MW
457 &k, &def))
458 return (0);
78daa0e0
MW
459 e = gmops->lookup(me, k, &f);
460 if (!e) return (0);
461 else if (f) return (gmops->entry_value(me, e));
462 else if (gmops->set_entry(me, e, def)) return (0);
463 else RETURN_OBJ(def);
c1756f78
MW
464}
465
466PyObject *gmapmeth_pop(PyObject *me, PyObject *arg, PyObject *kw)
467{
78daa0e0
MW
468 const gmap_ops *gmops = GMAP_OPS(me);
469 PyObject *k, *def = 0;
470 PyObject *rc = 0;
471 void *e;
c1756f78 472
85d110a4 473 if (!PyArg_ParseTupleAndKeywords(arg, kw, "O|O:pop", DEF_KWLIST, &k, &def))
78daa0e0
MW
474 goto end;
475 e = gmops->lookup(me, k, 0);
476 if (!e) {
477 if (PyErr_Occurred()) goto end;
478 else if (def) { rc = def; Py_INCREF(rc); }
479 else MAPERR(k);
480 } else {
481 rc = gmops->entry_value(me, e);
482 if (gmops->del_entry(me, e)) { Py_DECREF(rc); rc = 0; }
483 }
484end:
485 return (rc);
c1756f78
MW
486}
487
78daa0e0 488static int update_core(PyObject *me, PyObject *map)
c1756f78 489{
78daa0e0
MW
490 const gmap_ops *gmops = GMAP_OPS(me);
491 PyObject *i = 0, *item = 0, *k = 0, *v = 0;
492 void *e;
493 unsigned foundp;
494 int rc = -1;
495
496 i = PyObject_CallMethod(map, "iteritems", 0);
497
498 if (i) {
499 for (;;) {
500 item = PyIter_Next(i); if (!item) break;
501 if (!PyTuple_Check(item) || PyTuple_GET_SIZE(item) != 2)
502 TYERR("wanted a pair");
503 k = PyTuple_GET_ITEM(item, 0); Py_INCREF(k);
504 v = PyTuple_GET_ITEM(item, 1); Py_INCREF(v);
505 e = gmops->lookup(me, k, &foundp); if (!e) goto end;
506 if (gmops->set_entry(me, e, v)) goto end;
507 Py_DECREF(item); Py_DECREF(k); Py_DECREF(v); item = k = v = 0;
508 }
509 if (PyErr_Occurred()) goto end;
510 } else {
511 PyErr_Clear();
512 i = PyObject_GetIter(map); if (!i) goto end;
513 for (;;) {
514 k = PyIter_Next(i); if (!k) goto end;
515 v = PyObject_GetItem(map, k); if (!v) goto end;
516 e = gmops->lookup(me, k, &foundp); if (!e) goto end;
517 if (gmops->set_entry(me, e, v)) goto end;
518 Py_DECREF(k); Py_DECREF(v); k = v = 0;
519 }
520 if (PyErr_Occurred()) goto end;
c1756f78 521 }
78daa0e0 522 rc = 0;
c1756f78 523end:
78daa0e0
MW
524 Py_XDECREF(i); Py_XDECREF(item);
525 Py_XDECREF(k); Py_XDECREF(v);
c1756f78
MW
526 return (rc);
527}
528
78daa0e0 529PyObject *gmapmeth_update(PyObject *me, PyObject *arg, PyObject *kw)
c1756f78 530{
78daa0e0 531 PyObject *map = 0;
c1756f78 532
78daa0e0
MW
533 if (!PyArg_ParseTuple(arg, "|O:update", &map)) return (0);
534 if (map && update_core(me, map)) return (0);
535 if (kw && update_core(me, kw)) return (0);
536 RETURN_ME;
537}
538
539PyObject *gmapmeth_popitem(PyObject *me)
540{
541 const gmap_ops *gmops = GMAP_OPS(me);
542 union iterstate iter;
543 void *i;
544 PyObject *rc = 0;
545 void *e;
546
547 i = iter_init(me, &iter);
548 e = gmops->iter_next(me, i);
549 iter_free(me, &iter);
550 if (!e)
551 MAPERR(Py_None);
552 else {
553 rc = Py_BuildValue("(NN)",
554 gmops->entry_key(me, e), gmops->entry_value(me, e));
555 if (gmops->del_entry(me, e)) { Py_DECREF(rc); rc = 0; }
c1756f78 556 }
c1756f78 557end:
c1756f78
MW
558 return (rc);
559}
560
78daa0e0
MW
561const PyMethodDef gmapro_pymethods[] = {
562 GMAP_ROMETHODS
563 { 0 }
564};
565
87fa2d56 566const PyMethodDef gmap_pymethods[] = {
c1756f78
MW
567 GMAP_METHODS
568 { 0 }
569};
570
571/*----- Submodule initialization ------------------------------------------*/
572
573void pyke_gmap_pyinit(void)
574{
78daa0e0 575 INITTYPE(keyiter, root);
c1756f78
MW
576 INITTYPE(itemiter, root);
577 INITTYPE(valiter, root);
578}
579
580void pyke_gmap_pyinsert(PyObject *mod)
581{
78daa0e0
MW
582 INSERT("_KeyIter", keyiter_pytype);
583 INSERT("_ValueIter", valiter_pytype);
584 INSERT("_ItemIter", itemiter_pytype);
c1756f78
MW
585}
586
587/*----- That's all, folks -------------------------------------------------*/