chiark / gitweb /
@@@ mLib-python Pyke wip
[mLib-python] / atom.c
1 /* -*-c-*-
2  *
3  * Atoms and obarrays
4  *
5  * (c) 2019 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of the Python interface to mLib.
11  *
12  * mLib/Python is free software: you can redistribute it and/or modify it
13  * under the terms of the GNU General Public License as published by the
14  * Free Software Foundation; either version 2 of the License, or (at your
15  * option) any later version.
16  *
17  * mLib/Python is distributed in the hope that it will be useful, but
18  * WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with mLib/Python.  If not, write to the Free Software
24  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
25  * USA.
26  */
27
28 /*----- Header files ------------------------------------------------------*/
29
30 #include "mLib-python.h"
31
32 /*----- Atoms -------------------------------------------------------------*/
33
34 typedef struct {
35   PyObject_HEAD
36   atom *a;
37   PyObject *oawk;
38 } atom_pyobj;
39 static PyTypeObject *atom_pytype;
40 #define ATOM_PYCHECK(o) PyObject_TypeCheck((o), atom_pytype)
41 #define ATOM_A(o) (((atom_pyobj *)(o))->a)
42 #define ATOM_OAWK(o) (((atom_pyobj *)(o))->oawk)
43 #define ATOM_OAOBJ(o) (PyWeakref_GET_OBJECT(ATOM_OAWK(o)))
44
45 struct obentry {
46   assoc_base _b;
47   PyObject *aobj;
48 };
49
50 typedef struct {
51   GMAP_PYOBJ_HEAD
52   atom_table tab;
53   assoc_table map;
54   PyObject *wkls;
55 } obarray_pyobj;
56 static PyTypeObject *obarray_pytype;
57 #define OBARRAY_PYCHECK(o) PyObject_TypeCheck((o), obarray_pytype)
58 #define OBARRAY_TAB(o) (&((obarray_pyobj *)(o))->tab)
59 #define OBARRAY_MAP(o) (&((obarray_pyobj *)(o))->map)
60
61 static PyObject *default_obarray(void)
62 {
63   PyObject *oa = 0, *rc = 0;
64
65   if (!home_module) SYSERR("home module not set");
66   oa = PyObject_GetAttrString(home_module, "DEFAULT_ATOMTABLE");
67   if (!oa) goto end;
68   if (!OBARRAY_PYCHECK(oa)) TYERR("DEFAULT_ATOMTABLE isn't an AtomTable");
69   rc = oa; oa = 0;
70 end:
71   Py_XDECREF(oa);
72   return (rc);
73 }
74
75 static PyObject *atom_pywrap(PyObject *oaobj, atom *a)
76 {
77   struct obentry *e;
78   PyObject *oawk = 0, *rc = 0;
79   atom_pyobj *aobj;
80   unsigned f;
81
82   e = assoc_find(OBARRAY_MAP(oaobj), a, sizeof(*e), &f);
83   if (f)
84     rc = e->aobj;
85   else {
86     oawk = PyWeakref_NewRef(oaobj, 0); if (!oawk) goto end;
87     aobj = PyObject_NEW(atom_pyobj, atom_pytype);
88     aobj->a = a;
89     aobj->oawk = oawk; oawk = 0;
90     e->aobj = (PyObject *)aobj;
91     rc = (PyObject *)aobj;
92   }
93   Py_INCREF(rc);
94 end:
95   Py_XDECREF(oawk);
96   if (e && !rc) assoc_remove(OBARRAY_MAP(oaobj), e);
97   return (rc);
98 }
99
100 static PyObject *atom_pyintern(PyObject *oaobj, PyObject *x)
101 {
102   atom *a;
103   const char *p;
104   size_t sz;
105   PyObject *rc = 0;
106
107   if (ATOM_PYCHECK(x)) {
108     if (ATOM_OAOBJ(x) != oaobj) VALERR("wrong table for existing atom");
109     RETURN_OBJ(x);
110   }
111   if (x == Py_None)
112     a = atom_gensym(OBARRAY_TAB(oaobj));
113   else if (TEXT_CHECK(x))
114     { TEXT_PTRLEN(x, p, sz); a = atom_nintern(OBARRAY_TAB(oaobj), p, sz); }
115   else
116     TYERR("expected string or `None'");
117   rc = atom_pywrap(oaobj, a);
118 end:
119   return (rc);
120 }
121
122 static PyObject *atom_pynew(PyTypeObject *cls, PyObject *arg, PyObject *kw)
123 {
124   static const char *const kwlist[] = { "name", "table", 0 };
125   PyObject *name = Py_None, *oaobj = 0, *rc = 0;
126
127   if (!PyArg_ParseTupleAndKeywords(arg, kw, "|OO!:new", KWLIST,
128                                    &name, obarray_pytype, &oaobj))
129     { oaobj = 0; goto end; }
130   if (oaobj) Py_INCREF(oaobj);
131   else { oaobj = default_obarray(); if (!oaobj) goto end; }
132   rc = atom_pyintern(oaobj, name);
133 end:
134   Py_XDECREF(oaobj);
135   return (rc);
136 }
137
138 static void atom_pydealloc(PyObject *me)
139   { assert(!ATOM_OAWK(me)); FREEOBJ(me); }
140
141 static int atom_check(PyObject *me)
142 {
143   if (!ATOM_A(me)) VALERR("atom is stale");
144   return (0);
145 end:
146   return (-1);
147 }
148
149 static PyObject *atomget_name(PyObject *me, void *hunoz)
150 {
151   atom *a;
152
153   if (atom_check(me)) return (0);
154   a = ATOM_A(me);  return (TEXT_FROMSTRLEN(ATOM_NAME(a), ATOM_LEN(a)));
155 }
156
157 static PyObject *atomget_home(PyObject *me, void *hunoz)
158 {
159   PyObject *rc;
160
161   if (atom_check(me)) return (0);
162   rc = ATOM_OAOBJ(me); assert(rc != Py_None); RETURN_OBJ(rc);
163 }
164
165 static PyObject *atomget_internedp(PyObject *me, void *hunoz)
166 {
167   if (atom_check(me)) return (0);
168   return (getbool(!(ATOM_A(me)->f&ATOMF_GENSYM)));
169 }
170
171 static PyObject *atomget_livep(PyObject *me, void *hunoz)
172   { return (getbool(!!ATOM_A(me))); }
173
174 static PyObject *atom_pyrichcompare(PyObject *x, PyObject *y, int op)
175 {
176   int r;
177
178   switch (op) {
179     case Py_EQ: r = (x == y); break;
180     case Py_NE: r = (x != y); break;
181     default: TYERR("atoms are unordered");
182   }
183   return (getbool(r));
184 end:
185   return (0);
186 }
187
188 static Py_hash_t atom_pyhash(PyObject *me)
189   { return (atom_check(me) ? -1 : ATOM_HASH(ATOM_A(me))); }
190
191 static const PyGetSetDef atom_pygetset[] = {
192 #define GETSETNAME(op, name) atom##op##_##name
193   GET   (name,          "A.name -> STR: atom name")
194   GET   (home,          "A.home -> ATAB: atom home table")
195   GET   (internedp,     "A.internedp -> BOOL: atom interned (not gensym)?")
196   GET   (livep,         "A.livep -> BOOL: atom table still alive?")
197 #undef GETSETNAME
198   { 0 }
199 };
200
201 static const PyTypeObject atom_pytype_skel = {
202   PyVarObject_HEAD_INIT(0, 0)           /* Header */
203   "Atom",                               /* @tp_name@ */
204   sizeof(atom_pyobj),                   /* @tp_basicsize@ */
205   0,                                    /* @tp_itemsize@ */
206
207   atom_pydealloc,                       /* @tp_dealloc@ */
208   0,                                    /* @tp_print@ */
209   0,                                    /* @tp_getattr@ */
210   0,                                    /* @tp_setattr@ */
211   0,                                    /* @tp_compare@ */
212   0,                                    /* @tp_repr@ */
213   0,                                    /* @tp_as_number@ */
214   0,                                    /* @tp_as_sequence@ */
215   0,                                    /* @tp_as_mapping@ */
216   atom_pyhash,                          /* @tp_hash@ */
217   0,                                    /* @tp_call@ */
218   0,                                    /* @tp_str@ */
219   0,                                    /* @tp_getattro@ */
220   0,                                    /* @tp_setattro@ */
221   0,                                    /* @tp_as_buffer@ */
222   Py_TPFLAGS_DEFAULT |                  /* @tp_flags@ */
223     Py_TPFLAGS_BASETYPE,
224
225   /* @tp_doc@ */
226   "Atom([name = None], [table = DEFAULT_ATOMTABLE])",
227
228   0,                                    /* @tp_traverse@ */
229   0,                                    /* @tp_clear@ */
230   atom_pyrichcompare,                   /* @tp_richcompare@ */
231   0,                                    /* @tp_weaklistoffset@ */
232   0,                                    /* @tp_iter@ */
233   0,                                    /* @tp_iternext@ */
234   0,                                    /* @tp_methods@ */
235   0,                                    /* @tp_members@ */
236   PYGETSET(atom),                       /* @tp_getset@ */
237   0,                                    /* @tp_base@ */
238   0,                                    /* @tp_dict@ */
239   0,                                    /* @tp_descr_get@ */
240   0,                                    /* @tp_descr_set@ */
241   0,                                    /* @tp_dictoffset@ */
242   0,                                    /* @tp_init@ */
243   PyType_GenericAlloc,                  /* @tp_alloc@ */
244   atom_pynew,                           /* @tp_new@ */
245   0,                                    /* @tp_free@ */
246   0                                     /* @tp_is_gc@ */
247 };
248
249 static void *obarray_gmlookup(PyObject *me, PyObject *key, unsigned *f)
250 {
251   atom *a = 0;
252   const char *p;
253   size_t sz;
254
255   if (!TEXT_CHECK(key)) TYERR("expected string");
256   TEXT_PTRLEN(key, p, sz); a = sym_find(&OBARRAY_TAB(me)->t, p, sz, 0, f);
257 end:
258   return (a);
259 }
260
261 static void obarray_gmiterinit(PyObject *me, void *i)
262   { atom_mkiter(i, OBARRAY_TAB(me)); }
263
264 static void *obarray_gmiternext(PyObject *me, void *i)
265   { return (atom_next(i)); }
266
267 static PyObject *obarray_gmentrykey(PyObject *me, void *e)
268   { return (TEXT_FROMSTRLEN(ATOM_NAME(e), ATOM_LEN(e))); }
269
270 static PyObject *obarray_gmentryvalue(PyObject *me, void *e)
271   { return (atom_pywrap(me, e)); }
272
273 static const gmap_ops obarray_gmops = {
274   sizeof(atom_iter),
275   obarray_gmlookup,
276   obarray_gmiterinit,
277   obarray_gmiternext,
278   obarray_gmentrykey,
279   obarray_gmentryvalue
280 };
281
282 static PyObject *obarray_pynew(PyTypeObject *cls,
283                                PyObject *arg, PyObject *kw)
284 {
285   obarray_pyobj *rc = 0;
286   static const char *const kwlist[] = { 0 };
287
288   if (!PyArg_ParseTupleAndKeywords(arg, kw, ":new", KWLIST)) goto end;
289   rc = PyObject_NEW(obarray_pyobj, cls); if (!rc) goto end;
290   rc->gmops = &obarray_gmops; rc->wkls = 0;
291   atom_createtable(&rc->tab); assoc_create(&rc->map);
292 end:
293   return ((PyObject *)rc);
294 }
295
296 static void obarray_pydealloc(PyObject *me)
297 {
298   assoc_iter it;
299   struct obentry *e;
300
301   ASSOC_MKITER(&it, OBARRAY_MAP(me));
302   for (;;) {
303     ASSOC_NEXT(&it, e); if (!e) break;
304     ATOM_A(e->aobj) = 0;
305     Py_DECREF(ATOM_OAWK(e->aobj)); ATOM_OAWK(e->aobj) = 0;
306     Py_DECREF(e->aobj);
307   }
308   assoc_destroy(OBARRAY_MAP(me));
309   atom_destroytable(OBARRAY_TAB(me));
310   FREEOBJ(me);
311 }
312
313 static PyObject *oameth_intern(PyObject *me, PyObject *arg)
314 {
315   char *p;
316   Py_ssize_t sz;
317   atom *a;
318   PyObject *rc = 0;
319
320   if (!PyArg_ParseTuple(arg, "s#:intern", &p, &sz)) goto end;
321   a = atom_nintern(OBARRAY_TAB(me), p, sz);
322   rc = atom_pywrap(me, a);
323 end:
324   return (rc);
325 }
326
327 static PyObject *oameth_gensym(PyObject *me)
328   { return (atom_pywrap(me, atom_gensym(OBARRAY_TAB(me)))); }
329
330 static const PyMappingMethods obarray_pymapping = {
331   gmap_pysize,
332   atom_pyintern,
333   0
334 };
335
336 static const PyMethodDef obarray_pymethods[] = {
337   GMAP_ROMETHODS
338 #define METHNAME(name) oameth_##name
339   METH  (intern,        "ATAB.intern(STR) -> A: atom with given name")
340   NAMETH(gensym,        "ATAB.gensym() -> A: fresh uninterned atom")
341 #undef METHNAME
342   { 0 }
343 };
344
345 static const PyTypeObject obarray_pytype_skel = {
346   PyVarObject_HEAD_INIT(0, 0)           /* Header */
347   "AtomTable",                          /* @tp_name@ */
348   sizeof(obarray_pyobj),                /* @tp_basicsize@ */
349   0,                                    /* @tp_itemsize@ */
350
351   obarray_pydealloc,                    /* @tp_dealloc@ */
352   0,                                    /* @tp_print@ */
353   0,                                    /* @tp_getattr@ */
354   0,                                    /* @tp_setattr@ */
355   0,                                    /* @tp_compare@ */
356   0,                                    /* @tp_repr@ */
357   0,                                    /* @tp_as_number@ */
358   PYSEQUENCE(gmap),                     /* @tp_as_sequence@ */
359   PYMAPPING(obarray),                   /* @tp_as_mapping@ */
360   0,                                    /* @tp_hash@ */
361   0,                                    /* @tp_call@ */
362   0,                                    /* @tp_str@ */
363   0,                                    /* @tp_getattro@ */
364   0,                                    /* @tp_setattro@ */
365   0,                                    /* @tp_as_buffer@ */
366   Py_TPFLAGS_DEFAULT |                  /* @tp_flags@ */
367     Py_TPFLAGS_BASETYPE,
368
369   /* @tp_doc@ */
370   "AtomTable()",
371
372   0,                                    /* @tp_traverse@ */
373   0,                                    /* @tp_clear@ */
374   0,                                    /* @tp_richcompare@ */
375   offsetof(obarray_pyobj, wkls),        /* @tp_weaklistoffset@ */
376   gmap_pyiter,                          /* @tp_iter@ */
377   0,                                    /* @tp_iternext@ */
378   PYMETHODS(obarray),                   /* @tp_methods@ */
379   0,                                    /* @tp_members@ */
380   0,                                    /* @tp_getset@ */
381   0,                                    /* @tp_base@ */
382   0,                                    /* @tp_dict@ */
383   0,                                    /* @tp_descr_get@ */
384   0,                                    /* @tp_descr_set@ */
385   0,                                    /* @tp_dictoffset@ */
386   0,                                    /* @tp_init@ */
387   PyType_GenericAlloc,                  /* @tp_alloc@ */
388   obarray_pynew,                        /* @tp_new@ */
389   0,                                    /* @tp_free@ */
390   0                                     /* @tp_is_gc@ */
391 };
392
393 /*----- Association tables ------------------------------------------------*/
394
395 typedef struct {
396   GMAP_PYOBJ_HEAD
397   assoc_table t;
398   PyObject *oaobj;
399 } assoc_pyobj;
400 static PyTypeObject *assoc_pytype;
401 #define ASSOC_PYCHECK(o) PyObject_TypeCheck((o), assoc_pytype)
402 #define ASSOC_T(o) (&((assoc_pyobj *)(o))->t)
403 #define ASSOC_OAOBJ(o) (((assoc_pyobj *)(o))->oaobj)
404
405 struct aentry {
406   assoc_base _b;
407   PyObject *obj;
408 };
409
410 static void *assoc_gmlookup(PyObject *me, PyObject *key, unsigned *f)
411 {
412   struct aentry *e = 0;
413   char *p;
414   size_t sz;
415   atom *a;
416
417   if (TEXT_CHECK(key)) {
418     TEXT_PTRLEN(key, p, sz);
419     a = atom_nintern(OBARRAY_TAB(ASSOC_OAOBJ(me)), p, sz);
420   } else if (ATOM_PYCHECK(key)) {
421     if (atom_check(key)) goto end;
422     if (ATOM_OAOBJ(key) != ASSOC_OAOBJ(me))
423       VALERR("wrong atom table for assoc");
424     a = ATOM_A(key);
425   } else
426     TYERR("expected atom or string");
427   e = assoc_find(ASSOC_T(me), a, f ? sizeof(*e) : 0, f);
428   if (!e) goto end;
429   if (f && !*f) e->obj = 0;
430 end:
431   return (e);
432 }
433
434 static void assoc_gmiterinit(PyObject *me, void *i)
435   { assoc_iter *it = i; ASSOC_MKITER(it, ASSOC_T(me)); }
436
437 static void *assoc_gmiternext(PyObject *me, void *i)
438   { assoc_iter *it = i; void *e; ASSOC_NEXT(it, e); return (e); }
439
440 static PyObject *assoc_gmentrykey(PyObject *me, void *e)
441   { return (atom_pywrap(ASSOC_OAOBJ(me), ASSOC_ATOM(e))); }
442
443 static PyObject *assoc_gmentryvalue(PyObject *me, void *e)
444   { struct aentry *ae = e; RETURN_OBJ(ae->obj); }
445
446 static int assoc_gmsetentry(PyObject *me, void *e, PyObject *val)
447 {
448   struct aentry *ae = e;
449
450   Py_XDECREF(ae->obj); Py_INCREF(val); ae->obj = val;
451   return (0);
452 }
453
454 static int assoc_gmdelentry(PyObject *me, void *e)
455   { assoc_remove(ASSOC_T(me), e); return (0); }
456
457 static const gmap_ops assoc_gmops = {
458   sizeof(atom_iter),
459   assoc_gmlookup,
460   assoc_gmiterinit,
461   assoc_gmiternext,
462   assoc_gmentrykey,
463   assoc_gmentryvalue,
464   assoc_gmsetentry,
465   assoc_gmdelentry
466 };
467
468 static PyObject *assoc_pynew(PyTypeObject *cls, PyObject *arg, PyObject *kw)
469 {
470   PyObject *oaobj = 0, *map = Py_None;
471   assoc_pyobj *me = 0;
472
473   if (!PyArg_ParseTuple(arg, "|OO!:new", &map, obarray_pytype, &oaobj))
474     { oaobj = 0; goto end; }
475   if (oaobj) Py_INCREF(oaobj);
476   else { oaobj = default_obarray(); if (!oaobj) goto end; }
477   me = PyObject_NEW(assoc_pyobj, cls);
478   me->gmops = &assoc_gmops;
479   assoc_create(&me->t);
480   me->oaobj = oaobj; oaobj = 0;
481   if ((map != Py_None && gmap_pyupdate((PyObject *)me, map)) ||
482       gmap_pyupdate((PyObject *)me, kw))
483     { Py_DECREF(me); me = 0; goto end; }
484 end:
485   Py_XDECREF(oaobj);
486   return ((PyObject *)me);
487 }
488
489 static void assoc_pydealloc(PyObject *me)
490 {
491   assoc_iter it;
492   struct aentry *ae;
493
494   ASSOC_MKITER(&it, ASSOC_T(me));
495   for (;;) {
496     ASSOC_NEXT(&it, ae); if (!ae) break;
497     Py_DECREF(ae->obj);
498   }
499   Py_DECREF(ASSOC_OAOBJ(me));
500   FREEOBJ(me);
501 }
502
503 static Py_ssize_t assoc_pysize(PyObject *me)
504 {
505   assoc_table *t = ASSOC_T(me);
506   return (SYM_LIMIT(t->t.mask + 1) - t->load);
507 }
508
509 static const PyMemberDef assoc_pymembers[] = {
510 #define MEMBERSTRUCT assoc_pyobj
511   MEMRNM(table, T_OBJECT, oaobj, READONLY,
512                                          "AS.table -> ATAB: home atom table")
513 #undef MEMBERSTRUCT
514   { 0 }
515 };
516
517 static const PyMappingMethods assoc_pymapping = {
518   assoc_pysize,
519   gmap_pylookup,
520   gmap_pystore
521 };
522
523 static const PyTypeObject assoc_pytype_skel = {
524   PyVarObject_HEAD_INIT(0, 0)           /* Header */
525   "AssocTable",                         /* @tp_name@ */
526   sizeof(assoc_pyobj),                  /* @tp_basicsize@ */
527   0,                                    /* @tp_itemsize@ */
528
529   assoc_pydealloc,                      /* @tp_dealloc@ */
530   0,                                    /* @tp_print@ */
531   0,                                    /* @tp_getattr@ */
532   0,                                    /* @tp_setattr@ */
533   0,                                    /* @tp_compare@ */
534   0,                                    /* @tp_repr@ */
535   0,                                    /* @tp_as_number@ */
536   PYSEQUENCE(gmap),                     /* @tp_as_sequence@ */
537   PYMAPPING(assoc),                     /* @tp_as_mapping@ */
538   0,                                    /* @tp_hash@ */
539   0,                                    /* @tp_call@ */
540   0,                                    /* @tp_str@ */
541   0,                                    /* @tp_getattro@ */
542   0,                                    /* @tp_setattro@ */
543   0,                                    /* @tp_as_buffer@ */
544   Py_TPFLAGS_DEFAULT |                  /* @tp_flags@ */
545     Py_TPFLAGS_BASETYPE,
546
547   /* @tp_doc@ */
548   "AssocTable([MAP], [ATAB], [ATOM = VALUE, ...])",
549
550   0,                                    /* @tp_traverse@ */
551   0,                                    /* @tp_clear@ */
552   0,                                    /* @tp_richcompare@ */
553   0,                                    /* @tp_weaklistoffset@ */
554   gmap_pyiter,                          /* @tp_iter@ */
555   0,                                    /* @tp_iternext@ */
556   PYMETHODS(gmap),                      /* @tp_methods@ */
557   PYMEMBERS(assoc),                     /* @tp_members@ */
558   0,                                    /* @tp_getset@ */
559   0,                                    /* @tp_base@ */
560   0,                                    /* @tp_dict@ */
561   0,                                    /* @tp_descr_get@ */
562   0,                                    /* @tp_descr_set@ */
563   0,                                    /* @tp_dictoffset@ */
564   0,                                    /* @tp_init@ */
565   PyType_GenericAlloc,                  /* @tp_alloc@ */
566   assoc_pynew,                          /* @tp_new@ */
567   0,                                    /* @tp_free@ */
568   0                                     /* @tp_is_gc@ */
569 };
570
571 /*----- Main code ---------------------------------------------------------*/
572
573 void atom_pyinit(void)
574 {
575   INITTYPE(atom, root);
576   INITTYPE(obarray, root);
577   INITTYPE(assoc, root);
578 }
579
580 void atom_pyinsert(PyObject *mod)
581 {
582   INSERT("Atom", atom_pytype);
583   INSERT("AtomTable", obarray_pytype);
584   INSERT("AssocTable", assoc_pytype);
585 };
586
587 /*----- That's all, folks -------------------------------------------------*/