openenvutils/commandshell/shell/src/subst.c
author William Roberts <williamr@symbian.org>
Fri, 23 Apr 2010 14:37:17 +0100
branchRCL_3
changeset 22 c82a39b81a38
parent 4 0fdb7f6b0309
permissions -rw-r--r--
Rework addition of Symbian splash screen to reduce the source impact (uses SVG from Bug 2414) Notes: by using the OPTION SOURCEDIR parameter in the mifconv extension instructions, I can arrange to use the same source file name in sfimage, without having to export over the original Nokia file. This means that the name inside splashscreen.mbg is the same, which removes the need for the conditional compilation in SplashScreen.cpp, and gets rid of sf_splashscreen.mmp.


/*
* Copyright (c) 2007-2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/



/*
 * 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)"$(<file)"} by skipping 
	     * the double quotes.  We don't need to know what was
	     * actually there; the presence of a String or Qstring
	     * is good enough.
	     */
	    s++;
	} else
	    break;
    }
    /* Don't activate special pattern characters if inside quotes */
    globsubst = globsubst && !qt;

    /*
     * At this point, we usually expect a parameter name.
     * However, there may be a nested ${...} or $(...).
     * These say that the parameter itself is somewhere inside,
     * or that there isn't a parameter and we will get the values
     * from a command substitution itself.  In either case,
     * the current instance of paramsubst() doesn't fetch a value,
     * it just operates on what gets passed up.
     * (The first ought to have been {...}, reserving ${...}
     * for substituting a value at that point, but it's too late now.)
     */
    idbeg = s;
    if ((subexp = (inbrace && s[-1] && isstring(*s) &&
		   (s[1] == Inbrace || s[1] == Inpar)))) {
	int sav;
	int quoted = *s == Qstring;

	val = s++;
	skipparens(*s, *s == Inpar ? Outpar : Outbrace, &s);
	sav = *s;
	*s = 0;
	/*
	 * This handles arrays.  TODO: this is not the most obscure call to
	 * multsub() (see below) but even so it would be nicer to pass down
	 * and back the arrayness more rationally.  In that case, we should
	 * remove the aspar test and extract a value from an array, if
	 * necessary, when we handle (P) lower down.
	 */
	if (multsub(&val, (aspar ? NULL : &aval), &isarr, NULL) && quoted) {
	    /* Empty quoted string --- treat as null string, not elided */
	    isarr = -1;
	    aval = (char **) hcalloc(sizeof(char *));
	    aspar = 0;
	} else if (aspar)
	    idbeg = val;
	*s = sav;
	/*
	 * This tests for the second double quote in an expression
	 * like ${(f)"$(<file)"}, compare above.
	 */
	while (INULL(*s))
	    s++;
	v = (Value) NULL;
    } else if (aspar) {
	/*
	 * No subexpression, but in any case the value is going
	 * to give us the name of a parameter on which we do
	 * our remaining processing.  In other words, this
	 * makes ${(P)param} work like ${(P)${param}}.  (Probably
	 * better looked at, this is the basic code for ${(P)param}
	 * and it's been kludged into the subexp code because no
	 * opportunity for a kludge has been neglected.)
	 */
	if ((v = fetchvalue(&vbuf, &s, 1, (qt ? SCANPM_DQUOTED : 0)))) {
	    val = idbeg = getstrvalue(v);
	    subexp = 1;
	} else
	    vunset = 1;
    }
    /*
     * We need to retrieve a value either if we haven't already
     * got it from a subexpression, or if the processing so
     * far has just yielded us a parameter name to be processed
     * with (P).
     */
    if (!subexp || aspar) {
	char *ov = val;

	/*
	 * Second argument: decide whether to use the subexpression or
	 *   the string next on the line as the parameter name.
	 * Third argument:  decide how processing for brackets
	 *   1 means full processing
	 *   -1 appears to mean something along the lines of
	 *     only handle single digits and don't handle brackets.
	 *     I *think* (but it's really only a guess) that this
	 *     is used by the test below the wantt handling, so
	 *     that in certain cases we handle brackets there.
	 *   0 would apparently mean something like we know we
	 *     should have the name of a scalar and we get cross
	 *     if there's anything present which disagrees with that
	 * but you will search fetchvalue() in vain for comments on this.
	 * Fourth argument gives flags to do with keys, values, quoting,
	 * assigning depending on context and parameter flags.
	 *
	 * This is the last mention of subexp, so presumably this
	 * is what the code which makes sure subexp is set if aspar (the
	 * (P) flag) is set.  I *think* what's going on here is the
	 * second argument is for both input and output: with
	 * subexp, we only want the input effect, whereas normally
	 * we let fetchvalue set the main string pointer s to
	 * the end of the bit it's fetched.
	 */
	if (!(v = fetchvalue(&vbuf, (subexp ? &ov : &s),
			     (wantt ? -1 :
			      ((unset(KSHARRAYS) || inbrace) ? 1 : -1)),
			     hkeys|hvals|
			     (arrasg ? SCANPM_ASSIGNING : 0)|
			     (qt ? SCANPM_DQUOTED : 0))) ||
	    (v->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(&copy);
			break;
		    case 'r':
			remtext(&copy);
			break;
		    case 'e':
			rembutext(&copy);
			break;
		    case 't':
			remlpaths(&copy);
			break;
		    case 'l':
			downcase(&copy);
			break;
		    case 'u':
			upcase(&copy);
			break;
		    case 's':
			if (hsubl && hsubr)
			    subst(&copy, 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);
}