/*
 * 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 */

/**/
mod_export int incmdpos;

/**/
int aliasspaceflag;

/* != 0 if we are in the middle of a [[ ... ]] */
 
/**/
mod_export int incond;
 
/* != 0 if we are after a redirection (for ctxtlex only) */
 
/**/
mod_export int inredir;
 
/*
 * 1 if we are about to read a case pattern
 * -1 if we are not quite sure
 * 0 otherwise
 */
 
/**/
int incasepat;
 
/* != 0 if we just read a newline */
 
/**/
int isnewlin;

/* != 0 if we are after a for keyword */

/**/
int infor;

/* != 0 if we are after a repeat keyword; if it's nonzero it's a 1-based index
 * of the current token from the last-seen command position */

/**/
int inrepeat_; /* trailing underscore because of name clash with Zle/zle_vi.c */

/* != 0 if parsing arguments of typeset etc. */

/**/
mod_export int intypeset;

/* list of here-documents */

/**/
struct heredocs *hdocs;
 

#define YYERROR(O)  { tok = LEXERR; ecused = (O); return 0; }
#define YYERRORV(O) { tok = LEXERR; ecused = (O); return; }
#define COND_ERROR(X,Y) \
    do {					\
	zwarn(X,Y);				\
	herrflush();				\
	if (noerrs != 2)			\
	    errflag |= ERRFLAG_ERROR;		\
	YYERROR(ecused)				\
	    } while(0)


/* 
 * Word code.
 *
 * The parser now produces word code, reducing memory consumption compared
 * to the nested structs we had before.
 *
 * Word code layout:
 *
 *   WC_END
 *     - end of program code
 *
 *   WC_LIST
 *     - data contains type (sync, ...)
 *     - followed by code for this list
 *     - if not (type & Z_END), followed by next WC_LIST
 *
 *   WC_SUBLIST
 *     - data contains type (&&, ||, END) and flags (coprog, not)
 *     - followed by code for sublist
 *     - if not (type == END), followed by next WC_SUBLIST
 *
 *   WC_PIPE
 *     - data contains type (end, mid) and LINENO
 *     - if not (type == END), followed by offset to next WC_PIPE
 *     - followed by command
 *     - if not (type == END), followed by next WC_PIPE
 *
 *   WC_REDIR
 *     - must precede command-code (or WC_ASSIGN)
 *     - data contains type (<, >, ...)
 *     - followed by fd1 and name from struct redir
 *     - for the extended form {var}>... where the fd is assigned
 *       to var, there is an extra item to contain var
 *
 *   WC_ASSIGN
 *     - data contains type (scalar, array) and number of array-elements
 *     - followed by name and value
 *     Note variant for WC_TYPESET assignments: WC_ASSIGN_INC indicates
 *     a name with no equals, not an =+ which isn't valid here.
 *
 *   WC_SIMPLE
 *     - data contains the number of arguments (plus command)
 *     - followed by strings
 *
 *   WC_TYPESET
 *     Variant of WC_SIMPLE used when TYPESET reserved word found.
 *     - data contains the number of string arguments (plus command)
 *     - followed by strings
 *     - followed by number of assignments
 *     - followed by assignments if non-zero number.
 *
 *   WC_SUBSH
 *     - data unused
 *     - followed by list
 *
 *   WC_CURSH
 *     - data unused
 *     - followed by list
 *
 *   WC_TIMED
 *     - data contains type (followed by pipe or not)
 *     - if (type == PIPE), followed by pipe
 *
 *   WC_FUNCDEF
 *     - data contains offset to after body
 *     - followed by number of names
 *     - followed by names
 *     - followed by offset to first string
 *     - followed by length of string table
 *     - followed by number of patterns for body
 *     - followed by codes for body
 *     - followed by strings for body
 *
 *   WC_FOR
 *     - data contains type (list, ...) and offset to after body
 *     - if (type == COND), followed by init, cond, advance expressions
 *     - else if (type == PPARAM), followed by param name
 *     - else if (type == LIST), followed by param name, num strings, strings
 *     - followed by body
 *
 *   WC_SELECT
 *     - data contains type (list, ...) and offset to after body
 *     - if (type == PPARAM), followed by param name
 *     - else if (type == LIST), followed by param name, num strings, strings
 *     - followed by body
 *
 *   WC_WHILE
 *     - data contains type (while, until) and offset to after body
 *     - followed by condition
 *     - followed by body
 *
 *   WC_REPEAT
 *     - data contains offset to after body
 *     - followed by number-string
 *     - followed by body
 *
 *   WC_CASE
 *     - first CASE is always of type HEAD, data contains offset to esac
 *     - after that CASEs of type OR (;;), AND (;&) and TESTAND (;|),
 *       data is offset to next case
 *     - each OR/AND/TESTAND case is followed by pattern, pattern-number, list
 *
 *   WC_IF
 *     - first IF is of type HEAD, data contains offset to fi
 *     - after that IFs of type IF, ELIF, ELSE, data is offset to next
 *     - each non-HEAD is followed by condition (only IF, ELIF) and body
 *
 *   WC_COND
 *     - data contains type
 *     - if (type == AND/OR), data contains offset to after this one,
 *       followed by two CONDs
 *     - else if (type == NOT), followed by COND
 *     - else if (type == MOD), followed by name and strings
 *     - else if (type == MODI), followed by name, left, right
 *     - else if (type == STR[N]EQ), followed by left, right, pattern-number
 *     - else if (has two args) followed by left, right
 *     - else followed by string
 *
 *   WC_ARITH
 *     - followed by string (there's only one)
 *
 *   WC_AUTOFN
 *     - only used by the autoload builtin
 *
 * Lists and sublists may also be simplified, indicated by the presence
 * of the Z_SIMPLE or WC_SUBLIST_SIMPLE flags. In this case they are only
 * followed by a slot containing the line number, not by a WC_SUBLIST or
 * WC_PIPE, respectively. The real advantage of simplified lists and
 * sublists is that they can be executed faster, see exec.c. In the
 * parser, the test if a list can be simplified is done quite simply
 * by passing a int* around which gets set to non-zero if the thing
 * just parsed is `cmplx', i.e. may need to be run by forking or 
 * some such.
 *
 * In each of the above, strings are encoded as one word code. For empty
 * strings this is the bit pattern 11x, the lowest bit is non-zero if the
 * string contains tokens and zero otherwise (this is true for the other
 * ways to encode strings, too). For short strings (one to three
 * characters), this is the marker 01x with the 24 bits above that
 * containing the characters. Longer strings are encoded as the offset
 * into the strs character array stored in the eprog struct shifted by
 * two and ored with the bit pattern 0x.
 * The ecstrcode() function that adds the code for a string uses a simple
 * binary tree of strings already added so that long strings are encoded
 * only once.
 *
 * Note also that in the eprog struct the pattern, code, and string
 * arrays all point to the same memory block.
 *
 *
 * To make things even faster in future versions, we could not only 
 * test if the strings contain tokens, but instead what kind of
 * expansions need to be done on strings. In the execution code we
 * could then use these flags for a specialized version of prefork()
 * to avoid a lot of string parsing and some more string duplication.
 */

/**/
int eclen, ecused, ecnpats;
/**/
Wordcode ecbuf;
/**/
Eccstr ecstrs;
/**/
int ecsoffs, ecssub, ecnfunc;

#define EC_INIT_SIZE         256
#define EC_DOUBLE_THRESHOLD  32768
#define EC_INCREMENT         1024

/* save parse context */

/**/
void
parse_context_save(struct parse_stack *ps, int toplevel)
{
    (void)toplevel;

    ps->incmdpos = incmdpos;
    ps->aliasspaceflag = aliasspaceflag;
    ps->incond = incond;
    ps->inredir = inredir;
    ps->incasepat = incasepat;
    ps->isnewlin = isnewlin;
    ps->infor = infor;
    ps->inrepeat_ = inrepeat_;
    ps->intypeset = intypeset;

    ps->hdocs = hdocs;
    ps->eclen = eclen;
    ps->ecused = ecused;
    ps->ecnpats = ecnpats;
    ps->ecbuf = ecbuf;
    ps->ecstrs = ecstrs;
    ps->ecsoffs = ecsoffs;
    ps->ecssub = ecssub;
    ps->ecnfunc = ecnfunc;
    ecbuf = NULL;
    hdocs = NULL;
}

/* restore parse context */

/**/
void
parse_context_restore(const struct parse_stack *ps, int toplevel)
{
    (void)toplevel;

    if (ecbuf)
	zfree(ecbuf, eclen);

    incmdpos = ps->incmdpos;
    aliasspaceflag = ps->aliasspaceflag;
    incond = ps->incond;
    inredir = ps->inredir;
    incasepat = ps->incasepat;
    isnewlin = ps->isnewlin;
    infor = ps->infor;
    inrepeat_ = ps->inrepeat_;
    intypeset = ps->intypeset;

    hdocs = ps->hdocs;
    eclen = ps->eclen;
    ecused = ps->ecused;
    ecnpats = ps->ecnpats;
    ecbuf = ps->ecbuf;
    ecstrs = ps->ecstrs;
    ecsoffs = ps->ecsoffs;
    ecssub = ps->ecssub;
    ecnfunc = ps->ecnfunc;

    errflag &= ~ERRFLAG_ERROR;
}

/* Adjust pointers in here-doc structs. */

static void
ecadjusthere(int p, int d)
{
    struct heredocs *h;

    for (h = hdocs; h; h = h->next)
	if (h->pc >= p)
	    h->pc += d;
}

/* Insert n free code-slots at position p. */

static void
ecispace(int p, int n)
{
    int m;

    if ((eclen - ecused) < n) {
	int a = (eclen < EC_DOUBLE_THRESHOLD ? eclen : EC_INCREMENT);

	if (n > a) a = n;

	ecbuf = (Wordcode) zrealloc((char *) ecbuf, (eclen + a) * sizeof(wordcode));
	eclen += a;
    }
    if ((m = ecused - p) > 0)
	memmove(ecbuf + p + n, ecbuf + p, m * sizeof(wordcode));
    ecused += n;
    ecadjusthere(p, n);
}

/* Add one wordcode. */

static int
ecadd(wordcode c)
{
    if ((eclen - ecused) < 1) {
	int a = (eclen < EC_DOUBLE_THRESHOLD ? eclen : EC_INCREMENT);

	ecbuf = (Wordcode) zrealloc((char *) ecbuf, (eclen + a) * sizeof(wordcode));
	eclen += a;
    }
    ecbuf[ecused] = c;

    return ecused++;
}

/* Delete a wordcode. */

static void
ecdel(int p)
{
    int n = ecused - p - 1;

    if (n > 0)
	memmove(ecbuf + p, ecbuf + p + 1, n * sizeof(wordcode));
    ecused--;
    ecadjusthere(p, -1);
}

/* Build the wordcode for a string. */

static wordcode
ecstrcode(char *s)
{
    int l, t;

    unsigned val = hasher(s);

    if ((l = strlen(s) + 1) && l <= 4) {
	t = has_token(s);
	wordcode c = (t ? 3 : 2);
	switch (l) {
	case 4: c |= ((wordcode) STOUC(s[2])) << 19;
	case 3: c |= ((wordcode) STOUC(s[1])) << 11;
	case 2: c |= ((wordcode) STOUC(s[0])) <<  3; break;
	case 1: c = (t ? 7 : 6); break;
	}
	return c;
    } else {
	Eccstr p, *pp;
	int cmp;

	for (pp = &ecstrs; (p = *pp); ) {
	    if (!(cmp = p->nfunc - ecnfunc) && !(cmp = (((signed)p->hashval) - ((signed)val))) && !(cmp = strcmp(p->str, s))) {
		return p->offs;
            }
	    pp = (cmp < 0 ? &(p->left) : &(p->right));
	}

        t = has_token(s);

	p = *pp = (Eccstr) zhalloc(sizeof(*p));
	p->left = p->right = 0;
	p->offs = ((ecsoffs - ecssub) << 2) | (t ? 1 : 0);
	p->aoffs = ecsoffs;
	p->str = s;
	p->nfunc = ecnfunc;
        p->hashval = val;
	ecsoffs += l;

	return p->offs;
    }
}

#define ecstr(S) ecadd(ecstrcode(S))

#define par_save_list(C) \
    do { \
        int eu = ecused; \
        par_list(C); \
        if (eu == ecused) ecadd(WCB_END()); \
    } while (0)
#define par_save_list1(C) \
    do { \
        int eu = ecused; \
        par_list1(C); \
        if (eu == ecused) ecadd(WCB_END()); \
    } while (0)


/**/
mod_export void
init_parse_status(void)
{
    /*
     * These variables are currently declared by the parser, so we
     * initialise them here.  Possibly they are more naturally declared
     * by the lexical anaylser; however, as they are used for signalling
     * between the two it's a bit ambiguous.  We clear them when
     * using the lexical analyser for strings as well as here.
     */
    incasepat = incond = inredir = infor = intypeset = 0;
    inrepeat_ = 0;
    incmdpos = 1;
}

/* Initialise wordcode buffer. */

/**/
void
init_parse(void)
{
    queue_signals();

    if (ecbuf) zfree(ecbuf, eclen);

    ecbuf = (Wordcode) zalloc((eclen = EC_INIT_SIZE) * sizeof(wordcode));
    ecused = 0;
    ecstrs = NULL;
    ecsoffs = ecnpats = 0;
    ecssub = 0;
    ecnfunc = 0;

    init_parse_status();

    unqueue_signals();
}

/* Build eprog. */

/* careful: copy_ecstr is from arg1 to arg2, unlike memcpy */

static void
copy_ecstr(Eccstr s, char *p)
{
    while (s) {
	memcpy(p + s->aoffs, s->str, strlen(s->str) + 1);
	copy_ecstr(s->left, p);
	s = s->right;
    }
}

