/*
 * $Id: params.c,v 1.20 1995/05/30 02:47:41 coleman Exp coleman $
 *
 * params.c - parameters
 *
 * This file is part of zsh, the Z shell.
 *
 * Copyright (c) 1992-1995 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 its documentation 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.h"

static Param argvparam;

struct iparam {
    struct hashnode *next;
    char *nam;			/* hash data                             */
    void *value;
    int (*func1) ();		/* set func                              */
    int (*func2) ();		/* get func                              */
    int ct;			/* output base or field width            */
    int flags;
    void *data;			/* used by getfns                        */
    char *env;			/* location in environment, if exported  */
    char *ename;		/* name of corresponding environment var */
    Param old;			/* old struct for use with local         */
    int level;			/* if (old != NULL), level of localness  */
};

#define IFN(X) ((int (*)())(X))

/* Set up parameter hash table and environment variable */

/**/
void
buildparamtab(void)
{
    static struct iparam pinit[] =
    {
#define IPDEF1(A,B,C,D) {NULL,A,NULL,IFN(C),IFN(B),10,\
		PMFLAG_i|PMFLAG_SPECIAL|D,NULL,NULL,NULL,NULL,0}
	IPDEF1("#", poundgetfn, IFN(nullsetfn), PMFLAG_r),
	IPDEF1("ARGC", poundgetfn, IFN(nullsetfn), PMFLAG_r),
	IPDEF1("ERRNO", errnogetfn, IFN(nullsetfn), PMFLAG_r),
	IPDEF1("GID", gidgetfn, gidsetfn, 0),
	IPDEF1("EGID", egidgetfn, egidsetfn, 0),
	IPDEF1("HISTSIZE", histsizegetfn, histsizesetfn, 0),
	IPDEF1("LITHISTSIZE", lithistsizegetfn, lithistsizesetfn, 0),
	IPDEF1("RANDOM", randomgetfn, randomsetfn, 0),
	IPDEF1("SECONDS", secondsgetfn, secondssetfn, 0),
	IPDEF1("UID", uidgetfn, uidsetfn, 0),
	IPDEF1("EUID", euidgetfn, euidsetfn, 0),

#define IPDEF2(A,B,C,D) {NULL,A,NULL,IFN(C),IFN(B),0,\
		PMFLAG_SPECIAL|D,NULL,NULL,NULL,NULL,0}
	IPDEF2("-", dashgetfn, IFN(nullsetfn), PMFLAG_r),
	IPDEF2("histchars", histcharsgetfn, histcharssetfn, 0),
	IPDEF2("HOME", homegetfn, homesetfn, 0),
	IPDEF2("TERM", termgetfn, termsetfn, 0),
	IPDEF2("WORDCHARS", wordcharsgetfn, wordcharssetfn, 0),
	IPDEF2("IFS", ifsgetfn, ifssetfn, 0),
	IPDEF2("_", underscoregetfn, IFN(nullsetfn), PMFLAG_r),
#ifdef LC_ALL
	IPDEF2("LANG", strgetfn, langsetfn, 0),
#endif

#define IPDEF3(A) {NULL,A,NULL,IFN(nullsetfn),IFN(strconstgetfn),0,PMFLAG_r|\
		PMFLAG_SPECIAL,NULL,NULL,NULL,NULL,0}
	IPDEF3("HOSTTYPE"),
	IPDEF3("OSTYPE"),
	IPDEF3("MACHTYPE"),
	IPDEF3("VENDOR"),
	IPDEF3("ZSH_VERSION"),

#define IPDEF4(A,B) {NULL,A,NULL,IFN(nullsetfn),IFN(intvargetfn),10,\
		PMFLAG_r|PMFLAG_i|PMFLAG_SPECIAL,(void *)B,NULL,NULL,NULL,0}
	IPDEF4("!", &lastpid),
	IPDEF4("$", &mypid),
	IPDEF4("?", &lastval),
	IPDEF4("status", &lastval),
	IPDEF4("LINENO", &lineno),
	IPDEF4("PPID", &ppid),

#define IPDEF5(A,B) {NULL,A,NULL,IFN(intvarsetfn),IFN(intvargetfn),10,\
		PMFLAG_i|PMFLAG_SPECIAL,(void *)B,NULL,NULL,NULL,0}
	IPDEF5("BAUD", &baud),
	IPDEF5("COLUMNS", &columns),
	IPDEF5("DIRSTACKSIZE", &dirstacksize),
	IPDEF5("KEYTIMEOUT", &keytimeout),
	IPDEF5("LINES", &lines),
	IPDEF5("LISTMAX", &listmax),
	IPDEF5("LOGCHECK", &logcheck),
	IPDEF5("MAILCHECK", &mailcheck),
	IPDEF5("OPTIND", &zoptind),
	IPDEF5("PERIOD", &period),
	IPDEF5("REPORTTIME", &reporttime),
	IPDEF5("SAVEHIST", &savehist),
	IPDEF5("SHLVL", &shlvl),
	IPDEF5("TMOUT", &tmout),

#define IPDEF6(A,B) {NULL,A,NULL,IFN(nullsetfn),IFN(strvargetfn),0,\
		PMFLAG_r|PMFLAG_SPECIAL,(void *)B,NULL,NULL,NULL,0}
	IPDEF6("LOGNAME", &logname),
	IPDEF6("PWD", &pwd),
	IPDEF6("TTY", &ttystrname),
	IPDEF6("USERNAME", &username),

#define IPDEF7(A,B) {NULL,A,NULL,IFN(strvarsetfn),IFN(strvargetfn),0,\
		PMFLAG_SPECIAL,(void *)B,NULL,NULL,NULL,0}
	IPDEF7("FCEDIT", &fceditparam),
	IPDEF7("HOST", &hostvar),
	IPDEF7("OLDPWD", &oldpwd),
	IPDEF7("OPTARG", &zoptarg),
	IPDEF7("MAIL", &mailfile),
	IPDEF7("NULLCMD", &nullcmd),
	IPDEF7("POSTEDIT", &postedit),
	IPDEF7("prompt", &prompt),
	IPDEF7("PROMPT", &prompt),
	IPDEF7("PROMPT2", &prompt2),
	IPDEF7("PROMPT3", &prompt3),
	IPDEF7("PROMPT4", &prompt4),
	IPDEF7("READNULLCMD", &readnullcmd),
	IPDEF7("RPROMPT", &rprompt),
	IPDEF7("PS1", &prompt),
	IPDEF7("PS2", &prompt2),
	IPDEF7("PS3", &prompt3),
	IPDEF7("PS4", &prompt4),
	IPDEF7("RPS1", &rprompt),
	IPDEF7("SPROMPT", &sprompt),
	IPDEF7("TIMEFMT", &timefmt),
	IPDEF7("TMPPREFIX", &tmpprefix),
	IPDEF7("WATCHFMT", &watchfmt),
	IPDEF7("0", &argzero),

