Subject: Re: make: allow quoted lhs in conditionals
To: None <tech-toolchain@netbsd.org>
From: Simon J. Gerraty <sjg@crufty.net>
List: tech-toolchain
Date: 04/08/2004 01:32:50
Ok, revised patch with more extensive unit test.
Cross building -current/sparc toolchain and pkgsrc/devel/bmake
worked fine.

The only reason for passing termc (0 or ')') is to mimmic the previous
behavior for

.if (${VAR})

which isn't legal - according to the grama in cond.c, not sure what it
would/should mean anyway.

If nothing else, this makes CondToken easier to read ;-)

--sjg

Index: cond.c
===================================================================
RCS file: /cvsroot/src/usr.bin/make/cond.c,v
retrieving revision 1.22
diff -u -p -r1.22 cond.c
--- cond.c	8 Apr 2004 07:24:26 -0000	1.22
+++ cond.c	8 Apr 2004 08:24:29 -0000
@@ -528,6 +528,116 @@ CondCvtArg(char *str, double *value)
 
 /*-
  *-----------------------------------------------------------------------
+ * CondGetString --
+ *	Get a string from a variable reference or an optionally quoted
+ *	string.  This is called for the lhs and rhs of string compares.
+ *
+ * Results:
+ *	Sets doFree if needed,
+ *	Sets quoted if string was quoted,
+ *	Returns NULL on error,
+ *	else returns string - absent any quotes.
+ *
+ * Side Effects:
+ *	Moves condExpr to end of this token.
+ *
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+CondGetString(Boolean doEval, int termc, Boolean *quoted, Boolean *doFree)
+{
+    Buffer buf;
+    char *cp;
+    char *str;
+    int	len;
+    int qt;
+    char *start;
+
+    buf = Buf_Init(0);
+    str = NULL;
+    *quoted = qt = *condExpr == '"' ? 1 : 0;
+    if (qt)
+	condExpr++;
+    for (start = condExpr; *condExpr && str == NULL; condExpr++) {
+	switch (*condExpr) {
+	case '\\':
+	    if (condExpr[1] != '\0') {
+		condExpr++;
+		Buf_AddByte(buf, (Byte)*condExpr);
+	    }
+	    break;
+	case '"':
+	    if (qt) {
+		condExpr++;		/* we don't want the quotes */
+		goto got_str;
+	    } else
+		Buf_AddByte(buf, (Byte)*condExpr); /* likely? */
+	    break;
+	case '!':
+	case '=':
+	case '>':
+	case '<':
+	case ' ':
+	case '\t':
+	    if (!qt)
+		goto got_str;
+	    else
+		Buf_AddByte(buf, (Byte)*condExpr);
+	    break;
+	case '$':
+	    str = Var_Parse(condExpr, VAR_CMD, doEval, &len, doFree);
+	    if (str == var_Error) {
+		/*
+		 * Even if !doEval, we still report syntax errors, which
+		 * is what getting var_Error back with !doEval means.
+		 */
+		str = NULL;
+		goto cleanup;
+	    }
+	    condExpr += len;
+	    /*
+	     * If the '$' was first char (no quotes), and we are
+	     * followed by space, the operator or end of expression,
+	     * we are done.
+	     */
+	    if ((condExpr == start + len) &&
+		(*condExpr == '\0' ||
+		 (termc != 0 && *condExpr == termc) ||
+		 isspace((unsigned char) *condExpr) ||
+		 strchr("!=><", *condExpr))) {
+		goto cleanup;
+	    }
+	    /*
+	     * Nope, we better copy str to buf
+	     */
+	    for (cp = str; *cp; cp++) {
+		Buf_AddByte(buf, (Byte)*cp);
+	    }
+	    if (*doFree)
+		free(str);
+	    *doFree = FALSE;
+	    str = NULL;			/* not finished yet */
+	    condExpr--;			/* don't skip over next char */
+	    break;
+	default:
+	    if (!qt && termc != 0 && *condExpr == termc)
+		goto got_str;
+	    Buf_AddByte(buf, (Byte)*condExpr);
+	    break;
+	}
+    }
+ got_str:
+    Buf_AddByte(buf, (Byte)'\0');
+    str = (char *)Buf_GetAll(buf, NULL);
+    *doFree = TRUE;
+ cleanup:
+    Buf_Destroy(buf, FALSE);
+    return str;
+}
+
+/*-
+ *-----------------------------------------------------------------------
  * CondToken --
  *	Return the next token from the input.
  *
@@ -580,52 +690,27 @@ CondToken(Boolean doEval)
 	    case '\0':
 		t = EndOfFile;
 		break;
+	    case '"':
 	    case '$': {
 		char	*lhs;
 		char	*rhs;
 		char	*op;
-		int	varSpecLen;
-		Boolean	doFree;
-
+		Boolean	lhsFree;
+		Boolean	rhsFree;
+		Boolean lhsQuoted;
+		Boolean rhsQuoted;
+
+		lhsFree = rhsFree = FALSE;
+		lhsQuoted = rhsQuoted = FALSE;
+		
 		/*
 		 * Parse the variable spec and skip over it, saving its
 		 * value in lhs.
 		 */
 		t = Err;
