mirror of
https://github.com/zsh-users/zsh
synced 2024-11-02 09:47:59 +00:00
3986 lines
86 KiB
C
3986 lines
86 KiB
C
/*
|
|
* 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;
|
|
long cmp;
|
|
|
|
for (pp = &ecstrs; (p = *pp); ) {
|
|
if (!(cmp = p->nfunc - ecnfunc) && !(cmp = (((long)p->hashval) - ((long)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 substitution, 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 if (postassigns)
|
|
{
|
|
/* C.f. normal case below */
|
|
postassigns++;
|
|
ecadd(WCB_ASSIGN(WC_ASSIGN_SCALAR, WC_ASSIGN_INC, 0));
|
|
ecstr(toksave);
|
|
ecstr(""); /* TBD can possibly optimise out */
|
|
}
|
|
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)
|
|
{
|
|
int varid = WC_REDIR_VARID(ecbuf[pc]) ? REDIR_VARID_MASK : 0;
|
|
ecbuf[pc] = WCB_REDIR(type | REDIR_FROM_HEREDOC_MASK | varid);
|
|
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;
|
|
}
|