/*
 * computil.c - completion utilities
 *
 * This file is part of zsh, the Z shell.
 *
 * Copyright (c) 1999 Sven Wischnowsky
 * All rights reserved.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and to distribute modified versions of this software for any
 * purpose, provided that the above copyright notice and the following
 * two paragraphs appear in all copies of this software.
 *
 * In no event shall Sven Wischnowsky or the Zsh Development Group be liable
 * to any party for direct, indirect, special, incidental, or consequential
 * damages arising out of the use of this software and its documentation,
 * even if Sven Wischnowsky and the Zsh Development Group have been advised of
 * the possibility of such damage.
 *
 * Sven Wischnowsky and the Zsh Development Group specifically disclaim any
 * warranties, including, but not limited to, the implied warranties of
 * merchantability and fitness for a particular purpose.  The software
 * provided hereunder is on an "as is" basis, and Sven Wischnowsky and the
 * Zsh Development Group have no obligation to provide maintenance,
 * support, updates, enhancements, or modifications.
 *
 */

#include "computil.mdh"
#include "computil.pro"


/* Help for `_describe'. */

typedef struct cdset *Cdset;
typedef struct cdstr *Cdstr;
typedef struct cdrun *Cdrun;

struct cdstate {
    int showd;			/* != 0 if descriptions should be shown */
    char *sep;			/* the separator string */
    int slen;			/* its metafied length */
    int swidth;			/* its screen width */
    int maxmlen;                /* maximum length to allow for the matches */
    Cdset sets;			/* the sets of matches */
    int pre;                    /* longest prefix length (before description) */
    int premaxw;		/* ... and its screen width */
    int suf;                    /* longest suffix (description) */
    int maxg;                   /* size of largest group */
    int maxglen;                /* columns for matches of largest group */
    int groups;                 /* number of groups */
    int descs;                  /* number of non-group matches with desc */
    int gprew;                   /* prefix screen width for group display */
    Cdrun runs;                 /* runs to report to shell code */
};

struct cdstr {
    Cdstr next;                 /* the next one in this set */
    char *str;                  /* the string to display */
    char *desc;                 /* the description or NULL */
    char *match;                /* the match to add */
    char *sortstr;		/* unmetafied string used to sort matches */
    int len;                    /* length of str or match */
    int width;			/* ... and its screen width */
    Cdstr other;                /* next string with the same description */
    int kind;                   /* 0: not in a group, 1: the first, 2: other */
    Cdset set;                  /* the set this string is in */
    Cdstr run;                  /* next in this run */
};

struct cdrun {
    Cdrun next;                 /* ... */
    int type;                   /* see CRT_* below */
    Cdstr strs;                 /* strings in this run */
    int count;                  /* number of strings in this run */
};

#define CRT_SIMPLE 0
#define CRT_DESC   1
#define CRT_SPEC   2
#define CRT_DUMMY  3
#define CRT_EXPL   4

struct cdset {
    Cdset next;			/* guess what */
    char **opts;		/* the compadd-options */
    Cdstr strs;                 /* the strings/matches */
    int count;                  /* number of matches in this set */
    int desc;                   /* number of matches with description */
};

static struct cdstate cd_state;
static int cd_parsed = 0;

static void
freecdsets(Cdset p)
{
    Cdset n;
    Cdstr s, sn;
    Cdrun r, rn;

    for (; p; p = n) {
	n = p->next;
	if (p->opts)
	    freearray(p->opts);
        for (s = p->strs; s; s = sn) {
            sn = s->next;
	    zfree(s->sortstr, strlen(s->str) + 1);
            zsfree(s->str);
            zsfree(s->desc);
            if (s->match != s->str)
                zsfree(s->match);
            zfree(s, sizeof(*s));
        }
        for (r = cd_state.runs; r; r = rn) {
            rn = r->next;
            zfree(r, sizeof(*r));
        }
	zfree(p, sizeof(*p));
    }
}

/* Find matches with same descriptions and group them. */

static void
cd_group(int maxg)
{
    Cdset set1, set2;
    Cdstr str1, str2, *strp;
    int num, width;

    cd_state.groups = cd_state.descs = cd_state.maxglen = 0;
    cd_state.maxg = 0;

    for (set1 = cd_state.sets; set1; set1 = set1->next)
        for (str1 = set1->strs; str1; str1 = str1->next) {
            str1->kind = 0;
            str1->other = NULL;
        }

    for (set1 = cd_state.sets; set1; set1 = set1->next) {
        for (str1 = set1->strs; str1; str1 = str1->next) {
            if (!str1->desc || str1->kind != 0)
                continue;

            num = 1;
            width = str1->width + cd_state.swidth;
            if (width > cd_state.maxglen)
                cd_state.maxglen = width;
            strp = &(str1->other);

            for (set2 = set1; set2; set2 = set2->next) {
                for (str2 = (set2 == set1 ? str1->next : set2->strs);
                     str2; str2 = str2->next)
                    if (str2->desc && !strcmp(str1->desc, str2->desc)) {
                        width += CM_SPACE + str2->width;
                        if (width > cd_state.maxmlen || num == maxg)
                            break;
                        if (width > cd_state.maxglen)
                            cd_state.maxglen = width;
                        str1->kind = 1;
                        str2->kind = 2;
                        num++;
                        *strp = str2;
                        strp = &(str2->other);
                    }
                if (str2)
                    break;
            }
            *strp = NULL;

            if (num > 1)
                cd_state.groups++;
            else
                cd_state.descs++;

            if (num > cd_state.maxg)
                cd_state.maxg = num;
        }
    }
}

/* Calculate longest prefix and suffix and count the strings with
 * descriptions. */

static void
cd_calc(void)
{
    Cdset set;
    Cdstr str;
    int l;

    cd_state.pre = cd_state.suf = 0;

    for (set = cd_state.sets; set; set = set->next) {
        set->count = set->desc = 0;
        for (str = set->strs; str; str = str->next) {
            set->count++;
            if ((l = strlen(str->str)) > cd_state.pre)
                cd_state.pre = l;
            if ((l = ZMB_nicewidth(str->str)) > cd_state.premaxw)
                cd_state.premaxw = l;
            if (str->desc) {
                set->desc++;
                if ((l = strlen(str->desc)) > cd_state.suf) /* ### strlen() assumes no \n */
                    cd_state.suf = l;
            }
        }
    }
}

/* Return 1 if cd_state specifies unsorted groups, 0 otherwise. */
static int
cd_groups_want_sorting(void)
{
    Cdset set;
    char *const *i;

    for (set = cd_state.sets; set; set = set->next)
        for (i = set->opts; *i; i++) {
            if (!strncmp(*i, "-V", 2))
                return 0;
            else if (!strncmp(*i, "-J", 2))
                return 1;
        }

    /* Sorted by default */
    return 1;
}

static int
cd_sort(const void *a, const void *b)
{
    return zstrcmp((*((Cdstr *) a))->sortstr, (*((Cdstr *) b))->sortstr, 0);
}

static int
cd_prep(void)
{
    Cdrun run, *runp;
    Cdset set;
    Cdstr str, *strp;

    runp = &(cd_state.runs);

    if (cd_state.groups) {
        int preplines = cd_state.groups + cd_state.descs;
        VARARR(Cdstr, grps, preplines);
        VARARR(int, wids, cd_state.maxg);
        Cdstr gs, gp, gn, *gpp;
        int i, j, d;
        Cdrun expl;
        Cdstr *strp2;

        memset(wids, 0, cd_state.maxg * sizeof(int));
        strp = grps;

        for (set = cd_state.sets; set; set = set->next)
            for (str = set->strs; str; str = str->next) {
                if (str->kind != 1) {
                    if (!str->kind && str->desc) {
                        if (str->width > wids[0])
                            wids[0] = str->width;
                        str->other = NULL;
                        *strp++ = str;
                    }
                    continue;
                }
                gs = str;
                gs->kind = 2;
                gp = str->other;
                gs->other = NULL;
                for (; gp; gp = gn) {
                    gn = gp->other;
                    gp->other = NULL;
                    for (gpp = &gs; *gpp && (*gpp)->width > gp->width;
                         gpp = &((*gpp)->other));
                    gp->other = *gpp;
                    *gpp = gp;
                }
                for (gp = gs, i = 0; gp; gp = gp->other, i++)
                    if (gp->width > wids[i])
                        wids[i] = gp->width;

                *strp++ = gs;
            }

        cd_state.gprew = 0;
        for (i = 0; i < cd_state.maxg; i++) {
            cd_state.gprew += wids[i] + CM_SPACE;
	}

        if (cd_state.gprew > cd_state.maxmlen && cd_state.maxglen > 1)
            return 1;

	for (i = 0; i < preplines; i++) {
	    Cdstr s = grps[i];
	    int dummy;

	    s->sortstr = ztrdup(s->str);
	    unmetafy(s->sortstr, &dummy);
	}

        if (cd_groups_want_sorting())
            qsort(grps, preplines, sizeof(Cdstr), cd_sort);

        for (i = preplines, strp = grps; i > 1; i--, strp++) {
            strp2 = strp + 1;
            if (!strcmp((*strp)->desc, (*strp2)->desc))
                continue;
            for (j = i - 2, strp2++; j > 0; j--, strp2++)
                if (!strcmp((*strp)->desc, (*strp2)->desc)) {
                    Cdstr tmp = *strp2;

                    memmove(strp + 2, strp + 1,
                            (strp2 - strp - 1) * sizeof(Cdstr));

                    *++strp = tmp;
                    i--;
                }
        }
        expl =  (Cdrun) zalloc(sizeof(*run));
        expl->type = CRT_EXPL;
        expl->strs = grps[0];
        expl->count = preplines;

        for (i = preplines, strp = grps, strp2 = NULL; i; i--, strp++) {
            str = *strp;
            *strp = str->other;
            if (strp2)
                *strp2 = str;
            strp2 = &(str->run);

            *runp = run = (Cdrun) zalloc(sizeof(*run));
            runp = &(run->next);
            run->type = CRT_SPEC;
            run->strs = str;
            run->count = 1;
        }
        *strp2 = NULL;

        for (i = cd_state.maxg - 1; i; i--) {
            for (d = 0, j = preplines, strp = grps; j; j--, strp++) {
                if ((str = *strp)) {
                    if (d) {
                        *runp = run = (Cdrun) zalloc(sizeof(*run));
                        runp = &(run->next);
                        run->type = CRT_DUMMY;
                        run->strs = expl->strs;
                        run->count = d;
                        d = 0;
                    }
                    *runp = run = (Cdrun) zalloc(sizeof(*run));
                    runp = &(run->next);
                    run->type = CRT_SPEC;
                    run->strs = str;
                    run->strs->run = NULL;
                    run->count = 1;

                    *strp = str->other;
                } else
                    d++;
            }
            if (d) {
                *runp = run = (Cdrun) zalloc(sizeof(*run));
                runp = &(run->next);
                run->type = CRT_DUMMY;
                run->strs = expl->strs;
                run->count = d;
            }
        }
        *runp = expl;
        runp = &(expl->next);

        for (set = cd_state.sets; set; set = set->next) {
            for (i = 0, gs = NULL, gpp = &gs, str = set->strs;
                 str; str = str->next) {
                if (str->kind || str->desc)
                    continue;

                i++;
                *gpp = str;
                gpp = &(str->run);
            }
            *gpp = NULL;
            if (i) {
                *runp = run = (Cdrun) zalloc(sizeof(*run));
                runp = &(run->next);
                run->type = CRT_SIMPLE;
                run->strs = gs;
                run->count = i;
            }
        }
    } else if (cd_state.showd) {
        for (set = cd_state.sets; set; set = set->next) {
            if (set->desc) {
                *runp = run = (Cdrun) zalloc(sizeof(*run));
                runp = &(run->next);
                run->type = CRT_DESC;
                strp = &(run->strs);
                for (str = set->strs; str; str = str->next)
                    if (str->desc) {
                        *strp = str;
                        strp = &(str->run);
                    }
                *strp = NULL;
                run->count = set->desc;
            }
            if (set->desc != set->count) {
                *runp = run = (Cdrun) zalloc(sizeof(*run));
                runp = &(run->next);
                run->type = CRT_SIMPLE;
                strp = &(run->strs);
                for (str = set->strs; str; str = str->next)
                    if (!str->desc) {
                        *strp = str;
                        strp = &(str->run);
                    }
                *strp = NULL;
                run->count = set->count - set->desc;
            }
        }
    } else {
        for (set = cd_state.sets; set; set = set->next)
            if (set->count) {
                *runp = run = (Cdrun) zalloc(sizeof(*run));
                runp = &(run->next);
                run->type = CRT_SIMPLE;
                run->strs = set->strs;
                for (str = set->strs; str; str = str->next)
                    str->run = str->next;
                run->count = set->count;
            }
    }
    *runp = NULL;

    return 0;
}

/* Duplicate and concatenate two arrays.  Return the result. */

static char **
cd_arrcat(char **a, char **b)
{
    if (!b)
        return zarrdup(a);
    else {
        char **r = (char **) zalloc((arrlen(a) + arrlen(b) + 1) *
                                    sizeof(char *));
        char **p = r;

        for (; *a; a++)
            *p++ = ztrdup(*a);
        for (; *b; b++)
            *p++ = ztrdup(*b);

        *p = NULL;

        return r;
    }
}

/* Initialisation. Store and calculate the string and matches and so on.
 *
 * nam: argv[0] of the builtin
 * hide: ???
 * mlen: see max-matches-width style
 * sep: see list-seperator style
 * opts: options to (eventually) pass to compadd.
 *       Returned via 2nd return parameter of 'compdescribe -g'.
 * args: ??? (the positional arguments to 'compdescribe')
 * disp: 1 if descriptions should be shown, 0 otherwise
 */

static int
cd_init(char *nam, char *hide, char *mlen, char *sep,
        char **opts, char **args, int disp)
{
    Cdset *setp, set;
    Cdstr *strp, str;
    char **ap, *tmp;
    int grp = 0, itmp;

    if (cd_parsed) {
	zsfree(cd_state.sep);
	freecdsets(cd_state.sets);
	cd_parsed = 0;
    }
    setp = &(cd_state.sets);
    cd_state.sep = ztrdup(sep);
    cd_state.slen = strlen(sep);
    cd_state.swidth = ZMB_nicewidth(sep);
    cd_state.sets = NULL;
    cd_state.showd = disp;
    cd_state.maxg = cd_state.groups = cd_state.descs = 0;
    cd_state.maxmlen = atoi(mlen);
    cd_state.premaxw = 0;
    itmp = zterm_columns - cd_state.swidth - 4;
    if (cd_state.maxmlen > itmp)
        cd_state.maxmlen = itmp;
    if (cd_state.maxmlen < 4)
        cd_state.maxmlen = 4;
    if (*args && !strcmp(*args, "-g")) {
        args++;
        grp = 1;
    }
    while (*args) {
	*setp = set = (Cdset) zshcalloc(sizeof(*set));
	setp = &(set->next);
        *setp = NULL;
        set->opts = NULL;
        set->strs = NULL;

	if (!(ap = get_user_var(*args))) {
	    zwarnnam(nam, "invalid argument: %s", *args);
            zsfree(cd_state.sep);
            freecdsets(cd_state.sets);
	    return 1;
	}
        for (str = NULL, strp = &(set->strs); *ap; ap++) {
            *strp = str = (Cdstr) zalloc(sizeof(*str));
            strp = &(str->next);

            str->kind = 0;
            str->other = NULL;
            str->set = set;

	    /* Advance tmp to the first unescaped colon. */
	    for (tmp = *ap; *tmp && *tmp != ':'; tmp++)
                if (*tmp == '\\' && tmp[1])
                    tmp++;

            if (*tmp)
                str->desc = ztrdup(rembslash(tmp + 1));
            else
                str->desc = NULL;
            *tmp = '\0';
            str->str = str->match = ztrdup(rembslash(*ap));
            str->len = strlen(str->str);
            str->width = ZMB_nicewidth(str->str);
	    str->sortstr = NULL;
        }
        if (str)
            str->next = NULL;

	if (*++args && **args != '-') {
	    if (!(ap = get_user_var(*args))) {
		zwarnnam(nam, "invalid argument: %s", *args);
                zsfree(cd_state.sep);
                freecdsets(cd_state.sets);
		return 1;
	    }
            for (str = set->strs; str && *ap; str = str->next, ap++)
                str->match = ztrdup(*ap);

	    args++;
	}
        if (hide && *hide) {
            for (str = set->strs; str; str = str->next) {
                if (str->str == str->match)
                    str->str = ztrdup(str->str);
                if (hide[1] && str->str[0] == '-' && str->str[1] == '-')
                    memmove(str->str, str->str + 2, strlen(str->str) - 1);
                else if (str->str[0] == '-' || str->str[0] == '+')
                    memmove(str->str, str->str + 1, strlen(str->str));
            }
        }
	for (ap = args; *args &&
		 (args[0][0] != '-' || args[0][1] != '-' || args[0][2]);
	     args++);

	tmp = *args;
	*args = NULL;
	set->opts = cd_arrcat(ap, opts);
	if ((*args = tmp))
	    args++;
    }
    if (disp && grp) {
        int mg = zterm_columns;

        do {
            cd_group(mg);
            mg = cd_state.maxg - 1;
            cd_calc();
        } while (cd_prep());

    } else {
        cd_calc();
        cd_prep();
    }
    cd_parsed = 1;
    return 0;
}

/* Copy an array with one element in reserve (at the beginning). */

static char **
cd_arrdup(char **a)
{
    char **r = (char **) zalloc((arrlen(a) + 2) * sizeof(char *));
    char **p = r + 1;

    while (*a)
        *p++ = ztrdup(*a++);
    *p = NULL;

    return r;
}

/* Get the next set. */