-		lhs = Var_Parse(condExpr, VAR_CMD, doEval,&varSpecLen,&doFree);
-		if (lhs == var_Error) {
-		    /*
-		     * Even if !doEval, we still report syntax errors, which
-		     * is what getting var_Error back with !doEval means.
-		     */
-		    return(Err);
-		}
-		condExpr += varSpecLen;
-
-		if (!isspace((unsigned char) *condExpr) &&
-		    strchr("!=><", *condExpr) == NULL) {
-		    Buffer buf;
-		    char *cp;
-
-		    buf = Buf_Init(0);
-
-		    for (cp = lhs; *cp; cp++)
-			Buf_AddByte(buf, (Byte)*cp);
-
-		    if (doFree)
-			free(lhs);
-
-		    for (;*condExpr && !isspace((unsigned char) *condExpr);
-			 condExpr++)
-			Buf_AddByte(buf, (Byte)*condExpr);
-
-		    Buf_AddByte(buf, (Byte)'\0');
-		    lhs = (char *)Buf_GetAll(buf, &varSpecLen);
-		    Buf_Destroy(buf, FALSE);
-
-		    doFree = TRUE;
-		}
-
+		lhs = CondGetString(doEval, 0, &lhsQuoted, &lhsFree);
+		if (!lhs)
+		    return Err;
 		/*
 		 * Skip whitespace to get to the operator
 		 */
@@ -663,18 +748,11 @@ CondToken(Boolean doEval)
 				"Missing right-hand-side of operator");
 		    goto error;
 		}
-		rhs = condExpr;
+		rhs = CondGetString(doEval, ')', &rhsQuoted, &rhsFree);
+		if (!rhs)
+		    return Err;
 do_compare:
