1
0
Fork 0
mirror of git://git.code.sf.net/p/zsh/code synced 2025-06-11 19:18:01 +02:00

52864: Change ${|var|...} to ${{var} ...}, limit local REPLY to ${|...}

This commit is contained in:
Bart Schaefer 2024-04-01 22:35:33 -07:00
parent 5ba43e58c2
commit 76019f7174
5 changed files with 150 additions and 56 deletions

View file

@ -1,5 +1,9 @@
2024-04-01 Bart Schaefer <schaefer@zsh.org> 2024-04-01 Bart Schaefer <schaefer@zsh.org>
* 52864: Src/lex.c, Src/subst.c, Test/D10nofork.ztst,
Test/V10private.ztst: Change ${|var|...} to ${{var} ...},
limit local REPLY behavior to ${|...}, update tests.
* 52781 (and typo fix): Src/hashtable.c: HIST IGNORE_DUPS treats * 52781 (and typo fix): Src/hashtable.c: HIST IGNORE_DUPS treats
whitespace as significant when HIST_REDUCE_BLANKS is also set. whitespace as significant when HIST_REDUCE_BLANKS is also set.

View file

@ -1423,7 +1423,7 @@ gettokstr(int c, int sub)
if (lexstop) if (lexstop)
break; break;
if (!cmdsubst && in_brace_param && act == LX2_STRING && if (!cmdsubst && in_brace_param && act == LX2_STRING &&
(c == '|' || c == Bar || inblank(c))) { (c == '|' || c == Bar || c == '{' || c == Inbrace || inblank(c))) {
cmdsubst = in_brace_param; cmdsubst = in_brace_param;
cmdpush(CS_CURSH); cmdpush(CS_CURSH);
} else if (in_pattern == 2 && c != '/') } else if (in_pattern == 2 && c != '/')

View file