static Eprog
bld_eprog(int heap)
{
    Eprog ret;
    int l;

    queue_signals();

    ecadd(WCB_END());

    ret = heap ? (Eprog) zhalloc(sizeof(*ret)) : (Eprog) zalloc(sizeof(*ret));
    ret->len = ((ecnpats * sizeof(Patprog)) +
		(ecused * sizeof(wordcode)) +
		ecsoffs);
    ret->npats = ecnpats;
    ret->nref = heap ? -1 : 1;
    ret->pats = heap ? (Patprog *) zhalloc(ret->len) :
	(Patprog *) zshcalloc(ret->len);
    ret->prog = (Wordcode) (ret->pats + ecnpats);
    ret->strs = (char *) (ret->prog + ecused);
    ret->shf = NULL;
    ret->flags = heap ? EF_HEAP : EF_REAL;
    ret->dump = NULL;
    for (l = 0; l < ecnpats; l++)
	ret->pats[l] = dummy_patprog1;
    memcpy(ret->prog, ecbuf, ecused * sizeof(wordcode));
    copy_ecstr(ecstrs, ret->strs);

    zfree(ecbuf, eclen);
    ecbuf = NULL;

    unqueue_signals();

    return ret;
}

/**/
mod_export int
empty_eprog(Eprog p)
{
    return (!p || !p->prog || *p->prog == WCB_END());
}

static void
clear_hdocs(void)
{
    struct heredocs *p, *n;

    for (p = hdocs; p; p = n) {
        n = p->next;
        zfree(p, sizeof(struct heredocs));
    }
    hdocs = NULL;
}

/*
 * event	: ENDINPUT
 *			| SEPER
 *			| sublist [ SEPER | AMPER | AMPERBANG ]
 *
 * cmdsubst indicates our event is part of a command-style
 * substitution terminated by the token indicationg, usual closing
 * parenthesis.  In other cases endtok is ENDINPUT.
 */

/**/
Eprog
parse_event(int endtok)
{
    tok = ENDINPUT;
    incmdpos = 1;
    aliasspaceflag = 0;
    zshlex();
    init_parse();

    if (!par_event(endtok)) {
        clear_hdocs();
        return NULL;
    }
    if (endtok != ENDINPUT) {
	/* don't need to build an eprog for this */
	return &dummy_eprog;
    }
    return bld_eprog(1);
}

/**/
int
par_event(int endtok)
{
    int r = 0, p, c = 0;

    while (tok == SEPER) {
	if (isnewlin > 0 && endtok == ENDINPUT)
	    return 0;
	zshlex();
    }
    if (tok == ENDINPUT)
	return 0;
    if (tok == endtok)
	return 1;

    p = ecadd(0);

    if (par_sublist(&c)) {
	if (tok == ENDINPUT || tok == endtok) {
	    set_list_code(p, Z_SYNC, c);
	    r = 1;
	} else if (tok == SEPER) {
	    set_list_code(p, Z_SYNC, c);
	    if (isnewlin <= 0 || endtok != ENDINPUT)
		zshlex();
	    r = 1;
	} else if (tok == AMPER) {
	    set_list_code(p, Z_ASYNC, c);
	    zshlex();
	    r = 1;
	} else if (tok == AMPERBANG) {
	    set_list_code(p, (Z_ASYNC | Z_DISOWN), c);
	    zshlex();
	    r = 1;
	}
    }
    if (!r) {
	tok = LEXERR;
	if (errflag) {
	    yyerror(0);
	    ecused--;
	    return 0;
	}
	yyerror(1);
	herrflush();
	if (noerrs != 2)
	    errflag |= ERRFLAG_ERROR;
	ecused--;
	return 0;
    } else {
	int oec = ecused;

	if (!par_event(endtok)) {
	    ecused = oec;
	    ecbuf[p] |= wc_bdata(Z_END);
	    return errflag ? 0 : 1;
	}
    }
    return 1;
}

/**/
mod_export Eprog
parse_list(void)
{
    int c = 0;

    tok = ENDINPUT;
    init_parse();
    zshlex();
    par_list(&c);
    if (tok != ENDINPUT) {
        clear_hdocs();
	tok = LEXERR;
	yyerror(0);
	return NULL;
    }
    return bld_eprog(1);
}

/*
 * This entry point is only used for bin_test, our attempt to
 * provide compatibility with /bin/[ and /bin/test.  Hence
 * at this point condlex should always be set to testlex.
 */

/**/
mod_export Eprog
parse_cond(void)
{
    init_parse();

    if (!par_cond()) {
        clear_hdocs();
	return NULL;
    }
    return bld_eprog(1);
}

/* This adds a list wordcode. The important bit about this is that it also
 * tries to optimise this to a Z_SIMPLE list code. */

/**/
static void
set_list_code(int p, int type, int cmplx)
{
    if (!cmplx && (type == Z_SYNC || type == (Z_SYNC | Z_END)) &&
	WC_SUBLIST_TYPE(ecbuf[p + 1]) == WC_SUBLIST_END) {
	int ispipe = !(WC_SUBLIST_FLAGS(ecbuf[p + 1]) & WC_SUBLIST_SIMPLE);
	ecbuf[p] = WCB_LIST((type | Z_SIMPLE), ecused - 2 - p);
	ecdel(p + 1);
	if (ispipe)
	    ecbuf[p + 1] = WC_PIPE_LINENO(ecbuf[p + 1]);
    } else
	ecbuf[p] = WCB_LIST(type, 0);
}

/* The same for sublists. */

/**/
static void
set_sublist_code(int p, int type, int flags, int skip, int cmplx)
{
    if (cmplx)
	ecbuf[p] = WCB_SUBLIST(type, flags, skip);
    else {
	ecbuf[p] = WCB_SUBLIST(type, (flags | WC_SUBLIST_SIMPLE), skip);
	ecbuf[p + 1] = WC_PIPE_LINENO(ecbuf[p + 1]);
    }
}

/*
 * list	: { SEPER } [ sublist [ { SEPER | AMPER | AMPERBANG } list ] ]
 */

/**/
static void
par_list(int *cmplx)
{
    int p, lp = -1, c;

 rec:

    while (tok == SEPER)
	zshlex();

    p = ecadd(0);
    c = 0;

    if (par_sublist(&c)) {
	*cmplx |= c;
	if (tok == SEPER || tok == AMPER || tok == AMPERBANG) {
	    if (tok != SEPER)
		*cmplx = 1;
	    set_list_code(p, ((tok == SEPER) ? Z_SYNC :
			      (tok == AMPER) ? Z_ASYNC :
			      (Z_ASYNC | Z_DISOWN)), c);
	    incmdpos = 1;
	    do {
		zshlex();
	    } while (tok == SEPER);
	    lp = p;
	    goto rec;
	} else
	    set_list_code(p, (Z_SYNC | Z_END), c);
    } else {
	ecused--;
	if (lp >= 0)
	    ecbuf[lp] |= wc_bdata(Z_END);
    }
}

/**/
static void
par_list1(int *cmplx)
{
    int p = ecadd(0), c = 0;

    if (par_sublist(&c)) {
	set_list_code(p, (Z_SYNC | Z_END), c);
	*cmplx |= c;
    } else
	ecused--;
}

/*
 * sublist	: sublist2 [ ( DBAR | DAMPER ) { SEPER } sublist ]
 */

/**/
static int
par_sublist(int *cmplx)
{
    int f, p, c = 0;

    p = ecadd(0);

    if ((f = par_sublist2(&c)) != -1) {
	int e = ecused;

	*cmplx |= c;
	if (tok == DBAR || tok == DAMPER) {
	    enum lextok qtok = tok;
	    int sl;

	    cmdpush(tok == DBAR ? CS_CMDOR : CS_CMDAND);
	    zshlex();
	    while (tok == SEPER)
		zshlex();
	    sl = par_sublist(cmplx);
	    set_sublist_code(p, (sl ? (qtok == DBAR ?
				       WC_SUBLIST_OR : WC_SUBLIST_AND) :
				 WC_SUBLIST_END),
			     f, (e - 1 - p), c);
	    cmdpop();
	} else {
	    if (tok == AMPER || tok == AMPERBANG) {
		c = 1;
		*cmplx |= c;
	    }		
	    set_sublist_code(p, WC_SUBLIST_END, f, (e - 1 - p), c);
	}
	return 1;
    } else {
	ecused--;
	return 0;
    }
}

/*
 * sublist2	: [ COPROC | BANG ] pline
 */

/**/
static int
par_sublist2(int *cmplx)
{
    int f = 0;

    if (tok == COPROC) {
	*cmplx = 1;
	f |= WC_SUBLIST_COPROC;
	zshlex();
    } else if (tok == BANG) {
	*cmplx = 1;
	f |= WC_SUBLIST_NOT;
	zshlex();
    }
    if (!par_pline(cmplx) && !f)
	return -1;

    return f;
}

/*
 * pline	: cmd [ ( BAR | BARAMP ) { SEPER } pline ]
 */

/**/
static int
par_pline(int *cmplx)
{
    int p;
    zlong line = toklineno;

    p = ecadd(0);

    if (!par_cmd(cmplx, 0)) {
	ecused--;
	return 0;
    }
    if (tok == BAR) {
	*cmplx = 1;
	cmdpush(CS_PIPE);
	zshlex();
	while (tok == SEPER)
	    zshlex();
	ecbuf[p] = WCB_PIPE(WC_PIPE_MID, (line >= 0 ? line + 1 : 0));
	ecispace(p + 1, 1);
	ecbuf[p + 1] = ecused - 1 - p;
	if (!par_pline(cmplx)) {
	    tok = LEXERR;
	}
	cmdpop();
	return 1;
    } else if (tok == BARAMP) {
	int r;

	for (r = p + 1; wc_code(ecbuf[r]) == WC_REDIR;
	     r += WC_REDIR_WORDS(ecbuf[r]));

	ecispace(r, 3);
	ecbuf[r] = WCB_REDIR(REDIR_MERGEOUT);
	ecbuf[r + 1] = 2;
	ecbuf[r + 2] = ecstrcode("1");

	*cmplx = 1;
	cmdpush(CS_ERRPIPE);
	zshlex();
	while (tok == SEPER)
	    zshlex();
	ecbuf[p] = WCB_PIPE(WC_PIPE_MID, (line >= 0 ? line + 1 : 0));
	ecispace(p + 1, 1);
	ecbuf[p + 1] = ecused - 1 - p;
	if (!par_pline(cmplx)) {
	    tok = LEXERR;
	}
	cmdpop();
	return 1;
    } else {
	ecbuf[p] = WCB_PIPE(WC_PIPE_END, (line >= 0 ? line + 1 : 0));
	return 1;
    }
}

/*
 * cmd	: { redir } ( for | case | if | while | repeat |
 *				subsh | funcdef | time | dinbrack | dinpar | simple ) { redir }
 *
 * zsh_construct is passed through to par_subsh(), q.v.
 */

/**/
static int
par_cmd(int *cmplx, int zsh_construct)
{
    int r, nr = 0;

    r = ecused;

    if (IS_REDIROP(tok)) {
	*cmplx = 1;
	while (IS_REDIROP(tok)) {
	    nr += par_redir(&r, NULL);
	}
    }
    switch (tok) {
    case FOR:
	cmdpush(CS_FOR);
	par_for(cmplx);
	cmdpop();
	break;
    case FOREACH:
	cmdpush(CS_FOREACH);
	par_for(cmplx);
	cmdpop();
	break;
    case SELECT:
	*cmplx = 1;
	cmdpush(CS_SELECT);
	par_for(cmplx);
	cmdpop();
	break;
    case CASE:
	cmdpush(CS_CASE);
	par_case(cmplx);
	cmdpop();
	break;
    case IF:
	par_if(cmplx);
	break;
    case WHILE:
	cmdpush(CS_WHILE);
	par_while(cmplx);
	cmdpop();
	break;
    case UNTIL:
	cmdpush(CS_UNTIL);
	par_while(cmplx);
	cmdpop();
	break;
    case REPEAT:
	cmdpush(CS_REPEAT);
	par_repeat(cmplx);
	cmdpop();
	break;
    case INPAR:
	*cmplx = 1;
	cmdpush(CS_SUBSH);
	par_subsh(cmplx, zsh_construct);
	cmdpop();
	break;
    case INBRACE:
	cmdpush(CS_CURSH);
	par_subsh(cmplx, zsh_construct);
	cmdpop();
	break;
    case FUNC:
	cmdpush(CS_FUNCDEF);
	par_funcdef(cmplx);
	cmdpop();
	break;
    case DINBRACK:
	cmdpush(CS_COND);
	par_dinbrack();
	cmdpop();
	break;
    case DINPAR:
	ecadd(WCB_ARITH());
	ecstr(tokstr);
	zshlex();
	break;
    case TIME:
	{
	    static int inpartime = 0;

	    if (!inpartime) {
		*cmplx = 1;
		inpartime = 1;
		par_time();
		inpartime = 0;
		break;
	    }
	}
	tok = STRING;
	/* fall through */
    default:
	{
	    int sr;

	    if (!(sr = par_simple(cmplx, nr))) {
		if (!nr)
		    return 0;
	    } else {
		/* Take account of redirections */
		if (sr > 1) {
		    *cmplx = 1;
		    r += sr - 1;
		}
	    }
	}
	break;
    }
    if (IS_REDIROP(tok)) {
	*cmplx = 1;
	while (IS_REDIROP(tok))
	    (void)par_redir(&r, NULL);
    }
    incmdpos = 1;
    incasepat = 0;
    incond = 0;
    intypeset = 0;
    return 1;
}

