diff options
| author | Craig Jennings <c@cjennings.net> | 2025-05-08 18:49:34 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-05-08 18:51:59 -0500 |
| commit | 000e00871830cd15de032c80e2b62946cf19445c (patch) | |
| tree | 794a7922750472bbe0e024042d6ba84f411fc3e0 /dotfiles/system/.zsh/modules/Src/parse.c | |
| parent | fe302606931e4bad91c4ed6df81a4403523ba780 (diff) | |
adding missing dotfiles and folders
- profile.d/
- bashrc
- authinfo.gpg
- .zsh/
Diffstat (limited to 'dotfiles/system/.zsh/modules/Src/parse.c')
| -rw-r--r-- | dotfiles/system/.zsh/modules/Src/parse.c | 3977 |
1 files changed, 3977 insertions, 0 deletions
diff --git a/dotfiles/system/.zsh/modules/Src/parse.c b/dotfiles/system/.zsh/modules/Src/parse.c new file mode 100644 index 0000000..83383f1 --- /dev/null +++ b/dotfiles/system/.zsh/modules/Src/parse.c @@ -0,0 +1,3977 @@ +/* + * 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 if (unset(SHORTLOOPS)) { + YYERRORV(oecused); + } else + par_save_list1(cmplx); + + 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; +} |