-		if (*rhs == '"') {
-		    /*
-		     * Doing a string comparison. Only allow == and != for
-		     * operators.
-		     */
-		    char    *string;
-		    char    *cp, *cp2;
-		    int	    qt;
-		    Buffer  buf;
-
+		if (rhsQuoted || lhsQuoted) {
 do_string_compare:
 		    if (((*op != '!') && (*op != '=')) || (op[1] != '=')) {
 			Parse_Error(PARSE_WARNING,
@@ -682,63 +760,18 @@ do_string_compare:
 			goto error;
 		    }
 
-		    buf = Buf_Init(0);
-		    qt = *rhs == '"' ? 1 : 0;
-
-		    for (cp = &rhs[qt];
-			 ((qt && (*cp != '"')) ||
-			  (!qt && strchr(" \t)", *cp) == NULL)) &&
-			 (*cp != '\0'); cp++) {
-			if ((*cp == '\\') && (cp[1] != '\0')) {
-			    /*
-			     * Backslash escapes things -- skip over next
-			     * character, if it exists.
-			     */
-			    cp++;
-			    Buf_AddByte(buf, (Byte)*cp);
-			} else if (*cp == '$') {
-			    int	len;
-			    Boolean freeIt;
-
-			    cp2 = Var_Parse(cp, VAR_CMD, doEval,&len, &freeIt);
-			    if (cp2 != var_Error) {
-				Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2);
-				if (freeIt) {
-				    free(cp2);
-				}
-				cp += len - 1;
-			    } else {
-				Buf_AddByte(buf, (Byte)*cp);
-			    }
-			} else {
-			    Buf_AddByte(buf, (Byte)*cp);
-			}
-		    }
-
-		    Buf_AddByte(buf, (Byte)0);
-
-		    string = (char *)Buf_GetAll(buf, (int *)0);
-		    Buf_Destroy(buf, FALSE);
-
 		    if (DEBUG(COND)) {
 			printf("lhs = \"%s\", rhs = \"%s\", op = %.2s\n",
-			       lhs, string, op);
+			       lhs, rhs, op);
 		    }
 		    /*
 		     * Null-terminate rhs and perform the comparison.
 		     * t is set to the result.
 		     */
 		    if (*op == '=') {
-			t = strcmp(lhs, string) ? False : True;
+			t = strcmp(lhs, rhs) ? False : True;
 		    } else {
-			t = strcmp(lhs, string) ? True : False;
-		    }
-		    free(string);
-		    if (rhs == condExpr) {
-		    	if (!qt && *cp == ')')
-			    condExpr = cp;
-			else
-			    condExpr = cp + 1;
+			t = strcmp(lhs, rhs) ? True : False;
 		    }
 		} else {
 		    /*
@@ -746,44 +779,13 @@ do_string_compare:
 		     * lhs and the rhs to a double and compare the two.
 		     */
 		    double  	left, right;
-		    char    	*string;
-
+		    char	*cp;
+		
 		    if (CondCvtArg(lhs, &left))
 			goto do_string_compare;
-		    if (*rhs == '$') {
-			int 	len;
-			Boolean	freeIt;
-
-			string = Var_Parse(rhs, VAR_CMD, doEval,&len,&freeIt);
-			if (string == var_Error) {
-			    right = 0.0;
-			} else {
-			    if (CondCvtArg(string, &right)) {
-				if (freeIt)
-				    free(string);
-				goto do_string_compare;
-			    }
-			    if (freeIt)
-				free(string);
-			    if (rhs == condExpr)
-				condExpr += len;
-			}
-		    } else {
-			char *cp;
-
-			if ((cp = CondCvtArg(rhs, &right)) &&
+		    if ((cp = CondCvtArg(rhs, &right)) &&
 			    cp == rhs)
-			    goto do_string_compare;
-			if (rhs == condExpr) {
-			    /*
-			     * Skip over the right-hand side
-			     */
-			    if (cp)
-				condExpr = cp;
-			    else
-				condExpr = strchr(rhs, '\0');	
-			}
-		    }
+			goto do_string_compare;
 
 		    if (DEBUG(COND)) {
 			printf("left = %f, right = %f, op = %.2s\n", left,
@@ -823,8 +825,10 @@ do_string_compare:
 		    }
 		}
 error:
-		if (doFree)
+		if (lhsFree)
 		    free(lhs);
+		if (rhsFree)
+		    free(rhs);
 		break;
 	    }
 	    default: {
Index: unit-tests/cond1
===================================================================
RCS file: /cvsroot/src/usr.bin/make/unit-tests/cond1,v
retrieving revision 1.2
diff -u -p -r1.2 cond1
--- unit-tests/cond1	8 Apr 2004 07:24:26 -0000	1.2
+++ unit-tests/cond1	8 Apr 2004 08:24:29 -0000
@@ -1,10 +1,24 @@
 # $Id: cond1,v 1.2 2004/04/08 07:24:26 sjg Exp $
 
+# hard code these!
+TEST_UNAME_S= NetBSD
+TEST_UNAME_M= sparc
+TEST_MACHINE= i386
 
+.if (${TEST_UNAME_M} != ${TEST_MACHINE})
+Ok=(var != var),
+.endif
+.if ${TEST_UNAME_M} != ${TEST_MACHINE}
+Ok+= var != var,
+.endif
+.if !((${TEST_UNAME_M} != ${TEST_MACHINE}) && defined(X))
+Ok+= !((var != var) && defined(name)),
+.endif
 # from bsd.obj.mk
 MKOBJ?=no
 .if ${MKOBJ} == "no"
 o= no
+Ok+= var == "quoted",
 .else
 .if defined(notMAKEOBJDIRPREFIX) || defined(norMAKEOBJDIR)
 .if defined(notMAKEOBJDIRPREFIX)
@@ -69,6 +83,14 @@ B=this should be an error
 B=unknown
 .endif
 
+.if "quoted" == quoted
+C=clever
+.else
+C=dim
+.endif
+
 all:
 	@echo "$n is $X prime"
-	@echo "A='$A' B='$B' o='$o,${o2}'"
+	@echo "A='$A' B='$B' C='$C' o='$o,${o2}'"
+	@echo "Passed:${.newline} ${Ok:S/,/${.newline}/}"
+	@echo "${NUMBERS:@n@$n is ${("${PRIMES:M$n}" == ""):?not:} prime${.newline}@}"
Index: unit-tests/test.exp
===================================================================
RCS file: /cvsroot/src/usr.bin/make/unit-tests/test.exp,v
retrieving revision 1.10
diff -u -p -r1.10 test.exp
--- unit-tests/test.exp	8 Apr 2004 07:24:26 -0000	1.10
+++ unit-tests/test.exp	8 Apr 2004 08:24:29 -0000
@@ -1,7 +1,19 @@
-make: "cond1" line 55: warning: extra else
-make: "cond1" line 65: warning: extra else
+make: "cond1" line 69: warning: extra else
+make: "cond1" line 79: warning: extra else
 2 is  prime
-A='other' B='unknown' o='no,no'
+A='other' B='unknown' C='clever' o='no,no'
+Passed:
+ (var != var)
+ var != var
+ !((var != var) && defined(name))
+ var == quoted
+
+1 is not prime
+2 is  prime
+3 is  prime
+4 is not prime
+5 is  prime
+
 LIB=a X_LIBS:M${LIB${LIB:tu}} is "/tmp/liba.a"
 LIB=a X_LIBS:M*/lib${LIB}.a is "/tmp/liba.a"
 LIB=a X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBA.A"