#define IPDEF8(A,B,C) {NULL,A,NULL,IFN(colonarrsetfn),IFN(colonarrgetfn),0,\
		PMFLAG_SPECIAL,(void *)C,NULL,B,NULL,0}
	IPDEF8("CDPATH", "cdpath", &cdpath),
	IPDEF8("FIGNORE", "fignore", &fignore),
	IPDEF8("FPATH", "fpath", &fpath),
	IPDEF8("MAILPATH", "mailpath", &mailpath),
	IPDEF8("MANPATH", "manpath", &manpath),
	IPDEF8("WATCH", "watch", &watch),
	IPDEF8("PSVAR", "psvar", &psvar),
	{NULL, "PATH", NULL, IFN(colonpathsetfn), IFN(colonpathgetfn), 0,
	 PMFLAG_SPECIAL, (void *) NULL, NULL, "path", NULL, 0},

#define IPDEF9(A,B,C,D) {NULL,A,NULL,IFN(arrvarsetfn),IFN(arrvargetfn),0,\
		PMFLAG_A|PMFLAG_SPECIAL|C,(void *)B,NULL,D,NULL,0}
	IPDEF9("cdpath", &cdpath, 0, "CDPATH"),
	IPDEF9("fignore", &fignore, 0, "FIGNORE"),
	IPDEF9("fpath", &fpath, 0, "FPATH"),
	IPDEF9("mailpath", &mailpath, 0, "MAILPATH"),
	IPDEF9("manpath", &manpath, 0, "MANPATH"),
	IPDEF9("watch", &watch, 0, "WATCH"),
	IPDEF9("psvar", &psvar, 0, "PSVAR"),
	IPDEF9("signals", &sigptr, PMFLAG_r, NULL),
	IPDEF9("argv", &pparams, 0, NULL),
	IPDEF9("*", &pparams, 0, NULL),
	IPDEF9("@", &pparams, 0, NULL),

	{NULL, "path", NULL, IFN(pathsetfn), IFN(pathgetfn), 0,
	 PMFLAG_A | PMFLAG_SPECIAL, NULL, NULL, "PATH", NULL, 0},

	{NULL,}
    };
    struct iparam *ip;
    struct param *pm;
    char **envp, **envp2, **envp3;
    char buf[50], *str;
    int ct;

    paramtab = newhtable(151);

    for (ip = pinit; ip->nam; ip++)
	addhnode(ztrdup(ip->nam), ip, paramtab, (FFunc) 0);
    argvparam = (Param) gethnode("argv", paramtab);

    ((struct iparam *)gethnode("HOSTTYPE",   paramtab))->data = (void *) ztrdup(HOSTTYPE);
    ((struct iparam *)gethnode("OSTYPE",     paramtab))->data = (void *) ztrdup(OSTYPE);
    ((struct iparam *)gethnode("MACHTYPE",   paramtab))->data = (void *) ztrdup(MACHTYPE);
    ((struct iparam *)gethnode("VENDOR",     paramtab))->data = (void *) ztrdup(VENDOR);
    ((struct iparam *)gethnode("ZSH_VERSION",paramtab))->data = (void *) ztrdup(ZSH_VERSION);

    noerrs = 1;
    for (envp = environ, ct = 2; *envp; envp++, ct++);
    envp = environ;
    envp2 = envp3 = (char **)zalloc(sizeof(char *) * ct);

    for (; *envp; envp++)
	*envp2++ = ztrdup(*envp);
    *envp2 = NULL;
    envp = environ;
    environ = envp2 = envp3;
    for (; *envp; envp++, envp2++) {
	for (str = *envp; *str && *str != '='; str++);
	if (*str == '=') {
	    char *iname = NULL;

	    *str = '\0';
	    pm = (!idigit(**envp) && isident(*envp) && !strchr(*envp, '[')) ?
		setsparam(iname = *envp, ztrdup(str + 1)) : NULL;
	    if (pm) {
		pm->flags |= PMFLAG_x;
		pm->env = *envp2;
		if (pm->flags & PMFLAG_SPECIAL)
		    pm->env = replenv(pm->env, getsparam(iname));
	    }
	    *str = '=';
	}
    }
    pm = (struct param *)gethnode("HOME", paramtab);
    if (!(pm->flags & PMFLAG_x)) {
	pm->flags |= PMFLAG_x;
	pm->env = addenv("HOME", home);
    }
    pm = (struct param *)gethnode("PWD", paramtab);
    if (!(pm->flags & PMFLAG_x)) {
	pm->flags |= PMFLAG_x;
	pm->env = addenv("PWD", pwd);
    }
    pm = (struct param *)gethnode("LOGNAME", paramtab);
    if (!(pm->flags & PMFLAG_x)) {
	pm->flags |= PMFLAG_x;
	pm->env = addenv("LOGNAME", logname);
    }
    pm = (struct param *)gethnode("SHLVL", paramtab);
    if (!(pm->flags & PMFLAG_x))
	pm->flags |= PMFLAG_x;
    sprintf(buf, "%d", (int)++shlvl);
    pm->env = addenv("SHLVL", buf);
    noerrs = 0;
}