/*
 * 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(int *cmplx)
{
    int oecused = ecused, csh = (tok == FOREACH), p, sel = (tok == SELECT);
    int type;

    p = ecadd(0);

    incmdpos = 0;
    infor = tok == FOR ? 2 : 0;
    zshlex();
    if (tok == DINPAR) {
	zshlex();
	if (tok != DINPAR)
	    YYERRORV(oecused);
	ecstr(tokstr);
	zshlex();
	if (tok != DINPAR)
	    YYERRORV(oecused);
	ecstr(tokstr);
	zshlex();
	if (tok != DOUTPAR)
	    YYERRORV(oecused);
	ecstr(tokstr);
	infor = 0;
	incmdpos = 1;
	zshlex();
	type = WC_FOR_COND;
    } else {
	int np = 0, n, posix_in, ona = noaliases, onc = nocorrect;
	infor = 0;
	if (tok != STRING || !isident(tokstr))
	    YYERRORV(oecused);
	if (!sel)
	    np = ecadd(0);
	n = 0;
	incmdpos = 1;
	noaliases = nocorrect = 1;
	for (;;) {
	    n++;
	    ecstr(tokstr);
	    zshlex();
	    if (tok != STRING || !strcmp(tokstr, "in") || sel)
		break;
	    if (!isident(tokstr) || errflag)
	    {
		noaliases = ona;
		nocorrect = onc;
		YYERRORV(oecused);
	    }
	}
	noaliases = ona;
	nocorrect = onc;
	if (!sel)
	    ecbuf[np] = n;
	posix_in = isnewlin;
	while (isnewlin)
	    zshlex();
        if (tok == STRING && !strcmp(tokstr, "in")) {
	    incmdpos = 0;
	    zshlex();
	    np = ecadd(0);
	    n = par_wordlist();
	    if (tok != SEPER)
		YYERRORV(oecused);
	    ecbuf[np] = n;
	    type = (sel ? WC_SELECT_LIST : WC_FOR_LIST);
	} else if (!posix_in && tok == INPAR) {
	    incmdpos = 0;
	    zshlex();
	    np = ecadd(0);
	    n = par_nl_wordlist();
	    if (tok != OUTPAR)
		YYERRORV(oecused);
	    ecbuf[np] = n;
	    incmdpos = 1;
	    zshlex();
	    type = (sel ? WC_SELECT_LIST : WC_FOR_LIST);
	} else
	    type = (sel ? WC_SELECT_PPARAM : WC_FOR_PPARAM);
    }
    incmdpos = 1;
    while (tok == SEPER)
	zshlex();
    if (tok == DOLOOP) {
	zshlex();
	par_save_list(cmplx);
	if (tok != DONE)
	    YYERRORV(oecused);
	incmdpos = 0;
	zshlex();
    } else if (tok == INBRACE) {
	zshlex();
	par_save_list(cmplx);
	if (tok != OUTBRACE)
	    YYERRORV(oecused);
	incmdpos = 0;
	zshlex();
    } else if (csh || isset(CSHJUNKIELOOPS)) {
	par_save_list(cmplx);
	if (tok != ZEND)
	    YYERRORV(oecused);
	incmdpos = 0;
	zshlex();
    } else if (unset(SHORTLOOPS)) {
	YYERRORV(oecused);
    } else
	par_save_list1(cmplx);

    ecbuf[p] = (sel ?
		WCB_SELECT(type, ecused - 1 - p) :
		WCB_FOR(type, ecused - 1 - p));
}

/*
 * case	: CASE STRING { SEPER } ( "in" | INBRACE )
				{ { SEPER } STRING { BAR STRING } OUTPAR
					list [ DSEMI | SEMIAMP | SEMIBAR ] }
				{ SEPER } ( "esac" | OUTBRACE )
 */

/**/
static void
par_case(int *cmplx)
{
    int oecused = ecused, brflag, p, pp, palts, type, nalts;
    int ona, onc;

    p = ecadd(0);

    incmdpos = 0;
    zshlex();
    if (tok != STRING)
	YYERRORV(oecused);
    ecstr(tokstr);

    incmdpos = 1;
    ona = noaliases;
    onc = nocorrect;
    noaliases = nocorrect = 1;
    zshlex();
    while (tok == SEPER)
	zshlex();
    if (!(tok == STRING && !strcmp(tokstr, "in")) && tok != INBRACE)
    {
	noaliases = ona;
	nocorrect = onc;
	YYERRORV(oecused);
    }
    brflag = (tok == INBRACE);
    incasepat = 1;
    incmdpos = 0;
    noaliases = ona;
    nocorrect = onc;
    zshlex();

    for (;;) {
	char *str;
	int skip_zshlex;

	while (tok == SEPER)
	    zshlex();
	if (tok == OUTBRACE)
	    break;
	if (tok == INPAR)
	    zshlex();
	if (tok == BAR) {
	    str = dupstring("");
	    skip_zshlex = 1;
	} else {
	    if (tok != STRING)
		YYERRORV(oecused);
	    if (!strcmp(tokstr, "esac"))
		break;
	    str = dupstring(tokstr);
	    skip_zshlex = 0;
	}
	type = WC_CASE_OR;
	pp = ecadd(0);
	palts = ecadd(0);
	nalts = 0;
	/*
	 * Hack here.
	 *
	 * [Pause for astonished hubbub to subside.]
	 *
	 * The next token we get may be
	 * - ")" or "|" if we're looking at an honest-to-god
	 *   "case" pattern, either because there's no opening
	 *   parenthesis, or because SH_GLOB is set and we
	 *   managed to grab an initial "(" to mark the start
	 *   of the case pattern.
	 * - Something else --- we don't care what --- because
	 *   we're parsing a complete "(...)" as a complete
	 *   zsh pattern.  In that case, we treat this as a
	 *   single instance of a case pattern but we pretend
	 *   we're doing proper case parsing --- in which the
	 *   parentheses and bar are in different words from
	 *   the string, so may be separated by whitespace.
	 *   So we quietly massage the whitespace and hope
	 *   no one noticed.  This is horrible, but it's
	 *   unfortunately too difficult to combine traditional
	 *   zsh patterns with a properly parsed case pattern
	 *   without generating incompatibilities which aren't
	 *   all that popular (I've discovered).
	 * - We can also end up with something other than ")" or "|"
	 *   just because we're looking at garbage.
	 *
	 * Because of the second case, what happens next might
	 * be the start of the command after the pattern, so we
	 * need to treat it as in command position.  Luckily
	 * this doesn't affect our ability to match a | or ) as
	 * these are valid on command lines.
	 */
	incasepat = -1;
	incmdpos = 1;
	if (!skip_zshlex)
	    zshlex();
	for (;;) {
	    if (tok == OUTPAR) {
		ecstr(str);
		ecadd(ecnpats++);
		nalts++;

		incasepat = 0;
		incmdpos = 1;
		zshlex();
		break;
	    } else if (tok == BAR) {
		ecstr(str);
		ecadd(ecnpats++);
		nalts++;

		incasepat = 1;
		incmdpos = 0;
	    } else {
		if (!nalts && str[0] == Inpar) {
		    int pct = 0, sl;
		    char *s;

		    for (s = str; *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 + 1 || s[-2] != Meta))
				    chuck(--s);
			}
			if (*s == Outpar)
			    pct--;
		    }
		    if (*s || pct || s == str)
			YYERRORV(oecused);
		    /* Simplify pattern by removing surrounding (...) */
		    sl = strlen(str);
		    DPUTS(*str != Inpar || str[sl - 1] != Outpar,
			  "BUG: strange case pattern");
		    str[sl - 1] = '\0';
		    chuck(str);
		    ecstr(str);
		    ecadd(ecnpats++);
		    nalts++;
		    break;
		}
		YYERRORV(oecused);
	    }

	    zshlex();
	    switch (tok) {
	    case STRING:
		/* Normal case */
		str = dupstring(tokstr);
		zshlex();
		break;

	    case OUTPAR:
	    case BAR:
		/* Empty string */
		str = dupstring("");
		break;

	    default:
		/* Oops. */
		YYERRORV(oecused);
		break;
	    }
	}
	incasepat = 0;
	par_save_list(cmplx);
	if (tok == SEMIAMP)
	    type = WC_CASE_AND;
	else if (tok == SEMIBAR)
	    type = WC_CASE_TESTAND;
	ecbuf[pp] = WCB_CASE(type, ecused - 1 - pp);
	ecbuf[palts] = nalts;
	if ((tok == ESAC && !brflag) || (tok == OUTBRACE && brflag))
	    break;
	if (tok != DSEMI && tok != SEMIAMP && tok != SEMIBAR)
	    YYERRORV(oecused);
	incasepat = 1;
	incmdpos = 0;
	zshlex();
    }
    incmdpos = 1;
    incasepat = 0;
    zshlex();

    ecbuf[p] = WCB_CASE(WC_CASE_HEAD, ecused - 1 - p);
}

/*
 * 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(int *cmplx)
{
    int oecused = ecused, p, pp, type, usebrace = 0;
    enum lextok xtok;
    unsigned char nc;

    p = ecadd(0);

    for (;;) {
	xtok = tok;
	cmdpush(xtok == IF ? CS_IF : CS_ELIF);
	if (xtok == FI) {
	    incmdpos = 0;
	    zshlex();
	    break;
	}
	zshlex();
	if (xtok == ELSE)
	    break;
	while (tok == SEPER)
	    zshlex();
	if (!(xtok == IF || xtok == ELIF)) {
	    cmdpop();
	    YYERRORV(oecused);
	}
	pp = ecadd(0);
	type = (xtok == IF ? WC_IF_IF : WC_IF_ELIF);
	par_save_list(cmplx);
	incmdpos = 1;
	if (tok == ENDINPUT) {
	    cmdpop();
	    YYERRORV(oecused);
	}
	while (tok == SEPER)
	    zshlex();
	xtok = FI;
	nc = cmdstack[cmdsp - 1] == CS_IF ? CS_IFTHEN : CS_ELIFTHEN;
	if (tok == THEN) {
	    usebrace = 0;
	    cmdpop();
	    cmdpush(nc);
	    zshlex();
	    par_save_list(cmplx);
	    ecbuf[pp] = WCB_IF(type, ecused - 1 - pp);
	    incmdpos = 1;
	    cmdpop();
	} else if (tok == INBRACE) {
	    usebrace = 1;
	    cmdpop();
	    cmdpush(nc);
	    zshlex();
	    par_save_list(cmplx);
	    if (tok != OUTBRACE) {
		cmdpop();
		YYERRORV(oecused);
	    }
	    ecbuf[pp] = WCB_IF(type, ecused - 1 - pp);
	    /* command word (else) allowed to follow immediately */
	    zshlex();
	    incmdpos = 1;
	    if (tok == SEPER)
		break;
	    cmdpop();
	} else if (unset(SHORTLOOPS)) {
	    cmdpop();
	    YYERRORV(oecused);
	} else {
	    cmdpop();
	    cmdpush(nc);
	    par_save_list1(cmplx);
	    ecbuf[pp] = WCB_IF(type, ecused - 1 - pp);
	    incmdpos = 1;
	    break;
	}
    }
    cmdpop();
    if (xtok == ELSE || tok == ELSE) {
	pp = ecadd(0);
	cmdpush(CS_ELSE);
	while (tok == SEPER)
	    zshlex();
	if (tok == INBRACE && usebrace) {
	    zshlex();
	    par_save_list(cmplx);
	    if (tok != OUTBRACE) {
		cmdpop();
		YYERRORV(oecused);
	    }
	} else {
	    par_save_list(cmplx);
	    if (tok != FI) {
		cmdpop();
		YYERRORV(oecused);
	    }
	}
	incmdpos = 0;
	ecbuf[pp] = WCB_IF(WC_IF_ELSE, ecused - 1 - pp);
	zshlex();
	cmdpop();
    }
    ecbuf[p] = WCB_IF(WC_IF_HEAD, ecused - 1 - p);
}

/*
 * while	: ( WHILE | UNTIL ) ( INPAR list OUTPAR | list ) { SEPER }
				( DO list DONE | INBRACE list OUTBRACE | list ZEND )
 */

/**/
static void
par_while(int *cmplx)
{
    int oecused = ecused, p;
    int type = (tok == UNTIL ? WC_WHILE_UNTIL : WC_WHILE_WHILE);

    p = ecadd(0);
    zshlex();
    par_save_list(cmplx);
    incmdpos = 1;
    while (tok == SEPER)
	zshlex();
    if (tok == DOLOOP) {
	zshlex();
	par_save_list(cmplx);
	if (tok != DONE)
	    YYERRORV(oecused);
	incmdpos = 0;
	zshlex();
    } else if (tok == INBRACE) {
	zshlex();
	par_save_list(cmplx);
	if (tok != OUTBRACE)
	    YYERRORV(oecused);
	incmdpos = 0;
	zshlex();
    } else if (isset(CSHJUNKIELOOPS)) {
	par_save_list(cmplx);
	if (tok != ZEND)
	    YYERRORV(oecused);
	zshlex();
    } else
	YYERRORV(oecused);

    ecbuf[p] = WCB_WHILE(type, ecused - 1 - p);
}

/*
 * repeat	: REPEAT STRING { SEPER } ( DO list DONE | list1 )
 */

/**/
static void
par_repeat(int *cmplx)
{
    /* ### what to do about inrepeat_ here? */
    int oecused = ecused, p;

    p = ecadd(0);

    incmdpos = 0;
    zshlex();
    if (tok != STRING)
	YYERRORV(oecused);
    ecstr(tokstr);
    incmdpos = 1;
    zshlex();
    while (tok == SEPER)
	zshlex();
    if (tok == DOLOOP) {
	zshlex();
	par_save_list(cmplx);
	if (tok != DONE)
	    YYERRORV(oecused);
	incmdpos = 0;
	zshlex();
    } else if (tok == INBRACE) {
	zshlex();
	par_save_list(cmplx);
	if (tok != OUTBRACE)
	    YYERRORV(oecused);
	incmdpos = 0;
	zshlex();
    } else if (isset(CSHJUNKIELOOPS)) {
	par_save_list(cmplx);
	if (tok != ZEND)
	    YYERRORV(oecused);
	zshlex();
    } else if (unset(SHORTLOOPS)) {
	YYERRORV(oecused);
    } else
	par_save_list1(cmplx);

    ecbuf[p] = WCB_REPEAT(ecused - 1 - p);
}

/*
 * subsh	: INPAR list OUTPAR |
 *                INBRACE list OUTBRACE [ "always" INBRACE list OUTBRACE ]
 *
 * With zsh_construct non-zero, we're doing a zsh special in which
 * the following token is not considered in command position.  This
 * is used for arguments of anonymous functions.
 */

