openenvutils/commandshell/shell/src/params.c
changeset 0 2e3d3ce01487
child 1 0fdb7f6b0309
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/openenvutils/commandshell/shell/src/params.c	Tue Feb 02 10:12:00 2010 +0200
@@ -0,0 +1,4034 @@
+// params.c - parameters
+//
+// © Portions Copyright (c) Symbian Software Ltd 2007-2008. All rights reserved.
+//
+/*
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+#include "zsh.mdh"
+#include "params.pro"
+
+#include "version.h"
+
+#ifdef __SYMBIAN32__
+#ifdef __WINSCW__
+#pragma warn_unusedarg off
+#pragma warn_possunwant off
+#endif//__WINSCW__
+#endif//__SYMBIAN32__
+
+/* what level of localness we are at */
+ 
+/**/
+mod_export int locallevel;
+ 
+/* Variables holding values of special parameters */
+ 
+/**/
+mod_export
+char **pparams,		/* $argv        */
+     **cdpath,		/* $cdpath      */
+     **fpath,		/* $fpath       */
+     **mailpath,	/* $mailpath    */
+     **manpath,		/* $manpath     */
+     **psvar,		/* $psvar       */
+     **watch;		/* $watch       */
+/**/
+mod_export
+char **path,		/* $path        */
+     **fignore;		/* $fignore     */
+ 
+/**/
+char *argzero,		/* $0           */
+     *home,		/* $HOME        */
+     *nullcmd,		/* $NULLCMD     */
+     *oldpwd,		/* $OLDPWD      */
+     *zoptarg,		/* $OPTARG      */
+     *prompt,		/* $PROMPT      */
+     *prompt2,		/* $PROMPT2     */
+     *prompt3,		/* $PROMPT3     */
+     *prompt4,		/* $PROMPT4     */
+     *readnullcmd,	/* $READNULLCMD */
+     *rprompt,		/* $RPROMPT     */
+     *rprompt2,		/* $RPROMPT2    */
+     *sprompt,		/* $SPROMPT     */
+     *wordchars,	/* $WORDCHARS   */
+     *zsh_name;		/* $ZSH_NAME    */
+/**/
+mod_export
+char *ifs,		/* $IFS         */
+     *postedit,		/* $POSTEDIT    */
+     *term,		/* $TERM        */
+     *ttystrname,	/* $TTY         */
+     *pwd;		/* $PWD         */
+
+/**/
+mod_export
+zlong lastval,		/* $?           */
+     mypid,		/* $$           */
+     lastpid,		/* $!           */
+     columns,		/* $COLUMNS     */
+     lines,		/* $LINES       */
+     ppid;		/* $PPID        */
+/**/
+zlong lineno,		/* $LINENO      */
+     zoptind,		/* $OPTIND      */
+     shlvl;		/* $SHLVL       */
+
+/* $histchars */
+ 
+/**/
+mod_export unsigned char bangchar;
+/**/
+unsigned char hatchar, hashchar;
+ 
+/* $SECONDS = now.tv_sec - shtimer.tv_sec
+ *          + (now.tv_usec - shtimer.tv_usec) / 1000000.0
+ * (rounded to an integer if the parameter is not set to float) */
+ 
+/**/
+struct timeval shtimer;
+ 
+/* 0 if this $TERM setup is usable, otherwise it contains TERM_* flags */
+
+/**/
+mod_export int termflags;
+
+/* Standard methods for get/set/unset pointers in parameters */
+
+/**/
+mod_export const struct gsu_scalar stdscalar_gsu =
+{ strgetfn, strsetfn, stdunsetfn };
+/**/
+mod_export const struct gsu_scalar varscalar_gsu =
+{ strvargetfn, strvarsetfn, stdunsetfn };
+/**/
+mod_export const struct gsu_scalar nullsetscalar_gsu =
+{ strgetfn, nullstrsetfn, NULL };
+
+/**/
+mod_export const struct gsu_integer stdinteger_gsu =
+{ intgetfn, intsetfn, stdunsetfn };
+/**/
+mod_export const struct gsu_integer varinteger_gsu =
+{ intvargetfn, intvarsetfn, stdunsetfn };
+/**/
+mod_export const struct gsu_integer nullsetinteger_gsu =
+{ intgetfn, NULL, NULL };
+
+/**/
+mod_export const struct gsu_float stdfloat_gsu =
+{ floatgetfn, floatsetfn, stdunsetfn };
+
+/**/
+mod_export const struct gsu_array stdarray_gsu =
+{ arrgetfn, arrsetfn, stdunsetfn };
+/**/
+mod_export const struct gsu_array vararray_gsu =
+{ arrvargetfn, arrvarsetfn, stdunsetfn };
+
+/**/
+mod_export const struct gsu_hash stdhash_gsu =
+{ hashgetfn, hashsetfn, stdunsetfn };
+/**/
+mod_export const struct gsu_hash nullsethash_gsu =
+{ hashgetfn, nullsethashfn, nullunsetfn };
+
+
+/* Non standard methods (not exported) */
+static const struct gsu_integer pound_gsu =
+{ poundgetfn, nullintsetfn, stdunsetfn };
+static const struct gsu_integer errno_gsu =
+{ errnogetfn, errnosetfn, stdunsetfn };
+static const struct gsu_integer gid_gsu =
+{ gidgetfn, gidsetfn, stdunsetfn };
+static const struct gsu_integer egid_gsu =
+{ egidgetfn, egidsetfn, stdunsetfn };
+static const struct gsu_integer histsize_gsu =
+{ histsizegetfn, histsizesetfn, stdunsetfn };
+static const struct gsu_integer random_gsu =
+{ randomgetfn, randomsetfn, stdunsetfn };
+static const struct gsu_integer savehist_gsu =
+{ savehistsizegetfn, savehistsizesetfn, stdunsetfn };
+static const struct gsu_integer intseconds_gsu =
+{ intsecondsgetfn, intsecondssetfn, stdunsetfn };
+static const struct gsu_float floatseconds_gsu =
+{ floatsecondsgetfn, floatsecondssetfn, stdunsetfn };
+static const struct gsu_integer uid_gsu =
+{ uidgetfn, uidsetfn, stdunsetfn };
+static const struct gsu_integer euid_gsu =
+{ euidgetfn, euidsetfn, stdunsetfn };
+static const struct gsu_integer ttyidle_gsu =
+{ ttyidlegetfn, nullintsetfn, stdunsetfn };
+
+static const struct gsu_scalar username_gsu =
+{ usernamegetfn, usernamesetfn, stdunsetfn };
+static const struct gsu_scalar dash_gsu =
+{ dashgetfn, nullstrsetfn, stdunsetfn };
+static const struct gsu_scalar histchars_gsu =
+{ histcharsgetfn, histcharssetfn, stdunsetfn };
+static const struct gsu_scalar home_gsu =
+{ homegetfn, homesetfn, stdunsetfn };
+static const struct gsu_scalar term_gsu =
+{ termgetfn, termsetfn, stdunsetfn };
+static const struct gsu_scalar wordchars_gsu =
+{ wordcharsgetfn, wordcharssetfn, stdunsetfn };
+static const struct gsu_scalar ifs_gsu =
+{ ifsgetfn, ifssetfn, stdunsetfn };
+static const struct gsu_scalar underscore_gsu =
+{ underscoregetfn, nullstrsetfn, stdunsetfn };
+#ifdef USE_LOCALE
+static const struct gsu_scalar lc_blah_gsu =
+{ strgetfn, lcsetfn, stdunsetfn };
+static const struct gsu_scalar lang_gsu =
+{ strgetfn, langsetfn, stdunsetfn };
+static const struct gsu_scalar lc_all_gsu =
+{ strgetfn, lc_allsetfn, stdunsetfn };
+#endif
+
+static const struct gsu_integer varint_readonly_gsu =
+{ intvargetfn, nullintsetfn, stdunsetfn };
+static const struct gsu_integer zlevar_gsu =
+{ intvargetfn, zlevarsetfn, stdunsetfn };
+
+static const struct gsu_scalar colonarr_gsu =
+{ colonarrgetfn, colonarrsetfn, stdunsetfn };
+
+static const struct gsu_integer argc_gsu =
+{ poundgetfn, nullintsetfn, stdunsetfn };
+static const struct gsu_array pipestatus_gsu =
+{ pipestatgetfn, pipestatsetfn, stdunsetfn };
+
+/* Nodes for special parameters for parameter hash table */
+
+#ifdef HAVE_UNION_INIT
+# define BR(X) {X}
+typedef struct param initparam;
+#else
+# define BR(X) X
+typedef struct iparam {
+    struct hashnode *next;
+    char *nam;			/* hash data                             */
+    int flags;			/* PM_* flags (defined in zsh.h)         */
+    void *value;
+    void *gsu;			/* get/set/unset methods */
+    int base;			/* output base                           */
+    int width;			/* output field width                    */
+    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  */
+} initparam;
+#endif
+
+static initparam special_params[] ={
+#define GSU(X) BR((GsuScalar)(void *)(&(X)))
+#define NULL_GSU BR((GsuScalar)(void *)NULL)
+#define IPDEF1(A,B,C) {NULL,A,PM_INTEGER|PM_SPECIAL|C,BR(NULL),GSU(B),10,0,NULL,NULL,NULL,0}
+IPDEF1("#", pound_gsu, PM_READONLY),
+IPDEF1("ERRNO", errno_gsu, 0),
+IPDEF1("GID", gid_gsu, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF1("EGID", egid_gsu, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF1("HISTSIZE", histsize_gsu, PM_RESTRICTED),
+IPDEF1("RANDOM", random_gsu, 0),
+IPDEF1("SAVEHIST", savehist_gsu, PM_RESTRICTED),
+IPDEF1("SECONDS", intseconds_gsu, 0),
+IPDEF1("UID", uid_gsu, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF1("EUID", euid_gsu, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF1("TTYIDLE", ttyidle_gsu, PM_READONLY),
+
+#define IPDEF2(A,B,C) {NULL,A,PM_SCALAR|PM_SPECIAL|C,BR(NULL),GSU(B),0,0,NULL,NULL,NULL,0}
+IPDEF2("USERNAME", username_gsu, PM_DONTIMPORT|PM_RESTRICTED),
+IPDEF2("-", dash_gsu, PM_READONLY),
+IPDEF2("histchars", histchars_gsu, PM_DONTIMPORT),
+IPDEF2("HOME", home_gsu, 0),
+IPDEF2("TERM", term_gsu, 0),
+IPDEF2("WORDCHARS", wordchars_gsu, 0),
+IPDEF2("IFS", ifs_gsu, PM_DONTIMPORT),
+IPDEF2("_", underscore_gsu, PM_READONLY),
+
+#ifdef USE_LOCALE
+# define LCIPDEF(name) IPDEF2(name, lc_blah_gsu, PM_UNSET)
+IPDEF2("LANG", lang_gsu, PM_UNSET),
+IPDEF2("LC_ALL", lc_all_gsu, PM_UNSET),
+# ifdef LC_COLLATE
+LCIPDEF("LC_COLLATE"),
+# endif
+# ifdef LC_CTYPE
+LCIPDEF("LC_CTYPE"),
+# endif
+# ifdef LC_MESSAGES
+LCIPDEF("LC_MESSAGES"),
+# endif
+# ifdef LC_NUMERIC
+LCIPDEF("LC_NUMERIC"),
+# endif
+# ifdef LC_TIME
+LCIPDEF("LC_TIME"),
+# endif
+#endif /* USE_LOCALE */
+
+#define IPDEF4(A,B) {NULL,A,PM_INTEGER|PM_READONLY|PM_SPECIAL,BR((void *)B),GSU(varint_readonly_gsu),10,0,NULL,NULL,NULL,0}
+IPDEF4("!", &lastpid),
+IPDEF4("$", &mypid),
+IPDEF4("?", &lastval),
+IPDEF4("HISTCMD", &curhist),
+IPDEF4("LINENO", &lineno),
+IPDEF4("PPID", &ppid),
+
+#define IPDEF5(A,B,F) {NULL,A,PM_INTEGER|PM_SPECIAL,BR((void *)B),GSU(varinteger_gsu),10,0,NULL,NULL,NULL,0}
+IPDEF5("COLUMNS", &columns, zlevar_gsu),
+IPDEF5("LINES", &lines, zlevar_gsu),
+IPDEF5("OPTIND", &zoptind, varinteger_gsu),
+IPDEF5("SHLVL", &shlvl, varinteger_gsu),
+IPDEF5("TRY_BLOCK_ERROR", &try_errflag, varinteger_gsu),
+
+#define IPDEF7(A,B) {NULL,A,PM_SCALAR|PM_SPECIAL,BR((void *)B),GSU(varscalar_gsu),0,0,NULL,NULL,NULL,0}
+IPDEF7("OPTARG", &zoptarg),
+IPDEF7("NULLCMD", &nullcmd),
+IPDEF7("POSTEDIT", &postedit),
+IPDEF7("READNULLCMD", &readnullcmd),
+IPDEF7("PS1", &prompt),
+IPDEF7("RPS1", &rprompt),
+IPDEF7("RPROMPT", &rprompt),
+IPDEF7("PS2", &prompt2),
+IPDEF7("RPS2", &rprompt2),
+IPDEF7("RPROMPT2", &rprompt2),
+IPDEF7("PS3", &prompt3),
+IPDEF7("PS4", &prompt4),
+IPDEF7("SPROMPT", &sprompt),
+IPDEF7("0", &argzero),
+
+#define IPDEF8(A,B,C,D) {NULL,A,D|PM_SCALAR|PM_SPECIAL,BR((void *)B),GSU(colonarr_gsu),0,0,NULL,C,NULL,0}
+IPDEF8("CDPATH", &cdpath, "cdpath", 0),
+IPDEF8("FIGNORE", &fignore, "fignore", 0),
+IPDEF8("FPATH", &fpath, "fpath", 0),
+IPDEF8("MAILPATH", &mailpath, "mailpath", 0),
+IPDEF8("WATCH", &watch, "watch", 0),
+IPDEF8("PATH", &path, "path", PM_RESTRICTED),
+IPDEF8("PSVAR", &psvar, "psvar", 0),
+
+/* MODULE_PATH is not imported for security reasons */
+IPDEF8("MODULE_PATH", &module_path, "module_path", PM_DONTIMPORT|PM_RESTRICTED),
+
+#define IPDEF9F(A,B,C,D) {NULL,A,D|PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT,BR((void *)B),GSU(vararray_gsu),0,0,NULL,C,NULL,0}
+#define IPDEF9(A,B,C) IPDEF9F(A,B,C,0)
+IPDEF9F("*", &pparams, NULL, PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT|PM_READONLY),
+IPDEF9F("@", &pparams, NULL, PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT|PM_READONLY),
+{NULL,NULL,0,BR(NULL),NULL_GSU,0,0,NULL,NULL,NULL,0},
+
+#define IPDEF10(A,B) {NULL,A,PM_ARRAY|PM_SPECIAL,BR(NULL),GSU(B),10,0,NULL,NULL,NULL,0}
+
+/* The following parameters are not available in sh/ksh compatibility *
+ * mode. All of these have sh compatible equivalents.                */
+IPDEF1("ARGC", argc_gsu, PM_READONLY),
+IPDEF2("HISTCHARS", histchars_gsu, PM_DONTIMPORT),
+IPDEF4("status", &lastval),
+IPDEF7("prompt", &prompt),
+IPDEF7("PROMPT", &prompt),
+IPDEF7("PROMPT2", &prompt2),
+IPDEF7("PROMPT3", &prompt3),
+IPDEF7("PROMPT4", &prompt4),
+IPDEF8("MANPATH", &manpath, "manpath", 0),
+IPDEF9("argv", &pparams, NULL),
+IPDEF9("fignore", &fignore, "FIGNORE"),
+IPDEF9("cdpath", &cdpath, "CDPATH"),
+IPDEF9("fpath", &fpath, "FPATH"),
+IPDEF9("mailpath", &mailpath, "MAILPATH"),
+IPDEF9("manpath", &manpath, "MANPATH"),
+IPDEF9("psvar", &psvar, "PSVAR"),
+IPDEF9("watch", &watch, "WATCH"),
+
+IPDEF9F("module_path", &module_path, "MODULE_PATH", PM_RESTRICTED),
+IPDEF9F("path", &path, "PATH", PM_RESTRICTED),
+
+IPDEF10("pipestatus", pipestatus_gsu),
+
+{NULL,NULL,0,BR(NULL),NULL_GSU,0,0,NULL,NULL,NULL,0},
+};
+
+/*
+ * Special way of referring to the positional parameters.  Unlike $*
+ * and $@, this is not readonly.  This parameter is not directly
+ * visible in user space.
+ */
+initparam argvparam_pm = IPDEF9F("", &pparams, NULL, \
+				 PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT);
+
+#undef BR
+
+#define IS_UNSET_VALUE(V) \
+	((V) && (!(V)->pm || ((V)->pm->flags & PM_UNSET) || \
+		 !(V)->pm->nam || !*(V)->pm->nam))
+
+static Param argvparam;
+
+/* hash table containing the parameters */
+ 
+/**/
+mod_export HashTable paramtab, realparamtab;
+
+/**/
+mod_export HashTable
+newparamtable(int size, char const *name)
+{
+    HashTable ht;
+    if (!size)
+	size = 17;
+    ht = newhashtable(size, name, NULL);
+
+    ht->hash        = hasher;
+    ht->emptytable  = emptyhashtable;
+    ht->filltable   = NULL;
+    ht->cmpnodes    = strcmp;
+    ht->addnode     = addhashnode;
+    ht->getnode     = getparamnode;
+    ht->getnode2    = getparamnode;
+    ht->removenode  = removehashnode;
+    ht->disablenode = NULL;
+    ht->enablenode  = NULL;
+    ht->freenode    = freeparamnode;
+    ht->printnode   = printparamnode;
+
+    return ht;
+}
+
+/**/
+static HashNode
+getparamnode(HashTable ht, char *nam)
+{
+    HashNode hn = gethashnode2(ht, nam);
+    Param pm = (Param) hn;
+
+    if (pm && pm->u.str && (pm->flags & PM_AUTOLOAD)) {
+	char *mn = dupstring(pm->u.str);
+
+	if (!load_module(mn))
+	    return NULL;
+	hn = gethashnode2(ht, nam);
+	if (((Param) hn) == pm && (pm->flags & PM_AUTOLOAD)) {
+	    pm->flags &= ~PM_AUTOLOAD;
+	    zwarnnam(nam, "autoload failed", NULL, 0);
+	}
+    }
+    return hn;
+}
+
+/* Copy a parameter hash table */
+
+static HashTable outtable;
+
+/**/
+static void
+scancopyparams(HashNode hn, UNUSED(int flags))
+{
+    /* Going into a real parameter, so always use permanent storage */
+    Param pm = (Param)hn;
+    Param tpm = (Param) zshcalloc(sizeof *tpm);
+    tpm->nam = ztrdup(pm->nam);
+    copyparam(tpm, pm, 0);
+    addhashnode(outtable, tpm->nam, tpm);
+}
+
+/**/
+HashTable
+copyparamtable(HashTable ht, char *name)
+{
+    HashTable nht = newparamtable(ht->hsize, name);
+    outtable = nht;
+    scanhashtable(ht, 0, 0, 0, scancopyparams, 0);
+    outtable = NULL;
+    return nht;
+}
+
+/* Flag to freeparamnode to unset the struct */
+
+static int delunset;
+
+/* Function to delete a parameter table. */
+
+/**/
+mod_export void
+deleteparamtable(HashTable t)
+{
+    /* The parameters in the hash table need to be unset *
+     * before being deleted.                             */
+    int odelunset = delunset;
+    delunset = 1;
+    deletehashtable(t);
+    delunset = odelunset;
+}
+
+static unsigned numparamvals;
+
+/**/
+mod_export void
+scancountparams(UNUSED(HashNode hn), int flags)
+{
+    ++numparamvals;
+    if ((flags & SCANPM_WANTKEYS) && (flags & SCANPM_WANTVALS))
+	++numparamvals;
+}
+
+static Patprog scanprog;
+static char *scanstr;
+static char **paramvals;
+static Param foundparam;     
+
+/**/
+void
+scanparamvals(HashNode hn, int flags)
+{
+    struct value v;
+    Patprog prog;
+
+    if (numparamvals && !(flags & SCANPM_MATCHMANY) &&
+	(flags & (SCANPM_MATCHVAL|SCANPM_MATCHKEY|SCANPM_KEYMATCH)))
+	return;
+    v.pm = (Param)hn;
+    if ((flags & SCANPM_KEYMATCH)) {
+	char *tmp = dupstring(v.pm->nam);
+
+	tokenize(tmp);
+	remnulargs(tmp);
+
+	if (!(prog = patcompile(tmp, 0, NULL)) || !pattry(prog, scanstr))
+	    return;
+    } else if ((flags & SCANPM_MATCHKEY) && !pattry(scanprog, v.pm->nam)) {
+	return;
+    }
+    foundparam = v.pm;
+    if (flags & SCANPM_WANTKEYS) {
+	paramvals[numparamvals++] = v.pm->nam;
+	if (!(flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL)))
+	    return;
+    }
+    v.isarr = (PM_TYPE(v.pm->flags) & (PM_ARRAY|PM_HASHED));
+    v.inv = 0;
+    v.start = 0;
+    v.end = -1;
+    paramvals[numparamvals] = getstrvalue(&v);
+    if (flags & SCANPM_MATCHVAL) {
+	if (pattry(scanprog, paramvals[numparamvals])) {
+	    numparamvals += ((flags & SCANPM_WANTVALS) ? 1 :
+			     !(flags & SCANPM_WANTKEYS));
+	} else if (flags & SCANPM_WANTKEYS)
+	    --numparamvals;	/* Value didn't match, discard key */
+    } else
+	++numparamvals;
+}
+
+/**/
+char **
+paramvalarr(HashTable ht, int flags)
+{
+    numparamvals = 0;
+    if (ht)
+	scanhashtable(ht, 0, 0, PM_UNSET, scancountparams, flags);
+    paramvals = (char **) zhalloc((numparamvals + 1) * sizeof(char *));
+    if (ht) {
+	numparamvals = 0;
+	scanhashtable(ht, 0, 0, PM_UNSET, scanparamvals, flags);
+    }
+    paramvals[numparamvals] = 0;
+    return paramvals;
+}
+
+/* Return the full array (no indexing) referred to by a Value. *
+ * The array value is cached for the lifetime of the Value.    */
+
+/**/
+static char **
+getvaluearr(Value v)
+{
+    if (v->arr)
+	return v->arr;
+    else if (PM_TYPE(v->pm->flags) == PM_ARRAY)
+	return v->arr = v->pm->gsu.a->getfn(v->pm);
+    else if (PM_TYPE(v->pm->flags) == PM_HASHED) {
+	v->arr = paramvalarr(v->pm->gsu.h->getfn(v->pm), v->isarr);
+	/* Can't take numeric slices of associative arrays */
+	v->start = 0;
+	v->end = numparamvals + 1;
+	return v->arr;
+    } else
+	return NULL;
+}
+
+/*
+ * Split environment string into (name, value) pair.
+ * this is used to avoid in-place editing of environment table
+ * that results in core dump on some systems
+ */
+
+static int
+split_env_string(char *env, char **name, char **value)
+{
+    char *str, *tenv;
+
+    if (!env || !name || !value)
+	return 0;
+
+    tenv = strcpy(zhalloc(strlen(env) + 1), env);
+    for (str = tenv; *str && *str != '='; str++)
+	;
+    if (str != tenv && *str == '=') {
+	*str = '\0';
+	*name = tenv;
+	*value = str + 1;
+	return 1;
+    } else
+	return 0;
+}
+    
+/* Set up parameter hash table.  This will add predefined  *
+ * parameter entries as well as setting up parameter table *
+ * entries for environment variables we inherit.           */
+
+/**/
+void
+createparamtable(void)
+{
+    Param ip, pm;
+#ifndef HAVE_PUTENV
+    char **new_environ;
+    int  envsize;
+#endif
+    char **envp, **envp2, **sigptr, **t;
+    char buf[50], *str, *iname, *ivalue, *hostnam;
+    int  oae = opts[ALLEXPORT];
+#ifdef HAVE_UNAME
+    struct utsname unamebuf;
+    char *machinebuf;
+#endif
+
+    paramtab = realparamtab = newparamtable(151, "paramtab");
+
+    /* Add the special parameters to the hash table */
+    for (ip = special_params; ip->nam; ip++)
+	paramtab->addnode(paramtab, ztrdup(ip->nam), ip);
+    if (emulation != EMULATE_SH && emulation != EMULATE_KSH)
+	while ((++ip)->nam)
+	    paramtab->addnode(paramtab, ztrdup(ip->nam), ip);
+
+    argvparam = (Param) &argvparam_pm;
+
+    noerrs = 2;
+
+    /* Add the standard non-special parameters which have to    *
+     * be initialized before we copy the environment variables. *
+     * We don't want to override whatever values the user has   *
+     * given them in the environment.                           */
+    opts[ALLEXPORT] = 0;
+    setiparam("MAILCHECK", 60);
+    setiparam("LOGCHECK", 60);
+    setiparam("KEYTIMEOUT", 40);
+    setiparam("LISTMAX", 100);
+#ifdef HAVE_SELECT
+    setiparam("BAUD", getbaudrate(&shttyinfo));  /* get the output baudrate */
+#endif
+    setsparam("FCEDIT", ztrdup(DEFAULT_FCEDIT));
+    setsparam("TMPPREFIX", ztrdup(DEFAULT_TMPPREFIX));
+    setsparam("TIMEFMT", ztrdup(DEFAULT_TIMEFMT));
+    
+#ifndef __SYMBIAN32__    
+    setsparam("WATCHFMT", ztrdup(default_watchfmt));
+#endif
+
+    hostnam = (char *)zalloc(256);
+    gethostname(hostnam, 256);
+    setsparam("HOST", ztrdup(hostnam));
+    zfree(hostnam, 256);
+
+    setsparam("LOGNAME", ztrdup((str = (char*)getlogin()) && *str ? str : cached_username));
+
+#ifndef HAVE_PUTENV
+    /* Copy the environment variables we are inheriting to dynamic *
+     * memory, so we can do mallocs and frees on it.               */
+    envsize = sizeof(char *)*(1 + arrlen(environ));
+    new_environ = (char **) zalloc(envsize);
+    memcpy(new_environ, environ, envsize);
+    environ = new_environ;
+#endif
+
+    /* Use heap allocation to avoid many small alloc/free calls */
+    pushheap();
+
+    /* Now incorporate environment variables we are inheriting *
+     * into the parameter hash table. Copy them into dynamic   *
+     * memory so that we can free them if needed               */
+    for (envp = envp2 = environ; *envp2; envp2++) {
+	if (split_env_string(*envp2, &iname, &ivalue)) {
+	    if (!idigit(*iname) && isident(iname) && !strchr(iname, '[')) {
+		if ((!(pm = (Param) paramtab->getnode(paramtab, iname)) ||
+		     !(pm->flags & PM_DONTIMPORT || pm->flags & PM_EXPORTED)) &&
+		    (pm = setsparam(iname, metafy(ivalue, -1, META_DUP)))) {
+		    pm->flags |= PM_EXPORTED;
+		    if (pm->flags & PM_SPECIAL)
+			pm->env = mkenvstr (pm->nam,
+					    getsparam(pm->nam), pm->flags);
+		    else
+			pm->env = ztrdup(*envp2);
+		    *envp++ = pm->env;
+		}
+	    }
+	}
+    }
+    popheap();
+    *envp = '\0';
+    opts[ALLEXPORT] = oae;
+
+    pm = (Param) paramtab->getnode(paramtab, "HOME");
+    if (!(pm->flags & PM_EXPORTED))
+	addenv(pm, home);
+    pm = (Param) paramtab->getnode(paramtab, "LOGNAME");
+    if (!(pm->flags & PM_EXPORTED))
+	addenv(pm, pm->u.str);
+    pm = (Param) paramtab->getnode(paramtab, "SHLVL");
+    sprintf(buf, "%d", (int)++shlvl);
+    /* shlvl value in environment needs updating unconditionally */
+    addenv(pm, buf);
+
+    /* Add the standard non-special parameters */
+    set_pwd_env();
+#ifdef HAVE_UNAME
+    if(uname(&unamebuf)) setsparam("CPUTYPE", ztrdup("unknown"));
+    else
+    {
+       machinebuf = ztrdup(unamebuf.machine);
+       setsparam("CPUTYPE", machinebuf);
+    }
+	
+#else
+    setsparam("CPUTYPE", ztrdup("unknown"));
+#endif
+    setsparam("MACHTYPE", ztrdup(MACHTYPE));
+    setsparam("OSTYPE", ztrdup(OSTYPE));
+    setsparam("TTY", ztrdup(ttystrname));
+    setsparam("VENDOR", ztrdup(VENDOR));
+    setsparam("ZSH_NAME", ztrdup(zsh_name));
+    setsparam("ZSH_VERSION", ztrdup(ZSH_VERSION));
+    setaparam("signals", sigptr = zalloc((SIGCOUNT+4) * sizeof(char *)));
+    for (t = sigs; (*sigptr++ = ztrdup(*t++)); );
+
+    noerrs = 0;
+}
+
+/* assign various functions used for non-special parameters */
+
+/**/
+static void
+assigngetset(Param pm)
+{
+    switch (PM_TYPE(pm->flags)) {
+    case PM_SCALAR:
+	pm->gsu.s = &stdscalar_gsu;
+	break;
+    case PM_INTEGER:
+	pm->gsu.i = &stdinteger_gsu;
+	break;
+    case PM_EFLOAT:
+    case PM_FFLOAT:
+	pm->gsu.f = &stdfloat_gsu;
+	break;
+    case PM_ARRAY:
+	pm->gsu.a = &stdarray_gsu;
+	break;
+    case PM_HASHED:
+	pm->gsu.h = &stdhash_gsu;
+	break;
+    default:
+	DPUTS(1, "BUG: tried to create param node without valid flag");
+	break;
+    }
+}
+
+/* Create a parameter, so that it can be assigned to.  Returns NULL if the *
+ * parameter already exists or can't be created, otherwise returns the     *
+ * parameter node.  If a parameter of the same name exists in an outer     *
+ * scope, it is hidden by a newly created parameter.  An already existing  *
+ * parameter node at the current level may be `created' and returned       *
+ * provided it is unset and not special.  If the parameter can't be        *
+ * created because it already exists, the PM_UNSET flag is cleared.        */
+
+/**/
+mod_export Param
+createparam(char *name, int flags)
+{
+    Param pm, oldpm;
+
+    if (paramtab != realparamtab)
+	flags = (flags & ~PM_EXPORTED) | PM_HASHELEM;
+
+    if (name != nulstring) {
+	oldpm = (Param) (paramtab == realparamtab ?
+			 gethashnode2(paramtab, name) :
+			 paramtab->getnode(paramtab, name));
+
+	DPUTS(oldpm && oldpm->level > locallevel,
+	      "BUG: old local parameter not deleted");
+	if (oldpm && (oldpm->level == locallevel || !(flags & PM_LOCAL))) {
+	    if (!(oldpm->flags & PM_UNSET) || (oldpm->flags & PM_SPECIAL)) {
+		oldpm->flags &= ~PM_UNSET;
+		if ((oldpm->flags & PM_SPECIAL) && oldpm->ename) {
+		    Param altpm = 
+			(Param) paramtab->getnode(paramtab, oldpm->ename);
+		    if (altpm)
+			altpm->flags &= ~PM_UNSET;
+		}
+		return NULL;
+	    }
+	    if ((oldpm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+		zerr("%s: restricted", name, 0);
+		return NULL;
+	    }
+
+	    pm = oldpm;
+	    pm->base = pm->width = 0;
+	    oldpm = pm->old;
+	} else {
+	    pm = (Param) zshcalloc(sizeof *pm);
+	    if ((pm->old = oldpm)) {
+		/*
+		 * needed to avoid freeing oldpm, but we do take it
+		 * out of the environment when it's hidden.
+		 */
+		if (oldpm->env)
+		    delenv(oldpm);
+		paramtab->removenode(paramtab, name);
+	    }
+	    paramtab->addnode(paramtab, ztrdup(name), pm);
+	}
+
+	if (isset(ALLEXPORT) && !(flags & PM_HASHELEM))
+	    flags |= PM_EXPORTED;
+    } else {
+	pm = (Param) hcalloc(sizeof *pm);
+	pm->nam = nulstring;
+    }
+    pm->flags = flags & ~PM_LOCAL;
+
+    if(!(pm->flags & PM_SPECIAL))
+	assigngetset(pm);
+    return pm;
+}
+
+/* Copy a parameter */
+
+/**/
+void
+copyparam(Param tpm, Param pm, int toplevel)
+{
+    /*
+     * Note that tpm, into which we're copying, may not be in permanent
+     * storage.  However, the values themselves are later used directly
+     * to set the parameter, so must be permanently allocated (in accordance
+     * with sets.?fn() usage).
+     */
+    tpm->flags = pm->flags;
+    tpm->base = pm->base;
+    tpm->width = pm->width;
+    if (!toplevel)
+	tpm->flags &= ~PM_SPECIAL;
+    switch (PM_TYPE(pm->flags)) {
+    case PM_SCALAR:
+	tpm->u.str = ztrdup(pm->gsu.s->getfn(pm));
+	break;
+    case PM_INTEGER:
+	tpm->u.val = pm->gsu.i->getfn(pm);
+	break;
+    case PM_EFLOAT:
+    case PM_FFLOAT:
+	tpm->u.dval = pm->gsu.f->getfn(pm);
+	break;
+    case PM_ARRAY:
+	tpm->u.arr = zarrdup(pm->gsu.a->getfn(pm));
+	break;
+    case PM_HASHED:
+	tpm->u.hash = copyparamtable(pm->gsu.h->getfn(pm), pm->nam);
+	break;
+    }
+    /*
+     * If called from inside an associative array, that array is later going
+     * to be passed as a real parameter, so we need the gets and sets
+     * functions to be useful.  However, the saved associated array is
+     * not itself special, so we just use the standard ones.
+     * This is also why we switch off PM_SPECIAL.
+     */
+    if (!toplevel)
+	assigngetset(tpm);
+}
+
+/* Return 1 if the string s is a valid identifier, else return 0. */
+
+/**/
+mod_export int
+isident(char *s)
+{
+    char *ss;
+    int ne;
+
+    ne = noeval;		/* save the current value of noeval     */
+    if (!*s)			/* empty string is definitely not valid */
+	return 0;
+
+    if (idigit(*s)) {
+	/* If the first character is `s' is a digit, then all must be */
+	for (ss = ++s; *ss; ss++)
+	    if (!idigit(*ss))
+		break;
+    } else {
+	/* Find the first character in `s' not in the iident type table */
+	for (ss = s; *ss; ss++)
+	    if (!iident(*ss))
+		break;
+    }
+
+    /* If the next character is not [, then it is *
+     * definitely not a valid identifier.         */
+    if (!*ss)
+	return 1;
+    if (*ss != '[')
+	return 0;
+
+    /* Require balanced [ ] pairs with something between */
+    if (!(ss = parse_subscript(++ss, 1)))
+	return 0;
+    untokenize(s);
+    return !ss[1];
+}
+
+/**/
+static zlong
+getarg(char **str, int *inv, Value v, int a2, zlong *w)
+{
+    int hasbeg = 0, word = 0, rev = 0, ind = 0, down = 0, l, i, ishash;
+    int keymatch = 0, needtok = 0;
+    char *s = *str, *sep = NULL, *t, sav, *d, **ta, **p, *tt, c;
+    zlong num = 1, beg = 0, r = 0;
+    Patprog pprog = NULL;
+
+    ishash = (v->pm && PM_TYPE(v->pm->flags) == PM_HASHED);
+
+    /* first parse any subscription flags */
+    if (v->pm && (*s == '(' || *s == Inpar)) {
+	int escapes = 0;
+	int waste;
+	for (s++; *s != ')' && *s != Outpar && s != *str; s++) {
+	    switch (*s) {
+	    case 'r':
+		rev = 1;
+		keymatch = down = ind = 0;
+		break;
+	    case 'R':
+		rev = down = 1;
+		keymatch = ind = 0;
+		break;
+	    case 'k':
+		keymatch = ishash;
+		rev = 1;
+		down = ind = 0;
+		break;
+	    case 'K':
+		keymatch = ishash;
+		rev = down = 1;
+		ind = 0;
+		break;
+	    case 'i':
+		rev = ind = 1;
+		down = keymatch = 0;
+		break;
+	    case 'I':
+		rev = ind = down = 1;
+		keymatch = 0;
+		break;
+	    case 'w':
+		/* If the parameter is a scalar, then make subscription *
+		 * work on a per-word basis instead of characters.      */
+		word = 1;
+		break;
+	    case 'f':
+		word = 1;
+		sep = "\n";
+		break;
+	    case 'e':
+		/* Compatibility flag with no effect except to prevent *
+		 * special interpretation by getindex() of `*' or `@'. */
+		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 'b':
+		hasbeg = 1;
+		t = get_strarg(++s);
+		if (!*t)
+		    goto flagerr;
+		sav = *t;
+		*t = '\0';
+		if ((beg = mathevalarg(s + 1, &d)) > 0)
+		    beg--;
+		*t = sav;
+		s = t;
+		break;
+	    case 'p':
+		escapes = 1;
+		break;
+	    case 's':
+		/* This gives the string that separates words *
+		 * (for use with the `w' flag).               */
+		t = get_strarg(++s);
+		if (!*t)
+		    goto flagerr;
+		sav = *t;
+		*t = '\0';
+		sep = escapes ? getkeystring(s + 1, &waste, 1, &waste) :
+				dupstring(s + 1);
+		*t = sav;
+		s = t;
+		break;
+	    default:
+	      flagerr:
+		num = 1;
+		word = rev = ind = down = keymatch = 0;
+		sep = NULL;
+		s = *str - 1;
+	    }
+	}
+	if (s != *str)
+	    s++;
+    }
+    if (num < 0) {
+	down = !down;
+	num = -num;
+    }
+    if (v->isarr & SCANPM_WANTKEYS)
+	*inv = (ind || !(v->isarr & SCANPM_WANTVALS));
+    else if (v->isarr & SCANPM_WANTVALS)
+	*inv = 0;
+    else {
+	if (v->isarr) {
+	    if (ind) {
+		v->isarr |= SCANPM_WANTKEYS;
+		v->isarr &= ~SCANPM_WANTVALS;
+	    } else if (rev)
+		v->isarr |= SCANPM_WANTVALS;
+	    if (!down && !keymatch && ishash)
+		v->isarr &= ~SCANPM_MATCHMANY;
+	}
+	*inv = ind;
+    }
+
+    for (t = s, i = 0;
+	 (c = *t) && ((c != Outbrack &&
+		       (ishash || c != ',')) || i); t++) {
+	/* Untokenize INULL() except before brackets and double-quotes */
+	if (INULL(c)) {
+	    c = t[1];
+	    if (c == '[' || c == ']' ||
+		c == '(' || c == ')' ||
+		c == '{' || c == '}') {
+		/* This test handles nested subscripts in hash keys */
+		if (ishash && i)
+		    *t = ztokens[*t - Pound];
+		needtok = 1;
+		++t;
+	    } else if (c != '"')
+		*t = ztokens[*t - Pound];
+	    continue;
+	}
+	/* Inbrack and Outbrack are probably never found here ... */
+	if (c == '[' || c == Inbrack)
+	    i++;
+	else if (c == ']' || c == Outbrack)
+	    i--;
+	if (ispecial(c))
+	    needtok = 1;
+    }
+    if (!c)
+	return 0;
+    s = dupstrpfx(s, t - s);
+    *str = tt = t;
+    /* If we're NOT reverse subscripting, strip the INULL()s so brackets *
+     * are not backslashed after parsestr().  Otherwise leave them alone *
+     * so that the brackets will be escaped when we patcompile() or when *
+     * subscript arithmetic is performed (for nested subscripts).        */
+    if (ishash && (keymatch || !rev))
+	remnulargs(s);
+    if (needtok) {
+	if (parsestr(s))
+	    return 0;
+	singsub(&s);
+    } else if (rev)
+	remnulargs(s);	/* This is probably always a no-op, but ... */
+    if (!rev) {
+	if (ishash) {
+	    HashTable ht = v->pm->gsu.h->getfn(v->pm);
+	    if (!ht) {
+		ht = newparamtable(17, v->pm->nam);
+		v->pm->gsu.h->setfn(v->pm, ht);
+	    }
+	    untokenize(s);
+	    if (!(v->pm = (Param) ht->getnode(ht, s))) {
+		HashTable tht = paramtab;
+		paramtab = ht;
+		v->pm = createparam(s, PM_SCALAR|PM_UNSET);
+		paramtab = tht;
+	    }
+	    v->isarr = (*inv ? SCANPM_WANTINDEX : 0);
+	    v->start = 0;
+	    *inv = 0;	/* We've already obtained the "index" (key) */
+	    *w = v->end = -1;
+	    r = isset(KSHARRAYS) ? 1 : 0;
+	} else {
+	    r = mathevalarg(s, &s);
+	    if (isset(KSHARRAYS) && r >= 0)
+		r++;
+	}
+	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;
+	    while ((d = findword(&s, sep)) && --r);
+	    if (!d)
+		return 0;
+
+	    if (!a2 && *tt != ',')
+		*w = (zlong)(s - t);
+
+	    return (a2 ? s : d + 1) - t;
+	} else if (!v->isarr && !word) {
+	    s = getstrvalue(v);
+	    if (r > 0) {
+		for (t = s + r - 1; *s && s < t;)
+		    if (*s++ == Meta)
+			s++, t++, r++;
+	    } else {
+		r += ztrlen(s);
+		for (t = s + r; *s && s < t; r--)
+		    if (*s++ == Meta)
+			t++, r++;
+		r -= strlen(s);
+	    }
+	}
+    } else {
+	if (!v->isarr && !word) {
+	    l = strlen(s);
+	    if (a2) {
+		if (!l || *s != '*') {
+		    d = (char *) hcalloc(l + 2);
+		    *d = '*';
+		    strcpy(d + 1, s);
+		    s = d;
+		}
+	    } else {
+		if (!l || s[l - 1] != '*' || (l > 1 && s[l - 2] == '\\')) {
+		    d = (char *) hcalloc(l + 2);
+		    strcpy(d, s);
+		    strcat(d, "*");
+		    s = d;
+		}
+	    }
+	}
+	if (!keymatch) {
+	    tokenize(s);
+	    remnulargs(s);
+	}
+
+	if (keymatch || (pprog = patcompile(s, 0, NULL))) {
+	    int len;
+
+	    if (v->isarr) {
+		if (ishash) {
+		    scanprog = pprog;
+		    scanstr = s;
+		    if (keymatch)
+			v->isarr |= SCANPM_KEYMATCH;
+		    else if (ind)
+			v->isarr |= SCANPM_MATCHKEY;
+		    else
+			v->isarr |= SCANPM_MATCHVAL;
+		    if (down)
+			v->isarr |= SCANPM_MATCHMANY;
+		    if ((ta = getvaluearr(v)) &&
+			(*ta || ((v->isarr & SCANPM_MATCHMANY) &&
+				 (v->isarr & (SCANPM_MATCHKEY | SCANPM_MATCHVAL |
+					      SCANPM_KEYMATCH))))) {
+			*inv = v->inv;
+			*w = v->end;
+			return 1;
+		    }
+		} else
+		    ta = getarrvalue(v);
+		if (!ta || !*ta)
+		    return 0;
+		len = arrlen(ta);
+		if (beg < 0)
+		    beg += len;
+		if (beg >= 0 && beg < len) {
+		    if (down) {
+			if (!hasbeg)
+			    beg = len - 1;
+			for (r = 1 + beg, p = ta + beg; p >= ta; r--, p--) {
+			    if (pattry(pprog, *p) && !--num)
+				return r;
+			}
+		    } else
+			for (r = 1 + beg, p = ta + beg; *p; r++, p++)
+			    if (pattry(pprog, *p) && !--num)
+				return r;
+		}
+	    } else if (word) {
+		ta = sepsplit(d = s = getstrvalue(v), sep, 1, 1);
+		len = arrlen(ta);
+		if (beg < 0)
+		    beg += len;
+		if (beg >= 0 && beg < len) {
+		    if (down) {
+			if (!hasbeg)
+			    beg = len - 1;
+			for (r = 1 + beg, p = ta + beg; p >= ta; p--, r--)
+			    if (pattry(pprog, *p) && !--num)
+				break;
+			if (p < ta)
+			    return 0;
+		    } else {
+			for (r = 1 + beg, p = ta + beg; *p; r++, p++)
+			    if (pattry(pprog, *p) && !--num)
+				break;
+			if (!*p)
+			    return 0;
+		    }
+		}
+		if (a2)
+		    r++;
+		for (i = 0; (t = findword(&d, sep)) && *t; i++)
+		    if (!--r) {
+			r = (zlong)(t - s + (a2 ? -1 : 1));
+			if (!a2 && *tt != ',')
+			    *w = r + strlen(ta[i]) - 1;
+			return r;
+		    }
+		return a2 ? -1 : 0;
+	    } else {
+		d = getstrvalue(v);
+		if (!d || !*d)
+		    return 0;
+		len = strlen(d);
+		if (beg < 0)
+		    beg += len;
+		if (beg >= 0 && beg < len) {
+                    char *de = d + len;
+
+		    if (a2) {
+			if (down) {
+			    if (!hasbeg)
+				beg = len;
+			    for (r = beg, t = d + beg; t >= d; r--, t--) {
+				sav = *t;
+				*t = '\0';
+				if (pattry(pprog, d)
+				    && !--num) {
+				    *t = sav;
+				    return r;
+				}
+				*t = sav;
+			    }
+			} else
+			    for (r = beg, t = d + beg; t <= de; r++, t++) {
+				sav = *t;
+				*t = '\0';
+				if (pattry(pprog, d) &&
+				    !--num) {
+				    *t = sav;
+				    return r;
+				}
+				*t = sav;
+			    }
+		    } else {
+			if (down) {
+			    if (!hasbeg)
+				beg = len;
+			    for (r = beg + 1, t = d + beg; t >= d; r--, t--) {
+				if (pattry(pprog, t) &&
+				    !--num)
+				    return r;
+			    }
+			} else
+			    for (r = beg + 1, t = d + beg; t <= de; r++, t++)
+				if (pattry(pprog, t) &&
+				    !--num)
+				    return r;
+		    }
+		}
+		return down ? 0 : len + 1;
+	    }
+	}
+    }
+    return r;
+}
+
+/**/
+int
+getindex(char **pptr, Value v, int dq)
+{
+    int start, end, inv = 0;
+    char *s = *pptr, *tbrack;
+
+    *s++ = '[';
+    s = parse_subscript(s, dq);	/* Error handled after untokenizing */
+    /* Now we untokenize everything except INULL() markers so we can check *
+     * for the '*' and '@' special subscripts.  The INULL()s are removed  *
+     * in getarg() after we know whether we're doing reverse indexing.    */
+    for (tbrack = *pptr + 1; *tbrack && tbrack != s; tbrack++) {
+	if (INULL(*tbrack) && !*++tbrack)
+	    break;
+	if (itok(*tbrack))	/* Need to check for Nularg here? */
+	    *tbrack = ztokens[*tbrack - Pound];
+    }
+    /* If we reached the end of the string (s == NULL) we have an error */
+    if (*tbrack)
+	*tbrack = Outbrack;
+    else {
+	zerr("invalid subscript", NULL, 0);
+	*pptr = tbrack;
+	return 1;
+    }
+    s = *pptr + 1;
+    if ((s[0] == '*' || s[0] == '@') && s + 1 == tbrack) {
+	if ((v->isarr || IS_UNSET_VALUE(v)) && s[0] == '@')
+	    v->isarr |= SCANPM_ISVAR_AT;
+	v->start = 0;
+	v->end = -1;
+	s += 2;
+    } else {
+	zlong we = 0, dummy;
+
+	start = getarg(&s, &inv, v, 0, &we);
+
+	if (inv) {
+	    if (!v->isarr && start != 0) {
+		char *t, *p;
+		t = getstrvalue(v);
+		if (start > 0) {
+		    for (p = t + start - 1; p-- > t; )
+			if (*p == Meta)
+			    start--;
+		} else
+		    start = -ztrlen(t + start + strlen(t));
+	    }
+	    if (start > 0 && (isset(KSHARRAYS) || (v->pm->flags & PM_HASHED)))
+		start--;
+	    if (v->isarr != SCANPM_WANTINDEX) {
+		v->inv = 1;
+		v->isarr = 0;
+		v->start = start;
+		v->end = start + 1;
+	    }
+	    if (*s == ',') {
+		zerr("invalid subscript", NULL, 0);
+		*tbrack = ']';
+		*pptr = tbrack+1;
+		return 1;
+	    }
+	    if (s == tbrack)
+		s++;
+	} else {
+	    int com;
+
+	    if ((com = (*s == ','))) {
+		s++;
+		end = getarg(&s, &inv, v, 1, &dummy);
+	    } else {
+		end = we ? we : start;
+	    }
+	    if (start != end) com = 1;
+	    if (start > 0)
+		start--;
+	    else if (start == 0 && end == 0)
+		end++;
+	    if (s == tbrack) {
+		s++;
+		if (v->isarr && !com &&
+		    (!(v->isarr & SCANPM_MATCHMANY) ||
+		     !(v->isarr & (SCANPM_MATCHKEY | SCANPM_MATCHVAL |
+				   SCANPM_KEYMATCH))))
+		    v->isarr = 0;
+		v->start = start;
+		v->end = end;
+	    } else
+		s = *pptr;
+	}
+    }
+    *tbrack = ']';
+    *pptr = s;
+    return 0;
+}
+
+
+/**/
+mod_export Value
+getvalue(Value v, char **pptr, int bracks)
+{
+  return fetchvalue(v, pptr, bracks, 0);
+}
+
+/**/
+mod_export Value
+fetchvalue(Value v, char **pptr, int bracks, int flags)
+{
+    char *s, *t;
+    char sav, c;
+    int ppar = 0;
+
+    s = t = *pptr;
+
+    if (idigit(c = *s)) {
+	if (bracks >= 0)
+	    ppar = zstrtol(s, &s, 10);
+	else
+	    ppar = *s++ - '0';
+    }
+    else if (iident(c))
+	while (iident(*s))
+	    s++;
+    else if (c == Quest)
+	*s++ = '?';
+    else if (c == Pound)
+	*s++ = '#';
+    else if (c == String)
+	*s++ = '$';
+    else if (c == Qstring)
+	*s++ = '$';
+    else if (c == Star)
+	*s++ = '*';
+    else if (c == '#' || c == '-' || c == '?' || c == '$' ||
+	     c == '!' || c == '@' || c == '*')
+	s++;
+    else
+	return NULL;
+
+    if ((sav = *s))
+	*s = '\0';
+    if (ppar) {
+	if (v)
+	    memset(v, 0, sizeof(*v));
+	else
+	    v = (Value) hcalloc(sizeof *v);
+	v->pm = argvparam;
+	v->inv = 0;
+	v->start = ppar - 1;
+	v->end = ppar;
+	if (sav)
+	    *s = sav;
+    } else {
+	Param pm;
+	int isvarat;
+
+        isvarat = (t[0] == '@' && !t[1]);
+	pm = (Param) paramtab->getnode(paramtab, *t == '0' ? "0" : t);
+	if (sav)
+	    *s = sav;
+	*pptr = s;
+	if (!pm || (pm->flags & PM_UNSET))
+	    return NULL;
+	if (v)
+	    memset(v, 0, sizeof(*v));
+	else
+	    v = (Value) hcalloc(sizeof *v);
+	if (PM_TYPE(pm->flags) & (PM_ARRAY|PM_HASHED)) {
+	    /* Overload v->isarr as the flag bits for hashed arrays. */
+	    v->isarr = flags | (isvarat ? SCANPM_ISVAR_AT : 0);
+	    /* If no flags were passed, we need something to represent *
+	     * `true' yet differ from an explicit WANTVALS.  This is a *
+	     * bit of a hack, but makes some sense:  When no subscript *
+	     * is provided, all values are substituted.                */
+	    if (!v->isarr)
+		v->isarr = SCANPM_MATCHMANY;
+	}
+	v->pm = pm;
+	v->inv = 0;
+	v->start = 0;
+	v->end = -1;
+	if (bracks > 0 && (*s == '[' || *s == Inbrack)) {
+	    if (getindex(&s, v, (flags & SCANPM_DQUOTED))) {
+		*pptr = s;
+		return v;
+	    }
+	} else if (!(flags & SCANPM_ASSIGNING) && v->isarr &&
+		   iident(*t) && isset(KSHARRAYS))
+	    v->end = 1, v->isarr = 0;
+    }
+    if (!bracks && *s)
+	return NULL;
+    *pptr = s;
+    if (v->start > MAX_ARRLEN) {
+	zerr("subscript too %s: %d", "big", v->start + !isset(KSHARRAYS));
+	return NULL;
+    }
+    if (v->start < -MAX_ARRLEN) {
+	zerr("subscript too %s: %d", "small", v->start);
+	return NULL;
+    }
+    if (v->end > MAX_ARRLEN+1) {
+	zerr("subscript too %s: %d", "big", v->end - !!isset(KSHARRAYS));
+	return NULL;
+    }
+    if (v->end < -MAX_ARRLEN) {
+	zerr("subscript too %s: %d", "small", v->end);
+	return NULL;
+    }
+    return v;
+}
+
+/**/
+mod_export char *
+getstrvalue(Value v)
+{
+    char *s, **ss;
+    char buf[BDIGBUFSIZE];
+
+    if (!v)
+	return hcalloc(1);
+
+    if (v->inv && !(v->pm->flags & PM_HASHED)) {
+	sprintf(buf, "%d", v->start);
+	s = dupstring(buf);
+	return s;
+    }
+
+    switch(PM_TYPE(v->pm->flags)) {
+    case PM_HASHED:
+	/* (!v->isarr) should be impossible unless emulating ksh */
+	if (!v->isarr && emulation == EMULATE_KSH) {
+	    s = dupstring("[0]");
+	    if (getindex(&s, v, 0) == 0)
+		s = getstrvalue(v);
+	    return s;
+	} /* else fall through */
+    case PM_ARRAY:
+	ss = getvaluearr(v);
+	if (v->isarr)
+	    s = sepjoin(ss, NULL, 1);
+	else {
+	    if (v->start < 0)
+		v->start += arrlen(ss);
+	    s = (v->start >= arrlen(ss) || v->start < 0) ?
+		(char *) hcalloc(1) : ss[v->start];
+	}
+	return s;
+    case PM_INTEGER:
+	convbase(buf, v->pm->gsu.i->getfn(v->pm), v->pm->base);
+	s = dupstring(buf);
+	break;
+    case PM_EFLOAT:
+    case PM_FFLOAT:
+	s = convfloat(v->pm->gsu.f->getfn(v->pm),
+		      v->pm->base, v->pm->flags, NULL);
+	break;
+    case PM_SCALAR:
+	s = v->pm->gsu.s->getfn(v->pm);
+	break;
+    default:
+	s = NULL;
+	DPUTS(1, "BUG: param node without valid type");
+	break;
+    }
+
+    if (v->start == 0 && v->end == -1)
+	return s;
+
+    if (v->start < 0) {
+	v->start += strlen(s);
+	if (v->start < 0)
+	    v->start = 0;
+    }
+    if (v->end < 0)
+	v->end += strlen(s) + 1;
+    s = (v->start > (int)strlen(s)) ? dupstring("") : dupstring(s + v->start);
+    if (v->end <= v->start)
+	s[0] = '\0';
+    else if (v->end - v->start <= (int)strlen(s))
+	s[v->end - v->start + (s[v->end - v->start - 1] == Meta)] = '\0';
+
+    return s;
+}
+
+static char *nular[] = {"", NULL};
+
+/**/
+mod_export char **
+getarrvalue(Value v)
+{
+    char **s;
+
+    if (!v)
+	return arrdup(nular);
+    else if (IS_UNSET_VALUE(v))
+	return arrdup(&nular[1]);
+    if (v->inv) {
+	char buf[DIGBUFSIZE];
+
+	s = arrdup(nular);
+	sprintf(buf, "%d", v->start);
+	s[0] = dupstring(buf);
+	return s;
+    }
+    s = getvaluearr(v);
+    if (v->start == 0 && v->end == -1)
+	return s;
+    if (v->start < 0)
+	v->start += arrlen(s);
+    if (v->end < 0)
+	v->end += arrlen(s) + 1;
+    if (v->start > arrlen(s) || v->start < 0)
+	s = arrdup(nular);
+    else
+	s = arrdup(s + v->start);
+    if (v->end <= v->start)
+	s[0] = NULL;
+    else if (v->end - v->start <= arrlen(s))
+	s[v->end - v->start] = NULL;
+    return s;
+}
+
+/**/
+mod_export zlong
+getintvalue(Value v)
+{
+    if (!v || v->isarr)
+	return 0;
+    if (v->inv)
+	return v->start;
+    if (PM_TYPE(v->pm->flags) == PM_INTEGER)
+	return v->pm->gsu.i->getfn(v->pm);
+    if (v->pm->flags & (PM_EFLOAT|PM_FFLOAT))
+	return (zlong)v->pm->gsu.f->getfn(v->pm);
+    return mathevali(getstrvalue(v));
+}
+
+/**/
+mnumber
+getnumvalue(Value v)
+{
+    mnumber mn;
+    mn.type = MN_INTEGER;
+
+    if (!v || v->isarr) {
+	mn.u.l = 0;
+    } else if (v->inv) {
+	mn.u.l = v->start;
+    } else if (PM_TYPE(v->pm->flags) == PM_INTEGER) {
+	mn.u.l = v->pm->gsu.i->getfn(v->pm);
+    } else if (v->pm->flags & (PM_EFLOAT|PM_FFLOAT)) {
+	mn.type = MN_FLOAT;
+	mn.u.d = v->pm->gsu.f->getfn(v->pm);
+    } else
+	return matheval(getstrvalue(v));
+    return mn;
+}
+
+/**/
+void
+export_param(Param pm)
+{
+    char buf[BDIGBUFSIZE], *val;
+
+    if (PM_TYPE(pm->flags) & (PM_ARRAY|PM_HASHED)) {
+#if 0	/* Requires changes elsewhere in params.c and builtin.c */
+	if (emulation == EMULATE_KSH /* isset(KSHARRAYS) */) {
+	    struct value v;
+	    v.isarr = 1;
+	    v.inv = 0;
+	    v.start = 0;
+	    v.end = -1;
+	    val = getstrvalue(&v);
+	} else
+#endif
+	    return;
+    } else if (PM_TYPE(pm->flags) == PM_INTEGER)
+	convbase(val = buf, pm->gsu.i->getfn(pm), pm->base);
+    else if (pm->flags & (PM_EFLOAT|PM_FFLOAT))
+	val = convfloat(pm->gsu.f->getfn(pm), pm->base,
+			pm->flags, NULL);
+    else
+	val = pm->gsu.s->getfn(pm);
+
+    addenv(pm, val);
+}
+
+/**/
+mod_export void
+setstrvalue(Value v, char *val)
+{
+    if (v->pm->flags & PM_READONLY) {
+	zerr("read-only variable: %s", v->pm->nam, 0);
+	zsfree(val);
+	return;
+    }
+    if ((v->pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+	zerr("%s: restricted", v->pm->nam, 0);
+	zsfree(val);
+	return;
+    }
+    if ((v->pm->flags & PM_HASHED) && (v->isarr & SCANPM_MATCHMANY)) {
+	zerr("%s: attempt to set slice of associative array", v->pm->nam, 0);
+	zsfree(val);
+	return;
+    }
+    v->pm->flags &= ~PM_UNSET;
+    switch (PM_TYPE(v->pm->flags)) {
+    case PM_SCALAR:
+	if (v->start == 0 && v->end == -1) {
+	    v->pm->gsu.s->setfn(v->pm, val);
+	    if ((v->pm->flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) &&
+		!v->pm->width)
+		v->pm->width = strlen(val);
+	} else {
+	    char *z, *x;
+	    int zlen;
+
+	    z = dupstring(v->pm->gsu.s->getfn(v->pm));
+	    zlen = strlen(z);
+	    if (v->inv && unset(KSHARRAYS))
+		v->start--, v->end--;
+	    if (v->start < 0) {
+		v->start += zlen;
+		if (v->start < 0)
+		    v->start = 0;
+	    }
+	    if (v->start > zlen)
+		v->start = zlen;
+	    if (v->end < 0)
+		v->end += zlen + 1;
+	    else if (v->end > zlen)
+		v->end = zlen;
+	    x = (char *) zalloc(v->start + strlen(val) + zlen - v->end + 1);
+	    strncpy(x, z, v->start);
+	    strcpy(x + v->start, val);
+	    strcat(x + v->start, z + v->end);
+	    v->pm->gsu.s->setfn(v->pm, x);
+	    zsfree(val);
+	}
+	break;
+    case PM_INTEGER:
+	if (val) {
+	    v->pm->gsu.i->setfn(v->pm, mathevali(val));
+	    zsfree(val);
+	    if ((v->pm->flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) &&
+		!v->pm->width)
+		v->pm->width = strlen(val);
+	}
+	if (!v->pm->base && lastbase != -1)
+	    v->pm->base = lastbase;
+	break;
+    case PM_EFLOAT:
+    case PM_FFLOAT:
+	if (val) {
+	    mnumber mn = matheval(val);
+	    v->pm->gsu.f->setfn(v->pm, (mn.type & MN_FLOAT) ? mn.u.d :
+			       (double)mn.u.l);
+	    zsfree(val);
+	    if ((v->pm->flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) &&
+		!v->pm->width)
+		v->pm->width = strlen(val);
+	}
+	break;
+    case PM_ARRAY:
+	{
+	    char **ss = (char **) zalloc(2 * sizeof(char *));
+
+	    ss[0] = val;
+	    ss[1] = NULL;
+	    setarrvalue(v, ss);
+	}
+	break;
+    case PM_HASHED:
+        {
+	    foundparam->gsu.s->setfn(foundparam, val);
+        }
+	break;
+    }
+    if ((!v->pm->env && !(v->pm->flags & PM_EXPORTED) &&
+	 !(isset(ALLEXPORT) && !(v->pm->flags & PM_HASHELEM))) ||
+	(v->pm->flags & PM_ARRAY) || v->pm->ename)
+	return;
+    export_param(v->pm);
+}
+
+/**/
+void
+setnumvalue(Value v, mnumber val)
+{
+    char buf[BDIGBUFSIZE], *p;
+
+    if (v->pm->flags & PM_READONLY) {
+	zerr("read-only variable: %s", v->pm->nam, 0);
+	return;
+    }
+    if ((v->pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+	zerr("%s: restricted", v->pm->nam, 0);
+	return;
+    }
+    switch (PM_TYPE(v->pm->flags)) {
+    case PM_SCALAR:
+    case PM_ARRAY:
+	if ((val.type & MN_INTEGER) || outputradix) {
+	    if (!(val.type & MN_INTEGER))
+		val.u.l = (zlong) val.u.d;
+	    convbase(p = buf, val.u.l, outputradix);
+	} else
+	    p = convfloat(val.u.d, 0, 0, NULL);
+	setstrvalue(v, ztrdup(p));
+	break;
+    case PM_INTEGER:
+	v->pm->gsu.i->setfn(v->pm, (val.type & MN_INTEGER) ? val.u.l :
+			    (zlong) val.u.d);
+	setstrvalue(v, NULL);
+	break;
+    case PM_EFLOAT:
+    case PM_FFLOAT:
+	v->pm->gsu.f->setfn(v->pm, (val.type & MN_INTEGER) ?
+			    (double)val.u.l : val.u.d);
+	setstrvalue(v, NULL);
+	break;
+    }
+}
+
+/**/
+mod_export void
+setarrvalue(Value v, char **val)
+{
+    if (v->pm->flags & PM_READONLY) {
+	zerr("read-only variable: %s", v->pm->nam, 0);
+	freearray(val);
+	return;
+    }
+    if ((v->pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+	zerr("%s: restricted", v->pm->nam, 0);
+	freearray(val);
+	return;
+    }
+    if (!(PM_TYPE(v->pm->flags) & (PM_ARRAY|PM_HASHED))) {
+	freearray(val);
+	zerr("%s: attempt to assign array value to non-array",
+	     v->pm->nam, 0);
+	return;
+    }
+    if (v->start == 0 && v->end == -1) {
+	if (PM_TYPE(v->pm->flags) == PM_HASHED)
+	    arrhashsetfn(v->pm, val, 0);
+	else
+	    v->pm->gsu.a->setfn(v->pm, val);
+    } else if (v->start == -1 && v->end == 0 &&
+    	    PM_TYPE(v->pm->flags) == PM_HASHED) {
+    	arrhashsetfn(v->pm, val, 1);
+    } else {
+	char **old, **new, **p, **q, **r;
+	int n, ll, i;
+
+	if ((PM_TYPE(v->pm->flags) == PM_HASHED)) {
+	    freearray(val);
+	    zerr("%s: attempt to set slice of associative array",
+		 v->pm->nam, 0);
+	    return;
+	}
+	if (v->inv && unset(KSHARRAYS)) {
+	    if (v->start > 0)
+		v->start--;
+	    v->end--;
+	}
+	if (v->end < v->start)
+	    v->end = v->start;
+	q = old = v->pm->gsu.a->getfn(v->pm);
+	n = arrlen(old);
+	if (v->start < 0) {
+	    v->start += n;
+	    if (v->start < 0)
+		v->start = 0;
+	}
+	if (v->end < 0) {
+	    v->end += n + 1;
+	    if (v->end < 0)
+		v->end = 0;
+	}
+
+	ll = v->start + arrlen(val);
+	if (v->end <= n)
+	    ll += n - v->end + 1;
+
+	p = new = (char **) zshcalloc(sizeof(char *) * (ll + 1));
+
+	for (i = 0; i < v->start; i++)
+	    *p++ = i < n ? ztrdup(*q++) : ztrdup("");
+	for (r = val; *r;)
+	    *p++ = ztrdup(*r++);
+	if (v->end < n)
+	    for (q = old + v->end; *q;)
+		*p++ = ztrdup(*q++);
+	*p = NULL;
+
+	v->pm->gsu.a->setfn(v->pm, new);
+	freearray(val);
+    }
+}
+
+/* Retrieve an integer parameter */
+
+/**/
+mod_export zlong
+getiparam(char *s)
+{
+    struct value vbuf;
+    Value v;
+
+    if (!(v = getvalue(&vbuf, &s, 1)))
+	return 0;
+    return getintvalue(v);
+}
+
+/* Retrieve a numerical parameter, either integer or floating */
+
+/**/
+mnumber
+getnparam(char *s)
+{
+    struct value vbuf;
+    Value v;
+
+    if (!(v = getvalue(&vbuf, &s, 1))) {
+	mnumber mn;
+	mn.type = MN_INTEGER;
+	mn.u.l = 0;
+	return mn;
+    }
+    return getnumvalue(v);
+}
+
+/* Retrieve a scalar (string) parameter */
+
+/**/
+mod_export char *
+getsparam(char *s)
+{
+    struct value vbuf;
+    Value v;
+
+    if (!(v = getvalue(&vbuf, &s, 0)))
+	return NULL;
+    return getstrvalue(v);
+}
+
+/* Retrieve an array parameter */
+
+/**/
+mod_export char **
+getaparam(char *s)
+{
+    struct value vbuf;
+    Value v;
+
+    if (!idigit(*s) && (v = getvalue(&vbuf, &s, 0)) &&
+	PM_TYPE(v->pm->flags) == PM_ARRAY)
+	return v->pm->gsu.a->getfn(v->pm);
+    return NULL;
+}
+
+/* Retrieve an assoc array parameter as an array */
+
+/**/
+mod_export char **
+gethparam(char *s)
+{
+    struct value vbuf;
+    Value v;
+
+    if (!idigit(*s) && (v = getvalue(&vbuf, &s, 0)) &&
+	PM_TYPE(v->pm->flags) == PM_HASHED)
+	return paramvalarr(v->pm->gsu.h->getfn(v->pm), SCANPM_WANTVALS);
+    return NULL;
+}
+
+/* Retrieve the keys of an assoc array parameter as an array */
+
+/**/
+mod_export char **
+gethkparam(char *s)
+{
+    struct value vbuf;
+    Value v;
+
+    if (!idigit(*s) && (v = getvalue(&vbuf, &s, 0)) &&
+	PM_TYPE(v->pm->flags) == PM_HASHED)
+	return paramvalarr(v->pm->gsu.h->getfn(v->pm), SCANPM_WANTKEYS);
+    return NULL;
+}
+
+/**/
+mod_export Param
+assignsparam(char *s, char *val, int augment)
+{
+    struct value vbuf;
+    Value v;
+    char *t = s;
+    char *ss, *copy, *var;
+    size_t lvar;
+    mnumber lhs, rhs;
+    int sstart;
+
+    if (!isident(s)) {
+	zerr("not an identifier: %s", s, 0);
+	zsfree(val);
+	errflag = 1;
+	return NULL;
+    }
+    queue_signals();
+    if ((ss = strchr(s, '['))) {
+	*ss = '\0';
+	if (!(v = getvalue(&vbuf, &s, 1)))
+	    createparam(t, PM_ARRAY);
+	*ss = '[';
+	v = NULL;
+    } else {
+	if (!(v = getvalue(&vbuf, &s, 1)))
+	    createparam(t, PM_SCALAR);
+	else if ((((v->pm->flags & PM_ARRAY) && !augment) ||
+	    	 (v->pm->flags & PM_HASHED)) &&
+		 !(v->pm->flags & (PM_SPECIAL|PM_TIED)) && 
+		 unset(KSHARRAYS)) {
+	    unsetparam(t);
+	    createparam(t, PM_SCALAR);
+	    v = NULL;
+	}
+    }
+    if (!v && !(v = getvalue(&vbuf, &t, 1))) {
+	unqueue_signals();
+	zsfree(val);
+	return NULL;
+    }
+    if (augment) {
+	if (v->start == 0 && v->end == -1) {
+	    switch (PM_TYPE(v->pm->flags)) {
+	    case PM_SCALAR:
+		v->start = INT_MAX;  /* just append to scalar value */
+		break;
+	    case PM_INTEGER:
+	    case PM_EFLOAT:
+	    case PM_FFLOAT:
+		rhs = matheval(val);
+		lhs = getnumvalue(v);
+		if (lhs.type == MN_FLOAT) {
+		    if ((rhs.type) == MN_FLOAT)
+        		lhs.u.d = lhs.u.d + rhs.u.d;
+		    else
+			lhs.u.d = lhs.u.d + (double)rhs.u.l;
+		} else {
+        	    if ((rhs.type) == MN_INTEGER)
+			lhs.u.l = lhs.u.l + rhs.u.l;
+		    else
+			lhs.u.l = lhs.u.l + (zlong)rhs.u.d;
+		}
+		setnumvalue(v, lhs);
+    	    	unqueue_signals();
+		zsfree(val);
+		return v->pm; /* avoid later setstrvalue() call */
+	    case PM_ARRAY:
+	    	if (unset(KSHARRAYS)) {
+		    v->start = arrlen(v->pm->gsu.a->getfn(v->pm));
+		    v->end = v->start + 1;
+		} else {
+		    /* ksh appends scalar to first element */
+		    v->end = 1;
+		    goto kshappend;
+		}
+		break;
+	    }
+	} else {
+	    switch (PM_TYPE(v->pm->flags)) {
+	    case PM_SCALAR:
+    		if (v->end > 0)
+		    v->start = v->end;
+		else
+		    v->start = v->end = strlen(v->pm->gsu.s->getfn(v->pm)) +
+			v->end + 1;
+	    	break;
+	    case PM_INTEGER:
+	    case PM_EFLOAT:
+	    case PM_FFLOAT:
+		unqueue_signals();
+		zerr("attempt to add to slice of a numeric variable",
+		    NULL, 0);
+		zsfree(val);
+		return NULL;
+	    case PM_ARRAY:
+	      kshappend:
+		/* treat slice as the end element */
+		v->start = sstart = v->end > 0 ? v->end - 1 : v->end;
+		v->isarr = 0;
+		var = getstrvalue(v);
+		v->start = sstart;
+		copy = val;
+		lvar = strlen(var);
+		val = (char *)zalloc(lvar + strlen(val) + 1);
+		strcpy(val, var);
+		strcpy(val + lvar, copy);
+		zsfree(copy);
+		break;
+	    }
+	}
+    }
+    
+    setstrvalue(v, val);
+    unqueue_signals();
+    return v->pm;
+}
+
+/**/
+mod_export Param
+assignaparam(char *s, char **val, int augment)
+{
+    struct value vbuf;
+    Value v;
+    char *t = s;
+    char *ss;
+
+    if (!isident(s)) {
+	zerr("not an identifier: %s", s, 0);
+	freearray(val);
+	errflag = 1;
+	return NULL;
+    }
+    queue_signals();
+    if ((ss = strchr(s, '['))) {
+	*ss = '\0';
+	if (!(v = getvalue(&vbuf, &s, 1)))
+	    createparam(t, PM_ARRAY);
+	*ss = '[';
+	if (v && PM_TYPE(v->pm->flags) == PM_HASHED) {
+	    unqueue_signals();
+	    zerr("%s: attempt to set slice of associative array",
+		 v->pm->nam, 0);
+	    freearray(val);
+	    errflag = 1;
+	    return NULL;
+	}
+	v = NULL;
+    } else {
+	if (!(v = fetchvalue(&vbuf, &s, 1, SCANPM_ASSIGNING)))
+	    createparam(t, PM_ARRAY);
+	else if (!(PM_TYPE(v->pm->flags) & (PM_ARRAY|PM_HASHED)) &&
+		 !(v->pm->flags & (PM_SPECIAL|PM_TIED))) {
+	    int uniq = v->pm->flags & PM_UNIQUE;
+	    if (augment) {
+	    	/* insert old value at the beginning of the val array */
+		char **new;
+		int lv = arrlen(val);
+
+		new = (char **) zalloc(sizeof(char *) * (lv + 2));
+		*new = ztrdup(getstrvalue(v));
+		memcpy(new+1, val, sizeof(char *) * (lv + 1));
+		free(val);
+		val = new;
+		
+	    }
+	    unsetparam(t);
+	    createparam(t, PM_ARRAY | uniq);
+	    v = NULL;
+	}
+    }
+    if (!v)
+	if (!(v = fetchvalue(&vbuf, &t, 1, SCANPM_ASSIGNING))) {
+	    unqueue_signals();
+	    freearray(val);
+	    return NULL;
+	}
+
+    if (augment) {
+    	if (v->start == 0 && v->end == -1) {
+	    if (PM_TYPE(v->pm->flags) & PM_ARRAY) {
+	    	v->start = arrlen(v->pm->gsu.a->getfn(v->pm));
+	    	v->end = v->start + 1;
+	    } else if (PM_TYPE(v->pm->flags) & PM_HASHED)
+	    	v->start = -1, v->end = 0;
+	} else {
+	    if (v->end > 0)
+		v->start = v->end--;
+	    else if (PM_TYPE(v->pm->flags) & PM_ARRAY) {
+		v->end = arrlen(v->pm->gsu.a->getfn(v->pm)) + v->end;
+		v->start = v->end + 1;
+	    }
+	}
+    }
+
+    setarrvalue(v, val);
+    unqueue_signals();
+    return v->pm;
+}
+
+/**/
+mod_export Param
+sethparam(char *s, char **val)
+{
+    struct value vbuf;
+    Value v;
+    char *t = s;
+
+    if (!isident(s)) {
+	zerr("not an identifier: %s", s, 0);
+	freearray(val);
+	errflag = 1;
+	return NULL;
+    }
+    if (strchr(s, '[')) {
+	freearray(val);
+	zerr("nested associative arrays not yet supported", NULL, 0);
+	errflag = 1;
+	return NULL;
+    }
+    queue_signals();
+    if (!(v = fetchvalue(&vbuf, &s, 1, SCANPM_ASSIGNING)))
+	createparam(t, PM_HASHED);
+    else if (!(PM_TYPE(v->pm->flags) & PM_HASHED) &&
+	     !(v->pm->flags & PM_SPECIAL)) {
+	unsetparam(t);
+	createparam(t, PM_HASHED);
+	v = NULL;
+    }
+    if (!v)
+	if (!(v = fetchvalue(&vbuf, &t, 1, SCANPM_ASSIGNING))) {
+	    unqueue_signals();
+	    return NULL;
+	}
+    setarrvalue(v, val);
+    unqueue_signals();
+    return v->pm;
+}
+
+/**/
+mod_export Param
+setiparam(char *s, zlong val)
+{
+    struct value vbuf;
+    Value v;
+    char *t = s, *ss;
+    Param pm;
+    mnumber mnval;
+
+    if (!isident(s)) {
+	zerr("not an identifier: %s", s, 0);
+	errflag = 1;
+	return NULL;
+    }
+    queue_signals();
+    if (!(v = getvalue(&vbuf, &s, 1))) {
+	if ((ss = strchr(s, '[')))
+	    *ss = '\0';
+	if (!(pm = createparam(t, ss ? PM_ARRAY : PM_INTEGER)))
+	    pm = (Param) paramtab->getnode(paramtab, t);
+	DPUTS(!pm, "BUG: parameter not created");
+	if (ss) {
+	    *ss = '[';
+	} else {
+	    pm->base = outputradix;
+	}
+	v = getvalue(&vbuf, &t, 1);
+	DPUTS(!v, "BUG: value not found for new parameter");
+    }
+    mnval.type = MN_INTEGER;
+    mnval.u.l = val;
+    setnumvalue(v, mnval);
+    unqueue_signals();
+    return v->pm;
+}
+
+/*
+ * Like setiparam(), but can take an mnumber which can be integer or
+ * floating.
+ */
+
+/**/
+Param
+setnparam(char *s, mnumber val)
+{
+    struct value vbuf;
+    Value v;
+    char *t = s, *ss = NULL;
+    Param pm;
+
+    if (!isident(s)) {
+	zerr("not an identifier: %s", s, 0);
+	errflag = 1;
+	return NULL;
+    }
+    queue_signals();
+    if (!(v = getvalue(&vbuf, &s, 1))) {
+	if ((ss = strchr(s, '[')))
+	    *ss = '\0';
+	pm = createparam(t, ss ? PM_ARRAY :
+			 (val.type & MN_INTEGER) ? PM_INTEGER : PM_FFLOAT);
+	if (!pm)
+	    pm = (Param) paramtab->getnode(paramtab, t);
+	DPUTS(!pm, "BUG: parameter not created");
+	if (ss) {
+	    *ss = '[';
+	} else if (val.type & MN_INTEGER) {
+	    pm->base = outputradix;
+	}
+	v = getvalue(&vbuf, &t, 1);
+	DPUTS(!v, "BUG: value not found for new parameter");
+    }
+    setnumvalue(v, val);
+    unqueue_signals();
+    return v->pm;
+}
+
+/* Unset a parameter */
+
+/**/
+mod_export void
+unsetparam(char *s)
+{
+    Param pm;
+
+    queue_signals();
+    if ((pm = (Param) (paramtab == realparamtab ?
+		       gethashnode2(paramtab, s) :
+		       paramtab->getnode(paramtab, s))))
+	unsetparam_pm(pm, 0, 1);
+    unqueue_signals();
+}
+
+/* Unset a parameter */
+
+/**/
+mod_export int
+unsetparam_pm(Param pm, int altflag, int exp)
+{
+    Param oldpm, altpm;
+    char *altremove;
+
+    if ((pm->flags & PM_READONLY) && pm->level <= locallevel) {
+	zerr("read-only variable: %s", pm->nam, 0);
+	return 1;
+    }
+    if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+	zerr("%s: restricted", pm->nam, 0);
+	return 1;
+    }
+
+    if (pm->ename && !altflag)
+	altremove = ztrdup(pm->ename);
+    else
+	altremove = NULL;
+
+    if (!(pm->flags & PM_UNSET))
+	pm->gsu.s->unsetfn(pm, exp);
+    if (pm->env)
+	delenv(pm);
+
+    /* remove it under its alternate name if necessary */
+    if (altremove) {
+	altpm = (Param) paramtab->getnode(paramtab, altremove);
+	/* tied parameters are at the same local level as each other */
+	oldpm = NULL;
+	while (altpm && altpm->level > pm->level) {
+	    /* param under alternate name hidden by a local */
+	    oldpm = altpm;
+	    altpm = altpm->old;
+	}
+	if (altpm) {
+	    if (oldpm && !altpm->level) {
+		oldpm->old = NULL;
+		/* fudge things so removenode isn't called */
+		altpm->level = 1;
+	    }
+	    unsetparam_pm(altpm, 1, exp);
+	}
+
+	zsfree(altremove);
+    }
+
+    /*
+     * If this was a local variable, we need to keep the old
+     * struct so that it is resurrected at the right level.
+     * This is partly because when an array/scalar value is set
+     * and the parameter used to be the other sort, unsetparam()
+     * is called.  Beyond that, there is an ambiguity:  should
+     * foo() { local bar; unset bar; } make the global bar
+     * available or not?  The following makes the answer "no".
+     *
+     * Some specials, such as those used in zle, still need removing
+     * from the parameter table; they have the PM_REMOVABLE flag.
+     */
+    if ((pm->level && locallevel >= pm->level) ||
+	(pm->flags & (PM_SPECIAL|PM_REMOVABLE)) == PM_SPECIAL)
+	return 0;
+
+    /* remove parameter node from table */
+    paramtab->removenode(paramtab, pm->nam);
+
+    if (pm->old) {
+	oldpm = pm->old;
+	paramtab->addnode(paramtab, oldpm->nam, oldpm);
+	if ((PM_TYPE(oldpm->flags) == PM_SCALAR) &&
+	    !(pm->flags & PM_HASHELEM) &&
+	    (oldpm->flags & PM_NAMEDDIR) &&
+	    oldpm->gsu.s == &stdscalar_gsu)
+	    adduserdir(oldpm->nam, oldpm->u.str, 0, 0);
+	if (oldpm->flags & PM_EXPORTED) {
+	    /*
+	     * Re-export the old value which we removed in typeset_single().
+	     * I don't think we need to test for ALL_EXPORT here, since if
+	     * it was used to export the parameter originally the parameter
+	     * should still have the PM_EXPORTED flag.
+	     */
+	    export_param(oldpm);
+	}
+    }
+
+    paramtab->freenode((HashNode) pm); /* free parameter node */
+
+    return 0;
+}
+
+/* Standard function to unset a parameter.  This is mostly delegated to *
+ * the specific set function.
+ *
+ * This could usefully be made type-specific, but then we need
+ * to be more careful when calling the unset method directly.
+ */
+
+/**/
+mod_export void
+stdunsetfn(Param pm, UNUSED(int exp))
+{
+    switch (PM_TYPE(pm->flags)) {
+	case PM_SCALAR: pm->gsu.s->setfn(pm, NULL); break;
+	case PM_ARRAY:  pm->gsu.a->setfn(pm, NULL); break;
+	case PM_HASHED: pm->gsu.h->setfn(pm, NULL); break;
+	default:
+	    if (!(pm->flags & PM_SPECIAL))
+	    	pm->u.str = NULL;
+	    break;
+    }
+    if (!(pm->flags & PM_SPECIAL))
+	pm->flags &= ~PM_TIED;
+    pm->flags |= PM_UNSET;
+}
+
+/* Function to get value of an integer parameter */
+
+/**/
+mod_export zlong
+intgetfn(Param pm)
+{
+    return pm->u.val;
+}
+
+/* Function to set value of an integer parameter */
+
+/**/
+static void
+intsetfn(Param pm, zlong x)
+{
+    pm->u.val = x;
+}
+
+/* Function to get value of a floating point parameter */
+
+/**/
+static double
+floatgetfn(Param pm)
+{
+    return pm->u.dval;
+}
+
+/* Function to set value of an integer parameter */
+
+/**/
+static void
+floatsetfn(Param pm, double x)
+{
+    pm->u.dval = x;
+}
+
+/* Function to get value of a scalar (string) parameter */
+
+/**/
+mod_export char *
+strgetfn(Param pm)
+{
+    return pm->u.str ? pm->u.str : (char *) hcalloc(1);
+}
+
+/* Function to set value of a scalar (string) parameter */
+
+/**/
+static void
+strsetfn(Param pm, char *x)
+{
+    zsfree(pm->u.str);
+    pm->u.str = x;
+    if (!(pm->flags & PM_HASHELEM) &&
+	((pm->flags & PM_NAMEDDIR) || isset(AUTONAMEDIRS))) {
+	pm->flags |= PM_NAMEDDIR;
+	adduserdir(pm->nam, x, 0, 0);
+    }
+}
+
+/* Function to get value of an array parameter */
+
+static char *nullarray = NULL;
+
+/**/
+char **
+arrgetfn(Param pm)
+{
+    return pm->u.arr ? pm->u.arr : &nullarray;
+}
+
+/* Function to set value of an array parameter */
+
+/**/
+mod_export void
+arrsetfn(Param pm, char **x)
+{
+    if (pm->u.arr && pm->u.arr != x)
+	freearray(pm->u.arr);
+    if (pm->flags & PM_UNIQUE)
+	uniqarray(x);
+    pm->u.arr = x;
+    /* Arrays tied to colon-arrays may need to fix the environment */
+    if (pm->ename && x)
+	arrfixenv(pm->ename, x);
+}
+
+/* Function to get value of an association parameter */
+
+/**/
+mod_export HashTable
+hashgetfn(Param pm)
+{
+    return pm->u.hash;
+}
+
+/* Function to set value of an association parameter */
+
+/**/
+mod_export void
+hashsetfn(Param pm, HashTable x)
+{
+    if (pm->u.hash && pm->u.hash != x)
+	deleteparamtable(pm->u.hash);
+    pm->u.hash = x;
+}
+
+/* Function to dispose of setting of an unsettable hash */
+
+/**/
+mod_export void
+nullsethashfn(Param pm, HashTable x)
+{
+    deleteparamtable(x);
+}
+
+/* Function to set value of an association parameter using key/value pairs */
+
+/**/
+static void
+arrhashsetfn(Param pm, char **val, int augment)
+{
+    /* Best not to shortcut this by using the existing hash table,   *
+     * since that could cause trouble for special hashes.  This way, *
+     * it's up to pm->gsu.h->setfn() what to do.                     */
+    int alen = arrlen(val);
+    HashTable opmtab = paramtab, ht = 0;
+    char **aptr = val;
+    Value v = (Value) hcalloc(sizeof *v);
+    v->end = -1;
+
+    if (alen % 2) {
+	freearray(val);
+	zerr("bad set of key/value pairs for associative array",
+	     NULL, 0);
+	return;
+    }
+    if (alen)
+    	if (!(augment && (ht = paramtab = pm->gsu.h->getfn(pm))))
+	    ht = paramtab = newparamtable(17, pm->nam);
+    while (*aptr) {
+	/* The parameter name is ztrdup'd... */
+	v->pm = createparam(*aptr, PM_SCALAR|PM_UNSET);
+	/*
+	 * createparam() doesn't return anything if the parameter
+	 * already existed.
+	 */
+	if (!v->pm)
+	    v->pm = (Param) paramtab->getnode(paramtab, *aptr);
+	zsfree(*aptr++);
+	/* ...but we can use the value without copying. */
+	setstrvalue(v, *aptr++);
+    }
+    paramtab = opmtab;
+    pm->gsu.h->setfn(pm, ht);
+    free(val);		/* not freearray() */
+}
+
+/*
+ * These functions are used as the set function for special parameters that
+ * cannot be set by the user.  The set is incomplete as the only such
+ * parameters are scalar and integer.
+ */
+
+/**/
+mod_export void
+nullstrsetfn(UNUSED(Param pm), char *x)
+{
+    zsfree(x);
+}
+
+/**/
+void
+nullintsetfn(UNUSED(Param pm), UNUSED(zlong x))
+{}
+
+/**/
+mod_export void
+nullunsetfn(UNUSED(Param pm), UNUSED(int exp))
+{}
+
+
+/* Function to get value of generic special integer *
+ * parameter.  data is pointer to global variable   *
+ * containing the integer value.                    */
+
+/**/
+mod_export zlong
+intvargetfn(Param pm)
+{
+    return *pm->u.valptr;
+}
+
+/* Function to set value of generic special integer *
+ * parameter.  data is pointer to global variable   *
+ * where the value is to be stored.                 */
+
+/**/
+mod_export void
+intvarsetfn(Param pm, zlong x)
+{
+    *pm->u.valptr = x;
+}
+
+/* Function to set value of any ZLE-related integer *
+ * parameter.  data is pointer to global variable   *
+ * where the value is to be stored.                 */
+
+/**/
+void
+zlevarsetfn(Param pm, zlong x)
+{
+    zlong *p = pm->u.valptr;
+
+    *p = x;
+    if (p == &lines || p == &columns)
+	adjustwinsize(2 + (p == &columns));
+}
+
+/* Function to set value of generic special scalar    *
+ * parameter.  data is pointer to a character pointer *
+ * representing the scalar (string).                  */
+
+/**/
+mod_export void
+strvarsetfn(Param pm, char *x)
+{
+    char **q = ((char **)pm->u.data);
+
+    zsfree(*q);
+    *q = x;
+}
+
+/* Function to get value of generic special scalar    *
+ * parameter.  data is pointer to a character pointer *
+ * representing the scalar (string).                  */
+
+/**/
+mod_export char *
+strvargetfn(Param pm)
+{
+    char *s = *((char **)pm->u.data);
+
+    if (!s)
+	return hcalloc(1);
+    return s;
+}
+
+/* Function to get value of generic special array  *
+ * parameter.  data is a pointer to the pointer to *
+ * a pointer (a pointer to a variable length array *
+ * of pointers).                                   */
+
+/**/
+mod_export char **
+arrvargetfn(Param pm)
+{
+    char **arrptr = *((char ***)pm->u.data);
+
+    return arrptr ? arrptr : &nullarray;
+}
+
+/* Function to set value of generic special array parameter.    *
+ * data is pointer to a variable length array of pointers which *
+ * represents this array of scalars (strings).  If pm->ename is *
+ * non NULL, then it is a colon separated environment variable  *
+ * version of this array which will need to be updated.         */
+
+/**/
+mod_export void
+arrvarsetfn(Param pm, char **x)
+{
+    char ***dptr = (char ***)pm->u.data;
+
+    if (*dptr != x)
+	freearray(*dptr);
+    if (pm->flags & PM_UNIQUE)
+	uniqarray(x);
+    /*
+     * Special tied arrays point to variables accessible in other
+     * ways which need to be set to NULL.  We can't do this
+     * with user tied variables since we can leak memory.
+     */
+    if ((pm->flags & PM_SPECIAL) && !x)
+	*dptr = mkarray(NULL);
+    else
+	*dptr = x;
+    if (pm->ename && x)
+	arrfixenv(pm->ename, x);
+}
+
+/**/
+char *
+colonarrgetfn(Param pm)
+{
+    char ***dptr = (char ***)pm->u.data;
+    return *dptr ? zjoin(*dptr, ':', 1) : "";
+}
+
+/**/
+void
+colonarrsetfn(Param pm, char *x)
+{
+    char ***dptr = (char ***)pm->u.data;
+    /*
+     * We have to make sure this is never NULL, since that
+     * can cause problems.
+     */
+    if (*dptr)
+	freearray(*dptr);
+    if (x)
+	*dptr = colonsplit(x, pm->flags & PM_UNIQUE);
+    else
+	*dptr = mkarray(NULL);
+    if (pm->ename)
+	arrfixenv(pm->nam, *dptr);
+    zsfree(x);
+}
+
+/**/
+char *
+tiedarrgetfn(Param pm)
+{
+    struct tieddata *dptr = (struct tieddata *)pm->u.data;
+    return *dptr->arrptr ? zjoin(*dptr->arrptr, dptr->joinchar, 1) : "";
+}
+
+/**/
+void
+tiedarrsetfn(Param pm, char *x)
+{
+    struct tieddata *dptr = (struct tieddata *)pm->u.data;
+
+    if (*dptr->arrptr)
+	freearray(*dptr->arrptr);
+    if (x) {
+	char sepbuf[3];
+	if (imeta(dptr->joinchar))
+	{
+	    sepbuf[0] = Meta;
+	    sepbuf[1] = dptr->joinchar ^ 32;
+	    sepbuf[2] = '\0';
+	}
+	else
+	{
+	    sepbuf[0] = dptr->joinchar;
+	    sepbuf[1] = '\0';
+	}
+	*dptr->arrptr = sepsplit(x, sepbuf, 0, 0);
+	if (pm->flags & PM_UNIQUE)
+	    uniqarray(*dptr->arrptr);
+    } else
+	*dptr->arrptr = NULL;
+    if (pm->ename)
+	arrfixenv(pm->nam, *dptr->arrptr);
+    zsfree(x);
+}
+
+/**/
+void
+tiedarrunsetfn(Param pm, UNUSED(int exp))
+{
+    /*
+     * Special unset function because we allocated a struct tieddata
+     * in typeset_single to hold the special data which we now
+     * need to delete.
+     */
+    pm->gsu.s->setfn(pm, NULL);
+    zfree(pm->u.data, sizeof(struct tieddata));
+    /* paranoia -- shouldn't need these, but in case we reuse the struct... */
+    pm->u.data = NULL;
+    zsfree(pm->ename);
+    pm->ename = NULL;
+    pm->flags &= ~PM_TIED;
+    pm->flags |= PM_UNSET;
+}
+
+/**/
+void
+uniqarray(char **x)
+{
+    char **t, **p = x;
+
+    if (!x || !*x)
+	return;
+    while (*++p)
+	for (t = x; t < p; t++)
+	    if (!strcmp(*p, *t)) {
+		zsfree(*p);
+		for (t = p--; (*t = t[1]) != NULL; t++);
+		break;
+	    }
+}
+
+/**/
+void
+zhuniqarray(char **x)
+{
+    char **t, **p = x;
+
+    if (!x || !*x)
+	return;
+    while (*++p)
+	for (t = x; t < p; t++)
+	    if (!strcmp(*p, *t)) {
+		for (t = p--; (*t = t[1]) != NULL; t++);
+		break;
+	    }
+}
+
+/* Function to get value of special parameter `#' and `ARGC' */
+
+/**/
+zlong
+poundgetfn(UNUSED(Param pm))
+{
+    return arrlen(pparams);
+}
+
+/* Function to get value for special parameter `RANDOM' */
+
+/**/
+zlong
+randomgetfn(UNUSED(Param pm))
+{
+    return rand() & 0x7fff;
+}
+
+/* Function to set value of special parameter `RANDOM' */
+
+/**/
+void
+randomsetfn(UNUSED(Param pm), zlong v)
+{
+    srand((unsigned int)v);
+}
+
+/* Function to get value for special parameter `SECONDS' */
+
+/**/
+zlong
+intsecondsgetfn(UNUSED(Param pm))
+{
+    struct timeval now;
+    struct timezone dummy_tz;
+
+    gettimeofday(&now, &dummy_tz);
+
+    return (zlong)(now.tv_sec - shtimer.tv_sec) +
+	(zlong)(now.tv_usec - shtimer.tv_usec) / (zlong)1000000;
+}
+
+/* Function to set value of special parameter `SECONDS' */
+
+/**/
+void
+intsecondssetfn(UNUSED(Param pm), zlong x)
+{
+    struct timeval now;
+    struct timezone dummy_tz;
+    zlong diff;
+
+    gettimeofday(&now, &dummy_tz);
+    diff = (zlong)now.tv_sec - x;
+    shtimer.tv_sec = diff;
+    if ((zlong)shtimer.tv_sec != diff)
+	zwarn("SECONDS truncated on assignment", NULL, 0);
+    shtimer.tv_usec = 0;
+}
+
+/**/
+double
+floatsecondsgetfn(UNUSED(Param pm))
+{
+    struct timeval now;
+    struct timezone dummy_tz;
+
+    gettimeofday(&now, &dummy_tz);
+
+    return (double)(now.tv_sec - shtimer.tv_sec) +
+	(double)(now.tv_usec - shtimer.tv_usec) / 1000000.0;
+}
+
+/**/
+void
+floatsecondssetfn(UNUSED(Param pm), double x)
+{
+    struct timeval now;
+    struct timezone dummy_tz;
+
+    gettimeofday(&now, &dummy_tz);
+    shtimer.tv_sec = now.tv_sec - (zlong)x;
+    shtimer.tv_usec = now.tv_usec - (zlong)((x - (zlong)x) * 1000000.0);
+}
+
+/**/
+double
+getrawseconds(void)
+{
+    return (double)shtimer.tv_sec + (double)shtimer.tv_usec / 1000000.0;
+}
+
+/**/
+void
+setrawseconds(double x)
+{
+    shtimer.tv_sec = (zlong)x;
+    shtimer.tv_usec = (zlong)((x - (zlong)x) * 1000000.0);
+}
+
+/**/
+int
+setsecondstype(Param pm, int on, int off)
+{
+    int newflags = (pm->flags | on) & ~off;
+    int tp = PM_TYPE(newflags);
+    /* Only one of the numeric types is allowed. */
+    if (tp == PM_EFLOAT || tp == PM_FFLOAT)
+    {
+	pm->gsu.f = &floatseconds_gsu;
+    }
+    else if (tp == PM_INTEGER)
+    {
+	pm->gsu.i = &intseconds_gsu;
+    }
+    else
+	return 1;
+    pm->flags = newflags;
+    return 0;
+}
+
+/* Function to get value for special parameter `USERNAME' */
+
+/**/
+char *
+usernamegetfn(UNUSED(Param pm))
+{
+    return get_username();
+}
+
+/* Function to set value of special parameter `USERNAME' */
+
+/**/
+void
+usernamesetfn(UNUSED(Param pm), char *x)
+{
+#if defined(HAVE_SETUID) && defined(HAVE_GETPWNAM)
+    struct passwd *pswd;
+
+    if (x && (pswd = getpwnam(x)) && (pswd->pw_uid != cached_uid)) {
+# ifdef HAVE_INITGROUPS
+	initgroups(x, pswd->pw_gid);
+# endif
+	if(!setgid(pswd->pw_gid) && !setuid(pswd->pw_uid)) {
+	    zsfree(cached_username);
+	    cached_username = ztrdup(pswd->pw_name);
+	    cached_uid = pswd->pw_uid;
+	}
+    }
+#endif /* HAVE_SETUID && HAVE_GETPWNAM */
+    zsfree(x);
+}
+
+/* Function to get value for special parameter `UID' */
+
+/**/
+zlong
+uidgetfn(UNUSED(Param pm))
+{
+    return getuid();
+}
+
+/* Function to set value of special parameter `UID' */
+
+/**/
+void
+uidsetfn(UNUSED(Param pm), zlong x)
+{
+#ifdef HAVE_SETUID
+    setuid((uid_t)x);
+#endif
+}
+
+/* Function to get value for special parameter `EUID' */
+
+/**/
+zlong
+euidgetfn(UNUSED(Param pm))
+{
+    return geteuid();
+}
+
+/* Function to set value of special parameter `EUID' */
+
+/**/
+void
+euidsetfn(UNUSED(Param pm), zlong x)
+{
+#ifdef HAVE_SETEUID
+    seteuid((uid_t)x);
+#endif
+}
+
+/* Function to get value for special parameter `GID' */
+
+/**/
+zlong
+gidgetfn(UNUSED(Param pm))
+{
+    return getgid();
+}
+
+/* Function to set value of special parameter `GID' */
+
+/**/
+void
+gidsetfn(UNUSED(Param pm), zlong x)
+{
+#ifdef HAVE_SETUID
+    setgid((gid_t)x);
+#endif
+}
+
+/* Function to get value for special parameter `EGID' */
+
+/**/
+zlong
+egidgetfn(UNUSED(Param pm))
+{
+    return getegid();
+}
+
+/* Function to set value of special parameter `EGID' */
+
+/**/
+void
+egidsetfn(UNUSED(Param pm), zlong x)
+{
+#ifdef HAVE_SETEUID
+    setegid((gid_t)x);
+#endif
+}
+
+/**/
+zlong
+ttyidlegetfn(UNUSED(Param pm))
+{
+    struct stat ttystat;
+
+    if (SHTTY == -1 || fstat(SHTTY, &ttystat))
+	return -1;
+    return time(NULL) - ttystat.st_atime;
+}
+
+/* Function to get value for special parameter `IFS' */
+
+/**/
+char *
+ifsgetfn(UNUSED(Param pm))
+{
+    return ifs;
+}
+
+/* Function to set value of special parameter `IFS' */
+
+/**/
+void
+ifssetfn(UNUSED(Param pm), char *x)
+{
+    zsfree(ifs);
+    ifs = x;
+    inittyptab();
+}
+
+/* Functions to set value of special parameters `LANG' and `LC_*' */
+
+#ifdef USE_LOCALE
+static struct localename {
+    char *name;
+    int category;
+} lc_names[] = {
+#ifdef LC_COLLATE
+    {"LC_COLLATE", LC_COLLATE},
+#endif
+#ifdef LC_CTYPE
+    {"LC_CTYPE", LC_CTYPE},
+#endif
+#ifdef LC_MESSAGES
+    {"LC_MESSAGES", LC_MESSAGES},
+#endif
+#ifdef LC_NUMERIC
+    {"LC_NUMERIC", LC_NUMERIC},
+#endif
+#ifdef LC_TIME
+    {"LC_TIME", LC_TIME},
+#endif
+    {NULL, 0}
+};
+
+/**/
+static void
+setlang(char *x)
+{
+    struct localename *ln;
+
+    setlocale(LC_ALL, x ? x : "");
+    queue_signals();
+    for (ln = lc_names; ln->name; ln++)
+	if ((x = getsparam(ln->name)))
+	    setlocale(ln->category, x);
+    unqueue_signals();
+}
+
+/**/
+void
+lc_allsetfn(Param pm, char *x)
+{
+    strsetfn(pm, x);
+    if (!x) {
+	queue_signals();
+	setlang(getsparam("LANG"));
+	unqueue_signals();
+    }
+    else
+	setlocale(LC_ALL, x);
+}
+
+/**/
+void
+langsetfn(Param pm, char *x)
+{
+    strsetfn(pm, x);
+    setlang(x);
+}
+
+/**/
+void
+lcsetfn(Param pm, char *x)
+{
+    struct localename *ln;
+
+    strsetfn(pm, x);
+    if (getsparam("LC_ALL"))
+	return;
+    queue_signals();
+    if (!x)
+	x = getsparam("LANG");
+
+    for (ln = lc_names; ln->name; ln++)
+	if (!strcmp(ln->name, pm->nam))
+	    setlocale(ln->category, x ? x : "");
+    unqueue_signals();
+}
+#endif /* USE_LOCALE */
+
+/* Function to get value for special parameter `HISTSIZE' */
+
+/**/
+zlong
+histsizegetfn(UNUSED(Param pm))
+{
+    return histsiz;
+}
+
+/* Function to set value of special parameter `HISTSIZE' */
+
+/**/
+void
+histsizesetfn(UNUSED(Param pm), zlong v)
+{
+    if ((histsiz = v) < 1)
+	histsiz = 1;
+    resizehistents();
+}
+
+/* Function to get value for special parameter `SAVEHIST' */
+
+/**/
+zlong
+savehistsizegetfn(UNUSED(Param pm))
+{
+    return savehistsiz;
+}
+
+/* Function to set value of special parameter `SAVEHIST' */
+
+/**/
+void
+savehistsizesetfn(UNUSED(Param pm), zlong v)
+{
+    if ((savehistsiz = v) < 0)
+	savehistsiz = 0;
+}
+
+/* Function to set value for special parameter `ERRNO' */
+
+/**/
+void
+errnosetfn(UNUSED(Param pm), zlong x)
+{
+    errno = (int)x;
+    if ((zlong)errno != x)
+	zwarn("errno truncated on assignment", NULL, 0);
+}
+
+/* Function to get value for special parameter `ERRNO' */
+
+/**/
+zlong
+errnogetfn(UNUSED(Param pm))
+{
+    return errno;
+}
+
+/* Function to get value for special parameter `histchar' */
+
+/**/
+char *
+histcharsgetfn(UNUSED(Param pm))
+{
+    static char buf[4];
+
+    buf[0] = bangchar;
+    buf[1] = hatchar;
+    buf[2] = hashchar;
+    buf[3] = '\0';
+    return buf;
+}
+
+/* Function to set value of special parameter `histchar' */
+
+/**/
+void
+histcharssetfn(UNUSED(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 = '^';
+    }
+    inittyptab();
+}
+
+/* Function to get value for special parameter `HOME' */
+
+/**/
+char *
+homegetfn(UNUSED(Param pm))
+{
+    return home;
+}
+
+/* Function to set value of special parameter `HOME' */
+
+/**/
+void
+homesetfn(UNUSED(Param pm), char *x)
+{
+    zsfree(home);
+    if (x && isset(CHASELINKS) && (home = xsymlink(x)))
+	zsfree(x);
+    else
+	home = x ? x : ztrdup("");
+    finddir(NULL);
+}
+
+/* Function to get value for special parameter `WORDCHARS' */
+
+/**/
+char *
+wordcharsgetfn(UNUSED(Param pm))
+{
+    return wordchars;
+}
+
+/* Function to set value of special parameter `WORDCHARS' */
+
+/**/
+void
+wordcharssetfn(UNUSED(Param pm), char *x)
+{
+    zsfree(wordchars);
+    wordchars = x;
+    inittyptab();
+}
+
+/* Function to get value for special parameter `_' */
+
+/**/
+char *
+underscoregetfn(UNUSED(Param pm))
+{
+    char *u = dupstring(underscore);
+
+    untokenize(u);
+    return u;
+}
+
+/* Function to get value for special parameter `TERM' */
+
+/**/
+char *
+termgetfn(UNUSED(Param pm))
+{
+    return term;
+}
+
+/* Function to set value of special parameter `TERM' */
+
+/**/
+void
+termsetfn(UNUSED(Param pm), char *x)
+{
+    zsfree(term);
+    term = x ? x : ztrdup("");
+
+    /* If non-interactive, delay setting up term till we need it. */
+    if (unset(INTERACTIVE) || !*term)
+	termflags |= TERM_UNKNOWN;
+    else 
+	init_term();
+}
+
+/* Function to get value for special parameter `pipestatus' */
+
+/**/
+static char **
+pipestatgetfn(UNUSED(Param pm))
+{
+    char **x = (char **) zhalloc((numpipestats + 1) * sizeof(char *));
+    char buf[20], **p;
+    int *q, i;
+
+    for (p = x, q = pipestats, i = numpipestats; i--; p++, q++) {
+	sprintf(buf, "%d", *q);
+	*p = dupstring(buf);
+    }
+    *p = NULL;
+
+    return x;
+}
+
+/* Function to get value for special parameter `pipestatus' */
+
+/**/
+static void
+pipestatsetfn(UNUSED(Param pm), char **x)
+{
+    if (x) {
+        int i;
+
+        for (i = 0; *x && i < MAX_PIPESTATS; i++, x++)
+            pipestats[i] = atoi(*x);
+        numpipestats = i;
+    }
+    else
+        numpipestats = 0;
+}
+
+/**/
+void
+arrfixenv(char *s, char **t)
+{
+    Param pm;
+    int joinchar;
+
+    if (t == path)
+	cmdnamtab->emptytable(cmdnamtab);
+
+    pm = (Param) paramtab->getnode(paramtab, s);
+    
+    /*
+     * Only one level of a parameter can be exported.  Unless
+     * ALLEXPORT is set, this must be global.
+     */
+
+    if (pm->flags & PM_HASHELEM)
+	return;
+
+    if (isset(ALLEXPORT))
+	pm->flags |= PM_EXPORTED;
+
+    /*
+     * Do not "fix" parameters that were not exported
+     */
+
+    if (!(pm->flags & PM_EXPORTED))
+	return;
+
+    if (pm->flags & PM_TIED)
+	joinchar = ((struct tieddata *)pm->u.data)->joinchar;
+    else
+	joinchar = ':';
+
+    addenv(pm, t ? zjoin(t, joinchar, 1) : "");
+}
+
+
+/**/
+int
+zputenv(char *str)
+{
+#ifdef HAVE_PUTENV
+    return putenv(str);
+#else
+    char **ep;
+    int num_env;
+
+
+    /* First check if there is already an environment *
+     * variable matching string `name'.               */
+    if (findenv(str, &num_env)) {
+	environ[num_env] = str;
+    } else {
+    /* Else we have to make room and add it */
+	num_env = arrlen(environ);
+	environ = (char **) zrealloc(environ, (sizeof(char *)) * (num_env + 2));
+
+	/* Now add it at the end */
+	ep = environ + num_env;
+	*ep = str;
+	*(ep + 1) = NULL;
+    }
+    return 0;
+#endif
+}
+
+/**/
+static int
+findenv(char *name, int *pos)
+{
+    char **ep, *eq;
+    int  nlen;
+
+
+    eq = strchr(name, '=');
+    nlen = eq ? eq - name : (int)strlen(name);
+    for (ep = environ; *ep; ep++) 
+	if (!strncmp (*ep, name, nlen) && *((*ep)+nlen) == '=') {
+	    if (pos)
+		*pos = ep - environ;
+	    return 1;
+	}
+    
+    return 0;
+}
+
+/* Given *name = "foo", it searches the environment for string *
+ * "foo=bar", and returns a pointer to the beginning of "bar"  */
+
+/**/
+mod_export char *
+zgetenv(char *name)
+{
+#ifdef HAVE_GETENV
+    return getenv(name);
+#else
+    char **ep, *s, *t;
+ 
+    for (ep = environ; *ep; ep++) {
+       for (s = *ep, t = name; *s && *s == *t; s++, t++);
+       if (*s == '=' && !*t)
+           return s + 1;
+    }
+    return NULL;
+#endif
+}
+
+/**/
+static void
+copyenvstr(char *s, char *value, int flags)
+{
+    while (*s++) {
+	if ((*s = *value++) == Meta)
+	    *s = *value++ ^ 32;
+	if (flags & PM_LOWER)
+	    *s = tulower(*s);
+	else if (flags & PM_UPPER)
+	    *s = tuupper(*s);
+    }
+}
+
+/**/
+void
+addenv(Param pm, char *value)
+{
+    char *oldenv = 0, *newenv = 0, *env = 0;
+    int pos;
+
+    /* First check if there is already an environment *
+     * variable matching string `name'. If not, and   *
+     * we are not requested to add new, return        */
+    if (findenv(pm->nam, &pos))
+	oldenv = environ[pos];
+
+     newenv = mkenvstr(pm->nam, value, pm->flags);
+     if (zputenv(newenv)) {
+        zsfree(newenv);
+	pm->env = NULL;
+	return;
+    }
+    /*
+     * Under Cygwin we must use putenv() to maintain consistency.
+     * Unfortunately, current version (1.1.2) copies argument and may
+     * silently reuse existing environment string. This tries to
+     * check for both cases
+     */
+    if (findenv(pm->nam, &pos)) {
+	env = environ[pos];
+    
+#ifndef __SYMBIAN32__    
+    /*Th oldenv ptr is free'ed withing setenv() of libc/stdlib/setenv.c. no need to free here.*/
+	if (env != oldenv)
+	  zsfree(oldenv); 
+#endif //__SYMBIAN32__
+	
+	if (env != newenv)
+	    zsfree(newenv);
+	pm->flags |= PM_EXPORTED;
+	pm->env = env;
+	return;
+    }
+
+    DPUTS(1, "addenv should never reach the end");
+    pm->env = NULL;
+}
+
+
+/* Given strings *name = "foo", *value = "bar", *
+ * return a new string *str = "foo=bar".        */
+
+/**/
+static char *
+mkenvstr(char *name, char *value, int flags)
+{
+    char *str, *s;
+    int len_name, len_value;
+
+    len_name = strlen(name);
+    for (len_value = 0, s = value;
+	 *s && (*s++ != Meta || *s++ != 32); len_value++);
+    s = str = (char *) zalloc(len_name + len_value + 2);
+    strcpy(s, name);
+    s += len_name;
+    *s = '=';
+    copyenvstr(s, value, flags);
+    return str;
+}
+
+/* Given *name = "foo", *value = "bar", add the    *
+ * string "foo=bar" to the environment.  Return a  *
+ * pointer to the location of this new environment *
+ * string.                                         */
+
+
+/**/
+void
+delenvvalue(char *x)
+{
+#ifdef __SYMBIAN32__
+		// Use unsetenv to unset user defined environmental variables. 
+		// unsetenv unsets environmental variables from Backend heap, which were allocated by setenv
+    	unsetenv(x);
+#else	
+    	// use zsfree to unset user defined environmental variables
+    	// zsfree unsets environmental variables allocated on user heap
+    	char **ep;
+        
+    	for (ep = environ; *ep; ep++) {
+    	if (*ep == x)
+    	    break;
+        }
+        
+        if (*ep) {
+    	for (; (ep[0] = ep[1]); ep++);
+        }
+
+        zsfree(x);
+#endif
+}
+
+/* Delete a pointer from the list of pointers to environment *
+ * variables by shifting all the other pointers up one slot. */
+
+/**/
+void
+delenv(Param pm)
+{
+    delenvvalue(pm->env);
+    pm->env = NULL;
+    /*
+     * Note we don't remove PM_EXPORT from the flags.  This
+     * may be asking for trouble but we need to know later
+     * if we restore this parameter to its old value.
+     */
+}
+
+/**/
+mod_export void
+convbase(char *s, zlong v, int base)
+{
+    int digs = 0;
+    zulong x;
+
+    if (v < 0)
+	*s++ = '-', v = -v;
+    if (base >= -1 && base <= 1)
+	base = -10;
+
+    if (base > 0) {
+	if (isset(CBASES) && base == 16)
+	    sprintf(s, "0x");
+	else if (isset(CBASES) && base == 8 && isset(OCTALZEROES))
+	    sprintf(s, "0");
+	else if (base != 10)
+	    sprintf(s, "%d#", base);
+	else
+	    *s = 0;
+	s += strlen(s);
+    } else
+	base = -base;
+    for (x = v; x; digs++)
+	x /= base;
+    if (!digs)
+	digs = 1;
+    s[digs--] = '\0';
+    x = v;
+    while (digs >= 0) {
+	int dig = x % base;
+
+	s[digs--] = (dig < 10) ? '0' + dig : dig - 10 + 'A';
+	x /= base;
+    }
+}
+
+/*
+ * Convert a floating point value for output.
+ * Unlike convbase(), this has its own internal storage and returns
+ * a value from the heap.
+ */
+
+/**/
+char *
+convfloat(double dval, int digits, int flags, FILE *fout)
+{
+    char fmt[] = "%.*e";
+    char *prev_locale, *ret;
+
+    /*
+     * The difficulty with the buffer size is that a %f conversion
+     * prints all digits before the decimal point: with 64 bit doubles,
+     * that's around 310.  We can't check without doing some quite
+     * serious floating point operations we'd like to avoid.
+     * Then we are liable to get all the digits
+     * we asked for after the decimal point, or we should at least
+     * bargain for it.  So we just allocate 512 + digits.  This
+     * should work until somebody decides on 128-bit doubles.
+     */
+    if (!(flags & (PM_EFLOAT|PM_FFLOAT))) {
+	/*
+	 * Conversion from a floating point expression without using
+	 * a variable.  The best bet in this case just seems to be
+	 * to use the general %g format with something like the maximum
+	 * double precision.
+	 */
+	fmt[3] = 'g';
+	if (!digits)
+	    digits = 17;
+    } else {
+	if (flags & PM_FFLOAT)
+	    fmt[3] = 'f';
+	if (digits <= 0)
+	    digits = 10;
+	if (flags & PM_EFLOAT) {
+	    /*
+	     * Here, we are given the number of significant figures, but
+	     * %e wants the number of decimal places (unlike %g)
+	     */
+	    digits--;
+	}
+    }
+#ifdef USE_LOCALE
+    prev_locale = dupstring(setlocale(LC_NUMERIC, NULL));
+    setlocale(LC_NUMERIC, "POSIX");
+#endif
+    if (fout) {
+	fprintf(fout, fmt, digits, dval);
+	ret = NULL;
+    } else {
+	VARARR(char, buf, 512 + digits);
+	sprintf(buf, fmt, digits, dval);
+	if (!strchr(buf, 'e') && !strchr(buf, '.'))
+	    strcat(buf, ".");
+	ret = dupstring(buf);
+    }
+#ifdef USE_LOCALE
+    if (prev_locale) setlocale(LC_NUMERIC, prev_locale);
+#endif
+    return ret;
+}
+
+/* Start a parameter scope */
+
+/**/
+mod_export void
+startparamscope(void)
+{
+    locallevel++;
+}
+
+/* End a parameter scope: delete the parameters local to the scope. */
+
+/**/
+mod_export void
+endparamscope(void)
+{
+    locallevel--;
+    /* This pops anything from a higher locallevel */
+    saveandpophiststack(0, HFILE_USE_OPTIONS);
+    scanhashtable(paramtab, 0, 0, 0, scanendscope, 0);
+}
+
+/**/
+static void
+scanendscope(HashNode hn, UNUSED(int flags))
+{
+    Param pm = (Param)hn;
+    if (pm->level > locallevel) {
+	if ((pm->flags & (PM_SPECIAL|PM_REMOVABLE)) == PM_SPECIAL) {
+	    /*
+	     * Removable specials are normal in that they can be removed
+	     * to reveal an ordinary parameter beneath.  Here we handle
+	     * non-removable specials, which were made local by stealth
+	     * (see newspecial code in typeset_single()).  In fact the
+	     * visible pm is always the same struct; the pm->old is
+	     * just a place holder for old data and flags.
+	     */
+	    Param tpm = pm->old;
+
+	    if (!strcmp(pm->nam, "SECONDS"))
+	    {
+		setsecondstype(pm, PM_TYPE(tpm->flags), PM_TYPE(pm->flags));
+		/*
+		 * We restore SECONDS by restoring its raw internal value
+		 * that we cached off into tpm->u.dval.
+		 */
+		setrawseconds(tpm->u.dval);
+		tpm->flags |= PM_NORESTORE;
+	    }
+	    DPUTS(!tpm || PM_TYPE(pm->flags) != PM_TYPE(tpm->flags) ||
+		  !(tpm->flags & PM_SPECIAL),
+		  "BUG: in restoring scope of special parameter");
+	    pm->old = tpm->old;
+	    pm->flags = (tpm->flags & ~PM_NORESTORE);
+	    pm->level = tpm->level;
+	    pm->base = tpm->base;
+	    pm->width = tpm->width;
+	    if (pm->env)
+		delenv(pm);
+
+	    if (!(tpm->flags & PM_NORESTORE))
+		switch (PM_TYPE(pm->flags)) {
+		case PM_SCALAR:
+		    pm->gsu.s->setfn(pm, tpm->u.str);
+		    break;
+		case PM_INTEGER:
+		    pm->gsu.i->setfn(pm, tpm->u.val);
+		    break;
+		case PM_EFLOAT:
+		case PM_FFLOAT:
+		    pm->gsu.f->setfn(pm, tpm->u.dval);
+		    break;
+		case PM_ARRAY:
+		    pm->gsu.a->setfn(pm, tpm->u.arr);
+		    break;
+		case PM_HASHED:
+		    pm->gsu.h->setfn(pm, tpm->u.hash);
+		    break;
+		}
+	    zfree(tpm, sizeof(*tpm));
+
+	    if (pm->flags & PM_EXPORTED)
+		export_param(pm);
+	} else
+	    unsetparam_pm(pm, 0, 0);
+    }
+}
+
+
+/**********************************/
+/* Parameter Hash Table Functions */
+/**********************************/
+
+/**/
+void
+freeparamnode(HashNode hn)
+{
+    Param pm = (Param) hn;
+ 
+    /* Since the second flag to unsetfn isn't used, I don't *
+     * know what its value should be.                       */
+    if (delunset)
+	pm->gsu.s->unsetfn(pm, 1);
+    zsfree(pm->nam);
+    /* If this variable was tied by the user, ename was ztrdup'd */
+    if (pm->flags & PM_TIED)
+	zsfree(pm->ename);
+    zfree(pm, sizeof(struct param));
+}
+
+/* Print a parameter */
+
+enum paramtypes_flags {
+    PMTF_USE_BASE	= (1<<0),
+    PMTF_USE_WIDTH	= (1<<1),
+    PMTF_TEST_LEVEL	= (1<<2)
+};
+
+struct paramtypes {
+    int binflag;	/* The relevant PM_FLAG(S) */
+    const char *string;	/* String for verbose output */
+    int typeflag;	/* Flag for typeset -? */
+    int flags;		/* The enum above */
+};
+
+static const struct paramtypes pmtypes[] = {
+    { PM_AUTOLOAD, "undefined", 0, 0},
+    { PM_INTEGER, "integer", 'i', PMTF_USE_BASE},
+    { PM_EFLOAT, "float", 'E', 0},
+    { PM_FFLOAT, "float", 'F', 0},
+    { PM_ARRAY, "array", 'a', 0},
+    { PM_HASHED, "association", 'A', 0},
+    { 0, "local", 0, PMTF_TEST_LEVEL},
+    { PM_LEFT, "left justified", 'L', PMTF_USE_WIDTH},
+    { PM_RIGHT_B, "right justified", 'R', PMTF_USE_WIDTH},
+    { PM_RIGHT_Z, "zero filled", 'Z', PMTF_USE_WIDTH},
+    { PM_LOWER, "lowercase", 'l', 0},
+    { PM_UPPER, "uppercase", 'u', 0},
+    { PM_READONLY, "readonly", 'r', 0},
+    { PM_TAGGED, "tagged", 't', 0},
+    { PM_EXPORTED, "exported", 'x', 0}
+};
+
+#define PMTYPES_SIZE ((int)(sizeof(pmtypes)/sizeof(struct paramtypes)))
+
+/**/
+mod_export void
+printparamnode(HashNode hn, int printflags)
+{
+    Param p = (Param) hn;
+    char *t, **u;
+
+    if (p->flags & PM_UNSET)
+	return;
+
+    if (printflags & PRINT_TYPESET)
+	printf("typeset ");
+
+    /* Print the attributes of the parameter */
+    if (printflags & (PRINT_TYPE|PRINT_TYPESET)) {
+	int doneminus = 0, i;
+	const struct paramtypes *pmptr;
+
+	for (pmptr = pmtypes, i = 0; i < PMTYPES_SIZE; i++, pmptr++) {
+	    int doprint = 0;
+	    if (pmptr->flags & PMTF_TEST_LEVEL) {
+		if (p->level)
+		    doprint = 1;
+	    } else if (p->flags & pmptr->binflag)
+		doprint = 1;
+
+	    if (doprint) {
+		if (printflags & PRINT_TYPESET) {
+		    if (pmptr->typeflag) {
+			if (!doneminus) {
+			    putchar('-');
+			    doneminus = 1;
+			}
+			putchar(pmptr->typeflag);
+		    }
+		} else {
+		    printf("%s ", pmptr->string);
+		}
+		if ((pmptr->flags & PMTF_USE_BASE) && p->base) {
+		    printf("%d ", p->base);
+		    doneminus = 0;
+		}
+		if ((pmptr->flags & PMTF_USE_WIDTH) && p->width) {
+		    printf("%d ", p->width);
+		    doneminus = 0;
+		}
+	    }
+	}
+	if (doneminus)
+	    putchar(' ');
+    }
+
+    if ((printflags & PRINT_NAMEONLY) ||
+	((p->flags & PM_HIDEVAL) && !(printflags & PRINT_INCLUDEVALUE))) {
+	zputs(p->nam, stdout);
+	putchar('\n');
+	return;
+    }
+
+    quotedzputs(p->nam, stdout);
+
+    if (p->flags & PM_AUTOLOAD) {
+	putchar('\n');
+	return;
+    }
+    if (printflags & PRINT_KV_PAIR)
+	putchar(' ');
+    else if ((printflags & PRINT_TYPESET) &&
+	     (PM_TYPE(p->flags) == PM_ARRAY || PM_TYPE(p->flags) == PM_HASHED))
+	printf("\n%s=", p->nam);
+    else
+	putchar('=');
+
+    /* How the value is displayed depends *
+     * on the type of the parameter       */
+    switch (PM_TYPE(p->flags)) {
+    case PM_SCALAR:
+	/* string: simple output */
+	if (p->gsu.s->getfn && (t = p->gsu.s->getfn(p)))
+	    quotedzputs(t, stdout);
+	break;
+    case PM_INTEGER:
+	/* integer */
+#ifdef ZSH_64_BIT_TYPE
+	fputs(output64(p->gsu.i->getfn(p)), stdout);
+#else
+	printf("%ld", p->gsu.i->getfn(p));
+#endif
+	break;
+    case PM_EFLOAT:
+    case PM_FFLOAT:
+	/* float */
+	convfloat(p->gsu.f->getfn(p), p->base, p->flags, stdout);
+	break;
+    case PM_ARRAY:
+	/* array */
+	if (!(printflags & PRINT_KV_PAIR))
+	    putchar('(');
+	u = p->gsu.a->getfn(p);
+	if(*u) {
+	    quotedzputs(*u++, stdout);
+	    while (*u) {
+		putchar(' ');
+		quotedzputs(*u++, stdout);
+	    }
+	}
+	if (!(printflags & PRINT_KV_PAIR))
+	    putchar(')');
+	break;
+    case PM_HASHED:
+	/* association */
+	if (!(printflags & PRINT_KV_PAIR))
+	    putchar('(');
+	{
+            HashTable ht = p->gsu.h->getfn(p);
+            if (ht)
+		scanhashtable(ht, 0, 0, PM_UNSET,
+			      ht->printnode, PRINT_KV_PAIR);
+	}
+	if (!(printflags & PRINT_KV_PAIR))
+	    putchar(')');
+	break;
+    }
+    if (printflags & PRINT_KV_PAIR)
+	putchar(' ');
+    else
+	putchar('\n');
+}