/**/
struct param *
createparam(char *name, int flags)
{
    struct param *pm, *oldpm = (Param) gethnode(name, paramtab);
    int spec;

    spec = oldpm && (oldpm->flags & PMFLAG_SPECIAL);
    if ((oldpm && oldpm->level == locallevel) || spec) {
	pm = oldpm;
	pm->ct = 0;
	oldpm = pm->old;
	pm->flags = (flags & (PMFLAG_x | PMFLAG_L | PMFLAG_R | PMFLAG_Z |
			      PMFLAG_l | PMFLAG_u | PMFLAG_r | PMFLAG_t)) |
	    (pm->flags & (PMFLAG_s | PMFLAG_i | PMFLAG_A | PMFLAG_SPECIAL));
	if (pm->ename) {
	    Param altpm = (Param) gethnode(pm->ename, paramtab);

	    if (altpm)
		altpm->flags &= ~(PMFLAG_UNSET | PMFLAG_x | PMFLAG_L |
				  PMFLAG_R | PMFLAG_Z | PMFLAG_l |
				  PMFLAG_u | PMFLAG_r | PMFLAG_t);
	}
    } else {
	pm = (struct param *)zcalloc(sizeof *pm);
	if ((pm->old = oldpm))
	    remhnode(name, paramtab);	/* needed to avoid freeing oldpm */
	addhnode(ztrdup(name), pm, paramtab, freepm);
    }
    if (isset(ALLEXPORT) && !oldpm)
	pm->flags |= PMFLAG_x;
    if (!spec) {
	pm->flags = flags;
	if ((flags & PMTYPE) == PMFLAG_s) {
	    pm->sets.cfn = strsetfn;
	    pm->gets.cfn = strgetfn;
	} else if ((flags & PMTYPE) == PMFLAG_A) {
	    pm->sets.afn = arrsetfn;
	    pm->gets.afn = arrgetfn;
	} else {
	    pm->sets.ifn = intsetfn;
	    pm->gets.ifn = intgetfn;
	}
    }
    return pm;
}

/**/
int
isident(char *s)
{
    char *ss;
    int ne = noeval;

    if (!*s)
	return 0;

    for (ss = s; *ss; ss++)
	if (!iident(*ss))
	    break;
    if (!*ss || (*ss == '[' && ss[1] == '('))
	return 1;
    if (*ss != '[')
	return 0;
    noeval = 1;
    (void)mathevalarg(++ss, &ss);
    if (*ss == ',' || *ss == Comma)
	(void)mathevalarg(++ss, &ss);
    noeval = ne;
    if (*ss != ']' || ss[1])
	return 0;
    return 1;
}

char **garr;

/**/
long
getarg(char **str, int *inv, Value v, int a2, long *w)
{
    int num = 1, word = 0, rev = 0, ind = 0, down = 0, exp = 0, l, i;
    char *s = *str, *sep = NULL, *t, sav, *d, **ta, **p, *tt;
    long r = 0, rr, rrr;
    Comp c;

    if (*s == '(' || *s == Inpar) {
	for (s++; *s != ')' && *s != Outpar && s != *str; s++) {
	    switch (*s) {
	    case 'r':
		rev = 1;
		down = ind = 0;
		break;
	    case 'R':
		rev = down = 1;
		ind = 0;
		break;
	    case 'i':
		rev = ind = 1;
		down = 0;
		break;
	    case 'I':
		rev = ind = down = 1;
		break;
	    case 'w':
		word = 1;
		break;
	    case 'e':
		exp = 1;
		break;
	    case 'n':
		t = get_strarg(++s);
		if (!*t)
		    goto flagerr;
		sav = *t;
		*t = '\0';
		num = mathevalarg(s + 1, &d);
		if (!num)
		    num = 1;
		*t = sav;
		s = t;
		break;
	    case 's':
		t = get_strarg(++s);
		if (!*t)
		    goto flagerr;
		sav = *t;
		*t = '\0';
		sep = dupstring(s + 1);
		*t = sav;
		s = t;
		break;
	    default:
	      flagerr:
		num = 1;
		word = rev = ind = down = exp = 0;
		sep = NULL;
		s = *str - 1;
	    }
	}
	if (s != *str)
	    s++;
    }
    if (num < 0) {
	down = !down;
	num = -num;
    }
    *inv = ind;

    for (t = s, i = 0; *t && (*t != ']' || i) &&
	 (*t != Outbrack || i) && *t != ','; t++)
	if (*t == '[' || *t == Inbrack)
	    i++;
	else if (*t == ']' || *t == Outbrack)
	    i--;

    if (!*t)
	return 0;
    sav = *t;
    *t = '\0';
    s = dupstring(s);
    *t = sav;
    *str = tt = t;

    if (exp) {
	tokenize(s);
	lexsave();
	strinbeg();
	singsub(&s);
	strinend();
	lexrestore();
	untokenize(s);
    }
    if (!rev) {
	if (!(r = mathevalarg(s, &s)))
	    r = 1;
	if (word && !v->isarr) {
	    s = t = getstrvalue(v);
	    i = wordcount(s, sep, 0);
	    if (r < 0)
		r += i + 1;
	    if (r < 1)
		r = 1;
	    if (r > i)
		r = i;
	    if (!s || !*s)
		return 0;
	    rrr = r;
	    while ((d = findword(&s, sep)) && --r);
	    if (!d)
		return 0;

	    if (a2) {
		if ((d = findword(&s, sep))) {
		    r = (long)(d - t) - (sep ? strlen(sep) + (rrr == i ? 0 : 1) : 1);
		} else
		    r = -1;
	    } else {
		rr = (long)(d - t);
		if (rrr > 1)
		    rr++;
		r = rr;
	    }

	    if (!a2 && *tt != ',' && *tt != Comma)
		*w = (long)(s - t) - 1;
	}
    } else {
	if (!v->isarr && !word) {
	    l = strlen(s);
	    if (a2) {
		if (!l || *s != '*') {
		    d = (char *)ncalloc(l + 2);
		    *d = '*';
		    strcpy(d + 1, s);
		    s = d;
		}
	    } else {
		if (!l || s[l - 1] != '*') {
		    d = (char *)ncalloc(l + 2);
		    strcpy(d, s);
		    strcat(d, "*");
		    s = d;
		}
	    }
	}
	tokenize(s);

	if ((c = parsereg(s))) {
	    if (v->isarr) {
		ta = getarrvalue(v);
		if (!ta || !*ta)
		    return 0;
		if (down)
		    for (r = -1, p = ta + arrlen(ta) - 1; p >= ta; r--, p--) {
			if (domatch(*p, c, 0) && !--num)
			    return r;
		} else
		    for (r = 1, p = ta; *p; r++, p++)
			if (domatch(*p, c, 0) && !--num)
			    return r;
	    } else if (word) {
		ta = sepsplit(d = s = getstrvalue(v), sep);
		if (down) {
		    for (p = ta + (r = arrlen(ta)) - 1; p >= ta; p--, r--)
			if (domatch(*p, c, 0) && !--num)
			    break;
		    if (p < ta)
			return 0;
		} else {
		    for (r = 1, p = ta; *p; r++, p++)
			if (domatch(*p, c, 0) && !--num)
			    break;
		    if (!*p)
			return 0;
		}
		if (a2)
		    r++;
		for (i = 0, t = findword(&d, sep); t && *t;
		     i++, t = findword(&d, sep))
		    if (!--r) {
			r = (long)(t - s + (a2 ? -1 : 1));
			if (!a2 && *tt != ',' && *tt != Comma)
			    *w = r + strlen(ta[i]) - 2;
			return r;
		    }
		return a2 ? -1 : 0;
	    } else {
		d = getstrvalue(v);
		if (!d || !*d)
		    return 0;
		if (a2) {
		    if (down)
			for (r = -2, t = d + strlen(d) - 1; t >= d; r--, t--) {
			    sav = *t;
			    *t = '\0';
			    if (domatch(d, c, 0) && !--num) {
				*t = sav;
				return r;
			    }
			    *t = sav;
		    } else
			for (r = 0, t = d; *t; r++, t++) {
			    sav = *t;
			    *t = '\0';
			    if (domatch(d, c, 0) && !--num) {
				*t = sav;
				return r;
			    }
			    *t = sav;
			}
		} else {
		    if (down)
			for (r = -1, t = d + strlen(d) - 1; t >= d; r--, t--) {
			    if (domatch(t, c, 0) && !--num)
				return r;
		    } else
			for (r = 1, t = d; *t; r++, t++)
			    if (domatch(t, c, 0) && !--num)
				return r;
		}
	    }
	}
    }
    return r;
}

