chiark / gitweb /
Add 'pyke/' from commit '6c2569879c220eeac35957cdba9a043e8a5ea9ed'
authorMark Wooding <mdw@distorted.org.uk>
Sat, 11 Apr 2020 14:22:40 +0000 (15:22 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 11 Apr 2020 14:22:40 +0000 (15:22 +0100)
git-subtree-dir: pyke
git-subtree-mainline: c76685552d4bda3d354a8237749106e4460a4270
git-subtree-split: 6c2569879c220eeac35957cdba9a043e8a5ea9ed

pyke/.skelrc [new file with mode: 0644]
pyke/mapping-mLib.c [new file with mode: 0644]
pyke/mapping.c [new file with mode: 0644]
pyke/pyke-mLib.c [new file with mode: 0644]
pyke/pyke-mLib.h [new file with mode: 0644]
pyke/pyke.c [new file with mode: 0644]
pyke/pyke.h [new file with mode: 0644]

diff --git a/pyke/.skelrc b/pyke/.skelrc
new file mode 100644 (file)
index 0000000..21912db
--- /dev/null
@@ -0,0 +1,9 @@
+;;; -*-emacs-lisp-*-
+
+(setq skel-alist
+      (append
+       '((author . "Straylight/Edgeware")
+        (licence-text . "[[gpl]]")
+        (full-title . "Pyke: the Python Kit for Extensions")
+        (program . "Pyke"))
+       skel-alist))
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 -------------------------------------------------*/
diff --git a/pyke/mapping.c b/pyke/mapping.c
new file mode 100644 (file)
index 0000000..ad02d25
--- /dev/null
@@ -0,0 +1,965 @@
+/* -*-c-*-
+ *
+ * Generic mapping support
+ *
+ * (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.h"
+
+/*----- Iteration ---------------------------------------------------------*/
+
+static PyTypeObject *keyiter_pytype, *itemiter_pytype, *valiter_pytype;
+
+union iterstate {
+  void *external;
+  void *internal[4];
+};
+
+typedef struct iter_pyobj {
+  PyObject_HEAD
+  PyObject *map;
+  union iterstate iter;
+} iter_pyobj;
+#define ITER_MAP(o) (((iter_pyobj *)(o))->map)
+#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)
+{
+  PyObject *map = ITER_MAP(me);
+  iter_free(map, ITER_ITER(me));
+  Py_DECREF(map); FREEOBJ(me);
+}
+
+static PyObject *gmap_mkiter(PyObject *me, PyTypeObject *ty)
+{
+  iter_pyobj *iter = PyObject_NEW(iter_pyobj, ty);
+
+  iter->map = me; Py_INCREF(me);
+  iter_init(me, &iter->iter);
+  return ((PyObject *)iter);
+}
+
+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 = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "_KeyIter",                          /* @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 keys of a mapping.",
+
+  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 *valiter_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_value(map, e));
+}
+
+static const PyTypeObject valiter_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "_ValueIter",                                /* @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 values of a mapping.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  PyObject_SelfIter,                   /* @tp_iter@ */
+  valiter_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 *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 = {
+  PyVarObject_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@ */
+};
+
+/*----- Mapping views -----------------------------------------------------*/
+
+#ifdef PY3
+
+static PyTypeObject *keyview_pytype, *itemview_pytype, *valview_pytype;
+
+typedef struct view_pyobj {
+  PyObject_HEAD
+  PyObject *map;
+} view_pyobj;
+#define VIEW_MAP(o) (((view_pyobj *)(o))->map)
+
+static PyObject *gmap_mkview(PyObject *me, PyTypeObject *ty)
+{
+  view_pyobj *v = PyObject_NEW(view_pyobj, ty);
+  v->map = me; Py_INCREF(me);
+  return ((PyObject *)v);
+}
+
+static void view_pydealloc(PyObject *me)
+  { Py_DECREF(VIEW_MAP(me)); FREEOBJ(me); }
+
+#define BINOP(op, update)                                              \
+  static PyObject *view_py##op(PyObject *me, PyObject *you)            \
+  {                                                                    \
+    PyObject *set = 0;                                                 \
+    PyObject *rc = 0;                                                  \
+                                                                       \
+    set = PySet_New(me); if (!set) goto end;                           \
+    if (!PyObject_CallMethod(set, #update, "(O)", you)) goto end;      \
+    rc = set; set = 0;                                                 \
+  end:                                                                 \
+    Py_XDECREF(set);                                                   \
+    return (rc);                                                       \
+  }
+BINOP(and, intersection_update)
+BINOP(or, update)
+BINOP(xor, symmetric_difference_update)
+#undef BINOP
+
+static int all_contained_p(PyObject *x, PyObject *y)
+{
+  PyObject *i = 0, *e;
+  int b, rc = -1;
+
+  i = PyObject_GetIter(x); if (!i) goto end;
+  for (;;) {
+    e = PyIter_Next(i); if (!e) break;
+    b = PySequence_Contains(y, e); Py_DECREF(e); if (b < 0) goto end;
+    if (!b) { rc = 0; goto end; }
+  }
+  if (PyErr_Occurred()) goto end;
+  rc = 1;
+end:
+  Py_XDECREF(i);
+  return (rc);
+}
+
+static Py_ssize_t view_pysize(PyObject *me)
+  { return (PyMapping_Size(VIEW_MAP(me))); }
+
+static PyObject *view_pyrichcompare(PyObject *me, PyObject *you, int op)
+{
+  PyObject *map = ITER_MAP(me);
+  Py_ssize_t mysz, yoursz;
+  int b;
+
+  mysz = PyMapping_Size(map); if (mysz < 0) return (0);
+  yoursz = PyObject_Size(you);
+  if (yoursz < 0) { PyErr_Clear(); RETURN_NOTIMPL; }
+
+  switch (op) {
+    case Py_EQ:
+      if (mysz != yoursz) RETURN_FALSE;
+      b = all_contained_p(you, me);
+      break;
+    case Py_NE:
+      if (mysz != yoursz) RETURN_TRUE;
+      b = all_contained_p(you, me);
+      break;
+    case Py_LT:
+      if (mysz >= yoursz) RETURN_FALSE;
+      b = all_contained_p(me, you);
+      break;
+    case Py_LE:
+      if (mysz > yoursz) RETURN_FALSE;
+      b = all_contained_p(me, you);
+      break;
+    case Py_GE:
+      if (mysz < yoursz) RETURN_FALSE;
+      b = all_contained_p(you, me);
+      break;
+    case Py_GT:
+      if (mysz <= yoursz) RETURN_FALSE;
+      b = all_contained_p(you, me);
+      break;
+    default:
+      abort();
+  }
+  if (b < 0) return (0);
+  return (getbool(b));
+}
+
+static PyObject *keyview_pyiter(PyObject *me)
+  { return (gmap_mkiter(VIEW_MAP(me), keyiter_pytype)); }
+
+static int keyview_pyhaskey(PyObject *me, PyObject *k)
+{
+  PyObject *map = VIEW_MAP(me);
+  const struct gmap_ops *gmops = GMAP_OPS(map);
+  return (gmops->lookup(map, k, 0) ? 1 : PyErr_Occurred() ? -1 : 0);
+}
+
+static int itemview_pyhaskey(PyObject *me, PyObject *it)
+{
+  PyObject *map = VIEW_MAP(me);
+  const struct gmap_ops *gmops = GMAP_OPS(map);
+  void *e;
+  int b;
+  PyObject *v;
+
+  if (!PyTuple_Check(it) || PyTuple_GET_SIZE(it) != 2) return (0);
+  e = gmops->lookup(map, PyTuple_GET_ITEM(it, 0), 0);
+  if (!e) return (PyErr_Occurred() ? -1 : 0);
+  v = gmops->entry_value(map, e); if (!v) return (-1);
+  b = PyObject_RichCompareBool(v, PyTuple_GET_ITEM(it, 1), Py_EQ);
+  Py_DECREF(v); return (b);
+}
+
+static PyObject *valview_pyiter(PyObject *me)
+  { return (gmap_mkiter(VIEW_MAP(me), valiter_pytype)); }
+
+static PyObject *itemview_pyiter(PyObject *me)
+  { return (gmap_mkiter(VIEW_MAP(me), itemiter_pytype)); }
+
+static const PyNumberMethods view_pynumber = {
+  0,                                   /* @nb_add@ */
+  0,                                   /* @nb_subtract@ */
+  0,                                   /* @nb_multiply@ */
+#ifdef PY2
+  0,                                   /* @nb_divide@ */
+#endif
+  0,                                   /* @nb_remainder@ */
+  0,                                   /* @nb_divmod@ */
+  0,                                   /* @nb_power@ */
+  0,                                   /* @nb_negative@ */
+  0,                                   /* @nb_positive@ */
+  0,                                   /* @nb_absolute@ */
+  0,                                   /* @nb_nonzero@ */
+  0,                                   /* @nb_invert@ */
+  0,                                   /* @nb_lshift@ */
+  0,                                   /* @nb_rshift@ */
+  view_pyand,                          /* @nb_and@ */
+  view_pyxor,                          /* @nb_xor@ */
+  view_pyor,                           /* @nb_or@ */
+  0,                                   /* @nb_coerce@ */
+  0,                                   /* @nb_int@ */
+  0,                                   /* @nb_long@ */
+  0,                                   /* @nb_float@ */
+  0,                                   /* @nb_oct@ */
+  0,                                   /* @nb_hex@ */
+};
+
+static const PySequenceMethods keyview_pysequence = {
+  view_pysize,                         /* @sq_length@ */
+  0,                                   /* @sq_concat@ */
+  0,                                   /* @sq_repeat@ */
+  0,                                   /* @sq_item@ */
+  0,                                   /* @sq_slice@ */
+  0,                                   /* @sq_ass_item@ */
+  0,                                   /* @sq_ass_slice@ */
+  keyview_pyhaskey,                    /* @sq_contains@ */
+  0,                                   /* @sq_inplace_concat@ */
+  0,                                   /* @sq_inplace_repeat@ */
+};
+
+static const PyTypeObject keyview_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "_KeyView",                          /* @tp_name@ */
+  sizeof(view_pyobj),                  /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  view_pydealloc,                      /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  PYNUMBER(view),                      /* @tp_as_number@ */
+  PYSEQUENCE(keyview),                 /* @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@ */
+  "View of a mapping's keys.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  view_pyrichcompare,                  /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  keyview_pyiter,                      /* @tp_iter@ */
+  0,                                   /* @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 const PySequenceMethods valview_pysequence = {
+  view_pysize,                         /* @sq_length@ */
+  0,                                   /* @sq_concat@ */
+  0,                                   /* @sq_repeat@ */
+  0,                                   /* @sq_item@ */
+  0,                                   /* @sq_slice@ */
+  0,                                   /* @sq_ass_item@ */
+  0,                                   /* @sq_ass_slice@ */
+  0,                                   /* @sq_contains@ */
+  0,                                   /* @sq_inplace_concat@ */
+  0,                                   /* @sq_inplace_repeat@ */
+};
+
+static const PyTypeObject valview_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "_ValueView",                                /* @tp_name@ */
+  sizeof(view_pyobj),                  /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  view_pydealloc,                      /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  PYNUMBER(view),                      /* @tp_as_number@ */
+  PYSEQUENCE(valview),                 /* @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@ */
+  "View of a mapping's values.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  valview_pyiter,                      /* @tp_iter@ */
+  0,                                   /* @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 const PySequenceMethods itemview_pysequence = {
+  view_pysize,                         /* @sq_length@ */
+  0,                                   /* @sq_concat@ */
+  0,                                   /* @sq_repeat@ */
+  0,                                   /* @sq_item@ */
+  0,                                   /* @sq_slice@ */
+  0,                                   /* @sq_ass_item@ */
+  0,                                   /* @sq_ass_slice@ */
+  itemview_pyhaskey,                   /* @sq_contains@ */
+  0,                                   /* @sq_inplace_concat@ */
+  0,                                   /* @sq_inplace_repeat@ */
+};
+
+static const PyTypeObject itemview_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "_ItemView",                         /* @tp_name@ */
+  sizeof(view_pyobj),                  /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  view_pydealloc,                      /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  PYNUMBER(view),                      /* @tp_as_number@ */
+  PYSEQUENCE(itemview),                        /* @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@ */
+  "View of a mapping's key/value items.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  view_pyrichcompare,                  /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  itemview_pyiter,                     /* @tp_iter@ */
+  0,                                   /* @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@ */
+};
+
+#endif
+
+/*----- 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@ */
+  0,                                   /* @sq_repeat@ */
+  0,                                   /* @sq_item@ */
+  0,                                   /* @sq_slice@ */
+  0,                                   /* @sq_ass_item@ */
+  0,                                   /* @sq_ass_slice@ */
+  gmap_pyhaskey,                       /* @sq_contains@ */
+  0,                                   /* @sq_inplace_concat@ */
+  0                                    /* @sq_inplace_repeat@ */
+};
+
+#ifdef PY3
+
+PyObject *gmapmeth_keys(PyObject *me)
+  { return (gmap_mkview(me, keyview_pytype)); }
+
+PyObject *gmapmeth_values(PyObject *me)
+  { return (gmap_mkview(me, valview_pytype)); }
+
+PyObject *gmapmeth_items(PyObject *me)
+  { return (gmap_mkview(me, itemview_pytype)); }
+
+#else
+
+PyObject *gmapmeth_has_key(PyObject *me, PyObject *arg)
+{
+  PyObject *k;
+  void *e;
+  if (!PyArg_ParseTuple(arg, "O:has_key", &k)) return (0);
+  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)
+{
+  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) 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);
+  if (i) iter_free(me, &iter);
+  return (rc);
+}
+
+PyObject *gmapmeth_values(PyObject *me)
+{
+  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;
+  }
+  rc = l; l = 0;
+done:
+  Py_XDECREF(l);
+  if (i) iter_free(me, &iter);
+  return (rc);
+}
+
+PyObject *gmapmeth_items(PyObject *me)
+{
+  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;
+  }
+  rc = l; l = 0;
+done:
+  Py_XDECREF(l);
+  if (i) iter_free(me, &iter);
+  return (rc);
+}
+
+PyObject *gmapmeth_iterkeys(PyObject *me)
+  { return (gmap_mkiter(me, keyiter_pytype)); }
+
+PyObject *gmapmeth_itervalues(PyObject *me)
+  { return (gmap_mkiter(me, valiter_pytype)); }
+
+PyObject *gmapmeth_iteritems(PyObject *me)
+  { return (gmap_mkiter(me, itemiter_pytype)); }
+
+#endif
+
+PyObject *gmap_pyiter(PyObject *me)
+  { return gmap_mkiter(me, keyiter_pytype); }
+
+PyObject *gmapmeth_clear(PyObject *me)
+{
+  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;
+  }
+  iter_free(me, &iter);
+  rc = me; Py_INCREF(me);
+end:
+  return (rc);
+}
+
+static const char *const def_kwlist[] = { "key", "default", 0 };
+#define DEF_KWLIST ((/*unconst*/ char **)def_kwlist)
+
+PyObject *gmapmeth_get(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  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);
+  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)
+{
+  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);
+  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)
+{
+  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))
+    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);
+}
+
+static int update_core(PyObject *me, PyObject *map)
+{
+  const gmap_ops *gmops = GMAP_OPS(me);
+  PyObject *i = 0, *item = 0, *k = 0, *v = 0;
+  void *e;
+  unsigned foundp;
+  int rc = -1;
+
+  v = PyObject_CallMethod(map, PY23("iteritems", "items"), 0);
+#ifdef PY3
+  if (v) { i = PyObject_GetIter(v); Py_DECREF(v); v = 0; }
+#else
+  i = v; v = 0;
+#endif
+
+  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;
+  }
+  rc = 0;
+end:
+  Py_XDECREF(i); Py_XDECREF(item);
+  Py_XDECREF(k); Py_XDECREF(v);
+  return (rc);
+}
+
+PyObject *gmapmeth_update(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  PyObject *map = 0;
+
+  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; }
+  }
+end:
+  return (rc);
+}
+
+const PyMethodDef gmapro_pymethods[] = {
+  GMAP_ROMETHODS
+  { 0 }
+};
+
+const PyMethodDef gmap_pymethods[] = {
+  GMAP_METHODS
+  { 0 }
+};
+
+/*----- Submodule initialization ------------------------------------------*/
+
+void pyke_gmap_pyinit(void)
+{
+  INITTYPE(keyiter, root);
+  INITTYPE(itemiter, root);
+  INITTYPE(valiter, root);
+#ifdef PY3
+  INITTYPE(keyview, root);
+  INITTYPE(valview, root);
+  INITTYPE(itemview, root);
+#endif
+}
+
+void pyke_gmap_pyinsert(PyObject *mod)
+{
+  INSERT("_KeyIter", keyiter_pytype);
+  INSERT("_ValueIter", valiter_pytype);
+  INSERT("_ItemIter", itemiter_pytype);
+#ifdef PY3
+  INSERT("_KeyView", keyview_pytype);
+  INSERT("_ValueView", valview_pytype);
+  INSERT("_ItemView", itemview_pytype);
+#endif
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/pyke/pyke-mLib.c b/pyke/pyke-mLib.c
new file mode 100644 (file)
index 0000000..900dc41
--- /dev/null
@@ -0,0 +1,134 @@
+/* -*-c-*-
+ *
+ * Pyke: the Python Kit for Extensions, 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"
+
+/* #undef HAVE_LONG_LONG */
+
+/*----- Conversions -------------------------------------------------------*/
+
+#ifndef HAVE_LONG_LONG
+static PyObject *i32 = 0;
+static int init_i32(void)
+  { if (!i32 && (i32 = PyInt_FromLong(32)) == 0) return (-1); return (0); }
+#endif
+
+PyObject *getk64(kludge64 u)
+{
+#ifdef HAVE_LONG_LONG
+  return (PyLong_FromUnsignedLongLong(GET64(unsigned PY_LONG_LONG, u)));
+#else
+  PyObject *i = 0, *j = 0, *t;
+  PyObject *rc = 0;
+
+  if (init_i32()) goto end;
+  if ((i = PyLong_FromUnsignedLong(HI64(u))) == 0) goto end;
+  if ((t = PyNumber_InPlaceLshift(i, i32)) == 0) goto end;
+  Py_DECREF(i); i = t;
+  if ((j = PyLong_FromUnsignedLong(LO64(u))) == 0) goto end;
+  if ((t = PyNumber_InPlaceOr(i, j)) == 0) goto end;
+#  ifdef PY2
+  Py_DECREF(i); i = t;
+  if ((t = PyNumber_Int(i)) == 0) goto end;
+#  endif
+  rc = t;
+end:
+  Py_XDECREF(i);
+  Py_XDECREF(j);
+  return (rc);
+#endif
+}
+
+#ifdef HAVE_UINT64
+#  define CONVu64(n) do {                                              \
+     kludge64 k;                                                       \
+     uint64 t;                                                         \
+     if (!convk64(o, &k)) goto end;                                    \
+     t = GET64(uint64, k);                                             \
+     if (t > MASK##n) OVFERR("out of range");                          \
+     *p = t;                                                           \
+   } while (0)
+#else
+#  define CONVu64(n) assert(!"shouldn't be possible")
+#endif
+
+#define CONVU_(n)                                                      \
+  int convu##n(PyObject *o, void *pp)                                  \
+  {                                                                    \
+    unsigned long u;                                                   \
+    uint##n *p = pp;                                                   \
+                                                                       \
+    if (MASK##n > ULONG_MAX)                                           \
+      CONVu64(n);                                                      \
+    else {                                                             \
+      if (!convulong(o, &u)) goto end;                                 \
+      if (u > MASK##n) OVFERR("out of range");                         \
+      *p = u;                                                          \
+    }                                                                  \
+    return (1);                                                                \
+  end:                                                                 \
+    return (0);                                                                \
+  }
+DOUINTSZ(CONVU_)
+
+int convk64(PyObject *o, void *pp)
+{
+  PyObject *i = 0;
+  int rc = 0;
+#if HAVE_LONG_LONG
+  unsigned PY_LONG_LONG t;
+#else
+  PyObject *t;
+  uint32 lo, hi;
+#endif
+
+#if HAVE_LONG_LONG
+  if ((i = PyNumber_Long(o)) == 0) goto end;
+  t = PyLong_AsUnsignedLongLong(i);
+  if (t == (unsigned PY_LONG_LONG)-1 && PyErr_Occurred()) goto end;
+  ASSIGN64(*(kludge64 *)pp, t);
+#else
+  if (init_i32()) goto end;
+  if ((i = PyNumber_Int(o)) == 0) goto end;
+  lo = PyInt_AsUnsignedLongMask(i);
+  if ((t = PyNumber_InPlaceRshift(i, i32)) == 0) goto end;
+  Py_DECREF(i); i = t;
+  hi = PyInt_AsUnsignedLongMask(i);
+  if ((t = PyNumber_InPlaceRshift(i, i32)) == 0) goto end;
+  Py_DECREF(i); i = t;
+  if (PyObject_IsTrue(i)) OVFERR("out of range");
+  SET64(*(kludge64 *)pp, hi, lo);
+#endif
+  rc = 1;
+
+end:
+  Py_XDECREF(i);
+  return (rc);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/pyke/pyke-mLib.h b/pyke/pyke-mLib.h
new file mode 100644 (file)
index 0000000..aa50bd8
--- /dev/null
@@ -0,0 +1,68 @@
+/* -*-c-*-
+ *
+ * Pyke: the Python Kit for Extensions, 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.
+ */
+
+#ifndef PYKE_MLIB_H
+#define PYKE_MLIB_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifndef PYKE_H
+#  include "pyke.h"
+#endif
+
+PUBLIC_SYMBOLS;
+#include <mLib/bits.h>
+#include <mLib/sym.h>
+PRIVATE_SYMBOLS;
+
+/*----- Conversions -------------------------------------------------------*/
+
+#define DECL_CONVU_(n) extern int convu##n(PyObject *, void *);
+DOUINTSZ(DECL_CONVU_)
+  /* Define an `O&' input conversion `convuN' for each mLib type `uintN'. */
+
+extern int convk64(PyObject *, void *);
+  /* Input conversion for `kludge64'. */
+
+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
+  }
+#endif
+
+#endif
diff --git a/pyke/pyke.c b/pyke/pyke.c
new file mode 100644 (file)
index 0000000..330f15c
--- /dev/null
@@ -0,0 +1,419 @@
+/* -*-c-*-
+ *
+ * Pyke: the Python Kit for Extensions
+ *
+ * (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.h"
+
+/*----- External variables ------------------------------------------------*/
+
+PyObject *modname;
+PyObject *home_module;
+
+/*----- Conversions -------------------------------------------------------*/
+
+PyObject *getulong(unsigned long w)
+{
+  if (w <= LONG_MAX) return (PyInt_FromLong(w));
+  else return (PyLong_FromUnsignedLong(w));
+}
+
+PyObject *getbool(int b)
+  { if (b) RETURN_TRUE; else RETURN_FALSE; }
+
+int convulong(PyObject *o, void *pp)
+{
+  unsigned long *p = pp;
+  PyObject *t;
+
+  if (!o) VALERR("can't delete");
+#ifdef PY2
+  if (PyInt_Check(o)) {
+    long i = PyInt_AS_LONG(o);
+    if (i < 0) VALERR("must be nonnegative");
+    *p = i;
+  } else
+#endif
+  {
+    if ((t = PyNumber_Long(o)) == 0) goto end;
+    *p = PyLong_AsUnsignedLong(t);
+    Py_DECREF(t);
+    if (PyErr_Occurred()) goto end;
+  }
+  return (1);
+end:
+  return (0);
+}
+
+int convuint(PyObject *o, void *pp)
+{
+  unsigned long u;
+  unsigned *p = pp;
+
+  if (!convulong(o, &u)) goto end;
+  if (u > UINT_MAX) VALERR("out of range");
+  *p = u;
+  return (1);
+end:
+  return (0);
+}
+
+int convszt(PyObject *o, void *pp)
+{
+  unsigned long u;
+  size_t *p = pp;
+
+  if (!convulong(o, &u)) goto end;
+  if (u > ~(size_t)0) VALERR("out of range");
+  *p = u;
+  return (1);
+end:
+  return (0);
+}
+
+int convbool(PyObject *o, void *pp)
+{
+  if (!o) VALERR("can't delete");
+  *(int *)pp = PyObject_IsTrue(o);
+  return (1);
+end:
+  return (0);
+}
+
+int convbin(PyObject *o, void *pp)
+{
+  struct bin *r = pp;
+
+  if (BIN_CHECK(o)) {
+    r->p = BIN_PTR(o);
+    r->sz = BIN_LEN(o);
+    return (1);
+  }
+#ifdef PY2
+  if (PyUnicode_Check(o)) {
+    o = _PyUnicode_AsDefaultEncodedString(o, 0);
+    if (!o) return (0);
+    r->p = PyString_AS_STRING(o);
+    r->sz = PyString_GET_SIZE(o);
+    return (1);
+  }
+#endif
+  return (PyObject_AsReadBuffer(o, &r->p, &r->sz) ? 0 : 1);
+}
+
+/*----- Miscellaneous utilities -------------------------------------------*/
+
+PyObject *abstract_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  PyErr_SetString(PyExc_TypeError, "can't instantiate this class");
+  return (0);
+}
+
+PyObject *enrich_compare(int op, int cmp)
+{
+  int r = -1;
+
+  switch (op) {
+    case Py_LT: r = cmp <  0; break;
+    case Py_LE: r = cmp <= 0; break;
+    case Py_EQ: r = cmp == 0; break;
+    case Py_NE: r = cmp != 0; break;
+    case Py_GE: r = cmp >= 0; break;
+    case Py_GT: r = cmp >  0; break;
+    default: assert(0);
+  }
+  return (getbool(r));
+}
+
+/*----- Saving and restoring exceptions ----------------------------------*/
+
+void report_lost_exception_v(struct excinfo *exc,
+                            const char *why, va_list ap)
+{
+  PyObject *hookfn = 0;
+  PyObject *whyobj = 0;
+  PyObject *obj = 0;
+
+  /* Make sure we start out without a pending exception, or this will get
+   * really confusing.
+   */
+  assert(!PyErr_Occurred());
+
+  /* Format the explanation. */
+  if (why) whyobj = TEXT_VFORMAT(why, ap);
+  else { whyobj = Py_None; Py_INCREF(whyobj); }
+
+  /* Find our home module's `lostexchook' function.  This won't work if
+   * there's no module, or the function isn't defined, or it's `None'.
+   */
+  if (!home_module) goto sys;
+  hookfn = PyObject_GetAttrString(home_module, "lostexchook");
+  if (hookfn == Py_None) goto sys;
+  else if (hookfn) ;
+  else if (!PyErr_ExceptionMatches(PyExc_AttributeError)) goto ouch;
+  else { PyErr_Clear(); goto sys; }
+
+  /* Call the hook function. */
+  obj = PyObject_CallFunction(hookfn, "(OOOO)",
+                             whyobj, exc->ty, exc->val, exc->tb);
+  if (!obj) goto ouch;
+  goto end;
+
+  /* Something went wrong reporting the problem. */
+ouch:
+  PySys_WriteStderr("\n!!! FAILURE REPORTING LOST EXCEPTION\n");
+  PyErr_Print();
+  /* drop through... */
+
+  /* There was no hook, so try to do something sensible using
+   * `sys.excepthook'.
+   */
+sys:
+  PySys_WriteStderr("\n!!! LOST EXCEPTION: %s\n", TEXT_PTR(whyobj));
+  RESTORE_EXCINFO(exc);
+  PyErr_Print();
+  /* drop through... */
+
+  /* Clean up afterwards. */
+end:
+  Py_XDECREF(hookfn);
+  Py_XDECREF(whyobj);
+  Py_XDECREF(obj);
+}
+
+void report_lost_exception(struct excinfo *exc, const char *why, ...)
+{
+  va_list ap;
+
+  va_start(ap, why);
+  report_lost_exception_v(exc, why, ap);
+  va_end(ap);
+}
+
+void stash_exception(struct excinfo *exc, const char *why, ...)
+{
+  va_list ap;
+  struct excinfo stash;
+
+  if (!exc->ty)
+    STASH_EXCINFO(exc);
+  else {
+    va_start(ap, why);
+    STASH_EXCINFO(&stash);
+    report_lost_exception_v(&stash, why, ap);
+    va_end(ap);
+  }
+}
+
+void restore_exception(struct excinfo *exc, const char *why, ...)
+{
+  va_list ap;
+  struct excinfo stash;
+
+  if (!PyErr_Occurred())
+    RESTORE_EXCINFO(exc);
+  else {
+    va_start(ap, why);
+    STASH_EXCINFO(&stash);
+    report_lost_exception_v(exc, why, ap);
+    RESTORE_EXCINFO(&stash);
+    va_end(ap);
+  }
+}
+
+/*----- Type definitions --------------------------------------------------*/
+
+static const PyTypeObject emptytype = { 0 };
+
+void *newtype(PyTypeObject *metaty,
+             const PyTypeObject *skel,
+             const char *name)
+{
+  PyHeapTypeObject *ty =
+    (PyHeapTypeObject *)_PyObject_GC_Malloc(_PyObject_VAR_SIZE(metaty, 0));
+  if (!skel) skel = &emptytype;
+  memcpy(ty, skel, sizeof(*skel));
+#define COPY(blah) do {                                                        \
+    if (ty->ht_type.tp_as_##blah) {                                    \
+      memcpy(&ty->as_##blah,                                           \
+            ty->ht_type.tp_as_##blah,                                  \
+            sizeof(ty->as_##blah));                                    \
+      ty->ht_type.tp_as_##blah = &ty->as_##blah;                       \
+    }                                                                  \
+  } while (0)
+  COPY(number);
+  COPY(sequence);
+  COPY(mapping);
+  COPY(buffer);
+#undef COPY
+  if (name)
+    ty->ht_name = TEXT_FROMSTR(name);
+  else if (ty->ht_type.tp_name)
+    ty->ht_name = TEXT_FROMSTR(ty->ht_type.tp_name);
+  else
+    ty->ht_name = 0;
+  if (ty->ht_name)
+    ty->ht_type.tp_name = TEXT_STR(ty->ht_name);
+  ty->ht_slots = 0;
+#ifdef PY3
+  ty->ht_qualname = 0;
+#endif
+  (void)PyObject_INIT(&ty->ht_type, metaty);
+  Py_INCREF(metaty);
+  return (ty);
+}
+
+void typeready(PyTypeObject *ty)
+{
+#ifdef PY3
+  PyHeapTypeObject *hty = (PyHeapTypeObject *)ty;
+  hty->ht_qualname = hty->ht_name;
+#endif
+  PyType_Ready(ty);
+  PyDict_SetItemString(ty->tp_dict, "__module__", modname);
+}
+
+PyTypeObject *inittype(const PyTypeObject *tyskel,
+                      PyTypeObject *base, PyTypeObject *meta)
+{
+  PyTypeObject *ty = newtype(meta, tyskel, 0);
+  if (base) { ty->tp_base = base; Py_INCREF(base); }
+  ty->tp_flags |= Py_TPFLAGS_HEAPTYPE;
+  typeready(ty);
+  return (ty);
+}
+
+/*----- Populating modules ------------------------------------------------*/
+
+PyObject *mkexc(PyObject *mod, PyObject *base,
+               const char *name, const PyMethodDef *mm)
+{
+  PyObject *nameobj = 0;
+  PyObject *dict = 0;
+  PyObject *exc = 0;
+  PyObject *func = 0;
+  PyObject *meth = 0;
+
+  if ((dict = PyDict_New()) == 0) goto fail;
+
+  if (mm) {
+    while (mm->ml_name) {
+      if ((func = PyCFunction_NewEx((/*unconst*/ PyMethodDef *)mm,
+                                   0, mod)) == 0 ||
+         (meth = PY23(PyMethod_New(func, 0, exc),
+                      PyInstanceMethod_New(func))) == 0 ||
+         PyDict_SetItemString(dict, mm->ml_name, meth))
+       goto fail;
+      Py_DECREF(func); func = 0;
+      Py_DECREF(meth); meth = 0;
+      mm++;
+    }
+  }
+
+  if ((nameobj = TEXT_FORMAT("%s.%s", PyModule_GetName(mod), name)) == 0 ||
+      (exc = PyErr_NewException(TEXT_STR(nameobj), base, dict)) == 0)
+    goto fail;
+
+done:
+  Py_XDECREF(nameobj);
+  Py_XDECREF(dict);
+  return (exc);
+
+fail:
+  Py_XDECREF(exc);
+  Py_XDECREF(func);
+  Py_XDECREF(meth);
+  exc = 0;
+  goto done;
+}
+
+void setconstants(PyObject *mod, const struct nameval *c)
+{
+  PyObject *x;
+  unsigned long u;
+
+  while (c->name) {
+    u = c->value;
+    if (u <= LONG_MAX) x = PyInt_FromLong(u);
+    else if (c->f&CF_SIGNED) x = PyInt_FromLong(-1 - (long)(ULONG_MAX - u));
+    else x = PyLong_FromUnsignedLong(u);
+    PyModule_AddObject(mod, (/*unconst*/ char *)c->name, x);
+    c++;
+  }
+}
+
+/*----- Submodules --------------------------------------------------------*/
+
+static PyMethodDef *global_methods;
+static size_t nmethods, methodsz;
+
+void addmethods(const PyMethodDef *m)
+{
+  size_t n, want, newsz;
+
+  for (n = 0; m[n].ml_name; n++);
+  want = nmethods + n + 1;
+  if (want > methodsz) {
+    newsz = methodsz ? 2*methodsz : 16;
+    while (want > newsz) newsz *= 2;
+    if (!global_methods)
+      global_methods = PyObject_Malloc(newsz*sizeof(PyMethodDef));
+    else
+      global_methods = PyObject_Realloc(global_methods,
+                                       newsz*sizeof(PyMethodDef));
+    assert(global_methods);
+    methodsz = newsz;
+  }
+  memcpy(global_methods + nmethods, m, n*sizeof(PyMethodDef));
+  nmethods += n;
+  global_methods[nmethods].ml_name = 0;
+}
+
+PyMethodDef *donemethods(void) { return (global_methods); }
+
+/*----- Low-level Python interface ----------------------------------------*/
+
+static PyObject *meth__set_home_module(PyObject *me, PyObject *arg)
+{
+  PyObject *mod;
+
+  if (!PyArg_ParseTuple(arg, "O!:_set_home_module", &PyModule_Type, &mod))
+    return (0);
+  Py_XDECREF(home_module); home_module = mod; Py_INCREF(home_module);
+  RETURN_NONE;
+}
+
+static const PyMethodDef methods[] = {
+#define METHNAME(func) meth_##func
+  METH (_set_home_module, "_set_home_module(MOD)")
+#undef METHNAME
+  { 0 }
+};
+
+void pyke_core_pyinit(void) { addmethods(methods); }
+void pyke_core_pyinsert(PyObject *mod) { ; }
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/pyke/pyke.h b/pyke/pyke.h
new file mode 100644 (file)
index 0000000..a60fd76
--- /dev/null
@@ -0,0 +1,700 @@
+/* -*-c-*-
+ *
+ * Pyke: the Python Kit for Extensions
+ *
+ * (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.
+ */
+
+#ifndef PYKE_H
+#define PYKE_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#define PY_SSIZE_T_CLEAN
+
+#include <Python.h>
+#include <structmember.h>
+
+/*----- Other preliminaries -----------------------------------------------*/
+
+#define NOTHING
+#define COMMA ,
+
+/*----- Symbol visibility -------------------------------------------------*
+ *
+ * This library is very messy regarding symbol namespace.  Keep this mess
+ * within our shared-object.
+ */
+
+#define GOBBLE_SEMI extern int notexist
+#if defined(__GNUC__) && defined(__ELF__)
+#  define PRIVATE_SYMBOLS _Pragma("GCC visibility push(hidden)") GOBBLE_SEMI
+#  define PUBLIC_SYMBOLS _Pragma("GCC visibility pop") GOBBLE_SEMI
+#  define EXPORT __attribute__((__visibility__("default")))
+#else
+#  define PRIVATE_SYMBOLS GOBBLE_SEMI
+#  define PUBLIC_SYMBOLS GOBBLE_SEMI
+#  define EXPORT
+#endif
+
+PRIVATE_SYMBOLS;
+
+/*----- Python version compatibility hacks --------------------------------*/
+
+/* Explicit version switching. */
+#if PY_VERSION_HEX >= 0x03000000
+#  define PY3 1
+#  define PY23(two, three) three
+#else
+#  define PY2 1
+#  define PY23(two, three) two
+#endif
+
+/* The handy `Py_TYPE' and `Py_SIZE' macros turned up in 2.6.  Define them if
+ * they're not already here.
+ */
+#ifndef Py_TYPE
+#  define Py_TYPE(obj) (((PyObject *)(obj))->ob_type)
+#endif
+#ifndef Py_SIZE
+#  define Py_SIZE(obj) (((PyVarObject *)(obj))->ob_size)
+#endif
+
+/* Python 3 added internal structure to the various object headers, and
+ * defined a new macro `PyVarObject_HEAD_INIT' to initialize variable-length
+ * static instances correctly.  Define it if it's not already here.
+ */
+#ifndef PyVarObject_HEAD_INIT
+#  define PyVarObject_HEAD_INIT(super, sz) PyObject_HEAD_INIT(super) sz,
+#endif
+
+/* Python 3 doesn't have `int', only `long', even though it's called `int' at
+ * the Python level.  Provide some obvious macros to fill in the gaps.
+ */
+#ifdef PY3
+#  define PyInt_Check PyLong_Check
+#  define PyInt_FromLong PyLong_FromLong
+#  define PyInt_AS_LONG PyLong_AS_LONG
+#  define PyInt_AsLong PyLong_AsLong
+#  define PyInt_AsUnsignedLongMask PyLong_AsUnsignedLongMask
+#  define PyNumber_Int PyNumber_Long
+#endif
+
+/* Python 3.2 changed the type of hash values, so paper over this annoying
+ * difference.
+ */
+#if PY_VERSION_HEX < 0x03020000
+  typedef long Py_hash_t;
+#endif
+
+/* Python 3 always has the `CHECKTYPES' behaviour, and doesn't define the
+ * flag.
+ */
+#ifdef PY3
+#  define Py_TPFLAGS_CHECKTYPES 0
+#endif
+
+/* Plain octet strings.  Python 2 calls these `str', while Python 3 calls
+ * them `bytes'.  We call them `bin' here, and define the following.
+ *
+ *   * `BINOBJ' is the C type of a `bin' object.
+ *   * `BIN_TYPE' is the Python `type' object for `bin'.
+ *   * `BIN_CHECK(OBJ)' is true if OBJ is a `bin' object.
+ *   * `BIN_PTR(OBJ)' points to the first octet of OBJ, without checking!
+ *   * `BIN_LEN(OBJ)' yields the length of OBJ in octets, without checking!
+ *   * `BIN_FROMSTR(STR)' makes a `bin' object from a null-terminated string.
+ *   * `BIN_FORMAT(FMT, ARGS...)' and `BIN_VFORMAT(FMT, AP)' make a `bin'
+ *     object from a `printf'-like format string and arguments.
+ *   * `BIN_PREPAREWRITE(OBJ, PTR, LEN)' prepares to make a `bin' object: it
+ *     sets PTR to point to a buffer of LEN bytes; call `BIN_DONEWRITE' when
+ *     finished.  The variable OBJ will eventually be the resulting object,
+ *     but until `BIN_DONEWRITE' is called, it may in fact be some different
+ *     object.
+ *   * `BIN_DONEWRITE(OBJ, LEN)' completes making a `bin' object: it adjusts
+ *     its length to be LEN, which must not be larger than the LEN given to
+ *     `BIN_PREPAREWRITE', and sets OBJ to point to the finished object.
+ *   * `BIN_SETLEN(OBJ, LEN)' adjusts the length of OBJ downwards to LEN,
+ *     without checking!
+ #   * `Y' is a format character for `PyArg_ParseTuple...' for retrieving a
+ *     null-terminated octet string from a `bin' object.
+ #   * `YN' is a format character for `PyArg_ParseTuple...' for retrieving an
+ *     octet string and length from any sort-of vaguely binary-ish object.
+ */
+#ifdef PY3
+#  define BINOBJ PyBytesObject
+#  define BIN_TYPE PyBytes_Type
+#  define BIN_CHECK(obj) PyBytes_Check(obj)
+#  define BIN_PTR(obj) PyBytes_AS_STRING(obj)
+#  define BIN_LEN(obj) PyBytes_GET_SIZE(obj)
+#  define BIN_FROMSTR(str) PyBytes_FromString(str)
+#  define BIN_FROMSTRLEN(str, len) PyBytes_FromStringAndSize(str, len)
+#  define BIN_FORMAT PyBytes_FromFormat
+#  define BIN_VFORMAT PyBytes_FromFormatV
+#  define BIN_PREPAREWRITE(obj, ptr, sz) do {                          \
+     (obj) = PyBytes_FromStringAndSize(0, (sz));                       \
+     (ptr) = PyBytes_AS_STRING(obj);                                   \
+   } while (0)
+#  define BIN_DONEWRITE(obj, sz) do Py_SIZE(obj) = (sz); while (0)
+#  define BIN_SETLEN(obj, len) do Py_SIZE(obj) = (len); while (0)
+#  define Y "y"
+#  define YN "y#"
+#else
+#  define BINOBJ PyStringObject
+#  define BIN_TYPE PyString_Type
+#  define BIN_CHECK(obj) PyString_Check(obj)
+#  define BIN_PTR(obj) PyString_AS_STRING(obj)
+#  define BIN_LEN(obj) PyString_GET_SIZE(obj)
+#  define BIN_FROMSTR(str) PyString_FromString(str)
+#  define BIN_FROMSTRLEN(str, len) PyString_FromStringAndSize(str, len)
+#  define BIN_FORMAT PyString_FromFormat
+#  define BIN_VFORMAT PyString_FromFormatV
+#  define BIN_PREPAREWRITE(obj, ptr, sz) do {                          \
+     (obj) = PyString_FromStringAndSize(0, (sz));                      \
+     (ptr) = PyString_AS_STRING(obj);                                  \
+   } while (0)
+#  define BIN_DONEWRITE(obj, sz) do Py_SIZE(obj) = (sz); while (0)
+#  define BIN_SETLEN(obj, len) do Py_SIZE(obj) = (len); while (0)
+#  define Y "s"
+#  define YN "s#"
+#endif
+
+/* Text strings.  Both Python 2 and Python 3 call these `str', but they're
+ * very different because a Python 3 `str' is Unicode inside.  When dealing
+ * with Python 3 text, the data is UTF-8 encoded.  We call them `text' here,
+ * and define the following.
+ *
+ *   * `TEXTOBJ' is the C type of a `text' object.
+ *   * `TEXT_TYPE' is the Python `type' object for `text'.
+ *   * `TEXT_CHECK(OBJ)' is true if OBJ is a `text' object.
+ *   * `TEXT_STR(OBJ)' points to the first byte of a null-terminated string
+ *     OBJ, or is null.
+ *   * `TEXT_PTR(OBJ)' points to the first byte of OBJ, without checking!
+ *   * `TEXT_LEN(OBJ)' yields the length of OBJ in octets, without checking!
+ *   * `TEXT_FROMSTR(STR)' makes a `text' object from a null-terminated
+ *     string.
+ *   * `TEXT_FORMAT(FMT, ARGS...)' and `TEST_VFORMAT(FMT, AP)' make a `text'
+ *     object from a `printf'-like format string and arguments.
+ *   * `TEXT_PREPAREWRITE(OBJ, PTR, LEN)' prepares to make a `text' object:
+ *     it sets PTR to point to a buffer of LEN bytes; call `TEXT_DONEWRITE'
+ *     when finished.  The variable OBJ will eventually be the resulting
+ *     object, but until `TEXT_DONEWRITE' is called, it may in fact be some
+ *     different object.
+ *   * `TEXT_DONEWRITE(OBJ, LEN)' completes making a `text' object: it
+ *     adjusts its length to be LEN, which must not be larger than the LEN
+ *     given to `TEXT_PREPAREWRITE', and sets OBJ to point to the finished
+ *     object.
+ *
+ * (Use `s' and `s#' in `PyArg_ParseTuple...'.)
+ */
+#ifdef PY3
+#  define TEXTOBJ PyUnicodeObject
+#  define TEXT_TYPE PyUnicode_Type
+#  define TEXT_CHECK(obj) PyUnicode_Check(obj)
+#  if PY_VERSION_HEX >= 0x03030000
+#    define TEXT_PTR(obj) PyUnicode_AsUTF8(obj)
+#    define TEXT_STR(obj) PyUnicode_AsUTF8(obj)
+#    define TEXT_PTRLEN(obj, ptr, len) do {                            \
+       Py_ssize_t len_;                                                        \
+       (ptr) = PyUnicode_AsUTF8AndSize((obj), &len_);                  \
+       (len) = len_;                                                   \
+     } while (0)
+#    define TEXT_PREPAREWRITE(obj, ptr, sz) do {                       \
+       (obj) = PyUnicode_New((sz), 127);                               \
+       (ptr) = PyUnicode_DATA(obj);                                    \
+     } while (0)
+#    define TEXT_DONEWRITE(obj, len) do {                              \
+       size_t len_ = (len);                                            \
+       assert(PyUnicode_IS_COMPACT_ASCII(obj));                                \
+       ((char *)PyUnicode_DATA(obj))[len_] = 0;                                \
+       ((PyASCIIObject *)(obj))->length = len_;                                \
+     } while (0)
+#  else
+#    define TEXT_PTR(obj) _PyUnicode_AsString(obj)
+#    define TEXT_STR(obj) _PyUnicode_AsString(obj)
+#    define TEXT_PTRLEN(obj, ptr, len) do {                            \
+       Py_ssize_t len_;                                                        \
+       (ptr) = _PyUnicode_AsStringAndSize((obj), &len_);               \
+       (len) = len_;                                                   \
+     } while (0)
+#    define TEXT_PREPAREWRITE(obj, ptr, sz) do {                       \
+       (obj) = PyBytes_FromStringAndSize(0, (sz));                     \
+       (ptr) = PyBytes_AS_STRING(obj);                                 \
+     } while (0)
+#    define TEXT_DONEWRITE(obj, len) do {                              \
+       PyObject *new_;                                                 \
+       Py_SIZE(obj) = (len);                                           \
+       new_ = PyUnicode_FromEncodedObject(obj, 0, 0);                  \
+       assert(new_); Py_DECREF(obj); (obj) = new_;                     \
+     } while (0)
+#  endif
+#  define TEXT_FORMAT PyUnicode_FromFormat
+#  define TEXT_VFORMAT PyUnicode_FromFormatV
+#  define TEXT_FROMSTR(str) PyUnicode_FromString(str)
+#  define TEXT_FROMSTRLEN(str, len) PyUnicode_FromStringAndSize(str, len)
+#else
+#  define TEXTOBJ PyStringObject
+#  define TEXT_TYPE PyString_Type
+#  define TEXT_CHECK(obj) PyString_Check(obj)
+#  define TEXT_PTR(obj) PyString_AS_STRING(obj)
+#  define TEXT_STR(obj) PyString_AsString(obj)
+#  define TEXT_PTRLEN(obj, ptr, len) do {                              \
+     (ptr) = PyString_AS_STRING(obj);                                  \
+     (len) = PyString_GET_SIZE(obj);                                   \
+   } while (0)
+#  define TEXT_FORMAT PyString_FromFormat
+#  define TEXT_VFORMAT PyString_FromFormatV
+#  define TEXT_PREPAREWRITE(obj, ptr, sz) do {                         \
+     (obj) = PyString_FromStringAndSize(0, (sz));                      \
+     (ptr) = PyString_AS_STRING(obj);                                  \
+   } while (0)
+#  define TEXT_DONEWRITE(obj, sz) do { Py_SIZE(obj) = (sz); } while (0)
+#  define TEXT_FROMSTR(str) PyString_FromString(str)
+#  define TEXT_FROMSTRLEN(str, len) PyString_FromStringAndSize(str, len)
+#endif
+
+/*----- Utilities for returning values and exceptions ---------------------*/
+
+/* Returning values. */
+#define RETURN_OBJ(obj) do { Py_INCREF(obj); return (obj); } while (0)
+#define RETURN_NONE RETURN_OBJ(Py_None)
+#define RETURN_NOTIMPL RETURN_OBJ(Py_NotImplemented)
+#define RETURN_TRUE RETURN_OBJ(Py_True)
+#define RETURN_FALSE RETURN_OBJ(Py_False)
+#define RETURN_ME RETURN_OBJ(me)
+
+/* Returning exceptions.  (Note that `KeyError' is `MAPERR' here, because
+ * Catacomb has its own kind of `KeyError'.)
+ */
+#define EXCERR(exc, str) do {                                          \
+  PyErr_SetString(exc, str);                                           \
+  goto end;                                                            \
+} while (0)
+#define VALERR(str) EXCERR(PyExc_ValueError, str)
+#define OVFERR(str) EXCERR(PyExc_OverflowError, str)
+#define TYERR(str) EXCERR(PyExc_TypeError, str)
+#define IXERR(str) EXCERR(PyExc_IndexError, str)
+#define ZDIVERR(str) EXCERR(PyExc_ZeroDivisionError, str)
+#define SYSERR(str) EXCERR(PyExc_SystemError, str)
+#define NIERR(str) EXCERR(PyExc_NotImplementedError, str)
+#define MAPERR(idx) do {                                               \
+  PyErr_SetObject(PyExc_KeyError, idx);                                        \
+  goto end;                                                            \
+} while (0)
+#define OSERR(name) do {                                               \
+  PyErr_SetFromErrnoWithFilename(PyExc_OSError, name);                 \
+  goto end;                                                            \
+} while (0)
+
+/* Saving and restoring exceptions. */
+struct excinfo { PyObject *ty, *val, *tb; };
+#define EXCINFO_INIT { 0, 0, 0 }
+#define INIT_EXCINFO(exc) do {                                         \
+  struct excinfo *_exc = (exc); _exc->ty = _exc->val = _exc->tb = 0;   \
+} while (0)
+#define RELEASE_EXCINFO(exc) do {                                      \
+  struct excinfo *_exc = (exc);                                                \
+  Py_XDECREF(_exc->ty);         _exc->ty  = 0;                                 \
+  Py_XDECREF(_exc->val); _exc->val = 0;                                        \
+  Py_XDECREF(_exc->tb);         _exc->tb  = 0;                                 \
+} while (0)
+#define STASH_EXCINFO(exc) do {                                                \
+  struct excinfo *_exc = (exc);                                                \
+  PyErr_Fetch(&_exc->ty, &_exc->val, &_exc->tb);                       \
+  PyErr_NormalizeException(&_exc->ty, &_exc->val, &_exc->tb);          \
+} while (0)
+#define RESTORE_EXCINFO(exc) do {                                      \
+  struct excinfo *_exc = (exc);                                                \
+  PyErr_Restore(_exc->ty, _exc->val, _exc->tb);                                \
+  _exc->ty = _exc->val = _exc->tb = 0;                                 \
+} while (0)
+extern void report_lost_exception(struct excinfo *, const char *, ...);
+extern void report_lost_exception_v(struct excinfo *, const char *, va_list);
+extern void stash_exception(struct excinfo *, const char *, ...);
+extern void restore_exception(struct excinfo *, const char *, ...);
+
+/*----- Conversions -------------------------------------------------------*/
+
+/* Define an input conversion (`O&') function: check that the object has
+ * Python type TY, and extract a C pointer to CTY by calling EXT on the
+ * object (which may well be a macro).
+ */
+#define CONVFUNC(ty, cty, ext)                                         \
+  int conv##ty(PyObject *o, void *p)                                   \
+  {                                                                    \
+    if (!PyObject_TypeCheck(o, ty##_pytype))                           \
+      TYERR("wanted a " #ty);                                          \
+    *(cty *)p = ext(o);                                                        \
+    return (1);                                                                \
+  end:                                                                 \
+    return (0);                                                                \
+  }
+
+/* Input conversion functions for standard kinds of objects, with overflow
+ * checking where applicable.
+ */
+struct bin { const void *p; Py_ssize_t sz; };
+extern int convulong(PyObject *, void *); /* unsigned long */
+extern int convuint(PyObject *, void *); /* unsigned int */
+extern int convszt(PyObject *, void *);        /* size_t */
+extern int convbool(PyObject *, void *); /* bool */
+extern int convbin(PyObject *, void *); /* read buffer holding bytes */
+
+/* Output conversions. */
+extern PyObject *getbool(int);         /* bool */
+extern PyObject *getulong(unsigned long); /* any kind of unsigned integer */
+
+/*----- Miscellaneous utilities -------------------------------------------*/
+
+#define FREEOBJ(obj) (Py_TYPE(obj)->tp_free((PyObject *)(obj)))
+  /* Actually free OBJ, e.g., in a deallocation function. */
+
+extern PyObject *abstract_pynew(PyTypeObject *, PyObject *, PyObject *);
+  /* A `tp_new' function which refuses to make the object. */
+
+extern PyObject *enrich_compare(int /*op*/, int /*cmp*/);
+  /* Use a traditional compare-against-zero comparison result CMP to answer a
+   * modern Python `tp_richcompare' operation OP.
+   */
+
+#ifndef CONVERT_CAREFULLY
+#  define CONVERT_CAREFULLY(newty, expty, obj)                         \
+     (!sizeof(*(expty *)0 = (obj)) + (/*unconst*/ newty)(obj))
+  /* Convert OBJ to the type NEWTY, having previously checked that it is
+   * convertible to the expected type EXPTY.
+   *
+   * Because of the way we set up types, we can make many kinds of tables be
+   * `const' which can't usually be so (because Python will want to fiddle
+   * with their reference counts); and, besides, Python's internals are
+   * generally quite bad at being `const'-correct about tables.  One frequent
+   * application of this macro, then, is in removing `const' from a type
+   * without sacrificing all type safety.  The other common use is in
+   * checking that method function types match up with the signatures
+   * expected in their method definitions.
+   */
+#endif
+
+#define KWLIST CONVERT_CAREFULLY(char **, const char *const *, kwlist)
+  /* Strip `const' qualifiers from the keyword list `kwlist'.  Useful when
+   * calling `PyArg_ParseTupleAndKeywords', which isn't `const'-correct.
+   */
+
+/*----- Type definitions --------------------------------------------------*
+ *
+ * Pyke types are defined in a rather unusual way.
+ *
+ * The main code defines a `type skeleton' of type `PyTypeObject',
+ * conventionally named `TY_pytype_skel'.  Unlike typical Python type
+ * definitions in extensions, this can (and should) be read-only.  Also,
+ * there's no point in setting the `tp_base' pointer here, because the actual
+ * runtime base type object won't, in general, be known at compile time.
+ * Instead, the type skeletons are converted into Python `heap types' by the
+ * `INITTYPE' macro.  The main difference is that Python code can add
+ * attributes to heap types, and we make extensive use of this ability.
+ */
+
+extern void *newtype(PyTypeObject */*meta*/,
+                    const PyTypeObject */*skel*/, const char */*name*/);
+  /* Make and return a new Python type object, of type META (typically
+   * `PyType_Type', but may be a subclass), filled in from the skeleton SKEL
+   * (null to inherit everything), and named NAME.  The caller can mess with
+   * the type object further at this time: call `typeready' when it's set up
+   * properly.
+   */
+
+extern void typeready(PyTypeObject *);
+  /* The type object is now ready to be used. */
+
+extern PyTypeObject *inittype(const PyTypeObject */*skel*/,
+                             PyTypeObject */*base*/,
+                             PyTypeObject */*meta*/);
+  /* All-in-one function to construct a working type from a type skeleton
+   * SKEL, with known base type BASE (null for `object') and metaclass.
+   */
+
+/* Alias for built-in types, to fit in with Pyke naming conventions. */
+#define root_pytype 0
+#define type_pytype &PyType_Type
+
+#define INITTYPE_META(ty, base, meta) do {                             \
+  ty##_pytype = inittype(&ty##_pytype_skel, base##_pytype, meta##_pytype); \
+} while (0)
+#define INITTYPE(ty, base) INITTYPE_META(ty, base, type)
+  /* Macros to initialize a type from its skeleton. */
+
+/* Macros for filling in `PyMethodDef' tables, ensuring that functions have
+ * the expected signatures.
+ */
+#define STD_METHOD(decor, func, flags, doc)                            \
+  { #func, decor(func), METH_VARARGS | flags, doc },
+#define KEYWORD_METHOD(decor, func, flags, doc)                                \
+  { #func,                                                             \
+    CONVERT_CAREFULLY(PyCFunction, PyCFunctionWithKeywords, decor(func)), \
+    METH_VARARGS | METH_KEYWORDS | flags,                              \
+    doc },
+#define NOARG_METHOD(decor, func, flags, doc)                          \
+  { #func,                                                             \
+    CONVERT_CAREFULLY(PyCFunction, PyNoArgsFunction, decor(func)),     \
+    METH_NOARGS | flags,                                               \
+    doc },
+
+/* Convenience wrappers for filling in `PyMethodDef' tables, following
+ * Pyke naming convention.  Define `METHNAME' locally as
+ *
+ *     #define METHNAME(name) foometh_##func
+ *
+ * around the method table.
+ */
+#define METH(func, doc) STD_METHOD(METHNAME, func, 0, doc)
+#define KWMETH(func, doc) KEYWORD_METHOD(METHNAME, func, 0, doc)
+#define NAMETH(func, doc) NOARG_METHOD(METHNAME, func, 0, doc)
+#define CMTH(func, doc) STD_METHOD(METHNAME, func, METH_CLASS, doc)
+#define KWCMTH(func, doc) KEYWORD_METHOD(METHNAME, func, METH_CLASS, doc)
+#define NACMTH(func, doc) NOARG_METHOD(METHNAME, func, METH_CLASS, doc)
+#define SMTH(func, doc) STD_METHOD(METHNAME, func, METH_STATIC, doc)
+#define KWSMTH(func, doc) KEYWORD_METHOD(METHNAME, func, METH_STATIC, doc)
+#define NASMTH(func, doc) NOARG_METHOD(METHNAME, func, METH_STATIC, doc)
+
+/* Convenience wrappers for filling in `PyGetSetDef' tables, following Pyke
+ * naming convention.  Define `GETSETNAME' locally as
+ *
+ *     #define GETSETNAME(op, name) foo##op##_##func
+ *
+ * around the get/set table.
+ */
+#define GET(func, doc)                                                 \
+  { #func, GETSETNAME(get, func), 0, doc },
+#define GETSET(func, doc)                                              \
+  { #func, GETSETNAME(get, func), GETSETNAME(set, func), doc },
+
+/* Convenience wrappers for filling in `PyMemberDef' tables.  Define
+ * `MEMBERSTRUCT' locally as
+ *
+ *     #define MEMBERSTRUCT foo_pyobj
+ *
+ * around the member table.
+ */
+#define MEMRNM(name, ty, mem, f, doc)                                  \
+  { #name, ty, offsetof(MEMBERSTRUCT, mem), f, doc },
+#define MEMBER(name, ty, f, doc) MEMRNM(name, ty, name, f, doc)
+
+/* Wrappers for filling in pointers in a `PyTypeObject' structure, (a)
+ * following Pyke naming convention, and (b) stripping `const' from the types
+ * without losing type safety.
+ */
+#define UNCONST_TYPE_SLOT(type, suffix, op, ty)                                \
+  CONVERT_CAREFULLY(type *, const type *, op ty##_py##suffix)
+#define PYGETSET(ty) UNCONST_TYPE_SLOT(PyGetSetDef, getset, NOTHING, ty)
+#define PYMETHODS(ty) UNCONST_TYPE_SLOT(PyMethodDef, methods, NOTHING, ty)
+#define PYMEMBERS(ty) UNCONST_TYPE_SLOT(PyMemberDef, members, NOTHING, ty)
+#define PYNUMBER(ty) UNCONST_TYPE_SLOT(PyNumberMethods, number, &, ty)
+#define PYSEQUENCE(ty) UNCONST_TYPE_SLOT(PySequenceMethods, sequence, &, ty)
+#define PYMAPPING(ty) UNCONST_TYPE_SLOT(PyMappingMethods, mapping, &, ty)
+#define PYBUFFER(ty) UNCONST_TYPE_SLOT(PyBufferProcs, buffer, &, ty)
+
+/*----- Populating modules ------------------------------------------------*/
+
+extern PyObject *modname;
+  /* The overall module name.  Set this with `TEXT_FROMSTR'. */
+
+extern PyObject *home_module;
+  /* The overall module object. */
+
+extern PyObject *mkexc(PyObject */*mod*/, PyObject */*base*/,
+                      const char */*name*/, const PyMethodDef */*methods*/);
+  /* Make and return an exception class called NAME, which will end up in
+   * module MOD (though it is not added at this time).  The new class is a
+   * subclass of BASE.  Attach the METHODS to it.
+   */
+
+#define INSERT(name, ob) do {                                          \
+  PyObject *_o = (PyObject *)(ob);                                     \
+  Py_INCREF(_o);                                                       \
+  PyModule_AddObject(mod, name, _o);                                   \
+} while (0)
+  /* Insert a Python object OB into the module `mod' under the given NAME. */
+
+/* Numeric constants. */
+struct nameval { const char *name; unsigned f; unsigned long value; };
+#define CF_SIGNED 1u
+extern void setconstants(PyObject *, const struct nameval *);
+#define CONST(x) { #x, (x) >= 0 ? 0 : CF_SIGNED, x }
+#define CONSTFLAG(f, x) { #x, f, x }
+
+#define INSEXC(name, var, base, meth)                                  \
+  INSERT(name, var = mkexc(mod, base, name, meth))
+  /* Insert an exception class into the module `mod'; other arguments are as
+   * for `mkexc'.
+   */
+
+/*----- Submodules --------------------------------------------------------*
+ *
+ * It's useful to split the Python module up into multiple source files, and
+ * have each one contribute its definitions into the main module.
+ *
+ * Define a list-macro `MODULES' in the master header file naming the
+ * submodules to be processed, and run
+ *
+ *     MODULES(DECLARE_MODINIT)
+ *
+ * to declare the interface functions.
+ *
+ * Each submodule FOO defines two functions: `FOO_pyinit' initializes types
+ * (see `INITTYPE' above) and accumulates methods (`addmethods' below), while
+ * `FOO_pyinsert' populates the module with additional definitions
+ * (especially types, though also constants).
+ *
+ * The top-level module initialization should call `INIT_MODULES' before
+ * creating the Python module, and `INSERT_MODULES' afterwards to make
+ * everything work.
+ */
+
+extern void addmethods(const PyMethodDef *);
+extern PyMethodDef *donemethods(void);
+  /* Accumulate method-table fragments, and return the combined table of all
+   * of the fragments.
+   */
+
+#define DECLARE_MODINIT(m)                                             \
+  extern void m##_pyinit(void);                                                \
+  extern void m##_pyinsert(PyObject *);
+  /* Declare submodule interface functions. */
+
+#define DOMODINIT(m) m##_pyinit();
+#define DOMODINSERT(m) m##_pyinsert(mod);
+#define INIT_MODULES do { MODULES(DOMODINIT) } while (0)
+#define INSERT_MODULES do { MODULES(DOMODINSERT) } while (0)
+  /* Top-level dispatch to the various submodules. */
+
+/*----- 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)
+#define GMAP_KWMETH(func, doc) KEYWORD_METHOD(GMAP_METMNAME, func, 0, doc)
+#define GMAP_NAMETH(func, doc) NOARG_METHOD(GMAP_METMNAME, func, 0, doc)
+#define GMAP_METHDECL(func, doc)                                       \
+  extern PyObject *gmapmeth_##func(PyObject *, PyObject *);
+#define GMAP_KWMETHDECL(func, doc)                                     \
+  extern PyObject *gmapmeth_##func(PyObject *, PyObject *, PyObject *);
+#define GMAP_NAMETHDECL(func, doc)                                     \
+  extern PyObject *gmapmeth_##func(PyObject *);
+
+#ifdef PY3
+#  define GMAP_DOROMETHODS(METH, KWMETH, NAMETH)                       \
+    NAMETH(keys,       "D.keys() -> LIST")                             \
+    NAMETH(values,     "D.values() -> LIST")                           \
+    NAMETH(items,      "D.items() -> LIST")                            \
+    KWMETH(get,                "D.get(KEY, [default = None]) -> VALUE")
+#else
+#  define GMAP_DOROMETHODS(METH, KWMETH, NAMETH)                       \
+    METH  (has_key,    "D.has_key(KEY) -> BOOL")                       \
+    NAMETH(keys,       "D.keys() -> LIST")                             \
+    NAMETH(values,     "D.values() -> LIST")                           \
+    NAMETH(items,      "D.items() -> LIST")                            \
+    NAMETH(iterkeys,   "D.iterkeys() -> ITER")                         \
+    NAMETH(itervalues, "D.itervalues() -> ITER")                       \
+    NAMETH(iteritems,  "D.iteritems() -> ITER")                        \
+    KWMETH(get,                "D.get(KEY, [default = None]) -> VALUE")
+#endif
+
+#define GMAP_DOMETHODS(METH, KWMETH, NAMETH)                           \
+  GMAP_DOROMETHODS(METH, KWMETH, NAMETH)                               \
+  NAMETH(clear,                "D.clear()")                                    \
+  KWMETH(setdefault,   "D.setdefault(K, [default = None]) -> VALUE")   \
+  KWMETH(pop,          "D.pop(KEY, [default = <error>]) -> VALUE")     \
+  NAMETH(popitem,      "D.popitem() -> (KEY, VALUE)")                  \
+  KWMETH(update,       "D.update(MAP)")
+
+GMAP_DOMETHODS(GMAP_METHDECL, GMAP_KWMETHDECL, GMAP_NAMETHDECL)
+#define GMAP_ROMETHODS GMAP_DOROMETHODS(GMAP_METH, GMAP_KWMETH, GMAP_NAMETH)
+#define GMAP_METHODS GMAP_DOMETHODS(GMAP_METH, GMAP_KWMETH, GMAP_NAMETH)
+
+/* 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 -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif