From f6df6307ead281818ce6d469f55a3e807c877c3e Mon Sep 17 00:00:00 2001 From: Tanaka Akira Date: Tue, 16 Nov 1999 08:37:27 +0000 Subject: zsh-workers/8646 --- Src/Zle/computil.c | 316 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 258 insertions(+), 58 deletions(-) (limited to 'Src/Zle/computil.c') diff --git a/Src/Zle/computil.c b/Src/Zle/computil.c index 42de5c65c..24fa0e769 100644 --- a/Src/Zle/computil.c +++ b/Src/Zle/computil.c @@ -33,12 +33,19 @@ /* Help for `_display'. */ +/* Calculation state. */ + typedef struct cdisp *Cdisp; struct cdisp { - int pre, suf, colon; + int pre; /* prefix length */ + int suf; /* suffix length */ + int colon; /* number of strings with descriptions */ }; +/* Calculate longest prefix and suffix and count the strings with + * descriptions. */ + static void cdisp_calc(Cdisp disp, char **args) { @@ -56,6 +63,8 @@ cdisp_calc(Cdisp disp, char **args) } } +/* Build and return the array with the description-aligned strings. */ + static char ** cdisp_build(Cdisp disp, char *sep, char **args) { @@ -110,17 +119,17 @@ bin_compdisplay(char *nam, char **args, char *ops, int func) typedef struct cdset *Cdset; struct cdstate { - int showd; - char *sep; - Cdset sets; - struct cdisp disp; + int showd; /* != 0 if descriptions should be shown */ + char *sep; /* the separator string */ + Cdset sets; /* the sets of matches */ + struct cdisp disp; /* used to calculate the alignment */ }; struct cdset { - Cdset next; - char **opts; - char **strs; - char **matches; + Cdset next; /* guess what */ + char **opts; /* the compadd-options */ + char **strs; /* the display-strings */ + char **matches; /* the matches (or NULL) */ }; static struct cdstate cd_state; @@ -143,6 +152,7 @@ freecdsets(Cdset p) } } +/* Initialisation. Store and calculate the string and matches and so on. */ static int cd_init(char *nam, char *sep, char **args, int disp) { @@ -199,6 +209,8 @@ cd_init(char *nam, char *sep, char **args, int disp) return 0; } +/* Get the next set. */ + static int cd_get(char **params) { @@ -226,6 +238,9 @@ cd_get(char **params) memcpy(buf + pre, cd_state.sep, sepl); suf = pre + sepl; } + + /* Build the aligned display strings. */ + for (sdp = sd, ssp = ss, mdp = md, msp = ms, p = set->strs, mp = set->matches; *p; p++) { if ((cp = strchr(*p, ':')) && cp[1] && cd_state.showd) { @@ -321,29 +336,34 @@ typedef struct cadef *Cadef; typedef struct caopt *Caopt; typedef struct caarg *Caarg; +/* Cache for a set of _arguments-definitions. */ + struct cadef { - Cadef next; - Caopt opts; - int nopts, ndopts, nodopts; - Caarg args; - Caarg rest; - char **defs; - int ndefs; - int lastt; - Caopt *single; - char *match; - int argsactive; + Cadef next; /* next in cache */ + 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 arguments are still allowed */ + /* used while parsing a command line */ }; +/* Description for an option. */ + struct caopt { Caopt next; - char *name; - char *descr; - char **xor; - int type; - Caarg args; - int active; - int num; + 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 */ }; #define CAO_NEXT 1 @@ -351,14 +371,16 @@ struct caopt { #define CAO_ODIRECT 3 #define CAO_EQUAL 4 +/* Description for an argument */ + struct caarg { Caarg next; - char *descr; - char *action; - int type; - char *end; - char *opt; - int num; + char *descr; /* description */ + char *action; /* what to do for it */ + int type; /* CAA_* below */ + char *end; /* end-pattern for :::... */ + char *opt; /* option name if for an option */ + int num; /* it's the num'th argument */ }; #define CAA_NORMAL 1 @@ -367,9 +389,13 @@ struct caarg { #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) { @@ -386,6 +412,8 @@ arrcmp(char **a, char **b) } } +/* Memory stuff. Obviously. */ + static void freecaargs(Caarg a) { @@ -426,6 +454,8 @@ freecadef(Cadef d) } } +/* Remove backslashes before colons. */ + static char * rembslashcolon(char *s) { @@ -443,6 +473,8 @@ rembslashcolon(char *s) return r; } +/* Parse an argument definition. */ + static Caarg parse_caarg(int mult, int type, int num, char *oname, char **def) { @@ -455,12 +487,17 @@ parse_caarg(int mult, int type, int num, char *oname, char **def) ret->type = type; ret->opt = ztrdup(oname); + /* 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++) @@ -479,6 +516,8 @@ parse_caarg(int mult, int type, int num, char *oname, char **def) return ret; } +/* Parse an array of definitions. */ + static Cadef parse_cadef(char *nam, char **args) { @@ -490,6 +529,8 @@ parse_cadef(char *nam, char **args) nopts = ndopts = nodopts = 0; + /* First string is the auto-description definition. */ + for (p = args[0]; *p && (p[0] != '%' || p[1] != 'd'); p++); if (*p) { @@ -500,6 +541,8 @@ parse_cadef(char *nam, char **args) } else adpre = adsuf = NULL; + /* Now get the -s and -M options. */ + args++; while ((p = *args)) { if (!strcmp(p, "-s")) @@ -520,6 +563,8 @@ parse_cadef(char *nam, char **args) if (!*args) return NULL; + /* Looks good. Optimistically allocate the cadef structure. */ + PERMALLOC { ret = (Cadef) zalloc(sizeof(*ret)); ret->next = NULL; @@ -536,10 +581,14 @@ parse_cadef(char *nam, char **args) ret->match = ztrdup(match); } LASTALLOC; + /* Get the definitions. */ + for (optp = &(ret->opts); *args; args++) { p = dupstring(*args); xnum = 0; if (*p == '(') { + /* There is a xor list, get it. */ + LinkList list = newlinklist(); LinkNode node; char **xp, sav; @@ -560,6 +609,7 @@ parse_cadef(char *nam, char **args) xnum++; *p = sav; } + /* Oops, end-of-string. */ if (*p != ')') { freecadef(ret); zerrnam(nam, "invalid argument: %s", *args, 0); @@ -573,9 +623,10 @@ parse_cadef(char *nam, char **args) p++; } 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; @@ -583,6 +634,7 @@ parse_cadef(char *nam, char **args) rec: + /* Allowed more than once? */ if ((multi = (*p == '*'))) p++; @@ -590,11 +642,14 @@ parse_cadef(char *nam, char **args) (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. */ name = ++p; *p = (again ? '-' : '+'); again = 1 - again; } else { name = p; + /* If it's a long option skip over the first `-'. */ if (p[0] == '-' && p[1] == '-') p++; } @@ -603,12 +658,15 @@ parse_cadef(char *nam, char **args) zerrnam(nam, "invalid argument: %s", *args, 0); return NULL; } + /* Skip over the name. */ + for (p += 2; *p && *p != ':' && *p != '[' && ((*p != '-' && *p != '+' && *p != '=') || (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 == '-') { @@ -621,6 +679,7 @@ parse_cadef(char *nam, char **args) otype = CAO_EQUAL; c = *++p; } + /* Get the optional description, if any. */ if (c == '[') { for (descr = ++p; *p && *p != ']'; p++) if (*p == '\\' && p[1]) @@ -641,6 +700,7 @@ parse_cadef(char *nam, char **args) zerrnam(nam, "invalid option definition: %s", *args, 0); return NULL; } + /* Add the option name to the xor list if not `*-...'. */ if (!multi) { if (!xor) { xor = (char **) zalloc(2 * sizeof(char *)); @@ -649,14 +709,19 @@ parse_cadef(char *nam, char **args) xor[xnum] = ztrdup(name); } if (c == ':') { + /* There's at least one argument. */ + Caarg *oargp = &oargs; int atype, rest, oanum = 1; char *end; + /* Loop over the arguments. */ + while (c == ':') { rest = 0; end = NULL; + /* Get the argument type. */ if (*++p == ':') { atype = CAA_OPT; p++; @@ -684,6 +749,9 @@ parse_cadef(char *nam, char **args) rest = 1; } else atype = CAA_NORMAL; + + /* And the definition. */ + *oargp = parse_caarg(!rest, atype, oanum++, name, &p); oargp = &((*oargp)->next); if (rest) @@ -691,6 +759,8 @@ parse_cadef(char *nam, char **args) c = *p; } } + /* Store the option definition. */ + PERMALLOC { *optp = opt = (Caopt) zalloc(sizeof(*opt)); optp = &((*optp)->next); @@ -715,14 +785,20 @@ parse_cadef(char *nam, char **args) else if (otype == CAO_ODIRECT || otype == CAO_EQUAL) nodopts++; + /* If this is for single-letter option we also store a + * pointer for the definition in the array for fast lookup. */ + if (single && name[1] && !name[2]) ret->single[STOUC(name[1])] = opt; if (again) { + /* Do it all again for `*-...'. */ p = dupstring(*args); goto rec; } } else if (*p == '*') { + /* It's a rest-argument definition. */ + int type = CAA_REST; if (*++p != ':') { @@ -744,10 +820,13 @@ parse_cadef(char *nam, char **args) } ret->rest = parse_caarg(0, type, -1, NULL, &p); } else { + /* It's a normal argument definition. */ + int type = CAA_NORMAL; Caarg arg, tmp, pre; if (idigit(*p)) { + /* Argment number is given. */ int num = 0; while (*p && idigit(*p)) @@ -755,6 +834,7 @@ parse_cadef(char *nam, char **args) anum = num + 1; } else + /* Default number. */ anum++; if (*p != ':') { @@ -763,11 +843,14 @@ parse_cadef(char *nam, char **args) return NULL; } if (*++p == ':') { + /* Optional argument. */ type = CAA_OPT; p++; } arg = parse_caarg(0, type, anum - 1, NULL, &p); + /* Sort the new definition into the existing list. */ + for (tmp = ret->args, pre = NULL; tmp && tmp->num < anum - 1; pre = tmp, tmp = tmp->next); @@ -792,6 +875,9 @@ parse_cadef(char *nam, char **args) return ret; } +/* Given an array of definitions, return the cadef for it. From the cache + * are newly built. */ + static Cadef get_cadef(char *nam, char **args) { @@ -814,20 +900,26 @@ get_cadef(char *nam, char **args) return new; } +/* Get the option used in a word from the line, if any. */ + static Caopt ca_get_opt(Cadef d, char *line, int full, char **end) { Caopt p; if (full) { + /* The full string has to be an option. */ + for (p = d->opts; p; p = p->next) if (p->active && !strcmp(p->name, line)) return p; } else { + /* 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))) { if (end) { + /* Return a pointer to the end of the option. */ int l = strlen(p->name); if (p->type == CAO_EQUAL && line[l] == '=') @@ -841,6 +933,8 @@ ca_get_opt(Cadef d, char *line, int full, char **end) return NULL; } +/* Same as above, only for single-letter-style. */ + static Caopt ca_get_sopt(Cadef d, char *line, int full, char **end) { @@ -874,6 +968,8 @@ ca_get_sopt(Cadef d, char *line, int full, char **end) return NULL; } +/* Return the n'th argument definition. */ + static Caarg ca_get_arg(Cadef d, int n) { @@ -891,6 +987,8 @@ ca_get_arg(Cadef d, int n) return NULL; } +/* Use a xor list, marking options as inactive. */ + static void ca_inactive(Cadef d, char **xor) { @@ -906,6 +1004,8 @@ ca_inactive(Cadef d, char **xor) } } +/* State when parsing a command line. */ + struct castate { Cadef d; Caarg def, ddef; @@ -919,6 +1019,8 @@ struct castate { static struct castate ca_laststate; static int ca_parsed = 0, ca_alloced = 0; +/* Pars a command line. */ + static void ca_parse_line(Cadef d) { @@ -929,6 +1031,8 @@ ca_parse_line(Cadef d) int cur, doff; Patprog endpat = NULL; + /* Free old state. */ + if (ca_alloced) { int i = ca_laststate.d->nopts; LinkList *p = ca_laststate.oargs; @@ -940,10 +1044,14 @@ ca_parse_line(Cadef d) zfree(ca_laststate.oargs, ca_laststate.d->nopts * sizeof(LinkList)); } + /* MArk everything as active. */ + for (ptr = d->opts; ptr; ptr = ptr->next) ptr->active = 1; d->argsactive = 1; + /* Default values for the state. */ + state.d = d; state.def = state.ddef = NULL; state.curopt = NULL; @@ -964,10 +1072,15 @@ ca_parse_line(Cadef d) return; } + /* 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; doff = state.singles = 0; + + /* We've a definition for an argument, skip to the next. */ + if (state.def) { state.arg = 0; if (state.curopt) { @@ -999,6 +1112,8 @@ ca_parse_line(Cadef d) pe = NULL; + /* See if it's an option. */ + if (state.opt == 2 && (state.curopt = ca_get_opt(d, line, 0, &pe))) { ddef = state.def = state.curopt->args; doff = pe - line; @@ -1011,6 +1126,8 @@ ca_parse_line(Cadef d) } LASTALLOC; ca_inactive(d, state.curopt->xor); + /* Collect the argument strings. MAybe. */ + if (state.def && (state.curopt->type == CAO_DIRECT || (state.curopt->type == CAO_ODIRECT && pe[0]) || @@ -1030,6 +1147,8 @@ ca_parse_line(Cadef d) state.curopt = NULL; } else if (state.opt == 2 && d->single && (state.curopt = ca_get_sopt(d, line, 0, &pe))) { + /* Or maybe it's a single-letter option? */ + char *p; Caopt tmpopt; @@ -1064,6 +1183,7 @@ ca_parse_line(Cadef d) else state.curopt = NULL; } else if (state.arg) { + /* Otherwise it's a normal argument. */ if (state.inopt) { state.inopt = 0; state.nargbeg = cur - 1; @@ -1099,6 +1219,8 @@ ca_parse_line(Cadef d) 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) @@ -1116,6 +1238,8 @@ ca_parse_line(Cadef d) break; } } + /* Copy the state into the global one. */ + if (cur + 1 == compcurrent) { memcpy(&ca_laststate, &state, sizeof(state)); ca_laststate.ddef = NULL; @@ -1142,6 +1266,8 @@ ca_parse_line(Cadef d) } } +/* Build a colon-list from a list. */ + static char * ca_colonlist(LinkList l) { @@ -1383,34 +1509,42 @@ bin_comparguments(char *nam, char **args, char *ops, int func) typedef struct cvdef *Cvdef; typedef struct cvval *Cvval; +/* Definitions for _values. */ + struct cvdef { - char *descr; - int hassep; - char sep; - Cvdef next; - Cvval vals; - char **defs; - int ndefs; - int lastt; + char *descr; /* global description */ + int hassep; /* multiple values allowed */ + char sep; /* separator character */ + Cvdef next; /* next in cache */ + Cvval vals; /* value definitions */ + char **defs; /* original strings */ + int ndefs; /* number of ... */ + int lastt; /* last time used */ }; +/* One value definition. */ + struct cvval { Cvval next; - char *name; - char *descr; - char **xor; - int type; - Caarg arg; - int active; + 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) { @@ -1432,6 +1566,8 @@ freecvdef(Cvdef d) } } +/* Parse option definitions. */ + static Cvdef parse_cvdef(char *nam, char **args) { @@ -1471,6 +1607,8 @@ parse_cvdef(char *nam, char **args) for (valp = &(ret->vals); *args; args++) { p = dupstring(*args); xnum = 0; + + /* xor list? */ if (*p == '(') { LinkList list = newlinklist(); LinkNode node; @@ -1506,9 +1644,12 @@ parse_cvdef(char *nam, char **args) } 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++; @@ -1518,6 +1659,8 @@ parse_cvdef(char *nam, char **args) zerrnam(nam, "no multi-letter values with empty separator allowed", NULL, 0); return NULL; } + /* Optional description? */ + if ((c = *p) == '[') { *p = '\0'; for (descr = ++p; *p && *p != ']'; p++) @@ -1547,6 +1690,8 @@ parse_cvdef(char *nam, char **args) } xor[xnum] = ztrdup(name); } + /* Get argument? */ + if (c == ':') { if (hassep && !sep) { freecvdef(ret); @@ -1578,6 +1723,8 @@ parse_cvdef(char *nam, char **args) return ret; } +/* Get the definition from the cache or newly built. */ + static Cvdef get_cvdef(char *nam, char **args) { @@ -1600,6 +1747,8 @@ get_cvdef(char *nam, char **args) return new; } +/* Get the definition for a value. */ + static Cvval cv_get_val(Cvdef d, char *name) { @@ -1612,6 +1761,8 @@ cv_get_val(Cvdef d, char *name) return NULL; } +/* Handle a xor list. */ + static void cv_inactive(Cvdef d, char **xor) { @@ -1624,6 +1775,8 @@ cv_inactive(Cvdef d, char **xor) } } +/* Parse state. */ + struct cvstate { Cvdef d; Caarg def; @@ -1634,6 +1787,8 @@ struct cvstate { static struct cvstate cv_laststate; static int cv_parsed = 0, cv_alloced = 0; +/* Parse the current word. */ + static void cv_parse_word(Cvdef d) { @@ -1926,16 +2081,19 @@ bin_compvalues(char *nam, char **args, char *ops, int func) return 1; } - static int bin_compquote(char *nam, char **args, char *ops, int func) { char *name; Value v; + /* Anything to do? */ + if (!compqstack || !*compqstack) return 0; + /* For all parameters given... */ + while ((name = *args++)) { name = dupstring(name); if ((v = getvalue(&name, 0))) { @@ -1977,15 +2135,19 @@ bin_compquote(char *nam, char **args, char *ops, int func) return 0; } +/* Style stuff. */ + typedef struct cspat *Cspat; typedef struct cstyle *Cstyle; +/* A pattern and the styles for it. */ + struct cspat { Cspat next; - char *pat; - Patprog prog; - int weight; - Cstyle styles, lstyles; + char *pat; /* pattern string */ + Patprog prog; /* compiled pattern */ + int weight; /* how specific is the pattern? */ + Cstyle styles, lstyles; /* first/last style */ }; struct cstyle { @@ -1998,6 +2160,8 @@ struct cstyle { static Cspat compstyles, lcompstyles; +/* Memory stuff. */ + static void freecstyle(Cstyle s) { @@ -2030,6 +2194,8 @@ freecspat(Cspat p) } } +/* Get the struct for a pattern, if any. */ + static Cspat getcspat(char *pat) { @@ -2042,6 +2208,8 @@ getcspat(char *pat) return NULL; } +/* Get the style stuff for a name. */ + static Cstyle getcstyle(Cspat p, char *name) { @@ -2054,6 +2222,8 @@ getcstyle(Cspat p, char *name) return NULL; } +/* Store a value for a style. */ + static void setcstyle(Cspat p, char *name, char **vals) { @@ -2061,6 +2231,9 @@ setcstyle(Cspat p, char *name, char **vals) for (s = p->styles; s; s = s->next) if (!strcmp(name, s->name)) { + + /* Exists -> replace. */ + freearray(s->vals); PERMALLOC { s->vals = arrdup(vals); @@ -2068,6 +2241,9 @@ setcstyle(Cspat p, char *name, char **vals) return; } + + /* New style. */ + s = (Cstyle) zalloc(sizeof(*s)); s->name = ztrdup(name); @@ -2083,6 +2259,8 @@ setcstyle(Cspat p, char *name, char **vals) p->lstyles = s; } +/* Add a new pattern. */ + static Cspat addcspat(char *pat, Patprog prog) { @@ -2090,8 +2268,11 @@ addcspat(char *pat, Patprog prog) int weight, tmp, first; char *s; + /* Calculate the weight. */ + for (weight = 0, tmp = 2, first = 1, s = pat; *s; s++) { if (first && *s == '*' && (!s[1] || s[1] == ':')) { + /* Only `*' in this component. */ tmp = 0; continue; } @@ -2099,9 +2280,12 @@ addcspat(char *pat, Patprog prog) if (*s == '(' || *s == '|' || *s == '*' || *s == '[' || *s == '<' || *s == '?' || *s == '#' || *s == '^') + /* Is pattern. */ tmp = 1; if (*s == ':') { + /* Yet another component. */ + first = 1; weight += tmp; tmp = 2; @@ -2130,6 +2314,8 @@ addcspat(char *pat, Patprog prog) return p; } +/* Delete a style. */ + static void deletecstyle(Cspat p, char *name) { @@ -2151,6 +2337,8 @@ deletecstyle(Cspat p, char *name) } } +/* Delete a whole pattern with all its styles. */ + static void deletecspat(Cspat pat) { @@ -2175,6 +2363,8 @@ deletecspat(Cspat pat) } } +/* Look up a style for a context pattern. This does the matching. */ + static Cstyle lookupcstyle(char *ctxt, char *style) { @@ -2348,19 +2538,25 @@ bin_compstyles(char *nam, char **args, char *ops, int func) return 0; } +/* Tags stuff. */ + typedef struct ctags *Ctags; typedef struct ctset *Ctset; +/* A bunch of tag sets. */ + struct ctags { - char **all; - char *context; - int init; - Ctset sets; + 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; + char **tags; /* the tags */ }; /* Array of tag-set infos. Index is the locallevel. */ @@ -2398,6 +2594,8 @@ freectags(Ctags t) } } +/* Set the tags for the current local level. */ + static void settags(char **tags) { @@ -2416,6 +2614,8 @@ settags(char **tags) t->init = 1; } +/* Check if an array contains a string. */ + static int arrcontains(char **a, char *s) { -- cgit 1.4.1