about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Doc/Makefile.in6
-rw-r--r--Doc/Zsh/builtins.yo62
-rw-r--r--Doc/Zsh/expn.yo8
-rw-r--r--Doc/Zsh/options.yo2
-rw-r--r--Doc/Zsh/prompt.yo31
-rwxr-xr-xFunctions/multicomp37
-rwxr-xr-xFunctions/randline2
-rw-r--r--Makefile.in8
-rwxr-xr-xMisc/c2z10
-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
-rwxr-xr-xconfig.guess6
-rwxr-xr-xconfig.sub2
-rw-r--r--patchlist.txt85
27 files changed, 852 insertions, 375 deletions
diff --git a/Doc/Makefile.in b/Doc/Makefile.in
index 1883abfd1..1f8fa5ff9 100644
--- a/Doc/Makefile.in
+++ b/Doc/Makefile.in
@@ -139,9 +139,13 @@ install.info: zsh.info
 	  elif test -f $(sdir)/$$file; then \
 	    $(INSTALL_DATA) $(sdir)/$$file $(infodir); \
 	  else :; \
-	  fi || exit 1; \
+	   fi || exit 1; \
 	done
 
+install.html: zsh_toc.html
+	$(sdir_top)/mkinstalldirs $(htmldir)
+	$(INSTALL_DATA) *.html $(htmldir)
+
 # uninstall man pages
 uninstall.man:
 	for file in $(MAN); do \
diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo
index 8959543c5..1bbc9c36a 100644
--- a/Doc/Zsh/builtins.yo
+++ b/Doc/Zsh/builtins.yo
@@ -98,12 +98,13 @@ Change the current directory.  In the first form, change the
 current directory to var(arg), or to the value of tt($HOME) if
 var(arg) is not specified.  If var(arg) is `tt(-)', change to the
 value of tt($OLDPWD), the previous directory.
-If a directory named var(arg) is not found in the current directory
-and var(arg) does not begin with a slash,
-search each component of the shell parameter tt(cdpath).
-If the option tt(CDABLE_VARS) is set, and a parameter named var(arg)
-exists whose value begins with a slash, treat its value as
-the directory.
+Otherwise, if a directory named var(arg) is not found in the current
+directory and var(arg) does not begin with a slash, search each
+component of the shell parameter tt(cdpath).  If no directory is found
+and the option tt(CDABLE_VARS) is set, and a parameter named var(arg)
+exists whose value begins with a slash, treat its value as the
+directory.  In that case, the parameter is added to the named
+directory hash table.
 
 The second form of tt(cd) substitutes the string var(new)
 for the string var(old) in the name of the current directory,
@@ -357,11 +358,20 @@ is stored in tt(OPTARG).
 vindex(OPTIND, use of)
 vindex(OPTARG, use of)
 
+The first option to be examined may be changed by explicitly assigning
+to tt(OPTIND).  tt(OPTIND) has an initial value of tt(1), and is
+normally reset to tt(1) upon exit from a shell function.  tt(OPTARG)
+is not reset and retains its value from the most recent call to
+tt(getopts).  If either of tt(OPTIND) or tt(OPTARG) is explicitly
+unset, it remains unset, and the index or option argument is not
+stored.  The option itself is still stored in var(name) in this case.
+
 A leading `tt(:)' in var(optstring) causes tt(getopts) to store the
-letter of the invalid option in tt(OPTARG), and to set var(name)
-to `tt(?)' for an unknown option and to `tt(:)' when a required option
-is missing.  Otherwise, tt(getopts) prints an error
-message.  The exit status is nonzero when there are no more options.
+letter of any invalid option in tt(OPTARG), and to set var(name) to
+`tt(?)' for an unknown option and to `tt(:)' when a required option is
+missing.  Otherwise, tt(getopts) sets var(name) to `tt(?)' and prints
+an error message when an option is invalid.  The exit status is
+nonzero when there are no more options.
 )
 findex(hash)
 item(tt(hash) [ tt(-dfmrv) ] [ var(name)[tt(=)var(value)] ] ...)(
@@ -521,8 +531,8 @@ Same as tt(exit), except that it only works in a login shell.
 prefix(noglob)
 findex(popd)
 item(tt(popd) [ {tt(PLUS())|tt(-)}var(n) ])(
-Removes a entry from the directory stack, and perform a tt(cd) to
-the new top directory. With no argument, the current top entry is
+Remove an entry from the directory stack, and perform a tt(cd) to
+the new top directory.  With no argument, the current top entry is
 removed.  An argument of the form `tt(PLUS())var(n)' identifies a stack
 entry by counting from the left of the list shown by the tt(dirs) command,
 starting with zero.  An argument of the form tt(-n) counts from the right.
