diff options
author | Peter Stephenson <pws@users.sourceforge.net> | 2002-01-22 12:40:25 +0000 |
---|---|---|
committer | Peter Stephenson <pws@users.sourceforge.net> | 2002-01-22 12:40:25 +0000 |
commit | 13b57311def75963aa73892660687252c38ce183 (patch) | |
tree | ac5641188b15182d709fb75e9fb7bfa15397f8b7 | |
parent | 3807c902a23ea8b8e673103dff49d3d27a551756 (diff) | |
download | zsh-13b57311def75963aa73892660687252c38ce183.tar.gz zsh-13b57311def75963aa73892660687252c38ce183.tar.xz zsh-13b57311def75963aa73892660687252c38ce183.zip |
16486: Doc/Zsh/expn.yo, Src/glob.c, Src/pattern.c: support
(#q...) EXTENDED_GLOB syntax for qualifiers. May be chained, ignored by pattern matching code.
-rw-r--r-- | ChangeLog | 6 | ||||
-rw-r--r-- | Doc/Zsh/expn.yo | 33 | ||||
-rw-r--r-- | Src/glob.c | 974 | ||||
-rw-r--r-- | Src/pattern.c | 179 |
4 files changed, 699 insertions, 493 deletions
diff --git a/ChangeLog b/ChangeLog index c635f4c22..45ef93034 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2002-01-22 Peter Stephenson <pws@csr.com> + + * 16486: Doc/Zsh/expn.yo, Src/glob.c, Src/pattern.c: support + (#q...) EXTENDED_GLOB syntax for qualifiers. May be chained, + ignored by pattern matching code. + 2002-01-22 Sven Wischnowsky <wischnow@zsh.org> * 16483: Completion/Base/Completer/_complete, diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index f58c61145..3d40da10b 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -1442,6 +1442,17 @@ anywhere except at the start of the string, although this actually means need to use `tt((""~(#s)))' to match a zero-length portion of the string not at the start. ) +item(tt(q))( +A `tt(q)' and everything up to the closing parenthesis of the globbing +flags are ignored by the pattern matching code. This is intended to +support the use of glob qualifiers, see below. The result is that +the pattern `tt((#b)(*).c(#q.))' can be used both for globbing and for +matching against a string. In the former case, the `tt((#q.))' will be +treated as a glob qualifier and the `tt((#b))' will not be useful, while in +the latter case the `tt((#b))' is useful for backreferences and the +`tt((#q.))' will be ignored. Note that colon modifiers in the glob +qualifiers are also not applied in ordinary pattern matching. +) enditem() For example, the test string tt(fooxx) can be matched by the pattern @@ -1564,6 +1575,20 @@ qualifiers, for example `tt((^x))', can be forced to be treated as part of the glob pattern by doubling the parentheses, in this case producing `tt(((^x)))'. +If the option tt(EXTENDED_GLOB) is set, a different syntax for glob +qualifiers is available, namely `tt((#qx))' where tt(x) is any of the same +glob qualifiers used in the other format. The qualifiers must still appear +at the end of the pattern. However, with this syntax multiple glob +qualifiers may be chained together. They are treated as a logical AND of +the individual sets of flags. Also, as the syntax is unambiguous, the +expression will be treated as glob qualifiers just as long any parentheses +contained within it are balanced; appearance of `tt(|)', `tt(LPAR())' or +`tt(~)' does not negate the effect. Note that qualifiers will be +recognised in this form even if a bare glob qualifier exists at the end of +the pattern, for example `tt(*(#q*)(.))' will recognise executable regular +files if both options are set; however, mixed syntax should probably be +avoided for the sake of clarity. + A qualifier may be any one of the following: startitem() @@ -1847,3 +1872,11 @@ example(ls *.*~(lex|parse).[ch](^D^l1)) lists all files having a link count of one whose names contain a dot (but not those starting with a dot, since tt(GLOB_DOTS) is explicitly switched off) except for tt(lex.c), tt(lex.h), tt(parse.c) and tt(parse.h). + +example(print b*.pro(#q:s/pro/shmo/)(#q.:s/builtin/shmiltin/)) + +demonstrates how colon modifiers and other qualifiers may be chained +together. The ordinary qualifier `tt(.)' is applied first, then the colon +modifiers in order from left to right. So if tt(EXTENDED_GLOB) is set and +the base battern matches the regular file tt(builtin.pro), the shell will +print `tt(shmiltin.shmo)'. diff --git a/Src/glob.c b/Src/glob.c index 91b71dc77..76f23cdb6 100644 --- a/Src/glob.c +++ b/Src/glob.c @@ -38,7 +38,7 @@ /* flag for CSHNULLGLOB */ -typedef struct gmatch *Gmatch; +typedef struct gmatch *Gmatch; struct gmatch { char *name; @@ -76,7 +76,7 @@ struct gmatch { /**/ int badcshglob; - + /**/ int pathpos; /* position in pathbuf (needed by pattern code) */ @@ -707,6 +707,7 @@ static Complist parsepat(char *str) { long assert; + int ignore; patcompstart(); /* @@ -717,7 +718,7 @@ parsepat(char *str) (isset(KSHGLOB) && *str == '@' && str[1] == Inpar && str[2] == Pound)) { str += (*str == Inpar) ? 2 : 3; - if (!patgetglobflags(&str, &assert)) + if (!patgetglobflags(&str, &assert, &ignore)) return NULL; } @@ -914,6 +915,35 @@ gmatchcmp(Gmatch a, Gmatch b) return 0; } +/* + * Duplicate a list of qualifiers using the `next' linkage (not the + * `or' linkage). Return the head element and set *last (if last non-NULL) + * to point to the last element of the new list. All allocation is on the + * heap (or off the heap?) + */ +static struct qual *dup_qual_list(struct qual *orig, struct qual **lastp) +{ + struct qual *qfirst = NULL, *qlast = NULL; + + while (orig) { + struct qual *qnew = (struct qual *)zhalloc(sizeof(struct qual)); + *qnew = *orig; + qnew->next = qnew->or = NULL; + + if (!qfirst) + qfirst = qnew; + if (qlast) + qlast->next = qnew; + qlast = qnew; + + orig = orig->next; + } + + if (lastp) + *lastp = qlast; + return qfirst; +} + /* Main entry point to the globbing code for filename globbing. * * np points to a node in the list list which will be expanded * * into a series of nodes. */ @@ -932,6 +962,7 @@ zglob(LinkList list, LinkNode np, int nountok) int first = 0, end = -1; /* index of first match to return */ /* and index+1 of the last match */ struct globdata saved; /* saved glob state */ + int nobareglob = !isset(BAREGLOBQUAL); if (unset(GLOBOPT) || !haswilds(ostr)) { if (!nountok) @@ -941,13 +972,22 @@ zglob(LinkList list, LinkNode np, int nountok) save_globstate(saved); str = dupstring(ostr); - sl = strlen(str); uremnode(list, np); - /* Initialise state variables for current file pattern */ - qo = qn = quals = ql = NULL; + /* quals will hold the complete list of qualifiers (file static). */ + quals = NULL; + /* + * qualct and qualorct indicate we have qualifiers in the last + * alternative, or a set of alternatives, respectively. They + * are not necessarily an accurate count, however. + */ qualct = qualorct = 0; + /* + * colonmod is a concatenated list of all colon modifiers found in + * all sets of qualifiers. + */ colonmod = NULL; + /* The gf_* flags are qualifiers which are applied globally. */ gf_nullglob = isset(NULLGLOB); gf_markdirs = isset(MARKDIRS); gf_listtypes = gf_follow = 0; @@ -956,433 +996,549 @@ zglob(LinkList list, LinkNode np, int nountok) gf_sorts = gf_nsorts = 0; /* Check for qualifiers */ - if (isset(BAREGLOBQUAL) && str[sl - 1] == Outpar) { + while (!nobareglob || isset(EXTENDEDGLOB)) { + struct qual *newquals; char *s; + int sense, paren; + off_t data; + char *sdata, *newcolonmod; + int (*func) _((char *, Statptr, off_t, char *)); + + /* + * Initialise state variables for current file pattern. + * newquals is the root for the linked list of all qualifiers. + * qo is the root of the current list of alternatives. + * ql is the end of the current alternative where the `next' will go. + * qn is the current qualifier node to be added. + * + * Here is an attempt at a diagram. An `or' is added horizontally + * to the top line, a `next' at the bottom of the right hand line. + * `qn' is usually NULL unless a new `or' has just been added. + * + * quals -> x -> x -> qo + * | | | + * x x x + * | | + * x ql + * + * In fact, after each loop the complete set is in the file static + * `quals'. Then, if we have a second set of qualifiers, we merge + * the lists together. This is only tricky if one or both have an + * `or' in them; then we need to distribute over all alternatives. + */ + newquals = qo = qn = ql = NULL; + + sl = strlen(str); + if (str[sl - 1] != Outpar) + break; /* Check these are really qualifiers, not a set of * - * alternatives or exclusions */ - for (s = str + sl - 2; *s != Inpar; s--) - if (*s == Bar || *s == Outpar || - (isset(EXTENDEDGLOB) && *s == Tilde)) + * alternatives or exclusions. We can be more * + * lenient with an explicit (#q) than with a bare * + * set of qualifiers. */ + paren = 0; + for (s = str + sl - 2; *s && (*s != Inpar || paren); s--) { + switch (*s) { + case Outpar: + paren++; /*FALLTHROUGH*/ + case Bar: + nobareglob = 1; + break; + case Tilde: + if (isset(EXTENDEDGLOB)) + nobareglob = 1; break; - if (*s == Inpar && (!isset(EXTENDEDGLOB) || s[1] != Pound)) { - /* Real qualifiers found. */ - int sense = 0; /* bit 0 for match (0)/don't match (1) */ - /* bit 1 for follow links (2), don't (0) */ - off_t data = 0; /* Any numerical argument required */ - char *sdata = NULL; /* Any list argument required */ - int (*func) _((char *, Statptr, off_t, char *)); - - str[sl-1] = 0; - *s++ = 0; - while (*s && !colonmod) { - func = (int (*) _((char *, Statptr, off_t, char *)))0; - if (idigit(*s)) { - /* Store numeric argument for qualifier */ + case Inpar: + paren--; + break; + } + } + if (*s != Inpar) + break; + if (isset(EXTENDEDGLOB) && s[1] == Pound) { + if (s[2] == 'q') { + *s = 0; + s += 2; + } else + break; + } else if (nobareglob) + break; + + /* Real qualifiers found. */ + nobareglob = 1; + sense = 0; /* bit 0 for match (0)/don't match (1) */ + /* bit 1 for follow links (2), don't (0) */ + data = 0; /* Any numerical argument required */ + sdata = NULL; /* Any list argument required */ + newcolonmod = NULL; /* Contains trailing colon modifiers */ + + str[sl-1] = 0; + *s++ = 0; + while (*s && !newcolonmod) { + func = (int (*) _((char *, Statptr, off_t, char *)))0; + if (idigit(*s)) { + /* Store numeric argument for qualifier */ + func = qualflags; + data = 0; + sdata = NULL; + while (idigit(*s)) + data = data * 010 + (*s++ - '0'); + } else if (*s == ',') { + /* A comma separates alternative sets of qualifiers */ + s++; + sense = 0; + if (qualct) { + qn = (struct qual *)hcalloc(sizeof *qn); + qo->or = qn; + qo = qn; + qualorct++; + qualct = 0; + ql = NULL; + } + } else { + switch (*s++) { + case ':': + /* Remaining arguments are history-type * + * colon substitutions, handled separately. */ + newcolonmod = s - 1; + untokenize(newcolonmod); + if (colonmod) { + /* remember we're searching backwards */ + colonmod = dyncat(newcolonmod, colonmod); + } else + colonmod = newcolonmod; + break; + case Hat: + case '^': + /* Toggle sense: go from positive to * + * negative match and vice versa. */ + sense ^= 1; + break; + case '-': + /* Toggle matching of symbolic links */ + sense ^= 2; + break; + case '@': + /* Match symbolic links */ + func = qualislnk; + break; + case Equals: + case '=': + /* Match sockets */ + func = qualissock; + break; + case 'p': + /* Match named pipes */ + func = qualisfifo; + break; + case '/': + /* Match directories */ + func = qualisdir; + break; + case '.': + /* Match regular files */ + func = qualisreg; + break; + case '%': + /* Match special files: block, * + * character or any device */ + if (*s == 'b') + s++, func = qualisblk; + else if (*s == 'c') + s++, func = qualischr; + else + func = qualisdev; + break; + case Star: + /* Match executable plain files */ + func = qualiscom; + break; + case 'R': + /* Match world-readable files */ func = qualflags; - data = 0; - sdata = NULL; - while (idigit(*s)) - data = data * 010 + (*s++ - '0'); - } else if (*s == ',') { - /* A comma separates alternative sets of qualifiers */ - s++; - sense = 0; - if (qualct) { - qn = (struct qual *)hcalloc(sizeof *qn); - qo->or = qn; - qo = qn; - qualorct++; - qualct = 0; - ql = NULL; - } - } else - switch (*s++) { - case ':': - /* Remaining arguments are history-type * - * colon substitutions, handled separately. */ - colonmod = s - 1; - untokenize(colonmod); - break; - case Hat: - case '^': - /* Toggle sense: go from positive to * - * negative match and vice versa. */ - sense ^= 1; - break; - case '-': - /* Toggle matching of symbolic links */ - sense ^= 2; - break; - case '@': - /* Match symbolic links */ - func = qualislnk; - break; - case Equals: - case '=': - /* Match sockets */ - func = qualissock; - break; - case 'p': - /* Match named pipes */ - func = qualisfifo; - break; - case '/': - /* Match directories */ - func = qualisdir; - break; - case '.': - /* Match regular files */ - func = qualisreg; - break; - case '%': - /* Match special files: block, * - * character or any device */ - if (*s == 'b') - s++, func = qualisblk; - else if (*s == 'c') - s++, func = qualischr; - else - func = qualisdev; - break; - case Star: - /* Match executable plain files */ - func = qualiscom; - break; - case 'R': - /* Match world-readable files */ - func = qualflags; - data = 0004; - break; - case 'W': - /* Match world-writeable files */ - func = qualflags; - data = 0002; - break; - case 'X': - /* Match world-executable files */ - func = qualflags; - data = 0001; - break; - case 'A': - func = qualflags; - data = 0040; - break; - case 'I': - func = qualflags; - data = 0020; - break; - case 'E': - func = qualflags; - data = 0010; - break; - case 'r': - /* Match files readable by current process */ - func = qualflags; - data = 0400; - break; - case 'w': - /* Match files writeable by current process */ - func = qualflags; - data = 0200; - break; - case 'x': - /* Match files executable by current process */ - func = qualflags; - data = 0100; - break; - case 's': - /* Match setuid files */ - func = qualflags; - data = 04000; - break; - case 'S': - /* Match setgid files */ - func = qualflags; - data = 02000; - break; - case 't': - func = qualflags; - data = 01000; - break; - case 'd': - /* Match device files by device number * - * (as given by stat's st_dev element). */ - func = qualdev; + data = 0004; + break; + case 'W': + /* Match world-writeable files */ + func = qualflags; + data = 0002; + break; + case 'X': + /* Match world-executable files */ + func = qualflags; + data = 0001; + break; + case 'A': + func = qualflags; + data = 0040; + break; + case 'I': + func = qualflags; + data = 0020; + break; + case 'E': + func = qualflags; + data = 0010; + break; + case 'r': + /* Match files readable by current process */ + func = qualflags; + data = 0400; + break; + case 'w': + /* Match files writeable by current process */ + func = qualflags; + data = 0200; + break; + case 'x': + /* Match files executable by current process */ + func = qualflags; + data = 0100; + break; + case 's': + /* Match setuid files */ + func = qualflags; + data = 04000; + break; + case 'S': + /* Match setgid files */ + func = qualflags; + data = 02000; + break; + case 't': + func = qualflags; + data = 01000; + break; + case 'd': + /* Match device files by device number * + * (as given by stat's st_dev element). */ + func = qualdev; + data = qgetnum(&s); + break; + case 'l': + /* Match files with the given no. of hard links */ + func = qualnlink; + g_amc = -1; + goto getrange; + case 'U': + /* Match files owned by effective user ID */ + func = qualuid; + data = geteuid(); + break; + case 'G': + /* Match files owned by effective group ID */ + func = qualgid; + data = getegid(); + break; + case 'u': + /* Match files owned by given user id */ + func = qualuid; + /* either the actual uid... */ + if (idigit(*s)) data = qgetnum(&s); - break; - case 'l': - /* Match files with the given no. of hard links */ - func = qualnlink; - g_amc = -1; - goto getrange; - case 'U': - /* Match files owned by effective user ID */ - func = qualuid; - data = geteuid(); - break; - case 'G': - /* Match files owned by effective group ID */ - func = qualgid; - data = getegid(); - break; - case 'u': - /* Match files owned by given user id */ - func = qualuid; - /* either the actual uid... */ - if (idigit(*s)) - data = qgetnum(&s); - else { - /* ... or a user name */ - char sav, *tt; - - /* Find matching delimiters */ - tt = get_strarg(s); - if (!*tt) { - zerr("missing end of name", - NULL, 0); - data = 0; - } else { + else { + /* ... or a user name */ + char sav, *tt; + + /* Find matching delimiters */ + tt = get_strarg(s); + if (!*tt) { + zerr("missing end of name", + NULL, 0); + data = 0; + } else { #ifdef HAVE_GETPWNAM - struct passwd *pw; - sav = *tt; - *tt = '\0'; - - if ((pw = getpwnam(s + 1))) - data = pw->pw_uid; - else { - zerr("unknown user", NULL, 0); - data = 0; - } - *tt = sav; -#else /* !HAVE_GETPWNAM */ - sav = *tt; + struct passwd *pw; + sav = *tt; + *tt = '\0'; + + if ((pw = getpwnam(s + 1))) + data = pw->pw_uid; + else { zerr("unknown user", NULL, 0); data = 0; -#endif /* !HAVE_GETPWNAM */ - if (sav) - s = tt + 1; - else - s = tt; } + *tt = sav; +#else /* !HAVE_GETPWNAM */ + sav = *tt; + zerr("unknown user", NULL, 0); + data = 0; +#endif /* !HAVE_GETPWNAM */ + if (sav) + s = tt + 1; + else + s = tt; } - break; - case 'g': - /* Given gid or group id... works like `u' */ - func = qualgid; - /* either the actual gid... */ - if (idigit(*s)) - data = qgetnum(&s); - else { - /* ...or a delimited group name. */ - char sav, *tt; - - tt = get_strarg(s); - if (!*tt) { - zerr("missing end of name", - NULL, 0); - data = 0; - } else { + } + break; + case 'g': + /* Given gid or group id... works like `u' */ + func = qualgid; + /* either the actual gid... */ + if (idigit(*s)) + data = qgetnum(&s); + else { + /* ...or a delimited group name. */ + char sav, *tt; + + tt = get_strarg(s); + if (!*tt) { + zerr("missing end of name", + NULL, 0); + data = 0; + } else { #ifdef HAVE_GETGRNAM - struct group *gr; - sav = *tt; - *tt = '\0'; - - if ((gr = getgrnam(s + 1))) - data = gr->gr_gid; - else { - zerr("unknown group", NULL, 0); - data = 0; - } - *tt = sav; -#else /* !HAVE_GETGRNAM */ - sav = *tt; + struct group *gr; + sav = *tt; + *tt = '\0'; + + if ((gr = getgrnam(s + 1))) + data = gr->gr_gid; + else { zerr("unknown group", NULL, 0); data = 0; -#endif /* !HAVE_GETGRNAM */ - if (sav) - s = tt + 1; - else - s = tt; - } - } - break; - case 'f': - /* Match modes with chmod-spec. */ - func = qualmodeflags; - data = qgetmodespec(&s); - break; - case 'M': - /* Mark directories with a / */ - if ((gf_markdirs = !(sense & 1))) - gf_follow = sense & 2; - break; - case 'T': - /* Mark types in a `ls -F' type fashion */ - if ((gf_listtypes = !(sense & 1))) - gf_follow = sense & 2; - break; - case 'N': - /* Nullglob: remove unmatched patterns. */ - gf_nullglob = !(sense & 1); - break; - case 'D': - /* Glob dots: match leading dots implicitly */ - gf_noglobdots = sense & 1; - break; - case 'n': - /* Numeric glob sort */ - gf_numsort = !(sense & 1); - break; - case 'a': - /* Access time in given range */ - g_amc = 0; - func = qualtime; - goto getrange; - case 'm': - /* Modification time in given range */ - g_amc = 1; - func = qualtime; - goto getrange; - case 'c': - /* Inode creation time in given range */ - g_amc = 2; - func = qualtime; - goto getrange; - case 'L': - /* File size (Length) in given range */ - func = qualsize; - g_amc = -1; - /* Get size multiplier */ - g_units = TT_BYTES; - if (*s == 'p' || *s == 'P') - g_units = TT_POSIX_BLOCKS, ++s; - else if (*s == 'k' || *s == 'K') - g_units = TT_KILOBYTES, ++s; - else if (*s == 'm' || *s == 'M') - g_units = TT_MEGABYTES, ++s; - getrange: - /* Get time multiplier */ - if (g_amc >= 0) { - g_units = TT_DAYS; - if (*s == 'h') - g_units = TT_HOURS, ++s; - else if (*s == 'm') - g_units = TT_MINS, ++s; - else if (*s == 'w') - g_units = TT_WEEKS, ++s; - else if (*s == 'M') - g_units = TT_MONTHS, ++s; - else if (*s == 's') - g_units = TT_SECONDS, ++s; - } - /* See if it's greater than, equal to, or less than */ - if ((g_range = *s == '+' ? 1 : *s == '-' ? -1 : 0)) - ++s; - data = qgetnum(&s); - break; - - case 'o': - case 'O': - { - int t; - - switch (*s) { - case 'n': t = GS_NAME; break; - case 'L': t = GS_SIZE; break; - case 'l': t = GS_LINKS; break; - case 'a': t = GS_ATIME; break; - case 'm': t = GS_MTIME; break; - case 'c': t = GS_CTIME; break; - case 'd': t = GS_DEPTH; break; - default: - zerr("unknown sort specifier", NULL, 0); - restore_globstate(saved); - return; - } - if ((sense & 2) && !(t & (GS_NAME|GS_DEPTH))) - t <<= GS_SHIFT; - if (gf_sorts & t) { - zerr("doubled sort specifier", NULL, 0); - restore_globstate(saved); - return; } - gf_sorts |= t; - gf_sortlist[gf_nsorts++] = t | - (((sense & 1) ^ (s[-1] == 'O')) ? GS_DESC : 0); - s++; - break; + *tt = sav; +#else /* !HAVE_GETGRNAM */ + sav = *tt; + zerr("unknown group", NULL, 0); + data = 0; +#endif /* !HAVE_GETGRNAM */ + if (sav) + s = tt + 1; + else + s = tt; } - case 'e': - { - char sav, *tt = get_strarg(s); + } + break; + case 'f': + /* Match modes with chmod-spec. */ + func = qualmodeflags; + data = qgetmodespec(&s); + break; + case 'M': + /* Mark directories with a / */ + if ((gf_markdirs = !(sense & 1))) + gf_follow = sense & 2; + break; + case 'T': + /* Mark types in a `ls -F' type fashion */ + if ((gf_listtypes = !(sense & 1))) + gf_follow = sense & 2; + break; + case 'N': + /* Nullglob: remove unmatched patterns. */ + gf_nullglob = !(sense & 1); + break; + case 'D': + /* Glob dots: match leading dots implicitly */ + gf_noglobdots = sense & 1; + break; + case 'n': + /* Numeric glob sort */ + gf_numsort = !(sense & 1); + break; + case 'a': + /* Access time in given range */ + g_amc = 0; + func = qualtime; + goto getrange; + case 'm': + /* Modification time in given range */ + g_amc = 1; + func = qualtime; + goto getrange; + case 'c': + /* Inode creation time in given range */ + g_amc = 2; + func = qualtime; + goto getrange; + case 'L': + /* File size (Length) in given range */ + func = qualsize; + g_amc = -1; + /* Get size multiplier */ + g_units = TT_BYTES; + if (*s == 'p' || *s == 'P') + g_units = TT_POSIX_BLOCKS, ++s; + else if (*s == 'k' || *s == 'K') + g_units = TT_KILOBYTES, ++s; + else if (*s == 'm' || *s == 'M') + g_units = TT_MEGABYTES, ++s; + getrange: + /* Get time multiplier */ + if (g_amc >= 0) { + g_units = TT_DAYS; + if (*s == 'h') + g_units = TT_HOURS, ++s; + else if (*s == 'm') + g_units = TT_MINS, ++s; + else if (*s == 'w') + g_units = TT_WEEKS, ++s; + else if (*s == 'M') + g_units = TT_MONTHS, ++s; + else if (*s == 's') + g_units = TT_SECONDS, ++s; + } + /* See if it's greater than, equal to, or less than */ + if ((g_range = *s == '+' ? 1 : *s == '-' ? -1 : 0)) + ++s; + data = qgetnum(&s); + break; - if (!*tt) { - zerr("missing end of string", NULL, 0); - data = 0; - } else { - sav = *tt; - *tt = '\0'; - func = qualsheval; - sdata = dupstring(s + 1); - untokenize(sdata); - *tt = sav; - if (sav) - s = tt + 1; - else - s = tt; - } - break; - } - case '[': - case Inbrack: - { - char *os = --s; - struct value v; - - v.isarr = SCANPM_WANTVALS; - v.pm = NULL; - v.end = -1; - v.inv = 0; - if (getindex(&s, &v, 0) || s == os) { - zerr("invalid subscript", NULL, 0); - restore_globstate(saved); - return; - } - first = v.start; - end = v.end; - break; - } + case 'o': + case 'O': + { + int t; + + switch (*s) { + case 'n': t = GS_NAME; break; + case 'L': t = GS_SIZE; break; + case 'l': t = GS_LINKS; break; + case 'a': t = GS_ATIME; break; + case 'm': t = GS_MTIME; break; + case 'c': t = GS_CTIME; break; + case 'd': t = GS_DEPTH; break; default: - zerr("unknown file attribute", NULL, 0); + zerr("unknown sort specifier", NULL, 0); restore_globstate(saved); return; } - if (func) { - /* Requested test is performed by function func */ - if (!qn) - qn = (struct qual *)hcalloc(sizeof *qn); - if (ql) - ql->next = qn; - ql = qn; - if (!quals) - quals = qo = qn; - qn->func = func; - qn->sense = sense; - qn->data = data; - qn->sdata = sdata; - qn->range = g_range; - qn->units = g_units; - qn->amc = g_amc; - qn = NULL; - qualct++; + if ((sense & 2) && !(t & (GS_NAME|GS_DEPTH))) + t <<= GS_SHIFT; + if (gf_sorts & t) { + zerr("doubled sort specifier", NULL, 0); + restore_globstate(saved); + return; + } + gf_sorts |= t; + gf_sortlist[gf_nsorts++] = t | + (((sense & 1) ^ (s[-1] == 'O')) ? GS_DESC : 0); + s++; + break; + } + case 'e': + { + char sav, *tt = get_strarg(s); + + if (!*tt) { + zerr("missing end of string", NULL, 0); + data = 0; + } else { + sav = *tt; + *tt = '\0'; + func = qualsheval; + sdata = dupstring(s + 1); + untokenize(sdata); + *tt = sav; + if (sav) + s = tt + 1; + else + s = tt; + } + break; } - if (errflag) { + case '[': + case Inbrack: + { + char *os = --s; + struct value v; + + v.isarr = SCANPM_WANTVALS; + v.pm = NULL; + v.end = -1; + v.inv = 0; + if (getindex(&s, &v, 0) || s == os) { + zerr("invalid subscript", NULL, 0); + restore_globstate(saved); + return; + } + first = v.start; + end = v.end; + break; + } + default: + zerr("unknown file attribute", NULL, 0); restore_globstate(saved); return; } } + if (func) { + /* Requested test is performed by function func */ + if (!qn) + qn = (struct qual *)hcalloc(sizeof *qn); + if (ql) + ql->next = qn; + ql = qn; + if (!newquals) + newquals = qo = qn; + qn->func = func; + qn->sense = sense; + qn->data = data; + qn->sdata = sdata; + qn->range = g_range; + qn->units = g_units; + qn->amc = g_amc; + + qn = NULL; + qualct++; + } + if (errflag) { + restore_globstate(saved); + return; + } } + + if (quals && newquals) { + /* Merge previous group of qualifiers with new set. */ + if (quals->or || newquals->or) { + /* The hard case. */ + struct qual *qorhead = NULL, *qortail = NULL; + /* + * Distribute in the most trivial way, by creating + * all possible combinations of the two sets and chaining + * these into one long set of alternatives given + * by qorhead and qortail. + */ + for (qn = newquals; qn; qn = qn->or) { + for (qo = quals; qo; qo = qo->or) { + struct qual *qfirst, *qlast; + int islast = !qn->or && !qo->or; + /* Generate first set of qualifiers... */ + if (islast) { + /* Last time round: don't bother copying. */ + qfirst = qn; + for (qlast = qfirst; qlast->next; + qlast = qlast->next) + ; + } else + qfirst = dup_qual_list(qn, &qlast); + /* ... link into new `or' chain ... */ + if (!qorhead) + qorhead = qfirst; + if (qortail) + qortail->or = qfirst; + qortail = qfirst; + /* ... and concatenate second set. */ + qlast->next = islast ? qo : dup_qual_list(qo, NULL); + } + } + quals = qorhead; + } else { + /* + * Easy: we can just chain the qualifiers together. + * This is an optimisation; the code above will work, too. + * We retain the original left to right ordering --- remember + * we are searching for sets of qualifiers from the right. + */ + qn = newquals; + for ( ; newquals->next; newquals = newquals->next) + ; + newquals->next = quals; + quals = qn; + } + } else + quals = newquals; } q = parsepat(str); if (!q || errflag) { /* if parsing failed */ @@ -1638,7 +1794,7 @@ xpandredir(struct redir *fn, LinkList tab) if (fn->type == REDIR_MERGEIN || fn->type == REDIR_MERGEOUT) { if (s[0] == '-' && !s[1]) fn->type = REDIR_CLOSE; - else if (s[0] == 'p' && !s[1]) + else if (s[0] == 'p' && !s[1]) fn->fd2 = -2; else { while (idigit(*s)) @@ -1703,7 +1859,7 @@ xpandbraces(LinkList list, LinkNode *np) int rstart = zstrtol(str+1,&dots,10), rend = 0, err = 0, rev = 0; int wid1 = (dots - str) - 1, wid2 = (str2 - dots) - 2; int strp = str - str3; - + if (dots == str + 1 || *dots != '.' || dots[1] != '.') err++; else { @@ -1846,7 +2002,7 @@ struct repldata { }; typedef struct repldata *Repldata; -/* +/* * List of bits of matches to concatenate with replacement string. * The data is a struct repldata. It is not used in cases like * ${...//#foo/bar} even though SUB_GLOBAL is set, since the match diff --git a/Src/pattern.c b/Src/pattern.c index f75698adc..cf78a1138 100644 --- a/Src/pattern.c +++ b/Src/pattern.c @@ -631,7 +631,7 @@ patcompswitch(int paren, int *flagp) static long patcompbranch(int *flagp) { - long chain, latest, starter; + long chain, latest = 0, starter; int flags = 0; *flagp = P_PURESTR; @@ -647,44 +647,46 @@ patcompbranch(int *flagp) patparse[2] == Pound))) { /* Globbing flags. */ char *pp1 = patparse; - int oldglobflags = patglobflags; + int oldglobflags = patglobflags, ignore; long assert; patparse += (*patparse == '@') ? 3 : 2; - if (!patgetglobflags(&patparse, &assert)) + if (!patgetglobflags(&patparse, &assert, &ignore)) return 0; - if (assert) { - /* - * Start/end assertion looking like flags, but - * actually handled as a normal node - */ - latest = patnode(assert); - flags = 0; - } else { - if (pp1 == patstart) { - /* 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. + if (!ignore) { + if (assert) { + /* + * Start/end assertion looking like flags, but + * actually handled as a normal node */ - 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); + latest = patnode(assert); + flags = 0; } else { - /* No effect. */ - continue; + if (pp1 == patstart) { + /* 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) { @@ -720,74 +722,83 @@ patcompbranch(int *flagp) /**/ int -patgetglobflags(char **strp, long *assertp) +patgetglobflags(char **strp, long *assertp, int *ignore) { char *nptr, *ptr = *strp; zlong ret; *assertp = 0; + *ignore = 1; /* (#X): assumes we are still positioned on the first X */ for (; *ptr && *ptr != Outpar; ptr++) { - switch (*ptr) { - case 'a': - /* Approximate matching, max no. of errors follows */ - ret = zstrtol(++ptr, &nptr, 10); - /* - * We can't have more than 254, because we need 255 to - * mark 254 errors in wbranch and exclude sync strings - * (hypothetically --- hope no-one tries it). - */ - if (ret < 0 || ret > 254 || ptr == nptr) - return 0; - patglobflags = (patglobflags & ~0xff) | (ret & 0xff); - ptr = nptr-1; + if (*ptr == 'q') { + /* Glob qualifiers, ignored in pattern code */ + while (*ptr && *ptr != Outpar) + ptr++; break; + } else { + *ignore = 0; + switch (*ptr) { + case 'a': + /* Approximate matching, max no. of errors follows */ + ret = zstrtol(++ptr, &nptr, 10); + /* + * We can't have more than 254, because we need 255 to + * mark 254 errors in wbranch and exclude sync strings + * (hypothetically --- hope no-one tries it). + */ + if (ret < 0 || ret > 254 || ptr == nptr) + return 0; + patglobflags = (patglobflags & ~0xff) | (ret & 0xff); + ptr = nptr-1; + break; - case 'l': - /* Lowercase in pattern matches lower or upper in target */ - patglobflags = (patglobflags & ~GF_IGNCASE) | GF_LCMATCHUC; - break; + case 'l': + /* Lowercase in pattern matches lower or upper in target */ + patglobflags = (patglobflags & ~GF_IGNCASE) | GF_LCMATCHUC; + break; - case 'i': - /* Fully case insensitive */ - patglobflags = (patglobflags & ~GF_LCMATCHUC) | GF_IGNCASE; - break; + case 'i': + /* Fully case insensitive */ + patglobflags = (patglobflags & ~GF_LCMATCHUC) | GF_IGNCASE; + break; - case 'I': - /* Restore case sensitivity */ - patglobflags &= ~(GF_LCMATCHUC|GF_IGNCASE); - break; + case 'I': + /* Restore case sensitivity */ + patglobflags &= ~(GF_LCMATCHUC|GF_IGNCASE); + break; - case 'b': - /* Make backreferences */ - patglobflags |= GF_BACKREF; - break; + case 'b': + /* Make backreferences */ + patglobflags |= GF_BACKREF; + break; - case 'B': - /* Don't make backreferences */ - patglobflags &= ~GF_BACKREF; - break; + case 'B': + /* Don't make backreferences */ + patglobflags &= ~GF_BACKREF; + break; - case 'm': - /* Make references to complete match */ - patglobflags |= GF_MATCHREF; - break; + case 'm': + /* Make references to complete match */ + patglobflags |= GF_MATCHREF; + break; - case 'M': - /* Don't */ - patglobflags &= ~GF_MATCHREF; - break; + case 'M': + /* Don't */ + patglobflags &= ~GF_MATCHREF; + break; - case 's': - *assertp = P_ISSTART; - break; + case 's': + *assertp = P_ISSTART; + break; - case 'e': - *assertp = P_ISEND; - break; + case 'e': + *assertp = P_ISEND; + break; - default: - return 0; + default: + return 0; + } } } if (*ptr != Outpar) |