/**/
Value
getvalue(char **pptr, int bracks)
{
    char *s = *pptr, *t = *pptr;
    char sav;
    Value v;
    int inv = 0;

    garr = NULL;

    if (idigit(*s))
	while (idigit(*s))
	    s++;
    else if (iident(*s))
	while (iident(*s))
	    s++;
    else if (*s == Quest)
	*s++ = '?';
    else if (*s == Pound)
	*s++ = '#';
    else if (*s == String)
	*s++ = '$';
    else if (*s == Qstring)
	*s++ = '$';
    else if (*s == Star)
	*s++ = '*';
    else if (*s == '#' || *s == '-' || *s == '?' || *s == '$' ||
	     *s == '_' || *s == '!' || *s == '@' || *s == '*')
	s++;
    else
	return NULL;
    if ((sav = *s))
	*s = '\0';
    if (idigit(*t) && *t != '0') {
	v = (Value) hcalloc(sizeof *v);
	v->pm = argvparam;
	v->inv = 0;
	v->a = v->b = atoi(t) - 1;
	if (sav)
	    *s = sav;
    } else {
	struct param *pm;
	int isvarat = !strcmp(t, "@");

	pm = (struct param *)gethnode(t, paramtab);
	if (sav)
	    *s = sav;
	*pptr = s;
	if (!pm || (pm->flags & PMFLAG_UNSET))
	    return NULL;
	v = (Value) hcalloc(sizeof *v);
	if (pmtype(pm) == PMFLAG_A)
	    v->isarr = isvarat ? -1 : 1;
	v->pm = pm;
	v->inv = 0;
	v->a = 0;
	v->b = -1;
	if (bracks && (*s == '[' || *s == Inbrack)) {
	    int a, b;
	    char *olds = s, *tbrack;

	    *s++ = '[';
	    for (tbrack = s; *tbrack && *tbrack != ']' && *tbrack != Outbrack; tbrack++)
		if (itok(*tbrack))
		    *tbrack = ztokens[*tbrack - Pound];
	    if (*tbrack == Outbrack)
		*tbrack = ']';
	    if ((s[0] == '*' || s[0] == '@') && s[1] == ']') {
		if (v->isarr)
		    v->isarr = (s[0] == '*') ? 1 : -1;
		v->a = 0;
		v->b = -1;
		s += 2;
	    } else {
		long we = 0, dummy;

		a = getarg(&s, &inv, v, 0, &we);
		if (a > 0 && !inv)
		    a--;

		if (inv) {
		    v->inv = 1;
		    if (*s == ']' || *s == Outbrack)
			s++;
		    v->isarr = 0;
		    v->a = v->b = a;
		    if (*s == ',' || *s == Comma) {
			zerr("invalid subscript", NULL, 0);
			while (*s != ']' && *s != Outbrack)
			    s++;
			*pptr = s;
			return v;
		    }
		} else {
		    if (*s == ',' || *s == Comma) {
			s++;
			b = getarg(&s, &inv, v, 1, &dummy);
			if (b > 0)
			    b--;
		    } else {
			b = we ? we : a;
		    }
		    if (*s == ']' || *s == Outbrack) {
			s++;
			if (v->isarr && a == b)
			    v->isarr = 0;
			v->a = a;
			v->b = b;
		    } else
			s = olds;
		}
	    }
	}
    }
    if (!bracks && *s)
	return NULL;
    *pptr = s;
    if (v->a > MAX_ARRLEN ||
	v->a < -MAX_ARRLEN) {
	zerr("subscript to %s: %d", (v->a < 0) ? "small" : "big", v->a);
	return NULL;
    }
    if (v->b > MAX_ARRLEN ||
	v->b < -MAX_ARRLEN) {
	zerr("subscript to %s: %d", (v->b < 0) ? "small" : "big", v->b);
	return NULL;
    }
    return v;
}

/**/
char *
getstrvalue(Value v)
{
    char *s, **ss;
    static char buf[32];

    if (!v)
	return "";
    if (v->inv) {
	sprintf(buf, "%d", v->a);
	return dupstring(buf);
    }
    if (pmtype(v->pm) != PMFLAG_A) {
	if ((pmtype(v->pm) == PMFLAG_i))
	    convbase(s = buf, v->pm->gets.ifn(v->pm), v->pm->ct);
	else
	    s = v->pm->gets.cfn(v->pm);
	if (v->a == 0 && v->b == -1)
	    return s;
	if (v->a < 0)
	    v->a += strlen(s);
	if (v->b < 0)
	    v->b += strlen(s);
	s = (v->a > (int)strlen(s)) ? dupstring("") : dupstring(s + v->a);
	if (v->b < v->a)
	    s[0] = '\0';
	else if (v->b - v->a < (int)strlen(s))
	    s[v->b - v->a + 1] = '\0';
	return s;
    }
    if (v->isarr)
	return spacejoin(v->pm->gets.afn(v->pm));

    ss = v->pm->gets.afn(v->pm);
    if (v->a < 0)
	v->a += arrlen(ss);
    s = (v->a >= arrlen(ss) || v->a < 0) ? "" : ss[v->a];
    return s;
}