@@ -615,21 +625,9 @@ If var(arg) is not specified, change to the second directory
 on the stack (that is, exchange the top two entries), or
 change to tt($HOME) if the tt(PUSHD_TO_HOME)
 option is set or if there is only one entry on the stack.
-
-If var(arg) is `tt(-)', change to tt($OLDPWD), the previous directory.
-If a directory named var(arg) is not found in the current directory
-and var(arg) does not contain a slash,
-search each component of the shell parameter tt(cdpath).
-If the option tt(CDABLE_VARS) is set, and a parameter named var(arg)
-exists whose value begins with a slash, treat its value as
-the directory.
-
-If the option tt(PUSHD_SILENT) is not set, the directory
-stack will be printed after a tt(pushd) is performed.
-
-The second form of tt(pushd) substitutes the string var(new)
-for the string var(old) in the name of the current directory,
-and tries to change to this new directory.
+Otherwise, var(arg) is interpreted as it would be by tt(cd).
+The meaning of var(old) and var(new) in the second form is also
+the same as for tt(cd).
 
 The third form of tt(pushd) changes directory by rotating the
 directory list.  An argument of the form `tt(PLUS())var(n)' identifies a stack
@@ -637,6 +635,9 @@ entry by counting from the left of the list shown by the tt(dirs)
 command, starting with zero.  An argument of the form `tt(-)var(n)' counts
 from the right.  If the tt(PUSHD_MINUS) option is set, the meanings
 of `tt(PLUS())' and `tt(-)' in this context are swapped.
