Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

PATCH: (#q)



This is the EXTENDED_GLOB qualifier syntax as proposed last week.  I had to
wait for sufficient time to get my mind round the best way of handling or's
in multiple sets of qualifiers.  It turns out Scottish country dancing is a
good way of clearing your head.

This means (where `...' means some suitable non-empty pattern):
- ...(#q...) works if and only if EXTENDED_GLOB is set, no dependence
  on BARE_GLOB_QUAL.
- ...(#q...)(#q...) is an `and' of both sets of quals (complete,
  implying distribution over `or's inside each set).
- ...(#q...)... doesn't work, not even for intervening directories,
  which is a possible upgrade --- I can't think of any other good reason for
  allowing qualifiers not at the end of the pattern, despite the fact
  they could be unambiguously recognised.
- with BARE_GLOB_QUAL also set, ...(#q...)(...) is two sets of qualifiers;
  Bart and I agreed this was the most logical way, though the manual
  deprecates use of this mixed syntax.
- patterns completely ignore (#q...) if EXTENDED_GLOB is set.  This means
  you can use glob patterns with qualifiers to match filenames as ordinary
  strings, too.  One slight oddity is that there is no error if there is
  a (#q...) not at the end of a glob pattern.
- You can potentially get confused with the fact that colon modifiers won't
  be applied in the pattern code, either --- there's no reason why they
  should, but it does mean you can end up with a different string from the
  one produced by globbing.  I don't think there's anything to be done
  about that apart from noting it, which I've done.

This could do with some testing.  I haven't written tests yet; in fact,
there don't seem to be any glob qualifier tests at the moment.

The completion code could usefully go over to using this syntax, for the
reasons outlined by Oliver.

A large fraction of the patch is re-indentation.

Index: Doc/Zsh/expn.yo
===================================================================
RCS file: /cvsroot/zsh/zsh/Doc/Zsh/expn.yo,v
retrieving revision 1.40
diff -u -r1.40 expn.yo
--- Doc/Zsh/expn.yo	2001/12/17 14:06:33	1.40
+++ Doc/Zsh/expn.yo	2002/01/22 12:01:08
@@ -1442,6 +1442,17 @@
 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 @@
 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 @@
 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)'.
Index: Src/glob.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/glob.c,v
retrieving revision 1.22
diff -u -r1.22 glob.c
--- Src/glob.c	2001/10/08 08:13:11	1.22
+++ Src/glob.c	2002/01/22 12:01:08
@@ -38,7 +38,7 @@
 
 /* flag for CSHNULLGLOB */
 
-typedef struct gmatch *Gmatch; 
+typedef struct gmatch *Gmatch;
 
 struct gmatch {
     char *name;
@@ -76,7 +76,7 @@
 
 /**/
 int badcshglob;
- 
+
 /**/
 int pathpos;		/* position in pathbuf (needed by pattern code) */
 
@@ -707,6 +707,7 @@
 parsepat(char *str)
 {
     long assert;
+    int ignore;
 
     patcompstart();
     /*
@@ -717,7 +718,7 @@
 	(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 @@
     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 @@
     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 @@
     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 @@
     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;
+	    case Inpar:
+		paren--;
 		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 */
+	    }
+	}
+	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;
 		}
-		if (errflag) {
+		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;
+		}
+		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 @@
 	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 @@
 	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 @@
 };
 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
Index: Src/pattern.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/pattern.c,v
retrieving revision 1.10
diff -u -r1.10 pattern.c
--- Src/pattern.c	2001/09/09 06:17:03	1.10
+++ Src/pattern.c	2002/01/22 12:01:08
@@ -631,7 +631,7 @@
 static long
 patcompbranch(int *flagp)
 {
-    long chain, latest, starter;
+    long chain, latest = 0, starter;
     int flags = 0;
 
     *flagp = P_PURESTR;
@@ -647,44 +647,46 @@
 	      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 @@
 
 /**/
 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;
-	    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':
-	    /* Restore case sensitivity */
-	    patglobflags &= ~(GF_LCMATCHUC|GF_IGNCASE);
-	    break;
-
-	case 'b':
-	    /* 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':
-	    /* Don't */
-	    patglobflags &= ~GF_MATCHREF;
-	    break;
-
-	case 's':
-	    *assertp = P_ISSTART;
-	    break;
-
-	case 'e':
-	    *assertp = P_ISEND;
-	    break;
+	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 'i':
+		/* Fully case insensitive */
+		patglobflags = (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':
+		/* Don't make backreferences */
+		patglobflags &= ~GF_BACKREF;
+		break;
+
+	    case 'm':
+		/* Make references to complete match */
+		patglobflags |= GF_MATCHREF;
+		break;
+
+	    case 'M':
+		/* Don't */
+		patglobflags &= ~GF_MATCHREF;
+		break;
+
+	    case 's':
+		*assertp = P_ISSTART;
+		break;
+
+	    case 'e':
+		*assertp = P_ISEND;
+		break;
 
-	default:
-	    return 0;
+	    default:
+		return 0;
+	    }
 	}
     }
     if (*ptr != Outpar)

-- 
Peter Stephenson <pws@xxxxxxx>                  Software Engineer
CSR Ltd., Science Park, Milton Road,
Cambridge, CB4 0WH, UK                          Tel: +44 (0)1223 392070


**********************************************************************
The information transmitted is intended only for the person or
entity to which it is addressed and may contain confidential 
and/or privileged material. 
Any review, retransmission, dissemination or other use of, or
taking of any action in reliance upon, this information by 
persons or entities other than the intended recipient is 
prohibited.  
If you received this in error, please contact the sender and 
delete the material from any computer.
**********************************************************************



Messages sorted by: Reverse Date, Date, Thread, Author