diff -r 000000000000 -r 2e3d3ce01487 openenvutils/commandshell/shell/src/subst.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/openenvutils/commandshell/shell/src/subst.c Tue Feb 02 10:12:00 2010 +0200 @@ -0,0 +1,3067 @@ +// subst.c - various substitutions +// +// © Portions Copyright (c) Symbian Software Ltd 2007. All rights reserved. +// +/* + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * 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 Paul Falstad 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 Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad 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 Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "zsh.mdh" +#include "subst.pro" + +#ifdef __SYMBIAN32__ +#ifdef __WINSCW__ +#pragma warn_possunwant off +#endif//__WINSCW__ +#endif//__SYMBIAN32__ + +/**/ +char nulstring[] = {Nularg, '\0'}; + +/* Do substitutions before fork. These are: + * - Process substitution: <(...), >(...), =(...) + * - Parameter substitution + * - Command substitution + * Followed by + * - Quote removal + * - Brace expansion + * - Tilde and equals substitution + * + * PF_* flags are defined in zsh.h + */ + +/**/ +mod_export void +prefork(LinkList list, int flags) +{ + LinkNode node, stop = 0; + int keep = 0, asssub = (flags & PF_TYPESET) && isset(KSHTYPESET); + + queue_signals(); + for (node = firstnode(list); node; incnode(node)) { + char *str, c; + + str = (char *)getdata(node); + if (((c = *str) == Inang || c == Outang || c == Equals) && + str[1] == Inpar) { + if (c == Inang || c == Outang) + setdata(node, (void *) getproc(str)); /* <(...) or >(...) */ + else + setdata(node, (void *) getoutputfile(str)); /* =(...) */ + if (!getdata(node)) { + unqueue_signals(); + return; + } + } else { + if (isset(SHFILEEXPANSION)) + filesub((char **)getaddrdata(node), + flags & (PF_TYPESET|PF_ASSIGN)); + if (!(node = stringsubst(list, node, flags & PF_SINGLE, asssub))) { + unqueue_signals(); + return; + } + } + } + for (node = firstnode(list); node; incnode(node)) { + if (node == stop) + keep = 0; + if (*(char *)getdata(node)) { + remnulargs(getdata(node)); + if (unset(IGNOREBRACES) && !(flags & PF_SINGLE)) { + if (!keep) + stop = nextnode(node); + while (hasbraces(getdata(node))) { + keep = 1; + xpandbraces(list, &node); + } + } + if (unset(SHFILEEXPANSION)) + filesub((char **)getaddrdata(node), + flags & (PF_TYPESET|PF_ASSIGN)); + } else if (!(flags & PF_SINGLE) && !keep) + uremnode(list, node); + if (errflag) { + unqueue_signals(); + return; + } + } + unqueue_signals(); +} + +/**/ +static LinkNode +stringsubst(LinkList list, LinkNode node, int ssub, int asssub) +{ + int qt; + char *str3 = (char *)getdata(node); + char *str = str3, c; + + while (!errflag && (c = *str)) { + if ((qt = c == Qstring) || c == String) { + if ((c = str[1]) == Inpar) { + if (!qt) + mult_isarr = 1; + str++; + goto comsub; + } else if (c == Inbrack) { + /* $[...] */ + char *str2 = str; + str2++; + if (skipparens(Inbrack, Outbrack, &str2)) { + zerr("closing bracket missing", NULL, 0); + return NULL; + } + str2[-1] = *str = '\0'; + str = arithsubst(str + 2, &str3, str2); + setdata(node, (void *) str3); + continue; + } else if (c == Snull) { + str = getkeystring(str, NULL, 4, NULL); + continue; + } else { + node = paramsubst(list, node, &str, qt, ssub); + if (errflag || !node) + return NULL; + str3 = (char *)getdata(node); + continue; + } + } else if ((qt = c == Qtick) || (c == Tick ? (mult_isarr = 1) : 0)) + comsub: { + LinkList pl; + char *s, *str2 = str; + char endchar; + int l1, l2; + + if (c == Inpar) { + endchar = Outpar; + str[-1] = '\0'; +#ifdef DEBUG + if (skipparens(Inpar, Outpar, &str)) + dputs("BUG: parse error in command substitution"); +#else + skipparens(Inpar, Outpar, &str); +#endif + str--; + } else { + endchar = c; + *str = '\0'; + + while (*++str != endchar) + DPUTS(!*str, "BUG: parse error in command substitution"); + } + *str++ = '\0'; + if (endchar == Outpar && str2[1] == '(' && str[-2] == ')') { + /* Math substitution of the form $((...)) */ + str[-2] = '\0'; + str = arithsubst(str2 + 2, &str3, str); + setdata(node, (void *) str3); + continue; + } + + /* It is a command substitution, which will be parsed again * + * by the lexer, so we untokenize it first, but we cannot use * + * untokenize() since in the case of `...` some Bnulls should * + * be left unchanged. Note that the lexer doesn't tokenize * + * the body of a command substitution so if there are some * + * tokens here they are from a ${(e)~...} substitution. */ + for (str = str2; (c = *++str); ) + if (itok(c) && c != Nularg && + !(endchar != Outpar && c == Bnull && + (str[1] == '$' || str[1] == '\\' || str[1] == '`' || + (qt && str[1] == '"')))) + *str = ztokens[c - Pound]; + str++; + if (!(pl = getoutput(str2 + 1, qt || ssub))) { + zerr("parse error in command substitution", NULL, 0); + return NULL; + } + if (endchar == Outpar) + str2--; + if (!(s = (char *) ugetnode(pl))) { + str = strcpy(str2, str); + continue; + } + if (!qt && ssub && isset(GLOBSUBST)) + shtokenize(s); + l1 = str2 - str3; + l2 = strlen(s); + if (nonempty(pl)) { + LinkNode n = lastnode(pl); + str2 = (char *) hcalloc(l1 + l2 + 1); + strcpy(str2, str3); + strcpy(str2 + l1, s); + setdata(node, str2); + insertlinklist(pl, node, list); + s = (char *) getdata(node = n); + l1 = 0; + l2 = strlen(s); + } + str2 = (char *) hcalloc(l1 + l2 + strlen(str) + 1); + if (l1) + strcpy(str2, str3); + strcpy(str2 + l1, s); + str = strcpy(str2 + l1 + l2, str); + str3 = str2; + setdata(node, str3); + continue; + } else if (asssub && ((c == '=') || c == Equals) && str != str3) { + /* + * We are in a normal argument which looks like an assignment + * and is to be treated like one, with no word splitting. + */ + ssub = 1; + } + str++; + } + return errflag ? NULL : node; +} + +/* + * Simplified version of the prefork/singsub processing where + * we only do substitutions appropriate to quoting. Currently + * this means only the expansions in $'....'. This is used + * for the end tag for here documents. As we are not doing + * `...` expansions, we just use those for quoting. However, + * they stay in the text. This is weird, but that's not + * my fault. + * + * The remnulargs() makes this consistent with the other forms + * of substitution, indicating that quotes have been fully + * processed. + */ + +/**/ +void +quotesubst(char *str) +{ + char *s = str; + + while (*s) { + if (*s == String && s[1] == Snull) { + s = getkeystring(s, NULL, 4, NULL); + } else { + s++; + } + } + remnulargs(str); +} + +/**/ +mod_export void +globlist(LinkList list, int nountok) +{ + LinkNode node, next; + + badcshglob = 0; + for (node = firstnode(list); !errflag && node; node = next) { + next = nextnode(node); + zglob(list, node, nountok); + } + if (badcshglob == 1) + zerr("no match", NULL, 0); +} + +/* perform substitution on a single word */ + +/**/ +mod_export void +singsub(char **s) +{ + int omi = mult_isarr; + local_list1(foo); + + init_list1(foo, *s); + + prefork(&foo, PF_SINGLE); + mult_isarr = omi; + if (errflag) + return; + *s = (char *) ugetnode(&foo); + DPUTS(nonempty(&foo), "BUG: singsub() produced more than one word!"); +} + +/* Perform substitution on a single word. Unlike with singsub, the * + * result can have more than one word. A single word result is stored * + * in *s and *isarr is set to zero; otherwise *isarr is set to 1 and * + * the result is stored in *a. If `a' is zero a multiple word result is * + * joined using sep or the IFS parameter if sep is zero and the result * + * is returned in *s. The return value is true iff the expansion * + * resulted in an empty list. * + * The mult_isarr variable is used by paramsubst() to tell if it yields * + * an array. */ + +/**/ +static int mult_isarr; + +/**/ +static int +multsub(char **s, char ***a, int *isarr, UNUSED(char *sep)) +{ + int l, omi = mult_isarr; + char **r, **p; + local_list1(foo); +#ifdef __SYMBIAN32__ + sep=sep; +#endif + mult_isarr = 0; + init_list1(foo, *s); + prefork(&foo, 0); + if (errflag) { + if (isarr) + *isarr = 0; + mult_isarr = omi; + return 0; + } + if ((l = countlinknodes(&foo))) { + p = r = hcalloc((l + 1) * sizeof(char*)); + while (nonempty(&foo)) + *p++ = (char *)ugetnode(&foo); + *p = NULL; + /* + * This is the most obscure way of deciding whether a value is + * an array it would be possible to imagine. It seems to result + * partly because we don't pass down the qt and ssub flags from + * paramsubst() through prefork() properly, partly because we + * don't tidy up to get back the return type from multsub we + * need properly. The crux of neatening this up is to get rid + * of the following test. + */ + if (a && mult_isarr) { + *a = r; + *isarr = SCANPM_MATCHMANY; + mult_isarr = omi; + return 0; + } + *s = sepjoin(r, NULL, 1); + mult_isarr = omi; + if (isarr) + *isarr = 0; + return 0; + } + if (l) + *s = (char *) ugetnode(&foo); + else + *s = dupstring(""); + if (isarr) + *isarr = 0; + mult_isarr = omi; + return !l; +} + +/* + * ~, = subs: assign & PF_TYPESET => typeset or magic equals + * assign & PF_ASSIGN => normal assignment + */ + +/**/ +mod_export void +filesub(char **namptr, int assign) +{ + char *eql = NULL, *sub = NULL, *str, *ptr; + int len; + + filesubstr(namptr, assign); + + if (!assign) + return; + + if (assign & PF_TYPESET) { + if ((*namptr)[1] && (eql = sub = strchr(*namptr + 1, Equals))) { + str = sub + 1; + if ((sub[1] == Tilde || sub[1] == Equals) && filesubstr(&str, assign)) { + sub[1] = '\0'; + *namptr = dyncat(*namptr, str); + } + } else + return; + } + + ptr = *namptr; + while ((sub = strchr(ptr, ':'))) { + str = sub + 1; + len = sub - *namptr; + if (sub > eql && + (sub[1] == Tilde || sub[1] == Equals) && + filesubstr(&str, assign)) { + sub[1] = '\0'; + *namptr = dyncat(*namptr, str); + } + ptr = *namptr + len + 1; + } +} + +/**/ +mod_export int +filesubstr(char **namptr, int assign) +{ +#define isend(c) ( !(c) || (c)=='/' || (c)==Inpar || (assign && (c)==':') ) +#define isend2(c) ( !(c) || (c)==Inpar || (assign && (c)==':') ) + char *str = *namptr; + + if (*str == Tilde && str[1] != '=' && str[1] != Equals) { + char *ptr; + int val; + + val = zstrtol(str + 1, &ptr, 10); + if (isend(str[1])) { /* ~ */ + *namptr = dyncat(home, str + 1); + return 1; + } else if (str[1] == '+' && isend(str[2])) { /* ~+ */ + *namptr = dyncat(pwd, str + 2); + return 1; + } else if (str[1] == '-' && isend(str[2])) { /* ~- */ + char *tmp; + *namptr = dyncat((tmp = oldpwd) ? tmp : pwd, str + 2); + return 1; + } else if (!inblank(str[1]) && isend(*ptr) && + (!idigit(str[1]) || (ptr - str < 4))) { + char *ds; + + if (val < 0) + val = -val; + ds = dstackent(str[1], val); + if (!ds) + return 0; + *namptr = dyncat(ds, ptr); + return 1; + } else if (iuser(str[1])) { /* ~foo */ + char *ptr, *hom, save; + + for (ptr = ++str; *ptr && iuser(*ptr); ptr++); + save = *ptr; + if (!isend(save)) + return 0; + *ptr = 0; + if (!(hom = getnameddir(str))) { + if (isset(NOMATCH)) + zerr("no such user or named directory: %s", str, 0); + *ptr = save; + return 0; + } + *ptr = save; + *namptr = dyncat(hom, ptr); + return 1; + } + } else if (*str == Equals && isset(EQUALS) && str[1]) { /* =foo */ + char sav, *pp, *cnam; + + for (pp = str + 1; !isend2(*pp); pp++); + sav = *pp; + *pp = 0; + if (!(cnam = findcmd(str + 1, 1))) { + if (isset(NOMATCH)) + zerr("%s not found", str + 1, 0); + return 0; + } + *namptr = dupstring(cnam); + if (sav) { + *pp = sav; + *namptr = dyncat(*namptr, pp); + } + return 1; + } + return 0; +#undef isend +#undef isend2 +} + +/**/ +static char * +strcatsub(char **d, char *pb, char *pe, char *src, int l, char *s, int glbsub, + int copied) +{ + char *dest; + int pl = pe - pb; + + if (!pl && (!s || !*s)) { + *d = dest = (copied ? src : dupstring(src)); + if (glbsub) + shtokenize(dest); + } else { + *d = dest = hcalloc(pl + l + (s ? strlen(s) : 0) + 1); + strncpy(dest, pb, pl); + dest += pl; + strcpy(dest, src); + if (glbsub) + shtokenize(dest); + dest += l; + if (s) + strcpy(dest, s); + } + return dest; +} + +typedef int (*CompareFn) _((const void *, const void *)); + +/**/ +int +strpcmp(const void *a, const void *b) +{ +#ifdef HAVE_STRCOLL + return strcoll(*(char **)a, *(char **)b); +#else + return strcmp(*(char **)a, *(char **)b); +#endif +} + +/**/ +int +invstrpcmp(const void *a, const void *b) +{ +#ifdef HAVE_STRCOLL + return -strcoll(*(char **)a, *(char **)b); +#else + return -strcmp(*(char **)a, *(char **)b); +#endif +} + +/**/ +int +cstrpcmp(const void *a, const void *b) +{ +#ifdef HAVE_STRCOLL + VARARR(char, c, strlen(*(char **) a) + 1); + VARARR(char, d, strlen(*(char **) b) + 1); + char *s, *t; + int cmp; + + for (s = *(char **) a, t = c; (*t++ = tulower(*s++));); + for (s = *(char **) b, t = d; (*t++ = tulower(*s++));); + + cmp = strcoll(c, d); + + return cmp; +#else + char *c = *(char **)a, *d = *(char **)b; + + for (; *c && tulower(*c) == tulower(*d); c++, d++); + + return (int)STOUC(tulower(*c)) - (int)STOUC(tulower(*d)); +#endif +} + +/**/ +int +invcstrpcmp(const void *a, const void *b) +{ +#ifdef HAVE_STRCOLL + VARARR(char, c, strlen(*(char **) a) + 1); + VARARR(char, d, strlen(*(char **) b) + 1); + char *s, *t; + int cmp; + + for (s = *(char **) a, t = c; (*t++ = tulower(*s++));); + for (s = *(char **) b, t = d; (*t++ = tulower(*s++));); + + cmp = strcoll(c, d); + + return -cmp; +#else + char *c = *(char **)a, *d = *(char **)b; + + for (; *c && tulower(*c) == tulower(*d); c++, d++); + + return (int)STOUC(tulower(*d)) - (int)STOUC(tulower(*c)); +#endif +} + +/**/ +int +nstrpcmp(const void *a, const void *b) +{ + char *c = *(char **)a, *d = *(char **)b; + int cmp; + +#ifdef HAVE_STRCOLL + cmp = strcoll(c, d); +#endif + for (; *c == *d && *c; c++, d++); +#ifndef HAVE_STRCOLL + cmp = (int)STOUC(*c) - (int)STOUC(*d); +#endif + if (idigit(*c) || idigit(*d)) { + for (; c > *(char **)a && idigit(c[-1]); c--, d--); + if (idigit(*c) && idigit(*d)) { + while (*c == '0') + c++; + while (*d == '0') + d++; + for (; idigit(*c) && *c == *d; c++, d++); + if (idigit(*c) || idigit(*d)) { + cmp = (int)STOUC(*c) - (int)STOUC(*d); + while (idigit(*c) && idigit(*d)) + c++, d++; + if (idigit(*c) && !idigit(*d)) + return 1; + if (idigit(*d) && !idigit(*c)) + return -1; + } + } + } + return cmp; +} + +/**/ +int +invnstrpcmp(const void *a, const void *b) +{ + return -nstrpcmp(a, b); +} + +/**/ +int +instrpcmp(const void *a, const void *b) +{ + VARARR(char, c, strlen(*(char **) a) + 1); + VARARR(char, d, strlen(*(char **) b) + 1); + char **e = (char **)&c; + char **f = (char **)&d; + char *s, *t; + + for (s = *(char **) a, t = c; (*t++ = tulower(*s++));); + for (s = *(char **) b, t = d; (*t++ = tulower(*s++));); + + return nstrpcmp(&e, &f); +} + +/**/ +int +invinstrpcmp(const void *a, const void *b) +{ + return -instrpcmp(a, b); +} + +/**/ +static char * +dopadding(char *str, int prenum, int postnum, char *preone, char *postone, char *premul, char *postmul) +{ + char def[3], *ret, *t, *r; + int ls, ls2, lpreone, lpostone, lpremul, lpostmul, lr, f, m, c, cc; + + def[0] = *ifs ? *ifs : ' '; + def[1] = *ifs == Meta ? ifs[1] ^ 32 : '\0'; + def[2] = '\0'; + if (preone && !*preone) + preone = def; + if (postone && !*postone) + postone = def; + if (!premul || !*premul) + premul = def; + if (!postmul || !*postmul) + postmul = def; + + ls = strlen(str); + lpreone = preone ? strlen(preone) : 0; + lpostone = postone ? strlen(postone) : 0; + lpremul = strlen(premul); + lpostmul = strlen(postmul); + + lr = prenum + postnum; + + if (lr == ls) + return str; + + r = ret = (char *)zhalloc(lr + 1); + + if (prenum) { + if (postnum) { + ls2 = ls / 2; + + f = prenum - ls2; + if (f <= 0) + for (str -= f, c = prenum; c--; *r++ = *str++); + else { + if (f <= lpreone) + for (c = f, t = preone + lpreone - f; c--; *r++ = *t++); + else { + f -= lpreone; + if ((m = f % lpremul)) + for (c = m, t = premul + lpremul - m; c--; *r++ = *t++); + for (cc = f / lpremul; cc--;) + for (c = lpremul, t = premul; c--; *r++ = *t++); + for (c = lpreone; c--; *r++ = *preone++); + } + for (c = ls2; c--; *r++ = *str++); + } + ls2 = ls - ls2; + f = postnum - ls2; + if (f <= 0) + for (c = postnum; c--; *r++ = *str++); + else { + for (c = ls2; c--; *r++ = *str++); + if (f <= lpostone) + for (c = f; c--; *r++ = *postone++); + else { + f -= lpostone; + for (c = lpostone; c--; *r++ = *postone++); + for (cc = f / lpostmul; cc--;) + for (c = lpostmul, t = postmul; c--; *r++ = *t++); + if ((m = f % lpostmul)) + for (; m--; *r++ = *postmul++); + } + } + } else { + f = prenum - ls; + if (f <= 0) + for (c = prenum, str -= f; c--; *r++ = *str++); + else { + if (f <= lpreone) + for (c = f, t = preone + lpreone - f; c--; *r++ = *t++); + else { + f -= lpreone; + if ((m = f % lpremul)) + for (c = m, t = premul + lpremul - m; c--; *r++ = *t++); + for (cc = f / lpremul; cc--;) + for (c = lpremul, t = premul; c--; *r++ = *t++); + for (c = lpreone; c--; *r++ = *preone++); + } + for (c = ls; c--; *r++ = *str++); + } + } + } else if (postnum) { + f = postnum - ls; + if (f <= 0) + for (c = postnum; c--; *r++ = *str++); + else { + for (c = ls; c--; *r++ = *str++); + if (f <= lpostone) + for (c = f; c--; *r++ = *postone++); + else { + f -= lpostone; + for (c = lpostone; c--; *r++ = *postone++); + for (cc = f / lpostmul; cc--;) + for (c = lpostmul, t = postmul; c--; *r++ = *t++); + if ((m = f % lpostmul)) + for (; m--; *r++ = *postmul++); + } + } + } + *r = '\0'; + + return ret; +} + +/**/ +char * +get_strarg(char *s) +{ + char t = *s++; + + if (!t) + return s - 1; + + switch (t) { + case '(': + t = ')'; + break; + case '[': + t = ']'; + break; + case '{': + t = '}'; + break; + case '<': + t = '>'; + break; + case Inpar: + t = Outpar; + break; + case Inang: + t = Outang; + break; + case Inbrace: + t = Outbrace; + break; + case Inbrack: + t = Outbrack; + break; + } + + while (*s && *s != t) + s++; + + return s; +} + +/**/ +static int +get_intarg(char **s) +{ + char *t = get_strarg(*s + 1); + char *p, sav; + zlong ret; + + if (!*t) + return -1; + sav = *t; + *t = '\0'; + p = dupstring(*s + 2); + *s = t; + *t = sav; + if (parsestr(p)) + return -1; + singsub(&p); + if (errflag) + return -1; + ret = mathevali(p); + if (errflag) + return -1; + if (ret < 0) + ret = -ret; + return ret < 0 ? -ret : ret; +} + +/* Parsing for the (e) flag. */ + +static int +subst_parse_str(char **sp, int single, int err) +{ + char *s; + + *sp = s = dupstring(*sp); + + if (!(err ? parsestr(s) : parsestrnoerr(s))) { + if (!single) { + int qt = 0; + + for (; *s; s++) + if (!qt) { + if (*s == Qstring) + *s = String; + else if (*s == Qtick) + *s = Tick; + } else if (*s == Dnull) + qt = !qt; + } + return 0; + } + return 1; +} + +/* parameter substitution */ + +#define isstring(c) ((c) == '$' || (char)(c) == String || (char)(c) == Qstring) +#define isbrack(c) ((c) == '[' || (char)(c) == Inbrack) + +/* + * Given a linked list l with node n, perform parameter substitution + * starting from *str. Return the node with the substitutuion performed + * or NULL if it failed. + * + * If qt is true, the `$' was quoted. TODO: why can't we just look + * to see if the first character was String or Qstring? + * + * If ssub is true, we are being called via singsubst(), which means + * the result will be a single word. TODO: can we generate the + * single word at the end? TODO: if not, or maybe in any case, + * can we pass down the ssub flag from prefork with the other flags + * instead of pushing it into different arguments? (How exactly + * to qt and ssub differ? Are both necessary, if so is there some + * better way of separating the two?) + */ + +/**/ +LinkNode +paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub) +{ + char *aptr = *str, c, cc; + char *s = aptr, *fstr, *idbeg, *idend, *ostr = (char *) getdata(n); + int colf; /* != 0 means we found a colon after the name */ + /* + * There are far too many flags. They need to be grouped + * together into some structure which ties them to where they + * came from. + * + * Some flags have a an obscure relationship to their effect which + * depends on incrementing them to particular values in particular + * ways. + */ + /* + * Whether the value is an array (in aval) or not (in val). There's + * a movement from storing the value in the stuff read from the + * parameter (the value v) to storing them in val and aval. + * However, sometimes you find v reappearing temporarily. + * + * The values -1 and 2 are special to isarr. It looks like 2 is + * some kind of an internal flag to do with whether the array's been + * copied, in which case I don't know why we don't use the copied + * flag, but they do both occur close together so they presumably + * have different effects. The value -1 is isued to force us to + * keep an empty array. It's tested in the YUK chunk (I mean the + * one explicitly marked as such). + */ + int isarr = 0; + /* + * This is just the setting of the option except we need to + * take account of ^ and ^^. + */ + int plan9 = isset(RCEXPANDPARAM); + /* + * Likwise, but with ~ and ~~. Also, we turn it off later + * on if qt is passed down. + */ + int globsubst = isset(GLOBSUBST); + /* + * Indicates ${#pm}, massaged by whichlen which is set by + * the (c), (w), and (W) flags to indicate how we take the length. + */ + int getlen = 0; + int whichlen = 0; + /* + * Indicates ${+pm}: a simple boolean for once. + */ + int chkset = 0; + /* + * Indicates we have tried to get a value in v but that was + * unset. I don't quite understand why (v == NULL) isn't + * good enough, but there are places where we seem to need + * to second guess whether a value is a real value or not. + */ + int vunset = 0; + /* + * Indicates (t) flag, i.e. print out types. The code for + * this actually isn't too horrifically inbred compared with + * that for (P). + */ + int wantt = 0; + /* + * Indicates spliting a string into an array. There aren't + * actually that many special cases for this --- which may + * be why it doesn't work properly; we split in some cases + * where we shouldn't, in particular on the multsubs for + * handling embedded values for ${...=...} and the like. + */ + int spbreak = isset(SHWORDSPLIT) && !ssub && !qt; + /* Scalar and array value, see isarr above */ + char *val = NULL, **aval = NULL; + /* + * Padding based on setting in parameter rather than substitution + * flags. This is only used locally. + */ + unsigned int fwidth = 0; + /* + * vbuf and v are both used to retrieve parameter values; this + * is a kludge, we pass down vbuf and it may or may not return v. + */ + struct value vbuf; + Value v = NULL; + /* + * This expressive name refers to the set of flags which + * is applied to matching for #, %, / and their doubled variants: + * (M), (R), (B), (E), (N), (S). + */ + int flags = 0; + /* Value from (I) flag, used for ditto. */ + int flnum = 0; + /* + * sortit is an obscure combination of the settings for (o), (O), + * (i) and (n). casind is (i) and numord is (n); these are + * separate so we can have fun doing the obscure combinatorics later. + * indord is the (a) flag, which for consistency doesn't get + * combined into sortit. + */ + int sortit = 0, casind = 0, numord = 0, indord = 0; + /* (u): straightforward. */ + int unique = 0; + /* combination of (L), (U) and (C) flags. */ + int casmod = 0; + /* + * quotemod says we are doing either (q) (positive), (Q) (negative) + * or not (0). quotetype counts the q's for the first case. + * quoterr is simply (X) but gets passed around a lot because the + * combination (eX) needs it. + */ + int quotemod = 0, quotetype = 0, quoteerr = 0; + /* + * (V) flag: fairly straightforward, except that as with so + * many flags it's not easy to decide where to put it in the order. + */ + int visiblemod = 0; + /* + * The (z) flag, nothing to do with SH_WORD_SPLIT which is tied + * spbreak, see above; fairly straighforward in use but c.f. + * the comment for visiblemod. + */ + int shsplit = 0; + /* + * The separator from (j) and (s) respectively, or (F) and (f) + * respectively (hardwired to "\n" in that case). Slightly + * confusingly also used for ${#pm}, thought that's at least + * documented in the manual + */ + char *sep = NULL, *spsep = NULL; + /* + * Padding strings. The left and right padding strings which + * are repeated, then the ones which only occur once, for + * the (l) and (r) flags. + */ + char *premul = NULL, *postmul = NULL, *preone = NULL, *postone = NULL; + /* Replacement string for /orig/repl and //orig/repl */ + char *replstr = NULL; + /* The numbers for (l) and (r) */ + zlong prenum = 0, postnum = 0; + /* + * Whether the value has been copied. Optimisation: if we + * are modifying an expression, we only need to copy it the + * first time, and if we don't modify it we can just use the + * value from the parameter or input. + */ + int copied = 0; + /* + * The (A) flag for array assignment, with consequences for + * splitting and joining; (AA) gives arrasg == 2 for associative + * arrays. + */ + int arrasg = 0; + /* + * The (e) flag. As we need to do extra work not quite + * at the end, the effect of this is kludged in in several places. + */ + int eval = 0; + /* + * The (P) flag. This interacts a bit obscurely with whether + * or not we are dealing with a sub expression (subexp). + */ + int aspar = 0; + /* + * The (%) flag, c.f. visiblemod again. + */ + int presc = 0; + /* + * The (@) flag; interacts obscurely with qt and isarr. + * This is one of the things that decides whether multsub + * will produce an array, but in an extremely indirect fashion. + */ + int nojoin = 0; + /* + * != 0 means ${...}, otherwise $... What works without braces + * is largely a historical artefact (everything works with braces, + * I sincerely hope). + */ + char inbrace = 0; + /* + * Use for the (k) flag. Goes down into the parameter code, + * sometimes. + */ + char hkeys = 0; + /* + * Used for the (v) flag, ditto. Not quite sure why they're + * separate, but the tradition seems to be that things only + * get combined when that makes the result more obscure rather + * than less. + */ + char hvals = 0; + /* + * Whether we had to evaluate a subexpression, i.e. an + * internal ${...} or $(...) or plain $pm. We almost don't + * need to remember this (which would be neater), but the (P) + * flag means the subexp and !subexp code is obscurely combined, + * and the argument passing to fetchvalue has another kludge. + */ + int subexp; + + *s++ = '\0'; + /* + * Nothing to do unless the character following the $ is + * something we recognise. + * + * Shouldn't this be a table or something? We test for all + * these later on, too. + */ + if (!ialnum(c = *s) && c != '#' && c != Pound && c != '-' && + c != '!' && c != '$' && c != String && c != Qstring && + c != '?' && c != Quest && c != '_' && + c != '*' && c != Star && c != '@' && c != '{' && + c != Inbrace && c != '=' && c != Equals && c != Hat && + c != '^' && c != '~' && c != Tilde && c != '+') { + s[-1] = '$'; + *str = s; + return n; + } + DPUTS(c == '{', "BUG: inbrace == '{' in paramsubst()"); + /* + * Extra processing if there is an opening brace: mostly + * flags in parentheses, but also one ksh hack. + */ + if (c == Inbrace) { + inbrace = 1; + s++; + /* + * In ksh emulation a leading `!' is a special flag working + * sort of like our (k). + * TODO: this is one of very few cases tied directly to + * the emulation mode rather than an option. Since ksh + * doesn't have parameter flags it might be neater to + * handle this with the ^, =, ~ stuff, below. + */ + if ((c = *s) == '!' && s[1] != Outbrace && emulation == EMULATE_KSH) { + hkeys = SCANPM_WANTKEYS; + s++; + } else if (c == '(' || c == Inpar) { + char *t, sav; + int tt = 0; + zlong num; + /* + * The (p) flag is (uniquely) only remembered within + * this block. It says we do print-style handling + * on the values for flags, but only on those. + * This explains the ghastly macro, but why can't it + * be a function? UNTOK_AND_ESCAPE is defined + * so that the argument must be an lvalue. + */ + int escapes = 0; + int klen; +#define UNTOK(C) (itok(C) ? ztokens[(C) - Pound] : (C)) +#define UNTOK_AND_ESCAPE(X) {\ + untokenize(X = dupstring(s + 1));\ + if (escapes) {\ + X = getkeystring(X, &klen, 3, NULL);\ + X = metafy(X, klen, META_HREALLOC);\ + }\ + } + + for (s++; (c = *s) != ')' && c != Outpar; s++, tt = 0) { + switch (c) { + case ')': + case Outpar: + break; + case 'A': + ++arrasg; + break; + case '@': + nojoin = 1; + break; + case 'M': + flags |= SUB_MATCH; + break; + case 'R': + flags |= SUB_REST; + break; + case 'B': + flags |= SUB_BIND; + break; + case 'E': + flags |= SUB_EIND; + break; + case 'N': + flags |= SUB_LEN; + break; + case 'S': + flags |= SUB_SUBSTR; + break; + case 'I': + flnum = get_intarg(&s); + if (flnum < 0) + goto flagerr; + break; + + case 'L': + casmod = 2; + break; + case 'U': + casmod = 1; + break; + case 'C': + casmod = 3; + break; + + case 'o': + sortit = 1; + break; + case 'O': + sortit = 2; + break; + case 'i': + casind = 1; + break; + case 'n': + numord = 1; + break; + case 'a': + indord = 1; + break; + + case 'V': + visiblemod++; + break; + + case 'q': + quotemod++, quotetype++; + break; + case 'Q': + quotemod--; + break; + case 'X': + quoteerr = 1; + break; + + case 'e': + eval = 1; + break; + case 'P': + aspar = 1; + break; + + case 'c': + whichlen = 1; + break; + case 'w': + whichlen = 2; + break; + case 'W': + whichlen = 3; + break; + + case 'f': + spsep = "\n"; + break; + case 'F': + sep = "\n"; + break; + + case 's': + tt = 1; + /* fall through */ + case 'j': + t = get_strarg(++s); + if (*t) { + sav = *t; + *t = '\0'; + if (tt) + UNTOK_AND_ESCAPE(spsep) + else + UNTOK_AND_ESCAPE(sep) + *t = sav; + s = t; + } else + goto flagerr; + break; + + case 'l': + tt = 1; + /* fall through */ + case 'r': + sav = s[1]; + num = get_intarg(&s); + if (num < 0) + goto flagerr; + if (tt) + prenum = num; + else + postnum = num; + if (UNTOK(s[1]) != UNTOK(sav)) + break; + t = get_strarg(++s); + if (!*t) + goto flagerr; + sav = *t; + *t = '\0'; + if (tt) + UNTOK_AND_ESCAPE(premul) + else + UNTOK_AND_ESCAPE(postmul) + *t = sav; + sav = *s; + s = t + 1; + if (UNTOK(*s) != UNTOK(sav)) { + s--; + break; + } + t = get_strarg(s); + if (!*t) + goto flagerr; + sav = *t; + *t = '\0'; + if (tt) + UNTOK_AND_ESCAPE(preone) + else + UNTOK_AND_ESCAPE(postone) + *t = sav; + s = t; + break; + + case 'p': + escapes = 1; + break; + + case 'k': + hkeys = SCANPM_WANTKEYS; + break; + case 'v': + hvals = SCANPM_WANTVALS; + break; + + case 't': + wantt = 1; + break; + + case '%': + presc++; + break; + + case 'z': + shsplit = 1; + break; + + case 'u': + unique = 1; + break; + + default: + flagerr: + zerr("error in flags", NULL, 0); + return NULL; + } + } + s++; + } + } + /* Sort is done by indexing on sortit-1: + * bit 1: ascending (o)/descending (O) + * bit 2: case sensitive/independent (i) + * bit 3: strict order/numeric (n) + * unless indord (a) is set set, in which case only test for + * descending by assuming only (O) is possible (not verified). + */ + if (sortit) + sortit += (casind << 1) + (numord << 2); + + /* + * premul, postmul specify the padding character to be used + * multiple times with the (l) and (r) flags respectively. + */ + if (!premul) + premul = " "; + if (!postmul) + postmul = " "; + + /* + * Look for special unparenthesised flags. + * TODO: could make these able to appear inside parentheses, too, + * i.e. ${(^)...} etc. + */ + for (;;) { + if ((c = *s) == '^' || c == Hat) { + /* RC_EXPAND_PARAM on or off (doubled )*/ + if ((c = *++s) == '^' || c == Hat) { + plan9 = 0; + s++; + } else + plan9 = 1; + } else if ((c = *s) == '=' || c == Equals) { + /* SH_WORD_SPLIT on or off (doubled). spbreak = 2 means force */ + if ((c = *++s) == '=' || c == Equals) { + spbreak = 0; + s++; + } else + spbreak = 2; + } else if ((c == '#' || c == Pound) && + (iident(cc = s[1]) + || cc == '*' || cc == Star || cc == '@' + || cc == '-' || (cc == ':' && s[2] == '-') + || (isstring(cc) && (s[2] == Inbrace || s[2] == Inpar)))) { + getlen = 1 + whichlen, s++; + /* + * Return the length of the parameter. + * getlen can be more than 1 to indicate characters (2), + * words ignoring multiple delimiters (3), words taking + * account of multiple delimiters. delimiter is in + * spsep, NULL means $IFS. + */ + } else if (c == '~' || c == Tilde) { + /* GLOB_SUBST on or off (doubled) */ + if ((c = *++s) == '~' || c == Tilde) { + globsubst = 0; + s++; + } else + globsubst = 1; + } else if (c == '+') { + /* + * Return whether indicated parameter is set. + * Try to handle this when parameter is named + * by (P) (second part of test). + */ + if (iident(s[1]) || (aspar && isstring(s[1]) && + (s[2] == Inbrace || s[2] == Inpar))) + chkset = 1, s++; + else if (!inbrace) { + /* Special case for `$+' on its own --- leave unmodified */ + *aptr = '$'; + *str = aptr + 1; + return n; + } else { + zerr("bad substitution", NULL, 0); + return NULL; + } + } else if (inbrace && INULL(*s)) { + /* + * Handles things like ${(f)"$(pm && (v->pm->flags & PM_UNSET))) + vunset = 1; + + if (wantt) { + /* + * Handle the (t) flag: value now becomes the type + * information for the parameter. + */ + if (v && v->pm && !(v->pm->flags & PM_UNSET)) { + int f = v->pm->flags; + + switch (PM_TYPE(f)) { + case PM_SCALAR: val = "scalar"; break; + case PM_ARRAY: val = "array"; break; + case PM_INTEGER: val = "integer"; break; + case PM_EFLOAT: + case PM_FFLOAT: val = "float"; break; + case PM_HASHED: val = "association"; break; + } + val = dupstring(val); + if (v->pm->level) + val = dyncat(val, "-local"); + if (f & PM_LEFT) + val = dyncat(val, "-left"); + if (f & PM_RIGHT_B) + val = dyncat(val, "-right_blanks"); + if (f & PM_RIGHT_Z) + val = dyncat(val, "-right_zeros"); + if (f & PM_LOWER) + val = dyncat(val, "-lower"); + if (f & PM_UPPER) + val = dyncat(val, "-upper"); + if (f & PM_READONLY) + val = dyncat(val, "-readonly"); + if (f & PM_TAGGED) + val = dyncat(val, "-tag"); + if (f & PM_EXPORTED) + val = dyncat(val, "-export"); + if (f & PM_UNIQUE) + val = dyncat(val, "-unique"); + if (f & PM_HIDE) + val = dyncat(val, "-hide"); + if (f & PM_HIDE) + val = dyncat(val, "-hideval"); + if (f & PM_SPECIAL) + val = dyncat(val, "-special"); + vunset = 0; + } else + val = dupstring(""); + + v = NULL; + isarr = 0; + } + } + /* + * We get in here two ways; either we need to convert v into + * the local value system, or we need to get rid of brackets + * even if there isn't a v. + */ + while (v || ((inbrace || (unset(KSHARRAYS) && vunset)) && isbrack(*s))) { + if (!v) { + /* + * Index applied to non-existent parameter; we may or may + * not have a value to index, however. Create a temporary + * empty parameter as a trick, and index on that. This + * usually happens the second time around the loop when + * we've used up the original parameter value and want to + * apply a subscript to what's left. However, it's also + * possible it's got something to do with some of that murky + * passing of -1's as the third argument to fetchvalue() to + * inhibit bracket parsing at that stage. + */ + Param pm; + char *os = s; + + if (!isbrack(*s)) + break; + if (vunset) { + val = dupstring(""); + isarr = 0; + } + pm = createparam(nulstring, isarr ? PM_ARRAY : PM_SCALAR); + DPUTS(!pm, "BUG: parameter not created"); + if (isarr) + pm->u.arr = aval; + else + pm->u.str = val; + v = (Value) hcalloc(sizeof *v); + v->isarr = isarr; + v->pm = pm; + v->end = -1; + if (getindex(&s, v, qt) || s == os) + break; + } + /* + * This is where we extract a value (we know now we have + * one) into the local parameters for a scalar (val) or + * array (aval) value. TODO: move val and aval into + * a structure with a discriminator. Hope we can make + * more things array values at this point and dearrayify later. + * v->isarr tells us whether the stuff form down below looks + * like an array. Unlike multsub() this is probably clean + * enough to keep, although possibly the parameter passing + * needs reorganising. + * + * I think we get to discard the existing value of isarr + * here because it's already been taken account of, either + * in the subexp stuff or immediately above. + */ + if ((isarr = v->isarr)) { + /* No way to get here with v->inv != 0, so getvaluearr() * + * is called by getarrvalue(); needn't test PM_HASHED. */ + if (v->isarr == SCANPM_WANTINDEX) { + isarr = v->isarr = 0; + val = dupstring(v->pm->nam); + } else + aval = getarrvalue(v); + } else { + /* Value retrieved from parameter/subexpression is scalar */ + if (v->pm->flags & PM_ARRAY) { + /* + * Although the value is a scalar, the parameter + * itself is an array. Presumably this is due to + * being quoted, or doing single substitution or something, + * TODO: we're about to do some definitely stringy + * stuff, so something like this bit is probably + * necessary. However, I'd like to leave any + * necessary joining of arrays until this point + * to avoid the multsub() horror. + */ + int tmplen = arrlen(v->pm->gsu.a->getfn(v->pm)); + + if (v->start < 0) + v->start += tmplen + v->inv; + if (!v->inv && (v->start >= tmplen || v->start < 0)) + vunset = 1; + } + if (!vunset) { + /* + * There really is a value. Apply any necessary + * padding or case transformation. Note these + * are the per-parameter transformations specified + * with typeset, not the per-substitution ones set + * by flags. TODO: maybe therefore this would + * be more consistent if moved into getstrvalue()? + * Bet that's easier said than done. + */ + val = getstrvalue(v); + fwidth = v->pm->width ? v->pm->width : (int)strlen(val); + switch (v->pm->flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) { + char *t; + unsigned int t0; + + case PM_LEFT: + case PM_LEFT | PM_RIGHT_Z: + t = val; + if (v->pm->flags & PM_RIGHT_Z) + while (*t == '0') + t++; + else + while (iblank(*t)) + t++; + val = (char *) hcalloc(fwidth + 1); + val[fwidth] = '\0'; + if ((t0 = strlen(t)) > fwidth) + t0 = fwidth; + memset(val, ' ', fwidth); + strncpy(val, t, t0); + break; + case PM_RIGHT_B: + case PM_RIGHT_Z: + case PM_RIGHT_Z | PM_RIGHT_B: + { + int zero = 1; + + if (strlen(val) < fwidth) { + char *valprefend = val; + if (v->pm->flags & PM_RIGHT_Z) { + /* + * This is a documented feature: when deciding + * whether to pad with zeroes, ignore + * leading blanks already in the value; + * only look for numbers after that. + * Not sure how useful this really is. + * It's certainly confusing to code around. + */ + for (t = val; iblank(*t); t++) + ; + /* + * Allow padding after initial minus + * for numeric variables. + */ + if ((v->pm->flags & + (PM_INTEGER|PM_EFLOAT|PM_FFLOAT)) && + *t == '-') + t++; + /* + * Allow padding after initial 0x or + * base# for integer variables. + */ + if (v->pm->flags & PM_INTEGER) { + if (isset(CBASES) && + t[0] == '0' && t[1] == 'x') + t += 2; + else if ((valprefend = strchr(t, '#'))) + t = valprefend + 1; + } + valprefend = t; + if (!*t) + zero = 0; + else if (v->pm->flags & + (PM_INTEGER|PM_EFLOAT|PM_FFLOAT)) { + /* zero always OK */ + } else if (!idigit(*t)) + zero = 0; + } + t = (char *) hcalloc(fwidth + 1); + memset(t, (((v->pm->flags & PM_RIGHT_B) || !zero) ? + ' ' : '0'), fwidth); + /* + * How can the following trigger? We + * haven't altered val or fwidth since + * the last time we tested this. + */ + if ((t0 = strlen(val)) > fwidth) + t0 = fwidth; + /* + * Copy - or 0x or base# before any padding + * zeroes. + */ + if (zero && val != valprefend) { + int preflen = valprefend - val; + memcpy(t, val, preflen); + strcpy(t + (fwidth - t0) + preflen, + valprefend); + } else + strcpy(t + (fwidth - t0), val); + val = t; + } else { + t = (char *) hcalloc(fwidth + 1); + t[fwidth] = '\0'; + strncpy(t, val + strlen(val) - fwidth, fwidth); + val = t; + } + } + break; + } + switch (v->pm->flags & (PM_LOWER | PM_UPPER)) { + char *t; + + case PM_LOWER: + t = val; + for (; (c = *t); t++) + *t = tulower(c); + break; + case PM_UPPER: + t = val; + for (; (c = *t); t++) + *t = tuupper(c); + break; + } + } + } + /* + * Finished with the original parameter and its indices; + * carry on looping to see if we need to do more indexing. + * This means we final get rid of v in favour of val and + * aval. We could do with somehow encapsulating the bit + * where we need v. + */ + v = NULL; + if (!inbrace) + break; + } + /* + * We're now past the name or subexpression; the only things + * which can happen now are a closing brace, one of the standard + * parameter postmodifiers, or a history-style colon-modifier. + * + * Again, this duplicates tests for characters we're about to + * examine properly later on. + */ + if (inbrace && + (c = *s) != '-' && c != '+' && c != ':' && c != '%' && c != '/' && + c != '=' && c != Equals && + c != '#' && c != Pound && + c != '?' && c != Quest && + c != '}' && c != Outbrace) { + zerr("bad substitution", NULL, 0); + return NULL; + } + /* + * Join arrays up if we're in quotes and there isn't some + * override such as (@). + * TODO: hmm, if we're called as part of some recursive + * substitution do we want to delay this until we get back to + * the top level? Or is if there's a qt (i.e. this parameter + * substitution is in quotes) always good enough? Potentially + * we may be OK by now --- all potential `@'s and subexpressions + * have been handled, including any [@] index which comes up + * by virture of v->isarr being set to SCANPM_ISVAR_AT which + * is now in isarr. + * + * However, if we are replacing multsub() with something that + * doesn't mangle arrays, we may need to delay this step until after + * the foo:- or foo:= or whatever that causes that. Note the value + * (string or array) at this point is irrelevant if we are going to + * be doing that. This would mean // and stuff get applied + * arraywise even if quoted. That's probably wrong, so maybe + * this just stays. + * + * We do a separate stage of dearrayification in the YUK chunk, + * I think mostly because of the way we make array or scalar + * values appear to the caller. + */ + if (isarr) { + if (nojoin) + isarr = -1; + if (qt && !getlen && isarr > 0) { + val = sepjoin(aval, sep, 1); + isarr = 0; + } + } + + idend = s; + if (inbrace) { + /* + * This is to match a closing double quote in case + * we didn't have a subexpression, e.g. ${"foo"}. + * This form is pointless, but logically it ought to work. + */ + while (INULL(*s)) + s++; + } + /* + * We don't yet know whether a `:' introduces a history-style + * colon modifier or qualifies something like ${...:=...}. + * But if we remember the colon here it's easy to check later. + */ + if ((colf = *s == ':')) + s++; + + + /* fstr is to be the text following the substitution. If we have * + * braces, we look for it here, else we infer it later on. */ + fstr = s; + if (inbrace) { + int bct; + for (bct = 1; (c = *fstr); fstr++) { + if (c == Inbrace) + bct++; + else if (c == Outbrace && !--bct) + break; + } + + if (bct) { + noclosebrace: + zerr("closing brace expected", NULL, 0); + return NULL; + } + if (c) + *fstr++ = '\0'; + } + + /* Check for ${..?..} or ${..=..} or one of those. * + * Only works if the name is in braces. */ + + if (inbrace && ((c = *s) == '-' || + c == '+' || + c == ':' || /* i.e. a doubled colon */ + c == '=' || c == Equals || + c == '%' || + c == '#' || c == Pound || + c == '?' || c == Quest || + c == '/')) { + + /* + * Default index is 1 if no (I) or (I) gave zero. But + * why don't we set the default explicitly at the start + * and massage any passed index where we set flnum anyway? + */ + if (!flnum) + flnum++; + if (c == '%') + flags |= SUB_END; + + /* Check for ${..%%..} or ${..##..} */ + if ((c == '%' || c == '#' || c == Pound) && c == s[1]) { + s++; + /* we have %%, not %, or ##, not # */ + flags |= SUB_LONG; + } + s++; + if (s[-1] == '/') { + char *ptr; + /* + * previous flags are irrelevant, except for (S) which + * indicates shortest substring; else look for longest. + */ + flags = (flags & SUB_SUBSTR) ? 0 : SUB_LONG; + if ((c = *s) == '/') { + /* doubled, so replace all occurrences */ + flags |= SUB_GLOBAL; + c = *++s; + } + /* Check for anchored substitution */ + if (c == '%') { + /* anchor at tail */ + flags |= SUB_END; + s++; + } else if (c == '#' || c == Pound) { + /* anchor at head: this is the `normal' case in getmatch */ + s++; + } else + flags |= SUB_SUBSTR; + /* + * Find the / marking the end of the search pattern. + * If there isn't one, we're just going to delete that, + * i.e. replace it with an empty string. + * + * We used to use double backslashes to quote slashes, + * but actually that was buggy and using a single backslash + * is easier and more obvious. + */ + for (ptr = s; (c = *ptr) && c != '/'; ptr++) + { + if ((c == Bnull || c == '\\') && ptr[1]) + { + if (ptr[1] == '/') + chuck(ptr); + else + ptr++; + } + } + replstr = (*ptr && ptr[1]) ? ptr+1 : ""; + *ptr = '\0'; + } + + /* See if this was ${...:-...}, ${...:=...}, etc. */ + if (colf) + flags |= SUB_ALL; + /* + * With no special flags, i.e. just a # or % or whatever, + * the matched portion is removed and we keep the rest. + * We also want the rest when we're doing a substitution. + */ + if (!(flags & (SUB_MATCH|SUB_REST|SUB_BIND|SUB_EIND|SUB_LEN))) + flags |= SUB_REST; + + if (colf && !vunset) + vunset = (isarr) ? !*aval : !*val || (*val == Nularg && !val[1]); + + switch (s[-1]) { + case '+': + if (vunset) { + val = dupstring(""); + copied = 1; + isarr = 0; + break; + } + vunset = 1; + /* Fall Through! */ + case '-': + if (vunset) { + val = dupstring(s); + /* + * This is not good enough for sh emulation! Sh would + * split unquoted substrings, yet not split quoted ones + * (except according to $@ rules); but this leaves the + * unquoted substrings unsplit, and other code below + * for spbreak splits even within the quoted substrings. + * + * TODO: I think multsub needs to be told enough to + * decide about splitting with spbreak at this point + * (and equally in the `=' handler below). Then + * we can turn off spbreak to avoid the join & split + * nastiness later. + * + * What we really want to do is make this look as + * if it were the result of an assignment from + * the same value, taking account of quoting. + */ + multsub(&val, (aspar ? NULL : &aval), &isarr, NULL); + copied = 1; + } + break; + case ':': + /* this must be `::=', unconditional assignment */ + if (*s != '=' && *s != Equals) + goto noclosebrace; + vunset = 1; + s++; + /* Fall through */ + case '=': + case Equals: + if (vunset) { + char sav = *idend; + int l; + + *idend = '\0'; + val = dupstring(s); + isarr = 0; + /* + * TODO: this is one of those places where I don't + * think we want to do the joining until later on. + * We also need to handle spbreak and spsep at this + * point and unset them. + */ + if (spsep || spbreak || !arrasg) + multsub(&val, NULL, NULL, sep); + else + multsub(&val, &aval, &isarr, NULL); + if (arrasg) { + /* + * This is an array assignment in a context + * where we have no syntactic way of finding + * out what an array element is. So we just guess. + */ + char *arr[2], **t, **a, **p; + if (spsep || spbreak) { + aval = sepsplit(val, spsep, 0, 1); + isarr = 2; + l = arrlen(aval); + if (l && !*(aval[l-1])) + l--; + if (l && !**aval) + l--, t = aval + 1; + else + t = aval; + } else if (!isarr) { + if (!*val && arrasg > 1) { + arr[0] = NULL; + l = 0; + } else { + arr[0] = val; + arr[1] = NULL; + l = 1; + } + t = aval = arr; + } else + l = arrlen(aval), t = aval; + p = a = zalloc(sizeof(char *) * (l + 1)); + while (l--) { + untokenize(*t); + *p++ = ztrdup(*t++); + } + *p++ = NULL; + if (arrasg > 1) { + Param pm = sethparam(idbeg, a); + if (pm) + aval = paramvalarr(pm->gsu.h->getfn(pm), hkeys|hvals); + } else + setaparam(idbeg, a); + } else { + untokenize(val); + setsparam(idbeg, ztrdup(val)); + } + *idend = sav; + copied = 1; + if (isarr) { + if (nojoin) + isarr = -1; + if (qt && !getlen && isarr > 0 && !spsep && spbreak < 2) { + val = sepjoin(aval, sep, 1); + isarr = 0; + } + sep = spsep = NULL; + spbreak = 0; + } + } + break; + case '?': + case Quest: + if (vunset) { + char *msg; + + *idend = '\0'; + msg = tricat(idbeg, ": ", *s ? s : "parameter not set"); + zerr("%s", msg, 0); + zsfree(msg); + if (!interact) + exit(1); + return NULL; + } + break; + case '%': + case '#': + case Pound: + case '/': + /* This once was executed only `if (qt) ...'. But with that + * patterns in a expansion resulting from a ${(e)...} aren't + * tokenized even though this function thinks they are (it thinks + * they are because subst_parse_str() turns Qstring tokens + * into String tokens and for unquoted parameter expansions the + * lexer normally does tokenize patterns inside parameter + * expansions). */ + { + int one = noerrs, oef = errflag, haserr; + + if (!quoteerr) + noerrs = 1; + haserr = parse_subst_string(s); + noerrs = one; + if (!quoteerr) { + errflag = oef; + if (haserr) + shtokenize(s); + } else if (haserr || errflag) { + zerr("parse error in ${...%c...} substitution", + NULL, s[-1]); + return NULL; + } + } + { +#if 0 + /* + * This allows # and % to be at the start of + * a parameter in the substitution, which is + * a bit nasty, and can be done (although + * less efficiently) with anchors. + */ + + char t = s[-1]; + + singsub(&s); + + if (t == '/' && (flags & SUB_SUBSTR)) { + if ((c = *s) == '#' || c == '%') { + flags &= ~SUB_SUBSTR; + if (c == '%') + flags |= SUB_END; + s++; + } else if (c == '\\') { + s++; + } + } +#else + singsub(&s); +#endif + } + + /* + * Either loop over an array doing replacements or + * do the replacment on a string. + */ + if (!vunset && isarr) { + getmatcharr(&aval, s, flags, flnum, replstr); + copied = 1; + } else { + if (vunset) + val = dupstring(""); + getmatch(&val, s, flags, flnum, replstr); + copied = 1; + } + break; + } + } else { /* no ${...=...} or anything, but possible modifiers. */ + /* + * Handler ${+...}. TODO: strange, why do we handle this only + * if there isn't a trailing modifier? Why don't we do this + * e.g. when we hanlder the ${(t)...} flag? + */ + if (chkset) { + val = dupstring(vunset ? "0" : "1"); + isarr = 0; + } else if (vunset) { + if (unset(UNSET)) { + *idend = '\0'; + zerr("%s: parameter not set", idbeg, 0); + return NULL; + } + val = dupstring(""); + } + if (colf) { + /* + * History style colon modifiers. May need to apply + * on multiple elements of an array. + */ + s--; + if (unset(KSHARRAYS) || inbrace) { + if (!isarr) + modify(&val, &s); + else { + char *ss; + char **ap = aval; + char **pp = aval = (char **) hcalloc(sizeof(char *) * + (arrlen(aval) + 1)); + + while ((*pp = *ap++)) { + ss = s; + modify(pp++, &ss); + } + if (pp == aval) { + char *t = ""; + ss = s; + modify(&t, &ss); + } + s = ss; + } + copied = 1; + if (inbrace && *s) { + if (*s == ':' && !imeta(s[1])) + zerr("unrecognized modifier `%c'", NULL, s[1]); + else + zerr("unrecognized modifier", NULL, 0); + return NULL; + } + } + } + if (!inbrace) + fstr = s; + } + if (errflag) + return NULL; + /* + * This handles taking a length with ${#foo} and variations. + * TODO: again. one might naively have thought this had the + * same sort of effect as the ${(t)...} flag and the ${+...} + * test, although in this case we do need the value rather + * the the parameter, so maybe it's a bit different. + */ + if (getlen) { + long len = 0; + char buf[14]; + + if (isarr) { + char **ctr; + int sl = sep ? ztrlen(sep) : 1; + + if (getlen == 1) + for (ctr = aval; *ctr; ctr++, len++); + else if (getlen == 2) { + if (*aval) + for (len = -sl, ctr = aval; + len += sl + ztrlen(*ctr), *++ctr;); + } + else + for (ctr = aval; + *ctr; + len += wordcount(*ctr, spsep, getlen > 3), ctr++); + } else { + if (getlen < 3) + len = ztrlen(val); + else + len = wordcount(val, spsep, getlen > 3); + } + + sprintf(buf, "%ld", len); + val = dupstring(buf); + isarr = 0; + } + /* + * I think this mult_isarr stuff here is used to pass back + * the setting of whether we are an array to multsub, and + * thence to the top-level paramsubst(). The way the + * setting is passed back is completely obscure, however. + * It's presumably at this point because we try to remember + * whether the value was `really' an array before massaging + * some special cases. + * + * TODO: YUK. This is not the right place to turn arrays into + * scalars; we should pass back as an array, and let the calling + * code decide how to deal with it. This is almost certainly + * a lot harder than it sounds. Do we really need to handle + * one-element arrays as scalars at this point? Couldn't + * we just test for it later rather than having a multiple-valued + * wave-function for isarr? + */ + mult_isarr = isarr; + if (isarr > 0 && !plan9 && (!aval || !aval[0])) { + val = dupstring(""); + isarr = 0; + } else if (isarr && aval && aval[0] && !aval[1]) { + /* treat a one-element array as a scalar for purposes of * + * concatenation with surrounding text (some${param}thing) * + * and rc_expand_param handling. Note: mult_isarr (above) * + * propagates the true array type from nested expansions. */ + val = aval[0]; + isarr = 0; + } + /* ssub is true when we are called from singsub (via prefork). + * It means that we must join arrays and should not split words. */ + /* + * TODO: this is what is screwing up the use of SH_WORD_SPLIT + * after `:-' etc. If we fix multsub(), we might get away + * with simply unsetting the appropriate flags when they + * get handled. + */ + if (ssub || spbreak || spsep || sep) { + if (isarr) + val = sepjoin(aval, sep, 1), isarr = 0; + if (!ssub && (spbreak || spsep)) { + aval = sepsplit(val, spsep, 0, 1); + if (!aval || !aval[0]) + val = dupstring(""); + else if (!aval[1]) + val = aval[0]; + else + isarr = 2; + } + mult_isarr = isarr; + } + /* + * Perform case modififications. + */ + if (casmod) { + if (isarr) { + char **ap; + + if (!copied) + aval = arrdup(aval), copied = 1; + ap = aval; + + if (casmod == 1) + for (; *ap; ap++) + makeuppercase(ap); + else if (casmod == 2) + for (; *ap; ap++) + makelowercase(ap); + else + for (; *ap; ap++) + makecapitals(ap); + + } else { + if (!copied) + val = dupstring(val), copied = 1; + if (casmod == 1) + makeuppercase(&val); + else if (casmod == 2) + makelowercase(&val); + else + makecapitals(&val); + } + } + /* + * Perform prompt-style modifications. + */ + if (presc) { + int ops = opts[PROMPTSUBST], opb = opts[PROMPTBANG]; + int opp = opts[PROMPTPERCENT], len; + + if (presc < 2) { + opts[PROMPTPERCENT] = 1; + opts[PROMPTSUBST] = opts[PROMPTBANG] = 0; + } + /* + * TODO: It would be really quite nice to abstract the + * isarr and !issarr code into a function which gets + * passed a pointer to a function with the effect of + * the promptexpand bit. Then we could use this for + * a lot of stuff and bury val/aval/isarr inside a structure + * which gets passed to it. + */ + if (isarr) { + char **ap; + + if (!copied) + aval = arrdup(aval), copied = 1; + ap = aval; + for (; *ap; ap++) { + char *tmps; + unmetafy(*ap, &len); + untokenize(*ap); + tmps = unmetafy(promptexpand(metafy(*ap, len, META_NOALLOC), + 0, NULL, NULL), &len); + *ap = dupstring(tmps); + free(tmps); + } + } else { + char *tmps; + if (!copied) + val = dupstring(val), copied = 1; + unmetafy(val, &len); + untokenize(val); + tmps = unmetafy(promptexpand(metafy(val, len, META_NOALLOC), + 0, NULL, NULL), &len); + val = dupstring(tmps); + free(tmps); + } + opts[PROMPTSUBST] = ops; + opts[PROMPTBANG] = opb; + opts[PROMPTPERCENT] = opp; + } + /* + * One of the possible set of quotes to apply, depending on + * the repetitions of the (q) flag. + */ + if (quotemod) { + if (--quotetype > 3) + quotetype = 3; + if (isarr) { + char **ap; + + if (!copied) + aval = arrdup(aval), copied = 1; + ap = aval; + + if (quotemod > 0) { + if (quotetype) { + int sl; + char *tmp; + + for (; *ap; ap++) { + int pre = quotetype != 3 ? 1 : 2; + tmp = bslashquote(*ap, NULL, quotetype); + sl = strlen(tmp); + *ap = (char *) zhalloc(pre + sl + 2); + strcpy((*ap) + pre, tmp); + ap[0][pre - 1] = ap[0][pre + sl] = (quotetype != 2 ? '\'' : '"'); + ap[0][pre + sl + 1] = '\0'; + if (quotetype == 3) + ap[0][0] = '$'; + } + } else + for (; *ap; ap++) + *ap = bslashquote(*ap, NULL, 0); + } else { + int one = noerrs, oef = errflag, haserr = 0; + + if (!quoteerr) + noerrs = 1; + for (; *ap; ap++) { + haserr |= parse_subst_string(*ap); + remnulargs(*ap); + untokenize(*ap); + } + noerrs = one; + if (!quoteerr) + errflag = oef; + else if (haserr || errflag) { + zerr("parse error in parameter value", NULL, 0); + return NULL; + } + } + } else { + if (!copied) + val = dupstring(val), copied = 1; + if (quotemod > 0) { + if (quotetype) { + int pre = quotetype != 3 ? 1 : 2; + int sl; + char *tmp; + tmp = bslashquote(val, NULL, quotetype); + sl = strlen(tmp); + val = (char *) zhalloc(pre + sl + 2); + strcpy(val + pre, tmp); + val[pre - 1] = val[pre + sl] = (quotetype != 2 ? '\'' : '"'); + val[pre + sl + 1] = '\0'; + if (quotetype == 3) + val[0] = '$'; + } else + val = bslashquote(val, NULL, 0); + } else { + int one = noerrs, oef = errflag, haserr; + + if (!quoteerr) + noerrs = 1; + haserr = parse_subst_string(val); + noerrs = one; + if (!quoteerr) + errflag = oef; + else if (haserr || errflag) { + zerr("parse error in parameter value", NULL, 0); + return NULL; + } + remnulargs(val); + untokenize(val); + } + } + } + /* + * Transform special characters in the string to make them + * printable. + */ + if (visiblemod) { + if (isarr) { + char **ap; + if (!copied) + aval = arrdup(aval), copied = 1; + for (ap = aval; *ap; ap++) + *ap = nicedupstring(*ap); + } else { + if (!copied) + val = dupstring(val), copied = 1; + val = nicedupstring(val); + } + } + /* + * Nothing particularly to do with SH_WORD_SPLIT --- this + * performs lexical splitting on a string as specified by + * the (z) flag. + */ + if (shsplit) { + LinkList list = NULL; + + if (isarr) { + char **ap; + for (ap = aval; *ap; ap++) + list = bufferwords(list, *ap, NULL); + isarr = 0; + } else + list = bufferwords(NULL, val, NULL); + + if (!list || !firstnode(list)) + val = dupstring(""); + else if (!nextnode(firstnode(list))) + val = getdata(firstnode(list)); + else { + char **ap; + LinkNode node; + + aval = ap = (char **) zhalloc((countlinknodes(list) + 1) * + sizeof(char *)); + for (node = firstnode(list); node; incnode(node)) + *ap++ = (char *) getdata(node); + *ap = NULL; + mult_isarr = isarr = 2; + } + copied = 1; + } + /* + * TODO: hmm. At this point we have to be on our toes about + * whether we're putting stuff into a line or not, i.e. + * we don't want to do this from a recursive call; this is + * probably part of the point of the mult_isarr monkey business. + * Rather than passing back flags in a non-trivial way, maybe + * we could decide on the basis of flags passed down to us. + * + * This is the ideal place to do any last-minute conversion from + * array to strings. However, given all the transformations we've + * already done, probably if it's going to be done it will already + * have been. (I'd really like to keep everying in aval or + * equivalent and only locally decide if we need to treat it + * as a scalar.) + */ + if (isarr) { + char *x; + char *y; + int xlen; + int i; + LinkNode on = n; + + /* Handle the (u) flag; we need this before the next test */ + if (unique) { + if(!copied) + aval = arrdup(aval); + + i = arrlen(aval); + if (i > 1) + zhuniqarray(aval); + } + if ((!aval[0] || !aval[1]) && !plan9) { + /* + * Empty array or single element. Currently you only + * get a single element array at this point from the + * unique expansion above. but we can potentially + * have other reasons. + * + * The following test removes the markers + * from surrounding double quotes, but I don't know why + * that's necessary. + */ + int vallen; + if (aptr > (char *) getdata(n) && + aptr[-1] == Dnull && *fstr == Dnull) + *--aptr = '\0', fstr++; + vallen = aval[0] ? strlen(aval[0]) : 0; + y = (char *) hcalloc((aptr - ostr) + vallen + strlen(fstr) + 1); + strcpy(y, ostr); + *str = y + (aptr - ostr); + if (vallen) + { + strcpy(*str, aval[0]); + *str += vallen; + } + strcpy(*str, fstr); + setdata(n, y); + return n; + } + /* Handle (o) and (O) and their variants */ + if (sortit) { + if (!copied) + aval = arrdup(aval); + if (indord) { + if (sortit & 2) { + char *copy; + char **end = aval + arrlen(aval) - 1, **start = aval; + + /* reverse the array */ + while (start < end) { + copy = *end; + *end-- = *start; + *start++ = copy; + } + } + } else { + static CompareFn sortfn[] = { + strpcmp, invstrpcmp, cstrpcmp, invcstrpcmp, + nstrpcmp, invnstrpcmp, instrpcmp, invinstrpcmp + }; + + i = arrlen(aval); + if (i && (*aval[i-1] || --i)) + qsort(aval, i, sizeof(char *), sortfn[sortit-1]); + } + } + if (plan9) { + /* Handle RC_EXPAND_PARAM */ + LinkNode tn; + local_list1(tl); + + *--fstr = Marker; + init_list1(tl, fstr); + if (!eval && !stringsubst(&tl, firstnode(&tl), ssub, 0)) + return NULL; + *str = aptr; + tn = firstnode(&tl); + while ((x = *aval++)) { + if (prenum || postnum) + x = dopadding(x, prenum, postnum, preone, postone, + premul, postmul); + if (eval && subst_parse_str(&x, (qt && !nojoin), quoteerr)) + return NULL; + xlen = strlen(x); + for (tn = firstnode(&tl); + tn && *(y = (char *) getdata(tn)) == Marker; + incnode(tn)) { + strcatsub(&y, ostr, aptr, x, xlen, y + 1, globsubst, + copied); + if (qt && !*y && isarr != 2) + y = dupstring(nulstring); + if (plan9) + setdata(n, (void *) y), plan9 = 0; + else + insertlinknode(l, n, (void *) y), incnode(n); + } + } + for (; tn; incnode(tn)) { + y = (char *) getdata(tn); + if (*y == Marker) + continue; + if (qt && !*y && isarr != 2) + y = dupstring(nulstring); + if (plan9) + setdata(n, (void *) y), plan9 = 0; + else + insertlinknode(l, n, (void *) y), incnode(n); + } + if (plan9) { + uremnode(l, n); + return n; + } + } else { + /* + * Not RC_EXPAND_PARAM: simply join the first and + * last values. + * TODO: how about removing the restriction that + * aval[1] is non-NULL to promote consistency?, or + * simply changing the test so that we drop into + * the scalar branch, instead of tricking isarr? + */ + x = aval[0]; + if (prenum || postnum) + x = dopadding(x, prenum, postnum, preone, postone, + premul, postmul); + if (eval && subst_parse_str(&x, (qt && !nojoin), quoteerr)) + return NULL; + xlen = strlen(x); + strcatsub(&y, ostr, aptr, x, xlen, NULL, globsubst, copied); + if (qt && !*y && isarr != 2) + y = dupstring(nulstring); + setdata(n, (void *) y); + + i = 1; + /* aval[1] is non-null here */ + while (aval[i + 1]) { + x = aval[i++]; + if (prenum || postnum) + x = dopadding(x, prenum, postnum, preone, postone, + premul, postmul); + if (eval && subst_parse_str(&x, (qt && !nojoin), quoteerr)) + return NULL; + if (qt && !*x && isarr != 2) + y = dupstring(nulstring); + else { + y = dupstring(x); + if (globsubst) + shtokenize(y); + } + insertlinknode(l, n, (void *) y), incnode(n); + } + + x = aval[i]; + if (prenum || postnum) + x = dopadding(x, prenum, postnum, preone, postone, + premul, postmul); + if (eval && subst_parse_str(&x, (qt && !nojoin), quoteerr)) + return NULL; + xlen = strlen(x); + *str = strcatsub(&y, aptr, aptr, x, xlen, fstr, globsubst, copied); + if (qt && !*y && isarr != 2) + y = dupstring(nulstring); + insertlinknode(l, n, (void *) y), incnode(n); + } + if (eval) + n = on; + } else { + /* + * Scalar value. Handle last minute transformations + * such as left- or right-padding and the (e) flag to + * revaluate the result. + */ + int xlen; + char *x; + char *y; + + x = val; + if (prenum || postnum) + x = dopadding(x, prenum, postnum, preone, postone, + premul, postmul); + if (eval && subst_parse_str(&x, (qt && !nojoin), quoteerr)) + return NULL; + xlen = strlen(x); + *str = strcatsub(&y, ostr, aptr, x, xlen, fstr, globsubst, copied); + if (qt && !*y) + y = dupstring(nulstring); + setdata(n, (void *) y); + } + if (eval) + *str = (char *) getdata(n); + + return n; +} + +/* + * Arithmetic substitution: `a' is the string to be evaluated, `bptr' + * points to the beginning of the string containing it. The tail of + * the string is given by `rest'. *bptr is modified with the substituted + * string. The function returns a pointer to the tail in the substituted + * string. + */ + +/**/ +static char * +arithsubst(char *a, char **bptr, char *rest) +{ + char *s = *bptr, *t; + char buf[BDIGBUFSIZE], *b = buf; + mnumber v; + + singsub(&a); + v = matheval(a); + if ((v.type & MN_FLOAT) && !outputradix) + b = convfloat(v.u.d, 0, 0, NULL); + else { + if (v.type & MN_FLOAT) + v.u.l = (zlong) v.u.d; + convbase(buf, v.u.l, outputradix); + } + t = *bptr = (char *) hcalloc(strlen(*bptr) + strlen(b) + + strlen(rest) + 1); + t--; + while ((*++t = *s++)); + t--; + while ((*++t = *b++)); + strcat(t, rest); + return t; +} + +/**/ +void +modify(char **str, char **ptr) +{ + char *ptr1, *ptr2, *ptr3, del, *lptr, c, *test, *sep, *t, *tt, tc, *e; + char *copy, *all, *tmp, sav; + int gbal, wall, rec, al, nl; + + test = NULL; + + if (**ptr == ':') + *str = dupstring(*str); + + while (**ptr == ':') { + lptr = *ptr; + (*ptr)++; + wall = gbal = 0; + rec = 1; + c = '\0'; + sep = NULL; + + for (; !c && **ptr;) { + switch (**ptr) { + case 'h': + case 'r': + case 'e': + case 't': + case 'l': + case 'u': + case 'q': + case 'Q': + c = **ptr; + break; + + case 's': + c = **ptr; + (*ptr)++; + ptr1 = *ptr; + del = *ptr1++; + for (ptr2 = ptr1; *ptr2 != del && *ptr2; ptr2++); + if (!*ptr2) { + zerr("bad substitution", NULL, 0); + return; + } + *ptr2++ = '\0'; + for (ptr3 = ptr2; *ptr3 != del && *ptr3; ptr3++); + if ((sav = *ptr3)) + *ptr3++ = '\0'; + if (*ptr1) { + zsfree(hsubl); + hsubl = ztrdup(ptr1); + } + if (!hsubl) { + zerr("no previous substitution", NULL, 0); + return; + } + zsfree(hsubr); + for (tt = hsubl; *tt; tt++) + if (INULL(*tt)) + chuck(tt--); + untokenize(hsubl); + for (tt = hsubr = ztrdup(ptr2); *tt; tt++) + if (INULL(*tt)) + chuck(tt--); + ptr2[-1] = del; + if (sav) + ptr3[-1] = sav; + *ptr = ptr3 - 1; + break; + + case '&': + c = 's'; + break; + + case 'g': + (*ptr)++; + gbal = 1; + break; + + case 'w': + wall = 1; + (*ptr)++; + break; + case 'W': + wall = 1; + (*ptr)++; + ptr1 = get_strarg(ptr2 = *ptr); + if ((sav = *ptr1)) + *ptr1 = '\0'; + sep = dupstring(ptr2 + 1); + if (sav) + *ptr1 = sav; + *ptr = ptr1 + 1; + c = '\0'; + break; + + case 'f': + rec = -1; + (*ptr)++; + break; + case 'F': + rec = get_intarg(ptr); + (*ptr)++; + break; + default: + *ptr = lptr; + return; + } + } + (*ptr)++; + if (!c) { + *ptr = lptr; + return; + } + if (rec < 0) + test = dupstring(*str); + + while (rec--) { + if (wall) { + al = 0; + all = NULL; + for (t = e = *str; (tt = findword(&e, sep));) { + tc = *e; + *e = '\0'; + copy = dupstring(tt); + *e = tc; + switch (c) { + case 'h': + remtpath(©); + break; + case 'r': + remtext(©); + break; + case 'e': + rembutext(©); + break; + case 't': + remlpaths(©); + break; + case 'l': + downcase(©); + break; + case 'u': + upcase(©); + break; + case 's': + if (hsubl && hsubr) + subst(©, hsubl, hsubr, gbal); + break; + case 'q': + copy = bslashquote(copy, NULL, 0); + break; + case 'Q': + { + int one = noerrs, oef = errflag; + + noerrs = 1; + parse_subst_string(copy); + noerrs = one; + errflag = oef; + remnulargs(copy); + untokenize(copy); + } + break; + } + tc = *tt; + *tt = '\0'; + nl = al + strlen(t) + strlen(copy); + ptr1 = tmp = (char *)zhalloc(nl + 1); + if (all) + for (ptr2 = all; *ptr2;) + *ptr1++ = *ptr2++; + for (ptr2 = t; *ptr2;) + *ptr1++ = *ptr2++; + *tt = tc; + for (ptr2 = copy; *ptr2;) + *ptr1++ = *ptr2++; + *ptr1 = '\0'; + al = nl; + all = tmp; + t = e; + } + *str = all; + + } else { + switch (c) { + case 'h': + remtpath(str); + break; + case 'r': + remtext(str); + break; + case 'e': + rembutext(str); + break; + case 't': + remlpaths(str); + break; + case 'l': + downcase(str); + break; + case 'u': + upcase(str); + break; + case 's': + if (hsubl && hsubr) { + char *oldstr = *str; + + subst(str, hsubl, hsubr, gbal); + if (*str != oldstr) { + *str = dupstring(oldstr = *str); + zsfree(oldstr); + } + } + break; + case 'q': + *str = bslashquote(*str, NULL, 0); + break; + case 'Q': + { + int one = noerrs, oef = errflag; + + noerrs = 1; + parse_subst_string(*str); + noerrs = one; + errflag = oef; + remnulargs(*str); + untokenize(*str); + } + break; + } + } + if (rec < 0) { + if (!strcmp(test, *str)) + rec = 0; + else + test = dupstring(*str); + } + } + } +} + +/* get a directory stack entry */ + +/**/ +static char * +dstackent(char ch, int val) +{ + int backwards; + LinkNode end=(LinkNode)dirstack, n; + + backwards = ch == (isset(PUSHDMINUS) ? '+' : '-'); + if(!backwards && !val--) + return pwd; + if (backwards) + for (n=lastnode(dirstack); n != end && val; val--, n=prevnode(n)); + else + for (end=NULL, n=firstnode(dirstack); n && val; val--, n=nextnode(n)); + if (n == end) { + if (backwards && !val) + return pwd; + if (isset(NOMATCH)) + zerr("not enough directory stack entries.", NULL, 0); + return NULL; + } + return (char *)getdata(n); +}