static char *nular[] =
{"", NULL};

/**/
char **
getarrvalue(Value v)
{
    char **s;

    if (!v)
	return arrdup(nular);
    if (v->inv) {
	char buf[20];

	s = arrdup(nular);
	sprintf(buf, "%d", v->a);
	s[0] = dupstring(buf);
	return s;
    }
    s = v->pm->gets.afn(v->pm);
    if (v->a == 0 && v->b == -1)
	return s;
    if (v->a < 0)
	v->a += arrlen(s);
    if (v->b < 0)
	v->b += arrlen(s);
    if (v->a > arrlen(s) || v->a < 0)
	s = arrdup(nular);
    else
	s = arrdup(s) + v->a;
    if (v->b < v->a)
	s[0] = NULL;
    else if (v->b - v->a < arrlen(s))
	s[v->b - v->a + 1] = NULL;
    return s;
}

/**/
long
getintvalue(Value v)
{
    char **ss;

    if (!v || v->isarr)
	return 0;
    if (v->inv)
	return v->a;
    if (pmtype(v->pm) != PMFLAG_A) {
	if (pmtype(v->pm) == PMFLAG_i)
	    return v->pm->gets.ifn(v->pm);
	return zatol(v->pm->gets.cfn(v->pm));
    }
    ss = v->pm->gets.afn(v->pm);
    if (v->a < 0)
	v->a += arrlen(ss);
    if (v->a < 0 || v->a >= arrlen(ss))
	return 0;
    return zatol(ss[v->a]);
}

/**/
void
setstrvalue(Value v, char *val)
{
    char buf[32];

    if (v->pm->flags & PMFLAG_r) {
	zsfree(val);
	return;
    }
    switch (pmtype(v->pm)) {
    case PMFLAG_s:
	if (v->a == 0 && v->b == -1) {
	    (v->pm->sets.cfn) (v->pm, val);
	    if (v->pm->flags & (PMFLAG_L | PMFLAG_R | PMFLAG_Z) && !v->pm->ct)
		v->pm->ct = strlen(val);
	} else {
	    char *z, *y, *x;
	    int zlen;

	    z = dupstring((v->pm->gets.cfn) (v->pm));
	    zlen = strlen(z);
	    if (v->inv)
		v->a--, v->b--;
	    if (v->a < 0) {
		v->a += zlen;
		if (v->a < 0)
		    v->a = 0;
	    }
	    if (v->a > zlen)
		v->a = zlen;
	    if (v->b < 0)
		v->b += zlen;
	    if (v->b < v->a)
		v->b = v->a;
	    if (v->b > zlen - 1)
		v->b = zlen - 1;
	    z[v->a] = '\0';
	    y = z + v->b + 1;
	    x = (char *)zalloc(strlen(z) + strlen(y) + strlen(val) + 1);
	    strcpy(x, z);
	    strcat(x, val);
	    strcat(x, y);
	    (v->pm->sets.cfn) (v->pm, x);
	    zsfree(val);
	}
	break;
    case PMFLAG_i:
	(v->pm->sets.ifn) (v->pm, matheval(val));
	if (!v->pm->ct && lastbase != 1)
	    v->pm->ct = lastbase;
	zsfree(val);
	break;
    case PMFLAG_A:
	if (v->a != v->b) {
	    char **ss = (char **)zalloc(2 * sizeof(char *));

	    ss[0] = val;
	    ss[1] = NULL;
	} else {
	    char **ss = (v->pm->gets.afn) (v->pm);
	    int ac, ad, t0;

	    if (v->inv)
		v->a--, v->b--;
	    ac = arrlen(ss);
	    if (v->a < 0) {
		v->a += ac;
		if (v->a < 0)
		    v->a = 0;
	    }
	    if (v->a >= ac) {
		char **st = ss;

		ad = v->a + 1;
		ss = (char **)zalloc((ad + 1) * sizeof *ss);
		for (t0 = 0; t0 != ac; t0++)
		    ss[t0] = ztrdup(st[t0]);
		while (ac < ad)
		    ss[ac++] = ztrdup("");
		ss[ac] = NULL;
	    }
	    zsfree(ss[v->a]);
	    ss[v->a] = val;
	    (v->pm->sets.afn) (v->pm, ss);
	}
	break;
    }
    if ((!v->pm->env && !(v->pm->flags & PMFLAG_x) &&
	 !(isset(ALLEXPORT) && !v->pm->old)) ||
	(v->pm->flags & PMFLAG_A) || v->pm->ename)
	return;
    if (pmtype(v->pm) == PMFLAG_i)
	convbase(val = buf, v->pm->gets.ifn(v->pm), v->pm->ct);
    else
	val = v->pm->gets.cfn(v->pm);
    if (v->pm->env)
	v->pm->env = replenv(v->pm->env, val);
    else {
	v->pm->flags |= PMFLAG_x;
	v->pm->env = addenv(v->pm->nam, val);
    }
}

/**/
void
setintvalue(Value v, long val)
{
    char buf[20];

    if (v->pm->flags & PMFLAG_r)
	return;
    sprintf(buf, "%ld", val);
    if (v->pm->env) {
	v->pm->env = replenv(v->pm->env, buf);
    } else if ((v->pm->flags & PMFLAG_x) ||
	       (isset(ALLEXPORT) && !v->pm->old)) {
	v->pm->flags |= PMFLAG_x;
	v->pm->env = addenv(v->pm->nam, buf);
    }
    switch (pmtype(v->pm)) {
    case PMFLAG_s:
	(v->pm->sets.cfn) (v->pm, ztrdup(buf));
	break;
    case PMFLAG_i:
	(v->pm->sets.ifn) (v->pm, val);
	if (!v->pm->ct && lastbase != -1)
	    v->pm->ct = lastbase;
	break;
    case PMFLAG_A:
	zerr("attempt to assign integer to array", NULL, 0);
	break;
    }
}

