about summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Stephenson <pws@users.sourceforge.net>2005-04-01 10:30:08 +0000
committerPeter Stephenson <pws@users.sourceforge.net>2005-04-01 10:30:08 +0000
commite43b2acd67a7b06d4653ff910af1b4b51aedcdfe (patch)
tree53cea44589504314c83f32d92dd9073851d79b92
parent6f8b647733e27a702957f3345044cc46b8f26288 (diff)
downloadzsh-e43b2acd67a7b06d4653ff910af1b4b51aedcdfe.tar.gz
zsh-e43b2acd67a7b06d4653ff910af1b4b51aedcdfe.tar.xz
zsh-e43b2acd67a7b06d4653ff910af1b4b51aedcdfe.zip
21078: parse errors didn't cause non-zero exit status
-rw-r--r--ChangeLog5
-rw-r--r--Src/init.c407
-rw-r--r--Test/A01grammar.ztst450
3 files changed, 767 insertions, 95 deletions
diff --git a/ChangeLog b/ChangeLog
index 171c41bbf..470e10c3a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2005-04-01  Peter Stephenson  <pws@csr.com>
+
+	* 21078: Src/init.c, Test/A01grammar.ztst: parse errors didn't
+	cause non-zero exit status.
+
 2005-03-27  Clint Adams  <clint@zsh.org>
 
 	* 21075: Completion/Unix/Command/_baz: update baz completion
diff --git a/Src/init.c b/Src/init.c
index e7dec681e..4fdf38de7 100644
--- a/Src/init.c
+++ b/Src/init.c
@@ -34,6 +34,8 @@
 
 #include "init.pro"
 
+#include "version.h"
+
 /**/
 int noexitct = 0;
 
@@ -105,7 +107,8 @@ loop(int toplevel, int justonce)
     pushheap();
     for (;;) {
 	freeheap();
-	errflag = 0;
+	if (stophist == 3)	/* re-entry via preprompt() */
+	    hend(NULL);
 	hbegin(1);		/* init history mech        */
 	if (isset(SHINSTDIN)) {
 	    setblock_stdin();
@@ -113,43 +116,55 @@ loop(int toplevel, int justonce)
 	        int hstop = stophist;
 		stophist = 3;
 		preprompt();
-		stophist = hstop;
+		if (stophist != 3)
+		    hbegin(1);
+		else
+		    stophist = hstop;
+		errflag = 0;
 	    }
 	}
 	intr();			/* interrupts on            */
 	lexinit();              /* initialize lexical state */
 	if (!(prog = parse_event())) {	/* if we couldn't parse a list */
-	    hend();
+	    hend(NULL);
 	    if ((tok == ENDINPUT && !errflag) ||
 		(tok == LEXERR && (!isset(SHINSTDIN) || !toplevel)) ||
 		justonce)
 		break;
+	    if (tok == LEXERR && !lastval)
+		lastval = 1;
 	    continue;
 	}
