summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Stephenson <pws@users.sourceforge.net>2000-04-01 20:49:47 +0000
committerPeter Stephenson <pws@users.sourceforge.net>2000-04-01 20:49:47 +0000
commit48525452555a24b9d41748f26b4b77f160f01220 (patch)
treed814ca2f017d9d843fec7d286fefbca78244beb5
parente025336f2f6d9f107ee1e03b9900f04af0544ba9 (diff)
downloadzsh-48525452555a24b9d41748f26b4b77f160f01220.tar.gz
zsh-48525452555a24b9d41748f26b4b77f160f01220.tar.xz
zsh-48525452555a24b9d41748f26b4b77f160f01220.zip
Updated from list as far as 10376
-rw-r--r--Functions/Misc/is-at-least2
-rw-r--r--Functions/Misc/zrecompile13
-rw-r--r--Functions/Zftp/zfcd_match12
-rw-r--r--Functions/Zftp/zfget_match31
-rw-r--r--Functions/Zle/incremental-complete-word187
-rw-r--r--Functions/Zle/predict-on119
-rw-r--r--Misc/make-zsh-urls2
-rw-r--r--Src/Modules/parameter.c1699
-rw-r--r--Src/Modules/zpty.c153
-rw-r--r--Src/Zle/comp.h449
-rw-r--r--Src/Zle/compcore.c1500
-rw-r--r--Src/Zle/complete.c436
-rw-r--r--Src/Zle/compmatch.c124
-rw-r--r--Src/Zle/compresult.c515
-rw-r--r--Src/Zle/computil.c1498
-rw-r--r--Src/Zle/iwidgets.list43
-rw-r--r--Src/Zle/zle_utils.c92
-rw-r--r--Src/builtin.c1512
-rw-r--r--Src/cond.c326
-rw-r--r--Src/exec.c1882
-rw-r--r--Src/hashtable.c506
-rw-r--r--Src/jobs.c347
-rw-r--r--Src/loop.c380
-rw-r--r--Src/params.c1818
-rw-r--r--Src/parse.c2753
-rw-r--r--Src/pattern.c1116
-rw-r--r--Src/text.c858
-rw-r--r--Src/zsh.h964
-rw-r--r--Test/07cond.ztst35
-rw-r--r--Test/11glob.ztst19
-rw-r--r--Test/53completion.ztst13
31 files changed, 13788 insertions, 5616 deletions
diff --git a/Functions/Misc/is-at-least b/Functions/Misc/is-at-least
index b574c154f..3eb62ef67 100644
--- a/Functions/Misc/is-at-least
+++ b/Functions/Misc/is-at-least
@@ -1,4 +1,4 @@
-# $Id: is-at-least,v 1.1 2000/02/11 19:46:46 akr Exp $ -*- shell-script -*-
+# $Id: is-at-least,v 1.2 2000/04/01 20:49:47 pws Exp $ -*- shell-script -*-
 #
 # Test whether $ZSH_VERSION (or some value of your choice, if a second argument
 # is provided) is greater than or equal to x.y.z-r (in argument one). In fact,
diff --git a/Functions/Misc/zrecompile b/Functions/Misc/zrecompile
index 70410a580..1cdd05e0e 100644
--- a/Functions/Misc/zrecompile
+++ b/Functions/Misc/zrecompile
@@ -18,8 +18,8 @@
 #       seperated by `--'. For example:
 #
 #         zrecompile -p \
-#                    -r ~/.zshrc -- \
-#                    -m ~/.zcompdump -- \
+#                    -R ~/.zshrc -- \
+#                    -M ~/.zcompdump -- \
 #                    ~/zsh/comp.zwc ~/zsh/Completion/*/_* \
 #
 #       This makes ~/.zshrc be compiled into ~/.zshrc.zwc if that doesn't
@@ -33,8 +33,7 @@
 # that needed re-compilation could be compiled and non-zero if compilation
 # for at least one of the files failed.
 
-emulate -L zsh
-setopt extendedglob
+setopt localoptions extendedglob
 
 local opt check quiet zwc files re file pre ret map tmp mesg pats
 
@@ -68,7 +67,7 @@ if [[ -n $pats ]]; then
     fi
 
     files=( ${files:#*(.zwc|~)} )
-    if [[ $files[1] = -[rm] ]]; then
+    if [[ $files[1] = -[RM] ]]; then
       map=( $files[1] )
       shift 1 files
     else
@@ -146,10 +145,10 @@ for zwc; do
   # See if the wordcode file will be mapped.
 
   if [[ $files[1] = *\(mapped\)* ]]; then
-    map=-m
+    map=-M
     mesg='succeeded (old saved)'
   else
-    map=-r
+    map=-R
     mesg=succeeded
   fi
 
diff --git a/Functions/Zftp/zfcd_match b/Functions/Zftp/zfcd_match
index 67e719888..02a19af21 100644
--- a/Functions/Zftp/zfcd_match
+++ b/Functions/Zftp/zfcd_match
@@ -15,7 +15,7 @@ local tmpf=${TMPPREFIX}zfcm$$
 
 if [[ $ZFTP_SYSTEM = UNIX* ]]; then
   # hoo, aren't we lucky: this makes things so much easier
-  setopt localoptions rcexpandparam
+  setopt rcexpandparam
   local dir
   if [[ $1 = ?*/* ]]; then
     dir=${1%/*}
@@ -25,13 +25,15 @@ if [[ $ZFTP_SYSTEM = UNIX* ]]; then
   # If we're using -F, we get away with using a directory
   # to list, but not a glob.  Don't ask me why.
   # I hate having to rely on awk here.
-  zftp ls -F $dir >$tmpf
+  zftp ls -LF $dir >$tmpf
   reply=($(awk '/\/$/ { print substr($1, 0, length($1)-1) }' $tmpf))
   rm -f $tmpf
-  if [[ $dir = / ]]; then
-    reply=(${dir}$reply)
+  [[ -n $dir && $dir != */ ]] && dir="$dir/"
+  if [[ -n $WIDGET ]]; then
+    _all_labels directories expl 'remote directory'
+        compadd -S/ -q -P "$dir" - $reply
   elif [[ -n $dir ]]; then
-    reply=($dir/$reply)
+    reply=(${dir}$reply)
   fi
 else
   # I simply don't know what to do here.
diff --git a/Functions/Zftp/zfget_match b/Functions/Zftp/zfget_match
index 677108ede..0fe2bc06f 100644
--- a/Functions/Zftp/zfget_match
+++ b/Functions/Zftp/zfget_match
@@ -10,18 +10,27 @@ fi
 local tmpf=${TMPPREFIX}zfgm$$
 
 if [[ $ZFTP_SYSTEM == UNIX* && $1 == */* ]]; then
-  # On the first argument to ls, we usually get away with a glob.
-  zftp ls "$1*$2" >$tmpf
-  reply=($(<$tmpf))
-  rm -f $tmpf
-else
-  if (( $#zftp_fcache == 0 )); then
-    # Always cache the current directory and use it
-    # even if the system is UNIX.
-    zftp ls >$tmpf
-    zftp_fcache=($(<$tmpf))
+  if [[ -n $WIDGET ]]; then
+    local dir=${1:h}
+    [[ $dir = */ ]] || dir="$dir/"
+    zftp ls -LF $dir >$tmpf
+    local reply
+    reply=(${${${(f)"$(<$tmpf)"}##$dir}%\*})
+    rm -f $tmpf
+    _all_labels files expl 'remote file' compadd -P $dir - $reply
+  else
+    # On the first argument to ls, we usually get away with a glob.
+    zftp ls "$1*$2" >$tmpf
+    reply=($(<$tmpf))
     rm -f $tmpf
   fi
-  reply=($zftp_fcache);
+else
+  local fcache_name
+  zffcache
+  if [[ -n $WIDGET ]]; then
+    _all_labels files expl 'remote file' compadd -F fignore - ${(P)fcache_name}
+  else
+    reply=(${(P)fcache_name});
+  fi
 fi
 # }
diff --git a/Functions/Zle/incremental-complete-word b/Functions/Zle/incremental-complete-word
index 2a9c1aff2..e021bf6f7 100644
--- a/Functions/Zle/incremental-complete-word
+++ b/Functions/Zle/incremental-complete-word
@@ -1,89 +1,124 @@
-# incremental-complete-word() {
-
 # Autoload this function, run `zle -N <func-name>' and bind <func-name>
 # to a key.
 
+
 # This allows incremental completion of a word.  After starting this
-# command, a list of completion choices is shown after every character you
-# type, which you can delete with ^h or DEL.  RET will accept the
-# completion so far.  You can hit TAB to do normal completion and ^g to
-# abort back to the state when you started.
+# command, a list of completion choices can be shown after every character
+# you type, which you can delete with ^h or DEL.  RET will accept the
+# completion so far.  You can hit TAB to do normal completion, ^g to
+# abort back to the state when you started, and ^d to list the matches.
 #
-# Completion keys:
-#   incremental_prompt   Prompt to show in status line during icompletion;
-#                        the sequence `%u' is replaced by the unambiguous
-#                        part of all matches if there is any and it is
-#                        different from the word on the line
-# incremental_stop       Pattern matching keys which will cause icompletion
-#                         to stop and the key to be re-executed
-# incremental_break      Pattern matching keys which will cause icompletion
-#                         to stop and the key to be discarded
-# incremental_completer  Set of completers, like the `completer' key
-#   incremental_list     If set to a non-empty string, the matches will be
-#                        listed on every key-press
-
-emulate -L zsh
-unsetopt autolist menucomplete automenu # doesn't work well
-
-local key lbuf="$LBUFFER" rbuf="$RBUFFER" pmpt word lastl lastr wid twid
-
-[[ -n "$compconfig[incremental_completer]" ]] &&
-set ${(s.:.)compconfig[incremental_completer]}
-pmpt="${compconfig[incremental_prompt]-incremental completion...}"
-
-if [[ -n "$compconfig[incremental_list]" ]]; then
-  wid=list-choices
-else
-  wid=complete-word
-fi
-
-zle $wid "$@"
-LBUFFER="$lbuf"
-RBUFFER="$rbuf"
-if [[ "${LBUFFER}${RBUFFER}" = *${_lastcomp[unambiguous]}* ]]; then
-  word=''
-else
-  word="${_lastcomp[unambiguous]}"
-fi
-zle -R "${pmpt//\\%u/$word}"
-read -k key
-
-while [[ '#key' -ne '#\\r' && '#key' -ne '#\\n' &&
-         '#key' -ne '#\\C-g' ]]; do
-  twid=$wid
-  if [[ "$key" = ${~compconfig[incremental_stop]} ]]; then
-    zle -U "$key"
-    return
-  elif [[ "$key" = ${~compconfig[incremental_break]} ]]; then
-    return
-  elif [[ '#key' -eq '#\\C-h' || '#key' -eq '#\\C-?' ]]; then
-    [[ $#LBUFFER -gt $#l ]] && LBUFFER="$LBUFFER[1,-2]"
-  elif [[ '#key' -eq '#\\t' ]]; then
-    zle complete-word "$@"
-    lbuf="$LBUFFER"
-    rbuf="$RBUFFER"
-  elif [[ '#key' -eq '#\\C-d' ]]; then
-    twid=list-choices
+# This works only with the new function based completion system.
+
+# The main widget function.
+
+incremental-complete-word() {
+  #emulate -L zsh
+  unsetopt autolist menucomplete automenu # doesn't work well
+
+  local key lbuf="$LBUFFER" rbuf="$RBUFFER" pmpt pstr word
+  local lastl lastr wid twid num post toolong
+  local curcontext="${curcontext}" stop brk
+
+  [[ -z "$curcontext" ]] && curcontext=:::
+  curcontext="incremental:${curcontext#*:}"
+
+  zstyle -s ":completion:${curcontext}" prompt pmpt ||
+    pmpt='incremental (%c): %u%s  %l'
+  zstyle -s ":completion:${curcontext}" stop stop
+  zstyle -s ":completion:${curcontext}" break brk
+
+  if zstyle -t ":completion:${curcontext}" list; then
+    wid=list-choices
+    post=( icw-list-helper )
   else
-    LBUFFER="$LBUFFER$key"
+    wid=complete-word
+    post=()
   fi
-  lastl="$LBUFFER"
-  lastr="$RBUFFER"
-  zle $twid "$@"
-  LBUFFER="$lastl"
-  RBUFFER="$lastr"
-  if [[ "${LBUFFER}${RBUFFER}" = *${_lastcomp[unambiguous]}* ]]; then
+
+  comppostfuncs=( "$post[@]" )
+  zle $wid "$@"
+  LBUFFER="$lbuf"
+  RBUFFER="$rbuf"
+  num=$_lastcomp[nmatches]
+  if (( ! num )); then
+    word=''
+    state='-no match-'
+  elif [[ "${LBUFFER}${RBUFFER}" = *${_lastcomp[unambiguous]}* ]]; then
     word=''
+    state='-no prefix-'
   else
     word="${_lastcomp[unambiguous]}"
+    state=''
   fi
-  zle -R "${pmpt//\\%u/$word}"
+  zformat -f pstr "$pmpt" "u:${word}" "s:$state" "n:$num" \
+                          "l:$toolong" "c:${_lastcomp[completer][2,-1]}"
+  zle -R "$pstr"
   read -k key
-done
 
-if [[ '#key' -eq '#\\C-g' ]]; then
-  LBUFFER="$lbuf"
-  RBUFFER="$rbuf"
-fi
-zle -Rc
-# }
+  while [[ '#key' -ne '#\\r' && '#key' -ne '#\\n' &&
+           '#key' -ne '#\\C-g' ]]; do
+    twid=$wid
+    if [[ "$key" = ${~stop} ]]; then
+      zle -U "$key"
+      return
+    elif [[ "$key" = ${~brk} ]]; then
+      return
+    elif [[ '#key' -eq '#\\C-h' || '#key' -eq '#\\C-?' ]]; then
+      [[ $#LBUFFER -gt $#l ]] && LBUFFER="$LBUFFER[1,-2]"
+    elif [[ '#key' -eq '#\\t' ]]; then
+      zle complete-word "$@"
+      lbuf="$LBUFFER"
+      rbuf="$RBUFFER"
+    elif [[ '#key' -eq '#\\C-d' ]]; then
+      twid=list-choices
+    else
+      LBUFFER="$LBUFFER$key"
+    fi
+    lastl="$LBUFFER"
+    lastr="$RBUFFER"
+    [[ "$twid" = "$wid" ]] && comppostfuncs=( "$post[@]" )
+    toolong=''
+    zle $twid "$@"
+    LBUFFER="$lastl"
+    RBUFFER="$lastr"
+    num=$_lastcomp[nmatches]
+    if (( ! num )); then
+      word=''
+      state='-no match-'
+    elif [[ "${LBUFFER}${RBUFFER}" = *${_lastcomp[unambiguous]}* ]]; then
+      word=''
+      state='-no prefix-'
+    else
+      word="${_lastcomp[unambiguous]}"
+      state=''
+    fi
+    zformat -f pstr "$pmpt" "u:${word}" "s:$state" "n:$num" \
+                            "l:$toolong" "c:${_lastcomp[completer][2,-1]}"
+    zle -R "$pstr"
+    read -k key
+  done
+
+  if [[ '#key' -eq '#\\C-g' ]]; then
+    LBUFFER="$lbuf"
+    RBUFFER="$rbuf"
+  fi
+  zle -Rc
+}
+
+# Helper function used as a completion post-function used to make sure that
+# the list of matches in only shown if it fits on the screen.
+
+icw-list-helper() {
+
+  # +1 for the status line we will add...
+
+  if [[ compstate[list_lines]+BUFFERLINES+1 -gt LINES ]]; then
+    compstate[list]='list explanations'
+    [[ compstate[list_lines]+BUFFERLINES+1 -gt LINES ]] && compstate[list]=''
+
+    toolong='...'
+  fi
+}
+
+incremental-complete-word "$@"
diff --git a/Functions/Zle/predict-on b/Functions/Zle/predict-on
index 07ce0703a..bd7212050 100644
--- a/Functions/Zle/predict-on
+++ b/Functions/Zle/predict-on
@@ -1,64 +1,141 @@
 # This set of functions implements a sort of magic history searching.
 # After predict-on, typing characters causes the editor to look backward
-# in the history for the first line beginning with what you have typed
-# so far.  After predict-off, editing returns to normal for the line found.
+# in the history for the first line beginning with what you have typed so
+# far.  After predict-off, editing returns to normal for the line found.
 # In fact, you often don't even need to use predict-off, because if the
-# line doesn't match something in the history, adding a key at the end
-# behaves as normal --- though editing in the middle is liable to delete
+# line doesn't match something in the history, adding a key performs
+# standard completion --- though editing in the middle is liable to delete
 # the rest of the line.
 #
+# With the function based completion system (which is needed for this),
+# you should be able to type TAB at almost any point to advance the cursor
+# to the next "interesting" character position (usually the end of the
+# current word, but sometimes somewhere in the middle of the word).  And
+# of course as soon as the entire line is what you want, you can accept
+# with RETURN, without needing to move the cursor to the end first.
+#
 # To use it:
 #   autoload -U predict-on
 #   zle -N predict-on
 #   zle -N predict-off
 #   bindkey '...' predict-on
 #   bindkey '...' predict-off
-# Note that all the functions are defined when you first call type the
-# predict-on key, which means typing the predict-off key before that gives
-# a harmless error message.
+# Note that all functions are defined when you first type the predict-on
+# key, which means typing the predict-off key before that gives a harmless
+# error message.
 
 predict-on() {
-    zle -N self-insert insert-and-predict
-    zle -N magic-space insert-and-predict
-    zle -N backward-delete-char delete-backward-and-predict
+  zle -N self-insert insert-and-predict
+  zle -N magic-space insert-and-predict
+  zle -N backward-delete-char delete-backward-and-predict
+  zle -N delete-char-or-list delete-no-predict
 }
 predict-off() {
-    zle -A .self-insert self-insert
-    zle -A .magic-space magic-space
-    zle -A .backward-delete-char backward-delete-char
+  zle -A .self-insert self-insert
+  zle -A .magic-space magic-space
+  zle -A .backward-delete-char backward-delete-char
 }
 insert-and-predict () {
-  emulate -L zsh
-  if [[ ${RBUFFER[1]} = ${KEYS[-1]} ]]
+  setopt localoptions noshwordsplit noksharrays
+  if [[ $LBUFFER = *$'\012'* ]]
+  then
+    # Editing a multiline buffer, it's unlikely prediction is wanted
+    zle .$WIDGET "$@"
+    return
+  elif [[ ${RBUFFER[1]} = ${KEYS[-1]} ]]
   then
-    # same as what's typed, just move on
+    # Same as what's typed, just move on
     ((++CURSOR))
   else
     LBUFFER="$LBUFFER$KEYS"
     if [[ $LASTWIDGET == (self-insert|magic-space|backward-delete-char) ]]
     then
-      zle .history-beginning-search-backward || RBUFFER=""
+      if ! zle .history-beginning-search-backward
+      then
+	RBUFFER=""
+	if [[ ${KEYS[-1]} != ' ' ]]
+	then
+	  unsetopt automenu recexact
+	  integer curs=$CURSOR pos nchar=${#LBUFFER//[^${KEYS[-1]}]}
+	  local -a +h comppostfuncs
+	  local crs curcontext="${curcontext}"
+
+          [[ -z "$curcontext" ]] && curcontext=:::
+          curcontext="predict:${curcontext#*:}"
+
+	  comppostfuncs=( predict-limit-list )
+	  zle complete-word
+	  # Decide where to leave the cursor. The dummy loop is used to
+	  # get out of that `case'.
+	  repeat 1
+	  do
+	    zstyle -s ":completion:${curcontext}:" cursor crs
+	    case $crs in
+	    (complete)
+	      # At the place where the completion left it, if it is after
+	      # the character typed.
+	      [[ ${LBUFFER[-1]} = ${KEYS[-1]} ]] && break
+	      ;&
+	    (key)
+	      # Or maybe at the n'th occurrence of the character typed.
+	      pos=${BUFFER[(in:nchar:)${KEYS[-1]}]}
+	      if [[ pos -gt curs ]]
+	      then
+	        CURSOR=$pos
+	        break
+	      fi
+	      ;&
+	    (*)
+	      # Or else at the previous position.
+	      CURSOR=$curs
+	    esac
+	  done
+	fi
+      fi
     fi
   fi
   return 0
 }
 delete-backward-and-predict() {
-  emulate -L zsh
   if [[ -n "$LBUFFER" ]]
   then
+    setopt localoptions noshwordsplit noksharrays
+    if [[ $LBUFFER = *$'\012'* ]] then
+      # Editing a multiline buffer, it's unlikely prediction is wanted
+      zle .$WIDGET "$@"
     # If the last widget was e.g. a motion, then probably the intent is
     # to actually edit the line, not change the search prefix.
-    if [[ $LASTWIDGET == (self-insert|magic-space|backward-delete-char) ]]
+    elif [[ $LASTWIDGET == (self-insert|magic-space|backward-delete-char) ]]
     then
       ((--CURSOR))
       zle .history-beginning-search-forward || RBUFFER=""
       return 0
     else
-      # Depending on preference, you might call "predict-off" here,
-      # and also set up forward deletions to turn off prediction.
+      # Depending on preference, you might call "predict-off" here.
       LBUFFER="$LBUFFER[1,-2]"
     fi
   fi
 }
+delete-no-predict() {
+  [[ $WIDGET != delete-char-or-list || -n $RBUFFER ]] && predict-off
+  zle .$WIDGET "$@"
+}
+
+# This is a helper function for autocompletion to prevent long lists
+# of matches from forcing a "do you wish to see all ...?" prompt.
+
+predict-limit-list() {
+  if (( compstate[list_lines]+BUFFERLINES > LINES ||
+	( compstate[list_max] != 0 &&
+	    compstate[nmatches] > compstate[list_max] ) ))
+  then
+    compstate[list]=''
+  elif zstyle -t ":completion:predict::::" list always
+  then
+    compstate[list]='force list'
+  fi
+}
+
+# Handle zsh autoloading conventions
 
 [[ -o kshautoload ]] || predict-on "$@"
diff --git a/Misc/make-zsh-urls b/Misc/make-zsh-urls
index 35bbf9fbb..b74b8334e 100644
--- a/Misc/make-zsh-urls
+++ b/Misc/make-zsh-urls
@@ -1,6 +1,6 @@
 #!/usr/bin/perl -w
 #
-# $Id: make-zsh-urls,v 1.1 1999/11/09 02:36:42 akr Exp $
+# $Id: make-zsh-urls,v 1.2 2000/04/01 20:49:47 pws Exp $
 
 use strict;
 
diff --git a/Src/Modules/parameter.c b/Src/Modules/parameter.c
index 2257933f5..e8e387a59 100644
--- a/Src/Modules/parameter.c
+++ b/Src/Modules/parameter.c
@@ -30,6 +30,10 @@
 #include "parameter.mdh"
 #include "parameter.pro"
 
+/* This says if we are cleaning up when the module is unloaded. */
+
+static int incleanup;
+
 /* Empty dummy function for special hash parameters. */
 
 /**/
@@ -47,14 +51,14 @@ createspecialhash(char *name, GetNodeFunc get, ScanTabFunc scan)
     Param pm;
     HashTable ht;
 
-    if (!(pm = createparam(name, PM_SPECIAL|PM_REMOVABLE|PM_HASHED)))
+    if (!(pm = createparam(name, PM_SPECIAL|PM_HIDE|PM_REMOVABLE|PM_HASHED)))
 	return NULL;
 
     pm->level = pm->old ? locallevel : 0;
     pm->gets.hfn = hashgetfn;
     pm->sets.hfn = hashsetfn;
     pm->unsetfn = stdunsetfn;
-    pm->u.hash = ht = newhashtable(7, name, NULL);
+    pm->u.hash = ht = newhashtable(0, name, NULL);
 
     ht->hash        = hasher;
     ht->emptytable  = (TableFunc) shempty;
@@ -83,14 +87,21 @@ paramtypestr(Param pm)
     int f = pm->flags;
 
     if (!(f & PM_UNSET)) {
+	if (pm->flags & PM_AUTOLOAD)
+	    return dupstring("undefined");
+
 	switch (PM_TYPE(f)) {
 	case PM_SCALAR:  val = "scalar"; break;
 	case PM_ARRAY:   val = "array"; break;
 	case PM_INTEGER: val = "integer"; break;
+	case PM_EFLOAT:
+	case PM_FFLOAT:  val = "float"; break;
 	case PM_HASHED:  val = "association"; break;
 	}
 	DPUTS(!val, "BUG: type not handled in parameter");
 	val = dupstring(val);
+	if (pm->level)
+	    val = dyncat(val, "-local");
 	if (f & PM_LEFT)
 	    val = dyncat(val, "-left");
 	if (f & PM_RIGHT_B)
@@ -109,6 +120,10 @@ paramtypestr(Param pm)
 	    val = dyncat(val, "-export");
 	if (f & PM_UNIQUE)
 	    val = dyncat(val, "-unique");
+	if (f & PM_HIDE)
+	    val = dyncat(val, "-hide");
+	if (f & PM_SPECIAL)
+	    val = dyncat(val, "-special");
     } else
 	val = dupstring("");
 
@@ -121,27 +136,24 @@ getpmparameter(HashTable ht, char *name)
 {
     Param rpm, pm = NULL;
 
-    HEAPALLOC {
-	pm = (Param) zhalloc(sizeof(struct param));
-	pm->nam = dupstring(name);
-	pm->flags = PM_SCALAR | PM_READONLY;
-	pm->sets.cfn = NULL;
-	pm->gets.cfn = strgetfn;
-	pm->unsetfn = NULL;
-	pm->ct = 0;
-	pm->env = NULL;
-	pm->ename = NULL;
-	pm->old = NULL;
-	pm->level = 0;
-	if ((rpm = (Param) realparamtab->getnode(realparamtab, name)) &&
-	    !(rpm->flags & PM_UNSET))
-	    pm->u.str = paramtypestr(rpm);
-	else {
-	    pm->u.str = "";
-	    pm->flags |= PM_UNSET;
-	}
-    } LASTALLOC;
-
+    pm = (Param) zhalloc(sizeof(struct param));
+    pm->nam = dupstring(name);
+    pm->flags = PM_SCALAR | PM_READONLY;
+    pm->sets.cfn = NULL;
+    pm->gets.cfn = strgetfn;
+    pm->unsetfn = NULL;
+    pm->ct = 0;
+    pm->env = NULL;
+    pm->ename = NULL;
+    pm->old = NULL;
+    pm->level = 0;
+    if ((rpm = (Param) realparamtab->getnode(realparamtab, name)) &&
+	!(rpm->flags & PM_UNSET))
+	pm->u.str = paramtypestr(rpm);
+    else {
+	pm->u.str = dupstring("");
+	pm->flags |= PM_UNSET;
+    }
     return (HashNode) pm;
 }
 
@@ -166,7 +178,9 @@ scanpmparameters(HashTable ht, ScanFunc func, int flags)
     for (i = 0; i < realparamtab->hsize; i++)
 	for (hn = realparamtab->nodes[i]; hn; hn = hn->next) {
 	    pm.nam = hn->nam;
-	    if (func != scancountparams)
+	    if (func != scancountparams &&
+		((flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL)) ||
+		 !(flags & SCANPM_WANTKEYS)))
 		pm.u.str = paramtypestr((Param) hn);
 	    func((HashNode) &pm, flags);
 	}
@@ -179,12 +193,12 @@ static void
 setpmcommand(Param pm, char *value)
 {
     if (isset(RESTRICTED))
-	zwarnnam(NULL, "restricted: %s", value, 0);
+	zwarn("restricted: %s", value, 0);
     else {
 	Cmdnam cn = zcalloc(sizeof(*cn));
 
 	cn->flags = HASHED;
-	cn->u.cmd = ztrdup(value);
+	cn->u.cmd = value;
 
 	cmdnamtab->addnode(cmdnamtab, ztrdup(pm->nam), (HashNode) cn);
     }
@@ -207,6 +221,9 @@ setpmcommands(Param pm, HashTable ht)
     int i;
     HashNode hn;
 
+    if (!ht)
+	return;
+
     for (i = 0; i < ht->hsize; i++)
 	for (hn = ht->nodes[i]; hn; hn = hn->next) {
 	    Cmdnam cn = zcalloc(sizeof(*cn));
@@ -222,6 +239,7 @@ setpmcommands(Param pm, HashTable ht)
 
 	    cmdnamtab->addnode(cmdnamtab, ztrdup(hn->nam), (HashNode) cn);
 	}
+    deleteparamtable(ht);
 }
 
 /**/
@@ -236,34 +254,30 @@ getpmcommand(HashTable ht, char *name)
 	cmdnamtab->filltable(cmdnamtab);
 	cmd = (Cmdnam) cmdnamtab->getnode(cmdnamtab, name);
     }
-    HEAPALLOC {
-	pm = (Param) zhalloc(sizeof(struct param));
-	pm->nam = dupstring(name);
-	pm->flags = PM_SCALAR;
-	pm->sets.cfn = setpmcommand;
-	pm->gets.cfn = strgetfn;
-	pm->unsetfn = unsetpmcommand;
-	pm->ct = 0;
-	pm->env = NULL;
-	pm->ename = NULL;
-	pm->old = NULL;
-	pm->level = 0;
-	if (cmd) {
-	    if (cmd->flags & HASHED)
-		pm->u.str = cmd->u.cmd;
-	    else {
-		pm->u.str = zhalloc(strlen(*(cmd->u.name)) +
-				    strlen(name) + 2);
-		strcpy(pm->u.str, *(cmd->u.name));
-		strcat(pm->u.str, "/");
-		strcat(pm->u.str, name);
-	    }
-	} else {
-	    pm->u.str = "";
-	    pm->flags |= PM_UNSET;
+    pm = (Param) zhalloc(sizeof(struct param));
+    pm->nam = dupstring(name);
+    pm->flags = PM_SCALAR;
+    pm->sets.cfn = setpmcommand;
+    pm->gets.cfn = strgetfn;
+    pm->unsetfn = unsetpmcommand;
+    pm->ct = 0;
+    pm->env = NULL;
+    pm->ename = NULL;
+    pm->old = NULL;
+    pm->level = 0;
+    if (cmd) {
+	if (cmd->flags & HASHED)
+	    pm->u.str = cmd->u.cmd;
+	else {
+	    pm->u.str = zhalloc(strlen(*(cmd->u.name)) + strlen(name) + 2);
+	    strcpy(pm->u.str, *(cmd->u.name));
+	    strcat(pm->u.str, "/");
+	    strcat(pm->u.str, name);
 	}
-    } LASTALLOC;
-
+    } else {
+	pm->u.str = dupstring("");
+	pm->flags |= PM_UNSET;
+    }
     return (HashNode) pm;
 }
 
@@ -293,7 +307,9 @@ scanpmcommands(HashTable ht, ScanFunc func, int flags)
 	for (hn = cmdnamtab->nodes[i]; hn; hn = hn->next) {
 	    pm.nam = hn->nam;
 	    cmd = (Cmdnam) hn;
-	    if (func != scancountparams) {
+	    if (func != scancountparams &&
+		((flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL)) ||
+		 !(flags & SCANPM_WANTKEYS))) {
 		if (cmd->flags & HASHED)
 		    pm.u.str = cmd->u.cmd;
 		else {
@@ -312,43 +328,37 @@ scanpmcommands(HashTable ht, ScanFunc func, int flags)
 
 /**/
 static void
-setfunction(char *name, char *value)
+setfunction(char *name, char *val, int dis)
 {
-    char *val;
+    char *value = dupstring(val);
     Shfunc shf;
-    List list;
+    Eprog prog;
     int sn;
 
-    val = ztrdup(value);
     val = metafy(val, strlen(val), META_REALLOC);
 
-    HEAPALLOC {
-	list = parse_string(val);
-    } LASTALLOC;
+    prog = parse_string(val, 1);
 
-    if (!list || list == &dummy_list) {
-	zwarnnam(NULL, "invalid function definition", val, 0);
+    if (!prog || prog == &dummy_eprog) {
+	zwarn("invalid function definition", value, 0);
 	zsfree(val);
 	return;
     }
-    PERMALLOC {
-	shf = (Shfunc) zalloc(sizeof(*shf));
-	shf->funcdef = (List) dupstruct(list);
-	shf->flags = 0;
-
-	if (!strncmp(name, "TRAP", 4) &&
-	    (sn = getsignum(name + 4)) != -1) {
-	    if (settrap(sn, shf->funcdef)) {
-		freestruct(shf->funcdef);
-		zfree(shf, sizeof(*shf));
-		zsfree(val);
-		LASTALLOC_RETURN;
-	    }
-	    sigtrapped[sn] |= ZSIG_FUNC;
+    shf = (Shfunc) zalloc(sizeof(*shf));
+    shf->funcdef = dupeprog(prog, 0);
+    shf->flags = dis;
+
+    if (!strncmp(name, "TRAP", 4) &&
+	(sn = getsignum(name + 4)) != -1) {
+	if (settrap(sn, shf->funcdef)) {
+	    freeeprog(shf->funcdef);
+	    zfree(shf, sizeof(*shf));
+	    zsfree(val);
+	    return;
 	}
-	shfunctab->addnode(shfunctab, ztrdup(name), shf);
-    } LASTALLOC;
-
+	sigtrapped[sn] |= ZSIG_FUNC;
+    }
+    shfunctab->addnode(shfunctab, ztrdup(name), shf);
     zsfree(val);
 }
 
@@ -356,7 +366,14 @@ setfunction(char *name, char *value)
 static void
 setpmfunction(Param pm, char *value)
 {
-    setfunction(pm->nam, value);
+    setfunction(pm->nam, value, 0);
+}
+
+/**/
+static void
+setpmdisfunction(Param pm, char *value)
+{
+    setfunction(pm->nam, value, DISABLED);
 }
 
 /**/
@@ -371,11 +388,14 @@ unsetpmfunction(Param pm, int exp)
 
 /**/
 static void
-setpmfunctions(Param pm, HashTable ht)
+setfunctions(Param pm, HashTable ht, int dis)
 {
     int i;
     HashNode hn;
 
+    if (!ht)
+	return;
+
     for (i = 0; i < ht->hsize; i++)
 	for (hn = ht->nodes[i]; hn; hn = hn->next) {
 	    struct value v;
@@ -385,62 +405,100 @@ setpmfunctions(Param pm, HashTable ht)
 	    v.arr = NULL;
 	    v.pm = (Param) hn;
 
-	    setfunction(hn->nam, getstrvalue(&v));
+	    setfunction(hn->nam, ztrdup(getstrvalue(&v)), dis);
 	}
+    deleteparamtable(ht);
+}
+
+/**/
+static void
+setpmfunctions(Param pm, HashTable ht)
+{
+    setfunctions(pm, ht, 0);
+}
+
+/**/
+static void
+setpmdisfunctions(Param pm, HashTable ht)
+{
+    setfunctions(pm, ht, DISABLED);
 }
 
 /**/
 static HashNode
-getpmfunction(HashTable ht, char *name)
+getfunction(HashTable ht, char *name, int dis)
 {
     Shfunc shf;
     Param pm = NULL;
 
-    HEAPALLOC {
-	pm = (Param) zhalloc(sizeof(struct param));
-	pm->nam = dupstring(name);
-	pm->flags = PM_SCALAR;
-	pm->sets.cfn = setpmfunction;
-	pm->gets.cfn = strgetfn;
-	pm->unsetfn = unsetpmfunction;
-	pm->ct = 0;
-	pm->env = NULL;
-	pm->ename = NULL;
-	pm->old = NULL;
-	pm->level = 0;
-
-	if ((shf = (Shfunc) shfunctab->getnode(shfunctab, name))) {
-	    if (shf->flags & PM_UNDEFINED)
-		pm->u.str = "undefined";
-	    else {
-		char *t = getpermtext((void *) dupstruct((void *)
-							 shf->funcdef)), *h;
-
-		h = dupstring(t);
-		zsfree(t);
-		unmetafy(h, NULL);
-
-		pm->u.str = h;
-	    }
+    pm = (Param) zhalloc(sizeof(struct param));
+    pm->nam = dupstring(name);
+    pm->flags = PM_SCALAR;
+    pm->sets.cfn = (dis ? setpmdisfunction : setpmfunction);
+    pm->gets.cfn = strgetfn;
+    pm->unsetfn = unsetpmfunction;
+    pm->ct = 0;
+    pm->env = NULL;
+    pm->ename = NULL;
+    pm->old = NULL;
+    pm->level = 0;
+
+    if ((shf = (Shfunc) shfunctab->getnode2(shfunctab, name)) &&
+	(dis ? (shf->flags & DISABLED) : !(shf->flags & DISABLED))) {
+	if (shf->flags & PM_UNDEFINED) {
+	    pm->u.str = dyncat("builtin autoload -X",
+			       ((shf->flags & PM_UNALIASED) ?
+				((shf->flags & PM_TAGGED) ? "Ut" : "U") :
+				((shf->flags & PM_TAGGED) ? "t" : "")));
 	} else {
-	    pm->u.str = "";
-	    pm->flags |= PM_UNSET;
+	    char *t = getpermtext(shf->funcdef, NULL), *n, *h;
+
+	    if (shf->funcdef->flags & EF_RUN) {
+		n = nicedupstring(name);
+		h = (char *) zhalloc(strlen(t) + strlen(n) + 9);
+		h[0] = '\t';
+		strcpy(h + 1, t);
+		strcat(h, "\n\t");
+		strcat(h, n);
+		strcat(h, " \"$@\"");
+	    } else
+		h = dyncat("\t", t);
+	    zsfree(t);
+	    unmetafy(h, NULL);
+
+	    pm->u.str = h;
 	}
-    } LASTALLOC;
-
+    } else {
+	pm->u.str = dupstring("");
+	pm->flags |= PM_UNSET;
+    }
     return (HashNode) pm;
 }
 
 /**/
+static HashNode
+getpmfunction(HashTable ht, char *name)
+{
+    return getfunction(ht, name, 0);
+}
+
+/**/
+static HashNode
+getpmdisfunction(HashTable ht, char *name)
+{
+    return getfunction(ht, name, DISABLED);
+}
+
+/**/
 static void
-scanpmfunctions(HashTable ht, ScanFunc func, int flags)
+scanfunctions(HashTable ht, ScanFunc func, int flags, int dis)
 {
     struct param pm;
     int i;
     HashNode hn;
 
     pm.flags = PM_SCALAR;
-    pm.sets.cfn = setpmcommand;
+    pm.sets.cfn = (dis ? setpmdisfunction : setpmfunction);
     pm.gets.cfn = strgetfn;
     pm.unsetfn = unsetpmcommand;
     pm.ct = 0;
@@ -451,16 +509,32 @@ scanpmfunctions(HashTable ht, ScanFunc func, int flags)
 
     for (i = 0; i < shfunctab->hsize; i++)
 	for (hn = shfunctab->nodes[i]; hn; hn = hn->next) {
-	    if (!(hn->flags & DISABLED)) {
+	    if (dis ? (hn->flags & DISABLED) : !(hn->flags & DISABLED)) {
 		pm.nam = hn->nam;
-		if (func != scancountparams) {
-		    if (((Shfunc) hn)->flags & PM_UNDEFINED)
-			pm.u.str = "undefined";
-		    else {
-			char *t = getpermtext((void *)
-					      dupstruct((void *) ((Shfunc) hn)->funcdef));
-
-			unmetafy((pm.u.str = dupstring(t)), NULL);
+		if (func != scancountparams &&
+		    ((flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL)) ||
+		     !(flags & SCANPM_WANTKEYS))) {
+		    if (((Shfunc) hn)->flags & PM_UNDEFINED) {
+			Shfunc shf = (Shfunc) hn;
+			pm.u.str =
+			    dyncat("builtin autoload -X",
+				   ((shf->flags & PM_UNALIASED) ?
+				    ((shf->flags & PM_TAGGED) ? "Ut" : "U") :
+				    ((shf->flags & PM_TAGGED) ? "t" : "")));
+		    } else {
+			char *t = getpermtext(((Shfunc) hn)->funcdef, NULL), *n;
+
+			if (((Shfunc) hn)->funcdef->flags & EF_RUN) {
+			    n = nicedupstring(hn->nam);
+			    pm.u.str = (char *) zhalloc(strlen(t) + strlen(n) + 9);
+			    pm.u.str[0] = '\t';
+			    strcpy(pm.u.str + 1, t);
+			    strcat(pm.u.str, "\n\t");
+			    strcat(pm.u.str, n);
+			    strcat(pm.u.str, " \"$@\"");
+			} else
+			    pm.u.str = dyncat("\t", t);
+			unmetafy(pm.u.str, NULL);
 			zsfree(t);
 		    }
 		}
@@ -469,6 +543,173 @@ scanpmfunctions(HashTable ht, ScanFunc func, int flags)
 	}
 }
 
+/**/
+static void
+scanpmfunctions(HashTable ht, ScanFunc func, int flags)
+{
+    scanfunctions(ht, func, flags, 0);
+}
+
+/**/
+static void
+scanpmdisfunctions(HashTable ht, ScanFunc func, int flags)
+{
+    scanfunctions(ht, func, flags, DISABLED);
+}
+
+/* Functions for the funcstack special parameter. */
+
+/**/
+static char **
+funcstackgetfn(Param pm)
+{
+    Funcstack f;
+    int num;
+    char **ret, **p;
+
+    for (f = funcstack, num = 0; f; f = f->prev, num++);
+
+    ret = (char **) zhalloc((num + 1) * sizeof(char *));
+
+    for (f = funcstack, p = ret; f; f = f->prev, p++)
+	*p = f->name;
+    *p = NULL;
+
+    return ret;
+}
+
+/* Functions for the builtins special parameter. */
+
+/**/
+static HashNode
+getbuiltin(HashTable ht, char *name, int dis)
+{
+    Param pm = NULL;
+    Builtin bn;
+
+    pm = (Param) zhalloc(sizeof(struct param));
+    pm->nam = dupstring(name);
+    pm->flags = PM_SCALAR | PM_READONLY;
+    pm->sets.cfn = NULL;
+    pm->gets.cfn = strgetfn;
+    pm->unsetfn = NULL;
+    pm->ct = 0;
+    pm->env = NULL;
+    pm->ename = NULL;
+    pm->old = NULL;
+    pm->level = 0;
+    if ((bn = (Builtin) builtintab->getnode2(builtintab, name)) &&
+	(dis ? (bn->flags & DISABLED) : !(bn->flags & DISABLED))) {
+	char *t = ((bn->handlerfunc || (bn->flags & BINF_PREFIX)) ?
+		   "defined" : "undefined");
+
+	pm->u.str = dupstring(t);
+    } else {
+	pm->u.str = dupstring("");
+	pm->flags |= PM_UNSET;
+    }
+    return (HashNode) pm;
+}
+
+/**/
+static HashNode
+getpmbuiltin(HashTable ht, char *name)
+{
+    return getbuiltin(ht, name, 0);
+}
+
+/**/
+static HashNode
+getpmdisbuiltin(HashTable ht, char *name)
+{
+    return getbuiltin(ht, name, DISABLED);
+}
+
+/**/
+static void
+scanbuiltins(HashTable ht, ScanFunc func, int flags, int dis)
+{
+    struct param pm;
+    int i;
+    HashNode hn;
+
+    pm.flags = PM_SCALAR | PM_READONLY;
+    pm.sets.cfn = NULL;
+    pm.gets.cfn = strgetfn;
+    pm.unsetfn = NULL;
+    pm.ct = 0;
+    pm.env = NULL;
+    pm.ename = NULL;
+    pm.old = NULL;
+    pm.level = 0;
+
+    for (i = 0; i < builtintab->hsize; i++)
+	for (hn = builtintab->nodes[i]; hn; hn = hn->next) {
+	    if (dis ? (hn->flags & DISABLED) : !(hn->flags & DISABLED)) {
+		pm.nam = hn->nam;
+		if (func != scancountparams &&
+		    ((flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL)) ||
+		     !(flags & SCANPM_WANTKEYS))) {
+		    char *t = ((((Builtin) hn)->handlerfunc ||
+				(hn->flags & BINF_PREFIX)) ?
+			       "defined" : "undefined");
+
+		    pm.u.str = dupstring(t);
+		}
+		func((HashNode) &pm, flags);
+	    }
+	}
+}
+
+/**/
+static void
+scanpmbuiltins(HashTable ht, ScanFunc func, int flags)
+{
+    scanbuiltins(ht, func, flags, 0);
+}
+
+/**/
+static void
+scanpmdisbuiltins(HashTable ht, ScanFunc func, int flags)
+{
+    scanbuiltins(ht, func, flags, DISABLED);
+}
+
+/* Functions for the reswords special parameter. */
+
+/**/
+static char **
+getreswords(int dis)
+{
+    int i;
+    HashNode hn;
+    char **ret, **p;
+
+    p = ret = (char **) zhalloc((reswdtab->ct + 1) * sizeof(char *));
+
+    for (i = 0; i < reswdtab->hsize; i++)
+	for (hn = reswdtab->nodes[i]; hn; hn = hn->next)
+	    if (dis ? (hn->flags & DISABLED) : !(hn->flags & DISABLED))
+		*p++ = dupstring(hn->nam);
+    *p = NULL;
+
+    return ret;
+}
+
+/**/
+static char **
+reswordsgetfn(Param pm)
+{
+    return getreswords(0);
+}
+
+/**/
+static char **
+disreswordsgetfn(Param pm)
+{
+    return getreswords(DISABLED);
+}
+
 /* Functions for the options special parameter. */
 
 /**/
@@ -478,11 +719,12 @@ setpmoption(Param pm, char *value)
     int n;
 
     if (!value || (strcmp(value, "on") && strcmp(value, "off")))
-	zwarnnam(NULL, "invalid value: %s", value, 0);
+	zwarn("invalid value: %s", value, 0);
     else if (!(n = optlookup(pm->nam)))
-	zwarnnam(NULL, "no such option: %s", pm->nam, 0);
+	zwarn("no such option: %s", pm->nam, 0);
     else if (dosetopt(n, (value && strcmp(value, "off")), 0))
-	zwarnnam(NULL, "can't change option: %s", pm->nam, 0);
+	zwarn("can't change option: %s", pm->nam, 0);
+    zsfree(value);
 }
 
 /**/
@@ -492,9 +734,9 @@ unsetpmoption(Param pm, int exp)
     int n;
 
     if (!(n = optlookup(pm->nam)))
-	zwarnnam(NULL, "no such option: %s", pm->nam, 0);
+	zwarn("no such option: %s", pm->nam, 0);
     else if (dosetopt(n, 0, 0))
-	zwarnnam(NULL, "can't change option: %s", pm->nam, 0);
+	zwarn("can't change option: %s", pm->nam, 0);
 }
 
 /**/
@@ -504,6 +746,9 @@ setpmoptions(Param pm, HashTable ht)
     int i;
     HashNode hn;
 
+    if (!ht)
+	return;
+
     for (i = 0; i < ht->hsize; i++)
 	for (hn = ht->nodes[i]; hn; hn = hn->next) {
 	    struct value v;
@@ -516,11 +761,12 @@ setpmoptions(Param pm, HashTable ht)
 
 	    val = getstrvalue(&v);
 	    if (!val || (strcmp(val, "on") && strcmp(val, "off")))
-		zwarnnam(NULL, "invalid value: %s", val, 0);
+		zwarn("invalid value: %s", val, 0);
 	    else if (dosetopt(optlookup(hn->nam),
 			      (val && strcmp(val, "off")), 0))
-		zwarnnam(NULL, "can't change option: %s", hn->nam, 0);
+		zwarn("can't change option: %s", hn->nam, 0);
 	}
+    deleteparamtable(ht);
 }
 
 /**/
@@ -530,27 +776,24 @@ getpmoption(HashTable ht, char *name)
     Param pm = NULL;
     int n;
 
-    HEAPALLOC {
-	pm = (Param) zhalloc(sizeof(struct param));
-	pm->nam = dupstring(name);
-	pm->flags = PM_SCALAR;
-	pm->sets.cfn = setpmoption;
-	pm->gets.cfn = strgetfn;
-	pm->unsetfn = unsetpmoption;
-	pm->ct = 0;
-	pm->env = NULL;
-	pm->ename = NULL;
-	pm->old = NULL;
-	pm->level = 0;
-
-	if ((n = optlookup(name)))
-	    pm->u.str = dupstring(opts[n] ? "on" : "off");
-	else {
-	    pm->u.str = "";
-	    pm->flags |= PM_UNSET;
-	}
-    } LASTALLOC;
-
+    pm = (Param) zhalloc(sizeof(struct param));
+    pm->nam = dupstring(name);
+    pm->flags = PM_SCALAR;
+    pm->sets.cfn = setpmoption;
+    pm->gets.cfn = strgetfn;
+    pm->unsetfn = unsetpmoption;
+    pm->ct = 0;
+    pm->env = NULL;
+    pm->ename = NULL;
+    pm->old = NULL;
+    pm->level = 0;
+
+    if ((n = optlookup(name)))
+	pm->u.str = dupstring(opts[n] ? "on" : "off");
+    else {
+	pm->u.str = dupstring("");
+	pm->flags |= PM_UNSET;
+    }
     return (HashNode) pm;
 }
 
@@ -574,89 +817,1147 @@ scanpmoptions(HashTable ht, ScanFunc func, int flags)
 
     for (i = 0; i < optiontab->hsize; i++)
 	for (hn = optiontab->nodes[i]; hn; hn = hn->next) {
+	    int optno = ((Optname) hn)->optno, ison;
 	    pm.nam = hn->nam;
-	    pm.u.str = opts[((Optname) hn)->optno] ? "on" : "off";
+	    ison = optno < 0 ? !opts[-optno] : opts[optno];
+	    pm.u.str = dupstring(ison ? "on" : "off");
 	    func((HashNode) &pm, flags);
 	}
 }
 
-/* Names and Params for the special parameters. */
+/* Functions for the modules special parameter. */
 
-#define PAR_NAM "parameters"
-#define CMD_NAM "commands"
-#define FUN_NAM "functions"
-#define OPT_NAM "options"
+static char *modpmname;
+static int modpmfound;
 
-static Param parpm, cmdpm, funpm, optpm;
+/**/
+static void
+modpmbuiltinscan(HashNode hn, int dummy)
+{
+    if (!(((Builtin) hn)->flags & BINF_ADDED) &&
+	!strcmp(((Builtin) hn)->optstr, modpmname))
+	modpmfound = 1;
+}
+
+/**/
+static void
+modpmparamscan(HashNode hn, int dummy)
+{
+    if ((((Param) hn)->flags & PM_AUTOLOAD) &&
+	!strcmp(((Param) hn)->u.str, modpmname))
+	modpmfound = 1;
+}
+
+/**/
+static int
+findmodnode(LinkList l, char *nam)
+{
+    LinkNode node;
+
+    for (node = firstnode(l); node; incnode(node))
+	if (!strcmp(nam, (char *) getdata(node)))
+	    return 1;
+
+    return 0;
+}
+
+/**/
+static HashNode
+getpmmodule(HashTable ht, char *name)
+{
+    Param pm = NULL;
+    char *type = NULL;
+    LinkNode node;
+
+    pm = (Param) zhalloc(sizeof(struct param));
+    pm->nam = dupstring(name);
+    pm->flags = PM_SCALAR | PM_READONLY;
+    pm->sets.cfn = NULL;
+    pm->gets.cfn = strgetfn;
+    pm->unsetfn = NULL;
+    pm->ct = 0;
+    pm->env = NULL;
+    pm->ename = NULL;
+    pm->old = NULL;
+    pm->level = 0;
+
+    if (!type) {
+	Module m;
+
+	for (node = firstnode(modules); node; incnode(node)) {
+	    m = (Module) getdata(node);
+	    if (m->u.handle && !(m->flags & MOD_UNLOAD) &&
+		!strcmp(name, m->nam)) {
+		type = "loaded";
+		break;
+	    }
+	}
+    }
+    modpmname = name;
+    modpmfound = 0;
+    if (!type) {
+	scanhashtable(builtintab, 0, 0, 0, modpmbuiltinscan, 0);
+	if (!modpmfound) {
+	    Conddef p;
+
+	    for (p = condtab; p; p = p->next)
+		if (p->module && !strcmp(name, p->module)) {
+		    modpmfound = 1;
+		    break;
+		}
+	    if (!modpmfound)
+		scanhashtable(realparamtab, 0, 0, 0, modpmparamscan, 0);
+	}
+	if (modpmfound)
+	    type = "autoloaded";
+    }
+    if (type)
+	pm->u.str = dupstring(type);
+    else {
+	pm->u.str = dupstring("");
+	pm->flags |= PM_UNSET;
+    }
+    return (HashNode) pm;
+}
+
+/**/
+static void
+scanpmmodules(HashTable ht, ScanFunc func, int flags)
+{
+    struct param pm;
+    int i;
+    HashNode hn;
+    LinkList done = newlinklist();
+    LinkNode node;
+    Module m;
+    Conddef p;
+
+    pm.flags = PM_SCALAR | PM_READONLY;
+    pm.sets.cfn = NULL;
+    pm.gets.cfn = strgetfn;
+    pm.unsetfn = NULL;
+    pm.ct = 0;
+    pm.env = NULL;
+    pm.ename = NULL;
+    pm.old = NULL;
+    pm.level = 0;
+
+    pm.u.str = dupstring("builtin");
+    pm.u.str = dupstring("loaded");
+    for (node = firstnode(modules); node; incnode(node)) {
+	m = (Module) getdata(node);
+	if (m->u.handle && !(m->flags & MOD_UNLOAD)) {
+	    pm.nam = m->nam;
+	    addlinknode(done, pm.nam);
+	    func((HashNode) &pm, flags);
+	}
+    }
+    pm.u.str = dupstring("autoloaded");
+    for (i = 0; i < builtintab->hsize; i++)
+	for (hn = builtintab->nodes[i]; hn; hn = hn->next) {
+	    if (!(((Builtin) hn)->flags & BINF_ADDED) &&
+		!findmodnode(done, ((Builtin) hn)->optstr)) {
+		pm.nam = ((Builtin) hn)->optstr;
+		addlinknode(done, pm.nam);
+		func((HashNode) &pm, flags);
+	    }
+	}
+    for (p = condtab; p; p = p->next)
+	if (p->module && !findmodnode(done, p->module)) {
+	    pm.nam = p->module;
+	    addlinknode(done, pm.nam);
+	    func((HashNode) &pm, flags);
+	}
+    for (i = 0; i < realparamtab->hsize; i++)
+	for (hn = realparamtab->nodes[i]; hn; hn = hn->next) {
+	    if ((((Param) hn)->flags & PM_AUTOLOAD) &&
+		!findmodnode(done, ((Param) hn)->u.str)) {
+		pm.nam = ((Param) hn)->u.str;
+		addlinknode(done, pm.nam);
+		func((HashNode) &pm, flags);
+	    }
+	}
+}
+
+/* Functions for the dirstack special parameter. */
+
+/**/
+static void
+dirssetfn(Param pm, char **x)
+{
+    char **ox = x;
+
+    if (!incleanup) {
+	freelinklist(dirstack, freestr);
+	dirstack = znewlinklist();
+	while (x && *x)
+	    zaddlinknode(dirstack, ztrdup(*x++));
+    }
+    if (ox)
+	freearray(ox);
+}
+
+/**/
+static char **
+dirsgetfn(Param pm)
+{
+    int l = countlinknodes(dirstack);
+    char **ret = (char **) zhalloc((l + 1) * sizeof(char *)), **p;
+    LinkNode n;
+
+    for (n = firstnode(dirstack), p = ret; n; incnode(n), p++)
+	*p = dupstring((char *) getdata(n));
+    *p = NULL;
+
+    return ret;
+}
+
+/* Functions for the history special parameter. */
+
+/**/
+static HashNode
+getpmhistory(HashTable ht, char *name)
+{
+    Param pm = NULL;
+    Histent he;
+
+    pm = (Param) zhalloc(sizeof(struct param));
+    pm->nam = dupstring(name);
+    pm->flags = PM_SCALAR | PM_READONLY;
+    pm->sets.cfn = NULL;
+    pm->gets.cfn = strgetfn;
+    pm->unsetfn = NULL;
+    pm->ct = 0;
+    pm->env = NULL;
+    pm->ename = NULL;
+    pm->old = NULL;
+    pm->level = 0;
+    if ((he = quietgethist(atoi(name))))
+	pm->u.str = dupstring(he->text);
+    else {
+	pm->u.str = dupstring("");
+	pm->flags |= PM_UNSET;
+    }
+    return (HashNode) pm;
+}
+
+/**/
+static void
+scanpmhistory(HashTable ht, ScanFunc func, int flags)
+{
+    struct param pm;
+    int i = addhistnum(curhist, -1, HIST_FOREIGN);
+    Histent he = quietgethistent(i, GETHIST_UPWARD);
+    char buf[40];
+
+    pm.flags = PM_SCALAR | PM_READONLY;
+    pm.sets.cfn = NULL;
+    pm.gets.cfn = strgetfn;
+    pm.unsetfn = NULL;
+    pm.ct = 0;
+    pm.env = NULL;
+    pm.ename = NULL;
+    pm.old = NULL;
+    pm.level = 0;
+
+    while (he) {
+	if (func != scancountparams) {
+	    sprintf(buf, "%d", he->histnum);
+	    pm.nam = dupstring(buf);
+	    if ((flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL)) ||
+		!(flags & SCANPM_WANTKEYS))
+		pm.u.str = dupstring(he->text);
+	}
+	func((HashNode) &pm, flags);
+
+	he = up_histent(he);
+    }
+}
+
+/* Function for the historywords special parameter. */
+
+/**/
+static char **
+histwgetfn(Param pm)
+{
+    char **ret, **p, *h, *e, sav;
+    LinkList l = newlinklist();
+    LinkNode n;
+    int i = addhistnum(curhist, -1, HIST_FOREIGN), iw;
+    Histent he = quietgethistent(i, GETHIST_UPWARD);
+
+    while (he) {
+	for (iw = he->nwords - 1; iw >= 0; iw--) {
+	    h = he->text + he->words[iw * 2];
+	    e = he->text + he->words[iw * 2 + 1];
+	    sav = *e;
+	    *e = '\0';
+	    addlinknode(l, dupstring(h));
+	    *e = sav;
+	}
+	he = up_histent(he);
+    }
+    ret = (char **) zhalloc((countlinknodes(l) + 1) * sizeof(char *));
+
+    for (p = ret, n = firstnode(l); n; incnode(n), p++)
+	*p = (char *) getdata(n);
+    *p = NULL;
+
+    return ret;
+}
+
+/* Functions for the jobtexts special parameter. */
+
+/**/
+static char *
+pmjobtext(int job)
+{
+    Process pn;
+    int len = 1;
+    char *ret;
+
+    for (pn = jobtab[job].procs; pn; pn = pn->next)
+	len += strlen(pn->text) + 3;
+
+    ret = (char *) zhalloc(len);
+    ret[0] = '\0';
+
+    for (pn = jobtab[job].procs; pn; pn = pn->next) {
+	strcat(ret, pn->text);
+	if (pn->next)
+	    strcat(ret, " | ");
+    }
+    return ret;
+}
+
+/**/
+static HashNode
+getpmjobtext(HashTable ht, char *name)
+{
+    Param pm = NULL;
+    int job;
+
+    pm = (Param) zhalloc(sizeof(struct param));
+    pm->nam = dupstring(name);
+    pm->flags = PM_SCALAR | PM_READONLY;
+    pm->sets.cfn = NULL;
+    pm->gets.cfn = strgetfn;
+    pm->unsetfn = NULL;
+    pm->ct = 0;
+    pm->env = NULL;
+    pm->ename = NULL;
+    pm->old = NULL;
+    pm->level = 0;
+
+    if ((job = atoi(name)) >= 1 && job < MAXJOB &&
+	jobtab[job].stat && jobtab[job].procs &&
+	!(jobtab[job].stat & STAT_NOPRINT))
+	pm->u.str = pmjobtext(job);
+    else {
+	pm->u.str = dupstring("");
+	pm->flags |= PM_UNSET;
+    }
+    return (HashNode) pm;
+}
+
+/**/
+static void
+scanpmjobtexts(HashTable ht, ScanFunc func, int flags)
+{
+    struct param pm;
+    int job;
+    char buf[40];
+
+    pm.flags = PM_SCALAR | PM_READONLY;
+    pm.sets.cfn = NULL;
+    pm.gets.cfn = strgetfn;
+    pm.unsetfn = NULL;
+    pm.ct = 0;
+    pm.env = NULL;
+    pm.ename = NULL;
+    pm.old = NULL;
+    pm.level = 0;
+
+    for (job = 1; job < MAXJOB; job++) {
+	if (jobtab[job].stat && jobtab[job].procs &&
+	    !(jobtab[job].stat & STAT_NOPRINT)) {
+	    if (func != scancountparams) {
+		sprintf(buf, "%d", job);
+		pm.nam = dupstring(buf);
+		if ((flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL)) ||
+		    !(flags & SCANPM_WANTKEYS))
+		    pm.u.str = pmjobtext(job);
+	    }
+	    func((HashNode) &pm, flags);
+	}
+    }
+}
+
+/* Functions for the jobstates special parameter. */
+
+/**/
+static char *
+pmjobstate(int job)
+{
+    Process pn;
+    char buf[256], buf2[128], *ret, *state;
+
+    if (jobtab[job].stat & STAT_DONE)
+	ret = dupstring("done");
+    else if (jobtab[job].stat & STAT_STOPPED)
+	ret = dupstring("suspended");
+    else
+	ret = dupstring("running");
+
+    for (pn = jobtab[job].procs; pn; pn = pn->next) {
+
+	if (pn->status == SP_RUNNING)
+	    state = "running";
+	else if (WIFEXITED(pn->status)) {
+	    if (WEXITSTATUS(pn->status))
+		sprintf((state = buf2), "exit %d", (pn->status));
+	    else
+		state = "done";
+	} else if (WIFSTOPPED(pn->status))
+	    state = sigmsg(WSTOPSIG(pn->status));
+	else if (WCOREDUMP(pn->status))
+	    sprintf((state = buf2), "%s (core dumped)",
+		    sigmsg(WTERMSIG(pn->status)));
+	else
+	    state = sigmsg(WTERMSIG(pn->status));
+
+	sprintf(buf, ":%d=%s", pn->pid, state);
+
+	ret = dyncat(ret, buf);
+    }
+    return ret;
+}
+
+/**/
+static HashNode
+getpmjobstate(HashTable ht, char *name)
+{
+    Param pm = NULL;
+    int job;
+
+    pm = (Param) zhalloc(sizeof(struct param));
+    pm->nam = dupstring(name);
+    pm->flags = PM_SCALAR | PM_READONLY;
+    pm->sets.cfn = NULL;
+    pm->gets.cfn = strgetfn;
+    pm->unsetfn = NULL;
+    pm->ct = 0;
+    pm->env = NULL;
+    pm->ename = NULL;
+    pm->old = NULL;
+    pm->level = 0;
+
+    if ((job = atoi(name)) >= 1 && job < MAXJOB &&
+	jobtab[job].stat && jobtab[job].procs &&
+	!(jobtab[job].stat & STAT_NOPRINT))
+	pm->u.str = pmjobstate(job);
+    else {
+	pm->u.str = dupstring("");
+	pm->flags |= PM_UNSET;
+    }
+    return (HashNode) pm;
+}
+
+/**/
+static void
+scanpmjobstates(HashTable ht, ScanFunc func, int flags)
+{
+    struct param pm;
+    int job;
+    char buf[40];
+
+    pm.flags = PM_SCALAR | PM_READONLY;
+    pm.sets.cfn = NULL;
+    pm.gets.cfn = strgetfn;
+    pm.unsetfn = NULL;
+    pm.ct = 0;
+    pm.env = NULL;
+    pm.ename = NULL;
+    pm.old = NULL;
+    pm.level = 0;
+
+    for (job = 1; job < MAXJOB; job++) {
+	if (jobtab[job].stat && jobtab[job].procs &&
+	    !(jobtab[job].stat & STAT_NOPRINT)) {
+	    if (func != scancountparams) {
+		sprintf(buf, "%d", job);
+		pm.nam = dupstring(buf);
+		if ((flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL)) ||
+		    !(flags & SCANPM_WANTKEYS))
+		    pm.u.str = pmjobstate(job);
+	    }
+	    func((HashNode) &pm, flags);
+	}
+    }
+}
+
+/* Functions for the jobdirs special parameter. */
+
+/**/
+static char *
+pmjobdir(int job)
+{
+    char *ret;
+
+    ret = dupstring(jobtab[job].pwd ? jobtab[job].pwd : pwd);
+    return ret;
+}
+
+/**/
+static HashNode
+getpmjobdir(HashTable ht, char *name)
+{
+    Param pm = NULL;
+    int job;
+
+    pm = (Param) zhalloc(sizeof(struct param));
+    pm->nam = dupstring(name);
+    pm->flags = PM_SCALAR | PM_READONLY;
+    pm->sets.cfn = NULL;
+    pm->gets.cfn = strgetfn;
+    pm->unsetfn = NULL;
+    pm->ct = 0;
+    pm->env = NULL;
+    pm->ename = NULL;
+    pm->old = NULL;
+    pm->level = 0;
+
+    if ((job = atoi(name)) >= 1 && job < MAXJOB &&
+	jobtab[job].stat && jobtab[job].procs &&
+	!(jobtab[job].stat & STAT_NOPRINT))
+	pm->u.str = pmjobdir(job);
+    else {
+	pm->u.str = dupstring("");
+	pm->flags |= PM_UNSET;
+    }
+    return (HashNode) pm;
+}
+
+/**/
+static void
+scanpmjobdirs(HashTable ht, ScanFunc func, int flags)
+{
+    struct param pm;
+    int job;
+    char buf[40];
+
+    pm.flags = PM_SCALAR | PM_READONLY;
+    pm.sets.cfn = NULL;
+    pm.gets.cfn = strgetfn;
+    pm.unsetfn = NULL;
+    pm.ct = 0;
+    pm.env = NULL;
+    pm.ename = NULL;
+    pm.old = NULL;
+    pm.level = 0;
+
+    for (job = 1; job < MAXJOB; job++) {
+       if (jobtab[job].stat && jobtab[job].procs &&
+           !(jobtab[job].stat & STAT_NOPRINT)) {
+           if (func != scancountparams) {
+	       sprintf(buf, "%d", job);
+	       pm.nam = dupstring(buf);
+               if ((flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL)) ||
+		   !(flags & SCANPM_WANTKEYS))
+		   pm.u.str = pmjobdir(job);
+	   }
+           func((HashNode) &pm, flags);
+       }
+    }
+}
+
+/* Functions for the nameddirs special parameter. */
+
+/**/
+static void
+setpmnameddir(Param pm, char *value)
+{
+    if (!value || *value != '/' || strlen(value) >= PATH_MAX)
+	zwarn("invalid value: %s", value, 0);
+    else
+	adduserdir(pm->nam, value, 0, 1);
+    zsfree(value);
+}
+
+/**/
+static void
+unsetpmnameddir(Param pm, int exp)
+{
+    HashNode hd = nameddirtab->removenode(nameddirtab, pm->nam);
+
+    if (hd)
+	nameddirtab->freenode(hd);
+}
+
+/**/
+static void
+setpmnameddirs(Param pm, HashTable ht)
+{
+    int i;
+    HashNode hn, next, hd;
+
+    if (!ht)
+	return;
+
+    for (i = 0; i < nameddirtab->hsize; i++)
+	for (hn = nameddirtab->nodes[i]; hn; hn = next) {
+	    next = hn->next;
+	    if (!(((Nameddir) hn)->flags & ND_USERNAME) &&
+		(hd = nameddirtab->removenode(nameddirtab, hn->nam)))
+		nameddirtab->freenode(hd);
+	}
+
+    for (i = 0; i < ht->hsize; i++)
+	for (hn = ht->nodes[i]; hn; hn = hn->next) {
+	    struct value v;
+	    char *val;
+
+	    v.isarr = v.inv = v.a = 0;
+	    v.b = -1;
+	    v.arr = NULL;
+	    v.pm = (Param) hn;
+
+	    if (!(val = getstrvalue(&v)) || *val != '/' ||
+		strlen(val) >= PATH_MAX)
+		zwarn("invalid value: %s", val, 0);
+	    else
+		adduserdir(hn->nam, val, 0, 1);
+	}
+
+    /* The INTERACTIVE stuff ensures that the dirs are not immediatly removed
+     * when the sub-pms are deleted. */
+
+    i = opts[INTERACTIVE];
+    opts[INTERACTIVE] = 0;
+    deleteparamtable(ht);
+    opts[INTERACTIVE] = i;
+}
+
+/**/
+static HashNode
+getpmnameddir(HashTable ht, char *name)
+{
+    Param pm = NULL;
+    Nameddir nd;
+
+    pm = (Param) zhalloc(sizeof(struct param));
+    pm->nam = dupstring(name);
+    pm->flags = PM_SCALAR;
+    pm->sets.cfn = setpmnameddir;
+    pm->gets.cfn = strgetfn;
+    pm->unsetfn = unsetpmnameddir;
+    pm->ct = 0;
+    pm->env = NULL;
+    pm->ename = NULL;
+    pm->old = NULL;
+    pm->level = 0;
+    if ((nd = (Nameddir) nameddirtab->getnode(nameddirtab, name)) &&
+	!(nd->flags & ND_USERNAME))
+	pm->u.str = dupstring(nd->dir);
+    else {
+	pm->u.str = dupstring("");
+	pm->flags |= PM_UNSET;
+    }
+    return (HashNode) pm;
+}
+
+/**/
+static void
+scanpmnameddirs(HashTable ht, ScanFunc func, int flags)
+{
+    struct param pm;
+    int i;
+    HashNode hn;
+    Nameddir nd;
+
+    pm.flags = PM_SCALAR;
+    pm.sets.cfn = setpmnameddir;
+    pm.gets.cfn = strgetfn;
+    pm.unsetfn = unsetpmnameddir;
+    pm.ct = 0;
+    pm.env = NULL;
+    pm.ename = NULL;
+    pm.old = NULL;
+    pm.level = 0;
+
+    for (i = 0; i < nameddirtab->hsize; i++)
+	for (hn = nameddirtab->nodes[i]; hn; hn = hn->next) {
+	    if (!((nd = (Nameddir) hn)->flags & ND_USERNAME)) {
+		pm.nam = hn->nam;
+		if (func != scancountparams &&
+		    ((flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL)) ||
+		     !(flags & SCANPM_WANTKEYS)))
+		    pm.u.str = dupstring(nd->dir);
+		func((HashNode) &pm, flags);
+	    }
+	}
+}
+
+/* Functions for the userdirs special parameter. */
+
+/**/
+static HashNode
+getpmuserdir(HashTable ht, char *name)
+{
+    Param pm = NULL;
+    Nameddir nd;
+
+    nameddirtab->filltable(nameddirtab);
+
+    pm = (Param) zhalloc(sizeof(struct param));
+    pm->nam = dupstring(name);
+    pm->flags = PM_SCALAR | PM_READONLY;
+    pm->sets.cfn = NULL;
+    pm->gets.cfn = strgetfn;
+    pm->unsetfn = NULL;
+    pm->ct = 0;
+    pm->env = NULL;
+    pm->ename = NULL;
+    pm->old = NULL;
+    pm->level = 0;
+    if ((nd = (Nameddir) nameddirtab->getnode(nameddirtab, name)) &&
+	(nd->flags & ND_USERNAME))
+	pm->u.str = dupstring(nd->dir);
+    else {
+	pm->u.str = dupstring("");
+	pm->flags |= PM_UNSET;
+    }
+    return (HashNode) pm;
+}
+
+/**/
+static void
+scanpmuserdirs(HashTable ht, ScanFunc func, int flags)
+{
+    struct param pm;
+    int i;
+    HashNode hn;
+    Nameddir nd;
+
+    nameddirtab->filltable(nameddirtab);
+
+    pm.flags = PM_SCALAR | PM_READONLY;
+    pm.sets.cfn = NULL;
+    pm.gets.cfn = strgetfn;
+    pm.unsetfn = NULL;
+    pm.ct = 0;
+    pm.env = NULL;
+    pm.ename = NULL;
+    pm.old = NULL;
+    pm.level = 0;
+
+    for (i = 0; i < nameddirtab->hsize; i++)
+	for (hn = nameddirtab->nodes[i]; hn; hn = hn->next) {
+	    if ((nd = (Nameddir) hn)->flags & ND_USERNAME) {
+		pm.nam = hn->nam;
+		if (func != scancountparams &&
+		    ((flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL)) ||
+		     !(flags & SCANPM_WANTKEYS)))
+		    pm.u.str = dupstring(nd->dir);
+		func((HashNode) &pm, flags);
+	    }
+	}
+}
+
+/* Functions for the regularaliases and globalaliases special parameters. */
+
+/**/
+static void
+setralias(Param pm, char *value, int dis)
+{
+    aliastab->addnode(aliastab, ztrdup(pm->nam), createaliasnode(value, dis));
+}
+
+/**/
+static void
+setpmralias(Param pm, char *value)
+{
+    setralias(pm, value, 0);
+}
+
+/**/
+static void
+setpmdisralias(Param pm, char *value)
+{
+    setralias(pm, value, DISABLED);
+}
+
+/**/
+static void
+setgalias(Param pm, char *value, int dis)
+{
+    aliastab->addnode(aliastab, ztrdup(pm->nam),
+		      createaliasnode(value, dis | ALIAS_GLOBAL));
+}
+
+/**/
+static void
+setpmgalias(Param pm, char *value)
+{
+    setgalias(pm, value, 0);
+}
+
+/**/
+static void
+setpmdisgalias(Param pm, char *value)
+{
+    setgalias(pm, value, DISABLED);
+}
+
+/**/
+static void
+unsetpmalias(Param pm, int exp)
+{
+    HashNode hd = aliastab->removenode(aliastab, pm->nam);
+
+    if (hd)
+	aliastab->freenode(hd);
+}
+
+/**/
+static void
+setaliases(Param pm, HashTable ht, int global, int dis)
+{
+    int i;
+    HashNode hn, next, hd;
+
+    if (!ht)
+	return;
+
+    for (i = 0; i < aliastab->hsize; i++)
+	for (hn = aliastab->nodes[i]; hn; hn = next) {
+	    next = hn->next;
+	    if (((global && (((Alias) hn)->flags & ALIAS_GLOBAL)) ||
+		 (!global && !(((Alias) hn)->flags & ALIAS_GLOBAL))) &&
+		(hd = aliastab->removenode(aliastab, hn->nam)))
+		aliastab->freenode(hd);
+	}
+
+    for (i = 0; i < ht->hsize; i++)
+	for (hn = ht->nodes[i]; hn; hn = hn->next) {
+	    struct value v;
+	    char *val;
+
+	    v.isarr = v.inv = v.a = 0;
+	    v.b = -1;
+	    v.arr = NULL;
+	    v.pm = (Param) hn;
+
+	    if ((val = getstrvalue(&v)))
+		aliastab->addnode(aliastab, ztrdup(hn->nam),
+				  createaliasnode(ztrdup(val),
+						  (global ? ALIAS_GLOBAL : 0) |
+						  (dis ? DISABLED : 0)));
+	}
+    deleteparamtable(ht);
+}
+
+/**/
+static void
+setpmraliases(Param pm, HashTable ht)
+{
+    setaliases(pm, ht, 0, 0);
+}
+
+/**/
+static void
+setpmdisraliases(Param pm, HashTable ht)
+{
+    setaliases(pm, ht, 0, DISABLED);
+}
+
+/**/
+static void
+setpmgaliases(Param pm, HashTable ht)
+{
+    setaliases(pm, ht, 1, 0);
+}
+
+/**/
+static void
+setpmdisgaliases(Param pm, HashTable ht)
+{
+    setaliases(pm, ht, 1, DISABLED);
+}
+
+/**/
+static HashNode
+getalias(HashTable ht, char *name, int global, int dis)
+{
+    Param pm = NULL;
+    Alias al;
+
+    pm = (Param) zhalloc(sizeof(struct param));
+    pm->nam = dupstring(name);
+    pm->flags = PM_SCALAR;
+    pm->sets.cfn = (global ? (dis ? setpmdisgalias : setpmgalias) :
+		    (dis ? setpmdisralias : setpmralias));
+    pm->gets.cfn = strgetfn;
+    pm->unsetfn = unsetpmalias;
+    pm->ct = 0;
+    pm->env = NULL;
+    pm->ename = NULL;
+    pm->old = NULL;
+    pm->level = 0;
+    if ((al = (Alias) aliastab->getnode2(aliastab, name)) &&
+	((global && (al->flags & ALIAS_GLOBAL)) ||
+	 (!global && !(al->flags & ALIAS_GLOBAL))) &&
+	(dis ? (al->flags & DISABLED) : !(al->flags & DISABLED)))
+	pm->u.str = dupstring(al->text);
+    else {
+	pm->u.str = dupstring("");
+	pm->flags |= PM_UNSET;
+    }
+    return (HashNode) pm;
+}
+
+/**/
+static HashNode
+getpmralias(HashTable ht, char *name)
+{
+    return getalias(ht, name, 0, 0);
+}
+
+/**/
+static HashNode
+getpmdisralias(HashTable ht, char *name)
+{
+    return getalias(ht, name, 0, 0);
+}
+
+/**/
+static HashNode
+getpmgalias(HashTable ht, char *name)
+{
+    return getalias(ht, name, 1, 0);
+}
+
+/**/
+static HashNode
+getpmdisgalias(HashTable ht, char *name)
+{
+    return getalias(ht, name, 1, DISABLED);
+}
+
+/**/
+static void
+scanaliases(HashTable ht, ScanFunc func, int flags, int global, int dis)
+{
+    struct param pm;
+    int i;
+    HashNode hn;
+    Alias al;
+
+    pm.flags = PM_SCALAR;
+    pm.sets.cfn = (global ? (dis ? setpmdisgalias : setpmgalias) :
+		   (dis ? setpmdisralias : setpmralias));
+    pm.gets.cfn = strgetfn;
+    pm.unsetfn = unsetpmalias;
+    pm.ct = 0;
+    pm.env = NULL;
+    pm.ename = NULL;
+    pm.old = NULL;
+    pm.level = 0;
+
+    for (i = 0; i < aliastab->hsize; i++)
+	for (hn = aliastab->nodes[i]; hn; hn = hn->next) {
+	    if (((global && ((al = (Alias) hn)->flags & ALIAS_GLOBAL)) ||
+		 (!global && !((al = (Alias) hn)->flags & ALIAS_GLOBAL))) &&
+		(dis ? (al->flags & DISABLED) : !(al->flags & DISABLED))) {
+		pm.nam = hn->nam;
+		if (func != scancountparams &&
+		    ((flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL)) ||
+		     !(flags & SCANPM_WANTKEYS)))
+		    pm.u.str = dupstring(al->text);
+		func((HashNode) &pm, flags);
+	    }
+	}
+}
+
+/**/
+static void
+scanpmraliases(HashTable ht, ScanFunc func, int flags)
+{
+    scanaliases(ht, func, flags, 0, 0);
+}
+
+/**/
+static void
+scanpmdisraliases(HashTable ht, ScanFunc func, int flags)
+{
+    scanaliases(ht, func, flags, 0, DISABLED);
+}
+
+/**/
+static void
+scanpmgaliases(HashTable ht, ScanFunc func, int flags)
+{
+    scanaliases(ht, func, flags, 1, 0);
+}
+
+/**/
+static void
+scanpmdisgaliases(HashTable ht, ScanFunc func, int flags)
+{
+    scanaliases(ht, func, flags, 1, DISABLED);
+}
+
+/* Table for defined parameters. */
+
+struct pardef {
+    char *name;
+    int flags;
+    GetNodeFunc getnfn;
+    ScanTabFunc scantfn;
+    void (*hsetfn) _((Param, HashTable));
+    void (*setfn) _((Param, char **));
+    char **(*getfn) _((Param));
+    void (*unsetfn) _((Param, int));
+    Param pm;
+};
+
+static struct pardef partab[] = {
+    { "parameters", PM_READONLY,
+      getpmparameter, scanpmparameters, hashsetfn,
+      NULL, NULL, stdunsetfn, NULL },
+    { "commands", 0,
+      getpmcommand, scanpmcommands, setpmcommands,
+      NULL, NULL, stdunsetfn, NULL },
+    { "functions", 0,
+      getpmfunction, scanpmfunctions, setpmfunctions,
+      NULL, NULL, stdunsetfn, NULL },
+    { "dis_functions", 0,
+      getpmdisfunction, scanpmdisfunctions, setpmdisfunctions,
+      NULL, NULL, stdunsetfn, NULL },
+    { "funcstack", PM_ARRAY|PM_SPECIAL|PM_READONLY,
+      NULL, NULL, NULL,
+      arrsetfn, funcstackgetfn, stdunsetfn, NULL },
+    { "builtins", PM_READONLY,
+      getpmbuiltin, scanpmbuiltins, hashsetfn,
+      NULL, NULL, stdunsetfn, NULL },
+    { "dis_builtins", PM_READONLY,
+      getpmdisbuiltin, scanpmdisbuiltins, hashsetfn,
+      NULL, NULL, stdunsetfn, NULL },
+    { "reswords", PM_ARRAY|PM_SPECIAL|PM_READONLY,
+      NULL, NULL, NULL,
+      arrsetfn, reswordsgetfn, stdunsetfn, NULL },
+    { "dis_reswords", PM_ARRAY|PM_SPECIAL|PM_READONLY,
+      NULL, NULL, NULL,
+      arrsetfn, disreswordsgetfn, stdunsetfn, NULL },
+    { "options", 0,
+      getpmoption, scanpmoptions, setpmoptions,
+      NULL, NULL, stdunsetfn, NULL },
+    { "modules", PM_READONLY,
+      getpmmodule, scanpmmodules, hashsetfn,
+      NULL, NULL, stdunsetfn, NULL },
+    { "dirstack", PM_ARRAY|PM_SPECIAL|PM_REMOVABLE,
+      NULL, NULL, NULL,
+      dirssetfn, dirsgetfn, stdunsetfn, NULL },
+    { "history", PM_READONLY,
+      getpmhistory, scanpmhistory, hashsetfn,
+      NULL, NULL, stdunsetfn, NULL },
+    { "historywords", PM_ARRAY|PM_SPECIAL|PM_READONLY,
+      NULL, NULL, NULL,
+      arrsetfn, histwgetfn, stdunsetfn, NULL },
+    { "jobtexts", PM_READONLY,
+      getpmjobtext, scanpmjobtexts, hashsetfn,
+      NULL, NULL, stdunsetfn, NULL },
+    { "jobstates", PM_READONLY,
+      getpmjobstate, scanpmjobstates, hashsetfn,
+      NULL, NULL, stdunsetfn, NULL },
+    { "jobdirs", PM_READONLY,
+      getpmjobdir, scanpmjobdirs, hashsetfn,
+      NULL, NULL, stdunsetfn, NULL },
+    { "nameddirs", 0,
+      getpmnameddir, scanpmnameddirs, setpmnameddirs,
+      NULL, NULL, stdunsetfn, NULL },
+    { "userdirs", PM_READONLY,
+      getpmuserdir, scanpmuserdirs, hashsetfn,
+      NULL, NULL, stdunsetfn, NULL },
+    { "aliases", 0,
+      getpmralias, scanpmraliases, setpmraliases,
+      NULL, NULL, stdunsetfn, NULL },
+    { "galiases", 0,
+      getpmgalias, scanpmgaliases, setpmgaliases,
+      NULL, NULL, stdunsetfn, NULL },
+    { "dis_aliases", 0,
+      getpmdisralias, scanpmdisraliases, setpmdisraliases,
+      NULL, NULL, stdunsetfn, NULL },
+    { "dis_galiases", 0,
+      getpmdisgalias, scanpmdisgaliases, setpmdisgaliases,
+      NULL, NULL, stdunsetfn, NULL },
+    { NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
+};
 
 /**/
 int
-setup_parameter(Module m)
+setup_(Module m)
 {
+    incleanup = 0;
+
     return 0;
 }
 
 /**/
 int
-boot_parameter(Module m)
+boot_(Module m)
 {
     /* Create the special associative arrays.
      * As an example for autoloaded parameters, this is probably a bad
-     * example, because we the zsh core doesn't support creation of
+     * example, because the zsh core doesn't support creation of
      * special hashes, yet. */
 
-    unsetparam(PAR_NAM);
-    if (!(parpm = createspecialhash(PAR_NAM, getpmparameter,
-				    scanpmparameters)))
-	return 1;
-    parpm->flags |= PM_READONLY;
-    unsetparam(CMD_NAM);
-    if (!(cmdpm = createspecialhash(CMD_NAM, getpmcommand,
-				    scanpmcommands)))
-	return 1;
-    cmdpm->sets.hfn = setpmcommands;
-    unsetparam(FUN_NAM);
-    if (!(funpm = createspecialhash(FUN_NAM, getpmfunction,
-				    scanpmfunctions)))
-	return 1;
-    funpm->sets.hfn = setpmfunctions;
-    unsetparam(OPT_NAM);
-    if (!(optpm = createspecialhash(OPT_NAM, getpmoption,
-				    scanpmoptions)))
-	return 1;
-    optpm->sets.hfn = setpmoptions;
+    struct pardef *def;
 
+    for (def = partab; def->name; def++) {
+	unsetparam(def->name);
+
+	if (def->getnfn) {
+	    if (!(def->pm = createspecialhash(def->name, def->getnfn,
+					      def->scantfn)))
+		return 1;
+	    def->pm->flags |= def->flags;
+	    if (def->hsetfn)
+		def->pm->sets.hfn = def->hsetfn;
+	} else {
+	    if (!(def->pm = createparam(def->name, def->flags | PM_HIDE |
+					PM_REMOVABLE)))
+		return 1;
+	    def->pm->sets.afn = def->setfn;
+	    def->pm->gets.afn = def->getfn;
+	    def->pm->unsetfn = def->unsetfn;
+	}
+    }
     return 0;
 }
 
-#ifdef MODULE
-
 /**/
 int
-cleanup_parameter(Module m)
+cleanup_(Module m)
 {
     Param pm;
+    struct pardef *def;
 
-    /* Remove the special parameters if they are still the same. */
+    incleanup = 1;
 
-    if ((pm = (Param) paramtab->getnode(paramtab, PAR_NAM)) && pm == parpm) {
-	pm->flags &= ~PM_READONLY;
-	unsetparam_pm(pm, 0, 1);
+    for (def = partab; def->name; def++) {
+	if ((pm = (Param) paramtab->getnode(paramtab, def->name)) &&
+	    pm == def->pm) {
+	    pm->flags &= ~PM_READONLY;
+	    unsetparam_pm(pm, 0, 1);
+	}
     }
-    if ((pm = (Param) paramtab->getnode(paramtab, CMD_NAM)) && pm == cmdpm)
-	unsetparam_pm(pm, 0, 1);
-    if ((pm = (Param) paramtab->getnode(paramtab, FUN_NAM)) && pm == funpm)
-	unsetparam_pm(pm, 0, 1);
-    if ((pm = (Param) paramtab->getnode(paramtab, OPT_NAM)) && pm == optpm)
-	unsetparam_pm(pm, 0, 1);
     return 0;
 }
 
 /**/
 int
-finish_parameter(Module m)
+finish_(Module m)
 {
     return 0;
 }
-
-#endif
diff --git a/Src/Modules/zpty.c b/Src/Modules/zpty.c
index 088979514..26896525c 100644
--- a/Src/Modules/zpty.c
+++ b/Src/Modules/zpty.c
@@ -30,6 +30,13 @@
 #include "zpty.mdh"
 #include "zpty.pro"
 
+/* The number of bytes we normally read when given no pattern and the
+ * upper bound on the number of bytes we read (even if we are give a
+ * pattern). */
+
+#define READ_LEN 1024
+#define READ_MAX (1024 * 1024)
+
 typedef struct ptycmd *Ptycmd;
 
 struct ptycmd {
@@ -158,7 +165,7 @@ get_pty(int *master, int *slave)
 
 #else /* ! __osf__ */
 
-#if __SVR4
+#if defined(__SVR4) || defined(sinix)
 
 #include <sys/stropts.h>
 
@@ -167,11 +174,12 @@ get_pty(int *master, int *slave)
 {
     int mfd, sfd;
     char *name;
+    int ret;
 
     if ((mfd = open("/dev/ptmx", O_RDWR)) < 0)
 	return 1;
 
-    if (!(name = ptsname(mfd)) || grantpt(mfd) || unlockpt(mfd)) {
+    if (grantpt(mfd) || unlockpt(mfd) || !(name = ptsname(mfd))) {
 	close(mfd);
 	return 1;
     }
@@ -179,20 +187,31 @@ get_pty(int *master, int *slave)
 	close(mfd);
 	return 1;
     }
-    if (ioctl(sfd, I_PUSH, "ptem") ||
-	ioctl(sfd, I_PUSH, "ldterm") ||
-	ioctl(sfd, I_PUSH, "ttcompat")) {
-	close(mfd);
-	close(sfd);
-	return 1;
-    }
+    if ((ret = ioctl(sfd, I_FIND, "ptem")) != 1)
+       if (ret == -1 || ioctl(sfd, I_PUSH, "ptem") == -1) {
+	   close(mfd);
+	   close(sfd);
+	   return 1;
+       }
+    if ((ret = ioctl(sfd, I_FIND, "ldterm")) != 1)
+       if (ret == -1 || ioctl(sfd, I_PUSH, "ldterm") == -1) {
+	   close(mfd);
+	   close(sfd);
+	   return 1;
+       }
+    if ((ret = ioctl(sfd, I_FIND, "ttcompat")) != 1)
+       if (ret == -1 || ioctl(sfd, I_PUSH, "ttcompat") == -1) {
+	   close(mfd);
+	   close(sfd);
+	   return 1;
+       }
     *master = mfd;
     *slave = sfd;
 
     return 0;
 }
 
-#else /* ! __SVR4 */
+#else /* ! (defined(__SVR4) || defind(sinix)) */
 
 static int
 get_pty(int *master, int *slave)
@@ -242,17 +261,17 @@ newptycmd(char *nam, char *pname, char **args, int echo, int block)
     char *cmd;
 
     if (!(cmd = findcmd(*args, 1))) {
-	zerrnam(nam, "unknown command: %s", *args, 0);
+	zwarnnam(nam, "unknown command: %s", *args, 0);
 	return 1;
     }
     if (get_pty(&master, &slave)) {
-	zerrnam(nam, "can't open pseudo terminal", NULL, 0);
+	zwarnnam(nam, "can't open pseudo terminal", NULL, 0);
 	return 1;
     }
     if ((pid = fork()) == -1) {
 	close(master);
 	close(slave);
-	zerrnam(nam, "couldn't create pty command: %s", pname, 0);
+	zwarnnam(nam, "couldn't create pty command: %s", pname, 0);
 	return 1;
     } else if (!pid) {
 	if (!echo) {
@@ -298,6 +317,8 @@ newptycmd(char *nam, char *pname, char **args, int echo, int block)
 
 	close(slave);
 
+	setpgrp(0L, getpid());
+
 	execve(cmd, args, environ);
 	exit(0);
     }
@@ -307,9 +328,7 @@ newptycmd(char *nam, char *pname, char **args, int echo, int block)
     p = (Ptycmd) zalloc(sizeof(*p));
 
     p->name = ztrdup(pname);
-    PERMALLOC {
-	p->args = arrdup(args);
-    } LASTALLOC;
+    p->args = zarrdup(args);
     p->fd = master;
     p->pid = pid;
     p->echo = echo;
@@ -343,7 +362,9 @@ deleteptycmd(Ptycmd cmd)
     zsfree(p->name);
     freearray(p->args);
 
-    kill(p->pid, SIGHUP);
+    /* We kill the process group the command put itself in. */
+
+    kill(-(p->pid), SIGHUP);
 
     zclose(cmd->fd);
 
@@ -375,7 +396,7 @@ checkptycmd(Ptycmd cmd)
 static int
 ptyread(char *nam, Ptycmd cmd, char **args)
 {
-    int blen = 256, used = 0, ret;
+    int blen = 256, used = 0, ret = 1;
     char *buf = (char *) zhalloc(blen + 1);
     Patprog prog = NULL;
 
@@ -383,57 +404,113 @@ ptyread(char *nam, Ptycmd cmd, char **args)
 	char *p;
 
 	if (args[2]) {
-	    zerrnam(nam, "too many arguments", NULL, 0);
+	    zwarnnam(nam, "too many arguments", NULL, 0);
 	    return 1;
 	}
 	p = dupstring(args[1]);
 	tokenize(p);
 	remnulargs(p);
 	if (!(prog = patcompile(p, PAT_STATIC, NULL))) {
-	    zerrnam(nam, "bad pattern: %s", args[1], 0);
+	    zwarnnam(nam, "bad pattern: %s", args[1], 0);
 	    return 1;
 	}
     }
     do {
-	while ((ret = read(cmd->fd, buf + used, 1)) == 1) {
+	if (!ret) {
+	    checkptycmd(cmd);
+	    if (cmd->fin)
+		break;
+	}
+	if ((ret = read(cmd->fd, buf + used, 1)) == 1) {
 	    if (++used == blen) {
 		buf = hrealloc(buf, blen, blen << 1);
 		blen <<= 1;
 	    }
 	}
 	buf[used] = '\0';
-    } while (prog && !pattry(prog, buf));
 
-    if (*args)
-	setsparam(*args, ztrdup(buf));
-    else
-	printf("%s", buf);
+	/**** Hm. If we leave the loop when ret < 0 the user would have
+	 *    to make sure that `zpty -r' is tried more than once if
+	 *    there will be some output and we only got the ret == -1
+	 *    because the output is not yet available.
+	 *    The same for the `write' below. */
+
+	if (ret < 0 && (cmd->block
+#ifdef EWOULDBLOCK
+			|| errno != EWOULDBLOCK
+#else
+#ifdef EAGAIN
+			|| errno != EAGAIN
+#endif
+#endif
+			))
+	    break;
+
+	if (!prog && !ret)
+	    break;
+    } while (!errflag &&
+	     (prog ? (used < READ_MAX && (!ret || !pattry(prog, buf))) :
+	      (used < READ_LEN)));
 
+    if (*args)
+	setsparam(*args, ztrdup(metafy(buf, used, META_HREALLOC)));
+    else {
+	fflush(stdout);
+	write(1, buf, used);
+    }
     return !used;
 }
 
 static int
+ptywritestr(Ptycmd cmd, char *s, int len)
+{
+    int written;
+
+    for (; len; len -= written, s += written) {
+	if ((written = write(cmd->fd, s, len)) < 0 &&
+	    (cmd->block
+#ifdef EWOULDBLOCK
+			|| errno != EWOULDBLOCK
+#else
+#ifdef EAGAIN
+			|| errno != EAGAIN
+#endif
+#endif
+	     ))
+	    return 1;
+	if (written < 0) {
+	    checkptycmd(cmd);
+	    if (cmd->fin)
+		break;
+	    written = 0;
+	}
+    }
+    return 0;
+}
+
+static int
 ptywrite(Ptycmd cmd, char **args, int nonl)
 {
     if (*args) {
 	char sp = ' ';
 
-	while (*args) {
-	    write(cmd->fd, *args, strlen(*args));
+	while (*args)
+	    if (ptywritestr(cmd, *args, strlen(*args)) ||
+		(*++args && ptywritestr(cmd, &sp, 1)))
+		return 1;
 
-	    if (*++args)
-		write(cmd->fd, &sp, 1);
-	}
 	if (!nonl) {
 	    sp = '\n';
-	    write(cmd->fd, &sp, 1);
+	    if (ptywritestr(cmd, &sp, 1))
+		return 1;
 	}
     } else {
 	int n;
 	char buf[BUFSIZ];
 
 	while ((n = read(0, buf, BUFSIZ)) > 0)
-	    write(cmd->fd, buf, n);
+	    if (ptywritestr(cmd, buf, n))
+		return 1;
     }
     return 0;
 }
@@ -449,17 +526,17 @@ bin_zpty(char *nam, char **args, char *ops, int func)
 		      ops['d'] || ops['L'])) ||
 	(ops['d'] && (ops['b'] || ops['e'] || ops['L'])) ||
 	(ops['L'] && (ops['b'] || ops['e']))) {
-	zerrnam(nam, "illegal option combination", NULL, 0);
+	zwarnnam(nam, "illegal option combination", NULL, 0);
 	return 1;
     }
     if (ops['r'] || ops['w']) {
 	Ptycmd p;
 
 	if (!*args) {
-	    zerrnam(nam, "missing pty command name", NULL, 0);
+	    zwarnnam(nam, "missing pty command name", NULL, 0);
 	    return 1;
 	} else if (!(p = getptycmd(*args))) {
-	    zerrnam(nam, "no such pty command: %s", *args, 0);
+	    zwarnnam(nam, "no such pty command: %s", *args, 0);
 	    return 1;
 	}
 	checkptycmd(p);
@@ -486,11 +563,11 @@ bin_zpty(char *nam, char **args, char *ops, int func)
 	return ret;
     } else if (*args) {
 	if (!args[1]) {
-	    zerrnam(nam, "missing command", NULL, 0);
+	    zwarnnam(nam, "missing command", NULL, 0);
 	    return 1;
 	}
 	if (getptycmd(*args)) {
-	    zerrnam(nam, "pty command name already used: %s", *args, 0);
+	    zwarnnam(nam, "pty command name already used: %s", *args, 0);
 	    return 1;
 	}
 	return newptycmd(nam, *args, args + 1, ops['e'], ops['b']);
diff --git a/Src/Zle/comp.h b/Src/Zle/comp.h
index 5d7ef74e2..f513d4a5a 100644
--- a/Src/Zle/comp.h
+++ b/Src/Zle/comp.h
@@ -27,111 +27,362 @@
  *
  */
 
-#undef compctlread
+typedef struct cmatcher  *Cmatcher;
+typedef struct cmlist    *Cmlist;
+typedef struct cpattern  *Cpattern;
+typedef struct menuinfo  *Menuinfo;
+typedef struct cexpl *Cexpl;
+typedef struct cmgroup *Cmgroup;
+typedef struct cmatch *Cmatch;
 
-typedef struct compctlp  *Compctlp;
-typedef struct compctl   *Compctl;
-typedef struct compcond  *Compcond;
+/* This is for explantion strings. */
 
-/* node for compctl hash table (compctltab) */
+struct cexpl {
+    char *str;			/* the string */
+    int count;			/* the number of matches */
+    int fcount;			/* number of matches with fignore ignored */
+};
+
+/* This describes a group of matches. */
+
+struct cmgroup {
+    char *name;			/* the name of this group */
+    Cmgroup prev;		/* previous on the list */
+    Cmgroup next;		/* next one in list */
+    int flags;			/* see CGF_* below */
+    int mcount;			/* number of matches */
+    Cmatch *matches;		/* the matches */
+    int lcount;			/* number of things to list here */
+    int llcount;		/* number of line-displays */
+    char **ylist;		/* things to list */
+    int ecount;			/* number of explanation string */
+    Cexpl *expls;		/* explanation strings */
+    int ccount;			/* number of compctls used */
+    LinkList lexpls;		/* list of explanation string while building */
+    LinkList lmatches;		/* list of matches */
+    LinkList lfmatches;		/* list of matches without fignore */
+    LinkList lallccs;		/* list of used compctls */
+    int num;			/* number of this group */
+    int nbrbeg;			/* number of opened braces */
+    int nbrend;			/* number of closed braces */
+    /* The following is collected/used during listing. */
+    int dcount;			/* number of matches to list in columns */
+    int cols;			/* number of columns */
+    int lins;			/* number of lines */
+    int width;			/* column width */
+    int *widths;		/* column widths for listpacked */
+    int totl;			/* total length */
+    int shortest;		/* length of shortest match */
+    Cmgroup perm;		/* perm. alloced version of this group */
+    int new;			/* new matches since last permalloc() */
+};
+
+
+#define CGF_NOSORT   1		/* don't sort this group */
+#define CGF_LINES    2		/* these are to be printed on different lines */
+#define CGF_HASDL    4		/* has display strings printed on separate lines */
+#define CGF_UNIQALL  8		/* remove all duplicates */
+#define CGF_UNIQCON 16		/* remove consecutive duplicates */
+#define CGF_PACKED  32		/* LIST_PACKED for this group */
+#define CGF_ROWS    64		/* LIST_ROWS_FIRST for this group */
+
+/* This is the struct used to hold matches. */
+
+struct cmatch {
+    char *str;			/* the match itself */
+    char *ipre;			/* ignored prefix, has to be re-inserted */
+    char *ripre;		/* ignored prefix, unquoted */
+    char *isuf;			/* ignored suffix */
+    char *ppre;			/* the path prefix */
+    char *psuf;			/* the path suffix */
+    char *prpre;		/* path prefix for opendir */
+    char *pre;			/* prefix string from -P */
+    char *suf;			/* suffix string from -S */
+    char *disp;			/* string to display (compadd -d) */
+    char *autoq;		/* closing quote to add automatically */
+    int flags;			/* see CMF_* below */
+    int *brpl;			/* places where to put the brace prefixes */
+    int *brsl;			/* ...and the suffixes */
+    char *rems;			/* when to remove the suffix */
+    char *remf;			/* shell function to call for suffix-removal */
+    int qipl;			/* length of quote-prefix */
+    int qisl;			/* length of quote-suffix */
+    int rnum;			/* group relative number */
+    int gnum;			/* global number */
+};
+
+#define CMF_FILE     (1<< 0)	/* this is a file */
+#define CMF_REMOVE   (1<< 1)	/* remove the suffix */
+#define CMF_ISPAR    (1<< 2)	/* is paramter expansion */
+#define CMF_PARBR    (1<< 3)	/* paramter expansion with a brace */
+#define CMF_PARNEST  (1<< 4)	/* nested paramter expansion */
+#define CMF_NOLIST   (1<< 5)	/* should not be listed */
+#define CMF_DISPLINE (1<< 6)	/* display strings one per line */
+#define CMF_HIDE     (1<< 7)	/* temporarily hide this one */
+#define CMF_NOSPACE  (1<< 8)	/* don't add a space */
+#define CMF_PACKED   (1<< 9)	/* prefer LIST_PACKED */
+#define CMF_ROWS     (1<<10)	/* prefer LIST_ROWS_FIRST */
+#define CMF_MULT     (1<<11)	/* string appears more than once */
+#define CMF_FMULT    (1<<12)	/* first of multiple equal strings */
+
+/* Stuff for completion matcher control. */
+
+struct cmlist {
+    Cmlist next;		/* next one in the list of global matchers */
+    Cmatcher matcher;		/* the matcher definition */
+    char *str;			/* the string for it */
+};
+
+struct cmatcher {
+    int refc;			/* reference counter */
+    Cmatcher next;		/* next matcher */
+    int flags;			/* see CMF_* below */
+    Cpattern line;		/* what matches on the line */
+    int llen;			/* length of line pattern */
+    Cpattern word;		/* what matches in the word */
+    int wlen;			/* length of word pattern */
+    Cpattern left;		/* left anchor */
+    int lalen;			/* length of left anchor */
+    Cpattern right;		/* right anchor */
+    int ralen;			/* length of right anchor */
+};
+
+#define CMF_LINE  1
+#define CMF_LEFT  2
+#define CMF_RIGHT 4
+
+struct cpattern {
+    Cpattern next;		/* next sub-pattern */
+    unsigned char tab[256];	/* table of matched characters */
+    int equiv;			/* if this is a {...} class */
+};
+
+/* This is a special return value for parse_cmatcher(), *
+ * signalling an error. */
+
+#define pcm_err ((Cmatcher) 1)
+
+/* Information about what to put on the line as the unambiguous string.
+ * The code always keeps lists of these structs up to date while
+ * matches are added (in the aminfo structs below).
+ * The lists have two levels: in the first one we have one struct per
+ * word-part, where parts are separated by the anchors of `*' patterns.
+ * These structs have pointers (in the prefix and suffix fields) to
+ * lists of cline structs describing the strings before or after the
+ * the anchor. */
+
+typedef struct cline *Cline;
+typedef struct clsub Clsub;
+
+struct cline {
+    Cline next;
+    int flags;
+    char *line;
+    int llen;
+    char *word;
+    int wlen;
+    char *orig;
+    int olen;
+    int slen;
+    Cline prefix, suffix;
+    int min, max;
+};
+
+#define CLF_MISS      1
+#define CLF_DIFF      2
+#define CLF_SUF       4
+#define CLF_MID       8
+#define CLF_NEW      16
+#define CLF_LINE     32
+#define CLF_JOIN     64
+#define CLF_MATCHED 128
+
+/* Information for ambiguous completions. One for fignore ignored and   *
+ * one for normal completion. */
+
+typedef struct aminfo *Aminfo;
 
-struct compctlp {
-    HashNode next;		/* next in hash chain               */
-    char *nam;			/* command name                     */
-    int flags;			/* CURRENTLY UNUSED                 */
-    Compctl cc;			/* pointer to the compctl desc.     */
+struct aminfo {
+    Cmatch firstm;		/* the first match                        */
+    int exact;			/* if there was an exact match            */
+    Cmatch exactm;		/* the exact match (if any)               */
+    int count;			/* number of matches                      */
+    Cline line;			/* unambiguous line string                */
 };
 
-/* compctl -x condition */
-
-struct compcond {
-    Compcond and, or;		/* the next or'ed/and'ed conditions    */
-    int type;			/* the type (CCT_*)                    */
-    int n;			/* the array length                    */
-    union {			/* these structs hold the data used to */
-	struct {		/* test this condition                 */
-	    int *a, *b;		/* CCT_POS, CCT_NUMWORDS               */
-	}
-	r;
-	struct {		/* CCT_CURSTR, CCT_CURPAT,... */
-	    int *p;
-	    char **s;
-	}
-	s;
-	struct {		/* CCT_RANGESTR,... */
-	    char **a, **b;
-	}
-	l;
-    }
-    u;
+/* Information about menucompletion stuff. */
+
+struct menuinfo {
+    Cmgroup group;		/* position in the group list */
+    Cmatch *cur;		/* match currently inserted */
+    int pos;			/* begin on line */
+    int len;			/* length of inserted string */
+    int end;			/* end on the line */
+    int we;			/* non-zero if the cursor was at the end */
+    int insc;			/* length of suffix inserted */
+    int asked;			/* we asked if the list should be shown */
+    char *prebr;		/* prefix before a brace, if any */
+    char *postbr;		/* suffix after a brace */
+};
+
+/* Flags for compadd and addmatches(). */
+
+#define CAF_QUOTE    1
+#define CAF_NOSORT   2
+#define CAF_MATCH    4
+#define CAF_UNIQCON  8
+#define CAF_UNIQALL 16
+
+/* Data for compadd and addmatches() */
+
+typedef struct cadata *Cadata;
+
+struct cadata {
+    char *ipre;			/* ignored prefix (-i) */
+    char *isuf;			/* ignored suffix (-I) */
+    char *ppre;			/* `path' prefix (-p) */
+    char *psuf;			/* `path' suffix (-s) */
+    char *prpre;		/* expanded `path' prefix (-W) */
+    char *pre;			/* prefix to insert (-P) */
+    char *suf;			/* suffix to insert (-S) */
+    char *group;		/* name of the group (-[JV]) */
+    char *rems;			/* remove suffix on chars... (-r) */
+    char *remf;			/* function to remove suffix (-R) */
+    char *ign;			/* ignored suffixes (-F) */
+    int flags;			/* CMF_* flags (-[fqn]) */
+    int aflags;			/* CAF_* flags (-[QUa]) */
+    Cmatcher match;		/* match spec (parsed from -M) */
+    char *exp;			/* explanation (-X) */
+    char *apar;			/* array to store matches in (-A) */
+    char *opar;			/* array to store originals in (-O) */
+    char *dpar;			/* array to delete non-matches in (-D) */
+    char *disp;			/* array with display lists (-d) */
 };
 
-#define CCT_UNUSED     0
-#define CCT_POS        1
-#define CCT_CURSTR     2
-#define CCT_CURPAT     3
-#define CCT_WORDSTR    4
-#define CCT_WORDPAT    5
-#define CCT_CURSUF     6
-#define CCT_CURPRE     7
-#define CCT_CURSUB     8
-#define CCT_CURSUBC    9
-#define CCT_NUMWORDS  10
-#define CCT_RANGESTR  11
-#define CCT_RANGEPAT  12
-
-/* Contains the real description for compctls */
-
-struct compctl {
-    int refc;			/* reference count                         */
-    Compctl next;		/* next compctl for -x                     */
-    unsigned long mask;		/* mask of things to complete (CC_*)       */
-    char *keyvar;		/* for -k (variable)                       */
-    char *glob;			/* for -g (globbing)                       */
-    char *str;			/* for -s (expansion)                      */
-    char *func;			/* for -K (function)                       */
-    char *explain;		/* for -X (explanation)                    */
-    char *ylist;		/* for -y (user-defined desc. for listing) */
-    char *prefix, *suffix;	/* for -P and -S (prefix, suffix)          */
-    char *subcmd;		/* for -l (command name to use)            */
-    char *withd;		/* for -w (with directory                  */
-    char *hpat;			/* for -H (history pattern)                */
-    int hnum;			/* for -H (number of events to search)     */
-    Compctl ext;		/* for -x (first of the compctls after -x) */
-    Compcond cond;		/* for -x (condition for this compctl)     */
-    Compctl xor;		/* for + (next of the xor'ed compctls)     */
+/* List data. */
+
+typedef struct cldata *Cldata;
+
+struct cldata {
+    int columns;		/* screen width */
+    int lines;			/* screen height */
+    int menuacc;		/* value of global menuacc */
+    int valid;			/* no need to calculate anew */
+    int nlist;			/* number of matches to list */
+    int nlines;			/* number of lines needed */
+    int hidden;			/* != 0 if there are hidden matches */
+    int onlyexpl;		/* != 0 if only explanations to print */
+    int showall;		/* != 0 if hidden matches should be shown */
+};
+
+typedef void (*CLPrintFunc)(Cmgroup, Cmatch *, int, int, int, int,
+			    char *, struct stat *);
+
+/* Flags for fromcomp. */
+
+#define FC_LINE   1
+#define FC_INWORD 2
+
+/* Flags for special parameters. */
+
+#define CPN_WORDS      0
+#define CP_WORDS       (1 <<  CPN_WORDS)
+#define CPN_CURRENT    1
+#define CP_CURRENT     (1 <<  CPN_CURRENT)
+#define CPN_PREFIX     2
+#define CP_PREFIX      (1 <<  CPN_PREFIX)
+#define CPN_SUFFIX     3
+#define CP_SUFFIX      (1 <<  CPN_SUFFIX)
+#define CPN_IPREFIX    4
+#define CP_IPREFIX     (1 <<  CPN_IPREFIX)
+#define CPN_ISUFFIX    5
+#define CP_ISUFFIX     (1 <<  CPN_ISUFFIX)
+#define CPN_QIPREFIX   6
+#define CP_QIPREFIX    (1 <<  CPN_QIPREFIX)
+#define CPN_QISUFFIX   7
+#define CP_QISUFFIX    (1 <<  CPN_QISUFFIX)
+#define CPN_COMPSTATE  8
+#define CP_COMPSTATE   (1 <<  CPN_COMPSTATE)
+
+#define CP_REALPARAMS  9
+#define CP_ALLREALS    ((unsigned int) 0x1ff)
+
+
+#define CPN_NMATCHES   0
+#define CP_NMATCHES    (1 << CPN_NMATCHES)
+#define CPN_CONTEXT    1
+#define CP_CONTEXT     (1 << CPN_CONTEXT)
+#define CPN_PARAMETER  2
+#define CP_PARAMETER   (1 << CPN_PARAMETER)
+#define CPN_REDIRECT   3
+#define CP_REDIRECT    (1 << CPN_REDIRECT)
+#define CPN_QUOTE      4
+#define CP_QUOTE       (1 << CPN_QUOTE)
+#define CPN_QUOTING    5
+#define CP_QUOTING     (1 << CPN_QUOTING)
+#define CPN_RESTORE    6
+#define CP_RESTORE     (1 << CPN_RESTORE)
+#define CPN_LIST       7
+#define CP_LIST        (1 << CPN_LIST)
+#define CPN_INSERT     8
+#define CP_INSERT      (1 << CPN_INSERT)
+#define CPN_EXACT      9
+#define CP_EXACT       (1 << CPN_EXACT)
+#define CPN_EXACTSTR   10
+#define CP_EXACTSTR    (1 << CPN_EXACTSTR)
+#define CPN_PATMATCH   11
+#define CP_PATMATCH    (1 << CPN_PATMATCH)
+#define CPN_PATINSERT  12
+#define CP_PATINSERT   (1 << CPN_PATINSERT)
+#define CPN_UNAMBIG    13
+#define CP_UNAMBIG     (1 << CPN_UNAMBIG)
+#define CPN_UNAMBIGC   14
+#define CP_UNAMBIGC    (1 << CPN_UNAMBIGC)
+#define CPN_LISTMAX    15
+#define CP_LISTMAX     (1 << CPN_LISTMAX)
+#define CPN_LASTPROMPT 16
+#define CP_LASTPROMPT  (1 << CPN_LASTPROMPT)
+#define CPN_TOEND      17
+#define CP_TOEND       (1 << CPN_TOEND)
+#define CPN_OLDLIST    18
+#define CP_OLDLIST     (1 << CPN_OLDLIST)
+#define CPN_OLDINS     19
+#define CP_OLDINS      (1 << CPN_OLDINS)
+#define CPN_VARED      20
+#define CP_VARED       (1 << CPN_VARED)
+#define CPN_LISTLINES  21
+#define CP_LISTLINES   (1 << CPN_LISTLINES)
+#define CPN_QUOTES     22
+#define CP_QUOTES      (1 << CPN_QUOTES)
+#define CPN_IGNORED    23
+#define CP_IGNORED     (1 << CPN_IGNORED)
+
+#define CP_KEYPARAMS   24
+#define CP_ALLKEYS     ((unsigned int) 0xffffff)
+
+/* Hooks. */
+
+#define INSERTMATCHHOOK     (comphooks + 0)
+#define MENUSTARTHOOK       (comphooks + 1)
+#define COMPCTLMAKEHOOK     (comphooks + 2)
+#define COMPCTLCLEANUPHOOK  (comphooks + 3)
+#define COMPLISTMATCHESHOOK (comphooks + 4)
+
+/* compctl hook data struct */
+
+struct ccmakedat {
+    char *str;
+    int incmd;
+    int lst;
+};
+
+/* Data given to offered hooks. */
+
+typedef struct chdata *Chdata;
+
+struct chdata {
+    Cmgroup matches;		/* the matches generated */
+    int num;			/* the number of matches */
+    Cmatch cur;			/* current match or NULL */
 };
 
-/* objects to complete */
-#define CC_FILES	(1<<0)
-#define CC_COMMPATH	(1<<1)
-#define CC_REMOVE	(1<<2)
-#define CC_OPTIONS	(1<<3)
-#define CC_VARS		(1<<4)
-#define CC_BINDINGS	(1<<5)
-#define CC_ARRAYS	(1<<6)
-#define CC_INTVARS	(1<<7)
-#define CC_SHFUNCS	(1<<8)
-#define CC_PARAMS	(1<<9)
-#define CC_ENVVARS	(1<<10)
-#define CC_JOBS		(1<<11)
-#define CC_RUNNING	(1<<12)
-#define CC_STOPPED	(1<<13)
-#define CC_BUILTINS	(1<<14)
-#define CC_ALREG	(1<<15)
-#define CC_ALGLOB	(1<<16)
-#define CC_USERS	(1<<17)
-#define CC_DISCMDS	(1<<18)
-#define CC_EXCMDS	(1<<19)
-#define CC_SCALARS	(1<<20)
-#define CC_READONLYS	(1<<21)
-#define CC_SPECIALS	(1<<22)
-#define CC_DELETE	(1<<23)
-#define CC_NAMED	(1<<24)
-#define CC_QUOTEFLAG	(1<<25)
-#define CC_EXTCMDS	(1<<26)
-#define CC_RESWDS	(1<<27)
-#define CC_DIRS		(1<<28)
-
-#define CC_EXPANDEXPL	(1<<30)
-#define CC_RESERVED	(1<<31)
diff --git a/Src/Zle/compcore.c b/Src/Zle/compcore.c
index ea9ff5b20..3cc080cac 100644
--- a/Src/Zle/compcore.c
+++ b/Src/Zle/compcore.c
@@ -28,9 +28,6 @@
  */
 
 #include "complete.mdh"
-#define GLOBAL_PROTOTYPES
-#include "zle_tricky.pro"
-#undef GLOBAL_PROTOTYPES
 #include "compcore.pro"
 
 /* The last completion widget called. */
@@ -40,11 +37,17 @@ static Widget lastcompwidget;
 /* Flags saying what we have to do with the result. */
 
 /**/
-int useexact, useline, uselist;
+int useexact, useline, uselist, forcelist, startauto;
+
+/* Non-zero if we should go back to the last prompt. */
+
+/**/
+int dolastprompt;
 
 /* Non-zero if we should keep an old list. */
 
 /**/
+mod_export
 int oldlist, oldins;
 
 /* This is used to decide when the cursor should be moved to the end of    *
@@ -57,17 +60,17 @@ int movetoend;
 /* The match and group number to insert when starting menucompletion.   */
 
 /**/
-int insmnum, insgnum, insgroup, insspace;
+mod_export int insmnum, insgnum, insgroup, insspace;
 
 /* Information about menucompletion. */
 
 /**/
-struct menuinfo minfo;
+mod_export struct menuinfo minfo;
 
 /* Number of matches accepted with accept-and-menu-complete */
 
 /**/
-int menuacc;
+mod_export int menuacc;
 
 /* Brace insertion stuff. */
 
@@ -77,7 +80,7 @@ int hasunqu, useqbr, brpcs, brscs;
 /* Flags saying in what kind of string we are. */
 
 /**/
-int ispar, linwhat;
+mod_export int ispar, linwhat;
 
 /* A parameter expansion prefix (like ${). */
 
@@ -92,7 +95,7 @@ int parflags;
 /* Match flags for all matches in this group. */
 
 /**/
-int mflags;
+mod_export int mflags;
 
 /* Flags saying how the parameter expression we are in is quoted. */
 
@@ -101,17 +104,18 @@ int parq, eparq;
 
 /* We store the following prefixes/suffixes:                               *
  * ipre,ripre  -- the ignored prefix (quoted and unquoted)                 *
- * isuf        -- the ignored suffix                                       *
- * autoq       -- quotes to automatically insert                           */
+ * isuf        -- the ignored suffix                                       */
 
 /**/
-char *ipre, *ripre, *isuf;
+mod_export char *ipre, *ripre, *isuf;
 
 /* The list of matches.  fmatches contains the matches we first ignore *
  * because of fignore.                                                 */
 
 /**/
-LinkList matches, fmatches;
+mod_export LinkList matches;
+/**/
+LinkList fmatches;
 
 /* This holds the list of matches-groups. lastmatches holds the last list of 
  * permanently allocated matches, pmatches is the same for the list
@@ -120,47 +124,49 @@ LinkList matches, fmatches;
  * lmatches/lastlmatches is a pointer to the last element in the lists. */
 
 /**/
-Cmgroup lastmatches, pmatches, amatches, lmatches, lastlmatches;
+mod_export Cmgroup lastmatches, pmatches, amatches, lmatches, lastlmatches;
 
 /* Non-zero if we have permanently allocated matches (old and new). */
 
 /**/
-int hasoldlist, hasperm;
+mod_export int hasoldlist, hasperm;
 
 /* Non-zero if we have newly added matches. */
 
 /**/
-int newmatches;
+mod_export int newmatches;
 
 /* Number of permanently allocated matches and groups. */
 
 /**/
-int permmnum, permgnum, lastpermmnum, lastpermgnum;
+mod_export int permmnum, permgnum, lastpermmnum, lastpermgnum;
 
 /* The total number of matches and the number of matches to be listed. */
 
 /**/
-int nmatches, smatches;
+mod_export int nmatches;
+/**/
+mod_export int smatches;
 
 /* != 0 if only explanation strings should be printed */
 
 /**/
-int onlyexpl;
+mod_export int onlyexpl;
 
 /* Information about the matches for listing. */
 
 /**/
-struct cldata listdat;
+mod_export struct cldata listdat;
 
 /* This flag is non-zero if we are completing a pattern (with globcomplete) */
 
 /**/
-int ispattern, haspattern;
+mod_export int ispattern, haspattern;
 
-/* Non-zero if at least one match was added without -U. */
+/* Non-zero if at least one match was added without/with -U. */
 
 /**/
-int hasmatched;
+mod_export int hasmatched, hasunmatched;
 
 /* The current group of matches. */
 
@@ -170,12 +176,12 @@ Cmgroup mgroup;
 /* Match counter: all matches. */
 
 /**/
-int mnum;
+mod_export int mnum;
 
 /* The match counter when unambig_data() was called. */
 
 /**/
-int unambig_mnum;
+mod_export int unambig_mnum;
 
 /* Length of longest/shortest match. */
 
@@ -189,37 +195,37 @@ int maxmlen, minmlen;
 LinkList expls;
 
 /**/
-Cexpl curexpl;
+mod_export Cexpl curexpl;
 
 /* A stack of completion matchers to be used. */
 
 /**/
-Cmlist mstack;
+mod_export Cmlist mstack;
 
 /* The completion matchers used when building new stuff for the line. */
 
 /**/
-Cmlist bmatchers;
+mod_export Cmlist bmatchers;
 
 /* A list with references to all matchers we used. */
 
 /**/
-LinkList matchers;
+mod_export LinkList matchers;
 
 /* A heap of free Cline structures. */
 
 /**/
-Cline freecl;
+mod_export Cline freecl;
 
 /* Ambiguous information. */
 
 /**/
-Aminfo ainfo, fainfo;
+mod_export Aminfo ainfo, fainfo;
 
 /* The memory heap to use for new style completion generation. */
 
 /**/
-Heap compheap;
+mod_export Heap compheap;
 
 /* A list of some data.
  *
@@ -227,7 +233,7 @@ Heap compheap;
  * conceptually we don't know anything about compctls here... */
 
 /**/
-LinkList allccs;
+mod_export LinkList allccs;
 
 /* This says what of the state the line is in when completion is started *
  * came from a previous completion. If the FC_LINE bit is set, the       *
@@ -246,10 +252,6 @@ int fromcomp;
 /**/
 int lastend;
 
-/* Convenience macro for calling bslashquote() (formerly quotename()). */
-
-#define quotename(s, e) bslashquote(s, e, instring)
-
 #define inststr(X) inststrlen((X),1,-1)
 
 /* Main completion entry point, called from zle. */
@@ -260,153 +262,165 @@ do_completion(Hookdef dummy, Compldat dat)
 {
     int ret = 0, lst = dat->lst, incmd = dat->incmd;
     char *s = dat->s;
+    char *opm;
+    LinkNode n;
 
-    HEAPALLOC {
-	char *opm;
-	LinkNode n;
-
-	pushheap();
-
-	ainfo = fainfo = NULL;
-	matchers = newlinklist();
-
-	hasunqu = 0;
-	useline = (lst != COMP_LIST_COMPLETE);
-	useexact = isset(RECEXACT);
-	uselist = (useline ?
-		   ((isset(AUTOLIST) && !isset(BASHAUTOLIST)) ? 
-		    (isset(LISTAMBIGUOUS) ? 3 : 2) : 0) : 1);
-	zsfree(comppatmatch);
-	opm = comppatmatch = ztrdup(useglob ? "*" : "");
-	zsfree(comppatinsert);
-	comppatinsert = ztrdup("menu");
-	zsfree(compforcelist);
-	compforcelist = ztrdup("");
-	haspattern = 0;
-	complistmax = getiparam("LISTMAX");
-	zsfree(complastprompt);
-	complastprompt = ztrdup(((isset(ALWAYSLASTPROMPT) && zmult == 1) ||
-				(unset(ALWAYSLASTPROMPT) && zmult != 1)) ?
-				"yes" : "");
-	movetoend = ((cs == we || isset(ALWAYSTOEND)) ? 2 : 1);
-	showinglist = 0;
-	hasmatched = 0;
-	minmlen = 1000000;
-	maxmlen = -1;
-
-	/* Make sure we have the completion list and compctl. */
-	if (makecomplist(s, incmd, lst)) {
-	    /* Error condition: feeeeeeeeeeeeep(). */
-	    cs = 0;
-	    foredel(ll);
-	    inststr(origline);
-	    cs = origcs;
-	    clearlist = 1;
-	    ret = 1;
-	    minfo.cur = NULL;
-	    goto compend;
-	}
-	zsfree(lastprebr);
-	zsfree(lastpostbr);
-	lastprebr = lastpostbr = NULL;
-
-	if (comppatmatch && *comppatmatch && comppatmatch != opm)
-	    haspattern = 1;
-	if (!useline && uselist) {
-	    /* All this and the guy only wants to see the list, sigh. */
-	    cs = 0;
-	    foredel(ll);
-	    inststr(origline);
-	    cs = origcs;
-	    showinglist = -2;
-	} else if (useline == 2 && nmatches > 1) {
-	    int first = 1, nm = nmatches;
-	    Cmatch *mc;
+    pushheap();
+
+    ainfo = fainfo = NULL;
+    matchers = newlinklist();
+
+    zsfree(compqstack);
+    compqstack = ztrdup("\\");
+    if (instring == 2)
+	compqstack[0] = '"';
+    else if (instring)
+	compqstack[0] = '\'';
+
+    hasunqu = 0;
+    useline = (lst != COMP_LIST_COMPLETE);
+    useexact = isset(RECEXACT);
+    zsfree(compexactstr);
+    compexactstr = ztrdup("");
+    uselist = (useline ?
+	       ((isset(AUTOLIST) && !isset(BASHAUTOLIST)) ? 
+		(isset(LISTAMBIGUOUS) ? 3 : 2) : 0) : 1);
+    zsfree(comppatmatch);
+    opm = comppatmatch = ztrdup(useglob ? "*" : "");
+    zsfree(comppatinsert);
+    comppatinsert = ztrdup("menu");
+    forcelist = 0;
+    haspattern = 0;
+    complistmax = getiparam("LISTMAX");
+    zsfree(complastprompt);
+    complastprompt = ztrdup(((isset(ALWAYSLASTPROMPT) && zmult == 1) ||
+			     (unset(ALWAYSLASTPROMPT) && zmult != 1)) ?
+			    "yes" : "");
+    dolastprompt = 1;
+    zsfree(complist);
+    complist = ztrdup(isset(LISTROWSFIRST) ?
+		      (isset(LISTPACKED) ? "packed rows" : "rows") :
+		      (isset(LISTPACKED) ? "packed" : ""));
+    startauto = isset(AUTOMENU);
+    movetoend = ((cs == we || isset(ALWAYSTOEND)) ? 2 : 1);
+    showinglist = 0;
+    hasmatched = hasunmatched = 0;
+    minmlen = 1000000;
+    maxmlen = -1;
+    compignored = 0;
+
+    /* Make sure we have the completion list and compctl. */
+    if (makecomplist(s, incmd, lst)) {
+	/* Error condition: feeeeeeeeeeeeep(). */
+	cs = 0;
+	foredel(ll);
+	inststr(origline);
+	cs = origcs;
+	clearlist = 1;
+	ret = 1;
+	minfo.cur = NULL;
+	goto compend;
+    }
+    zsfree(lastprebr);
+    zsfree(lastpostbr);
+    lastprebr = lastpostbr = NULL;
+
+    if (comppatmatch && *comppatmatch && comppatmatch != opm)
+	haspattern = 1;
+    if (!useline && uselist) {
+	/* All this and the guy only wants to see the list, sigh. */
+	cs = 0;
+	foredel(ll);
+	inststr(origline);
+	cs = origcs;
+	showinglist = -2;
+    } else if (useline == 2 && nmatches > 1) {
+	int first = 1, nm = nmatches;
+	Cmatch *mc;
 
-	    menucmp = 1;
-	    menuacc = 0;
+	menucmp = 1;
+	menuacc = 0;
 
-	    for (minfo.group = amatches;
-		 minfo.group && !(minfo.group)->mcount;
-		 minfo.group = (minfo.group)->next);
+	for (minfo.group = amatches;
+	     minfo.group && !(minfo.group)->mcount;
+	     minfo.group = (minfo.group)->next);
 
-	    mc = (minfo.group)->matches;
+	mc = (minfo.group)->matches;
 
-	    while (1) {
-		if (!first)
-		    acceptlast();
-		first = 0;
+	while (1) {
+	    if (!first)
+		accept_last();
+	    first = 0;
 
-		if (!--nm)
-		    menucmp = 0;
+	    if (!--nm)
+		menucmp = 0;
 
-		do_single(*mc);
-		minfo.cur = mc;
+	    do_single(*mc);
+	    minfo.cur = mc;
 
-		if (!*++(minfo.cur)) {
-		    do {
-			if (!(minfo.group = (minfo.group)->next))
-			    break;
-		    } while (!(minfo.group)->mcount);
-		    if (!minfo.group)
+	    if (!*++(minfo.cur)) {
+		do {
+		    if (!(minfo.group = (minfo.group)->next))
 			break;
-		    minfo.cur = minfo.group->matches;
-		}
-		mc = minfo.cur;
+		} while (!(minfo.group)->mcount);
+		if (!minfo.group)
+		    break;
+		minfo.cur = minfo.group->matches;
 	    }
-	    menucmp = 0;
-	    minfo.cur = NULL;
-
-	    if (compforcelist && *compforcelist && uselist)
-		showinglist = -2;
-	    else
-		invalidatelist();
-	} else if (useline) {
-	    /* We have matches. */
-	    if (nmatches > 1) {
-		/* There is more than one match. */
-		ret = do_ambiguous();
-	    } else if (nmatches == 1) {
-		/* Only one match. */
-		Cmgroup m = amatches;
-
-		while (!m->mcount)
-		    m = m->next;
-		minfo.cur = NULL;
-		minfo.asked = 0;
-		do_single(m->matches[0]);
-		if (compforcelist && *compforcelist) {
-		    if (uselist)
-			showinglist = -2;
-		    else
-			clearlist = 1;
-		} else
-		    invalidatelist();
-	    }
-	} else {
-	    invalidatelist();
-	    if (compforcelist && *compforcelist)
-		clearlist = 1;
-	    cs = 0;
-	    foredel(ll);
-	    inststr(origline);
-	    cs = origcs;
+	    mc = minfo.cur;
 	}
-	/* Print the explanation strings if needed. */
-	if (!showinglist && validlist && usemenu != 2 && nmatches != 1 &&
-	    useline != 2 && (!oldlist || !listshown)) {
-	    onlyexpl = 1;
+	menucmp = 0;
+	minfo.cur = NULL;
+
+	if (forcelist)
 	    showinglist = -2;
+	else
+	    invalidatelist();
+    } else if (useline) {
+	/* We have matches. */
+	if (nmatches > 1) {
+	    /* There is more than one match. */
+	    ret = do_ambiguous();
+	} else if (nmatches == 1) {
+	    /* Only one match. */
+	    Cmgroup m = amatches;
+
+	    while (!m->mcount)
+		m = m->next;
+	    minfo.cur = NULL;
+	    minfo.asked = 0;
+	    do_single(m->matches[0]);
+	    if (forcelist) {
+		if (uselist)
+		    showinglist = -2;
+		else
+		    clearlist = 1;
+	    } else
+		invalidatelist();
 	}
-      compend:
-	for (n = firstnode(matchers); n; incnode(n))
-	    freecmatcher((Cmatcher) getdata(n));
+    } else {
+	invalidatelist();
+	if (forcelist)
+	    clearlist = 1;
+	cs = 0;
+	foredel(ll);
+	inststr(origline);
+	cs = origcs;
+    }
+    /* Print the explanation strings if needed. */
+    if (!showinglist && validlist && usemenu != 2 && nmatches != 1 &&
+	useline != 2 && (!oldlist || !listshown)) {
+	onlyexpl = 1;
+	showinglist = -2;
+    }
+ compend:
+    for (n = firstnode(matchers); n; incnode(n))
+	freecmatcher((Cmatcher) getdata(n));
 
-	ll = strlen((char *)line);
-	if (cs > ll)
-	    cs = ll;
-	popheap();
-    } LASTALLOC;
+    ll = strlen((char *)line);
+    if (cs > ll)
+	cs = ll;
+    popheap();
 
     return ret;
 }
@@ -428,7 +442,7 @@ before_complete(Hookdef dummy, int *lst)
     /* If we are doing a menu-completion... */
 
     if (menucmp && *lst != COMP_LIST_EXPAND && 
-	(!compwidget || compwidget == lastcompwidget)) {
+	(menucmp != 1 || !compwidget || compwidget == lastcompwidget)) {
 	do_menucmp(*lst);
 	return 1;
     }
@@ -442,13 +456,12 @@ before_complete(Hookdef dummy, int *lst)
     /* We may have to reset the cursor to its position after the   *
      * string inserted by the last completion. */
 
-    if (fromcomp & FC_INWORD)
-	if ((cs = lastend) > ll)
-	    cs = ll;
+    if ((fromcomp & FC_INWORD) && (cs = lastend) > ll)
+	cs = ll;
 
     /* Check if we have to start a menu-completion (via automenu). */
 
-    if (isset(AUTOMENU) && lastambig &&
+    if (startauto && lastambig &&
 	(!isset(BASHAUTOLIST) || lastambig == 2))
 	usemenu = 2;
 
@@ -477,11 +490,11 @@ after_complete(Hookdef dummy, Compldat dat)
 static void
 callcompfunc(char *s, char *fn)
 {
-    List list;
+    Eprog prog;
     int lv = lastval;
     char buf[20];
 
-    if ((list = getshfunc(fn)) != &dummy_list) {
+    if ((prog = getshfunc(fn)) != &dummy_eprog) {
 	char **p, *tmp;
 	int aadd = 0, usea = 1, icf = incompfunc, osc = sfcontext;
 	unsigned int rset, kset;
@@ -493,7 +506,7 @@ callcompfunc(char *s, char *fn)
 	rset = CP_ALLREALS;
 	kset = CP_ALLKEYS &
 	    ~(CP_PARAMETER | CP_REDIRECT | CP_QUOTE | CP_QUOTING |
-	      CP_EXACTSTR | CP_FORCELIST | CP_OLDLIST | CP_OLDINS |
+	      CP_EXACTSTR | CP_OLDLIST | CP_OLDINS |
 	      (useglob ? 0 : CP_PATMATCH));
 	zsfree(compvared);
 	if (varedarg) {
@@ -566,16 +579,11 @@ callcompfunc(char *s, char *fn)
 	if (usea && (!aadd || clwords[0])) {
 	    char **q;
 
-	    PERMALLOC {
-		q = compwords = (char **)
-		    zalloc((clwnum + 1) * sizeof(char *));
-		for (p = clwords + aadd; *p; p++, q++) {
-		    tmp = dupstring(*p);
-		    untokenize(tmp);
-		    *q = ztrdup(tmp);
-		}
-		*q = NULL;
-	    } LASTALLOC;
+	    q = compwords = (char **)
+		zalloc((clwnum + 1) * sizeof(char *));
+	    for (p = clwords + aadd; *p; p++, q++)
+		untokenize(*q = ztrdup(*p));
+	    *q = NULL;
 	} else
 	    compwords = (char **) zcalloc(sizeof(char *));
 
@@ -603,7 +611,7 @@ callcompfunc(char *s, char *fn)
 	zsfree(compprefix);
 	zsfree(compsuffix);
 	if (unset(COMPLETEINWORD)) {
-	    tmp = quotename(s, NULL);
+	    tmp = multiquote(s, 0);
 	    untokenize(tmp);
 	    compprefix = ztrdup(tmp);
 	    compsuffix = ztrdup("");
@@ -614,11 +622,11 @@ callcompfunc(char *s, char *fn)
 
 	    sav = *ss;
 	    *ss = '\0';
-	    tmp = quotename(s, NULL);
+	    tmp = multiquote(s, 0);
 	    untokenize(tmp);
 	    compprefix = ztrdup(tmp);
 	    *ss = sav;
-	    ss = quotename(ss, NULL);
+	    ss = multiquote(ss, 0);
 	    untokenize(ss);
 	    compsuffix = ztrdup(ss);
 	}
@@ -639,11 +647,20 @@ callcompfunc(char *s, char *fn)
 	case 2: complist = "autolist"; break;
 	case 3: complist = "ambiguous"; break;
 	}
+	if (isset(LISTPACKED))
+	    complist = dyncat(complist, " packed");
+	if (isset(LISTROWSFIRST))
+	    complist = dyncat(complist, " rows");
+
 	complist = ztrdup(complist);
 	zsfree(compinsert);
 	if (useline) {
 	    switch (usemenu) {
-	    case 0: compinsert = "unambiguous"; break;
+	    case 0:
+		compinsert = (isset(AUTOMENU) ?
+			      "automenu-unambiguous" :
+			      "unambiguous");
+		break;
 	    case 1: compinsert = "menu"; break;
 	    case 2: compinsert = "automenu"; break;
 	    }
@@ -701,7 +718,7 @@ callcompfunc(char *s, char *fn)
 		while (*p)
 		    addlinknode(largs, dupstring(*p++));
 	    }
-	    doshfunc(fn, list, largs, 0, 0);
+	    doshfunc(fn, prog, largs, 0, 0);
 	    cfret = lastval;
 	    lastval = olv;
 	} OLDHEAPS;
@@ -720,13 +737,14 @@ callcompfunc(char *s, char *fn)
 	    uselist = 3;
 	else
 	    uselist = 0;
-
+	forcelist = (complist && strstr(complist, "force"));
 	onlyexpl = (complist && strstr(complist, "expl"));
 
 	if (!compinsert)
 	    useline = 0;
 	else if (!strcmp(compinsert, "unambig") ||
-		 !strcmp(compinsert, "unambiguous"))
+		 !strcmp(compinsert, "unambiguous") ||
+		 !strcmp(compinsert, "automenu-unambiguous"))
 	    useline = 1, usemenu = 0;
 	else if (!strcmp(compinsert, "menu"))
 	    useline = 1, usemenu = 1;
@@ -747,6 +765,8 @@ callcompfunc(char *s, char *fn)
 	    insspace = (compinsert[strlen(compinsert) - 1] == ' ');
 	} else
 	    useline = usemenu = 0;
+	startauto = (compinsert &&
+		     !strcmp(compinsert, "automenu-unambiguous"));
 	useexact = (compexact && !strcmp(compexact, "accept"));
 
 	if (!comptoend || !*comptoend)
@@ -782,60 +802,21 @@ callcompfunc(char *s, char *fn)
 static int
 makecomplist(char *s, int incmd, int lst)
 {
-    struct cmlist ms;
-    Cmlist m;
-    char *p, *os = s;
-    int onm = nmatches, osi = movefd(0);
+    char *p;
 
     /* Inside $... ? */
     if (compfunc && (p = check_param(s, 0, 0)))
-	os = s = p;
-
-    /* We build a copy of the list of matchers to use to make sure that this
-     * works even if a shell function called from the completion code changes
-     * the global matchers. */
-
-    if ((m = cmatcher)) {
-	Cmlist mm, *mp = &mm;
-	int n;
-
-	for (n = 0; m; m = m->next, n++) {
-	    if (m->matcher) {
-		*mp = (Cmlist) zhalloc(sizeof(struct cmlist));
-		(*mp)->matcher = m->matcher;
-		(*mp)->next = NULL;
-		(*mp)->str = dupstring(m->str);
-		mp = &((*mp)->next);
-		addlinknode(matchers, m->matcher);
-		m->matcher->refc++;
-	    }
-	}
-	m = mm;
-	compmatcher = 1;
-	compmatchertot = n;
-    } else
-	compmatcher = 0;
+	s = p;
 
     linwhat = inwhat;
 
-    /* Walk through the global matchers. */
-    for (;;) {
+    if (compfunc) {
+	char *os = s;
+	int onm = nmatches, osi = movefd(0);
+
 	bmatchers = NULL;
-	zsfree(compmatcherstr);
-	if (m) {
-	    ms.next = NULL;
-	    ms.matcher = m->matcher;
-	    mstack = &ms;
-
-	    /* Store the matchers used in the bmatchers list which is used
-	     * when building new parts for the string to insert into the 
-	     * line. */
-	    add_bmatchers(m->matcher);
-	    compmatcherstr = ztrdup(m->str);
-	} else {
-	    mstack = NULL;
-	    compmatcherstr = ztrdup("");
-	}
+	mstack = NULL;
+
 	ainfo = (Aminfo) hcalloc(sizeof(struct aminfo));
 	fainfo = (Aminfo) hcalloc(sizeof(struct aminfo));
 
@@ -852,23 +833,12 @@ makecomplist(char *s, int incmd, int lst)
 	begcmgroup("default", 0);
 	menucmp = menuacc = newmatches = onlyexpl = 0;
 
-	runhookdef(COMPCTLBEFOREHOOK, NULL);
-
 	s = dupstring(os);
-	if (compfunc)
-	    callcompfunc(s, compfunc);
-	else {
-	    struct ccmakedat dat;
-
-	    dat.str = s;
-	    dat.incmd = incmd;
-	    dat.lst = lst;
-	    runhookdef(COMPCTLMAKEHOOK, (void *) &dat);
-	}
+	callcompfunc(s, compfunc);
 	endcmgroup(NULL);
 
-	runhookdef(COMPCTLAFTERHOOK,
-		   (void *) ((amatches && !oldlist) ? 1L : 0L));
+	/* Needed for compcall. */
+	runhookdef(COMPCTLCLEANUPHOOK, NULL);
 
 	if (oldlist) {
 	    nmatches = onm;
@@ -884,16 +854,14 @@ makecomplist(char *s, int incmd, int lst)
 
 	    return 0;
 	}
-	PERMALLOC {
-	    if (lastmatches) {
-		freematches(lastmatches);
-		lastmatches = NULL;
-	    }
-	    permmatches(1);
-	    amatches = pmatches;
-	    lastpermmnum = permmnum;
-	    lastpermgnum = permgnum;
-	} LASTALLOC;
+	if (lastmatches) {
+	    freematches(lastmatches);
+	    lastmatches = NULL;
+	}
+	permmatches(1);
+	amatches = pmatches;
+	lastpermmnum = permmnum;
+	lastpermgnum = permgnum;
 
 	lastmatches = pmatches;
 	lastlmatches = lmatches;
@@ -908,20 +876,69 @@ makecomplist(char *s, int incmd, int lst)
 
 	    return 0;
 	}
-	if (!m || !(m = m->next))
-	    break;
+	redup(osi, 0);
+	return 1;
+    } else {
+	struct ccmakedat dat;
+
+	dat.str = s;
+	dat.incmd = incmd;
+	dat.lst = lst;
+	runhookdef(COMPCTLMAKEHOOK, (void *) &dat);
+
+	/* Needed for compcall. */
+	runhookdef(COMPCTLCLEANUPHOOK, NULL);
+
+	return dat.lst;
+    }
+}
+
+/**/
+mod_export char *
+multiquote(char *s, int ign)
+{
+    if (s) {
+	char *os = s, *p = compqstack;
+
+	if (p && *p && (ign < 1 || p[ign])) {
+	    if (ign > 0)
+		p += ign;
+	    while (*p) {
+		if (ign >= 0 || p[1])
+		    s = bslashquote(s, NULL,
+				    (*p == '\'' ? 1 : (*p == '"' ? 2 : 0)));
+		p++;
+	    }
+	}
+	return (s == os ? dupstring(s) : s);
+    }
+    DPUTS(1, "BUG: null pointer in multiquote()");
+    return NULL;
+}
 
-	errflag = 0;
-	compmatcher++;
+/**/
+mod_export char *
+tildequote(char *s, int ign)
+{
+    if (s) {
+	int tilde;
+
+	if ((tilde = (*s == '~')))
+	    *s = 'x';
+	s = multiquote(s, ign);
+	if (tilde)
+	    *s = '~';
+
+	return s;
     }
-    redup(osi, 0);
-    return 1;
+    DPUTS(1, "BUG: null pointer in tildequote()");
+    return NULL;
 }
 
 /* Check if we have to complete a parameter name. */
 
 /**/
-char *
+mod_export char *
 check_param(char *s, int set, int test)
 {
     char *p;
@@ -1046,7 +1063,7 @@ check_param(char *s, int set, int test)
 /* Copy the given string and remove backslashes from the copy and return it. */
 
 /**/
-char *
+mod_export char *
 rembslash(char *s)
 {
     char *t = s = dupstring(s);
@@ -1065,7 +1082,7 @@ rembslash(char *s)
 /* This should probably be moved into tokenize(). */
 
 /**/
-char *
+mod_export char *
 ctokenize(char *p)
 {
     char *r = p;
@@ -1091,7 +1108,7 @@ ctokenize(char *p)
 }
 
 /**/
-char *
+mod_export char *
 comp_str(int *ipl, int *pl, int untok)
 {
     char *p = dupstring(compprefix);
@@ -1122,23 +1139,21 @@ comp_str(int *ipl, int *pl, int untok)
     return str;
 }
 
-/* This is for compset -q. */
-
 /**/
 int
 set_comp_sep(void)
 {
     int lip, lp;
-    char *s = comp_str(&lip, &lp, 0);
+    char *s = comp_str(&lip, &lp, 1);
     LinkList foo = newlinklist();
     LinkNode n;
     int owe = we, owb = wb, ocs = cs, swb, swe, scs, soffs, ne = noerrs;
-    int tl, got = 0, i = 0, cur = -1, oll = ll, sl;
-    int ois = instring, oib = inbackt, noffs = lip + lp;
-    char *tmp, *p, *ns, *ol = (char *) line, sav, oaq = autoq, *qp, *qs;
+    int tl, got = 0, i = 0, cur = -1, oll = ll, sl, remq;
+    int ois = instring, oib = inbackt, noffs = lp;
+    char *tmp, *p, *ns, *ol = (char *) line, sav, *qp, *qs, *ts, qc = '\0';
 
-    if (compisuffix)
-	s = dyncat(s, compisuffix);
+    s += lip;
+    wb += lip;
     untokenize(s);
 
     swb = swe = soffs = 0;
@@ -1155,7 +1170,8 @@ set_comp_sep(void)
     memcpy(tmp + 1, s, noffs);
     tmp[(scs = cs = 1 + noffs)] = 'x';
     strcpy(tmp + 2 + noffs, s + noffs);
-    tmp = rembslash(tmp);
+    if ((remq = (*compqstack == '\\')))
+	tmp = rembslash(tmp);
     inpush(dupstrspace(tmp), 0, NULL);
     line = (unsigned char *) tmp;
     ll = tl - 1;
@@ -1218,21 +1234,31 @@ set_comp_sep(void)
 		*p = '\'';
     }
     offs = owb;
+
+    untokenize(ts = dupstring(ns));
+
     if (*ns == Snull || *ns == Dnull) {
 	instring = (*ns == Snull ? 1 : 2);
 	inbackt = 0;
 	swb++;
 	if (ns[strlen(ns) - 1] == *ns && ns[1])
 	    swe--;
-	autoq = (*ns == Snull ? '\'' : '"');
+	zsfree(autoq);
+	autoq = ztrdup(compqstack[1] ? "" :
+		       multiquote(*ns == Snull ? "'" : "\"", 1));
+	qc = (*ns == Snull ? '\'' : '"');
+	ts++;
     } else {
 	instring = 0;
-	autoq = '\0';
+	zsfree(autoq);
+	autoq = NULL;
     }
     for (p = ns, i = swb; *p; p++, i++) {
 	if (INULL(*p)) {
-	    if (i < scs)
-		soffs--;
+	    if (i < scs) {
+		if (remq && *p == Bnull && p[1])
+		    swb -= 2;
+	    }
 	    if (p[1] || *p != Bnull) {
 		if (*p == Bnull) {
 		    if (scs == i + 1)
@@ -1248,17 +1274,28 @@ set_comp_sep(void)
 	    chuck(p--);
 	}
     }
+    ns = ts;
+
+    if (instring && strchr(compqstack, '\\')) {
+	int rl = strlen(ns), ql = strlen(multiquote(ns, !!compqstack[1]));
+
+	if (ql > rl)
+	    swb -= ql - rl;
+    }
     sav = s[(i = swb - 1)];
     s[i] = '\0';
-    qp = tricat(qipre, rembslash(s), "");
+    qp = rembslash(s);
     s[i] = sav;
     if (swe < swb)
 	swe = swb;
     swe--;
     sl = strlen(s);
-    if (swe > sl)
-	swe = sl, ns[swe - swb + 1] = '\0';
-    qs = tricat(rembslash(s + swe), qisuf, "");
+    if (swe > sl) {
+	swe = sl;
+	if (strlen(ns) > swe - swb + 1)
+	    ns[swe - swb + 1] = '\0';
+    }
+    qs = rembslash(s + swe);
     sl = strlen(ns);
     if (soffs > sl)
 	soffs = sl;
@@ -1266,6 +1303,11 @@ set_comp_sep(void)
     {
 	int set = CP_QUOTE | CP_QUOTING, unset = 0;
 
+	p = tricat((instring ? (instring == 1 ? "'" : "\"") : "\\"),
+		   compqstack, "");
+	zsfree(compqstack);
+	compqstack = p;
+
 	zsfree(compquote);
 	zsfree(compquoting);
 	if (instring == 2) {
@@ -1283,11 +1325,11 @@ set_comp_sep(void)
 	compquoting = ztrdup(compquoting);
 	comp_setunset(0, 0, set, unset);
 
+	zsfree(compprefix);
+	zsfree(compsuffix);
 	if (unset(COMPLETEINWORD)) {
 	    untokenize(ns);
-	    zsfree(compprefix);
 	    compprefix = ztrdup(ns);
-	    zsfree(compsuffix);
 	    compsuffix = ztrdup("");
 	} else {
 	    char *ss, sav;
@@ -1302,21 +1344,16 @@ set_comp_sep(void)
 	    untokenize(ss);
 	    compsuffix = ztrdup(ss);
 	}
+	tmp = tricat(compqiprefix, compiprefix, multiquote(qp, 1));
+	zsfree(compqiprefix);
+	compqiprefix = tmp;
+	tmp = tricat(multiquote(qs, 1), compisuffix, compqisuffix);
+	zsfree(compqisuffix);
+	compqisuffix = tmp;
 	zsfree(compiprefix);
 	compiprefix = ztrdup("");
 	zsfree(compisuffix);
 	compisuffix = ztrdup("");
-	zsfree(compqiprefix);
-	zsfree(compqisuffix);
-	if (ois) {
-	    compqiprefix = qp;
-	    compqisuffix = qs;
-	} else {
-	    compqiprefix = ztrdup(quotename(qp, NULL));
-	    zsfree(qp);
-	    compqisuffix = ztrdup(quotename(qs, NULL));
-	    zsfree(qs);
-	}
 	freearray(compwords);
 	i = countlinknodes(foo);
 	compwords = (char **) zalloc((i + 1) * sizeof(char *));
@@ -1327,7 +1364,6 @@ set_comp_sep(void)
 	compcurrent = cur + 1;
 	compwords[i] = NULL;
     }
-    autoq = oaq;
     instring = ois;
     inbackt = oib;
 
@@ -1337,7 +1373,7 @@ set_comp_sep(void)
 /* This stores the strings from the list in an array. */
 
 /**/
-void
+mod_export void
 set_list_array(char *name, LinkList l)
 {
     char **a, **p;
@@ -1354,7 +1390,7 @@ set_list_array(char *name, LinkList l)
 /* Get the words from a variable or a (list of words). */
 
 /**/
-char **
+mod_export char **
 get_user_var(char *nam)
 {
     if (!nam)
@@ -1423,19 +1459,37 @@ int
 addmatches(Cadata dat, char **argv)
 {
     char *s, *ms, *lipre = NULL, *lisuf = NULL, *lpre = NULL, *lsuf = NULL;
-    char **aign = NULL, **dparr = NULL, oaq = autoq, *oppre = dat->ppre;
-    char *oqp = qipre, *oqs = qisuf, qc, **disp = NULL;
+    char **aign = NULL, **dparr = NULL, *oaq = autoq, *oppre = dat->ppre;
+    char *oqp = qipre, *oqs = qisuf, qc, **disp = NULL, *ibuf = NULL;
     int lpl, lsl, pl, sl, bcp = 0, bcs = 0, bpadd = 0, bsadd = 0;
+    int ppl = 0, psl = 0;
     int llpl = 0, llsl = 0, nm = mnum, gflags = 0, ohp = haspattern;
-    int oisalt = 0, isalt, isexact, doadd, ois = instring, oib = inbackt;
+    int isexact, doadd, ois = instring, oib = inbackt;
     Cline lc = NULL, pline = NULL, sline = NULL;
     Cmatch cm;
     struct cmlist mst;
     Cmlist oms = mstack;
-    Patprog cp = NULL;
+    Patprog cp = NULL, *pign = NULL;
     LinkList aparl = NULL, oparl = NULL, dparl = NULL;
     Brinfo bp, bpl = brbeg, obpl, bsl = brend, obsl;
 
+    if (!*argv) {
+	SWITCHHEAPS(compheap) {
+	    /* Select the group in which to store the matches. */
+	    gflags = (((dat->aflags & CAF_NOSORT ) ? CGF_NOSORT  : 0) |
+		      ((dat->aflags & CAF_UNIQALL) ? CGF_UNIQALL : 0) |
+		      ((dat->aflags & CAF_UNIQCON) ? CGF_UNIQCON : 0));
+	    if (dat->group) {
+		endcmgroup(NULL);
+		begcmgroup(dat->group, gflags);
+	    } else {
+		endcmgroup(NULL);
+		begcmgroup("default", 0);
+	    }
+	} SWITCHBACKHEAPS;
+
+	return 1;
+    }
     for (bp = brbeg; bp; bp = bp->next)
 	bp->curpos = ((dat->aflags & CAF_QUOTE) ? bp->pos : bp->qpos);
     for (bp = brend; bp; bp = bp->next)
@@ -1447,300 +1501,373 @@ addmatches(Cadata dat, char **argv)
 	if (qc == '`') {
 	    instring = 0;
 	    inbackt = 0;
-	    autoq = '\0';
+	    autoq = "";
 	} else {
+	    char buf[2];
+
 	    instring = (qc == '\'' ? 1 : 2);
 	    inbackt = 0;
-	    autoq = qc;
+	    buf[0] = qc;
+	    buf[1] = '\0';
+	    autoq = multiquote(buf, 1);
 	}
     } else {
 	instring = inbackt = 0;
-	autoq = '\0';
+	autoq = NULL;
     }
     qipre = ztrdup(compqiprefix ? compqiprefix : "");
     qisuf = ztrdup(compqisuffix ? compqisuffix : "");
 
+    useexact = (compexact && !strcmp(compexact, "accept"));
+
     /* Switch back to the heap that was used when the completion widget
      * was invoked. */
     SWITCHHEAPS(compheap) {
-	HEAPALLOC {
-	    if ((doadd = (!dat->apar && !dat->opar && !dat->dpar)) &&
-		(dat->aflags & CAF_MATCH))
+	if ((doadd = (!dat->apar && !dat->opar && !dat->dpar))) {
+	    if (dat->aflags & CAF_MATCH)
 		hasmatched = 1;
-	    if (dat->apar)
-		aparl = newlinklist();
-	    if (dat->opar)
-		oparl = newlinklist();
-	    if (dat->dpar) {
-		if (*(dat->dpar) == '(')
-		    dparr = NULL;
-		else if ((dparr = get_user_var(dat->dpar)) && !*dparr)
-		    dparr = NULL;
-		dparl = newlinklist();
-	    }
-	    if (dat->exp) {
-		curexpl = (Cexpl) zhalloc(sizeof(struct cexpl));
-		curexpl->count = curexpl->fcount = 0;
-		curexpl->str = dupstring(dat->exp);
-	    } else
-		curexpl = NULL;
+	    else
+		hasunmatched = 1;
+	}
+	if (dat->apar)
+	    aparl = newlinklist();
+	if (dat->opar)
+	    oparl = newlinklist();
+	if (dat->dpar) {
+	    if (*(dat->dpar) == '(')
+		dparr = NULL;
+	    else if ((dparr = get_user_var(dat->dpar)) && !*dparr)
+		dparr = NULL;
+	    dparl = newlinklist();
+	}
+	if (dat->exp) {
+	    curexpl = (Cexpl) zhalloc(sizeof(struct cexpl));
+	    curexpl->count = curexpl->fcount = 0;
+	    curexpl->str = dupstring(dat->exp);
+	} else
+	    curexpl = NULL;
 
-	    /* Store the matcher in our stack of matchers. */
-	    if (dat->match) {
-		mst.next = mstack;
-		mst.matcher = dat->match;
-		mstack = &mst;
+	/* Store the matcher in our stack of matchers. */
+	if (dat->match) {
+	    mst.next = mstack;
+	    mst.matcher = dat->match;
+	    mstack = &mst;
 
-		if (!mnum)
-		    add_bmatchers(dat->match);
+	    add_bmatchers(dat->match);
 
-		addlinknode(matchers, dat->match);
-		dat->match->refc++;
+	    addlinknode(matchers, dat->match);
+	    dat->match->refc++;
+	}
+	if (mnum && (mstack || bmatchers))
+	    update_bmatchers();
+
+	/* Get the suffixes to ignore. */
+	if (dat->ign && (aign = get_user_var(dat->ign))) {
+	    char **ap, **sp, *tmp;
+	    Patprog *pp, prog;
+
+	    pign = (Patprog *) zhalloc((arrlen(aign) + 1) * sizeof(Patprog));
+
+	    for (ap = sp = aign, pp = pign; (tmp = *ap); ap++) {
+		tokenize(tmp);
+		remnulargs(tmp);
+		if (((tmp[0] == Quest && tmp[1] == Star) ||
+		     (tmp[1] == Quest && tmp[0] == Star)) &&
+		    tmp[2] && !haswilds(tmp + 2))
+		    untokenize(*sp++ = tmp + 2);
+		else if ((prog = patcompile(tmp, 0, NULL)))
+		    *pp++ = prog;
 	    }
-	    if (mnum && (mstack || bmatchers))
-		update_bmatchers();
-
-	    /* Get the suffixes to ignore. */
-	    if (dat->ign)
-		aign = get_user_var(dat->ign);
-	    /* Get the display strings. */
-	    if (dat->disp)
-		if ((disp = get_user_var(dat->disp)))
-		    disp--;
-	    /* Get the contents of the completion variables if we have
-	     * to perform matching. */
-	    if (dat->aflags & CAF_MATCH) {
-		lipre = dupstring(compiprefix);
-		lisuf = dupstring(compisuffix);
-		lpre = dupstring(compprefix);
-		lsuf = dupstring(compsuffix);
-		llpl = strlen(lpre);
-		llsl = strlen(lsuf);
-		/* Test if there is an existing -P prefix. */
-		if (dat->pre && *dat->pre) {
-		    char *dp = rembslash(dat->pre);
-
-		    pl = pfxlen(dp, lpre);
-		    llpl -= pl;
-		    lpre += pl;
+	    *sp = NULL;
+	    *pp = NULL;
+	    if (!*aign)
+		aign = NULL;
+	    if (!*pign)
+		pign = NULL;
+	}
+	/* Get the display strings. */
+	if (dat->disp)
+	    if ((disp = get_user_var(dat->disp)))
+		disp--;
+	/* Get the contents of the completion variables if we have
+	 * to perform matching. */
+	if (dat->aflags & CAF_MATCH) {
+	    lipre = dupstring(compiprefix);
+	    lisuf = dupstring(compisuffix);
+	    lpre = dupstring(compprefix);
+	    lsuf = dupstring(compsuffix);
+	    llpl = strlen(lpre);
+	    llsl = strlen(lsuf);
+	    /* Test if there is an existing -P prefix. */
+	    if (dat->pre && *dat->pre) {
+		pl = pfxlen(dat->pre, lpre);
+		llpl -= pl;
+		lpre += pl;
+	    }
+	}
+	/* Now duplicate the strings we have from the command line. */
+	if (dat->ipre)
+	    dat->ipre = (lipre ? dyncat(lipre, dat->ipre) :
+			 dupstring(dat->ipre));
+	else if (lipre)
+	    dat->ipre = lipre;
+	if (dat->isuf)
+	    dat->isuf = (lisuf ? dyncat(lisuf, dat->isuf) :
+			 dupstring(dat->isuf));
+	else if (lisuf)
+	    dat->isuf = lisuf;
+	if (dat->ppre) {
+	    dat->ppre = ((dat->flags & CMF_FILE) ?
+			 tildequote(dat->ppre, !!(dat->aflags & CAF_QUOTE)) :
+			 multiquote(dat->ppre, !!(dat->aflags & CAF_QUOTE)));
+	    lpl = strlen(dat->ppre);
+	} else
+	    lpl = 0;
+	if (dat->psuf) {
+	    dat->psuf = multiquote(dat->psuf, !!(dat->aflags & CAF_QUOTE));
+	    lsl = strlen(dat->psuf);
+	} else
+	    lsl = 0;
+	if (dat->aflags & CAF_MATCH) {
+	    int ml, gfl = 0;
+	    char *globflag = NULL;
+
+	    if (comppatmatch && *comppatmatch &&
+		dat->ppre && lpre[0] == '(' && lpre[1] == '#') {
+		char *p;
+
+		for (p = lpre + 2; *p && *p != ')'; p++);
+
+		if (*p == ')') {
+		    char sav = p[1];
+
+		    p[1] = '\0';
+		    globflag = dupstring(lpre);
+		    gfl = p - lpre + 1;
+		    p[1] = sav;
+
+		    lpre = p + 1;
+		    llpl -= gfl;
 		}
 	    }
-	    /* Now duplicate the strings we have from the command line. */
-	    if (dat->ipre)
-		dat->ipre = (lipre ? dyncat(lipre, dat->ipre) :
-			     dupstring(dat->ipre));
-	    else if (lipre)
-		dat->ipre = lipre;
-	    if (dat->isuf)
-		dat->isuf = (lisuf ? dyncat(lisuf, dat->isuf) :
-			     dupstring(dat->isuf));
-	    else if (lisuf)
-		dat->isuf = lisuf;
-	    if (dat->ppre) {
-		if (!(dat->aflags & CAF_QUOTE)) {
-		    dat->ppre = quotename(dat->ppre, NULL);
-		    if ((dat->flags & CMF_FILE) &&
-			dat->ppre[0] == '\\' && dat->ppre[1] == '~')
-			chuck(dat->ppre);
-		} else
-		    dat->ppre = dupstring(dat->ppre);
-		lpl = strlen(dat->ppre);
-	    } else
-		lpl = 0;
-	    if (dat->psuf) {
-		if (!(dat->aflags & CAF_QUOTE))
-		    dat->psuf = quotename(dat->psuf, NULL);
-		else
-		    dat->psuf = dupstring(dat->psuf);
-		lsl = strlen(dat->psuf);
-	    } else
-		lsl = 0;
-	    if (dat->aflags & CAF_MATCH) {
-		int ml;
-
-		s = dat->ppre ? dat->ppre : "";
-		if ((ml = match_str(lpre, s, &bpl, 0, NULL, 0, 0, 1)) >= 0) {
-		    if (matchsubs) {
-			Cline tmp = get_cline(NULL, 0, NULL, 0, NULL, 0, 0);
-
-			tmp->prefix = matchsubs;
-			if (matchlastpart)
-			    matchlastpart->next = tmp;
-			else
-			    matchparts = tmp;
-		    }
-		    pline = matchparts;
-		    lpre += ml;
-		    bcp = ml;
-		    bpadd = strlen(s) - ml;
-		} else {
-		    if (llpl <= lpl && strpfx(lpre, s))
-			lpre = "";
-		    else if (llpl > lpl && strpfx(s, lpre))
-			lpre += lpl;
+	    s = dat->ppre ? dat->ppre : "";
+	    if ((ml = match_str(lpre, s, &bpl, 0, NULL, 0, 0, 1)) >= 0) {
+		if (matchsubs) {
+		    Cline tmp = get_cline(NULL, 0, NULL, 0, NULL, 0, 0);
+
+		    tmp->prefix = matchsubs;
+		    if (matchlastpart)
+			matchlastpart->next = tmp;
 		    else
-			*argv = NULL;
-		    bcp = lpl;
+			matchparts = tmp;
 		}
-
-		s = dat->psuf ? dat->psuf : "";
-		if ((ml = match_str(lsuf, s, &bsl, 0, NULL, 1, 0, 1)) >= 0) {
-		    if (matchsubs) {
-			Cline tmp = get_cline(NULL, 0, NULL, 0, NULL, 0, CLF_SUF);
-
-			tmp->suffix = matchsubs;
-			if (matchlastpart)
-			    matchlastpart->next = tmp;
-			else
-			    matchparts = tmp;
-		    }
-		    sline = revert_cline(matchparts);
-		    lsuf[llsl - ml] = '\0';
-		    bcs = ml;
-		    bsadd = strlen(s) - ml;
-		} else {
-		    if (llsl <= lsl && strsfx(lsuf, s))
-			lsuf = "";
-		    else if (llsl > lsl && strsfx(s, lsuf))
-			lsuf[llsl - lsl] = '\0';
+		pline = matchparts;
+		lpre += ml;
+		llpl -= ml;
+		bcp = ml;
+		bpadd = strlen(s) - ml;
+	    } else {
+		if (llpl <= lpl && strpfx(lpre, s))
+		    lpre = "";
+		else if (llpl > lpl && strpfx(s, lpre))
+		    lpre += lpl;
+		else
+		    *argv = NULL;
+		bcp = lpl;
+	    }
+	    s = dat->psuf ? dat->psuf : "";
+	    if ((ml = match_str(lsuf, s, &bsl, 0, NULL, 1, 0, 1)) >= 0) {
+		if (matchsubs) {
+		    Cline tmp = get_cline(NULL, 0, NULL, 0, NULL, 0, CLF_SUF);
+
+		    tmp->suffix = matchsubs;
+		    if (matchlastpart)
+			matchlastpart->next = tmp;
 		    else
-			*argv = NULL;
-		    bcs = lsl;
+			matchparts = tmp;
 		}
-		if (comppatmatch && *comppatmatch) {
-		    int is = (*comppatmatch == '*');
-		    char *tmp = (char *) zhalloc(2 + llpl + llsl);
+		sline = revert_cline(matchparts);
+		lsuf[llsl - ml] = '\0';
+		llsl -= ml;
+		bcs = ml;
+		bsadd = strlen(s) - ml;
+	    } else {
+		if (llsl <= lsl && strsfx(lsuf, s))
+		    lsuf = "";
+		else if (llsl > lsl && strsfx(s, lsuf))
+		    lsuf[llsl - lsl] = '\0';
+		else
+		    *argv = NULL;
+		bcs = lsl;
+	    }
+	    if (comppatmatch && *comppatmatch) {
+		int is = (*comppatmatch == '*');
+		char *tmp = (char *) zhalloc(2 + llpl + llsl + gfl);
 
+		if (gfl) {
+		    strcpy(tmp, globflag);
+		    strcat(tmp, lpre);
+		} else
 		    strcpy(tmp, lpre);
-		    tmp[llpl] = 'x';
-		    strcpy(tmp + llpl + is, lsuf);
-
-		    tokenize(tmp);
-		    remnulargs(tmp);
-		    if (haswilds(tmp)) {
-			if (is)
-			    tmp[llpl] = Star;
-			if ((cp = patcompile(tmp, 0, NULL)))
-			    haspattern = 1;
-		    }
+		tmp[llpl + gfl] = 'x';
+		strcpy(tmp + llpl + gfl + is, lsuf);
+
+		tokenize(tmp);
+		remnulargs(tmp);
+		if (haswilds(tmp)) {
+		    if (is)
+			tmp[llpl + gfl] = Star;
+		    if ((cp = patcompile(tmp, 0, NULL)))
+			haspattern = 1;
 		}
 	    }
-	    if (*argv) {
-		if (dat->pre)
-		    dat->pre = dupstring(dat->pre);
-		if (dat->suf)
-		    dat->suf = dupstring(dat->suf);
-		if (!dat->prpre && (dat->prpre = oppre)) {
-		    singsub(&(dat->prpre));
-		    untokenize(dat->prpre);
-		} else
-		    dat->prpre = dupstring(dat->prpre);
-		/* Select the group in which to store the matches. */
-		gflags = (((dat->aflags & CAF_NOSORT ) ? CGF_NOSORT  : 0) |
-			  ((dat->aflags & CAF_UNIQALL) ? CGF_UNIQALL : 0) |
-			  ((dat->aflags & CAF_UNIQCON) ? CGF_UNIQCON : 0));
-		if (dat->group) {
-		    endcmgroup(NULL);
-		    begcmgroup(dat->group, gflags);
-		} else {
-		    endcmgroup(NULL);
-		    begcmgroup("default", 0);
-		}
-		/* Select the set of matches. */
-		oisalt = (dat->aflags & CAF_ALT);
-
-		if (dat->remf) {
-		    dat->remf = dupstring(dat->remf);
-		    dat->rems = NULL;
-		} else if (dat->rems)
-		    dat->rems = dupstring(dat->rems);
+	}
+	/* Select the group in which to store the matches. */
+	gflags = (((dat->aflags & CAF_NOSORT ) ? CGF_NOSORT  : 0) |
+		  ((dat->aflags & CAF_UNIQALL) ? CGF_UNIQALL : 0) |
+		  ((dat->aflags & CAF_UNIQCON) ? CGF_UNIQCON : 0));
+	if (dat->group) {
+	    endcmgroup(NULL);
+	    begcmgroup(dat->group, gflags);
+	} else {
+	    endcmgroup(NULL);
+	    begcmgroup("default", 0);
+	}
+	if (*argv) {
+	    if (dat->pre)
+		dat->pre = dupstring(dat->pre);
+	    if (dat->suf)
+		dat->suf = dupstring(dat->suf);
+	    if (!dat->prpre && (dat->prpre = oppre)) {
+		singsub(&(dat->prpre));
+		untokenize(dat->prpre);
+	    } else
+		dat->prpre = dupstring(dat->prpre);
+	    /* Select the set of matches. */
+
+	    if (dat->remf) {
+		dat->remf = dupstring(dat->remf);
+		dat->rems = NULL;
+	    } else if (dat->rems)
+		dat->rems = dupstring(dat->rems);
+
+	    if (lpre)
+		lpre = ((!(dat->aflags & CAF_QUOTE) &&
+			 (!dat->ppre && (dat->flags & CMF_FILE))) ?
+			tildequote(lpre, 1) : multiquote(lpre, 1));
+	    if (lsuf)
+		lsuf = multiquote(lsuf, 1);
+	}
+	/* Walk through the matches given. */
+	obpl = bpl;
+	obsl = bsl;
+	if (aign || pign) {
+	    int max = 0;
+	    char **ap = argv;
+
+	    ppl = (dat->ppre ? strlen(dat->ppre) : 0);
+	    while ((s = *ap++))
+		if ((sl = strlen(s)) > max)
+		    max = sl;
+	    psl = (dat->psuf ? strlen(dat->psuf) : 0);
+	    ibuf = (char *) zhalloc(1 + ppl + max + psl);
+	}
+	for (; (s = *argv); argv++) {
+	    bpl = obpl;
+	    bsl = obsl;
+	    if (disp) {
+		if (!*++disp)
+		    disp = NULL;
 	    }
-	    /* Walk through the matches given. */
-	    obpl = bpl;
-	    obsl = bsl;
-	    for (; (s = *argv); argv++) {
-		bpl = obpl;
-		bsl = obsl;
-		if (disp) {
-		    if (!*++disp)
-			disp = NULL;
-		}
-		sl = strlen(s);
-		isalt = oisalt;
-		if ((!dat->psuf || !*(dat->psuf)) && aign) {
+	    sl = strlen(s);
+	    if (aign || pign) {
+		int il = ppl + sl + psl, addit = 1;
+
+		if (ppl)
+		    memcpy(ibuf, dat->ppre, ppl);
+		strcpy(ibuf + ppl, s);
+		if (psl)
+		    strcpy(ibuf + ppl + sl, dat->psuf);
+
+		if (aign) {
 		    /* Do the suffix-test. If the match has one of the
-		     * suffixes from ign, we put it in the alternate set. */
+		     * suffixes from aign, we put it in the alternate set. */
 		    char **pt = aign;
 		    int filell;
 
-		    for (isalt = 0; !isalt && *pt; pt++)
-			if ((filell = strlen(*pt)) < sl
-			    && !strcmp(*pt, s + sl - filell))
-			    isalt = 1;
+		    for (; addit && *pt; pt++)
+			addit = !((filell = strlen(*pt)) < il &&
+				  !strcmp(*pt, ibuf + il - filell));
+		}
+		if (addit && pign) {
+		    Patprog *pt = pign;
 
-		    if (isalt && !doadd) {
-			if (dparr && !*++dparr)
-			    dparr = NULL;
-			continue;
-		    }
+		    for (; addit && *pt; pt++)
+			addit = !pattry(*pt, ibuf);
 		}
-		if (!(dat->aflags & CAF_MATCH)) {
-		    if (dat->aflags & CAF_QUOTE)
-			ms = dupstring(s);
-		    else
-			sl = strlen(ms = quotename(s, NULL));
-		    lc = bld_parts(ms, sl, -1, NULL);
-		    isexact = 0;
-		} else if (!(ms = comp_match(lpre, lsuf, s, cp, &lc,
-					     (!(dat->aflags & CAF_QUOTE) ?
-					      ((dat->ppre && dat->ppre) ||
-					       !(dat->flags & CMF_FILE) ? 1 : 2) : 0),
-					     &bpl, bcp, &bsl, bcs,
-					     &isexact))) {
+		if (!addit) {
+		    compignored++;
 		    if (dparr && !*++dparr)
 			dparr = NULL;
 		    continue;
 		}
-		if (doadd) {
-		    Brinfo bp;
-
-		    for (bp = obpl; bp; bp = bp->next)
-			bp->curpos += bpadd;
-		    for (bp = obsl; bp; bp = bp->next)
-			bp->curpos += bsadd;
-
-		    if ((cm = add_match_data(isalt, ms, lc, dat->ipre, NULL,
-					     dat->isuf, dat->pre, dat->prpre,
-					     dat->ppre, pline,
-					     dat->psuf, sline,
-					     dat->suf, dat->flags, isexact))) {
-			cm->rems = dat->rems;
-			cm->remf = dat->remf;
-			if (disp)
-			    cm->disp = dupstring(*disp);
-		    }
-		} else {
-		    if (dat->apar)
-			addlinknode(aparl, ms);
-		    if (dat->opar)
-			addlinknode(oparl, s);
-		    if (dat->dpar && dparr) {
-			addlinknode(dparl, *dparr);
-			if (!*++dparr)
-			    dparr = NULL;
-		    }
-		    free_cline(lc);
+	    }
+	    if (!(dat->aflags & CAF_MATCH)) {
+		if (dat->aflags & CAF_QUOTE)
+		    ms = dupstring(s);
+		else
+		    sl = strlen(ms = multiquote(s, 0));
+		lc = bld_parts(ms, sl, -1, NULL);
+		isexact = 0;
+	    } else if (!(ms = comp_match(lpre, lsuf, s, cp, &lc,
+					 (!(dat->aflags & CAF_QUOTE) ?
+					  (dat->ppre ||
+					   !(dat->flags & CMF_FILE) ? 1 : 2) : 0),
+					 &bpl, bcp, &bsl, bcs,
+					 &isexact))) {
+		if (dparr && !*++dparr)
+		    dparr = NULL;
+		continue;
+	    }
+	    if (doadd) {
+		Brinfo bp;
+
+		for (bp = obpl; bp; bp = bp->next)
+		    bp->curpos += bpadd;
+		for (bp = obsl; bp; bp = bp->next)
+		    bp->curpos += bsadd;
+
+		if ((cm = add_match_data(0, ms, lc, dat->ipre, NULL,
+					 dat->isuf, dat->pre, dat->prpre,
+					 dat->ppre, pline,
+					 dat->psuf, sline,
+					 dat->suf, dat->flags, isexact))) {
+		    cm->rems = dat->rems;
+		    cm->remf = dat->remf;
+		    if (disp)
+			cm->disp = dupstring(*disp);
 		}
+	    } else {
+		if (dat->apar)
+		    addlinknode(aparl, ms);
+		if (dat->opar)
+		    addlinknode(oparl, s);
+		if (dat->dpar && dparr) {
+		    addlinknode(dparl, *dparr);
+		    if (!*++dparr)
+			dparr = NULL;
+		}
+		free_cline(lc);
 	    }
-	    if (dat->apar)
-		set_list_array(dat->apar, aparl);
-	    if (dat->opar)
-		set_list_array(dat->opar, oparl);
-	    if (dat->dpar)
-		set_list_array(dat->dpar, dparl);
-	    if (dat->exp)
-		addexpl();
-	} LASTALLOC;
+	}
+	if (dat->apar)
+	    set_list_array(dat->apar, aparl);
+	if (dat->opar)
+	    set_list_array(dat->opar, oparl);
+	if (dat->dpar)
+	    set_list_array(dat->dpar, dparl);
+	if (dat->exp)
+	    addexpl();
     } SWITCHBACKHEAPS;
 
     /* We switched back to the current heap, now restore the stack of
@@ -1764,7 +1891,7 @@ addmatches(Cadata dat, char **argv)
 /* This adds all the data we have for a match. */
 
 /**/
-Cmatch
+mod_export Cmatch
 add_match_data(int alt, char *str, Cline line,
 	       char *ipre, char *ripre, char *isuf,
 	       char *pre, char *prpre,
@@ -1797,21 +1924,12 @@ add_match_data(int alt, char *str, Cline line,
 	salen += (qisl = strlen(qisuf));
 
     if (salen) {
-	char *asuf = (char *) zhalloc(salen);
 	Cline pp, p, s, sl = NULL;
-	
-
-	if (psl)
-	    memcpy(asuf, psuf, psl);
-	if (isl)
-	    memcpy(asuf + psl, isuf, isl);
-	if (qisl)
-	    memcpy(asuf + psl + isl, qisuf, qisl);
 
 	for (pp = NULL, p = line; p->next; pp = p, p = p->next);
 
-	if (salen > qisl) {
-	    s = bld_parts(asuf, salen - qisl, salen - qisl, &sl);
+	if (psl) {
+	    s = bld_parts(psuf, psl, psl, &sl);
 
 	    if (sline) {
 		Cline sp;
@@ -1821,6 +1939,7 @@ add_match_data(int alt, char *str, Cline line,
 		for (sp = sline; sp->next; sp = sp->next);
 		sp->next = s;
 		s = sline;
+		sline = NULL;
 	    }
 	    if (!(p->flags & (CLF_SUF | CLF_MID)) &&
 		!p->llen && !p->wlen && !p->olen) {
@@ -1832,7 +1951,7 @@ add_match_data(int alt, char *str, Cline line,
 		    s->prefix = p->prefix;
 		    p->prefix = NULL;
 		}
-		s->flags |= (p->flags & CLF_MATCHED);
+		s->flags |= (p->flags & CLF_MATCHED) | CLF_MID;
 		free_cline(p);
 		if (pp)
 		    pp->next = s;
@@ -1841,8 +1960,29 @@ add_match_data(int alt, char *str, Cline line,
 	    } else
 		p->next = s;
 	}
+	if (isl) {
+	    Cline tsl;
+
+	    s = bld_parts(isuf, isl, isl, &tsl);
+
+	    if (sl)
+		sl->next = s;
+	    else if (sline) {
+		Cline sp;
+
+		sline = cp_cline(sline, 1);
+
+		for (sp = sline; sp->next; sp = sp->next);
+		sp->next = s;
+		p->next = sline;
+		sline = NULL;
+	    } else
+		p->next = s;
+
+	    sl = tsl;
+	}
 	if (qisl) {
-	    Cline qsl = bld_parts(asuf + psl + isl, qisl, qisl, NULL);
+	    Cline qsl = bld_parts(qisuf, qisl, qisl, NULL);
 
 	    qsl->flags |= CLF_SUF;
 	    qsl->suffix = qsl->prefix;
@@ -1888,7 +2028,7 @@ add_match_data(int alt, char *str, Cline line,
 		p = bld_parts(ppre, ppl, ppl, &lp);
 
 	    if (lp->prefix && !(line->flags & (CLF_SUF | CLF_MID)) &&
-		!p->llen && !p->wlen && !p->olen) {
+		!lp->llen && !lp->wlen && !lp->olen) {
 		Cline lpp;
 
 		for (lpp = lp->prefix; lpp->next; lpp = lpp->next);
@@ -1952,8 +2092,8 @@ add_match_data(int alt, char *str, Cline line,
 	} else
 	    for (p = lp = cp_cline(pline, 1); lp->next; lp = lp->next);
 
-	if (lp->prefix && !(line->flags & (CLF_SUF | CLF_MID)) &&
-	    !p->llen && !p->wlen && !p->olen) {
+	if (lp->prefix && !(line->flags & CLF_SUF) &&
+	    !lp->llen && !lp->wlen && !lp->olen) {
 	    Cline lpp;
 
 	    for (lpp = lp->prefix; lpp->next; lpp = lpp->next);
@@ -1994,7 +2134,14 @@ add_match_data(int alt, char *str, Cline line,
 	cm->isuf = (isuf && *isuf ? isuf : NULL);
     cm->pre = pre;
     cm->suf = suf;
-    cm->flags = flags;
+    cm->flags = (flags |
+		 (complist ?
+		  ((strstr(complist, "packed") ? CMF_PACKED : 0) |
+		   (strstr(complist, "rows")   ? CMF_ROWS   : 0)) : 0));
+
+    if ((*compqstack == '\\' && compqstack[1]) ||
+	(autoq && *compqstack && compqstack[1] == '\\'))
+	cm->flags |= CMF_NOSPACE;
     if (nbrbeg) {
 	int *p;
 	Brinfo bp;
@@ -2017,7 +2164,7 @@ add_match_data(int alt, char *str, Cline line,
 	cm->brsl = NULL;
     cm->qipl = qipl;
     cm->qisl = qisl;
-    cm->autoq = (autoq ? autoq : (inbackt ? '`' : '\0'));
+    cm->autoq = dupstring(autoq ? autoq : (inbackt ? "`" : NULL));
     cm->rems = cm->remf = cm->disp = NULL;
 
     if ((lastprebr || lastpostbr) && !hasbrpsfx(cm, lastprebr, lastpostbr))
@@ -2032,7 +2179,12 @@ add_match_data(int alt, char *str, Cline line,
     addlinknode((alt ? fmatches : matches), cm);
 
     newmatches = 1;
+    mgroup->new = 1;
+    if (alt)
+	compignored++;
 
+    if (!complastprompt || !*complastprompt)
+	dolastprompt = 0;
     /* One more match for this explanation. */
     if (curexpl) {
 	if (alt)
@@ -2056,8 +2208,8 @@ add_match_data(int alt, char *str, Cline line,
     /* Do we have an exact match? More than one? */
     if (exact) {
 	if (!ai->exact) {
-	    ai->exact = 1;
-	    if (incompfunc) {
+	    ai->exact = useexact;
+	    if (incompfunc && (!compexactstr || !*compexactstr)) {
 		/* If a completion widget is active, we make the exact
 		 * string available in `compstate'. */
 
@@ -2076,7 +2228,7 @@ add_match_data(int alt, char *str, Cline line,
 		comp_setunset(0, 0, CP_EXACTSTR, 0);
 	    }
 	    ai->exactm = cm;
-	} else {
+	} else if (useexact) {
 	    ai->exact = 2;
 	    ai->exactm = NULL;
 	    if (incompfunc)
@@ -2089,7 +2241,7 @@ add_match_data(int alt, char *str, Cline line,
 /* This begins a new group of matches. */
 
 /**/
-void
+mod_export void
 begcmgroup(char *n, int flags)
 {
     if (n) {
@@ -2118,6 +2270,8 @@ begcmgroup(char *n, int flags)
     mgroup->matches = NULL;
     mgroup->ylist = NULL;
     mgroup->expls = NULL;
+    mgroup->perm = NULL;
+    mgroup->new = 0;
 
     mgroup->lexpls = expls = newlinklist();
     mgroup->lmatches = matches = newlinklist();
@@ -2132,7 +2286,7 @@ begcmgroup(char *n, int flags)
 /* End the current group for now. */
 
 /**/
-void
+mod_export void
 endcmgroup(char **ylist)
 {
     mgroup->ylist = ylist;
@@ -2141,7 +2295,7 @@ endcmgroup(char **ylist)
 /* Add an explanation string to the current group, joining duplicates. */
 
 /**/
-void
+mod_export void
 addexpl(void)
 {
     LinkNode n;
@@ -2221,7 +2375,7 @@ makearray(LinkList l, int type, int flags, int *np, int *nlp, int *llp)
     int n, nl = 0, ll = 0;
 
     /* Build an array for the matches. */
-    rp = ap = (Cmatch *) ncalloc(((n = countlinknodes(l)) + 1) *
+    rp = ap = (Cmatch *) hcalloc(((n = countlinknodes(l)) + 1) *
 				 sizeof(Cmatch));
 
     /* And copy them into it. */
@@ -2252,22 +2406,28 @@ makearray(LinkList l, int type, int flags, int *np, int *nlp, int *llp)
 		  (int (*) _((const void *, const void *)))matchcmp);
 
 	    if (!(flags & CGF_UNIQCON)) {
+		int dup;
+
 		/* And delete the ones that occur more than once. */
 		for (ap = cp = rp; *ap; ap++) {
 		    *cp++ = *ap;
 		    for (bp = ap; bp[1] && matcheq(*ap, bp[1]); bp++, n--);
 		    ap = bp;
 		    /* Mark those, that would show the same string in the list. */
-		    for (; bp[1] && !(*ap)->disp && !(bp[1])->disp &&
-			     !strcmp((*ap)->str, (bp[1])->str); bp++)
-			(bp[1])->flags |= CMF_NOLIST;
+		    for (dup = 0; bp[1] && !(*ap)->disp && !(bp[1])->disp &&
+			     !strcmp((*ap)->str, (bp[1])->str); bp++) {
+			(bp[1])->flags |= CMF_MULT;
+			dup = 1;
+		    }
+		    if (dup)
+			(*ap)->flags |= CMF_FMULT;
 		}
 		*cp = NULL;
 	    }
 	    for (ap = rp; *ap; ap++) {
 		if ((*ap)->disp && ((*ap)->flags & CMF_DISPLINE))
 		    ll++;
-		if ((*ap)->flags & CMF_NOLIST)
+		if ((*ap)->flags & (CMF_NOLIST | CMF_MULT))
 		    nl++;
 	    }
 	} else {
@@ -2282,20 +2442,26 @@ makearray(LinkList l, int type, int flags, int *np, int *nlp, int *llp)
 		    *cp = NULL;
 		}
 	    } else if (!(flags & CGF_UNIQCON)) {
+		int dup;
+
 		for (ap = cp = rp; *ap; ap++) {
 		    *cp++ = *ap;
 		    for (bp = ap; bp[1] && matcheq(*ap, bp[1]); bp++, n--);
 		    ap = bp;
-		    for (; bp[1] && !(*ap)->disp && !(bp[1])->disp &&
-			     !strcmp((*ap)->str, (bp[1])->str); bp++)
-			(bp[1])->flags |= CMF_NOLIST;
+		    for (dup = 0; bp[1] && !(*ap)->disp && !(bp[1])->disp &&
+			     !strcmp((*ap)->str, (bp[1])->str); bp++) {
+			(bp[1])->flags |= CMF_MULT;
+			dup = 1;
+		    }
+		    if (dup)
+			(*ap)->flags |= CMF_FMULT;
 		}
 		*cp = NULL;
 	    }
 	    for (ap = rp; *ap; ap++) {
 		if ((*ap)->disp && ((*ap)->flags & CMF_DISPLINE))
 		    ll++;
-		if ((*ap)->flags & CMF_NOLIST)
+		if ((*ap)->flags & (CMF_NOLIST | CMF_MULT))
 		    nl++;
 	    }
 	}
@@ -2317,7 +2483,7 @@ dupmatch(Cmatch m, int nbeg, int nend)
 {
     Cmatch r;
 
-    r = (Cmatch) ncalloc(sizeof(struct cmatch));
+    r = (Cmatch) zcalloc(sizeof(struct cmatch));
 
     r->str = ztrdup(m->str);
     r->ipre = ztrdup(m->ipre);
@@ -2349,10 +2515,10 @@ dupmatch(Cmatch m, int nbeg, int nend)
 	r->brsl = NULL;
     r->rems = ztrdup(m->rems);
     r->remf = ztrdup(m->remf);
-    r->autoq = m->autoq;
+    r->autoq = ztrdup(m->autoq);
     r->qipl = m->qipl;
     r->qisl = m->qisl;
-    r->disp = dupstring(m->disp);
+    r->disp = ztrdup(m->disp);
 
     return r;
 }
@@ -2360,24 +2526,24 @@ dupmatch(Cmatch m, int nbeg, int nend)
 /* This duplicates all groups of matches. */
 
 /**/
-int
+mod_export int
 permmatches(int last)
 {
-    Cmgroup g = amatches, n;
+    Cmgroup g = amatches, n, opm;
     Cmatch *p, *q;
     Cexpl *ep, *eq, e, o;
     LinkList mlist;
     static int fi = 0;
-    int nn, nl, ll, gn = 1, mn = 1, rn;
+    int nn, nl, ll, gn = 1, mn = 1, rn, ofi = fi;
 
-    if (pmatches && !newmatches)
+    if (pmatches && !newmatches) {
+	if (last && fi)
+	    ainfo = fainfo;
 	return fi;
-
+    }
     newmatches = fi = 0;
 
-    if (pmatches)
-	freematches(pmatches);
-
+    opm = pmatches;
     pmatches = lmatches = NULL;
     nmatches = smatches = 0;
 
@@ -2387,8 +2553,8 @@ permmatches(int last)
 	fi = 1;
     }
     while (g) {
-	HEAPALLOC {
-	    if (empty(g->lmatches))
+	if (fi != ofi || !g->perm || g->new) {
+	    if (fi)
 		/* We have no matches, try ignoring fignore. */
 		mlist = g->lfmatches;
 	    else
@@ -2407,51 +2573,67 @@ permmatches(int last)
 					   NULL, NULL);
 
 	    g->ccount = 0;
-	} LASTALLOC;
-
-	nmatches += g->mcount;
-	smatches += g->lcount;
-
-	n = (Cmgroup) ncalloc(sizeof(struct cmgroup));
-
-	if (!lmatches)
-	    lmatches = n;
-	if (pmatches)
-	    pmatches->prev = n;
-	n->next = pmatches;
-	pmatches = n;
-	n->prev = 0;
-	n->num = gn++;
-
-	n->flags = g->flags;
-	n->mcount = g->mcount;
-	n->matches = p = (Cmatch *) ncalloc((n->mcount + 1) *
-					    sizeof(Cmatch));
-	for (q = g->matches; *q; q++, p++)
-	    *p = dupmatch(*q, nbrbeg, nbrend);
-	*p = NULL;
-
-	n->lcount = g->lcount;
-	n->llcount = g->llcount;
-	if (g->ylist)
-	    n->ylist = arrdup(g->ylist);
-	else
-	    n->ylist = NULL;
-
-	if ((n->ecount = g->ecount)) {
-	    n->expls = ep = (Cexpl *) ncalloc((n->ecount + 1) *
-					      sizeof(Cexpl));
-	    for (eq = g->expls; (o = *eq); eq++, ep++) {
-		*ep = e = (Cexpl) ncalloc(sizeof(struct cexpl));
-		e->count = (fi ? o->fcount : o->count);
-		e->str = ztrdup(o->str);
-	    }
-	    *ep = NULL;
-	} else
-	    n->expls = NULL;
 
-	n->widths = NULL;
+	    nmatches += g->mcount;
+	    smatches += g->lcount;
+
+	    n = (Cmgroup) zcalloc(sizeof(struct cmgroup));
+
+	    if (g->perm) {
+		g->perm->next = NULL;
+		freematches(g->perm);
+	    }
+	    g->perm = n;
+
+	    if (!lmatches)
+		lmatches = n;
+	    if (pmatches)
+		pmatches->prev = n;
+	    n->next = pmatches;
+	    pmatches = n;
+	    n->prev = NULL;
+	    n->num = gn++;
+	    n->flags = g->flags;
+	    n->mcount = g->mcount;
+	    n->matches = p = (Cmatch *) zcalloc((n->mcount + 1) * sizeof(Cmatch));
+	    n->name = ztrdup(g->name);
+	    for (q = g->matches; *q; q++, p++)
+		*p = dupmatch(*q, nbrbeg, nbrend);
+	    *p = NULL;
+
+	    n->lcount = g->lcount;
+	    n->llcount = g->llcount;
+	    if (g->ylist)
+		n->ylist = zarrdup(g->ylist);
+	    else
+		n->ylist = NULL;
+
+	    if ((n->ecount = g->ecount)) {
+		n->expls = ep = (Cexpl *) zcalloc((n->ecount + 1) * sizeof(Cexpl));
+		for (eq = g->expls; (o = *eq); eq++, ep++) {
+		    *ep = e = (Cexpl) zcalloc(sizeof(struct cexpl));
+		    e->count = (fi ? o->fcount : o->count);
+		    e->str = ztrdup(o->str);
+		}
+		*ep = NULL;
+	    } else
+		n->expls = NULL;
 
+	    n->widths = NULL;
+	} else {
+	    if (!lmatches)
+		lmatches = g->perm;
+	    if (pmatches)
+		pmatches->prev = g->perm;
+	    g->perm->next = pmatches;
+	    pmatches = g->perm;
+	    g->perm->prev = NULL;
+
+	    nmatches += g->mcount;
+	    smatches += g->lcount;
+	    g->num = gn++;
+	}
+	g->new = 0;
 	g = g->next;
     }
     for (g = pmatches; g; g = g->next) {
@@ -2490,6 +2672,7 @@ freematch(Cmatch m, int nbeg, int nend)
     zsfree(m->rems);
     zsfree(m->remf);
     zsfree(m->disp);
+    zsfree(m->autoq);
     zfree(m->brpl, nbeg * sizeof(int));
     zfree(m->brsl, nend * sizeof(int));
 
@@ -2499,7 +2682,7 @@ freematch(Cmatch m, int nbeg, int nend)
 /* This frees the groups of matches. */
 
 /**/
-void
+mod_export void
 freematches(Cmgroup g)
 {
     Cmgroup n;
@@ -2508,7 +2691,7 @@ freematches(Cmgroup g)
 
     while (g) {
 	n = g->next;
-	
+
 	for (m = g->matches; *m; m++)
 	    freematch(*m, g->nbrbeg, g->nbrend);
 
@@ -2523,6 +2706,7 @@ freematches(Cmgroup g)
 	    }
 	    free(g->expls);
 	}
+	zsfree(g->name);
 	free(g);
 
 	g = n;
diff --git a/Src/Zle/complete.c b/Src/Zle/complete.c
index 283b8de62..ef961eeba 100644
--- a/Src/Zle/complete.c
+++ b/Src/Zle/complete.c
@@ -1,5 +1,5 @@
 /*
- * complete.c - the complete module
+ * complete.c - the complete module, interface part
  *
  * This file is part of zsh, the Z shell.
  *
@@ -29,12 +29,50 @@
 
 #include "complete.mdh"
 #include "complete.pro"
-#define GLOBAL_PROTOTYPES
-#include "zle_tricky.pro"
-#undef GLOBAL_PROTOTYPES
+
+/* global variables for shell parameters in new style completion */
 
 /**/
-void
+mod_export zlong compcurrent;
+/**/
+zlong complistmax,
+      complistlines,
+      compignored;
+
+/**/
+mod_export
+char **compwords,
+     *compprefix,
+     *compsuffix,
+     *compisuffix,
+     *compqiprefix,
+     *compqisuffix,
+     *compquote,
+     *compqstack,
+     *comppatmatch,
+     *complastprompt;
+/**/
+char *compiprefix,
+     *compcontext,
+     *compparameter,
+     *compredirect,
+     *compquoting,
+     *comprestore,
+     *complist,
+     *compinsert,
+     *compexact,
+     *compexactstr,
+     *comppatinsert,
+     *comptoend,
+     *compoldlist,
+     *compoldins,
+     *compvared;
+
+/**/
+Param *comprpms, *compkpms;
+
+/**/
+mod_export void
 freecmlist(Cmlist l)
 {
     Cmlist n;
@@ -51,7 +89,7 @@ freecmlist(Cmlist l)
 }
 
 /**/
-void
+mod_export void
 freecmatcher(Cmatcher m)
 {
     Cmatcher n;
@@ -86,29 +124,10 @@ freecpattern(Cpattern p)
     }
 }
 
-/* Copy a list of completion matchers. */
-
-static Cmlist
-cpcmlist(Cmlist l)
-{
-    Cmlist r = NULL, *p = &r, n;
-
-    while (l) {
-	*p = n = (Cmlist) zalloc(sizeof(struct cmlist));
-	n->next = NULL;
-	n->matcher = cpcmatcher(l->matcher);
-	n->str = ztrdup(l->str);
-
-	p = &(n->next);
-	l = l->next;
-    }
-    return r;
-}
-
 /* Copy a completion matcher list. */
 
 /**/
-Cmatcher
+mod_export Cmatcher
 cpcmatcher(Cmatcher m)
 {
     Cmatcher r = NULL, *p = &r, n;
@@ -155,37 +174,10 @@ cpcpattern(Cpattern o)
     return r;
 }
 
-/* Set the global match specs. */
-
-/**/
-int
-set_gmatcher(char *name, char **argv)
-{
-    Cmlist l = NULL, *q = &l, n;
-    Cmatcher m;
-
-    while (*argv) {
-	if ((m = parse_cmatcher(name, *argv)) == pcm_err)
-	    return 1;
-	*q = n = (Cmlist) zhalloc(sizeof(struct cmlist));
-	n->next = NULL;
-	n->matcher = m;
-	n->str = *argv++;
-
-	q = &(n->next);
-    }
-    freecmlist(cmatcher);
-    PERMALLOC {
-	cmatcher = cpcmlist(l);
-    } LASTALLOC;
-
-    return 1;
-}
-
 /* Parse a string for matcher control, containing multiple matchers. */
 
 /**/
-Cmatcher
+mod_export Cmatcher
 parse_cmatcher(char *name, char *s)
 {
     Cmatcher ret = NULL, r = NULL, n;
@@ -262,8 +254,11 @@ parse_cmatcher(char *name, char *s)
 		return pcm_err;
 	    }
 	    word = NULL;
-	    wl = -1;
-	    s++;
+	    if (*++s == '*') {
+		s++;
+		wl = -2;
+	    } else
+		wl = -1;
 	} else {
 	    word = parse_pattern(name, &s, &wl, 0, &err);
 
@@ -389,17 +384,17 @@ static int
 bin_compadd(char *name, char **argv, char *ops, int func)
 {
     struct cadata dat;
-    char *p, **sp, *e, *m = NULL;
+    char *p, **sp, *e, *m = NULL, *mstr = NULL;
     int dm;
     Cmatcher match = NULL;
 
     if (incompfunc != 1) {
-	zerrnam(name, "can only be called from completion function", NULL, 0);
+	zwarnnam(name, "can only be called from completion function", NULL, 0);
 	return 1;
     }
     dat.ipre = dat.isuf = dat.ppre = dat.psuf = dat.prpre =
 	dat.pre = dat.suf = dat.group = dat.rems = dat.remf = dat.disp = 
-	dat.ign = dat.exp = dat.apar = dat.opar = dat.dpar = dat.ylist = NULL;
+	dat.ign = dat.exp = dat.apar = dat.opar = dat.dpar = NULL;
     dat.match = NULL;
     dat.flags = 0;
     dat.aflags = CAF_MATCH;
@@ -462,10 +457,6 @@ bin_compadd(char *name, char **argv, char *ops, int func)
 		if (!(dat.aflags & CAF_UNIQALL))
 		    dat.aflags |= CAF_UNIQCON;
 		break;
-	    case 'y':
-		sp = &(dat.ylist);
-		e = "string expected after -%c";
-		break;
 	    case 'i':
 		sp = &(dat.ipre);
 		e = "string expected after -%c";
@@ -486,9 +477,6 @@ bin_compadd(char *name, char **argv, char *ops, int func)
 		sp = &(dat.prpre);
 		e = "string expected after -%c";
 		break;
-	    case 'a':
-		dat.aflags |= CAF_ALT;
-		break;
 	    case 'M':
 		sp = &m;
 		e = "matching specification expected after -%c";
@@ -531,7 +519,7 @@ bin_compadd(char *name, char **argv, char *ops, int func)
 		argv++;
 		goto ca_args;
 	    default:
-		zerrnam(name, "bad option: -%c", NULL, *p);
+		zwarnnam(name, "bad option: -%c", NULL, *p);
 		return 1;
 	    }
 	    if (sp) {
@@ -545,18 +533,29 @@ bin_compadd(char *name, char **argv, char *ops, int func)
 			*sp = *argv;
 		    p = "" - 1;
 		} else {
-		    zerrnam(name, e, NULL, *p);
+		    zwarnnam(name, e, NULL, *p);
 		    return 1;
 		}
-		if (dm && (match = parse_cmatcher(name, m)) == pcm_err) {
-		    match = NULL;
-		    return 1;
+		if (dm) {
+		    if (mstr)
+			mstr = tricat(mstr, " ", m);
+		    else
+			mstr = ztrdup(m);
+		    m = NULL;
 		}
 	    }
 	}
     }
+    if (mstr && (match = parse_cmatcher(name, mstr)) == pcm_err) {
+	zsfree(mstr);
+	return 1;
+    }
+    zsfree(mstr);
+
  ca_args:
-    if (!*argv)
+
+    if (!*argv && !dat.group &&
+	!(dat.aflags & (CAF_NOSORT|CAF_UNIQALL|CAF_UNIQCON)))
 	return 1;
 
     dat.match = match = cpcmatcher(match);
@@ -574,7 +573,7 @@ bin_compadd(char *name, char **argv, char *ops, int func)
 #define CVT_SUFPAT   5
 
 /**/
-void
+mod_export void
 ignore_prefix(int l)
 {
     if (l) {
@@ -598,7 +597,7 @@ ignore_prefix(int l)
 }
 
 /**/
-void
+mod_export void
 ignore_suffix(int l)
 {
     if (l) {
@@ -621,7 +620,7 @@ ignore_suffix(int l)
 }
 
 /**/
-void
+mod_export void
 restrict_range(int b, int e)
 {
     int wl = arrlen(compwords) - 1;
@@ -644,6 +643,7 @@ restrict_range(int b, int e)
     }
 }
 
+/**/
 static int
 do_comp_vars(int test, int na, char *sa, int nb, char *sb, int mod)
 {
@@ -740,7 +740,7 @@ do_comp_vars(int test, int na, char *sa, int nb, char *sb, int mod)
 		char *p, sav;
 
 		if (!(l = strlen(compprefix)))
-		    return 0;
+		    return ((na == 1 || na == -1) && pattry(pp, compprefix));
 		if (na < 0) {
 		    p = compprefix + l;
 		    na = -na;
@@ -766,7 +766,7 @@ do_comp_vars(int test, int na, char *sa, int nb, char *sb, int mod)
 		char *p;
 
 		if (!(ol = l = strlen(compsuffix)))
-		    return 0;
+		    return ((na == 1 || na == -1) && pattry(pp, compsuffix));
 		if (na < 0) {
 		    p = compsuffix;
 		    na = -na;
@@ -798,11 +798,11 @@ bin_compset(char *name, char **argv, char *ops, int func)
     char *sa = NULL, *sb = NULL;
 
     if (incompfunc != 1) {
-	zerrnam(name, "can only be called from completion function", NULL, 0);
+	zwarnnam(name, "can only be called from completion function", NULL, 0);
 	return 1;
     }
     if (argv[0][0] != '-') {
-	zerrnam(name, "missing option", NULL, 0);
+	zwarnnam(name, "missing option", NULL, 0);
 	return 1;
     }
     switch (argv[0][1]) {
@@ -814,7 +814,7 @@ bin_compset(char *name, char **argv, char *ops, int func)
     case 'S': test = CVT_SUFPAT; break;
     case 'q': return set_comp_sep();
     default:
-	zerrnam(name, "bad option -%c", NULL, argv[0][1]);
+	zwarnnam(name, "bad option -%c", NULL, argv[0][1]);
 	return 1;
     }
     if (argv[0][2]) {
@@ -823,7 +823,7 @@ bin_compset(char *name, char **argv, char *ops, int func)
 	na = 2;
     } else {
 	if (!(sa = argv[1])) {
-	    zerrnam(name, "missing string for option -%c", NULL, argv[0][1]);
+	    zwarnnam(name, "missing string for option -%c", NULL, argv[0][1]);
 	    return 1;
 	}
 	sb = argv[2];
@@ -831,7 +831,7 @@ bin_compset(char *name, char **argv, char *ops, int func)
     }
     if (((test == CVT_PRENUM || test == CVT_SUFNUM) ? !!sb :
 	 (sb && argv[na]))) {
-	zerrnam(name, "too many arguments", NULL, 0);
+	zwarnnam(name, "too many arguments", NULL, 0);
 	return 1;
     }
     switch (test) {
@@ -841,11 +841,9 @@ bin_compset(char *name, char **argv, char *ops, int func)
 	break;
     case CVT_RANGEPAT:
 	tokenize(sa);
-	sa = rembslash(sa);
 	remnulargs(sa);
 	if (sb) {
 	    tokenize(sb);
-	    sb = rembslash(sb);
 	    remnulargs(sb);
 	}
 	break;
@@ -861,7 +859,6 @@ bin_compset(char *name, char **argv, char *ops, int func)
 	} else
 	    na = -1;
 	tokenize(sa);
-	sa = rembslash(sa);
 	remnulargs(sa);
 	break;
     }
@@ -892,9 +889,6 @@ static struct compparam comprparams[] = {
 
 static struct compparam compkparams[] = {
     { "nmatches", PM_INTEGER | PM_READONLY, NULL, NULL, VAL(get_nmatches) },
-    { "matcher", PM_INTEGER, VAL(compmatcher), NULL, NULL },
-    { "matcher_string", PM_SCALAR, VAL(compmatcherstr), NULL, NULL },
-    { "total_matchers", PM_INTEGER, VAL(compmatchertot), NULL, NULL },
     { "context", PM_SCALAR, VAL(compcontext), NULL, NULL },
     { "parameter", PM_SCALAR, VAL(compparameter), NULL, NULL },
     { "redirect", PM_SCALAR, VAL(compredirect), NULL, NULL },
@@ -902,7 +896,6 @@ static struct compparam compkparams[] = {
     { "quoting", PM_SCALAR | PM_READONLY, VAL(compquoting), NULL, NULL },
     { "restore", PM_SCALAR, VAL(comprestore), NULL, NULL },
     { "list", PM_SCALAR, NULL, VAL(set_complist), VAL(get_complist) },
-    { "force_list", PM_SCALAR, VAL(compforcelist), NULL, NULL },
     { "insert", PM_SCALAR, VAL(compinsert), NULL, NULL },
     { "exact", PM_SCALAR, VAL(compexact), NULL, NULL },
     { "exact_string", PM_SCALAR, VAL(compexactstr), NULL, NULL },
@@ -917,8 +910,9 @@ static struct compparam compkparams[] = {
     { "old_list", PM_SCALAR, VAL(compoldlist), NULL, NULL },
     { "old_insert", PM_SCALAR, VAL(compoldins), NULL, NULL },
     { "vared", PM_SCALAR, VAL(compvared), NULL, NULL },
-    { "alternate_nmatches", PM_INTEGER | PM_READONLY, NULL, NULL, VAL(get_anmatches) },
     { "list_lines", PM_INTEGER | PM_READONLY, NULL, NULL, VAL(get_listlines) },
+    { "all_quotes", PM_SCALAR | PM_READONLY, VAL(compqstack), NULL, NULL },
+    { "ignored", PM_INTEGER | PM_READONLY, VAL(compignored), NULL, NULL },
     { NULL, 0, NULL, NULL, NULL }
 };
 
@@ -976,7 +970,7 @@ makecompparams(void)
 
     comprpms[CPN_COMPSTATE] = cpm;
     tht = paramtab;
-    cpm->level = locallevel;
+    cpm->level = locallevel + 1;
     cpm->gets.hfn = get_compstate;
     cpm->sets.hfn = set_compstate;
     cpm->unsetfn = compunsetfn;
@@ -1029,14 +1023,7 @@ set_compstate(Param pm, HashTable ht)
 static zlong
 get_nmatches(Param pm)
 {
-    return num_matches(1);
-}
-
-/**/
-static zlong
-get_anmatches(Param pm)
-{
-    return num_matches(0);
+    return (permmatches(0) ? 0 : nmatches);
 }
 
 /**/
@@ -1083,14 +1070,37 @@ static void
 compunsetfn(Param pm, int exp)
 {
     if (exp) {
-	if (PM_TYPE(pm->flags) == PM_SCALAR) {
-	    zsfree(*((char **) pm->u.data));
-	    *((char **) pm->u.data) = ztrdup("");
-	} else if (PM_TYPE(pm->flags) == PM_ARRAY) {
-	    freearray(*((char ***) pm->u.data));
-	    *((char ***) pm->u.data) = zcalloc(sizeof(char *));
+	if (pm->u.data) {
+	    if (PM_TYPE(pm->flags) == PM_SCALAR) {
+		zsfree(*((char **) pm->u.data));
+		*((char **) pm->u.data) = ztrdup("");
+	    } else if (PM_TYPE(pm->flags) == PM_ARRAY) {
+		freearray(*((char ***) pm->u.data));
+		*((char ***) pm->u.data) = zcalloc(sizeof(char *));
+	    } else if (PM_TYPE(pm->flags) == PM_HASHED) {
+		deleteparamtable(pm->u.hash);
+		pm->u.hash = NULL;
+	    }
 	}
-	pm->flags |= PM_UNSET;
+    } else if (PM_TYPE(pm->flags) == PM_HASHED) {
+	Param *p;
+	int i;
+
+	deletehashtable(pm->u.hash);
+	pm->u.hash = NULL;
+
+	for (p = compkpms, i = CP_KEYPARAMS; i--; p++)
+	    *p = NULL;
+    }
+    if (!exp) {
+	Param *p;
+	int i;
+
+	for (p = comprpms, i = CP_REALPARAMS; i; p++, i--)
+	    if (*p == pm) {
+		*p = NULL;
+		break;
+	    }
     }
 }
 
@@ -1102,31 +1112,35 @@ comp_setunset(int rset, int runset, int kset, int kunset)
 
     if (comprpms && (rset >= 0 || runset >= 0)) {
 	for (p = comprpms; rset || runset; rset >>= 1, runset >>= 1, p++) {
-	    if (rset & 1)
-		(*p)->flags &= ~PM_UNSET;
-	    if (runset & 1)
-		(*p)->flags |= PM_UNSET;
+	    if (*p) {
+		if (rset & 1)
+		    (*p)->flags &= ~PM_UNSET;
+		if (runset & 1)
+		    (*p)->flags |= PM_UNSET;
+	    }
 	}
     }
-    if (comprpms && (kset >= 0 || kunset >= 0)) {
+    if (compkpms && (kset >= 0 || kunset >= 0)) {
 	for (p = compkpms; kset || kunset; kset >>= 1, kunset >>= 1, p++) {
-	    if (kset & 1)
-		(*p)->flags &= ~PM_UNSET;
-	    if (kunset & 1)
-		(*p)->flags |= PM_UNSET;
+	    if (*p) {
+		if (kset & 1)
+		    (*p)->flags &= ~PM_UNSET;
+		if (kunset & 1)
+		    (*p)->flags |= PM_UNSET;
+	    }
 	}
     }
 }
 
 /**/
 static int
-comp_wrapper(List list, FuncWrap w, char *name)
+comp_wrapper(Eprog prog, FuncWrap w, char *name)
 {
     if (incompfunc != 1)
 	return 1;
     else {
 	char *orest, *opre, *osuf, *oipre, *oisuf, **owords;
-	char *oqipre, *oqisuf, *oq, *oqi;
+	char *oqipre, *oqisuf, *oq, *oqi, *oqs, *oaq;
 	zlong ocur;
 	unsigned int runset = 0, kunset = 0, m, sm;
 	Param *pp;
@@ -1142,52 +1156,65 @@ comp_wrapper(List list, FuncWrap w, char *name)
 	orest = comprestore;
 	comprestore = ztrdup("auto");
 	ocur = compcurrent;
-	opre = dupstring(compprefix);
-	osuf = dupstring(compsuffix);
-	oipre = dupstring(compiprefix);
-	oisuf = dupstring(compisuffix);
-	oqipre = dupstring(compqiprefix);
-	oqisuf = dupstring(compqisuffix);
-	oq = dupstring(compquote);
-	oqi = dupstring(compquoting);
-
-	HEAPALLOC {
-	    owords = arrdup(compwords);
-	} LASTALLOC;
-
-	runshfunc(list, w, name);
+	opre = ztrdup(compprefix);
+	osuf = ztrdup(compsuffix);
+	oipre = ztrdup(compiprefix);
+	oisuf = ztrdup(compisuffix);
+	oqipre = ztrdup(compqiprefix);
+	oqisuf = ztrdup(compqisuffix);
+	oq = ztrdup(compquote);
+	oqi = ztrdup(compquoting);
+	oqs = ztrdup(compqstack);
+	oaq = ztrdup(autoq);
+	owords = zarrdup(compwords);
+
+	runshfunc(prog, w, name);
 
 	if (comprestore && !strcmp(comprestore, "auto")) {
 	    compcurrent = ocur;
 	    zsfree(compprefix);
-	    compprefix = ztrdup(opre);
+	    compprefix = opre;
 	    zsfree(compsuffix);
-	    compsuffix = ztrdup(osuf);
+	    compsuffix = osuf;
 	    zsfree(compiprefix);
-	    compiprefix = ztrdup(oipre);
+	    compiprefix = oipre;
 	    zsfree(compisuffix);
-	    compisuffix = ztrdup(oisuf);
+	    compisuffix = oisuf;
 	    zsfree(compqiprefix);
-	    compqiprefix = ztrdup(oqipre);
+	    compqiprefix = oqipre;
 	    zsfree(compqisuffix);
-	    compqisuffix = ztrdup(oqisuf);
+	    compqisuffix = oqisuf;
 	    zsfree(compquote);
-	    compquote = ztrdup(oq);
+	    compquote = oq;
 	    zsfree(compquoting);
-	    compquoting = ztrdup(oqi);
+	    compquoting = oqi;
+	    zsfree(compqstack);
+	    compqstack = oqs;
+	    zsfree(autoq);
+	    autoq = oaq;
 	    freearray(compwords);
-	    PERMALLOC {
-		compwords = arrdup(owords);
-	    } LASTALLOC;
+	    compwords = owords;
 	    comp_setunset(CP_COMPSTATE |
 			  (~runset & (CP_WORDS | CP_CURRENT | CP_PREFIX |
 				     CP_SUFFIX | CP_IPREFIX | CP_ISUFFIX |
 				     CP_QIPREFIX | CP_QISUFFIX)),
 			  (runset & CP_ALLREALS),
 			  (~kunset & CP_RESTORE), (kunset & CP_ALLKEYS));
-	} else
+	} else {
 	    comp_setunset(CP_COMPSTATE, 0, (~kunset & CP_RESTORE),
 			  (kunset & CP_RESTORE));
+	    zsfree(opre);
+	    zsfree(osuf);
+	    zsfree(oipre);
+	    zsfree(oisuf);
+	    zsfree(oqipre);
+	    zsfree(oqisuf);
+	    zsfree(oq);
+	    zsfree(oqi);
+	    zsfree(oqs);
+	    zsfree(oaq);
+	    freearray(owords);
+	}
 	zsfree(comprestore);
 	comprestore = orest;
 
@@ -1228,42 +1255,6 @@ cond_range(char **a, int id)
 			(id ? cond_str(a, 1, 1) : NULL), 0);
 }
 
-/**/
-static void
-cmsetfn(Param pm, char **v)
-{
-    set_gmatcher(pm->nam, v);
-}
-
-/**/
-static char **
-cmgetfn(Param pm)
-{
-    int num;
-    Cmlist p;
-    char **ret, **q;
-
-    for (num = 0, p = cmatcher; p; p = p->next, num++);
-
-    ret = (char **) zhalloc((num + 1) * sizeof(char *));
-
-    for (q = ret, p = cmatcher; p; p = p->next, q++)
-	*q = dupstring(p->str);
-    *q = NULL;
-
-    return ret;
-}
-
-/**/
-static void
-cmunsetfn(Param pm, int exp)
-{
-    char *dummy[1];
-
-    dummy[0] = NULL;
-    set_gmatcher(pm->nam, dummy);
-}
-
 static struct builtin bintab[] = {
     BUILTIN("compadd", 0, bin_compadd, 0, -1, 0, NULL, NULL),
     BUILTIN("compset", 0, bin_compset, 1, 3, 0, NULL, NULL),
@@ -1280,53 +1271,108 @@ static struct funcwrap wrapper[] = {
     WRAPDEF(comp_wrapper),
 };
 
-static struct paramdef patab[] = {
-    PARAMDEF("compmatchers", PM_ARRAY|PM_SPECIAL, NULL, cmsetfn, cmgetfn, cmunsetfn)
+/* The order of the entries in this table has to match the *HOOK
+ * macros in comp.h */
+
+/**/
+struct hookdef comphooks[] = {
+    HOOKDEF("insert_match", NULL, HOOKF_ALL),
+    HOOKDEF("menu_start", NULL, HOOKF_ALL),
+    HOOKDEF("compctl_make", NULL, 0),
+    HOOKDEF("compctl_cleanup", NULL, 0),
+    HOOKDEF("comp_list_matches", ilistmatches, 0),
 };
 
 /**/
 int
-setup_complete(Module m)
+setup_(Module m)
 {
-    makecompparamsptr = makecompparams;
-    comp_setunsetptr = comp_setunset;
+    hasperm = 0;
+
+    comprpms = compkpms = NULL;
+    compwords = NULL;
+    compprefix = compsuffix = compiprefix = compisuffix = 
+	compqiprefix = compqisuffix =
+	compcontext = compparameter = compredirect = compquote =
+	compquoting = comprestore = complist = compinsert =
+	compexact = compexactstr = comppatmatch = comppatinsert =
+	complastprompt = comptoend = compoldlist = compoldins =
+	compvared = compqstack = NULL;
+
+    hascompmod = 1;
 
     return 0;
 }
 
 /**/
 int
-boot_complete(Module m)
+boot_(Module m)
 {
+    addhookfunc("complete", (Hookfn) do_completion);
+    addhookfunc("before_complete", (Hookfn) before_complete);
+    addhookfunc("after_complete", (Hookfn) after_complete);
+    addhookfunc("accept_completion", (Hookfn) accept_last);
+    addhookfunc("reverse_menu", (Hookfn) reverse_menu);
+    addhookfunc("list_matches", (Hookfn) list_matches);
+    addhookfunc("invalidate_list", (Hookfn) invalidate_list);
+    addhookdefs(m->nam, comphooks, sizeof(comphooks)/sizeof(*comphooks));
     if (!(addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)) |
 	  addconddefs(m->nam, cotab, sizeof(cotab)/sizeof(*cotab)) |
-	  addparamdefs(m->nam, patab, sizeof(patab)/sizeof(*patab)) |
 	  !addwrapper(m, wrapper)))
 	return 1;
     return 0;
 }
 
-#ifdef MODULE
-
 /**/
 int
-cleanup_complete(Module m)
+cleanup_(Module m)
 {
+    deletehookfunc("complete", (Hookfn) do_completion);
+    deletehookfunc("before_complete", (Hookfn) before_complete);
+    deletehookfunc("after_complete", (Hookfn) after_complete);
+    deletehookfunc("accept_completion", (Hookfn) accept_last);
+    deletehookfunc("reverse_menu", (Hookfn) reverse_menu);
+    deletehookfunc("list_matches", (Hookfn) list_matches);
+    deletehookfunc("invalidate_list", (Hookfn) invalidate_list);
+    deletehookdefs(m->nam, comphooks, sizeof(comphooks)/sizeof(*comphooks));
     deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
     deleteconddefs(m->nam, cotab, sizeof(cotab)/sizeof(*cotab));
-    deleteparamdefs(m->nam, patab, sizeof(patab)/sizeof(*patab));
     deletewrapper(m, wrapper);
     return 0;
 }
 
 /**/
 int
-finish_complete(Module m)
+finish_(Module m)
 {
-    makecompparamsptr = NULL;
-    comp_setunsetptr = NULL;
+    if (compwords)
+	freearray(compwords);
+    zsfree(compprefix);
+    zsfree(compsuffix);
+    zsfree(compiprefix);
+    zsfree(compisuffix);
+    zsfree(compqiprefix);
+    zsfree(compqisuffix);
+    zsfree(compcontext);
+    zsfree(compparameter);
+    zsfree(compredirect);
+    zsfree(compquote);
+    zsfree(compqstack);
+    zsfree(compquoting);
+    zsfree(comprestore);
+    zsfree(complist);
+    zsfree(compinsert);
+    zsfree(compexact);
+    zsfree(compexactstr);
+    zsfree(comppatmatch);
+    zsfree(comppatinsert);
+    zsfree(complastprompt);
+    zsfree(comptoend);
+    zsfree(compoldlist);
+    zsfree(compoldins);
+    zsfree(compvared);
+
+    hascompmod = 0;
 
     return 0;
 }
-
-#endif
diff --git a/Src/Zle/compmatch.c b/Src/Zle/compmatch.c
index 32e0c3a68..3dda28e11 100644
--- a/Src/Zle/compmatch.c
+++ b/Src/Zle/compmatch.c
@@ -28,15 +28,8 @@
  */
 
 #include "complete.mdh"
-#define GLOBAL_PROTOTYPES
-#include "zle_tricky.pro"
-#undef GLOBAL_PROTOTYPES
 #include "compmatch.pro"
 
-/* Convenience macro for calling bslashquote() (formerly quotename()). */
-
-#define quotename(s, e) bslashquote(s, e, instring)
-
 /* This compares two cpattern lists and returns non-zero if they are
  * equal. */
 
@@ -75,14 +68,14 @@ cmp_cmatchers(Cmatcher a, Cmatcher b)
 /* Add the given matchers to the bmatcher list. */
 
 /**/
-void
+mod_export void
 add_bmatchers(Cmatcher m)
 {
     Cmlist old = bmatchers, *q = &bmatchers, n;
 
     for (; m; m = m->next) {
 	if ((!m->flags && m->wlen > 0 && m->llen > 0) ||
-	    (m->flags == CMF_RIGHT && m->wlen == -1 && !m->llen)) {
+	    (m->flags == CMF_RIGHT && m->wlen < 0 && !m->llen)) {
 	    *q = n = (Cmlist) zhalloc(sizeof(struct cmlist));
 	    n->matcher = m;
 	    q = &(n->next);
@@ -95,7 +88,7 @@ add_bmatchers(Cmatcher m)
  * ensure that the bmatchers list contains no matchers not in mstack. */
 
 /**/
-void
+mod_export void
 update_bmatchers(void)
 {
     Cmlist p = bmatchers, q = NULL, ms;
@@ -141,6 +134,7 @@ get_cline(char *l, int ll, char *w, int wl, char *o, int ol, int fl)
     r->slen = 0;
     r->flags = fl;
     r->prefix = r->suffix = NULL;
+    r->min = r->max = 0;
     return r;
 }
 
@@ -443,7 +437,7 @@ match_str(char *l, char *w, Brinfo *bpp, int bc, int *rwlp,
 	  int sfx, int test, int part)
 {
     int ll = strlen(l), lw = strlen(w), oll = ll, olw = lw;
-    int il = 0, iw = 0, t, ind, add, he = 0, bpc, obc = bc;
+    int il = 0, iw = 0, t, ind, add, he = 0, bpc, obc = bc, bslash;
     VARARR(unsigned char, ea, ll + 1);
     char *ow;
     Cmlist ms;
@@ -553,7 +547,7 @@ match_str(char *l, char *w, Brinfo *bpp, int bc, int *rwlp,
 			    } else
 				t = match_str(l + llen + moff, tp + moff,
 					      NULL, 0, NULL, 0, 1, part);
-			    if (t || !both)
+			    if (t || (mp->wlen == -1 && !both))
 				break;
 			}
 		    }
@@ -743,12 +737,15 @@ match_str(char *l, char *w, Brinfo *bpp, int bc, int *rwlp,
 	if (mp)
 	    continue;
 
-	if (l[ind] == w[ind]) {
+	bslash = 0;
+	if (l[ind] == w[ind] ||
+	    (bslash = (lw > 1 && w[ind] == '\\' &&
+		       (ind ? (w[0] == l[0]) : (w[1] == l[0]))))) {
 	    /* No matcher could be used, but the strings have the same
 	     * character here, skip over it. */
-	    l += add; w += add;
-	    il++; iw++;
-	    ll--; lw--;
+	    l += add; w += (bslash ? (add + add ) : add);
+	    il++; iw += 1 + bslash;
+	    ll--; lw -= 1 + bslash;
 	    bc++;
 	    if (!test)
 		while (bp && bc >= (useqbr ? bp->qpos : bp->pos)) {
@@ -839,7 +836,7 @@ match_parts(char *l, char *w, int n, int part)
  * and the suffix don't match the word w. */
 
 /**/
-char *
+mod_export char *
 comp_match(char *pfx, char *sfx, char *w, Patprog cp, Cline *clp, int qu,
 	   Brinfo *bpl, int bcp, Brinfo *bsl, int bcs, int *exact)
 {
@@ -853,9 +850,8 @@ comp_match(char *pfx, char *sfx, char *w, Patprog cp, Cline *clp, int qu,
 	if (!pattry(cp, r))
 	    return NULL;
     
-	r = (qu ? quotename(r, NULL) : dupstring(r));
-	if (qu == 2 && r[0] == '\\' && r[1] == '~')
-	    chuck(r);
+	r = (qu == 2 ? tildequote(r, 0) : multiquote(r, !qu));
+
 	/* We still break it into parts here, trying to build a sensible
 	 * cline list for these matches, too. */
 	w = dupstring(w);
@@ -866,10 +862,7 @@ comp_match(char *pfx, char *sfx, char *w, Patprog cp, Cline *clp, int qu,
 	Cline pli, plil;
 	int mpl, rpl, wl;
 
-	w = (qu ? quotename(w, NULL) : dupstring(w));
-	if (qu == 2 && w[0] == '\\' && w[1] == '~')
-	    chuck(w);
-
+	w = (qu == 2 ? tildequote(w, 0) : multiquote(w, !qu));
 	wl = strlen(w);
 
 	/* Always try to match the prefix. */
@@ -1016,7 +1009,7 @@ bld_parts(char *str, int len, int plen, Cline *lp)
     while (len) {
 	for (t = 0, ms = bmatchers; ms && !t; ms = ms->next) {
 	    mp = ms->matcher;
-	    if (mp->flags == CMF_RIGHT && mp->wlen == -1 &&
+	    if (mp && mp->flags == CMF_RIGHT && mp->wlen < 0 &&
 		!mp->llen && len >= mp->ralen && mp->ralen &&
 		pattern_match(mp->right, str, NULL, NULL)) {
 		int olen = str - p, llen;
@@ -1136,7 +1129,7 @@ bld_line(Cpattern pat, char *line, char *lp,
 		t = 0;
 		for (ms = bmatchers; ms && !t; ms = ms->next) {
 		    mp = ms->matcher;
-		    if (!mp->flags && mp->wlen <= wlen && mp->llen <= l &&
+		    if (mp && !mp->flags && mp->wlen <= wlen && mp->llen <= l &&
 			pattern_match(mp->line, (sfx ? line - mp->llen : line),
 				      NULL, ea) &&
 			pattern_match(mp->word, (sfx ? word - mp->wlen : word),
@@ -1186,7 +1179,7 @@ join_strs(int la, char *sa, int lb, char *sb)
 	    /* Different characters, try the matchers. */
 	    for (t = 0, ms = bmatchers; ms && !t; ms = ms->next) {
 		mp = ms->matcher;
-		if (!mp->flags && mp->wlen > 0 && mp->llen > 0 &&
+		if (mp && !mp->flags && mp->wlen > 0 && mp->llen > 0 &&
 		    mp->wlen <= la && mp->wlen <= lb) {
 		    /* The pattern has no anchors and the word
 		     * pattern fits, try it. */
@@ -1373,7 +1366,7 @@ join_sub(Cmdata md, char *str, int len, int *mlen, int sfx, int join)
 	    /* We use only those patterns that match a non-empty
 	     * string in both the line and the word and that have
 	     * no anchors. */
-	    if (!mp->flags && mp->wlen > 0 && mp->llen > 0) {
+	    if (mp && !mp->flags && mp->wlen > 0 && mp->llen > 0) {
 		/* We first test, if the old string matches already the
 		 * new one. */
 		if (mp->llen <= ol && mp->wlen <= nl &&
@@ -1684,7 +1677,7 @@ join_mid(Cline o, Cline n)
  * didn't. */
 
 /**/
-static void
+static int
 sub_join(Cline a, Cline b, Cline e, int anew)
 {
     if (!e->suffix && a->prefix) {
@@ -1707,31 +1700,28 @@ sub_join(Cline a, Cline b, Cline e, int anew)
 	*p = e->prefix;
 	ca = a->prefix;
 
-	while (n != op) {
+	while (n) {
 	    e->prefix = cp_cline(n, 0);
 	    a->prefix = cp_cline(ca, 0);
 
 	    if (anew) {
 		join_psfx(e, a, NULL, NULL, 0);
-		if (e->prefix) {
-		    e->min += min;
-		    e->max += max;
-		    break;
-		}
+		if (e->prefix)
+		    return max - min;
 	    } else {
-		join_psfx(e, a, NULL, NULL, 0);
-		if (a->prefix) {
-		    a->min += min;
-		    a->max += max;
-		    break;
-		}
+		join_psfx(a, e, NULL, NULL, 0);
+		if (a->prefix)
+		    return max - min;
 	    }
 	    min -= n->min;
-	    max -= n->max;
 
+	    if (n == op)
+		break;
 	    n = n->next;
 	}
+	return max - min;
     }
+    return 0;
 }
 
 /* This simplifies the cline list given as the first argument so that
@@ -1748,7 +1738,8 @@ join_clines(Cline o, Cline n)
     if (!o)
 	return n;
     else {
-	Cline oo = o, nn = n, po = NULL, pn = NULL;
+	Cline oo = o, nn = n, po = NULL, pn = NULL, x;
+	int diff;
 
 	/* Walk through the lists. */
 	while (o && n) {
@@ -1760,7 +1751,7 @@ join_clines(Cline o, Cline n)
 
 		for (t = o; (tn = t->next) && (tn->flags & CLF_NEW); t = tn);
 		if (tn && cmp_anchors(tn, n, 0)) {
-		    sub_join(n, o, tn, 1);
+		    diff = sub_join(n, o, tn, 1);
 
 		    if (po)
 			po->next = tn;
@@ -1768,8 +1759,15 @@ join_clines(Cline o, Cline n)
 			oo = tn;
 		    t->next = NULL;
 		    free_cline(o);
+		    x = o;
 		    o = tn;
-		    o->flags |= CLF_MISS;
+		    if (po && cmp_anchors(x, po, 0)) {
+			po->flags |= CLF_MISS;
+			po->max += diff;
+		    } else {
+			o->flags |= CLF_MISS;
+			o->max += diff;
+		    }
 		    continue;
 		}
 	    }
@@ -1778,10 +1776,16 @@ join_clines(Cline o, Cline n)
 
 		for (t = n; (tn = t->next) && (tn->flags & CLF_NEW); t = tn);
 		if (tn && cmp_anchors(o, tn, 0)) {
-		    sub_join(o, n, tn, 0);
+		    diff = sub_join(o, n, tn, 0);
 
+		    if (po && cmp_anchors(n, pn, 0)) {
+			po->flags |= CLF_MISS;
+			po->max += diff;
+		    } else {
+			o->flags |= CLF_MISS;
+			o->max += diff;
+		    }
 		    n = tn;
-		    o->flags |= CLF_MISS;
 		    continue;
 		}
 	    }
@@ -1809,6 +1813,7 @@ join_clines(Cline o, Cline n)
 		     t = tn);
 		if (tn && cmp_anchors(tn, n, 1)) {
 		    sub_join(n, o, tn, 1);
+
 		    if (po)
 			po->next = tn;
 		    else
@@ -1837,24 +1842,41 @@ join_clines(Cline o, Cline n)
 		for (t = n; (tn = t->next) && !cmp_anchors(o, tn, 1); t = tn);
 
 		if (tn) {
-		    sub_join(o, n, tn, 0);
+		    diff = sub_join(o, n, tn, 0);
 
+		    if (po && cmp_anchors(n, pn, 0)) {
+			po->flags |= CLF_MISS;
+			po->max += diff;
+		    } else {
+			o->flags |= CLF_MISS;
+			o->max += diff;
+		    }
 		    n = tn;
-		    o->flags |= CLF_MISS;
+		    po = o;
+		    o = o->next;
+		    pn = n;
+		    n = n->next;
 		    continue;
 		} else {
 		    for (t = o; (tn = t->next) && !cmp_anchors(n, tn, 1);
 			 t = tn);
 
 		    if (tn) {
-			sub_join(n, o, tn, 1);
+			diff = sub_join(n, o, tn, 1);
 
 			if (po)
 			    po->next = tn;
 			else
 			    oo = tn;
+			x = o;
 			o = tn;
-			o->flags |= CLF_MISS;
+			if (po && cmp_anchors(x, po, 0)) {
+			    po->flags |= CLF_MISS;
+			    po->max += diff;
+			} else {
+			    o->flags |= CLF_MISS;
+			    o->max += diff;
+			}
 			continue;
 		    } else {
 			if (o->flags & CLF_SUF)
diff --git a/Src/Zle/compresult.c b/Src/Zle/compresult.c
index fe997b12b..0d93b8727 100644
--- a/Src/Zle/compresult.c
+++ b/Src/Zle/compresult.c
@@ -28,16 +28,8 @@
  */
 
 #include "complete.mdh"
-#define GLOBAL_PROTOTYPES
-#include "zle_tricky.pro"
-#undef GLOBAL_PROTOTYPES
 #include "compresult.pro"
 
-/* Convenience macro for calling bslashquote() (formerly quotename()). *
- * This uses the instring variable above.                              */
-
-#define quotename(s, e) bslashquote(s, e, instring)
-
 #define inststr(X) inststrlen((X),1,-1)
 
 /* This cuts the cline list before the stuff that isn't worth
@@ -72,7 +64,8 @@ cut_cline(Cline l)
 	    q = p;
     }
     if (!e && q && !q->orig && !q->olen && (q->flags & CLF_MISS) &&
-	!(q->flags & CLF_MATCHED) && (q->word ? q->wlen : q->llen) < 3) {
+	(!(q->flags & CLF_MATCHED) || (!q->prefix && !q->suffix)) &&
+	(q->word ? q->wlen : q->llen) < 3) {
 	q->word = q->line = NULL;
 	q->wlen = q->llen = 0;
     }
@@ -143,8 +136,9 @@ cline_str(Cline l, int ins, int *csp)
 
     l = cut_cline(l);
 
-    pmm = smm = dm = 0;
+    pmm = smm = dm = pcs = scs = 0;
     pm = pmax = sm = smax = d = mid = cbr = -1;
+    brp = brs = NULL;
 
     /* Get the information about the brace beginning and end we have
      * to re-insert. */
@@ -594,7 +588,7 @@ do_ambiguous(void)
     /* If we have to insert the first match, call do_single().  This is *
      * how REC_EXACT takes effect.  We effectively turn the ambiguous   *
      * completion into an unambiguous one.                              */
-    if (ainfo && ainfo->exact == 1 && useexact && !(fromcomp & FC_LINE)) {
+    if (ainfo && ainfo->exact == 1 && !(fromcomp & FC_LINE)) {
 	minfo.cur = NULL;
 	do_single(ainfo->exactm);
 	invalidatelist();
@@ -616,6 +610,7 @@ do_ambiguous(void)
 	do_ambig_menu();
     } else if (ainfo) {
 	int atend = (cs == we), la, eq, tcs;
+	VARARR(char, old, we - wb);
 
 	minfo.cur = NULL;
 	minfo.asked = 0;
@@ -623,11 +618,24 @@ do_ambiguous(void)
 	fixsuffix();
 
 	/* First remove the old string from the line. */
+	tcs = cs;
 	cs = wb;
+	memcpy(old, (char *) line + wb, we - wb);
 	foredel(we - wb);
 
 	/* Now get the unambiguous string and insert it into the line. */
 	cline_str(ainfo->line, 1, NULL);
+
+	/* Sometimes the different match specs used may result in a cline
+	 * that gives an empty string. If that happened, we re-insert the
+         * old string. Unless there were matches added with -U, that is. */
+	if (!(lastend - wb) && !hasunmatched) {
+	    cs = wb;
+	    foredel(lastend - wb);
+	    inststrlen(old, 0, we - wb);
+	    lastend = we;
+	    cs = tcs;
+	}
 	if (eparq) {
 	    tcs = cs;
 	    cs = lastend;
@@ -677,7 +685,7 @@ do_ambiguous(void)
     if (uselist && (usemenu != 2 || (!listshown && !oldlist)) &&
 	((!showinglist && (!listshown || !oldlist)) ||
 	 (usemenu == 3 && !oldlist)) &&
-	(smatches >= 2 || (compforcelist && *compforcelist)))
+	(smatches >= 2 || forcelist))
 	showinglist = -2;
 
     return ret;
@@ -690,7 +698,7 @@ do_ambiguous(void)
  * (l)stat().                                                         */
 
 /**/
-int
+mod_export int
 ztat(char *nam, struct stat *buf, int ls)
 {
     char b[PATH_MAX], *p;
@@ -708,7 +716,7 @@ ztat(char *nam, struct stat *buf, int ls)
 /* Insert a single match in the command line. */
 
 /**/
-void
+mod_export void
 do_single(Cmatch m)
 {
     int l, sr = 0, scs;
@@ -726,7 +734,8 @@ do_single(Cmatch m)
 	/* We are currently not in a menu-completion, *
 	 * so set the position variables.             */
 	minfo.pos = wb;
-	minfo.we = (movetoend >= 2 || (movetoend == 1 && !menucmp));
+	minfo.we = (movetoend >= 2 || (movetoend == 1 && !menucmp) ||
+		    (!movetoend && cs == we));
 	minfo.end = we;
     }
     /* If we are already in a menu-completion or if we have done a *
@@ -792,19 +801,40 @@ do_single(Cmatch m)
 	    else {
 		/* Build the path name. */
 		if (partest && !*psuf && !(m->flags & CMF_PARNEST)) {
-		    int ne = noerrs;
+		    int ne = noerrs, tryit = 1;
 
 		    p = (char *) zhalloc(strlen((m->flags & CMF_ISPAR) ?
 						parpre : m->ripre) +
 					 strlen(str) + 2);
 		    sprintf(p, "%s%s%c",
 			    ((m->flags & CMF_ISPAR) ? parpre : m->ripre), str,
-			    ((m->flags & CMF_PARBR) ? Outbrace : '\0'));
-		    noerrs = 1;
-		    parsestr(p);
-		    singsub(&p);
-		    errflag = 0;
-		    noerrs = ne;
+			    ((m->flags & CMF_PARBR) ? '}' : '\0'));
+		    if (*p == '$') {
+			char *n;
+			Param pm;
+
+			if (p[1] == '{') {
+			    char *e;
+
+			    n = dupstring(p + 2);
+			    e = n + strlen(n) - 1;
+
+			    if (*e == '}')
+				*e = '\0';
+			} else
+			    n = p + 1;
+
+			if ((pm = (Param) paramtab->getnode(paramtab, n)) &&
+			    PM_TYPE(pm->flags) != PM_SCALAR)
+			    tryit = 0;
+		    }
+		    if (tryit) {
+			noerrs = 1;
+			parsestr(p);
+			singsub(&p);
+			errflag = 0;
+			noerrs = ne;
+		    }
 		} else {
 		    p = (char *) zhalloc(strlen(prpre) + strlen(str) +
 				 strlen(psuf) + 3);
@@ -857,11 +887,13 @@ do_single(Cmatch m)
 	/* If we didn't add a suffix, add a space, unless we are *
 	 * doing menu completion or we are completing files and  *
 	 * the string doesn't name an existing file.             */
-	if (m->autoq && (!m->isuf || m->isuf[0] != m->autoq)) {
-	    inststrlen(&(m->autoq), 1, 1);
-	    minfo.insc++;
+	if (m->autoq && (!m->isuf || !strpfx(m->autoq, m->isuf))) {
+	    int al = strlen(m->autoq);
+	    inststrlen(m->autoq, 1, al);
+	    minfo.insc += al;
 	}
-	if (!menucmp && (usemenu != 3 || insspace)) {
+	if (!menucmp && !(m->flags & CMF_NOSPACE) &&
+	    (usemenu != 3 || insspace)) {
 	    inststrlen(" ", 1, 1);
 	    minfo.insc++;
 	    if (minfo.we)
@@ -897,7 +929,7 @@ do_single(Cmatch m)
  * insert the next completion.                                              */
 
 /**/
-void
+mod_export void
 do_menucmp(int lst)
 {
     /* Just list the matches if the list was requested. */
@@ -906,44 +938,40 @@ do_menucmp(int lst)
 	return;
     }
     /* Otherwise go to the next match in the array... */
-    HEAPALLOC {
-	do {
-	    if (!*++(minfo.cur)) {
-		do {
-		    if (!(minfo.group = (minfo.group)->next))
-			minfo.group = amatches;
-		} while (!(minfo.group)->mcount);
-		minfo.cur = minfo.group->matches;
-	    }
-	} while (menuacc &&
-		 !hasbrpsfx(*(minfo.cur), minfo.prebr, minfo.postbr));
-	/* ... and insert it into the command line. */
-	metafy_line();
-	do_single(*(minfo.cur));
-	unmetafy_line();
-    } LASTALLOC;
+    do {
+	if (!*++(minfo.cur)) {
+	    do {
+		if (!(minfo.group = (minfo.group)->next))
+		    minfo.group = amatches;
+	    } while (!(minfo.group)->mcount);
+	    minfo.cur = minfo.group->matches;
+	}
+    } while (menuacc &&
+	     !hasbrpsfx(*(minfo.cur), minfo.prebr, minfo.postbr));
+    /* ... and insert it into the command line. */
+    metafy_line();
+    do_single(*(minfo.cur));
+    unmetafy_line();
 }
 
 /**/
 int
 reverse_menu(Hookdef dummy, void *dummy2)
 {
-    HEAPALLOC {
-	do {
-	    if (minfo.cur == (minfo.group)->matches) {
-		do {
-		    if (!(minfo.group = (minfo.group)->prev))
-			minfo.group = lmatches;
-		} while (!(minfo.group)->mcount);
-		minfo.cur = (minfo.group)->matches + (minfo.group)->mcount - 1;
-	    } else
-		minfo.cur--;
-	} while (menuacc &&
-		 !hasbrpsfx(*(minfo.cur), minfo.prebr, minfo.postbr));
-	metafy_line();
-	do_single(*(minfo.cur));
-	unmetafy_line();
-    } LASTALLOC;
+    do {
+	if (minfo.cur == (minfo.group)->matches) {
+	    do {
+		if (!(minfo.group = (minfo.group)->prev))
+		    minfo.group = lmatches;
+	    } while (!(minfo.group)->mcount);
+	    minfo.cur = (minfo.group)->matches + (minfo.group)->mcount - 1;
+	} else
+	    minfo.cur--;
+    } while (menuacc &&
+	     !hasbrpsfx(*(minfo.cur), minfo.prebr, minfo.postbr));
+    metafy_line();
+    do_single(*(minfo.cur));
+    unmetafy_line();
 
     return 0;
 }
@@ -953,7 +981,7 @@ reverse_menu(Hookdef dummy, void *dummy2)
  * accept several selections from the list of matches.  */
 
 /**/
-int
+mod_export int
 accept_last(void)
 {
     if (!menuacc) {
@@ -1042,7 +1070,7 @@ do_ambig_menu(void)
     } else {
 	if (oldlist) {
 	    if (oldins && minfo.cur)
-		acceptlast();
+		accept_last();
 	} else
 	    minfo.cur = NULL;
     }
@@ -1074,24 +1102,6 @@ do_ambig_menu(void)
     minfo.cur = mc;
 }
 
-/* Return the real number of matches. */
-
-/**/
-zlong
-num_matches(int normal)
-{
-    int alt;
-
-    PERMALLOC {
-	alt = permmatches(0);
-    } LASTALLOC;
-
-    if (normal)
-	return (alt ? 0 : nmatches);
-    else
-	return (alt ? nmatches : 0);
-}
-
 /* Return the number of screen lines needed for the list. */
 
 /**/
@@ -1100,14 +1110,12 @@ list_lines(void)
 {
     Cmgroup oam;
 
-    PERMALLOC {
-	permmatches(0);
-    } LASTALLOC;
+    permmatches(0);
 
     oam = amatches;
     amatches = pmatches;
     listdat.valid = 0;
-    calclist();
+    calclist(0);
     listdat.valid = 0;
     amatches = oam;
 
@@ -1124,132 +1132,25 @@ comp_list(char *v)
     onlyexpl = (v && strstr(v, "expl"));
 }
 
-/* This is used to print the explanation string. *
- * It returns the number of lines printed.       */
-
-/**/
-int
-printfmt(char *fmt, int n, int dopr, int doesc)
-{
-    char *p = fmt, nc[DIGBUFSIZE];
-    int l = 0, cc = 0, b = 0, s = 0, u = 0, m;
-
-    for (; *p; p++) {
-	/* Handle the `%' stuff (%% == %, %n == <number of matches>). */
-	if (doesc && *p == '%') {
-	    if (*++p) {
-		m = 0;
-		switch (*p) {
-		case '%':
-		    if (dopr)
-			putc('%', shout);
-		    cc++;
-		    break;
-		case 'n':
-		    sprintf(nc, "%d", n);
-		    if (dopr)
-			fprintf(shout, nc);
-		    cc += strlen(nc);
-		    break;
-		case 'B':
-		    b = 1;
-		    if (dopr)
-			tcout(TCBOLDFACEBEG);
-		    break;
-		case 'b':
-		    b = 0; m = 1;
-		    if (dopr)
-			tcout(TCALLATTRSOFF);
-		    break;
-		case 'S':
-		    s = 1;
-		    if (dopr)
-			tcout(TCSTANDOUTBEG);
-		    break;
-		case 's':
-		    s = 0; m = 1;
-		    if (dopr)
-			tcout(TCSTANDOUTEND);
-		    break;
-		case 'U':
-		    u = 1;
-		    if (dopr)
-			tcout(TCUNDERLINEBEG);
-		    break;
-		case 'u':
-		    u = 0; m = 1;
-		    if (dopr)
-			tcout(TCUNDERLINEEND);
-		    break;
-		case '{':
-		    for (p++; *p && (*p != '%' || p[1] != '}'); p++, cc++)
-			if (dopr)
-			    putc(*p, shout);
-		    if (*p)
-			p++;
-		    else
-			p--;
-		    break;
-		}
-		if (dopr && m) {
-		    if (b)
-			tcout(TCBOLDFACEBEG);
-		    if (s)
-			tcout(TCSTANDOUTBEG);
-		    if (u)
-			tcout(TCUNDERLINEBEG);
-		}
-	    } else
-		break;
-	} else {
-	    cc++;
-	    if (*p == '\n') {
-		if (dopr) {
-		    if (tccan(TCCLEAREOL))
-			tcout(TCCLEAREOL);
-		    else {
-			int s = columns - 1 - (cc % columns);
-
-			while (s-- > 0)
-			    putc(' ', shout);
-		    }
-		}
-		l += 1 + (cc / columns);
-		cc = 0;
-	    }
-	    if (dopr)
-		putc(*p, shout);
-	}
-    }
-    if (dopr) {
-	if (tccan(TCCLEAREOL))
-	    tcout(TCCLEAREOL);
-	else {
-	    int s = columns - 1 - (cc % columns);
-
-	    while (s-- > 0)
-		putc(' ', shout);
-	}
-    }
-    return l + (cc / columns);
-}
-
 /* This skips over matches that are not to be listed. */
 
 /**/
 Cmatch *
-skipnolist(Cmatch *p)
+skipnolist(Cmatch *p, int showall)
 {
-    while (*p && (((*p)->flags & (CMF_NOLIST | CMF_HIDE)) ||
-		  ((*p)->disp && ((*p)->flags & (CMF_DISPLINE | CMF_HIDE)))))
+    int mask = (showall ? 0 : (CMF_NOLIST | CMF_MULT)) | CMF_HIDE;
+
+    while (*p && (((*p)->flags & mask) ||
+		  ((*p)->disp &&
+		   ((*p)->flags & (CMF_DISPLINE | CMF_HIDE)))))
 	p++;
 
     return p;
 }
 
 /**/
-void
-calclist(void)
+mod_export void
+calclist(int showall)
 {
     Cmgroup g;
     Cmatch *p, m;
@@ -1259,7 +1160,7 @@ calclist(void)
     VARARR(int, mlens, nmatches + 1);
 
     if (listdat.valid && onlyexpl == listdat.onlyexpl &&
-	menuacc == listdat.menuacc &&
+	menuacc == listdat.menuacc && showall == listdat.showall &&
 	lines == listdat.lines && columns == listdat.columns)
 	return;
 
@@ -1267,6 +1168,8 @@ calclist(void)
 	char **pp = g->ylist;
 	int nl = 0, l, glong = 1, gshort = columns, ndisp = 0, totl = 0;
 
+	g->flags |= CGF_PACKED | CGF_ROWS;
+
 	if (!onlyexpl && pp) {
 	    /* We have an ylist, lets see, if it contains newlines. */
 	    hidden = 1;
@@ -1283,8 +1186,8 @@ calclist(void)
 		while ((sptr = *pp)) {
 		    while (sptr && *sptr) {
 			nlines += (nlptr = strchr(sptr, '\n'))
-			    ? 1 + (nlptr-sptr)/columns
-			    : strlen(sptr)/columns;
+			    ? 1 + (nlptr-sptr) / columns
+			    : strlen(sptr) / columns;
 			sptr = nlptr ? nlptr+1 : NULL;
 		    }
 		    nlines++;
@@ -1327,7 +1230,11 @@ calclist(void)
 			mlens[m->gnum] = l;
 		    }
 		    nlist++;
-		} else if (!(m->flags & CMF_NOLIST)) {
+		    if (!(m->flags & CMF_PACKED))
+			g->flags &= ~CGF_PACKED;
+		    if (!(m->flags & CMF_ROWS))
+			g->flags &= ~CGF_ROWS;
+		} else if (showall || !(m->flags & (CMF_NOLIST | CMF_MULT))) {
 		    l = niceztrlen(m->str);
 		    ndisp++;
 		    if (l > glong)
@@ -1337,6 +1244,10 @@ calclist(void)
 		    totl += l;
 		    mlens[m->gnum] = l;
 		    nlist++;
+		    if (!(m->flags & CMF_PACKED))
+			g->flags &= ~CGF_PACKED;
+		    if (!(m->flags & CMF_ROWS))
+			g->flags &= ~CGF_ROWS;
 		} else
 		    hidden = 1;
 	    }
@@ -1361,9 +1272,11 @@ calclist(void)
 	}
     }
     if (!onlyexpl) {
+	char **pp;
+	int *ws, tlines, tline, tcols, maxlen, nth, width, glines;
+
 	for (g = amatches; g; g = g->next) {
-	    char **pp;
-	    int glines = 0;
+	    glines = 0;
 
 	    zfree(g->widths, 0);
 	    g->widths = NULL;
@@ -1396,20 +1309,19 @@ calclist(void)
 			    if (m->disp) {
 				if (!(m->flags & CMF_DISPLINE))
 				    glines += 1 + (mlens[m->gnum] / columns);
-			    } else if (!(m->flags & CMF_NOLIST))
-				glines += 1 + ((1 + mlens[m->gnum]) / columns);
+			    } else if (showall ||
+				       !(m->flags & (CMF_NOLIST | CMF_MULT)))
+				glines += 1 + ((mlens[m->gnum]) / columns);
 			}
 		}
 	    }
 	    g->lins = glines;
 	    nlines += glines;
 	}
-    }
-    if (!onlyexpl && isset(LISTPACKED)) {
-	char **pp;
-	int *ws, tlines, tline, tcols, maxlen, nth, width;
-
 	for (g = amatches; g; g = g->next) {
+	    if (!(g->flags & CGF_PACKED))
+		continue;
+
 	    ws = g->widths = (int *) zalloc(columns * sizeof(int));
 	    memset(ws, 0, columns * sizeof(int));
 	    tlines = g->lins;
@@ -1424,11 +1336,13 @@ calclist(void)
 		    for (i = 0; *pp; i++, pp++)
 			ylens[i] = strlen(*pp) + add;
 
-		    if (isset(LISTROWSFIRST)) {
+		    if (g->flags & CGF_ROWS) {
 			int count, tcol, first, maxlines = 0, llines;
+			int beg = columns / g->shortest, end = g->cols;
+
+			while (1) {
+			    tcols = (beg + end) >> 1;
 
-			for (tcols = columns / g->shortest; tcols > g->cols;
-			     tcols--) {
 			    for (nth = first = maxlen = width = maxlines =
 				     llines = tcol = 0,
 				     count = g->dcount;
@@ -1452,17 +1366,33 @@ calclist(void)
 				ws[tcol++] = maxlen;
 				width += maxlen;
 			    }
-			    if (!count && width < columns)
+			    if (!count && width < columns &&
+				(tcols <= 0 || beg == end))
 				break;
+
+			    if (beg == end) {
+				beg--;
+				end--;
+			    } else if (width < columns) {
+				if ((end = tcols) == beg - 1)
+				    end++;
+			    } else {
+				if ((beg = tcols) - 1 == end)
+				    end++;
+			    }
 			}
 			if (tcols > g->cols)
 			    tlines = maxlines;
 		    } else {
-			for (tlines = ((g->totl + columns) / columns);
-			     tlines < g->lins; tlines++) {
+			int beg = ((g->totl + columns) / columns);
+			int end = g->lins;
+
+			while (1) {
+			    tlines = (beg + end) >> 1;
+
 			    for (pp = g->ylist, nth = tline = width =
 				     maxlen = tcols = 0;
-				 *pp; nth++, pp++) {
+				 *pp; pp++) {
 				if (ylens[nth] > maxlen)
 				    maxlen = ylens[nth];
 				if (++tline == tlines) {
@@ -1471,24 +1401,41 @@ calclist(void)
 				    ws[tcols++] = maxlen;
 				    maxlen = tline = 0;
 				}
+				nth++;
 			    }
 			    if (tline) {
 				ws[tcols++] = maxlen;
 				width += maxlen;
 			    }
-			    if (nth == yl && width < columns)
+			    if (nth == yl && width < columns &&
+				(beg == end || tlines >= g->lins))
 				break;
+
+			    if (beg == end) {
+				beg++;
+				end++;
+			    } else if (width < columns) {
+				if ((end = tlines) == beg + 1)
+				    end--;
+			    } else {
+				if ((beg = tlines) + 1 == end)
+				    end--;
+			    }
 			}
+			if (tlines > g->lins)
+			    tlines = g->lins;
 		    }
 		}
 	    } else if (g->width) {
-		if (isset(LISTROWSFIRST)) {
+		if (g->flags & CGF_ROWS) {
 		    int addlen, count, tcol, maxlines = 0, llines, i;
+		    int beg = columns / g->shortest, end = g->cols;
 		    Cmatch *first;
 
-		    for (tcols = columns / g->shortest; tcols > g->cols;
-			 tcols--) {
-			p = first = skipnolist(g->matches);
+		    while (1) {
+			tcols = (beg + end) >> 1;
+
+			p = first = skipnolist(g->matches, showall);
 			for (maxlen = width = maxlines = llines = tcol = 0,
 				 count = g->dcount;
 			     count > 0; count--) {
@@ -1497,7 +1444,7 @@ calclist(void)
 			    if (addlen > maxlen)
 				maxlen = addlen;
 			    for (i = tcols; i && *p; i--)
-				p = skipnolist(p + 1);
+				p = skipnolist(p + 1, showall);
 
 			    llines++;
 			    if (!*p) {
@@ -1510,29 +1457,46 @@ calclist(void)
 				ws[tcol++] = maxlen;
 				maxlen = 0;
 
-				p = first = skipnolist(first + 1);
+				p = first = skipnolist(first + 1, showall);
 			    }
 			}
 			if (tlines) {
 			    ws[tcol++] = maxlen;
 			    width += maxlen;
 			}
-			if (!count && width < columns)
+			if (!count && width < columns &&
+			    (tcols <= 0 || beg == end))
 			    break;
+
+			if (beg == end) {
+			    beg--;
+			    end--;
+			} else if (width < columns) {
+			    if ((end = tcols) == beg - 1)
+				end++;
+			} else {
+			    if ((beg = tcols) - 1 == end)
+				end++;
+			}
 		    }
 		    if (tcols > g->cols)
 			tlines = maxlines;
 		} else {
 		    int addlen;
+		    int smask = ((showall ? 0 : (CMF_NOLIST | CMF_MULT)) |
+				 CMF_HIDE);
+		    int beg = ((g->totl + columns) / columns);
+		    int end = g->lins;
+
+		    while (1) {
+			tlines = (beg + end) >> 1;
 
-		    for (tlines = ((g->totl + columns) / columns);
-			 tlines < g->lins; tlines++) {
 			for (p = g->matches, nth = tline = width =
 				 maxlen = tcols = 0;
-			     (m = *p); p++, nth++) {
+			     (m = *p); p++) {
 			    if (!(m->flags &
 				  (m->disp ? (CMF_DISPLINE | CMF_HIDE) :
-				   (CMF_NOLIST | CMF_HIDE)))) {
+				   smask))) {
 				addlen = mlens[m->gnum] + add;
 				if (addlen > maxlen)
 				    maxlen = addlen;
@@ -1542,15 +1506,30 @@ calclist(void)
 				    ws[tcols++] = maxlen;
 				    maxlen = tline = 0;
 				}
+				nth++;
 			    }
 			}
 			if (tline) {
 			    ws[tcols++] = maxlen;
 			    width += maxlen;
 			}
-			if (nth == g->dcount && width < columns)
+			if (nth == g->dcount && width < columns &&
+			    (beg == end || tlines >= g->lins))
 			    break;
+
+			if (beg == end) {
+			    beg++;
+			    end++;
+			} else if (width < columns) {
+			    if ((end = tlines) == beg + 1)
+				end--;
+			} else {
+			    if ((beg = tlines) + 1 == end)
+				end--;
+			}
 		    }
+		    if (tlines > g->lins)
+			tlines = g->lins;
 		}
 	    }
 	    if (tlines == g->lins) {
@@ -1584,21 +1563,23 @@ calclist(void)
     listdat.onlyexpl = onlyexpl;
     listdat.columns = columns;
     listdat.lines = lines;
+    listdat.showall = showall;
 }
 
 /**/
-int asklist(void)
+mod_export int asklist(void)
 {
     /* Set the cursor below the prompt. */
     trashzle();
     showinglist = listshown = 0;
 
-    clearflag = (isset(USEZLE) && !termflags &&
-		 complastprompt && *complastprompt);
+    clearflag = (isset(USEZLE) && !termflags && dolastprompt);
+    lastlistlen = 0;
 
     /* Maybe we have to ask if the user wants to see the list. */
     if ((!minfo.cur || !minfo.asked) &&
-	((complistmax && listdat.nlist > complistmax) ||
+	((complistmax > 0 && listdat.nlist >= complistmax) ||
+	 (complistmax < 0 && listdat.nlines <= -complistmax) ||
 	 (!complistmax && listdat.nlines >= lines))) {
 	int qup;
 	zsetterm();
@@ -1614,8 +1595,7 @@ int asklist(void)
 		tcmultout(TCUP, TCMULTUP, nlnct);
 	    } else
 		putc('\n', shout);
-	    if (minfo.cur)
-		minfo.asked = 2;
+	    minfo.asked = 2;
 	    return 1;
 	}
 	if (clearflag) {
@@ -1626,15 +1606,14 @@ int asklist(void)
 	} else
 	    putc('\n', shout);
 	settyinfo(&shttyinfo);
-	if (minfo.cur)
-	    minfo.asked = 1;
+	minfo.asked = 1;
     }
-    return 0;
+    return (minfo.asked ? minfo.asked - 1 : 0);
 }
 
 /**/
-int
-printlist(int over, CLPrintFunc printm)
+mod_export int
+printlist(int over, CLPrintFunc printm, int showall)
 {
     Cmgroup g;
     Cmatch *p, m;
@@ -1715,7 +1694,7 @@ printlist(int over, CLPrintFunc printm)
 			    while (a--)
 				putc(' ', shout);
 			}
-			pq += (isset(LISTROWSFIRST) ? 1 : nc);
+			pq += ((g->flags & CGF_ROWS) ? 1 : nc);
 			mc++;
 			n--;
 		    }
@@ -1728,10 +1707,11 @@ printlist(int over, CLPrintFunc printm)
 				tcout(TCCLEAREOD);
 			}
 		    }
-		    pp += (isset(LISTROWSFIRST) ? g->cols : 1);
+		    pp += ((g->flags & CGF_ROWS) ? g->cols : 1);
 		}
 	    }
-	} else if (!listdat.onlyexpl && g->lcount) {
+	} else if (!listdat.onlyexpl &&
+		   (g->lcount || (showall && g->mcount))) {
 	    int n = g->dcount, nl, nc, i, j, wid;
 	    Cmatch *q;
 
@@ -1765,7 +1745,7 @@ printlist(int over, CLPrintFunc printm)
 			tcout(TCCLEAREOD);
 		}
 	    }
-	    for (p = skipnolist(g->matches); n && nl--;) {
+	    for (p = skipnolist(g->matches, showall); n && nl--;) {
 		i = g->cols;
 		mc = 0;
 		q = p;
@@ -1794,14 +1774,16 @@ printlist(int over, CLPrintFunc printm)
 		    printed++;
 
 		    if (--n)
-			for (j = (isset(LISTROWSFIRST) ? 1 : nc); j && *q; j--)
-			    q = skipnolist(q + 1);
+			for (j = ((g->flags & CGF_ROWS) ? 1 : nc);
+			     j && *q; j--)
+			    q = skipnolist(q + 1, showall);
 		    mc++;
 		}
-		while (i-- > 0)
-		    printm(g, NULL, mc++, ml, (!i),
+		while (i-- > 0) {
+		    printm(g, NULL, mc, ml, (!i),
 			   (g->widths ? g->widths[mc] : g->width), NULL, NULL);
-
+		    mc++;
+		}
 		if (n) {
 		    putc('\n', shout);
 		    ml++;
@@ -1811,25 +1793,30 @@ printlist(int over, CLPrintFunc printm)
 			    tcout(TCCLEAREOD);
 		    }
 		    if (nl)
-			for (j = (isset(LISTROWSFIRST) ? g->cols : 1); j && *p; j--)
-			    p = skipnolist(p + 1);
+			for (j = ((g->flags & CGF_ROWS) ? g->cols : 1);
+			     j && *p; j--)
+			    p = skipnolist(p + 1, showall);
 		}
 	    }
 	}
-	if (g->lcount)
+	if (g->lcount || (showall && g->mcount))
 	    pnl = 1;
 	g = g->next;
     }
+    lastlistlen = 0;
     if (clearflag) {
 	/* Move the cursor up to the prompt, if always_last_prompt *
 	 * is set and all that...                                  */
 	if ((ml = listdat.nlines + nlnct - 1) < lines) {
 	    tcmultout(TCUP, TCMULTUP, ml);
 	    showinglist = -1;
+
+	    lastlistlen = listdat.nlines;
 	} else
 	    clearflag = 0, putc('\n', shout);
     } else
 	putc('\n', shout);
+
     listshown = (clearflag ? 1 : -1);
 
     return printed;
@@ -1858,9 +1845,8 @@ iprintm(Cmgroup g, Cmatch *mp, int mc, int ml, int lastc, int width,
 	nicezputs(m->str, shout);
 	len = niceztrlen(m->str);
 
-	if (isset(LISTTYPES)) {
-	    if (buf)
-		putc(file_type(buf->st_mode), shout);
+	if (isset(LISTTYPES) && buf) {
+	    putc(file_type(buf->st_mode), shout);
 	    len++;
 	}
     }
@@ -1876,7 +1862,7 @@ iprintm(Cmgroup g, Cmatch *mp, int mc, int ml, int lastc, int width,
 int
 ilistmatches(Hookdef dummy, Chdata dat)
 {
-    calclist();
+    calclist(0);
 
     if (!listdat.nlines) {
 	showinglist = listshown = 0;
@@ -1885,7 +1871,7 @@ ilistmatches(Hookdef dummy, Chdata dat)
     if (asklist())
 	return 0;
 
-    printlist(0, iprintm);
+    printlist(0, iprintm, 0);
 
     return 0;
 }
@@ -1897,6 +1883,7 @@ int
 list_matches(Hookdef dummy, void *dummy2)
 {
     struct chdata dat;
+    int ret;
 
 #ifdef DEBUG
     /* Sanity check */
@@ -1909,18 +1896,20 @@ list_matches(Hookdef dummy, void *dummy2)
     dat.matches = amatches;
     dat.num = nmatches;
     dat.cur = NULL;
-    return runhookdef(COMPLISTMATCHESHOOK, (void *) &dat);
+    ret = runhookdef(COMPLISTMATCHESHOOK, (void *) &dat);
+
+    return ret;
 }
 
 /* Invalidate the completion list. */
 
 /**/
-int
+mod_export int
 invalidate_list(void)
 {
-    if (showinglist == -2)
-	listmatches();
     if (validlist) {
+	if (showinglist == -2)
+	    zrefresh();
 	freematches(lastmatches);
 	lastmatches = NULL;
 	hasoldlist = 0;
diff --git a/Src/Zle/computil.c b/Src/Zle/computil.c
index aed3d9808..a844ee1ef 100644
--- a/Src/Zle/computil.c
+++ b/Src/Zle/computil.c
@@ -33,22 +33,32 @@
 
 /* Help for `_display'. */
 
+/* Calculation state. */
+
 typedef struct cdisp *Cdisp;
 
 struct cdisp {
-    int pre, suf, colon;
+    int pre;			/* prefix length */
+    int suf;			/* suffix length */
+    int colon;			/* number of strings with descriptions */
 };
 
+/* Calculate longest prefix and suffix and count the strings with
+ * descriptions. */
+
 static void
 cdisp_calc(Cdisp disp, char **args)
 {
     char *cp;
-    int i;
+    int i, nbc;
 
     for (; *args; args++) {
-	if ((cp = strchr(*args, ':')) && cp[1]) {
+	for (nbc = 0, cp = *args; *cp && *cp != ':'; cp++)
+	    if (*cp == '\\' && cp[1])
+		cp++, nbc++;
+	if (*cp == ':' && cp[1]) {
 	    disp->colon++;
-	    if ((i = cp - *args) > disp->pre)
+	    if ((i = cp - *args - nbc) > disp->pre)
 		disp->pre = i;
 	    if ((i = strlen(cp + 1)) > disp->suf)
 		disp->suf = i;
@@ -56,78 +66,29 @@ cdisp_calc(Cdisp disp, char **args)
     }
 }
 
-static char **
-cdisp_build(Cdisp disp, char *sep, char **args)
-{
-    int sl = strlen(sep), pre = disp->pre, suf;
-    VARARR(char, buf, disp->pre + disp->suf + sl + 1);
-    char **ret, **rp, *cp;
-
-    ret = (char **) zalloc((arrlen(args) + 1) * sizeof(char *));
-
-    memcpy(buf + pre, sep, sl);
-    suf = pre + sl;
-
-    for (rp = ret; *args; args++) {
-	if ((cp = strchr(*args, ':')) && cp[1]) {
-	    memset(buf, ' ', pre);
-	    memcpy(buf, *args, (cp - *args));
-	    strcpy(buf + suf, cp + 1);
-	    *rp++ = ztrdup(buf);
-	} else {
-	    if (cp)
-		*cp = '\0';
-	    *rp++ = ztrdup(*args);
-	    if (cp)
-		*cp = ':';
-	}
-    }
-    *rp = NULL;
-
-    return ret;
-}
-
-/**/
-static int
-bin_compdisplay(char *nam, char **args, char *ops, int func)
-{
-    struct cdisp disp;
-
-    if (incompfunc != 1) {
-	zerrnam(nam, "can only be called from completion function", NULL, 0);
-	return 1;
-    }
-    disp.pre = disp.suf = disp.colon = 0;
-
-    cdisp_calc(&disp, args + 2);
-    setaparam(args[0], cdisp_build(&disp, args[1], args + 2));
-
-    return !disp.colon;
-}
-
 /* Help fuer `_describe'. */
 
 typedef struct cdset *Cdset;
 
 struct cdstate {
-    int showd;
-    char *sep;
-    Cdset sets;
-    struct cdisp disp;
+    int showd;			/* != 0 if descriptions should be shown */
+    char *sep;			/* the separator string */
+    Cdset sets;			/* the sets of matches */
+    struct cdisp disp;		/* used to calculate the alignment */
 };
 
 struct cdset {
-    Cdset next;
-    char **opts;
-    char **strs;
-    char **matches;
+    Cdset next;			/* guess what */
+    char **opts;		/* the compadd-options */
+    char **strs;		/* the display-strings */
+    char **matches;		/* the matches (or NULL) */
 };
 
 static struct cdstate cd_state;
 static int cd_parsed = 0;
 
 static void
-free_cdsets(Cdset p)
+freecdsets(Cdset p)
 {
     Cdset n;
 
@@ -143,6 +104,8 @@ free_cdsets(Cdset p)
     }
 }
 
+/* Initialisation. Store and calculate the string and matches and so on. */
+
 static int
 cd_init(char *nam, char *sep, char **args, int disp)
 {
@@ -151,7 +114,7 @@ cd_init(char *nam, char *sep, char **args, int disp)
 
     if (cd_parsed) {
 	zsfree(cd_state.sep);
-	free_cdsets(cd_state.sets);
+	freecdsets(cd_state.sets);
     }
     setp = &(cd_state.sets);
     cd_state.sep = ztrdup(sep);
@@ -164,24 +127,20 @@ cd_init(char *nam, char *sep, char **args, int disp)
 	setp = &(set->next);
 
 	if (!(ap = get_user_var(*args))) {
-	    zerrnam(nam, "invalid argument: %s", *args, 0);
+	    zwarnnam(nam, "invalid argument: %s", *args, 0);
 	    return 1;
 	}
-	PERMALLOC {
-	    set->strs = arrdup(ap);
-	} LASTALLOC;
+	set->strs = zarrdup(ap);
 
 	if (disp)
 	    cdisp_calc(&(cd_state.disp), set->strs);
 
 	if (*++args && **args != '-') {
 	    if (!(ap = get_user_var(*args))) {
-		zerrnam(nam, "invalid argument: %s", *args, 0);
+		zwarnnam(nam, "invalid argument: %s", *args, 0);
 		return 1;
 	    }
-	    PERMALLOC {
-		set->matches = arrdup(ap);
-	    } LASTALLOC;
+	    set->matches = zarrdup(ap);
 	    args++;
 	}
 	for (ap = args; *args &&
@@ -190,15 +149,15 @@ cd_init(char *nam, char *sep, char **args, int disp)
 
 	tmp = *args;
 	*args = NULL;
-	PERMALLOC {
-	    set->opts = arrdup(ap);
-	} LASTALLOC;
+	set->opts = zarrdup(ap);
 	if ((*args = tmp))
 	    args++;
     }
     return 0;
 }
 
+/* Get the next set. */
+
 static int
 cd_get(char **params)
 {
@@ -206,15 +165,21 @@ cd_get(char **params)
 
     if ((set = cd_state.sets)) {
 	char **sd, **sdp, **md, **mdp, **ss, **ssp, **ms, **msp;
-	char **p, **mp, *cp;
+	char **p, **mp, *cp, *copy, *cpp, oldc;
 	int dl = 1, sl = 1, sepl = strlen(cd_state.sep);
 	int pre = cd_state.disp.pre, suf = cd_state.disp.suf;
 	VARARR(char, buf, pre + suf + sepl + 1);
 
 	for (p = set->strs; *p; p++)
-	    if (cd_state.showd && (cp = strchr(*p, ':')) && cp[1])
-		dl++;
-	    else
+	    if (cd_state.showd) {
+		for (cp = *p; *cp && *cp != ':'; cp++)
+		    if (*cp == '\\' && cp[1])
+			cp++;
+		if (*cp == ':' && cp[1])
+		    dl++;
+		else
+		    sl++;
+	    } else
 		sl++;
 
 	sd = (char **) zalloc(dl * sizeof(char *));
@@ -226,41 +191,44 @@ cd_get(char **params)
 	    memcpy(buf + pre, cd_state.sep, sepl);
 	    suf = pre + sepl;
 	}
+
+	/* Build the aligned display strings. */
+
 	for (sdp = sd, ssp = ss, mdp = md, msp = ms,
 		 p = set->strs, mp = set->matches; *p; p++) {
-	    if ((cp = strchr(*p, ':')) && cp[1] && cd_state.showd) {
+	    copy = dupstring(*p);
+	    for (cp = cpp = copy; *cp && *cp != ':'; cp++) {
+		if (*cp == '\\' && cp[1])
+		    cp++;
+		*cpp++ = *cp;
+	    }
+	    oldc = *cpp;
+	    *cpp = '\0';
+	    if (((cpp == cp && oldc == ':') || *cp == ':') && cp[1] &&
+		cd_state.showd) {
 		memset(buf, ' ', pre);
-		memcpy(buf, *p, (cp - *p));
+		memcpy(buf, copy, (cpp - copy));
 		strcpy(buf + suf, cp + 1);
 		*sdp++ = ztrdup(buf);
 		if (mp) {
 		    *mdp++ = ztrdup(*mp);
 		    if (*mp)
 			mp++;
-		} else {
-		    *cp = '\0';
-		    *mdp++ = ztrdup(*p);
-		    *cp = ':';
-		}
+		} else
+		    *mdp++ = ztrdup(copy);
 	    } else {
-		if (cp)
-		    *cp = '\0';
-		*ssp++ = ztrdup(*p);
+		*ssp++ = ztrdup(copy);
 		if (mp) {
 		    *msp++ = ztrdup(*mp);
 		    if (*mp)
 			mp++;
 		} else
-		    *msp++ = ztrdup(*p);
-		if (cp)
-		    *cp = ':';
+		    *msp++ = ztrdup(copy);
 	    }
 	}
 	*sdp = *ssp = *mdp = *msp = NULL;
 
-	PERMALLOC {
-	    p = arrdup(set->opts);
-	} LASTALLOC;
+	p = zarrdup(set->opts);
 
 	setaparam(params[0], p);
 	setaparam(params[1], sd);
@@ -270,7 +238,7 @@ cd_get(char **params)
 
 	cd_state.sets = set->next;
 	set->next = NULL;
-	free_cdsets(set);
+	freecdsets(set);
 
 	return 0;
     }
@@ -282,34 +250,36 @@ static int
 bin_compdescribe(char *nam, char **args, char *ops, int func)
 {
     if (incompfunc != 1) {
-	zerrnam(nam, "can only be called from completion function", NULL, 0);
+	zwarnnam(nam, "can only be called from completion function", NULL, 0);
 	return 1;
     }
     if (!args[0][0] || !args[0][1] || args[0][2]) {
-	zerrnam(nam, "invalid argument: %s", args[0], 0);
+	zwarnnam(nam, "invalid argument: %s", args[0], 0);
 	return 1;
     }
     switch (args[0][1]) {
     case 'i':
+	cd_parsed = 1;
+	return cd_init(nam, "", args + 1, 0);
     case 'I':
 	cd_parsed = 1;
-	return cd_init(nam, args[1], args + 2, (args[0][1] == 'I'));
+	return cd_init(nam, args[1], args + 2, 1);
     case 'g':
 	if (cd_parsed) {
 	    int n = arrlen(args);
 
 	    if (n != 6) {
-		zerrnam(nam, (n < 6 ? "not enough arguments" :
+		zwarnnam(nam, (n < 6 ? "not enough arguments" :
 			      "too many arguments"), NULL, 0);
 		return 1;
 	    }
 	    return cd_get(args + 1);
 	} else {
-	    zerrnam(nam, "no parsed state", NULL, 0);
+	    zwarnnam(nam, "no parsed state", NULL, 0);
 	    return 1;
 	}
     }
-    zerrnam(nam, "invalid option: %s", args[0], 0);
+    zwarnnam(nam, "invalid option: %s", args[0], 0);
     return 1;
 }
 
@@ -319,29 +289,34 @@ typedef struct cadef *Cadef;
 typedef struct caopt *Caopt;
 typedef struct caarg *Caarg;
 
+/* Cache for a set of _arguments-definitions. */
+
 struct cadef {
-    Cadef next;
-    Caopt opts;
-    int nopts, ndopts, nodopts;
-    Caarg args;
-    Caarg rest;
-    char **defs;
-    int ndefs;
-    int lastt;
-    Caopt *single;
-    char *match;
-    int argsactive;
+    Cadef next;			/* next in cache */
+    Caopt opts;			/* the options */
+    int nopts, ndopts, nodopts;	/* number of options/direct/optional direct */
+    Caarg args;			/* the normal arguments */
+    Caarg rest;			/* the rest-argument */
+    char **defs;		/* the original strings */
+    int ndefs;			/* number of ... */
+    int lastt;			/* last time this was used */
+    Caopt *single;		/* array of single-letter options */
+    char *match;		/* -M spec to use */
+    int argsactive;		/* if arguments are still allowed */
+				/* used while parsing a command line */
 };
 
+/* Description for an option. */
+
 struct caopt {
     Caopt next;
-    char *name;
-    char *descr;
-    char **xor;
-    int type;
-    Caarg args;
-    int active;
-    int num;
+    char *name;			/* option name */
+    char *descr;		/* the description */
+    char **xor;			/* if this, then not ... */
+    int type;			/* type, CAO_* */
+    Caarg args;			/* option arguments */
+    int active;			/* still allowed on command line */
+    int num;			/* it's the num'th option */
 };
 
 #define CAO_NEXT    1
@@ -349,13 +324,18 @@ struct caopt {
 #define CAO_ODIRECT 3
 #define CAO_EQUAL   4
 
+/* Description for an argument */
+
 struct caarg {
     Caarg next;
-    char *descr;
-    char *action;
-    int type;
-    char *end;
-    int num;
+    char *descr;		/* description */
+    char **xor;			/* if this, then not ... */
+    char *action;		/* what to do for it */
+    int type;			/* CAA_* below */
+    char *end;			/* end-pattern for ::<pat>:... */
+    char *opt;			/* option name if for an option */
+    int num;			/* it's the num'th argument */
+    int active;			/* still allowed on command line */
 };
 
 #define CAA_NORMAL 1
@@ -364,9 +344,13 @@ struct caarg {
 #define CAA_RARGS  4
 #define CAA_RREST  5
 
+/* The cache of parsed descriptons. */
+
 #define MAX_CACACHE 8
 static Cadef cadef_cache[MAX_CACACHE];
 
+/* Compare two arrays of strings for equality. */
+
 static int
 arrcmp(char **a, char **b)
 {
@@ -383,45 +367,54 @@ arrcmp(char **a, char **b)
     }
 }
 
+/* Memory stuff. Obviously. */
+
 static void
-free_caargs(Caarg a)
+freecaargs(Caarg a)
 {
     Caarg n;
 
     for (; a; a = n) {
 	n = a->next;
 	zsfree(a->descr);
+	if (a->xor)
+	    freearray(a->xor);
 	zsfree(a->action);
 	zsfree(a->end);
+	zsfree(a->opt);
 	zfree(a, sizeof(*a));
     }
 }
 
 static void
-free_cadef(Cadef d)
+freecadef(Cadef d)
 {
     if (d) {
 	Caopt p, n;
 
 	zsfree(d->match);
-	freearray(d->defs);
+	if (d->defs)
+	    freearray(d->defs);
 
 	for (p = d->opts; p; p = n) {
 	    n = p->next;
 	    zsfree(p->name);
 	    zsfree(p->descr);
-	    freearray(p->xor);
-	    free_caargs(p->args);
+	    if (p->xor)
+		freearray(p->xor);
+	    freecaargs(p->args);
 	    zfree(p, sizeof(*p));
 	}
-	free_caargs(d->args);
-	free_caargs(d->rest);
+	freecaargs(d->args);
+	freecaargs(d->rest);
 	if (d->single)
 	    zfree(d->single, 256 * sizeof(Caopt));
 	zfree(d, sizeof(*d));
     }
 }
 
+/* Remove backslashes before colons. */
+
 static char *
 rembslashcolon(char *s)
 {
@@ -439,16 +432,41 @@ rembslashcolon(char *s)
     return r;
 }
 
+/* Add backslashes before colons. */
+
+static char *
+bslashcolon(char *s)
+{
+    char *p, *r;
+
+    r = p = zhalloc((2 * strlen(s)) + 1);
+
+    while (*s) {
+	if (*s == ':')
+	    *p++ = '\\';
+	*p++ = *s++;
+    }
+    *p = '\0';
+
+    return r;
+}
+
+/* Parse an argument definition. */
+
 static Caarg
-parse_caarg(int mult, int type, int num, char **def)
+parse_caarg(int mult, int type, int num, char *oname, char **def)
 {
     Caarg ret = (Caarg) zalloc(sizeof(*ret));
     char *p = *def, *d, sav;
 
     ret->next = NULL;
     ret->descr = ret->action = ret->end = NULL;
+    ret->xor = NULL;
     ret->num = num;
     ret->type = type;
+    ret->opt = ztrdup(oname);
+
+    /* Get the description. */
 
     for (d = p; *p && *p != ':'; p++)
 	if (*p == '\\' && p[1])
@@ -456,6 +474,9 @@ parse_caarg(int mult, int type, int num, char **def)
     sav = *p;
     *p = '\0';
     ret->descr = ztrdup(rembslashcolon(d));
+
+    /* Get the action if there is one. */
+
     if (sav) {
 	if (mult) {
 	    for (d = ++p; *p && *p != ':'; p++)
@@ -474,6 +495,8 @@ parse_caarg(int mult, int type, int num, char **def)
     return ret;
 }
 
+/* Parse an array of definitions. */
+
 static Cadef
 parse_cadef(char *nam, char **args)
 {
@@ -485,6 +508,8 @@ parse_cadef(char *nam, char **args)
 
     nopts = ndopts = nodopts = 0;
 
+    /* First string is the auto-description definition. */
+
     for (p = args[0]; *p && (p[0] != '%' || p[1] != 'd'); p++);
 
     if (*p) {
@@ -495,6 +520,8 @@ parse_cadef(char *nam, char **args)
     } else
 	adpre = adsuf = NULL;
 
+    /* Now get the -s and -M options. */
+
     args++;
     while ((p = *args)) {
 	if (!strcmp(p, "-s"))
@@ -515,26 +542,30 @@ parse_cadef(char *nam, char **args)
     if (!*args)
 	return NULL;
 
-    PERMALLOC {
-	ret = (Cadef) zalloc(sizeof(*ret));
-	ret->next = NULL;
-	ret->opts = NULL;
-	ret->args = ret->rest = NULL;
-	ret->defs = arrdup(oargs);
-	ret->ndefs = arrlen(oargs);
-	ret->lastt = time(0);
-	if (single) {
-	    ret->single = (Caopt *) zalloc(256 * sizeof(Caopt));
-	    memset(ret->single, 0, 256 * sizeof(Caopt));
-	} else
-	    ret->single = NULL;
-	ret->match = ztrdup(match);
-    } LASTALLOC;
+    /* Looks good. Optimistically allocate the cadef structure. */
+
+    ret = (Cadef) zalloc(sizeof(*ret));
+    ret->next = NULL;
+    ret->opts = NULL;
+    ret->args = ret->rest = NULL;
+    ret->defs = zarrdup(oargs);
+    ret->ndefs = arrlen(oargs);
+    ret->lastt = time(0);
+    if (single) {
+	ret->single = (Caopt *) zalloc(256 * sizeof(Caopt));
+	memset(ret->single, 0, 256 * sizeof(Caopt));
+    } else
+	ret->single = NULL;
+    ret->match = ztrdup(match);
+
+    /* Get the definitions. */
 
     for (optp = &(ret->opts); *args; args++) {
 	p = dupstring(*args);
 	xnum = 0;
 	if (*p == '(') {
+	    /* There is a xor list, get it. */
+
 	    LinkList list = newlinklist();
 	    LinkNode node;
 	    char **xp, sav;
@@ -555,9 +586,10 @@ parse_cadef(char *nam, char **args)
 		xnum++;
 		*p = sav;
 	    }
+	    /* Oops, end-of-string. */
 	    if (*p != ')') {
-		free_cadef(ret);
-		zerrnam(nam, "invalid argument: %s", *args, 0);
+		freecadef(ret);
+		zwarnnam(nam, "invalid argument: %s", *args, 0);
 		return NULL;
 	    }
 	    xor = (char **) zalloc((xnum + 2) * sizeof(char *));
@@ -568,9 +600,10 @@ parse_cadef(char *nam, char **args)
 	    p++;
 	} else
 	    xor = NULL;
-	
+
 	if (*p == '-' || *p == '+' ||
 	    (*p == '*' && (p[1] == '-' || p[1] == '+'))) {
+	    /* It's an option. */
 	    Caopt opt;
 	    Caarg oargs = NULL;
 	    int multi, otype = CAO_NEXT, again = 0;
@@ -578,25 +611,39 @@ parse_cadef(char *nam, char **args)
 
 	    rec:
 
+	    /* Allowed more than once? */
 	    if ((multi = (*p == '*')))
 		p++;
 
-	    if ((p[0] == '-' && p[1] == '+') ||
-		(p[0] == '+' && p[1] == '-')) {
+	    if (((p[0] == '-' && p[1] == '+') ||
+		 (p[0] == '+' && p[1] == '-')) &&
+		p[2] && p[2] != ':' && p[2] != '[' &&
+		p[2] != '=' && p[2] != '-' && p[2] != '+') {
+		/* It's a -+ or +- definition. We just execute the whole
+		 * stuff twice for such things. */
 		name = ++p;
 		*p = (again ? '-' : '+');
 		again = 1 - again;
 	    } else {
 		name = p;
+		/* If it's a long option skip over the first `-'. */
 		if (p[0] == '-' && p[1] == '-')
 		    p++;
 	    }
+	    if (!p[1]) {
+		freecadef(ret);
+		zwarnnam(nam, "invalid argument: %s", *args, 0);
+		return NULL;
+	    }
+
+	    /* Skip over the name. */
 	    for (p++; *p && *p != ':' && *p != '[' &&
 		     ((*p != '-' && *p != '+' && *p != '=') ||
 		      (p[1] != ':' && p[1] != '[')); p++)
 		if (*p == '\\' && p[1])
 		    p++;
 
+	    /* The character after the option name specifies the type. */
 	    c = *p;
 	    *p = '\0';
 	    if (c == '-') {
@@ -609,14 +656,15 @@ parse_cadef(char *nam, char **args)
 		otype = CAO_EQUAL;
 		c = *++p;
 	    }
+	    /* Get the optional description, if any. */
 	    if (c == '[') {
 		for (descr = ++p; *p && *p != ']'; p++)
 		    if (*p == '\\' && p[1])
 			p++;
 
 		if (!*p) {
-		    free_cadef(ret);
-		    zerrnam(nam, "invalid option definition: %s", *args, 0);
+		    freecadef(ret);
+		    zwarnnam(nam, "invalid option definition: %s", *args, 0);
 		    return NULL;
 		}
 		*p++ = '\0';
@@ -625,10 +673,11 @@ parse_cadef(char *nam, char **args)
 		descr = NULL;
 
 	    if (c && c != ':') {
-		free_cadef(ret);
-		zerrnam(nam, "invalid option definition: %s", *args, 0);
+		freecadef(ret);
+		zwarnnam(nam, "invalid option definition: %s", *args, 0);
 		return NULL;
 	    }
+	    /* Add the option name to the xor list if not `*-...'. */
 	    if (!multi) {
 		if (!xor) {
 		    xor = (char **) zalloc(2 * sizeof(char *));
@@ -637,27 +686,39 @@ parse_cadef(char *nam, char **args)
 		xor[xnum] = ztrdup(name);
 	    }
 	    if (c == ':') {
+		/* There's at least one argument. */
+
 		Caarg *oargp = &oargs;
-		int atype, rest;
+		int atype, rest, oanum = 1;
 		char *end;
 
+		/* Loop over the arguments. */
+
 		while (c == ':') {
 		    rest = 0;
 		    end = NULL;
 
+		    /* Get the argument type. */
 		    if (*++p == ':') {
 			atype = CAA_OPT;
 			p++;
 		    } else if (*p == '*') {
 			if (*++p != ':') {
-			    for (end = ++p; *p && *p != ':'; p++)
+			    char sav;
+
+			    for (end = p++; *p && *p != ':'; p++)
 				if (*p == '\\' && p[1])
 				    p++;
+			    sav = *p;
+			    *p = '\0';
+			    end = dupstring(end);
+			    tokenize(end);
+			    *p = sav;
 			}
 			if (*p != ':') {
-			    free_cadef(ret);
-			    free_caargs(oargs);
-			    zerrnam(nam, "invalid option definition: %s",
+			    freecadef(ret);
+			    freecaargs(oargs);
+			    zwarnnam(nam, "invalid option definition: %s",
 				    *args, 0);
 			    return NULL;
 			}
@@ -672,54 +733,74 @@ parse_cadef(char *nam, char **args)
 			rest = 1;
 		    } else
 			atype = CAA_NORMAL;
-		    *oargp = parse_caarg(!rest, atype, 0, &p);
+
+		    /* And the definition. */
+
+		    *oargp = parse_caarg(!rest, atype, oanum++, name, &p);
+		    if (end)
+			(*oargp)->end = ztrdup(end);
 		    oargp = &((*oargp)->next);
 		    if (rest)
 			break;
 		    c = *p;
 		}
 	    }
-	    PERMALLOC {
-		*optp = opt = (Caopt) zalloc(sizeof(*opt));
-		optp = &((*optp)->next);
-
-		opt->next = NULL;
-		opt->name = ztrdup(name);
-		if (descr)
-		    opt->descr = ztrdup(descr);
-		else if (adpre && oargs && !oargs->next)
+	    /* Store the option definition. */
+
+	    *optp = opt = (Caopt) zalloc(sizeof(*opt));
+	    optp = &((*optp)->next);
+
+	    opt->next = NULL;
+	    opt->name = ztrdup(rembslashcolon(name));
+	    if (descr)
+		opt->descr = ztrdup(descr);
+	    else if (adpre && oargs && !oargs->next) {
+		char *d;
+
+		for (d = oargs->descr; *d; d++)
+		    if (!iblank(*d))
+			break;
+
+		if (*d)
 		    opt->descr = tricat(adpre, oargs->descr, adsuf);
 		else
 		    opt->descr = NULL;
-		opt->xor = xor;
-		opt->type = otype;
-		opt->args = oargs;
-		opt->num = nopts++;
-	    } LASTALLOC;
+	    } else
+		opt->descr = NULL;
+	    opt->xor = xor;
+	    opt->type = otype;
+	    opt->args = oargs;
+	    opt->num = nopts++;
 
 	    if (otype == CAO_DIRECT)
 		ndopts++;
 	    else if (otype == CAO_ODIRECT || otype == CAO_EQUAL)
 		nodopts++;
 
+	    /* If this is for single-letter option we also store a
+	     * pointer for the definition in the array for fast lookup. */
+
 	    if (single && name[1] && !name[2])
 		ret->single[STOUC(name[1])] = opt;
 
 	    if (again) {
+		/* Do it all again for `*-...'. */
 		p = dupstring(*args);
 		goto rec;
 	    }
 	} else if (*p == '*') {
+	    /* It's a rest-argument definition. */
+
 	    int type = CAA_REST;
 
 	    if (*++p != ':') {
-		free_cadef(ret);
-		zerrnam(nam, "invalid rest argument definition: %s", *args, 0);
+		freecadef(ret);
+		zwarnnam(nam, "invalid rest argument definition: %s", *args, 0);
 		return NULL;
 	    }
 	    if (ret->rest) {
-		free_cadef(ret);
-		zerrnam(nam, "doubled rest argument definition: %s", *args, 0);
+		freecadef(ret);
+		zwarnnam(nam, "doubled rest argument definition: %s", *args, 0);
 		return NULL;
 	    }
 	    if (*++p == ':') {
@@ -729,40 +810,49 @@ parse_cadef(char *nam, char **args)
 		} else
 		    type = CAA_RARGS;
 	    }
-	    ret->rest = parse_caarg(0, type, -1, &p);
+	    ret->rest = parse_caarg(0, type, -1, NULL, &p);
+	    ret->rest->xor = xor;
 	} else {
+	    /* It's a normal argument definition. */
+
 	    int type = CAA_NORMAL;
 	    Caarg arg, tmp, pre;
 
 	    if (idigit(*p)) {
+		/* Argment number is given. */
 		int num = 0;
 
 		while (*p && idigit(*p))
-		    num = (num * 10) + ((int) *p++);
+		    num = (num * 10) + (((int) *p++) - '0');
 
 		anum = num + 1;
 	    } else
+		/* Default number. */
 		anum++;
 
 	    if (*p != ':') {
-		free_cadef(ret);
-		zerrnam(nam, "invalid argument: %s", *args, 0);
+		freecadef(ret);
+		zwarnnam(nam, "invalid argument: %s", *args, 0);
 		return NULL;
 	    }
 	    if (*++p == ':') {
+		/* Optional argument. */
 		type = CAA_OPT;
 		p++;
 	    }
-	    arg = parse_caarg(0, type, anum - 1, &p);
+	    arg = parse_caarg(0, type, anum - 1, NULL, &p);
+	    arg->xor = xor;
+
+	    /* Sort the new definition into the existing list. */
 
 	    for (tmp = ret->args, pre = NULL;
 		 tmp && tmp->num < anum - 1;
 		 pre = tmp, tmp = tmp->next);
 
 	    if (tmp && tmp->num == anum - 1) {
-		free_cadef(ret);
-		free_caargs(arg);
-		zerrnam(nam, "doubled argument definition: %s", *args, 0);
+		freecadef(ret);
+		freecaargs(arg);
+		zwarnnam(nam, "doubled argument definition: %s", *args, 0);
 		return NULL;
 	    }
 	    arg->next = tmp;
@@ -779,13 +869,16 @@ parse_cadef(char *nam, char **args)
     return ret;
 }
 
+/* Given an array of definitions, return the cadef for it. From the cache
+ * are newly built. */
+
 static Cadef
 get_cadef(char *nam, char **args)
 {
     Cadef *p, *min, new;
     int i, na = arrlen(args);
 
-    for (i = MAX_CACACHE, p = cadef_cache, min = NULL; *p && i--; p++)
+    for (i = MAX_CACACHE, p = cadef_cache, min = NULL; *p && i; p++, i--)
 	if (*p && na == (*p)->ndefs && arrcmp(args, (*p)->defs)) {
 	    (*p)->lastt = time(0);
 
@@ -795,26 +888,36 @@ get_cadef(char *nam, char **args)
     if (i)
 	min = p;
     if ((new = parse_cadef(nam, args))) {
-	free_cadef(*min);
+	freecadef(*min);
 	*min = new;
     }
     return new;
 }
 
+/* Get the option used in a word from the line, if any. */
+
 static Caopt
 ca_get_opt(Cadef d, char *line, int full, char **end)
 {
     Caopt p;
 
-    if (full) {
-	for (p = d->opts; p; p = p->next)
-	    if (p->active && !strcmp(p->name, line))
-		return p;
-    } else {
+    /* The full string may be an option. */
+
+    for (p = d->opts; p; p = p->next)
+	if (p->active && !strcmp(p->name, line)) {
+	    if (end)
+		*end = line + strlen(line);
+
+	    return p;
+	}
+
+    if (!full) {
+	/* The string from the line probably only begins with an option. */
 	for (p = d->opts; p; p = p->next)
-	    if (p->active && p->args && p->type != CAO_NEXT &&
-		strpfx(p->name, line)) {
+	    if (p->active && ((!p->args || p->type == CAO_NEXT) ?
+			      !strcmp(p->name, line) : strpfx(p->name, line))) {
 		if (end) {
+		    /* Return a pointer to the end of the option. */
 		    int l = strlen(p->name);
 
 		    if (p->type == CAO_EQUAL && line[l] == '=')
@@ -828,12 +931,13 @@ ca_get_opt(Cadef d, char *line, int full, char **end)
     return NULL;
 }
 
+/* Same as above, only for single-letter-style. */
+
 static Caopt
 ca_get_sopt(Cadef d, char *line, int full, char **end)
 {
     Caopt p;
-
-    line++;
+    char pre = *line++;
 
     if (full) {
 	for (p = NULL; *line; line++)
@@ -844,7 +948,7 @@ ca_get_sopt(Cadef d, char *line, int full, char **end)
     } else {
 	for (p = NULL; *line; line++)
 	    if ((p = d->single[STOUC(*line)]) && p->active &&
-		p->args && p->type != CAO_NEXT) {
+		p->args && p->type != CAO_NEXT && p->name[0] == pre) {
 		if (end) {
 		    line++;
 		    if (p->type == CAO_EQUAL && *line == '=')
@@ -852,13 +956,18 @@ ca_get_sopt(Cadef d, char *line, int full, char **end)
 		    *end = line;
 		}
 		break;
-	    } else if (!p || !p->active || (line[1] && p->args))
+	    } else if (!p || !p->active || (line[1] && p->args) ||
+		       p->name[0] != pre)
 		return NULL;
+	if (p && end)
+	    *end = line;
 	return p;
     }
     return NULL;
 }
 
+/* Return the n'th argument definition. */
+
 static Caarg
 ca_get_arg(Cadef d, int n)
 {
@@ -868,14 +977,16 @@ ca_get_arg(Cadef d, int n)
 	while (a && a->num < n)
 	    a = a->next;
 
-	if (a && a->num == n)
+	if (a && a->num == n && a->active)
 	    return a;
 
-	return d->rest;
+	return (d->rest && d->rest->active ? d->rest : NULL);
     }
     return NULL;
 }
 
+/* Use a xor list, marking options as inactive. */
+
 static void
 ca_inactive(Cadef d, char **xor)
 {
@@ -885,17 +996,32 @@ ca_inactive(Cadef d, char **xor)
 	for (; *xor; xor++) {
 	    if (xor[0][0] == ':' && !xor[0][1])
 		d->argsactive = 0;
-	    else if ((opt = ca_get_opt(d, *xor, 1, NULL)))
+	    else if (xor[0][0] == '*' && !xor[0][1]) {
+		if (d->rest)
+		    d->rest->active = 0;
+	    } else if (xor[0][0] >= '0' && xor[0][0] <= '9') {
+		int n = atoi(xor[0]);
+		Caarg a = d->args;
+
+		while (a && a->num < n)
+		    a = a->next;
+
+		if (a && a->num == n)
+		    a->active = 0;
+	    } else if ((opt = ca_get_opt(d, *xor, 1, NULL)))
 		opt->active = 0;
 	}
     }
 }
 
+/* State when parsing a command line. */
+
 struct castate {
     Cadef d;
+    int nopts;
     Caarg def, ddef;
     Caopt curopt;
-    int opt, arg, argbeg, optbeg, nargbeg, restbeg;
+    int opt, arg, argbeg, optbeg, nargbeg, restbeg, curpos;
     int inopt, inrest, inarg, nth, doff, singles;
     LinkList args;
     LinkList *oargs;
@@ -904,40 +1030,55 @@ struct castate {
 static struct castate ca_laststate;
 static int ca_parsed = 0, ca_alloced = 0;
 
+/* Pars a command line. */
+
 static void
 ca_parse_line(Cadef d)
 {
     Caarg adef, ddef;
-    Caopt ptr;
+    Caopt ptr, wasopt;
     struct castate state;
-    char *line, *pe;
+    char *line, *pe, **argxor = NULL;
     int cur, doff;
     Patprog endpat = NULL;
 
+    /* Free old state. */
+
     if (ca_alloced) {
-	int i = ca_laststate.d->nopts;
+	int i = ca_laststate.nopts;
 	LinkList *p = ca_laststate.oargs;
 
 	freelinklist(ca_laststate.args, freestr);
 	while (i--)
 	    if (*p++)
 		freelinklist(p[-1], freestr);
+
+	zfree(ca_laststate.oargs, ca_laststate.d->nopts * sizeof(LinkList));
     }
+    /* Mark everything as active. */
+
     for (ptr = d->opts; ptr; ptr = ptr->next)
 	ptr->active = 1;
     d->argsactive = 1;
+    if (d->rest)
+	d->rest->active = 1;
+    for (adef = d->args; adef; adef = adef->next)
+	adef->active = 1;
+
+    /* Default values for the state. */
 
     state.d = d;
+    state.nopts = d->nopts;
     state.def = state.ddef = NULL;
     state.curopt = NULL;
     state.argbeg = state.optbeg = state.nargbeg = state.restbeg =
 	state.nth = state.inopt = state.inarg = state.opt = state.arg = 1;
     state.inrest = state.doff = state.singles = state.doff = 0;
-    PERMALLOC {
-	state.args = newlinklist();
-	state.oargs = (LinkList *) zalloc(d->nopts * sizeof(LinkList));
-	memset(state.oargs, 0, d->nopts * sizeof(LinkList));
-    } LASTALLOC;
+    state.curpos = compcurrent;
+    state.args = znewlinklist();
+    state.oargs = (LinkList *) zalloc(d->nopts * sizeof(LinkList));
+    memset(state.oargs, 0, d->nopts * sizeof(LinkList));
+
     ca_alloced = 1;
 
     memcpy(&ca_laststate, &state, sizeof(state));
@@ -947,46 +1088,67 @@ ca_parse_line(Cadef d)
 
 	return;
     }
+    /* Loop over the words from the line. */
+
     for (line = compwords[1], cur = 2, state.curopt = NULL, state.def = NULL;
 	 line; line = compwords[cur++]) {
 	ddef = adef = NULL;
 	doff = state.singles = 0;
+
+	ca_inactive(d, argxor);
+
+	/* We've a definition for an argument, skip to the next. */
+
 	if (state.def) {
 	    state.arg = 0;
-	    if (state.curopt) {
-		PERMALLOC {
-		    addlinknode(state.oargs[state.curopt->num], ztrdup(line));
-		} LASTALLOC;
-	    }
-	    state.opt = (state.def->type == CAA_OPT && line[0] && line[1]);
+	    if (state.curopt)
+		zaddlinknode(state.oargs[state.curopt->num], ztrdup(line));
+
+	    state.opt = (state.def->type == CAA_OPT);
 
 	    if (state.def->type == CAA_REST || state.def->type == CAA_RARGS ||
 		state.def->type == CAA_RREST) {
 		if (state.def->end && pattry(endpat, line)) {
 		    state.def = NULL;
 		    state.curopt = NULL;
+		    state.opt = state.arg = 1;
 		    continue;
 		}
 	    } else if ((state.def = state.def->next))
 		state.argbeg = cur;
-	    else
+	    else {
 		state.curopt = NULL;
+		state.opt = 1;
+	    }
 	} else {
-	    state.opt = (line[0] && line[1]);
-	    state.arg = 1;
+	    state.opt = state.arg = 1;
 	    state.curopt = NULL;
 	}
+	if (state.opt)
+	    state.opt = (line[0] ? (line[1] ? 2 : 1) : 0);
+
 	pe = NULL;
 
-	if (state.opt && (state.curopt = ca_get_opt(d, line, 0, &pe))) {
+	wasopt = NULL;
+
+	/* See if it's an option. */
+
+	if (state.opt == 2 && (state.curopt = ca_get_opt(d, line, 0, &pe)) &&
+	    (state.curopt->type != CAO_EQUAL || 
+	     compwords[cur] || pe[-1] == '=')) {
+
 	    ddef = state.def = state.curopt->args;
 	    doff = pe - line;
 	    state.optbeg = state.argbeg = state.inopt = cur;
-	    PERMALLOC {
-		state.oargs[state.curopt->num] = newlinklist();
-	    } LASTALLOC;
+	    state.singles = (d->single && (!pe || !*pe) &&
+			     state.curopt->name[1] && !state.curopt->name[2]);
+
+	    state.oargs[state.curopt->num] = znewlinklist();
+
 	    ca_inactive(d, state.curopt->xor);
 
+	    /* Collect the argument strings. Maybe. */
+
 	    if (state.def &&
 		(state.curopt->type == CAO_DIRECT ||
 		 (state.curopt->type == CAO_ODIRECT && pe[0]) ||
@@ -996,27 +1158,32 @@ ca_parse_line(Cadef d)
 		    state.def->type != CAA_RARGS &&
 		    state.def->type != CAA_RREST)
 		    state.def = state.def->next;
-		PERMALLOC {
-		    addlinknode(state.oargs[state.curopt->num], ztrdup(pe));
-		} LASTALLOC;
+
+		zaddlinknode(state.oargs[state.curopt->num], ztrdup(pe));
 	    }
-	    if (!state.def)
+	    if (state.def)
+		state.opt = 0;
+	    else {
+		if (!d->single || (state.curopt->name[1] && state.curopt->name[2]))
+		    wasopt = state.curopt;
 		state.curopt = NULL;
-	} else if (state.opt && d->single &&
+	    }
+	} else if (state.opt == 2 && d->single &&
 		   (state.curopt = ca_get_sopt(d, line, 0, &pe))) {
+	    /* Or maybe it's a single-letter option? */
+
 	    char *p;
 	    Caopt tmpopt;
 
 	    ddef = state.def = state.curopt->args;
 	    doff = pe - line;
 	    state.optbeg = state.argbeg = state.inopt = cur;
-	    state.singles = !*pe;
+	    state.singles = (!pe || !*pe);
 
-	    for (p = line + 1; p <= pe; p++) {
+	    for (p = line + 1; p < pe; p++) {
 		if ((tmpopt = d->single[STOUC(*p)])) {
-		    PERMALLOC {
-			state.oargs[tmpopt->num] = newlinklist();
-		    } LASTALLOC;
+		    state.oargs[tmpopt->num] = znewlinklist();
+
 		    ca_inactive(d, tmpopt->xor);
 		}
 	    }
@@ -1029,31 +1196,40 @@ ca_parse_line(Cadef d)
 		    state.def->type != CAA_RARGS &&
 		    state.def->type != CAA_RREST)
 		    state.def = state.def->next;
-		PERMALLOC {
-		    addlinknode(state.oargs[state.curopt->num], ztrdup(pe));
-		} LASTALLOC;
+
+		zaddlinknode(state.oargs[state.curopt->num], ztrdup(pe));
 	    }
-	    if (!state.def)
+	    if (state.def)
+		state.opt = 0;
+	    else
 		state.curopt = NULL;
 	} else if (state.arg) {
-	    PERMALLOC {
-		addlinknode(state.args, ztrdup(line));
-	    } LASTALLOC;
+	    /* Otherwise it's a normal argument. */
+	    if (state.inopt) {
+		state.inopt = 0;
+		state.nargbeg = cur - 1;
+	    }
 	    if ((adef = state.def = ca_get_arg(d, state.nth)) &&
 		(state.def->type == CAA_RREST ||
 		 state.def->type == CAA_RARGS)) {
 		state.inrest = 0;
-		for (; line; line = compwords[cur++]) {
-		    PERMALLOC {
-			addlinknode(state.args, ztrdup(line));
-		    } LASTALLOC;
-		}
+		state.opt = (cur == state.nargbeg + 1);
+		state.optbeg = state.nargbeg;
+		state.argbeg = cur - 1;
+
+		for (; line; line = compwords[cur++])
+		    zaddlinknode(state.args, ztrdup(line));
+
+		memcpy(&ca_laststate, &state, sizeof(state));
+		ca_laststate.ddef = NULL;
+		ca_laststate.doff = 0;
 		break;
 	    }
-	    if (state.inopt) {
-		state.inopt = 0;
-		state.nargbeg = cur - 1;
-	    }
+	    zaddlinknode(state.args, ztrdup(line));
+
+	    if (state.def)
+		argxor = state.def->xor;
+
 	    if (state.def && state.def->type != CAA_NORMAL &&
 		state.def->type != CAA_OPT && state.inarg) {
 		state.restbeg = cur;
@@ -1064,6 +1240,8 @@ ca_parse_line(Cadef d)
 	    state.nth++;
 	    state.def = NULL;
 	}
+	/* Do the end-pattern test if needed. */
+
 	if (state.def && state.curopt &&
 	    (state.def->type == CAA_RREST || state.def->type == CAA_RARGS)) {
 	    if (state.def->end)
@@ -1071,52 +1249,77 @@ ca_parse_line(Cadef d)
 	    else {
 		LinkList l = state.oargs[state.curopt->num];
 
-		for (; line; line = compwords[cur++]) {
-		    PERMALLOC {
-			addlinknode(l, line);
-		    } LASTALLOC;
-		}
+		if (cur < compcurrent)
+		    memcpy(&ca_laststate, &state, sizeof(state));
+
+		for (; line; line = compwords[cur++])
+		    zaddlinknode(l, ztrdup(line));
+
+		ca_laststate.ddef = NULL;
+		ca_laststate.doff = 0;
 		break;
 	    }
-	}
+	} else if (state.def && state.def->end)
+	    endpat = patcompile(state.def->end, 0, NULL);
+
+	/* Copy the state into the global one. */
+
 	if (cur + 1 == compcurrent) {
 	    memcpy(&ca_laststate, &state, sizeof(state));
 	    ca_laststate.ddef = NULL;
 	    ca_laststate.doff = 0;
 	} else if (cur == compcurrent && !ca_laststate.def) {
-	    if ((ca_laststate.def = ddef))
-		ca_laststate.doff = doff;
-	    else {
+	    if ((ca_laststate.def = ddef)) {
+		ca_laststate.singles = state.singles;
+		if (state.curopt && state.curopt->type == CAO_NEXT) {
+		    ca_laststate.ddef = ddef;
+		    ca_laststate.def = NULL;
+		    ca_laststate.opt = 1;
+		    state.curopt->active = 1;
+		} else {
+		    ca_laststate.doff = doff;
+		    ca_laststate.opt = 0;
+		}
+	    } else {
 		ca_laststate.def = adef;
 		ca_laststate.ddef = NULL;
-		ca_laststate.argbeg = state.nargbeg;
-		ca_laststate.optbeg = state.restbeg;
+		ca_laststate.optbeg = state.nargbeg;
+		ca_laststate.argbeg = state.restbeg;
 		ca_laststate.singles = state.singles;
+		if (wasopt)
+		    wasopt->active = 1;
 	    }
 	}
     }
 }
 
+/* Build a colon-list from a list. */
+
 static char *
 ca_colonlist(LinkList l)
 {
     if (l) {
 	LinkNode n;
-	int len = 1;
+	int len = 0;
 	char *p, *ret, *q;
 
-	for (n = firstnode(l); n; incnode(n))
+	for (n = firstnode(l); n; incnode(n)) {
+	    len++;
 	    for (p = (char *) getdata(n); *p; p++)
 		len += (*p == ':' ? 2 : 1);
-
+	}
 	ret = q = (char *) zalloc(len);
 
-	for (n = firstnode(l); n; incnode(n))
+	for (n = firstnode(l); n;) {
 	    for (p = (char *) getdata(n); *p; p++) {
 		if (*p == ':')
 		    *q++ = '\\';
 		*q++ = *p;
 	    }
+	    incnode(n);
+	    if (n)
+		*q++ = ':';
+	}
 	*q = '\0';
 
 	return ret;
@@ -1130,36 +1333,37 @@ bin_comparguments(char *nam, char **args, char *ops, int func)
     int min, max, n;
 
     if (incompfunc != 1) {
-	zerrnam(nam, "can only be called from completion function", NULL, 0);
+	zwarnnam(nam, "can only be called from completion function", NULL, 0);
 	return 1;
     }
     if (args[0][0] != '-' || !args[0][1] || args[0][2]) {
-	zerrnam(nam, "invalid argument: %s", args[0], 0);
+	zwarnnam(nam, "invalid argument: %s", args[0], 0);
 	return 1;
     }
     if (args[0][1] != 'i' && !ca_parsed) {
-	zerrnam(nam, "no parsed state", NULL, 0);
+	zwarnnam(nam, "no parsed state", NULL, 0);
 	return 1;
     }
     switch (args[0][1]) {
     case 'i': min = 2; max = -1; break;
     case 'D': min = 2; max =  2; break;
+    case 'C': min = 1; max =  1; break;
     case 'O': min = 4; max =  4; break;
-    case 'L': min = 3; max =  3; break;
+    case 'L': min = 3; max =  4; break;
     case 's': min = 1; max =  1; break;
     case 'M': min = 1; max =  1; break;
     case 'a': min = 0; max =  0; break;
     case 'W': min = 2; max =  2; break;
     default:
-	zerrnam(nam, "invalid option: %s", args[0], 0);
+	zwarnnam(nam, "invalid option: %s", args[0], 0);
 	return 1;
     }
     n = arrlen(args) - 1;
     if (n < min) {
-	zerrnam(nam, "not enough arguments", NULL, 0);
+	zwarnnam(nam, "not enough arguments", NULL, 0);
 	return 1;
     } else if (max >= 0 && n > max) {
-	zerrnam(nam, "too many arguments", NULL, 0);
+	zwarnnam(nam, "too many arguments", NULL, 0);
 	return 1;
     }
     switch (args[0][1]) {
@@ -1189,19 +1393,45 @@ bin_comparguments(char *nam, char **args, char *ops, int func)
 		setsparam(args[1], ztrdup(arg->descr));
 		setsparam(args[2], ztrdup(arg->action));
 
-		ignore_prefix(ca_laststate.doff);
+		if (ca_laststate.doff > 0)
+		    ignore_prefix(ca_laststate.doff);
 		if (arg->type == CAA_RARGS)
-		    restrict_range(ca_laststate.argbeg - 1,
+		    restrict_range(ca_laststate.optbeg,
 				   arrlen(compwords) - 1);
 		else if (arg->type == CAA_RREST)
-		    restrict_range(ca_laststate.optbeg - 1,
+		    restrict_range(ca_laststate.argbeg,
 				   arrlen(compwords) - 1);
 		return 0;
 	    }
 	    return 1;
 	}
+    case 'C':
+	{
+	    Caarg arg = ca_laststate.def;
+
+	    if (arg) {
+		char buf[20];
+
+		if (arg->num > 0)
+		    sprintf(buf, "%d", arg->num);
+		else
+		    strcpy(buf, "rest");
+
+		setsparam(args[1], (arg->opt ? tricat(arg->opt, "-", buf) :
+				    tricat("argument-", buf, "")));
+		return 0;
+	    }
+	    return 1;
+	}
     case 'O':
-	if (ca_laststate.opt) {
+	if ((ca_laststate.opt || (ca_laststate.doff && ca_laststate.def) ||
+	     (ca_laststate.def &&
+	      (ca_laststate.def->type == CAA_OPT ||
+	       ca_laststate.def->type >= CAA_RARGS))) &&
+	    (!ca_laststate.def || ca_laststate.def->type < CAA_RARGS ||
+	     (ca_laststate.def->type == CAA_RARGS ?
+	      (ca_laststate.curpos == ca_laststate.argbeg + 1) :
+	      (compcurrent == 1)))) {
 	    LinkList next = newlinklist();
 	    LinkList direct = newlinklist();
 	    LinkList odirect = newlinklist();
@@ -1218,14 +1448,15 @@ bin_comparguments(char *nam, char **args, char *ops, int func)
 		    default:          l = equal;   break;
 		    }
 		    if (p->descr) {
-			int len = strlen(p->name) + strlen(p->descr) + 2;
+			char *n = bslashcolon(p->name);
+			int len = strlen(n) + strlen(p->descr) + 2;
 
 			str = (char *) zhalloc(len);
-			strcpy(str, p->name);
+			strcpy(str, n);
 			strcat(str, ":");
 			strcat(str, p->descr);
 		    } else
-			str = p->name;
+			str = bslashcolon(p->name);
 		    addlinknode(l, str);
 		}
 	    }
@@ -1235,8 +1466,8 @@ bin_comparguments(char *nam, char **args, char *ops, int func)
 	    set_list_array(args[4], equal);
 
 	    return 0;
-	} else
-	    return 1;
+	}
+	return 1;
     case 'L':
 	{
 	    Caopt opt = ca_get_opt(ca_laststate.d, args[1], 1, NULL);
@@ -1245,12 +1476,16 @@ bin_comparguments(char *nam, char **args, char *ops, int func)
 		setsparam(args[2], ztrdup(opt->args->descr));
 		setsparam(args[3], ztrdup(opt->args->action));
 
+		if (args[4])
+		    setsparam(args[4], tricat(opt->name, "-1", ""));
+
 		return 0;
 	    }
 	    return 1;
 	}
     case 's':
-	if (ca_laststate.d->single && ca_laststate.singles) {
+	if (ca_laststate.d->single && ca_laststate.singles &&
+	    ca_laststate.opt) {
 	    setsparam(args[1],
 		      ztrdup(ca_laststate.ddef ?
 			     (ca_laststate.ddef->type == CAO_DIRECT ?
@@ -1258,8 +1493,8 @@ bin_comparguments(char *nam, char **args, char *ops, int func)
 			      (ca_laststate.ddef->type == CAO_EQUAL ?
 			       "equal" : "next")) : ""));
 	    return 0;
-	} else
-	    return 1;
+	}
+	return 1;
     case 'M':
 	setsparam(args[1], ztrdup(ca_laststate.d->match));
 	return 0;
@@ -1310,67 +1545,79 @@ bin_comparguments(char *nam, char **args, char *ops, int func)
 typedef struct cvdef *Cvdef;
 typedef struct cvval *Cvval;
 
+/* Definitions for _values. */
+
 struct cvdef {
-    char *descr;
-    int hassep;
-    char sep;
-    Cvdef next;
-    Cvval vals;
-    char **defs;
-    int ndefs;
-    int lastt;
+    char *descr;		/* global description */
+    int hassep;			/* multiple values allowed */
+    char sep;			/* separator character */
+    Cvdef next;			/* next in cache */
+    Cvval vals;			/* value definitions */
+    char **defs;		/* original strings */
+    int ndefs;			/* number of ... */
+    int lastt;			/* last time used */
 };
 
+/* One value definition. */
+
 struct cvval {
     Cvval next;
-    char *name;
-    char *descr;
-    char **xor;
-    int type;
-    Caarg arg;
-    int active;
+    char *name;			/* value name */
+    char *descr;		/* description */
+    char **xor;			/* xor-list */
+    int type;			/* CVV_* below */
+    Caarg arg;			/* argument definition */
+    int active;			/* still allowed */
 };
 
 #define CVV_NOARG 0
 #define CVV_ARG   1
 #define CVV_OPT   2
 
+/* Cache. */
+
 #define MAX_CVCACHE 8
 static Cvdef cvdef_cache[MAX_CVCACHE];
 
+/* Memory stuff. */
+
 static void
-free_cvdef(Cvdef d)
+freecvdef(Cvdef d)
 {
     if (d) {
 	Cvval p, n;
 
 	zsfree(d->descr);
-	freearray(d->defs);
+	if (d->defs)
+	    freearray(d->defs);
 
 	for (p = d->vals; p; p = n) {
 	    n = p->next;
 	    zsfree(p->name);
 	    zsfree(p->descr);
-	    freearray(p->xor);
-	    free_caargs(p->arg);
+	    if (p->xor)
+		freearray(p->xor);
+	    freecaargs(p->arg);
 	    zfree(p, sizeof(*p));
 	}
 	zfree(d, sizeof(*d));
     }
 }
 
+/* Parse option definitions. */
+
 static Cvdef
 parse_cvdef(char *nam, char **args)
 {
     Cvdef ret;
     Cvval val, *valp;
     Caarg arg;
-    char **oargs = args, sep, *name, *descr, *p, *q, **xor, c;
-    int xnum, multi, vtype, hassep;
+    char **oargs = args, sep = '\0', *name, *descr, *p, *q, **xor, c;
+    int xnum, multi, vtype, hassep = 0;
 
     if (args[0][0] == '-' && args[0][1] == 's' && !args[0][2]) {
 	if (args[1][0] && args[1][1]) {
-	    zerrnam(nam, "invalid separator: %s", args[1], 0);
+	    zwarnnam(nam, "invalid separator: %s", args[1], 0);
 	    return NULL;
 	}
 	hassep = 1;
@@ -1378,26 +1625,26 @@ parse_cvdef(char *nam, char **args)
 	args += 2;
     }
     if (!args[0] || !args[1]) {
-	zerrnam(nam, "not enough arguments", NULL, 0);
+	zwarnnam(nam, "not enough arguments", NULL, 0);
 	return NULL;
     }
     descr = *args++;
 
-    PERMALLOC {
-	ret = (Cvdef) zalloc(sizeof(*ret));
-	ret->descr = ztrdup(descr);
-	ret->hassep = hassep;
-	ret->sep = sep;
-	ret->next = NULL;
-	ret->vals = NULL;
-	ret->defs = arrdup(oargs);
-	ret->ndefs = arrlen(oargs);
-	ret->lastt = time(0);
-    } LASTALLOC;
+    ret = (Cvdef) zalloc(sizeof(*ret));
+    ret->descr = ztrdup(descr);
+    ret->hassep = hassep;
+    ret->sep = sep;
+    ret->next = NULL;
+    ret->vals = NULL;
+    ret->defs = zarrdup(oargs);
+    ret->ndefs = arrlen(oargs);
+    ret->lastt = time(0);
 
     for (valp = &(ret->vals); *args; args++) {
 	p = dupstring(*args);
 	xnum = 0;
+
+	/* xor list? */
 	if (*p == '(') {
 	    LinkList list = newlinklist();
 	    LinkNode node;
@@ -1420,8 +1667,8 @@ parse_cvdef(char *nam, char **args)
 		*p = sav;
 	    }
 	    if (*p != ')') {
-		free_cvdef(ret);
-		zerrnam(nam, "invalid argument: %s", *args, 0);
+		freecvdef(ret);
+		zwarnnam(nam, "invalid argument: %s", *args, 0);
 		return NULL;
 	    }
 	    xor = (char **) zalloc((xnum + 2) * sizeof(char *));
@@ -1433,18 +1680,23 @@ parse_cvdef(char *nam, char **args)
 	} else
 	    xor = NULL;
 
+	/* More than once allowed? */
 	if ((multi = (*p == '*')))
 	    p++;
 
+	/* Skip option name. */
+
 	for (name = p; *p && *p != ':' && *p != '['; p++)
 	    if (*p == '\\' && p[1])
 		p++;
 
 	if (hassep && !sep && name + 1 != p) {
-	    free_cvdef(ret);
-	    zerrnam(nam, "no multi-letter values with empty separator allowed", NULL, 0);
+	    freecvdef(ret);
+	    zwarnnam(nam, "no multi-letter values with empty separator allowed", NULL, 0);
 	    return NULL;
 	}
+	/* Optional description? */
+
 	if ((c = *p) == '[') {
 	    *p = '\0';
 	    for (descr = ++p; *p && *p != ']'; p++)
@@ -1452,8 +1704,8 @@ parse_cvdef(char *nam, char **args)
 		    p++;
 
 	    if (!*p) {
-		free_cvdef(ret);
-		zerrnam(nam, "invalid value definition: %s", *args, 0);
+		freecvdef(ret);
+		zwarnnam(nam, "invalid value definition: %s", *args, 0);
 		return NULL;
 	    }
 	    *p++ = '\0';
@@ -1463,8 +1715,8 @@ parse_cvdef(char *nam, char **args)
 	    descr = NULL;
 	}
 	if (c && c != ':') {
-	    free_cvdef(ret);
-	    zerrnam(nam, "invalid value definition: %s", *args, 0);
+	    freecvdef(ret);
+	    zwarnnam(nam, "invalid value definition: %s", *args, 0);
 	    return NULL;
 	}
 	if (!multi) {
@@ -1474,10 +1726,12 @@ parse_cvdef(char *nam, char **args)
 	    }
 	    xor[xnum] = ztrdup(name);
 	}
+	/* Get argument? */
+
 	if (c == ':') {
 	    if (hassep && !sep) {
-		free_cvdef(ret);
-		zerrnam(nam, "no value with argument with empty separator allowed", NULL, 0);
+		freecvdef(ret);
+		zwarnnam(nam, "no value with argument with empty separator allowed", NULL, 0);
 		return NULL;
 	    }
 	    if (*++p == ':') {
@@ -1485,26 +1739,26 @@ parse_cvdef(char *nam, char **args)
 		vtype = CVV_OPT;
 	    } else
 		vtype = CVV_ARG;
-	    arg = parse_caarg(0, 0, 0, &p);
+	    arg = parse_caarg(0, 0, 0, name, &p);
 	} else {
 	    vtype = CVV_NOARG;
 	    arg = NULL;
 	}
-	PERMALLOC {
-	    *valp = val = (Cvval) zalloc(sizeof(*val));
-	    valp = &((*valp)->next);
-
-	    val->next = NULL;
-	    val->name = ztrdup(name);
-	    val->descr = ztrdup(descr);
-	    val->xor = xor;
-	    val->type = vtype;
-	    val->arg = arg;
-	} LASTALLOC;
+	*valp = val = (Cvval) zalloc(sizeof(*val));
+	valp = &((*valp)->next);
+
+	val->next = NULL;
+	val->name = ztrdup(name);
+	val->descr = ztrdup(descr);
+	val->xor = xor;
+	val->type = vtype;
+	val->arg = arg;
     }
     return ret;
 }
 
+/* Get the definition from the cache or newly built. */
+
 static Cvdef
 get_cvdef(char *nam, char **args)
 {
@@ -1521,12 +1775,14 @@ get_cvdef(char *nam, char **args)
     if (i)
 	min = p;
     if ((new = parse_cvdef(nam, args))) {
-	free_cvdef(*min);
+	freecvdef(*min);
 	*min = new;
     }
     return new;
 }
 
+/* Get the definition for a value. */
+
 static Cvval
 cv_get_val(Cvdef d, char *name)
 {
@@ -1539,6 +1795,8 @@ cv_get_val(Cvdef d, char *name)
     return NULL;
 }
 
+/* Handle a xor list. */
+
 static void
 cv_inactive(Cvdef d, char **xor)
 {
@@ -1551,6 +1809,8 @@ cv_inactive(Cvdef d, char **xor)
     }
 }
 
+/* Parse state. */
+
 struct cvstate {
     Cvdef d;
     Caarg def;
@@ -1561,6 +1821,8 @@ struct cvstate {
 static struct cvstate cv_laststate;
 static int cv_parsed = 0, cv_alloced = 0;
 
+/* Parse the current word. */
+
 static void
 cv_parse_word(Cvdef d)
 {
@@ -1577,9 +1839,8 @@ cv_parse_word(Cvdef d)
     state.d = d;
     state.def = NULL;
     state.val = NULL;
-    PERMALLOC {
-	state.vals = (LinkList) newlinklist();
-    } LASTALLOC;
+    state.vals = (LinkList) znewlinklist();
+
     cv_alloced = 1;
 
     if (d->hassep) {
@@ -1596,10 +1857,8 @@ cv_parse_word(Cvdef d)
 		    eq = "";
 
 		if ((ptr = cv_get_val(d, str))) {
-		    PERMALLOC {
-			addlinknode(state.vals, ztrdup(str));
-			addlinknode(state.vals, ztrdup(eq));
-		    } LASTALLOC;
+		    zaddlinknode(state.vals, ztrdup(str));
+		    zaddlinknode(state.vals, ztrdup(eq));
 
 		    cv_inactive(d, ptr->xor);
 		}
@@ -1625,10 +1884,8 @@ cv_parse_word(Cvdef d)
 			eq = "";
 
 		    if ((ptr = cv_get_val(d, str))) {
-			PERMALLOC {
-			    addlinknode(state.vals, ztrdup(str));
-			    addlinknode(state.vals, ztrdup(eq));
-			} LASTALLOC;
+			zaddlinknode(state.vals, ztrdup(str));
+			zaddlinknode(state.vals, ztrdup(eq));
 
 			cv_inactive(d, ptr->xor);
 		    }
@@ -1647,10 +1904,8 @@ cv_parse_word(Cvdef d)
 	    for (str = compprefix; *str; str++) {
 		tmp[0] = *str;
 		if ((ptr = cv_get_val(d, tmp))) {
-		    PERMALLOC {
-			addlinknode(state.vals, ztrdup(tmp));
-			addlinknode(state.vals, ztrdup(""));
-		    } LASTALLOC;
+		    zaddlinknode(state.vals, ztrdup(tmp));
+		    zaddlinknode(state.vals, ztrdup(""));
 
 		    cv_inactive(d, ptr->xor);
 		}
@@ -1658,10 +1913,8 @@ cv_parse_word(Cvdef d)
 	    for (str = compsuffix; *str; str++) {
 		tmp[0] = *str;
 		if ((ptr = cv_get_val(d, tmp))) {
-		    PERMALLOC {
-			addlinknode(state.vals, ztrdup(tmp));
-			addlinknode(state.vals, ztrdup(""));
-		    } LASTALLOC;
+		    zaddlinknode(state.vals, ztrdup(tmp));
+		    zaddlinknode(state.vals, ztrdup(""));
 
 		    cv_inactive(d, ptr->xor);
 		}
@@ -1696,35 +1949,36 @@ bin_compvalues(char *nam, char **args, char *ops, int func)
     int min, max, n;
 
     if (incompfunc != 1) {
-	zerrnam(nam, "can only be called from completion function", NULL, 0);
+	zwarnnam(nam, "can only be called from completion function", NULL, 0);
 	return 1;
     }
     if (args[0][0] != '-' || !args[0][1] || args[0][2]) {
-	zerrnam(nam, "invalid argument: %s", args[0], 0);
+	zwarnnam(nam, "invalid argument: %s", args[0], 0);
 	return 1;
     }
     if (args[0][1] != 'i' && !cv_parsed) {
-	zerrnam(nam, "no parsed state", NULL, 0);
+	zwarnnam(nam, "no parsed state", NULL, 0);
 	return 1;
     }
     switch (args[0][1]) {
     case 'i': min = 2; max = -1; break;
     case 'D': min = 2; max =  2; break;
+    case 'C': min = 1; max =  1; break;
     case 'V': min = 3; max =  3; break;
     case 's': min = 1; max =  1; break;
     case 'd': min = 1; max =  1; break;
-    case 'L': min = 3; max =  3; break;
+    case 'L': min = 3; max =  4; break;
     case 'v': min = 1; max =  1; break;
     default:
-	zerrnam(nam, "invalid option: %s", args[0], 0);
+	zwarnnam(nam, "invalid option: %s", args[0], 0);
 	return 1;
     }
     n = arrlen(args) - 1;
     if (n < min) {
-	zerrnam(nam, "not enough arguments", NULL, 0);
+	zwarnnam(nam, "not enough arguments", NULL, 0);
 	return 1;
     } else if (max >= 0 && n > max) {
-	zerrnam(nam, "too many arguments", NULL, 0);
+	zwarnnam(nam, "too many arguments", NULL, 0);
 	return 1;
     }
     switch (args[0][1]) {
@@ -1758,6 +2012,17 @@ bin_compvalues(char *nam, char **args, char *ops, int func)
 	    }
 	    return 1;
 	}
+    case 'C':
+	{
+	    Caarg arg = cv_laststate.def;
+
+	    if (arg) {
+		setsparam(args[1], ztrdup(arg->opt));
+
+		return 0;
+	    }
+	    return 1;
+	}
     case 'V':
 	{
 	    LinkList noarg = newlinklist();
@@ -1813,6 +2078,9 @@ bin_compvalues(char *nam, char **args, char *ops, int func)
 		setsparam(args[2], val->arg->descr);
 		setsparam(args[3], val->arg->action);
 
+		if (args[4])
+		    setsparam(args[4], ztrdup(val->name));
+
 		return 0;
 	    }
 	    return 1;
@@ -1838,37 +2106,508 @@ bin_compvalues(char *nam, char **args, char *ops, int func)
     return 1;
 }
 
+static int
+bin_compquote(char *nam, char **args, char *ops, int func)
+{
+    char *name;
+    struct value vbuf;
+    Value v;
+
+    /* Anything to do? */
+
+    if (!compqstack || !*compqstack)
+	return 0;
+
+    /* For all parameters given... */
+
+    while ((name = *args++)) {
+	name = dupstring(name);
+	if ((v = getvalue(&vbuf, &name, 0))) {
+	    switch (PM_TYPE(v->pm->flags)) {
+	    case PM_SCALAR:
+		{
+		    char *val = getstrvalue(v);
+
+		    val = bslashquote(val, NULL,
+				      (*compqstack == '\'' ? 1 :
+				       (*compqstack == '"' ? 2 : 0)));
+
+		    setstrvalue(v, ztrdup(val));
+		}
+		break;
+	    case PM_ARRAY:
+		{
+		    char **val = v->pm->gets.afn(v->pm);
+		    char **new = (char **) zalloc((arrlen(val) + 1) *
+						  sizeof(char *));
+		    char **p = new;
+
+		    for (; *val; val++, p++)
+			*p = ztrdup(bslashquote(*val, NULL,
+						(*compqstack == '\'' ? 1 :
+						 (*compqstack == '"' ? 2 :
+						  0))));
+		    *p = NULL;
+
+		    setarrvalue(v, new);
+		}
+		break;
+	    default:
+		zwarnnam(nam, "invalid parameter type: %s", args[-1], 0);
+	    }
+	} else
+	    zwarnnam(nam, "unknown parameter: %s", args[-1], 0);
+    }
+    return 0;
+}
+
+/* Tags stuff. */
+
+typedef struct ctags *Ctags;
+typedef struct ctset *Ctset;
+
+/* A bunch of tag sets. */
+
+struct ctags {
+    char **all;			/* all tags offered */
+    char *context;		/* the current context */
+    int init;			/* not yet used */
+    Ctset sets;			/* the tag sets */
+};
+
+/* A tag set. */
+
+struct ctset {
+    Ctset next;
+    char **tags;		/* the tags */
+    char *tag;			/* last tag checked for -A */
+    char **ptr;			/* ptr into tags for -A */
+};
+
+/* Array of tag-set infos. Index is the locallevel. */
+
+#define MAX_TAGS 256
+static Ctags comptags[MAX_TAGS];
+
+/* locallevel at last comptags -i */
+
+static int lasttaglevel;
+
+static void
+freectset(Ctset s)
+{
+    Ctset n;
+
+    while (s) {
+	n = s->next;
+
+	if (s->tags)
+	    freearray(s->tags);
+	zsfree(s->tag);
+	zfree(s, sizeof(*s));
+
+	s = n;
+    }
+}
+
+static void
+freectags(Ctags t)
+{
+    if (t) {
+	if (t->all)
+	    freearray(t->all);
+	zsfree(t->context);
+	freectset(t->sets);
+	zfree(t, sizeof(*t));
+    }
+}
+
+/* Set the tags for the current local level. */
+
+static void
+settags(int level, char **tags)
+{
+    Ctags t;
+
+    if (comptags[level])
+	freectags(comptags[level]);
+
+    comptags[level] = t = (Ctags) zalloc(sizeof(*t));
+
+    t->all = zarrdup(tags + 1);
+    t->context = ztrdup(*tags);
+    t->sets = NULL;
+    t->init = 1;
+}
+
+/* Check if an array contains a string. */
+
+static int
+arrcontains(char **a, char *s, int colon)
+{
+    char *p, *q;
+
+    while (*a) {
+	if (colon) {
+	    for (p = s, q = *a++; *p && *q && *p != ':' && *q != ':'; p++, q++)
+		if (*p != *q)
+		    break;
+	    if ((!*p || *p == ':') && (!*q || *q == ':'))
+		return 1;
+	} else if (!strcmp(*a++, s))
+	    return 1;
+    }
+    return 0;
+}
+
+static int
+bin_comptags(char *nam, char **args, char *ops, int func)
+{
+    int min, max, n, level;
+
+    if (incompfunc != 1) {
+	zwarnnam(nam, "can only be called from completion function", NULL, 0);
+	return 1;
+    }
+    if (args[0][0] != '-' || !args[0][1] ||
+	(args[0][2] && (args[0][2] != '-' || args[0][3]))) {
+	zwarnnam(nam, "invalid argument: %s", args[0], 0);
+	return 1;
+    }
+    level = locallevel - (args[0][2] ? 1 : 0);
+    if (level >= MAX_TAGS) {
+	zwarnnam(nam, "nesting level too deep", NULL, 0);
+	return 1;
+    }
+    if (args[0][1] != 'i' && args[0][1] != 'I' && !comptags[level]) {
+	zwarnnam(nam, "no tags registered", NULL, 0);
+	return 1;
+    }
+    switch (args[0][1]) {
+    case 'i': min = 2; max = -1; break;
+    case 'C': min = 1; max =  1; break;
+    case 'T': min = 0; max =  0; break;
+    case 'N': min = 0; max =  0; break;
+    case 'R': min = 1; max =  1; break;
+    case 'S': min = 1; max =  1; break;
+    case 'A': min = 2; max =  2; break;
+    default:
+	zwarnnam(nam, "invalid option: %s", args[0], 0);
+	return 1;
+    }
+    n = arrlen(args) - 1;
+    if (n < min) {
+	zwarnnam(nam, "not enough arguments", NULL, 0);
+	return 1;
+    } else if (max >= 0 && n > max) {
+	zwarnnam(nam, "too many arguments", NULL, 0);
+	return 1;
+    }
+    switch (args[0][1]) {
+    case 'i':
+	settags(level, args + 1);
+	lasttaglevel = level;
+	break;
+    case 'C':
+	setsparam(args[1], ztrdup(comptags[level]->context));
+	break;
+    case 'T':
+	return !comptags[level]->sets;
+    case 'N':
+	{
+	    Ctset s;
+
+	    if (comptags[level]->init)
+		comptags[level]->init = 0;
+	    else if ((s = comptags[level]->sets)) {
+		comptags[level]->sets = s->next;
+		s->next = NULL;
+		freectset(s);
+	    }
+	    return !comptags[level]->sets;
+	}
+    case 'R':
+	{
+	    Ctset s;
+
+	    return !((s = comptags[level]->sets) &&
+		     arrcontains(s->tags, args[1], 1));
+	}
+    case 'A':
+	{
+	    Ctset s;
+
+	    if (comptags[level] && (s = comptags[level]->sets)) {
+		char **q, *v = NULL;
+		int l = strlen(args[1]);
+
+		if (!s->tag || strcmp(s->tag, args[1])) {
+		    zsfree(s->tag);
+		    s->tag = ztrdup(args[1]);
+		    s->ptr = s->tags;
+		}
+		for (q = s->ptr; *q; q++) {
+		    if (strpfx(args[1], *q)) {
+			if (!(*q)[l]) {
+			    v = *q;
+			    break;
+			} else if ((*q)[l] == ':') {
+			    v = (*q) + l + 1;
+			    break;
+			}
+		    }
+		}
+		if (!v) {
+		    zsfree(s->tag);
+		    s->tag = NULL;
+		    return 1;
+		}
+		s->ptr = q + 1;
+		setsparam(args[2], ztrdup(*v == '-' ? dyncat(args[1], v) : v));
+		return 0;
+	    }
+	    return 1;
+	}
+    case 'S':
+	if (comptags[level]->sets) {
+	    char **ret;
+
+	    ret = zarrdup(comptags[level]->sets->tags);
+	    setaparam(args[1], ret);
+	} else
+	    return 1;
+
+	break;
+    }
+    return 0;
+}
+
+static int
+bin_comptry(char *nam, char **args, char *ops, int func)
+{
+    if (incompfunc != 1) {
+	zwarnnam(nam, "can only be called from completion function", NULL, 0);
+	return 1;
+    }
+    if (!lasttaglevel || !comptags[lasttaglevel]) {
+	zwarnnam(nam, "no tags registered", NULL, 0);
+	return 1;
+    }
+    if (*args) {
+	if (!strcmp(*args, "-m")) {
+	    char *s, *p, *q, *c, **all = comptags[lasttaglevel]->all;
+	    LinkList list = newlinklist();
+	    LinkNode node;
+	    int num = 0;
+	    Ctset set;
+
+	    while ((s = *++args)) {
+		while (*s) {
+		    while (*s && iblank(*s))
+			s++;
+		    for (p = q = s, c = NULL; *s && !iblank(*s); s++) {
+			if (!c && *s == ':')
+			    c = p;
+			if (*s == '\\' && s[1])
+			    s++;
+			*p++ = *s;
+		    }
+		    if (*s)
+			s++;
+		    *p = '\0';
+		    if (*q) {
+			char *qq = dupstring(q);
+			if (c)
+			    *c = '\0';
+
+			tokenize(qq);
+			if (haswilds(qq)) {
+			    Patprog prog;
+
+			    if ((prog = patcompile(qq, PAT_STATIC, NULL))) {
+				char **a, *n;
+				int l = (c ? strlen(c + 1) + 2 : 1), al;
+
+				for (a = all; *a; a++) {
+				    if (pattry(prog, *a)) {
+					n = (char *) zhalloc((al = strlen(*a)) + l);
+					strcpy(n, *a);
+					if (c) {
+					    n[al] = ':';
+					    strcpy(n + al + 1, c + 1);
+					}
+					addlinknode(list, n);
+					num++;
+				    }
+				}
+			    }
+			} else if (arrcontains(all, q, 0)) {
+			    for (set = comptags[lasttaglevel]->sets; set;
+				 set = set->next)
+				if (arrcontains(set->tags, q, 0))
+				    break;
+			    if (!set) {
+				addlinknode(list, q);
+				num++;
+			    }
+			}
+			if (c)
+			    *c = ':';
+		    }
+		}
+		if (num) {
+		    char **a;
+		    Ctset l;
+
+		    set = (Ctset) zalloc(sizeof(*set));
+
+		    a = set->tags = (char **) zalloc((num + 1) * sizeof(char *));
+		    for (node = firstnode(list); node; incnode(node))
+			*a++ = ztrdup((char *) getdata(node));
+
+		    *a = NULL;
+		    set->next = NULL;
+		    set->ptr = NULL;
+		    set->tag = NULL;
+
+		    if ((l = comptags[lasttaglevel]->sets)) {
+			while (l->next)
+			    l = l->next;
+
+			l->next = set;
+		    } else
+			comptags[lasttaglevel]->sets = set;
+		}
+	    }
+	} else {
+	    char **p, **q, **all;
+	    int sep = 0;
+
+	    if ((sep = !strcmp(*args, "-s")))
+		args++;
+
+	    for (p = q = args, all = comptags[lasttaglevel]->all; *p; p++)
+		if (arrcontains(all, *p, 1)) {
+		    Ctset s;
+
+		    for (s = comptags[lasttaglevel]->sets; s; s = s->next)
+			if (arrcontains(s->tags, *p, 0))
+			    break;
+
+		    if (!s)
+			*q++ = *p;
+		}
+	    *q = NULL;
+
+	    if (*args) {
+		char *dummy[2];
+
+		do {
+		    Ctset s = (Ctset) zalloc(sizeof(*s)), l;
+
+		    if (sep) {
+			dummy[0] = *args++;
+			dummy[1] = NULL;
+			s->tags = zarrdup(dummy);
+		    } else
+			s->tags = zarrdup(args);
+		    s->next = NULL;
+		    s->ptr = NULL;
+		    s->tag = NULL;
+
+		    if ((l = comptags[lasttaglevel]->sets)) {
+			while (l->next)
+			    l = l->next;
+
+			l->next = s;
+		    } else
+			comptags[lasttaglevel]->sets = s;
+		} while (sep && *args);
+	    }
+	}
+    }
+    return 0;
+}
+
+static char *
+fmtstr(char *str, char c, char *repl)
+{
+    int len, num, rlen;
+    char *s, *ret, *rp;
+
+    len = strlen(str);
+    rlen = strlen(repl);
+
+    for (num = 0, s = str; *s; s++)
+	if (*s == '%' && s[1] == c)
+	    num++, s++;
+
+    ret = (char *) zhalloc((num * (rlen - 2)) + len + 1);
+
+    for (s = str, rp = ret; *s; s++) {
+	if (*s == '%' && s[1] == c) {
+	    strcpy(rp, repl);
+	    rp += rlen;
+	    s++;
+	} else
+	    *rp++ = *s;
+    }
+    *rp = '\0';
+
+    return ret;
+}
+
+static int
+bin_compfmt(char *nam, char **args, char *ops, int func)
+{
+    char *param = args[0], *str = args[1];
+
+    for (args += 2; *args; args++) {
+	if (args[0][1] != ':') {
+	    zwarnnam(nam, "invalid argument `%s'", args[0], 0);
+	    return 1;
+	}
+	str = fmtstr(str, **args, *args + 2);
+    }
+    setsparam(param, ztrdup(str));
+    return 0;
+}
 
 static struct builtin bintab[] = {
-    BUILTIN("compdisplay", 0, bin_compdisplay, 2, -1, 0, NULL, NULL),
     BUILTIN("compdescribe", 0, bin_compdescribe, 3, -1, 0, NULL, NULL),
     BUILTIN("comparguments", 0, bin_comparguments, 1, -1, 0, NULL, NULL),
     BUILTIN("compvalues", 0, bin_compvalues, 1, -1, 0, NULL, NULL),
+    BUILTIN("compquote", 0, bin_compquote, 1, -1, 0, NULL, NULL),
+    BUILTIN("comptags", 0, bin_comptags, 1, -1, 0, NULL, NULL),
+    BUILTIN("comptry", 0, bin_comptry, 0, -1, 0, NULL, NULL),
+    BUILTIN("compfmt", 0, bin_compfmt, 2, -1, 0, NULL, NULL),
 };
 
 
 /**/
 int
-setup_computil(Module m)
+setup_(Module m)
 {
     memset(cadef_cache, 0, sizeof(cadef_cache));
     memset(cvdef_cache, 0, sizeof(cvdef_cache));
 
+    memset(comptags, 0, sizeof(comptags));
+
+    lasttaglevel = 0;
+
     return 0;
 }
 
 /**/
 int
-boot_computil(Module m)
+boot_(Module m)
 {
     return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
 }
 
-#ifdef MODULE
-
 /**/
 int
-cleanup_computil(Module m)
+cleanup_(Module m)
 {
     deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
     return 0;
@@ -1876,16 +2615,17 @@ cleanup_computil(Module m)
 
 /**/
 int
-finish_computil(Module m)
+finish_(Module m)
 {
     int i;
 
     for (i = 0; i < MAX_CACACHE; i++)
-	free_cadef(cadef_cache[i]);
+	freecadef(cadef_cache[i]);
     for (i = 0; i < MAX_CVCACHE; i++)
-	free_cvdef(cvdef_cache[i]);
+	freecvdef(cvdef_cache[i]);
+
+    for (i = 0; i < MAX_TAGS; i++)
+	freectags(comptags[i]);
 
     return 0;
 }
-
-#endif
diff --git a/Src/Zle/iwidgets.list b/Src/Zle/iwidgets.list
index 01862160e..33d36a18c 100644
--- a/Src/Zle/iwidgets.list
+++ b/Src/Zle/iwidgets.list
@@ -19,20 +19,21 @@
 "backward-kill-line", backwardkillline, ZLE_KILL | ZLE_KEEPSUFFIX
 "backward-kill-word", backwardkillword, ZLE_KILL | ZLE_KEEPSUFFIX
 "backward-word", backwardword, 0
+"beep", handlefeep, 0
 "beginning-of-buffer-or-history", beginningofbufferorhistory, 0
 "beginning-of-history", beginningofhistory, 0
 "beginning-of-line", beginningofline, 0
 "beginning-of-line-hist", beginningoflinehist, 0
 "capitalize-word", capitalizeword, 0
-"clear-screen", clearscreen, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
-"complete-word", completeword, ZLE_MENUCMP | ZLE_KEEPSUFFIX
-"copy-prev-word", copyprevword, 0
+"clear-screen", clearscreen, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL | ZLE_NOTCOMMAND
+"complete-word", completeword, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_ISCOMP
+"copy-prev-word", copyprevword, ZLE_KEEPSUFFIX
 "copy-region-as-kill", copyregionaskill, ZLE_KEEPSUFFIX
 "delete-char", deletechar, ZLE_KEEPSUFFIX
-"delete-char-or-list", deletecharorlist, ZLE_MENUCMP | ZLE_KEEPSUFFIX
+"delete-char-or-list", deletecharorlist, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_ISCOMP
 "delete-word", deleteword, ZLE_KEEPSUFFIX
 "describe-key-briefly", describekeybriefly, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
-"digit-argument", digitargument, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"digit-argument", digitargument, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL | ZLE_NOTCOMMAND
 "down-case-word", downcaseword, 0
 "down-history", downhistory, 0
 "down-line-or-history", downlineorhistory, ZLE_LINEMOVE | ZLE_LASTCOL
@@ -43,13 +44,14 @@
 "end-of-history", endofhistory, 0
 "end-of-line", endofline, 0
 "end-of-line-hist", endoflinehist, 0
+"end-of-list", endoflist, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
 "exchange-point-and-mark", exchangepointandmark, 0
 "execute-last-named-cmd", NULL, 0
 "execute-named-cmd", NULL, 0
 "expand-cmd-path", expandcmdpath, 0
 "expand-history", expandhistory, 0
-"expand-or-complete", expandorcomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX
-"expand-or-complete-prefix", expandorcompleteprefix, ZLE_MENUCMP | ZLE_KEEPSUFFIX
+"expand-or-complete", expandorcomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_ISCOMP
+"expand-or-complete-prefix", expandorcompleteprefix, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_ISCOMP
 "expand-word", expandword, 0
 "forward-char", forwardchar, 0
 "forward-word", forwardword, 0
@@ -68,12 +70,12 @@
 "kill-region", killregion, ZLE_KILL | ZLE_KEEPSUFFIX
 "kill-whole-line", killwholeline, ZLE_KILL | ZLE_KEEPSUFFIX
 "kill-word", killword, ZLE_KILL | ZLE_KEEPSUFFIX
-"list-choices", listchoices, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"list-choices", listchoices, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL | ZLE_ISCOMP
 "list-expand", listexpand, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
 "magic-space", magicspace, 0
-"menu-complete", menucomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX
-"menu-expand-or-complete", menuexpandorcomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX
-"neg-argument", negargument, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"menu-complete", menucomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_ISCOMP
+"menu-expand-or-complete", menuexpandorcomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_ISCOMP
+"neg-argument", negargument, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL | ZLE_NOTCOMMAND
 "overwrite-mode", overwritemode, 0
 "pound-insert", poundinsert, 0
 "push-input", pushinput, 0
@@ -83,19 +85,20 @@
 "quote-line", quoteline, 0
 "quote-region", quoteregion, 0
 "redisplay", redisplay, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
-"redo", redo, 0
-"reverse-menu-complete", reversemenucomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX
+"redo", redo, ZLE_KEEPSUFFIX
+"reverse-menu-complete", reversemenucomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_ISCOMP
 "run-help", processcmd, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
 "self-insert", selfinsert, ZLE_MENUCMP | ZLE_KEEPSUFFIX
 "self-insert-unmeta", selfinsertunmeta, ZLE_MENUCMP | ZLE_KEEPSUFFIX
 "send-break", sendbreak, 0
 "set-mark-command", setmarkcommand, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
 "spell-word", spellword, 0
+"set-local-history", setlocalhistory, 0
 "transpose-chars", transposechars, 0
 "transpose-words", transposewords, 0
 "undefined-key", undefinedkey, 0
-"undo", undo, 0
-"universal-argument", universalargument, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"undo", undo, ZLE_KEEPSUFFIX
+"universal-argument", universalargument, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL | ZLE_NOTCOMMAND
 "up-case-word", upcaseword, 0
 "up-history", uphistory, 0
 "up-line-or-history", uplineorhistory, ZLE_LINEMOVE | ZLE_LASTCOL
@@ -145,8 +148,8 @@
 "vi-open-line-below", viopenlinebelow, 0
 "vi-oper-swap-case", vioperswapcase, 0
 "vi-pound-insert", vipoundinsert, 0
-"vi-put-after", viputafter, ZLE_YANK
-"vi-put-before", viputbefore, ZLE_YANK
+"vi-put-after", viputafter, ZLE_YANK | ZLE_KEEPSUFFIX
+"vi-put-before", viputbefore, ZLE_YANK | ZLE_KEEPSUFFIX
 "vi-quoted-insert", viquotedinsert, ZLE_MENUCMP | ZLE_KEEPSUFFIX
 "vi-repeat-change", virepeatchange, 0
 "vi-repeat-find", virepeatfind, 0
@@ -159,7 +162,7 @@
 "vi-set-mark", visetmark, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
 "vi-substitute", visubstitute, 0
 "vi-swap-case", viswapcase, 0
-"vi-undo-change", viundochange, 0
+"vi-undo-change", viundochange, ZLE_KEEPSUFFIX
 "vi-unindent", viunindent, 0
 "vi-up-line-or-history", viuplineorhistory, ZLE_LINEMOVE
 "vi-yank", viyank, 0
@@ -168,5 +171,5 @@
 "what-cursor-position", whatcursorposition, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
 "where-is", whereis, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
 "which-command", processcmd, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
-"yank", yank, ZLE_YANK
-"yank-pop", yankpop, ZLE_YANK
+"yank", yank, ZLE_YANK | ZLE_KEEPSUFFIX
+"yank-pop", yankpop, ZLE_YANK | ZLE_KEEPSUFFIX
diff --git a/Src/Zle/zle_utils.c b/Src/Zle/zle_utils.c
index 8fe3e7f0b..51af32e0b 100644
--- a/Src/Zle/zle_utils.c
+++ b/Src/Zle/zle_utils.c
@@ -53,7 +53,7 @@ struct cutbuffer vibuf[35];
 /**/
 char *lastline;
 /**/
-int lastlinesz, lastll;
+int lastlinesz, lastll, lastcs;
 
 /* size of line buffer */
 
@@ -73,7 +73,7 @@ sizeline(int sz)
 /* insert space for ct chars at cursor position */
 
 /**/
-void
+mod_export void
 spaceinline(int ct)
 {
     int i;
@@ -105,7 +105,7 @@ shiftchars(int to, int cnt)
 }
 
 /**/
-void
+mod_export void
 backkill(int ct, int dir)
 {
     int i = (cs -= ct);
@@ -115,7 +115,7 @@ backkill(int ct, int dir)
 }
 
 /**/
-void
+mod_export void
 forekill(int ct, int dir)
 {
     int i = cs;
@@ -191,14 +191,14 @@ cut(int i, int ct, int dir)
 }
 
 /**/
-void
+mod_export void
 backdel(int ct)
 {
     shiftchars(cs -= ct, ct);
 }
 
 /**/
-void
+mod_export void
 foredel(int ct)
 {
     shiftchars(cs, ct);
@@ -283,7 +283,7 @@ hstrnstr(char *haystack, int pos, char *needle, int len, int dir, int sens)
  * characters are read.  Case is folded.                        */
 
 /**/
-int
+mod_export int
 getzlequery(void)
 {
     int c;
@@ -409,19 +409,11 @@ showmsg(char const *msg)
 /* handle the error flag */
 
 /**/
-void
-feep(void)
-{
-    feepflag = 1;
-}
-
-/**/
-void
-handlefeep(void)
+int
+handlefeep(char **args)
 {
-    if(feepflag)
-	beep();
-    feepflag = 0;
+    zbeep();
+    return 0;
 }
 
 /***************/
@@ -446,6 +438,7 @@ initundo(void)
     curchange->del = curchange->ins = NULL;
     lastline = zalloc(lastlinesz = linesz);
     memcpy(lastline, line, lastll = ll);
+    lastcs = cs;
 }
 
 /**/
@@ -519,6 +512,8 @@ mkundoent(void)
     ch->next = NULL;
     ch->hist = histline;
     ch->off = pre;
+    ch->old_cs = lastcs;
+    ch->new_cs = cs;
     if(suf + pre == lastll)
 	ch->del = NULL;
     else
@@ -549,32 +544,36 @@ setlastline(void)
     if(lastlinesz != linesz)
 	lastline = realloc(lastline, lastlinesz = linesz);
     memcpy(lastline, line, lastll = ll);
+    lastcs = cs;
 }
 
 /* move backwards through the change list */
 
 /**/
-void
-undo(void)
+int
+undo(char **args)
 {
     handleundo();
     do {
-	if(!curchange->prev) {
-	    feep();
-	    return;
-	}
-	unapplychange(curchange = curchange->prev);
+	if(!curchange->prev)
+	    return 1;
+	if (unapplychange(curchange->prev))
+	    curchange = curchange->prev;
+	else
+	    break;
     } while(curchange->flags & CH_PREV);
     setlastline();
+    return 0;
 }
 
 /**/
-static void
+static int
 unapplychange(struct change *ch)
 {
     if(ch->hist != histline) {
-	remember_edits();
-	setline(zle_get_event(histline = ch->hist));
+	zle_setline(quietgethist(ch->hist));
+	cs = ch->new_cs;
+	return 0;
     }
     cs = ch->off;
     if(ch->ins)
@@ -589,33 +588,37 @@ unapplychange(struct change *ch)
 	    else
 		line[cs++] = STOUC(*c);
     }
+    cs = ch->old_cs;
+    return 1;
 }
 
 /* move forwards through the change list */
 
 /**/
-void
-redo(void)
+int
+redo(char **args)
 {
     handleundo();
     do {
-	if(!curchange->next) {
-	    feep();
-	    return;
-	}
-	applychange(curchange);
-	curchange = curchange->next;
+	if(!curchange->next)
+	    return 1;
+	if (applychange(curchange))
+	    curchange = curchange->next;
+	else
+	    break;
     } while(curchange->prev->flags & CH_NEXT);
     setlastline();
+    return 0;
 }
 
 /**/
-static void
+static int
 applychange(struct change *ch)
 {
     if(ch->hist != histline) {
-	remember_edits();
-	setline(zle_get_event(histline = ch->hist));
+	zle_setline(quietgethist(ch->hist));
+	cs = ch->old_cs;
+	return 0;
     }
     cs = ch->off;
     if(ch->del)
@@ -630,13 +633,15 @@ applychange(struct change *ch)
 	    else
 		line[cs++] = STOUC(*c);
     }
+    cs = ch->new_cs;
+    return 1;
 }
 
 /* vi undo: toggle between the end of the undo list and the preceding point */
 
 /**/
-void
-viundochange(void)
+int
+viundochange(char **args)
 {
     handleundo();
     if(curchange->next) {
@@ -645,6 +650,7 @@ viundochange(void)
 	    curchange = curchange->next;
 	} while(curchange->next);
 	setlastline();
+	return 0;
     } else
-	undo();
+	return undo(args);
 }
diff --git a/Src/builtin.c b/Src/builtin.c
index 31f396d93..dd0d3e523 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -42,29 +42,30 @@ static struct builtin builtins[] =
     BUILTIN("[", 0, bin_test, 0, -1, BIN_BRACKET, NULL, NULL),
     BUILTIN(".", BINF_PSPECIAL, bin_dot, 1, -1, 0, NULL, NULL),
     BUILTIN(":", BINF_PSPECIAL, bin_true, 0, -1, 0, NULL, NULL),
-    BUILTIN("alias", BINF_MAGICEQUALS, bin_alias, 0, -1, 0, "Lgmr", NULL),
-    BUILTIN("autoload", BINF_TYPEOPTS, bin_functions, 0, -1, 0, "t", "u"),
+    BUILTIN("alias", BINF_MAGICEQUALS | BINF_PLUSOPTS, bin_alias, 0, -1, 0, "Lgmr", NULL),
+    BUILTIN("autoload", BINF_TYPEOPTS, bin_functions, 0, -1, 0, "tUXwkz", "u"),
     BUILTIN("bg", 0, bin_fg, 0, -1, BIN_BG, NULL, NULL),
     BUILTIN("break", BINF_PSPECIAL, bin_break, 0, 1, BIN_BREAK, NULL, NULL),
     BUILTIN("bye", 0, bin_break, 0, 1, BIN_EXIT, NULL, NULL),
     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, "AEFLRTUZafghilrtux", 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),
     BUILTIN("echo", BINF_PRINTOPTS | BINF_ECHOPTS, bin_print, 0, -1, BIN_ECHO, "neE", "-"),
     BUILTIN("echotc", 0, bin_echotc, 1, -1, 0, NULL, NULL),
-    BUILTIN("emulate", 0, bin_emulate, 1, 1, 0, "R", NULL),
+    BUILTIN("emulate", 0, bin_emulate, 1, 1, 0, "LR", NULL),
     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, "EFLRTUZafhilrtu", "xg"),
     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),
-    BUILTIN("functions", BINF_TYPEOPTS, bin_functions, 0, -1, 0, "mtu", NULL),
+    BUILTIN("float", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "EFghlrtux", "E"),
+    BUILTIN("functions", BINF_TYPEOPTS, bin_functions, 0, -1, 0, "mtuU", NULL),
     BUILTIN("getln", 0, bin_read, 0, -1, 0, "ecnAlE", "zr"),
     BUILTIN("getopts", 0, bin_getopts, 2, -1, 0, NULL, NULL),
     BUILTIN("hash", BINF_MAGICEQUALS, bin_hash, 0, -1, 0, "dfmrv", NULL),
@@ -74,11 +75,11 @@ static struct builtin builtins[] =
 #endif
 
     BUILTIN("history", 0, bin_fc, 0, -1, BIN_FC, "nrdDfEim", "l"),
-    BUILTIN("integer", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "lrtux", "i"),
+    BUILTIN("integer", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "ghlrtux", "i"),
     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, "AEFLRTUZahilrtu", NULL),
     BUILTIN("log", 0, bin_log, 0, 0, 0, NULL, NULL),
     BUILTIN("logout", 0, bin_break, 0, 1, BIN_LOGOUT, NULL, NULL),
 
@@ -86,14 +87,18 @@ static struct builtin builtins[] =
     BUILTIN("mem", 0, bin_mem, 0, 0, 0, "v", NULL),
 #endif
 
+#if defined(ZSH_PAT_DEBUG)
+    BUILTIN("patdebug", 0, bin_patdebug, 1, -1, 0, "p", NULL),
+#endif
+
     BUILTIN("popd", 0, bin_cd, 0, 2, BIN_POPD, NULL, NULL),
-    BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "RDPnrslzNu0123456789pioOcm-", NULL),
+    BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "RDPbnrslzNu0123456789pioOcm-", NULL),
     BUILTIN("pushd", 0, bin_cd, 0, 2, BIN_PUSHD, NULL, NULL),
     BUILTIN("pushln", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, NULL, "-nz"),
     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, "AEFLRTUZafghiltux", "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 +112,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, "AEFLRTUZafghilrtuxm", 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"),
@@ -118,10 +123,8 @@ static struct builtin builtins[] =
     BUILTIN("whence", 0, bin_whence, 0, -1, 0, "acmpvfsw", NULL),
     BUILTIN("where", 0, bin_whence, 0, -1, 0, "pmsw", "ca"),
     BUILTIN("which", 0, bin_whence, 0, -1, 0, "ampsw", "c"),
-
-#ifdef DYNAMIC
-    BUILTIN("zmodload", 0, bin_zmodload, 0, -1, 0, "Laudi", NULL),
-#endif
+    BUILTIN("zmodload", 0, bin_zmodload, 0, -1, 0, "ILabcfdipue", NULL),
+    BUILTIN("zcompile", 0, bin_zcompile, 0, -1, 0, "tUMRcmzka", NULL),
 };
 
 /****************************************/
@@ -131,7 +134,7 @@ static struct builtin builtins[] =
 /* hash table containing builtin commands */
 
 /**/
-HashTable builtintab;
+mod_export HashTable builtintab;
  
 /**/
 void
@@ -142,6 +145,7 @@ createbuiltintable(void)
     builtintab->hash        = hasher;
     builtintab->emptytable  = NULL;
     builtintab->filltable   = NULL;
+    builtintab->cmpnodes    = strcmp;
     builtintab->addnode     = addhashnode;
     builtintab->getnode     = gethashnode;
     builtintab->getnode2    = gethashnode2;
@@ -206,9 +210,10 @@ int
 execbuiltin(LinkList args, Builtin bn)
 {
     LinkNode n;
-    char ops[MAX_OPS], *arg, *pp, *name, **argv, **oargv, *optstr;
+    char ops[MAX_OPS], *arg, *pp, *name, *optstr;
     char *oxarg, *xarg = NULL;
-    int flags, sense, argc = 0, execop;
+    char typenumstr[] = TYPESET_OPTNUM;
+    int flags, sense, argc = 0, execop, xtr = isset(XTRACE), lxarg = 0;
 
     /* initialise some static variables */
     auxdata = NULL;
@@ -220,14 +225,11 @@ execbuiltin(LinkList args, Builtin bn)
 
     arg = (char *) ugetnode(args);
 
-#ifdef DYNAMIC
     if (!bn->handlerfunc) {
 	zwarnnam(name, "autoload failed", NULL, 0);
 	deletebuiltin(bn->nam);
 	return 1;
     }
-#endif
-
     /* get some information about the command */
     flags = bn->flags;
     optstr = bn->optstr;
@@ -249,12 +251,21 @@ execbuiltin(LinkList args, Builtin bn)
 		    break;
 	    }
 	    /* save the options in xarg, for execution tracing */
-	    if (xarg) {
-		oxarg = tricat(xarg, " ", arg);
-		zsfree(xarg);
-		xarg = oxarg;
-	    } else
-		xarg = ztrdup(arg);
+	    if (xtr) {
+		if (xarg) {
+		    int l = strlen(arg) + lxarg + 1;
+
+		    oxarg = zhalloc(l + 1);
+		    strcpy(oxarg, xarg);
+		    oxarg[lxarg] = ' ';
+		    strcpy(oxarg + lxarg + 1, arg);
+		    xarg = oxarg;
+		    lxarg = l + 1;
+		} else {
+		    xarg = dupstring(arg);
+		    lxarg = strlen(xarg);
+		}
+	    }
 	    /* handle -- or - (ops['-']), and + (ops['-'] and ops['+']) */
 	    if (arg[1] == '-')
 		arg++;
@@ -273,8 +284,7 @@ execbuiltin(LinkList args, Builtin bn)
 	    /* "typeset" may take a numeric argument *
 	     * at the tail of the options            */
 	    if (idigit(*arg) && (flags & BINF_TYPEOPT) &&
-		(arg[-1] == 'L' || arg[-1] == 'R' ||
-		 arg[-1] == 'Z' || arg[-1] == 'i'))
+		strchr(typenumstr, arg[-1]))
 		auxlen = (int)zstrtol(arg, &arg, 10);
 	    /* The above loop may have exited on an invalid option.  (We  *
 	     * assume that any option requiring metafication is invalid.) */
@@ -282,7 +292,6 @@ execbuiltin(LinkList args, Builtin bn)
 		if(*arg == Meta)
 		    *++arg ^= 32;
 		zerr("bad option: -%c", NULL, *arg);
-		zsfree(xarg);
 		return 1;
 	    }
 	    arg = (char *) ugetnode(args);
@@ -300,9 +309,9 @@ execbuiltin(LinkList args, Builtin bn)
 		auxdata = arg;
 		arg = (char *) ugetnode(args);
 	    }
-	    /* for "typeset", -L, -R, -Z and -i take a numeric extra argument */
-	    if ((flags & BINF_TYPEOPT) && (execop == 'L' || execop == 'R' ||
-		execop == 'Z' || execop == 'i') && arg && idigit(*arg)) {
+	    /* some "typeset" options take a numeric extra argument */
+	    if ((flags & BINF_TYPEOPT) && strchr(typenumstr, execop) &&
+		arg && idigit(*arg)) {
 		auxlen = atoi(arg);
 		arg = (char *) ugetnode(args);
 	    }
@@ -322,39 +331,42 @@ execbuiltin(LinkList args, Builtin bn)
 	while (n)
 	    argc++, incnode(n);
     }
-    /* Get the actual arguments, into argv.  Oargv saves the *
-     * beginning of the array for later reference.           */
-    oargv = argv = (char **)ncalloc(sizeof(char **) * (argc + 1));
-    if ((*argv++ = arg))
-	while ((*argv++ = (char *)ugetnode(args)));
-    argv = oargv;
-    if (errflag) {
-	zsfree(xarg);
-	errflag = 0;
-	return 1;
-    }
+    {
+	VARARR(char *, argarr, (argc + 1));
+	char **argv, **oargv;
+
+	/* Get the actual arguments, into argv.  Oargv saves the *
+	 * beginning of the array for later reference.           */
+	oargv = argv = argarr;
+	if ((*argv++ = arg))
+	    while ((*argv++ = (char *)ugetnode(args)));
+	argv = oargv;
+	if (errflag) {
+	    errflag = 0;
+	    return 1;
+	}
 
-    /* check that the argument count lies within the specified bounds */
-    if (argc < bn->minargs || (argc > bn->maxargs && bn->maxargs != -1)) {
-	zwarnnam(name, (argc < bn->minargs)
-		? "not enough arguments" : "too many arguments", NULL, 0);
-	zsfree(xarg);
-	return 1;
-    }
+	/* check that the argument count lies within the specified bounds */
+	if (argc < bn->minargs || (argc > bn->maxargs && bn->maxargs != -1)) {
+	    zwarnnam(name, (argc < bn->minargs)
+		     ? "not enough arguments" : "too many arguments", NULL, 0);
+	    return 1;
+	}
 
-    /* display execution trace information, if required */
-    if (isset(XTRACE)) {
-	fprintf(stderr, "%s%s", (prompt4) ? prompt4 : "", name);
-	if (xarg)
-	    fprintf(stderr, " %s", xarg);
-	while (*oargv)
-	    fprintf(stderr, " %s", *oargv++);
-	fputc('\n', stderr);
-	fflush(stderr);
+	/* display execution trace information, if required */
+	if (xtr) {
+	    printprompt4();
+	    fprintf(xtrerr, "%s", name);
+	    if (xarg)
+		fprintf(xtrerr, " %s", xarg);
+	    while (*oargv)
+		fprintf(xtrerr, " %s", *oargv++);
+	    fputc('\n', xtrerr);
+	    fflush(xtrerr);
+	}
+	/* call the handler function, and return its return value */
+	return (*(bn->handlerfunc)) (name, argv, ops, bn->funcid);
     }
-    zsfree(xarg);
-    /* call the handler function, and return its return value */
-    return (*(bn->handlerfunc)) (name, argv, ops, bn->funcid);
 }
 
 /* Enable/disable an element in one of the internal hash tables.  *
@@ -368,7 +380,7 @@ bin_enable(char *name, char **argv, char *ops, int func)
     HashTable ht;
     HashNode hn;
     ScanFunc scanfunc;
-    Comp com;
+    Patprog pprog;
     int flags1 = 0, flags2 = 0;
     int match = 0, returnval = 0;
 
@@ -405,8 +417,8 @@ bin_enable(char *name, char **argv, char *ops, int func)
 	for (; *argv; argv++) {
 	    /* parse pattern */
 	    tokenize(*argv);
-	    if ((com = parsereg(*argv)))
-		match += scanmatchtable(ht, com, 0, 0, scanfunc, 0);
+	    if ((pprog = patcompile(*argv, PAT_STATIC, 0)))
+		match += scanmatchtable(ht, pprog, 0, 0, scanfunc, 0);
 	    else {
 		untokenize(*argv);
 		zwarnnam(name, "bad pattern : %s", *argv, 0);
@@ -537,9 +549,7 @@ bin_set(char *nam, char **args, char *ops, int func)
     } else {
 	/* set shell arguments */
 	freearray(pparams);
-	PERMALLOC {
-	    pparams = arrdup(args);
-	} LASTALLOC;
+	pparams = zarrdup(args);
     }
     return 0;
 }
@@ -567,7 +577,7 @@ bin_pwd(char *name, char **argv, char *ops, int func)
 /* the directory stack */
  
 /**/
-LinkList dirstack;
+mod_export LinkList dirstack;
  
 /* dirs: list the directory stack, or replace it with a provided list */
 
@@ -598,15 +608,13 @@ bin_dirs(char *name, char **argv, char *ops, int func)
 	return 0;
     }
     /* replace the stack with the specified directories */
-    PERMALLOC {
-	l = newlinklist();
-	if (*argv) {
-	    while (*argv)
-		addlinknode(l, ztrdup(*argv++));
-	    freelinklist(dirstack, freestr);
-	    dirstack = l;
-	}
-    } LASTALLOC;
+    l = znewlinklist();
+    if (*argv) {
+	while (*argv)
+	    zaddlinknode(l, ztrdup(*argv++));
+	freelinklist(dirstack, freestr);
+	dirstack = l;
+    }
     return 0;
 }
 
@@ -618,6 +626,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;
@@ -634,17 +644,22 @@ set_pwd_env(void)
     setsparam("OLDPWD", ztrdup(oldpwd));
 
     pm = (Param) paramtab->getnode(paramtab, "PWD");
-    if (!(pm->flags & PM_EXPORTED)) {
+    if (!(pm->flags & PM_EXPORTED) &&
+	(!pm->level || (isset(ALLEXPORT) && !pm->old))) {
 	pm->flags |= PM_EXPORTED;
-	pm->env = addenv("PWD", pwd);
+	pm->env = addenv("PWD", pwd, pm->flags);
     }
     pm = (Param) paramtab->getnode(paramtab, "OLDPWD");
-    if (!(pm->flags & PM_EXPORTED)) {
+    if (!(pm->flags & PM_EXPORTED) &&
+	(!pm->level || (isset(ALLEXPORT) && !pm->old))) {
 	pm->flags |= PM_EXPORTED;
-	pm->env = addenv("PWD", pwd);
+	pm->env = addenv("OLDPWD", oldpwd, pm->flags);
     }
 }
 
+/* set if we are resolving links to their true paths */
+static int chasinglinks;
+
 /* The main pwd changing function.  The real work is done by other     *
  * functions.  cd_get_dest() does the initial argument processing;     *
  * cd_do_chdir() actually changes directory, if possible; cd_new_pwd() *
@@ -657,7 +672,6 @@ bin_cd(char *nam, char **argv, char *ops, int func)
 {
     LinkNode dir;
     struct stat st1, st2;
-    int chaselinks;
 
     if (isset(RESTRICTED)) {
 	zwarnnam(nam, "restricted", NULL, 0);
@@ -678,33 +692,32 @@ bin_cd(char *nam, char **argv, char *ops, int func)
 		goto brk;
 	    }
 	} while (*++s);
-	for (s = *argv; *++s; ops[*s] = 1);
+	for (s = *argv; *++s; ops[STOUC(*s)] = 1);
     }
   brk:
-    chaselinks = ops['P'] || (isset(CHASELINKS) && !ops['L']);
-    PERMALLOC {
-	pushnode(dirstack, ztrdup(pwd));
-	if (!(dir = cd_get_dest(nam, argv, ops, func))) {
-	    zsfree(getlinknode(dirstack));
-	    LASTALLOC_RETURN 1;
-	}
-    } LASTALLOC;
-    cd_new_pwd(func, dir, chaselinks);
+    chasinglinks = ops['P'] || (isset(CHASELINKS) && !ops['L']);
+    zpushnode(dirstack, ztrdup(pwd));
+    if (!(dir = cd_get_dest(nam, argv, ops, func))) {
+	zsfree(getlinknode(dirstack));
+	return 1;
+    }
+    cd_new_pwd(func, dir);
 
     if (stat(unmeta(pwd), &st1) < 0) {
+	setjobpwd();
 	zsfree(pwd);
 	pwd = metafy(zgetcwd(), -1, META_DUP);
     } else if (stat(".", &st2) < 0)
 	chdir(unmeta(pwd));
     else if (st1.st_ino != st2.st_ino || st1.st_dev != st2.st_dev) {
-	if (chaselinks) {
+	if (chasinglinks) {
+	    setjobpwd();
 	    zsfree(pwd);
 	    pwd = metafy(zgetcwd(), -1, META_DUP);
 	} else {
 	    chdir(unmeta(pwd));
 	}
     }
-    set_pwd_env();
     return 0;
 }
 
@@ -726,9 +739,9 @@ cd_get_dest(char *nam, char **argv, char *ops, int func)
 	if (func == BIN_PUSHD && unset(PUSHDTOHOME))
 	    dir = nextnode(firstnode(dirstack));
 	if (dir)
-	    insertlinknode(dirstack, dir, getlinknode(dirstack));
+	    zinsertlinknode(dirstack, dir, getlinknode(dirstack));
 	else if (func != BIN_POPD)
-	    pushnode(dirstack, ztrdup(home));
+	    zpushnode(dirstack, ztrdup(home));
     } else if (!argv[1]) {
 	int dd;
 	char *end;
@@ -749,8 +762,8 @@ cd_get_dest(char *nam, char **argv, char *ops, int func)
 	    }
 	}
 	if (!dir)
-	    pushnode(dirstack, ztrdup(strcmp(argv[0], "-")
-				      ? (doprintdir--, argv[0]) : oldpwd));
+	    zpushnode(dirstack, ztrdup(strcmp(argv[0], "-")
+				       ? (doprintdir--, argv[0]) : oldpwd));
     } else {
 	char *u, *d;
 	int len1, len2, len3;
@@ -766,7 +779,7 @@ cd_get_dest(char *nam, char **argv, char *ops, int func)
 	strncpy(d, pwd, len3);
 	strcpy(d + len3, argv[1]);
 	strcat(d, u + len1);
-	pushnode(dirstack, d);
+	zpushnode(dirstack, d);
 	doprintdir++;
     }
 
@@ -789,7 +802,7 @@ cd_get_dest(char *nam, char **argv, char *ops, int func)
 	    zsfree(remnode(dirstack, dir));
 	return NULL;
     }
-    if (dest != getdata(dir)) {
+    if (dest != (char *)getdata(dir)) {
 	zsfree(getdata(dir));
 	setdata(dir, dest);
     }
@@ -903,40 +916,54 @@ static char *
 cd_try_chdir(char *pfix, char *dest, int hard)
 {
     char *buf;
+    int dlen, dochaselinks = 0;
 
     /* handle directory prefix */
     if (pfix && *pfix) {
 	if (*pfix == '/')
 	    buf = tricat(pfix, "/", dest);
 	else {
-	    int pwl = strlen(pwd);
 	    int pfl = strlen(pfix);
+	    dlen = strlen(pwd);
 
-	    buf = zalloc(pwl + pfl + strlen(dest) + 3);
+	    buf = zalloc(dlen + pfl + strlen(dest) + 3);
 	    strcpy(buf, pwd);
-	    buf[pwl] = '/';
-	    strcpy(buf + pwl + 1, pfix);
-	    buf[pwl + 1 + pfl] = '/';
-	    strcpy(buf + pwl + pfl + 2, dest);
+	    buf[dlen] = '/';
+	    strcpy(buf + dlen + 1, pfix);
+	    buf[dlen + 1 + pfl] = '/';
+	    strcpy(buf + dlen + pfl + 2, dest);
 	}
     } else if (*dest == '/')
 	buf = ztrdup(dest);
     else {
-	int pwl = strlen(pwd);
-
-	buf = zalloc(pwl + strlen(dest) + 2);
+	dlen = strlen(pwd);
+	if (pwd[dlen-1] == '/')
+	    --dlen;
+	buf = zalloc(dlen + strlen(dest) + 2);
 	strcpy(buf, pwd);
-	buf[pwl] = '/';
-	strcpy(buf + pwl + 1, dest);
+	buf[dlen] = '/';
+	strcpy(buf + dlen + 1, dest);
     }
 
-    /* Normalise path.  See the definition of fixdir() for what this means. */
-    fixdir(buf);
-
-    if (lchdir(buf, NULL, hard)) {
-	zsfree(buf);
+    /* Normalise path.  See the definition of fixdir() for what this means.
+     * We do not do this if we are chasing links.
+     */
+    if (!chasinglinks)
+	dochaselinks = fixdir(buf);
+    else
+	unmetafy(buf, &dlen);
+
+    /* We try the full path first.  If that fails, try the
+     * argument to cd relatively.  This is useful if the cwd
+     * or a parent directory is renamed in the interim.
+     */
+    if (lchdir(buf, NULL, hard) && lchdir(dest, NULL, hard)) {
+	free(buf);
 	return NULL;
     }
+    /* the chdir succeeded, so decide if we should force links to be chased */
+    if (dochaselinks)
+	chasinglinks = 1;
     return metafy(buf, -1, META_NOALLOC);
 }
 
@@ -944,10 +971,9 @@ cd_try_chdir(char *pfix, char *dest, int hard)
 
 /**/
 static void
-cd_new_pwd(int func, LinkNode dir, int chaselinks)
+cd_new_pwd(int func, LinkNode dir)
 {
-    Param pm;
-    List l;
+    Eprog prog;
     char *new_pwd, *s;
     int dirstacksize;
 
@@ -961,7 +987,7 @@ cd_new_pwd(int func, LinkNode dir, int chaselinks)
     } else if (func == BIN_CD && unset(AUTOPUSHD))
 	zsfree(getlinknode(dirstack));
 
-    if (chaselinks) {
+    if (chasinglinks) {
 	s = new_pwd;
 	new_pwd = findpwd(s);
 	zsfree(s);
@@ -980,14 +1006,10 @@ cd_new_pwd(int func, LinkNode dir, int chaselinks)
     current (i.e. new) pwd */
     zsfree(oldpwd);
     oldpwd = pwd;
+    setjobpwd();
     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) {
@@ -996,10 +1018,14 @@ cd_new_pwd(int func, LinkNode dir, int chaselinks)
     }
 
     /* execute the chpwd function */
-    if ((l = getshfunc("chpwd")) != &dummy_list) {
+    if ((prog = getshfunc("chpwd")) != &dummy_eprog) {
+	int osc = sfcontext;
+
 	fflush(stdout);
 	fflush(stderr);
-	doshfunc(l, NULL, 0, 1);
+	sfcontext = SFC_HOOK;
+	doshfunc("chpwd", prog, NULL, 0, 1);
+	sfcontext = osc;
     }
 
     dirstacksize = getiparam("DIRSTACKSIZE");
@@ -1029,14 +1055,20 @@ printdirstack(void)
 }
 
 /* Normalise a path.  Segments consisting of ., and foo/.. *
- * combinations, are removed and the path is unmetafied.   */
+ * combinations, are removed and the path is unmetafied.
+ * Returns 1 if we found a ../ path which should force links to
+ * be chased, 0 otherwise.
+ */
 
 /**/
-static void
+int
 fixdir(char *src)
 {
-    char *dest = src;
-    char *d0 = dest;
+    char *dest = src, *d0 = dest;
+#ifdef __CYGWIN__
+    char *s0 = src;
+#endif
+    int ret = 0;
 
 /*** if have RFS superroot directory ***/
 #ifdef HAVE_SUPERROOT
@@ -1053,6 +1085,11 @@ fixdir(char *src)
     for (;;) {
 	/* compress multiple /es into single */
 	if (*src == '/') {
+#ifdef __CYGWIN__
+	    /* allow leading // under cygwin */
+	    if (src == s0 && src[1] == '/')
+		*dest++ = *src++;
+#endif
 	    *dest++ = *src++;
 	    while (*src == '/')
 		src++;
@@ -1063,17 +1100,40 @@ fixdir(char *src)
 	    while (dest > d0 + 1 && dest[-1] == '/')
 		dest--;
 	    *dest = '\0';
-	    return;
+	    return ret;
 	}
-	if (dest > d0 + 1 && src[0] == '.' && src[1] == '.' &&
-	  (src[2] == '\0' || src[2] == '/')) {
-	    /* remove a foo/.. combination */
-	    for (dest--; dest > d0 + 1 && dest[-1] != '/'; dest--);
-	    if (dest[-1] != '/')
-		dest--;
-	    src++;
-	    while (*++src == '/');
-	} else if (src[0] == '.' && (src[1] == '/' || src[1] == '\0')) {
+	if (src[0] == '.' && src[1] == '.' &&
+	    (src[2] == '\0' || src[2] == '/')) {
+	    if (isset(CHASEDOTS)) {
+		ret = 1;
+		/* and treat as normal path segment */
+	    } else {
+		if (dest > d0 + 1) {
+		    /*
+		     * remove a foo/.. combination:
+		     * first check foo exists, else return.
+		     */
+		    struct stat st;
+		    *dest = '\0';
+		    if (stat(d0, &st) < 0 || !S_ISDIR(st.st_mode)) {
+			char *ptrd, *ptrs;
+			if (dest == src)
+			    *dest = '.';
+			for (ptrs = src, ptrd = dest; *ptrs; ptrs++, ptrd++)
+			    *ptrd = (*ptrs == Meta) ? (*++ptrs ^ 32) : *ptrs;
+			*ptrd = '\0';
+			return 1;
+		    }
+		    for (dest--; dest > d0 + 1 && dest[-1] != '/'; dest--);
+		    if (dest[-1] != '/')
+			dest--;
+		}
+		src++;
+		while (*++src == '/');
+		continue;
+	    }
+	}
+	if (src[0] == '.' && (src[1] == '/' || src[1] == '\0')) {
 	    /* skip a . section */
 	    while (*++src == '/');
 	} else {
@@ -1086,7 +1146,7 @@ fixdir(char *src)
 }
 
 /**/
-void
+mod_export void
 printqt(char *str)
 {
     /* Print str, but turn any single quote into '\'' or ''. */
@@ -1098,7 +1158,7 @@ printqt(char *str)
 }
 
 /**/
-void
+mod_export void
 printif(char *str, int c)
 {
     /* If flag c has an argument, print that */
@@ -1119,7 +1179,7 @@ bin_fc(char *nam, char **argv, char *ops, int func)
     int first = -1, last = -1, retval, minflag = 0;
     char *s;
     struct asgment *asgf = NULL, *asgl = NULL;
-    Comp com = NULL;
+    Patprog pprog = NULL;
 
     /* fc is only permitted in interactive shells */
     if (!interact) {
@@ -1130,33 +1190,31 @@ bin_fc(char *nam, char **argv, char *ops, int func)
      * as a pattern that history lines have to match   */
     if (*argv && ops['m']) {
 	tokenize(*argv);
-	if (!(com = parsereg(*argv++))) {
+	if (!(pprog = patcompile(*argv++, 0, NULL))) {
 	    zwarnnam(nam, "invalid match pattern", NULL, 0);
 	    return 1;
 	}
     }
     if (ops['R']) {
 	/* read history from a file */
-	readhistfile(*argv ? *argv : getsparam("HISTFILE"), 1);
+	readhistfile(*argv, 1, ops['I'] ? HFILE_SKIPOLD : 0);
 	return 0;
     }
     if (ops['W']) {
 	/* write history to a file */
-	savehistfile(*argv ? *argv : getsparam("HISTFILE"), 1,
-		     (ops['I'] ? 2 : 0));
+	savehistfile(*argv, 1, ops['I'] ? HFILE_SKIPOLD : 0);
 	return 0;
     }
     if (ops['A']) {
 	/* append history to a file */
-	savehistfile(*argv ? *argv : getsparam("HISTFILE"), 1,
-		     (ops['I'] ? 3 : 1));
+	savehistfile(*argv, 1, HFILE_APPEND | (ops['I'] ? HFILE_SKIPOLD : 0));
 	return 0;
     }
     if (!(ops['l'] && unset(HISTNOSTORE)))
 	remhist();
     /* put foo=bar type arguments into the substitution list */
     while (*argv && equalsplit(*argv, &s)) {
-	Asgment a = (Asgment) alloc(sizeof *a);
+	Asgment a = (Asgment) zhalloc(sizeof *a);
 
 	if (!asgf)
 	    asgf = asgl = a;
@@ -1191,9 +1249,9 @@ bin_fc(char *nam, char **argv, char *ops, int func)
     }
     /* default values of first and last, and range checking */
     if (first == -1)
-	first = (ops['l']) ? curhist - 16 : curhist - 1;
+	first = ops['l']? addhistnum(curhist,-16,0) : addhistnum(curhist,-1,0);
     if (last == -1)
-	last = (ops['l']) ? curhist - 1 : first;
+	last = ops['l']? addhistnum(curhist,-1,0) : first;
     if (first < firsthist())
 	first = firsthist();
     if (last == -1)
@@ -1204,7 +1262,7 @@ bin_fc(char *nam, char **argv, char *ops, int func)
 	/* list the required part of the history */
 	retval = fclist(stdout, !ops['n'], ops['r'], ops['D'],
 			ops['d'] + ops['f'] * 2 + ops['E'] * 4 + ops['i'] * 8,
-			first, last, asgf, com);
+			first, last, asgf, pprog);
     else {
 	/* edit history file, and (if successful) use the result as a new command */
 	int tempfd;
@@ -1218,20 +1276,21 @@ bin_fc(char *nam, char **argv, char *ops, int func)
 		((out = fdopen(tempfd, "w")) == NULL)) {
 	    zwarnnam("fc", "can't open temp file: %e", NULL, errno);
 	} else {
-	    if (!fclist(out, 0, ops['r'], 0, 0, first, last, asgf, com)) {
+	    if (!fclist(out, 0, ops['r'], 0, 0, first, last, asgf, pprog)) {
 		char *editor;
 
 		editor = auxdata ? auxdata : getsparam("FCEDIT");
 		if (!editor)
 		    editor = DEFAULT_FCEDIT;
 
-		if (fcedit(editor, fil))
+		if (fcedit(editor, fil)) {
 		    if (stuff(fil))
 			zwarnnam("fc", "%e: %s", s, errno);
 		    else {
 			loop(0,1);
 			retval = lastval;
 		    }
+		}
 	    }
 	}
 	unlink(fil);
@@ -1257,7 +1316,7 @@ fcgetcomm(char *s)
      * numbers indicate reversed numbering.           */
     if ((cmd = atoi(s))) {
 	if (cmd < 0)
-	    cmd = curhist + cmd;
+	    cmd = addhistnum(curhist,cmd,HIST_FOREIGN);
 	if (cmd >= curhist) {
 	    zwarnnam("fc", "bad history number: %d", 0, cmd);
 	    return -1;
@@ -1289,7 +1348,7 @@ fcsubs(char **sp, struct asgment *sub)
 	oldpos = s;
 	/* loop over occurences of oldstr in s, replacing them with newstr */
 	while ((newpos = (char *)strstr(oldpos, oldstr))) {
-	    newmem = (char *) alloc(1 + (newpos - s)
+	    newmem = (char *) zhalloc(1 + (newpos - s)
 			+ strlen(newstr) + strlen(newpos + strlen(oldstr)));
 	    ztrncpy(newmem, s, newpos - s);
 	    strcat(newmem, newstr);
@@ -1318,10 +1377,10 @@ fcsubs(char **sp, struct asgment *sub)
 
 /**/
 static int
-fclist(FILE *f, int n, int r, int D, int d, int first, int last, struct asgment *subs, Comp com)
+fclist(FILE *f, int n, int r, int D, int d, int first, int last, struct asgment *subs, Patprog pprog)
 {
     int fclistdone = 0;
-    char *s, *hs;
+    char *s;
     Histent ent;
 
     /* reverse range if required */
@@ -1334,28 +1393,31 @@ fclist(FILE *f, int n, int r, int D, int d, int first, int last, struct asgment
     if (!subs)
 	fclistdone = 1;
 
-    for (;;) {
-	hs = quietgetevent(first);
-	if (!hs) {
+    ent = gethistent(first, first < last? GETHIST_DOWNWARD : GETHIST_UPWARD);
+    if (!ent || (first < last? ent->histnum > last : ent->histnum < last)) {
+	if (first == last)
 	    zwarnnam("fc", "no such event: %d", NULL, first);
-	    return 1;
-	}
-	s = dupstring(hs);
+	else
+	    zwarnnam("fc", "no events in that range", NULL, 0);
+	return 1;
+    }
+
+    for (;;) {
+	s = dupstring(ent->text);
 	/* this if does the pattern matching, if required */
-	if (!com || domatch(s, com, 0)) {
+	if (!pprog || pattry(pprog, s)) {
 	    /* perform substitution */
 	    fclistdone |= fcsubs(&s, subs);
 
 	    /* do numbering */
-	    if (n)
-		fprintf(f, "%5d  ", first);
-	    ent = NULL;
+	    if (n) {
+		fprintf(f, "%5d%c ", ent->histnum,
+			ent->flags & HIST_FOREIGN? '*' : ' ');
+	    }
 	    /* output actual time (and possibly date) of execution of the
 	    command, if required */
 	    if (d) {
 		struct tm *ltm;
-		if (!ent)
-		    ent = gethistent(first);
 		ltm = localtime(&ent->stim);
 		if (d >= 2) {
 		    if (d >= 8) {
@@ -1377,8 +1439,6 @@ fclist(FILE *f, int n, int r, int D, int d, int first, int last, struct asgment
 	    /* display the time taken by the command, if required */
 	    if (D) {
 		long diff;
-		if (!ent)
-		    ent = gethistent(first);
 		diff = (ent->ftim) ? ent->ftim - ent->stim : 0;
 		fprintf(f, "%ld:%02ld  ", diff / 60, diff % 60);
 	    }
@@ -1391,12 +1451,14 @@ fclist(FILE *f, int n, int r, int D, int d, int first, int last, struct asgment
 		fprintf(f, "%s\n", s);
 	}
 	/* move on to the next history line, or quit the loop */
-	if (first == last)
-	    break;
-	else if (first > last)
-	    first--;
-	else
-	    first++;
+	if (first < last) {
+	    if (!(ent = down_histent(ent)) || ent->histnum > last)
+		break;
+	}
+	else {
+	    if (!(ent = up_histent(ent)) || ent->histnum < last)
+		break;
+	}
     }
 
     /* final processing */
@@ -1465,6 +1527,265 @@ getasg(char *s)
     return &asg;
 }
 
+/* function to set a single parameter */
+
+/**/
+Param
+typeset_single(char *cname, char *pname, Param pm, int func,
+	       int on, int off, int roff, char *value, Param altpm)
+{
+    int usepm, tc, keeplocal = 0, newspecial = 0;
+
+    /*
+     * Do we use the existing pm?  Note that this isn't the end of the
+     * story, because if we try and create a new pm at the same
+     * locallevel as an unset one we use the pm struct anyway: that's
+     * handled in createparam().  Here we just avoid using it for the
+     * present tests if it's unset.
+     */
+    usepm = pm && !(pm->flags & PM_UNSET);
+
+    /*
+     * We need to compare types with an existing pm if special,
+     * even if that's unset
+     */
+    if (pm && (pm->flags & PM_SPECIAL))
+	usepm = 1;
+
+    /*
+     * Don't use an existing param if
+     *   - the local level has changed, and
+     *   - we are really locallizing the parameter
+     */
+    if (usepm && locallevel != pm->level && (on & PM_LOCAL)) {
+	/*
+	 * If the original parameter was special and we're creating
+	 * a new one, we need to keep it special.
+	 *
+	 * The -h (hide) flags prevents an existing special being made
+	 * local.  It can be applied either to the special or in the
+	 * typeset/local statement for the local variable.
+	 */
+	newspecial = (pm->flags & PM_SPECIAL)
+	    && !(on & PM_HIDE) && !(pm->flags & PM_HIDE & ~off);
+	usepm = 0;
+    }
+
+    /* attempting a type conversion, or making a tied colonarray? */
+    tc = 0;
+    if (usepm || newspecial) {
+	int chflags = ((off & pm->flags) | (on & ~pm->flags)) &
+	     (PM_INTEGER|PM_EFLOAT|PM_FFLOAT|PM_HASHED|
+	      PM_ARRAY|PM_TIED|PM_AUTOLOAD);
+	/* keep the parameter if just switching between floating types */
+	if ((tc = chflags && chflags != (PM_EFLOAT|PM_FFLOAT)))
+	    usepm = 0;
+    }
+    if (tc && (pm->flags & PM_SPECIAL)) {
+	zerrnam(cname, "%s: can't change type of a special parameter",
+		pname, 0);
+	return NULL;
+    }
+
+    /*
+     * According to the manual, local parameters don't get exported.
+     * A parameter will be local if
+     * 1. we are re-using an existing local parameter
+     *    or
+     * 2. we are not using an existing parameter, but
+     *   i. there is already a parameter, which will be hidden
+     *     or
+     *   ii. we are creating a new local parameter
+     */
+    if ((usepm && pm->level) ||
+	(!usepm && (pm || (locallevel && (on & PM_LOCAL)))))
+	on &= ~PM_EXPORTED;
+
+    if (usepm) {
+	on &= ~PM_LOCAL;
+	if (!on && !roff && !value) {
+	    paramtab->printnode((HashNode)pm, 0);
+	    return pm;
+	}
+	if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+	    zerrnam(cname, "%s: restricted", pname, 0);
+	    return pm;
+	}
+	if ((on & PM_UNIQUE) && !(pm->flags & PM_READONLY & ~off)) {
+	    Param apm;
+	    if (PM_TYPE(pm->flags) == PM_ARRAY)
+		uniqarray((*pm->gets.afn) (pm));
+	    else if (PM_TYPE(pm->flags) == PM_SCALAR && pm->ename &&
+		     (apm = (Param) paramtab->getnode(paramtab, pm->ename)))
+		uniqarray((*apm->gets.afn) (apm));
+	}
+	pm->flags = (pm->flags | on) & ~(off | PM_UNSET);
+	/* This auxlen/pm->ct stuff is a nasty hack. */
+	if ((on & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z | PM_INTEGER |
+		   PM_EFLOAT | PM_FFLOAT)) &&
+	    auxlen)
+	    pm->ct = auxlen;
+	if (!(pm->flags & (PM_ARRAY|PM_HASHED))) {
+	    if (pm->flags & PM_EXPORTED) {
+		if (!(pm->flags & PM_UNSET) && !pm->env && !value)
+		    pm->env = addenv(pname, getsparam(pname), pm->flags);
+	    } else if (pm->env &&
+		       (!pm->level || (isset(ALLEXPORT) && !pm->old))) {
+		delenv(pm->env);
+		zsfree(pm->env);
+		pm->env = NULL;
+	    }
+	    if (value)
+		setsparam(pname, ztrdup(value));
+	} else if (value) {
+	    zwarnnam(cname, "can't assign new value for array %s", pname, 0);
+	    return NULL;
+	}
+	return pm;
+    }
+
+    /*
+     * We're here either because we're creating a new parameter,
+     * or we're adding a parameter at a different local level,
+     * or we're converting the type of a parameter.  In the
+     * last case only, we need to delete the old parameter.
+     */
+    if (tc) {
+	/* Maintain existing readonly/exported status... */
+	on |= ~off & (PM_READONLY|PM_EXPORTED) & pm->flags;
+	/* ...but turn off existing readonly so we can delete it */
+	pm->flags &= ~PM_READONLY;
+	/*
+	 * If we're just changing the type, we should keep the
+	 * variable at the current level of localness.
+	 */
+	keeplocal = pm->level;
+	/*
+	 * Try to carry over a value, but not when changing from,
+	 * to, or between non-scalar types.
+	 */
+	if (!value && !((pm->flags|on) & (PM_ARRAY|PM_HASHED)))
+	    value = dupstring(getsparam(pname));
+	/* pname may point to pm->nam which is about to disappear */
+	pname = dupstring(pname);
+	unsetparam_pm(pm, 0, 1);
+    }
+
+    if (newspecial) {
+	Param tpm, pm2;
+	if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+	    zerrnam(cname, "%s: restricted", pname, 0);
+	    return pm;
+	}
+	/*
+	 * For specials, we keep the same struct but zero everything.
+	 * Maybe it would be easier to create a new struct but copy
+	 * the get/set methods.
+	 */
+	tpm = (Param) zalloc(sizeof *tpm);
+
+	tpm->nam = pm->nam;
+	if (pm->ename &&
+	    (pm2 = (Param) paramtab->getnode(paramtab, pm->ename)) &&
+	    pm2->level == locallevel) {
+	    /* This is getting silly, but anyway:  if one of a path/PATH
+	     * pair has already been made local at the current level, we
+	     * have to make sure that the other one does not have its value
+	     * saved:  since that comes from an internal variable it will
+	     * already reflect the local value, so restoring it on exit
+	     * would be wrong.
+	     *
+	     * This problem is also why we make sure we have a copy
+	     * of the environment entry in tpm->env, rather than relying
+	     * on the restored value to provide it.
+	     */
+	    tpm->flags = pm->flags | PM_NORESTORE;
+	} else {
+	    copyparam(tpm, pm, 1);
+	}
+	tpm->old = pm->old;
+	tpm->level = pm->level;
+	tpm->ct = pm->ct;
+	tpm->env = pm->env;
+
+	pm->old = tpm;
+	/*
+	 * The remaining on/off flags should be harmless to use,
+	 * because we've checked for unpleasant surprises above.
+	 */
+	pm->flags = (PM_TYPE(pm->flags) | on | PM_SPECIAL) & ~off;
+	/*
+	 * Final tweak: if we've turned on one of the flags with
+	 * numbers, we should use the appropriate integer.
+	 */
+	if (on & (PM_LEFT|PM_RIGHT_B|PM_RIGHT_Z|PM_INTEGER|
+		  PM_EFLOAT|PM_FFLOAT))
+	    pm->ct = auxlen;
+	else
+	    pm->ct = 0;
+	pm->env = NULL;
+    } else {
+	/*
+	 * Create a new node for a parameter with the flags in `on' minus the
+	 * readonly flag
+	 */
+	pm = createparam(pname, on & ~PM_READONLY);
+	DPUTS(!pm, "BUG: parameter not created");
+	pm->ct = auxlen;
+    }
+
+    if (altpm && PM_TYPE(pm->flags) == PM_SCALAR) {
+	/*
+	 * It seems safer to set this here than in createparam(),
+	 * to make sure we only ever use the colonarr functions
+	 * when u.data is correctly set.
+	 */
+	pm->sets.cfn = colonarrsetfn;
+	pm->gets.cfn = colonarrgetfn;
+	pm->u.data = &altpm->u.arr;
+    }
+
+    if (keeplocal)
+	pm->level = keeplocal;
+    else if (on & PM_LOCAL)
+	pm->level = locallevel;
+    if (value && !(pm->flags & (PM_ARRAY|PM_HASHED)))
+	setsparam(pname, ztrdup(value));
+    else if (newspecial && !(pm->old->flags & PM_NORESTORE)) {
+	/*
+	 * We need to use the special setting function to re-initialise
+	 * the special parameter to empty.
+	 */
+	switch (PM_TYPE(pm->flags)) {
+	case PM_SCALAR:
+	    pm->sets.cfn(pm, ztrdup(""));
+	    break;
+	case PM_INTEGER:
+	    pm->sets.ifn(pm, 0);
+	    break;
+	case PM_EFLOAT:
+	case PM_FFLOAT:
+	    pm->sets.ffn(pm, 0.0);
+	    break;
+	case PM_ARRAY:
+	    pm->sets.afn(pm, mkarray(NULL));
+	    break;
+	case PM_HASHED:
+	    pm->sets.hfn(pm, newparamtable(17, pm->nam));
+	    break;
+	}
+    }
+    pm->flags |= (on & PM_READONLY);
+    if (value && (pm->flags & (PM_ARRAY|PM_HASHED))) {
+	zerrnam(cname, "%s: can't assign initial value for array", pname, 0);
+	/* the only safe thing to do here seems to be unset the param */
+	unsetparam_pm(pm, 0, 1);
+	return NULL;
+    }
+
+    return pm;
+}
+
 /* declare, export, integer, local, readonly, typeset */
 
 /**/
@@ -1473,10 +1794,10 @@ 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;
-    int initon, initoff, of, i;
+    Patprog pprog;
+    char *optstr = TYPESET_OPTSTR;
+    int on = 0, off = 0, roff, bit = PM_ARRAY;
+    int i;
     int returnval = 0, printflags = 0;
 
     /* hash -f is really the builtin `functions' */
@@ -1487,25 +1808,40 @@ bin_typeset(char *name, char **argv, char *ops, int func)
      * Unfortunately, this depends on the order *
      * these flags are defined in zsh.h         */
     for (; *optstr; optstr++, bit <<= 1)
-	if (ops[*optstr] == 1)
+	if (ops[STOUC(*optstr)] == 1)
 	    on |= bit;
-	else if (ops[*optstr] == 2)
+	else if (ops[STOUC(*optstr)] == 2)
 	    off |= bit;
     roff = off;
 
     /* Sanity checks on the options.  Remove conficting options. */
+    if (on & PM_FFLOAT) {
+	off |= PM_RIGHT_B | PM_LEFT | PM_RIGHT_Z | PM_UPPER | PM_ARRAY |
+	    PM_HASHED | PM_INTEGER | PM_EFLOAT;
+	/* Allow `float -F' to work even though float sets -E by default */
+	on &= ~PM_EFLOAT;
+    }
+    if (on & PM_EFLOAT)
+	off |= PM_RIGHT_B | PM_LEFT | PM_RIGHT_Z | PM_UPPER | PM_ARRAY |
+	    PM_HASHED | PM_INTEGER | PM_FFLOAT;
     if (on & PM_INTEGER)
-	off |= PM_RIGHT_B | PM_LEFT | PM_RIGHT_Z | PM_UPPER | PM_ARRAY;
+	off |= PM_RIGHT_B | PM_LEFT | PM_RIGHT_Z | PM_UPPER | PM_ARRAY |
+	    PM_HASHED | PM_EFLOAT | PM_FFLOAT;
     if (on & PM_LEFT)
-	off |= PM_RIGHT_B | PM_INTEGER;
+	off |= PM_RIGHT_B | PM_INTEGER | PM_EFLOAT | PM_FFLOAT;
     if (on & PM_RIGHT_B)
-	off |= PM_LEFT | PM_INTEGER;
+	off |= PM_LEFT | PM_INTEGER | PM_EFLOAT | PM_FFLOAT;
     if (on & PM_RIGHT_Z)
-	off |= PM_INTEGER;
+	off |= PM_INTEGER | PM_EFLOAT | PM_FFLOAT;
     if (on & PM_UPPER)
 	off |= PM_LOWER;
     if (on & PM_LOWER)
 	off |= PM_UPPER;
+    if (on & PM_HASHED)
+	off |= PM_ARRAY;
+    if (on & PM_TIED)
+	off |= PM_INTEGER | PM_EFLOAT | PM_FFLOAT | PM_ARRAY | PM_HASHED;
+
     on &= ~off;
 
     /* Given no arguments, list whatever the options specify. */
@@ -1518,148 +1854,171 @@ bin_typeset(char *name, char **argv, char *ops, int func)
 	return 0;
     }
 
+    if (!ops['g'] && !ops['x'])
+	on |= PM_LOCAL;
+
+    if (on & PM_TIED) {
+	Param apm;
+	struct asgment asg0;
+	char *oldval = NULL;
+
+	if (ops['m']) {
+	    zwarnnam(name, "incompatible options for -T", NULL, 0);
+	    return 1;
+	}
+	on &= ~off;
+	if (!argv[1] || argv[2]) {
+	    zwarnnam(name, "-T requires names of scalar and array", NULL, 0);
+	    return 1;
+	}
+
+	if (!(asg = getasg(argv[0])))
+	    return 1;
+	asg0 = *asg;
+	if (!(asg = getasg(argv[1])))
+	    return 1;
+	if (!strcmp(asg0.name, asg->name)) {
+	    zerrnam(name, "can't tie a variable to itself", NULL, 0);
+	    return 1;
+	}
+	/*
+	 * Keep the old value of the scalar.  We need to do this
+	 * here as if it is already tied to the same array it
+	 * will be unset when we retie the array.  This is all
+	 * so that typeset -T is idempotent.
+	 *
+	 * We also need to remember here whether the damn thing is
+	 * exported and pass that along.  Isn't the world complicated?
+	 */
+	if ((pm = (Param) paramtab->getnode(paramtab, asg0.name))
+	    && !(pm->flags & PM_UNSET)
+	    && (locallevel == pm->level || !(on & PM_LOCAL))) {
+	    if (!asg0.value && !(PM_TYPE(pm->flags) & (PM_ARRAY|PM_HASHED)))
+		oldval = ztrdup(getsparam(asg0.name));
+	    on |= (pm->flags & PM_EXPORTED);
+	}
+	/*
+	 * Create the tied array; this is normal except that
+	 * it has the PM_TIED flag set.  Do it first because
+	 * we need the address.
+	 */
+	if (!(apm=typeset_single(name, asg->name,
+				 (Param)paramtab->getnode(paramtab,
+							  asg->name),
+				 func, (on | PM_ARRAY) & ~PM_EXPORTED,
+				 off, roff, asg->value, NULL)))
+	    return 1;
+
+	/*
+	 * Create the tied colonarray.  We make it as a normal scalar
+	 * and fix up the oddities later.
+	 */
+	if (!(pm=typeset_single(name, asg0.name,
+				(Param)paramtab->getnode(paramtab,
+							 asg0.name),
+				func, on, off, roff, asg0.value, apm))) {
+	    if (oldval)
+		zsfree(oldval);
+	    unsetparam_pm(apm, 1, 1);
+	    return 1;
+	}
+
+	pm->ename = ztrdup(asg->name);
+	apm->ename = ztrdup(asg0.name);
+	if (oldval)
+	    setsparam(asg0.name, oldval);
+
+	return 0;
+    }
+    if (off & PM_TIED) {
+	zerrnam(name, "use unset to remove tied variables", NULL, 0);
+	return 1;
+    }
+
     /* With the -m option, treat arguments as glob patterns */
     if (ops['m']) {
 	while ((asg = getasg(*argv++))) {
+	    LinkList pmlist = newlinklist();
+	    LinkNode pmnode;
+
 	    tokenize(asg->name);   /* expand argument */
-	    if (!(com = parsereg(asg->name))) {
+	    if (!(pprog = patcompile(asg->name, 0, NULL))) {
 		untokenize(asg->name);
 		zwarnnam(name, "bad pattern : %s", argv[-1], 0);
 		returnval = 1;
 		continue;
 	    }
-	    /* If no options or values are given, display all *
-	     * parameters matching the glob pattern.          */
-	    if (!(on || roff || asg->value)) {
-		scanmatchtable(paramtab, com, 0, 0, paramtab->printnode, 0);
-		continue;
-	    }
-	    /* Since either options or values are given, we search   *
-	     * through the parameter table and change all parameters *
-	     * matching the glob pattern to have these flags and/or  *
-	     * value.                                                */
+	    /*
+	     * Search through the parameter table and change all parameters
+	     * matching the glob pattern to have these flags and/or value.
+	     * Bad news:  if the parameter gets altered, e.g. by
+	     * a type conversion, then paramtab can be shifted around,
+	     * so we need to store the parameters to alter on a separate
+	     * list for later use.	     
+	     */
 	    for (i = 0; i < paramtab->hsize; i++) {
-		for (pm = (Param) paramtab->nodes[i]; pm; pm = (Param) pm->next) {
-		    if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED))
+		for (pm = (Param) paramtab->nodes[i]; pm;
+		     pm = (Param) pm->next) {
+		    if (((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) ||
+			(pm->flags & PM_UNSET))
 			continue;
-		    if (domatch(pm->nam, com, 0)) {
-			/* set up flags if we have any */
-			if (on || roff) {
-			    if (PM_TYPE(pm->flags) == PM_ARRAY && (on & PM_UNIQUE) &&
-				!(pm->flags & PM_READONLY & ~off))
-				uniqarray((*pm->gets.afn) (pm));
-			    pm->flags = (pm->flags | on) & ~off;
-			    if (PM_TYPE(pm->flags) != PM_ARRAY) {
-				if ((on & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z | PM_INTEGER)) && auxlen)
-				    pm->ct = auxlen;
-				/* did we just export this? */
-				if ((pm->flags & PM_EXPORTED) && !pm->env) {
-				    pm->env = addenv(pm->nam, (asg->value) ? asg->value : getsparam(pm->nam));
-				} else if (!(pm->flags & PM_EXPORTED) && pm->env) {
-				/* did we just unexport this? */
-				    delenv(pm->env);
-				    zsfree(pm->env);
-				    pm->env = NULL;
-				}
-			    }
-			}
-			/* set up a new value if given */
-			if (asg->value) {
-			    setsparam(pm->nam, ztrdup(asg->value));
-			}
-		    }
+		    if (pattry(pprog, pm->nam))
+			addlinknode(pmlist, pm);
 		}
 	    }
+	    for (pmnode = firstnode(pmlist); pmnode; incnode(pmnode)) {
+		pm = (Param) getdata(pmnode);
+		if (!typeset_single(name, pm->nam, pm, func, on, off, roff,
+				    asg->value, NULL))
+		    returnval = 1;
+	    }
 	}
 	return returnval;
     }
 
-    /* Save the values of on, off, and func */
-    initon = on;
-    initoff = off;
-    of = func;
-
     /* Take arguments literally.  Don't glob */
     while ((asg = getasg(*argv++))) {
-	/* restore the original values of on, off, and func */
-	on = initon;
-	off = initoff;
-	func = of;
-	on &= ~PM_ARRAY;
-
 	/* check if argument is a valid identifier */
 	if (!isident(asg->name)) {
 	    zerr("not an identifier: %s", asg->name, 0);
 	    returnval = 1;
 	    continue;
 	}
-	bit = 0;    /* flag for switching int<->not-int */
-	if ((pm = (Param)paramtab->getnode(paramtab, asg->name)) &&
-	    (((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)))) {
-	    /* if no flags or values are given, just print this parameter */
-	    if (!on && !roff && !asg->value) {
-		paramtab->printnode((HashNode) pm, 0);
-		continue;
-	    }
-	    if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
-		zerrnam(name, "%s: restricted", pm->nam, 0);
-		returnval = 1;
-		continue;
-	    }
-	    if((pm->flags & PM_SPECIAL) &&
-	       PM_TYPE((pm->flags | on) & ~off) != PM_TYPE(pm->flags)) {
-		zerrnam(name, "%s: cannot change type of a special parameter",
-		    pm->nam, 0);
-		returnval = 1;
-		continue;
-	    }
-	    if (PM_TYPE(pm->flags) == PM_ARRAY && (on & PM_UNIQUE) &&
-		!(pm->flags & PM_READONLY & ~off))
-		uniqarray((*pm->gets.afn) (pm));
-	    pm->flags = (pm->flags | on) & ~off;
-	    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->flags & PM_EXPORTED) {
-		    if (!(pm->flags & PM_UNSET) && !pm->env && !asg->value)
-			pm->env = addenv(asg->name, getsparam(asg->name));
-		} else if (pm->env) {
-		    delenv(pm->env);
-		    zsfree(pm->env);
-		    pm->env = NULL;
-		}
-		if (asg->value)
-		    setsparam(asg->name, ztrdup(asg->value));
-	    }
-	} else {
-	    if (bit) {
-		if (pm->flags & PM_READONLY) {
-		    on |= ~off & PM_READONLY;
-		    pm->flags &= ~PM_READONLY;
-		}
-		if (!asg->value)
-		    asg->value = dupstring(getsparam(asg->name));
-		unsetparam(asg->name);
-	    }
-	    /* create a new node for a parameter with the *
-	     * flags in `on' minus the readonly flag      */
-	    pm = createparam(ztrdup(asg->name), on & ~PM_READONLY);
-	    DPUTS(!pm, "BUG: parameter not created");
-	    pm->ct = auxlen;
-	    if (func != BIN_EXPORT)
-		pm->level = locallevel;
-	    if (asg->value)
-		setsparam(asg->name, ztrdup(asg->value));
-	    pm->flags |= (on & PM_READONLY);
-	}
+	if (!typeset_single(name, asg->name,
+			    (Param) (paramtab == realparamtab ?
+				     gethashnode2(paramtab, asg->name) :
+				     paramtab->getnode(paramtab, asg->name)),
+			    func, on, off, roff, asg->value, NULL))
+	    returnval = 1;
     }
     return returnval;
 }
 
+/* Helper for bin_functions() when run as "autoload -X" */
+
+/**/
+int
+eval_autoload(Shfunc shf, char *name, char *ops, int func)
+{
+    if (!(shf->flags & PM_UNDEFINED))
+	return 1;
+
+    if (shf->funcdef) {
+	freeeprog(shf->funcdef);
+	shf->funcdef = &dummy_eprog;
+    }
+    if (ops['X'] == 1) {
+	char *fargv[3];
+	fargv[0] = name;
+	fargv[1] = "\"$@\"";
+	fargv[2] = 0;
+	shf->funcdef = mkautofn(shf);
+	return bin_eval(name, fargv, ops, func);
+    }
+
+    return !loadautofn(shf, (ops['k'] ? 2 : (ops['z'] ? 0 : 1)), 1);
+}
+
 /* Display or change the attributes of shell functions.   *
  * If called as autoload, it will define a new autoloaded *
  * (undefined) shell function.                            */
@@ -1668,34 +2027,55 @@ bin_typeset(char *name, char **argv, char *ops, int func)
 int
 bin_functions(char *name, char **argv, char *ops, int func)
 {
-    Comp com;
+    Patprog pprog;
     Shfunc shf;
     int i, returnval = 0;
-    int on = 0, off = 0;
+    int on = 0, off = 0, pflags = 0;
 
     /* Do we have any flags defined? */
-    if (ops['u'] || ops['t']) {
-	if (ops['u'] == 1)
-	    on |= PM_UNDEFINED;
-	else if (ops['u'] == 2)
-	    off |= PM_UNDEFINED;
-
-	if (ops['t'] == 1)
-	    on |= PM_TAGGED;
-	else if (ops['t'] == 2)
-	    off |= PM_TAGGED;
-    }
-
-    if (off & PM_UNDEFINED) {
+    if (ops['u'] == 2)
+	off |= PM_UNDEFINED;
+    else if (ops['u'] == 1 || ops['X'])
+	on |= PM_UNDEFINED;
+    if (ops['U'] == 1)
+	on |= PM_UNALIASED|PM_UNDEFINED;
+    else if (ops['U'] == 2)
+	off |= PM_UNALIASED;
+    if (ops['t'] == 1)
+	on |= PM_TAGGED;
+    else if (ops['t'] == 2)
+	off |= PM_TAGGED;
+
+    if ((off & PM_UNDEFINED) || (ops['k'] && ops['z']) ||
+	(ops['X'] != 2 && (ops['k'] || ops['z'])) ||
+	(ops['X'] == 1 && (ops['m'] || *argv || !scriptname))) {
 	zwarnnam(name, "invalid option(s)", NULL, 0);
 	return 1;
     }
 
+    if (ops['f'] == 2 || ops['+'])
+	pflags |= PRINT_NAMEONLY;
+
     /* If no arguments given, we will print functions.  If flags *
      * are given, we will print only functions containing these  *
      * flags, else we'll print them all.                         */
     if (!*argv) {
-	scanhashtable(shfunctab, 1, on|off, DISABLED, shfunctab->printnode, 0);
+	if (ops['X'] == 1) {
+	    if ((shf = (Shfunc) shfunctab->getnode(shfunctab, scriptname))) {
+		DPUTS(!shf->funcdef,
+		      "BUG: Calling autoload from empty function");
+	    } else {
+		shf = (Shfunc) zcalloc(sizeof *shf);
+		shfunctab->addnode(shfunctab, ztrdup(scriptname), shf);
+	    }
+	    shf->flags = on;
+	    return eval_autoload(shf, scriptname, ops, func);
+	} else {
+	    if (ops['U'] && !ops['u'])
+		on &= ~PM_UNDEFINED;
+	    scanhashtable(shfunctab, 1, on|off, DISABLED, shfunctab->printnode,
+			  pflags);
+	}
 	return 0;
     }
 
@@ -1705,16 +2085,25 @@ bin_functions(char *name, char **argv, char *ops, int func)
 	for (; *argv; argv++) {
 	    /* expand argument */
 	    tokenize(*argv);
-	    if ((com = parsereg(*argv))) {
+	    if ((pprog = patcompile(*argv, PAT_STATIC, 0))) {
 		/* with no options, just print all functions matching the glob pattern */
 		if (!(on|off)) {
-		    scanmatchtable(shfunctab, com, 0, DISABLED, shfunctab->printnode, 0);
+		    scanmatchtable(shfunctab, pprog, 0, DISABLED,
+				   shfunctab->printnode, pflags);
 		} else {
 		/* apply the options to all functions matching the glob pattern */
 		    for (i = 0; i < shfunctab->hsize; i++) {
-			for (shf = (Shfunc) shfunctab->nodes[i]; shf; shf = (Shfunc) shf->next)
-			    if (domatch(shf->nam, com, 0) && !(shf->flags & DISABLED))
-				shf->flags = (shf->flags | on) & (~off);
+			for (shf = (Shfunc) shfunctab->nodes[i]; shf;
+			     shf = (Shfunc) shf->next)
+			    if (pattry(pprog, shf->nam) &&
+				!(shf->flags & DISABLED)) {
+				shf->flags = (shf->flags |
+					      (on & ~PM_UNDEFINED)) & ~off;
+				if (ops['X'] &&
+				    eval_autoload(shf, shf->nam, ops, func)) {
+				    returnval = 1;
+				}
+			    }
 		    }
 		}
 	    } else {
@@ -1728,14 +2117,21 @@ bin_functions(char *name, char **argv, char *ops, int func)
 
     /* Take the arguments literally -- do not glob */
     for (; *argv; argv++) {
-	if ((shf = (Shfunc) shfunctab->getnode(shfunctab, *argv))) {
+	if (ops['w']) {
+	    if (dump_autoload(*argv, on, ops, func)) {
+		zwarnnam(name, "invalid wordcode file: %s", *argv, 0);
+		returnval = 1;
+	    }
+	} else if ((shf = (Shfunc) shfunctab->getnode(shfunctab, *argv))) {
 	    /* if any flag was given */
-	    if (on|off)
+	    if (on|off) {
 		/* turn on/off the given flags */
 		shf->flags = (shf->flags | (on & ~PM_UNDEFINED)) & ~off;
-	    else
+		if (ops['X'] && eval_autoload(shf, shf->nam, ops, func))
+		    returnval = 1;
+	    } else
 		/* no flags, so just print */
-		shfunctab->printnode((HashNode) shf, 0);
+		shfunctab->printnode((HashNode) shf, pflags);
 	} else if (on & PM_UNDEFINED) {
 	    /* Add a new undefined (autoloaded) function to the *
 	     * hash table with the corresponding flags set.     */
@@ -1743,6 +2139,8 @@ bin_functions(char *name, char **argv, char *ops, int func)
 	    shf->flags = on;
 	    shf->funcdef = mkautofn(shf);
 	    shfunctab->addnode(shfunctab, ztrdup(*argv), shf);
+	    if (ops['X'] && eval_autoload(shf, shf->nam, ops, func))
+		returnval = 1;
 	} else
 	    returnval = 1;
     }
@@ -1750,30 +2148,28 @@ bin_functions(char *name, char **argv, char *ops, int func)
 }
 
 /**/
-static List
+Eprog
 mkautofn(Shfunc shf)
 {
-    List l;
-    Sublist s;
-    Pline p;
-    Cmd c;
-    AutoFn a;
-    PERMALLOC {
-	a = (AutoFn)allocnode(N_AUTOFN);
-	a->shf = shf;
-	c = (Cmd)allocnode(N_CMD);
-	c->type = AUTOFN;
-	c->u.autofn = a;
-	p = (Pline)allocnode(N_PLINE);
-	p->left = c;
-	p->type = END;
-	s = (Sublist)allocnode(N_SUBLIST);
-	s->left = p;
-	l = (List)allocnode(N_LIST);
-	l->left = s;
-	l->type = Z_SYNC;
-    } LASTALLOC;
-    return l;
+    Eprog p;
+
+    p = (Eprog) zalloc(sizeof(*p));
+    p->len = 5 * sizeof(wordcode);
+    p->prog = (Wordcode) zalloc(p->len);
+    p->strs = NULL;
+    p->shf = shf;
+    p->npats = 0;
+    p->pats = NULL;
+    p->flags = EF_REAL;
+    p->dump = NULL;
+
+    p->prog[0] = WCB_LIST((Z_SYNC | Z_END), 0);
+    p->prog[1] = WCB_SUBLIST(WC_SUBLIST_END, 0, 3);
+    p->prog[2] = WCB_PIPE(WC_PIPE_END, 0);
+    p->prog[3] = WCB_AUTOFN();
+    p->prog[4] = WCB_END();
+
+    return p;
 }
 
 /* unset: unset parameters */
@@ -1783,7 +2179,7 @@ int
 bin_unset(char *name, char **argv, char *ops, int func)
 {
     Param pm, next;
-    Comp com;
+    Patprog pprog;
     char *s;
     int match = 0, returnval = 0;
     int i;
@@ -1797,15 +2193,16 @@ bin_unset(char *name, char **argv, char *ops, int func)
 	while ((s = *argv++)) {
 	    /* expand */
 	    tokenize(s);
-	    if ((com = parsereg(s))) {
+	    if ((pprog = patcompile(s, PAT_STATIC, NULL))) {
 		/* Go through the parameter table, and unset any matches */
 		for (i = 0; i < paramtab->hsize; i++) {
 		    for (pm = (Param) paramtab->nodes[i]; pm; pm = next) {
 			/* record pointer to next, since we may free this one */
 			next = (Param) pm->next;
 			if ((!(pm->flags & PM_RESTRICTED) ||
-			    unset(RESTRICTED)) && domatch(pm->nam, com, 0)) {
-			    unsetparam(pm->nam);
+			    unset(RESTRICTED)) &&
+			    pattry(pprog, pm->nam)) {
+			    unsetparam_pm(pm, 0, 1);
 			    match++;
 			}
 		    }
@@ -1824,14 +2221,41 @@ bin_unset(char *name, char **argv, char *ops, int func)
 
     /* do not glob -- unset the given parameter */
     while ((s = *argv++)) {
-	pm = (Param) paramtab->getnode(paramtab, s);
+	char *ss = strchr(s, '[');
+	char *sse = ss;
+	if (ss) {
+	    if (skipparens('[', ']', &sse) || *sse) {
+		zerrnam(name, "%s: invalid parameter name", s, 0);
+		returnval = 1;
+		continue;
+	    }
+	    *ss = 0;
+	}
+	pm = (Param) (paramtab == realparamtab ?
+		      gethashnode2(paramtab, s) :
+		      paramtab->getnode(paramtab, s));
 	if (!pm)
 	    returnval = 1;
 	else if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
 	    zerrnam(name, "%s: restricted", pm->nam, 0);
 	    returnval = 1;
+	} else if (ss) {
+	    if (PM_TYPE(pm->flags) == PM_HASHED) {
+		HashTable tht = paramtab;
+		if ((paramtab = pm->gets.hfn(pm))) {
+		    *--sse = 0;
+		    unsetparam(ss+1);
+		    *sse = ']';
+		}
+		paramtab = tht;
+	    } else {
+		zerrnam(name, "%s: invalid element for unset", s, 0);
+		returnval = 1;
+	    }
 	} else
-	    unsetparam(s);
+	    unsetparam_pm(pm, 0, 1);
+	if (ss)
+	    *ss = '[';
     }
     return returnval;
 }
@@ -1843,7 +2267,7 @@ int
 bin_whence(char *nam, char **argv, char *ops, int func)
 {
     HashNode hn;
-    Comp com;
+    Patprog pprog;
     int returnval = 0;
     int printflags = 0;
     int csh, all, v, wd;
@@ -1872,7 +2296,7 @@ bin_whence(char *nam, char **argv, char *ops, int func)
 	for (; *argv; argv++) {
 	    /* parse the pattern */
 	    tokenize(*argv);
-	    if (!(com = parsereg(*argv))) {
+	    if (!(pprog = patcompile(*argv, PAT_STATIC, NULL))) {
 		untokenize(*argv);
 		zwarnnam(nam, "bad pattern : %s", *argv, 0);
 		returnval = 1;
@@ -1883,21 +2307,26 @@ bin_whence(char *nam, char **argv, char *ops, int func)
 		 * We're not using it, so search for ... */
 
 		/* aliases ... */
-		scanmatchtable(aliastab, com, 0, DISABLED, aliastab->printnode, printflags);
+		scanmatchtable(aliastab, pprog, 0, DISABLED,
+			       aliastab->printnode, printflags);
 
 		/* and reserved words ... */
-		scanmatchtable(reswdtab, com, 0, DISABLED, reswdtab->printnode, printflags);
+		scanmatchtable(reswdtab, pprog, 0, DISABLED,
+			       reswdtab->printnode, printflags);
 
 		/* and shell functions... */
-		scanmatchtable(shfunctab, com, 0, DISABLED, shfunctab->printnode, printflags);
+		scanmatchtable(shfunctab, pprog, 0, DISABLED,
+			       shfunctab->printnode, printflags);
 
 		/* and builtins. */
-		scanmatchtable(builtintab, com, 0, DISABLED, builtintab->printnode, printflags);
+		scanmatchtable(builtintab, pprog, 0, DISABLED,
+			       builtintab->printnode, printflags);
 	    }
 	    /* Done search for `internal' commands, if the -p option *
 	     * was not used.  Now search the path.                   */
 	    cmdnamtab->filltable(cmdnamtab);
-	    scanmatchtable(cmdnamtab, com, 0, 0, cmdnamtab->printnode, printflags);
+	    scanmatchtable(cmdnamtab, pprog, 0, 0,
+			   cmdnamtab->printnode, printflags);
 
 	}
     return returnval;
@@ -1979,7 +2408,7 @@ bin_whence(char *nam, char **argv, char *ops, int func)
 		puts(wd ? ": none" : " not found");
 		returnval = 1;
 	    }
-	} else if ((cnam = findcmd(*argv))) {
+	} else if ((cnam = findcmd(*argv, 1))) {
 	    /* Found external command. */
 	    if (wd) {
 		printf("%s: command\n", *argv);
@@ -1991,7 +2420,6 @@ bin_whence(char *nam, char **argv, char *ops, int func)
 		    print_if_link(cnam);
 		fputc('\n', stdout);
 	    }
-	    zsfree(cnam);
 	} else {
 	    /* Not found at all. */
 	    if (v || csh || wd)
@@ -2027,7 +2455,7 @@ int
 bin_hash(char *name, char **argv, char *ops, int func)
 {
     HashTable ht;
-    Comp com;
+    Patprog pprog;
     Asgment asg;
     int returnval = 0;
 
@@ -2065,9 +2493,9 @@ bin_hash(char *name, char **argv, char *ops, int func)
 	if (ops['m']) {
 	    /* with the -m option, treat the argument as a glob pattern */
 	    tokenize(*argv);  /* expand */
-	    if ((com = parsereg(*argv))) {
+	    if ((pprog = patcompile(*argv, PAT_STATIC, NULL))) {
 		/* display matching hash table elements */
-		scanmatchtable(ht, com, 0, 0, ht->printnode, 0);
+		scanmatchtable(ht, pprog, 0, 0, ht->printnode, 0);
 	    } else {
 		untokenize(*argv);
 		zwarnnam(name, "bad pattern : %s", *argv, 0);
@@ -2124,7 +2552,7 @@ bin_unhash(char *name, char **argv, char *ops, int func)
 {
     HashTable ht;
     HashNode hn, nhn;
-    Comp com;
+    Patprog pprog;
     int match = 0, returnval = 0;
     int i;
 
@@ -2144,13 +2572,13 @@ bin_unhash(char *name, char **argv, char *ops, int func)
 	for (; *argv; argv++) {
 	    /* expand argument */
 	    tokenize(*argv);
-	    if ((com = parsereg(*argv))) {
+	    if ((pprog = patcompile(*argv, PAT_STATIC, NULL))) {
 		/* remove all nodes matching glob pattern */
 		for (i = 0; i < ht->hsize; i++) {
 		    for (hn = ht->nodes[i]; hn; hn = nhn) {
 			/* record pointer to next, since we may free this one */
 			nhn = hn->next;
-			if (domatch(hn->nam, com, 0)) {
+			if (pattry(pprog, hn->nam)) {
 			    ht->freenode(ht->removenode(ht, hn->nam));
 			    match++;
 			}
@@ -2189,7 +2617,7 @@ int
 bin_alias(char *name, char **argv, char *ops, int func)
 {
     Alias a;
-    Comp com;
+    Patprog pprog;
     Asgment asg;
     int haveflags = 0, returnval = 0;
     int flags1 = 0, flags2 = DISABLED;
@@ -2210,6 +2638,8 @@ bin_alias(char *name, char **argv, char *ops, int func)
 
     if (ops['L'])
 	printflags |= PRINT_LIST;
+    else if (ops['r'] == 2 || ops['g'] == 2 || ops['m'] == 2 || ops['+'])
+	printflags |= PRINT_NAMEONLY;
 
     /* In the absence of arguments, list all aliases.  If a command *
      * line flag is specified, list only those of that type.        */
@@ -2223,9 +2653,10 @@ bin_alias(char *name, char **argv, char *ops, int func)
     if (ops['m']) {
 	for (; *argv; argv++) {
 	    tokenize(*argv);  /* expand argument */
-	    if ((com = parsereg(*argv))) {
+	    if ((pprog = patcompile(*argv, PAT_STATIC, NULL))) {
 		/* display the matching aliases */
-		scanmatchtable(aliastab, com, flags1, flags2, aliastab->printnode, printflags);
+		scanmatchtable(aliastab, pprog, flags1, flags2,
+			       aliastab->printnode, printflags);
 	    } else {
 		untokenize(*argv);
 		zwarnnam(name, "bad pattern : %s", *argv, 0);
@@ -2278,7 +2709,7 @@ bin_false(char *name, char **argv, char *ops, int func)
 /* the zle buffer stack */
  
 /**/
-LinkList bufstack;
+mod_export LinkList bufstack;
 
 /* echo, print, pushln */
 
@@ -2294,38 +2725,46 @@ bin_print(char *name, char **args, char *ops, int func)
     /* -m option -- treat the first argument as a pattern and remove
      * arguments not matching */
     if (ops['m']) {
-	Comp com;
+	Patprog pprog;
 	char **t, **p;
 
 	tokenize(*args);
-	if (!(com = parsereg(*args))) {
+	if (!(pprog = patcompile(*args, PAT_STATIC, NULL))) {
 	    untokenize(*args);
 	    zwarnnam(name, "bad pattern : %s", *args, 0);
 	    return 1;
 	}
 	for (p = ++args; *p; p++)
-	    if (!domatch(*p, com, 0))
+	    if (!pattry(pprog, *p))
 		for (t = p--; (*t = t[1]); t++);
     }
     /* compute lengths, and interpret according to -P, -D, -e, etc. */
     argc = arrlen(args);
-    len = (int *)ncalloc(argc * sizeof(int));
+    len = (int *) hcalloc(argc * sizeof(int));
     for(n = 0; n < argc; n++) {
 	/* first \ sequences */
 	if (!ops['e'] && (ops['R'] || ops['r'] || ops['E']))
 	    unmetafy(args[n], &len[n]);
 	else
-	    args[n] = getkeystring(args[n], &len[n],
-				    func != BIN_ECHO && !ops['e'], &nnl);
+	    args[n] = getkeystring(args[n], &len[n], ops['b'] ? 2 :
+				    (func != BIN_ECHO && !ops['e']), &nnl);
 	/* -P option -- interpret as a prompt sequence */
-	if(ops['P'])
-	    args[n] = unmetafy(promptexpand(metafy(args[n], len[n],
-		META_NOALLOC), 0, NULL, NULL), &len[n]);
+	if(ops['P']) {
+	    /*
+	     * promptexpand uses permanent storage: to avoid
+	     * messy memory management, stick it on the heap
+	     * instead.
+	     */
+	    char *str = unmetafy(promptexpand(metafy(args[n], len[n],
+				   META_NOALLOC), 0, NULL, NULL), &len[n]);
+	    args[n] = dupstring(str);
+	    free(str);
+	}
 	/* -D option -- interpret as a directory, and use ~ */
 	if(ops['D']) {
 	    Nameddir d = finddir(args[n]);
 	    if(d) {
-		char *arg = alloc(strlen(args[n]) + 1);
+		char *arg = zhalloc(strlen(args[n]) + 1);
 		sprintf(arg, "~%s%s", d->nam,
 			args[n] + strlen(d->dir));
 		args[n] = arg;
@@ -2336,9 +2775,7 @@ bin_print(char *name, char **args, char *ops, int func)
 
     /* -z option -- push the arguments onto the editing buffer stack */
     if (ops['z']) {
-	PERMALLOC {
-	    pushnode(bufstack, sepjoin(args, NULL));
-	} LASTALLOC;
+	zpushnode(bufstack, sepjoin(args, NULL, 0));
 	return 0;
     }
     /* -s option -- add the arguments to the history list */
@@ -2346,28 +2783,24 @@ bin_print(char *name, char **args, char *ops, int func)
 	int nwords = 0, nlen, iwords;
 	char **pargs = args;
 
-	PERMALLOC {
-	    ent = gethistent(++curhist);
-	    zsfree(ent->text);
-	    if (ent->nwords)
-		zfree(ent->words, ent->nwords*2*sizeof(short));
-	    while (*pargs++)
-		nwords++;
-	    if ((ent->nwords = nwords)) {
-		ent->words = (short *)zalloc(nwords*2*sizeof(short));
-		nlen = iwords = 0;
-		for (pargs = args; *pargs; pargs++) {
-		    ent->words[iwords++] = nlen;
-		    nlen += strlen(*pargs);
-		    ent->words[iwords++] = nlen;
-		    nlen++;
-		}
-	    } else
-		ent->words = (short *)NULL;
-	    ent->text = zjoin(args, ' ');
-	    ent->stim = ent->ftim = time(NULL);
-	    ent->flags = 0;
-	} LASTALLOC;
+	ent = prepnexthistent(++curhist);
+	while (*pargs++)
+	    nwords++;
+	if ((ent->nwords = nwords)) {
+	    ent->words = (short *)zalloc(nwords*2*sizeof(short));
+	    nlen = iwords = 0;
+	    for (pargs = args; *pargs; pargs++) {
+		ent->words[iwords++] = nlen;
+		nlen += strlen(*pargs);
+		ent->words[iwords++] = nlen;
+		nlen++;
+	    }
+	} else
+	    ent->words = (short *)NULL;
+	ent->text = zjoin(args, ' ', 0);
+	ent->stim = ent->ftim = time(NULL);
+	ent->flags = 0;
+	addhistnode(histtab, ent->text, ent);
 	return 0;
     }
     /* -u and -p -- output to other than standard output */
@@ -2534,7 +2967,7 @@ bin_shift(char *name, char **argv, char *ops, int func)
  
     /* optional argument can be either numeric or an array */
     if (*argv && !getaparam(*argv))
-        num = matheval(*argv++);
+        num = mathevali(*argv++);
  
     if (num < 0) {
         zwarnnam(name, "argument to shift must be non-negative", NULL, 0);
@@ -2549,9 +2982,7 @@ bin_shift(char *name, char **argv, char *ops, int func)
 		    ret++;
 		    continue;
 		}
-                PERMALLOC {
-		    s = arrdup(s + num);
-                } LASTALLOC;
+		s = zarrdup(s + num);
                 setaparam(*argv, s);
             }
     } else {
@@ -2570,6 +3001,9 @@ bin_shift(char *name, char **argv, char *ops, int func)
     return ret;
 }
 
+/**/
+int optcind;
+
 /* getopts: automagical option handling for shell scripts */
 
 /**/
@@ -2579,7 +3013,6 @@ bin_getopts(char *name, char **argv, char *ops, int func)
     int lenstr, lenoptstr, quiet, lenoptbuf;
     char *optstr = unmetafy(*argv++, &lenoptstr), *var = *argv++;
     char **args = (*argv) ? argv : pparams;
-    static int optcind = 0;
     char *str, optbuf[2] = " ", *p, opch;
 
     /* zoptind keeps count of the current argument number.  The *
@@ -2627,14 +3060,14 @@ bin_getopts(char *name, char **argv, char *ops, int func)
     if(opch == ':' || !(p = memchr(optstr, opch, lenoptstr))) {
 	p = "?";
 err:
-      zsfree(zoptarg);
+	zsfree(zoptarg);
+	setsparam(var, ztrdup(p));
 	if(quiet) {
-	    setsparam(var, ztrdup(p));
 	    zoptarg = metafy(optbuf, lenoptbuf, META_DUP);
 	} else {
 	    zerr(*p == '?' ? "bad option: -%c" :
 		"argument expected after -%c option", NULL, opch);
-          zoptarg=ztrdup("");
+	    zoptarg=ztrdup("");
 	    errflag = 0;
 	}
 	return 0;
@@ -2674,7 +3107,7 @@ bin_break(char *name, char **argv, char *ops, int func)
 
     /* handle one optional numeric argument */
     if (*argv) {
-	num = matheval(*argv++);
+	num = mathevali(*argv++);
 	nump = 1;
     }
 
@@ -2720,7 +3153,7 @@ bin_break(char *name, char **argv, char *ops, int func)
 /* we have printed a 'you have stopped (running) jobs.' message */
  
 /**/
-int stopmsg;
+mod_export int stopmsg;
  
 /* check to see if user has jobs running/stopped */
 
@@ -2754,42 +3187,45 @@ checkjobs(void)
  * because of a signal.                                    */
 
 /**/
-void
+mod_export void
 zexit(int val, int from_signal)
 {
     static int in_exit;
 
-    HEAPALLOC {
-	if (isset(MONITOR) && !stopmsg && !from_signal) {
-	    scanjobs();    /* check if jobs need printing           */
+    if (isset(MONITOR) && !stopmsg && !from_signal) {
+	scanjobs();    /* check if jobs need printing           */
+	if (isset(CHECKJOBS))
 	    checkjobs();   /* check if any jobs are running/stopped */
-	    if (stopmsg) {
-		stopmsg = 2;
-		LASTALLOC_RETURN;
-	    }
-	}
-	if (in_exit++ && from_signal)
+	if (stopmsg) {
+	    stopmsg = 2;
 	    LASTALLOC_RETURN;
-	if (isset(MONITOR))
-	    /* send SIGHUP to any jobs left running  */
-	    killrunjobs(from_signal);
-	if (isset(RCS) && interact) {
-	    if (!nohistsave)
-		savehistfile(getsparam("HISTFILE"), 1, isset(APPENDHISTORY) ? 3 : 0);
-	    if (islogin && !subsh) {
-		sourcehome(".zlogout");
+	}
+    }
+    if (in_exit++ && from_signal)
+	    return;
+
+    if (isset(MONITOR)) {
+	/* send SIGHUP to any jobs left running  */
+	killrunjobs(from_signal);
+    }
+    if (isset(RCS) && interact) {
+	if (!nohistsave)
+	    savehistfile(NULL, 1, HFILE_USE_OPTIONS);
+	if (islogin && !subsh) {
+	    sourcehome(".zlogout");
 #ifdef GLOBAL_ZLOGOUT
+	    if (isset(RCS) && isset(GLOBALRCS))
 		source(GLOBAL_ZLOGOUT);
 #endif
-	    }
 	}
-	if (sigtrapped[SIGEXIT])
-	    dotrap(SIGEXIT);
-	if (mypid != getpid())
-	    _exit(val);
-	else
-	    exit(val);
-    } LASTALLOC;
+    }
+    if (sigtrapped[SIGEXIT])
+	dotrap(SIGEXIT);
+    runhookdef(EXITHOOK, NULL);
+    if (mypid != getpid())
+	_exit(val);
+    else
+	exit(val);
 }
 
 /* . (dot), source */
@@ -2808,11 +3244,9 @@ bin_dot(char *name, char **argv, char *ops, int func)
 	return 0;
     old = pparams;
     /* get arguments for the script */
-    if (argv[1]) {
-	PERMALLOC {
-	    pparams = arrdup(argv + 1);
-	} LASTALLOC;
-    }
+    if (argv[1])
+	pparams = zarrdup(argv + 1);
+
     enam = arg0 = ztrdup(*argv);
     if (isset(FUNCTIONARGZERO)) {
 	old0 = argzero;
@@ -2880,6 +3314,8 @@ int
 bin_emulate(char *nam, char **argv, char *ops, int func)
 {
     emulate(*argv, ops['R']);
+    if (ops['L'])
+	opts[LOCALOPTIONS] = opts[LOCALTRAPS] = 1;
     return 0;
 }
 
@@ -2889,19 +3325,14 @@ bin_emulate(char *nam, char **argv, char *ops, int func)
 int
 bin_eval(char *nam, char **argv, char *ops, int func)
 {
-    List list;
-
-    inpush(zjoin(argv, ' '), 0, NULL);
-    strinbeg();
-    stophist = 2;
-    list = parse_list();
-    strinend();
-    inpop();
-    if (!list) {
+    Eprog prog;
+
+    prog = parse_string(zjoin(argv, ' ', 1), 0);
+    if (!prog) {
 	errflag = 0;
 	return 1;
     }
-    execlist(list, 1, 0);
+    execode(prog, 1, 0);
     if (errflag) {
 	lastval = errflag;
 	errflag = 0;
@@ -2928,7 +3359,7 @@ bin_read(char *name, char **args, char *ops, int func)
     char *reply, *readpmpt;
     int bsiz, c = 0, gotnl = 0, al = 0, first, nchars = 1, bslash;
     int haso = 0;	/* true if /dev/tty has been opened specially */
-    int isem = !strcmp(term, "emacs");
+    int isem = !strcmp(term, "emacs"), izle = zleactive && getkeyptr;
     char *buf, *bptr, *firstarg, *zbuforig;
     LinkList readll = newlinklist();
 
@@ -2937,12 +3368,11 @@ bin_read(char *name, char **args, char *ops, int func)
 	    nchars = 1;
 	args++;
     }
-
-    firstarg = *args;
-    if (*args && **args == '?')
-	args++;
-    /* default result parameter */
+    /* This `*args++ : *args' looks a bit weird, but it works around a bug
+     * in gcc-2.8.1 under DU 4.0. */
+    firstarg = (*args && **args == '?' ? *args++ : *args);
     reply = *args ? *args++ : ops['A'] ? "reply" : "REPLY";
+
     if (ops['A'] && *args) {
 	zwarnnam(name, "only one array argument allowed", NULL, 0);
 	return 1;
@@ -2953,33 +3383,37 @@ bin_read(char *name, char **args, char *ops, int func)
 	return compctlread(name, args, ops, reply);
 
     if ((ops['k'] && !ops['u'] && !ops['p']) || ops['q']) {
-	if (SHTTY == -1) {
-	    /* need to open /dev/tty specially */
-	    SHTTY = open("/dev/tty", O_RDWR|O_NOCTTY);
-	    haso = 1;
-	}
-	/* We should have a SHTTY opened by now. */
-	if (SHTTY == -1) {
-	    /* Unfortunately, we didn't. */
-	    fprintf(stderr, "not interactive and can't open terminal\n");
-	    fflush(stderr);
-	    return 1;
+	if (!zleactive) {
+	    if (SHTTY == -1) {
+		/* need to open /dev/tty specially */
+		SHTTY = open("/dev/tty", O_RDWR|O_NOCTTY);
+		haso = 1;
+	    }
+	    /* We should have a SHTTY opened by now. */
+	    if (SHTTY == -1) {
+		/* Unfortunately, we didn't. */
+		fprintf(stderr, "not interactive and can't open terminal\n");
+		fflush(stderr);
+		return 1;
+	    }
+	    if (unset(INTERACTIVE))
+		gettyinfo(&shttyinfo);
+	    /* attach to the tty */
+	    attachtty(mypgrp);
+	    if (!isem && ops['k'])
+		setcbreak();
+	    readfd = SHTTY;
 	}
-	if (unset(INTERACTIVE))
-	    gettyinfo(&shttyinfo);
-	/* attach to the tty */
-	attachtty(mypgrp);
-	if (!isem && ops['k'])
-	    setcbreak();
-	readfd = SHTTY;
     } else if (ops['u'] && !ops['p']) {
 	/* -u means take input from the specified file descriptor. *
 	 * -up means take input from the coprocess.                */
 	for (readfd = 9; readfd && !ops[readfd + '0']; --readfd);
-    } else if (ops['p'])
+	izle = 0;
+    } else if (ops['p']) {
 	readfd = coprocin;
-    else
-	readfd = 0;
+	izle = 0;
+    } else
+	readfd = izle = 0;
 
     /* handle prompt */
     if (firstarg) {
@@ -3003,18 +3437,25 @@ bin_read(char *name, char **args, char *ops, int func)
 	bptr = buf = (char *)zalloc(nchars+1);
 
 	do {
-	    /* If read returns 0, is end of file */
-	    if ((val = read(readfd, bptr, nchars)) <= 0)
-		break;
+	    if (izle) {
+		if ((val = getkeyptr(0)) < 0)
+		    break;
+		*bptr++ = (char) val;
+		nchars--;
+	    } else {
+		/* If read returns 0, is end of file */
+		if ((val = read(readfd, bptr, nchars)) <= 0)
+		    break;
 	    
-	    /* decrement number of characters read from number required */
-	    nchars -= val;
+		/* decrement number of characters read from number required */
+		nchars -= val;
 
-	    /* increment pointer past read characters */
-	    bptr += val;
+		/* increment pointer past read characters */
+		bptr += val;
+	    }
 	} while (nchars > 0);
 	
-	if (!ops['u'] && !ops['p']) {
+	if (!izle && !ops['u'] && !ops['p']) {
 	    /* dispose of result appropriately, etc. */
 	    if (isem)
 		while (val > 0 && read(SHTTY, &d, 1) == 1 && d != '\n');
@@ -3043,14 +3484,19 @@ bin_read(char *name, char **args, char *ops, int func)
 	readbuf[1] = '\0';
 
 	/* get, and store, reply */
-	readbuf[0] = ((char)getquery(NULL, 0)) == 'y' ? 'y' : 'n';
+	if (izle) {
+	    int key = getkeyptr(0);
 
-	/* dispose of result appropriately, etc. */
-	if (haso) {
-	    close(SHTTY);
-	    SHTTY = -1;
-	}
+	    readbuf[0] = (key == 'y' ? 'y' : 'n');
+	} else {
+	    readbuf[0] = ((char)getquery(NULL, 0)) == 'y' ? 'y' : 'n';
 
+	    /* dispose of result appropriately, etc. */
+	    if (haso) {
+		close(SHTTY);
+		SHTTY = -1;
+	    }
+	}
 	if (ops['e'] || ops['E'])
 	    printf("%s\n", readbuf);
 	if (!ops['e'])
@@ -3072,7 +3518,7 @@ bin_read(char *name, char **args, char *ops, int func)
 	buf = bptr = (char *)zalloc(bsiz = 64);
 	/* get input, a character at a time */
 	while (!gotnl) {
-	    c = zread();
+	    c = zread(izle);
 	    /* \ at the end of a line indicates a continuation *
 	     * line, except in raw mode (-r option)            */
 	    if (bslash && c == '\n') {
@@ -3162,7 +3608,7 @@ bin_read(char *name, char **args, char *ops, int func)
     bslash = 0;
     if (!gotnl)
 	for (;;) {
-	    c = zread();
+	    c = zread(izle);
 	    /* \ at the end of a line introduces a continuation line, except in
 	    raw mode (-r option) */
 	    if (bslash && c == '\n') {
@@ -3171,13 +3617,14 @@ bin_read(char *name, char **args, char *ops, int func)
 	    }
 	    if (c == EOF || (c == '\n' && !zbuf))
 		break;
-	    if (!bslash && isep(c) && bptr == buf)
+	    if (!bslash && isep(c) && bptr == buf) {
 		if (iwsep(c))
 		    continue;
 		else if (!first) {
 		    first = 1;
 		    continue;
 		}
+	    }
 	    bslash = c == '\\' && !bslash && !ops['r'];
 	    if (bslash)
 		continue;
@@ -3225,12 +3672,17 @@ bin_read(char *name, char **args, char *ops, int func)
 
 /**/
 static int
-zread(void)
+zread(int izle)
 {
     char cc, retry = 0;
 
+    if (izle) {
+	int c = getkeyptr(0);
+
+	return (c < 0 ? EOF : c);
+    }
     /* use zbuf if possible */
-    if (zbuf)
+    if (zbuf) {
 	/* If zbuf points to anything, it points to the next character in the
 	buffer.  This may be a null byte to indicate EOF.  If reading from the
 	buffer, move on the buffer pointer. */
@@ -3238,6 +3690,7 @@ zread(void)
 	    return zbuf++, STOUC(*zbuf++ ^ 32);
 	else
 	    return (*zbuf) ? STOUC(*zbuf++) : EOF;
+    }
     for (;;) {
 	/* read a character from readfd */
 	switch (read(readfd, &cc, 1)) {
@@ -3305,7 +3758,8 @@ int
 bin_test(char *name, char **argv, char *ops, int func)
 {
     char **s;
-    Cond c;
+    Eprog prog;
+    struct estate state;
 
     /* if "test" was invoked as "[", it needs a matching "]" *
      * which is subsequently ignored                         */
@@ -3325,7 +3779,7 @@ bin_test(char *name, char **argv, char *ops, int func)
     tok = NULLTOK;
     condlex = testlex;
     testlex();
-    c = par_cond();
+    prog = parse_cond();
     condlex = yylex;
 
     if (errflag) {
@@ -3333,13 +3787,19 @@ bin_test(char *name, char **argv, char *ops, int func)
 	return 1;
     }
 
-    if (!c || tok == LEXERR) {
+    if (!prog || tok == LEXERR) {
 	zwarnnam(name, tokstr ? "parse error" : "argument expected", NULL, 0);
 	return 1;
     }
 
     /* syntax is OK, so evaluate */
-    return !evalcond(c);
+
+    state.prog = prog;
+    state.pc = prog->prog;
+    state.strs = prog->strs;
+
+
+    return !evalcond(&state);
 }
 
 /* display a time, provided in units of 1/60s, as minutes and seconds */
@@ -3374,7 +3834,7 @@ bin_times(char *name, char **argv, char *ops, int func)
 int
 bin_trap(char *name, char **argv, char *ops, int func)
 {
-    List l;
+    Eprog prog;
     char *arg, *s;
     int sig;
 
@@ -3396,7 +3856,7 @@ bin_trap(char *name, char **argv, char *ops, int func)
 		if (!sigfuncs[sig])
 		    printf("trap -- '' %s\n", sigs[sig]);
 		else {
-		    s = getpermtext((void *) dupstruct((void *) sigfuncs[sig]));
+		    s = getpermtext(sigfuncs[sig], NULL);
 		    printf("trap -- ");
 		    quotedzputs(s, stdout);
 		    printf(" %s\n", sigs[sig]);
@@ -3422,26 +3882,24 @@ bin_trap(char *name, char **argv, char *ops, int func)
     /* Sort out the command to execute on trap */
     arg = *argv++;
     if (!*arg)
-	l = NULL;
-    else if (!(l = parse_string(arg))) {
+	prog = &dummy_eprog;
+    else if (!(prog = parse_string(arg, 0))) {
 	zwarnnam(name, "couldn't parse trap command", NULL, 0);
 	return 1;
     }
 
     /* set traps */
     for (; *argv; argv++) {
-	List t;
+	Eprog t;
 
 	sig = getsignum(*argv);
 	if (sig == -1) {
 	    zwarnnam(name, "undefined signal: %s", *argv, 0);
 	    break;
 	}
-	PERMALLOC {
-	    t = (List) dupstruct(l);
-	} LASTALLOC;
+	t = dupeprog(prog, 0);
 	if (settrap(sig, t))
-	    freestruct(t);
+	    freeeprog(t);
     }
     return *argv != NULL;
 }
@@ -3465,10 +3923,10 @@ bin_ttyctl(char *name, char **argv, char *ops, int func)
 int
 bin_let(char *name, char **argv, char *ops, int func)
 {
-    long val = 0;
+    zlong val = 0;
 
     while (*argv)
-	val = matheval(*argv++);
+	val = mathevali(*argv++);
     /* Errors in math evaluation in let are non-fatal. */
     errflag = 0;
     return !val;
@@ -3591,7 +4049,7 @@ bin_umask(char *nam, char **args, char *ops, int func)
 /* Generic builtin for facilities not available on this OS */
 
 /**/
-int
+mod_export int
 bin_notavail(char *nam, char **argv, char *ops, int func)
 {
     zwarnnam(nam, "not available on this system", NULL, 0);
diff --git a/Src/cond.c b/Src/cond.c
index 79886a720..8a54eeeb2 100644
--- a/Src/cond.c
+++ b/Src/cond.c
@@ -30,125 +30,277 @@
 #include "zsh.mdh"
 #include "cond.pro"
 
+int tracingcond;
+
+static char *condstr[COND_MOD] = {
+    "!", "&&", "||", "==", "!=", "<", ">", "-nt", "-ot", "-ef", "-eq",
+    "-ne", "-lt", "-gt", "-le", "-ge"
+};
+
 /**/
 int
-evalcond(Cond c)
+evalcond(Estate state)
 {
     struct stat *st;
+    char *left, *right;
+    Wordcode pcode;
+    wordcode code;
+    int ctype, htok = 0;
+
+ rec:
+
+    left = right = NULL;
+    pcode = state->pc++;
+    code = *pcode;
+    ctype = WC_COND_TYPE(code);
 
-    switch (c->type) {
+    switch (ctype) {
     case COND_NOT:
-	return !evalcond(c->left);
+	if (tracingcond)
+	    fprintf(xtrerr, " %s", condstr[ctype]);
+	return !evalcond(state);
     case COND_AND:
-	return evalcond(c->left) && evalcond(c->right);
+	if (evalcond(state)) {
+	    if (tracingcond)
+		fprintf(xtrerr, " %s", condstr[ctype]);
+	    goto rec;
+	} else {
+	    state->pc = pcode + (WC_COND_SKIP(code) + 1);
+	    return 0;
+	}
     case COND_OR:
-	return evalcond(c->left) || evalcond(c->right);
+	if (!evalcond(state)) {
+	    if (tracingcond)
+		fprintf(xtrerr, " %s", condstr[ctype]);
+	    goto rec;
+	} else {
+	    state->pc = pcode + (WC_COND_SKIP(code) + 1);
+	    return 1;
+	}
+    case COND_MOD:
+    case COND_MODI:
+	{
+	    Conddef cd;
+	    char *name = ecgetstr(state, EC_NODUP, NULL), **strs;
+	    int l = WC_COND_SKIP(code);
+
+	    if (ctype == COND_MOD)
+		strs = ecgetarr(state, l, EC_DUP, NULL);
+	    else {
+		char *sbuf[3];
+
+		sbuf[0] = ecgetstr(state, EC_NODUP, NULL);
+		sbuf[1] = ecgetstr(state, EC_NODUP, NULL);
+		sbuf[2] = NULL;
+
+		strs = arrdup(sbuf);
+		l = 2;
+	    }
+	    if ((cd = getconddef((ctype == COND_MODI), name + 1, 1))) {
+		if (ctype == COND_MOD &&
+		    (l < cd->min || (cd->max >= 0 && l > cd->max))) {
+		    zerr("unrecognized condition: `%s'", name, 0);
+		    return 0;
+		}
+		if (tracingcond)
+		    tracemodcond(name, strs, ctype == COND_MODI);
+		return cd->handler(strs, cd->condid);
+	    }
+	    else {
+		char *s = strs[0];
+
+		strs[0] = dupstring(name);
+		name = s;
+
+		if (name && name[0] == '-' &&
+		    (cd = getconddef(0, name + 1, 1))) {
+		    if (l < cd->min || (cd->max >= 0 && l > cd->max)) {
+			zerr("unrecognized condition: `%s'", name, 0);
+			return 0;
+		    }
+		    if (tracingcond)
+			tracemodcond(name, strs, ctype == COND_MODI);
+		    return cd->handler(strs, cd->condid);
+		} else
+		    zerr("unrecognized condition: `%s'", name, 0);
+	    }
+	    return 0;
+	}
     }
-    singsub((char **)&c->left);
-    untokenize(c->left);
-    if (c->right) {
-	singsub((char **)&c->right);
-	if (c->type != COND_STREQ && c->type != COND_STRNEQ)
-	    untokenize(c->right);
+    left = ecgetstr(state, EC_DUPTOK, &htok);
+    if (htok) {
+	singsub(&left);
+	untokenize(left);
     }
-    switch (c->type) {
+    if (ctype <= COND_GE && ctype != COND_STREQ && ctype != COND_STRNEQ) {
+	right = ecgetstr(state, EC_DUPTOK, &htok);
+	if (htok) {
+	    singsub(&right);
+	    untokenize(right);
+	}
+    }
+    if (tracingcond) {
+	if (ctype < COND_MOD) {
+	    char *rt = (char *) right;
+	    if (ctype == COND_STREQ || ctype == COND_STRNEQ) {
+		rt = dupstring(ecrawstr(state->prog, state->pc, NULL));
+		singsub(&rt);
+		untokenize(rt);
+	    }
+	    fprintf(xtrerr, " %s %s %s", left, condstr[ctype], rt);
+	} else
+	    fprintf(xtrerr, " -%c %s", ctype, left);
+    }
+
+    if (ctype >= COND_EQ && ctype <= COND_GE) {
+	mnumber mn1, mn2;
+	mn1 = matheval(left);
+	mn2 = matheval(right);
+
+	if (((mn1.type|mn2.type) & (MN_INTEGER|MN_FLOAT)) ==
+	    (MN_INTEGER|MN_FLOAT)) {
+	    /* promote to float */
+	    if (mn1.type & MN_INTEGER) {
+		mn1.type = MN_FLOAT;
+		mn1.u.d = (double)mn1.u.l;
+	    }
+	    if (mn2.type & MN_INTEGER) {
+		mn2.type = MN_FLOAT;
+		mn2.u.d = (double)mn2.u.l;
+	    }
+	}
+	switch(ctype) {
+	case COND_EQ:
+	    return (mn1.type & MN_FLOAT) ? (mn1.u.d == mn2.u.d) :
+		(mn1.u.l == mn2.u.l);
+	case COND_NE:
+	    return (mn1.type & MN_FLOAT) ? (mn1.u.d != mn2.u.d) :
+		(mn1.u.l != mn2.u.l);
+	case COND_LT:
+	    return (mn1.type & MN_FLOAT) ? (mn1.u.d < mn2.u.d) :
+		(mn1.u.l < mn2.u.l);
+	case COND_GT:
+	    return (mn1.type & MN_FLOAT) ? (mn1.u.d > mn2.u.d) :
+		(mn1.u.l > mn2.u.l);
+	case COND_LE:
+	    return (mn1.type & MN_FLOAT) ? (mn1.u.d <= mn2.u.d) :
+		(mn1.u.l <= mn2.u.l);
+	case COND_GE:
+	    return (mn1.type & MN_FLOAT) ? (mn1.u.d >= mn2.u.d) :
+		(mn1.u.l >= mn2.u.l);
+	}
+    }
+
+    switch (ctype) {
     case COND_STREQ:
-	return matchpat(c->left, c->right);
     case COND_STRNEQ:
-	return !matchpat(c->left, c->right);
+	{
+	    int test, npat = state->pc[1];
+	    Patprog pprog = state->prog->pats[npat];
+
+	    if (pprog == dummy_patprog1 || pprog == dummy_patprog2) {
+		char *opat;
+		int save;
+
+		right = opat = dupstring(ecrawstr(state->prog, state->pc,
+						  &htok));
+		if (htok)
+		    singsub(&right);
+		save = (!(state->prog->flags & EF_HEAP) &&
+			!strcmp(opat, right) && pprog != dummy_patprog2);
+
+		if (!(pprog = patcompile(right, (save ? PAT_ZDUP : PAT_STATIC),
+					 NULL)))
+		    zerr("bad pattern: %s", right, 0);
+		else if (save)
+		    state->prog->pats[npat] = pprog;
+	    }
+	    state->pc += 2;
+	    test = (pprog && pattry(pprog, left));
+
+	    return (ctype == COND_STREQ ? test : !test);
+	}
     case COND_STRLT:
-	return strcmp(c->left, c->right) < 0;
+	return strcmp(left, right) < 0;
     case COND_STRGTR:
-	return strcmp(c->left, c->right) > 0;
+	return strcmp(left, right) > 0;
     case 'e':
     case 'a':
-	return (doaccess(c->left, F_OK));
+	return (doaccess(left, F_OK));
     case 'b':
-	return (S_ISBLK(dostat(c->left)));
+	return (S_ISBLK(dostat(left)));
     case 'c':
-	return (S_ISCHR(dostat(c->left)));
+	return (S_ISCHR(dostat(left)));
     case 'd':
-	return (S_ISDIR(dostat(c->left)));
+	return (S_ISDIR(dostat(left)));
     case 'f':
-	return (S_ISREG(dostat(c->left)));
+	return (S_ISREG(dostat(left)));
     case 'g':
-	return (!!(dostat(c->left) & S_ISGID));
+	return (!!(dostat(left) & S_ISGID));
     case 'k':
-	return (!!(dostat(c->left) & S_ISVTX));
+	return (!!(dostat(left) & S_ISVTX));
     case 'n':
-	return (!!strlen(c->left));
+	return (!!strlen(left));
     case 'o':
-	return (optison(c->left));
+	return (optison(left));
     case 'p':
-	return (S_ISFIFO(dostat(c->left)));
+	return (S_ISFIFO(dostat(left)));
     case 'r':
-	return (doaccess(c->left, R_OK));
+	return (doaccess(left, R_OK));
     case 's':
-	return ((st = getstat(c->left)) && !!(st->st_size));
+	return ((st = getstat(left)) && !!(st->st_size));
     case 'S':
-	return (S_ISSOCK(dostat(c->left)));
+	return (S_ISSOCK(dostat(left)));
     case 'u':
-	return (!!(dostat(c->left) & S_ISUID));
+	return (!!(dostat(left) & S_ISUID));
     case 'w':
-	return (doaccess(c->left, W_OK));
+	return (doaccess(left, W_OK));
     case 'x':
 	if (privasserted()) {
-	    mode_t mode = dostat(c->left);
+	    mode_t mode = dostat(left);
 	    return (mode & S_IXUGO) || S_ISDIR(mode);
 	}
-	return doaccess(c->left, X_OK);
+	return doaccess(left, X_OK);
     case 'z':
-	return (!strlen(c->left));
+	return (!strlen(left));
     case 'h':
     case 'L':
-	return (S_ISLNK(dolstat(c->left)));
+	return (S_ISLNK(dolstat(left)));
     case 'O':
-	return ((st = getstat(c->left)) && st->st_uid == geteuid());
+	return ((st = getstat(left)) && st->st_uid == geteuid());
     case 'G':
-	return ((st = getstat(c->left)) && st->st_gid == getegid());
+	return ((st = getstat(left)) && st->st_gid == getegid());
     case 'N':
-	return ((st = getstat(c->left)) && st->st_atime <= st->st_mtime);
+	return ((st = getstat(left)) && st->st_atime <= st->st_mtime);
     case 't':
-	return isatty(matheval(c->left));
-    case COND_EQ:
-	return matheval(c->left) == matheval(c->right);
-    case COND_NE:
-	return matheval(c->left) != matheval(c->right);
-    case COND_LT:
-	return matheval(c->left) < matheval(c->right);
-    case COND_GT:
-	return matheval(c->left) > matheval(c->right);
-    case COND_LE:
-	return matheval(c->left) <= matheval(c->right);
-    case COND_GE:
-	return matheval(c->left) >= matheval(c->right);
+	return isatty(mathevali(left));
     case COND_NT:
     case COND_OT:
 	{
 	    time_t a;
 
-	    if (!(st = getstat(c->left)))
+	    if (!(st = getstat(left)))
 		return 0;
 	    a = st->st_mtime;
-	    if (!(st = getstat(c->right)))
+	    if (!(st = getstat(right)))
 		return 0;
-	    return (c->type == COND_NT) ? a > st->st_mtime : a < st->st_mtime;
+	    return (ctype == COND_NT) ? a > st->st_mtime : a < st->st_mtime;
 	}
     case COND_EF:
 	{
 	    dev_t d;
 	    ino_t i;
 
-	    if (!(st = getstat(c->left)))
+	    if (!(st = getstat(left)))
 		return 0;
 	    d = st->st_dev;
 	    i = st->st_ino;
-	    if (!(st = getstat(c->right)))
+	    if (!(st = getstat(right)))
 		return 0;
 	    return d == st->st_dev && i == st->st_ino;
 	}
     default:
-	zerr("bad cond structure", NULL, 0);
+	zerr("bad cond code", NULL, 0);
     }
     return 0;
 }
@@ -158,6 +310,10 @@ evalcond(Cond c)
 static int
 doaccess(char *s, int c)
 {
+#ifdef HAVE_FACCESSX
+    if (!strncmp(s, "/dev/fd/", 8))
+	return !faccessx(atoi(s + 8), c, ACC_SELF);
+#endif
     return !access(unmeta(s), c);
 }
 
@@ -224,3 +380,59 @@ optison(char *s)
     else
 	return isset(i);
 }
+
+/**/
+mod_export char *
+cond_str(char **args, int num, int raw)
+{
+    char *s = args[num];
+
+    if (has_token(s)) {
+	singsub(&s);
+	if (!raw)
+	    untokenize(s);
+    }
+    return s;
+}
+
+/**/
+mod_export zlong
+cond_val(char **args, int num)
+{
+    char *s = args[num];
+
+    if (has_token(s)) {
+	singsub(&s);
+	untokenize(s);
+    }
+    return mathevali(s);
+}
+
+/**/
+mod_export int
+cond_match(char **args, int num, char *str)
+{
+    char *s = args[num];
+
+    singsub(&s);
+
+    return matchpat(str, s);
+}
+
+/**/
+static void
+tracemodcond(char *name, char **args, int inf)
+{
+    char **aptr;
+
+    args = arrdup(args);
+    for (aptr = args; *aptr; aptr++)
+	untokenize(*aptr);
+    if (inf) {
+	fprintf(xtrerr, " %s %s %s", args[0], name, args[1]);
+    } else {
+	fprintf(xtrerr, " %s", name);
+	while (*args)
+	    fprintf(xtrerr, " %s", *args++);
+    }
+}
diff --git a/Src/exec.c b/Src/exec.c
index 1b355d028..0dffaf4e2 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -38,7 +38,7 @@ int noerrexit;
 /* suppress error messages */
  
 /**/
-int noerrs;
+mod_export int noerrs;
  
 /* do not save history on exec and exit */
 
@@ -48,7 +48,7 @@ int nohistsave;
 /* error/break flag */
  
 /**/
-int errflag;
+mod_export int errflag;
  
 /* Status of return from a trap */
  
@@ -63,7 +63,7 @@ int subsh;
 /* != 0 if we have a return pending */
  
 /**/
-int retflag;
+mod_export int retflag;
 
 /**/
 long lastval2;
@@ -92,17 +92,17 @@ int max_zsh_fd;
 /* input fd from the coprocess */
 
 /**/
-int coprocin;
+mod_export int coprocin;
 
 /* output fd from the coprocess */
 
 /**/
-int coprocout;
+mod_export int coprocout;
 
 /* != 0 if the line editor is active */
 
 /**/
-int zleactive;
+mod_export int zleactive;
 
 /* pid of process undergoing 'process substitution' */
  
@@ -113,45 +113,66 @@ pid_t cmdoutpid;
  
 /**/
 int cmdoutval;
- 
+
+/* The context in which a shell function is called, see SFC_* in zsh.h. */ 
+
+/**/
+mod_export int sfcontext;
+
 /* Stack to save some variables before executing a signal handler function */
 
 /**/
 struct execstack *exstack;
 
-#define execerr() if (!forked) { lastval = 1; return; } else _exit(1)
+/* Stack with names of functions currently active. */
+
+/**/
+mod_export Funcstack funcstack;
+
+#define execerr() if (!forked) { lastval = 1; goto done; } else _exit(1)
 
 static LinkList args;
 static int doneps4;
 
+/* Execution functions. */
+
+static int (*execfuncs[]) _((Estate, int)) = {
+    execcursh, exectime, execfuncdef, execfor, execselect,
+    execwhile, execrepeat, execcase, execif, execcond,
+    execarith, execautofn
+};
+
 /* parse string into a list */
 
 /**/
-List
-parse_string(char *s)
+mod_export Eprog
+parse_string(char *s, int ln)
 {
-    List l;
+    Eprog p;
+    int oldlineno = lineno;
 
     lexsave();
-    inpush(s, 0, NULL);
-    strinbeg();
-    stophist = 2;
-    l = parse_list();
+    inpush(s, (ln ? INP_LINENO : 0), NULL);
+    strinbeg(0);
+    lineno = ln ? 1 : -1;
+    p = parse_list();
+    lineno = oldlineno;
     strinend();
     inpop();
     lexrestore();
-    return l;
+    return p;
 }
 
+/**/
 #ifdef HAVE_GETRLIMIT
 
 /* the resource limits for the shell and its children */
 
 /**/
-struct rlimit current_limits[RLIM_NLIMITS], limits[RLIM_NLIMITS];
+mod_export struct rlimit current_limits[RLIM_NLIMITS], limits[RLIM_NLIMITS];
  
 /**/
-int
+mod_export int
 zsetlimit(int limnum, char *nam)
 {
     if (limits[limnum].rlim_max != current_limits[limnum].rlim_max ||
@@ -167,7 +188,7 @@ zsetlimit(int limnum, char *nam)
 }
 
 /**/
-int
+mod_export int
 setlimits(char *nam)
 {
     int limnum;
@@ -179,6 +200,7 @@ setlimits(char *nam)
     return ret;
 }
 
+/**/
 #endif /* HAVE_GETRLIMIT */
 
 /* fork and set limits */
@@ -206,6 +228,78 @@ zfork(void)
     return pid;
 }
 
+/*
+ *   Allen Edeln gebiet ich Andacht,
+ *   Hohen und Niedern von Heimdalls Geschlecht;
+ *   Ich will list_pipe's Wirken kuenden
+ *   Die aeltesten Sagen, der ich mich entsinne...
+ *
+ * In most shells, if you do something like:
+ *
+ *   cat foo | while read a; do grep $a bar; done
+ *
+ * the shell forks and executes the loop in the sub-shell thus created.
+ * In zsh this traditionally executes the loop in the current shell, which
+ * is nice to have if the loop does something to change the shell, like
+ * setting parameters or calling builtins.
+ * Putting the loop in a sub-shell makes live easy, because the shell only
+ * has to put it into the job-structure and then treats it as a normal
+ * process. Suspending and interrupting is no problem then.
+ * Some years ago, zsh either couldn't suspend such things at all, or
+ * it got really messed up when users tried to do it. As a solution, we
+ * implemented the list_pipe-stuff, which has since then become a reason
+ * for many nightmares.
+ * Pipelines like the one above are executed by the functions in this file
+ * which call each other (and sometimes recursively). The one above, for
+ * example would lead to a function call stack roughly like:
+ *
+ *  execlist->execpline->execcmd->execwhile->execlist->execpline
+ *
+ * (when waiting for the grep, ignoring execpline2 for now). At this time,
+ * zsh has build two job-table entries for it: one for the cat and one for
+ * the grep. If the user hits ^Z at this point (and jobbing is used), the 
+ * shell is notified that the grep was suspended. The list_pipe flag is
+ * used to tell the execpline where it was waiting that it was in a pipeline
+ * with a shell construct at the end (which may also be a shell function or
+ * several other things). When zsh sees the suspended grep, it forks to let
+ * the sub-shell execute the rest of the while loop. The parent shell walks
+ * up in the function call stack to the first execpline. There it has to find
+ * out that it has just forked and then has to add information about the sub-
+ * shell (its pid and the text for it) in the job entry of the cat. The pid
+ * is passed down in the list_pipe_pid variable.
+ * But there is a problem: the suspended grep is a child of the parent shell
+ * and can't be adopted by the sub-shell. So the parent shell also has to 
+ * keep the information about this process (more precisely: this pipeline)
+ * by keeping the job table entry it created for it. The fact that there
+ * are two jobs which have to be treated together is remembered by setting
+ * the STAT_SUPERJOB flag in the entry for the cat-job (which now also
+ * contains a process-entry for the whole loop -- the sub-shell) and by
+ * setting STAT_SUBJOB in the job of the grep-job. With that we can keep
+ * sub-jobs from being displayed and we can handle an fg/bg on the super-
+ * job correctly. When the super-job is continued, the shell also wakes up
+ * the sub-job. But then, the grep will exit sometime. Now the parent shell
+ * has to remember not to try to wake it up again (in case of another ^Z).
+ * It also has to wake up the sub-shell (which suspended itself immediately
+ * after creation), so that the rest of the loop is executed by it.
+ * But there is more: when the sub-shell is created, the cat may already
+ * have exited, so we can't put the sub-shell in the process group of it.
+ * In this case, we put the sub-shell in the process group of the parent
+ * shell and in any case, the sub-shell has to put all commands executed
+ * by it into its own process group, because only this way the parent
+ * shell can control them since it only knows the process group of the sub-
+ * shell. Of course, this information is also important when putting a job
+ * in the foreground, where we have to attach its process group to the
+ * controlling tty.
+ * All this is made more difficult because we have to handle return values
+ * correctly. If the grep is signaled, its exit status has to be propagated
+ * back to the parent shell which needs it to set the exit status of the
+ * super-job. And of course, when the grep is signaled (including ^C), the
+ * loop has to be stopped, etc.
+ * The code for all this is distributed over three files (exec.c, jobs.c,
+ * and signals.c) and none of them is a simple one. So, all in all, there
+ * may still be bugs, but considering the complexity (with race conditions,
+ * signal handling, and all that), this should probably be expected.
+ */
 
 /**/
 int list_pipe = 0, simple_pline = 0;
@@ -219,12 +313,18 @@ static char list_pipe_text[JOBTEXTSIZE];
 
 /**/
 static int
-execcursh(Cmd cmd)
+execcursh(Estate state, int do_exec)
 {
-    if (!list_pipe)
+    Wordcode end = state->pc + WC_CURSH_SKIP(state->pc[-1]);
+
+    if (!list_pipe && thisjob != list_pipe_job)
 	deletejob(jobtab + thisjob);
-    execlist(cmd->u.list, 1, cmd->flags & CFLAG_EXEC);
-    cmd->u.list = NULL;
+    cmdpush(CS_CURSH);
+    execlist(state, 1, do_exec);
+    cmdpop();
+
+    state->pc = end;
+
     return lastval;
 }
 
@@ -276,7 +376,9 @@ zexecve(char *pth, char **argv)
 		    if (execvebuf[1] == '!') {
 			for (t0 = 0; t0 != ct; t0++)
 			    if (execvebuf[t0] == '\n')
-				execvebuf[t0] = '\0';
+				break;
+			while (inblank(execvebuf[t0]))
+			    execvebuf[t0--] = '\0';
 			execvebuf[POUNDBANGLIMIT] = '\0';
 			for (ptr = execvebuf + 2; *ptr && *ptr == ' '; ptr++);
 			for (ptr2 = ptr; *ptr && *ptr != ' '; ptr++);
@@ -383,6 +485,7 @@ execute(Cmdnam not_used_yet, int dash)
     }
 
     argv = makecline(args);
+    closem(3);
     child_unblock();
     if ((int) strlen(arg0) >= PATH_MAX) {
 	zerr("command too long: %s", arg0, 0);
@@ -452,13 +555,17 @@ execute(Cmdnam not_used_yet, int dash)
     _exit(1);
 }
 
-#define try(X) { if (iscom(X)) return ztrdup(X); }
+#define RET_IF_COM(X) { if (iscom(X)) return docopy ? dupstring(X) : arg0; }
 
-/* get the full pathname of an external command */
+/*
+ * Get the full pathname of an external command.
+ * If the second argument is zero, return the first argument if found;
+ * if non-zero, return the path using heap memory.  (RET_IF_COM(X), above).
+ */
 
 /**/
-char *
-findcmd(char *arg0)
+mod_export char *
+findcmd(char *arg0, int docopy)
 {
     char **pp;
     char *z, *s, buf[MAXCMDLEN];
@@ -471,7 +578,7 @@ findcmd(char *arg0)
 	return NULL;
     for (s = arg0; *s; s++)
 	if (*s == '/') {
-	    try(arg0);
+	    RET_IF_COM(arg0);
 	    if (arg0 == s || unset(PATHDIRS)) {
 		return NULL;
 	    }
@@ -491,13 +598,13 @@ findcmd(char *arg0)
 			*z++ = '/';
 		    }
 		    strcpy(z, arg0);
-		    try(buf);
+		    RET_IF_COM(buf);
 		}
 	    strcpy(nn, cn->u.name ? *(cn->u.name) : "");
 	    strcat(nn, "/");
 	    strcat(nn, cn->nam);
 	}
-	try(nn);
+	RET_IF_COM(nn);
     }
     for (pp = path; *pp; pp++) {
 	z = buf;
@@ -506,7 +613,7 @@ findcmd(char *arg0)
 	    *z++ = '/';
 	}
 	strcpy(z, arg0);
-	try(buf);
+	RET_IF_COM(buf);
     }
     return NULL;
 }
@@ -528,9 +635,15 @@ isreallycom(Cmdnam cn)
 {
     char fullnam[MAXCMDLEN];
 
-    strcpy(fullnam, cn->u.name ? *(cn->u.name) : "");
-    strcat(fullnam, "/");
-    strcat(fullnam, cn->nam);
+    if (cn->flags & HASHED)
+	strcpy(fullnam, cn->u.cmd);
+    else if (!cn->u.name)
+	return 0;
+    else {
+	strcpy(fullnam, *(cn->u.name));
+	strcat(fullnam, "/");
+	strcat(fullnam, cn->nam);
+    }
     return iscom(fullnam);
 }
 
@@ -549,7 +662,7 @@ isrelative(char *s)
 }
 
 /**/
-Cmdnam
+mod_export Cmdnam
 hashcmd(char *arg0, char **pp)
 {
     Cmdnam cn;
@@ -588,17 +701,60 @@ hashcmd(char *arg0, char **pp)
 /* execute a string */
 
 /**/
-void
+mod_export void
 execstring(char *s, int dont_change_job, int exiting)
 {
-    List list;
+    Eprog prog;
 
     pushheap();
-    if ((list = parse_string(s)))
-	execlist(list, dont_change_job, exiting);
+    if ((prog = parse_string(s, 0)))
+	execode(prog, dont_change_job, exiting);
     popheap();
 }
 
+/**/
+void
+execode(Eprog p, int dont_change_job, int exiting)
+{
+    struct estate s;
+
+    s.prog = p;
+    s.pc = p->prog;
+    s.strs = p->strs;
+
+    execlist(&s, dont_change_job, exiting);
+}
+
+/* Execute a simplified command. This is used to execute things that
+ * will run completely in the shell, so that we can by-pass all that
+ * nasty job-handling and redirection stuff in execpline and execcmd. */
+
+/**/
+static int
+execsimple(Estate state)
+{
+    wordcode code = *state->pc++;
+
+    if (errflag)
+	return (lastval = 1);
+
+    if (code)
+	lineno = code - 1;
+
+    code = wc_code(*state->pc++);
+
+    if (code == WC_ASSIGN) {
+	cmdoutval = 0;
+	addvars(state, state->pc - 1, 0);
+	if (isset(XTRACE)) {
+	    fputc('\n', xtrerr);
+	    fflush(xtrerr);
+	}
+	return (lastval = (errflag ? errflag : cmdoutval));
+    } else
+	return (lastval = (execfuncs[code - WC_CURSH])(state, 0));
+}
+
 /* Main routine for executing a list.                                *
  * exiting means that the (sub)shell we are in is a definite goner   *
  * after the current list is finished, so we may be able to exec the *
@@ -608,45 +764,78 @@ execstring(char *s, int dont_change_job, int exiting)
 
 /**/
 void
-execlist(List list, int dont_change_job, int exiting)
+execlist(Estate state, int dont_change_job, int exiting)
 {
-    Sublist slist;
     static int donetrap;
-    int ret, cj;
-    int old_pline_level, old_list_pipe;
+    Wordcode next;
+    wordcode code;
+    int ret, cj, csp, ltype;
+    int old_pline_level, old_list_pipe, oldlineno;
+    /*
+     * ERREXIT only forces the shell to exit if the last command in a &&
+     * or || fails.  This is the case even if an earlier command is a
+     * shell function or other current shell structure, so we have to set
+     * noerrexit here if the sublist is not of type END.
+     */
+    int oldnoerrexit = noerrexit;
 
     cj = thisjob;
     old_pline_level = pline_level;
     old_list_pipe = list_pipe;
+    oldlineno = lineno;
 
     if (sourcelevel && unset(SHINSTDIN))
 	pline_level = list_pipe = 0;
 
     /* Loop over all sets of comands separated by newline, *
      * semi-colon or ampersand (`sublists').               */
-    while (list && list != &dummy_list && !breaks && !retflag) {
+    code = *state->pc++;
+    while (wc_code(code) == WC_LIST && !breaks && !retflag) {
+	ltype = WC_LIST_TYPE(code);
+	csp = cmdsp;
+
+	if (ltype & Z_SIMPLE) {
+	    next = state->pc + WC_LIST_SKIP(code);
+	    execsimple(state);
+	    state->pc = next;
+	    goto sublist_done;
+	}
 	/* Reset donetrap:  this ensures that a trap is only *
 	 * called once for each sublist that fails.          */
 	donetrap = 0;
-	simplifyright(list);
-	slist = list->left;
 
 	/* Loop through code followed by &&, ||, or end of sublist. */
-	while (slist) {
-	    switch (slist->type) {
-	    case END:
+	code = *state->pc++;
+	while (wc_code(code) == WC_SUBLIST) {
+	    next = state->pc + WC_SUBLIST_SKIP(code);
+	    if (!oldnoerrexit)
+		noerrexit = (WC_SUBLIST_TYPE(code) != WC_SUBLIST_END);
+	    switch (WC_SUBLIST_TYPE(code)) {
+	    case WC_SUBLIST_END:
 		/* End of sublist; just execute, ignoring status. */
-		execpline(slist, list->type, !list->right && exiting);
+		if (WC_SUBLIST_FLAGS(code) & WC_SUBLIST_SIMPLE)
+		    execsimple(state);
+		else
+		    execpline(state, code, ltype, (ltype & Z_END) && exiting);
+		state->pc = next;
 		goto sublist_done;
 		break;
-	    case ANDNEXT:
+	    case WC_SUBLIST_AND:
 		/* If the return code is non-zero, we skip pipelines until *
 		 * we find a sublist followed by ORNEXT.                   */
-		if ((ret = execpline(slist, Z_SYNC, 0))) {
-		    while ((slist = slist->right))
-			if (slist->type == ORNEXT)
-			    break;
-		    if (!slist) {
+		if ((ret = ((WC_SUBLIST_FLAGS(code) & WC_SUBLIST_SIMPLE) ?
+			    execsimple(state) :
+			    execpline(state, code, Z_SYNC, 0)))) {
+		    state->pc = next;
+		    code = *state->pc++;
+		    next = state->pc + WC_SUBLIST_SKIP(code);
+		    while (wc_code(code) == WC_SUBLIST &&
+			   WC_SUBLIST_TYPE(code) == WC_SUBLIST_AND) {
+			state->pc = next;
+			code = *state->pc++;
+			next = state->pc + WC_SUBLIST_SKIP(code);
+		    }
+		    if (wc_code(code) != WC_SUBLIST) {
 			/* We've skipped to the end of the list, not executing *
 			 * the final pipeline, so don't perform error handling *
 			 * for this sublist.                                   */
@@ -654,28 +843,43 @@ execlist(List list, int dont_change_job, int exiting)
 			goto sublist_done;
 		    }
 		}
+		cmdpush(CS_CMDAND);
 		break;
-	    case ORNEXT:
+	    case WC_SUBLIST_OR:
 		/* If the return code is zero, we skip pipelines until *
 		 * we find a sublist followed by ANDNEXT.              */
-		if (!(ret = execpline(slist, Z_SYNC, 0))) {
-		    while ((slist = slist->right))
-			if (slist->type == ANDNEXT)
-			    break;
-		    if (!slist) {
+		if (!(ret = ((WC_SUBLIST_FLAGS(code) & WC_SUBLIST_SIMPLE) ?
+			     execsimple(state) :
+			     execpline(state, code, Z_SYNC, 0)))) {
+		    state->pc = next;
+		    code = *state->pc++;
+		    next = state->pc + WC_SUBLIST_SKIP(code);
+		    while (wc_code(code) == WC_SUBLIST &&
+			   WC_SUBLIST_TYPE(code) == WC_SUBLIST_OR) {
+			state->pc = next;
+			code = *state->pc++;
+			next = state->pc + WC_SUBLIST_SKIP(code);
+		    }
+		    if (wc_code(code) != WC_SUBLIST) {
 			/* We've skipped to the end of the list, not executing *
 			 * the final pipeline, so don't perform error handling *
 			 * for this sublist.                                   */
 			donetrap = 1;
 			goto sublist_done;
-		     }
+		    }
 		}
+		cmdpush(CS_CMDOR);
 		break;
 	    }
-	    slist = slist->right;
+	    state->pc = next;
+	    code = *state->pc++;
 	}
+	state->pc--;
 sublist_done:
 
+	cmdsp = csp;
+	noerrexit = oldnoerrexit;
+
 	if (sigtrapped[SIGDEBUG])
 	    dotrap(SIGDEBUG);
 
@@ -696,12 +900,13 @@ sublist_done:
 		    exit(lastval);
 	    }
 	}
-
-	list = list->right;
+	if (ltype & Z_END)
+	    break;
+	code = *state->pc++;
     }
-
     pline_level = old_pline_level;
     list_pipe = old_list_pipe;
+    lineno = oldlineno;
     if (dont_change_job)
 	thisjob = cj;
 }
@@ -718,15 +923,17 @@ sublist_done:
 
 /**/
 static int
-execpline(Sublist l, int how, int last1)
+execpline(Estate state, wordcode slcode, int how, int last1)
 {
     int ipipe[2], opipe[2];
     int pj, newjob;
     int old_simple_pline = simple_pline;
-    static int lastwj;
+    int slflags = WC_SUBLIST_FLAGS(slcode);
+    wordcode code = *state->pc++;
+    static int lastwj, lpforked;
 
-    if (!l->left)
-	return lastval = (l->flags & PFLAG_NOT) != 0;
+    if (wc_code(code) != WC_PIPE)
+	return lastval = (slflags & WC_SUBLIST_NOT) != 0;
 
     pj = thisjob;
     ipipe[0] = ipipe[1] = opipe[0] = opipe[1] = 0;
@@ -735,10 +942,11 @@ execpline(Sublist l, int how, int last1)
     /* get free entry in job table and initialize it */
     if ((thisjob = newjob = initjob()) == -1)
 	return 1;
+
     if (how & Z_TIMED)
 	jobtab[thisjob].stat |= STAT_TIMED;
 
-    if (l->flags & PFLAG_COPROC) {
+    if (slflags & WC_SUBLIST_COPROC) {
 	how = Z_ASYNC;
 	if (coprocin >= 0) {
 	    zclose(coprocin);
@@ -750,20 +958,26 @@ execpline(Sublist l, int how, int last1)
 	coprocout = opipe[1];
 	fdtable[coprocin] = fdtable[coprocout] = 0;
     }
+    /* This used to set list_pipe_pid=0 unconditionally, but in things
+     * like `ls|if true; then sleep 20; cat; fi' where the sleep was
+     * stopped, the top-level execpline() didn't get the pid for the
+     * sub-shell because it was overwritten. */
     if (!pline_level++) {
 	list_pipe_job = newjob;
+        list_pipe_pid = 0;
 	nowait = 0;
+	simple_pline = (WC_PIPE_TYPE(code) == WC_PIPE_END);
     }
-    list_pipe_pid = lastwj = 0;
-    if (pline_level == 1)
-	simple_pline = (l->left->type == END);
-    execpline2(l->left, how, opipe[0], ipipe[1], last1);
+    lastwj = lpforked = 0;
+    execpline2(state, code, how, opipe[0], ipipe[1], last1);
     pline_level--;
     if (how & Z_ASYNC) {
 	lastwj = newjob;
 	jobtab[thisjob].stat |= STAT_NOSTTY;
-	if (l->flags & PFLAG_COPROC)
+	if (slflags & WC_SUBLIST_COPROC) {
 	    zclose(ipipe[1]);
+	    zclose(opipe[0]);
+	}
 	if (how & Z_DISOWN) {
 	    deletejob(jobtab + thisjob);
 	    thisjob = -1;
@@ -775,13 +989,14 @@ execpline(Sublist l, int how, int last1)
     } else {
 	if (newjob != lastwj) {
 	    Job jn = jobtab + newjob;
+	    int updated;
 
 	    if (newjob == list_pipe_job && list_pipe_child)
 		_exit(0);
 
 	    lastwj = thisjob = newjob;
 
-	    if (list_pipe)
+	    if (list_pipe || (pline_level && !(how & Z_TIMED)))
 		jn->stat |= STAT_NOPRINT;
 
 	    if (nowait) {
@@ -789,8 +1004,15 @@ execpline(Sublist l, int how, int last1)
 		    struct process *pn, *qn;
 
 		    curjob = newjob;
+		    DPUTS(!list_pipe_pid, "invalid list_pipe_pid");
 		    addproc(list_pipe_pid, list_pipe_text);
 
+		    /* If the super-job contains only the sub-shell, the
+		       sub-shell is the group leader. */
+		    if (!jn->procs->next || lpforked == 2) {
+			jn->gleader = list_pipe_pid;
+			jn->stat |= STAT_SUBLEADER;
+		    }
 		    for (pn = jobtab[jn->other].procs; pn; pn = pn->next)
 			if (WIFSTOPPED(pn->status))
 			    break;
@@ -801,27 +1023,42 @@ execpline(Sublist l, int how, int last1)
 		    }
 
 		    jn->stat &= ~(STAT_DONE | STAT_NOPRINT);
-		    jn->stat |= STAT_STOPPED | STAT_CHANGED;
+		    jn->stat |= STAT_STOPPED | STAT_CHANGED | STAT_LOCKED;
 		    printjob(jn, !!isset(LONGLISTJOBS), 1);
 		}
-		else
+		else if (newjob != list_pipe_job)
 		    deletejob(jn);
+		else
+		    lastwj = -1;
 	    }
 
+	    errbrk_saved = 0;
 	    for (; !nowait;) {
 		if (list_pipe_child) {
 		    jn->stat |= STAT_NOPRINT;
 		    makerunning(jn);
 		}
-		if (!(jn->stat & STAT_LOCKED))
+		if (!(jn->stat & STAT_LOCKED)) {
+		    updated = !!jobtab[thisjob].procs;
 		    waitjobs();
-
+		    child_block();
+		} else
+		    updated = 0;
+		if (!updated &&
+		    list_pipe_job && jobtab[list_pipe_job].procs &&
+		    !(jobtab[list_pipe_job].stat & STAT_STOPPED)) {
+		    child_unblock();
+		    child_block();
+		}
 		if (list_pipe_child &&
 		    jn->stat & STAT_DONE &&
 		    lastval2 & 0200)
 		    killpg(mypgrp, lastval2 & ~0200);
-		if ((list_pipe || last1) && !list_pipe_child &&
-		    jn->stat & STAT_STOPPED) {
+		if (!list_pipe_child && !lpforked && !subsh && jobbing &&
+		    (list_pipe || last1 || pline_level) &&
+		    ((jn->stat & STAT_STOPPED) ||
+		     (list_pipe_job && pline_level &&
+		      (jobtab[list_pipe_job].stat & STAT_STOPPED)))) {
 		    pid_t pid;
 		    int synch[2];
 
@@ -841,27 +1078,45 @@ execpline(Sublist l, int how, int last1)
 		    else if (pid) {
 			char dummy;
 
+			lpforked = 
+			    (killpg(jobtab[list_pipe_job].gleader, 0) == -1 ? 2 : 1);
 			list_pipe_pid = pid;
 			nowait = errflag = 1;
 			breaks = loops;
 			close(synch[1]);
 			read(synch[0], &dummy, 1);
 			close(synch[0]);
-			jobtab[list_pipe_job].other = newjob;
-			jobtab[list_pipe_job].stat |= STAT_SUPERJOB;
-			jn->stat |= STAT_SUBJOB | STAT_NOPRINT;
-			jn->other = pid;
-			killpg(jobtab[list_pipe_job].gleader, SIGSTOP);
+			/* If this job has finished, we leave it as a
+			 * normal (non-super-) job. */
+			if (!(jn->stat & STAT_DONE)) {
+			    jobtab[list_pipe_job].other = newjob;
+			    jobtab[list_pipe_job].stat |= STAT_SUPERJOB;
+			    jn->stat |= STAT_SUBJOB | STAT_NOPRINT;
+			    jn->other = pid;
+			}
+			if ((list_pipe || last1) && jobtab[list_pipe_job].procs)
+			    killpg(jobtab[list_pipe_job].gleader, SIGSTOP);
 			break;
 		    }
 		    else {
 			close(synch[0]);
 			entersubsh(Z_ASYNC, 0, 0);
-			setpgrp(0L, mypgrp = jobtab[list_pipe_job].gleader);
+			if (jobtab[list_pipe_job].procs) {
+			    if (setpgrp(0L, mypgrp = jobtab[list_pipe_job].gleader)
+				== -1) {
+				setpgrp(0L, mypgrp = getpid());
+			    }
+			} else
+			    setpgrp(0L, mypgrp = getpid());
 			close(synch[1]);
 			kill(getpid(), SIGSTOP);
 			list_pipe = 0;
 			list_pipe_child = 1;
+			opts[INTERACTIVE] = 0;
+			if (errbrk_saved) {
+			    errflag = prev_errflag;
+			    breaks = prev_breaks;
+			}
 			break;
 		    }
 		}
@@ -874,16 +1129,18 @@ execpline(Sublist l, int how, int last1)
 
 	    if (list_pipe && (lastval & 0200) && pj >= 0 &&
 		(!(jn->stat & STAT_INUSE) || (jn->stat & STAT_DONE))) {
+		deletejob(jn);
 		jn = jobtab + pj;
-		jn->stat |= STAT_NOPRINT;
-		killjb(jobtab + pj, lastval & ~0200);
+		killjb(jn, lastval & ~0200);
 	    }
-	    if (list_pipe_child || (list_pipe && (jn->stat & STAT_DONE)))
+	    if (list_pipe_child ||
+		((jn->stat & STAT_DONE) &&
+		 (list_pipe || (pline_level && !(jn->stat & STAT_SUBJOB)))))
 		deletejob(jn);
 	    thisjob = pj;
 
 	}
-	if (l->flags & PFLAG_NOT)
+	if (slflags & WC_SUBLIST_NOT)
 	    lastval = !lastval;
     }
     if (!pline_level)
@@ -897,32 +1154,43 @@ static int subsh_close = -1;
 
 /**/
 static void
-execpline2(Pline pline, int how, int input, int output, int last1)
+execpline2(Estate state, wordcode pcode,
+	   int how, int input, int output, int last1)
 {
     pid_t pid;
     int pipes[2];
-    int oldlineno;
 
     if (breaks || retflag)
 	return;
 
-    oldlineno = lineno;
-    lineno = pline->left->lineno;
+    if (WC_PIPE_LINENO(pcode))
+	lineno = WC_PIPE_LINENO(pcode) - 1;
 
-    if (pline_level == 1)
-	strcpy(list_pipe_text, getjobtext((void *) pline->left));
-    if (pline->type == END) {
-	execcmd(pline->left, input, output, how, last1 ? 1 : 2);
-	pline->left = NULL;
-    } else {
+    if (pline_level == 1) {
+	if ((how & Z_ASYNC) || (!sfcontext && !sourcelevel))
+	    strcpy(list_pipe_text,
+		   getjobtext(state->prog,
+			      state->pc + (WC_PIPE_TYPE(pcode) == WC_PIPE_END ?
+					   0 : 1)));
+	else
+	    list_pipe_text[0] = '\0';
+    }
+    if (WC_PIPE_TYPE(pcode) == WC_PIPE_END)
+	execcmd(state, input, output, how, last1 ? 1 : 2);
+    else {
 	int old_list_pipe = list_pipe;
+	Wordcode next = state->pc + (*state->pc);
+	wordcode code;
+
+	state->pc++;
+	code = *state->pc;
 
 	mpipe(pipes);
 
 	/* if we are doing "foo | bar" where foo is a current *
 	 * shell command, do foo in a subshell and do the     *
 	 * rest of the pipeline in the current shell.         */
-	if (pline->left->type >= CURSH && (how & Z_SYNC)) {
+	if (wc_code(code) >= WC_CURSH && (how & Z_SYNC)) {
 	    int synch[2];
 
 	    pipe(synch);
@@ -933,7 +1201,7 @@ execpline2(Pline pline, int how, int input, int output, int last1)
 	    } else if (pid) {
 		char dummy, *text;
 
-		text = getjobtext((void *) pline->left);
+		text = getjobtext(state->prog, state->pc);
 		addproc(pid, text);
 		close(synch[1]);
 		read(synch[0], &dummy, 1);
@@ -943,28 +1211,27 @@ execpline2(Pline pline, int how, int input, int output, int last1)
 		close(synch[0]);
 		entersubsh(how, 2, 0);
 		close(synch[1]);
-		execcmd(pline->left, input, pipes[1], how, 0);
+		execcmd(state, input, pipes[1], how, 0);
 		_exit(lastval);
 	    }
 	} else {
-	/* otherwise just do the pipeline normally. */
+	    /* otherwise just do the pipeline normally. */
 	    subsh_close = pipes[0];
-	    execcmd(pline->left, input, pipes[1], how, 0);
+	    execcmd(state, input, pipes[1], how, 0);
 	}
-	pline->left = NULL;
 	zclose(pipes[1]);
-	if (pline->right) {
-	    /* if another execpline() is invoked because the command is *
-	     * a list it must know that we're already in a pipeline     */
-	    list_pipe = 1;
-	    execpline2(pline->right, how, pipes[0], output, last1);
-	    list_pipe = old_list_pipe;
-	    zclose(pipes[0]);
-	    subsh_close = -1;
-	}
+	state->pc = next;
+
+	/* if another execpline() is invoked because the command is *
+	 * a list it must know that we're already in a pipeline     */
+	cmdpush(CS_PIPE);
+	list_pipe = 1;
+	execpline2(state, *state->pc++, how, pipes[0], output, last1);
+	list_pipe = old_list_pipe;
+	cmdpop();
+	zclose(pipes[0]);
+	subsh_close = -1;
     }
-
-    lineno = oldlineno;
 }
 
 /* make the argv array */
@@ -977,20 +1244,21 @@ makecline(LinkList list)
     char **argv, **ptr;
 
     /* A bigger argv is necessary for executing scripts */
-    ptr  =
-    argv = 2 + (char **) ncalloc((countlinknodes(list) + 4) * sizeof(char *));
+    ptr = argv = 2 + (char **) hcalloc((countlinknodes(list) + 4) *
+				       sizeof(char *));
+
     if (isset(XTRACE)) {
 	if (!doneps4)
-	    fprintf(stderr, "%s", (prompt4) ? prompt4 : "");
+	    printprompt4();
 
 	for (node = firstnode(list); node; incnode(node)) {
 	    *ptr++ = (char *)getdata(node);
-	    zputs(getdata(node), stderr);
+	    zputs(getdata(node), xtrerr);
 	    if (nextnode(node))
-		fputc(' ', stderr);
+		fputc(' ', xtrerr);
 	}
-	fputc('\n', stderr);
-	fflush(stderr);
+	fputc('\n', xtrerr);
+	fflush(xtrerr);
     } else {
 	for (node = firstnode(list); node; incnode(node))
 	    *ptr++ = (char *)getdata(node);
@@ -1000,18 +1268,33 @@ makecline(LinkList list)
 }
 
 /**/
-void
+mod_export void
 untokenize(char *s)
 {
-    for (; *s; s++)
-	if (itok(*s))
-	    if (*s == Nularg)
-		chuck(s--);
-	    else
-		*s = ztokens[*s - Pound];
+    if (*s) {
+	int c;
+
+	while ((c = *s++))
+	    if (itok(c)) {
+		char *p = s - 1;
+
+		if (c != Nularg)
+		    *p++ = ztokens[c - Pound];
+
+		while ((c = *s++)) {
+		    if (itok(c)) {
+			if (c != Nularg)
+			    *p++ = ztokens[c - Pound];
+		    } else
+			*p++ = c;
+		}
+		*p = '\0';
+		break;
+	    }
+    }
 }
 
-/* Open a file for writing redicection */
+/* Open a file for writing redirection */
 
 /**/
 static int
@@ -1052,34 +1335,34 @@ clobber_open(struct redir *f)
 static void
 closemn(struct multio **mfds, int fd)
 {
-    struct multio *mn = mfds[fd];
-    char buf[TCBUFSIZE];
-    int len, i;
+    if (fd >= 0 && mfds[fd] && mfds[fd]->ct >= 2) {
+	struct multio *mn = mfds[fd];
+	char buf[TCBUFSIZE];
+	int len, i;
 
-    if (fd < 0 || !mfds[fd] || mfds[fd]->ct < 2)
-	return;
-    if (zfork()) {
-	for (i = 0; i < mn->ct; i++)
-	    zclose(mn->fds[i]);
-	zclose(mn->pipe);
-	mn->ct = 1;
-	mn->fds[0] = fd;
-	return;
-    }
-    /* pid == 0 */
-    closeallelse(mn);
-    if (mn->rflag) {
-	/* tee process */
-	while ((len = read(mn->pipe, buf, TCBUFSIZE)) > 0)
+	if (zfork()) {
 	    for (i = 0; i < mn->ct; i++)
-		write(mn->fds[i], buf, len);
-    } else {
-	/* cat process */
-	for (i = 0; i < mn->ct; i++)
-	    while ((len = read(mn->fds[i], buf, TCBUFSIZE)) > 0)
-		write(mn->pipe, buf, len);
+		zclose(mn->fds[i]);
+	    zclose(mn->pipe);
+	    mn->ct = 1;
+	    mn->fds[0] = fd;
+	    return;
+	}
+	/* pid == 0 */
+	closeallelse(mn);
+	if (mn->rflag) {
+	    /* tee process */
+	    while ((len = read(mn->pipe, buf, TCBUFSIZE)) > 0)
+		for (i = 0; i < mn->ct; i++)
+		    write(mn->fds[i], buf, len);
+	} else {
+	    /* cat process */
+	    for (i = 0; i < mn->ct; i++)
+		while ((len = read(mn->fds[i], buf, TCBUFSIZE)) > 0)
+		    write(mn->pipe, buf, len);
+	}
+	_exit(0);
     }
-    _exit(0);
 }
 
 /* close all the mnodes (failure) */
@@ -1135,7 +1418,7 @@ addfd(int forked, int *save, struct multio **mfds, int fd1, int fd2, int rflag)
 
     if (!mfds[fd1] || unset(MULTIOS)) {
 	if(!mfds[fd1]) {		/* starting a new multio */
-	    mfds[fd1] = (struct multio *) alloc(sizeof(struct multio));
+	    mfds[fd1] = (struct multio *) zhalloc(sizeof(struct multio));
 	    if (!forked && save[fd1] == -2)
 		save[fd1] = (fd1 == fd2) ? -1 : movefd(fd1);
 	}
@@ -1170,40 +1453,48 @@ addfd(int forked, int *save, struct multio **mfds, int fd1, int fd2, int rflag)
 
 /**/
 static void
-addvars(LinkList l, int export)
+addvars(Estate state, Wordcode pc, int export)
 {
-    Varasg v;
     LinkList vl;
-    int xtr;
-    char **arr, **ptr;
+    int xtr, isstr, htok = 0;
+    char **arr, **ptr, *name;
+    Wordcode opc = state->pc;
+    wordcode ac;
+    local_list1(svl);
 
     xtr = isset(XTRACE);
-    if (xtr && nonempty(l)) {
-	fprintf(stderr, "%s", prompt4 ? prompt4 : "");
+    if (xtr) {
+	printprompt4();
 	doneps4 = 1;
     }
-
-    while (nonempty(l)) {
-	v = (Varasg) ugetnode(l);
-	singsub(&v->name);
-	if (errflag)
-	    return;
-	untokenize(v->name);
+    state->pc = pc;
+    while (wc_code(ac = *state->pc++) == WC_ASSIGN) {
+	name = ecgetstr(state, EC_DUPTOK, &htok);
+	if (htok)
+	    untokenize(name);
 	if (xtr)
-	    fprintf(stderr, "%s=", v->name);
-	if (v->type == PM_SCALAR) {
-	    vl = newlinklist();
-	    addlinknode(vl, v->str);
+	    fprintf(xtrerr, "%s=", name);
+	if ((isstr = (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR))) {
+	    init_list1(svl, ecgetstr(state, EC_DUPTOK, &htok));
+	    vl = &svl;
 	} else
-	    vl = v->arr;
-	prefork(vl, v->type == PM_SCALAR ? 7 : 3);
-	if (errflag)
-	    return;
-	if (isset(GLOBASSIGN) || v->type != PM_SCALAR)
-	    globlist(vl);
-	if (errflag)
-	    return;
-	if (v->type == PM_SCALAR && (empty(vl) || !nextnode(firstnode(vl)))) {
+	    vl = ecgetlist(state, WC_ASSIGN_NUM(ac), EC_DUPTOK, &htok);
+
+	if (vl && htok) {
+	    prefork(vl, (isstr ? (PF_SINGLE|PF_ASSIGN) :
+			 PF_ASSIGN));
+	    if (errflag) {
+		state->pc = opc;
+		return;
+	    }
+	    if (isset(GLOBASSIGN) || !isstr)
+		globlist(vl, 0);
+	    if (errflag) {
+		state->pc = opc;
+		return;
+	    }
+	}
+	if (isstr && (empty(vl) || !nextnode(firstnode(vl)))) {
 	    Param pm;
 	    char *val;
 	    int allexp;
@@ -1215,48 +1506,100 @@ addvars(LinkList l, int export)
 		val = ztrdup(ugetnode(vl));
 	    }
 	    if (xtr)
-		fprintf(stderr, "%s ", val);
-	    if (export) {
-		if (export < 0) {
-		    /* We are going to fork so do not bother freeing this */
-		    pm = (Param) paramtab->removenode(paramtab, v->name);
-		    if (isset(RESTRICTED) && (pm->flags & PM_RESTRICTED)) {
-			zerr("%s: restricted", pm->nam, 0);
-			zsfree(val);
-			return;
-		    }
+		fprintf(xtrerr, "%s ", val);
+	    if (export && !strchr(name, '[')) {
+		if (export < 0 && isset(RESTRICTED) &&
+		    (pm = (Param) paramtab->removenode(paramtab, name)) &&
+		    (pm->flags & PM_RESTRICTED)) {
+		    zerr("%s: restricted", pm->nam, 0);
+		    zsfree(val);
+		    state->pc = opc;
+		    return;
 		}
 		allexp = opts[ALLEXPORT];
 		opts[ALLEXPORT] = 1;
-		pm = setsparam(v->name, val);
+		pm = setsparam(name, val);
 		opts[ALLEXPORT] = allexp;
 	    } else
-		pm = setsparam(v->name, val);
-	    if (errflag)
+		pm = setsparam(name, val);
+	    if (errflag) {
+		state->pc = opc;
 		return;
+	    }
 	    continue;
 	}
-	ptr = arr = (char **) zalloc(sizeof(char **) * (countlinknodes(vl) + 1));
+	if (vl) {
+	    ptr = arr = (char **) zalloc(sizeof(char **) *
+					 (countlinknodes(vl) + 1));
 
-	while (nonempty(vl))
-	    *ptr++ = ztrdup((char *) ugetnode(vl));
+	    while (nonempty(vl))
+		*ptr++ = ztrdup((char *) ugetnode(vl));
+	} else
+	    ptr = arr = (char **) zalloc(sizeof(char **));
 
 	*ptr = NULL;
 	if (xtr) {
-	    fprintf(stderr, "( ");
+	    fprintf(xtrerr, "( ");
 	    for (ptr = arr; *ptr; ptr++)
-		fprintf(stderr, "%s ", *ptr);
-	    fprintf(stderr, ") ");
+		fprintf(xtrerr, "%s ", *ptr);
+	    fprintf(xtrerr, ") ");
 	}
-	setaparam(v->name, arr);
-	if (errflag)
+	setaparam(name, arr);
+	if (errflag) {
+	    state->pc = opc;
 	    return;
+	}
+    }
+    state->pc = opc;
+}
+
+/**/
+void
+setunderscore(char *str)
+{
+    if (str && *str) {
+	int l = strlen(str) + 1, nl = (l + 31) & ~31;
+
+	if (nl > underscorelen || (underscorelen - nl) > 64) {
+	    zfree(underscore, underscorelen);
+	    underscore = (char *) zalloc(underscorelen = nl);
+	}
+	strcpy(underscore, str);
+	underscoreused = l;
+    } else {
+	if (underscorelen > 128) {
+	    zfree(underscore, underscorelen);
+	    underscore = (char *) zalloc(underscorelen = 32);
+	}
+	*underscore = '\0';
+	underscoreused = 1;
+    }
+}
+
+/* These describe the type of espansions that need to be done on the words
+ * used in the thing we are about to execute. They are set in execcmd() and
+ * used in execsubst() which might be called from one of the functions
+ * called from execcmd() (like execfor() and so on). */
+
+static int esprefork, esglob = 1;
+
+/**/
+void
+execsubst(LinkList strs)
+{
+    if (strs) {
+	prefork(strs, esprefork);
+	if (esglob) {
+	    LinkList ostrs = strs;
+	    globlist(strs, 0);
+	    strs = ostrs;
+	}
     }
 }
 
 /**/
 static void
-execcmd(Cmd cmd, int input, int output, int how, int last1)
+execcmd(Estate state, int input, int output, int how, int last1)
 {
     HashNode hn = NULL;
     LinkNode node;
@@ -1264,15 +1607,35 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
     struct multio *mfds[10];
     char *text;
     int save[10];
-    int fil, dfil, is_cursh, type, i;
+    int fil, dfil, is_cursh, type, do_exec = 0, i, htok = 0;
     int nullexec = 0, assign = 0, forked = 0;
     int is_shfunc = 0, is_builtin = 0, is_exec = 0;
     /* Various flags to the command. */
     int cflags = 0, checked = 0;
+    LinkList redir;
+    wordcode code;
+    Wordcode beg = state->pc, varspc;
+    FILE *oxtrerr = xtrerr;
 
     doneps4 = 0;
-    args = cmd->args;
-    type = cmd->type;
+    redir = (wc_code(*state->pc) == WC_REDIR ? ecgetredirs(state) : NULL);
+    if (wc_code(*state->pc) == WC_ASSIGN) {
+	varspc = state->pc;
+	while (wc_code((code = *state->pc)) == WC_ASSIGN)
+	    state->pc += (WC_ASSIGN_TYPE(code) == WC_ASSIGN_SCALAR ?
+			  3 : WC_ASSIGN_NUM(code) + 2);
+    } else
+	varspc = NULL;
+
+    code = *state->pc++;
+
+    type = wc_code(code);
+
+    /* It would be nice if we could use EC_DUPTOK instead of EC_DUP here.
+     * But for that we would need to check/change all builtins so that
+     * they don't modify their argument strings. */
+    args = (type == WC_SIMPLE ?
+	    ecgetlist(state, WC_SIMPLE_ARGC(code), EC_DUP, &htok) : NULL);
 
     for (i = 0; i < 10; i++) {
 	save[i] = -2;
@@ -1281,7 +1644,7 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
 
     /* If the command begins with `%', then assume it is a *
      * reference to a job in the job table.                */
-    if (type == SIMPLE && nonempty(args) &&
+    if (type == WC_SIMPLE && args && nonempty(args) &&
 	*(char *)peekfirst(args) == '%') {
 	pushnode(args, dupstring((how & Z_DISOWN)
 				 ? "disown" : (how & Z_ASYNC) ? "bg" : "fg"));
@@ -1292,8 +1655,8 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
      * any redirections, then check if it matches as a prefix of a   *
      * job currently in the job table.  If it does, then we treat it *
      * as a command to resume this job.                              */
-    if (isset(AUTORESUME) && type == SIMPLE && (how & Z_SYNC) &&
-	nonempty(args) && empty(cmd->redir) && !input &&
+    if (isset(AUTORESUME) && type == WC_SIMPLE && (how & Z_SYNC) &&
+	args && nonempty(args) && (!redir || empty(redir)) && !input &&
 	!nextnode(firstnode(args))) {
 	if (unset(NOTIFY))
 	    scanjobs();
@@ -1306,8 +1669,8 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
      * command if it contains some tokens (e.g. x=ex; ${x}port), so this *
      * only works in simple cases.  has_token() is called to make sure   *
      * this really is a simple case.                                     */
-    if (type == SIMPLE) {
-	while (nonempty(args)) {
+    if (type == WC_SIMPLE) {
+	while (args && nonempty(args)) {
 	    char *cmdarg = (char *) peekfirst(args);
 	    checked = !has_token(cmdarg);
 	    if (!checked)
@@ -1323,13 +1686,12 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
 	    }
 	    if (!(hn->flags & BINF_PREFIX)) {
 		is_builtin = 1;
-#ifdef DYNAMIC
+
 		/* autoload the builtin if necessary */
 		if (!((Builtin) hn)->handlerfunc) {
 		    load_module(((Builtin) hn)->optstr);
 		    hn = builtintab->getnode(builtintab, cmdarg);
 		}
-#endif
 		assign = (hn->flags & BINF_MAGICEQUALS);
 		break;
 	    }
@@ -1344,18 +1706,20 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
     }
 
     /* Do prefork substitutions */
-    prefork(args, assign ? 2 : isset(MAGICEQUALSUBST));
+    esprefork = (assign || isset(MAGICEQUALSUBST)) ? PF_TYPESET : 0;
+    if (args && htok)
+	prefork(args, esprefork);
 
-    if (type == SIMPLE) {
+    if (type == WC_SIMPLE) {
 	int unglobbed = 0;
 
 	for (;;) {
 	    char *cmdarg;
 
 	    if (!(cflags & BINF_NOGLOB))
-		while (!checked && !errflag && nonempty(args) &&
+		while (!checked && !errflag && args && nonempty(args) &&
 		       has_token((char *) peekfirst(args)))
-		    glob(args, firstnode(args));
+		    glob(args, firstnode(args), 0);
 	    else if (!unglobbed) {
 		for (node = firstnode(args); node; incnode(node))
 		    untokenize((char *) getdata(node));
@@ -1364,46 +1728,59 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
 
 	    /* Current shell should not fork unless the *
 	     * exec occurs at the end of a pipeline.    */
-	    if ((cflags & BINF_EXEC) && last1 == 2)
-		cmd->flags |= CFLAG_EXEC;
+	    if ((cflags & BINF_EXEC) && last1)
+		do_exec = 1;
 
 	    /* Empty command */
-	    if (empty(args)) {
-		if (nonempty(cmd->redir)) {
-		    if (cmd->flags & CFLAG_EXEC) {
+	    if (!args || empty(args)) {
+		if (redir && nonempty(redir)) {
+		    if (do_exec) {
 			/* Was this "exec < foobar"? */
 			nullexec = 1;
 			break;
-		    } else if (!nullcmd || !*nullcmd ||
+		    } else if (varspc) {
+			nullexec = 2;
+			break;
+		    } else if (!nullcmd || !*nullcmd || opts[CSHNULLCMD] ||
 			       (cflags & BINF_PREFIX)) {
 			zerr("redirection with no command", NULL, 0);
 			errflag = lastval = 1;
 			return;
+		    } else if (!nullcmd || !*nullcmd || opts[SHNULLCMD]) {
+			if (!args)
+			    args = newlinklist();
+			addlinknode(args, dupstring(":"));
 		    } else if (readnullcmd && *readnullcmd &&
-			       ((Redir) peekfirst(cmd->redir))->type == READ &&
-			       !nextnode(firstnode(cmd->redir))) {
+			       ((Redir) peekfirst(redir))->type == READ &&
+			       !nextnode(firstnode(redir))) {
+			if (!args)
+			    args = newlinklist();
 			addlinknode(args, dupstring(readnullcmd));
-		    } else
+		    } else {
+			if (!args)
+			    args = newlinklist();
 			addlinknode(args, dupstring(nullcmd));
+		    }
 		} else if ((cflags & BINF_PREFIX) && (cflags & BINF_COMMAND)) {
 		    lastval = 0;
 		    return;
 		} else {
 		    cmdoutval = 0;
-		    addvars(cmd->vars, 0);
+		    if (varspc)
+			addvars(state, varspc, 0);
 		    if (errflag)
 			lastval = errflag;
 		    else
 			lastval = cmdoutval;
 		    if (isset(XTRACE)) {
-			fputc('\n', stderr);
-			fflush(stderr);
+			fputc('\n', xtrerr);
+			fflush(xtrerr);
 		    }
 		    return;
 		}
-	    } else if (isset(RESTRICTED) && (cflags & BINF_EXEC) &&
-		       (cmd->flags & CFLAG_EXEC)) {
-		zerrnam("exec", "%s: restricted", (char *) getdata(firstnode(args)), 0);
+	    } else if (isset(RESTRICTED) && (cflags & BINF_EXEC) && do_exec) {
+		zerrnam("exec", "%s: restricted",
+			(char *) getdata(firstnode(args)), 0);
 		lastval = 1;
 		return;
 	    }
@@ -1428,11 +1805,12 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
 	    }
 	    if (!(hn->flags & BINF_PREFIX)) {
 		is_builtin = 1;
-#ifdef DYNAMIC
+
 		/* autoload the builtin if necessary */
-		if (!((Builtin) hn)->handlerfunc)
+		if (!((Builtin) hn)->handlerfunc) {
 		    load_module(((Builtin) hn)->optstr);
-#endif
+		    hn = builtintab->getnode(builtintab, cmdarg);
+		}
 		break;
 	    }
 	    cflags &= ~BINF_BUILTIN & ~BINF_COMMAND;
@@ -1448,23 +1826,20 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
     }
 
     /* Get the text associated with this command. */
-    if (jobbing || (how & Z_TIMED))
-	text = getjobtext((void *) cmd);
+    if ((how & Z_ASYNC) ||
+	(!sfcontext && !sourcelevel && (jobbing || (how & Z_TIMED))))
+	text = getjobtext(state->prog, beg);
     else
 	text = NULL;
 
     /* Set up special parameter $_ */
-    zsfree(underscore);
-    if (nonempty(args)
-	&& (underscore = ztrdup((char *) getdata(lastnode(args)))))
-	untokenize(underscore); 
-    else
-  	underscore = ztrdup("");
+
+    setunderscore((args && nonempty(args)) ? ((char *) getdata(lastnode(args))) : "");
 
     /* Warn about "rm *" */
-    if (type == SIMPLE && interact && unset(RMSTARSILENT)
-	&& isset(SHINSTDIN) && nonempty(args) && nextnode(firstnode(args))
-	&& !strcmp(peekfirst(args), "rm")) {
+    if (type == WC_SIMPLE && interact && unset(RMSTARSILENT) &&
+	isset(SHINSTDIN) && args && nonempty(args) &&
+	nextnode(firstnode(args)) && !strcmp(peekfirst(args), "rm")) {
 	LinkNode node, next;
 
 	for (node = nextnode(firstnode(args)); node && !errflag; node = next) {
@@ -1493,28 +1868,33 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
 	return;
     }
 
-    if (type == SIMPLE && !nullexec) {
+    if (type == WC_SIMPLE && !nullexec) {
 	char *s;
-	char trycd = (isset(AUTOCD) && isset(SHINSTDIN)
-		      && empty(cmd->redir) && !empty(args)
-		      && !nextnode(firstnode(args))
-		      && *(char *)peekfirst(args));
+	char trycd = (isset(AUTOCD) && isset(SHINSTDIN) &&
+		      (!redir || empty(redir)) && args && !empty(args) &&
+		      !nextnode(firstnode(args)) && *(char *)peekfirst(args));
 
-	DPUTS(empty(args), "BUG: empty(args) in exec.c");
+	DPUTS((!args || empty(args)), "BUG: empty(args) in exec.c");
 	if (!hn) {
 	    /* Resolve external commands */
 	    char *cmdarg = (char *) peekfirst(args);
+	    char **checkpath = pathchecked;
+	    int dohashcmd = isset(HASHCMDS);
 
 	    hn = cmdnamtab->getnode(cmdnamtab, cmdarg);
 	    if (hn && trycd && !isreallycom((Cmdnam)hn)) {
+		if (!(((Cmdnam)hn)->flags & HASHED)) {
+		    checkpath = path;
+		    dohashcmd = 1;
+		}
 		cmdnamtab->removenode(cmdnamtab, cmdarg);
 		cmdnamtab->freenode(hn);
 		hn = NULL;
 	    }
-	    if (!hn && isset(HASHCMDS) && strcmp(cmdarg, "..")) {
+	    if (!hn && dohashcmd && strcmp(cmdarg, "..")) {
 		for (s = cmdarg; *s && *s != '/'; s++);
 		if (!*s)
-		    hn = (HashNode) hashcmd(cmdarg, pathchecked);
+		    hn = (HashNode) hashcmd(cmdarg, checkpath);
 	    }
 	}
 
@@ -1529,7 +1909,7 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
     }
 
     /* This is nonzero if the command is a current shell procedure? */
-    is_cursh = (is_builtin || is_shfunc || (type >= CURSH) || nullexec);
+    is_cursh = (is_builtin || is_shfunc || nullexec || type >= WC_CURSH);
 
     /**************************************************************************
      * Do we need to fork?  We need to fork if:                               *
@@ -1552,10 +1932,11 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
      * current shell.                                                         *
      **************************************************************************/
 
-    if ((how & Z_ASYNC) || (!(cmd->flags & CFLAG_EXEC) &&
-       (((is_builtin || is_shfunc) && output) ||
-       (!is_cursh && (last1 != 1 || sigtrapped[SIGZERR] ||
-        sigtrapped[SIGEXIT] || havefiles()))))) {
+    if ((how & Z_ASYNC) ||
+	(!do_exec &&
+	 (((is_builtin || is_shfunc) && output) ||
+	  (!is_cursh && (last1 != 1 || sigtrapped[SIGZERR] ||
+			 sigtrapped[SIGEXIT] || havefiles()))))) {
 
 	pid_t pid;
 	int synch[2];
@@ -1576,21 +1957,27 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
 	    closem(2);
 #endif
 	    if (how & Z_ASYNC) {
-		lastpid = (long) pid;
-	    } else if (!jobtab[thisjob].stty_in_env && nonempty(cmd->vars)) {
+		lastpid = (zlong) pid;
+	    } else if (!jobtab[thisjob].stty_in_env && varspc) {
 		/* search for STTY=... */
-		while (nonempty(cmd->vars))
-		    if (!strcmp(((Varasg) ugetnode(cmd->vars))->name, "STTY")) {
+		Wordcode p = varspc;
+		wordcode ac;
+
+		while (wc_code(ac = *p) == WC_ASSIGN) {
+		    if (!strcmp(ecrawstr(state->prog, p + 1, NULL), "STTY")) {
 			jobtab[thisjob].stty_in_env = 1;
 			break;
 		    }
+		    p += (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR ?
+			  3 : WC_ASSIGN_NUM(ac) + 2);
+		}
 	    }
 	    addproc(pid, text);
 	    return;
 	}
 	/* pid == 0 */
 	close(synch[0]);
-	entersubsh(how, type != SUBSH && !(how & Z_ASYNC) ? 2 : 1, 0);
+	entersubsh(how, (type != WC_SUBSH) && !(how & Z_ASYNC) ? 2 : 1, 0);
 	close(synch[1]);
 	forked = 1;
 	if (sigtrapped[SIGINT] & ZSIG_IGNORED)
@@ -1613,13 +2000,26 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
 	is_exec = 1;
     }
 
-    if (!(cflags & BINF_NOGLOB))
-	globlist(args);
+    if ((esglob = !(cflags & BINF_NOGLOB)) && args && htok) {
+	LinkList oargs = args;
+	globlist(args, 0);
+	args = oargs;
+    }
     if (errflag) {
 	lastval = 1;
 	goto err;
     }
 
+    /* Make a copy of stderr for xtrace output before redirecting */
+    fflush(xtrerr);
+    if (isset(XTRACE) && xtrerr == stderr &&
+	(type < WC_SUBSH || type == WC_TIMED)) {
+	if (!(xtrerr = fdopen(movefd(dup(fileno(stderr))), "w")))
+	    xtrerr = stderr;
+	else
+	    fdtable[fileno(xtrerr)] = 3;
+    }
+
     /* Add pipeline input/output to mnodes */
     if (input)
 	addfd(forked, save, mfds, 0, input, 0);
@@ -1627,11 +2027,12 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
 	addfd(forked, save, mfds, 1, output, 1);
 
     /* Do process substitutions */
-    spawnpipes(cmd->redir);
+    if (redir)
+	spawnpipes(redir);
 
     /* Do io redirections */
-    while (nonempty(cmd->redir)) {
-	fn = (Redir) ugetnode(cmd->redir);
+    while (redir && nonempty(redir)) {
+	fn = (Redir) ugetnode(redir);
 	DPUTS(fn->type == HEREDOC || fn->type == HEREDOCDASH,
 	      "BUG: unexpanded here document");
 	if (fn->type == INPIPE) {
@@ -1649,7 +2050,7 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
 	    }
 	    addfd(forked, save, mfds, fn->fd1, fn->fd2, 1);
 	} else {
-	    if (fn->type != HERESTR && xpandredir(fn, cmd->redir))
+	    if (fn->type != HERESTR && xpandredir(fn, redir))
 		continue;
 	    if (errflag) {
 		closemnodes(mfds);
@@ -1691,7 +2092,8 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
 		addfd(forked, save, mfds, fn->fd1, fil, 0);
 		/* If this is 'exec < file', read from stdin, *
 		 * not terminal, unless `file' is a terminal. */
-		if (nullexec && fn->fd1 == 0 && isset(SHINSTDIN) && interact)
+		if (nullexec == 1 && fn->fd1 == 0 &&
+		    isset(SHINSTDIN) && interact)
 		    init_io();
 		break;
 	    case CLOSE:
@@ -1702,16 +2104,28 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
 		break;
 	    case MERGEIN:
 	    case MERGEOUT:
-		if(fn->fd2 < 10)
+		if (fn->fd2 < 10)
 		    closemn(mfds, fn->fd2);
-		fil = dup(fn->fd2);
+		if (fn->fd2 > 9 &&
+		    (fdtable[fn->fd2] ||
+		     fn->fd2 == coprocin ||
+		     fn->fd2 == coprocout)) {
+		    fil = -1;
+		    errno = EBADF;
+		} else {
+		    int fd = fn->fd2;
+		    if(fd == -2)
+			fd = (fn->type == MERGEOUT) ? coprocout : coprocin;
+		    fil = dup(fd);
+		}
 		if (fil == -1) {
 		    char fdstr[4];
 
 		    closemnodes(mfds);
 		    fixfds(save);
-		    sprintf(fdstr, "%d", fn->fd2);
-		    zerr("%s: %e", fdstr, errno);
+		    if (fn->fd2 != -2)
+		    	sprintf(fdstr, "%d", fn->fd2);
+		    zerr("%s: %e", fn->fd2 == -2 ? "coprocess" : fdstr, errno);
 		    execerr();
 		}
 		addfd(forked, save, mfds, fn->fd1, fil, fn->type == MERGEOUT);
@@ -1748,37 +2162,43 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
     /* We are done with redirection.  close the mnodes, *
      * spawning tee/cat processes as necessary.         */
     for (i = 0; i < 10; i++)
-	closemn(mfds, i);
+	if (mfds[i] && mfds[i]->ct >= 2)
+	    closemn(mfds, i);
 
     if (nullexec) {
-	for (i = 0; i < 10; i++)
-	    if (save[i] != -2)
-		zclose(save[i]);
+	if (nullexec == 1) {
+	    /*
+	     * If nullexec is 1 we specifically *don't* restore the original
+	     * fd's before returning.
+	     */
+	    for (i = 0; i < 10; i++)
+		if (save[i] != -2)
+		    zclose(save[i]);
+	    goto done;
+	}
 	/*
-	 * Here we specifically *don't* restore the original fd's
-	 * before returning.
+	 * If nullexec is 2, we have variables to add with the redirections
+	 * in place.
 	 */
-	return;
-    }
-
-    if (isset(EXECOPT) && !errflag) {
+	if (varspc)
+	    addvars(state, varspc, 0);
+	lastval = errflag ? errflag : cmdoutval;
+	if (isset(XTRACE)) {
+	    fputc('\n', xtrerr);
+	    fflush(xtrerr);
+	}
+    } else if (isset(EXECOPT) && !errflag) {
 	/*
 	 * We delay the entersubsh() to here when we are exec'ing
 	 * the current shell (including a fake exec to run a builtin then
 	 * exit) in case there is an error return.
 	 */
 	if (is_exec)
-	    entersubsh(how, type != SUBSH ? 2 : 1, 1);
-	if (type >= CURSH) {
-	    static int (*func[]) _((Cmd)) = {
-		execcursh, exectime, execfuncdef, execfor, execwhile,
-		execrepeat, execif, execcase, execselect, execcond,
-		execarith, execautofn
-	    };
-
+	    entersubsh(how, (type != WC_SUBSH) ? 2 : 1, 1);
+	if (type >= WC_CURSH) {
 	    if (last1 == 1)
-		cmd->flags |= CFLAG_EXEC;
-	    lastval = (func[type - CURSH]) (cmd);
+		do_exec = 1;
+	    lastval = (execfuncs[type - WC_CURSH])(state, do_exec);
 	} else if (is_builtin || is_shfunc) {
 	    LinkList restorelist = 0, removelist = 0;
 	    /* builtin or shell function */
@@ -1786,24 +2206,29 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
 	    if (!forked && ((cflags & BINF_COMMAND) ||
 			    (unset(POSIXBUILTINS) && !assign) ||
 			    (isset(POSIXBUILTINS) && !is_shfunc &&
-			     !(hn->flags & BINF_PSPECIAL))))
-		save_params(cmd, &restorelist, &removelist);
-
-	    if (cmd->vars) {
+			     !(hn->flags & BINF_PSPECIAL)))) {
+		if (varspc)
+		    save_params(state, varspc, &restorelist, &removelist);
+		else
+		    restorelist = removelist = NULL;
+	    }
+	    if (varspc) {
 		/* Export this if the command is a shell function,
 		 * but not if it's a builtin.
 		 */
-		addvars(cmd->vars, is_shfunc);
+		addvars(state, varspc, is_shfunc);
 		if (errflag) {
-		    restore_params(restorelist, removelist);
+		    if (restorelist)
+			restore_params(restorelist, removelist);
 		    lastval = 1;
 		    fixfds(save);
-		    return;
+		    goto done;
 		}
 	    }
 
 	    if (is_shfunc) {
 		/* It's a shell function */
+
 #ifdef PATH_DEV_FD
 		int i;
 
@@ -1814,7 +2239,7 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
 		if (subsh_close >= 0)
 		    zclose(subsh_close);
 		subsh_close = -1;
-		execshfunc(cmd, (Shfunc) hn);
+		execshfunc((Shfunc) hn, args);
 #ifdef PATH_DEV_FD
 		for (i = 10; i <= max_zsh_fd; i++)
 		    if (fdtable[i] > 1)
@@ -1843,30 +2268,31 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
 		    clearerr(stdout);
 	    }
 
-	    if (cmd->flags & CFLAG_EXEC) {
+	    if (do_exec) {
 		if (subsh)
 		    _exit(lastval);
 
 		/* If we are exec'ing a command, and we are not in a subshell, *
 		 * then check if we should save the history file.              */
 		if (isset(RCS) && interact && !nohistsave)
-		    savehistfile(getsparam("HISTFILE"), 1, isset(APPENDHISTORY) ? 3 : 0);
+		    savehistfile(NULL, 1, HFILE_USE_OPTIONS);
 		exit(lastval);
 	    }
-
-	    restore_params(restorelist, removelist);
+	    if (restorelist)
+		restore_params(restorelist, removelist);
 
 	} else {
-	    if (cmd->flags & CFLAG_EXEC) {
+	    if (!forked)
 		setiparam("SHLVL", --shlvl);
+	    if (do_exec) {
 		/* If we are exec'ing a command, and we are not *
 		 * in a subshell, then save the history file.   */
 		if (!subsh && isset(RCS) && interact && !nohistsave)
-		    savehistfile(getsparam("HISTFILE"), 1, isset(APPENDHISTORY) ? 3 : 0);
+		    savehistfile(NULL, 1, HFILE_USE_OPTIONS);
 	    }
-	    if (type == SIMPLE) {
-		if (cmd->vars) {
-		    addvars(cmd->vars, -1);
+	    if (type == WC_SIMPLE) {
+		if (varspc) {
+		    addvars(state, varspc, -1);
 		    if (errflag)
 			_exit(1);
 		}
@@ -1881,7 +2307,7 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
 #endif
 		execute((Cmdnam) hn, cflags & BINF_DASH);
 	    } else {		/* ( ... ) */
-		DPUTS(cmd->vars && nonempty(cmd->vars),
+		DPUTS(varspc,
 		      "BUG: assigment before complex command");
 		list_pipe = 0;
 		if (subsh_close >= 0)
@@ -1889,7 +2315,7 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
                 subsh_close = -1;
 		/* If we're forked (and we should be), no need to return */
 		DPUTS(last1 != 1 && !forked, "BUG: not exiting?");
-		execlist(cmd->u.list, 0, 1);
+		execlist(state, 0, 1);
 	    }
 	}
     }
@@ -1898,54 +2324,48 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
     if (forked)
 	_exit(lastval);
     fixfds(save);
+
+ done:
+    if (xtrerr != oxtrerr) {
+	fil = fileno(xtrerr);
+	fclose(xtrerr);
+	xtrerr = oxtrerr;
+	zclose(fil);
+    }
 }
 
 /* Arrange to have variables restored. */
 
 /**/
 static void
-save_params(Cmd cmd, LinkList *restore_p, LinkList *remove_p)
+save_params(Estate state, Wordcode pc, LinkList *restore_p, LinkList *remove_p)
 {
     Param pm;
-    LinkNode node;
     char *s;
-
-    MUSTUSEHEAP("save_params()");
+    wordcode ac;
 
     *restore_p = newlinklist();
     *remove_p = newlinklist();
 
-    for (node = firstnode(cmd->vars); node; incnode(node)) {
-	s = ((Varasg) getdata(node))->name;
+    while (wc_code(ac = *pc) == WC_ASSIGN) {
+	s = ecrawstr(state->prog, pc + 1, NULL);
 	if ((pm = (Param) paramtab->getnode(paramtab, s))) {
 	    if (!(pm->flags & PM_SPECIAL)) {
 		paramtab->removenode(paramtab, s);
 	    } else if (!(pm->flags & PM_READONLY) &&
 		       (unset(RESTRICTED) || !(pm->flags & PM_RESTRICTED))) {
-		Param tpm = (Param) alloc(sizeof *tpm);
-
+		Param tpm = (Param) zhalloc(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);
 	    addlinknode(*restore_p, pm);
-	} else {
+	} else
 	    addlinknode(*remove_p, s);
-	}
+
+	pc += (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR ?
+	       3 : WC_ASSIGN_NUM(ac) + 2);
     }
 }
 
@@ -1958,14 +2378,12 @@ restore_params(LinkList restorelist, LinkList removelist)
     Param pm;
     char *s;
 
-    if (removelist) {
-	/* remove temporary parameters */
-	while ((s = (char *) ugetnode(removelist))) {
-	    if ((pm = (Param) paramtab->getnode(paramtab, s)) &&
-		!(pm->flags & PM_SPECIAL)) {
-		pm->flags &= ~PM_READONLY;
-		unsetparam_pm(pm, 0, 0);
-	    }
+    /* remove temporary parameters */
+    while ((s = (char *) ugetnode(removelist))) {
+	if ((pm = (Param) paramtab->getnode(paramtab, s)) &&
+	    !(pm->flags & PM_SPECIAL)) {
+	    pm->flags &= ~PM_READONLY;
+	    unsetparam_pm(pm, 0, 0);
 	}
     }
 
@@ -1986,14 +2404,21 @@ restore_params(LinkList restorelist, LinkList removelist)
 		case PM_INTEGER:
 		    tpm->sets.ifn(tpm, pm->u.val);
 		    break;
+		case PM_EFLOAT:
+		case PM_FFLOAT:
+		    tpm->sets.ffn(tpm, pm->u.dval);
+		    break;
 		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);
-	    if (pm->flags & PM_EXPORTED)
-		pm->env = addenv(pm->nam, getsparam(pm->nam));
+	    if ((pm->flags & PM_EXPORTED) && ((s = getsparam(pm->nam))))
+		pm->env = addenv(pm->nam, s, pm->flags);
 	}
     }
 }
@@ -2037,18 +2462,17 @@ entersubsh(int how, int cl, int fake)
 	}
     } else if (thisjob != -1 && cl) {
 	if (jobtab[list_pipe_job].gleader && (list_pipe || list_pipe_child)) {
-	    if (kill(jobtab[list_pipe_job].gleader, 0) == -1 ||
+	    if (killpg(jobtab[list_pipe_job].gleader, 0) == -1 ||
 		setpgrp(0L, jobtab[list_pipe_job].gleader) == -1) {
 		jobtab[list_pipe_job].gleader =
-		    jobtab[thisjob].gleader = mypgrp;
-		setpgrp(0L, mypgrp);
-
+		    jobtab[thisjob].gleader = (list_pipe_child ? mypgrp : getpid());
+		setpgrp(0L, jobtab[list_pipe_job].gleader);
 		if (how & Z_SYNC)
 		    attachtty(jobtab[thisjob].gleader);
 	    }
 	}
 	else if (!jobtab[thisjob].gleader ||
-		 (setpgrp(0L, jobtab[thisjob].gleader) == -1)) {
+		 setpgrp(0L, jobtab[thisjob].gleader) == -1) {
 	    jobtab[thisjob].gleader = getpid();
 	    if (list_pipe_job != thisjob &&
 		!jobtab[list_pipe_job].gleader)
@@ -2086,7 +2510,7 @@ entersubsh(int how, int cl, int fake)
 /* close internal shell fds */
 
 /**/
-void
+mod_export void
 closem(int how)
 {
     int i;
@@ -2147,8 +2571,14 @@ gethere(char *str, int typ)
     if (t > buf && t[-1] == '\n')
 	t--;
     *t = '\0';
-    if (!qt)
+    if (!qt) {
+	int ef = errflag;
+
 	parsestr(buf);
+
+	if (!errflag)
+	    errflag = ef;
+    }
     s = dupstring(buf);
     zfree(buf, bsiz);
     return s;
@@ -2184,24 +2614,26 @@ getherestr(struct redir *fn)
 LinkList
 getoutput(char *cmd, int qt)
 {
-    List list;
+    Eprog prog;
     int pipes[2];
     pid_t pid;
-    Cmd c;
-    Redir r;
+    Wordcode pc;
 
-    if (!(list = parse_string(cmd)))
+    if (!(prog = parse_string(cmd, 0)))
 	return NULL;
-    if (list != &dummy_list && !list->right && !list->left->flags &&
-	list->left->type == END && list->left->left->type == END &&
-	(c = list->left->left->left)->type == SIMPLE && empty(c->args) &&
-	empty(c->vars) && nonempty(c->redir) &&
-	!nextnode(firstnode(c->redir)) &&
-	(r = (Redir) getdata(firstnode(c->redir)))->fd1 == 0 &&
-	r->type == READ) {
+
+    pc = prog->prog;
+    if (prog != &dummy_eprog &&
+	wc_code(pc[0]) == WC_LIST && (WC_LIST_TYPE(pc[0]) & Z_END) &&
+	wc_code(pc[1]) == WC_SUBLIST && !WC_SUBLIST_FLAGS(pc[1]) &&
+	WC_SUBLIST_TYPE(pc[1]) == WC_SUBLIST_END &&
+	wc_code(pc[2]) == WC_PIPE && WC_PIPE_TYPE(pc[2]) == WC_PIPE_END &&
+	wc_code(pc[3]) == WC_REDIR && WC_REDIR_TYPE(pc[3]) == READ && 
+	!pc[4] &&
+	wc_code(pc[6]) == WC_SIMPLE && !WC_SIMPLE_ARGC(pc[6])) {
 	/* $(< word) */
 	int stream;
-	char *s = r->name;
+	char *s = dupstring(ecrawstr(prog, pc + 5, NULL));
 
 	singsub(&s);
 	if (errflag)
@@ -2213,7 +2645,6 @@ getoutput(char *cmd, int qt)
 	}
 	return readoutput(stream, qt);
     }
-
     mpipe(pipes);
     child_block();
     cmdoutval = 0;
@@ -2231,18 +2662,19 @@ getoutput(char *cmd, int qt)
 	zclose(pipes[1]);
 	retval = readoutput(pipes[0], qt);
 	fdtable[pipes[0]] = 0;
-	child_suspend(0);		/* unblocks */
+	waitforpid(pid);		/* unblocks */
 	lastval = cmdoutval;
 	return retval;
     }
-
     /* pid == 0 */
     child_unblock();
     zclose(pipes[0]);
     redup(pipes[1], 1);
     opts[MONITOR] = 0;
     entersubsh(Z_SYNC, 1, 0);
-    execlist(list, 0, 1);
+    cmdpush(CS_CMDSUBST);
+    execode(prog, 0, 1);
+    cmdpop();
     close(1);
     _exit(lastval);
     zerr("exit returned in child!!", NULL, 0);
@@ -2253,7 +2685,7 @@ getoutput(char *cmd, int qt)
 /* read output of command substitution */
 
 /**/
-static LinkList
+mod_export LinkList
 readoutput(int in, int qt)
 {
     LinkList ret;
@@ -2263,7 +2695,7 @@ readoutput(int in, int qt)
 
     fin = fdopen(in, "r");
     ret = newlinklist();
-    ptr = buf = (char *) ncalloc(bsiz = 64);
+    ptr = buf = (char *) hcalloc(bsiz = 64);
     while ((c = fgetc(fin)) != EOF || errno == EINTR) {
 	if (c == EOF) {
 	    errno = 0;
@@ -2276,7 +2708,7 @@ readoutput(int in, int qt)
 	    cnt++;
 	}
 	if (++cnt >= bsiz) {
-	    char *pp = (char *) ncalloc(bsiz *= 2);
+	    char *pp = (char *) hcalloc(bsiz *= 2);
 
 	    memcpy(pp, buf, cnt - 1);
 	    ptr = (buf = pp) + cnt - 1;
@@ -2294,7 +2726,7 @@ readoutput(int in, int qt)
 	}
 	addlinknode(ret, buf);
     } else {
-	char **words = spacesplit(buf, 0);
+	char **words = spacesplit(buf, 0, 1);
 
 	while (*words) {
 	    if (isset(GLOBSUBST))
@@ -2306,11 +2738,11 @@ readoutput(int in, int qt)
 }
 
 /**/
-static List
+static Eprog
 parsecmd(char *cmd)
 {
     char *str;
-    List list;
+    Eprog prog;
 
     for (str = cmd + 2; *str && *str != Outpar; str++);
     if (!*str || cmd[1] != Inpar) {
@@ -2318,11 +2750,11 @@ parsecmd(char *cmd)
 	return NULL;
     }
     *str = '\0';
-    if (str[1] || !(list = parse_string(cmd + 2))) {
+    if (str[1] || !(prog = parse_string(cmd + 2, 0))) {
 	zerr("parse error in process substitution", NULL, 0);
 	return NULL;
     }
-    return list;
+    return prog;
 }
 
 /* =(...) */
@@ -2333,22 +2765,22 @@ getoutputfile(char *cmd)
 {
     pid_t pid;
     char *nam;
-    List list;
+    Eprog prog;
     int fd;
 
     if (thisjob == -1)
 	return NULL;
-    if (!(list = parsecmd(cmd)))
+    if (!(prog = parsecmd(cmd)))
 	return NULL;
     if (!(nam = gettempname()))
 	return NULL;
 
     nam = ztrdup(nam);
-    PERMALLOC {
-	if (!jobtab[thisjob].filelist)
-	    jobtab[thisjob].filelist = newlinklist();
-	addlinknode(jobtab[thisjob].filelist, nam);
-    } LASTALLOC;
+
+    if (!jobtab[thisjob].filelist)
+	jobtab[thisjob].filelist = znewlinklist();
+    zaddlinknode(jobtab[thisjob].filelist, nam);
+
     child_block();
     fd = open(nam, O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY, 0600);
 
@@ -2371,7 +2803,9 @@ getoutputfile(char *cmd)
     redup(fd, 1);
     opts[MONITOR] = 0;
     entersubsh(Z_SYNC, 1, 0);
-    execlist(list, 0, 1);
+    cmdpush(CS_CMDSUBST);
+    execode(prog, 0, 1);
+    cmdpop();
     close(1);
     _exit(lastval);
     zerr("exit returned in child!!", NULL, 0);
@@ -2407,7 +2841,7 @@ getproc(char *cmd)
     zerr("doesn't look like your system supports FIFOs.", NULL, 0);
     return NULL;
 #else
-    List list;
+    Eprog prog;
     int out = *cmd == Inang;
     char *pnam;
 #ifndef PATH_DEV_FD
@@ -2422,16 +2856,15 @@ getproc(char *cmd)
     if (!(pnam = namedpipe()))
 	return NULL;
 #else
-    pnam = ncalloc(strlen(PATH_DEV_FD) + 6);
+    pnam = hcalloc(strlen(PATH_DEV_FD) + 6);
 #endif
-    if (!(list = parsecmd(cmd)))
+    if (!(prog = parsecmd(cmd)))
 	return NULL;
 #ifndef PATH_DEV_FD
-    PERMALLOC {
-	if (!jobtab[thisjob].filelist)
-	    jobtab[thisjob].filelist = newlinklist();
-	addlinknode(jobtab[thisjob].filelist, ztrdup(pnam));
-    } LASTALLOC;
+    if (!jobtab[thisjob].filelist)
+	jobtab[thisjob].filelist = znewlinklist();
+    zaddlinknode(jobtab[thisjob].filelist, ztrdup(pnam));
+
     if (zfork()) {
 #else
     mpipe(pipes);
@@ -2456,7 +2889,9 @@ getproc(char *cmd)
     redup(pipes[out], out);
     closem(0);   /* this closes pipes[!out] as well */
 #endif
-    execlist(list, 0, 1);
+    cmdpush(CS_CMDSUBST);
+    execode(prog, 0, 1);
+    cmdpop();
     zclose(out);
     _exit(lastval);
     return NULL;
@@ -2469,10 +2904,10 @@ getproc(char *cmd)
 static int
 getpipe(char *cmd)
 {
-    List list;
+    Eprog prog;
     int pipes[2], out = *cmd == Inang;
 
-    if (!(list = parsecmd(cmd)))
+    if (!(prog = parsecmd(cmd)))
 	return -1;
     mpipe(pipes);
     if (zfork()) {
@@ -2482,7 +2917,9 @@ getpipe(char *cmd)
     entersubsh(Z_ASYNC, 1, 0);
     redup(pipes[out], out);
     closem(0);	/* this closes pipes[!out] as well */
-    execlist(list, 0, 1);
+    cmdpush(CS_CMDSUBST);
+    execode(prog, 0, 1);
+    cmdpop();
     _exit(lastval);
     return 0;
 }
@@ -2518,26 +2955,62 @@ spawnpipes(LinkList l)
     }
 }
 
+extern int tracingcond;
+
 /* evaluate a [[ ... ]] */
 
 /**/
 static int
-execcond(Cmd cmd)
+execcond(Estate state, int do_exec)
 {
-    return !evalcond(cmd->u.cond);
+    int stat;
+
+    state->pc--;
+    if (isset(XTRACE)) {
+	printprompt4();
+	fprintf(xtrerr, "[[");
+	tracingcond++;
+    }
+    cmdpush(CS_COND);
+    stat = !evalcond(state);
+    cmdpop();
+    if (isset(XTRACE)) {
+	fprintf(xtrerr, " ]]\n");
+	fflush(xtrerr);
+	tracingcond--;
+    }
+    return stat;
 }
 
 /* evaluate a ((...)) arithmetic command */
 
 /**/
 static int
-execarith(Cmd cmd)
+execarith(Estate state, int do_exec)
 {
     char *e;
-    long val = 0;
+    zlong val = 0;
+    int htok = 0;
+
+    if (isset(XTRACE)) {
+	printprompt4();
+	fprintf(xtrerr, "((");
+    }
+    cmdpush(CS_MATH);
+    e = ecgetstr(state, EC_DUPTOK, &htok);
+    if (htok)
+	singsub(&e);
+    if (isset(XTRACE))
+	fprintf(xtrerr, " %s", e);
+
+    val = mathevali(e);
 
-    while ((e = (char *) ugetnode(cmd->args)))
-	val = matheval(e);
+    cmdpop();
+
+    if (isset(XTRACE)) {
+	fprintf(xtrerr, " ))\n");
+	fflush(xtrerr);
+    }
     errflag = 0;
     return !val;
 }
@@ -2546,16 +3019,16 @@ execarith(Cmd cmd)
 
 /**/
 static int
-exectime(Cmd cmd)
+exectime(Estate state, int do_exec)
 {
     int jb;
 
     jb = thisjob;
-    if (!cmd->u.pline) {
+    if (WC_TIMED_TYPE(state->pc[-1]) == WC_TIMED_EMPTY) {
 	shelltime();
 	return 0;
     }
-    execpline(cmd->u.pline, Z_TIMED|Z_SYNC, 0);
+    execpline(state, *state->pc++, Z_TIMED|Z_SYNC, 0);
     thisjob = jb;
     return lastval;
 }
@@ -2564,32 +3037,74 @@ exectime(Cmd cmd)
 
 /**/
 static int
-execfuncdef(Cmd cmd)
+execfuncdef(Estate state, int do_exec)
 {
     Shfunc shf;
     char *s;
-    int signum;
-
-    PERMALLOC {
-	while ((s = (char *) ugetnode(cmd->args))) {
-	    shf = (Shfunc) zalloc(sizeof *shf);
-	    shf->funcdef = (List) dupstruct(cmd->u.list);
-	    shf->flags = 0;
-
-	    /* is this shell function a signal trap? */
-	    if (!strncmp(s, "TRAP", 4) && (signum = getsignum(s + 4)) != -1) {
-		if (settrap(signum, shf->funcdef)) {
-		    freestruct(shf->funcdef);
-		    zfree(shf, sizeof *shf);
-		    LASTALLOC_RETURN 1;
-		}
-		sigtrapped[signum] |= ZSIG_FUNC;
+    int signum, nprg, sbeg, nstrs, npats, len, plen, i, htok = 0;
+    Wordcode beg = state->pc, end;
+    Eprog prog;
+    Patprog *pp;
+    LinkList names;
+
+    end = beg + WC_FUNCDEF_SKIP(state->pc[-1]);
+    names = ecgetlist(state, *state->pc++, EC_DUPTOK, &htok);
+    nprg = end - beg;
+    sbeg = *state->pc++;
+    nstrs = *state->pc++;
+    npats = *state->pc++;
+
+    nprg = (end - state->pc);
+    plen = nprg * sizeof(wordcode);
+    len = plen + (npats * sizeof(Patprog)) + nstrs;
+
+    if (htok)
+	execsubst(names);
+
+    while ((s = (char *) ugetnode(names))) {
+	prog = (Eprog) zalloc(sizeof(*prog));
+	prog->npats = npats;
+	prog->len = len;
+	if (state->prog->dump) {
+	    prog->flags = EF_MAP;
+	    incrdumpcount(state->prog->dump);
+	    prog->pats = pp = (Patprog *) zalloc(npats * sizeof(Patprog));
+	    prog->prog = state->pc;
+	    prog->strs = state->strs + sbeg;
+	    prog->dump = state->prog->dump;
+	} else {
+	    prog->flags = EF_REAL;
+	    prog->pats = pp = (Patprog *) zalloc(len);
+	    prog->prog = (Wordcode) (prog->pats + npats);
+	    prog->strs = (char *) (prog->prog + nprg);
+	    prog->dump = NULL;
+	    memcpy(prog->prog, state->pc, plen);
+	    memcpy(prog->strs, state->strs + sbeg, nstrs);
+	}
+	for (i = npats; i--; pp++)
+	    *pp = dummy_patprog1;
+	prog->shf = NULL;
+
+	shf = (Shfunc) zalloc(sizeof(*shf));
+	shf->funcdef = prog;
+	shf->flags = 0;
+
+	/* is this shell function a signal trap? */
+	if (!strncmp(s, "TRAP", 4) &&
+	    (signum = getsignum(s + 4)) != -1) {
+	    if (settrap(signum, shf->funcdef)) {
+		freeeprog(shf->funcdef);
+		zfree(shf, sizeof(*shf));
+		state->pc = end;
+		return 1;
 	    }
-	    shfunctab->addnode(shfunctab, ztrdup(s), shf);
+	    sigtrapped[signum] |= ZSIG_FUNC;
 	}
-    } LASTALLOC;
-    if(isset(HISTNOFUNCTIONS))
+	shfunctab->addnode(shfunctab, ztrdup(s), shf);
+    }
+    if (isset(HISTNOFUNCTIONS))
 	remhist();
+    state->pc = end;
     return 0;
 }
 
@@ -2597,22 +3112,45 @@ execfuncdef(Cmd cmd)
 
 /**/
 static void
-execshfunc(Cmd cmd, Shfunc shf)
+execshfunc(Shfunc shf, LinkList args)
 {
     LinkList last_file_list = NULL;
+    unsigned char *ocs;
+    int ocsp, osfc;
 
     if (errflag)
 	return;
 
-    if (!list_pipe) {
+    if (!list_pipe && thisjob != list_pipe_job) {
 	/* Without this deletejob the process table *
 	 * would be filled by a recursive function. */
 	last_file_list = jobtab[thisjob].filelist;
 	jobtab[thisjob].filelist = NULL;
 	deletejob(jobtab + thisjob);
     }
-
-    doshfunc(shf->funcdef, cmd->args, shf->flags, 0);
+    if (isset(XTRACE)) {
+	LinkNode lptr;
+	printprompt4();
+	if (args)
+	    for (lptr = firstnode(args); lptr; incnode(lptr)) {
+		if (lptr != firstnode(args))
+		    fputc(' ', xtrerr);
+		fprintf(xtrerr, "%s", (char *)getdata(lptr));
+	    }
+	fputc('\n', xtrerr);
+	fflush(xtrerr);
+    }
+    ocs = cmdstack;
+    ocsp = cmdsp;
+    cmdstack = (unsigned char *) zalloc(CMDSTACKSZ);
+    cmdsp = 0;
+    if ((osfc = sfcontext) == SFC_NONE)
+	sfcontext = SFC_DIRECT;
+    doshfunc(shf->nam, shf->funcdef, args, shf->flags, 0);
+    sfcontext = osfc;
+    free(cmdstack);
+    cmdstack = ocs;
+    cmdsp = ocsp;
 
     if (!list_pipe)
 	deletefilelist(last_file_list);
@@ -2626,168 +3164,244 @@ execshfunc(Cmd cmd, Shfunc shf)
 
 /**/
 static int
-execautofn(Cmd cmd)
+execautofn(Estate state, int do_exec)
 {
-    Shfunc shf = cmd->u.autofn->shf;
-    List l = getfpfunc(shf->nam);
-    if(l == &dummy_list) {
-	zerr("%s: function definition file not found", shf->nam, 0);
+    Shfunc shf;
+    char *oldscriptname;
+
+    if (!(shf = loadautofn(state->prog->shf, 1, 0)))
 	return 1;
+
+    oldscriptname = scriptname;
+    scriptname = dupstring(state->prog->shf->nam);
+    execode(shf->funcdef, 1, 0);
+    scriptname = oldscriptname;
+
+    return lastval;
+}
+
+/**/
+Shfunc
+loadautofn(Shfunc shf, int fksh, int autol)
+{
+    int noalias = noaliases, ksh = 1;
+    Eprog prog;
+
+    pushheap();
+
+    noaliases = (shf->flags & PM_UNALIASED);
+    prog = getfpfunc(shf->nam, &ksh);
+    noaliases = noalias;
+
+    if (ksh == 1)
+	ksh = fksh;
+
+    if (prog == &dummy_eprog) {
+	zerr("%s: function definition file not found", shf->nam, 0);
+	popheap();
+	return NULL;
     }
-    if(isset(KSHAUTOLOAD)) {
-	VARARR(char, n, strlen(shf->nam) + 1);
-	strcpy(n, shf->nam);
-	execlist(l, 1, 0);
-	shf = (Shfunc) shfunctab->getnode(shfunctab, n);
-	if(!shf || (shf->flags & PM_UNDEFINED)) {
-	    zerr("%s: function not defined by file", n, 0);
-	    return 1;
+    if (!prog)
+	prog = &dummy_eprog;
+    if (ksh == 2 || (ksh == 1 && isset(KSHAUTOLOAD))) {
+	if (autol) {
+	    prog->flags |= EF_RUN;
+
+	    freeeprog(shf->funcdef);
+	    if (prog->flags & EF_MAP)
+		shf->funcdef = prog;
+	    else
+		shf->funcdef = dupeprog(prog, 0);
+	    shf->flags &= ~PM_UNDEFINED;
+	} else {
+	    VARARR(char, n, strlen(shf->nam) + 1);
+	    strcpy(n, shf->nam);
+	    execode(prog, 1, 0);
+	    shf = (Shfunc) shfunctab->getnode(shfunctab, n);
+	    if (!shf || (shf->flags & PM_UNDEFINED)) {
+		zerr("%s: function not defined by file", n, 0);
+		popheap();
+		return NULL;
+	    }
 	}
     } else {
-	freestruct(shf->funcdef);
-	PERMALLOC {
-	    shf->funcdef = dupstruct(stripkshdef(l, shf->nam));
-	} LASTALLOC;
+	freeeprog(shf->funcdef);
+	if (prog->flags & EF_MAP)
+	    shf->funcdef = stripkshdef(prog, shf->nam);
+	else
+	    shf->funcdef = dupeprog(stripkshdef(prog, shf->nam), 0);
 	shf->flags &= ~PM_UNDEFINED;
     }
-    HEAPALLOC {
-	execlist(dupstruct(shf->funcdef), 1, 0);
-    } LASTALLOC;
-    return lastval;
+    popheap();
+
+    return shf;
 }
 
 /* execute a shell function */
 
 /**/
-void
-doshfunc(List list, LinkList doshargs, int flags, int noreturnval)
+mod_export void
+doshfunc(char *name, Eprog prog, LinkList doshargs, int flags, int noreturnval)
 /* If noreturnval is nonzero, then reset the current return *
  * value (lastval) to its value before the shell function   *
  * was executed.                                            */
 {
-    char **tab, **x, *oargv0 = NULL;
-    int xexittr, newexittr, oldzoptind, oldlastval;
-    char *ou;
-    void *xexitfn, *newexitfn;
-    char saveopts[OPT_SIZE];
-    int obreaks = breaks;
-
-    HEAPALLOC {
-	pushheap();
-	if (trapreturn < 0)
-	    trapreturn--;
-	oldlastval = lastval;
-	xexittr = sigtrapped[SIGEXIT];
-	if (xexittr & ZSIG_FUNC)
-	    xexitfn = shfunctab->removenode(shfunctab, "TRAPEXIT");
-	else
-	    xexitfn = sigfuncs[SIGEXIT];
-	sigtrapped[SIGEXIT] = 0;
-	sigfuncs[SIGEXIT] = NULL;
-	tab = pparams;
-	oldzoptind = zoptind;
-	zoptind = 1;
-
-	/* We need to save the current options even if LOCALOPTIONS is *
-	 * not currently set.  That's because if it gets set in the    *
-	 * function we need to restore the original options on exit.   */
-	memcpy(saveopts, opts, sizeof(opts));
-
-	if (flags & PM_TAGGED)
-	    opts[XTRACE] = 1;
-	opts[PRINTEXITVALUE] = 0;
-	if (doshargs) {
-	    LinkNode node;
-
-	    node = doshargs->first;
-	    pparams = x = (char **) zcalloc(((sizeof *x) * (1 + countlinknodes(doshargs))));
-	    if (isset(FUNCTIONARGZERO)) {
-		oargv0 = argzero;
-		argzero = ztrdup((char *) node->dat);
-	    }
-	    node = node->next;
-	    for (; node; node = node->next, x++)
-		*x = ztrdup((char *) node->dat);
-	} else {
-	    pparams = (char **) zcalloc(sizeof *pparams);
-	    if (isset(FUNCTIONARGZERO)) {
-		oargv0 = argzero;
-		argzero = ztrdup(argzero);
-	    }
+    char **tab, **x, *oargv0;
+    int oldzoptind, oldlastval, oldoptcind;
+    char saveopts[OPT_SIZE], *oldscriptname = NULL, *fname = dupstring(name);
+    int obreaks;
+    struct funcstack fstack;
+
+    pushheap();
+
+    oargv0 = NULL;
+    obreaks = breaks;;
+    if (trapreturn < 0)
+	trapreturn--;
+    oldlastval = lastval;
+
+    starttrapscope();
+
+    tab = pparams;
+    if (!(flags & PM_UNDEFINED)) {
+	oldscriptname = scriptname;
+	scriptname = dupstring(name);
+    }
+    oldzoptind = zoptind;
+    zoptind = 1;
+    oldoptcind = optcind;
+    optcind = 0;
+
+    /* We need to save the current options even if LOCALOPTIONS is *
+     * not currently set.  That's because if it gets set in the    *
+     * function we need to restore the original options on exit.   */
+    memcpy(saveopts, opts, sizeof(opts));
+
+    if (flags & PM_TAGGED)
+	opts[XTRACE] = 1;
+    opts[PRINTEXITVALUE] = 0;
+    if (doshargs) {
+	LinkNode node;
+
+	node = doshargs->first;
+	pparams = x = (char **) zcalloc(((sizeof *x) *
+					 (1 + countlinknodes(doshargs))));
+	if (isset(FUNCTIONARGZERO)) {
+	    oargv0 = argzero;
+	    argzero = ztrdup((char *) node->dat);
 	}
-	startparamscope();
-	ou = underscore;
-	underscore = ztrdup(underscore);
-	execlist(dupstruct(list), 1, 0);
-	zsfree(underscore);
-	underscore = ou;
-	endparamscope();
-
-	if (retflag) {
-	    retflag = 0;
-	    breaks = obreaks;
-	}
-	freearray(pparams);
-	if (oargv0) {
-	    zsfree(argzero);
-	    argzero = oargv0;
-	}
-	zoptind = oldzoptind;
-	pparams = tab;
-
-	if (isset(LOCALOPTIONS)) {
-	    /* restore all shell options except PRIVILEGED and RESTRICTED */
-	    saveopts[PRIVILEGED] = opts[PRIVILEGED];
-	    saveopts[RESTRICTED] = opts[RESTRICTED];
-	    memcpy(opts, saveopts, sizeof(opts));
-	} else {
-	    /* just restore a couple. */
-	    opts[XTRACE] = saveopts[XTRACE];
-	    opts[PRINTEXITVALUE] = saveopts[PRINTEXITVALUE];
-	    opts[LOCALOPTIONS] = saveopts[LOCALOPTIONS];
+	node = node->next;
+	for (; node; node = node->next, x++)
+	    *x = ztrdup((char *) node->dat);
+    } else {
+	pparams = (char **) zcalloc(sizeof *pparams);
+	if (isset(FUNCTIONARGZERO)) {
+	    oargv0 = argzero;
+	    argzero = ztrdup(argzero);
 	}
+    }
+    fstack.name = dupstring(name);
+    fstack.prev = funcstack;
+    funcstack = &fstack;
 
-	/*
-	 * The trap '...' EXIT runs in the environment of the caller,
-	 * so remember it here but run it after resetting the
-	 * traps for the parent.
-	 */
-	newexittr = sigtrapped[SIGEXIT];
-	newexitfn = sigfuncs[SIGEXIT];
-	if (newexittr & ZSIG_FUNC)
-	    shfunctab->removenode(shfunctab, "TRAPEXIT");
-
-	sigtrapped[SIGEXIT] = xexittr;
-	if (xexittr & ZSIG_FUNC) {
-	    shfunctab->addnode(shfunctab, ztrdup("TRAPEXIT"), xexitfn);
-	    sigfuncs[SIGEXIT] = ((Shfunc) xexitfn)->funcdef;
-	} else
-	    sigfuncs[SIGEXIT] = (List) xexitfn;
+    if (prog->flags & EF_RUN) {
+	Shfunc shf;
+
+	runshfunc(prog, NULL, fstack.name);
 
-	if (newexitfn) {
-	    dotrapargs(SIGEXIT, &newexittr, newexitfn);
-	    freestruct(newexitfn);
+	prog->flags &= ~EF_RUN;
+
+	if (!(shf = (Shfunc) shfunctab->getnode(shfunctab,
+						(name = fname)))) {
+	    zerr("%s: function not defined by file", name, 0);
+	    if (!noreturnval)
+		lastval = 1;
+	    popheap();
+	    return;
 	}
+	prog = shf->funcdef;
+    }
+    runshfunc(prog, wrappers, fstack.name);
+    funcstack = fstack.prev;
+    if (retflag) {
+	retflag = 0;
+	breaks = obreaks;
+    }
+    freearray(pparams);
+    if (oargv0) {
+	zsfree(argzero);
+	argzero = oargv0;
+    }
+    pparams = tab;
+    optcind = oldoptcind;
+    zoptind = oldzoptind;
+    if (oldscriptname)
+	scriptname = oldscriptname;
+
+    if (isset(LOCALOPTIONS)) {
+	/* restore all shell options except PRIVILEGED and RESTRICTED */
+	saveopts[PRIVILEGED] = opts[PRIVILEGED];
+	saveopts[RESTRICTED] = opts[RESTRICTED];
+	memcpy(opts, saveopts, sizeof(opts));
+    } else {
+	/* just restore a couple. */
+	opts[XTRACE] = saveopts[XTRACE];
+	opts[PRINTEXITVALUE] = saveopts[PRINTEXITVALUE];
+	opts[LOCALOPTIONS] = saveopts[LOCALOPTIONS];
+    }
 
-	if (trapreturn < -1)
-	    trapreturn++;
-	if (noreturnval)
-	    lastval = oldlastval;
-	popheap();
-    } LASTALLOC;
+    endtrapscope();
+
+    if (trapreturn < -1)
+	trapreturn++;
+    if (noreturnval)
+	lastval = oldlastval;
+    popheap();
+}
+
+/* This finally executes a shell function and any function wrappers     *
+ * defined by modules. This works by calling the wrapper function which *
+ * in turn has to call back this function with the arguments it gets.   */
+
+/**/
+mod_export void
+runshfunc(Eprog prog, FuncWrap wrap, char *name)
+{
+    int cont;
+    VARARR(char, ou, underscoreused);
+
+    memcpy(ou, underscore, underscoreused);
+
+    while (wrap) {
+	wrap->module->wrapper++;
+	cont = wrap->handler(prog, wrap->next, name);
+	wrap->module->wrapper--;
+
+	if (!wrap->module->wrapper &&
+	    (wrap->module->flags & MOD_UNLOAD))
+	    unload_module(wrap->module, NULL);
+
+	if (!cont)
+	    return;
+	wrap = wrap->next;
+    }
+    startparamscope();
+    execode(prog, 1, 0);
+    setunderscore(ou);
+    endparamscope();
 }
 
 /* Search fpath for an undefined function.  Finds the file, and returns the *
  * list of its contents.                                                    */
 
 /**/
-static List
-getfpfunc(char *s)
+Eprog
+getfpfunc(char *s, int *ksh)
 {
     char **pp, buf[PATH_MAX];
     off_t len;
     char *d;
-    List r;
+    Eprog r;
     int fd;
 
     pp = fpath;
@@ -2798,29 +3412,36 @@ getfpfunc(char *s)
 	    sprintf(buf, "%s/%s", *pp, s);
 	else
 	    strcpy(buf, s);
+	if ((r = try_dump_file(*pp, s, buf, ksh)))
+	    return r;
 	unmetafy(buf, NULL);
 	if (!access(buf, R_OK) && (fd = open(buf, O_RDONLY | O_NOCTTY)) != -1) {
 	    if ((len = lseek(fd, 0, 2)) != -1) {
+		d = (char *) zalloc(len + 1);
 		lseek(fd, 0, 0);
-		d = (char *) zcalloc(len + 1);
 		if (read(fd, d, len) == len) {
+		    char *oldscriptname = scriptname;
+
 		    close(fd);
+		    d[len] = '\0';
 		    d = metafy(d, len, META_REALLOC);
-		    HEAPALLOC {
-			r = parse_string(d);
-		    } LASTALLOC;
+
+		    scriptname = dupstring(s);
+		    r = parse_string(d, 1);
+		    scriptname = oldscriptname;
+
 		    zfree(d, len + 1);
+
 		    return r;
-		} else {
-		    zfree(d, len + 1);
+		} else
 		    close(fd);
-		}
-	    } else {
+
+		zfree(d, len + 1);
+	    } else
 		close(fd);
-	    }
 	}
     }
-    return &dummy_list;
+    return &dummy_eprog;
 }
 
 /* Handle the most common type of ksh-style autoloading, when doing a      *
@@ -2830,29 +3451,60 @@ getfpfunc(char *s)
  * contents of that definition.  Otherwise, use the entire file.           */
 
 /**/
-static List
-stripkshdef(List l, char *name)
+Eprog
+stripkshdef(Eprog prog, char *name)
 {
-    Sublist s;
-    Pline p;
-    Cmd c;
-    if(!l)
+    Wordcode pc = prog->prog;
+    wordcode code;
+
+    if (!prog)
 	return NULL;
-    if(l->type != Z_SYNC || l->right)
-	return l;
-    s = l->left;
-    if(s->flags || s->right)
-	return l;
-    p = s->left;
-    if(p->right)
-	return l;
-    c = p->left;
-    if(c->type != FUNCDEF || c->flags ||
-	nonempty(c->redir) || nonempty(c->vars) ||
-	empty(c->args) || lastnode(c->args) != firstnode(c->args) ||
-	strcmp(name, peekfirst(c->args)))
-	return l;
-    return c->u.list;
+    code = *pc++;
+    if (wc_code(code) != WC_LIST ||
+	(WC_LIST_TYPE(code) & (Z_SYNC|Z_END|Z_SIMPLE)) != (Z_SYNC|Z_END|Z_SIMPLE))
+	return prog;
+    pc++;
+    code = *pc++;
+    if (wc_code(code) != WC_FUNCDEF ||
+	*pc != 1 || strcmp(name, ecrawstr(prog, pc + 1, NULL)))
+	return prog;
+
+    {
+	Eprog ret;
+	Wordcode end = pc + WC_FUNCDEF_SKIP(code);
+	int sbeg = pc[2], nstrs = pc[3], nprg, npats = pc[4], plen, len, i;
+	Patprog *pp;
+
+	pc += 5;
+
+	nprg = end - pc;
+	plen = nprg * sizeof(wordcode);
+	len = plen + (npats * sizeof(Patprog)) + nstrs;
+
+	if (prog->flags & EF_MAP) {
+	    ret = prog;
+	    free(prog->pats);
+	    ret->pats = pp = (Patprog *) zalloc(npats * sizeof(Patprog));
+	    ret->prog = pc;
+	    ret->strs = prog->strs + sbeg;
+	} else {
+	    ret = (Eprog) zhalloc(sizeof(*ret));
+	    ret->flags = EF_HEAP;
+	    ret->pats = pp = (Patprog *) zhalloc(len);
+	    ret->prog = (Wordcode) (ret->pats + npats);
+	    ret->strs = (char *) (ret->prog + nprg);
+	    memcpy(ret->prog, pc, plen);
+	    memcpy(ret->strs, prog->strs + sbeg, nstrs);
+	    ret->dump = NULL;
+	}
+	ret->len = len;
+	ret->npats = npats;
+	for (i = npats; i--; pp++)
+	    *pp = dummy_patprog1;
+	ret->shf = NULL;
+
+	return ret;
+    }
 }
 
 /* check to see if AUTOCD applies here */
@@ -2901,9 +3553,26 @@ static int
 cancd2(char *s)
 {
     struct stat buf;
-    char *us = unmeta(s);
+    char *us, *us2 = NULL;
+    int ret;
 
-    return !(access(us, X_OK) || stat(us, &buf) || !S_ISDIR(buf.st_mode));
+    /*
+     * If CHASEDOTS and CHASELINKS are not set, we want to rationalize the
+     * path by removing foo/.. combinations in the logical rather than
+     * the physical path.  If either is set, we test the physical path.
+     */
+    if (!isset(CHASEDOTS) && !isset(CHASELINKS)) {
+	if (*s != '/')
+	    us = tricat(pwd[1] ? pwd : "", "/", s);
+	else
+	    us = ztrdup(s);
+	fixdir(us2 = us);
+    } else
+	us = unmeta(s);
+    ret = !(access(us, X_OK) || stat(us, &buf) || !S_ISDIR(buf.st_mode));
+    if (us2)
+	free(us2);
+    return ret;
 }
 
 /**/
@@ -2928,8 +3597,7 @@ execsave(void)
     es->trapreturn = trapreturn;
     es->noerrs = noerrs;
     es->subsh_close = subsh_close;
-    es->underscore = underscore;
-    underscore = ztrdup(underscore);
+    es->underscore = ztrdup(underscore);
     es->next = exstack;
     exstack = es;
     noerrs = cmdoutpid = 0;
@@ -2957,8 +3625,8 @@ execrestore(void)
     trapreturn = exstack->trapreturn;
     noerrs = exstack->noerrs;
     subsh_close = exstack->subsh_close;
-    zsfree(underscore);
-    underscore = exstack->underscore;
+    setunderscore(exstack->underscore);
+    zsfree(exstack->underscore);
     en = exstack->next;
     free(exstack);
     exstack = en;
diff --git a/Src/hashtable.c b/Src/hashtable.c
index 4adf3904d..7a881e9f8 100644
--- a/Src/hashtable.c
+++ b/Src/hashtable.c
@@ -77,13 +77,13 @@ static HashTable firstht, lastht;
 /* Generic hash function */
 
 /**/
-unsigned
+mod_export unsigned
 hasher(char *str)
 {
-    unsigned hashval = 0;
+    unsigned hashval = 0, c;
 
-    while (*str)
-	hashval += (hashval << 5) + ((unsigned) *str++);
+    while ((c = *((unsigned char *) str++)))
+	hashval += (hashval << 5) + c;
 
     return hashval;
 }
@@ -91,7 +91,7 @@ hasher(char *str)
 /* Get a new hash table */
 
 /**/
-HashTable
+mod_export HashTable
 newhashtable(int size, char const *name, PrintTableStats printinfo)
 {
     HashTable ht;
@@ -112,6 +112,7 @@ newhashtable(int size, char const *name, PrintTableStats printinfo)
     ht->hsize = size;
     ht->ct = 0;
     ht->scan = NULL;
+    ht->scantab = NULL;
     return ht;
 }
 
@@ -119,7 +120,7 @@ newhashtable(int size, char const *name, PrintTableStats printinfo)
  * existing pointers to the hash table are invalid.             */
 
 /**/
-void
+mod_export void
 deletehashtable(HashTable ht)
 {
     ht->emptytable(ht);
@@ -132,13 +133,14 @@ deletehashtable(HashTable ht)
 	ht->last->next = ht->next;
     else
 	firstht = ht->next;
+    zsfree(ht->tablename);
 #endif /* ZSH_HASH_DEBUG */
     zfree(ht->nodes, ht->hsize * sizeof(HashNode));
     zfree(ht, sizeof(*ht));
 }
 
 /* Add a node to a hash table.                          *
- * nam is the key to use in hashing.  dat is a pointer  *
+ * nam is the key to use in hashing.  nodeptr points    *
  * to the node to add.  If there is already a node in   *
  * the table with the same key, it is first freed, and  *
  * then the new node is added.  If the number of nodes  *
@@ -146,9 +148,20 @@ deletehashtable(HashTable ht)
  * the table is then expanded.                          */
 
 /**/
-void
+mod_export void
 addhashnode(HashTable ht, char *nam, void *nodeptr)
 {
+    HashNode oldnode = addhashnode2(ht, nam, nodeptr);
+    if (oldnode)
+	ht->freenode(oldnode);
+}
+
+/* Add a node to a hash table, returning the old node on replacment. */
+
+/**/
+HashNode
+addhashnode2(HashTable ht, char *nam, void *nodeptr)
+{
     unsigned hashval;
     HashNode hn, hp, hq;
 
@@ -164,15 +177,15 @@ addhashnode(HashTable ht, char *nam, void *nodeptr)
 	ht->nodes[hashval] = hn;
 	if (++ht->ct >= ht->hsize * 2 && !ht->scan)
 	    expandhashtable(ht);
-	return;
+	return NULL;
     }
 
     /* else check if the first node contains the same key */
-    if (!strcmp(hp->nam, hn->nam)) {
+    if (ht->cmpnodes(hp->nam, hn->nam) == 0) {
 	ht->nodes[hashval] = hn;
 	replacing:
 	hn->next = hp->next;
-	if(ht->scan)
+	if(ht->scan) {
 	    if(ht->scan->sorted) {
 		HashNode *tab = ht->scan->u.s.tab;
 		int i;
@@ -181,15 +194,15 @@ addhashnode(HashTable ht, char *nam, void *nodeptr)
 			tab[i] = hn;
 	    } else if(ht->scan->u.u == hp)
 		ht->scan->u.u = hn;
-	ht->freenode(hp);
-	return;
+	}
+	return hp;
     }
 
     /* else run through the list and check all the keys */
     hq = hp;
     hp = hp->next;
     for (; hp; hq = hp, hp = hp->next) {
-	if (!strcmp(hp->nam, hn->nam)) {
+	if (ht->cmpnodes(hp->nam, hn->nam) == 0) {
 	    hq->next = hn;
 	    goto replacing;
 	}
@@ -200,6 +213,7 @@ addhashnode(HashTable ht, char *nam, void *nodeptr)
     ht->nodes[hashval] = hn;
     if (++ht->ct >= ht->hsize * 2 && !ht->scan)
         expandhashtable(ht);
+    return NULL;
 }
 
 /* Get an enabled entry in a hash table.  *
@@ -208,7 +222,7 @@ addhashnode(HashTable ht, char *nam, void *nodeptr)
  * or isn't found, it returns NULL        */
 
 /**/
-HashNode
+mod_export HashNode
 gethashnode(HashTable ht, char *nam)
 {
     unsigned hashval;
@@ -216,7 +230,7 @@ gethashnode(HashTable ht, char *nam)
 
     hashval = ht->hash(nam) % ht->hsize;
     for (hp = ht->nodes[hashval]; hp; hp = hp->next) {
-	if (!strcmp(hp->nam, nam)) {
+	if (ht->cmpnodes(hp->nam, nam) == 0) {
 	    if (hp->flags & DISABLED)
 		return NULL;
 	    else
@@ -232,7 +246,7 @@ gethashnode(HashTable ht, char *nam)
  * it returns NULL.                       */
 
 /**/
-HashNode
+mod_export HashNode
 gethashnode2(HashTable ht, char *nam)
 {
     unsigned hashval;
@@ -240,7 +254,7 @@ gethashnode2(HashTable ht, char *nam)
 
     hashval = ht->hash(nam) % ht->hsize;
     for (hp = ht->nodes[hashval]; hp; hp = hp->next) {
-	if (!strcmp(hp->nam, nam))
+	if (ht->cmpnodes(hp->nam, nam) == 0)
 	    return hp;
     }
     return NULL;
@@ -252,7 +266,7 @@ gethashnode2(HashTable ht, char *nam)
  * is no such node, then it returns NULL        */
 
 /**/
-HashNode
+mod_export HashNode
 removehashnode(HashTable ht, char *nam)
 {
     unsigned hashval;
@@ -266,11 +280,11 @@ removehashnode(HashTable ht, char *nam)
 	return NULL;
 
     /* else check if the key in the first one matches */
-    if (!strcmp(hp->nam, nam)) {
+    if (ht->cmpnodes(hp->nam, nam) == 0) {
 	ht->nodes[hashval] = hp->next;
 	gotit:
 	ht->ct--;
-	if(ht->scan)
+	if(ht->scan) {
 	    if(ht->scan->sorted) {
 		HashNode *tab = ht->scan->u.s.tab;
 		int i;
@@ -279,6 +293,7 @@ removehashnode(HashTable ht, char *nam)
 			tab[i] = NULL;
 	    } else if(ht->scan->u.u == hp)
 		ht->scan->u.u = hp->next;
+	}
 	return hp;
     }
 
@@ -286,7 +301,7 @@ removehashnode(HashTable ht, char *nam)
     hq = hp;
     hp = hp->next;
     for (; hp; hq = hp, hp = hp->next) {
-	if (!strcmp(hp->nam, nam)) {
+	if (ht->cmpnodes(hp->nam, nam) == 0) {
 	    hq->next = hp->next;
 	    goto gotit;
 	}
@@ -314,7 +329,7 @@ enablehashnode(HashNode hn, int flags)
     hn->flags &= ~DISABLED;
 }
 
-/* Compare two hash table entries */
+/* Compare two hash table entries by name */
 
 /**/
 static int
@@ -343,11 +358,15 @@ hnamcmp(const void *ap, const void *bp)
  */
 
 /**/
-void
+mod_export void
 scanhashtable(HashTable ht, int sorted, int flags1, int flags2, ScanFunc scanfunc, int scanflags)
 {
     struct scanstatus st;
 
+    if (ht->scantab) {
+	ht->scantab(ht, scanfunc, scanflags);
+	return;
+    }
     if (sorted) {
 	int i, ct = ht->ct;
 	VARARR(HashNode, hnsorttab, ct);
@@ -399,7 +418,7 @@ scanhashtable(HashTable ht, int sorted, int flags1, int flags2, ScanFunc scanfun
 
 /**/
 int
-scanmatchtable(HashTable ht, Comp com, int flags1, int flags2, ScanFunc scanfunc, int scanflags)
+scanmatchtable(HashTable ht, Patprog pprog, int flags1, int flags2, ScanFunc scanfunc, int scanflags)
 {
     int i, hsize = ht->hsize;
     HashNode *nodes = ht->nodes;
@@ -414,9 +433,10 @@ scanmatchtable(HashTable ht, Comp com, int flags1, int flags2, ScanFunc scanfunc
 	    HashNode hn = st.u.u;
 	    st.u.u = st.u.u->next;
 	    if ((hn->flags & flags1) + !flags1 && !(hn->flags & flags2) &&
-		domatch(hn->nam, com, 0))
+		pattry(pprog, hn->nam)) {
 		scanfunc(hn, scanflags);
 		match++;
+	    }
 	}
 
     ht->scan = NULL;
@@ -489,12 +509,13 @@ resizehashtable(HashTable ht, int newsize)
 /* Generic method to empty a hash table */
 
 /**/
-void
+mod_export void
 emptyhashtable(HashTable ht)
 {
     resizehashtable(ht, ht->hsize);
 }
 
+/**/
 #ifdef ZSH_HASH_DEBUG
 
 /* Print info about hash table */
@@ -547,6 +568,7 @@ bin_hashinfo(char *nam, char **args, char *ops, int func)
     return 0;
 }
 
+/**/
 #endif /* ZSH_HASH_DEBUG */
 
 /********************************/
@@ -556,12 +578,12 @@ bin_hashinfo(char *nam, char **args, char *ops, int func)
 /* hash table containing external commands */
  
 /**/
-HashTable cmdnamtab;
+mod_export HashTable cmdnamtab;
  
 /* how far we've hashed the PATH so far */
  
 /**/
-char **pathchecked;
+mod_export char **pathchecked;
  
 /* Create a new command hash table */
  
@@ -574,6 +596,7 @@ createcmdnamtable(void)
     cmdnamtab->hash        = hasher;
     cmdnamtab->emptytable  = emptycmdnamtable;
     cmdnamtab->filltable   = fillcmdnamtable;
+    cmdnamtab->cmpnodes    = strcmp;
     cmdnamtab->addnode     = addhashnode;
     cmdnamtab->getnode     = gethashnode2;
     cmdnamtab->getnode2    = gethashnode2;
@@ -604,6 +627,9 @@ hashdir(char **dirp)
     Cmdnam cn;
     DIR *dir;
     char *fn;
+#ifdef _WIN32
+    char *exe;
+#endif
 
     if (isrelative(*dirp) || !(dir = opendir(unmeta(*dirp))))
 	return;
@@ -615,6 +641,23 @@ hashdir(char **dirp)
 	    cn->u.name = dirp;
 	    cmdnamtab->addnode(cmdnamtab, ztrdup(fn), cn);
 	}
+#ifdef _WIN32
+	/* Hash foo.exe as foo, since when no real foo exists, foo.exe
+	   will get executed by DOS automatically.  This quiets
+	   spurious corrections when CORRECT or CORRECT_ALL is set. */
+	if ((exe = strrchr(fn, '.')) &&
+	    (exe[1] == 'E' || exe[1] == 'e') &&
+	    (exe[2] == 'X' || exe[2] == 'x') &&
+	    (exe[3] == 'E' || exe[3] == 'e') && exe[4] == 0) {
+	    *exe = 0;
+	    if (!cmdnamtab->getnode(cmdnamtab, fn)) {
+		cn = (Cmdnam) zcalloc(sizeof *cn);
+		cn->flags = 0;
+		cn->u.name = dirp;
+		cmdnamtab->addnode(cmdnamtab, ztrdup(fn), cn);
+	    }
+	}
+#endif /* _WIN32 */
     }
     closedir(dir);
 }
@@ -713,7 +756,7 @@ printcmdnamnode(HashNode hn, int printflags)
 /* hash table containing the shell functions */
 
 /**/
-HashTable shfunctab;
+mod_export HashTable shfunctab;
 
 /**/
 void
@@ -724,6 +767,7 @@ createshfunctable(void)
     shfunctab->hash        = hasher;
     shfunctab->emptytable  = NULL;
     shfunctab->filltable   = NULL;
+    shfunctab->cmpnodes    = strcmp;
     shfunctab->addnode     = addhashnode;
     shfunctab->getnode     = gethashnode;
     shfunctab->getnode2    = gethashnode2;
@@ -798,7 +842,7 @@ freeshfuncnode(HashNode hn)
 
     zsfree(shf->nam);
     if (shf->funcdef)
-	freestruct(shf->funcdef);
+	freeeprog(shf->funcdef);
     zfree(shf, sizeof(struct shfunc));
 }
 
@@ -828,21 +872,34 @@ printshfuncnode(HashNode hn, int printflags)
     }
  
     if (f->flags & PM_UNDEFINED)
-	printf("undefined ");
-    if (f->flags & PM_TAGGED)
-	printf("traced ");
-    if ((f->flags & PM_UNDEFINED) || !f->funcdef) {
-	nicezputs(f->nam, stdout);
-	printf(" () { }\n");
-	return;
+	t = tricat("builtin autoload -X",
+		   ((f->flags & PM_UNALIASED)? "U" : ""),
+		   ((f->flags & PM_TAGGED)? "t" : ""));
+    else {
+	if (!f->funcdef)
+	    t = 0;
+	else
+	    t = getpermtext(f->funcdef, NULL);
     }
- 
-    t = getpermtext((void *) dupstruct((void *) f->funcdef));
+
     quotedzputs(f->nam, stdout);
-    printf(" () {\n\t");
-    zputs(t, stdout);
-    printf("\n}\n");
-    zsfree(t);
+    if (t) {
+	printf(" () {\n\t");
+	if (f->flags & PM_UNDEFINED)
+	    printf("%c undefined\n\t", hashchar);
+	if (f->flags & PM_TAGGED)
+	    printf("%c traced\n\t", hashchar);
+	zputs(t, stdout);
+	if (f->funcdef && (f->funcdef->flags & EF_RUN)) {
+	    printf("\n\t");
+	    quotedzputs(f->nam, stdout);
+	    printf(" \"$@\"");
+	}
+	printf("\n}\n");
+	zsfree(t);
+    } else {
+	printf(" () { }\n");
+    }
 }
 
 /**************************************/
@@ -882,7 +939,7 @@ static struct reswd reswds[] = {
 /* hash table containing the reserved words */
 
 /**/
-HashTable reswdtab;
+mod_export HashTable reswdtab;
 
 /* Build the hash table containing zsh's reserved words. */
 
@@ -897,6 +954,7 @@ createreswdtable(void)
     reswdtab->hash        = hasher;
     reswdtab->emptytable  = NULL;
     reswdtab->filltable   = NULL;
+    reswdtab->cmpnodes    = strcmp;
     reswdtab->addnode     = addhashnode;
     reswdtab->getnode     = gethashnode;
     reswdtab->getnode2    = gethashnode2;
@@ -944,7 +1002,7 @@ printreswdnode(HashNode hn, int printflags)
 /* hash table containing the aliases */
  
 /**/
-HashTable aliastab;
+mod_export HashTable aliastab;
  
 /* Create new hash table for aliases */
 
@@ -957,6 +1015,7 @@ createaliastable(void)
     aliastab->hash        = hasher;
     aliastab->emptytable  = NULL;
     aliastab->filltable   = NULL;
+    aliastab->cmpnodes    = strcmp;
     aliastab->addnode     = addhashnode;
     aliastab->getnode     = gethashnode;
     aliastab->getnode2    = gethashnode2;
@@ -974,7 +1033,7 @@ createaliastable(void)
 /* Create a new alias node */
 
 /**/
-Alias
+mod_export Alias
 createaliasnode(char *txt, int flags)
 {
     Alias al;
@@ -1061,101 +1120,25 @@ 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 */
 /****************************************/
 
+#ifdef HAVE_NIS_PLUS
+# include <rpcsvc/nis.h>
+#else
+# ifdef HAVE_NIS
+#  include	<rpc/types.h>
+#  include	<rpc/rpc.h>
+#  include	<rpcsvc/ypclnt.h>
+#  include	<rpcsvc/yp_prot.h>
+# endif
+#endif
+
 /* hash table containing named directories */
 
 /**/
-HashTable nameddirtab;
+mod_export HashTable nameddirtab;
  
 /* != 0 if all the usernames have already been *
  * added to the named directory hash table.    */
@@ -1173,6 +1156,7 @@ createnameddirtable(void)
     nameddirtab->hash        = hasher;
     nameddirtab->emptytable  = emptynameddirtable;
     nameddirtab->filltable   = fillnameddirtable;
+    nameddirtab->cmpnodes    = strcmp;
     nameddirtab->addnode     = addnameddirnode;
     nameddirtab->getnode     = gethashnode;
     nameddirtab->getnode2    = gethashnode2;
@@ -1200,12 +1184,122 @@ emptynameddirtable(HashTable ht)
 /* Add all the usernames in the password file/database *
  * to the named directories table.                     */
 
+#ifdef HAVE_NIS_PLUS
+static int
+add_userdir(nis_name table, nis_object *object, void *userdata)
+{
+    if (object->zo_data.objdata_u.en_data.en_cols.en_cols >= 6) {
+	static char name[40], dir[PATH_MAX + 1];
+	register entry_col *ec =
+	    object->zo_data.objdata_u.en_data.en_cols.en_cols_val;
+	register int nl = minimum(ec[0].ec_value.ec_value_len, 39);
+	register int dl = minimum(ec[5].ec_value.ec_value_len, PATH_MAX);
+
+	memcpy(name, ec[0].ec_value.ec_value_val, nl);
+	name[nl] = '\0';
+	memcpy(dir, ec[5].ec_value.ec_value_val, dl);
+	dir[dl] = '\0';
+
+	adduserdir(name, dir, ND_USERNAME, 1);
+    }
+    return 0;
+}
+#else
+# ifdef HAVE_NIS
+static int
+add_userdir(int status, char *key, int keylen, char *val, int vallen, char *dummy)
+{
+    char *p, *d, *de;
+
+    if (status != YP_TRUE)
+	return 1;
+
+    if (vallen > keylen && *(p = val + keylen) == ':') {
+	*p++ = '\0';
+	if ((de = strrchr(p, ':'))) {
+	    *de = '\0';
+	    if ((d = strrchr(p, ':'))) {
+		if (*++d && val[0])
+		    adduserdir(val, d, ND_USERNAME, 1);
+	    }
+	}
+    }
+    return 0;
+}
+# endif /* HAVE_NIS */
+#endif  /* HAVE_NIS_PLUS */
+
 /**/
 static void
 fillnameddirtable(HashTable ht)
 {
-#ifdef HAVE_GETPWENT
     if (!allusersadded) {
+#if defined(HAVE_NIS) || defined(HAVE_NIS_PLUS)
+	FILE *pwf;
+	char buf[BUFSIZ], *p, *d, *de;
+	int skipping, oldct = nameddirtab->ct, usepwf = 1;
+
+# ifndef HAVE_NIS_PLUS
+	char domain[YPMAXDOMAIN];
+	struct ypall_callback cb;
+
+	/* Get potential matches from NIS and cull those without local accounts */
+	if (getdomainname(domain, YPMAXDOMAIN) == 0) {
+	    cb.foreach = (int (*)()) add_userdir;
+	    cb.data = NULL;
+	    yp_all(domain, PASSWD_MAP, &cb);
+    }
+# else  /* HAVE_NIS_PLUS */
+	/* Maybe we should turn this string into a #define'd constant...? */
+
+	nis_list("passwd.org_dir", EXPAND_NAME|ALL_RESULTS|FOLLOW_LINKS|FOLLOW_PATH,
+		 add_userdir, 0);
+# endif
+	if (nameddirtab->ct == oldct) {
+	    /* Using NIS or NIS+ didn't add any user directories. This seems
+	     * fishy, so we fall back to using getpwent(). If we don't have
+	     * that, we only use the passwd file. */
+#ifdef HAVE_GETPWENT
+	    struct passwd *pw;
+ 
+	    setpwent();
+ 
+	    /* loop through the password file/database *
+	     * and add all entries returned.           */
+	    while ((pw = getpwent()) && !errflag)
+		adduserdir(pw->pw_name, pw->pw_dir, ND_USERNAME, 1);
+ 
+	    endpwent();
+	    usepwf = 0;
+#endif /* HAVE_GETPWENT */
+	}
+	if (usepwf) {
+	    /* Don't forget the non-NIS matches from the flat passwd file */
+	    if ((pwf = fopen(PASSWD_FILE, "r")) != NULL) {
+		skipping = 0;
+		while (fgets(buf, BUFSIZ, pwf) != NULL) {
+		    if (strchr(buf, '\n') != NULL) {
+			if (!skipping) {
+			    if ((p = strchr(buf, ':')) != NULL) {
+				*p++ = '\0';
+				if ((de = strrchr(p, ':'))) {
+				    *de = '\0';
+				    if ((d = strrchr(p, ':'))) {
+					if (*++d && buf[0])
+					    adduserdir(buf, d, ND_USERNAME, 1);
+				    }
+				}
+			    }
+			} else
+			    skipping = 0;
+		    } else
+			skipping = 1;
+		}
+		fclose(pwf);
+	    }
+	}
+#else  /* no NIS or NIS_PLUS */
+#ifdef HAVE_GETPWENT
 	struct passwd *pw;
  
 	setpwent();
@@ -1213,13 +1307,13 @@ fillnameddirtable(HashTable ht)
 	/* loop through the password file/database *
 	 * and add all entries returned.           */
 	while ((pw = getpwent()) && !errflag)
-	    adduserdir(ztrdup(pw->pw_name), pw->pw_dir, ND_USERNAME, 1);
+	    adduserdir(pw->pw_name, pw->pw_dir, ND_USERNAME, 1);
  
 	endpwent();
+#endif /* HAVE_GETPWENT */
+#endif
 	allusersadded = 1;
     }
-    return;
-#endif /* HAVE_GETPWENT */
 }
 
 /* Add an entry to the named directory hash *
@@ -1283,3 +1377,137 @@ printnameddirnode(HashNode hn, int printflags)
     quotedzputs(nd->dir, stdout);
     putchar('\n');
 }
+
+/*************************************/
+/* History Line Hash Table Functions */
+/*************************************/
+
+/**/
+void
+createhisttable(void)
+{
+    histtab = newhashtable(599, "histtab", NULL);
+
+    histtab->hash        = histhasher;
+    histtab->emptytable  = emptyhisttable;
+    histtab->filltable   = NULL;
+    histtab->cmpnodes    = histstrcmp;
+    histtab->addnode     = addhistnode;
+    histtab->getnode     = gethashnode2;
+    histtab->getnode2    = gethashnode2;
+    histtab->removenode  = removehashnode;
+    histtab->disablenode = NULL;
+    histtab->enablenode  = NULL;
+    histtab->freenode    = freehistnode;
+    histtab->printnode   = NULL;
+}
+
+/**/
+unsigned
+histhasher(char *str)
+{
+    unsigned hashval = 0;
+
+    while (inblank(*str)) str++;
+
+    while (*str) {
+	if (inblank(*str)) {
+	    do str++; while (inblank(*str));
+	    if (*str)
+		hashval += (hashval << 5) + ' ';
+	}
+	else
+	    hashval += (hashval << 5) + *(unsigned char *)str++;
+    }
+    return hashval;
+}
+
+/**/
+void
+emptyhisttable(HashTable ht)
+{
+    emptyhashtable(ht);
+    if (hist_ring)
+	histremovedups();
+}
+
+/* Compare two strings with normalized white-space */
+
+/**/
+int
+histstrcmp(const char *str1, const char *str2)
+{
+    while (inblank(*str1)) str1++;
+    while (inblank(*str2)) str2++;
+    while (*str1 && *str2) {
+	if (inblank(*str1)) {
+	    if (!inblank(*str2))
+		break;
+	    do str1++; while (inblank(*str1));
+	    do str2++; while (inblank(*str2));
+	}
+	else {
+	    if (*str1 != *str2)
+		break;
+	    str1++;
+	    str2++;
+	}
+    }
+    return *str1 - *str2;
+}
+
+/**/
+void
+addhistnode(HashTable ht, char *nam, void *nodeptr)
+{
+    HashNode oldnode = addhashnode2(ht, nam, nodeptr);
+    Histent he = (Histent)nodeptr;
+    if (oldnode && oldnode != (HashNode)nodeptr) {
+	if (he->flags & HIST_MAKEUNIQUE
+	 || (he->flags & HIST_FOREIGN && (Histent)oldnode == he->up)) {
+	    he->flags |= HIST_DUP;
+	    addhashnode(ht, oldnode->nam, oldnode); /* Remove the new dup */
+	}
+	else {
+	    oldnode->flags |= HIST_DUP;
+	    if (hist_ignore_all_dups)
+		freehistnode(oldnode); /* Remove the old dup */
+	}
+    }
+    else
+	he->flags &= ~HIST_MAKEUNIQUE;
+}
+
+/**/
+void
+freehistnode(HashNode nodeptr)
+{
+    freehistdata((Histent)nodeptr, 1);
+    zfree(nodeptr, sizeof (struct histent));
+}
+
+/**/
+void
+freehistdata(Histent he, int unlink)
+{
+    if (!he)
+	return;
+
+    if (!(he->flags & HIST_DUP))
+	removehashnode(histtab, he->text);
+
+    zsfree(he->text);
+    if (he->nwords)
+	zfree(he->words, he->nwords*2*sizeof(short));
+
+    if (unlink) {
+	if (!--histlinect)
+	    hist_ring = NULL;
+	else {
+	    if (he == hist_ring)
+		hist_ring = hist_ring->up;
+	    he->up->down = he->down;
+	    he->down->up = he->up;
+	}
+    }
+}
diff --git a/Src/jobs.c b/Src/jobs.c
index 331902d9f..962b0ded0 100644
--- a/Src/jobs.c
+++ b/Src/jobs.c
@@ -33,12 +33,12 @@
 /* the process group of the shell */
 
 /**/
-pid_t mypgrp;
+mod_export pid_t mypgrp;
  
 /* the job we are working on */
  
 /**/
-int thisjob;
+mod_export int thisjob;
 
 /* the current job (+) */
  
@@ -53,8 +53,8 @@ int prevjob;
 /* the job table */
  
 /**/
-struct job jobtab[MAXJOB];
- 
+mod_export struct job jobtab[MAXJOB];
+
 /* shell timings */
  
 /**/
@@ -64,13 +64,18 @@ struct tms shtms;
  
 /**/
 int ttyfrozen;
- 
-/* empty job structure for quick clearing of jobtab entries */
 
-static struct job zero;		/* static variables are initialized to zero */
+/* Previous values of errflag and breaks if the signal handler had to
+ * change them. And a flag saying if it did that. */
+
+/**/
+int prev_errflag, prev_breaks, errbrk_saved;
 
 static struct timeval dtimeval, now;
 
+/**/
+int numpipestats, pipestats[MAX_PIPESTATS];
+
 /* Diff two timevals for elapsed-time computations */
 
 /**/
@@ -96,9 +101,13 @@ makerunning(Job jn)
 
     jn->stat &= ~STAT_STOPPED;
     for (pn = jn->procs; pn; pn = pn->next)
+#if 0
 	if (WIFSTOPPED(pn->status) && 
 	    (!(jn->stat & STAT_SUPERJOB) || pn->next))
 	    pn->status = SP_RUNNING;
+#endif
+        if (WIFSTOPPED(pn->status))
+	    pn->status = SP_RUNNING;
 
     if (jn->stat & STAT_SUPERJOB)
 	makerunning(jobtab + jn->other);
@@ -125,6 +134,86 @@ findproc(pid_t pid, Job *jptr, Process *pptr)
     return 0;
 }
 
+/* Find the super-job of a sub-job. */
+
+/**/
+static int
+super_job(int sub)
+{
+    int i;
+
+    for (i = 1; i < MAXJOB; i++)
+	if ((jobtab[i].stat & STAT_SUPERJOB) &&
+	    jobtab[i].other == sub &&
+	    jobtab[i].gleader)
+	    return i;
+    return 0;
+}
+
+/**/
+static int
+handle_sub(int job, int fg)
+{
+    Job jn = jobtab + job, sj = jobtab + jn->other;
+
+    if ((sj->stat & STAT_DONE) || !sj->procs) {
+	struct process *p;
+		    
+	for (p = sj->procs; p; p = p->next)
+	    if (WIFSIGNALED(p->status)) {
+		if (jn->gleader != mypgrp && jn->procs->next)
+		    killpg(jn->gleader, WTERMSIG(p->status));
+		else
+		    kill(jn->procs->pid, WTERMSIG(p->status));
+		kill(sj->other, SIGCONT);
+		kill(sj->other, WTERMSIG(p->status));
+		break;
+	    }
+	if (!p) {
+	    int cp;
+
+	    jn->stat &= ~STAT_SUPERJOB;
+	    jn->stat |= STAT_WASSUPER;
+
+	    if ((cp = ((WIFEXITED(jn->procs->status) ||
+			WIFSIGNALED(jn->procs->status)) &&
+		       killpg(jn->gleader, 0) == -1))) {
+		Process p;
+		for (p = jn->procs; p->next; p = p->next);
+		jn->gleader = p->pid;
+	    }
+	    /* This deleted the job too early if the parent
+	       shell waited for a command in a list that will
+	       be executed by the sub-shell (e.g.: if we have
+	       `ls|if true;then sleep 20;cat;fi' and ^Z the
+	       sleep, the rest will be executed by a sub-shell,
+	       but the parent shell gets notified for the
+	       sleep.
+	       deletejob(sj); */
+	    /* If this super-job contains only the sub-shell,
+	       we have to attach the tty to its process group
+	       now. */
+	    if ((fg || thisjob == job) &&
+		(!jn->procs->next || cp || jn->procs->pid != jn->gleader))
+		attachtty(jn->gleader);
+	    kill(sj->other, SIGCONT);
+	}
+	curjob = jn - jobtab;
+    } else if (sj->stat & STAT_STOPPED) {
+	struct process *p;
+
+	jn->stat |= STAT_STOPPED;
+	for (p = jn->procs; p; p = p->next)
+	    if (p->status == SP_RUNNING ||
+		(!WIFEXITED(p->status) && !WIFSIGNALED(p->status)))
+		p->status = sj->procs->status;
+	curjob = jn - jobtab;
+	printjob(jn, !!isset(LONGLISTJOBS), 1);
+	return 1;
+    }
+    return 0;
+}
+
 /* Update status of process that we have just WAIT'ed for */
 
 /**/
@@ -175,16 +264,31 @@ update_job(Job jn)
 	    jn->ty = (struct ttyinfo *) zalloc(sizeof(struct ttyinfo));
 	    gettyinfo(jn->ty);
 	}
-	if (jn->stat & STAT_STOPPED)
+	if (jn->stat & STAT_STOPPED) {
+	    if (jn->stat & STAT_SUBJOB) {
+		/* If we have `cat foo|while read a; grep $a bar;done'
+		 * and have hit ^Z, the sub-job is stopped, but the
+		 * super-job may still be running, waiting to be stopped
+		 * or to exit. So we have to send it a SIGTSTP. */
+		int i;
+
+		if ((i = super_job(job)))
+		    killpg(jobtab[i].gleader, SIGTSTP);
+	    }
 	    return;
-    } else {                   /* job is done, so remember return value */
+	}
+    }
+    {                   /* job is done or stopped, remember return value */
 	lastval2 = val;
 	/* If last process was run in the current shell, keep old status
-	 * and let it handle its own traps
+	 * and let it handle its own traps, but always allow the test
+	 * for the pgrp.
 	 */
-	if (job == thisjob && !(jn->stat & STAT_CURSH)) {
-	  lastval = val;
-	  inforeground = 1;
+	if (jn->stat & STAT_CURSH)
+	    inforeground = 1;
+	else if (job == thisjob) {
+	    lastval = val;
+	    inforeground = 2;
 	}
     }
 
@@ -198,15 +302,72 @@ update_job(Job jn)
 	/* is this job in the foreground of an interactive shell? */
 	if (mypgrp != pgrp && inforeground &&
 	    (jn->gleader == pgrp || (pgrp > 1 && kill(-pgrp, 0) == -1))) {
-	    attachtty(mypgrp);
-	    adjustwinsize();   /* check window size and adjust if necessary */
+	    if (list_pipe) {
+		if (somestopped || (pgrp > 1 && kill(-pgrp, 0) == -1)) {
+		    attachtty(mypgrp);
+		    /* check window size and adjust if necessary */
+		    adjustwinsize(0);
+		} else {
+		    /*
+		     * Oh, dear, we're right in the middle of some confusion
+		     * of shell jobs on the righthand side of a pipeline, so
+		     * it's death to call attachtty() just yet.  Mark the
+		     * fact in the job, so that the attachtty() will be called
+		     * when the job is finally deleted.
+		     */
+		    jn->stat |= STAT_ATTACH;
+		}
+		/* If we have `foo|while true; (( x++ )); done', and hit
+		 * ^C, we have to stop the loop, too. */
+		if ((val & 0200) && inforeground == 1) {
+		    if (!errbrk_saved) {
+			errbrk_saved = 1;
+			prev_breaks = breaks;
+			prev_errflag = errflag;
+		    }
+		    breaks = loops;
+		    errflag = 1;
+		    inerrflush();
+		}
+	    } else {
+		attachtty(mypgrp);
+		/* check window size and adjust if necessary */
+		adjustwinsize(0);
+	    }
 	}
+    } else if (list_pipe && (val & 0200) && inforeground == 1) {
+	if (!errbrk_saved) {
+	    errbrk_saved = 1;
+	    prev_breaks = breaks;
+	    prev_errflag = errflag;
+	}
+	breaks = loops;
+	errflag = 1;
+	inerrflush();
     }
-
     if (somestopped && jn->stat & STAT_SUPERJOB)
 	return;
     jn->stat |= (somestopped) ? STAT_CHANGED | STAT_STOPPED :
 	STAT_CHANGED | STAT_DONE;
+    if (job == thisjob && (jn->stat & STAT_DONE)) {
+	int i;
+	Process p;
+
+	for (p = jn->procs, i = 0; p && i < MAX_PIPESTATS; p = p->next, i++)
+	    pipestats[i] = ((WIFSIGNALED(p->status)) ?
+			    0200 | WTERMSIG(p->status) :
+			    WEXITSTATUS(p->status));
+	if ((jn->stat & STAT_CURSH) && i < MAX_PIPESTATS)
+	    pipestats[i++] = lastval;
+	numpipestats = i;
+    }
+    if (!inforeground &&
+	(jn->stat & (STAT_SUBJOB | STAT_DONE)) == (STAT_SUBJOB | STAT_DONE)) {
+	int su;
+
+	if ((su = super_job(jn - jobtab)))
+	    handle_sub(su, 0);
+    }
     if ((jn->stat & (STAT_DONE | STAT_STOPPED)) == STAT_STOPPED) {
 	prevjob = curjob;
 	curjob = job;
@@ -214,7 +375,7 @@ update_job(Job jn)
     if ((isset(NOTIFY) || job == thisjob) && (jn->stat & STAT_LOCKED)) {
 	printjob(jn, !!isset(LONGLISTJOBS), 0);
 	if (zleactive)
-	    refresh();
+	    zrefresh();
     }
     if (sigtrapped[SIGCHLD] && job != thisjob)
 	dotrap(SIGCHLD);
@@ -223,7 +384,7 @@ update_job(Job jn)
      * process group from the shell, so the shell will not receive     *
      * terminal signals, therefore we we pretend that the shell got    *
      * the signal too.                                                 */
-    if (inforeground && isset(MONITOR) && WIFSIGNALED(status)) {
+    if (inforeground == 2 && isset(MONITOR) && WIFSIGNALED(status)) {
 	int sig = WTERMSIG(status);
 
 	if (sig == SIGINT || sig == SIGQUIT) {
@@ -256,13 +417,14 @@ setprevjob(void)
 
     for (i = MAXJOB - 1; i; i--)
 	if ((jobtab[i].stat & STAT_INUSE) && (jobtab[i].stat & STAT_STOPPED) &&
-	    i != curjob && i != thisjob) {
+	    !(jobtab[i].stat & STAT_SUBJOB) && i != curjob && i != thisjob) {
 	    prevjob = i;
 	    return;
 	}
 
     for (i = MAXJOB - 1; i; i--)
-	if ((jobtab[i].stat & STAT_INUSE) && i != curjob && i != thisjob) {
+	if ((jobtab[i].stat & STAT_INUSE) && !(jobtab[i].stat & STAT_SUBJOB) &&
+	    i != curjob && i != thisjob) {
 	    prevjob = i;
 	    return;
 	}
@@ -409,6 +571,7 @@ dumptime(Job jn)
 static int
 should_report_time(Job j)
 {
+    struct value vbuf;
     Value v;
     char *s = "REPORTTIME";
     int reporttime;
@@ -417,9 +580,10 @@ should_report_time(Job j)
     if (j->stat & STAT_TIMED)
 	return 1;
 
-    if (!(v = getvalue(&s, 0)) || (reporttime = getintvalue(v)) < 0)
+    if (!(v = getvalue(&vbuf, &s, 0)) ||
+	(reporttime = getintvalue(v)) < 0) {
 	return 0;
-
+    }
     /* can this ever happen? */
     if (!j->procs)
 	return 0;
@@ -462,10 +626,10 @@ printjob(Job jn, int lng, int synch)
 	if (jn->stat & STAT_SUPERJOB &&
 	    jn->procs->status == SP_RUNNING && !pn->next)
 	    pn->status = SP_RUNNING;
-	if (pn->status != SP_RUNNING)
+	if (pn->status != SP_RUNNING) {
 	    if (WIFSIGNALED(pn->status)) {
 		sig = WTERMSIG(pn->status);
-		llen = strlen(sigmsg[sig]);
+		llen = strlen(sigmsg(sig));
 		if (WCOREDUMP(pn->status))
 		    llen += 14;
 		if (llen > len)
@@ -476,13 +640,14 @@ printjob(Job jn, int lng, int synch)
 		    doputnl = 1;
 	    } else if (WIFSTOPPED(pn->status)) {
 		sig = WSTOPSIG(pn->status);
-		if ((int)strlen(sigmsg[sig]) > len)
-		    len = strlen(sigmsg[sig]);
+		if ((int)strlen(sigmsg(sig)) > len)
+		    len = strlen(sigmsg(sig));
 		if (job == thisjob && sig == SIGTSTP)
 		    doputnl = 1;
 	    } else if (isset(PRINTEXITVALUE) && isset(SHINSTDIN) &&
 		       WEXITSTATUS(pn->status))
 		sflag = 1;
+	}
     }
 
 /* print if necessary */
@@ -508,7 +673,7 @@ printjob(Job jn, int lng, int synch)
 			break;
 		    len2 += strlen(qn->text) + 2;
 		}
-	    if (job != thisjob)
+	    if (job != thisjob) {
 		if (fline)
 		    fprintf(fout, "[%ld]  %c ",
 			    (long)(jn - jobtab),
@@ -516,7 +681,7 @@ printjob(Job jn, int lng, int synch)
 			    : (job == prevjob) ? '-' : ' ');
 		else
 		    fprintf(fout, (job > 9) ? "        " : "       ");
-	    else
+	    } else
 		fprintf(fout, "zsh: ");
 	    if (lng & 1)
 		fprintf(fout, "%ld ", (long) pn->pid);
@@ -531,25 +696,26 @@ printjob(Job jn, int lng, int synch)
 		lng &= ~3;
 	    } else
 		fprintf(fout, "%*s", skip, "");
-	    if (pn->status == SP_RUNNING)
+	    if (pn->status == SP_RUNNING) {
 		if (!conted)
 		    fprintf(fout, "running%*s", len - 7 + 2, "");
 		else
 		    fprintf(fout, "continued%*s", len - 9 + 2, "");
-	    else if (WIFEXITED(pn->status))
+	    }
+	    else if (WIFEXITED(pn->status)) {
 		if (WEXITSTATUS(pn->status))
 		    fprintf(fout, "exit %-4d%*s", WEXITSTATUS(pn->status),
 			    len - 9 + 2, "");
 		else
 		    fprintf(fout, "done%*s", len - 4 + 2, "");
-	    else if (WIFSTOPPED(pn->status))
-		fprintf(fout, "%-*s", len + 2, sigmsg[WSTOPSIG(pn->status)]);
+	    } else if (WIFSTOPPED(pn->status))
+		fprintf(fout, "%-*s", len + 2, sigmsg(WSTOPSIG(pn->status)));
 	    else if (WCOREDUMP(pn->status))
 		fprintf(fout, "%s (core dumped)%*s",
-			sigmsg[WTERMSIG(pn->status)],
-			(int)(len - 14 + 2 - strlen(sigmsg[WTERMSIG(pn->status)])), "");
+			sigmsg(WTERMSIG(pn->status)),
+			(int)(len - 14 + 2 - strlen(sigmsg(WTERMSIG(pn->status)))), "");
 	    else
-		fprintf(fout, "%-*s", len + 2, sigmsg[WTERMSIG(pn->status)]);
+		fprintf(fout, "%-*s", len + 2, sigmsg(WTERMSIG(pn->status)));
 	    for (; pn != qn; pn = pn->next)
 		fprintf(fout, (pn->next) ? "%s | " : "%s", pn->text);
 	    putc('\n', fout);
@@ -565,7 +731,8 @@ printjob(Job jn, int lng, int synch)
  * the directory where the job is running, otherwise the current directory
  */
 
-    if ((lng & 4) || (interact && job == thisjob && strcmp(jn->pwd, pwd))) {
+    if ((lng & 4) || (interact && job == thisjob &&
+		      jn->pwd && strcmp(jn->pwd, pwd))) {
 	fprintf(shout, "(pwd %s: ", (lng & 4) ? "" : "now");
 	fprintdir((lng & 4) ? jn->pwd : pwd, shout);
 	fprintf(shout, ")\n");
@@ -607,20 +774,31 @@ deletejob(Job jn)
 {
     struct process *pn, *nx;
 
+    if (jn->stat & STAT_ATTACH) {
+	attachtty(mypgrp);
+	adjustwinsize(0);
+    }
+
     pn = jn->procs;
     jn->procs = NULL;
     for (; pn; pn = nx) {
 	nx = pn->next;
 	zfree(pn, sizeof(struct process));
     }
-    zsfree(jn->pwd);
-
     deletefilelist(jn->filelist);
 
     if (jn->ty)
 	zfree(jn->ty, sizeof(struct ttyinfo));
-
-    *jn = zero;
+    if (jn->pwd)
+	zsfree(jn->pwd);
+    jn->pwd = NULL;
+    if (jn->stat & STAT_WASSUPER)
+	deletejob(jobtab + jn->other);
+    jn->gleader = jn->other = 0;
+    jn->stat = jn->stty_in_env = 0;
+    jn->procs = NULL;
+    jn->filelist = NULL;
+    jn->ty = NULL;
 }
 
 /* add a process to the current job */
@@ -729,40 +907,20 @@ waitjob(int job, int sig)
 	       what this might be.  --oberon
 
 	    errflag = 0; */
-	    if (jn->stat & STAT_SUPERJOB) {
-		Job sj = jobtab + jn->other;
-		if (sj->stat & STAT_DONE) {
-		    struct process *p;
-		    
-		    for (p = sj->procs; p; p = p->next)
-			if (WIFSIGNALED(p->status)) {
-			    killpg(jn->gleader, WTERMSIG(p->status));
-			    kill(sj->other, SIGCONT);
-			    kill(sj->other, WTERMSIG(p->status));
-			    break;
-			}
-		    if (!p) {
-			jn->stat &= ~STAT_SUPERJOB;
-			kill(sj->other, SIGCONT);
-			deletejob(sj);
-		    }
-		    curjob = jn - jobtab;
-		}
-		else if (sj->stat & STAT_STOPPED) {
-		    struct process *p;
-
-		    jn->stat |= STAT_STOPPED;
-		    for (p = jn->procs; p; p = p->next)
-			p->status = sj->procs->status;
-		    curjob = jn - jobtab;
-		    printjob(jn, !!isset(LONGLISTJOBS), 1);
-		    break;
-		}
+	    if (subsh) {
+		killjb(jn, SIGCONT);
+		jn->stat &= ~STAT_STOPPED;
 	    }
+	    if (jn->stat & STAT_SUPERJOB)
+		if (handle_sub(jn - jobtab, 1))
+		    break;
 	    child_block();
 	}
-    } else
+    } else {
 	deletejob(jn);
+	pipestats[0] = lastval;
+	numpipestats = 1;
+    }
     child_unblock();
 }
 
@@ -772,24 +930,29 @@ waitjob(int job, int sig)
 void
 waitjobs(void)
 {
-    waitjob(thisjob, 0);
+    Job jn = jobtab + thisjob;
+
+    if (jn->procs)
+	waitjob(thisjob, 0);
+    else {
+	deletejob(jn);
+	pipestats[0] = lastval;
+	numpipestats = 1;
+    }
     thisjob = -1;
 }
 
 /* clear job table when entering subshells */
 
 /**/
-void
+mod_export void
 clearjobtab(void)
 {
     int i;
 
-    for (i = 1; i < MAXJOB; i++) {
-	if (jobtab[i].pwd)
-	    zsfree(jobtab[i].pwd);
+    for (i = 1; i < MAXJOB; i++)
 	if (jobtab[i].ty)
 	    zfree(jobtab[i].ty, sizeof(struct ttyinfo));
-    }
 
     memset(jobtab, 0, sizeof(jobtab)); /* zero out table */
 }
@@ -805,7 +968,8 @@ initjob(void)
     for (i = 1; i < MAXJOB; i++)
 	if (!jobtab[i].stat) {
 	    jobtab[i].stat = STAT_INUSE;
-	    jobtab[i].pwd = ztrdup(pwd);
+	    if (jobtab[i].pwd)
+		zsfree(jobtab[i].pwd);
 	    jobtab[i].gleader = 0;
 	    return i;
 	}
@@ -814,6 +978,21 @@ initjob(void)
     return -1;
 }
 
+/**/
+void
+setjobpwd(void)
+{
+    int i, l;
+
+    for (i = 1; i < MAXJOB; i++)
+	if (jobtab[i].stat && !jobtab[i].pwd) {
+	    if ((l = strlen(pwd)) >= PATH_MAX)
+		jobtab[i].pwd = ztrdup(pwd + l - PATH_MAX);
+	    else
+		jobtab[i].pwd = ztrdup(pwd);
+	}
+}
+
 /* print pids for & */
 
 /**/
@@ -1071,7 +1250,7 @@ bin_fg(char *name, char **argv, char *ops, int func)
         /* If you immediately type "exit" after "jobs", this      *
          * will prevent zexit from complaining about stopped jobs */
 	stopmsg = 2;
-    if (!*argv)
+    if (!*argv) {
 	/* This block handles all of the default cases (no arguments).  bg,
 	fg and disown act on the current job, and jobs and wait act on all the
 	jobs. */
@@ -1100,6 +1279,7 @@ bin_fg(char *name, char **argv, char *ops, int func)
 		    waitjob(job, SIGINT);
 	    return 0;
 	}
+    }
 
     /* Defaults have been handled.  We now have an argument or two, or three...
     In the default case for bg, fg and disown, the argument will be provided by
@@ -1157,7 +1337,7 @@ bin_fg(char *name, char **argv, char *ops, int func)
 		/* for bg and fg -- show the job we are operating on */
 		printjob(jobtab + job, (stopped) ? -1 : 0, 1);
 	    if (func != BIN_BG) {		/* fg or wait */
-		if (strcmp(jobtab[job].pwd, pwd)) {
+		if (jobtab[job].pwd && strcmp(jobtab[job].pwd, pwd)) {
 		    fprintf(shout, "(pwd : ");
 		    fprintdir(jobtab[job].pwd, shout);
 		    fprintf(shout, ")\n");
@@ -1165,7 +1345,14 @@ bin_fg(char *name, char **argv, char *ops, int func)
 		fflush(shout);
 		if (func != BIN_WAIT) {		/* fg */
 		    thisjob = job;
-		    attachtty(jobtab[job].gleader);
+		    if ((jobtab[job].stat & STAT_SUPERJOB) &&
+			((!jobtab[job].procs->next ||
+			  (jobtab[job].stat & STAT_SUBLEADER) ||
+			  killpg(jobtab[job].gleader, 0) == -1)) &&
+			jobtab[jobtab[job].other].gleader)
+			attachtty(jobtab[jobtab[job].other].gleader);
+		    else
+			attachtty(jobtab[job].gleader);
 		}
 	    }
 	    if (stopped) {
diff --git a/Src/loop.c b/Src/loop.c
index 5fbf2b841..d0280207a 100644
--- a/Src/loop.c
+++ b/Src/loop.c
@@ -43,47 +43,79 @@ int contflag;
 /* # of break levels */
  
 /**/
-int breaks;
- 
+mod_export int breaks;
+
 /**/
 int
-execfor(Cmd cmd)
+execfor(Estate state, int do_exec)
 {
-    List list;
-    Forcmd node;
-    char *str;
-    int val;
-    LinkList args;
+    Wordcode end, loop;
+    wordcode code = state->pc[-1];
+    int iscond = (WC_FOR_TYPE(code) == WC_FOR_COND), ctok = 0, atok = 0;
+    char *name, *str, *cond = NULL, *advance = NULL;
+    zlong val = 0;
+    LinkList args = NULL;
+
+    name = ecgetstr(state, EC_NODUP, NULL);
+    end = state->pc + WC_FOR_SKIP(code);
 
-    node = cmd->u.forcmd;
-    args = cmd->args;
-    if (node->condition) {
-	str = node->name;
+    if (iscond) {
+	str = dupstring(name);
 	singsub(&str);
+	if (isset(XTRACE)) {
+	    char *str2 = dupstring(str);
+	    untokenize(str2);
+	    printprompt4();
+	    fprintf(xtrerr, "%s\n", str2);
+	    fflush(xtrerr);
+	}
 	if (!errflag)
 	    matheval(str);
-	if (errflag)
+	if (errflag) {
+	    state->pc = end;
 	    return lastval = errflag;
-    } else if (!node->inflag) {
+	}
+	cond = ecgetstr(state, EC_NODUP, &ctok);
+	advance = ecgetstr(state, EC_NODUP, &atok);
+    } else if (WC_FOR_TYPE(code) == WC_FOR_LIST) {
+	int htok = 0;
+
+	if (!(args = ecgetlist(state, *state->pc++, EC_DUPTOK, &htok))) {
+	    state->pc = end;
+	    return 0;
+	}
+	if (htok)
+	    execsubst(args);
+    } else {
 	char **x;
 
 	args = newlinklist();
 	for (x = pparams; *x; x++)
-	    addlinknode(args, ztrdup(*x));
+	    addlinknode(args, dupstring(*x));
     }
     lastval = 0;
     loops++;
     pushheap();
+    cmdpush(CS_FOR);
+    loop = state->pc;
     for (;;) {
-	if (node->condition) {
-	    str = dupstring(node->condition);
-	    singsub(&str);
+	if (iscond) {
+	    if (ctok) {
+		str = dupstring(cond);
+		singsub(&str);
+	    } else
+		str = cond;
 	    if (!errflag) {
 		while (iblank(*str))
 		    str++;
-		if (*str)
-		    val = matheval(str);
-		else
+		if (*str) {
+		    if (isset(XTRACE)) {
+			printprompt4();
+			fprintf(xtrerr, "%s\n", str);
+			fflush(xtrerr);
+		    }
+		    val = mathevali(str);
+		} else
 		    val = 1;
 	    }
 	    if (errflag) {
@@ -95,22 +127,36 @@ execfor(Cmd cmd)
 	    if (!val)
 		break;
 	} else {
-	    str = (char *) ugetnode(args);
-	    if (!str)
+	    if (!args || !(str = (char *) ugetnode(args)))
 		break;
-	    setsparam(node->name, ztrdup(str));
+	    if (isset(XTRACE)) {
+		printprompt4();
+		fprintf(xtrerr, "%s=%s\n", name, str);
+		fflush(xtrerr);
+	    }
+	    setsparam(name, ztrdup(str));
 	}
-	list = (List) dupstruct(node->list);
-	execlist(list, 1, (cmd->flags & CFLAG_EXEC) && empty(args));
+	state->pc = loop;
+	execlist(state, 1, do_exec && args && empty(args));
 	if (breaks) {
 	    breaks--;
 	    if (breaks || !contflag)
 		break;
 	    contflag = 0;
 	}
-	if (node->condition && !errflag) {
-	    str = dupstring(node->advance);
-	    singsub(&str);
+	if (retflag)
+	    break;
+	if (iscond && !errflag) {
+	    if (atok) {
+		str = dupstring(advance);
+		singsub(&str);
+	    } else
+		str = advance;
+	    if (isset(XTRACE)) {
+		printprompt4();
+		fprintf(xtrerr, "%s\n", str);
+		fflush(xtrerr);
+	    }
 	    if (!errflag)
 		matheval(str);
 	}
@@ -123,44 +169,67 @@ execfor(Cmd cmd)
 	freeheap();
     }
     popheap();
+    cmdpop();
     loops--;
+    state->pc = end;
     return lastval;
 }
 
 /**/
 int
-execselect(Cmd cmd)
+execselect(Estate state, int do_exec)
 {
-    List list;
-    Forcmd node;
-    char *str, *s;
-    LinkList args;
+    Wordcode end, loop;
+    wordcode code = state->pc[-1];
+    char *str, *s, *name;
     LinkNode n;
-    int i;
+    int i, usezle;
     FILE *inp;
+    size_t more;
+    LinkList args;
+
+    end = state->pc + WC_FOR_SKIP(code);
+    name = ecgetstr(state, EC_NODUP, NULL);
 
-    node = cmd->u.forcmd;
-    args = cmd->args;
-    if (!node->inflag) {
+    if (WC_SELECT_TYPE(code) == WC_SELECT_PPARAM) {
 	char **x;
 
 	args = newlinklist();
 	for (x = pparams; *x; x++)
-	    addlinknode(args, ztrdup(*x));
+	    addlinknode(args, dupstring(*x));
+    } else {
+	int htok = 0;
+
+	if (!(args = ecgetlist(state, *state->pc++, EC_DUPTOK, &htok))) {
+	    state->pc = end;
+	    return 0;
+	}
+	if (htok)
+	    execsubst(args);
     }
-    if (empty(args))
+    if (!args || empty(args)) {
+	state->pc = end;
 	return 1;
+    }
     loops++;
     lastval = 0;
     pushheap();
-    inp = fdopen(dup((SHTTY == -1) ? 0 : SHTTY), "r");
-    selectlist(args);
+    cmdpush(CS_SELECT);
+    usezle = interact && SHTTY != -1 && isset(USEZLE);
+    inp = fdopen(dup(usezle ? SHTTY : 0), "r");
+    more = selectlist(args, 0);
+    loop = state->pc;
     for (;;) {
 	for (;;) {
 	    if (empty(bufstack)) {
-	    	if (interact && SHTTY != -1 && isset(USEZLE)) {
+	    	if (usezle) {
+		    int oef = errflag;
+
 		    isfirstln = 1;
 		    str = (char *)zleread(prompt3, NULL, 0);
+		    if (errflag)
+			str = NULL;
+		    errflag = oef;
 	    	} else {
 		    str = promptexpand(prompt3, 0, NULL, NULL);
 		    zputs(str, stderr);
@@ -181,7 +250,7 @@ execselect(Cmd cmd)
 		*s = '\0';
 	    if (*str)
 	      break;
-	    selectlist(args);
+	    more = selectlist(args, more);
 	}
 	setsparam("REPLY", ztrdup(str));
 	i = atoi(str);
@@ -194,9 +263,9 @@ execselect(Cmd cmd)
 	    else
 		str = "";
 	}
-	setsparam(node->name, ztrdup(str));
-	list = (List) dupstruct(node->list);
-	execlist(list, 1, 0);
+	setsparam(name, ztrdup(str));
+	state->pc = loop;
+	execlist(state, 1, 0);
 	freeheap();
 	if (breaks) {
 	    breaks--;
@@ -204,21 +273,23 @@ execselect(Cmd cmd)
 		break;
 	    contflag = 0;
 	}
-	if (errflag)
+	if (retflag || errflag)
 	    break;
     }
   done:
+    cmdpop();
     popheap();
     fclose(inp);
     loops--;
+    state->pc = end;
     return lastval;
 }
 
 /* And this is used to print select lists. */
 
 /**/
-static void
-selectlist(LinkList l)
+size_t
+selectlist(LinkList l, size_t start)
 {
     size_t longest = 1, fct, fw = 0, colsz, t0, t1, ct;
     LinkNode n;
@@ -226,7 +297,7 @@ selectlist(LinkList l)
 
     trashzle();
     ct = countlinknodes(l);
-    ap = arr = (char **)alloc((countlinknodes(l) + 1) * sizeof(char **));
+    ap = arr = (char **) zhalloc((countlinknodes(l) + 1) * sizeof(char **));
 
     for (n = (LinkNode) firstnode(l); n; incnode(n))
 	*ap++ = (char *)getdata(n);
@@ -245,7 +316,7 @@ selectlist(LinkList l)
     else
 	fw = (columns - 1) / fct;
     colsz = (ct + fct - 1) / fct;
-    for (t1 = 0; t1 != colsz; t1++) {
+    for (t1 = start; t1 != colsz && t1 - start < lines - 2; t1++) {
 	ap = arr + t1;
 	do {
 	    int t2 = strlen(*ap) + 2, t3;
@@ -271,70 +342,86 @@ selectlist(LinkList l)
        }
        while (*ap);*/
     fflush(stderr);
+
+    return t1 < colsz ? t1 : 0;
 }
 
 /**/
 int
-execwhile(Cmd cmd)
+execwhile(Estate state, int do_exec)
 {
-    List list;
-    struct whilecmd *node;
-    int olderrexit, oldval;
+    Wordcode end, loop;
+    wordcode code = state->pc[-1];
+    int olderrexit, oldval, isuntil = (WC_WHILE_TYPE(code) == WC_WHILE_UNTIL);
 
+    end = state->pc + WC_WHILE_SKIP(code);
     olderrexit = noerrexit;
-    node = cmd->u.whilecmd;
     oldval = 0;
     pushheap();
+    cmdpush(isuntil ? CS_UNTIL : CS_WHILE);
     loops++;
+    loop = state->pc;
     for (;;) {
-	list = (List) dupstruct(node->cont);
+	state->pc = loop;
 	noerrexit = 1;
-	execlist(list, 1, 0);
+	execlist(state, 1, 0);
 	noerrexit = olderrexit;
-	if (!((lastval == 0) ^ node->cond)) {
+	if (!((lastval == 0) ^ isuntil)) {
 	    if (breaks)
 		breaks--;
 	    lastval = oldval;
 	    break;
 	}
-	list = (List) dupstruct(node->loop);
-	execlist(list, 1, 0);
+	if (retflag) {
+	    lastval = oldval;
+	    break;
+	}
+	execlist(state, 1, 0);
 	if (breaks) {
 	    breaks--;
 	    if (breaks || !contflag)
 		break;
 	    contflag = 0;
 	}
-	freeheap();
 	if (errflag) {
 	    lastval = 1;
 	    break;
 	}
+	if (retflag)
+	    break;
+	freeheap();
 	oldval = lastval;
     }
+    cmdpop();
     popheap();
     loops--;
+    state->pc = end;
     return lastval;
 }
 
 /**/
 int
-execrepeat(Cmd cmd)
+execrepeat(Estate state, int do_exec)
 {
-    List list;
-    int count;
+    Wordcode end, loop;
+    wordcode code = state->pc[-1];
+    int count, htok = 0;
+    char *tmp;
+
+    end = state->pc + WC_REPEAT_SKIP(code);
 
     lastval = 0;
-    if (empty(cmd->args) || nextnode(firstnode(cmd->args))) {
-	zerr("bad argument for repeat", NULL, 0);
-	return 1;
-    }
-    count = atoi(peekfirst(cmd->args));
+    tmp = ecgetstr(state, EC_DUPTOK, &htok);
+    if (htok)
+	singsub(&tmp);
+    count = atoi(tmp);
     pushheap();
+    cmdpush(CS_REPEAT);
     loops++;
-    while (count--) {
-	list = (List) dupstruct(cmd->u.list);
-	execlist(list, 1, 0);
+    loop = state->pc;
+    while (count-- > 0) {
+	state->pc = loop;
+	execlist(state, 1, 0);
 	freeheap();
 	if (breaks) {
 	    breaks--;
@@ -346,76 +433,151 @@ execrepeat(Cmd cmd)
 	    lastval = 1;
 	    break;
 	}
+	if (retflag)
+	    break;
     }
+    cmdpop();
     popheap();
     loops--;
+    state->pc = end;
     return lastval;
 }
 
 /**/
 int
-execif(Cmd cmd)
+execif(Estate state, int do_exec)
 {
-    struct ifcmd *node;
-    int olderrexit;
-    List *i, *t;
+    Wordcode end, next;
+    wordcode code = state->pc[-1];
+    int olderrexit, s = 0, run = 0;
 
     olderrexit = noerrexit;
-    node = cmd->u.ifcmd;
-    i = node->ifls;
-    t = node->thenls;
+    end = state->pc + WC_IF_SKIP(code);
 
     if (!noerrexit)
 	noerrexit = 1;
-    while (*i) {
-	execlist(*i, 1, 0);
-	if (!lastval)
+    while (state->pc < end) {
+	code = *state->pc++;
+	if (wc_code(code) != WC_IF ||
+	    (run = (WC_IF_TYPE(code) == WC_IF_ELSE))) {
+	    if (run)
+		run = 2;
 	    break;
-	i++;
-	t++;
+	}
+	next = state->pc + WC_IF_SKIP(code);
+	cmdpush(s ? CS_ELIF : CS_IF);
+	execlist(state, 1, 0);
+	cmdpop();
+	if (!lastval) {
+	    run = 1;
+	    break;
+	}
+	if (retflag)
+	    break;
+	s = 1;
+	state->pc = next;
     }
     noerrexit = olderrexit;
 
-    if (*t)
-	execlist(*t, 1, cmd->flags & CFLAG_EXEC);
-    else
+    if (run) {
+	cmdpush(run == 2 ? CS_ELSE : (s ? CS_ELIFTHEN : CS_IFTHEN));
+	execlist(state, 1, do_exec);
+	cmdpop();
+    } else
 	lastval = 0;
+    state->pc = end;
 
     return lastval;
 }
 
 /**/
 int
-execcase(Cmd cmd)
+execcase(Estate state, int do_exec)
 {
-    struct casecmd *node;
-    char *word;
-    List *l;
-    char **p;
+    Wordcode end, next;
+    wordcode code = state->pc[-1];
+    char *word, *pat;
+    int npat, save;
+    Patprog *spprog, pprog;
 
-    node = cmd->u.casecmd;
-    l = node->lists;
-    p = node->pats;
+    end = state->pc + WC_CASE_SKIP(code);
 
-    word = *p++;
+    word = ecgetstr(state, EC_DUP, NULL);
     singsub(&word);
     untokenize(word);
     lastval = 0;
 
-    if (node) {
-	while (*p) {
-	    char *pat = *p + 1;
+    cmdpush(CS_CASE);
+    while (state->pc < end) {
+	code = *state->pc++;
+	if (wc_code(code) != WC_CASE)
+	    break;
+
+	pat = NULL;
+	pprog = NULL;
+	save = 0;
+	npat = state->pc[1];
+	spprog = state->prog->pats + npat;
+
+	next = state->pc + WC_CASE_SKIP(code);
+
+	if (isset(XTRACE)) {
+	    char *pat2, *opat;
+
+	    opat = pat = ecgetstr(state, EC_DUP, NULL);
 	    singsub(&pat);
-	    if (matchpat(word, pat)) {
-		do {
-		    execlist(*l++, 1, **p == ';' && (cmd->flags & CFLAG_EXEC));
-		} while(**p++ == '&' && *p);
-		break;
+	    save = (!(state->prog->flags & EF_HEAP) &&
+		    !strcmp(pat, opat) && *spprog != dummy_patprog2);
+
+	    pat2 = dupstring(pat);
+	    untokenize(pat2);
+	    printprompt4();
+	    fprintf(xtrerr, "case %s (%s)\n", word, pat2);
+	    fflush(xtrerr);
+	    state->pc++;
+	} else
+	    state->pc += 2;
+
+	if (*spprog != dummy_patprog1 && *spprog != dummy_patprog2)
+	    pprog = *spprog;
+
+	if (!pprog) {
+	    if (!pat) {
+		char *opat;
+		int htok = 0;
+
+		opat = pat = dupstring(ecrawstr(state->prog,
+						state->pc - 2, &htok));
+		if (htok)
+		    singsub(&pat);
+		save = (!(state->prog->flags & EF_HEAP) &&
+			!strcmp(pat, opat) && *spprog != dummy_patprog2);
 	    }
-	    p++;
-	    l++;
+	    if (!(pprog = patcompile(pat, (save ? PAT_ZDUP : PAT_STATIC),
+				     NULL)))
+		zerr("bad pattern: %s", pat, 0);
+	    else if (save)
+		*spprog = pprog;
 	}
+	if (pprog && pattry(pprog, word)) {
+	    execlist(state, 1, ((WC_CASE_TYPE(code) == WC_CASE_OR) &&
+				do_exec));
+	    while (!retflag && wc_code(code) == WC_CASE &&
+		   WC_CASE_TYPE(code) == WC_CASE_AND) {
+		state->pc = next;
+		code = *state->pc;
+		state->pc += 3;
+		next = state->pc + WC_CASE_SKIP(code) - 1;
+		execlist(state, 1, ((WC_CASE_TYPE(code) == WC_CASE_OR) &&
+				    do_exec));
+	    }
+	    break;
+	} else
+	    state->pc = next;
     }
+    cmdpop();
+
+    state->pc = end;
+
     return lastval;
 }
-
diff --git a/Src/params.c b/Src/params.c
index 4f7846820..a7444bfd1 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -35,59 +35,67 @@
 /* what level of localness we are at */
  
 /**/
-int locallevel;
+mod_export int locallevel;
  
 /* Variables holding values of special parameters */
  
 /**/
+mod_export
 char **pparams,		/* $argv        */
      **cdpath,		/* $cdpath      */
-     **fignore,		/* $fignore     */
      **fpath,		/* $fpath       */
      **mailpath,	/* $mailpath    */
      **manpath,		/* $manpath     */
-     **path,		/* $path        */
      **psvar,		/* $psvar       */
      **watch;		/* $watch       */
+/**/
+mod_export
+char **path,		/* $path        */
+     **fignore;		/* $fignore     */
  
 /**/
 char *argzero,		/* $0           */
-     *underscore,	/* $_           */
      *home,		/* $HOME        */
      *hostnam,		/* $HOST        */
-     *ifs,		/* $IFS         */
      *nullcmd,		/* $NULLCMD     */
      *oldpwd,		/* $OLDPWD      */
      *zoptarg,		/* $OPTARG      */
-     *postedit,		/* $POSTEDIT    */
      *prompt,		/* $PROMPT      */
      *prompt2,		/* $PROMPT2     */
      *prompt3,		/* $PROMPT3     */
      *prompt4,		/* $PROMPT4     */
-     *pwd,		/* $PWD         */
      *readnullcmd,	/* $READNULLCMD */
      *rprompt,		/* $RPROMPT     */
      *sprompt,		/* $SPROMPT     */
      *term,		/* $TERM        */
-     *ttystrname,	/* $TTY         */
      *wordchars,	/* $WORDCHARS   */
      *zsh_name;		/* $ZSH_NAME    */
+/**/
+mod_export
+char *ifs,		/* $IFS         */
+     *postedit,		/* $POSTEDIT    */
+     *ttystrname,	/* $TTY         */
+     *pwd;		/* $PWD         */
 
 /**/
-long lastval,		/* $?           */
+mod_export
+zlong lastval,		/* $?           */
      mypid,		/* $$           */
      lastpid,		/* $!           */
      columns,		/* $COLUMNS     */
-     lineno,		/* $LINENO      */
      lines,		/* $LINES       */
+     ppid;		/* $PPID        */
+/**/
+zlong lineno,		/* $LINENO      */
      zoptind,		/* $OPTIND      */
-     ppid,		/* $PPID        */
      shlvl;		/* $SHLVL       */
 
 /* $histchars */
  
 /**/
-unsigned char bangchar, hatchar, hashchar;
+mod_export unsigned char bangchar;
+/**/
+unsigned char hatchar, hashchar;
  
 /* $SECONDS = time(NULL) - shtimer.tv_sec */
  
@@ -97,7 +105,7 @@ struct timeval shtimer;
 /* 0 if this $TERM setup is usable, otherwise it contains TERM_* flags */
 
 /**/
-int termflags;
+mod_export int termflags;
  
 /* Nodes for special parameters for parameter hash table */
 
@@ -147,7 +155,7 @@ IPDEF2("WORDCHARS", wordcharsgetfn, wordcharssetfn, 0),
 IPDEF2("IFS", ifsgetfn, ifssetfn, PM_DONTIMPORT),
 IPDEF2("_", underscoregetfn, nullsetfn, PM_READONLY),
 
-#ifdef LC_ALL
+#ifdef USE_LOCALE
 # define LCIPDEF(name) IPDEF2(name, strgetfn, lcsetfn, PM_UNSET)
 IPDEF2("LANG", strgetfn, langsetfn, PM_UNSET),
 IPDEF2("LC_ALL", strgetfn, lc_allsetfn, PM_UNSET),
@@ -160,10 +168,13 @@ LCIPDEF("LC_CTYPE"),
 # ifdef LC_MESSAGES
 LCIPDEF("LC_MESSAGES"),
 # endif
+# ifdef LC_NUMERIC
+LCIPDEF("LC_NUMERIC"),
+# endif
 # ifdef LC_TIME
 LCIPDEF("LC_TIME"),
 # endif
-#endif
+#endif /* USE_LOCALE */
 
 #define IPDEF4(A,B) {NULL,A,PM_INTEGER|PM_READONLY|PM_SPECIAL,BR((void *)B),SFN(nullsetfn),GFN(intvargetfn),stdunsetfn,10,NULL,NULL,NULL,0}
 IPDEF4("!", &lastpid),
@@ -201,16 +212,15 @@ IPDEF8("WATCH", &watch, "watch", 0),
 IPDEF8("PATH", &path, "path", PM_RESTRICTED),
 IPDEF8("PSVAR", &psvar, "psvar", 0),
 
-#ifdef DYNAMIC
 /* MODULE_PATH is not imported for security reasons */
 IPDEF8("MODULE_PATH", &module_path, "module_path", PM_DONTIMPORT|PM_RESTRICTED),
-#endif
 
 #define IPDEF9F(A,B,C,D) {NULL,A,D|PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT,BR((void *)B),SFN(arrvarsetfn),GFN(arrvargetfn),stdunsetfn,0,NULL,C,NULL,0}
 #define IPDEF9(A,B,C) IPDEF9F(A,B,C,0)
 IPDEF9("*", &pparams, NULL),
 IPDEF9("@", &pparams, NULL),
 {NULL, NULL},
+#define IPDEF10(A,B,C) {NULL,A,PM_ARRAY|PM_SPECIAL,BR(NULL),SFN(C),GFN(B),stdunsetfn,10,NULL,NULL,NULL,0}
 
 /* The following parameters are not avaible in sh/ksh compatibility *
  * mode. All of these has sh compatible equivalents.                */
@@ -232,22 +242,208 @@ IPDEF9("manpath", &manpath, "MANPATH"),
 IPDEF9("psvar", &psvar, "PSVAR"),
 IPDEF9("watch", &watch, "WATCH"),
 
-#ifdef DYNAMIC
 IPDEF9F("module_path", &module_path, "MODULE_PATH", PM_RESTRICTED),
-#endif
 IPDEF9F("path", &path, "PATH", PM_RESTRICTED),
 
+IPDEF10("pipestatus", pipestatgetfn, pipestatsetfn),
+
 {NULL, NULL}
 };
 #undef BR
 
+#define IS_UNSET_VALUE(V) \
+	((V) && (!(V)->pm || ((V)->pm->flags & PM_UNSET) || \
+		 !(V)->pm->nam || !*(V)->pm->nam))
+
 static Param argvparam;
 
 /* hash table containing the parameters */
  
 /**/
-HashTable paramtab;
- 
+mod_export HashTable paramtab, realparamtab;
+
+/**/
+mod_export HashTable
+newparamtable(int size, char const *name)
+{
+    HashTable ht = newhashtable(size, name, NULL);
+
+    ht->hash        = hasher;
+    ht->emptytable  = emptyhashtable;
+    ht->filltable   = NULL;
+    ht->cmpnodes    = strcmp;
+    ht->addnode     = addhashnode;
+    ht->getnode     = getparamnode;
+    ht->getnode2    = getparamnode;
+    ht->removenode  = removehashnode;
+    ht->disablenode = NULL;
+    ht->enablenode  = NULL;
+    ht->freenode    = freeparamnode;
+    ht->printnode   = printparamnode;
+
+    return ht;
+}
+
+/**/
+static HashNode
+getparamnode(HashTable ht, char *nam)
+{
+    HashNode hn = gethashnode2(ht, nam);
+    Param pm = (Param) hn;
+
+    if (pm && pm->u.str && (pm->flags & PM_AUTOLOAD)) {
+	char *mn = dupstring(pm->u.str);
+
+	if (!load_module(mn))
+	    return NULL;
+	hn = gethashnode2(ht, nam);
+	if (((Param) hn) == pm && (pm->flags & PM_AUTOLOAD)) {
+	    pm->flags &= ~PM_AUTOLOAD;
+	    zwarnnam(nam, "autoload failed", NULL, 0);
+	}
+    }
+    return hn;
+}
+
+/* Copy a parameter hash table */
+
+static HashTable outtable;
+
+/**/
+static void
+scancopyparams(HashNode hn, 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;
+}
+
+/* Flag to freeparamnode to unset the struct */
+
+static int delunset;
+
+/* Function to delete a parameter table. */
+
+/**/
+mod_export void
+deleteparamtable(HashTable t)
+{
+    /* The parameters in the hash table need to be unset *
+     * before being deleted.                             */
+    int odelunset = delunset;
+    delunset = 1;
+    deletehashtable(t);
+    delunset = odelunset;
+}
+
+static unsigned numparamvals;
+
+/**/
+mod_export void
+scancountparams(HashNode hn, int flags)
+{
+    ++numparamvals;
+    if ((flags & SCANPM_WANTKEYS) && (flags & SCANPM_WANTVALS))
+	++numparamvals;
+}
+
+static Patprog scanprog;
+static char *scanstr;
+static char **paramvals;
+
+/**/
+void
+scanparamvals(HashNode hn, int flags)
+{
+    struct value v;
+    Patprog prog;
+
+    if (numparamvals && !(flags & SCANPM_MATCHMANY) &&
+	(flags & (SCANPM_MATCHVAL|SCANPM_MATCHKEY|SCANPM_KEYMATCH)))
+	return;
+    v.pm = (Param)hn;
+    if ((flags & SCANPM_KEYMATCH)) {
+	char *tmp = dupstring(v.pm->nam);
+
+	tokenize(tmp);
+	remnulargs(tmp);
+
+	if (!(prog = patcompile(tmp, 0, NULL)) || !pattry(prog, scanstr))
+	    return;
+    } else if ((flags & SCANPM_MATCHKEY) && !pattry(scanprog, v.pm->nam)) {
+	return;
+    }
+    if (flags & SCANPM_WANTKEYS) {
+	paramvals[numparamvals++] = v.pm->nam;
+	if (!(flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL)))
+	    return;
+    }
+    v.isarr = (PM_TYPE(v.pm->flags) & (PM_ARRAY|PM_HASHED));
+    v.inv = 0;
+    v.a = 0;
+    v.b = -1;
+    paramvals[numparamvals] = getstrvalue(&v);
+    if (flags & SCANPM_MATCHVAL) {
+	if (pattry(scanprog, paramvals[numparamvals])) {
+	    numparamvals += ((flags & SCANPM_WANTVALS) ? 1 :
+			     !(flags & SCANPM_WANTKEYS));
+	} else if (flags & SCANPM_WANTKEYS)
+	    --numparamvals;	/* Value didn't match, discard key */
+    } else
+	++numparamvals;
+}
+
+/**/
+char **
+paramvalarr(HashTable ht, int flags)
+{
+    numparamvals = 0;
+    if (ht)
+	scanhashtable(ht, 0, 0, PM_UNSET, scancountparams, flags);
+    paramvals = (char **) zhalloc((numparamvals + 1) * sizeof(char *));
+    if (ht) {
+	numparamvals = 0;
+	scanhashtable(ht, 0, 0, PM_UNSET, scanparamvals, flags);
+    }
+    paramvals[numparamvals] = 0;
+    return paramvals;
+}
+
+/* Return the full array (no indexing) referred to by a Value. *
+ * The array value is cached for the lifetime of the Value.    */
+
+/**/
+static char **
+getvaluearr(Value v)
+{
+    if (v->arr)
+	return v->arr;
+    else if (PM_TYPE(v->pm->flags) == PM_ARRAY)
+	return v->arr = v->pm->gets.afn(v->pm);
+    else if (PM_TYPE(v->pm->flags) == PM_HASHED) {
+	v->arr = paramvalarr(v->pm->gets.hfn(v->pm), v->isarr);
+	/* Can't take numeric slices of associative arrays */
+	v->a = 0;
+	v->b = numparamvals;
+	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.           */
@@ -259,21 +455,13 @@ createparamtable(void)
     Param ip, pm;
     char **new_environ, **envp, **envp2, **sigptr, **t;
     char buf[50], *str, *iname;
-    int num_env;
-
-    paramtab = newhashtable(151, "paramtab", NULL);
+    int num_env, oae = opts[ALLEXPORT];
+#ifdef HAVE_UNAME
+    struct utsname unamebuf;
+    char *machinebuf;
+#endif
 
-    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 = realparamtab = newparamtable(151, "paramtab");
 
     /* Add the special parameters to the hash table */
     for (ip = special_params; ip->nam; ip++)
@@ -284,90 +472,137 @@ createparamtable(void)
 
     argvparam = (Param) paramtab->getnode(paramtab, "*");
 
-    noerrs = 1;
-
-    HEAPALLOC {
-	/* Add the standard non-special parameters which have to    *
-	 * be initialized before we copy the environment variables. *
-	 * We don't want to override whatever values the users has  *
-	 * given them in the environment.                           */
-	setiparam("MAILCHECK", 60);
-	setiparam("LOGCHECK", 60);
-	setiparam("KEYTIMEOUT", 40);
-	setiparam("LISTMAX", 100);
+    noerrs = 2;
+
+    /* Add the standard non-special parameters which have to    *
+     * be initialized before we copy the environment variables. *
+     * We don't want to override whatever values the users has  *
+     * given them in the environment.                           */
+    opts[ALLEXPORT] = 0;
+    setiparam("MAILCHECK", 60);
+    setiparam("LOGCHECK", 60);
+    setiparam("KEYTIMEOUT", 40);
+    setiparam("LISTMAX", 100);
 #ifdef HAVE_SELECT
-	setiparam("BAUD", getbaudrate(&shttyinfo));  /* get the output baudrate */
+    setiparam("BAUD", getbaudrate(&shttyinfo));  /* get the output baudrate */
 #endif
-	setsparam("FCEDIT", ztrdup(DEFAULT_FCEDIT));
-	setsparam("TMPPREFIX", ztrdup(DEFAULT_TMPPREFIX));
-	setsparam("TIMEFMT", ztrdup(DEFAULT_TIMEFMT));
-	setsparam("WATCHFMT", ztrdup(default_watchfmt));
-	setsparam("HOST", ztrdup(hostnam));
-	setsparam("LOGNAME", ztrdup((str = getlogin()) && *str ? str : cached_username));
-
-	/* Copy the environment variables we are inheriting to dynamic *
-	 * memory, so we can do mallocs and frees on it.               */
-	num_env = arrlen(environ);
-	new_environ = (char **) zalloc(sizeof(char *) * (num_env + 1));
-	*new_environ = NULL;
-
-	/* Now incorporate environment variables we are inheriting *
-	 * into the parameter hash table.                          */
-	for (envp = new_environ, envp2 = environ; *envp2; envp2++) {
-	    for (str = *envp2; *str && *str != '='; str++);
-	    if (*str == '=') {
-		iname = NULL;
-		*str = '\0';
-		if (!idigit(**envp2) && isident(*envp2) && !strchr(*envp2, '[')) {
-		    iname = *envp2;
-		    if ((!(pm = (Param) paramtab->getnode(paramtab, iname)) ||
-			 !(pm->flags & PM_DONTIMPORT)) &&
-			(pm = setsparam(iname, metafy(str + 1, -1, META_DUP))) &&
-			!(pm->flags & PM_EXPORTED)) {
-			*str = '=';
-			pm->flags |= PM_EXPORTED;
-			pm->env = *envp++ = ztrdup(*envp2);
-			*envp = NULL;
-			if (pm->flags & PM_SPECIAL)
-			    pm->env = replenv(pm->env, getsparam(pm->nam));
-		    }
+    setsparam("FCEDIT", ztrdup(DEFAULT_FCEDIT));
+    setsparam("TMPPREFIX", ztrdup(DEFAULT_TMPPREFIX));
+    setsparam("TIMEFMT", ztrdup(DEFAULT_TIMEFMT));
+    setsparam("WATCHFMT", ztrdup(default_watchfmt));
+    setsparam("HOST", ztrdup(hostnam));
+    setsparam("LOGNAME", ztrdup((str = getlogin()) && *str ? str : cached_username));
+
+    /* Copy the environment variables we are inheriting to dynamic *
+     * memory, so we can do mallocs and frees on it.               */
+    num_env = arrlen(environ);
+    new_environ = (char **) zalloc(sizeof(char *) * (num_env + 1));
+    *new_environ = NULL;
+
+    /* Now incorporate environment variables we are inheriting *
+     * into the parameter hash table.                          */
+    for (envp = new_environ, envp2 = environ; *envp2; envp2++) {
+	for (str = *envp2; *str && *str != '='; str++);
+	if (*str == '=') {
+	    iname = NULL;
+	    *str = '\0';
+	    if (!idigit(**envp2) && isident(*envp2) && !strchr(*envp2, '[')) {
+		iname = *envp2;
+		if ((!(pm = (Param) paramtab->getnode(paramtab, iname)) ||
+		     !(pm->flags & PM_DONTIMPORT)) &&
+		    (pm = setsparam(iname, metafy(str + 1, -1, META_DUP))) &&
+		    !(pm->flags & PM_EXPORTED)) {
+		    *str = '=';
+		    pm->flags |= PM_EXPORTED;
+		    pm->env = *envp++ = ztrdup(*envp2);
+		    *envp = NULL;
+		    if (pm->flags & PM_SPECIAL)
+			pm->env = replenv(pm->env, getsparam(pm->nam),
+					  pm->flags);
 		}
-		*str = '=';
 	    }
+	    *str = '=';
 	}
-	environ = new_environ;
+    }
+    environ = new_environ;
+    opts[ALLEXPORT] = oae;
 
-	pm = (Param) paramtab->getnode(paramtab, "HOME");
-	if (!(pm->flags & PM_EXPORTED)) {
-	    pm->flags |= PM_EXPORTED;
-	    pm->env = addenv("HOME", home);
-	}
-	pm = (Param) paramtab->getnode(paramtab, "LOGNAME");
-	if (!(pm->flags & PM_EXPORTED)) {
-	    pm->flags |= PM_EXPORTED;
-	    pm->env = addenv("LOGNAME", pm->u.str);
-	}
-	pm = (Param) paramtab->getnode(paramtab, "SHLVL");
-	if (!(pm->flags & PM_EXPORTED))
-	    pm->flags |= PM_EXPORTED;
-	sprintf(buf, "%d", (int)++shlvl);
-	pm->env = addenv("SHLVL", buf);
-
-	/* Add the standard non-special parameters */
-	set_pwd_env();
-	setsparam("MACHTYPE", ztrdup(MACHTYPE));
-	setsparam("OSTYPE", ztrdup(OSTYPE));
-	setsparam("TTY", ztrdup(ttystrname));
-	setsparam("VENDOR", ztrdup(VENDOR));
-	setsparam("ZSH_NAME", ztrdup(zsh_name));
-	setsparam("ZSH_VERSION", ztrdup(ZSH_VERSION));
-	setaparam("signals", sigptr = zalloc((SIGCOUNT+4) * sizeof(char *)));
-	for (t = sigs; (*sigptr++ = ztrdup(*t++)); );
-    } LASTALLOC;
+    pm = (Param) paramtab->getnode(paramtab, "HOME");
+    if (!(pm->flags & PM_EXPORTED)) {
+	pm->flags |= PM_EXPORTED;
+	pm->env = addenv("HOME", home, pm->flags);
+    }
+    pm = (Param) paramtab->getnode(paramtab, "LOGNAME");
+    if (!(pm->flags & PM_EXPORTED)) {
+	pm->flags |= PM_EXPORTED;
+	pm->env = addenv("LOGNAME", pm->u.str, pm->flags);
+    }
+    pm = (Param) paramtab->getnode(paramtab, "SHLVL");
+    if (!(pm->flags & PM_EXPORTED))
+	pm->flags |= PM_EXPORTED;
+    sprintf(buf, "%d", (int)++shlvl);
+    pm->env = addenv("SHLVL", buf, pm->flags);
+
+    /* Add the standard non-special parameters */
+    set_pwd_env();
+#ifdef HAVE_UNAME
+    if(uname(&unamebuf)) setsparam("CPUTYPE", ztrdup("unknown"));
+    else
+    {
+       machinebuf = ztrdup(unamebuf.machine);
+       setsparam("CPUTYPE", machinebuf);
+    }
+	
+#else
+    setsparam("CPUTYPE", ztrdup("unknown"));
+#endif
+    setsparam("MACHTYPE", ztrdup(MACHTYPE));
+    setsparam("OSTYPE", ztrdup(OSTYPE));
+    setsparam("TTY", ztrdup(ttystrname));
+    setsparam("VENDOR", ztrdup(VENDOR));
+    setsparam("ZSH_NAME", ztrdup(zsh_name));
+    setsparam("ZSH_VERSION", ztrdup(ZSH_VERSION));
+    setaparam("signals", sigptr = zalloc((SIGCOUNT+4) * sizeof(char *)));
+    for (t = sigs; (*sigptr++ = ztrdup(*t++)); );
 
     noerrs = 0;
 }
 
+/* assign various functions used for non-special parameters */
+
+/**/
+static void
+assigngetset(Param pm)
+{
+    switch (PM_TYPE(pm->flags)) {
+    case PM_SCALAR:
+	pm->sets.cfn = strsetfn;
+	pm->gets.cfn = strgetfn;
+	break;
+    case PM_INTEGER:
+	pm->sets.ifn = intsetfn;
+	pm->gets.ifn = intgetfn;
+	break;
+    case PM_EFLOAT:
+    case PM_FFLOAT:
+	pm->sets.ffn = floatsetfn;
+	pm->gets.ffn = floatgetfn;
+	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     *
@@ -377,15 +612,19 @@ createparamtable(void)
  * created because it already exists, the PM_UNSET flag is cleared.        */
 
 /**/
-Param
+mod_export Param
 createparam(char *name, int flags)
 {
     Param pm, oldpm;
 
     if (name != nulstring) {
-	oldpm = (Param) paramtab->getnode(paramtab, name);
+	oldpm = (Param) (paramtab == realparamtab ?
+			 gethashnode2(paramtab, name) :
+			 paramtab->getnode(paramtab, name));
 
-	if (oldpm && oldpm->level == locallevel) {
+	DPUTS(oldpm && oldpm->level > locallevel,
+	      "BUG:  old local parameter not deleteed");
+	if (oldpm && (oldpm->level == locallevel || !(flags & PM_LOCAL))) {
 	    if (!(oldpm->flags & PM_UNSET) || (oldpm->flags & PM_SPECIAL)) {
 		oldpm->flags &= ~PM_UNSET;
 		return NULL;
@@ -409,33 +648,61 @@ createparam(char *name, int flags)
 
 	if (isset(ALLEXPORT) && !oldpm)
 	    flags |= PM_EXPORTED;
-    } else
-	pm = (Param) alloc(sizeof *pm);
-    pm->flags = flags;
-
-    if(!(pm->flags & PM_SPECIAL)) {
-	switch (PM_TYPE(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;
-	default:
-	    DPUTS(1, "BUG: tried to create param node without valid flag");
-	    break;
-	}
-	pm->unsetfn = stdunsetfn;
+    } else {
+	pm = (Param) zhalloc(sizeof *pm);
+	pm->nam = nulstring;
     }
+    pm->flags = flags & ~PM_LOCAL;
+
+    if(!(pm->flags & PM_SPECIAL))
+	assigngetset(pm);
     return pm;
 }
 
+/* Copy a parameter */
+
+/**/
+void
+copyparam(Param tpm, Param pm, int toplevel)
+{
+    /*
+     * Note that tpm, into which we're copying, may not be in permanent
+     * storage.  However, the values themselves are later used directly
+     * to set the parameter, so must be permanently allocated (in accordance
+     * with sets.?fn() usage).
+     */
+    tpm->flags = pm->flags;
+    if (!toplevel)
+	tpm->flags &= ~PM_SPECIAL;
+    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_EFLOAT:
+    case PM_FFLOAT:
+	tpm->u.dval = pm->gets.ffn(pm);
+	break;
+    case PM_ARRAY:
+	tpm->u.arr = zarrdup(pm->gets.afn(pm));
+	break;
+    case PM_HASHED:
+	tpm->u.hash = copyparamtable(pm->gets.hfn(pm), pm->nam);
+	break;
+    }
+    /*
+     * If called from inside an associative array, that array is later going
+     * to be passed as a real parameter, so we need the gets and sets
+     * functions to be useful.  However, the saved associated array is
+     * not itself special, so we just use the standard ones.
+     * This is also why we switch off PM_SPECIAL.
+     */
+    if (!toplevel)
+	assigngetset(tpm);
+}
+
 /* Return 1 if the string s is a valid identifier, else return 0. */
 
 /**/
@@ -454,6 +721,7 @@ isident(char *s)
 	if (!iident(*ss))
 	    break;
 
+#if 0
     /* If this exhaust `s' or the next two characters *
      * are [(, then it is a valid identifier.         */
     if (!*ss || (*ss == '[' && ss[1] == '('))
@@ -463,6 +731,7 @@ isident(char *s)
      * definitely not a valid identifier.              */
     if (*ss != '[')
 	return 0;
+
     noeval = 1;
     (void)mathevalarg(++ss, &ss);
     if (*ss == ',')
@@ -471,39 +740,64 @@ isident(char *s)
     if (*ss != ']' || ss[1])
 	return 0;
     return 1;
-}
+#else
+    /* If the next character is not [, then it is *
+     * definitely not a valid identifier.              */
+    if (!*ss)
+	return 1;
+    if (*ss != '[')
+	return 0;
 
-static char **garr;
+    /* Require balanced [ ] pairs */
+    if (skipparens('[', ']', &ss))
+	return 0;
+    return !*ss;
+#endif
+}
 
 /**/
-static long
-getarg(char **str, int *inv, Value v, int a2, long *w)
+static zlong
+getarg(char **str, int *inv, Value v, int a2, zlong *w)
 {
-    int num = 1, word = 0, rev = 0, ind = 0, down = 0, l, i;
-    char *s = *str, *sep = NULL, *t, sav, *d, **ta, **p, *tt;
-    long r = 0;
-    Comp c;
+    int hasbeg = 0, word = 0, rev = 0, ind = 0, down = 0, l, i, ishash;
+    int keymatch = 0, needtok = 0;
+    char *s = *str, *sep = NULL, *t, sav, *d, **ta, **p, *tt, c;
+    zlong num = 1, beg = 0, r = 0;
+    Patprog pprog = NULL;
+
+    ishash = (v->pm && PM_TYPE(v->pm->flags) == PM_HASHED);
 
     /* first parse any subscription flags */
-    if (*s == '(' || *s == Inpar) {
+    if (v->pm && (*s == '(' || *s == Inpar)) {
 	int escapes = 0;
 	int waste;
 	for (s++; *s != ')' && *s != Outpar && s != *str; s++) {
 	    switch (*s) {
 	    case 'r':
 		rev = 1;
-		down = ind = 0;
+		keymatch = down = ind = 0;
 		break;
 	    case 'R':
 		rev = down = 1;
+		keymatch = ind = 0;
+		break;
+	    case 'k':
+		keymatch = ishash;
+		rev = 1;
+		down = ind = 0;
+		break;
+	    case 'K':
+		keymatch = ishash;
+		rev = down = 1;
 		ind = 0;
 		break;
 	    case 'i':
 		rev = ind = 1;
-		down = 0;
+		down = keymatch = 0;
 		break;
 	    case 'I':
 		rev = ind = down = 1;
+		keymatch = 0;
 		break;
 	    case 'w':
 		/* If the parameter is a scalar, then make subscription *
@@ -529,6 +823,18 @@ getarg(char **str, int *inv, Value v, int a2, long *w)
 		*t = sav;
 		s = t;
 		break;
+	    case 'b':
+		hasbeg = 1;
+		t = get_strarg(++s);
+		if (!*t)
+		    goto flagerr;
+		sav = *t;
+		*t = '\0';
+		if ((beg = mathevalarg(s + 1, &d)) > 0)
+		    beg--;
+		*t = sav;
+		s = t;
+		break;
 	    case 'p':
 		escapes = 1;
 		break;
@@ -548,7 +854,7 @@ getarg(char **str, int *inv, Value v, int a2, long *w)
 	    default:
 	      flagerr:
 		num = 1;
-		word = rev = ind = down = 0;
+		word = rev = ind = down = keymatch = 0;
 		sep = NULL;
 		s = *str - 1;
 	    }
@@ -560,23 +866,62 @@ getarg(char **str, int *inv, Value v, int a2, long *w)
 	down = !down;
 	num = -num;
     }
-    *inv = ind;
+    if (v->isarr & SCANPM_WANTKEYS)
+	*inv = (ind || !(v->isarr & SCANPM_WANTVALS));
+    else if (v->isarr & SCANPM_WANTVALS)
+	*inv = 0;
+    else {
+	if (v->isarr) {
+	    if (ind) {
+		v->isarr |= SCANPM_WANTKEYS;
+		v->isarr &= ~SCANPM_WANTVALS;
+	    } else if (rev)
+		v->isarr |= SCANPM_WANTVALS;
+	    if (!down && !keymatch && ishash)
+		v->isarr &= ~SCANPM_MATCHMANY;
+	}
+	*inv = ind;
+    }
 
-    for (t=s, i=0; *t && ((*t != ']' && *t != Outbrack && *t != ',') || i); t++)
-	if (*t == '[' || *t == Inbrack)
+    for (t = s, i = 0;
+	 (c = *t) && ((c != ']' && c != Outbrack &&
+		       (ishash || c != ',')) || i); t++) {
+	if (c == '[' || c == Inbrack)
 	    i++;
-	else if (*t == ']' || *t == Outbrack)
+	else if (c == ']' || c == Outbrack)
 	    i--;
-
-    if (!*t)
+	if (ispecial(c))
+	    needtok = 1;
+    }
+    if (!c)
 	return 0;
     s = dupstrpfx(s, t - s);
     *str = tt = t;
-    if (parsestr(s))
-	return 0;
-    singsub(&s);
-
+    if (needtok) {
+	if (parsestr(s))
+	    return 0;
+	singsub(&s);
+    }
     if (!rev) {
+	if (ishash) {
+	    HashTable ht = v->pm->gets.hfn(v->pm);
+	    if (!ht) {
+		ht = newparamtable(17, v->pm->nam);
+		v->pm->sets.hfn(v->pm, ht);
+	    }
+	    untokenize(s);
+	    if (!(v->pm = (Param) ht->getnode(ht, s))) {
+		HashTable tht = paramtab;
+		paramtab = ht;
+		v->pm = createparam(s, PM_SCALAR|PM_UNSET);
+		paramtab = tht;
+	    }
+	    v->isarr = (*inv ? SCANPM_WANTINDEX : 0);
+	    v->a = 0;
+	    *inv = 0;	/* We've already obtained the "index" (key) */
+	    *w = v->b = -1;
+	    r = isset(KSHARRAYS) ? 1 : 0;
+	} else
 	if (!(r = mathevalarg(s, &s)) || (isset(KSHARRAYS) && r >= 0))
 	    r++;
 	if (word && !v->isarr) {
@@ -595,7 +940,7 @@ getarg(char **str, int *inv, Value v, int a2, long *w)
 		return 0;
 
 	    if (!a2 && *tt != ',')
-		*w = (long)(s - t) - 1;
+		*w = (zlong)(s - t) - 1;
 
 	    return (a2 ? s : d + 1) - t;
 	} else if (!v->isarr && !word) {
@@ -617,14 +962,14 @@ getarg(char **str, int *inv, Value v, int a2, long *w)
 	    l = strlen(s);
 	    if (a2) {
 		if (!l || *s != '*') {
-		    d = (char *) ncalloc(l + 2);
+		    d = (char *) hcalloc(l + 2);
 		    *d = '*';
 		    strcpy(d + 1, s);
 		    s = d;
 		}
 	    } else {
 		if (!l || s[l - 1] != '*') {
-		    d = (char *) ncalloc(l + 2);
+		    d = (char *) hcalloc(l + 2);
 		    strcpy(d, s);
 		    strcat(d, "*");
 		    s = d;
@@ -633,39 +978,77 @@ getarg(char **str, int *inv, Value v, int a2, long *w)
 	}
 	tokenize(s);
 
-	if ((c = parsereg(s))) {
+	if (keymatch || (pprog = patcompile(s, 0, NULL))) {
+	    int len;
+
 	    if (v->isarr) {
-		ta = getarrvalue(v);
+		if (ishash) {
+		    scanprog = pprog;
+		    scanstr = s;
+		    if (keymatch) {
+			untokenize(s);
+			v->isarr |= SCANPM_KEYMATCH;
+		    } else if (ind)
+			v->isarr |= SCANPM_MATCHKEY;
+		    else
+			v->isarr |= SCANPM_MATCHVAL;
+		    if (down)
+			v->isarr |= SCANPM_MATCHMANY;
+		    if ((ta = getvaluearr(v)) &&
+			(*ta || ((v->isarr & SCANPM_MATCHMANY) &&
+				 (v->isarr & (SCANPM_MATCHKEY | SCANPM_MATCHVAL |
+					      SCANPM_KEYMATCH))))) {
+			*inv = v->inv;
+			*w = v->b;
+			return 1;
+		    }
+		} else
+		    ta = getarrvalue(v);
 		if (!ta || !*ta)
 		    return 0;
-		if (down)
-		    for (r = -1, p = ta + arrlen(ta) - 1; p >= ta; r--, p--) {
-			if (domatch(*p, c, 0) && !--num)
-			    return r;
-		} else
-		    for (r = 1, p = ta; *p; r++, p++)
-			if (domatch(*p, c, 0) && !--num)
-			    return r;
+		len = arrlen(ta);
+		if (beg < 0)
+		    beg += len;
+		if (beg >= 0 && beg < len) {
+		    if (down) {
+			if (!hasbeg)
+			    beg = len - 1;
+			for (r = 1 + beg, p = ta + beg; p >= ta; r--, p--) {
+			    if (pattry(pprog, *p) && !--num)
+				return r;
+			}
+		    } else
+			for (r = 1 + beg, p = ta + beg; *p; r++, p++)
+			    if (pattry(pprog, *p) && !--num)
+				return r;
+		}
 	    } else if (word) {
-		ta = sepsplit(d = s = getstrvalue(v), sep, 1);
-		if (down) {
-		    for (p = ta + (r = arrlen(ta)) - 1; p >= ta; p--, r--)
-			if (domatch(*p, c, 0) && !--num)
-			    break;
-		    if (p < ta)
-			return 0;
-		} else {
-		    for (r = 1, p = ta; *p; r++, p++)
-			if (domatch(*p, c, 0) && !--num)
-			    break;
-		    if (!*p)
-			return 0;
+		ta = sepsplit(d = s = getstrvalue(v), sep, 1, 1);
+		len = arrlen(ta);
+		if (beg < 0)
+		    beg += len;
+		if (beg >= 0 && beg < len) {
+		    if (down) {
+			if (!hasbeg)
+			    beg = len - 1;
+			for (r = 1 + beg, p = ta + beg; p >= ta; p--, r--)
+			    if (pattry(pprog, *p) && !--num)
+				break;
+			if (p < ta)
+			    return 0;
+		    } else {
+			for (r = 1 + beg, p = ta + beg; *p; r++, p++)
+			    if (pattry(pprog, *p) && !--num)
+				break;
+			if (!*p)
+			    return 0;
+		    }
 		}
 		if (a2)
 		    r++;
 		for (i = 0; (t = findword(&d, sep)) && *t; i++)
 		    if (!--r) {
-			r = (long)(t - s + (a2 ? -1 : 1));
+			r = (zlong)(t - s + (a2 ? -1 : 1));
 			if (!a2 && *tt != ',')
 			    *w = r + strlen(ta[i]) - 2;
 			return r;
@@ -675,35 +1058,50 @@ getarg(char **str, int *inv, Value v, int a2, long *w)
 		d = getstrvalue(v);
 		if (!d || !*d)
 		    return 0;
-		if (a2) {
-		    if (down)
-			for (r = -2, t = d + strlen(d) - 1; t >= d; r--, t--) {
-			    sav = *t;
-			    *t = '\0';
-			    if (domatch(d, c, 0) && !--num) {
+		len = strlen(d);
+		if (beg < 0)
+		    beg += len;
+		if (beg >= 0 && beg < len) {
+		    if (a2) {
+			if (down) {
+			    if (!hasbeg)
+				beg = len - 1;
+			    for (r = beg, t = d + beg; t >= d; r--, t--) {
+				sav = *t;
+				*t = '\0';
+				if (pattry(pprog, d)
+				    && !--num) {
+				    *t = sav;
+				    return r;
+				}
 				*t = sav;
-				return r;
 			    }
-			    *t = sav;
-		    } else
-			for (r = 0, t = d; *t; r++, t++) {
-			    sav = *t;
-			    *t = '\0';
-			    if (domatch(d, c, 0) && !--num) {
+			} else
+			    for (r = beg, t = d + beg; *t; r++, t++) {
+				sav = *t;
+				*t = '\0';
+				if (pattry(pprog, d) &&
+				    !--num) {
+				    *t = sav;
+				    return r;
+				}
 				*t = sav;
-				return r;
 			    }
-			    *t = sav;
-			}
-		} else {
-		    if (down)
-			for (r = -1, t = d + strlen(d) - 1; t >= d; r--, t--) {
-			    if (domatch(t, c, 0) && !--num)
-				return r;
-		    } else
-			for (r = 1, t = d; *t; r++, t++)
-			    if (domatch(t, c, 0) && !--num)
-				return r;
+		    } else {
+			if (down) {
+			    if (!hasbeg)
+				beg = len - 1;
+			    for (r = beg + 1, t = d + beg; t >= d; r--, t--) {
+				if (pattry(pprog, t) &&
+				    !--num)
+				    return r;
+			    }
+			} else
+			    for (r = beg + 1, t = d + beg; *t; r++, t++)
+				if (pattry(pprog, t) &&
+				    !--num)
+				    return r;
+		    }
 		}
 		return 0;
 	    }
@@ -726,13 +1124,13 @@ getindex(char **pptr, Value v)
     if (*tbrack == Outbrack)
 	*tbrack = ']';
     if ((s[0] == '*' || s[0] == '@') && s[1] == ']') {
-	if (v->isarr)
-	    v->isarr = (s[0] == '*') ? 1 : -1;
+	if ((v->isarr || IS_UNSET_VALUE(v)) && s[0] == '@')
+	    v->isarr |= SCANPM_ISVAR_AT;
 	v->a = 0;
 	v->b = -1;
 	s += 2;
     } else {
-	long we = 0, dummy;
+	zlong we = 0, dummy;
 
 	a = getarg(&s, &inv, v, 0, &we);
 
@@ -747,11 +1145,13 @@ getindex(char **pptr, Value v)
 		} else
 		    a = -ztrlen(t + a + strlen(t));
 	    }
-	    if (a > 0 && isset(KSHARRAYS))
+	    if (a > 0 && (isset(KSHARRAYS) || (v->pm->flags & PM_HASHED)))
 		a--;
-	    v->inv = 1;
-	    v->isarr = 0;
-	    v->a = v->b = a;
+	    if (v->isarr != SCANPM_WANTINDEX) {
+		v->inv = 1;
+		v->isarr = 0;
+		v->a = v->b = a;
+	    }
 	    if (*s == ',') {
 		zerr("invalid subscript", NULL, 0);
 		while (*s != ']' && *s != Outbrack)
@@ -762,9 +1162,11 @@ getindex(char **pptr, Value v)
 	    if (*s == ']' || *s == Outbrack)
 		s++;
 	} else {
+	    int com;
+
 	    if (a > 0)
 		a--;
-	    if (*s == ',') {
+	    if ((com = (*s == ','))) {
 		s++;
 		b = getarg(&s, &inv, v, 1, &dummy);
 		if (b > 0)
@@ -774,7 +1176,10 @@ getindex(char **pptr, Value v)
 	    }
 	    if (*s == ']' || *s == Outbrack) {
 		s++;
-		if (v->isarr && a == b)
+		if (v->isarr && a == b && !com &&
+		    (!(v->isarr & SCANPM_MATCHMANY) ||
+		     !(v->isarr & (SCANPM_MATCHKEY | SCANPM_MATCHVAL |
+				   SCANPM_KEYMATCH))))
 		    v->isarr = 0;
 		v->a = a;
 		v->b = b;
@@ -788,37 +1193,43 @@ getindex(char **pptr, Value v)
 
 
 /**/
-Value
-getvalue(char **pptr, int bracks)
+mod_export Value
+getvalue(Value v, char **pptr, int bracks)
+{
+  return fetchvalue(v, pptr, bracks, 0);
+}
+
+/**/
+mod_export Value
+fetchvalue(Value v, char **pptr, int bracks, int flags)
 {
     char *s, *t;
-    char sav;
-    Value v;
+    char sav, c;
     int ppar = 0;
 
     s = t = *pptr;
-    garr = NULL;
 
-    if (idigit(*s))
+    if (idigit(c = *s)) {
 	if (bracks >= 0)
 	    ppar = zstrtol(s, &s, 10);
 	else
 	    ppar = *s++ - '0';
-    else if (iident(*s))
+    }
+    else if (iident(c))
 	while (iident(*s))
 	    s++;
-    else if (*s == Quest)
+    else if (c == Quest)
 	*s++ = '?';
-    else if (*s == Pound)
+    else if (c == Pound)
 	*s++ = '#';
-    else if (*s == String)
+    else if (c == String)
 	*s++ = '$';
-    else if (*s == Qstring)
+    else if (c == Qstring)
 	*s++ = '$';
-    else if (*s == Star)
+    else if (c == Star)
 	*s++ = '*';
-    else if (*s == '#' || *s == '-' || *s == '?' || *s == '$' ||
-	     *s == '_' || *s == '!' || *s == '@' || *s == '*')
+    else if (c == '#' || c == '-' || c == '?' || c == '$' ||
+	     c == '!' || c == '@' || c == '*')
 	s++;
     else
 	return NULL;
@@ -826,7 +1237,10 @@ getvalue(char **pptr, int bracks)
     if ((sav = *s))
 	*s = '\0';
     if (ppar) {
-	v = (Value) hcalloc(sizeof *v);
+	if (v)
+	    memset(v, 0, sizeof(*v));
+	else
+	    v = (Value) hcalloc(sizeof *v);
 	v->pm = argvparam;
 	v->inv = 0;
 	v->a = v->b = ppar - 1;
@@ -836,16 +1250,27 @@ getvalue(char **pptr, int bracks)
 	Param pm;
 	int isvarat;
 
-        isvarat = !strcmp(t, "@");
+        isvarat = (t[0] == '@' && !t[1]);
 	pm = (Param) paramtab->getnode(paramtab, *t == '0' ? "0" : t);
 	if (sav)
 	    *s = sav;
 	*pptr = s;
 	if (!pm || (pm->flags & PM_UNSET))
 	    return NULL;
-	v = (Value) hcalloc(sizeof *v);
-	if (PM_TYPE(pm->flags) == PM_ARRAY)
-	    v->isarr = isvarat ? -1 : 1;
+	if (v)
+	    memset(v, 0, sizeof(*v));
+	else
+	    v = (Value) hcalloc(sizeof *v);
+	if (PM_TYPE(pm->flags) & (PM_ARRAY|PM_HASHED)) {
+	    /* Overload v->isarr as the flag bits for hashed arrays. */
+	    v->isarr = flags | (isvarat ? SCANPM_ISVAR_AT : 0);
+	    /* If no flags were passed, we need something to represent *
+	     * `true' yet differ from an explicit WANTVALS.  This is a *
+	     * bit of a hack, but makes some sense:  When no subscript *
+	     * is provided, all values are substituted.                */
+	    if (!v->isarr)
+		v->isarr = SCANPM_MATCHMANY;
+	}
 	v->pm = pm;
 	v->inv = 0;
 	v->a = 0;
@@ -855,7 +1280,8 @@ getvalue(char **pptr, int bracks)
 		*pptr = s;
 		return v;
 	    }
-	} else if (v->isarr && iident(*t) && isset(KSHARRAYS))
+	} else if (!(flags & SCANPM_ASSIGNING) && v->isarr &&
+		   iident(*t) && isset(KSHARRAYS))
 	    v->b = 0, v->isarr = 0;
     }
     if (!bracks && *s)
@@ -863,68 +1289,82 @@ 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;
 }
 
 /**/
-char *
+mod_export char *
 getstrvalue(Value v)
 {
     char *s, **ss;
-    static char buf[(sizeof(long) * 8) + 4];
+    char buf[(sizeof(zlong) * 8) + 4];
 
     if (!v)
 	return hcalloc(1);
-    HEAPALLOC {
-	if (v->inv) {
-	    sprintf(buf, "%d", v->a);
-	    s = dupstring(buf);
-	    LASTALLOC_RETURN s;
-	}
 
-	switch(PM_TYPE(v->pm->flags)) {
-	case PM_ARRAY:
-	    if (v->isarr)
-		s = sepjoin(v->pm->gets.afn(v->pm), 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];
-	    }
-	    LASTALLOC_RETURN s;
-	case PM_INTEGER:
-	    convbase(s = buf, v->pm->gets.ifn(v->pm), v->pm->ct);
-	    break;
-	case PM_SCALAR:
-	    s = v->pm->gets.cfn(v->pm);
-	    break;
-	default:
-	    s = NULL;
-	    DPUTS(1, "BUG: param node without valid type");
-	    break;
+    if (v->inv && !(v->pm->flags & PM_HASHED)) {
+	sprintf(buf, "%d", v->a);
+	s = dupstring(buf);
+	return s;
+    }
+
+    switch(PM_TYPE(v->pm->flags)) {
+    case PM_HASHED:
+	/* (!v->isarr) should be impossible unless emulating ksh */
+	if (!v->isarr && emulation == EMULATE_KSH) {
+	    s = dupstring("[0]");
+	    if (getindex(&s, v) == 0)
+		s = getstrvalue(v);
+	    return s;
+	} /* else fall through */
+    case PM_ARRAY:
+	ss = getvaluearr(v);
+	if (v->isarr)
+	    s = sepjoin(ss, NULL, 1);
+	else {
+	    if (v->a < 0)
+		v->a += arrlen(ss);
+	    s = (v->a >= arrlen(ss) || v->a < 0) ? (char *) hcalloc(1) : ss[v->a];
 	}
+	return s;
+    case PM_INTEGER:
+	convbase(buf, v->pm->gets.ifn(v->pm), v->pm->ct);
+	s = dupstring(buf);
+	break;
+    case PM_EFLOAT:
+    case PM_FFLOAT:
+	s = convfloat(v->pm->gets.ffn(v->pm), v->pm->ct, v->pm->flags, NULL);
+	break;
+    case PM_SCALAR:
+	s = v->pm->gets.cfn(v->pm);
+	break;
+    default:
+	s = NULL;
+	DPUTS(1, "BUG: param node without valid type");
+	break;
+    }
+
+    if (v->a == 0 && v->b == -1)
+	return s;
+
+    if (v->a < 0)
+	v->a += strlen(s);
+    if (v->b < 0)
+	v->b += strlen(s);
+    s = (v->a > (int)strlen(s)) ? dupstring("") : dupstring(s + v->a);
+    if (v->b < v->a)
+	s[0] = '\0';
+    else if (v->b - v->a < (int)strlen(s))
+	s[v->b - v->a + 1 + (s[v->b - v->a] == Meta)] = '\0';
 
-	if (v->a == 0 && v->b == -1)
-	    LASTALLOC_RETURN s;
-	if (v->a < 0)
-	    v->a += strlen(s);
-	if (v->b < 0)
-	    v->b += strlen(s);
-	s = (v->a > (int)strlen(s)) ? dupstring("") : dupstring(s + v->a);
-	if (v->b < v->a)
-	    s[0] = '\0';
-	else if (v->b - v->a < (int)strlen(s))
-	    s[v->b - v->a + 1 + (s[v->b - v->a] == Meta)] = '\0';
-    } LASTALLOC;
     return s;
 }
 
@@ -938,6 +1378,8 @@ getarrvalue(Value v)
 
     if (!v)
 	return arrdup(nular);
+    else if (IS_UNSET_VALUE(v))
+	return arrdup(&nular[1]);
     if (v->inv) {
 	char buf[DIGBUFSIZE];
 
@@ -946,7 +1388,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)
@@ -956,7 +1398,7 @@ getarrvalue(Value v)
     if (v->a > arrlen(s) || v->a < 0)
 	s = arrdup(nular);
     else
-	s = arrdup(s) + v->a;
+	s = arrdup(s + v->a);
     if (v->b < v->a)
 	s[0] = NULL;
     else if (v->b - v->a < arrlen(s))
@@ -965,7 +1407,7 @@ getarrvalue(Value v)
 }
 
 /**/
-long
+mod_export zlong
 getintvalue(Value v)
 {
     if (!v || v->isarr)
@@ -974,14 +1416,37 @@ getintvalue(Value v)
 	return v->a;
     if (PM_TYPE(v->pm->flags) == PM_INTEGER)
 	return v->pm->gets.ifn(v->pm);
-    return matheval(getstrvalue(v));
+    if (v->pm->flags & (PM_EFLOAT|PM_FFLOAT))
+	return (zlong)v->pm->gets.ffn(v->pm);
+    return mathevali(getstrvalue(v));
 }
 
 /**/
-static void
+mnumber
+getnumvalue(Value v)
+{
+    mnumber mn;
+    mn.type = MN_INTEGER;
+
+    if (!v || v->isarr) {
+	mn.u.l = 0;
+    } else if (v->inv) {
+	mn.u.l = v->a;
+    } else if (PM_TYPE(v->pm->flags) == PM_INTEGER) {
+	mn.u.l = v->pm->gets.ifn(v->pm);
+    } else if (v->pm->flags & (PM_EFLOAT|PM_FFLOAT)) {
+	mn.type = MN_FLOAT;
+	mn.u.d = v->pm->gets.ffn(v->pm);
+    } else
+	return matheval(getstrvalue(v));
+    return mn;
+}
+
+/**/
+mod_export void
 setstrvalue(Value v, char *val)
 {
-    char buf[(sizeof(long) * 8) + 4];
+    char buf[(sizeof(zlong) * 8) + 4];
 
     if (v->pm->flags & PM_READONLY) {
 	zerr("read-only variable: %s", v->pm->nam, 0);
@@ -993,9 +1458,9 @@ setstrvalue(Value v, char *val)
 	zsfree(val);
 	return;
     }
+    v->pm->flags &= ~PM_UNSET;
     switch (PM_TYPE(v->pm->flags)) {
     case PM_SCALAR:
-	MUSTUSEHEAP("setstrvalue");
 	if (v->a == 0 && v->b == -1) {
 	    (v->pm->sets.cfn) (v->pm, val);
 	    if (v->pm->flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z) && !v->pm->ct)
@@ -1029,14 +1494,22 @@ setstrvalue(Value v, char *val)
 	break;
     case PM_INTEGER:
 	if (val) {
-	    (v->pm->sets.ifn) (v->pm, matheval(val));
+	    (v->pm->sets.ifn) (v->pm, mathevali(val));
 	    zsfree(val);
 	}
 	if (!v->pm->ct && lastbase != -1)
 	    v->pm->ct = lastbase;
 	break;
+    case PM_EFLOAT:
+    case PM_FFLOAT:
+	if (val) {
+	    mnumber mn = matheval(val);
+	    (v->pm->sets.ffn) (v->pm, (mn.type & MN_FLOAT) ? mn.u.d :
+			       (double)mn.u.l);
+	    zsfree(val);
+	}
+	break;
     case PM_ARRAY:
-	MUSTUSEHEAP("setstrvalue");
 	{
 	    char **ss = (char **) zalloc(2 * sizeof(char *));
 
@@ -1052,21 +1525,24 @@ setstrvalue(Value v, char *val)
 	return;
     if (PM_TYPE(v->pm->flags) == PM_INTEGER)
 	convbase(val = buf, v->pm->gets.ifn(v->pm), v->pm->ct);
+    else if (v->pm->flags & (PM_EFLOAT|PM_FFLOAT))
+	val = convfloat(v->pm->gets.ffn(v->pm), v->pm->ct,
+			v->pm->flags, NULL);
     else
 	val = v->pm->gets.cfn(v->pm);
     if (v->pm->env)
-	v->pm->env = replenv(v->pm->env, val);
+	v->pm->env = replenv(v->pm->env, val, v->pm->flags);
     else {
 	v->pm->flags |= PM_EXPORTED;
-	v->pm->env = addenv(v->pm->nam, val);
+	v->pm->env = addenv(v->pm->nam, val, v->pm->flags);
     }
 }
 
 /**/
-static void
-setintvalue(Value v, long val)
+void
+setnumvalue(Value v, mnumber val)
 {
-    char buf[DIGBUFSIZE];
+    char buf[DIGBUFSIZE], *p;
 
     if (v->pm->flags & PM_READONLY) {
 	zerr("read-only variable: %s", v->pm->nam, 0);
@@ -1079,18 +1555,28 @@ setintvalue(Value v, long val)
     switch (PM_TYPE(v->pm->flags)) {
     case PM_SCALAR:
     case PM_ARRAY:
-	sprintf(buf, "%ld", val);
-	setstrvalue(v, ztrdup(buf));
+	if (val.type & MN_INTEGER)
+	    convbase(p = buf, val.u.l, 0);
+	else
+	    p = convfloat(val.u.d, 0, 0, NULL);
+	setstrvalue(v, ztrdup(p));
 	break;
     case PM_INTEGER:
-	(v->pm->sets.ifn) (v->pm, val);
+	(v->pm->sets.ifn) (v->pm, (val.type & MN_INTEGER) ? val.u.l :
+			   (zlong) val.u.d);
+	setstrvalue(v, NULL);
+	break;
+    case PM_EFLOAT:
+    case PM_FFLOAT:
+	(v->pm->sets.ffn) (v->pm, (val.type & MN_INTEGER) ?
+			   (double)val.u.l : val.u.d);
 	setstrvalue(v, NULL);
 	break;
     }
 }
 
 /**/
-static void
+mod_export void
 setarrvalue(Value v, char **val)
 {
     if (v->pm->flags & PM_READONLY) {
@@ -1103,17 +1589,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);
@@ -1150,25 +1644,45 @@ setarrvalue(Value v, char **val)
 /* Retrieve an integer parameter */
 
 /**/
-long
+mod_export zlong
 getiparam(char *s)
 {
+    struct value vbuf;
     Value v;
 
-    if (!(v = getvalue(&s, 1)))
+    if (!(v = getvalue(&vbuf, &s, 1)))
 	return 0;
     return getintvalue(v);
 }
 
+/* Retrieve a numerical parameter, either integer or floating */
+
+/**/
+mnumber
+getnparam(char *s)
+{
+    struct value vbuf;
+    Value v;
+
+    if (!(v = getvalue(&vbuf, &s, 1))) {
+	mnumber mn;
+	mn.type = MN_INTEGER;
+	mn.u.l = 0;
+	return mn;
+    }
+    return getnumvalue(v);
+}
+
 /* Retrieve a scalar (string) parameter */
 
 /**/
-char *
+mod_export char *
 getsparam(char *s)
 {
+    struct value vbuf;
     Value v;
 
-    if (!(v = getvalue(&s, 0)))
+    if (!(v = getvalue(&vbuf, &s, 0)))
 	return NULL;
     return getstrvalue(v);
 }
@@ -1176,21 +1690,38 @@ getsparam(char *s)
 /* Retrieve an array parameter */
 
 /**/
-char **
+mod_export char **
 getaparam(char *s)
 {
+    struct value vbuf;
     Value v;
 
-    if (!idigit(*s) && (v = getvalue(&s, 0)) &&
+    if (!idigit(*s) && (v = getvalue(&vbuf, &s, 0)) &&
 	PM_TYPE(v->pm->flags) == PM_ARRAY)
 	return v->pm->gets.afn(v->pm);
     return NULL;
 }
 
+/* Retrieve an assoc array parameter as an array */
+
 /**/
-Param
+mod_export char **
+gethparam(char *s)
+{
+    struct value vbuf;
+    Value v;
+
+    if (!idigit(*s) && (v = getvalue(&vbuf, &s, 0)) &&
+	PM_TYPE(v->pm->flags) == PM_HASHED)
+	return paramvalarr(v->pm->gets.hfn(v->pm), SCANPM_WANTVALS);
+    return NULL;
+}
+
+/**/
+mod_export Param
 setsparam(char *s, char *val)
 {
+    struct value vbuf;
     Value v;
     char *t = s;
     char *ss;
@@ -1203,21 +1734,21 @@ setsparam(char *s, char *val)
     }
     if ((ss = strchr(s, '['))) {
 	*ss = '\0';
-	if (!(v = getvalue(&s, 1)))
+	if (!(v = getvalue(&vbuf, &s, 1)))
 	    createparam(t, PM_ARRAY);
 	*ss = '[';
 	v = NULL;
     } else {
-	if (!(v = getvalue(&s, 1)))
+	if (!(v = getvalue(&vbuf, &s, 1)))
 	    createparam(t, PM_SCALAR);
-	else if (PM_TYPE(v->pm->flags) == PM_ARRAY &&
-		 !(v->pm->flags & PM_SPECIAL) && unset(KSHARRAYS)) {
+	else if ((PM_TYPE(v->pm->flags) & (PM_ARRAY|PM_HASHED)) &&
+		 !(v->pm->flags & (PM_SPECIAL|PM_TIED)) && unset(KSHARRAYS)) {
 	    unsetparam(t);
 	    createparam(t, PM_SCALAR);
 	    v = NULL;
 	}
     }
-    if (!v && !(v = getvalue(&t, 1))) {
+    if (!v && !(v = getvalue(&vbuf, &t, 1))) {
 	zsfree(val);
 	return NULL;
     }
@@ -1226,9 +1757,10 @@ setsparam(char *s, char *val)
 }
 
 /**/
-Param
+mod_export Param
 setaparam(char *s, char **val)
 {
+    struct value vbuf;
     Value v;
     char *t = s;
     char *ss;
@@ -1241,15 +1773,21 @@ setaparam(char *s, char **val)
     }
     if ((ss = strchr(s, '['))) {
 	*ss = '\0';
-	if (!(v = getvalue(&s, 1)))
+	if (!(v = getvalue(&vbuf, &s, 1)))
 	    createparam(t, PM_ARRAY);
 	*ss = '[';
+	if (v && PM_TYPE(v->pm->flags) == PM_HASHED) {
+	    zerr("attempt to set slice of associative array", NULL, 0);
+	    freearray(val);
+	    errflag = 1;
+	    return NULL;
+	}
 	v = NULL;
     } else {
-	if (!(v = getvalue(&s, 1)))
+	if (!(v = fetchvalue(&vbuf, &s, 1, SCANPM_ASSIGNING)))
 	    createparam(t, PM_ARRAY);
-	else if (PM_TYPE(v->pm->flags) != PM_ARRAY &&
-		 !(v->pm->flags & PM_SPECIAL)) {
+	else if (!(PM_TYPE(v->pm->flags) & (PM_ARRAY|PM_HASHED)) &&
+		 !(v->pm->flags & (PM_SPECIAL|PM_TIED))) {
 	    int uniq = v->pm->flags & PM_UNIQUE;
 	    unsetparam(t);
 	    createparam(t, PM_ARRAY | uniq);
@@ -1257,54 +1795,126 @@ setaparam(char *s, char **val)
 	}
     }
     if (!v)
-	if (!(v = getvalue(&t, 1)))
+	if (!(v = fetchvalue(&vbuf, &t, 1, SCANPM_ASSIGNING)))
 	    return NULL;
-    if (isset(KSHARRAYS) && !ss)
-	/* the whole array should be set instead of only the first element */
-	v->b = -1;
     setarrvalue(v, val);
     return v->pm;
 }
 
 /**/
-Param
-setiparam(char *s, long val)
+mod_export Param
+sethparam(char *s, char **val)
 {
+    struct value vbuf;
+    Value v;
+    char *t = s;
+
+    if (!isident(s)) {
+	zerr("not an identifier: %s", s, 0);
+	freearray(val);
+	errflag = 1;
+	return NULL;
+    }
+    if (strchr(s, '[')) {
+	freearray(val);
+	zerr("nested associative arrays not yet supported", NULL, 0);
+	errflag = 1;
+	return NULL;
+    } else {
+	if (!(v = fetchvalue(&vbuf, &s, 1, SCANPM_ASSIGNING)))
+	    createparam(t, PM_HASHED);
+	else if (!(PM_TYPE(v->pm->flags) & PM_HASHED) &&
+		 !(v->pm->flags & PM_SPECIAL)) {
+	    unsetparam(t);
+	    createparam(t, PM_HASHED);
+	    v = NULL;
+	}
+    }
+    if (!v)
+	if (!(v = fetchvalue(&vbuf, &t, 1, SCANPM_ASSIGNING)))
+	    return NULL;
+    setarrvalue(v, val);
+    return v->pm;
+}
+
+/**/
+mod_export Param
+setiparam(char *s, zlong val)
+{
+    struct value vbuf;
     Value v;
     char *t = s;
     Param pm;
+    mnumber mnval;
 
     if (!isident(s)) {
 	zerr("not an identifier: %s", s, 0);
 	errflag = 1;
 	return NULL;
     }
-    if (!(v = getvalue(&s, 1))) {
+    if (!(v = getvalue(&vbuf, &s, 1))) {
 	pm = createparam(t, PM_INTEGER);
 	DPUTS(!pm, "BUG: parameter not created");
 	pm->u.val = val;
 	return pm;
     }
-    setintvalue(v, val);
+    mnval.type = MN_INTEGER;
+    mnval.u.l = val;
+    setnumvalue(v, mnval);
+    return v->pm;
+}
+
+/*
+ * Like setiparam(), but can take an mnumber which can be integer or
+ * floating.
+ */
+
+/**/
+Param
+setnparam(char *s, mnumber val)
+{
+    struct value vbuf;
+    Value v;
+    char *t = s;
+    Param pm;
+
+    if (!isident(s)) {
+	zerr("not an identifier: %s", s, 0);
+	errflag = 1;
+	return NULL;
+    }
+    if (!(v = getvalue(&vbuf, &s, 1))) {
+	pm = createparam(t, (val.type & MN_INTEGER) ? PM_INTEGER
+			 : PM_FFLOAT);
+	DPUTS(!pm, "BUG: parameter not created");
+	if (val.type & MN_INTEGER)
+	    pm->u.val = val.u.l;
+	else
+	    pm->u.dval = val.u.d;
+	return pm;
+    }
+    setnumvalue(v, val);
     return v->pm;
 }
 
 /* Unset a parameter */
 
 /**/
-void
+mod_export void
 unsetparam(char *s)
 {
     Param pm;
 
-    if ((pm = (Param) paramtab->getnode(paramtab, s)))
+    if ((pm = (Param) (paramtab == realparamtab ?
+		       gethashnode2(paramtab, s) :
+		       paramtab->getnode(paramtab, s))))
 	unsetparam_pm(pm, 0, 1);
 }
 
 /* Unset a parameter */
 
 /**/
-void
+mod_export void
 unsetparam_pm(Param pm, int altflag, int exp)
 {
     Param oldpm, altpm;
@@ -1331,22 +1941,30 @@ unsetparam_pm(Param pm, int altflag, int exp)
 	    unsetparam_pm(altpm, 1, exp);
     }
 
-    /* If this was a local variable, we need to keep the old     *
-     * struct so that it is resurrected at the right level.      *
-     * This is partly because when an array/scalar value is set  *
-     * and the parameter used to be the other sort, unsetparam() *
-     * is called.  Beyond that, there is an ambiguity:  should   *
-     * foo() { local bar; unset bar; } make the global bar       *
-     * available or not?  The following makes the answer "no".   */
-    if (locallevel >= pm->level)
+    /*
+     * If this was a local variable, we need to keep the old
+     * struct so that it is resurrected at the right level.
+     * This is partly because when an array/scalar value is set
+     * and the parameter used to be the other sort, unsetparam()
+     * is called.  Beyond that, there is an ambiguity:  should
+     * foo() { local bar; unset bar; } make the global bar
+     * available or not?  The following makes the answer "no".
+     *
+     * Some specials, such as those used in zle, still need removing
+     * from the parameter table; they have the PM_REMOVABLE flag.
+     */
+    if ((pm->level && locallevel >= pm->level) ||
+	(pm->flags & (PM_SPECIAL|PM_REMOVABLE)) == PM_SPECIAL)
 	return;
 
-    paramtab->removenode(paramtab, pm->nam); /* remove parameter node from table */
+    /* remove parameter node from table */
+    paramtab->removenode(paramtab, pm->nam);
 
     if (pm->old) {
 	oldpm = pm->old;
 	paramtab->addnode(paramtab, oldpm->nam, oldpm);
-	if ((PM_TYPE(oldpm->flags) == PM_SCALAR) && oldpm->sets.cfn == strsetfn)
+	if ((PM_TYPE(oldpm->flags) == PM_SCALAR) &&
+	    oldpm->sets.cfn == strsetfn)
 	    adduserdir(oldpm->nam, oldpm->u.str, 0, 0);
     }
 
@@ -1357,12 +1975,13 @@ unsetparam_pm(Param pm, int altflag, int exp)
  * the specific set function.                                           */
 
 /**/
-void
+mod_export void
 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;
 }
@@ -1370,7 +1989,7 @@ stdunsetfn(Param pm, int exp)
 /* Function to get value of an integer parameter */
 
 /**/
-static long
+static zlong
 intgetfn(Param pm)
 {
     return pm->u.val;
@@ -1380,15 +1999,33 @@ intgetfn(Param pm)
 
 /**/
 static void
-intsetfn(Param pm, long x)
+intsetfn(Param pm, zlong x)
 {
     pm->u.val = x;
 }
 
+/* Function to get value of a floating point parameter */
+
+/**/
+static double
+floatgetfn(Param pm)
+{
+    return pm->u.dval;
+}
+
+/* Function to set value of an integer parameter */
+
+/**/
+static void
+floatsetfn(Param pm, double x)
+{
+    pm->u.dval = x;
+}
+
 /* Function to get value of a scalar (string) parameter */
 
 /**/
-char *
+mod_export char *
 strgetfn(Param pm)
 {
     return pm->u.str ? pm->u.str : (char *) hcalloc(1);
@@ -1408,7 +2045,7 @@ strsetfn(Param pm, char *x)
 /* Function to get value of an array parameter */
 
 /**/
-static char **
+char **
 arrgetfn(Param pm)
 {
     static char *nullarray = NULL;
@@ -1419,7 +2056,7 @@ arrgetfn(Param pm)
 /* Function to set value of an array parameter */
 
 /**/
-static void
+mod_export void
 arrsetfn(Param pm, char **x)
 {
     if (pm->u.arr && pm->u.arr != x)
@@ -1427,6 +2064,70 @@ arrsetfn(Param pm, char **x)
     if (pm->flags & PM_UNIQUE)
 	uniqarray(x);
     pm->u.arr = x;
+    /* Arrays tied to colon-arrays may need to fix the environment */
+    if (pm->ename && x)
+	arrfixenv(pm->ename, x);
+}
+
+/* Function to get value of an association parameter */
+
+/**/
+mod_export HashTable
+hashgetfn(Param pm)
+{
+    return pm->u.hash;
+}
+
+/* Function to set value of an association parameter */
+
+/**/
+mod_export void
+hashsetfn(Param pm, HashTable x)
+{
+    if (pm->u.hash && pm->u.hash != x)
+	deleteparamtable(pm->u.hash);
+    pm->u.hash = x;
+}
+
+/* Function to 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 = 0;
+    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",
+	     NULL, 0);
+	return;
+    }
+    if (alen)
+	ht = paramtab = newparamtable(17, pm->nam);
+    while (*aptr) {
+	/* The parameter name is ztrdup'd... */
+	v->pm = createparam(*aptr, PM_SCALAR|PM_UNSET);
+	/*
+	 * createparam() doesn't return anything if the parameter
+	 * already existed.
+	 */
+	if (!v->pm)
+	    v->pm = (Param) paramtab->getnode(paramtab, *aptr);
+	zsfree(*aptr++);
+	/* ...but we can use the value without copying. */
+	setstrvalue(v, *aptr++);
+    }
+    paramtab = opmtab;
+    pm->sets.hfn(pm, ht);
+    free(val);		/* not freearray() */
 }
 
 /* This function is used as the set function for      *
@@ -1444,10 +2145,10 @@ nullsetfn(Param pm, char *x)
  * containing the integer value.                    */
 
 /**/
-long
+mod_export zlong
 intvargetfn(Param pm)
 {
-    return *((long *)pm->u.data);
+    return *((zlong *)pm->u.data);
 }
 
 /* Function to set value of generic special integer *
@@ -1455,10 +2156,10 @@ intvargetfn(Param pm)
  * where the value is to be stored.                 */
 
 /**/
-void
-intvarsetfn(Param pm, long x)
+mod_export void
+intvarsetfn(Param pm, zlong x)
 {
-    *((long *)pm->u.data) = x;
+    *((zlong *)pm->u.data) = x;
 }
 
 /* Function to set value of any ZLE-related integer *
@@ -1467,25 +2168,13 @@ intvarsetfn(Param pm, long x)
 
 /**/
 void
-zlevarsetfn(Param pm, long x)
+zlevarsetfn(Param pm, zlong x)
 {
-    if ((long *)pm->u.data == & columns) {
-	if(x <= 0)
-	    x = tccolumns > 0 ? tccolumns : 80;
-	if (x > 2)
-	    termflags &= ~TERM_NARROW;
-	else
-	    termflags |= TERM_NARROW;
-    } else if ((long *)pm->u.data == & lines) {
-	if(x <= 0)
-	    x = tclines > 0 ? tclines : 24;
-	if (x > 2)
-	    termflags &= ~TERM_SHORT;
-	else
-	    termflags |= TERM_SHORT;
-    }
+    zlong *p = (zlong *)pm->u.data;
 
-    *((long *)pm->u.data) = x;
+    *p = x;
+    if (p == &lines || p == &columns)
+	adjustwinsize(2 + (p == &columns));
 }
 
 /* Function to set value of generic special scalar    *
@@ -1493,7 +2182,7 @@ zlevarsetfn(Param pm, long x)
  * representing the scalar (string).                  */
 
 /**/
-void
+mod_export void
 strvarsetfn(Param pm, char *x)
 {
     char **q = ((char **)pm->u.data);
@@ -1507,7 +2196,7 @@ strvarsetfn(Param pm, char *x)
  * representing the scalar (string).                  */
 
 /**/
-char *
+mod_export char *
 strvargetfn(Param pm)
 {
     char *s = *((char **)pm->u.data);
@@ -1523,7 +2212,7 @@ strvargetfn(Param pm)
  * of pointers).                                   */
 
 /**/
-char **
+mod_export char **
 arrvargetfn(Param pm)
 {
     return *((char ***)pm->u.data);
@@ -1536,7 +2225,7 @@ arrvargetfn(Param pm)
  * version of this array which will need to be updated.         */
 
 /**/
-void
+mod_export void
 arrvarsetfn(Param pm, char **x)
 {
     char ***dptr = (char ***)pm->u.data;
@@ -1554,7 +2243,8 @@ arrvarsetfn(Param pm, char **x)
 char *
 colonarrgetfn(Param pm)
 {
-    return zjoin(*(char ***)pm->u.data, ':');
+    char ***dptr = (char ***)pm->u.data;
+    return *dptr ? zjoin(*dptr, ':', 1) : "";
 }
 
 /**/
@@ -1563,8 +2253,15 @@ colonarrsetfn(Param pm, char *x)
 {
     char ***dptr = (char ***)pm->u.data;
 
-    freearray(*dptr);
-    *dptr = x ? colonsplit(x, pm->flags & PM_UNIQUE) : mkarray(NULL);
+    /*
+     * If this is tied to a parameter (rather than internal) array,
+     * the array itself may be NULL.  Otherwise, we have to make
+     * sure it doesn't ever get null.
+     */
+    if (*dptr)
+	freearray(*dptr);
+    *dptr = x ? colonsplit(x, pm->flags & PM_UNIQUE) :
+	(pm->flags & PM_TIED) ? NULL : mkarray(NULL);
     if (pm->ename)
 	arrfixenv(pm->nam, *dptr);
     zsfree(x);
@@ -1593,7 +2290,7 @@ uniqarray(char **x)
 /* Function to get value of special parameter `#' and `ARGC' */
 
 /**/
-long
+zlong
 poundgetfn(Param pm)
 {
     return arrlen(pparams);
@@ -1602,7 +2299,7 @@ poundgetfn(Param pm)
 /* Function to get value for special parameter `RANDOM' */
 
 /**/
-long
+zlong
 randomgetfn(Param pm)
 {
     return rand() & 0x7fff;
@@ -1612,7 +2309,7 @@ randomgetfn(Param pm)
 
 /**/
 void
-randomsetfn(Param pm, long v)
+randomsetfn(Param pm, zlong v)
 {
     srand((unsigned int)v);
 }
@@ -1620,7 +2317,7 @@ randomsetfn(Param pm, long v)
 /* Function to get value for special parameter `SECONDS' */
 
 /**/
-long
+zlong
 secondsgetfn(Param pm)
 {
     return time(NULL) - shtimer.tv_sec;
@@ -1630,7 +2327,7 @@ secondsgetfn(Param pm)
 
 /**/
 void
-secondssetfn(Param pm, long x)
+secondssetfn(Param pm, zlong x)
 {
     shtimer.tv_sec = time(NULL) - x;
     shtimer.tv_usec = 0;
@@ -1670,7 +2367,7 @@ usernamesetfn(Param pm, char *x)
 /* Function to get value for special parameter `UID' */
 
 /**/
-long
+zlong
 uidgetfn(Param pm)
 {
     return getuid();
@@ -1690,7 +2387,7 @@ uidsetfn(Param pm, uid_t x)
 /* Function to get value for special parameter `EUID' */
 
 /**/
-long
+zlong
 euidgetfn(Param pm)
 {
     return geteuid();
@@ -1710,7 +2407,7 @@ euidsetfn(Param pm, uid_t x)
 /* Function to get value for special parameter `GID' */
 
 /**/
-long
+zlong
 gidgetfn(Param pm)
 {
     return getgid();
@@ -1730,7 +2427,7 @@ gidsetfn(Param pm, gid_t x)
 /* Function to get value for special parameter `EGID' */
 
 /**/
-long
+zlong
 egidgetfn(Param pm)
 {
     return getegid();
@@ -1748,7 +2445,7 @@ egidsetfn(Param pm, gid_t x)
 }
 
 /**/
-long
+zlong
 ttyidlegetfn(Param pm)
 {
     struct stat ttystat;
@@ -1780,7 +2477,7 @@ ifssetfn(Param pm, char *x)
 
 /* Functions to set value of special parameters `LANG' and `LC_*' */
 
-#ifdef LC_ALL
+#ifdef USE_LOCALE
 static struct localename {
     char *name;
     int category;
@@ -1794,6 +2491,9 @@ static struct localename {
 #ifdef LC_MESSAGES
     {"LC_MESSAGES", LC_MESSAGES},
 #endif
+#ifdef LC_NUMERIC
+    {"LC_NUMERIC", LC_NUMERIC},
+#endif
 #ifdef LC_TIME
     {"LC_TIME", LC_TIME},
 #endif
@@ -1847,12 +2547,12 @@ lcsetfn(Param pm, char *x)
 	if (!strcmp(ln->name, pm->nam))
 	    setlocale(ln->category, x ? x : "");
 }
-#endif
+#endif /* USE_LOCALE */
 
 /* Function to get value for special parameter `HISTSIZE' */
 
 /**/
-long
+zlong
 histsizegetfn(Param pm)
 {
     return histsiz;
@@ -1862,7 +2562,7 @@ histsizegetfn(Param pm)
 
 /**/
 void
-histsizesetfn(Param pm, long v)
+histsizesetfn(Param pm, zlong v)
 {
     if ((histsiz = v) <= 2)
 	histsiz = 2;
@@ -1872,7 +2572,7 @@ histsizesetfn(Param pm, long v)
 /* Function to get value for special parameter `ERRNO' */
 
 /**/
-long
+zlong
 errnogetfn(Param pm)
 {
     return errno;
@@ -1961,7 +2661,10 @@ wordcharssetfn(Param pm, char *x)
 char *
 underscoregetfn(Param pm)
 {
-    return underscore;
+    char *u = dupstring(underscore);
+
+    untokenize(u);
+    return u;
 }
 
 /* Function to get value for special parameter `TERM' */
@@ -1989,6 +2692,38 @@ termsetfn(Param pm, char *x)
 	init_term();
 }
 
+/* Function to get value for special parameter `pipestatus' */
+
+/**/
+static char **
+pipestatgetfn(Param pm)
+{
+    char **x = (char **) zhalloc((numpipestats + 1) * sizeof(char *));
+    char buf[20], **p;
+    int *q, i;
+
+    for (p = x, q = pipestats, i = numpipestats; i--; p++, q++) {
+	sprintf(buf, "%d", *q);
+	*p = dupstring(buf);
+    }
+    *p = NULL;
+
+    return x;
+}
+
+/* Function to get value for special parameter `pipestatus' */
+
+/**/
+static void
+pipestatsetfn(Param pm, char **x)
+{
+    int i;
+
+    for (i = 0; *x && i < MAX_PIPESTATS; i++, x++)
+	pipestats[i] = atoi(*x);
+    numpipestats = i;
+}
+
 /* We could probably replace the replenv with the actual code to *
  * do the replacing, since we've already scanned for the string. */
 
@@ -2000,28 +2735,33 @@ arrfixenv(char *s, char **t)
     int len_s;
     Param pm;
 
-    MUSTUSEHEAP("arrfixenv");
+    pm = (Param) paramtab->getnode(paramtab, s);
+    /*
+     * Only one level of a parameter can be exported.  Unless
+     * ALLEXPORT is set, this must be global.
+     */
     if (t == path)
 	cmdnamtab->emptytable(cmdnamtab);
-    u = zjoin(t, ':');
+    if (isset(ALLEXPORT) ? !!pm->old : pm->level)
+	return;
+    u = t ? zjoin(t, ':', 1) : "";
     len_s = strlen(s);
-    pm = (Param) paramtab->getnode(paramtab, s);
     for (ep = environ; *ep; ep++)
 	if (!strncmp(*ep, s, len_s) && (*ep)[len_s] == '=') {
-	    pm->env = replenv(*ep, u);
+	    pm->env = replenv(*ep, u, pm->flags);
 	    return;
 	}
     if (isset(ALLEXPORT))
 	pm->flags |= PM_EXPORTED;
     if (pm->flags & PM_EXPORTED)
-	pm->env = addenv(s, u);
+	pm->env = addenv(s, u, pm->flags);
 }
 
 /* Given *name = "foo", it searchs the environment for string *
  * "foo=bar", and returns a pointer to the beginning of "bar" */
 
 /**/
-char *
+mod_export char *
 zgetenv(char *name)
 {
     char **ep, *s, *t;
@@ -2034,11 +2774,25 @@ zgetenv(char *name)
     return NULL;
 }
 
+/**/
+static void
+copyenvstr(char *s, char *value, int flags)
+{
+    while (*s++) {
+	if ((*s = *value++) == Meta)
+	    *s = *value++ ^ 32;
+	if (flags & PM_LOWER)
+	    *s = tulower(*s);
+	else if (flags & PM_UPPER)
+	    *s = tuupper(*s);
+    }
+}
+
 /* Change the value of an existing environment variable */
 
 /**/
 char *
-replenv(char *e, char *value)
+replenv(char *e, char *value, int flags)
 {
     char **ep, *s;
     int len_value;
@@ -2051,9 +2805,7 @@ replenv(char *e, char *value)
 	    while (*s++ != '=');
 	    *ep = (char *) zrealloc(e, s - e + len_value + 1);
 	    s = s - e + *ep - 1;
-	    while (*s++)
-		if ((*s = *value++) == Meta)
-		    *s = *value++ ^ 32;
+	    copyenvstr(s, value, flags);
 	    return *ep;
 	}
     return NULL;
@@ -2064,7 +2816,7 @@ replenv(char *e, char *value)
 
 /**/
 static char *
-mkenvstr(char *name, char *value)
+mkenvstr(char *name, char *value, int flags)
 {
     char *str, *s;
     int len_name, len_value;
@@ -2076,9 +2828,7 @@ mkenvstr(char *name, char *value)
     strcpy(s, name);
     s += len_name;
     *s = '=';
-    while (*s++)
-	if ((*s = *value++) == Meta)
-	    *s = *value++ ^ 32;
+    copyenvstr(s, value, flags);
     return str;
 }
 
@@ -2089,7 +2839,7 @@ mkenvstr(char *name, char *value)
 
 /**/
 char *
-addenv(char *name, char *value)
+addenv(char *name, char *value, int flags)
 {
     char **ep, *s, *t;
     int num_env;
@@ -2100,7 +2850,7 @@ addenv(char *name, char *value)
 	for (s = *ep, t = name; *s && *s == *t; s++, t++);
 	if (*s == '=' && !*t) {
 	    zsfree(*ep);
-	    return *ep = mkenvstr(name, value);
+	    return *ep = mkenvstr(name, value, flags);
 	}
     }
 
@@ -2110,7 +2860,7 @@ addenv(char *name, char *value)
 
     /* Now add it at the end */
     ep = environ + num_env;
-    *ep = mkenvstr(name, value);
+    *ep = mkenvstr(name, value, flags);
     *(ep + 1) = NULL;
     return *ep;
 }
@@ -2133,11 +2883,11 @@ delenv(char *x)
 }
 
 /**/
-static void
-convbase(char *s, long v, int base)
+mod_export void
+convbase(char *s, zlong v, int base)
 {
     int digs = 0;
-    unsigned long x;
+    zulong x;
 
     if (v < 0)
 	*s++ = '-', v = -v;
@@ -2162,10 +2912,65 @@ convbase(char *s, long v, int base)
     }
 }
 
+/*
+ * Convert a floating point value for output.
+ * Unlike convbase(), this has its own internal storage and returns
+ * a value from the heap;
+ */
+
+/**/
+char *
+convfloat(double dval, int digits, int flags, FILE *fout)
+{
+    char fmt[] = "%.*e";
+
+    /*
+     * The difficulty with the buffer size is that a %f conversion
+     * prints all digits before the decimal point: with 64 bit doubles,
+     * that's around 310.  We can't check without doing some quite
+     * serious floating point operations we'd like to avoid.
+     * Then we are liable to get all the digits
+     * we asked for after the decimal point, or we should at least
+     * bargain for it.  So we just allocate 512 + digits.  This
+     * should work until somebody decides on 128-bit doubles.
+     */
+    if (!(flags & (PM_EFLOAT|PM_FFLOAT))) {
+	/*
+	 * Conversion from a floating point expression without using
+	 * a variable.  The best bet in this case just seems to be
+	 * to use the general %g format with something like the maximum
+	 * double precision.
+	 */
+	fmt[3] = 'g';
+	if (!digits)
+	    digits = 17;
+    } else {
+	if (flags & PM_FFLOAT)
+	    fmt[3] = 'f';
+	if (digits <= 0)
+	    digits = 10;
+	if (flags & PM_EFLOAT) {
+	    /*
+	     * Here, we are given the number of significant figures, but
+	     * %e wants the number of decimal places (unlike %g)
+	     */
+	    digits--;
+	}
+    }
+    if (fout) {
+	fprintf(fout, fmt, digits, dval);
+	return NULL;
+    } else {
+	VARARR(char, buf, 512 + digits);
+	sprintf(buf, fmt, digits, dval);
+	return dupstring(buf);
+    }
+}
+
 /* Start a parameter scope */
 
 /**/
-void
+mod_export void
 startparamscope(void)
 {
     locallevel++;
@@ -2174,7 +2979,7 @@ startparamscope(void)
 /* End a parameter scope: delete the parameters local to the scope. */
 
 /**/
-void
+mod_export void
 endparamscope(void)
 {
     locallevel--;
@@ -2186,6 +2991,187 @@ static void
 scanendscope(HashNode hn, int flags)
 {
     Param pm = (Param)hn;
-    if(pm->level > locallevel)
-	unsetparam_pm(pm, 0, 0);
+    if (pm->level > locallevel) {
+	if ((pm->flags & (PM_SPECIAL|PM_REMOVABLE)) == PM_SPECIAL) {
+	    /*
+	     * Removable specials are normal in that they can be removed
+	     * to reveal an ordinary parameter beneath.  Here we handle
+	     * non-removable specials, which were made local by stealth
+	     * (see newspecial code in typeset_single()).  In fact the
+	     * visible pm is always the same struct; the pm->old is
+	     * just a place holder for old data and flags.
+	     */
+	    Param tpm = pm->old;
+
+	    DPUTS(!tpm || PM_TYPE(pm->flags) != PM_TYPE(tpm->flags) ||
+		  !(tpm->flags & PM_SPECIAL),
+		  "BUG: in restoring scope of special parameter");
+	    pm->old = tpm->old;
+	    pm->flags = (tpm->flags & ~PM_NORESTORE);
+	    pm->level = tpm->level;
+	    pm->ct = tpm->ct;
+	    pm->env = tpm->env;
+
+	    if (!(tpm->flags & PM_NORESTORE))
+		switch (PM_TYPE(pm->flags)) {
+		case PM_SCALAR:
+		    pm->sets.cfn(pm, tpm->u.str);
+		    break;
+		case PM_INTEGER:
+		    pm->sets.ifn(pm, tpm->u.val);
+		    break;
+		case PM_EFLOAT:
+		case PM_FFLOAT:
+		    pm->sets.ffn(pm, tpm->u.dval);
+		    break;
+		case PM_ARRAY:
+		    pm->sets.afn(pm, tpm->u.arr);
+		    break;
+		case PM_HASHED:
+		    pm->sets.hfn(pm, tpm->u.hash);
+		    break;
+		}
+	    zfree(tpm, sizeof(*tpm));
+	} else
+	    unsetparam_pm(pm, 0, 0);
+    }
+}
+
+
+/**********************************/
+/* Parameter Hash Table Functions */
+/**********************************/
+
+/**/
+void
+freeparamnode(HashNode hn)
+{
+    Param pm = (Param) hn;
+ 
+    /* Since the second flag to unsetfn isn't used, I don't *
+     * know what its value should be.                       */
+    if (delunset)
+	pm->unsetfn(pm, 1);
+    zsfree(pm->nam);
+    /* If this variable was tied by the user, ename was ztrdup'd */
+    if (pm->flags & PM_TIED)
+	zsfree(pm->ename);
+    zfree(pm, sizeof(struct param));
+}
+
+/* Print a parameter */
+
+/**/
+mod_export 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_AUTOLOAD)
+	    printf("undefined ");
+	if (p->flags & PM_INTEGER)
+	    printf("integer ");
+	if (p->flags & (PM_EFLOAT|PM_FFLOAT))
+	    printf("float ");
+	else if (p->flags & PM_ARRAY)
+	    printf("array ");
+	else if (p->flags & PM_HASHED)
+	    printf("association ");
+	if (p->level)
+	    printf("local ");
+	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;
+    }
+
+    quotedzputs(p->nam, stdout);
+
+    if (p->flags & PM_AUTOLOAD) {
+	putchar('\n');
+	return;
+    }
+    if (printflags & PRINT_KV_PAIR)
+	putchar(' ');
+    else
+	putchar('=');
+
+    /* How the value is displayed depends *
+     * on the type of the parameter       */
+    switch (PM_TYPE(p->flags)) {
+    case PM_SCALAR:
+	/* string: simple output */
+	if (p->gets.cfn && (t = p->gets.cfn(p)))
+	    quotedzputs(t, stdout);
+	break;
+    case PM_INTEGER:
+	/* integer */
+#ifdef ZSH_64_BIT_TYPE
+	fputs(output64(p->gets.ifn(p)), stdout);
+#else
+	printf("%ld", p->gets.ifn(p));
+#endif
+	break;
+    case PM_EFLOAT:
+    case PM_FFLOAT:
+	/* float */
+	convfloat(p->gets.ffn(p), p->ct, p->flags, stdout);
+	break;
+    case PM_ARRAY:
+	/* array */
+	if (!(printflags & PRINT_KV_PAIR))
+	    putchar('(');
+	u = p->gets.afn(p);
+	if(*u) {
+	    quotedzputs(*u++, stdout);
+	    while (*u) {
+		putchar(' ');
+		quotedzputs(*u++, stdout);
+	    }
+	}
+	if (!(printflags & PRINT_KV_PAIR))
+	    putchar(')');
+	break;
+    case PM_HASHED:
+	/* association */
+	if (!(printflags & PRINT_KV_PAIR))
+	    putchar('(');
+	{
+            HashTable ht = p->gets.hfn(p);
+            if (ht)
+		scanhashtable(ht, 0, 0, PM_UNSET,
+			      ht->printnode, PRINT_KV_PAIR);
+	}
+	if (!(printflags & PRINT_KV_PAIR))
+	    putchar(')');
+	break;
+    }
+    if (printflags & PRINT_KV_PAIR)
+	putchar(' ');
+    else
+	putchar('\n');
 }
diff --git a/Src/parse.c b/Src/parse.c
index d42be2f2f..3ffed46d7 100644
--- a/Src/parse.c
+++ b/Src/parse.c
@@ -33,17 +33,17 @@
 /* != 0 if we are about to read a command word */
  
 /**/
-int incmdpos;
+mod_export int incmdpos;
  
 /* != 0 if we are in the middle of a [[ ... ]] */
  
 /**/
-int incond;
+mod_export int incond;
  
 /* != 0 if we are after a redirection (for ctxtlex only) */
  
 /**/
-int inredir;
+mod_export int inredir;
  
 /* != 0 if we are about to read a case pattern */
  
@@ -65,107 +65,477 @@ int infor;
 /**/
 struct heredocs *hdocs;
  
-/* used in arrays of lists instead of NULL pointers */
- 
+
+#define YYERROR(O)  { tok = LEXERR; ecused = (O); return 0; }
+#define YYERRORV(O) { tok = LEXERR; ecused = (O); return; }
+#define COND_ERROR(X,Y) do { \
+  zwarn(X,Y,0); \
+  herrflush(); \
+  if (noerrs != 2) \
+    errflag = 1; \
+  YYERROR(ecused) \
+} while(0)
+
+
+/* 
+ * Word code.
+ *
+ * For now we simply post-process the syntax tree produced by the
+ * parser. We compile it into a struct eprog. Some day the parser
+ * above should be changed to emit the word code directly.
+ *
+ * Word code layout:
+ *
+ *   WC_END
+ *     - end of program code
+ *
+ *   WC_LIST
+ *     - data contains type (sync, ...)
+ *     - follwed by code for this list
+ *     - if not (type & Z_END), followed by next WC_LIST
+ *
+ *   WC_SUBLIST
+ *     - data contains type (&&, ||, END) and flags (coprog, not)
+ *     - followed by code for sublist
+ *     - if not (type == END), followed by next WC_SUBLIST
+ *
+ *   WC_PIPE
+ *     - data contains type (end, mid) and LINENO
+ *     - if not (type == END), followed by offset to next WC_PIPE
+ *     - followed by command
+ *     - if not (type == END), followed by next WC_PIPE
+ *
+ *   WC_REDIR
+ *     - must precede command-code (or WC_ASSIGN)
+ *     - data contains type (<, >, ...)
+ *     - followed by fd1 and name from struct redir
+ *
+ *   WC_ASSIGN
+ *     - data contains type (scalar, array) and number of array-elements
+ *     - followed by name and value
+ *
+ *   WC_SIMPLE
+ *     - data contains the number of arguments (plus command)
+ *     - followed by strings
+ *
+ *   WC_SUBSH
+ *     - data unused
+ *     - followed by list
+ *
+ *   WC_CURSH
+ *     - data unused
+ *     - followed by list
+ *
+ *   WC_TIMED
+ *     - data contains type (followed by pipe or not)
+ *     - if (type == PIPE), followed by pipe
+ *
+ *   WC_FUNCDEF
+ *     - data contains offset to after body
+ *     - followed by number of names
+ *     - followed by names
+ *     - followed by offset to first string
+ *     - followed by length of string table
+ *     - followed by number of patterns for body
+ *     - follwoed by codes for body
+ *     - followed by strings for body
+ *
+ *   WC_FOR
+ *     - data contains type (list, ...) and offset to after body
+ *     - if (type == COND), followed by init, cond, advance expressions
+ *     - else if (type == PPARAM), followed by param name
+ *     - else if (type == LIST), followed by param name, num strings, strings
+ *     - followed by body
+ *
+ *   WC_SELECT
+ *     - data contains type (list, ...) and offset to after body
+ *     - if (type == PPARAM), followed by param name
+ *     - else if (type == LIST), followed by param name, num strings, strings
+ *     - followed by body
+ *
+ *   WC_WHILE
+ *     - data contains type (while, until) and ofsset to after body
+ *     - followed by condition
+ *     - followed by body
+ *
+ *   WC_REPEAT
+ *     - data contains offset to after body
+ *     - followed by number-string
+ *     - followed by body
+ *
+ *   WC_CASE
+ *     - first CASE is always of type HEAD, data contains offset to esac
+ *     - after that CASEs of type OR (;;) and AND (;&), data is offset to
+ *       next case
+ *     - each OR/AND case is followed by pattern, pattern-number, list
+ *
+ *   WC_IF
+ *     - first IF is of type HEAD, data contains offset to fi
+ *     - after that IFs of type IF, ELIF, ELSE, data is offset to next
+ *     - each non-HEAD is followed by condition (only IF, ELIF) and body
+ *
+ *   WC_COND
+ *     - data contains type
+ *     - if (type == AND/OR), data contains offset to after this one,
+ *       followed by two CONDs
+ *     - else if (type == NOT), followed by COND
+ *     - else if (type == MOD), followed by name and strings
+ *     - else if (type == MODI), followed by name, left, right
+ *     - else if (type == STR[N]EQ), followed by left, right, pattern-number
+ *     - else if (has two args) followed by left, right
+ *     - else followed by string
+ *
+ *   WC_ARITH
+ *     - followed by string (there's only one)
+ *
+ *   WC_AUTOFN
+ *     - only used by the autoload builtin
+ *
+ * Lists and sublists may also be simplified, indicated by the presence
+ * of the Z_SIMPLE or WC_SUBLIST_SIMPLE flags. In this case they are only
+ * followed by a slot containing the line number, not by a WC_SUBLIST or
+ * WC_PIPE, respectively. The real advantage of simplified lists and
+ * sublists is that they can be executed faster, see exec.c. In the
+ * parser, the test if a list can be simplified is done quite simply
+ * by passing a int* around which gets set to non-zero if the thing
+ * just parsed is `complex', i.e. may need to be run by forking or 
+ * some such.
+ *
+ * In each of the above, strings are encoded as one word code. For empty
+ * strings this is the bit pattern 11x, the lowest bit is non-zero if the
+ * string contains tokens and zero otherwise (this is true for the other
+ * ways to encode strings, too). For short strings (one to three
+ * characters), this is the marker 01x with the 24 bits above that
+ * containing the characters. Longer strings are encoded as the offset
+ * into the strs character array stored in the eprog struct shifted by
+ * two and ored with the bit pattern 0x.
+ * The ecstr() function that adds the code for a string uses a simple
+ * list of strings already added so that long strings are encoded only
+ * once.
+ *
+ * Note also that in the eprog struct the pattern, code, and string
+ * arrays all point to the same memory block.
+ *
+ *
+ * To make things even faster in future versions, we could not only 
+ * test if the strings contain tokens, but instead what kind of
+ * expansions need to be done on strings. In the execution code we
+ * could then use these flags for a specialized version of prefork()
+ * to avoid a lot of string parsing and some more string duplication.
+ */
+
+/**/
+int eclen, ecused, ecnpats;
+/**/
+Wordcode ecbuf;
+/**/
+Eccstr ecstrs;
 /**/
-struct list dummy_list;
+int ecsoffs, ecssub, ecnfunc;
 
-#define YYERROR  { tok = LEXERR; return NULL; }
-#define YYERRORV { tok = LEXERR; return; }
-#define COND_ERROR(X,Y) do{herrflush();zerr(X,Y,0);YYERROR}while(0)
+/* Adjust pointers in here-doc structs. */
 
-#define make_list()     allocnode(N_LIST)
-#define make_sublist()  allocnode(N_SUBLIST)
-#define make_pline()    allocnode(N_PLINE)
-#define make_cmd()      allocnode(N_CMD)
-#define make_forcmd()   allocnode(N_FOR)
-#define make_casecmd()  allocnode(N_CASE)
-#define make_ifcmd()    allocnode(N_IF)
-#define make_whilecmd() allocnode(N_WHILE)
-#define make_varnode()  allocnode(N_VARASG)
-#define make_cond()     allocnode(N_COND)
+static void
+ecadjusthere(int p, int d)
+{
+    struct heredocs *h;
+
+    for (h = hdocs; h; h = h->next)
+	if (h->pc >= p)
+	    h->pc += d;
+}
+
+/* Insert n free code-slots at position p. */
+
+static void
+ecispace(int p, int n)
+{
+    int m;
+
+    if ((eclen - ecused) < n) {
+	int a = (n > 256 ? n : 256);
+
+	ecbuf = (Wordcode) hrealloc((char *) ecbuf, eclen * sizeof(wordcode),
+				    (eclen + a) * sizeof(wordcode));
+	eclen += a;
+    }
+    if ((m = ecused - p) > 0)
+	memmove(ecbuf + p + n, ecbuf + p, m * sizeof(wordcode));
+    ecused += n;
+    ecadjusthere(p, n);
+}
+
+/* Add one wordcode. */
+
+static int
+ecadd(wordcode c)
+{
+    if ((eclen - ecused) < 1) {
+	ecbuf = (Wordcode) hrealloc((char *) ecbuf, eclen * sizeof(wordcode),
+				    (eclen + 256) * sizeof(wordcode));
+	eclen += 256;
+    }
+    ecbuf[ecused] = c;
+    ecused++;
+
+    return ecused - 1;
+}
+
+/* Delete a wordcode. */
+
+static void
+ecdel(int p)
+{
+    int n = ecused - p - 1;
+
+    if (n > 0)
+	memmove(ecbuf + p, ecbuf + p + 1, n * sizeof(wordcode));
+    ecused--;
+    ecadjusthere(p, -1);
+}
+
+/* Build the wordcode for a string. */
+
+static wordcode
+ecstrcode(char *s)
+{
+    int l, t = has_token(s);
+
+    if ((l = strlen(s) + 1) && l <= 4) {
+	wordcode c = (t ? 3 : 2);
+	switch (l) {
+	case 4: c |= ((wordcode) STOUC(s[2])) << 19;
+	case 3: c |= ((wordcode) STOUC(s[1])) << 11;
+	case 2: c |= ((wordcode) STOUC(s[0])) <<  3; break;
+	case 1: c = (t ? 7 : 6); break;
+	}
+	return c;
+    } else {
+	Eccstr p, q = NULL;
+
+	for (p = ecstrs; p; q = p, p = p->next)
+	    if (p->nfunc == ecnfunc && !strcmp(s, p->str))
+		return p->offs;
+
+	p = (Eccstr) zhalloc(sizeof(*p));
+	p->next = NULL;
+	if (q)
+	    q->next = p;
+	else
+	    ecstrs = p;
+	p->offs = ((ecsoffs - ecssub) << 2) | (t ? 1 : 0);
+	p->str = s;
+	p->nfunc = ecnfunc;
+	ecsoffs += l;
+
+	return p->offs;
+    }
+}
+
+static int
+ecstr(char *s)
+{
+    return ecadd(ecstrcode(s));
+}
+
+
+#define par_save_list(C) \
+    do { \
+        int eu = ecused; \
+        par_list(C); \
+        if (eu == ecused) ecadd(WCB_END()); \
+    } while (0)
+#define par_save_list1(C) \
+    do { \
+        int eu = ecused; \
+        par_list1(C); \
+        if (eu == ecused) ecadd(WCB_END()); \
+    } while (0)
+
+
+/* Initialise wordcode buffer. */
+
+static void
+init_parse(void)
+{
+    ecbuf = (Wordcode) zhalloc((eclen = 256) * sizeof(wordcode));
+    ecused = 0;
+    ecstrs = NULL;
+    ecsoffs = ecnpats = 0;
+    ecssub = 0;
+    ecnfunc = 0;
+}
+
+/* Build eprog. */
+
+static Eprog
+bld_eprog(void)
+{
+    Eprog ret;
+    Eccstr p;
+    char *q;
+    int l;
+
+    ecadd(WCB_END());
+
+    ret = (Eprog) zhalloc(sizeof(*ret));
+    ret->len = ((ecnpats * sizeof(Patprog)) +
+		(ecused * sizeof(wordcode)) +
+		ecsoffs);
+    ret->npats = ecnpats;
+    ret->pats = (Patprog *) zhalloc(ret->len);
+    ret->prog = (Wordcode) (ret->pats + ecnpats);
+    ret->strs = (char *) (ret->prog + ecused);
+    ret->shf = NULL;
+    ret->flags = EF_HEAP;
+    ret->dump = NULL;
+    for (l = 0; l < ecnpats; l++)
+	ret->pats[l] = dummy_patprog1;
+    memcpy(ret->prog, ecbuf, ecused * sizeof(wordcode));
+    for (p = ecstrs, q = ret->strs; p; p = p->next, q += l) {
+	l = strlen(p->str) + 1;
+	memcpy(q, p->str, l);
+    }
+    return ret;
+}
 
 /*
  * event	: ENDINPUT
  *			| SEPER
  *			| sublist [ SEPER | AMPER | AMPERBANG ]
  */
+
 /**/
-List
+Eprog
 parse_event(void)
 {
     tok = ENDINPUT;
     incmdpos = 1;
     yylex();
-    return par_event();
+    init_parse();
+    return ((par_event()) ? bld_eprog() : NULL);
 }
 
 /**/
-static List
+static int
 par_event(void)
 {
-    Sublist sl;
-    List l = NULL;
+    int r = 0, p, c = 0;
 
     while (tok == SEPER) {
 	if (isnewlin > 0)
-	    return NULL;
+	    return 0;
 	yylex();
     }
     if (tok == ENDINPUT)
-	return NULL;
-    if ((sl = par_sublist()))
+	return 0;
+
+    p = ecadd(0);
+
+    if (par_sublist(&c)) {
 	if (tok == ENDINPUT) {
-	    l = (List) make_list();
-	    l->type = Z_SYNC;
-	    l->left = sl;
+	    set_list_code(p, Z_SYNC, c);
+	    r = 1;
 	} else if (tok == SEPER) {
-	    l = (List) make_list();
-	    l->type = Z_SYNC;
-	    l->left = sl;
+	    set_list_code(p, Z_SYNC, c);
 	    if (isnewlin <= 0)
 		yylex();
+	    r = 1;
 	} else if (tok == AMPER) {
-	    l = (List) make_list();
-	    l->type = Z_ASYNC;
-	    l->left = sl;
+	    set_list_code(p, Z_ASYNC, c);
 	    yylex();
+	    r = 1;
 	} else if (tok == AMPERBANG) {
-	    l = (List) make_list();
-	    l->type = Z_ASYNC | Z_DISOWN;
-	    l->left = sl;
+	    set_list_code(p, (Z_ASYNC | Z_DISOWN), c);
 	    yylex();
-	} else
-	    l = NULL;
-    if (!l) {
+	    r = 1;
+	}
+    }
+    if (!r) {
 	if (errflag) {
-	    yyerror();
-	    return NULL;
+	    yyerror(0);
+	    ecused--;
+	    return 0;
 	}
+	yyerror(1);
 	herrflush();
-	yyerror();
-	return NULL;
+	if (noerrs != 2)
+	    errflag = 1;
+	ecused--;
+	return 0;
     } else {
-	l->right = par_event();
+	int oec = ecused;
+
+	par_event();
+	if (ecused == oec)
+	    ecbuf[p] |= wc_bdata(Z_END);
     }
-    return l;
+    return 1;
 }
 
 /**/
-List
+mod_export Eprog
 parse_list(void)
 {
-    List ret;
+    int c = 0;
 
     tok = ENDINPUT;
     incmdpos = 1;
     yylex();
-    ret = par_list();
-    if (tok == LEXERR) {
-	yyerror();
+    init_parse();
+    par_list(&c);
+#if 0 
+   if (tok == LEXERR)
+#endif
+   if (tok != ENDINPUT) {
+	yyerror(0);
 	return NULL;
     }
-    return ret;
+    return bld_eprog();
+}
+
+/**/
+mod_export Eprog
+parse_cond(void)
+{
+    init_parse();
+
+    if (!par_cond())
+	return NULL;
+
+    return bld_eprog();
+}
+
+/* This adds a list wordcode. The important bit about this is that it also
+ * tries to optimise this to a Z_SIMPLE list code. */
+
+/**/
+static void
+set_list_code(int p, int type, int complex)
+{
+    if (!complex && (type == Z_SYNC || type == (Z_SYNC | Z_END)) &&
+	WC_SUBLIST_TYPE(ecbuf[p + 1]) == WC_SUBLIST_END) {
+	int ispipe = !(WC_SUBLIST_FLAGS(ecbuf[p + 1]) & WC_SUBLIST_SIMPLE);
+	ecbuf[p] = WCB_LIST((type | Z_SIMPLE), ecused - 2 - p);
+	ecdel(p + 1);
+	if (ispipe)
+	    ecbuf[p + 1] = WC_PIPE_LINENO(ecbuf[p + 1]);
+    } else
+	ecbuf[p] = WCB_LIST(type, 0);
+}
+
+/* The same for sublists. */
+
+/**/
+static void
+set_sublist_code(int p, int type, int flags, int skip, int complex)
+{
+    if (complex)
+	ecbuf[p] = WCB_SUBLIST(type, flags, skip);
+    else {
+	ecbuf[p] = WCB_SUBLIST(type, (flags | WC_SUBLIST_SIMPLE), skip);
+	ecbuf[p + 1] = WC_PIPE_LINENO(ecbuf[p + 1]);
+    }
 }
 
 /*
@@ -173,46 +543,60 @@ parse_list(void)
  */
 
 /**/
-static List
-par_list(void)
+static int
+par_list(int *complex)
 {
-    Sublist sl;
-    List l = NULL;
+    int p, lp = -1, c;
+
+ rec:
 
     while (tok == SEPER)
 	yylex();
-    if ((sl = par_sublist()))
+
+    p = ecadd(0);
+    c = 0;
+
+    if (par_sublist(&c)) {
+	*complex |= c;
 	if (tok == SEPER || tok == AMPER || tok == AMPERBANG) {
-	    l = (List) make_list();
-	    l->left = sl;
-	    l->type = (tok == SEPER) ? Z_SYNC :
-		(tok == AMPER) ? Z_ASYNC : Z_ASYNC | Z_DISOWN;
+	    if (tok != SEPER)
+		*complex = 1;
+	    set_list_code(p, ((tok == SEPER) ? Z_SYNC :
+			      (tok == AMPER) ? Z_ASYNC :
+			      (Z_ASYNC | Z_DISOWN)), c);
 	    incmdpos = 1;
 	    do {
 		yylex();
 	    } while (tok == SEPER);
-	    l->right = par_list();
-	} else {
-	    l = (List) make_list();
-	    l->left = sl;
-	    l->type = Z_SYNC;
+	    lp = p;
+	    goto rec;
+	} else
+	    set_list_code(p, (Z_SYNC | Z_END), c);
+	return 1;
+    } else {
+	ecused--;
+	if (lp >= 0) {
+	    ecbuf[lp] |= wc_bdata(Z_END);
+	    return 1;
 	}
-    return l;
+	return 0;
+    }
 }
 
 /**/
-static List
-par_list1(void)
+static int
+par_list1(int *complex)
 {
-    Sublist sl;
-    List l = NULL;
+    int p = ecadd(0), c = 0;
 
-    if ((sl = par_sublist())) {
-	l = (List) make_list();
-	l->type = Z_SYNC;
-	l->left = sl;
+    if (par_sublist(&c)) {
+	set_list_code(p, (Z_SYNC | Z_END), c);
+	*complex |= c;
+	return 1;
+    } else {
+	ecused--;
+	return 0;
     }
-    return l;
 }
 
 /*
@@ -220,24 +604,37 @@ par_list1(void)
  */
 
 /**/
-static Sublist
-par_sublist(void)
+static int
+par_sublist(int *complex)
 {
-    Sublist sl;
+    int f, p, c = 0;
 
-    if ((sl = par_sublist2()))
+    p = ecadd(0);
+
+    if ((f = par_sublist2(&c)) != -1) {
+	int e = ecused;
+
+	*complex |= c;
 	if (tok == DBAR || tok == DAMPER) {
-	    int qtok = tok;
+	    int qtok = tok, sl;
 
 	    cmdpush(tok == DBAR ? CS_CMDOR : CS_CMDAND);
 	    yylex();
 	    while (tok == SEPER)
 		yylex();
-	    sl->right = par_sublist();
-	    sl->type = (qtok == DBAR) ? ORNEXT : ANDNEXT;
+	    sl = par_sublist(complex);
+	    set_sublist_code(p, (sl ? (qtok == DBAR ?
+				       WC_SUBLIST_OR : WC_SUBLIST_AND) :
+				 WC_SUBLIST_END),
+			     f, (e - 1 - p), c);
 	    cmdpop();
-	}
-    return sl;
+	} else
+	    set_sublist_code(p, WC_SUBLIST_END, f, (e - 1 - p), c);
+	return 1;
+    } else {
+	ecused--;
+	return 0;
+    }
 }
 
 /*
@@ -245,24 +642,24 @@ par_sublist(void)
  */
 
 /**/
-static Sublist
-par_sublist2(void)
+static int
+par_sublist2(int *complex)
 {
-    Sublist sl;
-    Pline p;
+    int f = 0;
 
-    sl = (Sublist) make_sublist();
     if (tok == COPROC) {
-	sl->flags |= PFLAG_COPROC;
+	*complex = 1;
+	f |= WC_SUBLIST_COPROC;
 	yylex();
     } else if (tok == BANG) {
-	sl->flags |= PFLAG_NOT;
+	*complex = 1;
+	f |= WC_SUBLIST_NOT;
 	yylex();
     }
-    if (!(p = par_pline()) && !sl->flags)
-	return NULL;
-    sl->left = p;
-    return sl;
+    if (!par_pline(complex) && !f)
+	return -1;
+
+    return f;
 }
 
 /*
@@ -270,48 +667,53 @@ par_sublist2(void)
  */
 
 /**/
-static Pline
-par_pline(void)
+static int
+par_pline(int *complex)
 {
-    Cmd c;
-    Pline p, p2;
+    int p, line = lineno;
 
-    if (!(c = par_cmd()))
-	return NULL;
+    p = ecadd(0);
+
+    if (!par_cmd(complex)) {
+	ecused--;
+	return 0;
+    }
     if (tok == BAR) {
+	*complex = 1;
 	cmdpush(CS_PIPE);
 	yylex();
 	while (tok == SEPER)
 	    yylex();
-	p2 = par_pline();
+	ecbuf[p] = WCB_PIPE(WC_PIPE_MID, (line >= 0 ? line + 1 : 0));
+	ecispace(p + 1, 1);
+	ecbuf[p + 1] = ecused - 1 - p;
+	par_pline(complex);
 	cmdpop();
-	p = (Pline) make_pline();
-	p->left = c;
-	p->right = p2;
-	p->type = PIPE;
-	return p;
+	return 1;
     } else if (tok == BARAMP) {
-	struct redir *rdr = (struct redir *)allocnode(N_REDIR);
+	int r;
+
+	for (r = p + 1; wc_code(ecbuf[r]) == WC_REDIR; r += 3);
 
-	rdr->type = MERGEOUT;
-	rdr->fd1 = 2;
-	rdr->name = dupstring("1");
-	addlinknode(c->redir, rdr);
+	ecispace(r, 3);
+	ecbuf[r] = WCB_REDIR(MERGEOUT);
+	ecbuf[r + 1] = 2;
+	ecbuf[r + 2] = ecstrcode("1");
 
+	*complex = 1;
 	cmdpush(CS_ERRPIPE);
 	yylex();
-	p2 = par_pline();
+	while (tok == SEPER)
+	    yylex();
+	ecbuf[p] = WCB_PIPE(WC_PIPE_MID, (line >= 0 ? line + 1 : 0));
+	ecispace(p + 1, 1);
+	ecbuf[p + 1] = ecused - 1 - p;
+	par_pline(complex);
 	cmdpop();
-	p = (Pline) make_pline();
-	p->left = c;
-	p->right = p2;
-	p->type = PIPE;
-	return p;
+	return 1;
     } else {
-	p = (Pline) make_pline();
-	p->left = c;
-	p->type = END;
-	return p;
+	ecbuf[p] = WCB_PIPE(WC_PIPE_END, (line >= 0 ? line + 1 : 0));
+	return 1;
     }
 }
 
@@ -321,96 +723,116 @@ par_pline(void)
  */
 
 /**/
-static Cmd
-par_cmd(void)
+static int
+par_cmd(int *complex)
 {
-    Cmd c;
+    int r, nr = 0;
+
+    r = ecused;
 
-    c = (Cmd) make_cmd();
-    c->lineno = lineno;
-    c->args = newlinklist();
-    c->redir = newlinklist();
-    c->vars = newlinklist();
-    while (IS_REDIROP(tok))
-	par_redir(c->redir);
+    if (IS_REDIROP(tok)) {
+	*complex = 1;
+	while (IS_REDIROP(tok)) {
+	    nr++;
+	    par_redir(&r);
+	}
+    }
     switch (tok) {
     case FOR:
 	cmdpush(CS_FOR);
-	par_for(c);
+	par_for(complex);
 	cmdpop();
 	break;
     case FOREACH:
 	cmdpush(CS_FOREACH);
-	par_for(c);
+	par_for(complex);
 	cmdpop();
 	break;
     case SELECT:
+	*complex = 1;
 	cmdpush(CS_SELECT);
-	par_for(c);
+	par_for(complex);
 	cmdpop();
 	break;
     case CASE:
 	cmdpush(CS_CASE);
-	par_case(c);
+	par_case(complex);
 	cmdpop();
 	break;
     case IF:
-	par_if(c);
+	par_if(complex);
 	break;
     case WHILE:
 	cmdpush(CS_WHILE);
-	par_while(c);
+	par_while(complex);
 	cmdpop();
 	break;
     case UNTIL:
 	cmdpush(CS_UNTIL);
-	par_while(c);
+	par_while(complex);
 	cmdpop();
 	break;
     case REPEAT:
 	cmdpush(CS_REPEAT);
-	par_repeat(c);
+	par_repeat(complex);
 	cmdpop();
 	break;
     case INPAR:
+	*complex = 1;
 	cmdpush(CS_SUBSH);
-	par_subsh(c);
+	par_subsh(complex);
 	cmdpop();
 	break;
     case INBRACE:
 	cmdpush(CS_CURSH);
-	par_subsh(c);
+	par_subsh(complex);
 	cmdpop();
 	break;
     case FUNC:
 	cmdpush(CS_FUNCDEF);
-	par_funcdef(c);
+	par_funcdef();
 	cmdpop();
 	break;
     case TIME:
-	par_time(c);
+	*complex = 1;
+	par_time();
 	break;
     case DINBRACK:
 	cmdpush(CS_COND);
-	par_dinbrack(c);
+	par_dinbrack();
 	cmdpop();
 	break;
     case DINPAR:
-	c->type = CARITH;
-	addlinknode(c->args, tokstr);
+	ecadd(WCB_ARITH());
+	ecstr(tokstr);
 	yylex();
 	break;
     default:
-	if (!par_simple(c))
-	    return NULL;
+	{
+	    int sr;
+
+	    if (!(sr = par_simple(complex, nr))) {
+		if (!nr)
+		    return 0;
+	    } else {
+		/* Three codes per redirection. */
+		if (sr > 1) {
+		    *complex = 1;
+		    r += (sr - 1) * 3;
+		}
+	    }
+	}
 	break;
     }
-    while (IS_REDIROP(tok))
-	par_redir(c->redir);
+    if (IS_REDIROP(tok)) {
+	*complex = 1;
+	while (IS_REDIROP(tok))
+	    par_redir(&r);
+    }
     incmdpos = 1;
     incasepat = 0;
     incond = 0;
-    return c;
+    return 1;
 }
 
 /*
@@ -421,82 +843,95 @@ par_cmd(void)
 
 /**/
 static void
-par_for(Cmd c)
+par_for(int *complex)
 {
-    Forcmd f;
-    int csh = (tok == FOREACH);
+    int oecused = ecused, csh = (tok == FOREACH), p, sel = (tok == SELECT);
+    int type;
+
+    p = ecadd(0);
 
-    f = (Forcmd) make_forcmd();
-    c->type = (tok == SELECT) ? CSELECT : CFOR;
     incmdpos = 0;
     infor = tok == FOR ? 2 : 0;
     yylex();
     if (tok == DINPAR) {
 	yylex();
 	if (tok != DINPAR)
-	    YYERRORV;
-	f->name = tokstr;
+	    YYERRORV(oecused);
+	ecstr(tokstr);
 	yylex();
 	if (tok != DINPAR)
-	    YYERRORV;
-	f->condition = tokstr;
+	    YYERRORV(oecused);
+	ecstr(tokstr);
 	yylex();
 	if (tok != DOUTPAR)
-	    YYERRORV;
-	f->advance = tokstr;
+	    YYERRORV(oecused);
+	ecstr(tokstr);
 	infor = 0;
 	incmdpos = 1;
 	yylex();
+	type = WC_FOR_COND;
     } else {
 	infor = 0;
 	if (tok != STRING || !isident(tokstr))
-	    YYERRORV;
-	f->name = tokstr;
+	    YYERRORV(oecused);
+	ecstr(tokstr);
 	incmdpos = 1;
 	yylex();
 	if (tok == STRING && !strcmp(tokstr, "in")) {
-	    f->inflag = 1;
+	    int np, n;
+
 	    incmdpos = 0;
 	    yylex();
-	    c->args = par_wordlist();
+	    np = ecadd(0);
+	    n = par_wordlist();
 	    if (tok != SEPER)
-		YYERRORV;
+		YYERRORV(oecused);
+	    ecbuf[np] = n;
+	    type = (sel ? WC_SELECT_LIST : WC_FOR_LIST);
 	} else if (tok == INPAR) {
-	    f->inflag = 1;
+	    int np, n;
+
 	    incmdpos = 0;
 	    yylex();
-	    c->args = par_nl_wordlist();
+	    np = ecadd(0);
+	    n = par_nl_wordlist();
 	    if (tok != OUTPAR)
-		YYERRORV;
+		YYERRORV(oecused);
+	    ecbuf[np] = n;
 	    incmdpos = 1;
 	    yylex();
-	}
+	    type = (sel ? WC_SELECT_LIST : WC_FOR_LIST);
+	} else
+	    type = (sel ? WC_SELECT_PPARAM : WC_FOR_PPARAM);
     }
     incmdpos = 1;
     while (tok == SEPER)
 	yylex();
     if (tok == DO) {
 	yylex();
-	f->list = par_list();
+	par_save_list(complex);
 	if (tok != DONE)
-	    YYERRORV;
+	    YYERRORV(oecused);
 	yylex();
     } else if (tok == INBRACE) {
 	yylex();
-	f->list = par_list();
+	par_save_list(complex);
 	if (tok != OUTBRACE)
-	    YYERRORV;
+	    YYERRORV(oecused);
 	yylex();
     } else if (csh || isset(CSHJUNKIELOOPS)) {
-	f->list = par_list();
+	par_save_list(complex);
 	if (tok != ZEND)
-	    YYERRORV;
+	    YYERRORV(oecused);
 	yylex();
     } else if (unset(SHORTLOOPS)) {
-	YYERRORV;
+	YYERRORV(oecused);
     } else
-	f->list = par_list1();
-    c->u.forcmd = f;
+	par_save_list1(complex);
+
+    ecbuf[p] = (sel ?
+		WCB_SELECT(type, ecused - 1 - p) :
+		WCB_FOR(type, ecused - 1 - p));
 }
 
 /*
@@ -508,35 +943,29 @@ par_for(Cmd c)
 
 /**/
 static void
-par_case(Cmd c)
+par_case(int *complex)
 {
-    int brflag;
-    LinkList pats, lists;
-    int n = 1;
-    char **pp;
-    List *ll;
-    LinkNode no;
-    struct casecmd *cc;
+    int oecused = ecused, brflag, p, pp, n = 1, type;
+
+    p = ecadd(0);
 
-    c->type = CCASE;
     incmdpos = 0;
     yylex();
     if (tok != STRING)
-	YYERRORV;
-    pats = newlinklist();
-    addlinknode(pats, tokstr);
+	YYERRORV(oecused);
+    ecstr(tokstr);
+
     incmdpos = 1;
     yylex();
     while (tok == SEPER)
 	yylex();
     if (!(tok == STRING && !strcmp(tokstr, "in")) && tok != INBRACE)
-	YYERRORV;
+	YYERRORV(oecused);
     brflag = (tok == INBRACE);
     incasepat = 1;
     incmdpos = 0;
     yylex();
-    cc = c->u.casecmd = (struct casecmd *)make_casecmd();
-    lists = newlinklist();
+
     for (;;) {
 	char *str;
 
@@ -545,14 +974,13 @@ par_case(Cmd c)
 	if (tok == OUTBRACE)
 	    break;
 	if (tok != STRING)
-	    YYERRORV;
+	    YYERRORV(oecused);
 	if (!strcmp(tokstr, "esac"))
 	    break;
-	str = ncalloc(strlen(tokstr) + 2);
-	*str = ';';
-	strcpy(str + 1, tokstr);
+	str = dupstring(tokstr);
 	incasepat = 0;
 	incmdpos = 1;
+	type = WC_CASE_OR;
 	for (;;) {
 	    yylex();
 	    if (tok == OUTPAR) {
@@ -566,7 +994,7 @@ par_case(Cmd c)
 
 		incasepat = 1;
 		incmdpos = 0;
-		str2 = ncalloc(sl + 2);
+		str2 = hcalloc(sl + 2);
 		strcpy(str2, str);
 		str2[sl] = Bar;
 		str2[sl+1] = '\0';
@@ -574,12 +1002,12 @@ par_case(Cmd c)
 	    } else {
 		int sl = strlen(str);
 
-		if (str[sl - 1] != Bar) {
+		if (!sl || str[sl - 1] != Bar) {
 		    /* POSIX allows (foo*) patterns */
 		    int pct;
 		    char *s;
 
-		    for (s = str + 1, pct = 0; *s; s++) {
+		    for (s = str, pct = 0; *s; s++) {
 			if (*s == Inpar)
 			    pct++;
 			if (!pct)
@@ -590,54 +1018,53 @@ par_case(Cmd c)
 				    chuck(s+1);
 			    if (*s == Bar || *s == Outpar)
 				while (iblank(s[-1]) &&
-				       (s < str+2 || s[-2] != Meta))
+				       (s < str + 1 || s[-2] != Meta))
 				    chuck(--s);
 			}
 			if (*s == Outpar)
 			    pct--;
 		    }
-		    if (*s || pct || s == str + 1)
-			YYERRORV;
+		    if (*s || pct || s == str)
+			YYERRORV(oecused);
+		    /* Simplify pattern by removing surrounding (...) */
+		    sl = strlen(str);
+		    DPUTS(*str != Inpar || str[sl - 1] != Outpar,
+			  "BUG: strange case pattern");
+		    str[sl - 1] = '\0';
+		    chuck(str);
 		    break;
 		} else {
 		    char *str2;
 
 		    if (tok != STRING)
-			YYERRORV;
-		    str2 = ncalloc(sl + strlen(tokstr) + 1);
+			YYERRORV(oecused);
+		    str2 = hcalloc(sl + strlen(tokstr) + 1);
 		    strcpy(str2, str);
 		    strcpy(str2 + sl, tokstr);
 		    str = str2;
 		}
 	    }
 	}
-	addlinknode(pats, str);
-	addlinknode(lists, par_list());
+	pp = ecadd(0);
+	ecstr(str);
+	ecadd(ecnpats++);
+	par_save_list(complex);
 	n++;
+	if (tok == SEMIAMP)
+	    type = WC_CASE_AND;
+	ecbuf[pp] = WCB_CASE(type, ecused - 1 - pp);
 	if ((tok == ESAC && !brflag) || (tok == OUTBRACE && brflag))
 	    break;
-	if(tok == SEMIAMP)
-	    *str = '&';
-	else if (tok != DSEMI)
-	    YYERRORV;
+	if (tok != DSEMI && tok != SEMIAMP)
+	    YYERRORV(oecused);
 	incasepat = 1;
 	incmdpos = 0;
 	yylex();
     }
-
     incmdpos = 1;
     yylex();
 
-    cc->pats = (char **)alloc((n + 1) * sizeof(char *));
-
-    for (pp = cc->pats, no = firstnode(pats); no; incnode(no))
-	*pp++ = (char *)getdata(no);
-    *pp = NULL;
-    cc->lists = (List *) alloc((n + 1) * sizeof(List));
-    for (ll = cc->lists, no = firstnode(lists); no; incnode(no), ll++)
-	if (!(*ll = (List) getdata(no)))
-	    *ll = &dummy_list;
-    *ll = NULL;
+    ecbuf[p] = WCB_CASE(WC_CASE_HEAD, ecused - 1 - p);
 }
 
 /*
@@ -649,20 +1076,13 @@ par_case(Cmd c)
 
 /**/
 static void
-par_if(Cmd c)
+par_if(int *complex)
 {
-    struct ifcmd *i;
-    int xtok;
+    int oecused = ecused, xtok, p, pp, type, usebrace = 0;
     unsigned char nc;
-    LinkList ifsl, thensl;
-    LinkNode no;
-    int ni = 0, nt = 0, usebrace = 0;
-    List l, *ll;
 
-    ifsl = newlinklist();
-    thensl = newlinklist();
+    p = ecadd(0);
 
-    c->type = CIF;
     for (;;) {
 	xtok = tok;
 	cmdpush(xtok == IF ? CS_IF : CS_ELIF);
@@ -675,10 +1095,11 @@ par_if(Cmd c)
 	    yylex();
 	if (!(xtok == IF || xtok == ELIF)) {
 	    cmdpop();
-	    YYERRORV;
+	    YYERRORV(oecused);
 	}
-	addlinknode(ifsl, par_list());
-	ni++;
+	pp = ecadd(0);
+	type = (xtok == IF ? WC_IF_IF : WC_IF_ELIF);
+	par_save_list(complex);
 	incmdpos = 1;
 	while (tok == SEPER)
 	    yylex();
@@ -689,79 +1110,63 @@ par_if(Cmd c)
 	    cmdpop();
 	    cmdpush(nc);
 	    yylex();
-	    addlinknode(thensl, par_list());
-	    nt++;
+	    par_save_list(complex);
+	    ecbuf[pp] = WCB_IF(type, ecused - 1 - pp);
 	    incmdpos = 1;
 	    cmdpop();
-	} else {
-	    if (tok == INBRACE) {
-		usebrace = 1;
-		cmdpop();
-		cmdpush(nc);
-		yylex();
-		l = par_list();
-		if (tok != OUTBRACE) {
-		    cmdpop();
-		    YYERRORV;
-		}
-		addlinknode(thensl, l);
-		nt++;
-		yylex();
-		incmdpos = 1;
-		if (tok == SEPER)
-		    break;
-		cmdpop();
-	    } else if (unset(SHORTLOOPS)) {
-		cmdpop();
-		YYERRORV;
-	    } else {
+	} else if (tok == INBRACE) {
+	    usebrace = 1;
+	    cmdpop();
+	    cmdpush(nc);
+	    yylex();
+	    par_save_list(complex);
+	    if (tok != OUTBRACE) {
 		cmdpop();
-		cmdpush(nc);
-		addlinknode(thensl, par_list1());
-		nt++;
-		incmdpos = 1;
-		break;
+		YYERRORV(oecused);
 	    }
+	    ecbuf[pp] = WCB_IF(type, ecused - 1 - pp);
+	    yylex();
+	    incmdpos = 1;
+	    if (tok == SEPER)
+		break;
+	    cmdpop();
+	} else if (unset(SHORTLOOPS)) {
+	    cmdpop();
+	    YYERRORV(oecused);
+	} else {
+	    cmdpop();
+	    cmdpush(nc);
+	    par_save_list1(complex);
+	    ecbuf[pp] = WCB_IF(type, ecused - 1 - pp);
+	    incmdpos = 1;
+	    break;
 	}
     }
     cmdpop();
     if (xtok == ELSE) {
+	pp = ecadd(0);
 	cmdpush(CS_ELSE);
 	while (tok == SEPER)
 	    yylex();
 	if (tok == INBRACE && usebrace) {
 	    yylex();
-	    l = par_list();
+	    par_save_list(complex);
 	    if (tok != OUTBRACE) {
 		cmdpop();
-		YYERRORV;
+		YYERRORV(oecused);
 	    }
 	} else {
-	    l = par_list();
+	    par_save_list(complex);
 	    if (tok != FI) {
 		cmdpop();
-		YYERRORV;
+		YYERRORV(oecused);
 	    }
 	}
-	addlinknode(thensl, l);
-	nt++;
+	ecbuf[pp] = WCB_IF(WC_IF_ELSE, ecused - 1 - pp);
 	yylex();
 	cmdpop();
     }
-    i = (struct ifcmd *)make_ifcmd();
-    i->ifls = (List *) alloc((ni + 1) * sizeof(List));
-    i->thenls = (List *) alloc((nt + 1) * sizeof(List));
-
-    for (ll = i->ifls, no = firstnode(ifsl); no; incnode(no), ll++)
-	if (!(*ll = (List) getdata(no)))
-	    *ll = &dummy_list;
-    *ll = NULL;
-    for (ll = i->thenls, no = firstnode(thensl); no; incnode(no), ll++)
-	if (!(*ll = (List) getdata(no)))
-	    *ll = &dummy_list;
-    *ll = NULL;
-
-    c->u.ifcmd = i;
+    ecbuf[p] = WCB_IF(WC_IF_HEAD, ecused - 1 - p);
 }
 
 /*
@@ -771,37 +1176,38 @@ par_if(Cmd c)
 
 /**/
 static void
-par_while(Cmd c)
+par_while(int *complex)
 {
-    struct whilecmd *w;
+    int oecused = ecused, p;
+    int type = (tok == UNTIL ? WC_WHILE_UNTIL : WC_WHILE_WHILE);
 
-    c->type = CWHILE;
-    w = c->u.whilecmd = (struct whilecmd *)make_whilecmd();
-    w->cond = (tok == UNTIL);
+    p = ecadd(0);
     yylex();
-    w->cont = par_list();
+    par_save_list(complex);
     incmdpos = 1;
     while (tok == SEPER)
 	yylex();
     if (tok == DO) {
 	yylex();
-	w->loop = par_list();
+	par_save_list(complex);
 	if (tok != DONE)
-	    YYERRORV;
+	    YYERRORV(oecused);
 	yylex();
     } else if (tok == INBRACE) {
 	yylex();
-	w->loop = par_list();
+	par_save_list(complex);
 	if (tok != OUTBRACE)
-	    YYERRORV;
+	    YYERRORV(oecused);
 	yylex();
     } else if (isset(CSHJUNKIELOOPS)) {
-	w->loop = par_list();
+	par_save_list(complex);
 	if (tok != ZEND)
-	    YYERRORV;
+	    YYERRORV(oecused);
 	yylex();
     } else
-	YYERRORV;
+	YYERRORV(oecused);
+
+    ecbuf[p] = WCB_WHILE(type, ecused - 1 - p);
 }
 
 /*
@@ -810,39 +1216,44 @@ par_while(Cmd c)
 
 /**/
 static void
-par_repeat(Cmd c)
+par_repeat(int *complex)
 {
-    c->type = CREPEAT;
+    int oecused = ecused, p;
+
+    p = ecadd(0);
+
     incmdpos = 0;
     yylex();
     if (tok != STRING)
-	YYERRORV;
-    addlinknode(c->args, tokstr);
+	YYERRORV(oecused);
+    ecstr(tokstr);
     incmdpos = 1;
     yylex();
     while (tok == SEPER)
 	yylex();
     if (tok == DO) {
 	yylex();
-	c->u.list = par_list();
+	par_save_list(complex);
 	if (tok != DONE)
-	    YYERRORV;
+	    YYERRORV(oecused);
 	yylex();
     } else if (tok == INBRACE) {
 	yylex();
-	c->u.list = par_list();
+	par_save_list(complex);
 	if (tok != OUTBRACE)
-	    YYERRORV;
+	    YYERRORV(oecused);
 	yylex();
     } else if (isset(CSHJUNKIELOOPS)) {
-	c->u.list = par_list();
+	par_save_list(complex);
 	if (tok != ZEND)
-	    YYERRORV;
+	    YYERRORV(oecused);
 	yylex();
     } else if (unset(SHORTLOOPS)) {
-	YYERRORV;
+	YYERRORV(oecused);
     } else
-	c->u.list = par_list1();
+	par_save_list1(complex);
+
+    ecbuf[p] = WCB_REPEAT(ecused - 1 - p);
 }
 
 /*
@@ -851,13 +1262,18 @@ par_repeat(Cmd c)
 
 /**/
 static void
-par_subsh(Cmd c)
+par_subsh(int *complex)
 {
-    c->type = (tok == INPAR) ? SUBSH : CURSH;
+    int oecused = ecused, otok = tok, p;
+
+    p = ecadd(0);
     yylex();
-    c->u.list = par_list();
-    if (tok != ((c->type == SUBSH) ? OUTPAR : OUTBRACE))
-	YYERRORV;
+    par_list(complex);
+    ecadd(WCB_END());
+    if (tok != ((otok == INPAR) ? OUTPAR : OUTBRACE))
+	YYERRORV(oecused);
+    ecbuf[p] = (otok == INPAR ? WCB_SUBSH(ecused - 1 - p) :
+		WCB_CURSH(ecused - 1 - p));
     incmdpos = 1;
     yylex();
 }
@@ -869,37 +1285,74 @@ par_subsh(Cmd c)
 
 /**/
 static void
-par_funcdef(Cmd c)
+par_funcdef(void)
 {
+    int oecused = ecused, oldlineno = lineno, num = 0, onp, p, c = 0;
+    int so, oecssub = ecssub;
+
+    lineno = 0;
     nocorrect = 1;
     incmdpos = 0;
     yylex();
-    c->type = FUNCDEF;
-    c->args = newlinklist();
+
+    p = ecadd(0);
+    ecadd(0);
+
     incmdpos = 1;
     while (tok == STRING) {
 	if (*tokstr == Inbrace && !tokstr[1]) {
 	    tok = INBRACE;
 	    break;
 	}
-	addlinknode(c->args, tokstr);
+	ecstr(tokstr);
+	num++;
 	yylex();
     }
+    ecadd(0);
+    ecadd(0);
+    ecadd(0);
+
     nocorrect = 0;
     if (tok == INOUTPAR)
 	yylex();
     while (tok == SEPER)
 	yylex();
+
+    ecnfunc++;
+    ecssub = so = ecsoffs;
+    onp = ecnpats;
+    ecnpats = 0;
+
     if (tok == INBRACE) {
 	yylex();
-	c->u.list = par_list();
-	if (tok != OUTBRACE)
-	    YYERRORV;
+	par_list(&c);
+	if (tok != OUTBRACE) {
+	    lineno += oldlineno;
+	    ecnpats = onp;
+	    ecssub = oecssub;
+	    YYERRORV(oecused);
+	}
 	yylex();
     } else if (unset(SHORTLOOPS)) {
-	YYERRORV;
+	lineno += oldlineno;
+	ecnpats = onp;
+	ecssub = oecssub;
+	YYERRORV(oecused);
     } else
-	c->u.list = par_list1();
+	par_list1(&c);
+
+    ecadd(WCB_END());
+    ecbuf[p + num + 2] = so - oecssub;
+    ecbuf[p + num + 3] = ecsoffs - so;
+    ecbuf[p + num + 4] = ecnpats;
+    ecbuf[p + 1] = num;
+
+    lineno += oldlineno;
+    ecnpats = onp;
+    ecssub = oecssub;
+    ecnfunc++;
+
+    ecbuf[p] = WCB_FUNCDEF(ecused - 1 - p);
 }
 
 /*
@@ -908,11 +1361,17 @@ par_funcdef(Cmd c)
 
 /**/
 static void
-par_time(Cmd c)
+par_time(void)
 {
+    int p, f, c = 0;
+
     yylex();
-    c->type = ZCTIME;
-    c->u.pline = par_sublist2();
+
+    p = ecadd(0);
+    ecadd(0);
+    f = par_sublist2(&c);
+    ecbuf[p] = WCB_TIMED((p + 1 == ecused) ? WC_TIMED_EMPTY : WC_TIMED_PIPE);
+    set_sublist_code(p + 1, WC_SUBLIST_END, f, ecused - 2 - p, c);
 }
 
 /*
@@ -921,15 +1380,16 @@ par_time(Cmd c)
 
 /**/
 static void
-par_dinbrack(Cmd c)
+par_dinbrack(void)
 {
-    c->type = COND;
+    int oecused = ecused;
+
     incond = 1;
     incmdpos = 0;
     yylex();
-    c->u.cond = par_cond();
+    par_cond();
     if (tok != DOUTBRACK)
-	YYERRORV;
+	YYERRORV(oecused);
     incond = 0;
     incmdpos = 1;
     yylex();
@@ -942,77 +1402,290 @@ par_dinbrack(Cmd c)
  */
 
 /**/
-static Cmd
-par_simple(Cmd c)
+static int
+par_simple(int *complex, int nr)
 {
-    int isnull = 1;
+    int oecused = ecused, isnull = 1, r, argc = 0, p, isfunc = 0, sr = 0;
+    int c = *complex;
 
-    c->type = SIMPLE;
+    r = ecused;
     for (;;) {
-	if (tok == NOCORRECT)
+	if (tok == NOCORRECT) {
+	    *complex = c = 1;
 	    nocorrect = 1;
-	else if (tok == ENVSTRING) {
-	    struct varasg *v = (struct varasg *)make_varnode();
-
-	    v->type = PM_SCALAR;
-	    equalsplit(v->name = tokstr, &v->str);
-	    addlinknode(c->vars, v);
+	} else if (tok == ENVSTRING) {
+	    char *p, *name, *str;
+
+	    ecadd(WCB_ASSIGN(WC_ASSIGN_SCALAR, 0));
+	    name = tokstr;
+	    for (p = tokstr; *p && *p != Inbrack && *p != '='; p++);
+	    if (*p == Inbrack && !skipparens(Inbrack, Outbrack, &p) &&
+		*p == '=') {
+		*p = '\0';
+		str = p + 1;
+	    } else
+		equalsplit(tokstr, &str);
+	    ecstr(name);
+	    ecstr(str);
 	    isnull = 0;
 	} else if (tok == ENVARRAY) {
-	    struct varasg *v = (struct varasg *)make_varnode();
-	    int oldcmdpos = incmdpos;
+	    int oldcmdpos = incmdpos, n;
 
-	    v->type = PM_ARRAY;
+	    p = ecadd(0);
 	    incmdpos = 0;
-	    v->name = tokstr;
+	    ecstr(tokstr);
 	    cmdpush(CS_ARRAY);
 	    yylex();
-	    v->arr = par_nl_wordlist();
+	    n = par_nl_wordlist();
+	    ecbuf[p] = WCB_ASSIGN(WC_ASSIGN_ARRAY, n);
 	    cmdpop();
 	    if (tok != OUTPAR)
-		YYERROR;
+		YYERROR(oecused);
 	    incmdpos = oldcmdpos;
-	    addlinknode(c->vars, v);
 	    isnull = 0;
 	} else
 	    break;
 	yylex();
     }
     if (tok == AMPER || tok == AMPERBANG)
-	YYERROR;
+	YYERROR(oecused);
+
+    p = ecadd(WCB_SIMPLE(0));
+
     for (;;) {
 	if (tok == STRING) {
+	    *complex = 1;
 	    incmdpos = 0;
-	    addlinknode(c->args, tokstr);
+	    ecstr(tokstr);
+	    argc++;
 	    yylex();
 	} else if (IS_REDIROP(tok)) {
-	    par_redir(c->redir);
+	    *complex = c = 1;
+	    par_redir(&r);
+	    p += 3;		/* 3 codes per redirection */
+	    sr++;
 	} else if (tok == INOUTPAR) {
+	    int oldlineno = lineno, onp, so, oecssub = ecssub;
+
+	    *complex = c;
+	    lineno = 0;
 	    incmdpos = 1;
 	    cmdpush(CS_FUNCDEF);
 	    yylex();
 	    while (tok == SEPER)
 		yylex();
+
+	    ecispace(p + 1, 1);
+	    ecbuf[p + 1] = argc;
+	    ecadd(0);
+	    ecadd(0);
+	    ecadd(0);
+
+	    ecnfunc++;
+	    ecssub = so = ecsoffs;
+	    onp = ecnpats;
+	    ecnpats = 0;
+
 	    if (tok == INBRACE) {
+		int c = 0;
+
 		yylex();
-		c->u.list = par_list();
+		par_list(&c);
 		if (tok != OUTBRACE) {
 		    cmdpop();
-		    YYERROR;
+		    lineno += oldlineno;
+		    ecnpats = onp;
+		    ecssub = oecssub;
+		    YYERROR(oecused);
 		}
 		yylex();
-	    } else
-		c->u.list = (List) expandstruct((struct node *) par_cmd(), N_LIST);
+	    } else {
+		int ll, sl, c = 0;
+
+		ll = ecadd(0);
+		sl = ecadd(0);
+
+		par_cmd(&c);
+
+		set_sublist_code(sl, WC_SUBLIST_END, 0, ecused - 1 - sl, c);
+		set_list_code(ll, (Z_SYNC | Z_END), c);
+	    }
 	    cmdpop();
-	    c->type = FUNCDEF;
+
+	    ecadd(WCB_END());
+	    ecbuf[p + argc + 2] = so - oecssub;
+	    ecbuf[p + argc + 3] = ecsoffs - so;
+	    ecbuf[p + argc + 4] = ecnpats;
+
+	    lineno += oldlineno;
+	    ecnpats = onp;
+	    ecssub = oecssub;
+	    ecnfunc++;
+
+	    ecbuf[p] = WCB_FUNCDEF(ecused - 1 - p);
+
+	    isfunc = 1;
 	} else
 	    break;
 	isnull = 0;
     }
-    if (isnull && empty(c->redir))
-	return NULL;
+    if (isnull && !(sr + nr)) {
+	ecused = p;
+	return 0;
+    }
     incmdpos = 1;
-    return c;
+
+    if (!isfunc)
+	ecbuf[p] = WCB_SIMPLE(argc);
+
+    return sr + 1;
+}
+
+/*
+ * redir	: ( OUTANG | ... | TRINANG ) STRING
+ */
+
+static int redirtab[TRINANG - OUTANG + 1] = {
+    WRITE,
+    WRITENOW,
+    APP,
+    APPNOW,
+    READ,
+    READWRITE,
+    HEREDOC,
+    HEREDOCDASH,
+    MERGEIN,
+    MERGEOUT,
+    ERRWRITE,
+    ERRWRITENOW,
+    ERRAPP,
+    ERRAPPNOW,
+    HERESTR,
+};
+
+/**/
+static void
+par_redir(int *rp)
+{
+    int r = *rp, type, fd1, oldcmdpos, oldnc;
+    char *name;
+
+    oldcmdpos = incmdpos;
+    incmdpos = 0;
+    oldnc = nocorrect;
+    if (tok != INANG && tok != INOUTANG)
+	nocorrect = 1;
+    type = redirtab[tok - OUTANG];
+    fd1 = tokfd;
+    yylex();
+    if (tok != STRING && tok != ENVSTRING)
+	YYERRORV(ecused);
+    incmdpos = oldcmdpos;
+    nocorrect = oldnc;
+
+    /* assign default fd */
+    if (fd1 == -1)
+	fd1 = IS_READFD(type) ? 0 : 1;
+
+    name = tokstr;
+
+    switch (type) {
+    case HEREDOC:
+    case HEREDOCDASH: {
+	/* <<[-] name */
+	struct heredocs **hd;
+
+	/* If we ever need more than three codes (or less), we have to change
+	 * the factors in par_cmd() and par_simple(), too. */
+	ecispace(r, 3);
+	*rp = r + 3;
+	ecbuf[r] = WCB_REDIR(type);
+	ecbuf[r + 1] = fd1;
+
+	for (hd = &hdocs; *hd; hd = &(*hd)->next);
+	*hd = zalloc(sizeof(struct heredocs));
+	(*hd)->next = NULL;
+	(*hd)->type = type;
+	(*hd)->pc = r;
+	(*hd)->str = tokstr;
+
+	yylex();
+	return;
+    }
+    case WRITE:
+    case WRITENOW:
+	if (tokstr[0] == Outang && tokstr[1] == Inpar)
+	    /* > >(...) */
+	    type = OUTPIPE;
+	else if (tokstr[0] == Inang && tokstr[1] == Inpar)
+	    YYERRORV(ecused);
+	break;
+    case READ:
+	if (tokstr[0] == Inang && tokstr[1] == Inpar)
+	    /* < <(...) */
+	    type = INPIPE;
+	else if (tokstr[0] == Outang && tokstr[1] == Inpar)
+	    YYERRORV(ecused);
+	break;
+    case READWRITE:
+	if ((tokstr[0] == Inang || tokstr[0] == Outang) && tokstr[1] == Inpar)
+	    type = tokstr[0] == Inang ? INPIPE : OUTPIPE;
+	break;
+    }
+    yylex();
+
+    /* If we ever need more than three codes (or less), we have to change
+     * the factors in par_cmd() and par_simple(), too. */
+    ecispace(r, 3);
+    *rp = r + 3;
+    ecbuf[r] = WCB_REDIR(type);
+    ecbuf[r + 1] = fd1;
+    ecbuf[r + 2] = ecstrcode(name);
+}
+
+/**/
+void
+setheredoc(int pc, int type, char *str)
+{
+    ecbuf[pc] = WCB_REDIR(type);
+    ecbuf[pc + 2] = ecstrcode(str);
+}
+
+/*
+ * wordlist	: { STRING }
+ */
+
+/**/
+static int
+par_wordlist(void)
+{
+    int num = 0;
+    while (tok == STRING) {
+	ecstr(tokstr);
+	num++;
+	yylex();
+    }
+    return num;
+}
+
+/*
+ * nl_wordlist	: { STRING | SEPER }
+ */
+
+/**/
+static int
+par_nl_wordlist(void)
+{
+    int num = 0;
+
+    while (tok == STRING || tok == SEPER) {
+	if (tok != SEPER) {
+	    ecstr(tokstr);
+	    num++;
+	}
+	yylex();
+    }
+    return num;
 }
 
 /*
@@ -1028,25 +1701,24 @@ void (*condlex) _((void)) = yylex;
  */
 
 /**/
-Cond
+static int
 par_cond(void)
 {
-    Cond c, c2;
+    int p = ecused, r;
 
-    c = par_cond_1();
+    r = par_cond_1();
     while (tok == SEPER)
 	condlex();
     if (tok == DBAR) {
 	condlex();
 	while (tok == SEPER)
 	    condlex();
-	c2 = (Cond) make_cond();
-	c2->left = (void *) c;
-	c2->right = (void *) par_cond();
-	c2->type = COND_OR;
-	return c2;
+	ecispace(p, 1);
+	par_cond();
+	ecbuf[p] = WCB_COND(COND_OR, ecused - 1 - p);
+	return 1;
     }
-    return c;
+    return r;
 }
 
 /*
@@ -1054,25 +1726,24 @@ par_cond(void)
  */
 
 /**/
-static Cond
+static int
 par_cond_1(void)
 {
-    Cond c, c2;
+    int r, p = ecused;
 
-    c = par_cond_2();
+    r = par_cond_2();
     while (tok == SEPER)
 	condlex();
     if (tok == DAMPER) {
 	condlex();
 	while (tok == SEPER)
 	    condlex();
-	c2 = (Cond) make_cond();
-	c2->left = (void *) c;
-	c2->right = (void *) par_cond_1();
-	c2->type = COND_AND;
-	return c2;
+	ecispace(p, 1);
+	par_cond_1();
+	ecbuf[p] = WCB_COND(COND_AND, ecused - 1 - p);
+	return 1;
     }
-    return c;
+    return r;
 }
 
 /*
@@ -1084,10 +1755,9 @@ par_cond_1(void)
  */
 
 /**/
-static Cond
+static int
 par_cond_2(void)
 {
-    Cond c, c2;
     char *s1, *s2, *s3;
     int dble = 0;
 
@@ -1121,31 +1791,31 @@ par_cond_2(void)
     }
     if (tok == BANG) {
 	condlex();
-	c = par_cond_2();
-	c2 = (Cond) make_cond();
-	c2->left = (void *) c;
-	c2->type = COND_NOT;
-	return c2;
+	ecadd(WCB_COND(COND_NOT, 0));
+	return par_cond_2();
     }
     if (tok == INPAR) {
+	int r;
+
 	condlex();
 	while (tok == SEPER)
 	    condlex();
-	c = par_cond();
+	r = par_cond();
 	while (tok == SEPER)
 	    condlex();
 	if (tok != OUTPAR)
-	    YYERROR;
+	    YYERROR(ecused);
 	condlex();
-	return c;
+	return r;
     }
-    if (tok != STRING)
+    if (tok != STRING) {
 	if (tok && tok != LEXERR && condlex == testlex) {
 	    s1 = tokstr;
 	    condlex();
 	    return par_cond_double("-n", s1);
 	} else
-	    YYERROR;
+	    YYERROR(ecused);
+    }
     s1 = tokstr;
     if (condlex == testlex)
 	dble = (*s1 == '-' && strspn(s1+1, "abcdefghknoprstuwxzLONGS") == 1
@@ -1155,24 +1825,23 @@ par_cond_2(void)
 	int xtok = tok;
 	condlex();
 	if (tok != STRING)
-	    YYERROR;
+	    YYERROR(ecused);
 	s3 = tokstr;
 	condlex();
-	c = (Cond) make_cond();
-	c->left = (void *) s1;
-	c->right = (void *) s3;
-	c->type = (xtok == INANG) ? COND_STRLT : COND_STRGTR;
-	c->ntype = NT_SET(N_COND, NT_STR, NT_STR, 0, 0);
-	return c;
+	ecadd(WCB_COND((xtok == INANG ? COND_STRLT : COND_STRGTR), 0));
+	ecstr(s1);
+	ecstr(s3);
+	return 1;
     }
-    if (tok != STRING)
+    if (tok != STRING) {
 	if (tok != LEXERR && condlex == testlex) {
 	    if (!dble)
 		return par_cond_double("-n", s1);
 	    else if (!strcmp(s1, "-t"))
 		return par_cond_double(s1, "1");
 	} else
-	    YYERROR;
+	    YYERROR(ecused);
+    }
     s2 = tokstr;
     incond++;			/* parentheses do globbing */
     condlex();
@@ -1180,200 +1849,1192 @@ par_cond_2(void)
     if (tok == STRING && !dble) {
 	s3 = tokstr;
 	condlex();
-	return par_cond_triple(s1, s2, s3);
+	if (tok == STRING) {
+	    LinkList l = newlinklist();
+
+	    addlinknode(l, s2);
+	    addlinknode(l, s3);
+
+	    while (tok == STRING) {
+		addlinknode(l, tokstr);
+		condlex();
+	    }
+	    return par_cond_multi(s1, l);
+	} else
+	    return par_cond_triple(s1, s2, s3);
     } else
 	return par_cond_double(s1, s2);
 }
 
-/*
- * redir	: ( OUTANG | ... | TRINANG ) STRING
- */
+/**/
+static int
+par_cond_double(char *a, char *b)
+{
+    if (a[0] != '-' || !a[1])
+	COND_ERROR("parse error: condition expected: %s", a);
+    else if (!a[2] && strspn(a+1, "abcdefgknoprstuwxzhLONGS") == 1) {
+	ecadd(WCB_COND(a[1], 0));
+	ecstr(b);
+    } else {
+	ecadd(WCB_COND(COND_MOD, 1));
+	ecstr(a);
+	ecstr(b);
+    }
+    return 1;
+}
 
-static int redirtab[TRINANG - OUTANG + 1] = {
-    WRITE,
-    WRITENOW,
-    APP,
-    APPNOW,
-    READ,
-    READWRITE,
-    HEREDOC,
-    HEREDOCDASH,
-    MERGEIN,
-    MERGEOUT,
-    ERRWRITE,
-    ERRWRITENOW,
-    ERRAPP,
-    ERRAPPNOW,
-    HERESTR,
-};
+/**/
+static int
+get_cond_num(char *tst)
+{
+    static char *condstrs[] =
+    {
+	"nt", "ot", "ef", "eq", "ne", "lt", "gt", "le", "ge", NULL
+    };
+    int t0;
+
+    for (t0 = 0; condstrs[t0]; t0++)
+	if (!strcmp(condstrs[t0], tst))
+	    return t0;
+    return -1;
+}
+
+/**/
+static int
+par_cond_triple(char *a, char *b, char *c)
+{
+    int t0;
+
+    if ((b[0] == Equals || b[0] == '=') &&
+	(!b[1] || ((b[1] == Equals || b[1] == '=') && !b[2]))) {
+	ecadd(WCB_COND(COND_STREQ, 0));
+	ecstr(a);
+	ecstr(c);
+	ecadd(ecnpats++);
+    } else if (b[0] == '!' && (b[1] == Equals || b[1] == '=') && !b[2]) {
+	ecadd(WCB_COND(COND_STRNEQ, 0));
+	ecstr(a);
+	ecstr(c);
+	ecadd(ecnpats++);
+    } else if (b[0] == '-') {
+	if ((t0 = get_cond_num(b + 1)) > -1) {
+	    ecadd(WCB_COND(t0 + COND_NT, 0));
+	    ecstr(a);
+	    ecstr(c);
+	} else {
+	    ecadd(WCB_COND(COND_MODI, 0));
+	    ecstr(b);
+	    ecstr(a);
+	    ecstr(c);
+	}
+    } else if (a[0] == '-' && a[1]) {
+	ecadd(WCB_COND(COND_MOD, 2));
+	ecstr(a);
+	ecstr(b);
+	ecstr(c);
+    } else
+	COND_ERROR("condition expected: %s", b);
+
+    return 1;
+}
+
+/**/
+static int
+par_cond_multi(char *a, LinkList l)
+{
+    if (a[0] != '-' || !a[1])
+	COND_ERROR("condition expected: %s", a);
+    else {
+	LinkNode n;
+
+	ecadd(WCB_COND(COND_MOD, countlinknodes(l)));
+	ecstr(a);
+	for (n = firstnode(l); n; incnode(n))
+	    ecstr((char *) getdata(n));
+    }
+    return 1;
+}
 
 /**/
 static void
-par_redir(LinkList l)
+yyerror(int noerr)
 {
-    struct redir *fn = (struct redir *)allocnode(N_REDIR);
-    int oldcmdpos, oldnc;
+    int t0;
+    char *t;
 
-    oldcmdpos = incmdpos;
-    incmdpos = 0;
-    oldnc = nocorrect;
-    if (tok != INANG && tok != INOUTANG)
-	nocorrect = 1;
-    fn->type = redirtab[tok - OUTANG];
-    fn->fd1 = tokfd;
-    yylex();
-    if (tok != STRING && tok != ENVSTRING)
-	YYERRORV;
-    incmdpos = oldcmdpos;
-    nocorrect = oldnc;
+    if ((t = dupstring(yytext)))
+	untokenize(t);
 
-    /* assign default fd */
-    if (fn->fd1 == -1)
-	fn->fd1 = IS_READFD(fn->type) ? 0 : 1;
+    for (t0 = 0; t0 != 20; t0++)
+	if (!t || !t[t0] || t[t0] == '\n')
+	    break;
+    if (t0 == 20)
+	zwarn("parse error near `%l...'", t, 20);
+    else if (t0)
+	zwarn("parse error near `%l'", t, t0);
+    else
+	zwarn("parse error", NULL, 0);
+    if (!noerr && noerrs != 2)
+	errflag = 1;
+}
 
-    fn->name = tokstr;
+/**/
+mod_export Eprog
+dupeprog(Eprog p, int heap)
+{
+    Eprog r;
+    int i;
+    Patprog *pp;
 
-    switch (fn->type) {
-    case HEREDOC:
-    case HEREDOCDASH: {
-	/* <<[-] name */
-	struct heredocs **hd;
+    if (p == &dummy_eprog)
+	return p;
 
-	for (hd = &hdocs; *hd; hd = &(*hd)->next);
-	*hd = zalloc(sizeof(struct heredocs));
-	(*hd)->next = NULL;
-	(*hd)->rd = fn;
-	break;
+    r = (heap ? (Eprog) zhalloc(sizeof(*r)) : (Eprog) zalloc(sizeof(*r)));
+    r->flags = (heap ? EF_HEAP : EF_REAL) | (p->flags & EF_RUN);
+    r->dump = NULL;
+    r->len = p->len;
+    r->npats = p->npats;
+    pp = r->pats = (heap ? (Patprog *) hcalloc(r->len) :
+		    (Patprog *) zcalloc(r->len));
+    r->prog = (Wordcode) (r->pats + r->npats);
+    r->strs = ((char *) r->prog) + (p->strs - ((char *) p->prog));
+    memcpy(r->prog, p->prog, r->len - (p->npats * sizeof(Patprog)));
+    r->shf = NULL;
+
+    for (i = r->npats; i--; pp++)
+	*pp = dummy_patprog1;
+
+    return r;
+}
+
+static LinkList eprog_free;
+
+/**/
+mod_export void
+freeeprog(Eprog p)
+{
+    if (p && p != &dummy_eprog)
+	zaddlinknode(eprog_free, p);
+}
+
+/**/
+void
+freeeprogs(void)
+{
+    Eprog p;
+    int i;
+    Patprog *pp;
+
+    while ((p = (Eprog) getlinknode(eprog_free))) {
+	for (i = p->npats, pp = p->pats; i--; pp++)
+	    freepatprog(*pp);
+	if (p->dump) {
+	    decrdumpcount(p->dump);
+	    zfree(p->pats, p->npats * sizeof(Patprog));
+	} else
+	    zfree(p->pats, p->len);
+	zfree(p, sizeof(*p));
     }
-    case WRITE:
-    case WRITENOW:
-	if (tokstr[0] == Outang && tokstr[1] == Inpar)
-	    /* > >(...) */
-	    fn->type = OUTPIPE;
-	else if (tokstr[0] == Inang && tokstr[1] == Inpar)
-	    YYERRORV;
-	break;
-    case READ:
-	if (tokstr[0] == Inang && tokstr[1] == Inpar)
-	    /* < <(...) */
-	    fn->type = INPIPE;
-	else if (tokstr[0] == Outang && tokstr[1] == Inpar)
-	    YYERRORV;
-	break;
-    case READWRITE:
-	if ((tokstr[0] == Inang || tokstr[0] == Outang) && tokstr[1] == Inpar)
-	    fn->type = tokstr[0] == Inang ? INPIPE : OUTPIPE;
-	break;
+}
+
+/**/
+char *
+ecgetstr(Estate s, int dup, int *tok)
+{
+    static char buf[4];
+    wordcode c = *s->pc++;
+    char *r;
+
+    if (c == 6 || c == 7)
+	r = "";
+    else if (c & 2) {
+	buf[0] = (char) ((c >>  3) & 0xff);
+	buf[1] = (char) ((c >> 11) & 0xff);
+	buf[2] = (char) ((c >> 19) & 0xff);
+	buf[3] = '\0';
+	r = dupstring(buf);
+	dup = EC_NODUP;
+    } else {
+	r = s->strs + (c >> 2);
     }
-    yylex();
-    addlinknode(l, fn);
+    if (tok)
+	*tok = (c & 1);
+
+    /*** Since function dump files are mapped read-only, avoiding to
+     *   to duplicate strings when they don't contain tokens may fail
+     *   when one of the many utility functions happens to write to
+     *   one of the strings (without really modifying it).
+     *   If that happens to you and you don't feel like debugging it,
+     *   just change the line below to:
+     *
+     *     return (dup ? dupstring(r) : r);
+     */
+
+    return ((dup == EC_DUP || (dup && (c & 1)))  ? dupstring(r) : r);
 }
 
-/*
- * wordlist	: { STRING }
- */
+/**/
+char *
+ecrawstr(Eprog p, Wordcode pc, int *tok)
+{
+    static char buf[4];
+    wordcode c = *pc;
+
+    if (c == 6 || c == 7) {
+	if (tok)
+	    *tok = (c & 1);
+	return "";
+    } else if (c & 2) {
+	buf[0] = (char) ((c >>  3) & 0xff);
+	buf[1] = (char) ((c >> 11) & 0xff);
+	buf[2] = (char) ((c >> 19) & 0xff);
+	buf[3] = '\0';
+	if (tok)
+	    *tok = (c & 1);
+	return buf;
+    } else {
+	if (tok)
+	    *tok = (c & 1);
+	return p->strs + (c >> 2);
+    }
+}
 
 /**/
-static LinkList
-par_wordlist(void)
+char **
+ecgetarr(Estate s, int num, int dup, int *tok)
 {
-    LinkList l;
+    char **ret, **rp;
+    int tf = 0, tmp = 0;
 
-    l = newlinklist();
-    while (tok == STRING) {
-	addlinknode(l, tokstr);
-	yylex();
+    ret = rp = (char **) zhalloc((num + 1) * sizeof(char *));
+
+    while (num--) {
+	*rp++ = ecgetstr(s, dup, &tmp);
+	tf |=  tmp;
     }
-    return l;
+    *rp = NULL;
+    if (tok)
+	*tok = tf;
+
+    return ret;
 }
 
-/*
- * nl_wordlist	: { STRING | SEPER }
+/**/
+LinkList
+ecgetlist(Estate s, int num, int dup, int *tok)
+{
+    if (num) {
+	LinkList ret;
+	int i, tf = 0, tmp = 0;
+
+	ret = newsizedlist(num);
+	for (i = 0; i < num; i++) {
+	    setsizednode(ret, i, ecgetstr(s, dup, &tmp));
+	    tf |= tmp;
+	}
+	if (tok)
+	    *tok = tf;
+	return ret;
+    }
+    if (tok)
+	*tok = 0;
+    return NULL;
+}
+
+/**/
+LinkList
+ecgetredirs(Estate s)
+{
+    LinkList ret = newlinklist();
+    wordcode code = *s->pc++;
+
+    while (wc_code(code) == WC_REDIR) {
+	Redir r = (Redir) zhalloc(sizeof(*r));
+
+	r->type = WC_REDIR_TYPE(code);
+	r->fd1 = *s->pc++;
+	r->name = ecgetstr(s, EC_DUP, NULL);
+
+	addlinknode(ret, r);
+
+	code = *s->pc++;
+    }
+    s->pc--;
+
+    return ret;
+}
+
+/**/
+mod_export struct eprog dummy_eprog;
+
+static wordcode dummy_eprog_code;
+
+/**/
+void
+init_eprog(void)
+{
+    dummy_eprog_code = WCB_END();
+    dummy_eprog.len = sizeof(wordcode);
+    dummy_eprog.prog = &dummy_eprog_code;
+    dummy_eprog.strs = NULL;
+
+    eprog_free = znewlinklist();
+}
+
+/* Code for function dump files.
+ *
+ * Dump files consist of a header and the function bodies (the wordcode
+ * plus the string table) and that twice: once for the byte-order of the
+ * host the file was created on and once for the other byte-order. The
+ * header describes where the beginning of the `other' version is and it
+ * is up to the shell reading the file to decide which version it needs.
+ * This is done by checking if the first word is FD_MAGIC (then the 
+ * shell reading the file has the same byte order as the one that created
+ * the file) or if it is FD_OMAGIC, then the `other' version has to be
+ * read.
+ * The header is the magic number, a word containing the flags (if the
+ * file should be mapped or read and if this header is the `other' one),
+ * the version string in a field of 40 characters and the descriptions
+ * for the functions in the dump file.
+ * Each description consists of a struct fdhead followed by the name,
+ * aligned to sizeof(wordcode) (i.e. 4 bytes).
  */
 
+#include "version.h"
+
+#define FD_EXT ".zwc"
+#define FD_MINMAP 4096
+
+#define FD_PRELEN 12
+#define FD_MAGIC  0x02030405
+#define FD_OMAGIC 0x05040302
+
+#define FDF_MAP   1
+#define FDF_OTHER 2
+
+typedef struct fdhead *FDHead;
+
+struct fdhead {
+    wordcode start;		/* offset to function definition */
+    wordcode len;		/* length of wordcode/strings */
+    wordcode npats;		/* number of patterns needed */
+    wordcode strs;		/* offset to strings */
+    wordcode hlen;		/* header length (incl. name) */
+    wordcode flags;		/* flags and offset to name tail */
+};
+
+#define fdheaderlen(f) (((Wordcode) (f))[FD_PRELEN])
+
+#define fdmagic(f)       (((Wordcode) (f))[0])
+#define fdsetbyte(f,i,v) \
+    ((((unsigned char *) (((Wordcode) (f)) + 1))[i]) = ((unsigned char) (v)))
+#define fdbyte(f,i)      ((wordcode) (((unsigned char *) (((Wordcode) (f)) + 1))[i]))
+#define fdflags(f)       fdbyte(f, 0)
+#define fdsetflags(f,v)  fdsetbyte(f, 0, v)
+#define fdother(f)       (fdbyte(f, 1) + (fdbyte(f, 2) << 8) + (fdbyte(f, 3) << 16))
+#define fdsetother(f, o) \
+    do { \
+        fdsetbyte(f, 1, ((o) & 0xff)); \
+        fdsetbyte(f, 2, (((o) >> 8) & 0xff)); \
+        fdsetbyte(f, 3, (((o) >> 16) & 0xff)); \
+    } while (0)
+#define fdversion(f)     ((char *) ((f) + 2))
+
+#define firstfdhead(f) ((FDHead) (((Wordcode) (f)) + FD_PRELEN))
+#define nextfdhead(f)  ((FDHead) (((Wordcode) (f)) + (f)->hlen))
+
+#define fdhflags(f)      (((FDHead) (f))->flags)
+#define fdhtail(f)       (((FDHead) (f))->flags >> 2)
+#define fdhbldflags(f,t) ((f) | ((t) << 2))
+
+#define FDHF_KSHLOAD 1
+#define FDHF_ZSHLOAD 2
+
+#define fdname(f)      ((char *) (((FDHead) (f)) + 1))
+
+/* This is used when building wordcode files. */
+
+typedef struct wcfunc *WCFunc;
+
+struct wcfunc {
+    char *name;
+    Eprog prog;
+    int flags;
+};
+
+/* Try to find the description for the given function name. */
+
+static FDHead
+dump_find_func(Wordcode h, char *name)
+{
+    FDHead n, e = (FDHead) (h + fdheaderlen(h));
+
+    for (n = firstfdhead(h); n < e; n = nextfdhead(n))
+	if (!strcmp(name, fdname(n) + fdhtail(n)))
+	    return n;
+
+    return NULL;
+}
+
 /**/
-static LinkList
-par_nl_wordlist(void)
+int
+bin_zcompile(char *nam, char **args, char *ops, int func)
 {
-    LinkList l;
+    int map, flags;
+    char *dump;
+
+    if ((ops['k'] && ops['z']) || (ops['R'] && ops['M']) ||
+	(ops['c'] && (ops['U'] || ops['k'] || ops['z'])) ||
+	(!(ops['c'] || ops['a']) && ops['m'])) {
+	zwarnnam(nam, "illegal combination of options", NULL, 0);
+	return 1;
+    }
+    if ((ops['c'] || ops['a']) && isset(KSHAUTOLOAD))
+	zwarnnam(nam, "functions will use zsh style autoloading", NULL, 0);
 
-    l = newlinklist();
-    while (tok == STRING || tok == SEPER) {
-	if (tok != SEPER)
-	    addlinknode(l, tokstr);
-	yylex();
+    flags = (ops['k'] ? FDHF_KSHLOAD :
+	     (ops['z'] ? FDHF_ZSHLOAD : 0));
+
+    if (ops['t']) {
+	Wordcode f;
+
+	if (!*args) {
+	    zwarnnam(nam, "too few arguments", NULL, 0);
+	    return 1;
+	}
+	if (!(f = load_dump_header(*args)) &&
+	    !(f = load_dump_header(dyncat(*args, FD_EXT)))) {
+	    zwarnnam(nam, "invalid dump file: %s", *args, 0);
+	    return 1;
+	}
+	if (args[1]) {
+	    for (args++; *args; args++)
+		if (!dump_find_func(f, *args))
+		    return 1;
+	    return 0;
+	} else {
+	    FDHead h, e = (FDHead) (f + fdheaderlen(f));
+
+	    printf("function dump file (%s) for zsh-%s\n",
+		   ((fdflags(f) & FDF_MAP) ? "mapped" : "read"), fdversion(f));
+	    for (h = firstfdhead(f); h < e; h = nextfdhead(h))
+		printf("%s\n", fdname(h));
+	    return 0;
+	}
+    }
+    if (!*args) {
+	zwarnnam(nam, "too few arguments", NULL, 0);
+	return 1;
     }
-    return l;
+    map = (ops['M'] ? 2 : (ops['R'] ? 0 : 1));
+
+    if (!args[1] && !(ops['c'] || ops['a']))
+	return build_dump(nam, dyncat(*args, FD_EXT), args, ops['U'], map, flags);
+
+    dump = (strsfx(FD_EXT, *args) ? *args : dyncat(*args, FD_EXT));
+
+    return ((ops['c'] || ops['a']) ?
+	    build_cur_dump(nam, dump, args + 1, ops['m'], map,
+			   (ops['c'] ? 1 : 0) | (ops['a'] ? 2 : 0)) :
+	    build_dump(nam, dump, args + 1, ops['U'], map, flags));
 }
 
+/* Load the header of a dump file. Returns NULL if the file isn't a
+ * valid dump file. */
+
 /**/
-static Cond
-par_cond_double(char *a, char *b)
+static Wordcode
+load_dump_header(char *name)
 {
-    Cond n = (Cond) make_cond();
+    int fd;
+    wordcode buf[FD_PRELEN + 1];
 
-    if (a[0] != '-' || !a[1] || a[2])
-	COND_ERROR("parse error: condition expected: %s", a);
-    n->left = (void *) b;
-    n->type = a[1];
-    n->ntype = NT_SET(N_COND, NT_STR, NT_STR, 0, 0);
-    return n;
+    if ((fd = open(name, O_RDONLY)) < 0)
+	return NULL;
+
+    if (read(fd, buf, (FD_PRELEN + 1) * sizeof(wordcode)) !=
+	((FD_PRELEN + 1) * sizeof(wordcode)) ||
+	(fdmagic(buf) != FD_MAGIC && fdmagic(buf) != FD_OMAGIC) ||
+	strcmp(ZSH_VERSION, fdversion(buf))) {
+	close(fd);
+	return NULL;
+    } else {
+	int len;
+	Wordcode head;
+
+	if (fdmagic(buf) == FD_MAGIC) {
+	    len = fdheaderlen(buf) * sizeof(wordcode);
+	    head = (Wordcode) zhalloc(len);
+	}
+	else {
+	    int o = fdother(buf);
+
+	    if (lseek(fd, o, 0) == -1 ||
+		read(fd, buf, (FD_PRELEN + 1) * sizeof(wordcode)) !=
+		((FD_PRELEN + 1) * sizeof(wordcode))) {
+		close(fd);
+		return NULL;
+	    }
+	    len = fdheaderlen(buf) * sizeof(wordcode);
+	    head = (Wordcode) zhalloc(len);
+	}
+	memcpy(head, buf, (FD_PRELEN + 1) * sizeof(wordcode));
+
+	if (read(fd, head + (FD_PRELEN + 1),
+		 len - ((FD_PRELEN + 1) * sizeof(wordcode))) !=
+	    len - ((FD_PRELEN + 1) * sizeof(wordcode))) {
+	    close(fd);
+	    return NULL;
+	}
+	close(fd);
+	return head;
+    }
+}
+
+/* Swap the bytes in a wordcode. */
+
+static void
+fdswap(Wordcode p, int n)
+{
+    wordcode c;
+
+    for (; n--; p++) {
+	c = *p;
+	*p = (((c & 0xff) << 24) |
+	      ((c & 0xff00) << 8) |
+	      ((c & 0xff0000) >> 8) |
+	      ((c & 0xff000000) >> 24));
+    }
+}
+
+/* Write a dump file. */
+
+static void
+write_dump(int dfd, LinkList progs, int map, int hlen, int tlen)
+{
+    LinkNode node;
+    WCFunc wcf;
+    int other = 0, ohlen, tmp;
+    wordcode pre[FD_PRELEN];
+    char *tail, *n;
+    struct fdhead head;
+    Eprog prog;
+
+    if (map == 1)
+	map = (tlen >= FD_MINMAP);
+
+    for (ohlen = hlen; ; hlen = ohlen) {
+	fdmagic(pre) = (other ? FD_OMAGIC : FD_MAGIC);
+	fdsetflags(pre, ((map ? FDF_MAP : 0) | other));
+	fdsetother(pre, tlen);
+	strcpy(fdversion(pre), ZSH_VERSION);
+	write(dfd, pre, FD_PRELEN * sizeof(wordcode));
+
+	for (node = firstnode(progs); node; incnode(node)) {
+	    wcf = (WCFunc) getdata(node);
+	    n = wcf->name;
+	    prog = wcf->prog;
+	    head.start = hlen;
+	    hlen += (prog->len - (prog->npats * sizeof(Patprog)) +
+		     sizeof(wordcode) - 1) / sizeof(wordcode);
+	    head.len = prog->len - (prog->npats * sizeof(Patprog));
+	    head.npats = prog->npats;
+	    head.strs = prog->strs - ((char *) prog->prog);
+	    head.hlen = (sizeof(struct fdhead) / sizeof(wordcode)) +
+		(strlen(n) + sizeof(wordcode)) / sizeof(wordcode);
+	    if ((tail = strrchr(n, '/')))
+		tail++;
+	    else
+		tail = n;
+	    head.flags = fdhbldflags(wcf->flags, (tail - n));
+	    if (other)
+		fdswap((Wordcode) &head, sizeof(head) / sizeof(wordcode));
+	    write(dfd, &head, sizeof(head));
+	    tmp = strlen(n) + 1;
+	    write(dfd, n, tmp);
+	    if ((tmp &= (sizeof(wordcode) - 1)))
+		write(dfd, &head, sizeof(wordcode) - tmp);
+	}
+	for (node = firstnode(progs); node; incnode(node)) {
+	    prog = ((WCFunc) getdata(node))->prog;
+	    tmp = (prog->len - (prog->npats * sizeof(Patprog)) +
+		   sizeof(wordcode) - 1) / sizeof(wordcode);
+	    if (other)
+		fdswap(prog->prog, (((Wordcode) prog->strs) - prog->prog));
+	    write(dfd, prog->prog, tmp * sizeof(wordcode));
+	}
+	if (other)
+	    break;
+	other = FDF_OTHER;
+    }
 }
 
 /**/
 static int
-get_cond_num(char *tst)
+build_dump(char *nam, char *dump, char **files, int ali, int map, int flags)
 {
-    static char *condstrs[] =
-    {
-	"nt", "ot", "ef", "eq", "ne", "lt", "gt", "le", "ge", NULL
-    };
-    int t0;
+    int dfd, fd, hlen, tlen, flen, ona = noaliases;
+    LinkList progs;
+    char *file;
+    Eprog prog;
+    WCFunc wcf;
+
+    if (!strsfx(FD_EXT, dump))
+	dump = dyncat(dump, FD_EXT);
+
+    if ((dfd = open(dump, O_WRONLY|O_CREAT, 0600)) < 0) {
+	zwarnnam(nam, "can't write dump file: %s", dump, 0);
+	return 1;
+    }
+    progs = newlinklist();
+    noaliases = ali;
+
+    for (hlen = FD_PRELEN, tlen = 0; *files; files++) {
+	if (!strcmp(*files, "-k")) {
+	    flags = (flags & ~(FDHF_KSHLOAD | FDHF_ZSHLOAD)) | FDHF_KSHLOAD;
+	    continue;
+	} else if (!strcmp(*files, "-z")) {
+	    flags = (flags & ~(FDHF_KSHLOAD | FDHF_ZSHLOAD)) | FDHF_ZSHLOAD;
+	    continue;
+	}
+	if ((fd = open(*files, O_RDONLY)) < 0 ||
+	    (flen = lseek(fd, 0, 2)) == -1) {
+	    if (fd >= 0)
+		close(fd);
+	    close(dfd);
+	    zwarnnam(nam, "can't open file: %s", *files, 0);
+	    noaliases = ona;
+	    unlink(dump);
+	    return 1;
+	}
+	file = (char *) zalloc(flen + 1);
+	file[flen] = '\0';
+	lseek(fd, 0, 0);
+	if (read(fd, file, flen) != flen) {
+	    close(fd);
+	    close(dfd);
+	    zfree(file, flen);
+	    zwarnnam(nam, "can't read file: %s", *files, 0);
+	    noaliases = ona;
+	    unlink(dump);
+	    return 1;
+	}
+	close(fd);
+	file = metafy(file, flen, META_REALLOC);
+
+	if (!(prog = parse_string(file, 1)) || errflag) {
+	    errflag = 0;
+	    close(dfd);
+	    zfree(file, flen);
+	    zwarnnam(nam, "can't read file: %s", *files, 0);
+	    noaliases = ona;
+	    unlink(dump);
+	    return 1;
+	}
+	zfree(file, flen);
 
-    for (t0 = 0; condstrs[t0]; t0++)
-	if (!strcmp(condstrs[t0], tst))
-	    return t0;
-    return -1;
+	wcf = (WCFunc) zhalloc(sizeof(*wcf));
+	wcf->name = *files;
+	wcf->prog = prog;
+	wcf->flags = ((prog->flags & EF_RUN) ? FDHF_KSHLOAD : flags);
+	addlinknode(progs, wcf);
+
+	flen = (strlen(*files) + sizeof(wordcode)) / sizeof(wordcode);
+	hlen += (sizeof(struct fdhead) / sizeof(wordcode)) + flen;
+
+	tlen += (prog->len - (prog->npats * sizeof(Patprog)) +
+		 sizeof(wordcode) - 1) / sizeof(wordcode);
+    }
+    noaliases = ona;
+
+    tlen = (tlen + hlen) * sizeof(wordcode);
+
+    write_dump(dfd, progs, map, hlen, tlen);
+
+    close(dfd);
+
+    return 0;
+}
+
+static int
+cur_add_func(char *nam, Shfunc shf, LinkList names, LinkList progs,
+	     int *hlen, int *tlen, int what)
+{
+    Eprog prog;
+    WCFunc wcf;
+
+    if (shf->flags & PM_UNDEFINED) {
+	int ona = noaliases;
+
+	if (!(what & 2)) {
+	    zwarnnam(nam, "function is not loaded: %s", shf->nam, 0);
+	    return 1;
+	}
+	noaliases = (shf->flags & PM_UNALIASED);
+	if (!(prog = getfpfunc(shf->nam, NULL)) || prog == &dummy_eprog) {
+	    noaliases = ona;
+	    zwarnnam(nam, "can't load function: %s", shf->nam, 0);
+	    return 1;
+	}
+	if (prog->dump)
+	    prog = dupeprog(prog, 1);
+	noaliases = ona;
+    } else {
+	if (!(what & 1)) {
+	    zwarnnam(nam, "function is already loaded: %s", shf->nam, 0);
+	    return 1;
+	}
+	prog = dupeprog(shf->funcdef, 1);
+    }
+    wcf = (WCFunc) zhalloc(sizeof(*wcf));
+    wcf->name = shf->nam;
+    wcf->prog = prog;
+    wcf->flags = ((prog->flags & EF_RUN) ? FDHF_KSHLOAD : FDHF_ZSHLOAD);
+    addlinknode(progs, wcf);
+    addlinknode(names, shf->nam);
+
+    *hlen += ((sizeof(struct fdhead) / sizeof(wordcode)) +
+	      ((strlen(shf->nam) + sizeof(wordcode)) / sizeof(wordcode)));
+    *tlen += (prog->len - (prog->npats * sizeof(Patprog)) +
+	      sizeof(wordcode) - 1) / sizeof(wordcode);
+
+    return 0;
 }
 
 /**/
-static Cond
-par_cond_triple(char *a, char *b, char *c)
+static int
+build_cur_dump(char *nam, char *dump, char **names, int match, int map,
+	       int what)
 {
-    Cond n = (Cond) make_cond();
-    int t0;
+    int dfd, hlen, tlen;
+    LinkList progs, lnames;
+    Shfunc shf = NULL;
 
-    if ((b[0] == Equals || b[0] == '=') &&
-	(!b[1] || ((b[1] == Equals || b[1] == '=') && !b[2])))
-	n->type = COND_STREQ;
-    else if (b[0] == '!' && (b[1] == Equals || b[1] == '=') && !b[2])
-	n->type = COND_STRNEQ;
-    else if (b[0] == '-') {
-	if ((t0 = get_cond_num(b + 1)) > -1)
-	    n->type = t0 + COND_NT;
-	else
-	    COND_ERROR("unrecognized condition: %s", b);
+    if (!strsfx(FD_EXT, dump))
+	dump = dyncat(dump, FD_EXT);
+
+    if ((dfd = open(dump, O_WRONLY|O_CREAT, 0600)) < 0) {
+	zwarnnam(nam, "can't write dump file: %s", dump, 0);
+	return 1;
+    }
+    progs = newlinklist();
+    lnames = newlinklist();
+
+    hlen = FD_PRELEN;
+    tlen = 0;
+
+    if (!*names) {
+	int i;
+	HashNode hn;
+
+	for (i = 0; i < shfunctab->hsize; i++)
+	    for (hn = shfunctab->nodes[i]; hn; hn = hn->next)
+		if (cur_add_func(nam, (Shfunc) hn, lnames, progs,
+				 &hlen, &tlen, what)) {
+		    errflag = 0;
+		    close(dfd);
+		    unlink(dump);
+		    return 1;
+		}
+    } else if (match) {
+	char *pat;
+	Patprog pprog;
+	int i;
+	HashNode hn;
+
+	for (; *names; names++) {
+	    tokenize(pat = dupstring(*names));
+	    if (!(pprog = patcompile(pat, PAT_STATIC, NULL))) {
+		zwarnnam(nam, "bad pattern: %s", *names, 0);
+		close(dfd);
+		unlink(dump);
+		return 1;
+	    }
+	    for (i = 0; i < shfunctab->hsize; i++)
+		for (hn = shfunctab->nodes[i]; hn; hn = hn->next)
+		    if (!listcontains(lnames, hn->nam) &&
+			pattry(pprog, hn->nam) &&
+			cur_add_func(nam, (Shfunc) hn, lnames, progs,
+				     &hlen, &tlen, what)) {
+			errflag = 0;
+			close(dfd);
+			unlink(dump);
+			return 1;
+		    }
+	}
+    } else {
+	for (; *names; names++) {
+	    if (errflag ||
+		!(shf = (Shfunc) shfunctab->getnode(shfunctab, *names))) {
+		zwarnnam(nam, "unknown function: %s", *names, 0);
+		errflag = 0;
+		close(dfd);
+		unlink(dump);
+		return 1;
+	    }
+	    if (cur_add_func(nam, shf, lnames, progs, &hlen, &tlen, what)) {
+		errflag = 0;
+		close(dfd);
+		unlink(dump);
+		return 1;
+	    }
+	}
+    }
+    if (empty(progs)) {
+	zwarnnam(nam, "no functions", NULL, 0);
+	errflag = 0;
+	close(dfd);
+	unlink(dump);
+	return 1;
+    }
+    tlen = (tlen + hlen) * sizeof(wordcode);
+
+    write_dump(dfd, progs, map, hlen, tlen);
+
+    close(dfd);
+
+    return 0;
+}
+
+#if defined(HAVE_SYS_MMAN_H) && defined(HAVE_MMAP) && defined(HAVE_MUNMAP)
+
+#include <sys/mman.h>
+
+#if defined(MAP_SHARED) && defined(PROT_READ)
+
+#define USE_MMAP 1
+
+#endif
+#endif
+
+#ifdef USE_MMAP
+
+/* List of dump files mapped. */
+
+static FuncDump dumps;
+
+/* Load a dump file (i.e. map it). */
+
+static void
+load_dump_file(char *dump, int other, int len)
+{
+    FuncDump d;
+    Wordcode addr;
+    int fd, off;
+
+    if (other) {
+	static size_t pgsz = 0;
+
+	if (!pgsz) {
+
+#ifdef _SC_PAGESIZE
+	    pgsz = sysconf(_SC_PAGESIZE);     /* SVR4 */
+#else
+# ifdef _SC_PAGE_SIZE
+	    pgsz = sysconf(_SC_PAGE_SIZE);    /* HPUX */
+# else
+	    pgsz = getpagesize();
+# endif
+#endif
+
+	    pgsz--;
+	}
+	off = len & ~pgsz;
     } else
-	COND_ERROR("condition expected: %s", b);
-    n->left = (void *) a;
-    n->right = (void *) c;
-    n->ntype = NT_SET(N_COND, NT_STR, NT_STR, 0, 0);
-    return n;
+	off = 0;
+
+    if ((fd = open(dump, O_RDONLY)) < 0)
+	return;
+
+    fd = movefd(fd);
+
+    if ((addr = (Wordcode) mmap(NULL, len, PROT_READ, MAP_SHARED, fd, off)) ==
+	((Wordcode) -1)) {
+	close(fd);
+	return;
+    }
+    d = (FuncDump) zalloc(sizeof(*d));
+    d->next = dumps;
+    dumps = d;
+    d->name = ztrdup(dump);
+    d->fd = fd;
+    d->map = addr + (other ? (len - off) / sizeof(wordcode) : 0);
+    d->addr = addr;
+    d->len = len;
+    d->count = 0;
 }
 
+#endif
+
+/* Try to load a function from one of the possible wordcode files for it.
+ * The first argument is a element of $fpath, the second one is the name
+ * of the function searched and the last one is the possible name for the
+ * uncompiled function file (<path>/<func>). */
+
 /**/
-static void
-yyerror(void)
+Eprog
+try_dump_file(char *path, char *name, char *file, int *ksh)
 {
-    int t0;
+    Eprog prog;
+    struct stat std, stc, stn;
+    int rd, rc, rn;
+    char *dig, *wc;
+
+    if (strsfx(FD_EXT, path))
+	return check_dump_file(path, name, ksh);
+
+    dig = dyncat(path, FD_EXT);
+    wc = dyncat(file, FD_EXT);
+
+    rd = stat(dig, &std);
+    rc = stat(wc, &stc);
+    rn = stat(file, &stn);
+
+    /* See if there is a digest file for the directory, it is younger than
+     * both the uncompiled function file and its compiled version (or they
+     * don't exist) and the digest file contains the definition for the
+     * function. */
+    if (!rd &&
+	(rc || std.st_mtime > stc.st_mtime) &&
+	(rn || std.st_mtime > stn.st_mtime) &&
+	(prog = check_dump_file(dig, name, ksh)))
+	return prog;
+
+    /* No digest file. Now look for the per-function compiled file. */
+    if (!rc &&
+	(rn || stc.st_mtime > stn.st_mtime) &&
+	(prog = check_dump_file(wc, name, ksh)))
+	return prog;
+
+    /* No compiled file for the function. The caller (getfpfunc() will
+     * check if the directory contains the uncompiled file for it. */
+    return NULL;
+}
 
-    for (t0 = 0; t0 != 20; t0++)
-	if (!yytext || !yytext[t0] || yytext[t0] == '\n')
-	    break;
-    if (t0 == 20)
-	zerr("parse error near `%l...'", yytext, 20);
-    else if (t0)
-	zerr("parse error near `%l'", yytext, t0);
+/* Almost the same, but for sourced files. */
+
+/**/
+Eprog
+try_source_file(char *file)
+{
+    Eprog prog;
+    struct stat stc, stn;
+    int rc, rn;
+    char *wc, *tail;
+
+    if ((tail = strrchr(file, '/')))
+	tail++;
     else
-	zerr("parse error", NULL, 0);
+	tail = file;
+
+    if (strsfx(FD_EXT, file))
+	return check_dump_file(file, tail, NULL);
+
+    wc = dyncat(file, FD_EXT);
+
+    rc = stat(wc, &stc);
+    rn = stat(file, &stn);
+
+    if (!rc && (rn || stc.st_mtime > stn.st_mtime) &&
+	(prog = check_dump_file(wc, tail, NULL)))
+	return prog;
+
+    return NULL;
+}
+
+/* See if `file' names a wordcode dump file and that contains the
+ * definition for the function `name'. If so, return an eprog for it. */
+
+/**/
+static Eprog
+check_dump_file(char *file, char *name, int *ksh)
+{
+    int isrec = 0;
+    Wordcode d;
+    FDHead h;
+    FuncDump f;
+
+#ifdef USE_MMAP
+
+ rec:
+
+#endif
+
+    d = NULL;
+
+#ifdef USE_MMAP
+
+    for (f = dumps; f; f = f->next)
+	if (!strcmp(file, f->name)) {
+	    d = f->map;
+	    break;
+	}
+
+#else
+
+    f = NULL;
+
+#endif
+
+    if (!f && (isrec || !(d = load_dump_header(file))))
+	return NULL;
+
+    if ((h = dump_find_func(d, name))) {
+	/* Found the name. If the file is already mapped, return the eprog,
+	 * otherwise map it and just go up. */
+
+#ifdef USE_MMAP
+
+	if (f) {
+	    Eprog prog = (Eprog) zalloc(sizeof(*prog));
+	    Patprog *pp;
+	    int np;
+
+	    prog->flags = EF_MAP;
+	    prog->len = h->len;
+	    prog->npats = np = h->npats;
+	    prog->pats = pp = (Patprog *) zalloc(np * sizeof(Patprog));
+	    prog->prog = f->map + h->start;
+	    prog->strs = ((char *) prog->prog) + h->strs;
+	    prog->shf = NULL;
+	    prog->dump = f;
+
+	    incrdumpcount(f);
+
+	    while (np--)
+		*pp++ = dummy_patprog1;
+
+	    if (ksh)
+		*ksh = ((fdhflags(h) & FDHF_KSHLOAD) ? 2 :
+			((fdhflags(h) & FDHF_ZSHLOAD) ? 0 : 1));
+
+	    return prog;
+	} else if (fdflags(d) & FDF_MAP) {
+	    load_dump_file(file, (fdflags(d) & FDF_OTHER), fdother(d));
+	    isrec = 1;
+	    goto rec;
+	} else
+
+#endif
+
+	    {
+	    Eprog prog;
+	    Patprog *pp;
+	    int np, fd, po = h->npats * sizeof(Patprog);
+
+	    if ((fd = open(file, O_RDONLY)) < 0 ||
+		lseek(fd, ((h->start * sizeof(wordcode)) +
+			   ((fdflags(d) & FDF_OTHER) ? fdother(d) : 0)), 0) < 0) {
+		if (fd >= 0)
+		    close(fd);
+		return NULL;
+	    }
+	    d = (Wordcode) zalloc(h->len + po);
+
+	    if (read(fd, ((char *) d) + po, h->len) != h->len) {
+		close(fd);
+		zfree(d, h->len);
+
+		return NULL;
+	    }
+	    close(fd);
+
+	    prog = (Eprog) zalloc(sizeof(*prog));
+
+	    prog->flags = EF_REAL;
+	    prog->len = h->len + po;
+	    prog->npats = np = h->npats;
+	    prog->pats = pp = (Patprog *) d;
+	    prog->prog = (Wordcode) (((char *) d) + po);
+	    prog->strs = ((char *) prog->prog) + h->strs;
+	    prog->shf = NULL;
+	    prog->dump = f;
+
+	    while (np--)
+		*pp++ = dummy_patprog1;
+
+	    if (ksh)
+		*ksh = ((fdhflags(h) & FDHF_KSHLOAD) ? 2 :
+			((fdhflags(h) & FDHF_ZSHLOAD) ? 0 : 1));
+
+	    return prog;
+	}
+    }
+    return NULL;
+}
+
+#ifdef USE_MMAP
+
+/* Increment the reference counter for a dump file. */
+
+/**/
+void
+incrdumpcount(FuncDump f)
+{
+    f->count++;
+}
+
+/* Decrement the reference counter for a dump file. If zero, unmap the file. */
+
+/**/
+void
+decrdumpcount(FuncDump f)
+{
+    f->count--;
+    if (!f->count) {
+	FuncDump p, q;
+
+	for (q = NULL, p = dumps; p && p != f; q = p, p = p->next);
+	if (p) {
+	    if (q)
+		q->next = p->next;
+	    else
+		dumps = p->next;
+	    munmap((void *) f->addr, f->len);
+	    zclose(f->fd);
+	    zsfree(f->name);
+	    zfree(f, sizeof(*f));
+	}
+    }
+}
+
+#else
+
+void
+incrdumpcount(FuncDump f)
+{
+}
+
+void
+decrdumpcount(FuncDump f)
+{
+}
+
+#endif
+
+/**/
+int
+dump_autoload(char *file, int on, char *ops, int func)
+{
+    Wordcode h;
+    FDHead n, e;
+    Shfunc shf;
+    int ret = 0;
+
+    if (!strsfx(FD_EXT, file))
+	file = dyncat(file, FD_EXT);
+
+    if (!(h = load_dump_header(file)))
+	return 1;
+
+    for (n = firstfdhead(h), e = (FDHead) (h + fdheaderlen(h)); n < e;
+	 n = nextfdhead(n)) {
+	shf = (Shfunc) zcalloc(sizeof *shf);
+	shf->flags = on;
+	shf->funcdef = mkautofn(shf);
+	shfunctab->addnode(shfunctab, ztrdup(fdname(n) + fdhtail(n)), shf);
+	if (ops['X'] && eval_autoload(shf, shf->nam, ops, func))
+	    ret = 1;
+    }
+    return ret;
 }
diff --git a/Src/pattern.c b/Src/pattern.c
index 048e3d3ec..914479847 100644
--- a/Src/pattern.c
+++ b/Src/pattern.c
@@ -1,5 +1,5 @@
 /*
- * glob.c - filename generation
+ * pattern.c - pattern matching
  *
  * This file is part of zsh, the Z shell.
  *
@@ -50,13 +50,28 @@
  */
 
 #include "zsh.mdh"
-#include "pattern.pro"
 
 /*
- * Globbing flags: lower 8 bits gives approx count
+ * The following union is used mostly for alignment purposes.
+ * Normal nodes are longs, while certain nodes take a char * as an argument;
+ * here we make sure that they both work out to the same length.
+ * The compiled regexp we construct consists of upats stuck together;
+ * anything else to be added (strings, numbers) is stuck after and
+ * then aligned to a whole number of upat units.
+ *
+ * Note also that offsets are in terms of the sizes of these things.
  */
-#define C_LCMATCHUC	0x0100
-#define C_IGNCASE	0x0200
+union upat {
+    long l;
+    unsigned char *p;
+};
+
+typedef union upat *Upat;
+
+#include "pattern.pro"
+
+/* Number of active parenthesised expressions allowed in backreferencing */
+#define NSUBEXP  9
 
 /* definition	number	opnd?	meaning */
 #define	P_END	  0x00	/* no	End of program. */
@@ -154,36 +169,17 @@
 #define PP_UNKWN  13
 #define PP_RANGE  14
 
-/* Align everything to the pointer type. */
-typedef char *zalign_t;
-
-#define	P_OP(p)		(*(long *)(p) & 0xff)
-#define	P_NEXT(p)	(*(long *)(p) >> 8)
-#define	P_OPERAND(p)	((p) + sizeof(zalign_t))
-#define P_ISBRANCH(p)   (*(long *)(p) & 0x20)
-#define P_ISEXCLUDE(p)	((*(long *)(p) & 0x30) == 0x30)
-#define P_NOTDOT(p)	(*(long *)(p) & 0x40)
+#define	P_OP(p)		((p)->l & 0xff)
+#define	P_NEXT(p)	((p)->l >> 8)
+#define	P_OPERAND(p)	((p) + 1)
+#define P_ISBRANCH(p)   ((p)->l & 0x20)
+#define P_ISEXCLUDE(p)	(((p)->l & 0x30) == 0x30)
+#define P_NOTDOT(p)	((p)->l & 0x40)
 
 #define P_SIMPLE        0x01	/* Simple enough to be #/## operand. */
 #define P_HSTART        0x02	/* Starts with # or ##'d pattern. */
 #define P_PURESTR	0x04	/* Can be matched with a strcmp */
 
-/*
- * pointer is end string to be parsed?
- * a bit dire because of extendedglob possibilities:
- * we need to make sure a ~ at the end of a string isn't mistaken
- * for an excluder or lots of emacs users get very cross.
- */
-#define ISENDCHAR(X) (!*(X) || ((patflags & PAT_FILE) && *(X) == '/') || \
-		      *(X) == Bar || *(X) == Outpar || \
-		      (isset(EXTENDEDGLOB) && \
-		       (*(X) == Hat || \
-			(*(X) == Tilde && \
-			 !(!(X)[1] || ((patflags & PAT_FILE) && \
-				       (X)[1] == '/') || \
-			   (X)[1] == Bar || (X)[1] == Outpar || \
-			   (X)[1] == Tilde)))))
-
 /* Next character after one which may be a Meta (x is any char *) */
 #define METANEXT(x)	(*(x) == Meta ? (x)+2 : (x)+1)
 /*
@@ -202,6 +198,36 @@ typedef zlong zrange_t;
 typedef unsigned long zrange_t;
 #endif
 
+/*
+ * Characters which terminate a pattern segment.  We actually use
+ * a pointer patendseg which skips the first character if we are not
+ * parsing a file pattern.
+ * Note that the size of this and the next array are hard-wired
+ * via the definitions.
+ */
+
+static char endseg[] = {
+    '/',			/* file only */
+    '\0', Bar, Outpar,		/* all patterns */
+    Tilde			/* extended glob only */
+};
+
+#define PATENDSEGLEN_NORM 4
+#define PATENDSEGLEN_EXT  5
+
+/* Characters which terminate a simple string */
+
+static char endstr[] = {
+    '/',			/* file only */
+    '\0', Bar, Outpar, Quest, Star, Inbrack, Inpar, Inang,
+				/* all patterns */
+    Tilde, Hat, Pound		/* extended glob only */
+};
+
+#define PATENDSTRLEN_NORM 9
+#define PATENDSTRLEN_EXT  12
+
+
 /* Default size for pattern buffer */
 #define P_DEF_ALLOC 256
 
@@ -212,6 +238,10 @@ static char *patcode;		/* point of code emission */
 static long patsize;		/* size of code */
 static char *patout;		/* start of code emission string */
 static long patalloc;		/* size allocated for same */
+static char *patendseg;		/* characters ending segment */
+static int patendseglen;	/* length of same */
+static char *patendstr;		/* characters ending plain string */
+static int patendstrlen;	/* length of sameo */
 
 /* Flags used in both compilation and execution */
 static int patflags;		    /* flags passed down to patcompile */
@@ -226,8 +256,8 @@ patadd(char *add, int ch, long n, int noalgn)
     /* Make sure everything gets aligned unless we get noalgn. */
     long newpatsize = patsize + n;
     if (!noalgn)
-	newpatsize = (newpatsize + sizeof(zalign_t) - 1) &
-		      ~(sizeof(zalign_t) - 1);
+	newpatsize = (newpatsize + sizeof(union upat) - 1) &
+		      ~(sizeof(union upat) - 1);
     if (patalloc < newpatsize) {
 	long newpatalloc =
 	    2*(newpatsize > patalloc ? newpatsize : patalloc);
@@ -245,7 +275,7 @@ patadd(char *add, int ch, long n, int noalgn)
 }
 
 static long rn_offs;
-/* operates on poiners, returns a pointer */
+/* operates on pointers to union upat, returns a pointer */
 #define PATNEXT(p) ((rn_offs = P_NEXT(p)) ? \
 		    (P_OP(p) == P_BACK) ? \
 		    ((p)-rn_offs) : ((p)+rn_offs) : NULL)
@@ -262,24 +292,18 @@ patcompstart(void)
 /* Top level pattern compilation subroutine */
 
 /**/
-Patprog
+mod_export Patprog
 patcompile(char *exp, int inflags, char **endexp)
 {
-    int flags, len;
+    int flags = 0, len = 0;
     long startoff;
-    char *pscan, *lng;
+    Upat pscan;
+    char *lng, *strp = NULL;
     Patprog p;
 
-    DPUTS(sizeof(long) > sizeof(zalign_t), "BUG: patprog alignment too small");
-
-#ifdef BACKREFERENCES
-    startoff = (inflags & PAT_BACKR) ? sizeof(struct patprog) :
-	sizeof(struct patprog_short);
-#else
     startoff = sizeof(struct patprog);
-#endif
     /* Ensure alignment of start of program string */
-    startoff = (startoff + sizeof(zalign_t) - 1) & ~(sizeof(zalign_t) - 1);
+    startoff = (startoff + sizeof(union upat) - 1) & ~(sizeof(union upat) - 1);
 
     /* Allocate reasonable sized chunk if none, reduce size if too big */
     if (patalloc != P_DEF_ALLOC)
@@ -287,10 +311,23 @@ patcompile(char *exp, int inflags, char **endexp)
     patcode = patout + startoff;
     patsize = patcode - patout;
     patstart = patparse = exp;
+    /*
+     * Note global patnpar numbers parentheses 1..9, while patnpar
+     * in struct is actual count of parentheses.
+     */
     patnpar = 1;
-    patflags = inflags;
+    patflags = inflags & ~PAT_PURES;
+
+    patendseg = endseg;
+    patendseglen = isset(EXTENDEDGLOB) ? PATENDSEGLEN_EXT : PATENDSEGLEN_NORM;
+    patendstr = endstr;
+    patendstrlen = isset(EXTENDEDGLOB) ? PATENDSTRLEN_EXT : PATENDSTRLEN_NORM;
 
     if (!(patflags & PAT_FILE)) {
+	patendseg++;
+	patendstr++;
+	patendseglen--;
+	patendstrlen--;
 	remnulargs(exp);
 	patglobflags = 0;
     }
@@ -299,65 +336,88 @@ patcompile(char *exp, int inflags, char **endexp)
      */
     ((Patprog)patout)->globflags = patglobflags;
 
-    if (patflags & PAT_ANY)
-	flags = 0;
-    else if (patcompswitch(0, &flags) == 0)
-	return NULL;
+    if (!(patflags & PAT_ANY)) {
+	/* Look for a really pure string, with no tokens at all. */
+	if (!patglobflags)
+	    for (strp = exp; *strp &&
+		     (!(patflags & PAT_FILE) || *strp != '/') && !itok(*strp);
+		 strp++)
+		;
+	if (!strp || (*strp && *strp != '/')) {
+	    /* No, do normal compilation. */
+	    strp = NULL;
+	    if (patcompswitch(0, &flags) == 0)
+		return NULL;
+	} else {
+	    /* Yes, copy the string and skip compilation altogether */
+	    patparse = strp;
+	    len = strp - exp;
+	    patadd(exp, 0, len + 1, 0);
+	    patout[startoff + len] = '\0';
+	    patflags |= PAT_PURES;
+	}
+    }
 
     /* end of compilation: safe to use pointers */
     p = (Patprog)patout;
     p->startoff = startoff;
     p->patstartch = '\0';
     p->globend = patglobflags;
-    p->flags = (patflags & ~PAT_PURES);
+    p->flags = patflags;
     p->mustoff = 0;
     p->size = patsize;
-    p->patmlen = 0;
-    pscan = patout + startoff;
+    p->patmlen = len;
+    p->patnpar = patnpar-1;
 
-    if (!(patflags & PAT_ANY) && P_OP(PATNEXT(pscan)) == P_END) {
-	/* only one top level choice */
-	pscan = P_OPERAND(pscan);
+    if (!strp) {
+	pscan = (Upat)(patout + startoff);
 
-	if (flags & P_PURESTR) {
-	    /*
-	     * The pattern can be matched with a simple strncmp/strcmp.
-	     * Careful in case we've overwritten the node for the next ptr.
-	     */
-	    char *dst = patout + startoff, *next;
-	    p->flags |= PAT_PURES;
-	    for (; pscan; pscan = next) {
-		next = PATNEXT(pscan);
-		if (P_OP(pscan) == P_EXACTLY) {
-		    char *opnd = P_OPERAND(pscan);
-		    while ((*dst = *opnd++))
-			dst++;
+	if (!(patflags & PAT_ANY) && P_OP(PATNEXT(pscan)) == P_END) {
+	    /* only one top level choice */
+	    pscan = P_OPERAND(pscan);
+
+	    if (flags & P_PURESTR) {
+		/*
+		 * The pattern can be matched with a simple strncmp/strcmp.
+		 * Careful in case we've overwritten the node for the next ptr.
+		 */
+		char *dst = patout + startoff;
+		Upat next;
+		p->flags |= PAT_PURES;
+		for (; pscan; pscan = next) {
+		    next = PATNEXT(pscan);
+		    if (P_OP(pscan) == P_EXACTLY) {
+			char *opnd = (char *)P_OPERAND(pscan);
+			while ((*dst = *opnd++))
+			    dst++;
+		    }
 		}
-	    }
-	    *dst++ = '\0';
-	    p->size = dst - patout;
-	    /* patmlen is reall strlen, don't include null byte */
-	    p->patmlen = p->size - startoff - 1;
-	} else {
-	    /* starting point info */
-	    if (P_OP(pscan) == P_EXACTLY && !p->globflags)
-		p->patstartch = *P_OPERAND(pscan);
-	    /* Find the longest literal string in something expensive.
-	     * This is itself not all that cheap if we have case-insensitive
-	     * matching or approximation, so don't.
-	     */
-	    if ((flags & P_HSTART) && !p->globflags) {
-		lng = NULL;
-		len = 0;
-		for (; pscan; pscan = PATNEXT(pscan))
-		    if (P_OP(pscan) == P_EXACTLY &&
-			strlen((char *)P_OPERAND(pscan)) >= len) {
-			lng = P_OPERAND(pscan);
-			len = strlen((char *)P_OPERAND(pscan));
+		*dst++ = '\0';
+		p->size = dst - patout;
+		/* patmlen is really strlen, don't include null byte */
+		p->patmlen = p->size - startoff - 1;
+	    } else {
+		/* starting point info */
+		if (P_OP(pscan) == P_EXACTLY && !p->globflags)
+		    p->patstartch = *(char *)P_OPERAND(pscan);
+		/*
+		 * Find the longest literal string in something expensive.
+		 * This is itself not all that cheap if we have
+		 * case-insensitive matching or approximation, so don't.
+		 */
+		if ((flags & P_HSTART) && !p->globflags) {
+		    lng = NULL;
+		    len = 0;
+		    for (; pscan; pscan = PATNEXT(pscan))
+			if (P_OP(pscan) == P_EXACTLY &&
+			    strlen((char *)P_OPERAND(pscan)) >= len) {
+			    lng = (char *)P_OPERAND(pscan);
+			    len = strlen(lng);
+			}
+		    if (lng) {
+			p->mustoff = lng - patout;
+			p->patmlen = len;
 		    }
-		if (lng) {
-		    p->mustoff = lng - patout;
-		    p->patmlen = len;
 		}
 	    }
 	}
@@ -367,8 +427,13 @@ patcompile(char *exp, int inflags, char **endexp)
      * The pattern was compiled in a fixed buffer:  unless told otherwise,
      * we stick the compiled pattern on the heap.  This is necessary
      * for files where we will often be compiling multiple segments at once.
+     * But if we get the ZDUP flag w always put it in zalloc()ed memory.
      */
-    if (!(patflags & PAT_STATIC)) {
+    if (patflags & PAT_ZDUP) {
+	Patprog newp = (Patprog)zalloc(patsize);
+	memcpy((char *)newp, (char *)p, patsize);
+	p = newp;
+    } else if (!(patflags & PAT_STATIC)) {
 	Patprog newp = (Patprog)zhalloc(patsize);
 	memcpy((char *)newp, (char *)p, patsize);
 	p = newp;
@@ -389,26 +454,22 @@ static long
 patcompswitch(int paren, int *flagp)
 {
     long starter, br, ender, excsync = 0;
-#ifdef BACKREFERENCES
     int parno = 0;
-#endif
-    int flags, gfchanged = 0, savflags = patflags, savglobflags = patglobflags;
-    char *ptr;
+    int flags, gfchanged = 0, savglobflags = patglobflags;
+    Upat ptr;
 
     *flagp = 0;
 
-#ifdef BACKREFERENCES
-    if (paren && (patflags & PAT_BACKR)) {
+    if (paren && (patglobflags & GF_BACKREF) && patnpar <= NSUBEXP) {
 	/*
 	 * parenthesized:  make an open node.
 	 * We can only refer to the first nine parentheses.
 	 * For any others, we just use P_OPEN on its own; there's
 	 * no gain in arbitrarily limiting the number of parentheses.
 	 */
-	parno = patnpar >= NSUBEXP ? 0 : patnpar++;
+	parno = patnpar++;
 	starter = patnode(P_OPEN + parno);
     } else
-#endif
 	starter = 0;
 
     br = patnode(P_BRANCH);
@@ -424,17 +485,16 @@ patcompswitch(int paren, int *flagp)
     *flagp |= flags & (P_HSTART|P_PURESTR);
 
     while (*patparse == Bar ||
-	   (isset(EXTENDEDGLOB) &&
-	    *patparse == Tilde && patparse[1] && patparse[1] != Bar &&
-	    patparse[1] != Outpar && patparse[1] != Tilde &&
-	    !((patflags & PAT_FILE) && patparse[1] == '/'))) {
+	   (isset(EXTENDEDGLOB) && *patparse == Tilde &&
+	    (patparse[1] == '/' ||
+	     !memchr(patendseg, patparse[1], patendseglen)))) {
 	int tilde = *patparse++ == Tilde;
 	long gfnode = 0, newbr;
 
 	*flagp &= ~P_PURESTR;
 
 	if (tilde) {
-	    unsigned char *unull = NULL;
+	    union upat up;
 	    /* excsync remembers the P_EXCSYNC node before a chain of
 	     * exclusions:  all point back to this.  only the
 	     * original (non-excluded) branch gets a trailing P_EXCSYNC.
@@ -455,10 +515,16 @@ patcompswitch(int paren, int *flagp)
 	    patglobflags &= ~0xff;
 	    br = patnode(!(patflags & PAT_FILET) || paren ?
 			 P_EXCLUDE : P_EXCLUDP);
-	    patadd((char *)&unull, 0, sizeof(unull), 0);
+	    up.p = NULL;
+	    patadd((char *)&up, 0, sizeof(up), 0);
 	    /* / is not treated as special if we are at top level */
-	    if (!paren)
-		patflags &= ~PAT_FILE;
+	    if (!paren && *patendseg == '/') {
+		tilde++;
+		patendseg++;
+		patendseglen--;
+		patendstr++;
+		patendstrlen--;
+	    }
 	} else {
 	    excsync = 0;
 	    br = patnode(P_BRANCH);
@@ -485,16 +551,23 @@ patcompswitch(int paren, int *flagp)
 		     * No gfchanged, as nothing to follow branch at top
 		     * level. 
 		     */
+		    union upat up;
 		    gfnode = patnode(P_GFLAGS);
-		    patadd((char *)&patglobflags, 0, sizeof(long),
-			   0);
+		    up.l = patglobflags;
+		    patadd((char *)&up, 0, sizeof(union upat), 0);
 		}
 	    } else {
 		patglobflags = savglobflags;
 	    }
 	}
 	newbr = patcompbranch(&flags);
-	patflags = savflags;
+	if (tilde == 2) {
+	    /* restore special treatment of / */
+	    patendseg--;
+	    patendseglen++;
+	    patendstr--;
+	    patendstrlen++;
+	}
 	if (!newbr)
 	    return 0;
 	if (gfnode)
@@ -513,21 +586,16 @@ patcompswitch(int paren, int *flagp)
      * branch at that point would indicate the current choices continue,
      * which they don't.
      */
-#ifdef BACKREFERENCES
-    ender = patnode(paren ? (patflags & PAT_BACKR) ? P_CLOSE+parno
-		    : P_NOTHING : P_END);
-#else
-    ender = patnode(paren ? P_NOTHING : P_END);
-#endif
+    ender = patnode(paren ? parno ? P_CLOSE+parno : P_NOTHING : P_END);
     pattail(starter, ender);
 
     /*
      * Hook the tails of the branches to the closing node,
      * except for exclusions which terminate where they are.
      */
-    for (ptr = patout + starter; ptr; ptr = PATNEXT(ptr))
+    for (ptr = (Upat)patout + starter; ptr; ptr = PATNEXT(ptr))
 	if (!P_ISEXCLUDE(ptr))
-	    patoptail(ptr-patout, ender);
+	    patoptail(ptr-(Upat)patout, ender);
 
     /* check for proper termination */
     if ((paren && *patparse++ != Outpar) ||
@@ -566,12 +634,9 @@ patcompbranch(int *flagp)
     *flagp = P_PURESTR;
 
     starter = chain = 0;
-    while (*patparse && !((patflags & PAT_FILE) && *patparse == '/') &&
-	   *patparse != Bar && *patparse != Outpar &&
-	   (!isset(EXTENDEDGLOB) || *patparse != Tilde ||
-	    !patparse[1] || patparse[1] == Bar || patparse[1] == Outpar
-	    || patparse[1] == Tilde ||
-	    ((patflags & PAT_FILE) && patparse[1] == '/'))) {
+    while (!memchr(patendseg, *patparse, patendseglen) ||
+	   (*patparse == Tilde && patparse[1] != '/' &&
+	    memchr(patendseg, patparse[1], patendseglen))) {
 	if (isset(EXTENDEDGLOB) &&
 	    ((!isset(SHGLOB) &&
 	      (*patparse == Inpar && patparse[1] == Pound)) ||
@@ -601,8 +666,10 @@ patcompbranch(int *flagp)
 	     */
 	    if (oldglobflags != patglobflags) {
 		/* Flags changed */
+		union upat up;
 		latest = patnode(P_GFLAGS);
-		patadd((char *)&patglobflags, 0, sizeof(int), 0);
+		up.l = patglobflags;
+		patadd((char *)&up, 0, sizeof(union upat), 0);
 	    } else {
 		/* No effect. */
 		continue;
@@ -663,17 +730,37 @@ patgetglobflags(char **strp)
 
 	case 'l':
 	    /* Lowercase in pattern matches lower or upper in target */
-	    patglobflags = (patglobflags & ~C_IGNCASE) | C_LCMATCHUC;
+	    patglobflags = (patglobflags & ~GF_IGNCASE) | GF_LCMATCHUC;
 	    break;
 
 	case 'i':
 	    /* Fully case insensitive */
-	    patglobflags = (patglobflags & ~C_LCMATCHUC) | C_IGNCASE;
+	    patglobflags = (patglobflags & ~GF_LCMATCHUC) | GF_IGNCASE;
 	    break;
 
 	case 'I':
 	    /* Restore case sensitivity */
-	    patglobflags &= ~(C_LCMATCHUC|C_IGNCASE);
+	    patglobflags &= ~(GF_LCMATCHUC|GF_IGNCASE);
+	    break;
+
+	case 'b':
+	    /* Make backreferences */
+	    patglobflags |= GF_BACKREF;
+	    break;
+
+	case 'B':
+	    /* Don't make backreferences */
+	    patglobflags &= ~GF_BACKREF;
+	    break;
+
+	case 'm':
+	    /* Make references to complete match */
+	    patglobflags |= GF_MATCHREF;
+	    break;
+
+	case 'M':
+	    /* Don't */
+	    patglobflags &= ~GF_MATCHREF;
 	    break;
 
 	default:
@@ -696,156 +783,105 @@ static long
 patcomppiece(int *flagp)
 {
     long starter, next, pound, op;
-    int flags, kshchar;
-    unsigned char *ptr;
-
-    starter = patcompatom(&flags, &kshchar);
-    if (!starter)
-	return 0;
-
-    if (!(pound = (*patparse == Pound && isset(EXTENDEDGLOB))) &&
-	(kshchar <= 0 || kshchar == '@' || kshchar == '!')) {
-	*flagp = flags;
-	return starter;
-    }
-
-    /* too much at once doesn't currently work */
-    if (kshchar && pound)
-	return 0;
-
-    if (kshchar == '*') {
-	op = P_ONEHASH;
-	*flagp = P_HSTART;
-    } else if (kshchar == '+') {
-	op = P_TWOHASH;
-	*flagp = P_HSTART;
-    } else if (kshchar == '?') {
-	op = 0;
-	*flagp = 0;
-    } else if (*++patparse == Pound) {
-	op = P_TWOHASH;
-	patparse++;
-	*flagp = P_HSTART;
-    } else {
-	op = P_ONEHASH;
-	*flagp = P_HSTART;
-    }
-
-    /*
-     * Note optimizations with pointers into P_NOTHING branches:  some
-     * should logically point to next node after current piece.
-     *
-     * Backtracking is also encoded in a slightly obscure way:  the
-     * code emitted ensures we test the non-empty branch of complex
-     * patterns before the empty branch on each repetition.  Hence
-     * each time we fail on a non-empty branch, we try the empty branch,
-     * which is equivalent to backtracking.
-     */
-    if ((flags & P_SIMPLE) && op == P_ONEHASH &&
-	P_OP(patout+starter) == P_ANY) {
-	/* Optimize ?# to *.  Silly thing to do, since who would use
-	 * use ?# ? But it makes the later code shorter.
-	 */
-	long *lptr = (long *)(patout + starter);
-	*lptr = (*lptr & ~0xff) | P_STAR;
-    } else if ((flags & P_SIMPLE) && op && !(patglobflags & 0xff)) {
-	/* Don't simplify if we need to look for approximations. */
-	patinsert(op, starter, NULL, 0);
-    } else if (op == P_ONEHASH) {
-	/* Emit x# as (x&|), where & means "self". */
-	ptr = NULL;
-	patinsert(P_WBRANCH, starter, (char *)&ptr, sizeof(ptr));
-	                                      /* Either x */
-	patoptail(starter, patnode(P_BACK));  /* and loop */
-	patoptail(starter, starter);	      /* back */
-	pattail(starter, patnode(P_BRANCH));  /* or */
-	pattail(starter, patnode(P_NOTHING)); /* null. */
-    } else if (op == P_TWOHASH) {
-	/* Emit x## as x(&|) where & means "self". */
-	next = patnode(P_WBRANCH);	      /* Either */
-	ptr = NULL;
-	patadd((char *)&ptr , 0, sizeof(ptr), 0);
-	pattail(starter, next);
-	pattail(patnode(P_BACK), starter);    /* loop back */
-	pattail(next, patnode(P_BRANCH));     /* or */
-	pattail(starter, patnode(P_NOTHING)); /* null. */
-    } else if (kshchar == '?') {
-	/* Emit ?(x) as (x|) */
-	patinsert(P_BRANCH, starter, NULL, 0); /* Either x */
-	pattail(starter, patnode(P_BRANCH));   /* or */
-	next = patnode(P_NOTHING);	       /* null */
-	pattail(starter, next);
-	patoptail(starter, next);
-    }
-    if (*patparse == Pound)
-	return 0;
-
-    return starter;
-}
-
-/*
- * Parse lowest level pattern.  If doing ordinary characters, we
- * gobble a complete string as far as we can get.
- * kshcharp returns a character found before an Inpar, for handling
- * as a closure.
- */
-
-/**/
-static long
-patcompatom(int *flagp, int *kshcharp)
-{
-    long starter;
-    int patch, flags, len, ch;
+    int flags, flags2, kshchar, len, ch, patch;
+    union upat up;
     char *nptr, *str0, cbuf[2];
     zrange_t from, to;
 
-    *flagp = 0;
+    flags = 0;
     str0 = patparse;
     for (;;) {
-	/* check kshglob here */
-	*kshcharp = '\0';
+	/*
+	 * Check if we have a string. First, we need to make sure
+	 * the string doesn't introduce a ksh-like parenthesised expression.
+	 */
+	kshchar = '\0';
 	if (isset(KSHGLOB) && *patparse && patparse[1] == Inpar) {
-	    if (strchr("?*+!@", (char)*patparse))
-		*kshcharp = STOUC(*patparse);
+	    if (strchr("?*+!@", *patparse))
+		kshchar = STOUC(*patparse);
 	    else if (*patparse == Star || *patparse == Quest)
-		*kshcharp = STOUC(ztokens[*patparse - Pound]);
+		kshchar = STOUC(ztokens[*patparse - Pound]);
 	}
 
-	if (patparse > str0) {
-	    /*
-	     * This is up here instead of at the end to simplify the
-	     * kshglob bracket testing.  Note patparse doesn't
-	     * get incremented till afterwards.
-	     */
-	    if (ISENDCHAR(patparse) || *kshcharp || *patparse == Quest ||
-		*patparse == Star || *patparse == Inbrack ||
-		(*patparse == Inpar && !isset(SHGLOB)) ||
-		*patparse == Inang ||
-		(isset(EXTENDEDGLOB) && *patparse == Pound))
-		break;
-	    else {
-		METAINC(patparse);
-		continue;
-	    }
-	}
+	/*
+	 * End of string (or no string at all) if ksh-type parentheses,
+	 * or special character, unless that character is a tilde and
+	 * the character following is an end-of-segment character.  Thus
+	 * tildes are not special if there is nothing following to
+	 * be excluded.
+	 */
+	if (kshchar || (memchr(patendstr, *patparse, patendstrlen) &&
+			(*patparse != Tilde ||
+			 patparse[1] == '/' ||
+			 !memchr(patendseg, patparse[1], patendseglen))))
+	    break;
+
+	METAINC(patparse);
+    }
 
-	if (*kshcharp)
+    if (patparse > str0) {
+	/* Ordinary string: cancel kshchar lookahead */
+	kshchar = '\0';
+	/*
+	 * Assume it matches a simple string until we find otherwise.
+	 */
+	flags |= P_PURESTR;
+	DPUTS(patparse == str0, "BUG: matched nothing in patcomppiece.");
+	/* more than one character matched? */
+	len = str0 + (*str0 == Meta ? 2 : 1) < patparse;
+	/*
+	 * If we have more than one character, a following hash only
+	 * applies to the last, so decrement.
+	 */
+	if (isset(EXTENDEDGLOB) && *patparse == Pound && len)
+	    patparse -= (patparse > str0 + 1 && patparse[-2] == Meta) ? 2 : 1;
+	/*
+	 * If len is 1, we can't have an active # following, so doesn't
+	 * matter that we don't make X in `XX#' simple.
+	 */
+	if (!len)
+	    flags |= P_SIMPLE;
+	starter = patnode(P_EXACTLY);
+	/* add enough space including null byte */
+	len = patparse - str0;
+	patadd(str0, 0, len + 1, 0);
+	nptr = (char *)P_OPERAND((Upat)patout + starter);
+	nptr[len] = '\0';
+	/*
+	 * It's much simpler to turn off pure string mode for
+	 * any case-insensitive or approximate matching; usually,
+	 * that is correct, or they wouldn't have been turned on.
+	 * However, we need to make sure we match a "." or ".."
+	 * in a file name as a pure string.  There's a minor bug
+	 * that this will also apply to something like
+	 * ..(#a1).. (i.e. the (#a1) has no effect), but if you're
+	 * going to write funny patterns, you get no sympathy from me.
+	 */
+	if (patglobflags &&
+	    (!(patflags & PAT_FILE) || (strcmp(nptr, ".") &&
+					strcmp(nptr, ".."))))
+	    flags &= ~P_PURESTR;
+	for (; *nptr; METAINC(nptr))
+	    if (itok(*nptr))
+		*nptr = ztokens[*nptr - Pound];
+    } else {
+	if (kshchar)
 	    patparse++;
 
 	patch = *patparse;
 	METAINC(patparse);
 	switch(patch) {
 	case Quest:
-	    *flagp |= P_SIMPLE;
-	    return patnode(P_ANY);
+	    flags |= P_SIMPLE;
+	    starter = patnode(P_ANY);
 	    break;
 	case Star:
 	    /* kshchar is used as a sign that we can't have #'s. */
-	    *kshcharp = -1;
-	    return patnode(P_STAR);
+	    kshchar = -1;
+	    starter = patnode(P_STAR);
 	    break;
 	case Inbrack:
-	    *flagp |= P_SIMPLE;
+	    flags |= P_SIMPLE;
 	    if (*patparse == Hat || *patparse == '^' || *patparse == '!') {
 		patparse++;
 		starter = patnode(P_ANYBUT);
@@ -919,15 +955,14 @@ patcompatom(int *flagp, int *kshcharp)
 	    patparse++;
 	    /* terminate null string and fix alignment */
 	    patadd(NULL, 0, 1, 0);
-	    return starter;
 	    break;
 	case Inpar:
 	    /* is this how to treat parentheses in SHGLOB? */
-	    if (isset(SHGLOB) && !*kshcharp)
+	    if (isset(SHGLOB) && !kshchar)
 		return 0;
-	    if (*kshcharp == '!') {
+	    if (kshchar == '!') {
 		/* This is nasty, we should really either handle all
-		 * kshglobbing upstairs or down here.  But most of the
+		 * kshglobbing below or here.  But most of the
 		 * others look like non-ksh patterns, while this one
 		 * doesn't, so we handle it here and leave the rest.
 		 * We treat it like an extendedglob ^, except that
@@ -939,12 +974,11 @@ patcompatom(int *flagp, int *kshcharp)
 		 * the expense of allowing the user to do things
 		 * they shouldn't.
 		 */
-		if (!(starter = patcompnot(1, &flags)))
+		if (!(starter = patcompnot(1, &flags2)))
 		    return 0;
-	    } else if (!(starter = patcompswitch(1, &flags)))
+	    } else if (!(starter = patcompswitch(1, &flags2)))
 		return 0;
-	    *flagp |= flags & P_HSTART;
-	    return starter;
+	    flags |= flags2 & P_HSTART;
 	    break;
 	case Inang:
 	    /* Numeric glob */
@@ -990,68 +1024,101 @@ patcompatom(int *flagp, int *kshcharp)
 	     * Mention in manual that matching digits with [...]
 	     * is more efficient.
 	     */
-	    return starter;
 	    break;
 	case Pound:
-	    if (!isset(EXTENDEDGLOB))
-		break;
+	    DPUTS(!isset(EXTENDEDGLOB), "BUG: # not treated as string");
+	    /*
+	     * A hash here is an error; it should follow something
+	     * repeatable.
+	     */
 	    return 0;
 	    break;
 #ifdef DEBUG
-	case Bar:
-	case Outpar:
-	case '\0':
-	    dputs("BUG: wrong character in patcompatom.");
+	default:
+	    dputs("BUG: character not handled in patcomppiece");
 	    return 0;
 	    break;
 #endif
 	}
     }
 
-    /* Simple string: cancel kshchar lookahead */
-    *kshcharp = '\0';
-    /*
-     * Assume it matches a simple string until we find otherwise.
-     */
-    *flagp |= P_PURESTR;
-    DPUTS(patparse == str0, "BUG: matched nothing in patcompatom.");
-    /* more than one character matched? */
-    len = str0 + (*str0 == Meta ? 2 : 1) < patparse;
+    if (!(pound = (*patparse == Pound && isset(EXTENDEDGLOB))) &&
+	(kshchar <= 0 || kshchar == '@' || kshchar == '!')) {
+	*flagp = flags;
+	return starter;
+    }
+
+    /* too much at once doesn't currently work */
+    if (kshchar && pound)
+	return 0;
+
+    if (kshchar == '*') {
+	op = P_ONEHASH;
+	*flagp = P_HSTART;
+    } else if (kshchar == '+') {
+	op = P_TWOHASH;
+	*flagp = P_HSTART;
+    } else if (kshchar == '?') {
+	op = 0;
+	*flagp = 0;
+    } else if (*++patparse == Pound) {
+	op = P_TWOHASH;
+	patparse++;
+	*flagp = P_HSTART;
+    } else {
+	op = P_ONEHASH;
+	*flagp = P_HSTART;
+    }
+
     /*
-     * Ordinary string of characters.
-     * If we have more than one character, a following hash only
-     * applies to the last, so decrement.
-     */
-    if (isset(EXTENDEDGLOB) && *patparse == Pound && len)
-	patparse -= (patparse > str0 + 1 && patparse[-2] == Meta) ? 2 : 1;
-    /* if len is 1, we can't have an active # following, so doesn't
-     * matter that we don't make X in `XX#' simple.
+     * Note optimizations with pointers into P_NOTHING branches:  some
+     * should logically point to next node after current piece.
+     *
+     * Backtracking is also encoded in a slightly obscure way:  the
+     * code emitted ensures we test the non-empty branch of complex
+     * patterns before the empty branch on each repetition.  Hence
+     * each time we fail on a non-empty branch, we try the empty branch,
+     * which is equivalent to backtracking.
      */
-    if (!len)
-	*flagp |= P_SIMPLE;
-    starter = patnode(P_EXACTLY);
-    while (str0 < patparse) {
-	if (*str0 == Meta) {
-	    cbuf[0] = *str0++;
-	    cbuf[1] = *str0++;
-	} else {
-	    cbuf[0] = itok(*str0) ? ztokens[*str0 - Pound] : *str0;
-	    str0++;
-	}
-	ch = UNMETA(cbuf);
-	/*
-	 * HACK: this cause a string consisting of any number of
-	 * dots in files to be matched exactly, even with approximation.
-	 * We just want to limit it to the first two.
+    if ((flags & P_SIMPLE) && op == P_ONEHASH &&
+	P_OP((Upat)patout+starter) == P_ANY) {
+	/* Optimize ?# to *.  Silly thing to do, since who would use
+	 * use ?# ? But it makes the later code shorter.
 	 */
-	if (((patglobflags & C_IGNCASE) && (islower(ch) || isupper(ch))) ||
-	    ((patglobflags & C_LCMATCHUC) && islower(ch)) ||
-	    ((patglobflags & 0xff) && !((patflags & PAT_FILE) && ch == '.')))
-	    *flagp &= ~P_PURESTR;
-	patadd(cbuf, 0, (cbuf[0] == Meta) ? 2 : 1, 1);
+	Upat uptr = (Upat)patout + starter;
+	uptr->l = (uptr->l & ~0xff) | P_STAR;
+    } else if ((flags & P_SIMPLE) && op && !(patglobflags & 0xff)) {
+	/* Don't simplify if we need to look for approximations. */
+	patinsert(op, starter, NULL, 0);
+    } else if (op == P_ONEHASH) {
+	/* Emit x# as (x&|), where & means "self". */
+	up.p = NULL;
+	patinsert(P_WBRANCH, starter, (char *)&up, sizeof(up));
+	                                      /* Either x */
+	patoptail(starter, patnode(P_BACK));  /* and loop */
+	patoptail(starter, starter);	      /* back */
+	pattail(starter, patnode(P_BRANCH));  /* or */
+	pattail(starter, patnode(P_NOTHING)); /* null. */
+    } else if (op == P_TWOHASH) {
+	/* Emit x## as x(&|) where & means "self". */
+	next = patnode(P_WBRANCH);	      /* Either */
+	up.p = NULL;
+	patadd((char *)&up, 0, sizeof(up), 0);
+	pattail(starter, next);
+	pattail(patnode(P_BACK), starter);    /* loop back */
+	pattail(next, patnode(P_BRANCH));     /* or */
+	pattail(starter, patnode(P_NOTHING)); /* null. */
+    } else if (kshchar == '?') {
+	/* Emit ?(x) as (x|) */
+	patinsert(P_BRANCH, starter, NULL, 0); /* Either x */
+	pattail(starter, patnode(P_BRANCH));   /* or */
+	next = patnode(P_NOTHING);	       /* null */
+	pattail(starter, next);
+	patoptail(starter, next);
     }
-    /* null terminate and fix alignment */
-    patadd(NULL, 0, 1, 0);
+    if (*patparse == Pound)
+	return 0;
+
     return starter;
 }
 
@@ -1064,7 +1131,7 @@ patcompatom(int *flagp, int *kshcharp)
 static long
 patcompnot(int paren, int *flagsp)
 {
-    unsigned char *unull = NULL;
+    union upat up;
     long excsync, br, excl, n, starter;
     int dummy;
 
@@ -1076,7 +1143,8 @@ patcompnot(int paren, int *flagsp)
     excsync = patnode(P_EXCSYNC);
     pattail(br, excsync);
     pattail(starter, excl = patnode(P_EXCLUDE));
-    patadd((char *)&unull, 0, sizeof(unull), 0);
+    up.p = NULL;
+    patadd((char *)&up, 0, sizeof(up), 0);
     if (!(br = (paren ? patcompswitch(1, &dummy) : patcompbranch(&dummy))))
 	return 0;
     pattail(br, patnode(P_EXCEND));
@@ -1093,9 +1161,11 @@ patcompnot(int paren, int *flagsp)
 static long
 patnode(long op)
 {
-    long starter = patcode - patout;
+    long starter = (Upat)patcode - (Upat)patout;
+    union upat up;
 
-    patadd((char *)&op, 0, sizeof(long), 0);
+    up.l = op;
+    patadd((char *)&up, 0, sizeof(union upat), 0);
     return starter;
 }
 
@@ -1109,22 +1179,22 @@ static void
 patinsert(long op, int opnd, char *xtra, int sz)
 {
     char *src, *dst, *opdst;
-    long buf, *lptr;
+    union upat buf, *lptr;
 
-    buf = 0;
-    patadd((char *)&buf, 0, sizeof(long), 0);
+    buf.l = 0;
+    patadd((char *)&buf, 0, sizeof(buf), 0);
     if (sz)
 	patadd(xtra, 0, sz, 0);
-    src = patcode - sizeof(long) - sz;
+    src = patcode - sizeof(union upat) - sz;
     dst = patcode;
-    opdst = patout + opnd;
+    opdst = patout + opnd * sizeof(union upat);
     while (src > opdst)
 	*--dst = *--src;
 
     /* A cast can't be an lvalue */
-    lptr = (long *)opdst;
-    *lptr = op;
-    opdst += sizeof(long);
+    lptr = (Upat)opdst;
+    lptr->l = op;
+    opdst += sizeof(union upat);
     while (sz--)
 	*opdst++ = *xtra++;
 }
@@ -1135,10 +1205,10 @@ patinsert(long op, int opnd, char *xtra, int sz)
 static void
 pattail(long p, long val)
 {
-    char *scan, *temp;
-    long offset, *lptr;
+    Upat scan, temp;
+    long offset;
 
-    scan = patout + p;
+    scan = (Upat)patout + p;
     for (;;) {
 	if (!(temp = PATNEXT(scan)))
 	    break;
@@ -1146,10 +1216,9 @@ pattail(long p, long val)
     }
 
     offset = (P_OP(scan) == P_BACK)
-	? (scan - patout) - val : val - (scan - patout);
+	? (scan - (Upat)patout) - val : val - (scan - (Upat)patout);
 
-    lptr = (long *)scan;
-    *lptr |= offset << 8;
+    scan->l |= offset << 8;
 }
 
 /* do pattail, but on operand of first argument; nop if operandless */
@@ -1157,14 +1226,14 @@ pattail(long p, long val)
 /**/
 static void patoptail(long p, long val)
 {
-    char *ptr = patout + p;
+    Upat ptr = (Upat)patout + p;
     int op = P_OP(ptr);
     if (!p || !P_ISBRANCH(ptr))
 	return;
     if (op == P_BRANCH)
 	pattail(P_OPERAND(p), val);
     else
-	pattail(P_OPERAND(p) + sizeof(char *), val);
+	pattail(P_OPERAND(p) + 1, val);
 }
 
 
@@ -1178,11 +1247,20 @@ char *patinput;		/* String input pointer */
 
 /* Length of input string, plus null byte, if needed */
 static int patinlen;
-#ifdef BACKREFERENCES
-static char **patstartp;		/* Pointer to backref starts */
-static char **patendp;			/* Pointer to backref ends */
-static int parsfound;			/* parentheses found */
-#endif
+
+/*
+ * Offset of string at which we are trying to match.
+ * This is added in to the positions recorded in patbeginp and patendp
+ * when we are looking for substrings.  Currently this only happens
+ * in the parameter substitution code.
+ */
+/**/
+int patoffset;
+
+static char *patbeginp[NSUBEXP];	/* Pointer to backref beginnings */
+static char *patendp[NSUBEXP];		/* Pointer to backref ends */
+static int parsfound;			/* parentheses (with backrefs) found */
+
 static int globdots;			/* Glob initial dots? */
 
 /*
@@ -1204,15 +1282,28 @@ pattrystart(void)
 }
 
 /**/
-int
+mod_export int
 pattry(Patprog prog, char *string)
 {
-#ifdef BACKREFERENCES
-    int i;
+    return pattryrefs(prog, string, NULL, NULL, NULL);
+}
+
+/* The last three arguments are used to report the positions for the
+ * backreferences. On entry, *nump should contain the maximum number
+ * positions to report. */
+
+/**/
+mod_export int
+pattryrefs(Patprog prog, char *string, int *nump, int *begp, int *endp)
+{
+    int i, maxnpos = 0;
     char **sp, **ep;
-#endif
     char *progstr = (char *)prog + prog->startoff;
 
+    if (nump) {
+	maxnpos = *nump;
+	*nump = 0;
+    }
     /* inherited from domatch, but why, exactly? */
     if (*string == Nularg)
 	string++;
@@ -1248,40 +1339,111 @@ pattry(Patprog prog, char *string)
 	    errsfound = 0;
 	}
 	globdots = !(patflags & PAT_NOGLD);
-#ifdef BACKREFERENCES
 	parsfound = 0;
-	if (patflags & PAT_BACKR) {
-	    patstartp = prog->ppStartp;
-	    patendp = prog->ppEndp;
-	} else {
-	    patstartp = patendp = NULL;
-	}
-#endif
 
-	if (patmatch(progstr)) {
-#ifdef BACKREFERENCES
-	    if (patflags & PAT_BACKR) {
-		prog->ppStartp[0] = string;
-		prog->ppEndp[0] = patinput;
-
-		sp = patstartp+1;
-		ep = patendp + 1;
-		for (i = 1; i < NSUBEXP; i++) {
-		    if (!(parsfound & (1 << (i - 1))))
-			*sp = 0;
-		    if (!(parsfound & (1 << (i + 15))))
-			*ep = 0;
-		    sp++;
-		    ep++;
-		}
-		
-	    }
-#endif
+	if (patmatch((Upat)progstr)) {
 	    /*
 	     * we were lazy and didn't save the globflags if an exclusion
 	     * failed, so set it now
 	     */
 	    patglobflags = prog->globend;
+	    /*
+	     * Should we clear backreferences and matches on a failed
+	     * match?
+	     */
+	    if ((patglobflags & GF_MATCHREF) && !(patflags & PAT_FILE)) {
+		/*
+		 * m flag: for global match.  This carries no overhead
+		 * in the pattern matching part.
+		 */
+		char *str;
+		int mlen = ztrsub(patinput, patinstart);
+
+		str = ztrduppfx(patinstart, patinput - patinstart);
+		setsparam("MATCH", str);
+		setiparam("MBEGIN", (zlong)(patoffset + !isset(KSHARRAYS)));
+		setiparam("MEND",
+			  (zlong)(mlen + patoffset + !isset(KSHARRAYS) - 1));
+	    }
+	    if (prog->patnpar && nump) {
+		/*
+		 * b flag: for backreferences using parentheses. Reported
+		 * directly.
+		 */
+		*nump = prog->patnpar;
+
+		sp = patbeginp;
+		ep = patendp;
+
+		for (i = 0; i < prog->patnpar && i < maxnpos; i++) {
+		    if (parsfound & (1 << i)) {
+			if (begp)
+			    *begp++ = ztrsub(*sp, patinstart) + patoffset;
+			if (endp)
+			    *endp++ = ztrsub(*ep, patinstart) + patoffset - 1;
+		    } else {
+			if (begp)
+			    *begp++ = -1;
+			if (endp)
+			    *endp++ = -1;
+		    }
+
+		    sp++;
+		    ep++;
+		}
+	    } else if (prog->patnpar && !(patflags & PAT_FILE)) {
+		/*
+		 * b flag: for backreferences using parentheses.
+		 */
+		int palen = prog->patnpar+1;
+		char **matcharr, **mbeginarr, **mendarr;
+		char numbuf[DIGBUFSIZE];
+
+		matcharr = zcalloc(palen*sizeof(char *));
+		mbeginarr = zcalloc(palen*sizeof(char *));
+		mendarr = zcalloc(palen*sizeof(char *));
+
+		sp = patbeginp;
+		ep = patendp;
+
+		for (i = 0; i < prog->patnpar; i++) {
+		    if (parsfound & (1 << i)) {
+			matcharr[i] = ztrduppfx(*sp, *ep - *sp);
+			/*
+			 * mbegin and mend give indexes into the string
+			 * in the standard notation, i.e. respecting
+			 * KSHARRAYS, and with the end index giving
+			 * the last character, not one beyond.
+			 * For example, foo=foo; [[ $foo = (f)oo ]] gives
+			 * (without KSHARRAYS) indexes 1 and 1, which
+			 * corresponds to indexing as ${foo[1,1]}.
+			 */
+			sprintf(numbuf, "%ld",
+				(long)(ztrsub(*sp, patinstart) + 
+				       patoffset +
+				       !isset(KSHARRAYS)));
+			mbeginarr[i] = ztrdup(numbuf);
+			sprintf(numbuf, "%ld",
+				(long)(ztrsub(*ep, patinstart) + 
+				       patoffset +
+				       !isset(KSHARRAYS) - 1));
+			mendarr[i] = ztrdup(numbuf);
+		    } else {
+			/* Pattern wasn't set: either it was in an
+			 * unmatched branch, or a hashed parenthesis
+			 * that didn't match at all.
+			 */
+			matcharr[i] = ztrdup("");
+			mbeginarr[i] = ztrdup("-1");
+			mendarr[i] = ztrdup("-1");
+		    }
+		    sp++;
+		    ep++;
+		}
+		setaparam("match", matcharr);
+		setaparam("mbegin", mbeginarr);
+		setaparam("mend", mendarr);
+	    }
 	    return 1;
 	} else
 	    return 0;
@@ -1293,10 +1455,10 @@ pattry(Patprog prog, char *string)
  * comes from the input string, the second the current pattern.
  */
 #define CHARMATCH(chin, chpa) (chin == chpa || \
-        ((patglobflags & C_IGNCASE) ? \
+        ((patglobflags & GF_IGNCASE) ? \
 	 ((isupper(chin) ? tolower(chin) : chin) == \
 	  (isupper(chpa) ? tolower(chpa) : chpa)) : \
-	 (patglobflags & C_LCMATCHUC) ? \
+	 (patglobflags & GF_LCMATCHUC) ? \
 	 (islower(chpa) && toupper(chpa) == chin) : 0))
 
 /*
@@ -1314,11 +1476,12 @@ static char *exactpos;
  */
 
 /**/
-int
-patmatch(char *prog)
+static int
+patmatch(Upat prog)
 {
     /* Current and next nodes */
-    char *scan = prog, *next, *opnd, *save, *start;
+    Upat scan = prog, next, opnd;
+    char *start, *save, *chrop;
     int savglobflags, op, no, min, nextch, fail = 0, saverrsfound;
     zrange_t from, to, comp;
 
@@ -1338,36 +1501,38 @@ patmatch(char *prog)
 	    break;
 	case P_EXACTLY:
 	    /*
-	     * acts as nothing if *opnd is null:  this is used by
+	     * acts as nothing if *chrop is null:  this is used by
 	     * approx code.
 	     */
-	    opnd = exactpos ? exactpos : P_OPERAND(scan);
+	    chrop = exactpos ? exactpos : (char *)P_OPERAND(scan);
 	    exactpos = NULL;
-	    while (*opnd && *patinput) {
+	    while (*chrop && *patinput) {
 		int chin = STOUC(UNMETA(patinput));
-		int chpa = STOUC(UNMETA(opnd));
+		int chpa = STOUC(UNMETA(chrop));
 		if (!CHARMATCH(chin, chpa)) {
 		    fail = 1;
 		    break;
 		}
-		METAINC(opnd);
+		METAINC(chrop);
 		METAINC(patinput);
 	    }
-	    if (*opnd) {
-		exactpos = opnd;
+	    if (*chrop) {
+		exactpos = chrop;
 		fail = 1;
 	    }
 	    break;
 	case P_ANYOF:
 	    if (!*patinput ||
-		!patmatchrange(P_OPERAND(scan), STOUC(UNMETA(patinput))))
+		!patmatchrange((char *)P_OPERAND(scan),
+			       STOUC(UNMETA(patinput))))
 		fail = 1;
 	    else
 		METAINC(patinput);
 	    break;
 	case P_ANYBUT:
 	    if (!*patinput ||
-		patmatchrange(P_OPERAND(scan), STOUC(UNMETA(patinput))))
+		patmatchrange((char *)P_OPERAND(scan),
+			      STOUC(UNMETA(patinput))))
 		fail = 1;
 	    else
 		METAINC(patinput);
@@ -1384,12 +1549,12 @@ patmatch(char *prog)
 	     * the first attempt.
 	     */
 	    op = P_OP(scan);
-	    start = P_OPERAND(scan);
+	    start = (char *)P_OPERAND(scan);
 	    from = to = 0;
 	    if (op != P_NUMTO) {
 #ifdef ZSH_64_BIT_TYPE
 		/* We can't rely on pointer alignment being good enough. */
-		memcpy((char *)&from, (char *)start, sizeof(zrange_t));
+		memcpy((char *)&from, start, sizeof(zrange_t));
 #else
 		from = *((zrange_t *) start);
 #endif
@@ -1414,7 +1579,8 @@ patmatch(char *prog)
 		    return 1;
 		}
 		if (!no && P_OP(next) == P_EXACTLY &&
-		    !idigit(STOUC(*P_OPERAND(next))) && !(patglobflags & 0xff))
+		    !idigit(STOUC(*(char *)P_OPERAND(next))) &&
+		    !(patglobflags & 0xff))
 		    return 0;
 		patinput = --save;
 		no++;
@@ -1434,7 +1600,8 @@ patmatch(char *prog)
 		if (patmatch(next))
 		    return 1;
 		if (!no && P_OP(next) == P_EXACTLY &&
-		    !idigit(STOUC(*P_OPERAND(next))) && !(patglobflags & 0xff))
+		    !idigit(STOUC(*(char *)P_OPERAND(next))) &&
+		    !(patglobflags & 0xff))
 		    return 0;
 		patinput = --save;
 		no++;
@@ -1447,9 +1614,8 @@ patmatch(char *prog)
 	case P_BACK:
 	    break;
 	case P_GFLAGS:
-	    patglobflags = *(int *)P_OPERAND(scan);
+	    patglobflags = P_OPERAND(scan)->l;
 	    break;
-#ifdef BACKREFERENCES
 	case P_OPEN:
 	case P_OPEN+1:
 	case P_OPEN+2:
@@ -1464,13 +1630,12 @@ patmatch(char *prog)
 	    save = patinput;
 
 	    if (patmatch(next)) {
-		DPUTS(!patstartp, "patstartp not set for backreferencing");
 		/*
-		 * Don't set ppStartp if some later invocation of
+		 * Don't set patbeginp if some later invocation of
 		 * the same parentheses already has.
 		 */
 		if (no && !(parsfound & (1 << (no - 1)))) {
-		    patstartp[no] = save;
+		    patbeginp[no-1] = save;
 		    parsfound |= 1 << (no - 1);
 		}
 		return 1;
@@ -1493,25 +1658,24 @@ patmatch(char *prog)
 	    if (patmatch(next)) {
 		DPUTS(!patendp, "patendp not set for backreferencing");
 		if (no && !(parsfound & (1 << (no + 15)))) {
-		    patendp[no] = save;
+		    patendp[no-1] = save;
 		    parsfound |= 1 << (no + 15);
 		}
 		return 1;
 	    } else
 		return 0;
 	    break;
-#endif
 	case P_EXCSYNC:
-	    /* See the P_EXCLUDE code below for where syncstrp comes from */
+	    /* See the P_EXCLUDE code below for where syncptr comes from */
 	    {
-		unsigned char **syncstrp, *syncptr;
-		char *after;
+		unsigned char *syncptr;
+		Upat after;
 		after = P_OPERAND(scan);
 		DPUTS(!P_ISEXCLUDE(after),
 		      "BUG: EXCSYNC not followed by EXCLUDE.");
-		syncstrp = (unsigned char **)P_OPERAND(after);
-		DPUTS(!*syncstrp, "BUG: EXCSYNC not handled by EXCLUDE");
-		syncptr = *syncstrp + (patinput - patinstart);
+		DPUTS(!P_OPERAND(after)->p,
+		      "BUG: EXCSYNC not handled by EXCLUDE");
+		syncptr = P_OPERAND(after)->p + (patinput - patinstart);
 		/*
 		 * If we already matched from here, this time we fail.
 		 * See WBRANCH code for story about error count.
@@ -1570,15 +1734,14 @@ patmatch(char *prog)
 			 * The pointer also tells us where the asserted
 			 * pattern matched for use by the exclusion.
 			 */
-			unsigned char **syncstrp, *oldsyncstr;
+			Upat syncstrp;
+			unsigned char *oldsyncstr;
 			char *matchpt = NULL;
 			int ret, savglobdots, matchederrs = 0;
-#ifdef BACKREFERENCES
 			int savparsfound = parsfound;
-#endif
 			DPUTS(P_OP(scan) == P_WBRANCH,
 			      "BUG: excluded WBRANCH");
-			syncstrp = (unsigned char **)P_OPERAND(next);
+			syncstrp = P_OPERAND(next);
 			/*
 			 * Unlike WBRANCH, each test at the same exclude
 			 * sync point (due to an external loop) is separate,
@@ -1586,14 +1749,15 @@ patmatch(char *prog)
 			 * (foo~bar)(foo~bar)... from the exclusion point
 			 * of view, so we use a different sync string.
 			 */
-			oldsyncstr = *syncstrp;
+			oldsyncstr = syncstrp->p;
 			if (!patinlen)
 			    patinlen = strlen(patinstart)+1;
-			*syncstrp = (unsigned char *)zcalloc(patinlen);
+			syncstrp->p = (unsigned char *)zcalloc(patinlen);
 			while ((ret = patmatch(P_OPERAND(scan)))) {
 			    unsigned char *syncpt;
 			    char savchar, *testptr;
-			    int savforce = forceerrs;
+			    char *savpatinstart = patinstart;
+			    int savforce = forceerrs, savpatinlen = patinlen;
 			    forceerrs = -1;
 			    savglobdots = globdots;
 			    matchederrs = errsfound;
@@ -1607,9 +1771,9 @@ patmatch(char *prog)
 			     * possibilities for approximation have been
 			     * checked.)
 			     */
-			    for (syncpt = *syncstrp; !*syncpt; syncpt++)
+			    for (syncpt = syncstrp->p; !*syncpt; syncpt++)
 				;
-			    testptr = patinstart + (syncpt - *syncstrp);
+			    testptr = patinstart + (syncpt - syncstrp->p);
 			    DPUTS(testptr > matchpt, "BUG: EXCSYNC failed");
 			    savchar = *testptr;
 			    *testptr = '\0';
@@ -1625,7 +1789,7 @@ patmatch(char *prog)
 				 */
 				patglobflags &= ~0xff;
 				errsfound = 0;
-				opnd = P_OPERAND(next) + sizeof(char *);
+				opnd = P_OPERAND(next) + 1;
 				if (P_OP(next) == P_EXCLUDP && pathpos) {
 				    /*
 				     * top level exclusion with a file,
@@ -1638,21 +1802,23 @@ patmatch(char *prog)
 					zalloc(pathpos + patinlen);
 				    strcpy(buf, pathbuf);
 				    strcpy(buf + pathpos, patinput);
-				    patinput = buf;
+				    patinput = patinstart = buf;
+				    patinlen += pathpos;
 				}
 				if (patmatch(opnd)) {
 				    ret = 0;
-#ifdef BACKREFERENCES
 				    /*
 				     * Another subtlety: if we exclude the
 				     * match, any parentheses just found
 				     * become invalidated.
 				     */
 				    parsfound = savparsfound;
-#endif
 				}
-				if (buf)
+				if (buf) {
+				    patinstart = savpatinstart;
+				    patinlen = savpatinlen;
 				    zfree(buf, pathpos + patinlen);
+				}
 				if (!ret)
 				    break;
 				next = PATNEXT(next);
@@ -1666,8 +1832,8 @@ patmatch(char *prog)
 			    patglobflags = savglobflags;
 			    errsfound = saverrsfound;
 			}
-			zfree((char *)*syncstrp, patinlen);
-			*syncstrp = oldsyncstr;
+			zfree((char *)syncstrp->p, patinlen);
+			syncstrp->p = oldsyncstr;
 			if (ret) {
 			    patinput = matchpt;
 			    errsfound = matchederrs;
@@ -1678,7 +1844,8 @@ patmatch(char *prog)
 			    ;
 		    } else {
 			int ret = 1, pfree = 0;
-			unsigned char **ptrp = NULL, *ptr;
+			Upat ptrp = NULL;
+			unsigned char *ptr;
 			if (P_OP(scan) == P_WBRANCH) {
 			    /*
 			     * This is where we make sure that we are not
@@ -1696,15 +1863,14 @@ patmatch(char *prog)
 			     * there is already a 1, then the test fails.
 			     */
 			    opnd = P_OPERAND(scan);
-			    ptrp = (unsigned char **)opnd;
-			    opnd += sizeof(unsigned char *);
-			    if (!*ptrp) {
+			    ptrp = opnd++;
+			    if (!ptrp->p) {
 				if (!patinlen)
 				    patinlen = strlen((char *)patinstart)+1;
-				*ptrp = (unsigned char *)zcalloc(patinlen);
+				ptrp->p = (unsigned char *)zcalloc(patinlen);
 				pfree = 1;
 			    }
-			    ptr = *ptrp + (patinput - patinstart);
+			    ptr = ptrp->p + (patinput - patinstart);
 
 			    /*
 			     * Without approximation, this is just a
@@ -1712,7 +1878,7 @@ patmatch(char *prog)
 			     * need to know how many errors there were
 			     * last time we made the test.  If errsfound
 			     * is now smaller than it was, hence we can
-			     * maker more approximations in the remaining
+			     * make more approximations in the remaining
 			     * code, we continue with the test.
 			     * (This is why the max number of errors is
 			     * 254, not 255.)
@@ -1725,8 +1891,8 @@ patmatch(char *prog)
 			if (ret)
 			    ret = patmatch(opnd);
 			if (pfree) {
-			    zfree((char *)*ptrp, patinlen);
-			    *ptrp = NULL;
+			    zfree((char *)ptrp->p, patinlen);
+			    ptrp->p = NULL;
 			}
 			if (ret)
 			    return 1;
@@ -1769,12 +1935,38 @@ patmatch(char *prog)
 		    return 0;
 		no = patrepeat(P_OPERAND(scan));
 	    }
-	    /* Lookahead to avoid useless matches */
-	    if (P_OP(next) == P_EXACTLY && !(patglobflags & 0xff))
-		nextch = STOUC(UNMETA(P_OPERAND(next)));
-	    else
-		nextch = -1;
 	    min = (op == P_TWOHASH) ? 1 : 0;
+	    /*
+	     * Lookahead to avoid useless matches. This is not possible
+	     * with approximation.
+	     */
+	    if (P_OP(next) == P_EXACTLY && !(patglobflags & 0xff)) {
+		char *nextop = (char *)P_OPERAND(next);
+		/*
+		 * If that P_EXACTLY is last (common in simple patterns,
+		 * such as *.c), then it can be only be matched at one
+		 * point in the test string, so record that.
+		 */
+		if (P_OP(PATNEXT(next)) == P_END &&
+		    !(patflags & PAT_NOANCH)) {
+		    int ptlen = strlen(patinput);
+		    int oplen = strlen(nextop);
+		    /* Are we in the right range? */
+		    if (oplen > strlen(min ? METANEXT(start) : start) ||
+			oplen < ptlen)
+			return 0;
+		    /* Yes, just position appropriately and test. */
+		    patinput += ptlen - oplen;
+		    if (patinput > start && patinput[-1] == Meta) {
+			/* doesn't align properly, no go */
+			return 0;
+		    }
+		    /* Continue loop with P_EXACTLY test. */
+		    break;
+		}
+		nextch = STOUC(UNMETA(nextop));
+	    } else
+		nextch = -1;
 	    save = patinput;
 	    savglobflags = patglobflags;
 	    saverrsfound = errsfound;
@@ -2010,13 +2202,13 @@ patmatchrange(char *range, int ch)
 /* repeatedly match something simple and say how many times */
 
 /**/
-static int patrepeat(char *p)
+static int patrepeat(Upat p)
 {
     int count = 0, tch, inch;
     char *scan, *opnd;
 
     scan = patinput;
-    opnd = P_OPERAND(p);
+    opnd = (char *)P_OPERAND(p);
 
     switch(P_OP(p)) {
 #ifdef DEBUG
@@ -2056,6 +2248,16 @@ static int patrepeat(char *p)
     return count;
 }
 
+/* Free a patprog. */
+
+/**/
+mod_export void
+freepatprog(Patprog prog)
+{
+    if (prog && prog != dummy_patprog1 && prog != dummy_patprog2)
+	zfree(prog, prog->size);
+}
+
 /**/
 #ifdef ZSH_PAT_DEBUG
 
@@ -2067,7 +2269,8 @@ static int patrepeat(char *p)
 static void
 patdump(Patprog r)
 {
-    char *s, *next, *codestart, *base, op = P_EXACTLY;
+    char *s, *base, op = P_EXACTLY;
+    Upat up, codestart, next;
 
     base = (char *)r;
     s = base + r->startoff;
@@ -2075,13 +2278,14 @@ patdump(Patprog r)
     if (r->flags & PAT_PURES) {
 	printf("STRING:%s\n", (char *)s);
     } else {
-	codestart = s;
+	codestart = (Upat)s;
 	while (op != P_END) {
-	    op = P_OP(s);
-	    printf("%2d%s", s-codestart, patprop(s));
-	    next = PATNEXT(s);
-	    printf("(%d)", next ? (s-codestart)+(next-s) : 0);
-	    s += sizeof(zalign_t);
+	    up = (Upat)s;
+	    op = P_OP(up);
+	    printf("%2d%s", up-codestart, patprop(up));
+	    next = PATNEXT(up);
+	    printf("(%d)", next ? next-codestart : 0);
+	    s += sizeof(union upat);
 	    if (op == P_ANYOF || op == P_ANYBUT || op == P_EXACTLY) {
 		while (*s != '\0') {
 		    if (itok(*s)) {
@@ -2107,16 +2311,15 @@ patdump(Patprog r)
 		    s += sizeof(zrange_t);
 		}
 	    } else if (op == P_GFLAGS) {
-		long *lptr = (long *)s;
-		printf("%ld, %ld", *lptr & ~0xff, *lptr & 0xff);
-		s += sizeof(long);
+		printf("%ld, %ld", (++up)->l & ~0xff, (++up)->l & 0xff);
+		s += sizeof(union upat);
 	    } else if (op == P_WBRANCH || op == P_EXCLUDE ||
 		       op == P_EXCLUDP) {
-		s += sizeof(char *);
+		s += sizeof(union upat);
 	    }
 	    putchar('\n');
-	    s = base + (((s - base) + sizeof(zalign_t) - 1) &
-			~(sizeof(zalign_t) - 1));
+	    s = base + (((s - base) + sizeof(union upat) - 1) &
+			~(sizeof(union upat) - 1));
 	}
     }
 
@@ -2125,18 +2328,16 @@ patdump(Patprog r)
 	printf("start `%c' ", r->patstartch);
     if (!(r->flags & PAT_NOANCH))
 	printf("EOL-anchor ");
-#ifdef BACKREFERENCES
-    if (r->flags & PAT_BACKR)
-	printf("backreferences ");
-#endif
+    if (r->patnpar)
+	printf("%d active backreferences ", r->patnpar);
     if (r->mustoff)
 	printf("must have \"%s\"", (char *)r + r->mustoff);
     printf("\n");
     if (r->globflags) {
 	printf("Globbing flags: ");
-	if (r->globflags & C_LCMATCHUC)
+	if (r->globflags & GF_LCMATCHUC)
 	    printf("LC matches UC ");
-	if (r->globflags & C_IGNCASE)
+	if (r->globflags & GF_IGNCASE)
 	    printf("Ignore case");
 	printf("\n");
 	if (r->globflags & 0xff)
@@ -2146,7 +2347,7 @@ patdump(Patprog r)
 
 /**/
 static char *
-patprop(char *op)
+patprop(Upat op)
 {
     char *p = NULL;
     static char buf[50];
@@ -2258,16 +2459,11 @@ int
 bin_patdebug(char *name, char **args, char *ops, int func)
 {
     Patprog prog;
-    int ret = 0, flags;
+    int ret = 0;
 
     tokenize(*args);
 
-#ifdef BACKREFERENCES
-    flags = ops['b'] ? PAT_BACKR : 0;
-#else
-    flags = 0;
-#endif
-    if (!(prog = patcompile((char *)*args, flags, 0)))
+    if (!(prog = patcompile((char *)*args, 0, 0)))
 	return 1;
     if (ops['p'] || !args[1]) {
 	patdump(prog);
diff --git a/Src/text.c b/Src/text.c
index b7df8012f..ab6ca5eb9 100644
--- a/Src/text.c
+++ b/Src/text.c
@@ -31,7 +31,7 @@
 #include "text.pro"
 
 static char *tptr, *tbuf, *tlim;
-static int tsiz, tindent, tnewlins;
+static int tsiz, tindent, tnewlins, tjob;
 
 /* add a character to the text buffer */
 
@@ -72,19 +72,18 @@ taddstr(char *s)
     tptr += sl;
 }
 
-#if 0
-/* add an integer to the text buffer */
-
 /**/
-void
-taddint(int x)
+static void
+taddlist(Estate state, int num)
 {
-    char buf[DIGBUFSIZE];
-
-    sprintf(buf, "%d", x);
-    taddstr(buf);
+    if (num) {
+	while (num--) {
+	    taddstr(ecgetstr(state, EC_NODUP, NULL));
+	    taddchr(' ');
+	}
+	tptr--;
+    }
 }
-#endif
 
 /* add a newline, or something equivalent, to the text buffer */
 
@@ -105,15 +104,26 @@ taddnl(void)
 /* get a permanent textual representation of n */
 
 /**/
-char *
-getpermtext(struct node *n)
+mod_export char *
+getpermtext(Eprog prog, Wordcode c)
 {
+    struct estate s;
+
+    if (!c)
+	c = prog->prog;
+
+    s.prog = prog;
+    s.pc = c;
+    s.strs = prog->strs;
+
     tnewlins = 1;
     tbuf = (char *)zalloc(tsiz = 32);
     tptr = tbuf;
     tlim = tbuf + tsiz;
     tindent = 1;
-    gettext2(n);
+    tjob = 0;
+    if (prog->len)
+	gettext2(&s);
     *tptr = '\0';
     untokenize(tbuf);
     return tbuf;
@@ -123,344 +133,587 @@ getpermtext(struct node *n)
 
 /**/
 char *
-getjobtext(struct node *n)
+getjobtext(Eprog prog, Wordcode c)
 {
     static char jbuf[JOBTEXTSIZE];
 
+    struct estate s;
+
+    if (!c)
+	c = prog->prog;
+
+    s.prog = prog;
+    s.pc = c;
+    s.strs = prog->strs;
+
     tnewlins = 0;
     tbuf = NULL;
     tptr = jbuf;
     tlim = tptr + JOBTEXTSIZE - 1;
     tindent = 1;
-    gettext2(n);
+    tjob = 1;
+    gettext2(&s);
     *tptr = '\0';
     untokenize(jbuf);
     return jbuf;
 }
 
-#define gt2(X) gettext2((struct node *) (X))
-
 /*
-	"gettext2" or "type checking and how to avoid it"
-	an epic function by Paul Falstad
-*/
-
-#define _Cond(X) ((Cond) (X))
-#define _Cmd(X) ((Cmd) (X))
-#define _Pline(X) ((Pline) (X))
-#define _Sublist(X) ((Sublist) (X))
-#define _List(X) ((List) (X))
-#define _casecmd(X) ((struct casecmd *) (X))
-#define _ifcmd(X) ((struct ifcmd *) (X))
-#define _whilecmd(X) ((struct whilecmd *) (X))
+ * gettext2() shows one way to walk through the word code without
+ * recursion. We start by reading a word code and executing the
+ * action for it. Some codes have sub-structures (like, e.g. WC_FOR)
+ * and require something to be done after the sub-structure has been
+ * handled. For these codes a tstack structure which describes what
+ * has to be done is pushed onto a stack. Codes without sub-structures
+ * arrange for the next structure being taken from the stack so that
+ * the action for it is executed instead of the one for the next
+ * word code. If the stack is empty at this point, we have handled
+ * the whole structure we were called for.
+ */
+
+typedef struct tstack *Tstack;
+
+struct tstack {
+    Tstack prev;
+    wordcode code;
+    int pop;
+    union {
+	struct {
+	    LinkList list;
+	} _redir;
+	struct {
+	    char *strs;
+	    Wordcode end;
+	} _funcdef;
+	struct {
+	    Wordcode end;
+	} _case;
+	struct {
+	    int cond;
+	    Wordcode end;
+	} _if;
+	struct {
+	    int par;
+	} _cond;
+	struct {
+	    Wordcode end;
+	} _subsh;
+    } u;
+};
+
+static Tstack tstack, tfree;
+
+static Tstack
+tpush(wordcode code, int pop)
+{
+    Tstack s;
+
+    if ((s = tfree))
+	tfree = s->prev;
+    else
+	s = (Tstack) zalloc(sizeof(*s));
+
+    s->prev = tstack;
+    tstack = s;
+    s->code = code;
+    s->pop = pop;
+
+    return s;
+}
 
 /**/
 static void
-gettext2(struct node *n)
+gettext2(Estate state)
 {
-    Cmd nn;
-
-    if (!n || ((List) n) == &dummy_list)
-	return;
-    switch (NT_TYPE(n->ntype)) {
-    case N_LIST:
-	gt2(_List(n)->left);
-	if (_List(n)->type & Z_ASYNC) {
-	    taddstr(" &");
-	    if (_List(n)->type & Z_DISOWN)
-		taddstr("|");
-	}
-	simplifyright(_List(n));
-	if (_List(n)->right) {
-	    if (tnewlins)
-		taddnl();
-	    else
-		taddstr((_List(n)->type & Z_ASYNC) ? " " : "; ");
-	    gt2(_List(n)->right);
-	}
-	break;
-    case N_SUBLIST:
-	if (_Sublist(n)->flags & PFLAG_NOT)
-	    taddstr("! ");
-	if (_Sublist(n)->flags & PFLAG_COPROC)
-	    taddstr("coproc ");
-	gt2(_Sublist(n)->left);
-	if (_Sublist(n)->right) {
-	    taddstr((_Sublist(n)->type == ORNEXT) ? " || " : " && ");
-	    gt2(_Sublist(n)->right);
-	}
-	break;
-    case N_PLINE:
-	gt2(_Pline(n)->left);
-	if (_Pline(n)->type == PIPE) {
-	    taddstr(" | ");
-	    gt2(_Pline(n)->right);
+    Tstack s, n;
+    int stack = 0;
+    wordcode code;
+
+    while (1) {
+	if (stack) {
+	    if (!(s = tstack))
+		return;
+	    if (s->pop) {
+		tstack = s->prev;
+		s->prev = tfree;
+		tfree = s;
+	    }
+	    code = s->code;
+	    stack = 0;
+	} else {
+	    s = NULL;
+	    code = *state->pc++;
 	}
-	break;
-    case N_CMD:
-	nn = _Cmd(n);
-	switch (nn->type) {
-	case SIMPLE:
-	    getsimptext(nn);
+	switch (wc_code(code)) {
+	case WC_LIST:
+	    if (!s) {
+		s = tpush(code, (WC_LIST_TYPE(code) & Z_END));
+		stack = 0;
+	    } else {
+		if (WC_LIST_TYPE(code) & Z_ASYNC) {
+		    taddstr(" &");
+		    if (WC_LIST_TYPE(code) & Z_DISOWN)
+			taddstr("|");
+		}
+		if (!(stack = (WC_LIST_TYPE(code) & Z_END))) {
+		    if (tnewlins)
+			taddnl();
+		    else
+			taddstr((WC_LIST_TYPE(code) & Z_ASYNC) ? " " : "; ");
+		    s->code = *state->pc++;
+		    s->pop = (WC_LIST_TYPE(s->code) & Z_END);
+		}
+	    }
+	    if (!stack && (WC_LIST_TYPE(s->code) & Z_SIMPLE))
+		state->pc++;
+	    break;
+	case WC_SUBLIST:
+	    if (!s) {
+		if (WC_SUBLIST_FLAGS(code) & WC_SUBLIST_NOT)
+		    taddstr("! ");
+		if (WC_SUBLIST_FLAGS(code) & WC_SUBLIST_COPROC)
+		    taddstr("coproc ");
+		s = tpush(code, (WC_SUBLIST_TYPE(code) == WC_SUBLIST_END));
+	    } else {
+		if (!(stack = (WC_SUBLIST_TYPE(code) == WC_SUBLIST_END))) {
+		    taddstr((WC_SUBLIST_TYPE(code) == WC_SUBLIST_OR) ?
+			    " || " : " && ");
+		    s->code = *state->pc++;
+		    s->pop = (WC_SUBLIST_TYPE(s->code) == WC_SUBLIST_END);
+		    if (WC_SUBLIST_FLAGS(s->code) & WC_SUBLIST_NOT)
+			taddstr("! ");
+		    if (WC_SUBLIST_FLAGS(s->code) & WC_SUBLIST_COPROC)
+			taddstr("coproc ");
+		}
+	    }
+	    if (!stack && (WC_SUBLIST_FLAGS(s->code) & WC_SUBLIST_SIMPLE))
+		state->pc++;
+	    break;
+	case WC_PIPE:
+	    if (!s) {
+		tpush(code, (WC_PIPE_TYPE(code) == WC_PIPE_END));
+		if (WC_PIPE_TYPE(code) == WC_PIPE_MID)
+		    state->pc++;
+	    } else {
+		if (!(stack = (WC_PIPE_TYPE(code) == WC_PIPE_END))) {
+		    taddstr(" | ");
+		    s->code = *state->pc++;
+		    if (!(s->pop = (WC_PIPE_TYPE(s->code) == WC_PIPE_END)))
+			state->pc++;
+		}
+	    }
 	    break;
-	case SUBSH:
-	    taddstr("( ");
-	    tindent++;
-	    gt2(nn->u.list);
-	    tindent--;
-	    taddstr(" )");
+	case WC_REDIR:
+	    if (!s) {
+		state->pc--;
+		n = tpush(code, 1);
+		n->u._redir.list = ecgetredirs(state);
+	    } else {
+		getredirs(s->u._redir.list);
+		stack = 1;
+	    }
 	    break;
-	case ZCTIME:
-	    taddstr("time ");
-	    tindent++;
-	    gt2(nn->u.pline);
-	    tindent--;
+	case WC_ASSIGN:
+	    taddstr(ecgetstr(state, EC_NODUP, NULL));
+	    taddchr('=');
+	    if (WC_ASSIGN_TYPE(code) == WC_ASSIGN_ARRAY) {
+		taddchr('(');
+		taddlist(state, WC_ASSIGN_NUM(code));
+		taddstr(") ");
+	    } else {
+		taddstr(ecgetstr(state, EC_NODUP, NULL));
+		taddchr(' ');
+	    }
 	    break;
-	case FUNCDEF:
-	    taddlist(nn->args);
-	    taddstr(" () {");
-	    tindent++;
-	    taddnl();
-	    gt2(nn->u.list);
-	    tindent--;
-	    taddnl();
-	    taddstr("}");
+	case WC_SIMPLE:
+	    taddlist(state, WC_SIMPLE_ARGC(code));
+	    stack = 1;
 	    break;
-	case CURSH:
-	    taddstr("{ ");
-	    tindent++;
-	    gt2(nn->u.list);
-	    tindent--;
-	    taddstr(" }");
+	case WC_SUBSH:
+	    if (!s) {
+		taddstr("( ");
+		tindent++;
+		n = tpush(code, 1);
+		n->u._subsh.end = state->pc + WC_SUBSH_SKIP(code);
+	    } else {
+		state->pc = s->u._subsh.end;
+		tindent--;
+		taddstr(" )");
+		stack = 1;
+	    }
 	    break;
-	case CFOR:
-	case CSELECT:
-	    taddstr((nn->type == CFOR) ? "for " : "select ");
-	    if (nn->u.forcmd->condition) {
-		taddstr("((");
-		taddstr(nn->u.forcmd->name);
-		taddstr("; ");
-		taddstr(nn->u.forcmd->condition);
-		taddstr("; ");
-		taddstr(nn->u.forcmd->advance);
-		taddstr(")) do");
+	case WC_CURSH:
+	    if (!s) {
+		taddstr("{ ");
+		tindent++;
+		n = tpush(code, 1);
+		n->u._subsh.end = state->pc + WC_CURSH_SKIP(code);
 	    } else {
-		taddstr(nn->u.forcmd->name);
-		if (nn->u.forcmd->inflag) {
-		    taddstr(" in ");
-		    taddlist(nn->args);
-		}
-		taddnl();
-		taddstr("do");
+		state->pc = s->u._subsh.end;
+		tindent--;
+		taddstr(" }");
+		stack = 1;
 	    }
-	    tindent++;
-	    taddnl();
-	    gt2(nn->u.forcmd->list);
-	    tindent--;
-	    taddnl();
-	    taddstr("done");
 	    break;
-	case CIF:
-	    gt2(nn->u.ifcmd);
-	    taddstr("fi");
+	case WC_TIMED:
+	    if (!s) {
+		taddstr("time");
+		if (WC_TIMED_TYPE(code) == WC_TIMED_PIPE) {
+		    taddchr(' ');
+		    tindent++;
+		    tpush(code, 1);
+		} else
+		    stack = 1;
+	    } else {
+		tindent--;
+		stack = 1;
+	    }
 	    break;
-	case CCASE:
-	    gt2(nn->u.casecmd);
+	case WC_FUNCDEF:
+	    if (!s) {
+		Wordcode p = state->pc;
+		Wordcode end = p + WC_FUNCDEF_SKIP(code);
+
+		taddlist(state, *state->pc++);
+		if (tjob) {
+		    taddstr(" () { ... }");
+		    state->pc = end;
+		    stack = 1;
+		} else {
+		    taddstr(" () {");
+		    tindent++;
+		    taddnl();
+		    n = tpush(code, 1);
+		    n->u._funcdef.strs = state->strs;
+		    n->u._funcdef.end = end;
+		    state->strs += *state->pc;
+		    state->pc += 3;
+		}
+	    } else {
+		state->strs = s->u._funcdef.strs;
+		state->pc = s->u._funcdef.end;
+		tindent--;
+		taddnl();
+		taddstr("}");
+		stack = 1;
+	    }
 	    break;
-	case COND:
-	    taddstr("[[ ");
-	    gt2(nn->u.cond);
-	    taddstr(" ]]");
+	case WC_FOR:
+	    if (!s) {
+		taddstr("for ");
+		if (WC_FOR_TYPE(code) == WC_FOR_COND) {
+		    taddstr("((");
+		    taddstr(ecgetstr(state, EC_NODUP, NULL));
+		    taddstr("; ");
+		    taddstr(ecgetstr(state, EC_NODUP, NULL));
+		    taddstr("; ");
+		    taddstr(ecgetstr(state, EC_NODUP, NULL));
+		    taddstr(")) do");
+		} else {
+		    taddstr(ecgetstr(state, EC_NODUP, NULL));
+		    if (WC_FOR_TYPE(code) == WC_FOR_LIST) {
+			taddstr(" in ");
+			taddlist(state, *state->pc++);
+		    }
+		    taddnl();
+		    taddstr("do");
+		}
+		tindent++;
+		taddnl();
+		tpush(code, 1);
+	    } else {
+		tindent--;
+		taddnl();
+		taddstr("done");
+		stack = 1;
+	    }
 	    break;
-	case CARITH:
-	    taddstr("((");
-	    taddlist(nn->args);
-	    taddstr("))");
+	case WC_SELECT:
+	    if (!s) {
+		taddstr("select ");
+		taddstr(ecgetstr(state, EC_NODUP, NULL));
+		if (WC_SELECT_TYPE(code) == WC_SELECT_LIST) {
+		    taddstr(" in ");
+		    taddlist(state, *state->pc++);
+		}
+		tindent++;
+		taddnl();
+		tpush(code, 1);
+	    } else {
+		tindent--;
+		taddnl();
+		taddstr("done");
+		stack = 1;
+	    }
 	    break;
-	case CREPEAT:
-	    taddstr("repeat ");
-	    taddlist(nn->args);
-	    taddnl();
-	    taddstr("do");
-	    tindent++;
-	    taddnl();
-	    gt2(nn->u.list);
-	    tindent--;
-	    taddnl();
-	    taddstr("done");
+	case WC_WHILE:
+	    if (!s) {
+		taddstr(WC_WHILE_TYPE(code) == WC_WHILE_UNTIL ?
+			"until " : "while ");
+		tindent++;
+		tpush(code, 0);
+	    } else if (!s->pop) {
+		tindent--;
+		taddnl();
+		taddstr("do");
+		tindent++;
+		taddnl();
+		s->pop = 1;
+	    } else {
+		tindent--;
+		taddnl();
+		taddstr("done");
+		stack = 1;
+	    }
 	    break;
-	case CWHILE:
-	    gt2(nn->u.whilecmd);
+	case WC_REPEAT:
+	    if (!s) {
+		taddstr("repeat ");
+		taddstr(ecgetstr(state, EC_NODUP, NULL));
+		taddnl();
+		taddstr("do");
+		tindent++;
+		taddnl();
+		tpush(code, 1);
+	    } else {
+		tindent--;
+		taddnl();
+		taddstr("done");
+		stack = 1;
+	    }
 	    break;
-	}
-	getredirs(nn);
-	break;
-    case N_COND:
-	getcond(_Cond(n), 0);
-	break;
-    case N_CASE:
-	{
-	    List *l;
-	    char **p;
-
-	    l = _casecmd(n)->lists;
-	    p = _casecmd(n)->pats;
-
-	    taddstr("case ");
-	    taddstr(*p++);
-	    taddstr(" in");
-	    tindent++;
-	    for (; *l; p++, l++) {
+	case WC_CASE:
+	    if (!s) {
+		Wordcode end = state->pc + WC_CASE_SKIP(code);
+
+		taddstr("case ");
+		taddstr(ecgetstr(state, EC_NODUP, NULL));
+		taddstr(" in");
+
+		if (state->pc >= end) {
+		    if (tnewlins)
+			taddnl();
+		    else
+			taddchr(' ');
+		    taddstr("esac");
+		    stack = 1;
+		} else {
+		    tindent++;
+		    if (tnewlins)
+			taddnl();
+		    else
+			taddchr(' ');
+		    code = *state->pc++;
+		    taddstr(ecgetstr(state, EC_NODUP, NULL));
+		    state->pc++;
+		    taddstr(") ");
+		    tindent++;
+		    n = tpush(code, 0);
+		    n->u._case.end = end;
+		    n->pop = (state->pc - 2 + WC_CASE_SKIP(code) >= end);
+		}
+	    } else if (state->pc < s->u._case.end) {
+		tindent--;
+		taddstr(WC_CASE_TYPE(code) == WC_CASE_OR ? " ;;" : ";&");
 		if (tnewlins)
 		    taddnl();
 		else
 		    taddchr(' ');
-		taddstr(*p + 1);
+		code = *state->pc++;
+		taddstr(ecgetstr(state, EC_NODUP, NULL));
+		state->pc++;
 		taddstr(") ");
 		tindent++;
-		gt2(*l);
+		s->code = code;
+		s->pop = ((state->pc - 2 + WC_CASE_SKIP(code)) >=
+			  s->u._case.end);
+	    } else {
+		tindent--;
+		taddstr(WC_CASE_TYPE(code) == WC_CASE_OR ? " ;;" : ";&");
 		tindent--;
-		taddstr(" ;");
-		taddchr(**p);
+		if (tnewlins)
+		    taddnl();
+		else
+		    taddchr(' ');
+		taddstr("esac");
+		stack = 1;
 	    }
-	    tindent--;
-	    if (tnewlins)
-		taddnl();
-	    else
-		taddchr(' ');
-	    taddstr("esac");
 	    break;
-	}
-    case N_IF:
-	{
-	    List *i, *t;
+	case WC_IF:
+	    if (!s) {
+		Wordcode end = state->pc + WC_IF_SKIP(code);
 
-	    taddstr("if ");
-	    for (i = _ifcmd(n)->ifls, t = _ifcmd(n)->thenls; *i; i++, t++) {
+		taddstr("if ");
 		tindent++;
-		gt2(*i);
+		state->pc++;
+
+		n = tpush(code, 0);
+		n->u._if.end = end;
+		n->u._if.cond = 1;
+	    } else if (s->pop) {
+		stack = 1;
+	    } else if (s->u._if.cond) {
 		tindent--;
 		taddnl();
 		taddstr("then");
 		tindent++;
 		taddnl();
-		gt2(*t);
+		s->u._if.cond = 0;
+	    } else if (state->pc < s->u._if.end) {
 		tindent--;
 		taddnl();
-		if (i[1]) {
+		code = *state->pc++;
+		if (WC_IF_TYPE(code) == WC_IF_ELIF) {
 		    taddstr("elif ");
+		    tindent++;
+		    s->u._if.cond = 1;
+		} else {
+		    taddstr("else");
+		    tindent++;
+		    taddnl();
 		}
-	    }
-	    if (*t) {
-		taddstr("else");
-		tindent++;
-		taddnl();
-		gt2(*t);
+	    } else {
+		s->pop = 1;
 		tindent--;
 		taddnl();
+		taddstr("fi");
+		stack = 1;
 	    }
 	    break;
-	}
-    case N_WHILE:
-	taddstr((_whilecmd(n)->cond) ? "until " : "while ");
-	tindent++;
-	gt2(_whilecmd(n)->cont);
-	tindent--;
-	taddnl();
-	taddstr("do");
-	tindent++;
-	taddnl();
-	gt2(_whilecmd(n)->loop);
-	tindent--;
-	taddnl();
-	taddstr("done");
-	break;
-    }
-}
-
-/* Print a condition bracketed by [[ ... ]].             *
- * With addpar non-zero, parenthesise the subexpression. */
-
-/**/
-static void
-getcond(Cond nm, int addpar)
-{
-    static char *c1[] =
-    {
-	"=", "!=", "<", ">", "-nt", "-ot", "-ef", "-eq",
-	"-ne", "-lt", "-gt", "-le", "-ge"
-    };
-
-    if (addpar)
-	taddstr("( ");
-    switch (nm->type) {
-    case COND_NOT:
-	taddstr("! ");
-	getcond(nm->left, _Cond(nm->left)->type <= COND_OR);
-	break;
-    case COND_AND:
-	getcond(nm->left, _Cond(nm->left)->type == COND_OR);
-	taddstr(" && ");
-	getcond(nm->right, _Cond(nm->right)->type == COND_OR);
-	break;
-    case COND_OR:
-	/* This is deliberately over-generous with parentheses: *
-	 * in fact omitting them gives correct precedence.      */
-	getcond(nm->left, _Cond(nm->left)->type == COND_AND);
-	taddstr(" || ");
-	getcond(nm->right, _Cond(nm->right)->type == COND_AND);
-	break;
-    default:
-	if (nm->type <= COND_GE) {
-	    /* Binary test: `a = b' etc. */
-	    taddstr(nm->left);
-	    taddstr(" ");
-	    taddstr(c1[nm->type - COND_STREQ]);
-	    taddstr(" ");
-	    taddstr(nm->right);
-	} else {
-	    /* Unary test: `-f foo' etc. */ 
-	    char c2[4];
-
-	    c2[0] = '-';
-	    c2[1] = nm->type;
-	    c2[2] = ' ';
-	    c2[3] = '\0';
-	    taddstr(c2);
-	    taddstr(nm->left);
-	}
-	break;
-    }
-    if (addpar)
-	taddstr(" )");
-}
-
-/**/
-static void
-getsimptext(Cmd cmd)
-{
-    LinkNode n;
-
-    for (n = firstnode(cmd->vars); n; incnode(n)) {
-	struct varasg *v = (struct varasg *)getdata(n);
-
-	taddstr(v->name);
-	taddchr('=');
-	if (PM_TYPE(v->type) == PM_ARRAY) {
-	    taddchr('(');
-	    taddlist(v->arr);
-	    taddstr(") ");
-	} else {
-	    taddstr(v->str);
-	    taddchr(' ');
+	case WC_COND:
+	    {
+		static char *c1[] = {
+		    "=", "!=", "<", ">", "-nt", "-ot", "-ef", "-eq",
+		    "-ne", "-lt", "-gt", "-le", "-ge"
+		};
+
+		int ctype;
+
+		if (!s) {
+		    taddstr("[[ ");
+		    n = tpush(code, 1);
+		    n->u._cond.par = 2;
+		} else if (s->u._cond.par == 2) {
+		    taddstr(" ]]");
+		    stack = 1;
+		    break;
+		} else if (s->u._cond.par == 1) {
+		    taddstr(" )");
+		    stack = 1;
+		    break;
+		} else if (WC_COND_TYPE(s->code) == COND_AND) {
+		    taddstr(" && ");
+		    code = *state->pc++;
+		    if (WC_COND_TYPE(code) == COND_OR) {
+			taddstr("( ");
+			n = tpush(code, 1);
+			n->u._cond.par = 1;
+		    }
+		} else if (WC_COND_TYPE(s->code) == COND_OR) {
+		    taddstr(" || ");
+		    code = *state->pc++;
+		    if (WC_COND_TYPE(code) == COND_AND) {
+			taddstr("( ");
+			n = tpush(code, 1);
+			n->u._cond.par = 1;
+		    }
+		}
+		while (!stack) {
+		    switch ((ctype = WC_COND_TYPE(code))) {
+		    case COND_NOT:
+			taddstr("! ");
+			code = *state->pc++;
+			if (WC_COND_TYPE(code) <= COND_OR) {
+			    taddstr("( ");
+			    n = tpush(code, 1);
+			    n->u._cond.par = 1;
+			}
+			break;
+		    case COND_AND:
+			n = tpush(code, 1);
+			n->u._cond.par = 0;
+			code = *state->pc++;
+			if (WC_COND_TYPE(code) == COND_OR) {
+			    taddstr("( ");
+			    n = tpush(code, 1);
+			    n->u._cond.par = 1;
+			}
+			break;
+		    case COND_OR:
+			n = tpush(code, 1);
+			n->u._cond.par = 0;
+			code = *state->pc++;
+			if (WC_COND_TYPE(code) == COND_AND) {
+			    taddstr("( ");
+			    n = tpush(code, 1);
+			    n->u._cond.par = 1;
+			}
+			break;
+		    case COND_MOD:
+			taddstr(ecgetstr(state, EC_NODUP, NULL));
+			taddchr(' ');
+			taddlist(state, WC_COND_SKIP(code));
+			stack = 1;
+			break;
+		    case COND_MODI:
+			{
+			    char *name = ecgetstr(state, EC_NODUP, NULL);
+
+			    taddstr(ecgetstr(state, EC_NODUP, NULL));
+			    taddchr(' ');
+			    taddstr(name);
+			    taddchr(' ');
+			    taddstr(ecgetstr(state, EC_NODUP, NULL));
+			    stack = 1;
+			}
+			break;
+		    default:
+			if (ctype <= COND_GE) {
+			    /* Binary test: `a = b' etc. */
+			    taddstr(ecgetstr(state, EC_NODUP, NULL));
+			    taddstr(" ");
+			    taddstr(c1[ctype - COND_STREQ]);
+			    taddstr(" ");
+			    taddstr(ecgetstr(state, EC_NODUP, NULL));
+			    if (ctype == COND_STREQ ||
+				ctype == COND_STRNEQ)
+				state->pc++;
+			} else {
+			    /* Unary test: `-f foo' etc. */ 
+			    char c2[4];
+
+			    c2[0] = '-';
+			    c2[1] = ctype;
+			    c2[2] = ' ';
+			    c2[3] = '\0';
+			    taddstr(c2);
+			    taddstr(ecgetstr(state, EC_NODUP, NULL));
+			}
+			stack = 1;
+			break;
+		    }
+		}
+	    }
+	    break;
+	case WC_ARITH:
+	    taddstr("((");
+	    taddstr(ecgetstr(state, EC_NODUP, NULL));
+	    taddstr("))");
+	    stack = 1;
+	    break;
+	case WC_END:
+	    stack = 1;
+	    break;
+	default:
+	    DPUTS(1, "unknown word code in gettext2()");
+	    return;
 	}
     }
-    taddlist(cmd->args);
 }
 
 /**/
 void
-getredirs(Cmd cmd)
+getredirs(LinkList redirs)
 {
     LinkNode n;
     static char *fstr[] =
@@ -468,10 +721,9 @@ getredirs(Cmd cmd)
 	">", ">|", ">>", ">>|", "&>", "&>|", "&>>", "&>>|", "<>", "<",
 	"<<", "<<-", "<<<", "<&", ">&", NULL /* >&- */, "<", ">"
     };
-
     taddchr(' ');
-    for (n = firstnode(cmd->redir); n; incnode(n)) {
-	struct redir *f = (struct redir *)getdata(n);
+    for (n = firstnode(redirs); n; incnode(n)) {
+	Redir f = (Redir) getdata(n);
 
 	switch (f->type) {
 	case WRITE:
@@ -493,7 +745,12 @@ getredirs(Cmd cmd)
 		taddchr('0' + f->fd1);
 	    taddstr(fstr[f->type]);
 	    taddchr(' ');
-	    taddstr(f->name);
+	    if (f->type == HERESTR) {
+		taddchr('\'');
+		taddstr(bslashquote(f->name, NULL, 1));
+		taddchr('\'');
+	    } else
+		taddstr(f->name);
 	    taddchr(' ');
 	    break;
 #ifdef DEBUG
@@ -509,18 +766,3 @@ getredirs(Cmd cmd)
     }
     tptr--;
 }
-
-/**/
-static void
-taddlist(LinkList l)
-{
-    LinkNode n;
-
-    if (!(n = firstnode(l)))
-	return;
-    for (; n; incnode(n)) {
-	taddstr(getdata(n));
-	taddchr(' ');
-    }
-    tptr--;
-}
diff --git a/Src/zsh.h b/Src/zsh.h
index e96fc6e86..35b9c6737 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -31,15 +31,75 @@
 #define zleread(X,Y,H)  zlereadptr(X,Y,H)
 #define spaceinline(X)  spaceinlineptr(X)
 #define gotword()       gotwordptr()
-#define refresh()       refreshptr()
+#define zrefresh()      refreshptr()
 
 #define compctlread(N,A,O,R) compctlreadptr(N,A,O,R)
 
 /* A few typical macros */
 #define minimum(a,b)  ((a) < (b) ? (a) : (b))
 
+/*
+ * Our longest integer type:  will be a 64 bit either if long already is,
+ * or if we found some alternative such as long long.
+ * Currently we only define this to be longer than a long if --enable-lfs
+ * was given.  That enables internal use of 64-bit types even if
+ * no actual large file support is present.
+ */
+#ifdef ZSH_64_BIT_TYPE
+typedef ZSH_64_BIT_TYPE zlong;
+#ifdef ZSH_64_BIT_UTYPE
+typedef ZSH_64_BIT_UTYPE zulong;
+#else
+typedef unsigned zlong zulong;
+#endif
+#else
+typedef long zlong;
+typedef unsigned long zulong;
+#endif
+
+/*
+ * Double float support requires 64-bit alignment, so if longs and
+ * pointers are less we need to pad out.
+ */
+#ifndef LONG_IS_64_BIT
+# define PAD_64_BIT 1
+#endif
+
 /* math.c */
-typedef int LV;
+typedef struct {
+    union {
+	zlong l;
+	double d;
+    } u;
+    int type;
+} mnumber;
+
+#define MN_INTEGER 1		/* mnumber is integer */
+#define MN_FLOAT   2		/* mnumber is floating point */
+
+typedef struct mathfunc *MathFunc;
+typedef mnumber (*NumMathFunc)(char *, int, mnumber *, int);
+typedef mnumber (*StrMathFunc)(char *, char *, int);
+
+struct mathfunc {
+    MathFunc next;
+    char *name;
+    int flags;
+    NumMathFunc nfunc;
+    StrMathFunc sfunc;
+    char *module;
+    int minargs;
+    int maxargs;
+    int funcid;
+};
+
+#define MFF_STR      1
+#define MFF_ADDED    2
+
+#define NUMMATHFUNC(name, func, min, max, id) \
+    { NULL, name, 0, func, NULL, NULL, min, max, id }
+#define STRMATHFUNC(name, func, id) \
+    { NULL, name, MFF_STR, NULL, func, NULL, 0, 0, id }
 
 /* Character tokens are sometimes casted to (unsigned char)'s.         * 
  * Unfortunately, some compilers don't correctly cast signed to        * 
@@ -207,6 +267,7 @@ enum {
 #define INP_HIST      (1<<2)	/* expanding history                       */
 #define INP_CONT      (1<<3)	/* continue onto previously stacked input  */
 #define INP_ALCONT    (1<<4)	/* stack is continued from alias expn.     */
+#define INP_LINENO    (1<<5)    /* update line number                      */
 
 /* Flags for metafy */
 #define META_REALLOC	0
@@ -228,34 +289,33 @@ typedef struct linklist  *LinkList;
 typedef struct hashnode  *HashNode;
 typedef struct hashtable *HashTable;
 
+typedef struct optname   *Optname;
 typedef struct reswd     *Reswd;
 typedef struct alias     *Alias;
 typedef struct param     *Param;
+typedef struct paramdef  *Paramdef;
 typedef struct cmdnam    *Cmdnam;
 typedef struct shfunc    *Shfunc;
+typedef struct funcstack *Funcstack;
+typedef struct funcwrap  *FuncWrap;
 typedef struct builtin   *Builtin;
 typedef struct nameddir  *Nameddir;
 typedef struct module    *Module;
+typedef struct linkedmod *Linkedmod;
 
+typedef struct patprog   *Patprog;
 typedef struct process   *Process;
 typedef struct job       *Job;
 typedef struct value     *Value;
-typedef struct varasg    *Varasg;
-typedef struct cond      *Cond;
-typedef struct cmd       *Cmd;
-typedef struct pline     *Pline;
-typedef struct sublist   *Sublist;
-typedef struct list      *List;
-typedef struct comp      *Comp;
+typedef struct conddef   *Conddef;
 typedef struct redir     *Redir;
 typedef struct complist  *Complist;
 typedef struct heap      *Heap;
 typedef struct heapstack *Heapstack;
 typedef struct histent   *Histent;
-typedef struct forcmd    *Forcmd;
-typedef struct autofn    *AutoFn;
+typedef struct hookdef   *Hookdef;
 
-typedef struct asgment  *Asgment;
+typedef struct asgment   *Asgment;
 
 
 /********************************/
@@ -277,167 +337,57 @@ struct linklist {
 
 /* Macros for manipulating link lists */
 
-#define addlinknode(X,Y) insertlinknode(X,(X)->last,Y)
-#define uaddlinknode(X,Y) uinsertlinknode(X,(X)->last,Y)
-#define empty(X)     ((X)->first == NULL)
-#define nonempty(X)  ((X)->first != NULL)
-#define firstnode(X) ((X)->first)
-#define getaddrdata(X) (&((X)->dat))
-#define getdata(X)   ((X)->dat)
-#define setdata(X,Y) ((X)->dat = (Y))
-#define lastnode(X)  ((X)->last)
-#define nextnode(X)  ((X)->next)
-#define prevnode(X)  ((X)->last)
-#define peekfirst(X) ((X)->first->dat)
-#define pushnode(X,Y) insertlinknode(X,(LinkNode) X,Y)
-#define incnode(X) (X = nextnode(X))
-#define gethistent(X) (histentarr+((X)%histentct))
-
+#define addlinknode(X,Y)    insertlinknode(X,(X)->last,Y)
+#define zaddlinknode(X,Y)   zinsertlinknode(X,(X)->last,Y)
+#define uaddlinknode(X,Y)   uinsertlinknode(X,(X)->last,Y)
+#define empty(X)            ((X)->first == NULL)
+#define nonempty(X)         ((X)->first != NULL)
+#define firstnode(X)        ((X)->first)
+#define getaddrdata(X)      (&((X)->dat))
+#define getdata(X)          ((X)->dat)
+#define setdata(X,Y)        ((X)->dat = (Y))
+#define lastnode(X)         ((X)->last)
+#define nextnode(X)         ((X)->next)
+#define prevnode(X)         ((X)->last)
+#define peekfirst(X)        ((X)->first->dat)
+#define pushnode(X,Y)       insertlinknode(X,(LinkNode) X,Y)
+#define zpushnode(X,Y)      zinsertlinknode(X,(LinkNode) X,Y)
+#define incnode(X)          (X = nextnode(X))
+#define firsthist()         (hist_ring? hist_ring->down->histnum : curhist)
+#define setsizednode(X,Y,Z) ((X)->first[(Y)].dat = (void *) (Z))
+
+/* stack allocated linked lists */
+
+#define local_list0(N) struct linklist N
+#define init_list0(N) \
+    do { \
+        (N).first = NULL; \
+        (N).last = (LinkNode) &(N); \
+    } while (0)
+#define local_list1(N) struct linklist N; struct linknode __n0
+#define init_list1(N,V0) \
+    do { \
+        (N).first = &__n0; \
+        (N).last = &__n0; \
+        __n0.next = NULL; \
+        __n0.last = (LinkNode) &(N); \
+        __n0.dat = (void *) (V0); \
+    } while (0)
 
 /********************************/
 /* Definitions for syntax trees */
 /********************************/
 
-/* struct list, struct sublist, struct pline, etc.  all fit the form *
- * of this structure and are used interchangably. The ptrs may hold  *
- * integers or pointers, depending on the type of the node.          */
-
-/* Generic node structure for syntax trees */
-struct node {
-    int ntype;			/* node type */
-};
-
-#define N_LIST    0
-#define N_SUBLIST 1
-#define N_PLINE   2
-#define N_CMD     3
-#define N_REDIR   4
-#define N_COND    5
-#define N_FOR     6
-#define N_CASE    7
-#define N_IF      8
-#define N_WHILE   9
-#define N_VARASG 10
-#define N_AUTOFN 11
-#define N_COUNT  12
-
-/* values for types[4] */
-
-#define NT_EMPTY 0
-#define NT_NODE  1
-#define NT_STR   2
-#define NT_LIST  4
-#define NT_ARR   8
-
-#define NT_TYPE(T) ((T) & 0xff)
-#define NT_N(T, N) (((T) >> (8 + (N) * 4)) & 0xf)
-#define NT_SET(T0, T1, T2, T3, T4) \
-    ((T0) | ((T1) << 8) | ((T2) << 12) | ((T3) << 16) | ((T4) << 20))
-#define NT_HEAP   (1 << 30)
-
-/* tree element for lists */
-
-struct list {
-    int ntype;			/* node type */
-    int type;
-    Sublist left;
-    List right;
-};
-
 /* These are control flags that are passed *
  * down the execution pipeline.            */
-#define Z_TIMED	(1<<0)	/* pipeline is being timed                   */
-#define Z_SYNC	(1<<1)	/* run this sublist synchronously       (;)  */
-#define Z_ASYNC	(1<<2)	/* run this sublist asynchronously      (&)  */
+#define Z_TIMED	 (1<<0)	/* pipeline is being timed                   */
+#define Z_SYNC	 (1<<1)	/* run this sublist synchronously       (;)  */
+#define Z_ASYNC  (1<<2)	/* run this sublist asynchronously      (&)  */
 #define Z_DISOWN (1<<3)	/* run this sublist without job control (&|) */
+/* (1<<4) is used for Z_END, see the wordcode definitions */
+/* (1<<5) is used for Z_SIMPLE, see the wordcode definitions */
 
-/* tree element for sublists */
-
-struct sublist {
-    int ntype;			/* node type */
-    int type;
-    int flags;			/* see PFLAGs below */
-    Pline left;
-    Sublist right;
-};
-
-#define ORNEXT  10		/* || */
-#define ANDNEXT 11		/* && */
-
-#define PFLAG_NOT     1		/* ! ... */
-#define PFLAG_COPROC 32		/* coproc ... */
-
-/* tree element for pipes */
-
-struct pline {
-    int ntype;			/* node type */
-    int type;
-    Cmd left;
-    Pline right;
-};
-
-#define END	0		/* pnode *right is null                     */
-#define PIPE	1		/* pnode *right is the rest of the pipeline */
-
-/* tree element for commands */
-
-struct cmd {
-    int ntype;			/* node type                    */
-    int type;
-    int flags;			/* see CFLAGs below             */
-    int lineno;			/* lineno of script for command */
-    union {
-	List list;		/* for SUBSH/CURSH/SHFUNC       */
-	Forcmd forcmd;
-	struct casecmd *casecmd;
-	struct ifcmd *ifcmd;
-	struct whilecmd *whilecmd;
-	Sublist pline;
-	Cond cond;
-	AutoFn autofn;
-	void *generic;
-    } u;
-    LinkList args;		/* command & argmument List (char *'s)   */
-    LinkList redir;		/* i/o redirections (struct redir *'s)   */
-    LinkList vars;		/* param assignments (struct varasg *'s) */
-};
-
-/* cmd types */
-#define SIMPLE   0
-#define SUBSH    1
-#define CURSH    2
-#define ZCTIME   3
-#define FUNCDEF  4
-#define CFOR     5
-#define CWHILE   6
-#define CREPEAT  7
-#define CIF      8
-#define CCASE    9
-#define CSELECT 10
-#define COND    11
-#define CARITH  12
-#define AUTOFN  13
-
-/* flags for command modifiers */
-#define CFLAG_EXEC	(1<<0)	/* exec ...    */
-
-/* tree element for redirection lists */
-
-struct redir {
-    int ntype;			/* node type */
-    int type;
-    int fd1, fd2;
-    char *name;
-};
-
-/* tree element for conditionals */
-
-struct cond {
-    int ntype;		/* node type                     */
-    int type;		/* can be cond_type, or a single */
-			/* letter (-a, -b, ...)          */
-    void *left, *right;
-};
+/* Condition types. */
 
 #define COND_NOT    0
 #define COND_AND    1
@@ -455,50 +405,34 @@ struct cond {
 #define COND_GT    13
 #define COND_LE    14
 #define COND_GE    15
-
-struct forcmd {			/* for/select */
-/* Cmd->args contains list of words to loop thru */
-    int ntype;			/* node type                          */
-    int inflag;			/* if there is an in ... clause       */
-    char *name;			/* initializer or parameter name      */
-    char *condition;		/* arithmetic terminating condition   */
-    char *advance;		/* evaluated after each loop          */
-    List list;			/* list to look through for each name */
-};
-
-struct casecmd {
-/* Cmd->args contains word to test */
-    int ntype;			/* node type       */
-    char **pats;
-    List *lists;		/* list to execute */
+#define COND_MOD   16
+#define COND_MODI  17
+
+typedef int (*CondHandler) _((char **, int));
+
+struct conddef {
+    Conddef next;		/* next in list                       */
+    char *name;			/* the condition name                 */
+    int flags;			/* see CONDF_* below                  */
+    CondHandler handler;	/* handler function                   */
+    int min;			/* minimum number of strings          */
+    int max;			/* maximum number of strings          */
+    int condid;			/* for overloading handler functions  */
+    char *module;		/* module to autoload                 */
 };
 
+#define CONDF_INFIX  1
+#define CONDF_ADDED  2
 
-/*  A command like "if foo then bar elif baz then fubar else fooble"  */
-/*  generates a tree like:                                            */
-/*                                                                    */
-/*  struct ifcmd a = { next =  &b,  ifl = "foo", thenl = "bar" }      */
-/*  struct ifcmd b = { next =  &c,  ifl = "baz", thenl = "fubar" }    */
-/*  struct ifcmd c = { next = NULL, ifl = NULL, thenl = "fooble" }    */
-
-struct ifcmd {
-    int ntype;			/* node type */
-    List *ifls;
-    List *thenls;
-};
+#define CONDDEF(name, flags, handler, min, max, condid) \
+    { NULL, name, flags, handler, min, max, condid, NULL }
 
-struct whilecmd {
-    int ntype;			/* node type                           */
-    int cond;			/* 0 for while, 1 for until            */
-    List cont;			/* condition                           */
-    List loop;			/* list to execute until condition met */
-};
-
-/* node for autoloading functions */
+/* tree element for redirection lists */
 
-struct autofn {
-    int ntype;			/* node type                             */
-    Shfunc shf;			/* the shell function to define	         */
+struct redir {
+    int type;
+    int fd1, fd2;
+    char *name;
 };
 
 /* The number of fds space is allocated for  *
@@ -520,14 +454,12 @@ struct multio {
     int fds[MULTIOUNIT];	/* list of src/dests redirected to/from this fd */
 };
 
-/* variable assignment tree element */
+/* structure for foo=bar assignments */
 
-struct varasg {
-    int ntype;			/* node type                             */
-    int type;			/* nonzero means array                   */
+struct asgment {
+    struct asgment *next;
     char *name;
-    char *str;			/* should've been a union here.  oh well */
-    LinkList arr;
+    char *value;
 };
 
 /* lvalue for variable assignment/expansion */
@@ -538,18 +470,191 @@ 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 */
+#define MAX_ARRLEN    262144
 
-struct asgment {
-    struct asgment *next;
-    char *name;
-    char *value;
+/********************************************/
+/* Defintions for word code                 */
+/********************************************/
+
+typedef unsigned int wordcode;
+typedef wordcode *Wordcode;
+
+typedef struct funcdump *FuncDump;
+typedef struct eprog *Eprog;
+
+struct funcdump {
+    FuncDump next;		/* next in list */
+    char *name;			/* path name */
+    int fd;			/* file descriptor */
+    Wordcode map;		/* pointer to header */
+    Wordcode addr;		/* mapped region */
+    int len;			/* length */
+    int count;			/* reference count */
 };
 
-#define MAX_ARRLEN    262144
+struct eprog {
+    int flags;			/* EF_* below */
+    int len;			/* total block length */
+    int npats;			/* Patprog cache size */
+    Patprog *pats;		/* the memory block, the patterns */
+    Wordcode prog;		/* memory block ctd, the code */
+    char *strs;			/* memory block ctd, the strings */
+    Shfunc shf;			/* shell function for autoload */
+    FuncDump dump;		/* dump file this is in */
+};
+
+#define EF_REAL 1
+#define EF_HEAP 2
+#define EF_MAP  4
+#define EF_RUN  8
+
+typedef struct estate *Estate;
+
+struct estate {
+    Eprog prog;			/* the eprog executed */
+    Wordcode pc;		/* program counter, current pos */
+    char *strs;			/* strings from prog */
+};
+
+typedef struct eccstr *Eccstr;
+
+struct eccstr {
+    Eccstr next;
+    char *str;
+    wordcode offs;
+    int nfunc;
+};
 
+#define EC_NODUP  0
+#define EC_DUP    1
+#define EC_DUPTOK 2
+
+#define WC_CODEBITS 5
+
+#define wc_code(C)   ((C) & ((wordcode) ((1 << WC_CODEBITS) - 1)))
+#define wc_data(C)   ((C) >> WC_CODEBITS)
+#define wc_bdata(D)  ((D) << WC_CODEBITS)
+#define wc_bld(C,D)  (((wordcode) (C)) | (((wordcode) (D)) << WC_CODEBITS))
+
+#define WC_END      0
+#define WC_LIST     1
+#define WC_SUBLIST  2
+#define WC_PIPE     3
+#define WC_REDIR    4
+#define WC_ASSIGN   5
+#define WC_SIMPLE   6
+#define WC_SUBSH    7
+#define WC_CURSH    8
+#define WC_TIMED    9
+#define WC_FUNCDEF 10
+#define WC_FOR     11
+#define WC_SELECT  12
+#define WC_WHILE   13
+#define WC_REPEAT  14
+#define WC_CASE    15
+#define WC_IF      16
+#define WC_COND    17
+#define WC_ARITH   18
+#define WC_AUTOFN  19
+
+#define WCB_END()           wc_bld(WC_END, 0)
+
+#define WC_LIST_TYPE(C)     wc_data(C)
+#define Z_END               (1<<4) 
+#define Z_SIMPLE            (1<<5)
+#define WC_LIST_SKIP(C)     (wc_data(C) >> 6)
+#define WCB_LIST(T,O)       wc_bld(WC_LIST, ((T) | ((O) << 6)))
+
+#define WC_SUBLIST_TYPE(C)  (wc_data(C) & ((wordcode) 3))
+#define WC_SUBLIST_END      0
+#define WC_SUBLIST_AND      1
+#define WC_SUBLIST_OR       2
+#define WC_SUBLIST_FLAGS(C) (wc_data(C) & ((wordcode) 0x1c))
+#define WC_SUBLIST_COPROC   4
+#define WC_SUBLIST_NOT      8
+#define WC_SUBLIST_SIMPLE  16
+#define WC_SUBLIST_SKIP(C)  (wc_data(C) >> 5)
+#define WCB_SUBLIST(T,F,O)  wc_bld(WC_SUBLIST, ((T) | (F) | ((O) << 5)))
+
+#define WC_PIPE_TYPE(C)     (wc_data(C) & ((wordcode) 1))
+#define WC_PIPE_END         0
+#define WC_PIPE_MID         1
+#define WC_PIPE_LINENO(C)   (wc_data(C) >> 1)
+#define WCB_PIPE(T,L)       wc_bld(WC_PIPE, ((T) | ((L) << 1)))
+
+#define WC_REDIR_TYPE(C)    wc_data(C)
+#define WCB_REDIR(T)        wc_bld(WC_REDIR, (T))
+
+#define WC_ASSIGN_TYPE(C)   (wc_data(C) & ((wordcode) 1))
+#define WC_ASSIGN_SCALAR    0
+#define WC_ASSIGN_ARRAY     1
+#define WC_ASSIGN_NUM(C)    (wc_data(C) >> 1)
+#define WCB_ASSIGN(T,N)     wc_bld(WC_ASSIGN, ((T) | ((N) << 1)))
+
+#define WC_SIMPLE_ARGC(C)   wc_data(C)
+#define WCB_SIMPLE(N)       wc_bld(WC_SIMPLE, (N))
+
+#define WC_SUBSH_SKIP(C)    wc_data(C)
+#define WCB_SUBSH(O)        wc_bld(WC_SUBSH, (O))
+
+#define WC_CURSH_SKIP(C)    wc_data(C)
+#define WCB_CURSH(O)        wc_bld(WC_CURSH, (O))
+
+#define WC_TIMED_TYPE(C)    wc_data(C)
+#define WC_TIMED_EMPTY      0
+#define WC_TIMED_PIPE       1
+#define WCB_TIMED(T)        wc_bld(WC_TIMED, (T))
+
+#define WC_FUNCDEF_SKIP(C)  wc_data(C)
+#define WCB_FUNCDEF(O)      wc_bld(WC_FUNCDEF, (O))
+
+#define WC_FOR_TYPE(C)      (wc_data(C) & 3)
+#define WC_FOR_PPARAM       0
+#define WC_FOR_LIST         1
+#define WC_FOR_COND         2
+#define WC_FOR_SKIP(C)      (wc_data(C) >> 2)
+#define WCB_FOR(T,O)        wc_bld(WC_FOR, ((T) | ((O) << 2)))
+
+#define WC_SELECT_TYPE(C)   (wc_data(C) & 1)
+#define WC_SELECT_PPARAM    0
+#define WC_SELECT_LIST      1
+#define WC_SELECT_SKIP(C)   (wc_data(C) >> 1)
+#define WCB_SELECT(T,O)     wc_bld(WC_SELECT, ((T) | ((O) << 1)))
+
+#define WC_WHILE_TYPE(C)    (wc_data(C) & 1)
+#define WC_WHILE_WHILE      0
+#define WC_WHILE_UNTIL      1
+#define WC_WHILE_SKIP(C)    (wc_data(C) >> 1)
+#define WCB_WHILE(T,O)      wc_bld(WC_WHILE, ((T) | ((O) << 1)))
+
+#define WC_REPEAT_SKIP(C)   wc_data(C)
+#define WCB_REPEAT(O)       wc_bld(WC_REPEAT, (O))
+
+#define WC_CASE_TYPE(C)     (wc_data(C) & 3)
+#define WC_CASE_HEAD        0
+#define WC_CASE_OR          1
+#define WC_CASE_AND         2
+#define WC_CASE_SKIP(C)     (wc_data(C) >> 2)
+#define WCB_CASE(T,O)       wc_bld(WC_CASE, ((T) | ((O) << 2)))
+
+#define WC_IF_TYPE(C)       (wc_data(C) & 3)
+#define WC_IF_HEAD          0
+#define WC_IF_IF            1
+#define WC_IF_ELIF          2
+#define WC_IF_ELSE          3
+#define WC_IF_SKIP(C)       (wc_data(C) >> 2)
+#define WCB_IF(T,O)         wc_bld(WC_IF, ((T) | ((O) << 2)))
+
+#define WC_COND_TYPE(C)     (wc_data(C) & 127)
+#define WC_COND_SKIP(C)     (wc_data(C) >> 7)
+#define WCB_COND(T,O)       wc_bld(WC_COND, ((T) | ((O) << 7)))
+
+#define WCB_ARITH()         wc_bld(WC_ARITH, 0)
+
+#define WCB_AUTOFN()        wc_bld(WC_AUTOFN, 0)
 
 /********************************************/
 /* Defintions for job table and job control */
@@ -583,9 +688,13 @@ struct job {
 #define STAT_INUSE	(1<<6)	/* this job entry is in use             */
 #define STAT_SUPERJOB	(1<<7)	/* job has a subjob                     */
 #define STAT_SUBJOB	(1<<8)	/* job is a subjob                      */
-#define STAT_CURSH	(1<<9)	/* last command is in current shell     */
-#define STAT_NOSTTY	(1<<10)	/* the tty settings are not inherited   */
+#define STAT_WASSUPER   (1<<9)  /* was a super-job, sub-job needs to be */
+				/* deleted */
+#define STAT_CURSH	(1<<10)	/* last command is in current shell     */
+#define STAT_NOSTTY	(1<<11)	/* the tty settings are not inherited   */
 				/* from this job when it exits.         */
+#define STAT_ATTACH	(1<<12)	/* delay reattaching shell to tty       */
+#define STAT_SUBLEADER  (1<<13) /* is super-job, but leader is sub-shell */
 
 #define SP_RUNNING -1		/* fake status for jobs currently running */
 
@@ -631,7 +740,9 @@ struct execstack {
 
 struct heredocs {
     struct heredocs *next;
-    Redir rd;
+    int type;
+    int pc;
+    char *str;
 };
 
 struct dirsav {
@@ -641,6 +752,8 @@ struct dirsav {
     ino_t ino;
 };
 
+#define MAX_PIPESTATS 256
+
 /*******************************/
 /* Definitions for Hash Tables */
 /*******************************/
@@ -654,10 +767,12 @@ typedef void     (*AddNodeFunc)    _((HashTable, char *, void *));
 typedef HashNode (*GetNodeFunc)    _((HashTable, char *));
 typedef HashNode (*RemoveNodeFunc) _((HashTable, char *));
 typedef void     (*FreeNodeFunc)   _((HashNode));
+typedef int      (*CompareFunc)    _((const char *, const char *));
 
 /* type of function that is passed to *
  * scanhashtable or scanmatchtable    */
 typedef void     (*ScanFunc)       _((HashNode, int));
+typedef void     (*ScanTabFunc)    _((HashTable, ScanFunc, int));
 
 typedef void (*PrintTableStats) _((HashTable));
 
@@ -673,6 +788,7 @@ struct hashtable {
     HashFunc hash;		/* pointer to hash function for this table    */
     TableFunc emptytable;	/* pointer to function to empty table         */
     TableFunc filltable;	/* pointer to function to fill table          */
+    CompareFunc cmpnodes;	/* pointer to function to compare two nodes     */
     AddNodeFunc addnode;	/* pointer to function to add new node        */
     GetNodeFunc getnode;	/* pointer to function to get an enabled node */
     GetNodeFunc getnode2;	/* pointer to function to get node            */
@@ -682,6 +798,7 @@ struct hashtable {
     ScanFunc enablenode;	/* pointer to function to enable a node       */
     FreeNodeFunc freenode;	/* pointer to function to free a node         */
     ScanFunc printnode;		/* pointer to function to print a node        */
+    ScanTabFunc scantab;	/* pointer to function to scan table          */
 
 #ifdef HASHTABLE_INTERNAL_MEMBERS
     HASHTABLE_INTERNAL_MEMBERS	/* internal use in hashtable.c                */
@@ -701,6 +818,15 @@ struct hashnode {
  * reserved words.                                        */
 #define DISABLED	(1<<0)
 
+/* node in shell option table */
+
+struct optname {
+    HashNode next;		/* next in hash chain */
+    char *nam;			/* hash data */
+    int flags;
+    int optno;			/* option number */
+};
+
 /* node in shell reserved word hash table (reswdtab) */
 
 struct reswd {
@@ -746,9 +872,42 @@ struct shfunc {
     HashNode next;		/* next in hash chain     */
     char *nam;			/* name of shell function */
     int flags;			/* various flags          */
-    List funcdef;		/* function definition    */
+    Eprog funcdef;		/* function definition    */
 };
 
+/* Shell function context types. */
+
+#define SFC_NONE     0		/* no function running */
+#define SFC_DIRECT   1		/* called directly from the user */
+#define SFC_SIGNAL   2		/* signal handler */
+#define SFC_HOOK     3		/* one of the special functions */
+#define SFC_WIDGET   4		/* user defined widget */
+#define SFC_COMPLETE 5		/* called from completion code */
+#define SFC_CWIDGET  6		/* new style completion widget */
+
+/* node in function stack */
+
+struct funcstack {
+    Funcstack prev;		/* previous in stack */
+    char *name;			/* name of function called */
+};
+
+/* node in list of function call wrappers */
+
+typedef int (*WrapFunc) _((Eprog, FuncWrap, char *));
+
+struct funcwrap {
+    FuncWrap next;
+    int flags;
+    WrapFunc handler;
+    Module module;
+};
+
+#define WRAPF_ADDED 1
+
+#define WRAPDEF(func) \
+    { NULL, 0, func, NULL }
+
 /* node in builtin command hash table (builtintab) */
 
 typedef int (*HandlerFunc) _((char *, char **, char *, int));
@@ -794,11 +953,87 @@ struct builtin {
 struct module {
     char *nam;
     int flags;
-    void *handle;
+    union {
+	void *handle;
+	Linkedmod linked;
+    } u;
     LinkList deps;
+    int wrapper;
 };
 
 #define MOD_BUSY    (1<<0)
+#define MOD_UNLOAD  (1<<1)
+#define MOD_SETUP   (1<<2)
+#define MOD_LINKED  (1<<3)
+#define MOD_INIT_S  (1<<4)
+#define MOD_INIT_B  (1<<5)
+
+typedef int (*Module_func) _((Module));
+
+struct linkedmod {
+    char *name;
+    Module_func setup;
+    Module_func boot;
+    Module_func cleanup;
+    Module_func finish;
+};
+
+/* C-function hooks */
+
+typedef int (*Hookfn) _((Hookdef, void *));
+
+struct hookdef {
+    Hookdef next;
+    char *name;
+    Hookfn def;
+    int flags;
+    LinkList funcs;
+};
+
+#define HOOKF_ALL 1
+
+#define HOOKDEF(name, func, flags) { NULL, name, (Hookfn) func, flags, NULL }
+
+/*
+ * Types used in pattern matching.  Most of these longs could probably
+ * happily be ints.
+ */
+
+struct patprog {
+    long		startoff;  /* length before start of programme */
+    long		size;	   /* total size from start of struct */
+    long		mustoff;   /* offset to string that must be present */
+    int			globflags; /* globbing flags to set at start */
+    int			globend;   /* globbing flags set after finish */
+    int			flags;	   /* PAT_* flags */
+    int			patmlen;   /* length of pure string or longest match */
+    int			patnpar;   /* number of active parentheses */
+    char		patstartch;
+};
+
+/* Flags used in pattern matchers (Patprog) and passed down to patcompile */
+
+#define PAT_FILE	0x0001	/* Pattern is a file name */
+#define PAT_FILET	0x0002	/* Pattern is top level file, affects ~ */
+#define PAT_ANY		0x0004	/* Match anything (cheap "*") */
+#define PAT_NOANCH	0x0008	/* Not anchored at end */
+#define PAT_NOGLD	0x0010	/* Don't glob dots */
+#define PAT_PURES	0x0020	/* Pattern is a pure string: set internally */
+#define PAT_STATIC	0x0040	/* Don't copy pattern to heap as per default */
+#define PAT_SCAN	0x0080	/* Scanning, so don't try must-match test */
+#define PAT_ZDUP        0x0100  /* Copy pattern in real memory */
+
+/* Globbing flags: lower 8 bits gives approx count */
+#define GF_LCMATCHUC	0x0100
+#define GF_IGNCASE	0x0200
+#define GF_BACKREF	0x0400
+#define GF_MATCHREF	0x0800
+
+/* Dummy Patprog pointers. Used mainly in executable code, but the
+ * pattern code needs to know about it, too. */
+
+#define dummy_patprog1 ((Patprog) 1)
+#define dummy_patprog2 ((Patprog) 2)
 
 /* node used in parameter hash table (paramtab) */
 
@@ -812,21 +1047,28 @@ struct param {
 	void *data;		/* used by special parameter functions    */
 	char **arr;		/* value if declared array   (PM_ARRAY)   */
 	char *str;		/* value if declared string  (PM_SCALAR)  */
-	long val;		/* value if declared integer (PM_INTEGER) */
+	zlong val;		/* value if declared integer (PM_INTEGER) */
+	double dval;		/* value if declared float
+				                    (PM_EFLOAT|PM_FFLOAT) */
+        HashTable hash;		/* value if declared assoc   (PM_HASHED)  */
     } u;
 
     /* pointer to function to set value of this parameter */
     union {
 	void (*cfn) _((Param, char *));
-	void (*ifn) _((Param, long));
+	void (*ifn) _((Param, zlong));
+	void (*ffn) _((Param, double));
 	void (*afn) _((Param, char **));
+        void (*hfn) _((Param, HashTable));
     } sets;
 
     /* pointer to function to get value of this parameter */
     union {
 	char *(*cfn) _((Param));
-	long (*ifn) _((Param));
+	zlong (*ifn) _((Param));
+	double (*ffn) _((Param));
 	char **(*afn) _((Param));
+        HashTable (*hfn) _((Param));
     } gets;
 
     /* pointer to function to unset this parameter */
@@ -842,30 +1084,108 @@ struct param {
 /* flags for parameters */
 
 /* parameter types */
-#define PM_SCALAR	0	/* scalar                                     */
-#define PM_ARRAY	(1<<0)	/* array                                      */
-#define PM_INTEGER	(1<<1)	/* integer                                    */
+#define PM_SCALAR	0	/* scalar                                   */
+#define PM_ARRAY	(1<<0)	/* array                                    */
+#define PM_INTEGER	(1<<1)	/* integer                                  */
+#define PM_EFLOAT	(1<<2)	/* double with %e output		    */
+#define PM_FFLOAT	(1<<3)	/* double with %f output		    */
+#define PM_HASHED	(1<<4)	/* association                              */
 
-#define PM_TYPE(X) (X & (PM_SCALAR|PM_INTEGER|PM_ARRAY))
+#define PM_TYPE(X) \
+  (X & (PM_SCALAR|PM_INTEGER|PM_EFLOAT|PM_FFLOAT|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 */
-#define PM_RIGHT_Z	(1<<4)	/* right justify and fill with leading zeros  */
-#define PM_LOWER	(1<<5)	/* all lower case                             */
+#define PM_LEFT		(1<<5)	/* left justify, remove leading blanks      */
+#define PM_RIGHT_B	(1<<6)	/* right justify, fill with leading blanks  */
+#define PM_RIGHT_Z	(1<<7)	/* right justify, fill with leading zeros   */
+#define PM_LOWER	(1<<8)	/* all lower case                           */
 
 /* The following are the same since they *
  * both represent -u option to typeset   */
-#define PM_UPPER	(1<<6)	/* all upper case                             */
-#define PM_UNDEFINED	(1<<6)	/* undefined (autoloaded) shell function      */
-
-#define PM_READONLY	(1<<7)	/* readonly                                   */
-#define PM_TAGGED	(1<<8)	/* tagged                                     */
-#define PM_EXPORTED	(1<<9)	/* exported                                   */
-#define PM_UNIQUE	(1<<10)	/* remove duplicates                          */
-#define PM_SPECIAL	(1<<11) /* special builtin parameter                  */
-#define PM_DONTIMPORT	(1<<12)	/* do not import this variable                */
-#define PM_RESTRICTED	(1<<13) /* cannot be changed in restricted mode       */
-#define PM_UNSET	(1<<14)	/* has null value                             */
+#define PM_UPPER	(1<<9)	/* all upper case                           */
+#define PM_UNDEFINED	(1<<9)	/* undefined (autoloaded) shell function    */
+
+#define PM_READONLY	(1<<10)	/* readonly                                 */
+#define PM_TAGGED	(1<<11)	/* tagged                                   */
+#define PM_EXPORTED	(1<<12)	/* exported                                 */
+
+/* The following are the same since they *
+ * both represent -U option to typeset   */
+#define PM_UNIQUE	(1<<13)	/* remove duplicates                        */
+#define PM_UNALIASED	(1<<13)	/* do not expand aliases when autoloading   */
+
+#define PM_HIDE		(1<<14)	/* Special behaviour hidden by local        */
+#define PM_TIED 	(1<<15)	/* array tied to colon-path or v.v.         */
+
+/* Remaining flags do not correspond directly to command line arguments */
+#define PM_LOCAL	(1<<16) /* this parameter will be made local        */
+#define PM_SPECIAL	(1<<17) /* special builtin parameter                */
+#define PM_DONTIMPORT	(1<<18)	/* do not import this variable              */
+#define PM_RESTRICTED	(1<<19) /* cannot be changed in restricted mode     */
+#define PM_UNSET	(1<<20)	/* has null value                           */
+#define PM_REMOVABLE	(1<<21)	/* special can be removed from paramtab     */
+#define PM_AUTOLOAD	(1<<22) /* autoloaded from module                   */
+#define PM_NORESTORE	(1<<23)	/* do not restore value of local special    */
+
+/* The option string corresponds to the first of the variables above */
+#define TYPESET_OPTSTR "aiEFALRZlurtxUhT"
+
+/* These typeset options take an optional numeric argument */
+#define TYPESET_OPTNUM "LRZiEF"
+
+/* Flags for extracting elements of arrays and associative arrays */
+#define SCANPM_WANTVALS   (1<<0)
+#define SCANPM_WANTKEYS   (1<<1)
+#define SCANPM_WANTINDEX  (1<<2)
+#define SCANPM_MATCHKEY   (1<<3)
+#define SCANPM_MATCHVAL   (1<<4)
+#define SCANPM_MATCHMANY  (1<<5)
+#define SCANPM_ASSIGNING  (1<<6)
+#define SCANPM_KEYMATCH   (1<<7)
+#define SCANPM_ISVAR_AT   ((-1)<<15)	/* Only sign bit is significant */
+
+/*
+ * Flags for doing matches inside parameter substitutions, i.e.
+ * ${...#...} and friends.  This could be an enum, but so
+ * could a lot of other things.
+ */
+
+#define SUB_END		0x0001	/* match end instead of begining, % or %%  */
+#define SUB_LONG	0x0002	/* % or # doubled, get longest match */
+#define SUB_SUBSTR	0x0004	/* match a substring */
+#define SUB_MATCH	0x0008	/* include the matched portion */
+#define SUB_REST	0x0010	/* include the unmatched portion */
+#define SUB_BIND	0x0020	/* index of beginning of string */
+#define SUB_EIND	0x0040	/* index of end of string */
+#define SUB_LEN		0x0080	/* length of match */
+#define SUB_ALL		0x0100	/* match complete string */
+#define SUB_GLOBAL	0x0200	/* global substitution ${..//all/these} */
+#define SUB_DOSUBST	0x0400	/* replacement string needs substituting */
+
+/* Flags as the second argument to prefork */
+#define PF_TYPESET	0x01	/* argument handled like typeset foo=bar */
+#define PF_ASSIGN	0x02	/* argument handled like the RHS of foo=bar */
+#define PF_SINGLE	0x04	/* single word substitution */
+
+struct paramdef {
+    char *name;
+    int flags;
+    void *var;
+    void *set;
+    void *get;
+    void *unset;
+};
+
+#define PARAMDEF(name, flags, var, set, get, unset) \
+    { name, flags, (void *) var, (void *) set, (void *) get, (void *) unset }
+#define INTPARAMDEF(name, var) \
+    { name, PM_INTEGER, (void *) var, (void *) intvarsetfn, \
+      (void *) intvargetfn, (void *) stdunsetfn }
+#define STRPARAMDEF(name, var) \
+    { name, PM_SCALAR, (void *) var, (void *) strvarsetfn, \
+      (void *) strvargetfn, (void *) stdunsetfn }
+#define ARRPARAMDEF(name, var) \
+    { name, PM_ARRAY, (void *) var, (void *) arrvarsetfn, \
+      (void *) arrvargetfn, (void *) stdunsetfn }
 
 /* node for named directory hash table (nameddirtab) */
 
@@ -880,19 +1200,21 @@ struct nameddir {
 /* flags for named directories */
 /* DISABLED is defined (1<<0) */
 #define ND_USERNAME	(1<<1)	/* nam is actually a username       */
+#define ND_NOABBREV	(1<<2)	/* never print as abbrev (PWD or OLDPWD) */
 
 
 /* flags for controlling printing of hash table nodes */
 #define PRINT_NAMEONLY		(1<<0)
 #define PRINT_TYPE		(1<<1)
 #define PRINT_LIST		(1<<2)
+#define PRINT_KV_PAIR		(1<<3)
 
 /* flags for printing for the whence builtin */
-#define PRINT_WHENCE_CSH	(1<<3)
-#define PRINT_WHENCE_VERBOSE	(1<<4)
-#define PRINT_WHENCE_SIMPLE	(1<<5)
-#define PRINT_WHENCE_FUNCDEF	(1<<6)
-#define PRINT_WHENCE_WORD	(1<<7)
+#define PRINT_WHENCE_CSH	(1<<4)
+#define PRINT_WHENCE_VERBOSE	(1<<5)
+#define PRINT_WHENCE_SIMPLE	(1<<6)
+#define PRINT_WHENCE_FUNCDEF	(1<<7)
+#define PRINT_WHENCE_WORD	(1<<8)
 
 /***********************************/
 /* Definitions for history control */
@@ -901,18 +1223,30 @@ struct nameddir {
 /* history entry */
 
 struct histent {
+    HashNode hash_next;		/* next in hash chain               */
     char *text;			/* the history line itself          */
+    int flags;			/* Misc flags                       */
+
+    Histent up;			/* previous line (moving upward)    */
+    Histent down;		/* next line (moving downward)      */
     char *zle_text;		/* the edited history line          */
     time_t stim;		/* command started time (datestamp) */
     time_t ftim;		/* command finished time            */
     short *words;		/* Position of words in history     */
 				/*   line:  as pairs of start, end  */
     int nwords;			/* Number of words in history line  */
-    int flags;			/* Misc flags                       */
+    int histnum;		/* A sequential history number      */
 };
 
-#define HIST_OLD	0x00000001	/* Command is already written to disk*/
-#define HIST_READ	0x00000002	/* Command was read back from disk*/
+#define HIST_MAKEUNIQUE	0x00000001	/* Kill this new entry if not unique */
+#define HIST_OLD	0x00000002	/* Command is already written to disk*/
+#define HIST_READ	0x00000004	/* Command was read back from disk*/
+#define HIST_DUP	0x00000008	/* Command duplicates a later line */
+#define HIST_FOREIGN	0x00000010	/* Command came from another shell */
+
+#define GETHIST_UPWARD  (-1)
+#define GETHIST_DOWNWARD  1
+#define GETHIST_EXACT     0
 
 /* Parts of the code where history expansion is disabled *
  * should be within a pair of STOPHIST ... ALLOWHIST     */
@@ -923,7 +1257,14 @@ struct histent {
 #define HISTFLAG_DONE   1
 #define HISTFLAG_NOEXEC 2
 #define HISTFLAG_RECALL 4
+#define HISTFLAG_SETTY  8
 
+#define HFILE_APPEND		0x0001
+#define HFILE_SKIPOLD		0x0002
+#define HFILE_SKIPDUPS		0x0004
+#define HFILE_SKIPFOREIGN	0x0008
+#define HFILE_FAST		0x0010
+#define HFILE_USE_OPTIONS	0x8000
 
 /******************************************/
 /* Definitions for programable completion */
@@ -972,12 +1313,15 @@ enum {
     BADPATTERN,
     BANGHIST,
     BAREGLOBQUAL,
+    BASHAUTOLIST,
     BEEP,
     BGNICE,
     BRACECCL,
     BSDECHO,
     CDABLEVARS,
+    CHASEDOTS,
     CHASELINKS,
+    CHECKJOBS,
     CLOBBER,
     COMPLETEALIASES,
     COMPLETEINWORD,
@@ -986,6 +1330,7 @@ enum {
     CSHJUNKIEHISTORY,
     CSHJUNKIELOOPS,
     CSHJUNKIEQUOTES,
+    CSHNULLCMD,
     CSHNULLGLOB,
     EQUALS,
     ERREXIT,
@@ -994,6 +1339,7 @@ enum {
     EXTENDEDHISTORY,
     FLOWCONTROL,
     FUNCTIONARGZERO,
+    GLOBALRCS,
     GLOBOPT,
     GLOBASSIGN,
     GLOBCOMPLETE,
@@ -1004,15 +1350,20 @@ enum {
     HASHLISTALL,
     HISTALLOWCLOBBER,
     HISTBEEP,
+    HISTEXPIREDUPSFIRST,
+    HISTFINDNODUPS,
+    HISTIGNOREALLDUPS,
     HISTIGNOREDUPS,
     HISTIGNORESPACE,
     HISTNOFUNCTIONS,
     HISTNOSTORE,
     HISTREDUCEBLANKS,
+    HISTSAVENODUPS,
     HISTVERIFY,
     HUP,
     IGNOREBRACES,
     IGNOREEOF,
+    INCAPPENDHISTORY,
     INTERACTIVE,
     INTERACTIVECOMMENTS,
     KSHARRAYS,
@@ -1021,8 +1372,11 @@ enum {
     KSHOPTIONPRINT,
     LISTAMBIGUOUS,
     LISTBEEP,
+    LISTPACKED,
+    LISTROWSFIRST,
     LISTTYPES,
     LOCALOPTIONS,
+    LOCALTRAPS,
     LOGINSHELL,
     LONGLISTJOBS,
     MAGICEQUALSUBST,
@@ -1056,9 +1410,11 @@ enum {
     RESTRICTED,
     RMSTARSILENT,
     RMSTARWAIT,
+    SHAREHISTORY,
     SHFILEEXPANSION,
     SHGLOB,
     SHINSTDIN,
+    SHNULLCMD,
     SHOPTIONLETTERS,
     SHORTLOOPS,
     SHWORDSPLIT,
@@ -1154,7 +1510,8 @@ struct ttyinfo {
 #define TCALLATTRSOFF  21
 #define TCSTANDOUTEND  22
 #define TCUNDERLINEEND 23
-#define TC_COUNT       24
+#define TCHORIZPOS     24
+#define TC_COUNT       25
 
 #define tccan(X) (tclen[X])
 
@@ -1178,14 +1535,20 @@ struct ttyinfo {
 /* Definitions for the %_ prompt escape */
 /****************************************/
 
-#define cmdpush(X) if (!(cmdsp >= 0 && cmdsp < 256)) {;} else cmdstack[cmdsp++]=(X)
+#define CMDSTACKSZ 256
+#define cmdpush(X) do { \
+                       if (cmdsp >= 0 && cmdsp < CMDSTACKSZ) \
+                           cmdstack[cmdsp++]=(X); \
+                   } while (0)
 #ifdef DEBUG
-# define cmdpop()  if (cmdsp <= 0) { \
-			fputs("BUG: cmdstack empty\n", stderr); \
-			fflush(stderr); \
-		   } else cmdsp--
+# define cmdpop()  do { \
+                       if (cmdsp <= 0) { \
+			   fputs("BUG: cmdstack empty\n", stderr); \
+			   fflush(stderr); \
+		       } else cmdsp--; \
+                   } while (0)
 #else
-# define cmdpop()   if (cmdsp <= 0) {;} else cmdsp--
+# define cmdpop()   do { if (cmdsp > 0) cmdsp--; } while (0)
 #endif
 
 #define CS_FOR          0
@@ -1224,35 +1587,41 @@ struct ttyinfo {
  * Memory management *
  *********************/
 
-#ifndef DEBUG
-# define HEAPALLOC	do { int nonlocal_useheap = global_heapalloc(); do
+/* heappush saves the current heap state using this structure */
 
-# define PERMALLOC	do { int nonlocal_useheap = global_permalloc(); do
+struct heapstack {
+    struct heapstack *next;	/* next one in list for this heap */
+    size_t used;
+};
 
-# define LASTALLOC	while (0); \
-			if (nonlocal_useheap) global_heapalloc(); \
-			else global_permalloc(); \
-		} while(0)
+/* A zsh heap. */
 
-# define LASTALLOC_RETURN \
-			if ((nonlocal_useheap ? global_heapalloc() : \
-			     global_permalloc()), 0) {;} else return
-#else
-# define HEAPALLOC	do { int nonlocal_useheap = global_heapalloc(); \
-			alloc_stackp++; do
+struct heap {
+    struct heap *next;		/* next one                                  */
+    size_t size;		/* size of heap                              */
+    size_t used;		/* bytes used from the heap                  */
+    struct heapstack *sp;	/* used by pushheap() to save the value used */
 
-# define PERMALLOC	do { int nonlocal_useheap = global_permalloc(); \
-			alloc_stackp++; do
+/* Uncomment the following if the struct needs padding to 64-bit size. */
+/* Make sure sizeof(heap) is a multiple of 8 
+#if defined(PAD_64_BIT) && !defined(__GNUC__)
+    size_t dummy;		
+#endif
+*/
+#define arena(X)	((char *) (X) + sizeof(struct heap))
+}
+#if defined(PAD_64_BIT) && defined(__GNUC__)
+  __attribute__ ((aligned (8)))
+#endif
+;
 
-# define LASTALLOC	while (0); alloc_stackp--; \
-			if (nonlocal_useheap) global_heapalloc(); \
-			else global_permalloc(); \
-		} while(0)
+# define LASTALLOC_RETURN return
 
-# define LASTALLOC_RETURN \
-			if ((nonlocal_useheap ? global_heapalloc() : \
-			    global_permalloc()),alloc_stackp--,0){;}else return
-#endif
+# define NEWHEAPS(h)    do { Heap _switch_oldheaps = h = new_heaps(); do
+# define OLDHEAPS       while (0); old_heaps(_switch_oldheaps); } while (0);
+
+# define SWITCHHEAPS(h)  do { Heap _switch_oldheaps = switch_heaps(h); do
+# define SWITCHBACKHEAPS while (0); switch_heaps(_switch_oldheaps); } while (0);
 
 /****************/
 /* Debug macros */
@@ -1260,12 +1629,8 @@ struct ttyinfo {
 
 #ifdef DEBUG
 # define DPUTS(X,Y) if (!(X)) {;} else dputs(Y)
-# define MUSTUSEHEAP(X) if (useheap) {;} else \
-		fprintf(stderr, "BUG: permanent allocation in %s\n", X), \
-		fflush(stderr)
 #else
 # define DPUTS(X,Y)
-# define MUSTUSEHEAP(X)
 #endif
 
 /**************************/
@@ -1274,9 +1639,20 @@ struct ttyinfo {
 
 /* These used in the sigtrapped[] array */
 
-#define ZSIG_TRAPPED	(1<<0)
-#define ZSIG_IGNORED	(1<<1)
-#define ZSIG_FUNC	(1<<2)
+#define ZSIG_TRAPPED	(1<<0)	/* Signal is trapped */
+#define ZSIG_IGNORED	(1<<1)	/* Signal is ignored */
+#define ZSIG_FUNC	(1<<2)	/* Trap is a function, not an eval list */
+/* Mask to get the above flags */
+#define ZSIG_MASK	(ZSIG_TRAPPED|ZSIG_IGNORED|ZSIG_FUNC)
+/* No. of bits to shift local level when storing in sigtrapped */
+#define ZSIG_SHIFT	3
+
+/**********************************/
+/* Flags to third argument of zle */
+/**********************************/
+
+#define ZLRF_HISTORY	0x01	/* OK to access the history list */
+#define ZLRF_NOSETTY	0x02	/* Don't set tty before return */
 
 /****************/
 /* Entry points */
@@ -1291,3 +1667,17 @@ typedef int (*CompctlReadFn) _((char *, char **, char *, char *));
 typedef void (*ZleVoidFn) _((void));
 typedef void (*ZleVoidIntFn) _((int));
 typedef unsigned char * (*ZleReadFn) _((char *, char *, int));
+
+/***************************************/
+/* Pseudo-keyword to mark exportedness */
+/***************************************/
+
+#define mod_export
+
+/***************************************/
+/* Hooks in core.                      */
+/***************************************/
+
+#define EXITHOOK       (zshhooks + 0)
+#define BEFORETRAPHOOK (zshhooks + 1)
+#define AFTERTRAPHOOK  (zshhooks + 2)
diff --git a/Test/07cond.ztst b/Test/07cond.ztst
index 7fff51ce2..1d1ba6679 100644
--- a/Test/07cond.ztst
+++ b/Test/07cond.ztst
@@ -11,10 +11,11 @@
   touch	unmodified
 
   touch zerolength
+  chgrp $EGID zerolength
   print 'Garbuglio' >nonzerolength
 
-  touch modish
-  chmod g+s modish
+  mkdir modish
+  chmod g+xs modish
   chmod u+s modish
   chmod +t modish
 
@@ -26,7 +27,11 @@
 0:-a cond
 
   # Find a block special file system.  This is a little tricky.
-  block=$(df / | tail -1 | awk '{ print $1 }') &&
+  block=$(df / | awk '
+    $NF == "/" {print $1}
+    $1 == "/" && substr($2,0,1) == "(" {
+      if((l = index($2,")") - 2) < 0) l = length($2) - 1;
+      print substr($2,2,l)}') &&
   [[ -b $block && ! -b zerolength ]]
 0:-b cond
 
@@ -61,7 +66,11 @@
   [[ -o rcs && ! -o norcs && -o noerrexit && ! -o errexit ]]
 0:-o cond
 
-  mknod pipe p
+  if whence mkfifo >/dev/null; then
+    mkfifo pipe
+  else
+    mknod pipe p
+  fi
   [[ -p pipe && ! -p zerolength ]]
 0:-p cond
 
@@ -76,7 +85,7 @@
   [[ -u modish && ! -u zerolength ]]
 0:-u cond
 
-  [[ -x $ZTST_testdir/ztst.zsh && ! -x zerolength ]]
+  [[ -x $ZTST_srcdir/ztst.zsh && ! -x zerolength ]]
 0:-x cond
 
   [[ -z $bar && -z '' && ! -z $foo ]]
@@ -89,8 +98,6 @@
   [[ -O zerolength ]]
 0:-O cond
 
-# there may be strange cases where this doesn't work, e.g.
-# inherited funny groups for directories via setgid.
   [[ -G zerolength ]]
 0:-G cond
 
@@ -132,4 +139,16 @@
 0:|| and && in conds
 
   [[ -e /dev/fd/0 ]]
-0:/dev/fd support in conds
+0:/dev/fd support in conds handled by access
+
+  [[ -O /dev/fd/0 ]]
+0:/dev/fd support in conds handled by stat
+
+  [[ ( -z foo && -z foo ) || -z foo ]]
+1:complex conds with skipping
+
+  [ '' != bar -a '' = '' ]
+0:strings with `[' builtin
+
+  [ `echo 0` -lt `echo 1` ]
+0:substituion in `[' builtin
diff --git a/Test/11glob.ztst b/Test/11glob.ztst
index 17c33eb63..f5819595b 100644
--- a/Test/11glob.ztst
+++ b/Test/11glob.ztst
@@ -1,8 +1,22 @@
 # Tests for globbing
 
 %prep
+  mkdir glob.tmp
+  mkdir glob.tmp/dir1
+  mkdir glob.tmp/dir2
+  : >glob.tmp/{,{dir1,dir2}/}{a,b,c}
+
   globtest () { $ZTST_testdir/../Src/zsh -f $ZTST_srcdir/../Misc/$1 }
 
+  regress_absolute_path_and_core_dump() {
+    local absolute_dir=$(cd glob.tmp && pwd -P)
+    [[ -n $absolute_dir ]] || return 1
+    setopt localoptions extendedglob nullglob
+    print $absolute_dir/**/*~/*
+    setopt nonullglob nomatch
+    print glob.tmp/**/*~(.)#
+  }
+
 %test
 
   globtest globtests
@@ -234,3 +248,8 @@
 >0:  [[ FOO = @(bar|(#i)foo) ]]
 >0:  [[ Modules = (#i)*m* ]]
 >0 tests failed.
+
+  ( regress_absolute_path_and_core_dump )
+0:exclusions regression test
+>
+>glob.tmp/a glob.tmp/b glob.tmp/c glob.tmp/dir1 glob.tmp/dir1/a glob.tmp/dir1/b glob.tmp/dir1/c glob.tmp/dir2 glob.tmp/dir2/a glob.tmp/dir2/b glob.tmp/dir2/c
diff --git a/Test/53completion.ztst b/Test/53completion.ztst
index 00f1c9218..2e33412d8 100644
--- a/Test/53completion.ztst
+++ b/Test/53completion.ztst
@@ -1,7 +1,10 @@
 # Tests for completion system.
 
 %prep
+  zmodload -i zsh/zpty
 
+  TERM=vt100
+  export ZTST_testdir ZTST_srcdir TERM
   comptest () { $ZTST_testdir/../Src/zsh -f $ZTST_srcdir/comptest -z $ZTST_testdir/../Src/zsh -d $ZTST_testdir/compdump.tmp "$@" }
 
   mkdir comp.tmp
@@ -104,11 +107,11 @@
 >DESCRIPTION:{desc1}
 >NO:{arg1}
 
-# code='compdef _tst tst; _tst () { _arguments "-\+[opt]" }'
-# comptest -c "$code" $'tst -\C-D'
-#0:_arguments
-#>DESCRIPTION:{option}
-#>NO:{-+ -- opt}
+ code='compdef _tst tst; _tst () { _arguments "-\+[opt]" }'
+ comptest -c "$code" $'tst -\C-D'
+0:_arguments
+>DESCRIPTION:{option}
+>NO:{-+ -- opt}
 
  code='compdef _tst tst; _tst () { _arguments "1:desc1:(arg1)" }'
  comptest -c "$code" $'tst \t'