From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 8637 invoked by alias); 7 Jan 2015 16:49:33 -0000 Mailing-List: contact zsh-workers-help@zsh.org; run by ezmlm Precedence: bulk X-No-Archive: yes List-Id: Zsh Workers List List-Post: List-Help: X-Seq: 34160 Received: (qmail 23022 invoked from network); 7 Jan 2015 16:49:20 -0000 X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on f.primenet.com.au X-Spam-Level: X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00,RCVD_IN_DNSWL_HI, SPF_HELO_PASS,T_HDRS_LCASE,T_MANY_HDRS_LCASE autolearn=ham version=3.3.2 X-AuditID: cbfec7f4-b7f126d000001e9a-b4-54ad638c1e7c Date: Wed, 07 Jan 2015 16:48:36 +0000 From: Peter Stephenson To: Zsh Hackers' List Subject: PATCH: fix command substitution parsing Message-id: <20150107164836.035546c4@pwslap01u.europe.root.pri> Organization: Samsung Cambridge Solution Centre X-Mailer: Claws Mail 3.7.9 (GTK+ 2.22.0; i386-redhat-linux-gnu) MIME-version: 1.0 Content-type: text/plain; charset=US-ASCII Content-transfer-encoding: 7bit X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFupjluLIzCtJLcpLzFFi42I5/e/4Fd2e5LUhBl9+S1ocbH7I5MDoserg B6YAxigum5TUnMyy1CJ9uwSujAsdL1gLWtcwVvzZ85y9gXFefRcjJ4eEgInEmR+zmCFsMYkL 99azdTFycQgJLGWUePdtMxOEs4RJ4u+rfVDONkaJtuO3wFpYBFQlrnTuYgGx2QQMJaZums3Y xcjBISKgLdH+UQwkLCxgILGjvYsVxOYVsJdom3mICcTmF9CXuPr3ExPEZnuJmVfOMELUCEr8 mHwPbCSzgJbE5m1NrBC2vMTmNW/B1goJqEvcuLubfQKjwCwkLbOQtMxC0rKAkXkVo2hqaXJB cVJ6rqFecWJucWleul5yfu4mRkgYftnBuPiY1SFGAQ5GJR7egr41IUKsiWXFlbmHGCU4mJVE eA3C1oYI8aYkVlalFuXHF5XmpBYfYmTi4JRqYAx9WbZt5xo3nR39D6YHltxTP6rVeU7ZMv/G P3ldTpbf2+N3OAvm7m9mstM7NlXt9E774B/mTFvfyH8QY+t6fOPl5FNsxxhE+O89mWC2RvCg 9YnpQp+/lfw++HP1krcCk1Js1RdpzSv+Mesai8HdGRyn/92dczgitzjn8oFjMfmu+1f13fZ9 bv9IiaU4I9FQi7moOBEAGtEhoCECAAA= According to the test suite enhanced with a few appropriate tests, this fixes the problem where syntactically significant but irrelevant unmatched closing parentheses caused command substitution to abort early. I don't believe that either, so we'll need to tease out whatever oddities remain. I think it's basically good enough to push, and the problems will only emerge when I do, but I'll use it myself for a day or so first. I did this by allowing lexsave() and lexrestore() to save and restore different layers separately. This is made a bit hairy by the fact that zsh doesn't really have layers. However, it looks like I've basically got away with saving and restoring the parser and lexer while keeping history and input continuous. Otherwise, it's following the method previously suggested: parse the code once in situ, but throw that away when it gets turned into a string and simply reparse it as an argument to the command substitution when the time comes. So this isn't mega-efficient, but I don't think it's a problem and don't see a better way without quite serious structural changes that are definitely not worthwhile overall. I'm not 100% convinced by the ingetc() -> zshlex_raw_add() hack, but it seems to work. I'll probably follow up on those changes and turn lexsave() and lexrestore() into context save and restore dispatching to different modules, which is sort of starting to look like a respectable implementation. Also, that stuff in skipcomm() is calling out for structs (and it's partly there to work around the effect of save and restore, so that may want more detailed control). I haven't tried using ZCONTEXT_LEX and ZCONTEXT_PARSE separately, don't know if that's really a flier, and have no use case for that anyway. But they are logically separate... well... different modules. Note that one thing is logically in the wrong place (should be the parser) but practically in the right one (actually in the history), namely the command stack. Associating it with the history means that you get the following effect interactively with appropriate prompt settings: % echo $(if true; then cmdsubst then> case foo in cmdsubst then case> foo) cmdsubst then case> echo Hi from the parser cmdsubst then case> ;; cmdsubst then case> esac cmdsubst then> fi cmdsubst> ) Hi from the parser for which I'm claiming a small but unspecified number of extra brownie points. This single patch has got some experiments squashed out but I may auction my git repository for charity... diff --git a/Src/init.c b/Src/init.c index 3059087..080fc85 100644 --- a/Src/init.c +++ b/Src/init.c @@ -142,7 +142,8 @@ loop(int toplevel, int justonce) use_exit_printed = 0; intr(); /* interrupts on */ lexinit(); /* initialize lexical state */ - if (!(prog = parse_event())) { /* if we couldn't parse a list */ + if (!(prog = parse_event(ENDINPUT))) { + /* if we couldn't parse a list */ hend(NULL); if ((tok == ENDINPUT && !errflag) || (tok == LEXERR && (!isset(SHINSTDIN) || !toplevel)) || diff --git a/Src/input.c b/Src/input.c index 9552331..04dda5a 100644 --- a/Src/input.c +++ b/Src/input.c @@ -179,12 +179,12 @@ shingetline(void) /* Get the next character from the input. * Will call inputline() to get a new line where necessary. */ - + /**/ int ingetc(void) { - int lastc; + int lastc = ' '; if (lexstop) return ' '; @@ -196,7 +196,7 @@ ingetc(void) continue; if (((inbufflags & INP_LINENO) || !strin) && lastc == '\n') lineno++; - return lastc; + break; } /* @@ -208,7 +208,7 @@ ingetc(void) */ if (!inbufct && (strin || errflag)) { lexstop = 1; - return ' '; + break; } /* If the next element down the input stack is a continuation of * this, use it. @@ -219,8 +219,10 @@ ingetc(void) } /* As a last resort, get some more input */ if (inputline()) - return ' '; + break; } + zshlex_raw_add(lastc); + return lastc; } /* Read a line from the current command stream and store it as input */ @@ -426,6 +428,7 @@ inungetc(int c) inbufleft = 0; inbuf = inbufptr = ""; } + zshlex_raw_back(); } } diff --git a/Src/lex.c b/Src/lex.c index 4addf80..d440f3d 100644 --- a/Src/lex.c +++ b/Src/lex.c @@ -148,6 +148,16 @@ mod_export int parend; /**/ mod_export int nocomments; +/* add raw input characters while parsing command substitution */ + +/**/ +static int lex_add_raw; + +/* variables associated with the above */ + +static char *tokstr_raw, *bptr_raw; +static int len_raw, bsiz_raw; + /* text of punctuation tokens */ /**/ @@ -216,6 +226,11 @@ struct lexstack { char *bptr; int bsiz; int len; + int lex_add_raw; + char *tokstr_raw; + char *bptr_raw; + int bsiz_raw; + int len_raw; short *chwords; int chwordlen; int chwordpos; @@ -241,89 +256,121 @@ struct lexstack { static struct lexstack *lstack = NULL; -/* save the lexical state */ +/* save the context or parts thereof */ /* is this a hack or what? */ /**/ mod_export void -lexsave(void) +lexsave_partial(int parts) { struct lexstack *ls; ls = (struct lexstack *)malloc(sizeof(struct lexstack)); - ls->incmdpos = incmdpos; - ls->incond = incond; - ls->incasepat = incasepat; - ls->dbparens = dbparens; - ls->isfirstln = isfirstln; - ls->isfirstch = isfirstch; - ls->histactive = histactive; - ls->histdone = histdone; - ls->lexflags = lexflags; - ls->stophist = stophist; - stophist = 0; - if (!lstack) { - /* top level, make this version visible to ZLE */ - zle_chline = chline; - /* ensure line stored is NULL-terminated */ - if (hptr) - *hptr = '\0'; + if (parts & ZCONTEXT_LEX) { + ls->incmdpos = incmdpos; + ls->incond = incond; + ls->incasepat = incasepat; + ls->dbparens = dbparens; + ls->isfirstln = isfirstln; + ls->isfirstch = isfirstch; + ls->lexflags = lexflags; + + ls->tok = tok; + ls->isnewlin = isnewlin; + ls->tokstr = tokstr; + ls->zshlextext = zshlextext; + ls->bptr = bptr; + ls->bsiz = bsiz; + ls->len = len; + ls->lex_add_raw = lex_add_raw; + ls->tokstr_raw = tokstr_raw; + ls->bptr_raw = bptr_raw; + ls->bsiz_raw = bsiz_raw; + ls->len_raw = len_raw; + ls->lexstop = lexstop; + ls->toklineno = toklineno; + + tokstr = zshlextext = bptr = NULL; + bsiz = 256; + tokstr_raw = bptr_raw = NULL; + bsiz_raw = len_raw = lex_add_raw = 0; + + inredir = 0; + } + if (parts & ZCONTEXT_HIST) { + if (!lstack) { + /* top level, make this version visible to ZLE */ + zle_chline = chline; + /* ensure line stored is NULL-terminated */ + if (hptr) + *hptr = '\0'; + } + ls->histactive = histactive; + ls->histdone = histdone; + ls->stophist = stophist; + ls->hline = chline; + ls->hptr = hptr; + ls->chwords = chwords; + ls->chwordlen = chwordlen; + ls->chwordpos = chwordpos; + ls->hwgetword = hwgetword; + ls->hgetc = hgetc; + ls->hungetc = hungetc; + ls->hwaddc = hwaddc; + ls->hwbegin = hwbegin; + ls->hwend = hwend; + ls->addtoline = addtoline; + ls->hlinesz = hlinesz; + /* + * We save and restore the command stack with history + * as it's visible to the user interactively, so if + * we're preserving history state we'll continue to + * show the current set of commands from input. + */ + ls->cstack = cmdstack; + ls->csp = cmdsp; + + stophist = 0; + chline = NULL; + hptr = NULL; + histactive = 0; + cmdstack = (unsigned char *)zalloc(CMDSTACKSZ); + cmdsp = 0; + } + if (parts & ZCONTEXT_PARSE) { + ls->hdocs = hdocs; + ls->eclen = eclen; + ls->ecused = ecused; + ls->ecnpats = ecnpats; + ls->ecbuf = ecbuf; + ls->ecstrs = ecstrs; + ls->ecsoffs = ecsoffs; + ls->ecssub = ecssub; + ls->ecnfunc = ecnfunc; + ecbuf = NULL; + hdocs = NULL; } - ls->hline = chline; - chline = NULL; - ls->hptr = hptr; - hptr = NULL; - ls->hlinesz = hlinesz; - ls->cstack = cmdstack; - ls->csp = cmdsp; - cmdstack = (unsigned char *)zalloc(CMDSTACKSZ); - ls->tok = tok; - ls->isnewlin = isnewlin; - ls->tokstr = tokstr; - ls->zshlextext = zshlextext; - ls->bptr = bptr; - tokstr = zshlextext = bptr = NULL; - ls->bsiz = bsiz; - bsiz = 256; - ls->len = len; - ls->chwords = chwords; - ls->chwordlen = chwordlen; - ls->chwordpos = chwordpos; - ls->hwgetword = hwgetword; - ls->lexstop = lexstop; - ls->hdocs = hdocs; - ls->hgetc = hgetc; - ls->hungetc = hungetc; - ls->hwaddc = hwaddc; - ls->hwbegin = hwbegin; - ls->hwend = hwend; - ls->addtoline = addtoline; - ls->eclen = eclen; - ls->ecused = ecused; - ls->ecnpats = ecnpats; - ls->ecbuf = ecbuf; - ls->ecstrs = ecstrs; - ls->ecsoffs = ecsoffs; - ls->ecssub = ecssub; - ls->ecnfunc = ecnfunc; - ls->toklineno = toklineno; - cmdsp = 0; - inredir = 0; - hdocs = NULL; - histactive = 0; - ecbuf = NULL; ls->next = lstack; lstack = ls; } -/* restore lexical state */ +/* save context in full */ /**/ mod_export void -lexrestore(void) +lexsave(void) +{ + lexsave_partial(ZCONTEXT_HIST|ZCONTEXT_LEX|ZCONTEXT_PARSE); +} + +/* restore context or part therefore */ + +/**/ +mod_export void +lexrestore_partial(int parts) { struct lexstack *ln = lstack; @@ -332,65 +379,89 @@ lexrestore(void) queue_signals(); lstack = lstack->next; - if (!lstack) { - /* Back to top level: don't need special ZLE value */ - DPUTS(ln->hline != zle_chline, "BUG: Ouch, wrong chline for ZLE"); - zle_chline = NULL; + if (parts & ZCONTEXT_LEX) { + incmdpos = ln->incmdpos; + incond = ln->incond; + incasepat = ln->incasepat; + dbparens = ln->dbparens; + isfirstln = ln->isfirstln; + isfirstch = ln->isfirstch; + lexflags = ln->lexflags; + tok = ln->tok; + isnewlin = ln->isnewlin; + tokstr = ln->tokstr; + zshlextext = ln->zshlextext; + bptr = ln->bptr; + bsiz = ln->bsiz; + len = ln->len; + lex_add_raw = ln->lex_add_raw; + tokstr_raw = ln->tokstr_raw; + bptr_raw = ln->bptr_raw; + bsiz_raw = ln->bsiz_raw; + len_raw = ln->len_raw; + lexstop = ln->lexstop; + toklineno = ln->toklineno; + } + + if (parts & ZCONTEXT_HIST) { + if (!lstack) { + /* Back to top level: don't need special ZLE value */ + DPUTS(ln->hline != zle_chline, "BUG: Ouch, wrong chline for ZLE"); + zle_chline = NULL; + } + histactive = ln->histactive; + histdone = ln->histdone; + stophist = ln->stophist; + chline = ln->hline; + hptr = ln->hptr; + chwords = ln->chwords; + chwordlen = ln->chwordlen; + chwordpos = ln->chwordpos; + hwgetword = ln->hwgetword; + hgetc = ln->hgetc; + hungetc = ln->hungetc; + hwaddc = ln->hwaddc; + hwbegin = ln->hwbegin; + hwend = ln->hwend; + addtoline = ln->addtoline; + hlinesz = ln->hlinesz; + if (cmdstack) + zfree(cmdstack, CMDSTACKSZ); + cmdstack = ln->cstack; + cmdsp = ln->csp; + } + + if (parts & ZCONTEXT_PARSE) { + if (ecbuf) + zfree(ecbuf, eclen); + + hdocs = ln->hdocs; + eclen = ln->eclen; + ecused = ln->ecused; + ecnpats = ln->ecnpats; + ecbuf = ln->ecbuf; + ecstrs = ln->ecstrs; + ecsoffs = ln->ecsoffs; + ecssub = ln->ecssub; + ecnfunc = ln->ecnfunc; + + errflag &= ~ERRFLAG_ERROR; } - incmdpos = ln->incmdpos; - incond = ln->incond; - incasepat = ln->incasepat; - dbparens = ln->dbparens; - isfirstln = ln->isfirstln; - isfirstch = ln->isfirstch; - histactive = ln->histactive; - histdone = ln->histdone; - lexflags = ln->lexflags; - stophist = ln->stophist; - chline = ln->hline; - hptr = ln->hptr; - if (cmdstack) - zfree(cmdstack, CMDSTACKSZ); - cmdstack = ln->cstack; - cmdsp = ln->csp; - tok = ln->tok; - isnewlin = ln->isnewlin; - tokstr = ln->tokstr; - zshlextext = ln->zshlextext; - bptr = ln->bptr; - bsiz = ln->bsiz; - len = ln->len; - chwords = ln->chwords; - chwordlen = ln->chwordlen; - chwordpos = ln->chwordpos; - hwgetword = ln->hwgetword; - lexstop = ln->lexstop; - hdocs = ln->hdocs; - hgetc = ln->hgetc; - hungetc = ln->hungetc; - hwaddc = ln->hwaddc; - hwbegin = ln->hwbegin; - hwend = ln->hwend; - addtoline = ln->addtoline; - if (ecbuf) - zfree(ecbuf, eclen); - eclen = ln->eclen; - ecused = ln->ecused; - ecnpats = ln->ecnpats; - ecbuf = ln->ecbuf; - ecstrs = ln->ecstrs; - ecsoffs = ln->ecsoffs; - ecssub = ln->ecssub; - ecnfunc = ln->ecnfunc; - hlinesz = ln->hlinesz; - toklineno = ln->toklineno; - errflag &= ~ERRFLAG_ERROR; free(ln); unqueue_signals(); } +/* complete restore context */ + +/**/ +mod_export void +lexrestore(void) +{ + lexrestore_partial(ZCONTEXT_HIST|ZCONTEXT_LEX|ZCONTEXT_PARSE); +} + /**/ void zshlex(void) @@ -1905,80 +1976,151 @@ exalias(void) return 0; } -/* skip (...) */ +/**/ +void +zshlex_raw_add(int c) +{ + if (!lex_add_raw) + return; + + *bptr_raw++ = c; + if (bsiz_raw == ++len_raw) { + int newbsiz = bsiz_raw * 2; + + tokstr_raw = (char *)hrealloc(tokstr_raw, bsiz_raw, newbsiz); + bptr_raw = tokstr_raw + len_raw; + memset(bptr_raw, 0, newbsiz - bsiz_raw); + bsiz_raw = newbsiz; + } +} + +/**/ +void +zshlex_raw_back(void) +{ + if (!lex_add_raw) + return; + bptr_raw--; + len_raw--; +} + +/* + * Skip (...) for command-style substitutions: $(...), <(...), >(...) + * + * In order to ensure we don't stop at closing parentheses with + * some other syntactic significance, we'll parse the input until + * we find an unmatched closing parenthesis. However, we'll throw + * away the result of the parsing and just keep the string we've built + * up on the way. + */ /**/ static int skipcomm(void) { - int pct = 1, c, start = 1; + char *new_tokstr, *new_bptr = bptr_raw; + int new_len, new_bsiz, new_lexstop, new_lex_add_raw; cmdpush(CS_CMDSUBST); SETPARBEGIN - c = Inpar; - do { - int iswhite; - add(c); - c = hgetc(); - if (itok(c) || lexstop) - break; - iswhite = inblank(c); - switch (c) { - case '(': - pct++; - break; - case ')': - pct--; - break; - case '\\': - add(c); - c = hgetc(); - break; - case '\'': { - int strquote = bptr[-1] == '$'; - add(c); - STOPHIST - while ((c = hgetc()) != '\'' && !lexstop) { - if (c == '\\' && strquote) { - add(c); - c = hgetc(); - } - add(c); - } - ALLOWHIST - break; - } - case '\"': - add(c); - while ((c = hgetc()) != '\"' && !lexstop) - if (c == '\\') { - add(c); - add(hgetc()); - } else - add(c); - break; - case '`': - add(c); - while ((c = hgetc()) != '`' && !lexstop) - if (c == '\\') - add(c), add(hgetc()); - else - add(c); - break; - case '#': - if (start) { - add(c); - while ((c = hgetc()) != '\n' && !lexstop) - add(c); - iswhite = 1; - } - break; + add(Inpar); + + new_lex_add_raw = lex_add_raw + 1; + if (!lex_add_raw) { + /* + * We'll combine the string so far with the input + * read in for the command substitution. To do this + * we'll just propagate the current tokstr etc. as the + * variables used for adding raw input, and + * ensure we swap those for the real tokstr etc. at the end. + * + * However, we need to save and restore the rest of the + * lexical and parse state as we're effectively parsing + * an internal string. Because we're still parsing it from + * the original input source (we have to --- we don't know + * when to stop inputting it otherwise and can't rely on + * the input being recoverable until we've read it) we need + * to keep the same history context. + */ + new_tokstr = tokstr; + new_bptr = bptr; + new_len = len; + new_bsiz = bsiz; + + lexsave_partial(ZCONTEXT_LEX|ZCONTEXT_PARSE); + } else { + /* + * Set up for nested command subsitution, however + * we don't actually need the string until we get + * back to the top level and recover the lot. + * The $() body just appears empty. + * + * We do need to propagate the raw variables which would + * otherwise by cleared, though. + */ + new_tokstr = tokstr_raw; + new_bptr = bptr_raw; + new_len = len_raw; + new_bsiz = bsiz_raw; + + lexsave_partial(ZCONTEXT_LEX|ZCONTEXT_PARSE); + } + tokstr_raw = new_tokstr; + bsiz_raw = new_bsiz; + len_raw = new_len; + bptr_raw = new_bptr; + lex_add_raw = new_lex_add_raw; + + if (!parse_event(OUTPAR) || tok != OUTPAR) + lexstop = 1; + /* Outpar lexical token gets added in caller if present */ + + /* + * We're going to keep the full raw input string + * as the current token string after popping the stack. + */ + new_tokstr = tokstr_raw; + new_bptr = bptr_raw; + new_len = len_raw; + new_bsiz = bsiz_raw; + /* + * We're also going to propagate the lexical state: + * if we couldn't parse the command substitution we + * can't continue. + */ + new_lexstop = lexstop; + + lexrestore_partial(ZCONTEXT_LEX|ZCONTEXT_PARSE); + + if (lex_add_raw) { + /* + * Keep going, so retain the raw variables. + */ + tokstr_raw = new_tokstr; + bptr_raw = new_bptr; + len_raw = new_len; + bsiz_raw = new_bsiz; + } else { + if (!new_lexstop) { + /* Ignore the ')' added on input */ + new_len--; + *--new_bptr = '\0'; } - start = iswhite; + + /* + * Convince the rest of lex.c we were examining a string + * all along. + */ + tokstr = new_tokstr; + bptr = new_bptr; + len = new_len; + bsiz = new_bsiz; + lexstop = new_lexstop; } - while (pct); + if (!lexstop) SETPAREND cmdpop(); + return lexstop; } diff --git a/Src/parse.c b/Src/parse.c index c1709e0..fa37ca3 100644 --- a/Src/parse.c +++ b/Src/parse.c @@ -361,7 +361,8 @@ ecstrcode(char *s) /* Initialise wordcode buffer. */ -static void +/**/ +void init_parse(void) { if (ecbuf) zfree(ecbuf, eclen); @@ -443,11 +444,15 @@ clear_hdocs() * 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(void) +parse_event(int endtok) { tok = ENDINPUT; incmdpos = 1; @@ -455,36 +460,42 @@ parse_event(void) zshlex(); init_parse(); - if (!par_event()) { + 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); } /**/ -static int -par_event(void) +int +par_event(int endtok) { int r = 0, p, c = 0; while (tok == SEPER) { - if (isnewlin > 0) + if (isnewlin > 0 && endtok == ENDINPUT) return 0; zshlex(); } if (tok == ENDINPUT) return 0; + if (tok == endtok) + return 0; p = ecadd(0); if (par_sublist(&c)) { - if (tok == ENDINPUT) { + 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) + if (isnewlin <= 0 || endtok != ENDINPUT) zshlex(); r = 1; } else if (tok == AMPER) { @@ -513,7 +524,7 @@ par_event(void) } else { int oec = ecused; - if (!par_event()) { + if (!par_event(endtok)) { ecused = oec; ecbuf[p] |= wc_bdata(Z_END); } diff --git a/Src/zsh.h b/Src/zsh.h index b366e0f..475b782 100644 --- a/Src/zsh.h +++ b/Src/zsh.h @@ -421,6 +421,15 @@ enum { #define META_HEAPDUP 6 #define META_HREALLOC 7 +/* Context to save and restore (bit fields) */ +enum { + /* History mechanism */ + ZCONTEXT_HIST = (1<<0), + /* Lexical analyser */ + ZCONTEXT_LEX = (1<<1), + /* Parser */ + ZCONTEXT_PARSE = (1<<2) +}; /**************************/ /* Abstract types for zsh */ diff --git a/Test/D08cmdsubst.ztst b/Test/D08cmdsubst.ztst index 5661b0a..a4c69a0 100644 --- a/Test/D08cmdsubst.ztst +++ b/Test/D08cmdsubst.ztst @@ -106,3 +106,45 @@ >34 >" >" OK + + echo $(case foo in + foo) + echo This test worked. + ;; + bar) + echo This test failed in a rather bizarre way. + ;; + *) + echo This test failed. + ;; + esac) +0:Parsing of command substitution with unmatched parentheses: case, basic +>This test worked. + + echo "$(case bar in + foo) + echo This test spoobed. + ;; + bar) + echo This test plurbled. + ;; + *) + echo This test bzonked. + ;; + esac)" +0:Parsing of command substitution with unmatched parentheses: case with quotes +>This test plurbled. + + echo before $( + echo start; echo unpretentious | + while read line; do + case $line in + u*) + print Word began with u + print and ended with a crunch + ;; + esac + done | sed -e 's/Word/Universe/'; echo end + ) after +0:Parsing of command substitution with ummatched parentheses: with frills +>before start Universe began with u and ended with a crunch end after -- Peter Stephenson Principal Software Engineer Tel: +44 (0)1223 434724 Samsung Cambridge Solution Centre St John's House, St John's Innovation Park, Cowley Road, Cambridge, CB4 0DS, UK