@@@ constification
[mLib-python] / ui.c
diff --git a/ui.c b/ui.c
index 82a1769..2912723 100644 (file)
--- a/ui.c
+++ b/ui.c
@@ -87,6 +87,487 @@ end:
   return (0);
 }
 
+/*----- Option parser -----------------------------------------------------*/
+
+struct optextra {
+  PyObject *tag;
+  PyObject *attr;
+};
+
+typedef struct {
+  PyObject_HEAD
+  char *stringdata;
+  struct option *longopt;
+  struct optextra *extra;
+  char **argv;
+  size_t nlong, narg;
+  int flags;
+  mdwopt_data opt;
+  PyObject *prog;
+  PyObject *state;
+} mdwopt_pyobj;
+static PyTypeObject *mdwopt_pytype;
+#define MDWOPT_PYCHECK(o) PyObject_TypeCheck((o), mdwopt_pytype)
+#define MDWOPT_OPT(o) (&((mdwopt_pyobj *)(o))->opt)
+#define MDWOPT_ARGV(o) (((mdwopt_pyobj *)(o))->argv)
+#define MDWOPT_SHORT(o) (((mdwopt_pyobj *)(o))->stringdata)
+#define MDWOPT_LONG(o) (((mdwopt_pyobj *)(o))->longopt)
+
+#define IXTAG(ix) (((ix)&0xff) | (((ix)&~0xff) << 2) | 0x200)
+#define TAGIX(tag) (((tag)&0xff) | (((tag)&~0x3ff) >> 2))
+
+DA_DECL(obj_v, PyObject *);
+DA_DECL(opt_v, struct option);
+DA_DECL(extra_v, struct optextra);
+DA_DECL(size_v, size_t);
+
+/* Ordering of strings within `stringdata'.
+ *
+ *   * `shortopt' (at the start so we don't need an extra pointer)
+ *   * `argv' (individual strings addressed by `argv')
+ *   * `longopt' names (in order, addressed by `longopt[i].name')
+ */
+
+struct optbuild {
+  dstr strbuf;                         /* string buffer */
+  size_t narg;                         /* number of arguments */
+  size_v off;                          /* offsets of string starts;
+                                        * doesn't include `shortopt' */
+  opt_v opt;                           /* options */
+  extra_v extra;                       /* option extra data */
+};
+#define OPTBUILD_INIT { DSTR_INIT, 0, DA_INIT, DA_INIT }
+
+static PyObject *mdwopt_pynew(PyTypeObject *cls, PyObject *arg, PyObject *kw)
+{
+  PyObject *argvobj = 0, *longoptobj = 0;
+  PyObject *it = 0, *t = 0, *u = 0;
+  const char *p; size_t sz;
+  size_t i;
+  Py_ssize_t n;
+  mdwopt_pyobj *me = 0;
+  const char *shortopt = "";
+  unsigned flags = 0;
+  struct optbuild build = OPTBUILD_INIT;
+  struct option *opt;
+  struct optextra *extra;
+  static const char *const kwlist[] =
+    { "argv", "shortopt", "longopt", "flags", 0 };
+
+#define EXTEND(var, vec) do {                                          \
+  DA_ENSURE(&build.vec, 1);                                            \
+  var = &DA(&build.vec)[DA_LEN(&build.vec)];                           \
+  DA_EXTEND(&build.vec, 1);                                            \
+} while (0)
+
+#define COPYTAB(slot, base, len) do {                                  \
+  me->slot = xmalloc((len)*sizeof(*me->slot));                         \
+  memcpy(me->slot, base, (len)*sizeof(*me->slot));                     \
+} while (0)
+
+  /* Collect the arguments. */
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|OsOO&:new", KWLIST,
+                                  &argvobj,
+                                  &shortopt,
+                                  &longoptobj,
+                                  convuint, &flags))
+    goto end;
+  if (!argvobj) {
+    argvobj = PySys_GetObject("argv");
+    if (!argvobj) SYSERR("sys.argv missing");
+  }
+
+  /* Commit the short-options string to the buffer.
+   *
+   * Putting this first means that we don't need a separate pointer.  All of
+   * the other things are arrays, so avoiding maintaining a separate index or
+   * pointer for the first element will just make things unnecessarily
+   * complicated.
+   */
+  DPUTM(&build.strbuf, shortopt, strlen(shortopt) + 1);
+
+  /* Collect the arguments to be parsed. */
+  it = PyObject_GetIter(argvobj); if (!it) goto end;
+  for (;;) {
+    t = PyIter_Next(it); if (!t) break;
+    if (!TEXT_CHECK(t)) TYERR("argv should be a sequence of strings");
+    DA_PUSH(&build.off, build.strbuf.len);
+    TEXT_PTRLEN(t, p, sz); DPUTM(&build.strbuf, p, sz + 1);
+    Py_DECREF(t); t = 0;
+  }
+  if (PyErr_Occurred()) goto end;
+  build.narg = DA_LEN(&build.off);
+  Py_DECREF(it); it = 0;
+
+  /* Collect the long-option specifications. */
+  if (longoptobj) {
+    it = PyObject_GetIter(longoptobj); if (!it) goto end;
+    for (;;) {
+
+      /* Get the next item and check that it's basically sensible. */
+      t = PyIter_Next(it); if (!t) break;
+      n = PySequence_Size(t); if (n < 0) goto end;
+      if (n < 2 || n > 4)
+       VALERR("long-options entry should be "
+              "(NAME, VAL, [FLAG = 0, [ATTR = None]])");
+
+      /* Allocate new entries in the options and extra-data tables. */
+      EXTEND(opt, opt);
+      EXTEND(extra, extra);
+      opt->flag = 0;
+      extra->tag = 0; extra->attr = 0;
+
+      /* Get the option name and contribute it to the string buffer. */
+      u = PySequence_GetItem(t, 0); if (!u) goto end;
+      if (!TEXT_CHECK(u)) TYERR("option name should be a string");
+      DA_PUSH(&build.off, build.strbuf.len);
+      TEXT_PTRLEN(u, p, sz); DPUTM(&build.strbuf, p, sz + 1);
+      Py_DECREF(u); u = 0;
+
+      /* Get the option tag and store it in the extra data.
+       * `PySequence_GetItem' bumps the refcount for us.
+       */
+      extra->tag = PySequence_GetItem(t, 1); if (!extra->tag) goto end;
+
+      /* Get the flags for this option. */
+      if (n < 3)
+       opt->has_arg = 0;
+      else {
+       u = PySequence_GetItem(t, 0); if (!u) goto end;
+       if (convint(u, &opt->has_arg)) goto end;
+       Py_DECREF(u); u = 0;
+      }
+
+      /* Finally, get the attribute name. */
+      if (n < 4)
+       { extra->attr = Py_None; Py_INCREF(Py_None); }
+      else {
+       extra->attr = PySequence_GetItem(t, 3);
+       if (!extra->attr) goto end;
+      }
+
+      /* Done.  Let's go round again. */
+      Py_DECREF(t); t = 0;
+    }
+    if (PyErr_Occurred()) goto end;
+    Py_DECREF(it); it = 0;
+  }
+
+  /* Allocate the state value. */
+  t = PyBaseObject_Type.tp_alloc(&PyBaseObject_Type, 0);
+  if (!t) goto end;
+
+  /* Allocate our return value. */
+  me = (mdwopt_pyobj *)cls->tp_alloc(cls, 0);
+  me->state = t; t = 0;
+  me->flags = flags;
+  me->narg = build.narg;
+  me->nlong = DA_LEN(&build.opt);
+
+  /* Add a final terminating entry to the long-options table. */
+  EXTEND(opt, opt); opt->name = 0;
+
+  /* Copy the main tables. */
+  COPYTAB(stringdata, build.strbuf.buf, build.strbuf.len);
+  COPYTAB(longopt, DA(&build.opt), DA_LEN(&build.opt));
+  COPYTAB(extra, DA(&build.extra), DA_LEN(&build.extra));
+
+  /* Fill in the `argv' vector. */
+  me->argv = xmalloc(build.narg*sizeof(*me->argv));
+  for (i = 0; i < build.narg; i++)
+    me->argv[i] = me->stringdata + DA(&build.off)[i + 1];
+  me->argv[build.narg] = 0;
+
+  /* Fix up the string pointers and values in the long-options table. */
+  for (i = 0; i < me->nlong; i++) {
+    me->longopt[i].name =
+      me->stringdata + DA(&build.off)[i + build.narg + 1];
+    me->longopt[i].val = IXTAG(i);
+  }
+
+  /* Initialize the parser state.  Set up everything because Python might
+   * ask awkward questions before we're ready.
+   */
+  me->opt.arg = 0;
+  me->opt.opt = -1;
+  me->opt.ind = 0;
+  me->opt.err = 1;
+  me->opt.prog = 0;
+
+  /* And other random things. */
+  me->prog = 0;
+
+end:
+  /* Clean up and go home. */
+  Py_XDECREF(it); Py_XDECREF(t); Py_XDECREF(u);
+  DDESTROY(&build.strbuf);
+  if (!me) for (i = 0; i < DA_LEN(&build.extra); i++) {
+    extra = &DA(&build.extra)[i];
+    Py_XDECREF(extra->tag); Py_XDECREF(extra->attr);
+  }
+  DA_DESTROY(&build.off);
+  DA_DESTROY(&build.opt);
+  DA_DESTROY(&build.extra);
+  return ((PyObject *)me);
+
+#undef EXTEND
+#undef COPYTAB
+}
+
+static void mdwopt_pydealloc(PyObject *me)
+{
+  mdwopt_pyobj *m = (mdwopt_pyobj *)me;
+  size_t i;
+
+  for (i = 0; i < m->nlong; i++)
+    { Py_DECREF(m->extra[i].tag); Py_DECREF(m->extra[i].attr); }
+  xfree(m->stringdata); xfree(m->longopt); xfree(m->extra);
+  FREEOBJ(me);
+}
+
+static PyObject *mdwopt_pynext(PyObject *me)
+{
+  mdwopt_pyobj *m = (mdwopt_pyobj *)me;
+  PyObject *val = 0, *arg = 0, *t = 0, *u = 0, *v = 0;
+  int f, ix, i;
+  unsigned char ch;
+  PyObject *rc = 0;
+
+again:
+  i = mdwopt(m->narg, m->argv, m->stringdata, m->longopt, &ix,
+                &m->opt, m->flags);
+
+  if (i == -1) goto end;
+
+  f = i&OPTF_NEGATED;
+
+  if (m->opt.arg) arg = TEXT_FROMSTR(m->opt.arg);
+  else { arg = Py_None; Py_INCREF(Py_None); }
+
+  if (ix < 0) {
+    ch = i&0xff;
+    val = TEXT_FROMSTRLEN((char *)&ch, 1); if (!val) goto end;
+  } else {
+    if (m->extra[ix].attr == Py_None)
+      { val = m->extra[ix].tag; Py_INCREF(val); }
+    else if (m->longopt[ix].has_arg&OPTF_SWITCH) {
+      t = PyObject_GetAttr(m->state, m->extra[ix].attr);
+      if (!t && PyErr_ExceptionMatches(PyExc_AttributeError)) {
+       PyErr_Clear();
+       t = PyInt_FromLong(0); if (!t) goto end;
+      }
+      if (!f)
+       { v = PyNumber_Or(t, m->extra[ix].tag); if (!v) goto end; }
+      else {
+       u = PyNumber_Invert(m->extra[ix].tag); if (!u) goto end;
+       v = PyNumber_And(t, u); if (!v) goto end;
+      }
+      if (PyObject_SetAttr(m->state, m->extra[ix].attr, v)) goto end;
+      Py_DECREF(t); Py_XDECREF(u); Py_DECREF(v);
+    } else {
+      if (PyObject_SetAttr(m->state, m->extra[ix].attr,
+                          f ?
+                          Py_None :
+                          m->longopt[ix].has_arg&OPTF_ARG ?
+                          arg : m->extra[ix].tag))
+       goto end;
+    }
+    Py_DECREF(arg);
+    goto again;
+  }
+
+  rc = Py_BuildValue("(OOi)", val, arg, f);
+end:
+  Py_XDECREF(val); Py_XDECREF(arg);
+  Py_XDECREF(t); Py_XDECREF(u); Py_XDECREF(v);
+  return (rc);
+}
+
+static PyObject *moget_argv(PyObject *me, void *hunoz)
+{
+  mdwopt_pyobj *m = (mdwopt_pyobj *)me;
+  PyObject *rc = 0, *t = 0;
+  size_t i = 0;
+
+  rc = PyList_New(m->narg); if (!rc) goto fail;
+  for (i = 0; i < m->narg; i++) {
+    t = TEXT_FROMSTR(m->argv[i]); if (!t) goto fail;
+    PyList_SET_ITEM(rc, i, t); t = 0;
+  }
+  return (rc);
+
+fail:
+  Py_XDECREF(t);
+  Py_XDECREF(rc);
+  return (0);
+}
+
+static PyObject *moget_err(PyObject *me, void *hunoz)
+  { mdwopt_pyobj *m = (mdwopt_pyobj *)me; return (getbool(m->opt.err)); }
+static int moset_err(PyObject *me, PyObject *v, void *hunoz)
+{
+  mdwopt_pyobj *m = (mdwopt_pyobj *)me;
+  int rc = -1;
+
+  if (!v) NIERR("__del__");
+  if (convbool(v, &m->opt.err)) goto end;
+  rc = 0;
+end:
+  return (rc);
+}
+
+static PyObject *moget_prog(PyObject *me, void *hunoz)
+{
+  mdwopt_pyobj *m = (mdwopt_pyobj *)me;
+
+  if (!m->opt.prog) RETURN_NONE;
+  if (!m->prog)
+    { m->prog = TEXT_FROMSTR(m->opt.prog); if (!m->prog) return (0); }
+  RETURN_OBJ(m->prog);
+}
+static int moset_prog(PyObject *me, PyObject *v, void *hunoz)
+{
+  mdwopt_pyobj *m = (mdwopt_pyobj *)me;
+  const char *p;
+  int rc = -1;
+
+  if (!v) NIERR("__del__");
+  p = TEXT_STR(v); if (!p) goto end;
+  m->opt.prog = (/*unconst*/ char *)p;
+  Py_XDECREF(m->prog); m->prog = v; Py_INCREF(v);
+  rc = 0;
+end:
+  return (rc);
+}
+
+static const PyMemberDef mdwopt_pymembers[] = {
+#define MEMBERSTRUCT mdwopt_pyobj
+  MEMBER(state, T_OBJECT_EX, 0, "M.state = object on which flags are set")
+  MEMRNM(arg, T_STRING, opt.arg, READONLY,
+                       "M.arg -> argument of most recent option")
+  MEMRNM(ind, T_INT, opt.ind, READONLY,
+                       "M.ind -> index of first non-option argument")
+#undef MEMBERSTRUCT
+  { 0 }
+};
+
+static const PyGetSetDef mdwopt_pygetset[] = {
+#define GETSETNAME(op, name) mo##op##_##name
+  GET  (argv,          "M.argv -> vector of arguments (permuted)")
+  GETSET(err,          "M.err = report errors to `stderr'?")
+  GETSET(prog,         "M.prog = program name (to report in errors")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef mdwopt_pymethods[] = {
+#define METHNAME(name) mometh_##name
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject mdwopt_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "MdwOpt",                            /* @tp_name@ */
+  sizeof(mdwopt_pyobj),                        /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  mdwopt_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@ */
+  "MdwOpt([argv = SEQ], [shortopt = STR], [longopt = SEQ], [flags = 0])\n"
+  "\n"
+  "ARGV is the sequence of arguments to be parsed.  If omitted, it\n"
+  "defaults to `sys.argv'.\n"
+  "\n"
+  "SHORTOPT has the form `[+|-|!][:]OPT...', where OPT is `CHAR[+][:[:]]'.\n"
+  "The CHAR names the option character; a `+' indicates that the option\n"
+  "may be negated; a `:' indicates that the option takes an argument, and\n"
+  " `::' means the argument is optional.  Before the OPTs, the following\n"
+  "may appear:\n"
+  "\n"
+  "  * `+' -- force POSIX option order: end iteration at first non-option;\n"
+  "  * `-' -- treat non-options as arguments to option `None';\n"
+  "  * `!' -- force default reordering behaviour: extract all options;\n"
+  "  * `:' -- return `:' rather than `?' for missing argument.\n"
+  "\n"
+  "LONGOPT is a sequence of tuples (NAME, VAL, [FLAG = 0, [ATTR = None]]):\n"
+  "the NAME is the long-option string; FLAG is a mask of flags listed\n"
+  "below; ATTR is `None' or an attribute name; VAL is the value to return\n"
+  "or, if ATTR is not `None', to store in `state.ATTR'.  Flags are:\n"
+  "\n"
+  "  * `OPTF_ARGREQ' -- argument is mandatory (like `:');\n"
+  "  * `OPTF_ARGOPT' -- argument is optional (like `::');\n"
+  "  * `OPTF_SWITCH' -- set or clear VAL bits in ATTR;\n"
+  "  * `OPTF_NEGATE' -- option may be negated\n"
+  "\n"
+  "Flags to the function are:\n"
+  "  * `OPTF_NOLONGS' -- don't accept long options at all;\n"
+  "  * `OPTF_NOSHORTS' -- accept long options with single `-';\n"
+  "  * `OPTF_NUMBERS' -- accept numeric options (value `#');\n"
+  "  * `OPTF_NEGATION' -- allow options to be negated;\n"
+  "  * `OPTF_ENVVAR' -- read options from environment variable;\n"
+  "  * `OPTF_NOPROGNAME' -- don't assume program name is in `ARGV[0]'\n."
+  "\n"
+  "The object is iterable, and yields triples of the form (VAL, ARG,\n"
+  "FLAGS): VAL is the option letter (for short options) or VAL slot (for a\n"
+  "long option); ARG is the argument, or `None'; and FLAG is a mask of the\n"
+  "following flags:\n"
+  "\n"
+  "  * `OPTF_NEGATED' -- set if the option was negated.\n"
+  "\n"
+  "Special values of VAL are:\n"
+  "\n"
+  "  * '?' -- an error was encountered;\n"
+  "  * ':' -- a required argument was omitted (if `:' is in SHORTOPT);\n"
+  "  * '#' -- a numeric option was found (if `OPTF_NUMBERS' is in FLAGS).\n"
+  "\n"
+  "Useful attributes:\n"
+  "\n"
+  "  * `arg' (read-only) -- argument to most recent option, or `None';\n"
+  "  * `argv' (read-only) -- vector of arguments to parse (permuted);\n"
+  "  * `ind' (read-only) -- index of first non-option argument;\n"
+  "  * `err' (read-write) -- boolean: report errors to `stderr'?;\n"
+  "  * `prog' (read-write) -- program name (to report in errors);\n"
+  "  * `state' (read-write) -- object to accumulate attribute settings\n.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  PyObject_SelfIter,                   /* @tp_iter@ */
+  mdwopt_pynext,                       /* @tp_iternext@ */
+  PYMETHODS(mdwopt),                   /* @tp_methods@ */
+  PYMEMBERS(mdwopt),                   /* @tp_members@ */
+  PYGETSET(mdwopt),                    /* @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@ */
+  mdwopt_pynew,                                /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
 /*----- Main code ---------------------------------------------------------*/
 
 static const PyMethodDef methods[] = {
@@ -100,11 +581,13 @@ static const PyMethodDef methods[] = {
 
 void ui_pyinit(void)
 {
+  INITTYPE(mdwopt, root);
   addmethods(methods);
 }
 
 void ui_pyinsert(PyObject *mod)
 {
+  INSERT("MdwOpt", mdwopt_pytype);
 }
 
 int ui_pyready(void) { return (set_program_name()); }