/**/
void
setintenv(char *s, long val)
{
    Param pm;
    char buf[20];

    if ((pm = (Param) gethnode(s, paramtab)) && pm->env) {
	sprintf(buf, "%ld", val);
	pm->env = replenv(pm->env, buf);
    }
}

/**/
void
setarrvalue(Value v, char **val)
{
    if (v->pm->flags & PMFLAG_r) {
	freearray(val);
	return;
    }
    if (pmtype(v->pm) != PMFLAG_A) {
	freearray(val);
	zerr("attempt to assign array value to non-array", NULL, 0);
	return;
    }
    if (v->a == 0 && v->b == -1)
	(v->pm->sets.afn) (v->pm, val);
    else {
	char **old, **new, **p, **q, **r;
	int n, nn, l, ll, i;

	if (v->inv)
	    v->a--, v->b--;
	q = old = v->pm->gets.afn(v->pm);
	n = arrlen(old);
	if (v->a < 0)
	    v->a += n;
	if (v->b < 0)
	    v->b += n;
	if (v->a < 0)
	    v->a = 0;
	if (v->b < 0)
	    v->b = 0;
	if (v->a >= n)
	    v->a = n;
	if (v->b >= n)
	    v->b = n;

	if (v->a > v->b)
	    (v->pm->sets.afn) (v->pm, arrdup(nular));
	else {
	    l = v->b - v->a + 1;

	    nn = arrlen(val);

	    ll = n - l + nn;

	    p = new = (char **)zcalloc(sizeof(char *) * (ll + 2));

	    for (i = 0; i < v->a; i++)
		*p++ = ztrdup(*q++);
	    for (r = val; *r;)
		*p++ = ztrdup(*r++);
	    if (*q)
		for (q += l; *q;)
		    *p++ = ztrdup(*q++);
	    *p = NULL;

	    (v->pm->sets.afn) (v->pm, new);
	}
	freearray(val);
    }
}

/**/
long
getiparam(char *s)
{
    Value v;

    if (!(v = getvalue(&s, 0)))
	return 0;
    return getintvalue(v);
}

/**/
char *
getsparam(char *s)
{
    Value v;

    if (!(v = getvalue(&s, 0)))
	return NULL;
    return getstrvalue(v);
}

/**/
char **
getaparam(char *s)
{
    Value v;

    if (!((v = getvalue(&s, 0)) && v->isarr))
	return NULL;
    return getarrvalue(v);
}

/**/
Param
setsparam(char *s, char *val)
{
    Value v;
    char *t = s;
    char *ss;

    if (!isident(s)) {
	zerr("not an identifier: %s", s, 0);
	zsfree(val);
	errflag = 1;
	return NULL;
    }
    if ((ss = strchr(s, '['))) {
	*ss = '\0';
	if (!(v = getvalue(&s, 1)))
	    createparam(t, PMFLAG_A);
	*ss = '[';
	v = NULL;
    } else {
	if (!(v = getvalue(&s, 1)))
	    createparam(t, PMFLAG_s);
	else if ((v->pm->flags & PMTYPE) == PMFLAG_A &&
		 !(v->pm->flags & PMFLAG_SPECIAL)) {
	    unsetparam(t);
	    createparam(t, PMFLAG_s);
	    v = NULL;
	}
    }
    if (!v)
	if (!(v = getvalue(&t, 1)))
	    return NULL;
    setstrvalue(v, val);
    return v->pm;
}

/**/
Param
setaparam(char *s, char **val)
{
    Value v;
    char *t = s;
    char *ss;

    if (!isident(s)) {
	zerr("not an identifier: %s", s, 0);
	freearray(val);
	errflag = 1;
	return NULL;
    }
    if ((ss = strchr(s, '['))) {
	*ss = '\0';
	if (!(v = getvalue(&s, 1)))
	    createparam(t, PMFLAG_A);
	*ss = '[';
	v = NULL;
    } else {
	if (!(v = getvalue(&s, 1)))
	    createparam(t, PMFLAG_A);
	else if ((v->pm->flags & PMTYPE) != PMFLAG_A &&
		 !(v->pm->flags & PMFLAG_SPECIAL)) {
	    unsetparam(t);
	    createparam(t, PMFLAG_A);
	    v = NULL;
	}
    }
    if (!v)
	if (!(v = getvalue(&t, 1)))
	    return NULL;
    setarrvalue(v, val);
    return v->pm;
}

/**/
Param
setiparam(char *s, long val)
{
    Value v;
    char *t = s;
    Param pm;

    if (!isident(s)) {
	zerr("not an identifier: %s", s, 0);
	errflag = 1;
	return NULL;
    }
    if (!(v = getvalue(&s, 0))) {
	pm = createparam(t, PMFLAG_i);
	pm->u.val = val;
	return pm;
    }
    setintvalue(v, val);
    return v->pm;
}

/**/
void
unsetparam(char *s)
{
    Param pm, oldpm;
    int spec;
    static int altflag = 0;

    if (!(pm = (Param) gethnode(s, paramtab)))
	return;
    if ((pm->flags & PMFLAG_r) && pm->level <= locallevel)
	return;
    spec = (pm->flags & PMFLAG_SPECIAL) && !pm->level;
    switch (pmtype(pm)) {
    case 0:
	(pm->sets.cfn) (pm, NULL);
	break;
    case PMFLAG_i:
	(pm->sets.ifn) (pm, 0);
	break;
    case PMFLAG_A:
	(pm->sets.afn) (pm, NULL);
	break;
    }
    if ((pm->flags & PMFLAG_x) && pm->env) {
	delenv(pm->env);
	zsfree(pm->env);
	pm->env = NULL;
    }
    if (pm->ename && !altflag) {
	altflag = 1;
	unsetparam(pm->ename);
	altflag = 0;
    }
    if ((locallevel && locallevel == pm->level) || spec) {
	pm->flags |= PMFLAG_UNSET;
    } else {
	oldpm = pm->old;
	freepm(remhnode(s, paramtab));
	if (oldpm) {
	    addhnode(ztrdup(s), oldpm, paramtab, freepm);
	    if (!(oldpm->flags & PMTYPE) && oldpm->sets.cfn == strsetfn)
		adduserdir(oldpm->nam, oldpm->u.str, 0, 1);
	}
    }
}

/**/
void
intsetfn(Param pm, long x)
{
    pm->u.val = x;
}