/**/
static void
par_subsh(int *cmplx, int zsh_construct)
{
    enum lextok otok = tok;
    int oecused = ecused, p, pp;

    p = ecadd(0);
    /* Extra word only needed for always block */
    pp = ecadd(0);
    zshlex();
    par_list(cmplx);
    ecadd(WCB_END());
    if (tok != ((otok == INPAR) ? OUTPAR : OUTBRACE))
	YYERRORV(oecused);
    incmdpos = !zsh_construct;
    zshlex();

    /* Optional always block.  No intervening SEPERs allowed. */
    if (otok == INBRACE && tok == STRING && !strcmp(tokstr, "always")) {
	ecbuf[pp] = WCB_TRY(ecused - 1 - pp);
	incmdpos = 1;
	do {
	    zshlex();
	} while (tok == SEPER);

	if (tok != INBRACE)
	    YYERRORV(oecused);
	cmdpop();
	cmdpush(CS_ALWAYS);

	zshlex();
	par_save_list(cmplx);
	while (tok == SEPER)
	    zshlex();

	incmdpos = 1;

	if (tok != OUTBRACE)
	    YYERRORV(oecused);
	zshlex();
	ecbuf[p] = WCB_TRY(ecused - 1 - p);
    } else {
	ecbuf[p] = (otok == INPAR ? WCB_SUBSH(ecused - 1 - p) :
		    WCB_CURSH(ecused - 1 - p));
    }
}

/*
 * funcdef	: FUNCTION wordlist [ INOUTPAR ] { SEPER }
 *					( list1 | INBRACE list OUTBRACE )
 */

/**/
static void
par_funcdef(int *cmplx)
{
    int oecused = ecused, num = 0, onp, p, c = 0;
    int so, oecssub = ecssub;
    zlong oldlineno = lineno;

    lineno = 0;
    nocorrect = 1;
    incmdpos = 0;
    zshlex();

    p = ecadd(0);
    ecadd(0);

    while (tok == STRING) {
	if ((*tokstr == Inbrace || *tokstr == '{') &&
	    !tokstr[1]) {
	    tok = INBRACE;
	    break;
	}
	ecstr(tokstr);
	num++;
	zshlex();
    }
    ecadd(0);
    ecadd(0);
    ecadd(0);

    nocorrect = 0;
    incmdpos = 1;
    if (tok == INOUTPAR)
	zshlex();
    while (tok == SEPER)
	zshlex();

    ecnfunc++;
    ecssub = so = ecsoffs;
    onp = ecnpats;
    ecnpats = 0;

    if (tok == INBRACE) {
	zshlex();
	par_list(&c);
	if (tok != OUTBRACE) {
	    lineno += oldlineno;
	    ecnpats = onp;
	    ecssub = oecssub;
	    YYERRORV(oecused);
	}
	if (num == 0) {
	    /* Anonymous function, possibly with arguments */
	    incmdpos = 0;
	}
	zshlex();
    } else if (unset(SHORTLOOPS)) {
	lineno += oldlineno;
	ecnpats = onp;
	ecssub = oecssub;
	YYERRORV(oecused);
    } else
	par_list1(&c);

    ecadd(WCB_END());
    ecbuf[p + num + 2] = so - oecssub;
    ecbuf[p + num + 3] = ecsoffs - so;
    ecbuf[p + num + 4] = ecnpats;
    ecbuf[p + 1] = num;

    ecnpats = onp;
    ecssub = oecssub;
    ecnfunc++;

    ecbuf[p] = WCB_FUNCDEF(ecused - 1 - p);

    if (num == 0) {
	/* Unnamed function */
	int parg = ecadd(0);
	ecadd(0);
	while (tok == STRING) {
	    ecstr(tokstr);
	    num++;
	    zshlex();
	}
	if (num > 0)
	    *cmplx = 1;
	ecbuf[parg] = ecused - parg; /*?*/
	ecbuf[parg+1] = num;
    }
    lineno += oldlineno;
}

/*
 * time	: TIME sublist2
 */

/**/
static void
par_time(void)
{
    int p, f, c = 0;

    zshlex();

    p = ecadd(0);
    ecadd(0);
    if ((f = par_sublist2(&c)) < 0) {
	ecused--;
	ecbuf[p] = WCB_TIMED(WC_TIMED_EMPTY);
    } else {
	ecbuf[p] = WCB_TIMED(WC_TIMED_PIPE);
	set_sublist_code(p + 1, WC_SUBLIST_END, f, ecused - 2 - p, c);
    }
}

/*
 * dinbrack	: DINBRACK cond DOUTBRACK
 */

/**/
static void
par_dinbrack(void)
{
    int oecused = ecused;

    incond = 1;
    incmdpos = 0;
    zshlex();
    par_cond();
    if (tok != DOUTBRACK)
	YYERRORV(oecused);
    incond = 0;
    incmdpos = 1;
    zshlex();
}

/*
 * simple	: { COMMAND | EXEC | NOGLOB | NOCORRECT | DASH }
					{ STRING | ENVSTRING | ENVARRAY wordlist OUTPAR | redir }
					[ INOUTPAR { SEPER } ( list1 | INBRACE list OUTBRACE ) ]
 *
 * Returns 0 if no code, else 1 plus the number of code words
 * used up by redirections.
 */

/**/
static int
par_simple(int *cmplx, int nr)
{
    int oecused = ecused, isnull = 1, r, argc = 0, p, isfunc = 0, sr = 0;
    int c = *cmplx, nrediradd, assignments = 0, ppost = 0, is_typeset = 0;
    char *hasalias = input_hasalias();
    wordcode postassigns = 0;

    r = ecused;
    for (;;) {
	if (tok == NOCORRECT) {
	    *cmplx = c = 1;
	    nocorrect = 1;
	} else if (tok == ENVSTRING) {
	    char *ptr, *name, *str;

	    name = tokstr;
	    for (ptr = tokstr;
		 *ptr && *ptr != Inbrack && *ptr != '=' && *ptr != '+';
	         ptr++);
	    if (*ptr == Inbrack) skipparens(Inbrack, Outbrack, &ptr);
	    if (*ptr == '+') {
	    	*ptr++ = '\0';
	    	ecadd(WCB_ASSIGN(WC_ASSIGN_SCALAR, WC_ASSIGN_INC, 0));
	    } else
		ecadd(WCB_ASSIGN(WC_ASSIGN_SCALAR, WC_ASSIGN_NEW, 0));

	    if (*ptr == '=') {
		*ptr = '\0';
		str = ptr + 1;
	    } else
		equalsplit(tokstr, &str);
	    for (ptr = str; *ptr; ptr++) {
		/*
		 * We can't treat this as "simple" if it contains
		 * expansions that require process subsitution, since then
		 * we need process handling.
		 */
		if (ptr[1] == Inpar &&
		    (*ptr == Equals || *ptr == Inang || *ptr == OutangProc)) {
		    *cmplx = 1;
		    break;
		}
	    }
	    ecstr(name);
	    ecstr(str);
	    isnull = 0;
	    assignments = 1;
	} else if (tok == ENVARRAY) {
	    int oldcmdpos = incmdpos, n, type2;

	    /*
	     * We consider array setting cmplx because it can
	     * contain process substitutions, which need a valid job.
	     */
	    *cmplx = c = 1;
	    p = ecadd(0);
	    incmdpos = 0;
	    if ((type2 = strlen(tokstr) - 1) && tokstr[type2] == '+') {
	    	tokstr[type2] = '\0';
		type2 = WC_ASSIGN_INC;
    	    } else
		type2 = WC_ASSIGN_NEW;
	    ecstr(tokstr);
	    cmdpush(CS_ARRAY);
	    zshlex();
	    n = par_nl_wordlist();
	    ecbuf[p] = WCB_ASSIGN(WC_ASSIGN_ARRAY, type2, n);
	    cmdpop();
	    if (tok != OUTPAR)
		YYERROR(oecused);
	    incmdpos = oldcmdpos;
	    isnull = 0;
	    assignments = 1;
	} else if (IS_REDIROP(tok)) {
	    *cmplx = c = 1;
	    nr += par_redir(&r, NULL);
	    continue;
	} else
	    break;
	zshlex();
	if (!hasalias)
	    hasalias = input_hasalias();
    }
    if (tok == AMPER || tok == AMPERBANG)
	YYERROR(oecused);

    p = ecadd(WCB_SIMPLE(0));

    for (;;) {
	if (tok == STRING || tok == TYPESET) {
	    int redir_var = 0;

	    *cmplx = 1;
	    incmdpos = 0;

	    if (tok == TYPESET)
		intypeset = is_typeset = 1;

	    if (!isset(IGNOREBRACES) && *tokstr == Inbrace)
	    {
		/* Look for redirs of the form {var}>file etc. */
		char *eptr = tokstr + strlen(tokstr) - 1;
		char *ptr = eptr;

		if (*ptr == Outbrace && ptr > tokstr + 1)
		{
		    if (itype_end(tokstr+1, IIDENT, 0) >= ptr)
		    {
			char *toksave = tokstr;
			char *idstring = dupstrpfx(tokstr+1, eptr-tokstr-1);
			redir_var = 1;
			zshlex();
			if (!hasalias)
			    hasalias = input_hasalias();

			if (IS_REDIROP(tok) && tokfd == -1)
			{
			    *cmplx = c = 1;
			    nrediradd = par_redir(&r, idstring);
			    p += nrediradd;
			    sr += nrediradd;
			}
			else
			{
			    ecstr(toksave);
			    argc++;
			}
		    }
		}
	    }

	    if (!redir_var)
	    {
		if (postassigns) {
		    /*
		     * We're in the variable part of a typeset,
		     * but this doesn't have an assignment.
		     * We'll parse it as if it does, but mark
		     * it specially with WC_ASSIGN_INC.
		     */
		    postassigns++;
		    ecadd(WCB_ASSIGN(WC_ASSIGN_SCALAR, WC_ASSIGN_INC, 0));
		    ecstr(tokstr);
		    ecstr("");	/* TBD can possibly optimise out */
		} else {
		    ecstr(tokstr);
		    argc++;
		}
		zshlex();
		if (!hasalias)
		    hasalias = input_hasalias();
	    }
	} else if (IS_REDIROP(tok)) {
	    *cmplx = c = 1;
	    nrediradd = par_redir(&r, NULL);
	    p += nrediradd;
	    if (ppost)
		ppost += nrediradd;
	    sr += nrediradd;
	} else if (tok == ENVSTRING) {
	    char *ptr, *name, *str;

	    if (!postassigns++)
		ppost = ecadd(0);

	    name = tokstr;
	    for (ptr = tokstr; *ptr && *ptr != Inbrack && *ptr != '=' && *ptr != '+';
	         ptr++);
	    if (*ptr == Inbrack) skipparens(Inbrack, Outbrack, &ptr);
	    ecadd(WCB_ASSIGN(WC_ASSIGN_SCALAR, WC_ASSIGN_NEW, 0));

	    if (*ptr == '=') {
		*ptr = '\0';
		str = ptr + 1;
	    } else
		equalsplit(tokstr, &str);
	    ecstr(name);
	    ecstr(str);
	    zshlex();
	    if (!hasalias)
		hasalias = input_hasalias();
	} else if (tok == ENVARRAY) {
	    int n, parr;

	    if (!postassigns++)
		ppost = ecadd(0);

	    parr = ecadd(0);
	    ecstr(tokstr);
	    cmdpush(CS_ARRAY);
	    /*
	     * Careful here: this must be the typeset case,
	     * but we need to tell the lexer not to look
	     * for assignments until we've finished the
	     * present one.
	     */
	    intypeset = 0;
	    zshlex();
	    n = par_nl_wordlist();
	    ecbuf[parr] = WCB_ASSIGN(WC_ASSIGN_ARRAY, WC_ASSIGN_NEW, n);
	    cmdpop();
	    intypeset = 1;
	    if (tok != OUTPAR)
		YYERROR(oecused);
	    zshlex();
	} else if (tok == INOUTPAR) {
	    zlong oldlineno = lineno;
	    int onp, so, oecssub = ecssub;

	    /* Error if too many function definitions at once */
	    if (!isset(MULTIFUNCDEF) && argc > 1)
		YYERROR(oecused);
	    /* Error if preceding assignments */
	    if (assignments || postassigns)
		YYERROR(oecused);
	    if (hasalias && !isset(ALIASFUNCDEF) && argc &&
		hasalias != input_hasalias()) {
		zwarn("defining function based on alias `%s'", hasalias);
		YYERROR(oecused);
	    }

	    *cmplx = c;
	    lineno = 0;
	    incmdpos = 1;
	    cmdpush(CS_FUNCDEF);
	    zshlex();
	    while (tok == SEPER)
		zshlex();

	    ecispace(p + 1, 1);
	    ecbuf[p + 1] = argc;
	    ecadd(0);
	    ecadd(0);
	    ecadd(0);

	    ecnfunc++;
	    ecssub = so = ecsoffs;
	    onp = ecnpats;
	    ecnpats = 0;

	    if (tok == INBRACE) {
		int c = 0;

		zshlex();
		par_list(&c);
		if (tok != OUTBRACE) {
		    cmdpop();
		    lineno += oldlineno;
		    ecnpats = onp;
		    ecssub = oecssub;
		    YYERROR(oecused);
		}
		if (argc == 0) {
		    /* Anonymous function, possibly with arguments */
		    incmdpos = 0;
		}
		zshlex();
	    } else {
		int ll, sl, c = 0;

		ll = ecadd(0);
		sl = ecadd(0);
		(void)ecadd(WCB_PIPE(WC_PIPE_END, 0));

		if (!par_cmd(&c, argc == 0)) {
		    cmdpop();
		    YYERROR(oecused);
		}
		if (argc == 0) {
		    /*
		     * Anonymous function, possibly with arguments.
		     * N.B. for cmplx structures in particular
		     * ( ... ) we rely on lower level code doing this
		     * to get the immediately following word (the
		     * first token after the ")" has already been
		     * read).
		     */
		    incmdpos = 0;
		}

		set_sublist_code(sl, WC_SUBLIST_END, 0, ecused - 1 - sl, c);
		set_list_code(ll, (Z_SYNC | Z_END), c);
	    }
	    cmdpop();

	    ecadd(WCB_END());
	    ecbuf[p + argc + 2] = so - oecssub;
	    ecbuf[p + argc + 3] = ecsoffs - so;
	    ecbuf[p + argc + 4] = ecnpats;

	    ecnpats = onp;
	    ecssub = oecssub;
	    ecnfunc++;

	    ecbuf[p] = WCB_FUNCDEF(ecused - 1 - p);

	    if (argc == 0) {
		/* Unnamed function */
		int parg = ecadd(0);
		ecadd(0);
		while (tok == STRING || IS_REDIROP(tok)) {
		    if (tok == STRING)
		    {
			ecstr(tokstr);
			argc++;
			zshlex();
		    } else {
			*cmplx = c = 1;
			nrediradd = par_redir(&r, NULL);
			p += nrediradd;
			if (ppost)
			    ppost += nrediradd;
			sr += nrediradd;
			parg += nrediradd;
		    }
		}
		if (argc > 0)
		    *cmplx = 1;
		ecbuf[parg] = ecused - parg; /*?*/
		ecbuf[parg+1] = argc;
	    }
	    lineno += oldlineno;

	    isfunc = 1;
	    isnull = 0;
	    break;
	} else
	    break;
	isnull = 0;
    }
    if (isnull && !(sr + nr)) {
	ecused = p;
	return 0;
    }
    incmdpos = 1;
    intypeset = 0;

    if (!isfunc) {
	if (is_typeset) {
	    ecbuf[p] = WCB_TYPESET(argc);
	    if (postassigns)
		ecbuf[ppost] = postassigns;
	    else
		ecadd(0);
	} else
	    ecbuf[p] = WCB_SIMPLE(argc);
    }

    return sr + 1;
}