@ -1866,10 +1866,11 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
* joining the array into a string (for compatibility with ksh/bash). * joining the array into a string (for compatibility with ksh/bash).
*/ */
int quoted_array_with_offset = 0; int quoted_array_with_offset = 0;
/* Indicates ${|...;} */ /*
char *rplyvar = NULL; * Nofork substitution controls
/* Indicates ${ ... ;} */ */
char *rplytmp = NULL; char *rplyvar = NULL; /* Indicates ${|...;} or ${{var} ...;} */
char *rplytmp = NULL; /* Indicates ${ ... ;} */
*s++ = '\0'; *s++ = '\0';
/* /*
@ -1897,14 +1898,16 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
* flags in parentheses, but also one ksh hack. * flags in parentheses, but also one ksh hack.
*/ */
if (c == Inbrace) { if (c == Inbrace) {
/* The command string to be run by ${|...;} */ /* For processing nofork command substitution string */
char *cmdarg = NULL; char *cmdarg = NULL, *endvar = NULL, inchar = *++s;
char *outbracep = s, sav = *s;
Param rplypm = NULL;
size_t slen = 0; size_t slen = 0;
int trim = (!EMULATION(EMULATE_ZSH)) ? 2 : !qt; int trim = (!EMULATION(EMULATE_ZSH)) ? 2 : !qt;
inbrace = 1;
s++;
/* Short-path for the nofork command substitution ${|cmd;} inbrace = 1; /* Outer scope boolean, see above */
/* Handling for nofork command substitution e.g. ${|cmd;}
* See other comments about kludges for why this is here. * See other comments about kludges for why this is here.
* *
* The command string is extracted and executed, and the * The command string is extracted and executed, and the
@ -1913,48 +1916,77 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
* should not be part of command substitution in any case. * should not be part of command substitution in any case.
* Use ${(U)${|cmd;}} as you would for ${(U)$(cmd;)}. * Use ${(U)${|cmd;}} as you would for ${(U)$(cmd;)}.
*/ */
if (*s == '|' || *s == Bar || inblank(*s)) { if (inchar == '|' || inchar == Bar || inblank(inchar)) {
char *outbracep = s;
char sav = *s;
*s = Inbrace; *s = Inbrace;
if (skipparens(Inbrace, Outbrace, &outbracep) == 0) { if (skipparens(Inbrace, Outbrace, &outbracep) == 0)
slen = outbracep - s - 1; slen = outbracep - s - 1;
if ((*s = sav) != Bar) { *s = sav;
sav = *outbracep; if (inchar == '|')
*outbracep = '\0'; inchar = Bar; /* Simplify later compares */
tokenize(s); } else if (inchar == '{' || inchar == Inbrace) {
*outbracep = sav; *s = Inbrace;
if ((outbracep = itype_end(s+1, INAMESPC, 0))) {
if (*outbracep == Inbrack &&
(outbracep = parse_subscript(++outbracep, 1, ']')))
++outbracep;
}
/* If we reached the first close brace, find the last */
if (outbracep && *outbracep == Outbrace) {
char outchar = inchar == Inbrace ? Outbrace : '}';
endvar = outbracep++;
/* Require space to avoid ${{var}} typo for ${${var}} */
if (!inblank(*outbracep)) {
zerr("bad substitution");
return NULL;
} }
*endvar = '|'; /* Almost anything but braces/brackets */
outbracep = s;
if (skipparens(Inbrace, outchar, &outbracep) == 0)
*endvar = Outbrace;
else { /* Never happens? */
*endvar = outchar;
outbracep = endvar + 1;
}
slen = outbracep - s - 1;
if (inchar != Inbrace)
outbracep[-1] = Outbrace;
*s = sav;
inchar = Inbrace; /* Simplify later compares */
} else {
zerr("bad substitution");
return NULL;
} }
} }
if (slen > 1) { if (slen > 1) {
char *outbracep = s + slen; char *outbracep = s + slen;
if (!itok(*s) || inblank(inchar)) {
/* This tokenize() is important */
char sav = *outbracep;
*outbracep = '\0';
tokenize(s);
*outbracep = sav;
}
if (*outbracep == Outbrace) { if (*outbracep == Outbrace) {
if ((rplyvar = itype_end(s+1, INAMESPC, 0))) { if (endvar == s+1) {
if (*rplyvar == Inbrack && /* For consistency with ${} we allow ${{}...} */
(rplyvar = parse_subscript(++rplyvar, 1, ']')))
++rplyvar;
}
if (rplyvar == s+1 && *rplyvar == Bar) {
/* Is ${||...} a subtitution error or a syntax error?
zerr("bad substitution");
return NULL;
*/
rplyvar = NULL; rplyvar = NULL;
} }
if (rplyvar && *rplyvar == Bar) { if (endvar && *endvar == Outbrace) {
cmdarg = dupstrpfx(rplyvar+1, outbracep-rplyvar-1); cmdarg = dupstrpfx(endvar+1, outbracep-endvar-1);
rplyvar = dupstrpfx(s+1,rplyvar-s-1); rplyvar = dupstrpfx(s+1,endvar-s-1);
} else { } else {
cmdarg = dupstrpfx(s+1, outbracep-s-1); cmdarg = dupstrpfx(s+1, outbracep-s-1);
rplyvar = "REPLY"; rplyvar = "REPLY";
} }
if (inblank(*s)) { if (inblank(inchar)) {
/* /*
* Admittedly a hack. Take advantage of the enforced * Admittedly a hack. Take advantage of the added
* locality of REPLY and the semantics of $(<file) to * parameter scope and the semantics of $(<file) to
* construct a command to write/read a temporary file. * construct a command to write/read a temporary file.
* Then fall through to the regular handling of $REPLY * Then fall through to the regular parameter handling
* to manage word splitting, expansion flags, etc. * to manage word splitting, expansion flags, etc.
*/ */
char *outfmt = ">| %s {\n%s\n;}"; /* 13 */ char *outfmt = ">| %s {\n%s\n;}"; /* 13 */
@ -1977,22 +2009,39 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
} }
if (rplyvar) { if (rplyvar) {
Param pm; /* char *rplyval = getsparam("REPLY"); cf. Future? below */
/* char *rplyval = getsparam("REPLY"); */
startparamscope(); /* "local" behaves as if in a function */ startparamscope(); /* "local" behaves as if in a function */
pm = createparam("REPLY", PM_LOCAL|PM_UNSET); if (inchar == Bar) {
if (pm) /* Shouldn't createparam() do this? */ /* rplyvar should be REPLY at this point, but create
pm->level = locallevel; * hardwired name anyway to expose any bugs elsewhere
/* if (rplyval) setsparam("REPLY", ztrdup(rplyval)); */ */
rplypm = createparam("REPLY", PM_LOCAL|PM_UNSET|PM_HIDE);
if (rplypm) /* Shouldn't createparam() do this? */
rplypm->level = locallevel;
/* Future? Expose global value of $REPLY if any? */
/* if (rplyval) setsparam("REPLY", ztrdup(rplyval)); */
} else if (inblank(inchar)) {
rplypm = createparam(".zsh.cmdsubst",
PM_LOCAL|PM_UNSET|PM_HIDE|
PM_READONLY_SPECIAL);
if (rplypm)
rplypm->level = locallevel;
}
if (inchar != Inbrace && !rplypm) {
zerr("failed to create scope for command substitution");
return NULL;
}
} }
if (rplyvar && cmdarg && *cmdarg) { if (rplyvar && cmdarg && *cmdarg) {
int obreaks = breaks; int obreaks = breaks;
Eprog cmdprog; Eprog cmdprog;
/* Execute the shell command */ /* Execute the shell command */
queue_signals();
untokenize(cmdarg); untokenize(cmdarg);
cmdprog = parse_string(cmdarg, 0); cmdprog = parse_string(cmdarg, 0);
if (cmdprog) { if (cmdprog) {
/* exec.c handles dont_queue_signals() */
execode(cmdprog, 1, 0, "cmdsubst"); execode(cmdprog, 1, 0, "cmdsubst");
cmdoutval = lastval; cmdoutval = lastval;
/* "return" behaves as if in a function */ /* "return" behaves as if in a function */
@ -2002,6 +2051,8 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
} }
} else /* parse error */ } else /* parse error */
errflag |= ERRFLAG_ERROR; errflag |= ERRFLAG_ERROR;
if (rplypm)
rplypm->node.flags &= ~PM_READONLY_SPECIAL;
if (rplytmp && !errflag) { if (rplytmp && !errflag) {
int onoerrs = noerrs, rplylen; int onoerrs = noerrs, rplylen;
noerrs = 2; noerrs = 2;
@ -2017,15 +2068,16 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
} }
noerrs = onoerrs; noerrs = onoerrs;
if (rplylen >= 0) if (rplylen >= 0)
setsparam("REPLY", metafy(cmdarg, rplylen, META_REALLOC)); setsparam(rplyvar, metafy(cmdarg, rplylen, META_REALLOC));
} }
unqueue_signals();
} }
if (rplytmp) if (rplytmp)
unlink(rplytmp); unlink(rplytmp);
if (rplyvar) { if (rplyvar) {
if (strcmp(rplyvar, "REPLY") == 0) { if (inchar != Inbrace) {
if ((val = dupstring(getsparam("REPLY")))) if ((val = dupstring(getsparam(rplyvar))))
vunset = 0; vunset = 0;
else { else {
vunset = 1; vunset = 1;

View file

@ -14,6 +14,28 @@
0:Basic substitution and REPLY scoping 0:Basic substitution and REPLY scoping
>INNER OUTER >INNER OUTER
reply=(x OUTER x)
purl ${{reply} reply=(\{ INNER \})} $reply
0:Basic substitution, brace quoting, and array result
>{
>INNER
>}
>{
>INNER
>}
() {
setopt localoptions ignorebraces
purl ${{reply} reply=({ INNER })} $reply
}
0:Basic substitution, ignorebraces, and array result
>{
>INNER
>}
>{
>INNER
>}
purr ${| REPLY=first}:${| REPLY=second}:$REPLY purr ${| REPLY=first}:${| REPLY=second}:$REPLY
0:re-scoping of REPLY in one statement 0:re-scoping of REPLY in one statement
>first:second:OUTER >first:second:OUTER
@ -229,7 +251,7 @@ F:Why not use this error in the previous case as well?
>26 >26
unset reply unset reply
purl ${|reply| reply=(1 2 ${| REPLY=3 } 4) } purl ${{reply} reply=(1 2 ${| REPLY=3 } 4) }
typeset -p reply typeset -p reply
0:array behavior with global assignment 0:array behavior with global assignment
>1 >1
@ -315,7 +337,7 @@ F:status of "print" should hide return
unset zz unset zz
outer=GLOBAL outer=GLOBAL
purr "${|zz| purr "${{zz}
local outer=LOCAL local outer=LOCAL
zz=NONLOCAL zz=NONLOCAL
} $outer $?" } $outer $?"
@ -440,7 +462,8 @@ F:must do this before evaluating the next test block
0:ignored braces, part 1 0:ignored braces, part 1
>buried} >buried}
purr "${ purr ${REPLY:-buried}}}" # Global $REPLY still set from earlier test
purr "${ purr ${REPLY:+buried}}}"
0:ignored braces, part 2 0:ignored braces, part 2
>buried >buried
>} >}
@ -453,6 +476,7 @@ F:must do this before evaluating the next test block
1:ignored braces, part 4 1:ignored braces, part 4
?(eval):3: parse error near `}' ?(eval):3: parse error near `}'
unsetopt ignorebraces
# "break" blocks function calls in outer loop # "break" blocks function calls in outer loop
# Could use print, but that might get fixed # Could use print, but that might get fixed
repeat 3 do purr ${ repeat 3 do purr ${
@ -467,11 +491,25 @@ F:must do this before evaluating the next test block
?1 ?1
?2 ?2
print -u $ZTST_fd ${ZTST_testname}: TEST COMPLETE # Cannot "purr": break skips pending function calls
0:make sure we got to the end # Use "repeat" to avoid infinite loop on failure
F:some tests might silently break the test harness repeat 3 do; echo ${|REPLY=x; break }; done
repeat 3 do; echo ${{x} x=y; break }; done
repeat 3 do; echo ${ echo z; break }; done
0:break after assignment completes the assignment
>x
>y
>z
# Subshell because error exits
( purr ${ echo ${unset?oops} } )
1:error handling (without crashing)
*?*unset: oops
purr ${ .zsh.cmdsubst=error }
1:reserved parameter name (without crashing)
*?*.zsh.cmdsubst: can't modify read-only parameter
%clean %clean
unfunction purr purl unfunction purr purl
unsetopt ignorebraces

View file

@ -497,7 +497,7 @@ F:Better if caught in checkclobberparam() but exec.c doesn't know scope
() { () {
private z=outer private z=outer
print ${(t)z} $z print ${(t)z} $z
print ${| REPLY=${|z| z=nofork} } print ${| REPLY=${{z} z=nofork} }
print ${(t)z} $z print ${(t)z} $z
} }
0:nofork may write to private in calling function 0:nofork may write to private in calling function
@ -518,9 +518,9 @@ F:Better if caught in checkclobberparam() but exec.c doesn't know scope
() { () {
private z=outer private z=outer
print ${(t)z} $z print ${(t)z} $z
print ${|z| print ${{z}
private q private q
z=${|q| q=nofork} z=${{q} q=nofork}
} }
print ${(t)z} $z print ${(t)z} $z
} }
@ -533,7 +533,7 @@ F:Better if caught in checkclobberparam() but exec.c doesn't know scope
print ${| print ${|
() { REPLY="{$q}" } () { REPLY="{$q}" }
} }
print ${|q| print ${{q}
() { q=nofork } () { q=nofork }
} }
} }