/**/
long
intgetfn(Param pm)
{
    return pm->u.val;
}

/**/
void
strsetfn(Param pm, char *x)
{
    zsfree(pm->u.str);
    pm->u.str = x;
    adduserdir(pm->nam, x, 0, 2);
}

/**/
char *
strgetfn(Param pm)
{
    return pm->u.str ? pm->u.str : "";
}

/**/
void
nullsetfn(Param pm, char *x)
{
    zsfree(x);
}

/**/
void
arrsetfn(Param pm, char **x)
{
    if (pm->u.arr && pm->u.arr != x)
	freearray(pm->u.arr);
    pm->u.arr = x;
}

/**/
char **
arrgetfn(Param pm)
{
    static char *nullarray = NULL;

    return pm->u.arr ? pm->u.arr : &nullarray;
}

/**/
void
intvarsetfn(Param pm, long x)
{
    *((long *)pm->data) = x;
}

/**/
long
intvargetfn(Param pm)
{
    return *((long *)pm->data);
}

/**/
void
strvarsetfn(Param pm, char *x)
{
    char **q = ((char **)pm->data);

    zsfree(*q);
    *q = x;
}

/**/
char *
strvargetfn(Param pm)
{
    char *s = *((char **)pm->data);

    if (!s)
	return "";
    return s;
}

/**/
char *
strconstgetfn(Param pm)
{
    return (char *)pm->data;
}

/**/
char **
colonfix(char *x, char *ename)
{
    char **s, **t;

    s = colonsplit(x);
    if (ename)
	arrfixenv(ename, s);
    zsfree(x);
    for (t = s; *t; t++)
	if (!**t) {
	    free(*t);
	    *t = ztrdup(".");
	}
    return s;
}

/**/
void
colonarrsetfn(Param pm, char *x)
{
    char ***dptr = (char ***)pm->data;

    freearray(*dptr);
    if (pm->data == (void *) & mailpath && x && !*x) {
	zsfree(x);
	*dptr = mkarray(NULL);
    } else
	*dptr = x ? colonfix(x, pm->ename ? pm->nam : NULL) : mkarray(NULL);
}

/**/
void
colonpathsetfn(Param pm, char *x)
{
    freearray(path);
    path = x ? colonfix(x, pm->nam) : mkarray(NULL);
    newcmdnamtab();
}

/**/
char *
colonarrgetfn(Param pm)
{
    return join(*(char ***)pm->data, ':');
}

/**/
char *
colonpathgetfn(Param pm)
{
    return join(path, ':');
}

/**/
char **
arrvargetfn(Param pm)
{
    return *((char ***)pm->data);
}

/**/
void
arrvarsetfn(Param pm, char **x)
{
    char ***dptr = (char ***)pm->data;

    if (*dptr != x)
	freearray(*dptr);
    *dptr = x ? x : mkarray(NULL);
    if (pm->ename && x)
	arrfixenv(pm->ename, x);
}

/**/
char **
pathgetfn(Param pm)
{
    return path;
}

/**/
void
pathsetfn(Param pm, char **x)
{
    if (path != x)
	freearray(path);
    path = x ? x : mkarray(NULL);
    newcmdnamtab();
    if (x)
	arrfixenv("PATH", x);
}

/**/
long
poundgetfn(Param pm)
{
    return arrlen(pparams);
}

/**/
long
randomgetfn(Param pm)
{
    return rand() & 0x7fff;
}

/**/
void
randomsetfn(Param pm, long v)
{
    srand((unsigned int)v);
}

/**/
long
secondsgetfn(Param pm)
{
    return time(NULL) - shtimer.tv_sec;
}

/**/
void
secondssetfn(Param pm, long x)
{
    shtimer.tv_sec = time(NULL) - x;
    shtimer.tv_usec = 0;
}

/**/
long
uidgetfn(Param pm)
{
    return getuid();
}

/**/
long
euidgetfn(Param pm)
{
    return geteuid();
}

/**/
void
uidsetfn(Param pm, uid_t x)
{
#ifdef HAVE_SETUID
    setuid(x);
#endif
}

/**/
void
euidsetfn(Param pm, uid_t x)
{
#ifdef HAVE_SETEUID
    seteuid(x);
#endif
}

/**/
long
gidgetfn(Param pm)
{
    return getgid();
}

/**/
long
egidgetfn(Param pm)
{
    return getegid();
}

/**/
void
gidsetfn(Param pm, gid_t x)
{
#ifdef HAVE_SETUID
    setgid(x);
#endif
}

/**/
void
egidsetfn(Param pm, gid_t x)
{
#ifdef HAVE_SETEUID
    setegid(x);
#endif
}

/**/
char *
ifsgetfn(Param pm)
{
    return ifs;
}

/**/
void
ifssetfn(Param pm, char *x)
{
    if (x) {
	zsfree(ifs);
	ifs = x;
    }
    inittyptab();
}

#ifdef LC_ALL
/**/
void
langsetfn(Param pm, char *x)
{
   setlocale(LC_ALL, x);
   strsetfn(pm, x);
}
#endif

/**/
void
histsizesetfn(Param pm, long v)
{
    if ((histsiz = v) <= 2)
	histsiz = 2;
    resizehistents();
}

/**/
long
histsizegetfn(Param pm)
{
    return histsiz;
}

/**/
void
lithistsizesetfn(Param pm, long v)
{
    if ((lithistsiz = v) <= 2)
	lithistsiz = 2;
    resizehistents();
}

/**/
long
lithistsizegetfn(Param pm)
{
    return lithistsiz;
}

/**/
long
errnogetfn(Param pm)
{
    return errno;
}

/**/
char *
dashgetfn(Param pm)
{
    static char buf[100];
    char *val;
    int t0;

    for (val = buf, t0 = ' '; t0 <= 'z'; t0++)
	if (isset(t0))
	    *val++ = t0;
    *val = '\0';
    return buf;
}

/**/
void
histcharssetfn(Param pm, char *x)
{
    if (x) {
	bangchar = x[0];
	hatchar = (bangchar) ? x[1] : '\0';
	hashchar = (hatchar) ? x[2] : '\0';
	zsfree(x);
    }
    else {
	bangchar = '!';
	hashchar = '#';
	hatchar = '^';
    }
}