/*
 * redir	: ( OUTANG | ... | TRINANG ) STRING
 *
 * Return number of code words required for redirection
 */

static int redirtab[TRINANG - OUTANG + 1] = {
    REDIR_WRITE,
    REDIR_WRITENOW,
    REDIR_APP,
    REDIR_APPNOW,
    REDIR_READ,
    REDIR_READWRITE,
    REDIR_HEREDOC,
    REDIR_HEREDOCDASH,
    REDIR_MERGEIN,
    REDIR_MERGEOUT,
    REDIR_ERRWRITE,
    REDIR_ERRWRITENOW,
    REDIR_ERRAPP,
    REDIR_ERRAPPNOW,
    REDIR_HERESTR,
};

/**/
static int
par_redir(int *rp, char *idstring)
{
    int r = *rp, type, fd1, oldcmdpos, oldnc, ncodes;
    char *name;

    oldcmdpos = incmdpos;
    incmdpos = 0;
    oldnc = nocorrect;
    if (tok != INANG && tok != INOUTANG)
	nocorrect = 1;
    type = redirtab[tok - OUTANG];
    fd1 = tokfd;
    zshlex();
    if (tok != STRING && tok != ENVSTRING)
	YYERROR(ecused);
    incmdpos = oldcmdpos;
    nocorrect = oldnc;

    /* assign default fd */
    if (fd1 == -1)
	fd1 = IS_READFD(type) ? 0 : 1;

    name = tokstr;

    switch (type) {
    case REDIR_HEREDOC:
    case REDIR_HEREDOCDASH: {
	/* <<[-] name */
	struct heredocs **hd;
	int htype = type;

	/*
	 * Add two here for the string to remember the HERE
	 * terminator in raw and munged form.
	 */
	if (idstring)
	{
	    type |= REDIR_VARID_MASK;
	    ncodes = 6;
	}
	else
	    ncodes = 5;

	/* If we ever to change the number of codes, we have to change
	 * the definition of WC_REDIR_WORDS. */
	ecispace(r, ncodes);
	*rp = r + ncodes;
	ecbuf[r] = WCB_REDIR(type | REDIR_FROM_HEREDOC_MASK);
	ecbuf[r + 1] = fd1;

	/*
	 * r + 2: the HERE string we recover
	 * r + 3: the HERE document terminator, raw
	 * r + 4: the HERE document terminator, munged
	 */
	if (idstring)
	    ecbuf[r + 5] = ecstrcode(idstring);

	for (hd = &hdocs; *hd; hd = &(*hd)->next)
	    ;
	*hd = zalloc(sizeof(struct heredocs));
	(*hd)->next = NULL;
	(*hd)->type = htype;
	(*hd)->pc = r;
	(*hd)->str = tokstr;

	zshlex();
	return ncodes;
    }
    case REDIR_WRITE:
    case REDIR_WRITENOW:
	if (tokstr[0] == OutangProc && tokstr[1] == Inpar)
	    /* > >(...) */
	    type = REDIR_OUTPIPE;
	else if (tokstr[0] == Inang && tokstr[1] == Inpar)
	    YYERROR(ecused);
	break;
    case REDIR_READ:
	if (tokstr[0] == Inang && tokstr[1] == Inpar)
	    /* < <(...) */
	    type = REDIR_INPIPE;
	else if (tokstr[0] == OutangProc && tokstr[1] == Inpar)
	    YYERROR(ecused);
	break;
    case REDIR_READWRITE:
	if ((tokstr[0] == Inang || tokstr[0] == OutangProc) &&
	    tokstr[1] == Inpar)
	    type = tokstr[0] == Inang ? REDIR_INPIPE : REDIR_OUTPIPE;
	break;
    }
    zshlex();

    /* If we ever to change the number of codes, we have to change
     * the definition of WC_REDIR_WORDS. */
    if (idstring)
    {
	type |= REDIR_VARID_MASK;
	ncodes = 4;
    }
    else
	ncodes = 3;

    ecispace(r, ncodes);
    *rp = r + ncodes;
    ecbuf[r] = WCB_REDIR(type);
    ecbuf[r + 1] = fd1;
    ecbuf[r + 2] = ecstrcode(name);
    if (idstring)
	ecbuf[r + 3] = ecstrcode(idstring);

    return ncodes;
}

/**/
void
setheredoc(int pc, int type, char *str, char *termstr, char *munged_termstr)
{
    ecbuf[pc] = WCB_REDIR(type | REDIR_FROM_HEREDOC_MASK);
    ecbuf[pc + 2] = ecstrcode(str);
    ecbuf[pc + 3] = ecstrcode(termstr);
    ecbuf[pc + 4] = ecstrcode(munged_termstr);
}

/*
 * wordlist	: { STRING }
 */

/**/
static int
par_wordlist(void)
{
    int num = 0;
    while (tok == STRING) {
	ecstr(tokstr);
	num++;
	zshlex();
    }
    return num;
}

/*
 * nl_wordlist	: { STRING | SEPER }
 */

/**/
static int
par_nl_wordlist(void)
{
    int num = 0;

    while (tok == STRING || tok == SEPER) {
	if (tok != SEPER) {
	    ecstr(tokstr);
	    num++;
	}
	zshlex();
    }
    return num;
}

/*
 * condlex is zshlex for normal parsing, but is altered to allow
 * the test builtin to use par_cond.
 */

/**/
void (*condlex) _((void)) = zshlex;

/*
 * cond	: cond_1 { SEPER } [ DBAR { SEPER } cond ]
 */

#define COND_SEP() (tok == SEPER && condlex != testlex && *zshlextext != ';')

/**/
static int
par_cond(void)
{
    int p = ecused, r;

    r = par_cond_1();
    while (COND_SEP())
	condlex();
    if (tok == DBAR) {
	condlex();
	while (COND_SEP())
	    condlex();
	ecispace(p, 1);
	par_cond();
	ecbuf[p] = WCB_COND(COND_OR, ecused - 1 - p);
	return 1;
    }
    return r;
}

/*
 * cond_1 : cond_2 { SEPER } [ DAMPER { SEPER } cond_1 ]
 */

/**/
static int
par_cond_1(void)
{
    int r, p = ecused;

    r = par_cond_2();
    while (COND_SEP())
	condlex();
    if (tok == DAMPER) {
	condlex();
	while (COND_SEP())
	    condlex();
	ecispace(p, 1);
	par_cond_1();
	ecbuf[p] = WCB_COND(COND_AND, ecused - 1 - p);
	return 1;
    }
    return r;
}

/*
 * Return 1 if condition matches.  This also works for non-elided options.
 *
 * input is test string, may begin - or Dash.
 * cond is condition following the -.
 */
static int check_cond(const char *input, const char *cond)
{
    if (!IS_DASH(input[0]))
	return 0;
    return !strcmp(input + 1, cond);
}

/*
 * cond_2	: BANG cond_2
				| INPAR { SEPER } cond_2 { SEPER } OUTPAR
				| STRING STRING STRING
				| STRING STRING
				| STRING ( INANG | OUTANG ) STRING
 */

/**/
static int
par_cond_2(void)
{
    char *s1, *s2, *s3;
    int dble = 0;
    int n_testargs = (condlex == testlex) ? arrlen(testargs) + 1 : 0;

    if (n_testargs) {
	/* See the description of test in POSIX 1003.2 */
	if (tok == NULLTOK)
	    /* no arguments: false */
	    return par_cond_double(dupstring("-n"), dupstring(""));
	if (n_testargs == 1) {
	    /* one argument: [ foo ] is equivalent to [ -n foo ] */
	    s1 = tokstr;
	    condlex();
	    /* ksh behavior: [ -t ] means [ -t 1 ]; bash disagrees */
	    if (unset(POSIXBUILTINS) && check_cond(s1, "t"))
		return par_cond_double(s1, dupstring("1"));
	    return par_cond_double(dupstring("-n"), s1);
	}
	if (n_testargs > 2) {
	    /* three arguments: if the second argument is a binary operator, *
	     * perform that binary test on the first and the third argument  */
	    if (!strcmp(*testargs, "=")  ||
		!strcmp(*testargs, "==") ||
		!strcmp(*testargs, "!=") ||
		(IS_DASH(**testargs) && get_cond_num(*testargs + 1) >= 0)) {
		s1 = tokstr;
		condlex();
		s2 = tokstr;
		condlex();
		s3 = tokstr;
		condlex();
		return par_cond_triple(s1, s2, s3);
	    }
	}
	/*
	 * We fall through here on any non-numeric infix operator
	 * or any other time there are at least two arguments.
	 */
    } else
	while (COND_SEP())
	    condlex();
    if (tok == BANG) {
	/*
	 * In "test" compatibility mode, "! -a ..." and "! -o ..."
	 * are treated as "[string] [and] ..." and "[string] [or] ...".
	 */
	if (!(n_testargs > 1 && (check_cond(*testargs, "a") ||
				 check_cond(*testargs, "o"))))
	{
	    condlex();
	    ecadd(WCB_COND(COND_NOT, 0));
	    return par_cond_2();
	}
    }
    if (tok == INPAR) {
	int r;

	condlex();
	while (COND_SEP())
	    condlex();
	r = par_cond();
	while (COND_SEP())
	    condlex();
	if (tok != OUTPAR)
	    YYERROR(ecused);
	condlex();
	return r;
    }
    s1 = tokstr;
    dble = (s1 && IS_DASH(*s1)
	    && (!n_testargs
		|| strspn(s1+1, "abcdefghknoprstuvwxzLONGS") == 1)
	    && !s1[2]);
    if (tok != STRING) {
	/* Check first argument for [[ STRING ]] re-interpretation */
	if (s1 /* tok != DOUTBRACK && tok != DAMPER && tok != DBAR */
	    && tok != LEXERR && (!dble || n_testargs)) {
	    do condlex(); while (COND_SEP());
	    return par_cond_double(dupstring("-n"), s1);
	} else
	    YYERROR(ecused);
    }
    condlex();
    if (n_testargs == 2 && tok != STRING && tokstr && IS_DASH(s1[0])) {
	/*
	 * Something like "test -z" followed by a token.
	 * We'll turn the token into a string (we've also
	 * checked it does have a string representation).
	 */
	tok = STRING;
    } else
	while (COND_SEP())
	    condlex();
    if (tok == INANG || tok == OUTANG) {
	enum lextok xtok = tok;
	do condlex(); while (COND_SEP());
	if (tok != STRING)
	    YYERROR(ecused);
	s3 = tokstr;
	do condlex(); while (COND_SEP());
	ecadd(WCB_COND((xtok == INANG ? COND_STRLT : COND_STRGTR), 0));
	ecstr(s1);
	ecstr(s3);
	return 1;
    }
    if (tok != STRING) {
	/*
	 * Check second argument in case semantics e.g. [ = -a = ]
	 * mean we have to go back and fix up the first one
	 */
	if (tok != LEXERR) {
	    if (!dble || n_testargs)
		return par_cond_double(dupstring("-n"), s1);
	    else
		return par_cond_multi(s1, newlinklist());
	} else
	    YYERROR(ecused);
    }
    s2 = tokstr;
    if (!n_testargs)
	dble = (s2 && IS_DASH(*s2) && !s2[2]);
    incond++;			/* parentheses do globbing */
    do condlex(); while (COND_SEP());
    incond--;			/* parentheses do grouping */
    if (tok == STRING && !dble) {
	s3 = tokstr;
	do condlex(); while (COND_SEP());
	if (tok == STRING) {
	    LinkList l = newlinklist();

	    addlinknode(l, s2);
	    addlinknode(l, s3);

	    while (tok == STRING) {
		addlinknode(l, tokstr);
		do condlex(); while (COND_SEP());
	    }
	    return par_cond_multi(s1, l);
	} else
	    return par_cond_triple(s1, s2, s3);
    } else
	return par_cond_double(s1, s2);
}

/**/
static int
par_cond_double(char *a, char *b)
{
    if (!IS_DASH(a[0]) || !a[1])
	COND_ERROR("parse error: condition expected: %s", a);
    else if (!a[2] && strspn(a+1, "abcdefgknoprstuvwxzhLONGS") == 1) {
	ecadd(WCB_COND(a[1], 0));
	ecstr(b);
    } else {
	ecadd(WCB_COND(COND_MOD, 1));
	ecstr(a);
	ecstr(b);
    }
    return 1;
}

/**/
static int
get_cond_num(char *tst)
{
    static char *condstrs[] =
    {
	"nt", "ot", "ef", "eq", "ne", "lt", "gt", "le", "ge", NULL
    };
    int t0;

    for (t0 = 0; condstrs[t0]; t0++)
	if (!strcmp(condstrs[t0], tst))
	    return t0;
    return -1;
}

