about summary refs log tree commit diff
path: root/Src
diff options
context:
space:
mode:
Diffstat (limited to 'Src')
-rw-r--r--Src/Makefile.in2
-rw-r--r--Src/Makemod.in.in5
-rw-r--r--Src/Zle/zle_tricky.c16
-rw-r--r--Src/builtin.c44
-rw-r--r--Src/exec.c22
-rw-r--r--Src/glob.c19
-rw-r--r--Src/hashtable.c87
-rw-r--r--Src/input.c2
-rw-r--r--Src/params.c471
-rw-r--r--Src/prompt.c256
-rw-r--r--Src/subst.c30
-rw-r--r--Src/text.c2
-rw-r--r--Src/utils.c4
-rw-r--r--Src/zsh.export1
-rw-r--r--Src/zsh.h7
15 files changed, 671 insertions, 297 deletions
diff --git a/Src/Makefile.in b/Src/Makefile.in
index 269ed7c1b..27891e92a 100644
--- a/Src/Makefile.in
+++ b/Src/Makefile.in
@@ -149,7 +149,7 @@ uninstall.bin: uninstall.bin-here
 # install binary, creating install directory if necessary
 install.bin-here: zsh install.bin-@L@
 	$(sdir_top)/mkinstalldirs $(bindir)
-	$(INSTALL_PROGRAM) zsh $(bindir)/zsh-$(VERSION)
+	$(INSTALL_PROGRAM) $(STRIPFLAGS) zsh $(bindir)/zsh-$(VERSION)
 	if test -f $(bindir)/zsh; then \
 	    rm -f $(bindir)/zsh.old; \
 	    ln $(bindir)/zsh $(bindir)/zsh.old; \
diff --git a/Src/Makemod.in.in b/Src/Makemod.in.in
index f27a6bd57..a5c760eb4 100644
--- a/Src/Makemod.in.in
+++ b/Src/Makemod.in.in
@@ -119,8 +119,9 @@ uninstall.modules: uninstall.modules-here
 install.bin-here uninstall.bin-here:
 
 install.modules-here:
-	$(sdir_top)/mkinstalldirs $(MODDIR)
-	modules='$(MODULES)'; for mod in $$modules; do \
+	modules='$(MODULES)'; \
+	if test -n "$$modules"; then $(sdir_top)/mkinstalldirs $(MODDIR); fi; \
+	for mod in $$modules; do \
 	    $(INSTALL_PROGRAM) $$mod $(MODDIR)/$$mod; \
 	done
 
diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c
index 9ffef3b80..a8998739c 100644
--- a/Src/Zle/zle_tricky.c
+++ b/Src/Zle/zle_tricky.c
@@ -2945,7 +2945,7 @@ docompletion(char *s, int lst, int incmd)
 	ainfo = fainfo = NULL;
 
 	/* Make sure we have the completion list and compctl. */
