pyke/mapping.c, key.c: Make the mapping code more intrusive and complete.
authorMark Wooding <mdw@distorted.org.uk>
Mon, 18 Nov 2019 10:41:12 +0000 (10:41 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Mon, 25 Nov 2019 17:43:08 +0000 (17:43 +0000)
Previously, mapping classes would implement a minimum of Python-level
operations, such as iterating over keys and mapping lookups, and the
generic mapping code would just synthesize missing operations in terms
of these.

Instead, introduce a mapping-specific vtable pointer in object
structures which describes the mapping operations in a form similar to
mLib's `sym' interface, which is rather more efficient in terms of hash
probes, and have the generic mapping code synthesize all of the Python-
level operations from these.

Needless to say, this is very messy.  Sorry.

key.c
pyke/mapping-mLib.c [new file with mode: 0644]
pyke/mapping.c
pyke/pyke-mLib.h
pyke/pyke.h
setup.py
t/testutils.py

diff --git a/key.c b/key.c
index a78b15e..ee97fae 100644 (file)
--- a/key.c
+++ b/key.c
@@ -123,7 +123,7 @@ static void keyexc_raise(int err)
 /*----- Data structures ---------------------------------------------------*/
 
 typedef struct keydata_pyobj {
-  PyObject_HEAD
+  GMAP_PYOBJ_HEAD
   key_data *kd;
 } keydata_pyobj;
 
@@ -137,19 +137,8 @@ static PyTypeObject *keydataec_pytype;
 #define KEYDATA_PYCHECK(o) PyObject_TypeCheck(o, keydata_pytype)
 #define KEYDATA_KD(o) (((keydata_pyobj *)(o))->kd)
 
-typedef struct subkeyiter_pyobj {
-  PyObject_HEAD
-  key_subkeyiter i;
-  PyObject *kdobj;
-} subkeyiter_pyobj;
-
-static PyTypeObject *subkeyiter_pytype;
-#define SUBKEYITER_PYCHECK(o) PyObject_TypeCheck(o, subkeyiter_pytype);
-#define SUBKEYITER_I(o) (&((subkeyiter_pyobj *)(o))->i)
-#define SUBKEYITER_KDOBJ(o) (((subkeyiter_pyobj *)(o))->kdobj)
-
 typedef struct keyfile_pyobj {
-  PyObject_HEAD
+  GMAP_PYOBJ_HEAD
   key_file kf;
 } keyfile_pyobj;
 
@@ -169,20 +158,8 @@ static PyTypeObject *key_pytype;
 #define KEY_KFOBJ(o) (((key_pyobj *)(o))->kfobj)
 #define KEY_KF(o) KEYFILE_KF(KEY_KFOBJ(o))
 
-typedef struct keyiter_pyobj {
-  PyObject_HEAD
-  key_iter i;
-  PyObject *kfobj;
-} keyiter_pyobj;
-
-static PyTypeObject *keyiter_pytype;
-#define KEYITER_PYCHECK(o) PyObject_TypeCheck(o, keyiter_pytype)
-#define KEYITER_I(o) (&((keyiter_pyobj *)(o))->i)
-#define KEYITER_KFOBJ(o) (((keyiter_pyobj *)(o))->kfobj)
-#define KEYITER_KF(o) KEYFILE_KF(KEYITER_KFOBJ(o))
-
 typedef struct keyattrs_pyobj {
-  PyObject_HEAD
+  GMAP_PYOBJ_HEAD
   PyObject *kobj;
 } keyattrs_pyobj;
 
@@ -192,20 +169,6 @@ static PyTypeObject *keyattrs_pytype;
 #define KEYATTRS_KF(o) KEY_KF(KEYATTRS_KOBJ(o))
 #define KEYATTRS_K(o) KEY_K(KEYATTRS_KOBJ(o))
 
-typedef struct keyattriter_pyobj {
-  PyObject_HEAD
-  key_attriter i;
-  PyObject *kobj;
-} keyattriter_pyobj;
-
-static PyTypeObject *keyattriter_pytype;
-#define KEYATTRITER_PYCHECK(o) PyObject_TypeCheck(o, keyattriter_pytype)
-#define KEYATTRITER_I(o) (&((keyattriter_pyobj *)(o))->i)
-#define KEYATTRITER_KOBJ(o) (((keyattriter_pyobj *)(o))->kobj)
-#define KEYATTRITER_K(o) KEY_K(KEYATTRITER_KOBJ(o))
-#define KEYATTRITER_KFOBJ(o) KEY_KFOBJ(KEYATTRITER_KOBJ(o))
-#define KEYATTRITER_KF(o) KEY_KF(KEYATTRITER_KOBJ(o))
-
 /*----- Filters -----------------------------------------------------------*/
 
 static int convfilter(PyObject *x, void *p)
@@ -305,21 +268,28 @@ static PyObject *kdmeth_writeflags(PyObject *me, PyObject *arg)
 
 /*----- Key data ----------------------------------------------------------*/
 
+static const gmap_ops keydatastruct_gmops;
+
 static PyObject *keydata_pywrap(key_data *kd)
 {
   PyTypeObject *ty;
   keydata_pyobj *kdobj;
+  const gmap_ops *gmops = 0;
 
   switch (kd->e & KF_ENCMASK) {
     case KENC_BINARY: ty = keydatabin_pytype; break;
     case KENC_ENCRYPT: ty = keydataenc_pytype; break;
     case KENC_MP: ty = keydatamp_pytype; break;
-    case KENC_STRUCT: ty = keydatastruct_pytype; break;
+    case KENC_STRUCT:
+      ty = keydatastruct_pytype;
+      gmops = &keydatastruct_gmops;
+      break;
     case KENC_STRING: ty = keydatastr_pytype; break;
     case KENC_EC: ty = keydataec_pytype; break;
     default: abort();
   }
   kdobj = PyObject_NEW(keydata_pyobj, ty);
+  kdobj->gmops = gmops;
   kdobj->kd = kd;
   return ((PyObject *)kdobj);
 }
@@ -968,75 +938,87 @@ static const PyTypeObject keydataec_pytype_skel = {
   0                                    /* @tp_is_gc@ */
 };
 
-static PyObject *subkeyiter_make(PyObject *kdobj)
+static void *keydatastruct_gmlookup(PyObject *me, PyObject *k, unsigned *f)
 {
-  subkeyiter_pyobj *me = PyObject_NEW(subkeyiter_pyobj, subkeyiter_pytype);
-  me->kdobj = kdobj;
-  Py_INCREF(kdobj);
-  key_mksubkeyiter(&me->i, KEYDATA_KD(kdobj));
-  return ((PyObject *)me);
+  key_data *kd = KEYDATA_KD(me);
+  const char *tag;
+  key_struct *ks;
+
+  assert((kd->e&KF_ENCMASK) == KENC_STRUCT);
+  if ((tag = PyString_AsString(k)) == 0) return (0);
+  if (f) { key_split(&kd); KEYDATA_KD(me) = kd; }
+  ks = sym_find(&kd->u.s, tag, -1, f ? sizeof(key_struct) : 0, f);
+  if (ks && f && !*f) ks->k = 0;
+  return (ks);
 }
 
-static PyObject *subkeyiter_pynext(PyObject *me)
+static void keydatastruct_gmiterinit(PyObject *me, void *i)
 {
-  const char *tag;
-  if (!key_nextsubkey(SUBKEYITER_I(me), &tag, 0))
-    return (0);
-  return (PyString_FromString(tag));
+  key_data *kd = KEYDATA_KD(me);
+
+  assert((kd->e&KF_ENCMASK) == KENC_STRUCT);
+  sym_mkiter(i, &kd->u.s);
 }
 
-static void subkeyiter_pydealloc(PyObject *me)
+static void *keydatastruct_gmiternext(PyObject *me, void *i)
+  { return (sym_next(i)); }
+
+static PyObject *keydatastruct_gmentrykey(PyObject *me, void *e)
+  { key_struct *ks = e; return (PyString_FromString(SYM_NAME(ks))); }
+
+static PyObject *keydatastruct_gmentryvalue(PyObject *me, void *e)
 {
-  Py_DECREF(SUBKEYITER_KDOBJ(me));
-  FREEOBJ(me);
+  key_struct *ks = e;
+  key_data *kd = ks->k;
+
+  key_incref(kd);
+  return (keydata_pywrap(kd));
 }
 
-static const PyTypeObject subkeyiter_pytype_skel = {
-  PyObject_HEAD_INIT(0) 0,             /* Header */
-  "SubKeyIter",                                /* @tp_name@ */
-  sizeof(subkeyiter_pyobj),            /* @tp_basicsize@ */
-  0,                                   /* @tp_itemsize@ */
+static key_struct *maybe_split_and_reprobe(PyObject *me, key_struct *ks)
+{
+  key_data *kd = KEYDATA_KD(me);
 
-  subkeyiter_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@ */
-  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,
+  if (kd->ref == 1) return (ks);
+  key_split(&kd); KEYDATA_KD(me) = kd;
+  ks = sym_find(&kd->u.s, SYM_NAME(ks), SYM_LEN(ks), 0, 0); assert(ks);
+  return (ks);
+}
 
-  /* @tp_doc@ */
-  "Iterator for structured keys.",
+static int keydatastruct_gmsetentry(PyObject *me, void *e, PyObject *val)
+{
+  key_struct *ks;
+  key_data *kd;
+  int rc = -1;
 
-  0,                                   /* @tp_traverse@ */
-  0,                                   /* @tp_clear@ */
-  0,                                   /* @tp_richcompare@ */
-  0,                                   /* @tp_weaklistoffset@ */
-  PyObject_SelfIter,                   /* @tp_iter@ */
-  subkeyiter_pynext,                   /* @tp_iternext@ */
-  0,                                   /* @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@ */
-  abstract_pynew,                      /* @tp_new@ */
-  0,                                   /* @tp_free@ */
-  0                                    /* @tp_is_gc@ */
+  if (!KEYDATA_PYCHECK(val)) TYERR("expected KeyData value");
+  ks = maybe_split_and_reprobe(me, e);
+  if (ks->k) key_drop(ks->k);
+  ks->k = kd = KEYDATA_KD(val); key_incref(kd);
+  rc = 0;
+end:
+  return (rc);
+}
+
+static int keydatastruct_gmdelentry(PyObject *me, void *e)
+{
+  key_struct *ks;
+
+  ks = maybe_split_and_reprobe(me, e);
+  if (ks->k) key_drop(ks->k);
+  sym_remove(&KEYDATA_KD(me)->u.s, ks);
+  return (0);
+}
+
+static const gmap_ops keydatastruct_gmops = {
+  sizeof(sym_iter),
+  keydatastruct_gmlookup,
+  keydatastruct_gmiterinit,
+  keydatastruct_gmiternext,
+  keydatastruct_gmentrykey,
+  keydatastruct_gmentryvalue,
+  keydatastruct_gmsetentry,
+  keydatastruct_gmdelentry
 };
 
 static PyObject *keydatastruct_pynew(PyTypeObject *ty,
@@ -1073,6 +1055,7 @@ static PyObject *keydatastruct_pynew(PyTypeObject *ty,
     Py_DECREF(it); it = 0;
   }
   me = (keydata_pyobj *)ty->tp_alloc(ty, 0);
+  me->gmops = &keydatastruct_gmops;
   me->kd = kd;
 end:
   if (kd && !me) key_drop(kd);
@@ -1081,49 +1064,13 @@ end:
   return ((PyObject *)me);
 }
 
-static PyObject *keydatastruct_pylookup(PyObject *me, PyObject *key)
-{
-  const char *tag;
-  key_data *kd;
-  PyObject *rc = 0;
-
-  if ((tag = PyString_AsString(key)) == 0)
-    goto end;
-  if ((kd = key_structfind(KEYDATA_KD(me), tag)) == 0)
-    MAPERR(key);
-  key_incref(kd);
-  rc = keydata_pywrap(kd);
-end:
-  return (rc);
-}
-
-static int keydatastruct_pystore(PyObject *me,
-                                PyObject *key, PyObject *value)
-{
-  const char *tag;
-  int rc = -1;
-
-  if ((tag = PyString_AsString(key)) == 0)
-    goto end;
-  key_split(&KEYDATA_KD(me));
-  if (value) {
-    if (!KEYDATA_PYCHECK(value))
-      TYERR("expected KeyData value");
-    key_structset(KEYDATA_KD(me), tag, KEYDATA_KD(value));
-  } else {
-    if (!key_structfind(KEYDATA_KD(me), tag))
-      MAPERR(key);
-    key_structset(KEYDATA_KD(me), tag, 0);
-  }
-  rc = 0;
-end:
-  return (rc);
-}
+static Py_ssize_t keydatastruct_pysize(PyObject *me)
+  { return gmap_pysize_from_sym(&KEYDATA_KD(me)->u.s); }
 
 static const PyMappingMethods keydatastruct_pymapping = {
-  gmap_pysize,                         /* @mp_length@ */
-  keydatastruct_pylookup,              /* @mp_subscript@ */
-  keydatastruct_pystore                        /* @mp_ass_subscript@ */
+  keydatastruct_pysize,
+  gmap_pylookup,
+  gmap_pystore
 };
 
 static const PyTypeObject keydatastruct_pytype_skel = {
@@ -1139,7 +1086,7 @@ static const PyTypeObject keydatastruct_pytype_skel = {
   0,                                   /* @tp_compare@ */
   0,                                   /* @tp_repr@ */
   0,                                   /* @tp_as_number@ */
-  0,                                   /* @tp_as_sequence@ */
+  PYSEQUENCE(gmap),                    /* @tp_as_sequence@ */
   PYMAPPING(keydatastruct),            /* @tp_as_mapping@ */
   0,                                   /* @tp_hash@ */
   0,                                   /* @tp_call@ */
@@ -1157,7 +1104,7 @@ static const PyTypeObject keydatastruct_pytype_skel = {
   0,                                   /* @tp_clear@ */
   0,                                   /* @tp_richcompare@ */
   0,                                   /* @tp_weaklistoffset@ */
-  subkeyiter_make,                     /* @tp_iter@ */
+  gmap_pyiter,                         /* @tp_iter@ */
   0,                                   /* @tp_iternext@ */
   PYMETHODS(gmap),                     /* @tp_methods@ */
   0,                                   /* @tp_members@ */
@@ -1176,125 +1123,78 @@ static const PyTypeObject keydatastruct_pytype_skel = {
 
 /*----- Key attributes ----------------------------------------------------*/
 
-static PyObject *keyattriter_make(PyObject *kaobj)
-{
-  PyObject *kobj = KEYATTRS_KOBJ(kaobj);
-  keyattriter_pyobj *me = PyObject_NEW(keyattriter_pyobj,
-                                      keyattriter_pytype);
-  me->kobj = kobj;
-  Py_INCREF(kobj);
-  key_mkattriter(&me->i, KEY_K(kobj));
-  return ((PyObject *)me);
-}
-
-static PyObject *keyattriter_pynext(PyObject *me)
+static void *keyattrs_gmlookup(PyObject *me, PyObject *k, unsigned *f)
 {
-  const char *name;
+  char *name = PyString_AsString(k);
+  key_attr *a = 0;
 
-  if (!key_nextattr(KEYATTRITER_I(me), &name, 0))
-    return (0);
-  return (PyString_FromString(name));
+  if (!name) goto end;
+  if (f && !(KEYATTRS_KF(me)->f&KF_WRITE)) KEYERR(KERR_READONLY);
+  a = sym_find(&KEYATTRS_K(me)->a, name, -1, f ? sizeof(key_attr) : 0, f);
+  if (f && !*f) a->p = 0;
+end:
+  return (a);
 }
 
-static void keyattriter_pydealloc(PyObject *me)
-{
-  Py_DECREF(KEYATTRITER_KOBJ(me));
-  FREEOBJ(me);
-}
+static void keyattrs_gmiterinit(PyObject *me, void *i)
+  { sym_mkiter(i, &KEYATTRS_K(me)->a); }
 
-static const PyTypeObject keyattriter_pytype_skel = {
-  PyObject_HEAD_INIT(0) 0,             /* Header */
-  "KeyAttributeIter",                  /* @tp_name@ */
-  sizeof(keyattriter_pyobj),           /* @tp_basicsize@ */
-  0,                                   /* @tp_itemsize@ */
+static void *keyattrs_gmiternext(PyObject *me, void *i)
+  { return (sym_next(i)); }
 
-  keyattriter_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@ */
-  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@ */
-  "Iterator for key attributes.",
+static PyObject *keyattrs_gmentrykey(PyObject *me, void *e)
+  { return (PyString_FromString(SYM_NAME(e))); }
 
-  0,                                   /* @tp_traverse@ */
-  0,                                   /* @tp_clear@ */
-  0,                                   /* @tp_richcompare@ */
-  0,                                   /* @tp_weaklistoffset@ */
-  PyObject_SelfIter,                   /* @tp_iter@ */
-  keyattriter_pynext,                  /* @tp_iternext@ */
-  0,                                   /* @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@ */
-  abstract_pynew,                      /* @tp_new@ */
-  0,                                   /* @tp_free@ */
-  0                                    /* @tp_is_gc@ */
-};
+static PyObject *keyattrs_gmentryvalue(PyObject *me, void *e)
+  { return (PyString_FromString(((key_attr *)e)->p)); }
 
-static PyObject *keyattrs_pylookup(PyObject *me, PyObject *key)
+static int keyattrs_gmsetentry(PyObject *me, void *e, PyObject *val)
 {
-  const char *attr;
-  const char *value;
-  PyObject *rc = 0;
+  key_attr *a = e;
+  const char *p;
+  Py_ssize_t n;
+  int rc = -1;
 
-  if ((attr = PyString_AsString(key)) == 0)
-    goto end;
-  if ((value = key_getattr(KEYATTRS_KF(me), KEYATTRS_K(me), attr)) == 0)
-    MAPERR(key);
-  rc = PyString_FromString(value);
+  if (!PyString_Check(val)) TYERR("expected string");
+  p = PyString_AS_STRING(val); n = PyString_GET_SIZE(val);
+  if (n > 255) VALERR("attribute too long");
+  if (memchr(p, 0, n)) VALERR("attribute must not contain nul");
+  if (a->p) xfree(a->p);
+  a->p = xmalloc(n + 1); memcpy(a->p, p, n + 1);
+  KEYATTRS_KF(me)->f |= KF_MODIFIED;
+  rc = 0;
 end:
   return (rc);
 }
 
-static int keyattrs_pystore(PyObject *me,
-                           PyObject *key, PyObject *value)
+static int keyattrs_gmdelentry(PyObject *me, void *e)
 {
-  const char *attr;
-  const char *val;
-  int err;
+  key_attr *a = e;
   int rc = -1;
 
-  if ((attr = PyString_AsString(key)) == 0)
-    goto end;
-  if (value) {
-    if ((val = PyString_AsString(value)) == 0)
-      goto end;
-    if ((err = key_putattr(KEYATTRS_KF(me), KEYATTRS_K(me),
-                          attr, val)) != 0)
-      KEYERR(err);
-  } else {
-    if (!key_getattr(KEYATTRS_KF(me), KEYATTRS_K(me), attr))
-      MAPERR(key);
-    if ((err = key_putattr(KEYATTRS_KF(me), KEYATTRS_K(me), attr, 0)) != 0)
-      KEYERR(err);
-  }
+  if (!(KEYATTRS_KF(me)->f&KF_WRITE)) KEYERR(KERR_READONLY);
+  if (a->p) { KEYATTRS_KF(me)->f |= KF_MODIFIED; xfree(a->p); }
+  sym_remove(&KEYATTRS_K(me)->a, a);
   rc = 0;
 end:
   return (rc);
 }
 
+static const gmap_ops keyattrs_gmops = {
+  sizeof(sym_iter),
+  keyattrs_gmlookup,
+  keyattrs_gmiterinit,
+  keyattrs_gmiternext,
+  keyattrs_gmentrykey,
+  keyattrs_gmentryvalue,
+  keyattrs_gmsetentry,
+  keyattrs_gmdelentry
+};
+
 static PyObject *keyattrs_make(PyObject *kobj)
 {
   keyattrs_pyobj *me = PyObject_NEW(keyattrs_pyobj, keyattrs_pytype);
+  me->gmops = &keyattrs_gmops;
   me->kobj = kobj;
   Py_INCREF(kobj);
   return ((PyObject *)me);
@@ -1306,10 +1206,13 @@ static void keyattrs_pydealloc(PyObject *me)
   FREEOBJ(me);
 }
 
+static Py_ssize_t keyattrs_pysize(PyObject *me)
+  { return gmap_pysize_from_sym(&KEYATTRS_K(me)->a); }
+
 static const PyMappingMethods keyattrs_pymapping = {
-  gmap_pysize,                         /* @mp_length@ */
-  keyattrs_pylookup,                   /* @mp_subscript@ */
-  keyattrs_pystore                     /* @mp_ass_subscript@ */
+  keyattrs_pysize,
+  gmap_pylookup,
+  gmap_pystore
 };
 
 static const PyTypeObject keyattrs_pytype_skel = {
@@ -1343,7 +1246,7 @@ static const PyTypeObject keyattrs_pytype_skel = {
   0,                                   /* @tp_clear@ */
   0,                                   /* @tp_richcompare@ */
   0,                                   /* @tp_weaklistoffset@ */
-  keyattriter_make,                    /* @tp_iter@ */
+  gmap_pyiter,                         /* @tp_iter@ */
   0,                                   /* @tp_iternext@ */
   PYMETHODS(gmap),                     /* @tp_methods@ */
   0,                                   /* @tp_members@ */
@@ -1663,81 +1566,51 @@ static const PyTypeObject key_pytype_skel = {
   0                                    /* @tp_is_gc@ */
 };
 
-/*----- Key iteration -----------------------------------------------------*/
-
-static PyObject *keyiter_new(PyObject *kfobj)
-{
-  keyiter_pyobj *me = PyObject_NEW(keyiter_pyobj, keyiter_pytype);
-  key_mkiter(&me->i, KEYFILE_KF(kfobj));
-  me->kfobj = kfobj;
-  Py_INCREF(kfobj);
-  return ((PyObject *)me);
-}
+/*----- Key files ---------------------------------------------------------*/
 
-static PyObject *keyiter_pynext(PyObject *me)
+static key *bytag(PyObject *me, PyObject *tagobj)
 {
-  key *k;
+  uint32 id;
+  char *tag;
+  key *k = 0;
 
-  if ((k = key_next(KEYITER_I(me))) == 0)
-    return (0);
-  return (getulong(k->id));
+  if (convu32(tagobj, &id))
+    k = key_byid(KEYFILE_KF(me), id);
+  else {
+    PyErr_Clear();
+    if ((tag = PyString_AsString(tagobj)) == 0)
+      goto end;
+    k = key_bytag(KEYFILE_KF(me), tag);
+  }
+end:
+  return (k);
 }
 
-static void keyiter_pydealloc(PyObject *me)
-{
-  Py_DECREF(KEYITER_KFOBJ(me));
-  FREEOBJ(me);
-}
+static void *keyfile_gmlookup(PyObject *me, PyObject *k, unsigned *f)
+  { key *kk = bytag(me, k); if (f) *f = !!kk; return (kk); }
 
-static const PyTypeObject keyiter_pytype_skel = {
-  PyObject_HEAD_INIT(0) 0,             /* Header */
-  "KeyFileIter",                       /* @tp_name@ */
-  sizeof(keyiter_pyobj),               /* @tp_basicsize@ */
-  0,                                   /* @tp_itemsize@ */
+static void keyfile_gmiterinit(PyObject *me, void *i)
+  { key_mkiter(i, KEYFILE_KF(me)); }
 
-  keyiter_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@ */
-  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,
+static void *keyfile_gmiternext(PyObject *me, void *i)
+  { return (key_next(i)); }
 
-  /* @tp_doc@ */
-  "Keyring iterator.",
+static PyObject *keyfile_gmentrykey(PyObject *me, void *e)
+  { key *k = e; return (getulong(k->id)); }
 
-  0,                                   /* @tp_traverse@ */
-  0,                                   /* @tp_clear@ */
-  0,                                   /* @tp_richcompare@ */
-  0,                                   /* @tp_weaklistoffset@ */
-  PyObject_SelfIter,                   /* @tp_iter@ */
-  keyiter_pynext,                      /* @tp_iternext@ */
-  0,                                   /* @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@ */
-  abstract_pynew,                      /* @tp_new@ */
-  0,                                   /* @tp_free@ */
-  0                                    /* @tp_is_gc@ */
-};
+static PyObject *keyfile_gmentryvalue(PyObject *me, void *e)
+  { return (key_pywrap(me, e)); }
 
-/*----- Key files ---------------------------------------------------------*/
+static const gmap_ops keyfile_gmops = {
+  sizeof(key_iter),
+  keyfile_gmlookup,
+  keyfile_gmiterinit,
+  keyfile_gmiternext,
+  keyfile_gmentrykey,
+  keyfile_gmentryvalue,
+  0,
+  0
+};
 
 struct reportinfo {
   PyObject *func;
@@ -1778,6 +1651,7 @@ static PyObject *keyfile_pynew(PyTypeObject *ty,
     TYERR("reporter function not callable");
   if ((rc = (keyfile_pyobj *)ty->tp_alloc(ty, 0)) == 0)
     goto end;
+  rc->gmops = &keyfile_gmops;
   if (key_open(&rc->kf, file, how, pythonreporter, &ri))
     OSERR(file);
   if (ri.stop) {
@@ -1877,46 +1751,14 @@ end:
   return (rc);
 }
 
-static PyObject *bytag(PyObject *me, PyObject *tagobj)
-{
-  uint32 id;
-  char *tag;
-  key *k;
-  PyObject *rc = 0;
-
-  if (convu32(tagobj, &id))
-    k = key_byid(KEYFILE_KF(me), id);
-  else {
-    PyErr_Clear();
-    if ((tag = PyString_AsString(tagobj)) == 0)
-      goto end;
-    k = key_bytag(KEYFILE_KF(me), tag);
-  }
-  if (!k) RETURN_NONE;
-  rc = key_pywrap(me, k);
-end:
-  return (rc);
-}
-
 static PyObject *kfmeth_bytag(PyObject *me, PyObject *arg)
 {
   PyObject *tagobj;
+  key *k;
 
   if (!PyArg_ParseTuple(arg, "O:bytag", &tagobj)) return (0);
-  return (bytag(me, tagobj));
-}
-
-static PyObject *keyfile_pylookup(PyObject *me, PyObject *key)
-{
-  PyObject *rc = bytag(me, key);
-  if (!rc) goto end;
-  if (rc == Py_None) {
-    Py_DECREF(rc);
-    rc = 0;
-    MAPERR(key);
-  }
-end:
-  return (rc);
+  if ((k = bytag(me, tagobj)) == 0) RETURN_NONE;
+  else return (key_pywrap(me, k));
 }
 
 static PyObject *kfmeth_newkey(PyObject *me, PyObject *arg, PyObject *kw)
@@ -2005,7 +1847,7 @@ static const PyGetSetDef keyfile_pygetset[] = {
 
 static const PyMappingMethods keyfile_pymapping = {
   gmap_pysize,
-  keyfile_pylookup,
+  gmap_pylookup,
   0
 };
 
@@ -2041,7 +1883,7 @@ static const PyTypeObject keyfile_pytype_skel = {
   0,                                   /* @tp_clear@ */
   0,                                   /* @tp_richcompare@ */
   0,                                   /* @tp_weaklistoffset@ */
-  keyiter_new,                         /* @tp_iter@ */
+  gmap_pyiter,                         /* @tp_iter@ */
   0,                                   /* @tp_iternext@ */
   PYMETHODS(keyfile),                  /* @tp_methods@ */
   0,                                   /* @tp_members@ */
@@ -2079,7 +1921,6 @@ void key_pyinit(void)
 {
   INITTYPE(keyfile, root);
   INITTYPE(key, root);
-  INITTYPE(keyiter, root);
   INITTYPE(keydata, root);
   INITTYPE(keydatabin, keydata);
   INITTYPE(keydataenc, keydata);
@@ -2087,9 +1928,7 @@ void key_pyinit(void)
   INITTYPE(keydatamp, keydata);
   INITTYPE(keydataec, keydata);
   INITTYPE(keydatastruct, keydata);
-  INITTYPE(subkeyiter, root);
   INITTYPE(keyattrs, root);
-  INITTYPE(keyattriter, root);
 }
 
 void key_pyinsert(PyObject *mod)
@@ -2098,10 +1937,8 @@ void key_pyinsert(PyObject *mod)
   INSEXC("KeyFileIOError", keyioexc, PyExc_OSError, 0);
   INSEXC("KeyFileBroken", keyfilebrokenexc, keyioexc, 0);
   INSERT("KeyFile", keyfile_pytype);
-  INSERT("KeyFileIter", keyiter_pytype);
   INSERT("Key", key_pytype);
   INSERT("KeyAttributes", keyattrs_pytype);
-  INSERT("KeyAttributeIter", keyattriter_pytype);
   INSERT("KeyData", keydata_pytype);
   INSERT("KeyDataBinary", keydatabin_pytype);
   INSERT("KeyDataEncrypted", keydataenc_pytype);
@@ -2109,7 +1946,6 @@ void key_pyinsert(PyObject *mod)
   INSERT("KeyDataECPt", keydataec_pytype);
   INSERT("KeyDataString", keydatastr_pytype);
   INSERT("KeyDataStructured", keydatastruct_pytype);
-  INSERT("SubKeyIter", subkeyiter_pytype);
   setconstants(mod, consts);
 }
 
diff --git a/pyke/mapping-mLib.c b/pyke/mapping-mLib.c
new file mode 100644 (file)
index 0000000..9b5c37e
--- /dev/null
@@ -0,0 +1,36 @@
+/* -*-c-*-
+ *
+ * Generic mapping support, with mLib integration
+ *
+ * (c) 2019 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Pyke: the Python Kit for Extensions.
+ *
+ * Pyke 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.
+ *
+ * Pyke 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 Pyke.  If not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "pyke-mLib.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+Py_ssize_t gmap_pysize_from_sym(sym_table *tab)
+  { return (SYM_LIMIT(tab->t.mask + 1) - tab->load); }
+
+/*----- That's all, folks -------------------------------------------------*/
index 363a2f0..4a0d412 100644 (file)
 
 /*----- Iteration ---------------------------------------------------------*/
 
-static PyTypeObject *itemiter_pytype, *valiter_pytype;
+static PyTypeObject *keyiter_pytype, *itemiter_pytype, *valiter_pytype;
+
+union iterstate {
+  void *external;
+  void *internal[4];
+};
 
 typedef struct iter_pyobj {
   PyObject_HEAD
   PyObject *map;
-  PyObject *i;
+  union iterstate iter;
 } iter_pyobj;
 #define ITER_MAP(o) (((iter_pyobj *)(o))->map)
-#define ITER_I(o) (((iter_pyobj *)(o))->i)
+#define ITER_ITER(o) (&((iter_pyobj *)(o))->iter)
+#define ITER_EXTERNALP(o)                                              \
+  (GMAP_OPS(ITER_MAP(o))->isz > sizeof(union iterstate))
+#define ITER_I(o) (ITER_EXTERNALP(o) ? ITER_ITER(o)->external          \
+                                    : &ITER_ITER(o)->internal)
+
+static void *iter_init(PyObject *me, union iterstate *iter)
+{
+  const gmap_ops *gmops = GMAP_OPS(me);
+  void *i;
+
+  if (gmops->isz <= sizeof(*iter)) i = &iter->internal;
+  else { i = iter->external = PyObject_Malloc(gmops->isz); assert(i); }
+  gmops->iter_init(me, i);
+  return (i);
+}
+
+static void iter_free(PyObject *me, union iterstate *iter)
+  { if (GMAP_OPS(me)->isz > sizeof(*iter)) PyObject_Free(iter->external); }
 
 static void iter_pydealloc(PyObject *me)
-  { Py_DECREF(ITER_MAP(me)); Py_DECREF(ITER_I(me)); FREEOBJ(me); }
+{
+  PyObject *map = ITER_MAP(me);
+  iter_free(map, ITER_ITER(me));
+  Py_DECREF(map); FREEOBJ(me);
+}
 
-static PyObject *itemiter_pynext(PyObject *me)
+static PyObject *gmap_mkiter(PyObject *me, PyTypeObject *ty)
 {
-  PyObject *k = 0, *v = 0, *rc = 0;
+  iter_pyobj *iter = PyObject_NEW(iter_pyobj, ty);
 
-  if ((k = PyIter_Next(ITER_I(me))) != 0 &&
-      (v = PyObject_GetItem(ITER_MAP(me), k)) != 0)
-    rc = Py_BuildValue("(OO)", k, v);
-  Py_XDECREF(k); Py_XDECREF(v);
-  return (rc);
+  iter->map = me; Py_INCREF(me);
+  iter_init(me, &iter->iter);
+  return ((PyObject *)iter);
 }
 
-static const PyTypeObject itemiter_pytype_skel = {
+static PyObject *keyiter_pynext(PyObject *me)
+{
+  PyObject *map = ITER_MAP(me);
+  const struct gmap_ops *gmops = GMAP_OPS(map);
+  void *e = gmops->iter_next(map, ITER_I(me));
+
+  if (!e) return (0);
+  else return (gmops->entry_key(map, e));
+}
+
+static const PyTypeObject keyiter_pytype_skel = {
   PyObject_HEAD_INIT(0) 0,             /* Header */
-  "ItemIter",                          /* @tp_name@ */
+  "_KeyIter",                          /* @tp_name@ */
   sizeof(iter_pyobj),                  /* @tp_basicsize@ */
   0,                                   /* @tp_itemsize@ */
 
@@ -79,14 +114,14 @@ static const PyTypeObject itemiter_pytype_skel = {
     Py_TPFLAGS_BASETYPE,
 
   /* @tp_doc@ */
-  "Iterates over the items of a mapping.",
+  "Iterates over the keys of a mapping.",
 
   0,                                   /* @tp_traverse@ */
   0,                                   /* @tp_clear@ */
   0,                                   /* @tp_richcompare@ */
   0,                                   /* @tp_weaklistoffset@ */
   PyObject_SelfIter,                   /* @tp_iter@ */
-  itemiter_pynext,                     /* @tp_iternext@ */
+  keyiter_pynext,                      /* @tp_iternext@ */
   0,                                   /* @tp_methods@ */
   0,                                   /* @tp_members@ */
   0,                                   /* @tp_getset@ */
@@ -104,17 +139,17 @@ static const PyTypeObject itemiter_pytype_skel = {
 
 static PyObject *valiter_pynext(PyObject *me)
 {
-  PyObject *k = 0, *rc = 0;
+  PyObject *map = ITER_MAP(me);
+  const struct gmap_ops *gmops = GMAP_OPS(map);
+  void *e = gmops->iter_next(map, ITER_I(me));
 
-  if ((k = PyIter_Next(ITER_I(me))) != 0)
-    rc = PyObject_GetItem(ITER_MAP(me), k);
-  Py_XDECREF(k);
-  return (rc);
+  if (!e) return (0);
+  else return (gmops->entry_value(map, e));
 }
 
 static const PyTypeObject valiter_pytype_skel = {
   PyObject_HEAD_INIT(0) 0,             /* Header */
-  "ValueIter",                         /* @tp_name@ */
+  "_ValueIter",                                /* @tp_name@ */
   sizeof(iter_pyobj),                  /* @tp_basicsize@ */
   0,                                   /* @tp_itemsize@ */
 
@@ -160,6 +195,117 @@ static const PyTypeObject valiter_pytype_skel = {
   0                                    /* @tp_is_gc@ */
 };
 
+static PyObject *itemiter_pynext(PyObject *me)
+{
+  PyObject *map = ITER_MAP(me);
+  const struct gmap_ops *gmops = GMAP_OPS(map);
+  void *e = gmops->iter_next(map, ITER_I(me));
+  PyObject *rc = 0;
+
+  if (e)
+    rc = Py_BuildValue("(NN)",
+                      gmops->entry_key(map, e),
+                      gmops->entry_value(map, e));
+  return (rc);
+}
+
+static const PyTypeObject itemiter_pytype_skel = {
+  PyObject_HEAD_INIT(0) 0,             /* Header */
+  "_ItemIter",                         /* @tp_name@ */
+  sizeof(iter_pyobj),                  /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  iter_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@ */
+  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@ */
+  "Iterates over the items of a mapping.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  PyObject_SelfIter,                   /* @tp_iter@ */
+  itemiter_pynext,                     /* @tp_iternext@ */
+  0,                                   /* @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@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Other mapping protocol support ------------------------------------*/
+
+Py_ssize_t gmap_pysize(PyObject *me)
+{
+  const gmap_ops *gmops = GMAP_OPS(me);
+  union iterstate iter;
+  void *i;
+  Py_ssize_t n = 0;
+
+  i = iter_init(me, &iter);
+  while (gmops->iter_next(me, i)) n++;
+  iter_free(me, &iter);
+  return (n);
+}
+
+PyObject *gmap_pylookup(PyObject *me, PyObject *key)
+{
+  const gmap_ops *gmops = GMAP_OPS(me);
+  void *e = gmops->lookup(me, key, 0);
+  PyObject *rc = 0;
+
+  if (!e) { if (!PyErr_Occurred()) MAPERR(key); else goto end; }
+  rc = gmops->entry_value(me, e);
+end:
+  return (rc);
+}
+
+int gmap_pystore(PyObject *me, PyObject *key, PyObject *value)
+{
+  const gmap_ops *gmops = GMAP_OPS(me);
+  unsigned f;
+  void *e = gmops->lookup(me, key, &f);
+  int rc = -1;
+
+  if (!e) goto end;
+  if (!value)
+    rc = gmops->del_entry(me, e);
+  else {
+    rc = gmops->set_entry(me, e, value);
+    if (rc && !f) gmops->del_entry(me, e);
+  }
+  rc = 0;
+end:
+  return (rc);
+}
+
+int gmap_pyhaskey(PyObject *me, PyObject *key)
+  { return (GMAP_OPS(me)->lookup(me, key, 0) ? 1 : PyErr_Occurred() ? -1 : 0); }
+
 const PySequenceMethods gmap_pysequence = {
   0,                                   /* @sq_length@ */
   0,                                   /* @sq_concat@ */
@@ -168,140 +314,118 @@ const PySequenceMethods gmap_pysequence = {
   0,                                   /* @sq_slice@ */
   0,                                   /* @sq_ass_item@ */
   0,                                   /* @sq_ass_slice@ */
-  PyMapping_HasKey,                    /* @sq_contains@ */
+  gmap_pyhaskey,                       /* @sq_contains@ */
   0,                                   /* @sq_inplace_concat@ */
   0                                    /* @sq_inplace_repeat@ */
 };
 
-/*----- Other mapping protocol support ------------------------------------*/
-
-Py_ssize_t gmap_pysize(PyObject *me)
-{
-  PyObject *i = 0, *x = 0;
-  Py_ssize_t rc = -1, n = 0;
-
-  if ((i = PyObject_GetIter(me)) == 0) goto done;
-  while ((x = PyIter_Next(i)) != 0) { n++; Py_DECREF(x); x = 0; }
-  if (PyErr_Occurred()) goto done;
-  rc = n;
-done:
-  Py_XDECREF(i); Py_XDECREF(x);
-  return (rc);
-}
-
 PyObject *gmapmeth_has_key(PyObject *me, PyObject *arg)
 {
   PyObject *k;
+  void *e;
   if (!PyArg_ParseTuple(arg, "O:has_key", &k)) return (0);
-  return (getbool(PyMapping_HasKey(me, k)));
+  e = GMAP_OPS(me)->lookup(me, k, 0);
+  if (e) RETURN_TRUE;
+  else if (!PyErr_Occurred()) RETURN_FALSE;
+  else return (0);
 }
 
 PyObject *gmapmeth_keys(PyObject *me)
 {
-  PyObject *l = 0, *i = 0, *k, *rc = 0;
+  const gmap_ops *gmops = GMAP_OPS(me);
+  union iterstate iter; void *i = 0, *e;
+  PyObject *l = 0, *k, *rc = 0;
   int err;
 
-  if ((l = PyList_New(0)) == 0 ||
-      (i = PyObject_GetIter(me)) == 0)
-    goto done;
-  while ((k = PyIter_Next(i)) != 0)
-    { err = PyList_Append(l, k); Py_DECREF(k); if (err) goto done; }
-  if (PyErr_Occurred()) goto done;
+  if ((l = PyList_New(0)) == 0) goto done;
+  i = iter_init(me, &iter);
+  while ((e = gmops->iter_next(me, i)) != 0) {
+    k = gmops->entry_key(me, e);
+    err = PyList_Append(l, k);
+    Py_DECREF(k);
+    if (err) goto done;
+  }
   rc = l; l = 0;
 done:
-  Py_XDECREF(l); Py_XDECREF(i);
+  Py_XDECREF(l);
+  if (i) iter_free(me, &iter);
   return (rc);
 }
 
 PyObject *gmapmeth_values(PyObject *me)
 {
-  PyObject *l = 0, *i = 0, *k, *v, *rc = 0;
-  int err = 0;
-
-  if ((l = PyList_New(0)) == 0 ||
-      (i = PyObject_GetIter(me)) == 0)
-    goto done;
-  while ((k = PyIter_Next(i)) != 0) {
-    if ((v = PyObject_GetItem(me, k)) == 0 ||
-       PyList_Append(l, v))
-      err = -1;
-    Py_DECREF(k); Py_XDECREF(v);
+  const gmap_ops *gmops = GMAP_OPS(me);
+  union iterstate iter; void *i = 0, *e;
+  PyObject *l = 0, *v, *rc = 0;
+  int err;
+
+  if ((l = PyList_New(0)) == 0) goto done;
+  i = iter_init(me, &iter);
+  while ((e = gmops->iter_next(me, i)) != 0) {
+    v = gmops->entry_value(me, e);
+    err = PyList_Append(l, v);
+    Py_DECREF(v);
     if (err) goto done;
   }
-  if (PyErr_Occurred()) goto done;
   rc = l; l = 0;
 done:
-  Py_XDECREF(l); Py_XDECREF(i);
+  Py_XDECREF(l);
+  if (i) iter_free(me, &iter);
   return (rc);
 }
 
 PyObject *gmapmeth_items(PyObject *me)
 {
-  PyObject *l = 0, *i = 0, *k, *v, *z, *rc = 0;
-  int err = 0;
-
-  if ((l = PyList_New(0)) == 0 ||
-      (i = PyObject_GetIter(me)) == 0)
-    goto done;
-  while ((k = PyIter_Next(i)) != 0) {
-    z = 0;
-    if ((v = PyObject_GetItem(me, k)) == 0 ||
-       (z = Py_BuildValue("(OO)", k, v)) == 0 ||
-       PyList_Append(l, z))
-      err = -1;
-    Py_DECREF(k); Py_XDECREF(v); Py_XDECREF(z);
+  const gmap_ops *gmops = GMAP_OPS(me);
+  union iterstate iter; void *i = 0, *e;
+  PyObject *l = 0, *z, *rc = 0;
+  int err;
+
+  if ((l = PyList_New(0)) == 0) goto done;
+  i = iter_init(me, &iter);
+  while ((e = gmops->iter_next(me, i)) != 0) {
+    if ((z = Py_BuildValue("(NN)",
+                          gmops->entry_key(me, e),
+                          gmops->entry_value(me, e))) == 0)
+      goto done;
+    err = PyList_Append(l, z);
+    Py_XDECREF(z);
     if (err) goto done;
   }
-  if (PyErr_Occurred()) goto done;
   rc = l; l = 0;
 done:
-  Py_XDECREF(l); Py_XDECREF(i);
+  Py_XDECREF(l);
+  if (i) iter_free(me, &iter);
   return (rc);
 }
 
 PyObject *gmapmeth_iterkeys(PyObject *me)
-  { return (PyObject_GetIter(me)); }
+  { return (gmap_mkiter(me, keyiter_pytype)); }
 
 PyObject *gmapmeth_itervalues(PyObject *me)
-{
-  PyObject *i;
-  iter_pyobj *ii;
-
-  if ((i = PyObject_GetIter(me)) == 0)
-    return (0);
-  ii = PyObject_NEW(iter_pyobj, valiter_pytype);
-  ii->map = me; Py_INCREF(me);
-  ii->i = i;
-  return ((PyObject *)ii);
-}
+  { return (gmap_mkiter(me, valiter_pytype)); }
 
 PyObject *gmapmeth_iteritems(PyObject *me)
-{
-  PyObject *i;
-  iter_pyobj *ii;
+  { return (gmap_mkiter(me, itemiter_pytype)); }
 
-  if ((i = PyObject_GetIter(me)) == 0)
-    return (0);
-  ii = PyObject_NEW(iter_pyobj, itemiter_pytype);
-  ii->map = me; Py_INCREF(me);
-  ii->i = i;
-  return ((PyObject *)ii);
-}
+PyObject *gmap_pyiter(PyObject *me)
+  { return gmap_mkiter(me, keyiter_pytype); }
 
 PyObject *gmapmeth_clear(PyObject *me)
 {
-  PyObject *i = 0, *k = 0, *rc = 0;
-
-  if ((i = PyObject_GetIter(me)) == 0)
-    goto end;
-  while ((k = PyIter_Next(i)) != 0) {
-    PyObject_DelItem(me, k);
-    Py_DECREF(k);
+  const gmap_ops *gmops = GMAP_OPS(me);
+  union iterstate iter;
+  void *i, *e;
+  PyObject *rc = 0;
+
+  i = iter_init(me, &iter);
+  for (;;) {
+    e = gmops->iter_next(me, i); if (!e) break;
+    if (gmops->del_entry(me, e)) goto end;
   }
-  if (PyErr_Occurred()) goto end;
+  iter_free(me, &iter);
   rc = me; Py_INCREF(me);
 end:
-  Py_XDECREF(i);
   return (rc);
 }
 
@@ -310,85 +434,135 @@ static const char *const def_kwlist[] = { "key", "default", 0 };
 
 PyObject *gmapmeth_get(PyObject *me, PyObject *arg, PyObject *kw)
 {
-  PyObject *k, *def = Py_None, *v;
+  const gmap_ops *gmops = GMAP_OPS(me);
+  PyObject *k, *def = Py_None;
+  void *e;
 
   if (!PyArg_ParseTupleAndKeywords(arg, kw, "O|O:get", DEF_KWLIST, &k, &def))
     return (0);
-  if ((v = PyObject_GetItem(me, k)) != 0) return (v);
-  PyErr_Clear();
-  RETURN_OBJ(def);
+  e = gmops->lookup(me, k, 0);
+  if (e) return (gmops->entry_value(me, e));
+  else if (!PyErr_Occurred()) RETURN_OBJ(def);
+  else return (0);
 }
 
 PyObject *gmapmeth_setdefault(PyObject *me, PyObject *arg, PyObject *kw)
 {
-  PyObject *k, *def = Py_None, *v;
+  const gmap_ops *gmops = GMAP_OPS(me);
+  PyObject *k, *def = Py_None;
+  void *e;
+  unsigned f;
 
   if (!PyArg_ParseTupleAndKeywords(arg, kw, "O|O:setdefault", DEF_KWLIST,
                                   &k, &def))
     return (0);
-  if ((v = PyObject_GetItem(me, k)) != 0) return (v);
-  PyErr_Clear();
-  if (PyObject_SetItem(me, k, def)) return (0);
-  RETURN_OBJ(def);
+  e = gmops->lookup(me, k, &f);
+  if (!e) return (0);
+  else if (f) return (gmops->entry_value(me, e));
+  else if (gmops->set_entry(me, e, def)) return (0);
+  else RETURN_OBJ(def);
 }
 
 PyObject *gmapmeth_pop(PyObject *me, PyObject *arg, PyObject *kw)
 {
-  PyObject *k, *def = 0, *v;
+  const gmap_ops *gmops = GMAP_OPS(me);
+  PyObject *k, *def = 0;
+  PyObject *rc = 0;
+  void *e;
 
   if (!PyArg_ParseTupleAndKeywords(arg, kw, "O|O:pop", DEF_KWLIST, &k, &def))
-    return (0);
-  if ((v = PyObject_GetItem(me, k)) != 0) {
-    PyObject_DelItem(me, k);
-    return (v);
-  } else if (def) {
-    PyErr_Clear();
-    RETURN_OBJ(def);
-  } else
-    return (0);
+    goto end;
+  e = gmops->lookup(me, k, 0);
+  if (!e) {
+    if (PyErr_Occurred()) goto end;
+    else if (def) { rc = def; Py_INCREF(rc); }
+    else MAPERR(k);
+  } else {
+    rc = gmops->entry_value(me, e);
+    if (gmops->del_entry(me, e)) { Py_DECREF(rc); rc = 0; }
+  }
+end:
+  return (rc);
 }
 
-PyObject *gmapmeth_update(PyObject *me, PyObject *arg)
+static int update_core(PyObject *me, PyObject *map)
 {
-  PyObject *map, *i = 0, *k, *v, *rc = 0;
-  int err = 0;
-
-  if (!PyArg_ParseTuple(arg, "O:update", &map) ||
-      (i = PyObject_GetIter(map)) == 0)
-    goto end;
-  while ((k = PyIter_Next(i)) != 0) {
-    if ((v = PyObject_GetItem(map, k)) == 0 ||
-       PyObject_SetItem(me, k, v))
-      err = -1;
-    Py_DECREF(k); Py_XDECREF(v);
-    if (err) goto end;
+  const gmap_ops *gmops = GMAP_OPS(me);
+  PyObject *i = 0, *item = 0, *k = 0, *v = 0;
+  void *e;
+  unsigned foundp;
+  int rc = -1;
+
+  i = PyObject_CallMethod(map, "iteritems", 0);
+
+  if (i) {
+    for (;;) {
+      item = PyIter_Next(i); if (!item) break;
+      if (!PyTuple_Check(item) || PyTuple_GET_SIZE(item) != 2)
+       TYERR("wanted a pair");
+      k = PyTuple_GET_ITEM(item, 0); Py_INCREF(k);
+      v = PyTuple_GET_ITEM(item, 1); Py_INCREF(v);
+      e = gmops->lookup(me, k, &foundp); if (!e) goto end;
+      if (gmops->set_entry(me, e, v)) goto end;
+      Py_DECREF(item); Py_DECREF(k); Py_DECREF(v); item = k = v = 0;
+    }
+    if (PyErr_Occurred()) goto end;
+  } else {
+    PyErr_Clear();
+    i = PyObject_GetIter(map); if (!i) goto end;
+    for (;;) {
+      k = PyIter_Next(i); if (!k) goto end;
+      v = PyObject_GetItem(map, k); if (!v) goto end;
+      e = gmops->lookup(me, k, &foundp); if (!e) goto end;
+      if (gmops->set_entry(me, e, v)) goto end;
+      Py_DECREF(k); Py_DECREF(v); k = v = 0;
+    }
+    if (PyErr_Occurred()) goto end;
   }
-  if (PyErr_Occurred()) goto end;
-  rc = me; Py_INCREF(me);
+  rc = 0;
 end:
-  Py_XDECREF(i);
+  Py_XDECREF(i); Py_XDECREF(item);
+  Py_XDECREF(k); Py_XDECREF(v);
   return (rc);
 }
 
-PyObject *gmapmeth_popitem(PyObject *me)
+PyObject *gmapmeth_update(PyObject *me, PyObject *arg, PyObject *kw)
 {
-  PyObject *i = 0, *k = 0, *v = 0, *rc = 0;
+  PyObject *map = 0;
 
-  if ((i = PyObject_GetIter(me)) == 0)
-    goto end;
-  if ((k = PyIter_Next(i)) == 0) {
-    if (!PyErr_Occurred()) VALERR("popitem(): mapping is empty");
-    goto end;
+  if (!PyArg_ParseTuple(arg, "|O:update", &map)) return (0);
+  if (map && update_core(me, map)) return (0);
+  if (kw && update_core(me, kw)) return (0);
+  RETURN_ME;
+}
+
+PyObject *gmapmeth_popitem(PyObject *me)
+{
+  const gmap_ops *gmops = GMAP_OPS(me);
+  union iterstate iter;
+  void *i;
+  PyObject *rc = 0;
+  void *e;
+
+  i = iter_init(me, &iter);
+  e = gmops->iter_next(me, i);
+  iter_free(me, &iter);
+  if (!e)
+    MAPERR(Py_None);
+  else {
+    rc = Py_BuildValue("(NN)",
+                      gmops->entry_key(me, e), gmops->entry_value(me, e));
+    if (gmops->del_entry(me, e)) { Py_DECREF(rc); rc = 0; }
   }
-  if ((v = PyObject_GetItem(me, k)) == 0 ||
-      PyObject_DelItem(me, k))
-    goto end;
-  rc = Py_BuildValue("(OO)", k, v);
 end:
-  Py_XDECREF(i); Py_XDECREF(k); Py_XDECREF(v);
   return (rc);
 }
 
+const PyMethodDef gmapro_pymethods[] = {
+  GMAP_ROMETHODS
+  { 0 }
+};
+
 const PyMethodDef gmap_pymethods[] = {
   GMAP_METHODS
   { 0 }
@@ -398,14 +572,16 @@ const PyMethodDef gmap_pymethods[] = {
 
 void pyke_gmap_pyinit(void)
 {
+  INITTYPE(keyiter, root);
   INITTYPE(itemiter, root);
   INITTYPE(valiter, root);
 }
 
 void pyke_gmap_pyinsert(PyObject *mod)
 {
-  INSERT("ItemIter", itemiter_pytype);
-  INSERT("ValueIter", valiter_pytype);
+  INSERT("_KeyIter", keyiter_pytype);
+  INSERT("_ValueIter", valiter_pytype);
+  INSERT("_ItemIter", itemiter_pytype);
 }
 
 /*----- That's all, folks -------------------------------------------------*/
index f1e8cd2..aa50bd8 100644 (file)
@@ -39,6 +39,7 @@
 
 PUBLIC_SYMBOLS;
 #include <mLib/bits.h>
+#include <mLib/sym.h>
 PRIVATE_SYMBOLS;
 
 /*----- Conversions -------------------------------------------------------*/
@@ -53,6 +54,11 @@ extern int convk64(PyObject *, void *);
 extern PyObject *getk64(kludge64);
   /* Output conversion for `kludge64'. */
 
+/*----- Mapping support ---------------------------------------------------*/
+
+extern Py_ssize_t gmap_pysize_from_sym(sym_table *);
+  /* Determine the size of a mapping based on a `sym_table'. */
+
 /*----- That's all, folks -------------------------------------------------*/
 
 #ifdef __cplusplus
index a544f8d..581e444 100644 (file)
@@ -371,6 +371,55 @@ extern PyMethodDef *donemethods(void);
 
 /*----- Generic mapping support -------------------------------------------*/
 
+/* Operations table.  ME is the mapping object throughout. */
+typedef struct gmap_ops {
+  size_t isz;                          /* iterator size */
+
+  void *(*lookup)(PyObject *me, PyObject *key, unsigned *f);
+    /* Lookup the KEY.  If it is found, return an entry pointer for it; if F
+     * is not null, set *F nonzero.  Otherwise, if F is null, return a null
+     * pointer (without setting a pending exception); if F is not null, then
+     * set *F zero and return a fresh entry pointer.  Return null on a Python
+     * exception (the caller will notice the difference.)
+     */
+
+  void (*iter_init)(PyObject *me, void *i);
+    /* Initialize an iterator at I. */
+
+  void *(*iter_next)(PyObject *me, void *i);
+    /* Return an entry pointer for a different item, or null if all have been
+     * visited.
+     */
+
+  PyObject *(*entry_key)(PyObject *me, void *e);
+    /* Return the key object for a mapping entry. */
+
+  PyObject *(*entry_value)(PyObject *me, void *e);
+    /* Return the value object for a mapping entry. */
+
+  int (*set_entry)(PyObject *me, void *e, PyObject *val);
+    /* Modify the entry by storing VAL in its place.  Return 0 on success,
+     * or -1 on a Python error.
+     */
+
+  int (*del_entry)(PyObject *me, void *e);
+    /* Delete the entry.  (It may be necessary to delete a freshly allocated
+     * entry, e.g., if `set_entry' failed.)  Return 0 on success, or -1 on a
+     * Python error.
+     */
+} gmap_ops;
+
+/* The intrusion at the head of a mapping object. */
+#define GMAP_PYOBJ_HEAD                                                        \
+  PyObject_HEAD                                                                \
+  const gmap_ops *gmops;
+
+typedef struct gmap_pyobj {
+  GMAP_PYOBJ_HEAD
+} gmap_pyobj;
+#define GMAP_OPS(obj) (((gmap_pyobj *)(obj))->gmops)
+  /* Discover the operations from a mapping object. */
+
 /* Mapping methods. */
 #define GMAP_METMNAME(func) gmapmeth_##func
 #define GMAP_METH(func, doc) STD_METHOD(GMAP_METMNAME, func, 0, doc)
@@ -399,7 +448,7 @@ extern PyMethodDef *donemethods(void);
   KWMETH(setdefault,   "D.setdefault(K, [default = None]) -> VALUE")   \
   KWMETH(pop,          "D.pop(KEY, [default = <error>]) -> VALUE")     \
   NAMETH(popitem,      "D.popitem() -> (KEY, VALUE)")                  \
-  METH (update,        "D.update(MAP)")
+  KWMETH(update,       "D.update(MAP)")
 
 GMAP_DOMETHODS(GMAP_METHDECL, GMAP_KWMETHDECL, GMAP_NAMETHDECL)
 #define GMAP_ROMETHODS GMAP_DOROMETHODS(GMAP_METH, GMAP_KWMETH, GMAP_NAMETH)
@@ -407,7 +456,12 @@ GMAP_DOMETHODS(GMAP_METHDECL, GMAP_KWMETHDECL, GMAP_NAMETHDECL)
 
 /* Mapping protocol implementation. */
 extern Py_ssize_t gmap_pysize(PyObject *); /* for `mp_length' */
+extern PyObject *gmap_pyiter(PyObject *); /* for `tp_iter' */
+extern PyObject *gmap_pylookup(PyObject *, PyObject *); /* for `mp_subscript' */
+extern int gmap_pystore(PyObject *, PyObject *, PyObject *); /* for `mp_ass_subscript' */
+extern int gmap_pyhaskey(PyObject *, PyObject *); /* for `sq_contains' */
 extern const PySequenceMethods gmap_pysequence; /* for `tp_as_sequence' */
+extern const PyMethodDef gmapro_pymethods[]; /* read-only methods */
 extern const PyMethodDef gmap_pymethods[]; /* all the standard methods */
 
 /*----- That's all, folks -------------------------------------------------*/
index 259b742..87a321c 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -12,7 +12,7 @@ cat = DC.Extension('catacomb._base',
                     'mp.c', 'field.c', 'ec.c', 'group.c', 'passphrase.c',
                     'share.c', 'key.c',
                     'pyke/pyke.c', 'pyke/pyke-mLib.c',
-                    'pyke/mapping.c'],
+                    'pyke/mapping.c', 'pyke/mapping-mLib.c'],
                    ##extra_compile_args = ['-O0'],
                    include_dirs = MS.uniquify(MS.INCLUDEDIRS),
                    library_dirs = MS.uniquify(MS.LIBDIRS),
index 42bbd76..b63a902 100644 (file)
@@ -165,8 +165,8 @@ class MutableMappingTestMixin (ImmutableMappingTextMixin):
 
     model.update({ 2: 212, 6: 606, 7: 707 })
     map.update({ me._mkkey(2): me._mkvalue(212),
-                 me._mkkey(6): me._mkvalue(606),
-                 me._mkkey(7): me._mkvalue(707) })
+                 me._mkkey(6): me._mkvalue(606) },
+               **{ me._mkkey(7): me._mkvalue(707) })
     check_views()
 
     model[9] = 909