/**/
static int
par_cond_triple(char *a, char *b, char *c)
{
    int t0;

    if ((b[0] == Equals || b[0] == '=') && !b[1]) {
	ecadd(WCB_COND(COND_STREQ, 0));
	ecstr(a);
	ecstr(c);
	ecadd(ecnpats++);
    } else if ((b[0] == Equals || b[0] == '=') &&
	       (b[1] == Equals || b[1] == '=') && !b[2]) {
	ecadd(WCB_COND(COND_STRDEQ, 0));
	ecstr(a);
	ecstr(c);
	ecadd(ecnpats++);
    } else if (b[0] == '!' && (b[1] == Equals || b[1] == '=') && !b[2]) {
	ecadd(WCB_COND(COND_STRNEQ, 0));
	ecstr(a);
	ecstr(c);
	ecadd(ecnpats++);
    } else if ((b[0] == Equals || b[0] == '=') &&
               (b[1] == '~' || b[1] == Tilde) && !b[2]) {
        /* We become an implicit COND_MODI but do not provide the first
	 * item, it's skipped */
	ecadd(WCB_COND(COND_REGEX, 0));
	ecstr(a);
	ecstr(c);
    } else if (IS_DASH(b[0])) {
	if ((t0 = get_cond_num(b + 1)) > -1) {
	    ecadd(WCB_COND(t0 + COND_NT, 0));
	    ecstr(a);
	    ecstr(c);
	} else {
	    ecadd(WCB_COND(COND_MODI, 0));
	    ecstr(b);
	    ecstr(a);
	    ecstr(c);
	}
    } else if (IS_DASH(a[0]) && a[1]) {
	ecadd(WCB_COND(COND_MOD, 2));
	ecstr(a);
	ecstr(b);
	ecstr(c);
    } else
	COND_ERROR("condition expected: %s", b);

    return 1;
}

/**/
static int
par_cond_multi(char *a, LinkList l)
{
    if (!IS_DASH(a[0]) || !a[1])
	COND_ERROR("condition expected: %s", a);
    else {
	LinkNode n;

	ecadd(WCB_COND(COND_MOD, countlinknodes(l)));
	ecstr(a);
	for (n = firstnode(l); n; incnode(n))
	    ecstr((char *) getdata(n));
    }
    return 1;
}

/**/
static void
yyerror(int noerr)
{
    int t0;
    char *t;

    if ((t = dupstring(zshlextext)))
	untokenize(t);

    for (t0 = 0; t0 != 20; t0++)
	if (!t || !t[t0] || t[t0] == '\n')
	    break;
    if (!(histdone & HISTFLAG_NOEXEC) && !(errflag & ERRFLAG_INT)) {
	if (t0 == 20)
	    zwarn("parse error near `%l...'", t, 20);
	else if (t0)
	    zwarn("parse error near `%l'", t, t0);
	else
	    zwarn("parse error");
    }
    if (!noerr && noerrs != 2)
	errflag |= ERRFLAG_ERROR;
}

/*
 * Duplicate a programme list, on the heap if heap is 1, else
 * in permanent storage.
 *
 * Be careful in case p is the Eprog for a function which will
 * later be autoloaded.  The shf element of the returned Eprog
 * must be set appropriately by the caller.  (Normally we create
 * the Eprog in this case by using mkautofn.)
 */

/**/
mod_export Eprog
dupeprog(Eprog p, int heap)
{
    Eprog r;
    int i;
    Patprog *pp;

    if (p == &dummy_eprog)
	return p;

    r = (heap ? (Eprog) zhalloc(sizeof(*r)) : (Eprog) zalloc(sizeof(*r)));
    r->flags = (heap ? EF_HEAP : EF_REAL) | (p->flags & EF_RUN);
    r->dump = NULL;
    r->len = p->len;
    r->npats = p->npats;
    /*
     * If Eprog is on the heap, reference count is not valid.
     * Otherwise, initialise reference count to 1 so that a freeeprog()
     * will delete it if it is not in use.
     */
    r->nref = heap ? -1 : 1;
    pp = r->pats = (heap ? (Patprog *) hcalloc(r->len) :
		    (Patprog *) zshcalloc(r->len));
    r->prog = (Wordcode) (r->pats + r->npats);
    r->strs = ((char *) r->prog) + (p->strs - ((char *) p->prog));
    memcpy(r->prog, p->prog, r->len - (p->npats * sizeof(Patprog)));
    r->shf = NULL;

    for (i = r->npats; i--; pp++)
	*pp = dummy_patprog1;

    return r;
}


/*
 * Pair of functions to mark an Eprog as in use, and to delete it
 * when it is no longer in use, by means of the reference count in
 * then nref element.
 *
 * If nref is negative, the Eprog is on the heap and is never freed.
 */

/* Increase the reference count of an Eprog so it won't be deleted. */

/**/
mod_export void
useeprog(Eprog p)
{
    if (p && p != &dummy_eprog && p->nref >= 0)
	p->nref++;
}

/* Free an Eprog if we have finished with it */

/**/
mod_export void
freeeprog(Eprog p)
{
    int i;
    Patprog *pp;

    if (p && p != &dummy_eprog) {
	/* paranoia */
	DPUTS(p->nref > 0 && (p->flags & EF_HEAP), "Heap EPROG has nref > 0");
	DPUTS(p->nref < 0 && !(p->flags & EF_HEAP), "Real EPROG has nref < 0");
	DPUTS(p->nref < -1, "Uninitialised EPROG nref");
#ifdef MAX_FUNCTION_DEPTH
	DPUTS(zsh_funcnest >=0 && p->nref > zsh_funcnest + 10,
	      "Overlarge EPROG nref");
#endif
	if (p->nref > 0 && !--p->nref) {
	    for (i = p->npats, pp = p->pats; i--; pp++)
		freepatprog(*pp);
	    if (p->dump) {
		decrdumpcount(p->dump);
		zfree(p->pats, p->npats * sizeof(Patprog));
	    } else
		zfree(p->pats, p->len);
	    zfree(p, sizeof(*p));
	}
    }
}

/**/
char *
ecgetstr(Estate s, int dup, int *tokflag)
{
    static char buf[4];
    wordcode c = *s->pc++;
    char *r;

    if (c == 6 || c == 7)
	r = "";
    else if (c & 2) {
	buf[0] = (char) ((c >>  3) & 0xff);
	buf[1] = (char) ((c >> 11) & 0xff);
	buf[2] = (char) ((c >> 19) & 0xff);
	buf[3] = '\0';
	r = dupstring(buf);
	dup = EC_NODUP;
    } else {
	r = s->strs + (c >> 2);
    }
    if (tokflag)
	*tokflag = (c & 1);

    /*** Since function dump files are mapped read-only, avoiding to
     *   to duplicate strings when they don't contain tokens may fail
     *   when one of the many utility functions happens to write to
     *   one of the strings (without really modifying it).
     *   If that happens to you and you don't feel like debugging it,
     *   just change the line below to:
     *
     *     return (dup ? dupstring(r) : r);
     */

    return ((dup == EC_DUP || (dup && (c & 1)))  ? dupstring(r) : r);
}

/**/
char *
ecrawstr(Eprog p, Wordcode pc, int *tokflag)
{
    static char buf[4];
    wordcode c = *pc;

    if (c == 6 || c == 7) {
	if (tokflag)
	    *tokflag = (c & 1);
	return "";
    } else if (c & 2) {
	buf[0] = (char) ((c >>  3) & 0xff);
	buf[1] = (char) ((c >> 11) & 0xff);
	buf[2] = (char) ((c >> 19) & 0xff);
	buf[3] = '\0';
	if (tokflag)
	    *tokflag = (c & 1);
	return buf;
    } else {
	if (tokflag)
	    *tokflag = (c & 1);
	return p->strs + (c >> 2);
    }
}

/**/
char **
ecgetarr(Estate s, int num, int dup, int *tokflag)
{
    char **ret, **rp;
    int tf = 0, tmp = 0;

    ret = rp = (char **) zhalloc((num + 1) * sizeof(char *));

    while (num--) {
	*rp++ = ecgetstr(s, dup, &tmp);
	tf |=  tmp;
    }
    *rp = NULL;
    if (tokflag)
	*tokflag = tf;

    return ret;
}

/**/
LinkList
ecgetlist(Estate s, int num, int dup, int *tokflag)
{
    if (num) {
	LinkList ret;
	int i, tf = 0, tmp = 0;

	ret = newsizedlist(num);
	for (i = 0; i < num; i++) {
	    setsizednode(ret, i, ecgetstr(s, dup, &tmp));
	    tf |= tmp;
	}
	if (tokflag)
	    *tokflag = tf;
	return ret;
    }
    if (tokflag)
	*tokflag = 0;
    return NULL;
}

/**/
LinkList
ecgetredirs(Estate s)
{
    LinkList ret = newlinklist();
    wordcode code = *s->pc++;

    while (wc_code(code) == WC_REDIR) {
	Redir r = (Redir) zhalloc(sizeof(*r));

	r->type = WC_REDIR_TYPE(code);
	r->fd1 = *s->pc++;
	r->name = ecgetstr(s, EC_DUP, NULL);
	if (WC_REDIR_FROM_HEREDOC(code)) {
	    r->flags = REDIRF_FROM_HEREDOC;
	    r->here_terminator = ecgetstr(s, EC_DUP, NULL);
	    r->munged_here_terminator = ecgetstr(s, EC_DUP, NULL);
	} else {
	    r->flags = 0;
	    r->here_terminator = NULL;
	    r->munged_here_terminator = NULL;
	}
	if (WC_REDIR_VARID(code))
	    r->varid = ecgetstr(s, EC_DUP, NULL);
	else
	    r->varid = NULL;

	addlinknode(ret, r);

	code = *s->pc++;
    }
    s->pc--;

    return ret;
}

/*
 * Copy the consecutive set of redirections in the state at s.
 * Return NULL if none, else an Eprog consisting only of the
 * redirections from permanently allocated memory.
 *
 * s is left in the state ready for whatever follows the redirections.
 */

/**/
Eprog
eccopyredirs(Estate s)
{
    Wordcode pc = s->pc;
    wordcode code = *pc;
    int ncode, ncodes = 0, r;

    if (wc_code(code) != WC_REDIR)
	return NULL;

    init_parse();

    while (wc_code(code) == WC_REDIR) {
#ifdef DEBUG
	int type = WC_REDIR_TYPE(code);
#endif

	DPUTS(type == REDIR_HEREDOC || type == REDIR_HEREDOCDASH,
	      "unexpanded here document");

	if (WC_REDIR_FROM_HEREDOC(code))
	    ncode = 5;
	else
	    ncode = 3;
	if (WC_REDIR_VARID(code))
	    ncode++;
	pc += ncode;
	ncodes += ncode;
	code = *pc;
    }
    r = ecused;
    ecispace(r, ncodes);

    code = *s->pc;
    while (wc_code(code) == WC_REDIR) {
	s->pc++;

	ecbuf[r++] = code;
	/* fd1 */
	ecbuf[r++] = *s->pc++;
	/* name or HERE string */
	/* No DUP needed as we'll copy into Eprog immediately below */
	ecbuf[r++] = ecstrcode(ecgetstr(s, EC_NODUP, NULL));
	if (WC_REDIR_FROM_HEREDOC(code))
	{
	    /* terminator, raw */
	    ecbuf[r++] = ecstrcode(ecgetstr(s, EC_NODUP, NULL));
	    /* terminator, munged */
	    ecbuf[r++] = ecstrcode(ecgetstr(s, EC_NODUP, NULL));
	}
	if (WC_REDIR_VARID(code))
	    ecbuf[r++] = ecstrcode(ecgetstr(s, EC_NODUP, NULL));

	code = *s->pc;
    }

    /* bld_eprog() appends a useful WC_END marker */
    return bld_eprog(0);
}

/**/
mod_export struct eprog dummy_eprog;

static wordcode dummy_eprog_code;

/**/
void
init_eprog(void)
{
    dummy_eprog_code = WCB_END();
    dummy_eprog.len = sizeof(wordcode);
    dummy_eprog.prog = &dummy_eprog_code;
    dummy_eprog.strs = NULL;
}

/* Code for function dump files.
 *
 * Dump files consist of a header and the function bodies (the wordcode
 * plus the string table) and that twice: once for the byte-order of the
 * host the file was created on and once for the other byte-order. The
 * header describes where the beginning of the `other' version is and it
 * is up to the shell reading the file to decide which version it needs.
 * This is done by checking if the first word is FD_MAGIC (then the 
 * shell reading the file has the same byte order as the one that created
 * the file) or if it is FD_OMAGIC, then the `other' version has to be
 * read.
 * The header is the magic number, a word containing the flags (if the
 * file should be mapped or read and if this header is the `other' one),
 * the version string in a field of 40 characters and the descriptions
 * for the functions in the dump file.
 *
 * NOTES:
 *  - This layout has to be kept; everything after it may be changed.
 *  - When incompatible changes are made, the FD_MAGIC and FD_OMAGIC
 *    numbers have to be changed.
 *
 * Each description consists of a struct fdhead followed by the name,
 * aligned to sizeof(wordcode) (i.e. 4 bytes).
 */

#include "version.h"

#define FD_EXT ".zwc"
#define FD_MINMAP 4096

#define FD_PRELEN 12
#define FD_MAGIC  0x04050607
#define FD_OMAGIC 0x07060504

#define FDF_MAP   1
#define FDF_OTHER 2

typedef struct fdhead *FDHead;

struct fdhead {
    wordcode start;		/* offset to function definition */
    wordcode len;		/* length of wordcode/strings */
    wordcode npats;		/* number of patterns needed */
    wordcode strs;		/* offset to strings */
    wordcode hlen;		/* header length (incl. name) */
    wordcode flags;		/* flags and offset to name tail */
};

#define fdheaderlen(f) (((Wordcode) (f))[FD_PRELEN])

#define fdmagic(f)       (((Wordcode) (f))[0])
#define fdsetbyte(f,i,v) \
    ((((unsigned char *) (((Wordcode) (f)) + 1))[i]) = ((unsigned char) (v)))
#define fdbyte(f,i)      ((wordcode) (((unsigned char *) (((Wordcode) (f)) + 1))[i]))
#define fdflags(f)       fdbyte(f, 0)
#define fdsetflags(f,v)  fdsetbyte(f, 0, v)
#define fdother(f)       (fdbyte(f, 1) + (fdbyte(f, 2) << 8) + (fdbyte(f, 3) << 16))
#define fdsetother(f, o) \
    do { \
        fdsetbyte(f, 1, ((o) & 0xff)); \
        fdsetbyte(f, 2, (((o) >> 8) & 0xff)); \
        fdsetbyte(f, 3, (((o) >> 16) & 0xff)); \
    } while (0)