-	if (makecomplist(s, incmd)) {
+	if (makecomplist(s, incmd, lst)) {
 	    /* Error condition: feeeeeeeeeeeeep(). */
 	    feep();
 	    goto compend;
@@ -3029,7 +3029,7 @@ static unsigned long ccont;
 
 /**/
 static int
-makecomplist(char *s, int incmd)
+makecomplist(char *s, int incmd, int lst)
 {
     struct cmlist ms;
     Cmlist m = cmatcher;
@@ -3062,7 +3062,7 @@ makecomplist(char *s, int incmd)
 	ccused = newlinklist();
 	ccstack = newlinklist();
 
-	makecomplistglobal(s, incmd);
+	makecomplistglobal(s, incmd, lst);
 
 	endcmgroup(NULL);
 
@@ -3098,12 +3098,14 @@ makecomplist(char *s, int incmd)
 
 /**/
 static void
-makecomplistglobal(char *os, int incmd)
+makecomplistglobal(char *os, int incmd, int lst)
 {
     Compctl cc;
     char *s;
 
-    if (inwhat == IN_ENV)
+    if (lst == COMP_WIDGET) {
+	cc = compwidget->u.cc;
+    } else if (inwhat == IN_ENV)
         /* Default completion for parameter values. */
         cc = &cc_default;
     else if (inwhat == IN_MATH) {
@@ -4413,7 +4415,8 @@ get_user_var(char *nam)
     } else {
 	/* Otherwise it should be a parameter name. */
 	char **arr = NULL, *val;
-	if (!(arr = getaparam(nam)) && (val = getsparam(nam))) {
+	if (!(arr = getaparam(nam)) && !(arr = gethparam(nam)) &&
+	    (val = getsparam(nam))) {
 	    arr = (char **)ncalloc(2*sizeof(char *));
 	    arr[0] = val;
 	    arr[1] = NULL;
@@ -5026,6 +5029,7 @@ do_single(Cmatch m)
 		else {
 		    p = (char *) ncalloc(strlen(ppre) + strlen(str) +
 					 strlen(psuf) + 1);
+		    sprintf(p, "%s%s%s", ppre, str, psuf);
 		}
 		parsestr(p);
 		if (ic)
diff --git a/Src/builtin.c b/Src/builtin.c
index 3aae0f769..dbe91a5b1 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -50,7 +50,7 @@ static struct builtin builtins[] =
     BUILTIN("cd", 0, bin_cd, 0, 2, BIN_CD, NULL, NULL),
     BUILTIN("chdir", 0, bin_cd, 0, 2, BIN_CD, NULL, NULL),
     BUILTIN("continue", BINF_PSPECIAL, bin_break, 0, 1, BIN_CONTINUE, NULL, NULL),
-    BUILTIN("declare", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "LRUZfilrtux", NULL),
+    BUILTIN("declare", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "ALRUZafilrtux", NULL),
     BUILTIN("dirs", 0, bin_dirs, 0, -1, 0, "v", NULL),
     BUILTIN("disable", 0, bin_enable, 0, -1, BIN_DISABLE, "afmr", NULL),
     BUILTIN("disown", 0, bin_fg, 0, -1, BIN_DISOWN, NULL, NULL),
@@ -60,7 +60,7 @@ static struct builtin builtins[] =
     BUILTIN("enable", 0, bin_enable, 0, -1, BIN_ENABLE, "afmr", NULL),
     BUILTIN("eval", BINF_PSPECIAL, bin_eval, 0, -1, BIN_EVAL, NULL, NULL),
     BUILTIN("exit", BINF_PSPECIAL, bin_break, 0, 1, BIN_EXIT, NULL, NULL),
-    BUILTIN("export", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, BIN_EXPORT, "LRUZfilrtu", "x"),
+    BUILTIN("export", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, BIN_EXPORT, "LRUZafilrtu", "x"),
     BUILTIN("false", 0, bin_false, 0, -1, 0, NULL, NULL),
     BUILTIN("fc", BINF_FCOPTS, bin_fc, 0, -1, BIN_FC, "nlreIRWAdDfEim", NULL),
     BUILTIN("fg", 0, bin_fg, 0, -1, BIN_FG, NULL, NULL),
@@ -78,7 +78,7 @@ static struct builtin builtins[] =
     BUILTIN("jobs", 0, bin_fg, 0, -1, BIN_JOBS, "dlpZrs", NULL),
     BUILTIN("kill", 0, bin_kill, 0, -1, 0, NULL, NULL),
     BUILTIN("let", 0, bin_let, 1, -1, 0, NULL, NULL),
-    BUILTIN("local", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "LRUZilrtu", NULL),
+    BUILTIN("local", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "ALRUZailrtu", NULL),
     BUILTIN("log", 0, bin_log, 0, 0, 0, NULL, NULL),
     BUILTIN("logout", 0, bin_break, 0, 1, BIN_LOGOUT, NULL, NULL),
 
@@ -93,7 +93,7 @@ static struct builtin builtins[] =
     BUILTIN("pwd", 0, bin_pwd, 0, 0, 0, "rLP", NULL),
     BUILTIN("r", BINF_R, bin_fc, 0, -1, BIN_FC, "nrl", NULL),
     BUILTIN("read", 0, bin_read, 0, -1, 0, "rzu0123456789pkqecnAlE", NULL),
-    BUILTIN("readonly", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "LRUZfiltux", "r"),
+    BUILTIN("readonly", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "ALRUZafiltux", "r"),
     BUILTIN("rehash", 0, bin_hash, 0, 0, 0, "dfv", "r"),
     BUILTIN("return", BINF_PSPECIAL, bin_break, 0, 1, BIN_RETURN, NULL, NULL),
     BUILTIN("set", BINF_PSPECIAL, bin_set, 0, -1, 0, NULL, NULL),
@@ -107,7 +107,7 @@ static struct builtin builtins[] =
     BUILTIN("trap", BINF_PSPECIAL, bin_trap, 0, -1, 0, NULL, NULL),
     BUILTIN("true", 0, bin_true, 0, -1, 0, NULL, NULL),
     BUILTIN("type", 0, bin_whence, 0, -1, 0, "ampfsw", "v"),
-    BUILTIN("typeset", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "LRUZfilrtuxm", NULL),
+    BUILTIN("typeset", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "ALRUZafilrtuxm", NULL),
     BUILTIN("umask", 0, bin_umask, 0, 1, 0, "S", NULL),
     BUILTIN("unalias", 0, bin_unhash, 1, -1, 0, "m", "a"),
     BUILTIN("unfunction", 0, bin_unhash, 1, -1, 0, "m", "f"),
@@ -618,6 +618,8 @@ set_pwd_env(void)
 {
     Param pm;
 
+    /* update the PWD and OLDPWD shell parameters */
+
     pm = (Param) paramtab->getnode(paramtab, "PWD");
     if (pm && PM_TYPE(pm->flags) != PM_SCALAR) {
 	pm->flags &= ~PM_READONLY;
@@ -704,7 +706,6 @@ bin_cd(char *nam, char **argv, char *ops, int func)
 	    chdir(unmeta(pwd));
 	}
     }
-    set_pwd_env();
     return 0;
 }
 
@@ -946,7 +947,6 @@ cd_try_chdir(char *pfix, char *dest, int hard)
 static void
 cd_new_pwd(int func, LinkNode dir, int chaselinks)
 {
-    Param pm;
     List l;
     char *new_pwd, *s;
     int dirstacksize;
@@ -981,13 +981,8 @@ cd_new_pwd(int func, LinkNode dir, int chaselinks)
     zsfree(oldpwd);
     oldpwd = pwd;
     pwd = new_pwd;
-    /* update the PWD and OLDPWD shell parameters */
-    if ((pm = (Param) paramtab->getnode(paramtab, "PWD")) &&
-	(pm->flags & PM_EXPORTED) && pm->env)
-	pm->env = replenv(pm->env, pwd);
-    if ((pm = (Param) paramtab->getnode(paramtab, "OLDPWD")) &&
-	(pm->flags & PM_EXPORTED) && pm->env)
-	pm->env = replenv(pm->env, oldpwd);
+    set_pwd_env();
+
     if (unset(PUSHDSILENT) && func != BIN_CD && isset(INTERACTIVE))
 	printdirstack();
     else if (doprintdir) {
@@ -1474,8 +1469,8 @@ bin_typeset(char *name, char **argv, char *ops, int func)
     Param pm;
     Asgment asg;
     Comp com;
-    char *optstr = "iLRZlurtxU";
-    int on = 0, off = 0, roff, bit = PM_INTEGER;
+    char *optstr = "aiLRZlurtxU----A";
+    int on = 0, off = 0, roff, bit = PM_ARRAY;
     int initon, initoff, of, i;
     int returnval = 0, printflags = 0;
 
@@ -1506,6 +1501,8 @@ bin_typeset(char *name, char **argv, char *ops, int func)
 	off |= PM_LOWER;
     if (on & PM_LOWER)
 	off |= PM_UPPER;
+    if (on & PM_HASHED)
+	off |= PM_ARRAY;
     on &= ~off;
 
     /* Given no arguments, list whatever the options specify. */
@@ -1548,8 +1545,15 @@ bin_typeset(char *name, char **argv, char *ops, int func)
 			    if (PM_TYPE(pm->flags) == PM_ARRAY && (on & PM_UNIQUE) &&
 				!(pm->flags & PM_READONLY & ~off))
 				uniqarray((*pm->gets.afn) (pm));
+			    if ((on & ~pm->flags) & PM_HASHED) {
+				char *nam = ztrdup(pm->nam);
+				unsetparam(nam);
+				pm = createparam(nam, on & ~PM_READONLY);
+				DPUTS(!pm, "BUG: parameter not created");
+			    }
 			    pm->flags = (pm->flags | on) & ~off;
-			    if (PM_TYPE(pm->flags) != PM_ARRAY) {
+			    if (PM_TYPE(pm->flags) != PM_ARRAY &&
+				PM_TYPE(pm->flags) != PM_HASHED) {
 				if ((on & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z | PM_INTEGER)) && auxlen)
 				    pm->ct = auxlen;
 				/* did we just export this? */
@@ -1598,7 +1602,8 @@ bin_typeset(char *name, char **argv, char *ops, int func)
 	    (((pm->flags & PM_SPECIAL) && pm->level == locallevel) ||
 	     (!(pm->flags & PM_UNSET) &&
 	      ((locallevel == pm->level) || func == BIN_EXPORT) &&
-	      !(bit = ((off & pm->flags) | (on & ~pm->flags)) & PM_INTEGER)))) {
+	      !(bit = (((off & pm->flags) | (on & ~pm->flags)) &
+		       (PM_INTEGER|PM_HASHED)))))) {
 	    /* if no flags or values are given, just print this parameter */
 	    if (!on && !roff && !asg->value) {
 		paramtab->printnode((HashNode) pm, 0);
@@ -1623,7 +1628,8 @@ bin_typeset(char *name, char **argv, char *ops, int func)
 	    if ((on & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z | PM_INTEGER)) &&
 		auxlen)
 		pm->ct = auxlen;
-	    if (PM_TYPE(pm->flags) != PM_ARRAY) {
+	    if (PM_TYPE(pm->flags) != PM_ARRAY &&
+		PM_TYPE(pm->flags) != PM_HASHED) {
 		if (pm->flags & PM_EXPORTED) {
 		    if (!(pm->flags & PM_UNSET) && !pm->env && !asg->value)
 			pm->env = addenv(asg->name, getsparam(asg->name));
diff --git a/Src/exec.c b/Src/exec.c
index 22fa526ec..bb70e59c8 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -1004,11 +1004,12 @@ void
 untokenize(char *s)
 {
     for (; *s; s++)
-	if (itok(*s))
+	if (itok(*s)) {
 	    if (*s == Nularg)
 		chuck(s--);
 	    else
 		*s = ztokens[*s - Pound];
+	}
 }
 
 /* Open a file for writing redicection */
@@ -1923,22 +1924,8 @@ save_params(Cmd cmd, LinkList *restore_p, LinkList *remove_p)
 	    } else if (!(pm->flags & PM_READONLY) &&
 		       (unset(RESTRICTED) || !(pm->flags & PM_RESTRICTED))) {
 		Param tpm = (Param) alloc(sizeof *tpm);
-
 		tpm->nam = s;
-		tpm->flags = pm->flags;
-		switch (PM_TYPE(pm->flags)) {
-		case PM_SCALAR:
-		    tpm->u.str = ztrdup(pm->gets.cfn(pm));
-		    break;
-		case PM_INTEGER:
-		    tpm->u.val = pm->gets.ifn(pm);
-		    break;
-		case PM_ARRAY:
-		    PERMALLOC {
-			tpm->u.arr = arrdup(pm->gets.afn(pm));
-		    } LASTALLOC;
-		    break;
-		}
+		copyparam(tpm, pm, 1);
 		pm = tpm;
 	    }
 	    addlinknode(*remove_p, s);
@@ -1989,6 +1976,9 @@ restore_params(LinkList restorelist, LinkList removelist)
 		case PM_ARRAY:
 		    tpm->sets.afn(tpm, pm->u.arr);
 		    break;
+		case PM_HASHED:
+		    tpm->sets.hfn(tpm, pm->u.hash);
+		    break;
 		}
 	    } else
 		paramtab->addnode(paramtab, pm->nam, pm);
diff --git a/Src/glob.c b/Src/glob.c
index 0c66adec8..ea5d0133c 100644
--- a/Src/glob.c
+++ b/Src/glob.c
@@ -355,21 +355,12 @@ scanner(Complist q)
 	    insert(c->str, 0);
     } else {
 	/* Do pattern matching on current path section. */
-	char *fn;
+	char *fn = pathbuf[pathbufcwd] ? unmeta(pathbuf + pathbufcwd) : ".";
 	int dirs = !!q->next;
-	DIR *lock;
+	DIR *lock = opendir(fn);
 	char *subdirs = NULL;
 	int subdirlen = 0;
 
-	fn = pathbuf[pathbufcwd] ? unmeta(pathbuf + pathbufcwd) : ".";
-	if (dirs) {
-	    struct stat st;
-	    stat(fn, &st);
-	    /* a directory with subdirectories has link count greater than 2 */
-	    if (!S_ISDIR(st.st_mode) || st.st_nlink == 2)
-		return;
-	}
-	lock = opendir(fn);
 	if (lock == NULL)
 	    return;
 	while ((fn = zreaddir(lock, 1)) && !errflag) {
@@ -594,7 +585,8 @@ parsecomp(int gflag)
 		 pptr[1] && pptr[1] != Outpar && pptr[1] != Bar) ||
 		*pptr == Outpar) {
 		if (*pptr == '/' || !*pptr ||
-		    (isset(EXTENDEDGLOB) && *pptr == Tilde &&
+		    ((*pptr == Bar ||
+		      (isset(EXTENDEDGLOB) && *pptr == Tilde)) &&
 		     (gflag & GF_TOPLEV)))
 		    c->stat |= C_LAST;
 		return c;
@@ -746,7 +738,8 @@ parsecomp(int gflag)
     }
     /* mark if last pattern component in path component or pattern */
     if (*pptr == '/' || !*pptr ||
-	(isset(EXTENDEDGLOB) && *pptr == Tilde && (gflag & GF_TOPLEV)))
+	((*pptr == Bar ||
+	 (isset(EXTENDEDGLOB) && *pptr == Tilde)) && (gflag & GF_TOPLEV)))
 	c->stat |= C_LAST;
     c->str = dupstrpfx(cstr, pptr - cstr);
     return c;
diff --git a/Src/hashtable.c b/Src/hashtable.c
index 4adf3904d..5bcfec231 100644
--- a/Src/hashtable.c
+++ b/Src/hashtable.c
@@ -1061,93 +1061,6 @@ printaliasnode(HashNode hn, int printflags)
     putchar('\n');
 }
 
-/**********************************/
-/* Parameter Hash Table Functions */
-/**********************************/
-
-/**/
-void
-freeparamnode(HashNode hn)
-{
-    Param pm = (Param) hn;
- 
-    zsfree(pm->nam);
-    zfree(pm, sizeof(struct param));
-}
-
-/* Print a parameter */
-
-/**/
-void
-printparamnode(HashNode hn, int printflags)
-{
-    Param p = (Param) hn;
-    char *t, **u;
-
-    if (p->flags & PM_UNSET)
-	return;
-
-    /* Print the attributes of the parameter */
-    if (printflags & PRINT_TYPE) {
-	if (p->flags & PM_INTEGER)
-	    printf("integer ");
-	if (p->flags & PM_ARRAY)
-	    printf("array ");
-	if (p->flags & PM_LEFT)
-	    printf("left justified %d ", p->ct);
-	if (p->flags & PM_RIGHT_B)
-	    printf("right justified %d ", p->ct);
-	if (p->flags & PM_RIGHT_Z)
-	    printf("zero filled %d ", p->ct);
-	if (p->flags & PM_LOWER)
-	    printf("lowercase ");
-	if (p->flags & PM_UPPER)
-	    printf("uppercase ");
-	if (p->flags & PM_READONLY)
-	    printf("readonly ");
-	if (p->flags & PM_TAGGED)
-	    printf("tagged ");
-	if (p->flags & PM_EXPORTED)
-	    printf("exported ");
-    }
-
-    if (printflags & PRINT_NAMEONLY) {
-	zputs(p->nam, stdout);
-	putchar('\n');
-	return;
-    }
-
-    /* How the value is displayed depends *
-     * on the type of the parameter       */
-    quotedzputs(p->nam, stdout);
-    putchar('=');
-    switch (PM_TYPE(p->flags)) {
-    case PM_SCALAR:
-	/* string: simple output */
-	if (p->gets.cfn && (t = p->gets.cfn(p)))
-	    quotedzputs(t, stdout);
-	putchar('\n');
-	break;
-    case PM_INTEGER:
-	/* integer */
-	printf("%ld\n", p->gets.ifn(p));
-	break;
-    case PM_ARRAY:
-	/* array */
-	putchar('(');
-	u = p->gets.afn(p);
-	if(*u) {
-	    quotedzputs(*u++, stdout);
-	    while (*u) {
-		putchar(' ');
-		quotedzputs(*u++, stdout);
-	    }
-	}
-	printf(")\n");
-	break;
-    }
-}
-
 /****************************************/
 /* Named Directory Hash Table Functions */
 /****************************************/
diff --git a/Src/input.c b/Src/input.c
index 576341a7c..45c65b2bf 100644
--- a/Src/input.c
+++ b/Src/input.c
@@ -176,7 +176,7 @@ shingetline(void)
 int
 ingetc(void)
 {
-    char lastc;
+    int lastc;
 
     if (lexstop)
 	return ' ';
diff --git a/Src/params.c b/Src/params.c
index 4f7846820..69fd8a904 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -232,6 +232,11 @@ IPDEF9("manpath", &manpath, "MANPATH"),
 IPDEF9("psvar", &psvar, "PSVAR"),
 IPDEF9("watch", &watch, "WATCH"),
 
+/*TEST BEGIN*/
+#define IPDEF10(A) {NULL,A,PM_HASHED|PM_SPECIAL|PM_DONTIMPORT,BR((void *)0),SFN(hashsetfn),GFN(hashgetfn),stdunsetfn,0,NULL,NULL,NULL,0}
+IPDEF10("testhash"),
+/*TEST END*/
+
 #ifdef DYNAMIC
 IPDEF9F("module_path", &module_path, "MODULE_PATH", PM_RESTRICTED),
 #endif
@@ -247,7 +252,137 @@ static Param argvparam;
  
 /**/
 HashTable paramtab;
- 
+
+/**/
+HashTable
+newparamtable(int size, char const *name)
+{
+    HashTable ht = newhashtable(size, name, NULL);
+
+    ht->hash        = hasher;
+    ht->emptytable  = emptyhashtable;
+    ht->filltable   = NULL;
+    ht->addnode     = addhashnode;
+    ht->getnode     = gethashnode2;
+    ht->getnode2    = gethashnode2;
+    ht->removenode  = removehashnode;
+    ht->disablenode = NULL;
+    ht->enablenode  = NULL;
+    ht->freenode    = freeparamnode;
+    ht->printnode   = printparamnode;
+
+    return ht;
+}
+
+/* Copy a parameter hash table */
+
+static HashTable outtable;
+
+/**/
+static void
+scancopyparams(HashNode hn, int flags)
+{
+    /* Going into a real parameter, so always use permanent storage */
+    Param pm = (Param)hn;
+    Param tpm = (Param) zcalloc(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;
+}
+
+#define SCANPM_WANTVALS   (1<<0)
+#define SCANPM_WANTKEYS   (1<<1)
+#define SCANPM_WANTINDEX  (1<<2)
+
+static unsigned numparamvals;
+
+/**/
+static void
+scancountparams(HashNode hn, int flags)
+{
+    if (!(((Param)hn)->flags & PM_UNSET)) {
+	++numparamvals;
+	if ((flags & SCANPM_WANTKEYS) && (flags & SCANPM_WANTVALS))
+	    ++numparamvals;
+    }
+}
+
+static char **paramvals;
+
+/**/
+static void
+scanparamvals(HashNode hn, int flags)
+{
+    struct value v;
+    v.pm = (Param)hn;
+    if (!(v.pm->flags & PM_UNSET)) {
+	if (flags & SCANPM_WANTKEYS) {
+	    paramvals[numparamvals++] = v.pm->nam;
+	    if (!(flags & SCANPM_WANTVALS))
+		return;
+	}
+	v.isarr = (PM_TYPE(v.pm->flags) & (PM_ARRAY|PM_HASHED));
+	v.inv = (flags & SCANPM_WANTINDEX);
+	v.a = 0;
+	v.b = -1;
+	paramvals[numparamvals++] = getstrvalue(&v);
+    }
+}
+
+/**/
+char **
+paramvalarr(HashTable ht, unsigned flags)
+{
+    MUSTUSEHEAP("paramvalarr");
+    numparamvals = 0;
+    if (ht)
+	scanhashtable(ht, 0, 0, 0, scancountparams, flags);
+    paramvals = (char **) alloc((numparamvals + 1) * sizeof(char *));
+    if (ht) {
+	numparamvals = 0;
+	scanhashtable(ht, 0, 0, 0, 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->gets.afn(v->pm);
+    else if (PM_TYPE(v->pm->flags) == PM_HASHED) {
+	unsigned flags = 0;
+	if (v->a)
+	    flags |= SCANPM_WANTKEYS;
+	if (v->b > v->a)
+	    flags |= SCANPM_WANTVALS;
+	v->arr = paramvalarr(v->pm->gets.hfn(v->pm), flags);
+	/* Can't take numeric slices of associative arrays */
+	v->a = 0;
+	v->b = -1;
+	return v->arr;
+    } else
+	return NULL;
+}
+
 /* Set up parameter hash table.  This will add predefined  *
  * parameter entries as well as setting up parameter table *
  * entries for environment variables we inherit.           */
@@ -261,19 +396,7 @@ createparamtable(void)
     char buf[50], *str, *iname;
     int num_env;
 
-    paramtab = newhashtable(151, "paramtab", NULL);
-
-    paramtab->hash        = hasher;
-    paramtab->emptytable  = NULL;
-    paramtab->filltable   = NULL;
-    paramtab->addnode     = addhashnode;
-    paramtab->getnode     = gethashnode2;
-    paramtab->getnode2    = gethashnode2;
-    paramtab->removenode  = removehashnode;
-    paramtab->disablenode = NULL;
-    paramtab->enablenode  = NULL;
-    paramtab->freenode    = freeparamnode;
-    paramtab->printnode   = printparamnode;
+    paramtab = newparamtable(151, "paramtab");
 
     /* Add the special parameters to the hash table */
     for (ip = special_params; ip->nam; ip++)
@@ -368,6 +491,36 @@ createparamtable(void)
     noerrs = 0;
 }
 
+/* assign various functions used for non-special parameters */
+
+/**/
+static void
+assigngetset(Param pm)
+{
+    switch (PM_TYPE(pm->flags)) {
+    case PM_SCALAR:
+	pm->sets.cfn = strsetfn;
+	pm->gets.cfn = strgetfn;
+	break;
+    case PM_INTEGER:
+	pm->sets.ifn = intsetfn;
+	pm->gets.ifn = intgetfn;
+	break;
+    case PM_ARRAY:
+	pm->sets.afn = arrsetfn;
+	pm->gets.afn = arrgetfn;
+	break;
+    case PM_HASHED:
+	pm->sets.hfn = hashsetfn;
+	pm->gets.hfn = hashgetfn;
+	break;
+    default:
+	DPUTS(1, "BUG: tried to create param node without valid flag");
+	break;
+    }
+    pm->unsetfn = stdunsetfn;
+}
+
 /* 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     *
@@ -413,27 +566,51 @@ createparam(char *name, int flags)
 	pm = (Param) alloc(sizeof *pm);
     pm->flags = flags;
 
-    if(!(pm->flags & PM_SPECIAL)) {
-	switch (PM_TYPE(flags)) {
+    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).
+     */
+    PERMALLOC {
+	tpm->flags = pm->flags;
+	if (!toplevel)
+	    tpm->flags &= ~PM_SPECIAL;
+	switch (PM_TYPE(pm->flags)) {
 	case PM_SCALAR:
-	    pm->sets.cfn = strsetfn;
-	    pm->gets.cfn = strgetfn;
+	    tpm->u.str = ztrdup(pm->gets.cfn(pm));
 	    break;
 	case PM_INTEGER:
-	    pm->sets.ifn = intsetfn;
-	    pm->gets.ifn = intgetfn;
+	    tpm->u.val = pm->gets.ifn(pm);
 	    break;
 	case PM_ARRAY:
-	    pm->sets.afn = arrsetfn;
-	    pm->gets.afn = arrgetfn;
+	    tpm->u.arr = arrdup(pm->gets.afn(pm));
 	    break;
-	default:
-	    DPUTS(1, "BUG: tried to create param node without valid flag");
+	case PM_HASHED:
+	    tpm->u.hash = copyparamtable(pm->gets.hfn(pm), pm->nam);
 	    break;
 	}
-	pm->unsetfn = stdunsetfn;
-    }
-    return pm;
+    } LASTALLOC;
+    /*
+     * 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. */
@@ -577,6 +754,23 @@ getarg(char **str, int *inv, Value v, int a2, long *w)
     singsub(&s);
 
     if (!rev) {
+	if (PM_TYPE(v->pm->flags) == PM_HASHED) {
+	    HashTable ht = v->pm->gets.hfn(v->pm);
+	    if (!ht) {
+		ht = newparamtable(17, v->pm->nam);
+		v->pm->sets.hfn(v->pm, ht);
+	    }
+	    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 = 0;
+	    v->a = 0;
+	    *w = v->b = -1;
+	    r = isset(KSHARRAYS) ? 1 : 0;
+	} else
 	if (!(r = mathevalarg(s, &s)) || (isset(KSHARRAYS) && r >= 0))
 	    r++;
 	if (word && !v->isarr) {
@@ -799,11 +993,12 @@ getvalue(char **pptr, int bracks)
     s = t = *pptr;
     garr = NULL;
 
-    if (idigit(*s))
+    if (idigit(*s)) {
 	if (bracks >= 0)
 	    ppar = zstrtol(s, &s, 10);
 	else
 	    ppar = *s++ - '0';
+    }
     else if (iident(*s))
 	while (iident(*s))
 	    s++;
@@ -844,7 +1039,7 @@ getvalue(char **pptr, int bracks)
 	if (!pm || (pm->flags & PM_UNSET))
 	    return NULL;
 	v = (Value) hcalloc(sizeof *v);
-	if (PM_TYPE(pm->flags) == PM_ARRAY)
+	if (PM_TYPE(pm->flags) & (PM_ARRAY|PM_HASHED))
 	    v->isarr = isvarat ? -1 : 1;
 	v->pm = pm;
 	v->inv = 0;
@@ -863,12 +1058,12 @@ getvalue(char **pptr, int bracks)
     *pptr = s;
     if (v->a > MAX_ARRLEN ||
 	v->a < -MAX_ARRLEN) {
-	zerr("subscript to %s: %d", (v->a < 0) ? "small" : "big", v->a);
+	zerr("subscript too %s: %d", (v->a < 0) ? "small" : "big", v->a);
 	return NULL;
     }
     if (v->b > MAX_ARRLEN ||
 	v->b < -MAX_ARRLEN) {
-	zerr("subscript to %s: %d", (v->b < 0) ? "small" : "big", v->b);
+	zerr("subscript too %s: %d", (v->b < 0) ? "small" : "big", v->b);
 	return NULL;
     }
     return v;
@@ -891,11 +1086,12 @@ getstrvalue(Value v)
 	}
 
 	switch(PM_TYPE(v->pm->flags)) {
+	case PM_HASHED:
 	case PM_ARRAY:
+	    ss = getvaluearr(v);
 	    if (v->isarr)
-		s = sepjoin(v->pm->gets.afn(v->pm), NULL);
+		s = sepjoin(ss, NULL);
 	    else {
-		ss = v->pm->gets.afn(v->pm);
 		if (v->a < 0)
 		    v->a += arrlen(ss);
 		s = (v->a >= arrlen(ss) || v->a < 0) ? (char *) hcalloc(1) : ss[v->a];
@@ -913,8 +1109,9 @@ getstrvalue(Value v)
 	    break;
 	}
 
-	if (v->a == 0 && v->b == -1)
+	if (v->a == 0 && v->b == -1) {
 	    LASTALLOC_RETURN s;
+	}
 	if (v->a < 0)
 	    v->a += strlen(s);
 	if (v->b < 0)
@@ -946,7 +1143,7 @@ getarrvalue(Value v)
 	s[0] = dupstring(buf);
 	return s;
     }
-    s = v->pm->gets.afn(v->pm);
+    s = getvaluearr(v);
     if (v->a == 0 && v->b == -1)
 	return s;
     if (v->a < 0)
@@ -993,6 +1190,7 @@ setstrvalue(Value v, char *val)
 	zsfree(val);
 	return;
     }
+    v->pm->flags &= ~PM_UNSET;
     switch (PM_TYPE(v->pm->flags)) {
     case PM_SCALAR:
 	MUSTUSEHEAP("setstrvalue");
@@ -1103,17 +1301,25 @@ setarrvalue(Value v, char **val)
 	freearray(val);
 	return;
     }
-    if (PM_TYPE(v->pm->flags) != PM_ARRAY) {
+    if (PM_TYPE(v->pm->flags) & ~(PM_ARRAY|PM_HASHED)) {
 	freearray(val);
 	zerr("attempt to assign array value to non-array", NULL, 0);
 	return;
     }
-    if (v->a == 0 && v->b == -1)
-	(v->pm->sets.afn) (v->pm, val);
-    else {
+    if (v->a == 0 && v->b == -1) {
+	if (PM_TYPE(v->pm->flags) == PM_HASHED)
+	    arrhashsetfn(v->pm, val);
+	else
+	    (v->pm->sets.afn) (v->pm, val);
+    } else {
 	char **old, **new, **p, **q, **r;
 	int n, ll, i;
 
+	if ((PM_TYPE(v->pm->flags) == PM_HASHED)) {
+	    freearray(val);
+	    zerr("attempt to set slice of associative array", NULL, 0);
+	    return;
+	}
 	if (v->inv && unset(KSHARRAYS))
 	    v->a--, v->b--;
 	q = old = v->pm->gets.afn(v->pm);
@@ -1187,6 +1393,20 @@ getaparam(char *s)
     return NULL;
 }
 
+/* Retrieve an assoc array parameter as an array */
+
+/**/
+char **
+gethparam(char *s)
+{
+    Value v;
+
+    if (!idigit(*s) && (v = getvalue(&s, 0)) &&
+	PM_TYPE(v->pm->flags) == PM_HASHED)
+	return paramvalarr(v->pm->gets.hfn(v->pm), SCANPM_WANTVALS);
+    return NULL;
+}
+
 /**/
 Param
 setsparam(char *s, char *val)
@@ -1248,7 +1468,7 @@ setaparam(char *s, char **val)
     } else {
 	if (!(v = getvalue(&s, 1)))
 	    createparam(t, PM_ARRAY);
-	else if (PM_TYPE(v->pm->flags) != PM_ARRAY &&
+	else if (!(PM_TYPE(v->pm->flags) & (PM_ARRAY|PM_HASHED)) &&
 		 !(v->pm->flags & PM_SPECIAL)) {
 	    int uniq = v->pm->flags & PM_UNIQUE;
 	    unsetparam(t);
@@ -1338,7 +1558,7 @@ unsetparam_pm(Param pm, int altflag, int exp)
      * 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".   */
-    if (locallevel >= pm->level)
+    if ((locallevel && locallevel >= pm->level) || (pm->flags & PM_SPECIAL))
 	return;
 
     paramtab->removenode(paramtab, pm->nam); /* remove parameter node from table */
@@ -1363,6 +1583,7 @@ stdunsetfn(Param pm, int exp)
     switch (PM_TYPE(pm->flags)) {
 	case PM_SCALAR: pm->sets.cfn(pm, NULL); break;
 	case PM_ARRAY:  pm->sets.afn(pm, NULL); break;
+        case PM_HASHED: pm->sets.hfn(pm, NULL); break;
     }
     pm->flags |= PM_UNSET;
 }
@@ -1429,6 +1650,70 @@ arrsetfn(Param pm, char **x)
     pm->u.arr = x;
 }
 
+/* Function to get value of an association parameter */
+
+/**/
+static HashTable
+hashgetfn(Param pm)
+{
+    return pm->u.hash;
+}
+
+/* Flag to freeparamnode to unset the struct */
+
+static int delunset;
+
+/* Function to set value of an association parameter */
+
+/**/
+static void
+hashsetfn(Param pm, HashTable x)
+{
+    if (pm->u.hash && pm->u.hash != x) {
+	/* The parameters in the hash table need to be unset *
+	 * before being deleted.                             */
+	int odelunset = delunset;
+	delunset = 1;
+	deletehashtable(pm->u.hash);
+	delunset = odelunset;
+    }
+    pm->u.hash = x;
+}
+
+/* Function to set value of an association parameter using key/value pairs */
+
+/**/
+static void
+arrhashsetfn(Param pm, char **val)
+{
+    /* 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->sets.hfn() what to do.                         */
+    int alen = arrlen(val);
+    HashTable opmtab = paramtab, ht;
+    char **aptr = val;
+    Value v = (Value) hcalloc(sizeof *v);
+    v->b = -1;
+
+    if (alen % 2) {
+	freearray(val);
+	zerr("bad set of key/value pairs for associative array\n",
+	     NULL, 0);
+	return;
+    }
+    ht = paramtab = newparamtable(17, pm->nam);
+    while (*aptr) {
+	/* The parameter name is ztrdup'd... */
+	v->pm = createparam(*aptr, PM_SCALAR|PM_UNSET);
+	zsfree(*aptr++);
+	/* ...but we can use the value without copying. */
+	setstrvalue(v, *aptr++);
+    }
+    paramtab = opmtab;
+    pm->sets.hfn(pm, ht);
+    free(val);		/* not freearray() */
+}
+
 /* This function is used as the set function for      *
  * special parameters that cannot be set by the user. */
 
@@ -2189,3 +2474,107 @@ scanendscope(HashNode hn, int flags)
     if(pm->level > locallevel)
 	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->unsetfn(pm, 1);
+    zsfree(pm->nam);
+    zfree(pm, sizeof(struct param));
+}
+
+/* Print a parameter */
+
+/**/
+void
+printparamnode(HashNode hn, int printflags)
+{
+    Param p = (Param) hn;
+    char *t, **u;
+
+    if (p->flags & PM_UNSET)
+	return;
+
+    /* Print the attributes of the parameter */
+    if (printflags & PRINT_TYPE) {
+	if (p->flags & PM_INTEGER)
+	    printf("integer ");
+	else if (p->flags & PM_ARRAY)
+	    printf("array ");
+	else if (p->flags & PM_HASHED)
+	    printf("association ");
+	if (p->flags & PM_LEFT)
+	    printf("left justified %d ", p->ct);
+	if (p->flags & PM_RIGHT_B)
+	    printf("right justified %d ", p->ct);
+	if (p->flags & PM_RIGHT_Z)
+	    printf("zero filled %d ", p->ct);
+	if (p->flags & PM_LOWER)
+	    printf("lowercase ");
+	if (p->flags & PM_UPPER)
+	    printf("uppercase ");
+	if (p->flags & PM_READONLY)
+	    printf("readonly ");
+	if (p->flags & PM_TAGGED)
+	    printf("tagged ");
+	if (p->flags & PM_EXPORTED)
+	    printf("exported ");
+    }
+
+    if (printflags & PRINT_NAMEONLY) {
+	zputs(p->nam, stdout);
+	putchar('\n');
+	return;
+    }
+
+    /* How the value is displayed depends *
+     * on the type of the parameter       */
+    quotedzputs(p->nam, stdout);
+    putchar('=');
+    switch (PM_TYPE(p->flags)) {
+    case PM_SCALAR:
+	/* string: simple output */
+	if (p->gets.cfn && (t = p->gets.cfn(p)))
+	    quotedzputs(t, stdout);
+	putchar('\n');
+	break;
+    case PM_INTEGER:
+	/* integer */
+	printf("%ld\n", p->gets.ifn(p));
+	break;
+    case PM_ARRAY:
+	/* array */
+	putchar('(');
+	u = p->gets.afn(p);
+	if(*u) {
+	    quotedzputs(*u++, stdout);
+	    while (*u) {
+		putchar(' ');
+		quotedzputs(*u++, stdout);
+	    }
+	}
+	printf(")\n");
+	break;
+    case PM_HASHED:
+	/* association */
+	putchar('(');
+	{
+            HashTable ht = p->gets.hfn(p);
+            if (ht)
+		scanhashtable(ht, 0, 0, 0, ht->printnode, 0);
+	}
+	printf(")\n");
+	break;
+    }
+}
diff --git a/Src/prompt.c b/Src/prompt.c
index 8c9216f95..69823a5e3 100644
--- a/Src/prompt.c
+++ b/Src/prompt.c
@@ -71,6 +71,10 @@ static int bufspc;
 
 static char *bp;
 
+/* Position of the start of the current line in the buffer */
+
+static char *bufline;
+
 /* bp1 is an auxilliary pointer into the buffer, which when non-NULL is *
  * moved whenever the buffer is reallocated.  It is used when data is   *
  * being temporarily held in the buffer.                                */
@@ -81,11 +85,9 @@ static char *bp1;
 
 static char *fm;
 
-/* Current truncation string (metafied), the length at which truncation *
- * occurs, and the direction in which it occurs.                        */
+/* Non-zero if truncating the current segment of the buffer. */
 
-static char *truncstr;
-static int trunclen, truncatleft;
+static int trunclen;
 
 /* Current level of nesting of %{ / %} sequences. */
 
@@ -95,10 +97,6 @@ static int dontcount;
 
 static char *rstring, *Rstring;
 
-/* If non-zero, Inpar, Outpar and Nularg can be added to the buffer. */
-
-static int nonsp;
-
 /* Perform prompt expansion on a string, putting the result in a *
  * permanently-allocated string.  If ns is non-zero, this string *
  * may have embedded Inpar and Outpar, which indicate a toggling *
@@ -130,9 +128,8 @@ promptexpand(char *s, int ns, char *rs, char *Rs)
 
     rstring = rs;
     Rstring = Rs;
-    nonsp = ns;
     fm = s;
-    bp = buf = zalloc(bufspc = 256);
+    bp = bufline = buf = zalloc(bufspc = 256);
     bp1 = NULL;
     trunclen = 0;
     putpromptchar(1, '\0');
@@ -140,6 +137,15 @@ promptexpand(char *s, int ns, char *rs, char *Rs)
     if(dontcount)
 	*bp++ = Outpar;
     *bp = 0;
+    if (!ns) {
+	/* If zero, Inpar, Outpar and Nularg should be removed. */
+	for (bp = buf; *bp; bp++) {
+	    if (*bp == Meta)
+		bp++;
+	    else if (*bp == Inpar || *bp == Outpar || *bp == Nularg)
+		chuck(bp);
+	}
+    }
     return buf;
 }
 
@@ -164,7 +170,7 @@ putpromptchar(int doprint, int endchar)
 		arg = zstrtol(fm, &fm, 10);
 	    }
 	    if (*fm == '(') {
-		int tc;
+		int tc, otrunclen;
 
 		if (idigit(*++fm)) {
 		    arg = zstrtol(fm, &fm, 10);
@@ -224,6 +230,12 @@ putpromptchar(int doprint, int endchar)
 		    if (getegid() == arg)
 			test = 1;
 		    break;
+		case 'l':
+		    *bp = '\0';
+		    countprompt(bufline, &t0, 0);
+		    if (t0 >= arg)
+			test = 1;
+		    break;
 		case 'L':
 		    if (shlvl >= arg)
 			test = 1;
@@ -249,10 +261,15 @@ putpromptchar(int doprint, int endchar)
 		if (!*fm || !(sep = *++fm))
 		    return 0;
 		fm++;
+		/* Don't do the current truncation until we get back */
+		otrunclen = trunclen;
+		trunclen = 0;
 		if (!putpromptchar(test == 1 && doprint, sep) || !*++fm ||
 		    !putpromptchar(test == 0 && doprint, ')')) {
+		    trunclen = otrunclen;
 		    return 0;
 		}
+		trunclen = otrunclen;
 		continue;
 	    }
 	    if (!doprint)
@@ -377,72 +394,24 @@ putpromptchar(int doprint, int endchar)
 		tsetcap(TCUNDERLINEEND, 1);
 		break;
 	    case '[':
-                if (idigit(*++fm))
-                    trunclen = zstrtol(fm, &fm, 10);
-                else
-                    trunclen = arg;
-                if (trunclen) {
-		    truncatleft = *fm && *fm != ']' && *fm++ == '<';
-		    bp1 = bp;
-		    while (*fm && *fm != ']') {
-			if (*fm == '\\' && fm[1])
-			    ++fm;
-			addbufspc(1);
-			*bp++ = *fm++;
-		    }
-		    addbufspc(2);
-		    if (bp1 == bp)
-			*bp++ = '<';
-                    *bp = '\0';
-		    zsfree(truncstr);
-                    truncstr = ztrdup(bp = bp1);
-		    bp1 = NULL;
-                } else {
-		    while (*fm && *fm != ']') {
-			if (*fm == '\\' && fm[1])
-			    fm++;
-			fm++;
-		    }
-		}
-		if(!*fm)
-		    return 0;
+		if (idigit(*++fm))
+		    arg = zstrtol(fm, &fm, 10);
+		if (!prompttrunc(arg, ']', doprint, endchar))
+		    return *fm;
 		break;
 	    case '<':
 	    case '>':
-		if((trunclen = arg)) {
-		    char ch = *fm++;
-		    truncatleft = ch == '<';
-		    bp1 = bp;
-		    while (*fm && *fm != ch) {
-			if (*fm == '\\' && fm[1])
-			    ++fm;
-			addbufspc(1);
-			*bp++ = *fm++;
-		    }
-		    addbufspc(1);
-                    *bp = '\0';
-		    zsfree(truncstr);
-                    truncstr = ztrdup(bp = bp1);
-		    bp1 = NULL;
-		} else {
-		    char ch = *fm++;
-		    while(*fm && *fm != ch) {
-			if (*fm == '\\' && fm[1])
-			    fm++;
-			fm++;
-		    }
-		}
-		if(!*fm)
-		    return 0;
+		if (!prompttrunc(arg, *fm, doprint, endchar))
+		    return *fm;
 		break;
 	    case '{': /*}*/
-		if (!dontcount++ && nonsp) {
+		if (!dontcount++) {
 		    addbufspc(1);
 		    *bp++ = Inpar;
 		}
 		break;
 	    case /*{*/ '}':
-		if (dontcount && !--dontcount && nonsp) {
+		if (dontcount && !--dontcount) {
 		    addbufspc(1);
 		    *bp++ = Outpar;
 		}
@@ -569,7 +538,7 @@ putpromptchar(int doprint, int endchar)
 		break;
 	    }
 	} else if(*fm == '!' && isset(PROMPTBANG)) {
-	    if(doprint)
+	    if(doprint) {
 		if(fm[1] == '!') {
 		    fm++;
 		    addbufspc(1);
@@ -579,6 +548,7 @@ putpromptchar(int doprint, int endchar)
 		    sprintf(bp, "%d", curhist);
 		    bp += strlen(bp);
 		}
+	    }
 	} else {
 	    char c = *fm == Meta ? *++fm ^ 32 : *fm;
 
@@ -604,6 +574,8 @@ pputc(char c)
 	c ^= 32;
     }
     *bp++ = c;
+    if (c == '\n' && !dontcount)
+	bufline = bp;
 }
 
 /* Make sure there is room for `need' more characters in the buffer. */
@@ -627,46 +599,19 @@ addbufspc(int need)
 }
 
 /* stradd() adds a metafied string to the prompt, *
- * in a visible representation, doing truncation. */
+ * in a visible representation.                   */
 
 /**/
 void
 stradd(char *d)
 {
-    /* dlen is the full length of the string we want to add */
-    int dlen = niceztrlen(d);
-    char *ps, *pd, *pc, *t;
-    int tlen, maxlen;
-    addbufspc(dlen);
+    char *ps, *pc;
+    addbufspc(niceztrlen(d));
     /* This loop puts the nice representation of the string into the prompt *
-     * buffer.  It might be modified later.  Note that bp isn't changed.    */
-    for(ps=d, pd=bp; *ps; ps++)
+     * buffer.                                                              */
+    for(ps=d; *ps; ps++)
 	for(pc=nicechar(*ps == Meta ? STOUC(*++ps)^32 : STOUC(*ps)); *pc; pc++)
-	    *pd++ = *pc;
-    if(!trunclen || dlen <= trunclen) {
-	/* No truncation is needed, so update bp and return, *
-	 * leaving the full string in the prompt.            */
-	bp += dlen;
-	return;
-    }
-    /* We need to truncate.  t points to the truncation string -- which is *
-     * inserted literally, without nice representation.  tlen is its       *
-     * length, and maxlen is the amout of the main string that we want to  *
-     * keep.  Note that if the truncation string is longer than the        *
-     * truncation length (tlen > trunclen), the truncation string is used  *
-     * in full.                                                            */
-    addbufspc(tlen = ztrlen(t = truncstr));
-    maxlen = tlen < trunclen ? trunclen - tlen : 0;
-    if(truncatleft) {
-	memmove(bp + strlen(t), bp + dlen - maxlen, maxlen);
-	while(*t)
-	    *bp++ = *t++;
-	bp += maxlen;
-    } else {
-	bp += maxlen;
-	while(*t)
-	    *bp++ = *t++;
-    }
+	    *bp++ = *pc;
 }
 
 /* tsetcap(), among other things, can write a termcap string into the buffer. */
@@ -684,12 +629,12 @@ tsetcap(int cap, int flag)
 	    tputs(tcstr[cap], 1, putshout);
 	    break;
 	case 1:
-	    if (!dontcount && nonsp) {
+	    if (!dontcount) {
 		addbufspc(1);
 		*bp++ = Inpar;
 	    }
 	    tputs(tcstr[cap], 1, putstr);
-	    if (!dontcount && nonsp) {
+	    if (!dontcount) {
 		int glitch = 0;
 
 		if (cap == TCSTANDOUTBEG || cap == TCSTANDOUTEND)
@@ -764,3 +709,108 @@ countprompt(char *str, int *wp, int *hp)
     if(hp)
 	*hp = h;
 }
+
+/**/
+static int
+prompttrunc(int arg, int truncchar, int doprint, int endchar)
+{
+    if (arg) {
+	char ch = *fm, *ptr = bp, *truncstr;
+	int truncatleft = ch == '<';
+
+	/*
+	 * If there is already a truncation active, return so that
+	 * can be finished, backing up so that the new truncation
+	 * can be started afterwards.
+	 */
+	if (trunclen) {
+	    while (*--fm != '%')
+		;
+	    fm--;
+	    return 0;
+	}
+
+	trunclen = arg;
+	if (*fm != ']')
+	    fm++;
+	while (*fm && *fm != truncchar) {
+	    if (*fm == '\\' && fm[1])
+		++fm;
+	    addbufspc(1);
+	    *bp++ = *fm++;
+	}
+	if (!*fm)
+	    return 0;
+	if (bp == ptr && truncchar == ']') {
+	    addbufspc(1);
+	    *bp++ = '<';
+	}
+	truncstr = ztrduppfx(ptr, bp - ptr);
+
+	bp = ptr;
+	fm++;
+	putpromptchar(doprint, endchar);
+	*bp = '\0';
+	if (bp - ptr > trunclen) {
+	    /*
+	     * We need to truncate.  t points to the truncation string -- *
+	     * which is inserted literally, without nice representation.  *
+	     * tlen is its length, and maxlen is the amount of the main	  *
+	     * string that we want to keep.  Note that if the truncation  *
+	     * string is longer than the truncation length (tlen >	  *
+	     * trunclen), the truncation string is used in full.	  *
+	     */
+	    char *t = truncstr;
+	    int fullen = bp - ptr;
+	    int tlen = ztrlen(t), maxlen;
+	    if (tlen > fullen) {
+		addbufspc(tlen - fullen);
+		bp += tlen - fullen;
+	    } else
+		bp -= fullen - trunclen;
+	    maxlen = tlen < trunclen ? trunclen - tlen : 0;
+	    if (truncatleft) {
+		if (maxlen)
+		    memmove(ptr + strlen(t), ptr + fullen - maxlen,
+			    maxlen);
+		while (*t)
+		    *ptr++ = *t++;
+	    } else {
+		ptr += maxlen;
+		while (*t)
+		    *ptr++ = *t++;
+	    }
+	}
+	zsfree(truncstr);
+	trunclen = 0;
+	/*
+	 * We may have returned early from the previous putpromptchar *
+	 * because we found another truncation following this one.    *
+	 * In that case we need to do the rest now.                   *
+	 */
+	if (!*fm)
+	    return 0;
+	if (*fm != endchar) {
+	    fm++;
+	    /*
+	     * With trunclen set to zero, we always reach endchar *
+	     * (or the terminating NULL) this time round.         *
+	     */
+	    if (!putpromptchar(doprint, endchar))
+		return 0;
+	    /* Now we have to trick it into matching endchar again */
+	    fm--;
+	}
+    } else {
+	if (*fm != ']')
+	    fm++;
+	while(*fm && *fm != truncchar) {
+	    if (*fm == '\\' && fm[1])
+		fm++;
+	    fm++;
+	}
+	if (trunclen || !*fm)
+	    return 0;
+    }
+    return 1;
+}
diff --git a/Src/subst.c b/Src/subst.c
index 8f840d266..cc1ae3027 100644
--- a/Src/subst.c
+++ b/Src/subst.c
@@ -716,6 +716,8 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
     int eval = 0;
     int nojoin = 0;
     char inbrace = 0;		/* != 0 means ${...}, otherwise $... */
+    char hkeys = 0;		/* 1 means get keys from associative array */
+    char hvals = 1;		/* > hkeys get values of associative array */
 
     *s++ = '\0';
     if (!ialnum(*s) && *s != '#' && *s != Pound && *s != '-' &&
@@ -732,12 +734,16 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
     if (*s == Inbrace) {
 	inbrace = 1;
 	s++;
-	if (*s == '(' || *s == Inpar) {
+	if (*s == '!' && s[1] != Outbrace && emulation == EMULATE_KSH) {
+	    hkeys = 1;
+	    s++;
+	} else if (*s == '(' || *s == Inpar) {
 	    char *t, sav;
 	    int tt = 0;
 	    long num;
 	    int escapes = 0;
 	    int klen;
+#define UNTOK(C)  (itok(C) ? ztokens[(C) - Pound] : (C))
 #define UNTOK_AND_ESCAPE(X) {\
 		untokenize(X = dupstring(s + 1));\
 		if (escapes) {\
@@ -851,7 +857,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
 			prenum = num;
 		    else
 			postnum = num;
-		    if (s[1] != sav)
+		    if (UNTOK(s[1]) != UNTOK(sav))
 			break;
 		    t = get_strarg(++s);
 		    if (!*t)
@@ -865,7 +871,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
 		    *t = sav;
 		    sav = *s;
 		    s = t + 1;
-		    if (*s != sav) {
+		    if (UNTOK(*s) != UNTOK(sav)) {
 			s--;
 			break;
 		    }
@@ -886,6 +892,13 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
 		    escapes = 1;
 		    break;
 
+		case 'k':
+		    hkeys = 1;
+		    break;
+		case 'v':
+		    hvals = 2;
+		    break;
+
 		default:
 		  flagerr:
 		    zerr("error in flags", NULL, 0);
@@ -986,9 +999,16 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
 	    if (getindex(&s, v) || s == os)
 		break;
 	}
-	if ((isarr = v->isarr))
+	if ((isarr = v->isarr)) {
+	    /* No way to reach here with v->inv != 0, so getvaluearr() *
+	     * will definitely be called by getarrvalue().  Slicing of *
+	     * associations isn't done, so use v->a and v->b for flags */
+	    if (PM_TYPE(v->pm->flags) == PM_HASHED) {
+		v->a = hkeys;
+		v->b = hvals;
+	    }
 	    aval = getarrvalue(v);
-	else {
+	} else {
 	    if (v->pm->flags & PM_ARRAY) {
 		int tmplen = arrlen(v->pm->gets.afn(v->pm));
 
diff --git a/Src/text.c b/Src/text.c
index b7df8012f..836a6a0a8 100644
--- a/Src/text.c
+++ b/Src/text.c
@@ -450,6 +450,8 @@ getsimptext(Cmd cmd)
 	    taddchr('(');
 	    taddlist(v->arr);
 	    taddstr(") ");
+	} else if (PM_TYPE(v->type) == PM_HASHED) {
+	    /* XXX */
 	} else {
 	    taddstr(v->str);
 	    taddchr(' ');
diff --git a/Src/utils.c b/Src/utils.c
index 87f82f7df..44223867f 100644
--- a/Src/utils.c
+++ b/Src/utils.c
@@ -510,8 +510,8 @@ adduserdir(char *s, char *t, int flags, int always)
     if ((flags & ND_USERNAME) && nameddirtab->getnode2(nameddirtab, s))
 	return;
 
-    /* Never hash PWD, because it's never useful */
-    if (!strcmp(s, "PWD"))
+    /* Never hash PWD unless it was explicitly requested */
+    if (!always && !strcmp(s, "PWD"))
 	return;
 
     /* Normal parameter assignments generate calls to this function, *
diff --git a/Src/zsh.export b/Src/zsh.export
index 8f676c7fd..701aeb990 100644
--- a/Src/zsh.export
+++ b/Src/zsh.export
@@ -59,6 +59,7 @@ freeheap
 getaparam
 gethashnode
 gethashnode2
+gethparam
 getiparam
 getkeystring
 getlinknode
diff --git a/Src/zsh.h b/Src/zsh.h
index d833278c9..837a76e88 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -538,6 +538,7 @@ struct value {
     int inv;		/* should we return the index ?        */
     int a;		/* first element of array slice, or -1 */
     int b;		/* last element of array slice, or -1  */
+    char **arr;		/* cache for hash turned into array */
 };
 
 /* structure for foo=bar assignments */
@@ -813,6 +814,7 @@ struct param {
 	char **arr;		/* value if declared array   (PM_ARRAY)   */
 	char *str;		/* value if declared string  (PM_SCALAR)  */
 	long val;		/* value if declared integer (PM_INTEGER) */
+        HashTable hash;		/* value if declared assoc   (PM_HASHED)  */
     } u;
 
     /* pointer to function to set value of this parameter */
@@ -820,6 +822,7 @@ struct param {
 	void (*cfn) _((Param, char *));
 	void (*ifn) _((Param, long));
 	void (*afn) _((Param, char **));
+        void (*hfn) _((Param, HashTable));
     } sets;
 
     /* pointer to function to get value of this parameter */
@@ -827,6 +830,7 @@ struct param {
 	char *(*cfn) _((Param));
 	long (*ifn) _((Param));
 	char **(*afn) _((Param));
+        HashTable (*hfn) _((Param));
     } gets;
 
     /* pointer to function to unset this parameter */
@@ -845,8 +849,9 @@ struct param {
 #define PM_SCALAR	0	/* scalar                                     */
 #define PM_ARRAY	(1<<0)	/* array                                      */
 #define PM_INTEGER	(1<<1)	/* integer                                    */
+#define PM_HASHED	(1<<15)	/* association                                */
 
-#define PM_TYPE(X) (X & (PM_SCALAR|PM_INTEGER|PM_ARRAY))
+#define PM_TYPE(X) (X & (PM_SCALAR|PM_INTEGER|PM_ARRAY|PM_HASHED))
 
 #define PM_LEFT		(1<<2)	/* left justify and remove leading blanks     */
 #define PM_RIGHT_B	(1<<3)	/* right justify and fill with leading blanks */