Subject: bin/7015: test(1) operator precedence inconsistent with POSIX
To: None <gnats-bugs@gnats.netbsd.org>
From: Andrew_L. Moore <alm@SlewSys.Org>
List: netbsd-bugs
Date: 02/18/1999 16:41:44
>Number:         7015
>Category:       bin
>Synopsis:       test(1) operator precedence inconsistent with POSIX
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    bin-bug-people (Utility Bug People)
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Thu Feb 18 17:20:01 1999
>Last-Modified:
>Originator:     Andrew_L. Moore
>Organization:
SlewSys Research
>Release:        (source from NetBSD 1.3-current)
>Environment:

	

>Description:

	
  To clarify the behavior of test(1) in a manner consistent with POSIX, the
  following rules are proposed:
  ===============================================
  1) A missing expression evaluates to false (a la `test' with no arguments).
  
  2) Precedence of operators in descending order:
  
        ( )
        binary (except -a and -o)
        unary  (except !)
        string-length
        !
        -a
        -o
  -----------------------------------------------
  * The precedence of binary over unary operators means that expressions
    such as:
  
        test \( -f = foo \)
  
    are interpreted as binary comparisons, not syntax errors, as in the current
    implementation.  This is consistent with POSIX: `test -f = foo' is
    interpreted as a binary comparison.  For binary file operators, this rule
    is important, since prefixing a filename argument -- e.g., with `./' --
    might not be feasible.
  
  * The precedence of parentheses over binary operators means that expressions
    such as:
  
        test \( = \)
  
    are interpreted as string-length tests, not binary comparisons as in the
    current implementation.  This is consistent with POSIX to the extent that
    the behavior of parentheses is "implementation-dependent".  It also
    allows parentheses to be used for disambiguating certain expressions.
    For instance, the expression:
  
        test -f = -o -o       
  
    is interpreted differently under the current and proposed rules.  To force
    the current interpretation under the new rules, it could be written:
  
       test \( -f = \) -o -o

>How-To-Repeat:

    (See above)

>Fix:

---- Cut Here ----
--- test.c	1999/02/14 09:16:21	1.1
+++ test.c	1999/02/18 22:37:03
@@ -150,6 +150,7 @@
 static int binop __P((void));
 static int filstat __P((char *, enum token));
 static enum token t_lex __P((char *));
+static int isoperand __P((void));
 static int getn __P((const char *));
 static int newerf __P((const char *, const char *));
 static int olderf __P((const char *, const char *));
@@ -170,42 +171,11 @@
 		argv[argc] = NULL;
 	}
 
-	/* Implement special cases from POSIX.2, section 4.62.4 */
-	switch (argc) {
-	case 1:
-		return 1;
-	case 2:
-		return (*argv[1] == '\0');
-	case 3:
-		if (argv[1][0] == '!' && argv[1][1] == '\0') {
-			return !(*argv[2] == '\0');
-		}
-		break;
-	case 4:
-		if (argv[1][0] != '!' || argv[1][1] != '\0') {
-			if (t_lex(argv[2]), 
-			    t_wp_op && t_wp_op->op_type == BINOP) {
-				t_wp = &argv[1];
-				return (binop() == 0);
-			}
-		}
-		break;
-	case 5:
-		if (argv[1][0] == '!' && argv[1][1] == '\0') {
-			if (t_lex(argv[3]), 
-			    t_wp_op && t_wp_op->op_type == BINOP) {
-				t_wp = &argv[2];
-				return !(binop() == 0);
-			}
-		}
-		break;
-	}
-
 	t_wp = &argv[1];
 	res = !oexpr(t_lex(*t_wp));
 
 	if (*t_wp != NULL && *++t_wp != NULL)
-		syntax(*t_wp, "unknown operand");
+		syntax(*t_wp, "unexpected operator");
 
 	return res;
 }
@@ -260,12 +230,15 @@
 primary(n)
 	enum token n;
 {
+	enum token nn;
 	int res;
 
 	if (n == EOI)
-		syntax(NULL, "argument expected");
+		return 0;		/* missing expression */
 	if (n == LPAREN) {
-		res = oexpr(t_lex(*++t_wp));
+		if ((nn = t_lex(*++t_wp)) == RPAREN)
+			return 0;	/* missing expression */
+		res = oexpr(nn);
 		if (t_lex(*++t_wp) != RPAREN)
 			syntax(NULL, "closing paren expected");
 		return res;
@@ -401,6 +374,9 @@
 	}
 	while (op->op_text) {
 		if (strcmp(s, op->op_text) == 0) {
+			if (op->op_type == UNOP && isoperand() ||
+			    op->op_num == LPAREN && *(t_wp+1) == 0)
+				break;
 			t_wp_op = op;
 			return op->op_num;
 		}
@@ -408,6 +384,26 @@
 	}
 	t_wp_op = (struct t_op *)0;
 	return OPERAND;
+}
+
+static int
+isoperand()
+{
+	struct t_op const *op = ops;
+	char *s;
+	char *t;
+
+	if ((s  = *(t_wp+1)) == 0)
+		return 1;
+	if ((t = *(t_wp+2)) == 0)
+		return 0;
+	while (op->op_text) {
+		if (strcmp(s, op->op_text) == 0)
+	    		return op->op_type == BINOP &&
+	    		    (t[0] != ')' || t[1] != '\0'); 
+		op++;
+	}
+	return 0;
 }
 
 /* atoi with error detection */


>Audit-Trail:
>Unformatted: