+/* -*-c-*-
+ *
+ * Atoms and obarrays
+ *
+ * (c) 2019 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the Python interface to mLib.
+ *
+ * mLib/Python is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * mLib/Python is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with mLib/Python. If not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "mLib-python.h"
+
+/*----- Atoms -------------------------------------------------------------*/
+
+typedef struct {
+ PyObject_HEAD
+ atom *a;
+ PyObject *oawk;
+} atom_pyobj;
+static PyTypeObject *atom_pytype;
+#define ATOM_PYCHECK(o) PyObject_TypeCheck((o), atom_pytype)
+#define ATOM_A(o) (((atom_pyobj *)(o))->a)
+#define ATOM_OAWK(o) (((atom_pyobj *)(o))->oawk)
+#define ATOM_OAOBJ(o) (PyWeakref_GET_OBJECT(ATOM_OAWK(o)))
+
+struct obentry {
+ assoc_base _b;
+ PyObject *aobj;
+};
+
+typedef struct {
+ GMAP_PYOBJ_HEAD
+ atom_table tab;
+ assoc_table map;
+ PyObject *wkls;
+} obarray_pyobj;
+static PyTypeObject *obarray_pytype;
+#define OBARRAY_PYCHECK(o) PyObject_TypeCheck((o), obarray_pytype)
+#define OBARRAY_TAB(o) (&((obarray_pyobj *)(o))->tab)
+#define OBARRAY_MAP(o) (&((obarray_pyobj *)(o))->map)
+
+static PyObject *default_obarray(void)
+{
+ PyObject *oa = 0, *rc = 0;
+
+ if (!home_module) SYSERR("home module not set");
+ oa = PyObject_GetAttrString(home_module, "DEFAULT_ATOMTABLE");
+ if (!oa) goto end;
+ if (!OBARRAY_PYCHECK(oa)) TYERR("DEFAULT_ATOMTABLE isn't an AtomTable");
+ rc = oa; oa = 0;
+end:
+ Py_XDECREF(oa);
+ return (rc);
+}
+
+static PyObject *atom_pywrap(PyObject *oaobj, atom *a)
+{
+ struct obentry *e;
+ PyObject *oawk = 0, *rc = 0;
+ atom_pyobj *aobj;
+ unsigned f;
+
+ e = assoc_find(OBARRAY_MAP(oaobj), a, sizeof(*e), &f);
+ if (f)
+ rc = e->aobj;
+ else {
+ oawk = PyWeakref_NewRef(oaobj, 0); if (!oawk) goto end;
+ aobj = PyObject_NEW(atom_pyobj, atom_pytype);
+ aobj->a = a;
+ aobj->oawk = oawk; oawk = 0;
+ e->aobj = (PyObject *)aobj;
+ rc = (PyObject *)aobj;
+ }
+ Py_INCREF(rc);
+end:
+ Py_XDECREF(oawk);
+ if (e && !rc) assoc_remove(OBARRAY_MAP(oaobj), e);
+ return (rc);
+}
+
+static PyObject *atom_pyintern(PyObject *oaobj, PyObject *x)
+{
+ atom *a;
+ const char *p;
+ size_t sz;
+ PyObject *rc = 0;
+
+ if (ATOM_PYCHECK(x)) {
+ if (ATOM_OAOBJ(x) != oaobj) VALERR("wrong table for existing atom");
+ RETURN_OBJ(x);
+ }
+ if (x == Py_None)
+ a = atom_gensym(OBARRAY_TAB(oaobj));
+ else if (TEXT_CHECK(x))
+ { TEXT_PTRLEN(x, p, sz); a = atom_nintern(OBARRAY_TAB(oaobj), p, sz); }
+ else
+ TYERR("expected string or `None'");
+ rc = atom_pywrap(oaobj, a);
+end:
+ return (rc);
+}
+
+static PyObject *atom_pynew(PyTypeObject *cls, PyObject *arg, PyObject *kw)
+{
+ static const char *const kwlist[] = { "name", "table", 0 };
+ PyObject *name = Py_None, *oaobj = 0, *rc = 0;
+
+ if (!PyArg_ParseTupleAndKeywords(arg, kw, "|OO!:new", KWLIST,
+ &name, obarray_pytype, &oaobj))
+ { oaobj = 0; goto end; }
+ if (oaobj) Py_INCREF(oaobj);
+ else { oaobj = default_obarray(); if (!oaobj) goto end; }
+ rc = atom_pyintern(oaobj, name);
+end:
+ Py_XDECREF(oaobj);
+ return (rc);
+}
+
+static void atom_pydealloc(PyObject *me)
+ { assert(!ATOM_OAWK(me)); FREEOBJ(me); }
+
+static int atom_check(PyObject *me)
+{
+ if (!ATOM_A(me)) VALERR("atom is stale");
+ return (0);
+end:
+ return (-1);
+}
+
+static PyObject *atomget_name(PyObject *me, void *hunoz)
+{
+ atom *a;
+
+ if (atom_check(me)) return (0);
+ a = ATOM_A(me); return (TEXT_FROMSTRLEN(ATOM_NAME(a), ATOM_LEN(a)));
+}
+
+static PyObject *atomget_home(PyObject *me, void *hunoz)
+{
+ PyObject *rc;
+
+ if (atom_check(me)) return (0);
+ rc = ATOM_OAOBJ(me); assert(rc != Py_None); RETURN_OBJ(rc);
+}
+
+static PyObject *atomget_internedp(PyObject *me, void *hunoz)
+{
+ if (atom_check(me)) return (0);
+ return (getbool(!(ATOM_A(me)->f&ATOMF_GENSYM)));
+}
+
+static PyObject *atomget_livep(PyObject *me, void *hunoz)
+ { return (getbool(!!ATOM_A(me))); }
+
+static PyObject *atom_pyrichcompare(PyObject *x, PyObject *y, int op)
+{
+ int r;
+
+ switch (op) {
+ case Py_EQ: r = (x == y); break;
+ case Py_NE: r = (x != y); break;
+ default: TYERR("atoms are unordered");
+ }
+ return (getbool(r));
+end:
+ return (0);
+}
+
+static Py_hash_t atom_pyhash(PyObject *me)
+ { return (atom_check(me) ? -1 : ATOM_HASH(ATOM_A(me))); }
+
+static const PyGetSetDef atom_pygetset[] = {
+#define GETSETNAME(op, name) atom##op##_##name
+ GET (name, "A.name -> STR: atom name")
+ GET (home, "A.home -> ATAB: atom home table")
+ GET (internedp, "A.internedp -> BOOL: atom interned (not gensym)?")
+ GET (livep, "A.livep -> BOOL: atom table still alive?")
+#undef GETSETNAME
+ { 0 }
+};
+
+static const PyTypeObject atom_pytype_skel = {
+ PyVarObject_HEAD_INIT(0, 0) /* Header */
+ "Atom", /* @tp_name@ */
+ sizeof(atom_pyobj), /* @tp_basicsize@ */
+ 0, /* @tp_itemsize@ */
+
+ atom_pydealloc, /* @tp_dealloc@ */
+ 0, /* @tp_print@ */
+ 0, /* @tp_getattr@ */
+ 0, /* @tp_setattr@ */
+ 0, /* @tp_compare@ */
+ 0, /* @tp_repr@ */
+ 0, /* @tp_as_number@ */
+ 0, /* @tp_as_sequence@ */
+ 0, /* @tp_as_mapping@ */
+ atom_pyhash, /* @tp_hash@ */
+ 0, /* @tp_call@ */
+ 0, /* @tp_str@ */
+ 0, /* @tp_getattro@ */
+ 0, /* @tp_setattro@ */
+ 0, /* @tp_as_buffer@ */
+ Py_TPFLAGS_DEFAULT | /* @tp_flags@ */
+ Py_TPFLAGS_BASETYPE,
+
+ /* @tp_doc@ */
+ "Atom([name = None], [table = DEFAULT_ATOMTABLE])",
+
+ 0, /* @tp_traverse@ */
+ 0, /* @tp_clear@ */
+ atom_pyrichcompare, /* @tp_richcompare@ */
+ 0, /* @tp_weaklistoffset@ */
+ 0, /* @tp_iter@ */
+ 0, /* @tp_iternext@ */
+ 0, /* @tp_methods@ */
+ 0, /* @tp_members@ */
+ PYGETSET(atom), /* @tp_getset@ */
+ 0, /* @tp_base@ */
+ 0, /* @tp_dict@ */
+ 0, /* @tp_descr_get@ */
+ 0, /* @tp_descr_set@ */
+ 0, /* @tp_dictoffset@ */
+ 0, /* @tp_init@ */
+ PyType_GenericAlloc, /* @tp_alloc@ */
+ atom_pynew, /* @tp_new@ */
+ 0, /* @tp_free@ */
+ 0 /* @tp_is_gc@ */
+};
+
+static void *obarray_gmlookup(PyObject *me, PyObject *key, unsigned *f)
+{
+ atom *a = 0;
+ const char *p;
+ size_t sz;
+
+ if (!TEXT_CHECK(key)) TYERR("expected string");
+ TEXT_PTRLEN(key, p, sz); a = sym_find(&OBARRAY_TAB(me)->t, p, sz, 0, f);
+end:
+ return (a);
+}
+
+static void obarray_gmiterinit(PyObject *me, void *i)
+ { atom_mkiter(i, OBARRAY_TAB(me)); }
+
+static void *obarray_gmiternext(PyObject *me, void *i)
+ { return (atom_next(i)); }
+
+static PyObject *obarray_gmentrykey(PyObject *me, void *e)
+ { return (TEXT_FROMSTRLEN(ATOM_NAME(e), ATOM_LEN(e))); }
+
+static PyObject *obarray_gmentryvalue(PyObject *me, void *e)
+ { return (atom_pywrap(me, e)); }
+
+static const gmap_ops obarray_gmops = {
+ sizeof(atom_iter),
+ obarray_gmlookup,
+ obarray_gmiterinit,
+ obarray_gmiternext,
+ obarray_gmentrykey,
+ obarray_gmentryvalue
+};
+
+static PyObject *obarray_pynew(PyTypeObject *cls,
+ PyObject *arg, PyObject *kw)
+{
+ obarray_pyobj *rc = 0;
+ static const char *const kwlist[] = { 0 };
+
+ if (!PyArg_ParseTupleAndKeywords(arg, kw, ":new", KWLIST)) goto end;
+ rc = PyObject_NEW(obarray_pyobj, cls); if (!rc) goto end;
+ rc->gmops = &obarray_gmops; rc->wkls = 0;
+ atom_createtable(&rc->tab); assoc_create(&rc->map);
+end:
+ return ((PyObject *)rc);
+}
+
+static void obarray_pydealloc(PyObject *me)
+{
+ assoc_iter it;
+ struct obentry *e;
+
+ ASSOC_MKITER(&it, OBARRAY_MAP(me));
+ for (;;) {
+ ASSOC_NEXT(&it, e); if (!e) break;
+ ATOM_A(e->aobj) = 0;
+ Py_DECREF(ATOM_OAWK(e->aobj)); ATOM_OAWK(e->aobj) = 0;
+ Py_DECREF(e->aobj);
+ }
+ assoc_destroy(OBARRAY_MAP(me));
+ atom_destroytable(OBARRAY_TAB(me));
+ FREEOBJ(me);
+}
+
+static PyObject *oameth_intern(PyObject *me, PyObject *arg)
+{
+ char *p;
+ Py_ssize_t sz;
+ atom *a;
+ PyObject *rc = 0;
+
+ if (!PyArg_ParseTuple(arg, "s#:intern", &p, &sz)) goto end;
+ a = atom_nintern(OBARRAY_TAB(me), p, sz);
+ rc = atom_pywrap(me, a);
+end:
+ return (rc);
+}
+
+static PyObject *oameth_gensym(PyObject *me)
+ { return (atom_pywrap(me, atom_gensym(OBARRAY_TAB(me)))); }
+
+static const PyMappingMethods obarray_pymapping = {
+ gmap_pysize,
+ atom_pyintern,
+ 0
+};
+
+static const PyMethodDef obarray_pymethods[] = {
+ GMAP_ROMETHODS
+#define METHNAME(name) oameth_##name
+ METH (intern, "ATAB.intern(STR) -> A: atom with given name")
+ NAMETH(gensym, "ATAB.gensym() -> A: fresh uninterned atom")
+#undef METHNAME
+ { 0 }
+};
+
+static const PyTypeObject obarray_pytype_skel = {
+ PyVarObject_HEAD_INIT(0, 0) /* Header */
+ "AtomTable", /* @tp_name@ */
+ sizeof(obarray_pyobj), /* @tp_basicsize@ */
+ 0, /* @tp_itemsize@ */
+
+ obarray_pydealloc, /* @tp_dealloc@ */
+ 0, /* @tp_print@ */
+ 0, /* @tp_getattr@ */
+ 0, /* @tp_setattr@ */
+ 0, /* @tp_compare@ */
+ 0, /* @tp_repr@ */
+ 0, /* @tp_as_number@ */
+ PYSEQUENCE(gmap), /* @tp_as_sequence@ */
+ PYMAPPING(obarray), /* @tp_as_mapping@ */
+ 0, /* @tp_hash@ */
+ 0, /* @tp_call@ */
+ 0, /* @tp_str@ */
+ 0, /* @tp_getattro@ */
+ 0, /* @tp_setattro@ */
+ 0, /* @tp_as_buffer@ */
+ Py_TPFLAGS_DEFAULT | /* @tp_flags@ */
+ Py_TPFLAGS_BASETYPE,
+
+ /* @tp_doc@ */
+ "AtomTable()",
+
+ 0, /* @tp_traverse@ */
+ 0, /* @tp_clear@ */
+ 0, /* @tp_richcompare@ */
+ offsetof(obarray_pyobj, wkls), /* @tp_weaklistoffset@ */
+ gmap_pyiter, /* @tp_iter@ */
+ 0, /* @tp_iternext@ */
+ PYMETHODS(obarray), /* @tp_methods@ */
+ 0, /* @tp_members@ */
+ 0, /* @tp_getset@ */
+ 0, /* @tp_base@ */
+ 0, /* @tp_dict@ */
+ 0, /* @tp_descr_get@ */
+ 0, /* @tp_descr_set@ */
+ 0, /* @tp_dictoffset@ */
+ 0, /* @tp_init@ */
+ PyType_GenericAlloc, /* @tp_alloc@ */
+ obarray_pynew, /* @tp_new@ */
+ 0, /* @tp_free@ */
+ 0 /* @tp_is_gc@ */
+};
+
+/*----- Association tables ------------------------------------------------*/
+
+typedef struct {
+ GMAP_PYOBJ_HEAD
+ assoc_table t;
+ PyObject *oaobj;
+} assoc_pyobj;
+static PyTypeObject *assoc_pytype;
+#define ASSOC_PYCHECK(o) PyObject_TypeCheck((o), assoc_pytype)
+#define ASSOC_T(o) (&((assoc_pyobj *)(o))->t)
+#define ASSOC_OAOBJ(o) (((assoc_pyobj *)(o))->oaobj)
+
+struct aentry {
+ assoc_base _b;
+ PyObject *obj;
+};
+
+static void *assoc_gmlookup(PyObject *me, PyObject *key, unsigned *f)
+{
+ struct aentry *e = 0;
+ char *p;
+ size_t sz;
+ atom *a;
+
+ if (TEXT_CHECK(key)) {
+ TEXT_PTRLEN(key, p, sz);
+ a = atom_nintern(OBARRAY_TAB(ASSOC_OAOBJ(me)), p, sz);
+ } else if (ATOM_PYCHECK(key)) {
+ if (atom_check(key)) goto end;
+ if (ATOM_OAOBJ(key) != ASSOC_OAOBJ(me))
+ VALERR("wrong atom table for assoc");
+ a = ATOM_A(key);
+ } else
+ TYERR("expected atom or string");
+ e = assoc_find(ASSOC_T(me), a, f ? sizeof(*e) : 0, f);
+ if (!e) goto end;
+ if (f && !*f) e->obj = 0;
+end:
+ return (e);
+}
+
+static void assoc_gmiterinit(PyObject *me, void *i)
+ { assoc_iter *it = i; ASSOC_MKITER(it, ASSOC_T(me)); }
+
+static void *assoc_gmiternext(PyObject *me, void *i)
+ { assoc_iter *it = i; void *e; ASSOC_NEXT(it, e); return (e); }
+
+static PyObject *assoc_gmentrykey(PyObject *me, void *e)
+ { return (atom_pywrap(ASSOC_OAOBJ(me), ASSOC_ATOM(e))); }
+
+static PyObject *assoc_gmentryvalue(PyObject *me, void *e)
+ { struct aentry *ae = e; RETURN_OBJ(ae->obj); }
+
+static int assoc_gmsetentry(PyObject *me, void *e, PyObject *val)
+{
+ struct aentry *ae = e;
+
+ Py_XDECREF(ae->obj); Py_INCREF(val); ae->obj = val;
+ return (0);
+}
+
+static int assoc_gmdelentry(PyObject *me, void *e)
+ { assoc_remove(ASSOC_T(me), e); return (0); }
+
+static const gmap_ops assoc_gmops = {
+ sizeof(atom_iter),
+ assoc_gmlookup,
+ assoc_gmiterinit,
+ assoc_gmiternext,
+ assoc_gmentrykey,
+ assoc_gmentryvalue,
+ assoc_gmsetentry,
+ assoc_gmdelentry
+};
+
+static PyObject *assoc_pynew(PyTypeObject *cls, PyObject *arg, PyObject *kw)
+{
+ PyObject *oaobj = 0, *map = Py_None;
+ assoc_pyobj *me = 0;
+
+ if (!PyArg_ParseTuple(arg, "|OO!:new", &map, obarray_pytype, &oaobj))
+ { oaobj = 0; goto end; }
+ if (oaobj) Py_INCREF(oaobj);
+ else { oaobj = default_obarray(); if (!oaobj) goto end; }
+ me = PyObject_NEW(assoc_pyobj, cls);
+ me->gmops = &assoc_gmops;
+ assoc_create(&me->t);
+ me->oaobj = oaobj; oaobj = 0;
+ if ((map != Py_None && gmap_pyupdate((PyObject *)me, map)) ||
+ gmap_pyupdate((PyObject *)me, kw))
+ { Py_DECREF(me); me = 0; goto end; }
+end:
+ Py_XDECREF(oaobj);
+ return ((PyObject *)me);
+}
+
+static void assoc_pydealloc(PyObject *me)
+{
+ assoc_iter it;
+ struct aentry *ae;
+
+ ASSOC_MKITER(&it, ASSOC_T(me));
+ for (;;) {
+ ASSOC_NEXT(&it, ae); if (!ae) break;
+ Py_DECREF(ae->obj);
+ }
+ Py_DECREF(ASSOC_OAOBJ(me));
+ FREEOBJ(me);
+}
+
+static Py_ssize_t assoc_pysize(PyObject *me)
+{
+ assoc_table *t = ASSOC_T(me);
+ return (SYM_LIMIT(t->t.mask + 1) - t->load);
+}
+
+static const PyMemberDef assoc_pymembers[] = {
+#define MEMBERSTRUCT assoc_pyobj
+ MEMRNM(table, T_OBJECT, oaobj, READONLY,
+ "AS.table -> ATAB: home atom table")
+#undef MEMBERSTRUCT
+ { 0 }
+};
+
+static const PyMappingMethods assoc_pymapping = {
+ assoc_pysize,
+ gmap_pylookup,
+ gmap_pystore
+};
+
+static const PyTypeObject assoc_pytype_skel = {
+ PyVarObject_HEAD_INIT(0, 0) /* Header */
+ "AssocTable", /* @tp_name@ */
+ sizeof(assoc_pyobj), /* @tp_basicsize@ */
+ 0, /* @tp_itemsize@ */
+
+ assoc_pydealloc, /* @tp_dealloc@ */
+ 0, /* @tp_print@ */
+ 0, /* @tp_getattr@ */
+ 0, /* @tp_setattr@ */
+ 0, /* @tp_compare@ */
+ 0, /* @tp_repr@ */
+ 0, /* @tp_as_number@ */
+ PYSEQUENCE(gmap), /* @tp_as_sequence@ */
+ PYMAPPING(assoc), /* @tp_as_mapping@ */
+ 0, /* @tp_hash@ */
+ 0, /* @tp_call@ */
+ 0, /* @tp_str@ */
+ 0, /* @tp_getattro@ */
+ 0, /* @tp_setattro@ */
+ 0, /* @tp_as_buffer@ */
+ Py_TPFLAGS_DEFAULT | /* @tp_flags@ */
+ Py_TPFLAGS_BASETYPE,
+
+ /* @tp_doc@ */
+ "AssocTable([MAP], [ATAB], [ATOM = VALUE, ...])",
+
+ 0, /* @tp_traverse@ */
+ 0, /* @tp_clear@ */
+ 0, /* @tp_richcompare@ */
+ 0, /* @tp_weaklistoffset@ */
+ gmap_pyiter, /* @tp_iter@ */
+ 0, /* @tp_iternext@ */
+ PYMETHODS(gmap), /* @tp_methods@ */
+ PYMEMBERS(assoc), /* @tp_members@ */
+ 0, /* @tp_getset@ */
+ 0, /* @tp_base@ */
+ 0, /* @tp_dict@ */
+ 0, /* @tp_descr_get@ */
+ 0, /* @tp_descr_set@ */
+ 0, /* @tp_dictoffset@ */
+ 0, /* @tp_init@ */
+ PyType_GenericAlloc, /* @tp_alloc@ */
+ assoc_pynew, /* @tp_new@ */
+ 0, /* @tp_free@ */
+ 0 /* @tp_is_gc@ */
+};
+
+/*----- Main code ---------------------------------------------------------*/
+
+void atom_pyinit(void)
+{
+ INITTYPE(atom, root);
+ INITTYPE(obarray, root);
+ INITTYPE(assoc, root);
+}
+
+void atom_pyinsert(PyObject *mod)
+{
+ INSERT("Atom", atom_pytype);
+ INSERT("AtomTable", obarray_pytype);
+ INSERT("AssocTable", assoc_pytype);
+};
+
+/*----- That's all, folks -------------------------------------------------*/