/**/
char *
histcharsgetfn(Param pm)
{
    static char buf[4];

    buf[0] = bangchar;
    buf[1] = hatchar;
    buf[2] = hashchar;
    buf[3] = '\0';
    return buf;
}

/**/
char *
homegetfn(Param pm)
{
    return home;
}

/**/
void
homesetfn(Param pm, char *x)
{
    zsfree(home);
    if (x && isset(CHASELINKS) && (home = xsymlink(x)))
	zsfree(x);
    else
	home = x ? x : ztrdup("");
    adduserdir("", home, 0, 1);
}

/**/
char *
wordcharsgetfn(Param pm)
{
    return wordchars;
}

/**/
void
wordcharssetfn(Param pm, char *x)
{
    zsfree(wordchars);
    if (x)
	wordchars = x;
    else
	wordchars = ztrdup(DEFAULT_WORDCHARS);
    inittyptab();
}

/**/
char *
underscoregetfn(Param pm)
{
    return underscore;
}

/**/
char *
termgetfn(Param pm)
{
    return term;
}

extern hasam;

/**/
void
termsetfn(Param pm, char *x)
{
    zsfree(term);
    term = x ? x : ztrdup("");
    if (!*term) {
	termok = 0;
    } else if (tgetent(termbuf, term) != 1) {
	zerr("can't find termcap info for %s", term, 0);
	errflag = 0;
	termok = 0;
    } else {
	char tbuf[1024], *pp;
	int t0;

	termok = 1;
	for (t0 = 0; t0 != TC_COUNT; t0++) {
	    pp = tbuf;
	    zsfree(tcstr[t0]);
	/* AIX tgetstr() ignores second argument */
	    if (!(pp = tgetstr(tccapnams[t0], &pp)))
		tcstr[t0] = NULL, tclen[t0] = 0;
	    else {
		tcstr[t0] = (char *)zalloc(tclen[t0] = strlen(pp) + 1);
		memcpy(tcstr[t0], pp, tclen[t0]);
	    }
	}

    /* if there's no termcap entry for cursor up, use single line mode. */

	if (!tccan(TCUP)) {
		tcstr[TCUP] = NULL;
		opts[SINGLELINEZLE] = OPT_SET;
	}
	hasam = tgetflag("am");
    /* if there's no termcap entry for cursor left, use \b. */

	if (!tccan(TCLEFT)) {
	    tcstr[TCLEFT] = ztrdup("\b");
	    tclen[TCLEFT] = 1;
	}
    /* if there's no termcap entry for clear, use ^L. */

	if (!tccan(TCCLEARSCREEN)) {
	    tcstr[TCCLEARSCREEN] = ztrdup("\14");
	    tclen[TCCLEARSCREEN] = 1;
	}
    /* if the termcap entry for down is \n, don't use it. */

	if (tccan(TCDOWN) && tcstr[TCDOWN][0] == '\n') {
	    tclen[TCDOWN] = 0;
	    zsfree(tcstr[TCDOWN]);
	    tcstr[TCDOWN] = NULL;
	}
    }
}

/**/
char *
mkenvstr(char *x, char *y)
{
    char *z;
    int xl = strlen(x), yl = strlen(y);

    z = (char *)zalloc(xl + yl + 2);
    strcpy(z, x);
    z[xl] = '=';
    strcpy(z + xl + 1, y);
    z[xl + yl + 1] = '\0';
    return z;
}

/**/
void
arrfixenv(char *s, char **t)
{
    char **ep, *u = join(t, ':');
    int sl = strlen(s);
    Param pm = (Param) gethnode(s, paramtab);

    for (ep = environ; *ep; ep++)
	if (!strncmp(*ep, s, sl) && (*ep)[sl] == '=') {
	    pm->env = replenv(*ep, u);
	    return;
	}
    if (isset(ALLEXPORT))
	pm->flags |= PMFLAG_x;
    if (pm->flags & PMFLAG_x)
	pm->env = addenv(s, u);
}

/* Change the value of an existing environment variable */
/* We can probably change this to use zrealloc.         */

/**/
char *
replenv(char *e, char *value)
{
    char **ep;

    for (ep = environ; *ep; ep++)
	if (*ep == e) {
	    char *s = e;

	    while (*s++ != '=');
	    *s = '\0';
	    *ep = (char *)zalloc(strlen(e) + strlen(value) + 2);
	    strcpy(*ep, e);
	    strcat(*ep, value);
	    zsfree(e);
	    return *ep;
	}
    return NULL;
}

/* we should probably change this to use zrealloc */

/**/
char *
addenv(char *name, char *value)
{
    char **ep, **ep2, **ep3;
    char *s, *t;
    int envct;

    /* First check if there is already an environment *
     * variable matching string 'name'.               */
    for (ep = environ; *ep; ep++) {
	for (s = *ep, t = name; *s && *s == *t; s++, t++);
	if (*s == '=' && !*t) {
	    zsfree(*ep);
	    return *ep = mkenvstr(name, value);
	}
    }

    /* Else we have to make room and add it */
    envct = arrlen(environ);
    ep = ep2 = (char **)zalloc((sizeof(char *)) * (envct + 3));

    for (ep3 = environ; (*ep2 = *ep3); ep3++, ep2++);
    *ep2 = mkenvstr(name, value);
    ep2[1] = NULL;
    free(environ);
    environ = ep;
    return *ep2;
}

/* Delete a pointer from the list of pointers to   *
 * environment variables by shifting all the other *
 * pointers up one slot.                           */

/**/
void
delenv(char *x)
{
    char **ep;

    ep = environ;
    for (; *ep; ep++)
	if (*ep == x)
	    break;
    if (*ep)
	for (; (ep[0] = ep[1]); ep++);
}

/**/
void
convbase(char *s, long v, int base)
{
    int digs = 0;
    unsigned long x;

    if (base <= 1)
	base = 10;

    if (base != 10) {
	sprintf(s, "%d#", base);
	s += strlen(s);
    }
    if (v < 0) {
	x = -v;
	digs++;
    }
    else
	x = v;
    for (; x; digs++)
	x /= base;
    if (!digs)
	digs = 1;
    s[digs--] = '\0';
    x = (v < 0) ? -v : v;
    while (digs >= 0) {
	int dig = x % base;

	s[digs--] = (dig < 10) ? '0' + dig : dig - 10 + 'A';
	x /= base;
    }
    if (v < 0)
	s[0] = '-';
}
