summary refs log tree commit diff
path: root/Src
diff options
context:
space:
mode:
Diffstat (limited to 'Src')
-rw-r--r--Src/.cvsignore25
-rw-r--r--Src/.distfiles11
-rw-r--r--Src/.exrc2
-rw-r--r--Src/.lastloc5
-rw-r--r--Src/Builtins/.cvsignore11
-rw-r--r--Src/Builtins/.distfiles5
-rw-r--r--Src/Builtins/.exrc2
-rw-r--r--Src/Builtins/rlimits.awk72
-rw-r--r--Src/Builtins/rlimits.c593
-rw-r--r--Src/Builtins/rlimits.mdd18
-rw-r--r--Src/Builtins/sched.c214
-rw-r--r--Src/Builtins/sched.mdd3
-rw-r--r--Src/Makefile.in195
-rw-r--r--Src/Makemod.in.in180
-rw-r--r--Src/Modules/.cvsignore10
-rw-r--r--Src/Modules/.distfiles8
-rw-r--r--Src/Modules/.exrc2
-rw-r--r--Src/Modules/cap.c141
-rw-r--r--Src/Modules/cap.mdd3
-rw-r--r--Src/Modules/clone.c115
-rw-r--r--Src/Modules/clone.mdd3
-rw-r--r--Src/Modules/example.c76
-rw-r--r--Src/Modules/example.mdd3
-rw-r--r--Src/Modules/files.c528
-rw-r--r--Src/Modules/files.mdd3
-rw-r--r--Src/Modules/stat.c535
-rw-r--r--Src/Modules/stat.mdd3
-rw-r--r--Src/Modules/zftp.c2596
-rw-r--r--Src/Modules/zftp.mdd3
-rw-r--r--Src/Zle/.cvsignore14
-rw-r--r--Src/Zle/.distfiles10
-rw-r--r--Src/Zle/.exrc2
-rw-r--r--Src/Zle/.lastloc10
-rw-r--r--Src/Zle/comp.h137
-rw-r--r--Src/Zle/comp1.c291
-rw-r--r--Src/Zle/comp1.export22
-rw-r--r--Src/Zle/comp1.mdd3
-rw-r--r--Src/Zle/compctl.c1085
-rw-r--r--Src/Zle/compctl.mdd5
-rw-r--r--Src/Zle/deltochar.c91
-rw-r--r--Src/Zle/deltochar.mdd3
-rw-r--r--Src/Zle/iwidgets.list172
-rw-r--r--Src/Zle/zle.export10
-rw-r--r--Src/Zle/zle.h137
-rw-r--r--Src/Zle/zle.mdd70
-rw-r--r--Src/Zle/zle_bindings.c421
-rw-r--r--Src/Zle/zle_hist.c1139
-rw-r--r--Src/Zle/zle_keymap.c1238
-rw-r--r--Src/Zle/zle_main.c907
-rw-r--r--Src/Zle/zle_misc.c816
-rw-r--r--Src/Zle/zle_move.c502
-rw-r--r--Src/Zle/zle_params.c196
-rw-r--r--Src/Zle/zle_refresh.c1116
-rw-r--r--Src/Zle/zle_things.sed9
-rw-r--r--Src/Zle/zle_thingy.c491
-rw-r--r--Src/Zle/zle_tricky.c4015
-rw-r--r--Src/Zle/zle_utils.c650
-rw-r--r--Src/Zle/zle_vi.c925
-rw-r--r--Src/Zle/zle_widget.sed7
-rw-r--r--Src/Zle/zle_word.c477
-rw-r--r--Src/ansi2knr.c413
-rw-r--r--Src/builtin.c3599
-rw-r--r--Src/compat.c285
-rw-r--r--Src/cond.c226
-rw-r--r--Src/exec.c2965
-rw-r--r--Src/glob.c2800
-rw-r--r--Src/hashtable.c1285
-rw-r--r--Src/hashtable.h62
-rw-r--r--Src/hist.c1670
-rw-r--r--Src/init.c936
-rw-r--r--Src/input.c530
-rw-r--r--Src/jobs.c1361
-rw-r--r--Src/lex.c1489
-rw-r--r--Src/linklist.c222
-rw-r--r--Src/loop.c421
-rw-r--r--Src/main.c99
-rw-r--r--Src/makepro.awk146
-rw-r--r--Src/math.c883
-rw-r--r--Src/mem.c1254
-rw-r--r--Src/mkbltnmlst.sh60
-rw-r--r--Src/mkmakemod.sh315
-rw-r--r--Src/mkmodindex.sh43
-rw-r--r--Src/modentry.c15
-rw-r--r--Src/module.c651
-rw-r--r--Src/options.c663
-rw-r--r--Src/params.c2191
-rw-r--r--Src/parse.c1379
-rw-r--r--Src/prompt.c766
-rw-r--r--Src/prototypes.h120
-rw-r--r--Src/signals.c748
-rw-r--r--Src/signals.h94
-rwxr-xr-xSrc/signames.awk98
-rw-r--r--Src/signames1.awk19
-rw-r--r--Src/signames2.awk100
-rw-r--r--Src/subst.c1773
-rw-r--r--Src/system.h598
-rw-r--r--Src/text.c526
-rw-r--r--Src/utils.c3726
-rw-r--r--Src/watch.c586
-rw-r--r--Src/xmods.conf5
-rw-r--r--Src/zsh.export235
-rw-r--r--Src/zsh.h1293
-rw-r--r--Src/zsh.mdd71
-rw-r--r--Src/ztype.h58
104 files changed, 57115 insertions, 0 deletions
diff --git a/Src/.cvsignore b/Src/.cvsignore
new file mode 100644
index 000000000..edec5401b
--- /dev/null
+++ b/Src/.cvsignore
@@ -0,0 +1,25 @@
+Makefile
+Makemod.in Makemod
+*.pro
+*.o
+*.o.c
+*.so
+*.mdh
+*.mdhi
+*.mdhs
+*.mdh.tmp
+modules.index
+modules.index.tmp
+modules.stamp
+modules-bltin
+stamp-modobjs
+stamp-modobjs.tmp
+ansi2knr
+zsh
+libzsh.so*
+sigcount.h
+signames.c
+zshpaths.h
+zshxmods.h
+bltinmods.list
+tags TAGS
diff --git a/Src/.distfiles b/Src/.distfiles
new file mode 100644
index 000000000..727c855cc
--- /dev/null
+++ b/Src/.distfiles
@@ -0,0 +1,11 @@
+DISTFILES_SRC='
+    .cvsignore .distfiles .exrc .indent.pro
+    Makefile.in Makemod.in.in
+    ansi2knr.c
+    builtin.c compat.c cond.c exec.c glob.c hashtable.c hashtable.h
+    hist.c init.c input.c jobs.c lex.c linklist.c loop.c main.c makepro.awk
+    math.c mem.c mkbltnmlst.sh mkmakemod.sh mkmodindex.sh
+    module.c options.c params.c parse.c prompt.c prototypes.h
+    signals.c signals.h signames.awk subst.c system.h text.c utils.c
+    watch.c xmods.conf zsh.h zsh.mdd ztype.h
+'
diff --git a/Src/.exrc b/Src/.exrc
new file mode 100644
index 000000000..91d0b39ef
--- /dev/null
+++ b/Src/.exrc
@@ -0,0 +1,2 @@
+set ai
+set sw=4
diff --git a/Src/.lastloc b/Src/.lastloc
new file mode 100644
index 000000000..b3c6cf0f3
--- /dev/null
+++ b/Src/.lastloc
@@ -0,0 +1,5 @@
+(("/home/user2/pws/src/zsh-3.1.5/Src/glob.c" . 15475)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/zsh.export" . 794)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/utils.c" . 10946)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/Makefile" . 4282)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/mkmakemod.sh" . 6832))
diff --git a/Src/Builtins/.cvsignore b/Src/Builtins/.cvsignore
new file mode 100644
index 000000000..ff73d86c4
--- /dev/null
+++ b/Src/Builtins/.cvsignore
@@ -0,0 +1,11 @@
+Makefile
+Makefile.in
+*.pro
+*.o
+*.o.c
+*.so
+*.mdh
+*.mdhi
+*.mdhs
+*.mdh.tmp
+rlimits.h
diff --git a/Src/Builtins/.distfiles b/Src/Builtins/.distfiles
new file mode 100644
index 000000000..cd36388ef
--- /dev/null
+++ b/Src/Builtins/.distfiles
@@ -0,0 +1,5 @@
+DISTFILES_SRC='
+    .cvsignore .distfiles .exrc
+    rlimits.mdd rlimits.c rlimits.awk
+    sched.mdd sched.c
+'
diff --git a/Src/Builtins/.exrc b/Src/Builtins/.exrc
new file mode 100644
index 000000000..91d0b39ef
--- /dev/null
+++ b/Src/Builtins/.exrc
@@ -0,0 +1,2 @@
+set ai
+set sw=4
diff --git a/Src/Builtins/rlimits.awk b/Src/Builtins/rlimits.awk
new file mode 100644
index 000000000..e2500582a
--- /dev/null
+++ b/Src/Builtins/rlimits.awk
@@ -0,0 +1,72 @@
+#
+# rlimits.awk: {g,n}awk script to generate rlimits.h
+#
+# NB: On SunOS 4.1.3 - user-functions don't work properly, also \" problems
+# Without 0 + hacks some nawks compare numbers as strings
+#
+BEGIN {limidx = 0}
+
+/^[\t ]*(#[\t ]*define[\t _]*RLIMIT_[A-Z]*[\t ]*[0-9][0-9]*|RLIMIT_[A-Z]*,[\t ]*)/ {
+    limindex = index($0, "RLIMIT_")
+    limtail = substr($0, limindex, 80)
+    split(limtail, tmp)
+    limnam = substr(tmp[1], 8, 20)
+    limnum = tmp[2]
+    # in this case I assume GNU libc resourcebits.h
+    if (limnum == "") {
+	limnum = limidx++
+	limindex = index($0, ",")
+	limnam = substr(limnam, 1, limindex-1)
+    }
+    limrev[limnam] = limnum
+    if (lim[limnum] == "") {
+	lim[limnum] = limnam
+	if (limnum ~ /^[0-9]*$/) {
+	    if (limnam == "MEMLOCK") { msg[limnum] = "memorylocked" }
+	    if (limnam == "RSS")     { msg[limnum] = "resident" }
+	    if (limnam == "VMEM")    { msg[limnum] = "vmemorysize" }
+	    if (limnam == "NOFILE")  { msg[limnum] = "descriptors" }
+	    if (limnam == "OFILE")   { msg[limnum] = "descriptors" }
+	    if (limnam == "CORE")    { msg[limnum] = "coredumpsize" }
+	    if (limnam == "STACK")   { msg[limnum] = "stacksize" }
+	    if (limnam == "DATA")    { msg[limnum] = "datasize" }
+	    if (limnam == "FSIZE")   { msg[limnum] = "filesize" }
+	    if (limnam == "CPU")     { msg[limnum] = "cputime" }
+	    if (limnam == "NPROC")   { msg[limnum] = "maxproc" }
+	    if (limnam == "AS")      { msg[limnum] = "addressspace" }
+	    if (limnam == "TCACHE")  { msg[limnum] = "cachedthreads" }
+        }
+    }
+}
+/^[\t ]*#[\t ]*define[\t _]*RLIM_NLIMITS[\t ]*[0-9][0-9]*/ {
+    limindex = index($0, "RLIM_")
+    limtail = substr($0, limindex, 80)
+    split(limtail, tmp)
+    nlimits = tmp[2]
+}
+# in case of GNU libc
+/^[\t ]*RLIM_NLIMITS[\t ]*=[\t ]*RLIMIT_NLIMITS/ {
+    nlimits = limidx
+}
+
+END {
+    if (limrev["MEMLOCK"] != "") {
+        irss = limrev["RSS"]
+        msg[irss] = "memoryuse"
+    }
+    ps = "%s"
+
+    printf("%s\n%s\n\n", "/** rlimits.h                              **/", "/** architecture-customized limits for zsh **/")
+    printf("#define ZSH_NLIMITS %d\n\nstatic char *recs[ZSH_NLIMITS+1] = {\n", 0 + nlimits)
+
+    for (i = 0; i < 0 + nlimits; i++)
+	if (msg[i] == "") {
+            badlimit++
+            printf("\t%c%s%c,\n", 34, lim[i], 34)
+	} else
+	    printf("\t%c%s%c,\n", 34, msg[i], 34)
+    print "\tNULL"
+    print "};"
+    print ""
+    exit(badlimit)
+}
diff --git a/Src/Builtins/rlimits.c b/Src/Builtins/rlimits.c
new file mode 100644
index 000000000..20b8d663d
--- /dev/null
+++ b/Src/Builtins/rlimits.c
@@ -0,0 +1,593 @@
+/*
+ * rlimits.c - resource limit builtins
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "rlimits.mdh"
+#include "rlimits.pro"
+
+#if defined(HAVE_GETRLIMIT) && defined(RLIM_INFINITY)
+
+/* Generated rec array containing limits required for the limit builtin.     *
+ * They must appear in this array in numerical order of the RLIMIT_* macros. */
+
+# include "rlimits.h"
+
+# if defined(RLIM_T_IS_QUAD_T) || defined(RLIM_T_IS_UNSIGNED)
+static rlim_t
+zstrtorlimt(const char *s, char **t, int base)
+{
+    rlim_t ret = 0;
+ 
+    if (!base)
+	if (*s != '0')
+	    base = 10;
+	else if (*++s == 'x' || *s == 'X')
+	    base = 16, s++;
+	else
+	    base = 8;
+ 
+    if (base <= 10)
+	for (; *s >= '0' && *s < ('0' + base); s++)
+	    ret = ret * base + *s - '0';
+    else
+	for (; idigit(*s) || (*s >= 'a' && *s < ('a' + base - 10))
+	     || (*s >= 'A' && *s < ('A' + base - 10)); s++)
+	    ret = ret * base + (idigit(*s) ? (*s - '0') : (*s & 0x1f) + 9);
+    if (t)
+	*t = (char *)s;
+    return ret;
+}
+# else /* !RLIM_T_IS_QUAD_T && !RLIM_T_IS_UNSIGNED */
+#  define zstrtorlimt(a, b, c)	zstrtol((a), (b), (c))
+# endif /* !RLIM_T_IS_QUAD_T && !RLIM_T_IS_UNSIGNED */
+
+/* Display resource limits.  hard indicates whether `hard' or `soft'  *
+ * limits should be displayed.  lim specifies the limit, or may be -1 *
+ * to show all.                                                       */
+
+/**/
+static void
+showlimits(int hard, int lim)
+{
+    int rt;
+    rlim_t val;
+
+    /* main loop over resource types */
+    for (rt = 0; rt != ZSH_NLIMITS; rt++)
+	if (rt == lim || lim == -1) {
+	    /* display limit for resource number rt */
+	    printf("%-16s", recs[rt]);
+	    val = (hard) ? limits[rt].rlim_max : limits[rt].rlim_cur;
+	    if (val == RLIM_INFINITY)
+		printf("unlimited\n");
+	    else if (rt==RLIMIT_CPU)
+		/* time-type resource -- display as hours, minutes and
+		seconds. */
+		printf("%d:%02d:%02d\n", (int)(val / 3600),
+		       (int)(val / 60) % 60, (int)(val % 60));
+# ifdef RLIMIT_NPROC
+	    else if (rt == RLIMIT_NPROC)
+		/* pure numeric resource */
+		printf("%d\n", (int)val);
+# endif /* RLIMIT_NPROC */
+# ifdef RLIMIT_NOFILE
+	    else if (rt == RLIMIT_NOFILE)
+		/* pure numeric resource */
+		printf("%d\n", (int)val);
+# endif /* RLIMIT_NOFILE */
+	    else if (val >= 1024L * 1024L)
+		/* memory resource -- display with `K' or `M' modifier */
+# ifdef RLIM_T_IS_QUAD_T
+		printf("%qdMB\n", val / (1024L * 1024L));
+	    else
+		printf("%qdkB\n", val / 1024L);
+# else
+		printf("%ldMB\n", val / (1024L * 1024L));
+            else
+		printf("%ldkB\n", val / 1024L);
+# endif /* RLIM_T_IS_QUAD_T */
+	}
+}
+
+/* Display a resource limit, in ulimit style.  lim specifies which   *
+ * limit should be displayed, and hard indicates whether the hard or *
+ * soft limit should be displayed.                                   */
+
+/**/
+static void
+printulimit(int lim, int hard, int head)
+{
+    rlim_t limit;
+
+    /* get the limit in question */
+    limit = (hard) ? limits[lim].rlim_max : limits[lim].rlim_cur;
+    /* display the appropriate heading */
+    switch (lim) {
+    case RLIMIT_CPU:
+	if (head)
+	    printf("cpu time (seconds)         ");
+	break;
+    case RLIMIT_FSIZE:
+	if (head)
+	    printf("file size (blocks)         ");
+	if (limit != RLIM_INFINITY)
+	    limit /= 512;
+	break;
+    case RLIMIT_DATA:
+	if (head)
+	    printf("data seg size (kbytes)     ");
+	if (limit != RLIM_INFINITY)
+	    limit /= 1024;
+	break;
+    case RLIMIT_STACK:
+	if (head)
+	    printf("stack size (kbytes)        ");
+	if (limit != RLIM_INFINITY)
+	    limit /= 1024;
+	break;
+    case RLIMIT_CORE:
+	if (head)
+	    printf("core file size (blocks)    ");
+	if (limit != RLIM_INFINITY)
+	    limit /= 512;
+	break;
+# ifdef RLIMIT_RSS
+    case RLIMIT_RSS:
+	if (head)
+	    printf("resident set size (kbytes) ");
+	if (limit != RLIM_INFINITY)
+	    limit /= 1024;
+	break;
+# endif /* RLIMIT_RSS */
+# ifdef RLIMIT_MEMLOCK
+    case RLIMIT_MEMLOCK:
+	if (head)
+	    printf("locked-in-memory size (kb) ");
+	if (limit != RLIM_INFINITY)
+	    limit /= 1024;
+	break;
+# endif /* RLIMIT_MEMLOCK */
+# ifdef RLIMIT_NPROC
+    case RLIMIT_NPROC:
+	if (head)
+	    printf("processes                  ");
+	break;
+# endif /* RLIMIT_NPROC */
+# ifdef RLIMIT_NOFILE
+    case RLIMIT_NOFILE:
+	if (head)
+	    printf("file descriptors           ");
+	break;
+# endif /* RLIMIT_NOFILE */
+# ifdef RLIMIT_VMEM
+    case RLIMIT_VMEM:
+	if (head)
+	    printf("virtual memory size (kb)   ");
+	if (limit != RLIM_INFINITY)
+	    limit /= 1024;
+	break;
+# endif /* RLIMIT_VMEM */
+# if defined RLIMIT_AS && RLIMIT_AS != RLIMIT_VMEM
+    case RLIMIT_AS:
+	if (head)
+	    printf("address space (kb)         ");
+	if (limit != RLIM_INFINITY)
+	    limit /= 1024;
+	break;
+# endif /* RLIMIT_AS */
+# ifdef RLIMIT_TCACHE
+    case RLIMIT_TCACHE:
+	if (head)
+	    printf("cached threads             ");
+	break;
+# endif /* RLIMIT_TCACHE */
+    }
+    /* display the limit */
+    if (limit == RLIM_INFINITY)
+	printf("unlimited\n");
+    else
+	printf("%ld\n", (long)limit);
+}
+
+/* limit: set or show resource limits.  The variable hard indicates *
+ * whether `hard' or `soft' resource limits are being set/shown.    */
+
+/**/
+static int
+bin_limit(char *nam, char **argv, char *ops, int func)
+{
+    char *s;
+    int hard, limnum, lim;
+    rlim_t val;
+    int ret = 0;
+
+    hard = ops['h'];
+    if (ops['s'] && !*argv)
+	return setlimits(NULL);
+    /* without arguments, display limits */
+    if (!*argv) {
+	showlimits(hard, -1);
+	return 0;
+    }
+    while ((s = *argv++)) {
+	/* Search for the appropriate resource name.  When a name matches (i.e. *
+	 * starts with) the argument, the lim variable changes from -1 to the   *
+	 * number of the resource.  If another match is found, lim goes to -2.  */
+	for (lim = -1, limnum = 0; limnum < ZSH_NLIMITS; limnum++)
+	    if (!strncmp(recs[limnum], s, strlen(s))) {
+		if (lim != -1)
+		    lim = -2;
+		else
+		    lim = limnum;
+	    }
+	/* lim==-1 indicates that no matches were found.       *
+	 * lim==-2 indicates that multiple matches were found. */
+	if (lim < 0) {
+	    zwarnnam("limit",
+		     (lim == -2) ? "ambiguous resource specification: %s"
+		     : "no such resource: %s", s, 0);
+	    return 1;
+	}
+	/* without value for limit, display the current limit */
+	if (!(s = *argv++)) {
+	    showlimits(hard, lim);
+	    return 0;
+	}
+	if (lim==RLIMIT_CPU) {
+	    /* time-type resource -- may be specified as seconds, or minutes or *
+	     * hours with the `m' and `h' modifiers, and `:' may be used to add *
+	     * together more than one of these.  It's easier to understand from *
+	     * the code:                                                        */
+	    val = zstrtorlimt(s, &s, 10);
+	    if (*s)
+		if ((*s == 'h' || *s == 'H') && !s[1])
+		    val *= 3600L;
+		else if ((*s == 'm' || *s == 'M') && !s[1])
+		    val *= 60L;
+		else if (*s == ':')
+		    val = val * 60 + zstrtorlimt(s + 1, &s, 10);
+		else {
+		    zwarnnam("limit", "unknown scaling factor: %s", s, 0);
+		    return 1;
+		}
+	}
+# ifdef RLIMIT_NPROC
+	else if (lim == RLIMIT_NPROC)
+	    /* pure numeric resource -- only a straight decimal number is
+	    permitted. */
+	    val = zstrtorlimt(s, &s, 10);
+# endif /* RLIMIT_NPROC */
+# ifdef RLIMIT_NOFILE
+	else if (lim == RLIMIT_NOFILE)
+	    /* pure numeric resource -- only a straight decimal number is
+	    permitted. */
+	    val = zstrtorlimt(s, &s, 10);
+# endif /* RLIMIT_NOFILE */
+	else {
+	    /* memory-type resource -- `k' and `M' modifiers are permitted,
+	    meaning (respectively) 2^10 and 2^20. */
+	    val = zstrtorlimt(s, &s, 10);
+	    if (!*s || ((*s == 'k' || *s == 'K') && !s[1]))
+		val *= 1024L;
+	    else if ((*s == 'M' || *s == 'm') && !s[1])
+		val *= 1024L * 1024;
+	    else {
+		zwarnnam("limit", "unknown scaling factor: %s", s, 0);
+		return 1;
+	    }
+	}
+	/* new limit is valid and has been interpreted; apply it to the
+	specified resource */
+	if (hard) {
+	    /* can only raise hard limits if running as root */
+	    if (val > current_limits[lim].rlim_max && geteuid()) {
+		zwarnnam("limit", "can't raise hard limits", NULL, 0);
+		return 1;
+	    } else {
+		limits[lim].rlim_max = val;
+		if (val < limits[lim].rlim_cur)
+		    limits[lim].rlim_cur = val;
+	    }
+	} else if (val > limits[lim].rlim_max) {
+	    zwarnnam("limit", "limit exceeds hard limit", NULL, 0);
+	    return 1;
+	} else
+	    limits[lim].rlim_cur = val;
+	if (ops['s'] && zsetlimit(lim, "limit"))
+	    ret++;
+    }
+    return ret;
+}
+
+/* unlimit: remove resource limits.  Much of this code is the same as *
+ * that in bin_limit().                                               */
+
+/**/
+static int
+bin_unlimit(char *nam, char **argv, char *ops, int func)
+{
+    int hard, limnum, lim;
+    int ret = 0;
+    uid_t euid = geteuid();
+
+    hard = ops['h'];
+    /* Without arguments, remove all limits. */
+    if (!*argv) {
+	for (limnum = 0; limnum != RLIM_NLIMITS; limnum++) {
+	    if (hard)
+		if (euid && current_limits[limnum].rlim_max != RLIM_INFINITY)
+		    ret++;
+		else
+		    limits[limnum].rlim_max = RLIM_INFINITY;
+	    else
+		limits[limnum].rlim_cur = limits[limnum].rlim_max;
+	}
+	if (ops['s'])
+	    ret += setlimits(nam);
+	if (ret)
+	    zwarnnam(nam, "can't remove hard limits", NULL, 0);
+    } else {
+	for (; *argv; argv++) {
+	    /* Search for the appropriate resource name.  When a name     *
+	     * matches (i.e. starts with) the argument, the lim variable  *
+	     * changes from -1 to the number of the resource.  If another *
+	     * match is found, lim goes to -2.                            */
+	    for (lim = -1, limnum = 0; limnum < ZSH_NLIMITS; limnum++)
+		if (!strncmp(recs[limnum], *argv, strlen(*argv))) {
+		    if (lim != -1)
+			lim = -2;
+		    else
+			lim = limnum;
+		}
+	    /* lim==-1 indicates that no matches were found.       *
+	     * lim==-2 indicates that multiple matches were found. */
+	    if (lim < 0) {
+		zwarnnam(nam,
+			 (lim == -2) ? "ambiguous resource specification: %s"
+			 : "no such resource: %s", *argv, 0);
+		return 1;
+	    }
+	    /* remove specified limit */
+	    if (hard)
+		if (euid && current_limits[lim].rlim_max != RLIM_INFINITY) {
+		    zwarnnam(nam, "can't remove hard limits", NULL, 0);
+		    ret++;
+		} else
+		    limits[lim].rlim_max = RLIM_INFINITY;
+	    else
+		limits[lim].rlim_cur = limits[lim].rlim_max;
+	    if (ops['s'] && zsetlimit(lim, nam))
+		ret++;
+	}
+    }
+    return ret;
+}
+
+/* ulimit: set or display resource limits */
+
+/**/
+static int
+bin_ulimit(char *name, char **argv, char *ops, int func)
+{
+    int res, resmask = 0, hard = 0, soft = 0, nres = 0;
+    char *options;
+
+    do {
+	options = *argv;
+	if (options && *options == '-' && !options[1]) {
+	    zwarnnam(name, "missing option letter", NULL, 0);
+	    return 1;
+	}
+	res = -1;
+	if (options && *options == '-') {
+	    argv++;
+	    while (*++options) {
+		if(*options == Meta)
+		    *++options ^= 32;
+		res = -1;
+		switch (*options) {
+		case 'H':
+		    hard = 1;
+		    continue;
+		case 'S':
+		    soft = 1;
+		    continue;
+		case 'a':
+		    if (*argv || options[1] || resmask) {
+			zwarnnam(name, "no arguments required after -a",
+				 NULL, 0);
+			return 1;
+		    }
+		    resmask = (1 << RLIM_NLIMITS) - 1;
+		    nres = RLIM_NLIMITS;
+		    continue;
+		case 't':
+		    res = RLIMIT_CPU;
+		    break;
+		case 'f':
+		    res = RLIMIT_FSIZE;
+		    break;
+		case 'd':
+		    res = RLIMIT_DATA;
+		    break;
+		case 's':
+		    res = RLIMIT_STACK;
+		    break;
+		case 'c':
+		    res = RLIMIT_CORE;
+		    break;
+# ifdef RLIMIT_RSS
+		case 'm':
+		    res = RLIMIT_RSS;
+		    break;
+# endif /* RLIMIT_RSS */
+# ifdef RLIMIT_MEMLOCK
+		case 'l':
+		    res = RLIMIT_MEMLOCK;
+		    break;
+# endif /* RLIMIT_MEMLOCK */
+# ifdef RLIMIT_NOFILE
+		case 'n':
+		    res = RLIMIT_NOFILE;
+		    break;
+# endif /* RLIMIT_NOFILE */
+# ifdef RLIMIT_NPROC
+		case 'u':
+		    res = RLIMIT_NPROC;
+		    break;
+# endif /* RLIMIT_NPROC */
+# ifdef RLIMIT_VMEM
+		case 'v':
+		    res = RLIMIT_VMEM;
+		    break;
+# endif /* RLIMIT_VMEM */
+		default:
+		    /* unrecognised limit */
+		    zwarnnam(name, "bad option: -%c", NULL, *options);
+		    return 1;
+		}
+		if (options[1]) {
+		    resmask |= 1 << res;
+		    nres++;
+		}
+	    }
+	}
+	if (!*argv || **argv == '-') {
+	    if (res < 0)
+		if (*argv || nres)
+		    continue;
+		else
+		    res = RLIMIT_FSIZE;
+	    resmask |= 1 << res;
+	    nres++;
+	    continue;
+	}
+	if (res < 0)
+	    res = RLIMIT_FSIZE;
+	if (strcmp(*argv, "unlimited")) {
+	    /* set limit to specified value */
+	    rlim_t limit;
+
+	    limit = zstrtorlimt(*argv, NULL, 10);
+	    /* scale appropriately */
+	    switch (res) {
+	    case RLIMIT_FSIZE:
+	    case RLIMIT_CORE:
+		limit *= 512;
+		break;
+	    case RLIMIT_DATA:
+	    case RLIMIT_STACK:
+# ifdef RLIMIT_RSS
+	    case RLIMIT_RSS:
+# endif /* RLIMIT_RSS */
+# ifdef RLIMIT_MEMLOCK
+	    case RLIMIT_MEMLOCK:
+# endif /* RLIMIT_MEMLOCK */
+# ifdef RLIMIT_VMEM
+	    case RLIMIT_VMEM:
+# endif /* RLIMIT_VMEM */
+		limit *= 1024;
+		break;
+	    }
+	    if (hard) {
+		/* can't raise hard limit unless running as root */
+		if (limit > current_limits[res].rlim_max && geteuid()) {
+		    zwarnnam(name, "can't raise hard limits", NULL, 0);
+		    return 1;
+		}
+		limits[res].rlim_max = limit;
+		if (limit < limits[res].rlim_cur)
+		    limits[res].rlim_cur = limit;
+	    }
+	    if (!hard || soft) {
+		/* can't raise soft limit above hard limit */
+		if (limit > limits[res].rlim_max) {
+		    if (limit > current_limits[res].rlim_max && geteuid()) {
+			zwarnnam(name, "value exceeds hard limit", NULL, 0);
+			return 1;
+		    }
+		    limits[res].rlim_max = limits[res].rlim_cur = limit;
+		} else
+		    limits[res].rlim_cur = limit;
+	    }
+	} else {
+	    /* remove specified limit */
+	    if (hard) {
+		/* can't remove hard limit unless running as root */
+		if (current_limits[res].rlim_max != RLIM_INFINITY && geteuid()) {
+		    zwarnnam(name, "can't remove hard limits", NULL, 0);
+		    return 1;
+		}
+		limits[res].rlim_max = RLIM_INFINITY;
+	    }
+	    if (!hard || soft)
+		/* `removal' of soft limit means setting it equal to the
+		   corresponding hard limit */
+		limits[res].rlim_cur = limits[res].rlim_max;
+	}
+	if (zsetlimit(res, name))
+	    return 1;
+	argv++;
+    } while (*argv);
+    for (res = 0; res < RLIM_NLIMITS; res++, resmask >>= 1)
+	if (resmask & 1)
+	    printulimit(res, hard, nres > 1);
+    return 0;
+}
+
+#else /* !HAVE_GETRLIMIT || !RLIM_INFINITY */
+
+# define bin_limit   bin_notavail
+# define bin_ulimit  bin_notavail
+# define bin_unlimit bin_notavail
+
+#endif /* !HAVE_GETRLIMIT || !RLIM_INFINITY */
+
+static struct builtin bintab[] = {
+    BUILTIN("limit",   0, bin_limit,   0, -1, 0, "sh", NULL),
+    BUILTIN("ulimit",  0, bin_ulimit,  0, -1, 0, NULL, NULL),
+    BUILTIN("unlimit", 0, bin_unlimit, 0, -1, 0, "hs", NULL),
+};
+
+/**/
+int
+boot_rlimits(Module m)
+{
+    return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_rlimits(Module m)
+{
+    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return 0;
+}
+#endif
diff --git a/Src/Builtins/rlimits.mdd b/Src/Builtins/rlimits.mdd
new file mode 100644
index 000000000..f0e41b73e
--- /dev/null
+++ b/Src/Builtins/rlimits.mdd
@@ -0,0 +1,18 @@
+autobins="limit ulimit unlimit"
+
+objects="rlimits.o"
+
+:<<\Make
+rlimits.o rlimits..o: rlimits.h
+
+# this file will not be made if limits are unavailable:
+# silent so the warning doesn't appear unless necessary
+rlimits.h: rlimits.awk @RLIMITS_INC_H@
+	@echo '$(AWK) -f $(sdir)/rlimits.awk @RLIMITS_INC_H@ > rlimits.h'; \
+	$(AWK) -f $(sdir)/rlimits.awk @RLIMITS_INC_H@ > rlimits.h || \
+	    echo WARNING: unknown limits:  mail rlimits.h to developers
+
+clean-here: clean.rlimits
+clean.rlimits:
+	rm -f rlimits.h
+Make
diff --git a/Src/Builtins/sched.c b/Src/Builtins/sched.c
new file mode 100644
index 000000000..b4914899e
--- /dev/null
+++ b/Src/Builtins/sched.c
@@ -0,0 +1,214 @@
+/*
+ * sched.c - execute commands at scheduled times
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "sched.mdh"
+#include "sched.pro"
+
+/* node in sched list */
+
+typedef struct schedcmd  *Schedcmd;
+
+struct schedcmd {
+    struct schedcmd *next;
+    char *cmd;			/* command to run */
+    time_t time;		/* when to run it */
+};
+
+/* the list of sched jobs pending */
+ 
+static struct schedcmd *schedcmds;
+
+/**/
+static int
+bin_sched(char *nam, char **argv, char *ops, int func)
+{
+    char *s = *argv++;
+    time_t t;
+    long h, m;
+    struct tm *tm;
+    struct schedcmd *sch, *sch2, *schl;
+    int sn;
+
+    /* If the argument begins with a -, remove the specified item from the
+    schedule. */
+    if (s && *s == '-') {
+	sn = atoi(s + 1);
+
+	if (!sn) {
+	    zwarnnam("sched", "usage for delete: sched -<item#>.", NULL, 0);
+	    return 1;
+	}
+	for (schl = (struct schedcmd *)&schedcmds, sch = schedcmds, sn--;
+	     sch && sn; sch = (schl = sch)->next, sn--);
+	if (!sch) {
+	    zwarnnam("sched", "not that many entries", NULL, 0);
+	    return 1;
+	}
+	schl->next = sch->next;
+	zsfree(sch->cmd);
+	zfree(sch, sizeof(struct schedcmd));
+
+	return 0;
+    }
+
+    /* given no arguments, display the schedule list */
+    if (!s) {
+	char tbuf[40];
+
+	for (sn = 1, sch = schedcmds; sch; sch = sch->next, sn++) {
+	    t = sch->time;
+	    tm = localtime(&t);
+	    ztrftime(tbuf, 20, "%a %b %e %k:%M:%S", tm);
+	    printf("%3d %s %s\n", sn, tbuf, sch->cmd);
+	}
+	return 0;
+    } else if (!*argv) {
+	/* other than the two cases above, sched *
+	 *requires at least two arguments        */
+	zwarnnam("sched", "not enough arguments", NULL, 0);
+	return 1;
+    }
+
+    /* The first argument specifies the time to schedule the command for.  The
+    remaining arguments form the command. */
+    if (*s == '+') {
+	/* + introduces a relative time.  The rest of the argument is an
+	hour:minute offset from the current time.  Once the hour and minute
+	numbers have been extracted, and the format verified, the resulting
+	offset is simply added to the current time. */
+	h = zstrtol(s + 1, &s, 10);
+	if (*s != ':') {
+	    zwarnnam("sched", "bad time specifier", NULL, 0);
+	    return 1;
+	}
+	m = zstrtol(s + 1, &s, 10);
+	if (*s) {
+	    zwarnnam("sched", "bad time specifier", NULL, 0);
+	    return 1;
+	}
+	t = time(NULL) + h * 3600 + m * 60;
+    } else {
+	/* If there is no +, an absolute time of day must have been given.
+	This is in hour:minute format, optionally followed by a string starting
+	with `a' or `p' (for a.m. or p.m.).  Characters after the `a' or `p'
+	are ignored. */
+	h = zstrtol(s, &s, 10);
+	if (*s != ':') {
+	    zwarnnam("sched", "bad time specifier", NULL, 0);
+	    return 1;
+	}
+	m = zstrtol(s + 1, &s, 10);
+	if (*s && *s != 'a' && *s != 'A' && *s != 'p' && *s != 'P') {
+	    zwarnnam("sched", "bad time specifier", NULL, 0);
+	    return 1;
+	}
+	t = time(NULL);
+	tm = localtime(&t);
+	t -= tm->tm_sec + tm->tm_min * 60 + tm->tm_hour * 3600;
+	if (*s == 'p' || *s == 'P')
+	    h += 12;
+	t += h * 3600 + m * 60;
+	/* If the specified time is before the current time, it must refer to
+	tomorrow. */
+	if (t < time(NULL))
+	    t += 3600 * 24;
+    }
+    /* The time has been calculated; now add the new entry to the linked list
+    of scheduled commands. */
+    sch = (struct schedcmd *) zcalloc(sizeof *sch);
+    sch->time = t;
+    PERMALLOC {
+	sch->cmd = zjoin(argv, ' ');
+    } LASTALLOC;
+    sch->next = NULL;
+    for (sch2 = (struct schedcmd *)&schedcmds; sch2->next; sch2 = sch2->next);
+    sch2->next = sch;
+    return 0;
+}
+
+/* Check scheduled commands; call this function from time to time. */
+
+/**/
+static void
+checksched(void)
+{
+    time_t t;
+    struct schedcmd *sch, *schl;
+
+    if(!schedcmds)
+	return;
+    t = time(NULL);
+    for (schl = (struct schedcmd *)&schedcmds, sch = schedcmds; sch;
+	 sch = (schl = sch)->next) {
+	if (sch->time <= t) {
+	    execstring(sch->cmd, 0, 0);
+	    schl->next = sch->next;
+	    zsfree(sch->cmd);
+	    zfree(sch, sizeof(struct schedcmd));
+	    sch = schl;
+	}
+    }
+}
+
+static void (*p_checksched) _((void)) = checksched;
+static struct linknode n_checksched = { NULL, NULL, &p_checksched };
+
+static struct builtin bintab[] = {
+    BUILTIN("sched", 0, bin_sched, 0, -1, 0, NULL, NULL),
+};
+
+/**/
+int
+boot_sched(Module m)
+{
+    if(!addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)))
+	return 1;
+    uaddlinknode(prepromptfns, &n_checksched);
+    return 0;
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_sched(Module m)
+{
+    struct schedcmd *sch, *schn;
+
+    for (sch = schedcmds; sch; sch = schn) {
+	schn = sch->next;
+	zsfree(sch->cmd);
+	zfree(sch, sizeof(*sch));
+    }
+    uremnode(prepromptfns, &n_checksched);
+    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return 0;
+}
+
+#endif
diff --git a/Src/Builtins/sched.mdd b/Src/Builtins/sched.mdd
new file mode 100644
index 000000000..6ed749f32
--- /dev/null
+++ b/Src/Builtins/sched.mdd
@@ -0,0 +1,3 @@
+autobins="sched"
+
+objects="sched.o"
diff --git a/Src/Makefile.in b/Src/Makefile.in
new file mode 100644
index 000000000..453159e17
--- /dev/null
+++ b/Src/Makefile.in
@@ -0,0 +1,195 @@
+#
+# Makefile for Src subdirectory
+#
+# Copyright (c) 1995-1997 Richard Coleman
+# All rights reserved.
+#
+# Permission is hereby granted, without written agreement and without
+# license or royalty fees, to use, copy, modify, and distribute this
+# software and to distribute modified versions of this software for any
+# purpose, provided that the above copyright notice and the following
+# two paragraphs appear in all copies of this software.
+#
+# In no event shall Richard Coleman or the Zsh Development Group be liable
+# to any party for direct, indirect, special, incidental, or consequential
+# damages arising out of the use of this software and its documentation,
+# even if Richard Coleman and the Zsh Development Group have been advised of
+# the possibility of such damage.
+#
+# Richard Coleman and the Zsh Development Group specifically disclaim any
+# warranties, including, but not limited to, the implied warranties of
+# merchantability and fitness for a particular purpose.  The software
+# provided hereunder is on an "as is" basis, and Richard Coleman and the
+# Zsh Development Group have no obligation to provide maintenance,
+# support, updates, enhancements, or modifications.
+#
+
+subdir = Src
+dir_top = ..
+SUBDIRS =
+
+@@version.mk@@
+@@defs.mk@@
+
+sdir_src      = $(sdir)
+dir_src       = .
+
+# ========= DEPENDENCIES FOR BUILDING ==========
+
+LINK        = $(CC) $(LDFLAGS) $(EXELDFLAGS) $(EXTRA_LDFLAGS) -o $@
+DLLINK      = $(DLLD) $(LDFLAGS) $(LIBLDFLAGS) $(DLLDFLAGS) -o $@
+
+all: bin modules
+
+bin: zsh
+
+modules: headers
+
+MAIN_OBJS = main.o
+
+LSTMP =
+LLIST =
+NSTMP = stamp-modobjs
+NLIST = `cat stamp-modobjs`
+
+LIBZSH = libzsh-$(VERSION).$(DL_EXT)
+NIBZSH =
+
+LDRUNPATH = LD_RUN_PATH=$(libdir)/zsh
+NDRUNPATH =
+
+zsh: $(@L@IBZSH) $(@L@STMP) $(MAIN_OBJS)
+	rm -f $@
+	$(@L@DRUNPATH) $(LINK) $(MAIN_OBJS) $(@L@LIST) $(@L@IBZSH) $(LIBS)
+
+$(LIBZSH): $(LIBOBJS) $(NSTMP)
+	rm -f $@
+	$(DLLINK) $(LIBOBJS) $(NLIST)
+
+stamp-modobjs: modobjs
+	@if cmp -s stamp-modobjs.tmp stamp-modobjs; then \
+	    rm -f stamp-modobjs.tmp; \
+	    echo "\`stamp-modobjs' is up to date."; \
+	else \
+	    mv -f stamp-modobjs.tmp stamp-modobjs; \
+	    echo "Updated \`stamp-modobjs'."; \
+	fi
+
+modobjs: headers rm-modobjs-tmp
+
+rm-modobjs-tmp:
+	rm -f stamp-modobjs.tmp
+
+Makemod modules.index prep: modules-bltin
+	( cd $(sdir_top) && $(SHELL) $(subdir)/mkmodindex.sh $(subdir) ) \
+	    > modules.index.tmp
+	@if cmp -s modules.index.tmp modules.index; then \
+	    rm -f modules.index.tmp; \
+	    echo "\`modules.index' is up to date."; \
+	else \
+	    mv -f modules.index.tmp modules.index; \
+	    echo "Updated \`modules.index'."; \
+	fi
+	@case $(sdir_top) in \
+	    /*) top_srcdir=$(sdir_top) ;; \
+	    *) top_srcdir=$(subdir)/$(sdir_top) ;; \
+	esac; \
+	export top_srcdir; \
+	echo 'cd $(dir_top) && $(SHELL)' \
+	    '$$top_srcdir/$(subdir)/mkmakemod.sh $(subdir) Makemod'; \
+	cd $(dir_top) && \
+	    $(SHELL) $$top_srcdir/$(subdir)/mkmakemod.sh $(subdir) Makemod
+	@$(MAKE) -f Makemod $(MAKEDEFS) prep || rm -f Makemod
+
+FORCE:
+
+# ========== LINKING IN MODULES ==========
+
+modules-bltin:
+	if test @D@ = N; then \
+	    cat $(sdir)/xmods.conf > $@; \
+	elif test @RTLD_GLOBAL_OK@ != yes; then \
+	    echo comp1 > $@; \
+	else \
+	    echo > $@; \
+	fi
+
+# ========== ANSI TO K&R CONVERSION ==========
+
+ANSI_KNR = ansi2knr
+ANSIKNR  =
+
+Makemod: $(ANSI@U@KNR)
+
+ansi2knr.o: ansi2knr.c
+	$(CC) -c $(CPPFLAGS) $(CFLAGS) $(sdir)/ansi2knr.c
+
+ansi2knr: ansi2knr.o
+	rm -f $@
+	$(CC) $(LDFLAGS) $(EXELDFLAGS) -o $@ ansi2knr.o
+
+# ========== DEPENDENCIES FOR INSTALLING ==========
+
+install: install.bin install.modules
+uninstall: uninstall.bin uninstall.modules
+
+install.bin: install.bin-here
+uninstall.bin: uninstall.bin-here
+
+# install binary, creating install directory if necessary
+install.bin-here: zsh install.bin-@L@
+	$(sdir_top)/mkinstalldirs $(bindir)
+	$(INSTALL_PROGRAM) zsh $(bindir)/zsh-$(VERSION)
+	if test -f $(bindir)/zsh; then \
+	    rm -f $(bindir)/zsh.old; \
+	    ln $(bindir)/zsh $(bindir)/zsh.old; \
+	else :; fi
+	rm -f $(bindir)/zsh.new
+	ln $(bindir)/zsh-$(VERSION) $(bindir)/zsh.new
+	mv $(bindir)/zsh.new $(bindir)/zsh
+
+install.bin-N:
+install.bin-L: $(LIBZSH)
+	$(sdir_top)/mkinstalldirs $(libdir)/zsh
+	$(INSTALL_PROGRAM) $(LIBZSH) $(libdir)/zsh/$(LIBZSH)
+
+# uninstall binary
+uninstall.bin-here: uninstall.bin-@L@
+	rm -f $(bindir)/zsh-$(VERSION) $(bindir)/zsh
+
+uninstall.bin-N:
+uninstall.bin-L:
+	rm -f $(libdir)/zsh/$(LIBZSH)
+
+# ========== DEPENDENCIES FOR CLEANUP ==========
+
+@@clean.mk@@
+
+mostlyclean-here:
+	rm -f stamp-modobjs stamp-modobjs.tmp
+
+clean-here:
+	rm -f modules.index.tmp modules.stamp zsh ansi2knr.o ansi2knr
+	rm -f libzsh-*.$(DL_EXT)
+
+distclean-here:
+	rm -f TAGS tags
+	rm -f modules.index modules-bltin Makefile
+
+mostlyclean: mostlyclean-modules
+clean: clean-modules
+distclean: distclean-modules
+realclean: realclean-modules
+
+mostlyclean-modules clean-modules distclean-modules realclean-modules: Makemod
+	@$(MAKE) -f Makemod $(MAKEDEFS) `echo $@ | sed 's/-modules//'`
+
+# ========== RECURSIVE MAKES ==========
+
+install.modules uninstall.modules \
+modobjs modules headers proto $(MAIN_OBJS): Makemod
+	@$(MAKE) -f Makemod $(MAKEDEFS) $@
+
+# ========== DEPENDENCIES FOR MAINTENANCE ==========
+
+@@config.mk@@
diff --git a/Src/Makemod.in.in b/Src/Makemod.in.in
new file mode 100644
index 000000000..f27a6bd57
--- /dev/null
+++ b/Src/Makemod.in.in
@@ -0,0 +1,180 @@
+#
+# Makemod.in.in
+#
+# Copyright (c) 1995-1997 Richard Coleman
+# All rights reserved.
+#
+# Permission is hereby granted, without written agreement and without
+# license or royalty fees, to use, copy, modify, and distribute this
+# software and to distribute modified versions of this software for any
+# purpose, provided that the above copyright notice and the following
+# two paragraphs appear in all copies of this software.
+#
+# In no event shall Richard Coleman or the Zsh Development Group be liable
+# to any party for direct, indirect, special, incidental, or consequential
+# damages arising out of the use of this software and its documentation,
+# even if Richard Coleman and the Zsh Development Group have been advised of
+# the possibility of such damage.
+#
+# Richard Coleman and the Zsh Development Group specifically disclaim any
+# warranties, including, but not limited to, the implied warranties of
+# merchantability and fitness for a particular purpose.  The software
+# provided hereunder is on an "as is" basis, and Richard Coleman and the
+# Zsh Development Group have no obligation to provide maintenance,
+# support, updates, enhancements, or modifications.
+#
+
+# ========== OVERRIDABLE VARIABLES ==========
+
+# subdir is done by mkmakemod.sh
+# dir_top is done by mkmakemod.sh
+# SUBDIRS is done by mkmakemod.sh
+
+@@version.mk@@
+@@defs.mk@@
+
+sdir_src      = $(sdir_top)/Src
+dir_src       = $(dir_top)/Src
+
+# ========== COMPILATION RULES ==========
+
+DNCFLAGS =
+
+COMPILE     = $(CC) -c -I. $(CPPFLAGS) $(DEFS) $(CFLAGS) $(D@L@CFLAGS)
+DLCOMPILE   = $(CC) -c -I. $(CPPFLAGS) $(DEFS) -DMODULE $(CFLAGS) $(DLCFLAGS)
+LINK        = $(CC) $(LDFLAGS) $(EXELDFLAGS) $(EXTRA_LDFLAGS) -o $@
+DLLINK      = $(DLLD) $(LDFLAGS) $(LIBLDFLAGS) $(DLLDFLAGS) -o $@
+
+KNR_OBJ=.o
+KNROBJ=._foo_
+
+ANSIOBJ=.o
+ANSI_OBJ=._foo_
+
+.SUFFIXES: .c .$(DL_EXT) ..o .._foo_ .o ._foo_ .pro
+
+.c$(ANSI@U@OBJ):
+	$(COMPILE) -o $@ $<
+	@rm -f $(dir_src)/stamp-modobjs
+
+.c$(KNR@U@OBJ):
+	$(dir_src)/ansi2knr $< > $@.c
+	$(COMPILE) -o $@ $@.c
+	rm -f $@.c
+	@rm -f $(dir_src)/stamp-modobjs
+
+.c.$(ANSI@U@OBJ):
+	$(DLCOMPILE) -o $@ $<
+
+.c.$(KNR@U@OBJ):
+	$(dir_src)/ansi2knr $< > $@.c
+	$(DLCOMPILE) -o $@ $@.c
+	rm -f $@.c
+
+.c.pro:
+	$(AWK) -f $(sdir_src)/makepro.awk $< $(subdir) > $@
+
+PROTODEPS = $(sdir_src)/makepro.awk
+
+# ========== DEPENDENCIES FOR BUILDING ==========
+
+all: modobjs modules
+
+modobjs: $(MODOBJS)
+modules: $(MODULES)
+headers: $(MDHS)
+proto: $(PROTOS)
+
+prep:
+	@case $(sdir_top) in \
+	    /*) top_srcdir=$(sdir_top) ;; \
+	    *) top_srcdir=$(subdir)/$(sdir_top) ;; \
+	esac; \
+	export top_srcdir; \
+	cd $(dir_top) || exit 1; \
+	subdirs='$(SUBDIRS)'; \
+	for subdir in $$subdirs; do \
+	    dir=$(subdir)/$$subdir; \
+	    test -d $$dir || mkdir $$dir; \
+	    $(SHELL) $$top_srcdir/Src/mkmakemod.sh $$dir Makefile || exit 1; \
+	    ( cd $$dir && $(MAKE) $(MAKEDEFS) $@ ) || exit 1; \
+	done
+
+headers prep: $(dir_src)/modules.stamp
+$(dir_src)/modules.stamp: $(MDDS)
+	echo 'timestamp for *.mdd files' > $@
+
+FORCE:
+
+# ========== DEPENDENCIES FOR INSTALLING ==========
+
+install: install.bin install.modules
+uninstall: uninstall.bin uninstall.modules
+
+install.bin: install.bin-here
+uninstall.bin: uninstall.bin-here
+install.modules: install.modules-here
+uninstall.modules: uninstall.modules-here
+
+install.bin-here uninstall.bin-here:
+
+install.modules-here:
+	$(sdir_top)/mkinstalldirs $(MODDIR)
+	modules='$(MODULES)'; for mod in $$modules; do \
+	    $(INSTALL_PROGRAM) $$mod $(MODDIR)/$$mod; \
+	done
+
+uninstall.modules-here:
+	modules='$(MODULES)'; for mod in $$modules; do \
+	    if test -f $(MODDIR)/$$mod; then \
+		rm -f $(MODDIR)/$$mod; \
+	    else :; fi; \
+	done
+
+# ========== DEPENDENCIES FOR CLEANUP ==========
+
+@@clean.mk@@
+
+mostlyclean-here:
+	rm -f *.o *.$(DL_EXT)
+
+clean-here:
+	rm -f *.o.c *.pro *.mdh *.mdhi *.mdhs *.mdh.tmp
+
+distclean-here:
+	rm -f $(makefile) $(makefile).in
+
+# ========== RECURSIVE MAKES ==========
+
+install.bin uninstall.bin install.modules uninstall.modules \
+modobjs modules headers proto:
+	@subdirs='$(SUBDIRS)'; for subdir in $$subdirs; do \
+	  ( cd $$subdir && $(MAKE) $(MAKEDEFS) $@ ) || exit 1; \
+	done
+
+# ========== DEPENDENCIES FOR MAINTENANCE ==========
+
+$(makefile): $(makefile).in $(dir_top)/config.status
+	@case $(sdir_top) in \
+	    /*) top_srcdir=$(sdir_top) ;; \
+	    *) top_srcdir=$(subdir)/$(sdir_top) ;; \
+	esac; \
+	export top_srcdir; \
+	echo 'cd $(dir_top) && $(SHELL)' \
+	    '$$top_srcdir/Src/mkmakemod.sh -m $(subdir) $(makefile)'; \
+	cd $(dir_top) && \
+	    $(SHELL) $$top_srcdir/Src/mkmakemod.sh -m $(subdir) $(makefile)
+
+$(makefile).in: $(sdir_src)/mkmakemod.sh $(sdir_src)/Makemod.in.in $(MDDS) $(dir_src)/modules-bltin
+	@case $(sdir_top) in \
+	    /*) top_srcdir=$(sdir_top) ;; \
+	    *) top_srcdir=$(subdir)/$(sdir_top) ;; \
+	esac; \
+	export top_srcdir; \
+	echo 'cd $(dir_top) && $(SHELL)' \
+	    '$$top_srcdir/Src/mkmakemod.sh -i $(subdir) $(makefile)'; \
+	cd $(dir_top) && \
+	    $(SHELL) $$top_srcdir/Src/mkmakemod.sh -i $(subdir) $(makefile)
+
+$(dir_src)/modules-bltin:
+	@cd $(dir_src) && $(MAKE) $(MAKEDEFS) modules-bltin
diff --git a/Src/Modules/.cvsignore b/Src/Modules/.cvsignore
new file mode 100644
index 000000000..169be5ef9
--- /dev/null
+++ b/Src/Modules/.cvsignore
@@ -0,0 +1,10 @@
+Makefile
+Makefile.in
+*.pro
+*.o
+*.o.c
+*.so
+*.mdh
+*.mdhi
+*.mdhs
+*.mdh.tmp
diff --git a/Src/Modules/.distfiles b/Src/Modules/.distfiles
new file mode 100644
index 000000000..4c98f97ea
--- /dev/null
+++ b/Src/Modules/.distfiles
@@ -0,0 +1,8 @@
+DISTFILES_SRC='
+    .cvsignore .distfiles .exrc
+    cap.mdd cap.c
+    clone.mdd clone.c
+    example.mdd example.c
+    files.mdd files.c
+    stat.mdd stat.c
+'
diff --git a/Src/Modules/.exrc b/Src/Modules/.exrc
new file mode 100644
index 000000000..91d0b39ef
--- /dev/null
+++ b/Src/Modules/.exrc
@@ -0,0 +1,2 @@
+set ai
+set sw=4
diff --git a/Src/Modules/cap.c b/Src/Modules/cap.c
new file mode 100644
index 000000000..008b6932d
--- /dev/null
+++ b/Src/Modules/cap.c
@@ -0,0 +1,141 @@
+/*
+ * cap.c - POSIX.1e (POSIX.6) capability set manipulation
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1997 Andrew Main
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Andrew Main or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Andrew Main and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Andrew Main and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Andrew Main and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "cap.mdh"
+#include "cap.pro"
+
+#ifdef HAVE_CAP_GET_PROC
+
+static int
+bin_cap(char *nam, char **argv, char *ops, int func)
+{
+    int ret = 0;
+    cap_t caps;
+    if(*argv) {
+	caps = cap_from_text(*argv);
+	if(!caps) {
+	    zwarnnam(nam, "invalid capability string", NULL, 0);
+	    return 1;
+	}
+	if(cap_set_proc(caps)) {
+	    zwarnnam(nam, "can't change capabilites: %e", NULL, errno);
+	    ret = 1;
+	}
+    } else {
+	char *result;
+	ssize_t length;
+	caps = cap_get_proc();
+	if(caps)
+	    result = cap_to_text(caps, &length);
+	if(!caps || !result) {
+	    zwarnnam(nam, "can't get capabilites: %e", NULL, errno);
+	    ret = 1;
+	} else
+	    puts(result);
+    }
+    cap_free(&caps);
+    return ret;
+}
+
+static int
+bin_getcap(char *nam, char **argv, char *ops, int func)
+{
+    int ret = 0;
+
+    do {
+	char *result;
+	ssize_t length;
+	cap_t caps = cap_get_file(*argv);
+	if(caps)
+	    result = cap_to_text(caps, &length);
+	if (!caps || !result) {
+	    zwarnnam(nam, "%s: %e", *argv, errno);
+	    ret = 1;
+	} else
+	    printf("%s %s\n", *argv, result);
+	cap_free(&caps);
+    } while(*++argv);
+    return ret;
+}
+
+static int
+bin_setcap(char *nam, char **argv, char *ops, int func)
+{
+    cap_t caps;
+    int ret = 0;
+
+    caps = cap_from_text(*argv++);
+    if(!caps) {
+	zwarnnam(nam, "invalid capability string", NULL, 0);
+	return 1;
+    }
+
+    do {
+	if(cap_set_file(*argv, caps)) {
+	    zwarnnam(nam, "%s: %e", *argv, errno);
+	    ret = 1;
+	}
+    } while(*++argv);
+    cap_free(&caps);
+    return ret;
+}
+
+#else /* !HAVE_CAP_GET_PROC */
+
+# define bin_cap    bin_notavail
+# define bin_getcap bin_notavail
+# define bin_setcap bin_notavail
+
+#endif /* !HAVE_CAP_GET_PROC */
+
+/* module paraphernalia */
+
+static struct builtin bintab[] = {
+    BUILTIN("cap",    0, bin_cap,    0,  1, 0, NULL, NULL),
+    BUILTIN("getcap", 0, bin_getcap, 1, -1, 0, NULL, NULL),
+    BUILTIN("setcap", 0, bin_setcap, 2, -1, 0, NULL, NULL),
+};
+
+/**/
+int
+boot_cap(Module m)
+{
+    return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_cap(Module m)
+{
+    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return 0;
+}
+#endif
diff --git a/Src/Modules/cap.mdd b/Src/Modules/cap.mdd
new file mode 100644
index 000000000..97f377e9d
--- /dev/null
+++ b/Src/Modules/cap.mdd
@@ -0,0 +1,3 @@
+autobins="cap getcap setcap"
+
+objects="cap.o"
diff --git a/Src/Modules/clone.c b/Src/Modules/clone.c
new file mode 100644
index 000000000..11387fc90
--- /dev/null
+++ b/Src/Modules/clone.c
@@ -0,0 +1,115 @@
+/*
+ * clone.c - start a forked instance of the current shell on a new terminal
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1997 Zoltán Hidvégi
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Zoltán Hidvégi or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Zoltán Hidvégi and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Zoltán Hidvégi and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Zoltán Hidvégi and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+/*
+ * The clone builtin can be used to start a forked instance of the current
+ * shell on a new terminal.  The only argument to the builtin is the name
+ * of the new terminal.  In the new shell the PID, PPID and TTY parameters
+ * are changed appropriately.  $! is set to zero in the new instance of the
+ * shell and to the pid of the new instance in the original shell.
+ *
+ */
+
+#include "clone.mdh"
+#include "clone.pro"
+
+/**/
+static int
+bin_clone(char *nam, char **args, char *ops, int func)
+{
+    int ttyfd, pid;
+
+    unmetafy(*args, NULL);
+    ttyfd = open(*args, O_RDWR|O_NOCTTY);
+    if (ttyfd < 0) {
+	zwarnnam(nam, "%s: %e", *args, errno);
+	return 1;
+    }
+    pid = fork();
+    if (!pid) {
+	clearjobtab();
+	ppid = getppid();
+	mypid = getpid();
+#ifdef HAVE_SETSID
+	if (setsid() != mypid) {
+	    zwarnnam(nam, "failed to create new session: %e", NULL, errno);
+#endif
+#ifdef TIOCNOTTY
+	    if (ioctl(SHTTY, TIOCNOTTY))
+		zwarnnam(nam, "%e", NULL, errno);
+	    setpgrp(0L, mypid);
+#endif
+#ifdef HAVE_SETSID
+	}
+#endif
+	if (ttyfd) {
+	    close(0);
+	    dup(ttyfd);
+	} else
+	    ttyfd = -1;
+	close(1);
+	close(2);
+	dup(0);
+	dup(0);
+	closem(0);
+	close(coprocin);
+	close(coprocout);
+	init_io();
+	setsparam("TTY", ztrdup(ttystrname));
+    }
+    close(ttyfd);
+    if (pid < 0) {
+	zerrnam(nam, "fork failed: %e", NULL, errno);
+	return 1;
+    }
+    lastpid = pid;
+    return 0;
+}
+
+static struct builtin bintab[] = {
+    BUILTIN("clone", 0, bin_clone, 1, 1, 0, NULL, NULL),
+};
+
+/**/
+int
+boot_clone(Module m)
+{
+    return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_clone(Module m)
+{
+    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return 0;
+}
+#endif
diff --git a/Src/Modules/clone.mdd b/Src/Modules/clone.mdd
new file mode 100644
index 000000000..5277d3151
--- /dev/null
+++ b/Src/Modules/clone.mdd
@@ -0,0 +1,3 @@
+autobins="clone"
+
+objects="clone.o"
diff --git a/Src/Modules/example.c b/Src/Modules/example.c
new file mode 100644
index 000000000..45ef3c28f
--- /dev/null
+++ b/Src/Modules/example.c
@@ -0,0 +1,76 @@
+/*
+ * example.c - an example module for zsh
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1996-1997 Zoltán Hidvégi
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Zoltán Hidvégi or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Zoltán Hidvégi and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Zoltán Hidvégi and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Zoltán Hidvégi and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "example.mdh"
+#include "example.pro"
+
+/**/
+static int
+bin_example(char *nam, char **args, char *ops, int func)
+{
+    unsigned char c;
+
+    printf("Options: ");
+    for (c = 32; ++c < 128;)
+	if (ops[c])
+	    putchar(c);
+    printf("\nArguments:");
+    for (; *args; args++) {
+	putchar(' ');
+	fputs(*args, stdout);
+    }
+    printf("\nName: %s\n", nam);
+    return 0;
+}
+
+/*
+ * boot_example is executed when the module is loaded.
+ */
+
+static struct builtin bintab[] = {
+    BUILTIN("example", 0, bin_example, 0, -1, 0, "flags", NULL),
+};
+
+/**/
+int
+boot_example(Module m)
+{
+    return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_example(Module m)
+{
+    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return 0;
+}
+#endif
diff --git a/Src/Modules/example.mdd b/Src/Modules/example.mdd
new file mode 100644
index 000000000..89f12097c
--- /dev/null
+++ b/Src/Modules/example.mdd
@@ -0,0 +1,3 @@
+autobins="example"
+
+objects="example.o"
diff --git a/Src/Modules/files.c b/Src/Modules/files.c
new file mode 100644
index 000000000..6127c5524
--- /dev/null
+++ b/Src/Modules/files.c
@@ -0,0 +1,528 @@
+/*
+ * files.c - file operation builtins
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1996-1997 Andrew Main
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Andrew Main or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Andrew Main and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Andrew Main and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Andrew Main and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "files.mdh"
+
+typedef int (*MoveFunc) _((char const *, char const *));
+
+#ifndef STDC_HEADERS
+extern int link _((const char *, const char *));
+extern int symlink _((const char *, const char *));
+extern int rename _((const char *, const char *));
+#endif
+
+#include "files.pro"
+
+/**/
+static int
+ask(void)
+{
+    int a = getchar(), c;
+    for(c = a; c != EOF && c != '\n'; )
+	c = getchar();
+    return a == 'y' || a == 'Y';
+}
+
+/* sync builtin */
+
+/**/
+static int
+bin_sync(char *nam, char **args, char *ops, int func)
+{
+    sync();
+    return 0;
+}
+
+/* mkdir builtin */
+
+/**/
+static int
+bin_mkdir(char *nam, char **args, char *ops, int func)
+{
+    mode_t oumask = umask(0);
+    mode_t mode = 0777 & ~oumask;
+    int err = 0;
+
+    umask(oumask);
+    if(ops['m']) {
+	char *str = *args++, *ptr;
+
+	if(!*args) {
+	    zwarnnam(nam, "not enough arguments", NULL, 0);
+	    return 1;
+	}
+	mode = zstrtol(str, &ptr, 8);
+	if(!*str || *ptr) {
+	    zwarnnam(nam, "invalid mode `%s'", str, 0);
+	    return 1;
+	}
+    }
+    for(; *args; args++) {
+	char *ptr = strchr(*args, 0);
+
+	while(ptr > *args + (**args == '/') && *--ptr == '/')
+	    *ptr = 0;
+	if(ztrlen(*args) > PATH_MAX - 1) {
+	    zwarnnam(nam, "%s: %e", *args, ENAMETOOLONG);
+	    err = 1;
+	    continue;
+	}
+	if(ops['p']) {
+	    char *ptr = *args;
+
+	    for(;;) {
+		while(*ptr == '/')
+		    ptr++;
+		while(*ptr && *ptr != '/')
+		    ptr++;
+		if(!*ptr) {
+		    err |= domkdir(nam, *args, mode, 1);
+		    break;
+		} else {
+		    int e;
+
+		    *ptr = 0;
+		    e = domkdir(nam, *args, mode | 0300, 1);
+		    if(e) {
+			err = 1;
+			break;
+		    }
+		    *ptr = '/';
+		}
+	    }
+	} else
+	    err |= domkdir(nam, *args, mode, 0);
+    }
+    return err;
+}
+
+/**/
+static int
+domkdir(char *nam, char *path, mode_t mode, int p)
+{
+    int err;
+    mode_t oumask;
+    char const *rpath = unmeta(path);
+
+    if(p) {
+	struct stat st;
+
+	if(!lstat(rpath, &st) && S_ISDIR(st.st_mode))
+	    return 0;
+    }
+    oumask = umask(0);
+    err = mkdir(path, mode) ? errno : 0;
+    umask(oumask);
+    if(!err)
+	return 0;
+    zwarnnam(nam, "cannot make directory `%s': %e", path, err);
+    return 1;
+}
+
+/* rmdir builtin */
+
+/**/
+static int
+bin_rmdir(char *nam, char **args, char *ops, int func)
+{
+    int err = 0;
+
+    for(; *args; args++) {
+	char *rpath = unmeta(*args);
+
+	if(!rpath) {
+	    zwarnnam(nam, "%s: %e", *args, ENAMETOOLONG);
+	    err = 1;
+	} else if(rmdir(rpath)) {
+	    zwarnnam(nam, "cannot remove directory `%s': %e", *args, errno);
+	    err = 1;
+	}
+    }
+    return err;
+}
+
+/* ln and mv builtins */
+
+#define BIN_LN 0
+#define BIN_MV 1
+
+#define MV_NODIRS (1<<0)
+#define MV_FORCE  (1<<1)
+#define MV_INTER  (1<<2)
+#define MV_ASKNW  (1<<3)
+#define MV_ATOMIC (1<<4)
+
+/* bin_ln actually does three related jobs: hard linking, symbolic *
+ * linking, and renaming.  If called as mv it renames, otherwise   *
+ * it looks at the -s option.  If hard linking, it will refuse to  *
+ * attempt linking to a directory unless the -d option is given.   */
+
+/**/
+static int
+bin_ln(char *nam, char **args, char *ops, int func)
+{
+    MoveFunc move;
+    int flags, space, err = 0;
+    char **a, *ptr, *rp;
+    struct stat st;
+    char buf[PATH_MAX * 2 + 1];
+
+
+    if(func == BIN_MV) {
+	move = rename;
+	flags = ops['f'] ? 0 : MV_ASKNW;
+	flags |= MV_ATOMIC;
+    } else {
+	flags = ops['f'] ? MV_FORCE : 0;
+#ifdef HAVE_LSTAT
+	if(ops['s'])
+	    move = symlink;
+	else
+#endif
+	     {
+	    move = link;
+	    if(!ops['d'])
+		flags |= MV_NODIRS;
+	}
+    }
+    if(ops['i'] && !ops['f'])
+	flags |= MV_INTER;
+    for(a = args; a[1]; a++) ;
+    if(a != args) {
+	rp = unmeta(*a);
+	if(rp && !stat(rp, &st) && S_ISDIR(st.st_mode))
+	    goto havedir;
+    }
+    if(a > args+1) {
+	zwarnnam(nam, "last of many arguments must be a directory", NULL, 0);
+	return 1;
+    }
+    if(!args[1]) {
+	ptr = strrchr(args[0], '/');
+	if(ptr)
+	    args[1] = ptr+1;
+	else
+	    args[1] = args[0];
+    }
+    return domove(nam, move, args[0], args[1], flags);
+    havedir:
+    strcpy(buf, *a);
+    *a = NULL;
+    space = PATH_MAX - 1 - ztrlen(buf);
+    rp = strchr(buf, 0);
+    *rp++ = '/';
+    for(; *args; args++) {
+	if(ztrlen(*args) > PATH_MAX) {
+	    zwarnnam(nam, "%s: %e", *args, ENAMETOOLONG);
+	    err = 1;
+	    continue;
+	}
+	ptr = strrchr(*args, '/');
+	if(ptr)
+	    ptr++;
+	else
+	    ptr = *args;
+	if(ztrlen(ptr) > space) {
+	    zwarnnam(nam, "%s: %e", ptr, ENAMETOOLONG);
+	    err = 1;
+	    continue;
+	}
+	strcpy(rp, ptr);
+	err |= domove(nam, move, *args, buf, flags);
+    }
+    return err;
+}
+
+/**/
+static int
+domove(char *nam, MoveFunc move, char *p, char *q, int flags)
+{
+    struct stat st;
+    char *qbuf;
+    char pbuf[PATH_MAX + 1];
+    strcpy(pbuf, unmeta(p));
+    qbuf = unmeta(q);
+    if(flags & MV_NODIRS) {
+	errno = EISDIR;
+	if(lstat(pbuf, &st) || S_ISDIR(st.st_mode)) {
+	    zwarnnam(nam, "%s: %e", p, errno);
+	    return 1;
+	}
+    }
+    if(!lstat(qbuf, &st)) {
+	int doit = flags & MV_FORCE;
+	if(S_ISDIR(st.st_mode)) {
+	    zwarnnam(nam, "%s: cannot overwrite directory", q, 0);
+	    return 1;
+	} else if(flags & MV_INTER) {
+	    nicezputs(nam, stderr);
+	    fputs(": replace `", stderr);
+	    nicezputs(q, stderr);
+	    fputs("'? ", stderr);
+	    fflush(stderr);
+	    if(!ask())
+		return 0;
+	    doit = 1;
+	} else if((flags & MV_ASKNW) &&
+		!S_ISLNK(st.st_mode) &&
+		access(qbuf, W_OK)) {
+	    nicezputs(nam, stderr);
+	    fputs(": replace `", stderr);
+	    nicezputs(q, stderr);
+	    fprintf(stderr, "', overriding mode %04o? ",
+		mode_to_octal(st.st_mode));
+	    fflush(stderr);
+	    if(!ask())
+		return 0;
+	    doit = 1;
+	}
+	if(doit && !(flags & MV_ATOMIC))
+	    unlink(qbuf);
+    }
+    if(move(pbuf, qbuf)) {
+	zwarnnam(nam, "%s: %e", p, errno);
+	return 1;
+    }
+    return 0;
+}
+
+/* rm builtin */
+
+/**/
+static int
+bin_rm(char *nam, char **args, char *ops, int func)
+{
+    int err = 0, len;
+    char *rp, *s;
+    struct dirsav ds;
+
+    ds.ino = ds.dev = 0;
+    ds.dirname = NULL;
+    ds.dirfd = ds.level = -1;
+    if (ops['r'] || ops['s']) {
+	if ((ds.dirfd = open(".", O_RDONLY|O_NOCTTY)) < 0 &&
+	    zgetdir(&ds) && *ds.dirname != '/')
+	    ds.dirfd = open("..", O_RDONLY|O_NOCTTY);
+    }
+    for(; !errflag && !(err & 2) && *args; args++) {
+	rp = ztrdup(*args);
+	unmetafy(rp, &len);
+	if (ops['s']) {
+	    s = strrchr(rp, '/');
+	    if (s && !s[1]) {
+		while (*s == '/' && s > rp)
+		    *s-- = '\0';
+		while (*s != '/' && s > rp)
+		    s--;
+	    }
+	    if (s && s[1]) {
+		int e;
+
+		*s = '\0';
+		e = lchdir(s > rp ? rp : "/", &ds, 1);
+		err |= -e;
+		if (!e) {
+		    struct dirsav d;
+
+		    d.ino = d.dev = 0;
+		    d.dirname = NULL;
+		    d.dirfd = d.level = -1;
+		    err |= dorm(nam, *args, s + 1, ops, &d, 0);
+		    zsfree(d.dirname);
+		    if (restoredir(&ds))
+			err |= 2;
+		} else
+		    zwarnnam(nam, "%s: %e", *args, errno);
+	    } else
+		err |= dorm(nam, *args, rp, ops, &ds, 0);
+	} else
+	    err |= dorm(nam, *args, rp, ops, &ds, 1);
+	zfree(rp, len + 1);
+    }
+    if ((err & 2) && ds.dirfd >= 0 && restoredir(&ds) && zchdir(pwd)) {
+	zsfree(pwd);
+	pwd = ztrdup("/");
+	chdir(pwd);
+    }
+    if (ds.dirfd >= 0)
+	close(ds.dirfd);
+    zsfree(ds.dirname);
+    return ops['f'] ? 0 : !!err;
+}
+
+/**/
+static int
+dorm(char *nam, char *arg, char *rp, char *ops, struct dirsav *ds, int first)
+{
+    struct stat st;
+
+    if((!ops['d'] || !ops['f']) && !lstat(rp, &st)) {
+	if(!ops['d'] && S_ISDIR(st.st_mode)) {
+	    if(ops['r'])
+		return dormr(nam, arg, rp, ops, ds, first);
+	    if(!ops['f'])
+		zwarnnam(nam, "%s: %e", arg, EISDIR);
+	    return 1;
+	}
+	if(!ops['f'] && ops['i']) {
+	    nicezputs(nam, stderr);
+	    fputs(": remove `", stderr);
+	    nicezputs(arg, stderr);
+	    fputs("'? ", stderr);
+	    fflush(stderr);
+	    if(!ask())
+		return 0;
+	} else if(!ops['f'] &&
+		!S_ISLNK(st.st_mode) &&
+	    	access(rp, W_OK)) {
+	    nicezputs(nam, stderr);
+	    fputs(": remove `", stderr);
+	    nicezputs(arg, stderr);
+	    fprintf(stderr, "', overriding mode %04o? ",
+		mode_to_octal(st.st_mode));
+	    fflush(stderr);
+	    if(!ask())
+		return 0;
+	}
+    }
+    if(!unlink(rp))
+	return 0;
+    if(!ops['f'])
+	zwarnnam(nam, "%s: %e", arg, errno);
+    return 1;
+}
+
+/**/
+static int
+dormr(char *nam, char *arg, char *rp, char *ops, struct dirsav *ds, int first)
+{
+    char *fn;
+    DIR *d;
+    int err;
+    struct dirsav dsav;
+    char *files = NULL;
+    int fileslen = 0;
+
+    err = -lchdir(rp, ds, !first);
+    if (err) {
+	if (!ops['f'])
+	    zwarnnam(nam, "%s: %e", arg, errno);
+	return err;
+    }
+
+    dsav.ino = dsav.dev = 0;
+    dsav.dirname = NULL;
+    dsav.dirfd = dsav.level = -1;
+    d = opendir(".");
+    if(!d) {
+	if(!ops['f'])
+	    zwarnnam(nam, "%s: %e", arg, errno);
+	err = 1;
+    } else {
+	int arglen = strlen(arg) + 1;
+
+	while (!errflag && (fn = zreaddir(d, 1))) {
+	    int l = strlen(fn) + 1;
+	    files = hrealloc(files, fileslen, fileslen + l);
+	    strcpy(files + fileslen, fn);
+	    fileslen += l;
+	}
+	closedir(d);
+	for (fn = files; !errflag && !(err & 2) && fn < files + fileslen;) {
+	    int l = strlen(fn) + 1;
+	    VARARR(char, narg, arglen + l);
+
+	    strcpy(narg,arg);
+	    narg[arglen-1] = '/';
+	    strcpy(narg + arglen, fn);
+	    unmetafy(fn, NULL);
+	    err |= dorm(nam, narg, fn, ops, &dsav, 0);
+	    fn += l;
+	}
+	hrealloc(files, fileslen, 0);
+    }
+    zsfree(dsav.dirname);
+    if (err & 2)
+	return 2;
+    if (restoredir(ds)) {
+	if(!ops['f'])
+	    zwarnnam(nam, "failed to return to previous directory: %e",
+		     NULL, errno);
+	return 2;
+    }
+    if(!ops['f'] && ops['i']) {
+	nicezputs(nam, stderr);
+	fputs(": remove `", stderr);
+	nicezputs(arg, stderr);
+	fputs("'? ", stderr);
+	fflush(stderr);
+	if(!ask())
+	    return err;
+    }
+    if(!rmdir(rp))
+	return err;
+    if(!ops['f'])
+	zwarnnam(nam, "%s: %e", arg, errno);
+    return 1;
+}
+
+/* module paraphernalia */
+
+#ifdef HAVE_LSTAT
+# define LN_OPTS "dfis"
+#else
+# define LN_OPTS "dfi"
+#endif
+
+static struct builtin bintab[] = {
+    BUILTIN("ln",    0, bin_ln,    1, -1, BIN_LN, LN_OPTS, NULL),
+    BUILTIN("mkdir", 0, bin_mkdir, 1, -1, 0,      "pm",    NULL),
+    BUILTIN("mv",    0, bin_ln,    2, -1, BIN_MV, "fi",    NULL),
+    BUILTIN("rm",    0, bin_rm,    1, -1, 0,      "dfirs", NULL),
+    BUILTIN("rmdir", 0, bin_rmdir, 1, -1, 0,      NULL,    NULL),
+    BUILTIN("sync",  0, bin_sync,  0,  0, 0,      NULL,    NULL),
+};
+
+/**/
+int
+boot_files(Module m)
+{
+    return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_files(Module m)
+{
+    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return 0;
+}
+#endif
diff --git a/Src/Modules/files.mdd b/Src/Modules/files.mdd
new file mode 100644
index 000000000..236ca2d5a
--- /dev/null
+++ b/Src/Modules/files.mdd
@@ -0,0 +1,3 @@
+autobins="ln mkdir mv rm rmdir sync"
+
+objects="files.o"
diff --git a/Src/Modules/stat.c b/Src/Modules/stat.c
new file mode 100644
index 000000000..09245b52f
--- /dev/null
+++ b/Src/Modules/stat.c
@@ -0,0 +1,535 @@
+/*
+ * stat.c - stat builtin interface to system call
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1996-1997 Peter Stephenson
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Peter Stephenson or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Peter Stephenson and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Peter Stephenson and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Peter Stephenson and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "stat.mdh"
+#include "stat.pro"
+
+enum statnum { ST_DEV, ST_INO, ST_MODE, ST_NLINK, ST_UID, ST_GID,
+		   ST_RDEV, ST_SIZE, ST_ATIM, ST_MTIM, ST_CTIM,
+		   ST_BLKSIZE, ST_BLOCKS, ST_READLINK, ST_COUNT };
+enum statflags { STF_NAME = 1,  STF_FILE = 2, STF_STRING = 4, STF_RAW = 8,
+		     STF_PICK = 16, STF_ARRAY = 32, STF_GMT = 64 };
+static char *statelts[] = { "device", "inode", "mode", "nlink",
+				"uid", "gid", "rdev", "size", "atime",
+				"mtime", "ctime", "blksize", "blocks",
+				"link", NULL };
+
+/**/
+static void
+statmodeprint(mode_t mode, char *outbuf, int flags)
+{
+    if (flags & STF_RAW) {
+	sprintf(outbuf, "%lu", (unsigned long)mode);
+	if (flags & STF_STRING)
+	    strcat(outbuf, " (");
+    }
+    if (flags & STF_STRING) {
+	static const char *modes = "?rwxrwxrwx";
+	static const mode_t mflags[] = { S_IRUSR, S_IWUSR, S_IXUSR,
+					 S_IRGRP, S_IWGRP, S_IXGRP,
+					 S_IROTH, S_IWOTH, S_IXOTH };
+	const mode_t *mfp = mflags;
+	char pm[11];
+	int i;
+
+	if (S_ISBLK(mode))
+	    *pm = 'b';
+	else if (S_ISCHR(mode))
+	    *pm = 'c';
+	else if (S_ISDIR(mode))
+	    *pm = 'd';
+	else if (S_ISFIFO(mode))
+	    *pm = 'p';
+	else if (S_ISLNK(mode))
+	    *pm = 'l';
+	else if (S_ISMPC(mode))
+	    *pm = 'm';
+	else if (S_ISNWK(mode))
+	    *pm = 'n';
+	else if (S_ISOFD(mode))
+	    *pm = 'M';
+	else if (S_ISOFL(mode))
+	    *pm = 'M';
+	else if (S_ISREG(mode))
+	    *pm = '-';
+	else if (S_ISSOCK(mode))
+	    *pm = 's';
+	else
+	    *pm = '?';
+
+	for (i = 1; i <= 9; i++)
+	    pm[i] = (mode & *mfp++) ? modes[i] : '-';
+
+	if (mode & S_ISUID)
+	    pm[3] = (mode & S_IXUSR) ? 's' : 'S';
+	if (mode & S_ISGID)
+	    pm[6] = (mode & S_IXGRP) ? 's' : 'S';
+	if (mode & S_ISVTX)
+	    pm[9] = (mode & S_IXOTH) ? 't' : 'T';
+
+	pm[10] = 0;
+	strcat(outbuf, pm);
+	if (flags & STF_RAW)
+	    strcat(outbuf, ")");
+    }
+}
+
+
+/**/
+static void
+statuidprint(uid_t uid, char *outbuf, int flags)
+{
+    if (flags & STF_RAW) {
+	sprintf(outbuf, "%lu", (unsigned long)uid);
+	if (flags & STF_STRING)
+	    strcat(outbuf, " (");
+    }
+    if (flags & STF_STRING) {
+#ifdef HAVE_GETPWUID
+	struct passwd *pwd;
+	pwd = getpwuid(uid);
+	strcat(outbuf, pwd ? pwd->pw_name : "???");
+#else /* !HAVE_GETPWUID */
+	strcat(outbuf, "???");
+#endif /* !HAVE_GETPWUID */
+	if (flags & STF_RAW)
+	    strcat(outbuf, ")");
+    }
+}
+
+
+/**/
+static void
+statgidprint(gid_t gid, char *outbuf, int flags)
+{
+    if (flags & STF_RAW) {
+	sprintf(outbuf, "%lu", (unsigned long)gid);
+	if (flags & STF_STRING)
+	    strcat(outbuf, " (");
+    }
+    if (flags & STF_STRING) {
+#ifdef HAVE_GETGRGID
+	struct group *gr;
+	gr = getgrgid(gid);
+	strcat(outbuf, gr ? gr->gr_name : "???");
+#else /* !HAVE_GETGRGID */
+	strcat(outbuf, "???");
+#endif /* !HAVE_GETGRGID */
+	if (flags & STF_RAW)
+	    strcat(outbuf, ")");
+    }
+}
+
+static char *timefmt;
+
+/**/
+static void
+stattimeprint(time_t tim, char *outbuf, int flags)
+{
+    if (flags & STF_RAW) {
+	sprintf(outbuf, "%ld", (unsigned long)tim);
+	if (flags & STF_STRING)
+	    strcat(outbuf, " (");
+    }
+    if (flags & STF_STRING) {
+	char *oend = outbuf + strlen(outbuf);
+	ztrftime(oend, 40, timefmt, (flags & STF_GMT) ? gmtime(&tim) :
+		 localtime(&tim));
+	if (flags & STF_RAW)
+	    strcat(outbuf, ")");
+    }
+}
+
+
+/**/
+static void
+statulprint(unsigned long num, char *outbuf)
+{
+    sprintf(outbuf, "%lu", num);
+}
+
+
+/**/
+static void
+statlinkprint(struct stat *sbuf, char *outbuf, char *fname)
+{
+    int num;
+
+    /* fname is NULL if we are looking at an fd */
+    if (fname && S_ISLNK(sbuf->st_mode) &&
+ 	(num = readlink(fname, outbuf, PATH_MAX)) > 0) {
+	/* readlink doesn't terminate the buffer itself */
+	outbuf[num] = '\0';
+    }
+}
+
+
+/**/
+static void
+statprint(struct stat *sbuf, char *outbuf, char *fname, int iwhich, int flags)
+{
+    char *optr = outbuf;
+
+    if (flags & STF_NAME) {
+	sprintf(outbuf, (flags & (STF_PICK|STF_ARRAY)) ?
+		"%s " : "%-8s", statelts[iwhich]);
+	optr += strlen(outbuf);
+    }
+    *optr = '\0';
+
+    /* cast values to unsigned long as safest bet */
+    switch (iwhich) {
+    case ST_DEV:
+	statulprint((unsigned long)sbuf->st_dev, optr);
+	break;
+
+    case ST_INO:
+	statulprint((unsigned long)sbuf->st_ino, optr);
+	break;
+
+    case ST_MODE:
+	statmodeprint(sbuf->st_mode, optr, flags);
+	break;
+
+    case ST_NLINK:
+	statulprint((unsigned long)sbuf->st_nlink, optr);
+	break;
+
+    case ST_UID:
+	statuidprint(sbuf->st_uid, optr, flags);
+	break;
+
+    case ST_GID:
+	statgidprint(sbuf->st_gid, optr, flags);
+	break;
+
+    case ST_RDEV:
+	statulprint((unsigned long)sbuf->st_rdev, optr);
+	break;
+
+    case ST_SIZE:
+	statulprint((unsigned long)sbuf->st_size, optr);
+	break;
+
+    case ST_ATIM:
+	stattimeprint(sbuf->st_atime, optr, flags);
+	break;
+
+    case ST_MTIM:
+	stattimeprint(sbuf->st_mtime, optr, flags);
+	break;
+
+    case ST_CTIM:
+	stattimeprint(sbuf->st_ctime, optr, flags);
+	break;
+
+    case ST_BLKSIZE:
+	statulprint((unsigned long)sbuf->st_blksize, optr);
+	break;
+
+    case ST_BLOCKS:
+	statulprint((unsigned long)sbuf->st_blocks, optr);
+	break;
+
+    case ST_READLINK:
+	statlinkprint(sbuf, optr, fname);
+	break;
+
+    case ST_COUNT:			/* keep some compilers happy */
+	break;
+    }
+}
+
+
+/*
+ *
+ * Options:
+ *  -f fd:   stat fd instead of file
+ *  -g:   use GMT rather than local time for time strings (forces -s on).
+ *  -n:   always print file name of file being statted
+ *  -N:   never print file name
+ *  -l:   list stat types
+ *  -L:   do lstat (else links are implicitly dereferenced by stat)
+ *  -t:   always print name of stat type
+ *  -T:   never print name of stat type
+ *  -r:   print raw alongside string data
+ *  -s:   string, print mode, times, uid, gid as appropriate strings:
+ *        harmless but unnecessary when combined with -r.
+ *  -A array:  assign results to given array, one stat result per element.
+ *        File names and type names are only added if explicitly requested:
+ *        file names are returned as a separate array element, type names as
+ *        prefix to element.  Note the formatting deliberately contains
+ *        fewer frills when -A is used.
+ *  -F fmt: specify a $TIME-like format for printing times; the default
+ *        is the (CTIME-like) "%a %b %e %k:%M:%S".  This option implies
+ *        -s as it is not useful for numerical times.
+ *
+ *  +type selects just element type of stat buffer (-l gives list):
+ *        type can be shortened to unambiguous string.  only one type is
+ *        allowed.  The extra type, +link, reads the target of a symbolic
+ *        link; it is empty if the stat was not an lstat or if 
+ *        a file descriptor was stat'd, if the stat'd file is
+ *        not a symbolic link, or if symbolic links are not
+ *        supported.  If +link is explicitly requested, the -L (lstat)
+ *        option is set automatically.
+ */
+/**/
+static int
+bin_stat(char *name, char **args, char *ops, int func)
+{
+    char **aptr, *arrnam = NULL, **array = NULL, **arrptr = NULL;
+    int len, iwhich = -1, ret = 0, flags = 0, arrsize = 0, fd = 0;
+    struct stat statbuf;
+    int found = 0, nargs;
+
+    timefmt = "%a %b %e %k:%M:%S";
+
+    for (; *args && (**args == '+' || **args == '-'); args++) {
+	char *arg = *args+1;
+	if (!*arg || *arg == '-' || *arg == '+') {
+	    args++;
+	    break;
+	}
+
+	if (**args == '+') {
+	    if (found)
+		break;
+	    len = strlen(arg);
+	    for (aptr = statelts; *aptr; aptr++)
+		if (!strncmp(*aptr, arg, len)) {
+		    found++;
+		    iwhich = aptr - statelts;
+		}
+	    if (found > 1) {
+		zerrnam(name, "%s: ambiguous stat element", arg, 0);
+		return 1;
+	    } else if (found == 0) {
+		zerrnam(name, "%s: no such stat element", arg, 0);
+		return 1;
+	    }
+	    /* if name of link requested, turn on lstat */
+	    if (iwhich == ST_READLINK)
+		ops['L'] = 1;
+	    flags |= STF_PICK;
+	} else {
+	    for (; *arg; arg++) {
+		if (strchr("glLnNrstT", *arg))
+		    ops[*arg] = 1;
+		else if (*arg == 'A') {
+		    if (arg[1]) {
+			arrnam = arg+1;
+		    } else if (!(arrnam = *++args)) {
+			zerrnam(name, "missing parameter name\n",
+				NULL, 0);
+			return 1;
+		    }
+		    flags |= STF_ARRAY;
+		    break;
+		} else if (*arg == 'f') {
+		    char *sfd;
+		    ops['f'] = 1;
+		    if (arg[1]) {
+			sfd = arg+1;
+		    } else if (!(sfd = *++args)) {
+			zerrnam(name, "missing file descriptor\n", NULL, 0);
+			return 1;
+		    }
+		    fd = zstrtol(sfd, &sfd, 10);
+		    if (*sfd) {
+			zerrnam(name, "bad file descriptor\n", NULL, 0);
+			return 1;
+		    }
+		    break;
+		} else if (*arg == 'F') {
+		    if (arg[1]) {
+			timefmt = arg+1;
+		    } else if (!(timefmt = *++args)) {
+			zerrnam(name, "missing time format\n", NULL, 0);
+			return 1;
+		    }
+		    /* force string format in order to use time format */
+		    ops['s'] = 1;
+		    break;
+		} else {
+		    zerrnam(name, "bad option: -%c", NULL, *arg);
+		    return 1;
+		}
+	    }
+	}
+    }
+
+    if (ops['l']) {
+	/* list types and return:  can also list to array */
+	if (arrnam) {
+	    arrptr = array = (char **)zalloc((ST_COUNT+1)*sizeof(char *));
+	    array[ST_COUNT] = NULL;
+	}
+	for (aptr = statelts; *aptr; aptr++) {
+	    if (arrnam) {
+		*arrptr++ = ztrdup(*aptr);
+	    } else {
+		printf("%s", *aptr);
+		if (aptr[1])
+		    putchar(' ');
+	    }
+	}
+	if (arrnam) {
+	    setaparam(arrnam, array);
+	    if (errflag)
+		return 1;
+	} else
+	    putchar('\n');
+	return 0;
+    }
+
+    if (!*args && !ops['f']) {
+	zwarnnam(name, "no files given", NULL, 0);
+	return 1;
+    } else if (*args && ops['f']) {
+	zwarnnam(name, "no files allowed with -f", NULL, 0);
+	return 1;
+    }
+
+    nargs = 0;
+    if (ops['f'])
+	nargs = 1;
+    else
+	for (aptr = args; *aptr; aptr++)
+	    nargs++;
+
+    if (ops['s'] || ops['r'])
+	flags |= STF_STRING;
+    if (ops['r'] || !ops['s'])
+	flags |= STF_RAW;
+    if (ops['n'])
+	flags |= STF_FILE;
+    if (ops['t'])
+	flags |= STF_NAME;
+    if (ops['g'])
+	flags |= STF_GMT;
+
+    if (!arrnam) {
+	if (nargs > 1)
+	    flags |= STF_FILE;
+	if (!(flags & STF_PICK))
+	    flags |= STF_NAME;
+    }
+
+    if (ops['N'] || ops['f'])
+	flags &= ~STF_FILE;
+    if (ops['T'])
+	flags &= ~STF_NAME;
+
+    if (arrnam) {
+	arrsize = (flags & STF_PICK) ? 1 : ST_COUNT;
+	if (flags & STF_FILE)
+	    arrsize++;
+	arrsize *= nargs;
+	arrptr = array = (char **)zcalloc((arrsize+1)*sizeof(char *));
+    }
+
+    for (; ops['f'] || *args; args++) {
+	char outbuf[PATH_MAX + 9]; /* "link   " + link name + NULL */
+	int rval = ops['f'] ? fstat(fd, &statbuf) :
+	    ops['L'] ? lstat(*args, &statbuf) : stat(*args, &statbuf);
+	if (rval) {
+	    if (ops['f'])
+		sprintf(outbuf, "%d", fd);
+	    zwarnnam(name, "%s: %e", ops['f'] ? outbuf : *args, errno);
+	    ret = 1;
+	    if (ops['f'] || arrnam)
+		break;
+	    else
+		continue;
+	}
+
+	if (flags & STF_FILE)
+	    if (arrnam)
+		*arrptr++ = ztrdup(*args);
+	    else
+		printf("%s%s", *args, (flags & STF_PICK) ? " " : ":\n");
+	if (iwhich > -1) {
+	    statprint(&statbuf, outbuf, *args, iwhich, flags);
+	    if (arrnam)
+		*arrptr++ = ztrdup(outbuf);
+	    else
+		printf("%s\n", outbuf);
+	} else {
+	    int i;
+	    for (i = 0; i < ST_COUNT; i++) {
+		statprint(&statbuf, outbuf, *args, i, flags);
+		if (arrnam)
+		    *arrptr++= ztrdup(outbuf);
+		else
+		    printf("%s\n", outbuf);
+	    }
+	}
+	if (ops['f'])
+	    break;
+
+	if (!arrnam && args[1] && !(flags & STF_PICK))
+	    putchar('\n');
+    }
+
+    if (arrnam)
+	if (ret) {
+	    for (aptr = array; *aptr; aptr++)
+		zsfree(*aptr);
+	    zfree(array, arrsize+1);
+	} else {
+	    setaparam(arrnam, array);
+	    if (errflag)
+		return 1;
+	}
+
+    return ret;
+}
+
+static struct builtin bintab[] = {
+    BUILTIN("stat", 0, bin_stat, 0, -1, 0, NULL, NULL),
+};
+
+/**/
+int
+boot_stat(Module m)
+{
+    return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_stat(Module m)
+{
+    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return 0;
+}
+
+#endif
diff --git a/Src/Modules/stat.mdd b/Src/Modules/stat.mdd
new file mode 100644
index 000000000..b775fda09
--- /dev/null
+++ b/Src/Modules/stat.mdd
@@ -0,0 +1,3 @@
+autobins="stat"
+
+objects="stat.o"
diff --git a/Src/Modules/zftp.c b/Src/Modules/zftp.c
new file mode 100644
index 000000000..ca0843419
--- /dev/null
+++ b/Src/Modules/zftp.c
@@ -0,0 +1,2596 @@
+/*
+ * zftp.c - builtin FTP client
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1998 Peter Stephenson
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Peter Stephenson or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Peter Stephenson and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Peter Stephenson and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Peter Stephenson and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+/*
+ * TODO:
+ *   can signal handling be improved?
+ *   error messages may need tidying up.
+ *   maybe we should block CTRL-c on some more operations,
+ *     otherwise you can get the connection closed prematurely.
+ *   some way of turning off progress reports when backgrounded
+ *     would be nice, but the shell doesn't make it easy to find that out.
+ *   the progress reports 100% a bit prematurely:  the data may still
+ *     be in transit, and we are stuck waiting for a message from the
+ *     server.  but there's really nothing else to do.  it's worst
+ *     with small files.
+ *   proxy/gateway connections if i knew what to do
+ *   options to specify e.g. a non-standard port
+ *   optimizing things out is hard in general when you don't know what
+ *     the shell's going to want, but they may be places to second guess
+ *     the user.  Some of the variables could be made special and so
+ *     only retrieve things like the current directory when necessary.
+ *     But it's much neater to have ordinary variables, which the shell
+ *     can manage without our interference, and getting the directory
+ *     just every time it changes isn't so bad.  The user can always
+ *     toggle the `Dumb' preference if it's feeling clever.
+ */
+#include "zftp.mdh"
+#include "zftp.pro"
+
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+/* it's a TELNET based protocol, but don't think I like doing this */
+#include <arpa/telnet.h>
+
+/* bet there are machines which have neither INADDR_NONE nor in_addr_t. */
+#ifndef INADDR_NONE
+#define INADDR_NONE (in_addr_t)-1
+#endif
+
+/*
+ * For FTP block mode
+ *
+ * The server on our AIX machine here happily accepts block mode, takes the
+ * first connection, then at the second complains that it's got nowhere
+ * to send data.  The same problem happens with ncftp, it's not just
+ * me.  And a lot of servers don't even support block mode. So I'm not sure
+ * how widespread the supposed ability to leave open the data fd between
+ * transfers.  Therefore, I've closed all connections after the transfer.
+ * But then what's the point in block mode?  I only implemented it because
+ * it says in RFC959 that you need it to be able to restart transfers
+ * later in the file.  However, it turns out that's not true for
+ * most servers --- but our AIX machine happily accepts the REST
+ * command and then dumps the whole file onto you.  Sigh.
+ *
+ * Note on block sizes:
+ * Not quite sure how to optimize this:  in principle
+ * we should handle blocks up to 65535 bytes, which
+ * is pretty big, and should presumably send blocks
+ * which are smaller to be on the safe side.
+ * Currently we send 32768 and use that also as
+ * the maximum to receive.  No-one's complained yet.  Of course,
+ * no-one's *used* it yet apart from me, but even so.
+ */
+
+struct zfheader {
+    char flags;
+    unsigned char bytes[2];
+};
+
+enum {
+    ZFHD_MARK = 16,		/* restart marker */
+    ZFHD_ERRS = 32,		/* suspected errors in block */
+    ZFHD_EOFB = 64,		/* block is end of record */
+    ZFHD_EORB = 128		/* block is end of file */
+};
+
+typedef int (*readwrite_t)(int, char *, size_t, int);
+
+struct zftpcmd {
+    const char *nam;
+    int (*fun) _((char *, char **, int));
+    int min, max, flags;
+};
+
+enum {
+    ZFTP_CONN  = 0x0001,	/* must be connected */
+    ZFTP_LOGI  = 0x0002,	/* must be logged in */
+    ZFTP_TBIN  = 0x0004,	/* set transfer type image */
+    ZFTP_TASC  = 0x0008,	/* set transfer type ASCII */
+    ZFTP_NLST  = 0x0010,	/* use NLST rather than LIST */
+    ZFTP_DELE  = 0x0020,	/* a delete rather than a make */
+    ZFTP_SITE  = 0x0040,	/* a site rather than a quote */
+    ZFTP_APPE  = 0x0080,	/* append rather than overwrite */
+    ZFTP_HERE  = 0x0100,	/* here rather than over there */
+    ZFTP_CDUP  = 0x0200,	/* CDUP rather than CWD */
+    ZFTP_REST  = 0x0400,	/* restart: set point in remote file */
+    ZFTP_RECV  = 0x0800		/* receive rather than send */
+};
+
+typedef struct zftpcmd *Zftpcmd;
+
+static struct zftpcmd zftpcmdtab[] = {
+    { "open", zftp_open, 0, 4, 0 },
+    { "params", zftp_params, 0, 4, 0 },
+    { "login", zftp_login, 0, 3, ZFTP_CONN },
+    { "user", zftp_login, 0, 3, ZFTP_CONN },
+    { "cd", zftp_cd, 1, 1, ZFTP_CONN|ZFTP_LOGI },
+    { "cdup", zftp_cd, 0, 0, ZFTP_CONN|ZFTP_LOGI|ZFTP_CDUP },
+    { "dir", zftp_dir, 0, -1, ZFTP_CONN|ZFTP_LOGI },
+    { "ls", zftp_dir, 0, -1, ZFTP_CONN|ZFTP_LOGI|ZFTP_NLST },
+    { "type", zftp_type, 0, 1, ZFTP_CONN|ZFTP_LOGI },
+    { "ascii", zftp_type, 0, 0, ZFTP_CONN|ZFTP_LOGI|ZFTP_TASC },
+    { "binary", zftp_type, 0, 0, ZFTP_CONN|ZFTP_LOGI|ZFTP_TBIN },
+    { "mode", zftp_mode, 0, 1, ZFTP_CONN|ZFTP_LOGI },
+    { "local", zftp_local, 0, -1, ZFTP_HERE },
+    { "remote", zftp_local, 1, -1, ZFTP_CONN|ZFTP_LOGI },
+    { "get", zftp_getput, 1, -1, ZFTP_CONN|ZFTP_LOGI|ZFTP_RECV },
+    { "getat", zftp_getput, 2, 2, ZFTP_CONN|ZFTP_LOGI|ZFTP_RECV|ZFTP_REST },
+    { "put", zftp_getput, 1, -1, ZFTP_CONN|ZFTP_LOGI },
+    { "putat", zftp_getput, 2, 2, ZFTP_CONN|ZFTP_LOGI|ZFTP_REST },
+    { "append", zftp_getput, 1, -1, ZFTP_CONN|ZFTP_LOGI|ZFTP_APPE },
+    { "appendat", zftp_getput, 2, 2, ZFTP_CONN|ZFTP_LOGI|ZFTP_APPE|ZFTP_REST },
+    { "delete", zftp_delete, 1, -1, ZFTP_CONN|ZFTP_LOGI },
+    { "mkdir", zftp_mkdir, 1, 1, ZFTP_CONN|ZFTP_LOGI },
+    { "rmdir", zftp_mkdir, 1, 1, ZFTP_CONN|ZFTP_LOGI|ZFTP_DELE },
+    { "rename", zftp_rename, 2, 2, ZFTP_CONN|ZFTP_LOGI },
+    { "quote", zftp_quote, 1, -1, ZFTP_CONN },
+    { "site", zftp_quote, 1, -1, ZFTP_CONN|ZFTP_SITE },
+    { "close", zftp_close, 0, 0, ZFTP_CONN },
+    { "quit", zftp_close, 0, 0, ZFTP_CONN },
+    { 0, 0, 0, 0}
+};
+
+static struct builtin bintab[] = {
+    BUILTIN("zftp", 0, bin_zftp, 1, -1, 0, NULL, NULL),
+};
+
+/*
+ * these are the non-special params to unset when a connection
+ * closes.  any special params are handled, well, specially.
+ * currently there aren't any, which is the way I like it.
+ */
+static char *zfparams[] = {
+    "ZFTP_HOST", "ZFTP_IP", "ZFTP_SYSTEM", "ZFTP_USER",
+    "ZFTP_ACCOUNT", "ZFTP_PWD", "ZFTP_TYPE", "ZFTP_MODE", NULL
+};
+
+/* flags for zfsetparam */
+
+enum {
+    ZFPM_READONLY = 0x01,	/* make parameter readonly */
+    ZFPM_IFUNSET  = 0x02,	/* only set if not already set */
+    ZFPM_INTEGER  = 0x04	/* passed pointer to long */
+};
+
+/*
+ * Basic I/O variables for control connection:
+ * zcfd != -1 is a flag that there is a connection open.
+ */
+static int zcfd = -1;
+static FILE *zcin;
+static struct sockaddr_in zsock;
+
+/*
+ * zcfinish = 0 keep going
+ *            1 line finished, alles klar
+ *            2 EOF
+ */
+static int zcfinish;
+/* zfclosing is set if zftp_close() is active */
+static int zfclosing;
+
+/*
+ * Now stuff for data connection
+ */
+static int zdfd = -1;
+static struct sockaddr_in zdsock;
+
+/*
+ * Stuff about last message:  last line of message and status code.
+ * The reply is also stored in $ZFTP_REPLY; we keep these separate
+ * for convenience.
+ */
+static char *lastmsg, lastcodestr[4];
+static int lastcode;
+
+/* flag for remote system is UNIX --- useful to know as things are simpler */
+static int zfis_unix, zfpassive_conn;
+
+/* remote system has size, mdtm commands */
+enum {
+    ZFCP_UNKN = 0,		/* dunno if it works on this server */
+    ZFCP_YUPP = 1,		/* it does */
+    ZFCP_NOPE = 2		/* it doesn't */
+};
+
+static int zfhas_size, zfhas_mdtm;
+
+/*
+ * We keep an fd open for communication between the main shell
+ * and forked off bits and pieces.  This allows us to know
+ * if something happend in a subshell:  mode changed, type changed,
+ * connection was closed.  If something too substantial happened
+ * in a subshell --- connection opened, ZFTP_USER and ZFTP_PWD changed
+ * --- we don't try to track it because it's too complicated.
+ */
+enum {
+    ZFST_ASCI = 0x00,		/* type for next transfer is ASCII */
+    ZFST_IMAG = 0x01,		/* type for next transfer is image */
+
+    ZFST_TMSK = 0x01,		/* mask for type flags */
+    ZFST_TBIT = 0x01,		/* number of bits in type flags */
+
+    ZFST_CASC = 0x00,		/* current type is ASCII - default */
+    ZFST_CIMA = 0x02,		/* current type is image */
+
+    ZFST_STRE = 0x00,		/* stream mode - default */
+    ZFST_BLOC = 0x04,		/* block mode */
+
+    ZFST_MMSK = 0x04,		/* mask for mode flags */
+
+    ZFST_LOGI = 0x08,		/* user logged in */
+    ZFST_NOPS = 0x10,		/* server doesn't understand PASV */
+    ZFST_NOSZ = 0x20,		/* server doesn't send `(XXXX bytes)' reply */
+    ZFST_TRSZ = 0x40,		/* tried getting 'size' from reply */
+    ZFST_CLOS = 0x80		/* connection closed */
+};
+#define ZFST_TYPE(x) (x & ZFST_TMSK)
+/*
+ * shift current type flags to match type flags: should be by
+ * the number of bits in the type flags
+ */
+#define ZFST_CTYP(x) ((x >> ZFST_TBIT) & ZFST_TMSK)
+#define ZFST_MODE(x) (x & ZFST_MMSK)
+
+static int zfstatfd = -1, zfstatus;
+
+/* Preferences, read in from the `zftp_prefs' array variable */
+enum {
+    ZFPF_SNDP = 0x01,		/* Use send port mode */
+    ZFPF_PASV = 0x02,		/* Try using passive mode */
+    ZFPF_DUMB = 0x04		/* Don't do clever things with variables */
+};
+
+/* The flags as stored internally. */
+int zfprefs;
+
+
+/* zfuserparams is the storage area for zftp_params() */
+char **zfuserparams;
+
+/*
+ * Bits and pieces for dealing with SIGALRM (and SIGPIPE, but that's
+ * easier).  The complication is that SIGALRM may already be handled
+ * by the user setting TMOUT and possibly setting their own trap --- in
+ * fact, it's always handled by the shell when it's interactive.  It's
+ * too difficult to use zsh's own signal handler --- either it would
+ * need rewriting to use a C function as a trap, or we would need a
+ * hack to make it callback via a hidden builtin from a function --- so
+ * just install our own, and use settrap() to restore the behaviour
+ * afterwards if necessary.  However, the more that could be done by
+ * the main shell code, the better I would like it.
+ *
+ * Since we don't want to go through the palaver of changing between
+ * the main zsh signal handler and ours every time we start or stop the
+ * alarm, we keep the flag zfalarmed set to 1 while zftp is rigged to
+ * handle alarms.  This is tested at the end of bin_zftp(), which is
+ * the entry point for all functions, and that restores the original
+ * handler for SIGALRM.  To turn off the alarm temporarily in the zftp
+ * code we then just call alarm(0).
+ *
+ * If we could rely on having select() or some replacement, we would
+ * only need the alarm during zftp_open().
+ */
+
+/* flags for alarm set, alarm gone off */
+int zfalarmed, zfdrrrring;
+/* remember old alarm status */
+time_t oaltime;
+unsigned int oalremain;
+
+/*
+ * Where to jump to when the alarm goes off.  This is much
+ * easier than fiddling with error flags at every turn.
+ * Since we don't expect too many alarm's, the simple setjmp()
+ * mechanism should be good enough.
+ *
+ * gcc -O gives apparently spurious `may be clobbered by longjmp' warnings.
+ */
+jmp_buf zfalrmbuf;
+
+/* The signal handler itself */
+
+/**/
+static RETSIGTYPE
+zfhandler(int sig)
+{
+    if (sig == SIGALRM) {
+	zfdrrrring = 1;
+#ifdef ETIMEDOUT		/* just in case */
+	errno = ETIMEDOUT;
+#else
+	errno = EIO;
+#endif
+	longjmp(zfalrmbuf, 1);
+    }
+    DPUTS(1, "zfhandler caught incorrect signal");
+}
+
+/* Set up for an alarm call */
+
+/**/
+static void
+zfalarm(int tmout)
+{
+    zfdrrrring = 0;
+    /*
+     * We need to do this even if tmout is zero, since there may
+     * be a non-zero timeout set in the main shell which we don't
+     * want to go off.  This could be argued the other way, since
+     * if we don't get that it's probably harmless.  But this looks safer.
+     */
+    if (zfalarmed) {
+	alarm(tmout);
+	return;
+    }
+    signal(SIGALRM, zfhandler);
+    oalremain = alarm(tmout);
+    if (oalremain)
+	oaltime = time(NULL);
+    /*
+     * We'll leave sigtrapped, sigfuncs and TRAPXXX as they are; since the
+     * shell's handler doesn't get the signal, they don't matter.
+     */
+    zfalarmed = 1;
+}
+
+/* Set up for a broken pipe */
+
+/**/
+static void
+zfpipe()
+{
+    /* Just ignore SIGPIPE and rely on getting EPIPE from the write. */
+    signal(SIGPIPE, SIG_IGN);
+}
+
+/* Unset the alarm, see above */
+
+/**/
+static void
+zfunalarm()
+{
+    if (oalremain) {
+	/*
+	 * The alarm was previously set, so set it back, adjusting
+	 * for the time used.  Mostly the alarm was set to zero
+	 * beforehand, so it would probably be best to reinstall
+	 * the proper signal handler before resetting the alarm.
+	 *
+	 * I love the way alarm() uses unsigned int while time_t
+	 * is probably something completely different.
+	 */
+	time_t tdiff = time(NULL) - oaltime;
+	alarm(oalremain < tdiff ? 1 : oalremain - tdiff);
+    } else
+	alarm(0);
+    if (sigtrapped[SIGALRM] || interact) {
+	if (sigfuncs[SIGALRM] || !sigtrapped[SIGALRM])
+	    install_handler(SIGALRM);
+	else
+	    signal_ignore(SIGALRM);
+    } else
+	signal_default(SIGALRM);
+    zfalarmed = 0;
+}
+
+/* Restore SIGPIPE handling to its usual status */
+
+/**/
+static void
+zfunpipe()
+{
+    if (sigtrapped[SIGPIPE]) {
+	if (sigfuncs[SIGPIPE])
+	    install_handler(SIGPIPE);
+	else
+	    signal_ignore(SIGPIPE);
+    } else
+	signal_default(SIGPIPE);
+}
+
+/*
+ * Same as movefd(), but don't mark the fd in the zsh tables,
+ * because we only want it closed by zftp.  However, we still
+ * need to shift the fd's out of the way of the user-visible 0-9.
+ */
+
+/**/
+static int
+zfmovefd(int fd)
+{
+    if (fd != -1 && fd < 10) {
+#ifdef F_DUPFD
+	int fe = fcntl(fd, F_DUPFD, 10);
+#else
+	int fe = zfmovefd(dup(fd));
+#endif
+	close(fd);
+	fd = fe;
+    }
+    return fd;
+}
+
+/*
+ * set a non-special parameter.
+ * if ZFPM_IFUNSET, don't set if it already exists.
+ * if ZFPM_READONLY, make it readonly, but only when creating it.
+ * if ZFPM_INTEGER, val pointer is to long (NB not int), don't free.
+ */
+/**/
+static void
+zfsetparam(char *name, void *val, int flags)
+{
+    Param pm = NULL;
+    int type = (flags & ZFPM_INTEGER) ? PM_INTEGER : PM_SCALAR;
+
+    if (!(pm = (Param) paramtab->getnode(paramtab, name))
+	|| (pm->flags & PM_UNSET)) {
+	/*
+	 * just make it readonly when creating, in case user
+	 * *really* knows what they're doing
+	 */
+	if ((pm = createparam(name, type)) && (flags & ZFPM_READONLY))
+	    pm->flags |= PM_READONLY;
+    } else if (flags & ZFPM_IFUNSET) {
+	pm = NULL;
+    }
+    if (!pm || PM_TYPE(pm->flags) != type) {
+	/* parameters are funny, you just never know */
+	if (type == PM_SCALAR)
+	    zsfree((char *)val);
+	return;
+    }
+    if (type == PM_INTEGER)
+	pm->sets.ifn(pm, *(long *)val);
+    else
+	pm->sets.cfn(pm, (char *)val);
+}
+
+/*
+ * Unset a ZFTP parameter when the connection is closed.
+ * We only do this with connection-specific parameters.
+ */
+
+/**/
+static void
+zfunsetparam(char *name)
+{
+    Param pm;
+
+    if ((pm = (Param) paramtab->getnode(paramtab, name))) {
+	pm->flags &= ~PM_READONLY;
+	unsetparam_pm(pm, 0, 1);
+    }
+}
+
+/*
+ * Join command and arguments to make a proper TELNET command line.
+ * New line is in permanent storage.
+ */
+
+/**/
+static char *
+zfargstring(char *cmd, char **args)
+{
+    int clen = strlen(cmd) + 3;
+    char *line, **aptr;
+
+    for (aptr = args; *aptr; aptr++)
+	clen += strlen(*aptr) + 1;
+    line = zalloc(clen);
+    strcpy(line, cmd);
+    for (aptr = args; *aptr; aptr++) {
+	strcat(line, " ");
+	strcat(line, *aptr);
+    }
+    strcat(line, "\r\n");
+
+    return line;
+}
+
+/*
+ * get a line on the control connection according to TELNET rules
+ * Return status is first digit of FTP reply code
+ */
+
+/**/
+static int
+zfgetline(char *ln, int lnsize, int tmout)
+{
+    int ch, added = 0;
+    /* current line point */
+    char *pcur = ln, cmdbuf[3];
+
+    zcfinish = 0;
+    /* leave room for null byte */
+    lnsize--;
+    /* in case we return unexpectedly before getting anything */
+    ln[0] = '\0';
+
+    if (setjmp(zfalrmbuf)) {
+	alarm(0);
+	zwarnnam("zftp", "timeout getting response", NULL, 0);
+	return 5;
+    }
+    zfalarm(tmout);
+
+    /*
+     * We need to be more careful about errors here; we
+     * should do the stuff with errflag and so forth.
+     * We should probably holdintr() here, since if we don't
+     * get the message, the connection is going to be messed up.
+     * But then we get `frustrated user syndrome'.
+     */
+    for (;;) {
+	ch = fgetc(zcin);
+
+	switch(ch) {
+	case EOF:
+	    if (ferror(zcin) && errno == EINTR) {
+		clearerr(zcin);
+		continue;
+	    }
+	    zcfinish = 2;
+	    break;
+
+	case '\r':
+	    /* always precedes something else */
+	    ch = fgetc(zcin);
+	    if (ch == EOF) {
+		zcfinish = 2;
+		break;
+	    }
+	    if (ch == '\n') {
+		zcfinish = 1;
+		break;
+	    }
+	    if (ch == '\0') {
+		ch = '\r';
+		break;
+	    }
+	    /* not supposed to get here */
+	    ch = '\r';
+	    break;
+
+	case '\n':
+	    /* not supposed to get here */
+	    zcfinish = 1;
+	    break;
+
+	case IAC:
+	    /*
+	     * oh great, now it's sending TELNET commands.  try
+	     * to persuade it not to.
+	     */
+	    ch = fgetc(zcin);
+	    switch (ch) {
+	    case WILL:
+	    case WONT:
+		ch = fgetc(zcin);
+		/* whatever it wants to do, stop it. */
+		cmdbuf[0] = (char)IAC;
+		cmdbuf[1] = (char)DONT;
+		cmdbuf[2] = ch;
+		write(zcfd, cmdbuf, 3);
+		continue;
+
+	    case DO:
+	    case DONT:
+		ch = fgetc(zcin);
+		/* well, tough, we're not going to. */
+		cmdbuf[0] = (char)IAC;
+		cmdbuf[1] = (char)WONT;
+		cmdbuf[2] = ch;
+		write(zcfd, cmdbuf, 3);
+		continue;
+
+	    case EOF:
+		/* strange machine. */
+		zcfinish = 2;
+		break;
+
+	    default:
+		break;
+	    }
+	    break;
+	}
+	
+	if (zcfinish)
+	    break;
+	if (added < lnsize) {
+	    *pcur++ = ch;
+	    added++;
+	}
+	/* junk it if we don't have room, but go on reading */
+    }
+
+    alarm(0);
+
+    *pcur = '\0';
+    /* if zcfinish == 2, at EOF, return that, else 0 */
+    return (zcfinish & 2);
+}
+
+/*
+ * Get a whole message from the server.  A dash after
+ * the first line code means keep reading until we get
+ * a line with the same code followed by a space.
+ *
+ * Note that this returns an FTP status code, the first
+ * digit of the reply.  There is also a pseudocode, 6, which
+ * means `there's no point trying anything, just yet'.
+ * We return it either if the connection is closed, or if
+ * we got a 530 (user not logged in), in which case whatever
+ * you're trying to do isn't going to work.
+ */
+
+/**/
+static int 
+zfgetmsg()
+{
+    char line[256], *ptr, *verbose;
+    int stopit, printing = 0, tmout;
+
+    if (zcfd == -1)
+	return 5;
+    if (!(verbose = getsparam("ZFTP_VERBOSE")))
+	verbose = "";
+    zsfree(lastmsg);
+    lastmsg = NULL;
+
+    tmout = getiparam("ZFTP_TMOUT");
+
+    zfgetline(line, 256, tmout);
+    ptr = line;
+    if (zfdrrrring || !isdigit((int)*ptr) || !isdigit((int)ptr[1]) || 
+	!isdigit((int)ptr[2])) {
+	/* timeout, or not talking FTP.  not really interested. */
+	zcfinish = 2;
+	if (!zfclosing)
+	    zfclose();
+	lastmsg = ztrdup("");
+	strcpy(lastcodestr, "000");
+	zfsetparam("ZFTP_REPLY", ztrdup(lastmsg), ZFPM_READONLY);
+	return 6;
+    }
+    strncpy(lastcodestr, ptr, 3);
+    ptr += 3;
+    lastcodestr[3] = '\0';
+    lastcode = atoi(lastcodestr);
+    zfsetparam("ZFTP_CODE", ztrdup(lastcodestr), ZFPM_READONLY);
+    stopit = (*ptr++ != '-');
+
+    if (strchr(verbose, lastcodestr[0])) {
+	/* print the whole thing verbatim */
+	printing = 1;
+	fputs(line, stderr);
+    }  else if (strchr(verbose, '0') && !stopit) {
+	/* print multiline parts with the code stripped */
+	printing = 2;
+	fputs(ptr, stderr);
+    }
+    if (printing)
+	fputc('\n', stderr);
+
+    while (zcfinish != 2 && !stopit) {
+	zfgetline(line, 256, tmout);
+	ptr = line;
+	if (zfdrrrring) {
+	    line[0] = '\0';
+	    break;
+	}
+
+	if (!strncmp(lastcodestr, line, 3)) {
+	    if (line[3] == ' ') {
+		stopit = 1;
+		ptr += 4;
+	    } else if (line[3] == '-')
+		ptr += 4;
+	} else if (!strncmp("    ", line, 4))
+	    ptr += 4;
+
+	if (printing == 2) {
+	    if (!stopit) {
+		fputs(ptr, stderr);
+		fputc('\n', stderr);
+	    }
+	} else if (printing) {
+	    fputs(line, stderr);
+	    fputc('\n', stderr);
+	}
+    }
+
+    if (printing)
+	fflush(stderr);
+
+    /* The internal message is just the text. */
+    lastmsg = ztrdup(ptr);
+    /*
+     * The parameter is the whole thing, including the code.
+     */
+    zfsetparam("ZFTP_REPLY", ztrdup(line), ZFPM_READONLY);
+    /*
+     * close the connection here if zcfinish == 2, i.e. EOF,
+     * or if we get a 421 (service not available, closing connection),
+     * but don't do it if it's expected (zfclosing set).
+     */
+    if ((zcfinish == 2 || lastcode == 421) && !zfclosing) {
+	zcfinish = 2;		/* don't need to tell server */
+	zfclose();
+	/* unexpected, so tell user */
+	zwarnnam("zftp", "remote server has closed connection", NULL, 0);
+	return 6;		/* pretend it failed, because it did */
+    }
+    if (lastcode == 530) {
+	/* user not logged in */
+	return 6;
+    }
+    /*
+     * May as well handle this here, though it's pretty uncommon.
+     * A 120 is something like "service ready in nnn minutes".
+     * It means we just hang around waiting for another reply.
+     */
+    if (lastcode == 120) {
+	zwarnnam("zftp", "delay expected, waiting: %s", lastmsg, 0);
+	return zfgetmsg();
+    }
+
+    /* first digit of code determines success, failure, not in the mood... */
+    return lastcodestr[0] - '0';
+}
+
+
+/*
+ * Send a command and get the reply.
+ * The command is expected to have the \r\n already tacked on.
+ * Returns the status code for the reply.
+ */
+
+/**/
+static int
+zfsendcmd(char *cmd)
+{
+    /*
+     * We use the fd directly; there's no point even using
+     * stdio with line buffering, since we always send the
+     * complete line in one string anyway.
+     */
+    int ret, tmout;
+
+    if (zcfd == -1)
+	return 5;
+    tmout = getiparam("ZFTP_TMOUT");
+    if (setjmp(zfalrmbuf)) {
+	alarm(0);
+	zwarnnam("zftp", "timeout sending message", NULL, 0);
+	return 5;
+    }
+    zfalarm(tmout);
+    ret = write(zcfd, cmd, strlen(cmd));
+    alarm(0);
+
+    if (ret <= 0) {
+	zwarnnam("zftp send", "failed sending control message", NULL, 0);
+	return 5;		/* FTP status code */
+    }
+
+    return zfgetmsg();
+}
+
+
+/* Set up a data connection, return 1 for failure, 0 for success */
+
+/**/
+static int
+zfopendata(char *name)
+{
+    if (!(zfprefs & (ZFPF_SNDP|ZFPF_PASV))) {
+	zwarnnam(name, "Must set preference S or P to transfer data", NULL, 0);
+	return 1;
+    }
+    zdfd = zfmovefd(socket(AF_INET, SOCK_STREAM, 0));
+    if (zdfd < 0) {
+	zwarnnam(name, "can't get data socket: %e", NULL, errno);
+	return 1;
+    }
+
+    zdsock = zsock;
+    zdsock.sin_family = AF_INET;
+
+    if (!(zfstatus & ZFST_NOPS) && (zfprefs & ZFPF_PASV)) {
+	char *ptr;
+	int i, nums[6], err;
+	unsigned char iaddr[4], iport[2];
+
+	if (zfsendcmd("PASV\r\n") == 6)
+	    return 1;
+	else if (lastcode >= 500 && lastcode <= 504) {
+	    /*
+	     * Fall back to send port mode.  That will
+	     * test the preferences for whether that's OK.
+	     */
+	    zfstatus |= ZFST_NOPS;
+	    zfclosedata();
+	    return zfopendata(name);
+	}
+	/*
+	 * OK, now we need to know what port we're looking at,
+	 * which is cunningly concealed in the reply.
+	 * lastmsg already has the reply code expunged.
+	 */
+	for (ptr = lastmsg; *ptr; ptr++)
+	    if (isdigit(*ptr))
+		break;
+	if (sscanf(ptr, "%d,%d,%d,%d,%d,%d",
+		   nums, nums+1, nums+2, nums+3, nums+4, nums+5) != 6) {
+	    zwarnnam(name, "bad response to PASV: %s", lastmsg, 0);
+	    zfclosedata();
+	    return 1;
+	}
+	for (i = 0; i < 4; i++)
+	    iaddr[i] = STOUC(nums[i]);
+	iport[0] = STOUC(nums[4]);
+	iport[1] = STOUC(nums[5]);
+
+	memcpy(&zdsock.sin_addr, iaddr, sizeof(iaddr));
+	memcpy(&zdsock.sin_port, iport, sizeof(iport));
+
+	/* we should timeout this connect */
+	do {
+	    err = connect(zdfd, (struct sockaddr *)&zdsock, sizeof(zdsock));
+	} while (err && errno == EINTR && !errflag);
+
+	if (err) {
+	    zwarnnam(name, "connect failed: %e", NULL, errno);
+	    zfclosedata();
+	    return 1;
+	}
+
+	zfpassive_conn = 1;
+    } else {
+	char portcmd[40];
+	unsigned char *addr, *port;
+	int ret, len;
+
+	if (!(zfprefs & ZFPF_SNDP)) {
+	    zwarnnam(name, "only sendport mode available for data", NULL, 0);
+	    return 1;
+	}
+
+	zdsock.sin_port = 0;	/* to be set by bind() */
+	len = sizeof(zdsock);
+	/* need to do timeout stuff and probably handle EINTR here */
+	if (bind(zdfd, (struct sockaddr *)&zdsock, sizeof(zdsock)) < 0)
+	    ret = 1;
+	else if (getsockname(zdfd, (struct sockaddr *)&zdsock, &len) < 0)
+	    ret = 2;
+	else if (listen(zdfd, 1) < 0)
+	    ret = 3;
+	else
+	    ret = 0;
+
+	if (ret) {
+	    zwarnnam(name, "failure on data socket: %s: %e",
+		     ret == 3 ? "listen" : ret == 2 ? "getsockname" : "bind",
+		     errno);
+	    zfclosedata();
+	    return 1;
+	}
+
+	addr = (unsigned char *) &zdsock.sin_addr;
+	port = (unsigned char *) &zdsock.sin_port;
+	sprintf(portcmd, "PORT %d,%d,%d,%d,%d,%d\r\n",
+		addr[0],addr[1],addr[2],addr[3],port[0],port[1]);
+	if (zfsendcmd(portcmd) >= 5) {
+	    zwarnnam(name, "port command failed", NULL, 0);
+	    zfclosedata();
+	    return 1;
+	}
+	zfpassive_conn = 0;
+    }
+
+    return 0;
+}
+
+/* Close the data connection. */
+
+/**/
+static void
+zfclosedata(void)
+{
+    if (zdfd == -1)
+	return;
+    close(zdfd);
+    zdfd = -1;
+}
+
+/*
+ * Set up a data connection and use cmd to initiate a transfer.
+ * The actual data fd will be zdfd; the calling routine
+ * must handle the data itself.
+ * rest is a REST command to specify starting somewhere other
+ * then the start of the remote file.
+ * getsize is non-zero if we want to try to find the number
+ * of bytes in the reply to a RETR command.
+ *
+ * Return 0 on success, 1 on failure.
+ */
+
+/**/
+static int
+zfgetdata(char *name, char *rest, char *cmd, int getsize)
+{
+    int len, newfd;
+
+    if (zfopendata(name))
+	return 1;
+
+    /*
+     * Set position in remote file for get/put.
+     * According to RFC959, the restart command needs something
+     * called a marker which has previously been put into the data.
+     * Luckily for the real world, UNIX machines just interpret this
+     * as an offset into the byte stream.
+     *
+     * This has to be sent immediately before the data transfer, i.e.
+     * after all mucking around with types and sizes and so on.
+     */
+    if (rest && zfsendcmd(rest) > 3) {
+	zfclosedata();
+	return 1;
+    }
+
+    if (zfsendcmd(cmd) > 2) {
+	zfclosedata();
+	return 1;
+    }
+    if (getsize || (!(zfstatus & ZFST_TRSZ) && !strncmp(cmd, "RETR", 4))) {
+	/*
+	 * See if we got something like:
+	 *   Opening data connection for nortypix.gif (1234567 bytes).
+	 * On the first RETR, always see if this works,  Then we
+	 * can avoid sending a special SIZE command beforehand.
+	 */
+	char *ptr = strstr(lastmsg, "bytes");
+	zfstatus |= ZFST_NOSZ|ZFST_TRSZ;
+	if (ptr) {
+	    while (ptr > lastmsg && !isdigit(*ptr))
+		ptr--;
+	    while (ptr > lastmsg && isdigit(ptr[-1]))
+		ptr--;
+	    if (isdigit(*ptr)) {
+		zfstatus &= ~ZFST_NOSZ;
+		if (getsize) {
+		    long sz = zstrtol(ptr, NULL, 10);
+		    zfsetparam("ZFTP_SIZE", &sz, ZFPM_READONLY|ZFPM_INTEGER);
+		}
+	    }
+	}
+    }
+
+    if (!zfpassive_conn) {
+	/*
+	 * the current zdfd is the socket we opened, but we need
+	 * to let the server set up a different fd for reading/writing.
+	 * then we can close the fd we were listening for a connection on.
+	 * don't expect me to understand this, i'm only the programmer.
+	 */
+
+	/* accept the connection */
+	len = sizeof(zdsock);
+	newfd = zfmovefd(accept(zdfd, (struct sockaddr *)&zdsock, &len));
+	zfclosedata();
+	if (newfd < 0) {
+	    zwarnnam(name, "unable to accept data.", NULL, 0);
+	    return 1;
+	}
+	zdfd = newfd;		/* this is now the actual data fd */
+    }
+
+
+    /* more options, just to look professional */
+#ifdef SO_LINGER
+    /*
+     * Since data can take arbitrary amounts of time to arrive,
+     * the socket can be made to hang around until it doesn't think
+     * anything is arriving.
+     *
+     * In BSD 4.3, you could only linger for infinity.  Don't
+     * know if this has changed.
+     */
+    {
+	struct linger li;
+
+	li.l_onoff = 1;
+	li.l_linger = 120;
+	setsockopt(zdfd, SOL_SOCKET, SO_LINGER, (char *)&li, sizeof(li));
+    }
+#endif
+#if defined(IP_TOS) && defined(IPTOS_THROUGHPUT)
+    /* try to get high throughput, snigger */
+    {
+	int arg = IPTOS_THROUGHPUT;
+	setsockopt(zdfd, IPPROTO_IP, IP_TOS, (char *)&arg, sizeof(arg));
+    }
+#endif
+#if defined(F_SETFD) && defined(FD_CLOEXEC)
+	/* If the shell execs a program, we don't want this fd left open. */
+	len = FD_CLOEXEC;
+	fcntl(zdfd, F_SETFD, &len);
+#endif
+
+    return 0;
+}
+
+/*
+ * Find out about a local or remote file and pass back the information.
+ *
+ * We could jigger this to use ls like ncftp does as a backup.
+ * But if the server is non-standard enough not to have SIZE and MDTM,
+ * there's a very good chance ls -l isn't going to do great things.
+ *
+ * if fd is >= 0, it is used for an fstat when remote is zero:
+ * this is because on a put we are taking input from fd 0.
+ */
+
+/**/
+static int
+zfstats(char *fnam, int remote, long *retsize, char **retmdtm, int fd)
+{
+    long sz = -1;
+    char *mt = NULL;
+    int ret;
+
+    if (retsize)
+	*retsize = -1;
+    if (retmdtm)
+	*retmdtm = NULL;
+    if (remote) {
+	char *cmd;
+	if ((zfhas_size == ZFCP_NOPE && retsize) ||
+	    (zfhas_mdtm == ZFCP_NOPE && retmdtm))
+	    return 2;
+
+	/*
+	 * File is coming from over there.
+	 * Make sure we get the type right.
+	 */
+	zfsettype(ZFST_TYPE(zfstatus));
+	if (retsize) {
+	    cmd = tricat("SIZE ", fnam, "\r\n");
+	    ret = zfsendcmd(cmd);
+	    zsfree(cmd);
+	    if (ret == 6)
+		return 1;
+	    else if (lastcode < 300) {
+		sz = zstrtol(lastmsg, 0, 10);
+		zfhas_size = ZFCP_YUPP;
+	    } else if (lastcode >= 500 && lastcode <= 504) {
+		zfhas_size = ZFCP_NOPE;
+		return 2;
+	    } else if (lastcode == 550)
+		return 1;
+	    /* if we got a 550 from SIZE, the file doesn't exist */
+	}
+
+	if (retmdtm) {
+	    cmd = tricat("MDTM ", fnam, "\r\n");
+	    ret = zfsendcmd(cmd);
+	    zsfree(cmd);
+	    if (ret == 6)
+		return 1;
+	    else if (lastcode < 300) {
+		mt = ztrdup(lastmsg);
+		zfhas_mdtm = ZFCP_YUPP;
+	    } else if (lastcode >= 500 && lastcode <= 504) {
+		zfhas_mdtm = ZFCP_NOPE;
+		return 2;
+	    } else if (lastcode == 550)
+		return 1;
+	}
+    } else {
+	/* File is over here */
+	struct stat statbuf;
+	struct tm *tm;
+	char tmbuf[20];
+
+	if ((fd == -1 ? stat(fnam, &statbuf) : fstat(fd, &statbuf)) < 0)
+	    return 1;
+	/* make sure it's long, since this has to be a pointer */
+	sz = statbuf.st_size;
+
+	if (retmdtm) {
+	    /* use gmtime() rather than localtime() for consistency */
+	    tm = gmtime(&statbuf.st_mtime);
+	    /*
+	     * FTP format for data is YYYYMMDDHHMMSS
+	     * Using tm directly is easier than worrying about
+	     * incompatible strftime()'s.
+	     */
+	    sprintf(tmbuf, "%04d%02d%02d%02d%02d%02d",
+		    tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday,
+		    tm->tm_hour, tm->tm_min, tm->tm_sec);
+	    mt = ztrdup(tmbuf);
+	}
+    }
+    if (retsize)
+	*retsize = sz;
+    if (retmdtm)
+	*retmdtm = mt;
+    return 0;
+}
+
+/* Set parameters to say what's coming */
+
+/**/
+static void
+zfstarttrans(char *nam, int recv, long sz)
+{
+    long cnt = 0;
+    /*
+     * sz = -1 signifies error getting size.  don't set ZFTP_SIZE if sz is
+     * zero, either: it probably came from an fstat() on a pipe, so it
+     * means we don't know and shouldn't tell the user porkies.
+     */
+    if (sz > 0)
+	zfsetparam("ZFTP_SIZE", &sz, ZFPM_READONLY|ZFPM_INTEGER);
+    zfsetparam("ZFTP_FILE", ztrdup(nam), ZFPM_READONLY);
+    zfsetparam("ZFTP_TRANSFER", ztrdup(recv ? "G" : "P"), ZFPM_READONLY);
+    zfsetparam("ZFTP_COUNT", &cnt, ZFPM_READONLY|ZFPM_INTEGER);
+}
+
+/* Tidy up afterwards */
+
+/**/
+static void
+zfendtrans()
+{
+    zfunsetparam("ZFTP_SIZE");
+    zfunsetparam("ZFTP_FILE");
+    zfunsetparam("ZFTP_TRANSFER");
+    zfunsetparam("ZFTP_COUNT");
+}
+
+/* Read with timeout if recv is set. */
+
+/**/
+static int
+zfread(int fd, char *bf, size_t sz, int tmout)
+{
+    int ret;
+
+    if (!tmout)
+	return read(fd, bf, sz);
+
+    if (setjmp(zfalrmbuf)) {
+	alarm(0);
+	zwarnnam("zftp", "timeout on network read", NULL, 0);
+	return -1;
+    }
+    zfalarm(tmout);
+
+    ret = read(fd, bf, sz);
+
+    /* we don't bother turning off the whole alarm mechanism here */
+    alarm(0);
+    return ret;
+}
+
+/* Write with timeout if recv is not set. */
+
+/**/
+static int
+zfwrite(int fd, char *bf, size_t sz, int tmout)
+{
+    int ret;
+
+    if (!tmout)
+	return write(fd, bf, sz);
+
+    if (setjmp(zfalrmbuf)) {
+	alarm(0);
+	zwarnnam("zftp", "timeout on network write", NULL, 0);
+	return -1;
+    }
+    zfalarm(tmout);
+
+    ret = write(fd, bf, sz);
+
+    /* we don't bother turning off the whole alarm mechanism here */
+    alarm(0);
+    return ret;
+}
+
+static int zfread_eof;
+
+/* Version of zfread when we need to read in block mode. */
+
+/**/
+static int
+zfread_block(int fd, char *bf, size_t sz, int tmout)
+{
+    int n;
+    struct zfheader hdr;
+    size_t blksz, cnt;
+    char *bfptr;
+    do {
+	/* we need the header */
+	do {
+	    n = zfread(fd, (char *)&hdr, sizeof(hdr), tmout);
+	} while (n < 0 && errno == EINTR);
+	if (n != 3 && !zfdrrrring) {
+	    zwarnnam("zftp", "failed to read FTP block header", NULL, 0);
+	    return n;
+	}
+	/* size is stored in network byte order */
+	if (hdr.flags & ZFHD_EOFB)
+	    zfread_eof = 1;
+	blksz = (hdr.bytes[0] << 8) | hdr.bytes[1];
+	if (blksz > sz) {
+	    /*
+	     * See comments in file headers
+	     */
+	    zwarnnam("zftp", "block too large to handle", NULL, 0);
+	    errno = EIO;
+	    return -1;
+	}
+	bfptr = bf;
+	cnt = blksz;
+	while (cnt) {
+	    n = zfread(fd, bfptr, cnt, tmout);
+	    if (n > 0) {
+		bfptr += n;
+		cnt -= n;
+	    } else if (n < 0 && (errflag || zfdrrrring || errno != EINTR))
+		return n;
+	    else
+		break;
+	}
+	if (cnt) {
+	    zwarnnam("zftp", "short data block", NULL, 0);
+	    errno = EIO;
+	    return -1;
+	}
+    } while ((hdr.flags & ZFHD_MARK) && !zfread_eof);
+    return (hdr.flags & ZFHD_MARK) ? 0 : blksz;
+}
+
+/* Version of zfwrite when we need to write in block mode. */
+
+/**/
+static int
+zfwrite_block(int fd, char *bf, size_t sz, int tmout)
+{
+    int n;
+    struct zfheader hdr;
+    size_t cnt;
+    char *bfptr;
+    /* we need the header */
+    do {
+	hdr.bytes[0] = (sz & 0xff00) >> 8;
+	hdr.bytes[1] = sz & 0xff;
+	hdr.flags = sz ? 0 : ZFHD_EOFB;
+	n = zfwrite(fd, (char *)&hdr, sizeof(hdr), tmout);
+    } while (n < 0 && errno == EINTR);
+    if (n != 3 && !zfdrrrring) {
+	zwarnnam("zftp", "failed to write FTP block header", NULL, 0);
+	return n;
+    }
+    bfptr = bf;
+    cnt = sz;
+    while (cnt) {
+	n = zfwrite(fd, bfptr, cnt, tmout);
+	if (n > 0) {
+	    bfptr += n;
+	    cnt -= n;
+	} else if (n < 0 && (errflag || zfdrrrring || errno != EINTR))
+	    return n;
+    }
+
+    return sz;
+}
+
+/*
+ * Move stuff from fdin to fdout, tidying up the data connection
+ * when finished.  The data connection could be either input or output:
+ * recv is 1 for receiving a file, 0 for sending.
+ *
+ * progress is 1 to use a progress meter.
+ * startat says how far in we're starting with a REST command.
+ *
+ * Since we're doing some buffering here anyway, we don't bother
+ * with a stdio layer.
+ */
+
+/**/
+static int
+zfsenddata(char *name, int recv, int progress, long startat)
+{
+#define ZF_BUFSIZE 32768
+#define ZF_ASCSIZE (ZF_BUFSIZE/2)
+    /* ret = 2 signals the local read/write failed, so send abort */
+    int n, ret = 0, gotack = 0, fdin, fdout, fromasc = 0, toasc = 0;
+    int rtmout = 0, wtmout = 0;
+    char lsbuf[ZF_BUFSIZE], *ascbuf = NULL, *optr;
+    long sofar = 0, last_sofar = 0;
+    readwrite_t read_ptr = zfread, write_ptr = zfwrite;
+    List l;
+
+    if (progress && (l = getshfunc("zftp_progress")) != &dummy_list) {
+	/*
+	 * progress to set up:  ZFTP_COUNT is zero.
+	 * We do this here in case we needed to wait for a RETR
+	 * command to tell us how many bytes are coming.
+	 */
+	doshfunc("zftp_progress", l, NULL, 0, 1);
+	/* Now add in the bit of the file we've got/sent already */
+	sofar = last_sofar = startat;
+    }
+    if (recv) {
+	fdin = zdfd;
+	fdout = 1;
+	rtmout = getiparam("ZFTP_TMOUT");
+	if (ZFST_CTYP(zfstatus) == ZFST_ASCI)
+	    fromasc = 1;
+	if (ZFST_MODE(zfstatus) == ZFST_BLOC)
+	    read_ptr = zfread_block;
+    } else {
+	fdin = 0;
+	fdout = zdfd;
+	wtmout = getiparam("ZFTP_TMOUT");
+	if (ZFST_CTYP(zfstatus) == ZFST_ASCI)
+	    toasc = 1;
+	if (ZFST_MODE(zfstatus) == ZFST_BLOC)
+	    write_ptr = zfwrite_block;
+    }
+
+    if (toasc)
+	ascbuf = zalloc(ZF_ASCSIZE);
+    zfpipe();
+    zfread_eof = 0;
+    while (!ret && !zfread_eof) {
+	n = (toasc) ? read_ptr(fdin, ascbuf, ZF_ASCSIZE, rtmout)
+	    : read_ptr(fdin, lsbuf, ZF_BUFSIZE, rtmout);
+	if (n > 0) {
+	    char *iptr;
+	    if (toasc) {
+		/* \n -> \r\n it shouldn't happen to a dog. */
+		char *iptr = ascbuf, *optr = lsbuf;
+		int cnt = n;
+		while (cnt--) {
+		    if (*iptr == '\n') {
+			*optr++ = '\r';
+			n++;
+		    }
+		    *optr++ = *iptr++;
+		}
+	    }
+	    if (fromasc && (iptr = memchr(lsbuf, '\r', n))) {
+		/* \r\n -> \n */
+		char *optr = iptr;
+		int cnt = n - (iptr - lsbuf);
+		while (cnt--) {
+		    if (*iptr != '\r' || iptr[1] != '\n') {
+			*optr++ = *iptr;
+		    } else
+			n--;
+		    iptr++;
+		}
+	    }
+	    optr = lsbuf;
+
+	    sofar += n;
+
+	    for (;;) {
+		/*
+		 * in principle, write can be interrupted after
+		 * safely writing some bytes, and will return the
+		 * number already written, which may not be the
+		 * complete buffer.  so make this robust.  they call me
+		 * `robustness stephenson'.  in my dreams.
+		 */
+		int newn = write_ptr(fdout, optr, n, wtmout);
+		if (newn == n)
+		    break;
+		if (newn < 0) {
+		    /*
+		     * The somewhat contorted test here (for write)
+		     * and below (for read) means:
+		     * real error if
+		     *  - errno is set and it's not just an interrupt, or
+		     *  - errflag is set, probably due to CTRL-c, or
+		     *  - zfdrrrring is set, due to the alarm going off.
+		     * print an error message if
+		     *  - not a timeout, since that was reported, and
+		     *    either
+		     *    - a non-interactive shell, where we don't
+		     *      know what happened otherwise
+		     *    - or both of
+		     *      - not errflag, i.e. CTRL-c or what have you,
+		     *        since the user probably knows about that, and
+		     *      - not a SIGPIPE, since usually people are
+		     *        silent about those when going to pagers
+		     *        (if you quit less or more in the middle
+		     *        and see an error message you think `I
+		     *        shouldn't have done that').
+		     *
+		     * If we didn't print an error message here,
+		     * and were going to do an abort (ret == 2)
+		     * because the error happened on the local end
+		     * of the connection, set ret to 3 and don't print
+		     * the 'aborting...' either.
+		     *
+		     * There must be a better way of doing this.
+		     */
+		    if (errno != EINTR || errflag || zfdrrrring) {
+			if (!zfdrrrring &&
+			    (!interact || (!errflag && errno != EPIPE))) {
+			    ret = recv ? 2 : 1;
+			    zwarnnam(name, "write failed: %e", NULL, errno);
+			} else
+			    ret = recv ? 3 : 1;
+			break;
+		    }
+		    continue;
+		}
+		optr += newn;
+		n -= newn;
+	    }
+	} else if (n < 0) {
+	    if (errno != EINTR || errflag || zfdrrrring) {
+		if (!zfdrrrring &&
+		    (!interact || (!errflag && errno != EPIPE))) {
+		    ret = recv ? 1 : 2;
+		    zwarnnam(name, "read failed: %e", NULL, errno);
+		} else
+		    ret = recv ? 1 : 3;
+		break;
+	    }
+	} else
+	    break;
+	if (!ret && sofar != last_sofar && progress &&
+	    (l = getshfunc("zftp_progress")) != &dummy_list) {
+	    zfsetparam("ZFTP_COUNT", &sofar, ZFPM_READONLY|ZFPM_INTEGER);
+	    doshfunc("zftp_progress", l, NULL, 0, 1);
+	    last_sofar = sofar;
+	}
+    }
+    zfunpipe();
+    /*
+     * At this point any timeout was on the data connection,
+     * so we don't need to force the control connection to close.
+     */
+    zfdrrrring = 0;
+    if (!errflag && !ret && !recv && ZFST_MODE(zfstatus) == ZFST_BLOC) {
+	/* send an end-of-file marker block */
+	ret = (zfwrite_block(fdout, lsbuf, 0, wtmout) < 0);
+    }
+    if (errflag || ret > 1) {
+	/*
+	 * some error occurred, maybe a keyboard interrupt, or
+	 * a local file/pipe handling problem.
+	 * send an abort.
+	 *
+	 * safest to block all signals here?  can get frustrating if
+	 * we're waiting for an abort.  don't I know.  let's start
+	 * off just by blocking SIGINT's.
+	 *
+	 * maybe the timeout for the abort should be shorter than
+	 * for normal commands.  and what about aborting after
+	 * we had a timeout on the data connection, is that
+	 * really a good idea?
+	 */
+	/* RFC 959 says this is what to send */
+	unsigned char msg[4] = { IAC, IP, IAC, SYNCH };
+
+	if (ret == 2)
+	    zwarnnam(name, "aborting data transfer...", NULL, 0);
+
+	holdintr();
+
+	/* the following is black magic, as far as I'm concerned. */
+	/* what are we going to do if it fails?  not a lot, actually. */
+	send(zcfd, (char *)msg, 3, 0);
+	send(zcfd, (char *)msg+3, 1, MSG_OOB);
+
+	zfsendcmd("ABOR\r\n");
+	if (lastcode == 226) {
+	    /*
+	     * 226 is supposed to mean the transfer got sent OK after
+	     * all, and the abort got ignored, at least that's what
+	     * rfc959 seems to be saying.  but in fact what can happen
+	     * is the transfer finishes (at least as far as the
+	     * server's concerned) and it's response is waiting, then
+	     * the abort gets sent, and we need to mop up a response to
+	     * that.  so actually in most cases we get two replies
+	     * anyway.  we could test if we had select() on all hosts.
+	     */
+	    /* gotack = 1; */
+	    /*
+	     * we'd better leave errflag, since we don't know
+	     * where it came from.  maybe the user wants to abort
+	     * a whole script or function.
+	     */
+	} else
+	    ret = 1;
+
+	noholdintr();
+    }
+	
+    if (toasc)
+	zfree(ascbuf, ZF_ASCSIZE);
+    zfclosedata();
+    if (!gotack && zfgetmsg() > 2)
+	ret = 1;
+    return ret != 0;
+}
+
+/* Open a new control connection, i.e. start a new FTP session */
+
+/**/
+static int
+zftp_open(char *name, char **args, int flags)
+{
+    struct in_addr ipaddr;
+    struct protoent *zprotop;
+    struct servent *zservp;
+    struct hostent *zhostp = NULL;
+    char **addrp, tbuf[2] = "X", *fname;
+    int err, len, tmout;
+
+    if (!*args) {
+	if (zfuserparams)
+	    args = zfuserparams;
+	else {
+	    zwarnnam(name, "no host specified", NULL, 0);
+	    return 1;
+	}
+    }
+
+    /*
+     * Close the existing connection if any.
+     * Probably this is the safest thing to do.  It's possible
+     * a `QUIT' will hang, though.
+     */
+    if (zcfd != -1)
+	zfclose();
+
+    /* this is going to give 0.  why bother? */
+    zprotop = getprotobyname("tcp");
+    zservp = getservbyname("ftp", "tcp");
+
+    if (!zprotop || !zservp) {
+	zwarnnam(name, "Somebody stole FTP!", NULL, 0);
+	return 1;
+    }
+
+    /* don't try talking to server yet */
+    zcfinish = 2;
+
+    /*
+     * This sets an alarm for the whole process, getting the host name
+     * as well as connecting.  Arguably you could time them out separately. 
+     */
+    tmout = getiparam("ZFTP_TMOUT");
+    if (setjmp(zfalrmbuf)) {
+	char *hname;
+	alarm(0);
+	if ((hname = getsparam("ZFTP_HOST")) && *hname) 
+	    zwarnnam(name, "timeout connecting to %s", hname, 0);
+	else
+	    zwarnnam(name, "timeout on host name lookup", NULL, 0);
+	zfclose();
+	return 1;
+    }
+    zfalarm(tmout);
+
+    /*
+     * Now this is what I like.  A library which provides decent higher
+     * level functions to do things like converting address types.  It saves
+     * so much trouble.  Pity about the rest of the network interface, though.
+     */
+    ipaddr.s_addr = inet_addr(args[0]);
+    if (ipaddr.s_addr != INADDR_NONE) {
+	/*
+	 * hmmm, we don't necessarily want to do this... maybe the
+	 * user is actively trying to avoid a bad nameserver.
+	 * perhaps better just to set ZFTP_HOST to the dot address, too.
+	 * that way shell functions know how it was opened.
+	 *
+	 * 	zhostp = gethostbyaddr(&ipaddr, sizeof(ipaddr), AF_INET);
+	 *
+	 * or, we could have a `no_lookup' flag.
+	 */
+	zfsetparam("ZFTP_HOST", ztrdup(args[0]), ZFPM_READONLY);
+	zsock.sin_family = AF_INET;
+    } else {
+	zhostp = gethostbyname(args[0]);
+	if (!zhostp || errflag) {
+	    /* should use herror() here if available, but maybe
+	     * needs configure test. on AIX it's present but not
+	     * in headers.
+	     */
+	    zwarnnam(name, "host not found: %s", args[0], 0);
+	    alarm(0);
+	    return 1;
+	}
+	zsock.sin_family = zhostp->h_addrtype;
+	zfsetparam("ZFTP_HOST", ztrdup(zhostp->h_name), ZFPM_READONLY);
+    }
+
+    zsock.sin_port = ntohs(zservp->s_port);
+    zcfd = zfmovefd(socket(zsock.sin_family, SOCK_STREAM, 0));
+    if (zcfd < 0) {
+	zwarnnam(name, "socket failed: %e", NULL, errno);
+	zfunsetparam("ZFTP_HOST");
+	alarm(0);
+	return 1;
+    }
+
+#if defined(F_SETFD) && defined(FD_CLOEXEC)
+    /* If the shell execs a program, we don't want this fd left open. */
+    len = FD_CLOEXEC;
+    fcntl(zcfd, F_SETFD, &len);
+#endif
+
+    /*
+     * now connect the socket.  manual pages all say things like `this is all
+     * explained oh-so-wonderfully in some other manual page'.  not.
+     */
+
+    err = 1;
+
+    if (ipaddr.s_addr != INADDR_NONE) {
+	/* dot address */
+	memcpy(&zsock.sin_addr, &ipaddr, sizeof(ipaddr));
+	do {
+	    err = connect(zcfd, (struct sockaddr *)&zsock, sizeof(zsock));
+	} while (err && errno == EINTR && !errflag);
+    } else {
+	/* host name: try all possible IP's */
+	for (addrp = zhostp->h_addr_list; *addrp; addrp++) {
+	    memcpy(&zsock.sin_addr, *addrp, zhostp->h_length);
+	    do {
+		err = connect(zcfd, (struct sockaddr *)&zsock, sizeof(zsock));
+	    } while (err && errno == EINTR && !errflag);
+	    /* you can check whether it's worth retrying here */
+	}
+    }
+
+    alarm(0);
+
+    if (err < 0) {
+	zwarnnam(name, "connect failed: %e", NULL, errno);
+	zfclose();
+	return 1;
+    }
+    zfsetparam("ZFTP_IP", ztrdup(inet_ntoa(zsock.sin_addr)), ZFPM_READONLY);
+    /* now we can talk to the control connection */
+    zcfinish = 0;
+
+    len = sizeof(zsock);
+    if (getsockname(zcfd, (struct sockaddr *)&zsock, &len) < 0) {
+	zwarnnam(name, "getsockname failed: %e", NULL, errno);
+	zfclose();
+	return 1;
+    }
+    /* nice to get some options right, ignore if they don't work */
+#ifdef SO_OOBINLINE
+    /*
+     * this says we want messages in line.  maybe sophisticated people
+     * do clever things with SIGURG.
+     */
+    len = 1;
+    setsockopt(zcfd, SOL_SOCKET, SO_OOBINLINE, (char *)&len, sizeof(len));
+#endif
+#if defined(IP_TOS) && defined(IPTOS_LOWDELAY)
+    /* for control connection we want low delay.  please don't laugh. */
+    len = IPTOS_LOWDELAY;
+    setsockopt(zcfd, IPPROTO_IP, IP_TOS, (char *)&len, sizeof(len));
+#endif
+
+    /*
+     * We use stdio with line buffering for convenience on input.
+     * On output, we can just dump a complete message to the fd via write().
+     */
+    zcin = fdopen(zcfd, "r");
+
+    if (!zcin) {
+	zwarnnam(name, "file handling error", NULL, 0);
+	zfclose();
+	return 1;
+    }
+
+#ifdef _IONBF
+    setvbuf(zcin, NULL, _IONBF, 0);
+#else
+    setlinebuf(zcin);
+#endif
+
+    /*
+     * now see what the remote server has to say about that.
+     */
+    if (zfgetmsg() >= 4) {
+	zfclose();
+	return 1;
+    }
+
+    zfis_unix = 0;
+    zfhas_size = zfhas_mdtm = ZFCP_UNKN;
+    zdfd = -1;
+    /* initial status: open, ASCII data, stream mode 'n' stuff */
+    zfstatus = 0;
+
+    /* open file for saving the current status */
+    fname = gettempname();
+    zfstatfd = open(fname, O_RDWR|O_CREAT, 0600);
+    DPUTS(zfstatfd == -1, "zfstatfd not created");
+#if defined(F_SETFD) && defined(FD_CLOEXEC)
+    /* If the shell execs a program, we don't want this fd left open. */
+    len = FD_CLOEXEC;
+    fcntl(zfstatfd, F_SETFD, &len);
+#endif
+    unlink(fname);
+
+    /* now find out what system we're connected to */
+    if (!(zfprefs & ZFPF_DUMB) && zfsendcmd("SYST\r\n") == 2) {
+	char *ptr = lastmsg, *eptr, *systype;
+	for (eptr = ptr; *eptr; eptr++)
+	    ;
+	systype = ztrduppfx(ptr, eptr-ptr);
+	if (!strncmp(systype, "UNIX Type: L8", 13)) {
+	    /*
+	     * Use binary for transfers.  This simple test saves much
+	     * hassle for all concerned, particularly me.
+	     */
+	    zfstatus |= ZFST_IMAG;
+	    zfis_unix = 1;
+	}
+	/*
+	 * we could set zfis_unix based just on the UNIX part,
+	 * but I don't really know the consequences of that.
+	 */
+	zfsetparam("ZFTP_SYSTEM", systype, ZFPM_READONLY);
+    } else if (zcfd == -1) {
+	/* final paranoid check */
+	return 1;
+    }
+	
+    tbuf[0] = (ZFST_TYPE(zfstatus) == ZFST_ASCI) ? 'A' : 'I';
+    zfsetparam("ZFTP_TYPE", ztrdup(tbuf), ZFPM_READONLY);
+    zfsetparam("ZFTP_MODE", ztrdup("S"), ZFPM_READONLY);
+    /* if remaining arguments, use them to log in. */
+    if (zcfd > -1 && *++args)
+	return zftp_login(name, args, flags);
+    /* if something wayward happened, connection was already closed */
+    return zcfd == -1;
+}
+
+/*
+ * Read a parameter string, with a prompt if reading from stdin.
+ * The returned string is on the heap.
+ * If noecho, turn off ECHO mode while reading.
+ */
+
+/**/
+static char *
+zfgetinfo(char *prompt, int noecho)
+{
+    int resettty = 0;
+    /* 256 characters should be enough, hardly worth allocating
+     * a password string byte by byte
+     */
+    char instr[256], *strret;
+    int len;
+
+    /*
+     * Only print the prompt if getting info from a tty.  Of
+     * course, we don't know if stderr has been redirected, but
+     * that seems a minor point.
+     */
+    if (isatty(0)) {
+	if (noecho) {
+	    /* hmmm... all this great big shell and we have to read
+	     * something with no echo by ourselves.
+	     * bin_read() is far to complicated for our needs.
+	     * we could use zread(), but that relies on static
+	     * variables, so someone doesn't want that to happen.
+	     *
+	     * this is modified from setcbreak() in utils.c,
+	     * except I don't see any point in using cbreak mode
+	     */
+	    struct ttyinfo ti;
+
+	    ti = shttyinfo;
+#ifdef HAS_TIO
+	    ti.tio.c_lflag &= ~ECHO;
+#else
+	    ti.sgttyb.sg_flags &= ~ECHO;
+#endif
+	    settyinfo(&ti);
+	    resettty = 1;
+	}
+	fflush(stdin);
+	fputs(prompt, stderr);
+	fflush(stderr);
+    }
+
+    fgets(instr, 256, stdin);
+    if (instr[len = strlen(instr)-1] == '\n')
+	instr[len] = '\0';
+
+    strret = dupstring(instr);
+
+    if (resettty) {
+	/* '\n' didn't get echoed */
+	fputc('\n', stdout);
+	fflush(stdout);
+    	settyinfo(&shttyinfo);
+    }
+
+    return strret;
+}
+
+/*
+ * set params for an open with no arguments.
+ * this allows easy re-opens.
+ */
+
+/**/
+static int
+zftp_params(char *name, char **args, int flags)
+{
+    char *prompts[] = { "Host: ", "User: ", "Password: ", "Account: " };
+    char **aptr, **newarr;
+    int i, j, len;
+
+    if (!*args) {
+	if (zfuserparams) {
+	    for (aptr = zfuserparams, i = 0; *aptr; aptr++, i++) {
+		if (i == 2) {
+		    len = strlen(*aptr);
+		    for (j = 0; j < len; j++)
+			fputc('*', stdout);
+		    fputc('\n', stdout);
+		} else
+		    fprintf(stdout, "%s\n", *aptr);
+	    }
+	}
+	return 0;
+    }
+    if (!strcmp(*args, "-")) {
+	if (zfuserparams)
+	    freearray(zfuserparams);
+	zfuserparams = 0;
+	return 0;
+    }
+    len = arrlen(args);
+    newarr = (char **)zcalloc((len+1)*sizeof(char *));
+    for (aptr = args, i = 0; *aptr && !errflag; aptr++, i++) {
+	char *str;
+	if (!strcmp(*aptr, "?"))
+	    str = zfgetinfo(prompts[i], i == 2);
+	else
+	    str = *aptr;
+	newarr[i] = ztrdup(str);
+    }
+    if (errflag) {
+	/* maybe user CTRL-c'd in the middle somewhere */
+	for (aptr = newarr; *aptr; aptr++)
+	    zsfree(*aptr);
+	zfree(newarr, len+1);
+	return 1;
+    }
+    if (zfuserparams)
+	freearray(zfuserparams);
+    zfuserparams = newarr;
+    return 0;
+}
+
+/* login a user:  often called as part of the open sequence */
+
+/**/
+static int
+zftp_login(char *name, char **args, int flags)
+{
+    char *ucmd, *passwd = NULL, *acct = NULL;
+    char *user;
+    int stopit;
+
+    if ((zfstatus & ZFST_LOGI) && zfsendcmd("REIN\r\n") >= 4)
+	return 1;
+
+    zfstatus &= ~ZFST_LOGI;
+    if (*args) {
+	user = *args++;
+    } else {
+	user = zfgetinfo("User: ", 0);
+    }
+
+    ucmd = tricat("USER ", user, "\r\n");
+    stopit = 0;
+
+    if (zfsendcmd(ucmd) == 6)
+	stopit = 2;
+
+    while (!stopit && !errflag) {
+	switch (lastcode) {
+	case 230: /* user logged in */
+	case 202: /* command not implemented, don't care */
+	    stopit = 1;
+	    break;
+
+	case 331: /* need password */
+	    if (*args)
+		passwd = *args++;
+	    else
+		passwd = zfgetinfo("Password: ", 1);
+	    zsfree(ucmd);
+	    ucmd = tricat("PASS ", passwd, "\r\n");
+	    if (zfsendcmd(ucmd) == 6)
+		stopit = 2;
+	    break;
+
+	case 332: /* need account */
+	case 532:
+	    if (*args)
+		acct = *args++;
+	    else
+		acct = zfgetinfo("Account: ", 0);
+	    zsfree(ucmd);
+	    ucmd = tricat("ACCT ", passwd, "\r\n");
+	    if (zfsendcmd(ucmd) == 6)
+		stopit = 2;
+	    break;
+
+	case 421: /* service not available, so closed anyway */
+	case 501: /* syntax error */
+	case 503: /* bad commands */
+	case 530: /* not logged in */
+	case 550: /* random can't-do-that */
+	default:  /* whatever, should flag this as bad karma */
+	    /* need more diagnostics here */
+	    stopit = 2;
+	    break;
+	}
+    }
+
+    zsfree(ucmd);
+    if (zcfd == -1)
+	return 1;
+    if (stopit == 2 || (lastcode != 230 && lastcode != 202)) {
+	zwarnnam(name, "login failed", NULL, 0);
+	return 1;
+    }
+
+    if (*args) {
+	int cnt;
+	for (cnt = 0; *args; args++)
+	    cnt++;
+	zwarnnam(name, "warning: %d comand arguments not used\n", NULL, cnt);
+    }
+    zfstatus |= ZFST_LOGI;
+    zfsetparam("ZFTP_USER", ztrdup(user), ZFPM_READONLY);
+    if (acct)
+	zfsetparam("ZFTP_ACCOUNT", ztrdup(acct), ZFPM_READONLY);
+
+    /*
+     * Get the directory.  This is possibly an unnecessary overhead, of
+     * course, but when you're being driven by shell functions there's
+     * just no way of telling.
+     */
+    return zfgetcwd();
+}
+
+/* do ls or dir on the remote directory */
+
+/**/
+static int
+zftp_dir(char *name, char **args, int flags)
+{
+    /* maybe should be cleverer about handling arguments */
+    char *cmd;
+    int ret;
+
+    /*
+     * RFC959 says this must be ASCII or EBCDIC, not image format.
+     * I rather suspect on a UNIX server we get away handsomely
+     * with doing everything, including this, as image.
+     */
+    zfsettype(ZFST_ASCI);
+
+    cmd = zfargstring((flags & ZFTP_NLST) ? "NLST" : "LIST", args);
+    ret = zfgetdata(name, NULL, cmd, 0);
+    zsfree(cmd);
+    if (ret)
+	return 1;
+
+    fflush(stdout);		/* since we're now using fd 1 */
+    return zfsenddata(name, 1, 0, 0);
+}
+
+/* change the remote directory */
+
+/**/
+static int
+zftp_cd(char *name, char **args, int flags)
+{
+    /* change directory --- enhance to allow 'zftp cdup' */
+    int ret;
+
+    if ((flags & ZFTP_CDUP) || !strcmp(*args, "..") ||
+	!strcmp(*args, "../")) {
+	ret = zfsendcmd("CDUP\r\n");
+    } else {
+	char *cmd = tricat("CWD ", *args, "\r\n");
+	ret = zfsendcmd(cmd);
+	zsfree(cmd);
+    }
+    if (ret > 2)
+	return 1;
+    /* sometimes the directory may be in the response. usually not. */
+    if (zfgetcwd())
+	return 1;
+
+    return 0;
+}
+
+/* get the remote directory */
+
+/**/
+static int
+zfgetcwd(void)
+{
+    char *ptr, *eptr;
+    int endc;
+    List l;
+
+    if (zfprefs & ZFPF_DUMB)
+	return 1;
+    if (zfsendcmd("PWD\r\n") > 2) {
+	zfunsetparam("ZFTP_PWD");
+	return 1;
+    }
+    ptr = lastmsg;
+    while (*ptr == ' ')
+	ptr++;
+    if (!*ptr)			/* ultra safe */
+	return 1;
+    if (*ptr == '"') {
+	ptr++;
+	endc = '"';
+    } else 
+	endc = ' ';
+    for (eptr = ptr; *eptr && *eptr != endc; eptr++)
+	;
+    zfsetparam("ZFTP_PWD", ztrduppfx(ptr, eptr-ptr), ZFPM_READONLY);
+
+    /*
+     * This isn't so necessary if we're going to have a shell function
+     * front end.  By putting it here, and in close when ZFTP_PWD is unset,
+     * we at least cover the bases.
+     */
+    if ((l = getshfunc("zftp_chpwd")) != &dummy_list)
+	doshfunc("zftp_chpwd", l, NULL, 0, 1);
+
+    return 0;
+}
+
+/*
+ * Set the type for the next transfer, usually image (binary) or ASCII.
+ */
+
+/**/
+static int
+zfsettype(int type)
+{
+    char buf[] = "TYPE X\r\n";
+    if (ZFST_TYPE(type) == ZFST_CTYP(zfstatus))
+	return 0;
+    buf[5] = (ZFST_TYPE(type) == ZFST_ASCI) ? 'A' : 'I';
+    if (zfsendcmd(buf) > 2)
+	return 1;
+    zfstatus &= ~(ZFST_TMSK << ZFST_TBIT);
+    /* shift the type left to set the current type bits */;
+    zfstatus |= type << ZFST_TBIT;
+    return 0;
+}
+
+/*
+ * Print or get a new type for the transfer.
+ * We don't actually set the type at this point.
+ */
+
+/**/
+static int
+zftp_type(char *name, char **args, int flags)
+{
+    char *str, nt, tbuf[2] = "A";
+    if (flags & (ZFTP_TBIN|ZFTP_TASC)) {
+	nt = (flags & ZFTP_TBIN) ? 'I' : 'A';
+    } else if (!(str = *args)) {
+	/*
+	 * Since this is supposed to be a low-level basis for
+	 * an FTP system, just print the single code letter.
+	 */
+	printf("%c\n", (ZFST_TYPE(zfstatus) == ZFST_ASCI) ? 'A' : 'I');
+	fflush(stdout);
+	return 0;
+    } else {
+	nt = toupper(*str);
+	/*
+	 * RFC959 specifies other types, but these are the only
+	 * ones we know what to do with.
+	 */
+	if (str[1] || (nt != 'A' && nt != 'B' && nt != 'I')) {
+	    zwarnnam(name, "transfer type %s not recognised", str, 0);
+	    return 1;
+	}
+	
+	if (nt == 'B')		/* binary = image */
+	    nt = 'I';
+    }
+
+    zfstatus &= ~ZFST_TMSK;
+    zfstatus |= (nt == 'I') ? ZFST_IMAG : ZFST_ASCI;
+    tbuf[0] = nt;
+    zfsetparam("ZFTP_TYPE", ztrdup(tbuf), ZFPM_READONLY);
+    return 0;
+}
+
+/**/
+static int
+zftp_mode(char *name, char **args, int flags)
+{
+    char *str, cmd[] = "MODE X\r\n";
+    int nt;
+
+    if (!(str = *args)) {
+	printf("%c\n", (ZFST_MODE(zfstatus) == ZFST_STRE) ? 'S' : 'B');
+	fflush(stdout);
+	return 0;
+    }
+    nt = str[0] = toupper(*str);
+    if (str[1] || (nt != 'S' && nt != 'B')) {
+	zwarnnam(name, "transfer mode %s not recognised", str, 0);
+	return 1;
+    }
+    cmd[5] = (char) nt;
+    if (zfsendcmd(cmd) > 2)
+	return 1;
+    zfstatus &= ZFST_MMSK;
+    zfstatus |= (nt == 'S') ? ZFST_STRE : ZFST_BLOC;
+    zfsetparam("ZFTP_MODE", ztrdup(str), ZFPM_READONLY);
+    return 0;
+}
+
+/**/
+static int
+zftp_local(char *name, char **args, int flags)
+{
+    int more = !!args[1], ret = 0, dofd = !*args;
+    while (*args || dofd) {
+	long sz;
+	char *mt;
+	int newret = zfstats(*args, !(flags & ZFTP_HERE), &sz, &mt,
+			     dofd ? 0 : -1);
+	if (newret == 2)	/* at least one is not implemented */
+	    return 2;
+	else if (newret) {
+	    ret = 1;
+	    if (mt)
+		zsfree(mt);
+	    args++;
+	    continue;
+	}
+	if (more) {
+	    fputs(*args, stdout);
+	    fputc(' ', stdout);
+	}
+	printf("%ld %s\n", sz, mt);
+	zsfree(mt);
+	if (dofd)
+	    break;
+	args++;
+    }
+    fflush(stdout);
+
+    return ret;
+}
+
+/*
+ * Generic transfer for get, put and append.
+ *
+ * Get sends all files to stdout, i.e. this is basically cat. It's up to a
+ * shell function driver to turn this into standard FTP-like commands.
+ *
+ * Put/append sends everything from stdin down the drai^H^H^Hata connection. 
+ * Slightly weird with multiple files in that it tries to read
+ * a separate complete file from stdin each time, which is
+ * only even potentially useful interactively.  But the only
+ * real alternative is just to allow one file at a time.
+ */
+
+/**/
+static int
+zftp_getput(char *name, char **args, int flags)
+{
+    int ret = 0, recv = (flags & ZFTP_RECV), getsize = 0, progress = 1;
+    char *cmd = recv ? "RETR " : (flags & ZFTP_APPE) ? "APPE " : "STOR ";
+    List l;
+
+    /*
+     * At this point I'd like to set progress to 0 if we're
+     * backgrounded, since it's hard for the user to find out.
+     * It turns out it's hard enough for us to find out.
+     * The problem is that zsh clears it's job table, so we
+     * just don't know if we're some forked shell in a pipeline
+     * somewhere or in the background.  This seems to me a problem.
+     */
+
+    zfsettype(ZFST_TYPE(zfstatus));
+
+    if (recv)
+	fflush(stdout);		/* since we may be using fd 1 */
+    for (; *args; args++) {
+	char *ln, *rest = NULL;
+	long startat = 0;
+	if (progress && (l = getshfunc("zftp_progress")) != &dummy_list) {
+	    long sz;
+	    /*
+	     * This calls the SIZE command to get the size for remote
+	     * files.  Some servers send the size with the reply to
+	     * the transfer command (i.e. RETR), in which
+	     * case we note the fact and don't call this
+	     * next time.  For that reason, the first call
+	     * of zftp_progress is delayed until zfsenddata().
+	     */
+	    if ((!(zfprefs & ZFPF_DUMB) &&
+		 (zfstatus & (ZFST_NOSZ|ZFST_TRSZ)) != ZFST_TRSZ)
+		|| !recv) {
+		/* the final 0 is a local fd to fstat if recv is zero */
+		zfstats(*args, recv, &sz, NULL, 0);
+		/* even if it doesn't support SIZE, it may tell us */
+		if (recv && sz == -1)
+		    getsize = 1;
+	    } else
+		getsize = 1;
+	    zfstarttrans(*args, recv, sz);
+	}
+
+	if (flags & ZFTP_REST) {
+	    startat = zstrtol(args[1], NULL, 10);
+	    rest = tricat("REST ", args[1], "\r\n");
+	}
+
+	ln = tricat(cmd, *args, "\r\n");
+	/* note zdfd doesn't exist till zfgetdata() creates it */
+	if (zfgetdata(name, rest, ln, getsize) ||
+	    zfsenddata(name, recv, progress, startat))
+	    ret = 1;
+	zsfree(ln);
+	if (progress && (l = getshfunc("zftp_progress")) != &dummy_list) {
+	    /* progress to finish: ZFTP_TRANSFER set to GF or PF */
+	    zfsetparam("ZFTP_TRANSFER", ztrdup(recv ? "GF" : "PF"),
+		       ZFPM_READONLY);
+	    doshfunc("zftp_progress", l, NULL, 0, 1);
+	}
+	if (rest) {
+	    zsfree(rest);
+	    args++;
+	}
+	if (errflag)
+	    break;
+    }
+    zfendtrans();
+    return ret;
+}
+
+/*
+ * Delete a list of files on the server.  We allow a list by analogy with
+ * `rm'.
+ */
+
+/**/
+static int
+zftp_delete(char *name, char **args, int flags)
+{
+    int ret = 0;
+    char *cmd, **aptr;
+    for (aptr = args; *aptr; aptr++) {
+	cmd = tricat("DELE ", *aptr, "\r\n");
+	if (zfsendcmd(cmd) > 2)
+	    ret = 1;
+	zsfree(cmd);
+    }
+    return ret;
+}
+
+/* Create or remove a directory on the server */
+
+/**/
+static int
+zftp_mkdir(char *name, char **args, int flags)
+{
+    int ret;
+    char *cmd = tricat((flags & ZFTP_DELE) ? "RMD " : "MKD ",
+		       *args, "\r\n");
+    ret = (zfsendcmd(cmd) > 2);
+    zsfree(cmd);
+    return ret;
+}
+
+/* Rename a file on the server */
+
+/**/
+static int
+zftp_rename(char *name, char **args, int flags)
+{
+    int ret;
+    char *cmd;
+
+    cmd = tricat("RNFR ", args[0], "\r\n");
+    ret = 1;
+    if (zfsendcmd(cmd) == 3) {
+	zsfree(cmd);
+	cmd = tricat("RNTO ", args[1], "\r\n");
+	if (zfsendcmd(cmd) == 2)
+	    ret = 0;
+    }
+    zsfree(cmd);
+    return ret;
+}
+
+/*
+ * Send random commands, either with SITE or literal.
+ * In the second case, the user better know what they're doing.
+ */
+
+/**/
+static int
+zftp_quote(char *name, char **args, int flags)
+{
+    int ret = 0;
+    char *cmd;
+
+    cmd = (flags & ZFTP_SITE) ? zfargstring("SITE", args)
+	: zfargstring(args[0], args+1);
+    ret = (zfsendcmd(cmd) > 2);
+    zsfree(cmd);
+
+    return ret;
+}
+
+/* Close the connection, ending the session */
+
+/**/
+static int
+zftp_close(char *name, char **args, int flags)
+{
+    char **aptr;
+    List l;
+    zfclosing = 1;
+    if (zcfinish != 2) {
+	/*
+	 * haven't had EOF from server, so send a QUIT and get the response.
+	 * maybe we should set a shorter timeout for this to avoid
+	 * CTRL-c rage.
+	 */
+	zfsendcmd("QUIT\r\n");
+    }
+    if (zcin)
+	fclose(zcin);
+    zcin = NULL;
+    close(zcfd);
+    zcfd = -1;
+
+    /* Write the final status in case this is a subshell */
+    zfstatus |= ZFST_CLOS;
+    lseek(zfstatfd, 0, 0);
+    write(zfstatfd, &zfstatus, sizeof(zfstatus));
+    close(zfstatfd);
+    zfstatfd = -1;
+
+    /* Unset the non-special parameters */
+    for (aptr = zfparams; *aptr; aptr++)
+	zfunsetparam(*aptr);
+
+    /* Now ZFTP_PWD is unset.  It's up to zftp_chpwd to notice. */
+    if ((l = getshfunc("zftp_chpwd")) != &dummy_list)
+	doshfunc("zftp_chpwd", l, NULL, 0, 1);
+
+    /* tidy up status variables, because mess is bad */
+    zfclosing = zfdrrrring = 0;
+
+    return 0;
+}
+
+/* Safe front end to zftp_close() from within the package */
+
+/**/
+static void
+zfclose(void)
+{
+    if (zcfd != -1)
+	zftp_close("zftp close", NULL, 0);
+}
+
+/* The builtin command frontend to the rest of the package */
+
+/**/
+static int
+bin_zftp(char *name, char **args, char *ops, int func)
+{
+    char fullname[11] = "zftp ";
+    char *cnam = *args++, *prefs, *ptr;
+    Zftpcmd zptr;
+    int n, ret;
+
+    for (zptr = zftpcmdtab; zptr->nam; zptr++)
+	if (!strcmp(zptr->nam, cnam))
+	    break;
+
+    if (!zptr->nam) {
+	zwarnnam(name, "no such subcommand: %s", cnam, 0);
+	return 1;
+    }
+
+    /* check number of arguments */
+    for (n = 0; args[n]; n++)
+	;
+    if (n < zptr->min || (zptr->max != -1 && n > zptr->max)) {
+	zwarnnam(name, "wrong no. of arguments for %s", cnam, 0);
+	return 1;
+    }
+
+    strcat(fullname, cnam);
+    if (zfstatfd != -1) {
+	/* Get the status in case it was set by a forked process */
+	int oldstatus = zfstatus;
+	lseek(zfstatfd, 0, 0);
+	read(zfstatfd, &zfstatus, sizeof(zfstatus));
+	if (zcfd != -1 && (zfstatus & ZFST_CLOS)) {
+	    /* got closed in subshell without us knowing */
+	    zcfinish = 2;
+	    zfclose();
+	} else {
+	    /*
+	     * fix up status types: unfortunately they may already
+	     * have been looked at between being changed in the subshell
+	     * and now, but we can't help that.
+	     */
+	    if (ZFST_TYPE(oldstatus) != ZFST_TYPE(zfstatus))
+		zfsetparam("ZFTP_TYPE",
+			   ztrdup(ZFST_TYPE(zfstatus) == ZFST_ASCI ?
+				  "A" : "I"), ZFPM_READONLY);
+	    if (ZFST_MODE(oldstatus) != ZFST_MODE(zfstatus))
+		zfsetparam("ZFTP_MODE",
+			   ztrdup(ZFST_MODE(zfstatus) == ZFST_BLOC ?
+				  "B" : "S"), ZFPM_READONLY);
+	}
+    }
+    if ((zptr->flags & ZFTP_CONN) && zcfd == -1) {
+	zwarnnam(fullname, "not connected.", NULL, 0);
+	return 1;
+    }
+
+    if ((prefs = getsparam("ZFTP_PREFS"))) {
+	zfprefs = 0;
+	for (ptr = prefs; *ptr; ptr++) {
+	    switch (toupper(*ptr)) {
+	    case 'S':
+		/* sendport */
+		zfprefs |= ZFPF_SNDP;
+		break;
+
+	    case 'P':
+		/*
+		 * passive
+		 * If we have already been told to use sendport mode,
+		 * we're never going to use passive mode.
+		 */
+		if (!(zfprefs & ZFPF_SNDP))
+		    zfprefs |= ZFPF_PASV;
+		break;
+
+	    case 'D':
+		/* dumb */
+		zfprefs |= ZFPF_DUMB;
+		break;
+
+	    default:
+		zwarnnam(name, "preference %c not recognized", NULL, *ptr);
+		break;
+	    }
+	}
+    }
+
+    ret = (*zptr->fun)(fullname, args, zptr->flags);
+
+    if (zfalarmed)
+	zfunalarm();
+    if (zfdrrrring) {
+	/* had a timeout, close the connection */
+	zcfinish = 2;		/* don't try sending QUIT */
+	zfclose();
+    }
+    if (zfstatfd != -1) {
+	/* Set the status in case another process needs to know */
+	lseek(zfstatfd, 0, 0);
+	write(zfstatfd, &zfstatus, sizeof(zfstatus));
+    }
+    return ret;
+}
+
+/* The load/unload routines required by the zsh library interface */
+
+/**/
+int
+boot_zftp(Module m)
+{
+    int ret;
+    if ((ret = addbuiltins(m->nam, bintab,
+			   sizeof(bintab)/sizeof(*bintab))) == 1) {
+	/* if successful, set some default parameters */
+	long tmout_def = 60;
+	zfsetparam("ZFTP_VERBOSE", ztrdup("450"), ZFPM_IFUNSET);
+	zfsetparam("ZFTP_TMOUT", &tmout_def, ZFPM_IFUNSET|ZFPM_INTEGER);
+	zfsetparam("ZFTP_PREFS", ztrdup("PS"), ZFPM_IFUNSET);
+	/* default preferences if user deletes variable */
+	zfprefs = ZFPF_SNDP|ZFPF_PASV;
+    }
+    return !ret;
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_zftp(Module m)
+{
+    /*
+     * There are various parameters hanging around, but they're
+     * all non-special so are entirely non-life-threatening.
+     */
+    zfclosedata();
+    zfclose();
+    zsfree(lastmsg);
+    if (zfuserparams)
+	freearray(zfuserparams);
+    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return 0;
+}
+
+#endif
diff --git a/Src/Modules/zftp.mdd b/Src/Modules/zftp.mdd
new file mode 100644
index 000000000..83051ae54
--- /dev/null
+++ b/Src/Modules/zftp.mdd
@@ -0,0 +1,3 @@
+autobins="zftp"
+
+objects="zftp.o"
diff --git a/Src/Zle/.cvsignore b/Src/Zle/.cvsignore
new file mode 100644
index 000000000..b10adcc3b
--- /dev/null
+++ b/Src/Zle/.cvsignore
@@ -0,0 +1,14 @@
+Makefile
+Makefile.in
+*.pro
+*.o
+*.o.c
+*.so
+*.mdh
+*.mdhi
+*.mdhs
+*.mdh.tmp
+thingies.list
+widgets.list
+zle_things.h
+zle_widget.h
diff --git a/Src/Zle/.distfiles b/Src/Zle/.distfiles
new file mode 100644
index 000000000..42c62efe9
--- /dev/null
+++ b/Src/Zle/.distfiles
@@ -0,0 +1,10 @@
+DISTFILES_SRC='
+    .cvsignore .distfiles .exrc
+    comp1.mdd comp.h comp1.c
+    compctl.mdd compctl.c
+    deltochar.mdd deltochar.c
+    zle.mdd iwidgets.list zle.h zle_bindings.c zle_hist.c
+    zle_keymap.c zle_main.c zle_misc.c zle_move.c zle_params.c
+    zle_refresh.c zle_things.sed zle_thingy.c zle_tricky.c
+    zle_utils.c zle_vi.c zle_widget.sed zle_word.c
+'
diff --git a/Src/Zle/.exrc b/Src/Zle/.exrc
new file mode 100644
index 000000000..91d0b39ef
--- /dev/null
+++ b/Src/Zle/.exrc
@@ -0,0 +1,2 @@
+set ai
+set sw=4
diff --git a/Src/Zle/.lastloc b/Src/Zle/.lastloc
new file mode 100644
index 000000000..a060de394
--- /dev/null
+++ b/Src/Zle/.lastloc
@@ -0,0 +1,10 @@
+(("/home/user2/pws/src/zsh-3.1.5/Src/Zle/zle.h" . 2619)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/zle.export" . 35)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/zle_main.c" . 13806)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/iwidgets.list" . 7831)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/comp.h" . 8512)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/zle_tricky.c" . 72920)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/comp1.c" . 9640)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/zle_vi.c" . 16035)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/zle_utils.c" . 8406)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/comp1.export" . 81))
diff --git a/Src/Zle/comp.h b/Src/Zle/comp.h
new file mode 100644
index 000000000..5d7ef74e2
--- /dev/null
+++ b/Src/Zle/comp.h
@@ -0,0 +1,137 @@
+/*
+ * comp.h - header file for completion
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#undef compctlread
+
+typedef struct compctlp  *Compctlp;
+typedef struct compctl   *Compctl;
+typedef struct compcond  *Compcond;
+
+/* node for compctl hash table (compctltab) */
+
+struct compctlp {
+    HashNode next;		/* next in hash chain               */
+    char *nam;			/* command name                     */
+    int flags;			/* CURRENTLY UNUSED                 */
+    Compctl cc;			/* pointer to the compctl desc.     */
+};
+
+/* 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;
+};
+
+#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)     */
+};
+
+/* 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/comp1.c b/Src/Zle/comp1.c
new file mode 100644
index 000000000..acd1288a6
--- /dev/null
+++ b/Src/Zle/comp1.c
@@ -0,0 +1,291 @@
+/*
+ * comp1.c - base of the completion system
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "comp1.mdh"
+
+#include "comp1.pro"
+
+/* default completion infos */
+ 
+/**/
+struct compctl cc_compos, cc_default, cc_first, cc_dummy;
+ 
+/* hash table for completion info for commands */
+ 
+/**/
+HashTable compctltab;
+
+/* Words on the command line, for use in completion */
+ 
+/**/
+int clwsize, clwnum, clwpos;
+/**/
+char **clwords;
+
+/* != 0 if in a shell function called from completion, such that read -[cl]  *
+ * will work (i.e., the line is metafied, and the above word arrays are OK). */
+
+/**/
+int incompctlfunc;
+
+/**/
+static void
+createcompctltable(void)
+{
+    compctltab = newhashtable(23, "compctltab", NULL);
+
+    compctltab->hash        = hasher;
+    compctltab->emptytable  = emptyhashtable;
+    compctltab->filltable   = NULL;
+    compctltab->addnode     = addhashnode;
+    compctltab->getnode     = gethashnode2;
+    compctltab->getnode2    = gethashnode2;
+    compctltab->removenode  = removehashnode;
+    compctltab->disablenode = NULL;
+    compctltab->enablenode  = NULL;
+    compctltab->freenode    = freecompctlp;
+    compctltab->printnode   = NULL;
+}
+
+/**/
+static void
+freecompctlp(HashNode hn)
+{
+    Compctlp ccp = (Compctlp) hn;
+
+    zsfree(ccp->nam);
+    freecompctl(ccp->cc);
+    zfree(ccp, sizeof(struct compctlp));
+}
+
+/**/
+void
+freecompctl(Compctl cc)
+{
+    if (cc == &cc_default ||
+ 	cc == &cc_first ||
+	cc == &cc_compos ||
+	--cc->refc > 0)
+	return;
+
+    zsfree(cc->keyvar);
+    zsfree(cc->glob);
+    zsfree(cc->str);
+    zsfree(cc->func);
+    zsfree(cc->explain);
+    zsfree(cc->ylist);
+    zsfree(cc->prefix);
+    zsfree(cc->suffix);
+    zsfree(cc->hpat);
+    zsfree(cc->subcmd);
+    if (cc->cond)
+	freecompcond(cc->cond);
+    if (cc->ext) {
+	Compctl n, m;
+
+	n = cc->ext;
+	do {
+	    m = (Compctl) (n->next);
+	    freecompctl(n);
+	    n = m;
+	}
+	while (n);
+    }
+    if (cc->xor && cc->xor != &cc_default)
+	freecompctl(cc->xor);
+    zfree(cc, sizeof(struct compctl));
+}
+
+/**/
+void
+freecompcond(void *a)
+{
+    Compcond cc = (Compcond) a;
+    Compcond and, or, c;
+    int n;
+
+    for (c = cc; c; c = or) {
+	or = c->or;
+	for (; c; c = and) {
+	    and = c->and;
+	    if (c->type == CCT_POS ||
+		c->type == CCT_NUMWORDS) {
+		free(c->u.r.a);
+		free(c->u.r.b);
+	    } else if (c->type == CCT_CURSUF ||
+		       c->type == CCT_CURPRE) {
+		for (n = 0; n < c->n; n++)
+		    if (c->u.s.s[n])
+			zsfree(c->u.s.s[n]);
+		free(c->u.s.s);
+	    } else if (c->type == CCT_RANGESTR ||
+		       c->type == CCT_RANGEPAT) {
+		for (n = 0; n < c->n; n++)
+		    if (c->u.l.a[n])
+			zsfree(c->u.l.a[n]);
+		free(c->u.l.a);
+		for (n = 0; n < c->n; n++)
+		    if (c->u.l.b[n])
+			zsfree(c->u.l.b[n]);
+		free(c->u.l.b);
+	    } else {
+		for (n = 0; n < c->n; n++)
+		    if (c->u.s.s[n])
+			zsfree(c->u.s.s[n]);
+		free(c->u.s.p);
+		free(c->u.s.s);
+	    }
+	    zfree(c, sizeof(struct compcond));
+	}
+    }
+}
+
+/**/
+int
+compctlread(char *name, char **args, char *ops, char *reply)
+{
+    char *buf, *bptr;
+
+    /* only allowed to be called for completion */
+    if (!incompctlfunc) {
+	zwarnnam(name, "option valid only in functions called for completion",
+		NULL, 0);
+	return 1;
+    }
+
+    if (ops['l']) {
+	/* -ln gives the index of the word the cursor is currently on, which is
+	available in cs (but remember that Zsh counts from one, not zero!) */
+	if (ops['n']) {
+	    char nbuf[14];
+
+	    if (ops['e'] || ops['E'])
+		printf("%d\n", cs + 1);
+	    if (!ops['e']) {
+		sprintf(nbuf, "%d", cs + 1);
+		setsparam(reply, ztrdup(nbuf));
+	    }
+	    return 0;
+	}
+	/* without -n, the current line is assigned to the given parameter as a
+	scalar */
+	if (ops['e'] || ops['E']) {
+	    zputs((char *) line, stdout);
+	    putchar('\n');
+	}
+	if (!ops['e'])
+	    setsparam(reply, ztrdup((char *) line));
+    } else {
+	int i;
+
+	/* -cn gives the current cursor position within the current word, which
+	is available in clwpos (but remember that Zsh counts from one, not
+	zero!) */
+	if (ops['n']) {
+	    char nbuf[14];
+
+	    if (ops['e'] || ops['E'])
+		printf("%d\n", clwpos + 1);
+	    if (!ops['e']) {
+		sprintf(nbuf, "%d", clwpos + 1);
+		setsparam(reply, ztrdup(nbuf));
+	    }
+	    return 0;
+	}
+	/* without -n, the words of the current line are assigned to the given
+	parameters separately */
+	if (ops['A'] && !ops['e']) {
+	    /* the -A option means that one array is specified, instead of
+	    many parameters */
+	    char **p, **b = (char **)zcalloc((clwnum + 1) * sizeof(char *));
+
+	    for (i = 0, p = b; i < clwnum; p++, i++)
+		*p = ztrdup(clwords[i]);
+
+	    setaparam(reply, b);
+	    return 0;
+	}
+	if (ops['e'] || ops['E']) {
+	    for (i = 0; i < clwnum; i++) {
+		zputs(clwords[i], stdout);
+		putchar('\n');
+	    }
+
+	    if (ops['e'])
+		return 0;
+	}
+
+	for (i = 0; i < clwnum && *args; reply = *args++, i++)
+	    setsparam(reply, ztrdup(clwords[i]));
+
+	if (i < clwnum) {
+	    int j, len;
+
+	    for (j = i, len = 0; j < clwnum; len += strlen(clwords[j++]));
+	    bptr = buf = zalloc(len + j - i);
+	    while (i < clwnum) {
+		strucpy(&bptr, clwords[i++]);
+		*bptr++ = ' ';
+	    }
+	    bptr[-1] = '\0';
+	} else
+	    buf = ztrdup("");
+	setsparam(reply, buf);
+    }
+    return 0;
+}
+
+/**/
+int
+boot_comp1(Module m)
+{
+    compctlreadptr = compctlread;
+    clwords = (char **) zcalloc((clwsize = 16) * sizeof(char *));
+    createcompctltable();
+    cc_compos.mask = CC_COMMPATH;
+    cc_default.refc = 10000;
+    cc_default.mask = CC_FILES;
+    cc_first.refc = 10000;
+    cc_first.mask = 0;
+    return 0;
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_comp1(Module m)
+{
+    deletehashtable(compctltab);
+    zfree(clwords, clwsize * sizeof(char *));
+    compctlreadptr = fallback_compctlread;
+    return 0;
+}
+
+#endif /* MODULE */
diff --git a/Src/Zle/comp1.export b/Src/Zle/comp1.export
new file mode 100644
index 000000000..1ac9195df
--- /dev/null
+++ b/Src/Zle/comp1.export
@@ -0,0 +1,22 @@
+#!
+cc_compos
+cc_default
+cc_dummy
+cc_first
+clwnum
+clwords
+clwpos
+clwsize
+cmatcher
+compctl_widgetptr
+compctltab
+freecmatcher
+freecmlist
+freecompcond
+freecompctl
+incompctlfunc
+instring
+patcomps
+printcompctlptr
+quotename
+rembslash
diff --git a/Src/Zle/comp1.mdd b/Src/Zle/comp1.mdd
new file mode 100644
index 000000000..9037e568a
--- /dev/null
+++ b/Src/Zle/comp1.mdd
@@ -0,0 +1,3 @@
+objects="comp1.o"
+
+headers="comp.h"
diff --git a/Src/Zle/compctl.c b/Src/Zle/compctl.c
new file mode 100644
index 000000000..658cf4161
--- /dev/null
+++ b/Src/Zle/compctl.c
@@ -0,0 +1,1085 @@
+/*
+ * compctl.c - the compctl builtin
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "compctl.mdh"
+#include "compctl.pro"
+
+#define COMP_LIST	(1<<0)	/* -L */
+#define COMP_COMMAND	(1<<1)	/* -C */
+#define COMP_DEFAULT	(1<<2)	/* -D */
+#define COMP_FIRST	(1<<3)	/* -T */
+#define COMP_REMOVE	(1<<4)
+
+#define COMP_SPECIAL (COMP_COMMAND|COMP_DEFAULT|COMP_FIRST)
+
+/* Flag for listing, command, default, or first completion */
+static int cclist;
+
+/* Mask for determining what to print */
+static unsigned long showmask = 0;
+
+/* Parse the basic flags for `compctl' */
+
+/**/
+static int
+get_compctl(char *name, char ***av, Compctl cc, int first, int isdef)
+{
+    /* Parse the basic flags for completion:
+     * first is a flag that we are not in extended completion,
+     * while hx indicates or (+) completion (need to know for
+     * default and command completion as the initial compctl is special). 
+     * cct is a temporary just to hold flags; it never needs freeing.
+     */
+    struct compctl cct;
+    char **argv = *av;
+    int ready = 0, hx = 0;
+
+    /* Handle `compctl + foo ...' specially:  turn it into
+     * a default compctl by removing it from the hash table.
+     */
+    if (first && argv[0][0] == '+' && !argv[0][1] &&
+	!(argv[1] && argv[1][0] == '-' && argv[1][1])) {
+	argv++;
+	if(argv[0] && argv[0][0] == '-')
+	    argv++;
+	*av = argv;
+	freecompctl(cc);
+ 	cclist = COMP_REMOVE;
+	return 0;
+    }
+
+    memset((void *)&cct, 0, sizeof(cct));
+
+    /* Loop through the flags until we have no more:        *
+     * those with arguments are not properly allocated yet, *
+     * we just hang on to the argument that was passed.     */
+    for (; !ready && argv[0] && argv[0][0] == '-' && (argv[0][1] || !first);) {
+	if (!argv[0][1])
+	    *argv = "-+";
+	while (!ready && *++(*argv)) {
+	    if(**argv == Meta)
+		*++*argv ^= 32;
+	    switch (**argv) {
+	    case 'f':
+		cct.mask |= CC_FILES;
+		break;
+	    case 'c':
+		cct.mask |= CC_COMMPATH;
+		break;
+	    case 'm':
+		cct.mask |= CC_EXTCMDS;
+		break;
+	    case 'w':
+		cct.mask |= CC_RESWDS;
+		break;
+	    case 'o':
+		cct.mask |= CC_OPTIONS;
+		break;
+	    case 'v':
+		cct.mask |= CC_VARS;
+		break;
+	    case 'b':
+		cct.mask |= CC_BINDINGS;
+		break;
+	    case 'A':
+		cct.mask |= CC_ARRAYS;
+		break;
+	    case 'I':
+		cct.mask |= CC_INTVARS;
+		break;
+	    case 'F':
+		cct.mask |= CC_SHFUNCS;
+		break;
+	    case 'p':
+		cct.mask |= CC_PARAMS;
+		break;
+	    case 'E':
+		cct.mask |= CC_ENVVARS;
+		break;
+	    case 'j':
+		cct.mask |= CC_JOBS;
+		break;
+	    case 'r':
+		cct.mask |= CC_RUNNING;
+		break;
+	    case 'z':
+		cct.mask |= CC_STOPPED;
+		break;
+	    case 'B':
+		cct.mask |= CC_BUILTINS;
+		break;
+	    case 'a':
+		cct.mask |= CC_ALREG | CC_ALGLOB;
+		break;
+	    case 'R':
+		cct.mask |= CC_ALREG;
+		break;
+	    case 'G':
+		cct.mask |= CC_ALGLOB;
+		break;
+	    case 'u':
+		cct.mask |= CC_USERS;
+		break;
+	    case 'd':
+		cct.mask |= CC_DISCMDS;
+		break;
+	    case 'e':
+		cct.mask |= CC_EXCMDS;
+		break;
+	    case 'N':
+		cct.mask |= CC_SCALARS;
+		break;
+	    case 'O':
+		cct.mask |= CC_READONLYS;
+		break;
+	    case 'Z':
+		cct.mask |= CC_SPECIALS;
+		break;
+	    case 'q':
+		cct.mask |= CC_REMOVE;
+		break;
+	    case 'U':
+		cct.mask |= CC_DELETE;
+		break;
+	    case 'n':
+		cct.mask |= CC_NAMED;
+		break;
+	    case 'Q':
+		cct.mask |= CC_QUOTEFLAG;
+		break;
+	    case '/':
+		cct.mask |= CC_DIRS;
+		break;
+	    case 'k':
+		if ((*argv)[1]) {
+		    cct.keyvar = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "variable name expected after -%c", NULL,
+			    **argv);
+		    return 1;
+		} else {
+		    cct.keyvar = *++argv;
+		    *argv = "" - 1;
+		}
+		break;
+	    case 'K':
+		if ((*argv)[1]) {
+		    cct.func = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "function name expected after -%c", NULL,
+			    **argv);
+		    return 1;
+		} else {
+		    cct.func = *++argv;
+		    *argv = "" - 1;
+		}
+		break;
+	    case 'Y':
+		cct.mask |= CC_EXPANDEXPL;
+		goto expl;
+	    case 'X':
+		cct.mask &= ~CC_EXPANDEXPL;
+	    expl:
+		if ((*argv)[1]) {
+		    cct.explain = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "string expected after -%c", NULL, **argv);
+		    return 1;
+		} else {
+		    cct.explain = *++argv;
+		    *argv = "" - 1;
+		}
+		break;
+	    case 'y':
+		if ((*argv)[1]) {
+		    cct.ylist = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "function/variable expected after -%c",
+			     NULL, **argv);
+		} else {
+		    cct.ylist = *++argv;
+		    *argv = "" - 1;
+		}
+		break;
+	    case 'P':
+		if ((*argv)[1]) {
+		    cct.prefix = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "string expected after -%c", NULL, **argv);
+		    return 1;
+		} else {
+		    cct.prefix = *++argv;
+		    *argv = "" - 1;
+		}
+		break;
+	    case 'S':
+		if ((*argv)[1]) {
+		    cct.suffix = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "string expected after -%c", NULL, **argv);
+		    return 1;
+		} else {
+		    cct.suffix = *++argv;
+		    *argv = "" - 1;
+		}
+		break;
+	    case 'g':
+		if ((*argv)[1]) {
+		    cct.glob = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "glob pattern expected after -%c", NULL,
+			    **argv);
+		    return 1;
+		} else {
+		    cct.glob = *++argv;
+		    *argv = "" - 1;
+		}
+		break;
+	    case 's':
+		if ((*argv)[1]) {
+		    cct.str = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "command string expected after -%c", NULL,
+			    **argv);
+		    return 1;
+		} else {
+		    cct.str = *++argv;
+		    *argv = "" - 1;
+		}
+		break;
+	    case 'l':
+		if ((*argv)[1]) {
+		    cct.subcmd = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "command name expected after -%c", NULL,
+			    **argv);
+		    return 1;
+		} else {
+		    cct.subcmd = *++argv;
+		    *argv = "" - 1;
+		}
+		break;
+	    case 'W':
+		if ((*argv)[1]) {
+		    cct.withd = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "path expected after -%c", NULL,
+			    **argv);
+		    return 1;
+		} else {
+		    cct.withd = *++argv;
+		    *argv = "" - 1;
+		}
+		break;
+	    case 'H':
+		if ((*argv)[1])
+		    cct.hnum = atoi((*argv) + 1);
+		else if (argv[1])
+		    cct.hnum = atoi(*++argv);
+		else {
+		    zwarnnam(name, "number expected after -%c", NULL,
+			    **argv);
+		    return 1;
+		}
+		if (!argv[1]) {
+		    zwarnnam(name, "missing pattern after -%c", NULL,
+			    **argv);
+		    return 1;
+		}
+		cct.hpat = *++argv;
+		if (cct.hnum < 1)
+		    cct.hnum = 0;
+		if (*cct.hpat == '*' && !cct.hpat[1])
+		    cct.hpat = "";
+		*argv = "" - 1;
+		break;
+	    case 'C':
+		if (first && !hx) {
+		    cclist |= COMP_COMMAND;
+		} else {
+		    zwarnnam(name, "misplaced command completion (-C) flag",
+			    NULL, 0);
+		    return 1;
+		}
+		break;
+	    case 'D':
+		if (first && !hx) {
+		    isdef = 1;
+		    cclist |= COMP_DEFAULT;
+		} else {
+		    zwarnnam(name, "misplaced default completion (-D) flag",
+			    NULL, 0);
+		    return 1;
+		}
+		break;
+ 	    case 'T':
+              if (first && !hx) {
+ 		    cclist |= COMP_FIRST;
+ 		} else {
+ 		    zwarnnam(name, "misplaced first completion (-T) flag",
+ 			    NULL, 0);
+ 		    return 1;
+ 		}
+ 		break;
+	    case 'L':
+		if (!first || hx) {
+		    zwarnnam(name, "illegal use of -L flag", NULL, 0);
+		    return 1;
+		}
+		cclist |= COMP_LIST;
+		break;
+	    case 'x':
+		if (!argv[1]) {
+		    zwarnnam(name, "condition expected after -%c", NULL,
+			    **argv);
+		    return 1;
+		}
+		if (first) {
+		    argv++;
+		    if (get_xcompctl(name, &argv, &cct, isdef)) {
+			if (cct.ext)
+			    freecompctl(cct.ext);
+			return 1;
+		    }
+		    ready = 2;
+		} else {
+		    zwarnnam(name, "recursive extended completion not allowed",
+			    NULL, 0);
+		    return 1;
+		}
+		break;
+	    default:
+		if (!first && (**argv == '-' || **argv == '+'))
+		    (*argv)--, argv--, ready = 1;
+		else {
+		    zwarnnam(name, "bad option: -%c", NULL, **argv);
+		    return 1;
+		}
+	    }
+	}
+
+	if (*++argv && (!ready || ready == 2) &&
+	    **argv == '+' && !argv[0][1]) {
+	    /* There's an alternative (+) completion:  assign
+	     * what we have so far before moving on to that.
+	     */
+	    if (cc_assign(name, &cc, &cct, first && !hx))
+		return 1;
+
+	    hx = 1;
+	    ready = 0;
+
+	    if (!*++argv || **argv != '-' ||
+		(**argv == '-' && (!argv[0][1] ||
+				   (argv[0][1] == '-' && !argv[0][2])))) {
+		/* No argument to +, which means do default completion */
+		if (isdef)
+		    zwarnnam(name,
+			    "recursive xor'd default completions not allowed",
+			    NULL, 0);
+		else
+		    cc->xor = &cc_default;
+	    } else {
+		/* more flags follow:  prepare to loop again */
+		cc->xor = (Compctl) zcalloc(sizeof(*cc));
+		cc = cc->xor;
+		memset((void *)&cct, 0, sizeof(cct));
+	    }
+	}
+    }
+    if (!ready && *argv && **argv == '-')
+	argv++;
+
+    if (! (cct.mask & (CC_EXCMDS | CC_DISCMDS)))
+	cct.mask |= CC_EXCMDS;
+
+    /* assign the last set of flags we parsed */
+    if (cc_assign(name, &cc, &cct, first && !hx))
+	return 1;
+
+    *av = argv;
+
+    return 0;
+}
+
+/* Handle the -x ... -- part of compctl. */
+
+/**/
+static int
+get_xcompctl(char *name, char ***av, Compctl cc, int isdef)
+{
+    char **argv = *av, *t, *tt, sav;
+    int n, l = 0, ready = 0;
+    Compcond m, c, o;
+    Compctl *next = &(cc->ext);
+
+    while (!ready) {
+	/* o keeps track of or's, m remembers the starting condition,
+	 * c is the current condition being parsed
+	 */
+	o = m = c = (Compcond) zcalloc(sizeof(*c));
+	/* Loop over each condition:  something like 's[...][...], p[...]' */
+	for (t = *argv; *t;) {
+	    while (*t == ' ')
+		t++;
+	    /* First get the condition code */
+	    switch (*t) {
+	    case 's':
+		c->type = CCT_CURSUF;
+		break;
+	    case 'S':
+		c->type = CCT_CURPRE;
+		break;
+	    case 'p':
+		c->type = CCT_POS;
+		break;
+	    case 'c':
+		c->type = CCT_CURSTR;
+		break;
+	    case 'C':
+		c->type = CCT_CURPAT;
+		break;
+	    case 'w':
+		c->type = CCT_WORDSTR;
+		break;
+	    case 'W':
+		c->type = CCT_WORDPAT;
+		break;
+	    case 'n':
+		c->type = CCT_CURSUB;
+		break;
+	    case 'N':
+		c->type = CCT_CURSUBC;
+		break;
+	    case 'm':
+		c->type = CCT_NUMWORDS;
+		break;
+	    case 'r':
+		c->type = CCT_RANGESTR;
+		break;
+	    case 'R':
+		c->type = CCT_RANGEPAT;
+		break;
+	    default:
+		t[1] = '\0';
+		zwarnnam(name, "unknown condition code: %s", t, 0);
+		zfree(m, sizeof(struct compcond));
+
+		return 1;
+	    }
+	    /* Now get the arguments in square brackets */
+	    if (t[1] != '[') {
+		t[1] = '\0';
+		zwarnnam(name, "expected condition after condition code: %s", t, 0);
+		zfree(m, sizeof(struct compcond));
+
+		return 1;
+	    }
+	    t++;
+	    /* First count how many or'd arguments there are,
+	     * marking the active ]'s and ,'s with unprintable characters.
+	     */
+	    for (n = 0, tt = t; *tt == '['; n++) {
+		for (l = 1, tt++; *tt && l; tt++)
+		    if (*tt == '\\' && tt[1])
+			tt++;
+		    else if (*tt == '[')
+			l++;
+		    else if (*tt == ']')
+			l--;
+		    else if (l == 1 && *tt == ',')
+			*tt = '\201';
+		if (tt[-1] == ']')
+		    tt[-1] = '\200';
+	    }
+
+	    if (l) {
+		t[1] = '\0';
+		zwarnnam(name, "error after condition code: %s", t, 0);
+		zfree(m, sizeof(struct compcond));
+
+		return 1;
+	    }
+	    c->n = n;
+
+	    /* Allocate space for all the arguments of the conditions */
+	    if (c->type == CCT_POS ||
+		c->type == CCT_NUMWORDS) {
+		c->u.r.a = (int *)zcalloc(n * sizeof(int));
+		c->u.r.b = (int *)zcalloc(n * sizeof(int));
+	    } else if (c->type == CCT_CURSUF ||
+		       c->type == CCT_CURPRE)
+		c->u.s.s = (char **)zcalloc(n * sizeof(char *));
+
+	    else if (c->type == CCT_RANGESTR ||
+		     c->type == CCT_RANGEPAT) {
+		c->u.l.a = (char **)zcalloc(n * sizeof(char *));
+		c->u.l.b = (char **)zcalloc(n * sizeof(char *));
+	    } else {
+		c->u.s.p = (int *)zcalloc(n * sizeof(int));
+		c->u.s.s = (char **)zcalloc(n * sizeof(char *));
+	    }
+	    /* Now loop over the actual arguments */
+	    for (l = 0; *t == '['; l++, t++) {
+		for (t++; *t && *t == ' '; t++);
+		tt = t;
+		if (c->type == CCT_POS ||
+		    c->type == CCT_NUMWORDS) {
+		    /* p[...] or m[...]:  one or two numbers expected */
+		    for (; *t && *t != '\201' && *t != '\200'; t++);
+		    if (!(sav = *t)) {
+			zwarnnam(name, "error in condition", NULL, 0);
+			freecompcond(m);
+			return 1;
+		    }
+		    *t = '\0';
+		    c->u.r.a[l] = atoi(tt);
+		    /* Second argument is optional:  see if it's there */
+		    if (sav == '\200')
+			/* no:  copy first argument */
+			c->u.r.b[l] = c->u.r.a[l];
+		    else {
+			tt = ++t;
+			for (; *t && *t != '\200'; t++);
+			if (!*t) {
+			    zwarnnam(name, "error in condition", NULL, 0);
+			    freecompcond(m);
+			    return 1;
+			}
+			*t = '\0';
+			c->u.r.b[l] = atoi(tt);
+		    }
+		} else if (c->type == CCT_CURSUF ||
+			   c->type == CCT_CURPRE) {
+		    /* -s[..] or -S[..]:  single string expected */
+		    for (; *t && *t != '\200'; t++)
+			if (*t == '\201')
+			    *t = ',';
+		    if (!*t) {
+			zwarnnam(name, "error in condition", NULL, 0);
+			freecompcond(m);
+			return 1;
+		    }
+		    *t = '\0';
+		    c->u.s.s[l] = ztrdup(tt);
+		} else if (c->type == CCT_RANGESTR ||
+			   c->type == CCT_RANGEPAT) {
+		    /* -r[..,..] or -R[..,..]:  two strings expected */
+		    for (; *t && *t != '\201'; t++);
+		    if (!*t) {
+			zwarnnam(name, "error in condition", NULL, 0);
+			freecompcond(m);
+			return 1;
+		    }
+		    *t = '\0';
+		    c->u.l.a[l] = ztrdup(tt);
+		    tt = ++t;
+		    /* any more commas are text, not active */
+		    for (; *t && *t != '\200'; t++)
+			if (*t == '\201')
+			    *t = ',';
+		    if (!*t) {
+			zwarnnam(name, "error in condition", NULL, 0);
+			freecompcond(m);
+			return 1;
+		    }
+		    *t = '\0';
+		    c->u.l.b[l] = ztrdup(tt);
+		} else {
+		    /* remaining patterns are number followed by string */
+		    for (; *t && *t != '\200' && *t != '\201'; t++);
+		    if (!*t || *t == '\200') {
+			zwarnnam(name, "error in condition", NULL, 0);
+			freecompcond(m);
+			return 1;
+		    }
+		    *t = '\0';
+		    c->u.s.p[l] = atoi(tt);
+		    tt = ++t;
+		    for (; *t && *t != '\200'; t++)
+			if (*t == '\201')
+			    *t = ',';
+		    if (!*t) {
+			zwarnnam(name, "error in condition", NULL, 0);
+			freecompcond(m);
+			return 1;
+		    }
+		    *t = '\0';
+		    c->u.s.s[l] = ztrdup(tt);
+		}
+	    }
+	    while (*t == ' ')
+		t++;
+	    if (*t == ',') {
+		/* Another condition to `or' */
+		o->or = c = (Compcond) zcalloc(sizeof(*c));
+		o = c;
+		t++;
+	    } else if (*t) {
+		/* Another condition to `and' */
+		c->and = (Compcond) zcalloc(sizeof(*c));
+		c = c->and;
+	    }
+	}
+	/* Assign condition to current compctl */
+	*next = (Compctl) zcalloc(sizeof(*cc));
+	(*next)->cond = m;
+	argv++;
+	/* End of the condition; get the flags that go with it. */
+	if (get_compctl(name, &argv, *next, 0, isdef))
+	    return 1;
+ 	if ((!argv || !*argv) && (cclist & COMP_SPECIAL))
+ 	    /* default, first, or command completion finished */
+	    ready = 1;
+	else {
+	    /* see if we are looking for more conditions or are
+	     * ready to return (ready = 1)
+	     */
+	    if (!argv || !*argv || **argv != '-' ||
+		((!argv[0][1] || argv[0][1] == '+') && !argv[1])) {
+		zwarnnam(name, "missing command names", NULL, 0);
+		return 1;
+	    }
+	    if (!strcmp(*argv, "--"))
+		ready = 1;
+	    else if (!strcmp(*argv, "-+") && argv[1] &&
+		     !strcmp(argv[1], "--")) {
+		ready = 1;
+		argv++;
+	    }
+	    argv++;
+	    /* prepare to put the next lot of conditions on the end */
+	    next = &((*next)->next);
+	}
+    }
+    /* save position at end of parsing */
+    *av = argv - 1;
+    return 0;
+}
+
+/**/
+static int
+cc_assign(char *name, Compctl *ccptr, Compctl cct, int reass)
+{
+    /* Copy over the details from the values in cct to those in *ccptr */
+    Compctl cc;
+
+    if (cct->subcmd && (cct->keyvar || cct->glob || cct->str ||
+			cct->func || cct->explain || cct->ylist ||
+			cct->prefix)) {
+	zwarnnam(name, "illegal combination of options", NULL, 0);
+	return 1;
+    }
+
+    /* Handle assignment of new default or command completion */
+    if (reass && !(cclist & COMP_LIST)) {
+	/* if not listing */
+	if (cclist == (COMP_COMMAND|COMP_DEFAULT)
+	    || cclist == (COMP_COMMAND|COMP_FIRST)
+	    || cclist == (COMP_DEFAULT|COMP_FIRST)
+	    || cclist == COMP_SPECIAL) {
+ 	    zwarnnam(name, "can't set -D, -T, and -C simultaneously", NULL, 0);
+	    /* ... because the following code wouldn't work. */
+	    return 1;
+	}
+	if (cclist & COMP_COMMAND) {
+	    /* command */
+	    *ccptr = &cc_compos;
+	    cc_reassign(*ccptr);
+	} else if (cclist & COMP_DEFAULT) {
+	    /* default */
+	    *ccptr = &cc_default;
+	    cc_reassign(*ccptr);
+ 	} else if (cclist & COMP_FIRST) {
+ 	    /* first */
+ 	    *ccptr = &cc_first;
+ 	    cc_reassign(*ccptr);
+	}
+    }
+
+    /* Free the old compctl */
+    cc = *ccptr;
+    zsfree(cc->keyvar);
+    zsfree(cc->glob);
+    zsfree(cc->str);
+    zsfree(cc->func);
+    zsfree(cc->explain);
+    zsfree(cc->ylist);
+    zsfree(cc->prefix);
+    zsfree(cc->suffix);
+    zsfree(cc->subcmd);
+    zsfree(cc->withd);
+    zsfree(cc->hpat);
+    
+    /* and copy over the new stuff, (permanently) allocating
+     * space for strings.
+     */
+    cc->mask = cct->mask;
+    cc->keyvar = ztrdup(cct->keyvar);
+    cc->glob = ztrdup(cct->glob);
+    cc->str = ztrdup(cct->str);
+    cc->func = ztrdup(cct->func);
+    cc->explain = ztrdup(cct->explain);
+    cc->ylist = ztrdup(cct->ylist);
+    cc->prefix = ztrdup(cct->prefix);
+    cc->suffix = ztrdup(cct->suffix);
+    cc->subcmd = ztrdup(cct->subcmd);
+    cc->withd = ztrdup(cct->withd);
+    cc->hpat = ztrdup(cct->hpat);
+    cc->hnum = cct->hnum;
+
+    /* careful with extended completion:  it's already allocated */
+    cc->ext = cct->ext;
+
+    return 0;
+}
+
+/**/
+static void
+cc_reassign(Compctl cc)
+{
+    /* Free up a new default or command completion:
+     * this is a hack to free up the parts which should be deleted,
+     * without removing the basic variable which is statically allocated
+     */
+    Compctl c2;
+
+    c2 = (Compctl) zcalloc(sizeof *cc);
+    c2->xor = cc->xor;
+    c2->ext = cc->ext;
+    c2->refc = 1;
+
+    freecompctl(c2);
+
+    cc->ext = cc->xor = NULL;
+}
+
+/**/
+static void
+compctl_process_cc(char **s, Compctl cc)
+{
+    Compctlp ccp;
+
+    if (cclist & COMP_REMOVE) {
+	/* Delete entries for the commands listed */
+	for (; *s; s++) {
+	    if ((ccp = (Compctlp) compctltab->removenode(compctltab, *s)))
+		compctltab->freenode((HashNode) ccp);
+	}
+    } else {
+	/* Add the compctl just read to the hash table */
+
+	cc->refc = 0;
+	for (; *s; s++) {
+	    cc->refc++;
+	    ccp = (Compctlp) zalloc(sizeof *ccp);
+	    ccp->cc = cc;
+	    compctltab->addnode(compctltab, ztrdup(*s), ccp);
+	}
+    }
+}
+
+/* Print a `compctl' */
+
+/**/
+static void
+printcompctl(char *s, Compctl cc, int printflags)
+{
+    Compctl cc2;
+    char *css = "fcqovbAIFpEjrzBRGudeNOZUnQmw/";
+    char *mss = " pcCwWsSnNmrR";
+    unsigned long t = 0x7fffffff;
+    unsigned long flags = cc->mask;
+    unsigned long oldshowmask;
+
+    if ((flags & CC_EXCMDS) && !(flags & CC_DISCMDS))
+	flags &= ~CC_EXCMDS;
+
+    /* If showmask is non-zero, then print only those *
+     * commands with that flag set.                   */
+    if (showmask && !(flags & showmask))
+	return;
+
+    /* Temporarily clear showmask in case we make *
+     * recursive calls to printcompctl.           */
+    oldshowmask = showmask;
+    showmask = 0;
+
+    /* print either command name or start of compctl command itself */
+    if (s) {
+	if (cclist & COMP_LIST) {
+	    printf("compctl");
+	    if (cc == &cc_compos)
+		printf(" -C");
+	    if (cc == &cc_default)
+		printf(" -D");
+	    if (cc == &cc_first)
+		printf(" -T");
+	} else
+	    quotedzputs(s, stdout);
+    }
+
+    /* loop through flags w/o args that are set, printing them if so */
+    if (flags & t) {
+	printf(" -");
+	if ((flags & (CC_ALREG | CC_ALGLOB)) == (CC_ALREG | CC_ALGLOB))
+	    putchar('a'), flags &= ~(CC_ALREG | CC_ALGLOB);
+	while (*css) {
+	    if (flags & t & 1)
+		putchar(*css);
+	    css++;
+	    flags >>= 1;
+	    t >>= 1;
+	}
+    }
+
+    /* now flags with arguments */
+    flags = cc->mask;
+    printif(cc->keyvar, 'k');
+    printif(cc->func, 'K');
+    printif(cc->explain, (cc->mask & CC_EXPANDEXPL) ? 'Y' : 'X');
+    printif(cc->ylist, 'y');
+    printif(cc->prefix, 'P');
+    printif(cc->suffix, 'S');
+    printif(cc->glob, 'g');
+    printif(cc->str, 's');
+    printif(cc->subcmd, 'l');
+    printif(cc->withd, 'W');
+    if (cc->hpat) {
+	printf(" -H %d ", cc->hnum);
+	quotedzputs(cc->hpat, stdout);
+    }
+
+    /* now the -x ... -- extended completion part */
+    if (cc->ext) {
+	Compcond c, o;
+	int i;
+
+	cc2 = cc->ext;
+	printf(" -x");
+
+	while (cc2) {
+	    /* loop over conditions */
+	    c = cc2->cond;
+
+	    printf(" '");
+	    for (c = cc2->cond; c;) {
+		/* loop over or's */
+		o = c->or;
+		while (c) {
+		    /* loop over and's */
+		    putchar(mss[c->type]);
+
+		    for (i = 0; i < c->n; i++) {
+			/* for all [...]'s of a given condition */
+			putchar('[');
+			switch (c->type) {
+			case CCT_POS:
+			case CCT_NUMWORDS:
+			    printf("%d,%d", c->u.r.a[i], c->u.r.b[i]);
+			    break;
+			case CCT_CURSUF:
+			case CCT_CURPRE:
+			    printqt(c->u.s.s[i]);
+			    break;
+			case CCT_RANGESTR:
+			case CCT_RANGEPAT:
+			    printqt(c->u.l.a[i]);
+			    putchar(',');
+			    printqt(c->u.l.b[i]);
+			    break;
+			default:
+			    printf("%d,", c->u.s.p[i]);
+			    printqt(c->u.s.s[i]);
+			}
+			putchar(']');
+		    }
+		    if ((c = c->and))
+			putchar(' ');
+		}
+		if ((c = o))
+		    printf(" , ");
+	    }
+	    putchar('\'');
+	    c = cc2->cond;
+	    cc2->cond = NULL;
+	    /* now print the flags for the current condition */
+	    printcompctl(NULL, cc2, 0);
+	    cc2->cond = c;
+	    if ((cc2 = (Compctl) (cc2->next)))
+		printf(" -");
+	}
+	if (cclist & COMP_LIST)
+	    printf(" --");
+    }
+    if (cc && cc->xor) {
+	/* print xor'd (+) completions */
+	printf(" +");
+	if (cc->xor != &cc_default)
+	    printcompctl(NULL, cc->xor, 0);
+    }
+    if (s) {
+	if ((cclist & COMP_LIST) && (cc != &cc_compos)
+	    && (cc != &cc_default) && (cc != &cc_first)) {
+	    if(s[0] == '-' || s[0] == '+')
+		printf(" -");
+	    putchar(' ');
+	    quotedzputs(s, stdout);
+	}
+	putchar('\n');
+    }
+
+    showmask = oldshowmask;
+}
+
+/**/
+static void
+printcompctlp(HashNode hn, int printflags)
+{
+    Compctlp ccp = (Compctlp) hn;
+
+    /* Function needed for use by scanhashtable() */
+    printcompctl(ccp->nam, ccp->cc, printflags);
+}
+
+/* Main entry point for the `compctl' builtin */
+
+/**/
+static int
+bin_compctl(char *name, char **argv, char *ops, int func)
+{
+    Compctl cc = NULL;
+    int ret = 0;
+
+    /* clear static flags */
+    cclist = 0;
+    showmask = 0;
+
+    /* Parse all the arguments */
+    if (*argv) {
+	cc = (Compctl) zcalloc(sizeof(*cc));
+	if (get_compctl(name, &argv, cc, 1, 0)) {
+	    freecompctl(cc);
+	    return 1;
+	}
+
+	/* remember flags for printing */
+	showmask = cc->mask;
+	if ((showmask & CC_EXCMDS) && !(showmask & CC_DISCMDS))
+	    showmask &= ~CC_EXCMDS;
+
+	/* if no command arguments or just listing, we don't want cc */
+	if (!*argv || (cclist & COMP_LIST))
+	    freecompctl(cc);
+    }
+
+    /* If no commands and no -C, -T, or -D, print all the compctl's *
+     * If some flags (other than -C, -T, or -D) were given, then    *
+     * only print compctl containing those flags.                   */
+    if (!*argv && !(cclist & COMP_SPECIAL)) {
+	scanhashtable(compctltab, 1, 0, 0, compctltab->printnode, 0);
+	printcompctl((cclist & COMP_LIST) ? "" : "COMMAND", &cc_compos, 0);
+	printcompctl((cclist & COMP_LIST) ? "" : "DEFAULT", &cc_default, 0);
+ 	printcompctl((cclist & COMP_LIST) ? "" : "FIRST", &cc_first, 0);
+	return ret;
+    }
+
+    /* If we're listing and we've made it to here, then there are arguments *
+     * or a COMP_SPECIAL flag (-D, -C, -T), so print only those.            */
+    if (cclist & COMP_LIST) {
+	HashNode hn;
+	char **ptr;
+
+	showmask = 0;
+	for (ptr = argv; *ptr; ptr++) {
+	    if ((hn = compctltab->getnode(compctltab, *ptr))) {
+		compctltab->printnode(hn, 0);
+	    } else {
+		zwarnnam(name, "no compctl defined for %s", *ptr, 0);
+		ret = 1;
+	    }
+	}
+	if (cclist & COMP_COMMAND)
+	    printcompctl("", &cc_compos, 0);
+	if (cclist & COMP_DEFAULT)
+	    printcompctl("", &cc_default, 0);
+	if (cclist & COMP_FIRST)
+	    printcompctl("", &cc_first, 0);
+	return ret;
+    }
+
+    /* Assign the compctl to the commands given */
+    if (*argv) {
+	if(cclist & COMP_SPECIAL)
+	    /* Ideally we'd handle this properly, setting both the *
+	     * special and normal completions.  For the moment,    *
+	     * this is better than silently failing.               */
+	    zwarnnam(name, "extraneous commands ignored", NULL, 0);
+	else
+	    compctl_process_cc(argv, cc);
+    }
+
+    return ret;
+}
+
+static struct builtin bintab[] = {
+    BUILTIN("compctl", 0, bin_compctl, 0, -1, 0, NULL, NULL),
+};
+
+/**/
+int
+boot_compctl(Module m)
+{
+    if(!addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)))
+	return 1;
+    compctltab->printnode = printcompctlp;
+    return 0;
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_compctl(Module m)
+{
+    compctltab->printnode = NULL;
+    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return 0;
+}
+#endif
diff --git a/Src/Zle/compctl.mdd b/Src/Zle/compctl.mdd
new file mode 100644
index 000000000..c83ecda29
--- /dev/null
+++ b/Src/Zle/compctl.mdd
@@ -0,0 +1,5 @@
+moddeps="comp1"
+
+autobins="compctl"
+
+objects="compctl.o"
diff --git a/Src/Zle/deltochar.c b/Src/Zle/deltochar.c
new file mode 100644
index 000000000..87f8593b8
--- /dev/null
+++ b/Src/Zle/deltochar.c
@@ -0,0 +1,91 @@
+/*
+ * deltochar.c - ZLE module implementing Emacs' zap-to-char
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1996-1997 Peter Stephenson
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Peter Stephenson or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Peter Stephenson and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Peter Stephenson and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Peter Stephenson and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "deltochar.mdh"
+#include "deltochar.pro"
+
+static Widget w_deletetochar;
+
+/**/
+static void
+deltochar(void)
+{
+    int c = getkey(0), dest = cs, ok = 0, n = zmult;
+
+    if (n > 0) {
+	while (n-- && dest != ll) {
+	    while (dest != ll && line[dest] != c)
+		dest++;
+	    if (dest != ll) {
+		dest++;
+		if (!n) {
+		    foredel(dest - cs);
+		    ok++;
+		}
+	    }
+	}
+    } else {
+	/* ignore character cursor is on when scanning backwards */
+	if (dest)
+	    dest--;
+	while (n++ && dest != 0) {
+	    while (dest != 0 && line[dest] != c)
+		dest--;
+	    if (line[dest] == c && !n) {
+		backdel(cs - dest);
+		ok++;
+	    }
+	}
+    }
+    if (!ok)
+	feep();
+}
+
+/**/
+int
+boot_deltochar(Module m)
+{
+    w_deletetochar = addzlefunction("delete-to-char", deltochar, ZLE_KEEPSUFFIX);
+    if (w_deletetochar)
+	return 0;
+    zwarnnam(m->nam, "name clash when adding ZLE function `delete-to-char'",
+	     NULL, 0);
+    return -1;
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_deltochar(Module m)
+{
+    deletezlefunction(w_deletetochar);
+    return 0;
+}
+#endif
diff --git a/Src/Zle/deltochar.mdd b/Src/Zle/deltochar.mdd
new file mode 100644
index 000000000..4d1f89d1c
--- /dev/null
+++ b/Src/Zle/deltochar.mdd
@@ -0,0 +1,3 @@
+moddeps="zle"
+
+objects="deltochar.o"
diff --git a/Src/Zle/iwidgets.list b/Src/Zle/iwidgets.list
new file mode 100644
index 000000000..01862160e
--- /dev/null
+++ b/Src/Zle/iwidgets.list
@@ -0,0 +1,172 @@
+#
+# intwidgets.list - list of internally implemented ZLE widgets
+#
+# Each line has the form:
+#
+# "canonical-name" , functionname , ZLE_FLAGS
+#
+# `#' starts a comment.  Blank lines are ignored.
+#
+
+"accept-and-hold", acceptandhold, 0
+"accept-and-infer-next-history", acceptandinfernexthistory, 0
+"accept-and-menu-complete", acceptandmenucomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX
+"accept-line", acceptline, 0
+"accept-line-and-down-history", acceptlineanddownhistory, 0
+"backward-char", backwardchar, 0
+"backward-delete-char", backwarddeletechar, ZLE_KEEPSUFFIX
+"backward-delete-word", backwarddeleteword, ZLE_KEEPSUFFIX
+"backward-kill-line", backwardkillline, ZLE_KILL | ZLE_KEEPSUFFIX
+"backward-kill-word", backwardkillword, ZLE_KILL | ZLE_KEEPSUFFIX
+"backward-word", backwardword, 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
+"copy-region-as-kill", copyregionaskill, ZLE_KEEPSUFFIX
+"delete-char", deletechar, ZLE_KEEPSUFFIX
+"delete-char-or-list", deletecharorlist, ZLE_MENUCMP | ZLE_KEEPSUFFIX
+"delete-word", deleteword, ZLE_KEEPSUFFIX
+"describe-key-briefly", describekeybriefly, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"digit-argument", digitargument, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"down-case-word", downcaseword, 0
+"down-history", downhistory, 0
+"down-line-or-history", downlineorhistory, ZLE_LINEMOVE | ZLE_LASTCOL
+"down-line-or-search", downlineorsearch, ZLE_LINEMOVE | ZLE_LASTCOL
+"emacs-backward-word", emacsbackwardword, 0
+"emacs-forward-word", emacsforwardword, 0
+"end-of-buffer-or-history", endofbufferorhistory, 0
+"end-of-history", endofhistory, 0
+"end-of-line", endofline, 0
+"end-of-line-hist", endoflinehist, 0
+"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-word", expandword, 0
+"forward-char", forwardchar, 0
+"forward-word", forwardword, 0
+"get-line", getline, 0
+"gosmacs-transpose-chars", gosmacstransposechars, 0
+"history-beginning-search-backward", historybeginningsearchbackward, 0
+"history-beginning-search-forward", historybeginningsearchforward, 0
+"history-incremental-search-backward", historyincrementalsearchbackward, 0
+"history-incremental-search-forward", historyincrementalsearchforward, 0
+"history-search-backward", historysearchbackward, 0
+"history-search-forward", historysearchforward, 0
+"infer-next-history", infernexthistory, 0
+"insert-last-word", insertlastword, ZLE_MENUCMP | ZLE_KEEPSUFFIX
+"kill-buffer", killbuffer, ZLE_KILL | ZLE_KEEPSUFFIX
+"kill-line", killline, ZLE_KILL | ZLE_KEEPSUFFIX
+"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-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
+"overwrite-mode", overwritemode, 0
+"pound-insert", poundinsert, 0
+"push-input", pushinput, 0
+"push-line", pushline, 0
+"push-line-or-edit", pushlineoredit, 0
+"quoted-insert", quotedinsert, ZLE_MENUCMP | ZLE_KEEPSUFFIX
+"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
+"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
+"transpose-chars", transposechars, 0
+"transpose-words", transposewords, 0
+"undefined-key", undefinedkey, 0
+"undo", undo, 0
+"universal-argument", universalargument, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"up-case-word", upcaseword, 0
+"up-history", uphistory, 0
+"up-line-or-history", uplineorhistory, ZLE_LINEMOVE | ZLE_LASTCOL
+"up-line-or-search", uplineorsearch, ZLE_LINEMOVE | ZLE_LASTCOL
+"vi-add-eol", viaddeol, 0
+"vi-add-next", viaddnext, 0
+"vi-backward-blank-word", vibackwardblankword, 0
+"vi-backward-char", vibackwardchar, 0
+"vi-backward-delete-char", vibackwarddeletechar, ZLE_KEEPSUFFIX
+"vi-backward-kill-word", vibackwardkillword, ZLE_KILL | ZLE_KEEPSUFFIX
+"vi-backward-word", vibackwardword, 0
+"vi-beginning-of-line", vibeginningofline, 0
+"vi-caps-lock-panic", vicapslockpanic, 0
+"vi-change", vichange, 0
+"vi-change-eol", vichangeeol, 0
+"vi-change-whole-line", vichangewholeline, 0
+"vi-cmd-mode", vicmdmode, 0
+"vi-delete", videlete, ZLE_KILL | ZLE_KEEPSUFFIX
+"vi-delete-char", videletechar, ZLE_KEEPSUFFIX
+"vi-digit-or-beginning-of-line", vidigitorbeginningofline, 0
+"vi-down-line-or-history", vidownlineorhistory, ZLE_LINEMOVE
+"vi-end-of-line", viendofline, ZLE_LASTCOL
+"vi-fetch-history", vifetchhistory, 0
+"vi-find-next-char", vifindnextchar, 0
+"vi-find-next-char-skip", vifindnextcharskip, 0
+"vi-find-prev-char", vifindprevchar, 0
+"vi-find-prev-char-skip", vifindprevcharskip, 0
+"vi-first-non-blank", vifirstnonblank, 0
+"vi-forward-blank-word", viforwardblankword, 0
+"vi-forward-blank-word-end", viforwardblankwordend, 0
+"vi-forward-char", viforwardchar, 0
+"vi-forward-word", viforwardword, 0
+"vi-forward-word-end", viforwardwordend, 0
+"vi-goto-column", vigotocolumn, 0
+"vi-goto-mark", vigotomark, 0
+"vi-goto-mark-line", vigotomarkline, 0
+"vi-history-search-backward", vihistorysearchbackward, 0
+"vi-history-search-forward", vihistorysearchforward, 0
+"vi-indent", viindent, 0
+"vi-insert", viinsert, 0
+"vi-insert-bol", viinsertbol, 0
+"vi-join", vijoin, 0
+"vi-kill-eol", vikilleol, ZLE_KILL | ZLE_KEEPSUFFIX
+"vi-kill-line", vikillline, ZLE_KILL | ZLE_KEEPSUFFIX
+"vi-match-bracket", vimatchbracket, 0
+"vi-open-line-above", viopenlineabove, 0
+"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-quoted-insert", viquotedinsert, ZLE_MENUCMP | ZLE_KEEPSUFFIX
+"vi-repeat-change", virepeatchange, 0
+"vi-repeat-find", virepeatfind, 0
+"vi-repeat-search", virepeatsearch, 0
+"vi-replace", vireplace, 0
+"vi-replace-chars", vireplacechars, 0
+"vi-rev-repeat-find", virevrepeatfind, 0
+"vi-rev-repeat-search", virevrepeatsearch, 0
+"vi-set-buffer", visetbuffer, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"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-unindent", viunindent, 0
+"vi-up-line-or-history", viuplineorhistory, ZLE_LINEMOVE
+"vi-yank", viyank, 0
+"vi-yank-eol", viyankeol, 0
+"vi-yank-whole-line", viyankwholeline, 0
+"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
diff --git a/Src/Zle/zle.export b/Src/Zle/zle.export
new file mode 100644
index 000000000..ccd5df98a
--- /dev/null
+++ b/Src/Zle/zle.export
@@ -0,0 +1,10 @@
+#!
+addzlefunction
+backdel
+backkill
+deletezlefunction
+feep
+foredel
+forekill
+getkey
+zmod
diff --git a/Src/Zle/zle.h b/Src/Zle/zle.h
new file mode 100644
index 000000000..faf6cf878
--- /dev/null
+++ b/Src/Zle/zle.h
@@ -0,0 +1,137 @@
+/*
+ * zle.h - header file for line editor
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#undef trashzle
+#undef zleread
+#undef spaceinline
+#undef gotword
+#undef refresh
+
+typedef struct widget *Widget;
+typedef struct thingy *Thingy;
+
+/* widgets (ZLE functions) */
+
+typedef void (*ZleIntFunc) _((void));
+
+struct widget {
+    int flags;		/* flags (see below) */
+    Thingy first;	/* `first' thingy that names this widget */
+    union {
+	ZleIntFunc fn;	/* pointer to internally implemented widget */
+	char *fnnam;	/* name of the shell function for user-defined widget */
+    } u;
+};
+
+#define WIDGET_INT	(1<<0)    /* widget is internally implemented */
+#define ZLE_MENUCMP	(1<<1)    /* DON'T invalidate completion list */
+#define ZLE_YANK	(1<<3)
+#define ZLE_LINEMOVE	(1<<4)    /* command is a line-oriented movement */
+#define ZLE_LASTCOL     (1<<5)    /* command maintains lastcol correctly */
+#define ZLE_KILL	(1<<6)
+#define ZLE_KEEPSUFFIX	(1<<9)    /* DON'T remove added suffix */
+
+/* thingies */
+
+struct thingy {
+    HashNode next;	/* next node in the hash chain */
+    char *nam;		/* name of the thingy */
+    int flags;		/* TH_* flags (see below) */
+    int rc;		/* reference count */
+    Widget widget;	/* widget named by this thingy */
+    Thingy samew;	/* `next' thingy (circularly) naming the same widget */
+};
+
+/* DISABLED is (1<<0) */
+#define TH_IMMORTAL	(1<<1)    /* can't refer to a different widget */
+
+/* command modifier prefixes */
+
+struct modifier {
+    int flags;		/* MOD_* flags (see below) */
+    int mult;		/* repeat count */
+    int tmult;		/* repeat count actually being edited */
+    int vibuf;		/* vi cut buffer */
+};
+
+#define MOD_MULT  (1<<0)   /* a repeat count has been selected */
+#define MOD_TMULT (1<<1)   /* a repeat count is being entered */
+#define MOD_VIBUF (1<<2)   /* a vi cut buffer has been selected */
+#define MOD_VIAPP (1<<3)   /* appending to the vi cut buffer */
+#define MOD_NEG   (1<<4)   /* last command was negate argument */
+
+/* current modifier status */
+
+#define zmult (zmod.mult)
+
+/* undo system */
+
+struct change {
+    struct change *prev, *next;	/* adjacent changes */
+    int flags;			/* see below */
+    int hist;			/* history line being changed */
+    int off;			/* offset of the text changes */
+    char *del;			/* characters to delete (metafied) */
+    char *ins;			/* characters to insert (metafied) */
+};
+
+#define CH_NEXT (1<<0)   /* next structure is also part of this change */
+#define CH_PREV (1<<1)   /* previous structure is also part of this change */
+
+/* known thingies */
+
+#define Th(X) (&thingies[X])
+
+/* opaque keymap type */
+
+typedef struct keymap *Keymap;
+
+typedef void (*KeyScanFunc) _((char *, Thingy, char *, void *));
+
+#define invicmdmode() (!strcmp(curkeymapname, "vicmd"))
+
+/* Standard type of suffix removal. */
+
+#define removesuffix() iremovesuffix(256)
+
+/* Cut/kill buffer type.  The buffer itself is purely binary data, *
+ * not NUL-terminated.  len is a length count.  flags uses the     *
+ * CUTBUFFER_* constants defined below.                            */
+
+struct cutbuffer {
+    char *buf;
+    size_t len;
+    char flags;
+};
+
+typedef struct cutbuffer *Cutbuffer;
+
+#define CUTBUFFER_LINE 1   /* for vi: buffer contains whole lines of data */
+
+#define KRINGCT 8   /* number of buffers in the kill ring */
diff --git a/Src/Zle/zle.mdd b/Src/Zle/zle.mdd
new file mode 100644
index 000000000..29f39d363
--- /dev/null
+++ b/Src/Zle/zle.mdd
@@ -0,0 +1,70 @@
+moddeps="comp1"
+
+autobins="bindkey vared zle"
+
+objects="zle_bindings.o zle_hist.o zle_keymap.o zle_main.o \
+zle_misc.o zle_move.o zle_params.o zle_refresh.o \
+zle_thingy.o zle_tricky.o zle_utils.o zle_vi.o zle_word.o"
+
+headers="zle.h zle_things.h"
+
+:<<\Make
+zle_things.h: thingies.list zle_things.sed
+	( \
+	    echo '/** zle_things.h                              **/'; \
+	    echo '/** indices of and pointers to known thingies **/'; \
+	    echo; \
+	    echo 'enum {'; \
+	    sed -n -f $(sdir)/zle_things.sed < thingies.list; \
+	    echo '    ZLE_BUILTIN_THINGY_COUNT'; \
+	    echo '};'; \
+	) > $@
+
+zle_widget.h: widgets.list zle_widget.sed
+	( \
+	    echo '/** zle_widget.h                                **/'; \
+	    echo '/** indices of and pointers to internal widgets **/'; \
+	    echo; \
+	    echo 'enum {'; \
+	    sed -n -f $(sdir)/zle_widget.sed < widgets.list; \
+	    echo '    ZLE_BUILTIN_WIDGET_COUNT'; \
+	    echo '};'; \
+	) > $@
+
+thingies.list: iwidgets.list
+	( \
+	    echo '/** thingies.list                            **/'; \
+	    echo '/** thingy structures for the known thingies **/'; \
+	    echo; \
+	    echo '/* format: T("name", TH_FLAGS, w_widget, t_nextthingy) */'; \
+	    echo; \
+	    sed -e 's/#.*//; /^$$/d; s/" *,.*/"/' \
+		-e 's/^"/T("/; s/$$/, 0,/; h' \
+		-e 's/-//g; s/^.*"\(.*\)".*/w_\1, t_D\1)/' \
+		-e 'H; g; s/\n/ /' \
+		< $(sdir)/iwidgets.list; \
+	    sed -e 's/#.*//; /^$$/d; s/" *,.*/"/' \
+		-e 's/^"/T("./; s/$$/, TH_IMMORTAL,/; h' \
+		-e 's/-//g; s/^.*"\.\(.*\)".*/w_\1, t_\1)/' \
+		-e 'H; g; s/\n/ /' \
+		< $(sdir)/iwidgets.list; \
+	) > $@
+
+widgets.list: iwidgets.list
+	( \
+	    echo '/** widgets.list                               **/'; \
+	    echo '/** widget structures for the internal widgets **/'; \
+	    echo; \
+	    echo '/* format: W(ZLE_FLAGS, t_firstname, functionname) */'; \
+	    echo; \
+	    sed -e 's/#.*//; /^$$/d; s/-//g' \
+		-e 's/^"\(.*\)" *, *\([^ ]*\) *, *\(.*\)/W(\3, t_\1, \2)/' \
+		< $(sdir)/iwidgets.list; \
+	) > $@
+
+zle_bindings.o zle_bindings..o: zle_widget.h widgets.list thingies.list
+
+clean-here: clean.zle
+clean.zle:
+	rm -f zle_things.h zle_widget.h widgets.list thingies.list
+Make
diff --git a/Src/Zle/zle_bindings.c b/Src/Zle/zle_bindings.c
new file mode 100644
index 000000000..40e555ad1
--- /dev/null
+++ b/Src/Zle/zle_bindings.c
@@ -0,0 +1,421 @@
+/*
+ * zle_bindings.c - commands and keymaps
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_widget.h"
+
+#include "zle_bindings.pro"
+
+/*
+ * widgets is the table of internally implemented widgets.  This
+ * table is not used directly, but each widget in it is referenced
+ * by address from within the table of thingies (below).  The only
+ * complication here is that not all systems support union
+ * initialisation.
+ */
+
+static
+#ifdef HAVE_UNION_INIT
+# define BR(X) {X}
+struct widget
+#else /* !HAVE_UNION_INIT */
+# define BR(X) X
+struct intwidget {
+    int flags;
+    Thingy first;
+    ZleIntFunc fn;
+}
+#endif /* !HAVE_UNION_INIT */
+widgets[] = {
+#define W(zle_flags, t_firstname, functionname) \
+    { WIDGET_INT | zle_flags, t_firstname, BR(functionname) },
+#include "widgets.list"
+#undef W
+};
+
+/*
+ * thingies is the table of `known thingies', that exist on startup.
+ * Some bits of ZLE rely on some of these thingies always being the
+ * ones in this table, rather than doing a name lookup and accepting
+ * any semantically identical thingy.  The initial reference count of
+ * these thingies is 2: 1 for the widget they name, and 1 extra to
+ * make sure they never get deleted.
+ */
+
+/**/
+struct thingy thingies[] = {
+#define T(name, th_flags, w_idget, t_next) \
+    { NULL, name, th_flags, 2, w_idget, t_next },
+#include "thingies.list"
+#undef T
+    { NULL, NULL, 0, 0, NULL, NULL }
+};
+
+/*
+ * Default key binding tables:
+ *
+ * In these tables, each element is bound to a single thingy, the index
+ * of which in the above table is stored here.
+ */
+
+/**/
+int emacsbind[32] = {
+    /* ^@ */ z_setmarkcommand,
+    /* ^A */ z_beginningofline,
+    /* ^B */ z_backwardchar,
+    /* ^C */ z_undefinedkey,
+    /* ^D */ z_deletecharorlist,
+    /* ^E */ z_endofline,
+    /* ^F */ z_forwardchar,
+    /* ^G */ z_sendbreak,
+    /* ^H */ z_backwarddeletechar,
+    /* ^I */ z_expandorcomplete,
+    /* ^J */ z_acceptline,
+    /* ^K */ z_killline,
+    /* ^L */ z_clearscreen,
+    /* ^M */ z_acceptline,
+    /* ^N */ z_downlineorhistory,
+    /* ^O */ z_acceptlineanddownhistory,
+    /* ^P */ z_uplineorhistory,
+    /* ^Q */ z_pushline,
+    /* ^R */ z_historyincrementalsearchbackward,
+    /* ^S */ z_historyincrementalsearchforward,
+    /* ^T */ z_transposechars,
+    /* ^U */ z_killwholeline,
+    /* ^V */ z_quotedinsert,
+    /* ^W */ z_backwardkillword,
+    /* ^X */ z_undefinedkey,
+    /* ^Y */ z_yank,
+    /* ^Z */ z_undefinedkey,
+    /* ^[ */ z_undefinedkey,
+    /* ^\ */ z_undefinedkey,
+    /* ^] */ z_undefinedkey,
+    /* ^^ */ z_undefinedkey,
+    /* ^_ */ z_undo,
+};
+
+/**/
+int metabind[128] = {
+    /* M-^@ */ z_undefinedkey,
+    /* M-^A */ z_undefinedkey,
+    /* M-^B */ z_undefinedkey,
+    /* M-^C */ z_undefinedkey,
+    /* M-^D */ z_listchoices,
+    /* M-^E */ z_undefinedkey,
+    /* M-^F */ z_undefinedkey,
+    /* M-^G */ z_sendbreak,
+    /* M-^H */ z_backwardkillword,
+    /* M-^I */ z_selfinsertunmeta,
+    /* M-^J */ z_selfinsertunmeta,
+    /* M-^K */ z_undefinedkey,
+    /* M-^L */ z_clearscreen,
+    /* M-^M */ z_selfinsertunmeta,
+    /* M-^N */ z_undefinedkey,
+    /* M-^O */ z_undefinedkey,
+    /* M-^P */ z_undefinedkey,
+    /* M-^Q */ z_undefinedkey,
+    /* M-^R */ z_undefinedkey,
+    /* M-^S */ z_undefinedkey,
+    /* M-^T */ z_undefinedkey,
+    /* M-^U */ z_undefinedkey,
+    /* M-^V */ z_undefinedkey,
+    /* M-^W */ z_undefinedkey,
+    /* M-^X */ z_undefinedkey,
+    /* M-^Y */ z_undefinedkey,
+    /* M-^Z */ z_undefinedkey,
+    /* M-^[ */ z_undefinedkey,
+    /* M-^\ */ z_undefinedkey,
+    /* M-^] */ z_undefinedkey,
+    /* M-^^ */ z_undefinedkey,
+    /* M-^_ */ z_copyprevword,
+    /* M-  */ z_expandhistory,
+    /* M-! */ z_expandhistory,
+    /* M-" */ z_quoteregion,
+    /* M-# */ z_undefinedkey,
+    /* M-$ */ z_spellword,
+    /* M-% */ z_undefinedkey,
+    /* M-& */ z_undefinedkey,
+    /* M-' */ z_quoteline,
+    /* M-( */ z_undefinedkey,
+    /* M-) */ z_undefinedkey,
+    /* M-* */ z_undefinedkey,
+    /* M-+ */ z_undefinedkey,
+    /* M-, */ z_undefinedkey,
+    /* M-- */ z_negargument,
+    /* M-. */ z_insertlastword,
+    /* M-/ */ z_undefinedkey,
+    /* M-0 */ z_digitargument,
+    /* M-1 */ z_digitargument,
+    /* M-2 */ z_digitargument,
+    /* M-3 */ z_digitargument,
+    /* M-4 */ z_digitargument,
+    /* M-5 */ z_digitargument,
+    /* M-6 */ z_digitargument,
+    /* M-7 */ z_digitargument,
+    /* M-8 */ z_digitargument,
+    /* M-9 */ z_digitargument,
+    /* M-: */ z_undefinedkey,
+    /* M-; */ z_undefinedkey,
+    /* M-< */ z_beginningofbufferorhistory,
+    /* M-= */ z_undefinedkey,
+    /* M-> */ z_endofbufferorhistory,
+    /* M-? */ z_whichcommand,
+    /* M-@ */ z_undefinedkey,
+    /* M-A */ z_acceptandhold,
+    /* M-B */ z_backwardword,
+    /* M-C */ z_capitalizeword,
+    /* M-D */ z_killword,
+    /* M-E */ z_undefinedkey,
+    /* M-F */ z_forwardword,
+    /* M-G */ z_getline,
+    /* M-H */ z_runhelp,
+    /* M-I */ z_undefinedkey,
+    /* M-J */ z_undefinedkey,
+    /* M-K */ z_undefinedkey,
+    /* M-L */ z_downcaseword,
+    /* M-M */ z_undefinedkey,
+    /* M-N */ z_historybeginningsearchforward,
+    /* M-O */ z_undefinedkey,
+    /* M-P */ z_historybeginningsearchbackward,
+    /* M-Q */ z_pushline,
+    /* M-R */ z_undefinedkey,
+    /* M-S */ z_spellword,
+    /* M-T */ z_transposewords,
+    /* M-U */ z_upcaseword,
+    /* M-V */ z_undefinedkey,
+    /* M-W */ z_copyregionaskill,
+    /* M-X */ z_undefinedkey,
+    /* M-Y */ z_undefinedkey,
+    /* M-Z */ z_undefinedkey,
+    /* M-[ */ z_undefinedkey,
+    /* M-\ */ z_undefinedkey,
+    /* M-] */ z_undefinedkey,
+    /* M-^ */ z_undefinedkey,
+    /* M-_ */ z_insertlastword,
+    /* M-` */ z_undefinedkey,
+    /* M-a */ z_acceptandhold,
+    /* M-b */ z_backwardword,
+    /* M-c */ z_capitalizeword,
+    /* M-d */ z_killword,
+    /* M-e */ z_undefinedkey,
+    /* M-f */ z_forwardword,
+    /* M-g */ z_getline,
+    /* M-h */ z_runhelp,
+    /* M-i */ z_undefinedkey,
+    /* M-j */ z_undefinedkey,
+    /* M-k */ z_undefinedkey,
+    /* M-l */ z_downcaseword,
+    /* M-m */ z_undefinedkey,
+    /* M-n */ z_historybeginningsearchforward,
+    /* M-o */ z_undefinedkey,
+    /* M-p */ z_historybeginningsearchbackward,
+    /* M-q */ z_pushline,
+    /* M-r */ z_undefinedkey,
+    /* M-s */ z_spellword,
+    /* M-t */ z_transposewords,
+    /* M-u */ z_upcaseword,
+    /* M-v */ z_undefinedkey,
+    /* M-w */ z_copyregionaskill,
+    /* M-x */ z_executenamedcmd,
+    /* M-y */ z_yankpop,
+    /* M-z */ z_executelastnamedcmd,
+    /* M-{ */ z_undefinedkey,
+    /* M-| */ z_vigotocolumn,
+    /* M-} */ z_undefinedkey,
+    /* M-~ */ z_undefinedkey,
+    /* M-^? */ z_backwardkillword,
+};
+
+/**/
+int viinsbind[32] = {
+    /* ^@ */ z_undefinedkey,
+    /* ^A */ z_selfinsert,
+    /* ^B */ z_selfinsert,
+    /* ^C */ z_selfinsert,
+    /* ^D */ z_listchoices,
+    /* ^E */ z_selfinsert,
+    /* ^F */ z_selfinsert,
+    /* ^G */ z_listexpand,
+    /* ^H */ z_vibackwarddeletechar,
+    /* ^I */ z_expandorcomplete,
+    /* ^J */ z_acceptline,
+    /* ^K */ z_selfinsert,
+    /* ^L */ z_clearscreen,
+    /* ^M */ z_acceptline,
+    /* ^N */ z_selfinsert,
+    /* ^O */ z_selfinsert,
+    /* ^P */ z_selfinsert,
+    /* ^Q */ z_viquotedinsert,
+    /* ^R */ z_redisplay,
+    /* ^S */ z_selfinsert,
+    /* ^T */ z_selfinsert,
+    /* ^U */ z_vikillline,
+    /* ^V */ z_viquotedinsert,
+    /* ^W */ z_vibackwardkillword,
+    /* ^X */ z_selfinsert,
+    /* ^Y */ z_selfinsert,
+    /* ^Z */ z_selfinsert,
+    /* ^[ */ z_vicmdmode,
+    /* ^\ */ z_selfinsert,
+    /* ^] */ z_selfinsert,
+    /* ^^ */ z_selfinsert,
+    /* ^_ */ z_selfinsert,
+};
+
+/**/
+int vicmdbind[128] = {
+    /* ^@ */ z_undefinedkey,
+    /* ^A */ z_undefinedkey,
+    /* ^B */ z_undefinedkey,
+    /* ^C */ z_undefinedkey,
+    /* ^D */ z_listchoices,
+    /* ^E */ z_undefinedkey,
+    /* ^F */ z_undefinedkey,
+    /* ^G */ z_listexpand,
+    /* ^H */ z_vibackwardchar,
+    /* ^I */ z_undefinedkey,
+    /* ^J */ z_acceptline,
+    /* ^K */ z_undefinedkey,
+    /* ^L */ z_clearscreen,
+    /* ^M */ z_acceptline,
+    /* ^N */ z_downhistory,
+    /* ^O */ z_undefinedkey,
+    /* ^P */ z_uphistory,
+    /* ^Q */ z_undefinedkey,
+    /* ^R */ z_redisplay,
+    /* ^S */ z_undefinedkey,
+    /* ^T */ z_undefinedkey,
+    /* ^U */ z_undefinedkey,
+    /* ^V */ z_undefinedkey,
+    /* ^W */ z_undefinedkey,
+    /* ^X */ z_undefinedkey,
+    /* ^Y */ z_undefinedkey,
+    /* ^Z */ z_undefinedkey,
+    /* ^[ */ z_undefinedkey,
+    /* ^\ */ z_undefinedkey,
+    /* ^] */ z_undefinedkey,
+    /* ^^ */ z_undefinedkey,
+    /* ^_ */ z_undefinedkey,
+    /*   */ z_viforwardchar,
+    /* ! */ z_undefinedkey,
+    /* " */ z_visetbuffer,
+    /* # */ z_poundinsert,
+    /* $ */ z_viendofline,
+    /* % */ z_vimatchbracket,
+    /* & */ z_undefinedkey,
+    /* ' */ z_vigotomarkline,
+    /* ( */ z_undefinedkey,
+    /* ) */ z_undefinedkey,
+    /* * */ z_undefinedkey,
+    /* + */ z_vidownlineorhistory,
+    /* , */ z_virevrepeatfind,
+    /* - */ z_viuplineorhistory,
+    /* . */ z_virepeatchange,
+    /* / */ z_vihistorysearchbackward,
+    /* 0 */ z_vidigitorbeginningofline,
+    /* 1 */ z_digitargument,
+    /* 2 */ z_digitargument,
+    /* 3 */ z_digitargument,
+    /* 4 */ z_digitargument,
+    /* 5 */ z_digitargument,
+    /* 6 */ z_digitargument,
+    /* 7 */ z_digitargument,
+    /* 8 */ z_digitargument,
+    /* 9 */ z_digitargument,
+    /* : */ z_undefinedkey,
+    /* ; */ z_virepeatfind,
+    /* < */ z_viunindent,
+    /* = */ z_listchoices,
+    /* > */ z_viindent,
+    /* ? */ z_vihistorysearchforward,
+    /* @ */ z_undefinedkey,
+    /* A */ z_viaddeol,
+    /* B */ z_vibackwardblankword,
+    /* C */ z_vichangeeol,
+    /* D */ z_vikilleol,
+    /* E */ z_viforwardblankwordend,
+    /* F */ z_vifindprevchar,
+    /* G */ z_vifetchhistory,
+    /* H */ z_undefinedkey,
+    /* I */ z_viinsertbol,
+    /* J */ z_vijoin,
+    /* K */ z_undefinedkey,
+    /* L */ z_undefinedkey,
+    /* M */ z_undefinedkey,
+    /* N */ z_virevrepeatsearch,
+    /* O */ z_viopenlineabove,
+    /* P */ z_viputbefore,
+    /* Q */ z_undefinedkey,
+    /* R */ z_vireplace,
+    /* S */ z_vichangewholeline,
+    /* T */ z_vifindprevcharskip,
+    /* U */ z_undefinedkey,
+    /* V */ z_undefinedkey,
+    /* W */ z_viforwardblankword,
+    /* X */ z_vibackwarddeletechar,
+    /* Y */ z_viyankwholeline,
+    /* Z */ z_undefinedkey,
+    /* [ */ z_undefinedkey,
+    /* \ */ z_undefinedkey,
+    /* ] */ z_undefinedkey,
+    /* ^ */ z_vifirstnonblank,
+    /* _ */ z_undefinedkey,
+    /* ` */ z_vigotomark,
+    /* a */ z_viaddnext,
+    /* b */ z_vibackwardword,
+    /* c */ z_vichange,
+    /* d */ z_videlete,
+    /* e */ z_viforwardwordend,
+    /* f */ z_vifindnextchar,
+    /* g */ z_undefinedkey,
+    /* h */ z_vibackwardchar,
+    /* i */ z_viinsert,
+    /* j */ z_downlineorhistory,
+    /* k */ z_uplineorhistory,
+    /* l */ z_viforwardchar,
+    /* m */ z_visetmark,
+    /* n */ z_virepeatsearch,
+    /* o */ z_viopenlinebelow,
+    /* p */ z_viputafter,
+    /* q */ z_undefinedkey,
+    /* r */ z_vireplacechars,
+    /* s */ z_visubstitute,
+    /* t */ z_vifindnextcharskip,
+    /* u */ z_viundochange,
+    /* v */ z_undefinedkey,
+    /* w */ z_viforwardword,
+    /* x */ z_videletechar,
+    /* y */ z_viyank,
+    /* z */ z_undefinedkey,
+    /* { */ z_undefinedkey,
+    /* | */ z_vigotocolumn,
+    /* } */ z_undefinedkey,
+    /* ~ */ z_viswapcase,
+    /* ^? */ z_vibackwardchar,
+};
diff --git a/Src/Zle/zle_hist.c b/Src/Zle/zle_hist.c
new file mode 100644
index 000000000..76e421c1c
--- /dev/null
+++ b/Src/Zle/zle_hist.c
@@ -0,0 +1,1139 @@
+/*
+ * zle_hist.c - history editing
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_hist.pro"
+
+/* Are references to earlier history lines permitted?  == 0 if       *
+ * editing or reading a standalone line, such as in vared or select. */
+
+/**/
+int histallowed;
+
+/* Column position of vi ideal cursor.  -1 if it is unknown -- most *
+ * movements and changes do this.                                   */
+
+/**/
+int lastcol;
+
+/* current history line number */
+
+/**/
+int histline;
+
+/* the last line in the history (the current one), metafied */
+
+/**/
+char *curhistline;
+
+/**/
+void
+remember_edits(void)
+{
+    if (histline == curhist) {
+	zsfree(curhistline);
+	curhistline = metafy((char *) line, ll, META_DUP);
+    }
+    else {
+	Histent ent = gethistent(histline);
+
+	if (metadiffer(ent->zle_text ? ent->zle_text : ent->text,
+		       (char *) line, ll)) {
+	    zsfree(ent->zle_text);
+	    ent->zle_text = metafy((char *) line, ll, META_DUP);
+	}
+    }
+}
+
+/**/
+void
+forget_edits(void)
+{
+    int i;
+
+    for (i = 0; i < histentct; i++) {
+	zsfree(histentarr[i].zle_text);
+	histentarr[i].zle_text = NULL;
+    }
+}
+
+/**/
+void
+uphistory(void)
+{
+    if (zmult < 0) {
+	zmult = -zmult;
+	downhistory();
+	zmult = -zmult;
+    } else if(!zle_goto_hist(histline - zmult) && isset(HISTBEEP))
+	feep();
+}
+
+/**/
+int
+upline(void)
+{
+    int n = zmult;
+
+    if (n < 0) {
+	zmult = -zmult;
+	n = downline();
+	zmult = -zmult;
+	return n;
+    }
+    if (lastcol == -1)
+	lastcol = cs - findbol();
+    cs = findbol();
+    while (n) {
+	if (!cs)
+	    break;
+	cs--;
+	cs = findbol();
+	n--;
+    }
+    if (!n) {
+	int x = findeol();
+
+	if ((cs += lastcol) >= x) {
+	    cs = x;
+	    if (cs > findbol() && invicmdmode())
+		cs--;
+	}
+    }
+    return n;
+}
+
+/**/
+void
+uplineorhistory(void)
+{
+    int ocs = cs;
+    int n = upline();
+    if (n) {
+	int m = zmult;
+
+	cs = ocs;
+	if (virangeflag || !histallowed) {
+	    feep();
+	    return;
+	}
+	zmult = n;
+	uphistory();
+	zmult = m;
+    }
+}
+
+/**/
+void
+viuplineorhistory(void)
+{
+    int col = lastcol;
+    uplineorhistory();
+    lastcol = col;
+    vifirstnonblank();
+}
+
+
+/**/
+void
+uplineorsearch(void)
+{
+    int ocs = cs;
+    int n = upline();
+    if (n) {
+	int m = zmult;
+
+	cs = ocs;
+	if (virangeflag || !histallowed) {
+	    feep();
+	    return;
+	}
+	zmult = n;
+	historysearchbackward();
+	zmult = m;
+    }
+}
+
+/**/
+int
+downline(void)
+{
+    int n = zmult;
+
+    if (n < 0) {
+	zmult = -zmult;
+	n = upline();
+	zmult = -zmult;
+	return n;
+    }
+    if (lastcol == -1)
+	lastcol = cs - findbol();
+    while (n) {
+	int x = findeol();
+
+	if (x == ll)
+	    break;
+	cs = x + 1;
+	n--;
+    }
+    if (!n) {
+	int x = findeol();
+
+	if ((cs += lastcol) >= x) {
+	    cs = x;
+	    if (cs > findbol() && invicmdmode())
+		cs--;
+	}
+    }
+    return n;
+}
+
+/**/
+void
+downlineorhistory(void)
+{
+    int ocs = cs;
+    int n = downline();
+    if (n) {
+	int m = zmult;
+
+	cs = ocs;
+	if (virangeflag || !histallowed) {
+	    feep();
+	    return;
+	}
+	zmult = n;
+	downhistory();
+	zmult = m;
+    }
+}
+
+/**/
+void
+vidownlineorhistory(void)
+{
+    int col = lastcol;
+    downlineorhistory();
+    lastcol = col;
+    vifirstnonblank();
+}
+
+/**/
+void
+downlineorsearch(void)
+{
+    int ocs = cs;
+    int n = downline();
+    if (n) {
+	int m = zmult;
+
+	cs = ocs;
+	if (virangeflag || !histallowed) {
+	    feep();
+	    return;
+	}
+	zmult = n;
+	historysearchforward();
+	zmult = m;
+    }
+}
+
+/**/
+void
+acceptlineanddownhistory(void)
+{
+    char *s;
+
+    if (!(s = zle_get_event(histline + 1))) {
+	feep();
+	return;
+    }
+    pushnode(bufstack, ztrdup(s));
+    done = 1;
+    stackhist = histline + 1;
+}
+
+/**/
+void
+downhistory(void)
+{
+    if (zmult < 0) {
+	zmult = -zmult;
+	uphistory();
+	zmult = -zmult;
+    } else if(!zle_goto_hist(histline + zmult) && isset(HISTBEEP))
+	feep();
+}
+
+/**/
+void
+historysearchbackward(void)
+{
+    int histpos, histmpos, hl = histline;
+    int n = zmult;
+    char *s;
+
+    if (!n)
+	return;
+    if (n < 0) {
+	zmult = -n;
+	historysearchforward();
+	zmult = n;
+	return;
+    }
+    for (histpos = histmpos = 0; histpos < ll && !iblank(line[histpos]);
+	histpos++, histmpos++)
+	if(imeta(line[histpos]))
+	    histmpos++;
+    for (;;) {
+	hl--;
+	if (!(s = zle_get_event(hl))) {
+	    feep();
+	    return;
+	}
+	if (metadiffer(s, (char *) line, histpos) < 0 &&
+	    iblank(s[histmpos] == Meta ? s[histmpos+1]^32 : s[histmpos]) &&
+	    metadiffer(s, (char *) line, ll) && !--n)
+	    break;
+    }
+    zle_goto_hist(hl);
+}
+
+/**/
+void
+historysearchforward(void)
+{
+    int histpos, histmpos, hl = histline;
+    int n = zmult;
+    char *s;
+
+    if (!n)
+	return;
+    if (n < 0) {
+	zmult = -n;
+	historysearchbackward();
+	zmult = n;
+	return;
+    }
+    for (histpos = histmpos = 0; histpos < ll && !iblank(line[histpos]);
+	histpos++, histmpos++)
+	if(imeta(line[histpos]))
+	    histmpos++;
+    for (;;) {
+	hl++;
+	if (!(s = zle_get_event(hl))) {
+	    feep();
+	    return;
+	}
+	if (metadiffer(s, (char *) line, histpos) < (histline == curhist) &&
+	    (!s[histmpos] ||
+	     iblank(s[histmpos] == Meta ? s[histmpos+1]^32 : s[histmpos])) &&
+	    metadiffer(s, (char *) line, ll) && !--n)
+	    break;
+    }
+    zle_goto_hist(hl);
+}
+
+/**/
+void
+beginningofbufferorhistory(void)
+{
+    if (findbol())
+	cs = 0;
+    else
+	beginningofhistory();
+}
+
+/**/
+void
+beginningofhistory(void)
+{
+    if (!zle_goto_hist(firsthist()) && isset(HISTBEEP))
+	feep();
+}
+
+/**/
+void
+endofbufferorhistory(void)
+{
+    if (findeol() != ll)
+	cs = ll;
+    else
+	endofhistory();
+}
+
+/**/
+void
+endofhistory(void)
+{
+    zle_goto_hist(curhist);
+}
+
+/**/
+void
+insertlastword(void)
+{
+    int n;
+    char *s, *t;
+    Histent he;
+
+/* multiple calls will now search back through the history, pem */
+    static char *lastinsert;
+    static int lasthist, lastpos;
+    int evhist = curhist - 1, save;
+
+    if (lastinsert) {
+	int lastlen = ztrlen(lastinsert);
+	int pos = cs;
+
+	if (lastpos <= pos &&
+	    lastlen == pos - lastpos &&
+	    memcmp(lastinsert, (char *)&line[lastpos], lastlen) == 0) {
+	    evhist = --lasthist;
+	    cs = lastpos;
+	    foredel(pos - cs);
+	}
+	zsfree(lastinsert);
+	lastinsert = NULL;
+    }
+    if (!(he = quietgethist(evhist)) || !he->nwords) {
+	feep();
+	return;
+    }
+    if (zmult > 0) {
+	n = he->nwords - (zmult - 1);
+    } else {
+	n = 1 - zmult;
+    }
+    if (n < 1 || n > he->nwords) {
+	feep();
+	return;
+    }
+    s = he->text + he->words[2*n-2];
+    t = he->text + he->words[2*n-1];
+    save = *t;
+    *t = '\0';			/* ignore trailing whitespace */
+
+    lasthist = evhist;
+    lastpos = cs;
+    lastinsert = ztrdup(s);
+    n = zmult;
+    zmult = 1;
+    doinsert(s);
+    zmult = n;
+    *t = save;
+}
+
+/**/
+char *
+qgetevent(int ev)
+{
+    return ((ev == curhist) ? curhistline : quietgetevent(ev));
+}
+
+/**/
+char *
+zle_get_event(int ev)
+{
+    Histent ent;
+
+    if (ev == curhist)
+	return curhistline;
+    if (! (ent = quietgethist(ev)))
+	return NULL;
+    if (ent->zle_text)
+	return ent->zle_text;
+    return ent->text;
+}
+
+/**/
+static int
+zle_goto_hist(int ev)
+{
+    char *t;
+
+    remember_edits();
+    if(!(t = zle_get_event(ev)))
+	return 0;
+    mkundoent();
+    histline = ev;
+    setline(t);
+    setlastline();
+    return 1;
+}
+
+/**/
+void
+pushline(void)
+{
+    int n = zmult;
+
+    if (n < 0)
+	return;
+    pushnode(bufstack, metafy((char *) line, ll, META_DUP));
+    while (--n)
+	pushnode(bufstack, ztrdup(""));
+    stackcs = cs;
+    *line = '\0';
+    ll = cs = 0;
+}
+
+/**/
+void
+pushlineoredit(void)
+{
+    int ics;
+    unsigned char *s;
+    char *hline = hgetline();
+
+    if (zmult < 0)
+	return;
+    if (hline && *hline) {
+	ics = ztrlen(hline);
+	sizeline(ics + ll + 1);
+	for (s = line + ll; --s >= line; *(s + ics) = *s);
+	for (s = line; *hline; hline++)
+	    *s++ = *hline == Meta ? *++hline ^ 32 : *hline;
+	ll += ics;
+	cs += ics;
+    }
+    pushline();
+    if (!isfirstln) {
+	errflag = done = 1;
+    }
+}
+
+/**/
+void
+pushinput(void)
+{
+    int i;
+
+    if (zmult < 0)
+	return;
+    zmult += i = !isfirstln;
+    pushlineoredit();
+    zmult -= i;
+}
+
+/**/
+void
+getline(void)
+{
+    char *s = (char *)getlinknode(bufstack);
+
+    if (!s)
+	feep();
+    else {
+	int cc;
+
+	unmetafy(s, &cc);
+	spaceinline(cc);
+	memcpy((char *)line + cs, s, cc);
+	cs += cc;
+	free(s);
+    }
+}
+
+/**/
+void
+historyincrementalsearchbackward(void)
+{
+    doisearch(-1);
+}
+
+/**/
+void
+historyincrementalsearchforward(void)
+{
+    doisearch(1);
+}
+
+static struct isrch_spot {
+    int hl;			/* This spot's histline */
+    unsigned short pos;		/* The search position in our metafied str */
+    unsigned short cs;		/* The visible search position to the user */
+    unsigned short len;		/* The search string's length */
+    unsigned short flags;	/* This spot's flags */
+#define ISS_FAILING	1
+#define ISS_FORWARD	2
+} *isrch_spots;
+
+static int max_spot = 0;
+
+#ifdef MODULE
+
+/**/
+void
+free_isrch_spots(void)
+{
+    zfree(isrch_spots, max_spot * sizeof(*isrch_spots));
+}
+
+#endif /* MODULE */
+
+/**/
+static void
+set_isrch_spot(int num, int hl, int pos, int cs, int len, int dir, int nomatch)
+{
+    if (num >= max_spot) {
+	if (!isrch_spots) {
+	    isrch_spots = (struct isrch_spot*)
+			    zalloc((max_spot = 64) * sizeof *isrch_spots);
+	} else {
+	    isrch_spots = (struct isrch_spot*)realloc((char*)isrch_spots,
+			    (max_spot += 64) * sizeof *isrch_spots);
+	}
+    }
+
+    isrch_spots[num].hl = hl;
+    isrch_spots[num].pos = (unsigned short)pos;
+    isrch_spots[num].cs = (unsigned short)cs;
+    isrch_spots[num].len = (unsigned short)len;
+    isrch_spots[num].flags = (dir > 0? ISS_FORWARD : 0)
+			   + (nomatch? ISS_FAILING : 0);
+}
+
+/**/
+static void
+get_isrch_spot(int num, int *hlp, int *posp, int *csp, int *lenp, int *dirp, int *nomatch)
+{
+    *hlp = isrch_spots[num].hl;
+    *posp = (int)isrch_spots[num].pos;
+    *csp = (int)isrch_spots[num].cs;
+    *lenp = (int)isrch_spots[num].len;
+    *dirp = (isrch_spots[num].flags & ISS_FORWARD)? 1 : -1;
+    *nomatch = (isrch_spots[num].flags & ISS_FAILING);
+}
+
+#define ISEARCH_PROMPT		"failing XXX-i-search: "
+#define NORM_PROMPT_POS		8
+#define FIRST_SEARCH_CHAR	(NORM_PROMPT_POS + 14)
+
+/**/
+static void
+doisearch(int dir)
+{
+    char *s, *ibuf = halloc(80), *sbuf = ibuf + FIRST_SEARCH_CHAR;
+    int sbptr = 0, top_spot = 0, pos, sibuf = 80;
+    int nomatch = 0, skip_line = 0, skip_pos = 0;
+    int odir = dir, sens = zmult == 1 ? 3 : 1;
+    int hl = histline;
+    Thingy cmd;
+    char *okeymap = curkeymapname;
+    static char *previous_search = NULL;
+    static int previous_search_len = 0;
+
+    strcpy(ibuf, ISEARCH_PROMPT);
+    memcpy(ibuf + NORM_PROMPT_POS, (dir == 1) ? "fwd" : "bck", 3);
+    remember_edits();
+    s = zle_get_event(hl);
+    selectkeymap("main", 1);
+    pos = metalen(s, cs);
+    for (;;) {
+	/* Remember the current values in case search fails (doesn't push). */
+	set_isrch_spot(top_spot, hl, pos, cs, sbptr, dir, nomatch);
+	if (sbptr == 1 && sbuf[0] == '^') {
+	    cs = 0;
+    	    nomatch = 0;
+	    statusline = ibuf + NORM_PROMPT_POS;
+	} else if (sbptr > 0) {
+	    char *last_line = s;
+
+	    for (;;) {
+		char *t;
+
+		if (skip_pos) {
+		    if (dir < 0) {
+			if (pos == 0)
+			    skip_line = 1;
+			else
+			    pos -= 1 + (pos != 1 && s[pos-2] == Meta);
+		    } else if (sbuf[0] != '^') {
+			if (pos >= strlen(s+1))
+			    skip_line = 1;
+			else
+			    pos += 1 + (s[pos] == Meta);
+		    } else
+			skip_line = 1;
+		    skip_pos = 0;
+		}
+		if (!skip_line && ((sbuf[0] == '^') ?
+		    (t = metadiffer(s, sbuf + 1, sbptr - 1) < sens ? s : NULL) :
+		    (t = hstrnstr(s, pos, sbuf, sbptr, dir, sens)))) {
+		    zle_goto_hist(hl);
+		    pos = t - s;
+		    cs = ztrsub(t, s) + (dir == 1? sbptr - (sbuf[0]=='^') : 0);
+	    	    nomatch = 0;
+		    statusline = ibuf + NORM_PROMPT_POS;
+		    break;
+		}
+		hl += dir;
+		if (!(s = zle_get_event(hl))) {
+		    if (sbptr == (int)isrch_spots[top_spot-1].len
+		     && (isrch_spots[top_spot-1].flags & ISS_FAILING))
+			top_spot--;
+		    get_isrch_spot(top_spot, &hl, &pos, &cs, &sbptr,
+				   &dir, &nomatch);
+		    if (!nomatch) {
+			feep();
+			nomatch = 1;
+		    }
+		    s = last_line;
+		    skip_line = 0;
+		    statusline = ibuf;
+		    break;
+		}
+		pos = dir == 1? 0 : strlen(s);
+		skip_line = !strcmp(last_line, s);
+	    }
+	} else {
+	    top_spot = 0;
+    	    nomatch = 0;
+	    statusline = ibuf + NORM_PROMPT_POS;
+	}
+	sbuf[sbptr] = '_';
+	statusll = sbuf - statusline + sbptr + 1;
+    ref:
+	refresh();
+	if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) {
+	    int i;
+	    get_isrch_spot(0, &hl, &pos, &i, &sbptr, &dir, &nomatch);
+	    s = zle_get_event(hl);
+	    zle_goto_hist(hl);
+	    cs = i;
+	    break;
+	}
+	if(cmd == Th(z_clearscreen)) {
+	    clearscreen();
+	    goto ref;
+	} else if(cmd == Th(z_redisplay)) {
+	    redisplay();
+	    goto ref;
+	} else if(cmd == Th(z_vicmdmode)) {
+	    if(selectkeymap(invicmdmode() ? "main" : "vicmd", 0))
+		feep();
+	    goto ref;
+	} else if(cmd == Th(z_vibackwarddeletechar) ||
+	    	cmd == Th(z_backwarddeletechar)) {
+	    if (top_spot)
+		get_isrch_spot(--top_spot, &hl, &pos, &cs, &sbptr,
+			       &dir, &nomatch);
+	    else
+		feep();
+	    if (nomatch) {
+		statusline = ibuf;
+		skip_pos = 1;
+	    }
+	    s = zle_get_event(hl);
+	    if (nomatch || !sbptr || (sbptr == 1 && sbuf[0] == '^')) {
+		int i = cs;
+		zle_goto_hist(hl);
+		cs = i;
+	    }
+	    memcpy(ibuf + NORM_PROMPT_POS, (dir == 1) ? "fwd" : "bck", 3);
+	    continue;
+	} else if(cmd == Th(z_acceptandhold)) {
+	    acceptandhold();
+	    break;
+	} else if(cmd == Th(z_acceptandinfernexthistory)) {
+	    acceptandinfernexthistory();
+	    break;
+	} else if(cmd == Th(z_acceptlineanddownhistory)) {
+	    acceptlineanddownhistory();
+	    break;
+	} else if(cmd == Th(z_acceptline)) {
+	    acceptline();
+	    break;
+	} else if(cmd == Th(z_historyincrementalsearchbackward)) {
+	    set_isrch_spot(top_spot++, hl, pos, cs, sbptr, dir, nomatch);
+	    if (dir != -1)
+		dir = -1;
+	    else
+		skip_pos = 1;
+	    goto rpt;
+	} else if(cmd == Th(z_historyincrementalsearchforward)) {
+	    set_isrch_spot(top_spot++, hl, pos, cs, sbptr, dir, nomatch);
+	    if (dir != 1)
+		dir = 1;
+	    else
+		skip_pos = 1;
+	    goto rpt;
+	} else if(cmd == Th(z_virevrepeatsearch)) {
+	    set_isrch_spot(top_spot++, hl, pos, cs, sbptr, dir, nomatch);
+	    dir = -odir;
+	    skip_pos = 1;
+	    goto rpt;
+	} else if(cmd == Th(z_virepeatsearch)) {
+	    set_isrch_spot(top_spot++, hl, pos, cs, sbptr, dir, nomatch);
+	    dir = odir;
+	    skip_pos = 1;
+	rpt:
+	    if (!sbptr && previous_search_len) {
+		if (previous_search_len > sibuf - FIRST_SEARCH_CHAR - 2) {
+		    ibuf = hrealloc(ibuf, sibuf, sibuf + previous_search_len);
+		    sbuf = ibuf + FIRST_SEARCH_CHAR;
+		    sibuf += previous_search_len;
+		}
+		memcpy(sbuf, previous_search, sbptr = previous_search_len);
+	    }
+	    memcpy(ibuf + NORM_PROMPT_POS, (dir == 1) ? "fwd" : "bck", 3);
+	    continue;
+	} else if(cmd == Th(z_viquotedinsert) ||
+	    	cmd == Th(z_quotedinsert)) {
+	    if(cmd == Th(z_viquotedinsert)) {
+		sbuf[sbptr] = '^';
+		refresh();
+	    }
+	    if ((c = getkey(0)) == EOF)
+		feep();
+	    else
+		goto ins;
+	} else {
+	    if(cmd == Th(z_selfinsertunmeta)) {
+		c &= 0x7f;
+		if(c == '\r')
+		    c = '\n';
+	    } else if (cmd == Th(z_magicspace))
+		c = ' ';
+	    else if (cmd != Th(z_selfinsert)) {
+		ungetkeycmd();
+		if (cmd == Th(z_sendbreak))
+		    sbptr = 0;
+		break;
+	    }
+	ins:
+	    if (sbptr == PATH_MAX) {
+		feep();
+		continue;
+	    }
+	    set_isrch_spot(top_spot++, hl, pos, cs, sbptr, dir, nomatch);
+	    if (sbptr == sibuf - FIRST_SEARCH_CHAR - 2) {
+		ibuf = hrealloc(ibuf, sibuf, sibuf * 2);
+		sbuf = ibuf + FIRST_SEARCH_CHAR;
+		sibuf *= 2;
+	    }
+	    sbuf[sbptr++] = c;
+	}
+	handlefeep();
+    }
+    if (sbptr) {
+	zfree(previous_search, previous_search_len);
+	previous_search = zalloc(sbptr);
+	memcpy(previous_search, sbuf, previous_search_len = sbptr);
+    }
+    statusline = NULL;
+    selectkeymap(okeymap, 1);
+}
+
+/**/
+void
+acceptandinfernexthistory(void)
+{
+    int t0;
+    char *s;
+
+    done = 1;
+    for (t0 = histline - 2;; t0--) {
+	if (!(s = qgetevent(t0)))
+	    return;
+	if (!metadiffer(s, (char *) line, ll))
+	    break;
+    }
+    if (!(s = qgetevent(t0 + 1)))
+	return;
+    pushnode(bufstack, ztrdup(s));
+    stackhist = t0 + 1;
+}
+
+/**/
+void
+infernexthistory(void)
+{
+    int t0;
+    char *s;
+
+    for (t0 = histline - 2;; t0--) {
+	if (!(s = qgetevent(t0))) {
+	    feep();
+	    return;
+	}
+	if (! metadiffer(s, (char *) line, ll))
+	    break;
+    }
+    if (!(s = qgetevent(t0 + 1))) {
+	feep();
+	return;
+    }
+    zle_goto_hist(t0 + 1);
+}
+
+/**/
+void
+vifetchhistory(void)
+{
+    if (zmult < 0)
+	return;
+    if (histline == curhist) {
+	if (!(zmod.flags & MOD_MULT)) {
+	    cs = ll;
+	    cs = findbol();
+	    return;
+	}
+    }
+    if (!zle_goto_hist((zmod.flags & MOD_MULT) ? zmult : curhist) &&
+	isset(HISTBEEP))
+	feep();
+}
+
+/* the last vi search */
+
+static char *visrchstr;
+static int visrchsense;
+
+/**/
+static int
+getvisrchstr(void)
+{
+    char *sbuf = halloc(80);
+    int sptr = 1, ret = 0, ssbuf = 80;
+    Thingy cmd;
+    char *okeymap = curkeymapname;
+
+    if (visrchstr) {
+	zsfree(visrchstr);
+	visrchstr = NULL;
+    }
+    statusline = sbuf;
+    sbuf[0] = (visrchsense == -1) ? '?' : '/';
+    selectkeymap("main", 1);
+    while (sptr) {
+	sbuf[sptr] = '_';
+	statusll = sptr + 1;
+	refresh();
+	if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) {
+	    ret = 0;
+	    break;
+	}
+	if(cmd == Th(z_magicspace)) {
+	    c = ' ';
+	    cmd = Th(z_selfinsert);
+	}
+	if(cmd == Th(z_redisplay)) {
+	    redisplay();
+	} else if(cmd == Th(z_clearscreen)) {
+	    clearscreen();
+	} else if(cmd == Th(z_acceptline) ||
+	    	cmd == Th(z_vicmdmode)) {
+	    sbuf[sptr] = 0;
+	    visrchstr = metafy(sbuf + 1, sptr - 1, META_DUP);
+	    ret = 1;
+	    sptr = 0;
+	} else if(cmd == Th(z_backwarddeletechar) ||
+	    	cmd == Th(z_vibackwarddeletechar)) {
+	    sptr--;
+	} else if(cmd == Th(z_backwardkillword) ||
+	    	cmd == Th(z_vibackwardkillword)) {
+	    while(sptr != 1 && iblank(sbuf[sptr - 1]))
+		sptr--;
+	    if(iident(sbuf[sptr - 1]))
+		while(sptr != 1 && iident(sbuf[sptr - 1]))
+		    sptr--;
+	    else
+		while(sptr != 1 && !iident(sbuf[sptr - 1]) && !iblank(sbuf[sptr - 1]))
+		    sptr--;
+	} else if(cmd == Th(z_viquotedinsert) || cmd == Th(z_quotedinsert)) {
+	    if(cmd == Th(z_viquotedinsert)) {
+		sbuf[sptr] = '^';
+		refresh();
+	    }
+	    if ((c = getkey(0)) == EOF)
+		feep();
+	    else
+		goto ins;
+	} else if(cmd == Th(z_selfinsertunmeta) || cmd == Th(z_selfinsert)) {
+	    if(cmd == Th(z_selfinsertunmeta)) {
+		c &= 0x7f;
+		if(c == '\r')
+		    c = '\n';
+	    }
+	  ins:
+	    if(sptr == ssbuf - 1) {
+		char *newbuf = halloc(ssbuf *= 2);
+		strcpy(newbuf, sbuf);
+		statusline = sbuf = newbuf;
+	    }
+	    sbuf[sptr++] = c;
+	} else {
+	    feep();
+	}
+	handlefeep();
+    }
+    statusline = NULL;
+    selectkeymap(okeymap, 1);
+    return ret;
+}
+
+/**/
+void
+vihistorysearchforward(void)
+{
+    visrchsense = 1;
+    if (getvisrchstr())
+	virepeatsearch();
+}
+
+/**/
+void
+vihistorysearchbackward(void)
+{
+    visrchsense = -1;
+    if (getvisrchstr())
+	virepeatsearch();
+}
+
+/**/
+void
+virepeatsearch(void)
+{
+    int hl = histline, t0;
+    int n = zmult;
+    char *s;
+
+    if (!visrchstr) {
+	feep();
+	return;
+    }
+    if (!n)
+	return;
+    if (n < 0) {
+	n = -n;
+	visrchsense = -visrchsense;
+    }
+    t0 = strlen(visrchstr);
+    for (;;) {
+	hl += visrchsense;
+	if (!(s = zle_get_event(hl))) {
+	    feep();
+	    return;
+	}
+	if (!metadiffer(s, (char *) line, ll))
+	    continue;
+	if (*visrchstr == '^') {
+	    if (strncmp(s, visrchstr + 1, t0 - 1) != 0)
+		continue;
+	} else if (!hstrnstr(s, 0, visrchstr, t0, 1, 1))
+	    continue;
+	if (--n <= 0)
+	    break;
+    }
+    zle_goto_hist(hl);
+}
+
+/**/
+void
+virevrepeatsearch(void)
+{
+    visrchsense = -visrchsense;
+    virepeatsearch();
+    visrchsense = -visrchsense;
+}
+
+/* Extra function added by A.R. Iano-Fletcher.	*/
+/*The extern variable "cs" is the position of the cursor. */
+/* history-beginning-search-backward */
+
+/**/
+void
+historybeginningsearchbackward(void)
+{
+    int cpos = cs;		/* save cursor position */
+    int hl = histline;
+    int n = zmult;
+    char *s;
+
+    if (!n)
+	return;
+    if (n < 0) {
+	zmult = -n;
+	historybeginningsearchforward();
+	zmult = n;
+	return;
+    }
+    for (;;) {
+	hl--;
+	if (!(s = zle_get_event(hl))) {
+	    feep();
+	    return;
+	}
+	if (metadiffer(s, (char *)line, cs) < 0 &&
+	    metadiffer(s, (char *)line, ll))
+	    if (--n <= 0)
+		break;
+    }
+
+    zle_goto_hist(hl);
+    cs = cpos;
+}
+
+/* Extra function added by A.R. Iano-Fletcher.	*/
+
+/* history-beginning-search-forward */
+/**/
+void
+historybeginningsearchforward(void)
+{
+    int cpos = cs;		/* save cursor position */
+    int hl = histline;
+    int n = zmult;
+    char *s;
+
+    if (!n)
+	return;
+    if (n < 0) {
+	zmult = -n;
+	historybeginningsearchbackward();
+	zmult = n;
+	return;
+    }
+    for (;;) {
+	hl++;
+	if (!(s = zle_get_event(hl))) {
+	    feep();
+	    return;
+	}
+	if (metadiffer(s, (char *)line, cs) < (hl == curhist) &&
+	    metadiffer(s, (char *)line, ll))
+	    if (--n <= 0)
+		break;
+    }
+
+    zle_goto_hist(hl);
+    cs = cpos;
+}
diff --git a/Src/Zle/zle_keymap.c b/Src/Zle/zle_keymap.c
new file mode 100644
index 000000000..7de96bd03
--- /dev/null
+++ b/Src/Zle/zle_keymap.c
@@ -0,0 +1,1238 @@
+/*
+ * zle_keymap.c - keymaps and key bindings
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+
+/*
+ * Keymap structures:
+ *
+ * There is a hash table of keymap names.  Each name just points to a keymap.
+ * More than one name may point to the same keymap.
+ *
+ * Each keymap consists of a table of bindings for each character, and a
+ * hash table of multi-character key bindings.  The keymap has no individual
+ * name, but maintains a reference count.
+ *
+ * In a keymap's table of initial bindings, each character is either bound to
+ * a thingy, or is a prefix (in which case NULL is stored).  Those prefix
+ * entries are matched by more complex entries in the multi-character
+ * binding hash table.  Each entry in this hash table (which is indexed by
+ * metafied key sequence) either has a normal thingy binding or a string to
+ * send (in which case the NULL thingy is used).  Each entry also has a count
+ * of other entries for which it is a prefix.
+ */
+
+typedef struct keymapname *KeymapName;
+typedef struct key *Key;
+
+struct keymapname {
+    HashNode next;	/* next in the hash chain */
+    char *nam;		/* name of the keymap */
+    int flags;		/* various flags (see below) */
+    Keymap keymap;	/* the keymap itsef */
+};
+
+#define KMN_IMMORTAL (1<<1)
+
+struct keymap {
+    Thingy first[256];	/* base binding of each character */
+    HashTable multi;	/* multi-character bindings */
+    int flags;		/* various flags (see below) */
+    int rc;		/* reference count */
+};
+
+#define KM_IMMUTABLE (1<<1)
+
+struct key {
+    HashNode next;	/* next in hash chain */
+    char *nam;		/* key sequence (metafied) */
+    Thingy bind;	/* binding of this key sequence */
+    char *str;		/* string for send-string (metafied) */
+    int prefixct;	/* number of sequences for which this is a prefix */
+};
+
+/* This structure is used when listing keymaps. */
+
+struct bindstate {
+    int flags;
+    char *kmname;
+    char *firstseq;
+    char *lastseq;
+    Thingy bind;
+    char *str;
+};
+
+#define BS_LIST (1<<0)
+#define BS_ALL  (1<<1)
+
+/* local functions */
+
+#include "zle_keymap.pro"
+
+/* currently selected keymap, and its name */
+
+/**/
+Keymap curkeymap;
+/**/
+char *curkeymapname;
+
+/* the hash table of keymap names */
+
+static HashTable keymapnamtab;
+
+/* key sequence reading data */
+
+static char *keybuf;
+static int keybuflen, keybufsz = 20;
+
+/* last command executed with execute-named-command */
+
+static Thingy lastnamed;
+
+/**********************************/
+/* hashtable management functions */
+/**********************************/
+
+/**/
+static void
+createkeymapnamtab(void)
+{
+    keymapnamtab = newhashtable(7, "keymapnamtab", NULL);
+
+    keymapnamtab->hash        = hasher;
+    keymapnamtab->emptytable  = emptyhashtable;
+    keymapnamtab->filltable   = NULL;
+    keymapnamtab->addnode     = addhashnode;
+    keymapnamtab->getnode     = gethashnode2;
+    keymapnamtab->getnode2    = gethashnode2;
+    keymapnamtab->removenode  = removehashnode;
+    keymapnamtab->disablenode = NULL;
+    keymapnamtab->enablenode  = NULL;
+    keymapnamtab->freenode    = freekeymapnamnode;
+    keymapnamtab->printnode   = NULL;
+}
+
+/**/
+static KeymapName
+makekeymapnamnode(Keymap keymap)
+{
+    KeymapName kmn = (KeymapName) zcalloc(sizeof(*kmn));
+
+    kmn->keymap = keymap;
+    return kmn;
+}
+
+/**/
+static void
+freekeymapnamnode(HashNode hn)
+{
+    KeymapName kmn = (KeymapName) hn;
+
+    zsfree(kmn->nam);
+    if(!--kmn->keymap->rc)
+	deletekeymap(kmn->keymap);
+    zfree(kmn, sizeof(*kmn));
+}
+
+/**/
+static HashTable
+newkeytab(char *kmname)
+{
+    HashTable ht = newhashtable(19,
+	kmname ?  dyncat("keytab:", kmname) : "keytab:", NULL);
+
+    ht->hash        = hasher;
+    ht->emptytable  = emptyhashtable;
+    ht->filltable   = NULL;
+    ht->addnode     = addhashnode;
+    ht->getnode     = gethashnode2;
+    ht->getnode2    = gethashnode2;
+    ht->removenode  = removehashnode;
+    ht->disablenode = NULL;
+    ht->enablenode  = NULL;
+    ht->freenode    = freekeynode;
+    ht->printnode   = NULL;
+
+    return ht;
+}
+
+/**/
+static Key
+makekeynode(Thingy t, char *str)
+{
+    Key k = (Key) zcalloc(sizeof(*k));
+
+    k->bind = t;
+    k->str = str;
+    return k;
+}
+
+/**/
+static void
+freekeynode(HashNode hn)
+{
+    Key k = (Key) hn;
+
+    zsfree(k->nam);
+    unrefthingy(k->bind);
+    zsfree(k->str);
+    zfree(k, sizeof(*k));
+}
+
+/**************************/
+/* main keymap operations */
+/**************************/
+
+static HashTable copyto;
+
+/**/
+static Keymap
+newkeymap(Keymap tocopy, char *kmname)
+{
+    Keymap km = zcalloc(sizeof(*km));
+    int i;
+
+    km->rc = 0;
+    km->multi = newkeytab(kmname);
+    if(tocopy) {
+	for(i = 256; i--; )
+	    km->first[i] = refthingy(tocopy->first[i]);
+	copyto = km->multi;
+	scanhashtable(tocopy->multi, 0, 0, 0, scancopykeys, 0);
+    } else {
+	for(i = 256; i--; )
+	    km->first[i] = refthingy(t_undefinedkey);
+    }
+    return km;
+}
+
+/**/
+static void
+scancopykeys(HashNode hn, int flags)
+{
+    Key k = (Key) hn;
+    Key kn = zalloc(sizeof(*k));
+
+    memcpy(kn, k, sizeof(*k));
+    refthingy(kn->bind);
+    kn->str = ztrdup(k->str);
+    copyto->addnode(copyto, ztrdup(k->nam), kn);
+}
+
+/**/
+static void
+deletekeymap(Keymap km)
+{
+    int i;
+
+    deletehashtable(km->multi);
+    for(i = 256; i--; )
+	unrefthingy(km->first[i]);
+    zfree(km, sizeof(*km));
+}
+
+static Keymap skm_km;
+static int skm_last;
+static KeyScanFunc skm_func;
+static void *skm_magic;
+
+/**/
+void
+scankeymap(Keymap km, int sort, KeyScanFunc func, void *magic)
+{
+    char m[3];
+
+    skm_km = km;
+    skm_last = sort ? -1 : 255;
+    skm_func = func;
+    skm_magic = magic;
+    scanhashtable(km->multi, sort, 0, 0, scankeys, 0);
+    if(!sort)
+	skm_last = -1;
+    while(skm_last < 255) {
+	skm_last++;
+	if(km->first[skm_last] && km->first[skm_last] != t_undefinedkey) {
+	    m[0] = skm_last;
+	    metafy(m, 1, META_NOALLOC);
+	    func(m, km->first[skm_last], NULL, magic);
+	}
+    }
+}
+
+/**/
+static void
+scankeys(HashNode hn, int flags)
+{
+    Key k = (Key) hn;
+    int f = k->nam[0] == Meta ? STOUC(k->nam[1])^32 : STOUC(k->nam[0]);
+    char m[3];
+
+    while(skm_last < f) {
+	skm_last++;
+	if(skm_km->first[skm_last] &&
+	   skm_km->first[skm_last] != t_undefinedkey) {
+	    m[0] = skm_last;
+	    metafy(m, 1, META_NOALLOC);
+	    skm_func(m, skm_km->first[skm_last], NULL, skm_magic);
+	}
+    }
+    skm_func(k->nam, k->bind, k->str, skm_magic);
+}
+
+/**************************/
+/* keymap name operations */
+/**************************/
+
+/**/
+Keymap
+openkeymap(char *name)
+{
+    KeymapName n = (KeymapName) keymapnamtab->getnode(keymapnamtab, name);
+    return n ? n->keymap : NULL;
+}
+
+/**/
+static int
+unlinkkeymap(char *name)
+{
+    KeymapName n = (KeymapName) keymapnamtab->getnode(keymapnamtab, name);
+    if(!n)
+	return 2;
+    if(n->flags & KMN_IMMORTAL)
+	return 1;
+    keymapnamtab->freenode(keymapnamtab->removenode(keymapnamtab, name));
+    return 0;
+}
+
+/**/
+static int
+linkkeymap(Keymap km, char *name)
+{
+    KeymapName n = (KeymapName) keymapnamtab->getnode(keymapnamtab, name);
+    if(n) {
+	if(n->flags & KMN_IMMORTAL)
+	    return 1;
+	if(n->keymap == km)
+	    return 0;
+	if(!--n->keymap->rc)
+	    deletekeymap(n->keymap);
+	n->keymap = km;
+    } else
+	keymapnamtab->addnode(keymapnamtab, ztrdup(name),
+	    makekeymapnamnode(km));
+    km->rc++;
+    return 0;
+}
+
+/* Select a keymap as the current ZLE keymap.  Can optionally fall back *
+ * on the guaranteed safe keymap if it fails.                           */
+
+/**/
+int
+selectkeymap(char *name, int fb)
+{
+    Keymap km = openkeymap(name);
+
+    if(!km) {
+	char *nm = niceztrdup(name);
+	char *msg = tricat("No such keymap `", nm, "'");
+
+	zsfree(nm);
+	showmsg(msg);
+	zsfree(msg);
+	if(!fb)
+	    return 1;
+	km = openkeymap(name = ".safe");
+    }
+    curkeymapname = name;
+    curkeymap = km;
+    return 0;
+}
+
+/* Reopen the currently selected keymap, in case it got deleted.  This *
+ * should be called after doing anything that might have run an        *
+ * arbitrary user-specified command.                                   */
+
+/**/
+void
+reselectkeymap(void)
+{
+    selectkeymap(curkeymapname, 1);
+}
+
+/******************************/
+/* operations on key bindings */
+/******************************/
+
+/* Add/delete/change a keybinding in some keymap.  km is the keymap to be *
+ * altered.  seq is the metafied key sequence whose binding is to change. *
+ * bind is the thingy to which the key sequence is to be bound.  For      *
+ * send-string, bind is NULL and str is the metafied key sequence to push *
+ * back onto the input.                                                   */
+
+/**/
+int
+bindkey(Keymap km, char *seq, Thingy bind, char *str)
+{
+    Key k;
+    int f = seq[0] == Meta ? STOUC(seq[1])^32 : STOUC(seq[0]);
+    char *buf, *ptr;
+
+    if(km->flags & KM_IMMUTABLE)
+	return 1;
+    if(!*seq)
+	return 2;
+    if(!bind || ztrlen(seq) > 1) {
+	/* key needs to become a prefix if isn't one already */
+	if(km->first[f]) {
+	    char fs[3];
+	    fs[0] = f;
+	    metafy(fs, 1, META_NOALLOC);
+	    km->multi->addnode(km->multi, ztrdup(fs),
+		makekeynode(km->first[f], NULL));
+	    km->first[f] = NULL;
+	}
+	k = (Key) km->multi->getnode(km->multi, seq);
+    } else {
+	/* If the sequence is a prefix entry only due to being *
+	 * a send-string binding, we can remove that entry.    */
+	if(!km->first[f]) {
+	    k = (Key) km->multi->getnode(km->multi, seq);
+	    if(!k->prefixct)
+		km->multi->freenode(km->multi->removenode(km->multi, seq));
+	    else
+		goto domulti;
+	} else
+	    unrefthingy(km->first[f]);
+	/* Just replace the single-character binding. */
+	km->first[f] = bind;
+	return 0;
+    }
+    domulti:
+    buf = ztrdup(seq);
+    ptr = strchr(buf, 0);
+    if(bind == t_undefinedkey) {
+	if(k) {
+	    zsfree(k->str);
+	    unrefthingy(k->bind);
+	    k->bind = t_undefinedkey;
+	    k->str = NULL;
+	    while(!k->prefixct && k->bind == t_undefinedkey) {
+		km->multi->freenode(km->multi->removenode(km->multi, buf));
+		*--ptr = 0;
+		if(ptr[-1] == Meta)
+		    *--ptr = 0;
+		k = (Key) km->multi->getnode(km->multi, buf);
+		k->prefixct--;
+		if(!k->prefixct && k->bind &&
+		    (!buf[1] || (buf[0] == Meta && !buf[2]))) {
+		    km->first[f] = refthingy(k->bind);
+		    km->multi->freenode(km->multi->removenode(km->multi, buf));
+		    break;
+		}
+	    }
+	}
+    } else {
+	if(!k) {
+	    int added;
+
+	    km->multi->addnode(km->multi, ztrdup(buf), makekeynode(bind, ztrdup(str)));
+	    do {
+		*--ptr = 0;
+		if(ptr > buf && ptr[-1] == Meta)
+		    *--ptr = 0;
+		k = (Key) km->multi->getnode(km->multi, buf);
+		if((added = !k))
+		    km->multi->addnode(km->multi, ztrdup(buf),
+			k = makekeynode(refthingy(t_undefinedkey), NULL));
+		k->prefixct++;
+	    } while(added);
+	} else {
+	    unrefthingy(k->bind);
+	    zsfree(k->str);
+	    k->bind = bind;
+	    k->str = bind ? NULL : ztrdup(str);
+	}
+    }
+    free(buf);
+    return 0;
+}
+
+/* Look up a key binding.  The binding is returned.  In the case of a  *
+ * send-string, NULL is returned and *strp is modified to point to the *
+ * metafied string of characters to be pushed back.                    */
+
+/**/
+Thingy
+keybind(Keymap km, char *seq, char **strp)
+{
+    Key k;
+
+    if(ztrlen(seq) == 1) {
+	int f = seq[0] == Meta ? STOUC(seq[1])^32 : STOUC(seq[0]);
+	Thingy bind = km->first[f];
+
+	if(bind)
+	    return bind;
+    }
+    k = (Key) km->multi->getnode(km->multi, seq);
+    if(!k)
+	return t_undefinedkey;
+    *strp = k->str;
+    return k->bind;
+}
+
+/* Check whether a key sequence is a prefix of a longer bound sequence. *
+ * One oddity: if *nothing* in the keymap is bound, this returns true   *
+ * for the empty sequence, even though this is not strictly accurate.   */
+
+/**/
+static int
+keyisprefix(Keymap km, char *seq)
+{
+    Key k;
+
+    if(!*seq)
+	return 1;
+    if(ztrlen(seq) == 1) {
+	int f = seq[0] == Meta ? STOUC(seq[1])^32 : STOUC(seq[0]);
+
+	if(km->first[f])
+	    return 0;
+    }
+    k = (Key) km->multi->getnode(km->multi, seq);
+    return k && k->prefixct;
+}
+
+/*******************/
+/* bindkey builtin */
+/*******************/
+
+/*
+ * THE BINDKEY BUILTIN
+ *
+ * Keymaps can be specified to bindkey in the following ways:
+ *
+ *   -e   select "emacs", also link it to "main"
+ *   -v   select "viins", also link it to "main"
+ *   -a   select "vicmd"
+ *   -M   first argument gives map name
+ *        defaults to "main"
+ *
+ * These operations cannot have a keymap selected in the normal way:
+ *
+ *   -l   list all the keymap names
+ *   -d   delete all keymaps and reset to the default state (no arguments)
+ *   -D   delete named keymaps
+ *   -A   link the two named keymaps (2 arguments)
+ *   -N   create new empty keymap (1 argument)
+ *   -N   create new keymap, copying the second named keymap (2 arguments)
+ *
+ * Other operations:
+ *
+ *   -m   add the meta bindings to the selected keymap (no arguments)
+ *   -r   unbind each named string in the selected keymap
+ *   -s   bind send-strings in the selected keymap (2+ arguments)
+ *        bind commands in the selected keymap (2+ arguments)
+ *        display one binding in the selected keymap (1 argument)
+ *        display the entire selected keymap (no arguments)
+ *
+ * There is an exception that the entire keymap display will not be performed
+ * if the -e or -v options were used.
+ *
+ * Other options:
+ *
+ *   -L   do listings in the form of bindkey commands
+ *   -R   for the binding operations, accept ranges instead of sequences
+ */
+
+/**/
+int
+bin_bindkey(char *name, char **argv, char *ops, int func)
+{
+    static struct opn {
+	char o;
+	char selp;
+	int (*func) _((char *, char *, Keymap, char **, char *, char));
+	int min, max;
+    } const opns[] = {
+	{ 'l', 0, bin_bindkey_lsmaps, 0,  0 },
+	{ 'd', 0, bin_bindkey_delall, 0,  0 },
+	{ 'D', 0, bin_bindkey_del,    1, -1 },
+	{ 'A', 0, bin_bindkey_link,   2,  2 },
+	{ 'N', 0, bin_bindkey_new,    1,  2 },
+	{ 'm', 1, bin_bindkey_meta,   0,  0 },
+	{ 'r', 1, bin_bindkey_bind,   1, -1 },
+	{ 's', 1, bin_bindkey_bind,   2, -1 },
+	{ 0,   1, bin_bindkey_bind,   0, -1 },
+    };
+    struct opn const *op, *opp;
+    char *kmname;
+    Keymap km;
+    int n;
+
+    /* select operation and ensure no clashing arguments */
+    for(op = opns; op->o && !ops[op->o]; op++) ;
+    if(op->o)
+	for(opp = op; (++opp)->o; )
+	    if(ops[opp->o]) {
+		zwarnnam(name, "incompatible operation selection options",
+		    NULL, 0);
+		return 1;
+	    }
+    n = ops['e'] + ops['v'] + ops['a'] + ops['M'];
+    if(!op->selp && n) {
+	zwarnnam(name, "keymap cannot be selected with -%c", NULL, op->o);
+	return 1;
+    }
+    if(n > 1) {
+	zwarnnam(name, "incompatible keymap selection options", NULL, 0);
+	return 1;
+    }
+
+    /* keymap selection */
+    if(op->selp) {
+	if(ops['e'])
+	    kmname = "emacs";
+	else if(ops['v'])
+	    kmname = "viins";
+	else if(ops['a'])
+	    kmname = "vicmd";
+	else if(ops['M']) {
+	    kmname = *argv++;
+	    if(!kmname) {
+		zwarnnam(name, "-M option requires a keymap argument", NULL, 0);
+		return 1;
+	    }
+	} else
+	    kmname = "main";
+	km = openkeymap(kmname);
+	if(!km) {
+	    zwarnnam(name, "no such keymap `%s'", kmname, 0);
+	    return 1;
+	}
+	if(ops['e'] || ops['v'])
+	    linkkeymap(km, "main");
+    } else {
+	kmname = NULL;
+	km = NULL;
+    }
+
+    /* listing is a special case */
+    if(!op->o && (!argv[0] || !argv[1])) {
+	if(ops['e'] || ops['v'])
+	    return 0;
+	return bin_bindkey_list(name, kmname, km, argv, ops, op->o);
+    }
+
+    /* check number of arguments */
+    for(n = 0; argv[n]; n++) ;
+    if(n < op->min) {
+	zwarnnam(name, "not enough arguments for -%c", NULL, op->o);
+	return 1;
+    } else if(op->max != -1 && n > op->max) {
+	zwarnnam(name, "too many arguments for -%c", NULL, op->o);
+	return 1;
+    }
+
+    /* pass on the work to the operation function */
+    return op->func(name, kmname, km, argv, ops, op->o);
+}
+
+/* list the available keymaps */
+
+/**/
+static int
+bin_bindkey_lsmaps(char *name, char *kmname, Keymap km, char **argv, char *ops, char func)
+{
+    scanhashtable(keymapnamtab, 1, 0, 0, scanlistmaps, ops['L']);
+    return 0;
+}
+
+/**/
+static void
+scanlistmaps(HashNode hn, int list)
+{
+    KeymapName n = (KeymapName) hn;
+
+    if(list) {
+	fputs("bindkey -N ", stdout);
+	if(n->nam[0] == '-')
+	    fputs("-- ", stdout);
+	quotedzputs(n->nam, stdout);
+    } else
+	nicezputs(n->nam, stdout);
+    putchar('\n');
+}
+
+/* reset all keymaps to the default state */
+
+/**/
+static int
+bin_bindkey_delall(char *name, char *kmname, Keymap km, char **argv, char *ops, char func)
+{
+    keymapnamtab->emptytable(keymapnamtab);
+    default_bindings();
+    return 0;
+}
+
+/* delete named keymaps */
+
+/**/
+static int
+bin_bindkey_del(char *name, char *kmname, Keymap km, char **argv, char *ops, char func)
+{
+    int ret = 0;
+
+    do {
+	int r = unlinkkeymap(*argv);
+	if(r == 1)
+	    zwarnnam(name, "keymap name `%s' is protected", *argv, 0);
+	else if(r == 2)
+	    zwarnnam(name, "no such keymap `%s'", *argv, 0);
+	ret |= !!r;
+    } while(*++argv);
+    return ret;
+}
+
+/* link named keymaps */
+
+/**/
+static int
+bin_bindkey_link(char *name, char *kmname, Keymap km, char **argv, char *ops, char func)
+{
+    km = openkeymap(argv[0]);
+    if(!km) {
+	zwarnnam(name, "no such keymap `%s'", argv[0], 0);
+	return 1;
+    } else if(linkkeymap(km, argv[1])) {
+	zwarnnam(name, "keymap name `%s' is protected", argv[1], 0);
+	return 1;
+    }
+    return 0;
+}
+
+/* create a new keymap */
+
+/**/
+static int
+bin_bindkey_new(char *name, char *kmname, Keymap km, char **argv, char *ops, char func)
+{
+    KeymapName kmn = (KeymapName) keymapnamtab->getnode(keymapnamtab, argv[0]);
+
+    if(kmn && (kmn -> flags & KMN_IMMORTAL)) {
+	zwarnnam(name, "keymap name `%s' is protected", argv[0], 0);
+	return 1;
+    }
+    if(argv[1]) {
+	km = openkeymap(argv[1]);
+	if(!km) {
+	    zwarnnam(name, "no such keymap `%s'", argv[0], 0);
+	    return 1;
+	}
+    } else
+	km = NULL;
+    linkkeymap(newkeymap(km, argv[0]), argv[0]);
+    return 0;
+}
+
+/* Add standard meta bindings to a keymap.  Only sequences currently either *
+ * unbound or bound to self-insert are affected.  Note that the use of      *
+ * bindkey() is quite necessary: if this function were to go through the    *
+ * km->first table itself, it would miss any prefix sequences that should   *
+ * be rebound.                                                              */
+
+/**/
+static int
+bin_bindkey_meta(char *name, char *kmname, Keymap km, char **argv, char *ops, char func)
+{
+    char m[3], *str;
+    int i;
+    Thingy fn;
+
+    if(km->flags & KM_IMMUTABLE) {
+	zwarnnam(name, "keymap `%s' is protected", kmname, 0);
+	return 1;
+    }
+    for(i = 128; i < 256; i++)
+	if(metabind[i - 128] != z_undefinedkey) {
+	    m[0] = i;
+	    metafy(m, 1, META_NOALLOC);
+	    fn = keybind(km, m, &str);
+	    if(fn == t_selfinsert || fn == t_undefinedkey)
+		bindkey(km, m, refthingy(Th(metabind[i - 128])), NULL);
+	}
+    return 0;
+}
+
+/* Change key bindings.  func can be:              *
+ *   'r'  bind sequences to undefined-key          *
+ *   's'  bind sequneces to specified send-strings *
+ *   0    bind sequences to specified functions    *
+ * If the -R option is used, bind to key ranges    *
+ * instead of single key sequences.                */
+
+/**/
+static int
+bin_bindkey_bind(char *name, char *kmname, Keymap km, char **argv, char *ops, char func)
+{
+    int ret = 0;
+
+    if(!func || func == 's') {
+	char **a;
+
+	for(a = argv+2; *a; a++)
+	    if(!*++a) {
+		zwarnnam(name, "even number of arguments required", NULL, 0);
+		return 1;
+	    }
+    }
+    if(km->flags & KM_IMMUTABLE) {
+	zwarnnam(name, "keymap `%s' is protected", kmname, 0);
+	return 1;
+    }
+    do {
+	char *useq = *argv, *bseq, *seq, *str;
+	int len;
+	Thingy fn;
+
+	if(func == 'r') {
+	    fn = refthingy(t_undefinedkey);
+	    str = NULL;
+	} else if(func == 's') {
+	    str = getkeystring(*++argv, &len, 2, NULL);
+	    fn = NULL;
+	    str = metafy(str, len, META_HREALLOC);
+	} else {
+	    fn = rthingy(*++argv);
+	    str = NULL;
+	}
+	bseq = getkeystring(useq, &len, 2, NULL);
+	seq = metafy(bseq, len, META_USEHEAP);
+	if(ops['R']) {
+	    int first, last;
+	    char m[3];
+
+	    if(len < 2 || len > 2 + (bseq[1] == '-') ||
+	       (first = STOUC(bseq[0])) > (last = STOUC(bseq[len - 1]))) {
+		zwarnnam(name, "malformed key range `%s'", useq, 0);
+		ret = 1;
+	    } else {
+		for(; first <= last; first++) {
+		    m[0] = first;
+		    metafy(m, 1, META_NOALLOC);
+		    bindkey(km, m, refthingy(fn), str);
+		}
+		unrefthingy(fn);
+	    }
+	} else {
+	    if(bindkey(km, seq, fn, str)) {
+		zwarnnam(name, "cannot bind to an empty key sequence", NULL, 0);
+		ret = 1;
+	    }
+	}
+    } while(*++argv);
+    return ret;
+}
+
+/* List key bindings.  If an argument is given, list just that one *
+ * binding, otherwise list the entire keymap.  If the -L option is *
+ * given, list in the form of bindkey commands.                    */
+
+/**/
+static int
+bin_bindkey_list(char *name, char *kmname, Keymap km, char **argv, char *ops, char func)
+{
+    struct bindstate bs;
+
+    bs.flags = ops['L'] ? BS_LIST : 0;
+    bs.kmname = kmname;
+    if(argv[0]) {
+	int len;
+	char *seq;
+
+	seq = getkeystring(argv[0], &len, 2, NULL);
+	seq = metafy(seq, len, META_HREALLOC);
+	bs.flags |= BS_ALL;
+	bs.firstseq = bs.lastseq = seq;
+	bs.bind = keybind(km, seq, &bs.str);
+	bindlistout(&bs);
+    } else {
+	bs.firstseq = ztrdup("");
+	bs.lastseq = ztrdup("");
+	bs.bind = t_undefinedkey;
+	bs.str = NULL;
+	scankeymap(km, 1, scanbindlist, &bs);
+	bindlistout(&bs);
+	zsfree(bs.firstseq);
+	zsfree(bs.lastseq);
+    }
+    return 0;
+}
+
+/**/
+static void
+scanbindlist(char *seq, Thingy bind, char *str, void *magic)
+{
+    struct bindstate *bs = magic;
+
+    if(bind == bs->bind && (bind || !strcmp(str, bs->str)) &&
+       ztrlen(seq) == 1 && ztrlen(bs->lastseq) == 1) {
+	int l = bs->lastseq[1] ?
+	    STOUC(bs->lastseq[1]) ^ 32 : STOUC(bs->lastseq[0]);
+	int t = seq[1] ? STOUC(seq[1]) ^ 32 : STOUC(seq[0]);
+
+	if(t == l + 1) {
+	    zsfree(bs->lastseq);
+	    bs->lastseq = ztrdup(seq);
+	    return;
+	}
+    }
+    bindlistout(bs);
+    zsfree(bs->firstseq);
+    bs->firstseq = ztrdup(seq);
+    zsfree(bs->lastseq);
+    bs->lastseq = ztrdup(seq);
+    bs->bind = bind;
+    bs->str = str;
+}
+
+/**/
+static void
+bindlistout(struct bindstate *bs)
+{
+    int range;
+
+    if(bs->bind == t_undefinedkey && !(bs->flags & BS_ALL))
+	return;
+    range = strcmp(bs->firstseq, bs->lastseq);
+    if(bs->flags & BS_LIST) {
+	int nodash = 1;
+
+	fputs("bindkey ", stdout);
+	if(range)
+	    fputs("-R ", stdout);
+	if(!bs->bind)
+	    fputs("-s ", stdout);
+	if(!strcmp(bs->kmname, "main"))
+	    ;
+	else if(!strcmp(bs->kmname, "vicmd"))
+	    fputs("-a ", stdout);
+	else {
+	    fputs("-M ", stdout);
+	    quotedzputs(bs->kmname, stdout);
+	    putchar(' ');
+	    nodash = 0;
+	}
+	if(nodash && bs->firstseq[0] == '-')
+	    fputs("-- ", stdout);
+    }
+    printbind(bs->firstseq, stdout);
+    if(range) {
+	putchar('-');
+	printbind(bs->lastseq, stdout);
+    }
+    putchar(' ');
+    if(bs->bind) {
+	((bs->flags & BS_LIST) ? quotedzputs : nicezputs)
+	    (bs->bind->nam, stdout);
+    } else
+	printbind(bs->str, stdout);
+    putchar('\n');
+}
+
+/****************************/
+/* initialisation functions */
+/****************************/
+
+/* main initialisation entry point */
+
+/**/
+void
+init_keymaps(void)
+{
+    createkeymapnamtab();
+    default_bindings();
+    keybuf = (char *)zalloc(keybufsz);
+    lastnamed = refthingy(t_undefinedkey);
+}
+
+#ifdef MODULE
+
+/* cleanup entry point (for unloading the zle module) */
+
+/**/
+void
+cleanup_keymaps(void)
+{
+    unrefthingy(lastnamed);
+    deletehashtable(keymapnamtab);
+    zfree(keybuf, keybufsz);
+}
+
+#endif /* MODULE */
+
+/* Create the default keymaps.  For efficiency reasons, this function   *
+ * assigns directly to the km->first array.  It knows that there are no *
+ * prefix bindings in the way, and that it is using a simple keymap.    */
+
+/**/
+static void
+default_bindings(void)
+{
+    Keymap vmap = newkeymap(NULL, "viins");
+    Keymap emap = newkeymap(NULL, "emacs");
+    Keymap amap = newkeymap(NULL, "vicmd");
+    Keymap smap = newkeymap(NULL, ".safe");
+    char buf[3], *ed;
+    int i;
+
+    /* vi insert mode and emacs mode:  *
+     *   0-31   taken from the tables  *
+     *  32-126  self-insert            *
+     * 127      same as entry[8]       *
+     * 128-255  self-insert            */
+    for (i = 0; i < 32; i++) {
+	vmap->first[i] = refthingy(Th(viinsbind[i]));
+	emap->first[i] = refthingy(Th(emacsbind[i]));
+    }
+    for (i = 32; i < 256; i++) {
+	vmap->first[i] = refthingy(t_selfinsert);
+	emap->first[i] = refthingy(t_selfinsert);
+    }
+    unrefthingy(t_selfinsert);
+    unrefthingy(t_selfinsert);
+    vmap->first[127] = refthingy(vmap->first[8]);
+    emap->first[127] = refthingy(emap->first[8]);
+
+    /* vi command mode:              *
+     *   0-127  taken from the table *
+     * 128-255  undefined-key        */
+    for (i = 0; i < 128; i++)
+	amap->first[i] = refthingy(Th(vicmdbind[i]));
+    for (i = 128; i < 256; i++)
+	amap->first[i] = refthingy(t_undefinedkey);
+
+    /* safe fallback keymap:
+     *   0-255  self-insert, except: *
+     *    '\n'  accept-line          *
+     *    '\r'  accept-line          */
+    for (i = 0; i < 256; i++)
+	smap->first[i] = refthingy(t_selfinsert);
+    unrefthingy(t_selfinsert);
+    unrefthingy(t_selfinsert);
+    smap->first['\n'] = refthingy(t_acceptline);
+    smap->first['\r'] = refthingy(t_acceptline);
+
+    /* vt100 arrow keys are bound by default, for historical reasons. *
+     * Both standard and keypad modes are supported.                  */
+
+    /* vi command mode: arrow keys */
+    bindkey(amap, "\33[A",  refthingy(t_uplineorhistory), NULL);
+    bindkey(amap, "\33[B",  refthingy(t_downlineorhistory), NULL);
+    bindkey(amap, "\33[C",  refthingy(t_viforwardchar), NULL);
+    bindkey(amap, "\33[D",  refthingy(t_vibackwardchar), NULL);
+    bindkey(amap, "\33OA",  refthingy(t_uplineorhistory), NULL);
+    bindkey(amap, "\33OB",  refthingy(t_downlineorhistory), NULL);
+    bindkey(amap, "\33OC",  refthingy(t_viforwardchar), NULL);
+    bindkey(amap, "\33OD",  refthingy(t_vibackwardchar), NULL);
+
+    /* emacs mode: arrow keys */
+    bindkey(emap, "\33[A",  refthingy(t_uplineorhistory), NULL);
+    bindkey(emap, "\33[B",  refthingy(t_downlineorhistory), NULL);
+    bindkey(emap, "\33[C",  refthingy(t_forwardchar), NULL);
+    bindkey(emap, "\33[D",  refthingy(t_backwardchar), NULL);
+    bindkey(emap, "\33OA",  refthingy(t_uplineorhistory), NULL);
+    bindkey(emap, "\33OB",  refthingy(t_downlineorhistory), NULL);
+    bindkey(emap, "\33OC",  refthingy(t_forwardchar), NULL);
+    bindkey(emap, "\33OD",  refthingy(t_backwardchar), NULL);
+   
+    /* emacs mode: ^X sequences */
+    bindkey(emap, "\30*",   refthingy(t_expandword), NULL);
+    bindkey(emap, "\30g",   refthingy(t_listexpand), NULL);
+    bindkey(emap, "\30G",   refthingy(t_listexpand), NULL);
+    bindkey(emap, "\30\16", refthingy(t_infernexthistory), NULL);
+    bindkey(emap, "\30\13", refthingy(t_killbuffer), NULL);
+    bindkey(emap, "\30\6",  refthingy(t_vifindnextchar), NULL);
+    bindkey(emap, "\30\17", refthingy(t_overwritemode), NULL);
+    bindkey(emap, "\30\25", refthingy(t_undo), NULL);
+    bindkey(emap, "\30\26", refthingy(t_vicmdmode), NULL);
+    bindkey(emap, "\30\12", refthingy(t_vijoin), NULL);
+    bindkey(emap, "\30\2",  refthingy(t_vimatchbracket), NULL);
+    bindkey(emap, "\30s",   refthingy(t_historyincrementalsearchforward), NULL);
+    bindkey(emap, "\30r",   refthingy(t_historyincrementalsearchbackward), NULL);
+    bindkey(emap, "\30u",   refthingy(t_undo), NULL);
+    bindkey(emap, "\30\30", refthingy(t_exchangepointandmark), NULL);
+    bindkey(emap, "\30=",   refthingy(t_whatcursorposition), NULL);
+
+    /* emacs mode: ESC sequences, all taken from the meta binding table */
+    buf[0] = '\33';
+    buf[2] = 0;
+    for (i = 0; i < 128; i++)
+	if (metabind[i] != z_undefinedkey) {
+	    buf[1] = i;
+	    bindkey(emap, buf, refthingy(Th(metabind[i])), NULL);
+	}
+
+    /* Put the keymaps in the right namespace.  The "main" keymap  *
+     * will be linked to the "emacs" keymap, except that if VISUAL *
+     * or EDITOR contain the string "vi" then it will be linked to *
+     * the "viins" keymap.                                         */
+    linkkeymap(vmap, "viins");
+    linkkeymap(emap, "emacs");
+    linkkeymap(amap, "vicmd");
+    linkkeymap(smap, ".safe");
+    if (((ed = zgetenv("VISUAL")) && strstr(ed, "vi")) ||
+	((ed = zgetenv("EDITOR")) && strstr(ed, "vi")))
+	linkkeymap(vmap, "main");
+    else
+	linkkeymap(emap, "main");
+
+    /* the .safe map cannot be modified or deleted */
+    smap->flags |= KM_IMMUTABLE;
+    ((KeymapName) keymapnamtab->getnode(keymapnamtab, ".safe"))->flags
+	|= KMN_IMMORTAL;
+}
+
+/*************************/
+/* reading key sequences */
+/*************************/
+
+/* read a sequence of keys that is bound to some command in a keymap */
+
+/**/
+char *
+getkeymapcmd(Keymap km, Thingy *funcp, char **strp)
+{
+    Thingy func = t_undefinedkey;
+    char *str = NULL;
+    int lastlen = 0, lastc = c;
+
+    keybuflen = 0;
+    keybuf[0] = 0;
+    while((c = getkeybuf(!!lastlen)) != EOF) {
+	char *s;
+	Thingy f = keybind(km, keybuf, &s);
+
+	if(f != t_undefinedkey) {
+	    lastlen = keybuflen;
+	    func = f;
+	    str = s;
+	    lastc = c;
+	}
+	if(!keyisprefix(km, keybuf))
+	    break;
+    }
+    if(!lastlen && keybuflen)
+	lastlen = keybuflen;
+    else
+	c = lastc;
+    if(lastlen != keybuflen) {
+	unmetafy(keybuf + lastlen, &keybuflen);
+	ungetkeys(keybuf+lastlen, keybuflen);
+	if(vichgflag)
+	    vichgbufptr -= keybuflen;
+	keybuf[lastlen] = 0;
+    }
+    *funcp = func;
+    *strp = str;
+    return keybuf;
+}
+
+/**/
+static int
+getkeybuf(int w)
+{
+    int c = getkey(w);
+
+    if(c < 0)
+	return EOF;
+    if(keybuflen + 3 > keybufsz)
+	keybuf = realloc(keybuf, keybufsz *= 2);
+    if(imeta(c)) {
+	keybuf[keybuflen++] = Meta;
+	keybuf[keybuflen++] = c ^ 32;
+    } else
+	keybuf[keybuflen++] = c;
+    keybuf[keybuflen] = 0;
+    return c;
+}
+
+/* Push back the last command sequence read by getkeymapcmd(). *
+ * Must be executed at most once after each getkeymapcmd().    */
+
+/**/
+void
+ungetkeycmd(void)
+{
+    ungetkeys(keybuf, keybuflen);
+}
+
+/* read a command from the current keymap, with widgets */
+
+/**/
+Thingy
+getkeycmd(void)
+{
+    Thingy func;
+    int hops = 0;
+    char *seq, *str;
+
+    sentstring:
+    seq = getkeymapcmd(curkeymap, &func, &str);
+    if(!*seq)
+	return NULL;
+    if(!func) {
+	int len;
+	char *pb;
+
+	if (++hops == 20) {
+	    zerr("string inserting another one too many times", NULL, 0);
+	    hops = 0;
+	    return NULL;
+	}
+	pb = unmetafy(ztrdup(str), &len);
+	ungetkeys(pb, len);
+	zfree(pb, strlen(str) + 1);
+	goto sentstring;
+    }
+    if (func == Th(z_executenamedcmd) && !statusline) {
+	while(func == Th(z_executenamedcmd))
+	    func = executenamedcommand("execute: ");
+	if(!func)
+	    func = t_undefinedkey;
+	else if(func != Th(z_executelastnamedcmd)) {
+	    unrefthingy(lastnamed);
+	    lastnamed = refthingy(func);
+	}
+    }
+    if (func == Th(z_executelastnamedcmd))
+	func = lastnamed;
+    return func;
+}
diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c
new file mode 100644
index 000000000..338cdef41
--- /dev/null
+++ b/Src/Zle/zle_main.c
@@ -0,0 +1,907 @@
+/*
+ * zle_main.c - main routines for line editor
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_main.pro"
+
+/* != 0 if we're done editing */
+
+/**/
+int done;
+
+/* location of mark */
+
+/**/
+int mark;
+
+/* last character pressed */
+
+/**/
+int c;
+
+/* the binding for this key */
+
+/**/
+Thingy bindk;
+
+/* insert mode/overwrite mode flag */
+
+/**/
+int insmode;
+
+static int eofchar, eofsent;
+static long keytimeout;
+
+#ifdef HAVE_SELECT
+/* Terminal baud rate */
+
+static int baud;
+#endif
+
+/* flags associated with last command */
+
+/**/
+int lastcmd;
+
+/* the status line, and its length */
+
+/**/
+char *statusline;
+/**/
+int statusll;
+
+/* The current history line and cursor position for the top line *
+ * on the buffer stack.                                          */
+
+/**/
+int stackhist, stackcs;
+
+/* != 0 if we are making undo records */
+
+/**/
+int undoing;
+
+/* current modifier status */
+
+/**/
+struct modifier zmod;
+
+/* Current command prefix status.  This is normally 0.  Prefixes set *
+ * this to 1.  Each time round the main loop, this is checked: if it *
+ * is 0, the modifier status is reset; if it is 1, the modifier      *
+ * status is left unchanged, and this flag is reset to 0.  The       *
+ * effect is that several prefix commands can be executed, and have  *
+ * cumulative effect, but any other command execution will clear the *
+ * modifiers.                                                        */
+
+/**/
+int prefixflag;
+
+/* != 0 if there is a pending beep (usually indicating an error) */
+
+/**/
+int feepflag;
+
+/* set up terminal */
+
+/**/
+void
+setterm(void)
+{
+    struct ttyinfo ti;
+
+#if defined(CLOBBERS_TYPEAHEAD) && defined(FIONREAD)
+    int val;
+
+    ioctl(SHTTY, FIONREAD, (char *)&val);
+    if (val)
+	return;
+#endif
+
+/* sanitize the tty */
+#ifdef HAS_TIO
+    shttyinfo.tio.c_lflag |= ICANON | ECHO;
+# ifdef FLUSHO
+    shttyinfo.tio.c_lflag &= ~FLUSHO;
+# endif
+#else				/* not HAS_TIO */
+    shttyinfo.sgttyb.sg_flags = (shttyinfo.sgttyb.sg_flags & ~CBREAK) | ECHO;
+    shttyinfo.lmodes &= ~LFLUSHO;
+#endif
+
+    attachtty(mypgrp);
+    ti = shttyinfo;
+#ifdef HAS_TIO
+    if (unset(FLOWCONTROL))
+	ti.tio.c_iflag &= ~IXON;
+    ti.tio.c_lflag &= ~(ICANON | ECHO
+# ifdef FLUSHO
+			| FLUSHO
+# endif
+	);
+# ifdef TAB3
+    ti.tio.c_oflag &= ~TAB3;
+# else
+#  ifdef OXTABS
+    ti.tio.c_oflag &= ~OXTABS;
+#  else
+    ti.tio.c_oflag &= ~XTABS;
+#  endif
+# endif
+    ti.tio.c_oflag |= ONLCR;
+    ti.tio.c_cc[VQUIT] =
+# ifdef VDISCARD
+	ti.tio.c_cc[VDISCARD] =
+# endif
+# ifdef VSUSP
+	ti.tio.c_cc[VSUSP] =
+# endif
+# ifdef VDSUSP
+	ti.tio.c_cc[VDSUSP] =
+# endif
+# ifdef VSWTCH
+	ti.tio.c_cc[VSWTCH] =
+# endif
+# ifdef VLNEXT
+	ti.tio.c_cc[VLNEXT] =
+# endif
+	VDISABLEVAL;
+# if defined(VSTART) && defined(VSTOP)
+    if (unset(FLOWCONTROL))
+	ti.tio.c_cc[VSTART] = ti.tio.c_cc[VSTOP] = VDISABLEVAL;
+# endif
+    eofchar = ti.tio.c_cc[VEOF];
+    ti.tio.c_cc[VMIN] = 1;
+    ti.tio.c_cc[VTIME] = 0;
+    ti.tio.c_iflag |= (INLCR | ICRNL);
+ /* this line exchanges \n and \r; it's changed back in getkey
+	so that the net effect is no change at all inside the shell.
+	This double swap is to allow typeahead in common cases, eg.
+
+	% bindkey -s '^J' 'echo foo^M'
+	% sleep 10
+	echo foo<return>  <--- typed before sleep returns
+
+	The shell sees \n instead of \r, since it was changed by the kernel
+	while zsh wasn't looking. Then in getkey() \n is changed back to \r,
+	and it sees "echo foo<accept line>", as expected. Without the double
+	swap the shell would see "echo foo\n", which is translated to
+	"echo fooecho foo<accept line>" because of the binding.
+	Note that if you type <line-feed> during the sleep the shell just sees
+	\n, which is translated to \r in getkey(), and you just get another
+	prompt. For type-ahead to work in ALL cases you have to use
+	stty inlcr.
+
+	Unfortunately it's IMPOSSIBLE to have a general solution if both
+	<return> and <line-feed> are mapped to the same character. The shell
+	could check if there is input and read it before setting it's own
+	terminal modes but if we get a \n we don't know whether to keep it or
+	change to \r :-(
+	*/
+
+#else				/* not HAS_TIO */
+    ti.sgttyb.sg_flags = (ti.sgttyb.sg_flags | CBREAK) & ~ECHO & ~XTABS;
+    ti.lmodes &= ~LFLUSHO;
+    eofchar = ti.tchars.t_eofc;
+    ti.tchars.t_quitc =
+	ti.ltchars.t_suspc =
+	ti.ltchars.t_flushc =
+	ti.ltchars.t_dsuspc = ti.ltchars.t_lnextc = -1;
+#endif
+
+#if defined(TTY_NEEDS_DRAINING) && defined(TIOCOUTQ) && defined(HAVE_SELECT)
+    if (baud) {			/**/
+	int n = 0;
+
+	while ((ioctl(SHTTY, TIOCOUTQ, (char *)&n) >= 0) && n) {
+	    struct timeval tv;
+
+	    tv.tv_sec = n / baud;
+	    tv.tv_usec = ((n % baud) * 1000000) / baud;
+	    select(0, NULL, NULL, NULL, &tv);
+	}
+    }
+#endif
+
+    settyinfo(&ti);
+}
+
+static char *kungetbuf;
+static int kungetct, kungetsz;
+
+/**/
+void
+ungetkey(int ch)
+{
+    if (kungetct == kungetsz)
+	kungetbuf = realloc(kungetbuf, kungetsz *= 2);
+    kungetbuf[kungetct++] = ch;
+}
+
+/**/
+void
+ungetkeys(char *s, int len)
+{
+    s += len;
+    while (len--)
+	ungetkey(*--s);
+}
+
+#if defined(pyr) && defined(HAVE_SELECT)
+static int
+breakread(int fd, char *buf, int n)
+{
+    fd_set f;
+
+    FD_ZERO(&f);
+    FD_SET(fd, &f);
+    return (select(fd + 1, (SELECT_ARG_2_T) & f, NULL, NULL, NULL) == -1 ?
+	    EOF : read(fd, buf, n));
+}
+
+# define read    breakread
+#endif
+
+/**/
+int
+getkey(int keytmout)
+{
+    char cc;
+    unsigned int ret;
+    long exp100ths;
+    int die = 0, r, icnt = 0;
+    int old_errno = errno;
+
+#ifdef HAVE_SELECT
+    fd_set foofd;
+
+#else
+# ifdef HAS_TIO
+    struct ttyinfo ti;
+
+# endif
+#endif
+
+    if (kungetct)
+	ret = STOUC(kungetbuf[--kungetct]);
+    else {
+	if (keytmout) {
+	    if (keytimeout > 500)
+		exp100ths = 500;
+	    else if (keytimeout > 0)
+		exp100ths = keytimeout;
+	    else
+		exp100ths = 0;
+#ifdef HAVE_SELECT
+	    if (exp100ths) {
+		struct timeval expire_tv;
+
+		expire_tv.tv_sec = exp100ths / 100;
+		expire_tv.tv_usec = (exp100ths % 100) * 10000L;
+		FD_ZERO(&foofd);
+		FD_SET(SHTTY, &foofd);
+		if (select(SHTTY+1, (SELECT_ARG_2_T) & foofd,
+			   NULL, NULL, &expire_tv) <= 0)
+		    return EOF;
+	    }
+#else
+# ifdef HAS_TIO
+	    ti = shttyinfo;
+	    ti.tio.c_lflag &= ~ICANON;
+	    ti.tio.c_cc[VMIN] = 0;
+	    ti.tio.c_cc[VTIME] = exp100ths / 10;
+#  ifdef HAVE_TERMIOS_H
+	    tcsetattr(SHTTY, TCSANOW, &ti.tio);
+#  else
+	    ioctl(SHTTY, TCSETA, &ti.tio);
+#  endif
+	    r = read(SHTTY, &cc, 1);
+#  ifdef HAVE_TERMIOS_H
+	    tcsetattr(SHTTY, TCSANOW, &shttyinfo.tio);
+#  else
+	    ioctl(SHTTY, TCSETA, &shttyinfo.tio);
+#  endif
+	    return (r <= 0) ? EOF : cc;
+# endif
+#endif
+	}
+	while ((r = read(SHTTY, &cc, 1)) != 1) {
+	    if (r == 0) {
+		/* The test for IGNOREEOF was added to make zsh ignore ^Ds
+		   that were typed while commands are running.  Unfortuantely
+		   this caused trouble under at least one system (SunOS 4.1).
+		   Here shells that lost their xterm (e.g. if it was killed
+		   with -9) didn't fail to read from the terminal but instead
+		   happily continued to read EOFs, so that the above read
+		   returned with 0, and, with IGNOREEOF set, this caused
+		   an infinite loop.  The simple way around this was to add
+		   the counter (icnt) so that this happens 20 times and than
+		   the shell gives up (yes, this is a bit dirty...). */
+		if (isset(IGNOREEOF) && icnt++ < 20)
+		    continue;
+		stopmsg = 1;
+		zexit(1, 0);
+	    }
+	    icnt = 0;
+	    if (errno == EINTR) {
+		die = 0;
+		if (!errflag && !retflag && !breaks)
+		    continue;
+		errflag = 0;
+		errno = old_errno;
+		return EOF;
+	    } else if (errno == EWOULDBLOCK) {
+		fcntl(0, F_SETFL, 0);
+	    } else if (errno == EIO && !die) {
+		ret = opts[MONITOR];
+		opts[MONITOR] = 1;
+		attachtty(mypgrp);
+		refresh();	/* kludge! */
+		opts[MONITOR] = ret;
+		die = 1;
+	    } else if (errno != 0) {
+		zerr("error on TTY read: %e", NULL, errno);
+		stopmsg = 1;
+		zexit(1, 0);
+	    }
+	}
+	if (cc == '\r')		/* undo the exchange of \n and \r determined by */
+	    cc = '\n';		/* setterm() */
+	else if (cc == '\n')
+	    cc = '\r';
+
+	ret = STOUC(cc);
+    }
+    if (vichgflag) {
+	if (vichgbufptr == vichgbufsz)
+	    vichgbuf = realloc(vichgbuf, vichgbufsz *= 2);
+	vichgbuf[vichgbufptr++] = ret;
+    }
+    errno = old_errno;
+    return ret;
+}
+
+/* Read a line.  It is returned metafied. */
+
+/**/
+unsigned char *
+zleread(char *lp, char *rp, int ha)
+{
+    unsigned char *s;
+    int old_errno = errno;
+    int tmout = getiparam("TMOUT");
+
+#ifdef HAVE_SELECT
+    long costmult;
+    struct timeval tv;
+    fd_set foofd;
+
+    baud = getiparam("BAUD");
+    costmult = (baud) ? 3840000L / baud : 0;
+    tv.tv_sec = 0;
+#endif
+
+    /* ZLE doesn't currently work recursively.  This is needed in case a *
+     * select loop is used in a function called from ZLE.  vared handles *
+     * this differently itself.                                          */
+    if(zleactive) {
+	char *pptbuf;
+	int pptlen;
+
+	pptbuf = unmetafy(promptexpand(lp, 0, NULL, NULL), &pptlen);
+	write(2, (WRITE_ARG_2_T)pptbuf, pptlen);
+	free(pptbuf);
+	return (unsigned char *)shingetline();
+    }
+
+    keytimeout = getiparam("KEYTIMEOUT");
+    if (!shout) {
+	if (SHTTY != -1)
+	    init_shout();
+
+	if (!shout)
+	    return NULL;
+	/* We could be smarter and default to a system read. */
+
+	/* If we just got a new shout, make sure the terminal is set up. */
+	if (termflags & TERM_UNKNOWN)
+	    init_term();
+    }
+
+    fflush(shout);
+    fflush(stderr);
+    intr();
+    insmode = unset(OVERSTRIKE);
+    eofsent = 0;
+    resetneeded = 0;
+    lpptbuf = promptexpand(lp, 1, NULL, NULL);
+    pmpt_attr = txtchange;
+    rpptbuf = promptexpand(rp, 1, NULL, NULL);
+    rpmpt_attr = txtchange;
+    histallowed = ha;
+    PERMALLOC {
+	histline = curhist;
+#ifdef HAVE_SELECT
+	FD_ZERO(&foofd);
+#endif
+	undoing = 1;
+	line = (unsigned char *)zalloc((linesz = 256) + 2);
+	virangeflag = lastcmd = done = cs = ll = mark = 0;
+	curhistline = NULL;
+	vichgflag = 0;
+	viinsbegin = 0;
+	statusline = NULL;
+	selectkeymap("main", 1);
+	fixsuffix();
+	if ((s = (unsigned char *)getlinknode(bufstack))) {
+	    setline((char *)s);
+	    zsfree((char *)s);
+	    if (stackcs != -1) {
+		cs = stackcs;
+		stackcs = -1;
+		if (cs > ll)
+		    cs = ll;
+	    }
+	    if (stackhist != -1) {
+		histline = stackhist;
+		stackhist = -1;
+	    }
+	}
+	initundo();
+	if (isset(PROMPTCR))
+	    putc('\r', shout);
+	if (tmout)
+	    alarm(tmout);
+	zleactive = 1;
+	resetneeded = 1;
+	errflag = retflag = 0;
+	lastcol = -1;
+	initmodifier(&zmod);
+	prefixflag = 0;
+	feepflag = 0;
+	refresh();
+	while (!done && !errflag) {
+
+	    statusline = NULL;
+	    vilinerange = 0;
+	    reselectkeymap();
+	    bindk = getkeycmd();
+	    if (!ll && isfirstln && c == eofchar) {
+		eofsent = 1;
+		break;
+	    }
+	    if (bindk) {
+		execzlefunc(bindk);
+		handleprefixes();
+		/* for vi mode, make sure the cursor isn't somewhere illegal */
+		if (invicmdmode() && cs > findbol() &&
+		    (cs == ll || line[cs] == '\n'))
+		    cs--;
+		if (undoing)
+		    handleundo();
+	    } else {
+		errflag = 1;
+		break;
+	    }
+#ifdef HAVE_SELECT
+	    if (baud && !(lastcmd & ZLE_MENUCMP)) {
+		FD_SET(SHTTY, &foofd);
+		if ((tv.tv_usec = cost * costmult) > 500000)
+		    tv.tv_usec = 500000;
+		if (!kungetct && select(SHTTY+1, (SELECT_ARG_2_T) & foofd,
+					NULL, NULL, &tv) <= 0)
+		    refresh();
+	    } else
+#endif
+		if (!kungetct)
+		    refresh();
+	    handlefeep();
+	}
+	statusline = NULL;
+	invalidatelist();
+	trashzle();
+	free(lpptbuf);
+	free(rpptbuf);
+	zleactive = 0;
+	alarm(0);
+    } LASTALLOC;
+    zsfree(curhistline);
+    freeundo();
+    if (eofsent) {
+	free(line);
+	line = NULL;
+    } else {
+	line[ll++] = '\n';
+	line = (unsigned char *) metafy((char *) line, ll, META_REALLOC);
+    }
+    forget_edits();
+    errno = old_errno;
+    return line;
+}
+
+/* execute a widget */
+
+/**/
+void
+execzlefunc(Thingy func)
+{
+    Widget w;
+
+    if(func->flags & DISABLED) {
+	/* this thingy is not the name of a widget */
+	char *nm = niceztrdup(func->nam);
+	char *msg = tricat("No such widget `", nm, "'");
+
+	zsfree(nm);
+	showmsg(msg);
+	zsfree(msg);
+	feep();
+    } else if((w = func->widget)->flags & WIDGET_INT) {
+	int wflags = w->flags;
+
+	if(!(wflags & ZLE_KEEPSUFFIX))
+	    removesuffix();
+	if(!(wflags & ZLE_MENUCMP)) {
+	    fixsuffix();
+	    invalidatelist();
+	}
+	if (wflags & ZLE_LINEMOVE)
+	    vilinerange = 1;
+	if(!(wflags & ZLE_LASTCOL))
+	    lastcol = -1;
+	w->u.fn();
+	lastcmd = wflags;
+    } else {
+	List l = getshfunc(w->u.fnnam);
+
+	if(l == &dummy_list) {
+	    /* the shell function doesn't exist */
+	    char *nm = niceztrdup(w->u.fnnam);
+	    char *msg = tricat("No such shell function `", nm, "'");
+
+	    zsfree(nm);
+	    showmsg(msg);
+	    zsfree(msg);
+	    feep();
+	} else {
+	  startparamscope();
+	  makezleparams();
+	  doshfunc(l, NULL, 0, 1);
+	  endparamscope();
+	  lastcmd = 0;
+	}
+    }
+}
+
+/* initialise command modifiers */
+
+/**/
+static void
+initmodifier(struct modifier *mp)
+{
+    mp->flags = 0;
+    mp->mult = 1;
+    mp->tmult = 1;
+    mp->vibuf = 0;
+}
+
+/* Reset command modifiers, unless the command just executed was a prefix. *
+ * Also set zmult, if the multiplier has been amended.                     */
+
+/**/
+static void
+handleprefixes(void)
+{
+    if (prefixflag) {
+	prefixflag = 0;
+	if(zmod.flags & MOD_TMULT) {
+	    zmod.flags |= MOD_MULT;
+	    zmod.mult = zmod.tmult;
+	}
+    } else
+	initmodifier(&zmod);
+}
+
+/* vared: edit (literally) a parameter value */
+
+/**/
+static int
+bin_vared(char *name, char **args, char *ops, int func)
+{
+    char *s;
+    char *t;
+    Param pm;
+    int create = 0;
+    char *p1 = NULL, *p2 = NULL;
+
+    /* all options are handled as arguments */
+    while (*args && **args == '-') {
+	while (*++(*args))
+	    switch (**args) {
+	    case 'c':
+		/* -c option -- allow creation of the parameter if it doesn't
+		yet exist */
+		create = 1;
+		break;
+	    case 'p':
+		/* -p option -- set main prompt string */
+		if ((*args)[1])
+		    p1 = *args + 1, *args = "" - 1;
+		else if (args[1])
+		    p1 = *(++args), *args = "" - 1;
+		else {
+		    zwarnnam(name, "prompt string expected after -%c", NULL,
+			     **args);
+		    return 1;
+		}
+		break;
+	    case 'r':
+		/* -r option -- set right prompt string */
+		if ((*args)[1])
+		    p2 = *args + 1, *args = "" - 1;
+		else if (args[1])
+		    p2 = *(++args), *args = "" - 1;
+		else {
+		    zwarnnam(name, "prompt string expected after -%c", NULL,
+			     **args);
+		    return 1;
+		}
+		break;
+	    case 'h':
+		/* -h option -- enable history */
+		ops['h'] = 1;
+		break;
+	    default:
+		/* unrecognised option character */
+		zwarnnam(name, "unknown option: %s", *args, 0);
+		return 1;
+	    }
+	args++;
+    }
+
+    /* check we have a parameter name */
+    if (!*args) {
+	zwarnnam(name, "missing variable", NULL, 0);
+	return 1;
+    }
+    /* handle non-existent parameter */
+    if (!(s = getsparam(args[0]))) {
+	if (create)
+	    createparam(args[0], PM_SCALAR);
+	else {
+	    zwarnnam(name, "no such variable: %s", args[0], 0);
+	    return 1;
+	}
+    }
+
+    if(zleactive) {
+	zwarnnam(name, "ZLE cannot be used recursively (yet)", NULL, 0);
+	return 1;
+    }
+
+    /* edit the parameter value */
+    PERMALLOC {
+	pushnode(bufstack, ztrdup(s));
+    } LASTALLOC;
+    t = (char *) zleread(p1, p2, ops['h']);
+    if (!t || errflag) {
+	/* error in editing */
+	errflag = 0;
+	return 1;
+    }
+    /* strip off trailing newline, if any */
+    if (t[strlen(t) - 1] == '\n')
+	t[strlen(t) - 1] = '\0';
+    /* final assignment of parameter value */
+    pm = (Param) paramtab->getnode(paramtab, args[0]);
+    if (pm && PM_TYPE(pm->flags) == PM_ARRAY) {
+	char **a;
+
+	PERMALLOC {
+	    a = spacesplit(t, 1);
+	} LASTALLOC;
+	setaparam(args[0], a);
+    } else
+	setsparam(args[0], t);
+    return 0;
+}
+
+/**/
+void
+describekeybriefly(void)
+{
+    char *seq, *str, *msg, *is;
+    Thingy func;
+
+    if (statusline)
+	return;
+    statusline = "Describe key briefly: _";
+    statusll = strlen(statusline);
+    refresh();
+    seq = getkeymapcmd(curkeymap, &func, &str);
+    statusline = NULL;
+    if(!*seq)
+	return;
+    msg = bindztrdup(seq);
+    msg = appstr(msg, " is ");
+    if (!func)
+	is = bindztrdup(str);
+    else
+	is = niceztrdup(func->nam);
+    msg = appstr(msg, is);
+    zsfree(is);
+    showmsg(msg);
+    zsfree(msg);
+}
+
+#define MAXFOUND 4
+
+struct findfunc {
+    Thingy func;
+    int found;
+    char *msg;
+};
+
+/**/
+static void
+scanfindfunc(char *seq, Thingy func, char *str, void *magic)
+{
+    struct findfunc *ff = magic;
+
+    if(func != ff->func)
+	return;
+    if (!ff->found++)
+	ff->msg = appstr(ff->msg, " is on");
+    if(ff->found <= MAXFOUND) {
+	char *b = bindztrdup(seq);
+
+	ff->msg = appstr(ff->msg, " ");
+	ff->msg = appstr(ff->msg, b);
+	zsfree(b);
+    }
+}
+
+/**/
+void
+whereis(void)
+{
+    struct findfunc ff;
+
+    if (!(ff.func = executenamedcommand("Where is: ")))
+	return;
+    ff.found = 0;
+    ff.msg = niceztrdup(ff.func->nam);
+    scankeymap(curkeymap, 1, scanfindfunc, &ff);
+    if (!ff.found)
+	ff.msg = appstr(ff.msg, " is not bound to any key");
+    else if(ff.found > MAXFOUND)
+	ff.msg = appstr(ff.msg, " et al");
+    showmsg(ff.msg);
+    zsfree(ff.msg);
+}
+
+/**/
+void
+trashzle(void)
+{
+    if (zleactive) {
+	/* This refresh() is just to get the main editor display right and *
+	 * get the cursor in the right place.  For that reason, we disable *
+	 * list display (which would otherwise result in infinite          *
+	 * recursion [at least, it would if refresh() didn't have its      *
+	 * extra `inlist' check]).                                         */
+	int sl = showinglist;
+	showinglist = 0;
+	refresh();
+	showinglist = sl;
+	moveto(nlnct, 0);
+	if (clearflag && tccan(TCCLEAREOD)) {
+	    tcout(TCCLEAREOD);
+	    clearflag = 0;
+	}
+	if (postedit)
+	    fprintf(shout, "%s", postedit);
+	fflush(shout);
+	resetneeded = 1;
+	settyinfo(&shttyinfo);
+    }
+    if (errflag)
+	kungetct = 0;
+}
+
+static struct builtin bintab[] = {
+    BUILTIN("bindkey", 0, bin_bindkey, 0, -1, 0, "evaMldDANmrsLR", NULL),
+    BUILTIN("vared",   0, bin_vared,   1,  7, 0, NULL,             NULL),
+    BUILTIN("zle",     0, bin_zle,     0, -1, 0, "lDANL",          NULL),
+};
+
+/**/
+int
+boot_zle(Module m)
+{
+    /* Set up editor entry points */
+    trashzleptr = trashzle;
+    gotwordptr = gotword;
+    refreshptr = refresh;
+    spaceinlineptr = spaceinline;
+    zlereadptr = zleread;
+
+    /* initialise the thingies */
+    init_thingies();
+
+    /* miscellaneous initialisations */
+    stackhist = stackcs = -1;
+    kungetbuf = (char *) zalloc(kungetsz = 32);
+
+    /* initialise the keymap system */
+    init_keymaps();
+
+    addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return 0;
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_zle(Module m)
+{
+    int i;
+
+    if(zleactive) {
+	zerrnam(m->nam, "can't unload the zle module while zle is active",
+	    NULL, 0);
+	return 1;
+    }
+
+    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    cleanup_keymaps();
+    deletehashtable(thingytab);
+
+    zfree(vichgbuf, vichgbufsz);
+    zfree(kungetbuf, kungetsz);
+    free_isrch_spots();
+
+    zfree(cutbuf.buf, cutbuf.len);
+    for(i = KRINGCT; i--; )
+	zfree(kring[i].buf, kring[i].len);
+    for(i = 35; i--; )
+	zfree(vibuf[i].buf, vibuf[i].len);
+
+    /* editor entry points */
+    trashzleptr = noop_function;
+    gotwordptr = noop_function;
+    refreshptr = noop_function;
+    spaceinlineptr = noop_function_int;
+    zlereadptr = fallback_zleread;
+
+    return 0;
+}
+
+#endif /* MODULE */
diff --git a/Src/Zle/zle_misc.c b/Src/Zle/zle_misc.c
new file mode 100644
index 000000000..42953852f
--- /dev/null
+++ b/Src/Zle/zle_misc.c
@@ -0,0 +1,816 @@
+/*
+ * zle_misc.c - miscellaneous editor routines
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_misc.pro"
+
+/* insert a metafied string, with repetition and suffix removal */
+
+/**/
+void
+doinsert(char *str)
+{
+    char *s;
+    int len = ztrlen(str);
+    int c1 = *str == Meta ? STOUC(str[1])^32 : STOUC(*str);/* first character */
+    int neg = zmult < 0;             /* insert *after* the cursor? */
+    int m = neg ? -zmult : zmult;    /* number of copies to insert */
+
+    iremovesuffix(c1);
+    invalidatelist();
+
+    if(insmode)
+	spaceinline(m * len);
+    else if(cs + m * len > ll)
+	spaceinline(cs + m * len - ll);
+    while(m--)
+	for(s = str; *s; s++)
+	    line[cs++] = *s == Meta ? *++s ^ 32 : *s;
+    if(neg)
+	cs += zmult * len;
+}
+
+/**/
+void
+selfinsert(void)
+{
+    char s[3], *p = s;
+
+    if(imeta(c)) {
+	*p++ = Meta;
+	c ^= 32;
+    }
+    *p++ = c;
+    *p = 0;
+    doinsert(s);
+}
+
+/**/
+void
+selfinsertunmeta(void)
+{
+    c &= 0x7f;
+    if (c == '\r')
+	c = '\n';
+    selfinsert();
+}
+
+/**/
+void
+deletechar(void)
+{
+    if (zmult < 0) {
+	zmult = -zmult;
+	backwarddeletechar();
+	zmult = -zmult;
+	return;
+    }
+    if (cs + zmult <= ll) {
+	cs += zmult;
+	backdel(zmult);
+    } else
+	feep();
+}
+
+/**/
+void
+backwarddeletechar(void)
+{
+    if (zmult < 0) {
+	zmult = -zmult;
+	deletechar();
+	zmult = -zmult;
+	return;
+    }
+    backdel(zmult > cs ? cs : zmult);
+}
+
+/**/
+void
+killwholeline(void)
+{
+    int i, fg, n = zmult;
+
+    if (n < 0)
+	return;
+    while (n--) {
+	if ((fg = (cs && cs == ll)))
+	    cs--;
+	while (cs && line[cs - 1] != '\n')
+	    cs--;
+	for (i = cs; i != ll && line[i] != '\n'; i++);
+	forekill(i - cs + (i != ll), fg);
+    }
+}
+
+/**/
+void
+killbuffer(void)
+{
+    cs = 0;
+    forekill(ll, 0);
+}
+
+/**/
+void
+backwardkillline(void)
+{
+    int i = 0, n = zmult;
+
+    if (n < 0) {
+	zmult = -n;
+	killline();
+	zmult = n;
+	return;
+    }
+    while (n--) {
+	if (cs && line[cs - 1] == '\n')
+	    cs--, i++;
+	else
+	    while (cs && line[cs - 1] != '\n')
+		cs--, i++;
+    }
+    forekill(i, 1);
+}
+
+/**/
+void
+gosmacstransposechars(void)
+{
+    int cc;
+
+    if (cs < 2 || line[cs - 1] == '\n' || line[cs - 2] == '\n') {
+	if (cs == ll || line[cs] == '\n' ||
+	    ((cs + 1 == ll || line[cs + 1] == '\n') &&
+	     (!cs || line[cs - 1] == '\n'))) {
+	    feep();
+	    return;
+	}
+	cs += (cs == 0 || line[cs - 1] == '\n') ? 2 : 1;
+    }
+    cc = line[cs - 2];
+    line[cs - 2] = line[cs - 1];
+    line[cs - 1] = cc;
+}
+
+/**/
+void
+transposechars(void)
+{
+    int cc, ct;
+    int n = zmult;
+    int neg = n < 0;
+
+    if (neg)
+	n = -n;
+    while (n--) {
+	if (!(ct = cs) || line[cs - 1] == '\n') {
+	    if (ll == cs || line[cs] == '\n') {
+		feep();
+		return;
+	    }
+	    if (!neg)
+		cs++;
+	    ct++;
+	}
+	if (neg) {
+	    if (cs && line[cs - 1] != '\n') {
+		cs--;
+		if (ct > 1 && line[ct - 2] != '\n')
+		    ct--;
+	    }
+	} else {
+	    if (cs != ll && line[cs] != '\n')
+		cs++;
+	}
+	if (ct == ll || line[ct] == '\n')
+	    ct--;
+	if (ct < 1 || line[ct - 1] == '\n') {
+	    feep();
+	    return;
+	}
+	cc = line[ct - 1];
+	line[ct - 1] = line[ct];
+	line[ct] = cc;
+    }
+}
+
+/**/
+void
+poundinsert(void)
+{
+    cs = 0;
+    vifirstnonblank();
+    if (line[cs] != '#') {
+	spaceinline(1);
+	line[cs] = '#';
+	cs = findeol();
+	while(cs != ll) {
+	    cs++;
+	    vifirstnonblank();
+	    spaceinline(1);
+	    line[cs] = '#';
+	    cs = findeol();
+	}
+    } else {
+	foredel(1);
+	cs = findeol();
+	while(cs != ll) {
+	    cs++;
+	    vifirstnonblank();
+	    if(line[cs] == '#')
+		foredel(1);
+	    cs = findeol();
+	}
+    }
+    done = 1;
+}
+
+/**/
+void
+acceptline(void)
+{
+    done = 1;
+}
+
+/**/
+void
+acceptandhold(void)
+{
+    pushnode(bufstack, metafy((char *)line, ll, META_DUP));
+    stackcs = cs;
+    done = 1;
+}
+
+/**/
+void
+killline(void)
+{
+    int i = 0, n = zmult;
+
+    if (n < 0) {
+	zmult = -n;
+	backwardkillline();
+	zmult = n;
+	return;
+    }
+    while (n--) {
+	if (line[cs] == '\n')
+	    cs++, i++;
+	else
+	    while (cs != ll && line[cs] != '\n')
+		cs++, i++;
+    }
+    backkill(i, 0);
+}
+
+/**/
+void
+killregion(void)
+{
+    if (mark > ll)
+	mark = ll;
+    if (mark > cs)
+	forekill(mark - cs, 0);
+    else
+	backkill(cs - mark, 1);
+}
+
+/**/
+void
+copyregionaskill(void)
+{
+    if (mark > ll)
+	mark = ll;
+    if (mark > cs)
+	cut(cs, mark - cs, 0);
+    else
+	cut(mark, cs - mark, 1);
+}
+
+static int kct, yankb, yanke;
+
+/**/
+void
+yank(void)
+{
+    Cutbuffer buf = &cutbuf;
+    int n = zmult;
+
+    if (n < 0)
+	return;
+    if (zmod.flags & MOD_VIBUF)
+	buf = &vibuf[zmod.vibuf];
+    if (!buf->buf) {
+	feep();
+	return;
+    }
+    mark = cs;
+    yankb = cs;
+    while (n--) {
+	kct = kringnum;
+	spaceinline(buf->len);
+	memcpy((char *)line + cs, buf->buf, buf->len);
+	cs += buf->len;
+	yanke = cs;
+    }
+}
+
+/**/
+void
+yankpop(void)
+{
+    int cc;
+
+    if (!(lastcmd & ZLE_YANK) || !kring[kct].buf) {
+	feep();
+	return;
+    }
+    cs = yankb;
+    foredel(yanke - yankb);
+    cc = kring[kct].len;
+    spaceinline(cc);
+    memcpy((char *)line + cs, kring[kct].buf, cc);
+    cs += cc;
+    yanke = cs;
+    kct = (kct + KRINGCT - 1) % KRINGCT;
+}
+
+/**/
+void
+overwritemode(void)
+{
+    insmode ^= 1;
+}
+/**/
+void
+whatcursorposition(void)
+{
+    char msg[100];
+    char *s = msg;
+    int bol = findbol();
+    int c = STOUC(line[cs]);
+
+    if (cs == ll)
+	strucpy(&s, "EOF");
+    else {
+	strucpy(&s, "Char: ");
+	switch (c) {
+	case ' ':
+	    strucpy(&s, "SPC");
+	    break;
+	case '\t':
+	    strucpy(&s, "TAB");
+	    break;
+	case '\n':
+	    strucpy(&s, "LFD");
+	    break;
+	default:
+	    if (imeta(c)) {
+		*s++ = Meta;
+		*s++ = c ^ 32;
+	    } else
+		*s++ = c;
+	}
+	sprintf(s, " (0%o, %d, 0x%x)", c, c, c);
+	s += strlen(s);
+    }
+    sprintf(s, "  point %d of %d(%d%%)  column %d", cs+1, ll+1,
+	    ll ? 100 * cs / ll : 0, cs - bol);
+    showmsg(msg);
+}
+
+/**/
+void
+undefinedkey(void)
+{
+    feep();
+}
+
+/**/
+void
+quotedinsert(void)
+{
+#ifndef HAS_TIO
+    struct sgttyb sob;
+
+    sob = shttyinfo.sgttyb;
+    sob.sg_flags = (sob.sg_flags | RAW) & ~ECHO;
+    ioctl(SHTTY, TIOCSETN, &sob);
+#endif
+    c = getkey(0);
+#ifndef HAS_TIO
+    setterm();
+#endif
+    if (c < 0)
+	feep();
+    else
+	selfinsert();
+}
+
+/**/
+void
+digitargument(void)
+{
+    int sign = (zmult < 0) ? -1 : 1;
+
+    if (!(zmod.flags & MOD_TMULT))
+	zmod.tmult = 0;
+    if (zmod.flags & MOD_NEG) {
+	/* If we just had a negative argument, this is the digit, *
+	 * rather than the -1 assumed by negargument()            */
+	zmod.tmult = sign * (c & 0xf);
+	zmod.flags &= ~MOD_NEG;
+    } else
+	zmod.tmult = zmod.tmult * 10 + sign * (c & 0xf);
+    zmod.flags |= MOD_TMULT;
+    prefixflag = 1;
+}
+
+/**/
+void
+negargument(void)
+{
+    if(zmod.flags & MOD_TMULT) {
+	feep();
+	return;
+    }
+    zmod.tmult = -1;
+    zmod.flags |= MOD_TMULT|MOD_NEG;
+    prefixflag = 1;
+}
+
+/**/
+void
+universalargument(void)
+{
+    int digcnt = 0, pref = 0, minus = 1, gotk;
+    while ((gotk = getkey(0)) != EOF) {
+	if (gotk == '-' && !digcnt) {
+	    minus = -1;
+	    digcnt++;
+	} else if (gotk >= '0' && gotk <= '9') {
+	    pref = pref * 10 + (gotk & 0xf);
+	    digcnt++;
+	} else {
+	    ungetkey(gotk);
+	    break;
+	}
+    }
+    if (digcnt)
+	zmod.tmult = minus * (pref ? pref : 1);
+    else
+	zmod.tmult *= 4;
+    zmod.flags |= MOD_TMULT;
+    prefixflag = 1;
+}
+
+/**/
+void
+copyprevword(void)
+{
+    int len, t0;
+
+    for (t0 = cs - 1; t0 >= 0; t0--)
+	if (iword(line[t0]))
+	    break;
+    for (; t0 >= 0; t0--)
+	if (!iword(line[t0]))
+	    break;
+    if (t0)
+	t0++;
+    len = cs - t0;
+    spaceinline(len);
+    memcpy((char *)&line[cs], (char *)&line[t0], len);
+    cs += len;
+}
+
+/**/
+void
+sendbreak(void)
+{
+    errflag = 1;
+}
+
+/**/
+void
+quoteregion(void)
+{
+    char *str;
+    size_t len;
+
+    if (mark > ll)
+	mark = ll;
+    if (mark < cs) {
+	int tmp = mark;
+	mark = cs;
+	cs = tmp;
+    }
+    str = (char *)hcalloc(len = mark - cs);
+    memcpy(str, (char *)&line[cs], len);
+    foredel(len);
+    str = makequote(str, &len);
+    spaceinline(len);
+    memcpy((char *)&line[cs], str, len);
+    mark = cs;
+    cs += len;
+}
+
+/**/
+void
+quoteline(void)
+{
+    char *str;
+    size_t len = ll;
+
+    str = makequote((char *)line, &len);
+    sizeline(len);
+    memcpy(line, str, len);
+    cs = ll = len;
+}
+
+/**/
+static char *
+makequote(char *str, size_t *len)
+{
+    int qtct = 0;
+    char *l, *ol;
+    char *end = str + *len;
+
+    for (l = str; l < end; l++)
+	if (*l == '\'')
+	    qtct++;
+    *len += 2 + qtct*3;
+    l = ol = (char *)halloc(*len);
+    *l++ = '\'';
+    for (; str < end; str++)
+	if (*str == '\'') {
+	    *l++ = '\'';
+	    *l++ = '\\';
+	    *l++ = '\'';
+	    *l++ = '\'';
+	} else
+	    *l++ = *str;
+    *l++ = '\'';
+    return ol;
+}
+
+static char *cmdbuf;
+static LinkList cmdll;
+static int cmdambig;
+
+/**/
+static void
+scancompcmd(HashNode hn, int flags)
+{
+    int l;
+    Thingy t = (Thingy) hn;
+
+    if(strpfx(cmdbuf, t->nam)) {
+	addlinknode(cmdll, t->nam);
+	l = pfxlen(peekfirst(cmdll), t->nam);
+	if (l < cmdambig)
+	    cmdambig = l;
+    }
+
+}
+
+#define NAMLEN 60
+
+/**/
+Thingy
+executenamedcommand(char *prmt)
+{
+    Thingy cmd;
+    int len, l = strlen(prmt);
+    char *ptr;
+    char *okeymap = curkeymapname;
+
+    cmdbuf = halloc(l + NAMLEN + 2);
+    strcpy(cmdbuf, prmt);
+    statusline = cmdbuf;
+    selectkeymap("main", 1);
+    ptr = cmdbuf += l;
+    len = 0;
+    for (;;) {
+	*ptr = '_';
+	statusll = l + len + 1;
+	refresh();
+	if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) {
+	    statusline = NULL;
+	    selectkeymap(okeymap, 1);
+	    return NULL;
+	}
+	if(cmd == Th(z_clearscreen)) {
+	    clearscreen();
+	} else if(cmd == Th(z_redisplay)) {
+	    redisplay();
+	} else if(cmd == Th(z_viquotedinsert)) {
+	    *ptr = '^';
+	    refresh();
+	    c = getkey(0);
+	    if(c == EOF || !c || len == NAMLEN)
+		feep();
+	    else
+		*ptr++ = c, len++;
+	} else if(cmd == Th(z_quotedinsert)) {
+	    if((c = getkey(0)) == EOF || !c || len == NAMLEN)
+		feep();
+	    else
+		*ptr++ = c, len++;
+	} else if(cmd == Th(z_backwarddeletechar) ||
+	    	cmd == Th(z_vibackwarddeletechar)) {
+	    if (len)
+		len--, ptr--;
+	} else if(cmd == Th(z_killregion) || cmd == Th(z_backwardkillword) ||
+	    	cmd == Th(z_vibackwardkillword)) {
+	    while (len && (len--, *--ptr != '-'));
+	} else if(cmd == Th(z_killwholeline) || cmd == Th(z_vikillline) ||
+	    	cmd == Th(z_backwardkillline)) {
+	    len = 0;
+	    ptr = cmdbuf;
+	} else {
+	    if(cmd == Th(z_acceptline) || cmd == Th(z_vicmdmode)) {
+		Thingy r;
+		unambiguous:
+		*ptr = 0;
+		r = rthingy(cmdbuf);
+		if (!(r->flags & DISABLED)) {
+		    unrefthingy(r);
+		    statusline = NULL;
+		    selectkeymap(okeymap, 1);
+		    return r;
+		}
+		unrefthingy(r);
+	    }
+	    if(cmd == Th(z_selfinsertunmeta)) {
+		c &= 0x7f;
+		if(c == '\r')
+		    c = '\n';
+		cmd = Th(z_selfinsert);
+	    }
+	    if (cmd == Th(z_listchoices) || cmd == Th(z_deletecharorlist) ||
+		cmd == Th(z_expandorcomplete) || cmd == Th(z_completeword) ||
+		cmd == Th(z_expandorcompleteprefix) || cmd == Th(z_vicmdmode) ||
+		cmd == Th(z_acceptline) || c == ' ' || c == '\t') {
+		cmdambig = 100;
+
+		HEAPALLOC {
+		    cmdll = newlinklist();
+		    *ptr = 0;
+
+		    scanhashtable(thingytab, 1, 0, DISABLED, scancompcmd, 0);
+		} LASTALLOC;
+		if (empty(cmdll))
+		    feep();
+		else if (cmd == Th(z_listchoices) ||
+		    cmd == Th(z_deletecharorlist)) {
+		    int zmultsav = zmult;
+		    *ptr = '_';
+		    statusll = l + len + 1;
+		    zmult = 1;
+		    listlist(cmdll);
+		    zmult = zmultsav;
+		} else if (!nextnode(firstnode(cmdll))) {
+		    strcpy(ptr = cmdbuf, peekfirst(cmdll));
+		    ptr += (len = strlen(ptr));
+		    if(cmd == Th(z_acceptline) || cmd == Th(z_vicmdmode))
+			goto unambiguous;
+		} else {
+		    strcpy(cmdbuf, peekfirst(cmdll));
+		    ptr = cmdbuf + cmdambig;
+		    *ptr = '_';
+		    if (isset(AUTOLIST) &&
+			!(isset(LISTAMBIGUOUS) && cmdambig > len)) {
+			int zmultsav = zmult;
+			if (isset(LISTBEEP))
+			    feep();
+			statusll = l + cmdambig + 1;
+			zmult = 1;
+			listlist(cmdll);
+			zmult = zmultsav;
+		    }
+		    len = cmdambig;
+		}
+	    } else {
+		if (len == NAMLEN || icntrl(c) || cmd != Th(z_selfinsert))
+		    feep();
+		else
+		    *ptr++ = c, len++;
+	    }
+	}
+	handlefeep();
+    }
+}
+
+/*****************/
+/* Suffix system */
+/*****************/
+
+/*
+ * The completion system sometimes tentatively adds a suffix to a word,
+ * which can be removed depending on what is inserted next.  These
+ * functions provide the capability to handle a removable suffix.
+ *
+ * Any removable suffix consists of characters immediately before the
+ * cursor.  Whether it is removed depends on the next editing action.
+ * There can be more than one suffix simultaneously present, with
+ * different actions deleting different numbers of characters.
+ *
+ * If the next editing action changes the buffer other than by inserting
+ * characters, normally the suffix should be removed so as to leave a
+ * meaningful complete word.  The behaviour should be the same if the
+ * next character inserted is a word separator.  If the next character
+ * reasonably belongs where it is typed, or if the next editing action
+ * is a deletion, the suffix should not be removed.  Other reasons for
+ * suffix removal may have other behaviour.
+ *
+ * In order to maintain a consistent state, after a suffix has been added
+ * the table *must* be zeroed, one way or another, before the buffer is
+ * changed.  If the suffix is not being removed, call fixsuffix() to
+ * indicate that it is being permanently fixed.
+ */
+
+/* Length of suffix to remove when inserting each possible character value.  *
+ * suffixlen[256] is the length to remove for non-insertion editing actions. */
+
+/**/
+int suffixlen[257];
+
+/* Set up suffix: the last n characters are a suffix that should be *
+ * removed in the usual word end conditions.                        */
+
+/**/
+void
+makesuffix(int n)
+{
+    suffixlen[256] = suffixlen[' '] = suffixlen['\t'] = suffixlen['\n'] = n;
+}
+
+/* Set up suffix for parameter names: the last n characters are a suffix *
+ * that should be removed if the next character is one of the ones that  *
+ * needs to go immediately after the parameter name.  br indicates that  *
+ * the name is in braces (${PATH} instead of $PATH), so the extra        *
+ * characters that can only be used in braces are included.              */
+
+/**/
+void
+makeparamsuffix(int br, int n)
+{
+    if(br || unset(KSHARRAYS))
+	suffixlen[':'] = suffixlen['['] = n;
+    if(br) {
+	suffixlen['#'] = suffixlen['%'] = suffixlen['?'] = n;
+	suffixlen['-'] = suffixlen['+'] = suffixlen['='] = n;
+	/*{*/ suffixlen['}'] = n;
+    }
+}
+
+/* Remove suffix, if there is one, when inserting character c. */
+
+/**/
+void
+iremovesuffix(int c)
+{
+    int sl = suffixlen[c];
+    if(sl) {
+	backdel(sl);
+	invalidatelist();
+    }
+    fixsuffix();
+}
+
+/* Fix the suffix in place, if there is one, making it non-removable. */
+
+/**/
+void
+fixsuffix(void)
+{
+    memset(suffixlen, 0, sizeof(suffixlen));
+}
diff --git a/Src/Zle/zle_move.c b/Src/Zle/zle_move.c
new file mode 100644
index 000000000..8ed4c657a
--- /dev/null
+++ b/Src/Zle/zle_move.c
@@ -0,0 +1,502 @@
+/*
+ * zle_move.c - editor movement
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_move.pro"
+
+static vimarkcs[27], vimarkline[27];
+
+/**/
+void
+beginningofline(void)
+{
+    int n = zmult;
+
+    if (n < 0) {
+	zmult = -n;
+	endofline();
+	zmult = n;
+	return;
+    }
+    while (n--) {
+	if (cs == 0)
+	    return;
+	if (line[cs - 1] == '\n')
+	    if (!--cs)
+		return;
+	while (cs && line[cs - 1] != '\n')
+	    cs--;
+    }
+}
+
+/**/
+void
+endofline(void)
+{
+    int n = zmult;
+
+    if (n < 0) {
+	zmult = -n;
+	beginningofline();
+	zmult = n;
+	return;
+    }
+    while (n--) {
+	if (cs >= ll) {
+	    cs = ll;
+	    return;
+	}
+	if (line[cs] == '\n')
+	    if (++cs == ll)
+		return;
+	while (cs != ll && line[cs] != '\n')
+	    cs++;
+    }
+}
+
+/**/
+void
+beginningoflinehist(void)
+{
+    int n = zmult;
+
+    if (n < 0) {
+	zmult = -n;
+	endoflinehist();
+	zmult = n;
+	return;
+    }
+    while (n) {
+	if (cs == 0)
+	    break;
+	if (line[cs - 1] == '\n')
+	    if (!--cs)
+		break;
+	while (cs && line[cs - 1] != '\n')
+	    cs--;
+	n--;
+    }
+    if (n) {
+	int m = zmult;
+
+	zmult = n;
+	uphistory();
+	zmult = m;
+	cs = 0;
+    }
+}
+
+/**/
+void
+endoflinehist(void)
+{
+    int n = zmult;
+
+    if (n < 0) {
+	zmult = -n;
+	beginningoflinehist();
+	zmult = n;
+	return;
+    }
+    while (n) {
+	if (cs >= ll) {
+	    cs = ll;
+	    break;
+	}
+	if (line[cs] == '\n')
+	    if (++cs == ll)
+		break;
+	while (cs != ll && line[cs] != '\n')
+	    cs++;
+	n--;
+    }
+    if (n) {
+	int m = zmult;
+
+	zmult = n;
+	downhistory();
+	zmult = m;
+    }
+}
+
+/**/
+void
+forwardchar(void)
+{
+    cs += zmult;
+    if (cs > ll)
+	cs = ll;
+    if (cs < 0)
+	cs = 0;
+}
+
+/**/
+void
+backwardchar(void)
+{
+    cs -= zmult;
+    if (cs > ll)
+	cs = ll;
+    if (cs < 0)
+	cs = 0;
+}
+
+/**/
+void
+setmarkcommand(void)
+{
+    mark = cs;
+}
+
+/**/
+void
+exchangepointandmark(void)
+{
+    int x;
+
+    x = mark;
+    mark = cs;
+    cs = x;
+    if (cs > ll)
+	cs = ll;
+}
+
+/**/
+void
+vigotocolumn(void)
+{
+    int x, y;
+
+    findline(&x, &y);
+    if (zmult >= 0)
+	cs = x + zmult - (zmult > 0);
+    else
+	cs = y + zmult;
+    if (cs > y)
+	cs = y;
+    if (cs < x)
+	cs = x;
+}
+
+/**/
+void
+vimatchbracket(void)
+{
+    int ocs = cs, dir, ct;
+    unsigned char oth, me;
+
+  otog:
+    if (cs == ll || line[cs] == '\n') {
+	feep();
+	cs = ocs;
+	return;
+    }
+    switch (me = line[cs]) {
+    case '{':
+	dir = 1;
+	oth = '}';
+	break;
+    case /*{*/ '}':
+	virangeflag = -virangeflag;
+	dir = -1;
+	oth = '{'; /*}*/
+	break;
+    case '(':
+	dir = 1;
+	oth = ')';
+	break;
+    case ')':
+	virangeflag = -virangeflag;
+	dir = -1;
+	oth = '(';
+	break;
+    case '[':
+	dir = 1;
+	oth = ']';
+	break;
+    case ']':
+	virangeflag = -virangeflag;
+	dir = -1;
+	oth = '[';
+	break;
+    default:
+	cs++;
+	goto otog;
+    }
+    ct = 1;
+    while (cs >= 0 && cs < ll && ct) {
+	cs += dir;
+	if (line[cs] == oth)
+	    ct--;
+	else if (line[cs] == me)
+	    ct++;
+    }
+    if (cs < 0 || cs >= ll) {
+	feep();
+	cs = ocs;
+    } else if(dir > 0 && virangeflag)
+	cs++;
+}
+
+/**/
+void
+viforwardchar(void)
+{
+    int lim = findeol() - invicmdmode();
+    int n = zmult;
+
+    if (n < 0) {
+	zmult = -n;
+	vibackwardchar();
+	zmult = n;
+	return;
+    }
+    if (cs >= lim) {
+	feep();
+	return;
+    }
+    while (n-- && cs < lim)
+	cs++;
+}
+
+/**/
+void
+vibackwardchar(void)
+{
+    int n = zmult;
+
+    if (n < 0) {
+	zmult = -n;
+	viforwardchar();
+	zmult = n;
+	return;
+    }
+    if (cs == findbol()) {
+	feep();
+	return;
+    }
+    while (n--) {
+	cs--;
+	if (cs < 0 || line[cs] == '\n') {
+	    cs++;
+	    break;
+	}
+    }
+}
+
+/**/
+void
+viendofline(void)
+{
+    int oldcs = cs, n = zmult;
+
+    if (n < 1) {
+	feep();
+	return;
+    }
+    while(n--) {
+	if (cs > ll) {
+	    cs = oldcs;
+	    feep();
+	    return;
+	}
+	cs = findeol() + 1;
+    }
+    cs--;
+    lastcol = 1<<30;
+}
+
+/**/
+void
+vibeginningofline(void)
+{
+    cs = findbol();
+}
+
+static int vfindchar, vfinddir, tailadd;
+
+/**/
+void
+vifindnextchar(void)
+{
+    if ((vfindchar = vigetkey()) != -1) {
+	vfinddir = 1;
+	tailadd = 0;
+	virepeatfind();
+    }
+}
+
+/**/
+void
+vifindprevchar(void)
+{
+    if ((vfindchar = vigetkey()) != -1) {
+	vfinddir = -1;
+	tailadd = 0;
+	virepeatfind();
+    }
+}
+
+/**/
+void
+vifindnextcharskip(void)
+{
+    if ((vfindchar = vigetkey()) != -1) {
+	vfinddir = 1;
+	tailadd = -1;
+	virepeatfind();
+    }
+}
+
+/**/
+void
+vifindprevcharskip(void)
+{
+    if ((vfindchar = vigetkey()) != -1) {
+	vfinddir = -1;
+	tailadd = 1;
+	virepeatfind();
+    }
+}
+
+/**/
+void
+virepeatfind(void)
+{
+    int ocs = cs, n = zmult;
+
+    if (!vfinddir) {
+	feep();
+	return;
+    }
+    if (n < 0) {
+	zmult = -n;
+	virevrepeatfind();
+	zmult = n;
+	return;
+    }
+    while (n--) {
+	do
+	    cs += vfinddir;
+	while (cs >= 0 && cs < ll && line[cs] != vfindchar && line[cs] != '\n');
+	if (cs < 0 || cs >= ll || line[cs] == '\n') {
+	    feep();
+	    cs = ocs;
+	    return;
+	}
+    }
+    cs += tailadd;
+    if (vfinddir == 1 && virangeflag)
+	cs++;
+}
+
+/**/
+void
+virevrepeatfind(void)
+{
+    if (zmult < 0) {
+	zmult = -zmult;
+	virepeatfind();
+	zmult = -zmult;
+	return;
+    }
+    vfinddir = -vfinddir;
+    virepeatfind();
+    vfinddir = -vfinddir;
+}
+
+/**/
+void
+vifirstnonblank(void)
+{
+    cs = findbol();
+    while (cs != ll && iblank(line[cs]))
+	cs++;
+}
+
+/**/
+void
+visetmark(void)
+{
+    int ch;
+
+    ch = getkey(0);
+    if (ch < 'a' || ch > 'z') {
+	feep();
+	return;
+    }
+    ch -= 'a';
+    vimarkcs[ch] = cs;
+    vimarkline[ch] = histline;
+}
+
+/**/
+void
+vigotomark(void)
+{
+    int ch;
+
+    ch = getkey(0);
+    if (ch == c)
+	ch = 26;
+    else {
+	if (ch < 'a' || ch > 'z') {
+	    feep();
+	    return;
+	}
+	ch -= 'a';
+    }
+    if (!vimarkline[ch]) {
+	feep();
+	return;
+    }
+    if (curhist != vimarkline[ch]) {
+	char *s;
+
+	remember_edits();
+	if (!(s = qgetevent(vimarkline[ch]))) {
+	    vimarkline[ch] = 0;
+	    feep();
+	    return;
+	}
+	histline = vimarkline[ch];
+	setline(s);
+    }
+    cs = vimarkcs[ch];
+    if (cs > ll)
+	cs = ll;
+}
+
+/**/
+void
+vigotomarkline(void)
+{
+    vigotomark();
+    vifirstnonblank();
+}
diff --git a/Src/Zle/zle_params.c b/Src/Zle/zle_params.c
new file mode 100644
index 000000000..ed1420829
--- /dev/null
+++ b/Src/Zle/zle_params.c
@@ -0,0 +1,196 @@
+/*
+ * zle_params.c - ZLE special parameters
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+
+#include "zle_params.pro"
+
+/*
+ * ZLE SPECIAL PARAMETERS:
+ *
+ * These special parameters are created, with a local scope, when
+ * running user-defined widget functions.  Reading and writing them
+ * reads and writes bits of ZLE state.  The parameters are:
+ *
+ * BUFFER   (scalar)   entire buffer contents
+ * CURSOR   (integer)  cursor position; 0 <= $CURSOR <= $#BUFFER
+ * LBUFFER  (scalar)   portion of buffer to the left of the cursor
+ * RBUFFER  (scalar)   portion of buffer to the right of the cursor
+ */
+
+#define FN(X) ( (void (*) _((void))) (X) )
+static struct zleparam {
+    char *name;
+    int type;
+    void (*setfn) _((void));
+    void (*getfn) _((void));
+    void (*unsetfn) _((Param, int));
+    void *data;
+} zleparams[] = {
+    { "BUFFER",  PM_SCALAR,  FN(set_buffer),  FN(get_buffer),
+	zleunsetfn, NULL },
+    { "CURSOR",  PM_INTEGER, FN(set_cursor),  FN(get_cursor),
+	zleunsetfn, NULL },
+    { "LBUFFER", PM_SCALAR,  FN(set_lbuffer), FN(get_lbuffer),
+	zleunsetfn, NULL },
+    { "RBUFFER", PM_SCALAR,  FN(set_rbuffer), FN(get_rbuffer),
+	zleunsetfn, NULL },
+    { NULL, 0, NULL, NULL, NULL, NULL }
+};
+
+/**/
+void
+makezleparams(void)
+{
+    struct zleparam *zp;
+
+    for(zp = zleparams; zp->name; zp++) {
+	Param pm = createparam(zp->name, zp->type | PM_SPECIAL);
+
+	pm->level = locallevel;
+	pm->u.data = zp->data;
+	switch(PM_TYPE(zp->type)) {
+	    case PM_SCALAR:
+		pm->sets.cfn = (void (*) _((Param, char *))) zp->setfn;
+		pm->gets.cfn = (char *(*) _((Param))) zp->getfn;
+		break;
+	    case PM_ARRAY:
+		pm->sets.afn = (void (*) _((Param, char **))) zp->setfn;
+		pm->gets.afn = (char **(*) _((Param))) zp->getfn;
+		break;
+	    case PM_INTEGER:
+		pm->sets.ifn = (void (*) _((Param, long))) zp->setfn;
+		pm->gets.ifn = (long (*) _((Param))) zp->getfn;
+		break;
+	}
+	pm->unsetfn = zp->unsetfn;
+    }
+}
+
+/* Special unset function for ZLE special parameters: act like the standard *
+ * unset function if this is a user-initiated unset, but nothing is done if *
+ * the parameter is merely going out of scope (which it will do).           */
+
+/**/
+static void
+zleunsetfn(Param pm, int exp)
+{
+    if(exp)
+	stdunsetfn(pm, exp);
+}
+
+/**/
+static void
+set_buffer(Param pm, char *x)
+{
+    if(x) {
+	unmetafy(x, &ll);
+	sizeline(ll);
+	strcpy((char *)line, x);
+	zsfree(x);
+	if(cs > ll)
+	    cs = ll;
+    } else
+	cs = ll = 0;
+}
+
+/**/
+static char *
+get_buffer(Param pm)
+{
+    return metafy((char *)line, ll, META_HEAPDUP);
+}
+
+/**/
+static void
+set_cursor(Param pm, long x)
+{
+    if(x < 0)
+	cs = 0;
+    else if(x > ll)
+	cs = ll;
+    else
+	cs = x;
+}
+
+/**/
+static long
+get_cursor(Param pm)
+{
+    return cs;
+}
+
+/**/
+static void
+set_lbuffer(Param pm, char *x)
+{
+    char *y;
+    int len;
+
+    if(x)
+	unmetafy(y = x, &len);
+    else
+	y = "", len = 0;
+    sizeline(ll - cs + len);
+    memmove(line + len, line + cs, ll - cs);
+    memcpy(line, y, len);
+    ll = ll - cs + len;
+    cs = len;
+    zsfree(x);
+}
+
+/**/
+static char *
+get_lbuffer(Param pm)
+{
+    return metafy((char *)line, cs, META_HEAPDUP);
+}
+
+/**/
+static void
+set_rbuffer(Param pm, char *x)
+{
+    char *y;
+    int len;
+
+    if(x)
+	unmetafy(y = x, &len);
+    else
+	y = "", len = 0;
+    sizeline(ll = cs + len);
+    memcpy(line + cs, y, len);
+    zsfree(x);
+}
+
+/**/
+static char *
+get_rbuffer(Param pm)
+{
+    return metafy((char *)line + cs, ll - cs, META_HEAPDUP);
+}
diff --git a/Src/Zle/zle_refresh.c b/Src/Zle/zle_refresh.c
new file mode 100644
index 000000000..4621b5124
--- /dev/null
+++ b/Src/Zle/zle_refresh.c
@@ -0,0 +1,1116 @@
+/*
+ * zle_refresh.c - screen update
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_refresh.pro"
+
+/* Expanded prompts */
+
+/**/
+char *lpptbuf, *rpptbuf;
+
+/* Text attributes after displaying prompts */
+
+/**/
+unsigned pmpt_attr, rpmpt_attr;
+
+/* number of lines displayed */
+
+/**/
+int nlnct;
+
+/* Most lines of the buffer we've shown at once with the current list *
+ * showing.  == 0 if there is no list.  == -1 if a new list has just  *
+ * been put on the screen.  == -2 if refresh() needs to put up a new  *
+ * list.                                                              */
+
+/**/
+int showinglist;
+
+/* Non-zero if ALWAYS_LAST_PROMPT has been used, meaning that the *
+ * screen below the buffer display should not be cleared by       *
+ * refresh(), but should be by trashzle().                        */
+
+/**/
+int clearflag;
+
+#ifdef HAVE_SELECT
+/* cost of last update */
+/**/
+int cost;
+
+# define SELECT_ADD_COST(X)	cost += X
+# define zputc(a, b)		putc(a, b), cost++
+# define zwrite(a, b, c, d)	fwrite(a, b, c, d), cost += (b * c)
+#else
+# define SELECT_ADD_COST(X)
+# define zputc(a, b)		putc(a, b)
+# define zwrite(a, b, c, d)	fwrite(a, b, c, d)
+#endif
+
+/* Oct/Nov 94: <mason> some code savagely redesigned to fix several bugs -
+   refreshline() & tc_rightcurs() majorly rewritten; refresh() fixed -
+   I've put my fingers into just about every routine in here -
+   any queries about updates to mason@werple.net.au */
+
+static char **nbuf = NULL,	/* new video buffer line-by-line char array */
+    **obuf = NULL;		/* old video buffer line-by-line char array */
+static int more_start,		/* more text before start of screen?	    */
+    more_end,			/* more stuff after end of screen?	    */
+    lppth,			/* lines taken up by the prompt		    */
+    olnct,			/* previous number of lines		    */
+    ovln,			/* previous video cursor position line	    */
+    pptw, rpw,                  /* prompt widths on screen                  */
+    rppth,			/* right prompt height                      */
+    vcs, vln,			/* video cursor position column & line	    */
+    vmaxln,			/* video maximum number of lines	    */
+    winw, winh, rwinh,		/* window width & height		    */
+    winpos;			/* singlelinezle: line's position in window */
+
+/**/
+void
+resetvideo(void)
+{
+    int ln;
+    static int lwinw = -1, lwinh = -1;	/* last window width & height */
+ 
+    genprompts();
+    winw = columns;  /* terminal width */
+    if (termflags & TERM_SHORT)
+	winh = 1;
+    else
+	winh = (lines < 2) ? 24 : lines;
+    rwinh = lines;		/* keep the real number of lines */
+    winpos = vln = vmaxln = 0;
+    if (lwinw != winw || lwinh != winh) {
+	if (nbuf) {
+	    for (ln = 0; ln != lwinh; ln++) {
+		zfree(nbuf[ln], lwinw + 2);
+		zfree(obuf[ln], lwinw + 2);
+	    }
+	    free(nbuf);
+	    free(obuf);
+	}
+	nbuf = (char **)zcalloc((winh + 1) * sizeof(char *));
+	obuf = (char **)zcalloc((winh + 1) * sizeof(char *));
+	nbuf[0] = (char *)zalloc(winw + 2);
+	obuf[0] = (char *)zalloc(winw + 2);
+
+	lwinw = winw;
+	lwinh = winh;
+    }
+    for (ln = 0; ln != winh + 1; ln++) {
+	if (nbuf[ln])
+	    *nbuf[ln] = '\0';
+	if (obuf[ln])
+	    *obuf[ln] = '\0';
+    }
+
+    if (pptw) {
+    	memset(nbuf[0], ' ', pptw);
+	memset(obuf[0], ' ', pptw);
+	nbuf[0][pptw] = obuf[0][pptw] = '\0';
+    }
+
+    vcs = pptw;
+    olnct = nlnct = 0;
+    if (showinglist > 0)
+	showinglist = -2;
+}
+
+/*
+ * Nov 96: <mason> changed to single line scroll
+ */
+
+/**/
+static void
+scrollwindow(int tline)
+{
+    int t0;
+    char *s;
+
+    s = nbuf[tline];
+    for (t0 = tline; t0 < winh - 1; t0++)
+	nbuf[t0] = nbuf[t0 + 1];
+    nbuf[winh - 1] = s;
+    if (!tline)
+	more_start = 1;
+    return;
+}
+
+/* this is the messy part. */
+/* this define belongs where it's used!!! */
+
+#define nextline					\
+{							\
+    *s = '\0';						\
+    if (ln != winh - 1)					\
+	ln++;						\
+    else {						\
+	if (!canscroll)	{				\
+	    if (nvln != -1 && nvln != winh - 1		\
+		&& (numscrolls != onumscrolls - 1	\
+		    || nvln <= winh / 2))		\
+	        break;					\
+	    numscrolls++;				\
+	    canscroll = winh / 2;			\
+	}						\
+	canscroll--;					\
+	scrollwindow(0);				\
+	if (nvln != -1)					\
+	    nvln--;					\
+    }							\
+    if (!nbuf[ln])					\
+	nbuf[ln] = (char *)zalloc(winw + 2);		\
+    s = (unsigned char *)nbuf[ln];			\
+    sen = s + winw;					\
+}
+
+#define snextline					\
+{							\
+    *s = '\0';						\
+    if (ln != winh - 1)					\
+	ln++;						\
+    else						\
+	if (tosln < 3) {				\
+	    more_status = 1;				\
+	    scrollwindow(tosln + 1);			\
+	} else if (tosln - 1 <= nvln) {			\
+	    scrollwindow(0);				\
+	    if (nvln)					\
+		nvln--, tosln--;			\
+	} else {					\
+	    tosln--;					\
+	    scrollwindow(tosln);			\
+	}						\
+    if (!nbuf[ln])					\
+	nbuf[ln] = (char *)zalloc(winw + 2);		\
+    s = (unsigned char *)nbuf[ln];			\
+    sen = s + winw;					\
+}
+
+static int cleareol,		/* clear to end-of-line (if can't cleareod) */
+    clearf,			/* alwayslastprompt used immediately before */
+    put_rpmpt,			/* whether we should display right-prompt   */
+    oput_rpmpt,			/* whether displayed right-prompt last time */
+    oxtabs,			/* oxtabs - tabs expand to spaces if set    */
+    numscrolls, onumscrolls;
+
+/**/
+void
+refresh(void)
+{
+    static int inlist;		/* avoiding recursion                        */
+    int canscroll = 0,		/* number of lines we are allowed to scroll  */
+	ln = 0,			/* current line we're working on	     */
+	more_status = 0,	/* more stuff in status line		     */
+	nvcs = 0, nvln = -1,	/* video cursor column and line		     */
+	t0 = -1,		/* tmp					     */
+	tosln = 0;		/* tmp in statusline stuff		     */
+    unsigned char *s,		/* pointer into the video buffer	     */
+	*t,			/* pointer into the real buffer		     */
+	*sen,			/* pointer to end of the video buffer (eol)  */
+	*scs;			/* pointer to cursor position in real buffer */
+    char **qbuf;		/* tmp					     */
+
+    /* If this is called from listmatches() (indirectly via trashzle()), and *
+     * that was called from the end of refresh(), then we don't need to do   *
+     * anything.  All this `inlist' code is actually unnecessary, but it     *
+     * improves speed a little in a common case.                             */
+    if (inlist)
+	return;
+
+#ifdef HAVE_SELECT
+    cost = 0;			/* reset */
+#endif
+
+/* Nov 96: <mason>  I haven't checked how complete this is.  sgtty stuff may
+   or may not work */
+    oxtabs = ((SGTTYFLAG & SGTABTYPE) == SGTABTYPE);
+
+    cleareol = 0;		/* unset */
+    more_start = more_end = 0;	/* unset */
+    if (isset(SINGLELINEZLE) || lines < 3
+	|| (termflags & (TERM_NOUP | TERM_BAD | TERM_UNKNOWN)))
+	termflags |= TERM_SHORT;
+    else
+	termflags &= ~TERM_SHORT;
+    if (resetneeded) {
+	onumscrolls = 0;
+	setterm();
+#ifdef TIOCGWINSZ
+	if (winchanged) {
+	    moveto(0, 0);
+	    t0 = olnct;		/* this is to clear extra lines even when */
+	    winchanged = 0;	/* the terminal cannot TCCLEAREOD	  */
+	}
+#endif
+	resetvideo();
+	resetneeded = 0;	/* unset */
+	oput_rpmpt = 0;		/* no right-prompt currently on screen */
+
+	/* we probably should only have explicitly set attributes */
+	tsetcap(TCALLATTRSOFF, 0);
+	tsetcap(TCSTANDOUTEND, 0);
+	tsetcap(TCUNDERLINEEND, 0);
+
+        if (!clearflag)
+            if (tccan(TCCLEAREOD))
+                tcout(TCCLEAREOD);
+            else
+                cleareol = 1;   /* request: clear to end of line */
+        if (t0 > -1)
+            olnct = t0;
+        if (termflags & TERM_SHORT)
+            vcs = 0;
+        else if (!clearflag && lpptbuf[0])
+            zputs(lpptbuf, shout);
+	if (clearflag) {
+	    zputc('\r', shout);
+	    vcs = 0;
+	    moveto(0, pptw);
+	}
+	fflush(shout);
+	clearf = clearflag;
+    } else if (winw != columns || rwinh != lines)
+	resetvideo();
+
+/* now winw equals columns and winh equals lines 
+   width comparisons can be made with winw, height comparisons with winh */
+
+    if (termflags & TERM_SHORT) {
+	singlerefresh();
+	return;
+    }
+
+    if (cs < 0) {
+#ifdef DEBUG
+	fprintf(stderr, "BUG: negative cursor position\n");
+	fflush(stderr); 
+#endif
+	cs = 0;
+    }
+    scs = line + cs;
+    numscrolls = 0;
+
+/* first, we generate the video line buffers so we know what to put on
+   the screen - also determine final cursor position (nvln, nvcs) */
+
+    /* Deemed necessary by PWS 1995/05/15 due to kill-line problems */
+    if (!*nbuf)
+	*nbuf = (char *)zalloc(winw + 2);
+
+    s = (unsigned char *)(nbuf[ln = 0] + pptw);
+    t = line;
+    sen = (unsigned char *)(*nbuf + winw);
+    for (; t < line+ll; t++) {
+	if (t == scs)			/* if cursor is here, remember it */
+	    nvcs = s - (unsigned char *)(nbuf[nvln = ln]);
+
+	if (*t == '\n')	{		/* newline */
+	    nbuf[ln][winw + 1] = '\0';	/* text not wrapped */
+	    nextline
+	} else if (*t == '\t') {		/* tab */
+	    t0 = (char *)s - nbuf[ln];
+	    if ((t0 | 7) + 1 >= winw) {
+		nbuf[ln][winw + 1] = '\n';	/* text wrapped */
+		nextline
+	    } else
+		do
+		    *s++ = ' ';
+		while ((++t0) & 7);
+	} else if (icntrl(*t)) {	/* other control character */
+	    *s++ = '^';
+	    if (s == sen) {
+		nbuf[ln][winw + 1] = '\n';	/* text wrapped */
+		nextline
+	    }
+	    *s++ = (*t == 127) ? '?' : (*t | '@');
+	} else				/* normal character */
+	    *s++ = *t;
+	if (s == sen) {
+	    nbuf[ln][winw + 1] = '\n';	/* text wrapped */
+	    nextline
+	}
+    }
+
+/* if we're really on the next line, don't fake it; do everything properly */
+    if (t == scs && (nvcs = s - (unsigned char *)(nbuf[nvln = ln])) == winw) {
+	nbuf[ln][winw + 1] = '\n';	/* text wrapped */
+	switch ('\0') { 	/* a sad hack to make the break */
+	case '\0':		/* in nextline work */
+	    nextline
+	}
+	*s = '\0';
+	nvcs = 0;
+	nvln++;
+    }
+
+    if (t != line + ll)
+	more_end = 1;
+
+    if (statusline) {
+	tosln = ln + 1;
+        if (ln == winh - 1) {
+	    if (nvln > 0) {
+		scrollwindow(0);
+		nvln--;
+	    }
+	    tosln--;
+	}
+	nbuf[ln][winw + 1] = '\0';	/* text not wrapped */
+	snextline
+	t = (unsigned char *)statusline;
+	for (; t < (unsigned char *)statusline + statusll; t++) {
+	    if (icntrl(*t)) {	/* simplified processing in the status line */
+		*s++ = '^';
+		if (s == sen) {
+		    nbuf[ln][winw + 1] = '\n';	/* text wrapped */
+		    snextline
+		}
+		*s++ = (*t == 127) ? '?' : (*t | '@');
+	    } else
+		*s++ = *t;
+	    if (s == sen) {
+		nbuf[ln][winw + 1] = '\n';	/* text wrapped */
+		snextline
+	    }
+	}
+    }
+
+/* insert <.... at end of last line if there is more text past end of screen */
+    if (more_end) {
+	if (!statusline)
+	    tosln = winh;
+	strncpy(nbuf[tosln - 1] + winw - 7, " <.... ", 7);
+	nbuf[tosln - 1][winw] = nbuf[tosln - 1][winw + 1] = '\0';
+    }
+
+/* insert <....> at end of first status line if status is too big */
+    if (more_status) {
+	strncpy(nbuf[tosln] + winw - 8, " <....> ", 8);
+	nbuf[tosln][winw] = nbuf[tosln][winw + 1] = '\0';
+    }
+
+    *s = '\0';
+    nlnct = ln + 1;
+    for (ln = nlnct; ln < winh; ln++)
+	zfree(nbuf[ln], winw + 2), nbuf[ln] = NULL;
+
+/* determine whether the right-prompt exists and can fit on the screen */
+    if (!more_start)
+	put_rpmpt = rppth == 1 && rpptbuf[0] && !strchr(rpptbuf, '\t') &&
+	    (int)strlen(nbuf[0]) + rpw < winw - 1;
+    else {
+/* insert >.... on first line if there is more text before start of screen */
+	memset(nbuf[0], ' ', pptw);
+	t0 = winw - pptw;
+	t0 = t0 > 5 ? 5 : t0;
+	strncpy(nbuf[0] + pptw, ">....", t0);
+	memset(nbuf[0] + pptw + t0, ' ', winw - t0 - pptw);
+	nbuf[0][winw] = nbuf[0][winw + 1] = '\0';
+    }
+
+    for (ln = 0; !clearf && (ln < nlnct); ln++) {
+	/* if we have more lines than last time, clear the newly-used lines */
+	if (ln >= olnct)
+	    cleareol = 1;
+
+    /* if old line and new line are different,
+       see if we can insert/delete a line to speed up update */
+
+	if (ln < olnct - 1 && !(hasam && vcs == winw) &&
+	    nbuf[ln] && obuf[ln] &&
+	    strncmp(nbuf[ln], obuf[ln], 16)) {
+	    if (tccan(TCDELLINE) && obuf[ln + 1] && obuf[ln + 1][0] &&
+		nbuf[ln] && !strncmp(nbuf[ln], obuf[ln + 1], 16)) {
+		moveto(ln, 0);
+		tcout(TCDELLINE);
+		zfree(obuf[ln], winw + 2);
+		for (t0 = ln; t0 != olnct; t0++)
+		    obuf[t0] = obuf[t0 + 1];
+		obuf[--olnct] = NULL;
+	    }
+	/* don't try to insert a line if olnct = vmaxln (vmaxln is the number
+	   of lines that have been displayed by this routine) so that we don't
+	   go off the end of the screen. */
+
+	    else if (tccan(TCINSLINE) && olnct < vmaxln && nbuf[ln + 1] &&
+		     obuf[ln] && !strncmp(nbuf[ln + 1], obuf[ln], 16)) {
+		moveto(ln, 0);
+		tcout(TCINSLINE);
+		for (t0 = olnct; t0 != ln; t0--)
+		    obuf[t0] = obuf[t0 - 1];
+		obuf[ln] = NULL;
+		olnct++;
+	    }
+	}
+
+    /* update the single line */
+	refreshline(ln);
+
+    /* output the right-prompt if appropriate */
+	if (put_rpmpt && !ln && !oput_rpmpt) {
+	    moveto(0, winw - 1 - rpw);
+	    zputs(rpptbuf, shout);
+	    vcs = winw - 1;
+	/* reset character attributes to that set by the main prompt */
+	    txtchange = pmpt_attr;
+	    if (txtchangeisset(TXTNOBOLDFACE) && (rpmpt_attr & TXTBOLDFACE))
+		tsetcap(TCALLATTRSOFF, 0);
+	    if (txtchangeisset(TXTNOSTANDOUT) && (rpmpt_attr & TXTSTANDOUT))
+		tsetcap(TCSTANDOUTEND, 0);
+	    if (txtchangeisset(TXTNOUNDERLINE) && (rpmpt_attr & TXTUNDERLINE))
+		tsetcap(TCUNDERLINEEND, 0);
+	    if (txtchangeisset(TXTBOLDFACE) && (rpmpt_attr & TXTNOBOLDFACE))
+		tsetcap(TCBOLDFACEBEG, 0);
+	    if (txtchangeisset(TXTSTANDOUT) && (rpmpt_attr & TXTNOSTANDOUT))
+		tsetcap(TCSTANDOUTBEG, 0);
+	    if (txtchangeisset(TXTUNDERLINE) && (rpmpt_attr & TXTNOUNDERLINE))
+		tsetcap(TCUNDERLINEBEG, 0);
+	}
+    }
+
+/* if old buffer had extra lines, set them to be cleared and refresh them
+individually */
+
+    if (olnct > nlnct) {
+	cleareol = 1;
+	for (ln = nlnct; ln < olnct; ln++)
+	    refreshline(ln);
+    }
+
+/* reset character attributes */
+    if (clearf && postedit) {
+	if ((txtchange = pmpt_attr ? pmpt_attr : rpmpt_attr)) {
+	    if (txtchangeisset(TXTNOBOLDFACE))
+		tsetcap(TCALLATTRSOFF, 0);
+	    if (txtchangeisset(TXTNOSTANDOUT))
+		tsetcap(TCSTANDOUTEND, 0);
+	    if (txtchangeisset(TXTNOUNDERLINE))
+		tsetcap(TCUNDERLINEEND, 0);
+	    if (txtchangeisset(TXTBOLDFACE))
+		tsetcap(TCBOLDFACEBEG, 0);
+	    if (txtchangeisset(TXTSTANDOUT))
+		tsetcap(TCSTANDOUTBEG, 0);
+	    if (txtchangeisset(TXTUNDERLINE))
+		tsetcap(TCUNDERLINEBEG, 0);
+	}
+    }
+    clearf = 0;
+
+/* move to the new cursor position */
+    moveto(nvln, nvcs);
+
+/* swap old and new buffers - better than freeing/allocating every time */
+    qbuf = nbuf;
+    nbuf = obuf;
+    obuf = qbuf;
+/* store current values so we can use them next time */
+    ovln = nvln;
+    olnct = nlnct;
+    oput_rpmpt = put_rpmpt;
+    onumscrolls = numscrolls;
+    if (nlnct > vmaxln)
+	vmaxln = nlnct;
+    fflush(shout);		/* make sure everything is written out */
+
+    /* if we have a new list showing, note it; if part of the list has been
+    overwritten, redisplay it. */
+    if (showinglist == -2 || (showinglist > 0 && showinglist < nlnct)) {
+	inlist = 1;
+	listmatches();
+	inlist = 0;
+	refresh();
+    }
+    if (showinglist == -1)
+	showinglist = nlnct;
+}
+
+#define tcinscost(X)   (tccan(TCMULTINS) ? tclen[TCMULTINS] : (X)*tclen[TCINS])
+#define tcdelcost(X)   (tccan(TCMULTDEL) ? tclen[TCMULTDEL] : (X)*tclen[TCDEL])
+#define tc_delchars(X)	(void) tcmultout(TCDEL, TCMULTDEL, (X))
+#define tc_inschars(X)	(void) tcmultout(TCINS, TCMULTINS, (X))
+#define tc_upcurs(X)	(void) tcmultout(TCUP, TCMULTUP, (X))
+#define tc_leftcurs(X)	(void) tcmultout(TCLEFT, TCMULTLEFT, (X))
+
+/* refresh one line, using whatever speed-up tricks are provided by the tty */
+
+/**/
+static void
+refreshline(int ln)
+{
+    char *nl, *ol, *p1;		/* line buffer pointers			 */
+    int ccs = 0,		/* temporary count for cursor position	 */
+	char_ins = 0,		/* number of characters inserted/deleted */
+	col_cleareol,		/* clear to end-of-line from this column */
+	i, j,			/* tmp					 */
+	ins_last,		/* insert pushed last character off line */
+	nllen, ollen,		/* new and old line buffer lengths	 */
+	rnllen;			/* real new line buffer length		 */
+
+/* 0: setup */
+    nl = nbuf[ln];
+    rnllen = nllen = nl ? strlen(nl) : 0;
+    ol = obuf[ln] ? obuf[ln] : "";
+    ollen = strlen(ol);
+
+/* optimisation: can easily happen for clearing old lines.  If the terminal has
+   the capability, then this is the easiest way to skip unnecessary stuff */
+    if (cleareol && !nllen && !(hasam && ln < nlnct - 1)
+	&& tccan(TCCLEAREOL)) {
+	moveto(ln, 0);
+	tcout(TCCLEAREOL);
+	return;	
+    }
+
+/* 1: pad out the new buffer with spaces to contain _all_ of the characters
+      which need to be written. do this now to allow some pre-processing */
+
+    if (cleareol 		/* request to clear to end of line */
+	|| !nllen 		/* no line buffer given */
+	|| (ln == 0 && (put_rpmpt != oput_rpmpt))) {	/* prompt changed */
+	p1 = halloc(winw + 2);
+	if (nllen)
+	    strncpy(p1, nl, nllen);
+	memset(p1 + nllen, ' ', winw - nllen);
+	p1[winw] = '\0';
+	p1[winw + 1] = (nllen < winw) ? '\0' : nl[winw + 1];
+	if (ln && nbuf[ln])
+	    memcpy(nl, p1, winw + 2);	/* next time obuf will be up-to-date */
+	else
+	    nl = p1;		/* don't keep the padding for prompt line */
+	nllen = winw;
+    } else if (ollen > nllen) { /* make new line at least as long as old */
+	p1 = halloc(ollen + 1);
+	strncpy(p1, nl, nllen);
+	memset(p1 + nllen, ' ', ollen - nllen);
+	p1[ollen] = '\0';
+	nl = p1;
+	nllen = ollen;
+    }
+
+/* 2: see if we can clear to end-of-line, and if it's faster, work out where
+   to do it from - we can normally only do so if there's no right-prompt.
+   With automatic margins, we shouldn't do it if there is another line, in
+   case it messes up cut and paste. */
+
+    if (hasam && ln < nlnct - 1 && rnllen == winw)
+	col_cleareol = -2;	/* clearing eol would be evil so don't */
+    else {
+	col_cleareol = -1;
+	if (tccan(TCCLEAREOL) && (nllen == winw || put_rpmpt != oput_rpmpt)) {
+	    for (i = nllen; i && nl[i - 1] == ' '; i--);
+	    for (j = ollen; j && ol[j - 1] == ' '; j--);
+	    if ((j > i + tclen[TCCLEAREOL])	/* new buf has enough spaces */
+		|| (nllen == winw && nl[winw - 1] == ' '))
+		col_cleareol = i;
+	}
+    }
+
+/* 2b: first a new trick for automargin niceness - good for cut and paste */
+
+    if (hasam && vcs == winw) {
+	if (nbuf[vln] && nbuf[vln][vcs + 1] == '\n') {
+	    vln++, vcs = 1;
+            if (nbuf[vln]  && *nbuf[vln])
+		zputc(*nbuf[vln], shout);
+	    else
+		zputc(' ', shout);  /* I don't think this should happen */
+	    if (ln == vln) {	/* better safe than sorry */
+		nl++;
+		if (*ol)
+		    ol++;
+		ccs = 1;
+	    }			/* else  hmmm... I wonder what happened */
+	} else {
+	    vln++, vcs = 0;
+	    zputc('\n', shout);
+	}
+    }
+    ins_last = 0;
+
+/* 2c: if we're on the first line, start checking at the end of the prompt;
+   we shouldn't be doing anything within the prompt */
+
+    if (ln == 0 && pptw) {
+	i = pptw - ccs;
+	j = strlen(ol);
+	nl += i;
+	ol += (i > j ? j : i);	/* if ol is too short, point it to '\0' */
+	ccs = pptw;
+    }
+
+/* 3: main display loop - write out the buffer using whatever tricks we can */
+
+    for (;;) {
+	if (*nl && *ol && nl[1] == ol[1]) /* skip only if second chars match */
+	/* skip past all matching characters */
+	    for (; *nl && (*nl == *ol); nl++, ol++, ccs++) ;
+
+	if (!*nl) {
+	    if (ccs == winw && hasam && char_ins > 0 && ins_last
+		&& vcs != winw) {
+		nl--;           /* we can assume we can go back here */
+		moveto(ln, winw - 1);
+		zputc(*nl, shout);
+		vcs++;
+		return;         /* write last character in line */
+	    }
+	    if ((char_ins <= 0) || (ccs >= winw))    /* written everything */
+		return;
+	    if (tccan(TCCLEAREOL) && (char_ins >= tclen[TCCLEAREOL])
+	    	&& col_cleareol != -2)
+	    /* we've got junk on the right yet to clear */
+		col_cleareol = 0;	/* force a clear to end of line */
+	}
+
+	moveto(ln, ccs);	/* move to where we do all output from */
+
+    /* if we can finish quickly, do so */
+	if ((col_cleareol >= 0) && (ccs >= col_cleareol)) {
+	    tcout(TCCLEAREOL);
+	    return;
+	}
+
+    /* we've written out the new but yet to clear rubbish due to inserts */
+	if (!*nl) {
+	    i = (winw - ccs < char_ins) ? (winw - ccs) : char_ins;
+	    if (tccan(TCDEL) && (tcdelcost(i) <= i + 1))
+		tc_delchars(i);
+	    else {
+		vcs += i;
+		while (i-- > 0)
+		    zputc(' ', shout);
+	    }
+	    return;
+	}
+
+    /* if we've reached the end of the old buffer, then there are few tricks
+       we can do, so we just dump out what we must and clear if we can */
+	if (!*ol) {
+	    i = (col_cleareol >= 0) ? col_cleareol : nllen;
+	    i -= vcs;
+	    zwrite(nl, i, 1, shout);
+	    vcs += i;
+	    if (col_cleareol >= 0)
+		tcout(TCCLEAREOL);
+	    return;
+	}
+
+    /* inserting & deleting chars: we can if there's no right-prompt */
+	if ((ln || !put_rpmpt || !oput_rpmpt) 
+	    && (nl[1] && ol[1] && nl[1] != ol[1])) { 
+
+	/* deleting characters - see if we can find a match series that
+	   makes it cheaper to delete intermediate characters
+	   eg. oldline: hifoobar \ hopefully cheaper here to delete two
+	       newline: foobar	 / characters, then we have six matches */
+	    if (tccan(TCDEL)) {
+		for (i = 1; *(ol + i); i++)
+		    if (tcdelcost(i) < pfxlen(ol + i, nl)) {
+			tc_delchars(i);
+			ol += i;
+			char_ins -= i;
+			i = 0;
+			break;
+		    }
+		if (!i)
+		    continue;
+	    }
+	/* inserting characters - characters pushed off the right should be
+	   annihilated, but we don't do this if we're on the last line lest
+	   undesired scrolling occurs due to `illegal' characters on screen */
+
+	    if (tccan(TCINS) && (vln != lines - 1)) {	/* not on last line */
+		for (i = 1; *(nl + i); i++)
+		    if (tcinscost(i) < pfxlen(nl + i, ol)) {
+			tc_inschars(i);
+			zwrite(nl, i, 1, shout);
+			nl += i;
+			char_ins += i;
+			ccs = (vcs += i);
+		    /* if we've pushed off the right, truncate oldline */
+			for (i = 0; *(ol + i) && i < winw - ccs; i++);
+			if (i == winw - ccs) {
+			    *(ol + i) = '\0';
+			    ins_last = 1;
+			}
+			i = 0;
+			break;
+		    }
+		if (!i)
+		    continue;
+	    }
+	}
+    /* we can't do any fancy tricks, so just dump the single character
+       and keep on trying */
+	zputc(*nl, shout);
+	nl++, ol++;
+	ccs++, vcs++;
+    }
+}
+
+/* move the cursor to line ln (relative to the prompt line),
+   absolute column cl; update vln, vcs - video line and column */
+
+/**/
+void
+moveto(int ln, int cl)
+{
+    int c;
+
+    if (vcs == winw) {
+	vln++, vcs = 0;
+	if (!hasam) {
+	    zputc('\r', shout);
+	    zputc('\n', shout);
+	} else {
+	    if ((vln < nlnct) && nbuf[vln] && *nbuf[vln])
+		c = *nbuf[vln];
+	    else
+		c = ' ';
+	    zputc(c, shout);
+	    zputc('\r', shout);
+	    if ((vln < olnct) && obuf[vln] && *obuf[vln])
+		*obuf[vln] = c;
+	}
+    }
+
+    if (ln == vln && cl == vcs)
+	return;
+
+/* move up */
+    if (ln < vln) {
+	tc_upcurs(vln - ln);
+	vln = ln;
+    }
+/* move down; if we might go off the end of the screen, use newlines
+   instead of TCDOWN */
+
+    while (ln > vln) {
+	if (vln < vmaxln - 1)
+	    if (ln > vmaxln - 1) {
+		if (tc_downcurs(vmaxln - 1 - vln))
+		    vcs = 0;
+		vln = vmaxln - 1;
+	    } else {
+		if (tc_downcurs(ln - vln))
+		    vcs = 0;
+		vln = ln;
+		continue;
+	    }
+	zputc('\r', shout), vcs = 0; /* safety precaution */
+	while (ln > vln) {
+	    zputc('\n', shout);
+	    vln++;
+	}
+    }
+
+    if (cl == vcs)
+	return;
+
+/* choose cheapest movements for ttys without multiple movement capabilities -
+   do this now because it's easier (to code) */
+    if (cl <= vcs / 2) {
+	zputc('\r', shout);
+	vcs = 0;
+    }
+    if (vcs < cl)
+	tc_rightcurs(cl);
+    else if (vcs > cl)
+	tc_leftcurs(vcs - cl);
+    vcs = cl;
+}
+
+/**/
+int
+tcmultout(int cap, int multcap, int ct)
+{
+    if (tccan(multcap) && (!tccan(cap) || tclen[multcap] <= tclen[cap] * ct)) {
+	tcoutarg(multcap, ct);
+	return 1;
+    } else if (tccan(cap)) {
+	while (ct--)
+	    tcout(cap);
+	return 1;
+    }
+    return 0;
+}
+
+/**/
+static void
+tc_rightcurs(int cl)
+{
+    int ct,			/* number of characters to move across	    */
+	i = vcs,		/* cursor position after initial movements  */
+	j;
+    char *t;
+
+    ct = cl - vcs;
+
+/* do a multright if we can - it's the most reliable */
+    if (tccan(TCMULTRIGHT)) {
+	tcoutarg(TCMULTRIGHT, ct);
+	return;
+    }
+
+/* try tabs if tabs are non destructive and multright is not possible */
+    if (!oxtabs && tccan(TCNEXTTAB) && ((vcs | 7) < cl)) {
+	i = (vcs | 7) + 1;
+	tcout(TCNEXTTAB);
+	for ( ; i + 8 <= cl; i += 8)
+	    tcout(TCNEXTTAB);
+	if ((ct = cl - i) == 0) /* number of chars still to move across */
+	    return;
+    }
+
+/* otherwise _carefully_ write the contents of the video buffer.
+   if we're anywhere in the prompt, goto the left column and write the whole
+   prompt out unless ztrlen(lpptbuf) == pptw : we can cheat then */
+    if (vln == 0 && i < pptw) {
+	if (strlen(lpptbuf) == pptw)
+	    fputs(lpptbuf + i, shout);
+	else if (tccan(TCRIGHT) && (tclen[TCRIGHT] * ct <= ztrlen(lpptbuf)))
+	    /* it is cheaper to send TCRIGHT than reprint the whole prompt */
+	    for (ct = pptw - i; ct--; )
+		tcout(TCRIGHT);
+        else {
+	    if (i != 0)
+		zputc('\r', shout);
+	    tc_upcurs(lppth - 1);
+	    zputs(lpptbuf, shout);
+	}
+	i = pptw;
+	ct = cl - i;
+    }
+
+    if (nbuf[vln]) {
+	for (j = 0, t = nbuf[vln]; *t && (j < i); j++, t++);
+	if (j == i)
+	    for ( ; *t && ct; ct--, t++)
+		zputc(*t, shout);
+    }
+    while (ct--)
+	zputc(' ', shout);	/* not my fault your terminal can't go right */
+}
+
+/**/
+static int
+tc_downcurs(int ct)
+{
+    int ret = 0;
+
+    if (ct && !tcmultout(TCDOWN, TCMULTDOWN, ct)) {
+	while (ct--)
+	    zputc('\n', shout);
+	zputc('\r', shout), ret = -1;
+    }
+    return ret;
+}
+
+/**/
+void
+tcout(int cap)
+{
+    tputs(tcstr[cap], 1, putshout);
+    SELECT_ADD_COST(tclen[cap]);
+}
+
+/**/
+static void
+tcoutarg(int cap, int arg)
+{
+    char *result;
+
+    result = tgoto(tcstr[cap], arg, arg);
+    tputs(result, 1, putshout);
+    SELECT_ADD_COST(strlen(result));
+}
+
+/**/
+void
+clearscreen(void)
+{
+    tcout(TCCLEARSCREEN);
+    resetneeded = 1;
+    clearflag = 0;
+}
+
+/**/
+void
+redisplay(void)
+{
+    moveto(0, 0);
+    zputc('\r', shout);		/* extra care */
+    tc_upcurs(lppth - 1);
+    resetneeded = 1;
+    clearflag = 0;
+}
+
+/**/
+static void
+singlerefresh(void)
+{
+    char *vbuf, *vp,		/* video buffer and pointer    */
+	**qbuf,			/* tmp			       */
+	*refreshop = *obuf;	/* pointer to old video buffer */
+    int t0,			/* tmp			       */
+	vsiz,			/* size of new video buffer    */
+	nvcs = 0;		/* new video cursor column     */
+
+    nlnct = 1;
+/* generate the new line buffer completely */
+    for (vsiz = 1 + pptw, t0 = 0; t0 != ll; t0++, vsiz++)
+	if (line[t0] == '\t')
+	    vsiz = (vsiz | 7) + 1;
+	else if (icntrl(line[t0]))
+	    vsiz++;
+    vbuf = (char *)zalloc(vsiz);
+
+    if (cs < 0) {
+#ifdef DEBUG
+	fprintf(stderr, "BUG: negative cursor position\n");
+	fflush(stderr); 
+#endif
+	cs = 0;
+    }
+
+    memcpy(vbuf, strchr(lpptbuf, 0) - pptw, pptw); /* only use last part of prompt */
+    vbuf[pptw] = '\0';
+    vp = vbuf + pptw;
+
+    for (t0 = 0; t0 != ll; t0++) {
+	if (line[t0] == '\t')
+	    for (*vp++ = ' '; (vp - vbuf) & 7; )
+		*vp++ = ' ';
+	else if (line[t0] == '\n') {
+	    *vp++ = '\\';
+	    *vp++ = 'n';
+	} else if (line[t0] == 0x7f) {
+	    *vp++ = '^';
+	    *vp++ = '?';
+	} else if (icntrl(line[t0])) {
+	    *vp++ = '^';
+	    *vp++ = line[t0] | '@';
+	} else
+	    *vp++ = line[t0];
+	if (t0 == cs)
+	    nvcs = vp - vbuf - 1;
+    }
+    if (t0 == cs)
+	nvcs = vp - vbuf;
+    *vp = '\0';
+
+/* determine which part of the new line buffer we want for the display */
+    if ((winpos && nvcs < winpos + 1) || (nvcs > winpos + winw - 2)) {
+	if ((winpos = nvcs - ((winw - hasam) / 2)) < 0)
+	    winpos = 0;
+    }
+    if (winpos)
+	vbuf[winpos] = '<';	/* line continues to the left */
+    if ((int)strlen(vbuf + winpos) > (winw - hasam)) {
+	vbuf[winpos + winw - hasam - 1] = '>';	/* line continues to right */
+	vbuf[winpos + winw - hasam] = '\0';
+    }
+    strcpy(nbuf[0], vbuf + winpos);
+    zfree(vbuf, vsiz);
+    nvcs -= winpos;
+
+/* display the `visable' portion of the line buffer */
+    for (t0 = 0, vp = *nbuf;;) {
+    /* skip past all matching characters */
+	for (; *vp && *vp == *refreshop; t0++, vp++, refreshop++) ;
+
+	if (!*vp && !*refreshop)
+	    break;
+
+	singmoveto(t0);		/* move to where we do all output from */
+
+	if (!*refreshop) {
+	    if ((t0 = strlen(vp)))
+		zwrite(vp, t0, 1, shout);
+	    vcs += t0;
+	    break;
+	}
+	if (!*vp) {
+	    if (tccan(TCCLEAREOL))
+		tcout(TCCLEAREOL);
+	    else
+		for (; *refreshop++; vcs++)
+		    zputc(' ', shout);
+	    break;
+	}
+	zputc(*vp, shout);
+	vcs++, t0++;
+	vp++, refreshop++;
+    }
+/* move to the new cursor position */
+    singmoveto(nvcs);
+
+    qbuf = nbuf;
+    nbuf = obuf;
+    obuf = qbuf;
+    fflush(shout);		/* make sure everything is written out */
+}
+
+/**/
+static void
+singmoveto(int pos)
+{
+    if (pos == vcs)
+	return;
+    if (pos <= vcs / 2) {
+	zputc('\r', shout);
+	vcs = 0;
+    }
+    if (pos < vcs) {
+	tc_leftcurs(vcs - pos);
+	vcs = pos;
+    }
+    if (pos > vcs) {
+	if (tcmultout(TCRIGHT, TCMULTRIGHT, pos - vcs))
+	    vcs = pos;
+	else
+	    while (pos > vcs) {
+		zputc(nbuf[0][vcs], shout);
+		vcs++;
+	    }
+    }
+}
+
+/* recheck size of prompts */
+
+/**/
+static void
+genprompts(void)
+{
+    countprompt(lpptbuf, &pptw, &lppth);
+    countprompt(rpptbuf, &rpw, &rppth);
+}
diff --git a/Src/Zle/zle_things.sed b/Src/Zle/zle_things.sed
new file mode 100644
index 000000000..781d23704
--- /dev/null
+++ b/Src/Zle/zle_things.sed
@@ -0,0 +1,9 @@
+/^ *T("/{
+    s/^[^"]*"/    z_/
+    s/".*$/,/
+    s/-//g
+    s/\./D/g
+    P
+    s/    z_\(.*\),/#define t_\1 (\&thingies[z_\1])/
+    P
+}
diff --git a/Src/Zle/zle_thingy.c b/Src/Zle/zle_thingy.c
new file mode 100644
index 000000000..c4f2e25e1
--- /dev/null
+++ b/Src/Zle/zle_thingy.c
@@ -0,0 +1,491 @@
+/*
+ * zle_thingy.c - thingies
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_thingy.pro"
+
+/*
+ * Thingies:
+ *
+ * From the user's point of view, a thingy is just a string.  Internally,
+ * the thingy is a struct thingy; these structures are in a hash table
+ * indexed by the string the user sees.  This hash table contains all
+ * thingies currently referenced anywhere; each has a reference count,
+ * and is deleted when it becomes unused.  Being the name of a function
+ * counts as a reference.
+ *
+ * The DISABLED flag on a thingy indicates that it is not the name of a
+ * widget.  This makes it easy to generate completion lists;
+ * looking only at the `enabled' nodes makes the thingy table look like
+ * a table of widgets.
+ */
+
+/* Hashtable of thingies.  Enabled nodes are those that refer to widgets. */
+
+/**/
+HashTable thingytab;
+
+/**********************************/
+/* hashtable management functions */
+/**********************************/
+
+/**/
+static void
+createthingytab(void)
+{
+    thingytab = newhashtable(199, "thingytab", NULL);
+
+    thingytab->hash        = hasher;
+    thingytab->emptytable  = emptythingytab;
+    thingytab->filltable   = NULL;
+    thingytab->addnode     = addhashnode;
+    thingytab->getnode     = gethashnode;
+    thingytab->getnode2    = gethashnode2;
+    thingytab->removenode  = removehashnode;
+    thingytab->disablenode = NULL;
+    thingytab->enablenode  = NULL;
+    thingytab->freenode    = freethingynode;
+    thingytab->printnode   = NULL;
+}
+
+/**/
+static void
+emptythingytab(HashTable ht)
+{
+    /* This will only be called when deleting the thingy table, which *
+     * is only done to unload the zle module.  A normal emptytable()  *
+     * function would free all the thingies, but we don't want to do  *
+     * that because some of them are the known thingies in the fixed  *
+     * `thingies' table.  As the module cleanup code deletes all the  *
+     * keymaps and so on before deleting the thingy table, we can     *
+     * just remove the user-defined widgets and then be sure that     *
+     * *all* the thingies left are the fixed ones.  This has the side *
+     * effect of freeing all resources used by user-defined widgets.  */
+    scanhashtable(thingytab, 0, 0, DISABLED, scanemptythingies, 0);
+}
+
+/**/
+static void
+scanemptythingies(HashNode hn, int flags)
+{
+    Thingy t = (Thingy) hn;
+
+    /* Mustn't unbind internal widgets -- we wouldn't want to free the *
+     * memory they use.                                                */
+    if(!(t->widget->flags & WIDGET_INT))
+	unbindwidget(t, 1);
+}
+
+/**/
+static Thingy
+makethingynode(void)
+{
+    Thingy t = (Thingy) zcalloc(sizeof(*t));
+
+    t->flags = DISABLED;
+    return t;
+}
+
+/**/
+static void
+freethingynode(HashNode hn)
+{
+    Thingy th = (Thingy) hn;
+
+    zsfree(th->nam);
+    zfree(th, sizeof(*th));
+}
+
+/************************/
+/* referencing thingies */
+/************************/
+
+/* It is important to maintain the reference counts on thingies.  When *
+ * copying a reference to a thingy, wrap the copy in refthingy(), to   *
+ * increase its reference count.  When removing a reference,           *
+ * unrefthingy() it.  Both of these functions handle NULL arguments    *
+ * correctly.                                                          */
+
+/**/
+Thingy
+refthingy(Thingy th)
+{
+    if(th)
+	th->rc++;
+    return th;
+}
+
+/**/
+void
+unrefthingy(Thingy th)
+{
+    if(th && !--th->rc)
+	thingytab->freenode(thingytab->removenode(thingytab, th->nam));
+}
+
+/* Use rthingy() to turn a string into a thingy.  It increases the reference *
+ * count, after creating the thingy structure if necessary.                  */
+
+/**/
+Thingy
+rthingy(char *nam)
+{
+    Thingy t = (Thingy) thingytab->getnode2(thingytab, nam);
+
+    if(!t)
+	thingytab->addnode(thingytab, ztrdup(nam), t = makethingynode());
+    return refthingy(t);
+}
+
+/***********/
+/* widgets */
+/***********/
+
+/*
+ * Each widget is attached to one or more thingies.  Each thingy
+ * names either zero or one widgets.  Thingies that name a widget
+ * are treated as being referenced.  The widget type, flags and pointer
+ * are stored in a separate structure pointed to by the thingies.  Each
+ * thingy also has a pointer to the `next' thingy (in a circular list)
+ * that references the same widget.  The DISABLED flag is unset in these
+ * thingies.
+ */
+
+/* Bind a widget to a thingy.  The thingy's reference count must already *
+ * have been incremented.  The widget may already be bound to other      *
+ * thingies; if it is not, then its `first' member must be NULL.  Return *
+ * is 0 on success, or -1 if the thingy has the TH_IMMORTAL flag set.    */
+
+/**/
+static int
+bindwidget(Widget w, Thingy t)
+{
+    if(t->flags & TH_IMMORTAL) {
+	unrefthingy(t);
+	return -1;
+    }
+    if(!(t->flags & DISABLED)) {
+	if(t->widget == w)
+	    return 0;
+	unbindwidget(t, 1);
+    }
+    if(w->first) {
+	t->samew = w->first->samew;
+	w->first->samew = t;
+    } else {
+	w->first = t;
+	t->samew = t;
+    }
+    t->widget = w;
+    t->flags &= ~DISABLED;
+    return 0;
+}
+
+/* Unbind a widget from a thingy.  This decrements the thingy's reference *
+ * count.  The widget will be destroyed if this is its last name.         *
+ * TH_IMMORTAL thingies won't be touched, unless override is non-zero.    *
+ * Returns 0 on success, or -1 if the thingy is protected.  If the thingy *
+ * doesn't actually reference a widget, this is considered successful.    */
+
+/**/
+static int
+unbindwidget(Thingy t, int override)
+{
+    Widget w;
+
+    if(t->flags & DISABLED)
+	return 0;
+    if(!override && (t->flags & TH_IMMORTAL))
+	return -1;
+    w = t->widget;
+    if(t->samew == t)
+	freewidget(w);
+    else {
+	Thingy p;
+	for(p = w->first; p->samew != t; p = p->samew) ;
+	w->first = p;   /* optimised for deletezlefunction() */
+	p->samew = t->samew;
+    }
+    t->flags &= ~TH_IMMORTAL;
+    t->flags |= DISABLED;
+    unrefthingy(t);
+    return 0;
+}
+
+/* Free a widget. */
+
+/**/
+static void
+freewidget(Widget w)
+{
+    if(!(w->flags & WIDGET_INT))
+	zsfree(w->u.fnnam);
+    zfree(w, sizeof(*w));
+}
+
+/* Add am internal widget provided by a module.  The name given is the  *
+ * canonical one, which must not begin with a dot.  The widget is first *
+ * bound to the dotted canonical name; if that name is already taken by *
+ * an internal widget, failure is indicated.  The same widget is then   *
+ * bound to the canonical name, and a pointer to the widget structure   *
+ * returned.                                                            */
+
+/**/
+Widget
+addzlefunction(char *name, ZleIntFunc ifunc, int flags)
+{
+    VARARR(char, dotn, strlen(name) + 2);
+    Widget w;
+    Thingy t;
+
+    if(name[0] == '.')
+	return NULL;
+    dotn[0] = '.';
+    strcpy(dotn + 1, name);
+    t = (Thingy) thingytab->getnode(thingytab, dotn);
+    if(t && (t->flags & TH_IMMORTAL))
+	return NULL;
+    w = zalloc(sizeof(*w));
+    w->flags = WIDGET_INT | flags;
+    w->first = NULL;
+    w->u.fn = ifunc;
+    t = rthingy(dotn);
+    bindwidget(w, t);
+    t->flags |= TH_IMMORTAL;
+    bindwidget(w, rthingy(name));
+    return w;
+}
+
+#ifdef DYNAMIC
+
+/* Delete an internal widget provided by a module.  Don't try to delete *
+ * a widget from the fixed table -- it would be bad.  (Thanks, Egon.)   */
+
+/**/
+void
+deletezlefunction(Widget w)
+{
+    Thingy p, n;
+
+    p = w->first;
+    while(1) {
+	n = p->samew;
+	if(n == p) {
+	    unbindwidget(p, 1);
+	    return;
+	}
+	unbindwidget(p, 1);
+	p = n;
+    }
+}
+
+#endif /* DYNAMIC */
+
+/***************/
+/* zle builtin */
+/***************/
+
+/*
+ * The available operations are:
+ *
+ *   -l   list user-defined widgets (no arguments)
+ *   -D   delete widget names
+ *   -A   link the two named widgets (2 arguments)
+ *   -N   create new user-defined widget (1 or 2 arguments)
+ *        invoke a widget (1 argument)
+ */
+
+/**/
+int
+bin_zle(char *name, char **args, char *ops, int func)
+{
+    static struct opn {
+	char o;
+	int (*func) _((char *, char **, char *, char));
+	int min, max;
+    } const opns[] = {
+	{ 'l', bin_zle_list, 0,  0 },
+	{ 'D', bin_zle_del,  1, -1 },
+	{ 'A', bin_zle_link, 2,  2 },
+	{ 'N', bin_zle_new,  1,  2 },
+	{ 0,   bin_zle_call, 0, -1 },
+    };
+    struct opn const *op, *opp;
+    int n;
+
+    /* select operation and ensure no clashing arguments */
+    for(op = opns; op->o && !ops[op->o]; op++) ;
+    if(op->o)
+	for(opp = op; (++opp)->o; )
+	    if(ops[opp->o]) {
+		zerrnam(name, "incompatible operation selection options",
+		    NULL, 0);
+		return 1;
+	    }
+
+    /* check number of arguments */
+    for(n = 0; args[n]; n++) ;
+    if(!op->o && n != 1) {
+	zerrnam(name, "wrong number of arguments", NULL, 0);
+	return 1;
+    }
+    if(n < op->min) {
+	zerrnam(name, "not enough arguments for -%c", NULL, op->o);
+	return 1;
+    } else if(op->max != -1 && n > op->max) {
+	zerrnam(name, "too many arguments for -%c", NULL, op->o);
+	return 1;
+    }
+
+    /* pass on the work to the operation function */
+    return op->func(name, args, ops, op->o);
+}
+
+/**/
+static int
+bin_zle_list(char *name, char **args, char *ops, char func)
+{
+    scanhashtable(thingytab, 1, 0, DISABLED, scanlistwidgets, ops['L']);
+    return 0;
+}
+
+/**/
+static void
+scanlistwidgets(HashNode hn, int list)
+{
+    Thingy t = (Thingy) hn;
+    Widget w = t->widget;
+
+    if(w->flags & WIDGET_INT)
+	return;
+    if(list) {
+	fputs("zle -N ", stdout);
+	if(t->nam[0] == '-')
+	    fputs("-- ", stdout);
+	quotedzputs(t->nam, stdout);
+	if(strcmp(t->nam, w->u.fnnam)) {
+	    fputc(' ', stdout);
+	    quotedzputs(w->u.fnnam, stdout);
+	}
+    } else {
+	nicezputs(t->nam, stdout);
+	if(strcmp(t->nam, w->u.fnnam)) {
+	    fputs(" (", stdout);
+	    nicezputs(w->u.fnnam, stdout);
+	    fputc(')', stdout);
+	}
+    }
+    putchar('\n');
+}
+
+/**/
+static int
+bin_zle_del(char *name, char **args, char *ops, char func)
+{
+    int ret = 0;
+
+    do {
+	Thingy t = (Thingy) thingytab->getnode(thingytab, *args);
+	if(!t) {
+	    zwarnnam(name, "no such widget `%s'", *args, 0);
+	    ret = 1;
+	} else if(unbindwidget(t, 0)) {
+	    zwarnnam(name, "widget name `%s' is protected", *args, 0);
+	    ret = 1;
+	}
+    } while(*++args);
+    return ret;
+}
+
+/**/
+static int
+bin_zle_link(char *name, char **args, char *ops, char func)
+{
+    Thingy t = (Thingy) thingytab->getnode(thingytab, args[0]);
+
+    if(!t) {
+	zerrnam(name, "no such widget `%s'", args[0], 0);
+	return 1;
+    } else if(bindwidget(t->widget, rthingy(args[1]))) {
+	zerrnam(name, "widget name `%s' is protected", args[1], 0);
+	return 1;
+    }
+    return 0;
+
+}
+
+/**/
+static int
+bin_zle_new(char *name, char **args, char *ops, char func)
+{
+    Widget w = zalloc(sizeof(*w));
+
+    w->flags = 0;
+    w->first = NULL;
+    w->u.fnnam = ztrdup(args[1] ? args[1] : args[0]);
+    if(!bindwidget(w, rthingy(args[0])))
+	return 0;
+    freewidget(w);
+    zerrnam(name, "widget name `%s' is protected", args[0], 0);
+    return 1;
+}
+
+/**/
+static int
+bin_zle_call(char *name, char **args, char *ops, char func)
+{
+    Thingy t;
+
+    if(!zleactive || incompctlfunc) {
+	zerrnam(name, "widgets can only be called when ZLE is active",
+	    NULL, 0);
+	return 1;
+    }
+    t = rthingy(args[0]);
+    PERMALLOC {
+      execzlefunc(t);
+    } LASTALLOC;
+    unrefthingy(t);
+    return 0;
+}
+
+/*******************/
+/* initialiasation */
+/*******************/
+
+/**/
+void
+init_thingies(void)
+{
+    Thingy t;
+
+    createthingytab();
+    for(t = thingies; t->nam; t++)
+	thingytab->addnode(thingytab, t->nam, t);
+}
diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c
new file mode 100644
index 000000000..1aa1a008c
--- /dev/null
+++ b/Src/Zle/zle_tricky.c
@@ -0,0 +1,4015 @@
+/*
+ * zle_tricky.c - expansion and completion
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_tricky.pro"
+
+/* The main part of ZLE maintains the line being edited as binary data, *
+ * but here, where we interface with the lexer and other bits of zsh,   *
+ * we need the line metafied.  The technique used is quite simple: on   *
+ * entry to the expansion/completion system, we metafy the line in      *
+ * place, adjusting ll and cs to match.  All completion and expansion   *
+ * is done on the metafied line.  Immediately before returning, the     *
+ * line is unmetafied again, changing ll and cs back.  (ll and cs might *
+ * have changed during completion, so they can't be merely saved and    *
+ * restored.)  The various indexes into the line that are used in this  *
+ * file only are not translated: they remain indexes into the metafied  *
+ * line.                                                                */
+
+#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>
+
+/* This is used when getting usernames from the NIS. */
+typedef struct {
+    int len;
+    char *s;
+}
+dopestring;
+# endif
+#endif
+
+#define inststr(X) inststrlen((X),1,-1)
+
+/* wb and we hold the beginning/end position of the word we are completing. */
+
+static int wb, we;
+
+/* offs is the cursor position within the tokenized *
+ * current word after removing nulargs.             */
+
+static int offs;
+
+/* These control the type of completion that will be done.  They are    *
+ * affected by the choice of ZLE command and by relevant shell options. */
+
+static int usemenu, useglob;
+
+/* != 0 if we are in the middle of a menu completion */
+
+static int menucmp;
+
+/* A pointer to the current position in the menu-completion array (the one *
+ * that was put in the command line last).                                 */
+
+static char **menucur;
+
+/* menupos is the point (in the command line) where the menu-completion   *
+ * strings are inserted.  menulen is the length of the string that was    *
+ * inserted last.  menuend is the end position of this string in the      *
+ * command line.  menuwe is non-zero if the cursor was at the end of the  *
+ * word (meaning that suffixes should go before the cursor).  menuinsc is *
+ * the length of any suffix that has been temporarily added.              */
+
+static int menupos, menulen, menuend, menuwe, menuinsc;
+
+/* This is used as a flag from get_comp_string() that we are doing *
+ * completion inside a brace expansion.                            */
+
+static int complinbrace;
+
+/* The list of matches.  fmatches contains the matches we first ignore *
+ * because of fignore.                                                 */
+
+static LinkList matches, fmatches;
+
+/* The list of matches turned into an array.  This is used to sort this *
+ * list and when menu-completion is used (directly or via automenu).    */
+
+static char **amatches;
+
+/* The number of matches. */
+
+static int nmatches;
+
+/* A list of user-defined explanations for the completions to be shown *
+ * instead of amatches when listing completions.                       */
+
+static char **aylist;
+
+/* !=0 if we have a valid completion list. */
+
+static int validlist;
+
+/* This flag is non-zero if we are completing a pattern (with globcomplete) */
+
+static int ispattern;
+
+/* Two patterns used when doing glob-completion.  The first one is built *
+ * from the whole word we are completing and the second one from that    *
+ * part of the word that was identified as a possible filename.          */
+
+static Comp patcomp, filecomp;
+
+/* We store the following prefixes/suffixes:                             *
+ * lpre/lsuf -- what's on the line                                       *
+ * rpre/rsuf -- same as lpre/lsuf, but expanded                          *
+ *                                                                       *
+ * ... and if we are completing files, too:                              *
+ * ppre/psuf -- the path prefix/suffix                                   *
+ * fpre/fsuf -- prefix/suffix of the pathname component the cursor is in *
+ * prpre     -- ppre in expanded form usable for opendir                 *
+ *                                                                       *
+ * The integer variables hold the lengths of lpre, lsuf, rpre, rsuf,     *
+ * fpre, and fsuf.  noreal is non-zero if we have rpre/rsuf.             */
+
+static char *lpre, *lsuf;
+static char *rpre, *rsuf;
+static char *ppre, *psuf, *prpre;
+static char *fpre, *fsuf;
+static int lpl, lsl, rpl, rsl, fpl, fsl;
+static int noreal;
+
+/* This is used when completing after `$' and holds the whole prefix,   *
+ * used in do_single() to check whether the word expands to a directory *
+ * name (in that case and if autoparamslash is set, we add a `/').      *
+ * qparampre is the same but quoted. The length of it is in qparprelen. *
+ * parambr is != 0 if the parameter name is in braces.                  */
+
+static char *parampre = NULL, *qparampre = NULL;
+static int qparprelen, parambr;
+
+/* This is either zero or equal to the special character the word we are *
+ * trying to complete starts with (e.g. Tilde or Equals).                */
+
+static char ic;
+
+/* These hold the minimum common prefix/suffix lengths (normal and for *
+ * fignore ignored).                                                   */
+
+static int ab, ae, fab, fae;
+
+/* This variable says what we are currently adding to the list of matches. */
+
+static int addwhat;
+
+/* firstm hold the first match we found, shortest contains the shortest *
+ * one (normal and for fignore ignored).                                */
+
+static char *firstm, *shortest, *ffirstm, *fshortest;
+
+/* This holds the word we are completing in quoted from. */
+
+static char *qword;
+
+/* This is the length of the shortest match we found (normal and for *
+ * fignore ignored).                                                 */
+
+static int shortl, fshortl;
+
+/* This is non-zero if we are doing a menu-completion and this is not the *
+ * first call (e.g. when automenu is set and menu-completion was entered  *
+ * due to this). */
+
+static int amenu;
+
+/* Find out if we have to insert a tab (instead of trying to complete). */
+
+/**/
+static int
+usetab(void)
+{
+    unsigned char *s = line + cs - 1;
+
+    for (; s >= line && *s != '\n'; s--)
+	if (*s != '\t' && *s != ' ')
+	    return 0;
+    return 1;
+}
+
+#define COMP_COMPLETE 0
+#define COMP_LIST_COMPLETE 1
+#define COMP_SPELL 2
+#define COMP_EXPAND 3
+#define COMP_EXPAND_COMPLETE 4
+#define COMP_LIST_EXPAND 5
+#define COMP_ISEXPAND(X) ((X) >= COMP_EXPAND)
+
+/**/
+void
+completeword(void)
+{
+    usemenu = isset(MENUCOMPLETE);
+    useglob = isset(GLOBCOMPLETE);
+    if (c == '\t' && usetab())
+	selfinsert();
+    else
+	docomplete(COMP_COMPLETE);
+}
+
+/**/
+void
+menucomplete(void)
+{
+    usemenu = 1;
+    useglob = isset(GLOBCOMPLETE);
+    if (c == '\t' && usetab())
+	selfinsert();
+    else
+	docomplete(COMP_COMPLETE);
+}
+
+/**/
+void
+listchoices(void)
+{
+    usemenu = isset(MENUCOMPLETE);
+    useglob = isset(GLOBCOMPLETE);
+    docomplete(COMP_LIST_COMPLETE);
+}
+
+/**/
+void
+spellword(void)
+{
+    usemenu = useglob = 0;
+    docomplete(COMP_SPELL);
+}
+
+/**/
+void
+deletecharorlist(void)
+{
+    char **mc = menucur;
+
+    usemenu = isset(MENUCOMPLETE);
+    useglob = isset(GLOBCOMPLETE);
+    if (cs != ll)
+	deletechar();
+    else
+	docomplete(COMP_LIST_COMPLETE);
+
+    menucur = mc;
+}
+
+/**/
+void
+expandword(void)
+{
+    usemenu = useglob = 0;
+    if (c == '\t' && usetab())
+	selfinsert();
+    else
+	docomplete(COMP_EXPAND);
+}
+
+/**/
+void
+expandorcomplete(void)
+{
+    usemenu = isset(MENUCOMPLETE);
+    useglob = isset(GLOBCOMPLETE);
+    if (c == '\t' && usetab())
+	selfinsert();
+    else
+	docomplete(COMP_EXPAND_COMPLETE);
+}
+
+/**/
+void
+menuexpandorcomplete(void)
+{
+    usemenu = 1;
+    useglob = isset(GLOBCOMPLETE);
+    if (c == '\t' && usetab())
+	selfinsert();
+    else
+	docomplete(COMP_EXPAND_COMPLETE);
+}
+
+/**/
+void
+listexpand(void)
+{
+    usemenu = isset(MENUCOMPLETE);
+    useglob = isset(GLOBCOMPLETE);
+    docomplete(COMP_LIST_EXPAND);
+}
+
+/**/
+void
+reversemenucomplete(void)
+{
+    if (!menucmp) {
+	menucomplete();
+	return;
+    }
+    HEAPALLOC {
+	if (menucur == amatches)
+	    menucur = amatches + nmatches - 1;
+	else
+	    menucur--;
+	metafy_line();
+	do_single(*menucur);
+	unmetafy_line();
+    } LASTALLOC;
+}
+
+/* Accepts the current completion and starts a new arg, *
+ * with the next completions. This gives you a way to   *
+ * accept several selections from the list of matches.  */
+
+/**/
+void
+acceptandmenucomplete(void)
+{
+    if (!menucmp) {
+	feep();
+	return;
+    }
+    cs = menuend + menuinsc;
+    inststrlen(" ", 1, 1);
+    if (qparampre)
+	inststrlen(qparampre, 1, qparprelen);
+    if (lpre && !ispattern)
+	inststrlen(lpre, 1, -1);
+    if (lsuf && !ispattern)
+	inststrlen(lsuf, 0, -1);
+    menupos = cs;
+    menuend = cs + (lsuf ? strlen(lsuf) : 0);
+    menulen = 0;
+    menuinsc = 0;
+    menuwe = 1;
+    menucomplete();
+}
+
+/* These are flags saying if we are completing in the command *
+ * position or in a redirection.                              */
+
+static int lincmd, linredir;
+
+/* Non-zero if the last completion done was ambiguous (used to find   *
+ * out if AUTOMENU should start).  More precisely, it's nonzero after *
+ * successfully doing any completion, unless the completion was       *
+ * unambiguous and did not cause the display of a completion list.    *
+ * From the other point of view, it's nonzero iff AUTOMENU (if set)   *
+ * should kick in on another completion.                              */
+
+static int lastambig;
+
+/* This describes some important things collected during the last *
+ * completion.  Its value is zero or the inclusive OR of some of  *
+ * the HAS_* things below.                                        */
+
+static int haswhat;
+
+/* We have a suffix to add (given with compctl -S). */
+
+#define HAS_SUFFIX  1
+
+/* We have filenames in the completion list. */
+
+#define HAS_FILES   2
+
+/* We have other things than files in the completion list.  If this is *
+ * not set but HAS_FILES is, we probably put the file type characters  *
+ * in the completion list (if listtypes is set) and we attempt to add  *
+ * a slash to completed directories.                                   */
+
+#define HAS_MISC    4
+
+/* This is set if we have filenames in the completion list that were *
+ * generated by a globcompletion pattern.                            */
+
+#define HAS_PATHPAT 8
+
+
+/* This holds the naem of the current command (used to find the right *
+ * compctl).                                                          */
+
+static char *cmdstr;
+
+
+/* Check if the given string is the name of a parameter and if this *
+ * parameter is one worth expanding.                                */
+
+/**/
+static int
+checkparams(char *p)
+{
+    int t0, n, l = strlen(p), e = 0;
+    struct hashnode *hn;
+
+    for (t0 = paramtab->hsize - 1, n = 0; n < 2 && t0 >= 0; t0--)
+	for (hn = paramtab->nodes[t0]; n < 2 && hn; hn = hn->next)
+	    if (pfxlen(p, hn->nam) == l) {
+		n++;
+		if (strlen(hn->nam) == l)
+		    e = 1;
+	    }
+    return (n == 1) ? (getsparam(p) != NULL) :
+	(!menucmp && e && isset(RECEXACT));
+}
+
+/* Check if the given string has wildcards.  The difficulty is that we *
+ * have to treat things like job specifications (%...) and parameter   *
+ * expressions correctly.                                              */
+
+/**/
+static int
+cmphaswilds(char *str)
+{
+    if ((*str == Inbrack || *str == Outbrack) && !str[1])
+	return 0;
+
+    /* If a leading % is immediately followed by ?, then don't *
+     * treat that ? as a wildcard.  This is so you don't have  *
+     * to escape job references such as %?foo.                 */
+    if (str[0] == '%' && str[1] ==Quest)
+	str += 2;
+
+    for (; *str;) {
+	if (*str == String || *str == Qstring) {
+	    /* A parameter expression. */
+
+	    if (*++str == Inbrace)
+		skipparens(Inbrace, Outbrace, &str);
+	    else if (*str == String || *str == Qstring)
+		str++;
+	    else {
+		/* Skip all the things a parameter expression might start *
+		 * with (before we come to the parameter name).           */
+		for (; *str; str++)
+		    if (*str != '^' && *str != Hat &&
+			*str != '=' && *str != Equals &&
+			*str != '~' && *str != Tilde)
+			break;
+		if (*str == '#' || *str == Pound)
+		    str++;
+		/* Star and Quest are parameter names here, not wildcards */
+		if (*str == Star || *str == Quest)
+		    str++;
+	    }
+	} else {
+	    /* Not a parameter expression so we check for wildcards */
+	    if (((*str == Pound || *str == Hat) && isset(EXTENDEDGLOB)) ||
+		*str == Star || *str == Bar || *str == Quest ||
+		!skipparens(Inbrack, Outbrack, &str) ||
+		!skipparens(Inang,   Outang,   &str) ||
+		(unset(IGNOREBRACES) &&
+		 !skipparens(Inbrace, Outbrace, &str)) ||
+		(*str == Inpar && str[1] == ':' &&
+		 !skipparens(Inpar, Outpar, &str)))
+		return 1;
+	    if (*str)
+		str++;
+	}
+    }
+    return 0;
+}
+
+/* The main entry point for completion. */
+
+/**/
+static void
+docomplete(int lst)
+{
+    char *s, *ol;
+    int olst = lst, chl = 0, ne = noerrs, ocs;
+
+    /* If we are doing a menu-completion... */
+
+    if (menucmp && lst != COMP_LIST_EXPAND) {
+	do_menucmp(lst);
+	return;
+    }
+
+    /* Check if we have to start a menu-completion (via automenu). */
+
+    if ((amenu = (isset(AUTOMENU) && lastambig)))
+	usemenu = 1;
+
+    /* Expand history references before starting completion.  If anything *
+     * changed, do no more.                                               */
+
+    if (doexpandhist())
+	return;
+
+    metafy_line();
+
+    ocs = cs;
+    if (!isfirstln && chline != NULL) {
+	/* If we are completing in a multi-line buffer (which was not  *
+	 * taken from the history), we have to prepend the stuff saved *
+	 * in chline to the contents of line.                          */
+
+	ol = dupstring((char *)line);
+	/* Make sure that chline is zero-terminated. */
+	*hptr = '\0';
+	cs = 0;
+	inststr(chline);
+	chl = cs;
+	cs += ocs;
+    } else
+	ol = NULL;
+    inwhat = IN_NOTHING;
+    qword = NULL;
+    /* Get the word to complete. */
+    noerrs = 1;
+    s = get_comp_string();
+    DPUTS(wb < 0 || cs < wb || cs > we,
+	  "BUG: 0 <= wb <= cs <= we is not true!");
+    noerrs = ne;
+    /* For vi mode, reset the start-of-insertion pointer to the beginning *
+     * of the word being completed, if it is currently later.  Vi itself  *
+     * would never change the pointer in the middle of an insertion, but  *
+     * then vi doesn't have completion.  More to the point, this is only  *
+     * an emulation.                                                      */
+    if (viinsbegin > ztrsub((char *) line + wb, (char *) line))
+	viinsbegin = ztrsub((char *) line + wb, (char *) line);
+    /* If we added chline to the line buffer, reset the original contents. */
+    if (ol) {
+	cs -= chl;
+	wb -= chl;
+	we -= chl;
+	if (wb < 0) {
+	    strcpy((char *) line, ol);
+	    ll = strlen((char *) line);
+	    cs = ocs;
+	    unmetafy_line();
+	    feep();
+	    return;
+	}
+	ocs = cs;
+	cs = 0;
+	foredel(chl);
+	cs = ocs;
+    }
+    freeheap();
+    /* Save the lexer state, in case the completion code uses the lexer *
+     * somewhere (e.g. when processing a compctl -s flag).              */
+    lexsave();
+    if (inwhat == IN_ENV)
+	lincmd = 0;
+    if (s) {
+	if (lst == COMP_EXPAND_COMPLETE) {
+	    /* Check if we have to do expansion or completion. */
+	    char *q = s;
+
+	    if (*q == Equals) {
+		/* The word starts with `=', see if we can expand it. */
+		q = s + 1;
+		if (cmdnamtab->getnode(cmdnamtab, q) || hashcmd(q, pathchecked))
+		    if (isset(RECEXACT))
+			lst = COMP_EXPAND;
+		    else {
+			int t0, n = 0;
+			char *fc;
+			struct hashnode *hn;
+
+			for (t0 = cmdnamtab->hsize - 1; t0 >= 0; t0--)
+			    for (hn = cmdnamtab->nodes[t0]; hn;
+				 hn = hn->next) {
+				if (strpfx(q, hn->nam) && (fc = findcmd(hn->nam))) {
+				    zsfree(fc);
+				    n++;
+				}
+				if (n == 2)
+				    break;
+			    }
+
+			if (n == 1)
+			    lst = COMP_EXPAND;
+		    }
+	    }
+	    if (lst == COMP_EXPAND_COMPLETE)
+		do {
+		    /* check if there is a parameter expresiion. */
+		    for (; *q && *q != String; q++);
+		    if (*q == String && q[1] != Inpar && q[1] != Inbrack) {
+			if (*++q == Inbrace) {
+			    if (! skipparens(Inbrace, Outbrace, &q) &&
+				q == s + cs - wb)
+				lst = COMP_EXPAND;
+			} else {
+			    char *t, sav, sav2;
+
+			    /* Skip the things parameter expressions might *
+			     * start with (the things before the parameter *
+			     * name).                                      */
+			    for (; *q; q++)
+				if (*q != '^' && *q != Hat &&
+				    *q != '=' && *q != Equals &&
+				    *q != '~' && *q != Tilde)
+				    break;
+			    if ((*q == '#' || *q == Pound || *q == '+') &&
+				q[1] != String)
+				q++;
+
+			    sav2 = *(t = q);
+			    if (*q == Quest || *q == Star || *q == String ||
+				*q == Qstring)
+				*q = ztokens[*q - Pound], ++q;
+			    else if (*q == '?' || *q == '*' || *q == '$' ||
+				     *q == '-' || *q == '!' || *q == '@')
+				q++;
+			    else if (idigit(*q))
+				do q++; while (idigit(*q));
+			    else
+				while (iident(*q))
+				    q++;
+			    sav = *q;
+			    *q = '\0';
+			    if (cs - wb == q - s &&
+				(idigit(sav2) || checkparams(t)))
+				lst = COMP_EXPAND;
+			    *q = sav;
+			    *t = sav2;
+			}
+			if (lst != COMP_EXPAND)
+			    lst = COMP_COMPLETE;
+		    } else
+			break;
+		} while (q < s + cs - wb);
+	    if (lst == COMP_EXPAND_COMPLETE) {
+		/* If it is still not clear if we should use expansion or   *
+		 * completion and there is a `$' or a backtick in the word, *
+		 * than do expansion.                                       */
+		for (q = s; *q; q++)
+		    if (*q == Tick || *q == Qtick ||
+			*q == String || *q == Qstring)
+			break;
+		lst = *q ? COMP_EXPAND : COMP_COMPLETE;
+	    }
+	    /* And do expansion if there are wildcards and globcomplete is *
+	     * not used.                                                   */
+	    if (unset(GLOBCOMPLETE) && cmphaswilds(s))
+		lst = COMP_EXPAND;
+	}
+	if (lincmd && (inwhat == IN_NOTHING))
+	    inwhat = IN_CMD;
+
+	if (lst == COMP_SPELL) {
+	    char *x, *q;
+
+	    for (q = s; *q; q++)
+		if (INULL(*q))
+		    *q = Nularg;
+	    cs = wb;
+	    foredel(we - wb);
+	    HEAPALLOC {
+		untokenize(x = dupstring(s));
+		if (*s == Tilde || *s == Equals || *s == String)
+		    *x = *s;
+		spckword(&x, 0, lincmd, 0);
+	    } LASTALLOC;
+	    untokenize(x);
+	    inststr(x);
+	} else if (COMP_ISEXPAND(lst)) {
+	    /* Do expansion. */
+	    char *ol = (olst == COMP_EXPAND_COMPLETE) ?
+		dupstring((char *)line) : (char *)line;
+	    int ocs = cs, ne = noerrs;
+
+	    noerrs = 1;
+	    doexpansion(s, lst, olst, lincmd);
+	    lastambig = 0;
+	    noerrs = ne;
+
+	    /* If expandorcomplete was invoked and the expansion didn't *
+	     * change the command line, do completion.                  */
+	    if (olst == COMP_EXPAND_COMPLETE &&
+		!strcmp(ol, (char *)line)) {
+		char *p;
+
+		cs = ocs;
+		errflag = 0;
+
+		p = s;
+		if (*p == Tilde || *p == Equals)
+		    p++;
+		for (; *p; p++)
+		    if (itok(*p))
+			if (*p != String && *p != Qstring)
+			    *p = ztokens[*p - Pound];
+			else if (p[1] == Inbrace)
+			    p++, skipparens(Inbrace, Outbrace, &p);
+		docompletion(s, lst, lincmd, 1);
+	    }
+	} else
+	    /* Just do completion. */
+	    docompletion(s, lst, lincmd, 0);
+	zsfree(s);
+    }
+    /* Reset the lexer state, pop the heap. */
+    lexrestore();
+    popheap();
+    zsfree(qword);
+    unmetafy_line();
+}
+
+/* Do completion, given that we are in the middle of a menu completion.  We *
+ * don't need to generate a list of matches, because that's already been    *
+ * done by previous commands.  We will either list the completions, or      *
+ * insert the next completion.                                              */
+
+/**/
+static void
+do_menucmp(int lst)
+{
+    /* Just list the matches if the list was requested. */
+    if (lst == COMP_LIST_COMPLETE) {
+	showinglist = -2;
+	return;
+    }
+    /* Otherwise go to the next match in the array... */
+    HEAPALLOC {
+	if (!*++menucur)
+	    menucur = amatches;
+	/* ... and insert it into the command line. */
+	metafy_line();
+	do_single(*menucur);
+	unmetafy_line();
+    } LASTALLOC;
+}
+
+/* 1 if we are completing in a string */
+static int instring;
+
+/* 1 if we are completing the prefix */
+static int comppref;
+
+/* This function inserts an `x' in the command line at the cursor position. *
+ *                                                                          *
+ * Oh, you want to know why?  Well, if completion is tried somewhere on an  *
+ * empty part of the command line, the lexer code would normally not be     *
+ * able to give us the `word' we want to complete, since there is no word.  *
+ * But we need to call the lexer to find out where we are (and for which    *
+ * command we are completing and such things).  So we temporarily add a `x' *
+ * (any character without special meaning would do the job) at the cursor   *
+ * position, than the lexer gives us the word `x' and its beginning and end *
+ * positions and we can remove the `x'.                                     *
+ *									    *
+ * If we are just completing the prefix (comppref set), we also insert a    *
+ * space after the x to end the word.  We never need to remove the space:   *
+ * anywhere we are able to retrieve a word for completion it will be	    *
+ * discarded as whitespace.  It has the effect of making any suffix	    *
+ * referrable to as the next word on the command line when indexing	    *
+ * from a completion function.                                              */
+
+/**/
+static void
+addx(char **ptmp)
+{
+    int addspace = 0;
+
+    if (!line[cs] || line[cs] == '\n' ||
+	(iblank(line[cs]) && (!cs || line[cs-1] != '\\')) ||
+	line[cs] == ')' || line[cs] == '`' ||
+	(instring && (line[cs] == '"' || line[cs] == '\'')) ||
+	(addspace = (comppref && !iblank(line[cs])))) {
+	*ptmp = (char *)line;
+	line = (unsigned char *)halloc(strlen((char *)line) + 3 + addspace);
+	memcpy(line, *ptmp, cs);
+	line[cs] = 'x';
+	if (addspace)
+	    line[cs+1] = ' ';
+	strcpy((char *)line + cs + 1 + addspace, (*ptmp) + cs);
+	addedx = 1 + addspace;
+    } else {
+	addedx = 0;
+	*ptmp = NULL;
+    }
+}
+
+/* Like dupstring, but add an extra space at the end of the string. */
+
+/**/
+static char *
+dupstrspace(const char *str)
+{
+    int len = strlen((char *)str);
+    char *t = (char *)ncalloc(len + 2);
+    strcpy(t, str);
+    strcpy(t+len, " ");
+    return t;
+}
+
+/* These functions metafy and unmetafy the ZLE buffer, as described at the *
+ * top of this file.  Note that ll and cs are translated.  They *must* be  *
+ * called in matching pairs, around all the expansion/completion code.     *
+ * Currently, there are four pairs: in history expansion, in the main      *
+ * completion function, and one in each of the middle-of-menu-completion   *
+ * functions (there's one for each direction).                             */
+
+/**/
+static void
+metafy_line(void)
+{
+    int len = ll;
+    char *s;
+
+    for (s = (char *) line; s < (char *) line + ll;)
+	if (imeta(*s++))
+	    len++;
+    sizeline(len);
+    (void) metafy((char *) line, ll, META_NOALLOC);
+    ll = len;
+    cs = metalen((char *) line, cs);
+}
+
+/**/
+static void
+unmetafy_line(void)
+{
+    cs = ztrsub((char *) line + cs, (char *) line);
+    (void) unmetafy((char *) line, &ll);
+}
+
+/* Lasciate ogni speranza.                                                  *
+ * This function is a nightmare.  It works, but I'm sure that nobody really *
+ * understands why.  The problem is: to make it cleaner we would need       *
+ * changes in the lexer code (and then in the parser, and then...).         */
+
+/**/
+static char *
+get_comp_string(void)
+{
+    int t0, tt0, i, j, k, cp, rd, sl, ocs;
+    char *s = NULL, *linptr, *tmp, *p, *tt = NULL;
+
+    complinbrace = 0;
+    /* This global flag is used to signal the lexer code if it should *
+     * expand aliases or not.                                         */
+    noaliases = isset(COMPLETEALIASES);
+
+    /* Find out if we are somewhere in a `string', i.e. inside '...', *
+     * "...", `...`, or ((...)).                                      */
+
+    for (i = j = k = 0, p = (char *)line; p < (char *)line + cs; p++)
+	if (*p == '`' && !(k & 1))
+	    i++;
+	else if (*p == '\"' && !(k & 1) && !(i & 1))
+	    j++;
+	else if (*p == '\'' && !(j & 1))
+	    k++;
+	else if (*p == '\\' && p[1] && !(k & 1))
+	    p++;
+    instring = (j & 1) ? 2 : (k & 1);
+    addx(&tmp);
+    if (instring) {
+	/* Yes, we are in a string. */
+	if (!tmp) {
+	    tmp = (char *)line;
+	    line = (unsigned char *) dupstring((char *) line);
+	}
+	/* Now remove the quotes.                                   *
+	 * What??  Why that??  Well, we want to be able to complete *
+	 * inside strings.  The lexer code gives us no help here,   *
+	 * so we have to cheat.  We remove the quotes, the lexer    *
+	 * will than treat the words in the strings normally and we *
+	 * can complete them.                                       *
+	 * This is completely the wrong thing to do, but it's       *
+	 * occasionally useful, and we can't handle quotes properly *
+	 * yet anyway.                                              */
+	for (p = (char *)line; *p; p++)
+	    if (*p == '"' || *p == '\'')
+		*p = ' ';
+    }
+    linptr = (char *)line;
+    pushheap();
+    HEAPALLOC {
+      start:
+	inwhat = IN_NOTHING;
+	/* Now set up the lexer and start it. */
+	parbegin = parend = -1;
+	lincmd = incmdpos;
+	linredir = inredir;
+	zsfree(cmdstr);
+	cmdstr = NULL;
+	zleparse = 1;
+	clwpos = -1;
+	lexsave();
+	inpush(dupstrspace((char *) linptr), 0, NULL);
+	strinbeg();
+	stophist = 2;
+	i = tt0 = cp = rd = 0;
+
+	/* This loop is possibly the wrong way to do this.  It goes through *
+	 * the previously massaged command line using the lexer.  It stores *
+	 * each token in each command (commands being regarded, roughly, as *
+	 * being separated by tokens | & &! |& || &&).  The loop stops when *
+	 * the end of the command containing the cursor is reached.  It's a *
+	 * simple way to do things, but suffers from an inability to        *
+	 * distinguish actual command arguments from, for example,          *
+	 * filenames in redirections.  (But note that code elsewhere checks *
+	 * if we are completing *in* a redirection.)  The only way to fix   *
+	 * this would be to pass the command line through the parser too,   *
+	 * and get the arguments that way.  Maybe in 3.1...                 */
+	do {
+	    lincmd = incmdpos;
+	    linredir = inredir;
+	    /* Get the next token. */
+	    ctxtlex();
+	    if (tok == DINPAR)
+		tokstr = NULL;
+
+	    /* We reached the end. */
+	    if (tok == ENDINPUT)
+		break;
+	    if (tok == BAR    || tok == AMPER     ||
+		tok == BARAMP || tok == AMPERBANG ||
+		((tok == DBAR || tok == DAMPER) && !incond)) {
+		/* This is one of the things that separate commands.  If we  *
+		 * already have the things we need (e.g. the token strings), *
+		 * leave the loop.                                           */
+		if (tt)
+		    break;
+		/* Otherwise reset the variables we are collecting data in. */
+		i = tt0 = cp = rd = 0;
+	    }
+	    if (lincmd && tok == STRING) {
+		/* The lexer says, this token is in command position, so *
+		 * store the token string (to find the right compctl).   */
+		zsfree(cmdstr);
+		cmdstr = ztrdup(tokstr);
+		i = 0;
+	    }
+	    if (!zleparse && !tt0) {
+		/* This is done when the lexer reached the word the cursor is on. */
+		tt = tokstr ? dupstring(tokstr) : NULL;
+		/* If we added a `x', remove it. */
+		if (addedx && tt)
+		    chuck(tt + cs - wb);
+		tt0 = tok;
+		/* Store the number of this word. */
+		clwpos = i;
+		cp = lincmd;
+		rd = linredir;
+		if (inwhat == IN_NOTHING && incond)
+		    inwhat = IN_COND;
+	    }
+	    if (!tokstr)
+		continue;
+	    /* We need to store the token strings of all words (for some of *
+	     * the more complicated compctl -x things).  They are stored in *
+	     * the clwords array.  Make this array big enough.              */
+	    if (i + 1 == clwsize) {
+		int n;
+		clwords = (char **)realloc(clwords,
+					   (clwsize *= 2) * sizeof(char *));
+		for(n = clwsize; --n > i; )
+		    clwords[n] = NULL;
+	    }
+	    zsfree(clwords[i]);
+	    /* And store the current token string. */
+	    clwords[i] = ztrdup(tokstr);
+	    sl = strlen(tokstr);
+	    /* Sometimes the lexer gives us token strings ending with *
+	     * spaces we delete the spaces.                           */
+	    while (sl && clwords[i][sl - 1] == ' ' &&
+		   (sl < 2 || (clwords[i][sl - 2] != Bnull &&
+			       clwords[i][sl - 2] != Meta)))
+		clwords[i][--sl] = '\0';
+	    /* If this is the word the cursor is in and we added a `x', *
+	     * remove it.                                               */
+	    if (clwpos == i++ && addedx)
+		chuck(&clwords[i - 1][((cs - wb) >= sl) ?
+				     (sl - 1) : (cs - wb)]);
+	} while (tok != LEXERR && tok != ENDINPUT &&
+		 (tok != SEPER || (zleparse && !tt0)));
+	/* Calculate the number of words stored in the clwords array. */
+	clwnum = (tt || !i) ? i : i - 1;
+	zsfree(clwords[clwnum]);
+	clwords[clwnum] = NULL;
+	t0 = tt0;
+	lincmd = cp;
+	linredir = rd;
+	strinend();
+	inpop();
+	errflag = zleparse = 0;
+	if (parbegin != -1) {
+	    /* We are in command or process substitution */
+	    if (parend >= 0 && !tmp)
+		line = (unsigned char *) dupstring(tmp = (char *)line);
+	    linptr = (char *) line + ll + addedx - parbegin + 1;
+	    if (parend >= 0) {
+		ll -= parend;
+		line[ll + addedx] = '\0';
+	    }
+	    lexrestore();
+	    goto start;
+	}
+
+	if (inwhat == IN_MATH)
+	    s = NULL;
+	else if (!t0 || t0 == ENDINPUT) {
+	    /* There was no word (empty line). */
+	    s = ztrdup("");
+	    we = wb = cs;
+	    clwpos = clwnum;
+	    t0 = STRING;
+	} else if (t0 == STRING) {
+	    /* We found a simple string. */
+	    s = ztrdup(clwords[clwpos]);
+	} else if (t0 == ENVSTRING) {
+	    /* The cursor was inside a parameter assignment. */
+	    for (s = tt; iident(*s); s++);
+	    if (skipparens(Inbrack, Outbrack, &s) > 0 || s > tt + cs - wb)
+		s = NULL, inwhat = IN_MATH;
+	    else if (*s == '=') {
+		s++;
+		wb += s - tt;
+		t0 = STRING;
+		s = ztrdup(s);
+		inwhat = IN_ENV;
+	    }
+	    lincmd = 1;
+	}
+	if (we > ll)
+	    we = ll;
+	tt = (char *)line;
+	if (tmp) {
+	    line = (unsigned char *)tmp;
+	    ll = strlen((char *)line);
+	}
+	if (t0 != STRING && inwhat != IN_MATH) {
+	    if (tmp) {
+		tmp = NULL;
+		linptr = (char *)line;
+		lexrestore();
+		goto start;
+	    }
+	    feep();
+	    noaliases = 0;
+	    lexrestore();
+	    LASTALLOC_RETURN NULL;
+	}
+
+	noaliases = 0;
+
+	/* Check if we are in an array subscript.  We simply assume that  *
+	 * we are in a subscript if we are in brackets.  Correct solution *
+	 * is very difficult.  This is quite close, but gets things like  *
+	 * foo[_ wrong (note no $).  If we are in a subscript, treat it   *
+	 * as being in math.                                              */
+	if (inwhat != IN_MATH) {
+	    int i = 0;
+	    for (tt = s; ++tt < s + cs - wb;)
+		if (*tt == Inbrack)
+		    i++;
+		else if (i && *tt == Outbrack)
+		    i--;
+	    if (i)
+		inwhat = IN_MATH;
+	}
+	if (inwhat == IN_MATH) {
+	    /* In mathematical expression, we complete parameter names (even *
+	     * if they don't have a `$' in front of them).  So we have to    *
+	     * find that name.                                               */
+	    for (we = cs; iident(line[we]); we++);
+	    for (wb = cs; --wb >= 0 && iident(line[wb]););
+	    wb++;
+	    zsfree(s);
+	    s = zalloc(we - wb + 1);
+	    strncpy(s, (char *) line + wb, we - wb);
+	    s[we - wb] = '\0';
+	}
+	/* This variable will hold the current word in quoted form. */
+	qword = ztrdup(s);
+	/* While building the quoted form, we also clean up the command line. */
+	offs = cs - wb;
+	for (p = s, tt = qword, i = wb; *p; p++, tt++, i++)
+	    if (INULL(*p)) {
+		if (i < cs)
+		    offs--;
+		if (p[1] || *p != Bnull) {
+		    if (*p == Bnull) {
+			*tt = '\\';
+			if (cs == i + 1)
+			    cs++, offs++;
+		    } else {
+			ocs = cs;
+			cs = i;
+			foredel(1);
+			chuck(tt--);
+			if ((cs = ocs) > i--)
+			    cs--;
+			we--;
+		    }
+		} else {
+		    ocs = cs;
+		    *tt = '\0';
+		    cs = we;
+		    backdel(1);
+		    if (ocs == we)
+			cs = we - 1;
+		    else
+			cs = ocs;
+		    we--;
+		}
+		chuck(p--);
+	    }
+
+	if (!isset(IGNOREBRACES)) {
+	    /* Try and deal with foo{xxx etc.; only simple cases
+	     * (only one inbrace, completion after inbrace and before outbrace
+	     * if present).
+	     */
+	    int myoffs = isset(COMPLETEINWORD) ? offs : strlen(s);
+	    tt = NULL;
+	    /* First check the conditions mentioned above
+	     * and locate opening brace
+	     */
+	    for (i = 0, p = s; *p; p++, i++) {
+		/* careful, ${... is not a brace expansion...
+		 * in fact, if it's got a substitution in it's too
+		 * hard for us anyway.  sorry.
+		 */
+		if (*p == String || *p == Qstring) {
+		    tt = NULL;
+		    break;
+		} else if (*p == Inbrace) {
+		    if (tt) {
+			/* too many inbraces */
+			tt = NULL;
+			break;
+		    }
+		    tt = p;
+		} else if (*p == Outbrace && i < myoffs) {
+		    /* outbrace is before cursor pos, so nothing to complete */
+		    tt = NULL;
+		    break;
+		}
+	    }
+
+	    if (tt && tt < s + myoffs) {
+		/* Braces are go:  delete opening brace */
+		char *com = NULL;
+		chuck(tt);
+		offs--;
+		myoffs--;
+
+		/* Look for text up to comma before cursor and delete it */
+		for (i = tt - s, p = tt; *p && i < myoffs; p++, i++)
+		    if (*p == Comma)
+			com = p;
+		if (com) {
+		    i = com - tt + 1;
+		    while (i--)
+			chuck(tt), offs--, myoffs--;
+		}
+
+		/* Look for text between subsequent comma
+		 * and closing brace or end of string and delete it
+		 */
+		for (p = s + myoffs; *p && *p != Outbrace; p++)
+		    if (*p == Comma) {
+			while (*p && *p != Outbrace)
+			    chuck(p);
+			break;
+		    }
+		if (*p == Outbrace)
+		    chuck(p);
+		else {
+		    /* we are still waiting for an outbrace and maybe commas */
+		    complinbrace = 1;
+		}
+	    }
+	}
+
+    } LASTALLOC;
+    lexrestore();
+
+    return (char *)s;
+}
+
+/* Expand the current word. */
+
+/**/
+static void
+doexpansion(char *s, int lst, int olst, int explincmd)
+{
+    LinkList vl;
+    char *ss;
+
+    DPUTS(useheap, "BUG: useheap in doexpansion()");
+    HEAPALLOC {
+	pushheap();
+	vl = newlinklist();
+	ss = dupstring(s);
+	addlinknode(vl, ss);
+	prefork(vl, 0);
+	if (errflag)
+	    goto end;
+	if ((lst == COMP_LIST_EXPAND) || (lst == COMP_EXPAND)) {
+	    int ng = opts[NULLGLOB];
+
+	    opts[NULLGLOB] = 1;
+	    globlist(vl);
+	    opts[NULLGLOB] = ng;
+	}
+	if (errflag)
+	    goto end;
+	if (empty(vl) || !*(char *)peekfirst(vl)) {
+	    if (!noerrs)
+		feep();
+	    goto end;
+	}
+	if (peekfirst(vl) == (void *) ss ||
+		(olst == COMP_EXPAND_COMPLETE &&
+		 !nextnode(firstnode(vl)) && *s == Tilde &&
+		 (ss = dupstring(s), filesubstr(&ss, 0)) &&
+		 !strcmp(ss, (char *)peekfirst(vl)))) {
+	    /* If expansion didn't change the word, try completion if *
+	     * expandorcomplete was called, otherwise, just beep.     */
+	    if (lst == COMP_EXPAND_COMPLETE)
+		docompletion(s, COMP_COMPLETE, explincmd, 0);
+	    else
+		feep();
+	    goto end;
+	}
+	if (lst == COMP_LIST_EXPAND) {
+	    /* Only the list of expansions was requested. */
+	    listlist(vl);
+	    goto end;
+	}
+	/* Remove the current word and put the expansions there. */
+	cs = wb;
+	foredel(we - wb);
+	while ((ss = (char *)ugetnode(vl))) {
+	    untokenize(ss);
+	    ss = quotename(ss, NULL, NULL, NULL);
+	    inststr(ss);
+#if 0
+	    if (nonempty(vl)) {
+		spaceinline(1);
+		line[cs++] = ' ';
+	    }
+#endif
+	    if (olst != COMP_EXPAND_COMPLETE || nonempty(vl) ||
+		(cs && line[cs-1] != '/')) {
+		spaceinline(1);
+		line[cs++] = ' ';
+	    }
+	}
+      end:
+	popheap();
+    } LASTALLOC;
+}
+
+/* This is called from the lexer to give us word positions. */
+
+/**/
+void
+gotword(void)
+{
+    we = ll + 1 - inbufct + (addedx == 2 ? 1 : 0);
+    if (cs <= we) {
+	wb = ll - wordbeg + addedx;
+	zleparse = 0;
+    }
+}
+
+/* Insert the given string into the command line.  If move is non-zero, *
+ * the cursor position is changed and len is the length of the string   *
+ * to insert (if it is -1, the length is calculated here).              */
+
+/**/
+static void
+inststrlen(char *str, int move, int len)
+{
+    if (!len)
+	return;
+    if (len == -1)
+	len = strlen(str);
+    spaceinline(len);
+    strncpy((char *)(line + cs), str, len);
+    if (move)
+	cs += len;
+}
+
+/* Quote the string s and return the result.  If e is non-zero, it the    *
+ * pointer it points to may point to aposition in s and in e the position *
+ * of the corresponding character in the quoted string is returned.  Like *
+ * e, te may point to a position in the string and pl is used to return   *
+ * the position of the character pointed to by te in the quoted string.   *
+ * The string is metafied and may contain tokens.                         */
+
+/**/
+static char *
+quotename(const char *s, char **e, char *te, int *pl)
+{
+    const char *u, *tt;
+    char *v, buf[PATH_MAX * 2];
+    int sf = 0;
+
+    tt = v = buf;
+    u = s;
+    for (; *u; u++) {
+	if (e && *e == u)
+	    *e = v, sf |= 1;
+	if (te == u)
+	    *pl = v - tt, sf |= 2;
+	if (ispecial(*u) &&
+	    (!instring || (isset(BANGHIST) &&
+			   *u == (char)bangchar) ||
+	     (instring == 2 &&
+	      (*u == '$' || *u == '`' || *u == '\"')) ||
+	     (instring == 1 && *u == '\'')))
+	    if (*u == '\n' || (instring == 1 && *u == '\'')) {
+		if (unset(RCQUOTES)) {
+		    *v++ = '\'';
+		    if (*u == '\'')
+			*v++ = '\\';
+		    *v++ = *u;
+		    *v++ = '\'';
+		} else if (*u == '\n')
+		    *v++ = '"', *v++ = '\n', *v++ = '"';
+		else
+		    *v++ = '\'', *v++ = '\'';
+		continue;
+	    } else
+		*v++ = '\\';
+	if(*u == Meta)
+	    *v++ = *u++;
+	*v++ = *u;
+    }
+    *v = '\0';
+    if (strcmp(buf, s))
+	tt = dupstring(buf);
+    else
+	tt = s;
+    v += tt - buf;
+    if (e && (sf & 1))
+	*e += tt - buf;
+
+    if (e && *e == u)
+	*e = v;
+    if (te == u)
+	*pl = v - tt;
+
+    return (char *) tt;
+}
+
+/* This adds a match to the list of matches.  The string to add is given   *
+ * in s, the type of match is given in the global variable addwhat and     *
+ * the parameter t (if not NULL) is a pointer to a hash node node which    *
+ * may be used to give other information to this function.                 *
+ *                                                                         *
+ * addwhat contains either one of the special values (negative, see below) *
+ * or the inclusive OR of some of the CC_* flags used for compctls.        */
+
+/**/
+static void
+addmatch(char *s, char *t)
+{
+    int test = 0, sl = strlen(s), pl = rpl, cc = 0, *bp, *ep, *sp;
+    char *e = NULL, *tt, *te, *fc, **fm;
+    Comp cp = patcomp;
+    HashNode hn;
+    Param pm;
+    LinkList l = matches;
+
+/*
+ * addwhat: -5 is for files,
+ *          -6 is for glob expansions,
+ *          -8 is for executable files (e.g. command paths),
+ *          -9 is for parameters
+ *          -7 is for command names (from cmdnamtab)
+ *          -4 is for a cdable parameter
+ *          -3 is for executable command names.
+ *          -2 is for anything unquoted
+ *          -1 is for other file specifications
+ *          (things with `~' of `=' at the beginning, ...).
+ */
+
+    /* Just to make the code cleaner */
+    hn = (HashNode) t;
+    pm = (Param) t;
+
+    if (!addwhat) {
+	test = 1;
+    } else if (addwhat == -1 || addwhat == -5 || addwhat == -6 ||
+	       addwhat == CC_FILES || addwhat == -7 || addwhat == -8) {
+	if (sl < fpl + fsl)
+	    return;
+
+	if ((addwhat == CC_FILES ||
+	     addwhat == -5) && !*psuf && !*fsuf) {
+	    /* If this is a filename, do the fignore check. */
+	    char **pt = fignore;
+	    int filell;
+
+	    for (test = 1; test && *pt; pt++)
+		if ((filell = strlen(*pt)) < sl
+		    && !strcmp(*pt, s + sl - filell))
+		    test = 0;
+
+	    if (!test)
+		l = fmatches;
+	}
+	pl = fpl;
+	if (addwhat == -5 || addwhat == -8) {
+	    test = 1;
+	    cp = filecomp;
+	    cc = cp || ispattern;
+	    e = s + sl - fsl;
+	} else {
+	    if ((cp = filecomp)) {
+		if ((test = domatch(s, filecomp, 0)))
+		    cc = 1;
+	    } else {
+		e = s + sl - fsl;
+		if ((test = !strncmp(s, fpre, fpl)))
+		    test = !strcmp(e, fsuf);
+		if (ispattern)
+		    cc = 1;
+	    }
+	}
+	if (test) {
+	    fc = NULL;
+	    if (addwhat == -7 && !(fc = findcmd(s)))
+		return;
+	    if (fc)
+		zsfree(fc);
+	    haswhat |= HAS_FILES;
+
+	    if (addwhat == CC_FILES || addwhat == -6 ||
+		addwhat == -5 || addwhat == -8) {
+		te = s + pl;
+		s = quotename(s, &e, te, &pl);
+		sl = strlen(s);
+	    } else if (!cc) {
+		s = dupstring(t = s);
+		e += s - t;
+	    }
+	    if (cc) {
+		tt = (char *)halloc(strlen(ppre) + strlen(psuf) + sl + 1);
+		strcpy(tt, ppre);
+		strcat(tt, s);
+		strcat(tt, psuf);
+		untokenize(s = tt);
+	    }
+	}
+    } else if (addwhat == CC_QUOTEFLAG || addwhat == -2  ||
+	      (addwhat == -3 && !(hn->flags & DISABLED)) ||
+	      (addwhat == -4 && (PM_TYPE(pm->flags) == PM_SCALAR) &&
+	       (tt = pm->gets.cfn(pm)) && *tt == '/')    ||
+	      (addwhat == -9 && !(hn->flags & PM_UNSET)) ||
+	      (addwhat > 0 &&
+	       ((!(hn->flags & PM_UNSET) &&
+		 (((addwhat & CC_ARRAYS)    &&  (hn->flags & PM_ARRAY))    ||
+		  ((addwhat & CC_INTVARS)   &&  (hn->flags & PM_INTEGER))  ||
+		  ((addwhat & CC_ENVVARS)   &&  (hn->flags & PM_EXPORTED)) ||
+		  ((addwhat & CC_SCALARS)   &&  (hn->flags & PM_SCALAR))   ||
+		  ((addwhat & CC_READONLYS) &&  (hn->flags & PM_READONLY)) ||
+		  ((addwhat & CC_SPECIALS)  &&  (hn->flags & PM_SPECIAL))  ||
+		  ((addwhat & CC_PARAMS)    && !(hn->flags & PM_EXPORTED)))) ||
+		((( addwhat & CC_SHFUNCS)				  ||
+		  ( addwhat & CC_BUILTINS)				  ||
+		  ( addwhat & CC_EXTCMDS)				  ||
+		  ( addwhat & CC_RESWDS)				  ||
+		  ((addwhat & CC_ALREG)   && !(hn->flags & ALIAS_GLOBAL)) ||
+		  ((addwhat & CC_ALGLOB)  &&  (hn->flags & ALIAS_GLOBAL))) &&
+		 (((addwhat & CC_DISCMDS) && (hn->flags & DISABLED)) ||
+		  ((addwhat & CC_EXCMDS)  && !(hn->flags & DISABLED)))) ||
+		((addwhat & CC_BINDINGS) && !(hn->flags & DISABLED))))) {
+	if (sl >= rpl + rsl) {
+	    if (cp)
+		test = domatch(s, patcomp, 0);
+	    else {
+		e = s + sl - rsl;
+		if ((test = !strncmp(s, rpre, rpl)))
+		    test = !strcmp(e, rsuf);
+	    }
+	}
+	if (!test && sl < lpl + lsl)
+	    return;
+	if (!test && lpre && lsuf && sl >= lpl + lsl) {
+	    e = s + sl - lsl;
+	    if ((test = !strncmp(s, lpre, lpl)))
+		test = !strcmp(e, lsuf);
+	    pl = lpl;
+	}
+	if (addwhat == CC_QUOTEFLAG) {
+	    te = s + pl;
+	    s = quotename(s, &e, te, &pl);
+	    sl = strlen(s);
+	}
+	if (test)
+	    haswhat |= HAS_MISC;
+    }
+    if (!test)
+	return;
+
+    if (ispattern) {
+	t = s;
+    } else {
+	t = s += pl;
+	if (*e)
+	    t = s = dupstrpfx(t, e - t);
+    }
+
+    if (l == fmatches) {
+	bp = &fab;
+	ep = &fae;
+	sp = &fshortl;
+	fm = &ffirstm;
+    } else {
+	bp = &ab;
+	ep = &ae;
+	sp = &shortl;
+	fm = &firstm;
+    }
+
+    if (!ispattern && *fm) {
+	if ((test = pfxlen(*fm, s)) < *bp)
+	    *bp = test;
+	if ((test = sfxlen(*fm, s)) < *ep)
+	    *ep = test;
+	if (*ep > *sp - *bp)
+	    *ep = *sp - *bp;
+    }
+
+    /* If we are doing a glob completion we store the whole string in *
+     * the list. Otherwise only the part that fits between the prefix *
+     * and the suffix is stored.                                      */
+    addlinknode(l, t);
+    if (!*fm) {
+	*bp = *ep = 10000;
+	*fm = t;
+	*sp = 100000;
+    }
+    if (!ispattern && (sl = strlen(t)) < *sp) {
+	*sp = sl;
+	if (l == fmatches)
+	    fshortest = t;
+	else
+	    shortest = t;
+    }
+}
+
+#ifdef HAVE_NIS_PLUS
+static int
+match_username(nis_name table, nis_object *object, void *userdata)
+{
+    if (errflag)
+	return 1;
+    else {
+	static char buf[40];
+	register entry_col *ec =
+	    object->zo_data.objdata_u.en_data.en_cols.en_cols_val;
+	register int l = minimum(ec->ec_value.ec_value_len, 39);
+
+	memcpy(buf, ec->ec_value.ec_value_val, l);
+	buf[l] = '\0';
+
+	addmatch(dupstring(buf), NULL);
+    }
+    return 0;
+}
+#else
+# ifdef HAVE_NIS
+static int
+match_username(int status, char *key, int keylen, char *val, int vallen, dopestring *data)
+{
+    if (errflag || status != YP_TRUE)
+	return 1;
+
+    if (vallen > keylen && val[keylen] == ':') {
+	val[keylen] = '\0';
+	addmatch(dupstring(val), NULL);
+    }
+    return 0;
+}
+# endif /* HAVE_NIS */
+#endif  /* HAVE_NIS_PLUS */
+
+/**/
+static void
+maketildelist(void)
+{
+#if defined(HAVE_NIS) || defined(HAVE_NIS_PLUS)
+    FILE *pwf;
+    char buf[BUFSIZ], *p;
+    int skipping;
+
+# ifndef HAVE_NIS_PLUS
+    char domain[YPMAXDOMAIN];
+    struct ypall_callback cb;
+    dopestring data;
+
+    data.s = fpre;
+    data.len = fpl;
+    /* Get potential matches from NIS and cull those without local accounts */
+    if (getdomainname(domain, YPMAXDOMAIN) == 0) {
+	cb.foreach = (int (*)()) match_username;
+	cb.data = (char *)&data;
+	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,
+	     match_username, 0);
+# endif
+    /* 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';
+			addmatch(dupstring(buf), NULL);
+		    }
+		} else
+		    skipping = 0;
+	    } else
+		skipping = 1;
+	}
+	fclose(pwf);
+    }
+#else  /* no NIS or NIS_PLUS */
+    /* add all the usernames to the named directory table */
+    nameddirtab->filltable(nameddirtab);
+#endif
+
+    scanhashtable(nameddirtab, 0, (addwhat==-1) ? 0 : ND_USERNAME, 0,
+	    addhnmatch, 0);
+}
+
+/* Copy the given string and remove backslashes from the copy and return it. */
+
+/**/
+static char *
+rembslash(char *s)
+{
+    char *t = s = dupstring(s);
+
+    while (*s)
+	if (*s == '\\') {
+	    chuck(s);
+	    if (*s)
+		s++;
+	} else
+	    s++;
+
+    return t;
+}
+
+/* This does the check for compctl -x `n' and `N' patterns. */
+
+/**/
+static int
+getcpat(char *wrd, int cpatindex, char *cpat, int class)
+{
+    char *str, *s, *t, *p;
+    int d = 0;
+
+    if (!wrd || !*wrd)
+	return -1;
+
+    cpat = rembslash(cpat);
+
+    str = ztrdup(wrd);
+    untokenize(str);
+    if (!cpatindex)
+	cpatindex++, d = 0;
+    else if ((d = (cpatindex < 0)))
+	cpatindex = -cpatindex;
+
+    for (s = d ? str + strlen(str) - 1 : str;
+	 d ? (s >= str) : *s;
+	 d ? s-- : s++) {
+	for (t = s, p = cpat; *t && *p; p++) {
+	    if (class) {
+		if (*p == *s && !--cpatindex) {
+		    zsfree(str);
+		    return (int)(s - str + 1);
+		}
+	    } else if (*t++ != *p)
+		break;
+	}
+	if (!class && !*p && !--cpatindex) {
+	    zsfree(str);
+	    t += wrd - str;
+	    for (d = 0; --t >= wrd;)
+		if (! INULL(*t))
+		    d++;
+	    return d;
+	}
+    }
+    zsfree(str);
+    return -1;
+}
+
+/* This holds a pointer to the compctl we are using. */
+
+static Compctl ccmain;
+
+
+/* Find the compctl to use and return it.  The first argument gives a *
+ * compctl to start searching with (if it is zero, the hash table is  *
+ * searched).  compadd is used to return a number of characters that  *
+ * should be ignored at the beginning of the word and incmd is        *
+ * non-zero if we are in command position.                            */
+
+/**/
+static Compctl
+get_ccompctl(Compctl occ, int *compadd, int incmd)
+{
+    Compctl compc, ret;
+    Compctlp ccp;
+    int t, i, a, b, tt, ra, rb, j, isf = 1;
+    Compcond or, cc;
+    char *s, *ss, *sc, *cmd = dupstring(cmdstr);
+    Comp comp;
+
+   first_rec:
+    *compadd = 0;
+    ra = 0;
+    rb = clwnum - 1;
+    sc = NULL;
+
+    if (!(ret = compc = occ)) {
+      if (isf) {
+        isf = 0;
+        ret = &cc_first;
+      }
+      else if (inwhat == IN_ENV)
+        /* Default completion for parameter values. */
+        ret = &cc_default;
+      else if (inwhat == IN_MATH) {
+        /* Parameter names inside mathematical expression. */
+        cc_dummy.mask = CC_PARAMS;
+	    ret = &cc_dummy;
+	    cc_dummy.refc = 10000;
+	} else if (inwhat == IN_COND) {
+	    /* We try to be clever here: in conditions we complete option   *
+	     * names after a `-o', file names after `-nt', `-ot', and `-ef' *
+	     * and file names and parameter names elsewhere.                */
+	    s = clwpos ? clwords[clwpos - 1] : "";
+	    cc_dummy.mask = !strcmp("-o", s) ? CC_OPTIONS :
+		((*s == '-' && s[1] && !s[2]) ||
+		 !strcmp("-nt", s) ||
+		 !strcmp("-ot", s) ||
+		 !strcmp("-ef", s)) ? CC_FILES :
+		(CC_FILES | CC_PARAMS);
+	    ret = &cc_dummy;
+	    cc_dummy.refc = 10000;
+	} else if (incmd)
+	    ret = &cc_compos;
+	/* And in redirections or if there is no command name (and we are *
+	 * not in command position) or if no special compctl was given    *
+	 * for the command: use default completion.  Note that we first   *
+	 * search the complete command name and than the trailing         *
+	 * pathname component.                                            */
+	else if (linredir ||
+ 		 !(cmd &&
+ 		   (((ccp = (Compctlp) compctltab->getnode(compctltab, cmd)) &&
+		     (compc = ret = ccp->cc)) ||
+ 		    ((s = dupstring(cmd)) && remlpaths(&s) &&
+		     (ccp = (Compctlp) compctltab->getnode(compctltab, s)) &&
+		     (compc = ret = ccp->cc)))))
+	    ret = &cc_default;
+
+	ccmain = compc = ret;
+	ccmain->refc++;
+    }
+    /* The compctl we found has extended completion patterns, check them. */
+    if (compc && compc->ext) {
+	compc = compc->ext;
+	/* This loops over the patterns separated by `--'. */
+	for (t = 0; compc && !t; compc = compc->next) {
+	    /* This loops over OR'ed patterns. */
+	    for (cc = compc->cond; cc && !t; cc = or) {
+		or = cc->or;
+		/* This loops over AND'ed patterns. */
+		for (t = 1; cc && t; cc = cc->and) {
+		    /* And this loops of [...] pairs. */
+		    for (t = i = 0; i < cc->n && !t; i++) {
+			s = NULL;
+			ra = 0;
+			rb = clwnum - 1;
+			switch (cc->type) {
+			case CCT_POS:
+			    tt = clwpos;
+			    goto cct_num;
+			case CCT_NUMWORDS:
+			    tt = clwnum;
+			  cct_num:
+			    if ((a = cc->u.r.a[i]) < 0)
+				a += clwnum;
+			    if ((b = cc->u.r.b[i]) < 0)
+				b += clwnum;
+			    if (cc->type == CCT_POS)
+				ra = a, rb = b;
+			    t = (tt >= a && tt <= b);
+			    break;
+			case CCT_CURSUF:
+			case CCT_CURPRE:
+			    s = ztrdup(clwpos < clwnum ? clwords[clwpos] : "");
+			    untokenize(s);
+			    sc = rembslash(cc->u.s.s[i]);
+			    a = strlen(sc);
+			    if (!strncmp(s, sc, a)) {
+				*compadd = (cc->type == CCT_CURSUF ? a : 0);
+				t = 1;
+			    }
+			    break;
+			case CCT_CURSUB:
+			case CCT_CURSUBC:
+			    if (clwpos < 0 || clwpos > clwnum)
+				t = 0;
+			    else {
+				a = getcpat(clwords[clwpos],
+					    cc->u.s.p[i],
+					    cc->u.s.s[i],
+					    cc->type == CCT_CURSUBC);
+				if (a != -1)
+				    *compadd = a, t = 1;
+			    }
+			    break;
+
+			case CCT_CURPAT:
+			case CCT_CURSTR:
+			    tt = clwpos;
+			    goto cct_str;
+			case CCT_WORDPAT:
+			case CCT_WORDSTR:
+			    tt = 0;
+			  cct_str:
+			    if ((a = tt + cc->u.s.p[i]) < 0)
+				a += clwnum;
+			    s = ztrdup((a < 0 || a >= clwnum) ? "" :
+				       clwords[a]);
+			    untokenize(s);
+
+			    if (cc->type == CCT_CURPAT ||
+				cc->type == CCT_WORDPAT) {
+				tokenize(ss = dupstring(cc->u.s.s[i]));
+				t = ((comp = parsereg(ss)) &&
+				     domatch(s, comp, 0));
+			    } else
+				t = (!strcmp(s, rembslash(cc->u.s.s[i])));
+			    break;
+			case CCT_RANGESTR:
+			case CCT_RANGEPAT:
+			    if (cc->type == CCT_RANGEPAT)
+				tokenize(sc = dupstring(cc->u.l.a[i]));
+			    for (j = clwpos; j; j--) {
+				untokenize(s = ztrdup(clwords[j]));
+				if (cc->type == CCT_RANGESTR)
+				    sc = rembslash(cc->u.l.a[i]);
+				if (cc->type == CCT_RANGESTR ?
+				    !strncmp(s, sc, strlen(sc)) :
+				    ((comp = parsereg(sc)) &&
+				     domatch(s, comp, 0))) {
+				    zsfree(s);
+				    ra = j + 1;
+				    t = 1;
+				    break;
+				}
+				zsfree(s);
+			    }
+			    if (t) {
+				if (cc->type == CCT_RANGEPAT)
+				    tokenize(sc = dupstring(cc->u.l.b[i]));
+				for (j++; j < clwnum; j++) {
+				    untokenize(s = ztrdup(clwords[j]));
+				    if (cc->type == CCT_RANGESTR)
+					sc = rembslash(cc->u.l.b[i]);
+				    if (cc->type == CCT_RANGESTR ?
+					!strncmp(s, sc, strlen(sc)) :
+					((comp = parsereg(sc)) &&
+					 domatch(s, comp, 0))) {
+					zsfree(s);
+					rb = j - 1;
+					t = clwpos <= rb;
+					break;
+				    }
+				    zsfree(s);
+				}
+			    }
+			    s = NULL;
+			}
+			zsfree(s);
+		    }
+		}
+	    }
+	    if (t)
+		break;
+	}
+	if (compc)
+	    /* We found a matching pattern, we may return it. */
+	    ret = compc;
+    }
+    if (ret->subcmd) {
+	/* The thing we want to return has a subcmd flag (-l). */
+	char **ow = clwords, *os = cmdstr, *ops = NULL;
+	int oldn = clwnum, oldp = clwpos;
+
+	/* So we restrict the words-array. */
+	if (ra >= clwnum)
+	    ra = clwnum - 1;
+	if (ra < 1)
+	    ra = 1;
+	if (rb >= clwnum)
+	    rb = clwnum - 1;
+	if (rb < 1)
+	    rb = 1;
+	clwnum = rb - ra + 1;
+	clwpos = clwpos - ra;
+
+	if (ret->subcmd[0]) {
+	    /* And probably put the command name given to the flag *
+	     * in the array.                                       */
+	    clwpos++;
+	    clwnum++;
+	    incmd = 0;
+	    ops = clwords[ra - 1];
+	    clwords[ra - 1] = cmdstr = ret->subcmd;
+	    clwords += ra - 1;
+	} else {
+	    cmdstr = clwords[ra];
+	    incmd = !clwpos;
+	    clwords += ra;
+	}
+	*compadd = 0;
+	if (ccmain != &cc_dummy)
+	    freecompctl(ccmain);
+	/* Then we call this function recursively. */
+
+	ret = get_ccompctl(NULL, compadd, incmd);
+	/* And restore the things we changed. */
+	clwords = ow;
+	cmdstr = os;
+	clwnum = oldn;
+	clwpos = oldp;
+	if (ops)
+	    clwords[ra - 1] = ops;
+    }
+    if (ret == &cc_first)
+      goto first_rec;
+    return ret;
+}
+
+/* Dump a hash table (without sorting).  For each element the addmatch  *
+ * function is called and at the beginning the addwhat variable is set. *
+ * This could be done using scanhashtable(), but this is easy and much  *
+ * more efficient.                                                      */
+
+/**/
+static void
+dumphashtable(HashTable ht, int what)
+{
+    HashNode hn;
+    int i;
+
+    addwhat = what;
+
+    for (i = 0; i < ht->hsize; i++)
+	for (hn = ht->nodes[i]; hn; hn = hn->next)
+	    addmatch(hn->nam, (char *) hn);
+
+}
+
+/* ScanFunc used by maketildelist() et al. */
+
+/**/
+static void
+addhnmatch(HashNode hn, int flags)
+{
+    addmatch(hn->nam, NULL);
+}
+
+/* Perform expansion on the given string and return the result. *
+ * During this errors are not reported.                         */
+
+/**/
+static char *
+getreal(char *str)
+{
+    LinkList l = newlinklist();
+    int ne = noerrs;
+
+    noerrs = 1;
+    addlinknode(l, dupstring(str));
+    prefork(l, 0);
+    noerrs = ne;
+    if (!errflag && nonempty(l))
+	return ztrdup(peekfirst(l));
+    errflag = 0;
+
+    return ztrdup(str);
+}
+
+/* This reads a directory and adds the files to the list of  *
+ * matches.  The parameters say which files should be added. */
+
+/**/
+static void
+gen_matches_files(int dirs, int execs, int all)
+{
+    DIR *d;
+    struct stat buf;
+    char *n, p[PATH_MAX], *q = NULL, *e;
+    LinkList l = NULL;
+    int ns = 0, ng = opts[NULLGLOB], test, aw = addwhat;
+
+    addwhat = execs ? -8 : -5;
+    opts[NULLGLOB] = 1;
+
+    if (*psuf) {
+	/* If there is a path suffix, check if it doesn't have a `*' or *
+	 * `)' at the end (this is used to determine if we should use   *
+	 * globbing).                                                   */
+	q = psuf + strlen(psuf) - 1;
+	ns = !(*q == Star || *q == Outpar);
+	l = newlinklist();
+	/* And generate only directory names. */
+	dirs = 1;
+	all = execs = 0;
+    }
+    /* Open directory. */
+    if ((d = opendir((prpre && *prpre) ? prpre : "."))) {
+	/* If we search only special files, prepare a path buffer for stat. */
+	if (!all && prpre) {
+	    strcpy(p, prpre);
+	    q = p + strlen(prpre);
+	}
+	/* Fine, now read the directory. */
+	while ((n = zreaddir(d, 1)) && !errflag) {
+	    /* Ignore files beginning with `.' unless the thing we found on *
+	     * the command line also starts with a dot or GLOBDOTS is set.  */
+	    if (*n != '.' || *fpre == '.' || isset(GLOBDOTS)) {
+		if (filecomp)
+		    /* If we have a pattern for the filename check, use it. */
+		    test = domatch(n, filecomp, 0);
+		else {
+		    /* Otherwise use the prefix and suffix strings directly. */
+		    e = n + strlen(n) - fsl;
+		    if ((test = !strncmp(n, fpre, fpl)))
+			test = !strcmp(e, fsuf);
+		}
+		/* Filename didn't match? */
+		if (!test)
+		    continue;
+		if (!all) {
+		    /* We still have to check the file type, so prepare *
+		     * the path buffer by appending the filename.       */
+		    strcpy(q, n);
+		    /* And do the stat. */
+		    if (stat(p, &buf) < 0)
+			continue;
+		}
+		if (all ||
+		    (dirs && S_ISDIR(buf.st_mode)) ||
+		    (execs && S_ISREG(buf.st_mode) && (buf.st_mode&S_IXUGO))) {
+		    /* If we want all files or the file has the right type... */
+		    if (*psuf) {
+			/* We have to test for a path suffix. */
+			int o = strlen(p), tt;
+
+			/* Append it to the path buffer. */
+			strcpy(p + o, psuf);
+
+			/* Do we have to use globbing? */
+			if (ispattern || (ns && isset(GLOBCOMPLETE))) {
+			    /* Yes, so append a `*' if needed. */
+			    if (ns) {
+				int tl = strlen(p);
+
+				p[tl] = Star;
+				p[tl + 1] = '\0';
+			    }
+			    /* Do the globbing... */
+			    remnulargs(p);
+			    addlinknode(l, p);
+			    globlist(l);
+			    /* And see if that produced a filename. */
+			    tt = nonempty(l);
+			    while (ugetnode(l));
+			} else
+			    /* Otherwise just check, if we have access *
+			     * to the file.                            */
+			    tt = !access(p, F_OK);
+
+			p[o] = '\0';
+			if (tt)
+			    /* Ok, we can add the filename to the *
+			     * list of matches.                   */
+			    addmatch(dupstring(n), NULL);
+		    } else
+			/* We want all files, so just add the name *
+			 * to the matches.                         */
+			addmatch(dupstring(n), NULL);
+		}
+	    }
+	}
+	closedir(d);
+    }
+    opts[NULLGLOB] = ng;
+    addwhat = aw;
+}
+
+/* This holds the explanation string we have to print. */
+
+static char *expl;
+
+/* This holds the suffix to add (given with compctl -S). */
+
+static char *ccsuffix;
+
+/* This s non-zero if the compctl -q flag was given (the suffix should *
+ * be removed when a space or something like that is typed next).      */
+
+static int remsuffix;
+
+/**/
+static void
+quotepresuf(char **ps)
+{
+    if (*ps) {
+	char *p = quotename(*ps, NULL, NULL, NULL);
+
+	if (p != *ps) {
+	    zsfree(*ps);
+	    *ps = ztrdup(p);
+	}
+    }
+}
+
+/**/
+static void
+docompletion(char *s, int lst, int incmd, int untokenized)
+{
+    static int delit, compadd;
+
+    fixsuffix();
+    HEAPALLOC {
+	pushheap();
+
+	/* Make sure we have the completion list and compctl. */
+	if(makecomplist(s, incmd, &delit, &compadd, untokenized)) {
+	    /* Error condition: feeeeeeeeeeeeep(). */
+	    feep();
+	    goto compend;
+	}
+
+	if (lst == COMP_LIST_COMPLETE)
+	    /* All this and the guy only wants to see the list, sigh. */
+	    showinglist = -2;
+	else {
+	    /* We have matches. */
+	    if (delit) {
+		/* If we have to delete the word from the command line, *
+		 * do it now.                                           */
+		wb -= compadd;
+		strcpy((char *)line + wb, (char *)line + we);
+		we = cs = wb;
+	    }
+	    if (nmatches > 1)
+		/* There are more than one match. */
+		do_ambiguous();
+	    else if (nmatches == 1) {
+		/* Only one match. */
+		do_single(amatches[0]);
+		invalidatelist();
+	    }
+	}
+
+	/* Print the explanation string if needed. */
+	if (!showinglist && expl && nmatches != 1) {
+	    int up;
+
+	    if (!nmatches)
+		feep();
+	    trashzle();
+
+	    clearflag = (isset(USEZLE) && !termflags &&
+			 (isset(ALWAYSLASTPROMPT) && zmult == 1)) ||
+			(unset(ALWAYSLASTPROMPT) && zmult != 1);
+
+	    up = printfmt(expl, nmatches, 1);
+
+	    if (clearflag)
+		tcmultout(TCUP, TCMULTUP, up + nlnct);
+	    else
+		putc('\n', shout);
+	    fflush(shout);
+	}
+      compend:
+	ll = strlen((char *)line);
+	if (cs > ll)
+	    cs = ll;
+	popheap();
+    } LASTALLOC;
+}
+
+/* Create the completion list.  This is called whenever some bit of  *
+ * completion code needs the list.  If the list is already available *
+ * (validlist!=0), this function doesn't do anything.  Along with    *
+ * the list is maintained the prefixes/suffixes etc.  When any of    *
+ * this becomes invalid -- e.g. if some text is changed on the       *
+ * command line -- invalidatelist() should be called, to set         *
+ * validlist to zero and free up the memory used.  This function     *
+ * returns non-zero on error.  delit and compadd return information  *
+ * about bits of the command line that need to be deleted.           */
+
+/**/
+static int
+makecomplist(char *s, int incmd, int *delit, int *compadd, int untokenized)
+{
+    Compctl cc = NULL;
+    int oloffs = offs, owe = we, owb = wb, ocs = cs, oll = ll, isf = 1;
+    int t, sf1, sf2, ooffs;
+    char *p, *sd = NULL, *tt, *s1, *s2, *os = NULL;
+    unsigned char *ol = NULL;
+
+    /* If we already have a list from a previous execution of this *
+     * function, skip the list building code.                      */
+    if (validlist)
+	return !nmatches;
+
+    os = dupstring(s);
+    ol = (unsigned char *)dupstring((char *)line);
+
+  xorrec:
+
+    DPUTS(ll != strlen((char *) line), "BUG: xorrec: ll != strlen(line)");
+
+    /* Go to the end of the word if complete_in_word is not set. */
+    if (unset(COMPLETEINWORD) && cs != we)
+	cs = we, offs = strlen(s);
+
+    ispattern = haswhat = lastambig = 0;
+    patcomp = filecomp = NULL;
+    menucur = NULL;
+    shortest = NULL;
+    fshortest = NULL;
+    rpre = rsuf = lpre = lsuf = ppre = psuf = prpre =
+	fpre = fsuf = firstm = ffirstm = parampre = qparampre = NULL;
+
+    /* Blank out the lists. */
+    matches = newlinklist();
+    fmatches = newlinklist();
+
+    /* If we don't have a compctl definition yet or we have a compctl *
+     * with extended completion, get it (or the next one, resp.).     */
+    if (!cc || cc->ext)
+	cc = get_ccompctl(cc, compadd, incmd);
+
+    /* *compadd is the number of characters we have to ignore at the *
+     * beginning of the word.                                        */
+    wb += *compadd;
+    s += *compadd;
+    if ((offs -= *compadd) < 0)
+	/* It's bigger than our word prefix, so we can't help here... */
+	return 1;
+
+    /* Insert the prefix (compctl -P), if any. */
+    if (cc->prefix) {
+	int pl = 0, sl = strlen(cc->prefix);
+
+	if (*s) {
+	    /* First find out how much of the prefix is already on the line. */
+	    sd = dupstring(s);
+	    untokenize(sd);
+	    pl = pfxlen(cc->prefix, sd);
+	    s += pl;
+	}
+	if (pl < sl) {
+	    int savecs = cs;
+
+	    /* Then insert the prefix. */
+	    cs = wb + pl;
+	    inststrlen(cc->prefix + pl, 0, sl - pl);
+	    cs = savecs + sl - pl;
+	}
+	/* And adjust the word beginning/end variables. */
+	wb += sl;
+	we += sl - pl;
+	offs -= pl;
+    }
+    /* Does this compctl have a suffix (compctl -S)? */
+    if ((ccsuffix = cc->suffix) && *ccsuffix) {
+	char *sdup = dupstring(ccsuffix);
+	int sl = strlen(sdup), suffixll;
+
+	/* Ignore trailing spaces. */
+	for (p = sdup + sl - 1; p >= sdup && *p == ' '; p--, sl--);
+	p[1] = '\0';
+
+	if (!sd) {
+	    sd = dupstring(s);
+	    untokenize(sd);
+	}
+	/* If the suffix is already there, ignore it (and don't add *
+	 * it again).                                               */
+	if (*sd && (suffixll = strlen(sd)) >= sl &&
+	    offs <= suffixll - sl && !strcmp(sdup, sd + suffixll - sl)) {
+	    ccsuffix = NULL;
+	    haswhat |= HAS_SUFFIX;
+	    s[suffixll - sl] = '\0';
+	}
+    }
+    /* Do we have one of the special characters `~' and `=' at the beginning? */
+    if ((ic = *s) != Tilde && ic != Equals)
+	ic = 0;
+
+    /* Check if we have to complete a parameter name... */
+
+    /* Try to find a `$'. */
+    for (p = s + offs; p > s && *p != String; p--);
+    if (*p == String) {
+	/* Handle $$'s */
+	while (p > s && p[-1] == String)
+	    p--;
+	while (p[1] == String && p[2] == String)
+	    p += 2;
+    }
+    if (*p == String &&	p[1] != Inpar && p[1] != Inbrack) {
+	/* This is really a parameter expression (not $(...) or $[...]). */
+	char *b = p + 1, *e = b;
+	int n = 0, br = 1;
+
+	if (*b == Inbrace) {
+	    /* If this is a ${...}, ignore the possible (...) flags. */
+	    b++, br++;
+	    n = skipparens(Inpar, Outpar, &b);
+	}
+
+	/* Ignore the stuff before the parameter name. */
+	for (; *b; b++)
+	    if (*b != '^' && *b != Hat &&
+		*b != '=' && *b != Equals &&
+		*b != '~' && *b != Tilde)
+		break;
+	if (*b == '#' || *b == Pound || *b == '+')
+	    b++;
+
+	e = b;
+	/* Find the end of the name. */
+	if (*e == Quest || *e == Star || *e == String || *e == Qstring ||
+	    *e == '?'   || *e == '*'  || *e == '$'    ||
+	    *e == '-'   || *e == '!'  || *e == '@')
+	    e++;
+	else if (idigit(*e))
+	    while (idigit(*e))
+		e++;
+	else if (iident(*e))
+	    while (iident(*e) ||
+		   (useglob && (*e == Star || *e == Quest)))
+		e++;
+
+	/* Now make sure that the cursor is inside the name. */
+	if (offs <= e - s && offs >= b - s && n <= 0) {
+	    /* It is. */
+	    parambr = br - 1;
+	    /* Get the prefix (anything up to the character before the name). */
+	    *e = '\0';
+	    parampre = ztrduppfx(s, b - s);
+	    qparampre = ztrdup(quotename(parampre, NULL, NULL, NULL));
+	    untokenize(qparampre);
+	    qparprelen = strlen(qparampre);
+	    /* And adjust wb, we, and offs again. */
+	    offs -= b - s;
+	    wb = cs - offs;
+	    we = wb + e - b;
+	    s = b;
+	    /* And now make sure that we complete parameter names. */
+	    cc = ccmain = &cc_dummy;
+	    cc_dummy.refc = 10000;
+	    cc_dummy.mask = CC_PARAMS | CC_ENVVARS;
+	}
+    }
+    ooffs = offs;
+    /* If we have to ignore the word, do that. */
+    if (cc->mask & CC_DELETE) {
+	*delit = 1;
+	*s = '\0';
+	offs = 0;
+    } else
+	*delit = 0;
+
+    /* Compute line prefix/suffix. */
+
+    lpl = offs;
+    lpre = zalloc(lpl + 1);
+    memcpy(lpre, s, lpl);
+    lpre[lpl] = '\0';
+    p = quotename(lpre, NULL, NULL, NULL);
+    if (strcmp(p, lpre) && !strpfx(p, qword)) {
+	int l1, l2;
+
+	backdel(l1 = cs - wb);
+	untokenize(p);
+	inststrlen(p, 1, l2 = strlen(p));
+	we += l2 - l1;
+    }
+    lsuf = ztrdup(s + offs);
+    lsl = strlen(lsuf);
+    if (lsl && (p = quotename(lsuf, NULL, NULL, NULL)) &&
+	(strcmp(p, lsuf) && !strsfx(p, qword))) {
+	int l1, l2;
+
+	foredel(l1 = strlen(s + offs));
+	untokenize(p);
+	inststrlen(p, 0, l2 = strlen(p));
+	we += l2 - l1;
+    }
+
+    /* First check for ~.../... */
+    if (ic == Tilde) {
+	for (p = lpre + lpl; p > lpre; p--)
+	    if (*p == '/')
+		break;
+
+	if (*p == '/')
+	    ic = 0;
+    }
+    /* Compute real prefix/suffix. */
+
+    noreal = !*delit;
+    for (p = lpre; *p && *p != String && *p != Tick; p++);
+    tt = ic && !parampre ? lpre + 1 : lpre;
+    rpre = (*p || *lpre == Tilde || *lpre == Equals) ?
+	(noreal = 0, getreal(tt)) :
+	ztrdup(tt);
+
+    for (p = lsuf; *p && *p != String && *p != Tick; p++);
+    rsuf = *p ? (noreal = 0, getreal(lsuf)) : ztrdup(lsuf);
+
+    /* Check if word is a pattern. */
+
+    for (s1 = NULL, sf1 = 0, p = rpre + (rpl = strlen(rpre)) - 1;
+	 p >= rpre && (ispattern != 3 || !sf1);
+	 p--)
+	if (itok(*p) && (p > rpre || (*p != Equals && *p != Tilde)))
+	    ispattern |= sf1 ? 1 : 2;
+	else if (*p == '/') {
+	    sf1++;
+	    if (!s1)
+		s1 = p;
+	}
+    for (s2 = NULL, sf2 = t = 0, p = rsuf; *p && (!t || !sf2); p++)
+	if (itok(*p))
+	    t |= sf2 ? 4 : 2;
+	else if (*p == '/') {
+	    sf2++;
+	    if (!s2)
+		s2 = p;
+	}
+    ispattern = ispattern | t;
+
+    /* But if we were asked not to do glob completion, we never treat the *
+     * thing as a pattern.                                                */
+    if (!useglob)
+	ispattern = 0;
+
+    if (ispattern) {
+	/* The word should be treated as a pattern, so compute the matcher. */
+	p = (char *)ncalloc(rpl + rsl + 2);
+	strcpy(p, rpre);
+	if (rpl && p[rpl - 1] != Star) {
+	    p[rpl] = Star;
+	    strcpy(p + rpl + 1, rsuf);
+	} else
+	    strcpy(p + rpl, rsuf);
+	patcomp = parsereg(p);
+    }
+    if (!patcomp) {
+	untokenize(rpre);
+	untokenize(rsuf);
+
+	rpl = strlen(rpre);
+	rsl = strlen(rsuf);
+    }
+    untokenize(lpre);
+    untokenize(lsuf);
+
+    /* Handle completion of files specially (of course). */
+
+    if ((cc->mask & (CC_FILES | CC_DIRS | CC_COMMPATH)) || cc->glob) {
+	/* s1 and s2 point to the last/first slash in the prefix/suffix. */
+	if (!s1)
+	    s1 = rpre;
+	if (!s2)
+	    s2 = rsuf + rsl;
+
+	/* Compute the path prefix/suffix. */
+	if (*s1 != '/')
+	    ppre = ztrdup("");
+	else
+	    ppre = ztrduppfx(rpre, s1 - rpre + 1);
+	psuf = ztrdup(s2);
+
+	/* And get the file prefix. */
+	fpre = ztrdup(((s1 == s || s1 == rpre || ic) &&
+		       (*s != '/' || cs == wb)) ? s1 : s1 + 1);
+	/* And the suffix. */
+	fsuf = ztrduppfx(rsuf, s2 - rsuf);
+
+	if (useglob && (ispattern & 2)) {
+	    int t2;
+
+	    /* We have to use globbing, so compute the pattern from *
+	     * the file prefix and suffix with a `*' between them.  */
+	    p = (char *)ncalloc((t2 = strlen(fpre)) + strlen(fsuf) + 2);
+	    strcpy(p, fpre);
+	    if ((!t2 || p[t2 - 1] != Star) && *fsuf != Star)
+		p[t2++] = Star;
+	    strcpy(p + t2, fsuf);
+	    filecomp = parsereg(p);
+	}
+	if (!filecomp) {
+	    untokenize(fpre);
+	    untokenize(fsuf);
+
+	    fpl = strlen(fpre);
+	    fsl = strlen(fsuf);
+	}
+	addwhat = -1;
+
+	/* Completion after `~', maketildelist adds the usernames *
+	 * and named directories.                                 */
+	if (ic == Tilde)
+	    maketildelist();
+	else if (ic == Equals) {
+	    /* Completion after `=', get the command names from *
+	     * the cmdnamtab and aliases from aliastab.         */
+	    if (isset(HASHLISTALL))
+		cmdnamtab->filltable(cmdnamtab);
+	    dumphashtable(cmdnamtab, -7);
+	    dumphashtable(aliastab, -2);
+	} else {
+	    /* Normal file completion... */
+	    if (ispattern & 1) {
+		/* But with pattern matching. */
+		LinkList l = newlinklist();
+		LinkNode n;
+		int ng = opts[NULLGLOB];
+
+		opts[NULLGLOB] = 1;
+
+		addwhat = 0;
+		p = (char *)ncalloc(lpl + lsl + 3);
+		strcpy(p, lpre);
+		if (*lsuf != '*' && *lpre && lpre[lpl - 1] != '*')
+		    strcat(p, "*");
+		strcat(p, lsuf);
+		if (*lsuf && lsuf[lsl - 1] != '*' && lsuf[lsl - 1] != ')')
+		    strcat(p, "*");
+
+		/* Do the globbing. */
+		tokenize(p);
+		remnulargs(p);
+		addlinknode(l, p);
+		globlist(l);
+
+		if (nonempty(l)) {
+		    /* And add the resulting words. */
+		    haswhat |= HAS_PATHPAT;
+		    for (n = firstnode(l); n; incnode(n))
+			addmatch(getdata(n), NULL);
+		}
+		opts[NULLGLOB] = ng;
+	    } else {
+		/* No pattern matching. */
+		addwhat = CC_FILES;
+		if (cc->withd) {
+		    prpre = tricat(cc->withd, "/", ppre);
+		} else
+		    prpre = ztrdup(ppre);
+
+		if (sf2)
+		    /* We are in the path, so add only directories. */
+		    gen_matches_files(1, 0, 0);
+		else {
+		    if (cc->mask & CC_FILES)
+			/* Add all files. */
+			gen_matches_files(0, 0, 1);
+		    else if (cc->mask & CC_COMMPATH) {
+			/* Completion of command paths. */
+			if (sf1 || cc->withd)
+			    /* There is a path prefix, so add *
+			     * directories and executables.   */
+			    gen_matches_files(1, 1, 0);
+			else {
+			    /* No path prefix, so add the things *
+			     * reachable via the PATH variable.  */
+			    char **pc = path, *pp = prpre;
+
+			    for (; *pc; pc++)
+				if (!**pc || (pc[0][0] == '.' && !pc[0][1]))
+				    break;
+			    if (*pc) {
+				prpre = "./";
+				gen_matches_files(1, 1, 0);
+				prpre = pp;
+			    }
+			}
+		    } else if (cc->mask & CC_DIRS)
+			gen_matches_files(1, 0, 0);
+		    /* The compctl has a glob pattern (compctl -g). */
+		    if (cc->glob) {
+			int ns, pl = strlen(prpre), o;
+			char *g = dupstring(cc->glob), pa[PATH_MAX];
+			char *p2, *p3;
+			int ne = noerrs, md = opts[MARKDIRS];
+
+			/* These are used in the globbing code to make *
+			 * things a bit faster.                        */
+			glob_pre = fpre;
+			glob_suf = fsuf;
+
+			noerrs = 1;
+			addwhat = -6;
+			strcpy(pa, prpre);
+			o = strlen(pa);
+			opts[MARKDIRS] = 0;
+
+			/* The compctl -g string may contain more than *
+			 * one pattern, so we need a loop.             */
+			while (*g) {
+			    LinkList l = newlinklist();
+			    int ng;
+
+			    /* Find the blank terminating the pattern. */
+			    while (*g && inblank(*g))
+				g++;
+			    /* Oops, we already reached the end of the
+			       string. */
+			    if (!*g)
+				break;
+			    for (p = g + 1; *p && !inblank(*p); p++)
+				if (*p == '\\' && p[1])
+				    p++;
+			    /* Get the pattern string. */
+			    tokenize(g = dupstrpfx(g, p - g));
+			    if (*g == '=')
+				*g = Equals;
+			    if (*g == '~')
+				*g = Tilde;
+			    remnulargs(g);
+			    if ((*g == Equals || *g == Tilde) && !cc->withd) {
+				/* The pattern has a `~' or `=' at the  *
+				 * beginning, so we expand this and use *
+				 * the result.                          */
+				filesub(&g, 0);
+				addlinknode(l, dupstring(g));
+			    } else if (*g == '/' && !cc->withd)
+				/* The pattern is a full path (starting *
+				 * with '/'), so add it unchanged.      */
+				addlinknode(l, dupstring(g));
+			    else {
+				/* It's a simple pattern, so append it to *
+				 * the path we have on the command line.  */
+				strcpy(pa + o, g);
+				addlinknode(l, dupstring(pa));
+			    }
+			    /* Do the globbing. */
+			    ng = opts[NULLGLOB];
+			    opts[NULLGLOB] = 1;
+			    globlist(l);
+			    opts[NULLGLOB] = ng;
+			    /* Get the results. */
+			    if (nonempty(l) && peekfirst(l)) {
+				for (p2 = (char *)peekfirst(l); *p2; p2++)
+				    if (itok(*p2))
+					break;
+				if (!*p2) {
+				    if ((*g == Equals || *g == Tilde ||
+					*g == '/') || cc->withd) {
+					/* IF the pattern started with `~',  *
+					 * `=', or `/', add the result only, *
+					 * if it really matches what we have *
+					 * on the line.                      *
+					 * Do this if an initial directory   *
+					 * was specified, too.               */
+					while ((p2 = (char *)ugetnode(l)))
+					    if (strpfx(prpre, p2))
+						addmatch(p2 + pl, NULL);
+				    } else {
+					/* Otherwise ignore the path we *
+					 * prepended to the pattern.    */
+					while ((p2 = p3 =
+						(char *)ugetnode(l))) {
+					    for (ns = sf1; *p3 && ns; p3++)
+						if (*p3 == '/')
+						    ns--;
+
+					    addmatch(p3, NULL);
+					}
+				    }
+				}
+			    }
+			    pa[o] = '\0';
+			    g = p;
+			}
+			glob_pre = glob_suf = NULL;
+			noerrs = ne;
+			opts[MARKDIRS] = md;
+		    }
+		}
+	    }
+	}
+    }
+    /* Use tricat() instead of dyncat() to get zalloc()'d memory. */
+    if (ic) {
+	/* Now change the `~' and `=' tokens to the real characters so *
+	 * that things starting with these characters will be added.   */
+	char *orpre = rpre;
+
+	rpre = tricat("", (ic == Tilde) ? "~" : "=", rpre);
+	rpl++;
+	zsfree(orpre);
+    }
+    if (!ic && (cc->mask & CC_COMMPATH) && !*ppre && !*psuf) {
+	/* If we have to complete commands, add alias names, *
+	 * shell functions and builtins too.                 */
+	dumphashtable(aliastab, -3);
+	dumphashtable(reswdtab, -3);
+	dumphashtable(shfunctab, -3);
+	dumphashtable(builtintab, -3);
+	if (isset(HASHLISTALL))
+	    cmdnamtab->filltable(cmdnamtab);
+	dumphashtable(cmdnamtab, -3);
+	/* And parameter names if autocd and cdablevars are set. */
+	if (isset(AUTOCD) && isset(CDABLEVARS))
+	    dumphashtable(paramtab, -4);
+    }
+    addwhat = (cc->mask & CC_QUOTEFLAG) ? -2 : CC_QUOTEFLAG;
+
+    if (cc->mask & CC_NAMED)
+	/* Add named directories. */
+	dumphashtable(nameddirtab, addwhat);
+    if (cc->mask & CC_OPTIONS)
+	/* Add option names. */
+	dumphashtable(optiontab, addwhat);
+    if (cc->mask & CC_VARS)
+	/* And parameter names. */
+	dumphashtable(paramtab, -9);
+    if (cc->mask & CC_BINDINGS)
+	/* And zle function names... */
+	dumphashtable(thingytab, CC_BINDINGS);
+    if (cc->keyvar) {
+	/* This adds things given to the compctl -k flag *
+	 * (from a parameter or a list of words).        */
+	char **usr = get_user_var(cc->keyvar);
+
+	if (usr)
+	    while (*usr)
+		addmatch(*usr++, NULL);
+    }
+    if (cc->mask & CC_USERS)
+	/* Add user names. */
+	maketildelist();
+    if (cc->func) {
+	/* This handles the compctl -K flag. */
+	List list;
+	char **r;
+	int lv = lastval;
+
+	/* Get the function. */
+	if ((list = getshfunc(cc->func)) != &dummy_list) {
+	    /* We have it, so build a argument list. */
+	    LinkList args = newlinklist();
+
+	    addlinknode(args, cc->func);
+
+	    if (*delit) {
+		p = dupstrpfx(os, ooffs);
+		untokenize(p);
+		addlinknode(args, p);
+		p = dupstring(os + ooffs);
+		untokenize(p);
+		addlinknode(args, p);
+	    } else {
+		addlinknode(args, lpre);
+		addlinknode(args, lsuf);
+	    }
+
+	    /* This flag allows us to use read -l and -c. */
+	    incompctlfunc = 1;
+	    /* Call the function. */
+	    doshfunc(list, args, 0, 1);
+	    incompctlfunc = 0;
+	    /* And get the result from the reply parameter. */
+	    if ((r = get_user_var("reply")))
+		while (*r)
+		    addmatch(*r++, NULL);
+	}
+	lastval = lv;
+    }
+    if (cc->mask & (CC_JOBS | CC_RUNNING | CC_STOPPED)) {
+	/* Get job names. */
+	int i;
+	char *j, *jj;
+
+	for (i = 0; i < MAXJOB; i++)
+	    if (jobtab[i].stat & STAT_INUSE) {
+		int stopped = jobtab[i].stat & STAT_STOPPED;
+
+		j = jj = dupstring(jobtab[i].procs->text);
+		/* Find the first word. */
+		for (; *jj; jj++)
+		    if (*jj == ' ') {
+			*jj = '\0';
+			break;
+		    }
+		if ((cc->mask & CC_JOBS) ||
+		    (stopped && (cc->mask & CC_STOPPED)) ||
+		    (!stopped && (cc->mask & CC_RUNNING)))
+		    addmatch(j, NULL);
+	    }
+    }
+    if (cc->str) {
+	/* Get the stuff from a compctl -s. */
+	LinkList foo = newlinklist();
+	LinkNode n;
+	int first = 1, ng = opts[NULLGLOB], oowe = we, oowb = wb;
+	char *tmpbuf;
+
+	opts[NULLGLOB] = 1;
+
+	/* Put the strin in the lexer buffer and call the lexer to *
+	 * get the words we have to expand.                        */
+	zleparse = 1;
+	lexsave();
+	tmpbuf = (char *)halloc(strlen(cc->str) + 5);
+	sprintf(tmpbuf, "foo %s", cc->str); /* KLUDGE! */
+	inpush(tmpbuf, 0, NULL);
+	strinbeg();
+	noaliases = 1;
+	do {
+	    ctxtlex();
+	    if (tok == ENDINPUT || tok == LEXERR)
+		break;
+	    if (!first && tokstr && *tokstr)
+		addlinknode(foo, ztrdup(tokstr));
+	    first = 0;
+	} while (tok != ENDINPUT && tok != LEXERR);
+	noaliases = 0;
+	strinend();
+	inpop();
+	errflag = zleparse = 0;
+	lexrestore();
+	/* Fine, now do full expansion. */
+	prefork(foo, 0);
+	if (!errflag) {
+	    globlist(foo);
+	    if (!errflag)
+		/* And add the resulting words as matches. */
+		for (n = firstnode(foo); n; incnode(n))
+		    addmatch((char *)n->dat, NULL);
+	}
+	opts[NULLGLOB] = ng;
+	we = oowe;
+	wb = oowb;
+    }
+    if (cc->hpat) {
+	/* We have a pattern to take things from the history. */
+	Comp compc = NULL;
+	char *e, *h, hpatsav;
+	Histent he;
+	int i = curhist - 1, n = cc->hnum;
+
+	/* Parse the pattern, if it isn't the null string. */
+	if (*(cc->hpat)) {
+	    char *thpat = dupstring(cc->hpat);
+
+	    tokenize(thpat);
+	    compc = parsereg(thpat);
+	}
+	/* n holds the number of history line we have to search. */
+	if (!n)
+	    n = -1;
+
+	/* Now search the history. */
+	while (n-- && (he = quietgethist(i--))) {
+	    int iwords;
+	    for (iwords = 0; iwords < he->nwords; iwords++) {
+		h = he->text + he->words[iwords*2];
+		e = he->text + he->words[iwords*2+1];
+		hpatsav = *e;
+		*e = '\0';
+		/* We now have a word from the history, ignore it *
+		 * if it begins with a quote or `$'.              */
+		if (*h != '\'' && *h != '"' && *h != '`' && *h != '$' &&
+		    (!compc || domatch(h, compc, 0)))
+		    /* Otherwise add it if it was matched. */
+		    addmatch(dupstring(h), NULL);
+		if (hpatsav)
+		    *e = hpatsav;
+	    }
+	}
+    }
+    if ((t = cc->mask & (CC_ARRAYS | CC_INTVARS | CC_ENVVARS | CC_SCALARS |
+			 CC_READONLYS | CC_SPECIALS | CC_PARAMS)))
+	/* Add various flavours of parameters. */
+	dumphashtable(paramtab, t);
+    if ((t = cc->mask & CC_SHFUNCS))
+	/* Add shell functions. */
+	dumphashtable(shfunctab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
+    if ((t = cc->mask & CC_BUILTINS))
+	/* Add builtins. */
+	dumphashtable(builtintab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
+    if ((t = cc->mask & CC_EXTCMDS))
+	/* Add external commands */
+	dumphashtable(cmdnamtab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
+    if ((t = cc->mask & CC_RESWDS))
+	/* Add reserved words */
+	dumphashtable(reswdtab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
+    if ((t = cc->mask & (CC_ALREG | CC_ALGLOB)))
+	/* Add the two types of aliases. */
+	dumphashtable(aliastab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
+
+    /* If we have no matches, ignore fignore. */
+    if (empty(matches)) {
+	matches = fmatches;
+	firstm = ffirstm;
+	shortest = fshortest;
+	ab = fab;
+	ae = fae;
+	shortl = fshortl;
+    }
+
+    /* Make an array from the list of matches. */
+    makearray(matches);
+    PERMALLOC {
+	amatches = arrdup(amatches);
+	if (firstm)
+	    firstm = ztrdup(firstm);
+	/* And quote the prefixes/suffixes. */
+	if (hasspecial(s)) {
+	    zfree(lpre, lpl);
+	    zfree(lsuf, lsl);
+	    lpre = zalloc(lpl + 1);
+	    memcpy(lpre, s, lpl);
+	    lpre[lpl] = '\0';
+	    lsuf = ztrdup(s + offs);
+	    quotepresuf(&lpre);
+	    quotepresuf(&lsuf);
+	    untokenize(lpre);
+	    untokenize(lsuf);
+	}
+	quotepresuf(&fpre);
+	quotepresuf(&fsuf);
+	quotepresuf(&ppre);
+	quotepresuf(&psuf);
+    } LASTALLOC;
+
+    if (!errflag && cc->ylist) {
+	/* generate the user-defined display list: if anything fails, *
+	 * we silently allow the normal completion list to be used.   */
+	char **yaptr, *uv = NULL;
+	List list;
+
+	if (cc->ylist[0] == '$' || cc->ylist[0] == '(') {
+	    /* from variable */
+	    uv = cc->ylist + (cc->ylist[0] == '$');
+	} else if ((list = getshfunc(cc->ylist)) != &dummy_list) {
+	    /* from function:  pass completions as arg list */
+	    LinkList args = newlinklist();
+	    int addlen = strlen(rpre) + strlen(rsuf) + 1;
+
+	    addlinknode(args, cc->ylist);
+	    for (yaptr = amatches; *yaptr; yaptr++) {
+		/* can't use tricat(). rats. */
+		char *ptr = (char *)halloc(addlen + strlen(*yaptr));
+		sprintf(ptr, "%s%s%s", rpre, *yaptr, rsuf);
+		addlinknode(args, ptr);
+	    }
+
+	    /* No harm in allowing read -l and -c here, too */
+	    incompctlfunc = 1;
+	    doshfunc(list, args, 0, 1);
+	    incompctlfunc = 0;
+	    uv = "reply";
+	}
+	if (uv && (yaptr = get_user_var(uv))) {
+	    PERMALLOC {
+		aylist = arrdup(yaptr);
+	    } LASTALLOC;
+	}
+    }
+
+    /* Get the explanation string we will have to print:    *
+     * do this here in case a -y function alters the messge */
+    if ((expl = cc->explain)) {
+	if (cc->mask & CC_EXPANDEXPL && !parsestr(expl = dupstring(expl))) {
+	    singsub(&expl);
+	    untokenize(expl);
+	}
+	expl = ztrdup(expl);
+    }
+
+    remsuffix = (cc->mask & CC_REMOVE);
+    ccsuffix = cc->suffix;
+
+    validlist = 1;
+    if (nmatches && !errflag)
+	return 0;
+
+    if ((isf || cc->xor) && !parampre) {
+	/* We found no matches, but there is a xor'ed completion: *
+	 * fine, so go back and continue with that compctl.       */
+	errflag = 0;
+	cc = cc->xor;
+	isf = 0;
+	wb = owb;
+	we = owe;
+	cs = ocs;
+	ll = oll;
+	strcpy((char *)line, (char *)ol);
+	offs = oloffs;
+	s = dupstring(os);
+	free(amatches);
+	zsfree(rpre);
+	zsfree(rsuf);
+	zsfree(lpre);
+	zsfree(lsuf);
+	zsfree(ppre);
+	zsfree(psuf);
+	zsfree(fpre);
+	zsfree(fsuf);
+	zsfree(prpre);
+	zsfree(parampre);
+	zsfree(qparampre);
+	zsfree(firstm);
+	if (expl)
+	    zsfree(expl);
+	expl = NULL;
+	if (aylist)
+	    freearray(aylist);
+	aylist = NULL;
+	goto xorrec;
+    }
+
+    /* No matches and xor'ed completion: restore the command line if  *
+     * it was alredy quoted, which is the case when s is untokenized. */
+    if (untokenized)
+	strcpy((char *)line, (char *)ol);
+    return 1;
+}
+
+/* Invalidate the completion list. */
+
+/**/
+void
+invalidatelist(void)
+{
+    if(showinglist == -2)
+	listmatches();
+    if(validlist) {
+	freearray(amatches);
+	if (aylist)
+	    freearray(aylist);
+	aylist = NULL;
+	if (expl)
+	    zsfree(expl);
+	expl = 0;
+	zsfree(rpre);
+	zsfree(rsuf);
+	zsfree(lpre);
+	zsfree(lsuf);
+	zsfree(ppre);
+	zsfree(psuf);
+	zsfree(fpre);
+	zsfree(fsuf);
+	zsfree(prpre);
+	zsfree(parampre);
+	zsfree(qparampre);
+	zsfree(firstm);
+	if (ccmain != &cc_dummy)
+	    freecompctl(ccmain);
+    }
+    lastambig = menucmp = showinglist = validlist = 0;
+    menucur = NULL;
+}
+
+/* Get the words from a variable or a compctl -k list. */
+
+/**/
+static char **
+get_user_var(char *nam)
+{
+    if (!nam)
+	return NULL;
+    else if (*nam == '(') {
+	/* It's a (...) list, not a parameter name. */
+	char *ptr, *s, **uarr, **aptr;
+	int count = 0, notempty = 0, brk = 0;
+	LinkList arrlist = newlinklist();
+
+	ptr = dupstring(nam);
+	s = ptr + 1;
+	while (*++ptr) {
+	    if (*ptr == '\\' && ptr[1])
+		chuck(ptr), notempty = 1;
+	    else if (*ptr == ',' || inblank(*ptr) || *ptr == ')') {
+		if (*ptr == ')')
+		    brk++;
+		if (notempty) {
+		    *ptr = '\0';
+		    count++;
+		    if (*s == '\n')
+			s++;
+		    addlinknode(arrlist, s);
+		}
+		s = ptr + 1;
+		notempty = 0;
+	    } else {
+		notempty = 1;
+		if(*ptr == Meta)
+		    ptr++;
+	    }
+	    if (brk)
+		break;
+	}
+	if (!brk || !count)
+	    return NULL;
+	*ptr = '\0';
+	aptr = uarr = (char **)ncalloc(sizeof(char *) * (count + 1));
+
+	while ((*aptr++ = (char *)ugetnode(arrlist)));
+	uarr[count] = NULL;
+	return uarr;
+    } else {
+	/* Otherwise it should be a parameter name. */
+	char **arr = NULL, *val;
+	if (!(arr = getaparam(nam)) && (val = getsparam(nam))) {
+	    arr = (char **)ncalloc(2*sizeof(char *));
+	    arr[0] = val;
+	    arr[1] = NULL;
+	}
+	return arr;
+    }
+
+}
+
+/* This is strcmp with ignoring backslashes. */
+
+/**/
+static int
+strbpcmp(const void *a, const void *b)
+{
+    char *aa = *((char **)a), *bb = *((char **)b);
+
+    while (*aa && *bb) {
+	if (*aa == '\\')
+	    aa++;
+	if (*bb == '\\')
+	    bb++;
+	if (*aa != *bb)
+	    return (int)(*aa - *bb);
+	if (*aa)
+	    aa++;
+	if (*bb)
+	    bb++;
+    }
+    return (int)(*aa - *bb);
+}
+
+/* Make an array from a linked list */
+
+/**/
+static void
+makearray(LinkList l)
+{
+    char **ap, **bp, **cp;
+    LinkNode nod;
+
+    /* Build an array for the matches. */
+    ap = amatches = (char **)ncalloc(((nmatches = countlinknodes(l)) + 1) *
+				     sizeof(char *));
+
+    /* And copy them into it. */
+    for (nod = firstnode(l); nod; incnode(nod))
+	*ap++ = (char *)getdata(nod);
+    *ap = NULL;
+
+    /* Now sort the array. */
+    qsort((void *) amatches, nmatches, sizeof(char *),
+	       (int (*) _((const void *, const void *)))strbpcmp);
+
+    /* And delete the ones that occur more than once. */
+    for (ap = cp = amatches; *ap; ap++) {
+	*cp++ = *ap;
+	for (bp = ap; bp[1] && !strcmp(*ap, bp[1]); bp++);
+	ap = bp;
+    }
+    *cp = NULL;
+    nmatches = arrlen(amatches);
+}
+
+/* Handle the case were we found more than one match. */
+
+/**/
+static void
+do_ambiguous(void)
+{
+    int p = (usemenu || ispattern), atend = (cs == we);
+    int inv = 0;
+
+    menucmp = 0;
+
+    /* 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 (shortest && shortl == 0 && isset(RECEXACT) &&
+	(usemenu == 0 || unset(AUTOMENU))) {
+	do_single(shortest);
+	invalidatelist();
+	return;
+    }
+    /* Setting lastambig here means that the completion is ambiguous and *
+     * AUTO_MENU might want to start a menu completion next time round,  *
+     * but this might be overridden below if we can complete an          *
+     * unambiguous prefix.                                               */
+    lastambig = 1;
+    if(p) {
+	/* p is set if we are in a position to start using menu completion *
+	 * due to one of the menu completion options, or due to the        *
+	 * menu-complete-word command, or due to using GLOB_COMPLETE which *
+	 * does menu-style completion regardless of the setting of the     *
+	 * normal menu completion options.                                 */
+	do_ambig_menu();
+    } else {
+	/* Sort-of general case: we have an ambiguous completion, and aren't *
+	 * starting menu completion or doing anything really weird.  We need *
+	 * to insert any unambiguous prefix and suffix, if possible.         */
+	if(ab)
+	    inststrlen(firstm, 1, ab);
+	if(ae && !atend)
+	    inststrlen(firstm + strlen(firstm) - ae, 0, ae);
+	if(ab || (ae && !atend))
+	    inv = 1;
+	/* If the LIST_AMBIGUOUS option (meaning roughly `show a list only *
+	 * if the completion is completely ambiguous') is set, and some    *
+	 * prefix was inserted, return now, bypassing the list-displaying  *
+	 * code.  On the way, invalidate the list and note that we don't   *
+	 * want to enter an AUTO_MENU imediately.                          */
+	if(isset(LISTAMBIGUOUS) && inv) {
+	    invalidatelist();
+	    lastambig = 0;
+	    return;
+	}
+    }
+    /* At this point, we might want a completion listing.  Show the listing *
+     * if it is needed.                                                     */
+    if (isset(LISTBEEP))
+	feep();
+    if (isset(AUTOLIST) && !amenu && !showinglist)
+	showinglist = -2;
+    if(inv)
+	invalidatelist();
+}
+
+/* This is a stat that ignores backslashes in the filename.  The `ls' *
+ * parameter says if we have to do lstat() or stat().  I think this   *
+ * should instead be done by use of a general function to expand a    *
+ * filename (stripping backslashes), combined with the actual         *
+ * (l)stat().                                                         */
+
+/**/
+static int
+ztat(char *nam, struct stat *buf, int ls)
+{
+    char b[PATH_MAX], *p;
+
+    for (p = b; p < b + sizeof(b) - 1 && *nam; nam++)
+	if (*nam == '\\' && nam[1])
+	    *p++ = *++nam;
+	else
+	    *p++ = *nam;
+    *p = '\0';
+
+    return ls ? lstat(b, buf) : stat(b, buf);
+}
+
+/* Insert a single match in the command line. */
+
+/**/
+static void
+do_single(char *str)
+{
+    int l;
+    int havesuff = 0;
+
+    fixsuffix();
+
+    if (!menucur) {
+	/* We are currently not in a menu-completion, *
+	 * so set the position variables.             */
+	if (ispattern) {
+	    cs = we;
+	    menupos = wb;
+	} else
+	    menupos = cs;
+	menuwe = (cs == we) || isset(ALWAYSTOEND);
+	menuend = we;
+    }
+    /* If we are already in a menu-completion or if we have done a *
+     * glob completion, we have to delete some of the stuff on the *
+     * command line.                                               */
+    if (menucur) {
+	if (menuinsc) {
+	    cs = menuend + lsl;
+	    foredel(menuinsc);
+	}
+	l = menulen;
+    } else if (ispattern)
+	l = we - wb;
+    else
+	l = 0;
+
+    menuinsc = 0;
+    cs = menupos;
+    foredel(l);
+
+    /* And than we insert the new string. */
+    inststrlen(str, 1, menulen = strlen(str));
+    menuend = cs;
+
+    cs += lsl;
+
+    if (ccsuffix) {
+	/* There is a compctl -S suffix.  Add it. */
+	if (!(haswhat & HAS_SUFFIX) && *ccsuffix) {
+	    havesuff = 1;
+	    inststr(ccsuffix);
+	    menuinsc = ztrlen(ccsuffix);
+	    if (remsuffix && menuwe)
+		makesuffix(menuinsc);
+	}
+	havesuff = 1;
+    } else {
+	/* There is no user-specified suffix, *
+	 * so generate one automagically.     */
+	if(parampre && parambr) {
+	    /*{{*/
+	    /* Completing a parameter in braces.  Add a removable `}' suffix. */
+	    inststrlen("}", 1, 1);
+	    menuinsc++;
+	}
+	if(!(haswhat & HAS_MISC) ||
+	    	  (parampre && isset(AUTOPARAMSLASH))) {
+	    /* If we have only filenames or we completed a parameter name  *
+	     * and AUTO_PARAM_SLASH is set, lets see if it is a directory. *
+	     * If it is, we append a slash.                                */
+	    char *p;
+	    struct stat buf;
+
+	    /* Build the path name. */
+	    if (ispattern || ic || parampre) {
+		int ne = noerrs;
+
+		noerrs = 1;
+
+		if (parampre) {
+		    int pl = strlen(parampre);
+		    p = (char *) ncalloc(pl + strlen(lpre) + strlen(str) +
+					 strlen(lsuf) + 1);
+		    sprintf(p, "%s%s%s%s", parampre, lpre, str, lsuf);
+		    if (pl && p[pl-1] == Inbrace)
+			strcpy(p+pl-1, p+pl);
+		}
+		else if (ic) {
+		    p = (char *) ncalloc(strlen(ppre) + strlen(fpre) + strlen(str) +
+					 strlen(fsuf) + strlen(psuf) + 2);
+		    sprintf(p, "%c%s%s%s%s%s", ic,
+			    ppre, fpre, str, fsuf, psuf);
+		}
+		else
+		    p = dupstring(str);
+		parsestr(p);
+		if (ic)
+		    *p = ic;
+		singsub(&p);
+
+		noerrs = ne;
+	    } else {
+		p = (char *) ncalloc((prpre ? strlen(prpre) : 0) + strlen(fpre) +
+				     strlen(str) + strlen(fsuf) + strlen(psuf) + 3);
+		sprintf(p, "%s%s%s%s%s",
+			(prpre && *prpre) ? prpre : "./", fpre, str,
+			fsuf, psuf);
+	    }
+	    /* And do the stat. */
+	    if (!ztat(p, &buf, 0) && S_ISDIR(buf.st_mode)) {
+		/* It is a directory, so add the slash. */
+		havesuff = 1;
+		inststrlen("/", 1, 1);
+		menuinsc++;
+		if(menuwe && isset(AUTOREMOVESLASH)) {
+		    makesuffix(1);
+		    suffixlen['/'] = 1;
+		}
+	    }
+	}
+    }
+    /* If completing in a brace expansion... */
+    if(complinbrace) {
+	if(havesuff) {
+	    /*{{*/
+	    /* If a suffix was added, and is removable, let *
+	     * `,' and `}' remove it.                       */
+	    if(isset(AUTOPARAMKEYS))
+		suffixlen[','] = suffixlen['}'] = suffixlen[256];
+	} else {
+	    /*{{*/
+	    /* Otherwise, add a `,' suffix, and let `}' remove it. */
+	    havesuff = 1;
+	    inststrlen(",", 1, 1);
+	    menuinsc++;
+	    if(menuwe && isset(AUTOPARAMKEYS))
+		suffixlen[','] = suffixlen['}'] = 1;
+	}
+    } else if(!menucmp && !havesuff) {
+	/* If we didn't add a suffix, add a space, unless we are *
+	 * doing menu completion.                                */
+	inststrlen(" ", 1, 1);
+	menuinsc++;
+	if(menuwe)
+	    makesuffix(1);
+    }
+    if(menuwe && parampre && isset(AUTOPARAMKEYS))
+	makeparamsuffix(parambr, menuinsc);
+
+    if (!menuwe)
+	cs = menuend;
+}
+
+/* This handles the beginning of menu-completion. */
+
+/**/
+static void
+do_ambig_menu(void)
+{
+    menucmp = 1;
+    menucur = NULL;
+    do_single(amatches[0]);
+    menucur = amatches;
+}
+
+/* Return the length of the common prefix of s and t. */
+
+/**/
+int
+pfxlen(char *s, char *t)
+{
+    int i = 0;
+
+    while (*s && *s == *t)
+	s++, t++, i++;
+    return i;
+}
+
+/* Return the length of the common suffix of s and t. */
+
+/**/
+static int
+sfxlen(char *s, char *t)
+{
+    if (*s && *t) {
+	int i = 0;
+	char *s2 = s + strlen(s) - 1, *t2 = t + strlen(t) - 1;
+
+	while (s2 >= s && t2 >= t && *s2 == *t2)
+	    s2--, t2--, i++;
+
+	return i;
+    } else
+	return 0;
+}
+
+/* This is used to print the explanation string. *
+ * It returns the number of lines printed.       */
+
+/**/
+static int
+printfmt(char *fmt, int n, int dopr)
+{
+    char *p = fmt, nc[DIGBUFSIZE];
+    int l = 0, cc = 0;
+
+    for (; *p; p++) {
+	/* Handle the `%' stuff (%% == %, %n == <number of matches>). */
+	if (*p == '%') {
+	    if (*++p) {
+		switch (*p) {
+		case '%':
+		    if (dopr)
+			putc('%', shout);
+		    cc++;
+		    break;
+		case 'n':
+		    sprintf(nc, "%d", n);
+		    if (dopr)
+			fprintf(shout, nc);
+		    cc += strlen(nc);
+		    break;
+		}
+	    } else
+		break;
+	} else {
+	    cc++;
+	    if (*p == '\n') {
+		l += 1 + (cc / columns);
+		cc = 0;
+	    }
+	    if (dopr)
+		putc(*p, shout);
+	}
+    }
+
+    return l + (cc / columns);
+}
+
+/* List the matches.  Note that the list entries are metafied. */
+
+/**/
+void
+listmatches(void)
+{
+    int longest = 1, fct, fw, colsz, t0, t1, ct, up, cl, xup = 0;
+    int off = 0, boff = 0, nboff = 0;
+    int of = (!aylist && isset(LISTTYPES) && !(haswhat & HAS_MISC));
+    char **arr, **ap, sav;
+    int nfpl, nfsl, nlpl, nlsl;
+    int listmax = getiparam("LISTMAX"), litnl = 0;
+    size_t (*strlenfn) _((char const *));
+
+#ifdef DEBUG
+    /* Sanity check */
+    if(!validlist) {
+	showmsg("BUG: listmatches called with bogus list");
+	return;
+    }
+#endif
+
+    /* Calculate lengths of prefixes/suffixes to be added */
+    nfpl = fpre ? niceztrlen(fpre) : 0;
+    nfsl = fsuf ? niceztrlen(fsuf) : 0;
+    nlpl = lpre ? niceztrlen(lpre) : 0;
+    nlsl = lsuf ? niceztrlen(lsuf) : 0;
+
+    /* Calculate the lengths of the prefixes/suffixes we have to ignore
+       during printing. */
+    if (ispattern && !aylist && !(haswhat & (HAS_MISC | HAS_PATHPAT))) {
+	if (ppre && *ppre)
+	    off = strlen(ppre);
+	if (psuf && *psuf) {
+	    boff = strlen(psuf);
+	    nboff = niceztrlen(psuf);
+	}
+    }
+
+    /* Set the cursor below the prompt. */
+    trashzle();
+    showinglist = 0;
+
+    clearflag = (isset(USEZLE) && !termflags &&
+		 (isset(ALWAYSLASTPROMPT) && zmult == 1)) ||
+	(unset(ALWAYSLASTPROMPT) && zmult != 1);
+
+    /* just to keep gcc happy */
+    fw = colsz = up = 0;
+    if (aylist) {
+	arr = aylist;
+	/* If no literal newlines, the remaining code should use strlen() */
+	strlenfn = (size_t (*) _((char const *)))strlen;
+
+	/* The hard bit here is that we are handling newlines literally.   *
+	 * In fact, we are in principle handling all characters literally, *
+	 * but it's quite enough work with just newlines.                  *
+	 * If there are such, we give up trying to print the list as       *
+	 * columns and print as rows, counting the extra newlines.         */
+	ct = 0;
+	for (ap = arr; *ap; ap++) {
+	    ct++;
+	    if (strchr(*ap, '\n'))
+		litnl++;
+	}
+	if (litnl) {
+	    colsz = ct;
+	    up = colsz + nlnct - clearflag;
+	    /* Count real newlines, as well as overflowing lines. */
+	    for (ap = arr; *ap; ap++) {
+		char *nlptr, *sptr = *ap;
+		while (sptr && *sptr) {
+		    up += (nlptr = strchr(sptr, '\n'))
+			? 1 + (nlptr-sptr)/columns
+			   : strlen(sptr)/columns;
+		    sptr = nlptr ? nlptr+1 : NULL;
+		}
+	    }
+	}
+    } else {
+	arr = amatches;
+	ct = nmatches;
+	strlenfn = niceztrlen;
+    }
+
+
+    if (!litnl) {
+	/* Calculate the column width, the number of columns and the
+	   number of lines. */
+	for (ap = arr; *ap; ap++)
+	    if ((cl = strlenfn(*ap + off) - nboff +
+		 ((ispattern || aylist) ? 0 :
+		  (!(haswhat & HAS_MISC) ?
+		   nfpl + nfsl : nlpl + nlsl))) > longest)
+		longest = cl;
+	if (of)
+	    longest++;
+
+	fw = longest + 2;
+	fct = (columns + 1) / fw;
+	if (fct == 0) {
+	    fct = 1;
+	    colsz = ct;
+	    up = colsz + nlnct - clearflag;
+	    for (ap = arr; *ap; ap++)
+		up += (strlenfn(*ap + off) - nboff + of +
+		       ((ispattern || aylist) ? 0 :
+			(!(haswhat & HAS_MISC) ?
+			 nfpl + nfsl : nlpl + nlsl))) / columns;
+	} else {
+	    colsz = (ct + fct - 1) / fct;
+	    up = colsz + nlnct - clearflag + (ct == 0);
+	}
+    }
+
+    /* Print the explanation string, if any. */
+    if (expl) {
+	xup = printfmt(expl, ct, 1) + 1;
+	putc('\n', shout);
+	up += xup;
+    }
+
+    /* Maybe we have to ask if the user wants to see the list. */
+    if ((listmax && ct > listmax) || (!listmax && up >= lines)) {
+	int qup;
+	setterm();
+	qup = printfmt("zsh: do you wish to see all %n possibilities? ", ct, 1);
+	fflush(shout);
+	if (getzlequery() != 'y') {
+	    if (clearflag) {
+		putc('\r', shout);
+		tcmultout(TCUP, TCMULTUP, qup);
+		if (tccan(TCCLEAREOD))
+		    tcout(TCCLEAREOD);
+		tcmultout(TCUP, TCMULTUP, nlnct + xup);
+	    } else
+		putc('\n', shout);
+	    return;
+	}
+	if (clearflag) {
+	    putc('\r', shout);
+	    tcmultout(TCUP, TCMULTUP, qup);
+	    if (tccan(TCCLEAREOD))
+		tcout(TCCLEAREOD);
+	} else
+	    putc('\n', shout);
+	settyinfo(&shttyinfo);
+    }
+
+    /* Now print the matches. */
+    for (t1 = 0; t1 != colsz; t1++) {
+	ap = arr + t1;
+	if (of) {
+	    /* We have to print the file types. */
+	    while (*ap) {
+		int t2;
+		char *pb;
+		struct stat buf;
+
+		/* Build the path name for the stat. */
+		if (ispattern) {
+		    int cut = strlen(*ap) - boff;
+
+		    sav = ap[0][cut];
+		    ap[0][cut] = '\0';
+		    nicezputs(*ap + off, shout);
+		    t2 = niceztrlen(*ap + off);
+		    ap[0][cut] = sav;
+		    pb = *ap;
+		} else {
+		    nicezputs(fpre, shout);
+		    nicezputs(*ap, shout);
+		    nicezputs(fsuf, shout);
+		    t2 = nfpl + niceztrlen(*ap) + nfsl;
+		    pb = (char *) halloc((prpre ? strlen(prpre) : 0) + 3 +
+					 strlen(fpre) + strlen(*ap) + strlen(fsuf));
+		    sprintf(pb, "%s%s%s%s",
+			    (prpre && *prpre) ? prpre : "./", fpre, *ap, fsuf);
+		}
+		if (ztat(pb, &buf, 1))
+		    putc(' ', shout);
+		else
+		    /* Print the file type character. */
+		    putc(file_type(buf.st_mode), shout);
+		for (t0 = colsz; t0 && *ap; t0--, ap++);
+		if (*ap)
+		    /* And add spaces to make the columns aligned. */
+		    for (++t2; t2 < fw; t2++)
+			putc(' ', shout);
+	    }
+	} else
+	    while (*ap) {
+		int t2;
+
+		if (aylist) {
+		    zputs(*ap, shout);
+		    t2 = strlen(*ap);
+		} else if (ispattern) {
+		    int cut = strlen(*ap) - boff;
+
+		    sav = ap[0][cut];
+		    ap[0][cut] = '\0';
+		    nicezputs(*ap + off, shout);
+		    t2 = niceztrlen(*ap + off);
+		    ap[0][cut] = sav;
+		} else if (!(haswhat & HAS_MISC)) {
+		    nicezputs(fpre, shout);
+		    nicezputs(*ap, shout);
+		    nicezputs(fsuf, shout);
+		    t2 = nfpl + niceztrlen(*ap) + nfsl;
+		} else {
+		    nicezputs(lpre, shout);
+		    nicezputs(*ap, shout);
+		    nicezputs(lsuf, shout);
+		    t2 = nlpl + niceztrlen(*ap) + nlsl;
+		}
+		for (t0 = colsz; t0 && *ap; t0--, ap++);
+		if (*ap)
+		    for (; t2 < fw; t2++)
+			putc(' ', shout);
+	    }
+	if (t1 != colsz - 1 || !clearflag)
+	    putc('\n', shout);
+    }
+    if (clearflag)
+	/* Move the cursor up to the prompt, if always_last_prompt *
+	 * is set and all that...                                  */
+	if (up < lines) {
+	    tcmultout(TCUP, TCMULTUP, up);
+	    showinglist = -1;
+	} else
+	    clearflag = 0, putc('\n', shout);
+}
+
+/* This is used to print expansions. */
+
+/**/
+void
+listlist(LinkList l)
+{
+    int hw = haswhat, ip = ispattern;
+    char *lp = lpre, *ls = lsuf;
+    int nm = nmatches, vl = validlist;
+    char **am = amatches, **ay = aylist;
+    char *ex = expl;
+
+    haswhat = HAS_MISC;
+    ispattern = 0;
+    validlist = 1;
+    lpre = lsuf = "";
+    aylist = NULL;
+    expl = NULL;
+
+    makearray(l);
+    listmatches();
+    showinglist = 0;
+
+    expl = ex;
+    amatches = am;
+    aylist = ay;
+    nmatches = nm;
+    validlist = vl;
+    lpre = lp;
+    lsuf = ls;
+    ispattern = ip;
+    haswhat = hw;
+}
+
+/* Expand the history references. */
+
+/**/
+int
+doexpandhist(void)
+{
+    unsigned char *ol;
+    int oll, ocs, ne = noerrs, err;
+
+    DPUTS(useheap, "BUG: useheap in doexpandhist()");
+    HEAPALLOC {
+	pushheap();
+	metafy_line();
+	oll = ll;
+	ocs = cs;
+	ol = (unsigned char *)dupstring((char *)line);
+	expanding = 1;
+	excs = cs;
+	ll = cs = 0;
+	lexsave();
+	/* We push ol as it will remain unchanged */
+	inpush((char *) ol, 0, NULL);
+	strinbeg();
+	noaliases = 1;
+	noerrs = 1;
+	exlast = inbufct;
+	do {
+	    ctxtlex();
+	} while (tok != ENDINPUT && tok != LEXERR);
+	stophist = 2;
+	while (!lexstop)
+	    hgetc();
+	/* We have to save errflags because it's reset in lexrestore. Since  *
+	 * noerrs was set to 1 errflag is true if there was a habort() which *
+	 * means that the expanded string is unusable.                       */
+	err = errflag;
+	noerrs = ne;
+	noaliases = 0;
+	strinend();
+	inpop();
+	zleparse = 0;
+	lexrestore();
+	expanding = 0;
+
+	if (!err) {
+	    cs = excs;
+	    if (strcmp((char *)line, (char *)ol)) {
+		unmetafy_line();
+		/* For vi mode -- reset the beginning-of-insertion pointer   *
+		 * to the beginning of the line.  This seems a little silly, *
+		 * if we are, for example, expanding "exec !!".              */
+		if (viinsbegin > findbol())
+		    viinsbegin = findbol();
+		popheap();
+		LASTALLOC_RETURN 1;
+	    }
+	}
+
+	strcpy((char *)line, (char *)ol);
+	ll = oll;
+	cs = ocs;
+	unmetafy_line();
+
+	popheap();
+    } LASTALLOC;
+    return 0;
+}
+
+/**/
+void
+magicspace(void)
+{
+    c = ' ';
+    selfinsert();
+    doexpandhist();
+}
+
+/**/
+void
+expandhistory(void)
+{
+    if (!doexpandhist())
+	feep();
+}
+
+static int cmdwb, cmdwe;
+
+/**/
+static char *
+getcurcmd(void)
+{
+    int curlincmd;
+    char *s = NULL;
+
+    DPUTS(useheap, "BUG: useheap in getcurcmd()");
+    HEAPALLOC {
+	zleparse = 2;
+	lexsave();
+	metafy_line();
+	inpush(dupstrspace((char *) line), 0, NULL);
+	unmetafy_line();
+	strinbeg();
+	pushheap();
+	do {
+	    curlincmd = incmdpos;
+	    ctxtlex();
+	    if (tok == ENDINPUT || tok == LEXERR)
+		break;
+	    if (tok == STRING && curlincmd) {
+		zsfree(s);
+		s = ztrdup(tokstr);
+		cmdwb = ll - wordbeg;
+		cmdwe = ll + 1 - inbufct;
+	    }
+	}
+	while (tok != ENDINPUT && tok != LEXERR && zleparse);
+	popheap();
+	strinend();
+	inpop();
+	errflag = zleparse = 0;
+	lexrestore();
+    } LASTALLOC;
+    return s;
+}
+
+/**/
+void
+processcmd(void)
+{
+    char *s;
+    int m = zmult;
+
+    s = getcurcmd();
+    if (!s) {
+	feep();
+	return;
+    }
+    zmult = 1;
+    pushline();
+    zmult = m;
+    inststr(bindk->nam);
+    inststr(" ");
+    untokenize(s);
+    HEAPALLOC {
+	inststr(quotename(s, NULL, NULL, NULL));
+    } LASTALLOC;
+    zsfree(s);
+    done = 1;
+}
+
+/**/
+void
+expandcmdpath(void)
+{
+    int oldcs = cs, na = noaliases;
+    char *s, *str;
+
+    noaliases = 1;
+    s = getcurcmd();
+    noaliases = na;
+    if (!s || cmdwb < 0 || cmdwe < cmdwb) {
+	feep();
+	return;
+    }
+    str = findcmd(s);
+    zsfree(s);
+    if (!str) {
+	feep();
+	return;
+    }
+    cs = cmdwb;
+    foredel(cmdwe - cmdwb);
+    spaceinline(strlen(str));
+    strncpy((char *)line + cs, str, strlen(str));
+    cs = oldcs;
+    if (cs >= cmdwe - 1)
+	cs += cmdwe - cmdwb + strlen(str);
+    if (cs > ll)
+	cs = ll;
+    zsfree(str);
+}
+
+/* Extra function added by AR Iano-Fletcher. */
+/* This is a expand/complete in the vein of wash. */
+
+/**/
+void
+expandorcompleteprefix(void)
+{
+    comppref = 1;
+    expandorcomplete();
+    comppref = 0;
+}
diff --git a/Src/Zle/zle_utils.c b/Src/Zle/zle_utils.c
new file mode 100644
index 000000000..8fe3e7f0b
--- /dev/null
+++ b/Src/Zle/zle_utils.c
@@ -0,0 +1,650 @@
+/*
+ * zle_utils.c - miscellaneous line editor utilities
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_utils.pro"
+
+/* Primary cut buffer */
+
+/**/
+struct cutbuffer cutbuf;
+
+/* Emacs-style kill buffer ring */
+
+/**/
+struct cutbuffer kring[KRINGCT];
+/**/
+int kringnum;
+
+/* Vi named cut buffers.  0-25 are the named buffers "a to "z, and *
+ * 26-34 are the numbered buffer stack "1 to "9.                   */
+
+/**/
+struct cutbuffer vibuf[35];
+
+/* the line before last mod (for undo purposes) */
+
+/**/
+char *lastline;
+/**/
+int lastlinesz, lastll;
+
+/* size of line buffer */
+
+/**/
+int linesz;
+
+/* make sure that the line buffer has at least sz chars */
+
+/**/
+void
+sizeline(int sz)
+{
+    while (sz > linesz)
+	line = (unsigned char *)realloc(line, (linesz *= 4) + 2);
+}
+
+/* insert space for ct chars at cursor position */
+
+/**/
+void
+spaceinline(int ct)
+{
+    int i;
+
+    sizeline(ct + ll);
+    for (i = ll; --i >= cs;)
+	line[i + ct] = line[i];
+    ll += ct;
+    line[ll] = '\0';
+
+    if (mark > cs)
+	mark += ct;
+}
+
+/**/
+static void
+shiftchars(int to, int cnt)
+{
+    if (mark >= to + cnt)
+	mark -= cnt;
+    else if (mark > to)
+	mark = to;
+
+    while (to + cnt < ll) {
+	line[to] = line[to + cnt];
+	to++;
+    }
+    line[ll = to] = '\0';
+}
+
+/**/
+void
+backkill(int ct, int dir)
+{
+    int i = (cs -= ct);
+
+    cut(i, ct, dir);
+    shiftchars(i, ct);
+}
+
+/**/
+void
+forekill(int ct, int dir)
+{
+    int i = cs;
+
+    cut(i, ct, dir);
+    shiftchars(i, ct);
+}
+
+/**/
+void
+cut(int i, int ct, int dir)
+{
+    if (zmod.flags & MOD_VIBUF) {
+	struct cutbuffer *b = &vibuf[zmod.vibuf];
+
+	if (!(zmod.flags & MOD_VIAPP) || !b->buf) {
+	    zfree(b->buf, b->len);
+	    b->buf = (char *)zalloc(ct);
+	    memcpy(b->buf, (char *) line + i, ct);
+	    b->len = ct;
+	    b->flags = vilinerange ? CUTBUFFER_LINE : 0;
+	} else {
+	    int len = b->len;
+
+	    if(vilinerange)
+		b->flags |= CUTBUFFER_LINE;
+	    b->buf = realloc(b->buf, ct + len + !!(b->flags & CUTBUFFER_LINE));
+	    if (b->flags & CUTBUFFER_LINE)
+		b->buf[len++] = '\n';
+	    memcpy(b->buf + len, (char *) line + i, ct);
+	    b->len = len + ct;
+	}
+	return;
+    } else {
+	/* Save in "1, shifting "1-"8 along to "2-"9 */
+	int n;
+	zfree(vibuf[34].buf, vibuf[34].len);
+	for(n=34; n>26; n--)
+	    vibuf[n] = vibuf[n-1];
+	vibuf[26].buf = (char *)zalloc(ct);
+	memcpy(vibuf[26].buf, (char *) line + i, ct);
+	vibuf[26].len = ct;
+	vibuf[26].flags = vilinerange ? CUTBUFFER_LINE : 0;
+    }
+    if (!cutbuf.buf) {
+	cutbuf.buf = ztrdup("");
+	cutbuf.len = cutbuf.flags = 0;
+    } else if (!(lastcmd & ZLE_KILL)) {
+	kringnum = (kringnum + 1) % KRINGCT;
+	if (kring[kringnum].buf)
+	    free(kring[kringnum].buf);
+	kring[kringnum] = cutbuf;
+	cutbuf.buf = ztrdup("");
+	cutbuf.len = cutbuf.flags = 0;
+    }
+    if (dir) {
+	char *s = (char *)zalloc(cutbuf.len + ct);
+
+	memcpy(s, (char *) line + i, ct);
+	memcpy(s + ct, cutbuf.buf, cutbuf.len);
+	free(cutbuf.buf);
+	cutbuf.buf = s;
+	cutbuf.len += ct;
+    } else {
+	cutbuf.buf = realloc(cutbuf.buf, cutbuf.len + ct);
+	memcpy(cutbuf.buf + cutbuf.len, (char *) line + i, ct);
+	cutbuf.len += ct;
+    }
+    if(vilinerange)
+	cutbuf.flags |= CUTBUFFER_LINE;
+    else
+	cutbuf.flags &= ~CUTBUFFER_LINE;
+}
+
+/**/
+void
+backdel(int ct)
+{
+    shiftchars(cs -= ct, ct);
+}
+
+/**/
+void
+foredel(int ct)
+{
+    shiftchars(cs, ct);
+}
+
+/**/
+void
+setline(char const *s)
+{
+    sizeline(strlen(s));
+    strcpy((char *) line, s);
+    unmetafy((char *) line, &ll);
+    if ((cs = ll) && invicmdmode())
+	cs--;
+}
+
+/**/
+int
+findbol(void)
+{
+    int x = cs;
+
+    while (x > 0 && line[x - 1] != '\n')
+	x--;
+    return x;
+}
+
+/**/
+int
+findeol(void)
+{
+    int x = cs;
+
+    while (x != ll && line[x] != '\n')
+	x++;
+    return x;
+}
+
+/**/
+void
+findline(int *a, int *b)
+{
+    *a = findbol();
+    *b = findeol();
+}
+
+/* Search for needle in haystack.  Haystack is a metafied string while *
+ * needle is unmetafied and len-long.  Start the search at position    *
+ * pos.  Search forward if dir > 0 otherwise search backward.          */
+
+/**/
+char *
+hstrnstr(char *haystack, int pos, char *needle, int len, int dir, int sens)
+{
+    char *s = haystack + pos;
+
+    if (dir > 0) {
+	while (*s) {
+	    if (metadiffer(s, needle, len) < sens)
+		return s;
+	    s += 1 + (*s == Meta);
+	}
+    } else {
+	for (;;) {
+	    if (metadiffer(s, needle, len) < sens)
+		return s;
+	    if (s == haystack)
+		break;
+	    s -= 1 + (s != haystack+1 && s[-2] == Meta);
+	}
+    }
+    return NULL;
+}
+
+/* Query the user, and return a single character response.  The *
+ * question is assumed to have been printed already, and the    *
+ * cursor is left immediately after the response echoed.        *
+ * (Might cause a problem if this takes it onto the next line.) *
+ * <Tab> is interpreted as 'y'; any other control character is  *
+ * interpreted as 'n'.  If there are any characters in the      *
+ * buffer, this is taken as a negative response, and no         *
+ * characters are read.  Case is folded.                        */
+
+/**/
+int
+getzlequery(void)
+{
+    int c;
+#ifdef FIONREAD
+    int val;
+
+    /* check for typeahead, which is treated as a negative response */
+    ioctl(SHTTY, FIONREAD, (char *)&val);
+    if (val) {
+	putc('n', shout);
+	return 'n';
+    }
+#endif
+
+    /* get a character from the tty and interpret it */
+    c = getkey(0);
+    if (c == '\t')
+	c = 'y';
+    else if (icntrl(c) || c == EOF)
+	c = 'n';
+    else
+	c = tulower(c);
+
+    /* echo response and return */
+    putc(c, shout);
+    return c;
+}
+
+/* Format a string, keybinding style. */
+
+/**/
+char *
+bindztrdup(char *str)
+{
+    int c, len = 1;
+    char *buf, *ptr, *ret;
+
+    for(ptr = str; *ptr; ptr++) {
+	c = *ptr == Meta ? STOUC(*++ptr) ^ 32 : STOUC(*ptr);
+	if(c & 0x80) {
+	    len += 3;
+	    c &= 0x7f;
+	}
+	if(c < 32 || c == 0x7f) {
+	    len++;
+	    c ^= 64;
+	}
+	len += c == '\\' || c == '^';
+	len++;
+    }
+    ptr = buf = zalloc(len);
+    for(; *str; str++) {
+	c = *str == Meta ? STOUC(*++str) ^ 32 : STOUC(*str);
+	if(c & 0x80) {
+	    *ptr++ = '\\';
+	    *ptr++ = 'M';
+	    *ptr++ = '-';
+	    c &= 0x7f;
+	}
+	if(c < 32 || c == 0x7f) {
+	    *ptr++ = '^';
+	    c ^= 64;
+	}
+	if(c == '\\' || c == '^')
+	    *ptr++ = '\\';
+	*ptr++ = c;
+    }
+    *ptr = 0;
+    ret = dquotedztrdup(buf);
+    zsfree(buf);
+    return ret;
+}
+
+/* Display a metafied string, keybinding-style. */
+
+/**/
+int
+printbind(char *str, FILE *stream)
+{
+    char *b = bindztrdup(str);
+    int ret = zputs(b, stream);
+
+    zsfree(b);
+    return ret;
+}
+
+/* Display a message where the completion list normally goes. *
+ * The message must be metafied.                              */
+
+/**/
+void
+showmsg(char const *msg)
+{
+    char const *p;
+    int up = 0, cc = 0, c;
+
+    trashzle();
+    clearflag = isset(USEZLE) && !termflags && isset(ALWAYSLASTPROMPT);
+
+    for(p = msg; (c = *p); p++) {
+	if(c == Meta)
+	    c = *++p ^ 32;
+	if(c == '\n') {
+	    putc('\n', shout);
+	    up += 1 + cc / columns;
+	    cc = 0;
+	} else {
+	    char const *n = nicechar(c);
+	    fputs(n, shout);
+	    cc += strlen(n);
+	}
+    }
+    up += cc / columns;
+
+    if (clearflag) {
+	putc('\r', shout);
+	tcmultout(TCUP, TCMULTUP, up + nlnct);
+    } else
+	putc('\n', shout);
+    showinglist = 0;
+}
+
+/* handle the error flag */
+
+/**/
+void
+feep(void)
+{
+    feepflag = 1;
+}
+
+/**/
+void
+handlefeep(void)
+{
+    if(feepflag)
+	beep();
+    feepflag = 0;
+}
+
+/***************/
+/* undo system */
+/***************/
+
+/* head of the undo list, and the current position */
+
+static struct change *changes, *curchange;
+
+/* list of pending changes, not yet in the undo system */
+
+static struct change *nextchanges, *endnextchanges;
+
+/**/
+void
+initundo(void)
+{
+    nextchanges = NULL;
+    changes = curchange = zalloc(sizeof(*curchange));
+    curchange->prev = curchange->next = NULL;
+    curchange->del = curchange->ins = NULL;
+    lastline = zalloc(lastlinesz = linesz);
+    memcpy(lastline, line, lastll = ll);
+}
+
+/**/
+void
+freeundo(void)
+{
+    freechanges(changes);
+    freechanges(nextchanges);
+    zfree(lastline, lastlinesz);
+}
+
+/**/
+static void
+freechanges(struct change *p)
+{
+    struct change *n;
+
+    for(; p; p = n) {
+	n = p->next;
+	zsfree(p->del);
+	zsfree(p->ins);
+	zfree(p, sizeof(*p));
+    }
+}
+
+/* register pending changes in the undo system */
+
+/**/
+void
+handleundo(void)
+{
+    mkundoent();
+    if(!nextchanges)
+	return;
+    setlastline();
+    if(curchange->next) {
+	freechanges(curchange->next);
+	curchange->next = NULL;
+	zsfree(curchange->del);
+	zsfree(curchange->ins);
+	curchange->del = curchange->ins = NULL;
+    }
+    nextchanges->prev = curchange->prev;
+    if(curchange->prev)
+	curchange->prev->next = nextchanges;
+    else
+	changes = nextchanges;
+    curchange->prev = endnextchanges;
+    endnextchanges->next = curchange;
+    nextchanges = endnextchanges = NULL;
+}
+
+/* add an entry to the undo system, if anything has changed */
+
+/**/
+void
+mkundoent(void)
+{
+    int pre, suf;
+    int sh = ll < lastll ? ll : lastll;
+    struct change *ch;
+
+    if(lastll == ll && !memcmp(lastline, line, ll))
+	return;
+    for(pre = 0; pre < sh && line[pre] == lastline[pre]; )
+	pre++;
+    for(suf = 0; suf < sh - pre &&
+	line[ll - 1 - suf] == lastline[lastll - 1 - suf]; )
+	suf++;
+    ch = zalloc(sizeof(*ch));
+    ch->next = NULL;
+    ch->hist = histline;
+    ch->off = pre;
+    if(suf + pre == lastll)
+	ch->del = NULL;
+    else
+	ch->del = metafy(lastline + pre, lastll - pre - suf, META_DUP);
+    if(suf + pre == ll)
+	ch->ins = NULL;
+    else
+	ch->ins = metafy((char *)line + pre, ll - pre - suf, META_DUP);
+    if(nextchanges) {
+	ch->flags = CH_PREV;
+	ch->prev = endnextchanges;
+	endnextchanges->flags |= CH_NEXT;
+	endnextchanges->next = ch;
+    } else {
+	nextchanges = ch;
+	ch->flags = 0;
+	ch->prev = NULL;
+    }
+    endnextchanges = ch;
+}
+
+/* set lastline to match line */
+
+/**/
+void
+setlastline(void)
+{
+    if(lastlinesz != linesz)
+	lastline = realloc(lastline, lastlinesz = linesz);
+    memcpy(lastline, line, lastll = ll);
+}
+
+/* move backwards through the change list */
+
+/**/
+void
+undo(void)
+{
+    handleundo();
+    do {
+	if(!curchange->prev) {
+	    feep();
+	    return;
+	}
+	unapplychange(curchange = curchange->prev);
+    } while(curchange->flags & CH_PREV);
+    setlastline();
+}
+
+/**/
+static void
+unapplychange(struct change *ch)
+{
+    if(ch->hist != histline) {
+	remember_edits();
+	setline(zle_get_event(histline = ch->hist));
+    }
+    cs = ch->off;
+    if(ch->ins)
+	foredel(ztrlen(ch->ins));
+    if(ch->del) {
+	char *c = ch->del;
+
+	spaceinline(ztrlen(c));
+	for(; *c; c++)
+	    if(*c == Meta)
+		line[cs++] = STOUC(*++c) ^ 32;
+	    else
+		line[cs++] = STOUC(*c);
+    }
+}
+
+/* move forwards through the change list */
+
+/**/
+void
+redo(void)
+{
+    handleundo();
+    do {
+	if(!curchange->next) {
+	    feep();
+	    return;
+	}
+	applychange(curchange);
+	curchange = curchange->next;
+    } while(curchange->prev->flags & CH_NEXT);
+    setlastline();
+}
+
+/**/
+static void
+applychange(struct change *ch)
+{
+    if(ch->hist != histline) {
+	remember_edits();
+	setline(zle_get_event(histline = ch->hist));
+    }
+    cs = ch->off;
+    if(ch->del)
+	foredel(ztrlen(ch->del));
+    if(ch->ins) {
+	char *c = ch->ins;
+
+	spaceinline(ztrlen(c));
+	for(; *c; c++)
+	    if(*c == Meta)
+		line[cs++] = STOUC(*++c) ^ 32;
+	    else
+		line[cs++] = STOUC(*c);
+    }
+}
+
+/* vi undo: toggle between the end of the undo list and the preceding point */
+
+/**/
+void
+viundochange(void)
+{
+    handleundo();
+    if(curchange->next) {
+	do {
+	    applychange(curchange);
+	    curchange = curchange->next;
+	} while(curchange->next);
+	setlastline();
+    } else
+	undo();
+}
diff --git a/Src/Zle/zle_vi.c b/Src/Zle/zle_vi.c
new file mode 100644
index 000000000..a599d8091
--- /dev/null
+++ b/Src/Zle/zle_vi.c
@@ -0,0 +1,925 @@
+/*
+ * zle_vi.c - vi-specific functions
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_vi.pro"
+
+/* != 0 if we're getting a vi range */
+
+/**/
+int virangeflag;
+
+/* kludge to get cw and dw to work right */
+
+/**/
+int wordflag;
+
+/* != 0 if we're killing lines into a buffer, vi-style */
+
+/**/
+int vilinerange;
+
+/* last vi change buffer, for vi change repetition */
+
+/**/
+int vichgbufsz, vichgbufptr, vichgflag;
+
+/**/
+char *vichgbuf;
+
+/* point where vi insert mode was last entered */
+
+/**/
+int viinsbegin;
+
+static struct modifier lastmod;
+static int inrepeat, vichgrepeat;
+
+/**/
+static void
+startvichange(int im)
+{
+    if (im != -1) {
+	insmode = im;
+	vichgflag = 1;
+    }
+    if (inrepeat) {
+	zmod = lastmod;
+	inrepeat = vichgflag = 0;
+	vichgrepeat = 1;
+    } else {
+	lastmod = zmod;
+	if (vichgbuf)
+	    free(vichgbuf);
+	vichgbuf = (char *)zalloc(vichgbufsz = 16);
+	vichgbuf[0] = c;
+	vichgbufptr = 1;
+	vichgrepeat = 0;
+    }
+}
+
+/**/
+static void
+startvitext(int im)
+{
+    startvichange(im);
+    selectkeymap("main", 1);
+    undoing = 0;
+    viinsbegin = cs;
+}
+
+/**/
+int
+vigetkey(void)
+{
+    Keymap mn = openkeymap("main");
+    char m[3], *str;
+    Thingy cmd;
+
+    if((c = getkey(0)) == EOF) {
+	feep();
+	return -1;
+    }
+
+    m[0] = c;
+    metafy(m, 1, META_NOALLOC);
+    if(mn)
+	cmd = keybind(mn, m, &str);
+    else
+	cmd = t_undefinedkey;
+
+    if (!cmd || cmd == Th(z_sendbreak)) {
+	feep();
+	return -1;
+    } else if (cmd == Th(z_quotedinsert)) {
+	if ((c = getkey(0)) == EOF) {
+	    feep();
+	    return -1;
+	}
+    } else if(cmd == Th(z_viquotedinsert)) {
+	char sav = line[cs];
+
+	line[cs] = '^';
+	refresh();
+	c = getkey(0);
+	line[cs] = sav;
+	if(c == EOF) {
+	    feep();
+	    return -1;
+	}
+    } else if (cmd == Th(z_vicmdmode))
+	return -1;
+    return c;
+}
+
+/**/
+static int
+getvirange(int wf)
+{
+    int pos = cs;
+    int mult1 = zmult, hist1 = histline;
+    Thingy k2;
+
+    virangeflag = 1;
+    wordflag = wf;
+    /* Now we need to execute the movement command, to see where it *
+     * actually goes.  virangeflag here indicates to the movement   *
+     * function that it should place the cursor at the end of the   *
+     * range, rather than where the cursor would actually go if it  *
+     * were executed normally.  This makes a difference to some     *
+     * commands, but not all.  For example, if searching forward    *
+     * for a character, under normal circumstances the cursor lands *
+     * on the character.  For a range, the range must include the   *
+     * character, so the cursor gets placed after the character if  *
+     * virangeflag is set.  vi-match-bracket needs to change the    *
+     * value of virangeflag under some circumstances, meaning that  *
+     * we need to change the *starting* position.                   */
+    zmod.flags &= ~MOD_TMULT;
+    do {
+	vilinerange = 0;
+	prefixflag = 0;
+	if (!(k2 = getkeycmd()) || (k2->flags & DISABLED) ||
+		k2 == Th(z_sendbreak)) {
+	    wordflag = 0;
+	    virangeflag = 0;
+	    feep();
+	    return -1;
+	}
+	if(k2 == bindk)
+	    /* The command key is repeated: a number of lines is used. */
+	    dovilinerange();
+	else
+	    execzlefunc(k2);
+	if(vichgrepeat)
+	    zmult = mult1;
+	else
+	    zmult = mult1 * zmod.tmult;
+    } while(prefixflag);
+    wordflag = 0;
+    virangeflag = 0;
+
+    /* It is an error to use a non-movement command to delimit the *
+     * range.  We here reject the case where the command modified  *
+     * the line, or selected a different history line.             */
+    if(histline != hist1 || ll != lastll || memcmp(line, lastline, ll)) {
+	histline = hist1;
+	memcpy(line, lastline, ll = lastll);
+	cs = pos;
+	feep();
+	return -1;
+    }
+
+    /* Can't handle an empty file.  Also, if the movement command *
+     * failed, or didn't move, it is an error.                    */
+    if (!ll || (cs == pos && virangeflag != 2)) {
+	feep();
+	return -1;
+    }
+
+    /* vi-match-bracket changes the value of virangeflag when *
+     * moving to the opening bracket, meaning that we need to *
+     * change the *starting* position.                        */
+    if(virangeflag == -1)
+	pos++;
+
+    /* Get the range the right way round.  cs is placed at the *
+     * start of the range, and pos (the return value of this   *
+     * function) is the end.                                   */
+    if (cs > pos) {
+	int tmp = cs;
+	cs = pos;
+	pos = tmp;
+    }
+
+    /* Was it a line-oriented move?  If so, the command will have set *
+     * the vilinerange flag.  In this case, entire lines are taken,   *
+     * rather than just the sequence of characters delimited by pos   *
+     * and cs.  The terminating newline is left out of the range,     *
+     * which the real command must deal with appropriately.  At this  *
+     * point we just need to make the range encompass entire lines.   */
+    if(vilinerange) {
+	int newcs = findbol();
+	cs = pos;
+	pos = findeol();
+	cs = newcs;
+    }
+    return pos;
+}
+
+/**/
+static void
+dovilinerange(void)
+{
+    int pos = cs, n = zmult;
+
+    /* A number of lines is taken as the range.  The current line *
+     * is included.  If the repeat count is positive the lines go *
+     * downward, otherwise upward.  The repeat count gives the    *
+     * number of lines.                                           */
+    vilinerange = 1;
+    if (!n) {
+	feep();
+	return;
+    }
+    if (n > 0) {
+	while(n-- && cs <= ll)
+	    cs = findeol() + 1;
+	if (n != -1) {
+	    cs = pos;
+	    feep();
+	    return;
+	}
+	cs--;
+    } else {
+	while(n++ && cs >= 0)
+	    cs = findbol() - 1;
+	if (n != 1) {
+	    cs = pos;
+	    feep();
+	    return;
+	}
+	cs++;
+    }
+    virangeflag = 2;
+}
+
+/**/
+void
+viaddnext(void)
+{
+    if (cs != findeol())
+	cs++;
+    startvitext(1);
+}
+
+/**/
+void
+viaddeol(void)
+{
+    cs = findeol();
+    startvitext(1);
+}
+
+/**/
+void
+viinsert(void)
+{
+    startvitext(1);
+}
+
+/**/
+void
+viinsertbol(void)
+{
+    vifirstnonblank();
+    startvitext(1);
+}
+
+/**/
+void
+videlete(void)
+{
+    int c2;
+
+    startvichange(1);
+    if ((c2 = getvirange(0)) != -1) {
+	forekill(c2 - cs, 0);
+	if (vilinerange && ll) {
+	    if (cs == ll)
+		cs--;
+	    foredel(1);
+	    vifirstnonblank();
+	}
+    }
+    vichgflag = 0;
+}
+
+/**/
+void
+videletechar(void)
+{
+    int n = zmult;
+
+    startvichange(-1);
+    /* handle negative argument */
+    if (n < 0) {
+	zmult = -n;
+	vibackwarddeletechar();
+	zmult = n;
+	return;
+    }
+    /* it is an error to be on the end of line */
+    if (cs == ll || line[cs] == '\n') {
+	feep();
+	return;
+    }
+    /* Put argument into the acceptable range -- it is not an error to  *
+     * specify a greater count than the number of available characters. */
+    if (n > findeol() - cs)
+	n = findeol() - cs;
+    /* do the deletion */
+    forekill(n, 0);
+}
+
+/**/
+void
+vichange(void)
+{
+    int c2;
+
+    startvichange(1);
+    if ((c2 = getvirange(1)) != -1) {
+	forekill(c2 - cs, 0);
+	selectkeymap("main", 1);
+	viinsbegin = cs;
+	undoing = 0;
+    }
+}
+
+/**/
+void
+visubstitute(void)
+{
+    int n = zmult;
+
+    startvichange(1);
+    if (n < 0) {
+	feep();
+	return;
+    }
+    /* it is an error to be on the end of line */
+    if (cs == ll || line[cs] == '\n') {
+	feep();
+	return;
+    }
+    /* Put argument into the acceptable range -- it is not an error to  *
+     * specify a greater count than the number of available characters. */
+    if (n > findeol() - cs)
+	n = findeol() - cs;
+    /* do the substitution */
+    forekill(n, 0);
+    startvitext(1);
+}
+
+/**/
+void
+vichangeeol(void)
+{
+    forekill(findeol() - cs, 0);
+    startvitext(1);
+}
+
+/**/
+void
+vichangewholeline(void)
+{
+    vifirstnonblank();
+    vichangeeol();
+}
+
+/**/
+void
+viyank(void)
+{
+    int oldcs = cs, c2;
+
+    startvichange(1);
+    if ((c2 = getvirange(0)) != -1)
+	cut(cs, c2 - cs, 0);
+    vichgflag = 0;
+    cs = oldcs;
+}
+
+/**/
+void
+viyankeol(void)
+{
+    int x = findeol();
+
+    startvichange(-1);
+    if (x == cs) {
+	feep();
+	return;
+    }
+    cut(cs, x - cs, 0);
+}
+
+/**/
+void
+viyankwholeline(void)
+{
+    int bol = findbol(), oldcs = cs;
+    int n = zmult;
+
+    startvichange(-1);
+    if (n < 1)
+	return;
+    while(n--) {
+     if (cs > ll) {
+	feep();
+	cs = oldcs;
+	return;
+     }
+     cs = findeol() + 1;
+    }
+    vilinerange = 1;
+    cut(bol, cs - bol - 1, 0);
+    cs = oldcs;
+}
+
+/**/
+void
+vireplace(void)
+{
+    startvitext(0);
+}
+
+/* vi-replace-chars has some oddities relating to vi-repeat-change.  In *
+ * the real vi, if one does 3r at the end of a line, it feeps without   *
+ * reading the argument, and won't repeat the action.  A successful rx  *
+ * followed by 3. at the end of a line (or 3rx followed by . at the end *
+ * of a line) will obviously feep after the ., even though it has the   *
+ * argument available.  Here repeating is tied very closely to argument *
+ * reading, so some trickery is needed to emulate this.  When repeating *
+ * a change, we always read the argument normally, even if the count    *
+ * was bad.  When recording a change for repeating, and a bad count is  *
+ * given, we squash the repeat buffer to avoid repeating the partial    *
+ * command; we've lost the previous change, but that can't be avoided   *
+ * without a rewrite of the repeat code.                                */
+
+/**/
+void
+vireplacechars(void)
+{
+    int ch, n = zmult;
+
+    startvichange(1);
+    /* check argument range */
+    if (n < 1 || n + cs > findeol()) {
+	if(vichgrepeat) {
+	    int ofeep = feepflag;
+	    vigetkey();
+	    feepflag = ofeep;
+	}
+	if(vichgflag) {
+	    free(vichgbuf);
+	    vichgbuf = NULL;
+	    vichgflag = 0;
+	}
+	feep();
+	return;
+    }
+    /* get key */
+    if((ch = vigetkey()) == -1) {
+	vichgflag = 0;
+	feep();
+	return;
+    }
+    /* do change */
+    if (ch == '\r' || ch == '\n') {
+	/* <return> handled specially */
+	cs += n - 1;
+	backkill(n - 1, 0);
+	line[cs++] = '\n';
+    } else {
+	while (n--)
+	    line[cs++] = ch;
+	cs--;
+    }
+    vichgflag = 0;
+}
+
+/**/
+void
+vicmdmode(void)
+{
+    if (invicmdmode() || selectkeymap("vicmd", 0))
+	feep();
+    undoing = 1;
+    vichgflag = 0;
+    if (cs != findbol())
+	cs--;
+}
+
+/**/
+void
+viopenlinebelow(void)
+{
+    cs = findeol();
+    spaceinline(1);
+    line[cs++] = '\n';
+    startvitext(1);
+}
+
+/**/
+void
+viopenlineabove(void)
+{
+    cs = findbol();
+    spaceinline(1);
+    line[cs] = '\n';
+    startvitext(1);
+}
+
+/**/
+void
+vioperswapcase(void)
+{
+    int oldcs, c2;
+
+    /* get the range */
+    startvichange(1);
+    if ((c2 = getvirange(0)) != -1) {
+	oldcs = cs;
+	/* swap the case of all letters within range */
+	while (cs < c2) {
+	    if (islower(line[cs]))
+		line[cs] = tuupper(line[cs]);
+	    else if (isupper(line[cs]))
+		line[cs] = tulower(line[cs]);
+	    cs++;
+	}
+	/* go back to the first line of the range */
+	cs = oldcs;
+	vifirstnonblank();
+    }
+    vichgflag = 0;
+}
+
+/**/
+void
+virepeatchange(void)
+{
+    /* make sure we have a change to repeat */
+    if (!vichgbuf || vichgflag) {
+	feep();
+	return;
+    }
+    /* restore or update the saved count and buffer */
+    if (zmod.flags & MOD_MULT) {
+	lastmod.mult = zmod.mult;
+	lastmod.flags |= MOD_MULT;
+    }
+    if (zmod.flags & MOD_VIBUF) {
+	lastmod.vibuf = zmod.vibuf;
+	lastmod.flags = (lastmod.flags & ~MOD_VIAPP) |
+	    MOD_VIBUF | (zmod.flags & MOD_VIAPP);
+    }
+    /* repeat the command */
+    inrepeat = 1;
+    ungetkeys(vichgbuf, vichgbufptr);
+}
+
+/**/
+void
+viindent(void)
+{
+    int oldcs = cs, c2;
+
+    /* get the range */
+    startvichange(1);
+    if ((c2 = getvirange(0)) == -1) {
+	vichgflag = 0;
+	return;
+    }
+    vichgflag = 0;
+    /* must be a line range */
+    if (!vilinerange) {
+	feep();
+	cs = oldcs;
+	return;
+    }
+    oldcs = cs;
+    /* add a tab to the beginning of each line within range */
+    while (cs < c2) {
+	spaceinline(1);
+	line[cs] = '\t';
+	cs = findeol() + 1;
+    }
+    /* go back to the first line of the range */
+    cs = oldcs;
+    vifirstnonblank();
+}
+
+/**/
+void
+viunindent(void)
+{
+    int oldcs = cs, c2;
+
+    /* get the range */
+    startvichange(1);
+    if ((c2 = getvirange(0)) == -1) {
+	vichgflag = 0;
+	return;
+    }
+    vichgflag = 0;
+    /* must be a line range */
+    if (!vilinerange) {
+	feep();
+	cs = oldcs;
+	return;
+    }
+    oldcs = cs;
+    /* remove a tab from the beginning of each line within range */
+    while (cs < c2) {
+	if (line[cs] == '\t')
+	    foredel(1);
+	cs = findeol() + 1;
+    }
+    /* go back to the first line of the range */
+    cs = oldcs;
+    vifirstnonblank();
+}
+
+/**/
+void
+vibackwarddeletechar(void)
+{
+    int n = zmult;
+
+    if (invicmdmode())
+	startvichange(-1);
+    /* handle negative argument */
+    if (n < 0) {
+	zmult = -n;
+	videletechar();
+	zmult = n;
+	return;
+    }
+    /* It is an error to be at the beginning of the line, or (in *
+     * insert mode) to delete past the beginning of insertion.   */
+    if ((!invicmdmode() && cs - n < viinsbegin) || cs == findbol()) {
+	feep();
+	return;
+    }
+    /* Put argument into the acceptable range -- it is not an error to  *
+     * specify a greater count than the number of available characters. */
+    if (n > cs - findbol())
+	n = cs - findbol();
+    /* do the deletion */
+    backkill(n, 1);
+}
+
+/**/
+void
+vikillline(void)
+{
+    if (viinsbegin > cs) {
+	feep();
+	return;
+    }
+    backdel(cs - viinsbegin);
+}
+
+/**/
+void
+viputbefore(void)
+{
+    Cutbuffer buf = &cutbuf;
+    int n = zmult;
+
+    startvichange(-1);
+    if (n < 0)
+	return;
+    if (zmod.flags & MOD_VIBUF)
+	buf = &vibuf[zmod.vibuf];
+    if (!buf->buf) {
+	feep();
+	return;
+    }
+    if(buf->flags & CUTBUFFER_LINE) {
+	cs = findbol();
+	spaceinline(buf->len + 1);
+	memcpy((char *)line + cs, buf->buf, buf->len);
+	line[cs + buf->len] = '\n';
+	vifirstnonblank();
+    } else {
+	while (n--) {
+	    spaceinline(buf->len);
+	    memcpy((char *)line + cs, buf->buf, buf->len);
+	    cs += buf->len;
+	}
+	if (cs)
+	    cs--;
+    }
+}
+
+/**/
+void
+viputafter(void)
+{
+    Cutbuffer buf = &cutbuf;
+    int n = zmult;
+
+    startvichange(-1);
+    if (n < 0)
+	return;
+    if (zmod.flags & MOD_VIBUF)
+	buf = &vibuf[zmod.vibuf];
+    if (!buf->buf) {
+	feep();
+	return;
+    }
+    if(buf->flags & CUTBUFFER_LINE) {
+	cs = findeol();
+	spaceinline(buf->len + 1);
+	line[cs++] = '\n';
+	memcpy((char *)line + cs, buf->buf, buf->len);
+	vifirstnonblank();
+    } else {
+	if (cs != findeol())
+	    cs++;
+	while (n--) {
+	    spaceinline(buf->len);
+	    memcpy((char *)line + cs, buf->buf, buf->len);
+	    cs += buf->len;
+	}
+	if (cs)
+	    cs--;
+    }
+
+}
+
+/**/
+void
+vijoin(void)
+{
+    int x;
+
+    startvichange(-1);
+    if ((x = findeol()) == ll) {
+	feep();
+	return;
+    }
+    cs = x + 1;
+    for (x = 1; cs != ll && iblank(line[cs]); cs++, x++);
+    backdel(x);
+    if (cs && iblank(line[cs-1]))
+	cs--;
+    else {
+	spaceinline(1);
+	line[cs] = ' ';
+    }
+}
+
+/**/
+void
+viswapcase(void)
+{
+    int eol, n = zmult;
+
+    startvichange(-1);
+    if (n < 1)
+	return;
+    eol = findeol();
+    while (cs < eol && n--) {
+	if (islower(line[cs]))
+	    line[cs] = tuupper(line[cs]);
+	else if (isupper(line[cs]))
+	    line[cs] = tulower(line[cs]);
+	cs++;
+    }
+    if (cs && cs == eol)
+	cs--;
+}
+
+/**/
+void
+vicapslockpanic(void)
+{
+    beep();
+    statusline = "press a lowercase key to continue";
+    statusll = strlen(statusline);
+    refresh();
+    while (!islower(getkey(0)));
+    statusline = NULL;
+}
+
+/**/
+void
+visetbuffer(void)
+{
+    int ch;
+
+    if ((zmod.flags & MOD_VIBUF) ||
+	(((ch = getkey(0)) < '1' || ch > '9') &&
+	 (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z'))) {
+	feep();
+	return;
+    }
+    if (ch >= 'A' && ch <= 'Z')	/* needed in cut() */
+	zmod.flags |= MOD_VIAPP;
+    else
+	zmod.flags &= ~MOD_VIAPP;
+    zmod.vibuf = tulower(ch) + (idigit(ch) ? -'1' + 26 : -'a');
+    zmod.flags |= MOD_VIBUF;
+    prefixflag = 1;
+}
+
+/**/
+void
+vikilleol(void)
+{
+    int n = findeol() - cs;
+
+    startvichange(-1);
+    if (!n) {
+	/* error -- line already empty */
+	feep();
+	return;
+    }
+    /* delete to end of line */
+    forekill(findeol() - cs, 0);
+}
+
+/**/
+void
+vipoundinsert(void)
+{
+    int oldcs = cs;
+
+    startvichange(-1);
+    vifirstnonblank();
+    if(line[cs] != '#') {
+	spaceinline(1);
+	line[cs] = '#';
+	if(cs <= viinsbegin)
+	    viinsbegin++;
+	cs = oldcs + (cs <= oldcs);
+    } else {
+	foredel(1);
+	if (cs < viinsbegin)
+	    viinsbegin--;
+	cs = oldcs - (cs < oldcs);
+    }
+}
+
+/**/
+void
+viquotedinsert(void)
+{
+#ifndef HAS_TIO
+    struct sgttyb sob;
+#endif
+
+    spaceinline(1);
+    line[cs] = '^';
+    refresh();
+#ifndef HAS_TIO
+    sob = shttyinfo.sgttyb;
+    sob.sg_flags = (sob.sg_flags | RAW) & ~ECHO;
+    ioctl(SHTTY, TIOCSETN, &sob);
+#endif
+    c = getkey(0);
+#ifndef HAS_TIO
+    setterm();
+#endif
+    foredel(1);
+    if(c < 0)
+	feep();
+    else
+	selfinsert();
+}
+
+/* the 0 key in vi: continue a repeat count in the manner of      *
+ * digit-argument if possible, otherwise do vi-beginning-of-line. */
+
+/**/
+void
+vidigitorbeginningofline(void)
+{
+    if(zmod.flags & MOD_TMULT)
+	digitargument();
+    else {
+	removesuffix();
+	invalidatelist();
+	vibeginningofline();
+    }
+}
diff --git a/Src/Zle/zle_widget.sed b/Src/Zle/zle_widget.sed
new file mode 100644
index 000000000..635322b42
--- /dev/null
+++ b/Src/Zle/zle_widget.sed
@@ -0,0 +1,7 @@
+/^ *W(/{
+    s/[^,]*, *t_/    wi_/
+    s/ *,.*/,/
+    P
+    s/    wi_\(.*\),/#define w_\1 (\&widgets[wi_\1])/
+    P
+}
diff --git a/Src/Zle/zle_word.c b/Src/Zle/zle_word.c
new file mode 100644
index 000000000..923216ef8
--- /dev/null
+++ b/Src/Zle/zle_word.c
@@ -0,0 +1,477 @@
+/*
+ * zle_word.c - word-related editor functions
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_word.pro"
+
+/**/
+void
+forwardword(void)
+{
+    int n = zmult;
+
+    if (n < 0) {
+	zmult = -n;
+	backwardword();
+	zmult = n;
+	return;
+    }
+    while (n--) {
+	while (cs != ll && iword(line[cs]))
+	    cs++;
+	if (wordflag && !n)
+	    return;
+	while (cs != ll && !iword(line[cs]))
+	    cs++;
+    }
+}
+
+/**/
+void
+viforwardword(void)
+{
+    int n = zmult;
+
+    if (n < 0) {
+	zmult = -n;
+	backwardword();
+	zmult = n;
+	return;
+    }
+    while (n--) {
+	if (iident(line[cs]))
+	    while (cs != ll && iident(line[cs]))
+		cs++;
+	else
+	    while (cs != ll && !iident(line[cs]) && !iblank(line[cs]))
+		cs++;
+	if (wordflag && !n)
+	    return;
+	while (cs != ll && iblank(line[cs]))
+	    cs++;
+    }
+}
+
+/**/
+void
+viforwardblankword(void)
+{
+    int n = zmult;
+
+    if (n < 0) {
+	zmult = -n;
+	vibackwardblankword();
+	zmult = n;
+	return;
+    }
+    while (n--) {
+	while (cs != ll && !iblank(line[cs]))
+	    cs++;
+	if (wordflag && !n)
+	    return;
+	while (cs != ll && iblank(line[cs]))
+	    cs++;
+    }
+}
+
+/**/
+void
+emacsforwardword(void)
+{
+    int n = zmult;
+
+    if (n < 0) {
+	zmult = -n;
+	emacsbackwardword();
+	zmult = n;
+	return;
+    }
+    while (n--) {
+	while (cs != ll && !iword(line[cs]))
+	    cs++;
+	if (wordflag && !n)
+	    return;
+	while (cs != ll && iword(line[cs]))
+	    cs++;
+    }
+}
+
+/**/
+void
+viforwardblankwordend(void)
+{
+    int n = zmult;
+
+    if (n < 0)
+	return;
+    while (n--) {
+	while (cs != ll && iblank(line[cs + 1]))
+	    cs++;
+	while (cs != ll && !iblank(line[cs + 1]))
+	    cs++;
+    }
+    if (cs != ll && virangeflag)
+	cs++;
+}
+
+/**/
+void
+viforwardwordend(void)
+{
+    int n = zmult;
+
+    if (n < 0) {
+	zmult = -n;
+	backwardword();
+	zmult = n;
+	return;
+    }
+    while (n--) {
+	if (iblank(line[cs + 1]))
+	    while (cs != ll && iblank(line[cs + 1]))
+		cs++;
+	if (iident(line[cs + 1]))
+	    while (cs != ll && iident(line[cs + 1]))
+		cs++;
+	else
+	    while (cs != ll && !iident(line[cs + 1]) && !iblank(line[cs + 1]))
+		cs++;
+    }
+    if (cs != ll && virangeflag)
+	cs++;
+}
+
+/**/
+void
+backwardword(void)
+{
+    int n = zmult;
+
+    if (n < 0) {
+	zmult = -n;
+	forwardword();
+	zmult = n;
+	return;
+    }
+    while (n--) {
+	while (cs && !iword(line[cs - 1]))
+	    cs--;
+	while (cs && iword(line[cs - 1]))
+	    cs--;
+    }
+}
+
+/**/
+void
+vibackwardword(void)
+{
+    int n = zmult;
+
+    if (n < 0) {
+	zmult = -n;
+	backwardword();
+	zmult = n;
+	return;
+    }
+    while (n--) {
+	while (cs && iblank(line[cs - 1]))
+	    cs--;
+	if (iident(line[cs - 1]))
+	    while (cs && iident(line[cs - 1]))
+		cs--;
+	else
+	    while (cs && !iident(line[cs - 1]) && !iblank(line[cs - 1]))
+		cs--;
+    }
+}
+
+/**/
+void
+vibackwardblankword(void)
+{
+    int n = zmult;
+
+    if (n < 0) {
+	zmult = -n;
+	viforwardblankword();
+	zmult = n;
+	return;
+    }
+    while (n--) {
+	while (cs && iblank(line[cs - 1]))
+	    cs--;
+	while (cs && !iblank(line[cs - 1]))
+	    cs--;
+    }
+}
+
+/**/
+void
+emacsbackwardword(void)
+{
+    int n = zmult;
+
+    if (n < 0) {
+	zmult = -n;
+	emacsforwardword();
+	zmult = n;
+	return;
+    }
+    while (n--) {
+	while (cs && !iword(line[cs - 1]))
+	    cs--;
+	while (cs && iword(line[cs - 1]))
+	    cs--;
+    }
+}
+
+/**/
+void
+backwarddeleteword(void)
+{
+    int x = cs, n = zmult;
+
+    if (n < 0) {
+	zmult = -n;
+	deleteword();
+	zmult = n;
+	return;
+    }
+    while (n--) {
+	while (x && !iword(line[x - 1]))
+	    x--;
+	while (x && iword(line[x - 1]))
+	    x--;
+    }
+    backdel(cs - x);
+}
+
+/**/
+void
+vibackwardkillword(void)
+{
+    int x = cs, lim = (viinsbegin > findbol()) ? viinsbegin : findbol();
+    int n = zmult;
+
+    if (n < 0) {
+	feep();
+	return;
+    }
+/* this taken from "vibackwardword" */
+    while (n--) {
+	while ((x > lim) && iblank(line[x - 1]))
+	    x--;
+	if (iident(line[x - 1]))
+	    while ((x > lim) && iident(line[x - 1]))
+		x--;
+	else
+	    while ((x > lim) && !iident(line[x - 1]) && !iblank(line[x - 1]))
+		x--;
+    }
+    backkill(cs - x, 1);
+}
+
+/**/
+void
+backwardkillword(void)
+{
+    int x = cs;
+    int n = zmult;
+
+    if (n < 0) {
+	zmult = -n;
+	killword();
+	zmult = n;
+	return;
+    }
+    while (n--) {
+	while (x && !iword(line[x - 1]))
+	    x--;
+	while (x && iword(line[x - 1]))
+	    x--;
+    }
+    backkill(cs - x, 1);
+}
+
+/**/
+void
+upcaseword(void)
+{
+    int n = zmult;
+    int neg = n < 0, ocs = cs;
+
+    if (neg)
+	n = -n;
+    while (n--) {
+	while (cs != ll && !iword(line[cs]))
+	    cs++;
+	while (cs != ll && iword(line[cs])) {
+	    line[cs] = tuupper(line[cs]);
+	    cs++;
+	}
+    }
+    if (neg)
+	cs = ocs;
+}
+
+/**/
+void
+downcaseword(void)
+{
+    int n = zmult;
+    int neg = n < 0, ocs = cs;
+
+    if (neg)
+	n = -n;
+    while (n--) {
+	while (cs != ll && !iword(line[cs]))
+	    cs++;
+	while (cs != ll && iword(line[cs])) {
+	    line[cs] = tulower(line[cs]);
+	    cs++;
+	}
+    }
+    if (neg)
+	cs = ocs;
+}
+
+/**/
+void
+capitalizeword(void)
+{
+    int first, n = zmult;
+    int neg = n < 0, ocs = cs;
+
+    if (neg)
+	n = -n;
+    while (n--) {
+	first = 1;
+	while (cs != ll && !iword(line[cs]))
+	    cs++;
+	while (cs != ll && iword(line[cs]) && !isalpha(line[cs]))
+	    cs++;
+	while (cs != ll && iword(line[cs])) {
+	    line[cs] = (first) ? tuupper(line[cs]) : tulower(line[cs]);
+	    first = 0;
+	    cs++;
+	}
+    }
+    if (neg)
+	cs = ocs;
+}
+
+/**/
+void
+deleteword(void)
+{
+    int x = cs;
+    int n = zmult;
+
+    if (n < 0) {
+	zmult = -n;
+	backwarddeleteword();
+	zmult = n;
+	return;
+    }
+    while (n--) {
+	while (x != ll && !iword(line[x]))
+	    x++;
+	while (x != ll && iword(line[x]))
+	    x++;
+    }
+    foredel(x - cs);
+}
+
+/**/
+void
+killword(void)
+{
+    int x = cs;
+    int n = zmult;
+
+    if (n < 0) {
+	zmult = -n;
+	backwardkillword();
+	zmult = n;
+	return;
+    }
+    while (n--) {
+	while (x != ll && !iword(line[x]))
+	    x++;
+	while (x != ll && iword(line[x]))
+	    x++;
+    }
+    forekill(x - cs, 0);
+}
+
+/**/
+void
+transposewords(void)
+{
+    int p1, p2, p3, p4, x = cs;
+    char *temp, *pp;
+    int n = zmult;
+    int neg = n < 0, ocs = cs;
+
+    if (neg)
+	n = -n;
+    while (n--) {
+	while (x != ll && line[x] != '\n' && !iword(line[x]))
+	    x++;
+	if (x == ll || line[x] == '\n') {
+	    x = cs;
+	    while (x && line[x - 1] != '\n' && !iword(line[x]))
+		x--;
+	    if (!x || line[x - 1] == '\n') {
+		feep();
+		return;
+	    }
+	}
+	for (p4 = x; p4 != ll && iword(line[p4]); p4++);
+	for (p3 = p4; p3 && iword(line[p3 - 1]); p3--);
+	if (!p3) {
+	    feep();
+	    return;
+	}
+	for (p2 = p3; p2 && !iword(line[p2 - 1]); p2--);
+	if (!p2) {
+	    feep();
+	    return;
+	}
+	for (p1 = p2; p1 && iword(line[p1 - 1]); p1--);
+	pp = temp = (char *)halloc(p4 - p1 + 1);
+	struncpy(&pp, (char *) line + p3, p4 - p3);
+	struncpy(&pp, (char *) line + p2, p3 - p2);
+	struncpy(&pp, (char *) line + p1, p2 - p1);
+	strncpy((char *)line + p1, temp, p4 - p1);
+	cs = p4;
+    }
+    if (neg)
+	cs = ocs;
+}
diff --git a/Src/ansi2knr.c b/Src/ansi2knr.c
new file mode 100644
index 000000000..e5502a7e6
--- /dev/null
+++ b/Src/ansi2knr.c
@@ -0,0 +1,413 @@
+/* Copyright (C) 1989, 1991, 1993, 1994 Aladdin Enterprises. All rights reserved. */
+
+/* ansi2knr.c */
+/* Convert ANSI function declarations to K&R syntax */
+
+/*
+ansi2knr is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY.  No author or distributor accepts responsibility
+to anyone for the consequences of using it or for whether it serves any
+particular purpose or works at all, unless he says so in writing.  Refer
+to the GNU General Public License for full details.
+
+Everyone is granted permission to copy, modify and redistribute
+ansi2knr, but only under the conditions described in the GNU
+General Public License.  A copy of this license is supposed to have been
+given to you along with ansi2knr so you can know your rights and
+responsibilities.  It should be in a file named COPYING.  Among other
+things, the copyright notice and this notice must be preserved on all
+copies.
+*/
+
+/*
+ * Usage:
+	ansi2knr input_file [output_file]
+ * If no output_file is supplied, output goes to stdout.
+ * There are no error messages.
+ *
+ * ansi2knr recognizes function definitions by seeing a non-keyword
+ * identifier at the left margin, followed by a left parenthesis,
+ * with a right parenthesis as the last character on the line.
+ * It will recognize a multi-line header provided that the last character
+ * of the last line of the header is a right parenthesis,
+ * and no intervening line ends with a left brace or a semicolon.
+ * These algorithms ignore whitespace and comments, except that
+ * the function name must be the first thing on the line.
+ * The following constructs will confuse it:
+ *	- Any other construct that starts at the left margin and
+ *	    follows the above syntax (such as a macro or function call).
+ *	- Macros that tinker with the syntax of the function header.
+ */
+
+/*
+ * Change history:
+	lpd 89-xx-xx original version
+	lpd 94-07-16 added some conditionals to help GNU `configure',
+		suggested by Francois Pinard <pinard@iro.umontreal.ca>;
+		properly erase prototype args in function parameters,
+		contributed by Jim Avera <jima@netcom.com>;
+		correct error in writeblanks (it shouldn't erase EOLs)
+ */
+
+/* Most of the conditionals here are to make ansi2knr work with */
+/* the GNU configure machinery. */
+
+#ifdef HAVE_CONFIG_H
+# ifdef CONFIG_BROKETS
+/*
+   We use <config.h> instead of "config.h" so that a compilation
+   using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h
+   (which it would do because it found this file in $srcdir).
+ */
+#  include <config.h>
+# else
+#  include "config.h"
+# endif
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+
+#ifdef HAVE_CONFIG_H
+
+/*
+   For properly autoconfiguring ansi2knr, use AC_CONFIG_HEADER(config.h).
+   This will define HAVE_CONFIG_H and so, activate the following lines.
+ */
+
+# if STDC_HEADERS || HAVE_STRING_H
+#  include <string.h>
+# else
+#  include <strings.h>
+# endif
+
+#else /* not HAVE_CONFIG_H */
+
+/*
+   Without AC_CONFIG_HEADER, merely use <string.h> as in the original
+   Ghostscript distribution.  This loses on older BSD systems.
+ */
+
+# include <string.h>
+
+#endif /* not HAVE_CONFIG_H */
+
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+#else
+/*
+   malloc and free should be declared in stdlib.h,
+   but if you've got a K&R compiler, they probably aren't.
+ */
+char *malloc();
+void free();
+#endif
+
+/* Scanning macros */
+#define isidchar(ch) (isalnum(ch) || (ch) == '_')
+#define isidfirstchar(ch) (isalpha(ch) || (ch) == '_')
+
+/* Forward references */
+char *skipspace();
+void writeblanks();
+int test1();
+int convert1();
+
+/* The main program */
+int
+main(argc, argv)
+    int argc;
+    char *argv[];
+{	FILE *in, *out;
+#define bufsize 5000			/* arbitrary size */
+	char *buf;
+	char *line;
+	switch ( argc )
+	   {
+	default:
+		printf("Usage: ansi2knr input_file [output_file]\n");
+		exit(0);
+	case 2:
+		out = stdout; break;
+	case 3:
+		out = fopen(argv[2], "w");
+		if ( out == NULL )
+		   {	fprintf(stderr, "Cannot open %s\n", argv[2]);
+			exit(1);
+		   }
+	   }
+	in = fopen(argv[1], "r");
+	if ( in == NULL )
+	   {	fprintf(stderr, "Cannot open %s\n", argv[1]);
+		exit(1);
+	   }
+	fprintf(out, "#line 1 \"%s\"\n", argv[1]);
+	buf = malloc(bufsize);
+	line = buf;
+	while ( fgets(line, (unsigned)(buf + bufsize - line), in) != NULL )
+	   {	switch ( test1(buf) )
+		   {
+		case 2:			/* a function header */
+			convert1(buf, out, 1);
+			break;
+		case 1:			/* a function */
+			convert1(buf, out, 0);
+			break;
+		case -1:		/* maybe the start of a function */
+			line = buf + strlen(buf);
+			if ( line != buf + (bufsize - 1) ) /* overflow check */
+				continue;
+			/* falls through */
+		default:		/* not a function */
+			fputs(buf, out);
+			break;
+		   }
+		line = buf;
+	   }
+	if ( line != buf ) fputs(buf, out);
+	free(buf);
+	fclose(out);
+	fclose(in);
+	return 0;
+}
+
+/* Skip over space and comments, in either direction. */
+char *
+skipspace(p, dir)
+    register char *p;
+    register int dir;			/* 1 for forward, -1 for backward */
+{	for ( ; ; )
+	   {	while ( isspace(*p) ) p += dir;
+		if ( !(*p == '/' && p[dir] == '*') ) break;
+		p += dir;  p += dir;
+		while ( !(*p == '*' && p[dir] == '/') )
+		   {	if ( *p == 0 ) return p;	/* multi-line comment?? */
+			p += dir;
+		   }
+		p += dir;  p += dir;
+	   }
+	return p;
+}
+
+/*
+ * Write blanks over part of a string.
+ * Don't overwrite end-of-line characters.
+ */
+void
+writeblanks(start, end)
+    char *start;
+    char *end;
+{	char *p;
+	for ( p = start; p < end; p++ )
+	  if ( *p != '\r' && *p != '\n' ) *p = ' ';
+}
+
+/*
+ * Test whether the string in buf is a function definition.
+ * The string may contain and/or end with a newline.
+ * Return as follows:
+ *	0 - definitely not a function definition;
+ *	1 - definitely a function definition;
+ *	2 - definitely a function prototype (NOT USED);
+ *	-1 - may be the beginning of a function definition,
+ *		append another line and look again.
+ * The reason we don't attempt to convert function prototypes is that
+ * Ghostscript's declaration-generating macros look too much like
+ * prototypes, and confuse the algorithms.
+ */
+int
+test1(buf)
+    char *buf;
+{	register char *p = buf;
+	char *bend;
+	char *endfn;
+	int contin;
+	if ( !isidfirstchar(*p) )
+		return 0;		/* no name at left margin */
+	bend = skipspace(buf + strlen(buf) - 1, -1);
+	switch ( *bend )
+	   {
+	case ';': contin = 0 /*2*/; break;
+	case ')': contin = 1; break;
+	case '{': return 0;		/* not a function */
+	default: contin = -1;
+	   }
+	while ( isidchar(*p) ) p++;
+	endfn = p;
+	p = skipspace(p, 1);
+	if ( *p++ != '(' )
+		return 0;		/* not a function */
+	p = skipspace(p, 1);
+	if ( *p == ')' )
+		return 0;		/* no parameters */
+	/* Check that the apparent function name isn't a keyword. */
+	/* We only need to check for keywords that could be followed */
+	/* by a left parenthesis (which, unfortunately, is most of them). */
+	   {	static char *words[] =
+		   {	"asm", "auto", "case", "char", "const", "double",
+			"extern", "float", "for", "if", "int", "long",
+			"register", "return", "short", "signed", "sizeof",
+			"static", "switch", "typedef", "unsigned",
+			"void", "volatile", "while", 0
+		   };
+		char **key = words;
+		char *kp;
+		int len = endfn - buf;
+		while ( (kp = *key) != 0 )
+		   {	if ( strlen(kp) == len && !strncmp(kp, buf, len) )
+				return 0;	/* name is a keyword */
+			key++;
+		   }
+	   }
+	return contin;
+}
+
+/* Convert a recognized function definition or header to K&R syntax. */
+int
+convert1(buf, out, header)
+    char *buf;
+    FILE *out;
+    int header;		/* Boolean */
+{	char *endfn;
+	register char *p;
+	char **breaks;
+	unsigned num_breaks = 2;	/* for testing */
+	char **btop;
+	char **bp;
+	char **ap;
+	/* Pre-ANSI implementations don't agree on whether strchr */
+	/* is called strchr or index, so we open-code it here. */
+	for ( endfn = buf; *(endfn++) != '('; ) ;
+top:	p = endfn;
+	breaks = (char **)malloc(sizeof(char *) * num_breaks * 2);
+	if ( breaks == 0 )
+	   {	/* Couldn't allocate break table, give up */
+		fprintf(stderr, "Unable to allocate break table!\n");
+		fputs(buf, out);
+		return -1;
+	   }
+	btop = breaks + num_breaks * 2 - 2;
+	bp = breaks;
+	/* Parse the argument list */
+	do
+	   {	int level = 0;
+		char *lp = NULL;
+		char *rp;
+		char *end = NULL;
+		if ( bp >= btop )
+		   {	/* Filled up break table. */
+			/* Allocate a bigger one and start over. */
+			free((char *)breaks);
+			num_breaks <<= 1;
+			goto top;
+		   }
+		*bp++ = p;
+		/* Find the end of the argument */
+		for ( ; end == NULL; p++ )
+		   {	switch(*p)
+			   {
+			case ',':
+				if ( !level ) end = p;
+				break;
+			case '(':
+				if ( !level ) lp = p;
+				level++;
+				break;
+			case ')':
+				if ( --level < 0 ) end = p;
+				else rp = p;
+				break;
+			case '/':
+				p = skipspace(p, 1) - 1;
+				break;
+			default:
+				;
+			   }
+		   }
+		/* Erase any embedded prototype parameters. */
+		if ( lp )
+		  writeblanks(lp + 1, rp);
+		p--;			/* back up over terminator */
+		/* Find the name being declared. */
+		/* This is complicated because of procedure and */
+		/* array modifiers. */
+		for ( ; ; )
+		   {	p = skipspace(p - 1, -1);
+			switch ( *p )
+			   {
+			case ']':	/* skip array dimension(s) */
+			case ')':	/* skip procedure args OR name */
+			   {	int level = 1;
+				while ( level )
+				 switch ( *--p )
+				   {
+				case ']': case ')': level++; break;
+				case '[': case '(': level--; break;
+				case '/': p = skipspace(p, -1) + 1; break;
+				default: ;
+				   }
+			   }
+				if ( *p == '(' && *skipspace(p + 1, 1) == '*' )
+				   {	/* We found the name being declared */
+					while ( !isidfirstchar(*p) )
+						p = skipspace(p, 1) + 1;
+					goto found;
+				   }
+				break;
+			default: goto found;
+			   }
+		   }
+found:		if ( *p == '.' && p[-1] == '.' && p[-2] == '.' )
+		   {	p++;
+			if ( bp == breaks + 1 )	/* sole argument */
+				writeblanks(breaks[0], p);
+			else
+				writeblanks(bp[-1] - 1, p);
+			bp--;
+		   }
+		else
+		   {	while ( isidchar(*p) ) p--;
+			*bp++ = p+1;
+		   }
+		p = end;
+	   }
+	while ( *p++ == ',' );
+	*bp = p;
+	/* Make a special check for 'void' arglist */
+	if ( bp == breaks+2 )
+	   {	p = skipspace(breaks[0], 1);
+		if ( !strncmp(p, "void", 4) )
+		   {	p = skipspace(p+4, 1);
+			if ( p == breaks[2] - 1 )
+			   {	bp = breaks;	/* yup, pretend arglist is empty */
+				writeblanks(breaks[0], p + 1);
+			   }
+		   }
+	   }
+	/* Put out the function name */
+	p = buf;
+	while ( p != endfn ) putc(*p, out), p++;
+	/* Put out the declaration */
+	if ( header )
+	  {	fputs(");", out);
+		for ( p = breaks[0]; *p; p++ )
+		  if ( *p == '\n' )
+		    putc('\n', out);
+	  }
+	else
+	  {	for ( ap = breaks+1; ap < bp; ap += 2 )
+		  {	p = *ap;
+			while ( isidchar(*p) )
+			  putc(*p, out), p++;
+			if ( ap < bp - 1 )
+			  fputs(", ", out);
+		  }
+		fputs(")  ", out);
+		/* Put out the argument declarations */
+		for ( ap = breaks+2; ap <= bp; ap += 2 )
+		  (*ap)[-1] = ';';
+		fputs(breaks[0], out);
+	  }
+	free((char *)breaks);
+	return 0;
+}
diff --git a/Src/builtin.c b/Src/builtin.c
new file mode 100644
index 000000000..31f396d93
--- /dev/null
+++ b/Src/builtin.c
@@ -0,0 +1,3599 @@
+/*
+ * builtin.c - builtin commands
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "builtin.pro"
+
+/* Builtins in the main executable */
+
+static struct builtin builtins[] =
+{
+    BIN_PREFIX("-", BINF_DASH),
+    BIN_PREFIX("builtin", BINF_BUILTIN),
+    BIN_PREFIX("command", BINF_COMMAND),
+    BIN_PREFIX("exec", BINF_EXEC),
+    BIN_PREFIX("noglob", BINF_NOGLOB),
+    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("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("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("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("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("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),
+
+#ifdef ZSH_HASH_DEBUG
+    BUILTIN("hashinfo", 0, bin_hashinfo, 0, 0, 0, NULL, NULL),
+#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("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("log", 0, bin_log, 0, 0, 0, NULL, NULL),
+    BUILTIN("logout", 0, bin_break, 0, 1, BIN_LOGOUT, NULL, NULL),
+
+#if defined(ZSH_MEM) & defined(ZSH_MEM_DEBUG)
+    BUILTIN("mem", 0, bin_mem, 0, 0, 0, "v", 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("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("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),
+    BUILTIN("setopt", 0, bin_setopt, 0, -1, BIN_SETOPT, NULL, NULL),
+    BUILTIN("shift", BINF_PSPECIAL, bin_shift, 0, -1, 0, NULL, NULL),
+    BUILTIN("source", BINF_PSPECIAL, bin_dot, 1, -1, 0, NULL, NULL),
+    BUILTIN("suspend", 0, bin_suspend, 0, 0, 0, "f", NULL),
+    BUILTIN("test", 0, bin_test, 0, -1, BIN_TEST, NULL, NULL),
+    BUILTIN("ttyctl", 0, bin_ttyctl, 0, 0, 0, "fu", NULL),
+    BUILTIN("times", BINF_PSPECIAL, bin_times, 0, 0, 0, NULL, NULL),
+    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("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"),
+    BUILTIN("unhash", 0, bin_unhash, 1, -1, 0, "adfm", NULL),
+    BUILTIN("unset", BINF_PSPECIAL, bin_unset, 1, -1, 0, "fm", NULL),
+    BUILTIN("unsetopt", 0, bin_setopt, 0, -1, BIN_UNSETOPT, NULL, NULL),
+    BUILTIN("wait", 0, bin_fg, 0, -1, BIN_WAIT, NULL, NULL),
+    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 Command Hash Table Functions */
+/****************************************/
+
+/* hash table containing builtin commands */
+
+/**/
+HashTable builtintab;
+ 
+/**/
+void
+createbuiltintable(void)
+{
+    builtintab = newhashtable(85, "builtintab", NULL);
+
+    builtintab->hash        = hasher;
+    builtintab->emptytable  = NULL;
+    builtintab->filltable   = NULL;
+    builtintab->addnode     = addhashnode;
+    builtintab->getnode     = gethashnode;
+    builtintab->getnode2    = gethashnode2;
+    builtintab->removenode  = removehashnode;
+    builtintab->disablenode = disablehashnode;
+    builtintab->enablenode  = enablehashnode;
+    builtintab->freenode    = freebuiltinnode;
+    builtintab->printnode   = printbuiltinnode;
+
+    addbuiltins("zsh", builtins, sizeof(builtins)/sizeof(*builtins));
+}
+
+/* Print a builtin */
+
+/**/
+static void
+printbuiltinnode(HashNode hn, int printflags)
+{
+    Builtin bn = (Builtin) hn;
+
+    if (printflags & PRINT_WHENCE_WORD) {
+	printf("%s: builtin\n", bn->nam);
+	return;
+    }
+
+    if (printflags & PRINT_WHENCE_CSH) {
+	printf("%s: shell built-in command\n", bn->nam);
+	return;
+    }
+
+    if (printflags & PRINT_WHENCE_VERBOSE) {
+	printf("%s is a shell builtin\n", bn->nam);
+	return;
+    }
+
+    /* default is name only */
+    printf("%s\n", bn->nam);
+}
+
+/**/
+static void
+freebuiltinnode(HashNode hn)
+{
+    Builtin bn = (Builtin) hn;
+ 
+    if(!(bn->flags & BINF_ADDED)) {
+	zsfree(bn->nam);
+	zsfree(bn->optstr);
+	zfree(bn, sizeof(struct builtin));
+    }
+}
+
+static char *auxdata;
+static int auxlen;
+
+/* execute a builtin handler function after parsing the arguments */
+
+#define MAX_OPS 128
+
+/**/
+int
+execbuiltin(LinkList args, Builtin bn)
+{
+    LinkNode n;
+    char ops[MAX_OPS], *arg, *pp, *name, **argv, **oargv, *optstr;
+    char *oxarg, *xarg = NULL;
+    int flags, sense, argc = 0, execop;
+
+    /* initialise some static variables */
+    auxdata = NULL;
+    auxlen = 0;
+
+    /* initialize some local variables */
+    memset(ops, 0, MAX_OPS);
+    name = (char *) ugetnode(args);
+
+    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;
+
+    /* Sort out the options. */
+    if ((flags & BINF_ECHOPTS) && isset(BSDECHO))
+	ops['E'] = 1;
+    if (optstr)
+	/* while arguments look like options ... */
+	while (arg &&
+	       ((sense = (*arg == '-')) ||
+		 ((flags & BINF_PLUSOPTS) && *arg == '+')) &&
+	       ((flags & BINF_PLUSOPTS) || !atoi(arg))) {
+	    /* unrecognised options to echo etc. are not really options */
+	    if (flags & BINF_ECHOPTS) {
+		char *p = arg;
+		while (*++p && strchr(optstr, (int) *p));
+		if (*p)
+		    break;
+	    }
+	    /* save the options in xarg, for execution tracing */
+	    if (xarg) {
+		oxarg = tricat(xarg, " ", arg);
+		zsfree(xarg);
+		xarg = oxarg;
+	    } else
+		xarg = ztrdup(arg);
+	    /* handle -- or - (ops['-']), and + (ops['-'] and ops['+']) */
+	    if (arg[1] == '-')
+		arg++;
+	    if (!arg[1]) {
+		ops['-'] = 1;
+		if (!sense)
+		    ops['+'] = 1;
+	    }
+	    /* save options in ops, as long as they are in bn->optstr */
+	    execop = -1;
+	    while (*++arg)
+		if (strchr(optstr, execop = (int)*arg))
+		    ops[(int)*arg] = (sense) ? 1 : 2;
+		else
+		    break;
+	    /* "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'))
+		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.) */
+	    if (*arg) {
+		if(*arg == Meta)
+		    *++arg ^= 32;
+		zerr("bad option: -%c", NULL, *arg);
+		zsfree(xarg);
+		return 1;
+	    }
+	    arg = (char *) ugetnode(args);
+	    /* for the "print" builtin, the options after -R are treated as
+	    options to "echo" */
+	    if ((flags & BINF_PRINTOPTS) && ops['R']) {
+		optstr = "ne";
+		flags |= BINF_ECHOPTS;
+	    }
+	    /* the option -- indicates the end of the options */
+	    if (ops['-'])
+		break;
+	    /* for "fc", -e takes an extra argument */
+	    if ((flags & BINF_FCOPTS) && execop == 'e') {
+		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)) {
+		auxlen = atoi(arg);
+		arg = (char *) ugetnode(args);
+	    }
+	}
+    if (flags & BINF_R)
+	auxdata = "-";
+    /* handle built-in options, for overloaded handler functions */
+    if ((pp = bn->defopts))
+	while (*pp)
+	    ops[(int)*pp++] = 1;
+
+    /* Set up the argument list. */
+    if (arg) {
+	/* count the arguments */
+	argc = 1;
+	n = firstnode(args);
+	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;
+    }
+
+    /* 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;
+    }
+
+    /* 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);
+    }
+    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.  *
+ * With no arguments, it lists all the currently enabled/disabled *
+ * elements in that particular hash table.                        */
+
+/**/
+int
+bin_enable(char *name, char **argv, char *ops, int func)
+{
+    HashTable ht;
+    HashNode hn;
+    ScanFunc scanfunc;
+    Comp com;
+    int flags1 = 0, flags2 = 0;
+    int match = 0, returnval = 0;
+
+    /* Find out which hash table we are working with. */
+    if (ops['f'])
+	ht = shfunctab;
+    else if (ops['r'])
+	ht = reswdtab;
+    else if (ops['a'])
+	ht = aliastab;
+    else
+	ht = builtintab;
+
+    /* Do we want to enable or disable? */
+    if (func == BIN_ENABLE) {
+	flags2 = DISABLED;
+	scanfunc = ht->enablenode;
+    } else {
+	flags1 = DISABLED;
+	scanfunc = ht->disablenode;
+    }
+
+    /* Given no arguments, print the names of the enabled/disabled elements  *
+     * in this hash table.  If func == BIN_ENABLE, then scanhashtable will   *
+     * print nodes NOT containing the DISABLED flag, else scanhashtable will *
+     * print nodes containing the DISABLED flag.                             */
+    if (!*argv) {
+	scanhashtable(ht, 1, flags1, flags2, ht->printnode, 0);
+	return 0;
+    }
+
+    /* With -m option, treat arguments as glob patterns. */
+    if (ops['m']) {
+	for (; *argv; argv++) {
+	    /* parse pattern */
+	    tokenize(*argv);
+	    if ((com = parsereg(*argv)))
+		match += scanmatchtable(ht, com, 0, 0, scanfunc, 0);
+	    else {
+		untokenize(*argv);
+		zwarnnam(name, "bad pattern : %s", *argv, 0);
+		returnval = 1;
+	    }
+	}
+	/* If we didn't match anything, we return 1. */
+	if (!match)
+	    returnval = 1;
+	return returnval;
+    }
+
+    /* Take arguments literally -- do not glob */
+    for (; *argv; argv++) {
+	    if ((hn = ht->getnode2(ht, *argv))) {
+		scanfunc(hn, 0);
+	    } else {
+		zwarnnam(name, "no such hash table element: %s", *argv, 0);
+		returnval = 1;
+	    }
+	}
+    return returnval;
+}
+
+/* set: either set the shell options, or set the shell arguments, *
+ * or declare an array, or show various things                    */
+
+/**/
+int
+bin_set(char *nam, char **args, char *ops, int func)
+{
+    int action, optno, array = 0, hadopt = 0,
+	hadplus = 0, hadend = 0, sort = 0;
+    char **x;
+
+    /* Obsolecent sh compatibility: set - is the same as set +xv *
+     * and set - args is the same as set +xv -- args             */
+    if (*args && **args == '-' && !args[0][1]) {
+	dosetopt(VERBOSE, 0, 0);
+	dosetopt(XTRACE, 0, 0);
+	if (!args[1])
+	    return 0;
+    }
+
+    /* loop through command line options (begins with "-" or "+") */
+    while (*args && (**args == '-' || **args == '+')) {
+	action = (**args == '-');
+	hadplus |= !action;
+	if(!args[0][1])
+	    *args = "--";
+	while (*++*args) {
+	    if(**args == Meta)
+		*++*args ^= 32;
+	    if(**args != '-' || action)
+		hadopt = 1;
+	    /* The pseudo-option `--' signifies the end of options. */
+	    if (**args == '-') {
+		hadend = 1;
+		args++;
+		goto doneoptions;
+	    } else if (**args == 'o') {
+		if (!*++*args)
+		    args++;
+		if (!*args) {
+		    zwarnnam(nam, "string expected after -o", NULL, 0);
+		    inittyptab();
+		    return 1;
+		}
+		if(!(optno = optlookup(*args)))
+		    zwarnnam(nam, "no such option: %s", *args, 0);
+		else if(dosetopt(optno, action, 0))
+		    zwarnnam(nam, "can't change option: %s", *args, 0);
+		break;
+	    } else if(**args == 'A') {
+		if(!*++*args)
+		    args++;
+		array = action ? 1 : -1;
+		goto doneoptions;
+	    } else if (**args == 's')
+		sort = action ? 1 : -1;
+	    else {
+	    	if (!(optno = optlookupc(**args)))
+		    zwarnnam(nam, "bad option: -%c", NULL, **args);
+		else if(dosetopt(optno, action, 0))
+		    zwarnnam(nam, "can't change option: -%c", NULL, **args);
+	    }
+	}
+	args++;
+    }
+    doneoptions:
+    inittyptab();
+
+    /* Show the parameters, possibly with values */
+    if (!hadopt && !*args)
+	scanhashtable(paramtab, 1, 0, 0, paramtab->printnode,
+	    hadplus ? PRINT_NAMEONLY : 0);
+
+    if (array && !*args) {
+	/* display arrays */
+	scanhashtable(paramtab, 1, PM_ARRAY, 0, paramtab->printnode,
+	    hadplus ? PRINT_NAMEONLY : 0);
+    }
+    if (!*args && !hadend)
+	return 0;
+    if (array)
+	args++;
+    if (sort)
+	qsort(args, arrlen(args), sizeof(char *),
+	      sort > 0 ? strpcmp : invstrpcmp);
+    if (array) {
+	/* create an array with the specified elements */
+	char **a = NULL, **y, *name = args[-1];
+	int len = arrlen(args);
+
+	if (array < 0 && (a = getaparam(name))) {
+	    int al = arrlen(a);
+
+	    if (al > len)
+		len = al;
+	}
+	for (x = y = zalloc((len + 1) * sizeof(char *)); len--; a++) {
+	    if (!*args)
+		args = a;
+	    *y++ = ztrdup(*args++);
+	}
+	*y++ = NULL;
+	setaparam(name, x);
+    } else {
+	/* set shell arguments */
+	freearray(pparams);
+	PERMALLOC {
+	    pparams = arrdup(args);
+	} LASTALLOC;
+    }
+    return 0;
+}
+
+/**** directory-handling builtins ****/
+
+/**/
+int doprintdir = 0;		/* set in exec.c (for autocd) */
+
+/* pwd: display the name of the current directory */
+
+/**/
+int
+bin_pwd(char *name, char **argv, char *ops, int func)
+{
+    if (ops['r'] || ops['P'] || (isset(CHASELINKS) && !ops['L']))
+	printf("%s\n", zgetcwd());
+    else {
+	zputs(pwd, stdout);
+	putchar('\n');
+    }
+    return 0;
+}
+
+/* the directory stack */
+ 
+/**/
+LinkList dirstack;
+ 
+/* dirs: list the directory stack, or replace it with a provided list */
+
+/**/
+int
+bin_dirs(char *name, char **argv, char *ops, int func)
+{
+    LinkList l;
+
+    /* with the -v option, provide a numbered list of directories, starting at
+    zero */
+    if (ops['v']) {
+	LinkNode node;
+	int pos = 1;
+
+	printf("0\t");
+	fprintdir(pwd, stdout);
+	for (node = firstnode(dirstack); node; incnode(node)) {
+	    printf("\n%d\t", pos++);
+	    fprintdir(getdata(node), stdout);
+	}
+	putchar('\n');
+	return 0;
+    }
+    /* given no arguments, list the stack normally */
+    if (!*argv) {
+	printdirstack();
+	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;
+    return 0;
+}
+
+/* cd, chdir, pushd, popd */
+
+/**/
+void
+set_pwd_env(void)
+{
+    Param pm;
+
+    pm = (Param) paramtab->getnode(paramtab, "PWD");
+    if (pm && PM_TYPE(pm->flags) != PM_SCALAR) {
+	pm->flags &= ~PM_READONLY;
+	unsetparam_pm(pm, 0, 1);
+    }
+
+    pm = (Param) paramtab->getnode(paramtab, "OLDPWD");
+    if (pm && PM_TYPE(pm->flags) != PM_SCALAR) {
+	pm->flags &= ~PM_READONLY;
+	unsetparam_pm(pm, 0, 1);
+    }
+
+    setsparam("PWD", ztrdup(pwd));
+    setsparam("OLDPWD", ztrdup(oldpwd));
+
+    pm = (Param) paramtab->getnode(paramtab, "PWD");
+    if (!(pm->flags & PM_EXPORTED)) {
+	pm->flags |= PM_EXPORTED;
+	pm->env = addenv("PWD", pwd);
+    }
+    pm = (Param) paramtab->getnode(paramtab, "OLDPWD");
+    if (!(pm->flags & PM_EXPORTED)) {
+	pm->flags |= PM_EXPORTED;
+	pm->env = addenv("PWD", pwd);
+    }
+}
+
+/* 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() *
+ * does the ancilliary processing associated with actually changing    *
+ * directory.                                                          */
+
+/**/
+int
+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);
+	return 1;
+    }
+    doprintdir = (doprintdir == -1);
+
+    for (; *argv && **argv == '-'; argv++) {
+	char *s = *argv + 1;
+
+	do {
+	    switch (*s) {
+	    case 's':
+	    case 'P':
+	    case 'L':
+		break;
+	    default:
+		goto brk;
+	    }
+	} while (*++s);
+	for (s = *argv; *++s; ops[*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);
+
+    if (stat(unmeta(pwd), &st1) < 0) {
+	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) {
+	    zsfree(pwd);
+	    pwd = metafy(zgetcwd(), -1, META_DUP);
+	} else {
+	    chdir(unmeta(pwd));
+	}
+    }
+    set_pwd_env();
+    return 0;
+}
+
+/* Get directory to chdir to */
+
+/**/
+static LinkNode
+cd_get_dest(char *nam, char **argv, char *ops, int func)
+{
+    LinkNode dir = NULL;
+    LinkNode target;
+    char *dest;
+
+    if (!argv[0]) {
+	if (func == BIN_POPD && !nextnode(firstnode(dirstack))) {
+	    zwarnnam(nam, "directory stack empty", NULL, 0);
+	    return NULL;
+	}
+	if (func == BIN_PUSHD && unset(PUSHDTOHOME))
+	    dir = nextnode(firstnode(dirstack));
+	if (dir)
+	    insertlinknode(dirstack, dir, getlinknode(dirstack));
+	else if (func != BIN_POPD)
+	    pushnode(dirstack, ztrdup(home));
+    } else if (!argv[1]) {
+	int dd;
+	char *end;
+
+	doprintdir++;
+	if (argv[0][1] && (argv[0][0] == '+' || argv[0][0] == '-')) {
+	    dd = zstrtol(argv[0] + 1, &end, 10); 
+	    if (*end == '\0') {
+		if ((argv[0][0] == '+') ^ isset(PUSHDMINUS))
+		    for (dir = firstnode(dirstack); dir && dd; dd--, incnode(dir));
+		else
+		    for (dir = lastnode(dirstack); dir != (LinkNode) dirstack && dd;
+			 dd--, dir = prevnode(dir)); 
+		if (!dir || dir == (LinkNode) dirstack) {
+		    zwarnnam(nam, "no such entry in dir stack", NULL, 0);
+		    return NULL;
+		}
+	    }
+	}
+	if (!dir)
+	    pushnode(dirstack, ztrdup(strcmp(argv[0], "-")
+				      ? (doprintdir--, argv[0]) : oldpwd));
+    } else {
+	char *u, *d;
+	int len1, len2, len3;
+
+	if (!(u = strstr(pwd, argv[0]))) {
+	    zwarnnam(nam, "string not in pwd: %s", argv[0], 0);
+	    return NULL;
+	}
+	len1 = strlen(argv[0]);
+	len2 = strlen(argv[1]);
+	len3 = u - pwd;
+	d = (char *)zalloc(len3 + len2 + strlen(u + len1) + 1);
+	strncpy(d, pwd, len3);
+	strcpy(d + len3, argv[1]);
+	strcat(d, u + len1);
+	pushnode(dirstack, d);
+	doprintdir++;
+    }
+
+    target = dir;
+    if (func == BIN_POPD) {
+	if (!dir) {
+	    target = dir = firstnode(dirstack);
+	} else if (dir != firstnode(dirstack)) {
+	    return dir;
+	}
+	dir = nextnode(dir);
+    }
+    if (!dir) {
+	dir = firstnode(dirstack);
+    }
+    if (!(dest = cd_do_chdir(nam, getdata(dir), ops['s']))) {
+	if (!target)
+	    zsfree(getlinknode(dirstack));
+	if (func == BIN_POPD)
+	    zsfree(remnode(dirstack, dir));
+	return NULL;
+    }
+    if (dest != getdata(dir)) {
+	zsfree(getdata(dir));
+	setdata(dir, dest);
+    }
+    return target ? target : dir;
+}
+
+/* Change to given directory, if possible.  This function works out  *
+ * exactly how the directory should be interpreted, including cdpath *
+ * and CDABLEVARS.  For each possible interpretation of the given    *
+ * path, this calls cd_try_chdir(), which attempts to chdir to that  *
+ * particular path.                                                  */
+
+/**/
+static char *
+cd_do_chdir(char *cnam, char *dest, int hard)
+{
+    char **pp, *ret;
+    int hasdot = 0, eno = ENOENT;
+    /* nocdpath indicates that cdpath should not be used.  This is the case iff
+    dest is a relative path whose first segment is . or .., but if the path is
+    absolute then cdpath won't be used anyway. */
+    int nocdpath = dest[0] == '.' &&
+    (dest[1] == '/' || !dest[1] || (dest[1] == '.' &&
+				    (dest[2] == '/' || !dest[2])));
+
+    /* if we have an absolute path, use it as-is only */
+    if (*dest == '/') {
+	if ((ret = cd_try_chdir(NULL, dest, hard)))
+	    return ret;
+	zwarnnam(cnam, "%e: %s", dest, errno);
+	return NULL;
+    }
+
+    /* if cdpath is being used, check it for . */
+    if (!nocdpath)
+	for (pp = cdpath; *pp; pp++)
+	    if (!(*pp)[0] || ((*pp)[0] == '.' && (*pp)[1] == '\0'))
+		hasdot = 1;
+    /* if there is no . in cdpath (or it is not being used), try the directory
+    as-is (i.e. from .) */
+    if (!hasdot) {
+	if ((ret = cd_try_chdir(NULL, dest, hard)))
+	    return ret;
+	if (errno != ENOENT)
+	    eno = errno;
+    }
+    /* if cdpath is being used, try given directory relative to each element in
+    cdpath in turn */
+    if (!nocdpath)
+	for (pp = cdpath; *pp; pp++) {
+	    if ((ret = cd_try_chdir(*pp, dest, hard))) {
+		if (strcmp(*pp, ".")) {
+		    doprintdir++;
+		}
+		return ret;
+	    }
+	    if (errno != ENOENT)
+		eno = errno;
+	}
+
+    /* handle the CDABLEVARS option */
+    if ((ret = cd_able_vars(dest))) {
+	if ((ret = cd_try_chdir(NULL, ret,hard))) {
+	    doprintdir++;
+	    return ret;
+	}
+	if (errno != ENOENT)
+	    eno = errno;
+    }
+
+    /* If we got here, it means that we couldn't chdir to any of the
+    multitudinous possible paths allowed by zsh.  We've run out of options!
+    Add more here! */
+    zwarnnam(cnam, "%e: %s", dest, eno);
+    return NULL;
+}
+
+/* If the CDABLEVARS option is set, return the new *
+ * interpretation of the given path.               */
+
+/**/
+char *
+cd_able_vars(char *s)
+{
+    char *rest, save;
+
+    if (isset(CDABLEVARS)) {
+	for (rest = s; *rest && *rest != '/'; rest++);
+	save = *rest;
+	*rest = 0;
+	s = getnameddir(s);
+	*rest = save;
+
+	if (s && *rest)
+	    s = dyncat(s, rest);
+
+	return s;
+    }
+    return NULL;
+}
+
+/* Attempt to change to a single given directory.  The directory,    *
+ * for the convenience of the calling function, may be provided in   *
+ * two parts, which must be concatenated before attempting to chdir. *
+ * Returns NULL if the chdir fails.  If the directory change is      *
+ * possible, it is performed, and a pointer to the new full pathname *
+ * is returned.                                                      */
+
+/**/
+static char *
+cd_try_chdir(char *pfix, char *dest, int hard)
+{
+    char *buf;
+
+    /* handle directory prefix */
+    if (pfix && *pfix) {
+	if (*pfix == '/')
+	    buf = tricat(pfix, "/", dest);
+	else {
+	    int pwl = strlen(pwd);
+	    int pfl = strlen(pfix);
+
+	    buf = zalloc(pwl + pfl + strlen(dest) + 3);
+	    strcpy(buf, pwd);
+	    buf[pwl] = '/';
+	    strcpy(buf + pwl + 1, pfix);
+	    buf[pwl + 1 + pfl] = '/';
+	    strcpy(buf + pwl + pfl + 2, dest);
+	}
+    } else if (*dest == '/')
+	buf = ztrdup(dest);
+    else {
+	int pwl = strlen(pwd);
+
+	buf = zalloc(pwl + strlen(dest) + 2);
+	strcpy(buf, pwd);
+	buf[pwl] = '/';
+	strcpy(buf + pwl + 1, dest);
+    }
+
+    /* Normalise path.  See the definition of fixdir() for what this means. */
+    fixdir(buf);
+
+    if (lchdir(buf, NULL, hard)) {
+	zsfree(buf);
+	return NULL;
+    }
+    return metafy(buf, -1, META_NOALLOC);
+}
+
+/* do the extra processing associated with changing directory */
+
+/**/
+static void
+cd_new_pwd(int func, LinkNode dir, int chaselinks)
+{
+    Param pm;
+    List l;
+    char *new_pwd, *s;
+    int dirstacksize;
+
+    if (func == BIN_PUSHD)
+	rolllist(dirstack, dir);
+    new_pwd = remnode(dirstack, dir);
+
+    if (func == BIN_POPD && firstnode(dirstack)) {
+	zsfree(new_pwd);
+	new_pwd = getlinknode(dirstack);
+    } else if (func == BIN_CD && unset(AUTOPUSHD))
+	zsfree(getlinknode(dirstack));
+
+    if (chaselinks) {
+	s = new_pwd;
+	new_pwd = findpwd(s);
+	zsfree(s);
+    }
+    if (isset(PUSHDIGNOREDUPS)) {
+	LinkNode n; 
+	for (n = firstnode(dirstack); n; incnode(n)) {
+	    if (!strcmp(new_pwd, getdata(n))) {
+		zsfree(remnode(dirstack, n));
+		break;
+	    }
+	}
+    }
+
+    /* shift around the pwd variables, to make oldpwd and pwd relate to the
+    current (i.e. new) pwd */
+    zsfree(oldpwd);
+    oldpwd = pwd;
+    pwd = new_pwd;
+    /* update the PWD and OLDPWD shell parameters */
+    if ((pm = (Param) paramtab->getnode(paramtab, "PWD")) &&
+	(pm->flags & PM_EXPORTED) && pm->env)
+	pm->env = replenv(pm->env, pwd);
+    if ((pm = (Param) paramtab->getnode(paramtab, "OLDPWD")) &&
+	(pm->flags & PM_EXPORTED) && pm->env)
+	pm->env = replenv(pm->env, oldpwd);
+    if (unset(PUSHDSILENT) && func != BIN_CD && isset(INTERACTIVE))
+	printdirstack();
+    else if (doprintdir) {
+	fprintdir(pwd, stdout);
+        putchar('\n');
+    }
+
+    /* execute the chpwd function */
+    if ((l = getshfunc("chpwd")) != &dummy_list) {
+	fflush(stdout);
+	fflush(stderr);
+	doshfunc(l, NULL, 0, 1);
+    }
+
+    dirstacksize = getiparam("DIRSTACKSIZE");
+    /* handle directory stack sizes out of range */
+    if (dirstacksize > 0) {
+	int remove = countlinknodes(dirstack) -
+		     (dirstacksize < 2 ? 2 : dirstacksize);
+	while (remove-- >= 0)
+	    zsfree(remnode(dirstack, lastnode(dirstack)));
+    }
+}
+
+/* Print the directory stack */
+
+/**/
+static void
+printdirstack(void)
+{
+    LinkNode node;
+
+    fprintdir(pwd, stdout);
+    for (node = firstnode(dirstack); node; incnode(node)) {
+	putchar(' ');
+	fprintdir(getdata(node), stdout);
+    }
+    putchar('\n');
+}
+
+/* Normalise a path.  Segments consisting of ., and foo/.. *
+ * combinations, are removed and the path is unmetafied.   */
+
+/**/
+static void
+fixdir(char *src)
+{
+    char *dest = src;
+    char *d0 = dest;
+
+/*** if have RFS superroot directory ***/
+#ifdef HAVE_SUPERROOT
+    /* allow /.. segments to remain */
+    while (*src == '/' && src[1] == '.' && src[2] == '.' &&
+      (!src[3] || src[3] == '/')) {
+	*dest++ = '/';
+	*dest++ = '.';
+	*dest++ = '.';
+	src += 3;
+    }
+#endif
+
+    for (;;) {
+	/* compress multiple /es into single */
+	if (*src == '/') {
+	    *dest++ = *src++;
+	    while (*src == '/')
+		src++;
+	}
+	/* if we are at the end of the input path, remove a trailing / (if it
+	exists), and return ct */
+	if (!*src) {
+	    while (dest > d0 + 1 && dest[-1] == '/')
+		dest--;
+	    *dest = '\0';
+	    return;
+	}
+	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')) {
+	    /* skip a . section */
+	    while (*++src == '/');
+	} else {
+	    /* copy a normal segment into the output */
+	    while (*src != '/' && *src != '\0')
+		if ((*dest++ = *src++) == Meta)
+		    dest[-1] = *src++ ^ 32;
+	}
+    }
+}
+
+/**/
+void
+printqt(char *str)
+{
+    /* Print str, but turn any single quote into '\'' or ''. */
+    for (; *str; str++)
+	if (*str == '\'')
+	    printf(isset(RCQUOTES) ? "''" : "'\\''");
+	else
+	    putchar(*str);
+}
+
+/**/
+void
+printif(char *str, int c)
+{
+    /* If flag c has an argument, print that */
+    if (str) {
+	printf(" -%c ", c);
+	quotedzputs(str, stdout);
+    }
+}
+
+/**** history list functions ****/
+
+/* fc, history, r */
+
+/**/
+int
+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;
+
+    /* fc is only permitted in interactive shells */
+    if (!interact) {
+	zwarnnam(nam, "not interactive shell", NULL, 0);
+	return 1;
+    }
+    /* with the -m option, the first argument is taken *
+     * as a pattern that history lines have to match   */
+    if (*argv && ops['m']) {
+	tokenize(*argv);
+	if (!(com = parsereg(*argv++))) {
+	    zwarnnam(nam, "invalid match pattern", NULL, 0);
+	    return 1;
+	}
+    }
+    if (ops['R']) {
+	/* read history from a file */
+	readhistfile(*argv ? *argv : getsparam("HISTFILE"), 1);
+	return 0;
+    }
+    if (ops['W']) {
+	/* write history to a file */
+	savehistfile(*argv ? *argv : getsparam("HISTFILE"), 1,
+		     (ops['I'] ? 2 : 0));
+	return 0;
+    }
+    if (ops['A']) {
+	/* append history to a file */
+	savehistfile(*argv ? *argv : getsparam("HISTFILE"), 1,
+		     (ops['I'] ? 3 : 1));
+	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);
+
+	if (!asgf)
+	    asgf = asgl = a;
+	else {
+	    asgl->next = a;
+	    asgl = a;
+	}
+	a->name = *argv;
+	a->value = s;
+	argv++;
+    }
+    /* interpret and check first history line specifier */
+    if (*argv) {
+	minflag = **argv == '-';
+	first = fcgetcomm(*argv);
+	if (first == -1)
+	    return 1;
+	argv++;
+    }
+    /* interpret and check second history line specifier */
+    if (*argv) {
+	last = fcgetcomm(*argv);
+	if (last == -1)
+	    return 1;
+	argv++;
+    }
+    /* There is a maximum of two history specifiers.  At least, there *
+     * will be as long as the history list is one-dimensional.        */
+    if (*argv) {
+	zwarnnam("fc", "too many arguments", NULL, 0);
+	return 1;
+    }
+    /* default values of first and last, and range checking */
+    if (first == -1)
+	first = (ops['l']) ? curhist - 16 : curhist - 1;
+    if (last == -1)
+	last = (ops['l']) ? curhist - 1 : first;
+    if (first < firsthist())
+	first = firsthist();
+    if (last == -1)
+	last = (minflag) ? curhist : first;
+    else if (last < first)
+	last = first;
+    if (ops['l'])
+	/* 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);
+    else {
+	/* edit history file, and (if successful) use the result as a new command */
+	int tempfd;
+	FILE *out;
+	char *fil;
+
+	retval = 1;
+	fil = gettempname();
+	if (((tempfd = open(fil, O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY, 0600))
+	    == -1) ||
+		((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)) {
+		char *editor;
+
+		editor = auxdata ? auxdata : getsparam("FCEDIT");
+		if (!editor)
+		    editor = DEFAULT_FCEDIT;
+
+		if (fcedit(editor, fil))
+		    if (stuff(fil))
+			zwarnnam("fc", "%e: %s", s, errno);
+		    else {
+			loop(0,1);
+			retval = lastval;
+		    }
+	    }
+	}
+	unlink(fil);
+    }
+    return retval;
+}
+
+/* History handling functions: these are called by ZLE, as well as  *
+ * the actual builtins.  fcgetcomm() gets a history line, specified *
+ * either by number or leading string.  fcsubs() performs a given   *
+ * set of simple old=new substitutions on a given command line.     *
+ * fclist() outputs a given range of history lines to a text file.  */
+
+/* get the history event associated with s */
+
+/**/
+static int
+fcgetcomm(char *s)
+{
+    int cmd;
+
+    /* First try to match a history number.  Negative *
+     * numbers indicate reversed numbering.           */
+    if ((cmd = atoi(s))) {
+	if (cmd < 0)
+	    cmd = curhist + cmd;
+	if (cmd >= curhist) {
+	    zwarnnam("fc", "bad history number: %d", 0, cmd);
+	    return -1;
+	}
+	return cmd;
+    }
+    /* not a number, so search by string */
+    cmd = hcomsearch(s);
+    if (cmd == -1)
+	zwarnnam("fc", "event not found: %s", s, 0);
+    return cmd;
+}
+
+/* Perform old=new substituions.  Uses the asgment structure from zsh.h, *
+ * which is essentially a linked list of string,replacement pairs.       */
+
+/**/
+static int
+fcsubs(char **sp, struct asgment *sub)
+{
+    char *oldstr, *newstr, *oldpos, *newpos, *newmem, *s = *sp;
+    int subbed = 0;
+
+    /* loop through the linked list */
+    while (sub) {
+	oldstr = sub->name;
+	newstr = sub->value;
+	sub = sub->next;
+	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)
+			+ strlen(newstr) + strlen(newpos + strlen(oldstr)));
+	    ztrncpy(newmem, s, newpos - s);
+	    strcat(newmem, newstr);
+	    oldpos = newmem + strlen(newmem);
+	    strcat(newmem, newpos + strlen(oldstr));
+	    s = newmem;
+	    subbed = 1;
+	}
+    }
+    *sp = s;
+    return subbed;
+}
+
+/* Print a series of history events to a file.  The file pointer is     *
+ * given by f, and the required range of events by first and last.      *
+ * subs is an optional list of foo=bar substitutions to perform on the  *
+ * history lines before output.  com is an optional comp structure      *
+ * that the history lines are required to match.  n, r, D and d are     *
+ * options: n indicates that each line should be numbered.  r indicates *
+ * that the lines should be output in reverse order (newest first).     *
+ * D indicates that the real time taken by each command should be       *
+ * output.  d indicates that the time of execution of each command      *
+ * should be output; d>1 means that the date should be output too; d>3  *
+ * means that mm/dd/yyyy form should be used for the dates, as opposed  *
+ * to dd.mm.yyyy form; d>7 means that yyyy-mm-dd form should be used.   */
+
+/**/
+static int
+fclist(FILE *f, int n, int r, int D, int d, int first, int last, struct asgment *subs, Comp com)
+{
+    int fclistdone = 0;
+    char *s, *hs;
+    Histent ent;
+
+    /* reverse range if required */
+    if (r) {
+	r = last;
+	last = first;
+	first = r;
+    }
+    /* suppress "no substitution" warning if no substitution is requested */
+    if (!subs)
+	fclistdone = 1;
+
+    for (;;) {
+	hs = quietgetevent(first);
+	if (!hs) {
+	    zwarnnam("fc", "no such event: %d", NULL, first);
+	    return 1;
+	}
+	s = dupstring(hs);
+	/* this if does the pattern matching, if required */
+	if (!com || domatch(s, com, 0)) {
+	    /* perform substitution */
+	    fclistdone |= fcsubs(&s, subs);
+
+	    /* do numbering */
+	    if (n)
+		fprintf(f, "%5d  ", first);
+	    ent = NULL;
+	    /* 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) {
+			fprintf(f, "%d-%02d-%02d ",
+				ltm->tm_year + 1900,
+				ltm->tm_mon + 1, ltm->tm_mday);
+		    } else if (d >= 4) {
+			fprintf(f, "%d.%d.%d ",
+				ltm->tm_mday, ltm->tm_mon + 1,
+				ltm->tm_year + 1900);
+		    } else {
+			fprintf(f, "%d/%d/%d ",
+				ltm->tm_mon + 1, ltm->tm_mday,
+				ltm->tm_year + 1900);
+		    }
+		}
+		fprintf(f, "%02d:%02d  ", ltm->tm_hour, ltm->tm_min);
+	    }
+	    /* 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);
+	    }
+
+	    /* output the command */
+	    if (f == stdout) {
+		nicezputs(s, f);
+		putc('\n', f);
+	    } else
+		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++;
+    }
+
+    /* final processing */
+    if (f != stdout)
+	fclose(f);
+    if (!fclistdone) {
+	zwarnnam("fc", "no substitutions performed", NULL, 0);
+	return 1;
+    }
+    return 0;
+}
+
+/* edit a history file */
+
+/**/
+static int
+fcedit(char *ename, char *fn)
+{
+    char *s;
+
+    if (!strcmp(ename, "-"))
+	return 1;
+
+    s = tricat(ename, " ", fn);
+    execstring(s, 1, 0);
+    zsfree(s);
+
+    return !lastval;
+}
+
+/**** parameter builtins ****/
+
+/* Separate an argument into name=value parts, returning them in an     *
+ * asgment structure.  Because the asgment structure used is global,    *
+ * only one of these can be active at a time.  The string s gets placed *
+ * in this global structure, so it needs to be in permanent memory.     */
+
+/**/
+static Asgment
+getasg(char *s)
+{
+    static struct asgment asg;
+
+    /* sanity check for valid argument */
+    if (!s)
+	return NULL;
+
+    /* check if name is empty */
+    if (*s == '=') {
+	zerr("bad assignment", NULL, 0);
+	return NULL;
+    }
+    asg.name = s;
+
+    /* search for `=' */
+    for (; *s && *s != '='; s++);
+
+    /* found `=', so return with a value */
+    if (*s) {
+	*s = '\0';
+	asg.value = s + 1;
+    } else {
+    /* didn't find `=', so we only have a name */
+	asg.value = NULL;
+    }
+    return &asg;
+}
+
+/* declare, export, integer, local, readonly, typeset */
+
+/**/
+int
+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;
+    int returnval = 0, printflags = 0;
+
+    /* hash -f is really the builtin `functions' */
+    if (ops['f'])
+	return bin_functions(name, argv, ops, func);
+
+    /* Translate the options into PM_* flags.   *
+     * Unfortunately, this depends on the order *
+     * these flags are defined in zsh.h         */
+    for (; *optstr; optstr++, bit <<= 1)
+	if (ops[*optstr] == 1)
+	    on |= bit;
+	else if (ops[*optstr] == 2)
+	    off |= bit;
+    roff = off;
+
+    /* Sanity checks on the options.  Remove conficting options. */
+    if (on & PM_INTEGER)
+	off |= PM_RIGHT_B | PM_LEFT | PM_RIGHT_Z | PM_UPPER | PM_ARRAY;
+    if (on & PM_LEFT)
+	off |= PM_RIGHT_B | PM_INTEGER;
+    if (on & PM_RIGHT_B)
+	off |= PM_LEFT | PM_INTEGER;
+    if (on & PM_RIGHT_Z)
+	off |= PM_INTEGER;
+    if (on & PM_UPPER)
+	off |= PM_LOWER;
+    if (on & PM_LOWER)
+	off |= PM_UPPER;
+    on &= ~off;
+
+    /* Given no arguments, list whatever the options specify. */
+    if (!*argv) {
+	if (!(on|roff))
+	    printflags |= PRINT_TYPE;
+	if (roff || ops['+'])
+	    printflags |= PRINT_NAMEONLY;
+	scanhashtable(paramtab, 1, on|roff, 0, paramtab->printnode, printflags);
+	return 0;
+    }
+
+    /* With the -m option, treat arguments as glob patterns */
+    if (ops['m']) {
+	while ((asg = getasg(*argv++))) {
+	    tokenize(asg->name);   /* expand argument */
+	    if (!(com = parsereg(asg->name))) {
+		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.                                                */
+	    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))
+			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));
+			}
+		    }
+		}
+	    }
+	}
+	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);
+	}
+    }
+    return returnval;
+}
+
+/* Display or change the attributes of shell functions.   *
+ * If called as autoload, it will define a new autoloaded *
+ * (undefined) shell function.                            */
+
+/**/
+int
+bin_functions(char *name, char **argv, char *ops, int func)
+{
+    Comp com;
+    Shfunc shf;
+    int i, returnval = 0;
+    int on = 0, off = 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) {
+	zwarnnam(name, "invalid option(s)", NULL, 0);
+	return 1;
+    }
+
+    /* 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);
+	return 0;
+    }
+
+    /* With the -m option, treat arguments as glob patterns */
+    if (ops['m']) {
+	on &= ~PM_UNDEFINED;
+	for (; *argv; argv++) {
+	    /* expand argument */
+	    tokenize(*argv);
+	    if ((com = parsereg(*argv))) {
+		/* with no options, just print all functions matching the glob pattern */
+		if (!(on|off)) {
+		    scanmatchtable(shfunctab, com, 0, DISABLED, shfunctab->printnode, 0);
+		} 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);
+		    }
+		}
+	    } else {
+		untokenize(*argv);
+		zwarnnam(name, "bad pattern : %s", *argv, 0);
+		returnval = 1;
+	    }
+	}
+	return returnval;
+    }
+
+    /* Take the arguments literally -- do not glob */
+    for (; *argv; argv++) {
+	if ((shf = (Shfunc) shfunctab->getnode(shfunctab, *argv))) {
+	    /* if any flag was given */
+	    if (on|off)
+		/* turn on/off the given flags */
+		shf->flags = (shf->flags | (on & ~PM_UNDEFINED)) & ~off;
+	    else
+		/* no flags, so just print */
+		shfunctab->printnode((HashNode) shf, 0);
+	} else if (on & PM_UNDEFINED) {
+	    /* Add a new undefined (autoloaded) function to the *
+	     * hash table with the corresponding flags set.     */
+	    shf = (Shfunc) zcalloc(sizeof *shf);
+	    shf->flags = on;
+	    shf->funcdef = mkautofn(shf);
+	    shfunctab->addnode(shfunctab, ztrdup(*argv), shf);
+	} else
+	    returnval = 1;
+    }
+    return returnval;
+}
+
+/**/
+static List
+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;
+}
+
+/* unset: unset parameters */
+
+/**/
+int
+bin_unset(char *name, char **argv, char *ops, int func)
+{
+    Param pm, next;
+    Comp com;
+    char *s;
+    int match = 0, returnval = 0;
+    int i;
+
+    /* unset -f is the same as unfunction */
+    if (ops['f'])
+	return bin_unhash(name, argv, ops, func);
+
+    /* with -m option, treat arguments as glob patterns */
+    if (ops['m']) {
+	while ((s = *argv++)) {
+	    /* expand */
+	    tokenize(s);
+	    if ((com = parsereg(s))) {
+		/* 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);
+			    match++;
+			}
+		    }
+		}
+	    } else {
+		untokenize(s);
+		zwarnnam(name, "bad pattern : %s", s, 0);
+		returnval = 1;
+	    }
+	}
+	/* If we didn't match anything, we return 1. */
+	if (!match)
+	    returnval = 1;
+	return returnval;
+    }
+
+    /* do not glob -- unset the given parameter */
+    while ((s = *argv++)) {
+	pm = (Param) 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
+	    unsetparam(s);
+    }
+    return returnval;
+}
+
+/* type, whence, which */
+
+/**/
+int
+bin_whence(char *nam, char **argv, char *ops, int func)
+{
+    HashNode hn;
+    Comp com;
+    int returnval = 0;
+    int printflags = 0;
+    int csh, all, v, wd;
+    int informed;
+    char *cnam;
+
+    /* Check some option information */
+    csh = ops['c'];
+    v   = ops['v'];
+    all = ops['a'];
+    wd  = ops['w'];
+
+    if (ops['w'])
+	printflags |= PRINT_WHENCE_WORD;
+    else if (ops['c'])
+	printflags |= PRINT_WHENCE_CSH;
+    else if (ops['v'])
+	printflags |= PRINT_WHENCE_VERBOSE;
+    else
+	printflags |= PRINT_WHENCE_SIMPLE;
+    if (ops['f'])
+	printflags |= PRINT_WHENCE_FUNCDEF;
+
+    /* With -m option -- treat arguments as a glob patterns */
+    if (ops['m']) {
+	for (; *argv; argv++) {
+	    /* parse the pattern */
+	    tokenize(*argv);
+	    if (!(com = parsereg(*argv))) {
+		untokenize(*argv);
+		zwarnnam(nam, "bad pattern : %s", *argv, 0);
+		returnval = 1;
+		continue;
+	    }
+	    if (!ops['p']) {
+		/* -p option is for path search only.    *
+		 * We're not using it, so search for ... */
+
+		/* aliases ... */
+		scanmatchtable(aliastab, com, 0, DISABLED, aliastab->printnode, printflags);
+
+		/* and reserved words ... */
+		scanmatchtable(reswdtab, com, 0, DISABLED, reswdtab->printnode, printflags);
+
+		/* and shell functions... */
+		scanmatchtable(shfunctab, com, 0, DISABLED, shfunctab->printnode, printflags);
+
+		/* and builtins. */
+		scanmatchtable(builtintab, com, 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);
+
+	}
+    return returnval;
+    }
+
+    /* Take arguments literally -- do not glob */
+    for (; *argv; argv++) {
+	informed = 0;
+
+	if (!ops['p']) {
+	    /* Look for alias */
+	    if ((hn = aliastab->getnode(aliastab, *argv))) {
+		aliastab->printnode(hn, printflags);
+		if (!all)
+		    continue;
+		informed = 1;
+	    }
+	    /* Look for reserved word */
+	    if ((hn = reswdtab->getnode(reswdtab, *argv))) {
+		reswdtab->printnode(hn, printflags);
+		if (!all)
+		    continue;
+		informed = 1;
+	    }
+	    /* Look for shell function */
+	    if ((hn = shfunctab->getnode(shfunctab, *argv))) {
+		shfunctab->printnode(hn, printflags);
+		if (!all)
+		    continue;
+		informed = 1;
+	    }
+	    /* Look for builtin command */
+	    if ((hn = builtintab->getnode(builtintab, *argv))) {
+		builtintab->printnode(hn, printflags);
+		if (!all)
+		    continue;
+		informed = 1;
+	    }
+	    /* Look for commands that have been added to the *
+	     * cmdnamtab with the builtin `hash foo=bar'.    */
+	    if ((hn = cmdnamtab->getnode(cmdnamtab, *argv)) && (hn->flags & HASHED)) {
+		cmdnamtab->printnode(hn, printflags);
+		if (!all)
+		    continue;
+		informed = 1;
+	    }
+	}
+
+	/* Option -a is to search the entire path, *
+	 * rather than just looking for one match. */
+	if (all) {
+	    char **pp, buf[PATH_MAX], *z;
+
+	    for (pp = path; *pp; pp++) {
+		z = buf;
+		if (**pp) {
+		    strucpy(&z, *pp);
+		    *z++ = '/';
+		}
+		if ((z - buf) + strlen(*argv) >= PATH_MAX)
+		    continue;
+		strcpy(z, *argv);
+		if (iscom(buf)) {
+		    if (wd) {
+			printf("%s: command\n", *argv);
+		    } else {
+			if (v && !csh)
+			    zputs(*argv, stdout), fputs(" is ", stdout);
+			zputs(buf, stdout);
+			if (ops['s'])
+			    print_if_link(buf);
+			fputc('\n', stdout);
+		    }
+		    informed = 1;
+		}
+	    }
+	    if (!informed && (wd || v || csh)) {
+		zputs(*argv, stdout);
+		puts(wd ? ": none" : " not found");
+		returnval = 1;
+	    }
+	} else if ((cnam = findcmd(*argv))) {
+	    /* Found external command. */
+	    if (wd) {
+		printf("%s: command\n", *argv);
+	    } else {
+		if (v && !csh)
+		    zputs(*argv, stdout), fputs(" is ", stdout);
+		zputs(cnam, stdout);
+		if (ops['s'])
+		    print_if_link(cnam);
+		fputc('\n', stdout);
+	    }
+	    zsfree(cnam);
+	} else {
+	    /* Not found at all. */
+	    if (v || csh || wd)
+		zputs(*argv, stdout), puts(wd ? ": none" : " not found");
+	    returnval = 1;
+	}
+    }
+    return returnval;
+}
+
+/**** command & named directory hash table builtins ****/
+
+/*****************************************************************
+ * hash -- explicitly hash a command.                            *
+ * 1) Given no arguments, list the hash table.                   *
+ * 2) The -m option prints out commands in the hash table that   *
+ *    match a given glob pattern.                                *
+ * 3) The -f option causes the entire path to be added to the    *
+ *    hash table (cannot be combined with any arguments).        *
+ * 4) The -r option causes the entire hash table to be discarded *
+ *    (cannot be combined with any arguments).                   *
+ * 5) Given argument of the form foo=bar, add element to command *
+ *    hash table, so that when `foo' is entered, then `bar' is   *
+ *    executed.                                                  *
+ * 6) Given arguments not of the previous form, add it to the    *
+ *    command hash table as if it were being executed.           *
+ * 7) The -d option causes analogous things to be done using     *
+ *    the named directory hash table.                            *
+ *****************************************************************/
+
+/**/
+int
+bin_hash(char *name, char **argv, char *ops, int func)
+{
+    HashTable ht;
+    Comp com;
+    Asgment asg;
+    int returnval = 0;
+
+    if (ops['d'])
+	ht = nameddirtab;
+    else
+	ht = cmdnamtab;
+
+    if (ops['r'] || ops['f']) {
+	/* -f and -r can't be used with any arguments */
+	if (*argv) {
+	    zwarnnam("hash", "too many arguments", NULL, 0);
+	    return 1;
+	}
+
+	/* empty the hash table */
+	if (ops['r'])
+	    ht->emptytable(ht);
+
+	/* fill the hash table in a standard way */
+	if (ops['f'])
+	    ht->filltable(ht);
+
+	return 0;
+    }
+
+    /* Given no arguments, display current hash table. */
+    if (!*argv) {
+	scanhashtable(ht, 1, 0, 0, ht->printnode, 0);
+	return 0;
+    }
+
+    while (*argv) {
+	void *hn;
+	if (ops['m']) {
+	    /* with the -m option, treat the argument as a glob pattern */
+	    tokenize(*argv);  /* expand */
+	    if ((com = parsereg(*argv))) {
+		/* display matching hash table elements */
+		scanmatchtable(ht, com, 0, 0, ht->printnode, 0);
+	    } else {
+		untokenize(*argv);
+		zwarnnam(name, "bad pattern : %s", *argv, 0);
+		returnval = 1;
+	    }
+	} else if((asg = getasg(*argv))->value) {
+	    if(isset(RESTRICTED)) {
+		zwarnnam(name, "restricted: %s", asg->value, 0);
+		returnval = 1;
+	    } else {
+		/* The argument is of the form foo=bar, *
+		 * so define an entry for the table.    */
+		if(ops['d']) {
+		    Nameddir nd = hn = zcalloc(sizeof *nd);
+		    nd->flags = 0;
+		    nd->dir = ztrdup(asg->value);
+		} else {
+		    Cmdnam cn = hn = zcalloc(sizeof *cn);
+		    cn->flags = HASHED;
+		    cn->u.cmd = ztrdup(asg->value);
+		}
+		ht->addnode(ht, ztrdup(asg->name), hn);
+		if(ops['v'])
+		    ht->printnode(hn, 0);
+	    }
+	} else if (!(hn = ht->getnode2(ht, asg->name))) {
+	    /* With no `=value' part to the argument, *
+	     * work out what it ought to be.          */
+	    if(ops['d']) {
+		if(!getnameddir(asg->name)) {
+		    zwarnnam(name, "no such directory name: %s", asg->name, 0);
+		    returnval = 1;
+		}
+	    } else {
+		if (!hashcmd(asg->name, path)) {
+		    zwarnnam(name, "no such command: %s", asg->name, 0);
+		    returnval = 1;
+		}
+	    }
+	    if(ops['v'] && (hn = ht->getnode2(ht, asg->name)))
+		ht->printnode(hn, 0);
+	} else if(ops['v'])
+	    ht->printnode(hn, 0);
+	argv++;
+    }
+    return returnval;
+}
+
+/* unhash: remove specified elements from a hash table */
+
+/**/
+int
+bin_unhash(char *name, char **argv, char *ops, int func)
+{
+    HashTable ht;
+    HashNode hn, nhn;
+    Comp com;
+    int match = 0, returnval = 0;
+    int i;
+
+    /* Check which hash table we are working with. */
+    if (ops['d'])
+	ht = nameddirtab;	/* named directories */
+    else if (ops['f'])
+	ht = shfunctab;		/* shell functions   */
+    else if (ops['a'])
+	ht = aliastab;		/* aliases           */
+    else
+	ht = cmdnamtab;		/* external commands */
+
+    /* With -m option, treat arguments as glob patterns. *
+     * "unhash -m '*'" is legal, but not recommended.    */
+    if (ops['m']) {
+	for (; *argv; argv++) {
+	    /* expand argument */
+	    tokenize(*argv);
+	    if ((com = parsereg(*argv))) {
+		/* 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)) {
+			    ht->freenode(ht->removenode(ht, hn->nam));
+			    match++;
+			}
+		    }
+		}
+	    } else {
+		untokenize(*argv);
+		zwarnnam(name, "bad pattern : %s", *argv, 0);
+		returnval = 1;
+	    }
+	}
+	/* If we didn't match anything, we return 1. */
+	if (!match)
+	    returnval = 1;
+	return returnval;
+    }
+
+    /* Take arguments literally -- do not glob */
+    for (; *argv; argv++) {
+	if ((hn = ht->removenode(ht, *argv))) {
+	    ht->freenode(hn);
+	} else {
+	    zwarnnam(name, "no such hash table element: %s", *argv, 0);
+	    returnval = 1;
+	}
+    }
+    return returnval;
+}
+
+/**** alias builtins ****/
+
+/* alias: display or create aliases. */
+
+/**/
+int
+bin_alias(char *name, char **argv, char *ops, int func)
+{
+    Alias a;
+    Comp com;
+    Asgment asg;
+    int haveflags = 0, returnval = 0;
+    int flags1 = 0, flags2 = DISABLED;
+    int printflags = 0;
+
+    /* Did we specify the type of alias? */
+    if (ops['r'] || ops['g']) {
+	if (ops['r'] && ops['g']) {
+	    zwarnnam(name, "illegal combination of options", NULL, 0);
+	    return 1;
+	}
+	haveflags = 1;
+	if (ops['g'])
+	    flags1 |= ALIAS_GLOBAL;
+	else
+	    flags2 |= ALIAS_GLOBAL;
+    }
+
+    if (ops['L'])
+	printflags |= PRINT_LIST;
+
+    /* In the absence of arguments, list all aliases.  If a command *
+     * line flag is specified, list only those of that type.        */
+    if (!*argv) {
+	scanhashtable(aliastab, 1, flags1, flags2, aliastab->printnode, printflags);
+	return 0;
+    }
+
+    /* With the -m option, treat the arguments as *
+     * glob patterns of aliases to display.       */
+    if (ops['m']) {
+	for (; *argv; argv++) {
+	    tokenize(*argv);  /* expand argument */
+	    if ((com = parsereg(*argv))) {
+		/* display the matching aliases */
+		scanmatchtable(aliastab, com, flags1, flags2, aliastab->printnode, printflags);
+	    } else {
+		untokenize(*argv);
+		zwarnnam(name, "bad pattern : %s", *argv, 0);
+		returnval = 1;
+	    }
+	}
+	return returnval;
+    }
+
+    /* Take arguments literally.  Don't glob */
+    while ((asg = getasg(*argv++))) {
+	if (asg->value && !ops['L']) {
+	    /* The argument is of the form foo=bar and we are not *
+	     * forcing a listing with -L, so define an alias      */
+	    aliastab->addnode(aliastab, ztrdup(asg->name),
+		createaliasnode(ztrdup(asg->value), flags1));
+	} else if ((a = (Alias) aliastab->getnode(aliastab, asg->name))) {
+	    /* display alias if appropriate */
+	    if (!haveflags ||
+		(ops['r'] && !(a->flags & ALIAS_GLOBAL)) ||
+		(ops['g'] &&  (a->flags & ALIAS_GLOBAL)))
+		aliastab->printnode((HashNode) a, printflags);
+	} else
+	    returnval = 1;
+    }
+    return returnval;
+}
+
+
+/**** miscellaneous builtins ****/
+
+/* true, : (colon) */
+
+/**/
+int
+bin_true(char *name, char **argv, char *ops, int func)
+{
+    return 0;
+}
+
+/* false builtin */
+
+/**/
+int
+bin_false(char *name, char **argv, char *ops, int func)
+{
+    return 1;
+}
+
+/* the zle buffer stack */
+ 
+/**/
+LinkList bufstack;
+
+/* echo, print, pushln */
+
+/**/
+int
+bin_print(char *name, char **args, char *ops, int func)
+{
+    int nnl = 0, fd, argc, n;
+    int *len;
+    Histent ent;
+    FILE *fout = stdout;
+
+    /* -m option -- treat the first argument as a pattern and remove
+     * arguments not matching */
+    if (ops['m']) {
+	Comp com;
+	char **t, **p;
+
+	tokenize(*args);
+	if (!(com = parsereg(*args))) {
+	    untokenize(*args);
+	    zwarnnam(name, "bad pattern : %s", *args, 0);
+	    return 1;
+	}
+	for (p = ++args; *p; p++)
+	    if (!domatch(*p, com, 0))
+		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));
+    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);
+	/* -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]);
+	/* -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);
+		sprintf(arg, "~%s%s", d->nam,
+			args[n] + strlen(d->dir));
+		args[n] = arg;
+		len[n] = strlen(args[n]);
+	    }
+	}
+    }
+
+    /* -z option -- push the arguments onto the editing buffer stack */
+    if (ops['z']) {
+	PERMALLOC {
+	    pushnode(bufstack, sepjoin(args, NULL));
+	} LASTALLOC;
+	return 0;
+    }
+    /* -s option -- add the arguments to the history list */
+    if (ops['s']) {
+	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;
+	return 0;
+    }
+    /* -u and -p -- output to other than standard output */
+    if (ops['u'] || ops['p']) {
+	if (ops['u']) {
+	    for (fd = 0; fd < 10; fd++)
+		if (ops[fd + '0'])
+		    break;
+	    if (fd == 10)
+		fd = 0;
+	} else
+	    fd = coprocout;
+	if ((fd = dup(fd)) < 0) {
+	    zwarnnam(name, "bad file number", NULL, 0);
+	    return 1;
+	}
+	if ((fout = fdopen(fd, "w")) == 0) {
+	    zwarnnam(name, "bad mode on fd", NULL, 0);
+	    return 1;
+	}
+    }
+
+    /* -o and -O -- sort the arguments */
+    if (ops['o']) {
+	if (ops['i'])
+	    qsort(args, arrlen(args), sizeof(char *), cstrpcmp);
+
+	else
+	    qsort(args, arrlen(args), sizeof(char *), strpcmp);
+    } else if (ops['O']) {
+	if (ops['i'])
+	    qsort(args, arrlen(args), sizeof(char *), invcstrpcmp);
+
+	else
+	    qsort(args, arrlen(args), sizeof(char *), invstrpcmp);
+    }
+    /* after sorting arguments, recalculate lengths */
+    if(ops['o'] || ops['O'])
+	for(n = 0; n < argc; n++)
+	    len[n] = strlen(args[n]);
+
+    /* -c -- output in columns */
+    if (ops['c']) {
+	int l, nc, nr, sc, n, t, i;
+	char **ap;
+
+	for (n = l = 0, ap = args; *ap; ap++, n++)
+	    if (l < (t = strlen(*ap)))
+		l = t;
+
+	sc = l + 2;
+	nc = (columns + 1) / sc;
+	if (!nc)
+	    nc = 1;
+	nr = (n + nc - 1) / nc;
+
+	for (i = 0; i < nr; i++) {
+	    ap = args + i;
+	    do {
+		l = strlen(*ap);
+		fprintf(fout, "%s", *ap);
+		for (t = nr; t && *ap; t--, ap++);
+		if(*ap)
+		    for (; l < sc; l++)
+			fputc(' ', fout);
+	    } while (*ap);
+	    fputc(ops['N'] ? '\0' : '\n', fout);
+	}
+	if (fout != stdout)
+	    fclose(fout);
+	return 0;
+    }
+    /* normal output */
+    for (; *args; args++, len++) {
+	fwrite(*args, *len, 1, fout);
+	if (args[1])
+	    fputc(ops['l'] ? '\n' : ops['N'] ? '\0' : ' ', fout);
+    }
+    if (!(ops['n'] || nnl))
+	fputc(ops['N'] ? '\0' : '\n', fout);
+    if (fout != stdout)
+	fclose(fout);
+    return 0;
+}
+
+/* echotc: output a termcap */
+
+/**/
+int
+bin_echotc(char *name, char **argv, char *ops, int func)
+{
+    char *s, buf[2048], *t, *u;
+    int num, argct;
+
+    s = *argv++;
+    if (termflags & TERM_BAD)
+	return 1;
+    if ((termflags & TERM_UNKNOWN) && (isset(INTERACTIVE) || !init_term()))
+	return 1;
+    /* if the specified termcap has a numeric value, display it */
+    if ((num = tgetnum(s)) != -1) {
+	printf("%d\n", num);
+	return 0;
+    }
+    /* if the specified termcap is boolean, and set, say so  *
+     * ncurses can tell if an existing boolean capability is *
+     * off so in this case we print "no".                    */
+#if !defined(NCURSES_VERSION) || !defined(COLOR_PAIR)
+    if (tgetflag(s) > 0) {
+	puts("yes");
+	return (0);
+    }
+#else /* NCURSES_VERSION && COLOR_PAIR */
+    switch (tgetflag(s)) {
+    case -1:
+	break;
+    case 0:
+	puts("no");
+	return 0;
+    default:
+	puts("yes");
+	return 0;
+    }
+#endif /* NCURSES_VERSION && COLOR_PAIR */
+    /* get a string-type capability */
+    u = buf;
+    t = tgetstr(s, &u);
+    if (!t || !*t) {
+	/* capability doesn't exist, or (if boolean) is off */
+	zwarnnam(name, "no such capability: %s", s, 0);
+	return 1;
+    }
+    /* count the number of arguments required */
+    for (argct = 0, u = t; *u; u++)
+	if (*u == '%') {
+	    if (u++, (*u == 'd' || *u == '2' || *u == '3' || *u == '.' ||
+		      *u == '+'))
+		argct++;
+	}
+    /* check that the number of arguments provided is correct */
+    if (arrlen(argv) != argct) {
+	zwarnnam(name, (arrlen(argv) < argct) ? "not enough arguments" :
+		 "too many arguments", NULL, 0);
+	return 1;
+    }
+    /* output string, through the proper termcap functions */
+    if (!argct)
+	tputs(t, 1, putraw);
+    else {
+	num = (argv[1]) ? atoi(argv[1]) : atoi(*argv);
+	tputs(tgoto(t, atoi(*argv), num), num, putraw);
+    }
+    return 0;
+}
+
+/* shift builtin */
+
+/**/
+int
+bin_shift(char *name, char **argv, char *ops, int func)
+{
+    int num = 1, l, ret = 0;
+    char **s;
+ 
+    /* optional argument can be either numeric or an array */
+    if (*argv && !getaparam(*argv))
+        num = matheval(*argv++);
+ 
+    if (num < 0) {
+        zwarnnam(name, "argument to shift must be non-negative", NULL, 0);
+        return 1;
+    }
+
+    if (*argv) {
+        for (; *argv; argv++)
+            if ((s = getaparam(*argv))) {
+                if (num > arrlen(s)) {
+		    zwarnnam(name, "shift count must be <= $#", NULL, 0);
+		    ret++;
+		    continue;
+		}
+                PERMALLOC {
+		    s = arrdup(s + num);
+                } LASTALLOC;
+                setaparam(*argv, s);
+            }
+    } else {
+        if (num > (l = arrlen(pparams))) {
+	    zwarnnam(name, "shift count must be <= $#", NULL, 0);
+	    ret = 1;
+	} else {
+	    s = zalloc((l - num + 1) * sizeof(char *));
+	    memcpy(s, pparams + num, (l - num + 1) * sizeof(char *));
+	    while (num--)
+		zsfree(pparams[num]);
+	    zfree(pparams, (l + 1) * sizeof(char *));
+	    pparams = s;
+	}
+    }
+    return ret;
+}
+
+/* getopts: automagical option handling for shell scripts */
+
+/**/
+int
+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 *
+     * user can set it to zero to start a new option parse.     */
+    if (zoptind < 1) {
+	/* first call */
+	zoptind = 1;
+	optcind = 0;
+    }
+    if(zoptind > arrlen(args))
+	/* no more options */
+	return 1;
+
+    /* leading ':' in optstr means don't print an error message */
+    quiet = *optstr == ':';
+    optstr += quiet;
+    lenoptstr -= quiet;
+
+    /* find place in relevant argument */
+    str = unmetafy(dupstring(args[zoptind - 1]), &lenstr);
+    if(optcind >= lenstr) {
+	optcind = 0;
+	if(!args[zoptind++])
+	    return 1;
+	str = unmetafy(dupstring(args[zoptind - 1]), &lenstr);
+    }
+    if(!optcind) {
+	if(lenstr < 2 || (*str != '-' && *str != '+'))
+	    return 1;
+	if(lenstr == 2 && str[0] == '-' && str[1] == '-') {
+	    zoptind++;
+	    return 1;
+	}
+	optcind++;
+    }
+    opch = str[optcind++];
+    if(str[0] == '+') {
+	optbuf[0] = '+';
+	lenoptbuf = 2;
+    } else
+	lenoptbuf = 1;
+    optbuf[lenoptbuf - 1] = opch;
+
+    /* check for legality */
+    if(opch == ':' || !(p = memchr(optstr, opch, lenoptstr))) {
+	p = "?";
+err:
+      zsfree(zoptarg);
+	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("");
+	    errflag = 0;
+	}
+	return 0;
+    }
+
+    /* check for required argument */
+    if(p[1] == ':') {
+	if(optcind == lenstr) {
+	    if(!args[zoptind]) {
+		p = ":";
+		goto err;
+	    }
+	    p = ztrdup(args[zoptind++]);
+	} else
+	    p = metafy(str+optcind, lenstr-optcind, META_DUP);
+	optcind = ztrlen(args[zoptind - 1]);
+	zsfree(zoptarg);
+	zoptarg = p;
+    } else {
+	zsfree(zoptarg);
+	zoptarg = ztrdup("");
+    }
+
+    setsparam(var, metafy(optbuf, lenoptbuf, META_DUP));
+    return 0;
+}
+
+/* break, bye, continue, exit, logout, return -- most of these take   *
+ * one numeric argument, and the other (logout) is related to return. *
+ * (return is treated as a logout when in a login shell.)             */
+
+/**/
+int
+bin_break(char *name, char **argv, char *ops, int func)
+{
+    int num = lastval, nump = 0;
+
+    /* handle one optional numeric argument */
+    if (*argv) {
+	num = matheval(*argv++);
+	nump = 1;
+    }
+
+    switch (func) {
+    case BIN_CONTINUE:
+	if (!loops) {   /* continue is only permitted in loops */
+	    zerrnam(name, "not in while, until, select, or repeat loop", NULL, 0);
+	    return 1;
+	}
+	contflag = 1;   /* ARE WE SUPPOSED TO FALL THROUGH HERE? */
+    case BIN_BREAK:
+	if (!loops) {   /* break is only permitted in loops */
+	    zerrnam(name, "not in while, until, select, or repeat loop", NULL, 0);
+	    return 1;
+	}
+	breaks = nump ? minimum(num,loops) : 1;
+	break;
+    case BIN_RETURN:
+	if (isset(INTERACTIVE) || locallevel || sourcelevel) {
+	    retflag = 1;
+	    breaks = loops;
+	    lastval = num;
+	    if (trapreturn == -2)
+		trapreturn = lastval;
+	    return lastval;
+	}
+	zexit(num, 0);	/* else treat return as logout/exit */
+	break;
+    case BIN_LOGOUT:
+	if (unset(LOGINSHELL)) {
+	    zerrnam(name, "not login shell", NULL, 0);
+	    return 1;
+	}
+	zexit(num, 0);
+	break;
+    case BIN_EXIT:
+	zexit(num, 0);
+	break;
+    }
+    return 0;
+}
+
+/* we have printed a 'you have stopped (running) jobs.' message */
+ 
+/**/
+int stopmsg;
+ 
+/* check to see if user has jobs running/stopped */
+
+/**/
+static void
+checkjobs(void)
+{
+    int i;
+
+    for (i = 1; i < MAXJOB; i++)
+	if (i != thisjob && (jobtab[i].stat & STAT_LOCKED) &&
+	    !(jobtab[i].stat & STAT_NOPRINT))
+	    break;
+    if (i < MAXJOB) {
+	if (jobtab[i].stat & STAT_STOPPED) {
+
+#ifdef USE_SUSPENDED
+	    zerr("you have suspended jobs.", NULL, 0);
+#else
+	    zerr("you have stopped jobs.", NULL, 0);
+#endif
+
+	} else
+	    zerr("you have running jobs.", NULL, 0);
+	stopmsg = 1;
+    }
+}
+
+/* exit the shell.  val is the return value of the shell.  *
+ * from_signal should be non-zero if zexit is being called *
+ * because of a signal.                                    */
+
+/**/
+void
+zexit(int val, int from_signal)
+{
+    static int in_exit;
+
+    HEAPALLOC {
+	if (isset(MONITOR) && !stopmsg && !from_signal) {
+	    scanjobs();    /* check if jobs need printing           */
+	    checkjobs();   /* check if any jobs are running/stopped */
+	    if (stopmsg) {
+		stopmsg = 2;
+		LASTALLOC_RETURN;
+	    }
+	}
+	if (in_exit++ && from_signal)
+	    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");
+#ifdef GLOBAL_ZLOGOUT
+		source(GLOBAL_ZLOGOUT);
+#endif
+	    }
+	}
+	if (sigtrapped[SIGEXIT])
+	    dotrap(SIGEXIT);
+	if (mypid != getpid())
+	    _exit(val);
+	else
+	    exit(val);
+    } LASTALLOC;
+}
+
+/* . (dot), source */
+
+/**/
+int
+bin_dot(char *name, char **argv, char *ops, int func)
+{
+    char **old, *old0 = NULL;
+    int ret, diddot = 0, dotdot = 0;
+    char buf[PATH_MAX];
+    char *s, **t, *enam, *arg0;
+    struct stat st;
+
+    if (!*argv || strlen(*argv) >= PATH_MAX)
+	return 0;
+    old = pparams;
+    /* get arguments for the script */
+    if (argv[1]) {
+	PERMALLOC {
+	    pparams = arrdup(argv + 1);
+	} LASTALLOC;
+    }
+    enam = arg0 = ztrdup(*argv);
+    if (isset(FUNCTIONARGZERO)) {
+	old0 = argzero;
+	argzero = arg0;
+    }
+    s = unmeta(enam);
+    errno = ENOENT;
+    ret = 1;
+    /* for source only, check in current directory first */
+    if (*name != '.' && access(s, F_OK) == 0
+	&& stat(s, &st) >= 0 && !S_ISDIR(st.st_mode)) {
+	diddot = 1;
+	ret = source(enam);
+    }
+    if (ret) {
+	/* use a path with / in it */
+	for (s = arg0; *s; s++)
+	    if (*s == '/') {
+		if (*arg0 == '.') {
+		    if (arg0 + 1 == s)
+			++diddot;
+		    else if (arg0[1] == '.' && arg0 + 2 == s)
+			++dotdot;
+		}
+		ret = source(arg0);
+		break;
+	    }
+	if (!*s || (ret && isset(PATHDIRS) && diddot < 2 && dotdot == 0)) {
+	    /* search path for script */
+	    for (t = path; *t; t++) {
+		if (!(*t)[0] || ((*t)[0] == '.' && !(*t)[1])) {
+		    if (diddot)
+			continue;
+		    diddot = 1;
+		    strcpy(buf, arg0);
+		} else {
+		    if (strlen(*t) + strlen(arg0) + 1 >= PATH_MAX)
+			continue;
+		    sprintf(buf, "%s/%s", *t, arg0);
+		}
+		s = unmeta(buf);
+		if (access(s, F_OK) == 0 && stat(s, &st) >= 0
+		    && !S_ISDIR(st.st_mode)) {
+		    ret = source(enam = buf);
+		    break;
+		}
+	    }
+	}
+    }
+    /* clean up and return */
+    if (argv[1]) {
+	freearray(pparams);
+	pparams = old;
+    }
+    if (ret)
+	zwarnnam(name, "%e: %s", enam, errno);
+    zsfree(arg0);
+    if (old0)
+	argzero = old0;
+    return ret ? ret : lastval;
+}
+
+/**/
+int
+bin_emulate(char *nam, char **argv, char *ops, int func)
+{
+    emulate(*argv, ops['R']);
+    return 0;
+}
+
+/* eval: simple evaluation */
+
+/**/
+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) {
+	errflag = 0;
+	return 1;
+    }
+    execlist(list, 1, 0);
+    if (errflag) {
+	lastval = errflag;
+	errflag = 0;
+    }
+    return lastval;
+}
+
+static char *zbuf;
+static int readfd;
+
+/* Read a character from readfd, or from the buffer zbuf.  Return EOF on end of
+file/buffer. */
+
+/* read: get a line of input, or (for compctl functions) return some *
+ * useful data about the state of the editing line.  The -E and -e   *
+ * options mean that the result should be sent to stdout.  -e means, *
+ * in addition, that the result should not actually be assigned to   *
+ * the specified parameters.                                         */
+
+/**/
+int
+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");
+    char *buf, *bptr, *firstarg, *zbuforig;
+    LinkList readll = newlinklist();
+
+    if ((ops['k'] || ops['b']) && *args && idigit(**args)) {
+	if (!(nchars = atoi(*args)))
+	    nchars = 1;
+	args++;
+    }
+
+    firstarg = *args;
+    if (*args && **args == '?')
+	args++;
+    /* default result parameter */
+    reply = *args ? *args++ : ops['A'] ? "reply" : "REPLY";
+    if (ops['A'] && *args) {
+	zwarnnam(name, "only one array argument allowed", NULL, 0);
+	return 1;
+    }
+
+    /* handle compctl case */
+    if(ops['l'] || ops['c'])
+	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 (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'])
+	readfd = coprocin;
+    else
+	readfd = 0;
+
+    /* handle prompt */
+    if (firstarg) {
+	for (readpmpt = firstarg;
+	     *readpmpt && *readpmpt != '?'; readpmpt++);
+	if (*readpmpt++) {
+	    if (isatty(0)) {
+		zputs(readpmpt, stderr);
+		fflush(stderr);
+	    }
+	    readpmpt[-1] = '\0';
+	}
+    }
+
+    /* option -k means read only a given number of characters (default 1) */
+    if (ops['k']) {
+	int val;
+	char d;
+
+	/* allocate buffer space for result */
+	bptr = buf = (char *)zalloc(nchars+1);
+
+	do {
+	    /* 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;
+
+	    /* increment pointer past read characters */
+	    bptr += val;
+	} while (nchars > 0);
+	
+	if (!ops['u'] && !ops['p']) {
+	    /* dispose of result appropriately, etc. */
+	    if (isem)
+		while (val > 0 && read(SHTTY, &d, 1) == 1 && d != '\n');
+	    else
+		settyinfo(&shttyinfo);
+	    if (haso) {
+		close(SHTTY);
+		SHTTY = -1;
+	    }
+	}
+
+	if (ops['e'] || ops['E'])
+	    fwrite(buf, bptr - buf, 1, stdout);
+	if (!ops['e'])
+	    setsparam(reply, metafy(buf, bptr - buf, META_REALLOC));
+	else
+	    zfree(buf, bptr - buf + 1);
+	return val <= 0;
+    }
+
+    /* option -q means get one character, and interpret it as a Y or N */
+    if (ops['q']) {
+	char readbuf[2];
+
+	/* set up the buffer */
+	readbuf[1] = '\0';
+
+	/* get, and store, reply */
+	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'])
+	    setsparam(reply, ztrdup(readbuf));
+
+	return readbuf[0] == 'n';
+    }
+
+    /* All possible special types of input have been exhausted.  Take one line,
+    and assign words to the parameters until they run out.  Leftover words go
+    onto the last parameter.  If an array is specified, all the words become
+    separate elements of the array. */
+
+    zbuforig = zbuf = (!ops['z']) ? NULL :
+	(nonempty(bufstack)) ? (char *) getlinknode(bufstack) : ztrdup("");
+    first = 1;
+    bslash = 0;
+    while (*args || (ops['A'] && !gotnl)) {
+	buf = bptr = (char *)zalloc(bsiz = 64);
+	/* get input, a character at a time */
+	while (!gotnl) {
+	    c = zread();
+	    /* \ at the end of a line indicates a continuation *
+	     * line, except in raw mode (-r option)            */
+	    if (bslash && c == '\n') {
+		bslash = 0;
+		continue;
+	    }
+	    if (c == EOF || c == '\n')
+		break;
+	    if (!bslash && isep(c)) {
+		if (bptr != buf || (!iwsep(c) && first)) {
+		    first |= !iwsep(c);
+		    break;
+		}
+		first |= !iwsep(c);
+		continue;
+	    }
+	    bslash = c == '\\' && !bslash && !ops['r'];
+	    if (bslash)
+		continue;
+	    first = 0;
+	    if (imeta(c)) {
+		*bptr++ = Meta;
+		*bptr++ = c ^ 32;
+	    } else
+		*bptr++ = c;
+	    /* increase the buffer size, if necessary */
+	    if (bptr >= buf + bsiz - 1) {
+		int blen = bptr - buf;
+
+		buf = realloc(buf, bsiz *= 2);
+		bptr = buf + blen;
+	    }
+	}
+	if (c == '\n' || c == EOF)
+	    gotnl = 1;
+	*bptr = '\0';
+	/* dispose of word appropriately */
+	if (ops['e'] || ops['E']) {
+	    zputs(buf, stdout);
+	    putchar('\n');
+	}
+	if (!ops['e']) {
+	    if (ops['A']) {
+		addlinknode(readll, buf);
+		al++;
+	    } else
+		setsparam(reply, buf);
+	} else
+	    free(buf);
+	if (!ops['A'])
+	    reply = *args++;
+    }
+    /* handle EOF */
+    if (c == EOF) {
+	if (readfd == coprocin) {
+	    close(coprocin);
+	    close(coprocout);
+	    coprocin = coprocout = -1;
+	}
+    }
+    /* final assignment (and display) of array parameter */
+    if (ops['A']) {
+	char **pp, **p = NULL;
+	LinkNode n;
+
+	p = (ops['e'] ? (char **)NULL
+	     : (char **)zalloc((al + 1) * sizeof(char *)));
+
+	for (pp = p, n = firstnode(readll); n; incnode(n)) {
+	    if (ops['e'] || ops['E']) {
+		zputs((char *) getdata(n), stdout);
+		putchar('\n');
+	    }
+	    if (p)
+		*pp++ = (char *)getdata(n);
+	    else
+		zsfree(getdata(n));
+	}
+	if (p) {
+	    *pp++ = NULL;
+	    setaparam(reply, p);
+	}
+	return c == EOF;
+    }
+    buf = bptr = (char *)zalloc(bsiz = 64);
+    /* any remaining part of the line goes into one parameter */
+    bslash = 0;
+    if (!gotnl)
+	for (;;) {
+	    c = zread();
+	    /* \ at the end of a line introduces a continuation line, except in
+	    raw mode (-r option) */
+	    if (bslash && c == '\n') {
+		bslash = 0;
+		continue;
+	    }
+	    if (c == EOF || (c == '\n' && !zbuf))
+		break;
+	    if (!bslash && isep(c) && bptr == buf)
+		if (iwsep(c))
+		    continue;
+		else if (!first) {
+		    first = 1;
+		    continue;
+		}
+	    bslash = c == '\\' && !bslash && !ops['r'];
+	    if (bslash)
+		continue;
+	    if (imeta(c)) {
+		*bptr++ = Meta;
+		*bptr++ = c ^ 32;
+	    } else
+		*bptr++ = c;
+	    /* increase the buffer size, if necessary */
+	    if (bptr >= buf + bsiz - 1) {
+		int blen = bptr - buf;
+
+		buf = realloc(buf, bsiz *= 2);
+		bptr = buf + blen;
+	    }
+	}
+    while (bptr > buf && iwsep(bptr[-1]))
+	bptr--;
+    *bptr = '\0';
+    /* final assignment of reply, etc. */
+    if (ops['e'] || ops['E']) {
+	zputs(buf, stdout);
+	putchar('\n');
+    }
+    if (!ops['e'])
+	setsparam(reply, buf);
+    else
+	zsfree(buf);
+    if (zbuforig) {
+	char first = *zbuforig;
+
+	zsfree(zbuforig);
+	if (!first)
+	    return 1;
+    } else if (c == EOF) {
+	if (readfd == coprocin) {
+	    close(coprocin);
+	    close(coprocout);
+	    coprocin = coprocout = -1;
+	}
+	return 1;
+    }
+    return 0;
+}
+
+/**/
+static int
+zread(void)
+{
+    char cc, retry = 0;
+
+    /* use zbuf if possible */
+    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. */
+	if (*zbuf == Meta)
+	    return zbuf++, STOUC(*zbuf++ ^ 32);
+	else
+	    return (*zbuf) ? STOUC(*zbuf++) : EOF;
+    for (;;) {
+	/* read a character from readfd */
+	switch (read(readfd, &cc, 1)) {
+	case 1:
+	    /* return the character read */
+	    return STOUC(cc);
+#if defined(EAGAIN) || defined(EWOULDBLOCK)
+	case -1:
+	    if (!retry && readfd == 0 && (
+# ifdef EAGAIN
+		    errno == EAGAIN
+#  ifdef EWOULDBLOCK
+		    ||
+#  endif /* EWOULDBLOCK */
+# endif /* EAGAIN */
+# ifdef EWOULDBLOCK
+		    errno == EWOULDBLOCK
+# endif /* EWOULDBLOCK */
+		) && setblock_stdin()) {
+		retry = 1;
+		continue;
+	    }
+	    break;
+#endif /* EAGAIN || EWOULDBLOCK */
+	}
+	return EOF;
+    }
+}
+
+/* holds arguments for testlex() */
+/**/
+char **testargs;
+
+/* test, [: the old-style general purpose logical expression builtin */
+
+/**/
+void
+testlex(void)
+{
+    if (tok == LEXERR)
+	return;
+
+    tokstr = *testargs;
+    if (!*testargs) {
+	/* if tok is already zero, reading past the end:  error */
+	tok = tok ? NULLTOK : LEXERR;
+	return;
+    } else if (!strcmp(*testargs, "-o"))
+	tok = DBAR;
+    else if (!strcmp(*testargs, "-a"))
+	tok = DAMPER;
+    else if (!strcmp(*testargs, "!"))
+	tok = BANG;
+    else if (!strcmp(*testargs, "("))
+	tok = INPAR;
+    else if (!strcmp(*testargs, ")"))
+	tok = OUTPAR;
+    else
+	tok = STRING;
+    testargs++;
+}
+
+/**/
+int
+bin_test(char *name, char **argv, char *ops, int func)
+{
+    char **s;
+    Cond c;
+
+    /* if "test" was invoked as "[", it needs a matching "]" *
+     * which is subsequently ignored                         */
+    if (func == BIN_BRACKET) {
+	for (s = argv; *s; s++);
+	if (s == argv || strcmp(s[-1], "]")) {
+	    zwarnnam(name, "']' expected", NULL, 0);
+	    return 1;
+	}
+	s[-1] = NULL;
+    }
+    /* an empty argument list evaluates to false (1) */
+    if (!*argv)
+	return 1;
+
+    testargs = argv;
+    tok = NULLTOK;
+    condlex = testlex;
+    testlex();
+    c = par_cond();
+    condlex = yylex;
+
+    if (errflag) {
+	errflag = 0;
+	return 1;
+    }
+
+    if (!c || tok == LEXERR) {
+	zwarnnam(name, tokstr ? "parse error" : "argument expected", NULL, 0);
+	return 1;
+    }
+
+    /* syntax is OK, so evaluate */
+    return !evalcond(c);
+}
+
+/* display a time, provided in units of 1/60s, as minutes and seconds */
+#define pttime(X) printf("%ldm%ld.%02lds",((long) (X))/3600,\
+			 ((long) (X))/60%60,((long) (X))*100/60%100)
+
+/* times: display, in a two-line format, the times provided by times(3) */
+
+/**/
+int
+bin_times(char *name, char **argv, char *ops, int func)
+{
+    struct tms buf;
+
+    /* get time accounting information */
+    if (times(&buf) == -1)
+	return 1;
+    pttime(buf.tms_utime);	/* user time */
+    putchar(' ');
+    pttime(buf.tms_stime);	/* system time */
+    putchar('\n');
+    pttime(buf.tms_cutime);	/* user time, children */
+    putchar(' ');
+    pttime(buf.tms_cstime);	/* system time, children */
+    putchar('\n');
+    return 0;
+}
+
+/* trap: set/unset signal traps */
+
+/**/
+int
+bin_trap(char *name, char **argv, char *ops, int func)
+{
+    List l;
+    char *arg, *s;
+    int sig;
+
+    if (*argv && !strcmp(*argv, "--"))
+	argv++;
+
+    /* If given no arguments, list all currently-set traps */
+    if (!*argv) {
+	for (sig = 0; sig < VSIGCOUNT; sig++) {
+	    if (sigtrapped[sig] & ZSIG_FUNC) {
+		char fname[20];
+		HashNode hn;
+
+		sprintf(fname, "TRAP%s", sigs[sig]);
+		if ((hn = shfunctab->getnode(shfunctab, fname)))
+		    shfunctab->printnode(hn, 0);
+		DPUTS(!hn, "BUG: I did not find any trap functions!");
+	    } else if (sigtrapped[sig]) {
+		if (!sigfuncs[sig])
+		    printf("trap -- '' %s\n", sigs[sig]);
+		else {
+		    s = getpermtext((void *) dupstruct((void *) sigfuncs[sig]));
+		    printf("trap -- ");
+		    quotedzputs(s, stdout);
+		    printf(" %s\n", sigs[sig]);
+		    zsfree(s);
+		}
+	    }
+	}
+	return 0;
+    }
+
+    /* If we have a signal number, unset the specified *
+     * signals.  With only -, remove all traps.        */
+    if ((getsignum(*argv) != -1) || (!strcmp(*argv, "-") && argv++)) {
+	if (!*argv)
+	    for (sig = 0; sig < VSIGCOUNT; sig++)
+		unsettrap(sig);
+	else
+	    while (*argv)
+		unsettrap(getsignum(*argv++));
+	return 0;
+    }
+
+    /* Sort out the command to execute on trap */
+    arg = *argv++;
+    if (!*arg)
+	l = NULL;
+    else if (!(l = parse_string(arg))) {
+	zwarnnam(name, "couldn't parse trap command", NULL, 0);
+	return 1;
+    }
+
+    /* set traps */
+    for (; *argv; argv++) {
+	List t;
+
+	sig = getsignum(*argv);
+	if (sig == -1) {
+	    zwarnnam(name, "undefined signal: %s", *argv, 0);
+	    break;
+	}
+	PERMALLOC {
+	    t = (List) dupstruct(l);
+	} LASTALLOC;
+	if (settrap(sig, t))
+	    freestruct(t);
+    }
+    return *argv != NULL;
+}
+
+/**/
+int
+bin_ttyctl(char *name, char **argv, char *ops, int func)
+{
+    if (ops['f'])
+	ttyfrozen = 1;
+    else if (ops['u'])
+	ttyfrozen = 0;
+    else
+	printf("tty is %sfrozen\n", ttyfrozen ? "" : "not ");
+    return 0;
+}
+
+/* let -- mathematical evaluation */
+
+/**/
+int
+bin_let(char *name, char **argv, char *ops, int func)
+{
+    long val = 0;
+
+    while (*argv)
+	val = matheval(*argv++);
+    /* Errors in math evaluation in let are non-fatal. */
+    errflag = 0;
+    return !val;
+}
+
+/* umask command.  umask may be specified as octal digits, or in the  *
+ * symbolic form that chmod(1) uses.  Well, a subset of it.  Remember *
+ * that only the bottom nine bits of umask are used, so there's no    *
+ * point allowing the set{u,g}id and sticky bits to be specified.     */
+
+/**/
+int
+bin_umask(char *nam, char **args, char *ops, int func)
+{
+    mode_t um;
+    char *s = *args;
+
+    /* Get the current umask. */
+    um = umask(0);
+    umask(um);
+    /* No arguments means to display the current setting. */
+    if (!s) {
+	if (ops['S']) {
+	    char *who = "ugo";
+
+	    while (*who) {
+		char *what = "rwx";
+		printf("%c=", *who++);
+		while (*what) {
+		    if (!(um & 0400))
+			putchar(*what);
+		    um <<= 1;
+		    what++;
+		}
+		putchar(*who ? ',' : '\n');
+	    }
+	} else {
+	    if (um & 0700)
+		putchar('0');
+	    printf("%03o\n", (unsigned)um);
+	}
+	return 0;
+    }
+
+    if (idigit(*s)) {
+	/* Simple digital umask. */
+	um = zstrtol(s, &s, 8);
+	if (*s) {
+	    zwarnnam(nam, "bad umask", NULL, 0);
+	    return 1;
+	}
+    } else {
+	/* Symbolic notation -- slightly complicated. */
+	int whomask, umaskop, mask;
+
+	/* More than one symbolic argument may be used at once, each separated
+	by commas. */
+	for (;;) {
+	    /* First part of the argument -- who does this apply to?
+	    u=owner, g=group, o=other. */
+	    whomask = 0;
+	    while (*s == 'u' || *s == 'g' || *s == 'o' || *s == 'a')
+		if (*s == 'u')
+		    s++, whomask |= 0700;
+		else if (*s == 'g')
+		    s++, whomask |= 0070;
+		else if (*s == 'o')
+		    s++, whomask |= 0007;
+		else if (*s == 'a')
+		    s++, whomask |= 0777;
+	    /* Default whomask is everyone. */
+	    if (!whomask)
+		whomask = 0777;
+	    /* Operation may be +, - or =. */
+	    umaskop = (int)*s;
+	    if (!(umaskop == '+' || umaskop == '-' || umaskop == '=')) {
+		if (umaskop)
+		    zwarnnam(nam, "bad symbolic mode operator: %c", NULL, umaskop);
+		else
+		    zwarnnam(nam, "bad umask", NULL, 0);
+		return 1;
+	    }
+	    /* Permissions mask -- r=read, w=write, x=execute. */
+	    mask = 0;
+	    while (*++s && *s != ',')
+		if (*s == 'r')
+		    mask |= 0444 & whomask;
+		else if (*s == 'w')
+		    mask |= 0222 & whomask;
+		else if (*s == 'x')
+		    mask |= 0111 & whomask;
+		else {
+		    zwarnnam(nam, "bad symbolic mode permission: %c",
+			     NULL, *s);
+		    return 1;
+		}
+	    /* Apply parsed argument to um. */
+	    if (umaskop == '+')
+		um &= ~mask;
+	    else if (umaskop == '-')
+		um |= mask;
+	    else		/* umaskop == '=' */
+		um = (um | (whomask)) & ~mask;
+	    if (*s == ',')
+		s++;
+	    else
+		break;
+	}
+	if (*s) {
+	    zwarnnam(nam, "bad character in symbolic mode: %c", NULL, *s);
+	    return 1;
+	}
+    }
+
+    /* Finally, set the new umask. */
+    umask(um);
+    return 0;
+}
+
+/* Generic builtin for facilities not available on this OS */
+
+/**/
+int
+bin_notavail(char *nam, char **argv, char *ops, int func)
+{
+    zwarnnam(nam, "not available on this system", NULL, 0);
+    return 1;
+}
diff --git a/Src/compat.c b/Src/compat.c
new file mode 100644
index 000000000..ca9c57aac
--- /dev/null
+++ b/Src/compat.c
@@ -0,0 +1,285 @@
+/*
+ * compat.c - compatibiltiy routines for the deprived
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "compat.pro"
+
+/* Return pointer to first occurence of string t *
+ * in string s.  Return NULL if not present.     */
+
+#ifndef HAVE_STRSTR
+char *
+strstr(const char *s, const char *t)
+{
+    char *p1, *p2;
+ 
+    for (; *s; s++) {
+        for (p1 = s, p2 = t; *p2; p1++, p2++)
+            if (*p1 != *p2)
+                break;
+        if (!*p2)
+            return (char *)s;
+    }
+    return NULL;
+}
+#endif
+
+
+#ifndef HAVE_GETHOSTNAME
+int
+gethostname(char *name, size_t namelen)
+{
+    struct utsname uts;
+
+    uname(&uts);
+    if(strlen(uts.nodename) >= namelen) {
+	errno = EINVAL;
+	return -1;
+    }
+    strcpy(name, uts.nodename);
+    return 0;
+}
+#endif
+
+
+#ifndef HAVE_GETTIMEOFDAY
+int
+gettimeofday(struct timeval *tv, struct timezone *tz)
+{
+    tv->tv_usec = 0;
+    tv->tv_sec = (long)time((time_t) 0);
+    return 0;
+}
+#endif
+
+
+/* compute the difference between two calendar times */
+
+#ifndef HAVE_DIFFTIME
+double
+difftime(time_t t2, time_t t1)
+{
+    return ((double)t2 - (double)t1);
+}
+#endif
+
+
+#ifndef HAVE_STRERROR
+extern char *sys_errlist[];
+
+/* Get error message string associated with a particular  *
+ * error number, and returns a pointer to that string.    *
+ * This is not a particularly robust version of strerror. */
+
+char *
+strerror(int errnum)
+{
+    return (sys_errlist[errnum]);
+}
+#endif
+
+
+/**/
+char *
+zgetdir(struct dirsav *d)
+{
+    char nbuf[PATH_MAX+3];
+    char *buf;
+    int bufsiz, pos, len;
+    struct stat sbuf;
+    struct dirent *de;
+    DIR *dir;
+    ino_t ino, pino;
+    dev_t dev, pdev;
+
+    buf = halloc(bufsiz = PATH_MAX);
+    pos = bufsiz - 1;
+    buf[pos] = '\0';
+    strcpy(nbuf, "../");
+    if (stat(".", &sbuf) < 0) {
+	if (d)
+	    return NULL;
+	buf[0] = '.';
+	buf[1] = '\0';
+	return buf;
+    }
+
+    pino = sbuf.st_ino;
+    pdev = sbuf.st_dev;
+    if (d)
+	d->ino = pino, d->dev = pdev;
+#ifdef HAVE_FCHDIR
+    else
+#endif
+	holdintr();
+
+    for (;;) {
+	if (stat("..", &sbuf) < 0)
+	    break;
+
+	ino = pino;
+	dev = pdev;
+	pino = sbuf.st_ino;
+	pdev = sbuf.st_dev;
+
+	if (ino == pino && dev == pdev) {
+	    if (!buf[pos])
+		buf[--pos] = '/';
+	    if (d) {
+#ifndef HAVE_FCHDIR
+		zchdir(buf + pos);
+		noholdintr();
+#endif
+		return d->dirname = ztrdup(buf + pos);
+	    }
+	    zchdir(buf + pos);
+	    noholdintr();
+	    return buf + pos;
+	}
+
+	if (!(dir = opendir("..")))
+	    break;
+
+	while ((de = readdir(dir))) {
+	    char *fn = de->d_name;
+	    /* Ignore `.' and `..'. */
+	    if (fn[0] == '.' &&
+		(fn[1] == '\0' ||
+		 (fn[1] == '.' && fn[2] == '\0')))
+		continue;
+#ifdef HAVE_STRUCT_DIRENT_D_STAT
+	    if(de->d_stat.st_dev == dev && de->d_stat.st_ino == ino) {
+		strncpy(nbuf + 3, fn, PATH_MAX);
+		break;
+	    }
+#else /* !HAVE_STRUCT_DIRENT_D_STAT */
+# ifdef HAVE_STRUCT_DIRENT_D_INO
+	    if (dev != pdev || (ino_t) de->d_ino == ino)
+# endif /* HAVE_STRUCT_DIRENT_D_INO */
+	    {
+		strncpy(nbuf + 3, fn, PATH_MAX);
+		lstat(nbuf, &sbuf);
+		if (sbuf.st_dev == dev && sbuf.st_ino == ino)
+		    break;
+	    }
+#endif /* !HAVE_STRUCT_DIRENT_D_STAT */
+	}
+	closedir(dir);
+	if (!de)
+	    break;
+	len = strlen(nbuf + 2);
+	pos -= len;
+	while (pos <= 1) {
+	    char *newbuf = halloc(2*bufsiz);
+	    memcpy(newbuf + bufsiz, buf, bufsiz);
+	    buf = newbuf;
+	    pos += bufsiz;
+	    bufsiz *= 2;
+	}
+	memcpy(buf + pos, nbuf + 2, len);
+#ifdef HAVE_FCHDIR
+	if (d)
+	    return d->dirname = ztrdup(buf + pos + 1);
+#endif
+	if (chdir(".."))
+	    break;
+    }
+    if (d) {
+#ifndef HAVE_FCHDIR
+	if (*buf)
+	    zchdir(buf + pos + 1);
+	noholdintr();
+#endif
+	return NULL;
+    }
+    if (*buf)
+	zchdir(buf + pos + 1);
+    noholdintr();
+    buf[0] = '.';
+    buf[1] = '\0';
+    return buf;
+}
+
+/**/
+char *
+zgetcwd(void)
+{
+    return zgetdir(NULL);
+}
+
+/* chdir with arbitrary long pathname.  Returns 0 on success, 0 on normal *
+ * faliliure and -2 when chdir failed and the current directory is lost.  */
+
+/**/
+int
+zchdir(char *dir)
+{
+    char *s;
+    int currdir = -2;
+
+    for (;;) {
+	if (!*dir)
+	    return 0;
+	if (!chdir(dir))
+	    return 0;
+	if ((errno != ENAMETOOLONG && errno != ENOMEM) ||
+	    strlen(dir) < PATH_MAX)
+	    break;
+	for (s = dir + PATH_MAX - 1; s > dir && *s != '/'; s--);
+	if (s == dir)
+	    break;
+#ifdef HAVE_FCHDIR
+	if (currdir == -2)
+	    currdir = open(".", O_RDONLY|O_NOCTTY);
+#endif
+	*s = '\0';
+	if (chdir(dir)) {
+	    *s = '/';
+	    break;
+	}
+#ifndef HAVE_FCHDIR
+	currdir = -1;
+#endif
+	*s = '/';
+	while (*++s == '/');
+	dir = s;
+    }
+#ifdef HAVE_FCHDIR
+    if (currdir == -1 || (currdir >= 0 && fchdir(currdir))) {
+	if (currdir >= 0)
+	    close(currdir);
+	return -2;
+    }
+    if (currdir >= 0)
+	close(currdir);
+    return -1;
+#else
+    return currdir == -2 ? -1 : -2;
+#endif
+}
diff --git a/Src/cond.c b/Src/cond.c
new file mode 100644
index 000000000..79886a720
--- /dev/null
+++ b/Src/cond.c
@@ -0,0 +1,226 @@
+/*
+ * cond.c - evaluate conditional expressions
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "cond.pro"
+
+/**/
+int
+evalcond(Cond c)
+{
+    struct stat *st;
+
+    switch (c->type) {
+    case COND_NOT:
+	return !evalcond(c->left);
+    case COND_AND:
+	return evalcond(c->left) && evalcond(c->right);
+    case COND_OR:
+	return evalcond(c->left) || evalcond(c->right);
+    }
+    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);
+    }
+    switch (c->type) {
+    case COND_STREQ:
+	return matchpat(c->left, c->right);
+    case COND_STRNEQ:
+	return !matchpat(c->left, c->right);
+    case COND_STRLT:
+	return strcmp(c->left, c->right) < 0;
+    case COND_STRGTR:
+	return strcmp(c->left, c->right) > 0;
+    case 'e':
+    case 'a':
+	return (doaccess(c->left, F_OK));
+    case 'b':
+	return (S_ISBLK(dostat(c->left)));
+    case 'c':
+	return (S_ISCHR(dostat(c->left)));
+    case 'd':
+	return (S_ISDIR(dostat(c->left)));
+    case 'f':
+	return (S_ISREG(dostat(c->left)));
+    case 'g':
+	return (!!(dostat(c->left) & S_ISGID));
+    case 'k':
+	return (!!(dostat(c->left) & S_ISVTX));
+    case 'n':
+	return (!!strlen(c->left));
+    case 'o':
+	return (optison(c->left));
+    case 'p':
+	return (S_ISFIFO(dostat(c->left)));
+    case 'r':
+	return (doaccess(c->left, R_OK));
+    case 's':
+	return ((st = getstat(c->left)) && !!(st->st_size));
+    case 'S':
+	return (S_ISSOCK(dostat(c->left)));
+    case 'u':
+	return (!!(dostat(c->left) & S_ISUID));
+    case 'w':
+	return (doaccess(c->left, W_OK));
+    case 'x':
+	if (privasserted()) {
+	    mode_t mode = dostat(c->left);
+	    return (mode & S_IXUGO) || S_ISDIR(mode);
+	}
+	return doaccess(c->left, X_OK);
+    case 'z':
+	return (!strlen(c->left));
+    case 'h':
+    case 'L':
+	return (S_ISLNK(dolstat(c->left)));
+    case 'O':
+	return ((st = getstat(c->left)) && st->st_uid == geteuid());
+    case 'G':
+	return ((st = getstat(c->left)) && st->st_gid == getegid());
+    case 'N':
+	return ((st = getstat(c->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);
+    case COND_NT:
+    case COND_OT:
+	{
+	    time_t a;
+
+	    if (!(st = getstat(c->left)))
+		return 0;
+	    a = st->st_mtime;
+	    if (!(st = getstat(c->right)))
+		return 0;
+	    return (c->type == COND_NT) ? a > st->st_mtime : a < st->st_mtime;
+	}
+    case COND_EF:
+	{
+	    dev_t d;
+	    ino_t i;
+
+	    if (!(st = getstat(c->left)))
+		return 0;
+	    d = st->st_dev;
+	    i = st->st_ino;
+	    if (!(st = getstat(c->right)))
+		return 0;
+	    return d == st->st_dev && i == st->st_ino;
+	}
+    default:
+	zerr("bad cond structure", NULL, 0);
+    }
+    return 0;
+}
+
+
+/**/
+static int
+doaccess(char *s, int c)
+{
+    return !access(unmeta(s), c);
+}
+
+
+static struct stat st;
+
+/**/
+static struct stat *
+getstat(char *s)
+{
+/* /dev/fd/n refers to the open file descriptor n.  We always use fstat *
+ * in this case since on Solaris /dev/fd/n is a device special file     */
+    if (!strncmp(s, "/dev/fd/", 8)) {
+	if (fstat(atoi(s + 8), &st))
+	    return NULL;
+        return &st;
+    }
+
+    if (stat(unmeta(s), &st))
+	return NULL;
+    return &st;
+}
+
+
+/**/
+static mode_t
+dostat(char *s)
+{
+    struct stat *statp;
+
+    if (!(statp = getstat(s)))
+	return 0;
+    return statp->st_mode;
+}
+
+
+/* pem@aaii.oz; needed since dostat now uses "stat" */
+
+/**/
+static mode_t
+dolstat(char *s)
+{
+    if (lstat(unmeta(s), &st) < 0)
+	return 0;
+    return st.st_mode;
+}
+
+
+/**/
+static int
+optison(char *s)
+{
+    int i;
+
+    if (strlen(s) == 1)
+	i = optlookupc(*s);
+    else
+	i = optlookup(s);
+    if (!i) {
+	zerr("no such option: %s", s, 0);
+	return 0;
+    } else if(i < 0)
+	return unset(-i);
+    else
+	return isset(i);
+}
diff --git a/Src/exec.c b/Src/exec.c
new file mode 100644
index 000000000..1b355d028
--- /dev/null
+++ b/Src/exec.c
@@ -0,0 +1,2965 @@
+/*
+ * exec.c - command execution
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "exec.pro"
+
+/* used to suppress ERREXIT and trapping of SIGZERR, SIGEXIT. */
+
+/**/
+int noerrexit;
+
+/* suppress error messages */
+ 
+/**/
+int noerrs;
+ 
+/* do not save history on exec and exit */
+
+/**/
+int nohistsave;
+ 
+/* error/break flag */
+ 
+/**/
+int errflag;
+ 
+/* Status of return from a trap */
+ 
+/**/
+int trapreturn;
+ 
+/* != 0 if this is a subshell */
+ 
+/**/
+int subsh;
+ 
+/* != 0 if we have a return pending */
+ 
+/**/
+int retflag;
+
+/**/
+long lastval2;
+ 
+/* The table of file descriptors.  A table element is zero if the  *
+ * corresponding fd is not used by the shell.  It is greater than  *
+ * 1 if the fd is used by a <(...) or >(...) substitution and 1 if *
+ * it is an internal file descriptor which must be closed before   *
+ * executing an external command.  The first ten elements of the   *
+ * table is not used.  A table element is set by movefd and cleard *
+ * by zclose.                                                      */
+
+/**/
+char *fdtable;
+
+/* The allocated size of fdtable */
+
+/**/
+int fdtable_size;
+
+/* The highest fd that marked with nonzero in fdtable */
+
+/**/
+int max_zsh_fd;
+
+/* input fd from the coprocess */
+
+/**/
+int coprocin;
+
+/* output fd from the coprocess */
+
+/**/
+int coprocout;
+
+/* != 0 if the line editor is active */
+
+/**/
+int zleactive;
+
+/* pid of process undergoing 'process substitution' */
+ 
+/**/
+pid_t cmdoutpid;
+ 
+/* exit status of process undergoing 'process substitution' */
+ 
+/**/
+int cmdoutval;
+ 
+/* Stack to save some variables before executing a signal handler function */
+
+/**/
+struct execstack *exstack;
+
+#define execerr() if (!forked) { lastval = 1; return; } else _exit(1)
+
+static LinkList args;
+static int doneps4;
+
+/* parse string into a list */
+
+/**/
+List
+parse_string(char *s)
+{
+    List l;
+
+    lexsave();
+    inpush(s, 0, NULL);
+    strinbeg();
+    stophist = 2;
+    l = parse_list();
+    strinend();
+    inpop();
+    lexrestore();
+    return l;
+}
+
+#ifdef HAVE_GETRLIMIT
+
+/* the resource limits for the shell and its children */
+
+/**/
+struct rlimit current_limits[RLIM_NLIMITS], limits[RLIM_NLIMITS];
+ 
+/**/
+int
+zsetlimit(int limnum, char *nam)
+{
+    if (limits[limnum].rlim_max != current_limits[limnum].rlim_max ||
+	limits[limnum].rlim_cur != current_limits[limnum].rlim_cur) {
+	if (setrlimit(limnum, limits + limnum)) {
+	    if (nam)
+		zwarnnam(nam, "setrlimit failed: %e", NULL, errno);
+	    return -1;
+	}
+	current_limits[limnum] = limits[limnum];
+    }
+    return 0;
+}
+
+/**/
+int
+setlimits(char *nam)
+{
+    int limnum;
+    int ret = 0;
+
+    for (limnum = 0; limnum < RLIM_NLIMITS; limnum++)
+	if (zsetlimit(limnum, nam))
+	    ret++;
+    return ret;
+}
+
+#endif /* HAVE_GETRLIMIT */
+
+/* fork and set limits */
+
+/**/
+static pid_t
+zfork(void)
+{
+    pid_t pid;
+
+    if (thisjob >= MAXJOB - 1) {
+	zerr("job table full", NULL, 0);
+	return -1;
+    }
+    pid = fork();
+    if (pid == -1) {
+	zerr("fork failed: %e", NULL, errno);
+	return -1;
+    }
+#ifdef HAVE_GETRLIMIT
+    if (!pid)
+	/* set resource limits for the child process */
+	setlimits(NULL);
+#endif
+    return pid;
+}
+
+
+/**/
+int list_pipe = 0, simple_pline = 0;
+
+static pid_t list_pipe_pid;
+static int nowait, pline_level = 0;
+static int list_pipe_child = 0, list_pipe_job;
+static char list_pipe_text[JOBTEXTSIZE];
+
+/* execute a current shell command */
+
+/**/
+static int
+execcursh(Cmd cmd)
+{
+    if (!list_pipe)
+	deletejob(jobtab + thisjob);
+    execlist(cmd->u.list, 1, cmd->flags & CFLAG_EXEC);
+    cmd->u.list = NULL;
+    return lastval;
+}
+
+/* execve after handling $_ and #! */
+
+#define POUNDBANGLIMIT 64
+
+/**/
+static int
+zexecve(char *pth, char **argv)
+{
+    int eno;
+    static char buf[PATH_MAX * 2];
+    char **eep;
+
+    unmetafy(pth, NULL);
+    for (eep = argv; *eep; eep++)
+	if (*eep != pth)
+	    unmetafy(*eep, NULL);
+    for (eep = environ; *eep; eep++)
+	if (**eep == '_' && (*eep)[1] == '=')
+	    break;
+    buf[0] = '_';
+    buf[1] = '=';
+    if (*pth == '/')
+	strcpy(buf + 2, pth);
+    else
+	sprintf(buf + 2, "%s/%s", pwd, pth);
+    if (!*eep)
+	eep[1] = NULL;
+    *eep = buf;
+    execve(pth, argv, environ);
+
+    /* If the execve returns (which in general shouldn't happen),   *
+     * then check for an errno equal to ENOEXEC.  This errno is set *
+     * if the process file has the appropriate access permission,   *
+     * but has an invalid magic number in its header.               */
+    if ((eno = errno) == ENOEXEC) {
+	char execvebuf[POUNDBANGLIMIT + 1], *ptr, *ptr2, *argv0;
+	int fd, ct, t0;
+
+	if ((fd = open(pth, O_RDONLY|O_NOCTTY)) >= 0) {
+	    argv0 = *argv;
+	    *argv = pth;
+	    ct = read(fd, execvebuf, POUNDBANGLIMIT);
+	    close(fd);
+	    if (ct > 0) {
+		if (execvebuf[0] == '#') {
+		    if (execvebuf[1] == '!') {
+			for (t0 = 0; t0 != ct; t0++)
+			    if (execvebuf[t0] == '\n')
+				execvebuf[t0] = '\0';
+			execvebuf[POUNDBANGLIMIT] = '\0';
+			for (ptr = execvebuf + 2; *ptr && *ptr == ' '; ptr++);
+			for (ptr2 = ptr; *ptr && *ptr != ' '; ptr++);
+			if (*ptr) {
+			    *ptr = '\0';
+			    argv[-2] = ptr2;
+			    argv[-1] = ptr + 1;
+			    execve(ptr2, argv - 2, environ);
+			} else {
+			    argv[-1] = ptr2;
+			    execve(ptr2, argv - 1, environ);
+			}
+		    } else {
+			argv[-1] = "sh";
+			execve("/bin/sh", argv - 1, environ);
+		    }
+		} else {
+		    for (t0 = 0; t0 != ct; t0++)
+			if (!execvebuf[t0])
+			    break;
+		    if (t0 == ct) {
+			argv[-1] = "sh";
+			execve("/bin/sh", argv - 1, environ);
+		    }
+		}
+	    } else
+		eno = errno;
+	    *argv = argv0;
+	} else
+	    eno = errno;
+    }
+    /* restore the original arguments and path but do not bother with *
+     * null characters as these cannot be passed to external commands *
+     * anyway.  So the result is truncated at the first null char.    */
+    pth = metafy(pth, -1, META_NOALLOC);
+    for (eep = argv; *eep; eep++)
+	if (*eep != pth)
+	    (void) metafy(*eep, -1, META_NOALLOC);
+    return eno;
+}
+
+#define MAXCMDLEN (PATH_MAX*4)
+
+/* test whether we really want to believe the error number */
+
+/**/
+static int
+isgooderr(int e, char *dir)
+{
+    /*
+     * Maybe the directory was unreadable, or maybe it wasn't
+     * even a directory. 
+     */
+    return ((e != EACCES || !access(dir, X_OK)) &&
+	    e != ENOENT && e != ENOTDIR); 
+}
+
+/* execute an external command */
+
+/**/
+void
+execute(Cmdnam not_used_yet, int dash)
+{
+    Cmdnam cn;
+    static LinkList exargs;
+    char buf[MAXCMDLEN], buf2[MAXCMDLEN];
+    char *s, *z, *arg0;
+    char **argv, **pp;
+    int eno = 0, ee;
+
+    arg0 = (char *) peekfirst(args);
+    if (isset(RESTRICTED) && strchr(arg0, '/')) {
+	zerr("%s: restricted", arg0, 0);
+	_exit(1);
+    }
+
+    /* If the parameter STTY is set in the command's environment, *
+     * we first run the stty command with the value of this       *
+     * parameter as it arguments.                                 */
+    if (!exargs && (s = zgetenv("STTY")) && isatty(0)) {
+	char *t;
+
+	exargs = args;	/* this prevents infinite recursion */
+	args = NULL;
+	t = tricat("stty", " ", s);
+	execstring(t, 1, 0);
+	zsfree(t);
+	args = exargs;
+	exargs = NULL;
+    }
+
+    cn = (Cmdnam) cmdnamtab->getnode(cmdnamtab, arg0);
+
+    /* If ARGV0 is in the commands environment, we use *
+     * that as argv[0] for this external command       */
+    if (unset(RESTRICTED) && (z = zgetenv("ARGV0"))) {
+	setdata(firstnode(args), (void *) ztrdup(z));
+	delenv(z - 6);
+    } else if (dash) {
+    /* Else if the pre-command `-' was given, we add `-' *
+     * to the front of argv[0] for this command.         */
+	sprintf(buf2, "-%s", arg0);
+	setdata(firstnode(args), (void *) ztrdup(buf2));
+    }
+
+    argv = makecline(args);
+    child_unblock();
+    if ((int) strlen(arg0) >= PATH_MAX) {
+	zerr("command too long: %s", arg0, 0);
+	_exit(1);
+    }
+    for (s = arg0; *s; s++)
+	if (*s == '/') {
+	    errno = zexecve(arg0, argv);
+	    if (arg0 == s || unset(PATHDIRS) ||
+		(arg0[0] == '.' && (arg0 + 1 == s ||
+				    (arg0[1] == '.' && arg0 + 2 == s)))) {
+		zerr("%e: %s", arg0, errno);
+		_exit(1);
+	    }
+	    break;
+	}
+
+    if (cn) {
+	char nn[PATH_MAX], *dptr;
+
+	if (cn->flags & HASHED)
+	    strcpy(nn, cn->u.cmd);
+	else {
+	    for (pp = path; pp < cn->u.name; pp++)
+		if (!**pp || (**pp == '.' && (*pp)[1] == '\0')) {
+		    ee = zexecve(arg0, argv);
+		    if (isgooderr(ee, *pp))
+			eno = ee;
+		} else if (**pp != '/') {
+		    z = buf;
+		    strucpy(&z, *pp);
+		    *z++ = '/';
+		    strcpy(z, arg0);
+		    ee = zexecve(buf, argv);
+		    if (isgooderr(ee, *pp))
+			eno = ee;
+		}
+	    strcpy(nn, cn->u.name ? *(cn->u.name) : "");
+	    strcat(nn, "/");
+	    strcat(nn, cn->nam);
+	}
+	ee = zexecve(nn, argv);
+
+	if ((dptr = strrchr(nn, '/')))
+	    *dptr = '\0';
+	if (isgooderr(ee, *nn ? nn : "/"))
+	    eno = ee;
+    }
+    for (pp = path; *pp; pp++)
+	if (!(*pp)[0] || ((*pp)[0] == '.' && !(*pp)[1])) {
+	    ee = zexecve(arg0, argv);
+	    if (isgooderr(ee, *pp))
+		eno = ee;
+	} else {
+	    z = buf;
+	    strucpy(&z, *pp);
+	    *z++ = '/';
+	    strcpy(z, arg0);
+	    ee = zexecve(buf, argv);
+	    if (isgooderr(ee, *pp))
+		eno = ee;
+	}
+    if (eno)
+	zerr("%e: %s", arg0, eno);
+    else
+	zerr("command not found: %s", arg0, 0);
+    _exit(1);
+}
+
+#define try(X) { if (iscom(X)) return ztrdup(X); }
+
+/* get the full pathname of an external command */
+
+/**/
+char *
+findcmd(char *arg0)
+{
+    char **pp;
+    char *z, *s, buf[MAXCMDLEN];
+    Cmdnam cn;
+
+    cn = (Cmdnam) cmdnamtab->getnode(cmdnamtab, arg0);
+    if (!cn && isset(HASHCMDS))
+	cn = hashcmd(arg0, path);
+    if ((int) strlen(arg0) > PATH_MAX)
+	return NULL;
+    for (s = arg0; *s; s++)
+	if (*s == '/') {
+	    try(arg0);
+	    if (arg0 == s || unset(PATHDIRS)) {
+		return NULL;
+	    }
+	    break;
+	}
+    if (cn) {
+	char nn[PATH_MAX];
+
+	if (cn->flags & HASHED)
+	    strcpy(nn, cn->u.cmd);
+	else {
+	    for (pp = path; pp < cn->u.name; pp++)
+		if (**pp != '/') {
+		    z = buf;
+		    if (**pp) {
+			strucpy(&z, *pp);
+			*z++ = '/';
+		    }
+		    strcpy(z, arg0);
+		    try(buf);
+		}
+	    strcpy(nn, cn->u.name ? *(cn->u.name) : "");
+	    strcat(nn, "/");
+	    strcat(nn, cn->nam);
+	}
+	try(nn);
+    }
+    for (pp = path; *pp; pp++) {
+	z = buf;
+	if (**pp) {
+	    strucpy(&z, *pp);
+	    *z++ = '/';
+	}
+	strcpy(z, arg0);
+	try(buf);
+    }
+    return NULL;
+}
+
+/**/
+int
+iscom(char *s)
+{
+    struct stat statbuf;
+    char *us = unmeta(s);
+
+    return (access(us, X_OK) == 0 && stat(us, &statbuf) >= 0 &&
+	    S_ISREG(statbuf.st_mode));
+}
+
+/**/
+int
+isreallycom(Cmdnam cn)
+{
+    char fullnam[MAXCMDLEN];
+
+    strcpy(fullnam, cn->u.name ? *(cn->u.name) : "");
+    strcat(fullnam, "/");
+    strcat(fullnam, cn->nam);
+    return iscom(fullnam);
+}
+
+/**/
+int
+isrelative(char *s)
+{
+    if (*s != '/')
+	return 1;
+    for (; *s; s++)
+	if (*s == '.' && s[-1] == '/' &&
+	    (s[1] == '/' || s[1] == '\0' ||
+	     (s[1] == '.' && (s[2] == '/' || s[2] == '\0'))))
+	    return 1;
+    return 0;
+}
+
+/**/
+Cmdnam
+hashcmd(char *arg0, char **pp)
+{
+    Cmdnam cn;
+    char *s, buf[PATH_MAX];
+    char **pq;
+
+    for (; *pp; pp++)
+	if (**pp == '/') {
+	    s = buf;
+	    strucpy(&s, *pp);
+	    *s++ = '/';
+	    if ((s - buf) + strlen(arg0) >= PATH_MAX)
+		continue;
+	    strcpy(s, arg0);
+	    if (iscom(buf))
+		break;
+	}
+
+    if (!*pp)
+	return NULL;
+
+    cn = (Cmdnam) zcalloc(sizeof *cn);
+    cn->flags = 0;
+    cn->u.name = pp;
+    cmdnamtab->addnode(cmdnamtab, ztrdup(arg0), cn);
+
+    if (isset(HASHDIRS)) {
+	for (pq = pathchecked; pq <= pp; pq++)
+	    hashdir(pq);
+	pathchecked = pp + 1;
+    }
+
+    return cn;
+}
+
+/* execute a string */
+
+/**/
+void
+execstring(char *s, int dont_change_job, int exiting)
+{
+    List list;
+
+    pushheap();
+    if ((list = parse_string(s)))
+	execlist(list, dont_change_job, exiting);
+    popheap();
+}
+
+/* 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 *
+ * last command directly instead of forking.  If dont_change_job is  *
+ * nonzero, then restore the current job number after executing the  *
+ * list.                                                             */
+
+/**/
+void
+execlist(List list, int dont_change_job, int exiting)
+{
+    Sublist slist;
+    static int donetrap;
+    int ret, cj;
+    int old_pline_level, old_list_pipe;
+
+    cj = thisjob;
+    old_pline_level = pline_level;
+    old_list_pipe = list_pipe;
+
+    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) {
+	/* 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:
+		/* End of sublist; just execute, ignoring status. */
+		execpline(slist, list->type, !list->right && exiting);
+		goto sublist_done;
+		break;
+	    case ANDNEXT:
+		/* 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) {
+			/* 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;
+		    }
+		}
+		break;
+	    case ORNEXT:
+		/* 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) {
+			/* 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;
+		     }
+		}
+		break;
+	    }
+	    slist = slist->right;
+	}
+sublist_done:
+
+	if (sigtrapped[SIGDEBUG])
+	    dotrap(SIGDEBUG);
+
+	/* Check whether we are suppressing traps/errexit *
+	 * (typically in init scripts) and if we haven't  *
+	 * already performed them for this sublist.       */
+	if (!noerrexit && !donetrap) {
+	    if (sigtrapped[SIGZERR] && lastval) {
+		dotrap(SIGZERR);
+		donetrap = 1;
+	    }
+	    if (lastval && isset(ERREXIT)) {
+		if (sigtrapped[SIGEXIT])
+		    dotrap(SIGEXIT);
+		if (mypid != getpid())
+		    _exit(lastval);
+		else
+		    exit(lastval);
+	    }
+	}
+
+	list = list->right;
+    }
+
+    pline_level = old_pline_level;
+    list_pipe = old_list_pipe;
+    if (dont_change_job)
+	thisjob = cj;
+}
+
+/* Execute a pipeline.                                                *
+ * last1 is a flag that this command is the last command in a shell   *
+ * that is about to exit, so we can exec instead of forking.  It gets *
+ * passed all the way down to execcmd() which actually makes the      *
+ * decision.  A 0 is always passed if the command is not the last in  *
+ * the pipeline.  This function assumes that the sublist is not NULL. *
+ * If last1 is zero but the command is at the end of a pipeline, we   *
+ * pass 2 down to execcmd().                                          *
+ */
+
+/**/
+static int
+execpline(Sublist l, int how, int last1)
+{
+    int ipipe[2], opipe[2];
+    int pj, newjob;
+    int old_simple_pline = simple_pline;
+    static int lastwj;
+
+    if (!l->left)
+	return lastval = (l->flags & PFLAG_NOT) != 0;
+
+    pj = thisjob;
+    ipipe[0] = ipipe[1] = opipe[0] = opipe[1] = 0;
+    child_block();
+
+    /* 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) {
+	how = Z_ASYNC;
+	if (coprocin >= 0) {
+	    zclose(coprocin);
+	    zclose(coprocout);
+	}
+	mpipe(ipipe);
+	mpipe(opipe);
+	coprocin = ipipe[0];
+	coprocout = opipe[1];
+	fdtable[coprocin] = fdtable[coprocout] = 0;
+    }
+    if (!pline_level++) {
+	list_pipe_job = newjob;
+	nowait = 0;
+    }
+    list_pipe_pid = lastwj = 0;
+    if (pline_level == 1)
+	simple_pline = (l->left->type == END);
+    execpline2(l->left, how, opipe[0], ipipe[1], last1);
+    pline_level--;
+    if (how & Z_ASYNC) {
+	lastwj = newjob;
+	jobtab[thisjob].stat |= STAT_NOSTTY;
+	if (l->flags & PFLAG_COPROC)
+	    zclose(ipipe[1]);
+	if (how & Z_DISOWN) {
+	    deletejob(jobtab + thisjob);
+	    thisjob = -1;
+	}
+	else
+	    spawnjob();
+	child_unblock();
+	return 0;
+    } else {
+	if (newjob != lastwj) {
+	    Job jn = jobtab + newjob;
+
+	    if (newjob == list_pipe_job && list_pipe_child)
+		_exit(0);
+
+	    lastwj = thisjob = newjob;
+
+	    if (list_pipe)
+		jn->stat |= STAT_NOPRINT;
+
+	    if (nowait) {
+		if(!pline_level) {
+		    struct process *pn, *qn;
+
+		    curjob = newjob;
+		    addproc(list_pipe_pid, list_pipe_text);
+
+		    for (pn = jobtab[jn->other].procs; pn; pn = pn->next)
+			if (WIFSTOPPED(pn->status))
+			    break;
+
+		    if (pn) {
+			for (qn = jn->procs; qn->next; qn = qn->next);
+			qn->status = pn->status;
+		    }
+
+		    jn->stat &= ~(STAT_DONE | STAT_NOPRINT);
+		    jn->stat |= STAT_STOPPED | STAT_CHANGED;
+		    printjob(jn, !!isset(LONGLISTJOBS), 1);
+		}
+		else
+		    deletejob(jn);
+	    }
+
+	    for (; !nowait;) {
+		if (list_pipe_child) {
+		    jn->stat |= STAT_NOPRINT;
+		    makerunning(jn);
+		}
+		if (!(jn->stat & STAT_LOCKED))
+		    waitjobs();
+
+		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) {
+		    pid_t pid;
+		    int synch[2];
+
+		    pipe(synch);
+
+		    if ((pid = fork()) == -1) {
+			trashzle();
+			close(synch[0]);
+			close(synch[1]);
+			putc('\n', stderr);
+			fprintf(stderr, "zsh: job can't be suspended\n");
+			fflush(stderr);
+			makerunning(jn);
+			killjb(jn, SIGCONT);
+			thisjob = newjob;
+		    }
+		    else if (pid) {
+			char dummy;
+
+			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);
+			break;
+		    }
+		    else {
+			close(synch[0]);
+			entersubsh(Z_ASYNC, 0, 0);
+			setpgrp(0L, mypgrp = jobtab[list_pipe_job].gleader);
+			close(synch[1]);
+			kill(getpid(), SIGSTOP);
+			list_pipe = 0;
+			list_pipe_child = 1;
+			break;
+		    }
+		}
+		else if (subsh && jn->stat & STAT_STOPPED)
+		    thisjob = newjob;
+		else
+		    break;
+	    }
+	    child_unblock();
+
+	    if (list_pipe && (lastval & 0200) && pj >= 0 &&
+		(!(jn->stat & STAT_INUSE) || (jn->stat & STAT_DONE))) {
+		jn = jobtab + pj;
+		jn->stat |= STAT_NOPRINT;
+		killjb(jobtab + pj, lastval & ~0200);
+	    }
+	    if (list_pipe_child || (list_pipe && (jn->stat & STAT_DONE)))
+		deletejob(jn);
+	    thisjob = pj;
+
+	}
+	if (l->flags & PFLAG_NOT)
+	    lastval = !lastval;
+    }
+    if (!pline_level)
+	simple_pline = old_simple_pline;
+    return lastval;
+}
+
+static int subsh_close = -1;
+
+/* execute pipeline.  This function assumes the `pline' is not NULL. */
+
+/**/
+static void
+execpline2(Pline pline, 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 (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 {
+	int old_list_pipe = list_pipe;
+
+	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)) {
+	    int synch[2];
+
+	    pipe(synch);
+	    if ((pid = fork()) == -1) {
+		close(synch[0]);
+		close(synch[1]);
+		zerr("fork failed: %e", NULL, errno);
+	    } else if (pid) {
+		char dummy, *text;
+
+		text = getjobtext((void *) pline->left);
+		addproc(pid, text);
+		close(synch[1]);
+		read(synch[0], &dummy, 1);
+		close(synch[0]);
+	    } else {
+		zclose(pipes[0]);
+		close(synch[0]);
+		entersubsh(how, 2, 0);
+		close(synch[1]);
+		execcmd(pline->left, input, pipes[1], how, 0);
+		_exit(lastval);
+	    }
+	} else {
+	/* otherwise just do the pipeline normally. */
+	    subsh_close = pipes[0];
+	    execcmd(pline->left, 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;
+	}
+    }
+
+    lineno = oldlineno;
+}
+
+/* make the argv array */
+
+/**/
+static char **
+makecline(LinkList list)
+{
+    LinkNode node;
+    char **argv, **ptr;
+
+    /* A bigger argv is necessary for executing scripts */
+    ptr  =
+    argv = 2 + (char **) ncalloc((countlinknodes(list) + 4) * sizeof(char *));
+    if (isset(XTRACE)) {
+	if (!doneps4)
+	    fprintf(stderr, "%s", (prompt4) ? prompt4 : "");
+
+	for (node = firstnode(list); node; incnode(node)) {
+	    *ptr++ = (char *)getdata(node);
+	    zputs(getdata(node), stderr);
+	    if (nextnode(node))
+		fputc(' ', stderr);
+	}
+	fputc('\n', stderr);
+	fflush(stderr);
+    } else {
+	for (node = firstnode(list); node; incnode(node))
+	    *ptr++ = (char *)getdata(node);
+    }
+    *ptr = NULL;
+    return (argv);
+}
+
+/**/
+void
+untokenize(char *s)
+{
+    for (; *s; s++)
+	if (itok(*s))
+	    if (*s == Nularg)
+		chuck(s--);
+	    else
+		*s = ztokens[*s - Pound];
+}
+
+/* Open a file for writing redicection */
+
+/**/
+static int
+clobber_open(struct redir *f)
+{
+    struct stat buf;
+    int fd, oerrno;
+
+    /* If clobbering, just open. */
+    if (isset(CLOBBER) || IS_CLOBBER_REDIR(f->type))
+	return open(unmeta(f->name),
+		O_WRONLY | O_CREAT | O_TRUNC | O_NOCTTY, 0666);
+
+    /* If not clobbering, attempt to create file exclusively. */
+    if ((fd = open(unmeta(f->name),
+		   O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY, 0666)) >= 0)
+	return fd;
+
+    /* If that fails, we are still allowed to open non-regular files. *
+     * Try opening, and if it's a regular file then close it again    *
+     * because we weren't supposed to open it.                        */
+    oerrno = errno;
+    if ((fd = open(unmeta(f->name), O_WRONLY | O_NOCTTY)) != -1) {
+	if(!fstat(fd, &buf) && !S_ISREG(buf.st_mode))
+	    return fd;
+	close(fd);
+    }
+    errno = oerrno;
+    return -1;
+}
+
+/* size of buffer for tee and cat processes */
+#define TCBUFSIZE 4092
+
+/* close an multio (success) */
+
+/**/
+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)
+	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)
+	    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);
+}
+
+/* close all the mnodes (failure) */
+
+/**/
+static void
+closemnodes(struct multio **mfds)
+{
+    int i, j;
+
+    for (i = 0; i < 10; i++)
+	if (mfds[i]) {
+	    for (j = 0; j < mfds[i]->ct; j++)
+		zclose(mfds[i]->fds[j]);
+	    mfds[i] = NULL;
+	}
+}
+
+/**/
+static void
+closeallelse(struct multio *mn)
+{
+    int i, j;
+
+    for (i = 0; i < OPEN_MAX; i++)
+	if (mn->pipe != i) {
+	    for (j = 0; j < mn->ct; j++)
+		if (mn->fds[j] == i)
+		    break;
+	    if (j == mn->ct)
+		zclose(i);
+	}
+}
+
+/* A multio is a list of fds associated with a certain fd.       *
+ * Thus if you do "foo >bar >ble", the multio for fd 1 will have *
+ * two fds, the result of open("bar",...), and the result of     *
+ * open("ble",....).                                             */
+
+/* Add a fd to an multio.  fd1 must be < 10, and may be in any state. *
+ * fd2 must be open, and is `consumed' by this function.  Note that   *
+ * fd1 == fd2 is possible, and indicates that fd1 was really closed.  *
+ * We effectively do `fd2 = movefd(fd2)' at the beginning of this     *
+ * function, but in most cases we can avoid an extra dup by delaying  *
+ * the movefd: we only >need< to move it if we're actually doing a    *
+ * multiple redirection.                                              */
+
+/**/
+static void
+addfd(int forked, int *save, struct multio **mfds, int fd1, int fd2, int rflag)
+{
+    int pipes[2];
+
+    if (!mfds[fd1] || unset(MULTIOS)) {
+	if(!mfds[fd1]) {		/* starting a new multio */
+	    mfds[fd1] = (struct multio *) alloc(sizeof(struct multio));
+	    if (!forked && save[fd1] == -2)
+		save[fd1] = (fd1 == fd2) ? -1 : movefd(fd1);
+	}
+	redup(fd2, fd1);
+	mfds[fd1]->ct = 1;
+	mfds[fd1]->fds[0] = fd1;
+	mfds[fd1]->rflag = rflag;
+    } else {
+	if (mfds[fd1]->rflag != rflag) {
+	    zerr("file mode mismatch on fd %d", NULL, fd1);
+	    return;
+	}
+	if (mfds[fd1]->ct == 1) {	/* split the stream */
+	    mfds[fd1]->fds[0] = movefd(fd1);
+	    mfds[fd1]->fds[1] = movefd(fd2);
+	    mpipe(pipes);
+	    mfds[fd1]->pipe = pipes[1 - rflag];
+	    redup(pipes[rflag], fd1);
+	    mfds[fd1]->ct = 2;
+	} else {		/* add another fd to an already split stream */
+	    if(!(mfds[fd1]->ct % MULTIOUNIT)) {
+		int new = sizeof(struct multio) + sizeof(int) * mfds[fd1]->ct;
+		int old = new - sizeof(int) * MULTIOUNIT;
+		mfds[fd1] = hrealloc((char *)mfds[fd1], old, new);
+	    }
+	    mfds[fd1]->fds[mfds[fd1]->ct++] = movefd(fd2);
+	}
+    }
+    if (subsh_close >= 0 && !fdtable[subsh_close])
+	subsh_close = -1;
+}
+
+/**/
+static void
+addvars(LinkList l, int export)
+{
+    Varasg v;
+    LinkList vl;
+    int xtr;
+    char **arr, **ptr;
+
+    xtr = isset(XTRACE);
+    if (xtr && nonempty(l)) {
+	fprintf(stderr, "%s", prompt4 ? prompt4 : "");
+	doneps4 = 1;
+    }
+
+    while (nonempty(l)) {
+	v = (Varasg) ugetnode(l);
+	singsub(&v->name);
+	if (errflag)
+	    return;
+	untokenize(v->name);
+	if (xtr)
+	    fprintf(stderr, "%s=", v->name);
+	if (v->type == PM_SCALAR) {
+	    vl = newlinklist();
+	    addlinknode(vl, v->str);
+	} 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)))) {
+	    Param pm;
+	    char *val;
+	    int allexp;
+
+	    if (empty(vl))
+		val = ztrdup("");
+	    else {
+		untokenize(peekfirst(vl));
+		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;
+		    }
+		}
+		allexp = opts[ALLEXPORT];
+		opts[ALLEXPORT] = 1;
+		pm = setsparam(v->name, val);
+		opts[ALLEXPORT] = allexp;
+	    } else
+		pm = setsparam(v->name, val);
+	    if (errflag)
+		return;
+	    continue;
+	}
+	ptr = arr = (char **) zalloc(sizeof(char **) * (countlinknodes(vl) + 1));
+
+	while (nonempty(vl))
+	    *ptr++ = ztrdup((char *) ugetnode(vl));
+
+	*ptr = NULL;
+	if (xtr) {
+	    fprintf(stderr, "( ");
+	    for (ptr = arr; *ptr; ptr++)
+		fprintf(stderr, "%s ", *ptr);
+	    fprintf(stderr, ") ");
+	}
+	setaparam(v->name, arr);
+	if (errflag)
+	    return;
+    }
+}
+
+/**/
+static void
+execcmd(Cmd cmd, int input, int output, int how, int last1)
+{
+    HashNode hn = NULL;
+    LinkNode node;
+    Redir fn;
+    struct multio *mfds[10];
+    char *text;
+    int save[10];
+    int fil, dfil, is_cursh, type, i;
+    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;
+
+    doneps4 = 0;
+    args = cmd->args;
+    type = cmd->type;
+
+    for (i = 0; i < 10; i++) {
+	save[i] = -2;
+	mfds[i] = NULL;
+    }
+
+    /* If the command begins with `%', then assume it is a *
+     * reference to a job in the job table.                */
+    if (type == SIMPLE && nonempty(args) &&
+	*(char *)peekfirst(args) == '%') {
+	pushnode(args, dupstring((how & Z_DISOWN)
+				 ? "disown" : (how & Z_ASYNC) ? "bg" : "fg"));
+	how = Z_SYNC;
+    }
+
+    /* If AUTORESUME is set, the command is SIMPLE, and doesn't have *
+     * 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 &&
+	!nextnode(firstnode(args))) {
+	if (unset(NOTIFY))
+	    scanjobs();
+	if (findjobnam(peekfirst(args)) != -1)
+	    pushnode(args, dupstring("fg"));
+    }
+
+    /* Check if it's a builtin needing automatic MAGIC_EQUALS_SUBST      *
+     * handling.  Things like typeset need this.  We can't detect the    *
+     * 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)) {
+	    char *cmdarg = (char *) peekfirst(args);
+	    checked = !has_token(cmdarg);
+	    if (!checked)
+		break;
+	    if (!(cflags & (BINF_BUILTIN | BINF_COMMAND)) &&
+		(hn = shfunctab->getnode(shfunctab, cmdarg))) {
+		is_shfunc = 1;
+		break;
+	    }
+	    if (!(hn = builtintab->getnode(builtintab, cmdarg))) {
+		checked = !(cflags & BINF_BUILTIN);
+		break;
+	    }
+	    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;
+	    }
+	    cflags &= ~BINF_BUILTIN & ~BINF_COMMAND;
+	    cflags |= hn->flags;
+	    uremnode(args, firstnode(args));
+	    hn = NULL;
+	    checked = 0;
+	    if ((cflags & BINF_COMMAND) && unset(POSIXBUILTINS))
+		break;
+	}
+    }
+
+    /* Do prefork substitutions */
+    prefork(args, assign ? 2 : isset(MAGICEQUALSUBST));
+
+    if (type == SIMPLE) {
+	int unglobbed = 0;
+
+	for (;;) {
+	    char *cmdarg;
+
+	    if (!(cflags & BINF_NOGLOB))
+		while (!checked && !errflag && nonempty(args) &&
+		       has_token((char *) peekfirst(args)))
+		    glob(args, firstnode(args));
+	    else if (!unglobbed) {
+		for (node = firstnode(args); node; incnode(node))
+		    untokenize((char *) getdata(node));
+		unglobbed = 1;
+	    }
+
+	    /* 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;
+
+	    /* Empty command */
+	    if (empty(args)) {
+		if (nonempty(cmd->redir)) {
+		    if (cmd->flags & CFLAG_EXEC) {
+			/* Was this "exec < foobar"? */
+			nullexec = 1;
+			break;
+		    } else if (!nullcmd || !*nullcmd ||
+			       (cflags & BINF_PREFIX)) {
+			zerr("redirection with no command", NULL, 0);
+			errflag = lastval = 1;
+			return;
+		    } else if (readnullcmd && *readnullcmd &&
+			       ((Redir) peekfirst(cmd->redir))->type == READ &&
+			       !nextnode(firstnode(cmd->redir))) {
+			addlinknode(args, dupstring(readnullcmd));
+		    } else
+			addlinknode(args, dupstring(nullcmd));
+		} else if ((cflags & BINF_PREFIX) && (cflags & BINF_COMMAND)) {
+		    lastval = 0;
+		    return;
+		} else {
+		    cmdoutval = 0;
+		    addvars(cmd->vars, 0);
+		    if (errflag)
+			lastval = errflag;
+		    else
+			lastval = cmdoutval;
+		    if (isset(XTRACE)) {
+			fputc('\n', stderr);
+			fflush(stderr);
+		    }
+		    return;
+		}
+	    } else if (isset(RESTRICTED) && (cflags & BINF_EXEC) &&
+		       (cmd->flags & CFLAG_EXEC)) {
+		zerrnam("exec", "%s: restricted", (char *) getdata(firstnode(args)), 0);
+		lastval = 1;
+		return;
+	    }
+
+	    if (errflag || checked ||
+		(unset(POSIXBUILTINS) && (cflags & BINF_COMMAND)))
+		break;
+
+	    cmdarg = (char *) peekfirst(args);
+	    if (!(cflags & (BINF_BUILTIN | BINF_COMMAND)) &&
+		(hn = shfunctab->getnode(shfunctab, cmdarg))) {
+		is_shfunc = 1;
+		break;
+	    }
+	    if (!(hn = builtintab->getnode(builtintab, cmdarg))) {
+		if (cflags & BINF_BUILTIN) {
+		    zerr("no such builtin: %s", cmdarg, 0);
+		    errflag = lastval = 1;
+		    return;
+		}
+		break;
+	    }
+	    if (!(hn->flags & BINF_PREFIX)) {
+		is_builtin = 1;
+#ifdef DYNAMIC
+		/* autoload the builtin if necessary */
+		if (!((Builtin) hn)->handlerfunc)
+		    load_module(((Builtin) hn)->optstr);
+#endif
+		break;
+	    }
+	    cflags &= ~BINF_BUILTIN & ~BINF_COMMAND;
+	    cflags |= hn->flags;
+	    uremnode(args, firstnode(args));
+	    hn = NULL;
+	}
+    }
+
+    if (errflag) {
+	lastval = 1;
+	return;
+    }
+
+    /* Get the text associated with this command. */
+    if (jobbing || (how & Z_TIMED))
+	text = getjobtext((void *) cmd);
+    else
+	text = NULL;
+
+    /* Set up special parameter $_ */
+    zsfree(underscore);
+    if (nonempty(args)
+	&& (underscore = ztrdup((char *) getdata(lastnode(args)))))
+	untokenize(underscore); 
+    else
+  	underscore = ztrdup("");
+
+    /* Warn about "rm *" */
+    if (type == SIMPLE && interact && unset(RMSTARSILENT)
+	&& isset(SHINSTDIN) && nonempty(args) && nextnode(firstnode(args))
+	&& !strcmp(peekfirst(args), "rm")) {
+	LinkNode node, next;
+
+	for (node = nextnode(firstnode(args)); node && !errflag; node = next) {
+	    char *s = (char *) getdata(node);
+	    int l = strlen(s);
+
+	    next = nextnode(node);
+	    if (s[0] == Star && !s[1]) {
+		if (!checkrmall(pwd))
+		    uremnode(args, node);
+	    } else if (l > 2 && s[l - 2] == '/' && s[l - 1] == Star) {
+		char t = s[l - 2];
+
+		s[l - 2] = 0;
+		if (!checkrmall(s))
+		    uremnode(args, node);
+		s[l - 2] = t;
+	    }
+	}
+	if (!nextnode(firstnode(args)))
+	    errflag = 1;
+    }
+
+    if (errflag) {
+	lastval = 1;
+	return;
+    }
+
+    if (type == SIMPLE && !nullexec) {
+	char *s;
+	char trycd = (isset(AUTOCD) && isset(SHINSTDIN)
+		      && empty(cmd->redir) && !empty(args)
+		      && !nextnode(firstnode(args))
+		      && *(char *)peekfirst(args));
+
+	DPUTS(empty(args), "BUG: empty(args) in exec.c");
+	if (!hn) {
+	    /* Resolve external commands */
+	    char *cmdarg = (char *) peekfirst(args);
+
+	    hn = cmdnamtab->getnode(cmdnamtab, cmdarg);
+	    if (hn && trycd && !isreallycom((Cmdnam)hn)) {
+		cmdnamtab->removenode(cmdnamtab, cmdarg);
+		cmdnamtab->freenode(hn);
+		hn = NULL;
+	    }
+	    if (!hn && isset(HASHCMDS) && strcmp(cmdarg, "..")) {
+		for (s = cmdarg; *s && *s != '/'; s++);
+		if (!*s)
+		    hn = (HashNode) hashcmd(cmdarg, pathchecked);
+	    }
+	}
+
+	/* If no command found yet, see if it  *
+	 * is a directory we should AUTOCD to. */
+	if (!hn && trycd && (s = cancd(peekfirst(args)))) {
+	    peekfirst(args) = (void *) s;
+	    pushnode(args, dupstring("cd"));
+	    if ((hn = builtintab->getnode(builtintab, "cd")))
+		is_builtin = 1;
+	}
+    }
+
+    /* This is nonzero if the command is a current shell procedure? */
+    is_cursh = (is_builtin || is_shfunc || (type >= CURSH) || nullexec);
+
+    /**************************************************************************
+     * Do we need to fork?  We need to fork if:                               *
+     * 1) The command is supposed to run in the background. (or)              *
+     * 2) There is no `exec' flag, and either:                                *
+     *    a) This is a builtin or shell function with output piped somewhere. *
+     *    b) This is an external command and we can't do a `fake exec'.       *
+     *                                                                        *
+     * A `fake exec' is possible if we have all the following conditions:     *
+     * 1) last1 flag is 1.  This indicates that the current shell will not    *
+     *    be needed after the current command.  This is typically the case    *
+     *    when when the command is the last stage in a subshell, or is the    *
+     *    last command after the option `-c'.                                 *
+     * 2) We are not trapping EXIT or ZERR.                                   *
+     * 3) We don't have any files to delete.                                  *
+     *                                                                        *
+     * The condition above for a `fake exec' will also work for a current     *
+     * shell command such as a builtin, but doesn't really buy us anything    *
+     * (doesn't save us a process), since it is already running in the        *
+     * current shell.                                                         *
+     **************************************************************************/
+
+    if ((how & Z_ASYNC) || (!(cmd->flags & CFLAG_EXEC) &&
+       (((is_builtin || is_shfunc) && output) ||
+       (!is_cursh && (last1 != 1 || sigtrapped[SIGZERR] ||
+        sigtrapped[SIGEXIT] || havefiles()))))) {
+
+	pid_t pid;
+	int synch[2];
+	char dummy;
+
+	child_block();
+	pipe(synch);
+
+	if ((pid = zfork()) == -1) {
+	    close(synch[0]);
+	    close(synch[1]);
+	    return;
+	} if (pid) {
+	    close(synch[1]);
+	    read(synch[0], &dummy, 1);
+	    close(synch[0]);
+#ifdef PATH_DEV_FD
+	    closem(2);
+#endif
+	    if (how & Z_ASYNC) {
+		lastpid = (long) pid;
+	    } else if (!jobtab[thisjob].stty_in_env && nonempty(cmd->vars)) {
+		/* search for STTY=... */
+		while (nonempty(cmd->vars))
+		    if (!strcmp(((Varasg) ugetnode(cmd->vars))->name, "STTY")) {
+			jobtab[thisjob].stty_in_env = 1;
+			break;
+		    }
+	    }
+	    addproc(pid, text);
+	    return;
+	}
+	/* pid == 0 */
+	close(synch[0]);
+	entersubsh(how, type != SUBSH && !(how & Z_ASYNC) ? 2 : 1, 0);
+	close(synch[1]);
+	forked = 1;
+	if (sigtrapped[SIGINT] & ZSIG_IGNORED)
+	    holdintr();
+#ifdef HAVE_NICE
+	/* Check if we should run background jobs at a lower priority. */
+	if ((how & Z_ASYNC) && isset(BGNICE))
+	    nice(5);
+#endif /* HAVE_NICE */
+
+    } else if (is_cursh) {
+	/* This is a current shell procedure that didn't need to fork.    *
+	 * This includes current shell procedures that are being exec'ed, *
+	 * as well as null execs.                                         */
+	jobtab[thisjob].stat |= STAT_CURSH;
+    } else {
+	/* This is an exec (real or fake) for an external command.    *
+	 * Note that any form of exec means that the subshell is fake *
+	 * (but we may be in a subshell already).                     */
+	is_exec = 1;
+    }
+
+    if (!(cflags & BINF_NOGLOB))
+	globlist(args);
+    if (errflag) {
+	lastval = 1;
+	goto err;
+    }
+
+    /* Add pipeline input/output to mnodes */
+    if (input)
+	addfd(forked, save, mfds, 0, input, 0);
+    if (output)
+	addfd(forked, save, mfds, 1, output, 1);
+
+    /* Do process substitutions */
+    spawnpipes(cmd->redir);
+
+    /* Do io redirections */
+    while (nonempty(cmd->redir)) {
+	fn = (Redir) ugetnode(cmd->redir);
+	DPUTS(fn->type == HEREDOC || fn->type == HEREDOCDASH,
+	      "BUG: unexpanded here document");
+	if (fn->type == INPIPE) {
+	    if (fn->fd2 == -1) {
+		closemnodes(mfds);
+		fixfds(save);
+		execerr();
+	    }
+	    addfd(forked, save, mfds, fn->fd1, fn->fd2, 0);
+	} else if (fn->type == OUTPIPE) {
+	    if (fn->fd2 == -1) {
+		closemnodes(mfds);
+		fixfds(save);
+		execerr();
+	    }
+	    addfd(forked, save, mfds, fn->fd1, fn->fd2, 1);
+	} else {
+	    if (fn->type != HERESTR && xpandredir(fn, cmd->redir))
+		continue;
+	    if (errflag) {
+		closemnodes(mfds);
+		fixfds(save);
+		execerr();
+	    }
+	    if (isset(RESTRICTED) && IS_WRITE_FILE(fn->type)) {
+		zerr("writing redirection not allowed in restricted mode", NULL, 0);
+		execerr();
+	    }
+	    if (unset(EXECOPT))
+		continue;
+	    switch(fn->type) {
+	    case HERESTR:
+		fil = getherestr(fn);
+		if (fil == -1) {
+		    closemnodes(mfds);
+		    fixfds(save);
+		    if (errno != EINTR)
+			zerr("%e", NULL, errno);
+		    execerr();
+		}
+		addfd(forked, save, mfds, fn->fd1, fil, 0);
+		break;
+	    case READ:
+	    case READWRITE:
+		if (fn->type == READ)
+		    fil = open(unmeta(fn->name), O_RDONLY | O_NOCTTY);
+		else
+		    fil = open(unmeta(fn->name),
+			       O_RDWR | O_CREAT | O_NOCTTY, 0666);
+		if (fil == -1) {
+		    closemnodes(mfds);
+		    fixfds(save);
+		    if (errno != EINTR)
+			zerr("%e: %s", fn->name, errno);
+		    execerr();
+		}
+		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)
+		    init_io();
+		break;
+	    case CLOSE:
+		if (!forked && fn->fd1 < 10 && save[fn->fd1] == -2)
+		    save[fn->fd1] = movefd(fn->fd1);
+		closemn(mfds, fn->fd1);
+		zclose(fn->fd1);
+		break;
+	    case MERGEIN:
+	    case MERGEOUT:
+		if(fn->fd2 < 10)
+		    closemn(mfds, fn->fd2);
+		fil = dup(fn->fd2);
+		if (fil == -1) {
+		    char fdstr[4];
+
+		    closemnodes(mfds);
+		    fixfds(save);
+		    sprintf(fdstr, "%d", fn->fd2);
+		    zerr("%s: %e", fdstr, errno);
+		    execerr();
+		}
+		addfd(forked, save, mfds, fn->fd1, fil, fn->type == MERGEOUT);
+		break;
+	    default:
+		if (IS_APPEND_REDIR(fn->type))
+		    fil = open(unmeta(fn->name),
+			       (unset(CLOBBER) && !IS_CLOBBER_REDIR(fn->type)) ?
+			       O_WRONLY | O_APPEND | O_NOCTTY :
+			       O_WRONLY | O_APPEND | O_CREAT | O_NOCTTY, 0666);
+		else
+		    fil = clobber_open(fn);
+		if(fil != -1 && IS_ERROR_REDIR(fn->type))
+		    dfil = dup(fil);
+		else
+		    dfil = 0;
+		if (fil == -1 || dfil == -1) {
+		    if(fil != -1)
+			close(fil);
+		    closemnodes(mfds);
+		    fixfds(save);
+		    if (errno != EINTR)
+			zerr("%e: %s", fn->name, errno);
+		    execerr();
+		}
+		addfd(forked, save, mfds, fn->fd1, fil, 1);
+		if(IS_ERROR_REDIR(fn->type))
+		    addfd(forked, save, mfds, 2, dfil, 1);
+		break;
+	    }
+	}
+    }
+
+    /* We are done with redirection.  close the mnodes, *
+     * spawning tee/cat processes as necessary.         */
+    for (i = 0; i < 10; i++)
+	closemn(mfds, i);
+
+    if (nullexec) {
+	for (i = 0; i < 10; i++)
+	    if (save[i] != -2)
+		zclose(save[i]);
+	/*
+	 * Here we specifically *don't* restore the original fd's
+	 * before returning.
+	 */
+	return;
+    }
+
+    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
+	    };
+
+	    if (last1 == 1)
+		cmd->flags |= CFLAG_EXEC;
+	    lastval = (func[type - CURSH]) (cmd);
+	} else if (is_builtin || is_shfunc) {
+	    LinkList restorelist = 0, removelist = 0;
+	    /* builtin or shell function */
+
+	    if (!forked && ((cflags & BINF_COMMAND) ||
+			    (unset(POSIXBUILTINS) && !assign) ||
+			    (isset(POSIXBUILTINS) && !is_shfunc &&
+			     !(hn->flags & BINF_PSPECIAL))))
+		save_params(cmd, &restorelist, &removelist);
+
+	    if (cmd->vars) {
+		/* Export this if the command is a shell function,
+		 * but not if it's a builtin.
+		 */
+		addvars(cmd->vars, is_shfunc);
+		if (errflag) {
+		    restore_params(restorelist, removelist);
+		    lastval = 1;
+		    fixfds(save);
+		    return;
+		}
+	    }
+
+	    if (is_shfunc) {
+		/* It's a shell function */
+#ifdef PATH_DEV_FD
+		int i;
+
+		for (i = 10; i <= max_zsh_fd; i++)
+		    if (fdtable[i] > 1)
+			fdtable[i]++;
+#endif
+		if (subsh_close >= 0)
+		    zclose(subsh_close);
+		subsh_close = -1;
+		execshfunc(cmd, (Shfunc) hn);
+#ifdef PATH_DEV_FD
+		for (i = 10; i <= max_zsh_fd; i++)
+		    if (fdtable[i] > 1)
+			if (--(fdtable[i]) <= 2)
+			    zclose(i);
+#endif
+	    } else {
+		/* It's a builtin */
+		if (forked)
+		    closem(1);
+		lastval = execbuiltin(args, (Builtin) hn);
+#ifdef PATH_DEV_FD
+		closem(2);
+#endif
+		if (isset(PRINTEXITVALUE) && isset(SHINSTDIN) && lastval && !subsh) {
+		    fprintf(stderr, "zsh: exit %ld\n", (long)lastval);
+		}
+		fflush(stdout);
+		if (save[1] == -2) {
+		    if (ferror(stdout)) {
+			zerr("write error: %e", NULL, errno);
+			clearerr(stdout);
+			errflag = 0;
+		    }
+		} else
+		    clearerr(stdout);
+	    }
+
+	    if (cmd->flags & CFLAG_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);
+		exit(lastval);
+	    }
+
+	    restore_params(restorelist, removelist);
+
+	} else {
+	    if (cmd->flags & CFLAG_EXEC) {
+		setiparam("SHLVL", --shlvl);
+		/* 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);
+	    }
+	    if (type == SIMPLE) {
+		if (cmd->vars) {
+		    addvars(cmd->vars, -1);
+		    if (errflag)
+			_exit(1);
+		}
+		closem(1);
+		if (coprocin)
+		    zclose(coprocin);
+		if (coprocout)
+		    zclose(coprocout);
+#ifdef HAVE_GETRLIMIT
+		if (!forked)
+		    setlimits(NULL);
+#endif
+		execute((Cmdnam) hn, cflags & BINF_DASH);
+	    } else {		/* ( ... ) */
+		DPUTS(cmd->vars && nonempty(cmd->vars),
+		      "BUG: assigment before complex command");
+		list_pipe = 0;
+		if (subsh_close >= 0)
+		    zclose(subsh_close);
+                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);
+	    }
+	}
+    }
+
+  err:
+    if (forked)
+	_exit(lastval);
+    fixfds(save);
+}
+
+/* Arrange to have variables restored. */
+
+/**/
+static void
+save_params(Cmd cmd, LinkList *restore_p, LinkList *remove_p)
+{
+    Param pm;
+    LinkNode node;
+    char *s;
+
+    MUSTUSEHEAP("save_params()");
+
+    *restore_p = newlinklist();
+    *remove_p = newlinklist();
+
+    for (node = firstnode(cmd->vars); node; incnode(node)) {
+	s = ((Varasg) getdata(node))->name;
+	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);
+
+		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;
+		}
+		pm = tpm;
+	    }
+	    addlinknode(*remove_p, s);
+	    addlinknode(*restore_p, pm);
+	} else {
+	    addlinknode(*remove_p, s);
+	}
+    }
+}
+
+/* Restore saved parameters after executing a shfunc or builtin */
+
+/**/
+static void
+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);
+	    }
+	}
+    }
+
+    if (restorelist) {
+	/* restore saved parameters */
+	while ((pm = (Param) ugetnode(restorelist))) {
+	    if (pm->flags & PM_SPECIAL) {
+		Param tpm = (Param) paramtab->getnode(paramtab, pm->nam);
+
+		DPUTS(!tpm || PM_TYPE(pm->flags) != PM_TYPE(tpm->flags) ||
+		      !(pm->flags & PM_SPECIAL),
+		      "BUG: in restoring special parameters");
+		tpm->flags = pm->flags;
+		switch (PM_TYPE(pm->flags)) {
+		case PM_SCALAR:
+		    tpm->sets.cfn(tpm, pm->u.str);
+		    break;
+		case PM_INTEGER:
+		    tpm->sets.ifn(tpm, pm->u.val);
+		    break;
+		case PM_ARRAY:
+		    tpm->sets.afn(tpm, pm->u.arr);
+		    break;
+		}
+	    } else
+		paramtab->addnode(paramtab, pm->nam, pm);
+	    if (pm->flags & PM_EXPORTED)
+		pm->env = addenv(pm->nam, getsparam(pm->nam));
+	}
+    }
+}
+
+/* restore fds after redirecting a builtin */
+
+/**/
+static void
+fixfds(int *save)
+{
+    int old_errno = errno;
+    int i;
+
+    for (i = 0; i != 10; i++)
+	if (save[i] != -2)
+	    redup(save[i], i);
+    errno = old_errno;
+}
+
+/**/
+static void
+entersubsh(int how, int cl, int fake)
+{
+    int sig;
+
+    if (cl != 2)
+	for (sig = 0; sig < VSIGCOUNT; sig++)
+	    if (!(sigtrapped[sig] & ZSIG_FUNC))
+		unsettrap(sig);
+    if (unset(MONITOR)) {
+	if (how & Z_ASYNC) {
+	    settrap(SIGINT, NULL);
+	    settrap(SIGQUIT, NULL);
+	    if (isatty(0)) {
+		close(0);
+		if (open("/dev/null", O_RDWR | O_NOCTTY)) {
+		    zerr("can't open /dev/null: %e", NULL, errno);
+		    _exit(1);
+		}
+	    }
+	}
+    } 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 ||
+		setpgrp(0L, jobtab[list_pipe_job].gleader) == -1) {
+		jobtab[list_pipe_job].gleader =
+		    jobtab[thisjob].gleader = mypgrp;
+		setpgrp(0L, mypgrp);
+
+		if (how & Z_SYNC)
+		    attachtty(jobtab[thisjob].gleader);
+	    }
+	}
+	else if (!jobtab[thisjob].gleader ||
+		 (setpgrp(0L, jobtab[thisjob].gleader) == -1)) {
+	    jobtab[thisjob].gleader = getpid();
+	    if (list_pipe_job != thisjob &&
+		!jobtab[list_pipe_job].gleader)
+		jobtab[list_pipe_job].gleader = jobtab[thisjob].gleader;
+	    setpgrp(0L, jobtab[thisjob].gleader);
+	    if (how & Z_SYNC)
+		attachtty(jobtab[thisjob].gleader);
+	}
+    }
+    if (!fake)
+	subsh = 1;
+    if (SHTTY != -1) {
+	zclose(SHTTY);
+	SHTTY = -1;
+    }
+    if (isset(MONITOR)) {
+	signal_default(SIGTTOU);
+	signal_default(SIGTTIN);
+	signal_default(SIGTSTP);
+    }
+    if (interact) {
+	signal_default(SIGTERM);
+	if (!(sigtrapped[SIGINT] & ZSIG_IGNORED))
+	    signal_default(SIGINT);
+    }
+    if (!(sigtrapped[SIGQUIT] & ZSIG_IGNORED))
+	signal_default(SIGQUIT);
+    opts[MONITOR] = opts[USEZLE] = 0;
+    zleactive = 0;
+    if (cl)
+	clearjobtab();
+    times(&shtms);
+}
+
+/* close internal shell fds */
+
+/**/
+void
+closem(int how)
+{
+    int i;
+
+    for (i = 10; i <= max_zsh_fd; i++)
+	if (fdtable[i] && (!how || fdtable[i] == how))
+	    zclose(i);
+}
+
+/* convert here document into a here string */
+
+/**/
+char *
+gethere(char *str, int typ)
+{
+    char *buf;
+    int bsiz, qt = 0, strip = 0;
+    char *s, *t, *bptr, c;
+
+    for (s = str; *s; s++)
+	if (INULL(*s)) {
+	    *s = Nularg;
+	    qt = 1;
+	}
+    untokenize(str);
+    if (typ == HEREDOCDASH) {
+	strip = 1;
+	while (*str == '\t')
+	    str++;
+    }
+    bptr = buf = zalloc(bsiz = 256);
+    for (;;) {
+	t = bptr;
+
+	while ((c = hgetc()) == '\t' && strip)
+	    ;
+	for (;;) {
+	    if (bptr == buf + bsiz) {
+		buf = realloc(buf, 2 * bsiz);
+		t = buf + bsiz - (bptr - t);
+		bptr = buf + bsiz;
+		bsiz *= 2;
+	    }
+	    if (lexstop || c == '\n')
+		break;
+	    *bptr++ = c;
+	    c = hgetc();
+	}
+	*bptr = '\0';
+	if (!strcmp(t, str))
+	    break;
+	if (lexstop) {
+	    t = bptr;
+	    break;
+	}
+	*bptr++ = '\n';
+    }
+    if (t > buf && t[-1] == '\n')
+	t--;
+    *t = '\0';
+    if (!qt)
+	parsestr(buf);
+    s = dupstring(buf);
+    zfree(buf, bsiz);
+    return s;
+}
+
+/* open here string fd */
+
+/**/
+static int
+getherestr(struct redir *fn)
+{
+    char *s, *t;
+    int fd, len;
+
+    t = fn->name;
+    singsub(&t);
+    untokenize(t);
+    unmetafy(t, &len);
+    t[len++] = '\n';
+    s = gettempname();
+    if (!s || (fd = open(s, O_CREAT|O_WRONLY|O_EXCL|O_NOCTTY, 0600)) == -1)
+	return -1;
+    write(fd, t, len);
+    close(fd);
+    fd = open(s, O_RDONLY | O_NOCTTY);
+    unlink(s);
+    return fd;
+}
+
+/* $(...) */
+
+/**/
+LinkList
+getoutput(char *cmd, int qt)
+{
+    List list;
+    int pipes[2];
+    pid_t pid;
+    Cmd c;
+    Redir r;
+
+    if (!(list = parse_string(cmd)))
+	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) {
+	/* $(< word) */
+	int stream;
+	char *s = r->name;
+
+	singsub(&s);
+	if (errflag)
+	    return NULL;
+	untokenize(s);
+	if ((stream = open(unmeta(s), O_RDONLY | O_NOCTTY)) == -1) {
+	    zerr("%e: %s", s, errno);
+	    return NULL;
+	}
+	return readoutput(stream, qt);
+    }
+
+    mpipe(pipes);
+    child_block();
+    cmdoutval = 0;
+    if ((cmdoutpid = pid = zfork()) == -1) {
+	/* fork error */
+	zclose(pipes[0]);
+	zclose(pipes[1]);
+	errflag = 1;
+	cmdoutpid = 0;
+	child_unblock();
+	return NULL;
+    } else if (pid) {
+	LinkList retval;
+
+	zclose(pipes[1]);
+	retval = readoutput(pipes[0], qt);
+	fdtable[pipes[0]] = 0;
+	child_suspend(0);		/* 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);
+    close(1);
+    _exit(lastval);
+    zerr("exit returned in child!!", NULL, 0);
+    kill(getpid(), SIGKILL);
+    return NULL;
+}
+
+/* read output of command substitution */
+
+/**/
+static LinkList
+readoutput(int in, int qt)
+{
+    LinkList ret;
+    char *buf, *ptr;
+    int bsiz, c, cnt = 0;
+    FILE *fin;
+
+    fin = fdopen(in, "r");
+    ret = newlinklist();
+    ptr = buf = (char *) ncalloc(bsiz = 64);
+    while ((c = fgetc(fin)) != EOF || errno == EINTR) {
+	if (c == EOF) {
+	    errno = 0;
+	    clearerr(fin);
+	    continue;
+	}
+	if (imeta(c)) {
+	    *ptr++ = Meta;
+	    c ^= 32;
+	    cnt++;
+	}
+	if (++cnt >= bsiz) {
+	    char *pp = (char *) ncalloc(bsiz *= 2);
+
+	    memcpy(pp, buf, cnt - 1);
+	    ptr = (buf = pp) + cnt - 1;
+	}
+	*ptr++ = c;
+    }
+    fclose(fin);
+    while (cnt && ptr[-1] == '\n')
+	ptr--, cnt--;
+    *ptr = '\0';
+    if (qt) {
+	if (!cnt) {
+	    *ptr++ = Nularg;
+	    *ptr = '\0';
+	}
+	addlinknode(ret, buf);
+    } else {
+	char **words = spacesplit(buf, 0);
+
+	while (*words) {
+	    if (isset(GLOBSUBST))
+		tokenize(*words);
+	    addlinknode(ret, *words++);
+	}
+    }
+    return ret;
+}
+
+/**/
+static List
+parsecmd(char *cmd)
+{
+    char *str;
+    List list;
+
+    for (str = cmd + 2; *str && *str != Outpar; str++);
+    if (!*str || cmd[1] != Inpar) {
+	zerr("oops.", NULL, 0);
+	return NULL;
+    }
+    *str = '\0';
+    if (str[1] || !(list = parse_string(cmd + 2))) {
+	zerr("parse error in process substitution", NULL, 0);
+	return NULL;
+    }
+    return list;
+}
+
+/* =(...) */
+
+/**/
+char *
+getoutputfile(char *cmd)
+{
+    pid_t pid;
+    char *nam;
+    List list;
+    int fd;
+
+    if (thisjob == -1)
+	return NULL;
+    if (!(list = 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;
+    child_block();
+    fd = open(nam, O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY, 0600);
+
+    if (fd < 0 || (cmdoutpid = pid = zfork()) == -1) {
+	/* fork or open error */
+	child_unblock();
+	return nam;
+    } else if (pid) {
+	int os;
+
+	close(fd);
+	os = jobtab[thisjob].stat;
+	waitforpid(pid);
+	cmdoutval = 0;
+	jobtab[thisjob].stat = os;
+	return nam;
+    }
+
+    /* pid == 0 */
+    redup(fd, 1);
+    opts[MONITOR] = 0;
+    entersubsh(Z_SYNC, 1, 0);
+    execlist(list, 0, 1);
+    close(1);
+    _exit(lastval);
+    zerr("exit returned in child!!", NULL, 0);
+    kill(getpid(), SIGKILL);
+    return NULL;
+}
+
+#if !defined(PATH_DEV_FD) && defined(HAVE_FIFOS)
+/* get a temporary named pipe */
+
+static char *
+namedpipe(void)
+{
+    char *tnam = gettempname();
+
+# ifdef HAVE_MKFIFO
+    if (mkfifo(tnam, 0600) < 0)
+# else
+    if (mknod(tnam, 0010600, 0) < 0)
+# endif
+	return NULL;
+    return tnam;
+}
+#endif /* ! PATH_DEV_FD && HAVE_FIFOS */
+
+/* <(...) or >(...) */
+
+/**/
+char *
+getproc(char *cmd)
+{
+#if !defined(HAVE_FIFOS) && !defined(PATH_DEV_FD)
+    zerr("doesn't look like your system supports FIFOs.", NULL, 0);
+    return NULL;
+#else
+    List list;
+    int out = *cmd == Inang;
+    char *pnam;
+#ifndef PATH_DEV_FD
+    int fd;
+#else
+    int pipes[2];
+#endif
+
+    if (thisjob == -1)
+	return NULL;
+#ifndef PATH_DEV_FD
+    if (!(pnam = namedpipe()))
+	return NULL;
+#else
+    pnam = ncalloc(strlen(PATH_DEV_FD) + 6);
+#endif
+    if (!(list = 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 (zfork()) {
+#else
+    mpipe(pipes);
+    if (zfork()) {
+	sprintf(pnam, "%s/%d", PATH_DEV_FD, pipes[!out]);
+	zclose(pipes[out]);
+	fdtable[pipes[!out]] = 2;
+#endif
+	return pnam;
+    }
+#ifndef PATH_DEV_FD
+    closem(0);
+    fd = open(pnam, out ? O_WRONLY | O_NOCTTY : O_RDONLY | O_NOCTTY);
+    if (fd == -1) {
+	zerr("can't open %s: %e", pnam, errno);
+	_exit(1);
+    }
+    entersubsh(Z_ASYNC, 1, 0);
+    redup(fd, out);
+#else
+    entersubsh(Z_ASYNC, 1, 0);
+    redup(pipes[out], out);
+    closem(0);   /* this closes pipes[!out] as well */
+#endif
+    execlist(list, 0, 1);
+    zclose(out);
+    _exit(lastval);
+    return NULL;
+#endif   /* HAVE_FIFOS and PATH_DEV_FD not defined */
+}
+
+/* > >(...) or < <(...) (does not use named pipes) */
+
+/**/
+static int
+getpipe(char *cmd)
+{
+    List list;
+    int pipes[2], out = *cmd == Inang;
+
+    if (!(list = parsecmd(cmd)))
+	return -1;
+    mpipe(pipes);
+    if (zfork()) {
+	zclose(pipes[out]);
+	return pipes[!out];
+    }
+    entersubsh(Z_ASYNC, 1, 0);
+    redup(pipes[out], out);
+    closem(0);	/* this closes pipes[!out] as well */
+    execlist(list, 0, 1);
+    _exit(lastval);
+    return 0;
+}
+
+/* open pipes with fds >= 10 */
+
+/**/
+static void
+mpipe(int *pp)
+{
+    pipe(pp);
+    pp[0] = movefd(pp[0]);
+    pp[1] = movefd(pp[1]);
+}
+
+/* Do process substitution with redirection */
+
+/**/
+static void
+spawnpipes(LinkList l)
+{
+    LinkNode n;
+    Redir f;
+    char *str;
+
+    n = firstnode(l);
+    for (; n; incnode(n)) {
+	f = (Redir) getdata(n);
+	if (f->type == OUTPIPE || f->type == INPIPE) {
+	    str = f->name;
+	    f->fd2 = getpipe(str);
+	}
+    }
+}
+
+/* evaluate a [[ ... ]] */
+
+/**/
+static int
+execcond(Cmd cmd)
+{
+    return !evalcond(cmd->u.cond);
+}
+
+/* evaluate a ((...)) arithmetic command */
+
+/**/
+static int
+execarith(Cmd cmd)
+{
+    char *e;
+    long val = 0;
+
+    while ((e = (char *) ugetnode(cmd->args)))
+	val = matheval(e);
+    errflag = 0;
+    return !val;
+}
+
+/* perform time ... command */
+
+/**/
+static int
+exectime(Cmd cmd)
+{
+    int jb;
+
+    jb = thisjob;
+    if (!cmd->u.pline) {
+	shelltime();
+	return 0;
+    }
+    execpline(cmd->u.pline, Z_TIMED|Z_SYNC, 0);
+    thisjob = jb;
+    return lastval;
+}
+
+/* Define a shell function */
+
+/**/
+static int
+execfuncdef(Cmd cmd)
+{
+    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;
+	    }
+	    shfunctab->addnode(shfunctab, ztrdup(s), shf);
+	}
+    } LASTALLOC;
+    if(isset(HISTNOFUNCTIONS))
+	remhist();
+    return 0;
+}
+
+/* Main entry point to execute a shell function. */
+
+/**/
+static void
+execshfunc(Cmd cmd, Shfunc shf)
+{
+    LinkList last_file_list = NULL;
+
+    if (errflag)
+	return;
+
+    if (!list_pipe) {
+	/* 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 (!list_pipe)
+	deletefilelist(last_file_list);
+}
+
+/* Function to execute the special type of command that represents an *
+ * autoloaded shell function.  The command structure tells us which   *
+ * function it is.  This function is actually called as part of the   *
+ * execution of the autoloaded function itself, so when the function  *
+ * has been autoloaded, its list is just run with no frills.          */
+
+/**/
+static int
+execautofn(Cmd cmd)
+{
+    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);
+	return 1;
+    }
+    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;
+	}
+    } else {
+	freestruct(shf->funcdef);
+	PERMALLOC {
+	    shf->funcdef = dupstruct(stripkshdef(l, shf->nam));
+	} LASTALLOC;
+	shf->flags &= ~PM_UNDEFINED;
+    }
+    HEAPALLOC {
+	execlist(dupstruct(shf->funcdef), 1, 0);
+    } LASTALLOC;
+    return lastval;
+}
+
+/* execute a shell function */
+
+/**/
+void
+doshfunc(List list, 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);
+	    }
+	}
+	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];
+	}
+
+	/*
+	 * 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 (newexitfn) {
+	    dotrapargs(SIGEXIT, &newexittr, newexitfn);
+	    freestruct(newexitfn);
+	}
+
+	if (trapreturn < -1)
+	    trapreturn++;
+	if (noreturnval)
+	    lastval = oldlastval;
+	popheap();
+    } LASTALLOC;
+}
+
+/* Search fpath for an undefined function.  Finds the file, and returns the *
+ * list of its contents.                                                    */
+
+/**/
+static List
+getfpfunc(char *s)
+{
+    char **pp, buf[PATH_MAX];
+    off_t len;
+    char *d;
+    List r;
+    int fd;
+
+    pp = fpath;
+    for (; *pp; pp++) {
+	if (strlen(*pp) + strlen(s) + 1 >= PATH_MAX)
+	    continue;
+	if (**pp)
+	    sprintf(buf, "%s/%s", *pp, s);
+	else
+	    strcpy(buf, s);
+	unmetafy(buf, NULL);
+	if (!access(buf, R_OK) && (fd = open(buf, O_RDONLY | O_NOCTTY)) != -1) {
+	    if ((len = lseek(fd, 0, 2)) != -1) {
+		lseek(fd, 0, 0);
+		d = (char *) zcalloc(len + 1);
+		if (read(fd, d, len) == len) {
+		    close(fd);
+		    d = metafy(d, len, META_REALLOC);
+		    HEAPALLOC {
+			r = parse_string(d);
+		    } LASTALLOC;
+		    zfree(d, len + 1);
+		    return r;
+		} else {
+		    zfree(d, len + 1);
+		    close(fd);
+		}
+	    } else {
+		close(fd);
+	    }
+	}
+    }
+    return &dummy_list;
+}
+
+/* Handle the most common type of ksh-style autoloading, when doing a      *
+ * zsh-style autoload.  Given the list read from an autoload file, and the *
+ * name of the function being defined, check to see if the file consists   *
+ * entirely of a single definition for that function.  If so, use the      *
+ * contents of that definition.  Otherwise, use the entire file.           */
+
+/**/
+static List
+stripkshdef(List l, char *name)
+{
+    Sublist s;
+    Pline p;
+    Cmd c;
+    if(!l)
+	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;
+}
+
+/* check to see if AUTOCD applies here */
+
+/**/
+static char *
+cancd(char *s)
+{
+    int nocdpath = s[0] == '.' &&
+    (s[1] == '/' || !s[1] || (s[1] == '.' && (s[2] == '/' || !s[1])));
+    char *t;
+
+    if (*s != '/') {
+	char sbuf[PATH_MAX], **cp;
+
+	if (cancd2(s))
+	    return s;
+	if (access(unmeta(s), X_OK) == 0)
+	    return NULL;
+	if (!nocdpath)
+	    for (cp = cdpath; *cp; cp++) {
+		if (strlen(*cp) + strlen(s) + 1 >= PATH_MAX)
+		    continue;
+		if (**cp)
+		    sprintf(sbuf, "%s/%s", *cp, s);
+		else
+		    strcpy(sbuf, s);
+		if (cancd2(sbuf)) {
+		    doprintdir = -1;
+		    return dupstring(sbuf);
+		}
+	    }
+	if ((t = cd_able_vars(s))) {
+	    if (cancd2(t)) {
+		doprintdir = -1;
+		return t;
+	    }
+	}
+	return NULL;
+    }
+    return cancd2(s) ? s : NULL;
+}
+
+/**/
+static int
+cancd2(char *s)
+{
+    struct stat buf;
+    char *us = unmeta(s);
+
+    return !(access(us, X_OK) || stat(us, &buf) || !S_ISDIR(buf.st_mode));
+}
+
+/**/
+void
+execsave(void)
+{
+    struct execstack *es;
+
+    es = (struct execstack *) malloc(sizeof(struct execstack));
+    es->args = args;
+    es->list_pipe_pid = list_pipe_pid;
+    es->nowait = nowait;
+    es->pline_level = pline_level;
+    es->list_pipe_child = list_pipe_child;
+    es->list_pipe_job = list_pipe_job;
+    strcpy(es->list_pipe_text, list_pipe_text);
+    es->lastval = lastval;
+    es->noeval = noeval;
+    es->badcshglob = badcshglob;
+    es->cmdoutpid = cmdoutpid;
+    es->cmdoutval = cmdoutval;
+    es->trapreturn = trapreturn;
+    es->noerrs = noerrs;
+    es->subsh_close = subsh_close;
+    es->underscore = underscore;
+    underscore = ztrdup(underscore);
+    es->next = exstack;
+    exstack = es;
+    noerrs = cmdoutpid = 0;
+}
+
+/**/
+void
+execrestore(void)
+{
+    struct execstack *en;
+
+    DPUTS(!exstack, "BUG: execrestore() without execsave()");
+    args = exstack->args;
+    list_pipe_pid = exstack->list_pipe_pid;
+    nowait = exstack->nowait;
+    pline_level = exstack->pline_level;
+    list_pipe_child = exstack->list_pipe_child;
+    list_pipe_job = exstack->list_pipe_job;
+    strcpy(list_pipe_text, exstack->list_pipe_text);
+    lastval = exstack->lastval;
+    noeval = exstack->noeval;
+    badcshglob = exstack->badcshglob;
+    cmdoutpid = exstack->cmdoutpid;
+    cmdoutval = exstack->cmdoutval;
+    trapreturn = exstack->trapreturn;
+    noerrs = exstack->noerrs;
+    subsh_close = exstack->subsh_close;
+    zsfree(underscore);
+    underscore = exstack->underscore;
+    en = exstack->next;
+    free(exstack);
+    exstack = en;
+}
diff --git a/Src/glob.c b/Src/glob.c
new file mode 100644
index 000000000..be7a04515
--- /dev/null
+++ b/Src/glob.c
@@ -0,0 +1,2800 @@
+/*
+ * glob.c - filename generation
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "glob.pro"
+
+/* flag for CSHNULLGLOB */
+ 
+/**/
+int badcshglob;
+ 
+static int mode;		/* != 0 if we are parsing glob patterns */
+static int pathpos;		/* position in pathbuf                  */
+static int matchsz;		/* size of matchbuf                     */
+static int matchct;		/* number of matches found              */
+static char *pathbuf;		/* pathname buffer                      */
+static int pathbufsz;		/* size of pathbuf			*/
+static int pathbufcwd;		/* where did we chdir()'ed		*/
+static char **matchbuf;		/* array of matches                     */
+static char **matchptr;		/* &matchbuf[matchct]                   */
+static char *colonmod;		/* colon modifiers in qualifier list    */
+
+typedef struct stat *Statptr;	 /* This makes the Ultrix compiler happy.  Go figure. */
+
+/* modifier for unit conversions */
+
+#define TT_DAYS 0
+#define TT_HOURS 1
+#define TT_MINS 2
+#define TT_WEEKS 3
+#define TT_MONTHS 4
+
+#define TT_BYTES 0
+#define TT_POSIX_BLOCKS 1
+#define TT_KILOBYTES 2
+#define TT_MEGABYTES 3
+
+typedef int (*TestMatchFunc) _((struct stat *, long));
+
+struct qual {
+    struct qual *next;		/* Next qualifier, must match                */
+    struct qual *or;		/* Alternative set of qualifiers to match    */
+    TestMatchFunc func;		/* Function to call to test match            */
+    long data;			/* Argument passed to function               */
+    int sense;			/* Whether asserting or negating             */
+    int amc;			/* Flag for which time to test (a, m, c)     */
+    int range;			/* Whether to test <, > or = (as per signum) */
+    int units;			/* Multiplier for time or size, respectively */
+};
+
+/* Qualifiers pertaining to current pattern */
+static struct qual *quals;
+
+/* Other state values for current pattern */
+static int qualct, qualorct;
+static int range, amc, units;
+static int gf_nullglob, gf_markdirs, gf_noglobdots, gf_listtypes, gf_follow;
+
+/* Prefix, suffix for doing zle trickery */
+
+/**/
+char *glob_pre, *glob_suf;
+
+/* pathname component in filename patterns */
+
+struct complist {
+    Complist next;
+    Comp comp;
+    int closure;		/* 1 if this is a (foo/)# */
+    int follow; 		/* 1 to go thru symlinks */
+};
+struct comp {
+    Comp left, right, next, exclude;
+    char *str;
+    int stat;
+};
+
+/* Type of Comp:  a closure with one or two #'s, the end of a *
+ * pattern or path component, a piece of path to be added.    */
+#define C_ONEHASH	1
+#define C_TWOHASH	2
+#define C_OPTIONAL	4
+#define C_STAR		8    
+#define C_CLOSURE	(C_ONEHASH|C_TWOHASH|C_OPTIONAL|C_STAR)
+#define C_LAST		16
+#define C_PATHADD	32
+
+/* Test macros for the above */
+#define CLOSUREP(c)	(c->stat & C_CLOSURE)
+#define ONEHASHP(c)	(c->stat & (C_ONEHASH|C_STAR))
+#define TWOHASHP(c)	(c->stat & C_TWOHASH)
+#define OPTIONALP(c)	(c->stat & C_OPTIONAL)
+#define STARP(c)	(c->stat & C_STAR)
+#define LASTP(c)	(c->stat & C_LAST)
+#define PATHADDP(c)	(c->stat & C_PATHADD)
+
+/* Flags passed down to guts when compiling */
+#define GF_PATHADD	1	/* file glob, adding path components */
+#define GF_TOPLEV	2	/* outside (), so ~ ends main match */
+
+static char *pptr;		/* current place in string being matched */
+static Comp tail;
+static int first;		/* are leading dots special? */
+
+/* Add a component to pathbuf: This keeps track of how    *
+ * far we are into a file name, since each path component *
+ * must be matched separately.                            */
+
+/**/
+static void
+addpath(char *s)
+{
+    DPUTS(!pathbuf, "BUG: pathbuf not initialised");
+    while (pathpos + (int) strlen(s) + 1 >= pathbufsz)
+	pathbuf = realloc(pathbuf, pathbufsz *= 2);
+    while ((pathbuf[pathpos++] = *s++));
+    pathbuf[pathpos - 1] = '/';
+    pathbuf[pathpos] = '\0';
+}
+
+/* stat the filename s appended to pathbuf.  l should be true for lstat,    *
+ * false for stat.  If st is NULL, the file is only chechked for existance. *
+ * s == "" is treated as s == ".".  This is necessary since on most systems *
+ * foo/ can be used to reference a non-directory foo.  Returns nonzero if   *
+ * the file does not exists.                                                */
+
+/**/
+static int
+statfullpath(const char *s, struct stat *st, int l)
+{
+    char buf[PATH_MAX];
+
+    DPUTS(strlen(s) + !*s + pathpos - pathbufcwd >= PATH_MAX,
+	  "BUG: statfullpath(): pathname too long");
+    strcpy(buf, pathbuf + pathbufcwd);
+    strcpy(buf + pathpos - pathbufcwd, s);
+    if (!*s) {
+	buf[pathpos - pathbufcwd] = '.';
+	buf[pathpos - pathbufcwd + 1] = '\0';
+	l = 0;
+    }
+    unmetafy(buf, NULL);
+    if (!st)
+	return access(buf, F_OK) && (!l || readlink(buf, NULL, 0));
+    return l ? lstat(buf, st) : stat(buf, st);
+}
+
+/* add a match to the list */
+
+/**/
+static void
+insert(char *s, int checked)
+{
+    struct stat buf, buf2, *bp;
+    char *news = s;
+    int statted = 0;
+
+    if (gf_listtypes || gf_markdirs) {
+	/* Add the type marker to the end of the filename */
+	mode_t mode;
+	checked = statted = 1;
+	if (statfullpath(s, &buf, 1))
+	    return;
+	mode = buf.st_mode;
+	if (gf_follow) {
+	    if (!S_ISLNK(mode) || statfullpath(s, &buf2, 0))
+		memcpy(&buf2, &buf, sizeof(buf));
+	    statted = 2;
+	    mode = buf2.st_mode;
+	}
+	if (gf_listtypes || S_ISDIR(mode)) {
+	    int ll = strlen(s);
+
+	    news = (char *)ncalloc(ll + 2);
+	    strcpy(news, s);
+	    news[ll] = file_type(mode);
+	    news[ll + 1] = '\0';
+	}
+    }
+    if (qualct || qualorct) {
+	/* Go through the qualifiers, rejecting the file if appropriate */
+	struct qual *qo, *qn;
+
+	if (!statted && statfullpath(s, &buf, 1))
+	    return;
+	qo = quals;
+	for (qn = qo; qn && qn->func;) {
+	    range = qn->range;
+	    amc = qn->amc;
+	    units = qn->units;
+	    if ((qn->sense & 2) && statted != 2) {
+		/* If (sense & 2), we're following links */
+		if (!S_ISLNK(buf.st_mode) || statfullpath(s, &buf2, 0))
+		    memcpy(&buf2, &buf, sizeof(buf));
+		statted = 2;
+	    }
+	    bp = (qn->sense & 2) ? &buf2 : &buf;
+	    /* Reject the file if the function returned zero *
+	     * and the sense was positive (sense&1 == 0), or *
+	     * vice versa.                                   */
+	    if ((!((qn->func) (bp, qn->data)) ^ qn->sense) & 1) {
+		/* Try next alternative, or return if there are no more */
+		if (!(qo = qo->or))
+		    return;
+		qn = qo;
+		continue;
+	    }
+	    qn = qn->next;
+	}
+    } else if (!checked && statfullpath(s, NULL, 1))
+	return;
+
+    news = dyncat(pathbuf, news);
+    if (colonmod) {
+	/* Handle the remainder of the qualifer:  e.g. (:r:s/foo/bar/). */
+	s = colonmod;
+	modify(&news, &s);
+    }
+    *matchptr++ = news;
+    if (++matchct == matchsz) {
+	matchbuf = (char **)realloc((char *)matchbuf,
+				    sizeof(char **) * (matchsz *= 2));
+
+	matchptr = matchbuf + matchct;
+    }
+}
+
+/* Check to see if str is eligible for filename generation. */
+
+/**/
+int
+haswilds(char *str)
+{
+    /* `[' and `]' are legal even if bad patterns are usually not. */
+    if ((*str == Inbrack || *str == Outbrack) && !str[1])
+	return 0;
+
+    /* If % is immediately followed by ?, then that ? is     *
+     * not treated as a wildcard.  This is so you don't have *
+     * to escape job references such as %?foo.               */
+    if (str[0] == '%' && str[1] == Quest)
+	str[1] = '?';
+
+    for (; *str; str++) {
+	switch (*str) {
+	    case Inpar:
+	    case Bar:
+	    case Star:
+	    case Inbrack:
+	    case Inang:
+	    case Quest:
+		return 1;
+	    case Pound:
+	    case Hat:
+		if (isset(EXTENDEDGLOB))
+		    return 1;
+		break;
+	}
+    }
+    return 0;
+}
+
+/* Do the globbing:  scanner is called recursively *
+ * with successive bits of the path until we've    *
+ * tried all of it.                                */
+
+/**/
+static void
+scanner(Complist q)
+{
+    Comp c;
+    int closure;
+    int pbcwdsav = pathbufcwd;
+    struct dirsav ds;
+
+    ds.ino = ds.dev = 0;
+    ds.dirname = NULL;
+    ds.dirfd = ds.level = -1;
+    if (!q)
+	return;
+
+    if ((closure = q->closure))	/* (foo/)# - match zero or more dirs */
+	if (q->closure == 2)	/* (foo/)## - match one or more dirs */
+	    q->closure = 1;
+	else
+	    scanner(q->next);
+    c = q->comp;
+    /* Now the actual matching for the current path section. */
+    if (!(c->next || c->left) && !haswilds(c->str)) {
+	/* It's a straight string to the end of the path section. */
+	int l = strlen(c->str);
+
+	if (l + !l + pathpos - pathbufcwd >= PATH_MAX) {
+	    int err;
+
+	    if (l >= PATH_MAX)
+		return;
+	    err = lchdir(pathbuf + pathbufcwd, &ds, 0);
+	    if (err == -1)
+		return;
+	    if (err) {
+		zerr("current directory lost during glob", NULL, 0);
+		return;
+	    }
+	    pathbufcwd = pathpos;
+	}
+	if (q->next) {
+	    /* Not the last path section. Just add it to the path. */
+	    int oppos = pathpos;
+
+	    if (!errflag && !(q->closure && !strcmp(c->str, "."))) {
+		addpath(c->str);
+		if (!closure || statfullpath("", NULL, 1))
+		    scanner((q->closure) ? q : q->next);
+		pathbuf[pathpos = oppos] = '\0';
+	    }
+	} else
+	    insert(c->str, 0);
+    } else {
+	/* Do pattern matching on current path section. */
+	char *fn;
+	int dirs = !!q->next;
+	DIR *lock;
+	char *subdirs = NULL;
+	int subdirlen = 0;
+
+	fn = pathbuf[pathbufcwd] ? unmeta(pathbuf + pathbufcwd) : ".";
+	if (dirs) {
+	    struct stat st;
+	    stat(fn, &st);
+	    /* a directory with subdirectories has link count greater than 2 */
+	    if (!S_ISDIR(st.st_mode) || st.st_nlink == 2)
+		return;
+	}
+	lock = opendir(fn);
+	if (lock == NULL)
+	    return;
+	while ((fn = zreaddir(lock, 1)) && !errflag) {
+	    /* prefix and suffix are zle trickery */
+	    if (!dirs && !colonmod &&
+		((glob_pre && !strpfx(glob_pre, fn))
+		 || (glob_suf && !strsfx(glob_suf, fn))))
+		continue;
+	    if (domatch(fn, c, gf_noglobdots)) {
+		/* if this name matchs the pattern... */
+		if (pbcwdsav == pathbufcwd &&
+		    strlen(fn) + pathpos - pathbufcwd >= PATH_MAX) {
+		    int err;
+
+		    DPUTS(pathpos == pathbufcwd,
+			  "BUG: filename longer than PATH_MAX");
+		    err = lchdir(pathbuf + pathbufcwd, &ds, 0);
+		    if (err == -1)
+			break;
+		    if (err) {
+			zerr("current directory lost during glob", NULL, 0);
+			break;
+		    }
+		    pathbufcwd = pathpos;
+		}
+		if (dirs) {
+		    int l;
+
+		    /* if not the last component in the path */
+		    if (closure) {
+			/* if matching multiple directories */
+			struct stat buf;
+
+			if (statfullpath(fn, &buf, !q->follow)) {
+			    if (errno != ENOENT && errno != EINTR &&
+				errno != ENOTDIR && !errflag) {
+				zerr("%e: %s", fn, errno);
+				errflag = 0;
+			    }
+			    continue;
+			}
+			if (!S_ISDIR(buf.st_mode))
+			    continue;
+		    }
+		    l = strlen(fn) + 1;
+		    subdirs = hrealloc(subdirs, subdirlen, subdirlen + l);
+		    strcpy(subdirs + subdirlen, fn);
+		    subdirlen += l;
+		} else
+		    /* if the last filename component, just add it */
+		    insert(fn, 1);
+	    }
+	}
+	closedir(lock);
+	if (subdirs) {
+	    int oppos = pathpos;
+
+	    for (fn = subdirs; fn < subdirs+subdirlen; fn += strlen(fn) + 1) {
+		addpath(fn);
+		scanner((q->closure) ? q : q->next);  /* scan next level */
+		pathbuf[pathpos = oppos] = '\0';
+	    }
+	    hrealloc(subdirs, subdirlen, 0);
+	}
+    }
+    if (pbcwdsav < pathbufcwd) {
+	if (restoredir(&ds))
+	    zerr("current directory lost during glob", NULL, 0);
+	zsfree(ds.dirname);
+	if (ds.dirfd >= 0)
+	    close(ds.dirfd);
+	pathbufcwd = pbcwdsav;
+    }
+}
+
+/* Parse a series of path components pointed to by pptr */
+
+/* enum used with ksh-like patterns, @(...) etc. */
+
+enum { KF_NONE, KF_AT, KF_QUEST, KF_STAR, KF_PLUS, KF_NOT };
+
+/* parse lowest level pattern */
+
+/**/
+static Comp
+parsecomp(int gflag)
+{
+    int kshfunc;
+    Comp c = (Comp) alloc(sizeof *c), c1, c2;
+    char *cstr, *ls = NULL;
+
+    /* In case of alternatives, code coming up is stored in tail. */
+    c->next = tail;
+    cstr = pptr;
+
+    while (*pptr && (mode || *pptr != '/') && *pptr != Bar &&
+	   (unset(EXTENDEDGLOB) || *pptr != Tilde ||
+	    !pptr[1] || pptr[1] == Outpar || pptr[1] == Bar) &&
+	   *pptr != Outpar) {
+	/* Go through code until we find something separating alternatives,
+	 * or path components if relevant.
+	 */
+	if (*pptr == Hat && isset(EXTENDEDGLOB)) {
+	    /* negate remaining pattern */
+	    Comp stail = tail;
+	    tail = NULL;
+	    c->str = dupstrpfx(cstr, pptr - cstr);
+	    pptr++;
+
+	    c1 = (Comp) alloc(sizeof *c1);
+	    c1->stat |= C_STAR;
+
+	    c2 = (Comp) alloc(sizeof *c2);
+	    if (!(c2->exclude = parsecomp(gflag)))
+		return NULL;
+	    if (!*pptr || *pptr == '/')
+		c2->stat |= C_LAST;
+	    c2->left = c1;
+	    c2->next = stail;
+	    c->next = c2;
+	    tail = stail;
+	    return c;
+	}
+
+	/* Ksh-type globs */
+	kshfunc = KF_NONE;
+	if (isset(KSHGLOB) && *pptr && pptr[1] == Inpar) {
+	    switch (*pptr) {
+	    case '@':		/* just do paren as usual */
+		kshfunc = KF_AT;
+		break;
+
+	    case Quest:
+	    case '?':		/* matched optionally, treat as (...|) */
+		kshfunc = KF_QUEST;
+		break;
+
+	    case Star:
+	    case '*':		/* treat as (...)# */
+		kshfunc = KF_STAR;
+		break;
+
+	    case '+':		/* treat as (...)## */
+		kshfunc = KF_PLUS;
+		break;
+
+	    case '!':		/* treat as (*~...) */
+		kshfunc = KF_NOT;
+		break;
+	    }
+	    if (kshfunc != KF_NONE)
+		pptr++;
+	}
+
+	if (*pptr == Inpar) {
+	    /* Found a group (...) */
+	    char *startp = pptr, *endp;
+	    Comp stail = tail;
+	    int dpnd = 0;
+
+	    /* Need matching close parenthesis */
+	    if (skipparens(Inpar, Outpar, &pptr)) {
+		errflag = 1;
+		return NULL;
+	    }
+	    if (kshfunc == KF_STAR)
+		dpnd = 1;
+	    else if (kshfunc == KF_PLUS)
+		dpnd = 2;
+	    else if (kshfunc == KF_QUEST)
+		dpnd = 3;
+	    if (*pptr == Pound && isset(EXTENDEDGLOB)) {
+		/* Zero (or one) or more repetitions of group */
+		pptr++;
+		if(*pptr == Pound) {
+		    pptr++;
+		    if(dpnd == 0)
+			dpnd = 2;
+		    else if(dpnd == 3)
+			dpnd = 1;
+		} else
+		    dpnd = 1;
+	    }
+	    /* Parse the remaining pattern following the group... */
+	    if (!(c1 = parsecomp(gflag)))
+		return NULL;
+	    /* ...remembering what comes after it... */
+	    tail = (dpnd || kshfunc == KF_NOT) ? NULL : c1;
+	    /* ...before going back and parsing inside the group. */
+	    endp = pptr;
+	    pptr = startp;
+	    c->str = dupstrpfx(cstr, (pptr - cstr) - (kshfunc != KF_NONE));
+	    pptr++;
+	    c2 = (Comp) alloc(sizeof *c);
+	    c->next = c2;
+	    c2->next = (dpnd || kshfunc == KF_NOT) ?
+		c1 : (Comp) alloc(sizeof *c);
+	    if (!(c2->left = parsecompsw(0)))
+		return NULL;
+	    if (kshfunc == KF_NOT) {
+		/* we'd actually rather it didn't match.  Instead, match *
+		 * a star and put the parsed pattern into exclude.       */
+		Comp c3 = (Comp) alloc(sizeof *c3);
+		c3->stat |= C_STAR;
+
+		c2->exclude = c2->left;
+		c2->left = c3;
+	    }
+	    /* Remember closures for group. */
+	    if (dpnd)
+		c2->stat |= (dpnd == 3) ? C_OPTIONAL
+		    : (dpnd == 2) ? C_TWOHASH : C_ONEHASH;
+	    pptr = endp;
+	    tail = stail;
+	    return c;
+	}
+	if (*pptr == Star && pptr[1] &&
+	    (unset(EXTENDEDGLOB) || !(gflag & GF_TOPLEV) ||
+	     pptr[1] != Tilde || !pptr[2] || pptr[2] == Bar ||
+	     pptr[2] == Outpar) && (mode || pptr[1] != '/')) {
+	    /* Star followed by other patterns is now treated as a special
+	     * type of closure in doesmatch().
+	     */
+	    c->str = dupstrpfx(cstr, pptr - cstr);
+	    pptr++;
+	    c1 = (Comp) alloc(sizeof *c1);
+	    c1->stat |= C_STAR;
+	    if (!(c2 = parsecomp(gflag)))
+		return NULL;
+	    c1->next = c2;
+	    c->next = c1;
+	    return c;
+	}
+	if (*pptr == Pound && isset(EXTENDEDGLOB)) {
+	    /* repeat whatever we've just had (ls) zero or more times */
+	    if (!ls)
+		return NULL;
+	    c2 = (Comp) alloc(sizeof *c);
+	    c2->str = dupstrpfx(ls, pptr - ls);
+	    pptr++;
+	    if (*pptr == Pound) {
+		/* need one or more matches: cheat by copying previous char */
+		pptr++;
+		c->next = c1 = (Comp) alloc(sizeof *c);
+		c1->str = c2->str;
+	    } else
+		c1 = c;
+	    c1->next = c2;
+	    c2->stat |= C_ONEHASH;
+	    /* parse the rest of the pattern and return. */
+	    c2->next = parsecomp(gflag);
+	    if (!c2->next)
+		return NULL;
+	    c->str = dupstrpfx(cstr, ls - cstr);
+	    return c;
+	}
+	ls = pptr;		/* whatever we just parsed */
+	if (*pptr == Inang) {
+	    /* Numeric glob */
+	    int dshct;
+
+	    dshct = (pptr[1] == Outang);
+	    while (*++pptr && *pptr != Outang)
+		if (*pptr == '-' && !dshct)
+		    dshct = 1;
+		else if (!idigit(*pptr))
+		    break;
+	    if (*pptr != Outang)
+		return NULL;
+	} else if (*pptr == Inbrack) {
+	    /* Character set: brackets had better match */
+	    if (pptr[1] == Outbrack)
+		*++pptr = ']';
+	    else if ((pptr[1] == Hat || pptr[1] == '^' || pptr[1] == '!') &&
+		     pptr[2] == Outbrack)
+		*(pptr += 2) = ']';
+	    while (*++pptr && *pptr != Outbrack) {
+		if (itok(*pptr)) {
+		    /* POSIX classes: make sure it's a real one, *
+		     * leave the Inbrack tokenised if so.        */
+		    char *nptr;
+		    if (*pptr == Inbrack && pptr[1] == ':'
+			&& (nptr = strchr(pptr+2, ':')) && 
+			*++nptr == Outbrack)
+			pptr = nptr;
+		    *pptr = ztokens[*pptr - Pound];
+		}
+	    }
+	    if (*pptr != Outbrack)
+		return NULL;
+	} else if (itok(*pptr) && *pptr != Star && *pptr != Quest)
+	    /* something that can be tokenised which isn't otherwise special */
+	    *pptr = ztokens[*pptr - Pound];
+	pptr++;
+    }
+    /* mark if last pattern component in path component or pattern */
+    if (*pptr == '/' || !*pptr ||
+	(isset(EXTENDEDGLOB) && *pptr == Tilde && (gflag & GF_TOPLEV)))
+	c->stat |= C_LAST;
+    c->str = dupstrpfx(cstr, pptr - cstr);
+    return c;
+}
+
+/* Parse pattern possibly with different alternatives (|) */
+
+/**/
+static Comp
+parsecompsw(int gflag)
+{
+    Comp c1, c2, c3, excl = NULL, stail = tail;
+    char *sptr;
+
+    /*
+     * Check for a tilde in the expression.  We need to know this in 
+     * advance so as to be able to treat the whole a~b expression by
+     * backtracking:  see exclusion code in doesmatch().
+     */
+    if (isset(EXTENDEDGLOB)) {
+	int pct = 0;
+	for (sptr = pptr; *sptr; sptr++) {
+	    if (*sptr == Inpar)
+		pct++;
+	    else if (*sptr == Outpar && --pct < 0)
+		break;
+	    else if (*sptr == Bar && !pct)
+		break;
+	    else if (*sptr == Tilde && !pct) {
+		tail = NULL;
+		break;
+	    }
+	}
+    }
+
+    c1 = parsecomp(gflag);
+    if (!c1)
+	return NULL;
+    if (isset(EXTENDEDGLOB) && *pptr == Tilde) {
+	/* Matching remainder of pattern excludes the pattern from matching */
+	int oldmode = mode;
+
+	mode = 1;
+	pptr++;
+	excl = parsecomp(gflag);
+	mode = oldmode;
+	if (!excl)
+	    return NULL;
+    }
+    tail = stail;
+    if (*pptr == Bar || excl) {
+	/* found an alternative or something to exclude */
+	c2 = (Comp) alloc(sizeof *c2);
+	if (*pptr == Bar) {
+	    /* get the next alternative after the | */
+	    pptr++;
+	    c3 = parsecompsw(gflag);
+	    if (!c3)
+		return NULL;
+	} else
+	    c3 = NULL;
+	/* mark if end of pattern or path component */
+	if (!*pptr || *pptr == '/')
+	    c1->stat |= c2->stat = C_LAST;
+	c2->str = dupstring("");
+	c2->left = c1;
+	c2->right = c3;
+	if ((c2->exclude = excl))
+	    c2->next = stail;
+	if (gflag & GF_PATHADD)
+	    c2->stat |= C_PATHADD;
+	return c2;
+    }
+    return c1;
+}
+
+/* This function tokenizes a zsh glob pattern */
+
+/**/
+static Complist
+parsecomplist(void)
+{
+    Comp c1;
+    Complist p1;
+    char *str;
+
+    if (pptr[0] == Star && pptr[1] == Star &&
+        (pptr[2] == '/' || (pptr[2] == Star && pptr[3] == '/'))) {
+	/* Match any number of directories. */
+	int follow;
+
+	/* with three stars, follow symbolic links */
+	follow = (pptr[2] == Star);
+	pptr += (3 + follow);
+
+	/* Now get the next path component if there is one. */
+	p1 = (Complist) alloc(sizeof *p1);
+	if ((p1->next = parsecomplist()) == NULL) {
+	    errflag = 1;
+	    return NULL;
+	}
+	p1->comp = (Comp) alloc(sizeof *p1->comp);
+	p1->comp->stat |= C_LAST;	/* end of path component  */
+	p1->comp->str = dupstring("*");
+	*p1->comp->str = Star;		/* match anything...      */
+	p1->closure = 1;		/* ...zero or more times. */
+	p1->follow = follow;
+	return p1;
+    }
+
+    /* Parse repeated directories such as (dir/)# and (dir/)## */
+    if (*(str = pptr) == Inpar && !skipparens(Inpar, Outpar, &str) &&
+        *str == Pound && isset(EXTENDEDGLOB) && str[-2] == '/') {
+	pptr++;
+	if (!(c1 = parsecompsw(0)))
+	    return NULL;
+	if (pptr[0] == '/' && pptr[1] == Outpar && pptr[2] == Pound) {
+	    int pdflag = 0;
+
+	    pptr += 3;
+	    if (*pptr == Pound) {
+		pdflag = 1;
+		pptr++;
+	    }
+	    p1 = (Complist) alloc(sizeof *p1);
+	    p1->comp = c1;
+	    p1->closure = 1 + pdflag;
+	    p1->follow = 0;
+	    p1->next = parsecomplist();
+	    return (p1->comp) ? p1 : NULL;
+	}
+    } else {
+	/* parse single path component */
+	if (!(c1 = parsecompsw(GF_PATHADD|GF_TOPLEV)))
+	    return NULL;
+	/* then do the remaining path compoents */
+	if (*pptr == '/' || !*pptr) {
+	    int ef = *pptr == '/';
+
+	    p1 = (Complist) alloc(sizeof *p1);
+	    p1->comp = c1;
+	    p1->closure = 0;
+	    p1->next = ef ? (pptr++, parsecomplist()) : NULL;
+	    return (ef && !p1->next) ? NULL : p1;
+	}
+    }
+    errflag = 1;
+    return NULL;
+}
+
+/* turn a string into a Complist struct:  this has path components */
+
+/**/
+static Complist
+parsepat(char *str)
+{
+    mode = 0;			/* path components present */
+    pptr = str;
+    tail = NULL;
+    return parsecomplist();
+}
+
+/* get number after qualifier */
+
+/**/
+static long
+qgetnum(char **s)
+{
+    long v = 0;
+
+    if (!idigit(**s)) {
+	zerr("number expected", NULL, 0);
+	return 0;
+    }
+    while (idigit(**s))
+	v = v * 10 + *(*s)++ - '0';
+    return v;
+}
+
+/* get octal number after qualifier */
+
+/**/
+static long
+qgetoctnum(char **s)
+{
+    long v = 0;
+
+    if (!idigit(**s)) {
+	zerr("octal number expected", NULL, 0);
+	return 0;
+    }
+    while (**s >= '0' && **s <= '7')
+	v = v * 010 + *(*s)++ - '0';
+    return v;
+}
+
+/* Main entry point to the globbing code for filename globbing. *
+ * np points to a node in the list list which will be expanded  *
+ * into a series of nodes.                                      */
+
+/**/
+void
+glob(LinkList list, LinkNode np)
+{
+    struct qual *qo, *qn, *ql;
+    LinkNode node = prevnode(np);
+    char *str;				/* the pattern                   */
+    int sl;				/* length of the pattern         */
+    Complist q;				/* pattern after parsing         */
+    char *ostr = (char *)getdata(np);	/* the pattern before the parser */
+					/* chops it up                   */
+
+    MUSTUSEHEAP("glob");
+    if (unset(GLOBOPT) || !haswilds(ostr)) {
+	untokenize(ostr);
+	return;
+    }
+    str = dupstring(ostr);
+    sl = strlen(str);
+    uremnode(list, np);
+
+    /* Initialise state variables for current file pattern */
+    qo = qn = quals = ql = NULL;
+    qualct = qualorct = 0;
+    colonmod = NULL;
+    gf_nullglob = isset(NULLGLOB);
+    gf_markdirs = isset(MARKDIRS);
+    gf_listtypes = gf_follow = 0;
+    gf_noglobdots = unset(GLOBDOTS);
+
+    /* Check for qualifiers */
+    if (isset(BAREGLOBQUAL) && str[sl - 1] == Outpar) {
+	char *s;
+
+	/* Check these are really qualifiers, not a set of *
+	 * alternatives or exclusions                      */
+	for (s = str + sl - 2; *s != Inpar; s--)
+	    if (*s == Bar || *s == Outpar ||
+		(isset(EXTENDEDGLOB) && *s == Tilde))
+		break;
+	if (*s == Inpar) {
+	    /* Real qualifiers found. */
+	    int sense = 0;	/* bit 0 for match (0)/don't match (1)   */
+				/* bit 1 for follow links (2), don't (0) */
+	    long data = 0;	/* Any numerical argument required       */
+	    int (*func) _((Statptr, long));
+
+	    str[sl-1] = 0;
+	    *s++ = 0;
+	    while (*s && !colonmod) {
+		func = (int (*) _((Statptr, long)))0;
+		if (idigit(*s)) {
+		    /* Store numeric argument for qualifier */
+		    func = qualflags;
+		    data = 0;
+		    while (idigit(*s))
+			data = data * 010 + (*s++ - '0');
+		} else if (*s == ',') {
+		    /* A comma separates alternative sets of qualifiers */
+		    s++;
+		    sense = 0;
+		    if (qualct) {
+			qn = (struct qual *)hcalloc(sizeof *qn);
+			qo->or = qn;
+			qo = qn;
+			qualorct++;
+			qualct = 0;
+			ql = NULL;
+		    }
+		} else
+		    switch (*s++) {
+		    case ':':
+			/* Remaining arguments are history-type     *
+			 * colon substitutions, handled separately. */
+			colonmod = s - 1;
+			untokenize(colonmod);
+			break;
+		    case Hat:
+		    case '^':
+			/* Toggle sense:  go from positive to *
+			 * negative match and vice versa.     */
+			sense ^= 1;
+			break;
+		    case '-':
+			/* Toggle matching of symbolic links */
+			sense ^= 2;
+			break;
+		    case '@':
+			/* Match symbolic links */
+			func = qualislnk;
+			break;
+		    case Equals:
+		    case '=':
+			/* Match sockets */
+			func = qualissock;
+			break;
+		    case 'p':
+			/* Match named pipes */
+			func = qualisfifo;
+			break;
+		    case '/':
+			/* Match directories */
+			func = qualisdir;
+			break;
+		    case '.':
+			/* Match regular files */
+			func = qualisreg;
+			break;
+		    case '%':
+			/* Match special files: block, *
+			 * character or any device     */
+			if (*s == 'b')
+			    s++, func = qualisblk;
+			else if (*s == 'c')
+			    s++, func = qualischr;
+			else
+			    func = qualisdev;
+			break;
+		    case Star:
+			/* Match executable plain files */
+			func = qualiscom;
+			break;
+		    case 'R':
+			/* Match world-readable files */
+			func = qualflags;
+			data = 0004;
+			break;
+		    case 'W':
+			/* Match world-writeable files */
+			func = qualflags;
+			data = 0002;
+			break;
+		    case 'X':
+			/* Match world-executable files */
+			func = qualflags;
+			data = 0001;
+			break;
+		    case 'A':
+			func = qualflags;
+			data = 0040;
+			break;
+		    case 'I':
+			func = qualflags;
+			data = 0020;
+			break;
+		    case 'E':
+			func = qualflags;
+			data = 0010;
+			break;
+		    case 'r':
+			/* Match files readable by current process */
+			func = qualflags;
+			data = 0400;
+			break;
+		    case 'w':
+			/* Match files writeable by current process */
+			func = qualflags;
+			data = 0200;
+			break;
+		    case 'x':
+			/* Match files executable by current process */
+			func = qualflags;
+			data = 0100;
+			break;
+		    case 's':
+			/* Match setuid files */
+			func = qualflags;
+			data = 04000;
+			break;
+		    case 'S':
+			/* Match setgid files */
+			func = qualflags;
+			data = 02000;
+			break;
+		    case 't':
+			func = qualflags;
+			data = 01000;
+			break;
+		    case 'd':
+			/* Match device files by device number  *
+			 * (as given by stat's st_dev element). */
+			func = qualdev;
+			data = qgetnum(&s);
+			break;
+		    case 'l':
+			/* Match files with the given no. of hard links */
+			func = qualnlink;
+			amc = -1;
+			goto getrange;
+		    case 'U':
+			/* Match files owned by effective user ID */
+			func = qualuid;
+			data = geteuid();
+			break;
+		    case 'G':
+			/* Match files owned by effective group ID */
+			func = qualgid;
+			data = getegid();
+			break;
+		    case 'u':
+			/* Match files owned by given user id */
+			func = qualuid;
+			/* either the actual uid... */
+			if (idigit(*s))
+			    data = qgetnum(&s);
+			else {
+			    /* ... or a user name */
+			    char sav, *tt;
+
+			    /* Find matching delimiters */
+			    tt = get_strarg(s);
+			    if (!*tt) {
+				zerr("missing end of name",
+				     NULL, 0);
+				data = 0;
+			    } else {
+#ifdef HAVE_GETPWNAM
+				struct passwd *pw;
+				sav = *tt;
+				*tt = '\0';
+
+				if ((pw = getpwnam(s + 1)))
+				    data = pw->pw_uid;
+				else {
+				    zerr("unknown user", NULL, 0);
+				    data = 0;
+				}
+				*tt = sav;
+#else /* !HAVE_GETPWNAM */
+				sav = *tt;
+				zerr("unknown user", NULL, 0);
+				data = 0;
+#endif /* !HAVE_GETPWNAM */
+				if (sav)
+				    s = tt + 1;
+				else
+				    s = tt;
+			    }
+			}
+			break;
+		    case 'g':
+			/* Given gid or group id... works like `u' */
+			func = qualgid;
+			/* either the actual gid... */
+			if (idigit(*s))
+			    data = qgetnum(&s);
+			else {
+			    /* ...or a delimited group name. */
+			    char sav, *tt;
+
+			    tt = get_strarg(s);
+			    if (!*tt) {
+				zerr("missing end of name",
+				     NULL, 0);
+				data = 0;
+			    } else {
+#ifdef HAVE_GETGRNAM
+				struct group *gr;
+				sav = *tt;
+				*tt = '\0';
+
+				if ((gr = getgrnam(s + 1)))
+				    data = gr->gr_gid;
+				else {
+				    zerr("unknown group", NULL, 0);
+				    data = 0;
+				}
+				*tt = sav;
+#else /* !HAVE_GETGRNAM */
+				sav = *tt;
+				zerr("unknown group", NULL, 0);
+				data = 0;
+#endif /* !HAVE_GETGRNAM */
+				if (sav)
+				    s = tt + 1;
+				else
+				    s = tt;
+			    }
+			}
+			break;
+		    case 'o':
+			/* Match octal mode of file exactly. *
+			 * Currently undocumented.           */
+			func = qualeqflags;
+			data = qgetoctnum(&s);
+			break;
+		    case 'M':
+			/* Mark directories with a / */
+			if ((gf_markdirs = !(sense & 1)))
+			    gf_follow = sense & 2;
+			break;
+		    case 'T':
+			/* Mark types in a `ls -F' type fashion */
+			if ((gf_listtypes = !(sense & 1)))
+			    gf_follow = sense & 2;
+			break;
+		    case 'N':
+			/* Nullglob:  remove unmatched patterns. */
+			gf_nullglob = !(sense & 1);
+			break;
+		    case 'D':
+			/* Glob dots: match leading dots implicitly */
+			gf_noglobdots = sense & 1;
+			break;
+		    case 'a':
+			/* Access time in given range */
+			amc = 0;
+			func = qualtime;
+			goto getrange;
+		    case 'm':
+			/* Modification time in given range */
+			amc = 1;
+			func = qualtime;
+			goto getrange;
+		    case 'c':
+			/* Inode creation time in given range */
+			amc = 2;
+			func = qualtime;
+			goto getrange;
+		    case 'L':
+			/* File size (Length) in given range */
+			func = qualsize;
+			amc = -1;
+			/* Get size multiplier */
+			units = TT_BYTES;
+			if (*s == 'p' || *s == 'P')
+			    units = TT_POSIX_BLOCKS, ++s;
+			else if (*s == 'k' || *s == 'K')
+			    units = TT_KILOBYTES, ++s;
+			else if (*s == 'm' || *s == 'M')
+			    units = TT_MEGABYTES, ++s;
+		      getrange:
+			/* Get time multiplier */
+			if (amc >= 0) {
+			    units = TT_DAYS;
+			    if (*s == 'h')
+				units = TT_HOURS, ++s;
+			    else if (*s == 'm')
+				units = TT_MINS, ++s;
+			    else if (*s == 'w')
+				units = TT_WEEKS, ++s;
+			    else if (*s == 'M')
+				units = TT_MONTHS, ++s;
+			}
+			/* See if it's greater than, equal to, or less than */
+			if ((range = *s == '+' ? 1 : *s == '-' ? -1 : 0))
+			    ++s;
+			data = qgetnum(&s);
+			break;
+
+		    default:
+			zerr("unknown file attribute", NULL, 0);
+			return;
+		    }
+		if (func) {
+		    /* Requested test is performed by function func */
+		    if (!qn)
+			qn = (struct qual *)hcalloc(sizeof *qn);
+		    if (ql)
+			ql->next = qn;
+		    ql = qn;
+		    if (!quals)
+			quals = qo = qn;
+		    qn->func = func;
+		    qn->sense = sense;
+		    qn->data = data;
+		    qn->range = range;
+		    qn->units = units;
+		    qn->amc = amc;
+		    qn = NULL;
+		    qualct++;
+		}
+		if (errflag)
+		    return;
+	    }
+	}
+    }
+    if (!pathbuf)
+	pathbuf = zalloc(pathbufsz = PATH_MAX);
+    DPUTS(pathbufcwd, "BUG: glob changed directory");
+    if (*str == '/') {		/* pattern has absolute path */
+	str++;
+	pathbuf[0] = '/';
+	pathbuf[pathpos = 1] = '\0';
+    } else			/* pattern is relative to pwd */
+	pathbuf[pathpos = 0] = '\0';
+    q = parsepat(str);
+    if (!q || errflag) {	/* if parsing failed */
+	if (unset(BADPATTERN)) {
+	    untokenize(ostr);
+	    insertlinknode(list, node, ostr);
+	    return;
+	}
+	errflag = 0;
+	zerr("bad pattern: %s", ostr, 0);
+	return;
+    }
+
+    /* Initialise receptacle for matched files, *
+     * expanded by insert() where necessary.    */
+    matchptr = matchbuf = (char **)zalloc((matchsz = 16) * sizeof(char *));
+    matchct = 0;
+
+    /* The actual processing takes place here: matches go into  *
+     * matchbuf.  This is the only top-level call to scanner(). */
+    scanner(q);
+
+    /* Deal with failures to match depending on options */
+    if (matchct)
+	badcshglob |= 2;	/* at least one cmd. line expansion O.K. */
+    else if (!gf_nullglob)
+	if (isset(CSHNULLGLOB)) {
+	    badcshglob |= 1;	/* at least one cmd. line expansion failed */
+	} else if (isset(NOMATCH)) {
+	    zerr("no matches found: %s", ostr, 0);
+	    free(matchbuf);
+	    return;
+	} else {
+	    /* treat as an ordinary string */
+	    untokenize(*matchptr++ = dupstring(ostr));
+	    matchct = 1;
+	}
+    /* Sort arguments in to lexical (and possibly numeric) order. *
+     * This is reversed to facilitate insertion into the list.    */
+    qsort((void *) & matchbuf[0], matchct, sizeof(char *),
+	       (int (*) _((const void *, const void *)))notstrcmp);
+
+    matchptr = matchbuf;
+    while (matchct--)		/* insert matches in the arg list */
+	insertlinknode(list, node, *matchptr++);
+    free(matchbuf);
+}
+
+/* Return the order of two strings, taking into account *
+ * possible numeric order if NUMERICGLOBSORT is set.    *
+ * The comparison here is reversed.                     */
+
+/**/
+static int
+notstrcmp(char **a, char **b)
+{
+    char *c = *b, *d = *a;
+    int cmp;
+
+#ifdef HAVE_STRCOLL
+    cmp = strcoll(c, d);
+#endif
+    for (; *c == *d && *c; c++, d++);
+#ifndef HAVE_STRCOLL
+    cmp = (int)STOUC(*c) - (int)STOUC(*d);
+#endif
+    if (isset(NUMERICGLOBSORT) && (idigit(*c) || idigit(*d))) {
+	for (; c > *b && idigit(c[-1]); c--, d--);
+	if (idigit(*c) && idigit(*d)) {
+	    while (*c == '0')
+		c++;
+	    while (*d == '0')
+		d++;
+	    for (; idigit(*c) && *c == *d; c++, d++);
+	    if (idigit(*c) || idigit(*d)) {
+		cmp = (int)STOUC(*c) - (int)STOUC(*d);
+		while (idigit(*c) && idigit(*d))
+		    c++, d++;
+		if (idigit(*c) && !idigit(*d))
+		    return 1;
+		if (idigit(*d) && !idigit(*c))
+		    return -1;
+	    }
+	}
+    }
+    return cmp;
+}
+
+/* Return the trailing character for marking file types */
+
+/**/
+char
+file_type(mode_t filemode)
+{
+    if(S_ISBLK(filemode))
+	return '#';
+    else if(S_ISCHR(filemode))
+	return '%';
+    else if(S_ISDIR(filemode))
+	return '/';
+    else if(S_ISFIFO(filemode))
+	return '|';
+    else if(S_ISLNK(filemode))
+	return '@';
+    else if(S_ISREG(filemode))
+	return (filemode & S_IXUGO) ? '*' : ' ';
+    else if(S_ISSOCK(filemode))
+	return '=';
+    else
+	return '?';
+}
+
+/* check to see if str is eligible for brace expansion */
+
+/**/
+int
+hasbraces(char *str)
+{
+    char *lbr, *mbr, *comma;
+
+    if (isset(BRACECCL)) {
+	/* In this case, any properly formed brace expression  *
+	 * will match and expand to the characters in between. */
+	int bc;
+
+	for (bc = 0; *str; ++str)
+	    if (*str == Inbrace) {
+		if (!bc && str[1] == Outbrace)
+		    *str++ = '{', *str = '}';
+		else
+		    bc++;
+	    } else if (*str == Outbrace)
+		if (!bc)
+		    *str = '}';
+		else if (!--bc)
+		    return 1;
+	return 0;
+    }
+    /* Otherwise we need to look for... */
+    lbr = mbr = comma = NULL;
+    for (;;) {
+	switch (*str++) {
+	case Inbrace:
+	    if (!lbr) {
+		lbr = str - 1;
+		while (idigit(*str))
+		    str++;
+		if (*str == '.' && str[1] == '.') {
+		    str++;
+		    while (idigit(*++str));
+		    if (*str == Outbrace &&
+			(idigit(lbr[1]) || idigit(str[-1])))
+			return 1;
+		}
+	    } else {
+		char *s = --str;
+
+		if (skipparens(Inbrace, Outbrace, &str)) {
+		    *lbr = *s = '{';
+		    if (comma)
+			str = comma;
+		    if (mbr && mbr < str)
+			str = mbr;
+		    lbr = mbr = comma = NULL;
+		} else if (!mbr)
+		    mbr = s;
+	    }
+	    break;
+	case Outbrace:
+	    if (!lbr)
+		str[-1] = '}';
+	    else if (comma)
+		return 1;
+	    else {
+		*lbr = '{';
+		str[-1] = '}';
+		if (mbr)
+		    str = mbr;
+		mbr = lbr = NULL;
+	    }
+	    break;
+	case Comma:
+	    if (!lbr)
+		str[-1] = ',';
+	    else if (!comma)
+		comma = str - 1;
+	    break;
+	case '\0':
+	    if (lbr)
+		*lbr = '{';
+	    if (!mbr && !comma)
+		return 0;
+	    if (comma)
+		str = comma;
+	    if (mbr && mbr < str)
+		str = mbr;
+	    lbr = mbr = comma = NULL;
+	    break;
+	}
+    }
+}
+
+/* expand stuff like >>*.c */
+
+/**/
+int
+xpandredir(struct redir *fn, LinkList tab)
+{
+    LinkList fake;
+    char *nam;
+    struct redir *ff;
+    int ret = 0;
+
+    /* Stick the name in a list... */
+    fake = newlinklist();
+    addlinknode(fake, fn->name);
+    /* ...which undergoes all the usual shell expansions */
+    prefork(fake, isset(MULTIOS) ? 0 : 4);
+    /* Globbing is only done for multios. */
+    if (!errflag && isset(MULTIOS))
+	globlist(fake);
+    if (errflag)
+	return 0;
+    if (nonempty(fake) && !nextnode(firstnode(fake))) {
+	/* Just one match, the usual case. */
+	char *s = peekfirst(fake);
+	fn->name = s;
+	untokenize(s);
+	if (fn->type == MERGEIN || fn->type == MERGEOUT) {
+	    if (s[0] == '-' && !s[1])
+		fn->type = CLOSE;
+	    else if (s[0] == 'p' && !s[1]) 
+		fn->fd2 = (fn->type == MERGEOUT) ? coprocout : coprocin;
+	    else {
+		while (idigit(*s))
+		    s++;
+		if (!*s && s > fn->name)
+		    fn->fd2 = zstrtol(fn->name, NULL, 10);
+		else if (fn->type == MERGEIN)
+		    zerr("file number expected", NULL, 0);
+		else
+		    fn->type = ERRWRITE;
+	    }
+	}
+    } else if (fn->type == MERGEIN)
+	zerr("file number expected", NULL, 0);
+    else {
+	if (fn->type == MERGEOUT)
+	    fn->type = ERRWRITE;
+	while ((nam = (char *)ugetnode(fake))) {
+	    /* Loop over matches, duplicating the *
+	     * redirection for each file found.   */
+	    ff = (struct redir *)alloc(sizeof *ff);
+	    *ff = *fn;
+	    ff->name = nam;
+	    addlinknode(tab, ff);
+	    ret = 1;
+	}
+    }
+    return ret;
+}
+
+/* concatenate s1 and s2 in dynamically allocated buffer */
+
+/**/
+char *
+dyncat(char *s1, char *s2)
+{
+    /* This version always uses space from the current heap. */
+    char *ptr;
+    int l1 = strlen(s1);
+
+    ptr = (char *)ncalloc(l1 + strlen(s2) + 1);
+    strcpy(ptr, s1);
+    strcpy(ptr + l1, s2);
+    return ptr;
+}
+
+/* concatenate s1, s2, and s3 in dynamically allocated buffer */
+
+/**/
+char *
+tricat(char const *s1, char const *s2, char const *s3)
+{
+    /* This version always uses permanently-allocated space. */
+    char *ptr;
+
+    ptr = (char *)zalloc(strlen(s1) + strlen(s2) + strlen(s3) + 1);
+    strcpy(ptr, s1);
+    strcat(ptr, s2);
+    strcat(ptr, s3);
+    return ptr;
+}
+
+/* brace expansion */
+
+/**/
+void
+xpandbraces(LinkList list, LinkNode *np)
+{
+    LinkNode node = (*np), last = prevnode(node);
+    char *str = (char *)getdata(node), *str3 = str, *str2;
+    int prev, bc, comma, dotdot;
+
+    for (; *str != Inbrace; str++);
+    /* First, match up braces and see what we have. */
+    for (str2 = str, bc = comma = dotdot = 0; *str2; ++str2)
+	if (*str2 == Inbrace)
+	    ++bc;
+	else if (*str2 == Outbrace) {
+	    if (--bc == 0)
+		break;
+	} else if (bc == 1)
+	    if (*str2 == Comma)
+		++comma;	/* we have {foo,bar} */
+	    else if (*str2 == '.' && str2[1] == '.')
+		dotdot++;	/* we have {num1..num2} */
+    DPUTS(bc, "BUG: unmatched brace in xpandbraces()");
+    if (!comma && dotdot) {
+	/* Expand range like 0..10 numerically: comma or recursive
+	   brace expansion take precedence. */
+	char *dots, *p;
+	LinkNode olast = last;
+	/* Get the first number of the range */
+	int rstart = zstrtol(str+1,&dots,10), rend = 0, err = 0, rev = 0;
+	int wid1 = (dots - str) - 1, wid2 = (str2 - dots) - 2;
+	int strp = str - str3;
+      
+	if (dots == str + 1 || *dots != '.' || dots[1] != '.')
+	    err++;
+	else {
+	    /* Get the last number of the range */
+	    rend = zstrtol(dots+2,&p,10);
+	    if (p == dots+2 || p != str2)
+		err++;
+	}
+	if (!err) {
+	    /* If either no. begins with a zero, pad the output with   *
+	     * zeroes. Otherwise, choose a min width to suppress them. */
+	    int minw = (str[1] == '0') ? wid1 : (dots[2] == '0' ) ? wid2 :
+		(wid2 > wid1) ? wid1 : wid2;
+	    if (rstart > rend) {
+		/* Handle decreasing ranges correctly. */
+		int rt = rend;
+		rend = rstart;
+		rstart = rt;
+		rev = 1;
+	    }
+	    uremnode(list, node);
+	    for (; rend >= rstart; rend--) {
+		/* Node added in at end, so do highest first */
+		p = dupstring(str3);
+		sprintf(p + strp, "%0*d", minw, rend);
+		strcat(p + strp, str2 + 1);
+		insertlinknode(list, last, p);
+		if (rev)	/* decreasing:  add in reverse order. */
+		    last = nextnode(last);
+	    }
+	    *np = nextnode(olast);
+	    return;
+	}
+    }
+    if (!comma && isset(BRACECCL)) {	/* {a-mnop} */
+	/* Here we expand each character to a separate node,      *
+	 * but also ranges of characters like a-m.  ccl is a      *
+	 * set of flags saying whether each character is present; *
+	 * the final list is in lexical order.                    */
+	char ccl[256], *p;
+	unsigned char c1, c2, lastch;
+	unsigned int len, pl;
+
+	uremnode(list, node);
+	memset(ccl, 0, sizeof(ccl) / sizeof(ccl[0]));
+	for (p = str + 1, lastch = 0; p < str2;) {
+	    if (itok(c1 = *p++))
+		c1 = ztokens[c1 - STOUC(Pound)];
+	    if ((char) c1 == Meta)
+		c1 = 32 ^ *p++;
+	    if (itok(c2 = *p))
+		c2 = ztokens[c2 - STOUC(Pound)];
+	    if ((char) c2 == Meta)
+		c2 = 32 ^ p[1];
+	    if (c1 == '-' && lastch && p < str2 && (int)lastch <= (int)c2) {
+		while ((int)lastch < (int)c2)
+		    ccl[lastch++] = 1;
+		lastch = 0;
+	    } else
+		ccl[lastch = c1] = 1;
+	}
+	pl = str - str3;
+	len = pl + strlen(++str2) + 2;
+	for (p = ccl + 255; p-- > ccl;)
+	    if (*p) {
+		c1 = p - ccl;
+		if (imeta(c1)) {
+		    str = ncalloc(len + 1);
+		    str[pl] = Meta;
+		    str[pl+1] = c1 ^ 32;
+		    strcpy(str + pl + 2, str2);
+		} else {
+		    str = ncalloc(len);
+		    str[pl] = c1;
+		    strcpy(str + pl + 1, str2);
+		}
+		memcpy(str, str3, pl);
+		insertlinknode(list, last, str);
+	    }
+	*np = nextnode(last);
+	return;
+    }
+    prev = str++ - str3;
+    str2++;
+    uremnode(list, node);
+    node = last;
+    /* Finally, normal comma expansion               *
+     * str1{foo,bar}str2 -> str1foostr2 str1barstr2. *
+     * Any number of intervening commas is allowed.  */
+    for (;;) {
+	char *zz, *str4;
+	int cnt;
+
+	for (str4 = str, cnt = 0; cnt || (*str != Comma && *str !=
+					  Outbrace); str++) {
+	    if (*str == Inbrace)
+		cnt++;
+	    else if (*str == Outbrace)
+		cnt--;
+	    DPUTS(!*str, "BUG: illegal brace expansion");
+	}
+	/* Concatenate the string before the braces (str3), the section *
+	 * just found (str4) and the text after the braces (str2)       */
+	zz = (char *)ncalloc(prev + (str - str4) + strlen(str2) + 1);
+	ztrncpy(zz, str3, prev);
+	strncat(zz, str4, str - str4);
+	strcat(zz, str2);
+	/* and add this text to the argument list. */
+	insertlinknode(list, node, zz);
+	incnode(node);
+	if (*str != Outbrace)
+	    str++;
+	else
+	    break;
+    }
+    *np = nextnode(last);
+}
+
+/* check to see if a matches b (b is not a filename pattern) */
+
+/**/
+int
+matchpat(char *a, char *b)
+{
+    Comp c = parsereg(b);
+
+    if (!c) {
+	zerr("bad pattern: %s", b, 0);
+	return 0;
+    }
+    return domatch(a, c, 0);
+}
+
+/* do the ${foo%%bar}, ${foo#bar} stuff */
+/* please do not laugh at this code. */
+
+/* Having found a match in getmatch, decide what part of string
+ * to return.  The matched part starts b characters into string s
+ * and finishes e characters in: 0 <= b <= e <= strlen(s)
+ * (yes, empty matches should work).
+ * Bits 3 and higher in fl are used: the flags are
+ *   8:		Result is matched portion.
+ *  16:		Result is unmatched portion.
+ *		(N.B. this should be set for standard ${foo#bar} etc. matches.)
+ *  32:		Result is numeric position of start of matched portion.
+ *  64:		Result is numeric position of end of matched portion.
+ * 128:		Result is length of matched portion.
+ */
+
+/**/
+static char *
+get_match_ret(char *s, int b, int e, int fl)
+{
+    char buf[80], *r, *p, *rr;
+    int ll = 0, l = strlen(s), bl = 0, t = 0, i;
+
+    if (fl & 8)			/* matched portion */
+	ll += 1 + (e - b);
+    if (fl & 16)		/* unmatched portion */
+	ll += 1 + (l - (e - b));
+    if (fl & 32) {
+	/* position of start of matched portion */
+	sprintf(buf, "%d ", b + 1);
+	ll += (bl = strlen(buf));
+    }
+    if (fl & 64) {
+	/* position of end of matched portion */
+	sprintf(buf + bl, "%d ", e + 1);
+	ll += (bl = strlen(buf));
+    }
+    if (fl & 128) {
+	/* length of matched portion */
+	sprintf(buf + bl, "%d ", e - b);
+	ll += (bl = strlen(buf));
+    }
+    if (bl)
+	buf[bl - 1] = '\0';
+
+    rr = r = (char *)ncalloc(ll);
+
+    if (fl & 8) {
+	/* copy matched portion to new buffer */
+	for (i = b, p = s + b; i < e; i++)
+	    *rr++ = *p++;
+	t = 1;
+    }
+    if (fl & 16) {
+	/* Copy unmatched portion to buffer.  If both portions *
+	 * requested, put a space in between (why?)            */
+	if (t)
+	    *rr++ = ' ';
+	/* there may be unmatched bits at both beginning and end of string */
+	for (i = 0, p = s; i < b; i++)
+	    *rr++ = *p++;
+	for (i = e, p = s + e; i < l; i++)
+	    *rr++ = *p++;
+	t = 1;
+    }
+    *rr = '\0';
+    if (bl) {
+	/* if there was a buffer (with a numeric result), add it; *
+	 * if there was other stuff too, stick in a space first.  */
+	if (t)
+	    *rr++ = ' ';
+	strcpy(rr, buf);
+    }
+    return r;
+}
+
+/* It is called from paramsubst to get the match for ${foo#bar} etc.
+ * Bits of fl determines the required action:
+ *   bit 0: match the end instead of the beginning (% or %%)
+ *   bit 1: % or # was doubled so get the longest match
+ *   bit 2: substring match
+ *   bit 3: include the matched portion
+ *   bit 4: include the unmatched portion
+ *   bit 5: the index of the beginning
+ *   bit 6: the index of the end
+ *   bit 7: the length of the match
+ *   bit 8: match the complete string
+ * *sp points to the string we have to modify. The n'th match will be
+ * returned in *sp. ncalloc is used to get memory for the result string.
+ */
+
+/**/
+int
+getmatch(char **sp, char *pat, int fl, int n)
+{
+    Comp c;
+    char *s = *sp, *t, sav;
+    int i, j, l = strlen(*sp);
+
+    c = parsereg(pat);
+    if (!c) {
+	zerr("bad pattern: %s", pat, 0);
+	return 1;
+    }
+    if (fl & 256) {
+	i = domatch(s, c, 0);
+	*sp = get_match_ret(*sp, 0, domatch(s, c, 0) ? l : 0, fl);
+	if (! **sp && (((fl & 8) && !i) || ((fl & 16) && i)))
+	    return 0;
+	return 1;
+    }
+    switch (fl & 7) {
+    case 0:
+	/* Smallest possible match at head of string:    *
+	 * start adding characters until we get a match. */
+	for (i = 0, t = s; i <= l; i++, t++) {
+	    sav = *t;
+	    *t = '\0';
+	    if (domatch(s, c, 0) && !--n) {
+		*t = sav;
+		*sp = get_match_ret(*sp, 0, i, fl);
+		return 1;
+	    }
+	    if ((*t = sav) == Meta)
+		i++, t++;
+	}
+	break;
+
+    case 1:
+	/* Smallest possible match at tail of string:  *
+	 * move back down string until we get a match. */
+	for (t = s + l; t >= s; t--) {
+	    if (domatch(t, c, 0) && !--n) {
+		*sp = get_match_ret(*sp, t - s, l, fl);
+		return 1;
+	    }
+	    if (t > s+1 && t[-2] == Meta)
+		t--;
+	}
+	break;
+
+    case 2:
+	/* Largest possible match at head of string:        *
+	 * delete characters from end until we get a match. */
+	for (t = s + l; t > s; t--) {
+	    sav = *t;
+	    *t = '\0';
+	    if (domatch(s, c, 0) && !--n) {
+		*t = sav;
+		*sp = get_match_ret(*sp, 0, t - s, fl);
+		return 1;
+	    }
+	    *t = sav;
+	    if (t >= s+2 && t[-2] == Meta)
+		t--;
+	}
+	break;
+
+    case 3:
+	/* Largest possible match at tail of string:       *
+	 * move forward along string until we get a match. */
+	for (i = 0, t = s; i < l; i++, t++) {
+	    if (domatch(t, c, 0) && !--n) {
+		*sp = get_match_ret(*sp, i, l, fl);
+		return 1;
+	    }
+	    if (*t == Meta)
+		i++, t++;
+	}
+	break;
+
+    case 4:
+	/* Smallest at start, but matching substrings. */
+	if (domatch(s + l, c, 0) && !--n) {
+	    *sp = get_match_ret(*sp, 0, 0, fl);
+	    return 1;
+	}
+	for (i = 1; i <= l; i++) {
+	    for (t = s, j = i; j <= l; j++, t++) {
+		sav = s[j];
+		s[j] = '\0';
+		if (domatch(t, c, 0) && !--n) {
+		    s[j] = sav;
+		    *sp = get_match_ret(*sp, t - s, j, fl);
+		    return 1;
+		}
+		if ((s[j] = sav) == Meta)
+		    j++;
+		if (*t == Meta)
+		    t++;
+	    }
+	    if (s[i] == Meta)
+		i++;
+	}
+	break;
+
+    case 5:
+	/* Smallest at end, matching substrings */
+	if (domatch(s + l, c, 0) && !--n) {
+	    *sp = get_match_ret(*sp, l, l, fl);
+	    return 1;
+	}
+	for (i = l; i--;) {
+	    if (i && s[i-1] == Meta)
+		i--;
+	    for (t = s + l, j = i; j >= 0; j--, t--) {
+		sav = *t;
+		*t = '\0';
+		if (domatch(s + j, c, 0) && !--n) {
+		    *t = sav;
+		    *sp = get_match_ret(*sp, j, t - s, fl);
+		    return 1;
+		}
+		*t = sav;
+		if (t >= s+2 && t[-2] == Meta)
+		    t--;
+		if (j >= 2 && s[j-2] == Meta)
+		    j--;
+	    }
+	}
+	break;
+
+    case 6:
+	/* Largest at start, matching substrings. */
+	for (i = l; i; i--) {
+	    for (t = s, j = i; j <= l; j++, t++) {
+		sav = s[j];
+		s[j] = '\0';
+		if (domatch(t, c, 0) && !--n) {
+		    s[j] = sav;
+		    *sp = get_match_ret(*sp, t - s, j, fl);
+		    return 1;
+		}
+		if ((s[j] = sav) == Meta)
+		    j++;
+		if (*t == Meta)
+		    t++;
+	    }
+	    if (i >= 2 && s[i-2] == Meta)
+		i--;
+	}
+	if (domatch(s + l, c, 0) && !--n) {
+	    *sp = get_match_ret(*sp, 0, 0, fl);
+	    return 1;
+	}
+	break;
+
+    case 7:
+	/* Largest at end, matching substrings. */
+	for (i = 0; i < l; i++) {
+	    for (t = s + l, j = i; j >= 0; j--, t--) {
+		sav = *t;
+		*t = '\0';
+		if (domatch(s + j, c, 0) && !--n) {
+		    *t = sav;
+		    *sp = get_match_ret(*sp, j, t - s, fl);
+		    return 1;
+		}
+		*t = sav;
+		if (t >= s+2 && t[-2] == Meta)
+		    t--;
+		if (j >= 2 && s[j-2] == Meta)
+		    j--;
+	    }
+	    if (s[i] == Meta)
+		i++;
+	}
+	if (domatch(s + l, c, 0) && !--n) {
+	    *sp = get_match_ret(*sp, l, l, fl);
+	    return 1;
+	}
+	break;
+    }
+    /* munge the whole string */
+    *sp = get_match_ret(*sp, 0, 0, fl);
+    return 1;
+}
+
+/* The main entry point for matching a string str against  *
+ * a compiled pattern c.  `fist' indicates whether leading *
+ * dots are special.                                       */
+
+/**/
+int
+domatch(char *str, Comp c, int fist)
+{
+    int ret;
+    pptr = str;
+    first = fist;
+    if (*pptr == Nularg)
+	pptr++;
+    PERMALLOC {
+	ret = doesmatch(c);
+    } LASTALLOC;
+    return ret;
+}
+
+#define untok(C)  (itok(C) ? ztokens[(C) - Pound] : (C))
+
+/* See if pattern has a matching exclusion (~...) part */
+
+/**/
+static int
+excluded(Comp c, char *eptr)
+{
+    char *saves = pptr;
+    int savei = first, ret;
+
+    first = 0;
+    if (PATHADDP(c) && pathpos) {
+	VARARR(char, buf, pathpos + strlen(eptr) + 1);
+
+	strcpy(buf, pathbuf);
+	strcpy(buf + pathpos, eptr);
+	pptr = buf;
+	ret = doesmatch(c->exclude);
+    } else {
+	pptr = eptr;
+	ret = doesmatch(c->exclude);
+    }
+    if (*pptr)
+	ret = 0;
+
+    pptr = saves;
+    first = savei;
+
+    return ret;
+}
+
+struct gclose {
+    char *start;
+    char *end;
+};
+typedef struct gclose *Gclose;
+
+static int inclosure;		/* see comment in doesmatch() */
+
+/* Add a list of matches that fit the closure.  trystring is a string of
+ * the same length as the target string; a non-zero in that string
+ * indicates that we have already tried to match the patterns following
+ * the closure (c->next) at that point and failed.  This means that not
+ * only should we not bother using the corresponding match, we should
+ * also not bother going any further, since the first time we got to
+ * that position (when it was marked), we must already have failed on
+ * and backtracked over any further closure matches beyond that point.
+ */
+
+/**/
+static void
+addclosures(Comp c, LinkList closlist, int *pdone, char *trystring)
+{
+    Gclose gcnode;
+    char *opptr = pptr;
+
+    while (*pptr) {
+	if (STARP(c)) {
+	    if (trystring[(pptr+1)-opptr])
+		break;
+	    gcnode = (Gclose)zalloc(sizeof(struct gclose));
+	    gcnode->start = pptr;
+	    gcnode->end = ++pptr;
+	} else {
+	    char *saves = pptr;
+	    if (OPTIONALP(c) && *pdone >= 1)
+		return;
+	    if (!matchonce(c) || saves == pptr ||
+		trystring[pptr-opptr]) {
+		pptr = saves;
+		break;
+	    }
+	    gcnode = (Gclose)zalloc(sizeof(struct gclose));
+	    gcnode->start = saves;
+	    gcnode->end = pptr;
+	}
+	pushnode(closlist, gcnode);
+	(*pdone)++;
+    }
+}
+
+/* see if current string in pptr matches c */
+
+/**/
+static int
+doesmatch(Comp c)
+{
+    if (CLOSUREP(c)) {
+	int done, retflag = 0;
+	char *saves, *trystring, *opptr;
+	LinkList closlist;
+	Gclose gcnode;
+
+	if (first && *pptr == '.')
+	    return 0;
+
+	if (!inclosure && !c->left) {
+	    /* We are not inside another closure, and the current
+	     * pattern is a simple string.  We handle this very common
+	     * case specially: otherwise, matches like *foo* are
+	     * extremely slow.  Here, instead of backtracking, we track
+	     * forward until we get a match.  At top level, we are bound
+	     * to get there eventually, so this is OK.
+	     */
+	    char looka;
+
+	    if (STARP(c) && c->next &&
+		!c->next->left && (looka = *c->next->str) &&
+		!itok(looka)) {
+		/* Another simple optimisation for a very common case:
+		 * we are processing a * and there is
+		 * an ordinary character match next.  We look ahead for
+		 * that character, taking care of Meta bytes.
+		 */
+		while (*pptr) {
+		    for (; *pptr; pptr++) {
+			if (*pptr == Meta)
+			    pptr++;
+			else if (*pptr == looka)
+			    break;
+		    }
+		    if (!*(saves = pptr))
+			break;
+		    if (doesmatch(c->next))
+			return 1;
+		    pptr = saves+1;
+		}
+	    } else {
+		/* Standard track-forward code */
+		for (done = 0; ; done++) {
+		    saves = pptr;
+		    if ((done || ONEHASHP(c) || OPTIONALP(c)) &&
+			((!c->next && (!LASTP(c) || !*pptr)) ||
+			 (c->next && doesmatch(c->next))))
+			return 1;
+		    if (done && OPTIONALP(c))
+			return 0;
+		    pptr = saves;
+		    first = 0;
+		    if (STARP(c)) {
+			if (!*pptr)
+			    return 0;
+			pptr++;
+		    } else if (!matchonce(c) || pptr == saves)
+			return 0;
+		}
+	    }
+	    return 0;
+	}
+	/* The full, gory backtracking code is now necessary. */
+	inclosure++;
+	closlist = newlinklist();
+	trystring = zcalloc(strlen(pptr)+1);
+	opptr = pptr;
+
+	/* Start by making a list where each match is as long
+	 * as possible.  We later have to take account of the
+	 * fact that earlier matches may be too long.
+	 */
+	done = 0;
+	addclosures(c, closlist, &done, trystring);
+	for (;;) {
+	    if (TWOHASHP(c) && !done)
+		break;
+	    saves = pptr;
+	    /* do we really want this LASTP here?? */
+	    if ((!c->next && (!LASTP(c) || !*pptr)) ||
+		(c->next && doesmatch(c->next))) {
+		retflag = 1;
+		break;
+	    }
+	    trystring[saves-opptr] = 1;
+	    /*
+	     * If we failed, the first thing to try is whether we can
+	     * shorten the match using the last pattern in the closure.
+	     */
+	    gcnode = firstnode(closlist) ? peekfirst(closlist) : NULL;
+	    if (gcnode && --gcnode->end > gcnode->start
+		&& (gcnode->end[-1] != Meta ||
+		    --gcnode->end > gcnode->start)) {
+		char savec = *gcnode->end;
+		*gcnode->end = '\0';
+		pptr = gcnode->start;
+		if (matchonce(c) && pptr != gcnode->start
+		    && !trystring[pptr-opptr]) {
+		    *gcnode->end = savec;
+		    gcnode->end = pptr;
+		    /* Try again to construct a list based on
+		     * this new position
+		     */
+		    addclosures(c, closlist, &done, trystring+(pptr-opptr));
+		    continue;
+		}
+		*gcnode->end = savec;
+	    }
+	    /* We've now exhausted the possibilities with that match,
+	     * backtrack to the previous.
+	     */
+	    if ((gcnode = (Gclose)getlinknode(closlist))) {
+		pptr = gcnode->start;
+		zfree(gcnode, sizeof(struct gclose));
+		done--;
+	    } else
+		break;
+	}
+	freelinklist(closlist, free);
+	zfree(trystring, strlen(opptr)+1);
+	inclosure--;
+
+	return retflag;
+    } else
+	return matchonce(c);
+}
+
+/**/
+static int
+posix_range(char **patptr, int ch)
+{
+    /* Match POSIX ranges, which correspond to ctype macros,  *
+     * e.g. [:alpha:] -> isalpha.  It just doesn't seem worth * 
+     * the palaver of creating a hash table for this.         */
+    char *start = *patptr;
+    int len;
+
+    /* we made sure in parsecomp() there was a ':' to search for */
+    *patptr = strchr(start, ':');
+    len = (*patptr)++ - start;
+
+    if (!strncmp(start, "alpha", len))
+	return isalpha(ch);
+    if (!strncmp(start, "alnum", len))
+	return isalnum(ch);
+    if (!strncmp(start, "blank", len))
+	return ch == ' ' || ch == '\t';
+    if (!strncmp(start, "cntrl", len))
+	return iscntrl(ch);
+    if (!strncmp(start, "digit", len))
+	return isdigit(ch);
+    if (!strncmp(start, "graph", len))
+	return isgraph(ch);
+    if (!strncmp(start, "lower", len))
+	return islower(ch);
+    if (!strncmp(start, "print", len))
+	return isprint(ch);
+    if (!strncmp(start, "punct", len))
+	return ispunct(ch);
+    if (!strncmp(start, "space", len))
+	return isspace(ch);
+    if (!strncmp(start, "upper", len))
+	return isupper(ch);
+    if (!strncmp(start, "xdigit", len))
+	return isxdigit(ch);
+    return 0;
+}
+
+/**/
+static void
+rangematch(char **patptr, int ch, int rchar)
+{
+    /* Check for a character in a [...] or [^...].  The [ *
+     * and optional ^ have already been skipped.          */
+
+    char *pat = *patptr;
+#ifdef HAVE_STRCOLL
+    char l_buf[2], r_buf[2], ch_buf[2];
+
+    ch_buf[0] = ch;
+    l_buf[1] = r_buf[1] = ch_buf[1] = '\0';
+#endif
+
+#define PAT(X) (pat[X] == Meta ? pat[(X)+1] ^ 32 : untok(pat[X]))
+#define PPAT(X) (pat[(X)-1] == Meta ? pat[X] ^ 32 : untok(pat[X]))
+
+    for (pat++; *pat != Outbrack && *pat;
+	 *pat == Meta ? pat += 2 : pat++) {
+	if (*pat == Inbrack) {
+	    /* Inbrack can only occur inside a range if we found [:...:]. */
+	    pat += 2;
+	    if (posix_range(&pat, ch))
+		break;
+	} else if (*pat == '-' && pat[-1] != rchar &&
+		   pat[1] != Outbrack) {
+#ifdef HAVE_STRCOLL
+	    l_buf[0] = PPAT(-1);
+	    r_buf[0] = PAT(1);
+	    if (strcoll(l_buf, ch_buf) <= 0 &&
+		strcoll(ch_buf, r_buf) <= 0)
+#else
+		if (PPAT(-1) <= ch && PAT(1) >= ch)
+#endif
+		    break;
+	} else if (ch == PAT(0))
+	    break;
+    }
+
+    *patptr = pat;
+}
+
+/**/
+static int
+matchonce(Comp c)
+{
+    char *pat = c->str;
+    for (;;) {
+	/* loop until success or failure of pattern */
+	if (!pat || !*pat) {
+	    /* No current pattern (c->str). */
+	    char *saves;
+	    int savei;
+
+	    if (errflag)
+		return 0;
+	    /* Remember state in case we need to go back and   *
+	     * check for exclusion of pattern or alternatives. */
+	    saves = pptr;
+	    savei = first;
+	    /* Loop over alternatives with exclusions: (foo~bar|...). *
+	     * Exclusions apply to the pattern in c->left.            */
+	    if (c->left || c->right) {
+		int ret = 0, ret2 = 0;
+		if (c->exclude) {
+		    char *exclend = 0;
+
+		    /* We may need to back up on things like `(*~foo)'
+		     * if the `*' matched `foo' but needs to match `fo'.
+		     * exclend marks the end of the shortened text.  We
+		     * need to restore it to match the tail.
+		     * We set `inclosure' because we need the more
+		     * sophisticated code in doesmatch() for any nested
+		     * closures.
+		     */
+		    inclosure++;
+
+		    while (!exclend || exclend >= pptr) {
+			char exclsav = 0;
+			if (exclend) {
+			     exclsav = *exclend;
+			    *exclend = '\0';
+			 }
+			if ((ret = doesmatch(c->left))) {
+			    if (exclend)
+				*exclend = exclsav;
+			    exclsav = *(exclend = pptr);
+			    *exclend = '\0';
+			    ret2 = !excluded(c, saves);
+			}
+			if (exclend)
+			    *exclend = exclsav;
+
+			if (!ret)
+			    break;
+			if ((ret = ret2 &&
+			     ((!c->next && (!LASTP(c) || !*pptr))
+			      || (c->next && doesmatch(c->next)))) ||
+			    (!c->next && LASTP(c)))
+			    break;
+			/* Back up if necessary: exclend gives the position
+			 * of the end of the match we are excluding,
+			 * so only try to match to there.
+			 */
+			exclend--;
+			pptr = saves;
+		    }
+		    inclosure--;
+		    if (ret)
+			return 1;
+		} else
+		    ret = doesmatch(c->left);
+		ret2 = 0;
+		if (c->right && (!ret || inclosure)) {
+		    /* If in a closure, we always want the longest match. */
+		    char *newpptr = pptr;
+		    pptr = saves;
+		    first = savei;
+		    ret2 = doesmatch(c->right);
+		    if (ret && (!ret2 || pptr < newpptr))
+			pptr = newpptr;
+		}
+		if (!ret && !ret2)
+		    return 0;
+	    }
+	    if (CLOSUREP(c))
+		return 1;
+	    if (!c->next)	/* no more patterns left */
+		return (!LASTP(c) || !*pptr);
+	    /* optimisation when next pattern is not a closure */
+	    if (!CLOSUREP(c->next)) {
+		c = c->next;
+		pat = c->str;
+		continue;
+	    }
+	    return doesmatch(c->next);
+	}
+	/* Don't match leading dot if first is set */
+	if (first && *pptr == '.' && *pat != '.')
+	    return 0;
+	if (*pat == Star) {	/* final * is not expanded to ?#; returns success */
+	    while (*pptr)
+		pptr++;
+	    return 1;
+	}
+	first = 0;		/* finished checking start of pattern */
+	if (*pat == Quest && *pptr) {
+	    /* match exactly one character */
+	    if (*pptr == Meta)
+		pptr++;
+	    pptr++;
+	    pat++;
+	    continue;
+	}
+	if (*pat == Inbrack) {
+	    /* Match groups of characters */
+	    char ch;
+
+	    if (!*pptr)
+		break;
+	    ch = *pptr == Meta ? pptr[1] ^ 32 : *pptr;
+	    if (pat[1] == Hat || pat[1] == '^' || pat[1] == '!') {
+		/* group is negated */
+		*++pat = Hat;
+		rangematch(&pat, ch, Hat);
+		DPUTS(!*pat, "BUG: something is very wrong in doesmatch()");
+		if (*pat != Outbrack)
+		    break;
+		pat++;
+		*pptr == Meta ? pptr += 2 : pptr++;
+		continue;
+	    } else {
+		/* pattern is not negated (affirmed? asserted?) */
+		rangematch(&pat, ch, Inbrack);
+		DPUTS(!pat || !*pat, "BUG: something is very wrong in doesmatch()");
+		if (*pat == Outbrack)
+		    break;
+		for (*pptr == Meta ? pptr += 2 : pptr++;
+		     *pat != Outbrack; pat++);
+		pat++;
+		continue;
+	    }
+	}
+	if (*pat == Inang) {
+	    /* Numeric globbing. */
+	    unsigned long t1, t2, t3;
+	    char *ptr;
+
+	    if (!idigit(*pptr))
+		break;
+	    if (*++pat == Outang || 
+		(*pat == '-' && pat[1] == Outang && ++pat)) {
+		/* <> or <->:  any number matches */
+		while (idigit(*++pptr));
+		pat++;
+	    } else {
+		/* Flag that there is no upper limit */
+		int not3 = 0;
+		char *opptr = pptr;
+		/*
+		 * Form is <a-b>, where a or b are numbers or blank.
+		 * t1 = number supplied:  must be positive, so use
+		 * unsigned arithmetic.
+		 */
+		t1 = (unsigned long)zstrtol(pptr, &ptr, 10);
+		pptr = ptr;
+		/* t2 = lower limit */
+		if (idigit(*pat))
+		    t2 = (unsigned long)zstrtol(pat, &ptr, 10);
+		else
+		    t2 = 0, ptr = pat;
+		if (*ptr != '-' || (not3 = (ptr[1] == Outang)))
+				/* exact match or no upper limit */
+		    t3 = t2, pat = ptr + not3;
+		else		/* t3 = upper limit */
+		    t3 = (unsigned long)zstrtol(ptr + 1, &pat, 10);
+		DPUTS(*pat != Outang, "BUG: wrong internal range pattern");
+		pat++;
+		/*
+		 * If the number found is too large for the pattern,
+		 * try matching just the first part.  This way
+		 * we always get the longest possible match.
+		 */
+		while (!not3 && t1 > t3 && pptr > opptr+1) {
+		  pptr--;
+		  t1 /= 10;
+		}
+		if (t1 < t2 || (!not3 && t1 > t3))
+		    break;
+	    }
+	    continue;
+	}
+	if (*pptr == *pat) {
+	    /* just plain old characters */
+	    pptr++;
+	    pat++;
+	    continue;
+	}
+	break;
+    }
+    return 0;
+}
+
+/* turn a string into a Comp struct:  this doesn't treat / specially */
+
+/**/
+Comp
+parsereg(char *str)
+{
+    remnulargs(str);
+    mode = 1;			/* no path components */
+    pptr = str;
+    tail = NULL;
+    return parsecompsw(GF_TOPLEV);
+}
+
+/* blindly turn a string into a tokenised expression without lexing */
+
+/**/
+void
+tokenize(char *s)
+{
+    char *t;
+    int bslash = 0;
+
+    for (; *s; s++) {
+      cont:
+	switch (*s) {
+	case Bnull:
+	case '\\':
+	    if (bslash) {
+		s[-1] = Bnull;
+		break;
+	    }
+	    bslash = 1;
+	    continue;
+	case '<':
+	    if (isset(SHGLOB))
+		break;
+	    if (bslash) {
+		s[-1] = Bnull;
+		break;
+	    }
+	    t = s;
+	    while (idigit(*++s));
+	    if (*s != '-')
+		goto cont;
+	    while (idigit(*++s));
+	    if (*s != '>')
+		goto cont;
+	    *t = Inang;
+	    *s = Outang;
+	    break;
+	case '^':
+	case '#':
+	case '~':
+	    if (unset(EXTENDEDGLOB))
+		break;
+	case '(':
+	case '|':
+	case ')':
+	    if (isset(SHGLOB))
+		break;
+	case '[':
+	case ']':
+	case '*':
+	case '?':
+	    for (t = ztokens; *t; t++)
+		if (*t == *s) {
+		    if (bslash)
+			s[-1] = Bnull;
+		    else
+			*s = (t - ztokens) + Pound;
+		    break;
+		}
+	}
+	bslash = 0;
+    }
+}
+
+/* remove unnecessary Nulargs */
+
+/**/
+void
+remnulargs(char *s)
+{
+    int nl = *s;
+    char *t = s;
+
+    while (*s)
+	if (INULL(*s))
+	    chuck(s);
+	else
+	    s++;
+    if (!*t && nl) {
+	t[0] = Nularg;
+	t[1] = '\0';
+    }
+}
+
+/* qualifier functions:  mostly self-explanatory, see glob(). */
+
+/* device number */
+
+/**/
+static int
+qualdev(struct stat *buf, long dv)
+{
+    return buf->st_dev == dv;
+}
+
+/* number of hard links to file */
+
+/**/
+static int
+qualnlink(struct stat *buf, long ct)
+{
+    return (range < 0 ? buf->st_nlink < ct :
+	    range > 0 ? buf->st_nlink > ct :
+	    buf->st_nlink == ct);
+}
+
+/* user ID */
+
+/**/
+static int
+qualuid(struct stat *buf, long uid)
+{
+    return buf->st_uid == uid;
+}
+
+/* group ID */
+
+/**/
+static int
+qualgid(struct stat *buf, long gid)
+{
+    return buf->st_gid == gid;
+}
+
+/* device special file? */
+
+/**/
+static int
+qualisdev(struct stat *buf, long junk)
+{
+    return S_ISBLK(buf->st_mode) || S_ISCHR(buf->st_mode);
+}
+
+/* block special file? */
+
+/**/
+static int
+qualisblk(struct stat *buf, long junk)
+{
+    return S_ISBLK(buf->st_mode);
+}
+
+/* character special file? */
+
+/**/
+static int
+qualischr(struct stat *buf, long junk)
+{
+    return S_ISCHR(buf->st_mode);
+}
+
+/* directory? */
+
+/**/
+static int
+qualisdir(struct stat *buf, long junk)
+{
+    return S_ISDIR(buf->st_mode);
+}
+
+/* FIFO? */
+
+/**/
+static int
+qualisfifo(struct stat *buf, long junk)
+{
+    return S_ISFIFO(buf->st_mode);
+}
+
+/* symbolic link? */
+
+/**/
+static int
+qualislnk(struct stat *buf, long junk)
+{
+    return S_ISLNK(buf->st_mode);
+}
+
+/* regular file? */
+
+/**/
+static int
+qualisreg(struct stat *buf, long junk)
+{
+    return S_ISREG(buf->st_mode);
+}
+
+/* socket? */
+
+/**/
+static int
+qualissock(struct stat *buf, long junk)
+{
+    return S_ISSOCK(buf->st_mode);
+}
+
+/* given flag is set in mode */
+
+/**/
+static int
+qualflags(struct stat *buf, long mod)
+{
+    return mode_to_octal(buf->st_mode) & mod;
+}
+
+/* mode matches number supplied exactly  */
+
+/**/
+static int
+qualeqflags(struct stat *buf, long mod)
+{
+    return mode_to_octal(buf->st_mode) == mod;
+}
+
+/* regular executable file? */
+
+/**/
+static int
+qualiscom(struct stat *buf, long mod)
+{
+    return S_ISREG(buf->st_mode) && (buf->st_mode & S_IXUGO);
+}
+
+/* size in required range? */
+
+/**/
+static int
+qualsize(struct stat *buf, long size)
+{
+    unsigned long scaled = buf->st_size;
+
+    switch (units) {
+    case TT_POSIX_BLOCKS:
+	scaled += 511l;
+	scaled /= 512l;
+	break;
+    case TT_KILOBYTES:
+	scaled += 1023l;
+	scaled /= 1024l;
+	break;
+    case TT_MEGABYTES:
+	scaled += 1048575l;
+	scaled /= 1048576l;
+	break;
+    }
+
+    return (range < 0 ? scaled < (unsigned long) size :
+	    range > 0 ? scaled > (unsigned long) size :
+	    scaled == (unsigned long) size);
+}
+
+/* time in required range? */
+
+/**/
+static int
+qualtime(struct stat *buf, long days)
+{
+    time_t now, diff;
+
+    time(&now);
+    diff = now - (amc == 0 ? buf->st_atime : amc == 1 ? buf->st_mtime :
+		  buf->st_ctime);
+    /* handle multipliers indicating units */
+    switch (units) {
+    case TT_DAYS:
+	diff /= 86400l;
+	break;
+    case TT_HOURS:
+	diff /= 3600l;
+	break;
+    case TT_MINS:
+	diff /= 60l;
+	break;
+    case TT_WEEKS:
+	diff /= 604800l;
+	break;
+    case TT_MONTHS:
+	diff /= 2592000l;
+	break;
+    }
+
+    return (range < 0 ? diff < days :
+	    range > 0 ? diff > days :
+	    diff == days);
+}
diff --git a/Src/hashtable.c b/Src/hashtable.c
new file mode 100644
index 000000000..4adf3904d
--- /dev/null
+++ b/Src/hashtable.c
@@ -0,0 +1,1285 @@
+/*
+ * hashtable.c - hash tables
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "../config.h"
+
+#ifdef ZSH_HASH_DEBUG
+# define HASHTABLE_DEBUG_MEMBERS \
+    /* Members of struct hashtable used for debugging hash tables */ \
+    HashTable next, last;	/* linked list of all hash tables           */ \
+    char *tablename;		/* string containing name of the hash table */ \
+    PrintTableStats printinfo;	/* pointer to function to print table stats */
+#else /* !ZSH_HASH_DEBUG */
+# define HASHTABLE_DEBUG_MEMBERS
+#endif /* !ZSH_HASH_DEBUG */
+
+#define HASHTABLE_INTERNAL_MEMBERS \
+    ScanStatus scan;		/* status of a scan over this hashtable     */ \
+    HASHTABLE_DEBUG_MEMBERS
+
+typedef struct scanstatus *ScanStatus;
+
+#include "zsh.mdh"
+#include "hashtable.pro"
+
+/* Structure for recording status of a hashtable scan in progress.  When a *
+ * scan starts, the .scan member of the hashtable structure points to one  *
+ * of these.  That member being non-NULL disables resizing of the          *
+ * hashtable (when adding elements).  When elements are deleted, the       *
+ * contents of this structure is used to make sure the scan won't stumble  *
+ * into the deleted element.                                               */
+
+struct scanstatus {
+    int sorted;
+    union {
+	struct {
+	    HashNode *tab;
+	    int ct;
+	} s;
+	HashNode u;
+    } u;
+};
+
+/********************************/
+/* Generic Hash Table functions */
+/********************************/
+
+#ifdef ZSH_HASH_DEBUG
+static HashTable firstht, lastht;
+#endif /* ZSH_HASH_DEBUG */
+
+/* Generic hash function */
+
+/**/
+unsigned
+hasher(char *str)
+{
+    unsigned hashval = 0;
+
+    while (*str)
+	hashval += (hashval << 5) + ((unsigned) *str++);
+
+    return hashval;
+}
+
+/* Get a new hash table */
+
+/**/
+HashTable
+newhashtable(int size, char const *name, PrintTableStats printinfo)
+{
+    HashTable ht;
+
+    ht = (HashTable) zcalloc(sizeof *ht);
+#ifdef ZSH_HASH_DEBUG
+    ht->next = NULL;
+    if(!firstht)
+	firstht = ht;
+    ht->last = lastht;
+    if(lastht)
+	lastht->next = ht;
+    lastht = ht;
+    ht->printinfo = printinfo ? printinfo : printhashtabinfo;
+    ht->tablename = ztrdup(name);
+#endif /* ZSH_HASH_DEBUG */
+    ht->nodes = (HashNode *) zcalloc(size * sizeof(HashNode));
+    ht->hsize = size;
+    ht->ct = 0;
+    ht->scan = NULL;
+    return ht;
+}
+
+/* Delete a hash table.  After this function has been used, any *
+ * existing pointers to the hash table are invalid.             */
+
+/**/
+void
+deletehashtable(HashTable ht)
+{
+    ht->emptytable(ht);
+#ifdef ZSH_HASH_DEBUG
+    if(ht->next)
+	ht->next->last = ht->last;
+    else
+	lastht = ht->last;
+    if(ht->last)
+	ht->last->next = ht->next;
+    else
+	firstht = ht->next;
+#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  *
+ * 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  *
+ * is now greater than twice the number of hash values, *
+ * the table is then expanded.                          */
+
+/**/
+void
+addhashnode(HashTable ht, char *nam, void *nodeptr)
+{
+    unsigned hashval;
+    HashNode hn, hp, hq;
+
+    hn = (HashNode) nodeptr;
+    hn->nam = nam;
+
+    hashval = ht->hash(hn->nam) % ht->hsize;
+    hp = ht->nodes[hashval];
+
+    /* check if this is the first node for this hash value */
+    if (!hp) {
+	hn->next = NULL;
+	ht->nodes[hashval] = hn;
+	if (++ht->ct >= ht->hsize * 2 && !ht->scan)
+	    expandhashtable(ht);
+	return;
+    }
+
+    /* else check if the first node contains the same key */
+    if (!strcmp(hp->nam, hn->nam)) {
+	ht->nodes[hashval] = hn;
+	replacing:
+	hn->next = hp->next;
+	if(ht->scan)
+	    if(ht->scan->sorted) {
+		HashNode *tab = ht->scan->u.s.tab;
+		int i;
+		for(i = ht->scan->u.s.ct; i--; )
+		    if(tab[i] == hp)
+			tab[i] = hn;
+	    } else if(ht->scan->u.u == hp)
+		ht->scan->u.u = hn;
+	ht->freenode(hp);
+	return;
+    }
+
+    /* 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)) {
+	    hq->next = hn;
+	    goto replacing;
+	}
+    }
+
+    /* else just add it at the front of the list */
+    hn->next = ht->nodes[hashval];
+    ht->nodes[hashval] = hn;
+    if (++ht->ct >= ht->hsize * 2 && !ht->scan)
+        expandhashtable(ht);
+}
+
+/* Get an enabled entry in a hash table.  *
+ * If successful, it returns a pointer to *
+ * the hashnode.  If the node is DISABLED *
+ * or isn't found, it returns NULL        */
+
+/**/
+HashNode
+gethashnode(HashTable ht, char *nam)
+{
+    unsigned hashval;
+    HashNode hp;
+
+    hashval = ht->hash(nam) % ht->hsize;
+    for (hp = ht->nodes[hashval]; hp; hp = hp->next) {
+	if (!strcmp(hp->nam, nam)) {
+	    if (hp->flags & DISABLED)
+		return NULL;
+	    else
+		return hp;
+	}
+    }
+    return NULL;
+}
+
+/* Get an entry in a hash table.  It will *
+ * ignore the DISABLED flag and return a  *
+ * pointer to the hashnode if found, else *
+ * it returns NULL.                       */
+
+/**/
+HashNode
+gethashnode2(HashTable ht, char *nam)
+{
+    unsigned hashval;
+    HashNode hp;
+
+    hashval = ht->hash(nam) % ht->hsize;
+    for (hp = ht->nodes[hashval]; hp; hp = hp->next) {
+	if (!strcmp(hp->nam, nam))
+	    return hp;
+    }
+    return NULL;
+}
+
+/* Remove an entry from a hash table.           *
+ * If successful, it removes the node from the  *
+ * table and returns a pointer to it.  If there *
+ * is no such node, then it returns NULL        */
+
+/**/
+HashNode
+removehashnode(HashTable ht, char *nam)
+{
+    unsigned hashval;
+    HashNode hp, hq;
+
+    hashval = ht->hash(nam) % ht->hsize;
+    hp = ht->nodes[hashval];
+
+    /* if no nodes at this hash value, return NULL */
+    if (!hp)
+	return NULL;
+
+    /* else check if the key in the first one matches */
+    if (!strcmp(hp->nam, nam)) {
+	ht->nodes[hashval] = hp->next;
+	gotit:
+	ht->ct--;
+	if(ht->scan)
+	    if(ht->scan->sorted) {
+		HashNode *tab = ht->scan->u.s.tab;
+		int i;
+		for(i = ht->scan->u.s.ct; i--; )
+		    if(tab[i] == hp)
+			tab[i] = NULL;
+	    } else if(ht->scan->u.u == hp)
+		ht->scan->u.u = hp->next;
+	return hp;
+    }
+
+    /* else run through the list and check the rest of the keys */
+    hq = hp;
+    hp = hp->next;
+    for (; hp; hq = hp, hp = hp->next) {
+	if (!strcmp(hp->nam, nam)) {
+	    hq->next = hp->next;
+	    goto gotit;
+	}
+    }
+
+    /* else it is not in the list, so return NULL */
+    return NULL;
+}
+
+/* Disable a node in a hash table */
+
+/**/
+void
+disablehashnode(HashNode hn, int flags)
+{
+    hn->flags |= DISABLED;
+}
+
+/* Enable a node in a hash table */
+
+/**/
+void
+enablehashnode(HashNode hn, int flags)
+{
+    hn->flags &= ~DISABLED;
+}
+
+/* Compare two hash table entries */
+
+/**/
+static int
+hnamcmp(const void *ap, const void *bp)
+{
+    HashNode a = *(HashNode *)ap;
+    HashNode b = *(HashNode *)bp;
+    return ztrcmp((unsigned char *) a->nam, (unsigned char *) b->nam);
+}
+
+/* Scan the nodes in a hash table and execute scanfunc on nodes based on
+ * the flags that are set/unset.  scanflags is passed unchanged to
+ * scanfunc (if executed).
+ *
+ * If sorted != 0, then sort entries of hash table before scanning.
+ * If flags1 > 0, then execute scanfunc on a node only if at least one of
+ *                these flags is set.
+ * If flags2 > 0, then execute scanfunc on a node only if all of
+ *                these flags are NOT set.
+ * The conditions above for flags1/flags2 must both be true.
+ *
+ * It is safe to add, remove or replace hash table elements from within
+ * the scanfunc.  Replaced elements will appear in the scan exactly once,
+ * the new version if it was not scanned before the replacement was made.
+ * Added elements might or might not appear in the scan.
+ */
+
+/**/
+void
+scanhashtable(HashTable ht, int sorted, int flags1, int flags2, ScanFunc scanfunc, int scanflags)
+{
+    struct scanstatus st;
+
+    if (sorted) {
+	int i, ct = ht->ct;
+	VARARR(HashNode, hnsorttab, ct);
+	HashNode *htp, hn;
+
+	for (htp = hnsorttab, i = 0; i < ht->hsize; i++)
+	    for (hn = ht->nodes[i]; hn; hn = hn->next)
+		*htp++ = hn;
+	qsort((void *)hnsorttab, ct, sizeof(HashNode), hnamcmp);
+
+	st.sorted = 1;
+	st.u.s.tab = hnsorttab;
+	st.u.s.ct = ct;
+	ht->scan = &st;
+
+	for (htp = hnsorttab, i = 0; i < ct; i++, htp++)
+	    if (*htp && ((*htp)->flags & flags1) + !flags1 &&
+		!((*htp)->flags & flags2))
+		scanfunc(*htp, scanflags);
+
+	ht->scan = NULL;
+    } else {
+	int i, hsize = ht->hsize;
+	HashNode *nodes = ht->nodes;
+
+	st.sorted = 0;
+	ht->scan = &st;
+
+	for (i = 0; i < hsize; i++)
+	    for (st.u.u = nodes[i]; st.u.u; ) {
+		HashNode hn = st.u.u;
+		st.u.u = st.u.u->next;
+		if ((hn->flags & flags1) + !flags1 && !(hn->flags & flags2))
+		    scanfunc(hn, scanflags);
+	    }
+
+	ht->scan = NULL;
+    }
+}
+
+/* Scan all nodes in a hash table and executes scanfunc on the *
+ * nodes which meet all the following criteria:                *
+ * The hash key must match the glob pattern given by `com'.    *
+ * If (flags1 > 0), then any flag in flags1 must be set.       *
+ * If (flags2 > 0), then all flags in flags2 must NOT be set.  *
+ *                                                             *
+ * scanflags is passed unchanged to scanfunc (if executed).    *
+ * The return value is the number of matches.                  */
+
+/**/
+int
+scanmatchtable(HashTable ht, Comp com, int flags1, int flags2, ScanFunc scanfunc, int scanflags)
+{
+    int i, hsize = ht->hsize;
+    HashNode *nodes = ht->nodes;
+    int match = 0;
+    struct scanstatus st;
+
+    st.sorted = 0;
+    ht->scan = &st;
+
+    for (i = 0; i < hsize; i++)
+	for (st.u.u = nodes[i]; st.u.u; ) {
+	    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))
+		scanfunc(hn, scanflags);
+		match++;
+	}
+
+    ht->scan = NULL;
+
+    return match;
+}
+
+/* Expand hash tables when they get too many entries. *
+ * The new size is 4 times the previous size.         */
+
+/**/
+static void
+expandhashtable(HashTable ht)
+{
+    struct hashnode **onodes, **ha, *hn, *hp;
+    int i, osize;
+
+    osize = ht->hsize;
+    onodes = ht->nodes;
+
+    ht->hsize = osize * 4;
+    ht->nodes = (HashNode *) zcalloc(ht->hsize * sizeof(HashNode));
+    ht->ct = 0;
+
+    /* scan through the old list of nodes, and *
+     * rehash them into the new list of nodes  */
+    for (i = 0, ha = onodes; i < osize; i++, ha++) {
+	for (hn = *ha; hn;) {
+	    hp = hn->next;
+	    ht->addnode(ht, hn->nam, hn);
+	    hn = hp;
+	}
+    }
+    zfree(onodes, osize * sizeof(HashNode));
+}
+
+/* Empty the hash table and resize it if necessary */
+
+/**/
+static void
+resizehashtable(HashTable ht, int newsize)
+{
+    struct hashnode **ha, *hn, *hp;
+    int i;
+
+    /* free all the hash nodes */
+    ha = ht->nodes;
+    for (i = 0; i < ht->hsize; i++, ha++) {
+	for (hn = *ha; hn;) {
+	    hp = hn->next;
+	    ht->freenode(hn);
+	    hn = hp;
+	}
+    }
+
+    /* If new size desired is different from current size, *
+     * we free it and allocate a new nodes array.          */
+    if (ht->hsize != newsize) {
+	zfree(ht->nodes, ht->hsize * sizeof(HashNode));
+	ht->nodes = (HashNode *) zcalloc(newsize * sizeof(HashNode));
+	ht->hsize = newsize;
+    } else {
+	/* else we just re-zero the current nodes array */
+	memset(ht->nodes, 0, newsize * sizeof(HashNode));
+    }
+
+    ht->ct = 0;
+}
+
+/* Generic method to empty a hash table */
+
+/**/
+void
+emptyhashtable(HashTable ht)
+{
+    resizehashtable(ht, ht->hsize);
+}
+
+#ifdef ZSH_HASH_DEBUG
+
+/* Print info about hash table */
+
+#define MAXDEPTH 7
+
+/**/
+static void
+printhashtabinfo(HashTable ht)
+{
+    HashNode hn;
+    int chainlen[MAXDEPTH + 1];
+    int i, tmpcount, total;
+
+    printf("name of table   : %s\n",   ht->tablename);
+    printf("size of nodes[] : %d\n",   ht->hsize);
+    printf("number of nodes : %d\n\n", ht->ct);
+
+    memset(chainlen, 0, sizeof(chainlen));
+
+    /* count the number of nodes just to be sure */
+    total = 0;
+    for (i = 0; i < ht->hsize; i++) {
+	tmpcount = 0;
+	for (hn = ht->nodes[i]; hn; hn = hn->next)
+	    tmpcount++;
+	if (tmpcount >= MAXDEPTH)
+	    chainlen[MAXDEPTH]++;
+	else
+	    chainlen[tmpcount]++;
+	total += tmpcount;
+    }
+
+    for (i = 0; i < MAXDEPTH; i++)
+	printf("number of hash values with chain of length %d  : %4d\n", i, chainlen[i]);
+    printf("number of hash values with chain of length %d+ : %4d\n", MAXDEPTH, chainlen[MAXDEPTH]);
+    printf("total number of nodes                         : %4d\n", total);
+}
+
+/**/
+int
+bin_hashinfo(char *nam, char **args, char *ops, int func)
+{
+    HashTable ht;
+    printf("----------------------------------------------------\n");
+    for(ht = firstht; ht; ht = ht->next) {
+	ht->printinfo(ht);
+	printf("----------------------------------------------------\n");
+    }
+    return 0;
+}
+
+#endif /* ZSH_HASH_DEBUG */
+
+/********************************/
+/* Command Hash Table Functions */
+/********************************/
+
+/* hash table containing external commands */
+ 
+/**/
+HashTable cmdnamtab;
+ 
+/* how far we've hashed the PATH so far */
+ 
+/**/
+char **pathchecked;
+ 
+/* Create a new command hash table */
+ 
+/**/
+void
+createcmdnamtable(void)
+{
+    cmdnamtab = newhashtable(201, "cmdnamtab", NULL);
+
+    cmdnamtab->hash        = hasher;
+    cmdnamtab->emptytable  = emptycmdnamtable;
+    cmdnamtab->filltable   = fillcmdnamtable;
+    cmdnamtab->addnode     = addhashnode;
+    cmdnamtab->getnode     = gethashnode2;
+    cmdnamtab->getnode2    = gethashnode2;
+    cmdnamtab->removenode  = removehashnode;
+    cmdnamtab->disablenode = NULL;
+    cmdnamtab->enablenode  = NULL;
+    cmdnamtab->freenode    = freecmdnamnode;
+    cmdnamtab->printnode   = printcmdnamnode;
+
+    pathchecked = path;
+}
+
+/**/
+static void
+emptycmdnamtable(HashTable ht)
+{
+    emptyhashtable(ht);
+    pathchecked = path;
+}
+
+/* Add all commands in a given directory *
+ * to the command hashtable.             */
+
+/**/
+void
+hashdir(char **dirp)
+{
+    Cmdnam cn;
+    DIR *dir;
+    char *fn;
+
+    if (isrelative(*dirp) || !(dir = opendir(unmeta(*dirp))))
+	return;
+
+    while ((fn = zreaddir(dir, 1))) {
+	if (!cmdnamtab->getnode(cmdnamtab, fn)) {
+	    cn = (Cmdnam) zcalloc(sizeof *cn);
+	    cn->flags = 0;
+	    cn->u.name = dirp;
+	    cmdnamtab->addnode(cmdnamtab, ztrdup(fn), cn);
+	}
+    }
+    closedir(dir);
+}
+
+/* Go through user's PATH and add everything to *
+ * the command hashtable.                       */
+
+/**/
+static void
+fillcmdnamtable(HashTable ht)
+{
+    char **pq;
+ 
+    for (pq = pathchecked; *pq; pq++)
+	hashdir(pq);
+
+    pathchecked = pq;
+}
+
+/**/
+static void
+freecmdnamnode(HashNode hn)
+{
+    Cmdnam cn = (Cmdnam) hn;
+ 
+    zsfree(cn->nam);
+    if (cn->flags & HASHED)
+	zsfree(cn->u.cmd);
+ 
+    zfree(cn, sizeof(struct cmdnam));
+}
+
+/* Print an element of the cmdnamtab hash table (external command) */
+ 
+/**/
+static void
+printcmdnamnode(HashNode hn, int printflags)
+{
+    Cmdnam cn = (Cmdnam) hn;
+
+    if (printflags & PRINT_WHENCE_WORD) {
+	printf("%s: %s\n", cn->nam, (cn->flags & HASHED) ? 
+	       "hashed" : "command");
+	return;
+    }
+
+    if ((printflags & PRINT_WHENCE_CSH) || (printflags & PRINT_WHENCE_SIMPLE)) {
+	if (cn->flags & HASHED) {
+	    zputs(cn->u.cmd, stdout);
+	    putchar('\n');
+	} else {
+	    zputs(*(cn->u.name), stdout);
+	    putchar('/');
+	    zputs(cn->nam, stdout);
+	    putchar('\n');
+	}
+	return;
+    }
+
+    if (printflags & PRINT_WHENCE_VERBOSE) {
+	if (cn->flags & HASHED) {
+	    nicezputs(cn->nam, stdout);
+	    printf(" is hashed to ");
+	    nicezputs(cn->u.cmd, stdout);
+	    putchar('\n');
+	} else {
+	    nicezputs(cn->nam, stdout);
+	    printf(" is ");
+	    nicezputs(*(cn->u.name), stdout);
+	    putchar('/');
+	    nicezputs(cn->nam, stdout);
+	    putchar('\n');
+	}
+	return;
+    }
+
+    if (cn->flags & HASHED) {
+	quotedzputs(cn->nam, stdout);
+	putchar('=');
+	quotedzputs(cn->u.cmd, stdout);
+	putchar('\n');
+    } else {
+	quotedzputs(cn->nam, stdout);
+	putchar('=');
+	quotedzputs(*(cn->u.name), stdout);
+	putchar('/');
+	quotedzputs(cn->nam, stdout);
+	putchar('\n');
+    }
+}
+
+/***************************************/
+/* Shell Function Hash Table Functions */
+/***************************************/
+
+/* hash table containing the shell functions */
+
+/**/
+HashTable shfunctab;
+
+/**/
+void
+createshfunctable(void)
+{
+    shfunctab = newhashtable(7, "shfunctab", NULL);
+
+    shfunctab->hash        = hasher;
+    shfunctab->emptytable  = NULL;
+    shfunctab->filltable   = NULL;
+    shfunctab->addnode     = addhashnode;
+    shfunctab->getnode     = gethashnode;
+    shfunctab->getnode2    = gethashnode2;
+    shfunctab->removenode  = removeshfuncnode;
+    shfunctab->disablenode = disableshfuncnode;
+    shfunctab->enablenode  = enableshfuncnode;
+    shfunctab->freenode    = freeshfuncnode;
+    shfunctab->printnode   = printshfuncnode;
+}
+
+/* Remove an entry from the shell function hash table.   *
+ * It checks if the function is a signal trap and if so, *
+ * it will disable the trapping of that signal.          */
+
+/**/
+static HashNode
+removeshfuncnode(HashTable ht, char *nam)
+{
+    HashNode hn;
+
+    if ((hn = removehashnode(shfunctab, nam))) {
+	if (!strncmp(hn->nam, "TRAP", 4))
+	    unsettrap(getsignum(hn->nam + 4));
+	return hn;
+    } else
+	return NULL;
+}
+
+/* Disable an entry in the shell function hash table.    *
+ * It checks if the function is a signal trap and if so, *
+ * it will disable the trapping of that signal.          */
+
+/**/
+static void
+disableshfuncnode(HashNode hn, int flags)
+{
+    hn->flags |= DISABLED;
+    if (!strncmp(hn->nam, "TRAP", 4)) {
+	int signum = getsignum(hn->nam + 4);
+	sigtrapped[signum] &= ~ZSIG_FUNC;
+	sigfuncs[signum] = NULL;
+	unsettrap(signum);
+    }
+}
+
+/* Re-enable an entry in the shell function hash table.  *
+ * It checks if the function is a signal trap and if so, *
+ * it will re-enable the trapping of that signal.        */
+
+/**/
+static void
+enableshfuncnode(HashNode hn, int flags)
+{
+    Shfunc shf = (Shfunc) hn;
+    int signum;
+
+    shf->flags &= ~DISABLED;
+    if (!strncmp(shf->nam, "TRAP", 4)) {
+	signum = getsignum(shf->nam + 4);
+	if (signum != -1) {
+	    settrap(signum, shf->funcdef);
+	    sigtrapped[signum] |= ZSIG_FUNC;
+	}
+    }
+}
+
+/**/
+static void
+freeshfuncnode(HashNode hn)
+{
+    Shfunc shf = (Shfunc) hn;
+
+    zsfree(shf->nam);
+    if (shf->funcdef)
+	freestruct(shf->funcdef);
+    zfree(shf, sizeof(struct shfunc));
+}
+
+/* Print a shell function */
+ 
+/**/
+static void
+printshfuncnode(HashNode hn, int printflags)
+{
+    Shfunc f = (Shfunc) hn;
+    char *t;
+ 
+    if ((printflags & PRINT_NAMEONLY) ||
+	((printflags & PRINT_WHENCE_SIMPLE) &&
+	!(printflags & PRINT_WHENCE_FUNCDEF))) {
+	zputs(f->nam, stdout);
+	putchar('\n');
+	return;
+    }
+ 
+    if ((printflags & (PRINT_WHENCE_VERBOSE|PRINT_WHENCE_WORD)) &&
+	!(printflags & PRINT_WHENCE_FUNCDEF)) {
+	nicezputs(f->nam, stdout);
+	printf((printflags & PRINT_WHENCE_WORD) ? ": function\n" :
+	       " is a shell function\n");
+	return;
+    }
+ 
+    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 = getpermtext((void *) dupstruct((void *) f->funcdef));
+    quotedzputs(f->nam, stdout);
+    printf(" () {\n\t");
+    zputs(t, stdout);
+    printf("\n}\n");
+    zsfree(t);
+}
+
+/**************************************/
+/* Reserved Word Hash Table Functions */
+/**************************************/
+
+/* Nodes for reserved word hash table */
+
+static struct reswd reswds[] = {
+    {NULL, "!", 0, BANG},
+    {NULL, "[[", 0, DINBRACK},
+    {NULL, "{", 0, INBRACE},
+    {NULL, "}", 0, OUTBRACE},
+    {NULL, "case", 0, CASE},
+    {NULL, "coproc", 0, COPROC},
+    {NULL, "do", 0, DO},
+    {NULL, "done", 0, DONE},
+    {NULL, "elif", 0, ELIF},
+    {NULL, "else", 0, ELSE},
+    {NULL, "end", 0, ZEND},
+    {NULL, "esac", 0, ESAC},
+    {NULL, "fi", 0, FI},
+    {NULL, "for", 0, FOR},
+    {NULL, "foreach", 0, FOREACH},
+    {NULL, "function", 0, FUNC},
+    {NULL, "if", 0, IF},
+    {NULL, "nocorrect", 0, NOCORRECT},
+    {NULL, "repeat", 0, REPEAT},
+    {NULL, "select", 0, SELECT},
+    {NULL, "then", 0, THEN},
+    {NULL, "time", 0, TIME},
+    {NULL, "until", 0, UNTIL},
+    {NULL, "while", 0, WHILE},
+    {NULL, NULL}
+};
+
+/* hash table containing the reserved words */
+
+/**/
+HashTable reswdtab;
+
+/* Build the hash table containing zsh's reserved words. */
+
+/**/
+void
+createreswdtable(void)
+{
+    Reswd rw;
+
+    reswdtab = newhashtable(23, "reswdtab", NULL);
+
+    reswdtab->hash        = hasher;
+    reswdtab->emptytable  = NULL;
+    reswdtab->filltable   = NULL;
+    reswdtab->addnode     = addhashnode;
+    reswdtab->getnode     = gethashnode;
+    reswdtab->getnode2    = gethashnode2;
+    reswdtab->removenode  = NULL;
+    reswdtab->disablenode = disablehashnode;
+    reswdtab->enablenode  = enablehashnode;
+    reswdtab->freenode    = NULL;
+    reswdtab->printnode   = printreswdnode;
+
+    for (rw = reswds; rw->nam; rw++)
+	reswdtab->addnode(reswdtab, rw->nam, rw);
+}
+
+/* Print a reserved word */
+
+/**/
+static void
+printreswdnode(HashNode hn, int printflags)
+{
+    Reswd rw = (Reswd) hn;
+
+    if (printflags & PRINT_WHENCE_WORD) {
+	printf("%s: reserved\n", rw->nam);
+	return;
+    }
+
+    if (printflags & PRINT_WHENCE_CSH) {
+	printf("%s: shell reserved word\n", rw->nam);
+	return;
+    }
+
+    if (printflags & PRINT_WHENCE_VERBOSE) {
+	printf("%s is a reserved word\n", rw->nam);
+	return;
+    }
+
+    /* default is name only */
+    printf("%s\n", rw->nam);
+}
+
+/********************************/
+/* Aliases Hash Table Functions */
+/********************************/
+
+/* hash table containing the aliases */
+ 
+/**/
+HashTable aliastab;
+ 
+/* Create new hash table for aliases */
+
+/**/
+void
+createaliastable(void)
+{
+    aliastab = newhashtable(23, "aliastab", NULL);
+
+    aliastab->hash        = hasher;
+    aliastab->emptytable  = NULL;
+    aliastab->filltable   = NULL;
+    aliastab->addnode     = addhashnode;
+    aliastab->getnode     = gethashnode;
+    aliastab->getnode2    = gethashnode2;
+    aliastab->removenode  = removehashnode;
+    aliastab->disablenode = disablehashnode;
+    aliastab->enablenode  = enablehashnode;
+    aliastab->freenode    = freealiasnode;
+    aliastab->printnode   = printaliasnode;
+
+    /* add the default aliases */
+    aliastab->addnode(aliastab, ztrdup("run-help"), createaliasnode(ztrdup("man"), 0));
+    aliastab->addnode(aliastab, ztrdup("which-command"), createaliasnode(ztrdup("whence"), 0));
+}
+
+/* Create a new alias node */
+
+/**/
+Alias
+createaliasnode(char *txt, int flags)
+{
+    Alias al;
+
+    al = (Alias) zcalloc(sizeof *al);
+    al->flags = flags;
+    al->text = txt;
+    al->inuse = 0;
+    return al;
+}
+
+/**/
+static void
+freealiasnode(HashNode hn)
+{
+    Alias al = (Alias) hn;
+ 
+    zsfree(al->nam);
+    zsfree(al->text);
+    zfree(al, sizeof(struct alias));
+}
+
+/* Print an alias */
+
+/**/
+static void
+printaliasnode(HashNode hn, int printflags)
+{
+    Alias a = (Alias) hn;
+
+    if (printflags & PRINT_NAMEONLY) {
+	zputs(a->nam, stdout);
+	putchar('\n');
+	return;
+    }
+
+    if (printflags & PRINT_WHENCE_WORD) {
+	printf("%s: alias\n", a->nam);
+	return;
+    }
+
+    if (printflags & PRINT_WHENCE_SIMPLE) {
+	zputs(a->text, stdout);
+	putchar('\n');
+	return;
+    }
+
+    if (printflags & PRINT_WHENCE_CSH) {
+	nicezputs(a->nam, stdout);
+	if (a->flags & ALIAS_GLOBAL)
+	    printf(": globally aliased to ");
+	else
+	    printf(": aliased to ");
+	nicezputs(a->text, stdout);
+	putchar('\n');
+	return;
+    }
+
+    if (printflags & PRINT_WHENCE_VERBOSE) {
+	nicezputs(a->nam, stdout);
+	if (a->flags & ALIAS_GLOBAL)
+	    printf(" is a global alias for ");
+	else
+	    printf(" is an alias for ");
+	nicezputs(a->text, stdout);
+	putchar('\n');
+	return;
+    }
+
+    if (printflags & PRINT_LIST) {
+	printf("alias ");
+	if (a->flags & ALIAS_GLOBAL)
+	    printf("-g ");
+
+	/* If an alias begins with `-', then we must output `-- ' *
+	 * first, so that it is not interpreted as an option.     */
+	if(a->nam[0] == '-')
+	    printf("-- ");
+    }
+
+    quotedzputs(a->nam, stdout);
+    putchar('=');
+    quotedzputs(a->text, stdout);
+    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 */
+/****************************************/
+
+/* hash table containing named directories */
+
+/**/
+HashTable nameddirtab;
+ 
+/* != 0 if all the usernames have already been *
+ * added to the named directory hash table.    */
+
+static int allusersadded;
+
+/* Create new hash table for named directories */
+
+/**/
+void
+createnameddirtable(void)
+{
+    nameddirtab = newhashtable(201, "nameddirtab", NULL);
+
+    nameddirtab->hash        = hasher;
+    nameddirtab->emptytable  = emptynameddirtable;
+    nameddirtab->filltable   = fillnameddirtable;
+    nameddirtab->addnode     = addnameddirnode;
+    nameddirtab->getnode     = gethashnode;
+    nameddirtab->getnode2    = gethashnode2;
+    nameddirtab->removenode  = removenameddirnode;
+    nameddirtab->disablenode = NULL;
+    nameddirtab->enablenode  = NULL;
+    nameddirtab->freenode    = freenameddirnode;
+    nameddirtab->printnode   = printnameddirnode;
+
+    allusersadded = 0;
+    finddir(NULL);		/* clear the finddir cache */
+}
+
+/* Empty the named directories table */
+
+/**/
+static void
+emptynameddirtable(HashTable ht)
+{
+    emptyhashtable(ht);
+    allusersadded = 0;
+    finddir(NULL);		/* clear the finddir cache */
+}
+
+/* Add all the usernames in the password file/database *
+ * to the named directories table.                     */
+
+/**/
+static void
+fillnameddirtable(HashTable ht)
+{
+#ifdef HAVE_GETPWENT
+    if (!allusersadded) {
+	struct passwd *pw;
+ 
+	setpwent();
+ 
+	/* 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);
+ 
+	endpwent();
+	allusersadded = 1;
+    }
+    return;
+#endif /* HAVE_GETPWENT */
+}
+
+/* Add an entry to the named directory hash *
+ * table, clearing the finddir() cache and  *
+ * initialising the `diff' member.          */
+
+/**/
+static void
+addnameddirnode(HashTable ht, char *nam, void *nodeptr)
+{
+    Nameddir nd = (Nameddir) nodeptr;
+
+    nd->diff = strlen(nd->dir) - strlen(nam);
+    finddir(NULL);		/* clear the finddir cache */
+    addhashnode(ht, nam, nodeptr);
+}
+
+/* Remove an entry from the named directory  *
+ * hash table, clearing the finddir() cache. */
+
+/**/
+static HashNode
+removenameddirnode(HashTable ht, char *nam)
+{
+    HashNode hn = removehashnode(ht, nam);
+
+    if(hn)
+	finddir(NULL);		/* clear the finddir cache */
+    return hn;
+}
+
+/* Free up the memory used by a named directory hash node. */
+
+/**/
+static void
+freenameddirnode(HashNode hn)
+{
+    Nameddir nd = (Nameddir) hn;
+ 
+    zsfree(nd->nam);
+    zsfree(nd->dir);
+    zfree(nd, sizeof(struct nameddir));
+}
+
+/* Print a named directory */
+
+/**/
+static void
+printnameddirnode(HashNode hn, int printflags)
+{
+    Nameddir nd = (Nameddir) hn;
+
+    if (printflags & PRINT_NAMEONLY) {
+	zputs(nd->nam, stdout);
+	putchar('\n');
+	return;
+    }
+
+    quotedzputs(nd->nam, stdout);
+    putchar('=');
+    quotedzputs(nd->dir, stdout);
+    putchar('\n');
+}
diff --git a/Src/hashtable.h b/Src/hashtable.h
new file mode 100644
index 000000000..5b78c9c18
--- /dev/null
+++ b/Src/hashtable.h
@@ -0,0 +1,62 @@
+/*
+ * hashtable.h - header file for hash table handling code
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+/* Builtin function numbers; used by handler functions that handle more *
+ * than one builtin.  Note that builtins such as compctl, that are not  *
+ * overloaded, don't get a number.                                      */
+
+#define BIN_TYPESET   0
+#define BIN_BG        1
+#define BIN_FG        2
+#define BIN_JOBS      3
+#define BIN_WAIT      4
+#define BIN_DISOWN    5
+#define BIN_BREAK     6
+#define BIN_CONTINUE  7
+#define BIN_EXIT      8
+#define BIN_RETURN    9
+#define BIN_CD       10
+#define BIN_POPD     11
+#define BIN_PUSHD    12
+#define BIN_PRINT    13
+#define BIN_EVAL     14
+#define BIN_SCHED    15
+#define BIN_FC       16
+#define BIN_PUSHLINE 17
+#define BIN_LOGOUT   18
+#define BIN_TEST     19
+#define BIN_BRACKET  20
+#define BIN_EXPORT   21
+#define BIN_ECHO     22
+#define BIN_DISABLE  23
+#define BIN_ENABLE   24
+
+/* These currently depend on being 0 and 1. */
+#define BIN_SETOPT    0
+#define BIN_UNSETOPT  1
diff --git a/Src/hist.c b/Src/hist.c
new file mode 100644
index 000000000..a4c5735c1
--- /dev/null
+++ b/Src/hist.c
@@ -0,0 +1,1670 @@
+/*
+ * hist.c - history expansion
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "hist.pro"
+
+/* != 0 means history substitution is turned off */
+ 
+/**/
+int stophist;
+ 
+/* this line began with a space, so junk it if HISTIGNORESPACE is on */
+ 
+/**/
+int spaceflag;
+ 
+/* if != 0, we are expanding the current line */
+
+/**/
+int expanding;
+
+/* these are used to modify the cursor position during expansion */
+
+/**/
+int excs, exlast;
+
+/*
+ * Current history event number
+ *
+ * Note on curhist: with history inactive, this points to the
+ * last line actually added to the history list.  With history active,
+ * the line does not get added to the list until hend(), if at all.
+ * However, curhist is incremented to reflect the current line anyway.
+ * Thus if the line is not added to the list, curhist must be
+ * decremented in hend().
+ */
+ 
+/**/
+int curhist;
+
+/* number of history entries */
+ 
+/**/
+int histentct;
+ 
+/* array of history entries */
+ 
+/**/
+Histent histentarr;
+ 
+/* capacity of history lists */
+ 
+/**/
+int histsiz;
+ 
+/* if = 1, we have performed history substitution on the current line *
+ * if = 2, we have used the 'p' modifier                              */
+ 
+/**/
+int histdone;
+ 
+/* state of the history mechanism */
+ 
+/**/
+int histactive;
+
+/* Bits of histactive variable */
+#define HA_ACTIVE	(1<<0)	/* History mechanism is active */
+#define HA_NOSTORE	(1<<1)	/* Don't store the line when finished */
+#define HA_JUNKED	(1<<2)	/* Last history line was already junked */
+#define HA_NOINC	(1<<3)	/* Don't store, curhist not incremented */
+
+/* Array of word beginnings and endings in current history line. */
+
+/**/
+short *chwords;
+
+/* Max, actual position in chwords.
+ * nwords = chwordpos/2 because we record beginning and end of words.
+ */
+
+/**/
+int chwordlen, chwordpos;
+
+/* the last l for s/l/r/ history substitution */
+ 
+/**/
+char *hsubl;
+
+/* the last r for s/l/r/ history substitution */
+ 
+/**/
+char *hsubr;
+ 
+/* pointer into the history line */
+ 
+/**/
+char *hptr;
+ 
+/* the current history line */
+ 
+/**/
+char *chline;
+
+/* true if the last character returned by hgetc was an escaped bangchar *
+ * if it is set and NOBANGHIST is unset hwaddc escapes bangchars        */
+
+/**/
+int qbang;
+ 
+/* max size of histline */
+ 
+/**/
+int hlinesz;
+ 
+/* default event (usually curhist-1, that is, "!!") */
+ 
+static int defev;
+ 
+/* add a character to the current history word */
+
+/**/
+void
+hwaddc(int c)
+{
+    /* Only if history line exists and lexing has not finished. */
+    if (chline && !(errflag || lexstop)) {
+	/* Quote un-expanded bangs in the history line. */
+	if (c == bangchar && stophist < 2 && qbang)
+	    /* If qbang is not set, we do not escape this bangchar as it's *
+	     * not mecessary (e.g. it's a bang in !=, or it is followed    *
+	     * by a space). Roughly speaking, qbang is zero only if the    *
+	     * history interpreter has already digested this bang and      *
+	     * found that it is not necessary to escape it.                */
+	    hwaddc('\\');
+	*hptr++ = c;
+
+	/* Resize history line if necessary */
+	if (hptr - chline >= hlinesz) {
+	    int oldsiz = hlinesz;
+
+	    chline = realloc(chline, hlinesz = oldsiz + 16);
+	    hptr = chline + oldsiz;
+	}
+    }
+}
+
+/* This function adds a character to the zle input line. It is used when *
+ * zsh expands history (see doexpandhist() in zle_tricky.c). It also     *
+ * calculates the new cursor position after the expansion. It is called  *
+ * from hgetc() and from gettok() in lex.c for characters in comments.   */
+ 
+/**/
+void
+addtoline(int c)
+{
+    if (! expanding || lexstop)
+	return;
+    if (qbang && c == bangchar && stophist < 2) {
+	exlast--;
+	spaceinline(1);
+	line[cs++] = '\\';
+    }
+    if (excs > cs) {
+	excs += 1 + inbufct - exlast;
+	if (excs < cs)
+	    /* this case could be handled better but it is    *
+	     * so rare that it does not worth it              */
+	    excs = cs;
+    }
+    exlast = inbufct;
+    spaceinline(1);
+    line[cs++] = itok(c) ? ztokens[c - Pound] : c;
+}
+
+/**/
+int
+hgetc(void)
+{
+    int c = ingetc();
+
+    qbang = 0;
+    if (!stophist && !(inbufflags & INP_ALIAS)) {
+	/* If necessary, expand history characters. */
+	c = histsubchar(c);
+	if (c < 0) {
+	    /* bad expansion */
+	    errflag = lexstop = 1;
+	    return ' ';
+	}
+    }
+    if ((inbufflags & INP_HIST) && !stophist) {
+	/* the current character c came from a history expansion          *
+	 * (inbufflags && INP_HIST) and history is not disabled           *
+	 * (e.g. we are not inside single quotes). In that case, \!       *
+	 * should be treated as ! (since this \! came from a previous     *
+	 * history line where \ was used to escape the bang). So if       *
+	 * c == '\\' we fetch one more character to see if it's a bang,   *
+	 * and if it is not, we unget it and reset c back to '\\'         */
+	qbang = 0;
+	if (c == '\\' && !(qbang = (c = ingetc()) == bangchar))
+	    safeinungetc(c), c = '\\';
+    } else if (stophist || (inbufflags & INP_ALIAS))
+	/* If the result is a bangchar which came from history or alias  *
+	 * expansion, we treat it as an escaped bangchar, unless history *
+	 * is disabled. If stophist == 1 it only means that history is   *
+	 * temporarily disabled by a !" which won't appear in in the     *
+	 * history, so we still have an escaped bang. stophist > 1 if    *
+	 * history is disabled with NOBANGHIST or by someone else (e.g.  *
+	 * when the lexer scans single quoted text).                     */
+	qbang = c == bangchar && (stophist < 2);
+    hwaddc(c);
+    addtoline(c);
+
+    return c;
+}
+
+/**/
+static void
+safeinungetc(int c)
+{
+    if (lexstop)
+	lexstop = 0;
+    else
+	inungetc(c);
+}
+
+/**/
+void
+herrflush(void)
+{
+    while (!lexstop && inbufct)
+	hwaddc(ingetc());
+}
+
+/* extract :s/foo/bar/ delimiters and arguments */
+
+/**/
+static int
+getsubsargs(char *subline)
+{
+    int del;
+    char *ptr1, *ptr2;
+
+    del = ingetc();
+    ptr1 = hdynread2(del);
+    if (!ptr1)
+	return 1;
+    ptr2 = hdynread2(del);
+    if (strlen(ptr1)) {
+	zsfree(hsubl);
+	hsubl = ptr1;
+    }
+    zsfree(hsubr);
+    hsubr = ptr2;
+    if (hsubl && !strstr(subline, hsubl)) {
+	herrflush();
+	zerr("substitution failed", NULL, 0);
+	return 1;
+    }
+    return 0;
+}
+
+/* Get the maximum no. of words for a history entry. */
+
+/**/
+static int
+getargc(Histent ehist)
+{
+    return ehist->nwords ? ehist->nwords-1 : 0;
+}
+
+/* Perform history substitution, returning the next character afterwards. */
+
+/**/
+static int
+histsubchar(int c)
+{
+    int ev, farg, evset = -1, larg, argc, cflag = 0, bflag = 0;
+    static int mev = -1, marg = -1;
+    char buf[256], *ptr;
+    char *sline;
+    Histent ehist;
+
+    /* look, no goto's */
+    if (isfirstch && c == hatchar) {
+	/* Line begins ^foo^bar */
+	isfirstch = 0;
+	inungetc(hatchar);
+	if (!(ehist = gethist(defev))
+	    || !(sline = getargs(ehist, 0, getargc(ehist)))
+	    || getsubsargs(sline) || !hsubl)
+	    return -1;
+	subst(&sline, hsubl, hsubr, 0);
+    } else {
+	/* Line doesn't begin ^foo^bar */
+	if (c != ' ')
+	    isfirstch = 0;
+	if (c == '\\') {
+	    int g = ingetc();
+
+	    if (g != bangchar)
+		safeinungetc(g);
+	    else {
+		qbang = 1;
+		return bangchar;
+	    }
+	}
+	if (c != bangchar)
+	    return c;
+	*hptr = '\0';
+	if ((c = ingetc()) == '{') {
+	    bflag = cflag = 1;
+	    c = ingetc();
+	}
+	if (c == '\"') {
+	    stophist = 1;
+	    return ingetc();
+	}
+	if ((!cflag && inblank(c)) || c == '=' || c == '(' || lexstop) {
+	    safeinungetc(c);
+	    return bangchar;
+	}
+	cflag = 0;
+	ptr = buf;
+
+	/* get event number */
+
+	if (c == '?') {
+	    for (;;) {
+		c = ingetc();
+		if (c == '?' || c == '\n' || lexstop)
+		    break;
+		else
+		    *ptr++ = c;
+	    }
+	    if (c != '\n' && !lexstop)
+		c = ingetc();
+	    *ptr = '\0';
+	    mev = ev = hconsearch(hsubl = ztrdup(buf), &marg);
+	    evset = 0;
+	    if (ev == -1) {
+		herrflush();
+		zerr("no such event: %s", buf, 0);
+		return -1;
+	    }
+	} else {
+	    int t0;
+
+	    for (;;) {
+		if (inblank(c) || c == ';' || c == ':' || c == '^' ||
+		    c == '$' || c == '*' || c == '%' || c == '}' ||
+		    c == '\'' || c == '"' || c == '`' || lexstop)
+		    break;
+		if (ptr != buf) {
+		    if (c == '-')
+			break;
+		    if ((idigit(buf[0]) || buf[0] == '-') && !idigit(c))
+			break;
+		}
+		*ptr++ = c;
+		if (c == '#' || c == bangchar) {
+		    c = ingetc();
+		    break;
+		}
+		c = ingetc();
+	    }
+	    *ptr = 0;
+	    if (!*buf)
+		if (c != '%') {
+		    if (isset(CSHJUNKIEHISTORY))
+			ev = curhist - 1;
+		    else
+			ev = defev;
+		    if (c == ':' && evset == -1)
+			evset = 0;
+		    else
+			evset = 1;
+		} else {
+		    if (marg != -1)
+			ev = mev;
+		    else
+			ev = defev;
+		    evset = 0;
+	    } else if ((t0 = atoi(buf))) {
+		ev = (t0 < 0) ? curhist + t0 : t0;
+		evset = 1;
+	    } else if ((unsigned)*buf == bangchar) {
+		ev = curhist - 1;
+		evset = 1;
+	    } else if (*buf == '#') {
+		ev = curhist;
+		evset = 1;
+	    } else if ((ev = hcomsearch(buf)) == -1) {
+		herrflush();
+		zerr("event not found: %s", buf, 0);
+		return -1;
+	    } else
+		evset = 1;
+	}
+
+	/* get the event */
+
+	if (!(ehist = gethist(defev = ev)))
+	    return -1;
+
+	/* extract the relevant arguments */
+
+	argc = getargc(ehist);
+	if (c == ':') {
+	    cflag = 1;
+	    c = ingetc();
+	    if (c == '%' && marg != -1) {
+		if (!evset) {
+		    ehist = gethist(defev = mev);
+		    argc = getargc(ehist);
+		} else {
+		    herrflush();
+		    zerr("Ambiguous history reference", NULL, 0);
+		    return -1;
+		}
+
+	    }
+	}
+	if (c == '*') {
+	    farg = 1;
+	    larg = argc;
+	    cflag = 0;
+	} else {
+	    inungetc(c);
+	    larg = farg = getargspec(argc, marg, evset);
+	    if (larg == -2)
+		return -1;
+	    if (farg != -1)
+		cflag = 0;
+	    c = ingetc();
+	    if (c == '*') {
+		cflag = 0;
+		larg = argc;
+	    } else if (c == '-') {
+		cflag = 0;
+		larg = getargspec(argc, marg, evset);
+		if (larg == -2)
+		    return -1;
+		if (larg == -1)
+		    larg = argc - 1;
+	    } else
+		inungetc(c);
+	}
+	if (farg == -1)
+	    farg = 0;
+	if (larg == -1)
+	    larg = argc;
+	if (!(sline = getargs(ehist, farg, larg)))
+	    return -1;
+    }
+
+    /* do the modifiers */
+
+    for (;;) {
+	c = (cflag) ? ':' : ingetc();
+	cflag = 0;
+	if (c == ':') {
+	    int gbal = 0;
+
+	    if ((c = ingetc()) == 'g') {
+		gbal = 1;
+		c = ingetc();
+	    }
+	    switch (c) {
+	    case 'p':
+		histdone = HISTFLAG_DONE | HISTFLAG_NOEXEC;
+		break;
+	    case 'h':
+		if (!remtpath(&sline)) {
+		    herrflush();
+		    zerr("modifier failed: h", NULL, 0);
+		    return -1;
+		}
+		break;
+	    case 'e':
+		if (!rembutext(&sline)) {
+		    herrflush();
+		    zerr("modifier failed: e", NULL, 0);
+		    return -1;
+		}
+		break;
+	    case 'r':
+		if (!remtext(&sline)) {
+		    herrflush();
+		    zerr("modifier failed: r", NULL, 0);
+		    return -1;
+		}
+		break;
+	    case 't':
+		if (!remlpaths(&sline)) {
+		    herrflush();
+		    zerr("modifier failed: t", NULL, 0);
+		    return -1;
+		}
+		break;
+	    case 's':
+		if (getsubsargs(sline))
+		    return -1; /* fall through */
+	    case '&':
+		if (hsubl && hsubr)
+		    subst(&sline, hsubl, hsubr, gbal);
+		else {
+		    herrflush();
+		    zerr("no previous substitution", NULL, 0);
+		    return -1;
+		}
+		break;
+	    case 'q':
+		quote(&sline);
+		break;
+	    case 'x':
+		quotebreak(&sline);
+		break;
+	    case 'l':
+		downcase(&sline);
+		break;
+	    case 'u':
+		upcase(&sline);
+		break;
+	    default:
+		herrflush();
+		zerr("illegal modifier: %c", NULL, c);
+		return -1;
+	    }
+	} else {
+	    if (c != '}' || !bflag)
+		inungetc(c);
+	    if (c != '}' && bflag) {
+		zerr("'}' expected", NULL, 0);
+		return -1;
+	    }
+	    break;
+	}
+    }
+
+    /*
+     * Push the expanded value onto the input stack,
+     * marking this as a history word for purposes of the alias stack.
+     */
+
+    lexstop = 0;
+    /* this function is called only called from hgetc and only if      *
+     * !(inbufflags & INP_ALIAS). History expansion should never be    *
+     * done with INP_ALIAS (to prevent recursive history expansion and *
+     * histoty expansion of aliases). Escapes are not removed here.    *
+     * This is now handled in hgetc.                                   */
+    inpush(sline, INP_HIST, NULL); /* sline from heap, don't free */
+    histdone |= HISTFLAG_DONE;
+    if (isset(HISTVERIFY))
+	histdone |= HISTFLAG_NOEXEC | HISTFLAG_RECALL;
+
+    /* Don't try and re-expand line. */
+    return ingetc();
+}
+
+/* unget a char and remove it from chline. It can only be used *
+ * to unget a character returned by hgetc.                     */
+
+/**/
+void
+hungetc(int c)
+{
+    int doit = 1;
+
+    while (!lexstop) {
+	if (hptr[-1] != (char) c && stophist < 4 &&
+	    hptr > chline + 1 && hptr[-1] == '\n' && hptr[-2] == '\\')
+	    hungetc('\n'), hungetc('\\');
+
+	if (expanding) {
+	    cs--;
+	    ll--;
+	    exlast++;
+	}
+	DPUTS(hptr <= chline, "BUG: hungetc attempted at buffer start");
+	hptr--;
+	DPUTS(*hptr != (char) c, "BUG: wrong character in hungetc() ");
+	qbang = (c == bangchar && stophist < 2 &&
+		 hptr > chline && hptr[-1] == '\\');
+	if (doit)
+	    inungetc(c);
+	if (!qbang)
+	    return;
+	doit = !stophist && ((inbufflags & INP_HIST) ||
+				 !(inbufflags & INP_ALIAS));
+	c = '\\';
+    }
+}
+
+/* begin reading a string */
+
+/**/
+void
+strinbeg(void)
+{
+    strin++;
+    hbegin();
+    lexinit();
+}
+
+/* done reading a string */
+
+/**/
+void
+strinend(void)
+{
+    hend();
+    DPUTS(!strin, "BUG: strinend() called without strinbeg()");
+    strin--;
+    isfirstch = 1;
+    histdone = 0;
+}
+
+/* initialize the history mechanism */
+
+/**/
+void
+hbegin(void)
+{
+    Histent curhistent;
+
+    isfirstln = isfirstch = 1;
+    errflag = histdone = spaceflag = 0;
+    stophist = (!interact || unset(BANGHIST) || unset(SHINSTDIN)) << 1;
+    chline = hptr = zcalloc(hlinesz = 16);
+    chwords = zalloc((chwordlen = 16)*sizeof(short));
+    chwordpos = 0;
+
+    if (histactive & HA_JUNKED)
+	curhist--;
+    curhistent = gethistent(curhist);
+    if (!curhistent->ftim)
+	curhistent->ftim = time(NULL);
+    histactive = HA_ACTIVE;
+    if (interact && isset(SHINSTDIN) && !strin) {
+	attachtty(mypgrp);
+	defev = curhist;
+	curhist++;
+    } else
+	histactive |= HA_NOINC;
+}
+
+/* compare current line with history entry using only text in words */
+
+/**/
+static int
+histcmp(Histent he)
+{
+    int kword, lword;
+    int nwords = chwordpos/2;
+
+    /* If the history entry came from a file, the words were not
+     * divided by the lexer so we have to resort to strcmp.
+     */
+    if (he->flags & HIST_READ)
+	return strcmp(he->text, chline);
+
+    if (nwords != he->nwords)
+	return 1;
+
+    for (kword = 0; kword < 2*nwords; kword += 2)
+	if ((lword = chwords[kword+1]-chwords[kword])
+	    != he->words[kword+1]-he->words[kword] ||
+	    memcmp(he->text+he->words[kword], chline+chwords[kword], lword))
+	    return 1;
+
+    return 0;
+}
+
+/**/
+void
+histreduceblanks(void)
+{
+    int i, len, pos, needblank;
+
+    for (i = 0, len = 0; i < chwordpos; i += 2) {
+	len += chwords[i+1] - chwords[i]
+	     + (i > 0 && chwords[i] > chwords[i-1]);
+    }
+    if (chline[len] == '\0')
+	return;
+
+    for (i = 0, pos = 0; i < chwordpos; i += 2) {
+	len = chwords[i+1] - chwords[i];
+	needblank = (i < chwordpos-2 && chwords[i+2] > chwords[i+1]);
+	if (pos != chwords[i]) {
+	    memcpy(chline + pos, chline + chwords[i], len + needblank);
+	    chwords[i] = pos;
+	    chwords[i+1] = chwords[i] + len;
+	}
+	pos += len + needblank;
+    }
+    chline[pos] = '\0';
+}
+
+/* say we're done using the history mechanism */
+
+/**/
+int
+hend(void)
+{
+    int flag, save = 1;
+
+    DPUTS(!chline, "BUG: chline is NULL in hend()");
+    if (histactive & (HA_NOSTORE|HA_NOINC)) {
+	zfree(chline, hlinesz);
+	zfree(chwords, chwordlen*sizeof(short));
+	chline = NULL;
+	if (!(histactive & HA_NOINC))
+	    curhist--;
+	histactive = 0;
+	return 1;
+    }
+    flag = histdone;
+    histdone = 0;
+    if (hptr < chline + 1)
+	save = 0;
+    else {
+	*hptr = '\0';
+	if (hptr[-1] == '\n')
+	    if (chline[1]) {
+		*--hptr = '\0';
+	    } else
+		save = 0;
+	if (!*chline || !strcmp(chline, "\n") ||
+	    (isset(HISTIGNORESPACE) && spaceflag))
+	    save = 0;
+    }
+    if (flag & (HISTFLAG_DONE | HISTFLAG_RECALL)) {
+	char *ptr;
+
+	ptr = ztrdup(chline);
+	if ((flag & (HISTFLAG_DONE | HISTFLAG_RECALL)) == HISTFLAG_DONE) {
+	    zputs(ptr, shout);
+	    fputc('\n', shout);
+	    fflush(shout);
+	}
+	if (flag & HISTFLAG_RECALL) {
+	    PERMALLOC {
+		pushnode(bufstack, ptr);
+	    } LASTALLOC;
+	    save = 0;
+	} else
+	    zsfree(ptr);
+    }
+    if (save) {
+	Histent he;
+	int keepflags = 0;
+
+#ifdef DEBUG
+	/* debugging only */
+	if (chwordpos%2) {
+	    hwend();
+	    DPUTS(1, "BUG: uncompleted line in history");
+	}
+#endif
+	/* get rid of pesky \n which we've already nulled out */
+	if (!chline[chwords[chwordpos-2]])
+	    chwordpos -= 2;
+	/* strip superfluous blanks, if desired */
+	if (isset(HISTREDUCEBLANKS))
+	    histreduceblanks();
+
+	if (isset(HISTIGNOREDUPS) && (he = gethistent(curhist - 1))
+	 && he->text && !histcmp(he)) {
+	    /* This history entry compares the same as the previous.
+	     * In case minor changes were made, we overwrite the
+	     * previous one with the current one.  This also gets
+	     * the timestamp right.  However, keep the old flags.
+	     */
+	    keepflags = he->flags;
+	    curhist--;
+	}
+
+	he =  gethistent(curhist);
+	zsfree(he->text);
+	he->text = ztrdup(chline);
+	if (he->nwords)
+	    zfree(he->words, he->nwords*2*sizeof(short));
+	he->stim = time(NULL);
+	he->ftim = 0L;
+	he->flags = keepflags;
+
+	if ((he->nwords = chwordpos/2)) {
+	    he->words = (short *)zalloc(chwordpos * sizeof(short));
+	    memcpy(he->words, chwords, chwordpos * sizeof(short));
+	}
+    } else
+	curhist--;
+    zfree(chline, hlinesz);
+    zfree(chwords, chwordlen*sizeof(short));
+    chline = NULL;
+    histactive = 0;
+    return !(flag & HISTFLAG_NOEXEC || errflag);
+}
+
+/* remove the current line from the history List */
+
+/**/
+void
+remhist(void)
+{
+    if (!(histactive & HA_ACTIVE)) {
+	if (!(histactive & HA_JUNKED)) {
+	    /* make sure this doesn't show up when we do firsthist() */
+	    Histent he = gethistent(curhist);
+	    zsfree(he->text);
+	    he->text = NULL;
+	    histactive |= HA_JUNKED;
+	    /* curhist-- is delayed until the next hbegin() */
+	}
+    } else
+	histactive |= HA_NOSTORE;
+}
+
+/* Gives current expansion word if not last word before chwordpos. */
+
+/**/
+int hwgetword = -1;
+
+/* begin a word */
+
+/**/
+void
+hwbegin(int offset)
+{
+    if (chwordpos%2)
+	chwordpos--;	/* make sure we're on a word start, not end */
+    /* If we're expanding an alias, we should overwrite the expansion
+     * in the history.
+     */
+    if ((inbufflags & INP_ALIAS) && !(inbufflags & INP_HIST))
+	hwgetword = chwordpos;
+    else
+	hwgetword = -1;
+    chwords[chwordpos++] = hptr - chline + offset;
+}
+
+/* add a word to the history List */
+
+/**/
+void
+hwend(void)
+{
+    if (chwordpos%2 && chline) {
+	/* end of word reached and we've already begun a word */
+	if (hptr > chline + chwords[chwordpos-1]) {
+	    chwords[chwordpos++] = hptr - chline;
+	    if (chwordpos >= chwordlen) {
+		chwords = (short *) realloc(chwords,
+					    (chwordlen += 16)*sizeof(short));
+	    }
+	    if (hwgetword > -1) {
+		/* We want to reuse the current word position */
+		chwordpos = hwgetword;
+		/* Start from where previous word ended, if possible */
+		hptr = chline + chwords[chwordpos ? chwordpos - 1 : 0];
+	    }
+	} else {
+	    /* scrub that last word, it doesn't exist */
+	    chwordpos--;
+	}
+    }
+}
+
+/* Go back to immediately after the last word, skipping space. */
+
+/**/
+void
+histbackword(void)
+{
+    if (!(chwordpos%2) && chwordpos)
+	hptr = chline + chwords[chwordpos-1];
+}
+
+/* Get the start and end point of the current history word */
+
+/**/
+static void
+hwget(char **startptr)
+{
+    int pos = hwgetword > -1 ? hwgetword : chwordpos - 2;
+
+#ifdef DEBUG
+    /* debugging only */
+    if (hwgetword == -1 && !chwordpos) {
+	/* no words available */
+	DPUTS(1, "BUG: hwget() called with no words");
+	*startptr = "";
+	return;
+    } 
+    else if (hwgetword == -1 && chwordpos%2) {
+	DPUTS(1, "BUG: hwget() called in middle of word");
+	*startptr = "";
+	return;
+    }
+#endif
+
+    *startptr = chline + chwords[pos];
+    chline[chwords[++pos]] = '\0';
+}
+
+/* Replace the current history word with rep, if different */
+
+/**/
+void
+hwrep(char *rep)
+{
+    char *start;
+    hwget(&start);
+
+    if (!strcmp(rep, start))
+	return;
+    
+    hptr = start;
+    chwordpos = (hwgetword > -1) ? hwgetword : chwordpos - 2;
+    hwbegin(0);
+    qbang = 1;
+    while (*rep)
+	hwaddc(*rep++);
+    hwend();
+}
+
+/* Get the entire current line, deleting it in the history. */
+
+/**/
+char *
+hgetline(void)
+{
+    /* Currently only used by pushlineoredit().
+     * It's necessary to prevent that from getting too pally with
+     * the history code.
+     */
+    char *ret;
+
+    if (!chline || hptr == chline)
+	return NULL;
+    *hptr = '\0';
+    ret = dupstring(chline);
+
+    /* reset line */
+    hptr = chline;
+    chwordpos = 0;
+    hwgetword = -1;
+
+    return ret;
+}
+
+/* get an argument specification */
+
+/**/
+static int
+getargspec(int argc, int marg, int evset)
+{
+    int c, ret = -1;
+
+    if ((c = ingetc()) == '0')
+	return 0;
+    if (idigit(c)) {
+	ret = 0;
+	while (idigit(c)) {
+	    ret = ret * 10 + c - '0';
+	    c = ingetc();
+	}
+	inungetc(c);
+    } else if (c == '^')
+	ret = 1;
+    else if (c == '$')
+	ret = argc;
+    else if (c == '%') {
+	if (evset) {
+	    herrflush();
+	    zerr("Ambiguous history reference", NULL, 0);
+	    return -2;
+	}
+	if (marg == -1) {
+	    herrflush();
+	    zerr("%% with no previous word matched", NULL, 0);
+	    return -2;
+	}
+	ret = marg;
+    } else
+	inungetc(c);
+    return ret;
+}
+
+/* do ?foo? search */
+
+/**/
+static int
+hconsearch(char *str, int *marg)
+{
+    int t0, t1 = 0;
+    char *s;
+    Histent he;
+
+    for (t0 = curhist - 1; (he = quietgethist(t0)); t0--)
+	if ((s = strstr(he->text, str))) {
+	    int pos = s - he->text;
+	    while (t1 < he->nwords && he->words[2*t1] <= pos)
+		t1++;
+	    *marg = t1 - 1;
+	    return t0;
+	}
+    return -1;
+}
+
+/* do !foo search */
+
+/**/
+int
+hcomsearch(char *str)
+{
+    int t0;
+    char *hs;
+
+    for (t0 = curhist - 1; (hs = quietgetevent(t0)); t0--)
+	if (!strncmp(hs, str, strlen(str)))
+	    return t0;
+    return -1;
+}
+
+/* various utilities for : modifiers */
+
+/**/
+int
+remtpath(char **junkptr)
+{
+    char *str = *junkptr, *remcut;
+
+    if ((remcut = strrchr(str, '/'))) {
+	if (str != remcut)
+	    *remcut = '\0';
+	else
+	    str[1] = '\0';
+	return 1;
+    }
+    return 0;
+}
+
+/**/
+int
+remtext(char **junkptr)
+{
+    char *str = *junkptr, *remcut;
+
+    if ((remcut = strrchr(str, '.')) && remcut != str) {
+	*remcut = '\0';
+	return 1;
+    }
+    return 0;
+}
+
+/**/
+int
+rembutext(char **junkptr)
+{
+    char *str = *junkptr, *remcut;
+
+    if ((remcut = strrchr(str, '.')) && remcut != str) {
+	*junkptr = dupstring(remcut + 1);	/* .xx or xx? */
+	return 1;
+    }
+    return 0;
+}
+
+/**/
+int
+remlpaths(char **junkptr)
+{
+    char *str = *junkptr, *remcut;
+
+    if ((remcut = strrchr(str, '/'))) {
+	*remcut = '\0';
+	*junkptr = dupstring(remcut + 1);
+	return 1;
+    }
+    return 0;
+}
+
+/**/
+int
+makeuppercase(char **junkptr)
+{
+    char *str = *junkptr;
+
+    for (; *str; str++)
+	*str = tuupper(*str);
+    return 1;
+}
+
+/**/
+int
+makelowercase(char **junkptr)
+{
+    char *str = *junkptr;
+
+    for (; *str; str++)
+	*str = tulower(*str);
+    return 1;
+}
+
+/**/
+int
+makecapitals(char **junkptr)
+{
+    char *str = *junkptr;
+
+    for (; *str;) {
+	for (; *str && !ialnum(*str); str++);
+	if (*str)
+	    *str = tuupper(*str), str++;
+	for (; *str && ialnum(*str); str++)
+	    *str = tulower(*str);
+    }
+    return 1;
+}
+
+/**/
+void
+subst(char **strptr, char *in, char *out, int gbal)
+{
+    char *str = *strptr, *instr = *strptr, *substcut, *sptr, *oldstr;
+    int off, inlen, outlen;
+
+    if (!*in)
+	in = str, gbal = 0;
+    if (!(substcut = (char *)strstr(str, in)))
+	return;
+    inlen = strlen(in);
+    sptr = convamps(out, in, inlen);
+    outlen = strlen(sptr);
+
+    do {
+	*substcut = '\0';
+	off = substcut - *strptr + outlen;
+	substcut += inlen;
+	*strptr = tricat(oldstr = *strptr, sptr, substcut);
+	if (oldstr != instr)
+	    zsfree(oldstr);
+	str = (char *)*strptr + off;
+    } while (gbal && (substcut = (char *)strstr(str, in)));
+}
+
+/**/
+static char *
+convamps(char *out, char *in, int inlen)
+{
+    char *ptr, *ret, *pp;
+    int slen, sdup = 0;
+
+    for (ptr = out, slen = 0; *ptr; ptr++, slen++)
+	if (*ptr == '\\')
+	    ptr++, sdup = 1;
+	else if (*ptr == '&')
+	    slen += inlen - 1, sdup = 1;
+    if (!sdup)
+	return out;
+    ret = pp = (char *)alloc(slen + 1);
+    for (ptr = out; *ptr; ptr++)
+	if (*ptr == '\\')
+	    *pp++ = *++ptr;
+	else if (*ptr == '&') {
+	    strcpy(pp, in);
+	    pp += inlen;
+	} else
+	    *pp++ = *ptr;
+    *pp = '\0';
+    return ret;
+}
+
+/**/
+struct histent *
+quietgethist(int ev)
+{
+    static struct histent storehist;
+
+    if (ev < firsthist() || ev > curhist)
+	return NULL;
+    if (ev == curhist && (histactive & HA_ACTIVE)) {
+	/* The current history line has not been stored.  Build it up
+	 * from other variables.
+	 */
+	storehist.text = chline;
+	storehist.nwords = chwordpos/2;
+	storehist.words = chwords;
+
+	return &storehist;
+    } else
+	return gethistent(ev);
+}
+
+/**/
+char *
+quietgetevent(int ev)
+{
+    Histent ent = quietgethist(ev);
+
+    return ent ? ent->text : NULL;
+}
+
+/**/
+static Histent
+gethist(int ev)
+{
+    Histent ret;
+
+    ret = quietgethist(ev);
+    if (!ret) {
+	herrflush();
+	zerr("no such event: %d", NULL, ev);
+    }
+    return ret;
+}
+
+/**/
+static char *
+getargs(Histent elist, int arg1, int arg2)
+{
+    short *words = elist->words;
+    int pos1, nwords = elist->nwords;
+
+    if (arg2 < arg1 || arg1 >= nwords || arg2 >= nwords) {
+	/* remember, argN is indexed from 0, nwords is total no. of words */
+	herrflush();
+	zerr("no such word in event", NULL, 0);
+	return NULL;
+    }
+
+    pos1 = words[2*arg1];
+    return dupstrpfx(elist->text + pos1, words[2*arg2+1] - pos1);
+}
+
+/**/
+void
+upcase(char **x)
+{
+    char *pp = *(char **)x;
+
+    for (; *pp; pp++)
+	*pp = tuupper(*pp);
+}
+
+/**/
+void
+downcase(char **x)
+{
+    char *pp = *(char **)x;
+
+    for (; *pp; pp++)
+	*pp = tulower(*pp);
+}
+
+/**/
+int
+quote(char **tr)
+{
+    char *ptr, *rptr, **str = (char **)tr;
+    int len = 3;
+    int inquotes = 0;
+
+    for (ptr = *str; *ptr; ptr++, len++)
+	if (*ptr == '\'') {
+	    len += 3;
+	    if (!inquotes)
+		inquotes = 1;
+	    else
+		inquotes = 0;
+	} else if (inblank(*ptr) && !inquotes && ptr[-1] != '\\')
+	    len += 2;
+    ptr = *str;
+    *str = rptr = (char *)alloc(len);
+    *rptr++ = '\'';
+    for (; *ptr; ptr++)
+	if (*ptr == '\'') {
+	    if (!inquotes)
+		inquotes = 1;
+	    else
+		inquotes = 0;
+	    *rptr++ = '\'';
+	    *rptr++ = '\\';
+	    *rptr++ = '\'';
+	    *rptr++ = '\'';
+	} else if (inblank(*ptr) && !inquotes && ptr[-1] != '\\') {
+	    *rptr++ = '\'';
+	    *rptr++ = *ptr;
+	    *rptr++ = '\'';
+	} else
+	    *rptr++ = *ptr;
+    *rptr++ = '\'';
+    *rptr++ = 0;
+    str[1] = NULL;
+    return 0;
+}
+
+/**/
+static int
+quotebreak(char **tr)
+{
+    char *ptr, *rptr, **str = (char **)tr;
+    int len = 3;
+
+    for (ptr = *str; *ptr; ptr++, len++)
+	if (*ptr == '\'')
+	    len += 3;
+	else if (inblank(*ptr))
+	    len += 2;
+    ptr = *str;
+    *str = rptr = (char *)alloc(len);
+    *rptr++ = '\'';
+    for (; *ptr;)
+	if (*ptr == '\'') {
+	    *rptr++ = '\'';
+	    *rptr++ = '\\';
+	    *rptr++ = '\'';
+	    *rptr++ = '\'';
+	    ptr++;
+	} else if (inblank(*ptr)) {
+	    *rptr++ = '\'';
+	    *rptr++ = *ptr++;
+	    *rptr++ = '\'';
+	} else
+	    *rptr++ = *ptr++;
+    *rptr++ = '\'';
+    *rptr++ = '\0';
+    return 0;
+}
+
+#if 0
+/* read an arbitrary amount of data into a buffer until stop is found */
+
+/**/
+char *
+hdynread(int stop)
+{
+    int bsiz = 256, ct = 0, c;
+    char *buf = (char *)zalloc(bsiz), *ptr;
+
+    ptr = buf;
+    while ((c = ingetc()) != stop && c != '\n' && !lexstop) {
+	if (c == '\\')
+	    c = ingetc();
+	*ptr++ = c;
+	if (++ct == bsiz) {
+	    buf = realloc(buf, bsiz *= 2);
+	    ptr = buf + ct;
+	}
+    }
+    *ptr = 0;
+    if (c == '\n') {
+	inungetc('\n');
+	zerr("delimiter expected", NULL, 0);
+	zfree(buf, bsiz);
+	return NULL;
+    }
+    return buf;
+}
+#endif
+
+/**/
+static char *
+hdynread2(int stop)
+{
+    int bsiz = 256, ct = 0, c;
+    char *buf = (char *)zalloc(bsiz), *ptr;
+
+    ptr = buf;
+    while ((c = ingetc()) != stop && c != '\n' && !lexstop) {
+	if (c == '\n') {
+	    inungetc(c);
+	    break;
+	}
+	if (c == '\\')
+	    c = ingetc();
+	*ptr++ = c;
+	if (++ct == bsiz) {
+	    buf = realloc(buf, bsiz *= 2);
+	    ptr = buf + ct;
+	}
+    }
+    *ptr = 0;
+    if (c == '\n')
+	inungetc('\n');
+    return buf;
+}
+
+/**/
+void
+inithist(void)
+{
+    histentct = histsiz;
+    histentarr = (Histent) zcalloc(histentct * sizeof *histentarr);
+}
+
+/**/
+void
+resizehistents(void)
+{
+    int newentct, t0, t1, firstlex;
+    Histent newarr;
+
+    newentct = histsiz;
+    newarr = (Histent) zcalloc(newentct * sizeof *newarr);
+    firstlex = curhist - histsiz + 1;
+    t0 = firsthist();
+    if (t0 < curhist - newentct)
+	t0 = curhist - newentct;
+    t1 = t0 % newentct;
+    for (; t0 <= curhist; t0++) {
+	newarr[t1] = *gethistent(t0);
+	if (t0 < firstlex) {
+	    zsfree(newarr[t1].text);
+	    newarr[t1].text = NULL;
+	}
+	t1++;
+	if (t1 == newentct)
+	    t1 = 0;
+    }
+    free(histentarr);
+    histentarr = newarr;
+    histentct = newentct;
+}
+
+/**/
+void
+readhistfile(char *s, int err)
+{
+    char *buf;
+    FILE *in;
+    Histent ent;
+    time_t tim = time(NULL);
+    short *wordlist;
+    int nwordpos, nwordlist, bufsiz;
+
+    if (!s)
+	return;
+    if ((in = fopen(unmeta(s), "r"))) {
+	nwordlist = 16;
+	wordlist = (short *)zalloc(nwordlist*sizeof(short));
+	bufsiz = 1024;
+	buf = zalloc(bufsiz);
+
+	while (fgets(buf, bufsiz, in)) {
+	    int l = strlen(buf);
+	    char *pt, *start;
+
+	    while (l) {
+		while (buf[l - 1] != '\n') {
+		    buf = zrealloc(buf, 2 * bufsiz);
+		    bufsiz = 2 * bufsiz;
+		    if (!fgets(buf + l, bufsiz - l, in)) {
+			l++;
+			break;
+		    }
+		    l = strlen(buf);
+		}
+		buf[l - 1] = '\0';
+		if (l > 1 && buf[l - 2] == '\\') {
+		    buf[l - 2] = '\n';
+		    fgets(buf + l - 1, bufsiz - (l - 1), in);
+		    l = strlen(buf);
+		} else
+		    break;
+	    }
+
+	    ent = gethistent(++curhist);
+	    pt = buf;
+	    if (*pt == ':') {
+		pt++;
+		ent->stim = zstrtol(pt, NULL, 0);
+		for (; *pt != ':' && *pt; pt++);
+		if (*pt) {
+		    pt++;
+		    ent->ftim = zstrtol(pt, NULL, 0);
+		    for (; *pt != ';' && *pt; pt++);
+		    if (*pt)
+			pt++;
+		} else {
+		    ent->ftim = tim;
+		}
+		if (ent->stim == 0)
+		    ent->stim = tim;
+		if (ent->ftim == 0)
+		    ent->ftim = tim;
+	    } else {
+		ent->ftim = ent->stim = tim;
+	    }
+
+	    zsfree(ent->text);
+	    ent->text = ztrdup(pt);
+	    ent->flags = HIST_OLD|HIST_READ;
+	    if (ent->nwords)
+		zfree(ent->words, ent->nwords*2*sizeof(short));
+
+	    /* Divide up the words.  We don't know how it lexes,
+	       so just look for spaces.
+	       */
+	    nwordpos = 0;
+	    start = pt;
+	    do {
+		while (*pt == ' ')
+		    pt++;
+		if (*pt) {
+		    if (nwordpos >= nwordlist)
+			wordlist = (short *) realloc(wordlist,
+					(nwordlist += 16)*sizeof(short));
+		    wordlist[nwordpos++] = pt - start;
+		    while (*pt && *pt != ' ')
+			pt++;
+		    wordlist[nwordpos++] = pt - start;
+		}
+	    } while (*pt);
+
+	    ent->nwords = nwordpos/2;
+	    if (ent->nwords) {
+		ent->words = (short *)zalloc(nwordpos*sizeof(short));
+		memcpy(ent->words, wordlist, nwordpos*sizeof(short));
+	    } else
+		ent->words = (short *)NULL;
+	}
+	fclose(in);
+
+	zfree(wordlist, nwordlist*sizeof(short));
+	zfree(buf, bufsiz);
+    } else if (err)
+	zerr("can't read history file", s, 0);
+}
+
+/**/
+void
+savehistfile(char *s, int err, int app)
+{
+    char *t;
+    FILE *out;
+    int ev;
+    Histent ent;
+    int savehist = getiparam("SAVEHIST");
+
+    if (!s || !interact || savehist <= 0)
+	return;
+    ev = curhist - savehist + 1;
+    if (ev < firsthist())
+	ev = firsthist();
+    if (app & 1)
+	out = fdopen(open(unmeta(s),
+		     O_CREAT | O_WRONLY | O_APPEND | O_NOCTTY, 0600), "a");
+    else
+	out = fdopen(open(unmeta(s),
+		     O_CREAT | O_WRONLY | O_TRUNC | O_NOCTTY, 0600), "w");
+    if (out) {
+	for (; ev <= curhist - !!(histactive & HA_ACTIVE); ev++) {
+	    ent = gethistent(ev);
+	    if (app & 2) {
+		if (ent->flags & HIST_OLD)
+		    continue;
+		ent->flags |= HIST_OLD;
+	    }
+	    t = ent->text;
+	    if (isset(EXTENDEDHISTORY)) {
+		fprintf(out, ": %ld:%ld;",
+			(long)ent->stim,
+			(long)ent->ftim);
+	    } else if (*t == ':')
+		fputc('\\', out);
+
+	    for (; *t; t++) {
+		if (*t == '\n')
+		    fputc('\\', out);
+		fputc(*t, out);
+	    }
+	    fputc('\n', out);
+	}
+	fclose(out);
+
+	if (app & 2 && (out = fopen(unmeta(s), "r"))) {
+	    char **store, buf[1024], **ptr;
+	    int i, l, histnum = 0;
+
+	    store = (char **)zcalloc((savehist + 1) * sizeof *store);
+	    while (fgets(buf, sizeof(buf), out)) {
+		char *t;
+
+		if (store[i = histnum % savehist])
+		    free(store[i]);
+		store[i] = ztrdup(buf);
+		l = strlen(buf);
+		if (l > 1) {
+		    t = store[i] + l;
+		    while ((t[-1] != '\n' ||
+			    (t[-1] == '\n' && t[-2] == '\\')) &&
+			   fgets(buf, sizeof(buf), out)) {
+			l += strlen(buf);
+			store[i] = zrealloc(store[i], l + 1);
+			t = store[i] + l;
+			strcat(store[i], buf);
+		    }
+		}
+		histnum++;
+	    }
+	    fclose(out);
+	    if ((out = fdopen(open(unmeta(s),
+			    O_WRONLY | O_TRUNC | O_NOCTTY, 0600), "w"))) {
+		if (histnum < savehist)
+		    for (i = 0; i < histnum; i++)
+			fprintf(out, "%s", store[i]);
+		else
+		    for (i = histnum; i < histnum + savehist; i++)
+			fprintf(out, "%s", store[i % savehist]);
+		fclose(out);
+	    }
+	    for (ptr = store; *ptr; ptr++)
+		zsfree(*ptr);
+	    free(store);
+	}
+    } else if (err)
+	zerr("can't write history file %s", s, 0);
+}
+
+/**/
+int
+firsthist(void)
+{
+    int ev;
+    Histent ent;
+
+    ev = curhist - histentct + 1;
+    if (ev < 1)
+	ev = 1;
+    do {
+	ent = gethistent(ev);
+	if (ent->text)
+	    break;
+	ev++;
+    }
+    while (ev < curhist);
+    return ev;
+}
+
diff --git a/Src/init.c b/Src/init.c
new file mode 100644
index 000000000..33496adc6
--- /dev/null
+++ b/Src/init.c
@@ -0,0 +1,936 @@
+/*
+ * init.c - main loop and initialization routines
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "init.pro"
+
+#include "zshpaths.h"
+#include "zshxmods.h"
+
+/**/
+int noexitct = 0;
+
+/* what level of sourcing we are at */
+ 
+/**/
+int sourcelevel;
+
+/* the shell tty fd */
+
+/**/
+int SHTTY;
+
+/* the FILE attached to the shell tty */
+
+/**/
+FILE *shout;
+
+/* termcap strings */
+ 
+/**/
+char *tcstr[TC_COUNT];
+
+/* lengths of each termcap string */
+ 
+/**/
+int tclen[TC_COUNT];
+
+/* Values of the li, co and am entries */
+
+/**/
+int tclines, tccolumns, hasam;
+
+#ifdef DEBUG
+/* depth of allocation type stack */
+
+/**/
+int alloc_stackp;
+#endif
+
+/* keep executing lists until EOF found */
+
+/**/
+void
+loop(int toplevel, int justonce)
+{
+    List list;
+#ifdef DEBUG
+    int oasp = toplevel ? 0 : alloc_stackp;
+#endif
+
+    pushheap();
+    for (;;) {
+	freeheap();
+	errflag = 0;
+	if (isset(SHINSTDIN)) {
+	    setblock_stdin();
+	    if (interact)
+		preprompt();
+	}
+	hbegin();		/* init history mech        */
+	intr();			/* interrupts on            */
+	lexinit();              /* initialize lexical state */
+	if (!(list = parse_event())) {	/* if we couldn't parse a list */
+	    hend();
+	    if ((tok == ENDINPUT && !errflag) ||
+		(tok == LEXERR && (!isset(SHINSTDIN) || !toplevel)) ||
+		justonce)
+		break;
+	    continue;
+	}
+	if (hend()) {
+	    int toksav = tok;
+	    List prelist;
+
+	    if (toplevel && (prelist = getshfunc("preexec")) != &dummy_list) {
+		Histent he = gethistent(curhist);
+		LinkList args;
+		PERMALLOC {
+		    args = newlinklist();
+		    addlinknode(args, "preexec");
+		    if (he && he->text)
+			addlinknode(args, he->text);
+		} LASTALLOC;
+		doshfunc(prelist, args, 0, 1);
+		freelinklist(args, (FreeFunc) NULL);
+		errflag = 0;
+	    }
+	    if (stopmsg)	/* unset 'you have stopped jobs' flag */
+		stopmsg--;
+	    execlist(list, 0, 0);
+	    tok = toksav;
+	    if (toplevel)
+		noexitct = 0;
+	}
+	DPUTS(alloc_stackp != oasp, "BUG: alloc_stackp changed in loop()");
+	if (ferror(stderr)) {
+	    zerr("write error", NULL, 0);
+	    clearerr(stderr);
+	}
+	if (subsh)		/* how'd we get this far in a subshell? */
+	    exit(lastval);
+	if (((!interact || sourcelevel) && errflag) || retflag)
+	    break;
+	if (trapreturn) {
+	    lastval = trapreturn;
+	    trapreturn = 0;
+	}
+	if (isset(SINGLECOMMAND) && toplevel) {
+	    if (sigtrapped[SIGEXIT])
+		dotrap(SIGEXIT);
+	    exit(lastval);
+	}
+	if (justonce)
+	    break;
+    }
+    popheap();
+}
+
+static char *cmd;
+static int restricted;
+
+/**/
+void
+parseargs(char **argv)
+{
+    char **x;
+    int action, optno;
+    LinkList paramlist;
+    int bourne = (emulation == EMULATE_KSH || emulation == EMULATE_SH);
+
+    argzero = *argv++;
+    SHIN = 0;
+
+    /* There's a bit of trickery with opts[INTERACTIVE] here.  It starts *
+     * at a value of 2 (instead of 1) or 0.  If it is explicitly set on  *
+     * the command line, it goes to 1 or 0.  If input is coming from     *
+     * somewhere that normally makes the shell non-interactive, we do    *
+     * "opts[INTERACTIVE] &= 1", so that only a *default* on state will  *
+     * be changed.  At the end of the function, a value of 2 gets        *
+     * changed to 1.                                                     */
+    opts[INTERACTIVE] = isatty(0) ? 2 : 0;
+    opts[SHINSTDIN] = 0;
+    opts[SINGLECOMMAND] = 0;
+
+    /* loop through command line options (begins with "-" or "+") */
+    while (*argv && (**argv == '-' || **argv == '+')) {
+	action = (**argv == '-');
+	if(!argv[0][1])
+	    *argv = "--";
+	while (*++*argv) {
+	    /* The pseudo-option `--' signifies the end of options. *
+	     * `-b' does too, csh-style, unless we're emulating a   *
+	     * Bourne style shell.                                  */
+	    if (**argv == '-' || (!bourne && **argv == 'b')) {
+		argv++;
+		goto doneoptions;
+	    }
+
+	    if (**argv == 'c') {         /* -c command */
+		if (!*++argv) {
+		    zerr("string expected after -c", NULL, 0);
+		    exit(1);
+		}
+		cmd = *argv++;
+		opts[INTERACTIVE] &= 1;
+		opts[SHINSTDIN] = 0;
+		goto doneoptions;
+	    } else if (**argv == 'o') {
+		if (!*++*argv)
+		    argv++;
+		if (!*argv) {
+		    zerr("string expected after -o", NULL, 0);
+		    exit(1);
+		}
+		if(!(optno = optlookup(*argv)))
+		    zerr("no such option: %s", *argv, 0);
+		else if (optno == RESTRICTED)
+		    restricted = action;
+		else
+		    dosetopt(optno, action, 1);
+		break;
+	    } else {
+	    	if (!(optno = optlookupc(**argv))) {
+		    zerr("bad option: -%c", NULL, **argv);
+		    exit(1);
+		} else if (optno == RESTRICTED)
+		    restricted = action;
+		else
+		    dosetopt(optno, action, 1);
+	    }
+	}
+	argv++;
+    }
+    doneoptions:
+    paramlist = newlinklist();
+    if (*argv) {
+	if (unset(SHINSTDIN)) {
+	    argzero = *argv;
+	    if (!cmd)
+		SHIN = movefd(open(unmeta(argzero), O_RDONLY | O_NOCTTY));
+	    if (SHIN == -1) {
+		zerr("can't open input file: %s", argzero, 0);
+		exit(1);
+	    }
+	    opts[INTERACTIVE] &= 1;
+	    argv++;
+	}
+	while (*argv)
+	    addlinknode(paramlist, ztrdup(*argv++));
+    } else
+	opts[SHINSTDIN] = 1;
+    if(isset(SINGLECOMMAND))
+	opts[INTERACTIVE] &= 1;
+    opts[INTERACTIVE] = !!opts[INTERACTIVE];
+    pparams = x = (char **) zcalloc((countlinknodes(paramlist) + 1) * sizeof(char *));
+
+    while ((*x++ = (char *)getlinknode(paramlist)));
+    free(paramlist);
+    argzero = ztrdup(argzero);
+}
+
+
+/**/
+void
+init_io(void)
+{
+    long ttpgrp;
+    static char outbuf[BUFSIZ], errbuf[BUFSIZ];
+
+#ifdef RSH_BUG_WORKAROUND
+    int i;
+#endif
+
+/* stdout, stderr fully buffered */
+#ifdef _IOFBF
+    setvbuf(stdout, outbuf, _IOFBF, BUFSIZ);
+    setvbuf(stderr, errbuf, _IOFBF, BUFSIZ);
+#else
+    setbuffer(stdout, outbuf, BUFSIZ);
+    setbuffer(stderr, errbuf, BUFSIZ);
+#endif
+
+/* This works around a bug in some versions of in.rshd. *
+ * Currently this is not defined by default.            */
+#ifdef RSH_BUG_WORKAROUND
+    if (cmd) {
+	for (i = 3; i < 10; i++)
+	    close(i);
+    }
+#endif
+
+    if (shout) {
+	fclose(shout);
+	shout = 0;
+    }
+    if (SHTTY != -1) {
+	zclose(SHTTY);
+	SHTTY = -1;
+    }
+
+    /* Make sure the tty is opened read/write. */
+    if (isatty(0)) {
+	zsfree(ttystrname);
+	if ((ttystrname = ztrdup(ttyname(0))))
+	    SHTTY = movefd(open(ttystrname, O_RDWR | O_NOCTTY));
+    }
+    if (SHTTY == -1 &&
+	(SHTTY = movefd(open("/dev/tty", O_RDWR | O_NOCTTY))) != -1) {
+	zsfree(ttystrname);
+	ttystrname = ztrdup("/dev/tty");
+    }
+    if (SHTTY == -1) {
+	zsfree(ttystrname);
+	ttystrname = ztrdup("");
+    }
+
+    /* We will only use zle if shell is interactive, *
+     * SHTTY != -1, and shout != 0                   */
+    if (interact && SHTTY != -1) {
+	init_shout();
+	if(!shout)
+	    opts[USEZLE] = 0;
+    } else
+	opts[USEZLE] = 0;
+
+#ifdef JOB_CONTROL
+    /* If interactive, make the shell the foreground process */
+    if (opts[MONITOR] && interact && (SHTTY != -1)) {
+	attachtty(GETPGRP());
+	if ((mypgrp = GETPGRP()) > 0) {
+	    while ((ttpgrp = gettygrp()) != -1 && ttpgrp != mypgrp) {
+		sleep(1);
+		mypgrp = GETPGRP();
+		if (mypgrp == gettygrp())
+		    break;
+		killpg(mypgrp, SIGTTIN);
+		mypgrp = GETPGRP();
+	    }
+	} else
+	    opts[MONITOR] = 0;
+    } else
+	opts[MONITOR] = 0;
+#else
+    opts[MONITOR] = 0;
+#endif
+}
+
+/**/
+void
+init_shout(void)
+{
+    static char shoutbuf[BUFSIZ];
+#if defined(JOB_CONTROL) && defined(TIOCSETD) && defined(NTTYDISC)
+    int ldisc = NTTYDISC;
+
+    ioctl(SHTTY, TIOCSETD, (char *)&ldisc);
+#endif
+
+    /* Associate terminal file descriptor with a FILE pointer */
+    shout = fdopen(SHTTY, "w");
+#ifdef _IOFBF
+    setvbuf(shout, shoutbuf, _IOFBF, BUFSIZ);
+#endif
+  
+    gettyinfo(&shttyinfo);	/* get tty state */
+#if defined(__sgi)
+    if (shttyinfo.tio.c_cc[VSWTCH] <= 0)	/* hack for irises */
+	shttyinfo.tio.c_cc[VSWTCH] = CSWTCH;
+#endif
+}
+
+/* names of the termcap strings we want */
+
+static char *tccapnams[TC_COUNT] = {
+    "cl", "le", "LE", "nd", "RI", "up", "UP", "do",
+    "DO", "dc", "DC", "ic", "IC", "cd", "ce", "al", "dl", "ta",
+    "md", "so", "us", "me", "se", "ue"
+};
+
+/* Initialise termcap */
+
+/**/
+int
+init_term(void)
+{
+#ifndef TGETENT_ACCEPTS_NULL
+    static char termbuf[2048];	/* the termcap buffer */
+#endif
+
+    if (!*term) {
+	termflags |= TERM_UNKNOWN;
+	return 0;
+    }
+
+    /* unset zle if using zsh under emacs */
+    if (!strcmp(term, "emacs"))
+	opts[USEZLE] = 0;
+
+#ifdef TGETENT_ACCEPTS_NULL
+    /* If possible, we let tgetent allocate its own termcap buffer */
+    if (tgetent(NULL, term) != 1) {
+#else
+    if (tgetent(termbuf, term) != 1) {
+#endif
+
+	if (isset(INTERACTIVE))
+	    zerr("can't find termcap info for %s", term, 0);
+	errflag = 0;
+	termflags |= TERM_BAD;
+	return 0;
+    } else {
+	char tbuf[1024], *pp;
+	int t0;
+
+	termflags &= ~TERM_BAD;
+	termflags &= ~TERM_UNKNOWN;
+	for (t0 = 0; t0 != TC_COUNT; t0++) {
+	    pp = tbuf;
+	    zsfree(tcstr[t0]);
+	/* AIX tgetstr() ignores second argument */
+	    if (!(pp = tgetstr(tccapnams[t0], &pp)))
+		tcstr[t0] = NULL, tclen[t0] = 0;
+	    else {
+		tclen[t0] = strlen(pp);
+		tcstr[t0] = (char *) zalloc(tclen[t0] + 1);
+		memcpy(tcstr[t0], pp, tclen[t0] + 1);
+	    }
+	}
+
+	/* check whether terminal has automargin (wraparound) capability */
+	hasam = tgetflag("am");
+
+	tclines = tgetnum("li");
+	tccolumns = tgetnum("co");
+
+	/* if there's no termcap entry for cursor up, use single line mode: *
+	 * this is flagged by termflags which is examined in zle_refresh.c  *
+	 */
+	if (tccan(TCUP))
+	    termflags &= ~TERM_NOUP;
+	else {
+	    tcstr[TCUP] = NULL;
+	    termflags |= TERM_NOUP;
+	}
+
+	/* if there's no termcap entry for cursor left, use \b. */
+	if (!tccan(TCLEFT)) {
+	    tcstr[TCLEFT] = ztrdup("\b");
+	    tclen[TCLEFT] = 1;
+	}
+
+	/* if the termcap entry for down is \n, don't use it. */
+	if (tccan(TCDOWN) && tcstr[TCDOWN][0] == '\n') {
+	    tclen[TCDOWN] = 0;
+	    zsfree(tcstr[TCDOWN]);
+	    tcstr[TCDOWN] = NULL;
+	}
+
+	/* if there's no termcap entry for clear, use ^L. */
+	if (!tccan(TCCLEARSCREEN)) {
+	    tcstr[TCCLEARSCREEN] = ztrdup("\14");
+	    tclen[TCCLEARSCREEN] = 1;
+	}
+    }
+    return 1;
+}
+
+/* Initialize lots of global variables and hash tables */
+
+/**/
+void
+setupvals(void)
+{
+#ifdef HAVE_GETPWUID
+    struct passwd *pswd;
+#endif
+    struct timezone dummy_tz;
+    char *ptr;
+#ifdef HAVE_GETRLIMIT
+    int i;
+#endif
+
+    noeval = 0;
+    curhist = 0;
+    histsiz = DEFAULT_HISTSIZE;
+    inithist();
+
+    cmdstack = (unsigned char *) zalloc(256);
+    cmdsp = 0;
+
+    bangchar = '!';
+    hashchar = '#';
+    hatchar = '^';
+    termflags = TERM_UNKNOWN;
+    curjob = prevjob = coprocin = coprocout = -1;
+    gettimeofday(&shtimer, &dummy_tz);	/* init $SECONDS */
+    srand((unsigned int)(shtimer.tv_sec + shtimer.tv_usec)); /* seed $RANDOM */
+
+    hostnam     = (char *) zalloc(256);
+    gethostname(hostnam, 256);
+
+    /* Set default path */
+    path    = (char **) zalloc(sizeof(*path) * 5);
+    path[0] = ztrdup("/bin");
+    path[1] = ztrdup("/usr/bin");
+    path[2] = ztrdup("/usr/ucb");
+    path[3] = ztrdup("/usr/local/bin");
+    path[4] = NULL;
+
+    cdpath   = mkarray(NULL);
+    manpath  = mkarray(NULL);
+    fignore  = mkarray(NULL);
+    fpath    = mkarray(NULL);
+    mailpath = mkarray(NULL);
+    watch    = mkarray(NULL);
+    psvar    = mkarray(NULL);
+#ifdef DYNAMIC
+    module_path = mkarray(ztrdup(MODULE_DIR));
+    modules = newlinklist();
+#endif
+
+    /* Set default prompts */
+    if(unset(INTERACTIVE)) {
+	prompt = ztrdup("");
+	prompt2 = ztrdup("");
+    } else if (emulation == EMULATE_KSH || emulation == EMULATE_SH) {
+	prompt  = ztrdup(privasserted() ? "# " : "$ ");
+	prompt2 = ztrdup("> ");
+    } else {
+	prompt  = ztrdup("%m%# ");
+	prompt2 = ztrdup("%_> ");
+    }
+    prompt3 = ztrdup("?# ");
+    prompt4 = ztrdup("+ ");
+    sprompt = ztrdup("zsh: correct '%R' to '%r' [nyae]? ");
+
+    ifs         = ztrdup(DEFAULT_IFS);
+    wordchars   = ztrdup(DEFAULT_WORDCHARS);
+    postedit    = ztrdup("");
+    underscore  = ztrdup("");
+
+    zoptarg = ztrdup("");
+    zoptind = 1;
+
+    ppid  = (long) getppid();
+    mypid = (long) getpid();
+    term  = ztrdup("");
+
+    /* The following variable assignments cause zsh to behave more *
+     * like Bourne and Korn shells when invoked as "sh" or "ksh".  *
+     * NULLCMD=":" and READNULLCMD=":"                             */
+
+    if (emulation == EMULATE_KSH || emulation == EMULATE_SH) {
+	nullcmd     = ztrdup(":");
+	readnullcmd = ztrdup(":");
+    } else {
+	nullcmd     = ztrdup("cat");
+	readnullcmd = ztrdup("more");
+    }
+
+    /* We cache the uid so we know when to *
+     * recheck the info for `USERNAME'     */
+    cached_uid = getuid();
+
+    /* Get password entry and set info for `HOME' and `USERNAME' */
+#ifdef HAVE_GETPWUID
+    if ((pswd = getpwuid(cached_uid))) {
+	home = metafy(pswd->pw_dir, -1, META_DUP);
+	cached_username = ztrdup(pswd->pw_name);
+    } else
+#endif /* HAVE_GETPWUID */
+	   {
+	home = ztrdup("/");
+	cached_username = ztrdup("");
+    }
+
+    /* Try a cheap test to see if we can *
+     * initialize `PWD' from `HOME'      */
+    if (ispwd(home))
+	pwd = ztrdup(home);
+    else if ((ptr = zgetenv("PWD")) && ispwd(ptr))
+	pwd = ztrdup(ptr);
+    else
+	pwd = metafy(zgetcwd(), -1, META_DUP);
+
+    oldpwd = ztrdup(pwd);  /* initialize `OLDPWD' = `PWD' */
+
+    inittyptab();     /* initialize the ztypes table */
+    initlextabs();    /* initialize lexing tables    */
+
+    createreswdtable();     /* create hash table for reserved words    */
+    createaliastable();     /* create hash table for aliases           */
+    createcmdnamtable();    /* create hash table for external commands */
+    createshfunctable();    /* create hash table for shell functions   */
+    createbuiltintable();   /* create hash table for builtin commands  */
+    createnameddirtable();  /* create hash table for named directories */
+    createparamtable();     /* create paramater hash table             */
+
+#ifdef TIOCGWINSZ
+    adjustwinsize();
+#else
+    /* Using zero below sets the defaults from termcap */
+    setiparam("COLUMNS", 0);
+    setiparam("LINES", 0);
+#endif
+
+#ifdef HAVE_GETRLIMIT
+    for (i = 0; i != RLIM_NLIMITS; i++) {
+	getrlimit(i, current_limits + i);
+	limits[i] = current_limits[i];
+    }
+#endif
+
+    breaks = loops = 0;
+    lastmailcheck = time(NULL);
+    locallevel = sourcelevel = 0;
+    trapreturn = 0;
+    noerrexit = -1;
+    nohistsave = 1;
+    dirstack = newlinklist();
+    bufstack = newlinklist();
+    prepromptfns = newlinklist();
+    hsubl = hsubr = NULL;
+    lastpid = 0;
+    bshin = SHIN ? fdopen(SHIN, "r") : stdin;
+    if (isset(SHINSTDIN) && !SHIN && unset(INTERACTIVE)) {
+#ifdef _IONBF
+	setvbuf(stdin, NULL, _IONBF, 0);
+#else
+	setlinebuf(stdin);
+#endif
+    }
+
+    times(&shtms);
+}
+
+/* Initialize signal handling */
+
+/**/
+void
+init_signals(void)
+{
+    intr();
+
+#ifndef QDEBUG
+    signal_ignore(SIGQUIT);
+#endif
+
+    install_handler(SIGHUP);
+    install_handler(SIGCHLD);
+#ifdef SIGWINCH
+    install_handler(SIGWINCH);
+#endif
+    if (interact) {
+	install_handler(SIGALRM);
+	signal_ignore(SIGTERM);
+    }
+    if (jobbing) {
+	long ttypgrp;
+
+	while ((ttypgrp = gettygrp()) != -1 && ttypgrp != mypgrp)
+	    kill(0, SIGTTIN);
+	if (ttypgrp == -1) {
+	    opts[MONITOR] = 0;
+	} else {
+	    signal_ignore(SIGTTOU);
+	    signal_ignore(SIGTSTP);
+	    signal_ignore(SIGTTIN);
+	    attachtty(mypgrp);
+	}
+    }
+    if (islogin) {
+	signal_setmask(signal_mask(0));
+    } else if (interact) {
+	sigset_t set;
+
+	sigemptyset(&set);
+	sigaddset(&set, SIGINT);
+	sigaddset(&set, SIGQUIT);
+	signal_unblock(set);
+    }
+}
+
+/* Source the init scripts.  If called as "ksh" or "sh"  *
+ * then we source the standard sh/ksh scripts instead of *
+ * the standard zsh scripts                              */
+
+/**/
+void
+run_init_scripts(void)
+{
+    noerrexit = -1;
+
+    if (emulation == EMULATE_KSH || emulation == EMULATE_SH) {
+	if (islogin)
+	    source("/etc/profile");
+	if (unset(PRIVILEGED)) {
+	    char *s = getsparam("ENV");
+	    if (islogin)
+		sourcehome(".profile");
+	    noerrs = 1;
+	    if (s && !parsestr(s)) {
+		singsub(&s);
+		noerrs = 0;
+		source(s);
+	    }
+	    noerrs = 0;
+	} else
+	    source("/etc/suid_profile");
+    } else {
+#ifdef GLOBAL_ZSHENV
+	source(GLOBAL_ZSHENV);
+#endif
+	if (isset(RCS)) {
+	    if (unset(PRIVILEGED))
+		sourcehome(".zshenv");
+	    if (islogin) {
+#ifdef GLOBAL_ZPROFILE
+		source(GLOBAL_ZPROFILE);
+#endif
+		if (unset(PRIVILEGED))
+		    sourcehome(".zprofile");
+	    }
+	    if (interact) {
+#ifdef GLOBAL_ZSHRC
+		source(GLOBAL_ZSHRC);
+#endif
+		if (unset(PRIVILEGED))
+		    sourcehome(".zshrc");
+	    }
+	    if (islogin) {
+#ifdef GLOBAL_ZLOGIN
+		source(GLOBAL_ZLOGIN);
+#endif
+		if (unset(PRIVILEGED))
+		    sourcehome(".zlogin");
+	    }
+	}
+    }
+    noerrexit = 0;
+    nohistsave = 0;
+}
+
+/* Miscellaneous initializations that happen after init scripts are run */
+
+/**/
+void
+init_misc(void)
+{
+    if (*zsh_name == 'r' || restricted)
+	dosetopt(RESTRICTED, 1, 0);
+    if (cmd) {
+	if (SHIN >= 10)
+	    fclose(bshin);
+	SHIN = movefd(open("/dev/null", O_RDONLY | O_NOCTTY));
+	bshin = fdopen(SHIN, "r");
+	execstring(cmd, 0, 1);
+	stopmsg = 1;
+	zexit(lastval, 0);
+    }
+
+    if (interact && isset(RCS))
+	readhistfile(getsparam("HISTFILE"), 0);
+}
+
+/* source a file */
+
+/**/
+int
+source(char *s)
+{
+    int tempfd, fd, cj, oldlineno;
+    int oldshst, osubsh, oloops;
+    FILE *obshin;
+    char *old_scriptname = scriptname;
+
+    if (!s || (tempfd = movefd(open(unmeta(s), O_RDONLY | O_NOCTTY))) == -1) {
+	return 1;
+    }
+
+    /* save the current shell state */
+    fd        = SHIN;            /* store the shell input fd                  */
+    obshin    = bshin;          /* store file handle for buffered shell input */
+    osubsh    = subsh;           /* store whether we are in a subshell        */
+    cj        = thisjob;         /* store our current job number              */
+    oldlineno = lineno;          /* store our current lineno                  */
+    oloops    = loops;           /* stored the # of nested loops we are in    */
+    oldshst   = opts[SHINSTDIN]; /* store current value of this option        */
+
+    SHIN = tempfd;
+    bshin = fdopen(SHIN, "r");
+    subsh  = 0;
+    lineno = 0;
+    loops  = 0;
+    dosetopt(SHINSTDIN, 0, 1);
+    scriptname = s;
+
+    sourcelevel++;
+    loop(0, 0);			/* loop through the file to be sourced        */
+    sourcelevel--;
+    fclose(bshin);
+    fdtable[SHIN] = 0;
+
+    /* restore the current shell state */
+    SHIN = fd;                       /* the shell input fd                   */
+    bshin = obshin;                  /* file handle for buffered shell input */
+    subsh = osubsh;                  /* whether we are in a subshell         */
+    thisjob = cj;                    /* current job number                   */
+    lineno = oldlineno;              /* our current lineno                   */
+    loops = oloops;                  /* the # of nested loops we are in      */
+    dosetopt(SHINSTDIN, oldshst, 1); /* SHINSTDIN option                     */
+    errflag = 0;
+    retflag = 0;
+    scriptname = old_scriptname;
+
+    return 0;
+}
+
+/* Try to source a file in the home directory */
+
+/**/
+void
+sourcehome(char *s)
+{
+    char buf[PATH_MAX];
+    char *h;
+
+    if (emulation == EMULATE_SH || emulation == EMULATE_KSH ||
+	!(h = getsparam("ZDOTDIR")))
+	h = home;
+    if (strlen(h) + strlen(s) + 1 >= PATH_MAX) {
+	zerr("path too long: %s", s, 0);
+	return;
+    }
+    sprintf(buf, "%s/%s", h, s);
+    source(buf);
+}
+
+/**/
+void
+init_bltinmods(void)
+{
+    static struct module mod = { NULL, 0, NULL, NULL };
+#include "bltinmods.list"
+    mod.nam = NULL;
+}
+
+/* ZLE entry point pointers.  They are defined here because the initial *
+ * values depend on whether ZLE is linked in or not -- if it is, we     *
+ * avoid wasting space with the fallback functions.  No other source    *
+ * file needs to know which modules are linked in.                      */
+
+#ifdef LINKED_XMOD_zle
+
+/**/
+ZleVoidFn trashzleptr;
+/**/
+ZleVoidFn gotwordptr;
+/**/
+ZleVoidFn refreshptr;
+/**/
+ZleVoidIntFn spaceinlineptr;
+/**/
+ZleReadFn zlereadptr;
+
+#else /* !LINKED_XMOD_zle */
+
+ZleVoidFn trashzleptr = noop_function;
+ZleVoidFn gotwordptr = noop_function;
+ZleVoidFn refreshptr = noop_function;
+ZleVoidIntFn spaceinlineptr = noop_function_int;
+# ifdef UNLINKED_XMOD_zle
+ZleReadFn zlereadptr = autoload_zleread;
+# else /* !UNLINKED_XMOD_zle */
+ZleReadFn zlereadptr = fallback_zleread;
+# endif /* !UNLINKED_XMOD_zle */
+
+/**/
+void
+noop_function(void)
+{
+    /* do nothing */
+}
+
+/**/
+void
+noop_function_int(int nothing)
+{
+    /* do nothing */
+}
+
+# ifdef UNLINKED_XMOD_zle
+
+/**/
+static unsigned char *
+autoload_zleread(char *lp, char *rp, int ha)
+{
+    zlereadptr = fallback_zleread;
+    load_module("zle");
+    return zleread(lp, rp, ha);
+}
+
+# endif /* UNLINKED_XMOD_zle */
+
+/**/
+unsigned char *
+fallback_zleread(char *lp, char *rp, int ha)
+{
+    char *pptbuf;
+    int pptlen;
+
+    pptbuf = unmetafy(promptexpand(lp, 0, NULL, NULL), &pptlen);
+    write(2, (WRITE_ARG_2_T)pptbuf, pptlen);
+    free(pptbuf);
+    return (unsigned char *)shingetline();
+}
+
+#endif /* !LINKED_XMOD_zle */
+
+/* compctl entry point pointers.  Similar to the ZLE ones. */
+
+#ifdef LINKED_XMOD_comp1
+
+/**/
+CompctlReadFn compctlreadptr;
+
+#else /* !LINKED_XMOD_comp1 */
+
+CompctlReadFn compctlreadptr = fallback_compctlread;
+
+/**/
+int
+fallback_compctlread(char *name, char **args, char *ops, char *reply)
+{
+    zwarnnam(name, "option valid only in functions called from completion",
+	    NULL, 0);
+    return 1;
+}
+
+#endif /* !LINKED_XMOD_comp1 */
diff --git a/Src/input.c b/Src/input.c
new file mode 100644
index 000000000..576341a7c
--- /dev/null
+++ b/Src/input.c
@@ -0,0 +1,530 @@
+/*
+ * input.c - read and store lines of input
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+
+/*
+ * This file deals with input buffering, supplying characters to the
+ * history expansion code a character at a time.  Input is stored on a
+ * stack, which allows insertion of strings into the input, possibly with
+ * flags marking the end of alias expansion, with minimal copying of
+ * strings.  The same stack is used to record the fact that the input
+ * is a history or alias expansion and to store the alias while it is in use.
+ * 
+ * Input is taken either from zle, if appropriate, or read directly from
+ * the input file, or may be supplied by some other part of the shell (such
+ * as `eval' or $(...) substitution).  In the last case, it should be
+ * supplied by pushing a new level onto the stack, via inpush(input_string,
+ * flag, alias); if the current input really needs to be altered, use
+ * inputsetline(input_string, flag).  `Flag' can include or's of INP_FREE
+ * (if the input string is to be freed when used), INP_CONT (if the input
+ * is to continue onto what's already in the input queue), INP_ALIAS
+ * (push supplied alias onto stack) or INP_HIST (ditto, but used to
+ * mark history expansion).  `alias' is ignored unless INP_ALIAS or
+ * INP_HIST is supplied.  INP_ALIAS is always set if INP_HIST is.
+ * 
+ * Note that the input string is itself used as the input buffer: it is not
+ * copied, nor is it every written back to, so using a constant string
+ * should work.  Consequently, when passing areas of memory from the heap
+ * it is necessary that that heap last as long as the operation of reading
+ * the string.  After the string is read, the stack should be popped with
+ * inpop(), which effectively flushes any unread input as well as restoring
+ * the previous input state.
+ *
+ * The internal flag INP_ALCONT shows that the stack element was pushed
+ * by an alias expansion; it should not be needed elsewhere.
+ *
+ * The global variable inalmore is set to indicate aliases should
+ * continue to be expanded because the last alias expansion ended
+ * in a space.  It is only reset after a complete word was read
+ * without expanding a new alias, in exalias().
+ *
+ * PWS 1996/12/10
+ */
+
+#include "zsh.mdh"
+#include "input.pro"
+
+/* the shell input fd */
+
+/**/
+int SHIN;
+
+/* buffered shell input for non-interactive shells */
+
+/**/
+FILE *bshin;
+
+/* != 0 means we are reading input from a string */
+ 
+/**/
+int strin;
+ 
+/* total # of characters waiting to be read. */
+
+/**/
+int inbufct;
+
+/* the flags controlling the input routines in input.c: see INP_* in zsh.h */
+
+/**/
+int inbufflags;
+
+static char *inbuf;		/* Current input buffer */
+static char *inbufptr;		/* Pointer into input buffer */
+static char *inbufpush;		/* Character at which to re-push alias */
+static int inbufleft;		/* Characters left in current input
+				   stack element */
+
+
+ /* Input must be stacked since the input queue is used by
+  * various different parts of the shell.
+  */
+
+struct instacks {
+    char *buf, *bufptr;
+    Alias alias;
+    int bufleft, bufct, flags;
+};
+static struct instacks *instack, *instacktop;
+/*
+ * Input stack size.  We need to push the stack for aliases, history
+ * expansion, and reading from internal strings: only if these operations
+ * are nested do we need more than one extra level.  Thus we shouldn't need
+ * too much space as a rule.  Initially, INSTACK_INITIAL is allocated; if
+ * more is required, an extra INSTACK_EXPAND is added each time.
+ */
+#define INSTACK_INITIAL	4
+#define INSTACK_EXPAND	4
+
+static int instacksz = INSTACK_INITIAL;
+
+/* Read a line from bshin.  Convert tokens and   *
+ * null characters to Meta c^32 character pairs. */
+
+/**/
+char *
+shingetline(void)
+{
+    char *line = NULL;
+    int ll = 0;
+    int c;
+    char buf[BUFSIZ];
+    char *p;
+
+    p = buf;
+    for (;;) {
+	do {
+	    errno = 0;
+	    c = fgetc(bshin);
+	} while (c < 0 && errno == EINTR);
+	if (c < 0 || c == '\n') {
+	    if (c == '\n')
+		*p++ = '\n';
+	    if (p > buf) {
+		*p++ = '\0';
+		line = zrealloc(line, ll + (p - buf));
+		memcpy(line + ll, buf, p - buf);
+	    }
+	    return line;
+	}
+	if (imeta(c)) {
+	    *p++ = Meta;
+	    *p++ = c ^ 32;
+	} else
+	    *p++ = c;
+	if (p >= buf + BUFSIZ - 1) {
+	    line = zrealloc(line, ll + (p - buf) + 1);
+	    memcpy(line + ll, buf, p - buf);
+	    ll += p - buf;
+	    line[ll] = '\0';
+	    p = buf;
+	}
+    }
+}
+
+/* Get the next character from the input.
+ * Will call inputline() to get a new line where necessary.
+ */
+  
+/**/
+int
+ingetc(void)
+{
+    char lastc;
+
+    if (lexstop)
+	return ' ';
+    for (;;) {
+	if (inbufleft) {
+	    inbufleft--;
+	    inbufct--;
+	    if (itok(lastc = STOUC(*inbufptr++)))
+		continue;
+	    return lastc;
+	}
+
+	/* If the next element down the input stack is a continuation of
+	 * this, use it.
+	 */ 
+	if (inbufflags & INP_CONT) {
+	    inpoptop();
+	    continue;
+	}
+	/*
+	 * Otherwise, see if we have reached the end of input
+	 * (due to an error, or to reading from a single string).
+	 */
+	if (strin || errflag) {
+	    lexstop = 1;
+	    return ' ';
+	}
+	/* As a last resort, get some more input */
+	if (inputline())
+	    return ' ';
+    }
+}
+
+/* Read a line from the current command stream and store it as input */
+
+/**/
+static int
+inputline(void)
+{
+    char *ingetcline, *ingetcpmptl = NULL, *ingetcpmptr = NULL;
+
+    /* If reading code interactively, work out the prompts. */
+    if (interact && isset(SHINSTDIN))
+	if (!isfirstln)
+	    ingetcpmptl = prompt2;
+	else {
+	    ingetcpmptl = prompt;
+	    if (rprompt)
+		ingetcpmptr = rprompt;
+	}
+    if (!(interact && isset(SHINSTDIN) && SHTTY != -1 && isset(USEZLE))) {
+	/*
+	 * If not using zle, read the line straight from the input file.
+	 * Possibly we don't get the whole line at once:  in that case,
+	 * we get another chunk with the next call to inputline().
+	 */
+
+	if (interact && isset(SHINSTDIN)) {
+	    /*
+	     * We may still be interactive (e.g. running under emacs),
+	     * so output a prompt if necessary.  We don't know enough
+	     * about the input device to be able to handle an rprompt,
+	     * though.
+	     */
+	    char *pptbuf;
+	    int pptlen;
+	    pptbuf = unmetafy(promptexpand(ingetcpmptl, 0, NULL, NULL), &pptlen);
+	    write(2, (WRITE_ARG_2_T)pptbuf, pptlen);
+	    free(pptbuf);
+	}
+	ingetcline = shingetline();
+    } else
+	ingetcline = (char *)zleread(ingetcpmptl, ingetcpmptr, 1);
+    if (!ingetcline) {
+	return lexstop = 1;
+    }
+    if (errflag) {
+	free(ingetcline);
+	return lexstop = errflag = 1;
+    }
+    /* Look for a space, to see if this shouldn't be put into history */
+    if (isfirstln)
+	spaceflag = *ingetcline == ' ';
+    if (isset(VERBOSE)) {
+	/* Output the whole line read so far. */
+	zputs(ingetcline, stderr);
+	fflush(stderr);
+    }
+    if (*ingetcline && ingetcline[strlen(ingetcline) - 1] == '\n') {
+	/* We've now read a complete line. */
+	lineno++;
+	if (interact && isset(SUNKEYBOARDHACK) && isset(SHINSTDIN) &&
+	    SHTTY != -1 && *ingetcline && ingetcline[1] &&
+	    ingetcline[strlen(ingetcline) - 2] == '`') {
+	    /* Junk an unmatched "`" at the end of the line. */
+	    int ct;
+	    char *ptr;
+
+	    for (ct = 0, ptr = ingetcline; *ptr; ptr++)
+		if (*ptr == '`')
+		    ct++;
+	    if (ct & 1) {
+		ptr[-2] = '\n';
+		ptr[-1] = '\0';
+	    }
+	}
+    }
+    isfirstch = 1;
+    /* Put this into the input channel. */
+    inputsetline(ingetcline, INP_FREE);
+
+    return 0;
+}
+
+/*
+ * Put a string in the input queue:
+ * inbuf is only freeable if the flags include INP_FREE.
+ */
+
+/**/
+static void
+inputsetline(char *str, int flags)
+{
+    if ((inbufflags & INP_FREE) && inbuf) {
+	free(inbuf);
+    }
+    inbuf = inbufptr = str;
+    inbufleft = strlen(inbuf);
+
+    /*
+     * inbufct must reflect the total number of characters left,
+     * as it used by other parts of the shell, so we need to take account
+     * of whether the input stack continues, and whether there
+     * is an extra space to add on at the end.
+     */
+    if (flags & INP_CONT)
+	inbufct += inbufleft;
+    else
+	inbufct = inbufleft;
+    inbufflags = flags;
+}
+
+/*
+ * Backup one character of the input.
+ * The last character can always be backed up, provided we didn't just
+ * expand an alias or a history reference.
+ * In fact, the character is ignored and the previous character is used.
+ * (If that's wrong, the bug is in the calling code.  Use the #ifdef DEBUG
+ * code to check.) 
+ */
+
+/**/
+void
+inungetc(int c)
+{
+    if (!lexstop) {
+	if (inbufptr != inbuf) {
+#ifdef DEBUG
+	    /* Just for debugging: enable only if foul play suspected. */
+	    if (inbufptr[-1] != (char) c)
+		fprintf(stderr, "Warning: backing up wrong character.\n");
+#endif
+	    /* Just decrement the pointer:  if it's not the same
+	     * character being pushed back, we're in trouble anyway.
+	     */
+	    inbufptr--;
+	    inbufct++;
+	    inbufleft++;
+	}
+#ifdef DEBUG
+        else if (!(inbufflags & INP_CONT)) {
+	    /* Just for debugging */
+	    fprintf(stderr, "Attempt to inungetc() at start of input.\n");
+	}
+#endif
+	else {
+	    /*
+	     * The character is being backed up from a previous input stack
+	     * layer.  However, there was an expansion in the middle, so we
+	     * can't back up where we want to.  Instead, we just push it
+	     * onto the input stack as an extra character.
+	     */
+	    char *cback = (char *)zcalloc(2);
+	    cback[0] = (char) c;
+	    inpush(cback, INP_FREE|INP_CONT, NULL);
+	}
+	/* If we are back at the start of a segment,
+	 * we may need to restore an alias popped from the stack.
+	 * Note this may be a dummy (history expansion) entry.
+	 */
+	if (inbufptr == inbufpush && inbufflags & INP_ALCONT) {
+	    /*
+	     * Go back up the stack over all entries which were alias
+	     * expansions and were pushed with nothing remaining to read.
+	     */
+	    do {
+		if (instacktop->alias)
+		    instacktop->alias->inuse = 1;
+		instacktop++;
+	    } while ((instacktop->flags & INP_ALCONT) && !instacktop->bufleft);
+	    inbufflags = INP_CONT|INP_ALIAS;
+	    inbufleft = 0;
+	    inbuf = inbufptr = "";
+	}
+    }
+}
+
+/* stuff a whole file into the input queue and print it */
+
+/**/
+int
+stuff(char *fn)
+{
+    FILE *in;
+    char *buf;
+    int len;
+
+    if (!(in = fopen(unmeta(fn), "r"))) {
+	zerr("can't open %s", fn, 0);
+	return 1;
+    }
+    fseek(in, 0, 2);
+    len = ftell(in);
+    fseek(in, 0, 0);
+    buf = (char *)zalloc(len + 1);
+    if (!(fread(buf, len, 1, in))) {
+	zerr("read error on %s", fn, 0);
+	fclose(in);
+	zfree(buf, len + 1);
+	return 1;
+    }
+    fclose(in);
+    buf[len] = '\0';
+    fwrite(buf, len, 1, stderr);
+    fflush(stderr);
+    inputsetline(metafy(buf, len, META_REALLOC), INP_FREE);
+    return 0;
+}
+
+/* flush input queue */
+
+/**/
+void
+inerrflush(void)
+{
+    while (!lexstop && inbufct)
+	ingetc();
+}
+
+/* Set some new input onto a new element of the input stack */
+
+/**/
+void
+inpush(char *str, int flags, Alias inalias)
+{
+    if (!instack) {
+	/* Initial stack allocation */
+	instack = (struct instacks *)zalloc(instacksz*sizeof(struct instacks));
+	instacktop = instack;
+    }
+
+    instacktop->buf = inbuf;
+    instacktop->bufptr = inbufptr;
+    instacktop->bufleft = inbufleft;
+    instacktop->bufct = inbufct;
+    inbufflags &= ~INP_ALCONT;
+    if (flags & (INP_ALIAS|INP_HIST)) {
+	/*
+	 * Text is expansion for history or alias, so continue
+	 * back to old level when done.  Also mark stack top
+	 * as alias continuation so as to back up if necessary,
+	 * and mark alias as in use.
+	 */
+	flags |= INP_CONT|INP_ALIAS;
+	instacktop->flags = inbufflags | INP_ALCONT;
+	if ((instacktop->alias = inalias))
+	    inalias->inuse = 1;
+    } else {
+	/* If we are continuing an alias expansion, record the alias
+	 * expansion in new set of flags (do we need this?)
+	 */
+	if (((instacktop->flags = inbufflags) & INP_ALIAS) &&
+	    (flags & INP_CONT))
+	    flags |= INP_ALIAS;
+    }
+
+    instacktop++;
+    if (instacktop == instack + instacksz) {
+	/* Expand the stack */
+	instack = (struct instacks *)
+	    realloc(instack,
+		    (instacksz + INSTACK_EXPAND)*sizeof(struct instacks));
+	instacktop = instack + instacksz;
+	instacksz += INSTACK_EXPAND;
+    }
+    /*
+     * We maintain the entry above the highest one with real
+     * text as a flag to inungetc() that it can stop re-pushing the stack.
+     */
+    instacktop->flags = 0;
+
+    inbufpush = inbuf = NULL;
+
+    inputsetline(str, flags);
+}
+
+/* Remove the top element of the stack */
+
+/**/
+static void
+inpoptop(void)
+{
+    if (inbuf && (inbufflags & INP_FREE))
+	free(inbuf);
+
+    instacktop--;
+
+    inbuf = instacktop->buf;
+    inbufptr = inbufpush = instacktop->bufptr;
+    inbufleft = instacktop->bufleft;
+    inbufct = instacktop->bufct;
+    inbufflags = instacktop->flags;
+
+    if (!(inbufflags & INP_ALCONT))
+	return;
+
+    if (instacktop->alias) {
+	char *t = instacktop->alias->text;
+	/* a real alias:  mark it as unused. */
+	instacktop->alias->inuse = 0;
+	if (*t && t[strlen(t) - 1] == ' ') {
+	    inalmore = 1;
+	    histbackword();
+	}
+    }
+}
+
+/* Remove the top element of the stack and all its continuations. */
+
+/**/
+void
+inpop(void)
+{
+    int remcont;
+
+    do {
+	remcont = inbufflags & INP_CONT;
+
+	inpoptop();
+    } while (remcont);
+}
diff --git a/Src/jobs.c b/Src/jobs.c
new file mode 100644
index 000000000..331902d9f
--- /dev/null
+++ b/Src/jobs.c
@@ -0,0 +1,1361 @@
+/*
+ * jobs.c - job control
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "jobs.pro"
+
+/* the process group of the shell */
+
+/**/
+pid_t mypgrp;
+ 
+/* the job we are working on */
+ 
+/**/
+int thisjob;
+
+/* the current job (+) */
+ 
+/**/
+int curjob;
+ 
+/* the previous job (-) */
+ 
+/**/
+int prevjob;
+ 
+/* the job table */
+ 
+/**/
+struct job jobtab[MAXJOB];
+ 
+/* shell timings */
+ 
+/**/
+struct tms shtms;
+ 
+/* 1 if ttyctl -f has been executed */
+ 
+/**/
+int ttyfrozen;
+ 
+/* empty job structure for quick clearing of jobtab entries */
+
+static struct job zero;		/* static variables are initialized to zero */
+
+static struct timeval dtimeval, now;
+
+/* Diff two timevals for elapsed-time computations */
+
+/**/
+static struct timeval *
+dtime(struct timeval *dt, struct timeval *t1, struct timeval *t2)
+{
+    dt->tv_sec = t2->tv_sec - t1->tv_sec;
+    dt->tv_usec = t2->tv_usec - t1->tv_usec;
+    if (dt->tv_usec < 0) {
+	dt->tv_usec += 1000000.0;
+	dt->tv_sec -= 1.0;
+    }
+    return dt;
+}
+
+/* change job table entry from stopped to running */
+
+/**/
+void
+makerunning(Job jn)
+{
+    Process pn;
+
+    jn->stat &= ~STAT_STOPPED;
+    for (pn = jn->procs; pn; pn = pn->next)
+	if (WIFSTOPPED(pn->status) && 
+	    (!(jn->stat & STAT_SUPERJOB) || pn->next))
+	    pn->status = SP_RUNNING;
+
+    if (jn->stat & STAT_SUPERJOB)
+	makerunning(jobtab + jn->other);
+}
+
+/* Find process and job associated with pid.         *
+ * Return 1 if search was successful, else return 0. */
+
+/**/
+int
+findproc(pid_t pid, Job *jptr, Process *pptr)
+{
+    Process pn;
+    int i;
+
+    for (i = 1; i < MAXJOB; i++)
+	for (pn = jobtab[i].procs; pn; pn = pn->next)
+	    if (pn->pid == pid) {
+		*pptr = pn;
+		*jptr = jobtab + i;
+		return 1;
+	    }
+
+    return 0;
+}
+
+/* Update status of process that we have just WAIT'ed for */
+
+/**/
+void
+update_process(Process pn, int status)
+{
+    struct timezone dummy_tz;
+    long childs, childu;
+
+    childs = shtms.tms_cstime;
+    childu = shtms.tms_cutime;
+    times(&shtms);                          /* get time-accounting info          */
+
+    pn->status = status;                    /* save the status returned by WAIT  */
+    pn->ti.st  = shtms.tms_cstime - childs; /* compute process system space time */
+    pn->ti.ut  = shtms.tms_cutime - childu; /* compute process user space time   */
+
+    gettimeofday(&pn->endtime, &dummy_tz);  /* record time process exited        */
+}
+
+/* Update status of job, possibly printing it */
+
+/**/
+void
+update_job(Job jn)
+{
+    Process pn;
+    int job;
+    int val = 0, status = 0;
+    int somestopped = 0, inforeground = 0;
+
+    for (pn = jn->procs; pn; pn = pn->next) {
+	if (pn->status == SP_RUNNING)      /* some processes in this job are running       */
+	    return;                        /* so no need to update job table entry         */
+	if (WIFSTOPPED(pn->status))        /* some processes are stopped                   */
+	    somestopped = 1;               /* so job is not done, but entry needs updating */
+	if (!pn->next)                     /* last job in pipeline determines exit status  */
+	    val = (WIFSIGNALED(pn->status)) ? 0200 | WTERMSIG(pn->status) :
+		WEXITSTATUS(pn->status);
+	if (pn->pid == jn->gleader)        /* if this process is process group leader      */
+	    status = pn->status;
+    }
+
+    job = jn - jobtab;   /* compute job number */
+
+    if (somestopped) {
+	if (jn->stty_in_env && !jn->ty) {
+	    jn->ty = (struct ttyinfo *) zalloc(sizeof(struct ttyinfo));
+	    gettyinfo(jn->ty);
+	}
+	if (jn->stat & STAT_STOPPED)
+	    return;
+    } else {                   /* job is done, so remember return value */
+	lastval2 = val;
+	/* If last process was run in the current shell, keep old status
+	 * and let it handle its own traps
+	 */
+	if (job == thisjob && !(jn->stat & STAT_CURSH)) {
+	  lastval = val;
+	  inforeground = 1;
+	}
+    }
+
+    if (shout && !ttyfrozen && !jn->stty_in_env && !zleactive &&
+	job == thisjob && !somestopped && !(jn->stat & STAT_NOSTTY))
+	gettyinfo(&shttyinfo);
+
+    if (isset(MONITOR)) {
+	pid_t pgrp = gettygrp();           /* get process group of tty      */
+
+	/* 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 (somestopped && jn->stat & STAT_SUPERJOB)
+	return;
+    jn->stat |= (somestopped) ? STAT_CHANGED | STAT_STOPPED :
+	STAT_CHANGED | STAT_DONE;
+    if ((jn->stat & (STAT_DONE | STAT_STOPPED)) == STAT_STOPPED) {
+	prevjob = curjob;
+	curjob = job;
+    }
+    if ((isset(NOTIFY) || job == thisjob) && (jn->stat & STAT_LOCKED)) {
+	printjob(jn, !!isset(LONGLISTJOBS), 0);
+	if (zleactive)
+	    refresh();
+    }
+    if (sigtrapped[SIGCHLD] && job != thisjob)
+	dotrap(SIGCHLD);
+
+    /* When MONITOR is set, the foreground process runs in a different *
+     * 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)) {
+	int sig = WTERMSIG(status);
+
+	if (sig == SIGINT || sig == SIGQUIT) {
+	    if (sigtrapped[sig]) {
+		dotrap(sig);
+		/* We keep the errflag as set or not by dotrap.
+		 * This is to fulfil the promise to carry on
+		 * with the jobs if trap returns zero.
+		 * Setting breaks = loops ensures a consistent return
+		 * status if inside a loop.  Maybe the code in loops
+		 * should be changed.
+		 */
+		if (errflag)
+		    breaks = loops;
+	    } else {
+		breaks = loops;
+		errflag = 1;
+	    }
+	}
+    }
+}
+
+/* set the previous job to something reasonable */
+
+/**/
+static void
+setprevjob(void)
+{
+    int i;
+
+    for (i = MAXJOB - 1; i; i--)
+	if ((jobtab[i].stat & STAT_INUSE) && (jobtab[i].stat & STAT_STOPPED) &&
+	    i != curjob && i != thisjob) {
+	    prevjob = i;
+	    return;
+	}
+
+    for (i = MAXJOB - 1; i; i--)
+	if ((jobtab[i].stat & STAT_INUSE) && i != curjob && i != thisjob) {
+	    prevjob = i;
+	    return;
+	}
+
+    prevjob = -1;
+}
+
+static long clktck = 0;
+
+/**/
+static void
+set_clktck(void)
+{
+#ifdef _SC_CLK_TCK
+    if (!clktck)
+	/* fetch clock ticks per second from *
+	 * sysconf only the first time       */
+	clktck = sysconf(_SC_CLK_TCK);
+#else
+# ifdef __NeXT__
+    /* NeXTStep 3.3 defines CLK_TCK wrongly */
+    clktck = 60;
+# else
+#  ifdef CLK_TCK
+    clktck = CLK_TCK;
+#  else
+#   ifdef HZ
+     clktck = HZ;
+#   else
+     clktck = 60;
+#   endif
+#  endif
+# endif
+#endif
+}
+
+/**/
+static void
+printhhmmss(double secs)
+{
+    int mins = (int) secs / 60;
+    int hours = mins / 60;
+
+    secs -= 60 * mins;
+    mins -= 60 * hours;
+    if (hours)
+	fprintf(stderr, "%d:%02d:%05.2f", hours, mins, secs);
+    else if (mins)
+	fprintf(stderr,      "%d:%05.2f",        mins, secs);
+    else
+	fprintf(stderr,           "%.3f",              secs);
+}
+
+/**/
+static void
+printtime(struct timeval *real, struct timeinfo *ti, char *desc)
+{
+    char *s;
+    double elapsed_time, user_time, system_time;
+    int percent;
+
+    if (!desc)
+	desc = "";
+
+    set_clktck();
+    /* go ahead and compute these, since almost every TIMEFMT will have them */
+    elapsed_time = real->tv_sec + real->tv_usec / 1000000.0;
+    user_time    = ti->ut / (double) clktck;
+    system_time  = ti->st / (double) clktck;
+    percent      =  100.0 * (ti->ut + ti->st)
+	/ (clktck * real->tv_sec + clktck * real->tv_usec / 1000000.0);
+
+    if (!(s = getsparam("TIMEFMT")))
+	s = DEFAULT_TIMEFMT;
+
+    for (; *s; s++)
+	if (*s == '%')
+	    switch (*++s) {
+	    case 'E':
+		fprintf(stderr, "%4.2fs", elapsed_time);
+		break;
+	    case 'U':
+		fprintf(stderr, "%4.2fs", user_time);
+		break;
+	    case 'S':
+		fprintf(stderr, "%4.2fs", system_time);
+		break;
+	    case '*':
+		switch (*++s) {
+		case 'E':
+		    printhhmmss(elapsed_time);
+		    break;
+		case 'U':
+		    printhhmmss(user_time);
+		    break;
+		case 'S':
+		    printhhmmss(system_time);
+		    break;
+		default:
+		    fprintf(stderr, "%%*");
+		    s--;
+		    break;
+		}
+		break;
+	    case 'P':
+		fprintf(stderr, "%d%%", percent);
+		break;
+	    case 'J':
+		fprintf(stderr, "%s", desc);
+		break;
+	    case '%':
+		putc('%', stderr);
+		break;
+	    case '\0':
+		s--;
+		break;
+	    default:
+		fprintf(stderr, "%%%c", *s);
+		break;
+	} else
+	    putc(*s, stderr);
+    putc('\n', stderr);
+    fflush(stderr);
+}
+
+/**/
+static void
+dumptime(Job jn)
+{
+    Process pn;
+
+    if (!jn->procs)
+	return;
+    for (pn = jn->procs; pn; pn = pn->next)
+	printtime(dtime(&dtimeval, &pn->bgtime, &pn->endtime), &pn->ti, pn->text);
+}
+
+/* Check whether shell should report the amount of time consumed   *
+ * by job.  This will be the case if we have preceded the command  *
+ * with the keyword time, or if REPORTTIME is non-negative and the *
+ * amount of time consumed by the job is greater than REPORTTIME   */
+
+/**/
+static int
+should_report_time(Job j)
+{
+    Value v;
+    char *s = "REPORTTIME";
+    int reporttime;
+
+    /* if the time keyword was used */
+    if (j->stat & STAT_TIMED)
+	return 1;
+
+    if (!(v = getvalue(&s, 0)) || (reporttime = getintvalue(v)) < 0)
+	return 0;
+
+    /* can this ever happen? */
+    if (!j->procs)
+	return 0;
+
+    set_clktck();
+    return ((j->procs->ti.ut + j->procs->ti.st) / clktck >= reporttime);
+}
+
+/* !(lng & 3) means jobs    *
+ *  (lng & 1) means jobs -l *
+ *  (lng & 2) means jobs -p
+ *  (lng & 4) means jobs -d
+ *
+ * synch = 0 means asynchronous
+ * synch = 1 means synchronous
+ * synch = 2 means called synchronously from jobs
+*/
+
+/**/
+void
+printjob(Job jn, int lng, int synch)
+{
+    Process pn;
+    int job = jn - jobtab, len = 9, sig, sflag = 0, llen;
+    int conted = 0, lineleng = columns, skip = 0, doputnl = 0;
+    FILE *fout = (synch == 2) ? stdout : shout;
+
+    if (jn->stat & STAT_NOPRINT)
+	return;
+
+    if (lng < 0) {
+	conted = 1;
+	lng = 0;
+    }
+
+/* find length of longest signame, check to see */
+/* if we really need to print this job          */
+
+    for (pn = jn->procs; pn; pn = pn->next) {
+	if (jn->stat & STAT_SUPERJOB &&
+	    jn->procs->status == SP_RUNNING && !pn->next)
+	    pn->status = SP_RUNNING;
+	if (pn->status != SP_RUNNING)
+	    if (WIFSIGNALED(pn->status)) {
+		sig = WTERMSIG(pn->status);
+		llen = strlen(sigmsg[sig]);
+		if (WCOREDUMP(pn->status))
+		    llen += 14;
+		if (llen > len)
+		    len = llen;
+		if (sig != SIGINT && sig != SIGPIPE)
+		    sflag = 1;
+		if (job == thisjob && sig == SIGINT)
+		    doputnl = 1;
+	    } else if (WIFSTOPPED(pn->status)) {
+		sig = WSTOPSIG(pn->status);
+		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 */
+
+    if (interact && jobbing && ((jn->stat & STAT_STOPPED) || sflag ||
+				job != thisjob)) {
+	int len2, fline = 1;
+	Process qn;
+
+	if (!synch)
+	    trashzle();
+	if (doputnl && !synch)
+	    putc('\n', fout);
+	for (pn = jn->procs; pn;) {
+	    len2 = ((job == thisjob) ? 5 : 10) + len;	/* 2 spaces */
+	    if (lng & 3)
+		qn = pn->next;
+	    else
+		for (qn = pn->next; qn; qn = qn->next) {
+		    if (qn->status != pn->status)
+			break;
+		    if ((int)strlen(qn->text) + len2 + ((qn->next) ? 3 : 0) > lineleng)
+			break;
+		    len2 += strlen(qn->text) + 2;
+		}
+	    if (job != thisjob)
+		if (fline)
+		    fprintf(fout, "[%ld]  %c ",
+			    (long)(jn - jobtab),
+			    (job == curjob) ? '+'
+			    : (job == prevjob) ? '-' : ' ');
+		else
+		    fprintf(fout, (job > 9) ? "        " : "       ");
+	    else
+		fprintf(fout, "zsh: ");
+	    if (lng & 1)
+		fprintf(fout, "%ld ", (long) pn->pid);
+	    else if (lng & 2) {
+		pid_t x = jn->gleader;
+
+		fprintf(fout, "%ld ", (long) x);
+		do
+		    skip++;
+		while ((x /= 10));
+		skip++;
+		lng &= ~3;
+	    } else
+		fprintf(fout, "%*s", skip, "");
+	    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))
+		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 (WCOREDUMP(pn->status))
+		fprintf(fout, "%s (core dumped)%*s",
+			sigmsg[WTERMSIG(pn->status)],
+			(int)(len - 14 + 2 - strlen(sigmsg[WTERMSIG(pn->status)])), "");
+	    else
+		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);
+	    fline = 0;
+	}
+	fflush(fout);
+    } else if (doputnl && interact && !synch) {
+	putc('\n', fout);
+	fflush(fout);
+    }
+
+/* print "(pwd now: foo)" messages: with (lng & 4) we are printing
+ * the directory where the job is running, otherwise the current directory
+ */
+
+    if ((lng & 4) || (interact && job == thisjob && strcmp(jn->pwd, pwd))) {
+	fprintf(shout, "(pwd %s: ", (lng & 4) ? "" : "now");
+	fprintdir((lng & 4) ? jn->pwd : pwd, shout);
+	fprintf(shout, ")\n");
+	fflush(shout);
+    }
+/* delete job if done */
+
+    if (jn->stat & STAT_DONE) {
+	if (should_report_time(jn))
+	    dumptime(jn);
+	deletejob(jn);
+	if (job == curjob) {
+	    curjob = prevjob;
+	    prevjob = job;
+	}
+	if (job == prevjob)
+	    setprevjob();
+    } else
+	jn->stat &= ~STAT_CHANGED;
+}
+
+/**/
+void
+deletefilelist(LinkList file_list)
+{
+    char *s;
+    if (file_list) {
+	while ((s = (char *)getlinknode(file_list))) {
+	    unlink(s);
+	    zsfree(s);
+	}
+	zfree(file_list, sizeof(struct linklist));
+    }
+}
+
+/**/
+void
+deletejob(Job jn)
+{
+    struct process *pn, *nx;
+
+    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;
+}
+
+/* add a process to the current job */
+
+/**/
+void
+addproc(pid_t pid, char *text)
+{
+    Process pn;
+    struct timezone dummy_tz;
+
+    pn = (Process) zcalloc(sizeof *pn);
+    pn->pid = pid;
+    if (text)
+	strcpy(pn->text, text);
+    else
+	*pn->text = '\0';
+    gettimeofday(&pn->bgtime, &dummy_tz);
+    pn->status = SP_RUNNING;
+    pn->next = NULL;
+
+    /* if this is the first process we are adding to *
+     * the job, then it's the group leader.          */
+    if (!jobtab[thisjob].gleader)
+	jobtab[thisjob].gleader = pid;
+
+    /* attach this process to end of process list of current job */
+    if (jobtab[thisjob].procs) {
+	Process n;
+
+	for (n = jobtab[thisjob].procs; n->next; n = n->next);
+	pn->next = NULL;
+	n->next = pn;
+    } else {
+	/* first process for this job */
+	jobtab[thisjob].procs = pn;
+    }
+    /* If the first process in the job finished before any others were *
+     * added, maybe STAT_DONE got set incorrectly.  This can happen if *
+     * a $(...) was waited for and the last existing job in the        *
+     * pipeline was already finished.  We need to be very careful that *
+     * there was no call to printjob() between then and now, else      *
+     * the job will already have been deleted from the table.          */
+    jobtab[thisjob].stat &= ~STAT_DONE;
+}
+
+/* Check if we have files to delete.  We need to check this to see *
+ * if it's all right to exec a command without forking in the last *
+ * component of subshells or after the `-c' option.                */
+
+/**/
+int
+havefiles(void)
+{
+    int i;
+
+    for (i = 1; i < MAXJOB; i++)
+	if (jobtab[i].stat && jobtab[i].filelist)
+	    return 1;
+    return 0;
+
+}
+
+/* wait for a particular process */
+
+/**/
+void
+waitforpid(pid_t pid)
+{
+    int first = 1;
+
+    /* child_block() around this loop in case #ifndef WNOHANG */
+    child_block();		/* unblocked in child_suspend() */
+    while (!errflag && (kill(pid, 0) >= 0 || errno != ESRCH)) {
+	if (first)
+	    first = 0;
+	else
+	    kill(pid, SIGCONT);
+
+	child_suspend(SIGINT);
+	child_block();
+    }
+    child_unblock();
+}
+
+/* wait for a job to finish */
+
+/**/
+static void
+waitjob(int job, int sig)
+{
+    Job jn = jobtab + job;
+
+    child_block();		 /* unblocked during child_suspend() */
+    if (jn->procs) {		 /* if any forks were done         */
+	jn->stat |= STAT_LOCKED;
+	if (jn->stat & STAT_CHANGED)
+	    printjob(jn, !!isset(LONGLISTJOBS), 1);
+	while (!errflag && jn->stat &&
+	       !(jn->stat & STAT_DONE) &&
+	       !(interact && (jn->stat & STAT_STOPPED))) {
+	    child_suspend(sig);
+	    /* Commenting this out makes ^C-ing a job started by a function
+	       stop the whole function again.  But I guess it will stop
+	       something else from working properly, we have to find out
+	       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;
+		}
+	    }
+	    child_block();
+	}
+    } else
+	deletejob(jn);
+    child_unblock();
+}
+
+/* wait for running job to finish */
+
+/**/
+void
+waitjobs(void)
+{
+    waitjob(thisjob, 0);
+    thisjob = -1;
+}
+
+/* clear job table when entering subshells */
+
+/**/
+void
+clearjobtab(void)
+{
+    int i;
+
+    for (i = 1; i < MAXJOB; i++) {
+	if (jobtab[i].pwd)
+	    zsfree(jobtab[i].pwd);
+	if (jobtab[i].ty)
+	    zfree(jobtab[i].ty, sizeof(struct ttyinfo));
+    }
+
+    memset(jobtab, 0, sizeof(jobtab)); /* zero out table */
+}
+
+/* Get a free entry in the job table and initialize it. */
+
+/**/
+int
+initjob(void)
+{
+    int i;
+
+    for (i = 1; i < MAXJOB; i++)
+	if (!jobtab[i].stat) {
+	    jobtab[i].stat = STAT_INUSE;
+	    jobtab[i].pwd = ztrdup(pwd);
+	    jobtab[i].gleader = 0;
+	    return i;
+	}
+
+    zerr("job table full or recursion limit exceeded", NULL, 0);
+    return -1;
+}
+
+/* print pids for & */
+
+/**/
+void
+spawnjob(void)
+{
+    Process pn;
+
+    /* if we are not in a subshell */
+    if (!subsh) {
+	if (curjob == -1 || !(jobtab[curjob].stat & STAT_STOPPED)) {
+	    curjob = thisjob;
+	    setprevjob();
+	} else if (prevjob == -1 || !(jobtab[prevjob].stat & STAT_STOPPED))
+	    prevjob = thisjob;
+	if (interact && jobbing && jobtab[thisjob].procs) {
+	    fprintf(stderr, "[%d]", thisjob);
+	    for (pn = jobtab[thisjob].procs; pn; pn = pn->next)
+		fprintf(stderr, " %ld", (long) pn->pid);
+	    fprintf(stderr, "\n");
+	    fflush(stderr);
+	}
+    }
+    if (!jobtab[thisjob].procs)
+	deletejob(jobtab + thisjob);
+    else
+	jobtab[thisjob].stat |= STAT_LOCKED;
+    thisjob = -1;
+}
+
+/**/
+void
+shelltime(void)
+{
+    struct timeinfo ti;
+    struct timezone dummy_tz;
+    struct tms buf;
+
+    times(&buf);
+    ti.ut = buf.tms_utime;
+    ti.st = buf.tms_stime;
+    gettimeofday(&now, &dummy_tz);
+    printtime(dtime(&dtimeval, &shtimer, &now), &ti, "shell");
+    ti.ut = buf.tms_cutime;
+    ti.st = buf.tms_cstime;
+    printtime(dtime(&dtimeval, &shtimer, &now), &ti, "children");
+}
+
+/* see if jobs need printing */
+ 
+/**/
+void
+scanjobs(void)
+{
+    int i;
+ 
+    for (i = 1; i < MAXJOB; i++)
+        if (jobtab[i].stat & STAT_CHANGED)
+            printjob(jobtab + i, 0, 1);
+}
+
+/**** job control builtins ****/
+
+/* This simple function indicates whether or not s may represent      *
+ * a number.  It returns true iff s consists purely of digits and     *
+ * minuses.  Note that minus may appear more than once, and the empty *
+ * string will produce a `true' response.                             */
+
+/**/
+static int
+isanum(char *s)
+{
+    while (*s == '-' || idigit(*s))
+	s++;
+    return *s == '\0';
+}
+
+/* Make sure we have a suitable current and previous job set. */
+
+/**/
+static void
+setcurjob(void)
+{
+    if (curjob == thisjob ||
+	(curjob != -1 && !(jobtab[curjob].stat & STAT_INUSE))) {
+	curjob = prevjob;
+	setprevjob();
+	if (curjob == thisjob ||
+	    (curjob != -1 && !((jobtab[curjob].stat & STAT_INUSE) &&
+			       curjob != thisjob))) {
+	    curjob = prevjob;
+	    setprevjob();
+	}
+    }
+}
+
+/* Convert a job specifier ("%%", "%1", "%foo", "%?bar?", etc.) *
+ * to a job number.                                             */
+
+/**/
+static int
+getjob(char *s, char *prog)
+{
+    int jobnum, returnval;
+
+    /* if there is no %, treat as a name */
+    if (*s != '%')
+	goto jump;
+    s++;
+    /* "%%", "%+" and "%" all represent the current job */
+    if (*s == '%' || *s == '+' || !*s) {
+	if (curjob == -1) {
+	    zwarnnam(prog, "no current job", NULL, 0);
+	    returnval = -1;
+	    goto done;
+	}
+	returnval = curjob;
+	goto done;
+    }
+    /* "%-" represents the previous job */
+    if (*s == '-') {
+	if (prevjob == -1) {
+	    zwarnnam(prog, "no previous job", NULL, 0);
+	    returnval = -1;
+	    goto done;
+	}
+	returnval = prevjob;
+	goto done;
+    }
+    /* a digit here means we have a job number */
+    if (idigit(*s)) {
+	jobnum = atoi(s);
+	if (jobnum && jobnum < MAXJOB && jobtab[jobnum].stat &&
+	    !(jobtab[jobnum].stat & STAT_SUBJOB) && jobnum != thisjob) {
+	    returnval = jobnum;
+	    goto done;
+	}
+	zwarnnam(prog, "%%%s: no such job", s, 0);
+	returnval = -1;
+	goto done;
+    }
+    /* "%?" introduces a search string */
+    if (*s == '?') {
+	struct process *pn;
+
+	for (jobnum = MAXJOB - 1; jobnum >= 0; jobnum--)
+	    if (jobtab[jobnum].stat && !(jobtab[jobnum].stat & STAT_SUBJOB) &&
+		jobnum != thisjob)
+		for (pn = jobtab[jobnum].procs; pn; pn = pn->next)
+		    if (strstr(pn->text, s + 1)) {
+			returnval = jobnum;
+			goto done;
+		    }
+	zwarnnam(prog, "job not found: %s", s, 0);
+	returnval = -1;
+	goto done;
+    }
+  jump:
+    /* anything else is a job name, specified as a string that begins the
+    job's command */
+    if ((jobnum = findjobnam(s)) != -1) {
+	returnval = jobnum;
+	goto done;
+    }
+    /* if we get here, it is because none of the above succeeded and went
+    to done */
+    zwarnnam(prog, "job not found: %s", s, 0);
+    returnval = -1;
+  done:
+    return returnval;
+}
+
+/* For jobs -Z (which modifies the shell's name as seen in ps listings).  *
+ * hackzero is the start of the safely writable space, and hackspace is   *
+ * its length, excluding a final NUL terminator that will always be left. */
+
+static char *hackzero;
+static int hackspace;
+
+/* Initialise the jobs -Z system.  The technique is borrowed from perl: *
+ * check through the argument and environment space, to see how many of *
+ * the strings are in contiguous space.  This determines the value of   *
+ * hackspace.                                                           */
+
+/**/
+void
+init_hackzero(char **argv, char **envp)
+{
+    char *p, *q;
+
+    hackzero = *argv;
+    p = strchr(hackzero, 0);
+    while(*++argv) {
+	q = *argv;
+	if(q != p+1)
+	    goto done;
+	p = strchr(q, 0);
+    }
+    for(; *envp; envp++) {
+	q = *envp;
+	if(q != p+1)
+	    goto done;
+	p = strchr(q, 0);
+    }
+    done:
+    hackspace = p - hackzero;
+}
+
+/* bg, disown, fg, jobs, wait: most of the job control commands are     *
+ * here.  They all take the same type of argument.  Exception: wait can *
+ * take a pid or a job specifier, whereas the others only work on jobs. */
+
+/**/
+int
+bin_fg(char *name, char **argv, char *ops, int func)
+{
+    int job, lng, firstjob = -1, retval = 0;
+
+    if (ops['Z']) {
+	int len;
+
+	if(isset(RESTRICTED)) {
+	    zwarnnam(name, "-Z is restricted", NULL, 0);
+	    return 1;
+	}
+	if(!argv[0] || argv[1]) {
+	    zwarnnam(name, "-Z requires one argument", NULL, 0);
+	    return 1;
+	}
+	unmetafy(*argv, &len);
+	if(len > hackspace)
+	    len = hackspace;
+	memcpy(hackzero, *argv, len);
+	memset(hackzero + len, 0, hackspace - len);
+	return 0;
+    }
+
+    lng = (ops['l']) ? 1 : (ops['p']) ? 2 : 0;
+    if (ops['d'])
+	lng |= 4;
+    
+    if ((func == BIN_FG || func == BIN_BG) && !jobbing) {
+	/* oops... maybe bg and fg should have been disabled? */
+	zwarnnam(name, "no job control in this shell.", NULL, 0);
+	return 1;
+    }
+
+    /* If necessary, update job table. */
+    if (unset(NOTIFY))
+	scanjobs();
+
+    setcurjob();
+
+    if (func == BIN_JOBS)
+        /* If you immediately type "exit" after "jobs", this      *
+         * will prevent zexit from complaining about stopped jobs */
+	stopmsg = 2;
+    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. */
+ 	if (func == BIN_FG || func == BIN_BG || func == BIN_DISOWN) {
+	    /* W.r.t. the above comment, we'd better have a current job at this
+	    point or else. */
+	    if (curjob == -1 || (jobtab[curjob].stat & STAT_NOPRINT)) {
+		zwarnnam(name, "no current job", NULL, 0);
+		return 1;
+	    }
+	    firstjob = curjob;
+	} else if (func == BIN_JOBS) {
+	    /* List jobs. */
+	    for (job = 0; job != MAXJOB; job++)
+		if (job != thisjob && jobtab[job].stat) {
+		    if ((!ops['r'] && !ops['s']) ||
+			(ops['r'] && ops['s']) ||
+			(ops['r'] && !(jobtab[job].stat & STAT_STOPPED)) ||
+			(ops['s'] && jobtab[job].stat & STAT_STOPPED))
+			printjob(job + jobtab, lng, 2);
+		}
+	    return 0;
+	} else {   /* Must be BIN_WAIT, so wait for all jobs */
+	    for (job = 0; job != MAXJOB; job++)
+		if (job != thisjob && jobtab[job].stat)
+		    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
+    the above routine.  We now loop over the arguments. */
+    for (; (firstjob != -1) || *argv; (void)(*argv && argv++)) {
+	int stopped, ocj = thisjob;
+
+	if (func == BIN_WAIT && isanum(*argv)) {
+	    /* wait can take a pid; the others can't. */
+	    waitforpid((long)atoi(*argv));
+	    retval = lastval2;
+	    thisjob = ocj;
+	    continue;
+	}
+	/* The only type of argument allowed now is a job spec.  Check it. */
+	job = (*argv) ? getjob(*argv, name) : firstjob;
+	firstjob = -1;
+	if (job == -1) {
+	    retval = 1;
+	    break;
+	}
+	if (!(jobtab[job].stat & STAT_INUSE) ||
+	    (jobtab[job].stat & STAT_NOPRINT)) {
+	    zwarnnam(name, "no such job: %d", 0, job);
+	    return 1;
+	}
+	/* We have a job number.  Now decide what to do with it. */
+	switch (func) {
+	case BIN_FG:
+	case BIN_BG:
+	case BIN_WAIT:
+	    if (func == BIN_BG)
+		jobtab[job].stat |= STAT_NOSTTY;
+	    if ((stopped = (jobtab[job].stat & STAT_STOPPED)))
+		makerunning(jobtab + job);
+	    else if (func == BIN_BG) {
+		/* Silly to bg a job already running. */
+		zwarnnam(name, "job already in background", NULL, 0);
+		thisjob = ocj;
+		return 1;
+	    }
+	    /* It's time to shuffle the jobs around!  Reset the current job,
+	    and pick a sensible secondary job. */
+	    if (curjob == job) {
+		curjob = prevjob;
+		prevjob = (func == BIN_BG) ? -1 : job;
+	    }
+	    if (prevjob == job || prevjob == -1)
+		setprevjob();
+	    if (curjob == -1) {
+		curjob = prevjob;
+		setprevjob();
+	    }
+	    if (func != BIN_WAIT)
+		/* 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)) {
+		    fprintf(shout, "(pwd : ");
+		    fprintdir(jobtab[job].pwd, shout);
+		    fprintf(shout, ")\n");
+		}
+		fflush(shout);
+		if (func != BIN_WAIT) {		/* fg */
+		    thisjob = job;
+		    attachtty(jobtab[job].gleader);
+		}
+	    }
+	    if (stopped) {
+		if (func != BIN_BG && jobtab[job].ty)
+		    settyinfo(jobtab[job].ty);
+		killjb(jobtab + job, SIGCONT);
+	    }
+	    if (func == BIN_WAIT)
+	        waitjob(job, SIGINT);
+	    if (func != BIN_BG) {
+		waitjobs();
+		retval = lastval2;
+	    }
+	    break;
+	case BIN_JOBS:
+	    printjob(job + jobtab, lng, 2);
+	    break;
+	case BIN_DISOWN:
+	    deletejob(jobtab + job);
+	    break;
+	}
+	thisjob = ocj;
+    }
+    return retval;
+}
+
+/* kill: send a signal to a process.  The process(es) may be specified *
+ * by job specifier (see above) or pid.  A signal, defaulting to       *
+ * SIGTERM, may be specified by name or number, preceded by a dash.    */
+
+/**/
+int
+bin_kill(char *nam, char **argv, char *ops, int func)
+{
+    int sig = SIGTERM;
+    int returnval = 0;
+
+    /* check for, and interpret, a signal specifier */
+    if (*argv && **argv == '-') {
+	if (idigit((*argv)[1]))
+	    /* signal specified by number */
+	    sig = atoi(*argv + 1);
+	else if ((*argv)[1] != '-' || (*argv)[2]) {
+	    char *signame;
+
+	    /* with argument "-l" display the list of signal names */
+	    if ((*argv)[1] == 'l' && (*argv)[2] == '\0') {
+		if (argv[1]) {
+		    while (*++argv) {
+			sig = zstrtol(*argv, &signame, 10);
+			if (signame == *argv) {
+			    for (sig = 1; sig <= SIGCOUNT; sig++)
+				if (!cstrpcmp(sigs + sig, &signame))
+				    break;
+			    if (sig > SIGCOUNT) {
+				zwarnnam(nam, "unknown signal: SIG%s",
+					 signame, 0);
+				returnval++;
+			    } else
+				printf("%d\n", sig);
+			} else {
+			    if (*signame) {
+				zwarnnam(nam, "unknown signal: SIG%s",
+					 signame, 0);
+				returnval++;
+			    } else {
+				if (WIFSIGNALED(sig))
+				    sig = WTERMSIG(sig);
+				else if (WIFSTOPPED(sig))
+				    sig = WSTOPSIG(sig);
+				if (1 <= sig && sig <= SIGCOUNT)
+				    printf("%s\n", sigs[sig]);
+				else
+				    printf("%d\n", sig);
+			    }
+			}
+		    }
+		    return returnval;
+		}
+		printf("%s", sigs[1]);
+		for (sig = 2; sig <= SIGCOUNT; sig++)
+		    printf(" %s", sigs[sig]);
+		putchar('\n');
+		return 0;
+	    }
+	    if ((*argv)[1] == 's' && (*argv)[2] == '\0')
+		signame = *++argv;
+	    else
+		signame = *argv + 1;
+
+	    /* check for signal matching specified name */
+	    for (sig = 1; sig <= SIGCOUNT; sig++)
+		if (!cstrpcmp(sigs + sig, &signame))
+		    break;
+	    if (*signame == '0' && !signame[1])
+		sig = 0;
+	    if (sig > SIGCOUNT) {
+		zwarnnam(nam, "unknown signal: SIG%s", signame, 0);
+		zwarnnam(nam, "type kill -l for a List of signals", NULL, 0);
+		return 1;
+	    }
+	}
+	argv++;
+    }
+
+    setcurjob();
+
+    /* Remaining arguments specify processes.  Loop over them, and send the
+    signal (number sig) to each process. */
+    for (; *argv; argv++) {
+	if (**argv == '%') {
+	    /* job specifier introduced by '%' */
+	    int p;
+
+	    if ((p = getjob(*argv, nam)) == -1) {
+		returnval++;
+		continue;
+	    }
+	    if (killjb(jobtab + p, sig) == -1) {
+		zwarnnam("kill", "kill %s failed: %e", *argv, errno);
+		returnval++;
+		continue;
+	    }
+	    /* automatically update the job table if sending a SIGCONT to a
+	    job, and send the job a SIGCONT if sending it a non-stopping
+	    signal. */
+	    if (jobtab[p].stat & STAT_STOPPED) {
+		if (sig == SIGCONT)
+		    jobtab[p].stat &= ~STAT_STOPPED;
+		if (sig != SIGKILL && sig != SIGCONT && sig != SIGTSTP
+		    && sig != SIGTTOU && sig != SIGTTIN && sig != SIGSTOP)
+		    killjb(jobtab + p, SIGCONT);
+	    }
+	} else if (!isanum(*argv)) {
+	    zwarnnam("kill", "illegal pid: %s", *argv, 0);
+	    returnval++;
+	} else if (kill(atoi(*argv), sig) == -1) {
+	    zwarnnam("kill", "kill %s failed: %e", *argv, errno);
+	    returnval++;
+	}
+    }
+    return returnval < 126 ? returnval : 1;
+}
+
+/* Suspend this shell */
+
+/**/
+int
+bin_suspend(char *name, char **argv, char *ops, int func)
+{
+    /* won't suspend a login shell, unless forced */
+    if (islogin && !ops['f']) {
+	zwarnnam(name, "can't suspend login shell", NULL, 0);
+	return 1;
+    }
+    if (jobbing) {
+	/* stop ignoring signals */
+	signal_default(SIGTTIN);
+	signal_default(SIGTSTP);
+	signal_default(SIGTTOU);
+    }
+    /* suspend ourselves with a SIGTSTP */
+    kill(0, SIGTSTP);
+    if (jobbing) {
+	/* stay suspended */
+	while (gettygrp() != mypgrp) {
+	    sleep(1);
+	    if (gettygrp() != mypgrp)
+		kill(0, SIGTTIN);
+	}
+	/* restore signal handling */
+	signal_ignore(SIGTTOU);
+	signal_ignore(SIGTSTP);
+	signal_ignore(SIGTTIN);
+    }
+    return 0;
+}
+
+/* find a job named s */
+
+/**/
+int
+findjobnam(char *s)
+{
+    int jobnum;
+
+    for (jobnum = MAXJOB - 1; jobnum >= 0; jobnum--)
+	if (!(jobtab[jobnum].stat & (STAT_SUBJOB | STAT_NOPRINT)) &&
+	    jobtab[jobnum].stat && jobtab[jobnum].procs && jobnum != thisjob &&
+	    jobtab[jobnum].procs->text && strpfx(s, jobtab[jobnum].procs->text))
+	    return jobnum;
+    return -1;
+}
diff --git a/Src/lex.c b/Src/lex.c
new file mode 100644
index 000000000..6f4f2dd20
--- /dev/null
+++ b/Src/lex.c
@@ -0,0 +1,1489 @@
+/*
+ * lex.c - lexical analysis
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "lex.pro"
+
+/* tokens */
+
+/**/
+char ztokens[] = "#$^*()$=|{}[]`<>?~`,'\"\\";
+
+/* parts of the current token */
+
+/**/
+char *yytext, *tokstr;
+/**/
+int tok, tokfd;
+
+/* lexical analyzer error flag */
+ 
+/**/
+int lexstop;
+
+/* if != 0, this is the first line of the command */
+ 
+/**/
+int isfirstln;
+ 
+/* if != 0, this is the first char of the command (not including white space) */
+ 
+/**/
+int isfirstch;
+
+/* flag that an alias should be expanded after expansion ending in space */
+
+/**/
+int inalmore;
+
+/* don't do spelling correction */
+ 
+/**/
+int nocorrect;
+
+/* the line buffer */
+
+/**/
+unsigned char *line;
+
+/* cursor position and line length */
+
+/**/
+int cs, ll;
+
+/* inwhat says what exactly we are in     *
+ * (its value is one of the IN_* things). */
+
+/**/
+int inwhat;
+
+/* 1 if x added to complete in a blank between words */
+
+/**/
+int addedx;
+
+/* 1 if aliases should not be expanded */
+ 
+/**/
+int noaliases;
+
+/* we are parsing a line sent to use by the editor */
+ 
+/**/
+int zleparse;
+ 
+/**/
+int wordbeg;
+ 
+/**/
+int parbegin;
+
+/**/
+int parend;
+ 
+/* text of puctuation tokens */
+
+static char *tokstrings[WHILE + 1] = {
+    NULL,	/* NULLTOK	  0  */
+    ";",	/* SEPER	     */
+    "\\n",	/* NEWLIN	     */
+    ";",	/* SEMI		     */
+    ";;",	/* DSEMI	     */
+    "&",	/* AMPER	  5  */
+    "(",	/* INPAR	     */
+    ")",	/* OUTPAR	     */
+    "||",	/* DBAR		     */
+    "&&",	/* DAMPER	     */
+    ")",	/* OUTANG	  10 */
+    ">|",	/* OUTANGBANG	     */
+    ">>",	/* DOUTANG	     */
+    ">>|",	/* DOUTANGBANG	     */
+    "<",	/* INANG	     */
+    "<>",	/* INOUTANG	  15 */
+    "<<",	/* DINANG	     */
+    "<<-",	/* DINANGDASH	     */
+    "<&",	/* INANGAMP	     */
+    ">&",	/* OUTANGAMP	     */
+    "&>",	/* AMPOUTANG	  20 */
+    "&>|",	/* OUTANGAMPBANG     */
+    ">>&",	/* DOUTANGAMP	     */
+    ">>&|",	/* DOUTANGAMPBANG    */
+    "<<<",	/* TRINANG	     */
+    "|",	/* BAR		  25 */
+    "|&",	/* BARAMP	     */
+    "()",	/* INOUTPAR	     */
+    "((",	/* DINPAR	     */
+    "))",	/* DOUTPAR	     */
+    "&|",	/* AMPERBANG	  30 */
+    ";&",	/* SEMIAMP	     */
+};
+
+/* lexical state */
+
+static int dbparens;
+static int len = 0, bsiz = 256;
+static char *bptr;
+
+struct lexstack {
+    struct lexstack *next;
+
+    int incmdpos;
+    int incond;
+    int incasepat;
+    int dbparens;
+    int isfirstln;
+    int isfirstch;
+    int histactive;
+    int histdone;
+    int spaceflag;
+    int stophist;
+    int hlinesz;
+    char *hline;
+    char *hptr;
+    int tok;
+    int isnewlin;
+    char *tokstr;
+    char *yytext;
+    char *bptr;
+    int bsiz;
+    short *chwords;
+    int chwordlen;
+    int chwordpos;
+    int hwgetword;
+    int lexstop;
+    struct heredocs *hdocs;
+
+    unsigned char *cstack;
+    int csp;
+};
+
+static struct lexstack *lstack = NULL;
+
+/* save the lexical state */
+
+/* is this a hack or what? */
+
+/**/
+void
+lexsave(void)
+{
+    struct lexstack *ls;
+
+    ls = (struct lexstack *)malloc(sizeof(struct lexstack));
+
+    ls->incmdpos = incmdpos;
+    ls->incond = incond;
+    ls->incasepat = incasepat;
+    ls->dbparens = dbparens;
+    ls->isfirstln = isfirstln;
+    ls->isfirstch = isfirstch;
+    ls->histactive = histactive;
+    ls->histdone = histdone;
+    ls->spaceflag = spaceflag;
+    ls->stophist = stophist;
+    ls->hline = chline;
+    ls->hptr = hptr;
+    ls->hlinesz = hlinesz;
+    ls->cstack = cmdstack;
+    ls->csp = cmdsp;
+    cmdstack = (unsigned char *)zalloc(256);
+    ls->tok = tok;
+    ls->isnewlin = isnewlin;
+    ls->tokstr = tokstr;
+    ls->yytext = yytext;
+    ls->bptr = bptr;
+    ls->bsiz = bsiz;
+    ls->chwords = chwords;
+    ls->chwordlen = chwordlen;
+    ls->chwordpos = chwordpos;
+    ls->hwgetword = hwgetword;
+    ls->lexstop = lexstop;
+    ls->hdocs = hdocs;
+    cmdsp = 0;
+    inredir = 0;
+    hdocs = NULL;
+
+    ls->next = lstack;
+    lstack = ls;
+}
+
+/* restore lexical state */
+
+/**/
+void
+lexrestore(void)
+{
+    struct lexstack *ln;
+
+    DPUTS(!lstack, "BUG: lexrestore() without lexsave()");
+    incmdpos = lstack->incmdpos;
+    incond = lstack->incond;
+    incasepat = lstack->incasepat;
+    dbparens = lstack->dbparens;
+    isfirstln = lstack->isfirstln;
+    isfirstch = lstack->isfirstch;
+    histactive = lstack->histactive;
+    histdone = lstack->histdone;
+    spaceflag = lstack->spaceflag;
+    stophist = lstack->stophist;
+    chline = lstack->hline;
+    hptr = lstack->hptr;
+    if (cmdstack)
+	free(cmdstack);
+    cmdstack = lstack->cstack;
+    cmdsp = lstack->csp;
+    tok = lstack->tok;
+    isnewlin = lstack->isnewlin;
+    tokstr = lstack->tokstr;
+    yytext = lstack->yytext;
+    bptr = lstack->bptr;
+    bsiz = lstack->bsiz;
+    chwords = lstack->chwords;
+    chwordlen = lstack->chwordlen;
+    chwordpos = lstack->chwordpos;
+    hwgetword = lstack->hwgetword;
+    lexstop = lstack->lexstop;
+    hdocs = lstack->hdocs;
+    hlinesz = lstack->hlinesz;
+    errflag = 0;
+
+    ln = lstack->next;
+    free(lstack);
+    lstack = ln;
+}
+
+/**/
+void
+yylex(void)
+{
+    if (tok == LEXERR)
+	return;
+    do
+	tok = gettok();
+    while (tok != ENDINPUT && exalias());
+    if (tok == NEWLIN || tok == ENDINPUT) {
+	while (hdocs) {
+	    struct heredocs *next = hdocs->next;
+
+	    hwbegin(0);
+	    cmdpush(hdocs->rd->type == HEREDOC ? CS_HEREDOC : CS_HEREDOCD);
+	    STOPHIST
+	    hdocs->rd->name = gethere(hdocs->rd->name, hdocs->rd->type);
+	    ALLOWHIST
+	    cmdpop();
+	    hwend();
+	    hdocs->rd->type = HERESTR;
+	    zfree(hdocs, sizeof(struct heredocs));
+	    hdocs = next;
+	}
+    }
+    if (tok != NEWLIN)
+	isnewlin = 0;
+    else
+	isnewlin = (inbufct) ? -1 : 1;
+    if (tok == SEMI || tok == NEWLIN)
+	tok = SEPER;
+}
+
+/**/
+void
+ctxtlex(void)
+{
+    static int oldpos;
+
+    yylex();
+    switch (tok) {
+    case SEPER:
+    case NEWLIN:
+    case SEMI:
+    case DSEMI:
+    case SEMIAMP:
+    case AMPER:
+    case AMPERBANG:
+    case INPAR:
+    case INBRACE:
+    case DBAR:
+    case DAMPER:
+    case BAR:
+    case BARAMP:
+    case INOUTPAR:
+    case DO:
+    case THEN:
+    case ELIF:
+    case ELSE:
+    case DOUTBRACK:
+	incmdpos = 1;
+	break;
+    case STRING:
+ /* case ENVSTRING: */
+    case ENVARRAY:
+    case OUTPAR:
+    case CASE:
+    case DINBRACK:
+	incmdpos = 0;
+	break;
+    }
+    if (tok != DINPAR)
+	infor = tok == FOR ? 2 : 0;
+    if (IS_REDIROP(tok) || tok == FOR || tok == FOREACH || tok == SELECT) {
+	inredir = 1;
+	oldpos = incmdpos;
+	incmdpos = 0;
+    } else if (inredir) {
+	incmdpos = oldpos;
+	inredir = 0;
+    }
+}
+
+#define LX1_BKSLASH 0
+#define LX1_COMMENT 1
+#define LX1_NEWLIN 2
+#define LX1_SEMI 3
+#define LX1_AMPER 5
+#define LX1_BAR 6
+#define LX1_INPAR 7
+#define LX1_OUTPAR 8
+#define LX1_INANG 13
+#define LX1_OUTANG 14
+#define LX1_OTHER 15
+
+#define LX2_BREAK 0
+#define LX2_OUTPAR 1
+#define LX2_BAR 2
+#define LX2_STRING 3
+#define LX2_INBRACK 4
+#define LX2_OUTBRACK 5
+#define LX2_TILDE 6
+#define LX2_INPAR 7
+#define LX2_INBRACE 8
+#define LX2_OUTBRACE 9
+#define LX2_OUTANG 10
+#define LX2_INANG 11
+#define LX2_EQUALS 12
+#define LX2_BKSLASH 13
+#define LX2_QUOTE 14
+#define LX2_DQUOTE 15
+#define LX2_BQUOTE 16
+#define LX2_COMMA 17
+#define LX2_OTHER 18
+#define LX2_META 19
+
+static unsigned char lexact1[256], lexact2[256], lextok2[256];
+
+/**/
+void
+initlextabs(void)
+{
+    int t0;
+    static char *lx1 = "\\q\n;!&|(){}[]<>";
+    static char *lx2 = ";)|$[]~({}><=\\\'\"`,";
+
+    for (t0 = 0; t0 != 256; t0++) {
+	lexact1[t0] = LX1_OTHER;
+	lexact2[t0] = LX2_OTHER;
+	lextok2[t0] = t0;
+    }
+    for (t0 = 0; lx1[t0]; t0++)
+	lexact1[(int)lx1[t0]] = t0;
+    for (t0 = 0; lx2[t0]; t0++)
+	lexact2[(int)lx2[t0]] = t0;
+    lexact2['&'] = LX2_BREAK;
+    lexact2[STOUC(Meta)] = LX2_META;
+    lextok2['*'] = Star;
+    lextok2['?'] = Quest;
+    lextok2['{'] = Inbrace;
+    lextok2['['] = Inbrack;
+    lextok2['$'] = String;
+    lextok2['~'] = Tilde;
+    lextok2['#'] = Pound;
+    lextok2['^'] = Hat;
+}
+
+/* initialize lexical state */
+
+/**/
+void
+lexinit(void)
+{
+    incond = incasepat = nocorrect =
+    infor = dbparens = lexstop = 0;
+    incmdpos = 1;
+    tok = ENDINPUT;
+}
+
+/* add a char to the string buffer */
+
+/**/
+void
+add(int c)
+{
+    *bptr++ = c;
+    if (bsiz == ++len) {
+	int newbsiz;
+
+	newbsiz = bsiz * 8;
+	while (newbsiz < inbufct)
+	    newbsiz *= 2;
+	bptr = len + (tokstr = (char *)hrealloc(tokstr, bsiz, newbsiz));
+	bsiz = newbsiz;
+    }
+}
+
+#define SETPARBEGIN {if (zleparse && !(inbufflags & INP_ALIAS) && cs >= ll+1-inbufct) parbegin = inbufct;}
+#define SETPAREND {\
+	    if (zleparse && !(inbufflags & INP_ALIAS) && parbegin != -1 && parend == -1)\
+		if (cs >= ll + 1 - inbufct)\
+		    parbegin = -1;\
+		else\
+		    parend = inbufct;}
+
+static int
+cmd_or_math(int cs_type)
+{
+    int oldlen = len;
+    int c;
+
+    cmdpush(cs_type);
+    c = dquote_parse(')', 0);
+    cmdpop();
+    *bptr = '\0';
+    if (!c) {
+	c = hgetc();
+	if (c == ')')
+	    return 1;
+	hungetc(c);
+	lexstop = 0;
+	c = ')';
+    }
+    hungetc(c);
+    lexstop = 0;
+    while (len > oldlen) {
+	len--;
+	hungetc(itok(*--bptr) ? ztokens[*bptr - Pound] : *bptr);
+    }
+    hungetc('(');
+    return 0;
+}
+
+static int
+cmd_or_math_sub(void)
+{
+    int c = hgetc();
+
+    if (c == '(') {
+	add(Inpar);
+	add('(');
+	if (cmd_or_math(CS_MATHSUBST)) {
+	    add(')');
+	    return 0;
+	}
+	bptr -= 2;
+	len -= 2;
+    } else {
+	hungetc(c);
+	lexstop = 0;
+    }
+    return skipcomm();
+}
+
+/**/
+int
+gettok(void)
+{
+    int c, d;
+    int peekfd = -1, peek;
+
+    MUSTUSEHEAP("gettok");
+  beginning:
+    tokstr = NULL;
+    while (iblank(c = hgetc()) && !lexstop);
+    if (lexstop)
+	return (errflag) ? LEXERR : ENDINPUT;
+    isfirstln = 0;
+    wordbeg = inbufct - (qbang && c == bangchar);
+    hwbegin(-1-(qbang && c == bangchar));
+    /* word includes the last character read and possibly \ before ! */
+    if (dbparens) {
+	len = 0;
+	bptr = tokstr = (char *)ncalloc(bsiz = 256);
+	hungetc(c);
+	cmdpush(CS_MATH);
+	c = dquote_parse(infor ? ';' : ')', 0);
+	cmdpop();
+	*bptr = '\0';
+	if (!c && infor) {
+	    infor--;
+	    return DINPAR;
+	}
+	if (c || (c = hgetc()) != ')') {
+	    hungetc(c);
+	    return LEXERR;
+	}
+	dbparens = 0;
+	return DOUTPAR;
+    } else if (idigit(c)) {	/* handle 1< foo */
+	d = hgetc();
+	if (d == '>' || d == '<') {
+	    peekfd = c - '0';
+	    c = d;
+	} else {
+	    hungetc(d);
+	    lexstop = 0;
+	}
+    }
+
+    /* chars in initial position in word */
+
+    if (c == hashchar &&
+	(isset(INTERACTIVECOMMENTS) ||
+	 (!zleparse && !expanding &&
+	  (!interact || unset(SHINSTDIN) || strin)))) {
+	/* History is handled here to prevent extra  *
+	 * newlines being inserted into the history. */
+
+	while ((c = ingetc()) != '\n' && !lexstop) {
+	    hwaddc(c);
+	    addtoline(c);
+	}
+
+	if (errflag)
+	    peek = LEXERR;
+	else {
+	    hwend();
+	    hwbegin(0);
+	    hwaddc('\n');
+	    addtoline('\n');
+	    peek = NEWLIN;
+	}
+	return peek;
+    }
+    switch (lexact1[STOUC(c)]) {
+    case LX1_BKSLASH:
+	d = hgetc();
+	if (d == '\n')
+	    goto beginning;
+	hungetc(d);
+	lexstop = 0;
+	break;
+    case LX1_NEWLIN:
+	return NEWLIN;
+    case LX1_SEMI:
+	d = hgetc();
+	if(d == ';')
+	    return DSEMI;
+	else if(d == '&')
+	    return SEMIAMP;
+	hungetc(d);
+	lexstop = 0;
+	return SEMI;
+    case LX1_AMPER:
+	d = hgetc();
+	if (d == '&')
+	    return DAMPER;
+	else if (d == '!' || d == '|')
+	    return AMPERBANG;
+	else if (d == '>') {
+	    d = hgetc();
+	    if (d == '!' || d == '|')
+		return OUTANGAMPBANG;
+	    else if (d == '>') {
+		d = hgetc();
+		if (d == '!' || d == '|')
+		    return DOUTANGAMPBANG;
+		hungetc(d);
+		lexstop = 0;
+		return DOUTANGAMP;
+	    }
+	    hungetc(d);
+	    lexstop = 0;
+	    return AMPOUTANG;
+	}
+	hungetc(d);
+	lexstop = 0;
+	return AMPER;
+    case LX1_BAR:
+	d = hgetc();
+	if (d == '|')
+	    return DBAR;
+	else if (d == '&')
+	    return BARAMP;
+	hungetc(d);
+	lexstop = 0;
+	return BAR;
+    case LX1_INPAR:
+	d = hgetc();
+	if (d == '(') {
+	    if (infor) {
+		dbparens = 1;
+		return DINPAR;
+	    }
+	    if (incmdpos) {
+		len = 0;
+		bptr = tokstr = (char *)ncalloc(bsiz = 256);
+		return cmd_or_math(CS_MATH) ? DINPAR : INPAR;
+	    }
+	} else if (d == ')')
+	    return INOUTPAR;
+	hungetc(d);
+	lexstop = 0;
+	if (!(incond == 1 || incmdpos))
+	    break;
+	return INPAR;
+    case LX1_OUTPAR:
+	return OUTPAR;
+    case LX1_INANG:
+	d = hgetc();
+	if (!incmdpos && d == '(') {
+	    hungetc(d);
+	    lexstop = 0;
+	    break;
+	}
+	if (d == '>')
+	    peek = INOUTANG;
+	else if (idigit(d) || d == '-') {
+	    int tbs = 256, n = 0, nc;
+	    char *tbuf, *tbp, *ntb;
+
+	    tbuf = tbp = (char *)zalloc(tbs);
+	    hungetc(d);
+
+	    while ((nc = hgetc()) && !lexstop) {
+		if (!idigit(nc) && nc != '-')
+		    break;
+		*tbp++ = (char)nc;
+		if (++n == tbs) {
+		    ntb = (char *)realloc(tbuf, tbs *= 2);
+		    tbp += ntb - tbuf;
+		    tbuf = ntb;
+		}
+	    }
+	    if (nc == '>' && !lexstop) {
+		hungetc(nc);
+		while (n--)
+		    hungetc(*--tbp);
+		zfree(tbuf, tbs);
+		break;
+	    }
+	    if (nc && !lexstop)
+		hungetc(nc);
+	    lexstop = 0;
+	    while (n--)
+		hungetc(*--tbp);
+	    zfree(tbuf, tbs);
+	    peek = INANG;
+	} else if (d == '<') {
+	    int e = hgetc();
+
+	    if (e == '(') {
+		hungetc(e);
+		hungetc(d);
+		peek = INANG;
+	    } else if (e == '<')
+		peek = TRINANG;
+	    else if (e == '-')
+		peek = DINANGDASH;
+	    else {
+		hungetc(e);
+		lexstop = 0;
+		peek = DINANG;
+	    }
+	} else if (d == '&')
+	    peek = INANGAMP;
+	else {
+	    peek = INANG;
+	    hungetc(d);
+	    lexstop = 0;
+	}
+	tokfd = peekfd;
+	return peek;
+    case LX1_OUTANG:
+	d = hgetc();
+	if (d == '(') {
+	    hungetc(d);
+	    break;
+	} else if (d == '&') {
+	    d = hgetc();
+	    if (d == '!' || d == '|')
+		peek = OUTANGAMPBANG;
+	    else {
+		hungetc(d);
+		lexstop = 0;
+		peek = OUTANGAMP;
+	    }
+	} else if (d == '!' || d == '|')
+	    peek = OUTANGBANG;
+	else if (d == '>') {
+	    d = hgetc();
+	    if (d == '&') {
+		d = hgetc();
+		if (d == '!' || d == '|')
+		    peek = DOUTANGAMPBANG;
+		else {
+		    hungetc(d);
+		    lexstop = 0;
+		    peek = DOUTANGAMP;
+		}
+	    } else if (d == '!' || d == '|')
+		peek = DOUTANGBANG;
+	    else if (d == '(') {
+		hungetc(d);
+		hungetc('>');
+		peek = OUTANG;
+	    } else {
+		hungetc(d);
+		lexstop = 0;
+		peek = DOUTANG;
+		if (isset(HISTALLOWCLOBBER))
+		    hwaddc('|');
+	    }
+	} else {
+	    hungetc(d);
+	    lexstop = 0;
+	    peek = OUTANG;
+	    if (!incond && isset(HISTALLOWCLOBBER))
+		hwaddc('|');
+	}
+	tokfd = peekfd;
+	return peek;
+    }
+
+    /* we've started a string, now get the *
+     * rest of it, performing tokenization */
+    return gettokstr(c, 0);
+}
+
+/**/
+static int
+gettokstr(int c, int sub)
+{
+    int bct = 0, pct = 0, brct = 0;
+    int intpos = 1, in_brace_param = 0;
+    int peek, inquote;
+#ifdef DEBUG
+    int ocmdsp = cmdsp;
+#endif
+
+    peek = STRING;
+    if (!sub) {
+	len = 0;
+	bptr = tokstr = (char *)ncalloc(bsiz = 256);
+    }
+    for (;;) {
+	int act;
+	int e;
+
+	if (inblank(c) && !in_brace_param && !pct)
+	    act = LX2_BREAK;
+	else {
+	    act = lexact2[STOUC(c)];
+	    c = lextok2[STOUC(c)];
+	}
+	switch (act) {
+	case LX2_BREAK:
+	    if (!in_brace_param && !sub)
+		goto brk;
+	    break;
+	case LX2_META:
+	    c = hgetc();
+#ifdef DEBUG
+	    if (lexstop) {
+		fputs("BUG: input terminated by Meta\n", stderr);
+		fflush(stderr);
+		goto brk;
+	    }
+#endif
+	    add(Meta);
+	    break;
+	case LX2_OUTPAR:
+	    if ((sub || in_brace_param) && isset(SHGLOB))
+		break;
+	    if (!in_brace_param && !pct--)
+		if (sub) {
+		    pct = 0;
+		    break;
+		} else
+		    goto brk;
+	    c = Outpar;
+	    break;
+	case LX2_BAR:
+	    if (!pct && !in_brace_param)
+		if (sub)
+		    break;
+		else
+		    goto brk;
+	    if (unset(SHGLOB) || (!sub && !in_brace_param))
+		c = Bar;
+	    break;
+	case LX2_STRING:
+	    e = hgetc();
+	    if (e == '[') {
+		cmdpush(CS_MATHSUBST);
+		add(String);
+		add(Inbrack);
+		c = dquote_parse(']', sub);
+		cmdpop();
+		if (c) {
+		    peek = LEXERR;
+		    goto brk;
+		}
+		c = Outbrack;
+	    } else if (e == '(') {
+		add(String);
+		c = cmd_or_math_sub();
+		if (c) {
+		    peek = LEXERR;
+		    goto brk;
+		}
+		c = Outpar;
+	    } else {
+		if (e == '{') {
+		    add(c);
+		    c = Inbrace;
+		    ++bct;
+		    cmdpush(CS_BRACEPAR);
+		    if (!in_brace_param)
+			in_brace_param = bct;
+		} else {
+		    hungetc(e);
+		    lexstop = 0;
+		}
+	    }
+	    break;
+	case LX2_INBRACK:
+	    if (!in_brace_param)
+		brct++;
+	    c = Inbrack;
+	    break;
+	case LX2_OUTBRACK:
+	    if (!in_brace_param)
+		brct--;
+	    if (brct < 0)
+		brct = 0;
+	    c = Outbrack;
+	    break;
+	case LX2_INPAR:
+	    if ((sub || in_brace_param) && isset(SHGLOB))
+		break;
+	    if (!in_brace_param) {
+		if (!sub) {
+		    e = hgetc();
+		    hungetc(e);
+		    lexstop = 0;
+		    if (e == ')' ||
+			(incmdpos && !brct && peek != ENVSTRING))
+			goto brk;
+		}
+		pct++;
+	    }
+	    c = Inpar;
+	    break;
+	case LX2_INBRACE:
+	    if (isset(IGNOREBRACES) || sub)
+		c = '{';
+	    else {
+		if (!len && incmdpos) {
+		    add('{');
+		    *bptr = '\0';
+		    return STRING;
+		}
+		if (in_brace_param)
+		    cmdpush(CS_BRACE);
+		bct++;
+	    }
+	    break;
+	case LX2_OUTBRACE:
+	    if ((isset(IGNOREBRACES) || sub) && !in_brace_param)
+		break;
+	    if (!bct)
+		break;
+	    if (in_brace_param)
+		cmdpop();
+	    if (bct-- == in_brace_param)
+		in_brace_param = 0;
+	    c = Outbrace;
+	    break;
+	case LX2_COMMA:
+	    if (unset(IGNOREBRACES) && !sub && bct > in_brace_param)
+		c = Comma;
+	    break;
+	case LX2_OUTANG:
+	    if (!intpos)
+		if (in_brace_param || sub)
+		    break;
+		else
+		    goto brk;
+	    e = hgetc();
+	    if (e != '(') {
+		hungetc(e);
+		lexstop = 0;
+		goto brk;
+	    }
+	    add(Outang);
+	    if (skipcomm()) {
+		peek = LEXERR;
+		goto brk;
+	    }
+	    c = Outpar;
+	    break;
+	case LX2_INANG:
+	    if (isset(SHGLOB) && sub)
+		break;
+	    e = hgetc();
+	    if (!(idigit(e) || e == '-' || (e == '(' && intpos))) {
+		hungetc(e);
+		lexstop = 0;
+		if (in_brace_param || sub)
+		    break;
+		goto brk;
+	    }
+	    c = Inang;
+	    if (e == '(') {
+		add(c);
+		if (skipcomm()) {
+		    peek = LEXERR;
+		    goto brk;
+		}
+		c = Outpar;
+	    } else {
+		add(c);
+		c = e;
+		while (c != '>' && !lexstop)
+		    add(c), c = hgetc();
+		c = Outang;
+	    }
+	    break;
+	case LX2_EQUALS:
+	    if (intpos) {
+		e = hgetc();
+		if (e != '(') {
+		    hungetc(e);
+		    lexstop = 0;
+		    c = Equals;
+		} else {
+		    add(Equals);
+		    if (skipcomm()) {
+			peek = LEXERR;
+			goto brk;
+		    }
+		    c = Outpar;
+		}
+	    } else if (!sub && peek != ENVSTRING &&
+		       incmdpos && !bct && !brct) {
+		char *t = tokstr;
+		if (idigit(*t))
+		    while (++t < bptr && idigit(*t));
+		else {
+		    while (iident(*t) && ++t < bptr);
+		    if (t < bptr) {
+			*bptr = '\0';
+			skipparens(Inbrack, Outbrack, &t);
+		    }
+		}
+		if (t == bptr) {
+		    e = hgetc();
+		    if (e == '(' && incmdpos) {
+			*bptr = '\0';
+			return ENVARRAY;
+		    }
+		    hungetc(e);
+		    lexstop = 0;
+		    peek = ENVSTRING;
+		    intpos = 2;
+		} else
+		    c = Equals;
+	    } else
+		c = Equals;
+	    break;
+	case LX2_BKSLASH:
+	    c = hgetc();
+	    if (c == '\n') {
+		c = hgetc();
+		if (!lexstop)
+		    continue;
+	    } else
+		add(Bnull);
+	    if (lexstop)
+		goto brk;
+	    break;
+	case LX2_QUOTE: {
+	    int strquote = (len && bptr[-1] == String);
+
+	    add(Snull);
+	    cmdpush(CS_QUOTE);
+	    for (;;) {
+		STOPHIST
+		while ((c = hgetc()) != '\'' && !lexstop) {
+		    if (strquote && c == '\\') {
+			add(c);
+			c = hgetc();
+			if (lexstop)
+			    break;
+		    } else if (!sub && isset(CSHJUNKIEQUOTES) && c == '\n') {
+			if (bptr[-1] == '\\')
+			    bptr--, len--;
+			else
+			    break;
+		    }
+		    add(c);
+		}
+		ALLOWHIST
+		if (c != '\'') {
+		    zerr("unmatched \'", NULL, 0);
+		    peek = LEXERR;
+		    cmdpop();
+		    goto brk;
+		}
+		e = hgetc();
+		if (e != '\'' || unset(RCQUOTES))
+		    break;
+		add(c);
+	    }
+	    cmdpop();
+	    hungetc(e);
+	    lexstop = 0;
+	    c = Snull;
+	    break;
+	}
+	case LX2_DQUOTE:
+	    add(Dnull);
+	    cmdpush(CS_DQUOTE);
+	    c = dquote_parse('"', sub);
+	    cmdpop();
+	    if (c) {
+		zerr("unmatched \"", NULL, 0);
+		peek = LEXERR;
+		goto brk;
+	    }
+	    c = Dnull;
+	    break;
+	case LX2_BQUOTE:
+	    add(Tick);
+	    cmdpush(CS_BQUOTE);
+	    SETPARBEGIN
+	    inquote = 0;
+	    while ((c = hgetc()) != '`' && !lexstop)
+		if (c == '\\') {
+		    c = hgetc();
+		    if (c != '\n') {
+			add(c == '`' || c == '\\' || c == '$' ? Bnull : '\\');
+			add(c);
+		    }
+		    else if (!sub && isset(CSHJUNKIEQUOTES))
+			add(c);
+		} else {
+		    if (!sub && isset(CSHJUNKIEQUOTES) && c == '\n') {
+			break;
+		    }
+		    add(c);
+		    if (c == '\'')
+			if ((inquote = !inquote))
+			    STOPHIST
+			else
+			    ALLOWHIST
+		}
+	    if (inquote)
+		ALLOWHIST
+	    cmdpop();
+	    if (c != '`') {
+		zerr("unmatched `", NULL, 0);
+		peek = LEXERR;
+		goto brk;
+	    }
+	    c = Tick;
+	    SETPAREND
+	    break;
+	}
+	add(c);
+	c = hgetc();
+	if (intpos)
+	    intpos--;
+	if (lexstop)
+	    break;
+    }
+  brk:
+    hungetc(c);
+    if (in_brace_param) {
+	while(bct-- >= in_brace_param)
+	    cmdpop();
+	zerr("closing brace expected", NULL, 0);
+    } else if (unset(IGNOREBRACES) && !sub && len > 1 &&
+	       peek == STRING && bptr[-1] == '}' && bptr[-2] != Bnull) {
+	/* hack to get {foo} command syntax work */
+	bptr--;
+	len--;
+	lexstop = 0;
+	hungetc('}');
+    }
+    *bptr = '\0';
+    DPUTS(cmdsp != ocmdsp, "BUG: gettok: cmdstack changed.");
+    return peek;
+}
+
+/**/
+static int
+dquote_parse(char endchar, int sub)
+{
+    int pct = 0, brct = 0, bct = 0, intick = 0, err = 0;
+    int c;
+    int math = endchar == ')' || endchar == ']';
+    int zlemath = math && cs > ll + addedx - inbufct;
+
+    while (((c = hgetc()) != endchar || bct ||
+	    (math && ((pct > 0) || (brct > 0))) ||
+	    intick) && !lexstop) {
+      cont:
+	switch (c) {
+	case '\\':
+	    c = hgetc();
+	    if (c != '\n') {
+		if (c == '$' || c == '\\' || (c == '}' && !intick && bct) ||
+		    c == endchar || c == '`')
+		    add(Bnull);
+		else {
+		    /* lexstop is implicitely handled here */
+		    add('\\');
+		    goto cont;
+		}
+	    } else if (sub || unset(CSHJUNKIEQUOTES) || endchar != '"')
+		continue;
+	    break;
+	case '\n':
+	    err = !sub && isset(CSHJUNKIEQUOTES) && endchar == '"';
+	    break;
+	case '$':
+	    if (intick)
+		break;
+	    c = hgetc();
+	    if (c == '(') {
+		add(Qstring);
+		err = cmd_or_math_sub();
+		c = Outpar;
+	    } else if (c == '[') {
+		add(String);
+		add(Inbrack);
+		cmdpush(CS_MATHSUBST);
+		err = dquote_parse(']', sub);
+		cmdpop();
+		c = Outbrack;
+	    } else if (c == '{') {
+		add(Qstring);
+		c = Inbrace;
+		cmdpush(CS_BRACEPAR);
+		bct++;
+	    } else if (c == '$')
+		add(Qstring);
+	    else {
+		hungetc(c);
+		lexstop = 0;
+		c = Qstring;
+	    }
+	    break;
+	case '}':
+	    if (intick || !bct)
+		break;
+	    c = Outbrace;
+	    bct--;
+	    cmdpop();
+	    break;
+	case '`':
+	    c = Qtick;
+	    if (intick == 2)
+		ALLOWHIST
+	    if ((intick = !intick)) {
+		SETPARBEGIN
+		cmdpush(CS_BQUOTE);
+	    } else {
+		SETPAREND
+	        cmdpop();
+	    }
+	    break;
+	case '\'':
+	    if (!intick)
+		break;
+	    if (intick == 1)
+		intick = 2, STOPHIST
+	    else
+		intick = 1, ALLOWHIST
+	    break;
+	case '(':
+	    pct++;
+	    break;
+	case ')':
+	    err = (!pct-- && math);
+	    break;
+	case '[':
+	    brct++;
+	    break;
+	case ']':
+	    err = (!brct-- && math);
+	    break;
+	case '"':
+	    if (intick || (!endchar && !bct))
+		break;
+	    if (bct) {
+		add(Dnull);
+		err = dquote_parse('"', sub);
+		c = Dnull;
+	    } else
+		err = 1;
+	    break;
+	}
+	if (err || lexstop)
+	    break;
+	add(c);
+    }
+    if (intick == 2)
+	ALLOWHIST
+    if (intick)
+	cmdpop();
+    while (bct--)
+	cmdpop();
+    if (lexstop)
+	err = intick || endchar || err;
+    else if (err == 1)
+	err = c;
+    if (zlemath && cs <= ll + 1 - inbufct)
+	inwhat = IN_MATH;
+    return err;
+}
+
+/* Tokenize a string given in s. Parsing is done as in double *
+ * quotes.  This is usually called before singsub().          */
+
+/**/
+int
+parsestr(char *s)
+{
+    int l = strlen(s), err;
+
+    HEAPALLOC {
+	lexsave();
+	untokenize(s);
+	inpush(dupstring(s), 0, NULL);
+	strinbeg();
+	stophist = 2;
+	len = 0;
+	bptr = tokstr = s;
+	bsiz = l + 1;
+	err = dquote_parse('\0', 1);
+	*bptr = '\0';
+	strinend();
+	inpop();
+	DPUTS(cmdsp, "BUG: parsestr: cmdstack not empty.");
+	lexrestore();
+	if (err) {
+	    untokenize(s);
+	    if (err > 32 && err < 127)
+		zerr("parse error near `%c'", NULL, err);
+	    else
+		zerr("parse error", NULL, 0);
+	}
+    } LASTALLOC;
+    return err;
+}
+
+/* Tokenize a string given in s. Parsing is done as if s were a normal *
+ * command-line argument but it may contain separators.  This is used  *
+ * to parse the right-hand side of ${...%...} substitutions.           */
+
+/**/
+int
+parse_subst_string(char *s)
+{
+    int c, l = strlen(s), err;
+
+    if (! *s)
+	return 0;
+    lexsave();
+    untokenize(s);
+    inpush(dupstring(s), 0, NULL);
+    strinbeg();
+    stophist = 2;
+    len = 0;
+    bptr = tokstr = s;
+    bsiz = l + 1;
+    c = hgetc();
+    c = gettokstr(c, 1);
+    err = errflag;
+    strinend();
+    inpop();
+    DPUTS(cmdsp, "BUG: parse_subst_string: cmdstack not empty.");
+    lexrestore();
+    errflag = err;
+    if (c == LEXERR) {
+	untokenize(s);
+	return 1;
+    }
+#ifdef DEBUG
+    if (c != STRING || len != l || errflag) {
+	fprintf(stderr, "Oops. Bug in parse_subst_string: %s\n",
+		len < l ? "len < l" : errflag ? "errflag" : "c != STRING");
+	fflush(stderr);
+	untokenize(s);
+	return 1;
+    }
+#endif
+    return 0;
+}
+
+/* expand aliases and reserved words */
+
+/**/
+int
+exalias(void)
+{
+    Alias an;
+    Reswd rw;
+
+    hwend();
+    if (interact && isset(SHINSTDIN) && !strin && !incasepat &&
+	tok == STRING && !nocorrect && !(inbufflags & INP_ALIAS) &&
+	(isset(CORRECTALL) || (isset(CORRECT) && incmdpos)))
+	spckword(&tokstr, 1, incmdpos, 1);
+
+    if (!tokstr) {
+	yytext = tokstrings[tok];
+	if (yytext)
+	    yytext = dupstring(yytext);
+	return 0;
+    }
+
+    if (has_token(tokstr)) {
+	char *p, *t;
+
+	yytext = p = ncalloc(strlen(tokstr) + 1);
+	for (t = tokstr; (*p++ = itok(*t) ? ztokens[*t++ - Pound] : *t++););
+    } else
+	yytext = tokstr;
+
+    if (zleparse && !(inbufflags & INP_ALIAS)) {
+	int zp = zleparse;
+
+	gotword();
+	if (zp == 1 && !zleparse) {
+	    return 0;
+	}
+    }
+
+    if (tok == STRING) {
+	/* Check for an alias */
+	an = noaliases ? NULL : (Alias) aliastab->getnode(aliastab, yytext);
+	if (an && !an->inuse && ((an->flags & ALIAS_GLOBAL) || incmdpos ||
+	     inalmore)) {
+	    inpush(an->text, INP_ALIAS, an);
+	    /* remove from history if it begins with space */
+	    if (isset(HISTIGNORESPACE) && an->text[0] == ' ')
+		remhist();
+	    lexstop = 0;
+	    return 1;
+	}
+
+	/* Then check for a reserved word */
+	if ((incmdpos ||
+	     (unset(IGNOREBRACES) && yytext[0] == '}' && !yytext[1])) &&
+	    (rw = (Reswd) reswdtab->getnode(reswdtab, yytext))) {
+	    tok = rw->token;
+	    if (tok == DINBRACK)
+		incond = 1;
+	} else if (incond && !strcmp(yytext, "]]")) {
+	    tok = DOUTBRACK;
+	    incond = 0;
+	} else if (incond && yytext[0] == '!' && !yytext[1])
+	    tok = BANG;
+    }
+    inalmore = 0;
+    return 0;
+}
+
+/* skip (...) */
+
+/**/
+static int
+skipcomm(void)
+{
+    int pct = 1, c;
+
+    cmdpush(CS_CMDSUBST);
+    SETPARBEGIN
+    c = Inpar;
+    do {
+	add(c);
+	c = hgetc();
+	if (itok(c) || lexstop)
+	    break;
+	switch (c) {
+	case '(':
+	    pct++;
+	    break;
+	case ')':
+	    pct--;
+	    break;
+	case '\\':
+	    add(c);
+	    c = hgetc();
+	    break;
+	case '\'': {
+	    int strquote = bptr[-1] == '$';
+	    add(c);
+	    STOPHIST
+	    while ((c = hgetc()) != '\'' && !lexstop) {
+		if (c == '\\' && strquote) {
+		    add(c);
+		    c = hgetc();
+		}
+		add(c);
+	    }
+	    ALLOWHIST
+	    break;
+	}
+	case '\"':
+	    add(c);
+	    while ((c = hgetc()) != '\"' && !lexstop)
+		if (c == '\\') {
+		    add(c);
+		    add(hgetc());
+		} else
+		    add(c);
+	    break;
+	case '`':
+	    add(c);
+	    while ((c = hgetc()) != '`' && !lexstop)
+		if (c == '\\')
+		    add(c), add(hgetc());
+		else
+		    add(c);
+	    break;
+	}
+    }
+    while (pct);
+    if (!lexstop)
+	SETPAREND
+    cmdpop();
+    return lexstop;
+}
diff --git a/Src/linklist.c b/Src/linklist.c
new file mode 100644
index 000000000..62a962595
--- /dev/null
+++ b/Src/linklist.c
@@ -0,0 +1,222 @@
+/*
+ * linklist.c - linked lists
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "linklist.pro"
+
+/* Get an empty linked list header */
+
+/**/
+LinkList
+newlinklist(void)
+{
+    LinkList list;
+
+    list = (LinkList) alloc(sizeof *list);
+    list->first = NULL;
+    list->last = (LinkNode) list;
+    return list;
+}
+
+/* Insert a node in a linked list after a given node */
+
+/**/
+LinkNode
+insertlinknode(LinkList list, LinkNode node, void *dat)
+{
+    LinkNode tmp, new;
+
+    tmp = node->next;
+    node->next = new = (LinkNode) alloc(sizeof *tmp);
+    new->last = node;
+    new->dat = dat;
+    new->next = tmp;
+    if (tmp)
+	tmp->last = new;
+    else
+	list->last = new;
+    return new;
+}
+
+/* Insert an already-existing node into a linked list after a given node */
+
+/**/
+LinkNode
+uinsertlinknode(LinkList list, LinkNode node, LinkNode new)
+{
+    LinkNode tmp = node->next;
+    node->next = new;
+    new->last = node;
+    new->next = tmp;
+    if (tmp)
+	tmp->last = new;
+    else
+	list->last = new;
+    return new;
+}
+
+/* Insert a list in another list */
+
+/**/
+void
+insertlinklist(LinkList l, LinkNode where, LinkList x)
+{
+    LinkNode nx;
+
+    nx = where->next;
+    if (!l->first)
+	return;
+    where->next = l->first;
+    l->last->next = nx;
+    l->first->last = where;
+    if (nx)
+	nx->last = l->last;
+    else
+	x->last = l->last;
+}
+
+/* Get top node in a linked list */
+
+/**/
+void *
+getlinknode(LinkList list)
+{
+    void *dat;
+    LinkNode node;
+
+    if (!(node = list->first))
+	return NULL;
+    dat = node->dat;
+    list->first = node->next;
+    if (node->next)
+	node->next->last = (LinkNode) list;
+    else
+	list->last = (LinkNode) list;
+    zfree(node, sizeof(struct linknode));
+    return dat;
+}
+
+/* Get top node in a linked list without freeing */
+
+/**/
+void *
+ugetnode(LinkList list)
+{
+    void *dat;
+    LinkNode node;
+
+    if (!(node = list->first))
+	return NULL;
+    dat = node->dat;
+    list->first = node->next;
+    if (node->next)
+	node->next->last = (LinkNode) list;
+    else
+	list->last = (LinkNode) list;
+    return dat;
+}
+
+/* Remove a node from a linked list */
+
+/**/
+void *
+remnode(LinkList list, LinkNode nd)
+{
+    void *dat;
+
+    nd->last->next = nd->next;
+    if (nd->next)
+	nd->next->last = nd->last;
+    else
+	list->last = nd->last;
+    dat = nd->dat;
+    zfree(nd, sizeof(struct linknode));
+
+    return dat;
+}
+
+/* Remove a node from a linked list without freeing */
+
+/**/
+void *
+uremnode(LinkList list, LinkNode nd)
+{
+    void *dat;
+
+    nd->last->next = nd->next;
+    if (nd->next)
+	nd->next->last = nd->last;
+    else
+	list->last = nd->last;
+    dat = nd->dat;
+    return dat;
+}
+
+/* Free a linked list */
+
+/**/
+void
+freelinklist(LinkList list, FreeFunc freefunc)
+{
+    LinkNode node, next;
+
+    for (node = list->first; node; node = next) {
+	next = node->next;
+	if (freefunc)
+	    freefunc(node->dat);
+	zfree(node, sizeof(struct linknode));
+    }
+    zfree(list, sizeof(struct linklist));
+}
+
+/* Count the number of nodes in a linked list */
+
+/**/
+int
+countlinknodes(LinkList list)
+{
+    LinkNode nd;
+    int ct = 0;
+
+    for (nd = firstnode(list); nd; incnode(nd), ct++);
+    return ct;
+}
+
+/**/
+void
+rolllist(LinkList l, LinkNode nd)
+{
+    l->last->next = l->first;
+    l->first->last = l->last;
+    l->first = nd;
+    l->last = nd->last;
+    nd->last = (LinkNode) l;
+    l->last->next = 0;
+}
+
diff --git a/Src/loop.c b/Src/loop.c
new file mode 100644
index 000000000..5fbf2b841
--- /dev/null
+++ b/Src/loop.c
@@ -0,0 +1,421 @@
+/*
+ * loop.c - loop execution
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "loop.pro"
+
+/* # of nested loops we are in */
+ 
+/**/
+int loops;
+ 
+/* # of continue levels */
+ 
+/**/
+int contflag;
+ 
+/* # of break levels */
+ 
+/**/
+int breaks;
+ 
+/**/
+int
+execfor(Cmd cmd)
+{
+    List list;
+    Forcmd node;
+    char *str;
+    int val;
+    LinkList args;
+
+    node = cmd->u.forcmd;
+    args = cmd->args;
+    if (node->condition) {
+	str = node->name;
+	singsub(&str);
+	if (!errflag)
+	    matheval(str);
+	if (errflag)
+	    return lastval = errflag;
+    } else if (!node->inflag) {
+	char **x;
+
+	args = newlinklist();
+	for (x = pparams; *x; x++)
+	    addlinknode(args, ztrdup(*x));
+    }
+    lastval = 0;
+    loops++;
+    pushheap();
+    for (;;) {
+	if (node->condition) {
+	    str = dupstring(node->condition);
+	    singsub(&str);
+	    if (!errflag) {
+		while (iblank(*str))
+		    str++;
+		if (*str)
+		    val = matheval(str);
+		else
+		    val = 1;
+	    }
+	    if (errflag) {
+		if (breaks)
+		    breaks--;
+		lastval = 1;
+		break;
+	    }
+	    if (!val)
+		break;
+	} else {
+	    str = (char *) ugetnode(args);
+	    if (!str)
+		break;
+	    setsparam(node->name, ztrdup(str));
+	}
+	list = (List) dupstruct(node->list);
+	execlist(list, 1, (cmd->flags & CFLAG_EXEC) && empty(args));
+	if (breaks) {
+	    breaks--;
+	    if (breaks || !contflag)
+		break;
+	    contflag = 0;
+	}
+	if (node->condition && !errflag) {
+	    str = dupstring(node->advance);
+	    singsub(&str);
+	    if (!errflag)
+		matheval(str);
+	}
+	if (errflag) {
+	    if (breaks)
+		breaks--;
+	    lastval = 1;
+	    break;
+	}
+	freeheap();
+    }
+    popheap();
+    loops--;
+    return lastval;
+}
+
+/**/
+int
+execselect(Cmd cmd)
+{
+    List list;
+    Forcmd node;
+    char *str, *s;
+    LinkList args;
+    LinkNode n;
+    int i;
+    FILE *inp;
+
+    node = cmd->u.forcmd;
+    args = cmd->args;
+    if (!node->inflag) {
+	char **x;
+
+	args = newlinklist();
+	for (x = pparams; *x; x++)
+	    addlinknode(args, ztrdup(*x));
+    }
+    if (empty(args))
+	return 1;
+    loops++;
+    lastval = 0;
+    pushheap();
+    inp = fdopen(dup((SHTTY == -1) ? 0 : SHTTY), "r");
+    selectlist(args);
+    for (;;) {
+	for (;;) {
+	    if (empty(bufstack)) {
+	    	if (interact && SHTTY != -1 && isset(USEZLE)) {
+		    isfirstln = 1;
+		    str = (char *)zleread(prompt3, NULL, 0);
+	    	} else {
+		    str = promptexpand(prompt3, 0, NULL, NULL);
+		    zputs(str, stderr);
+		    free(str);
+		    fflush(stderr);
+		    str = fgets(zalloc(256), 256, inp);
+	    	}
+	    } else
+		str = (char *)getlinknode(bufstack);
+	    if (!str || errflag) {
+		if (breaks)
+		    breaks--;
+		fprintf(stderr, "\n");
+		fflush(stderr);
+		goto done;
+	    }
+	    if ((s = strchr(str, '\n')))
+		*s = '\0';
+	    if (*str)
+	      break;
+	    selectlist(args);
+	}
+	setsparam("REPLY", ztrdup(str));
+	i = atoi(str);
+	if (!i)
+	    str = "";
+	else {
+	    for (i--, n = firstnode(args); n && i; incnode(n), i--);
+	    if (n)
+		str = (char *) getdata(n);
+	    else
+		str = "";
+	}
+	setsparam(node->name, ztrdup(str));
+	list = (List) dupstruct(node->list);
+	execlist(list, 1, 0);
+	freeheap();
+	if (breaks) {
+	    breaks--;
+	    if (breaks || !contflag)
+		break;
+	    contflag = 0;
+	}
+	if (errflag)
+	    break;
+    }
+  done:
+    popheap();
+    fclose(inp);
+    loops--;
+    return lastval;
+}
+
+/* And this is used to print select lists. */
+
+/**/
+static void
+selectlist(LinkList l)
+{
+    size_t longest = 1, fct, fw = 0, colsz, t0, t1, ct;
+    LinkNode n;
+    char **arr, **ap;
+
+    trashzle();
+    ct = countlinknodes(l);
+    ap = arr = (char **)alloc((countlinknodes(l) + 1) * sizeof(char **));
+
+    for (n = (LinkNode) firstnode(l); n; incnode(n))
+	*ap++ = (char *)getdata(n);
+    *ap = NULL;
+    for (ap = arr; *ap; ap++)
+	if (strlen(*ap) > longest)
+	    longest = strlen(*ap);
+    t0 = ct;
+    longest++;
+    while (t0)
+	t0 /= 10, longest++;
+    /* to compensate for added ')' */
+    fct = (columns - 1) / (longest + 3);
+    if (fct == 0)
+	fct = 1;
+    else
+	fw = (columns - 1) / fct;
+    colsz = (ct + fct - 1) / fct;
+    for (t1 = 0; t1 != colsz; t1++) {
+	ap = arr + t1;
+	do {
+	    int t2 = strlen(*ap) + 2, t3;
+
+	    fprintf(stderr, "%d) %s", t3 = ap - arr + 1, *ap);
+	    while (t3)
+		t2++, t3 /= 10;
+	    for (; t2 < fw; t2++)
+		fputc(' ', stderr);
+	    for (t0 = colsz; t0 && *ap; t0--, ap++);
+	}
+	while (*ap);
+	fputc('\n', stderr);
+    }
+
+ /* Below is a simple attempt at doing it the Korn Way..
+       ap = arr;
+       t0 = 0;
+       do {
+           t0++;
+           fprintf(stderr,"%d) %s\n",t0,*ap);
+           ap++;
+       }
+       while (*ap);*/
+    fflush(stderr);
+}
+
+/**/
+int
+execwhile(Cmd cmd)
+{
+    List list;
+    struct whilecmd *node;
+    int olderrexit, oldval;
+
+    olderrexit = noerrexit;
+    node = cmd->u.whilecmd;
+    oldval = 0;
+    pushheap();
+    loops++;
+    for (;;) {
+	list = (List) dupstruct(node->cont);
+	noerrexit = 1;
+	execlist(list, 1, 0);
+	noerrexit = olderrexit;
+	if (!((lastval == 0) ^ node->cond)) {
+	    if (breaks)
+		breaks--;
+	    lastval = oldval;
+	    break;
+	}
+	list = (List) dupstruct(node->loop);
+	execlist(list, 1, 0);
+	if (breaks) {
+	    breaks--;
+	    if (breaks || !contflag)
+		break;
+	    contflag = 0;
+	}
+	freeheap();
+	if (errflag) {
+	    lastval = 1;
+	    break;
+	}
+	oldval = lastval;
+    }
+    popheap();
+    loops--;
+    return lastval;
+}
+
+/**/
+int
+execrepeat(Cmd cmd)
+{
+    List list;
+    int count;
+
+    lastval = 0;
+    if (empty(cmd->args) || nextnode(firstnode(cmd->args))) {
+	zerr("bad argument for repeat", NULL, 0);
+	return 1;
+    }
+    count = atoi(peekfirst(cmd->args));
+    pushheap();
+    loops++;
+    while (count--) {
+	list = (List) dupstruct(cmd->u.list);
+	execlist(list, 1, 0);
+	freeheap();
+	if (breaks) {
+	    breaks--;
+	    if (breaks || !contflag)
+		break;
+	    contflag = 0;
+	}
+	if (errflag) {
+	    lastval = 1;
+	    break;
+	}
+    }
+    popheap();
+    loops--;
+    return lastval;
+}
+
+/**/
+int
+execif(Cmd cmd)
+{
+    struct ifcmd *node;
+    int olderrexit;
+    List *i, *t;
+
+    olderrexit = noerrexit;
+    node = cmd->u.ifcmd;
+    i = node->ifls;
+    t = node->thenls;
+
+    if (!noerrexit)
+	noerrexit = 1;
+    while (*i) {
+	execlist(*i, 1, 0);
+	if (!lastval)
+	    break;
+	i++;
+	t++;
+    }
+    noerrexit = olderrexit;
+
+    if (*t)
+	execlist(*t, 1, cmd->flags & CFLAG_EXEC);
+    else
+	lastval = 0;
+
+    return lastval;
+}
+
+/**/
+int
+execcase(Cmd cmd)
+{
+    struct casecmd *node;
+    char *word;
+    List *l;
+    char **p;
+
+    node = cmd->u.casecmd;
+    l = node->lists;
+    p = node->pats;
+
+    word = *p++;
+    singsub(&word);
+    untokenize(word);
+    lastval = 0;
+
+    if (node) {
+	while (*p) {
+	    char *pat = *p + 1;
+	    singsub(&pat);
+	    if (matchpat(word, pat)) {
+		do {
+		    execlist(*l++, 1, **p == ';' && (cmd->flags & CFLAG_EXEC));
+		} while(**p++ == '&' && *p);
+		break;
+	    }
+	    p++;
+	    l++;
+	}
+    }
+    return lastval;
+}
+
diff --git a/Src/main.c b/Src/main.c
new file mode 100644
index 000000000..7ec8f07bd
--- /dev/null
+++ b/Src/main.c
@@ -0,0 +1,99 @@
+/*
+ * main.c - the main() function
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "main.pro"
+
+/**/
+int
+main(int argc, char **argv)
+{
+    char **t;
+#ifdef LC_ALL
+    setlocale(LC_ALL, "");
+#endif
+
+    global_permalloc();
+
+    init_hackzero(argv, environ);
+
+    for (t = argv; *t; *t = metafy(*t, -1, META_ALLOC), t++);
+
+    if (!(zsh_name = strrchr(argv[0], '/')))
+	zsh_name = argv[0];
+    else
+	zsh_name++;
+    if (*zsh_name == '-')
+	zsh_name++;
+
+    fdtable_size = OPEN_MAX;
+    fdtable = zcalloc(fdtable_size);
+
+    createoptiontable();
+    emulate(zsh_name, 1);   /* initialises most options */
+    opts[LOGINSHELL] = (**argv == '-');
+    opts[MONITOR] = 1;   /* may be unset in init_io() */
+    opts[PRIVILEGED] = (getuid() != geteuid() || getgid() != getegid());
+    opts[USEZLE] = 1;   /* may be unset in init_io() */
+    parseargs(argv);   /* sets INTERACTIVE, SHINSTDIN and SINGLECOMMAND */
+
+    SHTTY = -1;
+    init_io();
+    setupvals();
+    init_signals();
+    global_heapalloc();
+    init_bltinmods();
+    run_init_scripts();
+    init_misc();
+
+    for (;;) {
+	do
+	    loop(1,0);
+	while (tok != ENDINPUT && (tok != LEXERR || isset(SHINSTDIN)));
+	if (tok == LEXERR) {
+	    stopmsg = 1;
+	    zexit(lastval, 0);
+	}
+	if (!(isset(IGNOREEOF) && interact)) {
+#if 0
+	    if (interact)
+		fputs(islogin ? "logout\n" : "exit\n", shout);
+#endif
+	    zexit(lastval, 0);
+	    continue;
+	}
+	noexitct++;
+	if (noexitct >= 10) {
+	    stopmsg = 1;
+	    zexit(lastval, 0);
+	}
+	zerrnam("zsh", (!islogin) ? "use 'exit' to exit."
+		: "use 'logout' to logout.", NULL, 0);
+    }
+}
diff --git a/Src/makepro.awk b/Src/makepro.awk
new file mode 100644
index 000000000..b5d2f3dc4
--- /dev/null
+++ b/Src/makepro.awk
@@ -0,0 +1,146 @@
+#
+# makepro.awk - generate prototype lists
+#
+
+BEGIN {
+    aborting = 0
+
+    # arg 1 is the name of the file to process
+    # arg 2 is the name of the subdirectory it is in
+    if(ARGC != 3) {
+	aborting = 1
+	exit 1
+    }
+    name = ARGV[1]
+    gsub(/^.*\//, "", name)
+    gsub(/\.c$/, "", name)
+    name = ARGV[2] "_" name
+    gsub(/\//, "_", name)
+    ARGC--
+
+    # `locals' is a list of local declarations, built up while global
+    # declarations are output.
+    locals = ""
+
+    printf "#ifndef have_%s_globals\n", name
+    printf "#define have_%s_globals\n", name
+    printf "\n"
+}
+
+# all relevant declarations are preceded by "/**/" on a line by itself
+
+/^\/\*\*\/$/ {
+    # The declaration is on following lines.  The interesting part might
+    # be terminated by a `{' (`int foo(void) { }' or `int bar[] = {')
+    # or `;' (`int x;').
+    line = ""
+    isfunc = 0
+    while(1) {
+	if(getline <= 0) {
+	    aborting = 1
+	    exit 1
+	}
+	gsub(/\t/, " ")
+	line = line " " $0
+	gsub(/\/\*([^*]|\*+[^*\/])*\*+\//, " ", line)
+	if(line ~ /\/\*/)
+	    continue
+	# If it is a function definition, note so.
+	if(line ~ /\) *[{].*$/) #}
+	    isfunc = 1
+	if(sub(/ *[{;].*$/, "", line)) #}
+	    break
+    }
+    # Put spaces around each identifier.
+    while(match(line, /[^_0-9A-Za-z ][_0-9A-Za-z]/) ||
+	    match(line, /[_0-9A-Za-z][^_0-9A-Za-z ]/))
+	line = substr(line, 1, RSTART) " " substr(line, RSTART+1)
+    # Separate declarations into a type and a list of declarators.
+    # In each declarator, "@{" and "@}" are used in place of parens to
+    # mark function parameter lists, and "@!" is used in place of commas
+    # in parameter lists.  "@<" and "@>" are used in place of
+    # non-parameter list parens.
+    gsub(/ _ +/, " _ ", line)
+    while(1) {
+	if(isfunc && match(line, /\([^()]*\)$/))
+	    line = substr(line, 1, RSTART-1) " _ (" substr(line, RSTART) ")"
+	else if(match(line, / _ \(\([^,()]*,/))
+	    line = substr(line, 1, RSTART+RLENGTH-2) "@!" substr(line, RSTART+RLENGTH)
+	else if(match(line, / _ \(\([^,()]*\)\)/))
+	    line = substr(line, 1, RSTART-1) "@{" substr(line, RSTART+5, RLENGTH-7) "@}" substr(line, RSTART+RLENGTH)
+	else if(match(line, /\([^,()]*\)/))
+	    line = substr(line, 1, RSTART-1) "@<" substr(line, RSTART+1, RLENGTH-2) "@>" substr(line, RSTART+RLENGTH)
+	else
+	    break
+    }
+    sub(/^ */, "", line)
+    match(line, /^((const|enum|static|struct|union) +)*([_0-9A-Za-z]+ +|((char|double|float|int|long|short|unsigned|void) +)+)((const|static) +)*/)
+    dtype = substr(line, 1, RLENGTH)
+    sub(/ *$/, "", dtype)
+    islocal = " " dtype " " ~ / static /
+    line = substr(line, RLENGTH+1) ","
+    # Handle each declarator.
+    output = ""
+    while(match(line, /^[^,]*,/)) {
+	# Separate out the name from the declarator.  Use "@+" and "@-"
+	# to bracket the name within the declarator.  Strip off any
+	# initialiser.
+	dcltor = substr(line, 1, RLENGTH-1)
+	line = substr(line, RLENGTH+1)
+	sub(/\=.*$/, "", dcltor)
+	match(dcltor, /^([^_0-9A-Za-z]| const )*/)
+	dcltor = substr(dcltor, 1, RLENGTH) "@+" substr(dcltor, RLENGTH+1)
+	match(dcltor, /^.*@\+[_0-9A-Za-z]+/)
+	dcltor = substr(dcltor, 1, RLENGTH) "@-" substr(dcltor, RLENGTH+1)
+	dnam = dcltor
+	sub(/^.*@\+/, "", dnam)
+	sub(/@-.*$/, "", dnam)
+
+	# Put parens etc. back
+	gsub(/@[{]/, " _((", dcltor)
+	gsub(/@}/, "))", dcltor)
+	gsub(/@</, "(", dcltor)
+	gsub(/@>/, ")", dcltor)
+	gsub(/@!/, ",", dcltor)
+
+	# If this is a module boot/cleanup function, conditionally rename it.
+	if(" " dtype " " ~ / int / && dcltor ~ / *@\+(boot|cleanup)_[_0-9A-Za-z]+@- *_\(\( *Module +[_0-9A-Za-z]+ *\)\) */) {
+	    modtype = dnam
+	    sub(/_.*$/, "", modtype)
+	    output = output "# if defined(DYNAMIC_NAME_CLASH_OK) && defined(MODULE)\n"
+	    output = output "#  define " dnam " " modtype "_\n"
+	    output = output "# endif\n"
+	}
+
+	# Format the declaration for output
+	dcl = dtype " " dcltor ";"
+	if(!islocal)
+	    dcl = "extern " dcl
+	gsub(/@[+-]/, "", dcl)
+	gsub(/ +/, " ", dcl)
+	while(match(dcl, /[^_0-9A-Za-z] ./) || match(dcl, /. [^_0-9A-Za-z]/))
+	    dcl = substr(dcl, 1, RSTART) substr(dcl, RSTART+2)
+	output = output dcl "\n"
+    }
+
+    # Output global declarations now, but save up locals until the end.
+    if(islocal)
+	locals = locals output
+    else
+	printf "%s", output
+}
+
+END {
+    if(aborting)
+	exit 1
+    printf "\n"
+    printf "#endif /* !have_%s_globals */\n", name
+    if(locals != "") {
+	printf "\n"
+	printf "#ifndef GLOBAL_PROTOTYPES\n"
+	printf "\n"
+	printf locals
+	printf "\n"
+	printf "#endif /* !GLOBAL_PROTOTYPES */\n"
+    }
+}
diff --git a/Src/math.c b/Src/math.c
new file mode 100644
index 000000000..7a0a1f9bd
--- /dev/null
+++ b/Src/math.c
@@ -0,0 +1,883 @@
+/*
+ * math.c - mathematical expression evaluation
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "math.pro"
+
+/* nonzero means we are not evaluating, just parsing */
+ 
+/**/
+int noeval;
+ 
+/* last input base we used */
+
+/**/
+int lastbase;
+ 
+static char *ptr;
+
+static long yyval;
+static LV yylval;
+
+static int mlevel = 0;
+
+/* != 0 means recognize unary plus, minus, etc. */
+
+static int unary = 1;
+
+/* LR = left-to-right associativity *
+ * RL = right-to-left associativity *
+ * BOO = short-circuiting boolean   */
+
+#define LR 0
+#define RL 1
+#define BOOL 2
+
+#define M_INPAR 0
+#define M_OUTPAR 1
+#define NOT 2
+#define COMP 3
+#define POSTPLUS 4
+#define POSTMINUS 5
+#define UPLUS 6
+#define UMINUS 7
+#define AND 8
+#define XOR 9
+#define OR 10
+#define MUL 11
+#define DIV 12
+#define MOD 13
+#define PLUS 14
+#define MINUS 15
+#define SHLEFT 16
+#define SHRIGHT 17
+#define LES 18
+#define LEQ 19
+#define GRE 20
+#define GEQ 21
+#define DEQ 22
+#define NEQ 23
+#define DAND 24
+#define DOR 25
+#define DXOR 26
+#define QUEST 27
+#define COLON 28
+#define EQ 29
+#define PLUSEQ 30
+#define MINUSEQ 31
+#define MULEQ 32
+#define DIVEQ 33
+#define MODEQ 34
+#define ANDEQ 35
+#define XOREQ 36
+#define OREQ 37
+#define SHLEFTEQ 38
+#define SHRIGHTEQ 39
+#define DANDEQ 40
+#define DOREQ 41
+#define DXOREQ 42
+#define COMMA 43
+#define EOI 44
+#define PREPLUS 45
+#define PREMINUS 46
+#define NUM 47
+#define ID 48
+#define POWER 49
+#define CID 50
+#define POWEREQ 51
+#define TOKCOUNT 52
+
+/* precedences */
+
+static int prec[TOKCOUNT] =
+{
+     1, 137,  2,  2,   2,
+     2,   2,  2,  4,   5,
+     6,   8,  8,  8,   9,
+     9,   3,  3, 10,  10,
+    10,  10, 11, 11,  12,
+    13,  13, 14, 14,  15,
+    15,  15, 15, 15,  15,
+    15,  15, 15, 15,  15,
+    15,  15, 15, 16, 200,
+     2,   2,  0,  0,   7,
+     0,  15
+};
+
+#define TOPPREC 16
+#define ARGPREC (TOPPREC-1)
+
+static int type[TOKCOUNT] =
+{
+    LR, LR, RL, RL, RL,
+    RL, RL, RL, LR, LR,
+    LR, LR, LR, LR, LR,
+    LR, LR, LR, LR, LR,
+    LR, LR, LR, LR, BOOL,
+    BOOL, LR, RL, RL, RL,
+    RL, RL, RL, RL, RL,
+    RL, RL, RL, RL, RL,
+    BOOL, BOOL, RL, RL, RL,
+    RL, RL, LR, LR, RL,
+    LR, RL
+};
+
+#define LVCOUNT 32
+
+/* list of lvalues (variables) */
+
+static int lvc;
+static char **lvals;
+
+
+/**/
+static int
+zzlex(void)
+{
+    int cct = 0;
+
+    for (;; cct = 0)
+	switch (*ptr++) {
+	case '+':
+	    if (*ptr == '+' && (unary || !ialnum(*ptr))) {
+		ptr++;
+		return (unary) ? PREPLUS : POSTPLUS;
+	    }
+	    if (*ptr == '=') {
+		unary = 1;
+		ptr++;
+		return PLUSEQ;
+	    }
+	    return (unary) ? UPLUS : PLUS;
+	case '-':
+	    if (*ptr == '-' && (unary || !ialnum(*ptr))) {
+		ptr++;
+		return (unary) ? PREMINUS : POSTMINUS;
+	    }
+	    if (*ptr == '=') {
+		unary = 1;
+		ptr++;
+		return MINUSEQ;
+	    }
+	    return (unary) ? UMINUS : MINUS;
+	case '(':
+	    unary = 1;
+	    return M_INPAR;
+	case ')':
+	    return M_OUTPAR;
+	case '!':
+	    if (*ptr == '=') {
+		unary = 1;
+		ptr++;
+		return NEQ;
+	    }
+	    return NOT;
+	case '~':
+	    return COMP;
+	case '&':
+	    unary = 1;
+	    if (*ptr == '&') {
+		if (*++ptr == '=') {
+		    ptr++;
+		    return DANDEQ;
+		}
+		return DAND;
+	    } else if (*ptr == '=') {
+		ptr++;
+		return ANDEQ;
+	    }
+	    return AND;
+	case '|':
+	    unary = 1;
+	    if (*ptr == '|') {
+		if (*++ptr == '=') {
+		    ptr++;
+		    return DOREQ;
+		}
+		return DOR;
+	    } else if (*ptr == '=') {
+		ptr++;
+		return OREQ;
+	    }
+	    return OR;
+	case '^':
+	    unary = 1;
+	    if (*ptr == '^') {
+		if (*++ptr == '=') {
+		    ptr++;
+		    return DXOREQ;
+		}
+		return DXOR;
+	    } else if (*ptr == '=') {
+		ptr++;
+		return XOREQ;
+	    }
+	    return XOR;
+	case '*':
+	    unary = 1;
+	    if (*ptr == '*') {
+		if (*++ptr == '=') {
+		    ptr++;
+		    return POWEREQ;
+		}
+		return POWER;
+	    }
+	    if (*ptr == '=') {
+		ptr++;
+		return MULEQ;
+	    }
+	    return MUL;
+	case '/':
+	    unary = 1;
+	    if (*ptr == '=') {
+		ptr++;
+		return DIVEQ;
+	    }
+	    return DIV;
+	case '%':
+	    unary = 1;
+	    if (*ptr == '=') {
+		ptr++;
+		return MODEQ;
+	    }
+	    return MOD;
+	case '<':
+	    unary = 1;
+	    if (*ptr == '<') {
+		if (*++ptr == '=') {
+		    ptr++;
+		    return SHLEFTEQ;
+		}
+		return SHLEFT;
+	    } else if (*ptr == '=') {
+		ptr++;
+		return LEQ;
+	    }
+	    return LES;
+	case '>':
+	    unary = 1;
+	    if (*ptr == '>') {
+		if (*++ptr == '=') {
+		    ptr++;
+		    return SHRIGHTEQ;
+		}
+		return SHRIGHT;
+	    } else if (*ptr == '=') {
+		ptr++;
+		return GEQ;
+	    }
+	    return GRE;
+	case '=':
+	    unary = 1;
+	    if (*ptr == '=') {
+		ptr++;
+		return DEQ;
+	    }
+	    return EQ;
+	case '$':
+	    unary = 0;
+	    yyval = mypid;
+	    return NUM;
+	case '?':
+	    if (unary) {
+		yyval = lastval;
+		unary = 0;
+		return NUM;
+	    }
+	    unary = 1;
+	    return QUEST;
+	case ':':
+	    unary = 1;
+	    return COLON;
+	case ',':
+	    unary = 1;
+	    return COMMA;
+	case '\0':
+	    unary = 1;
+	    ptr--;
+	    return EOI;
+	case '[':
+	    unary = 0;
+	    {
+		int base = zstrtol(ptr, &ptr, 10);
+
+		if (*ptr == ']')
+		    ptr++;
+		yyval = zstrtol(ptr, &ptr, lastbase = base);
+		return NUM;
+	    }
+	case ' ':
+	case '\t':
+	case '\n':
+	    break;
+	case '0':
+	    if (*ptr == 'x' || *ptr == 'X') {
+		unary = 0;
+		/* Should we set lastbase here? */
+		yyval = zstrtol(++ptr, &ptr, lastbase = 16);
+		return NUM;
+	    }
+	/* Fall through! */
+	default:
+	    if (idigit(*--ptr)) {
+		unary = 0;
+		yyval = zstrtol(ptr, &ptr, 10);
+
+		if (*ptr == '#') {
+		    ptr++;
+		    yyval = zstrtol(ptr, &ptr, lastbase = yyval);
+		}
+		return NUM;
+	    }
+	    if (*ptr == '#') {
+		if (*++ptr == '\\') {
+		    ptr++;
+		    yyval = *ptr == Meta ? *++ptr ^ 32 : *ptr;
+		    ptr++;
+		    unary = 0;
+		    return NUM;
+		}
+		cct = 1;
+	    }
+	    if (iident(*ptr)) {
+		char *p, q;
+
+		p = ptr;
+		if (lvc == LVCOUNT) {
+		    zerr("too many identifiers (complain to author)", NULL, 0);
+		    return EOI;
+		}
+		unary = 0;
+		while (iident(*++ptr));
+		if (*ptr == '[') {
+		    int l;
+		    for (ptr++, l = 1; *ptr && l; ptr++) {
+			if (*ptr == '[')
+			    l++;
+			if (*ptr == ']')
+			    l--;
+			if (*ptr == '\\' && ptr[1])
+			    ptr++;
+		    }
+		}
+		q = *ptr;
+		*ptr = '\0';
+		lvals[yylval = lvc++] = ztrdup(p);
+		*ptr = q;
+		return cct ? CID : ID;
+	    }
+	    else if (cct) {
+		yyval = poundgetfn(NULL);
+		unary = 0;
+		return NUM;
+	    }
+	    return EOI;
+	}
+}
+
+/* the value stack */
+
+#define STACKSZ 100
+static int mtok;			/* last token */
+static int sp = -1;			/* stack pointer */
+
+struct mathvalue {
+    LV lval;
+    long val;
+};
+
+static struct mathvalue *stack;
+
+/**/
+static void
+push(long val, LV lval)
+{
+    if (sp == STACKSZ - 1)
+	zerr("stack overflow", NULL, 0);
+    else
+	sp++;
+    stack[sp].val = val;
+    stack[sp].lval = lval;
+}
+
+
+/**/
+static long
+getcvar(LV s)
+{
+    char *t;
+
+    if (!(t = getsparam(lvals[s])))
+	return 0;
+    return STOUC(*t == Meta ? t[1] ^ 32 : *t);
+}
+
+
+/**/
+static long
+setvar(LV s, long v)
+{
+    if (s == -1 || s >= lvc) {
+	zerr("lvalue required", NULL, 0);
+	return 0;
+    }
+    if (noeval)
+	return v;
+    setiparam(lvals[s], v);
+    return v;
+}
+
+
+/**/
+static int
+notzero(long a)
+{
+    if (a == 0) {
+	zerr("division by zero", NULL, 0);
+	return 0;
+    }
+    return 1;
+}
+
+/* macro to pop two values off the value stack */
+#define pop2() { \
+	if (sp < 1) { \
+ 	    zerr("bad math expression: unbalanced stack", NULL, 0); \
+	    return; \
+	} \
+	b = stack[sp--].val; \
+	a = stack[sp--].val; \
+    }
+
+/* macro to pop three values off the value stack */
+#define pop3() { \
+	if (sp < 2) { \
+ 	    zerr("bad math expression: unbalanced stack", NULL, 0); \
+	    return; \
+	} \
+	c = stack[sp--].val; \
+	b = stack[sp--].val; \
+	a = stack[sp--].val; \
+    }
+
+#define nolval() {stack[sp].lval= -1;}
+#define pushv(X) { push(X,-1); }
+#define pop2lv() { pop2() lv = stack[sp+1].lval; }
+#define set(X) { push(setvar(lv,X),lv); }
+
+
+/**/
+void
+op(int what)
+{
+    long a, b, c;
+    LV lv;
+
+    if (sp < 0) {
+	zerr("bad math expression: stack empty", NULL, 0);
+	return;
+    }
+    switch (what) {
+    case NOT:
+	stack[sp].val = !stack[sp].val;
+	nolval();
+	break;
+    case COMP:
+	stack[sp].val = ~stack[sp].val;
+	nolval();
+	break;
+    case POSTPLUS:
+	(void)setvar(stack[sp].lval, stack[sp].val + 1);
+	break;
+    case POSTMINUS:
+	(void)setvar(stack[sp].lval, stack[sp].val - 1);
+	break;
+    case UPLUS:
+	nolval();
+	break;
+    case UMINUS:
+	stack[sp].val = -stack[sp].val;
+	nolval();
+	break;
+    case AND:
+	pop2();
+	pushv(a & b);
+	break;
+    case XOR:
+	pop2();
+	pushv(a ^ b);
+	break;
+    case OR:
+	pop2();
+	pushv(a | b);
+	break;
+    case MUL:
+	pop2();
+	pushv(a * b);
+	break;
+    case DIV:
+	pop2();
+	if (notzero(b))
+	    pushv(a / b);
+	break;
+    case MOD:
+	pop2();
+	if (notzero(b))
+	    pushv(a % b);
+	break;
+    case PLUS:
+	pop2();
+	pushv(a + b);
+	break;
+    case MINUS:
+	pop2();
+	pushv(a - b);
+	break;
+    case SHLEFT:
+	pop2();
+	pushv(a << b);
+	break;
+    case SHRIGHT:
+	pop2();
+	pushv(a >> b);
+	break;
+    case LES:
+	pop2();
+	pushv((long)(a < b));
+	break;
+    case LEQ:
+	pop2();
+	pushv((long)(a <= b));
+	break;
+    case GRE:
+	pop2();
+	pushv((long)(a > b));
+	break;
+    case GEQ:
+	pop2();
+	pushv((long)(a >= b));
+	break;
+    case DEQ:
+	pop2();
+	pushv((long)(a == b));
+	break;
+    case NEQ:
+	pop2();
+	pushv((long)(a != b));
+	break;
+    case DAND:
+	pop2();
+	pushv((long)(a && b));
+	break;
+    case DOR:
+	pop2();
+	pushv((long)(a || b));
+	break;
+    case DXOR:
+	pop2();
+	pushv((long)((a && !b) || (!a && b)));
+	break;
+    case QUEST:
+	pop3();
+	pushv((a) ? b : c);
+	break;
+    case COLON:
+	break;
+    case EQ:
+	pop2();
+	lv = stack[sp + 1].lval;
+	set(b);
+	break;
+    case PLUSEQ:
+	pop2lv();
+	set(a + b);
+	break;
+    case MINUSEQ:
+	pop2lv();
+	set(a - b);
+	break;
+    case MULEQ:
+	pop2lv();
+	set(a * b);
+	break;
+    case DIVEQ:
+	pop2lv();
+	if (notzero(b))
+	    set(a / b);
+	break;
+    case MODEQ:
+	pop2lv();
+	if (notzero(b))
+	    set(a % b);
+	break;
+    case ANDEQ:
+	pop2lv();
+	set(a & b);
+	break;
+    case XOREQ:
+	pop2lv();
+	set(a ^ b);
+	break;
+    case OREQ:
+	pop2lv();
+	set(a | b);
+	break;
+    case SHLEFTEQ:
+	pop2lv();
+	set(a << b);
+	break;
+    case SHRIGHTEQ:
+	pop2lv();
+	set(a >> b);
+	break;
+    case DANDEQ:
+	pop2lv();
+	set((long)(a && b));
+	break;
+    case DOREQ:
+	pop2lv();
+	set((long)(a || b));
+	break;
+    case DXOREQ:
+	pop2lv();
+	set((long)((a && !b) || (!a && b)));
+	break;
+    case COMMA:
+	pop2();
+	pushv(b);
+	break;
+    case PREPLUS:
+	stack[sp].val = setvar(stack[sp].lval,
+			       stack[sp].val + 1);
+	break;
+    case PREMINUS:
+	stack[sp].val = setvar(stack[sp].lval,
+			       stack[sp].val - 1);
+	break;
+    case POWER:
+	pop2();
+	if (b < 0) {
+	    zerr("can't handle negative exponents", NULL, 0);
+	    return;
+	}
+	for (c = 1; b--; c *= a);
+	pushv(c);
+	break;
+    case POWEREQ:
+	pop2lv();
+	if (b < 0) {
+	    zerr("can't handle negative exponents", NULL, 0);
+	    return;
+	}
+	for (c = 1; b--; c *= a);
+	set(c);
+	break;
+    default:
+	zerr("out of integers", NULL, 0);
+	return;
+    }
+}
+
+
+/**/
+static void
+bop(int tk)
+{
+    switch (tk) {
+    case DAND:
+    case DANDEQ:
+	if (!stack[sp].val)
+	    noeval++;
+	break;
+    case DOR:
+    case DOREQ:
+	if (stack[sp].val)
+	    noeval++;
+	break;
+    };
+}
+
+
+/**/
+static long
+mathevall(char *s, int prek, char **ep)
+{
+    int t0;
+    int xlastbase, xnoeval, xunary, xlvc;
+    char *xptr;
+    long xyyval;
+    LV xyylval;
+    char **xlvals = 0;
+    int xsp;
+    struct mathvalue *xstack = 0;
+    long ret;
+
+    xlastbase = xnoeval = xunary = xlvc = xyyval = xyylval = xsp = 0;
+    xptr = NULL;
+    if (mlevel++) {
+	xlastbase = lastbase;
+	xnoeval = noeval;
+	xunary = unary;
+	xlvc = lvc;
+	xptr = ptr;
+	xyyval = yyval;
+	xyylval = yylval;
+	xlvals = lvals;
+
+	xsp = sp;
+	xstack = stack;
+    }
+    stack = (struct mathvalue *)zalloc(STACKSZ*sizeof(struct mathvalue));
+    lastbase = -1;
+    lvals = (char **)zcalloc(LVCOUNT*sizeof(char *));
+    lvc = 0;
+    ptr = s;
+    sp = -1;
+    unary = 1;
+    mathparse(prek);
+    *ep = ptr;
+    if (sp)
+	zerr("bad math expression: unbalanced stack", NULL, 0);
+    for (t0 = 0; t0 != lvc; t0++)
+	zsfree(lvals[t0]);
+
+    ret = stack[0].val;
+
+    zfree(lvals, LVCOUNT*sizeof(char *));
+    zfree(stack, STACKSZ*sizeof(struct mathvalue));
+    if (--mlevel) {
+	lastbase = xlastbase;
+	noeval = xnoeval;
+	unary = xunary;
+	lvc = xlvc;
+	ptr = xptr;
+	yyval = xyyval;
+	yylval = xyylval;
+	lvals = xlvals;
+
+	sp = xsp;
+	stack = xstack;
+    }
+    return ret;
+}
+
+
+/**/
+long
+matheval(char *s)
+{
+    char *junk;
+    long x;
+    int xmtok = mtok;
+
+    if (!*s)
+	return 0;
+    x = mathevall(s, TOPPREC, &junk);
+    mtok = xmtok;
+    if (*junk)
+	zerr("bad math expression: illegal character: %c", NULL, *junk);
+    return x;
+}
+
+
+/**/
+long
+mathevalarg(char *s, char **ss)
+{
+    long x;
+    int xmtok = mtok;
+
+    x = mathevall(s, ARGPREC, ss);
+    if (mtok == COMMA)
+	(*ss)--;
+    mtok = xmtok;
+    return x;
+}
+
+
+/* operator-precedence parse the string and execute */
+
+/**/
+static void
+mathparse(int pc)
+{
+    int q, otok, onoeval;
+
+    if (errflag)
+	return;
+    mtok = zzlex();
+    while (prec[mtok] <= pc) {
+	if (errflag)
+	    return;
+	switch (mtok) {
+	case NUM:
+	    push(yyval, -1);
+	    break;
+	case ID:
+	    push(getiparam(lvals[yylval]), yylval);
+	    break;
+	case CID:
+	    push(getcvar(yylval), yylval);
+	    break;
+	case M_INPAR:
+	    mathparse(TOPPREC);
+	    if (mtok != M_OUTPAR) {
+		if (!errflag)
+		    zerr("')' expected", NULL, 0);
+		return;
+	    }
+	    break;
+	case QUEST:
+	    q = stack[sp].val;
+
+	    if (!q)
+		noeval++;
+	    mathparse(prec[QUEST] - 1);
+	    if (!q)
+		noeval--;
+	    else
+		noeval++;
+	    mathparse(prec[QUEST]);
+	    if (q)
+		noeval--;
+	    op(QUEST);
+	    continue;
+	default:
+	    otok = mtok;
+	    onoeval = noeval;
+	    if (type[otok] == BOOL)
+		bop(otok);
+	    mathparse(prec[otok] - (type[otok] != RL));
+	    noeval = onoeval;
+	    op(otok);
+	    continue;
+	}
+	mtok = zzlex();
+    }
+}
diff --git a/Src/mem.c b/Src/mem.c
new file mode 100644
index 000000000..1145f8c5e
--- /dev/null
+++ b/Src/mem.c
@@ -0,0 +1,1254 @@
+/*
+ * mem.c - memory management
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "mem.pro"
+
+/*
+	There are two ways to allocate memory in zsh.  The first way is
+	to call zalloc/zcalloc, which call malloc/calloc directly.  It
+	is legal to call realloc() or free() on memory allocated this way.
+	The second way is to call halloc/hcalloc, which allocates memory
+	from one of the memory pools on the heap stack.  Such memory pools 
+	will automatically created when the heap allocation routines are
+	called.  To be sure that they are freed at appropriate times
+	one should call pushheap() before one starts using heaps and
+	popheap() after that (when the memory allocated on the heaps since
+	the last pushheap() isn't needed anymore).
+	pushheap() saves the states of all currently allocated heaps and
+	popheap() resets them to the last state saved and destroys the
+	information about that state.  If you called pushheap() and
+	allocated some memory on the heaps and then come to a place where
+	you don't need the allocated memory anymore but you still want
+	to allocate memory on the heap, you should call freeheap().  This
+	works like popheap(), only that it doesn't free the information
+	about the heap states (i.e. the heaps are like after the call to
+	pushheap() and you have to call popheap some time later).
+
+	Memory allocated in this way does not have to be freed explicitly;
+	it will all be freed when the pool is destroyed.  In fact,
+	attempting to free this memory may result in a core dump.
+	The pair of pointers ncalloc and alloc may point to either
+	zalloc & zcalloc or halloc & hcalloc; permalloc() sets them to the
+	former, and heapalloc() sets them to the latter. This can be useful.
+	For example, the dupstruct() routine duplicates a syntax tree,
+	allocating the new memory for the tree using alloc().  If you want
+	to duplicate a structure for a one-time use (i.e. to execute the list
+	in a for loop), call heapalloc(), then dupstruct().  If you want
+	to duplicate a structure in order to preserve it (i.e. a function
+	definition), call permalloc(), then dupstruct().
+
+	If we use zsh's own allocator we use a simple trick to avoid that
+	the (*real*) heap fills up with empty zsh-heaps: we allocate a
+	large block of memory before allocating a heap pool, this memory
+	is freed again immediately after the pool is allocated. If there
+	are only small blocks on the free list this guarantees that the
+	memory for the pool is at the end of the memory which means that
+	we can give it back to the system when the pool is freed.
+*/
+
+/* != 0 if we are allocating in the heaplist */
+ 
+/**/
+int useheap;
+
+/* Current allocation pointers.  ncalloc() is either zalloc() or halloc(); *
+ * alloc() is either zcalloc() or hcalloc().                               */
+
+/**/
+void *(*ncalloc) _((size_t)), *(*alloc) _((size_t));
+
+#ifdef ZSH_MEM_WARNING
+# ifndef DEBUG
+#  define DEBUG 1
+# endif
+#endif
+
+#if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG)
+
+static int h_m[1025], h_push, h_pop, h_free;
+
+#endif
+
+#define H_ISIZE  sizeof(long)
+#define HEAPSIZE (8192 - H_ISIZE)
+#define HEAP_ARENA_SIZE (HEAPSIZE - sizeof(struct heap))
+#define HEAPFREE (16384 - H_ISIZE)
+
+/* set default allocation to heap stack */
+
+/**/
+int
+global_heapalloc(void)
+{
+    int luh = useheap;
+
+    alloc = hcalloc;
+    ncalloc = halloc;
+    useheap = 1;
+    return luh;
+}
+
+/* set default allocation to malloc() */
+
+/**/
+int
+global_permalloc(void)
+{
+    int luh = useheap;
+
+    alloc = zcalloc;
+    ncalloc = zalloc;
+    useheap = 0;
+    return luh;
+}
+
+/* heappush saves the current heap state using this structure */
+
+struct heapstack {
+    struct heapstack *next;	/* next one in list for this heap */
+    size_t used;
+};
+
+/* A zsh heap. */
+
+struct heap {
+    struct heap *next;		/* next one                                  */
+    size_t used;		/* bytes used from the heap                  */
+    struct heapstack *sp;	/* used by pushheap() to save the value used */
+#define arena(X)	((char *) (X) + sizeof(struct heap))
+};
+
+/* list of zsh heap */
+
+static Heap heaps;
+
+/* save states of zsh heaps */
+
+/**/
+void
+pushheap(void)
+{
+    Heap h;
+    Heapstack hs;
+
+#if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG)
+    h_push++;
+#endif
+
+    for (h = heaps; h; h = h->next) {
+	DPUTS(!h->used, "BUG: empty heap");
+	hs = (Heapstack) zalloc(sizeof(*hs));
+	hs->next = h->sp;
+	h->sp = hs;
+	hs->used = h->used;
+    }
+}
+
+/* reset heaps to previous state */
+
+/**/
+void
+freeheap(void)
+{
+    Heap h, hn, hl = NULL;
+
+#if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG)
+    h_free++;
+#endif
+    for (h = heaps; h; h = hn) {
+	hn = h->next;
+	if (h->sp) {
+#ifdef ZSH_MEM_DEBUG
+	    memset(arena(h) + h->sp->used, 0xff, h->used - h->sp->used);
+#endif
+	    h->used = h->sp->used;
+	    hl = h;
+	} else
+	    zfree(h, HEAPSIZE);
+    }
+    if (hl)
+	hl->next = NULL;
+    else
+	heaps = NULL;
+}
+
+/* reset heap to previous state and destroy state information */
+
+/**/
+void
+popheap(void)
+{
+    Heap h, hn, hl = NULL;
+    Heapstack hs;
+
+#if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG)
+    h_pop++;
+#endif
+
+    for (h = heaps; h; h = hn) {
+	hn = h->next;
+	if ((hs = h->sp)) {
+	    h->sp = hs->next;
+#ifdef ZSH_MEM_DEBUG
+	    memset(arena(h) + hs->used, 0xff, h->used - hs->used);
+#endif
+	    h->used = hs->used;
+	    zfree(hs, sizeof(*hs));
+
+	    hl = h;
+	} else
+	    zfree(h, HEAPSIZE);
+    }
+    if (hl)
+	hl->next = NULL;
+    else
+	heaps = NULL;
+}
+
+/* allocate memory from the current memory pool */
+
+/**/
+void *
+halloc(size_t size)
+{
+    Heap h;
+    size_t n;
+
+    size = (size + H_ISIZE - 1) & ~(H_ISIZE - 1);
+
+#if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG)
+    h_m[size < 1024 ? (size / H_ISIZE) : 1024]++;
+#endif
+
+    /* find a heap with enough free space */
+
+    for (h = heaps; h; h = h->next) {
+	if (HEAP_ARENA_SIZE >= (n = size + h->used)) {
+	    h->used = n;
+	    return arena(h) + n - size;
+	}
+    }
+
+    {
+	Heap hp;
+        /* not found, allocate new heap */
+#ifdef ZSH_MEM
+	static int called = 0;
+	void *foo = called ? (void *)malloc(HEAPFREE) : NULL;
+            /* tricky, see above */
+#endif
+
+	queue_signals();
+	n = HEAP_ARENA_SIZE > size ? HEAPSIZE : size + sizeof(*h);
+	for (hp = NULL, h = heaps; h; hp = h, h = h->next);
+
+	h = (Heap) zalloc(n);
+
+#ifdef ZSH_MEM
+	if (called)
+	    zfree(foo, HEAPFREE);
+	called = 1;
+#endif
+
+	h->used = size;
+	h->next = NULL;
+	h->sp = NULL;
+
+	if (hp)
+	    hp->next = h;
+	else
+	    heaps = h;
+
+	unqueue_signals();
+	return arena(h);
+    }
+}
+
+/**/
+void *
+hrealloc(char *p, size_t old, size_t new)
+{
+    Heap h, ph;
+
+    old = (old + H_ISIZE - 1) & ~(H_ISIZE - 1);
+    new = (new + H_ISIZE - 1) & ~(H_ISIZE - 1);
+
+    if (old == new)
+	return p;
+    if (!old && !p)
+	return halloc(new);
+
+    /* find the heap with p */
+
+    for (h = heaps, ph = NULL; h; ph = h, h = h->next)
+	if (p >= arena(h) && p < arena(h) + HEAP_ARENA_SIZE)
+	    break;
+
+    DPUTS(!h, "BUG: hrealloc() called for non-heap memory.");
+    DPUTS(h->sp && arena(h) + h->sp->used > p,
+	  "BUG: hrealloc() wants to realloc pushed memory");
+
+    if (p + old < arena(h) + h->used) {
+	if (new > old) {
+	    char *ptr = (char *) halloc(new);
+	    memcpy(ptr, p, old);
+	    return ptr;
+	} else
+	    return new ? p : NULL;
+    }
+
+    DPUTS(p + old != arena(h) + h->used, "BUG: hrealloc more than allocated");
+
+    if (p == arena(h)) {
+	if (!new) {
+	    if (ph)
+		ph->next = h->next;
+	    else
+		heaps = h->next;
+	    zfree(h, HEAPSIZE);
+	    return NULL;
+	}
+	if (old > HEAP_ARENA_SIZE || new > HEAP_ARENA_SIZE) {
+	    size_t n = HEAP_ARENA_SIZE > new ? HEAPSIZE : new + sizeof(*h);
+
+	    if (ph)
+		ph->next = h = (Heap) realloc(h, n);
+	    else
+		heaps = h = (Heap) realloc(h, n);
+	}
+	h->used = new;
+	return arena(h);
+    }
+    DPUTS(h->used > HEAP_ARENA_SIZE, "BUG: hrealloc at invalid address");
+    if (h->used + (new - old) <= HEAP_ARENA_SIZE) {
+	h->used += new - old;
+	return p;
+    } else {
+	char *t = halloc(new);
+	memcpy(t, p, old > new ? new : old);
+	h->used -= old;
+#ifdef ZSH_MEM_DEBUG
+	memset(p, 0xff, old);
+#endif
+	return t;
+    }
+}
+
+/* allocate memory from the current memory pool and clear it */
+
+/**/
+void *
+hcalloc(size_t size)
+{
+    void *ptr;
+
+    ptr = halloc(size);
+    memset(ptr, 0, size);
+    return ptr;
+}
+
+/* allocate permanent memory */
+
+/**/
+void *
+zalloc(size_t size)
+{
+    void *ptr;
+
+    if (!size)
+	size = 1;
+    if (!(ptr = (void *) malloc(size))) {
+	zerr("fatal error: out of memory", NULL, 0);
+	exit(1);
+    }
+
+    return ptr;
+}
+
+/**/
+void *
+zcalloc(size_t size)
+{
+    void *ptr;
+
+    if (!size)
+	size = 1;
+    if (!(ptr = (void *) malloc(size))) {
+	zerr("fatal error: out of memory", NULL, 0);
+	exit(1);
+    }
+    memset(ptr, 0, size);
+
+    return ptr;
+}
+
+/* This front-end to realloc is used to make sure we have a realloc *
+ * that conforms to POSIX realloc.  Older realloc's can fail if     *
+ * passed a NULL pointer, but POSIX realloc should handle this.  A  *
+ * better solution would be for configure to check if realloc is    *
+ * POSIX compliant, but I'm not sure how to do that.                */
+
+/**/
+void *
+zrealloc(void *ptr, size_t size)
+{
+    if (ptr) {
+	if (size) {
+	    /* Do normal realloc */
+	    if (!(ptr = (void *) realloc(ptr, size))) {
+		zerr("fatal error: out of memory", NULL, 0);
+		exit(1);
+	    }
+	    return ptr;
+	}
+	else
+	    /* If ptr is not NULL, but size is zero, *
+	     * then object pointed to is freed.      */
+	    free(ptr);
+    } else {
+	/* If ptr is NULL, then behave like malloc */
+	return malloc(size);
+    }
+
+    return NULL;
+}
+
+/**/
+char *
+dupstring(const char *s)
+{
+    char *t;
+
+    if (!s)
+	return NULL;
+    t = (char *)ncalloc(strlen((char *)s) + 1);
+    strcpy(t, s);
+    return t;
+}
+
+/**/
+char *
+ztrdup(const char *s)
+{
+    char *t;
+
+    if (!s)
+	return NULL;
+    t = (char *)zalloc(strlen((char *)s) + 1);
+    strcpy(t, s);
+    return t;
+}
+
+#ifdef ZSH_MEM
+
+/*
+   Below is a simple segment oriented memory allocator for systems on
+   which it is better than the system's one. Memory is given in blocks
+   aligned to an integer multiple of sizeof(long) (4 bytes on most machines,
+   but 8 bytes on e.g. a dec alpha). Each block is preceded by a header
+   which contains the length of the data part (in bytes). In allocated
+   blocks only this field of the structure m_hdr is senseful. In free
+   blocks the second field (next) is a pointer to the next free segment
+   on the free list.
+
+   On top of this simple allocator there is a second allocator for small
+   chunks of data. It should be both faster and less space-consuming than
+   using the normal segment mechanism for such blocks.
+   For the first M_NSMALL-1 possible sizes memory is allocated in arrays
+   that can hold M_SNUM blocks. Each array is stored in one segment of the
+   main allocator. In these segments the third field of the header structure
+   (free) contains a pointer to the first free block in the array. The
+   last field (used) gives the number of already used blocks in the array.
+
+   If the macro name ZSH_MEM_DEBUG is defined, some information about the memory
+   usage is stored. This information can than be viewed by calling the
+   builtin `mem' (which is only available if ZSH_MEM_DEBUG is set).
+
+   If ZSH_MEM_WARNING is defined, error messages are printed in case of errors.
+
+   If ZSH_SECURE_FREE is defined, free() checks if the given address is really
+   one that was returned by malloc(), it ignores it if it wasn't (printing
+   an error message if ZSH_MEM_WARNING is also defined).
+*/
+#if !defined(__hpux) && !defined(DGUX) && !defined(__osf__)
+# if defined(_BSD)
+#  ifndef HAVE_BRK_PROTO
+   extern int brk _((caddr_t));
+#  endif
+#  ifndef HAVE_SBRK_PROTO
+   extern caddr_t sbrk _((int));
+#  endif
+# else
+#  ifndef HAVE_BRK_PROTO
+   extern int brk _((void *));
+#  endif
+#  ifndef HAVE_SBRK_PROTO
+   extern void *sbrk _((int));
+#  endif
+# endif
+#endif
+
+#if defined(_BSD) && !defined(STDC_HEADERS)
+# define FREE_RET_T   int
+# define FREE_ARG_T   char *
+# define FREE_DO_RET
+# define MALLOC_RET_T char *
+# define MALLOC_ARG_T size_t
+#else
+# define FREE_RET_T   void
+# define FREE_ARG_T   void *
+# define MALLOC_RET_T void *
+# define MALLOC_ARG_T size_t
+#endif
+
+/* structure for building free list in blocks holding small blocks */
+
+struct m_shdr {
+    struct m_shdr *next;	/* next one on free list */
+};
+
+struct m_hdr {
+    long len;			/* length of memory block */
+    struct m_hdr *next;		/* if free: next on free list
+				   if block of small blocks: next one with
+				                 small blocks of same size*/
+    struct m_shdr *free;	/* if block of small blocks: free list */
+    long used;			/* if block of small blocks: number of used
+				                                     blocks */
+};
+
+
+/* alignment for memory blocks */
+
+#define M_ALIGN (sizeof(long))
+
+/* length of memory header, length of first field of memory header and
+   minimal size of a block left free (if we allocate memory and take a
+   block from the free list that is larger than needed, it must have at
+   least M_MIN extra bytes to be splitted; if it has, the rest is put on
+   the free list) */
+
+#define M_HSIZE (sizeof(struct m_hdr))
+#define M_ISIZE (sizeof(long))
+#define M_MIN   (2 * M_ISIZE)
+
+/* a pointer to the last free block, a pointer to the free list (the blocks
+   on this list are kept in order - lowest address first) */
+
+static struct m_hdr *m_lfree, *m_free;
+
+/* system's pagesize */
+
+static long m_pgsz = 0;
+
+/* the highest and the lowest valid memory addresses, kept for fast validity
+   checks in free() and to find out if and when we can give memory back to
+   the system */
+
+static char *m_high, *m_low;
+
+/* Management of blocks for small blocks:
+   Such blocks are kept in lists (one list for each of the sizes that are
+   allocated in such blocks).  The lists are stored in the m_small array.
+   M_SIDX() calculates the index into this array for a given size.  M_SNUM
+   is the size (in small blocks) of such blocks.  M_SLEN() calculates the
+   size of the small blocks held in a memory block, given a pointer to the
+   header of it.  M_SBLEN() gives the size of a memory block that can hold
+   an array of small blocks, given the size of these small blocks.  M_BSLEN()
+   caculates the size of the small blocks held in a memory block, given the
+   length of that block (including the header of the memory block.  M_NSMALL
+   is the number of possible block sizes that small blocks should be used
+   for. */
+
+
+#define M_SIDX(S)  ((S) / M_ISIZE)
+#define M_SNUM     50
+#define M_SLEN(M)  ((M)->len / M_SNUM)
+#define M_SBLEN(S) ((S) * M_SNUM + sizeof(struct m_shdr *) +  \
+		    sizeof(long) + sizeof(struct m_hdr *))
+#define M_BSLEN(S) (((S) - sizeof(struct m_shdr *) -  \
+		     sizeof(long) - sizeof(struct m_hdr *)) / M_SNUM)
+#define M_NSMALL 8
+
+static struct m_hdr *m_small[M_NSMALL];
+
+#ifdef ZSH_MEM_DEBUG
+
+static int m_s = 0, m_b = 0;
+static int m_m[1025], m_f[1025];
+
+static struct m_hdr *m_l;
+
+#endif /* ZSH_MEM_DEBUG */
+
+MALLOC_RET_T
+malloc(MALLOC_ARG_T size)
+{
+    struct m_hdr *m, *mp, *mt;
+    long n, s, os = 0;
+    struct heap *h, *hp, *hf = NULL, *hfp = NULL;
+
+    /* some systems want malloc to return the highest valid address plus one
+       if it is called with an argument of zero */
+
+    if (!size)
+	return (MALLOC_RET_T) m_high;
+
+    queue_signals();  /* just queue signals rather than handling them */
+
+    /* first call, get page size */
+
+    if (!m_pgsz) {
+
+#ifdef _SC_PAGESIZE
+	m_pgsz = sysconf(_SC_PAGESIZE);     /* SVR4 */
+#else
+# ifdef _SC_PAGE_SIZE
+	m_pgsz = sysconf(_SC_PAGE_SIZE);    /* HPUX */
+# else
+	m_pgsz = getpagesize();
+# endif
+#endif
+
+	m_free = m_lfree = NULL;
+    }
+    size = (size + M_ALIGN - 1) & ~(M_ALIGN - 1);
+
+    /* Do we need a small block? */
+
+    if ((s = M_SIDX(size)) && s < M_NSMALL) {
+	/* yep, find a memory block with free small blocks of the
+	   appropriate size (if we find it in this list, this means that
+	   it has room for at least one more small block) */
+	for (mp = NULL, m = m_small[s]; m && !m->free; mp = m, m = m->next);
+
+	if (m) {
+	    /* we found one */
+	    struct m_shdr *sh = m->free;
+
+	    m->free = sh->next;
+	    m->used++;
+
+	    /* if all small blocks in this block are allocated, the block is 
+	       put at the end of the list blocks wth small blocks of this
+	       size (i.e., we try to keep blocks with free blocks at the
+	       beginning of the list, to make the search faster */
+
+	    if (m->used == M_SNUM && m->next) {
+		for (mt = m; mt->next; mt = mt->next);
+
+		mt->next = m;
+		if (mp)
+		    mp->next = m->next;
+		else
+		    m_small[s] = m->next;
+		m->next = NULL;
+	    }
+#ifdef ZSH_MEM_DEBUG
+	    m_m[size / M_ISIZE]++;
+#endif
+
+	    unqueue_signals();
+	    return (MALLOC_RET_T) sh;
+	}
+	/* we still want a small block but there were no block with a free
+	   small block of the requested size; so we use the real allocation
+	   routine to allocate a block for small blocks of this size */
+	os = size;
+	size = M_SBLEN(size);
+    } else
+	s = 0;
+
+/* search the free list for an block of at least the requested size */
+    for (mp = NULL, m = m_free; m && m->len < size; mp = m, m = m->next);
+
+ /* if there is an empty zsh heap at a lower address we steal it and take
+    the memory from it, putting the rest on the free list (remember
+    that the blocks on the free list are ordered) */
+
+    for (hp = NULL, h = heaps; h; hp = h, h = h->next)
+	if (!h->used &&
+	    (!hf || h < hf) &&
+	    (!m || ((char *)m) > ((char *)h)))
+	    hf = h, hfp = hp;
+
+    if (hf) {
+	/* we found such a heap */
+	Heapstack hso, hsn;
+
+	/* delete structures on the list holding the heap states */
+	for (hso = hf->sp; hso; hso = hsn) {
+	    hsn = hso->next;
+	    zfree(hso, sizeof(*hso));
+	}
+	/* take it from the list of heaps */
+	if (hfp)
+	    hfp->next = hf->next;
+	else
+	    heaps = hf->next;
+	/* now we simply free it and than search the free list again */
+	zfree(hf, HEAPSIZE);
+
+	for (mp = NULL, m = m_free; m && m->len < size; mp = m, m = m->next);
+    }
+    if (!m) {
+	/* no matching free block was found, we have to request new
+	   memory from the system */
+	n = (size + M_HSIZE + m_pgsz - 1) & ~(m_pgsz - 1);
+
+	if (((char *)(m = (struct m_hdr *)sbrk(n))) == ((char *)-1)) {
+	    DPUTS(1, "MEM: allocation error at sbrk.");
+	    unqueue_signals();
+	    return NULL;
+	}
+	/* set m_low, for the check in free() */
+	if (!m_low)
+	    m_low = (char *)m;
+
+#ifdef ZSH_MEM_DEBUG
+	m_s += n;
+
+	if (!m_l)
+	    m_l = m;
+#endif
+
+	/* save new highest address */
+	m_high = ((char *)m) + n;
+
+	/* initialize header */
+	m->len = n - M_ISIZE;
+	m->next = NULL;
+
+	/* put it on the free list and set m_lfree pointing to it */
+	if ((mp = m_lfree))
+	    m_lfree->next = m;
+	m_lfree = m;
+    }
+    if ((n = m->len - size) > M_MIN) {
+	/* the block we want to use has more than M_MIN bytes plus the
+	   number of bytes that were requested; we split it in two and
+	   leave the rest on the free list */
+	struct m_hdr *mtt = (struct m_hdr *)(((char *)m) + M_ISIZE + size);
+
+	mtt->len = n - M_ISIZE;
+	mtt->next = m->next;
+
+	m->len = size;
+
+	/* put the rest on the list */
+	if (m_lfree == m)
+	    m_lfree = mtt;
+
+	if (mp)
+	    mp->next = mtt;
+	else
+	    m_free = mtt;
+    } else if (mp) {
+	/* the block we found wasn't the first one on the free list */
+	if (m == m_lfree)
+	    m_lfree = mp;
+	mp->next = m->next;
+    } else {
+	/* it was the first one */
+	m_free = m->next;
+	if (m == m_lfree)
+	    m_lfree = m_free;
+    }
+
+    if (s) {
+	/* we are allocating a block that should hold small blocks */
+	struct m_shdr *sh, *shn;
+
+	/* build the free list in this block and set `used' filed */
+	m->free = sh = (struct m_shdr *)(((char *)m) +
+					 sizeof(struct m_hdr) + os);
+
+	for (n = M_SNUM - 2; n--; sh = shn)
+	    shn = sh->next = sh + s;
+	sh->next = NULL;
+
+	m->used = 1;
+
+	/* put the block on the list of blocks holding small blocks if
+	   this size */
+	m->next = m_small[s];
+	m_small[s] = m;
+
+#ifdef ZSH_MEM_DEBUG
+	m_m[os / M_ISIZE]++;
+#endif
+
+	unqueue_signals();
+	return (MALLOC_RET_T) (((char *)m) + sizeof(struct m_hdr));
+    }
+#ifdef ZSH_MEM_DEBUG
+    m_m[m->len < (1024 * M_ISIZE) ? (m->len / M_ISIZE) : 1024]++;
+#endif
+
+    unqueue_signals();
+    return (MALLOC_RET_T) & m->next;
+}
+
+/* this is an internal free(); the second argument may, but need not hold
+   the size of the block the first argument is pointing to; if it is the
+   right size of this block, freeing it will be faster, though; the value
+   0 for this parameter means: `don't know' */
+
+/**/
+void
+zfree(void *p, int sz)
+{
+    struct m_hdr *m = (struct m_hdr *)(((char *)p) - M_ISIZE), *mp, *mt = NULL;
+    int i;
+# ifdef DEBUG
+    int osz = sz;
+# endif
+
+#ifdef ZSH_SECURE_FREE
+    sz = 0;
+#else
+    sz = (sz + M_ALIGN - 1) & ~(M_ALIGN - 1);
+#endif
+
+    if (!p)
+	return;
+
+    /* first a simple check if the given address is valid */
+    if (((char *)p) < m_low || ((char *)p) > m_high ||
+	((long)p) & (M_ALIGN - 1)) {
+	DPUTS(1, "BUG: attempt to free storage at invalid address");
+	return;
+    }
+
+    queue_signals();
+
+  fr_rec:
+
+    if ((i = sz / M_ISIZE) < M_NSMALL || !sz)
+	/* if the given sizes says that it is a small block, find the
+	   memory block holding it; we search all blocks with blocks
+	   of at least the given size; if the size parameter is zero,
+	   this means, that all blocks are searched */
+	for (; i < M_NSMALL; i++) {
+	    for (mp = NULL, mt = m_small[i];
+		 mt && (((char *)mt) > ((char *)p) ||
+			(((char *)mt) + mt->len) < ((char *)p));
+		 mp = mt, mt = mt->next);
+
+	    if (mt) {
+		/* we found the block holding the small block */
+		struct m_shdr *sh = (struct m_shdr *)p;
+
+#ifdef ZSH_SECURE_FREE
+		struct m_shdr *sh2;
+
+		/* check if the given address is equal to the address of
+		   the first small block plus an integer multiple of the
+		   block size */
+		if ((((char *)p) - (((char *)mt) + sizeof(struct m_hdr))) %
+		    M_BSLEN(mt->len)) {
+
+		    DPUTS(1, "BUG: attempt to free storage at invalid address");
+		    unqueue_signals();
+		    return;
+		}
+		/* check, if the address is on the (block-intern) free list */
+		for (sh2 = mt->free; sh2; sh2 = sh2->next)
+		    if (((char *)p) == ((char *)sh2)) {
+
+			DPUTS(1, "BUG: attempt to free already free storage");
+			unqueue_signals();
+			return;
+		    }
+#endif
+		DPUTS(M_BSLEN(mt->len) < osz,
+		      "BUG: attempt to free more than allocated.");
+
+#ifdef ZSH_MEM_DEBUG
+		m_f[M_BSLEN(mt->len) / M_ISIZE]++;
+		memset(sh, 0xff, M_BSLEN(mt->len));
+#endif
+
+		/* put the block onto the free list */
+		sh->next = mt->free;
+		mt->free = sh;
+
+		if (--mt->used) {
+		    /* if there are still used blocks in this block, we
+		       put it at the beginning of the list with blocks
+		       holding small blocks of the same size (since we
+		       know that there is at least one free block in it,
+		       this will make allocation of small blocks faster;
+		       it also guarantees that long living memory blocks
+		       are preferred over younger ones */
+		    if (mp) {
+			mp->next = mt->next;
+			mt->next = m_small[i];
+			m_small[i] = mt;
+		    }
+		    unqueue_signals();
+		    return;
+		}
+		/* if there are no more used small blocks in this
+		   block, we free the whole block */
+		if (mp)
+		    mp->next = mt->next;
+		else
+		    m_small[i] = mt->next;
+
+		m = mt;
+		p = (void *) & m->next;
+
+		break;
+	    } else if (sz) {
+		/* if we didn't find a block and a size was given, try it
+		   again as if no size were given */
+		sz = 0;
+		goto fr_rec;
+	    }
+	}
+#ifdef ZSH_MEM_DEBUG
+    if (!mt)
+	m_f[m->len < (1024 * M_ISIZE) ? (m->len / M_ISIZE) : 1024]++;
+#endif
+
+#ifdef ZSH_SECURE_FREE
+    /* search all memory blocks, if one of them is at the given address */
+    for (mt = (struct m_hdr *)m_low;
+	 ((char *)mt) < m_high;
+	 mt = (struct m_hdr *)(((char *)mt) + M_ISIZE + mt->len))
+	if (((char *)p) == ((char *)&mt->next))
+	    break;
+
+    /* no block was found at the given address */
+    if (((char *)mt) >= m_high) {
+	DPUTS(1, "BUG: attempt to free storage at invalid address");
+	unqueue_signals();
+	return;
+    }
+#endif
+
+    /* see if the block is on the free list */
+    for (mp = NULL, mt = m_free; mt && mt < m; mp = mt, mt = mt->next);
+
+    if (m == mt) {
+	/* it is, ouch! */
+	DPUTS(1, "BUG: attempt to free already free storage");
+	unqueue_signals();
+	return;
+    }
+    DPUTS(m->len < osz, "BUG: attempt to free more than allocated");
+#ifdef ZSH_MEM_DEBUG
+    memset(p, 0xff, m->len);
+#endif
+    if (mt && ((char *)mt) == (((char *)m) + M_ISIZE + m->len)) {
+	/* the block after the one we are freeing is free, we put them
+	   together */
+	m->len += mt->len + M_ISIZE;
+	m->next = mt->next;
+
+	if (mt == m_lfree)
+	    m_lfree = m;
+    } else
+	m->next = mt;
+
+    if (mp && ((char *)m) == (((char *)mp) + M_ISIZE + mp->len)) {
+	/* the block before the one we are freeing is free, we put them
+	   together */
+	mp->len += m->len + M_ISIZE;
+	mp->next = m->next;
+
+	if (m == m_lfree)
+	    m_lfree = mp;
+    } else if (mp)
+	/* otherwise, we just put it on the free list */
+	mp->next = m;
+    else {
+	m_free = m;
+	if (!m_lfree)
+	    m_lfree = m_free;
+    }
+
+    /* if the block we have just freed was at the end of the process heap
+       and now there is more than one page size of memory, we can give
+       it back to the system (and we do it ;-) */
+    if ((((char *)m_lfree) + M_ISIZE + m_lfree->len) == m_high &&
+	m_lfree->len >= m_pgsz + M_MIN) {
+	long n = (m_lfree->len - M_MIN) & ~(m_pgsz - 1);
+
+	m_lfree->len -= n;
+	if (brk(m_high -= n) == -1)
+	    DPUTS(1, "MEM: allocation error at brk.");
+
+#ifdef ZSH_MEM_DEBUG
+	m_b += n;
+#endif
+    }
+    unqueue_signals();
+}
+
+FREE_RET_T
+free(FREE_ARG_T p)
+{
+    zfree(p, 0);		/* 0 means: size is unknown */
+
+#ifdef FREE_DO_RET
+    return 0;
+#endif
+}
+
+/* this one is for strings (and only strings, real strings, real C strings,
+   those that have a zero byte at the end) */
+
+/**/
+void
+zsfree(char *p)
+{
+    if (p)
+	zfree(p, strlen(p) + 1);
+}
+
+MALLOC_RET_T
+realloc(MALLOC_RET_T p, MALLOC_ARG_T size)
+{
+    struct m_hdr *m = (struct m_hdr *)(((char *)p) - M_ISIZE), *mp, *mt;
+    char *r;
+    int i, l = 0;
+
+    /* some system..., see above */
+    if (!p && size)
+	return (MALLOC_RET_T) malloc(size);
+    /* and some systems even do this... */
+    if (!p || !size)
+	return (MALLOC_RET_T) p;
+
+    queue_signals();  /* just queue signals caught rather than handling them */
+
+    /* check if we are reallocating a small block, if we do, we have
+       to compute the size of the block from the sort of block it is in */
+    for (i = 0; i < M_NSMALL; i++) {
+	for (mp = NULL, mt = m_small[i];
+	     mt && (((char *)mt) > ((char *)p) ||
+		    (((char *)mt) + mt->len) < ((char *)p));
+	     mp = mt, mt = mt->next);
+
+	if (mt) {
+	    l = M_BSLEN(mt->len);
+	    break;
+	}
+    }
+    if (!l)
+	/* otherwise the size of the block is in the memory just before
+	   the given address */
+	l = m->len;
+
+    /* now allocate the new block, copy the old contents, and free the
+       old block */
+    r = malloc(size);
+    memcpy(r, (char *)p, (size > l) ? l : size);
+    free(p);
+
+    unqueue_signals();
+    return (MALLOC_RET_T) r;
+}
+
+MALLOC_RET_T
+calloc(MALLOC_ARG_T n, MALLOC_ARG_T size)
+{
+    long l;
+    char *r;
+
+    if (!(l = n * size))
+	return (MALLOC_RET_T) m_high;
+
+    r = malloc(l);
+
+    memset(r, 0, l);
+
+    return (MALLOC_RET_T) r;
+}
+
+#ifdef ZSH_MEM_DEBUG
+
+/**/
+int
+bin_mem(char *name, char **argv, char *ops, int func)
+{
+    int i, ii, fi, ui, j;
+    struct m_hdr *m, *mf, *ms;
+    char *b, *c, buf[40];
+    long u = 0, f = 0;
+
+    if (ops['v']) {
+	printf("The lower and the upper addresses of the heap. Diff gives\n");
+	printf("the difference between them, i.e. the size of the heap.\n\n");
+    }
+    printf("low mem %ld\t high mem %ld\t diff %ld\n",
+	   (long)m_l, (long)m_high, (long)(m_high - ((char *)m_l)));
+
+    if (ops['v']) {
+	printf("\nThe number of bytes that were allocated using sbrk() and\n");
+	printf("the number of bytes that were given back to the system\n");
+	printf("via brk().\n");
+    }
+    printf("\nsbrk %d\tbrk %d\n", m_s, m_b);
+
+    if (ops['v']) {
+	printf("\nInformation about the sizes that were allocated or freed.\n");
+	printf("For each size that were used the number of mallocs and\n");
+	printf("frees is shown. Diff gives the difference between these\n");
+	printf("values, i.e. the number of blocks of that size that is\n");
+	printf("currently allocated. Total is the product of size and diff,\n");
+	printf("i.e. the number of bytes that are allocated for blocks of\n");
+	printf("this size.\n");
+    }
+    printf("\nsize\tmalloc\tfree\tdiff\ttotal\n");
+    for (i = 0; i < 1024; i++)
+	if (m_m[i] || m_f[i])
+	    printf("%ld\t%d\t%d\t%d\t%ld\n", (long)i * M_ISIZE, m_m[i], m_f[i],
+		   m_m[i] - m_f[i], (long)i * M_ISIZE * (m_m[i] - m_f[i]));
+
+    if (m_m[i] || m_f[i])
+	printf("big\t%d\t%d\t%d\n", m_m[i], m_f[i], m_m[i] - m_f[i]);
+
+    if (ops['v']) {
+	printf("\nThe list of memory blocks. For each block the following\n");
+	printf("information is shown:\n\n");
+	printf("num\tthe number of this block\n");
+	printf("tnum\tlike num but counted separatedly for used and free\n");
+	printf("\tblocks\n");
+	printf("addr\tthe address of this block\n");
+	printf("len\tthe length of the block\n");
+	printf("state\tthe state of this block, this can be:\n");
+	printf("\t  used\tthis block is used for one big block\n");
+	printf("\t  free\tthis block is free\n");
+	printf("\t  small\tthis block is used for an array of small blocks\n");
+	printf("cum\tthe accumulated sizes of the blocks, counted\n");
+	printf("\tseparatedly for used and free blocks\n");
+	printf("\nFor blocks holding small blocks the number of free\n");
+	printf("blocks, the number of used blocks and the size of the\n");
+	printf("blocks is shown. For otherwise used blocks the first few\n");
+	printf("bytes are shown as an ASCII dump.\n");
+    }
+    printf("\nblock list:\nnum\ttnum\taddr\tlen\tstate\tcum\n");
+    for (m = m_l, mf = m_free, ii = fi = ui = 1; ((char *)m) < m_high;
+	 m = (struct m_hdr *)(((char *)m) + M_ISIZE + m->len), ii++) {
+	for (j = 0, ms = NULL; j < M_NSMALL && !ms; j++)
+	    for (ms = m_small[j]; ms; ms = ms->next)
+		if (ms == m)
+		    break;
+
+	if (m == mf)
+	    buf[0] = '\0';
+	else if (m == ms)
+	    sprintf(buf, "%ld %ld %ld", M_SNUM - ms->used, ms->used,
+		    (m->len - sizeof(struct m_hdr)) / M_SNUM + 1);
+
+	else {
+	    for (i = 0, b = buf, c = (char *)&m->next; i < 20 && i < m->len;
+		 i++, c++)
+		*b++ = (*c >= ' ' && *c < 127) ? *c : '.';
+	    *b = '\0';
+	}
+
+	printf("%d\t%d\t%ld\t%ld\t%s\t%ld\t%s\n", ii,
+	       (m == mf) ? fi++ : ui++,
+	       (long)m, m->len,
+	       (m == mf) ? "free" : ((m == ms) ? "small" : "used"),
+	       (m == mf) ? (f += m->len) : (u += m->len),
+	       buf);
+
+	if (m == mf)
+	    mf = mf->next;
+    }
+
+    if (ops['v']) {
+	printf("\nHere is some information about the small blocks used.\n");
+	printf("For each size the arrays with the number of free and the\n");
+	printf("number of used blocks are shown.\n");
+    }
+    printf("\nsmall blocks:\nsize\tblocks (free/used)\n");
+
+    for (i = 0; i < M_NSMALL; i++)
+	if (m_small[i]) {
+	    printf("%ld\t", (long)i * M_ISIZE);
+
+	    for (ii = 0, m = m_small[i]; m; m = m->next) {
+		printf("(%ld/%ld) ", M_SNUM - m->used, m->used);
+		if (!((++ii) & 7))
+		    printf("\n\t");
+	    }
+	    putchar('\n');
+	}
+    if (ops['v']) {
+	printf("\n\nBelow is some information about the allocation\n");
+	printf("behaviour of the zsh heaps. First the number of times\n");
+	printf("pushheap(), popheap(), and freeheap() were called.\n");
+    }
+    printf("\nzsh heaps:\n\n");
+
+    printf("push %d\tpop %d\tfree %d\n\n", h_push, h_pop, h_free);
+
+    if (ops['v']) {
+	printf("\nThe next list shows for several sizes the number of times\n");
+	printf("memory of this size were taken from heaps.\n\n");
+    }
+    printf("size\tmalloc\ttotal\n");
+    for (i = 0; i < 1024; i++)
+	if (h_m[i])
+	    printf("%ld\t%d\t%ld\n", (long)i * H_ISIZE, h_m[i],
+		   (long)i * H_ISIZE * h_m[i]);
+    if (h_m[1024])
+	printf("big\t%d\n", h_m[1024]);
+
+    return 0;
+}
+
+#endif
+
+#else				/* not ZSH_MEM */
+
+/**/
+void
+zfree(void *p, int sz)
+{
+    if (p)
+	free(p);
+}
+
+/**/
+void
+zsfree(char *p)
+{
+    if (p)
+	free(p);
+}
+
+#endif
diff --git a/Src/mkbltnmlst.sh b/Src/mkbltnmlst.sh
new file mode 100644
index 000000000..4a90ecd20
--- /dev/null
+++ b/Src/mkbltnmlst.sh
@@ -0,0 +1,60 @@
+#! /bin/sh
+#
+# mkbltnmlst.sh: generate boot code for linked-in modules
+#
+# Written by Andrew Main
+#
+srcdir=${srcdir-`echo $0|sed 's%/[^/][^/]*$%%'`}
+test "x$srcdir" = "x$0" && srcdir=.
+test "x$srcdir" = "x"   && srcdir=.
+MODBINS=${MODBINS-modules-bltin}
+XMODCF=${XMODCF-$srcdir/xmods.conf}
+
+bin_mods=" zsh "`sed 's/^/ /;s/$/ /' $MODBINS`
+x_mods=`cat $XMODCF`
+. ./modules.index
+
+trap "rm -f $1; exit 1" 1 2 15
+
+exec > $1
+
+echo "#ifdef DYNAMIC"
+for x_mod in $x_mods; do
+    case $bin_mods in
+	*" $x_mod "*) ;;
+	*)  echo "/* non-linked-in known module \`$x_mod' */"
+	    eval "loc=\$loc_$x_mod"
+	    unset moddeps autobins
+	    . $srcdir/../$loc/${x_mod}.mdd
+	    for bin in $autobins; do
+		echo "    add_autobin(\"$bin\", \"$x_mod\");"
+	    done
+	    for dep in $moddeps; do
+		case $bin_mods in
+		    *" $dep "*)
+			echo "    /* depends on \`$dep' */" ;;
+		    *)	echo "    add_dep(\"$x_mod\", \"$dep\");" ;;
+		esac
+	    done ;;
+    esac
+done
+echo "#endif /* DYNAMIC */"
+echo
+done_mods=" "
+for bin_mod in $bin_mods; do
+    echo "/* linked-in module \`$bin_mod' */"
+    eval "loc=\$loc_$bin_mod"
+    unset moddeps
+    . $srcdir/../$loc/${bin_mod}.mdd
+    for dep in $moddeps; do
+	case $done_mods in
+	    *" $dep "*)
+		echo "    /* depends on \`$dep' */" ;;
+	    *)	echo >&2 "ERROR: linked-in module \`$bin_mod' depends on \`$dep'"
+		rm -f $1
+		exit 1 ;;
+	esac
+    done
+    echo "    mod.nam = \"$bin_mod\"; boot_$bin_mod(&mod);"
+    done_mods="$done_mods$bin_mod "
+done
diff --git a/Src/mkmakemod.sh b/Src/mkmakemod.sh
new file mode 100644
index 000000000..b088929fa
--- /dev/null
+++ b/Src/mkmakemod.sh
@@ -0,0 +1,315 @@
+#!/bin/sh
+#
+# mkmakemod.sh: generate Makefile.in files for module building
+#
+# Options:
+#   -m = file is already generated; only build the second stage
+#   -i = do not build second stage
+#
+# Args:
+#   $1 = subdirectory to look in, relative to $top_srcdir
+#   $2 = final output filename, within the $1 directory
+#
+# This script must be run from the top-level build directory, and $top_srcdir
+# must be set correctly in the environment.
+#
+# This looks in $1, and uses all the *.mdd files there.  Each .mdd file
+# defines one module.  The .mdd file is actually a shell script, which will
+# be sourced.  It may define the following shell variables:
+#
+#   moddeps       modules on which this module depends (default none)
+#   nozshdep      non-empty indicates no dependence on the `zsh' pseudo-module
+#   alwayslink    if non-empty, always link the module into the executable
+#   autobins      builtins defined by the module, for autoloading
+#   objects       .o files making up this module (*must* be defined)
+#   proto         .pro files for this module (default generated from $objects)
+#   headers       extra headers for this module (default none)
+#   hdrdeps       extra headers on which the .mdh depends (default none)
+#   otherincs     extra headers that are included indirectly (default none)
+#
+# The .mdd file may also include a Makefile.in fragment between lines
+# `:<<\Make' and `Make' -- this will be copied into Makemod.in.
+#
+# The resulting Makemod.in knows how to build each module that is defined.
+# For each module in also knows how to build a .mdh file.  Each source file
+# should #include the .mdh file for the module it is a part of.  The .mdh
+# file #includes the .mdh files for any module dependencies, then each of
+# $headers, and then each of $proto (for global declarations).  It will
+# be recreated if any of the dependency .mdh files changes, or if any of
+# $headers or $hdrdeps changes.  When anything depends on it, all of $proto
+# and $otherincs will be made up to date, but the .mdh file won't actually
+# be rebuilt if those files change.
+#
+# The order of sections of the output file is thus:
+#   simple generated macros
+#   macros generated from *.mdd
+#   included Makemod.in.in
+#   rules generated from *.mdd
+# The order dependencies are basically that the generated macros are required
+# in Makemod.in.in, but some of the macros that it creates are needed in the
+# later rules.
+#
+
+# sed script to normalise a pathname
+sed_normalise='
+    s,^,/,
+    s,$,/,
+    :1
+    s,/\./,/,
+    t1
+    :2
+    s,/[^/.][^/]*/\.\./,/,
+    s,/\.[^/.][^/]*/\.\./,/,
+    s,/\.\.[^/][^/]*/\.\./,/,
+    t2
+    s,^/$,.,
+    s,^/,,
+    s,\(.\)/$,\1,
+'
+
+# decide which stages to process
+first_stage=true
+second_stage=true
+if test ."$1" = .-m; then
+    shift
+    first_stage=false
+elif test ."$1" = .-i; then
+    shift
+    second_stage=false
+fi
+
+top_srcdir=`echo $top_srcdir | sed "$sed_normalise"`
+the_subdir=$1
+the_makefile=$2
+
+if $first_stage; then
+
+    trap "rm -f $the_subdir/${the_makefile}.in" 1 2 15
+    echo "creating $the_subdir/${the_makefile}.in"
+    exec 3>&1 >$the_subdir/${the_makefile}.in
+    echo "##### ${the_makefile}.in generated automatically by mkmakemod.sh"
+    echo "##### DO NOT EDIT!"
+    echo
+    echo "##### ===== DEFINITIONS ===== #####"
+    echo
+    echo "makefile = ${the_makefile}"
+    echo "dir_top = "`echo $the_subdir | sed 's,[^/][^/]*,..,g'`
+    echo "subdir = $the_subdir"
+    echo
+
+    . Src/modules.index
+    bin_mods=" zsh "`sed 's/^/ /;s/$/ /' Src/modules-bltin`
+    if grep '%@D@%D%' config.status >/dev/null; then
+	is_dynamic=true
+    else
+	is_dynamic=false
+    fi
+
+    here_modules=
+    all_subdirs=
+    all_modobjs=
+    all_modules=
+    all_mdds=
+    all_mdhs=
+    all_proto=
+    lastsub=//
+    for module in $module_list; do
+	eval "loc=\$loc_$module"
+	case $loc in
+	    $the_subdir)
+		here_modules="$here_modules $module"
+		build=$is_dynamic
+		case $is_dynamic@$bin_mods in
+		    *" $module "*)
+			build=true
+			all_modobjs="$all_modobjs modobjs.${module}" ;;
+		    true@*)
+			all_modules="$all_modules ${module}.\$(DL_EXT)" ;;
+		esac
+		all_mdds="$all_mdds ${module}.mdd"
+		$build && all_mdhs="$all_mdhs ${module}.mdh"
+		$build && all_proto="$all_proto proto.${module}"
+		;;
+	    $lastsub | $lastsub/*) ;;
+	    $the_subdir/*)
+		all_subdirs="$all_subdirs $loc"
+		lastsub=$loc
+		;;
+	esac
+    done
+    all_subdirs=`echo "$all_subdirs" | sed "s' $the_subdir/' 'g"`
+    echo "MODOBJS =$all_modobjs"
+    echo "MODULES =$all_modules"
+    echo "MDDS    =$all_mdds"
+    echo "MDHS    =$all_mdhs"
+    echo "PROTOS  =$all_proto"
+    echo "SUBDIRS =$all_subdirs"
+    echo
+
+    echo "##### ===== INCLUDING Makemod.in.in ===== #####"
+    echo
+    cat $top_srcdir/Src/Makemod.in.in
+    echo
+
+    case $the_subdir in
+	Src) modobjs_sed= ;;
+	Src/*) modobjs_sed="| sed 's\" \" "`echo $the_subdir | sed 's,^Src/,,'`"/\"g' " ;;
+	*) modobjs_sed="| sed 's\" \" ../$the_subdir/\"g' " ;;
+    esac
+
+    other_mdhs=
+    remote_mdhs=
+    for module in $here_modules; do
+
+	unset moddeps nozshdep alwayslink
+	unset autobins
+	unset objects proto headers hdrdeps otherincs
+	. $top_srcdir/$the_subdir/${module}.mdd
+	test -n "${moddeps+set}" || moddeps=
+	test -n "$nozshdep" || moddeps="$moddeps zsh"
+	test -n "${proto+set}" ||
+	    proto=`echo $objects '' | sed 's,\.o ,.pro ,g'`
+
+	dobjects=`echo $objects '' | sed 's,\.o ,..o ,g'`
+	modhdeps=
+	for dep in $moddeps; do
+	    eval "loc=\$loc_$dep"
+	    case $the_subdir in
+		$loc)
+		    mdh="${dep}.mdh"
+		    ;;
+		$loc/*)
+		    mdh="\$(dir_top)/$loc/${dep}.mdh"
+		    case "$other_mdhs " in
+			*" $mdh "*) ;;
+			*) other_mdhs="$other_mdhs $mdh" ;;
+		    esac
+		    ;;
+		*)
+		    mdh="\$(dir_top)/$loc/${dep}.mdh"
+		    case "$remote_mdhs " in
+			*" $mdh "*) ;;
+			*) remote_mdhs="$remote_mdhs $mdh" ;;
+		    esac
+		    ;;
+	    esac
+	    modhdeps="$modhdeps $mdh"
+	done
+
+	echo "##### ===== DEPENDENCIES GENERATED FROM ${module}.mdd ===== #####"
+	echo
+	echo "MODOBJS_${module} = $objects"
+	echo "MODDOBJS_${module} = $dobjects"
+	echo "PROTO_${module} = $proto"
+	echo "INCS_${module} = \$(PROTO_${module}) $otherincs"
+	echo
+	echo "proto.${module}: \$(PROTO_${module})"
+	echo "\$(PROTO_${module}): \$(PROTODEPS)"
+	echo
+	echo "modobjs.${module}: \$(MODOBJS_${module})"
+	echo "	echo '' \$(MODOBJS_${module}) $modobjs_sed>> \$(dir_src)/stamp-modobjs.tmp"
+	echo
+	if test -z "$alwayslink"; then
+	    echo "${module}.\$(DL_EXT): \$(MODDOBJS_${module})"
+	    echo '	rm -f $@'
+	    echo "	\$(DLLINK) \$(MODDOBJS_${module}) \$(LIBS)"
+	    echo
+	fi
+	echo "${module}.mdhi: ${module}.mdhs \$(INCS_${module})"
+	echo "	@test -f \$@ || echo 'do not delete this file' > \$@"
+	echo
+	echo "${module}.mdhs: ${module}.mdd"
+	echo "	@\$(MAKE) -f \$(makefile) \$(MAKEDEFS) ${module}.mdh.tmp"
+	echo "	@if cmp -s ${module}.mdh ${module}.mdh.tmp; then \\"
+	echo "	    rm -f ${module}.mdh.tmp; \\"
+	echo "	    echo \"\\\`${module}.mdh' is up to date.\"; \\"
+	echo "	else \\"
+	echo "	    mv -f ${module}.mdh.tmp ${module}.mdh; \\"
+	echo "	    echo \"Updated \\\`${module}.mdh'.\"; \\"
+	echo "	fi"
+	echo "	echo 'timestamp for ${module}.mdh against ${module}.mdd' > \$@"
+	echo
+	echo "${module}.mdh: ${modhdeps} ${headers} ${hdrdeps} ${module}.mdhi"
+	echo "	@\$(MAKE) -f \$(makefile) \$(MAKEDEFS) ${module}.mdh.tmp"
+	echo "	@mv -f ${module}.mdh.tmp ${module}.mdh"
+	echo "	@echo \"Updated \\\`${module}.mdh'.\""
+	echo
+	echo "${module}.mdh.tmp:"
+	echo "	@( \\"
+	echo "	    echo '#ifndef have_${module}_module'; \\"
+	echo "	    echo '#define have_${module}_module'; \\"
+	echo "	    echo; \\"
+	if test -n "$moddeps"; then
+	    echo "	    echo '/* Module dependencies */'; \\"
+	    echo "	    for mod in $modhdeps; do \\"
+	    echo "		echo '# define USING_MODULE'; \\"
+	    echo "		echo '# include \"'\$\$mod'\"'; \\"
+	    echo "	    done; \\"
+	    echo "	    echo '# undef USING_MODULE'; \\"
+	    echo "	    echo; \\"
+	fi
+	if test -n "$headers"; then
+	    echo "	    echo '/* Extra headers for this module */'; \\"
+	    echo "	    for hdr in $headers; do \\"
+	    echo "		if test -f \$\$hdr; then \\"
+	    echo "		    echo '# include \"'\$\$hdr'\"'; \\"
+	    echo "		else \\"
+	    echo "		    echo '# include \"\$(sdir)/'\$\$hdr'\"'; \\"
+	    echo "		fi; \\"
+	    echo "	    done; \\"
+	    echo "	    echo; \\"
+	fi
+	if test -n "$proto"; then
+	    echo "	    echo '# define GLOBAL_PROTOTYPES'; \\"
+	    echo "	    for pro in \$(PROTO_${module}); do \\"
+	    echo "		echo '# include \"'\$\$pro'\"'; \\"
+	    echo "	    done; \\"
+	    echo "	    echo '# undef GLOBAL_PROTOTYPES'; \\"
+	    echo "	    echo; \\"
+	fi
+	echo "	    echo '#endif /* !have_${module}_module */'; \\"
+	echo "	) > \$@"
+	echo
+	echo "\$(MODOBJS_${module}) \$(MODDOBJS_${module}): ${module}.mdh"
+	sed -e '/^ *: *<< *\\Make *$/,/^Make$/!d' \
+	    -e 's/^ *: *<< *\\Make *$//; /^Make$/d' \
+	    < $top_srcdir/$the_subdir/${module}.mdd
+	echo
+
+    done
+
+    if test -n "$remote_mdhs$other_mdhs"; then
+	echo "##### ===== DEPENDENCIES FOR REMOTE MODULES ===== #####"
+	echo
+	for mdh in $remote_mdhs; do
+	    echo "$mdh: FORCE"
+	    echo "	@cd @%@ && \$(MAKE) \$(MAKEDEFS) @%@$mdh"
+	    echo
+	done | sed 's,^\(.*\)@%@\(.*\)@%@\(.*\)/\([^/]*\)$,\1\3\2\4,'
+	if test -n "$other_mdhs"; then
+	    echo "${other_mdhs}:"
+	    echo "	false # should only happen with make -n"
+	    echo
+	fi
+    fi
+
+    echo "##### End of ${the_makefile}.in"
+
+    exec >&3 3>&-
+
+fi
+
+if $second_stage; then
+
+    trap "rm -f $the_subdir/${the_makefile}" 1 2 15
+
+    # The standard config.status requires the pathname for the .in file to
+    # be relative to the top of the source tree.  As we have it in the build
+    # tree, this is a problem.  zsh's configure script edits config.status,
+    # adding the feature that an input filename starting with "!" has the
+    # "!" removed and is not mangled further.
+    CONFIG_FILES=$the_subdir/${the_makefile}:\!$the_subdir/${the_makefile}.in CONFIG_HEADERS= ./config.status
+
+fi
+
+exit 0
diff --git a/Src/mkmodindex.sh b/Src/mkmodindex.sh
new file mode 100644
index 000000000..b4616f638
--- /dev/null
+++ b/Src/mkmodindex.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+#
+# mkmodindex.sh: search for *.mdd files, and index the modules
+#
+# $@ = directories to search from
+#
+
+echo "# module index generated by mkmodindex.sh"
+echo
+
+module_list=' '
+while test $# -ne 0; do
+    dir=$1
+    shift
+    ( set $dir/*.mdd; test -f $1 ) || continue
+    dosubs=false
+    for mod in `echo '' $dir/*.mdd '' | sed 's, [^ ]*/, ,g;s,\.mdd , ,g'`; do
+	case `echo "$mod@ $module_list " | sed 's,^.*[^_0-9A-Za-z].*@,@@,'` in
+	    @@*)
+		echo >&2 "WARNING: illegally named module \`$mod' in $dir"
+		echo >&2 "         (ignoring it)"
+		;;
+	    *@*" $mod "*)
+		eval "loc=\$loc_$mod"
+		echo >&2 "WARNING: module \`$mod' (in $loc) duplicated in $dir"
+		echo >&2 "         (ignoring duplicate)"
+		dosubs=true
+		;;
+	    *)
+		module_list="$module_list$mod "
+		echo "loc_$mod=$dir"
+		eval "loc_$mod=\$dir"
+		dosubs=true
+		;;
+	esac
+    done
+    $dosubs && set `echo $dir/*/. '' | sed 's,/\. , ,g'` "$@"
+done
+
+echo
+echo $module_list | sed 's/^/module_list="/;s/$/"/'
+
+exit 0
diff --git a/Src/modentry.c b/Src/modentry.c
new file mode 100644
index 000000000..63c4b825d
--- /dev/null
+++ b/Src/modentry.c
@@ -0,0 +1,15 @@
+#include "zsh.mdh"
+
+int boot_ _((Module));
+int cleanup_ _((Module));
+int modentry _((int boot, Module m));
+
+/**/
+int
+modentry(int boot, Module m)
+{
+    if (boot)
+	return boot_(m);
+    else
+	return cleanup_(m);
+}
diff --git a/Src/module.c b/Src/module.c
new file mode 100644
index 000000000..ce5989f07
--- /dev/null
+++ b/Src/module.c
@@ -0,0 +1,651 @@
+/*
+ * module.c - deal with dynamic modules
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1996-1997 Zoltán Hidvégi
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Zoltán Hidvégi or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Zoltán Hidvégi and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Zoltán Hidvégi and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Zoltán Hidvégi and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "module.pro"
+
+/* The `zsh' module contains all the base code that can't actually be built *
+ * as a separate module.  It is initialised by main(), so there's nothing   *
+ * for the boot function to do.                                             */
+
+/**/
+int
+boot_zsh(Module m)
+{
+    return 0;
+}
+
+/* addbuiltin() can be used to add a new builtin.  It returns zero on *
+ * success, 1 on failure.  The only possible type of failure is that  *
+ * a builtin with the specified name already exists.  An autoloaded   *
+ * builtin can be replaced using this function.                       */
+
+/**/
+int
+addbuiltin(Builtin b)
+{
+    Builtin bn = (Builtin) builtintab->getnode2(builtintab, b->nam);
+    if (bn && (bn->flags & BINF_ADDED))
+	return 1;
+    if (bn)
+	builtintab->freenode(builtintab->removenode(builtintab, b->nam));
+    PERMALLOC {
+	builtintab->addnode(builtintab, b->nam, b);
+    } LASTALLOC;
+    return 0;
+}
+
+/* Add multiple builtins.  binl points to a table of `size' builtin      *
+ * structures.  Those for which (.flags & BINF_ADDED) is false are to be *
+ * added; that flag is set if they succeed.  If any fail, an error       *
+ * message is printed, using nam as the leading name.  Returns 1 if all  *
+ * additions succeed, 2 if some succeed and some fail, and 0 if all (and *
+ * at least 1) fail.  The usual usage in a boot_*() function would be    *
+ *  return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); */
+
+/**/
+int
+addbuiltins(char const *nam, Builtin binl, int size)
+{
+    int hads = 0, hadf = 0, n;
+
+    for(n = 0; n < size; n++) {
+	Builtin b = &binl[n];
+	if(b->flags & BINF_ADDED)
+	    continue;
+	if(addbuiltin(b)) {
+	    zwarnnam(nam, "name clash when adding builtin `%s'", b->nam, 0);
+	    hadf = 1;
+	} else {
+	    b->flags |= BINF_ADDED;
+	    hads = 2;
+	}
+    }
+    return hadf ? hads : 1;
+}
+
+#ifdef DYNAMIC
+
+/* $module_path ($MODULE_PATH) */
+
+/**/
+char **module_path;
+
+/* List of modules */
+
+/**/
+LinkList modules;
+
+/* Define an autoloadable builtin.  It returns 0 on success, or 1 on *
+ * failure.  The only possible cause of failure is that a builtin    *
+ * with the specified name already exists.                           */
+
+/**/
+int
+add_autobin(char *nam, char *module)
+{
+    Builtin bn = zcalloc(sizeof(*bn));
+    bn->nam = ztrdup(nam);
+    bn->optstr = ztrdup(module);
+    return addbuiltin(bn);
+}
+
+/* Remove the builtin added previously by addbuiltin().  Returns *
+ * zero on succes and -1 if there is no builtin with that name.  */
+
+/**/
+int
+deletebuiltin(char *nam)
+{
+    Builtin bn;
+
+    bn = (Builtin) builtintab->removenode(builtintab, nam);
+    if (!bn)
+	return -1;
+    builtintab->freenode((HashNode)bn);
+    return 0;
+}
+
+/* Delete multiple builtins.  binl points to a table of `size' builtin  *
+ * structures.  Those for which (.flags & BINF_ADDED) is true are to be *
+ * deleted; that flag is cleared.  If any fail, an error message is     *
+ * printed, using nam as the leading name.  Returns 1 if all deletions  *
+ * succeed, 2 if some succeed and some fail, and 0 if all (and at least *
+ * 1) fail.  In normal use, from a cleanup_*() function, this return    *
+ * value would be ignored -- the only cause of failure would be that a  *
+ * wayward module had deleted our builtin without telling us.           */
+
+/**/
+int
+deletebuiltins(char const *nam, Builtin binl, int size)
+{
+    int hads = 0, hadf = 0, n;
+
+    for(n = 0; n < size; n++) {
+	Builtin b = &binl[n];
+	if(!(b->flags & BINF_ADDED))
+	    continue;
+	if(deletebuiltin(b->nam)) {
+	    zwarnnam(nam, "builtin `%s' already deleted", b->nam, 0);
+	    hadf = 1;
+	} else
+	    hads = 2;
+	b->flags &= ~BINF_ADDED;
+    }
+    return hadf ? hads : 1;
+}
+
+#ifdef HAVE_DLFCN_H
+# include <dlfcn.h>
+#else
+# include <sys/types.h>
+# include <nlist.h>
+# include <link.h>
+#endif
+#ifndef RTLD_LAZY
+# define RTLD_LAZY 1
+#endif
+#ifndef RTLD_GLOBAL
+# define RTLD_GLOBAL 0
+#endif
+#ifndef HAVE_DLCLOSE
+# define dlclose(X) ((X), 0)
+#endif
+
+#ifdef DLSYM_NEEDS_UNDERSCORE
+# define STR_BOOT      "_boot_"
+# define STR_BOOT_S    "_boot_%s"
+# define STR_CLEANUP   "_cleanup_"
+# define STR_CLEANUP_S "_cleanup_%s"
+#else /* !DLSYM_NEEDS_UNDERSCORE */
+# define STR_BOOT      "boot_"
+# define STR_BOOT_S    "boot_%s"
+# define STR_CLEANUP   "cleanup_"
+# define STR_CLEANUP_S "cleanup_%s"
+#endif /* !DLSYM_NEEDS_UNDERSCORE */
+typedef int (*Module_func) _((Module));
+
+/**/
+static void *
+try_load_module(char const *name)
+{
+    char buf[PATH_MAX + 1];
+    char **pp;
+    void *ret = NULL;
+    int l;
+
+    if (strchr(name, '/')) {
+	ret = dlopen(unmeta(name), RTLD_LAZY | RTLD_GLOBAL);
+	if (ret || 
+	    unset(PATHDIRS) ||
+	    (*name == '/') ||
+	    (*name == '.' && name[1] == '/') ||
+	    (*name == '.' && name[1] == '.' && name[2] == '/'))
+	    return ret;
+    }
+
+    l = strlen(name) + 1;
+    for (pp = module_path; !ret && *pp; pp++) {
+	if (l + (**pp ? strlen(*pp) : 1) > PATH_MAX)
+	    continue;
+	sprintf(buf, "%s/%s", **pp ? *pp : ".", name);
+	ret = dlopen(unmeta(buf), RTLD_LAZY | RTLD_GLOBAL);
+    }
+
+    return ret;
+}
+
+/**/
+static void *
+do_load_module(char const *name)
+{
+    void *ret = NULL;
+    char buf[PATH_MAX + 1];
+
+    if (strlen(name) + strlen(DL_EXT) < PATH_MAX) {
+	sprintf(buf, "%s.%s", name, DL_EXT);
+	ret = try_load_module(buf);
+    }
+    if (!ret)
+	ret = try_load_module(name);
+    if (!ret) {
+	int waserr = errflag;
+	zerr("failed to load module: %s", name, 0);
+	errflag = waserr;
+    }
+    return ret;
+}
+
+/**/
+static LinkNode
+find_module(const char *name)
+{
+    Module m;
+    LinkNode node;
+
+    for (node = firstnode(modules); node; incnode(node)) {
+	m = (Module) getdata(node);
+	if (!strcmp(m->nam, name))
+	    return node;
+    }
+    return NULL;
+}
+
+/**/
+static int
+init_module(Module m)
+{
+    char *s, *t;
+#ifndef DYNAMIC_NAME_CLASH_OK
+    char buf[PATH_MAX + 1];
+#endif
+    Module_func fn;
+
+    s = strrchr(m->nam, '/');
+    if (s)
+	s = dupstring(++s);
+    else
+	s = m->nam;
+    if ((t = strrchr(s, '.')))
+	*t = '\0';
+#ifdef DYNAMIC_NAME_CLASH_OK
+    fn = (Module_func) dlsym(m->handle, STR_BOOT);
+#else /* !DYNAMIC_NAME_CLASH_OK */
+    if (strlen(s) + 6 > PATH_MAX)
+	return 1;
+    sprintf(buf, STR_BOOT_S, s);
+    fn = (Module_func) dlsym(m->handle, buf);
+#endif /* !DYNAMIC_NAME_CLASH_OK */
+    if(fn)
+	return fn(m);
+    zwarnnam(m->nam, "no boot function", NULL, 0);
+    return 1;
+}
+
+/**/
+Module
+load_module(char const *name)
+{
+    Module m;
+    void *handle;
+    LinkNode node, n;
+
+    if (!(node = find_module(name))) {
+	if (!(handle = do_load_module(name)))
+	    return NULL;
+	m = zcalloc(sizeof(*m));
+	m->nam = ztrdup(name);
+	m->handle = handle;
+	if (init_module(m)) {
+	    dlclose(handle);
+	    zsfree(m->nam);
+	    zfree(m, sizeof(*m));
+	    return NULL;
+	}
+	PERMALLOC {
+	    addlinknode(modules, m);
+	} LASTALLOC;
+	return m;
+    }
+    m = (Module) getdata(node);
+    if (m->handle)
+	return m;
+    if (m->flags & MOD_BUSY) {
+	zerr("circular dependencies for module %s", name, 0);
+	return NULL;
+    }
+    m->flags |= MOD_BUSY;
+    for (n = firstnode(m->deps); n; incnode(n))
+	if (!load_module((char *) getdata(n))) {
+	    m->flags &= ~MOD_BUSY;
+	    return NULL;
+	}
+    m->flags &= ~MOD_BUSY;
+    if (!(m->handle = do_load_module(name)))
+	return NULL;
+    if (init_module(m)) {
+	dlclose(m->handle);
+	m->handle = NULL;
+	return NULL;
+    }
+    return m;
+}
+
+/**/
+static int
+cleanup_module(Module m)
+{
+    char *s, *t;
+#ifndef DYNAMIC_NAME_CLASH_OK
+    char buf[PATH_MAX + 1];
+#endif
+    Module_func fn;
+
+    s = strrchr(m->nam, '/');
+    if (s)
+	s = dupstring(++s);
+    else
+	s = m->nam;
+    if ((t = strrchr(s, '.')))
+	*t = '\0';
+#ifdef DYNAMIC_NAME_CLASH_OK
+    fn = (Module_func) dlsym(m->handle, STR_CLEANUP);
+#else /* !DYNAMIC_NAME_CLASH_OK */
+    if (strlen(s) + 9 > PATH_MAX)
+	return 1;
+    sprintf(buf, STR_CLEANUP_S, s);
+    fn = (Module_func) dlsym(m->handle, buf);
+#endif /* !DYNAMIC_NAME_CLASH_OK */
+    if(fn)
+	return fn(m);
+    zwarnnam(m->nam, "no cleanup function", NULL, 0);
+    return 1;
+}
+
+/**/
+void
+add_dep(char *name, char *from)
+{
+    LinkNode node;
+    Module m;
+
+    PERMALLOC {
+	if (!(node = find_module(name))) {
+	    m = zcalloc(sizeof(*m));
+	    m->nam = ztrdup(name);
+	    addlinknode(modules, m);
+	} else
+	    m = (Module) getdata(node);
+	if (!m->deps)
+	    m->deps = newlinklist();
+	for (node = firstnode(m->deps);
+	     node && strcmp((char *) getdata(node), from);
+	     incnode(node));
+	if (!node)
+	    addlinknode(m->deps, ztrdup(from));
+    } LASTALLOC;
+}
+
+/**/
+static void
+autoloadscan(HashNode hn, int printflags)
+{
+    Builtin bn = (Builtin) hn;
+
+    if(bn->flags & BINF_ADDED)
+	return;
+    if(printflags & PRINT_LIST) {
+	fputs("zmodload -a ", stdout);
+	if(bn->optstr[0] == '-')
+	    fputs("-- ", stdout);
+	quotedzputs(bn->optstr, stdout);
+	if(strcmp(bn->nam, bn->optstr)) {
+	    putchar(' ');
+	    quotedzputs(bn->nam, stdout);
+	}
+    } else {
+	nicezputs(bn->nam, stdout);
+	if(strcmp(bn->nam, bn->optstr)) {
+	    fputs(" (", stdout);
+	    nicezputs(bn->optstr, stdout);
+	    putchar(')');
+	}
+    }
+    putchar('\n');
+}
+
+/**/
+int
+bin_zmodload(char *nam, char **args, char *ops, int func)
+{
+    if(ops['d'] && ops['a']) {
+	zwarnnam(nam, "-d cannot be combined with -a", NULL, 0);
+	return 1;
+    }
+    if (ops['u'] && !*args) {
+	zwarnnam(nam, "what do you want to unload?", NULL, 0);
+	return 1;
+    }
+    if(ops['d'])
+	return bin_zmodload_dep(nam, args, ops);
+    else if(ops['a'])
+	return bin_zmodload_auto(nam, args, ops);
+    else
+	return bin_zmodload_load(nam, args, ops);
+}
+
+/**/
+static int
+bin_zmodload_dep(char *nam, char **args, char *ops)
+{
+    LinkNode node;
+    Module m;
+    if(ops['u']) {
+	/* remove dependencies */
+	char *tnam = *args++;
+	node = find_module(tnam);
+	if (!node)
+	    return 0;
+	m = (Module) getdata(node);
+	if(*args && m->deps) {
+	    do {
+		for(node = firstnode(m->deps); node; incnode(node))
+		    if(!strcmp(*args, getdata(node))) {
+			zsfree(getdata(node));
+			remnode(m->deps, node);
+			break;
+		    }
+	    } while(*++args);
+	    if(empty(m->deps)) {
+		freelinklist(m->deps, freestr);
+		m->deps = NULL;
+	    }
+	} else {
+	    if (m->deps) {
+		freelinklist(m->deps, freestr);
+		m->deps = NULL;
+	    }
+	}
+	if (!m->deps && !m->handle) {
+	    remnode(modules, node);
+	    zsfree(m->nam);
+	    zfree(m, sizeof(*m));
+	}
+	return 0;
+    } else if(!args[0] || !args[1]) {
+	/* list dependencies */
+	for (node = firstnode(modules); node; incnode(node)) {
+	    m = (Module) getdata(node);
+	    if (m->deps && (!args[0] || !strcmp(args[0], m->nam))) {
+		LinkNode n;
+		if(ops['L']) {
+		    printf("zmodload -d ");
+		    if(m->nam[0] == '-')
+			fputs("-- ", stdout);
+		    quotedzputs(m->nam, stdout);
+		} else {
+		    nicezputs(m->nam, stdout);
+		    putchar(':');
+		}
+		for (n = firstnode(m->deps); n; incnode(n)) {
+		    putchar(' ');
+		    if(ops['L'])
+			quotedzputs((char *) getdata(n), stdout);
+		    else
+			nicezputs((char *) getdata(n), stdout);
+		}
+		putchar('\n');
+	    }
+	}
+	return 0;
+    } else {
+	/* add dependencies */
+	int ret = 0;
+	char *tnam = *args++;
+
+	for(; *args; args++) {
+	    if(isset(RESTRICTED) && strchr(*args, '/')) {
+		zwarnnam(nam, "%s: restricted", *args, 0);
+		ret = 1;
+	    } else
+		add_dep(tnam, *args);
+	}
+	return ret;
+    }
+}
+
+/**/
+static int
+bin_zmodload_auto(char *nam, char **args, char *ops)
+{
+    int ret = 0;
+    if(ops['u']) {
+	/* remove autoloaded builtins */
+	for (; *args; args++) {
+	    Builtin bn = (Builtin) builtintab->getnode2(builtintab, *args);
+	    if (!bn) {
+		if(!ops['i']) {
+		    zwarnnam(nam, "%s: no such builtin", *args, 0);
+		    ret = 1;
+		}
+	    } else if (bn->flags & BINF_ADDED) {
+		zwarnnam(nam, "%s: builtin is already defined", *args, 0);
+		ret = 1;
+	    } else
+		deletebuiltin(*args);
+	}
+	return ret;
+    } else if(!*args) {
+	/* list autoloaded builtins */
+	scanhashtable(builtintab, 0, 0, 0,
+	    autoloadscan, ops['L'] ? PRINT_LIST : 0);
+	return 0;
+    } else {
+	/* add autoloaded builtins */
+	char *modnam;
+	modnam = *args++;
+	if(isset(RESTRICTED) && strchr(modnam, '/')) {
+	    zwarnnam(nam, "%s: restricted", modnam, 0);
+	    return 1;
+	}
+	do {
+	    char *bnam = *args ? *args++ : modnam;
+	    if (strchr(bnam, '/')) {
+		zwarnnam(nam, "%s: `/' is illegal in a builtin", bnam, 0);
+		ret = 1;
+	    } else if (add_autobin(bnam, modnam) && !ops['i']) {
+		zwarnnam(nam, "failed to add builtin %s", bnam, 0);
+		ret = 1;
+	    }
+	} while(*args);
+	return ret;
+    }
+}
+
+/**/
+static int
+bin_zmodload_load(char *nam, char **args, char *ops)
+{
+    LinkNode node;
+    Module m;
+    int ret = 0;
+    if(ops['u']) {
+	/* unload modules */
+	for(; *args; args++) {
+	    node = find_module(*args);
+	    if (node) {
+		LinkNode mn, dn;
+
+		for (mn = firstnode(modules); mn; incnode(mn)) {
+		    m = (Module) getdata(mn);
+		    if (m->deps && m->handle)
+			for (dn = firstnode(m->deps); dn; incnode(dn))
+			    if (!strcmp((char *) getdata(dn), *args)) {
+				zwarnnam(nam, "module %s is in use by another module and cannot be unloaded", *args, 0);
+				ret = 1;
+				goto cont;
+			    }
+		}
+
+		m = (Module) getdata(node);
+		if (m->handle && cleanup_module(m))
+		    ret = 1;
+		else {
+		    if (m->handle)
+			dlclose(m->handle);
+		    m->handle = NULL;
+		    if(!m->deps) {
+			remnode(modules, node);
+			zsfree(m->nam);
+			zfree(m, sizeof(*m));
+		    }
+		}
+	    } else if (!ops['i']) {
+		zwarnnam(nam, "no such module %s", *args, 0);
+		ret = 1;
+	    }
+	    cont: ;
+	}
+	return ret;
+    } else if(!*args) {
+	/* list modules */
+	for (node = firstnode(modules); node; incnode(node)) {
+	    m = (Module) getdata(node);
+	    if (m->handle) {
+		if(ops['L']) {
+		    printf("zmodload ");
+		    if(m->nam[0] == '-')
+			fputs("-- ", stdout);
+		    quotedzputs(m->nam, stdout);
+		} else
+		    nicezputs(m->nam, stdout);
+		putchar('\n');
+	    }
+	}
+	return 0;
+    } else {
+	/* load modules */
+	for (; *args; args++) {
+	    node = find_module(*args);
+	    if (node && ((Module) getdata(node))->handle) {
+		if (!ops['i']) {
+		    zwarnnam(nam, "module %s already loaded.", *args, 0);
+		    ret = 1;
+		}
+	    } else if (isset(RESTRICTED) && strchr(*args, '/')) {
+		zwarnnam(nam, "%s: restricted", *args, 0);
+		ret = 1;
+	    } else if (!load_module(*args))
+		ret = 1;
+	}
+	return ret;
+    }
+}
+
+#endif /* DYNAMIC */
diff --git a/Src/options.c b/Src/options.c
new file mode 100644
index 000000000..745a6627c
--- /dev/null
+++ b/Src/options.c
@@ -0,0 +1,663 @@
+/*
+ * options.c - shell options
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "options.pro"
+
+/* current emulation (used to decide which set of option letters is used) */
+
+/**/
+int emulation;
+ 
+/* the options; e.g. if opts[SHGLOB] != 0, SH_GLOB is turned on */
+ 
+/**/
+char opts[OPT_SIZE];
+ 
+/* Option name hash table */
+
+/**/
+HashTable optiontab;
+ 
+typedef struct optname *Optname;
+
+struct optname {
+    HashNode next;		/* next in hash chain */
+    char *nam;			/* hash data */
+    int flags;
+    int optno;			/* option number */
+};
+
+/* The canonical option name table */
+
+#define OPT_CSH		EMULATE_CSH
+#define OPT_KSH		EMULATE_KSH
+#define OPT_SH		EMULATE_SH
+#define OPT_ZSH		EMULATE_ZSH
+
+#define OPT_ALL		(OPT_CSH|OPT_KSH|OPT_SH|OPT_ZSH)
+#define OPT_BOURNE	(OPT_KSH|OPT_SH)
+#define OPT_BSHELL	(OPT_KSH|OPT_SH|OPT_ZSH)
+#define OPT_NONBOURNE	(OPT_ALL & ~OPT_BOURNE)
+#define OPT_NONZSH	(OPT_ALL & ~OPT_ZSH)
+
+#define OPT_EMULATE	(1<<5)	/* option is relevant to emulation */
+#define OPT_SPECIAL	(1<<6)	/* option should never be set by emulate() */
+#define OPT_ALIAS	(1<<7)	/* option is an alias to an other option */
+
+#define defset(X) (!!((X)->flags & emulation))
+
+static struct optname optns[] = {
+{NULL, "allexport",	      0,			 ALLEXPORT},
+{NULL, "alwayslastprompt",    OPT_ALL,			 ALWAYSLASTPROMPT},
+{NULL, "alwaystoend",	      0,			 ALWAYSTOEND},
+{NULL, "appendhistory",	      OPT_ALL,			 APPENDHISTORY},
+{NULL, "autocd",	      0,			 AUTOCD},
+{NULL, "autolist",	      OPT_ALL,			 AUTOLIST},
+{NULL, "automenu",	      OPT_ALL,			 AUTOMENU},
+{NULL, "autonamedirs",	      0,			 AUTONAMEDIRS},
+{NULL, "autoparamkeys",	      OPT_ALL,			 AUTOPARAMKEYS},
+{NULL, "autoparamslash",      OPT_ALL,			 AUTOPARAMSLASH},
+{NULL, "autopushd",	      0,			 AUTOPUSHD},
+{NULL, "autoremoveslash",     OPT_ALL,			 AUTOREMOVESLASH},
+{NULL, "autoresume",	      0,			 AUTORESUME},
+{NULL, "badpattern",	      OPT_EMULATE|OPT_NONBOURNE, BADPATTERN},
+{NULL, "banghist",	      OPT_EMULATE|OPT_NONBOURNE, BANGHIST},
+{NULL, "bareglobqual",        OPT_EMULATE|OPT_ZSH,       BAREGLOBQUAL},
+{NULL, "beep",		      OPT_ALL,			 BEEP},
+{NULL, "bgnice",	      OPT_EMULATE|OPT_NONBOURNE, BGNICE},
+{NULL, "braceccl",	      0,			 BRACECCL},
+{NULL, "bsdecho",	      OPT_EMULATE|OPT_SH,	 BSDECHO},
+{NULL, "cdablevars",	      0,			 CDABLEVARS},
+{NULL, "chaselinks",	      0,			 CHASELINKS},
+{NULL, "clobber",	      OPT_ALL,			 CLOBBER},
+{NULL, "completealiases",     0,			 COMPLETEALIASES},
+{NULL, "completeinword",      0,			 COMPLETEINWORD},
+{NULL, "correct",	      0,			 CORRECT},
+{NULL, "correctall",	      0,			 CORRECTALL},
+{NULL, "cshjunkiehistory",    OPT_EMULATE|OPT_CSH,	 CSHJUNKIEHISTORY},
+{NULL, "cshjunkieloops",      OPT_EMULATE|OPT_CSH,	 CSHJUNKIELOOPS},
+{NULL, "cshjunkiequotes",     OPT_EMULATE|OPT_CSH,	 CSHJUNKIEQUOTES},
+{NULL, "cshnullglob",	      OPT_EMULATE|OPT_CSH,	 CSHNULLGLOB},
+{NULL, "equals",	      OPT_EMULATE|OPT_ZSH,	 EQUALS},
+{NULL, "errexit",	      0,			 ERREXIT},
+{NULL, "exec",		      OPT_ALL,			 EXECOPT},
+{NULL, "extendedglob",	      0,			 EXTENDEDGLOB},
+{NULL, "extendedhistory",     OPT_EMULATE|OPT_CSH,	 EXTENDEDHISTORY},
+{NULL, "flowcontrol",	      OPT_ALL,			 FLOWCONTROL},
+{NULL, "functionargzero",     OPT_EMULATE|OPT_NONBOURNE, FUNCTIONARGZERO},
+{NULL, "glob",		      OPT_ALL,			 GLOBOPT},
+{NULL, "globassign",	      OPT_EMULATE|OPT_CSH,	 GLOBASSIGN},
+{NULL, "globcomplete",	      0,			 GLOBCOMPLETE},
+{NULL, "globdots",	      0,			 GLOBDOTS},
+{NULL, "globsubst",	      OPT_EMULATE|OPT_NONZSH,	 GLOBSUBST},
+{NULL, "hashcmds",	      OPT_ALL,			 HASHCMDS},
+{NULL, "hashdirs",	      OPT_ALL,			 HASHDIRS},
+{NULL, "hashlistall",	      OPT_ALL,			 HASHLISTALL},
+{NULL, "histallowclobber",    0,			 HISTALLOWCLOBBER},
+{NULL, "histbeep",	      OPT_ALL,			 HISTBEEP},
+{NULL, "histignoredups",      0,			 HISTIGNOREDUPS},
+{NULL, "histignorespace",     0,			 HISTIGNORESPACE},
+{NULL, "histnofunctions",     0,			 HISTNOFUNCTIONS},
+{NULL, "histnostore",	      0,			 HISTNOSTORE},
+{NULL, "histreduceblanks",    0,			 HISTREDUCEBLANKS},
+{NULL, "histverify",	      0,			 HISTVERIFY},
+{NULL, "hup",		      OPT_EMULATE|OPT_ZSH,	 HUP},
+{NULL, "ignorebraces",	      OPT_EMULATE|OPT_SH,	 IGNOREBRACES},
+{NULL, "ignoreeof",	      0,			 IGNOREEOF},
+{NULL, "interactive",	      OPT_SPECIAL,		 INTERACTIVE},
+{NULL, "interactivecomments", OPT_EMULATE|OPT_BOURNE,	 INTERACTIVECOMMENTS},
+{NULL, "ksharrays",	      OPT_EMULATE|OPT_BOURNE,	 KSHARRAYS},
+{NULL, "kshautoload",	      OPT_EMULATE|OPT_BOURNE,	 KSHAUTOLOAD},
+{NULL, "kshglob",             OPT_EMULATE|OPT_KSH,       KSHGLOB},
+{NULL, "kshoptionprint",      OPT_EMULATE|OPT_KSH,	 KSHOPTIONPRINT},
+{NULL, "listambiguous",	      OPT_ALL,			 LISTAMBIGUOUS},
+{NULL, "listbeep",	      OPT_ALL,			 LISTBEEP},
+{NULL, "listtypes",	      OPT_ALL,			 LISTTYPES},
+{NULL, "localoptions",	      OPT_EMULATE|OPT_KSH,	 LOCALOPTIONS},
+{NULL, "login",		      OPT_SPECIAL,		 LOGINSHELL},
+{NULL, "longlistjobs",	      0,			 LONGLISTJOBS},
+{NULL, "magicequalsubst",     0,			 MAGICEQUALSUBST},
+{NULL, "mailwarning",	      0,			 MAILWARNING},
+{NULL, "markdirs",	      0,			 MARKDIRS},
+{NULL, "menucomplete",	      0,			 MENUCOMPLETE},
+{NULL, "monitor",	      OPT_SPECIAL,		 MONITOR},
+{NULL, "multios",	      OPT_EMULATE|OPT_ZSH,	 MULTIOS},
+{NULL, "nomatch",	      OPT_EMULATE|OPT_NONBOURNE, NOMATCH},
+{NULL, "notify",	      OPT_ZSH,			 NOTIFY},
+{NULL, "nullglob",	      OPT_EMULATE,		 NULLGLOB},
+{NULL, "numericglobsort",     0,			 NUMERICGLOBSORT},
+{NULL, "overstrike",	      0,			 OVERSTRIKE},
+{NULL, "pathdirs",	      0,			 PATHDIRS},
+{NULL, "posixbuiltins",	      OPT_EMULATE|OPT_BOURNE,	 POSIXBUILTINS},
+{NULL, "printeightbit",       0,                         PRINTEIGHTBIT},
+{NULL, "printexitvalue",      0,			 PRINTEXITVALUE},
+{NULL, "privileged",	      OPT_SPECIAL,		 PRIVILEGED},
+{NULL, "promptbang",	      OPT_EMULATE|OPT_KSH,	 PROMPTBANG},
+{NULL, "promptcr",	      OPT_ALL,			 PROMPTCR},
+{NULL, "promptpercent",	      OPT_EMULATE|OPT_NONBOURNE, PROMPTPERCENT},
+{NULL, "promptsubst",	      OPT_EMULATE|OPT_KSH,	 PROMPTSUBST},
+{NULL, "pushdignoredups",     0,			 PUSHDIGNOREDUPS},
+{NULL, "pushdminus",	      0,			 PUSHDMINUS},
+{NULL, "pushdsilent",	      0,			 PUSHDSILENT},
+{NULL, "pushdtohome",	      0,			 PUSHDTOHOME},
+{NULL, "rcexpandparam",	      0,			 RCEXPANDPARAM},
+{NULL, "rcquotes",	      0,			 RCQUOTES},
+{NULL, "rcs",		      OPT_ALL,			 RCS},
+{NULL, "recexact",	      0,			 RECEXACT},
+{NULL, "restricted",	      OPT_SPECIAL,		 RESTRICTED},
+{NULL, "rmstarsilent",	      OPT_BOURNE,		 RMSTARSILENT},
+{NULL, "rmstarwait",	      0,			 RMSTARWAIT},
+{NULL, "shfileexpansion",     OPT_EMULATE|OPT_BOURNE,	 SHFILEEXPANSION},
+{NULL, "shglob",	      OPT_EMULATE|OPT_BOURNE,	 SHGLOB},
+{NULL, "shinstdin",	      OPT_SPECIAL,		 SHINSTDIN},
+{NULL, "shoptionletters",     OPT_EMULATE|OPT_BOURNE,	 SHOPTIONLETTERS},
+{NULL, "shortloops",	      OPT_ALL,			 SHORTLOOPS},
+{NULL, "shwordsplit",	      OPT_EMULATE|OPT_BOURNE,	 SHWORDSPLIT},
+{NULL, "singlecommand",	      OPT_SPECIAL,		 SINGLECOMMAND},
+{NULL, "singlelinezle",	      OPT_KSH,			 SINGLELINEZLE},
+{NULL, "sunkeyboardhack",     0,			 SUNKEYBOARDHACK},
+{NULL, "unset",		      OPT_EMULATE|OPT_BSHELL,	 UNSET},
+{NULL, "verbose",	      0,			 VERBOSE},
+{NULL, "xtrace",	      0,			 XTRACE},
+{NULL, "zle",		      OPT_SPECIAL,		 USEZLE},
+{NULL, "braceexpand",	      OPT_ALIAS, /* ksh/bash */	 -IGNOREBRACES},
+{NULL, "dotglob",	      OPT_ALIAS, /* bash */	 GLOBDOTS},
+{NULL, "hashall",	      OPT_ALIAS, /* bash */	 HASHCMDS},
+{NULL, "histappend",	      OPT_ALIAS, /* bash */	 APPENDHISTORY},
+{NULL, "histexpand",	      OPT_ALIAS, /* bash */	 BANGHIST},
+{NULL, "log",		      OPT_ALIAS, /* ksh */	 -HISTNOFUNCTIONS},
+{NULL, "mailwarn",	      OPT_ALIAS, /* bash */	 MAILWARNING},
+{NULL, "onecmd",	      OPT_ALIAS, /* bash */	 SINGLECOMMAND},
+{NULL, "physical",	      OPT_ALIAS, /* ksh/bash */	 CHASELINKS},
+{NULL, "promptvars",	      OPT_ALIAS, /* bash */	 PROMPTSUBST},
+{NULL, "stdin",		      OPT_ALIAS, /* ksh */	 SHINSTDIN},
+{NULL, "trackall",	      OPT_ALIAS, /* ksh */	 HASHCMDS},
+{NULL, NULL, 0, 0}
+};
+
+/* Option letters */
+
+#define optletters (isset(SHOPTIONLETTERS) ? kshletters : zshletters)
+
+#define FIRST_OPT '0'
+#define LAST_OPT 'y'
+
+static short zshletters[LAST_OPT - FIRST_OPT + 1] = {
+    /* 0 */  CORRECT,
+    /* 1 */  PRINTEXITVALUE,
+    /* 2 */ -BADPATTERN,
+    /* 3 */ -NOMATCH,
+    /* 4 */  GLOBDOTS,
+    /* 5 */  NOTIFY,
+    /* 6 */  BGNICE,
+    /* 7 */  IGNOREEOF,
+    /* 8 */  MARKDIRS,
+    /* 9 */  AUTOLIST,
+    /* : */  0,
+    /* ; */  0,
+    /* < */  0,
+    /* = */  0,
+    /* > */  0,
+    /* ? */  0,
+    /* @ */  0,
+    /* A */  0,
+    /* B */ -BEEP,
+    /* C */ -CLOBBER,
+    /* D */  PUSHDTOHOME,
+    /* E */  PUSHDSILENT,
+    /* F */ -GLOBOPT,
+    /* G */  NULLGLOB,
+    /* H */  RMSTARSILENT,
+    /* I */  IGNOREBRACES,
+    /* J */  AUTOCD,
+    /* K */ -BANGHIST,
+    /* L */  SUNKEYBOARDHACK,
+    /* M */  SINGLELINEZLE,
+    /* N */  AUTOPUSHD,
+    /* O */  CORRECTALL,
+    /* P */  RCEXPANDPARAM,
+    /* Q */  PATHDIRS,
+    /* R */  LONGLISTJOBS,
+    /* S */  RECEXACT,
+    /* T */  CDABLEVARS,
+    /* U */  MAILWARNING,
+    /* V */ -PROMPTCR,
+    /* W */  AUTORESUME,
+    /* X */  LISTTYPES,
+    /* Y */  MENUCOMPLETE,
+    /* Z */  USEZLE,
+    /* [ */  0,
+    /* \ */  0,
+    /* ] */  0,
+    /* ^ */  0,
+    /* _ */  0,
+    /* ` */  0,
+    /* a */  ALLEXPORT,
+    /* b */  0,
+    /* c */  0,
+    /* d */  0,
+    /* e */  ERREXIT,
+    /* f */ -RCS,
+    /* g */  HISTIGNORESPACE,
+    /* h */  HISTIGNOREDUPS,
+    /* i */  INTERACTIVE,
+    /* j */  0,
+    /* k */  INTERACTIVECOMMENTS,
+    /* l */  LOGINSHELL,
+    /* m */  MONITOR,
+    /* n */ -EXECOPT,
+    /* o */  0,
+    /* p */  PRIVILEGED,
+    /* q */  0,
+    /* r */  RESTRICTED,
+    /* s */  SHINSTDIN,
+    /* t */  SINGLECOMMAND,
+    /* u */ -UNSET,
+    /* v */  VERBOSE,
+    /* w */  CHASELINKS,
+    /* x */  XTRACE,
+    /* y */  SHWORDSPLIT,
+};
+
+static short kshletters[LAST_OPT - FIRST_OPT + 1] = {
+    /* 0 */  0,
+    /* 1 */  0,
+    /* 2 */  0,
+    /* 3 */  0,
+    /* 4 */  0,
+    /* 5 */  0,
+    /* 6 */  0,
+    /* 7 */  0,
+    /* 8 */  0,
+    /* 9 */  0,
+    /* : */  0,
+    /* ; */  0,
+    /* < */  0,
+    /* = */  0,
+    /* > */  0,
+    /* ? */  0,
+    /* @ */  0,
+    /* A */  0,
+    /* B */  0,
+    /* C */ -CLOBBER,
+    /* D */  0,
+    /* E */  0,
+    /* F */  0,
+    /* G */  0,
+    /* H */  0,
+    /* I */  0,
+    /* J */  0,
+    /* K */  0,
+    /* L */  0,
+    /* M */  0,
+    /* N */  0,
+    /* O */  0,
+    /* P */  0,
+    /* Q */  0,
+    /* R */  0,
+    /* S */  0,
+    /* T */  0,
+    /* U */  0,
+    /* V */  0,
+    /* W */  0,
+    /* X */  MARKDIRS,
+    /* Y */  0,
+    /* Z */  0,
+    /* [ */  0,
+    /* \ */  0,
+    /* ] */  0,
+    /* ^ */  0,
+    /* _ */  0,
+    /* ` */  0,
+    /* a */  ALLEXPORT,
+    /* b */  NOTIFY,
+    /* c */  0,
+    /* d */  0,
+    /* e */  ERREXIT,
+    /* f */ -GLOBOPT,
+    /* g */  0,
+    /* h */  0,
+    /* i */  INTERACTIVE,
+    /* j */  0,
+    /* k */  0,
+    /* l */  LOGINSHELL,
+    /* m */  MONITOR,
+    /* n */ -EXECOPT,
+    /* o */  0,
+    /* p */  PRIVILEGED,
+    /* q */  0,
+    /* r */  RESTRICTED,
+    /* s */  SHINSTDIN,
+    /* t */  SINGLECOMMAND,
+    /* u */ -UNSET,
+    /* v */  VERBOSE,
+    /* w */  0,
+    /* x */  XTRACE,
+    /* y */  0,
+};
+
+/* Initialisation of the option name hash table */
+
+/**/
+static void
+printoptionnode(HashNode hn, int set)
+{
+    Optname on = (Optname) hn;
+    int optno = on->optno;
+
+    if (optno < 0)
+	optno = -optno;
+    if (isset(KSHOPTIONPRINT)) {
+	if (defset(on))
+	    printf("no%-20s%s\n", on->nam, isset(optno) ? "off" : "on");
+	else
+	    printf("%-22s%s\n", on->nam, isset(optno) ? "on" : "off");
+    } else if (set == (isset(optno) ^ defset(on))) {
+	if (set ^ isset(optno))
+	    fputs("no", stdout);
+	puts(on->nam);
+    }
+}
+
+/**/
+void
+createoptiontable(void)
+{
+    Optname on;
+
+    optiontab = newhashtable(101, "optiontab", NULL);
+
+    optiontab->hash        = hasher;
+    optiontab->emptytable  = NULL;
+    optiontab->filltable   = NULL;
+    optiontab->addnode     = addhashnode;
+    optiontab->getnode     = gethashnode;
+    optiontab->getnode2    = gethashnode2;
+    optiontab->removenode  = NULL;
+    optiontab->disablenode = disablehashnode;
+    optiontab->enablenode  = enablehashnode;
+    optiontab->freenode    = NULL;
+    optiontab->printnode   = printoptionnode;
+
+    for (on = optns; on->nam; on++)
+	optiontab->addnode(optiontab, on->nam, on);
+}
+
+/* Setting of default options */
+
+/**/
+static void
+setemulate(HashNode hn, int fully)
+{
+    Optname on = (Optname) hn;
+
+    /* Set options: each non-special option is set according to the *
+     * current emulation mode if either it is considered relevant   *
+     * to emulation or we are doing a full emulation (as indicated  *
+     * by the `fully' parameter).                                   */
+    if (!(on->flags & OPT_ALIAS) &&
+	((fully && !(on->flags & OPT_SPECIAL)) ||
+	 (on->flags & OPT_EMULATE)))
+	opts[on->optno] = defset(on);
+}
+
+/**/
+void
+emulate(const char *zsh_name, int fully)
+{
+    char ch = *zsh_name;
+
+    if (ch == 'r')
+	ch = zsh_name[1];
+
+    /* Work out the new emulation mode */
+    if (ch == 'c')
+	emulation = EMULATE_CSH;
+    else if (ch == 'k')
+	emulation = EMULATE_KSH;
+    else if (ch == 's' || ch == 'b')
+	emulation = EMULATE_SH;
+    else
+	emulation = EMULATE_ZSH;
+
+    scanhashtable(optiontab, 0, 0, 0, setemulate, fully);
+}
+
+/* setopt, unsetopt */
+
+/**/
+static void
+setoption(HashNode hn, int value)
+{
+    dosetopt(((Optname) hn)->optno, value, 0);
+}
+
+/**/
+int
+bin_setopt(char *nam, char **args, char *ops, int isun)
+{
+    int action, optno, match = 0;
+
+    /* With no arguments or options, display options. */
+    if (!*args) {
+	scanhashtable(optiontab, 1, 0, OPT_ALIAS, optiontab->printnode, !isun);
+	return 0;
+    }
+
+    /* loop through command line options (begins with "-" or "+") */
+    while (*args && (**args == '-' || **args == '+')) {
+	action = (**args == '-') ^ isun;
+	if(!args[0][1])
+	    *args = "--";
+	while (*++*args) {
+	    if(**args == Meta)
+		*++*args ^= 32;
+	    /* The pseudo-option `--' signifies the end of options. */
+	    if (**args == '-') {
+		args++;
+		goto doneoptions;
+	    } else if (**args == 'o') {
+		if (!*++*args)
+		    args++;
+		if (!*args) {
+		    zwarnnam(nam, "string expected after -o", NULL, 0);
+		    inittyptab();
+		    return 1;
+		}
+		if(!(optno = optlookup(*args)))
+		    zwarnnam(nam, "no such option: %s", *args, 0);
+		else if(dosetopt(optno, action, 0))
+		    zwarnnam(nam, "can't change option: %s", *args, 0);
+		break;
+	    } else if(**args == 'm') {
+		match = 1;
+	    } else {
+	    	if (!(optno = optlookupc(**args)))
+		    zwarnnam(nam, "bad option: -%c", NULL, **args);
+		else if(dosetopt(optno, action, 0))
+		    zwarnnam(nam, "can't change option: -%c", NULL, **args);
+	    }
+	}
+	args++;
+    }
+    doneoptions:
+
+    if (!match) {
+	/* Not globbing the arguments -- arguments are simply option names. */
+	while (*args) {
+	    if(!(optno = optlookup(*args++)))
+		zwarnnam(nam, "no such option: %s", args[-1], 0);
+	    else if(dosetopt(optno, !isun, 0))
+		zwarnnam(nam, "can't change option: %s", args[-1], 0);
+	}
+    } else {
+	/* Globbing option (-m) set. */
+	while (*args) {
+	    Comp com;
+
+	    /* Expand the current arg. */
+	    tokenize(*args);
+	    if (!(com = parsereg(*args))) {
+		untokenize(*args);
+		zwarnnam(nam, "bad pattern: %s", *args, 0);
+		continue;
+	    }
+	    /* Loop over expansions. */
+	    scanmatchtable(optiontab, com, 0, OPT_ALIAS, setoption, !isun);
+	    args++;
+	}
+    }
+    inittyptab();
+    return 0;
+}
+
+/* Identify an option name */
+
+/**/
+int
+optlookup(char const *name)
+{
+    char *s, *t;
+    Optname n;
+
+    s = t = dupstring(name);
+
+    /* exorcise underscores, and change to lowercase */
+    while (*t)
+	if (*t == '_')
+	    chuck(t);
+	else {
+	    *t = tulower(*t);
+	    t++;
+	}
+
+    /* look up name in the table */
+    if (s[0] == 'n' && s[1] == 'o' &&
+	(n = (Optname) optiontab->getnode(optiontab, s + 2))) {
+	return -n->optno;
+    } else if ((n = (Optname) optiontab->getnode(optiontab, s)))
+	return n->optno;
+    else
+	return OPT_INVALID;
+}
+
+/* Identify an option letter */
+
+/**/
+int
+optlookupc(char c)
+{
+    if(c < FIRST_OPT || c > LAST_OPT)
+	return 0;
+
+    return optletters[c - FIRST_OPT];
+}
+
+/**/
+static void
+restrictparam(char *nam)
+{
+    Param pm = (Param) paramtab->getnode(paramtab, nam);
+
+    if (pm) {
+	pm->flags |= PM_SPECIAL | PM_RESTRICTED;
+	return;
+    }
+    createparam(nam, PM_SCALAR | PM_UNSET | PM_SPECIAL | PM_RESTRICTED);
+}
+
+/* list of restricted parameters which are not otherwise special */
+static char *rparams[] = {
+    "SHELL", "HISTFILE", "LD_LIBRARY_PATH", "LD_AOUT_LIBRARY_PATH",
+    "LD_PRELOAD", "LD_AOUT_PRELOAD", NULL
+};
+
+/* Set or unset an option, as a result of user request.  The option *
+ * number may be negative, indicating that the sense is reversed    *
+ * from the usual meaning of the option.                            */
+
+/**/
+int
+dosetopt(int optno, int value, int force)
+{
+    if(!optno)
+	return -1;
+    if(optno < 0) {
+	optno = -optno;
+	value = !value;
+    }
+    if (optno == RESTRICTED) {
+	if (isset(RESTRICTED))
+	    return value ? 0 : -1;
+	if (value) {
+	    char **s;
+
+	    for (s = rparams; *s; s++)
+		restrictparam(*s);
+	}
+    } else if(!force && (optno == INTERACTIVE || optno == SHINSTDIN ||
+	    optno == SINGLECOMMAND)) {
+	/* it is not permitted to change the value of these options */
+	return -1;
+    } else if(!force && optno == USEZLE && value) {
+	/* we require a terminal in order to use ZLE */
+	if(!interact || SHTTY == -1 || !shout)
+	    return -1;
+    } else if(optno == PRIVILEGED && !value) {
+	/* unsetting PRIVILEGED causes the shell to make itself unprivileged */
+#ifdef HAVE_SETUID
+	setuid(getuid());
+	setgid(getgid());
+#endif /* HAVE_SETUID */
+    }
+    opts[optno] = value;
+    if (optno == BANGHIST || optno == SHINSTDIN)
+	inittyptab();
+    return 0;
+}
+
+/* Function to get value for special parameter `-' */
+
+/**/
+char *
+dashgetfn(Param pm)
+{
+    static char buf[LAST_OPT - FIRST_OPT + 2];
+    char *val = buf;
+    int i;
+
+    for(i = 0; i <= LAST_OPT - FIRST_OPT; i++) {
+	int optno = optletters[i];
+	if(optno && ((optno > 0) ? isset(optno) : unset(-optno)))
+	    *val++ = FIRST_OPT + i;
+    }
+    *val = '\0';
+    return buf;
+}
diff --git a/Src/params.c b/Src/params.c
new file mode 100644
index 000000000..4f7846820
--- /dev/null
+++ b/Src/params.c
@@ -0,0 +1,2191 @@
+/*
+ * params.c - parameters
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "params.pro"
+
+#include "version.h"
+
+/* what level of localness we are at */
+ 
+/**/
+int locallevel;
+ 
+/* Variables holding values of special parameters */
+ 
+/**/
+char **pparams,		/* $argv        */
+     **cdpath,		/* $cdpath      */
+     **fignore,		/* $fignore     */
+     **fpath,		/* $fpath       */
+     **mailpath,	/* $mailpath    */
+     **manpath,		/* $manpath     */
+     **path,		/* $path        */
+     **psvar,		/* $psvar       */
+     **watch;		/* $watch       */
+ 
+/**/
+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    */
+
+/**/
+long lastval,		/* $?           */
+     mypid,		/* $$           */
+     lastpid,		/* $!           */
+     columns,		/* $COLUMNS     */
+     lineno,		/* $LINENO      */
+     lines,		/* $LINES       */
+     zoptind,		/* $OPTIND      */
+     ppid,		/* $PPID        */
+     shlvl;		/* $SHLVL       */
+
+/* $histchars */
+ 
+/**/
+unsigned char bangchar, hatchar, hashchar;
+ 
+/* $SECONDS = time(NULL) - shtimer.tv_sec */
+ 
+/**/
+struct timeval shtimer;
+ 
+/* 0 if this $TERM setup is usable, otherwise it contains TERM_* flags */
+
+/**/
+int termflags;
+ 
+/* Nodes for special parameters for parameter hash table */
+
+static
+#ifdef HAVE_UNION_INIT
+# define BR(X) {X}
+struct param
+#else
+# define BR(X) X
+struct iparam {
+    struct hashnode *next;
+    char *nam;			/* hash data                             */
+    int flags;			/* PM_* flags (defined in zsh.h)         */
+    void *value;
+    void (*func1) _((void));	/* set func                              */
+    char *(*func2) _((void));	/* get func                              */
+    void (*unsetfn) _((Param, int));	/* unset func                    */
+    int ct;			/* output base or field width            */
+    char *env;			/* location in environment, if exported  */
+    char *ename;		/* name of corresponding environment var */
+    Param old;			/* old struct for use with local         */
+    int level;			/* if (old != NULL), level of localness  */
+}
+#endif
+special_params[] ={
+#define SFN(X) BR(((void (*)_((Param, char *)))(X)))
+#define GFN(X) BR(((char *(*)_((Param)))(X)))
+#define IPDEF1(A,B,C,D) {NULL,A,PM_INTEGER|PM_SPECIAL|D,BR(NULL),SFN(C),GFN(B),stdunsetfn,10,NULL,NULL,NULL,0}
+IPDEF1("#", poundgetfn, nullsetfn, PM_READONLY),
+IPDEF1("ERRNO", errnogetfn, nullsetfn, PM_READONLY),
+IPDEF1("GID", gidgetfn, gidsetfn, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF1("EGID", egidgetfn, egidsetfn, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF1("HISTSIZE", histsizegetfn, histsizesetfn, PM_RESTRICTED),
+IPDEF1("RANDOM", randomgetfn, randomsetfn, 0),
+IPDEF1("SECONDS", secondsgetfn, secondssetfn, 0),
+IPDEF1("UID", uidgetfn, uidsetfn, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF1("EUID", euidgetfn, euidsetfn, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF1("TTYIDLE", ttyidlegetfn, nullsetfn, PM_READONLY),
+
+#define IPDEF2(A,B,C,D) {NULL,A,PM_SCALAR|PM_SPECIAL|D,BR(NULL),SFN(C),GFN(B),stdunsetfn,0,NULL,NULL,NULL,0}
+IPDEF2("USERNAME", usernamegetfn, usernamesetfn, PM_DONTIMPORT|PM_RESTRICTED),
+IPDEF2("-", dashgetfn, nullsetfn, PM_READONLY),
+IPDEF2("histchars", histcharsgetfn, histcharssetfn, PM_DONTIMPORT),
+IPDEF2("HOME", homegetfn, homesetfn, 0),
+IPDEF2("TERM", termgetfn, termsetfn, 0),
+IPDEF2("WORDCHARS", wordcharsgetfn, wordcharssetfn, 0),
+IPDEF2("IFS", ifsgetfn, ifssetfn, PM_DONTIMPORT),
+IPDEF2("_", underscoregetfn, nullsetfn, PM_READONLY),
+
+#ifdef LC_ALL
+# define LCIPDEF(name) IPDEF2(name, strgetfn, lcsetfn, PM_UNSET)
+IPDEF2("LANG", strgetfn, langsetfn, PM_UNSET),
+IPDEF2("LC_ALL", strgetfn, lc_allsetfn, PM_UNSET),
+# ifdef LC_COLLATE
+LCIPDEF("LC_COLLATE"),
+# endif
+# ifdef LC_CTYPE
+LCIPDEF("LC_CTYPE"),
+# endif
+# ifdef LC_MESSAGES
+LCIPDEF("LC_MESSAGES"),
+# endif
+# ifdef LC_TIME
+LCIPDEF("LC_TIME"),
+# endif
+#endif
+
+#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),
+IPDEF4("$", &mypid),
+IPDEF4("?", &lastval),
+IPDEF4("LINENO", &lineno),
+IPDEF4("PPID", &ppid),
+
+#define IPDEF5(A,B,F) {NULL,A,PM_INTEGER|PM_SPECIAL,BR((void *)B),SFN(F),GFN(intvargetfn),stdunsetfn,10,NULL,NULL,NULL,0}
+IPDEF5("COLUMNS", &columns, zlevarsetfn),
+IPDEF5("LINES", &lines, zlevarsetfn),
+IPDEF5("OPTIND", &zoptind, intvarsetfn),
+IPDEF5("SHLVL", &shlvl, intvarsetfn),
+
+#define IPDEF7(A,B) {NULL,A,PM_SCALAR|PM_SPECIAL,BR((void *)B),SFN(strvarsetfn),GFN(strvargetfn),stdunsetfn,0,NULL,NULL,NULL,0}
+IPDEF7("OPTARG", &zoptarg),
+IPDEF7("NULLCMD", &nullcmd),
+IPDEF7("POSTEDIT", &postedit),
+IPDEF7("READNULLCMD", &readnullcmd),
+IPDEF7("RPROMPT", &rprompt),
+IPDEF7("PS1", &prompt),
+IPDEF7("PS2", &prompt2),
+IPDEF7("PS3", &prompt3),
+IPDEF7("PS4", &prompt4),
+IPDEF7("RPS1", &rprompt),
+IPDEF7("SPROMPT", &sprompt),
+IPDEF7("0", &argzero),
+
+#define IPDEF8(A,B,C,D) {NULL,A,D|PM_SCALAR|PM_SPECIAL,BR((void *)B),SFN(colonarrsetfn),GFN(colonarrgetfn),stdunsetfn,0,NULL,C,NULL,0}
+IPDEF8("CDPATH", &cdpath, "cdpath", 0),
+IPDEF8("FIGNORE", &fignore, "fignore", 0),
+IPDEF8("FPATH", &fpath, "fpath", 0),
+IPDEF8("MAILPATH", &mailpath, "mailpath", 0),
+IPDEF8("WATCH", &watch, "watch", 0),
+IPDEF8("PATH", &path, "path", PM_RESTRICTED),
+IPDEF8("PSVAR", &psvar, "psvar", 0),
+
+#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},
+
+/* The following parameters are not avaible in sh/ksh compatibility *
+ * mode. All of these has sh compatible equivalents.                */
+IPDEF1("ARGC", poundgetfn, nullsetfn, PM_READONLY),
+IPDEF2("HISTCHARS", histcharsgetfn, histcharssetfn, PM_DONTIMPORT),
+IPDEF4("status", &lastval),
+IPDEF7("prompt", &prompt),
+IPDEF7("PROMPT", &prompt),
+IPDEF7("PROMPT2", &prompt2),
+IPDEF7("PROMPT3", &prompt3),
+IPDEF7("PROMPT4", &prompt4),
+IPDEF8("MANPATH", &manpath, "manpath", 0),
+IPDEF9("argv", &pparams, NULL),
+IPDEF9("fignore", &fignore, "FIGNORE"),
+IPDEF9("cdpath", &cdpath, "CDPATH"),
+IPDEF9("fpath", &fpath, "FPATH"),
+IPDEF9("mailpath", &mailpath, "MAILPATH"),
+IPDEF9("manpath", &manpath, "MANPATH"),
+IPDEF9("psvar", &psvar, "PSVAR"),
+IPDEF9("watch", &watch, "WATCH"),
+
+#ifdef DYNAMIC
+IPDEF9F("module_path", &module_path, "MODULE_PATH", PM_RESTRICTED),
+#endif
+IPDEF9F("path", &path, "PATH", PM_RESTRICTED),
+
+{NULL, NULL}
+};
+#undef BR
+
+static Param argvparam;
+
+/* hash table containing the parameters */
+ 
+/**/
+HashTable paramtab;
+ 
+/* Set up parameter hash table.  This will add predefined  *
+ * parameter entries as well as setting up parameter table *
+ * entries for environment variables we inherit.           */
+
+/**/
+void
+createparamtable(void)
+{
+    Param ip, pm;
+    char **new_environ, **envp, **envp2, **sigptr, **t;
+    char buf[50], *str, *iname;
+    int num_env;
+
+    paramtab = newhashtable(151, "paramtab", NULL);
+
+    paramtab->hash        = hasher;
+    paramtab->emptytable  = NULL;
+    paramtab->filltable   = NULL;
+    paramtab->addnode     = addhashnode;
+    paramtab->getnode     = gethashnode2;
+    paramtab->getnode2    = gethashnode2;
+    paramtab->removenode  = removehashnode;
+    paramtab->disablenode = NULL;
+    paramtab->enablenode  = NULL;
+    paramtab->freenode    = freeparamnode;
+    paramtab->printnode   = printparamnode;
+
+    /* Add the special parameters to the hash table */
+    for (ip = special_params; ip->nam; ip++)
+	paramtab->addnode(paramtab, ztrdup(ip->nam), ip);
+    if (emulation != EMULATE_SH && emulation != EMULATE_KSH)
+	while ((++ip)->nam)
+	    paramtab->addnode(paramtab, ztrdup(ip->nam), ip);
+
+    argvparam = (Param) 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);
+#ifdef HAVE_SELECT
+	setiparam("BAUD", getbaudrate(&shttyinfo));  /* get the output baudrate */
+#endif
+	setsparam("FCEDIT", ztrdup(DEFAULT_FCEDIT));
+	setsparam("TMPPREFIX", ztrdup(DEFAULT_TMPPREFIX));
+	setsparam("TIMEFMT", ztrdup(DEFAULT_TIMEFMT));
+	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));
+		    }
+		}
+		*str = '=';
+	    }
+	}
+	environ = new_environ;
+
+	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;
+
+    noerrs = 0;
+}
+
+/* Create a parameter, so that it can be assigned to.  Returns NULL if the *
+ * parameter already exists or can't be created, otherwise returns the     *
+ * parameter node.  If a parameter of the same name exists in an outer     *
+ * scope, it is hidden by a newly created parameter.  An already existing  *
+ * parameter node at the current level may be `created' and returned       *
+ * provided it is unset and not special.  If the parameter can't be        *
+ * created because it already exists, the PM_UNSET flag is cleared.        */
+
+/**/
+Param
+createparam(char *name, int flags)
+{
+    Param pm, oldpm;
+
+    if (name != nulstring) {
+	oldpm = (Param) paramtab->getnode(paramtab, name);
+
+	if (oldpm && oldpm->level == locallevel) {
+	    if (!(oldpm->flags & PM_UNSET) || (oldpm->flags & PM_SPECIAL)) {
+		oldpm->flags &= ~PM_UNSET;
+		return NULL;
+	    }
+	    if ((oldpm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+		zerr("%s: restricted", name, 0);
+		return NULL;
+	    }
+
+	    pm = oldpm;
+	    pm->ct = 0;
+	    oldpm = pm->old;
+	} else {
+	    pm = (Param) zcalloc(sizeof *pm);
+	    if ((pm->old = oldpm)) {
+		/* needed to avoid freeing oldpm */
+		paramtab->removenode(paramtab, name);
+	    }
+	    paramtab->addnode(paramtab, ztrdup(name), pm);
+	}
+
+	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;
+    }
+    return pm;
+}
+
+/* Return 1 if the string s is a valid identifier, else return 0. */
+
+/**/
+int
+isident(char *s)
+{
+    char *ss;
+    int ne;
+
+    ne = noeval;		/* save the current value of noeval     */
+    if (!*s)			/* empty string is definitely not valid */
+	return 0;
+
+    /* find the first character in `s' not in the iident type table */
+    for (ss = s; *ss; ss++)
+	if (!iident(*ss))
+	    break;
+
+    /* If this exhaust `s' or the next two characters *
+     * are [(, then it is a valid identifier.         */
+    if (!*ss || (*ss == '[' && ss[1] == '('))
+	return 1;
+
+    /* Else if the next character is not [, then it is *
+     * definitely not a valid identifier.              */
+    if (*ss != '[')
+	return 0;
+    noeval = 1;
+    (void)mathevalarg(++ss, &ss);
+    if (*ss == ',')
+	(void)mathevalarg(++ss, &ss);
+    noeval = ne;		/* restore the value of noeval */
+    if (*ss != ']' || ss[1])
+	return 0;
+    return 1;
+}
+
+static char **garr;
+
+/**/
+static long
+getarg(char **str, int *inv, Value v, int a2, long *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;
+
+    /* first parse any subscription flags */
+    if (*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;
+		break;
+	    case 'R':
+		rev = down = 1;
+		ind = 0;
+		break;
+	    case 'i':
+		rev = ind = 1;
+		down = 0;
+		break;
+	    case 'I':
+		rev = ind = down = 1;
+		break;
+	    case 'w':
+		/* If the parameter is a scalar, then make subscription *
+		 * work on a per-word basis instead of characters.      */
+		word = 1;
+		break;
+	    case 'f':
+		word = 1;
+		sep = "\n";
+		break;
+	    case 'e':
+		/* obsolate compatibility flag without any real effect */
+		break;
+	    case 'n':
+		t = get_strarg(++s);
+		if (!*t)
+		    goto flagerr;
+		sav = *t;
+		*t = '\0';
+		num = mathevalarg(s + 1, &d);
+		if (!num)
+		    num = 1;
+		*t = sav;
+		s = t;
+		break;
+	    case 'p':
+		escapes = 1;
+		break;
+	    case 's':
+		/* This gives the string that separates words *
+		 * (for use with the `w' flag.                */
+		t = get_strarg(++s);
+		if (!*t)
+		    goto flagerr;
+		sav = *t;
+		*t = '\0';
+		sep = escapes ? getkeystring(s + 1, &waste, 1, &waste) :
+				dupstring(s + 1);
+		*t = sav;
+		s = t;
+		break;
+	    default:
+	      flagerr:
+		num = 1;
+		word = rev = ind = down = 0;
+		sep = NULL;
+		s = *str - 1;
+	    }
+	}
+	if (s != *str)
+	    s++;
+    }
+    if (num < 0) {
+	down = !down;
+	num = -num;
+    }
+    *inv = ind;
+
+    for (t=s, i=0; *t && ((*t != ']' && *t != Outbrack && *t != ',') || i); t++)
+	if (*t == '[' || *t == Inbrack)
+	    i++;
+	else if (*t == ']' || *t == Outbrack)
+	    i--;
+
+    if (!*t)
+	return 0;
+    s = dupstrpfx(s, t - s);
+    *str = tt = t;
+    if (parsestr(s))
+	return 0;
+    singsub(&s);
+
+    if (!rev) {
+	if (!(r = mathevalarg(s, &s)) || (isset(KSHARRAYS) && r >= 0))
+	    r++;
+	if (word && !v->isarr) {
+	    s = t = getstrvalue(v);
+	    i = wordcount(s, sep, 0);
+	    if (r < 0)
+		r += i + 1;
+	    if (r < 1)
+		r = 1;
+	    if (r > i)
+		r = i;
+	    if (!s || !*s)
+		return 0;
+	    while ((d = findword(&s, sep)) && --r);
+	    if (!d)
+		return 0;
+
+	    if (!a2 && *tt != ',')
+		*w = (long)(s - t) - 1;
+
+	    return (a2 ? s : d + 1) - t;
+	} else if (!v->isarr && !word) {
+	    s = getstrvalue(v);
+	    if (r > 0) {
+		for (t = s + r - 1; *s && s < t;)
+		    if (*s++ == Meta)
+			s++, t++, r++;
+	    } else {
+		r += ztrlen(s);
+		for (t = s + r; *s && s < t; r--)
+		    if (*s++ == Meta)
+			t++, r++;
+		r -= strlen(s);
+	    }
+	}
+    } else {
+	if (!v->isarr && !word) {
+	    l = strlen(s);
+	    if (a2) {
+		if (!l || *s != '*') {
+		    d = (char *) ncalloc(l + 2);
+		    *d = '*';
+		    strcpy(d + 1, s);
+		    s = d;
+		}
+	    } else {
+		if (!l || s[l - 1] != '*') {
+		    d = (char *) ncalloc(l + 2);
+		    strcpy(d, s);
+		    strcat(d, "*");
+		    s = d;
+		}
+	    }
+	}
+	tokenize(s);
+
+	if ((c = parsereg(s))) {
+	    if (v->isarr) {
+		ta = getarrvalue(v);
+		if (!ta || !*ta)
+		    return 0;
+		if (down)
+		    for (r = -1, p = ta + arrlen(ta) - 1; p >= ta; r--, p--) {
+			if (domatch(*p, c, 0) && !--num)
+			    return r;
+		} else
+		    for (r = 1, p = ta; *p; r++, p++)
+			if (domatch(*p, c, 0) && !--num)
+			    return r;
+	    } else if (word) {
+		ta = sepsplit(d = s = getstrvalue(v), sep, 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;
+		}
+		if (a2)
+		    r++;
+		for (i = 0; (t = findword(&d, sep)) && *t; i++)
+		    if (!--r) {
+			r = (long)(t - s + (a2 ? -1 : 1));
+			if (!a2 && *tt != ',')
+			    *w = r + strlen(ta[i]) - 2;
+			return r;
+		    }
+		return a2 ? -1 : 0;
+	    } else {
+		d = getstrvalue(v);
+		if (!d || !*d)
+		    return 0;
+		if (a2) {
+		    if (down)
+			for (r = -2, t = d + strlen(d) - 1; t >= d; r--, t--) {
+			    sav = *t;
+			    *t = '\0';
+			    if (domatch(d, c, 0) && !--num) {
+				*t = sav;
+				return r;
+			    }
+			    *t = sav;
+		    } else
+			for (r = 0, t = d; *t; r++, t++) {
+			    sav = *t;
+			    *t = '\0';
+			    if (domatch(d, c, 0) && !--num) {
+				*t = sav;
+				return r;
+			    }
+			    *t = sav;
+			}
+		} else {
+		    if (down)
+			for (r = -1, t = d + strlen(d) - 1; t >= d; r--, t--) {
+			    if (domatch(t, c, 0) && !--num)
+				return r;
+		    } else
+			for (r = 1, t = d; *t; r++, t++)
+			    if (domatch(t, c, 0) && !--num)
+				return r;
+		}
+		return 0;
+	    }
+	}
+    }
+    return r;
+}
+
+/**/
+int
+getindex(char **pptr, Value v)
+{
+    int a, b, inv = 0;
+    char *s = *pptr, *tbrack;
+
+    *s++ = '[';
+    for (tbrack = s; *tbrack && *tbrack != ']' && *tbrack != Outbrack; tbrack++)
+	if (itok(*tbrack))
+	    *tbrack = ztokens[*tbrack - Pound];
+    if (*tbrack == Outbrack)
+	*tbrack = ']';
+    if ((s[0] == '*' || s[0] == '@') && s[1] == ']') {
+	if (v->isarr)
+	    v->isarr = (s[0] == '*') ? 1 : -1;
+	v->a = 0;
+	v->b = -1;
+	s += 2;
+    } else {
+	long we = 0, dummy;
+
+	a = getarg(&s, &inv, v, 0, &we);
+
+	if (inv) {
+	    if (!v->isarr && a != 0) {
+		char *t, *p;
+		t = getstrvalue(v);
+		if (a > 0) {
+		    for (p = t + a - 1; p-- > t; )
+			if (*p == Meta)
+			    a--;
+		} else
+		    a = -ztrlen(t + a + strlen(t));
+	    }
+	    if (a > 0 && isset(KSHARRAYS))
+		a--;
+	    v->inv = 1;
+	    v->isarr = 0;
+	    v->a = v->b = a;
+	    if (*s == ',') {
+		zerr("invalid subscript", NULL, 0);
+		while (*s != ']' && *s != Outbrack)
+		    s++;
+		*pptr = s;
+		return 1;
+	    }
+	    if (*s == ']' || *s == Outbrack)
+		s++;
+	} else {
+	    if (a > 0)
+		a--;
+	    if (*s == ',') {
+		s++;
+		b = getarg(&s, &inv, v, 1, &dummy);
+		if (b > 0)
+		    b--;
+	    } else {
+		b = we ? we : a;
+	    }
+	    if (*s == ']' || *s == Outbrack) {
+		s++;
+		if (v->isarr && a == b)
+		    v->isarr = 0;
+		v->a = a;
+		v->b = b;
+	    } else
+		s = *pptr;
+	}
+    }
+    *pptr = s;
+    return 0;
+}
+
+
+/**/
+Value
+getvalue(char **pptr, int bracks)
+{
+    char *s, *t;
+    char sav;
+    Value v;
+    int ppar = 0;
+
+    s = t = *pptr;
+    garr = NULL;
+
+    if (idigit(*s))
+	if (bracks >= 0)
+	    ppar = zstrtol(s, &s, 10);
+	else
+	    ppar = *s++ - '0';
+    else if (iident(*s))
+	while (iident(*s))
+	    s++;
+    else if (*s == Quest)
+	*s++ = '?';
+    else if (*s == Pound)
+	*s++ = '#';
+    else if (*s == String)
+	*s++ = '$';
+    else if (*s == Qstring)
+	*s++ = '$';
+    else if (*s == Star)
+	*s++ = '*';
+    else if (*s == '#' || *s == '-' || *s == '?' || *s == '$' ||
+	     *s == '_' || *s == '!' || *s == '@' || *s == '*')
+	s++;
+    else
+	return NULL;
+
+    if ((sav = *s))
+	*s = '\0';
+    if (ppar) {
+	v = (Value) hcalloc(sizeof *v);
+	v->pm = argvparam;
+	v->inv = 0;
+	v->a = v->b = ppar - 1;
+	if (sav)
+	    *s = sav;
+    } else {
+	Param pm;
+	int isvarat;
+
+        isvarat = !strcmp(t, "@");
+	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;
+	v->pm = pm;
+	v->inv = 0;
+	v->a = 0;
+	v->b = -1;
+	if (bracks > 0 && (*s == '[' || *s == Inbrack)) {
+	    if (getindex(&s, v)) {
+		*pptr = s;
+		return v;
+	    }
+	} else if (v->isarr && iident(*t) && isset(KSHARRAYS))
+	    v->b = 0, v->isarr = 0;
+    }
+    if (!bracks && *s)
+	return NULL;
+    *pptr = s;
+    if (v->a > MAX_ARRLEN ||
+	v->a < -MAX_ARRLEN) {
+	zerr("subscript to %s: %d", (v->a < 0) ? "small" : "big", v->a);
+	return NULL;
+    }
+    if (v->b > MAX_ARRLEN ||
+	v->b < -MAX_ARRLEN) {
+	zerr("subscript to %s: %d", (v->b < 0) ? "small" : "big", v->b);
+	return NULL;
+    }
+    return v;
+}
+
+/**/
+char *
+getstrvalue(Value v)
+{
+    char *s, **ss;
+    static char buf[(sizeof(long) * 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->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;
+}
+
+static char *nular[] = {"", NULL};
+
+/**/
+char **
+getarrvalue(Value v)
+{
+    char **s;
+
+    if (!v)
+	return arrdup(nular);
+    if (v->inv) {
+	char buf[DIGBUFSIZE];
+
+	s = arrdup(nular);
+	sprintf(buf, "%d", v->a);
+	s[0] = dupstring(buf);
+	return s;
+    }
+    s = v->pm->gets.afn(v->pm);
+    if (v->a == 0 && v->b == -1)
+	return s;
+    if (v->a < 0)
+	v->a += arrlen(s);
+    if (v->b < 0)
+	v->b += arrlen(s);
+    if (v->a > arrlen(s) || v->a < 0)
+	s = arrdup(nular);
+    else
+	s = arrdup(s) + v->a;
+    if (v->b < v->a)
+	s[0] = NULL;
+    else if (v->b - v->a < arrlen(s))
+	s[v->b - v->a + 1] = NULL;
+    return s;
+}
+
+/**/
+long
+getintvalue(Value v)
+{
+    if (!v || v->isarr)
+	return 0;
+    if (v->inv)
+	return v->a;
+    if (PM_TYPE(v->pm->flags) == PM_INTEGER)
+	return v->pm->gets.ifn(v->pm);
+    return matheval(getstrvalue(v));
+}
+
+/**/
+static void
+setstrvalue(Value v, char *val)
+{
+    char buf[(sizeof(long) * 8) + 4];
+
+    if (v->pm->flags & PM_READONLY) {
+	zerr("read-only variable: %s", v->pm->nam, 0);
+	zsfree(val);
+	return;
+    }
+    if ((v->pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+	zerr("%s: restricted", v->pm->nam, 0);
+	zsfree(val);
+	return;
+    }
+    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)
+		v->pm->ct = strlen(val);
+	} else {
+	    char *z, *x;
+	    int zlen;
+
+	    z = dupstring((v->pm->gets.cfn) (v->pm));
+	    zlen = strlen(z);
+	    if (v->inv && unset(KSHARRAYS))
+		v->a--, v->b--;
+	    if (v->a < 0) {
+		v->a += zlen;
+		if (v->a < 0)
+		    v->a = 0;
+	    }
+	    if (v->a > zlen)
+		v->a = zlen;
+	    if (v->b < 0)
+		v->b += zlen;
+	    if (v->b > zlen - 1)
+		v->b = zlen - 1;
+	    x = (char *) zalloc(v->a + strlen(val) + zlen - v->b);
+	    strncpy(x, z, v->a);
+	    strcpy(x + v->a, val);
+	    strcat(x + v->a, z + v->b + 1);
+	    (v->pm->sets.cfn) (v->pm, x);
+	    zsfree(val);
+	}
+	break;
+    case PM_INTEGER:
+	if (val) {
+	    (v->pm->sets.ifn) (v->pm, matheval(val));
+	    zsfree(val);
+	}
+	if (!v->pm->ct && lastbase != -1)
+	    v->pm->ct = lastbase;
+	break;
+    case PM_ARRAY:
+	MUSTUSEHEAP("setstrvalue");
+	{
+	    char **ss = (char **) zalloc(2 * sizeof(char *));
+
+	    ss[0] = val;
+	    ss[1] = NULL;
+	    setarrvalue(v, ss);
+	}
+	break;
+    }
+    if ((!v->pm->env && !(v->pm->flags & PM_EXPORTED) &&
+	 !(isset(ALLEXPORT) && !v->pm->old)) ||
+	(v->pm->flags & PM_ARRAY) || v->pm->ename)
+	return;
+    if (PM_TYPE(v->pm->flags) == PM_INTEGER)
+	convbase(val = buf, v->pm->gets.ifn(v->pm), v->pm->ct);
+    else
+	val = v->pm->gets.cfn(v->pm);
+    if (v->pm->env)
+	v->pm->env = replenv(v->pm->env, val);
+    else {
+	v->pm->flags |= PM_EXPORTED;
+	v->pm->env = addenv(v->pm->nam, val);
+    }
+}
+
+/**/
+static void
+setintvalue(Value v, long val)
+{
+    char buf[DIGBUFSIZE];
+
+    if (v->pm->flags & PM_READONLY) {
+	zerr("read-only variable: %s", v->pm->nam, 0);
+	return;
+    }
+    if ((v->pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+	zerr("%s: restricted", v->pm->nam, 0);
+	return;
+    }
+    switch (PM_TYPE(v->pm->flags)) {
+    case PM_SCALAR:
+    case PM_ARRAY:
+	sprintf(buf, "%ld", val);
+	setstrvalue(v, ztrdup(buf));
+	break;
+    case PM_INTEGER:
+	(v->pm->sets.ifn) (v->pm, val);
+	setstrvalue(v, NULL);
+	break;
+    }
+}
+
+/**/
+static void
+setarrvalue(Value v, char **val)
+{
+    if (v->pm->flags & PM_READONLY) {
+	zerr("read-only variable: %s", v->pm->nam, 0);
+	freearray(val);
+	return;
+    }
+    if ((v->pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+	zerr("%s: restricted", v->pm->nam, 0);
+	freearray(val);
+	return;
+    }
+    if (PM_TYPE(v->pm->flags) != PM_ARRAY) {
+	freearray(val);
+	zerr("attempt to assign array value to non-array", NULL, 0);
+	return;
+    }
+    if (v->a == 0 && v->b == -1)
+	(v->pm->sets.afn) (v->pm, val);
+    else {
+	char **old, **new, **p, **q, **r;
+	int n, ll, i;
+
+	if (v->inv && unset(KSHARRAYS))
+	    v->a--, v->b--;
+	q = old = v->pm->gets.afn(v->pm);
+	n = arrlen(old);
+	if (v->a < 0)
+	    v->a += n;
+	if (v->b < 0)
+	    v->b += n;
+	if (v->a < 0)
+	    v->a = 0;
+	if (v->b < 0)
+	    v->b = 0;
+
+	ll = v->a + arrlen(val);
+	if (v->b < n)
+	    ll += n - v->b;
+
+	p = new = (char **) zcalloc(sizeof(char *) * (ll + 1));
+
+	for (i = 0; i < v->a; i++)
+	    *p++ = i < n ? ztrdup(*q++) : ztrdup("");
+	for (r = val; *r;)
+	    *p++ = ztrdup(*r++);
+	if (v->b + 1 < n)
+	    for (q = old + v->b + 1; *q;)
+		*p++ = ztrdup(*q++);
+	*p = NULL;
+
+	(v->pm->sets.afn) (v->pm, new);
+	freearray(val);
+    }
+}
+
+/* Retrieve an integer parameter */
+
+/**/
+long
+getiparam(char *s)
+{
+    Value v;
+
+    if (!(v = getvalue(&s, 1)))
+	return 0;
+    return getintvalue(v);
+}
+
+/* Retrieve a scalar (string) parameter */
+
+/**/
+char *
+getsparam(char *s)
+{
+    Value v;
+
+    if (!(v = getvalue(&s, 0)))
+	return NULL;
+    return getstrvalue(v);
+}
+
+/* Retrieve an array parameter */
+
+/**/
+char **
+getaparam(char *s)
+{
+    Value v;
+
+    if (!idigit(*s) && (v = getvalue(&s, 0)) &&
+	PM_TYPE(v->pm->flags) == PM_ARRAY)
+	return v->pm->gets.afn(v->pm);
+    return NULL;
+}
+
+/**/
+Param
+setsparam(char *s, char *val)
+{
+    Value v;
+    char *t = s;
+    char *ss;
+
+    if (!isident(s)) {
+	zerr("not an identifier: %s", s, 0);
+	zsfree(val);
+	errflag = 1;
+	return NULL;
+    }
+    if ((ss = strchr(s, '['))) {
+	*ss = '\0';
+	if (!(v = getvalue(&s, 1)))
+	    createparam(t, PM_ARRAY);
+	*ss = '[';
+	v = NULL;
+    } else {
+	if (!(v = getvalue(&s, 1)))
+	    createparam(t, PM_SCALAR);
+	else if (PM_TYPE(v->pm->flags) == PM_ARRAY &&
+		 !(v->pm->flags & PM_SPECIAL) && unset(KSHARRAYS)) {
+	    unsetparam(t);
+	    createparam(t, PM_SCALAR);
+	    v = NULL;
+	}
+    }
+    if (!v && !(v = getvalue(&t, 1))) {
+	zsfree(val);
+	return NULL;
+    }
+    setstrvalue(v, val);
+    return v->pm;
+}
+
+/**/
+Param
+setaparam(char *s, char **val)
+{
+    Value v;
+    char *t = s;
+    char *ss;
+
+    if (!isident(s)) {
+	zerr("not an identifier: %s", s, 0);
+	freearray(val);
+	errflag = 1;
+	return NULL;
+    }
+    if ((ss = strchr(s, '['))) {
+	*ss = '\0';
+	if (!(v = getvalue(&s, 1)))
+	    createparam(t, PM_ARRAY);
+	*ss = '[';
+	v = NULL;
+    } else {
+	if (!(v = getvalue(&s, 1)))
+	    createparam(t, PM_ARRAY);
+	else if (PM_TYPE(v->pm->flags) != PM_ARRAY &&
+		 !(v->pm->flags & PM_SPECIAL)) {
+	    int uniq = v->pm->flags & PM_UNIQUE;
+	    unsetparam(t);
+	    createparam(t, PM_ARRAY | uniq);
+	    v = NULL;
+	}
+    }
+    if (!v)
+	if (!(v = getvalue(&t, 1)))
+	    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)
+{
+    Value v;
+    char *t = s;
+    Param pm;
+
+    if (!isident(s)) {
+	zerr("not an identifier: %s", s, 0);
+	errflag = 1;
+	return NULL;
+    }
+    if (!(v = getvalue(&s, 1))) {
+	pm = createparam(t, PM_INTEGER);
+	DPUTS(!pm, "BUG: parameter not created");
+	pm->u.val = val;
+	return pm;
+    }
+    setintvalue(v, val);
+    return v->pm;
+}
+
+/* Unset a parameter */
+
+/**/
+void
+unsetparam(char *s)
+{
+    Param pm;
+
+    if ((pm = (Param) paramtab->getnode(paramtab, s)))
+	unsetparam_pm(pm, 0, 1);
+}
+
+/* Unset a parameter */
+
+/**/
+void
+unsetparam_pm(Param pm, int altflag, int exp)
+{
+    Param oldpm, altpm;
+
+    if ((pm->flags & PM_READONLY) && pm->level <= locallevel) {
+	zerr("read-only variable: %s", pm->nam, 0);
+	return;
+    }
+    if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+	zerr("%s: restricted", pm->nam, 0);
+	return;
+    }
+    pm->unsetfn(pm, exp);
+    if ((pm->flags & PM_EXPORTED) && pm->env) {
+	delenv(pm->env);
+	zsfree(pm->env);
+	pm->env = NULL;
+    }
+
+    /* remove it under its alternate name if necessary */
+    if (pm->ename && !altflag) {
+	altpm = (Param) paramtab->getnode(paramtab, pm->ename);
+	if (altpm)
+	    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)
+	return;
+
+    paramtab->removenode(paramtab, pm->nam); /* remove parameter node from table */
+
+    if (pm->old) {
+	oldpm = pm->old;
+	paramtab->addnode(paramtab, oldpm->nam, oldpm);
+	if ((PM_TYPE(oldpm->flags) == PM_SCALAR) && oldpm->sets.cfn == strsetfn)
+	    adduserdir(oldpm->nam, oldpm->u.str, 0, 0);
+    }
+
+    paramtab->freenode((HashNode) pm); /* free parameter node */
+}
+
+/* Standard function to unset a parameter.  This is mostly delegated to *
+ * the specific set function.                                           */
+
+/**/
+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;
+    }
+    pm->flags |= PM_UNSET;
+}
+
+/* Function to get value of an integer parameter */
+
+/**/
+static long
+intgetfn(Param pm)
+{
+    return pm->u.val;
+}
+
+/* Function to set value of an integer parameter */
+
+/**/
+static void
+intsetfn(Param pm, long x)
+{
+    pm->u.val = x;
+}
+
+/* Function to get value of a scalar (string) parameter */
+
+/**/
+char *
+strgetfn(Param pm)
+{
+    return pm->u.str ? pm->u.str : (char *) hcalloc(1);
+}
+
+/* Function to set value of a scalar (string) parameter */
+
+/**/
+static void
+strsetfn(Param pm, char *x)
+{
+    zsfree(pm->u.str);
+    pm->u.str = x;
+    adduserdir(pm->nam, x, 0, 0);
+}
+
+/* Function to get value of an array parameter */
+
+/**/
+static char **
+arrgetfn(Param pm)
+{
+    static char *nullarray = NULL;
+
+    return pm->u.arr ? pm->u.arr : &nullarray;
+}
+
+/* Function to set value of an array parameter */
+
+/**/
+static void
+arrsetfn(Param pm, char **x)
+{
+    if (pm->u.arr && pm->u.arr != x)
+	freearray(pm->u.arr);
+    if (pm->flags & PM_UNIQUE)
+	uniqarray(x);
+    pm->u.arr = x;
+}
+
+/* This function is used as the set function for      *
+ * special parameters that cannot be set by the user. */
+
+/**/
+void
+nullsetfn(Param pm, char *x)
+{
+    zsfree(x);
+}
+
+/* Function to get value of generic special integer *
+ * parameter.  data is pointer to global variable   *
+ * containing the integer value.                    */
+
+/**/
+long
+intvargetfn(Param pm)
+{
+    return *((long *)pm->u.data);
+}
+
+/* Function to set value of generic special integer *
+ * parameter.  data is pointer to global variable   *
+ * where the value is to be stored.                 */
+
+/**/
+void
+intvarsetfn(Param pm, long x)
+{
+    *((long *)pm->u.data) = x;
+}
+
+/* Function to set value of any ZLE-related integer *
+ * parameter.  data is pointer to global variable   *
+ * where the value is to be stored.                 */
+
+/**/
+void
+zlevarsetfn(Param pm, long 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;
+    }
+
+    *((long *)pm->u.data) = x;
+}
+
+/* Function to set value of generic special scalar    *
+ * parameter.  data is pointer to a character pointer *
+ * representing the scalar (string).                  */
+
+/**/
+void
+strvarsetfn(Param pm, char *x)
+{
+    char **q = ((char **)pm->u.data);
+
+    zsfree(*q);
+    *q = x;
+}
+
+/* Function to get value of generic special scalar    *
+ * parameter.  data is pointer to a character pointer *
+ * representing the scalar (string).                  */
+
+/**/
+char *
+strvargetfn(Param pm)
+{
+    char *s = *((char **)pm->u.data);
+
+    if (!s)
+	return hcalloc(1);
+    return s;
+}
+
+/* Function to get value of generic special array  *
+ * parameter.  data is a pointer to the pointer to *
+ * a pointer (a pointer to a variable length array *
+ * of pointers).                                   */
+
+/**/
+char **
+arrvargetfn(Param pm)
+{
+    return *((char ***)pm->u.data);
+}
+
+/* Function to set value of generic special array parameter.    *
+ * data is pointer to a variable length array of pointers which *
+ * represents this array of scalars (strings).  If pm->ename is *
+ * non NULL, then it is a colon separated environment variable  *
+ * version of this array which will need to be updated.         */
+
+/**/
+void
+arrvarsetfn(Param pm, char **x)
+{
+    char ***dptr = (char ***)pm->u.data;
+
+    if (*dptr != x)
+	freearray(*dptr);
+    if (pm->flags & PM_UNIQUE)
+	uniqarray(x);
+    *dptr = x ? x : mkarray(NULL);
+    if (pm->ename && x)
+	arrfixenv(pm->ename, x);
+}
+
+/**/
+char *
+colonarrgetfn(Param pm)
+{
+    return zjoin(*(char ***)pm->u.data, ':');
+}
+
+/**/
+void
+colonarrsetfn(Param pm, char *x)
+{
+    char ***dptr = (char ***)pm->u.data;
+
+    freearray(*dptr);
+    *dptr = x ? colonsplit(x, pm->flags & PM_UNIQUE) : mkarray(NULL);
+    if (pm->ename)
+	arrfixenv(pm->nam, *dptr);
+    zsfree(x);
+}
+
+/**/
+int
+uniqarray(char **x)
+{
+    int changes = 0;
+    char **t, **p = x;
+
+    if (!x || !*x)
+	return 0;
+    while (*++p)
+	for (t = x; t < p; t++)
+	    if (!strcmp(*p, *t)) {
+		zsfree(*p);
+		for (t = p--; (*t = t[1]) != NULL; t++);
+		changes++;
+		break;
+	    }
+    return changes;
+}
+
+/* Function to get value of special parameter `#' and `ARGC' */
+
+/**/
+long
+poundgetfn(Param pm)
+{
+    return arrlen(pparams);
+}
+
+/* Function to get value for special parameter `RANDOM' */
+
+/**/
+long
+randomgetfn(Param pm)
+{
+    return rand() & 0x7fff;
+}
+
+/* Function to set value of special parameter `RANDOM' */
+
+/**/
+void
+randomsetfn(Param pm, long v)
+{
+    srand((unsigned int)v);
+}
+
+/* Function to get value for special parameter `SECONDS' */
+
+/**/
+long
+secondsgetfn(Param pm)
+{
+    return time(NULL) - shtimer.tv_sec;
+}
+
+/* Function to set value of special parameter `SECONDS' */
+
+/**/
+void
+secondssetfn(Param pm, long x)
+{
+    shtimer.tv_sec = time(NULL) - x;
+    shtimer.tv_usec = 0;
+}
+
+/* Function to get value for special parameter `USERNAME' */
+
+/**/
+char *
+usernamegetfn(Param pm)
+{
+    return get_username();
+}
+
+/* Function to set value of special parameter `USERNAME' */
+
+/**/
+void
+usernamesetfn(Param pm, char *x)
+{
+#if defined(HAVE_SETUID) && defined(HAVE_GETPWNAM)
+    struct passwd *pswd;
+
+    if (x && (pswd = getpwnam(x)) && (pswd->pw_uid != cached_uid)) {
+# ifdef HAVE_INITGROUPS
+	initgroups(x, pswd->pw_gid);
+# endif
+	if(!setgid(pswd->pw_gid) && !setuid(pswd->pw_uid)) {
+	    zsfree(cached_username);
+	    cached_username = ztrdup(pswd->pw_name);
+	    cached_uid = pswd->pw_uid;
+	}
+    }
+#endif /* HAVE_SETUID && HAVE_GETPWNAM */
+}
+
+/* Function to get value for special parameter `UID' */
+
+/**/
+long
+uidgetfn(Param pm)
+{
+    return getuid();
+}
+
+/* Function to set value of special parameter `UID' */
+
+/**/
+void
+uidsetfn(Param pm, uid_t x)
+{
+#ifdef HAVE_SETUID
+    setuid(x);
+#endif
+}
+
+/* Function to get value for special parameter `EUID' */
+
+/**/
+long
+euidgetfn(Param pm)
+{
+    return geteuid();
+}
+
+/* Function to set value of special parameter `EUID' */
+
+/**/
+void
+euidsetfn(Param pm, uid_t x)
+{
+#ifdef HAVE_SETEUID
+    seteuid(x);
+#endif
+}
+
+/* Function to get value for special parameter `GID' */
+
+/**/
+long
+gidgetfn(Param pm)
+{
+    return getgid();
+}
+
+/* Function to set value of special parameter `GID' */
+
+/**/
+void
+gidsetfn(Param pm, gid_t x)
+{
+#ifdef HAVE_SETUID
+    setgid(x);
+#endif
+}
+
+/* Function to get value for special parameter `EGID' */
+
+/**/
+long
+egidgetfn(Param pm)
+{
+    return getegid();
+}
+
+/* Function to set value of special parameter `EGID' */
+
+/**/
+void
+egidsetfn(Param pm, gid_t x)
+{
+#ifdef HAVE_SETEUID
+    setegid(x);
+#endif
+}
+
+/**/
+long
+ttyidlegetfn(Param pm)
+{
+    struct stat ttystat;
+
+    if (SHTTY == -1 || fstat(SHTTY, &ttystat))
+	return -1;
+    return time(NULL) - ttystat.st_atime;
+}
+
+/* Function to get value for special parameter `IFS' */
+
+/**/
+char *
+ifsgetfn(Param pm)
+{
+    return ifs;
+}
+
+/* Function to set value of special parameter `IFS' */
+
+/**/
+void
+ifssetfn(Param pm, char *x)
+{
+    zsfree(ifs);
+    ifs = x;
+    inittyptab();
+}
+
+/* Functions to set value of special parameters `LANG' and `LC_*' */
+
+#ifdef LC_ALL
+static struct localename {
+    char *name;
+    int category;
+} lc_names[] = {
+#ifdef LC_COLLATE
+    {"LC_COLLATE", LC_COLLATE},
+#endif
+#ifdef LC_CTYPE
+    {"LC_CTYPE", LC_CTYPE},
+#endif
+#ifdef LC_MESSAGES
+    {"LC_MESSAGES", LC_MESSAGES},
+#endif
+#ifdef LC_TIME
+    {"LC_TIME", LC_TIME},
+#endif
+    {NULL, 0}
+};
+
+/**/
+static void
+setlang(char *x)
+{
+    struct localename *ln;
+
+    setlocale(LC_ALL, x ? x : "");
+    for (ln = lc_names; ln->name; ln++)
+	if ((x = getsparam(ln->name)))
+	    setlocale(ln->category, x);
+}
+
+/**/
+void
+lc_allsetfn(Param pm, char *x)
+{
+    strsetfn(pm, x);
+    if (!x)
+	setlang(getsparam("LANG"));
+    else
+	setlocale(LC_ALL, x);
+}
+
+/**/
+void
+langsetfn(Param pm, char *x)
+{
+    strsetfn(pm, x);
+    setlang(x);
+}
+
+/**/
+void
+lcsetfn(Param pm, char *x)
+{
+    struct localename *ln;
+
+    strsetfn(pm, x);
+    if (getsparam("LC_ALL"))
+	return;
+    if (!x)
+	x = getsparam("LANG");
+
+    for (ln = lc_names; ln->name; ln++)
+	if (!strcmp(ln->name, pm->nam))
+	    setlocale(ln->category, x ? x : "");
+}
+#endif
+
+/* Function to get value for special parameter `HISTSIZE' */
+
+/**/
+long
+histsizegetfn(Param pm)
+{
+    return histsiz;
+}
+
+/* Function to set value of special parameter `HISTSIZE' */
+
+/**/
+void
+histsizesetfn(Param pm, long v)
+{
+    if ((histsiz = v) <= 2)
+	histsiz = 2;
+    resizehistents();
+}
+
+/* Function to get value for special parameter `ERRNO' */
+
+/**/
+long
+errnogetfn(Param pm)
+{
+    return errno;
+}
+
+/* Function to get value for special parameter `histchar' */
+
+/**/
+char *
+histcharsgetfn(Param pm)
+{
+    static char buf[4];
+
+    buf[0] = bangchar;
+    buf[1] = hatchar;
+    buf[2] = hashchar;
+    buf[3] = '\0';
+    return buf;
+}
+
+/* Function to set value of special parameter `histchar' */
+
+/**/
+void
+histcharssetfn(Param pm, char *x)
+{
+    if (x) {
+	bangchar = x[0];
+	hatchar = (bangchar) ? x[1] : '\0';
+	hashchar = (hatchar) ? x[2] : '\0';
+	zsfree(x);
+    } else {
+	bangchar = '!';
+	hashchar = '#';
+	hatchar = '^';
+    }
+    inittyptab();
+}
+
+/* Function to get value for special parameter `HOME' */
+
+/**/
+char *
+homegetfn(Param pm)
+{
+    return home;
+}
+
+/* Function to set value of special parameter `HOME' */
+
+/**/
+void
+homesetfn(Param pm, char *x)
+{
+    zsfree(home);
+    if (x && isset(CHASELINKS) && (home = xsymlink(x)))
+	zsfree(x);
+    else
+	home = x ? x : ztrdup("");
+    finddir(NULL);
+}
+
+/* Function to get value for special parameter `WORDCHARS' */
+
+/**/
+char *
+wordcharsgetfn(Param pm)
+{
+    return wordchars;
+}
+
+/* Function to set value of special parameter `WORDCHARS' */
+
+/**/
+void
+wordcharssetfn(Param pm, char *x)
+{
+    zsfree(wordchars);
+    wordchars = x;
+    inittyptab();
+}
+
+/* Function to get value for special parameter `_' */
+
+/**/
+char *
+underscoregetfn(Param pm)
+{
+    return underscore;
+}
+
+/* Function to get value for special parameter `TERM' */
+
+/**/
+char *
+termgetfn(Param pm)
+{
+    return term;
+}
+
+/* Function to set value of special parameter `TERM' */
+
+/**/
+void
+termsetfn(Param pm, char *x)
+{
+    zsfree(term);
+    term = x ? x : ztrdup("");
+
+    /* If non-interactive, delay setting up term till we need it. */
+    if (unset(INTERACTIVE) || !*term)
+	termflags |= TERM_UNKNOWN;
+    else 
+	init_term();
+}
+
+/* We could probably replace the replenv with the actual code to *
+ * do the replacing, since we've already scanned for the string. */
+
+/**/
+static void
+arrfixenv(char *s, char **t)
+{
+    char **ep, *u;
+    int len_s;
+    Param pm;
+
+    MUSTUSEHEAP("arrfixenv");
+    if (t == path)
+	cmdnamtab->emptytable(cmdnamtab);
+    u = zjoin(t, ':');
+    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);
+	    return;
+	}
+    if (isset(ALLEXPORT))
+	pm->flags |= PM_EXPORTED;
+    if (pm->flags & PM_EXPORTED)
+	pm->env = addenv(s, u);
+}
+
+/* Given *name = "foo", it searchs the environment for string *
+ * "foo=bar", and returns a pointer to the beginning of "bar" */
+
+/**/
+char *
+zgetenv(char *name)
+{
+    char **ep, *s, *t;
+ 
+    for (ep = environ; *ep; ep++) {
+	for (s = *ep, t = name; *s && *s == *t; s++, t++);
+	if (*s == '=' && !*t)
+	    return s + 1;
+    }
+    return NULL;
+}
+
+/* Change the value of an existing environment variable */
+
+/**/
+char *
+replenv(char *e, char *value)
+{
+    char **ep, *s;
+    int len_value;
+
+    for (ep = environ; *ep; ep++)
+	if (*ep == e) {
+	    for (len_value = 0, s = value;
+		 *s && (*s++ != Meta || *s++ != 32); len_value++);
+	    s = e;
+	    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;
+	    return *ep;
+	}
+    return NULL;
+}
+
+/* Given strings *name = "foo", *value = "bar", *
+ * return a new string *str = "foo=bar".        */
+
+/**/
+static char *
+mkenvstr(char *name, char *value)
+{
+    char *str, *s;
+    int len_name, len_value;
+
+    len_name = strlen(name);
+    for (len_value = 0, s = value;
+	 *s && (*s++ != Meta || *s++ != 32); len_value++);
+    s = str = (char *) zalloc(len_name + len_value + 2);
+    strcpy(s, name);
+    s += len_name;
+    *s = '=';
+    while (*s++)
+	if ((*s = *value++) == Meta)
+	    *s = *value++ ^ 32;
+    return str;
+}
+
+/* Given *name = "foo", *value = "bar", add the    *
+ * string "foo=bar" to the environment.  Return a  *
+ * pointer to the location of this new environment *
+ * string.                                         */
+
+/**/
+char *
+addenv(char *name, char *value)
+{
+    char **ep, *s, *t;
+    int num_env;
+
+    /* First check if there is already an environment *
+     * variable matching string `name'.               */
+    for (ep = environ; *ep; ep++) {
+	for (s = *ep, t = name; *s && *s == *t; s++, t++);
+	if (*s == '=' && !*t) {
+	    zsfree(*ep);
+	    return *ep = mkenvstr(name, value);
+	}
+    }
+
+    /* Else we have to make room and add it */
+    num_env = arrlen(environ);
+    environ = (char **) zrealloc(environ, (sizeof(char *)) * (num_env + 2));
+
+    /* Now add it at the end */
+    ep = environ + num_env;
+    *ep = mkenvstr(name, value);
+    *(ep + 1) = NULL;
+    return *ep;
+}
+
+/* Delete a pointer from the list of pointers to environment *
+ * variables by shifting all the other pointers up one slot. */
+
+/**/
+void
+delenv(char *x)
+{
+    char **ep;
+
+    for (ep = environ; *ep; ep++) {
+	if (*ep == x)
+	    break;
+    }
+    if (*ep)
+	for (; (ep[0] = ep[1]); ep++);
+}
+
+/**/
+static void
+convbase(char *s, long v, int base)
+{
+    int digs = 0;
+    unsigned long x;
+
+    if (v < 0)
+	*s++ = '-', v = -v;
+    if (base <= 1)
+	base = 10;
+
+    if (base != 10) {
+	sprintf(s, "%d#", base);
+	s += strlen(s);
+    }
+    for (x = v; x; digs++)
+	x /= base;
+    if (!digs)
+	digs = 1;
+    s[digs--] = '\0';
+    x = v;
+    while (digs >= 0) {
+	int dig = x % base;
+
+	s[digs--] = (dig < 10) ? '0' + dig : dig - 10 + 'A';
+	x /= base;
+    }
+}
+
+/* Start a parameter scope */
+
+/**/
+void
+startparamscope(void)
+{
+    locallevel++;
+}
+
+/* End a parameter scope: delete the parameters local to the scope. */
+
+/**/
+void
+endparamscope(void)
+{
+    locallevel--;
+    scanhashtable(paramtab, 0, 0, 0, scanendscope, 0);
+}
+
+/**/
+static void
+scanendscope(HashNode hn, int flags)
+{
+    Param pm = (Param)hn;
+    if(pm->level > locallevel)
+	unsetparam_pm(pm, 0, 0);
+}
diff --git a/Src/parse.c b/Src/parse.c
new file mode 100644
index 000000000..d42be2f2f
--- /dev/null
+++ b/Src/parse.c
@@ -0,0 +1,1379 @@
+/*
+ * parse.c - parser
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "parse.pro"
+
+/* != 0 if we are about to read a command word */
+ 
+/**/
+int incmdpos;
+ 
+/* != 0 if we are in the middle of a [[ ... ]] */
+ 
+/**/
+int incond;
+ 
+/* != 0 if we are after a redirection (for ctxtlex only) */
+ 
+/**/
+int inredir;
+ 
+/* != 0 if we are about to read a case pattern */
+ 
+/**/
+int incasepat;
+ 
+/* != 0 if we just read a newline */
+ 
+/**/
+int isnewlin;
+
+/* != 0 if we are after a for keyword */
+
+/**/
+int infor;
+
+/* list of here-documents */
+
+/**/
+struct heredocs *hdocs;
+ 
+/* used in arrays of lists instead of NULL pointers */
+ 
+/**/
+struct list dummy_list;
+
+#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)
+
+#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)
+
+/*
+ * event	: ENDINPUT
+ *			| SEPER
+ *			| sublist [ SEPER | AMPER | AMPERBANG ]
+ */
+/**/
+List
+parse_event(void)
+{
+    tok = ENDINPUT;
+    incmdpos = 1;
+    yylex();
+    return par_event();
+}
+
+/**/
+static List
+par_event(void)
+{
+    Sublist sl;
+    List l = NULL;
+
+    while (tok == SEPER) {
+	if (isnewlin > 0)
+	    return NULL;
+	yylex();
+    }
+    if (tok == ENDINPUT)
+	return NULL;
+    if ((sl = par_sublist()))
+	if (tok == ENDINPUT) {
+	    l = (List) make_list();
+	    l->type = Z_SYNC;
+	    l->left = sl;
+	} else if (tok == SEPER) {
+	    l = (List) make_list();
+	    l->type = Z_SYNC;
+	    l->left = sl;
+	    if (isnewlin <= 0)
+		yylex();
+	} else if (tok == AMPER) {
+	    l = (List) make_list();
+	    l->type = Z_ASYNC;
+	    l->left = sl;
+	    yylex();
+	} else if (tok == AMPERBANG) {
+	    l = (List) make_list();
+	    l->type = Z_ASYNC | Z_DISOWN;
+	    l->left = sl;
+	    yylex();
+	} else
+	    l = NULL;
+    if (!l) {
+	if (errflag) {
+	    yyerror();
+	    return NULL;
+	}
+	herrflush();
+	yyerror();
+	return NULL;
+    } else {
+	l->right = par_event();
+    }
+    return l;
+}
+
+/**/
+List
+parse_list(void)
+{
+    List ret;
+
+    tok = ENDINPUT;
+    incmdpos = 1;
+    yylex();
+    ret = par_list();
+    if (tok == LEXERR) {
+	yyerror();
+	return NULL;
+    }
+    return ret;
+}
+
+/*
+ * list	: { SEPER } [ sublist [ { SEPER | AMPER | AMPERBANG } list ] ]
+ */
+
+/**/
+static List
+par_list(void)
+{
+    Sublist sl;
+    List l = NULL;
+
+    while (tok == SEPER)
+	yylex();
+    if ((sl = par_sublist()))
+	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;
+	    incmdpos = 1;
+	    do {
+		yylex();
+	    } while (tok == SEPER);
+	    l->right = par_list();
+	} else {
+	    l = (List) make_list();
+	    l->left = sl;
+	    l->type = Z_SYNC;
+	}
+    return l;
+}
+
+/**/
+static List
+par_list1(void)
+{
+    Sublist sl;
+    List l = NULL;
+
+    if ((sl = par_sublist())) {
+	l = (List) make_list();
+	l->type = Z_SYNC;
+	l->left = sl;
+    }
+    return l;
+}
+
+/*
+ * sublist	: sublist2 [ ( DBAR | DAMPER ) { SEPER } sublist ]
+ */
+
+/**/
+static Sublist
+par_sublist(void)
+{
+    Sublist sl;
+
+    if ((sl = par_sublist2()))
+	if (tok == DBAR || tok == DAMPER) {
+	    int qtok = tok;
+
+	    cmdpush(tok == DBAR ? CS_CMDOR : CS_CMDAND);
+	    yylex();
+	    while (tok == SEPER)
+		yylex();
+	    sl->right = par_sublist();
+	    sl->type = (qtok == DBAR) ? ORNEXT : ANDNEXT;
+	    cmdpop();
+	}
+    return sl;
+}
+
+/*
+ * sublist2	: [ COPROC | BANG ] pline
+ */
+
+/**/
+static Sublist
+par_sublist2(void)
+{
+    Sublist sl;
+    Pline p;
+
+    sl = (Sublist) make_sublist();
+    if (tok == COPROC) {
+	sl->flags |= PFLAG_COPROC;
+	yylex();
+    } else if (tok == BANG) {
+	sl->flags |= PFLAG_NOT;
+	yylex();
+    }
+    if (!(p = par_pline()) && !sl->flags)
+	return NULL;
+    sl->left = p;
+    return sl;
+}
+
+/*
+ * pline	: cmd [ ( BAR | BARAMP ) { SEPER } pline ]
+ */
+
+/**/
+static Pline
+par_pline(void)
+{
+    Cmd c;
+    Pline p, p2;
+
+    if (!(c = par_cmd()))
+	return NULL;
+    if (tok == BAR) {
+	cmdpush(CS_PIPE);
+	yylex();
+	while (tok == SEPER)
+	    yylex();
+	p2 = par_pline();
+	cmdpop();
+	p = (Pline) make_pline();
+	p->left = c;
+	p->right = p2;
+	p->type = PIPE;
+	return p;
+    } else if (tok == BARAMP) {
+	struct redir *rdr = (struct redir *)allocnode(N_REDIR);
+
+	rdr->type = MERGEOUT;
+	rdr->fd1 = 2;
+	rdr->name = dupstring("1");
+	addlinknode(c->redir, rdr);
+
+	cmdpush(CS_ERRPIPE);
+	yylex();
+	p2 = par_pline();
+	cmdpop();
+	p = (Pline) make_pline();
+	p->left = c;
+	p->right = p2;
+	p->type = PIPE;
+	return p;
+    } else {
+	p = (Pline) make_pline();
+	p->left = c;
+	p->type = END;
+	return p;
+    }
+}
+
+/*
+ * cmd	: { redir } ( for | case | if | while | repeat |
+ *				subsh | funcdef | time | dinbrack | dinpar | simple ) { redir }
+ */
+
+/**/
+static Cmd
+par_cmd(void)
+{
+    Cmd c;
+
+    c = (Cmd) make_cmd();
+    c->lineno = lineno;
+    c->args = newlinklist();
+    c->redir = newlinklist();
+    c->vars = newlinklist();
+    while (IS_REDIROP(tok))
+	par_redir(c->redir);
+    switch (tok) {
+    case FOR:
+	cmdpush(CS_FOR);
+	par_for(c);
+	cmdpop();
+	break;
+    case FOREACH:
+	cmdpush(CS_FOREACH);
+	par_for(c);
+	cmdpop();
+	break;
+    case SELECT:
+	cmdpush(CS_SELECT);
+	par_for(c);
+	cmdpop();
+	break;
+    case CASE:
+	cmdpush(CS_CASE);
+	par_case(c);
+	cmdpop();
+	break;
+    case IF:
+	par_if(c);
+	break;
+    case WHILE:
+	cmdpush(CS_WHILE);
+	par_while(c);
+	cmdpop();
+	break;
+    case UNTIL:
+	cmdpush(CS_UNTIL);
+	par_while(c);
+	cmdpop();
+	break;
+    case REPEAT:
+	cmdpush(CS_REPEAT);
+	par_repeat(c);
+	cmdpop();
+	break;
+    case INPAR:
+	cmdpush(CS_SUBSH);
+	par_subsh(c);
+	cmdpop();
+	break;
+    case INBRACE:
+	cmdpush(CS_CURSH);
+	par_subsh(c);
+	cmdpop();
+	break;
+    case FUNC:
+	cmdpush(CS_FUNCDEF);
+	par_funcdef(c);
+	cmdpop();
+	break;
+    case TIME:
+	par_time(c);
+	break;
+    case DINBRACK:
+	cmdpush(CS_COND);
+	par_dinbrack(c);
+	cmdpop();
+	break;
+    case DINPAR:
+	c->type = CARITH;
+	addlinknode(c->args, tokstr);
+	yylex();
+	break;
+    default:
+	if (!par_simple(c))
+	    return NULL;
+	break;
+    }
+    while (IS_REDIROP(tok))
+	par_redir(c->redir);
+    incmdpos = 1;
+    incasepat = 0;
+    incond = 0;
+    return c;
+}
+
+/*
+ * for  : ( FOR DINPAR expr SEMI expr SEMI expr DOUTPAR |
+ *    ( FOR[EACH] | SELECT ) name ( "in" wordlist | INPAR wordlist OUTPAR ) )
+ *	{ SEPER } ( DO list DONE | INBRACE list OUTBRACE | list ZEND | list1 )
+ */
+
+/**/
+static void
+par_for(Cmd c)
+{
+    Forcmd f;
+    int csh = (tok == FOREACH);
+
+    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;
+	yylex();
+	if (tok != DINPAR)
+	    YYERRORV;
+	f->condition = tokstr;
+	yylex();
+	if (tok != DOUTPAR)
+	    YYERRORV;
+	f->advance = tokstr;
+	infor = 0;
+	incmdpos = 1;
+	yylex();
+    } else {
+	infor = 0;
+	if (tok != STRING || !isident(tokstr))
+	    YYERRORV;
+	f->name = tokstr;
+	incmdpos = 1;
+	yylex();
+	if (tok == STRING && !strcmp(tokstr, "in")) {
+	    f->inflag = 1;
+	    incmdpos = 0;
+	    yylex();
+	    c->args = par_wordlist();
+	    if (tok != SEPER)
+		YYERRORV;
+	} else if (tok == INPAR) {
+	    f->inflag = 1;
+	    incmdpos = 0;
+	    yylex();
+	    c->args = par_nl_wordlist();
+	    if (tok != OUTPAR)
+		YYERRORV;
+	    incmdpos = 1;
+	    yylex();
+	}
+    }
+    incmdpos = 1;
+    while (tok == SEPER)
+	yylex();
+    if (tok == DO) {
+	yylex();
+	f->list = par_list();
+	if (tok != DONE)
+	    YYERRORV;
+	yylex();
+    } else if (tok == INBRACE) {
+	yylex();
+	f->list = par_list();
+	if (tok != OUTBRACE)
+	    YYERRORV;
+	yylex();
+    } else if (csh || isset(CSHJUNKIELOOPS)) {
+	f->list = par_list();
+	if (tok != ZEND)
+	    YYERRORV;
+	yylex();
+    } else if (unset(SHORTLOOPS)) {
+	YYERRORV;
+    } else
+	f->list = par_list1();
+    c->u.forcmd = f;
+}
+
+/*
+ * case	: CASE STRING { SEPER } ( "in" | INBRACE )
+				{ { SEPER } STRING { BAR STRING } OUTPAR
+					list [ DSEMI | SEMIAMP ] }
+				{ SEPER } ( "esac" | OUTBRACE )
+ */
+
+/**/
+static void
+par_case(Cmd c)
+{
+    int brflag;
+    LinkList pats, lists;
+    int n = 1;
+    char **pp;
+    List *ll;
+    LinkNode no;
+    struct casecmd *cc;
+
+    c->type = CCASE;
+    incmdpos = 0;
+    yylex();
+    if (tok != STRING)
+	YYERRORV;
+    pats = newlinklist();
+    addlinknode(pats, tokstr);
+    incmdpos = 1;
+    yylex();
+    while (tok == SEPER)
+	yylex();
+    if (!(tok == STRING && !strcmp(tokstr, "in")) && tok != INBRACE)
+	YYERRORV;
+    brflag = (tok == INBRACE);
+    incasepat = 1;
+    incmdpos = 0;
+    yylex();
+    cc = c->u.casecmd = (struct casecmd *)make_casecmd();
+    lists = newlinklist();
+    for (;;) {
+	char *str;
+
+	while (tok == SEPER)
+	    yylex();
+	if (tok == OUTBRACE)
+	    break;
+	if (tok != STRING)
+	    YYERRORV;
+	if (!strcmp(tokstr, "esac"))
+	    break;
+	str = ncalloc(strlen(tokstr) + 2);
+	*str = ';';
+	strcpy(str + 1, tokstr);
+	incasepat = 0;
+	incmdpos = 1;
+	for (;;) {
+	    yylex();
+	    if (tok == OUTPAR) {
+		incasepat = 0;
+		incmdpos = 1;
+		yylex();
+		break;
+	    } else if (tok == BAR) {
+		char *str2;
+		int sl = strlen(str);
+
+		incasepat = 1;
+		incmdpos = 0;
+		str2 = ncalloc(sl + 2);
+		strcpy(str2, str);
+		str2[sl] = Bar;
+		str2[sl+1] = '\0';
+		str = str2;
+	    } else {
+		int sl = strlen(str);
+
+		if (str[sl - 1] != Bar) {
+		    /* POSIX allows (foo*) patterns */
+		    int pct;
+		    char *s;
+
+		    for (s = str + 1, pct = 0; *s; s++) {
+			if (*s == Inpar)
+			    pct++;
+			if (!pct)
+			    break;
+			if (pct == 1) {
+			    if (*s == Bar || *s == Inpar)
+				while (iblank(s[1]))
+				    chuck(s+1);
+			    if (*s == Bar || *s == Outpar)
+				while (iblank(s[-1]) &&
+				       (s < str+2 || s[-2] != Meta))
+				    chuck(--s);
+			}
+			if (*s == Outpar)
+			    pct--;
+		    }
+		    if (*s || pct || s == str + 1)
+			YYERRORV;
+		    break;
+		} else {
+		    char *str2;
+
+		    if (tok != STRING)
+			YYERRORV;
+		    str2 = ncalloc(sl + strlen(tokstr) + 1);
+		    strcpy(str2, str);
+		    strcpy(str2 + sl, tokstr);
+		    str = str2;
+		}
+	    }
+	}
+	addlinknode(pats, str);
+	addlinknode(lists, par_list());
+	n++;
+	if ((tok == ESAC && !brflag) || (tok == OUTBRACE && brflag))
+	    break;
+	if(tok == SEMIAMP)
+	    *str = '&';
+	else if (tok != DSEMI)
+	    YYERRORV;
+	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;
+}
+
+/*
+ * if	: { ( IF | ELIF ) { SEPER } ( INPAR list OUTPAR | list )
+			{ SEPER } ( THEN list | INBRACE list OUTBRACE | list1 ) }
+			[ FI | ELSE list FI | ELSE { SEPER } INBRACE list OUTBRACE ]
+			(you get the idea...?)
+ */
+
+/**/
+static void
+par_if(Cmd c)
+{
+    struct ifcmd *i;
+    int xtok;
+    unsigned char nc;
+    LinkList ifsl, thensl;
+    LinkNode no;
+    int ni = 0, nt = 0, usebrace = 0;
+    List l, *ll;
+
+    ifsl = newlinklist();
+    thensl = newlinklist();
+
+    c->type = CIF;
+    for (;;) {
+	xtok = tok;
+	cmdpush(xtok == IF ? CS_IF : CS_ELIF);
+	yylex();
+	if (xtok == FI)
+	    break;
+	if (xtok == ELSE)
+	    break;
+	while (tok == SEPER)
+	    yylex();
+	if (!(xtok == IF || xtok == ELIF)) {
+	    cmdpop();
+	    YYERRORV;
+	}
+	addlinknode(ifsl, par_list());
+	ni++;
+	incmdpos = 1;
+	while (tok == SEPER)
+	    yylex();
+	xtok = FI;
+	nc = cmdstack[cmdsp - 1] == CS_IF ? CS_IFTHEN : CS_ELIFTHEN;
+	if (tok == THEN) {
+	    usebrace = 0;
+	    cmdpop();
+	    cmdpush(nc);
+	    yylex();
+	    addlinknode(thensl, par_list());
+	    nt++;
+	    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 {
+		cmdpop();
+		cmdpush(nc);
+		addlinknode(thensl, par_list1());
+		nt++;
+		incmdpos = 1;
+		break;
+	    }
+	}
+    }
+    cmdpop();
+    if (xtok == ELSE) {
+	cmdpush(CS_ELSE);
+	while (tok == SEPER)
+	    yylex();
+	if (tok == INBRACE && usebrace) {
+	    yylex();
+	    l = par_list();
+	    if (tok != OUTBRACE) {
+		cmdpop();
+		YYERRORV;
+	    }
+	} else {
+	    l = par_list();
+	    if (tok != FI) {
+		cmdpop();
+		YYERRORV;
+	    }
+	}
+	addlinknode(thensl, l);
+	nt++;
+	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;
+}
+
+/*
+ * while	: ( WHILE | UNTIL ) ( INPAR list OUTPAR | list ) { SEPER }
+				( DO list DONE | INBRACE list OUTBRACE | list ZEND )
+ */
+
+/**/
+static void
+par_while(Cmd c)
+{
+    struct whilecmd *w;
+
+    c->type = CWHILE;
+    w = c->u.whilecmd = (struct whilecmd *)make_whilecmd();
+    w->cond = (tok == UNTIL);
+    yylex();
+    w->cont = par_list();
+    incmdpos = 1;
+    while (tok == SEPER)
+	yylex();
+    if (tok == DO) {
+	yylex();
+	w->loop = par_list();
+	if (tok != DONE)
+	    YYERRORV;
+	yylex();
+    } else if (tok == INBRACE) {
+	yylex();
+	w->loop = par_list();
+	if (tok != OUTBRACE)
+	    YYERRORV;
+	yylex();
+    } else if (isset(CSHJUNKIELOOPS)) {
+	w->loop = par_list();
+	if (tok != ZEND)
+	    YYERRORV;
+	yylex();
+    } else
+	YYERRORV;
+}
+
+/*
+ * repeat	: REPEAT STRING { SEPER } ( DO list DONE | list1 )
+ */
+
+/**/
+static void
+par_repeat(Cmd c)
+{
+    c->type = CREPEAT;
+    incmdpos = 0;
+    yylex();
+    if (tok != STRING)
+	YYERRORV;
+    addlinknode(c->args, tokstr);
+    incmdpos = 1;
+    yylex();
+    while (tok == SEPER)
+	yylex();
+    if (tok == DO) {
+	yylex();
+	c->u.list = par_list();
+	if (tok != DONE)
+	    YYERRORV;
+	yylex();
+    } else if (tok == INBRACE) {
+	yylex();
+	c->u.list = par_list();
+	if (tok != OUTBRACE)
+	    YYERRORV;
+	yylex();
+    } else if (isset(CSHJUNKIELOOPS)) {
+	c->u.list = par_list();
+	if (tok != ZEND)
+	    YYERRORV;
+	yylex();
+    } else if (unset(SHORTLOOPS)) {
+	YYERRORV;
+    } else
+	c->u.list = par_list1();
+}
+
+/*
+ * subsh	: ( INPAR | INBRACE ) list ( OUTPAR | OUTBRACE )
+ */
+
+/**/
+static void
+par_subsh(Cmd c)
+{
+    c->type = (tok == INPAR) ? SUBSH : CURSH;
+    yylex();
+    c->u.list = par_list();
+    if (tok != ((c->type == SUBSH) ? OUTPAR : OUTBRACE))
+	YYERRORV;
+    incmdpos = 1;
+    yylex();
+}
+
+/*
+ * funcdef	: FUNCTION wordlist [ INOUTPAR ] { SEPER }
+ *					( list1 | INBRACE list OUTBRACE )
+ */
+
+/**/
+static void
+par_funcdef(Cmd c)
+{
+    nocorrect = 1;
+    incmdpos = 0;
+    yylex();
+    c->type = FUNCDEF;
+    c->args = newlinklist();
+    incmdpos = 1;
+    while (tok == STRING) {
+	if (*tokstr == Inbrace && !tokstr[1]) {
+	    tok = INBRACE;
+	    break;
+	}
+	addlinknode(c->args, tokstr);
+	yylex();
+    }
+    nocorrect = 0;
+    if (tok == INOUTPAR)
+	yylex();
+    while (tok == SEPER)
+	yylex();
+    if (tok == INBRACE) {
+	yylex();
+	c->u.list = par_list();
+	if (tok != OUTBRACE)
+	    YYERRORV;
+	yylex();
+    } else if (unset(SHORTLOOPS)) {
+	YYERRORV;
+    } else
+	c->u.list = par_list1();
+}
+
+/*
+ * time	: TIME sublist2
+ */
+
+/**/
+static void
+par_time(Cmd c)
+{
+    yylex();
+    c->type = ZCTIME;
+    c->u.pline = par_sublist2();
+}
+
+/*
+ * dinbrack	: DINBRACK cond DOUTBRACK
+ */
+
+/**/
+static void
+par_dinbrack(Cmd c)
+{
+    c->type = COND;
+    incond = 1;
+    incmdpos = 0;
+    yylex();
+    c->u.cond = par_cond();
+    if (tok != DOUTBRACK)
+	YYERRORV;
+    incond = 0;
+    incmdpos = 1;
+    yylex();
+}
+
+/*
+ * simple	: { COMMAND | EXEC | NOGLOB | NOCORRECT | DASH }
+					{ STRING | ENVSTRING | ENVARRAY wordlist OUTPAR | redir }
+					[ INOUTPAR { SEPER } ( list1 | INBRACE list OUTBRACE ) ]
+ */
+
+/**/
+static Cmd
+par_simple(Cmd c)
+{
+    int isnull = 1;
+
+    c->type = SIMPLE;
+    for (;;) {
+	if (tok == NOCORRECT)
+	    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);
+	    isnull = 0;
+	} else if (tok == ENVARRAY) {
+	    struct varasg *v = (struct varasg *)make_varnode();
+	    int oldcmdpos = incmdpos;
+
+	    v->type = PM_ARRAY;
+	    incmdpos = 0;
+	    v->name = tokstr;
+	    cmdpush(CS_ARRAY);
+	    yylex();
+	    v->arr = par_nl_wordlist();
+	    cmdpop();
+	    if (tok != OUTPAR)
+		YYERROR;
+	    incmdpos = oldcmdpos;
+	    addlinknode(c->vars, v);
+	    isnull = 0;
+	} else
+	    break;
+	yylex();
+    }
+    if (tok == AMPER || tok == AMPERBANG)
+	YYERROR;
+    for (;;) {
+	if (tok == STRING) {
+	    incmdpos = 0;
+	    addlinknode(c->args, tokstr);
+	    yylex();
+	} else if (IS_REDIROP(tok)) {
+	    par_redir(c->redir);
+	} else if (tok == INOUTPAR) {
+	    incmdpos = 1;
+	    cmdpush(CS_FUNCDEF);
+	    yylex();
+	    while (tok == SEPER)
+		yylex();
+	    if (tok == INBRACE) {
+		yylex();
+		c->u.list = par_list();
+		if (tok != OUTBRACE) {
+		    cmdpop();
+		    YYERROR;
+		}
+		yylex();
+	    } else
+		c->u.list = (List) expandstruct((struct node *) par_cmd(), N_LIST);
+	    cmdpop();
+	    c->type = FUNCDEF;
+	} else
+	    break;
+	isnull = 0;
+    }
+    if (isnull && empty(c->redir))
+	return NULL;
+    incmdpos = 1;
+    return c;
+}
+
+/*
+ * condlex is yylex for normal parsing, but is altered to allow
+ * the test builtin to use par_cond.
+ */
+
+/**/
+void (*condlex) _((void)) = yylex;
+
+/*
+ * cond	: cond_1 { SEPER } [ DBAR { SEPER } cond ]
+ */
+
+/**/
+Cond
+par_cond(void)
+{
+    Cond c, c2;
+
+    c = 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;
+    }
+    return c;
+}
+
+/*
+ * cond_1 : cond_2 { SEPER } [ DAMPER { SEPER } cond_1 ]
+ */
+
+/**/
+static Cond
+par_cond_1(void)
+{
+    Cond c, c2;
+
+    c = 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;
+    }
+    return c;
+}
+
+/*
+ * cond_2	: BANG cond_2
+				| INPAR { SEPER } cond_2 { SEPER } OUTPAR
+				| STRING STRING STRING
+				| STRING STRING
+				| STRING ( INANG | OUTANG ) STRING
+ */
+
+/**/
+static Cond
+par_cond_2(void)
+{
+    Cond c, c2;
+    char *s1, *s2, *s3;
+    int dble = 0;
+
+    if (condlex == testlex) {
+	/* See the description of test in POSIX 1003.2 */
+	if (tok == NULLTOK)
+	    /* no arguments: false */
+	    return par_cond_double(dupstring("-n"), dupstring(""));
+	if (!*testargs) {
+	    /* one argument: [ foo ] is equivalent to [ -n foo ] */
+	    s1 = tokstr;
+	    condlex();
+	    return par_cond_double(dupstring("-n"), s1);
+	}
+	if (testargs[1] && !testargs[2]) {
+	    /* three arguments: if the second argument is a binary operator, *
+	     * perform that binary test on the first and the trird argument  */
+	    if (!strcmp(*testargs, "=")  ||
+		!strcmp(*testargs, "==") ||
+		!strcmp(*testargs, "!=") ||
+		(**testargs == '-' && get_cond_num(*testargs + 1) >= 0)) {
+		s1 = tokstr;
+		condlex();
+		s2 = tokstr;
+		condlex();
+		s3 = tokstr;
+		condlex();
+		return par_cond_triple(s1, s2, s3);
+	    }
+	}
+    }
+    if (tok == BANG) {
+	condlex();
+	c = par_cond_2();
+	c2 = (Cond) make_cond();
+	c2->left = (void *) c;
+	c2->type = COND_NOT;
+	return c2;
+    }
+    if (tok == INPAR) {
+	condlex();
+	while (tok == SEPER)
+	    condlex();
+	c = par_cond();
+	while (tok == SEPER)
+	    condlex();
+	if (tok != OUTPAR)
+	    YYERROR;
+	condlex();
+	return c;
+    }
+    if (tok != STRING)
+	if (tok && tok != LEXERR && condlex == testlex) {
+	    s1 = tokstr;
+	    condlex();
+	    return par_cond_double("-n", s1);
+	} else
+	    YYERROR;
+    s1 = tokstr;
+    if (condlex == testlex)
+	dble = (*s1 == '-' && strspn(s1+1, "abcdefghknoprstuwxzLONGS") == 1
+		  && !s1[2]);
+    condlex();
+    if (tok == INANG || tok == OUTANG) {
+	int xtok = tok;
+	condlex();
+	if (tok != STRING)
+	    YYERROR;
+	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;
+    }
+    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;
+    s2 = tokstr;
+    incond++;			/* parentheses do globbing */
+    condlex();
+    incond--;			/* parentheses do grouping */
+    if (tok == STRING && !dble) {
+	s3 = tokstr;
+	condlex();
+	return par_cond_triple(s1, s2, s3);
+    } else
+	return par_cond_double(s1, s2);
+}
+
+/*
+ * 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(LinkList l)
+{
+    struct redir *fn = (struct redir *)allocnode(N_REDIR);
+    int oldcmdpos, oldnc;
+
+    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;
+
+    /* assign default fd */
+    if (fn->fd1 == -1)
+	fn->fd1 = IS_READFD(fn->type) ? 0 : 1;
+
+    fn->name = tokstr;
+
+    switch (fn->type) {
+    case HEREDOC:
+    case HEREDOCDASH: {
+	/* <<[-] name */
+	struct heredocs **hd;
+
+	for (hd = &hdocs; *hd; hd = &(*hd)->next);
+	*hd = zalloc(sizeof(struct heredocs));
+	(*hd)->next = NULL;
+	(*hd)->rd = fn;
+	break;
+    }
+    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;
+    }
+    yylex();
+    addlinknode(l, fn);
+}
+
+/*
+ * wordlist	: { STRING }
+ */
+
+/**/
+static LinkList
+par_wordlist(void)
+{
+    LinkList l;
+
+    l = newlinklist();
+    while (tok == STRING) {
+	addlinknode(l, tokstr);
+	yylex();
+    }
+    return l;
+}
+
+/*
+ * nl_wordlist	: { STRING | SEPER }
+ */
+
+/**/
+static LinkList
+par_nl_wordlist(void)
+{
+    LinkList l;
+
+    l = newlinklist();
+    while (tok == STRING || tok == SEPER) {
+	if (tok != SEPER)
+	    addlinknode(l, tokstr);
+	yylex();
+    }
+    return l;
+}
+
+/**/
+static Cond
+par_cond_double(char *a, char *b)
+{
+    Cond n = (Cond) make_cond();
+
+    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;
+}
+
+/**/
+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 Cond
+par_cond_triple(char *a, char *b, char *c)
+{
+    Cond n = (Cond) make_cond();
+    int t0;
+
+    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);
+    } 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;
+}
+
+/**/
+static void
+yyerror(void)
+{
+    int t0;
+
+    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);
+    else
+	zerr("parse error", NULL, 0);
+}
diff --git a/Src/prompt.c b/Src/prompt.c
new file mode 100644
index 000000000..8c9216f95
--- /dev/null
+++ b/Src/prompt.c
@@ -0,0 +1,766 @@
+/*
+ * prompt.c - construct zsh prompts
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "prompt.pro"
+
+/* text attribute mask */
+ 
+/**/
+unsigned txtattrmask;
+
+/* text change - attribute change made by prompts */
+
+/**/
+unsigned txtchange;
+
+/* the command stack for use with %_ in prompts */
+ 
+/**/
+unsigned char *cmdstack;
+/**/
+int cmdsp;
+
+/* parser states, for %_ */
+
+static char *cmdnames[] = {
+    "for",      "while",     "repeat",    "select",
+    "until",    "if",        "then",      "else",
+    "elif",     "math",      "cond",      "cmdor",
+    "cmdand",   "pipe",      "errpipe",   "foreach",
+    "case",     "function",  "subsh",     "cursh",
+    "array",    "quote",     "dquote",    "bquote",
+    "cmdsubst", "mathsubst", "elif-then", "heredoc",
+    "heredocd", "brace",     "braceparam",
+};
+ 
+/* The buffer into which an expanded and metafied prompt is being written, *
+ * and its size.                                                           */
+
+static char *buf;
+static int bufspc;
+
+/* bp is the pointer to the current position in the buffer, where the next *
+ * character will be added.                                                */
+
+static char *bp;
+
+/* bp1 is an auxilliary pointer into the buffer, which when non-NULL is *
+ * moved whenever the buffer is reallocated.  It is used when data is   *
+ * being temporarily held in the buffer.                                */
+
+static char *bp1;
+
+/* The format string, for %-expansion. */
+
+static char *fm;
+
+/* Current truncation string (metafied), the length at which truncation *
+ * occurs, and the direction in which it occurs.                        */
+
+static char *truncstr;
+static int trunclen, truncatleft;
+
+/* Current level of nesting of %{ / %} sequences. */
+
+static int dontcount;
+
+/* Strings to use for %r and %R (for the spelling prompt). */
+
+static char *rstring, *Rstring;
+
+/* If non-zero, Inpar, Outpar and Nularg can be added to the buffer. */
+
+static int nonsp;
+
+/* Perform prompt expansion on a string, putting the result in a *
+ * permanently-allocated string.  If ns is non-zero, this string *
+ * may have embedded Inpar and Outpar, which indicate a toggling *
+ * between spacing and non-spacing parts of the prompt, and      *
+ * Nularg, which (in a non-spacing sequence) indicates a         *
+ * `glitch' space.                                               */
+
+/**/
+char *
+promptexpand(char *s, int ns, char *rs, char *Rs)
+{
+    if(!s)
+	return ztrdup("");
+
+    if ((termflags & TERM_UNKNOWN) && (unset(INTERACTIVE)))
+        init_term();
+
+    if (isset(PROMPTSUBST)) {
+	int olderr = errflag;
+
+	HEAPALLOC {
+	    s = dupstring(s);
+	    if (!parsestr(s))
+		singsub(&s);
+	} LASTALLOC;
+	/* Ignore errors in prompt substitution */
+	errflag = olderr;
+    }
+
+    rstring = rs;
+    Rstring = Rs;
+    nonsp = ns;
+    fm = s;
+    bp = buf = zalloc(bufspc = 256);
+    bp1 = NULL;
+    trunclen = 0;
+    putpromptchar(1, '\0');
+    addbufspc(1);
+    if(dontcount)
+	*bp++ = Outpar;
+    *bp = 0;
+    return buf;
+}
+
+/* Perform %- and !-expansion as required on a section of the prompt.  The *
+ * section is ended by an instance of endchar.  If doprint is 0, the valid *
+ * % sequences are merely skipped over, and nothing is stored.             */
+
+/**/
+static int
+putpromptchar(int doprint, int endchar)
+{
+    char *ss, *tmbuf = NULL;
+    int t0, arg, test, sep;
+    struct tm *tm;
+    time_t timet;
+    Nameddir nd;
+
+    for (; *fm && *fm != endchar; fm++) {
+	arg = 0;
+	if (*fm == '%' && isset(PROMPTPERCENT)) {
+	    if (idigit(*++fm)) {
+		arg = zstrtol(fm, &fm, 10);
+	    }
+	    if (*fm == '(') {
+		int tc;
+
+		if (idigit(*++fm)) {
+		    arg = zstrtol(fm, &fm, 10);
+		}
+		test = 0;
+		ss = pwd;
+		switch (tc = *fm) {
+		case 'c':
+		case '.':
+		case '~':
+		    if ((nd = finddir(ss))) {
+			arg--;
+			ss += strlen(nd->dir);
+		    }
+		case '/':
+		case 'C':
+		    for (; *ss; ss++)
+			if (*ss == '/')
+			    arg--;
+		    if (arg <= 0)
+			test = 1;
+		    break;
+		case 't':
+		case 'T':
+		case 'd':
+		case 'D':
+		case 'w':
+		    timet = time(NULL);
+		    tm = localtime(&timet);
+		    switch (tc) {
+		    case 't':
+			test = (arg == tm->tm_min);
+			break;
+		    case 'T':
+			test = (arg == tm->tm_hour);
+			break;
+		    case 'd':
+			test = (arg == tm->tm_mday);
+			break;
+		    case 'D':
+			test = (arg == tm->tm_mon);
+			break;
+		    case 'w':
+			test = (arg == tm->tm_wday);
+			break;
+		    }
+		    break;
+		case '?':
+		    if (lastval == arg)
+			test = 1;
+		    break;
+		case '#':
+		    if (geteuid() == arg)
+			test = 1;
+		    break;
+		case 'g':
+		    if (getegid() == arg)
+			test = 1;
+		    break;
+		case 'L':
+		    if (shlvl >= arg)
+			test = 1;
+		    break;
+		case 'S':
+		    if (time(NULL) - shtimer.tv_sec >= arg)
+			test = 1;
+		    break;
+		case 'v':
+		    if (arrlen(psvar) >= arg)
+			test = 1;
+		    break;
+		case '_':
+		    test = (cmdsp >= arg);
+		    break;
+		case '!':
+		    test = privasserted();
+		    break;
+		default:
+		    test = -1;
+		    break;
+		}
+		if (!*fm || !(sep = *++fm))
+		    return 0;
+		fm++;
+		if (!putpromptchar(test == 1 && doprint, sep) || !*++fm ||
+		    !putpromptchar(test == 0 && doprint, ')')) {
+		    return 0;
+		}
+		continue;
+	    }
+	    if (!doprint)
+		switch(*fm) {
+		  case '[':
+		    while(idigit(*++fm));
+		    while(*++fm != ']');
+		    continue;
+		  case '<':
+		    while(*++fm != '<');
+		    continue;
+		  case '>':
+		    while(*++fm != '>');
+		    continue;
+		  case 'D':
+		    if(fm[1]=='{')
+			while(*++fm != '}');
+		    continue;
+		  default:
+		    continue;
+		}
+	    switch (*fm) {
+	    case '~':
+		if ((nd = finddir(pwd))) {
+		    char *t = tricat("~", nd->nam, pwd + strlen(nd->dir));
+		    stradd(t);
+		    zsfree(t);
+		    break;
+		}
+	    case 'd':
+	    case '/':
+		stradd(pwd);
+		break;
+	    case 'c':
+	    case '.':
+	        {
+		    char *t;
+
+		    if ((nd = finddir(pwd)))
+			t = tricat("~", nd->nam, pwd + strlen(nd->dir));
+		    else
+			t = ztrdup(pwd);
+		    if (!arg)
+			arg++;
+		    for (ss = t + strlen(t); ss > t; ss--)
+			if (*ss == '/' && !--arg) {
+			    ss++;
+			    break;
+			}
+		    if(*ss == '/' && ss[1] && ss != t)
+			ss++;
+		    stradd(ss);
+		    zsfree(t);
+		    break;
+		}
+	    case 'C':
+		if (!arg)
+		    arg++;
+		for (ss = pwd + strlen(pwd); ss > pwd; ss--)
+		    if (*ss == '/' && !--arg) {
+			ss++;
+			break;
+		    }
+		if (*ss == '/' && ss[1] && (ss != pwd))
+		    ss++;
+		stradd(ss);
+		break;
+	    case 'h':
+	    case '!':
+		addbufspc(DIGBUFSIZE);
+		sprintf(bp, "%d", curhist);
+		bp += strlen(bp);
+		break;
+	    case 'M':
+		stradd(hostnam);
+		break;
+	    case 'm':
+		if (!arg)
+		    arg++;
+		for (ss = hostnam; *ss; ss++)
+		    if (*ss == '.' && !--arg)
+			break;
+		t0 = *ss;
+		*ss = '\0';
+		stradd(hostnam);
+		*ss = t0;
+		break;
+	    case 'S':
+		txtchangeset(TXTSTANDOUT, TXTNOSTANDOUT);
+		txtset(TXTSTANDOUT);
+		tsetcap(TCSTANDOUTBEG, 1);
+		break;
+	    case 's':
+		txtchangeset(TXTNOSTANDOUT, TXTSTANDOUT);
+		txtset(TXTDIRTY);
+		txtunset(TXTSTANDOUT);
+		tsetcap(TCSTANDOUTEND, 1);
+		break;
+	    case 'B':
+		txtchangeset(TXTBOLDFACE, TXTNOBOLDFACE);
+		txtset(TXTDIRTY);
+		txtset(TXTBOLDFACE);
+		tsetcap(TCBOLDFACEBEG, 1);
+		break;
+	    case 'b':
+		txtchangeset(TXTNOBOLDFACE, TXTBOLDFACE);
+		txtchangeset(TXTNOSTANDOUT, TXTSTANDOUT);
+		txtchangeset(TXTNOUNDERLINE, TXTUNDERLINE);
+		txtset(TXTDIRTY);
+		txtunset(TXTBOLDFACE);
+		tsetcap(TCALLATTRSOFF, 1);
+		break;
+	    case 'U':
+		txtchangeset(TXTUNDERLINE, TXTNOUNDERLINE);
+		txtset(TXTUNDERLINE);
+		tsetcap(TCUNDERLINEBEG, 1);
+		break;
+	    case 'u':
+		txtchangeset(TXTNOUNDERLINE, TXTUNDERLINE);
+		txtset(TXTDIRTY);
+		txtunset(TXTUNDERLINE);
+		tsetcap(TCUNDERLINEEND, 1);
+		break;
+	    case '[':
+                if (idigit(*++fm))
+                    trunclen = zstrtol(fm, &fm, 10);
+                else
+                    trunclen = arg;
+                if (trunclen) {
+		    truncatleft = *fm && *fm != ']' && *fm++ == '<';
+		    bp1 = bp;
+		    while (*fm && *fm != ']') {
+			if (*fm == '\\' && fm[1])
+			    ++fm;
+			addbufspc(1);
+			*bp++ = *fm++;
+		    }
+		    addbufspc(2);
+		    if (bp1 == bp)
+			*bp++ = '<';
+                    *bp = '\0';
+		    zsfree(truncstr);
+                    truncstr = ztrdup(bp = bp1);
+		    bp1 = NULL;
+                } else {
+		    while (*fm && *fm != ']') {
+			if (*fm == '\\' && fm[1])
+			    fm++;
+			fm++;
+		    }
+		}
+		if(!*fm)
+		    return 0;
+		break;
+	    case '<':
+	    case '>':
+		if((trunclen = arg)) {
+		    char ch = *fm++;
+		    truncatleft = ch == '<';
+		    bp1 = bp;
+		    while (*fm && *fm != ch) {
+			if (*fm == '\\' && fm[1])
+			    ++fm;
+			addbufspc(1);
+			*bp++ = *fm++;
+		    }
+		    addbufspc(1);
+                    *bp = '\0';
+		    zsfree(truncstr);
+                    truncstr = ztrdup(bp = bp1);
+		    bp1 = NULL;
+		} else {
+		    char ch = *fm++;
+		    while(*fm && *fm != ch) {
+			if (*fm == '\\' && fm[1])
+			    fm++;
+			fm++;
+		    }
+		}
+		if(!*fm)
+		    return 0;
+		break;
+	    case '{': /*}*/
+		if (!dontcount++ && nonsp) {
+		    addbufspc(1);
+		    *bp++ = Inpar;
+		}
+		break;
+	    case /*{*/ '}':
+		if (dontcount && !--dontcount && nonsp) {
+		    addbufspc(1);
+		    *bp++ = Outpar;
+		}
+		break;
+	    case 't':
+	    case '@':
+	    case 'T':
+	    case '*':
+	    case 'w':
+	    case 'W':
+	    case 'D':
+		{
+		    char *tmfmt, *dd;
+
+		    switch (*fm) {
+		    case 'T':
+			tmfmt = "%K:%M";
+			break;
+		    case '*':
+			tmfmt = "%K:%M:%S";
+			break;
+		    case 'w':
+			tmfmt = "%a %f";
+			break;
+		    case 'W':
+			tmfmt = "%m/%d/%y";
+			break;
+		    case 'D':
+			if (fm[1] == '{') /*}*/ {
+			    for (ss = fm + 2; *ss && *ss != /*{*/ '}'; ss++)
+				if(*ss == '\\' && ss[1])
+				    ss++;
+			    dd = tmfmt = tmbuf = zalloc(ss - fm);
+			    for (ss = fm + 2; *ss && *ss != /*{*/ '}'; ss++) {
+				if(*ss == '\\' && ss[1])
+				    ss++;
+				*dd++ = *ss;
+			    }
+			    *dd = 0;
+			    fm = ss - !*ss;
+			} else
+			    tmfmt = "%y-%m-%d";
+			break;
+		    default:
+			tmfmt = "%l:%M%p";
+			break;
+		    }
+		    timet = time(NULL);
+		    tm = localtime(&timet);
+		    for(t0=80; ; t0*=2) {
+			addbufspc(t0);
+			if(ztrftime(bp, t0, tmfmt, tm) != t0)
+			    break;
+		    }
+		    bp += strlen(bp);
+		    free(tmbuf);
+		    tmbuf = NULL;
+		    break;
+		}
+	    case 'n':
+		stradd(get_username());
+		break;
+	    case 'l':
+		if (*ttystrname) {
+		    ss = (strncmp(ttystrname, "/dev/tty", 8) ?
+			    ttystrname + 5 : ttystrname + 8);
+		    stradd(ss);
+		} else
+		    stradd("()");
+		break;
+	    case 'L':
+		addbufspc(DIGBUFSIZE);
+		sprintf(bp, "%ld", (long)shlvl);
+		bp += strlen(bp);
+		break;
+	    case '?':
+		addbufspc(DIGBUFSIZE);
+		sprintf(bp, "%ld", (long)lastval);
+		bp += strlen(bp);
+		break;
+	    case '%':
+	    case ')':
+		addbufspc(1);
+		*bp++ = *fm;
+		break;
+	    case '#':
+		addbufspc(1);
+		*bp++ = privasserted() ? '#' : '%';
+		break;
+	    case 'v':
+		if (!arg)
+		    arg = 1;
+		if (arrlen(psvar) >= arg)
+		    stradd(psvar[arg - 1]);
+		break;
+	    case 'E':
+                tsetcap(TCCLEAREOL, 1);
+		break;
+	    case '_':
+		if (cmdsp) {
+		    if (arg > cmdsp || arg <= 0)
+			arg = cmdsp;
+		    for (t0 = cmdsp - arg; arg--; t0++) {
+			stradd(cmdnames[cmdstack[t0]]);
+			if (arg) {
+			    addbufspc(1);
+			    *bp++=' ';
+			}
+		    }
+		}
+		break;
+	    case 'r':
+		if(rstring)
+		    stradd(rstring);
+		break;
+	    case 'R':
+		if(Rstring)
+		    stradd(Rstring);
+		break;
+	    case '\0':
+		return 0;
+	    case Meta:
+		fm++;
+		break;
+	    }
+	} else if(*fm == '!' && isset(PROMPTBANG)) {
+	    if(doprint)
+		if(fm[1] == '!') {
+		    fm++;
+		    addbufspc(1);
+		    pputc('!');
+		} else {
+		    addbufspc(DIGBUFSIZE);
+		    sprintf(bp, "%d", curhist);
+		    bp += strlen(bp);
+		}
+	} else {
+	    char c = *fm == Meta ? *++fm ^ 32 : *fm;
+
+	    if (doprint) {
+		addbufspc(1);
+		pputc(c);
+	    }
+	}
+    }
+
+    return *fm;
+}
+
+/* pputc adds a character to the buffer, metafying.  There must *
+ * already be space.                                            */
+
+/**/
+static void
+pputc(char c)
+{
+    if(imeta(STOUC(c))) {
+	*bp++ = Meta;
+	c ^= 32;
+    }
+    *bp++ = c;
+}
+
+/* Make sure there is room for `need' more characters in the buffer. */
+
+/**/
+static void
+addbufspc(int need)
+{
+    need *= 2;   /* for metafication */
+    if((bp - buf) + need > bufspc) {
+	int bo = bp - buf;
+	int bo1 = bp1 ? bp1 - buf : -1;
+
+	if(need & 255)
+	    need = (need | 255) + 1;
+	buf = realloc(buf, bufspc += need);
+	bp = buf + bo;
+	if(bo1 != -1)
+	    bp1 = buf + bo1;
+    }
+}
+
+/* stradd() adds a metafied string to the prompt, *
+ * in a visible representation, doing truncation. */
+
+/**/
+void
+stradd(char *d)
+{
+    /* dlen is the full length of the string we want to add */
+    int dlen = niceztrlen(d);
+    char *ps, *pd, *pc, *t;
+    int tlen, maxlen;
+    addbufspc(dlen);
+    /* This loop puts the nice representation of the string into the prompt *
+     * buffer.  It might be modified later.  Note that bp isn't changed.    */
+    for(ps=d, pd=bp; *ps; ps++)
+	for(pc=nicechar(*ps == Meta ? STOUC(*++ps)^32 : STOUC(*ps)); *pc; pc++)
+	    *pd++ = *pc;
+    if(!trunclen || dlen <= trunclen) {
+	/* No truncation is needed, so update bp and return, *
+	 * leaving the full string in the prompt.            */
+	bp += dlen;
+	return;
+    }
+    /* We need to truncate.  t points to the truncation string -- which is *
+     * inserted literally, without nice representation.  tlen is its       *
+     * length, and maxlen is the amout of the main string that we want to  *
+     * keep.  Note that if the truncation string is longer than the        *
+     * truncation length (tlen > trunclen), the truncation string is used  *
+     * in full.                                                            */
+    addbufspc(tlen = ztrlen(t = truncstr));
+    maxlen = tlen < trunclen ? trunclen - tlen : 0;
+    if(truncatleft) {
+	memmove(bp + strlen(t), bp + dlen - maxlen, maxlen);
+	while(*t)
+	    *bp++ = *t++;
+	bp += maxlen;
+    } else {
+	bp += maxlen;
+	while(*t)
+	    *bp++ = *t++;
+    }
+}
+
+/* tsetcap(), among other things, can write a termcap string into the buffer. */
+
+/**/
+void
+tsetcap(int cap, int flag)
+{
+    if (!(termflags & TERM_SHORT) && tcstr[cap]) {
+	switch(flag) {
+	case -1:
+	    tputs(tcstr[cap], 1, putraw);
+	    break;
+	case 0:
+	    tputs(tcstr[cap], 1, putshout);
+	    break;
+	case 1:
+	    if (!dontcount && nonsp) {
+		addbufspc(1);
+		*bp++ = Inpar;
+	    }
+	    tputs(tcstr[cap], 1, putstr);
+	    if (!dontcount && nonsp) {
+		int glitch = 0;
+
+		if (cap == TCSTANDOUTBEG || cap == TCSTANDOUTEND)
+		    glitch = tgetnum("sg");
+		else if (cap == TCUNDERLINEBEG || cap == TCUNDERLINEEND)
+		    glitch = tgetnum("ug");
+		if(glitch < 0)
+		    glitch = 0;
+		addbufspc(glitch + 1);
+		while(glitch--)
+		    *bp++ = Nularg;
+		*bp++ = Outpar;
+	    }
+	    break;
+	}
+
+	if (txtisset(TXTDIRTY)) {
+	    txtunset(TXTDIRTY);
+	    if (txtisset(TXTBOLDFACE) && cap != TCBOLDFACEBEG)
+		tsetcap(TCBOLDFACEBEG, flag);
+	    if (txtisset(TXTSTANDOUT))
+		tsetcap(TCSTANDOUTBEG, flag);
+	    if (txtisset(TXTUNDERLINE))
+		tsetcap(TCUNDERLINEBEG, flag);
+	}
+    }
+}
+
+/**/
+int
+putstr(int d)
+{
+    addbufspc(1);
+    pputc(d);
+    return 0;
+}
+
+/* Count height etc. of a prompt string returned by promptexpand(). *
+ * This depends on the current terminal width, and tabs and         *
+ * newlines require nontrivial processing.                          */
+
+/**/
+void
+countprompt(char *str, int *wp, int *hp)
+{
+    int w = 0, h = 1;
+    int s = 1;
+    for(; *str; str++) {
+	if(*str == Meta)
+	    str++;
+	if(*str == Inpar)
+	    s = 0;
+	else if(*str == Outpar)
+	    s = 1;
+	else if(*str == Nularg)
+	    w++;
+	else if(s) {
+	    if(*str == '\t')
+		w = (w | 7) + 1;
+	    else if(*str == '\n')
+		w = columns;
+	    else
+		w++;
+	}
+	if(w >= columns) {
+	    w = 0;
+	    h++;
+	}
+    }
+    if(wp)
+	*wp = w;
+    if(hp)
+	*hp = h;
+}
diff --git a/Src/prototypes.h b/Src/prototypes.h
new file mode 100644
index 000000000..f7f560111
--- /dev/null
+++ b/Src/prototypes.h
@@ -0,0 +1,120 @@
+/*
+ * prototypes.h - prototypes header file
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#ifndef HAVE_STDLIB_H
+char *malloc _((size_t));
+char *realloc _((void *, size_t));
+char *calloc _((size_t, size_t));
+#endif
+
+#ifndef HAVE_TERMCAP_H
+extern int tgetent _((char *bp, char *name));
+extern int tgetnum _((char *id));
+extern int tgetflag _((char *id));
+extern char *tgetstr _((char *id, char **area));
+extern char *tgoto _((char *cm, int destcol, int destline));
+extern int tputs _((char *cp, int affcnt, int (*outc) (int)));
+#endif
+
+/* MISSING PROTOTYPES FOR VARIOUS OPERATING SYSTEMS */
+
+/* HP/UX 9 c89 */
+#if defined(__hpux) && defined(_XPG3) && !defined(_POSIX1_1988)
+# define WRITE_ARG_2_T void *
+#else
+# define WRITE_ARG_2_T char *
+#endif
+
+#if defined(__hpux) && defined(_HPUX_SOURCE)
+# define SELECT_ARG_2_T int *
+#else
+# define SELECT_ARG_2_T fd_set *
+#endif
+
+#ifdef __osf__
+char *mktemp _((char *));
+#endif
+
+#if defined(__osf__) && defined(__alpha) && defined(__GNUC__)
+/* Digital cc does not need these prototypes, gcc does need them */
+# ifndef HAVE_IOCTL_PROTO
+int ioctl _((int d, unsigned long request, void *argp));
+# endif
+int mknod _((const char *pathname, int mode, dev_t device));
+int nice _((int increment));
+int select _((int nfds, fd_set * readfds, fd_set * writefds, fd_set * exceptfds, struct timeval *timeout));
+#endif
+
+#if defined(DGUX) && defined(__STDC__)
+/* Just plain missing. */
+extern int getrlimit _((int resource, struct rlimit *rlp));
+extern int setrlimit _((int resource, const struct rlimit *rlp));
+extern int getrusage _((int who, struct rusage *rusage));
+extern int gettimeofday _((struct timeval *tv, struct timezone *tz));
+extern int wait3 _((union wait *wait_status, int options, struct rusage *rusage));
+extern int getdomainname _((char *name, int maxlength));
+extern int select _((int nfds, fd_set * readfds, fd_set * writefds, fd_set * exceptfds, struct timeval *timeout));
+#endif /* DGUX and __STDC__ */
+
+#ifdef __NeXT__
+extern pid_t getppid(void);
+#endif
+
+#if defined(__sun__) && !defined(__SVR4)  /* SunOS */
+extern char *strerror _((int errnum));
+#endif
+
+/**************************************************/
+/*** prototypes for functions built in compat.c ***/
+#ifndef HAVE_STRSTR
+extern char *strstr _((const char *s, const char *t));
+#endif
+
+#ifndef HAVE_GETHOSTNAME
+extern int gethostname _((char *name, size_t namelen));
+#endif
+
+#ifndef HAVE_GETTIMEOFDAY
+extern int gettimeofday _((struct timeval *tv, struct timezone *tz));
+#endif
+
+#ifndef HAVE_DIFFTIME
+extern double difftime _((time_t t2, time_t t1));
+#endif
+
+#ifndef HAVE_STRERROR
+extern char *strerror _((int errnum));
+#endif
+
+/*** end of prototypes for functions in compat.c ***/
+/***************************************************/
+
+#ifndef HAVE_MEMMOVE
+extern void bcopy _((const void *, void *, int));
+#endif
diff --git a/Src/signals.c b/Src/signals.c
new file mode 100644
index 000000000..5dc19dd22
--- /dev/null
+++ b/Src/signals.c
@@ -0,0 +1,748 @@
+/*
+ * signals.c - signals handling code
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "signals.pro"
+ 
+/* Array describing the state of each signal: an element contains *
+ * 0 for the default action or some ZSIG_* flags ored together.   */
+
+/**/
+int sigtrapped[VSIGCOUNT];
+
+/* trap functions for each signal */
+
+/**/
+List sigfuncs[VSIGCOUNT];
+
+/* Variables used by signal queueing */
+
+/**/
+int queueing_enabled, queue_front, queue_rear;
+/**/
+int signal_queue[MAX_QUEUE_SIZE];
+/**/
+sigset_t signal_mask_queue[MAX_QUEUE_SIZE];
+
+/* This is only used on machines that don't understand signal sets.  *
+ * On SYSV machines this will represent the signals that are blocked *
+ * (held) using sighold.  On machines which can't block signals at   *
+ * all, we will simulate this by ignoring them and remembering them  *
+ * in this variable.                                                 */
+#if !defined(POSIX_SIGNALS) && !defined(BSD_SIGNALS)
+static sigset_t blocked_set;
+#endif
+
+#ifdef POSIX_SIGNALS
+# define signal_jmp_buf       sigjmp_buf
+# define signal_setjmp(b)     sigsetjmp((b),1)
+# define signal_longjmp(b,n)  siglongjmp((b),(n))
+#else
+# define signal_jmp_buf       jmp_buf
+# define signal_setjmp(b)     setjmp(b)
+# define signal_longjmp(b,n)  longjmp((b),(n))
+#endif
+ 
+#ifdef NO_SIGNAL_BLOCKING
+# define signal_process(sig)  signal_ignore(sig)
+# define signal_reset(sig)    install_handler(sig)
+#else
+# define signal_process(sig)  ;
+# define signal_reset(sig)    ;
+#endif
+
+/* Install signal handler for given signal.           *
+ * If possible, we want to make sure that interrupted *
+ * system calls are not restarted.                    */
+
+/**/
+void
+install_handler(int sig)
+{
+#ifdef POSIX_SIGNALS
+    struct sigaction act;
+ 
+    act.sa_handler = (SIGNAL_HANDTYPE) handler;
+    sigemptyset(&act.sa_mask);        /* only block sig while in handler */
+    act.sa_flags = 0;
+# ifdef SA_INTERRUPT                  /* SunOS 4.x */
+    if (interact)
+        act.sa_flags |= SA_INTERRUPT; /* make sure system calls are not restarted */
+# endif
+    sigaction(sig, &act, (struct sigaction *)NULL);
+#else
+# ifdef BSD_SIGNALS
+    struct sigvec vec;
+ 
+    vec.sv_handler = (SIGNAL_HANDTYPE) handler;
+    vec.sv_mask = sigmask(sig);    /* mask out this signal while in handler    */
+#  ifdef SV_INTERRUPT
+    vec.sv_flags = SV_INTERRUPT;   /* make sure system calls are not restarted */
+#  endif
+    sigvec(sig, &vec, (struct sigvec *)NULL);
+# else
+#  ifdef SYSV_SIGNALS
+    /* we want sigset rather than signal because it will   *
+     * block sig while in handler.  signal usually doesn't */
+    sigset(sig, handler);
+#  else  /* NO_SIGNAL_BLOCKING (bummer) */
+    signal(sig, handler);
+
+#  endif /* SYSV_SIGNALS  */
+# endif  /* BSD_SIGNALS   */
+#endif   /* POSIX_SIGNALS */
+}
+
+/* enable ^C interrupts */
+ 
+/**/
+void
+intr(void)
+{
+    if (interact)
+        install_handler(SIGINT);
+}
+
+#if 0
+/* disable ^C interrupts */
+ 
+/**/
+void
+nointr(void)
+{
+    if (interact)
+        signal_ignore(SIGINT);
+}
+#endif
+ 
+/* temporarily block ^C interrupts */
+ 
+/**/
+void
+holdintr(void)
+{
+    if (interact)
+        signal_block(signal_mask(SIGINT));
+}
+
+/* release ^C interrupts */
+ 
+/**/
+void
+noholdintr(void)
+{
+    if (interact)
+        signal_unblock(signal_mask(SIGINT));
+}
+ 
+/* create a signal mask containing *
+ * only the given signal           */
+ 
+/**/
+sigset_t
+signal_mask(int sig)
+{
+    sigset_t set;
+ 
+    sigemptyset(&set);
+    if (sig)
+        sigaddset(&set, sig);
+    return set;
+}
+
+/* Block the signals in the given signal *
+ * set. Return the old signal set.       */
+ 
+/**/
+sigset_t
+signal_block(sigset_t set)
+{
+    sigset_t oset;
+ 
+#ifdef POSIX_SIGNALS
+    sigprocmask(SIG_BLOCK, &set, &oset);
+#else
+# ifdef BSD_SIGNALS
+    oset = sigblock(set);
+# else
+#  ifdef SYSV_SIGNALS
+    int i;
+ 
+    oset = blocked_set;
+    for (i = 1; i <= NSIG; ++i) {
+        if (sigismember(&set, i) && !sigismember(&blocked_set, i)) {
+            sigaddset(&blocked_set, i);
+            sighold(i);
+        }
+    }
+#  else  /* NO_SIGNAL_BLOCKING */
+/* We will just ignore signals if the system doesn't have *
+ * the ability to block them.                             */
+    int i;
+
+    oset = blocked_set;
+    for (i = 1; i <= NSIG; ++i) {
+        if (sigismember(&set, i) && !sigismember(&blocked_set, i)) {
+            sigaddset(&blocked_set, i);
+            signal_ignore(i);
+        }
+   }
+#  endif /* SYSV_SIGNALS  */
+# endif  /* BSD_SIGNALS   */
+#endif   /* POSIX_SIGNALS */
+ 
+    return oset;
+}
+
+/* Unblock the signals in the given signal *
+ * set. Return the old signal set.         */
+
+/**/
+sigset_t
+signal_unblock(sigset_t set)
+{
+    sigset_t oset;
+ 
+#ifdef POSIX_SIGNALS
+    sigprocmask(SIG_UNBLOCK, &set, &oset);
+#else
+# ifdef BSD_SIGNALS
+    sigfillset(&oset);
+    oset = sigsetmask(oset);
+    sigsetmask(oset & ~set);
+# else
+#  ifdef SYSV_SIGNALS
+    int i;
+ 
+    oset = blocked_set;
+    for (i = 1; i <= NSIG; ++i) {
+        if (sigismember(&set, i) && sigismember(&blocked_set, i)) {
+            sigdelset(&blocked_set, i);
+            sigrelse(i);
+        }
+    }
+#  else  /* NO_SIGNAL_BLOCKING */
+/* On systems that can't block signals, we are just ignoring them.  So *
+ * to unblock signals, we just reenable the signal handler for them.   */
+    int i;
+
+    oset = blocked_set;
+    for (i = 1; i <= NSIG; ++i) {
+        if (sigismember(&set, i) && sigismember(&blocked_set, i)) {
+            sigdelset(&blocked_set, i);
+            install_handler(i);
+        }
+   }
+#  endif /* SYSV_SIGNALS  */
+# endif  /* BSD_SIGNALS   */
+#endif   /* POSIX_SIGNALS */
+ 
+    return oset;
+}
+
+/* set the process signal mask to *
+ * be the given signal mask       */
+
+/**/
+sigset_t
+signal_setmask(sigset_t set)
+{
+    sigset_t oset;
+ 
+#ifdef POSIX_SIGNALS
+    sigprocmask(SIG_SETMASK, &set, &oset);
+#else
+# ifdef BSD_SIGNALS
+    oset = sigsetmask(set);
+# else
+#  ifdef SYSV_SIGNALS
+    int i;
+ 
+    oset = blocked_set;
+    for (i = 1; i <= NSIG; ++i) {
+        if (sigismember(&set, i) && !sigismember(&blocked_set, i)) {
+            sigaddset(&blocked_set, i);
+            sighold(i);
+        } else if (!sigismember(&set, i) && sigismember(&blocked_set, i)) {
+            sigdelset(&blocked_set, i);
+            sigrelse(i);
+        }
+    }
+#  else  /* NO_SIGNAL_BLOCKING */
+    int i;
+
+    oset = blocked_set;
+    for (i = 1; i < NSIG; ++i) {
+        if (sigismember(&set, i) && !sigismember(&blocked_set, i)) {
+            sigaddset(&blocked_set, i);
+            signal_ignore(i);
+        } else if (!sigismember(&set, i) && sigismember(&blocked_set, i)) {
+            sigdelset(&blocked_set, i);
+            install_handler(i);
+        }
+    }
+#  endif /* SYSV_SIGNALS  */
+# endif  /* BSD_SIGNALS   */
+#endif   /* POSIX_SIGNALS */
+ 
+    return oset;
+}
+
+#if defined(NO_SIGNAL_BLOCKING)
+static int suspend_longjmp = 0;
+static signal_jmp_buf suspend_jmp_buf;
+#endif
+ 
+/**/
+int
+signal_suspend(int sig, int sig2)
+{
+    int ret;
+ 
+#ifdef POSIX_SIGNALS
+    sigset_t set;
+
+    sigfillset(&set);
+    sigdelset(&set, sig);
+    sigdelset(&set, SIGHUP);  /* still don't know why we add this? */
+    if (sig2)
+        sigdelset(&set, sig2);
+    ret = sigsuspend(&set);
+#else
+# ifdef BSD_SIGNALS
+    sigset_t set;
+
+    sigfillset(&set);
+    sigdelset(&set, sig);
+    if (sig2)
+      sigdelset(&set, sig2);
+    ret = sigpause(set);
+# else
+#  ifdef SYSV_SIGNALS
+    ret = sigpause(sig);
+
+#  else  /* NO_SIGNAL_BLOCKING */
+    /* need to use signal_longjmp to make this race-free *
+     * between the child_unblock() and pause()           */
+    if (signal_setjmp(suspend_jmp_buf) == 0) {
+        suspend_longjmp = 1;   /* we want to signal_longjmp after catching signal */
+        child_unblock();       /* do we need to unblock sig2 as well?             */
+        ret = pause();
+    }
+    suspend_longjmp = 0;       /* turn off using signal_longjmp since we are past *
+                                * the pause() function.                           */
+#  endif /* SYSV_SIGNALS  */
+# endif  /* BSD_SIGNALS   */
+#endif   /* POSIX_SIGNALS */
+ 
+    return ret;
+}
+
+/* What flavor of waitpid/wait3/wait shall we use? */
+ 
+#ifdef HAVE_WAITPID
+# define  WAIT(pid, statusp, options) waitpid(pid, statusp, options)
+#else
+# ifdef HAVE_WAIT3
+#  define WAIT(pid, statusp, options) wait3((void *) statusp, options, NULL)
+# else
+#  define WAIT(pid, statusp, options) wait(statusp)
+# endif
+#endif
+
+/* the signal handler */
+ 
+/**/
+RETSIGTYPE
+handler(int sig)
+{
+    sigset_t newmask, oldmask;
+
+#if defined(NO_SIGNAL_BLOCKING)
+    int do_jump;
+    signal_jmp_buf jump_to;
+#endif
+ 
+    signal_process(sig);
+ 
+    sigfillset(&newmask);
+    oldmask = signal_block(newmask);        /* Block all signals temporarily           */
+ 
+#if defined(NO_SIGNAL_BLOCKING)
+    do_jump = suspend_longjmp;              /* do we need to longjmp to signal_suspend */
+    suspend_longjmp = 0;                    /* In case a SIGCHLD somehow arrives       */
+
+    if (sig == SIGCHLD) {                   /* Traps can cause nested child_suspend()  */
+        if (do_jump)
+            jump_to = suspend_jmp_buf;      /* Copy suspend_jmp_buf                    */
+    }
+#endif
+
+    if (queueing_enabled) {           /* Are we queueing signals now?      */
+        int temp_rear = ++queue_rear % MAX_QUEUE_SIZE;
+
+	DPUTS(temp_rear == queue_front, "BUG: signal queue full");
+        if (temp_rear != queue_front) { /* Make sure it's not full (extremely unlikely) */
+            queue_rear = temp_rear;                  /* ok, not full, so add to queue   */
+            signal_queue[queue_rear] = sig;          /* save signal caught              */
+            signal_mask_queue[queue_rear] = oldmask; /* save current signal mask        */
+        }
+        signal_reset(sig);
+        return;
+    }
+ 
+    signal_setmask(oldmask);          /* Reset signal mask, signal traps ok now */
+ 
+    switch (sig) {
+    case SIGCHLD:
+
+	/* keep WAITING until no more child processes to reap */
+	for (;;)
+	  cont: {
+            int old_errno = errno; /* save the errno, since WAIT may change it */
+	    int status;
+	    Job jn;
+	    Process pn;
+            pid_t pid;
+	    pid_t *procsubpid = &cmdoutpid;
+	    int *procsubval = &cmdoutval;
+	    struct execstack *es = exstack;
+
+            pid = WAIT(-1, &status, WNOHANG|WUNTRACED);  /* reap the child process */
+
+            if (!pid)  /* no more children to reap */
+                break;
+
+	    /* check if child returned was from process substitution */
+	    for (;;) {
+		if (pid == *procsubpid) {
+		    *procsubpid = 0;
+		    if (WIFSIGNALED(status))
+			*procsubval = (0200 | WTERMSIG(status));
+		    else
+			*procsubval = WEXITSTATUS(status);
+		    times(&shtms);
+		    goto cont;
+		}
+		if (!es)
+		    break;
+		procsubpid = &es->cmdoutpid;
+		procsubval = &es->cmdoutval;
+		es = es->next;
+	    }
+
+	    /* check for WAIT error */
+            if (pid == -1) {
+                if (errno != ECHILD)
+                    zerr("wait failed: %e", NULL, errno);
+                errno = old_errno;    /* WAIT changed errno, so restore the original */
+                break;
+            }
+
+	    /* Find the process and job containing this pid and update it. */
+	    if (findproc(pid, &jn, &pn)) {
+		update_process(pn, status);
+		update_job(jn);
+	    } else {
+		/* If not found, update the shell record of time spent by
+		 * children in sub processes anyway:  otherwise, this
+		 * will get added on to the next found process that terminates.
+		 */
+		times(&shtms);
+	    }
+        }
+        break;
+ 
+    case SIGHUP:
+        if (sigtrapped[SIGHUP])
+            dotrap(SIGHUP);
+        else {
+            stopmsg = 1;
+            zexit(SIGHUP, 1);
+        }
+        break;
+ 
+    case SIGINT:
+        if (sigtrapped[SIGINT])
+            dotrap(SIGINT);
+        else {
+	    if ((isset(PRIVILEGED) || isset(RESTRICTED)) &&
+		isset(INTERACTIVE) && noerrexit < 0)
+		zexit(SIGINT, 1);
+            if (list_pipe || chline || simple_pline) {
+                breaks = loops;
+                errflag = 1;
+		inerrflush();
+            }
+        }
+        break;
+
+#ifdef SIGWINCH
+    case SIGWINCH:
+        adjustwinsize();  /* check window size and adjust */
+	if (sigtrapped[SIGWINCH])
+	    dotrap(SIGWINCH);
+        break;
+#endif
+
+    case SIGALRM:
+        if (sigtrapped[SIGALRM]) {
+	    int tmout;
+            dotrap(SIGALRM);
+            if ((tmout = getiparam("TMOUT")))
+                alarm(tmout);           /* reset the alarm */
+        } else {
+	    int idle = ttyidlegetfn(NULL);
+	    int tmout = getiparam("TMOUT");
+	    if (idle >= 0 && idle < tmout)
+		alarm(tmout - idle);
+	    else {
+		errflag = noerrs = 0;
+		zerr("timeout", NULL, 0);
+		errflag = 0;
+		stopmsg = 1;
+		zexit(SIGALRM, 1);
+	    }
+        }
+        break;
+ 
+    default:
+        dotrap(sig);
+        break;
+    }   /* end of switch(sig) */
+ 
+    signal_reset(sig);
+
+/* This is used to make signal_suspend() race-free */
+#if defined(NO_SIGNAL_BLOCKING)
+    if (do_jump)
+        signal_longjmp(jump_to, 1);
+#endif
+
+} /* handler */
+
+ 
+/* SIGHUP any jobs left running */
+ 
+/**/
+void
+killrunjobs(int from_signal)
+{
+    int i, killed = 0;
+ 
+    if (unset(HUP))
+        return;
+    for (i = 1; i < MAXJOB; i++)
+        if ((from_signal || i != thisjob) && (jobtab[i].stat & STAT_LOCKED) &&
+            !(jobtab[i].stat & STAT_NOPRINT) &&
+            !(jobtab[i].stat & STAT_STOPPED)) {
+            if (killpg(jobtab[i].gleader, SIGHUP) != -1)
+                killed++;
+        }
+    if (killed)
+        zerr("warning: %d jobs SIGHUPed", NULL, killed);
+}
+
+
+/* send a signal to a job (simply involves kill if monitoring is on) */
+ 
+/**/
+int
+killjb(Job jn, int sig)
+{
+    Process pn;
+    int err = 0;
+ 
+    if (jobbing) {
+        if (jn->stat & STAT_SUPERJOB) {
+            if (sig == SIGCONT) {
+                for (pn = jobtab[jn->other].procs; pn; pn = pn->next)
+                    kill(pn->pid, sig);
+ 
+                for (pn = jn->procs; pn->next; pn = pn->next)
+                    err = kill(pn->pid, sig);
+ 
+                return err;
+            }
+ 
+            killpg(jobtab[jn->other].gleader, sig);
+            return killpg(jn->gleader, sig);
+        }
+        else
+            return (killpg(jn->gleader, sig));
+    }
+    for (pn = jn->procs; pn; pn = pn->next)
+        if ((err = kill(pn->pid, sig)) == -1 && errno != ESRCH)
+            return -1;
+    return err;
+}
+
+/**/
+int
+settrap(int sig, List l)
+{
+    if (sig == -1)
+        return 1;
+    if (jobbing && (sig == SIGTTOU || sig == SIGTSTP || sig == SIGTTIN)) {
+        zerr("can't trap SIG%s in interactive shells", sigs[sig], 0);
+        return 1;
+    }
+    if (sigfuncs[sig])
+	unsettrap(sig);
+    sigfuncs[sig] = l;
+    if (!l) {
+	sigtrapped[sig] = ZSIG_IGNORED;
+        if (sig && sig <= SIGCOUNT &&
+#ifdef SIGWINCH
+            sig != SIGWINCH &&
+#endif
+            sig != SIGCHLD)
+            signal_ignore(sig);
+    } else {
+        sigtrapped[sig] = ZSIG_TRAPPED;
+        if (sig && sig <= SIGCOUNT &&
+#ifdef SIGWINCH
+            sig != SIGWINCH &&
+#endif
+            sig != SIGCHLD)
+            install_handler(sig);
+    }
+    return 0;
+}
+
+/**/
+void
+unsettrap(int sig)
+{
+    int trapped;
+
+    if (sig == -1 || !(trapped = sigtrapped[sig]) ||
+	(jobbing && (sig == SIGTTOU || sig == SIGTSTP || sig == SIGTTIN))) {
+        return;
+    }
+    sigtrapped[sig] = 0;
+    if (sig == SIGINT && interact) {
+	/* PWS 1995/05/16:  added test for interactive, also noholdintr() *
+	 * as subshells ignoring SIGINT have it blocked from delivery     */
+        intr();
+	noholdintr();
+    } else if (sig == SIGHUP)
+        install_handler(sig);
+    else if (sig && sig <= SIGCOUNT &&
+#ifdef SIGWINCH
+             sig != SIGWINCH &&
+#endif
+             sig != SIGCHLD)
+        signal_default(sig);
+    if (trapped & ZSIG_FUNC) {
+	char func[20];
+	HashNode hn;
+
+	sprintf(func, "TRAP%s", sigs[sig]);
+	if ((hn = shfunctab->removenode(shfunctab, func)))
+	    shfunctab->freenode(hn);
+    } else if (sigfuncs[sig]) {
+	freestruct(sigfuncs[sig]);
+	sigfuncs[sig] = NULL;
+    }
+}
+
+/* Execute a trap function for a given signal, possibly
+ * with non-standard sigtrapped & sigfuncs values
+ */
+
+/**/
+void
+dotrapargs(int sig, int *sigtr, void *sigfn)
+{
+    LinkList args;
+    char *name, num[4];
+    int trapret = 0;
+    int obreaks = breaks;
+ 
+    /* if signal is being ignored or the trap function		      *
+     * is NULL, then return					      *
+     *								      *
+     * Also return if errflag is set.  In fact, the code in the       *
+     * function will test for this, but this way we keep status flags *
+     * intact without working too hard.  Special cases (e.g. calling  *
+     * a trap for SIGINT after the error flag was set) are handled    *
+     * by the calling code.  (PWS 1995/06/08).			      */
+    if ((*sigtr & ZSIG_IGNORED) || !sigfn || errflag)
+        return;
+
+    *sigtr |= ZSIG_IGNORED;
+
+    lexsave();
+    execsave();
+    breaks = 0;
+    if (*sigtr & ZSIG_FUNC) {
+	PERMALLOC {
+	    args = newlinklist();
+	    name = (char *) zalloc(5 + strlen(sigs[sig]));
+	    sprintf(name, "TRAP%s", sigs[sig]);
+	    addlinknode(args, name);
+	    sprintf(num, "%d", sig);
+	    addlinknode(args, num);
+	} LASTALLOC;
+	trapreturn = -1;
+	doshfunc(sigfn, args, 0, 1);
+	freelinklist(args, (FreeFunc) NULL);
+	zsfree(name);
+    } else HEAPALLOC {
+	execlist(dupstruct(sigfn), 1, 0);
+    } LASTALLOC;
+    if (trapreturn > 0)
+	trapret = trapreturn;
+    else if (errflag)
+	trapret = 1;
+    execrestore();
+    lexrestore();
+
+    if (trapret > 0) {
+	breaks = loops;
+	errflag = 1;
+    } else {
+	breaks += obreaks;
+	if (breaks > loops)
+	    breaks = loops;
+    }
+
+    if (*sigtr != ZSIG_IGNORED)
+	*sigtr &= ~ZSIG_IGNORED;
+}
+
+/* Standard call to execute a trap for a given signal */
+
+/**/
+void
+dotrap(int sig)
+{
+    dotrapargs(sig, sigtrapped+sig, sigfuncs[sig]);
+}
diff --git a/Src/signals.h b/Src/signals.h
new file mode 100644
index 000000000..b6485e6b3
--- /dev/null
+++ b/Src/signals.h
@@ -0,0 +1,94 @@
+/*
+ * signals.h - header file for signals handling code
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#define SIGNAL_HANDTYPE RETSIGTYPE (*)_((int))
+
+#ifndef HAVE_KILLPG
+# define killpg(pgrp,sig) kill(-(pgrp),sig)
+#endif
+
+#define SIGZERR   (SIGCOUNT+1)
+#define SIGDEBUG  (SIGCOUNT+2)
+#define VSIGCOUNT (SIGCOUNT+3)
+#define SIGEXIT    0
+
+#ifdef SV_BSDSIG
+# define SV_INTERRUPT SV_BSDSIG
+#endif
+
+/* If not a POSIX machine, then we create our *
+ * own POSIX style signal sets functions.     */
+#ifndef POSIX_SIGNALS
+# define sigemptyset(s)    (*(s) = 0)
+# if NSIG == 32
+#  define sigfillset(s)    (*(s) = ~(sigset_t)0, 0)
+# else
+#  define sigfillset(s)    (*(s) = (1 << NSIG) - 1, 0)
+# endif
+# define sigaddset(s,n)    (*(s) |=  (1 << ((n) - 1)), 0)
+# define sigdelset(s,n)    (*(s) &= ~(1 << ((n) - 1)), 0)
+# define sigismember(s,n)  ((*(s) & (1 << ((n) - 1))) != 0)
+#endif   /* ifndef POSIX_SIGNALS */
+ 
+#define child_block()      signal_block(signal_mask(SIGCHLD))
+#define child_unblock()    signal_unblock(signal_mask(SIGCHLD))
+#define child_suspend(S)   signal_suspend(SIGCHLD, S)
+
+/* ignore a signal */
+#define signal_ignore(S)   signal(S, SIG_IGN)
+
+/* return a signal to it default action */
+#define signal_default(S)  signal(S, SIG_DFL)
+
+/* Use a circular queue to save signals caught during    *
+ * critical sections of code.  You call queue_signals to *
+ * start queueing, and unqueue_signals to process the    *
+ * queue and stop queueing.  Since the kernel doesn't    *
+ * queue signals, it is probably overkill for zsh to do  *
+ * this, but it shouldn't hurt anything to do it anyway. */
+
+/* Right now I'm queueing all signals, but maybe we only *
+ * need to queue SIGCHLD.  Anybody know?                 */
+
+#define MAX_QUEUE_SIZE 16
+
+#define queue_signals()    (queueing_enabled++)
+
+#define unqueue_signals()  do { \
+    DPUTS(!queueing_enabled, "BUG: unqueue_signals called but not queueing"); \
+    if (!--queueing_enabled) { \
+	while (queue_front != queue_rear) {      /* while signals in queue */ \
+	    sigset_t oset; \
+	    queue_front = (queue_front + 1) % MAX_QUEUE_SIZE; \
+	    oset = signal_setmask(signal_mask_queue[queue_front]); \
+	    handler(signal_queue[queue_front]);  /* handle queued signal   */ \
+	    signal_setmask(oset); \
+	} \
+    } \
+} while (0)
diff --git a/Src/signames.awk b/Src/signames.awk
new file mode 100755
index 000000000..5d2eeb61e
--- /dev/null
+++ b/Src/signames.awk
@@ -0,0 +1,98 @@
+#
+# {g,n}awk script to generate signames.c
+#
+# NB: On SunOS 4.1.3 - user-functions don't work properly, also \" problems
+# Without 0 + hacks some nawks compare numbers as strings
+#
+/^[\t ]*#[\t ]*define[\t _]*SIG[A-Z][A-Z0-9]*[\t ]*[1-9][0-9]*/ { 
+    sigindex = index($0, "SIG")
+    sigtail = substr($0, sigindex, 80)
+    split(sigtail, tmp)
+    signam = substr(tmp[1], 4, 20)
+    signum = tmp[2]
+    if (sig[signum] == "") {
+	sig[signum] = signam
+	if (0 + max < 0 + signum && signum < 60)
+	    max = signum
+	if (signam == "ABRT")   { msg[signum] = "abort" }
+	if (signam == "ALRM")   { msg[signum] = "alarm" }
+	if (signam == "BUS")    { msg[signum] = "bus error" }
+	if (signam == "CHLD")   { msg[signum] = "death of child" }
+	if (signam == "CLD")    { msg[signum] = "death of child" }
+	if (signam == "CONT")   { msg[signum] = "continued" }
+	if (signam == "EMT")    { msg[signum] = "EMT instruction" }
+	if (signam == "FPE")    { msg[signum] = "floating point exception" }
+	if (signam == "HUP")    { msg[signum] = "hangup" }
+	if (signam == "ILL")    { msg[signum] = "illegal hardware instruction" }
+	if (signam == "INFO")   { msg[signum] = "status request from keyboard" }
+	if (signam == "INT")    { msg[signum] = "interrupt" }
+	if (signam == "IO")     { msg[signum] = "i/o ready" }
+	if (signam == "IOT")    { msg[signum] = "IOT instruction" }
+	if (signam == "KILL")   { msg[signum] = "killed" }
+	if (signam == "LOST")	{ msg[signum] = "resource lost" }
+	if (signam == "PIPE")   { msg[signum] = "broken pipe" }
+	if (signam == "POLL")	{ msg[signum] = "pollable event occurred" }
+	if (signam == "PROF")   { msg[signum] = "profile signal" }
+	if (signam == "PWR")    { msg[signum] = "power fail" }
+	if (signam == "QUIT")   { msg[signum] = "quit" }
+	if (signam == "SEGV")   { msg[signum] = "segmentation fault" }
+	if (signam == "SYS")    { msg[signum] = "invalid system call" }
+	if (signam == "TERM")   { msg[signum] = "terminated" }
+	if (signam == "TRAP")   { msg[signum] = "trace trap" }
+	if (signam == "URG")	{ msg[signum] = "urgent condition" }
+	if (signam == "USR1")   { msg[signum] = "user-defined signal 1" }
+	if (signam == "USR2")   { msg[signum] = "user-defined signal 2" }
+	if (signam == "VTALRM") { msg[signum] = "virtual time alarm" }
+	if (signam == "WINCH")  { msg[signum] = "window size changed" }
+	if (signam == "XCPU")   { msg[signum] = "cpu limit exceeded" }
+	if (signam == "XFSZ")   { msg[signum] = "file size limit exceeded" }
+    }
+}
+
+END {
+    ps = "%s"
+    ifdstr = sprintf("# ifdef USE_SUSPENDED\n\t%csuspended%s%c,\n%s else\n\t%cstopped%s%c,\n# endif\n", 34, ps, 34, "#", 34, ps, 34)
+
+    printf "/** signames.c                                 **/\n"
+    printf "/** architecture-customized signames.c for zsh **/\n"
+    printf "\n"
+    printf "#define SIGCOUNT\t%d\n", max
+    printf "\n"
+    printf "#include %czsh.mdh%c\n", 34, 34
+    printf "\n"
+    printf "/**/\n"
+    printf "char *sigmsg[SIGCOUNT+2] = {\n"
+    printf "\t%c%s%c,\n", 34, "done", 34
+
+    for (i = 1; i <= 0 + max; i++)
+	if (msg[i] == "") {
+	    if (sig[i] == "")
+		printf("\t%c%c,\n", 34, 34)
+	    else if (sig[i] == "STOP")
+		printf ifdstr, " (signal)", " (signal)"
+	    else if (sig[i] == "TSTP")
+		printf ifdstr, "", ""
+	    else if (sig[i] == "TTIN")
+		printf ifdstr, " (tty input)", " (tty input)"
+	    else if (sig[i] == "TTOU")
+		printf ifdstr, " (tty output)", " (tty output)"
+	    else
+		printf("\t%cSIG%s%c,\n", 34, sig[i], 34)
+	} else
+	    printf("\t%c%s%c,\n", 34, msg[i], 34)
+    print "\tNULL"
+    print "};"
+    print ""
+    print "/**/"
+    printf "char *sigs[SIGCOUNT+4] = {\n"
+    printf("\t%cEXIT%c,\n", 34, 34)
+    for (i = 1; i <= 0 + max; i++)
+	if (sig[i] == "")
+	    printf("\t%c%d%c,\n", 34, i, 34)
+	else
+	    printf("\t%c%s%c,\n", 34, sig[i], 34)
+    printf("\t%cZERR%c,\n", 34, 34)
+    printf("\t%cDEBUG%c,\n", 34, 34)
+    print "\tNULL"
+    print "};"
+}
diff --git a/Src/signames1.awk b/Src/signames1.awk
new file mode 100644
index 000000000..27d21ac7b
--- /dev/null
+++ b/Src/signames1.awk
@@ -0,0 +1,19 @@
+# This is an awk script which finds out what the possibilities for
+# the signal names are, and dumps them out so that cpp can turn them
+# into numbers.  Since we don't need to decide here what the
+# real signals are, we can afford to be generous about definitions,
+# in case the definitions are in terms of other definitions.
+# However, we need to avoid definitions with parentheses, which will
+# mess up the syntax.
+BEGIN { printf "#include <signal.h>\n\n" }
+
+/^[\t ]*#[\t ]*define[\t _]*SIG[A-Z][A-Z0-9]*[\t ][\t ]*[^(\t ]/ { 
+    sigindex = index($0, "SIG")
+    sigtail = substr($0, sigindex, 80)
+    split(sigtail, tmp)
+    signam = substr(tmp[1], 4, 20)
+    if (substr($0, sigindex-1, 1) == "_")
+        printf("XXNAMES XXSIG%s _SIG%s\n", signam, signam)
+    else
+        printf("XXNAMES XXSIG%s SIG%s\n", signam, signam)
+}
diff --git a/Src/signames2.awk b/Src/signames2.awk
new file mode 100644
index 000000000..3aea76ee3
--- /dev/null
+++ b/Src/signames2.awk
@@ -0,0 +1,100 @@
+#
+# {g,n}awk script to generate signames.c
+# This version relies on the previous output of the preprocessor
+# on sigtmp.c, sigtmp.out, which is in turn generated by signames1.awk.
+#
+# NB: On SunOS 4.1.3 - user-functions don't work properly, also \" problems
+# Without 0 + hacks some nawks compare numbers as strings
+#
+/^XXNAMES XXSIG[A-Z][A-Z0-9]* [1-9][0-9]*/ {
+    sigindex = index($0, "SIG")
+    sigtail = substr($0, sigindex, 80)
+    split(sigtail, tmp)
+    signam = substr(tmp[1], 4, 20)
+    signum = tmp[2]
+    if (sig[signum] == "") {
+	sig[signum] = signam
+	if (0 + max < 0 + signum && signum < 60)
+	    max = signum
+	if (signam == "ABRT")   { msg[signum] = "abort" }
+	if (signam == "ALRM")   { msg[signum] = "alarm" }
+	if (signam == "BUS")    { msg[signum] = "bus error" }
+	if (signam == "CHLD")   { msg[signum] = "death of child" }
+	if (signam == "CLD")    { msg[signum] = "death of child" }
+	if (signam == "CONT")   { msg[signum] = "continued" }
+	if (signam == "EMT")    { msg[signum] = "EMT instruction" }
+	if (signam == "FPE")    { msg[signum] = "floating point exception" }
+	if (signam == "HUP")    { msg[signum] = "hangup" }
+	if (signam == "ILL")    { msg[signum] = "illegal hardware instruction" }
+	if (signam == "INFO")   { msg[signum] = "status request from keyboard" }
+	if (signam == "INT")    { msg[signum] = "interrupt" }
+	if (signam == "IO")     { msg[signum] = "i/o ready" }
+	if (signam == "IOT")    { msg[signum] = "IOT instruction" }
+	if (signam == "KILL")   { msg[signum] = "killed" }
+	if (signam == "LOST")	{ msg[signum] = "resource lost" }
+	if (signam == "PIPE")   { msg[signum] = "broken pipe" }
+	if (signam == "POLL")	{ msg[signum] = "pollable event occurred" }
+	if (signam == "PROF")   { msg[signum] = "profile signal" }
+	if (signam == "PWR")    { msg[signum] = "power fail" }
+	if (signam == "QUIT")   { msg[signum] = "quit" }
+	if (signam == "SEGV")   { msg[signum] = "segmentation fault" }
+	if (signam == "SYS")    { msg[signum] = "invalid system call" }
+	if (signam == "TERM")   { msg[signum] = "terminated" }
+	if (signam == "TRAP")   { msg[signum] = "trace trap" }
+	if (signam == "URG")	{ msg[signum] = "urgent condition" }
+	if (signam == "USR1")   { msg[signum] = "user-defined signal 1" }
+	if (signam == "USR2")   { msg[signum] = "user-defined signal 2" }
+	if (signam == "VTALRM") { msg[signum] = "virtual time alarm" }
+	if (signam == "WINCH")  { msg[signum] = "window size changed" }
+	if (signam == "XCPU")   { msg[signum] = "cpu limit exceeded" }
+	if (signam == "XFSZ")   { msg[signum] = "file size limit exceeded" }
+    }
+}
+
+END {
+    ps = "%s"
+    ifdstr = sprintf("# ifdef USE_SUSPENDED\n\t%csuspended%s%c,\n%s else\n\t%cstopped%s%c,\n# endif\n", 34, ps, 34, "#", 34, ps, 34)
+
+    printf "/** signames.c                                 **/\n"
+    printf "/** architecture-customized signames.c for zsh **/\n"
+    printf "\n"
+    printf "#define SIGCOUNT\t%d\n", max
+    printf "\n"
+    printf "#include %czsh.mdh%c\n", 34, 34
+    printf "\n"
+    printf "/**/\n"
+    printf "char *sigmsg[SIGCOUNT+2] = {\n"
+    printf "\t%c%s%c,\n", 34, "done", 34
+
+    for (i = 1; i <= 0 + max; i++)
+	if (msg[i] == "") {
+	    if (sig[i] == "")
+		printf("\t%c%c,\n", 34, 34)
+	    else if (sig[i] == "STOP")
+		printf ifdstr, " (signal)", " (signal)"
+	    else if (sig[i] == "TSTP")
+		printf ifdstr, "", ""
+	    else if (sig[i] == "TTIN")
+		printf ifdstr, " (tty input)", " (tty input)"
+	    else if (sig[i] == "TTOU")
+		printf ifdstr, " (tty output)", " (tty output)"
+	    else
+		printf("\t%cSIG%s%c,\n", 34, sig[i], 34)
+	} else
+	    printf("\t%c%s%c,\n", 34, msg[i], 34)
+    print "\tNULL"
+    print "};"
+    print ""
+    print "/**/"
+    printf "char *sigs[SIGCOUNT+4] = {\n"
+    printf("\t%cEXIT%c,\n", 34, 34)
+    for (i = 1; i <= 0 + max; i++)
+	if (sig[i] == "")
+	    printf("\t%c%d%c,\n", 34, i, 34)
+	else
+	    printf("\t%c%s%c,\n", 34, sig[i], 34)
+    printf("\t%cZERR%c,\n", 34, 34)
+    printf("\t%cDEBUG%c,\n", 34, 34)
+    print "\tNULL"
+    print "};"
+}
diff --git a/Src/subst.c b/Src/subst.c
new file mode 100644
index 000000000..8f840d266
--- /dev/null
+++ b/Src/subst.c
@@ -0,0 +1,1773 @@
+/*
+ * subst.c - various substitutions
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "subst.pro"
+
+/**/
+char nulstring[] = {Nularg, '\0'};
+
+/* Do substitutions before fork. These are:
+ *  - Process substitution: <(...), >(...), =(...)
+ *  - Parameter substitution
+ *  - Command substitution
+ * Followed by
+ *  - Quote removal
+ *  - Brace expansion
+ *  - Tilde and equals substitution
+ *
+ * Bits 0 and 1 of flags are used in filesub.
+ * bit 0 is set when we are doing MAGIC_EQUALSUBST or normal
+ *	 assignment but not a typeset.
+ * bit 1 is set on a real assignment (both typeset and normal).
+ * bit 2 is a flag to paramsubst (single word sub)
+ */
+
+/**/
+void
+prefork(LinkList list, int flags)
+{
+    LinkNode node;
+
+    MUSTUSEHEAP("prefork");
+    for (node = firstnode(list); node; incnode(node)) {
+	char *str, *str3;
+
+	str = str3 = (char *)getdata(node);
+	if ((*str == Inang || *str == Outang || *str == Equals) &&
+	    str[1] == Inpar) {
+	    if (*str == Inang || *str == Outang)
+		setdata(node, (void *) getproc(str));	/* <(...) or >(...) */
+	    else
+		setdata(node, (void *) getoutputfile(str));	/* =(...) */
+	    if (!getdata(node))
+		return;
+	} else {
+	    if (isset(SHFILEEXPANSION))
+		filesub((char **)getaddrdata(node), flags & 3);
+	    if (!(node = stringsubst(list, node, flags & 4)))
+		return;
+	}
+    }
+    for (node = firstnode(list); node; incnode(node)) {
+	if (*(char *)getdata(node)) {
+	    remnulargs(getdata(node));
+	    if (unset(IGNOREBRACES) && !(flags & 4))
+		while (hasbraces(getdata(node)))
+		    xpandbraces(list, &node);
+	    if (unset(SHFILEEXPANSION))
+		filesub((char **)getaddrdata(node), flags & 3);
+	} else if (!(flags & 4))
+	    uremnode(list, node);
+	if (errflag)
+	    return;
+    }
+}
+
+/**/
+static LinkNode
+stringsubst(LinkList list, LinkNode node, int ssub)
+{
+    int qt;
+    char *str3 = (char *)getdata(node);
+    char *str  = str3;
+
+    while (!errflag && *str) {
+	if ((qt = *str == Qstring) || *str == String)
+	    if (str[1] == Inpar) {
+		str++;
+		goto comsub;
+	    } else if (str[1] == Inbrack) {
+		/* $[...] */
+		char *str2 = str;
+		str2++;
+		if (skipparens(Inbrack, Outbrack, &str2)) {
+		    zerr("closing bracket missing", NULL, 0);
+		    return NULL;
+		}
+		str2[-1] = *str = '\0';
+		str = arithsubst(str + 2, &str3, str2);
+		setdata(node, (void *) str3);
+		continue;
+	    } else if (str[1] == Snull) {
+		str = getkeystring(str, NULL, 4, NULL);
+		continue;
+	    } else {
+		node = paramsubst(list, node, &str, qt, ssub);
+		if (errflag || !node)
+		    return NULL;
+		str3 = (char *)getdata(node);
+		continue;
+	    }
+	else if ((qt = *str == Qtick) || *str == Tick)
+	  comsub: {
+	    LinkList pl;
+	    char *s, *str2 = str;
+	    char endchar;
+	    int l1, l2;
+
+	    if (*str == Inpar) {
+		endchar = Outpar;
+		str[-1] = '\0';
+		if (skipparens(Inpar, Outpar, &str))
+		    DPUTS(1, "BUG: parse error in command substitution");
+		str--;
+	    } else {
+		endchar = *str;
+		*str = '\0';
+
+		while (*++str != endchar)
+		    DPUTS(!*str, "BUG: parse error in command substitution");
+	    }
+	    *str++ = '\0';
+	    if (endchar == Outpar && str2[1] == '(' && str[-2] == ')') {
+		/* Math substitution of the form $((...)) */
+		str = arithsubst(str2 + 1, &str3, str);
+		setdata(node, (void *) str3);
+		continue;
+	    }
+
+	    /* It is a command substitution, which will be parsed again   *
+	     * by the lexer, so we untokenize it first, but we cannot use *
+	     * untokenize() since in the case of `...` some Bnulls should *
+	     * be left unchanged.  Note that the lexer doesn't tokenize   *
+	     * the body of a command substitution so if there are some    *
+	     * tokens here they are from a ${(e)~...} substitution.       */
+	    for (str = str2; *++str; )
+		if (itok(*str) && *str != Nularg &&
+		    !(endchar != Outpar && *str == Bnull &&
+		      (str[1] == '$' || str[1] == '\\' || str[1] == '`' ||
+		       (qt && str[1] == '"'))))
+		    *str = ztokens[*str - Pound];
+	    str++;
+	    if (!(pl = getoutput(str2 + 1, qt || ssub))) {
+		zerr("parse error in command substitution", NULL, 0);
+		return NULL;
+	    }
+	    if (endchar == Outpar)
+		str2--;
+	    if (!(s = (char *) ugetnode(pl))) {
+		str = strcpy(str2, str);
+		continue;
+	    }
+	    if (!qt && ssub && isset(GLOBSUBST))
+		tokenize(s);
+	    l1 = str2 - str3;
+	    l2 = strlen(s);
+	    if (nonempty(pl)) {
+		LinkNode n = lastnode(pl);
+		str2 = (char *) ncalloc(l1 + l2 + 1);
+		strcpy(str2, str3);
+		strcpy(str2 + l1, s);
+		setdata(node, str2);
+		insertlinklist(pl, node, list);
+		s = (char *) getdata(node = n);
+		l1 = 0;
+		l2 = strlen(s);
+	    }
+	    str2 = (char *) ncalloc(l1 + l2 + strlen(str) + 1);
+	    if (l1)
+		strcpy(str2, str3);
+	    strcpy(str2 + l1, s);
+	    str = strcpy(str2 + l1 + l2, str);
+	    str3 = str2;
+	    setdata(node, str3);
+	    continue;
+	}
+	str++;
+    }
+    return errflag ? NULL : node;
+}
+
+/**/
+void
+globlist(LinkList list)
+{
+    LinkNode node, next;
+
+    badcshglob = 0;
+    for (node = firstnode(list); !errflag && node; node = next) {
+	next = nextnode(node);
+	glob(list, node);
+    }
+    if (badcshglob == 1)
+	zerr("no match", NULL, 0);
+}
+
+/* perform substitution on a single word */
+
+/**/
+void
+singsub(char **s)
+{
+    LinkList foo;
+
+    foo = newlinklist();
+    addlinknode(foo, *s);
+    prefork(foo, 4);
+    if (errflag)
+	return;
+    *s = (char *) ugetnode(foo);
+    DPUTS(nonempty(foo), "BUG: singsub() produced more than one word!");
+}
+
+/* Perform substitution on a single word. Unlike with singsub, the      *
+ * result can have more than one words. A single word result is sroted  *
+ * in *s and *isarr is set to zero; otherwise *isarr is set to 1 and    *
+ * the result is stored in *a. If `a' is zero a multiple word result is *
+ * joined using sep or the IFS parameter if sep is zero and the result  *
+ * is returned in *s.  The return value is true iff the expansion       *
+ * resulted in an empty list                                            */
+
+/**/
+static int
+multsub(char **s, char ***a, int *isarr, char *sep)
+{
+    LinkList foo;
+    int l;
+    char **r, **p;
+
+    foo = newlinklist();
+    addlinknode(foo, *s);
+    prefork(foo, 0);
+    if (errflag) {
+	if (isarr)
+	    *isarr = 0;
+	return 0;
+    }
+    if ((l = countlinknodes(foo)) > 1) {
+	p = r = ncalloc((l + 1) * sizeof(char*));
+	while (nonempty(foo))
+	    *p++ = (char *)ugetnode(foo);
+	*p = NULL;
+	if (a) {
+	    *a = r;
+	    *isarr = 1;
+	    return 0;
+	}
+	*s = sepjoin(r, NULL);
+	return 0;
+    }
+    if (l)
+	*s = (char *) ugetnode(foo);
+    else
+	*s = dupstring("");
+    if (isarr)
+	*isarr = 0;
+    return !l;
+}
+
+/* ~, = subs: assign = 2 => typeset; assign = 1 => something that looks
+	like an assignment but may not be; assign = 3 => normal assignment */
+
+/**/
+void
+filesub(char **namptr, int assign)
+{
+    char *sub = NULL, *str, *ptr;
+    int len;
+
+    filesubstr(namptr, assign);
+
+    if (!assign)
+	return;
+
+    if (assign < 3)
+	if ((*namptr)[1] && (sub = strchr(*namptr + 1, Equals))) {
+	    if (assign == 1)
+		for (ptr = *namptr; ptr != sub; ptr++)
+		    if (!iident(*ptr) && !INULL(*ptr))
+			return;
+	    str = sub + 1;
+	    if ((sub[1] == Tilde || sub[1] == Equals) && filesubstr(&str, assign)) {
+		sub[1] = '\0';
+		*namptr = dyncat(*namptr, str);
+	    }
+	} else
+	    return;
+
+    ptr = *namptr;
+    while ((sub = strchr(ptr, ':'))) {
+	str = sub + 1;
+	len = sub - *namptr;
+	if ((sub[1] == Tilde || sub[1] == Equals) && filesubstr(&str, assign)) {
+	    sub[1] = '\0';
+	    *namptr = dyncat(*namptr, str);
+	}
+	ptr = *namptr + len + 1;
+    }
+}
+
+/**/
+int
+filesubstr(char **namptr, int assign)
+{
+#define isend(c) ( !(c) || (c)=='/' || (c)==Inpar || (assign && (c)==':') )
+#define isend2(c) ( !(c) || (c)==Inpar || (assign && (c)==':') )
+    char *str = *namptr;
+
+    if (*str == Tilde && str[1] != '=' && str[1] != Equals) {
+	char *ptr;
+	int val;
+
+	val = zstrtol(str + 1, &ptr, 10);
+	if (isend(str[1])) {   /* ~ */
+	    *namptr = dyncat(home, str + 1);
+	    return 1;
+	} else if (str[1] == '+' && isend(str[2])) {   /* ~+ */
+	    *namptr = dyncat(pwd, str + 2);
+	    return 1;
+	} else if (str[1] == '-' && isend(str[2])) {   /* ~- */
+	    char *tmp;
+	    *namptr = dyncat((tmp = oldpwd) ? tmp : pwd, str + 2);
+	    return 1;
+	} else if (!inblank(str[1]) && isend(*ptr) &&
+		   (!idigit(str[1]) || (ptr - str < 4))) {
+	    char *ds;
+
+	    if (val < 0)
+		val = -val;
+	    ds = dstackent(str[1], val);
+	    if (!ds)
+		return 0;
+	    *namptr = dyncat(ds, ptr);
+	    return 1;
+	} else if (iuser(str[1])) {   /* ~foo */
+	    char *ptr, *hom, save;
+
+	    for (ptr = ++str; *ptr && iuser(*ptr); ptr++);
+	    save = *ptr;
+	    if (!isend(save))
+		return 0;
+	    *ptr = 0;
+	    if (!(hom = getnameddir(str))) {
+		if (isset(NOMATCH))
+		    zerr("no such user or named directory: %s", str, 0);
+		*ptr = save;
+		return 0;
+	    }
+	    *ptr = save;
+	    *namptr = dyncat(hom, ptr);
+	    return 1;
+	}
+    } else if (*str == Equals && isset(EQUALS) && str[1]) {   /* =foo */
+	char sav, *pp, *cnam;
+
+	for (pp = str + 1; !isend2(*pp); pp++);
+	sav = *pp;
+	*pp = 0;
+	if (!(cnam = findcmd(str + 1))) {
+	    Alias a = (Alias) aliastab->getnode(aliastab, str + 1);
+	    
+	    if (a)
+		cnam = ztrdup(a->text);
+	    else {
+		if (isset(NOMATCH))
+		    zerr("%s not found", str + 1, 0);
+		return 0;
+	    }
+	}
+	*namptr = dupstring(cnam);
+	zsfree(cnam);
+	if (sav) {
+	    *pp = sav;
+	    *namptr = dyncat(*namptr, pp);
+	}
+	return 1;
+    }
+    return 0;
+#undef isend
+#undef isend2
+}
+
+/**/
+static char *
+strcatsub(char **d, char *pb, char *pe, char *src, int l, char *s, int glbsub)
+{
+    int pl = pe - pb;
+    char *dest = ncalloc(pl + l + (s ? strlen(s) : 0) + 1);
+
+    *d = dest;
+    strncpy(dest, pb, pl);
+    dest += pl;
+    strcpy(dest, src);
+    if (glbsub)
+	tokenize(dest);
+    dest += l;
+    if (s)
+	strcpy(dest, s);
+    return dest;
+}
+
+typedef int (*CompareFn) _((const void *, const void *));
+
+/**/
+int
+strpcmp(const void *a, const void *b)
+{
+#ifdef HAVE_STRCOLL
+    return strcoll(*(char **)a, *(char **)b);
+#else
+    return strcmp(*(char **)a, *(char **)b);
+#endif
+}
+
+/**/
+int
+invstrpcmp(const void *a, const void *b)
+{
+#ifdef HAVE_STRCOLL
+    return -strcoll(*(char **)a, *(char **)b);
+#else
+    return -strcmp(*(char **)a, *(char **)b);
+#endif
+}
+
+/**/
+int
+cstrpcmp(const void *a, const void *b)
+{
+#ifdef HAVE_STRCOLL
+    VARARR(char, c, strlen(*(char **) a) + 1);
+    VARARR(char, d, strlen(*(char **) b) + 1);
+    char *s, *t;
+    int   cmp;
+
+    for (s = *(char **) a, t = c; (*t++ = tulower(*s++)););
+    for (s = *(char **) b, t = d; (*t++ = tulower(*s++)););
+
+    cmp = strcoll(c, d);
+
+    return cmp;
+#else
+    char *c = *(char **)a, *d = *(char **)b;
+
+    for (; *c && tulower(*c) == tulower(*d); c++, d++);
+
+    return (int)STOUC(tulower(*c)) - (int)STOUC(tulower(*d));
+#endif
+}
+
+/**/
+int
+invcstrpcmp(const void *a, const void *b)
+{
+#ifdef HAVE_STRCOLL
+    VARARR(char, c, strlen(*(char **) a) + 1);
+    VARARR(char, d, strlen(*(char **) b) + 1);
+    char *s, *t;
+    int   cmp;
+
+    for (s = *(char **) a, t = c; (*t++ = tulower(*s++)););
+    for (s = *(char **) b, t = d; (*t++ = tulower(*s++)););
+
+    cmp = strcoll(c, d);
+
+    return -cmp;
+#else
+    char *c = *(char **)a, *d = *(char **)b;
+
+    for (; *c && tulower(*c) == tulower(*d); c++, d++);
+
+    return (int)STOUC(tulower(*d)) - (int)STOUC(tulower(*c));
+#endif
+}
+
+/**/
+static char *
+dopadding(char *str, int prenum, int postnum, char *preone, char *postone, char *premul, char *postmul)
+{
+    char def[3], *ret, *t, *r;
+    int ls, ls2, lpreone, lpostone, lpremul, lpostmul, lr, f, m, c, cc;
+
+    def[0] = *ifs ? *ifs : ' ';
+    def[1] = *ifs == Meta ? ifs[1] ^ 32 : '\0';
+    def[2] = '\0';
+    if (preone && !*preone)
+	preone = def;
+    if (postone && !*postone)
+	postone = def;
+    if (!premul || !*premul)
+	premul = def;
+    if (!postmul || !*postmul)
+	postmul = def;
+
+    ls = strlen(str);
+    lpreone = preone ? strlen(preone) : 0;
+    lpostone = postone ? strlen(postone) : 0;
+    lpremul = strlen(premul);
+    lpostmul = strlen(postmul);
+
+    lr = prenum + postnum;
+
+    if (lr == ls)
+	return str;
+
+    r = ret = (char *)halloc(lr + 1);
+
+    if (prenum) {
+	if (postnum) {
+	    ls2 = ls / 2;
+
+	    f = prenum - ls2;
+	    if (f <= 0)
+		for (str -= f, c = prenum; c--; *r++ = *str++);
+	    else {
+		if (f <= lpreone)
+		    for (c = f, t = preone + lpreone - f; c--; *r++ = *t++);
+		else {
+		    f -= lpreone;
+		    if ((m = f % lpremul))
+			for (c = m, t = premul + lpremul - m; c--; *r++ = *t++);
+		    for (cc = f / lpremul; cc--;)
+			for (c = lpremul, t = premul; c--; *r++ = *t++);
+		    for (c = lpreone; c--; *r++ = *preone++);
+		}
+		for (c = ls2; c--; *r++ = *str++);
+	    }
+	    ls2 = ls - ls2;
+	    f = postnum - ls2;
+	    if (f <= 0)
+		for (c = postnum; c--; *r++ = *str++);
+	    else {
+		for (c = ls2; c--; *r++ = *str++);
+		if (f <= lpostone)
+		    for (c = f; c--; *r++ = *postone++);
+		else {
+		    f -= lpostone;
+		    for (c = lpostone; c--; *r++ = *postone++);
+		    for (cc = f / lpostmul; cc--;)
+			for (c = lpostmul, t = postmul; c--; *r++ = *t++);
+		    if ((m = f % lpostmul))
+			for (; m--; *r++ = *postmul++);
+		}
+	    }
+	} else {
+	    f = prenum - ls;
+	    if (f <= 0)
+		for (c = prenum, str -= f; c--; *r++ = *str++);
+	    else {
+		if (f <= lpreone)
+		    for (c = f, t = preone + lpreone - f; c--; *r++ = *t++);
+		else {
+		    f -= lpreone;
+		    if ((m = f % lpremul))
+			for (c = m, t = premul + lpremul - m; c--; *r++ = *t++);
+		    for (cc = f / lpremul; cc--;)
+			for (c = lpremul, t = premul; c--; *r++ = *t++);
+		    for (c = lpreone; c--; *r++ = *preone++);
+		}
+		for (c = ls; c--; *r++ = *str++);
+	    }
+	}
+    } else if (postnum) {
+	f = postnum - ls;
+	if (f <= 0)
+	    for (c = postnum; c--; *r++ = *str++);
+	else {
+	    for (c = ls; c--; *r++ = *str++);
+	    if (f <= lpostone)
+		for (c = f; c--; *r++ = *postone++);
+	    else {
+		f -= lpostone;
+		for (c = lpostone; c--; *r++ = *postone++);
+		for (cc = f / lpostmul; cc--;)
+		    for (c = lpostmul, t = postmul; c--; *r++ = *t++);
+		if ((m = f % lpostmul))
+		    for (; m--; *r++ = *postmul++);
+	    }
+	}
+    }
+    *r = '\0';
+
+    return ret;
+}
+
+/**/
+char *
+get_strarg(char *s)
+{
+    char t = *s++;
+
+    if (!t)
+	return s - 1;
+
+    switch (t) {
+    case '(':
+	t = ')';
+	break;
+    case '[':
+	t = ']';
+	break;
+    case '{':
+	t = '}';
+	break;
+    case '<':
+	t = '>';
+	break;
+    case Inpar:
+	t = Outpar;
+	break;
+    case Inang:
+	t = Outang;
+	break;
+    case Inbrace:
+	t = Outbrace;
+	break;
+    case Inbrack:
+	t = Outbrack;
+	break;
+    }
+
+    while (*s && *s != t)
+	s++;
+
+    return s;
+}
+
+/**/
+static int
+get_intarg(char **s)
+{
+    char *t = get_strarg(*s + 1);
+    char *p, sav;
+    long ret;
+
+    if (!*t)
+	return -1;
+    sav = *t;
+    *t = '\0';
+    p = dupstring(*s + 2);
+    *s = t;
+    *t = sav;
+    if (parsestr(p))
+	return -1;
+    singsub(&p);
+    if (errflag)
+	return -1;
+    ret = matheval(p);
+    if (errflag)
+	return -1;
+    if (ret < 0)
+	ret = -ret;
+    return ret < 0 ? -ret : ret;
+}
+
+/* parameter substitution */
+
+#define	isstring(c) ((c) == '$' || (char)(c) == String || (char)(c) == Qstring)
+#define isbrack(c)  ((c) == '[' || (char)(c) == Inbrack)
+
+/**/
+LinkNode
+paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
+{
+    char *aptr = *str;
+    char *s = aptr, *fstr, *idbeg, *idend, *ostr = (char *) getdata(n);
+    int colf;			/* != 0 means we found a colon after the name */
+    int doub = 0;		/* != 0 means we have %%, not %, or ##, not # */
+    int isarr = 0;
+    int plan9 = isset(RCEXPANDPARAM);
+    int globsubst = isset(GLOBSUBST);
+    int getlen = 0;
+    int whichlen = 0;
+    int chkset = 0;
+    int vunset = 0;
+    int spbreak = isset(SHWORDSPLIT) && !ssub && !qt;
+    char *val = NULL, **aval = NULL;
+    unsigned int fwidth = 0;
+    Value v;
+    int flags = 0;
+    int flnum = 0;
+    int substr = 0;
+    int sortit = 0, casind = 0;
+    int casmod = 0;
+    char *sep = NULL, *spsep = NULL;
+    char *premul = NULL, *postmul = NULL, *preone = NULL, *postone = NULL;
+    long prenum = 0, postnum = 0;
+    int copied = 0;
+    int arrasg = 0;
+    int eval = 0;
+    int nojoin = 0;
+    char inbrace = 0;		/* != 0 means ${...}, otherwise $... */
+
+    *s++ = '\0';
+    if (!ialnum(*s) && *s != '#' && *s != Pound && *s != '-' &&
+	*s != '!' && *s != '$' && *s != String && *s != Qstring &&
+	*s != '?' && *s != Quest && *s != '_' &&
+	*s != '*' && *s != Star && *s != '@' && *s != '{' &&
+	*s != Inbrace && *s != '=' && *s != Equals && *s != Hat &&
+	*s != '^' && *s != '~' && *s != Tilde && *s != '+') {
+	s[-1] = '$';
+	*str = s;
+	return n;
+    }
+    DPUTS(*s == '{', "BUG: inbrace == '{' in paramsubst()");
+    if (*s == Inbrace) {
+	inbrace = 1;
+	s++;
+	if (*s == '(' || *s == Inpar) {
+	    char *t, sav;
+	    int tt = 0;
+	    long num;
+	    int escapes = 0;
+	    int klen;
+#define UNTOK_AND_ESCAPE(X) {\
+		untokenize(X = dupstring(s + 1));\
+		if (escapes) {\
+		    X = getkeystring(X, &klen, 3, NULL);\
+		    X = metafy(X, klen, META_HREALLOC);\
+		}\
+	    }
+
+	    for (s++; *s != ')' && *s != Outpar; s++, tt = 0) {
+		switch (*s) {
+		case ')':
+		case Outpar:
+		    break;
+		case 'A':
+		    arrasg = 1;
+		    break;
+		case '@':
+		    nojoin = 1;
+		    break;
+		case 'M':
+		    flags |= 8;
+		    break;
+		case 'R':
+		    flags |= 16;
+		    break;
+		case 'B':
+		    flags |= 32;
+		    break;
+		case 'E':
+		    flags |= 64;
+		    break;
+		case 'N':
+		    flags |= 128;
+		    break;
+		case 'S':
+		    substr = 1;
+		    break;
+		case 'I':
+		    flnum = get_intarg(&s);
+		    if (flnum < 0)
+			goto flagerr;
+		    break;
+
+		case 'L':
+		    casmod = 2;
+		    break;
+		case 'U':
+		    casmod = 1;
+		    break;
+		case 'C':
+		    casmod = 3;
+		    break;
+
+		case 'o':
+		    sortit = 1;
+		    break;
+		case 'O':
+		    sortit = 2;
+		    break;
+		case 'i':
+		    casind = 1;
+		    break;
+		case 'e':
+		    eval = 1;
+		    break;
+
+		case 'c':
+		    whichlen = 1;
+		    break;
+		case 'w':
+		    whichlen = 2;
+		    break;
+		case 'W':
+		    whichlen = 3;
+		    break;
+
+		case 'f':
+		    spsep = "\n";
+		    break;
+		case 'F':
+		    sep = "\n";
+		    break;
+
+		case 's':
+		    tt = 1;
+		/* fall through */
+		case 'j':
+		    t = get_strarg(++s);
+		    if (*t) {
+			sav = *t;
+			*t = '\0';
+			if (tt)
+			    UNTOK_AND_ESCAPE(spsep)
+			else
+			    UNTOK_AND_ESCAPE(sep)
+			*t = sav;
+			s = t;
+		    } else
+			goto flagerr;
+		    break;
+
+		case 'l':
+		    tt = 1;
+		/* fall through */
+		case 'r':
+		    sav = s[1];
+		    num = get_intarg(&s);
+		    if (num < 0)
+			goto flagerr;
+		    if (tt)
+			prenum = num;
+		    else
+			postnum = num;
+		    if (s[1] != sav)
+			break;
+		    t = get_strarg(++s);
+		    if (!*t)
+			goto flagerr;
+		    sav = *t;
+		    *t = '\0';
+		    if (tt)
+			UNTOK_AND_ESCAPE(premul)
+		    else
+			UNTOK_AND_ESCAPE(postmul)
+		    *t = sav;
+		    sav = *s;
+		    s = t + 1;
+		    if (*s != sav) {
+			s--;
+			break;
+		    }
+		    t = get_strarg(s);
+		    if (!*t)
+			goto flagerr;
+		    sav = *t;
+		    *t = '\0';
+		    if (tt)
+			UNTOK_AND_ESCAPE(preone)
+		    else
+			UNTOK_AND_ESCAPE(postone)
+		    *t = sav;
+		    s = t;
+		    break;
+
+		case 'p':
+		    escapes = 1;
+		    break;
+
+		default:
+		  flagerr:
+		    zerr("error in flags", NULL, 0);
+		    return NULL;
+		}
+	    }
+	    s++;
+	}
+    }
+    if (sortit)
+	sortit += (casind << 1);
+
+    if (!premul)
+	premul = " ";
+    if (!postmul)
+	postmul = " ";
+
+    for (;;) {
+	if (*s == '^' || *s == Hat) {
+	    if (*++s == '^' || *s == Hat) {
+		plan9 = 0;
+		s++;
+	    } else
+		plan9 = 1;
+	} else if (*s == '=' || *s == Equals) {
+	    if (*++s == '=' || *s == Equals) {
+		spbreak = 0;
+		s++;
+	    } else
+		spbreak = 1;
+	} else if ((*s == '#' || *s == Pound) &&
+		   (iident(s[1])
+		    || s[1] == '*' || s[1] == Star || s[1] == '@'
+		    || (isstring(s[1]) && (s[2] == Inbrace || s[2] == Inpar))))
+	    getlen = 1 + whichlen, s++;
+	else if (*s == '~' || *s == Tilde) {
+	    if (*++s == '~' || *s == Tilde) {
+		globsubst = 0;
+		s++;
+	    } else
+		globsubst = 1;
+	} else if (*s == '+')
+	    if (iident(s[1]))
+		chkset = 1, s++;
+	    else if (!inbrace) {
+		*aptr = '$';
+		*str = aptr + 1;
+		return n;
+	    } else {
+		zerr("bad substitution", NULL, 0);
+		return NULL;
+	    }
+	else
+	    break;
+    }
+    globsubst = globsubst && !qt;
+
+    idbeg = s;
+    if (s[-1] && isstring(*s) && (s[1] == Inbrace || s[1] == Inpar)) {
+	int sav;
+	int quoted = *s == Qstring;
+
+	val = s++;
+	skipparens(*s, *s == Inpar ? Outpar : Outbrace, &s);
+	sav = *s;
+	*s = 0;
+	if (multsub(&val, &aval, &isarr, NULL) && quoted) {
+	    isarr = -1;
+	    aval = alloc(sizeof(char *));
+	}
+	if (isarr)
+	    isarr = -1;
+	copied = 1;
+	*s = sav;
+	v = (Value) NULL;
+    } else if (!(v = getvalue(&s, (unset(KSHARRAYS) || inbrace) ? 1 : -1)))
+	vunset = 1;
+    while (v || ((inbrace || (unset(KSHARRAYS) && vunset)) && isbrack(*s))) {
+	if (!v) {
+	    Param pm;
+	    char *os = s;
+
+	    if (!isbrack(*s))
+		break;
+	    if (vunset) {
+		val = dupstring("");
+		isarr = 0;
+	    }
+	    pm = createparam(nulstring, isarr ? PM_ARRAY : PM_SCALAR);
+	    if (isarr)
+		pm->u.arr = aval;
+	    else
+		pm->u.str = val;
+	    v = (Value) hcalloc(sizeof *v);
+	    v->isarr = isarr;
+	    v->pm = pm;
+	    v->b = -1;
+	    if (getindex(&s, v) || s == os)
+		break;
+	}
+	if ((isarr = v->isarr))
+	    aval = getarrvalue(v);
+	else {
+	    if (v->pm->flags & PM_ARRAY) {
+		int tmplen = arrlen(v->pm->gets.afn(v->pm));
+
+		if (v->a < 0)
+		    v->a += tmplen + v->inv;
+		if (!v->inv && (v->a >= tmplen || v->a < 0))
+		    vunset = 1;
+	    }
+	    if (!vunset) {
+		val = getstrvalue(v);
+		fwidth = v->pm->ct ? v->pm->ct : strlen(val);
+		switch (v->pm->flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) {
+		    char *t;
+		    unsigned int t0;
+
+		case PM_LEFT:
+		case PM_LEFT | PM_RIGHT_Z:
+		    t = val;
+		    if (v->pm->flags & PM_RIGHT_Z)
+			while (*t == '0')
+			    t++;
+		    else
+			while (iblank(*t))
+			    t++;
+		    val = (char *)ncalloc(fwidth + 1);
+		    val[fwidth] = '\0';
+		    if ((t0 = strlen(t)) > fwidth)
+			t0 = fwidth;
+		    memset(val, ' ', fwidth);
+		    strncpy(val, t, t0);
+		    break;
+		case PM_RIGHT_B:
+		case PM_RIGHT_Z:
+		case PM_RIGHT_Z | PM_RIGHT_B:
+		    if (strlen(val) < fwidth) {
+			t = (char *)ncalloc(fwidth + 1);
+			memset(t, (v->pm->flags & PM_RIGHT_B) ? ' ' : '0', fwidth);
+			if ((t0 = strlen(val)) > fwidth)
+			    t0 = fwidth;
+			strcpy(t + (fwidth - t0), val);
+			val = t;
+		    } else {
+			t = (char *)ncalloc(fwidth + 1);
+			t[fwidth] = '\0';
+			strncpy(t, val + strlen(val) - fwidth, fwidth);
+			val = t;
+		    }
+		    break;
+		}
+		switch (v->pm->flags & (PM_LOWER | PM_UPPER)) {
+		    char *t;
+
+		case PM_LOWER:
+		    t = val;
+		    for (; *t; t++)
+			*t = tulower(*t);
+		    break;
+		case PM_UPPER:
+		    t = val;
+		    for (; *t; t++)
+			*t = tuupper(*t);
+		    break;
+		}
+	    }
+	}
+	v = NULL;
+	if (!inbrace)
+	    break;
+    }
+    if (isarr) {
+	if (nojoin)
+	    isarr = -1;
+	if (qt && !getlen && isarr > 0) {
+	    val = sepjoin(aval, sep);
+	    isarr = 0;
+	}
+    }
+
+    idend = s;
+    if ((colf = *s == ':'))
+	s++;
+
+
+    /* fstr is to be the text following the substitution.  If we have *
+     * braces, we look for it here, else we infer it later on.        */
+    fstr = s;
+    if (inbrace) {
+	int bct;
+	for (bct = 1;; fstr++) {
+	    if (!*fstr)
+		break;
+	    else if (*fstr == Inbrace)
+		bct++;
+	    else if (*fstr == Outbrace && !--bct)
+		break;
+	}
+
+	if (bct) {
+	noclosebrace:
+	    zerr("closing brace expected", NULL, 0);
+	    return NULL;
+	}
+	if (*fstr)
+	    *fstr++ = '\0';
+    }
+
+    /* Check for ${..?..} or ${..=..} or one of those. *
+     * Only works if the name is in braces.            */
+
+    if (inbrace && (*s == '-' ||
+		    *s == '+' ||
+		    *s == ':' ||
+		    *s == '=' || *s == Equals ||
+		    *s == '%' ||
+		    *s == '#' || *s == Pound ||
+		    *s == '?' || *s == Quest)) {
+
+	if (!flnum)
+	    flnum++;
+	if (*s == '%')
+	    flags |= 1;
+
+	/* Check for ${..%%..} or ${..##..} */
+	if ((*s == '%' || *s == '#' || *s == Pound) && *s == s[1]) {
+	    s++;
+	    doub = 1;
+	}
+	s++;
+
+	flags |= (doub << 1) | (substr << 2) | (colf << 8);
+	if (!(flags & 0xf8))
+	    flags |= 16;
+
+	if (colf && !vunset)
+	    vunset = (isarr) ? !*aval : !*val || (*val == Nularg && !val[1]);
+
+	switch (s[-1]) {
+	case '+':
+	    if (vunset) {
+		val = dupstring("");
+		copied = 1;
+		isarr = 0;
+		break;
+	    }
+	    vunset = 1;
+	/* Fall Through! */
+	case '-':
+	    if (vunset) {
+		val = dupstring(s);
+		multsub(&val, &aval, &isarr, NULL);
+		copied = 1;
+	    }
+	    break;
+	case ':':
+	    if (*s != '=' && *s != Equals)
+		goto noclosebrace;
+	    vunset = 1;
+	    s++;
+	    /* Fall through */
+	case '=':
+	case Equals:
+	    if (vunset) {
+		char sav = *idend;
+		int l;
+
+		*idend = '\0';
+		val = dupstring(s);
+		isarr = 0;
+		if (spsep || spbreak || !arrasg)
+		    multsub(&val, NULL, NULL, sep);
+		else
+		    multsub(&val, &aval, &isarr, NULL);
+		if (arrasg) {
+		    char *arr[2], **t, **a, **p;
+		    if (spsep || spbreak) {
+			aval = sepsplit(val, spsep, 0);
+			isarr = 2;
+			sep = spsep = NULL;
+			spbreak = 0;
+			l = arrlen(aval);
+			if (l && !*(aval[l-1]))
+			    l--;
+			if (l && !**aval)
+			    l--, t = aval + 1;
+			else
+			    t = aval;
+		    } else if (!isarr) {
+			arr[0] = val;
+			arr[1] = NULL;
+			t = aval = arr;
+			l = 1;
+		    } else
+			l = arrlen(aval), t = aval;
+		    p = a = zalloc(sizeof(char *) * (l + 1));
+		    while (l--) {
+			untokenize(*t);
+			*p++ = ztrdup(*t++);
+		    }
+		    *p++ = NULL;
+		    setaparam(idbeg, a);
+		} else {
+		    untokenize(val);
+		    setsparam(idbeg, ztrdup(val));
+		}
+		*idend = sav;
+		copied = 1;
+	    }
+	    break;
+	case '?':
+	case Quest:
+	    if (vunset) {
+		char *msg;
+
+		*idend = '\0';
+		msg = tricat(idbeg, ": ", *s ? s : "parameter not set");
+		zerr("%s", msg, 0);
+		zsfree(msg);
+		if (!interact)
+		    exit(1);
+		return NULL;
+	    }
+	    break;
+	case '%':
+	case '#':
+	case Pound:
+	    if (qt)
+		if (parse_subst_string(s)) {
+		    zerr("parse error in ${...%c...} substitution",
+			 NULL, s[-1]);
+		    return NULL;
+		}
+	    singsub(&s);
+
+	    if (!vunset && isarr) {
+		char **ap = aval;
+		char **pp = aval = (char **)ncalloc(sizeof(char *) * (arrlen(aval) + 1));
+
+		while ((*pp = *ap++)) {
+		    if (getmatch(pp, s, flags, flnum))
+			pp++;
+		}
+		copied = 1;
+	    } else {
+		if (vunset)
+		    val = dupstring("");
+		getmatch(&val, s, flags, flnum);
+		copied = 1;
+	    }
+	    break;
+	}
+    } else {			/* no ${...=...} or anything, but possible modifiers. */
+	if (chkset) {
+	    val = dupstring(vunset ? "0" : "1");
+	    isarr = 0;
+	} else if (vunset) {
+	    if (unset(UNSET)) {
+		*idend = '\0';
+		zerr("%s: parameter not set", idbeg, 0);
+		return NULL;
+	    }
+	    val = dupstring("");
+	}
+	if (colf) {
+	    s--;
+	    if (unset(KSHARRAYS) || inbrace) {
+		if (!isarr)
+		    modify(&val, &s);
+		else {
+		    char *ss;
+		    char **ap = aval;
+		    char **pp = aval = (char **)ncalloc(sizeof(char *) * (arrlen(aval) + 1));
+
+		    while ((*pp = *ap++)) {
+			ss = s;
+			modify(pp++, &ss);
+		    }
+		    if (pp == aval) {
+			char *t = "";
+			ss = s;
+			modify(&t, &ss);
+		    }
+		    s = ss;
+		}
+		if (inbrace && *s) {
+		    if (*s == ':' && !imeta(s[1]))
+			zerr("unrecognized modifier `%c'", NULL, s[1]);
+		    else
+			zerr("unrecognized modifier", NULL, 0);
+		    return NULL;
+		}
+	    }
+	}
+	if (!inbrace)
+	    fstr = s;
+    }
+    if (errflag)
+	return NULL;
+    if (getlen) {
+	long len = 0;
+	char buf[14];
+
+	if (isarr) {
+	    char **ctr;
+	    int sl = sep ? ztrlen(sep) : 1;
+
+	    if (getlen == 1)
+		for (ctr = aval; *ctr; ctr++, len++);
+	    else if (getlen == 2) {
+		if (*aval)
+		    for (len = -sl, ctr = aval;
+			 len += sl + ztrlen(*ctr), *++ctr;);
+	    }
+	    else
+		for (ctr = aval;
+		     *ctr;
+		     len += wordcount(*ctr, spsep, getlen > 3), ctr++);
+	} else {
+	    if (getlen < 3)
+		len = ztrlen(val);
+	    else
+		len = wordcount(val, spsep, getlen > 3);
+	}
+
+	sprintf(buf, "%ld", len);
+	val = dupstring(buf);
+	isarr = 0;
+    }
+    if (isarr > 0 && !plan9 && (!aval || !aval[0])) {
+	val = dupstring("");
+	isarr = 0;
+    } else if (isarr && aval && aval[0] && !aval[1]) {
+	val = aval[0];
+	isarr = 0;
+    }
+    /* ssub is true when we are called from singsub (via prefork).
+     * It means that we must join arrays and should not split words. */
+    if (ssub || spbreak || spsep || sep) {
+	if (isarr)
+	    val = sepjoin(aval, sep), isarr = 0;
+	if (!ssub && (spbreak || spsep)) {
+	    aval = sepsplit(val, spsep, 0);
+	    if (!aval || !aval[0])
+		val = dupstring("");
+	    else if (!aval[1])
+		val = aval[0];
+	    else
+		isarr = 2;
+	}
+    }
+    if (casmod) {
+	if (isarr) {
+	    char **ap;
+
+	    if (!copied)
+		aval = arrdup(aval), copied = 1;
+	    ap = aval;
+
+	    if (casmod == 1)
+		for (; *ap; ap++)
+		    makeuppercase(ap);
+	    else if (casmod == 2)
+		for (; *ap; ap++)
+		    makelowercase(ap);
+	    else
+		for (; *ap; ap++)
+		    makecapitals(ap);
+
+	} else {
+	    if (!copied)
+		val = dupstring(val), copied = 1;
+	    if (casmod == 1)
+		makeuppercase(&val);
+	    else if (casmod == 2)
+		makelowercase(&val);
+	    else
+		makecapitals(&val);
+	}
+    }
+    if (isarr) {
+	char *x;
+	char *y;
+	int xlen;
+	int i;
+	LinkNode on = n;
+
+	if (!aval[0] && !plan9) {
+	    if (aptr > (char *) getdata(n) &&
+		aptr[-1] == Dnull && *fstr == Dnull)
+		*--aptr = '\0', fstr++;
+	    y = (char *)ncalloc((aptr - ostr) + strlen(fstr) + 1);
+	    strcpy(y, ostr);
+	    *str = y + (aptr - ostr);
+	    strcpy(*str, fstr);
+	    setdata(n, y);
+	    return n;
+	}
+	if (sortit) {
+	    static CompareFn sortfn[] = {
+		strpcmp, invstrpcmp, cstrpcmp, invcstrpcmp
+	    };
+
+	    if (!copied)
+		aval = arrdup(aval);
+
+	    i = arrlen(aval);
+	    if (i && (*aval[i-1] || --i))
+		qsort(aval, i, sizeof(char *), sortfn[sortit-1]);
+	}
+	if (plan9) {
+	    LinkList tl = newlinklist();
+	    LinkNode tn;
+
+	    *--fstr = Marker;
+	    addlinknode(tl, fstr);
+	    if (!eval && !stringsubst(tl, firstnode(tl), ssub))
+		return NULL;
+	    *str = aptr;
+	    tn = firstnode(tl);
+	    while ((x = *aval++)) {
+		if (prenum || postnum)
+		    x = dopadding(x, prenum, postnum, preone, postone,
+				  premul, postmul);
+		if (eval && parsestr(x))
+		    return NULL;
+		xlen = strlen(x);
+		for (tn = firstnode(tl);
+		     tn && *(y = (char *) getdata(tn)) == Marker;
+		     incnode(tn)) {
+		    strcatsub(&y, ostr, aptr, x, xlen, y + 1, globsubst);
+		    if (qt && !*y && isarr != 2)
+			y = dupstring(nulstring);
+		    if (plan9)
+			setdata(n, (void *) y), plan9 = 0;
+		    else
+			insertlinknode(l, n, (void *) y), incnode(n);
+		}
+	    }
+	    for (; tn; incnode(tn)) {
+		y = (char *) getdata(tn);
+		if (*y == Marker)
+		    continue;
+		if (qt && !*y && isarr != 2)
+		    y = dupstring(nulstring);
+		if (plan9)
+		    setdata(n, (void *) y), plan9 = 0;
+		else
+		    insertlinknode(l, n, (void *) y), incnode(n);
+	    }
+	    if (plan9) {
+		uremnode(l, n);
+		return n;
+	    }
+	} else {
+	    x = aval[0];
+	    if (prenum || postnum)
+		x = dopadding(x, prenum, postnum, preone, postone,
+			      premul, postmul);
+	    if (eval && parsestr(x))
+		return NULL;
+	    xlen = strlen(x);
+	    strcatsub(&y, ostr, aptr, x, xlen, NULL, globsubst);
+	    if (qt && !*y && isarr != 2)
+		y = dupstring(nulstring);
+	    setdata(n, (void *) y);
+
+	    i = 1;
+	    /* aval[1] is non-null here */
+	    while (aval[i + 1]) {
+		x = aval[i++];
+		if (prenum || postnum)
+		    x = dopadding(x, prenum, postnum, preone, postone,
+				  premul, postmul);
+		if (eval && parsestr(x))
+		    return NULL;
+		if (qt && !*x && isarr != 2)
+		    y = dupstring(nulstring);
+		else {
+		    y = dupstring(x);
+		    if (globsubst)
+			tokenize(y);
+		}
+		insertlinknode(l, n, (void *) y), incnode(n);
+	    }
+
+	    x = aval[i];
+	    if (prenum || postnum)
+		x = dopadding(x, prenum, postnum, preone, postone,
+			      premul, postmul);
+	    if (eval && parsestr(x))
+		return NULL;
+	    xlen = strlen(x);
+	    *str = strcatsub(&y, aptr, aptr, x, xlen, fstr, globsubst);
+	    if (qt && !*y && isarr != 2)
+		y = dupstring(nulstring);
+	    insertlinknode(l, n, (void *) y), incnode(n);
+	}
+	if (eval)
+	    n = on;
+    } else {
+	int xlen;
+	char *x;
+	char *y;
+
+	x = val;
+	if (prenum || postnum)
+	    x = dopadding(x, prenum, postnum, preone, postone,
+			  premul, postmul);
+	if (eval && parsestr(x))
+	    return NULL;
+	xlen = strlen(x);
+	*str = strcatsub(&y, ostr, aptr, x, xlen, fstr, globsubst);
+	if (qt && !*y && isarr != 2)
+	    y = dupstring(nulstring);
+	setdata(n, (void *) y);
+    }
+    if (eval)
+	*str = (char *) getdata(n);
+
+    return n;
+}
+
+/*
+ * Arithmetic substitution: `a' is the string to be evaluated, `bptr'
+ * points to the beginning of the string containing it.  The tail of
+ * the string is given by `rest'. *bptr is modified with the substituted
+ * string. The function returns a pointer to the tail in the substituted
+ * string.
+ */
+
+/**/
+static char *
+arithsubst(char *a, char **bptr, char *rest)
+{
+    char *s = *bptr, *t, buf[DIGBUFSIZE];
+    char *b = buf;
+    long v;
+
+    singsub(&a);
+    v = matheval(a);
+    sprintf(buf, "%ld", v);
+    t = *bptr = (char *)ncalloc(strlen(*bptr) + strlen(buf) + strlen(rest) + 1);
+    t--;
+    while ((*++t = *s++));
+    t--;
+    while ((*++t = *b++));
+    strcat(t, rest);
+    return t;
+}
+
+/**/
+void
+modify(char **str, char **ptr)
+{
+    char *ptr1, *ptr2, *ptr3, del, *lptr, c, *test, *sep, *t, *tt, tc, *e;
+    char *copy, *all, *tmp, sav;
+    int gbal, wall, rec, al, nl;
+
+    test = NULL;
+
+    if (**ptr == ':')
+	*str = dupstring(*str);
+
+    while (**ptr == ':') {
+	lptr = *ptr;
+	(*ptr)++;
+	wall = gbal = 0;
+	rec = 1;
+	c = '\0';
+	sep = NULL;
+
+	for (; !c && **ptr;) {
+	    switch (**ptr) {
+	    case 'h':
+	    case 'r':
+	    case 'e':
+	    case 't':
+	    case 'l':
+	    case 'u':
+		c = **ptr;
+		break;
+
+	    case 's':
+		c = **ptr;
+		(*ptr)++;
+		ptr1 = *ptr;
+		del = *ptr1++;
+		for (ptr2 = ptr1; *ptr2 != del && *ptr2; ptr2++);
+		if (!*ptr2) {
+		    zerr("bad substitution", NULL, 0);
+		    return;
+		}
+		*ptr2++ = '\0';
+		for (ptr3 = ptr2; *ptr3 != del && *ptr3; ptr3++);
+		if ((sav = *ptr3))
+		    *ptr3++ = '\0';
+		if (*ptr1) {
+		    zsfree(hsubl);
+		    hsubl = ztrdup(ptr1);
+		}
+		if (!hsubl) {
+		    zerr("no previous substitution", NULL, 0);
+		    return;
+		}
+		zsfree(hsubr);
+		for (tt = hsubl; *tt; tt++)
+		    if (INULL(*tt))
+			chuck(tt--);
+		untokenize(hsubl);
+		for (tt = hsubr = ztrdup(ptr2); *tt; tt++)
+		    if (INULL(*tt))
+			chuck(tt--);
+		ptr2[-1] = del;
+		if (sav)
+		    ptr3[-1] = sav;
+		*ptr = ptr3 - 1;
+		break;
+
+	    case '&':
+		c = 's';
+		break;
+
+	    case 'g':
+		(*ptr)++;
+		gbal = 1;
+		break;
+
+	    case 'w':
+		wall = 1;
+		(*ptr)++;
+		break;
+	    case 'W':
+		wall = 1;
+		(*ptr)++;
+		ptr1 = get_strarg(ptr2 = *ptr);
+		if ((sav = *ptr1))
+		    *ptr1 = '\0';
+		sep = dupstring(ptr2 + 1);
+		if (sav)
+		    *ptr1 = sav;
+		*ptr = ptr1 + 1;
+		c = '\0';
+		break;
+
+	    case 'f':
+		rec = -1;
+		(*ptr)++;
+		break;
+	    case 'F':
+		rec = get_intarg(ptr);
+		(*ptr)++;
+		break;
+	    default:
+		*ptr = lptr;
+		return;
+	    }
+	}
+	(*ptr)++;
+	if (!c) {
+	    *ptr = lptr;
+	    return;
+	}
+	if (rec < 0)
+	    test = dupstring(*str);
+
+	while (rec--) {
+	    if (wall) {
+		al = 0;
+		all = NULL;
+		for (t = e = *str; (tt = findword(&e, sep));) {
+		    tc = *e;
+		    *e = '\0';
+		    copy = dupstring(tt);
+		    *e = tc;
+		    switch (c) {
+		    case 'h':
+			remtpath(&copy);
+			break;
+		    case 'r':
+			remtext(&copy);
+			break;
+		    case 'e':
+			rembutext(&copy);
+			break;
+		    case 't':
+			remlpaths(&copy);
+			break;
+		    case 'l':
+			downcase(&copy);
+			break;
+		    case 'u':
+			upcase(&copy);
+			break;
+		    case 's':
+			if (hsubl && hsubr)
+			    subst(&copy, hsubl, hsubr, gbal);
+			break;
+		    }
+		    tc = *tt;
+		    *tt = '\0';
+		    nl = al + strlen(t) + strlen(copy);
+		    ptr1 = tmp = (char *)halloc(nl + 1);
+		    if (all)
+			for (ptr2 = all; *ptr2;)
+			    *ptr1++ = *ptr2++;
+		    for (ptr2 = t; *ptr2;)
+			*ptr1++ = *ptr2++;
+		    *tt = tc;
+		    for (ptr2 = copy; *ptr2;)
+			*ptr1++ = *ptr2++;
+		    *ptr1 = '\0';
+		    al = nl;
+		    all = tmp;
+		    t = e;
+		}
+		*str = all;
+
+	    } else {
+		switch (c) {
+		case 'h':
+		    remtpath(str);
+		    break;
+		case 'r':
+		    remtext(str);
+		    break;
+		case 'e':
+		    rembutext(str);
+		    break;
+		case 't':
+		    remlpaths(str);
+		    break;
+		case 'l':
+		    downcase(str);
+		    break;
+		case 'u':
+		    upcase(str);
+		    break;
+		case 's':
+		    if (hsubl && hsubr) {
+			char *oldstr = *str;
+
+			subst(str, hsubl, hsubr, gbal);
+			if (*str != oldstr) {
+			    *str = dupstring(oldstr = *str);
+			    zsfree(oldstr);
+			}
+		    }
+		    break;
+		}
+	    }
+	    if (rec < 0) {
+		if (!strcmp(test, *str))
+		    rec = 0;
+		else
+		    test = dupstring(*str);
+	    }
+	}
+    }
+}
+
+/* get a directory stack entry */
+
+/**/
+static char *
+dstackent(char ch, int val)
+{
+    int backwards;
+    LinkNode end=(LinkNode)dirstack, n;
+
+    backwards = ch == (isset(PUSHDMINUS) ? '+' : '-');
+    if(!backwards && !val--)
+	return pwd;
+    if (backwards)
+	for (n=lastnode(dirstack); n != end && val; val--, n=prevnode(n));
+    else
+	for (end=NULL, n=firstnode(dirstack); n && val; val--, n=nextnode(n));
+    if (n == end) {
+	if (isset(NOMATCH))
+	    zerr("not enough directory stack entries.", NULL, 0);
+	return NULL;
+    }
+    return (char *)getdata(n);
+}
diff --git a/Src/system.h b/Src/system.h
new file mode 100644
index 000000000..e42f3b891
--- /dev/null
+++ b/Src/system.h
@@ -0,0 +1,598 @@
+/*
+ * system.h - system configuration header file
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#ifdef __hpux
+# define _INCLUDE_POSIX_SOURCE 1
+# define _INCLUDE_XOPEN_SOURCE 1
+# define _INCLUDE_HPUX_SOURCE 1
+#endif
+
+#ifdef sinix
+# define _XPG_IV 1
+#endif
+
+/* NeXT has half-implemented POSIX support *
+ * which currently fools configure         */
+#ifdef __NeXT__
+# undef HAVE_TERMIOS_H
+# undef HAVE_SYS_UTSNAME_H
+#endif
+
+#ifdef PROTOTYPES
+# define _(Args) Args
+#else
+# define _(Args) ()
+#endif
+
+#ifndef HAVE_ALLOCA
+# define alloca halloc
+#else
+# ifdef __GNUC__
+#  define alloca __builtin_alloca
+# else
+#  if HAVE_ALLOCA_H
+#   include <alloca.h>
+#  else
+#   ifdef _AIX
+ #   pragma alloca
+#   else
+#    ifndef alloca
+char *alloca _((size_t));
+#    endif
+#   endif
+#  endif
+# endif
+#endif
+
+#ifdef HAVE_LIBC_H     /* NeXT */
+# include <libc.h>
+#endif
+
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <setjmp.h>
+
+#ifdef HAVE_PWD_H
+# include <pwd.h>
+#endif
+
+#ifdef HAVE_GRP_H
+# include <grp.h>
+#endif
+
+#ifdef HAVE_DIRENT_H
+# include <dirent.h>
+#else /* !HAVE_DIRENT_H */
+# ifdef HAVE_SYS_NDIR_H
+#  include <sys/ndir.h>
+# endif
+# ifdef HAVE_SYS_DIR_H
+#  include <sys/dir.h>
+# endif
+# ifdef HAVE_NDIR_H
+#  include <ndir.h>
+# endif
+# define dirent direct
+# undef HAVE_STRUCT_DIRENT_D_INO
+# undef HAVE_STRUCT_DIRENT_D_STAT
+# ifdef HAVE_STRUCT_DIRECT_D_INO
+#  define HAVE_STRUCT_DIRENT_D_INO HAVE_STRUCT_DIRECT_D_INO
+# endif
+# ifdef HAVE_STRUCT_DIRECT_D_STAT
+#  define HAVE_STRUCT_DIRENT_D_STAT HAVE_STRUCT_DIRECT_D_STAT
+# endif
+#endif /* !HAVE_DIRENT_H */
+
+#ifdef HAVE_STDLIB_H
+# ifdef ZSH_MEM
+   /* malloc and calloc are macros in GNU's stdlib.h unless the
+    * the __MALLOC_0_RETURNS_NULL macro is defined */
+#  define __MALLOC_0_RETURNS_NULL
+# endif
+# include <stdlib.h>
+#endif
+
+#ifdef HAVE_ERRNO_H
+# include <errno.h>
+#endif
+
+#ifdef TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+#  include <time.h>
+# endif
+#endif
+
+/* This is needed by some old SCO unices */
+#ifndef HAVE_STRUCT_TIMEZONE
+struct timezone {
+    int tz_minuteswest;
+    int tz_dsttime;
+};
+#endif
+
+/* There's more than one non-standard way to get at this data */
+#if !defined(HAVE_STRUCT_DIRENT_D_INO) && defined(HAVE_STRUCT_DIRENT_D_STAT)
+# define d_ino d_stat.st_ino
+# define HAVE_STRUCT_DIRENT_D_INO HAVE_STRUCT_DIRENT_D_STAT
+#endif /* !HAVE_STRUCT_DIRENT_D_INO && HAVE_STRUCT_DIRENT_D_STAT */
+
+/* Sco needs the following include for struct utimbuf *
+ * which is strange considering we do not use that    *
+ * anywhere in the code                               */
+#ifdef __sco
+# include <utime.h>
+#endif
+
+#ifdef HAVE_SYS_TIMES_H
+# include <sys/times.h>
+#endif
+
+#if STDC_HEADERS || HAVE_STRING_H
+# include <string.h>
+/* An ANSI string.h and pre-ANSI memory.h might conflict.  */
+# if !STDC_HEADERS && HAVE_MEMORY_H
+#  include <memory.h>
+# endif /* not STDC_HEADERS and HAVE_MEMORY_H */
+#else   /* not STDC_HEADERS and not HAVE_STRING_H */
+# include <strings.h>
+/* memory.h and strings.h conflict on some systems.  */
+#endif /* not STDC_HEADERS and not HAVE_STRING_H */
+
+#ifdef HAVE_LOCALE_H
+# include <locale.h>
+#endif
+
+#ifdef HAVE_LIMITS_H
+# include <limits.h>
+#endif
+
+#ifdef HAVE_VARIABLE_LENGTH_ARRAYS
+# define VARARR(X,Y,Z)	X (Y)[Z]
+#else
+# define VARARR(X,Y,Z)	X *(Y) = (X *) alloca(sizeof(X) * (Z))
+#endif
+
+/* we should be getting this value from pathconf(_PC_PATH_MAX) */
+/* but this is too much trouble                                */
+#ifndef PATH_MAX
+# ifdef MAXPATHLEN
+#  define PATH_MAX MAXPATHLEN
+# else
+   /* so we will just pick something */
+#  define PATH_MAX 1024
+# endif
+#endif
+
+/* we should be getting this value from sysconf(_SC_OPEN_MAX) */
+/* but this is too much trouble                               */
+#ifndef OPEN_MAX
+# ifdef NOFILE
+#  define OPEN_MAX NOFILE
+# else
+   /* so we will just pick something */
+#  define OPEN_MAX 64
+# endif
+#endif
+
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#else
+# include <sys/file.h>
+#endif
+
+/* The following will only be defined if <sys/wait.h> is POSIX.    *
+ * So we don't have to worry about union wait. But some machines   *
+ * (NeXT) include <sys/wait.h> from other include files, so we     *
+ * need to undef and then redefine the wait macros if <sys/wait.h> *
+ * is not POSIX.                                                   */
+
+#ifdef HAVE_SYS_WAIT_H
+# include <sys/wait.h>
+#else
+# undef WIFEXITED
+# undef WEXITSTATUS
+# undef WIFSIGNALED
+# undef WTERMSIG
+# undef WCOREDUMP
+# undef WIFSTOPPED
+# undef WSTOPSIG
+#endif
+
+/* missing macros for wait/waitpid/wait3 */
+#ifndef WIFEXITED
+# define WIFEXITED(X) (((X)&0377)==0)
+#endif
+#ifndef WEXITSTATUS
+# define WEXITSTATUS(X) (((X)>>8)&0377)
+#endif
+#ifndef WIFSIGNALED
+# define WIFSIGNALED(X) (((X)&0377)!=0&&((X)&0377)!=0177)
+#endif
+#ifndef WTERMSIG
+# define WTERMSIG(X) ((X)&0177)
+#endif
+#ifndef WCOREDUMP
+# define WCOREDUMP(X) ((X)&0200)
+#endif
+#ifndef WIFSTOPPED
+# define WIFSTOPPED(X) (((X)&0377)==0177)
+#endif
+#ifndef WSTOPSIG
+# define WSTOPSIG(X) (((X)>>8)&0377)
+#endif
+
+#ifdef HAVE_SYS_SELECT_H
+# ifndef TIME_H_SELECT_H_CONFLICTS
+#  include <sys/select.h>
+# endif
+#endif
+
+#ifdef HAVE_SYS_FILIO_H
+# include <sys/filio.h>
+#endif
+
+#ifdef HAVE_TERMIOS_H
+# ifdef __sco
+   /* termios.h includes sys/termio.h instead of sys/termios.h; *
+    * hence the declaration for struct termios is missing       */
+#  include <sys/termios.h>
+# else
+#  include <termios.h>
+# endif
+# ifdef _POSIX_VDISABLE
+#  define VDISABLEVAL _POSIX_VDISABLE
+# else
+#  define VDISABLEVAL 0
+# endif
+# define HAS_TIO 1
+#else    /* not TERMIOS */
+# ifdef HAVE_TERMIO_H
+#  include <termio.h>
+#  define VDISABLEVAL -1
+#  define HAS_TIO 1
+# else   /* not TERMIOS and TERMIO */
+#  include <sgtty.h>
+# endif  /* HAVE_TERMIO_H  */
+#endif   /* HAVE_TERMIOS_H */
+
+#ifdef HAVE_TERMCAP_H
+# include <termcap.h>
+#endif
+
+#if defined(GWINSZ_IN_SYS_IOCTL) || defined(CLOBBERS_TYPEAHEAD)
+# include <sys/ioctl.h>
+#endif
+#ifdef WINSIZE_IN_PTEM
+# include <sys/stream.h>
+# include <sys/ptem.h>
+#endif
+
+#ifdef HAVE_SYS_PARAM_H
+# include <sys/param.h>
+#endif
+
+#ifdef HAVE_SYS_UTSNAME_H
+# include <sys/utsname.h>
+#endif
+
+#define DEFAULT_WORDCHARS "*?_-.[]~=/&;!#$%^(){}<>"
+#define DEFAULT_TIMEFMT   "%J  %U user %S system %P cpu %*E total"
+
+/* Posix getpgrp takes no argument, while the BSD version *
+ * takes the process ID as an argument                    */
+#ifdef GETPGRP_VOID
+# define GETPGRP() getpgrp()
+#else
+# define GETPGRP() getpgrp(0)
+#endif
+
+#ifndef HAVE_GETLOGIN
+# define getlogin() cuserid(NULL)
+#endif
+
+#ifdef HAVE_SETPGID
+# define setpgrp setpgid
+#endif
+
+/* can we set the user/group id of a process */
+
+#ifndef HAVE_SETUID
+# ifdef HAVE_SETREUID
+#  define setuid(X) setreuid(X,X)
+#  define setgid(X) setregid(X,X)
+#  define HAVE_SETUID
+# endif
+#endif
+
+/* can we set the effective user/group id of a process */
+
+#ifndef HAVE_SETEUID
+# ifdef HAVE_SETREUID
+#  define seteuid(X) setreuid(-1,X)
+#  define setegid(X) setregid(-1,X)
+#  define HAVE_SETEUID
+# else
+#  ifdef HAVE_SETRESUID
+#   define seteuid(X) setresuid(-1,X,-1)
+#   define setegid(X) setresgid(-1,X,-1)
+#   define HAVE_SETEUID
+#  endif
+# endif
+#endif
+
+#ifdef HAVE_SYS_RESOURCE_H
+# include <sys/resource.h>
+# if defined(__hpux) && !defined(RLIMIT_CPU)
+/* HPUX does have the BSD rlimits in the kernel.  Officially they are *
+ * unsupported but quite a few of them like RLIMIT_CORE seem to work. *
+ * All the following are in the <sys/resource.h> but made visible     *
+ * only for the kernel.                                               */
+#  define	RLIMIT_CPU	0
+#  define	RLIMIT_FSIZE	1
+#  define	RLIMIT_DATA	2
+#  define	RLIMIT_STACK	3
+#  define	RLIMIT_CORE	4
+#  define	RLIMIT_RSS	5
+#  define	RLIMIT_NOFILE   6
+#  define	RLIMIT_OPEN_MAX	RLIMIT_NOFILE
+#  define	RLIM_NLIMITS	7
+#  define	RLIM_INFINITY	0x7fffffff
+# endif
+#endif
+
+/* we use the SVR4 constant instead of the BSD one */
+#if !defined(RLIMIT_NOFILE) && defined(RLIMIT_OFILE)
+# define RLIMIT_NOFILE RLIMIT_OFILE
+#endif
+#if !defined(RLIMIT_VMEM) && defined(RLIMIT_AS)
+# define RLIMIT_VMEM RLIMIT_AS
+#endif
+
+#ifdef HAVE_SYS_CAPABILITY_H
+# include <sys/capability.h>
+#endif
+
+/* DIGBUFSIZ is the length of a buffer which can hold the -LONG_MAX-1 *
+ * converted to printable decimal form including the sign and the     *
+ * terminating null character. Below 0.30103 > lg 2.                  */
+#define DIGBUFSIZE ((int)(((sizeof(long) * 8) - 1) * 0.30103) + 3)
+
+/* If your stat macros are broken, we will *
+ * just undefine them.                     */
+
+#ifdef STAT_MACROS_BROKEN
+# undef S_ISBLK
+# undef S_ISCHR
+# undef S_ISDIR
+# undef S_ISFIFO
+# undef S_ISLNK
+# undef S_ISMPB
+# undef S_ISMPC
+# undef S_ISNWK
+# undef S_ISOFD
+# undef S_ISOFL
+# undef S_ISREG
+# undef S_ISSOCK
+#endif  /* STAT_MACROS_BROKEN.  */
+
+/* If you are missing the stat macros, we *
+ * define our own                         */
+
+#ifndef S_IFMT
+# define S_IFMT 0170000
+#endif
+
+#if !defined(S_ISBLK) && defined(S_IFBLK)
+# define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
+#endif
+#if !defined(S_ISCHR) && defined(S_IFCHR)
+# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
+#endif
+#if !defined(S_ISDIR) && defined(S_IFDIR)
+# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
+#endif
+#if !defined(S_ISFIFO) && defined(S_IFIFO)
+# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
+#endif
+#if !defined(S_ISLNK) && defined(S_IFLNK)
+# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
+#endif
+#if !defined(S_ISMPB) && defined(S_IFMPB)        /* V7 */
+# define S_ISMPB(m) (((m) & S_IFMT) == S_IFMPB)
+#endif
+#if !defined(S_ISMPC) && defined(S_IFMPC)        /* V7 */
+# define S_ISMPC(m) (((m) & S_IFMT) == S_IFMPC)
+#endif
+#if !defined(S_ISNWK) && defined(S_IFNWK)        /* HP/UX */
+# define S_ISNWK(m) (((m) & S_IFMT) == S_IFNWK)
+#endif
+#if !defined(S_ISOFD) && defined(S_IFOFD)        /* Cray */
+# define S_ISOFD(m) (((m) & S_IFMT) == S_IFOFD)
+#endif
+#if !defined(S_ISOFL) && defined(S_IFOFL)        /* Cray */
+# define S_ISOFL(m) (((m) & S_IFMT) == S_IFOFL)
+#endif
+#if !defined(S_ISREG) && defined(S_IFREG)
+# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
+#endif
+#if !defined(S_ISSOCK) && defined(S_IFSOCK)
+# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
+#endif
+
+/* We will pretend to have all file types on any system. */
+
+#ifndef S_ISBLK
+# define S_ISBLK(m) ((void)(m), 0)
+#endif
+#ifndef S_ISCHR
+# define S_ISCHR(m) ((void)(m), 0)
+#endif
+#ifndef S_ISDIR
+# define S_ISDIR(m) ((void)(m), 0)
+#endif
+#ifndef S_ISFIFO
+# define S_ISFIFO(m) ((void)(m), 0)
+#endif
+#ifndef S_ISLNK
+# define S_ISLNK(m) ((void)(m), 0)
+#endif
+#ifndef S_ISMPB
+# define S_ISMPB(m) ((void)(m), 0)
+#endif
+#ifndef S_ISMPC
+# define S_ISMPC(m) ((void)(m), 0)
+#endif
+#ifndef S_ISNWK
+# define S_ISNWK(m) ((void)(m), 0)
+#endif
+#ifndef S_ISOFD
+# define S_ISOFD(m) ((void)(m), 0)
+#endif
+#ifndef S_ISOFL
+# define S_ISOFL(m) ((void)(m), 0)
+#endif
+#ifndef S_ISREG
+# define S_ISREG(m) ((void)(m), 0)
+#endif
+#ifndef S_ISSOCK
+# define S_ISSOCK(m) ((void)(m), 0)
+#endif
+
+/* file mode permission bits */
+
+#ifndef S_ISUID
+# define S_ISUID 04000
+#endif
+#ifndef S_ISGID
+# define S_ISGID 02000
+#endif
+#ifndef S_ISVTX
+# define S_ISVTX 01000
+#endif
+#ifndef S_IRUSR
+# define S_IRUSR 00400
+#endif
+#ifndef S_IWUSR
+# define S_IWUSR 00200
+#endif
+#ifndef S_IXUSR
+# define S_IXUSR 00100
+#endif
+#ifndef S_IRGRP
+# define S_IRGRP 00040
+#endif
+#ifndef S_IWGRP
+# define S_IWGRP 00020
+#endif
+#ifndef S_IXGRP
+# define S_IXGRP 00010
+#endif
+#ifndef S_IROTH
+# define S_IROTH 00004
+#endif
+#ifndef S_IWOTH
+# define S_IWOTH 00002
+#endif
+#ifndef S_IXOTH
+# define S_IXOTH 00001
+#endif
+#ifndef S_IRWXU
+# define S_IRWXU (S_IRUSR|S_IWUSR|S_IXUSR)
+#endif
+#ifndef S_IRWXG
+# define S_IRWXG (S_IRGRP|S_IWGRP|S_IXGRP)
+#endif
+#ifndef S_IRWXO
+# define S_IRWXO (S_IROTH|S_IWOTH|S_IXOTH)
+#endif
+#ifndef S_IRUGO
+# define S_IRUGO (S_IRUSR|S_IRGRP|S_IROTH)
+#endif
+#ifndef S_IWUGO
+# define S_IWUGO (S_IWUSR|S_IWGRP|S_IWOTH)
+#endif
+#ifndef S_IXUGO
+# define S_IXUGO (S_IXUSR|S_IXGRP|S_IXOTH)
+#endif
+
+#ifndef HAVE_LSTAT
+# define lstat stat
+#endif
+
+#ifndef HAVE_READLINK
+# define readlink(PATH, BUF, BUFSZ) \
+    ((void)(PATH), (void)(BUF), (void)(BUFSZ), errno = ENOSYS, -1)
+#endif
+
+#ifndef F_OK          /* missing macros for access() */
+# define F_OK 0
+# define X_OK 1
+# define W_OK 2
+# define R_OK 4
+#endif
+
+#ifndef HAVE_MEMCPY
+# define memcpy memmove
+#endif
+
+#ifndef HAVE_MEMMOVE
+# define memmove(dest, src, len) bcopy((src), (dest), (len))
+#endif
+
+#ifndef offsetof
+# define offsetof(TYPE, MEM) ((char *)&((TYPE *)0)->MEM - (char *)(TYPE *)0)
+#endif
+
+extern char **environ;
+
+/* These variables are sometimes defined in, *
+ * and needed by, the termcap library.       */
+#if MUST_DEFINE_OSPEED
+extern char PC, *BC, *UP;
+extern short ospeed;
+#endif
+
+#ifndef O_NOCTTY
+# define O_NOCTTY 0
+#endif
diff --git a/Src/text.c b/Src/text.c
new file mode 100644
index 000000000..b7df8012f
--- /dev/null
+++ b/Src/text.c
@@ -0,0 +1,526 @@
+/*
+ * text.c - textual representations of syntax trees
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "text.pro"
+
+static char *tptr, *tbuf, *tlim;
+static int tsiz, tindent, tnewlins;
+
+/* add a character to the text buffer */
+
+/**/
+static void
+taddchr(int c)
+{
+    *tptr++ = c;
+    if (tptr == tlim) {
+	if (!tbuf) {
+	    tptr--;
+	    return;
+	}
+	tbuf = realloc(tbuf, tsiz *= 2);
+	tlim = tbuf + tsiz;
+	tptr = tbuf + tsiz / 2;
+    }
+}
+
+/* add a string to the text buffer */
+
+/**/
+static void
+taddstr(char *s)
+{
+    int sl = strlen(s);
+
+    while (tptr + sl >= tlim) {
+	int x = tptr - tbuf;
+
+	if (!tbuf)
+	    return;
+	tbuf = realloc(tbuf, tsiz *= 2);
+	tlim = tbuf + tsiz;
+	tptr = tbuf + x;
+    }
+    strcpy(tptr, s);
+    tptr += sl;
+}
+
+#if 0
+/* add an integer to the text buffer */
+
+/**/
+void
+taddint(int x)
+{
+    char buf[DIGBUFSIZE];
+
+    sprintf(buf, "%d", x);
+    taddstr(buf);
+}
+#endif
+
+/* add a newline, or something equivalent, to the text buffer */
+
+/**/
+static void
+taddnl(void)
+{
+    int t0;
+
+    if (tnewlins) {
+	taddchr('\n');
+	for (t0 = 0; t0 != tindent; t0++)
+	    taddchr('\t');
+    } else
+	taddstr("; ");
+}
+
+/* get a permanent textual representation of n */
+
+/**/
+char *
+getpermtext(struct node *n)
+{
+    tnewlins = 1;
+    tbuf = (char *)zalloc(tsiz = 32);
+    tptr = tbuf;
+    tlim = tbuf + tsiz;
+    tindent = 1;
+    gettext2(n);
+    *tptr = '\0';
+    untokenize(tbuf);
+    return tbuf;
+}
+
+/* get a representation of n in a job text buffer */
+
+/**/
+char *
+getjobtext(struct node *n)
+{
+    static char jbuf[JOBTEXTSIZE];
+
+    tnewlins = 0;
+    tbuf = NULL;
+    tptr = jbuf;
+    tlim = tptr + JOBTEXTSIZE - 1;
+    tindent = 1;
+    gettext2(n);
+    *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))
+
+/**/
+static void
+gettext2(struct node *n)
+{
+    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);
+	}
+	break;
+    case N_CMD:
+	nn = _Cmd(n);
+	switch (nn->type) {
+	case SIMPLE:
+	    getsimptext(nn);
+	    break;
+	case SUBSH:
+	    taddstr("( ");
+	    tindent++;
+	    gt2(nn->u.list);
+	    tindent--;
+	    taddstr(" )");
+	    break;
+	case ZCTIME:
+	    taddstr("time ");
+	    tindent++;
+	    gt2(nn->u.pline);
+	    tindent--;
+	    break;
+	case FUNCDEF:
+	    taddlist(nn->args);
+	    taddstr(" () {");
+	    tindent++;
+	    taddnl();
+	    gt2(nn->u.list);
+	    tindent--;
+	    taddnl();
+	    taddstr("}");
+	    break;
+	case CURSH:
+	    taddstr("{ ");
+	    tindent++;
+	    gt2(nn->u.list);
+	    tindent--;
+	    taddstr(" }");
+	    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");
+	    } else {
+		taddstr(nn->u.forcmd->name);
+		if (nn->u.forcmd->inflag) {
+		    taddstr(" in ");
+		    taddlist(nn->args);
+		}
+		taddnl();
+		taddstr("do");
+	    }
+	    tindent++;
+	    taddnl();
+	    gt2(nn->u.forcmd->list);
+	    tindent--;
+	    taddnl();
+	    taddstr("done");
+	    break;
+	case CIF:
+	    gt2(nn->u.ifcmd);
+	    taddstr("fi");
+	    break;
+	case CCASE:
+	    gt2(nn->u.casecmd);
+	    break;
+	case COND:
+	    taddstr("[[ ");
+	    gt2(nn->u.cond);
+	    taddstr(" ]]");
+	    break;
+	case CARITH:
+	    taddstr("((");
+	    taddlist(nn->args);
+	    taddstr("))");
+	    break;
+	case CREPEAT:
+	    taddstr("repeat ");
+	    taddlist(nn->args);
+	    taddnl();
+	    taddstr("do");
+	    tindent++;
+	    taddnl();
+	    gt2(nn->u.list);
+	    tindent--;
+	    taddnl();
+	    taddstr("done");
+	    break;
+	case CWHILE:
+	    gt2(nn->u.whilecmd);
+	    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++) {
+		if (tnewlins)
+		    taddnl();
+		else
+		    taddchr(' ');
+		taddstr(*p + 1);
+		taddstr(") ");
+		tindent++;
+		gt2(*l);
+		tindent--;
+		taddstr(" ;");
+		taddchr(**p);
+	    }
+	    tindent--;
+	    if (tnewlins)
+		taddnl();
+	    else
+		taddchr(' ');
+	    taddstr("esac");
+	    break;
+	}
+    case N_IF:
+	{
+	    List *i, *t;
+
+	    taddstr("if ");
+	    for (i = _ifcmd(n)->ifls, t = _ifcmd(n)->thenls; *i; i++, t++) {
+		tindent++;
+		gt2(*i);
+		tindent--;
+		taddnl();
+		taddstr("then");
+		tindent++;
+		taddnl();
+		gt2(*t);
+		tindent--;
+		taddnl();
+		if (i[1]) {
+		    taddstr("elif ");
+		}
+	    }
+	    if (*t) {
+		taddstr("else");
+		tindent++;
+		taddnl();
+		gt2(*t);
+		tindent--;
+		taddnl();
+	    }
+	    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(' ');
+	}
+    }
+    taddlist(cmd->args);
+}
+
+/**/
+void
+getredirs(Cmd cmd)
+{
+    LinkNode n;
+    static char *fstr[] =
+    {
+	">", ">|", ">>", ">>|", "&>", "&>|", "&>>", "&>>|", "<>", "<",
+	"<<", "<<-", "<<<", "<&", ">&", NULL /* >&- */, "<", ">"
+    };
+
+    taddchr(' ');
+    for (n = firstnode(cmd->redir); n; incnode(n)) {
+	struct redir *f = (struct redir *)getdata(n);
+
+	switch (f->type) {
+	case WRITE:
+	case WRITENOW:
+	case APP:
+	case APPNOW:
+	case ERRWRITE:
+	case ERRWRITENOW:
+	case ERRAPP:
+	case ERRAPPNOW:
+	case READ:
+	case READWRITE:
+	case HERESTR:
+	case MERGEIN:
+	case MERGEOUT:
+	case INPIPE:
+	case OUTPIPE:
+	    if (f->fd1 != (IS_READFD(f->type) ? 0 : 1))
+		taddchr('0' + f->fd1);
+	    taddstr(fstr[f->type]);
+	    taddchr(' ');
+	    taddstr(f->name);
+	    taddchr(' ');
+	    break;
+#ifdef DEBUG
+	case CLOSE:
+	    DPUTS(1, "BUG: CLOSE in getredirs()");
+	    taddchr(f->fd1 + '0');
+	    taddstr(">&- ");
+	    break;
+	default:
+	    DPUTS(1, "BUG: unknown redirection in getredirs()");
+#endif
+	}
+    }
+    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/utils.c b/Src/utils.c
new file mode 100644
index 000000000..3619fa95d
--- /dev/null
+++ b/Src/utils.c
@@ -0,0 +1,3726 @@
+/*
+ * utils.c - miscellaneous utilities
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "utils.pro"
+
+/* Print an error */
+
+/**/
+void
+zwarnnam(const char *cmd, const char *fmt, const char *str, int num)
+{
+    int waserr;
+
+    waserr = errflag;
+    zerrnam(cmd, fmt, str, num);
+    errflag = waserr;
+}
+
+/* name of script being sourced */
+
+/**/
+char *scriptname;
+ 
+/**/
+void
+zerr(const char *fmt, const char *str, int num)
+{
+    if (errflag || noerrs)
+	return;
+    errflag = 1;
+    trashzle();
+    /*
+     * scriptname is set when sourcing scripts, so that we get the
+     * correct name instead of the generic name of whatever
+     * program/script is running.
+     */
+    nicezputs(isset(SHINSTDIN) ? "zsh" :
+	      scriptname ? scriptname : argzero, stderr);
+    fputs(": ", stderr);
+    zerrnam(NULL, fmt, str, num);
+}
+
+/**/
+void
+zerrnam(const char *cmd, const char *fmt, const char *str, int num)
+{
+    if (cmd) {
+	if (errflag || noerrs)
+	    return;
+	errflag = 1;
+	trashzle();
+	if(unset(SHINSTDIN)) {
+	    nicezputs(scriptname ? scriptname : argzero, stderr);
+	    fputs(": ", stderr);
+	}
+	nicezputs(cmd, stderr);
+	fputs(": ", stderr);
+    }
+    while (*fmt)
+	if (*fmt == '%') {
+	    fmt++;
+	    switch (*fmt++) {
+	    case 's':
+		nicezputs(str, stderr);
+		break;
+	    case 'l': {
+		char *s;
+		num = metalen(str, num);
+		s = halloc(num + 1);
+		memcpy(s, str, num);
+		s[num] = '\0';
+		nicezputs(s, stderr);
+		break;
+	    }
+	    case 'd':
+		fprintf(stderr, "%d", num);
+		break;
+	    case '%':
+		putc('%', stderr);
+		break;
+	    case 'c':
+		fputs(nicechar(num), stderr);
+		break;
+	    case 'e':
+		/* print the corresponding message for this errno */
+		if (num == EINTR) {
+		    fputs("interrupt\n", stderr);
+		    errflag = 1;
+		    return;
+		}
+		/* If the message is not about I/O problems, it looks better *
+		 * if we uncapitalize the first letter of the message        */
+		if (num == EIO)
+		    fputs(strerror(num), stderr);
+		else {
+		    char *errmsg = strerror(num);
+		    fputc(tulower(errmsg[0]), stderr);
+		    fputs(errmsg + 1, stderr);
+		}
+		break;
+	    }
+	} else {
+	    putc(*fmt == Meta ? *++fmt ^ 32 : *fmt, stderr);
+	    fmt++;
+	}
+    if (unset(SHINSTDIN) && lineno)
+	fprintf(stderr, " [%ld]\n", lineno);
+    else
+	putc('\n', stderr);
+    fflush(stderr);
+}
+
+/* Output a single character, for the termcap routines.     *
+ * This is used instead of putchar since it can be a macro. */
+
+/**/
+int
+putraw(int c)
+{
+    putc(c, stdout);
+    return 0;
+}
+
+/* Output a single character, for the termcap routines. */
+
+/**/
+int
+putshout(int c)
+{
+    putc(c, shout);
+    return 0;
+}
+
+/* Turn a character into a visible representation thereof.  The visible *
+ * string is put together in a static buffer, and this function returns *
+ * a pointer to it.  Printable characters stand for themselves, DEL is  *
+ * represented as "^?", newline and tab are represented as "\n" and     *
+ * "\t", and normal control characters are represented in "^C" form.    *
+ * Characters with bit 7 set, if unprintable, are represented as "\M-"  *
+ * followed by the visible representation of the character with bit 7   *
+ * stripped off.  Tokens are interpreted, rather than being treated as  *
+ * literal characters.                                                  */
+
+/**/
+char *
+nicechar(int c)
+{
+    static char buf[6];
+    char *s = buf;
+    c &= 0xff;
+    if (isprint(c))
+	goto done;
+    if (c & 0x80) {
+	if (isset(PRINTEIGHTBIT))
+	    goto done;
+	*s++ = '\\';
+	*s++ = 'M';
+	*s++ = '-';
+	c &= 0x7f;
+	if(isprint(c))
+	    goto done;
+    }
+    if (c == 0x7f) {
+	*s++ = '^';
+	c = '?';
+    } else if (c == '\n') {
+	*s++ = '\\';
+	c = 'n';
+    } else if (c == '\t') {
+	*s++ = '\\';
+	c = 't';
+    } else if (c < 0x20) {
+	*s++ = '^';
+	c += 0x40;
+    }
+    done:
+    *s++ = c;
+    *s = 0;
+    return buf;
+}
+
+#if 0
+/* Output a string's visible representation. */
+
+/**/
+void
+nicefputs(char *s, FILE *f)
+{
+    for (; *s; s++)
+	fputs(nicechar(STOUC(*s)), f);
+}
+#endif
+
+/* Return the length of the visible representation of a string. */
+
+/**/
+size_t
+nicestrlen(char *s)
+{
+    size_t l = 0;
+
+    for (; *s; s++)
+	l += strlen(nicechar(STOUC(*s)));
+    return l;
+}
+
+/* get a symlink-free pathname for s relative to PWD */
+
+/**/
+char *
+findpwd(char *s)
+{
+    char *t;
+
+    if (*s == '/')
+	return xsymlink(s);
+    s = tricat((pwd[1]) ? pwd : "", "/", s);
+    t = xsymlink(s);
+    zsfree(s);
+    return t;
+}
+
+/* Check whether a string contains the *
+ * name of the present directory.      */
+
+/**/
+int
+ispwd(char *s)
+{
+    struct stat sbuf, tbuf;
+
+    if (stat(unmeta(s), &sbuf) == 0 && stat(".", &tbuf) == 0)
+	if (sbuf.st_dev == tbuf.st_dev && sbuf.st_ino == tbuf.st_ino)
+	    return 1;
+    return 0;
+}
+
+static char xbuf[PATH_MAX*2];
+
+/**/
+static char **
+slashsplit(char *s)
+{
+    char *t, **r, **q;
+    int t0;
+
+    if (!*s)
+	return (char **) zcalloc(sizeof(char **));
+
+    for (t = s, t0 = 0; *t; t++)
+	if (*t == '/')
+	    t0++;
+    q = r = (char **) zalloc(sizeof(char **) * (t0 + 2));
+
+    while ((t = strchr(s, '/'))) {
+	*q++ = ztrduppfx(s, t - s);
+	while (*t == '/')
+	    t++;
+	if (!*t) {
+	    *q = NULL;
+	    return r;
+	}
+	s = t;
+    }
+    *q++ = ztrdup(s);
+    *q = NULL;
+    return r;
+}
+
+/* expands symlinks and .. or . expressions */
+/* if flag = 0, only expand .. and . expressions */
+
+/**/
+static int
+xsymlinks(char *s, int flag)
+{
+    char **pp, **opp;
+    char xbuf2[PATH_MAX*2], xbuf3[PATH_MAX*2];
+    int t0, ret = 0;
+
+    opp = pp = slashsplit(s);
+    for (; *pp; pp++) {
+	if (!strcmp(*pp, ".")) {
+	    zsfree(*pp);
+	    continue;
+	}
+	if (!strcmp(*pp, "..")) {
+	    char *p;
+
+	    zsfree(*pp);
+	    if (!strcmp(xbuf, "/"))
+		continue;
+	    p = xbuf + strlen(xbuf);
+	    while (*--p != '/');
+	    *p = '\0';
+	    continue;
+	}
+	if (unset(CHASELINKS)) {
+	    strcat(xbuf, "/");
+	    strcat(xbuf, *pp);
+	    zsfree(*pp);
+	    continue;
+	}
+	sprintf(xbuf2, "%s/%s", xbuf, *pp);
+	t0 = readlink(unmeta(xbuf2), xbuf3, PATH_MAX);
+	if (t0 == -1 || !flag) {
+	    strcat(xbuf, "/");
+	    strcat(xbuf, *pp);
+	    zsfree(*pp);
+	} else {
+	    ret = 1;
+	    metafy(xbuf3, t0, META_NOALLOC);
+	    if (*xbuf3 == '/') {
+		strcpy(xbuf, "");
+		xsymlinks(xbuf3 + 1, flag);
+	    } else
+		xsymlinks(xbuf3, flag);
+	    zsfree(*pp);
+	}
+    }
+    free(opp);
+    return ret;
+}
+
+/* expand symlinks in s, and remove other weird things */
+
+/**/
+char *
+xsymlink(char *s)
+{
+    if (unset(CHASELINKS))
+	return ztrdup(s);
+    if (*s != '/')
+	return NULL;
+    *xbuf = '\0';
+    if (!xsymlinks(s + 1, 1))
+	return ztrdup(s);
+    if (!*xbuf)
+	return ztrdup("/");
+    return ztrdup(xbuf);
+}
+
+/**/
+void
+print_if_link(char *s)
+{
+    int chase;
+
+    if (*s == '/') {
+	chase = opts[CHASELINKS];
+	opts[CHASELINKS] = 1;
+	*xbuf = '\0';
+	if (xsymlinks(s + 1, 1))
+	    printf(" -> "), zputs(*xbuf ? xbuf : "/", stdout);
+	opts[CHASELINKS] = chase;
+    }
+}
+
+/* print a directory */
+
+/**/
+void
+fprintdir(char *s, FILE *f)
+{
+    Nameddir d = finddir(s);
+
+    if (!d)
+	fputs(unmeta(s), f);
+    else {
+	putc('~', f);
+	fputs(unmeta(d->nam), f);
+	fputs(unmeta(s + strlen(d->dir)), f);
+    }
+}
+
+/* Returns the current username.  It caches the username *
+ * and uid to try to avoid requerying the password files *
+ * or NIS/NIS+ database.                                 */
+
+/**/
+uid_t cached_uid;
+/**/
+char *cached_username;
+
+/**/
+char *
+get_username(void)
+{
+#ifdef HAVE_GETPWUID
+    struct passwd *pswd;
+    uid_t current_uid;
+ 
+    current_uid = getuid();
+    if (current_uid != cached_uid) {
+	cached_uid = current_uid;
+	zsfree(cached_username);
+	if ((pswd = getpwuid(current_uid)))
+	    cached_username = ztrdup(pswd->pw_name);
+	else
+	    cached_username = ztrdup("");
+    }
+#else /* !HAVE_GETPWUID */
+    cached_uid = current_uid;
+#endif /* !HAVE_GETPWUID */
+    return cached_username;
+}
+
+/* static variables needed by finddir(). */
+
+static char *finddir_full;
+static Nameddir finddir_last;
+static int finddir_best;
+
+/* ScanFunc used by finddir(). */
+
+/**/
+static void
+finddir_scan(HashNode hn, int flags)
+{
+    Nameddir nd = (Nameddir) hn;
+
+    if(nd->diff > finddir_best && !dircmp(nd->dir, finddir_full)) {
+	finddir_last=nd;
+	finddir_best=nd->diff;
+    }
+}
+
+/* See if a path has a named directory as its prefix. *
+ * If passed a NULL argument, it will invalidate any  *
+ * cached information.                                */
+
+/**/
+Nameddir
+finddir(char *s)
+{
+    static struct nameddir homenode = { NULL, "", 0, NULL, 0 };
+    static int ffsz;
+
+    /* Invalidate directory cache if argument is NULL.  This is called *
+     * whenever a node is added to or removed from the hash table, and *
+     * whenever the value of $HOME changes.  (On startup, too.)        */
+    if (!s) {
+	homenode.dir = home;
+	homenode.diff = strlen(home);
+	if(homenode.diff==1)
+	    homenode.diff = 0;
+	if(!finddir_full)
+	    finddir_full = zalloc(ffsz = PATH_MAX);
+	finddir_full[0] = 0;
+	return finddir_last = NULL;
+    }
+
+    if(!strcmp(s, finddir_full) && *finddir_full)
+	return finddir_last;
+
+    if(strlen(s) >= ffsz) {
+	free(finddir_full);
+	finddir_full = zalloc(ffsz = strlen(s) * 2);
+    }
+    strcpy(finddir_full, s);
+    finddir_best=0;
+    finddir_last=NULL;
+    finddir_scan((HashNode)&homenode, 0);
+    scanhashtable(nameddirtab, 0, 0, 0, finddir_scan, 0);
+    return finddir_last;
+}
+
+/* add a named directory */
+
+/**/
+void
+adduserdir(char *s, char *t, int flags, int always)
+{
+    Nameddir nd;
+
+    /* We don't maintain a hash table in non-interactive shells. */
+    if (!interact)
+	return;
+
+    /* The ND_USERNAME flag means that this possible hash table *
+     * entry is derived from a passwd entry.  Such entries are  *
+     * subordinate to explicitly generated entries.             */
+    if ((flags & ND_USERNAME) && nameddirtab->getnode2(nameddirtab, s))
+	return;
+
+    /* Normal parameter assignments generate calls to this function, *
+     * with always==0.  Unless the AUTO_NAME_DIRS option is set, we  *
+     * don't let such assignments actually create directory names.   *
+     * Instead, a reference to the parameter as a directory name can *
+     * cause the actual creation of the hash table entry.            */
+    if (!always && unset(AUTONAMEDIRS) &&
+	    !nameddirtab->getnode2(nameddirtab, s))
+	return;
+
+    if (!t || *t != '/' || strlen(t) >= PATH_MAX) {
+	/* We can't use this value as a directory, so simply remove *
+	 * the corresponding entry in the hash table, if any.       */
+	HashNode hn = nameddirtab->removenode(nameddirtab, s);
+
+	if(hn)
+	    nameddirtab->freenode(hn);
+	return;
+    }
+
+    /* add the name */
+    nd = (Nameddir) zcalloc(sizeof *nd);
+    nd->flags = flags;
+    nd->dir = ztrdup(t);
+    nameddirtab->addnode(nameddirtab, ztrdup(s), nd);
+}
+
+/* Get a named directory: this function can cause a directory name *
+ * to be added to the hash table, if it isn't there already.       */
+
+/**/
+char *
+getnameddir(char *name)
+{
+    Param pm;
+    char *str;
+    Nameddir nd;
+
+    /* Check if it is already in the named directory table */
+    if ((nd = (Nameddir) nameddirtab->getnode(nameddirtab, name)))
+	return dupstring(nd->dir);
+
+    /* Check if there is a scalar parameter with this name whose value *
+     * begins with a `/'.  If there is, add it to the hash table and   *
+     * return the new value.                                           */
+    if ((pm = (Param) paramtab->getnode(paramtab, name)) &&
+	    (PM_TYPE(pm->flags) == PM_SCALAR) &&
+	    (str = getsparam(name)) && *str == '/') {
+	adduserdir(name, str, 0, 1);
+	return str;
+    }
+
+#ifdef HAVE_GETPWNAM
+    {
+	/* Retrieve an entry from the password table/database for this user. */
+	struct passwd *pw;
+	if ((pw = getpwnam(name))) {
+	    char *dir = xsymlink(pw->pw_dir);
+	    adduserdir(name, dir, ND_USERNAME, 1);
+	    str = dupstring(dir);
+	    zsfree(dir);
+	    return str;
+	}
+    }
+#endif /* HAVE_GETPWNAM */
+
+    /* There are no more possible sources of directory names, so give up. */
+    return NULL;
+}
+
+/**/
+static int
+dircmp(char *s, char *t)
+{
+    if (s) {
+	for (; *s == *t; s++, t++)
+	    if (!*s)
+		return 0;
+	if (!*s && *t == '/')
+	    return 0;
+    }
+    return 1;
+}
+
+/* extra functions to call before displaying the prompt */
+
+/**/
+LinkList prepromptfns;
+
+/* the last time we checked mail */
+ 
+/**/
+time_t lastmailcheck;
+ 
+/* the last time we checked the people in the WATCH variable */
+ 
+/**/
+time_t lastwatch;
+
+/* do pre-prompt stuff */
+
+/**/
+void
+preprompt(void)
+{
+    static time_t lastperiodic;
+    LinkNode ln;
+    List list;
+    int period = getiparam("PERIOD");
+    int mailcheck = getiparam("MAILCHECK");
+
+    /* If NOTIFY is not set, then check for completed *
+     * jobs before we print the prompt.               */
+    if (unset(NOTIFY))
+	scanjobs();
+    if (errflag)
+	return;
+
+    /* If a shell function named "precmd" exists, *
+     * then execute it.                           */
+    if ((list = getshfunc("precmd")) != &dummy_list)
+	doshfunc(list, NULL, 0, 1);
+    if (errflag)
+	return;
+
+    /* If 1) the parameter PERIOD exists, 2) the shell function     *
+     * "periodic" exists, 3) it's been greater than PERIOD since we *
+     * executed "periodic", then execute it now.                    */
+    if (period && (time(NULL) > lastperiodic + period) &&
+	(list = getshfunc("periodic")) != &dummy_list) {
+	doshfunc(list, NULL, 0, 1);
+	lastperiodic = time(NULL);
+    }
+    if (errflag)
+	return;
+
+    /* If WATCH is set, then check for the *
+     * specified login/logout events.      */
+    if (watch) {
+	if ((int) difftime(time(NULL), lastwatch) > getiparam("LOGCHECK")) {
+	    dowatch();
+	    lastwatch = time(NULL);
+	}
+    }
+    if (errflag)
+	return;
+
+    /* Check mail */
+    if (mailcheck && (int) difftime(time(NULL), lastmailcheck) > mailcheck) {
+	char *mailfile;
+
+	if (mailpath && *mailpath && **mailpath)
+	    checkmailpath(mailpath);
+	else if ((mailfile = getsparam("MAIL")) && *mailfile) {
+	    char *x[2];
+
+	    x[0] = mailfile;
+	    x[1] = NULL;
+	    checkmailpath(x);
+	}
+	lastmailcheck = time(NULL);
+    }
+
+    /* Some people have claimed that C performs type    *
+     * checking, but they were later found to be lying. */
+    for(ln = firstnode(prepromptfns); ln; ln = nextnode(ln))
+	(**(void (**) _((void)))getdata(ln))();
+}
+
+/**/
+static void
+checkmailpath(char **s)
+{
+    struct stat st;
+    char *v, *u, c;
+
+    while (*s) {
+	for (v = *s; *v && *v != '?'; v++);
+	c = *v;
+	*v = '\0';
+	if (c != '?')
+	    u = NULL;
+	else
+	    u = v + 1;
+	if (**s == 0) {
+	    *v = c;
+	    zerr("empty MAILPATH component: %s", *s, 0);
+	} else if (stat(unmeta(*s), &st) == -1) {
+	    if (errno != ENOENT)
+		zerr("%e: %s", *s, errno);
+	} else if (S_ISDIR(st.st_mode)) {
+	    LinkList l;
+	    DIR *lock = opendir(unmeta(*s));
+	    char buf[PATH_MAX * 2], **arr, **ap;
+	    int ct = 1;
+
+	    if (lock) {
+		char *fn;
+		HEAPALLOC {
+		    pushheap();
+		    l = newlinklist();
+		    while ((fn = zreaddir(lock, 1)) && !errflag) {
+			if (u)
+			    sprintf(buf, "%s/%s?%s", *s, fn, u);
+			else
+			    sprintf(buf, "%s/%s", *s, fn);
+			addlinknode(l, dupstring(buf));
+			ct++;
+		    }
+		    closedir(lock);
+		    ap = arr = (char **) alloc(ct * sizeof(char *));
+
+		    while ((*ap++ = (char *)ugetnode(l)));
+		    checkmailpath(arr);
+		    popheap();
+		} LASTALLOC;
+	    }
+	} else {
+	    if (st.st_size && st.st_atime <= st.st_mtime &&
+		st.st_mtime > lastmailcheck)
+		if (!u) {
+		    fprintf(shout, "You have new mail.\n");
+		    fflush(shout);
+		} else {
+		    char *usav = underscore;
+
+		    underscore = *s;
+		    HEAPALLOC {
+			u = dupstring(u);
+			if (! parsestr(u)) {
+			    singsub(&u);
+			    zputs(u, shout);
+			    fputc('\n', shout);
+			    fflush(shout);
+			}
+			underscore = usav;
+		    } LASTALLOC;
+		}
+	    if (isset(MAILWARNING) && st.st_atime > st.st_mtime &&
+		st.st_atime > lastmailcheck && st.st_size) {
+		fprintf(shout, "The mail in %s has been read.\n", unmeta(*s));
+		fflush(shout);
+	    }
+	}
+	*v = c;
+	s++;
+    }
+}
+
+/**/
+void
+freestr(void *a)
+{
+    zsfree(a);
+}
+
+/**/
+void
+gettyinfo(struct ttyinfo *ti)
+{
+    if (SHTTY != -1) {
+#ifdef HAVE_TERMIOS_H
+# ifdef HAVE_TCGETATTR
+	if (tcgetattr(SHTTY, &ti->tio) == -1)
+# else
+	if (ioctl(SHTTY, TCGETS, &ti->tio) == -1)
+# endif
+	    zerr("bad tcgets: %e", NULL, errno);
+#else
+# ifdef HAVE_TERMIO_H
+	ioctl(SHTTY, TCGETA, &ti->tio);
+# else
+	ioctl(SHTTY, TIOCGETP, &ti->sgttyb);
+	ioctl(SHTTY, TIOCLGET, &ti->lmodes);
+	ioctl(SHTTY, TIOCGETC, &ti->tchars);
+	ioctl(SHTTY, TIOCGLTC, &ti->ltchars);
+# endif
+#endif
+    }
+}
+
+/**/
+void
+settyinfo(struct ttyinfo *ti)
+{
+    if (SHTTY != -1) {
+#ifdef HAVE_TERMIOS_H
+# ifdef HAVE_TCGETATTR
+#  ifndef TCSADRAIN
+#   define TCSADRAIN 1	/* XXX Princeton's include files are screwed up */
+#  endif
+	tcsetattr(SHTTY, TCSADRAIN, &ti->tio);
+    /* if (tcsetattr(SHTTY, TCSADRAIN, &ti->tio) == -1) */
+# else
+	ioctl(SHTTY, TCSETS, &ti->tio);
+    /* if (ioctl(SHTTY, TCSETS, &ti->tio) == -1) */
+# endif
+	/*	zerr("settyinfo: %e",NULL,errno)*/ ;
+#else
+# ifdef HAVE_TERMIO_H
+	ioctl(SHTTY, TCSETA, &ti->tio);
+# else
+	ioctl(SHTTY, TIOCSETN, &ti->sgttyb);
+	ioctl(SHTTY, TIOCLSET, &ti->lmodes);
+	ioctl(SHTTY, TIOCSETC, &ti->tchars);
+	ioctl(SHTTY, TIOCSLTC, &ti->ltchars);
+# endif
+#endif
+    }
+}
+
+/* the default tty state */
+ 
+/**/
+struct ttyinfo shttyinfo;
+
+/* != 0 if we need to call resetvideo() */
+
+/**/
+int resetneeded;
+
+#ifdef TIOCGWINSZ
+/* window size changed */
+
+/**/
+int winchanged;
+#endif
+ 
+/* check the size of the window and adjust if necessary */
+
+/**/
+void
+adjustwinsize(void)
+{
+#ifdef TIOCGWINSZ
+    int oldcols = columns, oldrows = lines;
+
+    if (SHTTY == -1)
+	return;
+
+    ioctl(SHTTY, TIOCGWINSZ, (char *)&shttyinfo.winsize);
+    setiparam("COLUMNS", shttyinfo.winsize.ws_col);
+    setiparam("LINES", shttyinfo.winsize.ws_row);
+    if (zleactive && (oldcols != columns || oldrows != lines)) {
+	resetneeded = winchanged = 1;
+	refresh();
+    }
+#endif   /* TIOCGWINSZ */
+}
+
+/* Move a fd to a place >= 10 and mark the new fd in fdtable.  If the fd *
+ * is already >= 10, it is not moved.  If it is invalid, -1 is returned. */
+
+/**/
+int
+movefd(int fd)
+{
+    if(fd != -1 && fd < 10) {
+#ifdef F_DUPFD
+	int fe = fcntl(fd, F_DUPFD, 10);
+#else
+	int fe = movefd(dup(fd));
+#endif
+	zclose(fd);
+	fd = fe;
+    }
+    if(fd != -1) {
+	if (fd > max_zsh_fd) {
+	    while (fd >= fdtable_size)
+		fdtable = zrealloc(fdtable, (fdtable_size *= 2));
+	    max_zsh_fd = fd;
+	}
+	fdtable[fd] = 1;
+    }
+    return fd;
+}
+
+/* Move fd x to y.  If x == -1, fd y is closed. */
+
+/**/
+void
+redup(int x, int y)
+{
+    if(x < 0)
+	zclose(y);
+    else if (x != y) {
+	while (y >= fdtable_size)
+	    fdtable = zrealloc(fdtable, (fdtable_size *= 2));
+	dup2(x, y);
+	if ((fdtable[y] = fdtable[x]) && y > max_zsh_fd)
+	    max_zsh_fd = y;
+	zclose(x);
+    }
+}
+
+/* Close the given fd, and clear it from fdtable. */
+
+/**/
+int
+zclose(int fd)
+{
+    if (fd >= 0) {
+	fdtable[fd] = 0;
+	while (max_zsh_fd > 0 && !fdtable[max_zsh_fd])
+	    max_zsh_fd--;
+	if (fd == coprocin)
+	    coprocin = -1;
+	if (fd == coprocout)
+	    coprocout = -1;
+    }
+    return close(fd);
+}
+
+/* Get a file name relative to $TMPPREFIX which *
+ * is unique, for use as a temporary file.      */
+ 
+/**/
+char *
+gettempname(void)
+{
+    char *s;
+ 
+    if (!(s = getsparam("TMPPREFIX")))
+	s = DEFAULT_TMPPREFIX;
+ 
+    return ((char *) mktemp(dyncat(unmeta(s), "XXXXXX")));
+}
+
+/* Check if a string contains a token */
+
+/**/
+int
+has_token(const char *s)
+{
+    while(*s)
+	if(itok(*s++))
+	    return 1;
+    return 0;
+}
+
+/* Delete a character in a string */
+ 
+/**/
+void
+chuck(char *str)
+{
+    while ((str[0] = str[1]))
+	str++;
+}
+
+/**/
+int
+tulower(int c)
+{
+    c &= 0xff;
+    return (isupper(c) ? tolower(c) : c);
+}
+
+/**/
+int
+tuupper(int c)
+{
+    c &= 0xff;
+    return (islower(c) ? toupper(c) : c);
+}
+
+/* copy len chars from t into s, and null terminate */
+
+/**/
+void
+ztrncpy(char *s, char *t, int len)
+{
+    while (len--)
+	*s++ = *t++;
+    *s = '\0';
+}
+
+/* copy t into *s and update s */
+
+/**/
+void
+strucpy(char **s, char *t)
+{
+    char *u = *s;
+
+    while ((*u++ = *t++));
+    *s = u - 1;
+}
+
+/**/
+void
+struncpy(char **s, char *t, int n)
+{
+    char *u = *s;
+
+    while (n--)
+	*u++ = *t++;
+    *s = u;
+    *u = '\0';
+}
+
+/* Return the number of elements in an array of pointers. *
+ * It doesn't count the NULL pointer at the end.          */
+
+/**/
+int
+arrlen(char **s)
+{
+    int count;
+
+    for (count = 0; *s; s++, count++);
+    return count;
+}
+
+/* Skip over a balanced pair of parenthesis. */
+
+/**/
+int
+skipparens(char inpar, char outpar, char **s)
+{
+    int level;
+
+    if (**s != inpar)
+	return -1;
+
+    for (level = 1; *++*s && level;)
+	if (**s == inpar)
+	   ++level;
+	else if (**s == outpar)
+	   --level;
+
+   return level;
+}
+
+/* Convert string to long.  This function (without the z) *
+ * is contained in the ANSI standard C library, but a lot *
+ * of them seem to be broken.                             */
+
+/**/
+long
+zstrtol(const char *s, char **t, int base)
+{
+    long ret = 0;
+    int neg;
+
+    while (inblank(*s))
+	s++;
+
+    if ((neg = (*s == '-')))
+	s++;
+    else if (*s == '+')
+	s++;
+
+    if (!base)
+	if (*s != '0')
+	    base = 10;
+	else if (*++s == 'x' || *s == 'X')
+	    base = 16, s++;
+	else
+	    base = 8;
+ 
+    if (base <= 10)
+	for (; *s >= '0' && *s < ('0' + base); s++)
+	    ret = ret * base + *s - '0';
+    else
+	for (; idigit(*s) || (*s >= 'a' && *s < ('a' + base - 10))
+	     || (*s >= 'A' && *s < ('A' + base - 10)); s++)
+	    ret = ret * base + (idigit(*s) ? (*s - '0') : (*s & 0x1f) + 9);
+    if (t)
+	*t = (char *)s;
+    return neg ? -ret : ret;
+}
+
+/**/
+int
+setblock_stdin(void)
+{
+#ifdef O_NDELAY
+# ifdef O_NONBLOCK
+#  define NONBLOCK (O_NDELAY|O_NONBLOCK)
+# else /* !O_NONBLOCK */
+#  define NONBLOCK O_NDELAY
+# endif /* !O_NONBLOCK */
+#else /* !O_NDELAY */
+# ifdef O_NONBLOCK
+#  define NONBLOCK O_NONBLOCK
+# else /* !O_NONBLOCK */
+#  define NONBLOCK 0
+# endif /* !O_NONBLOCK */
+#endif /* !O_NDELAY */
+
+#if NONBLOCK
+    struct stat st;
+    long mode;
+
+    if (!fstat(0, &st) && !S_ISREG(st.st_mode)) {
+	mode = fcntl(0, F_GETFL);
+	if (mode != -1 && (mode & NONBLOCK) &&
+	    !fcntl(0, F_SETFL, mode & ~NONBLOCK))
+	    return 1;
+    }
+#endif /* NONBLOCK */
+    return 0;
+
+#undef NONBLOCK
+}
+
+/**/
+int
+checkrmall(char *s)
+{
+    fprintf(shout, "zsh: sure you want to delete all the files in ");
+    if (*s != '/') {
+	nicezputs(pwd[1] ? unmeta(pwd) : "", shout);
+	fputc('/', shout);
+    }
+    nicezputs(s, shout);
+    if(isset(RMSTARWAIT)) {
+	fputs("? (waiting ten seconds)", shout);
+	fflush(shout);
+	beep();
+	sleep(10);
+	fputc('\n', shout);
+    }
+    fputs(" [yn]? ", shout);
+    fflush(shout);
+    beep();
+    return (getquery("ny", 1) == 'y');
+}
+
+/**/
+int
+getquery(char *valid_chars, int purge)
+{
+    char c, d;
+    int isem = !strcmp(term, "emacs");
+
+#ifdef FIONREAD
+    int val = 0;
+#endif
+
+    attachtty(mypgrp);
+    if (!isem)
+	setcbreak();
+
+#ifdef FIONREAD
+    ioctl(SHTTY, FIONREAD, (char *)&val);
+    if(purge) {
+	while(val--)
+	    read(SHTTY, &c, 1);
+    } else if (val) {
+	if (!isem)
+	    settyinfo(&shttyinfo);
+	write(SHTTY, "n\n", 2);
+	return 'n';
+    }
+#endif
+    while (read(SHTTY, &c, 1) == 1) {
+	if (c == 'Y' || c == '\t')
+	    c = 'y';
+	else if (c == 'N')
+	    c = 'n';
+	if (!valid_chars)
+	    break;
+	if (c == '\n') {
+	    c = *valid_chars;
+	    break;
+	}
+	if (strchr(valid_chars, c)) {
+	    write(SHTTY, "\n", 1);
+	    break;
+	}
+	beep();
+	if (icntrl(c))
+	    write(SHTTY, "\b \b", 3);
+	write(SHTTY, "\b \b", 3);
+    }
+    if (isem) {
+	if (c != '\n')
+	    while (read(SHTTY, &d, 1) == 1 && d != '\n');
+    } else {
+	settyinfo(&shttyinfo);
+	if (c != '\n' && !valid_chars)
+	    write(SHTTY, "\n", 1);
+    }
+    return (int)c;
+}
+
+static int d;
+static char *guess, *best;
+
+/**/
+static void
+spscan(HashNode hn, int scanflags)
+{
+    int nd;
+
+    nd = spdist(hn->nam, guess, (int) strlen(guess) / 4 + 1);
+    if (nd <= d) {
+	best = hn->nam;
+	d = nd;
+    }
+}
+
+/* spellcheck a word */
+/* fix s ; if hist is nonzero, fix the history list too */
+
+/**/
+void
+spckword(char **s, int hist, int cmd, int ask)
+{
+    char *t, *u;
+    int x;
+    char ic = '\0';
+    int ne;
+    int preflen = 0;
+
+    if ((histdone & HISTFLAG_NOEXEC) || **s == '-' || **s == '%')
+	return;
+    if (!strcmp(*s, "in"))
+	return;
+    if (!(*s)[0] || !(*s)[1])
+	return;
+    if (shfunctab->getnode(shfunctab, *s) ||
+	builtintab->getnode(builtintab, *s) ||
+	cmdnamtab->getnode(cmdnamtab, *s) ||
+	aliastab->getnode(aliastab, *s)  ||
+	reswdtab->getnode(reswdtab, *s))
+	return;
+    else if (isset(HASHLISTALL)) {
+	cmdnamtab->filltable(cmdnamtab);
+	if (cmdnamtab->getnode(cmdnamtab, *s))
+	    return;
+    }
+    t = *s;
+    if (*t == Tilde || *t == Equals || *t == String)
+	t++;
+    for (; *t; t++)
+	if (itok(*t))
+	    return;
+    best = NULL;
+    for (t = *s; *t; t++)
+	if (*t == '/')
+	    break;
+    if (**s == Tilde && !*t)
+	return;
+    if (**s == String && !*t) {
+	guess = *s + 1;
+	if (*t || !ialpha(*guess))
+	    return;
+	ic = String;
+	d = 100;
+	scanhashtable(paramtab, 1, 0, 0, spscan, 0);
+    } else if (**s == Equals) {
+	if (*t)
+	    return;
+	if (hashcmd(guess = *s + 1, pathchecked))
+	    return;
+	d = 100;
+	ic = Equals;
+	scanhashtable(aliastab, 1, 0, 0, spscan, 0);
+	scanhashtable(cmdnamtab, 1, 0, 0, spscan, 0);
+    } else {
+	guess = *s;
+	if (*guess == Tilde || *guess == String) {
+	    ic = *guess;
+	    if (!*++t)
+		return;
+	    guess = dupstring(guess);
+	    ne = noerrs;
+	    noerrs = 1;
+	    singsub(&guess);
+	    noerrs = ne;
+	    if (!guess)
+		return;
+	    preflen = strlen(guess) - strlen(t);
+	}
+	if (access(unmeta(guess), F_OK) == 0)
+	    return;
+	if ((u = spname(guess)) != guess)
+	    best = u;
+	if (!*t && cmd) {
+	    if (hashcmd(guess, pathchecked))
+		return;
+	    d = 100;
+	    scanhashtable(reswdtab, 1, 0, 0, spscan, 0);
+	    scanhashtable(aliastab, 1, 0, 0, spscan, 0);
+	    scanhashtable(shfunctab, 1, 0, 0, spscan, 0);
+	    scanhashtable(builtintab, 1, 0, 0, spscan, 0);
+	    scanhashtable(cmdnamtab, 1, 0, 0, spscan, 0);
+	}
+    }
+    if (errflag)
+	return;
+    if (best && (int)strlen(best) > 1 && strcmp(best, guess)) {
+	if (ic) {
+	    if (preflen) {
+		/* do not correct the result of an expansion */
+		if (strncmp(guess, best, preflen))
+		    return;
+		/* replace the temporarily expanded prefix with the original */
+		u = (char *) ncalloc(t - *s + strlen(best + preflen) + 1);
+		strncpy(u, *s, t - *s);
+		strcpy(u + (t - *s), best + preflen);
+	    } else {
+		u = (char *) ncalloc(strlen(best) + 2);
+		strcpy(u + 1, best);
+	    }
+	    best = u;
+	    guess = *s;
+	    *guess = *best = ztokens[ic - Pound];
+	}
+	if (ask) {
+	    char *pptbuf;
+	    pptbuf = promptexpand(sprompt, 0, best, guess);
+	    zputs(pptbuf, shout);
+	    free(pptbuf);
+	    fflush(shout);
+	    beep();
+	    x = getquery("nyae ", 0);
+	} else
+	    x = 'y';
+	if (x == 'y' || x == ' ') {
+	    *s = dupstring(best);
+	    if (hist)
+		hwrep(best);
+	} else if (x == 'a') {
+	    histdone |= HISTFLAG_NOEXEC;
+	} else if (x == 'e') {
+	    histdone |= HISTFLAG_NOEXEC | HISTFLAG_RECALL;
+	}
+	if (ic)
+	    **s = ic;
+    }
+}
+
+/**/
+int
+ztrftime(char *buf, int bufsize, char *fmt, struct tm *tm)
+{
+    int hr12;
+#ifndef HAVE_STRFTIME
+    static char *astr[] =
+    {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+    static char *estr[] =
+    {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
+     "Aug", "Sep", "Oct", "Nov", "Dec"};
+#else
+    char *origbuf = buf;
+#endif
+    char tmp[3];
+
+
+    tmp[0] = '%';
+    tmp[2] = '\0';
+    while (*fmt)
+	if (*fmt == '%') {
+	    fmt++;
+	    switch (*fmt++) {
+	    case 'd':
+		*buf++ = '0' + tm->tm_mday / 10;
+		*buf++ = '0' + tm->tm_mday % 10;
+		break;
+	    case 'e':
+	    case 'f':
+		if (tm->tm_mday > 9)
+		    *buf++ = '0' + tm->tm_mday / 10;
+		else if (fmt[-1] == 'e')
+		    *buf++ = ' ';
+		*buf++ = '0' + tm->tm_mday % 10;
+		break;
+	    case 'k':
+	    case 'K':
+		if (tm->tm_hour > 9)
+		    *buf++ = '0' + tm->tm_hour / 10;
+		else if (fmt[-1] == 'k')
+		    *buf++ = ' ';
+		*buf++ = '0' + tm->tm_hour % 10;
+		break;
+	    case 'l':
+	    case 'L':
+		hr12 = tm->tm_hour % 12;
+		if (hr12 == 0)
+		    hr12 = 12;
+	        if (hr12 > 9)
+		  *buf++ = '1';
+		else if (fmt[-1] == 'l')
+		  *buf++ = ' ';
+		*buf++ = '0' + (hr12 % 10);
+		break;
+	    case 'm':
+		*buf++ = '0' + (tm->tm_mon + 1) / 10;
+		*buf++ = '0' + (tm->tm_mon + 1) % 10;
+		break;
+	    case 'M':
+		*buf++ = '0' + tm->tm_min / 10;
+		*buf++ = '0' + tm->tm_min % 10;
+		break;
+	    case 'S':
+		*buf++ = '0' + tm->tm_sec / 10;
+		*buf++ = '0' + tm->tm_sec % 10;
+		break;
+	    case 'y':
+		*buf++ = '0' + (tm->tm_year / 10) % 10;
+		*buf++ = '0' + tm->tm_year % 10;
+		break;
+#ifndef HAVE_STRFTIME
+	    case 'a':
+		strucpy(&buf, astr[tm->tm_wday]);
+		break;
+	    case 'b':
+		strucpy(&buf, estr[tm->tm_mon]);
+		break;
+	    case 'p':
+		*buf++ = (tm->tm_hour > 11) ? 'p' : 'a';
+		*buf++ = 'm';
+		break;
+	    default:
+		*buf++ = '%';
+		if (fmt[-1] != '%')
+		    *buf++ = fmt[-1];
+#else
+	    default:
+		*buf = '\0';
+		tmp[1] = fmt[-1];
+		strftime(buf, bufsize - strlen(origbuf), tmp, tm);
+		buf += strlen(buf);
+#endif
+		break;
+	    }
+	} else
+	    *buf++ = *fmt++;
+    *buf = '\0';
+    return 0;
+}
+
+/**/
+char *
+zjoin(char **arr, int delim)
+{
+    int len = 0;
+    char **s, *ret, *ptr;
+
+    for (s = arr; *s; s++)
+	len += strlen(*s) + 1;
+    if (!len)
+	return "";
+    ptr = ret = (char *) ncalloc(len);
+    for (s = arr; *s; s++) {
+	strucpy(&ptr, *s);
+	if (delim)
+	    *ptr++ = delim;
+    }
+    ptr[-1] = '\0';
+    return ret;
+}
+
+/* Split a string containing a colon separated list *
+ * of items into an array of strings.               */
+
+/**/
+char **
+colonsplit(char *s, int uniq)
+{
+    int ct;
+    char *t, **ret, **ptr, **p;
+
+    for (t = s, ct = 0; *t; t++) /* count number of colons */
+	if (*t == ':')
+	    ct++;
+    ptr = ret = (char **) zalloc(sizeof(char **) * (ct + 2));
+
+    t = s;
+    do {
+	s = t;
+        /* move t to point at next colon */
+	for (; *t && *t != ':'; t++);
+	if (uniq)
+	    for (p = ret; p < ptr; p++)
+		if (strlen(*p) == t - s && ! strncmp(*p, s, t - s))
+		    goto cont;
+	*ptr = (char *) zalloc((t - s) + 1);
+	ztrncpy(*ptr++, s, t - s);
+      cont: ;
+    }
+    while (*t++);
+    *ptr = NULL;
+    return ret;
+}
+
+/**/
+static int
+skipwsep(char **s)
+{
+    char *t = *s;
+    int i = 0;
+
+    while (*t && iwsep(*t == Meta ? t[1] ^ 32 : *t)) {
+	if (*t == Meta)
+	    t++;
+	t++;
+	i++;
+    }
+    *s = t;
+    return i;
+}
+
+/**/
+char **
+spacesplit(char *s, int allownull)
+{
+    char *t, **ret, **ptr;
+
+    ptr = ret = (char **) ncalloc(sizeof(*ret) * (wordcount(s, NULL, -!allownull) + 1));
+
+    t = s;
+    skipwsep(&s);
+    if (*s && isep(*s == Meta ? s[1] ^ 32 : *s))
+	*ptr++ = dupstring(allownull ? "" : nulstring);
+    else if (!allownull && t != s)
+	*ptr++ = dupstring("");
+    while (*s) {
+	if (isep(*s == Meta ? s[1] ^ 32 : *s)) {
+	    if (*s == Meta)
+		s++;
+	    s++;
+	    skipwsep(&s);
+	}
+	t = s;
+	findsep(&s, NULL);
+	if (s > t || allownull) {
+	    *ptr = (char *) ncalloc((s - t) + 1);
+	    ztrncpy(*ptr++, t, s - t);
+	} else
+	    *ptr++ = dupstring(nulstring);
+	t = s;
+	skipwsep(&s);
+    }
+    if (!allownull && t != s)
+	*ptr++ = dupstring("");
+    *ptr = NULL;
+    return ret;
+}
+
+/**/
+static int
+findsep(char **s, char *sep)
+{
+    int i;
+    char *t, *tt;
+
+    if (!sep) {
+	for (t = *s; *t; t++) {
+	    if (*t == Meta) {
+		if (isep(t[1] ^ 32))
+		    break;
+		t++;
+	    } else if (isep(*t))
+		break;
+	}
+	i = t - *s;
+	*s = t;
+	return i;
+    }
+    if (!sep[0]) {
+	return **s ? ++*s, 1 : -1;
+    }
+    for (i = 0; **s; i++) {
+	for (t = sep, tt = *s; *t && *tt && *t == *tt; t++, tt++);
+	if (!*t)
+	    return i;
+	if (*(*s)++ == Meta) {
+	    (*s)++;
+#ifdef DEBUG
+	    if (! **s)
+		fprintf(stderr, "BUG: unexpected end of string in findsep()\n");
+#endif
+	}
+    }
+    return -1;
+}
+
+/**/
+char *
+findword(char **s, char *sep)
+{
+    char *r, *t;
+    int sl;
+
+    if (!**s)
+	return NULL;
+
+    if (sep) {
+	sl = strlen(sep);
+	r = *s;
+	while (! findsep(s, sep)) {
+	    r = *s += sl;
+	}
+	return r;
+    }
+    for (t = *s; *t; t++) {
+	if (*t == Meta) {
+	    if (! isep(t[1] ^ 32))
+		break;
+	    t++;
+	} else if (! isep(*t))
+	    break;
+    }
+    *s = t;
+    findsep(s, sep);
+    return t;
+}
+
+/**/
+int
+wordcount(char *s, char *sep, int mul)
+{
+    int r, sl, c;
+
+    if (sep) {
+	r = 1;
+	sl = strlen(sep);
+	for (; (c = findsep(&s, sep)) >= 0; s += sl)
+	    if ((c && *(s + sl)) || mul)
+		r++;
+    } else {
+	char *t = s;
+
+	r = 0;
+	if (mul <= 0)
+	    skipwsep(&s);
+	if ((*s && isep(*s == Meta ? s[1] ^ 32 : *s)) ||
+	    (mul < 0 && t != s))
+	    r++;
+	for (; *s; r++) {
+	    if (isep(*s == Meta ? s[1] ^ 32 : *s)) {
+		if (*s == Meta)
+		    s++;
+		s++;
+		if (mul <= 0)
+		    skipwsep(&s);
+	    }
+	    findsep(&s, NULL);
+	    t = s;
+	    if (mul <= 0)
+		skipwsep(&s);
+	}
+	if (mul < 0 && t != s)
+	    r++;
+    }
+    return r;
+}
+
+/**/
+char *
+sepjoin(char **s, char *sep)
+{
+    char *r, *p, **t;
+    int l, sl, elide = 0;
+    char sepbuf[3];
+
+    if (!*s)
+	return "";
+    if (!sep) {
+	elide = 1;
+	sep = sepbuf;
+	sepbuf[0] = *ifs;
+	sepbuf[1] = *ifs == Meta ? ifs[1] ^ 32 : '\0';
+	sepbuf[2] = '\0';
+    }
+    sl = strlen(sep);
+    for (t = s, l = 1 - sl; *t; l += strlen(*t) + sl, t++);
+    r = p = (char *) ncalloc(l);
+    t = s;
+    while (*t) {
+	strucpy(&p, *t);
+	if (*++t)
+	    strucpy(&p, sep);
+    }
+    *p = '\0';
+    return r;
+}
+
+/**/
+char **
+sepsplit(char *s, char *sep, int allownull)
+{
+    int n, sl;
+    char *t, *tt, **r, **p;
+
+    if (!sep)
+	return spacesplit(s, allownull);
+
+    sl = strlen(sep);
+    n = wordcount(s, sep, 1);
+    r = p = (char **) ncalloc((n + 1) * sizeof(char *));
+
+    for (t = s; n--;) {
+	tt = t;
+	findsep(&t, sep);
+	*p = (char *) ncalloc(t - tt + 1);
+	strncpy(*p, tt, t - tt);
+	(*p)[t - tt] = '\0';
+	p++;
+	t += sl;
+    }
+    *p = NULL;
+
+    return r;
+}
+
+/* Get the definition of a shell function */
+
+/**/
+List
+getshfunc(char *nam)
+{
+    Shfunc shf;
+
+    if (!(shf = (Shfunc) shfunctab->getnode(shfunctab, nam)))
+	return &dummy_list;
+
+    return shf->funcdef;
+}
+
+/* allocate a tree element */
+
+static int sizetab[N_COUNT] = {
+    sizeof(struct list),
+    sizeof(struct sublist),
+    sizeof(struct pline),
+    sizeof(struct cmd),
+    sizeof(struct redir),
+    sizeof(struct cond),
+    sizeof(struct forcmd),
+    sizeof(struct casecmd),
+    sizeof(struct ifcmd),
+    sizeof(struct whilecmd),
+    sizeof(struct varasg),
+    sizeof(struct autofn),
+};
+
+static int offstab[N_COUNT] = {
+    offsetof(struct list, left),
+    offsetof(struct sublist, left),
+    offsetof(struct pline, left),
+    offsetof(struct cmd, u),
+    offsetof(struct redir, name),
+    offsetof(struct cond, left),
+    offsetof(struct forcmd, name),
+    offsetof(struct casecmd, pats),
+    offsetof(struct ifcmd, ifls),
+    offsetof(struct whilecmd, cont),
+    offsetof(struct varasg, name),
+    sizeof(struct autofn),
+};
+
+static int flagtab[N_COUNT] = {
+    NT_SET(N_LIST, NT_NODE, NT_NODE, 0, 0),
+    NT_SET(N_SUBLIST, NT_NODE, NT_NODE, 0, 0),
+    NT_SET(N_PLINE, NT_NODE, NT_NODE, 0, 0),
+    NT_SET(N_CMD, NT_NODE, NT_STR | NT_LIST, NT_NODE | NT_LIST, NT_NODE | NT_LIST),
+    NT_SET(N_REDIR, NT_STR, 0, 0, 0),
+    NT_SET(N_COND, NT_NODE, NT_NODE, 0, 0),
+    NT_SET(N_FOR, NT_STR, NT_STR, NT_STR, NT_NODE),
+    NT_SET(N_CASE, NT_STR | NT_ARR, NT_NODE | NT_ARR, 0, 0),
+    NT_SET(N_IF, NT_NODE | NT_ARR, NT_NODE | NT_ARR, 0, 0),
+    NT_SET(N_WHILE, NT_NODE, NT_NODE, 0, 0),
+    NT_SET(N_VARASG, NT_STR, NT_STR, NT_STR | NT_LIST, 0),
+    NT_SET(N_AUTOFN, 0, 0, 0, 0),
+};
+
+/**/
+void *
+allocnode(int type)
+{
+    struct node *n;
+
+    n = (struct node *) alloc(sizetab[type]);
+    memset((void *) n, 0, sizetab[type]);
+    n->ntype = flagtab[type];
+    if (useheap)
+	n->ntype |= NT_HEAP;
+
+    return (void *) n;
+}
+
+/**/
+void *
+dupstruct(void *a)
+{
+    struct node *n, *r;
+
+    n = (struct node *) a;
+    if (!a || ((List) a) == &dummy_list)
+	return (void *) a;
+
+    if ((n->ntype & NT_HEAP) && !useheap) {
+	HEAPALLOC {
+	    n = (struct node *) dupstruct2((void *) n);
+	} LASTALLOC;
+	n = simplifystruct(n);
+    }
+    r = (struct node *)dupstruct2((void *) n);
+
+    if (!(n->ntype & NT_HEAP) && useheap)
+	r = expandstruct(r, N_LIST);
+
+    return (void *) r;
+}
+
+/**/
+static struct node *
+simplifystruct(struct node *n)
+{
+    if (!n || ((List) n) == &dummy_list)
+	return n;
+
+    switch (NT_TYPE(n->ntype)) {
+    case N_LIST:
+	{
+	    List l = (List) n;
+
+	    l->left = (Sublist) simplifystruct((struct node *)l->left);
+	    if ((l->type & Z_SYNC) && !l->right)
+		return (struct node *)l->left;
+	}
+	break;
+    case N_SUBLIST:
+	{
+	    Sublist sl = (Sublist) n;
+
+	    sl->left = (Pline) simplifystruct((struct node *)sl->left);
+	    if (sl->type == END && !sl->flags && !sl->right)
+		return (struct node *)sl->left;
+	}
+	break;
+    case N_PLINE:
+	{
+	    Pline pl = (Pline) n;
+
+	    pl->left = (Cmd) simplifystruct((struct node *)pl->left);
+	    if (pl->type == END && !pl->right)
+		return (struct node *)pl->left;
+	}
+	break;
+    case N_CMD:
+	{
+	    Cmd c = (Cmd) n;
+	    int i = 0;
+
+	    if (empty(c->args))
+		c->args = NULL, i++;
+	    if (empty(c->redir))
+		c->redir = NULL, i++;
+	    if (empty(c->vars))
+		c->vars = NULL, i++;
+
+	    c->u.list = (List) simplifystruct((struct node *)c->u.list);
+	    if (i == 3 && !c->flags &&
+		(c->type == CWHILE || c->type == CIF ||
+		 c->type == COND))
+		return (struct node *)c->u.list;
+	}
+	break;
+    case N_FOR:
+	{
+	    Forcmd f = (Forcmd) n;
+
+	    f->list = (List) simplifystruct((struct node *)f->list);
+	}
+	break;
+    case N_CASE:
+	{
+	    struct casecmd *c = (struct casecmd *)n;
+	    List *l;
+
+	    for (l = c->lists; *l; l++)
+		*l = (List) simplifystruct((struct node *)*l);
+	}
+	break;
+    case N_IF:
+	{
+	    struct ifcmd *i = (struct ifcmd *)n;
+	    List *l;
+
+	    for (l = i->ifls; *l; l++)
+		*l = (List) simplifystruct((struct node *)*l);
+	    for (l = i->thenls; *l; l++)
+		*l = (List) simplifystruct((struct node *)*l);
+	}
+	break;
+    case N_WHILE:
+	{
+	    struct whilecmd *w = (struct whilecmd *)n;
+
+	    w->cont = (List) simplifystruct((struct node *)w->cont);
+	    w->loop = (List) simplifystruct((struct node *)w->loop);
+	}
+    }
+
+    return n;
+}
+
+/**/
+struct node *
+expandstruct(struct node *n, int exp)
+{
+    struct node *m;
+
+    if (!n || ((List) n) == &dummy_list)
+	return n;
+
+    if (exp != N_COUNT && exp != NT_TYPE(n->ntype)) {
+	switch (exp) {
+	case N_LIST:
+	    {
+		List l;
+
+		m = (struct node *) allocnode(N_LIST);
+		l = (List) m;
+		l->type = Z_SYNC;
+		l->left = (Sublist) expandstruct(n, N_SUBLIST);
+
+		return (struct node *)l;
+	    }
+	case N_SUBLIST:
+	    {
+		Sublist sl;
+
+		m = (struct node *) allocnode(N_SUBLIST);
+		sl = (Sublist) m;
+		sl->type = END;
+		sl->left = (Pline) expandstruct(n, N_PLINE);
+
+		return (struct node *)sl;
+	    }
+	case N_PLINE:
+	    {
+		Pline pl;
+
+		m = (struct node *) allocnode(N_PLINE);
+		pl = (Pline) m;
+		pl->type = END;
+		pl->left = (Cmd) expandstruct(n, N_CMD);
+
+		return (struct node *)pl;
+	    }
+	case N_CMD:
+	    {
+		Cmd c;
+
+		m = (struct node *) allocnode(N_CMD);
+		c = (Cmd) m;
+		switch (NT_TYPE(n->ntype)) {
+		case N_WHILE:
+		    c->type = CWHILE;
+		    break;
+		case N_IF:
+		    c->type = CIF;
+		    break;
+		case N_COND:
+		    c->type = COND;
+		}
+		c->u.list = (List) expandstruct(n, NT_TYPE(n->ntype));
+		c->args = newlinklist();
+		c->vars = newlinklist();
+		c->redir = newlinklist();
+
+		return (struct node *)c;
+	    }
+	}
+    } else
+	switch (NT_TYPE(n->ntype)) {
+	case N_LIST:
+	    {
+		List l = (List) n;
+
+		l->left = (Sublist) expandstruct((struct node *)l->left,
+						 N_SUBLIST);
+		l->right = (List) expandstruct((struct node *)l->right,
+					       N_LIST);
+	    }
+	    break;
+	case N_SUBLIST:
+	    {
+		Sublist sl = (Sublist) n;
+
+		sl->left = (Pline) expandstruct((struct node *)sl->left,
+						N_PLINE);
+		sl->right = (Sublist) expandstruct((struct node *)sl->right,
+						   N_SUBLIST);
+	    }
+	    break;
+	case N_PLINE:
+	    {
+		Pline pl = (Pline) n;
+
+		pl->left = (Cmd) expandstruct((struct node *)pl->left,
+					      N_CMD);
+		pl->right = (Pline) expandstruct((struct node *)pl->right,
+						 N_PLINE);
+	    }
+	    break;
+	case N_CMD:
+	    {
+		Cmd c = (Cmd) n;
+
+		if (!c->args)
+		    c->args = newlinklist();
+		if (!c->vars)
+		    c->vars = newlinklist();
+		if (!c->redir)
+		    c->redir = newlinklist();
+
+		switch (c->type) {
+		case CFOR:
+		case CSELECT:
+		    c->u.list = (List) expandstruct((struct node *)c->u.list,
+						    N_FOR);
+		    break;
+		case CWHILE:
+		    c->u.list = (List) expandstruct((struct node *)c->u.list,
+						    N_WHILE);
+		    break;
+		case CIF:
+		    c->u.list = (List) expandstruct((struct node *)c->u.list,
+						    N_IF);
+		    break;
+		case CCASE:
+		    c->u.list = (List) expandstruct((struct node *)c->u.list,
+						    N_CASE);
+		    break;
+		case COND:
+		    c->u.list = (List) expandstruct((struct node *)c->u.list,
+						    N_COND);
+		    break;
+		case ZCTIME:
+		    c->u.list = (List) expandstruct((struct node *)c->u.list,
+						    N_SUBLIST);
+		    break;
+		case AUTOFN:
+		    c->u.list = (List) expandstruct((struct node *)c->u.list,
+						    N_AUTOFN);
+		    break;
+		default:
+		    c->u.list = (List) expandstruct((struct node *)c->u.list,
+						    N_LIST);
+		}
+	    }
+	    break;
+	case N_FOR:
+	    {
+		Forcmd f = (Forcmd) n;
+
+		f->list = (List) expandstruct((struct node *)f->list,
+					      N_LIST);
+	    }
+	    break;
+	case N_CASE:
+	    {
+		struct casecmd *c = (struct casecmd *)n;
+		List *l;
+
+		for (l = c->lists; *l; l++)
+		    *l = (List) expandstruct((struct node *)*l, N_LIST);
+	    }
+	    break;
+	case N_IF:
+	    {
+		struct ifcmd *i = (struct ifcmd *)n;
+		List *l;
+
+		for (l = i->ifls; *l; l++)
+		    *l = (List) expandstruct((struct node *)*l, N_LIST);
+		for (l = i->thenls; *l; l++)
+		    *l = (List) expandstruct((struct node *)*l, N_LIST);
+	    }
+	    break;
+	case N_WHILE:
+	    {
+		struct whilecmd *w = (struct whilecmd *)n;
+
+		w->cont = (List) expandstruct((struct node *)w->cont,
+					      N_LIST);
+		w->loop = (List) expandstruct((struct node *)w->loop,
+					      N_LIST);
+	    }
+	}
+
+    return n;
+}
+
+/* duplicate a syntax tree */
+
+/**/
+static void *
+dupstruct2(void *a)
+{
+    void **onodes, **nnodes, *ret, *n, *on;
+    int type, heap;
+    size_t nodeoffs;
+
+    if (!a || ((List) a) == &dummy_list)
+	return a;
+    type = *(int *)a;
+    ret = alloc(sizetab[NT_TYPE(type)]);
+    memcpy(ret, a, nodeoffs = offstab[NT_TYPE(type)]);
+    *(int*)ret = (type & ~NT_HEAP) | (useheap ? NT_HEAP : 0);
+    onodes = (void **) ((char *)a + nodeoffs);
+    nnodes = (void **) ((char *)ret + nodeoffs);
+    heap = type & NT_HEAP;
+    for (type = (type & 0xffff00) >> 4; (type >>= 4); *nnodes++ = n) {
+	if (!(on = *onodes++))
+	    n = NULL;
+	else {
+	    switch (type & 0xf) {
+	    case NT_NODE:
+		n = dupstruct2(on);
+		break;
+	    case NT_STR:
+		n = dupstring(on);
+		break;
+	    case NT_LIST | NT_NODE:
+		if (heap)
+		    if (useheap)
+			n =  duplist(on, (VFunc) dupstruct2);
+		    else
+			n = list2arr(on, (VFunc) dupstruct2);
+		else if (useheap)
+		    n = arr2list(on, (VFunc) dupstruct2);
+		else
+		    n = duparray(on, (VFunc) dupstruct2);
+		break;
+	    case NT_LIST | NT_STR:
+		if (heap)
+		    if (useheap)
+			n = duplist(on, (VFunc) dupstring);
+		    else
+			n = list2arr(on, (VFunc) ztrdup);
+		else if (useheap)
+		    n = arr2list(on, (VFunc) dupstring);
+		else
+		    n = duparray(on, (VFunc) ztrdup);
+		break;
+	    case NT_NODE | NT_ARR:
+		n = duparray(on, (VFunc) dupstruct2);
+		break;
+	    case NT_STR | NT_ARR:
+		n = duparray(on, (VFunc) (useheap ? dupstring : ztrdup));
+		break;
+	    default:
+		DPUTS(1, "BUG: bad node type in dupstruct2()");
+		abort();
+	    }
+	}
+    }
+    return ret;
+}
+
+/* free a syntax tree */
+
+/**/
+void
+freestruct(void *a)
+{
+    void **nodes, *n;
+    int type, size;
+
+    if (!a || ((List) a) == &dummy_list)
+	return;
+
+    type = * (int *) a;
+    nodes = (void **) ((char *)a + offstab[NT_TYPE(type)]);
+    size = sizetab[NT_TYPE(type)];
+    for (type = (type & 0xffff00) >> 4; (type >>= 4);) {
+	if ((n = *nodes++)) {
+	    switch (type & 0xf) {
+	    case NT_NODE:
+		freestruct(n);
+		break;
+	    case NT_STR:
+		zsfree((char *) n);
+		break;
+	    case NT_LIST | NT_NODE:
+	    case NT_NODE | NT_ARR:
+	    {
+		void **p = (void **) n;
+
+		while (*p)
+		    freestruct(*p++);
+		zfree(n, sizeof(void *) * (p + 1 - (void **) n));
+		break;
+	    }
+	    case NT_LIST | NT_STR:
+	    case NT_STR | NT_ARR:
+		freearray((char **) n);
+		break;
+	    default:
+		DPUTS(1, "BUG: bad node type in freenode()");
+		abort();
+	    }
+	}
+    }
+    DPUTS(size != ((char *) nodes) - ((char *) a),
+	"BUG: size wrong in freenode()");
+    zfree(a, size);
+}
+
+/**/
+static LinkList
+duplist(LinkList l, VFunc func)
+{
+    LinkList ret;
+    LinkNode node;
+
+    ret = newlinklist();
+    for (node = firstnode(l); node; incnode(node))
+	addlinknode(ret, func(getdata(node)));
+    return ret;
+}
+
+/**/
+static char **
+duparray(char **arr, VFunc func)
+{
+    char **ret, **rr;
+
+    ret = (char **) alloc((arrlen(arr) + 1) * sizeof(char *));
+    for (rr = ret; *arr;)
+	*rr++ = (char *)func(*arr++);
+    *rr = NULL;
+
+    return ret;
+}
+
+/**/
+static char **
+list2arr(LinkList l, VFunc func)
+{
+    char **arr, **r;
+    LinkNode n;
+
+    arr = r = (char **) alloc((countlinknodes(l) + 1) * sizeof(char *));
+
+    for (n = firstnode(l); n; incnode(n))
+	*r++ = (char *)func(getdata(n));
+    *r = NULL;
+
+    return arr;
+}
+
+/**/
+static LinkList
+arr2list(char **arr, VFunc func)
+{
+    LinkList l = newlinklist();
+
+    while (*arr)
+	addlinknode(l, func(*arr++));
+
+    return l;
+}
+
+/**/
+char **
+mkarray(char *s)
+{
+    char **t = (char **) zalloc((s) ? (2 * sizeof s) : (sizeof s));
+
+    if ((*t = s))
+	t[1] = NULL;
+    return t;
+}
+
+/**/
+void
+beep(void)
+{
+    if (isset(BEEP))
+	write(SHTTY, "\07", 1);
+}
+
+/**/
+void
+freearray(char **s)
+{
+    char **t = s;
+
+    while (*s)
+	zsfree(*s++);
+    free(t);
+}
+
+/**/
+int
+equalsplit(char *s, char **t)
+{
+    for (; *s && *s != '='; s++);
+    if (*s == '=') {
+	*s++ = '\0';
+	*t = s;
+	return 1;
+    }
+    return 0;
+}
+
+/* see if the right side of a list is trivial */
+
+/**/
+void
+simplifyright(List l)
+{
+    Cmd c;
+
+    if (l == &dummy_list || !l->right)
+	return;
+    if (l->right->right || l->right->left->right ||
+	l->right->left->flags || l->right->left->left->right ||
+	l->left->flags)
+	return;
+    c = l->left->left->left;
+    if (c->type != SIMPLE || nonempty(c->args) || nonempty(c->redir)
+	|| nonempty(c->vars))
+	return;
+    l->right = NULL;
+    return;
+}
+
+/* the ztypes table */
+
+/**/
+short int typtab[256];
+
+/* initialize the ztypes table */
+
+/**/
+void
+inittyptab(void)
+{
+    int t0;
+    char *s;
+
+    for (t0 = 0; t0 != 256; t0++)
+	typtab[t0] = 0;
+    for (t0 = 0; t0 != 32; t0++)
+	typtab[t0] = typtab[t0 + 128] = ICNTRL;
+    typtab[127] = ICNTRL;
+    for (t0 = '0'; t0 <= '9'; t0++)
+	typtab[t0] = IDIGIT | IALNUM | IWORD | IIDENT | IUSER;
+    for (t0 = 'a'; t0 <= 'z'; t0++)
+	typtab[t0] = typtab[t0 - 'a' + 'A'] = IALPHA | IALNUM | IIDENT | IUSER | IWORD;
+    for (t0 = 0240; t0 != 0400; t0++)
+	typtab[t0] = IALPHA | IALNUM | IIDENT | IUSER | IWORD;
+    typtab['_'] = IIDENT | IUSER;
+    typtab['-'] = IUSER;
+    typtab[' '] |= IBLANK | INBLANK;
+    typtab['\t'] |= IBLANK | INBLANK;
+    typtab['\n'] |= INBLANK;
+    typtab['\0'] |= IMETA;
+    typtab[STOUC(Meta)  ] |= IMETA;
+    typtab[STOUC(Marker)] |= IMETA;
+    for (t0 = (int)STOUC(Pound); t0 <= (int)STOUC(Nularg); t0++)
+	typtab[t0] |= ITOK | IMETA;
+    for (s = ifs ? ifs : DEFAULT_IFS; *s; s++) {
+	if (inblank(*s))
+	    if (s[1] == *s)
+		s++;
+	    else
+		typtab[STOUC(*s)] |= IWSEP;
+	typtab[STOUC(*s == Meta ? *++s ^ 32 : *s)] |= ISEP;
+    }
+    for (s = wordchars ? wordchars : DEFAULT_WORDCHARS; *s; s++)
+	typtab[STOUC(*s == Meta ? *++s ^ 32 : *s)] |= IWORD;
+    for (s = SPECCHARS; *s; s++)
+	typtab[STOUC(*s)] |= ISPECIAL;
+    if (isset(BANGHIST) && bangchar && interact && isset(SHINSTDIN))
+	typtab[bangchar] |= ISPECIAL;
+}
+
+/**/
+char **
+arrdup(char **s)
+{
+    char **x, **y;
+
+    y = x = (char **) ncalloc(sizeof(char *) * (arrlen(s) + 1));
+
+    while ((*x++ = dupstring(*s++)));
+    return y;
+}
+
+/**/
+static char *
+spname(char *oldname)
+{
+    char *p, spnameguess[PATH_MAX + 1], spnamebest[PATH_MAX + 1];
+    static char newname[PATH_MAX + 1];
+    char *new = newname, *old;
+    int bestdist = 200, thisdist;
+
+    old = oldname;
+    for (;;) {
+	while (*old == '/')
+	    *new++ = *old++;
+	*new = '\0';
+	if (*old == '\0')
+	    return newname;
+	p = spnameguess;
+	for (; *old != '/' && *old != '\0'; old++)
+	    if (p < spnameguess + PATH_MAX)
+		*p++ = *old;
+	*p = '\0';
+	if ((thisdist = mindist(newname, spnameguess, spnamebest)) >= 3) {
+	    if (bestdist < 3) {
+		strcpy(new, spnameguess);
+		strcat(new, old);
+		return newname;
+	    } else
+	    	return NULL;
+	} else
+	    bestdist = thisdist;
+	for (p = spnamebest; (*new = *p++);)
+	    new++;
+    }
+}
+
+/**/
+static int
+mindist(char *dir, char *mindistguess, char *mindistbest)
+{
+    int mindistd, nd;
+    DIR *dd;
+    char *fn;
+    char buf[PATH_MAX];
+
+    if (dir[0] == '\0')
+	dir = ".";
+    mindistd = 100;
+    sprintf(buf, "%s/%s", dir, mindistguess);
+    if (access(unmeta(buf), F_OK) == 0) {
+	strcpy(mindistbest, mindistguess);
+	return 0;
+    }
+    if (!(dd = opendir(unmeta(dir))))
+	return mindistd;
+    while ((fn = zreaddir(dd, 0))) {
+	nd = spdist(fn, mindistguess,
+		    (int)strlen(mindistguess) / 4 + 1);
+	if (nd <= mindistd) {
+	    strcpy(mindistbest, fn);
+	    mindistd = nd;
+	    if (mindistd == 0)
+		break;
+	}
+    }
+    closedir(dd);
+    return mindistd;
+}
+
+/**/
+static int
+spdist(char *s, char *t, int thresh)
+{
+    char *p, *q;
+    char *keymap =
+    "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\
+\t1234567890-=\t\
+\tqwertyuiop[]\t\
+\tasdfghjkl;'\n\t\
+\tzxcvbnm,./\t\t\t\
+\n\n\n\n\n\n\n\n\n\n\n\n\n\n\
+\t!@#$%^&*()_+\t\
+\tQWERTYUIOP{}\t\
+\tASDFGHJKL:\"\n\t\
+\tZXCVBNM<>?\n\n\t\
+\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
+
+    if (!strcmp(s, t))
+	return 0;
+/* any number of upper/lower mistakes allowed (dist = 1) */
+    for (p = s, q = t; *p && tulower(*p) == tulower(*q); p++, q++);
+    if (!*p && !*q)
+	return 1;
+    if (!thresh)
+	return 200;
+    for (p = s, q = t; *p && *q; p++, q++)
+	if (*p == *q)
+	    continue;		/* don't consider "aa" transposed, ash */
+	else if (p[1] == q[0] && q[1] == p[0])	/* transpositions */
+	    return spdist(p + 2, q + 2, thresh - 1) + 1;
+	else if (p[1] == q[0])	/* missing letter */
+	    return spdist(p + 1, q + 0, thresh - 1) + 2;
+	else if (p[0] == q[1])	/* missing letter */
+	    return spdist(p + 0, q + 1, thresh - 1) + 2;
+	else if (*p != *q)
+	    break;
+    if ((!*p && strlen(q) == 1) || (!*q && strlen(p) == 1))
+	return 2;
+    for (p = s, q = t; *p && *q; p++, q++)
+	if (p[0] != q[0] && p[1] == q[1]) {
+	    int t0;
+	    char *z;
+
+	/* mistyped letter */
+
+	    if (!(z = strchr(keymap, p[0])) || *z == '\n' || *z == '\t')
+		return spdist(p + 1, q + 1, thresh - 1) + 1;
+	    t0 = z - keymap;
+	    if (*q == keymap[t0 - 15] || *q == keymap[t0 - 14] ||
+		*q == keymap[t0 - 13] ||
+		*q == keymap[t0 - 1] || *q == keymap[t0 + 1] ||
+		*q == keymap[t0 + 13] || *q == keymap[t0 + 14] ||
+		*q == keymap[t0 + 15])
+		return spdist(p + 1, q + 1, thresh - 1) + 2;
+	    return 200;
+	} else if (*p != *q)
+	    break;
+    return 200;
+}
+
+/* set cbreak mode, or the equivalent */
+
+/**/
+void
+setcbreak(void)
+{
+    struct ttyinfo ti;
+
+    ti = shttyinfo;
+#ifdef HAS_TIO
+    ti.tio.c_lflag &= ~ICANON;
+    ti.tio.c_cc[VMIN] = 1;
+    ti.tio.c_cc[VTIME] = 0;
+#else
+    ti.sgttyb.sg_flags |= CBREAK;
+#endif
+    settyinfo(&ti);
+}
+
+/* give the tty to some process */
+
+/**/
+void
+attachtty(pid_t pgrp)
+{
+    static int ep = 0;
+
+    if (jobbing) {
+#ifdef HAVE_TCSETPGRP
+	if (SHTTY != -1 && tcsetpgrp(SHTTY, pgrp) == -1 && !ep)
+#else
+# if ardent
+	if (SHTTY != -1 && setpgrp() == -1 && !ep)
+# else
+	int arg = pgrp;
+
+	if (SHTTY != -1 && ioctl(SHTTY, TIOCSPGRP, &arg) == -1 && !ep)
+# endif
+#endif
+	{
+	    if (pgrp != mypgrp && kill(pgrp, 0) == -1)
+		attachtty(mypgrp);
+	    else {
+		if (errno != ENOTTY)
+		{
+		    zerr("can't set tty pgrp: %e", NULL, errno);
+		    fflush(stderr);
+		}
+		opts[MONITOR] = 0;
+		ep = 1;
+		errflag = 0;
+	    }
+	}
+    }
+}
+
+/* get the process group associated with the tty */
+
+/**/
+pid_t
+gettygrp(void)
+{
+    pid_t arg;
+
+    if (SHTTY == -1)
+	return -1;
+
+#ifdef HAVE_TCSETPGRP
+    arg = tcgetpgrp(SHTTY);
+#else
+    ioctl(SHTTY, TIOCGPGRP, &arg);
+#endif
+
+    return arg;
+}
+
+/* Return the output baudrate */
+
+#ifdef HAVE_SELECT
+/**/
+long
+getbaudrate(struct ttyinfo *shttyinfo)
+{
+    long speedcode;
+
+#ifdef HAS_TIO
+# if defined(HAVE_TCGETATTR) && defined(HAVE_TERMIOS_H)
+    speedcode = cfgetospeed(&shttyinfo->tio);
+# else
+    speedcode = shttyinfo->tio.c_cflag & CBAUD;
+# endif
+#else
+    speedcode = shttyinfo->sgttyb.sg_ospeed;
+#endif
+
+    switch (speedcode) {
+    case B0:
+	return (0L);
+    case B50:
+	return (50L);
+    case B75:
+	return (75L);
+    case B110:
+	return (110L);
+    case B134:
+	return (134L);
+    case B150:
+	return (150L);
+    case B200:
+	return (200L);
+    case B300:
+	return (300L);
+    case B600:
+	return (600L);
+#ifdef _B900
+    case _B900:
+	return (900L);
+#endif
+    case B1200:
+	return (1200L);
+    case B1800:
+	return (1800L);
+    case B2400:
+	return (2400L);
+#ifdef _B3600
+    case _B3600:
+	return (3600L);
+#endif
+    case B4800:
+	return (4800L);
+#ifdef _B7200
+    case _B7200:
+	return (7200L);
+#endif
+    case B9600:
+	return (9600L);
+#ifdef B19200
+    case B19200:
+	return (19200L);
+#else
+# ifdef EXTA
+    case EXTA:
+	return (19200L);
+# endif
+#endif
+#ifdef B38400
+    case B38400:
+	return (38400L);
+#else
+# ifdef EXTB
+    case EXTB:
+	return (38400L);
+# endif
+#endif
+#ifdef B57600
+    case B57600:
+	return (57600L);
+#endif
+#ifdef B115200
+    case B115200:
+	return (115200L);
+#endif
+#ifdef B230400
+    case B230400:
+	return (230400L);
+#endif
+#ifdef B460800
+    case B460800:
+	return (460800L);
+#endif
+    default:
+	if (speedcode >= 100)
+	    return speedcode;
+	break;
+    }
+    return (0L);
+}
+#endif
+
+/* Escape tokens and null characters.  Buf is the string which should be     *
+ * escaped.  len is the length of the string.  If len is -1, buf should be   *
+ * null terminated.  If len is non-negative and the third paramerer is not   *
+ * META_DUP, buf should point to an at least len+1 long memory area.  The    *
+ * return value points to the quoted string.  If the given string does not   *
+ * contain any special character which should be quoted and the third        *
+ * parameter is not META_(HEAP|)DUP, buf is returned unchanged (a            *
+ * terminating null character is appended to buf if necessary).  Otherwise   *
+ * the third `heap' argument determines the method used to allocate space    *
+ * for the result.  It can have the following values:                        *
+ *   META_REALLOC:  use zrealloc on buf                                      *
+ *   META_HREALLOC: use hrealloc on buf                                      *
+ *   META_USEHEAP:  get memory from the heap.  This leaves buf unchanged.    *
+ *   META_NOALLOC:  buf points to a memory area which is long enough to hold *
+ *                  the quoted form, just quote it and return buf.           *
+ *   META_STATIC:   store the quoted string in a static area.  The original  *
+ *                  sting should be at most PATH_MAX long.                   *
+ *   META_ALLOC:    allocate memory for the new string with zalloc().        *
+ *   META_DUP:      leave buf unchanged and allocate space for the return    *
+ *                  value even if buf does not contains special characters   *
+ *   META_HEAPDUP:  same as META_DUP, but uses the heap                      */
+
+/**/
+char *
+metafy(char *buf, int len, int heap)
+{
+    int meta = 0;
+    char *t, *p, *e;
+    static char mbuf[PATH_MAX*2+1];
+
+    if (len == -1) {
+	for (e = buf, len = 0; *e; len++)
+	    if (imeta(*e++))
+		meta++;
+    } else
+	for (e = buf; e < buf + len;)
+	    if (imeta(*e++))
+		meta++;
+
+    if (meta || heap == META_DUP || heap == META_HEAPDUP) {
+	switch (heap) {
+	case META_REALLOC:
+	    buf = zrealloc(buf, len + meta + 1);
+	    break;
+	case META_HREALLOC:
+	    buf = hrealloc(buf, len, len + meta + 1);
+	    break;
+	case META_ALLOC:
+	case META_DUP:
+	    buf = memcpy(zalloc(len + meta + 1), buf, len);
+	    break;
+	case META_USEHEAP:
+	case META_HEAPDUP:
+	    buf = memcpy(halloc(len + meta + 1), buf, len);
+	    break;
+	case META_STATIC:
+#ifdef DEBUG
+	    if (len > PATH_MAX) {
+		fprintf(stderr, "BUG: len = %d > PATH_MAX in metafy\n", len);
+		fflush(stderr);
+	    }
+#endif
+	    buf = memcpy(mbuf, buf, len);
+	    break;
+#ifdef DEBUG
+	case META_NOALLOC:
+	    break;
+	default:
+	    fprintf(stderr, "BUG: metafy called with invaild heap value\n");
+	    fflush(stderr);
+	    break;
+#endif
+	}
+	p = buf + len;
+	e = t = buf + len + meta;
+	while (meta) {
+	    if (imeta(*--t = *--p)) {
+		*t-- ^= 32;
+		*t = Meta;
+		meta--;
+	    }
+	}
+    }
+    *e = '\0';
+    return buf;
+}
+
+/**/
+char *
+unmetafy(char *s, int *len)
+{
+    char *p, *t;
+
+    for (p = s; *p && *p != Meta; p++);
+    for (t = p; (*t = *p++);)
+	if (*t++ == Meta)
+	    t[-1] = *p++ ^ 32;
+    if (len)
+	*len = t - s;
+    return s;
+}
+
+/* Return the character length of a metafied substring, given the      *
+ * unmetafied substring length.                                        */
+
+/**/
+int
+metalen(const char *s, int len)
+{
+    int mlen = len;
+
+    while (len--) {
+	if (*s++ == Meta) {
+	    mlen++;
+	    s++;
+	}
+    }
+    return mlen;
+}
+
+/* This function converts a zsh internal string to a form which can be *
+ * passed to a system call as a filename.  The result is stored in a   *
+ * single static area.  NULL returned if the result is longer than     *
+ * 4 * PATH_MAX.                                                       */
+
+/**/
+char *
+unmeta(const char *file_name)
+{
+    static char fn[4 * PATH_MAX];
+    char *p;
+    const char *t;
+
+    for (t = file_name, p = fn; *t && p < fn + 4 * PATH_MAX - 1; p++)
+	if ((*p = *t++) == Meta)
+	    *p = *t++ ^ 32;
+    if (*t)
+	return NULL;
+    if (p - fn == t - file_name)
+	return (char *) file_name;
+    *p = '\0';
+    return fn;
+}
+
+/* Unmetafy and compare two strings, with unsigned characters. *
+ * "a\0" sorts after "a".                                      */
+
+/**/
+int
+ztrcmp(unsigned char const *s1, unsigned char const *s2)
+{
+    int c1, c2;
+
+    while(*s1 && *s1 == *s2) {
+	s1++;
+	s2++;
+    }
+
+    if(!(c1 = *s1))
+	c1 = -1;
+    else if(c1 == STOUC(Meta))
+	c1 = *++s1 ^ 32;
+    if(!(c2 = *s2))
+	c2 = -1;
+    else if(c2 == STOUC(Meta))
+	c2 = *++s2 ^ 32;
+
+    if(c1 == c2)
+	return 0;
+    else if(c1 < c2)
+	return -1;
+    else
+	return 1;
+}
+
+/* Return zero if the metafied string s and the non-metafied,  *
+ * len-long string r are the same.  Return -1 if r is a prefix *
+ * of s.  Return 1 if r is the lowercase version of s.  Return *
+ * 2 is r is the lowercase prefix of s and return 3 otherwise. */
+
+/**/
+int
+metadiffer(char const *s, char const *r, int len)
+{
+    int l = len;
+
+    while (l-- && *s && *r++ == (*s == Meta ? *++s ^ 32 : *s))
+	s++;
+    if (*s && l < 0)
+	return -1;
+    if (l < 0)
+	return 0;
+    if (!*s)
+	return 3;
+    s -= len - l - 1;
+    r -= len - l;
+    while (len-- && *s && *r++ == tulower(*s == Meta ? *++s ^ 32 : *s))
+	s++;
+    if (*s && len < 0)
+	return 2;
+    if (len < 0)
+	return 1;
+    return 3;
+}
+
+/* Return the unmetafied length of a metafied string. */
+
+/**/
+int
+ztrlen(char const *s)
+{
+    int l;
+
+    for (l = 0; *s; l++)
+	if (*s++ == Meta) {
+#ifdef DEBUG
+	    if (! *s)
+		fprintf(stderr, "BUG: unexpected end of string in ztrlen()\n");
+	    else
+#endif
+	    s++;
+	}
+    return l;
+}
+
+/* Subtract two pointers in a metafied string. */
+
+/**/
+int
+ztrsub(char const *t, char const *s)
+{
+    int l = t - s;
+
+    while (s != t)
+	if (*s++ == Meta) {
+#ifdef DEBUG
+	    if (! *s || s == t)
+		fprintf(stderr, "BUG: substring ends in the middle of a metachar in ztrsub()\n");
+	    else
+#endif
+	    s++;
+	    l--;
+	}
+    return l;
+}
+
+/**/
+char *
+zreaddir(DIR *dir, int ignoredots)
+{
+    struct dirent *de;
+
+    do {
+	de = readdir(dir);
+	if(!de)
+	    return NULL;
+    } while(ignoredots && de->d_name[0] == '.' &&
+	(!de->d_name[1] || (de->d_name[1] == '.' && !de->d_name[2])));
+
+    return metafy(de->d_name, -1, META_STATIC);
+}
+
+/* Unmetafy and output a string.  Tokens are skipped. */
+
+/**/
+int
+zputs(char const *s, FILE *stream)
+{
+    int c;
+
+    while (*s) {
+	if (*s == Meta)
+	    c = *++s ^ 32;
+	else if(itok(*s)) {
+	    s++;
+	    continue;
+	} else
+	    c = *s;
+	s++;
+	if (fputc(c, stream) < 0)
+	    return EOF;
+    }
+    return 0;
+}
+
+/* Create a visibly-represented duplicate of a string. */
+
+/**/
+char *
+niceztrdup(char const *s)
+{
+    int c, len = strlen(s) * 5;
+    char *buf = zalloc(len);
+    char *p = buf, *n, *ret;
+
+    while ((c = *s++)) {
+	if (itok(c))
+	    if (c <= Comma)
+		c = ztokens[c - Pound];
+	    else 
+		continue;
+	if (c == Meta)
+	    c = *s++ ^ 32;
+	n = nicechar(c);
+	while(*n)
+	    *p++ = *n++;
+    }
+    ret = metafy(buf, p - buf, META_DUP);
+    zfree(buf, len);
+    return ret;
+}
+
+/* Unmetafy and output a string, displaying special characters readably. */
+
+/**/
+int
+nicezputs(char const *s, FILE *stream)
+{
+    int c;
+
+    while ((c = *s++)) {
+	if (itok(c))
+	    if (c <= Comma)
+		c = ztokens[c - Pound];
+	    else 
+		continue;
+	if (c == Meta)
+	    c = *s++ ^ 32;
+	if(fputs(nicechar(c), stream) < 0)
+	    return EOF;
+    }
+    return 0;
+}
+
+/* Return the length of the visible representation of a metafied string. */
+
+/**/
+size_t
+niceztrlen(char const *s)
+{
+    size_t l = 0;
+    int c;
+
+    while ((c = *s++)) {
+	if (itok(c))
+	    if (c <= Comma)
+		c = ztokens[c - Pound];
+	    else 
+		continue;
+	if (c == Meta)
+	    c = *s++ ^ 32;
+	l += strlen(nicechar(STOUC(c)));
+    }
+    return l;
+}
+
+/* check for special characters in the string */
+
+/**/
+int
+hasspecial(char const *s)
+{
+    for (; *s; s++)
+	if (ispecial(*s == Meta ? *++s ^ 32 : *s))
+	    return 1;
+    return 0;
+}
+
+/* Unmetafy and output a string, quoted if it contains special characters. */
+
+/**/
+int
+quotedzputs(char const *s, FILE *stream)
+{
+    int inquote = 0, c;
+
+    /* check for empty string */
+    if(!*s)
+	return fputs("''", stream);
+
+    if (!hasspecial(s))
+	return zputs(s, stream);
+
+    if (isset(RCQUOTES)) {
+	/* use rc-style quotes-within-quotes for the whole string */
+	if(fputc('\'', stream) < 0)
+	    return EOF;
+	while(*s) {
+	    if (*s == Meta)
+		c = *++s ^ 32;
+	    else
+		c = *s;
+	    s++;
+	    if (c == '\'') {
+		if(fputc('\'', stream) < 0)
+		    return EOF;
+	    } else if(c == '\n' && isset(CSHJUNKIEQUOTES)) {
+		if(fputc('\\', stream) < 0)
+		    return EOF;
+	    }
+	    if(fputc(c, stream) < 0)
+		return EOF;
+	}
+	if(fputc('\'', stream) < 0)
+	    return EOF;
+    } else {
+	/* use Bourne-style quoting, avoiding empty quoted strings */
+	while(*s) {
+	    if (*s == Meta)
+		c = *++s ^ 32;
+	    else
+		c = *s;
+	    s++;
+	    if (c == '\'') {
+		if(inquote) {
+		    if(fputc('\'', stream) < 0)
+			return EOF;
+		    inquote=0;
+		}
+		if(fputs("\\'", stream) < 0)
+		    return EOF;
+	    } else {
+		if (!inquote) {
+		    if(fputc('\'', stream) < 0)
+			return EOF;
+		    inquote=1;
+		}
+		if(c == '\n' && isset(CSHJUNKIEQUOTES)) {
+		    if(fputc('\\', stream) < 0)
+			return EOF;
+		}
+		if(fputc(c, stream) < 0)
+		    return EOF;
+	    }
+	}
+	if (inquote) {
+	    if(fputc('\'', stream) < 0)
+		return EOF;
+	}
+    }
+    return 0;
+}
+
+/* Double-quote a metafied string. */
+
+/**/
+char *
+dquotedztrdup(char const *s)
+{
+    int len = strlen(s) * 4 + 2;
+    char *buf = zalloc(len);
+    char *p = buf, *ret;
+
+    if(isset(CSHJUNKIEQUOTES)) {
+	int inquote = 0;
+
+	while(*s) {
+	    int c = *s++;
+
+	    if (c == Meta)
+		c = *s++ ^ 32;
+	    switch(c) {
+		case '"':
+		case '$':
+		case '`':
+		    if(inquote) {
+			*p++ = '"';
+			inquote = 0;
+		    }
+		    *p++ = '\\';
+		    *p++ = c;
+		    break;
+		default:
+		    if(!inquote) {
+			*p++ = '"';
+			inquote = 1;
+		    }
+		    if(c == '\n')
+			*p++ = '\\';
+		    *p++ = c;
+		    break;
+	    }
+	}
+	if (inquote)
+	    *p++ = '"';
+    } else {
+	int pending = 0;
+
+	*p++ = '"';
+	while(*s) {
+	    int c = *s++;
+
+	    if (c == Meta)
+		c = *s++ ^ 32;
+	    switch(c) {
+		case '\\':
+		    if(pending)
+			*p++ = '\\';
+		    *p++ = '\\';
+		    pending = 1;
+		    break;
+		case '"':
+		case '$':
+		case '`':
+		    if(pending)
+			*p++ = '\\';
+		    *p++ = '\\';
+		    /* fall through */
+		default:
+		    *p++ = c;
+		    pending = 0;
+		    break;
+	    }
+	}
+	if(pending)
+	    *p++ = '\\';
+	*p++ = '"';
+    }
+    ret = metafy(buf, p - buf, META_DUP);
+    zfree(buf, len);
+    return ret;
+}
+
+#if 0
+/* Unmetafy and output a string, double quoting it in its entirety. */
+
+/**/
+int
+dquotedzputs(char const *s, FILE *stream)
+{
+    char *d = dquotedztrdup(s);
+    int ret = zputs(d, stream);
+
+    zsfree(d);
+    return ret;
+}
+#endif
+
+/**/
+char *
+getkeystring(char *s, int *len, int fromwhere, int *misc)
+{
+    char *buf;
+    char *t, *u = NULL;
+    char svchar = '\0';
+    int meta = 0, control = 0;
+
+    if (fromwhere != 4)
+	buf = halloc(strlen(s) + 1);
+    else {
+	buf = s;
+	s += 2;
+    }
+    for (t = buf; *s; s++) {
+	if (*s == '\\' && s[1]) {
+	    switch (*++s) {
+	    case 'a':
+#ifdef __STDC__
+		*t++ = '\a';
+#else
+		*t++ = '\07';
+#endif
+		break;
+	    case 'n':
+		*t++ = '\n';
+		break;
+	    case 'b':
+		*t++ = '\b';
+		break;
+	    case 't':
+		*t++ = '\t';
+		break;
+	    case 'v':
+		*t++ = '\v';
+		break;
+	    case 'f':
+		*t++ = '\f';
+		break;
+	    case 'r':
+		*t++ = '\r';
+		break;
+	    case 'E':
+		if (!fromwhere) {
+		    *t++ = '\\', s--;
+		    continue;
+		}
+	    case 'e':
+		*t++ = '\033';
+		break;
+	    case 'M':
+		if (fromwhere) {
+		    if (s[1] == '-')
+			s++;
+		    meta = 1 + control;	/* preserve the order of ^ and meta */
+		} else
+		    *t++ = '\\', s--;
+		continue;
+	    case 'C':
+		if (fromwhere) {
+		    if (s[1] == '-')
+			s++;
+		    control = 1;
+		} else
+		    *t++ = '\\', s--;
+		continue;
+	    case Meta:
+		*t++ = '\\', s--;
+		break;
+	    case 'c':
+		if (fromwhere < 2) {
+		    *misc = 1;
+		    break;
+		}
+	    default:
+		if ((idigit(*s) && *s < '8') || *s == 'x') {
+		    if (!fromwhere)
+			if (*s == '0')
+			    s++;
+			else if (*s != 'x') {
+			    *t++ = '\\', s--;
+			    continue;
+			}
+		    if (s[1] && s[2] && s[3]) {
+			svchar = s[3];
+			s[3] = '\0';
+			u = s;
+		    }
+		    *t++ = zstrtol(s + (*s == 'x'), &s,
+				   (*s == 'x') ? 16 : 8);
+		    if (svchar) {
+			u[3] = svchar;
+			svchar = '\0';
+		    }
+		    s--;
+		} else {
+		    if (!fromwhere && *s != '\\')
+			*t++ = '\\';
+		    *t++ = *s;
+		}
+		break;
+	    }
+	} else if (fromwhere == 4 && *s == Snull) {
+	    for (u = t; (*u++ = *s++););
+	    return t + 1;
+	} else if (*s == '^' && fromwhere == 2) {
+	    control = 1;
+	    continue;
+	} else if (*s == Meta)
+	    *t++ = *++s ^ 32;
+	else
+	    *t++ = *s;
+	if (meta == 2) {
+	    t[-1] |= 0x80;
+	    meta = 0;
+	}
+	if (control) {
+	    if (t[-1] == '?')
+		t[-1] = 0x7f;
+	    else
+		t[-1] &= 0x9f;
+	    control = 0;
+	}
+	if (meta) {
+	    t[-1] |= 0x80;
+	    meta = 0;
+	}
+	if (fromwhere == 4 && imeta(t[-1])) {
+	    *t = t[-1] ^ 32;
+	    t[-1] = Meta;
+	    t++;
+	}
+    }
+    DPUTS(fromwhere == 4, "BUG: unterminated $' substitution");
+    *t = '\0';
+    *len = t - buf;
+    return buf;
+}
+
+/* Return non-zero if s is a prefix of t. */
+
+/**/
+int
+strpfx(char *s, char *t)
+{
+    while (*s && *s == *t)
+	s++, t++;
+    return !*s;
+}
+
+/* Return non-zero if s is a suffix of t. */
+
+/**/
+int
+strsfx(char *s, char *t)
+{
+    int ls = strlen(s), lt = strlen(t);
+
+    if (ls <= lt)
+	return !strcmp(t + lt - ls, s);
+    return 0;
+}
+
+/**/
+char *
+dupstrpfx(const char *s, int len)
+{
+    char *r = ncalloc(len + 1);
+
+    memcpy(r, s, len);
+    r[len] = '\0';
+    return r;
+}
+
+/**/
+char *
+ztrduppfx(const char *s, int len)
+{
+    char *r = zalloc(len + 1);
+
+    memcpy(r, s, len);
+    r[len] = '\0';
+    return r;
+}
+
+/* Append a string to an allocated string, reallocating to make room. */
+
+/**/
+char *
+appstr(char *base, char const *append)
+{
+    return strcat(realloc(base, strlen(base) + strlen(append) + 1), append);
+}
+
+/**/
+static int
+upchdir(int n)
+{
+    char buf[PATH_MAX];
+    char *s;
+    int err = -1;
+
+    while (n > 0) {
+	for (s = buf; s < buf + PATH_MAX - 4 && n--; )
+	    *s++ = '.', *s++ = '.', *s++ = '/';
+	s[-1] = '\0';
+	if (chdir(buf))
+	    return err;
+	err = -2;
+    }
+    return 0;
+}
+
+/* Change directory, without following symlinks.  Returns 0 on success, -1 *
+ * on failure.  Sets errno to ENOTDIR if any symlinks are encountered.  If *
+ * fchdir() fails, or the current directory is unreadable, we might end up *
+ * in an unwanted directory in case of failure.                            */
+
+/**/
+int
+lchdir(char const *path, struct dirsav *d, int hard)
+{
+    char const *pptr;
+    int level;
+    struct stat st1;
+    struct dirsav ds;
+#ifdef HAVE_LSTAT
+    char buf[PATH_MAX + 1], *ptr;
+    int err;
+    struct stat st2;
+#endif
+
+    if (!d) {
+	ds.ino = ds.dev = 0;
+	ds.dirname = NULL;
+	ds.dirfd = -1;
+	d = &ds;
+    }
+#ifdef HAVE_LSTAT
+    if ((*path == '/' || !hard) &&
+	(d != &ds || hard)){
+#else
+    if (*path == '/') {
+#endif
+	level = -1;
+#ifdef HAVE_FCHDIR
+	if (d->dirfd < 0 && (d->dirfd = open(".", O_RDONLY | O_NOCTTY)) < 0 &&
+	    zgetdir(d) && *d->dirname != '/')
+	    d->dirfd = open("..", O_RDONLY | O_NOCTTY);
+#else
+	if (!d->dirname)
+	    zgetdir(d);
+#endif
+    } else {
+	level = 0;
+	if (!d->dev && !d->ino) {
+	    stat(".", &st1);
+	    d->dev = st1.st_dev;
+	    d->ino = st1.st_ino;
+	}
+    }
+
+#ifdef HAVE_LSTAT
+    if (!hard)
+#endif
+    {
+	if (d != &ds) {
+	    for (pptr = path; *pptr; level++) {
+		while (*pptr && *pptr++ != '/');
+		while (*pptr == '/')
+		    pptr++;
+	    }
+	    d->level = level;
+	}
+	return zchdir((char *) path);
+    }
+#ifdef HAVE_LSTAT
+    if (*path == '/')
+	chdir("/");
+    for(;;) {
+	while(*path == '/')
+	    path++;
+	if(!*path) {
+	    if (d == &ds) {
+		zsfree(ds.dirname);
+		if (ds.dirfd >=0)
+		    close(ds.dirfd);
+	    } else
+		d->level = level;
+	    return 0;
+	}
+	for(pptr = path; *++pptr && *pptr != '/'; ) ;
+	if(pptr - path > PATH_MAX) {
+	    err = ENAMETOOLONG;
+	    break;
+	}
+	for(ptr = buf; path != pptr; )
+	    *ptr++ = *path++;
+	*ptr = 0;
+	if(lstat(buf, &st1)) {
+	    err = errno;
+	    break;
+	}
+	if(!S_ISDIR(st1.st_mode)) {
+	    err = ENOTDIR;
+	    break;
+	}
+	if(chdir(buf)) {
+	    err = errno;
+	    break;
+	}
+	if (level >= 0)
+	    level++;
+	if(lstat(".", &st2)) {
+	    err = errno;
+	    break;
+	}
+	if(st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) {
+	    err = ENOTDIR;
+	    break;
+	}
+    }
+    if (restoredir(d)) {
+	if (d == &ds) {
+	    zsfree(ds.dirname);
+	    if (ds.dirfd >=0)
+		close(ds.dirfd);
+	}
+	errno = err;
+	return -2;
+    }
+    if (d == &ds) {
+	zsfree(ds.dirname);
+	if (ds.dirfd >=0)
+	    close(ds.dirfd);
+    }
+    errno = err;
+    return -1;
+#endif /* HAVE_LSTAT */
+}
+
+/**/
+int
+restoredir(struct dirsav *d)
+{
+    int err = 0;
+    struct stat sbuf;
+
+    if (d->dirname && *d->dirname == '/')
+	return chdir(d->dirname);
+#ifdef HAVE_FCHDIR
+    if (d->dirfd >= 0) {
+	if (!fchdir(d->dirfd)) {
+	    if (!d->dirname) {
+		return 0;
+	    } else if (chdir(d->dirname)) {
+		close(d->dirfd);
+		d->dirfd = -1;
+		err = -2;
+	    }
+	} else {
+	    close(d->dirfd);
+	    d->dirfd = err = -1;
+	}
+    } else
+#endif
+    if (d->level > 0)
+	err = upchdir(d->level);
+    else if (d->level < 0)
+	err = -1;
+    if (d->dev || d->ino) {
+	stat(".", &sbuf);
+	if (sbuf.st_ino != d->ino || sbuf.st_dev != d->dev)
+	    err = -2;
+    }
+    return err;
+}
+
+/* Get a signal number from a string */
+
+/**/
+int
+getsignum(char *s)
+{
+    int x, i;
+
+    /* check for a signal specified by number */
+    x = atoi(s);
+    if (idigit(*s) && x >= 0 && x < VSIGCOUNT)
+	return x;
+
+    /* search for signal by name */
+    for (i = 0; i < VSIGCOUNT; i++)
+	if (!strcmp(s, sigs[i]))
+	    return i;
+
+    /* no matching signal */
+    return -1;
+}
+
+/* Check whether the shell is running with privileges in effect.  *
+ * This is the case if EITHER the euid is zero, OR (if the system *
+ * supports POSIX.1e (POSIX.6) capability sets) the process'      *
+ * Effective or Inheritable capability sets are non-empty.        */
+
+/**/
+int
+privasserted(void)
+{
+    if(!geteuid())
+	return 1;
+#ifdef HAVE_CAP_GET_PROC
+    {
+	cap_t caps = cap_get_proc();
+	if(caps) {
+	    /* POSIX doesn't define a way to test whether a capability set *
+	     * is empty or not.  Typical.  I hope this is conforming...    */
+	    cap_flag_value_t val;
+	    cap_value_t n;
+	    for(n = 0; !cap_get_flag(caps, n, CAP_EFFECTIVE, &val); n++)
+		if(val ||
+		   (!cap_get_flag(caps, n, CAP_INHERITABLE, &val) && val)) {
+		    cap_free(&caps);
+		    return 1;
+		}
+	    cap_free(&caps);
+	}
+    }
+#endif /* HAVE_CAP_GET_PROC */
+    return 0;
+}
+
+#ifdef DEBUG
+
+/**/
+void
+dputs(char *message)
+{
+    fprintf(stderr, "%s\n", message);
+    fflush(stderr);
+}
+
+#endif /* DEBUG */
+
+/**/
+int
+mode_to_octal(mode_t mode)
+{
+    int m = 0;
+
+    if(mode & S_ISUID)
+	m |= 04000;
+    if(mode & S_ISGID)
+	m |= 02000;
+    if(mode & S_ISVTX)
+	m |= 01000;
+    if(mode & S_IRUSR)
+	m |= 00400;
+    if(mode & S_IWUSR)
+	m |= 00200;
+    if(mode & S_IXUSR)
+	m |= 00100;
+    if(mode & S_IRGRP)
+	m |= 00040;
+    if(mode & S_IWGRP)
+	m |= 00020;
+    if(mode & S_IXGRP)
+	m |= 00010;
+    if(mode & S_IROTH)
+	m |= 00004;
+    if(mode & S_IWOTH)
+	m |= 00002;
+    if(mode & S_IXOTH)
+	m |= 00001;
+    return m;
+}
diff --git a/Src/watch.c b/Src/watch.c
new file mode 100644
index 000000000..2532e0a63
--- /dev/null
+++ b/Src/watch.c
@@ -0,0 +1,586 @@
+/*
+ * watch.c - login/logout watching
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+
+/* Headers for utmp/utmpx structures */
+#ifdef HAVE_UTMP_H
+# include <utmp.h>
+#endif
+#ifdef HAVE_UTMPX_H
+# include <utmpx.h>
+#endif
+
+/* Find utmp file */
+#if !defined(REAL_UTMP_FILE) && defined(UTMP_FILE)
+# define REAL_UTMP_FILE UTMP_FILE
+#endif
+#if !defined(REAL_UTMP_FILE) && defined(_PATH_UTMP)
+# define REAL_UTMP_FILE _PATH_UTMP
+#endif
+#if !defined(REAL_UTMP_FILE) && defined(PATH_UTMP_FILE)
+# define REAL_UTMP_FILE PATH_UTMP_FILE
+#endif
+
+/* Find wtmp file */
+#if !defined(REAL_WTMP_FILE) && defined(WTMP_FILE)
+# define REAL_WTMP_FILE WTMP_FILE
+#endif
+#if !defined(REAL_WTMP_FILE) && defined(_PATH_WTMP)
+# define REAL_WTMP_FILE _PATH_WTMP
+#endif
+#if !defined(REAL_WTMP_FILE) && defined(PATH_WTMP_FILE)
+# define REAL_WTMP_FILE PATH_WTMP_FILE
+#endif
+
+/* Find utmpx file */
+#if !defined(REAL_UTMPX_FILE) && defined(UTMPX_FILE)
+# define REAL_UTMPX_FILE UTMPX_FILE
+#endif
+#if !defined(REAL_UTMPX_FILE) && defined(_PATH_UTMPX)
+# define REAL_UTMPX_FILE _PATH_UTMPX
+#endif
+#if !defined(REAL_UTMPX_FILE) && defined(PATH_UTMPX_FILE)
+# define REAL_UTMPX_FILE PATH_UTMPX_FILE
+#endif
+
+/* Find wtmpx file */
+#if !defined(REAL_WTMPX_FILE) && defined(WTMPX_FILE)
+# define REAL_WTMPX_FILE WTMPX_FILE
+#endif
+#if !defined(REAL_WTMPX_FILE) && defined(_PATH_WTMPX)
+# define REAL_WTMPX_FILE _PATH_WTMPX
+#endif
+#if !defined(REAL_WTMPX_FILE) && defined(PATH_WTMPX_FILE)
+# define REAL_WTMPX_FILE PATH_WTMPX_FILE
+#endif
+
+/* Decide which structure to use.  We use a structure that exists in *
+ * the headers, and require that its corresponding utmp file exist.  *
+ * (wtmp is less important.)                                         */
+
+#if !defined(WATCH_STRUCT_UTMP) && defined(HAVE_STRUCT_UTMPX) && defined(REAL_UTMPX_FILE)
+# define WATCH_STRUCT_UTMP struct utmpx
+# ifdef HAVE_STRUCT_UTMPX_UT_XTIME
+#  undef ut_time
+#  define ut_time ut_xtime
+# else /* !HAVE_STRUCT_UTMPX_UT_XTIME */
+#  ifdef HAVE_STRUCT_UTMPX_UT_TV
+#   undef ut_time
+#   define ut_time ut_tv.tv_sec
+#  endif /* HAVE_STRUCT_UTMPX_UT_TV */
+# endif /* !HAVE_STRUCT_UTMPX_UT_XTIME */
+# define WATCH_UTMP_FILE REAL_UTMPX_FILE
+# ifdef REAL_WTMPX_FILE
+#  define WATCH_WTMP_FILE REAL_WTMPX_FILE
+# endif
+# ifdef HAVE_STRUCT_UTMPX_UT_HOST
+#  define WATCH_UTMP_UT_HOST 1
+# endif
+#endif
+
+#if !defined(WATCH_STRUCT_UTMP) && defined(HAVE_STRUCT_UTMP) && defined(REAL_UTMP_FILE)
+# define WATCH_STRUCT_UTMP struct utmp
+# define WATCH_UTMP_FILE REAL_UTMP_FILE
+# ifdef REAL_WTMP_FILE
+#  define WATCH_WTMP_FILE REAL_WTMP_FILE
+# endif
+# ifdef HAVE_STRUCT_UTMP_UT_HOST
+#  define WATCH_UTMP_UT_HOST 1
+# endif
+#endif
+
+#ifdef WATCH_UTMP_UT_HOST
+# define DEFAULT_WATCHFMT "%n has %a %l from %m."
+#else /* !WATCH_UTMP_UT_HOST */
+# define DEFAULT_WATCHFMT "%n has %a %l."
+#endif /* !WATCH_UTMP_UT_HOST */
+
+/**/
+char const * const default_watchfmt = DEFAULT_WATCHFMT;
+
+#ifdef WATCH_STRUCT_UTMP
+
+# include "watch.pro"
+
+# ifndef WATCH_WTMP_FILE
+#  define WATCH_WTMP_FILE "/dev/null"
+# endif
+
+static int wtabsz;
+static WATCH_STRUCT_UTMP *wtab;
+static time_t lastutmpcheck;
+
+/* get the time of login/logout for WATCH */
+
+/**/
+static time_t
+getlogtime(WATCH_STRUCT_UTMP *u, int inout)
+{
+    FILE *in;
+    WATCH_STRUCT_UTMP uu;
+    int first = 1;
+    int srchlimit = 50;		/* max number of wtmp records to search */
+
+    if (inout)
+	return u->ut_time;
+    if (!(in = fopen(WATCH_WTMP_FILE, "r")))
+	return time(NULL);
+    fseek(in, 0, 2);
+    do {
+	if (fseek(in, ((first) ? -1 : -2) * sizeof(WATCH_STRUCT_UTMP), 1)) {
+	    fclose(in);
+	    return time(NULL);
+	}
+	first = 0;
+	if (!fread(&uu, sizeof(WATCH_STRUCT_UTMP), 1, in)) {
+	    fclose(in);
+	    return time(NULL);
+	}
+	if (uu.ut_time < lastwatch || !srchlimit--) {
+	    fclose(in);
+	    return time(NULL);
+	}
+    }
+    while (memcmp(&uu, u, sizeof(uu)));
+
+    do
+	if (!fread(&uu, sizeof(WATCH_STRUCT_UTMP), 1, in)) {
+	    fclose(in);
+	    return time(NULL);
+	}
+    while (strncmp(uu.ut_line, u->ut_line, sizeof(u->ut_line)));
+    fclose(in);
+    return uu.ut_time;
+}
+
+/* Mutually recursive call to handle ternaries in $WATCHFMT */
+
+# define BEGIN3 '('
+# define END3 ')'
+
+/**/
+static char *
+watch3ary(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt)
+{
+    int truth = 1, sep;
+
+    switch (*fmt++) {
+    case 'n':
+	truth = (u->ut_name[0] != 0);
+	break;
+    case 'a':
+	truth = inout;
+	break;
+    case 'l':
+	if (!strncmp(u->ut_line, "tty", 3))
+	    truth = (u->ut_line[3] != 0);
+	else
+	    truth = (u->ut_line[0] != 0);
+	break;
+# ifdef WATCH_UTMP_UT_HOST
+    case 'm':
+    case 'M':
+	truth = (u->ut_host[0] != 0);
+	break;
+# endif /* WATCH_UTMP_UT_HOST */
+    default:
+	prnt = 0;		/* Skip unknown conditionals entirely */
+	break;
+    }
+    sep = *fmt++;
+    fmt = watchlog2(inout, u, fmt, (truth && prnt), sep);
+    return watchlog2(inout, u, fmt, (!truth && prnt), END3);
+}
+
+/* print a login/logout event */
+
+/**/
+static char *
+watchlog2(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt, int fini)
+{
+    char buf[40], buf2[80];
+    time_t timet;
+    struct tm *tm;
+    char *fm2;
+# ifdef WATCH_UTMP_UT_HOST
+    char *p;
+    int i;
+# endif /* WATCH_UTMP_UT_HOST */
+
+    while (*fmt)
+	if (*fmt == '\\')
+	    if (*++fmt) {
+		if (prnt)
+		    putchar(*fmt);
+		++fmt;
+	    } else if (fini)
+		return fmt;
+	    else
+		break;
+	else if (*fmt == fini)
+	    return ++fmt;
+	else if (*fmt != '%') {
+	    if (prnt)
+		putchar(*fmt);
+	    ++fmt;
+	} else {
+	    if (*++fmt == BEGIN3)
+		fmt = watch3ary(inout, u, ++fmt, prnt);
+	    else if (!prnt)
+		++fmt;
+	    else
+		switch (*(fm2 = fmt++)) {
+		case 'n':
+		    printf("%.*s", (int)sizeof(u->ut_name), u->ut_name);
+		    break;
+		case 'a':
+		    printf("%s", (!inout) ? "logged off" : "logged on");
+		    break;
+		case 'l':
+		    if (!strncmp(u->ut_line, "tty", 3))
+			printf("%.*s", (int)sizeof(u->ut_line) - 3, u->ut_line + 3);
+		    else
+			printf("%.*s", (int)sizeof(u->ut_line), u->ut_line);
+		    break;
+# ifdef WATCH_UTMP_UT_HOST
+		case 'm':
+		    for (p = u->ut_host, i = sizeof(u->ut_host); i && *p; i--, p++) {
+			if (*p == '.' && !idigit(p[1]))
+			    break;
+			putchar(*p);
+		    }
+		    break;
+		case 'M':
+		    printf("%.*s", (int)sizeof(u->ut_host), u->ut_host);
+		    break;
+# endif /* WATCH_UTMP_UT_HOST */
+		case 'T':
+		case 't':
+		case '@':
+		case 'W':
+		case 'w':
+		case 'D':
+		    switch (*fm2) {
+		    case '@':
+		    case 't':
+			fm2 = "%l:%M%p";
+			break;
+		    case 'T':
+			fm2 = "%K:%M";
+			break;
+		    case 'w':
+			fm2 = "%a %f";
+			break;
+		    case 'W':
+			fm2 = "%m/%d/%y";
+			break;
+		    case 'D':
+			if (fm2[1] == '{') {
+			    char *dd, *ss;
+			    int n = 79;
+
+			    for (ss = fm2 + 2, dd = buf2;
+				 n-- && *ss && *ss != '}'; ++ss, ++dd)
+				*dd = *((*ss == '\\' && ss[1]) ? ++ss : ss);
+			    if (*ss == '}') {
+				*dd = '\0';
+				fmt = ss + 1;
+				fm2 = buf2;
+			    }
+			    else fm2 = "%y-%m-%d";
+			}
+			else fm2 = "%y-%m-%d";
+			break;
+		    }
+		    timet = getlogtime(u, inout);
+		    tm = localtime(&timet);
+		    ztrftime(buf, 40, fm2, tm);
+		    printf("%s", (*buf == ' ') ? buf + 1 : buf);
+		    break;
+		case '%':
+		    putchar('%');
+		    break;
+		case 'S':
+		    txtset(TXTSTANDOUT);
+		    tsetcap(TCSTANDOUTBEG, -1);
+		    break;
+		case 's':
+		    txtset(TXTDIRTY);
+		    txtunset(TXTSTANDOUT);
+		    tsetcap(TCSTANDOUTEND, -1);
+		    break;
+		case 'B':
+		    txtset(TXTDIRTY);
+		    txtset(TXTBOLDFACE);
+		    tsetcap(TCBOLDFACEBEG, -1);
+		    break;
+		case 'b':
+		    txtset(TXTDIRTY);
+		    txtunset(TXTBOLDFACE);
+		    tsetcap(TCALLATTRSOFF, -1);
+		    break;
+		case 'U':
+		    txtset(TXTUNDERLINE);
+		    tsetcap(TCUNDERLINEBEG, -1);
+		    break;
+		case 'u':
+		    txtset(TXTDIRTY);
+		    txtunset(TXTUNDERLINE);
+		    tsetcap(TCUNDERLINEEND, -1);
+		    break;
+		default:
+		    putchar('%');
+		    putchar(*fm2);
+		    break;
+		}
+	}
+    if (prnt)
+	putchar('\n');
+
+    return fmt;
+}
+
+/* check the List for login/logouts */
+
+/**/
+static void
+watchlog(int inout, WATCH_STRUCT_UTMP *u, char **w, char *fmt)
+{
+    char *v, *vv, sav;
+    int bad;
+
+    if (!*u->ut_name)
+	return;
+
+    if (*w && !strcmp(*w, "all")) {
+	(void)watchlog2(inout, u, fmt, 1, 0);
+	return;
+    }
+    if (*w && !strcmp(*w, "notme") &&
+	strncmp(u->ut_name, get_username(), sizeof(u->ut_name))) {
+	(void)watchlog2(inout, u, fmt, 1, 0);
+	return;
+    }
+    for (; *w; w++) {
+	bad = 0;
+	v = *w;
+	if (*v != '@' && *v != '%') {
+	    for (vv = v; *vv && *vv != '@' && *vv != '%'; vv++);
+	    sav = *vv;
+	    *vv = '\0';
+	    if (strncmp(u->ut_name, v, sizeof(u->ut_name)))
+		bad = 1;
+	    *vv = sav;
+	    v = vv;
+	}
+	for (;;)
+	    if (*v == '%') {
+		for (vv = ++v; *vv && *vv != '@'; vv++);
+		sav = *vv;
+		*vv = '\0';
+		if (strncmp(u->ut_line, v, sizeof(u->ut_line)))
+		    bad = 1;
+		*vv = sav;
+		v = vv;
+	    }
+# ifdef WATCH_UTMP_UT_HOST
+	    else if (*v == '@') {
+		for (vv = ++v; *vv && *vv != '%'; vv++);
+		sav = *vv;
+		*vv = '\0';
+		if (strncmp(u->ut_host, v, strlen(v)))
+		    bad = 1;
+		*vv = sav;
+		v = vv;
+	    }
+# endif /* WATCH_UTMP_UT_HOST */
+	    else
+		break;
+	if (!bad) {
+	    (void)watchlog2(inout, u, fmt, 1, 0);
+	    return;
+	}
+    }
+}
+
+/* compare 2 utmp entries */
+
+/**/
+static int
+ucmp(WATCH_STRUCT_UTMP *u, WATCH_STRUCT_UTMP *v)
+{
+    if (u->ut_time == v->ut_time)
+	return strncmp(u->ut_line, v->ut_line, sizeof(u->ut_line));
+    return u->ut_time - v->ut_time;
+}
+
+/* initialize the user List */
+
+/**/
+static void
+readwtab(void)
+{
+    WATCH_STRUCT_UTMP *uptr;
+    int wtabmax = 32;
+    FILE *in;
+
+    wtabsz = 0;
+    if (!(in = fopen(WATCH_UTMP_FILE, "r")))
+	return;
+    uptr = wtab = (WATCH_STRUCT_UTMP *)zalloc(wtabmax * sizeof(WATCH_STRUCT_UTMP));
+    while (fread(uptr, sizeof(WATCH_STRUCT_UTMP), 1, in))
+# ifdef USER_PROCESS
+	if   (uptr->ut_type == USER_PROCESS)
+# else /* !USER_PROCESS */
+	if   (uptr->ut_name[0])
+# endif /* !USER_PROCESS */
+	{
+	    uptr++;
+	    if (++wtabsz == wtabmax)
+		uptr = (wtab = (WATCH_STRUCT_UTMP *)realloc((void *) wtab, (wtabmax *= 2) *
+						      sizeof(WATCH_STRUCT_UTMP))) + wtabsz;
+	}
+    fclose(in);
+
+    if (wtabsz)
+	qsort((void *) wtab, wtabsz, sizeof(WATCH_STRUCT_UTMP),
+	           (int (*) _((const void *, const void *)))ucmp);
+}
+
+/* Check for login/logout events; executed before *
+ * each prompt if WATCH is set                    */
+
+/**/
+void
+dowatch(void)
+{
+    FILE *in;
+    WATCH_STRUCT_UTMP *utab, *uptr, *wptr;
+    struct stat st;
+    char **s;
+    char *fmt;
+    int utabsz = 0, utabmax = wtabsz + 4;
+    int uct, wct;
+
+    s = watch;
+    if (!(fmt = getsparam("WATCHFMT")))
+	fmt = DEFAULT_WATCHFMT;
+
+    holdintr();
+    if (!wtab) {
+	readwtab();
+	noholdintr();
+	return;
+    }
+    if ((stat(WATCH_UTMP_FILE, &st) == -1) || (st.st_mtime <= lastutmpcheck)) {
+	noholdintr();
+	return;
+    }
+    lastutmpcheck = st.st_mtime;
+    uptr = utab = (WATCH_STRUCT_UTMP *) zalloc(utabmax * sizeof(WATCH_STRUCT_UTMP));
+
+    if (!(in = fopen(WATCH_UTMP_FILE, "r"))) {
+	free(utab);
+	noholdintr();
+	return;
+    }
+    while (fread(uptr, sizeof *uptr, 1, in))
+# ifdef USER_PROCESS
+	if (uptr->ut_type == USER_PROCESS)
+# else /* !USER_PROCESS */
+	if (uptr->ut_name[0])
+# endif /* !USER_PROCESS */
+	{
+	    uptr++;
+	    if (++utabsz == utabmax)
+		uptr = (utab = (WATCH_STRUCT_UTMP *)realloc((void *) utab, (utabmax *= 2) *
+						      sizeof(WATCH_STRUCT_UTMP))) + utabsz;
+	}
+    fclose(in);
+    noholdintr();
+    if (errflag) {
+	free(utab);
+	return;
+    }
+    if (utabsz)
+	qsort((void *) utab, utabsz, sizeof(WATCH_STRUCT_UTMP),
+	           (int (*) _((const void *, const void *)))ucmp);
+
+    wct = wtabsz;
+    uct = utabsz;
+    uptr = utab;
+    wptr = wtab;
+    if (errflag) {
+	free(utab);
+	return;
+    }
+    while ((uct || wct) && !errflag)
+	if (!uct || (wct && ucmp(uptr, wptr) > 0))
+	    wct--, watchlog(0, wptr++, s, fmt);
+	else if (!wct || (uct && ucmp(uptr, wptr) < 0))
+	    uct--, watchlog(1, uptr++, s, fmt);
+	else
+	    uptr++, wptr++, wct--, uct--;
+    free(wtab);
+    wtab = utab;
+    wtabsz = utabsz;
+    fflush(stdout);
+}
+
+/**/
+int
+bin_log(char *nam, char **argv, char *ops, int func)
+{
+    if (!watch)
+	return 1;
+    if (wtab)
+	free(wtab);
+    wtab = (WATCH_STRUCT_UTMP *)zalloc(1);
+    wtabsz = 0;
+    lastutmpcheck = 0;
+    dowatch();
+    return 0;
+}
+
+#else /* !WATCH_STRUCT_UTMP */
+
+/**/
+void dowatch(void)
+{
+}
+
+/**/
+int
+bin_log(char *nam, char **argv, char *ops, int func)
+{
+    return bin_notavail(nam, argv, ops, func);
+}
+
+#endif /* !WATCH_STRUCT_UTMP */
diff --git a/Src/xmods.conf b/Src/xmods.conf
new file mode 100644
index 000000000..c36105721
--- /dev/null
+++ b/Src/xmods.conf
@@ -0,0 +1,5 @@
+rlimits
+comp1
+zle
+compctl
+sched
diff --git a/Src/zsh.export b/Src/zsh.export
new file mode 100644
index 000000000..8f676c7fd
--- /dev/null
+++ b/Src/zsh.export
@@ -0,0 +1,235 @@
+#!
+SHTTY
+addbuiltins
+addedx
+addhashnode
+aliastab
+alloc_stackp
+appstr
+arrdup
+arrlen
+attachtty
+bangchar
+bin_notavail
+breaks
+bufstack
+builtintab
+chline
+chuck
+clearjobtab
+closem
+cmdnamtab
+columns
+compctlreadptr
+coprocin
+coprocout
+countlinknodes
+countprompt
+createparam
+ctxtlex
+curhist
+current_limits
+deletebuiltins
+deletehashtable
+domatch
+doshfunc
+dputs
+dquotedztrdup
+dummy_list
+dupstring
+dupstrpfx
+dyncat
+emptyhashtable
+endparamscope
+errflag
+excs
+execstring
+exlast
+expanding
+fallback_compctlread
+fallback_zleread
+fignore
+file_type
+filesub
+filesubstr
+findcmd
+firsthist
+freearray
+freeheap
+getaparam
+gethashnode
+gethashnode2
+getiparam
+getkeystring
+getlinknode
+getshfunc
+getsparam
+glob_pre
+glob_suf
+global_heapalloc
+global_permalloc
+globlist
+gotwordptr
+halloc
+hasam
+hashcmd
+hasher
+hasspecial
+haswilds
+hcalloc
+hgetc
+hgetline
+histentarr
+histentct
+hptr
+hrealloc
+inbufct
+incmdpos
+incond
+init_io
+init_shout
+init_term
+inpop
+inpush
+inredir
+insertlinknode
+intr
+inwhat
+isfirstln
+jobtab
+lastpid
+lastval
+lchdir
+lexrestore
+lexsave
+lexstop
+limits
+line
+lines
+locallevel
+metadiffer
+metafy
+metalen
+mode_to_octal
+mypgrp
+mypid
+nameddirtab
+ncalloc
+newhashtable
+newlinklist
+nicechar
+nicezputs
+niceztrdup
+niceztrlen
+noaliases
+noerrs
+noop_function
+noop_function_int
+optiontab
+opts
+paramtab
+parbegin
+parend
+parsereg
+parsestr
+path
+pathchecked
+popheap
+postedit
+ppid
+prefork
+prepromptfns
+printif
+printqt
+promptexpand
+pushheap
+putshout
+pwd
+quietgetevent
+quietgethist
+quotedzputs
+refreshptr
+remlpaths
+remnulargs
+removehashnode
+resetneeded
+restoredir
+reswdtab
+retflag
+scanhashtable
+setaparam
+setlimits
+setsparam
+settyinfo
+shfunctab
+shingetline
+shout
+shttyinfo
+singsub
+skipparens
+spaceinlineptr
+spacesplit
+spckword
+startparamscope
+stdunsetfn
+stophist
+stopmsg
+strinbeg
+strinend
+strpfx
+strsfx
+strucpy
+struncpy
+tclen
+tcstr
+termflags
+tgoto
+tok
+tokenize
+tokstr
+tputs
+trashzleptr
+tricat
+tsetcap
+ttystrname
+tulower
+tuupper
+txtchange
+typtab
+ugetnode
+uinsertlinknode
+unmeta
+unmetafy
+untokenize
+uremnode
+useheap
+winchanged
+wordbeg
+zalloc
+zbeep
+zcalloc
+zchdir
+zerr
+zerrnam
+zexit
+zfree
+zgetdir
+zgetenv
+zjoin
+zleactive
+zleparse
+zlereadptr
+zputs
+zreaddir
+zsetlimit
+zsfree
+zshcs
+zshll
+zstrtol
+ztokens
+ztrdup
+ztrduppfx
+ztrftime
+ztrlen
+ztrsub
+zwarnnam
diff --git a/Src/zsh.h b/Src/zsh.h
new file mode 100644
index 000000000..e96fc6e86
--- /dev/null
+++ b/Src/zsh.h
@@ -0,0 +1,1293 @@
+/*
+ * zsh.h - standard header file
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#define trashzle()      trashzleptr()
+#define zleread(X,Y,H)  zlereadptr(X,Y,H)
+#define spaceinline(X)  spaceinlineptr(X)
+#define gotword()       gotwordptr()
+#define refresh()       refreshptr()
+
+#define compctlread(N,A,O,R) compctlreadptr(N,A,O,R)
+
+/* A few typical macros */
+#define minimum(a,b)  ((a) < (b) ? (a) : (b))
+
+/* math.c */
+typedef int LV;
+
+/* Character tokens are sometimes casted to (unsigned char)'s.         * 
+ * Unfortunately, some compilers don't correctly cast signed to        * 
+ * unsigned promotions; i.e. (int)(unsigned char)((char) -1) evaluates * 
+ * to -1, instead of 255 like it should.  We circumvent the troubles   * 
+ * of such shameful delinquency by casting to a larger unsigned type   * 
+ * then back down to unsigned char.                                    */
+
+#ifdef BROKEN_SIGNED_TO_UNSIGNED_CASTING
+# define STOUC(X)	((unsigned char)(unsigned short)(X))
+#else
+# define STOUC(X)	((unsigned char)(X))
+#endif
+
+/* Meta together with the character following Meta denotes the character *
+ * which is the exclusive or of 32 and the character following Meta.     *
+ * This is used to represent characters which otherwise has special      *
+ * meaning for zsh.  These are the characters for which the imeta() test *
+ * is true: the null character, and the characters from Meta to Marker.  */
+
+#define Meta		((char) 0x83)
+
+/* Note that the fourth character in DEFAULT_IFS is Meta *
+ * followed by a space which denotes the null character. */
+
+#define DEFAULT_IFS	" \t\n\203 "
+
+/* Character tokens */
+#define Pound		((char) 0x84)
+#define String		((char) 0x85)
+#define Hat		((char) 0x86)
+#define Star		((char) 0x87)
+#define Inpar		((char) 0x88)
+#define Outpar		((char) 0x89)
+#define Qstring	        ((char) 0x8a)
+#define Equals		((char) 0x8b)
+#define Bar	      	((char) 0x8c)
+#define Inbrace	        ((char) 0x8d)
+#define Outbrace	((char) 0x8e)
+#define Inbrack	        ((char) 0x8f)
+#define Outbrack	((char) 0x90)
+#define Tick		((char) 0x91)
+#define Inang		((char) 0x92)
+#define Outang		((char) 0x93)
+#define Quest		((char) 0x94)
+#define Tilde		((char) 0x95)
+#define Qtick		((char) 0x96)
+#define Comma		((char) 0x97)
+#define Snull		((char) 0x98)
+#define Dnull		((char) 0x99)
+#define Bnull		((char) 0x9a)
+#define Nularg		((char) 0x9b)
+
+#define INULL(x)	(((x) & 0xfc) == 0x98)
+
+/* Marker used in paramsubst for rc_expand_param */
+#define Marker		((char) 0x9c)
+
+/* chars that need to be quoted if meant literally */
+
+#define SPECCHARS "#$^*()=|{}[]`<>?~;&\n\t \\\'\""
+
+enum {
+    NULLTOK,		/* 0  */
+    SEPER,
+    NEWLIN,
+    SEMI,
+    DSEMI,
+    AMPER,		/* 5  */
+    INPAR,
+    OUTPAR,
+    DBAR,
+    DAMPER,
+    OUTANG,		/* 10 */
+    OUTANGBANG,
+    DOUTANG,
+    DOUTANGBANG,
+    INANG,
+    INOUTANG,		/* 15 */
+    DINANG,
+    DINANGDASH,
+    INANGAMP,
+    OUTANGAMP,
+    AMPOUTANG,		/* 20 */
+    OUTANGAMPBANG,
+    DOUTANGAMP,
+    DOUTANGAMPBANG,
+    TRINANG,
+    BAR,		/* 25 */
+    BARAMP,
+    INOUTPAR,
+    DINPAR,
+    DOUTPAR,
+    AMPERBANG,		/* 30 */
+    SEMIAMP,
+    DOUTBRACK,
+    STRING,
+    ENVSTRING,
+    ENVARRAY,		/* 35 */
+    ENDINPUT,
+    LEXERR,
+
+    /* Tokens for reserved words */
+    BANG,	/* !         */
+    DINBRACK,	/* [[        */
+    INBRACE,    /* {         */	/* 40 */
+    OUTBRACE,   /* }         */
+    CASE,	/* case      */
+    COPROC,	/* coproc    */
+    DO,		/* do        */
+    DONE,	/* done      */ /* 45 */
+    ELIF,	/* elif      */
+    ELSE,	/* else      */
+    ZEND,	/* end       */
+    ESAC,	/* esac      */
+    FI,		/* fi        */ /* 50 */
+    FOR,	/* for       */
+    FOREACH,	/* foreach   */
+    FUNC,	/* function  */
+    IF,		/* if        */
+    NOCORRECT,	/* nocorrect */ /* 55 */
+    REPEAT,	/* repeat    */
+    SELECT,	/* select    */
+    THEN,	/* then      */
+    TIME,	/* time      */
+    UNTIL,	/* until     */ /* 60 */
+    WHILE	/* while     */
+};
+
+/* Redirection types.  If you modify this, you may also have to modify *
+ * redirtab in parse.c and getredirs() in text.c and the IS_* macros   *
+ * below.                                                              */
+
+enum {
+    WRITE,		/* > */
+    WRITENOW,		/* >| */
+    APP,		/* >> */
+    APPNOW,		/* >>| */
+    ERRWRITE,		/* &>, >& */
+    ERRWRITENOW,	/* >&| */
+    ERRAPP,		/* >>& */
+    ERRAPPNOW,		/* >>&| */
+    READWRITE,		/* <> */
+    READ,		/* < */
+    HEREDOC,		/* << */
+    HEREDOCDASH,	/* <<- */
+    HERESTR,		/* <<< */
+    MERGEIN,		/* <&n */
+    MERGEOUT,		/* >&n */
+    CLOSE,		/* >&-, <&- */
+    INPIPE,		/* < <(...) */
+    OUTPIPE		/* > >(...) */
+};
+
+#define IS_WRITE_FILE(X)      ((X)>=WRITE && (X)<=READWRITE)
+#define IS_APPEND_REDIR(X)    (IS_WRITE_FILE(X) && ((X) & 2))
+#define IS_CLOBBER_REDIR(X)   (IS_WRITE_FILE(X) && ((X) & 1))
+#define IS_ERROR_REDIR(X)     ((X)>=ERRWRITE && (X)<=ERRAPPNOW)
+#define IS_READFD(X)          (((X)>=READWRITE && (X)<=MERGEIN) || (X)==INPIPE)
+#define IS_REDIROP(X)         ((X)>=OUTANG && (X)<=TRINANG)
+
+/* Flags for input stack */
+#define INP_FREE      (1<<0)	/* current buffer can be free'd            */
+#define INP_ALIAS     (1<<1)	/* expanding alias or history              */
+#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.     */
+
+/* Flags for metafy */
+#define META_REALLOC	0
+#define META_USEHEAP	1
+#define META_STATIC	2
+#define META_DUP	3
+#define META_ALLOC	4
+#define META_NOALLOC	5
+#define META_HEAPDUP	6
+#define META_HREALLOC	7
+
+
+/**************************/
+/* Abstract types for zsh */
+/**************************/
+
+typedef struct linknode  *LinkNode;
+typedef struct linklist  *LinkList;
+typedef struct hashnode  *HashNode;
+typedef struct hashtable *HashTable;
+
+typedef struct reswd     *Reswd;
+typedef struct alias     *Alias;
+typedef struct param     *Param;
+typedef struct cmdnam    *Cmdnam;
+typedef struct shfunc    *Shfunc;
+typedef struct builtin   *Builtin;
+typedef struct nameddir  *Nameddir;
+typedef struct module    *Module;
+
+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 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 asgment  *Asgment;
+
+
+/********************************/
+/* Definitions for linked lists */
+/********************************/
+
+/* linked list abstract data type */
+
+struct linknode {
+    LinkNode next;
+    LinkNode last;
+    void *dat;
+};
+
+struct linklist {
+    LinkNode first;
+    LinkNode last;
+};
+
+/* 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))
+
+
+/********************************/
+/* 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_DISOWN (1<<3)	/* run this sublist without job control (&|) */
+
+/* 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;
+};
+
+#define COND_NOT    0
+#define COND_AND    1
+#define COND_OR     2
+#define COND_STREQ  3
+#define COND_STRNEQ 4
+#define COND_STRLT  5
+#define COND_STRGTR 6
+#define COND_NT     7
+#define COND_OT     8
+#define COND_EF     9
+#define COND_EQ    10
+#define COND_NE    11
+#define COND_LT    12
+#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 */
+};
+
+
+/*  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;
+};
+
+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 */
+
+struct autofn {
+    int ntype;			/* node type                             */
+    Shfunc shf;			/* the shell function to define	         */
+};
+
+/* The number of fds space is allocated for  *
+ * each time a multio must increase in size. */
+#define MULTIOUNIT 8
+
+/* A multio is a list of fds associated with a certain fd.       *
+ * Thus if you do "foo >bar >ble", the multio for fd 1 will have *
+ * two fds, the result of open("bar",...), and the result of     *
+ * open("ble",....).                                             */
+
+/* structure used for multiple i/o redirection */
+/* one for each fd open                        */
+
+struct multio {
+    int ct;			/* # of redirections on this fd                 */
+    int rflag;			/* 0 if open for reading, 1 if open for writing */
+    int pipe;			/* fd of pipe if ct > 1                         */
+    int fds[MULTIOUNIT];	/* list of src/dests redirected to/from this fd */
+};
+
+/* variable assignment tree element */
+
+struct varasg {
+    int ntype;			/* node type                             */
+    int type;			/* nonzero means array                   */
+    char *name;
+    char *str;			/* should've been a union here.  oh well */
+    LinkList arr;
+};
+
+/* lvalue for variable assignment/expansion */
+
+struct value {
+    int isarr;
+    Param pm;		/* parameter node                      */
+    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  */
+};
+
+/* structure for foo=bar assignments */
+
+struct asgment {
+    struct asgment *next;
+    char *name;
+    char *value;
+};
+
+#define MAX_ARRLEN    262144
+
+
+/********************************************/
+/* Defintions for job table and job control */
+/********************************************/
+
+/* size of job table */
+#define MAXJOB 50
+
+/* entry in the job table */
+
+struct job {
+    pid_t gleader;		/* process group leader of this job  */
+    pid_t other;		/* subjob id or subshell pid         */
+    int stat;                   /* see STATs below                   */
+    char *pwd;			/* current working dir of shell when *
+				 * this job was spawned              */
+    struct process *procs;	/* list of processes                 */
+    LinkList filelist;		/* list of files to delete when done */
+    int stty_in_env;		/* if STTY=... is present            */
+    struct ttyinfo *ty;		/* the modes specified by STTY       */
+};
+
+#define STAT_CHANGED	(1<<0)	/* status changed and not reported      */
+#define STAT_STOPPED	(1<<1)	/* all procs stopped or exited          */
+#define STAT_TIMED	(1<<2)	/* job is being timed                   */
+#define STAT_DONE	(1<<3)	/* job is done                          */
+#define STAT_LOCKED	(1<<4)	/* shell is finished creating this job, */
+                                /*   may be deleted from job table      */
+#define STAT_NOPRINT	(1<<5)	/* job was killed internally,           */
+                                /*   we don't want to show that         */
+#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   */
+				/* from this job when it exits.         */
+
+#define SP_RUNNING -1		/* fake status for jobs currently running */
+
+struct timeinfo {
+    long ut;                    /* user space time   */
+    long st;                    /* system space time */
+};
+
+#define JOBTEXTSIZE 80
+
+/* node in job process lists */
+
+struct process {
+    struct process *next;
+    pid_t pid;                  /* process id                       */
+    char text[JOBTEXTSIZE];	/* text to print when 'jobs' is run */
+    int status;			/* return code from waitpid/wait3() */
+    struct timeinfo ti;
+    struct timeval bgtime;	/* time job was spawned             */
+    struct timeval endtime;	/* time job exited                  */
+};
+
+struct execstack {
+    struct execstack *next;
+
+    LinkList args;
+    pid_t list_pipe_pid;
+    int nowait;
+    int pline_level;
+    int list_pipe_child;
+    int list_pipe_job;
+    char list_pipe_text[JOBTEXTSIZE];
+    int lastval;
+    int noeval;
+    int badcshglob;
+    pid_t cmdoutpid;
+    int cmdoutval;
+    int trapreturn;
+    int noerrs;
+    int subsh_close;
+    char *underscore;
+};
+
+struct heredocs {
+    struct heredocs *next;
+    Redir rd;
+};
+
+struct dirsav {
+    int dirfd, level;
+    char *dirname;
+    dev_t dev;
+    ino_t ino;
+};
+
+/*******************************/
+/* Definitions for Hash Tables */
+/*******************************/
+
+typedef void *(*VFunc) _((void *));
+typedef void (*FreeFunc) _((void *));
+
+typedef unsigned (*HashFunc)       _((char *));
+typedef void     (*TableFunc)      _((HashTable));
+typedef void     (*AddNodeFunc)    _((HashTable, char *, void *));
+typedef HashNode (*GetNodeFunc)    _((HashTable, char *));
+typedef HashNode (*RemoveNodeFunc) _((HashTable, char *));
+typedef void     (*FreeNodeFunc)   _((HashNode));
+
+/* type of function that is passed to *
+ * scanhashtable or scanmatchtable    */
+typedef void     (*ScanFunc)       _((HashNode, int));
+
+typedef void (*PrintTableStats) _((HashTable));
+
+/* hash table for standard open hashing */
+
+struct hashtable {
+    /* HASHTABLE DATA */
+    int hsize;			/* size of nodes[]  (number of hash values)   */
+    int ct;			/* number of elements                         */
+    HashNode *nodes;		/* array of size hsize                        */
+
+    /* HASHTABLE METHODS */
+    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          */
+    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            */
+				/* (getnode2 will ignore DISABLED flag)       */
+    RemoveNodeFunc removenode;	/* pointer to function to delete a node       */
+    ScanFunc disablenode;	/* pointer to function to disable a node      */
+    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        */
+
+#ifdef HASHTABLE_INTERNAL_MEMBERS
+    HASHTABLE_INTERNAL_MEMBERS	/* internal use in hashtable.c                */
+#endif
+};
+
+/* generic hash table node */
+
+struct hashnode {
+    HashNode next;		/* next in hash chain */
+    char *nam;			/* hash key           */
+    int flags;			/* various flags      */
+};
+
+/* The flag to disable nodes in a hash table.  Currently  *
+ * you can disable builtins, shell functions, aliases and *
+ * reserved words.                                        */
+#define DISABLED	(1<<0)
+
+/* node in shell reserved word hash table (reswdtab) */
+
+struct reswd {
+    HashNode next;		/* next in hash chain        */
+    char *nam;			/* name of reserved word     */
+    int flags;			/* flags                     */
+    int token;			/* corresponding lexer token */
+};
+
+/* node in alias hash table (aliastab) */
+
+struct alias {
+    HashNode next;		/* next in hash chain       */
+    char *nam;			/* hash data                */
+    int flags;			/* flags for alias types    */
+    char *text;			/* expansion of alias       */
+    int inuse;			/* alias is being expanded  */
+};
+
+/* is this alias global */
+#define ALIAS_GLOBAL	(1<<1)
+
+/* node in command path hash table (cmdnamtab) */
+
+struct cmdnam {
+    HashNode next;		/* next in hash chain */
+    char *nam;			/* hash data          */
+    int flags;
+    union {
+	char **name;		/* full pathname for external commands */
+	char *cmd;		/* file name for hashed commands       */
+    }
+    u;
+};
+
+/* flag for nodes explicitly added to *
+ * cmdnamtab with hash builtin        */
+#define HASHED		(1<<1)
+
+/* node in shell function hash table (shfunctab) */
+
+struct shfunc {
+    HashNode next;		/* next in hash chain     */
+    char *nam;			/* name of shell function */
+    int flags;			/* various flags          */
+    List funcdef;		/* function definition    */
+};
+
+/* node in builtin command hash table (builtintab) */
+
+typedef int (*HandlerFunc) _((char *, char **, char *, int));
+#define NULLBINCMD ((HandlerFunc) 0)
+
+struct builtin {
+    HashNode next;		/* next in hash chain                                 */
+    char *nam;			/* name of builtin                                    */
+    int flags;			/* various flags                                      */
+    HandlerFunc handlerfunc;	/* pointer to function that executes this builtin     */
+    int minargs;		/* minimum number of arguments                        */
+    int maxargs;		/* maximum number of arguments, or -1 for no limit    */
+    int funcid;			/* xbins (see above) for overloaded handlerfuncs      */
+    char *optstr;		/* string of legal options                            */
+    char *defopts;		/* options set by default for overloaded handlerfuncs */
+};
+
+#define BUILTIN(name, flags, handler, min, max, funcid, optstr, defopts) \
+    { NULL, name, flags, handler, min, max, funcid, optstr, defopts }
+#define BIN_PREFIX(name, flags) \
+    BUILTIN(name, flags | BINF_PREFIX, NULLBINCMD, 0, 0, 0, NULL, NULL)
+
+/* builtin flags */
+/* DISABLE IS DEFINED AS (1<<0) */
+#define BINF_PLUSOPTS		(1<<1)	/* +xyz legal */
+#define BINF_R			(1<<2)	/* this is the builtin `r' (fc -e -) */
+#define BINF_PRINTOPTS		(1<<3)
+#define BINF_ADDED		(1<<4)	/* is in the builtins hash table */
+#define BINF_FCOPTS		(1<<5)
+#define BINF_TYPEOPT		(1<<6)
+#define BINF_ECHOPTS		(1<<7)
+#define BINF_MAGICEQUALS	(1<<8)  /* needs automatic MAGIC_EQUAL_SUBST substitution */
+#define BINF_PREFIX		(1<<9)
+#define BINF_DASH		(1<<10)
+#define BINF_BUILTIN		(1<<11)
+#define BINF_COMMAND		(1<<12)
+#define BINF_EXEC		(1<<13)
+#define BINF_NOGLOB		(1<<14)
+#define BINF_PSPECIAL		(1<<15)
+
+#define BINF_TYPEOPTS   (BINF_TYPEOPT|BINF_PLUSOPTS)
+
+struct module {
+    char *nam;
+    int flags;
+    void *handle;
+    LinkList deps;
+};
+
+#define MOD_BUSY    (1<<0)
+
+/* node used in parameter hash table (paramtab) */
+
+struct param {
+    HashNode next;		/* next in hash chain */
+    char *nam;			/* hash data          */
+    int flags;			/* PM_* flags         */
+
+    /* the value of this parameter */
+    union {
+	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) */
+    } u;
+
+    /* pointer to function to set value of this parameter */
+    union {
+	void (*cfn) _((Param, char *));
+	void (*ifn) _((Param, long));
+	void (*afn) _((Param, char **));
+    } sets;
+
+    /* pointer to function to get value of this parameter */
+    union {
+	char *(*cfn) _((Param));
+	long (*ifn) _((Param));
+	char **(*afn) _((Param));
+    } gets;
+
+    /* pointer to function to unset this parameter */
+    void (*unsetfn) _((Param, int));
+
+    int ct;			/* output base or field width            */
+    char *env;			/* location in environment, if exported  */
+    char *ename;		/* name of corresponding environment var */
+    Param old;			/* old struct for use with local         */
+    int level;			/* if (old != NULL), level of localness  */
+};
+
+/* flags for parameters */
+
+/* parameter types */
+#define PM_SCALAR	0	/* scalar                                     */
+#define PM_ARRAY	(1<<0)	/* array                                      */
+#define PM_INTEGER	(1<<1)	/* integer                                    */
+
+#define PM_TYPE(X) (X & (PM_SCALAR|PM_INTEGER|PM_ARRAY))
+
+#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                             */
+
+/* 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                             */
+
+/* node for named directory hash table (nameddirtab) */
+
+struct nameddir {
+    HashNode next;		/* next in hash chain               */
+    char *nam;			/* directory name                   */
+    int flags;			/* see below                        */
+    char *dir;			/* the directory in full            */
+    int diff;			/* strlen(.dir) - strlen(.nam)      */
+};
+
+/* flags for named directories */
+/* DISABLED is defined (1<<0) */
+#define ND_USERNAME	(1<<1)	/* nam is actually a username       */
+
+
+/* flags for controlling printing of hash table nodes */
+#define PRINT_NAMEONLY		(1<<0)
+#define PRINT_TYPE		(1<<1)
+#define PRINT_LIST		(1<<2)
+
+/* 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)
+
+/***********************************/
+/* Definitions for history control */
+/***********************************/
+
+/* history entry */
+
+struct histent {
+    char *text;			/* the history line itself          */
+    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                       */
+};
+
+#define HIST_OLD	0x00000001	/* Command is already written to disk*/
+#define HIST_READ	0x00000002	/* Command was read back from disk*/
+
+/* Parts of the code where history expansion is disabled *
+ * should be within a pair of STOPHIST ... ALLOWHIST     */
+
+#define STOPHIST (stophist += 4);
+#define ALLOWHIST (stophist -= 4);
+
+#define HISTFLAG_DONE   1
+#define HISTFLAG_NOEXEC 2
+#define HISTFLAG_RECALL 4
+
+
+/******************************************/
+/* Definitions for programable completion */
+/******************************************/
+
+/* Nothing special. */
+#define IN_NOTHING 0
+/* In command position. */
+#define IN_CMD     1
+/* In a mathematical environment. */
+#define IN_MATH    2
+/* In a condition. */
+#define IN_COND    3
+/* In a parameter assignment (e.g. `foo=bar'). */
+#define IN_ENV     4
+
+
+/******************************/
+/* Definition for zsh options */
+/******************************/
+
+/* Possible values of emulation */
+
+#define EMULATE_CSH  (1<<1) /* C shell */
+#define EMULATE_KSH  (1<<2) /* Korn shell */
+#define EMULATE_SH   (1<<3) /* Bourne shell */
+#define EMULATE_ZSH  (1<<4) /* `native' mode */
+
+/* option indices */
+
+enum {
+    OPT_INVALID,
+    ALLEXPORT,
+    ALWAYSLASTPROMPT,
+    ALWAYSTOEND,
+    APPENDHISTORY,
+    AUTOCD,
+    AUTOLIST,
+    AUTOMENU,
+    AUTONAMEDIRS,
+    AUTOPARAMKEYS,
+    AUTOPARAMSLASH,
+    AUTOPUSHD,
+    AUTOREMOVESLASH,
+    AUTORESUME,
+    BADPATTERN,
+    BANGHIST,
+    BAREGLOBQUAL,
+    BEEP,
+    BGNICE,
+    BRACECCL,
+    BSDECHO,
+    CDABLEVARS,
+    CHASELINKS,
+    CLOBBER,
+    COMPLETEALIASES,
+    COMPLETEINWORD,
+    CORRECT,
+    CORRECTALL,
+    CSHJUNKIEHISTORY,
+    CSHJUNKIELOOPS,
+    CSHJUNKIEQUOTES,
+    CSHNULLGLOB,
+    EQUALS,
+    ERREXIT,
+    EXECOPT,
+    EXTENDEDGLOB,
+    EXTENDEDHISTORY,
+    FLOWCONTROL,
+    FUNCTIONARGZERO,
+    GLOBOPT,
+    GLOBASSIGN,
+    GLOBCOMPLETE,
+    GLOBDOTS,
+    GLOBSUBST,
+    HASHCMDS,
+    HASHDIRS,
+    HASHLISTALL,
+    HISTALLOWCLOBBER,
+    HISTBEEP,
+    HISTIGNOREDUPS,
+    HISTIGNORESPACE,
+    HISTNOFUNCTIONS,
+    HISTNOSTORE,
+    HISTREDUCEBLANKS,
+    HISTVERIFY,
+    HUP,
+    IGNOREBRACES,
+    IGNOREEOF,
+    INTERACTIVE,
+    INTERACTIVECOMMENTS,
+    KSHARRAYS,
+    KSHAUTOLOAD,
+    KSHGLOB,
+    KSHOPTIONPRINT,
+    LISTAMBIGUOUS,
+    LISTBEEP,
+    LISTTYPES,
+    LOCALOPTIONS,
+    LOGINSHELL,
+    LONGLISTJOBS,
+    MAGICEQUALSUBST,
+    MAILWARNING,
+    MARKDIRS,
+    MENUCOMPLETE,
+    MONITOR,
+    MULTIOS,
+    NOMATCH,
+    NOTIFY,
+    NULLGLOB,
+    NUMERICGLOBSORT,
+    OVERSTRIKE,
+    PATHDIRS,
+    POSIXBUILTINS,
+    PRINTEIGHTBIT,
+    PRINTEXITVALUE,
+    PRIVILEGED,
+    PROMPTBANG,
+    PROMPTCR,
+    PROMPTPERCENT,
+    PROMPTSUBST,
+    PUSHDIGNOREDUPS,
+    PUSHDMINUS,
+    PUSHDSILENT,
+    PUSHDTOHOME,
+    RCEXPANDPARAM,
+    RCQUOTES,
+    RCS,
+    RECEXACT,
+    RESTRICTED,
+    RMSTARSILENT,
+    RMSTARWAIT,
+    SHFILEEXPANSION,
+    SHGLOB,
+    SHINSTDIN,
+    SHOPTIONLETTERS,
+    SHORTLOOPS,
+    SHWORDSPLIT,
+    SINGLECOMMAND,
+    SINGLELINEZLE,
+    SUNKEYBOARDHACK,
+    UNSET,
+    VERBOSE,
+    XTRACE,
+    USEZLE,
+    OPT_SIZE
+};
+
+#undef isset
+#define isset(X) (opts[X])
+#define unset(X) (!opts[X])
+
+#define interact (isset(INTERACTIVE))
+#define jobbing  (isset(MONITOR))
+#define islogin  (isset(LOGINSHELL))
+
+/***********************************************/
+/* Defintions for terminal and display control */
+/***********************************************/
+
+/* tty state structure */
+
+struct ttyinfo {
+#ifdef HAVE_TERMIOS_H
+    struct termios tio;
+#else
+# ifdef HAVE_TERMIO_H
+    struct termio tio;
+# else
+    struct sgttyb sgttyb;
+    int lmodes;
+    struct tchars tchars;
+    struct ltchars ltchars;
+# endif
+#endif
+#ifdef TIOCGWINSZ
+    struct winsize winsize;
+#endif
+};
+
+/* defines for whether tabs expand to spaces */
+#if defined(HAVE_TERMIOS_H) || defined(HAVE_TERMIO_H)
+#define SGTTYFLAG       shttyinfo.tio.c_oflag
+#else   /* we're using sgtty */
+#define SGTTYFLAG       shttyinfo.sgttyb.sg_flags
+#endif
+# ifdef TAB3
+#define SGTABTYPE       TAB3
+# else
+#  ifdef OXTABS
+#define SGTABTYPE       OXTABS
+#  else
+#define SGTABTYPE       XTABS
+#  endif
+# endif
+
+/* flags for termflags */
+
+#define TERM_BAD	0x01	/* terminal has extremely basic capabilities */
+#define TERM_UNKNOWN	0x02	/* unknown terminal type */
+#define TERM_NOUP	0x04	/* terminal has no up capability */
+#define TERM_SHORT	0x08	/* terminal is < 3 lines high */
+#define TERM_NARROW	0x10	/* terminal is < 3 columns wide */
+
+/* interesting termcap strings */
+
+#define TCCLEARSCREEN   0
+#define TCLEFT          1
+#define TCMULTLEFT      2
+#define TCRIGHT         3
+#define TCMULTRIGHT     4
+#define TCUP            5
+#define TCMULTUP        6
+#define TCDOWN          7
+#define TCMULTDOWN      8
+#define TCDEL           9
+#define TCMULTDEL      10
+#define TCINS          11
+#define TCMULTINS      12
+#define TCCLEAREOD     13
+#define TCCLEAREOL     14
+#define TCINSLINE      15
+#define TCDELLINE      16
+#define TCNEXTTAB      17
+#define TCBOLDFACEBEG  18
+#define TCSTANDOUTBEG  19
+#define TCUNDERLINEBEG 20
+#define TCALLATTRSOFF  21
+#define TCSTANDOUTEND  22
+#define TCUNDERLINEEND 23
+#define TC_COUNT       24
+
+#define tccan(X) (tclen[X])
+
+#define TXTBOLDFACE   0x01
+#define TXTSTANDOUT   0x02
+#define TXTUNDERLINE  0x04
+#define TXTDIRTY      0x80
+
+#define txtisset(X)  (txtattrmask & (X))
+#define txtset(X)    (txtattrmask |= (X))
+#define txtunset(X)  (txtattrmask &= ~(X))
+
+#define TXTNOBOLDFACE	0x10
+#define TXTNOSTANDOUT	0x20
+#define TXTNOUNDERLINE	0x40
+
+#define txtchangeisset(X)	(txtchange & (X))
+#define txtchangeset(X, Y)	(txtchange |= (X), txtchange &= ~(Y))
+
+/****************************************/
+/* Definitions for the %_ prompt escape */
+/****************************************/
+
+#define cmdpush(X) if (!(cmdsp >= 0 && cmdsp < 256)) {;} else cmdstack[cmdsp++]=(X)
+#ifdef DEBUG
+# define cmdpop()  if (cmdsp <= 0) { \
+			fputs("BUG: cmdstack empty\n", stderr); \
+			fflush(stderr); \
+		   } else cmdsp--
+#else
+# define cmdpop()   if (cmdsp <= 0) {;} else cmdsp--
+#endif
+
+#define CS_FOR          0
+#define CS_WHILE        1
+#define CS_REPEAT       2
+#define CS_SELECT       3
+#define CS_UNTIL        4
+#define CS_IF           5
+#define CS_IFTHEN       6
+#define CS_ELSE         7
+#define CS_ELIF         8
+#define CS_MATH         9
+#define CS_COND        10
+#define CS_CMDOR       11
+#define CS_CMDAND      12
+#define CS_PIPE        13
+#define CS_ERRPIPE     14
+#define CS_FOREACH     15
+#define CS_CASE        16
+#define CS_FUNCDEF     17
+#define CS_SUBSH       18
+#define CS_CURSH       19
+#define CS_ARRAY       20
+#define CS_QUOTE       21
+#define CS_DQUOTE      22
+#define CS_BQUOTE      23
+#define CS_CMDSUBST    24
+#define CS_MATHSUBST   25
+#define CS_ELIFTHEN    26
+#define CS_HEREDOC     27
+#define CS_HEREDOCD    28
+#define CS_BRACE       29
+#define CS_BRACEPAR    30
+
+/*********************
+ * Memory management *
+ *********************/
+
+#ifndef DEBUG
+# define HEAPALLOC	do { int nonlocal_useheap = global_heapalloc(); do
+
+# define PERMALLOC	do { int nonlocal_useheap = global_permalloc(); do
+
+# define LASTALLOC	while (0); \
+			if (nonlocal_useheap) global_heapalloc(); \
+			else global_permalloc(); \
+		} while(0)
+
+# 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
+
+# define PERMALLOC	do { int nonlocal_useheap = global_permalloc(); \
+			alloc_stackp++; do
+
+# define LASTALLOC	while (0); alloc_stackp--; \
+			if (nonlocal_useheap) global_heapalloc(); \
+			else global_permalloc(); \
+		} while(0)
+
+# define LASTALLOC_RETURN \
+			if ((nonlocal_useheap ? global_heapalloc() : \
+			    global_permalloc()),alloc_stackp--,0){;}else return
+#endif
+
+/****************/
+/* Debug macros */
+/****************/
+
+#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
+
+/**************************/
+/* Signal handling macros */
+/**************************/
+
+/* These used in the sigtrapped[] array */
+
+#define ZSIG_TRAPPED	(1<<0)
+#define ZSIG_IGNORED	(1<<1)
+#define ZSIG_FUNC	(1<<2)
+
+/****************/
+/* Entry points */
+/****************/
+
+/* compctl entry point pointers */
+
+typedef int (*CompctlReadFn) _((char *, char **, char *, char *));
+
+/* ZLE entry point pointers */
+
+typedef void (*ZleVoidFn) _((void));
+typedef void (*ZleVoidIntFn) _((int));
+typedef unsigned char * (*ZleReadFn) _((char *, char *, int));
diff --git a/Src/zsh.mdd b/Src/zsh.mdd
new file mode 100644
index 000000000..244029d65
--- /dev/null
+++ b/Src/zsh.mdd
@@ -0,0 +1,71 @@
+nozshdep=1
+alwayslink=1
+
+# autobins not specified because of alwayslink
+
+objects="builtin.o compat.o cond.o exec.o glob.o hashtable.o \
+hist.o init.o input.o jobs.o lex.o linklist.o loop.o math.o \
+mem.o module.o options.o params.o parse.o prompt.o signals.o \
+signames.o subst.o text.o utils.o watch.o"
+
+headers="../config.h system.h zsh.h sigcount.h signals.h \
+prototypes.h hashtable.h ztype.h"
+
+:<<\Make
+signames.c: signames.awk @SIGNAL_H@
+	$(AWK) -f $(sdir)/signames.awk @SIGNAL_H@ > $@
+
+sigcount.h: signames.c
+	grep 'define.*SIGCOUNT' signames.c > $@
+
+init.o: bltinmods.list zshpaths.h zshxmods.h
+
+params.o: version.h
+
+version.h: $(sdir_top)/Config/version.mk
+	echo '#define ZSH_VERSION "'$(VERSION)'"' > $@
+
+zshpaths.h: FORCE
+	@echo '#define MODULE_DIR "'$(MODDIR)'"' > zshpaths.h.tmp
+	@if cmp -s zshpaths.h zshpaths.h.tmp; then \
+	    rm -f zshpaths.h.tmp; \
+	    echo "\`zshpaths.h' is up to date." ; \
+	else \
+	    mv -f zshpaths.h.tmp zshpaths.h; \
+	    echo "Updated \`zshpaths.h'." ; \
+	fi
+
+bltinmods.list: modules.stamp modules-bltin xmods.conf mkbltnmlst.sh
+	srcdir='$(sdir)' MODBINS='modules-bltin' \
+	XMODCF='$(sdir)/xmods.conf' $(SHELL) $(sdir)/mkbltnmlst.sh $@
+
+zshxmods.h: modules-bltin xmods.conf
+	@echo "Creating \`$@'."
+	@( \
+	    binmods=`sed 's/^/ /;s/$$/ /' modules-bltin`; \
+	    for mod in `cat $(sdir_src)/xmods.conf`; do \
+		case $$binmods in \
+		    *" $$mod "*) \
+			echo "#define    LINKED_XMOD_$$mod 1" ;; \
+		    *)  echo "#ifdef DYNAMIC"; \
+			echo "# define UNLINKED_XMOD_$$mod 1"; \
+			echo "#endif" ;; \
+		esac; \
+	    done; \
+	    echo; \
+	    for mod in $$binmods; do \
+		echo "int boot_$$mod _((Module));"; \
+	    done; \
+	) > $@
+
+clean-here: clean.zsh
+clean.zsh:
+	rm -f sigcount.h signames.c bltinmods.list version.h zshpaths.h zshxmods.h
+
+# This is not properly part of this module, but it is built as if it were.
+main.o: main.c zsh.mdh main.pro
+	$(CC) -c -I. $(CPPFLAGS) $(DEFS) $(CFLAGS) -o $@ $(sdir)/main.c
+
+main.pro: $(PROTODEPS)
+proto.zsh: main.pro
+Make
diff --git a/Src/ztype.h b/Src/ztype.h
new file mode 100644
index 000000000..595ff0588
--- /dev/null
+++ b/Src/ztype.h
@@ -0,0 +1,58 @@
+/*
+ * ztype.h - character classification macros
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#define IDIGIT   (1 <<  0)
+#define IALNUM   (1 <<  1)
+#define IBLANK   (1 <<  2)
+#define INBLANK  (1 <<  3)
+#define ITOK     (1 <<  4)
+#define ISEP     (1 <<  5)
+#define IALPHA   (1 <<  6)
+#define IIDENT   (1 <<  7)
+#define IUSER    (1 <<  8)
+#define ICNTRL   (1 <<  9)
+#define IWORD    (1 << 10)
+#define ISPECIAL (1 << 11)
+#define IMETA    (1 << 12)
+#define IWSEP    (1 << 13)
+#define _icom(X,Y) (typtab[STOUC(X)] & Y)
+#define idigit(X) _icom(X,IDIGIT)
+#define ialnum(X) _icom(X,IALNUM)
+#define iblank(X) _icom(X,IBLANK)	/* blank, not including \n */
+#define inblank(X) _icom(X,INBLANK)	/* blank or \n */
+#define itok(X) _icom(X,ITOK)
+#define isep(X) _icom(X,ISEP)
+#define ialpha(X) _icom(X,IALPHA)
+#define iident(X) _icom(X,IIDENT)
+#define iuser(X) _icom(X,IUSER)	/* username char */
+#define icntrl(X) _icom(X,ICNTRL)
+#define iword(X) _icom(X,IWORD)
+#define ispecial(X) _icom(X,ISPECIAL)
+#define imeta(X) _icom(X,IMETA)
+#define iwsep(X) _icom(X,IWSEP)