+
+If the option tt(PUSHD_SILENT) is not set, the directory
+stack will be printed after a tt(pushd) is performed.
 )
 findex(pushln)
 item(tt(pushln) [ var(arg) ... ])(
@@ -727,7 +728,8 @@ is interactive.
 
 The value (exit status) of tt(read) is 1 when an end-of-file is
 encountered, or when tt(-c) or tt(-l) is present and the command is
-not called from a tt(compctl) function.  Otherwise the value is 0.
+not called from a tt(compctl) function, or as described for tt(-q).
+Otherwise the value is 0.
 
 The behavior of some combinations of the tt(-k), tt(-p), tt(-q), tt(-u)
 and tt(-z) flags is undefined.  Presently tt(-q) cancels all the others,
@@ -835,7 +837,7 @@ var(arg) is a command to be read and executed when the shell
 receives var(sig).  Each var(sig) can be given as a number
 or as the name of a signal.
 If var(arg) is `tt(-)', then all traps var(sig) are reset to their
-default values.  If var(arg) is the null string, then this signal
+default values.  If var(arg) is the empty string, then this signal
 is ignored by the shell and by the commands it invokes.
 
 If var(sig) is tt(ZERR) then var(arg) will be executed
@@ -958,7 +960,7 @@ or functions (with the tt(-f) flag) with matching names are printed.
 findex(ulimit)
 cindex(resource limits)
 cindex(limits, resource)
-item(tt(ulimit) [ tt(-SHacdflmnpstv) [ tt(limit) ] ... ])(
+item(tt(ulimit) [ tt(-SHacdflmnpstv) [ var(limit) ] ... ])(
 Set or display resource limits of the shell and the processes started by
 the shell.  The value of var(limit) can be a number in the unit specified
 below or the value `tt(unlimited)'.  If the tt(-H) flag is given use
diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo
index f201e4751..ba8a1d239 100644
--- a/Doc/Zsh/expn.yo
+++ b/Doc/Zsh/expn.yo
@@ -573,11 +573,11 @@ enditem()
 subsect(Example)
 The flag tt(f) is useful to split a double-quoted substitution line by
 line.  For example, `tt("${(f)$LPAR()<)var(file)tt(RPAR()}")'
-will substitue the contents of var(file) divided so that one line is
-supplied per argument to var(cmd).  Compare this with the effect of
+substitutes the contents of var(file) divided so that each line is
+an element of the resulting array.  Compare this with the effect of
 `tt($)tt(LPAR()<)var(file)tt(RPAR())' alone, which divides the file
-up by words, or the same inside double quotes, where the entire
-contents of the file are passed as a single argument.
+up by words, or the same inside double quotes, which makes the entire
+content of the file a single string.
 texinode(Command Substitution)(Arithmetic Expansion)(Parameter Expansion)(Expansion)
 sect(Command Substitution)
 cindex(command substitution)
diff --git a/Doc/Zsh/options.yo b/Doc/Zsh/options.yo
index 0fdf78e8a..3af59560d 100644
--- a/Doc/Zsh/options.yo
+++ b/Doc/Zsh/options.yo
@@ -614,7 +614,7 @@ Thus if `tt(/usr/local/bin)' is in the user's path, and he types
 Commands explicitly beginning with `tt(/)', `tt(./)' or `tt(../)'
 are not subject to the path search.
 This also applies to the tt(.) builtin,
-and searches for modules performed by the tt(zmodload) builtin.
+and to searches for modules performed by the tt(zmodload) builtin.
 )
 pindex(POSIX_BUILTINS)
 item(tt(POSIX_BUILTINS))(
diff --git a/Doc/Zsh/prompt.yo b/Doc/Zsh/prompt.yo
index e1628da86..685cca8c4 100644
--- a/Doc/Zsh/prompt.yo
+++ b/Doc/Zsh/prompt.yo
@@ -175,6 +175,8 @@ sitem(tt(w))(True if the day of the week is equal to var(n) (Sunday = 0).)
 sitem(tt(?))(True if the exit status of the last command was var(n).)
 sitem(tt(#))(True if the effective uid of the current process is var(n).)
 sitem(tt(g))(True if the effective gid of the current process is var(n).)
+sitem(tt(l))(True if at least var(n) characters have already been
+printed on the current line.)
 sitem(tt(L))(True if the tt(SHLVL) parameter is at least var(n).)
 sitem(tt(S))(True if the tt(SECONDS) parameter is at least var(n).)
 sitem(tt(v))(True if the array tt(psvar) has at least var(n) elements.)
@@ -185,25 +187,40 @@ endsitem()
 xitem(tt(%<)var(string)tt(<))
 xitem(tt(%>)var(string)tt(>))
 item(tt(%[)var(xstring)tt(]))(
-Specifies truncation behaviour.
+Specifies truncation behaviour for the remainder of the prompt string.
 The third, deprecated, form is equivalent to `tt(%)var(xstringx)',
 i.e. var(x) may be `tt(<)' or `tt(>)'.
 The numeric argument, which in the third form may appear immediately
 after the `tt([)', specifies the maximum permitted length of
-the various strings that can be displayed in the prompt.  If this
-integer is zero, or missing, truncation is disabled.  Truncation is
-initially disabled.
+the various strings that can be displayed in the prompt.
 The var(string) will be displayed in
-place of the truncated portion of any string.
+place of the truncated portion of any string; note this does not
+undergo prompt expansion.
 
 The forms with `tt(<)' truncate at the left of the string,
 and the forms with `tt(>)' truncate at the right of the string.
 For example, if the current directory is `tt(/home/pike)',
 the prompt `tt(%8<..<%/)' will expand to `tt(..e/pike)'.
 In this string, the terminating character (`tt(<)', `tt(>)' or `tt(])'),
-or in fact any character, may be quoted by a preceding `tt(\)'.
-% escapes are em(not) recognised.
+or in fact any character, may be quoted by a preceding `tt(\)'; note
+when using tt(print -P), however, that this must be doubled as the
+string is also subject to standard tt(print) processing, in addition
+to any backslashes removed by a double quoted string:  the worst case
+is therefore `tt(print -P "%<\\\\<<...")'.
+
 If the var(string) is longer than the specified truncation length,
 it will appear in full, completely replacing the truncated string.
+
+The part of the prompt string to be truncated runs to the end of the
+string, or to the end of the next enclosing group of the `tt(%LPAR())'
+construct, or to the next truncation encountered at the same grouping
+level (i.e. truncations inside a `tt(%LPAR())' are separate), which
+ever comes first.  In particular, a truncation with argument zero
+(e.g. `tt(%<<)') marks the end of the range of the string to be
+truncated while turning off truncation from there on. For example, the
+prompt '%10<...<%~%<<%# ' will print a truncated representation of the
+current directory, followed by a `tt(%)' or `tt(#)', followed by a
+space.  Without the `tt(%<<)', those two characters would be included
+in the string to be truncated.
 )
 enditem()
diff --git a/Functions/multicomp b/Functions/multicomp
index ab206de0d..bef4e179f 100755
--- a/Functions/multicomp
+++ b/Functions/multicomp
@@ -5,8 +5,6 @@
 # Usage: e.g.
 # compctl -D -f + -U -Q -S '' -K multicomp
 #
-# Note that exactly matched directories are not expanded, e.g.
-# s/zsh-2.4/s<TAB> will not expand to src/zsh-2.4old/src.
 # Will expand glob patterns already in the word, but use complete-word,
 # not TAB (expand-or-complete), or you will get ordinary glob expansion.
 # Requires the -U option to compctl.
@@ -42,32 +40,32 @@ while [[ -n "$pref" ]]; do
   [[ "$pref" = /* ]] && sofar=(${sofar}/) && pref="${pref#/}"
   head="${pref%%/*}"
   pref="${pref#$head}"
-  if [[ -n "$pref" && -z $sofar[2] && -d "${sofar}$head" ]]; then
-    # Exactly matched directory: don't try to glob
-    reply=("${sofar}$head")
+  [[ -z "$pref" ]] && globdir=
+  # if path segment contains wildcards, don't add another.
+  if [[ "$head" = *[\[\(\*\?\$\~]* ]]; then
+    wild=$head
   else
     [[ -z "$pref" ]] && globdir=
     # if path segment contains wildcards, don't add another.
     if [[ "$head" = *[\[\(\*\?\$\~]* || -z "$head" ]]; then
       wild=$head
     else
-      # Simulate case-insensitive globbing for ASCII characters
-      wild="[${(j(][))${(s())head:l}}]*"	# :gs/a/[a]/ etc.
-      # The following could all be one expansion, but for readability:
-      wild=$wild:gs/a/aA/:gs/b/bB/:gs/c/cC/:gs/d/dD/:gs/e/eE/:gs/f/fF/
-      wild=$wild:gs/g/gG/:gs/h/hH/:gs/i/iI/:gs/j/jJ/:gs/k/kK/:gs/l/lL/
-      wild=$wild:gs/m/mM/:gs/n/nN/:gs/o/oO/:gs/p/pP/:gs/q/qQ/:gs/r/rR/
-      wild=$wild:gs/s/sS/:gs/t/tT/:gs/u/uU/:gs/v/vV/:gs/w/wW/:gs/x/xX/
-      wild=$wild:gs/y/yY/:gs/z/zZ/:gs/-/_/:gs/_/-_/:gs/[]//
+    # Simulate case-insensitive globbing for ASCII characters
+    wild="[${(j(][))${(s())head:l}}]*" # :gs/a/[a]/ etc.
+    # The following could all be one expansion, but for readability:
+    wild=$wild:gs/a/aA/:gs/b/bB/:gs/c/cC/:gs/d/dD/:gs/e/eE/:gs/f/fF/
+    wild=$wild:gs/g/gG/:gs/h/hH/:gs/i/iI/:gs/j/jJ/:gs/k/kK/:gs/l/lL/
+    wild=$wild:gs/m/mM/:gs/n/nN/:gs/o/oO/:gs/p/pP/:gs/q/qQ/:gs/r/rR/
+    wild=$wild:gs/s/sS/:gs/t/tT/:gs/u/uU/:gs/v/vV/:gs/w/wW/:gs/x/xX/
+    wild=$wild:gs/y/yY/:gs/z/zZ/:gs/-/_/:gs/_/-_/:gs/[]//
 
-      # Expand on both sides of '.' (except when leading) as for '/'
-      wild="${${wild:gs/[.]/*.*/}#\*}"
-    fi
-
-    reply=(${sofar}"${wild}${globdir}")
-    reply=(${~reply})
+    # Expand on both sides of '.' (except when leading) as for '/'
+    wild="${${wild:gs/[.]/*.*/}#\*}"
   fi
 
+  reply=(${sofar}"${wild}${globdir}")
+  reply=(${~reply})
+
   [[ -z $reply[1] ]] && reply=() && break
   [[ -n $pref ]] && sofar=($reply)
 done
@@ -77,4 +75,3 @@ done
 [[ -n "$origtop" ]] && reply=("$origtop"${reply#$newtop})
 
 # }
-
diff --git a/Functions/randline b/Functions/randline
index 9af714fa2..7b06b7982 100755
--- a/Functions/randline
+++ b/Functions/randline
@@ -1,3 +1,3 @@
 # get a random line from a file
-integer z=$(wc -l <$1)
+integer z="$(wc -l <$1)"
 sed -n $[RANDOM%z+1]p $1
diff --git a/Makefile.in b/Makefile.in
index c91ddcf56..b2be5bf44 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -51,6 +51,10 @@ META-FAQ: FORCE
 
 # ========== DEPENDENCIES FOR INSTALLING ==========
 
+# install stripped
+install-strip:
+	$(MAKE) install STRIPFLAGS="-s"
+
 # install/uninstall everything
 install: install.bin install.modules install.man install.info
 uninstall: uninstall.bin uninstall.modules uninstall.man uninstall.info
@@ -71,6 +75,10 @@ install.man uninstall.man:
 install.info uninstall.info:
 	@cd Doc && $(MAKE) $(MAKEDEFS) $@
 
+# install/uninstall just the html pages
+install.html uninstall.html:
+	@cd Doc && $(MAKE) $(MAKEDEFS) $@
+
 # ========== DEPENDENCIES FOR CLEANUP ==========
 
 @@clean.mk@@
diff --git a/Misc/c2z b/Misc/c2z
index 8a90d5fd8..534137296 100755
--- a/Misc/c2z
+++ b/Misc/c2z
@@ -3,6 +3,7 @@
 # c2z - environment conversion tool
 # Contributed by Bart Schaefer
 # (Tweaked a bit by Paul Falstad)
+# (Tweaked again by Bart Schaefer)
 #
 # This is a quick script to convert csh aliases to zsh aliases/functions.
 # It also converts the csh environment and local variables to zsh.  c2z
@@ -81,7 +82,13 @@ grep -v ! /tmp/cz$$.1 >/tmp/cz$$.3
 sed -e "s/'/'"\\\\"''"/g \
     -e 's/^\([^'"$T"']*\)'"$T"'\(.*\)$/alias -- \1='"'\2'/" \
     /tmp/cz$$.3
-sed -e 's/![:#]*/$/g' \
+sed -e 's/>\(&*\)!/>\1|/g' \
+    -e 's/!\*:q/"$@"/g' \
+    -e 's/\(![:#]*[^'"$T"']*\):q/"\1"/g' \
+    -e 's/!\([-0-9][0-9]*\)\(:x\)*/$\2(fc -nl \1)/g' \
+    -e 's/\$:x(fc/$=(fc/g' \
+    -e 's/![:#]*\([^'"$T"']\)/$==\1/g' \
+    -e 's/\$=\(=[^'"$T"']*\):x/$\1/g' \
     -e 's/\$cwd/$PWD/' \
     -e 's/^\([^'"$T"']*\)'"$T"'\(.*\)$/\1 () { \2 }/' \
     /tmp/cz$$.2
@@ -92,6 +99,7 @@ exec < /tmp/cz$$.e
 # Would be nice to deal with embedded newlines, e.g. in TERMCAP, but ...
 sed -e '/^SHLVL/d' \
     -e '/^PWD/d' \
+    -e '/^_=/d' \
     -e "s/'/'"\\\\"''"/g \
     -e "s/^\([A-Za-z0-9_]*=\)/export \1'/" \
     -e "s/$/'/"
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 */
diff --git a/config.guess b/config.guess
index 92b77bc52..206b98874 100755
--- a/config.guess
+++ b/config.guess
@@ -557,6 +557,12 @@ EOF
                            # says <Richard.M.Bartel@ccMail.Census.GOV>
         echo i586-unisys-sysv4
         exit 0 ;;
+    Power*:Rhapsody:*:*)
+        echo powerpc-apple-rhapsody${UNAME_RELEASE}
+        exit 0 ;;
+    *:Rhapsody:*:*)
+        echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE}
+        exit 0 ;;
 esac
 
 #echo '(No uname command or uname output not recognized.)' 1>&2
diff --git a/config.sub b/config.sub
index c5aecfe37..7e9735ea7 100755
--- a/config.sub
+++ b/config.sub
@@ -689,7 +689,7 @@ case $os in
 	      | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \
 	      | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \
 	      | -cygwin32* | -pe* | -psos* | -moss* | -proelf* | -rtems* \
-	      | -linux* | -uxpv*)
+	      | -linux* | -uxpv* | -rhapsody* )
 	# Remember, each alternative MUST END IN *, to match a version number.
 		;;
 	-sunos5*)
diff --git a/patchlist.txt b/patchlist.txt
index fc471a727..9c98bd6e3 100644
--- a/patchlist.txt
+++ b/patchlist.txt
@@ -5,7 +5,7 @@ patches.  (The version number built into the shell has not been changed.)
 
 Zoli's AIX dynamic loading patch from 3933, slightly updated, without
 some hunks which weren't needed on AIX 3.x so I don't know how to
-update properly.
+update them properly
 
 My completion widgets patch
 
@@ -18,24 +18,85 @@ My patch in 4477 to rename three functions to avoid clashes when
 dynamic loading (particularly necessary on IRIX and AIX), including
 the effect of Sven's additional fix in 4488
 
-My patch 4513 for case-insensitive globbing via flags, plus fixlet 4552
+Sven's magna opera patch-or 4510 and patch-match 4509 to add control
+of alternative matches and arbitrary mapping between characters in the
+command line and the matched string, plus all known related fixes
+4526, 4527, 4534, 4555, 4557
 
-Sven's magna opera patch-or 4510 and patch-match 4509 to add control of
-alternative matches and arbitrary mapping between characters in the
-command line and the matched, plus all known fixes 4526, 4527, 4534,
-4555, 4557
+My patch 4513 for case-insensitive globbing via flags, plus fixlet 4552
 
 My ~PWD patch 4533
 
 My suggestion for fixing the suffix on a yank in 4564
 
 Bart's deltochar patch including new flags to allow commands not to
-interrupt cumulative effects in 4570
+interrupt cumulative effects in 4570 (new flag merged with compctl
+widgets flags).
+
+Bart's doc fix 4574
+
+A fixsuffix() added by hand in delcharorlist() which I've somehow
+missed along the way. The fixsuffix() horror is probably not yet
+resolved; 4576 has side effects and hasn't been applied.
+
+My latest version of lete2ctl, not posted but available at
+http://www.ifh.de/~pws/computing/lete2ctl .
+
+Bart's chpwd() fix 4589
+
+  Second edition
+
+Added line in zle_tricky.c missed when patching by hand, spotted by
+Bart.  (Whitespace is still non-canonical.)
+
+Fixed up my compctl widgets patch for use with Sven's rewrite, which I
+hadn't done properly before.
+
+Bart's function fixes, 4471
+
+Bart's doc fixes, 4472
+
+Bart's PWD and OLDPWD reshuffle, 4589
+
+My test-line-length patch for prompts, 4591
+
+Configure patch from Wilfredo Sanchez in 4594, with some extra
+tabbification and without the setterm() hunk, since I've already renamed
+that to zsetterm(), avoiding the conflict
+
+My globbing fix for a bug which shows up in `case' constructs, 4595
+
+Alternative version of the ~PWD patch (allow users to hash PWD
+explicitly if that's what turns them on), 4596
+
+Bart's experimental associative array patch, 4598, plus various
+additions, 4599, 4602, 4608, 4641, 4653, 4654.  No documentation yet;
+if you want to play with this, so far:
+% typeset -A hash                    # create associative array $hash
+% hash[one]=eins hash[two]=zwei      # assign elements
+% hash=(one eins two zwei)           # same, assign whole array (*)
+% print $hash[one]                   # retrieve elements
+eins
+% print $hash                        # whole array looks like normal array
+eins zwei
+% print ${(k)hash}                   # flag to get keys
+one two
+% print ${(kv)hash}                  # flag to get keys and values (**)
+one eins two zwei
+Comparison of (*) and (**) will reveal how to copy an associative
+array, but you always need to declare it with typeset -A or an
+ordinary array will appear.  There is a predefined special associative
+array $testhash, for testing purposes only, which will eventually
+disappear.
+
+My rewrite of prompt truncation, 4601
+
+Bart's params error message fix, 4606
+
+My input fix for 8 bit characters, 4612
 
-Bart's doc fiz 4574
+Bart's version of the *** fix, 4624
 
-Sven's latest word on the fixsuffix() horror in 4576, plus a
-fixsuffix() added by hand in delcharorlist() which I've somehow missed
-along the way
+Bart's parameter substitution flag delimiter fix, 4644
 
-My latest version of lete2ctl, not posted
+My special parameter unset fix, 4662