From: Mark Wooding Date: Sat, 11 Apr 2020 14:30:26 +0000 (+0100) Subject: @@@ mdwopt buggy wip X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/mLib-python/commitdiff_plain/fcc8f4ca09d374f3130f5ced3891f455a7eee3cb @@@ mdwopt buggy wip --- diff --git a/mLib-python.h b/mLib-python.h index 33000d4..6005b24 100644 --- a/mLib-python.h +++ b/mLib-python.h @@ -41,6 +41,8 @@ PUBLIC_SYMBOLS; #include #include #include +#include +#include #include #include #include diff --git a/pyke/pyke.c b/pyke/pyke.c index 330f15c..2b65c9f 100644 --- a/pyke/pyke.c +++ b/pyke/pyke.c @@ -44,6 +44,41 @@ PyObject *getulong(unsigned long w) PyObject *getbool(int b) { if (b) RETURN_TRUE; else RETURN_FALSE; } +int convlong(PyObject *o, void *pp) +{ + long *p = pp; + PyObject *t; + + if (!o) VALERR("can't delete"); +#ifdef PY2 + if (PyInt_Check(o)) + *p = PyInt_AS_LONG(o); + else +#endif + { + if ((t = PyNumber_Index(o)) == 0) goto end; + *p = PyLong_AsLong(t); + Py_DECREF(t); + if (PyErr_Occurred()) goto end; + } + return (1); +end: + return (0); +} + +int convint(PyObject *o, void *pp) +{ + long l; + int *p = pp; + + if (!convlong(o, &l)) goto end; + if (INT_MIN > l || l > INT_MAX) VALERR("out of range"); + *p = l; + return (1); +end: + return (0); +} + int convulong(PyObject *o, void *pp) { unsigned long *p = pp; @@ -58,7 +93,7 @@ int convulong(PyObject *o, void *pp) } else #endif { - if ((t = PyNumber_Long(o)) == 0) goto end; + if ((t = PyNumber_Index(o)) == 0) goto end; *p = PyLong_AsUnsignedLong(t); Py_DECREF(t); if (PyErr_Occurred()) goto end; diff --git a/pyke/pyke.h b/pyke/pyke.h index ada47fc..477e9d0 100644 --- a/pyke/pyke.h +++ b/pyke/pyke.h @@ -356,6 +356,8 @@ extern void restore_exception(struct excinfo *, const char *, ...); * checking where applicable. */ struct bin { const void *p; Py_ssize_t sz; }; +extern int convlong(PyObject *, void *); /* long */ +extern int convint(PyObject *, void *); /* int */ extern int convulong(PyObject *, void *); /* unsigned long */ extern int convuint(PyObject *, void *); /* unsigned int */ extern int convszt(PyObject *, void *); /* size_t */ diff --git a/t/t-misc.py b/t/t-misc.py index 66ea0e1..fb84091 100644 --- a/t/t-misc.py +++ b/t/t-misc.py @@ -34,6 +34,7 @@ import unittest as U class Tests (U.TestCase): def test_path(me): + print(M.__file__) me.assertTrue(M.__file__.startswith("build")) ###----- That's all, folks -------------------------------------------------- diff --git a/t/t-ui.py b/t/t-ui.py index 60e49b6..4d3a6e7 100644 --- a/t/t-ui.py +++ b/t/t-ui.py @@ -65,6 +65,10 @@ class TestUI (U.TestCase): else: raise AssertionError("die didn't exit") + def test_mdwopt(me): + mo = M.MdwOpt() + print(list(mo)) + ###----- That's all, folks -------------------------------------------------- if __name__ == "__main__": U.main() diff --git a/ui.c b/ui.c index 4316d93..0ac0fa1 100644 --- a/ui.c +++ b/ui.c @@ -96,43 +96,76 @@ struct optextra { typedef struct { PyObject_HEAD - mdwopt_data opt; - char **argv, *stringdata; - char *shortopt; + 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))->shortopt) +#define MDWOPT_SHORT(o) (((mdwopt_pyobj *)(o))->stringdata) #define MDWOPT_LONG(o) (((mdwopt_pyobj *)(o))->longopt) -#define IXTAG(ix) (((ix)&0xff) | (((ix)&~0xff) << 1)) -#define TAGIX(tag) (((tag)&0xff) | (((tag)&~0x1ff) >> 1)) +#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) { - DA_DECL(obj_v, PyObject *); - DA_DECL(opt_v, struct option); - DA_DECL(size_v, size_t); - PyObject *argvobj = 0, *longoptobj = 0; PyObject *it = 0, *t = 0, *u = 0; char *p; size_t sz; + size_t i; Py_ssize_t n; mdwopt_pyobj *me = 0; - char *shortopt; + char *shortopt = ""; unsigned flags = 0; - int f; - opt_v opts = DA_INIT; - obj_v tags = DA_INIT; - size_v off = DA_INIT; - dstr strbuf = DSTR_INIT; - size_t narg; + 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, @@ -144,68 +177,285 @@ static PyObject *mdwopt_pynew(PyTypeObject *cls, PyObject *arg, PyObject *kw) 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"); - TEXT_PTRLEN(t, p, sz); - DA_PUSH(&off, strbuf.len); - DPUTM(&strbuf, p, sz + 1); + 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; - narg = DA_LEN(&off); + build.narg = DA_LEN(&build.off); Py_DECREF(it); it = 0; - it = PyObject_GetIter(longoptobj); if (!it) goto end; - for (;;) { - 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 " - "(OPT, TAG, [FLAGS = 0, [ATTR = None]])"); - - u = PySequence_GetItem(t, 0); if (!u) goto end; - if (!TEXT_CHECK(u)) TYERR("option name should be a string"); - TEXT_PTRLEN(u, p, sz); - DA_PUSH(&off, strbuf.len); - DPUTM(&strbuf, p, sz + 1); - Py_DECREF(u); u = 0; - - u = PySequence_GetItem(t, 1); if (!u) goto end; - DA_PUSH(&tags, u); u = 0; - - if (n < 3) - f = 0; - else { + /* 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 (getint(u, &f)) 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: - return (PyObject *)me; + /* 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; - xfree(m->stringdata); - xfree(m->longopt); + 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; + char *p; + int rc = -1; + + if (!v) NIERR("__del__"); + p = TEXT_STR(v); if (!p) goto end; + m->opt.prog = 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 } }; @@ -241,14 +491,68 @@ static const PyTypeObject mdwopt_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ - "MdwOpt([argv = SEQ], [shortopt = STR], [longopt = SEQ], [flags = 0])", + "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@ */ - 0, /* @tp_iter@ */ - 0, /* @tp_iternext@ */ + PyObject_SelfIter, /* @tp_iter@ */ + mdwopt_pynext, /* @tp_iternext@ */ PYMETHODS(mdwopt), /* @tp_methods@ */ PYMEMBERS(mdwopt), /* @tp_members@ */ PYGETSET(mdwopt), /* @tp_getset@ */