#define fdversion(f)     ((char *) ((f) + 2))

#define firstfdhead(f) ((FDHead) (((Wordcode) (f)) + FD_PRELEN))
#define nextfdhead(f)  ((FDHead) (((Wordcode) (f)) + (f)->hlen))

#define fdhflags(f)      (((FDHead) (f))->flags)
#define fdhtail(f)       (((FDHead) (f))->flags >> 2)
#define fdhbldflags(f,t) ((f) | ((t) << 2))

#define FDHF_KSHLOAD 1
#define FDHF_ZSHLOAD 2

#define fdname(f)      ((char *) (((FDHead) (f)) + 1))

/* This is used when building wordcode files. */

typedef struct wcfunc *WCFunc;

struct wcfunc {
    char *name;
    Eprog prog;
    int flags;
};

/* Try to find the description for the given function name. */

static FDHead
dump_find_func(Wordcode h, char *name)
{
    FDHead n, e = (FDHead) (h + fdheaderlen(h));

    for (n = firstfdhead(h); n < e; n = nextfdhead(n))
	if (!strcmp(name, fdname(n) + fdhtail(n)))
	    return n;

    return NULL;
}

/**/
int
bin_zcompile(char *nam, char **args, Options ops, UNUSED(int func))
{
    int map, flags, ret;
    char *dump;

    if ((OPT_ISSET(ops,'k') && OPT_ISSET(ops,'z')) ||
	(OPT_ISSET(ops,'R') && OPT_ISSET(ops,'M')) ||
	(OPT_ISSET(ops,'c') &&
	 (OPT_ISSET(ops,'U') || OPT_ISSET(ops,'k') || OPT_ISSET(ops,'z'))) ||
	(!(OPT_ISSET(ops,'c') || OPT_ISSET(ops,'a')) && OPT_ISSET(ops,'m'))) {
	zwarnnam(nam, "illegal combination of options");
	return 1;
    }
    if ((OPT_ISSET(ops,'c') || OPT_ISSET(ops,'a')) && isset(KSHAUTOLOAD))
	zwarnnam(nam, "functions will use zsh style autoloading");

    flags = (OPT_ISSET(ops,'k') ? FDHF_KSHLOAD :
	     (OPT_ISSET(ops,'z') ? FDHF_ZSHLOAD : 0));

    if (OPT_ISSET(ops,'t')) {
	Wordcode f;

	if (!*args) {
	    zwarnnam(nam, "too few arguments");
	    return 1;
	}
	if (!(f = load_dump_header(nam, (strsfx(FD_EXT, *args) ? *args :
					 dyncat(*args, FD_EXT)), 1)))
		return 1;

	if (args[1]) {
	    for (args++; *args; args++)
		if (!dump_find_func(f, *args))
		    return 1;
	    return 0;
	} else {
	    FDHead h, e = (FDHead) (f + fdheaderlen(f));

	    printf("zwc file (%s) for zsh-%s\n",
		   ((fdflags(f) & FDF_MAP) ? "mapped" : "read"), fdversion(f));
	    for (h = firstfdhead(f); h < e; h = nextfdhead(h))
		printf("%s\n", fdname(h));
	    return 0;
	}
    }
    if (!*args) {
	zwarnnam(nam, "too few arguments");
	return 1;
    }
    map = (OPT_ISSET(ops,'M') ? 2 : (OPT_ISSET(ops,'R') ? 0 : 1));

    if (!args[1] && !(OPT_ISSET(ops,'c') || OPT_ISSET(ops,'a'))) {
	queue_signals();
	ret = build_dump(nam, dyncat(*args, FD_EXT), args, OPT_ISSET(ops,'U'),
			 map, flags);
	unqueue_signals();
	return ret;
    }
    dump = (strsfx(FD_EXT, *args) ? *args : dyncat(*args, FD_EXT));

    queue_signals();
    ret = ((OPT_ISSET(ops,'c') || OPT_ISSET(ops,'a')) ?
	   build_cur_dump(nam, dump, args + 1, OPT_ISSET(ops,'m'), map,
			  (OPT_ISSET(ops,'c') ? 1 : 0) | 
			  (OPT_ISSET(ops,'a') ? 2 : 0)) :
	   build_dump(nam, dump, args + 1, OPT_ISSET(ops,'U'), map, flags));
    unqueue_signals();

    return ret;
}

/* Load the header of a dump file. Returns NULL if the file isn't a
 * valid dump file. */

/**/
static Wordcode
load_dump_header(char *nam, char *name, int err)
{
    int fd, v = 1;
    wordcode buf[FD_PRELEN + 1];

    if ((fd = open(name, O_RDONLY)) < 0) {
	if (err)
	    zwarnnam(nam, "can't open zwc file: %s", name);
	return NULL;
    }
    if (read(fd, buf, (FD_PRELEN + 1) * sizeof(wordcode)) !=
	((FD_PRELEN + 1) * sizeof(wordcode)) ||
	(v = (fdmagic(buf) != FD_MAGIC && fdmagic(buf) != FD_OMAGIC)) ||
	strcmp(fdversion(buf), ZSH_VERSION)) {
	if (err) {
	    if (!v) {
		zwarnnam(nam, "zwc file has wrong version (zsh-%s): %s",
			 fdversion(buf), name);
	    } else
		zwarnnam(nam, "invalid zwc file: %s" , name);
	}
	close(fd);
	return NULL;
    } else {
	int len;
	Wordcode head;

	if (fdmagic(buf) == FD_MAGIC) {
	    len = fdheaderlen(buf) * sizeof(wordcode);
	    head = (Wordcode) zhalloc(len);
	}
	else {
	    int o = fdother(buf);

	    if (lseek(fd, o, 0) == -1 ||
		read(fd, buf, (FD_PRELEN + 1) * sizeof(wordcode)) !=
		((FD_PRELEN + 1) * sizeof(wordcode))) {
		zwarnnam(nam, "invalid zwc file: %s" , name);
		close(fd);
		return NULL;
	    }
	    len = fdheaderlen(buf) * sizeof(wordcode);
	    head = (Wordcode) zhalloc(len);
	}
	memcpy(head, buf, (FD_PRELEN + 1) * sizeof(wordcode));

	len -= (FD_PRELEN + 1) * sizeof(wordcode);
	if (read(fd, head + (FD_PRELEN + 1), len) != len) {
	    close(fd);
	    zwarnnam(nam, "invalid zwc file: %s" , name);
	    return NULL;
	}
	close(fd);
	return head;
    }
}

/* Swap the bytes in a wordcode. */

static void
fdswap(Wordcode p, int n)
{
    wordcode c;

    for (; n--; p++) {
	c = *p;
	*p = (((c & 0xff) << 24) |
	      ((c & 0xff00) << 8) |
	      ((c & 0xff0000) >> 8) |
	      ((c & 0xff000000) >> 24));
    }
}

/* Write a dump file. */

static void
write_dump(int dfd, LinkList progs, int map, int hlen, int tlen)
{
    LinkNode node;
    WCFunc wcf;
    int other = 0, ohlen, tmp;
    wordcode pre[FD_PRELEN];
    char *tail, *n;
    struct fdhead head;
    Eprog prog;

    if (map == 1)
	map = (tlen >= FD_MINMAP);

    memset(pre, 0, sizeof(wordcode) * FD_PRELEN);

    for (ohlen = hlen; ; hlen = ohlen) {
	fdmagic(pre) = (other ? FD_OMAGIC : FD_MAGIC);
	fdsetflags(pre, ((map ? FDF_MAP : 0) | other));
	fdsetother(pre, tlen);
	strcpy(fdversion(pre), ZSH_VERSION);
	write_loop(dfd, (char *)pre, FD_PRELEN * sizeof(wordcode));

	for (node = firstnode(progs); node; incnode(node)) {
	    wcf = (WCFunc) getdata(node);
	    n = wcf->name;
	    prog = wcf->prog;
	    head.start = hlen;
	    hlen += (prog->len - (prog->npats * sizeof(Patprog)) +
		     sizeof(wordcode) - 1) / sizeof(wordcode);
	    head.len = prog->len - (prog->npats * sizeof(Patprog));
	    head.npats = prog->npats;
	    head.strs = prog->strs - ((char *) prog->prog);
	    head.hlen = (sizeof(struct fdhead) / sizeof(wordcode)) +
		(strlen(n) + sizeof(wordcode)) / sizeof(wordcode);
	    if ((tail = strrchr(n, '/')))
		tail++;
	    else
		tail = n;
	    head.flags = fdhbldflags(wcf->flags, (tail - n));
	    if (other)
		fdswap((Wordcode) &head, sizeof(head) / sizeof(wordcode));
	    write_loop(dfd, (char *)&head, sizeof(head));
	    tmp = strlen(n) + 1;
	    write_loop(dfd, n, tmp);
	    if ((tmp &= (sizeof(wordcode) - 1)))
		write_loop(dfd, (char *)&head, sizeof(wordcode) - tmp);
	}
	for (node = firstnode(progs); node; incnode(node)) {
	    prog = ((WCFunc) getdata(node))->prog;
	    tmp = (prog->len - (prog->npats * sizeof(Patprog)) +
		   sizeof(wordcode) - 1) / sizeof(wordcode);
	    if (other)
		fdswap(prog->prog, (((Wordcode) prog->strs) - prog->prog));
	    write_loop(dfd, (char *)prog->prog, tmp * sizeof(wordcode));
	}
	if (other)
	    break;
	other = FDF_OTHER;
    }
}

/**/
static int
build_dump(char *nam, char *dump, char **files, int ali, int map, int flags)
{
    int dfd, fd, hlen, tlen, flen, ona = noaliases;
    LinkList progs;
    char *file;
    Eprog prog;
    WCFunc wcf;

    if (!strsfx(FD_EXT, dump))
	dump = dyncat(dump, FD_EXT);

    unlink(dump);
    if ((dfd = open(dump, O_WRONLY|O_CREAT, 0444)) < 0) {
	zwarnnam(nam, "can't write zwc file: %s", dump);
	return 1;
    }
    progs = newlinklist();
    noaliases = ali;

    for (hlen = FD_PRELEN, tlen = 0; *files; files++) {
	struct stat st;

	if (check_cond(*files, "k")) {
	    flags = (flags & ~(FDHF_KSHLOAD | FDHF_ZSHLOAD)) | FDHF_KSHLOAD;
	    continue;
	} else if (check_cond(*files, "z")) {
	    flags = (flags & ~(FDHF_KSHLOAD | FDHF_ZSHLOAD)) | FDHF_ZSHLOAD;
	    continue;
	}
	if ((fd = open(*files, O_RDONLY)) < 0 ||
	    fstat(fd, &st) != 0 || !S_ISREG(st.st_mode) ||
	    (flen = lseek(fd, 0, 2)) == -1) {
	    if (fd >= 0)
		close(fd);
	    close(dfd);
	    zwarnnam(nam, "can't open file: %s", *files);
	    noaliases = ona;
	    unlink(dump);
	    return 1;
	}
	file = (char *) zalloc(flen + 1);
	file[flen] = '\0';
	lseek(fd, 0, 0);
	if (read(fd, file, flen) != flen) {
	    close(fd);
	    close(dfd);
	    zfree(file, flen);
	    zwarnnam(nam, "can't read file: %s", *files);
	    noaliases = ona;
	    unlink(dump);
	    return 1;
	}
	close(fd);
	file = metafy(file, flen, META_REALLOC);

	if (!(prog = parse_string(file, 1)) || errflag) {
	    errflag &= ~ERRFLAG_ERROR;
	    close(dfd);
	    zfree(file, flen);
	    zwarnnam(nam, "can't read file: %s", *files);
	    noaliases = ona;
	    unlink(dump);
	    return 1;
	}
	zfree(file, flen);

	wcf = (WCFunc) zhalloc(sizeof(*wcf));
	wcf->name = *files;
	wcf->prog = prog;
	wcf->flags = ((prog->flags & EF_RUN) ? FDHF_KSHLOAD : flags);
	addlinknode(progs, wcf);

	flen = (strlen(*files) + sizeof(wordcode)) / sizeof(wordcode);
	hlen += (sizeof(struct fdhead) / sizeof(wordcode)) + flen;

	tlen += (prog->len - (prog->npats * sizeof(Patprog)) +
		 sizeof(wordcode) - 1) / sizeof(wordcode);
    }
    noaliases = ona;

    tlen = (tlen + hlen) * sizeof(wordcode);

    write_dump(dfd, progs, map, hlen, tlen);

    close(dfd);

    return 0;
}

static int
cur_add_func(char *nam, Shfunc shf, LinkList names, LinkList progs,
	     int *hlen, int *tlen, int what)
{
    Eprog prog;
    WCFunc wcf;

    if (shf->node.flags & PM_UNDEFINED) {
	int ona = noaliases;

	if (!(what & 2)) {
	    zwarnnam(nam, "function is not loaded: %s", shf->node.nam);
	    return 1;
	}
	noaliases = (shf->node.flags & PM_UNALIASED);
	if (!(prog = getfpfunc(shf->node.nam, NULL, NULL, NULL, 0)) ||
	    prog == &dummy_eprog) {
	    noaliases = ona;
	    zwarnnam(nam, "can't load function: %s", shf->node.nam);
	    return 1;
	}
	if (prog->dump)
	    prog = dupeprog(prog, 1);
	noaliases = ona;
    } else {
	if (!(what & 1)) {
	    zwarnnam(nam, "function is already loaded: %s", shf->node.nam);
	    return 1;
	}
	prog = dupeprog(shf->funcdef, 1);
    }
    wcf = (WCFunc) zhalloc(sizeof(*wcf));
    wcf->name = shf->node.nam;
    wcf->prog = prog;
    wcf->flags = ((prog->flags & EF_RUN) ? FDHF_KSHLOAD : FDHF_ZSHLOAD);
    addlinknode(progs, wcf);
    addlinknode(names, shf->node.nam);

    *hlen += ((sizeof(struct fdhead) / sizeof(wordcode)) +
	      ((strlen(shf->node.nam) + sizeof(wordcode)) / sizeof(wordcode)));
    *tlen += (prog->len - (prog->npats * sizeof(Patprog)) +
	      sizeof(wordcode) - 1) / sizeof(wordcode);

    return 0;
}

