1
0
Fork 0
mirror of git://git.code.sf.net/p/zsh/code synced 2024-06-01 12:56:04 +02:00

10547: (#s) and (#e) pattern assertions

This commit is contained in:
Peter Stephenson 2000-04-06 18:44:01 +00:00
parent 181811bf80
commit d2330ba055
7 changed files with 875 additions and 276 deletions

View File

@ -1,3 +1,9 @@
2000-04-06 Peter Stephenson <pws@pwstephenson.fsnet.co.uk>
* 10547: Doc/Zsh/expn.yo, Misc/globtests, Src/pattern.c,
Src/subst.c, Test/11glob.ztst, Test/ztst.zsh: add
(#s) and (#e) to match at start and end of string.
2000-04-06 Andrew Main <zefram@zsh.org> 2000-04-06 Andrew Main <zefram@zsh.org>
* zefram2: Src/lex.c: Support "3&> foo" etc. * zefram2: Src/lex.c: Support "3&> foo" etc.

View File

@ -1299,6 +1299,17 @@ item(tt(a)var(num))(
Approximate matching: var(num) errors are allowed in the string matched by Approximate matching: var(num) errors are allowed in the string matched by
the pattern. The rules for this are described in the next subsection. the pattern. The rules for this are described in the next subsection.
) )
item(tt(s), tt(e))(
Unlike the other flags, these have only a local effect, and each must
appear on its own: `tt((#s))' and `tt((#e))' are the only valid forms.
The `tt((#s))' flag succeeds only at the start of the test string, and the
`tt((#e))' flag succeeds only at the end of the test string; they
correspond to `tt(^)' and `tt($)' in standard regular expressions. They
are useful for matching path segments in patterns. For example,
`tt(*((#s)|/)test((#e)|/)*)' matches a path segment `tt(test)' in any of
the following strings: tt(test), tt(test/at/start), tt(at/end/test),
tt(in/test/middle).
)
enditem() enditem()
For example, the test string tt(fooxx) can be matched by the pattern For example, the test string tt(fooxx) can be matched by the pattern

View File

@ -14,6 +14,13 @@ while read res str pat; do
(( failed++ )) (( failed++ ))
fi fi
done <<EOT done <<EOT
# a few simple things certain nameless idiots have been known to mess up
t foo~ foo~
t foo~ (foo~)
t foo~ (foo~|)
t foo.c *.c~boo*
f foo.c *.c~boo*~foo*
# closures
t fofo (fo#)# t fofo (fo#)#
t ffo (fo#)# t ffo (fo#)#
t foooofo (fo#)# t foooofo (fo#)#
@ -75,6 +82,7 @@ f mad.moo.cow (*~*.*).(*~*.*)
t moo.cow (^*.*).(^*.*) t moo.cow (^*.*).(^*.*)
f sane.moo.cow (^*.*).(^*.*) f sane.moo.cow (^*.*).(^*.*)
f mucca.pazza mu(^c#)?.pa(^z#)? f mucca.pazza mu(^c#)?.pa(^z#)?
f _foo~ _(|*[^~])
t fff ((^f)) t fff ((^f))
t fff ((^f)#) t fff ((^f)#)
t fff ((^f)##) t fff ((^f)##)
@ -94,6 +102,8 @@ t zoox (^z*|*x)
t foo (^foo)# t foo (^foo)#
f foob (^foo)b* f foob (^foo)b*
t foobb (^foo)b* t foobb (^foo)b*
f foob (*~foo)b*
t foobb (*~foo)b*
f zsh ^z* f zsh ^z*
t a%1X [[:alpha:][:punct:]]#[[:digit:]][^[:lower:]] t a%1X [[:alpha:][:punct:]]#[[:digit:]][^[:lower:]]
f a%1 [[:alpha:][:punct:]]#[[:digit:]][^[:lower:]] f a%1 [[:alpha:][:punct:]]#[[:digit:]][^[:lower:]]
@ -103,5 +113,71 @@ t :] [:]]#
t [ [[] t [ [[]
t ] []] t ] []]
t [] [^]]] t [] [^]]]
# Case insensitive matching
t fooxx (#i)FOOXX
f fooxx (#l)FOOXX
t FOOXX (#l)fooxx
f fooxx (#i)FOO(#I)X(#i)X
t fooXx (#i)FOO(#I)X(#i)X
t fooxx ((#i)FOOX)x
f fooxx ((#i)FOOX)X
f BAR (bar|(#i)foo)
t FOO (bar|(#i)foo)
t Modules (#i)*m*
t fooGRUD (#i)(bar|(#I)foo|(#i)rod)grud
f FOOGRUD (#i)(bar|(#I)foo|(#i)rod)grud
t readme (#i)readme~README|readme
# the readme doesn't get excluded the second time...
t readme (#i)readme~README|readme~README
# Ranges with backtracking
t 633 <1-1000>33
t 633 <-1000>33
t 633 <1->33
t 633 <->33
# Approximate matching
t READ.ME (#ia1)readme
f READ..ME (#ia1)readme
t README (#ia1)readm
t READM (#ia1)readme
t README (#ia1)eadme
t EADME (#ia1)readme
t READEM (#ia1)readme
f ADME (#ia1)readme
f README (#ia1)read
t bob (#a1)[b][b]
f bob (#a1)[b][b]a
t bob (#a1)[b]o[b]a
f bob (#a1)[c]o[b]
t abcd (#a2)XbcX
t abcd (#a2)ad
t ad (#a2)abcd
t abcd (#a2)bd
t bd (#a2)abcd
t badc (#a2)abcd
# This next one is a little tricky: a[d]bc[] = a[]bc[d]
t adbc (#a2)abcd
f dcba (#a2)abcd
# the next one is [d][cb][a] = [a][bc][d] with a transposition
t dcba (#a3)abcd
t aabaXaaabY (#a1)(a#b)#Y
t aabaXaaabY (#a1)(a#b)(a#b)Y
t aaXaaaaabY (#a1)(a#b)(a#b)Y
t aaaXaaabY (#a1)(a##b)##Y
t aaaXbaabY (#a1)(a##b)##Y
f read.me (#ia1)README~READ.ME
t read.me (#ia1)README~READ_ME
f read.me (#ia1)README~(#a1)READ_ME
t test *((#s)|/)test((#e)|/)*
t test/path *((#s)|/)test((#e)|/)*
t path/test *((#s)|/)test((#e)|/)*
t path/test/ohyes *((#s)|/)test((#e)|/)*
f atest *((#s)|/)test((#e)|/)*
f testy *((#s)|/)test((#e)|/)*
f testy/path *((#s)|/)test((#e)|/)*
f path/atest *((#s)|/)test((#e)|/)*
f atest/path *((#s)|/)test((#e)|/)*
f path/testy *((#s)|/)test((#e)|/)*
f path/testy/ohyes *((#s)|/)test((#e)|/)*
f path/atest/ohyes *((#s)|/)test((#e)|/)*
EOT EOT
print "$failed tests failed." print "$failed tests failed."

View File

@ -83,6 +83,8 @@ typedef union upat *Upat;
#define P_ONEHASH 0x06 /* node Match this (simple) thing 0 or more times. */ #define P_ONEHASH 0x06 /* node Match this (simple) thing 0 or more times. */
#define P_TWOHASH 0x07 /* node Match this (simple) thing 1 or more times. */ #define P_TWOHASH 0x07 /* node Match this (simple) thing 1 or more times. */
#define P_GFLAGS 0x08 /* long Match nothing and set globbing flags */ #define P_GFLAGS 0x08 /* long Match nothing and set globbing flags */
#define P_ISSTART 0x09 /* no Match start of string. */
#define P_ISEND 0x0a /* no Match end of string. */
/* numbered so we can test bit 5 for a branch */ /* numbered so we can test bit 5 for a branch */
#define P_BRANCH 0x20 /* node Match this alternative, or the next... */ #define P_BRANCH 0x20 /* node Match this alternative, or the next... */
#define P_WBRANCH 0x21 /* uc* node P_BRANCH, but match at least 1 char */ #define P_WBRANCH 0x21 /* uc* node P_BRANCH, but match at least 1 char */
@ -645,34 +647,44 @@ patcompbranch(int *flagp)
/* Globbing flags. */ /* Globbing flags. */
char *pp1 = patparse; char *pp1 = patparse;
int oldglobflags = patglobflags; int oldglobflags = patglobflags;
long assert;
patparse += (*patparse == '@') ? 3 : 2; patparse += (*patparse == '@') ? 3 : 2;
if (!patgetglobflags(&patparse)) if (!patgetglobflags(&patparse, &assert))
return 0; return 0;
if (pp1 == patstart) { if (assert) {
/* Right at start of pattern, the simplest case. /*
* Put them into the flags and don't emit anything. * Start/end assertion looking like flags, but
* actually handled as a normal node
*/ */
((Patprog)patout)->globflags = patglobflags; latest = patnode(assert);
continue; flags = 0;
} else if (!*patparse) {
/* Right at the end, so just leave the flags for
* the next Patprog in the chain to pick up.
*/
break;
}
/*
* Otherwise, we have to stick them in as a pattern
* matching nothing.
*/
if (oldglobflags != patglobflags) {
/* Flags changed */
union upat up;
latest = patnode(P_GFLAGS);
up.l = patglobflags;
patadd((char *)&up, 0, sizeof(union upat), 0);
} else { } else {
/* No effect. */ if (pp1 == patstart) {
continue; /* Right at start of pattern, the simplest case.
* Put them into the flags and don't emit anything.
*/
((Patprog)patout)->globflags = patglobflags;
continue;
} else if (!*patparse) {
/* Right at the end, so just leave the flags for
* the next Patprog in the chain to pick up.
*/
break;
}
/*
* Otherwise, we have to stick them in as a pattern
* matching nothing.
*/
if (oldglobflags != patglobflags) {
/* Flags changed */
union upat up;
latest = patnode(P_GFLAGS);
up.l = patglobflags;
patadd((char *)&up, 0, sizeof(union upat), 0);
} else {
/* No effect. */
continue;
}
} }
} else if (isset(EXTENDEDGLOB) && *patparse == Hat) { } else if (isset(EXTENDEDGLOB) && *patparse == Hat) {
/* /*
@ -707,10 +719,12 @@ patcompbranch(int *flagp)
/**/ /**/
int int
patgetglobflags(char **strp) patgetglobflags(char **strp, long *assertp)
{ {
char *nptr, *ptr = *strp; char *nptr, *ptr = *strp;
zlong ret; zlong ret;
*assertp = 0;
/* (#X): assumes we are still positioned on the first X */ /* (#X): assumes we are still positioned on the first X */
for (; *ptr && *ptr != Outpar; ptr++) { for (; *ptr && *ptr != Outpar; ptr++) {
switch (*ptr) { switch (*ptr) {
@ -763,12 +777,23 @@ patgetglobflags(char **strp)
patglobflags &= ~GF_MATCHREF; patglobflags &= ~GF_MATCHREF;
break; break;
case 's':
*assertp = P_ISSTART;
break;
case 'e':
*assertp = P_ISEND;
break;
default: default:
return 0; return 0;
} }
} }
if (*ptr != Outpar) if (*ptr != Outpar)
return 0; return 0;
/* Start/end assertions must appear on their own. */
if (*assertp && (*strp)[1] != Outpar)
return 0;
*strp = ptr + 1; *strp = ptr + 1;
return 1; return 1;
} }
@ -1989,6 +2014,14 @@ patmatch(Upat prog)
* anything here. * anything here.
*/ */
return 0; return 0;
case P_ISSTART:
if (patinput != patinstart)
fail = 1;
break;
case P_ISEND:
if (*patinput)
fail = 1;
break;
case P_END: case P_END:
if (!(fail = (*patinput && !(patflags & PAT_NOANCH)))) if (!(fail = (*patinput && !(patflags & PAT_NOANCH))))
return 1; return 1;
@ -2387,6 +2420,12 @@ patprop(Upat op)
case P_GFLAGS: case P_GFLAGS:
p = "GFLAGS"; p = "GFLAGS";
break; break;
case P_ISSTART:
p = "ISSTART";
break;
case P_ISEND:
p = "ISEND";
break;
case P_NOTHING: case P_NOTHING:
p = "NOTHING"; p = "NOTHING";
break; break;

View File

@ -42,27 +42,22 @@ char nulstring[] = {Nularg, '\0'};
* - Brace expansion * - Brace expansion
* - Tilde and equals substitution * - Tilde and equals substitution
* *
* Bits 0 and 1 of flags are used in filesub. * PF_* flags are defined in zsh.h
* bit 0 is set when we are doing MAGIC_EQUALSUBST or normal
* assignment but not a typeset.
* bit 1 is set on a real assignment (both typeset and normal).
* bit 2 is a flag to paramsubst (single word sub)
*/ */
/**/ /**/
void mod_export void
prefork(LinkList list, int flags) prefork(LinkList list, int flags)
{ {
LinkNode node; LinkNode node;
MUSTUSEHEAP("prefork");
for (node = firstnode(list); node; incnode(node)) { for (node = firstnode(list); node; incnode(node)) {
char *str, *str3; char *str, c;
str = str3 = (char *)getdata(node); str = (char *)getdata(node);
if ((*str == Inang || *str == Outang || *str == Equals) && if (((c = *str) == Inang || c == Outang || c == Equals) &&
str[1] == Inpar) { str[1] == Inpar) {
if (*str == Inang || *str == Outang) if (c == Inang || c == Outang)
setdata(node, (void *) getproc(str)); /* <(...) or >(...) */ setdata(node, (void *) getproc(str)); /* <(...) or >(...) */
else else
setdata(node, (void *) getoutputfile(str)); /* =(...) */ setdata(node, (void *) getoutputfile(str)); /* =(...) */
@ -70,20 +65,22 @@ prefork(LinkList list, int flags)
return; return;
} else { } else {
if (isset(SHFILEEXPANSION)) if (isset(SHFILEEXPANSION))
filesub((char **)getaddrdata(node), flags & 3); filesub((char **)getaddrdata(node),
if (!(node = stringsubst(list, node, flags & 4))) flags & (PF_TYPESET|PF_ASSIGN));
if (!(node = stringsubst(list, node, flags & PF_SINGLE)))
return; return;
} }
} }
for (node = firstnode(list); node; incnode(node)) { for (node = firstnode(list); node; incnode(node)) {
if (*(char *)getdata(node)) { if (*(char *)getdata(node)) {
remnulargs(getdata(node)); remnulargs(getdata(node));
if (unset(IGNOREBRACES) && !(flags & 4)) if (unset(IGNOREBRACES) && !(flags & PF_SINGLE))
while (hasbraces(getdata(node))) while (hasbraces(getdata(node)))
xpandbraces(list, &node); xpandbraces(list, &node);
if (unset(SHFILEEXPANSION)) if (unset(SHFILEEXPANSION))
filesub((char **)getaddrdata(node), flags & 3); filesub((char **)getaddrdata(node),
} else if (!(flags & 4)) flags & (PF_TYPESET|PF_ASSIGN));
} else if (!(flags & PF_SINGLE))
uremnode(list, node); uremnode(list, node);
if (errflag) if (errflag)
return; return;
@ -96,14 +93,16 @@ stringsubst(LinkList list, LinkNode node, int ssub)
{ {
int qt; int qt;
char *str3 = (char *)getdata(node); char *str3 = (char *)getdata(node);
char *str = str3; char *str = str3, c;
while (!errflag && *str) { while (!errflag && (c = *str)) {
if ((qt = *str == Qstring) || *str == String) if ((qt = c == Qstring) || c == String) {
if (str[1] == Inpar) { if ((c = str[1]) == Inpar) {
if (!qt)
mult_isarr = 1;
str++; str++;
goto comsub; goto comsub;
} else if (str[1] == Inbrack) { } else if (c == Inbrack) {
/* $[...] */ /* $[...] */
char *str2 = str; char *str2 = str;
str2++; str2++;
@ -115,7 +114,7 @@ stringsubst(LinkList list, LinkNode node, int ssub)
str = arithsubst(str + 2, &str3, str2); str = arithsubst(str + 2, &str3, str2);
setdata(node, (void *) str3); setdata(node, (void *) str3);
continue; continue;
} else if (str[1] == Snull) { } else if (c == Snull) {
str = getkeystring(str, NULL, 4, NULL); str = getkeystring(str, NULL, 4, NULL);
continue; continue;
} else { } else {
@ -125,21 +124,25 @@ stringsubst(LinkList list, LinkNode node, int ssub)
str3 = (char *)getdata(node); str3 = (char *)getdata(node);
continue; continue;
} }
else if ((qt = *str == Qtick) || *str == Tick) } else if ((qt = c == Qtick) || c == Tick)
comsub: { comsub: {
LinkList pl; LinkList pl;
char *s, *str2 = str; char *s, *str2 = str;
char endchar; char endchar;
int l1, l2; int l1, l2;
if (*str == Inpar) { if (c == Inpar) {
endchar = Outpar; endchar = Outpar;
str[-1] = '\0'; str[-1] = '\0';
#ifdef DEBUG
if (skipparens(Inpar, Outpar, &str)) if (skipparens(Inpar, Outpar, &str))
DPUTS(1, "BUG: parse error in command substitution"); dputs("BUG: parse error in command substitution");
#else
skipparens(Inpar, Outpar, &str);
#endif
str--; str--;
} else { } else {
endchar = *str; endchar = c;
*str = '\0'; *str = '\0';
while (*++str != endchar) while (*++str != endchar)
@ -148,7 +151,8 @@ stringsubst(LinkList list, LinkNode node, int ssub)
*str++ = '\0'; *str++ = '\0';
if (endchar == Outpar && str2[1] == '(' && str[-2] == ')') { if (endchar == Outpar && str2[1] == '(' && str[-2] == ')') {
/* Math substitution of the form $((...)) */ /* Math substitution of the form $((...)) */
str = arithsubst(str2 + 1, &str3, str); str[-2] = '\0';
str = arithsubst(str2 + 2, &str3, str);
setdata(node, (void *) str3); setdata(node, (void *) str3);
continue; continue;
} }
@ -159,12 +163,12 @@ stringsubst(LinkList list, LinkNode node, int ssub)
* be left unchanged. Note that the lexer doesn't tokenize * * be left unchanged. Note that the lexer doesn't tokenize *
* the body of a command substitution so if there are some * * the body of a command substitution so if there are some *
* tokens here they are from a ${(e)~...} substitution. */ * tokens here they are from a ${(e)~...} substitution. */
for (str = str2; *++str; ) for (str = str2; (c = *++str); )
if (itok(*str) && *str != Nularg && if (itok(c) && c != Nularg &&
!(endchar != Outpar && *str == Bnull && !(endchar != Outpar && c == Bnull &&
(str[1] == '$' || str[1] == '\\' || str[1] == '`' || (str[1] == '$' || str[1] == '\\' || str[1] == '`' ||
(qt && str[1] == '"')))) (qt && str[1] == '"'))))
*str = ztokens[*str - Pound]; *str = ztokens[c - Pound];
str++; str++;
if (!(pl = getoutput(str2 + 1, qt || ssub))) { if (!(pl = getoutput(str2 + 1, qt || ssub))) {
zerr("parse error in command substitution", NULL, 0); zerr("parse error in command substitution", NULL, 0);
@ -182,7 +186,7 @@ stringsubst(LinkList list, LinkNode node, int ssub)
l2 = strlen(s); l2 = strlen(s);
if (nonempty(pl)) { if (nonempty(pl)) {
LinkNode n = lastnode(pl); LinkNode n = lastnode(pl);
str2 = (char *) ncalloc(l1 + l2 + 1); str2 = (char *) hcalloc(l1 + l2 + 1);
strcpy(str2, str3); strcpy(str2, str3);
strcpy(str2 + l1, s); strcpy(str2 + l1, s);
setdata(node, str2); setdata(node, str2);
@ -191,7 +195,7 @@ stringsubst(LinkList list, LinkNode node, int ssub)
l1 = 0; l1 = 0;
l2 = strlen(s); l2 = strlen(s);
} }
str2 = (char *) ncalloc(l1 + l2 + strlen(str) + 1); str2 = (char *) hcalloc(l1 + l2 + strlen(str) + 1);
if (l1) if (l1)
strcpy(str2, str3); strcpy(str2, str3);
strcpy(str2 + l1, s); strcpy(str2 + l1, s);
@ -206,15 +210,15 @@ stringsubst(LinkList list, LinkNode node, int ssub)
} }
/**/ /**/
void mod_export void
globlist(LinkList list) globlist(LinkList list, int nountok)
{ {
LinkNode node, next; LinkNode node, next;
badcshglob = 0; badcshglob = 0;
for (node = firstnode(list); !errflag && node; node = next) { for (node = firstnode(list); !errflag && node; node = next) {
next = nextnode(node); next = nextnode(node);
glob(list, node); glob(list, node, nountok);
} }
if (badcshglob == 1) if (badcshglob == 1)
zerr("no match", NULL, 0); zerr("no match", NULL, 0);
@ -223,71 +227,84 @@ globlist(LinkList list)
/* perform substitution on a single word */ /* perform substitution on a single word */
/**/ /**/
void mod_export void
singsub(char **s) singsub(char **s)
{ {
LinkList foo; local_list1(foo);
foo = newlinklist(); init_list1(foo, *s);
addlinknode(foo, *s);
prefork(foo, 4); prefork(&foo, PF_SINGLE);
if (errflag) if (errflag)
return; return;
*s = (char *) ugetnode(foo); *s = (char *) ugetnode(&foo);
DPUTS(nonempty(foo), "BUG: singsub() produced more than one word!"); DPUTS(nonempty(&foo), "BUG: singsub() produced more than one word!");
} }
/* Perform substitution on a single word. Unlike with singsub, the * /* Perform substitution on a single word. Unlike with singsub, the *
* result can have more than one words. A single word result is sroted * * result can have more than one word. A single word result is stored *
* in *s and *isarr is set to zero; otherwise *isarr is set to 1 and * * in *s and *isarr is set to zero; otherwise *isarr is set to 1 and *
* the result is stored in *a. If `a' is zero a multiple word result is * * the result is stored in *a. If `a' is zero a multiple word result is *
* joined using sep or the IFS parameter if sep is zero and the result * * joined using sep or the IFS parameter if sep is zero and the result *
* is returned in *s. The return value is true iff the expansion * * is returned in *s. The return value is true iff the expansion *
* resulted in an empty list */ * resulted in an empty list. *
* The mult_isarr variable is used by paramsubst() to tell if it yields *
* an array. */
/**/
static int mult_isarr;
/**/ /**/
static int static int
multsub(char **s, char ***a, int *isarr, char *sep) multsub(char **s, char ***a, int *isarr, char *sep)
{ {
LinkList foo; int l, omi = mult_isarr;
int l;
char **r, **p; char **r, **p;
local_list1(foo);
foo = newlinklist(); mult_isarr = 0;
addlinknode(foo, *s); init_list1(foo, *s);
prefork(foo, 0); prefork(&foo, 0);
if (errflag) { if (errflag) {
if (isarr)
*isarr = 0;
mult_isarr = omi;
return 0;
}
if ((l = countlinknodes(&foo))) {
p = r = hcalloc((l + 1) * sizeof(char*));
while (nonempty(&foo))
*p++ = (char *)ugetnode(&foo);
*p = NULL;
if (a && mult_isarr) {
*a = r;
*isarr = SCANPM_MATCHMANY;
mult_isarr = omi;
return 0;
}
*s = sepjoin(r, NULL, 1);
mult_isarr = omi;
if (isarr) if (isarr)
*isarr = 0; *isarr = 0;
return 0; return 0;
} }
if ((l = countlinknodes(foo)) > 1) {
p = r = ncalloc((l + 1) * sizeof(char*));
while (nonempty(foo))
*p++ = (char *)ugetnode(foo);
*p = NULL;
if (a) {
*a = r;
*isarr = 1;
return 0;
}
*s = sepjoin(r, NULL);
return 0;
}
if (l) if (l)
*s = (char *) ugetnode(foo); *s = (char *) ugetnode(&foo);
else else
*s = dupstring(""); *s = dupstring("");
if (isarr) if (isarr)
*isarr = 0; *isarr = 0;
mult_isarr = omi;
return !l; return !l;
} }
/* ~, = subs: assign = 2 => typeset; assign = 1 => something that looks /*
like an assignment but may not be; assign = 3 => normal assignment */ * ~, = subs: assign & PF_TYPESET => typeset or magic equals
* assign & PF_ASSIGN => normal assignment
*/
/**/ /**/
void mod_export void
filesub(char **namptr, int assign) filesub(char **namptr, int assign)
{ {
char *sub = NULL, *str, *ptr; char *sub = NULL, *str, *ptr;
@ -298,12 +315,8 @@ filesub(char **namptr, int assign)
if (!assign) if (!assign)
return; return;
if (assign < 3) if (assign & PF_TYPESET) {
if ((*namptr)[1] && (sub = strchr(*namptr + 1, Equals))) { if ((*namptr)[1] && (sub = strchr(*namptr + 1, Equals))) {
if (assign == 1)
for (ptr = *namptr; ptr != sub; ptr++)
if (!iident(*ptr) && !INULL(*ptr))
return;
str = sub + 1; str = sub + 1;
if ((sub[1] == Tilde || sub[1] == Equals) && filesubstr(&str, assign)) { if ((sub[1] == Tilde || sub[1] == Equals) && filesubstr(&str, assign)) {
sub[1] = '\0'; sub[1] = '\0';
@ -311,6 +324,7 @@ filesub(char **namptr, int assign)
} }
} else } else
return; return;
}
ptr = *namptr; ptr = *namptr;
while ((sub = strchr(ptr, ':'))) { while ((sub = strchr(ptr, ':'))) {
@ -325,7 +339,7 @@ filesub(char **namptr, int assign)
} }
/**/ /**/
int mod_export int
filesubstr(char **namptr, int assign) filesubstr(char **namptr, int assign)
{ {
#define isend(c) ( !(c) || (c)=='/' || (c)==Inpar || (assign && (c)==':') ) #define isend(c) ( !(c) || (c)=='/' || (c)==Inpar || (assign && (c)==':') )
@ -382,11 +396,11 @@ filesubstr(char **namptr, int assign)
for (pp = str + 1; !isend2(*pp); pp++); for (pp = str + 1; !isend2(*pp); pp++);
sav = *pp; sav = *pp;
*pp = 0; *pp = 0;
if (!(cnam = findcmd(str + 1))) { if (!(cnam = findcmd(str + 1, 1))) {
Alias a = (Alias) aliastab->getnode(aliastab, str + 1); Alias a = (Alias) aliastab->getnode(aliastab, str + 1);
if (a) if (a)
cnam = ztrdup(a->text); cnam = a->text;
else { else {
if (isset(NOMATCH)) if (isset(NOMATCH))
zerr("%s not found", str + 1, 0); zerr("%s not found", str + 1, 0);
@ -394,7 +408,6 @@ filesubstr(char **namptr, int assign)
} }
} }
*namptr = dupstring(cnam); *namptr = dupstring(cnam);
zsfree(cnam);
if (sav) { if (sav) {
*pp = sav; *pp = sav;
*namptr = dyncat(*namptr, pp); *namptr = dyncat(*namptr, pp);
@ -408,20 +421,27 @@ filesubstr(char **namptr, int assign)
/**/ /**/
static char * static char *
strcatsub(char **d, char *pb, char *pe, char *src, int l, char *s, int glbsub) strcatsub(char **d, char *pb, char *pe, char *src, int l, char *s, int glbsub,
int copied)
{ {
char *dest;
int pl = pe - pb; int pl = pe - pb;
char *dest = ncalloc(pl + l + (s ? strlen(s) : 0) + 1);
*d = dest; if (!pl && (!s || !*s)) {
strncpy(dest, pb, pl); *d = dest = (copied ? src : dupstring(src));
dest += pl; if (glbsub)
strcpy(dest, src); tokenize(dest);
if (glbsub) } else {
tokenize(dest); *d = dest = hcalloc(pl + l + (s ? strlen(s) : 0) + 1);
dest += l; strncpy(dest, pb, pl);
if (s) dest += pl;
strcpy(dest, s); strcpy(dest, src);
if (glbsub)
tokenize(dest);
dest += l;
if (s)
strcpy(dest, s);
}
return dest; return dest;
} }
@ -529,7 +549,7 @@ dopadding(char *str, int prenum, int postnum, char *preone, char *postone, char
if (lr == ls) if (lr == ls)
return str; return str;
r = ret = (char *)halloc(lr + 1); r = ret = (char *)zhalloc(lr + 1);
if (prenum) { if (prenum) {
if (postnum) { if (postnum) {
@ -657,7 +677,7 @@ get_intarg(char **s)
{ {
char *t = get_strarg(*s + 1); char *t = get_strarg(*s + 1);
char *p, sav; char *p, sav;
long ret; zlong ret;
if (!*t) if (!*t)
return -1; return -1;
@ -671,7 +691,7 @@ get_intarg(char **s)
singsub(&p); singsub(&p);
if (errflag) if (errflag)
return -1; return -1;
ret = matheval(p); ret = mathevali(p);
if (errflag) if (errflag)
return -1; return -1;
if (ret < 0) if (ret < 0)
@ -679,6 +699,26 @@ get_intarg(char **s)
return ret < 0 ? -ret : ret; return ret < 0 ? -ret : ret;
} }
/* Parsing for the (e) flag. */
static int
subst_parse_str(char **sp, int single)
{
char *s;
*sp = s = dupstring(*sp);
if (!parsestr(s)) {
if (!single) {
for (; *s; s++)
if (*s == Qstring)
*s = String;
}
return 0;
}
return 1;
}
/* parameter substitution */ /* parameter substitution */
#define isstring(c) ((c) == '$' || (char)(c) == String || (char)(c) == Qstring) #define isstring(c) ((c) == '$' || (char)(c) == String || (char)(c) == Qstring)
@ -688,10 +728,9 @@ get_intarg(char **s)
LinkNode LinkNode
paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub) paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
{ {
char *aptr = *str; char *aptr = *str, c, cc;
char *s = aptr, *fstr, *idbeg, *idend, *ostr = (char *) getdata(n); char *s = aptr, *fstr, *idbeg, *idend, *ostr = (char *) getdata(n);
int colf; /* != 0 means we found a colon after the name */ int colf; /* != 0 means we found a colon after the name */
int doub = 0; /* != 0 means we have %%, not %, or ##, not # */
int isarr = 0; int isarr = 0;
int plan9 = isset(RCEXPANDPARAM); int plan9 = isset(RCEXPANDPARAM);
int globsubst = isset(GLOBSUBST); int globsubst = isset(GLOBSUBST);
@ -699,45 +738,58 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
int whichlen = 0; int whichlen = 0;
int chkset = 0; int chkset = 0;
int vunset = 0; int vunset = 0;
int wantt = 0;
int spbreak = isset(SHWORDSPLIT) && !ssub && !qt; int spbreak = isset(SHWORDSPLIT) && !ssub && !qt;
char *val = NULL, **aval = NULL; char *val = NULL, **aval = NULL;
unsigned int fwidth = 0; unsigned int fwidth = 0;
Value v; struct value vbuf;
Value v = NULL;
int flags = 0; int flags = 0;
int flnum = 0; int flnum = 0;
int substr = 0;
int sortit = 0, casind = 0; int sortit = 0, casind = 0;
int casmod = 0; int casmod = 0;
int quotemod = 0, quotetype = 0, quoteerr = 0;
int visiblemod = 0;
char *sep = NULL, *spsep = NULL; char *sep = NULL, *spsep = NULL;
char *premul = NULL, *postmul = NULL, *preone = NULL, *postone = NULL; char *premul = NULL, *postmul = NULL, *preone = NULL, *postone = NULL;
long prenum = 0, postnum = 0; char *replstr = NULL; /* replacement string for /orig/repl */
zlong prenum = 0, postnum = 0;
int copied = 0; int copied = 0;
int arrasg = 0; int arrasg = 0;
int eval = 0; int eval = 0;
int aspar = 0;
int presc = 0;
int nojoin = 0; int nojoin = 0;
char inbrace = 0; /* != 0 means ${...}, otherwise $... */ char inbrace = 0; /* != 0 means ${...}, otherwise $... */
char hkeys = 0;
char hvals = 0;
int subexp;
*s++ = '\0'; *s++ = '\0';
if (!ialnum(*s) && *s != '#' && *s != Pound && *s != '-' && if (!ialnum(c = *s) && c != '#' && c != Pound && c != '-' &&
*s != '!' && *s != '$' && *s != String && *s != Qstring && c != '!' && c != '$' && c != String && c != Qstring &&
*s != '?' && *s != Quest && *s != '_' && c != '?' && c != Quest && c != '_' &&
*s != '*' && *s != Star && *s != '@' && *s != '{' && c != '*' && c != Star && c != '@' && c != '{' &&
*s != Inbrace && *s != '=' && *s != Equals && *s != Hat && c != Inbrace && c != '=' && c != Equals && c != Hat &&
*s != '^' && *s != '~' && *s != Tilde && *s != '+') { c != '^' && c != '~' && c != Tilde && c != '+') {
s[-1] = '$'; s[-1] = '$';
*str = s; *str = s;
return n; return n;
} }
DPUTS(*s == '{', "BUG: inbrace == '{' in paramsubst()"); DPUTS(c == '{', "BUG: inbrace == '{' in paramsubst()");
if (*s == Inbrace) { if (c == Inbrace) {
inbrace = 1; inbrace = 1;
s++; s++;
if (*s == '(' || *s == Inpar) { if ((c = *s) == '!' && s[1] != Outbrace && emulation == EMULATE_KSH) {
hkeys = SCANPM_WANTKEYS;
s++;
} else if (c == '(' || c == Inpar) {
char *t, sav; char *t, sav;
int tt = 0; int tt = 0;
long num; zlong num;
int escapes = 0; int escapes = 0;
int klen; int klen;
#define UNTOK(C) (itok(C) ? ztokens[(C) - Pound] : (C))
#define UNTOK_AND_ESCAPE(X) {\ #define UNTOK_AND_ESCAPE(X) {\
untokenize(X = dupstring(s + 1));\ untokenize(X = dupstring(s + 1));\
if (escapes) {\ if (escapes) {\
@ -746,34 +798,34 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
}\ }\
} }
for (s++; *s != ')' && *s != Outpar; s++, tt = 0) { for (s++; (c = *s) != ')' && c != Outpar; s++, tt = 0) {
switch (*s) { switch (c) {
case ')': case ')':
case Outpar: case Outpar:
break; break;
case 'A': case 'A':
arrasg = 1; ++arrasg;
break; break;
case '@': case '@':
nojoin = 1; nojoin = 1;
break; break;
case 'M': case 'M':
flags |= 8; flags |= SUB_MATCH;
break; break;
case 'R': case 'R':
flags |= 16; flags |= SUB_REST;
break; break;
case 'B': case 'B':
flags |= 32; flags |= SUB_BIND;
break; break;
case 'E': case 'E':
flags |= 64; flags |= SUB_EIND;
break; break;
case 'N': case 'N':
flags |= 128; flags |= SUB_LEN;
break; break;
case 'S': case 'S':
substr = 1; flags |= SUB_SUBSTR;
break; break;
case 'I': case 'I':
flnum = get_intarg(&s); flnum = get_intarg(&s);
@ -800,9 +852,27 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
case 'i': case 'i':
casind = 1; casind = 1;
break; break;
case 'V':
visiblemod++;
break;
case 'q':
quotemod++, quotetype++;
break;
case 'Q':
quotemod--;
break;
case 'X':
quoteerr = 1;
break;
case 'e': case 'e':
eval = 1; eval = 1;
break; break;
case 'P':
aspar = 1;
break;
case 'c': case 'c':
whichlen = 1; whichlen = 1;
@ -851,7 +921,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
prenum = num; prenum = num;
else else
postnum = num; postnum = num;
if (s[1] != sav) if (UNTOK(s[1]) != UNTOK(sav))
break; break;
t = get_strarg(++s); t = get_strarg(++s);
if (!*t) if (!*t)
@ -865,7 +935,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
*t = sav; *t = sav;
sav = *s; sav = *s;
s = t + 1; s = t + 1;
if (*s != sav) { if (UNTOK(*s) != UNTOK(sav)) {
s--; s--;
break; break;
} }
@ -886,6 +956,21 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
escapes = 1; escapes = 1;
break; break;
case 'k':
hkeys = SCANPM_WANTKEYS;
break;
case 'v':
hvals = SCANPM_WANTVALS;
break;
case 't':
wantt = 1;
break;
case '%':
presc++;
break;
default: default:
flagerr: flagerr:
zerr("error in flags", NULL, 0); zerr("error in flags", NULL, 0);
@ -904,31 +989,33 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
postmul = " "; postmul = " ";
for (;;) { for (;;) {
if (*s == '^' || *s == Hat) { if ((c = *s) == '^' || c == Hat) {
if (*++s == '^' || *s == Hat) { if ((c = *++s) == '^' || c == Hat) {
plan9 = 0; plan9 = 0;
s++; s++;
} else } else
plan9 = 1; plan9 = 1;
} else if (*s == '=' || *s == Equals) { } else if ((c = *s) == '=' || c == Equals) {
if (*++s == '=' || *s == Equals) { if ((c = *++s) == '=' || c == Equals) {
spbreak = 0; spbreak = 0;
s++; s++;
} else } else
spbreak = 1; spbreak = 1;
} else if ((*s == '#' || *s == Pound) && } else if ((c == '#' || c == Pound) &&
(iident(s[1]) (iident(cc = s[1])
|| s[1] == '*' || s[1] == Star || s[1] == '@' || cc == '*' || cc == Star || cc == '@'
|| (isstring(s[1]) && (s[2] == Inbrace || s[2] == Inpar)))) || cc == '-' || (cc == ':' && s[2] == '-')
|| (isstring(cc) && (s[2] == Inbrace || s[2] == Inpar))))
getlen = 1 + whichlen, s++; getlen = 1 + whichlen, s++;
else if (*s == '~' || *s == Tilde) { else if (c == '~' || c == Tilde) {
if (*++s == '~' || *s == Tilde) { if ((c = *++s) == '~' || c == Tilde) {
globsubst = 0; globsubst = 0;
s++; s++;
} else } else
globsubst = 1; globsubst = 1;
} else if (*s == '+') } else if (c == '+') {
if (iident(s[1])) if (iident(s[1]) || (aspar && isstring(s[1]) &&
(s[2] == Inbrace || s[2] == Inpar)))
chkset = 1, s++; chkset = 1, s++;
else if (!inbrace) { else if (!inbrace) {
*aptr = '$'; *aptr = '$';
@ -938,13 +1025,16 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
zerr("bad substitution", NULL, 0); zerr("bad substitution", NULL, 0);
return NULL; return NULL;
} }
} else if (inbrace && INULL(*s))
s++;
else else
break; break;
} }
globsubst = globsubst && !qt; globsubst = globsubst && !qt;
idbeg = s; idbeg = s;
if (s[-1] && isstring(*s) && (s[1] == Inbrace || s[1] == Inpar)) { if ((subexp = (inbrace && s[-1] && isstring(*s) &&
(s[1] == Inbrace || s[1] == Inpar)))) {
int sav; int sav;
int quoted = *s == Qstring; int quoted = *s == Qstring;
@ -952,17 +1042,78 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
skipparens(*s, *s == Inpar ? Outpar : Outbrace, &s); skipparens(*s, *s == Inpar ? Outpar : Outbrace, &s);
sav = *s; sav = *s;
*s = 0; *s = 0;
if (multsub(&val, &aval, &isarr, NULL) && quoted) { if (multsub(&val, (aspar ? NULL : &aval), &isarr, NULL) && quoted) {
isarr = -1; isarr = -1;
aval = alloc(sizeof(char *)); aval = (char **) hcalloc(sizeof(char *));
} aspar = 0;
if (isarr) } else if (aspar)
isarr = -1; idbeg = val;
copied = 1;
*s = sav; *s = sav;
while (INULL(*s))
s++;
v = (Value) NULL; v = (Value) NULL;
} else if (!(v = getvalue(&s, (unset(KSHARRAYS) || inbrace) ? 1 : -1))) } else if (aspar) {
vunset = 1; if ((v = getvalue(&vbuf, &s, 1))) {
val = idbeg = getstrvalue(v);
subexp = 1;
} else
vunset = 1;
}
if (!subexp || aspar) {
char *ov = val;
if (!(v = fetchvalue(&vbuf, (subexp ? &ov : &s),
(wantt ? -1 :
((unset(KSHARRAYS) || inbrace) ? 1 : -1)),
hkeys|hvals|(arrasg ? SCANPM_ASSIGNING : 0))) ||
(v->pm && (v->pm->flags & PM_UNSET)))
vunset = 1;
if (wantt) {
if (v && v->pm && !(v->pm->flags & PM_UNSET)) {
int f = v->pm->flags;
switch (PM_TYPE(f)) {
case PM_SCALAR: val = "scalar"; break;
case PM_ARRAY: val = "array"; break;
case PM_INTEGER: val = "integer"; break;
case PM_EFLOAT:
case PM_FFLOAT: val = "float"; break;
case PM_HASHED: val = "association"; break;
}
val = dupstring(val);
if (v->pm->level)
val = dyncat(val, "-local");
if (f & PM_LEFT)
val = dyncat(val, "-left");
if (f & PM_RIGHT_B)
val = dyncat(val, "-right_blanks");
if (f & PM_RIGHT_Z)
val = dyncat(val, "-right_zeros");
if (f & PM_LOWER)
val = dyncat(val, "-lower");
if (f & PM_UPPER)
val = dyncat(val, "-upper");
if (f & PM_READONLY)
val = dyncat(val, "-readonly");
if (f & PM_TAGGED)
val = dyncat(val, "-tag");
if (f & PM_EXPORTED)
val = dyncat(val, "-export");
if (f & PM_UNIQUE)
val = dyncat(val, "-unique");
if (f & PM_HIDE)
val = dyncat(val, "-hide");
if (f & PM_SPECIAL)
val = dyncat(val, "-special");
vunset = 0;
} else
val = dupstring("");
v = NULL;
isarr = 0;
}
}
while (v || ((inbrace || (unset(KSHARRAYS) && vunset)) && isbrack(*s))) { while (v || ((inbrace || (unset(KSHARRAYS) && vunset)) && isbrack(*s))) {
if (!v) { if (!v) {
Param pm; Param pm;
@ -986,9 +1137,15 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
if (getindex(&s, v) || s == os) if (getindex(&s, v) || s == os)
break; break;
} }
if ((isarr = v->isarr)) if ((isarr = v->isarr)) {
aval = getarrvalue(v); /* No way to get here with v->inv != 0, so getvaluearr() *
else { * is called by getarrvalue(); needn't test PM_HASHED. */
if (v->isarr == SCANPM_WANTINDEX) {
isarr = v->isarr = 0;
val = dupstring(v->pm->nam);
} else
aval = getarrvalue(v);
} else {
if (v->pm->flags & PM_ARRAY) { if (v->pm->flags & PM_ARRAY) {
int tmplen = arrlen(v->pm->gets.afn(v->pm)); int tmplen = arrlen(v->pm->gets.afn(v->pm));
@ -1013,7 +1170,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
else else
while (iblank(*t)) while (iblank(*t))
t++; t++;
val = (char *)ncalloc(fwidth + 1); val = (char *) hcalloc(fwidth + 1);
val[fwidth] = '\0'; val[fwidth] = '\0';
if ((t0 = strlen(t)) > fwidth) if ((t0 = strlen(t)) > fwidth)
t0 = fwidth; t0 = fwidth;
@ -1023,18 +1180,28 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
case PM_RIGHT_B: case PM_RIGHT_B:
case PM_RIGHT_Z: case PM_RIGHT_Z:
case PM_RIGHT_Z | PM_RIGHT_B: case PM_RIGHT_Z | PM_RIGHT_B:
if (strlen(val) < fwidth) { {
t = (char *)ncalloc(fwidth + 1); int zero = 1;
memset(t, (v->pm->flags & PM_RIGHT_B) ? ' ' : '0', fwidth);
if ((t0 = strlen(val)) > fwidth) if (strlen(val) < fwidth) {
t0 = fwidth; if (v->pm->flags & PM_RIGHT_Z) {
strcpy(t + (fwidth - t0), val); for (t = val; iblank(*t); t++);
val = t; if (!*t || !idigit(*t))
} else { zero = 0;
t = (char *)ncalloc(fwidth + 1); }
t[fwidth] = '\0'; t = (char *) hcalloc(fwidth + 1);
strncpy(t, val + strlen(val) - fwidth, fwidth); memset(t, (((v->pm->flags & PM_RIGHT_B) || !zero) ?
val = t; ' ' : '0'), fwidth);
if ((t0 = strlen(val)) > fwidth)
t0 = fwidth;
strcpy(t + (fwidth - t0), val);
val = t;
} else {
t = (char *) hcalloc(fwidth + 1);
t[fwidth] = '\0';
strncpy(t, val + strlen(val) - fwidth, fwidth);
val = t;
}
} }
break; break;
} }
@ -1043,13 +1210,13 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
case PM_LOWER: case PM_LOWER:
t = val; t = val;
for (; *t; t++) for (; (c = *t); t++)
*t = tulower(*t); *t = tulower(c);
break; break;
case PM_UPPER: case PM_UPPER:
t = val; t = val;
for (; *t; t++) for (; (c = *t); t++)
*t = tuupper(*t); *t = tuupper(c);
break; break;
} }
} }
@ -1062,12 +1229,15 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
if (nojoin) if (nojoin)
isarr = -1; isarr = -1;
if (qt && !getlen && isarr > 0) { if (qt && !getlen && isarr > 0) {
val = sepjoin(aval, sep); val = sepjoin(aval, sep, 1);
isarr = 0; isarr = 0;
} }
} }
idend = s; idend = s;
if (inbrace)
while (INULL(*s))
s++;
if ((colf = *s == ':')) if ((colf = *s == ':'))
s++; s++;
@ -1077,12 +1247,10 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
fstr = s; fstr = s;
if (inbrace) { if (inbrace) {
int bct; int bct;
for (bct = 1;; fstr++) { for (bct = 1; (c = *fstr); fstr++) {
if (!*fstr) if (c == Inbrace)
break;
else if (*fstr == Inbrace)
bct++; bct++;
else if (*fstr == Outbrace && !--bct) else if (c == Outbrace && !--bct)
break; break;
} }
@ -1091,36 +1259,83 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
zerr("closing brace expected", NULL, 0); zerr("closing brace expected", NULL, 0);
return NULL; return NULL;
} }
if (*fstr) if (c)
*fstr++ = '\0'; *fstr++ = '\0';
} }
/* Check for ${..?..} or ${..=..} or one of those. * /* Check for ${..?..} or ${..=..} or one of those. *
* Only works if the name is in braces. */ * Only works if the name is in braces. */
if (inbrace && (*s == '-' || if (inbrace && ((c = *s) == '-' ||
*s == '+' || c == '+' ||
*s == ':' || c == ':' ||
*s == '=' || *s == Equals || c == '=' || c == Equals ||
*s == '%' || c == '%' ||
*s == '#' || *s == Pound || c == '#' || c == Pound ||
*s == '?' || *s == Quest)) { c == '?' || c == Quest ||
c == '/')) {
if (!flnum) if (!flnum)
flnum++; flnum++;
if (*s == '%') if (c == '%')
flags |= 1; flags |= SUB_END;
/* Check for ${..%%..} or ${..##..} */ /* Check for ${..%%..} or ${..##..} */
if ((*s == '%' || *s == '#' || *s == Pound) && *s == s[1]) { if ((c == '%' || c == '#' || c == Pound) && c == s[1]) {
s++; s++;
doub = 1; /* we have %%, not %, or ##, not # */
flags |= SUB_LONG;
} }
s++; s++;
if (s[-1] == '/') {
char *ptr;
/*
* previous flags are irrelevant, except for (S) which
* indicates shortest substring; else look for longest.
*/
flags = (flags & SUB_SUBSTR) ? 0 : SUB_LONG;
if ((c = *s) == '/') {
/* doubled, so replace all occurrences */
flags |= SUB_GLOBAL;
s++;
}
/* Check for anchored substitution */
if (c == '%') {
/* anchor at tail */
flags |= SUB_END;
s++;
} else if (c == '#' || c == Pound) {
/* anchor at head: this is the `normal' case in getmatch */
s++;
} else
flags |= SUB_SUBSTR;
/*
* Find the / marking the end of the search pattern.
* If there isn't one, we're just going to delete that,
* i.e. replace it with an empty string.
*
* This allows quotation of the slash with '\\/'. Why
* two? Well, for a non-quoted string we can check for
* Bnull+/, which is what you get from `\/', but inside
* double quotes the Bnull isn't there, so it's not
* consistent.
*/
for (ptr = s; (c = *ptr) && c != '/'; ptr++)
if (c == '\\' && ptr[1] == '/')
chuck(ptr);
replstr = (*ptr && ptr[1]) ? ptr+1 : "";
*ptr = '\0';
}
flags |= (doub << 1) | (substr << 2) | (colf << 8); if (colf)
if (!(flags & 0xf8)) flags |= SUB_ALL;
flags |= 16; /*
* With no special flags, i.e. just a # or % or whatever,
* the matched portion is removed and we keep the rest.
* We also want the rest when we're doing a substitution.
*/
if (!(flags & (SUB_MATCH|SUB_REST|SUB_BIND|SUB_EIND|SUB_LEN)))
flags |= SUB_REST;
if (colf && !vunset) if (colf && !vunset)
vunset = (isarr) ? !*aval : !*val || (*val == Nularg && !val[1]); vunset = (isarr) ? !*aval : !*val || (*val == Nularg && !val[1]);
@ -1138,7 +1353,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
case '-': case '-':
if (vunset) { if (vunset) {
val = dupstring(s); val = dupstring(s);
multsub(&val, &aval, &isarr, NULL); multsub(&val, NULL, &isarr, NULL);
copied = 1; copied = 1;
} }
break; break;
@ -1164,7 +1379,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
if (arrasg) { if (arrasg) {
char *arr[2], **t, **a, **p; char *arr[2], **t, **a, **p;
if (spsep || spbreak) { if (spsep || spbreak) {
aval = sepsplit(val, spsep, 0); aval = sepsplit(val, spsep, 0, 1);
isarr = 2; isarr = 2;
sep = spsep = NULL; sep = spsep = NULL;
spbreak = 0; spbreak = 0;
@ -1176,10 +1391,15 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
else else
t = aval; t = aval;
} else if (!isarr) { } else if (!isarr) {
arr[0] = val; if (!*val && arrasg > 1) {
arr[1] = NULL; arr[0] = NULL;
l = 0;
} else {
arr[0] = val;
arr[1] = NULL;
l = 1;
}
t = aval = arr; t = aval = arr;
l = 1;
} else } else
l = arrlen(aval), t = aval; l = arrlen(aval), t = aval;
p = a = zalloc(sizeof(char *) * (l + 1)); p = a = zalloc(sizeof(char *) * (l + 1));
@ -1188,7 +1408,12 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
*p++ = ztrdup(*t++); *p++ = ztrdup(*t++);
} }
*p++ = NULL; *p++ = NULL;
setaparam(idbeg, a); if (arrasg > 1) {
Param pm = sethparam(idbeg, a);
if (pm)
aval = paramvalarr(pm->gets.hfn(pm), hkeys|hvals);
} else
setaparam(idbeg, a);
} else { } else {
untokenize(val); untokenize(val);
setsparam(idbeg, ztrdup(val)); setsparam(idbeg, ztrdup(val));
@ -1214,27 +1439,47 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
case '%': case '%':
case '#': case '#':
case Pound: case Pound:
if (qt) case '/':
if (parse_subst_string(s)) { if (qt) {
int one = noerrs, oef = errflag, haserr;
if (!quoteerr)
noerrs = 1;
haserr = parse_subst_string(s);
noerrs = one;
if (!quoteerr) {
errflag = oef;
if (haserr)
tokenize(s);
} else if (haserr || errflag) {
zerr("parse error in ${...%c...} substitution", zerr("parse error in ${...%c...} substitution",
NULL, s[-1]); NULL, s[-1]);
return NULL; return NULL;
} }
singsub(&s); }
{
char t = s[-1];
singsub(&s);
if (t == '/' && (flags & SUB_SUBSTR)) {
if ((c = *s) == '#' || c == '%') {
flags &= ~SUB_SUBSTR;
if (c == '%')
flags |= SUB_END;
s++;
} else if (c == '\\') {
s++;
}
}
}
if (!vunset && isarr) { if (!vunset && isarr) {
char **ap = aval; getmatcharr(&aval, s, flags, flnum, replstr);
char **pp = aval = (char **)ncalloc(sizeof(char *) * (arrlen(aval) + 1));
while ((*pp = *ap++)) {
if (getmatch(pp, s, flags, flnum))
pp++;
}
copied = 1; copied = 1;
} else { } else {
if (vunset) if (vunset)
val = dupstring(""); val = dupstring("");
getmatch(&val, s, flags, flnum); getmatch(&val, s, flags, flnum, replstr);
copied = 1; copied = 1;
} }
break; break;
@ -1259,7 +1504,8 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
else { else {
char *ss; char *ss;
char **ap = aval; char **ap = aval;
char **pp = aval = (char **)ncalloc(sizeof(char *) * (arrlen(aval) + 1)); char **pp = aval = (char **) hcalloc(sizeof(char *) *
(arrlen(aval) + 1));
while ((*pp = *ap++)) { while ((*pp = *ap++)) {
ss = s; ss = s;
@ -1272,6 +1518,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
} }
s = ss; s = ss;
} }
copied = 1;
if (inbrace && *s) { if (inbrace && *s) {
if (*s == ':' && !imeta(s[1])) if (*s == ':' && !imeta(s[1]))
zerr("unrecognized modifier `%c'", NULL, s[1]); zerr("unrecognized modifier `%c'", NULL, s[1]);
@ -1316,10 +1563,15 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
val = dupstring(buf); val = dupstring(buf);
isarr = 0; isarr = 0;
} }
mult_isarr = isarr;
if (isarr > 0 && !plan9 && (!aval || !aval[0])) { if (isarr > 0 && !plan9 && (!aval || !aval[0])) {
val = dupstring(""); val = dupstring("");
isarr = 0; isarr = 0;
} else if (isarr && aval && aval[0] && !aval[1]) { } else if (isarr && aval && aval[0] && !aval[1]) {
/* treat a one-element array as a scalar for purposes of *
* concatenation with surrounding text (some${param}thing) *
* and rc_expand_param handling. Note: mult_isarr (above) *
* propagates the true array type from nested expansions. */
val = aval[0]; val = aval[0];
isarr = 0; isarr = 0;
} }
@ -1327,9 +1579,9 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
* It means that we must join arrays and should not split words. */ * It means that we must join arrays and should not split words. */
if (ssub || spbreak || spsep || sep) { if (ssub || spbreak || spsep || sep) {
if (isarr) if (isarr)
val = sepjoin(aval, sep), isarr = 0; val = sepjoin(aval, sep, 1), isarr = 0;
if (!ssub && (spbreak || spsep)) { if (!ssub && (spbreak || spsep)) {
aval = sepsplit(val, spsep, 0); aval = sepsplit(val, spsep, 0, 1);
if (!aval || !aval[0]) if (!aval || !aval[0])
val = dupstring(""); val = dupstring("");
else if (!aval[1]) else if (!aval[1])
@ -1337,6 +1589,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
else else
isarr = 2; isarr = 2;
} }
mult_isarr = isarr;
} }
if (casmod) { if (casmod) {
if (isarr) { if (isarr) {
@ -1367,6 +1620,134 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
makecapitals(&val); makecapitals(&val);
} }
} }
if (presc) {
int ops = opts[PROMPTSUBST], opb = opts[PROMPTBANG];
int opp = opts[PROMPTPERCENT], len;
if (presc < 2) {
opts[PROMPTPERCENT] = 1;
opts[PROMPTSUBST] = opts[PROMPTBANG] = 0;
}
if (isarr) {
char **ap;
if (!copied)
aval = arrdup(aval), copied = 1;
ap = aval;
for (; *ap; ap++) {
unmetafy(*ap, &len);
untokenize(*ap);
*ap = unmetafy(promptexpand(metafy(*ap, len, META_NOALLOC),
0, NULL, NULL), &len);
}
} else {
if (!copied)
val = dupstring(val), copied = 1;
unmetafy(val, &len);
untokenize(val);
val = unmetafy(promptexpand(metafy(val, len, META_NOALLOC),
0, NULL, NULL), &len);
}
opts[PROMPTSUBST] = ops;
opts[PROMPTBANG] = opb;
opts[PROMPTPERCENT] = opp;
}
if (quotemod) {
if (--quotetype > 3)
quotetype = 3;
if (isarr) {
char **ap;
if (!copied)
aval = arrdup(aval), copied = 1;
ap = aval;
if (quotemod > 0) {
if (quotetype) {
int sl;
char *tmp;
for (; *ap; ap++) {
int pre = quotetype != 3 ? 1 : 2;
tmp = bslashquote(*ap, NULL, quotetype);
sl = strlen(tmp);
*ap = (char *) zhalloc(pre + sl + 2);
strcpy((*ap) + pre, tmp);
ap[0][pre - 1] = ap[0][pre + sl] = (quotetype != 2 ? '\'' : '"');
ap[0][pre + sl + 1] = '\0';
if (quotetype == 3)
ap[0][0] = '$';
}
} else
for (; *ap; ap++)
*ap = bslashquote(*ap, NULL, 0);
} else {
int one = noerrs, oef = errflag, haserr = 0;
if (!quoteerr)
noerrs = 1;
for (; *ap; ap++) {
haserr |= parse_subst_string(*ap);
remnulargs(*ap);
untokenize(*ap);
}
noerrs = one;
if (!quoteerr)
errflag = oef;
else if (haserr || errflag) {
zerr("parse error in parameter value", NULL, 0);
return NULL;
}
}
} else {
if (!copied)
val = dupstring(val), copied = 1;
if (quotemod > 0) {
if (quotetype) {
int pre = quotetype != 3 ? 1 : 2;
int sl;
char *tmp;
tmp = bslashquote(val, NULL, quotetype);
sl = strlen(tmp);
val = (char *) zhalloc(pre + sl + 2);
strcpy(val + pre, tmp);
val[pre - 1] = val[pre + sl] = (quotetype != 2 ? '\'' : '"');
val[pre + sl + 1] = '\0';
if (quotetype == 3)
val[0] = '$';
} else
val = bslashquote(val, NULL, 0);
} else {
int one = noerrs, oef = errflag, haserr;
if (!quoteerr)
noerrs = 1;
haserr = parse_subst_string(val);
noerrs = one;
if (!quoteerr)
errflag = oef;
else if (haserr || errflag) {
zerr("parse error in parameter value", NULL, 0);
return NULL;
}
remnulargs(val);
untokenize(val);
}
}
}
if (visiblemod) {
if (isarr) {
char **ap;
if (!copied)
aval = arrdup(aval), copied = 1;
for (ap = aval; *ap; ap++)
*ap = nicedupstring(*ap);
} else {
if (!copied)
val = dupstring(val), copied = 1;
val = nicedupstring(val);
}
}
if (isarr) { if (isarr) {
char *x; char *x;
char *y; char *y;
@ -1378,7 +1759,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
if (aptr > (char *) getdata(n) && if (aptr > (char *) getdata(n) &&
aptr[-1] == Dnull && *fstr == Dnull) aptr[-1] == Dnull && *fstr == Dnull)
*--aptr = '\0', fstr++; *--aptr = '\0', fstr++;
y = (char *)ncalloc((aptr - ostr) + strlen(fstr) + 1); y = (char *) hcalloc((aptr - ostr) + strlen(fstr) + 1);
strcpy(y, ostr); strcpy(y, ostr);
*str = y + (aptr - ostr); *str = y + (aptr - ostr);
strcpy(*str, fstr); strcpy(*str, fstr);
@ -1398,26 +1779,27 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
qsort(aval, i, sizeof(char *), sortfn[sortit-1]); qsort(aval, i, sizeof(char *), sortfn[sortit-1]);
} }
if (plan9) { if (plan9) {
LinkList tl = newlinklist();
LinkNode tn; LinkNode tn;
local_list1(tl);
*--fstr = Marker; *--fstr = Marker;
addlinknode(tl, fstr); init_list1(tl, fstr);
if (!eval && !stringsubst(tl, firstnode(tl), ssub)) if (!eval && !stringsubst(&tl, firstnode(&tl), ssub))
return NULL; return NULL;
*str = aptr; *str = aptr;
tn = firstnode(tl); tn = firstnode(&tl);
while ((x = *aval++)) { while ((x = *aval++)) {
if (prenum || postnum) if (prenum || postnum)
x = dopadding(x, prenum, postnum, preone, postone, x = dopadding(x, prenum, postnum, preone, postone,
premul, postmul); premul, postmul);
if (eval && parsestr(x)) if (eval && subst_parse_str(&x, (qt && !nojoin)))
return NULL; return NULL;
xlen = strlen(x); xlen = strlen(x);
for (tn = firstnode(tl); for (tn = firstnode(&tl);
tn && *(y = (char *) getdata(tn)) == Marker; tn && *(y = (char *) getdata(tn)) == Marker;
incnode(tn)) { incnode(tn)) {
strcatsub(&y, ostr, aptr, x, xlen, y + 1, globsubst); strcatsub(&y, ostr, aptr, x, xlen, y + 1, globsubst,
copied);
if (qt && !*y && isarr != 2) if (qt && !*y && isarr != 2)
y = dupstring(nulstring); y = dupstring(nulstring);
if (plan9) if (plan9)
@ -1446,10 +1828,10 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
if (prenum || postnum) if (prenum || postnum)
x = dopadding(x, prenum, postnum, preone, postone, x = dopadding(x, prenum, postnum, preone, postone,
premul, postmul); premul, postmul);
if (eval && parsestr(x)) if (eval && subst_parse_str(&x, (qt && !nojoin)))
return NULL; return NULL;
xlen = strlen(x); xlen = strlen(x);
strcatsub(&y, ostr, aptr, x, xlen, NULL, globsubst); strcatsub(&y, ostr, aptr, x, xlen, NULL, globsubst, copied);
if (qt && !*y && isarr != 2) if (qt && !*y && isarr != 2)
y = dupstring(nulstring); y = dupstring(nulstring);
setdata(n, (void *) y); setdata(n, (void *) y);
@ -1461,7 +1843,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
if (prenum || postnum) if (prenum || postnum)
x = dopadding(x, prenum, postnum, preone, postone, x = dopadding(x, prenum, postnum, preone, postone,
premul, postmul); premul, postmul);
if (eval && parsestr(x)) if (eval && subst_parse_str(&x, (qt && !nojoin)))
return NULL; return NULL;
if (qt && !*x && isarr != 2) if (qt && !*x && isarr != 2)
y = dupstring(nulstring); y = dupstring(nulstring);
@ -1477,10 +1859,10 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
if (prenum || postnum) if (prenum || postnum)
x = dopadding(x, prenum, postnum, preone, postone, x = dopadding(x, prenum, postnum, preone, postone,
premul, postmul); premul, postmul);
if (eval && parsestr(x)) if (eval && subst_parse_str(&x, (qt && !nojoin)))
return NULL; return NULL;
xlen = strlen(x); xlen = strlen(x);
*str = strcatsub(&y, aptr, aptr, x, xlen, fstr, globsubst); *str = strcatsub(&y, aptr, aptr, x, xlen, fstr, globsubst, copied);
if (qt && !*y && isarr != 2) if (qt && !*y && isarr != 2)
y = dupstring(nulstring); y = dupstring(nulstring);
insertlinknode(l, n, (void *) y), incnode(n); insertlinknode(l, n, (void *) y), incnode(n);
@ -1496,11 +1878,11 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
if (prenum || postnum) if (prenum || postnum)
x = dopadding(x, prenum, postnum, preone, postone, x = dopadding(x, prenum, postnum, preone, postone,
premul, postmul); premul, postmul);
if (eval && parsestr(x)) if (eval && subst_parse_str(&x, (qt && !nojoin)))
return NULL; return NULL;
xlen = strlen(x); xlen = strlen(x);
*str = strcatsub(&y, ostr, aptr, x, xlen, fstr, globsubst); *str = strcatsub(&y, ostr, aptr, x, xlen, fstr, globsubst, copied);
if (qt && !*y && isarr != 2) if (qt && !*y)
y = dupstring(nulstring); y = dupstring(nulstring);
setdata(n, (void *) y); setdata(n, (void *) y);
} }
@ -1522,14 +1904,18 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
static char * static char *
arithsubst(char *a, char **bptr, char *rest) arithsubst(char *a, char **bptr, char *rest)
{ {
char *s = *bptr, *t, buf[DIGBUFSIZE]; char *s = *bptr, *t;
char *b = buf; char buf[DIGBUFSIZE], *b = buf;
long v; mnumber v;
singsub(&a); singsub(&a);
v = matheval(a); v = matheval(a);
sprintf(buf, "%ld", v); if (v.type & MN_FLOAT)
t = *bptr = (char *)ncalloc(strlen(*bptr) + strlen(buf) + strlen(rest) + 1); b = convfloat(v.u.d, 0, 0, NULL);
else
convbase(buf, v.u.l, 0);
t = *bptr = (char *) hcalloc(strlen(*bptr) + strlen(b) +
strlen(rest) + 1);
t--; t--;
while ((*++t = *s++)); while ((*++t = *s++));
t--; t--;
@ -1567,6 +1953,8 @@ modify(char **str, char **ptr)
case 't': case 't':
case 'l': case 'l':
case 'u': case 'u':
case 'q':
case 'Q':
c = **ptr; c = **ptr;
break; break;
@ -1587,7 +1975,7 @@ modify(char **str, char **ptr)
if (*ptr1) { if (*ptr1) {
zsfree(hsubl); zsfree(hsubl);
hsubl = ztrdup(ptr1); hsubl = ztrdup(ptr1);
} }
if (!hsubl) { if (!hsubl) {
zerr("no previous substitution", NULL, 0); zerr("no previous substitution", NULL, 0);
return; return;
@ -1685,11 +2073,26 @@ modify(char **str, char **ptr)
if (hsubl && hsubr) if (hsubl && hsubr)
subst(&copy, hsubl, hsubr, gbal); subst(&copy, hsubl, hsubr, gbal);
break; break;
case 'q':
copy = bslashquote(copy, NULL, 0);
break;
case 'Q':
{
int one = noerrs, oef = errflag;
noerrs = 1;
parse_subst_string(copy);
noerrs = one;
errflag = oef;
remnulargs(copy);
untokenize(copy);
}
break;
} }
tc = *tt; tc = *tt;
*tt = '\0'; *tt = '\0';
nl = al + strlen(t) + strlen(copy); nl = al + strlen(t) + strlen(copy);
ptr1 = tmp = (char *)halloc(nl + 1); ptr1 = tmp = (char *)zhalloc(nl + 1);
if (all) if (all)
for (ptr2 = all; *ptr2;) for (ptr2 = all; *ptr2;)
*ptr1++ = *ptr2++; *ptr1++ = *ptr2++;
@ -1736,6 +2139,21 @@ modify(char **str, char **ptr)
} }
} }
break; break;
case 'q':
*str = bslashquote(*str, NULL, 0);
break;
case 'Q':
{
int one = noerrs, oef = errflag;
noerrs = 1;
parse_subst_string(*str);
noerrs = one;
errflag = oef;
remnulargs(*str);
untokenize(*str);
}
break;
} }
} }
if (rec < 0) { if (rec < 0) {
@ -1765,6 +2183,8 @@ dstackent(char ch, int val)
else else
for (end=NULL, n=firstnode(dirstack); n && val; val--, n=nextnode(n)); for (end=NULL, n=firstnode(dirstack); n && val; val--, n=nextnode(n));
if (n == end) { if (n == end) {
if (backwards && !val)
return pwd;
if (isset(NOMATCH)) if (isset(NOMATCH))
zerr("not enough directory stack entries.", NULL, 0); zerr("not enough directory stack entries.", NULL, 0);
return NULL; return NULL;

View File

@ -162,6 +162,18 @@
>1: [[ read.me = (#ia1)README~READ.ME ]] >1: [[ read.me = (#ia1)README~READ.ME ]]
>0: [[ read.me = (#ia1)README~READ_ME ]] >0: [[ read.me = (#ia1)README~READ_ME ]]
>1: [[ read.me = (#ia1)README~(#a1)READ_ME ]] >1: [[ read.me = (#ia1)README~(#a1)READ_ME ]]
>0: [[ test = *((#s)|/)test((#e)|/)* ]]
>0: [[ test/path = *((#s)|/)test((#e)|/)* ]]
>0: [[ path/test = *((#s)|/)test((#e)|/)* ]]
>0: [[ path/test/ohyes = *((#s)|/)test((#e)|/)* ]]
>1: [[ atest = *((#s)|/)test((#e)|/)* ]]
>1: [[ testy = *((#s)|/)test((#e)|/)* ]]
>1: [[ testy/path = *((#s)|/)test((#e)|/)* ]]
>1: [[ path/atest = *((#s)|/)test((#e)|/)* ]]
>1: [[ atest/path = *((#s)|/)test((#e)|/)* ]]
>1: [[ path/testy = *((#s)|/)test((#e)|/)* ]]
>1: [[ path/testy/ohyes = *((#s)|/)test((#e)|/)* ]]
>1: [[ path/atest/ohyes = *((#s)|/)test((#e)|/)* ]]
>0 tests failed. >0 tests failed.
globtest globtests.ksh globtest globtests.ksh

View File

@ -14,16 +14,21 @@
# Produce verbose messages if non-zero. # Produce verbose messages if non-zero.
# If 1, produce reports of tests executed; if 2, also report on progress. # If 1, produce reports of tests executed; if 2, also report on progress.
ZTST_verbose=0 # Defined in such a way that any value from the environment is used.
: ${ZTST_verbose:=0}
# We require all options to be reset, not just emulation options. # We require all options to be reset, not just emulation options.
# Unfortunately, due to the crud which may be in /etc/zshenv this might # Unfortunately, due to the crud which may be in /etc/zshenv this might
# still not be good enough. Maybe we should trick it somehow. # still not be good enough. Maybe we should trick it somehow.
emulate -R zsh emulate -R zsh
# Set the module load path to correspond to this build of zsh.
# This Modules directory should have been created by "make check".
[[ -d Modules/zsh ]] && module_path=( $PWD/Modules )
# We need to be able to save and restore the options used in the test. # We need to be able to save and restore the options used in the test.
# We use the $options variable of the parameter module for this. # We use the $options variable of the parameter module for this.
zmodload -i parameter zmodload -i zsh/parameter
# Note that both the following are regular arrays, since we only use them # Note that both the following are regular arrays, since we only use them
# in whole array assignments to/from $options. # in whole array assignments to/from $options.
@ -42,18 +47,31 @@ ZTST_mainopts=(${(kv)options})
ZTST_testdir=$PWD ZTST_testdir=$PWD
ZTST_testname=$1 ZTST_testname=$1
# The source directory is not necessarily the current directory,
# but if $0 doesn't contain a `/' assume it is.
if [[ $0 = */* ]]; then
ZTST_srcdir=${0%/*}
else
ZTST_srcdir=$PWD
fi
[[ $ZTST_srcdir = /* ]] || ZTST_srcdir="$ZTST_testdir/$ZTST_srcdir"
# Set the function autoload paths to correspond to this build of zsh.
fpath=( $ZTST_srcdir/../(Completion|Functions)/*~*/CVS(/) )
: ${TMPPREFIX:=/tmp/zsh}
# Temporary files for redirection inside tests. # Temporary files for redirection inside tests.
ZTST_in=${TMPPREFIX-:/tmp/zsh}.ztst.in.$$ ZTST_in=${TMPPREFIX}.ztst.in.$$
# hold the expected output # hold the expected output
ZTST_out=${TMPPREFIX-:/tmp/zsh}.ztst.out.$$ ZTST_out=${TMPPREFIX}.ztst.out.$$
ZTST_err=${TMPPREFIX-:/tmp/zsh}.ztst.err.$$ ZTST_err=${TMPPREFIX}.ztst.err.$$
# hold the actual output from the test # hold the actual output from the test
ZTST_tout=${TMPPREFIX-:/tmp/zsh}.ztst.tout.$$ ZTST_tout=${TMPPREFIX}.ztst.tout.$$
ZTST_terr=${TMPPREFIX-:/tmp/zsh}.ztst.terr.$$ ZTST_terr=${TMPPREFIX}.ztst.terr.$$
ZTST_cleanup() { ZTST_cleanup() {
rm -rf $ZTST_testdir/dummy.tmp $ZTST_testdir/*.tmp \ cd $ZTST_testdir
$ZTST_in $ZTST_out $ZTST_err $ZTST_tout $ZTST_terr rm -rf $ZTST_testdir/dummy.tmp $ZTST_testdir/*.tmp ${TMPPREFIX}.ztst*$$
} }
# This cleanup always gets performed, even if we abort. Later, # This cleanup always gets performed, even if we abort. Later,
@ -67,10 +85,11 @@ rm -rf dummy.tmp *.tmp
# Report failure. Note that all output regarding the tests goes to stdout. # Report failure. Note that all output regarding the tests goes to stdout.
# That saves an unpleasant mixture of stdout and stderr to sort out. # That saves an unpleasant mixture of stdout and stderr to sort out.
ZTST_testfailed() { ZTST_testfailed() {
print "Test $ZTST_testname failed: $1" print -r "Test $ZTST_testname failed: $1"
if [[ -n $ZTST_message ]]; then if [[ -n $ZTST_message ]]; then
print "Was testing: $ZTST_message" print -r "Was testing: $ZTST_message"
fi fi
print -r "$ZTST_testname: test failed."
ZTST_cleanup ZTST_cleanup
exit 1 exit 1
} }
@ -79,7 +98,7 @@ ZTST_testfailed() {
ZTST_verbose() { ZTST_verbose() {
local lev=$1 local lev=$1
shift shift
[[ -n $ZTST_verbose && $ZTST_verbose -ge $lev ]] && print $* >&8 [[ -n $ZTST_verbose && $ZTST_verbose -ge $lev ]] && print -- $* >&8
} }
[[ ! -r $ZTST_testname ]] && ZTST_testfailed "can't read test file." [[ ! -r $ZTST_testname ]] && ZTST_testfailed "can't read test file."
@ -97,7 +116,7 @@ ZTST_cursect=''
ZTST_getline() { ZTST_getline() {
local IFS= local IFS=
while true; do while true; do
read ZTST_curline <&9 || return 1 read -r ZTST_curline <&9 || return 1
[[ $ZTST_curline == \#* ]] || return 0 [[ $ZTST_curline == \#* ]] || return 0
done done
} }
@ -144,7 +163,7 @@ $ZTST_code"
# Read in a piece for redirection. # Read in a piece for redirection.
ZTST_getredir() { ZTST_getredir() {
local char=${ZTST_curline[1]} local char=${ZTST_curline[1]} fn
ZTST_redir=${ZTST_curline[2,-1]} ZTST_redir=${ZTST_curline[2,-1]}
while ZTST_getline; do while ZTST_getline; do
[[ $ZTST_curline[1] = $char ]] || break [[ $ZTST_curline[1] = $char ]] || break
@ -153,6 +172,22 @@ ${ZTST_curline[2,-1]}"
done done
ZTST_verbose 2 "ZTST_getredir: read redir for '$char': ZTST_verbose 2 "ZTST_getredir: read redir for '$char':
$ZTST_redir" $ZTST_redir"
case $char in
'<') fn=$ZTST_in
;;
'>') fn=$ZTST_out
;;
'?') fn=$ZTST_err
;;
*) ZTST_testfailed "bad redir operator: $char"
;;
esac
if [[ $ZTST_flags = *q* ]]; then
print -r -- "${(e)ZTST_redir}" >>$fn
else
print -r -- "$ZTST_redir" >>$fn
fi
} }
# Execute an indented chunk. Redirections will already have # Execute an indented chunk. Redirections will already have
@ -209,27 +244,24 @@ $ZTST_curline"
fi fi
;; ;;
[[:space:]]##[^[:space:]]*) ZTST_getchunk [[:space:]]##[^[:space:]]*) ZTST_getchunk
[[ $ZTST_curline != [-0-9]* ]] && if [[ $ZTST_curline == (#b)([-0-9]##)([[:alpha:]]#)(:*)# ]]; then
ZTST_testfailed "expecting test status at:
$ZTST_curline"
ZTST_xstatus=$ZTST_curline
if [[ $ZTST_curline == (#b)([^:]##):(*) ]]; then
ZTST_xstatus=$match[1] ZTST_xstatus=$match[1]
ZTST_message=$match[2] ZTST_flags=$match[2]
ZTST_message=${match[3]:+${match[3][2,-1]}}
else
ZTST_testfailed "expecting test status at:
$ZTST_curline"
fi fi
ZTST_getline ZTST_getline
found=1 found=1
;; ;;
'<'*) ZTST_getredir '<'*) ZTST_getredir
print -r "${(e)ZTST_redir}" >>$ZTST_in
found=1 found=1
;; ;;
'>'*) ZTST_getredir '>'*) ZTST_getredir
print -r "${(e)ZTST_redir}" >>$ZTST_out
found=1 found=1
;; ;;
'?'*) ZTST_getredir '?'*) ZTST_getredir
print -r "${(e)ZTST_redir}" >>$ZTST_err
found=1 found=1
;; ;;
*) ZTST_testfailed "bad line in test block: *) ZTST_testfailed "bad line in test block:
@ -240,8 +272,7 @@ $ZTST_curline"
# If we found some code to execute... # If we found some code to execute...
if [[ -n $ZTST_code ]]; then if [[ -n $ZTST_code ]]; then
ZTST_verbose 1 "Running test: ZTST_verbose 1 "Running test: $ZTST_message"
$ZTST_message"
ZTST_verbose 2 "ZTST_test: expecting status: $ZTST_xstatus" ZTST_verbose 2 "ZTST_test: expecting status: $ZTST_xstatus"
ZTST_execchunk <$ZTST_in >$ZTST_tout 2>$ZTST_terr ZTST_execchunk <$ZTST_in >$ZTST_tout 2>$ZTST_terr
@ -249,7 +280,9 @@ $ZTST_message"
# First check we got the right status, if specified. # First check we got the right status, if specified.
if [[ $ZTST_xstatus != - && $ZTST_xstatus != $ZTST_status ]]; then if [[ $ZTST_xstatus != - && $ZTST_xstatus != $ZTST_status ]]; then
ZTST_testfailed "bad status $ZTST_status, expected $ZTST_xstatus from: ZTST_testfailed "bad status $ZTST_status, expected $ZTST_xstatus from:
$ZTST_code" $ZTST_code${$(<$ZTST_terr):+
Error output:
$(<$ZTST_terr)}"
fi fi
ZTST_verbose 2 "ZTST_test: test produced standard output: ZTST_verbose 2 "ZTST_test: test produced standard output:
@ -258,11 +291,13 @@ ZTST_test: and standard error:
$(<$ZTST_terr)" $(<$ZTST_terr)"
# Now check output and error. # Now check output and error.
if ! diff -c $ZTST_out $ZTST_tout; then if [[ $ZTST_flags != *d* ]] && ! diff -c $ZTST_out $ZTST_tout; then
ZTST_testfailed "output differs from expected as shown above for: ZTST_testfailed "output differs from expected as shown above for:
$ZTST_code" $ZTST_code${$(<$ZTST_terr):+
Error output:
$(<$ZTST_terr)}"
fi fi
if ! diff -c $ZTST_err $ZTST_terr; then if [[ $ZTST_flags != *D* ]] && ! diff -c $ZTST_err $ZTST_terr; then
ZTST_testfailed "error output differs from expected as shown above for: ZTST_testfailed "error output differs from expected as shown above for:
$ZTST_code" $ZTST_code"
fi fi