static int
cd_get(char **params)
{
    Cdrun run;

    if ((run = cd_state.runs)) {
        Cdstr str;
        char **mats, **mp, **dpys, **dp, **opts, *csl = "";

        cd_state.runs = run->next;

        switch (run->type) {
        case CRT_SIMPLE:
            mats = mp = (char **) zalloc((run->count + 1) * sizeof(char *));
            dpys = dp = (char **) zalloc((run->count + 1) * sizeof(char *));

            for (str = run->strs; str; str = str->run) {
                *mp++ = ztrdup(str->match);
                *dp++ = ztrdup(str->str ? str->str : str->match);
            }
            *mp = *dp = NULL;
            opts = zarrdup(run->strs->set->opts);
            if (cd_state.groups) {
                /* We are building a columnised list with dummy matches
                 * but there are also matches without descriptions.
                 * Those end up in a different group, so make sure that
                 * group doesn't have an explanation. */

                for (mp = dp = opts; *mp; mp++) {
                    if (dp[0][0] == '-' && dp[0][1] == 'X') {
                        if (!dp[0][2] && dp[1])
                            mp++;
                    } else
                        *dp++ = *mp;
                }
                *dp = NULL;
            }
            break;

        case CRT_DESC:
            {
		/*
		 * The buffer size:
		 *     max prefix length (cd_state.pre) +
		 *     max padding (cd_state.premaxw generously :) +
		 *     separator length (cd_state.slen) +
		 *     inter matches gap (CM_SPACE) +
		 *     max description length (cd_state.suf) +
		 *     trailing \0
		 */
                VARARR(char, buf,
                       cd_state.pre + cd_state.suf +
		       cd_state.premaxw + cd_state.slen + 3);
                mats = mp = (char **) zalloc((run->count + 1) * sizeof(char *));
                dpys = dp = (char **) zalloc((run->count + 1) * sizeof(char *));

                for (str = run->strs; str; str = str->run) {
		    char *p = buf, *pp, *d;
		    int l, remw, w;

                    *mp++ = ztrdup(str->match);
		    strcpy(p, str->str);
		    p += str->len;
                    memset(p, ' ', (l = (cd_state.premaxw - str->width + CM_SPACE)));
		    p += l;

		    remw = zterm_columns - cd_state.premaxw -
			cd_state.swidth - 3;
		    while (remw < 0 && zterm_columns) {
			/* line wrapped, use remainder of the extra line */
			remw += zterm_columns;
		    }
		    if (cd_state.slen < remw) {
			strcpy(p, cd_state.sep);
			p += cd_state.slen;
			remw -= cd_state.slen;

			/*
			 * copy a character at once until no more screen
			 * width is available. Leave 1 character at the
			 * end of screen as safety margin
			 */
			d = str->desc;
			w = ZMB_nicewidth(d);
			if (w <= remw)
			    strcpy(p, d);
			else {
			    pp = p;
			    while (remw > 0 && *d) {
				l = MB_METACHARLEN(d);
				memcpy(pp, d, l);
				pp[l] = '\0';
				w = ZMB_nicewidth(pp);
				if (w > remw) {
				    *pp = '\0';
				    break;
				}

				pp += l;
				d += l;
				remw -= w;
			    }
			}
		    }

                    *dp++ = ztrdup(buf);
                }
                *mp = *dp = NULL;
                opts = cd_arrdup(run->strs->set->opts);
                opts[0] = ztrdup("-l");
                break;
            }

        case CRT_SPEC:
            mats = (char **) zalloc(2 * sizeof(char *));
            dpys = (char **) zalloc(2 * sizeof(char *));
            mats[0] = ztrdup(run->strs->match);
            dpys[0] = ztrdup(run->strs->str);
            mats[1] = dpys[1] = NULL;
            opts = cd_arrdup(run->strs->set->opts);

            /* Set -2V, possibly reusing the group name from an existing -J/-V
             * flag. */
            for (dp = opts + 1; *dp; dp++)
                if ((dp[0][0] == '-' && dp[0][1] == 'J') ||
		    (dp[0][0] == '-' && dp[0][1] == 'V'))
                    break;
            if (*dp) {
                char *s = tricat("-2V", "", dp[0] + 2);

                zsfree(*dp);
                *dp = s;

                memmove(opts, opts + 1,
                        (arrlen(opts + 1) + 1) * sizeof(char *));
                
            } else
                opts[0] = ztrdup("-2V-default-");
            csl = "packed";
            break;
  
        case CRT_DUMMY:
            {
                char buf[20];

                sprintf(buf, "-E%d", run->count);

                mats = (char **) zalloc(sizeof(char *));
                dpys = (char **) zalloc(sizeof(char *));
                mats[0] = dpys[0] = NULL;

                opts = cd_arrdup(run->strs->set->opts);
                opts[0] = ztrdup(buf);

                csl = "packed";
            }
            break;

	default: /* This silences the "might be used uninitialized" warnings */
        case CRT_EXPL:
            {
		/* add columns as safety margin */
                VARARR(char, dbuf, cd_state.suf + cd_state.slen +
		       zterm_columns);
                char buf[20], *p, *pp, *d;
                int i = run->count, remw, w, l;

                sprintf(buf, "-E%d", i);

                mats = (char **) zalloc(sizeof(char *));
                dpys = (char **) zalloc((i + 1) * sizeof(char *));

                for (dp = dpys, str = run->strs; str; str = str->run) {
                    if (str->run && !strcmp(str->desc, str->run->desc)) {
                        *dp++ = ztrdup("");
                        continue;
                    }

                    strcpy(dbuf, cd_state.sep);
		    remw = zterm_columns - cd_state.gprew -
			cd_state.swidth - CM_SPACE;
		    p = pp = dbuf + cd_state.slen;
		    d = str->desc;
		    w = ZMB_nicewidth(d);
		    if (w <= remw) {
			strcpy(p, d);
			remw -= w;
			pp += strlen(d);
		    } else
			while (remw > 0 && *d) {
			    l = MB_METACHARLEN(d);
			    memcpy(pp, d, l);
			    pp[l] = '\0';
			    w = ZMB_nicewidth(pp);
			    if (w > remw) {
				*pp = '\0';
				break;
			    }

			    pp += l;
			    d += l;
			    remw -= w;
			}

		    while (remw-- > 0)
			*pp++ = ' ';
		    *pp = '\0';

                    *dp++ = ztrdup(dbuf);
                }
                mats[0] = *dp = NULL;

                opts = cd_arrdup(run->strs->set->opts);
                opts[0] = ztrdup(buf);

                csl = "packed";
            }
            break;
        }
        setsparam(params[0], ztrdup(csl));
        setaparam(params[1], opts);
        setaparam(params[2], mats);
        setaparam(params[3], dpys);

        zfree(run, sizeof(*run));

        return 0;
    }
    return 1;
}