/**/
static int
build_cur_dump(char *nam, char *dump, char **names, int match, int map,
	       int what)
{
    int dfd, hlen, tlen;
    LinkList progs, lnames;
    Shfunc shf = NULL;

    if (!strsfx(FD_EXT, dump))
	dump = dyncat(dump, FD_EXT);

    unlink(dump);
    if ((dfd = open(dump, O_WRONLY|O_CREAT, 0444)) < 0) {
	zwarnnam(nam, "can't write zwc file: %s", dump);
	return 1;
    }
    progs = newlinklist();
    lnames = newlinklist();

    hlen = FD_PRELEN;
    tlen = 0;

    if (!*names) {
	int i;
	HashNode hn;

	for (i = 0; i < shfunctab->hsize; i++)
	    for (hn = shfunctab->nodes[i]; hn; hn = hn->next)
		if (cur_add_func(nam, (Shfunc) hn, lnames, progs,
				 &hlen, &tlen, what)) {
		    errflag &= ~ERRFLAG_ERROR;
		    close(dfd);
		    unlink(dump);
		    return 1;
		}
    } else if (match) {
	char *pat;
	Patprog pprog;
	int i;
	HashNode hn;

	for (; *names; names++) {
	    tokenize(pat = dupstring(*names));
	    /* Signal-safe here, caller queues signals */
	    if (!(pprog = patcompile(pat, PAT_STATIC, NULL))) {
		zwarnnam(nam, "bad pattern: %s", *names);
		close(dfd);
		unlink(dump);
		return 1;
	    }
	    for (i = 0; i < shfunctab->hsize; i++)
		for (hn = shfunctab->nodes[i]; hn; hn = hn->next)
		    if (!linknodebydatum(lnames, hn->nam) &&
			pattry(pprog, hn->nam) &&
			cur_add_func(nam, (Shfunc) hn, lnames, progs,
				     &hlen, &tlen, what)) {
			errflag &= ~ERRFLAG_ERROR;
			close(dfd);
			unlink(dump);
			return 1;
		    }
	}
    } else {
	for (; *names; names++) {
	    if (errflag ||
		!(shf = (Shfunc) shfunctab->getnode(shfunctab, *names))) {
		zwarnnam(nam, "unknown function: %s", *names);
		errflag &= ~ERRFLAG_ERROR;
		close(dfd);
		unlink(dump);
		return 1;
	    }
	    if (cur_add_func(nam, shf, lnames, progs, &hlen, &tlen, what)) {
		errflag &= ~ERRFLAG_ERROR;
		close(dfd);
		unlink(dump);
		return 1;
	    }
	}
    }
    if (empty(progs)) {
	zwarnnam(nam, "no functions");
	errflag &= ~ERRFLAG_ERROR;
	close(dfd);
	unlink(dump);
	return 1;
    }
    tlen = (tlen + hlen) * sizeof(wordcode);

    write_dump(dfd, progs, map, hlen, tlen);

    close(dfd);

    return 0;
}

/**/
#if defined(HAVE_SYS_MMAN_H) && defined(HAVE_MMAP) && defined(HAVE_MUNMAP)

#include <sys/mman.h>

/**/
#if defined(MAP_SHARED) && defined(PROT_READ)

/**/
#define USE_MMAP 1

/**/
#endif
/**/
#endif

/**/
#ifdef USE_MMAP

/* List of dump files mapped. */

static FuncDump dumps;

/**/
static int
zwcstat(char *filename, struct stat *buf)
{
    if (stat(filename, buf)) {
#ifdef HAVE_FSTAT
        FuncDump f;
    
	for (f = dumps; f; f = f->next) {
	    if (!strncmp(filename, f->filename, strlen(f->filename)) &&
		!fstat(f->fd, buf))
		return 0;
	}
#endif
	return 1;
    } else return 0;
}

/* Load a dump file (i.e. map it). */

static void
load_dump_file(char *dump, struct stat *sbuf, int other, int len)
{
    FuncDump d;
    Wordcode addr;
    int fd, off, mlen;

    if (other) {
	static size_t pgsz = 0;

	if (!pgsz) {

#ifdef _SC_PAGESIZE
	    pgsz = sysconf(_SC_PAGESIZE);     /* SVR4 */
#else
# ifdef _SC_PAGE_SIZE
	    pgsz = sysconf(_SC_PAGE_SIZE);    /* HPUX */
# else
	    pgsz = getpagesize();
# endif
#endif

	    pgsz--;
	}
	off = len & ~pgsz;
        mlen = len + (len - off);
    } else {
	off = 0;
        mlen = len;
    }
    if ((fd = open(dump, O_RDONLY)) < 0)
	return;

    fd = movefd(fd);
    if (fd == -1)
	return;

    if ((addr = (Wordcode) mmap(NULL, mlen, PROT_READ, MAP_SHARED, fd, off)) ==
	((Wordcode) -1)) {
	close(fd);
	return;
    }
    d = (FuncDump) zalloc(sizeof(*d));
    d->next = dumps;
    dumps = d;
    d->dev = sbuf->st_dev;
    d->ino = sbuf->st_ino;
    d->fd = fd;
#ifdef FD_CLOEXEC
    fcntl(fd, F_SETFD, FD_CLOEXEC);
#endif
    d->map = addr + (other ? (len - off) / sizeof(wordcode) : 0);
    d->addr = addr;
    d->len = len;
    d->count = 0;
    d->filename = ztrdup(dump);
}

#else

#define zwcstat(f, b) (!!stat(f, b))

/**/
#endif

/* Try to load a function from one of the possible wordcode files for it.
 * The first argument is a element of $fpath, the second one is the name
 * of the function searched and the last one is the possible name for the
 * uncompiled function file (<path>/<func>). */

/**/
Eprog
try_dump_file(char *path, char *name, char *file, int *ksh, int test_only)
{
    Eprog prog;
    struct stat std, stc, stn;
    int rd, rc, rn;
    char *dig, *wc;

    if (strsfx(FD_EXT, path)) {
	queue_signals();
	prog = check_dump_file(path, NULL, name, ksh, test_only);
	unqueue_signals();
	return prog;
    }
    dig = dyncat(path, FD_EXT);
    wc = dyncat(file, FD_EXT);

    rd = zwcstat(dig, &std);
    rc = stat(wc, &stc);
    rn = stat(file, &stn);

    /* See if there is a digest file for the directory, it is younger than
     * both the uncompiled function file and its compiled version (or they
     * don't exist) and the digest file contains the definition for the
     * function. */
    queue_signals();
    if (!rd &&
	(rc || std.st_mtime >= stc.st_mtime) &&
	(rn || std.st_mtime >= stn.st_mtime) &&
	(prog = check_dump_file(dig, &std, name, ksh, test_only))) {
	unqueue_signals();
	return prog;
    }
    /* No digest file. Now look for the per-function compiled file. */
    if (!rc &&
	(rn || stc.st_mtime >= stn.st_mtime) &&
	(prog = check_dump_file(wc, &stc, name, ksh, test_only))) {
	unqueue_signals();
	return prog;
    }
    /* No compiled file for the function. The caller (getfpfunc() will
     * check if the directory contains the uncompiled file for it. */
    unqueue_signals();
    return NULL;
}

/* Almost the same, but for sourced files. */

/**/
Eprog
try_source_file(char *file)
{
    Eprog prog;
    struct stat stc, stn;
    int rc, rn;
    char *wc, *tail;

    if ((tail = strrchr(file, '/')))
	tail++;
    else
	tail = file;

    if (strsfx(FD_EXT, file)) {
	queue_signals();
	prog = check_dump_file(file, NULL, tail, NULL, 0);
	unqueue_signals();
	return prog;
    }
    wc = dyncat(file, FD_EXT);

    rc = stat(wc, &stc);
    rn = stat(file, &stn);

    queue_signals();
    if (!rc && (rn || stc.st_mtime >= stn.st_mtime) &&
	(prog = check_dump_file(wc, &stc, tail, NULL, 0))) {
	unqueue_signals();
	return prog;
    }
    unqueue_signals();
    return NULL;
}

/* See if `file' names a wordcode dump file and that contains the
 * definition for the function `name'. If so, return an eprog for it. */

/**/
static Eprog
check_dump_file(char *file, struct stat *sbuf, char *name, int *ksh,
		int test_only)
{
    int isrec = 0;
    Wordcode d;
    FDHead h;
    FuncDump f;
    struct stat lsbuf;

    if (!sbuf) {
	if (zwcstat(file, &lsbuf))
	    return NULL;
	sbuf = &lsbuf;
    }

#ifdef USE_MMAP

 rec:

#endif

    d = NULL;

#ifdef USE_MMAP

    for (f = dumps; f; f = f->next)
	if (f->dev == sbuf->st_dev && f->ino == sbuf->st_ino) {
	    d = f->map;
	    break;
	}

#else

    f = NULL;

#endif

    if (!f && (isrec || !(d = load_dump_header(NULL, file, 0))))
	return NULL;

    if ((h = dump_find_func(d, name))) {
	/* Found the name. If the file is already mapped, return the eprog,
	 * otherwise map it and just go up. */
	if (test_only)
	{
	    /* This is all we need.  Just return dummy. */
	    return &dummy_eprog;
	}

#ifdef USE_MMAP

	if (f) {
	    Eprog prog = (Eprog) zalloc(sizeof(*prog));
	    Patprog *pp;
	    int np;

	    prog->flags = EF_MAP;
	    prog->len = h->len;
	    prog->npats = np = h->npats;
	    prog->nref = 1;	/* allocated from permanent storage */
	    prog->pats = pp = (Patprog *) zalloc(np * sizeof(Patprog));
	    prog->prog = f->map + h->start;
	    prog->strs = ((char *) prog->prog) + h->strs;
	    prog->shf = NULL;
	    prog->dump = f;

	    incrdumpcount(f);

	    while (np--)
		*pp++ = dummy_patprog1;

	    if (ksh)
		*ksh = ((fdhflags(h) & FDHF_KSHLOAD) ? 2 :
			((fdhflags(h) & FDHF_ZSHLOAD) ? 0 : 1));

	    return prog;
	} else if (fdflags(d) & FDF_MAP) {
	    load_dump_file(file, sbuf, (fdflags(d) & FDF_OTHER), fdother(d));
	    isrec = 1;
	    goto rec;
	} else

#endif

	{
	    Eprog prog;
	    Patprog *pp;
	    int np, fd, po = h->npats * sizeof(Patprog);

	    if ((fd = open(file, O_RDONLY)) < 0 ||
		lseek(fd, ((h->start * sizeof(wordcode)) +
			   ((fdflags(d) & FDF_OTHER) ? fdother(d) : 0)), 0) < 0) {
		if (fd >= 0)
		    close(fd);
		return NULL;
	    }
	    d = (Wordcode) zalloc(h->len + po);

	    if (read(fd, ((char *) d) + po, h->len) != (int)h->len) {
		close(fd);
		zfree(d, h->len);

		return NULL;
	    }
	    close(fd);

	    prog = (Eprog) zalloc(sizeof(*prog));

	    prog->flags = EF_REAL;
	    prog->len = h->len + po;
	    prog->npats = np = h->npats;
	    prog->nref = 1; /* allocated from permanent storage */
	    prog->pats = pp = (Patprog *) d;
	    prog->prog = (Wordcode) (((char *) d) + po);
	    prog->strs = ((char *) prog->prog) + h->strs;
	    prog->shf = NULL;
	    prog->dump = f;

	    while (np--)
		*pp++ = dummy_patprog1;

	    if (ksh)
		*ksh = ((fdhflags(h) & FDHF_KSHLOAD) ? 2 :
			((fdhflags(h) & FDHF_ZSHLOAD) ? 0 : 1));

	    return prog;
	}
    }
    return NULL;
}

#ifdef USE_MMAP

/* Increment the reference counter for a dump file. */

/**/
void
incrdumpcount(FuncDump f)
{
    f->count++;
}

/**/
static void
freedump(FuncDump f)
{
    munmap((void *) f->addr, f->len);
    zclose(f->fd);
    zsfree(f->filename);
    zfree(f, sizeof(*f));
}

/* Decrement the reference counter for a dump file. If zero, unmap the file. */

/**/
void
decrdumpcount(FuncDump f)
{
    f->count--;
    if (!f->count) {
	FuncDump p, q;

	for (q = NULL, p = dumps; p && p != f; q = p, p = p->next);
	if (p) {
	    if (q)
		q->next = p->next;
	    else
		dumps = p->next;
	    freedump(f);
	}
    }
}

#ifndef FD_CLOEXEC
/**/
mod_export void
closedumps(void)
{
    while (dumps) {
	FuncDump p = dumps->next;
	freedump(dumps);
	dumps = p;
    }
}
#endif

#else

void
incrdumpcount(FuncDump f)
{
}

void
decrdumpcount(FuncDump f)
{
}

#ifndef FD_CLOEXEC
/**/
mod_export void
closedumps(void)
{
}
#endif

#endif

/**/
int
dump_autoload(char *nam, char *file, int on, Options ops, int func)
{
    Wordcode h;
    FDHead n, e;
    Shfunc shf;
    int ret = 0;

    if (!strsfx(FD_EXT, file))
	file = dyncat(file, FD_EXT);

    if (!(h = load_dump_header(nam, file, 1)))
	return 1;

    for (n = firstfdhead(h), e = (FDHead) (h + fdheaderlen(h)); n < e;
	 n = nextfdhead(n)) {
	shf = (Shfunc) zshcalloc(sizeof *shf);
	shf->node.flags = on;
	shf->funcdef = mkautofn(shf);
	shf->sticky = NULL;
	shfunctab->addnode(shfunctab, ztrdup(fdname(n) + fdhtail(n)), shf);
	if (OPT_ISSET(ops,'X') && eval_autoload(shf, shf->node.nam, ops, func))
	    ret = 1;
    }
    return ret;
}