-	if (hend()) {
+	if (hend(prog)) {
 	    int toksav = tok;
 	    Eprog preprog;
 
 	    if (toplevel && (preprog = getshfunc("preexec")) != &dummy_eprog) {
 		LinkList args;
 		int osc = sfcontext;
+		char *cmdstr;
 
 		args = znewlinklist();
 		zaddlinknode(args, "preexec");
-		if (hist_ring)
+		/* If curline got dumped from the history, we don't know
+		 * what the user typed. */
+		if (hist_ring && curline.histnum == curhist)
 		    zaddlinknode(args, hist_ring->text);
+		else
+		    zaddlinknode(args, "");
+		zaddlinknode(args, getjobtext(prog, NULL));
+		zaddlinknode(args, cmdstr = getpermtext(prog, NULL));
 
 		sfcontext = SFC_HOOK;
 		doshfunc("preexec", preprog, args, 0, 1);
 		sfcontext = osc;
+		zsfree(cmdstr);
 		freelinklist(args, (FreeFunc) NULL);
 		errflag = 0;
 	    }
 	    if (stopmsg)	/* unset 'you have stopped jobs' flag */
 		stopmsg--;
 	    execode(prog, 0, 0);
-	    if (toplevel)
-		freeeprogs();
 	    tok = toksav;
 	    if (toplevel)
 		noexitct = 0;
@@ -184,10 +199,10 @@ static int restricted;
 void
 parseargs(char **argv)
 {
+    int optionbreak = 0;
     char **x;
     int action, optno;
     LinkList paramlist;
-    int bourne = (emulation == EMULATE_KSH || emulation == EMULATE_SH);
 
     argzero = *argv++;
     SHIN = 0;
@@ -204,24 +219,47 @@ parseargs(char **argv)
     opts[SINGLECOMMAND] = 0;
 
     /* loop through command line options (begins with "-" or "+") */
-    while (*argv && (**argv == '-' || **argv == '+')) {
+    while (!optionbreak && *argv && (**argv == '-' || **argv == '+')) {
 	char *args = *argv;
 	action = (**argv == '-');
-	if(!argv[0][1])
+	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 == '-') {
+		if(!argv[0][1]) {
+		    /* The pseudo-option `--' signifies the end of options. */
+		    argv++;
+		    goto doneoptions;
+		}
+		if(*argv != args+1 || **argv != '-')
+		    goto badoptionstring;
+		/* GNU-style long options */
+		++*argv;
+		if (!strcmp(*argv, "version")) {
+		    printf("zsh %s (%s-%s-%s)\n",
+			    ZSH_VERSION, MACHTYPE, VENDOR, OSTYPE);
+		    exit(0);
+		}
+		if (!strcmp(*argv, "help")) {
+		    printhelp();
+		    exit(0);
+		}
+		/* `-' characters are allowed in long options */
+		for(args = *argv; *args; args++)
+		    if(*args == '-')
+			*args = '_';
+		goto longoptions;
 	    }
 
-	    if (**argv == 'c') {         /* -c command */
+	    if (unset(SHOPTIONLETTERS) && **argv == 'b') {
+		/* -b ends options at the end of this argument */
+		optionbreak = 1;
+	    } else if (**argv == 'c') {
+		/* -c command */
 		cmd = *argv;
 		opts[INTERACTIVE] &= 1;
 		opts[SHINSTDIN] = 0;
+		scriptname = ztrdup("zsh");
 	    } else if (**argv == 'o') {
 		if (!*++*argv)
 		    argv++;
@@ -229,9 +267,11 @@ parseargs(char **argv)
 		    zerr("string expected after -o", NULL, 0);
 		    exit(1);
 		}
-		if(!(optno = optlookup(*argv)))
+	    longoptions:
+		if (!(optno = optlookup(*argv))) {
 		    zerr("no such option: %s", *argv, 0);
-		else if (optno == RESTRICTED)
+		    exit(1);
+		} else if (optno == RESTRICTED)
 		    restricted = action;
 		else
 		    dosetopt(optno, action, 1);
@@ -240,6 +280,7 @@ parseargs(char **argv)
 		/* zsh's typtab not yet set, have to use ctype */
 		while (*++*argv)
 		    if (!isspace(STOUC(**argv))) {
+		    badoptionstring:
 			zerr("bad option string: `%s'", args, 0);
 			exit(1);
 		    }
@@ -284,19 +325,37 @@ parseargs(char **argv)
     if(isset(SINGLECOMMAND))
 	opts[INTERACTIVE] &= 1;
     opts[INTERACTIVE] = !!opts[INTERACTIVE];
-    pparams = x = (char **) zcalloc((countlinknodes(paramlist) + 1) * sizeof(char *));
+    pparams = x = (char **) zshcalloc((countlinknodes(paramlist) + 1) * sizeof(char *));
 
     while ((*x++ = (char *)getlinknode(paramlist)));
     free(paramlist);
     argzero = ztrdup(argzero);
 }
 
+/**/
+static void
+printhelp(void)
+{
+    printf("Usage: %s [<options>] [<argument> ...]\n", argzero);
+    printf("\nSpecial options:\n");
+    printf("  --help     show this message, then exit\n");
+    printf("  --version  show zsh version number, then exit\n");
+    if(unset(SHOPTIONLETTERS))
+	printf("  -b         end option processing, like --\n");
+    printf("  -c         take first argument as a command to execute\n");
+    printf("  -o OPTION  set an option by name (see below)\n");
+    printf("\nNormal options are named.  An option may be turned on by\n");
+    printf("`-o OPTION', `--OPTION', `+o no_OPTION' or `+-no-OPTION'.  An\n");
+    printf("option may be turned off by `-o no_OPTION', `--no-OPTION',\n");
+    printf("`+o OPTION' or `+-OPTION'.  Options are listed below only in\n");
+    printf("`--OPTION' or `--no-OPTION' form.\n");
+    printoptionlist();
+}
 
 /**/
 mod_export void
 init_io(void)
 {
-    long ttpgrp;
     static char outbuf[BUFSIZ], errbuf[BUFSIZ];
 
 #ifdef RSH_BUG_WORKAROUND
@@ -322,7 +381,13 @@ init_io(void)
 #endif
 
     if (shout) {
-	fclose(shout);
+	/*
+	 * Check if shout was set to stderr, if so don't close it.
+	 * We do this if we are interactive but don't have a
+	 * terminal.
+	 */
+	if (shout != stderr)
+	    fclose(shout);
 	shout = 0;
     }
     if (SHTTY != -1) {
@@ -391,29 +456,21 @@ init_io(void)
 
     /* We will only use zle if shell is interactive, *
      * SHTTY != -1, and shout != 0                   */
-    if (interact && SHTTY != -1) {
+    if (interact) {
 	init_shout();
-	if(!shout)
+	if(!SHTTY || !shout)
 	    opts[USEZLE] = 0;
     } else
 	opts[USEZLE] = 0;
 
 #ifdef JOB_CONTROL
-    /* If interactive, make the shell the foreground process */
+    /* If interactive, make sure the shell is in the foreground and is the
+     * process group leader.
+     */
+    mypid = (zlong)getpid();
     if (opts[MONITOR] && interact && (SHTTY != -1)) {
-	if ((mypgrp = GETPGRP()) > 0) {
-	    while ((ttpgrp = gettygrp()) != -1 && ttpgrp != mypgrp) {
-		sleep(1);	/* give parent time to change pgrp */
-		mypgrp = GETPGRP();
-		if (mypgrp == mypid)
-		    attachtty(mypgrp);
-		if (mypgrp == gettygrp())
-		    break;
-		killpg(mypgrp, SIGTTIN);
-		mypgrp = GETPGRP();
-	    }
-	} else
-	    opts[MONITOR] = 0;
+	origpgrp = GETPGRP();
+        acquire_pgrp(); /* might also clear opts[MONITOR] */
     } else
 	opts[MONITOR] = 0;
 #else
@@ -427,8 +484,18 @@ init_shout(void)
 {
     static char shoutbuf[BUFSIZ];
 #if defined(JOB_CONTROL) && defined(TIOCSETD) && defined(NTTYDISC)
-    int ldisc = NTTYDISC;
+    int ldisc;
+#endif
+
+    if (SHTTY == -1)
+    {
+	/* Since we're interative, it's nice to have somewhere to write. */
+	shout = stderr;
+	return;
+    }
 
+#if defined(JOB_CONTROL) && defined(TIOCSETD) && defined(NTTYDISC)
+    ldisc = NTTYDISC;
     ioctl(SHTTY, TIOCSETD, (char *)&ldisc);
 #endif
 
@@ -450,7 +517,8 @@ init_shout(void)
 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", "ch"
+    "md", "so", "us", "me", "se", "ue", "ch",
+    "ku", "kd", "kl", "kr"
 };
 
 /* Initialise termcap */
@@ -474,13 +542,13 @@ init_term(void)
 
 #ifdef TGETENT_ACCEPTS_NULL
     /* If possible, we let tgetent allocate its own termcap buffer */
-    if (tgetent(NULL, term) != 1) {
+    if (tgetent(NULL, term) != TGETENT_SUCCESS)
 #else
-    if (tgetent(termbuf, term) != 1) {
+    if (tgetent(termbuf, term) != TGETENT_SUCCESS)
 #endif
-
+    {
 	if (isset(INTERACTIVE))
-	    zerr("can't find termcap info for %s", term, 0);
+	    zerr("can't find terminal definition for %s", term, 0);
 	errflag = 0;
 	termflags |= TERM_BAD;
 	return 0;
@@ -552,14 +620,11 @@ setupvals(void)
 #endif
     struct timezone dummy_tz;
     char *ptr;
-#ifdef HAVE_GETRLIMIT
-    int i;
-#endif
+    int i, j;
 #if defined(SITEFPATH_DIR) || defined(FPATH_DIR)
     char **fpathptr;
 # if defined(FPATH_DIR) && defined(FPATH_SUBDIRS)
     char *fpath_subdirs[] = FPATH_SUBDIRS;
-    int j;
 # endif
 # ifdef SITEFPATH_DIR
     int fpathlen = 1;
@@ -567,6 +632,47 @@ setupvals(void)
     int fpathlen = 0;
 # endif
 #endif
+    int close_fds[10], tmppipe[2];
+
+    /*
+     * Workaround a problem with NIS (in one guise or another) which
+     * grabs file descriptors and keeps them for future reference.
+     * We don't want these to be in the range where the user can
+     * open fd's, i.e. 0 to 9 inclusive.  So we make sure all
+     * fd's in that range are in use.
+     */
+    memset(close_fds, 0, 10*sizeof(int));
+    if (pipe(tmppipe) == 0) {
+	/*
+	 * Strategy:  Make sure we have at least fd 0 open (hence
+	 * the pipe).  From then on, keep dup'ing until we are
+	 * up to 9.  If we go over the top, close immediately, else
+	 * mark for later closure.
+	 */
+	i = -1;			/* max fd we have checked */
+	while (i < 9) {
+	    /* j is current fd */
+	    if (i < tmppipe[0])
+		j = tmppipe[0];
+	    else if (i < tmppipe[1])
+		j = tmppipe[1];
+	    else {
+		j = dup(0);
+		if (j == -1)
+		    break;
+	    }
+	    if (j < 10)
+		close_fds[j] = 1;
+	    else
+		close(j);
+	    if (i < j)
+		i = j;
+	}
+	if (i < tmppipe[0])
+	    close(tmppipe[0]);
+	if (i < tmppipe[1])
+	    close(tmppipe[1]);
+    }
 
     addhookdefs(argzero, zshhooks, sizeof(zshhooks)/sizeof(*zshhooks));
 
@@ -594,9 +700,6 @@ setupvals(void)
     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");
@@ -695,7 +798,8 @@ setupvals(void)
      * initialize `PWD' from `HOME'      */
     if (ispwd(home))
 	pwd = ztrdup(home);
-    else if ((ptr = zgetenv("PWD")) && ispwd(ptr))
+    else if ((ptr = zgetenv("PWD")) && (strlen(ptr) < PATH_MAX) &&
+	     (ptr = metafy(ptr, -1, META_STATIC), ispwd(ptr)))
 	pwd = ztrdup(ptr);
     else
 	pwd = metafy(zgetcwd(), -1, META_DUP);
@@ -706,12 +810,12 @@ setupvals(void)
     initlextabs();    /* initialize lexing tables    */
 
     createreswdtable();     /* create hash table for reserved words    */
-    createaliastable();     /* create hash table for aliases           */
+    createaliastables();    /* create hash tables 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             */
+    createparamtable();     /* create parameter hash table             */
 
     condtab = NULL;
     wrappers = NULL;
@@ -754,7 +858,12 @@ setupvals(void)
 #endif
     }
 
-    times(&shtms);
+    get_usage();
+
+    /* Close the file descriptors we opened to block off 0 to 9 */
+    for (i = 0; i < 10; i++)
+	if (close_fds[i])
+	    close(i);
 }
 
 /* Initialize signal handling */
@@ -763,6 +872,12 @@ setupvals(void)
 void
 init_signals(void)
 {
+    if (interact) {
+	int i;
+	signal_setmask(signal_mask(0));
+	for (i=0; i<NSIG; ++i)
+	    signal_default(i);
+    }
     sigchld_mask = signal_mask(SIGCHLD);
 
     intr();
@@ -771,7 +886,10 @@ init_signals(void)
     signal_ignore(SIGQUIT);
 #endif
 
-    install_handler(SIGHUP);
+    if (signal_ignore(SIGHUP) == SIG_IGN)
+	opts[HUP] = 0;
+    else
+	install_handler(SIGHUP);
     install_handler(SIGCHLD);
 #ifdef SIGWINCH
     install_handler(SIGWINCH);
@@ -781,28 +899,9 @@ init_signals(void)
 	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);
+	signal_ignore(SIGTTOU);
+	signal_ignore(SIGTSTP);
+	signal_ignore(SIGTTIN);
     }
 }
 
@@ -904,7 +1003,7 @@ source(char *s)
     int oldshst, osubsh, oloops;
     FILE *obshin;
     char *old_scriptname = scriptname, *us;
-    char *ocs;
+    unsigned char *ocs;
     int ocsp;
 
     if (!s || 
@@ -943,7 +1042,7 @@ source(char *s)
 	execode(prog, 1, 0);
 	popheap();
     } else
-	loop(0, 0);		     /* loop through the file to be sourced        */
+	loop(0, 0);		     /* loop through the file to be sourced  */
     sourcelevel--;
 
     /* restore the current shell state */
@@ -976,18 +1075,20 @@ source(char *s)
 void
 sourcehome(char *s)
 {
-    char buf[PATH_MAX];
     char *h;
 
+    queue_signals();
     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;
+
+    {
+	/* Let source() complain if path is too long */
+	VARARR(char, buf, strlen(h) + strlen(s) + 2);
+	sprintf(buf, "%s/%s", h, s);
+	unqueue_signals();
+	source(buf);
     }
-    sprintf(buf, "%s/%s", h, s);
-    source(buf);
 }
 
 /**/
@@ -1009,7 +1110,7 @@ noop_function(void)
 
 /**/
 mod_export void
-noop_function_int(int nothing)
+noop_function_int(UNUSED(int nothing))
 {
     /* do nothing */
 }
@@ -1024,51 +1125,63 @@ noop_function_int(int nothing)
 /**/
 mod_export ZleVoidFn trashzleptr = noop_function;
 /**/
-mod_export ZleVoidFn gotwordptr = noop_function;
-/**/
 mod_export ZleVoidFn refreshptr = noop_function;
 /**/
 mod_export ZleVoidIntFn spaceinlineptr = noop_function_int;
 /**/
 mod_export ZleReadFn zlereadptr = autoload_zleread;
+/**/
+mod_export ZleVoidIntFn zlesetkeymapptr = noop_function_int;
 
 #else /* !LINKED_XMOD_zshQszle */
 
 mod_export ZleVoidFn trashzleptr = noop_function;
-mod_export ZleVoidFn gotwordptr = noop_function;
 mod_export ZleVoidFn refreshptr = noop_function;
 mod_export ZleVoidIntFn spaceinlineptr = noop_function_int;
 # ifdef UNLINKED_XMOD_zshQszle
 mod_export ZleReadFn zlereadptr = autoload_zleread;
+mod_export ZleVoidIntFn zlesetkeymapptr = autoload_zlesetkeymap;
 # else /* !UNLINKED_XMOD_zshQszle */
 mod_export ZleReadFn zlereadptr = fallback_zleread;
+mod_export ZleVoidIntFn zlesetkeymapptr = noop_function_int;
 # endif /* !UNLINKED_XMOD_zshQszle */
 
 #endif /* !LINKED_XMOD_zshQszle */
 
 /**/
 unsigned char *
-autoload_zleread(char *lp, char *rp, int ha)
+autoload_zleread(char **lp, char **rp, int ha, int con)
 {
     zlereadptr = fallback_zleread;
     if (load_module("zsh/zle"))
 	load_module("zsh/compctl");
-    return zleread(lp, rp, ha);
+    return zleread(lp, rp, ha, con);
 }
 
 /**/
 mod_export unsigned char *
-fallback_zleread(char *lp, char *rp, int ha)
+fallback_zleread(char **lp, UNUSED(char **rp), UNUSED(int ha), UNUSED(int con))
 {
     char *pptbuf;
     int pptlen;
 
-    pptbuf = unmetafy(promptexpand(lp, 0, NULL, NULL), &pptlen);
+    pptbuf = unmetafy(promptexpand(lp ? *lp : NULL, 0, NULL, NULL), &pptlen);
     write(2, (WRITE_ARG_2_T)pptbuf, pptlen);
     free(pptbuf);
+
     return (unsigned char *)shingetline();
 }
 
+/**/
+static void
+autoload_zlesetkeymap(int mode)
+{
+    zlesetkeymapptr = noop_function_int;
+    load_module("zsh/zle");
+    (*zlesetkeymapptr)(mode);
+}
+
+
 /* compctl entry point pointers.  Similar to the ZLE ones. */
 
 /**/
@@ -1076,9 +1189,113 @@ mod_export CompctlReadFn compctlreadptr = fallback_compctlread;
 
 /**/
 mod_export int
-fallback_compctlread(char *name, char **args, char *ops, char *reply)
+fallback_compctlread(char *name, UNUSED(char **args), UNUSED(Options ops), UNUSED(char *reply))
 {
     zwarnnam(name, "option valid only in functions called from completion",
 	    NULL, 0);
     return 1;
 }
+
+/*
+ * This is real main entry point. This has to be mod_export'ed
+ * so zsh.exe can found it on Cygwin
+ */
+
+/**/
+mod_export int
+zsh_main(UNUSED(int argc), char **argv)
+{
+    char **t;
+    int t0;
+#ifdef USE_LOCALE
+    setlocale(LC_ALL, "");
+#endif
+
+    init_jobs(argv, environ);
+
+    /*
+     * Provisionally set up the type table to allow metafication.
+     * This will be done properly when we have decided if we are
+     * interactive
+     */
+    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 (t = argv; *t; *t = metafy(*t, -1, META_ALLOC), t++);
+
+    zsh_name = argv[0];
+    do {
+      char *arg0 = zsh_name;
+      if (!(zsh_name = strrchr(arg0, '/')))
+	  zsh_name = arg0;
+      else
+	  zsh_name++;
+      if (*zsh_name == '-')
+	  zsh_name++;
+      if (strcmp(zsh_name, "su") == 0) {
+	  char *sh = zgetenv("SHELL");
+	  if (sh && *sh && arg0 != sh)
+	      zsh_name = sh;
+	  else
+	      break;
+      } else
+	  break;
+    } while (zsh_name);
+
+    fdtable_size = zopenmax();
+    fdtable = zshcalloc(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();
+    init_bltinmods();
+    run_init_scripts();
+    init_misc();
+
+    for (;;) {
+	/*
+	 * See if we can free up some of jobtab.
+	 * We only do this at top level, because if we are
+	 * executing stuff we may refer to them by job pointer.
+	 */
+	maybeshrinkjobtab();
+
+	do
+	    loop(1,0);
+	while (tok != ENDINPUT && (tok != LEXERR || isset(SHINSTDIN)));
+	if (tok == LEXERR) {
+	    /* Make sure a parse error exits with non-zero status */
+	    if (!lastval)
+		lastval = 1;
+	    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/Test/A01grammar.ztst b/Test/A01grammar.ztst
new file mode 100644
index 000000000..81a963b05
--- /dev/null
+++ b/Test/A01grammar.ztst
@@ -0,0 +1,450 @@
+#
+# This file contains tests corresponding to the `Shell Grammar' texinfo node.
+#
+
+%prep
+
+  mkdir basic.tmp && cd basic.tmp
+
+  touch foo bar
+  echo "'" >unmatched_quote.txt
+
+%test
+#
+# Tests for `Simple Commands and Pipelines'
+#
+  echo foo | cat | sed 's/foo/bar/'
+0:Basic pipeline handling
+>bar
+
+  false | true
+0:Exit status of pipeline with builtins (true)
+
+  true | false
+1:Exit status of pipeline with builtins (false)
+
+  fn() { local foo; read foo; print $foo; }
+  coproc fn
+  print -p coproc test output
+  read -p bar
+  print $bar
+0:Basic coprocess handling
+>coproc test output
+
+  true | false && print true || print false
+0:Basic sublist (i)
+>false
+
+  false | true && print true || print false
+0:Basic sublist (ii)
+>true
+
+  (cd /NonExistentDirectory >&/dev/null) || print false
+0:Basic subshell list with error
+>false
+
+  { cd /NonExistentDirectory >&/dev/null } || print false
+0:Basic current shell list with error
+>false
+
+#
+# Tests for `Precommand Modifiers'
+#
+  - $ZTST_testdir/../Src/zsh -fc "[[ \$0 = \"-$ZTST_testdir/../Src/zsh\" ]]"
+0:`-' precommand modifier
+
+  echo f*
+  noglob echo f*
+0:`noglob' precommand modifier
+>foo
+>f*
+
+  (exec /bin/sh; echo bar)
+0:`exec' precommand modifier
+
+  cat() { echo Function cat executed; }
+  command cat && unfunction cat
+0:`command' precommand modifier
+<External command cat executed
+>External command cat executed
+
+  cd() { echo Not cd at all; }
+  builtin cd . && unfunction cd
+0:`builtin' precommand modifier
+
+#
+# Tests for `Complex Commands'
+#
+
+  if true; then
+    print true-1
+  elif true; then
+    print true-2
+  else
+    print false
+  fi
+0:`if ...' (i)
+>true-1
+
+  if false; then
+    print true-1
+  elif true; then
+    print true-2
+  else
+    print false
+  fi
+0:`if ...' (ii)
+>true-2
+
+  if false; then
+    print true-1
+  elif false; then
+    print true-2
+  else
+    print false
+  fi
+0:`if ...' (iii)
+>false
+
+  if true;
+    :
+  fi
+1d:`if ...' (iv)
+?(eval):3: parse error near `fi'
+
+  for name in word to term; do
+    print $name
+  done
+0:`for' loop
+>word
+>to
+>term
+
+  for name
+  in word to term; do
+    print $name
+  done
+0:`for' loop with newline before in keyword
+>word
+>to
+>term
+
+  for (( name = 0; name < 3; name++ )); do
+    print $name
+  done
+0:arithmetic `for' loop
+>0
+>1
+>2
+
+  for keyvar valvar in key1 val1 key2 val2; do
+     print key=$keyvar val=$valvar
+  done
+0:enhanced `for' syntax with two loop variables
+>key=key1 val=val1
+>key=key2 val=val2
+
+  for keyvar valvar stuffvar in keyA valA stuffA keyB valB stuffB; do
+     print key=$keyvar val=$valvar stuff=$stuffvar
+  done
+0:enhanced `for' syntax with three loop variables
+>key=keyA val=valA stuff=stuffA
+>key=keyB val=valB stuff=stuffB
+
+  for in in in in in stop; do
+    print in=$in
+  done
+0:compatibility of enhanced `for' syntax with standard syntax
+>in=in
+>in=in
+>in=in
+>in=stop
+
+  name=0
+  while (( name < 3 )); do
+    print $name
+    (( name++ ))
+  done
+0:`while' loop
+>0
+>1
+>2
+
+  name=0
+  until (( name == 3 )); do
+    print $name
+    (( name++ ))
+  done
+0:`until' loop
+>0
+>1
+>2
+
+  repeat 3 do
+    echo over and over
+  done
+0:`repeat' loop
+>over and over
+>over and over
+>over and over
+
+  word=Trinity
+  case $word in
+    Michaelmas) print 0
+                ;;
+    Hilary) print 1
+            ;;
+    Trinity) print 2
+             ;;
+    *) print 3
+       ;;
+  esac
+0:`case', old syntax
+>2
+
+  word=Trinity
+  case $word in
+    (Michaelmas) print 0
+                ;;
+    (Hilary) print 1
+            ;;
+    (Trinity) print 2
+             ;;
+    (*) print 3
+       ;;
+  esac
+0:`case', new syntax
+>2
+
+  word=Hilary
+  case $word in
+    (Michaelmas) print 0
+                ;;
+    (Hilary) print 1
+            ;&
+    (Trinity) print 2
+             ;&
+    (*) print 3
+       ;;
+  esac
+0:`case', new syntax, cascaded
+>1
+>2
+>3
+
+## Select now reads from stdin if the shell is not interactive.
+## Its own output goes to stderr.
+  (COLUMNS=80
+  PS3="input> "
+  select name in one two three; do
+    print $name
+  done)
+0:`select' loop
+<2
+?1) one     2) two     3) three   
+?input> input> 
+>two
+
+  function name1 name2 () { print This is $0; }
+  name2
+  name1 name2() { print This is still $0; }
+  name2
+0:`function' keyword
+>This is name2
+>This is still name2
+
+  (time cat) >&/dev/null
+0:`time' keyword (status only)
+
+  if [[ -f foo && -d . && -n $ZTST_testdir ]]; then
+    true
+  else
+    false
+  fi
+0:basic [[ ... ]] test
+
+#
+# Current shell execution with try/always form.
+# We put those with errors in subshells so that any unhandled error doesn't
+# propagate.
+#
+
+  {
+     print The try block.
+  } always {
+     print The always block.
+  }
+  print After the always block.
+0:Basic `always' syntax
+>The try block.
+>The always block.
+>After the always block.
+
+  ({
+    print Position one.
+    print ${*this is an error*}
+    print Position two.
+  } always {
+    if (( TRY_BLOCK_ERROR )); then
+      print An error occurred.
+    else
+      print No error occurred.
+    fi
+  }
+  print Position three)
+1:Always block with error not reset
+>Position one.
+>An error occurred.
+?(eval):3: bad substitution
+
+  ({
+    print Stelle eins.
+    print ${*voici une erreur}
+    print Posizione due.
+  } always {
+    if (( TRY_BLOCK_ERROR )); then
+      print Erratum factum est. Retro ponetur.
+      (( TRY_BLOCK_ERROR = 0 ))
+    else
+      print unray touay foay anguageslay
+    fi
+  }
+  print Status after always block is $?.)
+0:Always block with error reset
+>Stelle eins.
+>Erratum factum est. Retro ponetur.
+>Status after always block is 1.
+?(eval):3: bad substitution
+
+# Outputting of structures from the wordcode is distinctly non-trivial,
+# we probably ought to have more like the following...
+  fn1() { { echo foo; } }
+  fn2() { { echo foo; } always { echo bar; } }
+  fn3() { ( echo foo; ) }
+  functions fn1 fn2 fn3
+0:Output of syntactic structures with and without always blocks
+>fn1 () {
+>	{
+>		echo foo
+>	}
+>}
+>fn2 () {
+>	{
+>		echo foo
+>	} always {
+>		echo bar
+>	}
+>}
+>fn3 () {
+>	(
+>		echo foo
+>	)
+>}
+
+
+#
+# Tests for `Alternate Forms For Complex Commands'
+#
+
+  if (true) { print true-1 } elif (true) { print true-2 } else { print false }
+  if (false) { print true-1 } elif (true) { print true-2 } else { print false }
+  if (false) { print true-1 } elif (false) { print true-2 } else { print false }
+0:Alternate `if' with braces
+>true-1
+>true-2
+>false
+
+  if true; print true
+0:Short form of `if'
+>true
+
+  for name ( word1 word2 word3 ) print $name
+0:Form of `for' with parentheses.
+>word1
+>word2
+>word3
+
+  for name in alpha beta gamma; print $name
+0:Short form of `for'
+>alpha
+>beta
+>gamma
+
+  for (( val = 2; val < 10; val *= val )) print $val
+0:Short arithmetic `for'
+>2
+>4
+
+  foreach name ( verbiage words periphrasis )
+    print $name
+  end
+0:Csh-like `for'
+>verbiage
+>words
+>periphrasis
+
+# see comment with braces used in if loops
+  val=0;
+  while (( val < 2 )) { print $((val++)); }
+0:Alternative `while'
+>0
+>1
+
+  val=2;
+  until (( val == 0 )) { print $((val--)); }
+0:Alternative `until'
+>2
+>1
+
+  repeat 3 print Hip hip hooray
+0:Short `repeat'
+>Hip hip hooray
+>Hip hip hooray
+>Hip hip hooray
+
+  case bravo {
+    (alpha) print schmalpha
+	    ;;
+    (bravo) print schmavo
+	    ;;
+    (charlie) print schmarlie
+	    ;;
+  }
+0:`case' with braces
+>schmavo
+
+  print 'This test hangs the shell when it fails...' >&8
+  name=0
+# The number 4375 here is chosen to produce more than 16384 bytes of output
+  while (( name < 4375 )); do
+    print -n $name
+    (( name++ ))
+  done < /dev/null | { read name; print done }
+0:Bug regression: `while' loop with redirection and pipeline
+>done
+
+# This used to be buggy and print X at the end of each iteration.
+  for f in 1 2 3 4; do
+    print $f || break
+  done && print X
+0:Handling of ||'s and &&'s with a for loop in between
+>1
+>2
+>3
+>4
+>X
+
+# Same bug for &&, used to print `no' at the end of each iteration
+  for f in 1 2 3 4; do
+    false && print strange
+  done || print no
+0:Handling of &&'s and ||'s with a for loop in between
+>no
+
+  $ZTST_testdir/../Src/zsh -f unmatched_quote.txt
+1:Parse error with file causes non-zero exit status
+?unmatched_quote.txt:2: unmatched '
+
+  $ZTST_testdir/../Src/zsh -f <unmatched_quote.txt
+1:Parse error on standard input causes non-zero exit status
+?zsh: unmatched '