/**/
static int
bin_compdescribe(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
{
    int n = arrlen(args);

    if (incompfunc != 1) {
	zwarnnam(nam, "can only be called from completion function");
	return 1;
    }
    if (!args[0][0] || !args[0][1] || args[0][2]) {
	zwarnnam(nam, "invalid argument: %s", args[0]);
	return 1;
    }
    switch (args[0][1]) {
    case 'i':
        if (n < 3) {
            zwarnnam(nam, "not enough arguments");

            return 1;
        }
	return cd_init(nam, args[1], args[2], "", NULL, args + 3, 0);
    case 'I':
        if (n < 6) {
            zwarnnam(nam, "not enough arguments");

            return 1;
        } else {
            char **opts;

            if (!(opts = getaparam(args[4]))) {
		zwarnnam(nam, "unknown parameter: %s", args[4]);
		return 1;
            }
            return cd_init(nam, args[1], args[2], args[3], opts, args + 5, 1);
        }
    case 'g':
	if (cd_parsed) {
	    if (n != 5) {
		zwarnnam(nam, (n < 5 ? "not enough arguments" :
			      "too many arguments"));
		return 1;
	    }
	    return cd_get(args + 1);
	} else {
	    zwarnnam(nam, "no parsed state");
	    return 1;
	}
    }
    zwarnnam(nam, "invalid option: %s", args[0]);
    return 1;
}

/* Help for `_arguments'. */

typedef struct cadef *Cadef;
typedef struct caopt *Caopt;
typedef struct caarg *Caarg;

/* Cache for a set of _arguments-definitions. */

struct cadef {
    Cadef next;			/* next in cache */
    Cadef snext;		/* next set */
    Caopt opts;			/* the options */
    int nopts, ndopts, nodopts;	/* number of options/direct/optional direct */
    Caarg args;			/* the normal arguments */
    Caarg rest;			/* the rest-argument */
    char **defs;		/* the original strings */
    int ndefs;			/* number of ... */
    int lastt;			/* last time this was used */
    Caopt *single;		/* array of single-letter options */
    char *match;		/* -M spec to use */
    int argsactive;		/* if normal arguments are still allowed */
				/* used while parsing a command line */
    char *set;			/* set name prefix (<name>-), shared */
    int flags;			/* see CDF_* below */
    char *nonarg;		/* pattern for non-args (-A argument) */
};

#define CDF_SEP 1		/* -S was specified: -- terminates options */

/* Description for an option. */

struct caopt {
    Caopt next;
    char *name;			/* option name */
    char *descr;		/* the description */
    char **xor;			/* if this, then not ... */
    int type;			/* type, CAO_* */
    Caarg args;			/* option arguments */
    int active;			/* still allowed on command line */
    int num;			/* it's the num'th option */
    char *gsname;		/* group or set name, shared */
    int not;			/* don't complete this option (`!...') */
};

#define CAO_NEXT    1		/* argument follows in next argument (`-opt:...') */
#define CAO_DIRECT  2		/* argument follows option directly (`-opt-:...') */
#define CAO_ODIRECT 3		/* argument may follow option directly (`-opt+:...') */
#define CAO_EQUAL   4		/* argument follows mandatory equals (`-opt=-:...') */
#define CAO_OEQUAL  5		/* argument follows optional equals (`-opt=:...') */

/* Description for an argument */

struct caarg {
    Caarg next;
    char *descr;		/* description */
    char **xor;			/* if this, then not ... */
    char *action;		/* what to do for it */
    int type;			/* CAA_* below */
    char *end;			/* end-pattern for ::<pat>:... */
    char *opt;			/* option name if for an option */
    int num;			/* it's the num'th argument */
    int min;			/* earliest possible arg pos, given optional args */
    int direct;			/* true if argument number was given explicitly */
    int active;			/* still allowed on command line */
    char *gsname;		/* group or set name, shared */
};

#define CAA_NORMAL 1
#define CAA_OPT    2
#define CAA_REST   3
#define CAA_RARGS  4
#define CAA_RREST  5

/* The cache of parsed descriptons. */

#define MAX_CACACHE 8
static Cadef cadef_cache[MAX_CACACHE];

/* Compare two arrays of strings for equality. */

static int
arrcmp(char **a, char **b)
{
    if (!a && !b)
	return 1;
    else if (!a || !b)
	return 0;
    else {
	while (*a && *b)
	    if (strcmp(*a++, *b++))
		return 0;

	return (!*a && !*b);
    }
}

/* Memory stuff. Obviously. */

static void
freecaargs(Caarg a)
{
    Caarg n;

    for (; a; a = n) {
	n = a->next;
	zsfree(a->descr);
	if (a->xor)
	    freearray(a->xor);
	zsfree(a->action);
	zsfree(a->end);
	zsfree(a->opt);
	zfree(a, sizeof(*a));
    }
}

static void
freecadef(Cadef d)
{
    Cadef s;
    Caopt p, n;

    while (d) {
	s = d->snext;
	zsfree(d->match);
	zsfree(d->set);
	if (d->defs)
	    freearray(d->defs);

	for (p = d->opts; p; p = n) {
	    n = p->next;
	    zsfree(p->name);
	    zsfree(p->descr);
	    if (p->xor)
		freearray(p->xor);
	    freecaargs(p->args);
	    zfree(p, sizeof(*p));
	}
	freecaargs(d->args);
	freecaargs(d->rest);
	zsfree(d->nonarg);
	if (d->single)
	    zfree(d->single, 256 * sizeof(Caopt));
	zfree(d, sizeof(*d));
	d = s;
    }
}

/* Remove backslashes before colons. */

static char *
rembslashcolon(char *s)
{
    char *p, *r;

    r = p = s = dupstring(s);

    while (*s) {
	if (s[0] != '\\' || s[1] != ':')
	    *p++ = *s;
	s++;
    }
    *p = '\0';

    return r;
}

/* Add backslashes before colons. */

static char *
bslashcolon(char *s)
{
    char *p, *r;

    r = p = zhalloc((2 * strlen(s)) + 1);

    while (*s) {
	if (*s == ':')
	    *p++ = '\\';
	*p++ = *s++;
    }
    *p = '\0';

    return r;
}

/* Parse an argument definition. */

static Caarg
parse_caarg(int mult, int type, int num, int opt, char *oname, char **def,
	    char *set)
{
    Caarg ret = (Caarg) zalloc(sizeof(*ret));
    char *p = *def, *d, sav;

    ret->next = NULL;
    ret->descr = ret->action = ret->end = NULL;
    ret->xor = NULL;
    ret->num = num;
    ret->min = num - opt;
    ret->type = type;
    ret->opt = ztrdup(oname);
    ret->direct = 0;
    ret->gsname = set;

    /* Get the description. */

    for (d = p; *p && *p != ':'; p++)
	if (*p == '\\' && p[1])
	    p++;
    sav = *p;
    *p = '\0';
    ret->descr = ztrdup(rembslashcolon(d));

    /* Get the action if there is one. */

    if (sav) {
	if (mult) {
	    for (d = ++p; *p && *p != ':'; p++)
		if (*p == '\\' && p[1])
		    p++;
	    sav = *p;
	    *p = '\0';
	    ret->action = ztrdup(rembslashcolon(d));
	    if (sav)
		*p = ':';
	} else
	    ret->action = ztrdup(rembslashcolon(p + 1));
    } else
	ret->action = ztrdup("");
    *def = p;

    return ret;
}

static Cadef
alloc_cadef(char **args, int single, char *match, char *nonarg, int flags)
{
    Cadef ret;

    ret = (Cadef) zalloc(sizeof(*ret));
    ret->next = ret->snext = NULL;
    ret->opts = NULL;
    ret->args = ret->rest = NULL;
    ret->nonarg = ztrdup(nonarg);
    if (args) {
	ret->defs = zarrdup(args);
	ret->ndefs = arrlen(args);
    } else {
	ret->defs = NULL;
	ret->ndefs = 0;
    }
    ret->nopts = 0;
    ret->ndopts = 0;
    ret->nodopts = 0;
    ret->lastt = time(0);
    ret->set = NULL;
    if (single) {
	ret->single = (Caopt *) zalloc(256 * sizeof(Caopt));
	memset(ret->single, 0, 256 * sizeof(Caopt));
    } else
	ret->single = NULL;
    ret->match = ztrdup(match);
    ret->flags = flags;

    return ret;
}

static void
set_cadef_opts(Cadef def)
{
    Caarg argp;
    int xnum;

    for (argp = def->args, xnum = 0; argp; argp = argp->next) {
	if (!argp->direct)
	    argp->min = argp->num - xnum;
	if (argp->type == CAA_OPT)
	    xnum++;
    }
}

/* Parse an array of definitions. */

static Cadef
parse_cadef(char *nam, char **args)
{
    Cadef all, ret;
    Caopt *optp;
    char **orig_args = args, *p, *q, *match = "r:|[_-]=* r:|=*", **xor, **sargs;
    char *adpre, *adsuf, *axor = NULL, *doset = NULL, **pendset = NULL, **curset = NULL;
    char *nonarg = NULL;
    int single = 0, anum = 1, xnum, flags = 0;
    int foreignset = 0, not = 0;

    /* First string is the auto-description definition. */

    for (p = args[0]; *p && (p[0] != '%' || p[1] != 'd'); p++);

    if (*p) {
	*p = '\0';
	adpre = dupstring(args[0]);
	*p = '%';
	adsuf = dupstring(p + 2);
    } else
	adpre = adsuf = NULL;

    /* Now get the -s, -A, -S and -M options. */

    args++;
    while ((p = *args) && *p == '-' && p[1]) {
	for (q = ++p; *q; q++)
	    if (*q == 'M' || *q == 'A') {
		q = "";
		break;
	    } else if (*q != 's' && *q != 'S')
		break;

	if (*q)
	    break;

	for (; *p; p++) {
	    if (*p == 's')
		single = 1;
	    else if (*p == 'S')
		flags |= CDF_SEP;
	    else if (*p == 'A') {
		if (p[1]) {
		    nonarg = p + 1;
		    p += strlen(p+1);
		} else if (args[1])
		    nonarg = *++args;
		else
		    break;
	    } else if (*p == 'M') {
		if (p[1]) {
		    match = p + 1;
		    p += strlen(p+1);
		} else if (args[1])
		    match = *++args;
		else
		    break;
	    }
	}
	if (*p)
	    break;

	args++;
    }
    if (*args && !strcmp(*args, ":"))
        args++;
    if (!*args)
	return NULL;

    if (nonarg)
	tokenize(nonarg = dupstring(nonarg));
    /* Looks good. Optimistically allocate the cadef structure. */

    all = ret = alloc_cadef(orig_args, single, match, nonarg, flags);
    optp = &(ret->opts);
    sargs = args;

    /* Get the definitions. */

    for (; *args || pendset; args++) {
	if (!*args) {
	    /* start new set */
	    args = sargs; /* go back and repeat parse of common options */
	    doset = NULL;
	    set_cadef_opts(ret);
	    ret = ret->snext = alloc_cadef(NULL, single, match, nonarg, flags);
	    optp = &(ret->opts);
	    anum = 1;
	    foreignset = 0;
	    curset = pendset;
	    pendset = 0;
        }
        if (args[0][0] == '-' && !args[0][1] && args[1]) {
	    if ((foreignset = curset && args != curset)) {
		if (!pendset && args > curset)
		    pendset = args; /* mark pointer to next pending set */
		++args;
	    } else {
		/* Carrying on: this is the current set */
		char *p = *++args;
		int l = strlen(p) - 1;

		if (*p == '(' && p[l] == ')') {
		    axor = p = dupstring(p + 1);
		    p[l - 1] = '\0';
		} else
		    axor = NULL;
		if (!*p) {
		    freecadef(all);
		    zwarnnam(nam, "empty set name");
		    return NULL;
		}
		ret->set = doset = tricat(p, "-", "");
		curset = args; /* needed for the first set */
	    }
	    continue;
	} else if (args[0][0] == '+' && !args[0][1] && args[1]) {
	    char *p;
	    int l;

	    foreignset = 0; /* group not in any set, don't want to skip it */
	    p = *++args;
	    l = strlen(p) - 1;
	    if (*p == '(' && p[l] == ')') {
		axor = p = dupstring(p + 1);
		p[l - 1] = '\0';
	    } else
		axor = NULL;
	    if (!*p) {
		freecadef(all);
		zwarnnam(nam, "empty group name");
		return NULL;
	    }
	    doset = tricat(p, "-", "");
	    continue;
	} else if (foreignset) /* skipping over a different set */
	    continue;
	p = dupstring(*args);
	xnum = 0;
	if ((not = (*p == '!')))
	    p++;
	if (*p == '(') {
	    /* There is a xor list, get it. */

	    LinkList list = newlinklist();
	    LinkNode node;
	    char **xp, sav;

	    while (*p && *p != ')') {
		for (p++; inblank(*p); p++);

		if (*p == ')')
		    break;
		for (q = p++; *p && *p != ')' && !inblank(*p); p++);

		if (!*p)
		    break;

		sav = *p;
		*p = '\0';
		addlinknode(list, dupstring(q));
		xnum++;
		*p = sav;
	    }
	    /* Oops, end-of-string. */
	    if (*p != ')') {
		freecadef(all);
		zwarnnam(nam, "invalid argument: %s", *args);
		return NULL;
	    }
	    if (doset && axor)
		xnum++;
	    xor = (char **) zalloc((xnum + 2) * sizeof(char *));
	    for (node = firstnode(list), xp = xor; node; incnode(node), xp++)
		*xp = ztrdup((char *) getdata(node));
	    if (doset && axor)
		*xp++ = ztrdup(axor);
	    xp[0] = xp[1] = NULL;

	    p++;
	} else if (doset && axor) {
	    xnum = 1;
	    xor = (char **) zalloc(3 * sizeof(char *));
	    xor[0] = ztrdup(axor);
	    xor[1] = xor[2] = NULL;
	} else
	    xor = NULL;

	if (*p == '-' || *p == '+' ||
	    (*p == '*' && (p[1] == '-' || p[1] == '+'))) {
	    /* It's an option. */
	    Caopt opt;
	    Caarg oargs = NULL;
	    int multi, otype = CAO_NEXT, again = 0;
	    char *name, *descr, c, *againp = NULL;

	    rec:

	    /* Allowed more than once? */
	    if ((multi = (*p == '*')))
		p++;

	    if (((p[0] == '-' && p[1] == '+') ||
		 (p[0] == '+' && p[1] == '-')) &&
		p[2] && p[2] != ':' && p[2] != '[' &&
		p[2] != '=' && p[2] != '-' && p[2] != '+') {
		/* It's a -+ or +- definition. We just execute the whole
		 * stuff twice for such things. */
		againp = dupstring(p);
		name = ++p;
		*p = (again ? '-' : '+');
		again++;
	    } else {
		name = p;
		/* If it's a long option skip over the first `-'. */
		if (p[0] == '-' && p[1] == '-')
		    p++;
	    }
	    if (!p[1]) {
		freecadef(all);
		zwarnnam(nam, "invalid argument: %s", *args);
		return NULL;
	    }

	    /* Skip over the name. */
	    for (p++; *p && *p != ':' && *p != '[' &&
		     ((*p != '-' && *p != '+') ||
		      (p[1] != ':' && p[1] != '[')) &&
		     (*p != '=' ||
		      (p[1] != ':' && p[1] != '[' && p[1] != '-')); p++)
		if (*p == '\\' && p[1])
		    p++;

	    /* The character after the option name specifies the type. */
	    c = *p;
	    *p = '\0';
	    if (c == '-') {
		otype = CAO_DIRECT;
		c = *++p;
	    } else if (c == '+') {
		otype = CAO_ODIRECT;
		c = *++p;
	    } else if (c == '=') {
		otype = CAO_OEQUAL;
		if ((c = *++p) == '-') {
		    otype = CAO_EQUAL;
		    c = *++p;
		}
	    }
	    /* Get the optional description, if any. */
	    if (c == '[') {
		for (descr = ++p; *p && *p != ']'; p++)
		    if (*p == '\\' && p[1])
			p++;

		if (!*p) {
		    freecadef(all);
		    zwarnnam(nam, "invalid option definition: %s", *args);
		    return NULL;
		}
		*p++ = '\0';
		c = *p;
	    } else
		descr = NULL;

	    if (c && c != ':') {
		freecadef(all);
		zwarnnam(nam, "invalid option definition: %s", *args);
		return NULL;
	    }
	    /* Add the option name to the xor list if not `*-...'. */
	    if (!multi) {
		if (!xor) {
		    xor = (char **) zalloc(2 * sizeof(char *));
		    xor[0] = xor[1] = NULL;
		}
                zsfree(xor[xnum]);
		xor[xnum] = ztrdup(rembslashcolon(name));
	    }
	    if (c == ':') {
		/* There's at least one argument. */

		Caarg *oargp = &oargs;
		int atype, rest, oanum = 1, onum = 0;
		char *end;

		/* Loop over the arguments. */

		while (c == ':') {
		    rest = 0;
		    end = NULL;

		    /* Get the argument type. */
		    if (*++p == ':') {
			atype = CAA_OPT;
			p++;
		    } else if (*p == '*') {
			if (*++p != ':') {
			    char sav;

			    for (end = p++; *p && *p != ':'; p++)
				if (*p == '\\' && p[1])
				    p++;
			    sav = *p;
			    *p = '\0';
			    end = dupstring(end);
			    tokenize(end);
			    *p = sav;
			}
			if (*p != ':') {
			    freecadef(all);
			    freecaargs(oargs);
			    zwarnnam(nam, "invalid option definition: %s",
				    *args);
			    return NULL;
			}
			if (*++p == ':') {
			    if (*++p == ':') {
				atype = CAA_RREST;
				p++;
			    } else
				atype = CAA_RARGS;
			} else
			    atype = CAA_REST;
			rest = 1;
		    } else
			atype = CAA_NORMAL;

		    /* And the definition. */

		    *oargp = parse_caarg(!rest, atype, oanum++, onum,
					 name, &p, doset);
		    if (atype == CAA_OPT)
			onum++;
		    if (end)
			(*oargp)->end = ztrdup(end);
		    oargp = &((*oargp)->next);
		    if (rest)
			break;
		    c = *p;
		}
	    }
	    /* Store the option definition. */

	    *optp = opt = (Caopt) zalloc(sizeof(*opt));
	    optp = &((*optp)->next);

	    opt->next = NULL;
	    opt->gsname = doset;
	    opt->name = ztrdup(rembslashcolon(name));
	    if (descr)
		opt->descr = ztrdup(descr);
	    else if (adpre && oargs && !oargs->next) {
		char *d;

		for (d = oargs->descr; *d; d++)
		    if (!iblank(*d))
			break;

		if (*d)
		    opt->descr = tricat(adpre, oargs->descr, adsuf);
		else
		    opt->descr = NULL;
	    } else
		opt->descr = NULL;
	    opt->xor = (again == 1 && xor ? zarrdup(xor) : xor);
	    opt->type = otype;
	    opt->args = oargs;
	    opt->num = ret->nopts++;
	    opt->not = not;

	    if (otype == CAO_DIRECT || otype == CAO_EQUAL)
		ret->ndopts++;
	    else if (otype == CAO_ODIRECT || otype == CAO_OEQUAL)
		ret->nodopts++;

	    /* If this is for single-letter option we also store a
	     * pointer for the definition in the array for fast lookup.
	     * But don't treat '--' as a single option called '-' */


	    if (single && name[1] && !name[2] && name[1] != '-')
		ret->single[STOUC(name[1])] = opt;

	    if (again == 1) {
		/* Do it all again for `*-...'. */
		p = againp;
		goto rec;
	    }
	} else if (*p == '*') {
	    /* It's a rest-argument definition. */

	    int type = CAA_REST;

	    if (not)
		continue;

	    if (*++p != ':') {
		freecadef(all);
		zwarnnam(nam, "invalid rest argument definition: %s", *args);
		return NULL;
	    }
	    if (ret->rest) {
		freecadef(all);
		zwarnnam(nam, "doubled rest argument definition: %s", *args);
		return NULL;
	    }
	    if (*++p == ':') {
		if (*++p == ':') {
		    type = CAA_RREST;
		    p++;
		} else
		    type = CAA_RARGS;
	    }
	    ret->rest = parse_caarg(0, type, -1, 0, NULL, &p, doset);
	    ret->rest->xor = xor;
	} else {
	    /* It's a normal argument definition. */

	    int type = CAA_NORMAL, direct;
	    Caarg arg, tmp, pre;

	    if (not)
		continue;

	    if ((direct = idigit(*p))) {
		/* Argument number is given. */
		int num = 0;

		while (*p && idigit(*p))
		    num = (num * 10) + (((int) *p++) - '0');

		anum = num + 1;
	    } else
		/* Default number. */
		anum++;

	    if (*p != ':') {
		freecadef(all);
		zwarnnam(nam, "invalid argument: %s", *args);
		if (xor)
		    free(xor);
		return NULL;
	    }
	    if (*++p == ':') {
		/* Optional argument. */
		type = CAA_OPT;
		p++;
	    }
	    arg = parse_caarg(0, type, anum - 1, 0, NULL, &p, doset);
	    arg->xor = xor;
	    arg->direct = direct;

	    /* Sort the new definition into the existing list. */

	    for (tmp = ret->args, pre = NULL;
		 tmp && tmp->num < anum - 1;
		 pre = tmp, tmp = tmp->next);

	    if (tmp && tmp->num == anum - 1) {
		freecadef(all);
		freecaargs(arg);
		zwarnnam(nam, "doubled argument definition: %s", *args);
		return NULL;
	    }
	    arg->next = tmp;
	    if (pre)
		pre->next = arg;
	    else
		ret->args = arg;
	}
    }
    set_cadef_opts(ret);

    return all;
}

/* Given an array of definitions, return the cadef for it. From the cache
 * are newly built. */

static Cadef
get_cadef(char *nam, char **args)
{
    Cadef *p, *min, new;
    int i, na = arrlen(args);

    for (i = MAX_CACACHE, p = cadef_cache, min = NULL; i && *p; p++, i--)
	if (*p && na == (*p)->ndefs && arrcmp(args, (*p)->defs)) {
	    (*p)->lastt = time(0);

	    return *p;
	} else if (!min || !*p || (*p)->lastt < (*min)->lastt)
	    min = p;
    if (i > 0)
	min = p;
    if ((new = parse_cadef(nam, args))) {
	freecadef(*min);
	*min = new;
    }
    return new;
}

/*
 * Get the option used in a word from the line, if any.
 *
 * "d" is a complete set of argument/option definitions to scan.
 * "line" is the word we are scanning.
 * "full" indicates that the option must match a full word; otherwise
 *   we look for "=" arguments or prefixes.
 * *"end" is set to point to the end of the option, in some cases
 *   leaving an option argument after it.
 */

static Caopt
ca_get_opt(Cadef d, char *line, int full, char **end)
{
    Caopt p;

    /* The full string may be an option. */

    for (p = d->opts; p; p = p->next)
	if (p->active && !strcmp(p->name, line)) {
	    if (end)
		*end = line + strlen(line);

	    return p;
	}

    if (!full) {
	/* The string from the line probably only begins with an option. */
	for (p = d->opts; p; p = p->next)
	    if (p->active && ((!p->args || p->type == CAO_NEXT) ?
			      !strcmp(p->name, line) : strpfx(p->name, line))) {
		int l = strlen(p->name);
		if ((p->type == CAO_OEQUAL || p->type == CAO_EQUAL) &&
		    line[l] && line[l] != '=')
		    continue;

		if (end) {
		    /* Return a pointer to the end of the option. */
		    if ((p->type == CAO_OEQUAL || p->type == CAO_EQUAL) &&
			line[l] == '=')
			l++;

		    *end = line + l;
		}
		return p;
	    }
    }
    return NULL;
}

/* Same as above, only for single-letter-style. */

static Caopt
ca_get_sopt(Cadef d, char *line, char **end, LinkList *lp)
{
    Caopt p, pp = NULL;
    char pre = *line++;
    LinkList l = NULL;

    *lp = NULL;
    for (p = NULL; *line; line++) {
	if ((p = d->single[STOUC(*line)]) && p->active &&
	    p->args && p->name[0] == pre) {
	    if (p->type == CAO_NEXT) {
		if (!l)
		    *lp = l = newlinklist();
		addlinknode(l, p);
	    } else {
		if (end) {
		    line++;
		    if ((p->type == CAO_OEQUAL || p->type == CAO_EQUAL) &&
			*line == '=')
			line++;
		    *end = line;
		}
		pp = p;
		break;
	    }
	} else if (!p || (p && !p->active))
	    return NULL;
	pp = (p->name[0] == pre ? p : NULL);
	p = NULL;
    }
    if (pp && end)
	*end = line;
    return pp;
}

/* Search for an option in all sets except the current one.
 * Return true if found */

static int
ca_foreign_opt(Cadef curset, Cadef all, char *option)
{
    Cadef d;
    Caopt p;

    for (d = all; d; d = d->snext) {
	if (d == curset)
	    continue;

	for (p = d->opts; p; p = p->next) {
	    if (!strcmp(p->name, option))
		return 1;
	}
    }
    return 0;
}

/* Return the n'th argument definition. */

static Caarg
ca_get_arg(Cadef d, int n)
{
    if (d->argsactive) {
	Caarg a = d->args;

	while (a && (!a->active || n < a->min || n > a->num)) {
            if (!a->active)
                n++;
	    a = a->next;
        }
	if (a && a->min <= n && a->num >= n && a->active)
	    return a;

	return (d->rest && d->rest->active ? d->rest : NULL);
    }
    return NULL;
}

/* Mark options as inactive.
 *   d: option definitions for a set
 *   pass either:
 *     xor: a list if exclusions
 *     opts: if set, all options excluded leaving only nornal/rest arguments */

static void
ca_inactive(Cadef d, char **xor, int cur, int opts)
{
    if ((xor || opts) && cur <= compcurrent) {
	Caopt opt;
	char *x;
        /* current word could be a prefix of a longer one so only do
	 * exclusions for single-letter options (for option clumping) */
	int single = !opts && (cur == compcurrent);

	for (; (x = (opts ? "-" : *xor)); xor++) {
	    int excludeall = 0;
	    char *grp = NULL;
	    size_t grplen;
	    char *next, *sep = x;

	    while (*sep != '+' && *sep != '-' && *sep != ':' && *sep != '*' && !idigit(*sep)) {
		if (!(next = strchr(sep, '-')) || !*++next) {
		    /* exclusion is just the name of a set or group */
		    excludeall = 1; /* excluding options and args */
		    sep += strlen(sep);
		    /* A trailing '-' is included in the various gsname fields but is not
		     * there for this branch. This is why we add excludeall to grplen
		     * when checking for the null in a few places below */
		    break;
		}
		sep = next;
	    }
	    if (sep > x) { /* exclusion included a set or group name */
		grp = x;
		grplen = sep - grp;
		x = sep;
	    }

	    if (excludeall || (x[0] == ':' && !x[1])) {
		if (grp) {
		    Caarg a;

		    for (a = d->args; a; a = a->next)
			if (a->gsname && !strncmp(a->gsname, grp, grplen) &&
				!a->gsname[grplen + excludeall])
			    a->active = 0;
		    if (d->rest && d->rest->gsname &&
			    !strncmp(d->rest->gsname, grp, grplen) &&
			    !d->rest->gsname[grplen + excludeall])
			d->rest->active = 0;
		} else
		    d->argsactive = 0;
	    }

	    if (excludeall || (x[0] == '-' && !x[1])) {
		Caopt p;

		for (p = d->opts; p; p = p->next)
		    if ((!grp || (p->gsname && !strncmp(p->gsname, grp, grplen) &&
			    !p->gsname[grplen + excludeall])) &&
			    !(single && *p->name && p->name[1] && p->name[2]))
			p->active = 0;
	    }

	    if (excludeall || (x[0] == '*' && !x[1])) {
		if (d->rest && (!grp || (d->rest->gsname &&
			!strncmp(d->rest->gsname, grp, grplen) &&
			!d->rest->gsname[grplen + excludeall])))
		    d->rest->active = 0;
            }

	    if (!excludeall) {
		if (idigit(x[0])) {
		    int n = atoi(x);
		    Caarg a = d->args;

		    while (a && a->num < n)
			a = a->next;

		    if (a && a->num == n && (!grp || (a->gsname &&
			    !strncmp(a->gsname, grp, grplen))))
			a->active = 0;
		} else if ((opt = ca_get_opt(d, x, 1, NULL)) &&
			(!grp || (opt->gsname && !strncmp(opt->gsname, grp, grplen))) &&
			!(single && *opt->name && opt->name[1] && opt->name[2]))
		    opt->active = 0;
		if (opts)
		    break;
	    }
	}
    }
}

/* State when parsing a command line. */

typedef struct castate *Castate;

/* Encapsulates details from parsing the current line against a particular set,
 * Covers positions of options and normal arguments. Used as a linked list
 * with one state for each set. */

struct castate {
    Castate snext;	/* state for next set */
    Cadef d;		/* parsed _arguments specs for the set */
    int nopts;		/* number of specified options (size of oargs) */
    Caarg def;		/* definition for the current set */
    Caarg ddef;
    Caopt curopt;	/* option description corresponding to option found on the command-line */
    Caopt dopt;
    int opt;		/* the length of the option up to a maximum of 2 */
    int arg;		/* completing arguments to an option or rest args */
    int argbeg;         /* position of first rest argument (+1) */
    int optbeg;		/* first word after the last option to the left of the cursor:
			 * in effect the start of any arguments to the current option */
    int nargbeg;	/* same as optbeg but used during parse */
    int restbeg;	/* same as argbeg but used during parse */
    int curpos;		/* current word position */
    int argend;         /* total number of words */
    int inopt;		/* set to current word pos if word is a recognised option */
    int inarg;          /* in a normal argument */
    int nth;		/* number of current normal arg */
    int singles;	/* argument consists of clumped options */
    int oopt;
    int actopts;	/* count of active options */
    LinkList args;	/* list of non-option args used for populating $line */
    LinkList *oargs;	/* list of lists used for populating $opt_args */
};

static struct castate ca_laststate;
static int ca_parsed = 0, ca_alloced = 0;
static int ca_doff; /* no. of chars of ignored prefix (for clumped options or arg to an option) */

static void
freecastate(Castate s)
{
    int i;
    LinkList *p;

    freelinklist(s->args, freestr);
    for (i = s->nopts, p = s->oargs; i--; p++)
	if (*p)
	    freelinklist(*p, freestr);
    zfree(s->oargs, s->d->nopts * sizeof(LinkList));
}

/* Return a copy of an option's argument, ignoring possible quoting
 * in the option name. */

static char *
ca_opt_arg(Caopt opt, char *line)
{
    char *o = opt->name;

    while (1) {
        if (*o == '\\')
            o++;
        if (*line == '\\' || *line == '\'' || *line == '"')
            line++;
        if (!*o || *o != *line)
            break;
        o++;
        line++;
    }
    if (*line && (opt->type == CAO_EQUAL || opt->type == CAO_OEQUAL)) {
        if (*line == '\\')
            line++;
        if (*line == '=')
            line++;
    }
    return ztrdup(line);
}

/* Parse the command line for a particular argument set (d).
 * Returns 1 if the set should be skipped because it doesn't match
 * existing options on the line. */

static int
ca_parse_line(Cadef d, Cadef all, int multi, int first)
{
    Caarg adef, ddef;
    Caopt ptr, wasopt = NULL, dopt;
    struct castate state;
    char *line, *oline, *pe, **argxor = NULL;
    int cur, doff, argend, arglast;
    Patprog endpat = NULL, napat = NULL;
    LinkList sopts = NULL;
#if 0
    int ne;
#endif

    /* Free old state. */

    if (first && ca_alloced) {
	Castate s = &ca_laststate, ss;

	while (s) {
	    ss = s->snext;
	    freecastate(s);
	    s = ss;
	}
    }
    /* Mark everything as active. */

    for (ptr = d->opts; ptr; ptr = ptr->next)
	ptr->active = 1;
    d->argsactive = 1;
    if (d->rest)
	d->rest->active = 1;
    for (adef = d->args; adef; adef = adef->next)
	adef->active = 1;

    /* Default values for the state. */

    state.snext = NULL;
    state.d = d;
    state.nopts = d->nopts;
    state.def = state.ddef = NULL;
    state.curopt = state.dopt = NULL;
    state.argbeg = state.optbeg = state.nargbeg = state.restbeg = state.actopts =
	state.nth = state.inopt = state.inarg = state.opt = state.arg = 1;
    state.argend = argend = arrlen(compwords) - 1;
    state.singles = state.oopt = 0;
    state.curpos = compcurrent;
    state.args = znewlinklist();
    state.oargs = (LinkList *) zalloc(d->nopts * sizeof(LinkList));
    memset(state.oargs, 0, d->nopts * sizeof(LinkList));

    ca_alloced = 1;

    memcpy(&ca_laststate, &state, sizeof(state));

    if (!compwords[1]) {
	ca_laststate.opt = ca_laststate.arg = 0;

	goto end;
    }
    if (d->nonarg) /* argument to -A */
	napat = patcompile(d->nonarg, 0, NULL);

    /* Loop over the words from the line. */

    for (line = compwords[1], cur = 2, state.curopt = NULL, state.def = NULL;
	 line; line = compwords[cur++]) {
	ddef = adef = NULL;
	dopt = NULL;
	state.singles = arglast = 0;

        oline = line;
#if 0
        /*
	 * remove quotes.
	 * This is commented out:  it doesn't allow you to discriminate
	 * between command line values that can be expanded and those
	 * that can't, and in some cases this generates inconsistency;
	 * for example, ~/foo\[bar unqotes to ~/foo[bar which doesn't
	 * work either way---it's wrong if the ~ is quoted, and
	 * wrong if the [ isn't quoted..  So it's now up to the caller to
	 * unquote.
	 */
        line = dupstring(line);
        ne = noerrs;
        noerrs = 2;
        parse_subst_string(line);
        noerrs = ne;
#endif
        remnulargs(line);
        untokenize(line);

	ca_inactive(d, argxor, cur - 1, 0);
	if ((d->flags & CDF_SEP) && cur != compcurrent && !strcmp(line, "--")) {
	    ca_inactive(d, NULL, cur, 1);
	    continue;
	}

	/* We've got a definition for an option/rest argument. For an option,
	 * this means that we're completing arguments to that option. */
	if (state.def) {
	    state.arg = 0;
	    if (state.curopt)
		zaddlinknode(state.oargs[state.curopt->num], ztrdup(oline));

	    if ((state.opt = (state.def->type == CAA_OPT)) && state.def->opt)
		state.oopt++;

	    if (state.def->type == CAA_REST || state.def->type == CAA_RARGS ||
		state.def->type == CAA_RREST) {
		if (state.def->end && pattry(endpat, line)) {
		    state.def = NULL;
		    state.curopt = NULL;
		    state.opt = state.arg = 1;
		    state.argend = ca_laststate.argend = cur - 1;
		    goto cont;
		}
	    } else if ((state.def = state.def->next)) {
		state.argbeg = cur;
		state.argend = argend;
	    } else if (sopts && nonempty(sopts)) {
		state.curopt = (Caopt) uremnode(sopts, firstnode(sopts));
		state.def = state.curopt->args;
		state.opt = 0;
		state.argbeg = state.optbeg = state.inopt = cur;
		state.argend = argend;
		doff = 0;
		state.singles = 1;
		if (!state.oargs[state.curopt->num])
		    state.oargs[state.curopt->num] = znewlinklist();
		goto cont;
	    } else {
		state.curopt = NULL;
		state.opt = 1;
	    }
	} else {
	    state.opt = state.arg = 1;
	    state.curopt = NULL;
	}
	if (state.opt)
	    state.opt = (line[0] ? (line[1] ? 2 : 1) : 0);

	pe = NULL;

	wasopt = NULL;

	/* See if it's an option. */

	if (state.opt == 2 && (state.curopt = ca_get_opt(d, line, 0, &pe)) &&
	    (state.curopt->type == CAO_OEQUAL ?
	     (compwords[cur] || pe[-1] == '=') :
	     (state.curopt->type == CAO_EQUAL ?
	      (pe[-1] == '=' || !pe[0]) : 1))) {

	    if ((ddef = state.def = ((state.curopt->type != CAO_EQUAL ||
				      pe[-1] == '=') ?
				     state.curopt->args : NULL)))
		dopt = state.curopt;

	    doff = pe - line;
	    state.optbeg = state.argbeg = state.inopt = cur;
	    state.argend = argend;
	    state.singles = (d->single && (!pe || !*pe) &&
			     state.curopt->name[1] && !state.curopt->name[2] &&
			     /* Don't treat '--' as a single option called '-' */
			     state.curopt->name[1] != '-');

	    if (!state.oargs[state.curopt->num])
		state.oargs[state.curopt->num] = znewlinklist();

	    ca_inactive(d, state.curopt->xor, cur, 0);

	    /* Collect the argument strings. Maybe. */

	    if (state.def &&
		(state.curopt->type == CAO_DIRECT ||
		 state.curopt->type == CAO_EQUAL ||
		 (state.curopt->type == CAO_ODIRECT && pe[0]) ||
		 (state.curopt->type == CAO_OEQUAL &&
		  (pe[0] || pe[-1] == '=')))) {
		if (state.def->type != CAA_REST &&
		    state.def->type != CAA_RARGS &&
		    state.def->type != CAA_RREST)
		    state.def = state.def->next;

		zaddlinknode(state.oargs[state.curopt->num],
                             ca_opt_arg(state.curopt, oline));
	    }
	    if (state.def)
		state.opt = 0;
	    else {
		if (!d->single || (state.curopt->name[1] && state.curopt->name[2]))
		    wasopt = state.curopt;
		state.curopt = NULL;
	    }
	} else if (state.opt == 2 && d->single &&
		   ((state.curopt = ca_get_sopt(d, line, &pe, &sopts)) ||
		    (cur != compcurrent && sopts && nonempty(sopts)))) {
	    /* Or maybe it's a single-letter option? */

	    char *p;
	    Caopt tmpopt;

	    if (cur != compcurrent && sopts && nonempty(sopts))
		state.curopt = (Caopt) uremnode(sopts, firstnode(sopts));

	    if (!state.oargs[state.curopt->num])
		state.oargs[state.curopt->num] = znewlinklist();

	    state.def = state.curopt->args;
	    ddef = (state.curopt->type == CAO_NEXT && cur == compcurrent ?
		    NULL : state.def);
	    dopt = state.curopt;
	    doff = pe - line;
	    state.optbeg = state.argbeg = state.inopt = cur;
	    state.argend = argend;
	    state.singles = (!pe || !*pe);

	    for (p = line + 1; p < pe; p++) {
		if ((tmpopt = d->single[STOUC(*p)])) {
		    if (!state.oargs[tmpopt->num])
			state.oargs[tmpopt->num] = znewlinklist();

		    ca_inactive(d, tmpopt->xor, cur, 0);
		}
	    }
	    if (state.def &&
		(state.curopt->type == CAO_DIRECT ||
		 state.curopt->type == CAO_EQUAL ||
		 (state.curopt->type == CAO_ODIRECT && pe[0]) ||
		 (state.curopt->type == CAO_OEQUAL &&
		  (pe[0] || pe[-1] == '=')))) {
		if (state.def->type != CAA_REST &&
		    state.def->type != CAA_RARGS &&
		    state.def->type != CAA_RREST)
		    state.def = state.def->next;

		zaddlinknode(state.oargs[state.curopt->num],
                             ca_opt_arg(state.curopt, line));
	    }
	    if (state.def)
		state.opt = 0;
	    else
		state.curopt = NULL;
	} else if (multi && (*line == '-' || *line == '+') && cur != compcurrent
		&& (ca_foreign_opt(d, all, line)))
	    return 1;
	else if (state.arg &&
		 (!napat || cur <= compcurrent || !pattry(napat, line))) {
	    /* Otherwise it's a normal argument. */
	    if (napat && cur <= compcurrent)
		ca_inactive(d, NULL, cur + 1, 1);

	    arglast = 1;
	    /* if this is the first normal arg after an option, may have been
	     * earlier normal arguments if they're intermixed with options */
	    if (state.inopt) {
		state.inopt = 0;
		state.nargbeg = cur - 1;
		state.argend = argend;
	    }
	    if (!d->args && !d->rest && *line && *line != '-' && *line != '+') {
		if (!multi && cur > compcurrent)
		    break;
		return 1;
	    }
	    if ((adef = state.def = ca_get_arg(d, state.nth)) &&
		(state.def->type == CAA_RREST ||
		 state.def->type == CAA_RARGS)) {

		/* Bart 2009/11/17:
		 * We've reached the "rest" definition.  If at this point
		 * we already found another definition that describes the
		 * current word, use that instead.  If not, prep for the
		 * "narrowing" of scope to only the remaining words.
		 *
		 * We can't test ca_laststate.def in the loop conditions
		 * at the top because this same loop also handles the
		 * ':*PATTERN:MESSAGE:ACTION' form for multiple arguments
		 * after an option, which may need to continue scanning.
		 * There might be an earlier point at which this test can
		 * be made but tracking it down is not worth the effort.
		 */
		if (ca_laststate.def)
		    break;

		state.opt = (cur == state.nargbeg + 1 &&
			     (!multi || !*line || 
			      *line == '-' || *line == '+'));
		state.optbeg = state.nargbeg;
		state.argbeg = cur - 1;
		state.argend = argend;

		for (; line; line = compwords[cur++])
		    zaddlinknode(state.args, ztrdup(line));

		memcpy(&ca_laststate, &state, sizeof(state));
		ca_laststate.ddef = NULL;
		ca_laststate.dopt = NULL;
		break;
	    }
	    zaddlinknode(state.args, ztrdup(line));
            if (adef)
                state.oopt = adef->num - state.nth;

	    if (state.def)
		argxor = state.def->xor;

	    if (state.def && state.def->type != CAA_NORMAL &&
		state.def->type != CAA_OPT && state.inarg) {
		state.restbeg = cur;
		state.inarg = 0;
	    } else if (!state.def || state.def->type == CAA_NORMAL ||
		       state.def->type == CAA_OPT)
		state.inarg = 1;
	    state.nth++;
	    state.def = NULL;
	}
	/* Do the end-pattern test if needed. */

	if (state.def && state.curopt &&
	    (state.def->type == CAA_RREST || state.def->type == CAA_RARGS)) {
	    if (state.def->end)
		endpat = patcompile(state.def->end, 0, NULL);
	    else {
		LinkList l = state.oargs[state.curopt->num];

		if (cur < compcurrent)
		    memcpy(&ca_laststate, &state, sizeof(state));

		for (; line; line = compwords[cur++])
		    zaddlinknode(l, ztrdup(line));

		ca_laststate.ddef = NULL;
		ca_laststate.dopt = NULL;
		break;
	    }
	} else if (state.def && state.def->end)
	    endpat = patcompile(state.def->end, 0, NULL);

	/* Copy the state into the global one. */

    cont:

	if (cur + 1 == compcurrent) {
	    memcpy(&ca_laststate, &state, sizeof(state));
	    ca_laststate.ddef = NULL;
	    ca_laststate.dopt = NULL;
	} else if (cur == compcurrent && !ca_laststate.def) {
	    if ((ca_laststate.def = ddef)) {
		ca_laststate.singles = state.singles;
		if (state.curopt && state.curopt->type == CAO_NEXT) {
		    ca_laststate.ddef = ddef;
		    ca_laststate.dopt = dopt;
		    ca_laststate.def = NULL;
		    ca_laststate.opt = 1;
		    state.curopt->active = 1;
		} else {
		    ca_doff = doff;
		    ca_laststate.opt = 0;
		}
	    } else {
		ca_laststate.def = adef;
		ca_laststate.opt = (!arglast || !multi || !*line || 
				    *line == '-' || *line == '+');
		ca_laststate.ddef = NULL;
		ca_laststate.dopt = NULL;
		ca_laststate.optbeg = state.nargbeg;
		ca_laststate.argbeg = state.restbeg;
		ca_laststate.argend = state.argend;
		ca_laststate.singles = state.singles;
		ca_laststate.oopt = state.oopt;
		if (wasopt)
		    wasopt->active = 1;
	    }
	}
    }
 end:

    ca_laststate.actopts = 0;
    for (ptr = d->opts; ptr; ptr = ptr->next)
	if (ptr->active)
	    ca_laststate.actopts++;

    return 0;
}

/* Build a colon-list from a list.
 *
 * This is only used to populate values of $opt_args.
 */

static char *
ca_colonlist(LinkList l)
{
    if (l) {
	LinkNode n;
	int len = 0;
	char *p, *ret, *q;

	/* Compute the length to be allocated. */
	for (n = firstnode(l); n; incnode(n)) {
	    len++;
	    for (p = (char *) getdata(n); *p; p++)
		len += (*p == ':' || *p == '\\') ? 2 : 1;
	}
	ret = q = (char *) zalloc(len);

	/* Join L into RET, joining with colons and escaping colons and
	 * backslashes. */
	for (n = firstnode(l); n;) {
	    for (p = (char *) getdata(n); *p; p++) {
		if (*p == ':' || *p == '\\')
		    *q++ = '\\';
		*q++ = *p;
	    }
	    incnode(n);
	    if (n)
		*q++ = ':';
	}
	*q = '\0';

	return ret;
    } else
	return ztrdup("");
}

/*
 * This function adds the current set of descriptions, actions,
 * and subcontext descriptions to the given linked list for passing
 * up in comparguments -D and comparguments -L.  opt is the
 * option string (may be NULL if this isn't an option argument) and arg the
 * argument structure (either an option argument or a normal argument
 * as determined by arg->type).
 */

static void
ca_set_data(LinkList descr, LinkList act, LinkList subc,
	    char *opt, Caarg arg, Caopt optdef, int single)
{
    LinkNode dnode, anode;
    char nbuf[40], *buf;
    int restr = 0, onum, miss = 0, rest, oopt = 1, lopt = 0, addopt;

 rec:

    addopt = (opt ? 0 : ca_laststate.oopt);

    for (; arg && (opt || (arg->num < 0 ||
			   (arg->min <= ca_laststate.nth + addopt &&
			    arg->num >= ca_laststate.nth)));) {
	lopt = (arg->type == CAA_OPT);
	if (!opt && !lopt && oopt > 0)
	    oopt = 0;

	for (dnode = firstnode(descr), anode = firstnode(act);
	     dnode; incnode(dnode), incnode(anode))
	    if (!strcmp((char *) getdata(dnode), arg->descr) &&
		!strcmp((char *) getdata(anode), arg->action))
		break;

	/* with an ignored prefix, we're not completing any normal arguments */
	if (single && !arg->opt)
	    return;

	if (!dnode) {
	    addlinknode(descr, arg->descr);
	    addlinknode(act, arg->action);

	    if (!restr) {

		if ((restr = (arg->type == CAA_RARGS)))
		    restrict_range(ca_laststate.optbeg, ca_laststate.argend);
		else if ((restr = (arg->type == CAA_RREST)))
		    restrict_range(ca_laststate.argbeg, ca_laststate.argend);
	    }
	    if (arg->opt) {
		buf = (char *) zhalloc((arg->gsname ? strlen(arg->gsname) : 0) +
				       strlen(arg->opt) + 40);
		if (arg->num > 0 && arg->type < CAA_REST)
		    sprintf(buf, "%soption%s-%d",
			    (arg->gsname ? arg->gsname : ""), arg->opt, arg->num);
		else
		    sprintf(buf, "%soption%s-rest",
			    (arg->gsname ? arg->gsname : ""), arg->opt);
	    } else if (arg->num > 0) {
		sprintf(nbuf, "argument-%d", arg->num);
		buf = (arg->gsname ? dyncat(arg->gsname, nbuf) : dupstring(nbuf));
	    } else
		buf = (arg->gsname ? dyncat(arg->gsname, "argument-rest") :
		       dupstring("argument-rest"));

	    addlinknode(subc, buf);
	}
	/*
	 * If this is an argument to an option, and the option definition says
	 * the argument to the option is required and in the following
	 * (i.e. this) word, then it must match what we've just told it to
	 * match---don't try to match normal arguments.
	 *
	 * This test may be too stringent for what we need, or it
	 * may be too loose; I've simply tweaked it until it gets
	 * the case above right.
	 */
	if (arg->type == CAA_NORMAL &&
	    opt && optdef &&
	    (optdef->type == CAO_NEXT || optdef->type == CAO_ODIRECT ||
	     optdef->type == CAO_OEQUAL))
	    return;

	if (single)
	    break;

	if (!opt) {
	    if (arg->num >= 0 && !arg->next && miss)
		arg = (ca_laststate.d->rest && ca_laststate.d->rest->active ?
		       ca_laststate.d->rest : NULL);
	    else {
		onum = arg->num;
		rest = (onum != arg->min && onum == ca_laststate.nth);
		if ((arg = arg->next)) {
		    if (arg->num != onum + 1)
			miss = 1;
		} else if (rest || (oopt > 0 && !opt)) {
		    arg = (ca_laststate.d->rest && ca_laststate.d->rest->active ?
			   ca_laststate.d->rest : NULL);
		    oopt = -1;
		}
	    }
	} else {
	    if (!lopt)
		break;
	    arg = arg->next;
	}
    }
    if (!single && opt && (lopt || ca_laststate.oopt)) {
	opt = NULL;
	arg = ca_get_arg(ca_laststate.d, ca_laststate.nth);
	goto rec;
    }
    if (!opt && oopt > 0) {
	oopt = -1;
	arg = (ca_laststate.d->rest && ca_laststate.d->rest->active ?
	       ca_laststate.d->rest : NULL);

	goto rec;
    }
}

static int
bin_comparguments(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
{
    int min, max, n;
    Castate lstate = &ca_laststate;

    if (incompfunc != 1) {
	zwarnnam(nam, "can only be called from completion function");
	return 1;
    }
    if (args[0][0] != '-' || !args[0][1] || args[0][2]) {
	zwarnnam(nam, "invalid argument: %s", args[0]);
	return 1;
    }
    if (args[0][1] != 'i' && args[0][1] != 'I' && !ca_parsed) {
	zwarnnam(nam, "no parsed state");
	return 1;
    }
    switch (args[0][1]) {
    case 'i': min = 2; max = -1; break;
    case 'D': min = 3; max =  3; break;
    case 'O': min = 4; max =  4; break;
    case 'L': min = 3; max =  4; break;
    case 's': min = 1; max =  1; break;
    case 'M': min = 1; max =  1; break;
    case 'a': min = 0; max =  0; break;
    case 'W': min = 2; max =  2; break;
    case 'n': min = 1; max =  1; break;
    default:
	zwarnnam(nam, "invalid option: %s", args[0]);
	return 1;
    }
    n = arrlen(args) - 1;
    if (n < min) {
	zwarnnam(nam, "not enough arguments");
	return 1;
    } else if (max >= 0 && n > max) {
	zwarnnam(nam, "too many arguments");
	return 1;
    }
    switch (args[0][1]) {
    case 'i':
        /* This initialises the internal data structures. Arguments are the
         * auto-description string, the optional -s, -S, -A and -M options
         * given to _arguments and the specs. */
	if (compcurrent > 1 && compwords[0]) {
	    Cadef def, all;
	    int cap = ca_parsed, multi, first = 1, use, ret = 0;
	    Castate states = NULL, sp;

	    ca_parsed = 0;

	    if (!(def = all = get_cadef(nam, args + 1)))
		return 1;

	    multi = !!def->snext; /* if we have sets */
	    ca_parsed = cap;
	    ca_doff = 0;

	    while (def) { /* for each set */
		use = !ca_parse_line(def, all, multi, first);
		def = def->snext;
		if (use && def) {
		    /* entry needed so save it into list */
		    sp = (Castate) zalloc(sizeof(*sp));
		    memcpy(sp, &ca_laststate, sizeof(*sp));
		    sp->snext = states;
		    states = sp;
		} else if (!use && !def) {
		    /* final entry not needed */
		    if (states) {
			freecastate(&ca_laststate);
			memcpy(&ca_laststate, states, sizeof(*sp));
			sp = states->snext;
			zfree(states, sizeof(*states));
			states = sp;
		    } else
			ret = 1;
		}
		first = 0;
	    }
	    ca_parsed = 1;
	    ca_laststate.snext = states;

	    return ret;
	}
	return 1;

    case 'D':
        /* This returns the descriptions, actions and sub-contexts for the
         * things _arguments has to execute at this place on the line (the
         * sub-contexts are used as tags).
         * The return value is particularly important here, it says if 
         * there are arguments to complete at all. */
	{
	    LinkList descr, act, subc;
	    Caarg arg;
	    int ret = 1;

	    descr = newlinklist();
	    act = newlinklist();
	    subc = newlinklist();

	    ignore_prefix(ca_doff);
	    while (lstate) {
		arg = lstate->def;

		if (arg) {
		    ret = 0;
		    ca_set_data(descr, act, subc, arg->opt, arg,
				lstate->curopt, (ca_doff > 0));
		}
		lstate = lstate->snext;
	    }
	    if (!ret) {
		set_list_array(args[1], descr);
		set_list_array(args[2], act);
		set_list_array(args[3], subc);
	    }
	    return ret;
	}
    case 'O':
        /* This returns the descriptions for the options in the arrays whose
         * names are given as arguments.  The descriptions are strings in a
         * form usable by _describe.  The return value says if there are any
         * options to be completed. */
	{
	    LinkList next = newlinklist();
	    LinkList direct = newlinklist();
	    LinkList odirect = newlinklist();
	    LinkList equal = newlinklist(), l;
            LinkNode node;
	    Caopt p;
	    char *str;
	    int ret = 1;

	    for (; lstate; lstate = lstate->snext) {
		if (lstate->actopts &&
		    (lstate->opt || (ca_doff && lstate->def) ||
		     (lstate->def && lstate->def->opt &&
		      (lstate->def->type == CAA_OPT ||
		       (lstate->def->type >= CAA_RARGS &&
			lstate->def->num < 0)))) &&
		    (!lstate->def || lstate->def->type < CAA_RARGS ||
		     (lstate->def->type == CAA_RARGS ?
		      (lstate->curpos == lstate->argbeg + 1) :
		      (compcurrent == 1)))) {
		    ret = 0;
		    for (p = lstate->d->opts; p; p = p->next) {
			if (p->active && !p->not) {
			    switch (p->type) {
			    case CAO_NEXT:    l = next;    break;
			    case CAO_DIRECT:  l = direct;  break;
			    case CAO_ODIRECT: l = odirect; break;
			    default:          l = equal;   break;
			    }
			    if (p->descr) {
				char *n = bslashcolon(p->name);
				int len = strlen(n) + strlen(p->descr) + 2;

				str = (char *) zhalloc(len);
				strcpy(str, n);
				strcat(str, ":");
				strcat(str, p->descr);
			    } else
				str = bslashcolon(p->name);

                            for (node = firstnode(l); node; incnode(node))
                                if (!strcmp(str, (char *) getdata(node)))
                                    break;

                            if (!node)
                                addlinknode(l, str);
			}
		    }
		}
	    }
	    if (!ret) {
		set_list_array(args[1], next);
		set_list_array(args[2], direct);
		set_list_array(args[3], odirect);
		set_list_array(args[4], equal);

		return 0;
	    }
	    return (ca_laststate.singles ? 2 : 1);
	}
    case 'L':
        /* This tests if the beginning of the current word matches an option.
         * It is for cases like `./configure --pre=/<TAB>' which should
         * complete to `--prefix=/...'.  The options name isn't fully typed
         * and _arguments finds out that there is no option `--pre' and that
         * it should complete some argument to an option.  It then uses -L
         * to find the option the argument is for. */
	{
	    LinkList descr, act, subc;
	    Caopt opt;
	    int ret = 1;

	    descr = newlinklist();
	    act = newlinklist();
	    subc = newlinklist();

	    while (lstate) {
		opt = ca_get_opt(lstate->d, args[1], 1, NULL);

		if (opt && opt->args) {
		    ret = 0;
		    ca_set_data(descr, act, subc, opt->name, opt->args, opt, 1);
		}
		lstate = lstate->snext;
	    }
	    if (!ret) {
		set_list_array(args[2], descr);
		set_list_array(args[3], act);
		set_list_array(args[4], subc);
	    }
	    return ret;
	}
    case 's':
        /* This returns zero if we are completing single letter options.
         * It also uses its argument as the name of a parameter and sets
         * that to a string describing the argument behaviour of the last
         * option in the current word so that we can get the auto-suffix
         * right. */
	for (; lstate; lstate = lstate->snext)
	    if (lstate->d->single && lstate->singles &&
		lstate->actopts
#if 0
                /* let's try without, for the -W option of _arguments */
                && lstate->opt
#endif
                ) {
		setsparam(args[1],
			  ztrdup((lstate->ddef && lstate->dopt) ?
				 (lstate->dopt->type == CAO_DIRECT ?
				  "direct" :
				  ((lstate->dopt->type == CAO_OEQUAL ||
				    lstate->dopt->type == CAO_EQUAL) ?
				   "equal" : "next")) : ""));
		return 0;
	    }
	return 1;
    case 'M':
        /* This returns the match specs defined for the set of specs we are
         * using.  Returned, as usual in a parameter whose name is given as
         * the argument. */
	setsparam(args[1], ztrdup(ca_laststate.d->match));
	return 0;
    case 'a':
        /* This just sets the return value.  To zero if there would be or
         * were any normal arguments to be completed.  Used to decide if
         * _arguments should say `no arguments' or `no more arguments'. */
	for (; lstate; lstate = lstate->snext)
	    if (lstate->d->args || lstate->d->rest)
		return 0;
	return 1;
    case 'W':
        /* This gets two parameter names as arguments.  The first is set to
         * the current word sans any option prefixes handled by comparguments.
         * The second parameter is set to an array containing the options on
         * the line and their arguments.  I.e. the stuff _arguments returns
         * to its caller in the `line' and `opt_args' parameters. */
	{
	    Castate s;
	    char **ret, **p;
	    LinkNode n;
	    LinkList *a;
	    Caopt o;
	    int num;

	    for (num = 0, s = lstate; s; s = s->snext)
		num += countlinknodes(s->args);

	    ret = p = zalloc((num + 1) * sizeof(char *));

	    for (s = lstate; s; s = s->snext)
		for (n = firstnode(s->args); n; incnode(n))
		    *p++ = ztrdup((char *) getdata(n));
	    *p = NULL;

	    setaparam(args[1], ret);

	    for (num = 0, s = lstate; s; s = s->snext)
		for (o = s->d->opts, a = s->oargs; o; o = o->next, a++)
		    if (*a)
			num += 2;

	    ret = p = zalloc((num + 1) * sizeof(char *));

	    for (s = lstate; s; s = s->snext)
		for (o = s->d->opts, a = s->oargs; o; o = o->next, a++)
		    if (*a) {
			*p++ = (o->gsname ? tricat(o->gsname, o->name, "") :
				ztrdup(o->name));
			*p++ = ca_colonlist(*a);
		    }
	    *p = NULL;

	    sethparam(args[2], ret);
	}
	return 0;
    case 'n':
	/*
	 * This returns the array index of the word where normal
	 * arguments began.  It uses optbeg rather than nargbeg
	 * (the value used when parsing) because nargbeg is assigned
	 * to optbeg in the returned value and nargbeg isn't
	 * used.
	 *
	 * -->PLEASE DON'T ASK<--
	 *
	 * Thank you.
	 */
	setiparam(args[1], (zlong)ca_laststate.optbeg + !isset(KSHARRAYS));
	return 0;
    }
    return 1;
}

/* Help for `_values'. */

typedef struct cvdef *Cvdef;
typedef struct cvval *Cvval;

/* Definitions for _values. */

struct cvdef {
    char *descr;		/* global description */
    int hassep;			/* multiple values allowed */
    char sep;			/* separator character */
    char argsep;                /* argument separator */
    Cvdef next;			/* next in cache */
    Cvval vals;			/* value definitions */
    char **defs;		/* original strings */
    int ndefs;			/* number of ... */
    int lastt;			/* last time used */
    int words;                  /* if to look at other words */
};

/* One value definition. */

struct cvval {
    Cvval next;
    char *name;			/* value name */
    char *descr;		/* description */
    char **xor;			/* xor-list */
    int type;			/* CVV_* below */
    Caarg arg;			/* argument definition */
    int active;			/* still allowed */
};

#define CVV_NOARG 0
#define CVV_ARG   1
#define CVV_OPT   2

/* Cache. */

#define MAX_CVCACHE 8
static Cvdef cvdef_cache[MAX_CVCACHE];

/* Memory stuff. */

static void
freecvdef(Cvdef d)
{
    if (d) {
	Cvval p, n;

	zsfree(d->descr);
	if (d->defs)
	    freearray(d->defs);

	for (p = d->vals; p; p = n) {
	    n = p->next;
	    zsfree(p->name);
	    zsfree(p->descr);
	    if (p->xor)
		freearray(p->xor);
	    freecaargs(p->arg);
	    zfree(p, sizeof(*p));
	}
	zfree(d, sizeof(*d));
    }
}

/* Parse option definitions. */

static Cvdef
parse_cvdef(char *nam, char **args)
{
    Cvdef ret;
    Cvval val, *valp;
    Caarg arg;
    char **oargs = args, sep = '\0', asep = '=', *name, *descr, *p, *q, **xor, c;
    int xnum, multi, vtype, hassep = 0, words = 0;

    while (args && args[0] && args[1] &&
           args[0][0] == '-' &&
           (args[0][1] == 's' || args[0][1] == 'S' || args[0][1] == 'w') &&
           !args[0][2]) {

        if (args[0][1] == 's') {
            hassep = 1;
            sep = args[1][0];
            args += 2;
        } else if (args[0][1] == 'S') {
            asep = args[1][0];
            args += 2;
        } else {
            words = 1;
            args++;
        }
    }
    if (!args[0] || !args[1]) {
	zwarnnam(nam, "not enough arguments");
	return NULL;
    }
    descr = *args++;

    ret = (Cvdef) zalloc(sizeof(*ret));
    ret->descr = ztrdup(descr);
    ret->hassep = hassep;
    ret->sep = sep;
    ret->argsep = asep;
    ret->next = NULL;
    ret->vals = NULL;
    ret->defs = zarrdup(oargs);
    ret->ndefs = arrlen(oargs);
    ret->lastt = time(0);
    ret->words = words;

    for (valp = &(ret->vals); *args; args++) {
	int bs = 0;
	p = dupstring(*args);
	xnum = 0;

	/* xor list? */
	if (*p == '(') {
	    LinkList list = newlinklist();
	    LinkNode node;
	    char **xp, sav;

	    while (*p && *p != ')') {
		for (p++; inblank(*p); p++);

		if (*p == ')')
		    break;
		for (q = p++; *p && *p != ')' && !inblank(*p); p++);

		if (!*p)
		    break;

		sav = *p;
		*p = '\0';
		addlinknode(list, dupstring(q));
		xnum++;
		*p = sav;
	    }
	    if (*p != ')') {
		freecvdef(ret);
		zwarnnam(nam, "invalid argument: %s", *args);
		return NULL;
	    }
	    xor = (char **) zalloc((xnum + 2) * sizeof(char *));
	    for (node = firstnode(list), xp = xor; node; incnode(node), xp++)
		*xp = ztrdup((char *) getdata(node));
	    xp[0] = xp[1] = NULL;

	    p++;
	} else
	    xor = NULL;

	/* More than once allowed? */
	if ((multi = (*p == '*')))
	    p++;

	/* Skip option name. */

	for (name = p; *p && *p != ':' && *p != '['; p++)
	    if (*p == '\\' && p[1])
		p++, bs = 1;

	if (hassep && !sep && name + bs + 1 < p) {
	    freecvdef(ret);
	    if (xor) freearray(xor);
	    zwarnnam(nam, "no multi-letter values with empty separator allowed");
	    return NULL;
	}
	/* Optional description? */

	if ((c = *p) == '[') {
	    *p = '\0';
	    for (descr = ++p; *p && *p != ']'; p++)
		if (*p == '\\' && p[1])
		    p++;

	    if (!*p) {
		freecvdef(ret);
		if (xor) freearray(xor);
		zwarnnam(nam, "invalid value definition: %s", *args);
		return NULL;
	    }
	    *p++ = '\0';
	    c = *p;
	} else {
	    *p = '\0';
	    descr = NULL;
	}
	if (c && c != ':') {
	    freecvdef(ret);
	    if (xor) freearray(xor);
	    zwarnnam(nam, "invalid value definition: %s", *args);
	    return NULL;
	}
	/* Get argument? */

	if (c == ':') {
	    if (hassep && !sep) {
		freecvdef(ret);
		if (xor) freearray(xor);
		zwarnnam(nam, "no value with argument with empty separator allowed");
		return NULL;
	    }
	    if (*++p == ':') {
		p++;
		vtype = CVV_OPT;
	    } else
		vtype = CVV_ARG;
	    arg = parse_caarg(0, 0, 0, 0, name, &p, NULL);
	} else {
	    vtype = CVV_NOARG;
	    arg = NULL;
	}
	if (!multi) {
	    if (!xor) {
		xor = (char **) zalloc(2 * sizeof(char *));
		xor[1] = NULL;
	    }
	    xor[xnum] = ztrdup(name);
	}
	*valp = val = (Cvval) zalloc(sizeof(*val));
	valp = &((*valp)->next);

	val->next = NULL;
	val->name = ztrdup(name);
	val->descr = ztrdup(descr);
	val->xor = xor;
	val->type = vtype;
	val->arg = arg;
    }
    return ret;
}

/* Get the definition from the cache or newly built. */

static Cvdef
get_cvdef(char *nam, char **args)
{
    Cvdef *p, *min, new;
    int i, na = arrlen(args);

    for (i = MAX_CVCACHE, p = cvdef_cache, min = NULL; *p && i--; p++)
	if (*p && na == (*p)->ndefs && arrcmp(args, (*p)->defs)) {
	    (*p)->lastt = time(0);

	    return *p;
	} else if (!min || !*p || (*p)->lastt < (*min)->lastt)
	    min = p;
    if (i > 0)
	min = p;
    if ((new = parse_cvdef(nam, args))) {
	freecvdef(*min);
	*min = new;
    }
    return new;
}

/* Get the definition for a value. */

static Cvval
cv_get_val(Cvdef d, char *name)
{
    Cvval p;

    for (p = d->vals; p; p = p->next)
	if (!strcmp(name, p->name))
	    return p;

    return NULL;
}

static Cvval
cv_quote_get_val(Cvdef d, char *name)
{
    int ne;

    /* remove quotes */
    name = dupstring(name);
    ne = noerrs;
    noerrs = 2;
    parse_subst_string(name);
    noerrs = ne;
    remnulargs(name);
    untokenize(name);

    return cv_get_val(d, name);
}

/* Handle a xor list. */

static void
cv_inactive(Cvdef d, char **xor)
{
    if (xor) {
	Cvval val;

	for (; *xor; xor++)
	    if ((val = cv_get_val(d, *xor)))
		val->active = 0;
    }
}

/* Parse state. */

struct cvstate {
    Cvdef d;
    Caarg def;
    Cvval val;
    LinkList vals;
};

static struct cvstate cv_laststate;
static int cv_parsed = 0, cv_alloced = 0;

/* Get the next value in the string.  Return it's definition and update the
 * sp pointer to point to the end of the value (plus argument, if any).
 * If there is no next value, the string pointer is set to null.  In any
 * case ap will point to the beginning of the argument or will be a null
 * pointer if there is no argument.
 */

static Cvval
cv_next(Cvdef d, char **sp, char **ap)
{
    Cvval r = NULL;
    char *s = *sp;

    if (!*s) {
        *sp = *ap = NULL;

        return NULL;
    }
    if ((d->hassep && !d->sep) || !d->argsep) {
        char sav, ec, *v = s, *os;

        ec = ((d->hassep && d->sep) ? d->sep : d->argsep);

        do {
            sav = *++s;
            *s = '\0';
            if ((r = cv_quote_get_val(d, v))) {
                *s = sav;

                break;
            }
            *s = sav;
        } while (*s && *s != ec);

        os = s;

        if (d->hassep && d->sep) {
            if ((s = strchr(s, d->sep)))
                *sp = s + 1;
            else
                *sp = NULL;
        } else
            *sp = s;
        if (d->argsep && *os == d->argsep) {
            *ap = os + 1;
            *sp = NULL;
        } else if (r && r->type != CVV_NOARG)
            *ap = os;
        else
            *ap = NULL;

        return r;

    } else if (d->hassep) {
        char *ns = strchr(s, d->sep), *as = 0, *sap, sav = 0;
        int skip = 0;

        if (d->argsep && (as = strchr(s, d->argsep)) && (!ns || as <= ns)) {
            *ap = as + 1;
            ns = strchr(as + 1, d->sep);
            skip = 1;
            sap = as;
        } else {
            *ap = NULL;
            sap = ns;
        }
        if (sap) {
            sav = *sap;
            *sap = '\0';
        }
        if ((!(r = cv_quote_get_val(d, s)) || r->type == CVV_NOARG) && skip)
            ns = as;

        if (sap)
            *sap = sav;

        *sp = ((!ns || (ns == as && r && r->type != CVV_NOARG)) ? NULL : ns + 1);

        return r;
    } else {
        char *as = strchr(s, d->argsep), *sap, sav = 0;

        *sp = NULL;

        if (as) {
            *ap = as + 1;
            sap = as;
            sav = *as;
            *sap = '\0';
        } else
            *ap = sap = NULL;

        r = cv_quote_get_val(d, s);

        if (sap)
            *sap = sav;

        return r;
    }
}

/* Parse the current word. */

static void
cv_parse_word(Cvdef d)
{
    Cvval val;
    struct cvstate state;
    char *str, *arg = NULL, *pign = compprefix;
    int nosfx = 0;

    if (cv_alloced)
	freelinklist(cv_laststate.vals, freestr);

    for (val = d->vals; val; val = val->next)
	val->active = 1;

    state.d = d;
    state.def = NULL;
    state.val = NULL;
    state.vals = (LinkList) znewlinklist();

    cv_alloced = 1;

    if (d->words && compwords[0]) {
        int i;

        for (i = 1; compwords[i]; i++)
            if (i != compcurrent - 1)
                for (str = compwords[i]; str && *str; ) {
                    if ((val = cv_next(d, &str, &arg))) {
                        zaddlinknode(state.vals, ztrdup(val->name));
                        if (arg) {
                            char sav = '\0';

                            if (str) {
                                sav = str[-1];
                                str[-1] = '\0';
                            }
                            zaddlinknode(state.vals, ztrdup(arg));
                            if (str)
                                str[-1] = sav;
                        } else
                            zaddlinknode(state.vals, ztrdup(""));

                        if (i + 1 < compcurrent)
                            cv_inactive(d, val->xor);
                    }
                }

        val = NULL;
        arg = NULL;
    }
    for (str = compprefix; str && *str; ) {
        if ((val = cv_next(d, &str, &arg))) {
            zaddlinknode(state.vals, ztrdup(val->name));
            if (arg) {
                if (str) {
                    char sav = str[-1];

                    str[-1] = '\0';
                    zaddlinknode(state.vals, ztrdup(arg));
                    str[-1] = sav;
                } else {
                    zaddlinknode(state.vals, tricat(arg, compsuffix, ""));
                    nosfx = 1;
                }
            } else
                zaddlinknode(state.vals, ztrdup(""));

            cv_inactive(d, val->xor);

            if (str)
                pign = str;
            else
                val->active = 1;
        }
    }
    state.val = val;
    if (val && arg && !str)
        state.def = val->arg;

    if (!nosfx && d->hassep) {
        int ign = 0;
        char *more = NULL;

        ignore_prefix(pign - compprefix);

        if (!d->sep && (!val || val->type == CVV_NOARG)) {
            ign = strlen(compsuffix);
            more = compsuffix;
        } else {
            if (d->sep) {
                char *ns = strchr(compsuffix, d->sep), *as;

                if (d->argsep && (as = strchr(compsuffix, d->argsep)) &&
                    (!ns || as <= ns)) {
                    ign = strlen(as);
                } else
                    ign = (ns ? strlen(ns) : 0);

                more = (ns ? ns + 1 : NULL);
            } else if (d->argsep) {
                char *as;

                if ((as = strchr(compsuffix, d->argsep)))
                    ign = strlen(as);
            }
        }
        more = dupstring(more);

        if (ign)
            ignore_suffix(ign);

        while (more && *more) {
            if ((val = cv_next(d, &more, &arg))) {
                zaddlinknode(state.vals, ztrdup(val->name));
                if (arg) {
                    if (more) {
                        char sav = more[-1];

                        more[-1] = '\0';
                        zaddlinknode(state.vals, ztrdup(arg));
                        more[-1] = sav;
                    } else {
                        zaddlinknode(state.vals, tricat(arg, compsuffix, ""));
                        nosfx = 1;
                    }
                } else
                    zaddlinknode(state.vals, ztrdup(""));

                cv_inactive(d, val->xor);
            }
        }
    } else if (arg)
        ignore_prefix(arg - compprefix);
    else
        ignore_prefix(pign - compprefix);

    memcpy(&cv_laststate, &state, sizeof(state));
}

static int
bin_compvalues(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
{
    int min, max, n;

    if (incompfunc != 1) {
	zwarnnam(nam, "can only be called from completion function");
	return 1;
    }
    if (args[0][0] != '-' || !args[0][1] || args[0][2]) {
	zwarnnam(nam, "invalid argument: %s", args[0]);
	return 1;
    }
    if (args[0][1] != 'i' && !cv_parsed) {
	zwarnnam(nam, "no parsed state");
	return 1;
    }
    switch (args[0][1]) {
    case 'i': min = 2; max = -1; break;
    case 'D': min = 2; max =  2; break;
    case 'C': min = 1; max =  1; break;
    case 'V': min = 3; max =  3; break;
    case 's': min = 1; max =  1; break;
    case 'S': min = 1; max =  1; break;
    case 'd': min = 1; max =  1; break;
    case 'L': min = 3; max =  4; break;
    case 'v': min = 1; max =  1; break;
    default:
	zwarnnam(nam, "invalid option: %s", args[0]);
	return 1;
    }
    n = arrlen(args) - 1;
    if (n < min) {
	zwarnnam(nam, "not enough arguments");
	return 1;
    } else if (max >= 0 && n > max) {
	zwarnnam(nam, "too many arguments");
	return 1;
    }
    switch (args[0][1]) {
    case 'i':
        /* This initialises the internal data structures.  The arguments are
         * just the arguments that were given to _values itself. */
	{
	    Cvdef def = get_cvdef(nam, args + 1);
	    int cvp = cv_parsed;

	    cv_parsed = 0;

	    if (!def)
		return 1;

	    cv_parsed = cvp;
	    cv_parse_word(def);
	    cv_parsed = 1;

	    return 0;
	}

    case 'D':
        /* This returns the description and action to use if we are at
         * a place where some action has to be used at all.  In that case
         * zero is returned and non-zero otherwise. */
	{
	    Caarg arg = cv_laststate.def;

	    if (arg) {
		setsparam(args[1], ztrdup(arg->descr));
		setsparam(args[2], ztrdup(arg->action));

		return 0;
	    }
	    return 1;
	}
    case 'C':
        /* This returns the sub-context (i.e.: the tag) to use when executing
         * an action. */
	{
	    Caarg arg = cv_laststate.def;

	    if (arg) {
		setsparam(args[1], ztrdup(arg->opt));

		return 0;
	    }
	    return 1;
	}
    case 'V':
        /* This is what -O is for comparguments: it returns (in three arrays)
         * the values for values without arguments, with arguments and with
         * optional arguments (so that we can get the auto-suffixes right).
         * As for comparguments, the strings returned are usable for _describe. */
	{
	    LinkList noarg = newlinklist();
	    LinkList arg = newlinklist();
	    LinkList opt = newlinklist(), l;
	    Cvval p;
	    char *str;

	    for (p = cv_laststate.d->vals; p; p = p->next) {
		if (p->active) {
		    switch (p->type) {
		    case CVV_NOARG: l = noarg; break;
		    case CVV_ARG:   l = arg;   break;
		    default:        l = opt;   break;
		    }
		    if (p->descr) {
			int len = strlen(p->name) + strlen(p->descr) + 2;

			str = (char *) zhalloc(len);
			strcpy(str, p->name);
			strcat(str, ":");
			strcat(str, p->descr);
		    } else
			str = p->name;
		    addlinknode(l, str);
		}
	    }
	    set_list_array(args[1], noarg);
	    set_list_array(args[2], arg);
	    set_list_array(args[3], opt);

	    return 0;
	}
    case 's':
        /* This returns the value separator, if any, and sets the return
         * value to say if there is such a separator. */
	if (cv_laststate.d->hassep) {
	    char tmp[2];

	    tmp[0] = cv_laststate.d->sep;
	    tmp[1] = '\0';
	    setsparam(args[1], ztrdup(tmp));

	    return 0;
	}
	return 1;
    case 'S':
        /* Like -s, but for the separator between values and their arguments. */
	{
	    char tmp[2];

	    tmp[0] = cv_laststate.d->argsep;
	    tmp[1] = '\0';
	    setsparam(args[1], ztrdup(tmp));
	}
	return 0;
    case 'd':
        /* This returns the description string (first argument to _values)
         * which is passed down to _describe. */
	setsparam(args[1], ztrdup(cv_laststate.d->descr));
	return 0;
    case 'L':
        /* Almost the same as for comparguments.  This gets a value name
         * and returns the description and action of its first argument, if
         * any.  The rest (prefix matching) is in _values.  Return non-zero
         * if there is no such option. */
	{
	    Cvval val = cv_get_val(cv_laststate.d, args[1]);

	    if (val && val->arg) {
		setsparam(args[2], ztrdup(val->arg->descr));
		setsparam(args[3], ztrdup(val->arg->action));

		if (args[4])
		    setsparam(args[4], ztrdup(val->name));

		return 0;
	    }
	    return 1;
	}
    case 'v':
        /* Again, as for comparguments.  This returns the values and their
         * arguments as an array which will be stored in val_args in _values. */
	if (cv_laststate.vals) {
	    char **ret;

	    ret = zlinklist2array(cv_laststate.vals);
	    sethparam(args[1], ret);

	    return 0;
	}
	return 1;
    }
    return 1;
}

static char *
comp_quote(char *str, int prefix)
{
    int x;
    char *ret;

    if ((x = (prefix && *str == '=')))
	*str = 'x';

    ret = quotestring(str, *compqstack);

    if (x)
	*str = *ret = '=';

    return ret;
}

static int
bin_compquote(char *nam, char **args, Options ops, UNUSED(int func))
{
    char *name;
    struct value vbuf;
    Value v;

    if (incompfunc != 1) {
	zwarnnam(nam, "can only be called from completion function");
	return 1;
    }
    /* Anything to do? */

    if (!compqstack || !*compqstack)
	return 0;

    /* For all parameters given... */

    while ((name = *args++)) {
	name = dupstring(name);
	queue_signals();
	if ((v = getvalue(&vbuf, &name, 0))) {
	    switch (PM_TYPE(v->pm->node.flags)) {
	    case PM_SCALAR:
		setstrvalue(v, ztrdup(comp_quote(getstrvalue(v), 
						 OPT_ISSET(ops,'p'))));
		break;
	    case PM_ARRAY:
		{
		    char **val = v->pm->gsu.a->getfn(v->pm);
		    char **new = (char **) zalloc((arrlen(val) + 1) *
						  sizeof(char *));
		    char **p = new;

		    for (; *val; val++, p++)
			*p = ztrdup(comp_quote(*val, OPT_ISSET(ops,'p')));
		    *p = NULL;

		    setarrvalue(v, new);
		}
		break;
	    default:
		zwarnnam(nam, "invalid parameter type: %s", args[-1]);
	    }
	} else
	    zwarnnam(nam, "unknown parameter: %s", args[-1]);
	unqueue_signals();
    }
    return 0;
}

/* Tags stuff. */

typedef struct ctags *Ctags;
typedef struct ctset *Ctset;

/* A bunch of tag sets. */

struct ctags {
    char **all;			/* all tags offered */
    char *context;		/* the current context */
    int init;			/* not yet used */
    Ctset sets;			/* the tag sets */
};

/* A tag set. */

struct ctset {
    Ctset next;
    char **tags;		/* the tags */
    char *tag;			/* last tag checked for -A */
    char **ptr;			/* ptr into tags for -A */
};

/* Array of tag-set infos. Index is the locallevel. */

#define MAX_TAGS 256
static Ctags comptags[MAX_TAGS];

/* locallevel at last comptags -i */

static int lasttaglevel;

static void
freectset(Ctset s)
{
    Ctset n;

    while (s) {
	n = s->next;

	if (s->tags)
	    freearray(s->tags);
	zsfree(s->tag);
	zfree(s, sizeof(*s));

	s = n;
    }
}

static void
freectags(Ctags t)
{
    if (t) {
	if (t->all)
	    freearray(t->all);
	zsfree(t->context);
	freectset(t->sets);
	zfree(t, sizeof(*t));
    }
}

/* Set the tags for the current local level. */

static void
settags(int level, char **tags)
{
    Ctags t;

    if (comptags[level])
	freectags(comptags[level]);

    comptags[level] = t = (Ctags) zalloc(sizeof(*t));

    t->all = zarrdup(tags + 1);
    t->context = ztrdup(*tags);
    t->sets = NULL;
    t->init = 1;
}

/* Check if an array contains a string. */

/**/
static int
arrcontains(char **a, char *s, int colon)
{
    char *p, *q;

    while (*a) {
	if (colon) {
	    for (p = s, q = *a++; *p && *q && *p != ':' && *q != ':'; p++, q++)
		if (*p != *q)
		    break;
	    if ((!*p || *p == ':') && (!*q || *q == ':'))
		return 1;
	} else if (!strcmp(*a++, s))
	    return 1;
    }
    return 0;
}

static int
bin_comptags(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
{
    int min, max, n, level;

    if (incompfunc != 1) {
	zwarnnam(nam, "can only be called from completion function");
	return 1;
    }
    if (args[0][0] != '-' || !args[0][1] ||
	(args[0][2] && (args[0][2] != '-' || args[0][3]))) {
	zwarnnam(nam, "invalid argument: %s", args[0]);
	return 1;
    }
    level = locallevel - (args[0][2] ? 1 : 0);
    if (level >= MAX_TAGS) {
	zwarnnam(nam, "nesting level too deep");
	return 1;
    }
    if (args[0][1] != 'i' && args[0][1] != 'I' && !comptags[level]) {
	zwarnnam(nam, "no tags registered");
	return 1;
    }
    switch (args[0][1]) {
    case 'i': min = 2; max = -1; break;
    case 'C': min = 1; max =  1; break;
    case 'T': min = 0; max =  0; break;
    case 'N': min = 0; max =  0; break;
    case 'R': min = 1; max =  1; break;
    case 'S': min = 1; max =  1; break;
    case 'A': min = 2; max =  3; break;
    default:
	zwarnnam(nam, "invalid option: %s", args[0]);
	return 1;
    }
    n = arrlen(args) - 1;
    if (n < min) {
	zwarnnam(nam, "not enough arguments");
	return 1;
    } else if (max >= 0 && n > max) {
	zwarnnam(nam, "too many arguments");
	return 1;
    }
    switch (args[0][1]) {
    case 'i':
	settags(level, args + 1);
	lasttaglevel = level;
	break;
    case 'C':
	setsparam(args[1], ztrdup(comptags[level]->context));
	break;
    case 'T':
	return !comptags[level]->sets;
    case 'N':
	{
	    Ctset s;

	    if (comptags[level]->init)
		comptags[level]->init = 0;
	    else if ((s = comptags[level]->sets)) {
		comptags[level]->sets = s->next;
		s->next = NULL;
		freectset(s);
	    }
	    return !comptags[level]->sets;
	}
    case 'R':
	{
	    Ctset s;

	    return !((s = comptags[level]->sets) &&
		     arrcontains(s->tags, args[1], 1));
	}
    case 'A':
	{
	    Ctset s;

	    if (comptags[level] && (s = comptags[level]->sets)) {
		char **q, *v = NULL;
		int l = strlen(args[1]);

		if (!s->tag || strcmp(s->tag, args[1])) {
		    zsfree(s->tag);
		    s->tag = ztrdup(args[1]);
		    s->ptr = s->tags;
		}
		for (q = s->ptr; *q; q++) {
		    if (strpfx(args[1], *q)) {
			if (!(*q)[l]) {
			    v = *q;
			    break;
			} else if ((*q)[l] == ':') {
			    v = (*q) + l + 1;
			    break;
			}
		    }
		}
		if (!v) {
		    zsfree(s->tag);
		    s->tag = NULL;
		    return 1;
		}
		s->ptr = q + 1;
		setsparam(args[2], ztrdup(*v == '-' ? dyncat(args[1], v) : v));
		if (args[3]) {
		    char *r = dupstring(*q), *p;

		    for (p = r + (v - *q); *p && *p != ':'; p++);
		    *p = '\0';

		    setsparam(args[3], ztrdup(r));
		}
		return 0;
	    }
	    return 1;
	}
    case 'S':
	if (comptags[level]->sets) {
	    char **ret;

	    ret = zarrdup(comptags[level]->sets->tags);
	    setaparam(args[1], ret);
	} else
	    return 1;

	break;
    }
    return 0;
}

static int
bin_comptry(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
{
    if (incompfunc != 1) {
	zwarnnam(nam, "can only be called from completion function");
	return 1;
    }
    if (!lasttaglevel || !comptags[lasttaglevel]) {
	zwarnnam(nam, "no tags registered");
	return 1;
    }
    if (*args) {
	if (!strcmp(*args, "-m")) {
	    char *s, *p, *q, *c, **all = comptags[lasttaglevel]->all;
	    LinkList list = newlinklist();
	    int num = 0;
	    Ctset set;

	    while ((s = *++args)) {
		while (*s) {
		    while (*s && iblank(*s))
			s++;
		    for (p = q = s, c = NULL; *s && !inblank(*s); s++) {
			if (!c && *s == ':')
			    c = p;
			if (*s == '\\' && s[1])
			    s++;
			*p++ = *s;
		    }
		    if (*s)
			s++;
		    *p = '\0';
		    if (*q) {
			char *qq, *qqq;

			queue_signals();

			if (c)
			    *c = '\0';

			qqq = qq = dupstring(q);
			while (*qqq) {
			    if (*qqq == '\\' && qqq[1])
				qqq++;
			    else if (*qqq == '{')
				*qqq = Inbrace;
			    else if (*qqq == '}')
				*qqq = Outbrace;
			    else if (*qqq == ',')
				*qqq = Comma;
			    qqq++;
			}
			tokenize(qq);
			if (haswilds(qq) || hasbraces(qq)) {
			    Patprog prog;
			    LinkNode bnode, node;
			    LinkList blist = newlinklist();

			    addlinknode(blist, qq);
			    for (bnode = firstnode(blist); bnode; incnode(bnode))
				while (hasbraces(getdata(bnode)))
				    xpandbraces(blist, &bnode);

			    for (bnode = firstnode(blist); bnode; incnode(bnode)) {
				qq = (char *) getdata(bnode);
				if ((prog = patcompile(qq, PAT_STATIC, NULL))) {
				    char **a, *n;
				    int l = (c ? strlen(c + 1) + 2 : 1), al;

				    for (a = all; *a; a++) {
					for (node = firstnode(list); node;
					     incnode(node)) {
					    char *as, *ls;

					    for (as = *a, ls = (char *) getdata(node);
						 *as && *ls && *ls != ':'; as++, ls++)
						if (*as != *ls)
						    break;
					    if (!*as && (!*ls || *ls == ':'))
						break;
					}
					if (node)
					    continue;
					if (pattry(prog, *a)) {
					    n = (char *) zhalloc((al = strlen(*a)) + l);
					    strcpy(n, *a);
					    if (c) {
						n[al] = ':';
						strcpy(n + al + 1, c + 1);
					    }
					    addlinknode(list, n);
					    num++;
					}
				    }
				}
			    }
			} else if (arrcontains(all, q, 0)) {
			    for (set = comptags[lasttaglevel]->sets; set;
				 set = set->next)
				if (arrcontains(set->tags, q, 0))
				    break;
			    if (!set) {
				addlinknode(list, q);
				num++;
			    }
			}
			if (c)
			    *c = ':';

			unqueue_signals();
		    }
		}
		if (num) {
		    Ctset l;

		    set = (Ctset) zalloc(sizeof(*set));

		    set->tags = zlinklist2array(list);
		    set->next = NULL;
		    set->ptr = NULL;
		    set->tag = NULL;

		    if ((l = comptags[lasttaglevel]->sets)) {
			while (l->next)
			    l = l->next;

			l->next = set;
		    } else
			comptags[lasttaglevel]->sets = set;
		}
	    }
	} else {
	    char **p, **q, **all;
	    int sep = 0;

	    if ((sep = !strcmp(*args, "-s")))
		args++;

	    for (p = q = args, all = comptags[lasttaglevel]->all; *p; p++)
		if (arrcontains(all, *p, 1)) {
		    Ctset s;

		    for (s = comptags[lasttaglevel]->sets; s; s = s->next)
			if (arrcontains(s->tags, *p, 0))
			    break;

		    if (!s)
			*q++ = *p;
		}
	    *q = NULL;

	    if (*args) {
		char *dummy[2];

		do {
		    Ctset s = (Ctset) zalloc(sizeof(*s)), l;

		    if (sep) {
			dummy[0] = *args++;
			dummy[1] = NULL;
			s->tags = zarrdup(dummy);
		    } else
			s->tags = zarrdup(args);
		    s->next = NULL;
		    s->ptr = NULL;
		    s->tag = NULL;

		    if ((l = comptags[lasttaglevel]->sets)) {
			while (l->next)
			    l = l->next;

			l->next = s;
		    } else
			comptags[lasttaglevel]->sets = s;
		} while (sep && *args);
	    }
	}
    }
    return 0;
}

#define PATH_MAX2 (PATH_MAX * 2)

/*
 * Return a list of files we should accept exactly, without
 * trying pattern matching.
 *
 * This is based on the accept-exact style, which may be
 * an array so is passed in via "accept".  The trial files
 * are input in "names".  "skipped" is passed down straight
 * from the file completion function:  it's got something to
 * do with other components in the path but it's hard to work out
 * quite what.
 *
 * There is one extra trick here for Cygwin.  Regardless of the style,
 * if the file ends in a colon it has to be a drive or a special device
 * file and we always accept it exactly because treating it as a pattern
 * won't work.
 */
static LinkList
cfp_test_exact(LinkList names, char **accept, char *skipped)
{
    char buf[PATH_MAX2 + 1], *suf, *p;
    int l, sl, found = 0;
    struct stat st;
    LinkNode node;
    LinkList ret = newlinklist(), alist = NULL;
#ifdef __CYGWIN__
    int accept_off = 0;
#endif

    /*
     * Don't do this unless completion has provided either a
     * prefix or suffix from the command line.
     */
    if (!(compprefix && *compprefix) && !(compsuffix && *compsuffix))
	return NULL;

    /*
     * See if accept-exact is off, implicitly or explicitly.
     */
    if (!accept || !*accept ||
	((!strcmp(*accept, "false") || !strcmp(*accept, "no") ||
	  !strcmp(*accept, "off") || !strcmp(*accept, "0")) && !accept[1])) {
#ifdef __CYGWIN__
	accept_off = 1;
#else
	/* If not Cygwin, nothing to do here. */
	return NULL;
#endif
    }

    /*
     * See if the style is something other than just a boolean.
     */
    if (
#ifdef __CYGWIN__
	!accept_off &&
#endif
	(accept[1] ||
	 (strcmp(*accept, "true") && strcmp(*accept, "yes") &&
	  strcmp(*accept, "on") && strcmp(*accept, "1")))) {
	Patprog prog;

	alist = newlinklist();

	for (; (p = *accept); accept++) {
	    if (*p == '*' && !p[1]) {
		alist = NULL;
		break;
	    }
	    tokenize(p = dupstring(p));
	    if ((prog = patcompile(p, 0, NULL)))
		addlinknode(alist, prog);
	}
    }
    /*
     * Assemble the bits other than the set of file names:
     * the other components, and the prefix and suffix.
     */
    sl = strlen(skipped) + (compprefix ? strlen(compprefix) : 0) +
	(compsuffix ? strlen(compsuffix) : 0);

    if (sl > PATH_MAX2)
	return NULL;

    suf = dyncat(skipped, rembslash(dyncat(compprefix ? compprefix : "",
		                           compsuffix ? compsuffix : "")));

    for (node = firstnode(names); node; incnode(node)) {
	l = strlen(p = (char *) getdata(node));
	if (l + sl < PATH_MAX2) {
#ifdef __CYGWIN__
	    char *testbuf;
#define TESTBUF testbuf
#else
#define TESTBUF buf
#endif
	    strcpy(buf, p);
	    strcpy(buf + l, suf);
#ifdef __CYGWIN__
	    if (accept_off) {
		int sl = strlen(buf);
		/*
		 * If accept-exact is not set, accept this only if
		 * it looks like a special file such as a drive.
		 * We still test if it exists.
		 */
		if (!sl || strchr(buf, '/') || buf[sl-1] != ':')
		    continue;
		if (sl == 2) {
		    /*
		     * Recent versions of Cygwin only recognise "c:/",
		     * but not "c:", as special directories.  So
		     * we have to append the slash for the purpose of
		     * the test.
		     */
		    testbuf = zhalloc(sl + 2);
		    strcpy(testbuf, buf);
		    testbuf[sl] = '/';
		    testbuf[sl+1] = '\0';
		} else {
		    /* Don't do this with stuff like PRN: */
		    testbuf = buf;
		}
	    } else {
		testbuf = buf;
	    }
#endif
	    if (!ztat(TESTBUF, &st, 0)) {
		/*
		 * File exists; if accept-exact contained non-boolean
		 * values it must match those, too.
		 */
		if (alist) {
		    LinkNode anode;

		    for (anode = firstnode(alist); anode; incnode(anode))
			if (pattry((Patprog) getdata(anode), buf))
			    break;

		    if (!anode)
			continue;
		}
		found = 1;
		addlinknode(ret, dupstring(buf));
	    }
	}
    }
    return (found ? ret : NULL);
}


/*
 * This code constructs (from heap) and returns a string that
 * corresponds to a series of matches; when compiled as a pattern, at
 * each position it matches either the character from the string "add"
 * or the corresponding single-character match from the set of matchers.
 * To take a simple case, if add is "a" and the single matcher for the
 * character position matches "[0-9]", the pattern returned is "[0-9a]".
 * We take account of equivalences between the word and line, too.
 *
 * As there are virtually no comments in this file, I don't really
 * know why we're doing this, but it's to do with a matcher which
 * is passed as an argument to the utility compfiles -p/-P.
 */
static char *
cfp_matcher_range(Cmatcher *ms, char *add)
{
    Cmatcher *mp, m;
    int len = 0, mt;
    char *ret = NULL, *p = NULL, *adds = add;

    /*
     * Do this twice:  once to work out the length of the
     * string in len, the second time to build it in ret.
     * This is probably worthwhile because otherwise memory
     * management is difficult.
     */
    for (;;) {
	MB_METACHARINIT();
	for (mp = ms; *add; ) {
	    convchar_t addc;
	    int addlen;

	    addlen = MB_METACHARLENCONV(add, &addc);
#ifdef MULTIBYTE_SUPPORT
	    if (addc == WEOF)
		addc = (wchar_t)(*add == Meta ? add[1] ^ 32 : *add);
#endif

	    if (!(m = *mp)) {
		/*
		 * No matcher, so just match the character
		 * itself.
		 *
		 * TODO: surely this needs quoting if it's a
		 * metacharacter?
		 */
		if (ret) {
		    memcpy(p, add, addlen);
		    p += addlen;
		} else
		    len += addlen;
	    } else if (m->flags & CMF_RIGHT) {
		/*
		 * Right-anchored:  match anything followed
		 * by the character itself.
		 */
		if (ret) {
		    *p++ = '*';
		    /* TODO: quote again? */
		    memcpy(p, add, addlen);
		    p += addlen;
		} else
		    len += addlen + 1;
	    } else {
		/* The usual set of matcher possibilities. */
		convchar_t ind;
		if (m->line->tp == CPAT_EQUIV &&
		    m->word->tp == CPAT_EQUIV) {
		    /*
		     * Genuine equivalence.  Add the character to match
		     * and the equivalent character from the word
		     * pattern.
		     *
		     * TODO: we could be more careful here with special
		     * cases as we are in the basic character class
		     * code below.
		     */
		    if (ret) {
			*p++ = '[';
			memcpy(p, add, addlen);
			p += addlen;
		    } else
			len += addlen + 1;
		    if (PATMATCHRANGE(m->line->u.str, addc, &ind, &mt)) {
			/*
			 * Find the equivalent match for ind in the
			 * word pattern.
			 */
			if ((ind = pattern_match_equivalence
			     (m->word, ind, mt, addc)) != CHR_INVALID) {
			    if (ret) {
				if (imeta(ind)) {
				    *p++ = Meta;
				    *p++ = ind ^ 32;
				} else
				    *p++ = ind;
			    } else
				len += imeta(ind) ? 2 : 1;
			}
		    }
		    if (ret)
			*p++ = ']';
		    else
			len++;
		} else {
		    int newlen, addadd;

		    switch (m->word->tp) {
		    case CPAT_NCLASS:
			/*
			 * TODO: the old logic implies that we need to
			 * match *add, i.e. it should be deleted from
			 * the set of character's we're not allowed to
			 * match.  That's too much like hard work for
			 * now.  Indeed, in general it's impossible
			 * without trickery.  Consider *add == 'A',
			 * range == "[^[:upper:]]": we would have to
			 * resort to something like "(A|[^[:upper:]])";
			 * and in an expression like that *add may or
			 * may not need backslashing.  So we're deep
			 * into see-if-we-can-get-away-without
			 * territory.
			 */
			if (ret) {
			    *p++ = '[';
			    *p++ = '^';
			} else
			    len += 2;
			/*
			 * Convert the compiled range string back
			 * to an ordinary string.
			 */
			newlen =
			    pattern_range_to_string(m->word->u.str, p);
			DPUTS(!newlen, "empty character range");
			if (ret) {
			    p += newlen;
			    *p++ = ']';
			} else
			    len += newlen + 1;
			break;
			    
		    case CPAT_CCLASS:
			/*
			 * If there is an equivalence only on one
			 * side it's not equivalent to anything.
			 * Treat it as an ordinary character class.
			 */ 
		    case CPAT_EQUIV:
		    case CPAT_CHAR:
			if (ret)
			    *p++ = '[';
			else
			    len++;
			/*
			 * We needed to add *add specially only if
			 * it is not covered by the range.  This
			 * is necessary for correct syntax---consider
			 * if *add is ] and ] is also the first
			 * character in the range.
			 */
			addadd = !pattern_match1(m->word, addc, &mt);
			if (addadd && *add == ']') {
			    if (ret)
				*p++ = *add;
			    else
				len++;
			}
			if (m->word->tp == CPAT_CHAR) {
			    /*
			     * The matcher just matches a single
			     * character, but we need to be able
			     * to match *add, too, hence we do
			     * this as a [...].
			     */
			    if (ret) {
				if (imeta(m->word->u.chr)) {
				    *p++ = Meta;
				    *p++ = m->word->u.chr ^ 32;
				} else
				    *p++ = m->word->u.chr;
			    } else
				len += imeta(m->word->u.chr) ? 2 : 1;
			} else {
			    /*
			     * Convert the compiled range string back
			     * to an ordinary string.
			     */
			    newlen =
				pattern_range_to_string(m->word->u.str, p);
			    DPUTS(!newlen, "empty character range");
			    if (ret)
				p += newlen;
			    else
				len += newlen;
			}
			if (addadd && *add != ']') {
			    if (ret) {
				memcpy(p, add, addlen);
				p += addlen;
			    } else
				len += addlen;
			}
			if (ret)
			    *p++ = ']';
			else
			    len++;
			break;

		    case CPAT_ANY:
			if (ret)
			    *p++ = '?';
			else
			    len++;
			break;
		    }
		}
	    }
	    add += addlen;
	    mp++;
	}
	if (ret) {
	    *p = '\0';
	    return ret;
	}
	p = ret = zhalloc(len + 1);
	add = adds;
    }
}


static char *
cfp_matcher_pats(char *matcher, char *add)
{
    Cmatcher m = parse_cmatcher(NULL, matcher);

    if (m && m != pcm_err) {
	char *tmp;
	int al = strlen(add), zl = ztrlen(add), tl, cl;
	VARARR(Cmatcher, ms, zl);	/* One Cmatcher per character */
	Cmatcher *mp;
	Cpattern stopp;
	int stopl = 0;

	/* zl >= (number of wide characters) is guaranteed */
	memset(ms, 0, zl * sizeof(Cmatcher));

	for (; m && *add; m = m->next) {
	    stopp = NULL;
	    if (!(m->flags & (CMF_LEFT|CMF_RIGHT))) {
		if (m->llen == 1 && m->wlen == 1) {
		    /*
		     * In this loop and similar loops below we step
		     * through tmp one (possibly wide) character at a
		     * time.  pattern_match() compares only the first
		     * character using unmeta_one() so keep in step.
		     */
		    for (tmp = add, tl = al, mp = ms; tl; ) {
			if (pattern_match(m->line, tmp, NULL, NULL)) {
			    if (*mp) {
				*tmp = '\0';
				al = tmp - add;
				break;
			    } else
				*mp = m;
			}
			(void) unmeta_one(tmp, &cl);
			tl -= cl;
			tmp += cl;
			mp++;
		    }
		} else {
		    stopp = m->line;
		    stopl = m->llen;
		}
	    } else if (m->flags & CMF_RIGHT) {
		if (m->wlen < 0 && !m->llen && m->ralen == 1) {
		    for (tmp = add, tl = al, mp = ms; tl; ) {
			if (pattern_match(m->right, tmp, NULL, NULL)) {
			    if (*mp || (tmp == add && *tmp == '.')) {
				*tmp = '\0';
				al = tmp - add;
				break;
			    } else
				*mp = m;
			}
			(void) unmeta_one(tmp, &cl);
			tl -= cl;
			tmp += cl;
			mp++;
		    }
		} else if (m->llen) {
		    stopp = m->line;
		    stopl = m->llen;
		} else {
		    stopp = m->right;
		    stopl = m->ralen;
		}
	    } else {
		if (!m->lalen)
		    return "";

		stopp = m->left;
		stopl = m->lalen;
	    }
	    if (stopp)
		for (tmp = add, tl = al; tl >= stopl; ) {
		    if (pattern_match(stopp, tmp, NULL, NULL)) {
			*tmp = '\0';
			al = tmp - add;
			break;
		    }
		    (void) unmeta_one(tmp, &cl);
		    tl -= cl;
		    tmp += cl;
		}
	}
	if (*add)
	    return cfp_matcher_range(ms, add);
    }
    return add;
}

/*
 * ### This function call is skipped by _approximate, so "opt" probably means "optimize".
 */

static void
cfp_opt_pats(char **pats, char *matcher)
{
    char *add, **p, *q, *t, *s;

    if (!compprefix || !*compprefix)
	return;

    if (comppatmatch && *comppatmatch) {
	tokenize(t = rembslash(dyncat(compprefix, compsuffix)));
	remnulargs(t);
	if (haswilds(t))
	    return;
    }
    add = (char *) zhalloc(strlen(compprefix) * 2 + 1);
    for (s = compprefix, t = add; *s; s++) {
	if (*s != '\\' || !s[1] || s[1] == '*' || s[1] == '?' ||
	    s[1] == '<' || s[1] == '>' || s[1] == '(' || s[1] == ')' ||
	    s[1] == '[' || s[1] == ']' || s[1] == '|' || s[1] == '#' ||
	    s[1] == '^' || s[1] == '~' || s[1] == '=') {
	    if ((s == compprefix || s[-1] != '\\') &&
		(*s == '*' || *s == '?' || *s == '<' || *s == '>' ||
		 *s == '(' || *s == ')' || *s == '[' || *s == ']' ||
		 *s == '|' || *s == '#' || *s == '^' || *s == '~' ||
		 *s == '='))
		*t++ = '\\';
	    *t++ = *s;
	}
    }
    *t = '\0';
    for (p = pats; *add && (q = *p); p++) {
	if (*q) {
	    q = dupstring(q);
	    t = q + strlen(q) - 1;
	    if (*t == ')') {
		for (s = t--; t > q; t--)
		    if (*t == ')' || *t == '|' || *t == '~' || *t == '(')
			break;
		if (t != q && *t == '(')
		    *t = '\0';
	    }
	    for (; *q && *add; q++) {
		if (*q == '\\' && q[1]) {
		    for (s = add, q++; *s && *s != *q; s++);
		    *s = '\0';
		} else if (*q == '<') {
		    for (s = add; *s && !idigit(*s); s++);
		    *s = '\0';
		} else if (*q == '[') {
		    int not;
		    char *x = ++q;

		    if ((not = (*x == '!' || *x == '^')))
			x++;
		    for (; *x; x++) {
			if (x[1] == '-' && x[2]) {
			    char c1 = *x, c2 = x[2];

			    for (s = add; *s && (*x < c1 || *x > c2); s++);
			    *s = '\0';
			} else {
			    for (s = add; *s && *s != *x; s++);
			    *s = '\0';
			}
		    }
		} else if (*q != '?' && *q != '*' && *q != '(' && *q != ')' &&
			   *q != '|' && *q != '~' && *q != '#') {
		    for (s = add; *s && *s != *q; s++);
		    *s = '\0';
		}
	    }
	}
    }
    if (*add) {
	if (*matcher && !(add = cfp_matcher_pats(matcher, add)))
	    return;

	for (p = pats; *p; p++)
	    if (**p == '*')
		*p = dyncat(add, *p);
    }
}

static LinkList
cfp_bld_pats(UNUSED(int dirs), LinkList names, char *skipped, char **pats)
{
    LinkList ret = newlinklist();
    LinkNode node;
    int ol, sl = strlen(skipped), pl, dot;
    char **p, *o, *str;

    dot = (unset(GLOBDOTS) && compprefix && *compprefix == '.');
    for (node = firstnode(names); node; incnode(node)) {
	ol = strlen(o = (char *) getdata(node));
	for (p = pats; *p; p++) {
	    pl = strlen(*p);
	    str = (char *) zhalloc(ol + sl + pl + 1);
	    strcpy(str, o);
	    strcpy(str + ol, skipped);
	    strcpy(str + ol + sl, *p);
	    addlinknode(ret, str);
	    if (dot && **p != '.') {
		str = (char *) zhalloc(ol + sl + pl + 2);
		strcpy(str, o);
		strcpy(str + ol, skipped);
		str[ol + sl] = '.';
		strcpy(str + ol + sl + 1, *p);
		addlinknode(ret, str);
	    }
	}
    }
    return ret;
}

static LinkList
cfp_add_sdirs(LinkList final, LinkList orig, char *skipped,
	      char *sdirs, char **fake)
{
    int add = 0;

    if (*sdirs && (isset(GLOBDOTS) || (compprefix && *compprefix == '.'))) {
	if (!strcmp(sdirs, "yes") || !strcmp(sdirs, "true") ||
	    !strcmp(sdirs, "on") || !strcmp(sdirs, "1"))
	    add = 2;
	else if (!strcmp(sdirs, ".."))
	    add = 1;
    }
    if (add) {
	LinkNode node;
	char *s1 = dyncat(skipped, "..");
	char *s2 = (add == 2 ? dyncat(skipped, ".") : NULL), *m;

	for (node = firstnode(orig); node; incnode(node)) {
	    if ((m = (char *) getdata(node))) {
		addlinknode(final, dyncat(m, s1));
		if (s2)
		    addlinknode(final, dyncat(m, s2));
	    }
	}
    }
    if (fake && *fake) {
	LinkNode node;
	char *m, *f, *p, *t, *a, c;
	int sl = strlen(skipped) + 1;
	struct stat st1, st2;
	Patprog pprog;

	for (; (f = *fake); fake++) {
	    f = dupstring(f);
	    for (p = t = f; *p; p++) {
		if (*p == ':')
		    break;
		else if (*p == '\\' && p[1] == ':') {
		    /*
		     * strip quoted colons here; rely
		     * on tokenization to strip other backslashes
		     */
		    p++;
		}
		*t++ = *p;
	    }
	    if (*p) {
		*t = *p++ = '\0';
		if (!*p)
		    continue;

		queue_signals();	/* Protect PAT_STATIC */

		tokenize(f);
		pprog = patcompile(f, PAT_STATIC, NULL);
		untokenize(f);
		for (node = firstnode(orig); node; incnode(node)) {
		    if ((m = (char *) getdata(node)) &&
			((pprog ? pattry(pprog, m) : !strcmp(f, m)) ||
			 (!stat(f, &st1) && !stat((*m ? m : "."), &st2) &&
			  st1.st_dev == st2.st_dev &&
			  st1.st_ino == st2.st_ino))) {
			while (*p) {
			    while (*p && inblank(*p))
				p++;
			    if (!*p)
				break;
			    for (f = t = p; *p; p++) {
				if (inblank(*p))
				    break;
				else if (*p == '\\' && p[1])
				    p++;
				*t++ = *p;
			    }
			    c = *t;
			    *t = '\0';
			    a = (char *) zhalloc(strlen(m) + sl + strlen(f));
			    strcpy(a, m);
			    strcat(a, skipped);
			    strcat(a, f);
			    addlinknode(final, a);
			    *t = c;
			}
		    }
		}

		unqueue_signals();
	    }
	}
    }
    return final;
}

static LinkList
cf_pats(int dirs, int noopt, LinkList names, char **accept, char *skipped,
	char *matcher, char *sdirs, char **fake, char **pats)
{
    LinkList ret;
    char *dpats[2];

    if ((ret = cfp_test_exact(names, accept, skipped)))
	return cfp_add_sdirs(ret, names, skipped, sdirs, fake);

    if (dirs) {
	dpats[0] = "*(-/)";
	dpats[1] = NULL;
	pats = dpats;
    }
    if (!noopt)
	cfp_opt_pats(pats, matcher);

    return cfp_add_sdirs(cfp_bld_pats(dirs, names, skipped, pats),
			 names, skipped, sdirs, fake);
}

/*
 * This function looks at device/inode pairs to determine if
 * a file is one we should ignore because of its relationship
 * to the current or parent directory.
 *
 * We don't follow symbolic links here, because typically
 * a user will not want an explicit link to the current or parent
 * directory ignored.
 */
static void
cf_ignore(char **names, LinkList ign, char *style, char *path)
{
    int pl = strlen(path), tpar, tpwd, found;
    struct stat nst, est, st;
    char *n, *c, *e;

    tpar = !!strstr(style, "parent");
    if ((tpwd = !!strstr(style, "pwd")) && lstat(pwd, &est))
	tpwd = 0;

    if (!tpar && !tpwd)
	return;

    for (; (n = *names); names++) {
	if (!ztat(n, &nst, 1) && S_ISDIR(nst.st_mode)) {
	    if (tpwd && nst.st_dev == est.st_dev && nst.st_ino == est.st_ino) {
		addlinknode(ign, quotestring(n, QT_BACKSLASH));
		continue;
	    }
	    if (tpar && !strncmp((c = dupstring(n)), path, pl)) {
		found = 0;
		while ((e = strrchr(c, '/')) && e > c + pl) {
		    *e = '\0';
		    if (!ztat(c, &st, 0) &&
			st.st_dev == nst.st_dev && st.st_ino == nst.st_ino) {
			found = 1;
			break;
		    }
		}
		if (found || ((e = strrchr(c, '/')) && e > c + pl &&
			      !ztat(c, &st, 1) && st.st_dev == nst.st_dev &&
			      st.st_ino == nst.st_ino))
		    addlinknode(ign, quotestring(n, QT_BACKSLASH));
	    }
	}
    }
}

static LinkList
cf_remove_other(char **names, char *pre, int *amb)
{
    char *p;

    if ((p = strchr(pre, '/'))) {
	char **n;

	*p = '\0';
	pre = dyncat(pre, "/");
	*p = '/';

	for (n = names; *n; n++)
	    if (strpfx(pre, *n))
		break;

	if (*n) {
	    LinkList ret = newlinklist();

	    for (; *names; names++)
		if (strpfx(pre, *names))
		    addlinknode(ret, dupstring(*names));

	    *amb = 0;

	    return ret;
	} else {
	    if (!(p = *names++))
		*amb = 0;
	    else {
		char *q;

		if ((q = strchr((p = dupstring(p)), '/')))
		    *q = '\0';

                p = dyncat(p, "/");

		for (; *names; names++)
		    if (!strpfx(p, *names)) {
			*amb = 1;
			return NULL;
		    }
	    }
	}
    } else {
	if (!(p = *names++))
	    *amb = 0;
	else
	    for (; *names; names++)
		if (strcmp(p, *names)) {
		    *amb = 1;
		    return NULL;
		}
    }
    return NULL;
}

/*
 * SYNOPSIS:
 *     1. compfiles -p  parnam1 parnam2 skipped matcher sdirs parnam3 varargs [..varargs]
 *     2. compfiles -p- parnam1 parnam2 skipped matcher sdirs parnam3 varargs [..varargs]
 *     3. compfiles -P  parnam1 parnam2 skipped matcher sdirs parnam3 
 *
 *     1. Set parnam1 to an array of patterns....
 *        ${(P)parnam1} is an in/out parameter.
 *     2. Like #1 but without calling cfp_opt_pats().  (This is only used by _approximate.)
 *     3. Like #1 but varargs is implicitly set to  char *varargs[2] = { "*(-/)", NULL };.
 *
 *     parnam2 has to do with the accept-exact style (see cfp_test_exact()).
 */

static int
bin_compfiles(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
{
    if (incompfunc != 1) {
	zwarnnam(nam, "can only be called from completion function");
	return 1;
    }
    if (**args != '-') {
	zwarnnam(nam, "missing option: %s", *args);
	return 1;
    }
    switch (args[0][1]) {
    case 'p':
    case 'P':
	if (args[0][2] && (args[0][2] != '-' || args[0][3])) {
	    zwarnnam(nam, "invalid option: %s", *args);
	    return 1;
	} else {
	    char **tmp;
	    LinkList l;

	    if (!args[1] || !args[2] || !args[3] || !args[4] || !args[5] ||
		!args[6] || (args[0][1] == 'p' && !args[7])) {
		zwarnnam(nam, "too few arguments");
		return 1;
	    }
	    queue_signals();
	    if (!(tmp = getaparam(args[1]))) {
		unqueue_signals();
		zwarnnam(nam, "unknown parameter: %s", args[1]);
		return 0;
	    }
	    for (l = newlinklist(); *tmp; tmp++)
		addlinknode(l, quotestring(*tmp, QT_BACKSLASH_PATTERN));
	    set_list_array(args[1], cf_pats((args[0][1] == 'P'), !!args[0][2],
					    l, getaparam(args[2]), args[3],
					    args[4], args[5],
					    getaparam(args[6]), args + 7));
	    unqueue_signals();
	    return 0;
	}
    case 'i':
	if (args[0][2]) {
	    zwarnnam(nam, "invalid option: %s", *args);
	    return 1;
	} else {
	    char **tmp;
	    LinkList l;

	    if (!args[1] || !args[2] || !args[3] || !args[4]) {
		zwarnnam(nam, "too few arguments");
		return 1;
	    }
	    if (args[5]) {
		zwarnnam(nam, "too many arguments");
		return 1;
	    }
	    queue_signals();
	    tmp = getaparam(args[2]);
	    l = newlinklist();
	    if (tmp)
		for (; *tmp; tmp++)
		    addlinknode(l, *tmp);
	    if (!(tmp = getaparam(args[1]))) {
		unqueue_signals();
		zwarnnam(nam, "unknown parameter: %s", args[1]);
		return 0;
	    }
	    cf_ignore(tmp, l, args[3], args[4]);
	    unqueue_signals();
	    set_list_array(args[2], l);
	    return 0;
	}
    case 'r':
	{
	    char **tmp;
	    LinkList l;
	    int ret = 0;

	    if (!args[1] || !args[2]) {
		zwarnnam(nam, "too few arguments");
		return 1;
	    }
	    if (args[3]) {
		zwarnnam(nam, "too many arguments");
		return 1;
	    }
	    queue_signals();
	    if (!(tmp = getaparam(args[1]))) {
		unqueue_signals();
		zwarnnam(nam, "unknown parameter: %s", args[1]);
		return 0;
	    }
	    if ((l = cf_remove_other(tmp, args[2], &ret)))
		set_list_array(args[1], l);
	    unqueue_signals();
	    return ret;
	}
    }
    zwarnnam(nam, "invalid option: %s", *args);
    return 1;
}

static int
bin_compgroups(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
{
    Heap oldheap;
    char *n;

    if (incompfunc != 1) {
	zwarnnam(nam, "can only be called from completion function");
	return 1;
    }
    SWITCHHEAPS(oldheap, compheap) {
	while ((n = *args++)) {
	    endcmgroup(NULL);
	    begcmgroup(n, CGF_NOSORT|CGF_UNIQCON);
	    endcmgroup(NULL);
	    begcmgroup(n, CGF_UNIQALL);
	    endcmgroup(NULL);
	    begcmgroup(n, CGF_NOSORT|CGF_UNIQCON);
	    endcmgroup(NULL);
	    begcmgroup(n, CGF_UNIQALL);
	    endcmgroup(NULL);
	    begcmgroup(n, CGF_NOSORT);
	    endcmgroup(NULL);
	    begcmgroup(n, 0);
	}
    } SWITCHBACKHEAPS(oldheap);

    return 0;
}

static struct builtin bintab[] = {
    BUILTIN("comparguments", 0, bin_comparguments, 1, -1, 0, NULL, NULL),
    BUILTIN("compdescribe", 0, bin_compdescribe, 3, -1, 0, NULL, NULL),
    BUILTIN("compfiles", 0, bin_compfiles, 1, -1, 0, NULL, NULL),
    BUILTIN("compgroups", 0, bin_compgroups, 1, -1, 0, NULL, NULL),
    BUILTIN("compquote", 0, bin_compquote, 1, -1, 0, "p", NULL),
    BUILTIN("comptags", 0, bin_comptags, 1, -1, 0, NULL, NULL),
    BUILTIN("comptry", 0, bin_comptry, 0, -1, 0, NULL, NULL),
    BUILTIN("compvalues", 0, bin_compvalues, 1, -1, 0, NULL, NULL)
};

static struct features module_features = {
    bintab, sizeof(bintab)/sizeof(*bintab),
    NULL, 0,
    NULL, 0,
    NULL, 0,
    0
};


/**/
int
setup_(UNUSED(Module m))
{
    memset(cadef_cache, 0, sizeof(cadef_cache));
    memset(cvdef_cache, 0, sizeof(cvdef_cache));

    memset(comptags, 0, sizeof(comptags));

    lasttaglevel = 0;

    return 0;
}

/**/
int
features_(Module m, char ***features)
{
    *features = featuresarray(m, &module_features);
    return 0;
}

/**/
int
enables_(Module m, int **enables)
{
    return handlefeatures(m, &module_features, enables);
}

/**/
int
boot_(UNUSED(Module m))
{
    return 0;
}

/**/
int
cleanup_(Module m)
{
    return setfeatureenables(m, &module_features, NULL);
}

/**/
int
finish_(UNUSED(Module m))
{
    int i;

    for (i = 0; i < MAX_CACACHE; i++)
	freecadef(cadef_cache[i]);
    for (i = 0; i < MAX_CVCACHE; i++)
	freecvdef(cvdef_cache[i]);

    for (i = 0; i < MAX_TAGS; i++)
	freectags(comptags[i]);

    return 0;
}
