? make.core
Index: var.c
===================================================================
RCS file: /cvsroot/src/usr.bin/make/var.c,v
retrieving revision 1.107
diff -u -p -r1.107 var.c
--- var.c	22 Apr 2006 19:40:40 -0000	1.107
+++ var.c	11 May 2006 00:15:40 -0000
@@ -1882,427 +1882,159 @@ VarChangeCase(char *str, int upper)
    return str;
 }
 
-/*-
- *-----------------------------------------------------------------------
- * Var_Parse --
- *	Given the start of a variable invocation, extract the variable
- *	name and find its value, then modify it according to the
- *	specification.
- *
- * Input:
- *	str		The string to parse
- *	ctxt		The context for the variable
- *	err		TRUE if undefined variables are an error
- *	lengthPtr	OUT: The length of the specification
- *	freePtr		OUT: TRUE if caller should free result
- *
- * Results:
- *	The (possibly-modified) value of the variable or var_Error if the
- *	specification is invalid. The length of the specification is
- *	placed in *lengthPtr (for invalid specifications, this is just
- *	2...?).
- *	A Boolean in *freePtr telling whether the returned string should
- *	be freed by the caller.
+/*
+ * Now we need to apply any modifiers the user wants applied.
+ * These are:
+ *  	  :M<pattern>	words which match the given <pattern>.
+ *  	  	    	<pattern> is of the standard file
+ *  	  	    	wildcarding form.
+ *  	  :N<pattern>	words which do not match the given <pattern>.
+ *  	  :S<d><pat1><d><pat2><d>[1gW]
+ *  	  	    	Substitute <pat2> for <pat1> in the value
+ *  	  :C<d><pat1><d><pat2><d>[1gW]
+ *  	  	    	Substitute <pat2> for regex <pat1> in the value
+ *  	  :H	    	Substitute the head of each word
+ *  	  :T	    	Substitute the tail of each word
+ *  	  :E	    	Substitute the extension (minus '.') of
+ *  	  	    	each word
+ *  	  :R	    	Substitute the root of each word
+ *  	  	    	(pathname minus the suffix).
+ *		  :O		("Order") Alphabeticaly sort words in variable.
+ *		  :Ox		("intermiX") Randomize words in variable.
+ *		  :u		("uniq") Remove adjacent duplicate words.
+ *		  :tu		Converts the variable contents to uppercase.
+ *		  :tl		Converts the variable contents to lowercase.
+ *		  :ts[c]	Sets varSpace - the char used to
+ *				separate words to 'c'. If 'c' is
+ *				omitted then no separation is used.
+ *		  :tW		Treat the variable contents as a single
+ *				word, even if it contains spaces.
+ *				(Mnemonic: one big 'W'ord.)
+ *		  :tw		Treat the variable contents as multiple
+ *				space-separated words.
+ *				(Mnemonic: many small 'w'ords.)
+ *		  :[index]	Select a single word from the value.
+ *		  :[start..end]	Select multiple words from the value.
+ *		  :[*] or :[0]	Select the entire value, as a single
+ *				word.  Equivalent to :tW.
+ *		  :[@]		Select the entire value, as multiple
+ *				words.	Undoes the effect of :[*].
+ *				Equivalent to :tw.
+ *		  :[#]		Returns the number of words in the value.
  *
- * Side Effects:
- *	None.
- *
- *-----------------------------------------------------------------------
+ *		  :?<true-value>:<false-value>
+ *				If the variable evaluates to true, return
+ *				true value, else return the second value.
+ *	    	  :lhs=rhs  	Like :S, but the rhs goes to the end of
+ *	    	    	    	the invocation.
+ *		  :sh		Treat the current value as a command
+ *				to be run, new value is its output.
+ * The following added so we can handle ODE makefiles.
+ *		  :@<tmpvar>@<newval>@
+ *				Assign a temporary local variable <tmpvar>
+ *				to the current value of each word in turn
+ *				and replace each word with the result of
+ *				evaluating <newval>
+ *		  :D<newval>	Use <newval> as value if variable defined
+ *		  :U<newval>	Use <newval> as value if variable undefined
+ *		  :L		Use the name of the variable as the value.
+ *		  :P		Use the path of the node that has the same
+ *				name as the variable as the value.  This
+ *				basically includes an implied :L so that
+ *				the common method of refering to the path
+ *				of your dependent 'x' in a rule is to use
+ *				the form '${x:P}'.
+ *		  :!<cmd>!	Run cmd much the same as :sh run's the
+ *				current value of the variable.
+ * The ::= modifiers, actually assign a value to the variable.
+ * Their main purpose is in supporting modifiers of .for loop
+ * iterators and other obscure uses.  They always expand to
+ * nothing.  In a target rule that would otherwise expand to an
+ * empty line they can be preceded with @: to keep make happy.
+ * Eg.
+ * 
+ * foo:	.USE
+ * .for i in ${.TARGET} ${.TARGET:R}.gz
+ * 		@: ${t::=$i}
+ *		@echo blah ${t:T}
+ * .endfor
+ * 
+ *		  ::=<str>	Assigns <str> as the new value of variable.
+ *		  ::?=<str>	Assigns <str> as value of variable if
+ *				it was not already set.
+ *		  ::+=<str>	Appends <str> to variable.
+ *		  ::!=<cmd>	Assigns output of <cmd> as the new value of
+ *				variable.
  */
-/* coverity[+alloc : arg-*4] */
-char *
-Var_Parse(const char *str, GNode *ctxt, Boolean err, int *lengthPtr,
-	  void **freePtr)
+
+static char *
+ApplyModifiers(char *nstr, const char *tstr,
+	       int startc, int endc,
+	       Var *v, GNode *ctxt, Boolean err,
+	       int *lengthPtr, void **freePtr)
 {
-    const char	   *tstr;    	/* Pointer into str */
-    char	   *tstr2;	/* Secondary tstr if we need
-				 * to expand modifiers  */
-    Var	    	   *v;	    	/* Variable in invocation */
+    const char 	   *start;
     const char     *cp;    	/* Secondary pointer into str (place marker
 				 * for tstr) */
-    Boolean 	    haveModifier;/* TRUE if have modifiers for the variable */
-    char	    endc;    	/* Ending character when variable in parens
-				 * or braces */
-    char	    startc=0;	/* Starting character when variable in parens
-				 * or braces */
+    char	*newStr;	/* New value to return */
+    char	termc;		/* Character which terminated scan */
     int             cnt;	/* Used to count brace pairs when variable in
 				 * in parens or braces */
-    int		    vlen;	/* Length of variable name */
-    const char 	   *start;
-    char	    delim;
-    char	   *nstr;
-    Boolean 	    dynamic;	/* TRUE if the variable is local and we're
-				 * expanding it in a non-local context. This
-				 * is done to support dynamic sources. The
-				 * result is just the invocation, unaltered */
+    char	delim;
     Var_Parse_State parsestate; /* Flags passed to helper functions */
 
-    *freePtr = NULL;
-    dynamic = FALSE;
-    start = str;
+    delim = '\0';
     parsestate.oneBigWord = FALSE;
     parsestate.varSpace = ' ';	/* word separator */
-    tstr2 = NULL;
-
-    if (str[1] != PROPEN && str[1] != BROPEN) {
-	/*
-	 * If it's not bounded by braces of some sort, life is much simpler.
-	 * We just need to check for the first character and return the
-	 * value if it exists.
-	 */
-	char	  name[2];
-
-	name[0] = str[1];
-	name[1] = '\0';
 
-	v = VarFind(name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD);
-	if (v == (Var *)NIL) {
-	    *lengthPtr = 2;
-
-	    if ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)) {
-		/*
-		 * If substituting a local variable in a non-local context,
-		 * assume it's for dynamic source stuff. We have to handle
-		 * this specially and return the longhand for the variable
-		 * with the dollar sign escaped so it makes it back to the
-		 * caller. Only four of the local variables are treated
-		 * specially as they are the only four that will be set
-		 * when dynamic sources are expanded.
-		 */
-		switch (str[1]) {
-		    case '@':
-			return UNCONST("$(.TARGET)");
-		    case '%':
-			return UNCONST("$(.ARCHIVE)");
-		    case '*':
-			return UNCONST("$(.PREFIX)");
-		    case '!':
-			return UNCONST("$(.MEMBER)");
-		}
-	    }
-	    /*
-	     * Error
+    start = cp = tstr;
+    
+    while (*tstr && *tstr != endc) {
+
+	if (*tstr == '$') {
+	    /* 
+	     * We have some complex modifiers in a variable.
 	     */
-	    return (err ? var_Error : varNoError);
-	} else {
-	    haveModifier = FALSE;
-	    tstr = &str[1];
-	    endc = str[1];
-	}
-    } else if (str[1] == '\0') {
-	*lengthPtr = 1;
-	return (err ? var_Error : varNoError);
-    } else { 
-	Buffer buf;	/* Holds the variable name */
+	    void *freeIt;
+	    char *rval;
+	    int rlen;
 
-	startc = str[1];
-	endc = startc == PROPEN ? PRCLOSE : BRCLOSE;
-	buf = Buf_Init(MAKE_BSIZE);
+	    rval = Var_Parse(tstr, ctxt, err, &rlen, &freeIt);
 
-	/*
-	 * Skip to the end character or a colon, whichever comes first.
-	 */
-	for (tstr = str + 2;
-	     *tstr != '\0' && *tstr != endc && *tstr != ':';
-	     tstr++)
-	{
-	    /*
-	     * A variable inside a variable, expand
-	     */
-	    if (*tstr == '$') {
-		int rlen;
-		void *freeIt;
-		char *rval = Var_Parse(tstr, ctxt, err, &rlen, &freeIt);
-		if (rval != NULL) {
-		    Buf_AddBytes(buf, strlen(rval), (Byte *)rval);
-		}
-		if (freeIt)
-		    free(freeIt);
-		tstr += rlen - 1;
+	    if (DEBUG(VAR)) {
+		printf("Got '%s' from '%.*s'%.*s\n",
+		       rval, rlen, tstr, rlen, tstr + rlen);
 	    }
-	    else
-		Buf_AddByte(buf, (Byte)*tstr);
-	}
-	if (*tstr == ':') {
-	    haveModifier = TRUE;
-	} else if (*tstr != '\0') {
-	    haveModifier = FALSE;
-	} else {
-	    /*
-	     * If we never did find the end character, return NULL
-	     * right now, setting the length to be the distance to
-	     * the end of the string, since that's what make does.
-	     */
-	    *lengthPtr = tstr - str;
-	    return (var_Error);
-	}
-	*WR(tstr) = '\0';
-	Buf_AddByte(buf, (Byte)'\0');
-	str = Buf_GetAll(buf, NULL);
-	vlen = strlen(str);
 
-	v = VarFind(str, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD);
-	if ((v == (Var *)NIL) && (ctxt != VAR_CMD) && (ctxt != VAR_GLOBAL) &&
-	    (vlen == 2) && (str[1] == 'F' || str[1] == 'D'))
-	{
-	    /*
-	     * Check for bogus D and F forms of local variables since we're
-	     * in a local context and the name is the right length.
-	     */
-	    switch(*str) {
-		case '@':
-		case '%':
-		case '*':
-		case '!':
-		case '>':
-		case '<':
-		{
-		    char    vname[2];
-		    char    *val;
+	    tstr += rlen;
 
-		    /*
-		     * Well, it's local -- go look for it.
-		     */
-		    vname[0] = *str;
-		    vname[1] = '\0';
-		    v = VarFind(vname, ctxt, 0);
-
-		    if (v != (Var *)NIL) {
-			/*
-			 * No need for nested expansion or anything, as we're
-			 * the only one who sets these things and we sure don't
-			 * but nested invocations in them...
-			 */
-			val = (char *)Buf_GetAll(v->val, NULL);
+	    if (rval != NULL && *rval) {
+		int used;
 
-			if (str[1] == 'D') {
-			    val = VarModify(ctxt, &parsestate, val, VarHead,
-					    (ClientData)0);
-			} else {
-			    val = VarModify(ctxt, &parsestate, val, VarTail,
-					    (ClientData)0);
-			}
-			/*
-			 * Resulting string is dynamically allocated, so
-			 * tell caller to free it.
-			 */
-			*freePtr = val;
-			*lengthPtr = tstr-start+1;
-			*WR(tstr) = endc;
-			Buf_Destroy(buf, TRUE);
-			VarFreeEnv(v, TRUE);
-			return(val);
-		    }
-		    break;
+		nstr = ApplyModifiers(nstr, rval,
+				      0, 0,
+				      v, ctxt, err, &used, freePtr);
+		if (nstr == var_Error
+		    || (nstr == varNoError && err == 0)
+		    || strlen(rval) != used) {
+		    if (freeIt)
+			free(freeIt);
+		    goto out;		/* error already reported */
 		}
 	    }
+	    if (freeIt)
+		free(freeIt);
+	    if (*tstr == ':')
+		tstr++;
+	    continue;
 	}
-
-	if (v == (Var *)NIL) {
-	    if (((vlen == 1) ||
-		 (((vlen == 2) && (str[1] == 'F' ||
-					 str[1] == 'D')))) &&
-		((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)))
-	    {
-		/*
-		 * If substituting a local variable in a non-local context,
-		 * assume it's for dynamic source stuff. We have to handle
-		 * this specially and return the longhand for the variable
-		 * with the dollar sign escaped so it makes it back to the
-		 * caller. Only four of the local variables are treated
-		 * specially as they are the only four that will be set
-		 * when dynamic sources are expanded.
-		 */
-		switch (*str) {
-		    case '@':
-		    case '%':
-		    case '*':
-		    case '!':
-			dynamic = TRUE;
-			break;
-		}
-	    } else if ((vlen > 2) && (*str == '.') &&
-		       isupper((unsigned char) str[1]) &&
-		       ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)))
+	if (DEBUG(VAR)) {
+	    printf("Applying :%c to \"%s\"\n", *tstr, nstr);
+	}
+	newStr = var_Error;
+	switch (*tstr) {
+	case ':':
 	    {
-		int	len;
-
-		len = vlen - 1;
-		if ((strncmp(str, ".TARGET", len) == 0) ||
-		    (strncmp(str, ".ARCHIVE", len) == 0) ||
-		    (strncmp(str, ".PREFIX", len) == 0) ||
-		    (strncmp(str, ".MEMBER", len) == 0))
-		{
-		    dynamic = TRUE;
-		}
-	    }
-
-	    if (!haveModifier) {
-		/*
-		 * No modifiers -- have specification length so we can return
-		 * now.
-		 */
-		*lengthPtr = tstr - start + 1;
-		*WR(tstr) = endc;
-		if (dynamic) {
-		    char *pstr = emalloc(*lengthPtr + 1);
-		    strncpy(pstr, start, *lengthPtr);
-		    pstr[*lengthPtr] = '\0';
-		    *freePtr = pstr;
-		    Buf_Destroy(buf, TRUE);
-		    return(pstr);
-		} else {
-		    Buf_Destroy(buf, TRUE);
-		    return (err ? var_Error : varNoError);
-		}
-	    } else {
-		/*
-		 * Still need to get to the end of the variable specification,
-		 * so kludge up a Var structure for the modifications
-		 */
-		v = emalloc(sizeof(Var));
-		v->name = UNCONST(str);
-		v->val = Buf_Init(1);
-		v->flags = VAR_JUNK;
-		Buf_Destroy(buf, FALSE);
-	    }
-	} else
-	    Buf_Destroy(buf, TRUE);
-    }
-
-
-    if (v->flags & VAR_IN_USE) {
-	Fatal("Variable %s is recursive.", v->name);
-	/*NOTREACHED*/
-    } else {
-	v->flags |= VAR_IN_USE;
-    }
-    /*
-     * Before doing any modification, we have to make sure the value
-     * has been fully expanded. If it looks like recursion might be
-     * necessary (there's a dollar sign somewhere in the variable's value)
-     * we just call Var_Subst to do any other substitutions that are
-     * necessary. Note that the value returned by Var_Subst will have
-     * been dynamically-allocated, so it will need freeing when we
-     * return.
-     */
-    nstr = (char *)Buf_GetAll(v->val, NULL);
-    if (strchr(nstr, '$') != NULL) {
-	nstr = Var_Subst(NULL, nstr, ctxt, err);
-	*freePtr = nstr;
-    }
-
-    v->flags &= ~VAR_IN_USE;
-
-    /*
-     * Now we need to apply any modifiers the user wants applied.
-     * These are:
-     *  	  :M<pattern>	words which match the given <pattern>.
-     *  	  	    	<pattern> is of the standard file
-     *  	  	    	wildcarding form.
-     *  	  :N<pattern>	words which do not match the given <pattern>.
-     *  	  :S<d><pat1><d><pat2><d>[1gW]
-     *  	  	    	Substitute <pat2> for <pat1> in the value
-     *  	  :C<d><pat1><d><pat2><d>[1gW]
-     *  	  	    	Substitute <pat2> for regex <pat1> in the value
-     *  	  :H	    	Substitute the head of each word
-     *  	  :T	    	Substitute the tail of each word
-     *  	  :E	    	Substitute the extension (minus '.') of
-     *  	  	    	each word
-     *  	  :R	    	Substitute the root of each word
-     *  	  	    	(pathname minus the suffix).
-     *		  :O		("Order") Alphabeticaly sort words in variable.
-     *		  :Ox		("intermiX") Randomize words in variable.
-     *		  :u		("uniq") Remove adjacent duplicate words.
-     *		  :tu		Converts the variable contents to uppercase.
-     *		  :tl		Converts the variable contents to lowercase.
-     *		  :ts[c]	Sets varSpace - the char used to
-     *				separate words to 'c'. If 'c' is
-     *				omitted then no separation is used.
-     *		  :tW		Treat the variable contents as a single
-     *				word, even if it contains spaces.
-     *				(Mnemonic: one big 'W'ord.)
-     *		  :tw		Treat the variable contents as multiple
-     *				space-separated words.
-     *				(Mnemonic: many small 'w'ords.)
-     *		  :[index]	Select a single word from the value.
-     *		  :[start..end]	Select multiple words from the value.
-     *		  :[*] or :[0]	Select the entire value, as a single
-     *				word.  Equivalent to :tW.
-     *		  :[@]		Select the entire value, as multiple
-     *				words.	Undoes the effect of :[*].
-     *				Equivalent to :tw.
-     *		  :[#]		Returns the number of words in the value.
-     *
-     *		  :?<true-value>:<false-value>
-     *				If the variable evaluates to true, return
-     *				true value, else return the second value.
-     *	    	  :lhs=rhs  	Like :S, but the rhs goes to the end of
-     *	    	    	    	the invocation.
-     *		  :sh		Treat the current value as a command
-     *				to be run, new value is its output.
-     * The following added so we can handle ODE makefiles.
-     *		  :@<tmpvar>@<newval>@
-     *				Assign a temporary local variable <tmpvar>
-     *				to the current value of each word in turn
-     *				and replace each word with the result of
-     *				evaluating <newval>
-     *		  :D<newval>	Use <newval> as value if variable defined
-     *		  :U<newval>	Use <newval> as value if variable undefined
-     *		  :L		Use the name of the variable as the value.
-     *		  :P		Use the path of the node that has the same
-     *				name as the variable as the value.  This
-     *				basically includes an implied :L so that
-     *				the common method of refering to the path
-     *				of your dependent 'x' in a rule is to use
-     *				the form '${x:P}'.
-     *		  :!<cmd>!	Run cmd much the same as :sh run's the
-     *				current value of the variable.
-     * The ::= modifiers, actually assign a value to the variable.
-     * Their main purpose is in supporting modifiers of .for loop
-     * iterators and other obscure uses.  They always expand to
-     * nothing.  In a target rule that would otherwise expand to an
-     * empty line they can be preceded with @: to keep make happy.
-     * Eg.
-     * 
-     * foo:	.USE
-     * .for i in ${.TARGET} ${.TARGET:R}.gz
-     * 		@: ${t::=$i}
-     *		@echo blah ${t:T}
-     * .endfor
-     * 
-     *		  ::=<str>	Assigns <str> as the new value of variable.
-     *		  ::?=<str>	Assigns <str> as value of variable if
-     *				it was not already set.
-     *		  ::+=<str>	Appends <str> to variable.
-     *		  ::!=<cmd>	Assigns output of <cmd> as the new value of
-     *				variable.
-     */
-    if ((nstr != NULL) && haveModifier) {
-	/*
-	 * Skip initial colon while putting it back.
-	 */
-	*WR(tstr) = ':';
-	tstr++;
-	delim = '\0';
-
-	while (*tstr && *tstr != endc) {
-	    char	*newStr;    /* New value to return */
-	    char	termc;	    /* Character which terminated scan */
-
-	    if (*tstr == '$') {
-		    newStr = Var_Subst(NULL, tstr, ctxt, err);
-		    if (tstr2)
-			    free(tstr2);
-		    tstr = tstr2 = newStr;
-	    }
-	    if (DEBUG(VAR)) {
-		printf("Applying :%c to \"%s\"\n", *tstr, nstr);
-	    }
-	    newStr = var_Error;
-	    switch (*tstr) {
-	        case ':':
-		{
 		if (tstr[1] == '=' ||
 		    (tstr[2] == '=' &&
 		     (tstr[1] == '!' || tstr[1] == '+' || tstr[1] == '?'))) {
@@ -2344,9 +2076,9 @@ Var_Parse(const char *str, GNode *ctxt, 
 		    pattern.flags = 0;
 
 		    pattern.rhs = VarGetPattern(ctxt, &parsestate, err,
-						     &cp, delim, NULL,
-						     &pattern.rightLen,
-						     NULL);
+						&cp, delim, NULL,
+						&pattern.rightLen,
+						NULL);
 		    if (v->flags & VAR_JUNK) {
 			/* restore original name */
 			free(v->name);
@@ -2367,7 +2099,7 @@ Var_Parse(const char *str, GNode *ctxt, 
 			if (emsg)
 			    Error(emsg, nstr);
 			else
-			   Var_Set(v->name, newStr,  v_ctxt, 0);
+			    Var_Set(v->name, newStr,  v_ctxt, 0);
 			if (newStr)
 			    free(newStr);
 			break;
@@ -2384,59 +2116,59 @@ Var_Parse(const char *str, GNode *ctxt, 
 		    break;
 		}
 		goto default_case; /* "::<unrecognised>" */
-		}
-	        case '@':
-		{
-		    VarLoop_t	loop;
-		    int flags = VAR_NOSUBST;
-
-		    cp = ++tstr;
-		    delim = '@';
-		    if ((loop.tvar = VarGetPattern(ctxt, &parsestate, err,
-						   &cp, delim,
-						   &flags, &loop.tvarLen,
-						   NULL)) == NULL)
-			goto cleanup;
-
-		    if ((loop.str = VarGetPattern(ctxt, &parsestate, err,
-						  &cp, delim,
-						  &flags, &loop.strLen,
-						  NULL)) == NULL)
-			goto cleanup;
-
-		    termc = *cp;
-		    delim = '\0';
+	    }
+	case '@':
+	    {
+		VarLoop_t	loop;
+		int flags = VAR_NOSUBST;
 
-		    loop.err = err;
-		    loop.ctxt = ctxt;
-		    newStr = VarModify(ctxt, &parsestate, nstr, VarLoopExpand,
-				       (ClientData)&loop);
-		    free(loop.tvar);
-		    free(loop.str);
-		    break;
-		}
-	        case 'D':
-	        case 'U':
-		{
-		    Buffer  buf;    	/* Buffer for patterns */
-		    int	    wantit;	/* want data in buffer */
+		cp = ++tstr;
+		delim = '@';
+		if ((loop.tvar = VarGetPattern(ctxt, &parsestate, err,
+					       &cp, delim,
+					       &flags, &loop.tvarLen,
+					       NULL)) == NULL)
+		    goto cleanup;
+
+		if ((loop.str = VarGetPattern(ctxt, &parsestate, err,
+					      &cp, delim,
+					      &flags, &loop.strLen,
+					      NULL)) == NULL)
+		    goto cleanup;
+
+		termc = *cp;
+		delim = '\0';
+
+		loop.err = err;
+		loop.ctxt = ctxt;
+		newStr = VarModify(ctxt, &parsestate, nstr, VarLoopExpand,
+				   (ClientData)&loop);
+		free(loop.tvar);
+		free(loop.str);
+		break;
+	    }
+	case 'D':
+	case 'U':
+	    {
+		Buffer  buf;    	/* Buffer for patterns */
+		int	    wantit;	/* want data in buffer */
 
-		    /*
-		     * Pass through tstr looking for 1) escaped delimiters,
-		     * '$'s and backslashes (place the escaped character in
-		     * uninterpreted) and 2) unescaped $'s that aren't before
-		     * the delimiter (expand the variable substitution).
-		     * The result is left in the Buffer buf.
-		     */
-		    buf = Buf_Init(0);
-		    for (cp = tstr + 1;
-			 *cp != endc && *cp != ':' && *cp != '\0';
-			 cp++) {
-			if ((*cp == '\\') &&
-			    ((cp[1] == ':') ||
-			     (cp[1] == '$') ||
-			     (cp[1] == endc) ||
-			     (cp[1] == '\\')))
+		/*
+		 * Pass through tstr looking for 1) escaped delimiters,
+		 * '$'s and backslashes (place the escaped character in
+		 * uninterpreted) and 2) unescaped $'s that aren't before
+		 * the delimiter (expand the variable substitution).
+		 * The result is left in the Buffer buf.
+		 */
+		buf = Buf_Init(0);
+		for (cp = tstr + 1;
+		     *cp != endc && *cp != ':' && *cp != '\0';
+		     cp++) {
+		    if ((*cp == '\\') &&
+			((cp[1] == ':') ||
+			 (cp[1] == '$') ||
+			 (cp[1] == endc) ||
+			 (cp[1] == '\\')))
 			{
 			    Buf_AddByte(buf, (Byte)cp[1]);
 			    cp++;
@@ -2457,343 +2189,343 @@ Var_Parse(const char *str, GNode *ctxt, 
 			} else {
 			    Buf_AddByte(buf, (Byte)*cp);
 			}
-		    }
-		    Buf_AddByte(buf, (Byte)'\0');
+		}
+		Buf_AddByte(buf, (Byte)'\0');
 
-		    termc = *cp;
+		termc = *cp;
 
-		    if (*tstr == 'U')
-			wantit = ((v->flags & VAR_JUNK) != 0);
-		    else
-			wantit = ((v->flags & VAR_JUNK) == 0);
-		    if ((v->flags & VAR_JUNK) != 0)
-			v->flags |= VAR_KEEP;
-		    if (wantit) {
-			newStr = (char *)Buf_GetAll(buf, NULL);
-			Buf_Destroy(buf, FALSE);
-		    } else {
-			newStr = nstr;
-			Buf_Destroy(buf, TRUE);
-		    }
-		    break;
+		if (*tstr == 'U')
+		    wantit = ((v->flags & VAR_JUNK) != 0);
+		else
+		    wantit = ((v->flags & VAR_JUNK) == 0);
+		if ((v->flags & VAR_JUNK) != 0)
+		    v->flags |= VAR_KEEP;
+		if (wantit) {
+		    newStr = (char *)Buf_GetAll(buf, NULL);
+		    Buf_Destroy(buf, FALSE);
+		} else {
+		    newStr = nstr;
+		    Buf_Destroy(buf, TRUE);
 		}
-	        case 'L':
-		{
-		    if ((v->flags & VAR_JUNK) != 0)
-			v->flags |= VAR_KEEP;
+		break;
+	    }
+	case 'L':
+	    {
+		if ((v->flags & VAR_JUNK) != 0)
+		    v->flags |= VAR_KEEP;
+		newStr = strdup(v->name);
+		cp = ++tstr;
+		termc = *tstr;
+		break;
+	    }
+	case 'P':
+	    {
+		GNode *gn;
+		    
+		if ((v->flags & VAR_JUNK) != 0)
+		    v->flags |= VAR_KEEP;
+		gn = Targ_FindNode(v->name, TARG_NOCREATE);
+		if (gn == NILGNODE || gn->type & OP_NOPATH) {
+		    newStr = NULL;
+		} else if (gn->path) {
+		    newStr = strdup(gn->path);
+		} else {
+		    newStr = Dir_FindFile(v->name, Suff_FindPath(gn));
+		}
+		if (!newStr) {
 		    newStr = strdup(v->name);
-		    cp = ++tstr;
-		    termc = *tstr;
-		    break;
 		}
-	        case 'P':
-		{
-		    GNode *gn;
+		cp = ++tstr;
+		termc = *tstr;
+		break;
+	    }
+	case '!':
+	    {
+		const char *emsg;
+		VarPattern 	    pattern;
+		pattern.flags = 0;
+
+		delim = '!';
+
+		cp = ++tstr;
+		if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, err,
+						 &cp, delim,
+						 NULL, &pattern.rightLen,
+						 NULL)) == NULL)
+		    goto cleanup;
+		newStr = Cmd_Exec(pattern.rhs, &emsg);
+		free(UNCONST(pattern.rhs));
+		if (emsg)
+		    Error(emsg, nstr);
+		termc = *cp;
+		delim = '\0';
+		if (v->flags & VAR_JUNK) {
+		    v->flags |= VAR_KEEP;
+		}
+		break;
+	    }
+	case '[':
+	    {
+		/*
+		 * Look for the closing ']', recursively
+		 * expanding any embedded variables.
+		 *
+		 * estr is a pointer to the expanded result,
+		 * which we must free().
+		 */
+		char *estr;
 		    
-		    if ((v->flags & VAR_JUNK) != 0)
-			v->flags |= VAR_KEEP;
-		    gn = Targ_FindNode(v->name, TARG_NOCREATE);
-		    if (gn == NILGNODE || gn->type & OP_NOPATH) {
-			newStr = NULL;
-		    } else if (gn->path) {
-			newStr = strdup(gn->path);
+		cp = tstr+1; /* point to char after '[' */
+		delim = ']'; /* look for closing ']' */
+		estr = VarGetPattern(ctxt, &parsestate,
+				     err, &cp, delim,
+				     NULL, NULL, NULL);
+		if (estr == NULL)
+		    goto cleanup; /* report missing ']' */
+		/* now cp points just after the closing ']' */
+		delim = '\0';
+		if (cp[0] != ':' && cp[0] != endc) {
+		    /* Found junk after ']' */
+		    free(estr);
+		    goto bad_modifier;
+		}
+		if (estr[0] == '\0') {
+		    /* Found empty square brackets in ":[]". */
+		    free(estr);
+		    goto bad_modifier;
+		} else if (estr[0] == '#' && estr[1] == '\0') {
+		    /* Found ":[#]" */
+
+		    /*
+		     * We will need enough space for the decimal
+		     * representation of an int.  We calculate the
+		     * space needed for the octal representation,
+		     * and add enough slop to cope with a '-' sign
+		     * (which should never be needed) and a '\0'
+		     * string terminator.
+		     */
+		    int newStrSize =
+			(sizeof(int) * CHAR_BIT + 2) / 3 + 2;
+
+		    newStr = emalloc(newStrSize);
+		    if (parsestate.oneBigWord) {
+			strncpy(newStr, "1", newStrSize);
 		    } else {
-			newStr = Dir_FindFile(v->name, Suff_FindPath(gn));
-		    }
-		    if (!newStr) {
-			newStr = strdup(v->name);
+			/* XXX: brk_string() is a rather expensive
+			 * way of counting words. */
+			char **av;
+			char *as;
+			int ac;
+
+			av = brk_string(nstr, &ac, FALSE, &as);
+			snprintf(newStr, newStrSize,  "%d", ac);
+			free(as);
+			free(av);
 		    }
-		    cp = ++tstr;
-		    termc = *tstr;
+		    termc = *cp;
+		    free(estr);
 		    break;
-		}
-	        case '!':
-		{
-		    const char *emsg;
-		    VarPattern 	    pattern;
-		    pattern.flags = 0;
-
-		    delim = '!';
-
-		    cp = ++tstr;
-		    if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, err,
-						     &cp, delim,
-						     NULL, &pattern.rightLen,
-						     NULL)) == NULL)
-			goto cleanup;
-		    newStr = Cmd_Exec(pattern.rhs, &emsg);
-		    free(UNCONST(pattern.rhs));
-		    if (emsg)
-			Error(emsg, nstr);
+		} else if (estr[0] == '*' && estr[1] == '\0') {
+		    /* Found ":[*]" */
+		    parsestate.oneBigWord = TRUE;
+		    newStr = nstr;
 		    termc = *cp;
-		    delim = '\0';
-		    if (v->flags & VAR_JUNK) {
-			v->flags |= VAR_KEEP;
-		    }
+		    free(estr);
 		    break;
-		}
-		case '[':
-		{
+		} else if (estr[0] == '@' && estr[1] == '\0') {
+		    /* Found ":[@]" */
+		    parsestate.oneBigWord = FALSE;
+		    newStr = nstr;
+		    termc = *cp;
+		    free(estr);
+		    break;
+		} else {
 		    /*
-		     * Look for the closing ']', recursively
-		     * expanding any embedded variables.
-		     *
-		     * estr is a pointer to the expanded result,
-		     * which we must free().
+		     * We expect estr to contain a single
+		     * integer for :[N], or two integers
+		     * separated by ".." for :[start..end].
 		     */
-		    char *estr;
-		    
-		    cp = tstr+1; /* point to char after '[' */
-		    delim = ']'; /* look for closing ']' */
-		    estr = VarGetPattern(ctxt, &parsestate,
-					 err, &cp, delim,
-					 NULL, NULL, NULL);
-		    if (estr == NULL)
-			goto cleanup; /* report missing ']' */
-		    /* now cp points just after the closing ']' */
-		    delim = '\0';
-		    if (cp[0] != ':' && cp[0] != endc) {
-			/* Found junk after ']' */
-			free(estr);
-			goto bad_modifier;
-		    }
-		    if (estr[0] == '\0') {
-			/* Found empty square brackets in ":[]". */
-			free(estr);
-			goto bad_modifier;
-		    } else if (estr[0] == '#' && estr[1] == '\0') {
-			/* Found ":[#]" */
+		    char *ep;
 
-			/*
-			 * We will need enough space for the decimal
-			 * representation of an int.  We calculate the
-			 * space needed for the octal representation,
-			 * and add enough slop to cope with a '-' sign
-			 * (which should never be needed) and a '\0'
-			 * string terminator.
-			 */
-			 int newStrSize =
-				 (sizeof(int) * CHAR_BIT + 2) / 3 + 2;
+		    VarSelectWords_t seldata = { 0, 0 };
 
-			newStr = emalloc(newStrSize);
-			if (parsestate.oneBigWord) {
-				strncpy(newStr, "1", newStrSize);
-			} else {
-			    /* XXX: brk_string() is a rather expensive
-			     * way of counting words. */
-			    char **av;
-			    char *as;
-			    int ac;
-
-			    av = brk_string(nstr, &ac, FALSE, &as);
-			    snprintf(newStr, newStrSize,  "%d", ac);
-			    free(as);
-			    free(av);
+		    seldata.start = strtol(estr, &ep, 0);
+		    if (ep == estr) {
+			/* Found junk instead of a number */
+			free(estr);
+			goto bad_modifier;
+		    } else if (ep[0] == '\0') {
+			/* Found only one integer in :[N] */
+			seldata.end = seldata.start;
+		    } else if (ep[0] == '.' && ep[1] == '.' &&
+			       ep[2] != '\0') {
+			/* Expecting another integer after ".." */
+			ep += 2;
+			seldata.end = strtol(ep, &ep, 0);
+			if (ep[0] != '\0') {
+			    /* Found junk after ".." */
+			    free(estr);
+			    goto bad_modifier;
 			}
-			termc = *cp;
+		    } else {
+			/* Found junk instead of ".." */
 			free(estr);
-			break;
-		    } else if (estr[0] == '*' && estr[1] == '\0') {
-			/* Found ":[*]" */
+			goto bad_modifier;
+		    }
+		    /*
+		     * Now seldata is properly filled in,
+		     * but we still have to check for 0 as
+		     * a special case.
+		     */
+		    if (seldata.start == 0 && seldata.end == 0) {
+			/* ":[0]" or perhaps ":[0..0]" */
 			parsestate.oneBigWord = TRUE;
 			newStr = nstr;
 			termc = *cp;
 			free(estr);
 			break;
-		    } else if (estr[0] == '@' && estr[1] == '\0') {
-			/* Found ":[@]" */
-			parsestate.oneBigWord = FALSE;
-			newStr = nstr;
-			termc = *cp;
+		    } else if (seldata.start == 0 ||
+			       seldata.end == 0) {
+			/* ":[0..N]" or ":[N..0]" */
 			free(estr);
-			break;
-		    } else {
+			goto bad_modifier;
+		    }
+		    /*
+		     * Normal case: select the words
+		     * described by seldata.
+		     */
+		    newStr = VarSelectWords(ctxt, &parsestate,
+					    nstr, &seldata);
+
+		    termc = *cp;
+		    free(estr);
+		    break;
+		}
+
+	    }
+	case 't':
+	    {
+		cp = tstr + 1;	/* make sure it is set */
+		if (tstr[1] != endc && tstr[1] != ':') {
+		    if (tstr[1] == 's') {
 			/*
-			 * We expect estr to contain a single
-			 * integer for :[N], or two integers
-			 * separated by ".." for :[start..end].
+			 * Use the char (if any) at tstr[2]
+			 * as the word separator.
 			 */
-			char *ep;
-
-			VarSelectWords_t seldata = { 0, 0 };
+			VarPattern pattern;
 
-			seldata.start = strtol(estr, &ep, 0);
-			if (ep == estr) {
-			    /* Found junk instead of a number */
-			    free(estr);
-			    goto bad_modifier;
-			} else if (ep[0] == '\0') {
-			    /* Found only one integer in :[N] */
-			    seldata.end = seldata.start;
-			} else if (ep[0] == '.' && ep[1] == '.' &&
-				   ep[2] != '\0') {
-			    /* Expecting another integer after ".." */
-			    ep += 2;
-			    seldata.end = strtol(ep, &ep, 0);
-			    if (ep[0] != '\0') {
-				/* Found junk after ".." */
-				free(estr);
-				goto bad_modifier;
+			if (tstr[2] != endc &&
+			    (tstr[3] == endc || tstr[3] == ':')) {
+			    /* ":ts<unrecognised><endc>" or
+			     * ":ts<unrecognised>:" */
+			    parsestate.varSpace = tstr[2];
+			    cp = tstr + 3;
+			} else if (tstr[2] == endc || tstr[2] == ':') {
+			    /* ":ts<endc>" or ":ts:" */
+			    parsestate.varSpace = 0; /* no separator */
+			    cp = tstr + 2;
+			} else if (tstr[2] == '\\') {
+			    switch (tstr[3]) {
+			    case 'n':
+				parsestate.varSpace = '\n';
+				cp = tstr + 4;
+				break;
+			    case 't':
+				parsestate.varSpace = '\t';
+				cp = tstr + 4;
+				break;
+			    default:
+				if (isdigit((unsigned char)tstr[3])) {
+				    char *ep;
+					
+				    parsestate.varSpace =
+					strtoul(&tstr[3], &ep, 0);
+				    if (*ep != ':' && *ep != endc)
+					goto bad_modifier;
+				    cp = ep;
+				} else {
+				    /*
+				     * ":ts<backslash><unrecognised>".
+				     */
+				    goto bad_modifier;
+				}
+				break;
 			    }
 			} else {
-			    /* Found junk instead of ".." */
-			    free(estr);
-			    goto bad_modifier;
-			}
-			/*
-			 * Now seldata is properly filled in,
-			 * but we still have to check for 0 as
-			 * a special case.
-			 */
-			if (seldata.start == 0 && seldata.end == 0) {
-			    /* ":[0]" or perhaps ":[0..0]" */
-			    parsestate.oneBigWord = TRUE;
-			    newStr = nstr;
-			    termc = *cp;
-			    free(estr);
-			    break;
-			} else if (seldata.start == 0 ||
-				   seldata.end == 0) {
-			    /* ":[0..N]" or ":[N..0]" */
-			    free(estr);
+			    /*
+			     * Found ":ts<unrecognised><unrecognised>".
+			     */
 			    goto bad_modifier;
 			}
-			/*
-			 * Normal case: select the words
-			 * described by seldata.
-			 */
-			newStr = VarSelectWords(ctxt, &parsestate,
-						nstr, &seldata);
 
 			termc = *cp;
-			free(estr);
-			break;
-		    }
-
-		}
-	        case 't':
-		{
-		    cp = tstr + 1;	/* make sure it is set */
-		    if (tstr[1] != endc && tstr[1] != ':') {
-			if (tstr[1] == 's') {
-			    /*
-			     * Use the char (if any) at tstr[2]
-			     * as the word separator.
-			     */
-			    VarPattern pattern;
 
-			    if (tstr[2] != endc &&
-				(tstr[3] == endc || tstr[3] == ':')) {
-				/* ":ts<unrecognised><endc>" or
-				 * ":ts<unrecognised>:" */
-				parsestate.varSpace = tstr[2];
-				cp = tstr + 3;
-			    } else if (tstr[2] == endc || tstr[2] == ':') {
-				/* ":ts<endc>" or ":ts:" */
-				parsestate.varSpace = 0; /* no separator */
-				cp = tstr + 2;
-			    } else if (tstr[2] == '\\') {
-				switch (tstr[3]) {
-				case 'n':
-				    parsestate.varSpace = '\n';
-				    cp = tstr + 4;
-				    break;
-				case 't':
-				    parsestate.varSpace = '\t';
-				    cp = tstr + 4;
-				    break;
-				default:
-				    if (isdigit((unsigned char)tstr[3])) {
-					char *ep;
-					
-					parsestate.varSpace =
-						strtoul(&tstr[3], &ep, 0);
-					if (*ep != ':' && *ep != endc)
-					    goto bad_modifier;
-					cp = ep;
-				    } else {
-					/*
-					 * ":ts<backslash><unrecognised>".
-					 */
-					goto bad_modifier;
-				    }
-				    break;
-				}
-			    } else {
-				/*
-				 * Found ":ts<unrecognised><unrecognised>".
-				 */
-				goto bad_modifier;
-			    }
+			/*
+			 * We cannot be certain that VarModify
+			 * will be used - even if there is a
+			 * subsequent modifier, so do a no-op
+			 * VarSubstitute now to for str to be
+			 * re-expanded without the spaces.
+			 */
+			pattern.flags = VAR_SUB_ONE;
+			pattern.lhs = pattern.rhs = "\032";
+			pattern.leftLen = pattern.rightLen = 1;
 
+			newStr = VarModify(ctxt, &parsestate, nstr,
+					   VarSubstitute,
+					   (ClientData)&pattern);
+		    } else if (tstr[2] == endc || tstr[2] == ':') {
+			/*
+			 * Check for two-character options:
+			 * ":tu", ":tl"
+			 */
+			if (tstr[1] == 'u' || tstr[1] == 'l') {
+			    newStr = VarChangeCase(nstr, (tstr[1] == 'u'));
+			    cp = tstr + 2;
+			    termc = *cp;
+			} else if (tstr[1] == 'W' || tstr[1] == 'w') {
+			    parsestate.oneBigWord = (tstr[1] == 'W');
+			    newStr = nstr;
+			    cp = tstr + 2;
 			    termc = *cp;
-
-			    /*
-			     * We cannot be certain that VarModify
-			     * will be used - even if there is a
-			     * subsequent modifier, so do a no-op
-			     * VarSubstitute now to for str to be
-			     * re-expanded without the spaces.
-			     */
-			    pattern.flags = VAR_SUB_ONE;
-			    pattern.lhs = pattern.rhs = "\032";
-			    pattern.leftLen = pattern.rightLen = 1;
-
-			    newStr = VarModify(ctxt, &parsestate, nstr,
-					       VarSubstitute,
-					       (ClientData)&pattern);
-			} else if (tstr[2] == endc || tstr[2] == ':') {
-			    /*
-			     * Check for two-character options:
-			     * ":tu", ":tl"
-			     */
-                            if (tstr[1] == 'u' || tstr[1] == 'l') {
-                                newStr = VarChangeCase(nstr, (tstr[1] == 'u'));
-                                cp = tstr + 2;
-                                termc = *cp;
-                            } else if (tstr[1] == 'W' || tstr[1] == 'w') {
-				parsestate.oneBigWord = (tstr[1] == 'W');
-				newStr = nstr;
-				cp = tstr + 2;
-				termc = *cp;
-                            } else {
-				/* Found ":t<unrecognised>:" or
-				 * ":t<unrecognised><endc>". */
-				goto bad_modifier;
-			    }
 			} else {
-			    /*
-			     * Found ":t<unrecognised><unrecognised>".
-			     */
+			    /* Found ":t<unrecognised>:" or
+			     * ":t<unrecognised><endc>". */
 			    goto bad_modifier;
 			}
 		    } else {
 			/*
-			 * Found ":t<endc>" or ":t:".
+			 * Found ":t<unrecognised><unrecognised>".
 			 */
 			goto bad_modifier;
 		    }
-		    break;
-		}
-		case 'N':
-		case 'M':
-		{
-		    char    *pattern;
-		    char    *cp2;
-		    Boolean copy;
-		    int nest;
-
-		    copy = FALSE;
-		    nest = 1;
+		} else {
 		    /*
-		     * In the loop below, ignore ':' unless we are at
-		     * (or back to) the original brace level.
-		     * XXX This will likely not work right if $() and ${}
-		     * are intermixed.
+		     * Found ":t<endc>" or ":t:".
 		     */
-		    for (cp = tstr + 1;
-			 *cp != '\0' && !(*cp == ':' && nest == 1);
-			 cp++)
+		    goto bad_modifier;
+		}
+		break;
+	    }
+	case 'N':
+	case 'M':
+	    {
+		char    *pattern;
+		char    *cp2;
+		Boolean copy;
+		int nest;
+
+		copy = FALSE;
+		nest = 1;
+		/*
+		 * In the loop below, ignore ':' unless we are at
+		 * (or back to) the original brace level.
+		 * XXX This will likely not work right if $() and ${}
+		 * are intermixed.
+		 */
+		for (cp = tstr + 1;
+		     *cp != '\0' && !(*cp == ':' && nest == 1);
+		     cp++)
 		    {
 			if (*cp == '\\' &&
 			    (cp[1] == ':' ||
@@ -2810,414 +2542,749 @@ Var_Parse(const char *str, GNode *ctxt, 
 				break;
 			}
 		    }
-		    termc = *cp;
-		    *WR(cp) = '\0';
-		    if (copy) {
-			/*
-			 * Need to compress the \:'s out of the pattern, so
-			 * allocate enough room to hold the uncompressed
-			 * pattern (note that cp started at tstr+1, so
-			 * cp - tstr takes the null byte into account) and
-			 * compress the pattern into the space.
-			 */
-			pattern = emalloc(cp - tstr);
-			for (cp2 = pattern, cp = tstr + 1;
-			     *cp != '\0';
-			     cp++, cp2++)
+		termc = *cp;
+		*WR(cp) = '\0';
+		if (copy) {
+		    /*
+		     * Need to compress the \:'s out of the pattern, so
+		     * allocate enough room to hold the uncompressed
+		     * pattern (note that cp started at tstr+1, so
+		     * cp - tstr takes the null byte into account) and
+		     * compress the pattern into the space.
+		     */
+		    pattern = emalloc(cp - tstr);
+		    for (cp2 = pattern, cp = tstr + 1;
+			 *cp != '\0';
+			 cp++, cp2++)
 			{
 			    if ((*cp == '\\') &&
 				(cp[1] == ':' || cp[1] == endc)) {
-				    cp++;
+				cp++;
 			    }
 			    *cp2 = *cp;
 			}
-			*cp2 = '\0';
-		    } else {
-			pattern = UNCONST(&tstr[1]);
-		    }
-		    if ((cp2 = strchr(pattern, '$'))) {
-			cp2 = pattern;
-			pattern = Var_Subst(NULL, cp2, ctxt, err);
-			if (copy)
-			    free(cp2);
-			copy = TRUE;
-		    }
-		    if (*tstr == 'M' || *tstr == 'm') {
-			newStr = VarModify(ctxt, &parsestate, nstr, VarMatch,
-					   (ClientData)pattern);
-		    } else {
-			newStr = VarModify(ctxt, &parsestate, nstr, VarNoMatch,
-					   (ClientData)pattern);
-		    }
-		    if (copy) {
-			free(pattern);
-		    }
-		    break;
+		    *cp2 = '\0';
+		} else {
+		    pattern = UNCONST(&tstr[1]);
 		}
-		case 'S':
-		{
-		    VarPattern 	    pattern;
-		    Var_Parse_State tmpparsestate;
+		if ((cp2 = strchr(pattern, '$'))) {
+		    cp2 = pattern;
+		    pattern = Var_Subst(NULL, cp2, ctxt, err);
+		    if (copy)
+			free(cp2);
+		    copy = TRUE;
+		}
+		if (*tstr == 'M' || *tstr == 'm') {
+		    newStr = VarModify(ctxt, &parsestate, nstr, VarMatch,
+				       (ClientData)pattern);
+		} else {
+		    newStr = VarModify(ctxt, &parsestate, nstr, VarNoMatch,
+				       (ClientData)pattern);
+		}
+		if (copy) {
+		    free(pattern);
+		}
+		break;
+	    }
+	case 'S':
+	    {
+		VarPattern 	    pattern;
+		Var_Parse_State tmpparsestate;
 
-		    pattern.flags = 0;
-		    tmpparsestate = parsestate;
-		    delim = tstr[1];
-		    tstr += 2;
+		pattern.flags = 0;
+		tmpparsestate = parsestate;
+		delim = tstr[1];
+		tstr += 2;
 
-		    /*
-		     * If pattern begins with '^', it is anchored to the
-		     * start of the word -- skip over it and flag pattern.
-		     */
-		    if (*tstr == '^') {
-			pattern.flags |= VAR_MATCH_START;
-			tstr += 1;
+		/*
+		 * If pattern begins with '^', it is anchored to the
+		 * start of the word -- skip over it and flag pattern.
+		 */
+		if (*tstr == '^') {
+		    pattern.flags |= VAR_MATCH_START;
+		    tstr += 1;
+		}
+
+		cp = tstr;
+		if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, err,
+						 &cp, delim,
+						 &pattern.flags,
+						 &pattern.leftLen,
+						 NULL)) == NULL)
+		    goto cleanup;
+
+		if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, err,
+						 &cp, delim, NULL,
+						 &pattern.rightLen,
+						 &pattern)) == NULL)
+		    goto cleanup;
+
+		/*
+		 * Check for global substitution. If 'g' after the final
+		 * delimiter, substitution is global and is marked that
+		 * way.
+		 */
+		for (;; cp++) {
+		    switch (*cp) {
+		    case 'g':
+			pattern.flags |= VAR_SUB_GLOBAL;
+			continue;
+		    case '1':
+			pattern.flags |= VAR_SUB_ONE;
+			continue;
+		    case 'W':
+			tmpparsestate.oneBigWord = TRUE;
+			continue;
 		    }
+		    break;
+		}
 
-		    cp = tstr;
-		    if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, err,
-						     &cp, delim,
-						     &pattern.flags,
-						     &pattern.leftLen,
-						     NULL)) == NULL)
-			goto cleanup;
+		termc = *cp;
+		newStr = VarModify(ctxt, &tmpparsestate, nstr,
+				   VarSubstitute,
+				   (ClientData)&pattern);
 
-		    if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, err,
-						     &cp, delim, NULL,
-						     &pattern.rightLen,
-						     &pattern)) == NULL)
-			goto cleanup;
+		/*
+		 * Free the two strings.
+		 */
+		free(UNCONST(pattern.lhs));
+		free(UNCONST(pattern.rhs));
+		delim = '\0';
+		break;
+	    }	
+	case '?':
+	    {
+		VarPattern 	pattern;
+		Boolean	value;
 
-		    /*
-		     * Check for global substitution. If 'g' after the final
-		     * delimiter, substitution is global and is marked that
-		     * way.
-		     */
-		    for (;; cp++) {
-			switch (*cp) {
-			case 'g':
-			    pattern.flags |= VAR_SUB_GLOBAL;
-			    continue;
-			case '1':
-			    pattern.flags |= VAR_SUB_ONE;
-			    continue;
-			case 'W':
-			    tmpparsestate.oneBigWord = TRUE;
-			    continue;
-			}
-			break;
-		    }
+		/* find ':', and then substitute accordingly */
 
-		    termc = *cp;
-		    newStr = VarModify(ctxt, &tmpparsestate, nstr,
-				       VarSubstitute,
-				       (ClientData)&pattern);
+		pattern.flags = 0;
 
-		    /*
-		     * Free the two strings.
-		     */
-		    free(UNCONST(pattern.lhs));
+		cp = ++tstr;
+		delim = ':';
+		if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, err,
+						 &cp, delim, NULL,
+						 &pattern.leftLen,
+						 NULL)) == NULL)
+		    goto cleanup;
+
+		/* BROPEN or PROPEN */
+		delim = endc;
+		if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, err,
+						 &cp, delim, NULL,
+						 &pattern.rightLen,
+						 NULL)) == NULL)
+		    goto cleanup;
+
+		termc = *--cp;
+		delim = '\0';
+		if (Cond_EvalExpression(1, v->name, &value, 0)
+		    == COND_INVALID) {
+		    Error("Bad conditional expression `%s' in %s?%s:%s",
+			  v->name, v->name, pattern.lhs, pattern.rhs);
+		    goto cleanup;
+		}
+
+		if (value) {
+		    newStr = UNCONST(pattern.lhs);
 		    free(UNCONST(pattern.rhs));
-		    delim = '\0';
+		} else {
+		    newStr = UNCONST(pattern.rhs);
+		    free(UNCONST(pattern.lhs));
+		}
+		if (v->flags & VAR_JUNK) {
+		    v->flags |= VAR_KEEP;
+		}
+		break;
+	    }
+#ifndef NO_REGEX
+	case 'C':
+	    {
+		VarREPattern    pattern;
+		char           *re;
+		int             error;
+		Var_Parse_State tmpparsestate;
+
+		pattern.flags = 0;
+		tmpparsestate = parsestate;
+		delim = tstr[1];
+		tstr += 2;
+
+		cp = tstr;
+
+		if ((re = VarGetPattern(ctxt, &parsestate, err, &cp, delim,
+					NULL, NULL, NULL)) == NULL)
+		    goto cleanup;
+
+		if ((pattern.replace = VarGetPattern(ctxt, &parsestate,
+						     err, &cp, delim, NULL,
+						     NULL, NULL)) == NULL){
+		    free(re);
+		    goto cleanup;
+		}
+
+		for (;; cp++) {
+		    switch (*cp) {
+		    case 'g':
+			pattern.flags |= VAR_SUB_GLOBAL;
+			continue;
+		    case '1':
+			pattern.flags |= VAR_SUB_ONE;
+			continue;
+		    case 'W':
+			tmpparsestate.oneBigWord = TRUE;
+			continue;
+		    }
 		    break;
-		}	
-		case '?':
-		{
-		    VarPattern 	pattern;
-		    Boolean	value;
+		}
 
-		    /* find ':', and then substitute accordingly */
+		termc = *cp;
 
-		    pattern.flags = 0;
+		error = regcomp(&pattern.re, re, REG_EXTENDED);
+		free(re);
+		if (error)  {
+		    *lengthPtr = cp - start + 1;
+		    VarREError(error, &pattern.re, "RE substitution error");
+		    free(pattern.replace);
+		    goto cleanup;
+		}
 
-		    cp = ++tstr;
-		    delim = ':';
-		    if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, err,
-						     &cp, delim, NULL,
-						     &pattern.leftLen,
-						     NULL)) == NULL)
-			goto cleanup;
+		pattern.nsub = pattern.re.re_nsub + 1;
+		if (pattern.nsub < 1)
+		    pattern.nsub = 1;
+		if (pattern.nsub > 10)
+		    pattern.nsub = 10;
+		pattern.matches = emalloc(pattern.nsub *
+					  sizeof(regmatch_t));
+		newStr = VarModify(ctxt, &tmpparsestate, nstr,
+				   VarRESubstitute,
+				   (ClientData) &pattern);
+		regfree(&pattern.re);
+		free(pattern.replace);
+		free(pattern.matches);
+		delim = '\0';
+		break;
+	    }
+#endif
+	case 'Q':
+	    if (tstr[1] == endc || tstr[1] == ':') {
+		newStr = VarQuote(nstr);
+		cp = tstr + 1;
+		termc = *cp;
+		break;
+	    }
+	    goto default_case;
+	case 'T':
+	    if (tstr[1] == endc || tstr[1] == ':') {
+		newStr = VarModify(ctxt, &parsestate, nstr, VarTail,
+				   (ClientData)0);
+		cp = tstr + 1;
+		termc = *cp;
+		break;
+	    }
+	    goto default_case;
+	case 'H':
+	    if (tstr[1] == endc || tstr[1] == ':') {
+		newStr = VarModify(ctxt, &parsestate, nstr, VarHead,
+				   (ClientData)0);
+		cp = tstr + 1;
+		termc = *cp;
+		break;
+	    }
+	    goto default_case;
+	case 'E':
+	    if (tstr[1] == endc || tstr[1] == ':') {
+		newStr = VarModify(ctxt, &parsestate, nstr, VarSuffix,
+				   (ClientData)0);
+		cp = tstr + 1;
+		termc = *cp;
+		break;
+	    }
+	    goto default_case;
+	case 'R':
+	    if (tstr[1] == endc || tstr[1] == ':') {
+		newStr = VarModify(ctxt, &parsestate, nstr, VarRoot,
+				   (ClientData)0);
+		cp = tstr + 1;
+		termc = *cp;
+		break;
+	    }
+	    goto default_case;
+	case 'O':
+	    {
+		char otype;
 
-			/* BROPEN or PROPEN */
-		    delim = endc;
-		    if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, err,
-						     &cp, delim, NULL,
-						     &pattern.rightLen,
-						     NULL)) == NULL)
-			goto cleanup;
+		cp = tstr + 1;	/* skip to the rest in any case */
+		if (tstr[1] == endc || tstr[1] == ':') {
+		    otype = 's';
+		    termc = *cp;
+		} else if ( (tstr[1] == 'x') &&
+			    (tstr[2] == endc || tstr[2] == ':') ) {
+		    otype = tstr[1];
+		    cp = tstr + 2;
+		    termc = *cp;
+		} else {
+		    goto bad_modifier;
+		}
+		newStr = VarOrder(nstr, otype);
+		break;
+	    }
+	case 'u':
+	    if (tstr[1] == endc || tstr[1] == ':') {
+		newStr = VarUniq(nstr);
+		cp = tstr + 1;
+		termc = *cp;
+		break;
+	    }
+	    goto default_case;
+#ifdef SUNSHCMD
+	case 's':
+	    if (tstr[1] == 'h' && (tstr[2] == endc || tstr[2] == ':')) {
+		const char *emsg;
+		newStr = Cmd_Exec(nstr, &emsg);
+		if (emsg)
+		    Error(emsg, nstr);
+		cp = tstr + 2;
+		termc = *cp;
+		break;
+	    }
+	    goto default_case;
+#endif
+	default:
+	default_case: 
+	{
+#ifdef SYSVVARSUB
+	    /*
+	     * This can either be a bogus modifier or a System-V
+	     * substitution command.
+	     */
+	    VarPattern      pattern;
+	    Boolean         eqFound;
 
-		    termc = *--cp;
-		    delim = '\0';
-		    if (Cond_EvalExpression(1, v->name, &value, 0)
-			== COND_INVALID) {
-			Error("Bad conditional expression `%s' in %s?%s:%s",
-			      v->name, v->name, pattern.lhs, pattern.rhs);
-			goto cleanup;
-		    }
+	    pattern.flags = 0;
+	    eqFound = FALSE;
+	    /*
+	     * First we make a pass through the string trying
+	     * to verify it is a SYSV-make-style translation:
+	     * it must be: <string1>=<string2>)
+	     */
+	    cp = tstr;
+	    cnt = 1;
+	    while (*cp != '\0' && cnt) {
+		if (*cp == '=') {
+		    eqFound = TRUE;
+		    /* continue looking for endc */
+		}
+		else if (*cp == endc)
+		    cnt--;
+		else if (*cp == startc)
+		    cnt++;
+		if (cnt)
+		    cp++;
+	    }
+	    if (*cp == endc && eqFound) {
 
-		    if (value) {
-			newStr = UNCONST(pattern.lhs);
-			free(UNCONST(pattern.rhs));
-		    } else {
-			newStr = UNCONST(pattern.rhs);
-			free(UNCONST(pattern.lhs));
-		    }
-		    if (v->flags & VAR_JUNK) {
-			v->flags |= VAR_KEEP;
-		    }
-		    break;
-		}
-#ifndef NO_REGEX
-		case 'C':
+		/*
+		 * Now we break this sucker into the lhs and
+		 * rhs. We must null terminate them of course.
+		 */
+		delim='=';
+		cp = tstr;
+		if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, 
+						 err, &cp, delim, &pattern.flags,
+						 &pattern.leftLen, NULL)) == NULL)
+		    goto cleanup;
+		delim = endc;
+		if ((pattern.rhs = VarGetPattern(ctxt, &parsestate,
+						 err, &cp, delim, NULL, &pattern.rightLen,
+						 &pattern)) == NULL)
+		    goto cleanup;
+
+		/*
+		 * SYSV modifications happen through the whole
+		 * string. Note the pattern is anchored at the end.
+		 */
+		termc = *--cp;
+		delim = '\0';
+		newStr = VarModify(ctxt, &parsestate, nstr,
+				   VarSYSVMatch,
+				   (ClientData)&pattern);
+		free(UNCONST(pattern.lhs));
+		free(UNCONST(pattern.rhs));
+	    } else
+#endif
 		{
-		    VarREPattern    pattern;
-		    char           *re;
-		    int             error;
-		    Var_Parse_State tmpparsestate;
+		    Error("Unknown modifier '%c'", *tstr);
+		    for (cp = tstr+1;
+			 *cp != ':' && *cp != endc && *cp != '\0';
+			 cp++)
+			continue;
+		    termc = *cp;
+		    newStr = var_Error;
+		}
+	    }
+	}
+	if (DEBUG(VAR)) {
+	    printf("Result is \"%s\"\n", newStr);
+	}
 
-		    pattern.flags = 0;
-		    tmpparsestate = parsestate;
-		    delim = tstr[1];
-		    tstr += 2;
+	if (newStr != nstr) {
+	    if (*freePtr) {
+		free(nstr);
+		*freePtr = NULL;
+	    }
+	    nstr = newStr;
+	    if (nstr != var_Error && nstr != varNoError) {
+		*freePtr = nstr;
+	    }
+	}
+	if (termc == '\0' && endc != '\0') {
+	    Error("Unclosed variable specification for %s", v->name);
+	} else if (termc == ':') {
+	    *WR(cp) = termc;
+	    cp++;
+	} else {
+	    *WR(cp) = termc;
+	}
+	tstr = cp;
+    }
+ out:
+    *lengthPtr = tstr - start;
+    return (nstr);
 
-		    cp = tstr;
+ bad_modifier:
+    /* "{(" */
+    Error("Bad modifier `:%.*s' for %s", (int)strcspn(tstr, ":)}"), tstr,
+	  v->name);
 
-		    if ((re = VarGetPattern(ctxt, &parsestate, err, &cp, delim,
-					    NULL, NULL, NULL)) == NULL)
-			goto cleanup;
+ cleanup:
+    *lengthPtr = cp - start;
+    if (delim != '\0')
+	Error("Unclosed substitution for %s (%c missing)",
+	      v->name, delim);
+    if (*freePtr) {
+	free(*freePtr);
+	*freePtr = NULL;
+    }
+    return (var_Error);
+}
 
-		    if ((pattern.replace = VarGetPattern(ctxt, &parsestate,
-							 err, &cp, delim, NULL,
-							 NULL, NULL)) == NULL){
-			free(re);
-			goto cleanup;
-		    }
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Parse --
+ *	Given the start of a variable invocation, extract the variable
+ *	name and find its value, then modify it according to the
+ *	specification.
+ *
+ * Input:
+ *	str		The string to parse
+ *	ctxt		The context for the variable
+ *	err		TRUE if undefined variables are an error
+ *	lengthPtr	OUT: The length of the specification
+ *	freePtr		OUT: TRUE if caller should free result
+ *
+ * Results:
+ *	The (possibly-modified) value of the variable or var_Error if the
+ *	specification is invalid. The length of the specification is
+ *	placed in *lengthPtr (for invalid specifications, this is just
+ *	2...?).
+ *	A Boolean in *freePtr telling whether the returned string should
+ *	be freed by the caller.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+/* coverity[+alloc : arg-*4] */
+char *
+Var_Parse(const char *str, GNode *ctxt, Boolean err, int *lengthPtr,
+	  void **freePtr)
+{
+    const char	   *tstr;    	/* Pointer into str */
+    Var	    	   *v;	    	/* Variable in invocation */
+    Boolean 	    haveModifier;/* TRUE if have modifiers for the variable */
+    char	    endc;    	/* Ending character when variable in parens
+				 * or braces */
+    char	    startc=0;	/* Starting character when variable in parens
+				 * or braces */
+    int		    vlen;	/* Length of variable name */
+    const char 	   *start;
+    char	   *nstr;
+    Boolean 	    dynamic;	/* TRUE if the variable is local and we're
+				 * expanding it in a non-local context. This
+				 * is done to support dynamic sources. The
+				 * result is just the invocation, unaltered */
+    Var_Parse_State parsestate; /* Flags passed to helper functions */
 
-		    for (;; cp++) {
-			switch (*cp) {
-			case 'g':
-			    pattern.flags |= VAR_SUB_GLOBAL;
-			    continue;
-			case '1':
-			    pattern.flags |= VAR_SUB_ONE;
-			    continue;
-			case 'W':
-			    tmpparsestate.oneBigWord = TRUE;
-			    continue;
-			}
-			break;
-		    }
+    *freePtr = NULL;
+    dynamic = FALSE;
+    start = str;
+    parsestate.oneBigWord = FALSE;
+    parsestate.varSpace = ' ';	/* word separator */
 
-		    termc = *cp;
+    if (str[1] != PROPEN && str[1] != BROPEN) {
+	/*
+	 * If it's not bounded by braces of some sort, life is much simpler.
+	 * We just need to check for the first character and return the
+	 * value if it exists.
+	 */
+	char	  name[2];
+
+	name[0] = str[1];
+	name[1] = '\0';
+
+	v = VarFind(name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD);
+	if (v == (Var *)NIL) {
+	    *lengthPtr = 2;
+
+	    if ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)) {
+		/*
+		 * If substituting a local variable in a non-local context,
+		 * assume it's for dynamic source stuff. We have to handle
+		 * this specially and return the longhand for the variable
+		 * with the dollar sign escaped so it makes it back to the
+		 * caller. Only four of the local variables are treated
+		 * specially as they are the only four that will be set
+		 * when dynamic sources are expanded.
+		 */
+		switch (str[1]) {
+		    case '@':
+			return UNCONST("$(.TARGET)");
+		    case '%':
+			return UNCONST("$(.ARCHIVE)");
+		    case '*':
+			return UNCONST("$(.PREFIX)");
+		    case '!':
+			return UNCONST("$(.MEMBER)");
+		}
+	    }
+	    /*
+	     * Error
+	     */
+	    return (err ? var_Error : varNoError);
+	} else {
+	    haveModifier = FALSE;
+	    tstr = &str[1];
+	    endc = str[1];
+	}
+    } else if (str[1] == '\0') {
+	*lengthPtr = 1;
+	return (err ? var_Error : varNoError);
+    } else { 
+	Buffer buf;	/* Holds the variable name */
 
-		    error = regcomp(&pattern.re, re, REG_EXTENDED);
-		    free(re);
-		    if (error)  {
-			*lengthPtr = cp - start + 1;
-			VarREError(error, &pattern.re, "RE substitution error");
-			free(pattern.replace);
-			goto cleanup;
-		    }
+	startc = str[1];
+	endc = startc == PROPEN ? PRCLOSE : BRCLOSE;
+	buf = Buf_Init(MAKE_BSIZE);
 
-		    pattern.nsub = pattern.re.re_nsub + 1;
-		    if (pattern.nsub < 1)
-			pattern.nsub = 1;
-		    if (pattern.nsub > 10)
-			pattern.nsub = 10;
-		    pattern.matches = emalloc(pattern.nsub *
-					      sizeof(regmatch_t));
-		    newStr = VarModify(ctxt, &tmpparsestate, nstr,
-				       VarRESubstitute,
-				       (ClientData) &pattern);
-		    regfree(&pattern.re);
-		    free(pattern.replace);
-		    free(pattern.matches);
-		    delim = '\0';
-		    break;
+	/*
+	 * Skip to the end character or a colon, whichever comes first.
+	 */
+	for (tstr = str + 2;
+	     *tstr != '\0' && *tstr != endc && *tstr != ':';
+	     tstr++)
+	{
+	    /*
+	     * A variable inside a variable, expand
+	     */
+	    if (*tstr == '$') {
+		int rlen;
+		void *freeIt;
+		char *rval = Var_Parse(tstr, ctxt, err, &rlen, &freeIt);
+		if (rval != NULL) {
+		    Buf_AddBytes(buf, strlen(rval), (Byte *)rval);
 		}
-#endif
-		case 'Q':
-		    if (tstr[1] == endc || tstr[1] == ':') {
-			newStr = VarQuote(nstr);
-			cp = tstr + 1;
-			termc = *cp;
-			break;
-		    }
-		    goto default_case;
-		case 'T':
-		    if (tstr[1] == endc || tstr[1] == ':') {
-			newStr = VarModify(ctxt, &parsestate, nstr, VarTail,
-					   (ClientData)0);
-			cp = tstr + 1;
-			termc = *cp;
-			break;
-		    }
-		    goto default_case;
-		case 'H':
-		    if (tstr[1] == endc || tstr[1] == ':') {
-			newStr = VarModify(ctxt, &parsestate, nstr, VarHead,
-					   (ClientData)0);
-			cp = tstr + 1;
-			termc = *cp;
-			break;
-		    }
-		    goto default_case;
-		case 'E':
-		    if (tstr[1] == endc || tstr[1] == ':') {
-			newStr = VarModify(ctxt, &parsestate, nstr, VarSuffix,
-					   (ClientData)0);
-			cp = tstr + 1;
-			termc = *cp;
-			break;
-		    }
-		    goto default_case;
-		case 'R':
-		    if (tstr[1] == endc || tstr[1] == ':') {
-			newStr = VarModify(ctxt, &parsestate, nstr, VarRoot,
-					   (ClientData)0);
-			cp = tstr + 1;
-			termc = *cp;
-			break;
-		    }
-		    goto default_case;
-		case 'O':
-		{
-		    char otype;
+		if (freeIt)
+		    free(freeIt);
+		tstr += rlen - 1;
+	    }
+	    else
+		Buf_AddByte(buf, (Byte)*tstr);
+	}
+	if (*tstr == ':') {
+	    haveModifier = TRUE;
+	} else if (*tstr != '\0') {
+	    haveModifier = FALSE;
+	} else {
+	    /*
+	     * If we never did find the end character, return NULL
+	     * right now, setting the length to be the distance to
+	     * the end of the string, since that's what make does.
+	     */
+	    *lengthPtr = tstr - str;
+	    return (var_Error);
+	}
+	*WR(tstr) = '\0';
+	Buf_AddByte(buf, (Byte)'\0');
+	str = Buf_GetAll(buf, NULL);
+	vlen = strlen(str);
 
-		    cp = tstr + 1;	/* skip to the rest in any case */
-		    if (tstr[1] == endc || tstr[1] == ':') {
-			otype = 's';
-			termc = *cp;
-		    } else if ( (tstr[1] == 'x') &&
-		    		(tstr[2] == endc || tstr[2] == ':') ) {
-			otype = tstr[1];
-			cp = tstr + 2;
-			termc = *cp;
-		    } else {
-			goto bad_modifier;
-		    }
-		    newStr = VarOrder(nstr, otype);
-		    break;
-		}
-		case 'u':
-		    if (tstr[1] == endc || tstr[1] == ':') {
-			newStr = VarUniq(nstr);
-			cp = tstr + 1;
-			termc = *cp;
-			break;
-		    }
-		    goto default_case;
-#ifdef SUNSHCMD
-		case 's':
-		    if (tstr[1] == 'h' && (tstr[2] == endc || tstr[2] == ':')) {
-			const char *emsg;
-			newStr = Cmd_Exec(nstr, &emsg);
-			if (emsg)
-			    Error(emsg, nstr);
-			cp = tstr + 2;
-			termc = *cp;
-			break;
-		    }
-		    goto default_case;
-#endif
-                default:
-		default_case: 
+	v = VarFind(str, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD);
+	if ((v == (Var *)NIL) && (ctxt != VAR_CMD) && (ctxt != VAR_GLOBAL) &&
+	    (vlen == 2) && (str[1] == 'F' || str[1] == 'D'))
+	{
+	    /*
+	     * Check for bogus D and F forms of local variables since we're
+	     * in a local context and the name is the right length.
+	     */
+	    switch(*str) {
+		case '@':
+		case '%':
+		case '*':
+		case '!':
+		case '>':
+		case '<':
 		{
-#ifdef SYSVVARSUB
-		    /*
-		     * This can either be a bogus modifier or a System-V
-		     * substitution command.
-		     */
-		    VarPattern      pattern;
-		    Boolean         eqFound;
+		    char    vname[2];
+		    char    *val;
 
-		    pattern.flags = 0;
-		    eqFound = FALSE;
 		    /*
-		     * First we make a pass through the string trying
-		     * to verify it is a SYSV-make-style translation:
-		     * it must be: <string1>=<string2>)
+		     * Well, it's local -- go look for it.
 		     */
-		    cp = tstr;
-		    cnt = 1;
-		    while (*cp != '\0' && cnt) {
-			if (*cp == '=') {
-			    eqFound = TRUE;
-			    /* continue looking for endc */
-			}
-			else if (*cp == endc)
-			    cnt--;
-			else if (*cp == startc)
-			    cnt++;
-			if (cnt)
-			    cp++;
-		    }
-		    if (*cp == endc && eqFound) {
+		    vname[0] = *str;
+		    vname[1] = '\0';
+		    v = VarFind(vname, ctxt, 0);
 
+		    if (v != (Var *)NIL) {
 			/*
-			 * Now we break this sucker into the lhs and
-			 * rhs. We must null terminate them of course.
+			 * No need for nested expansion or anything, as we're
+			 * the only one who sets these things and we sure don't
+			 * but nested invocations in them...
 			 */
-			delim='=';
-			cp = tstr;
-			if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, 
-				err, &cp, delim, &pattern.flags,
-				&pattern.leftLen, NULL)) == NULL)
-				goto cleanup;
-			delim = endc;
-			if ((pattern.rhs = VarGetPattern(ctxt, &parsestate,
-				err, &cp, delim, NULL, &pattern.rightLen,
-				&pattern)) == NULL)
-				goto cleanup;
+			val = (char *)Buf_GetAll(v->val, NULL);
 
+			if (str[1] == 'D') {
+			    val = VarModify(ctxt, &parsestate, val, VarHead,
+					    (ClientData)0);
+			} else {
+			    val = VarModify(ctxt, &parsestate, val, VarTail,
+					    (ClientData)0);
+			}
 			/*
-			 * SYSV modifications happen through the whole
-			 * string. Note the pattern is anchored at the end.
+			 * Resulting string is dynamically allocated, so
+			 * tell caller to free it.
 			 */
-			termc = *--cp;
-			delim = '\0';
-			newStr = VarModify(ctxt, &parsestate, nstr,
-					   VarSYSVMatch,
-					   (ClientData)&pattern);
-			free(UNCONST(pattern.lhs));
-			free(UNCONST(pattern.rhs));
-		    } else
-#endif
-		    {
-			Error("Unknown modifier '%c'", *tstr);
-			for (cp = tstr+1;
-			     *cp != ':' && *cp != endc && *cp != '\0';
-			     cp++)
-				 continue;
-			termc = *cp;
-			newStr = var_Error;
+			*freePtr = val;
+			*lengthPtr = tstr-start+1;
+			*WR(tstr) = endc;
+			Buf_Destroy(buf, TRUE);
+			VarFreeEnv(v, TRUE);
+			return(val);
 		    }
+		    break;
 		}
 	    }
-	    if (DEBUG(VAR)) {
-		printf("Result is \"%s\"\n", newStr);
-	    }
+	}
 
-	    if (newStr != nstr) {
-		if (*freePtr) {
-		    free(nstr);
+	if (v == (Var *)NIL) {
+	    if (((vlen == 1) ||
+		 (((vlen == 2) && (str[1] == 'F' ||
+					 str[1] == 'D')))) &&
+		((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)))
+	    {
+		/*
+		 * If substituting a local variable in a non-local context,
+		 * assume it's for dynamic source stuff. We have to handle
+		 * this specially and return the longhand for the variable
+		 * with the dollar sign escaped so it makes it back to the
+		 * caller. Only four of the local variables are treated
+		 * specially as they are the only four that will be set
+		 * when dynamic sources are expanded.
+		 */
+		switch (*str) {
+		    case '@':
+		    case '%':
+		    case '*':
+		    case '!':
+			dynamic = TRUE;
+			break;
 		}
-		nstr = newStr;
-		if (nstr != var_Error && nstr != varNoError) {
-		    *freePtr = nstr;
-		} else {
-		    *freePtr = NULL;
+	    } else if ((vlen > 2) && (*str == '.') &&
+		       isupper((unsigned char) str[1]) &&
+		       ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)))
+	    {
+		int	len;
+
+		len = vlen - 1;
+		if ((strncmp(str, ".TARGET", len) == 0) ||
+		    (strncmp(str, ".ARCHIVE", len) == 0) ||
+		    (strncmp(str, ".PREFIX", len) == 0) ||
+		    (strncmp(str, ".MEMBER", len) == 0))
+		{
+		    dynamic = TRUE;
 		}
 	    }
-	    if (termc == '\0') {
-		Error("Unclosed variable specification for %s", v->name);
-	    } else if (termc == ':') {
-		*WR(cp) = termc;
-		cp++;
+
+	    if (!haveModifier) {
+		/*
+		 * No modifiers -- have specification length so we can return
+		 * now.
+		 */
+		*lengthPtr = tstr - start + 1;
+		*WR(tstr) = endc;
+		if (dynamic) {
+		    char *pstr = emalloc(*lengthPtr + 1);
+		    strncpy(pstr, start, *lengthPtr);
+		    pstr[*lengthPtr] = '\0';
+		    *freePtr = pstr;
+		    Buf_Destroy(buf, TRUE);
+		    return(pstr);
+		} else {
+		    Buf_Destroy(buf, TRUE);
+		    return (err ? var_Error : varNoError);
+		}
 	    } else {
-		*WR(cp) = termc;
+		/*
+		 * Still need to get to the end of the variable specification,
+		 * so kludge up a Var structure for the modifications
+		 */
+		v = emalloc(sizeof(Var));
+		v->name = UNCONST(str);
+		v->val = Buf_Init(1);
+		v->flags = VAR_JUNK;
+		Buf_Destroy(buf, FALSE);
 	    }
-	    tstr = cp;
-	}
+	} else
+	    Buf_Destroy(buf, TRUE);
+    }
+
+
+    if (v->flags & VAR_IN_USE) {
+	Fatal("Variable %s is recursive.", v->name);
+	/*NOTREACHED*/
+    } else {
+	v->flags |= VAR_IN_USE;
+    }
+    /*
+     * Before doing any modification, we have to make sure the value
+     * has been fully expanded. If it looks like recursion might be
+     * necessary (there's a dollar sign somewhere in the variable's value)
+     * we just call Var_Subst to do any other substitutions that are
+     * necessary. Note that the value returned by Var_Subst will have
+     * been dynamically-allocated, so it will need freeing when we
+     * return.
+     */
+    nstr = (char *)Buf_GetAll(v->val, NULL);
+    if (strchr(nstr, '$') != NULL) {
+	nstr = Var_Subst(NULL, nstr, ctxt, err);
+	*freePtr = nstr;
+    }
+
+    v->flags &= ~VAR_IN_USE;
+
+    if ((nstr != NULL) && haveModifier) {
+	int used;
+	/*
+	 * Skip initial colon while putting it back.
+	 */
+	*WR(tstr) = ':';
+	tstr++;
+
+	nstr = ApplyModifiers(nstr, tstr, startc, endc,
+			      v, ctxt, err, &used, freePtr);
+	tstr += used;
 	*lengthPtr = tstr - start + 1;
     } else {
 	*lengthPtr = tstr - start + 1;
@@ -3246,8 +3313,8 @@ Var_Parse(const char *str, GNode *ctxt, 
 	if (!(v->flags & VAR_KEEP)) {
 	    if (*freePtr) {
 		free(nstr);
+		*freePtr = NULL;
 	    }
-	    *freePtr = NULL;
 	    if (dynamic) {
 		nstr = emalloc(*lengthPtr + 1);
 		strncpy(nstr, start, *lengthPtr);
@@ -3262,32 +3329,7 @@ Var_Parse(const char *str, GNode *ctxt, 
 	free(v->name);
 	free(v);
     }
-    /*
-     * XXX: If we need to free tstr2 - tstr is  no longer valid either.
-     */
-    if (tstr2)
-	free(tstr2);
     return (nstr);
-
- bad_modifier:
-							/* "{(" */
-    Error("Bad modifier `:%.*s' for %s", (int)strcspn(tstr, ":)}"), tstr,
-	  v->name);
-
-cleanup:
-    *lengthPtr = cp - start + 1;
-    if (*freePtr) {
-	free(nstr);
-	*freePtr = NULL;
-    }
-    if (tstr2)
-	free(tstr2);
-    if (delim != '\0')
-	Error("Unclosed substitution for %s (%c missing)",
-	      v->name, delim);
-    if (v->flags & VAR_JUNK)
-	free(v);
-    return (var_Error);
 }
 
 /*-
Index: unit-tests/Makefile
===================================================================
RCS file: /cvsroot/src/usr.bin/make/unit-tests/Makefile,v
retrieving revision 1.19
diff -u -p -r1.19 Makefile
--- unit-tests/Makefile	26 Feb 2006 22:45:46 -0000	1.19
+++ unit-tests/Makefile	11 May 2006 00:15:40 -0000
@@ -22,6 +22,7 @@ SUBFILES= \
 	comment \
 	cond1 \
 	dotwait \
+	moderrs \
 	modmatch \
 	modmisc \
 	modorder \
Index: unit-tests/moderrs
===================================================================
RCS file: unit-tests/moderrs
diff -N unit-tests/moderrs
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ unit-tests/moderrs	11 May 2006 00:15:40 -0000
@@ -0,0 +1,36 @@
+# $Id$
+#
+# various modifier error tests
+
+VAR=TheVariable
+# incase we have to change it ;-)
+MOD_UNKN=Z
+MOD_TERM=S,V,v
+MOD_S:= ${MOD_TERM},
+
+all:	modunkn modunknV varterm vartermV modterm modtermV
+
+modunkn:
+	@echo "Expect: Unknown modifier 'Z'"
+	@echo "VAR:Z=${VAR:Z}"
+
+modunknV:
+	@echo "Expect: Unknown modifier 'Z'"
+	@echo "VAR:${MOD_UNKN}=${VAR:${MOD_UNKN}}"
+
+varterm:
+	@echo "Expect: Unclosed variable specification for VAR"
+	@echo VAR:S,V,v,=${VAR:S,V,v,
+
+vartermV:
+	@echo "Expect: Unclosed variable specification for VAR"
+	@echo VAR:${MOD_TERM},=${VAR:${MOD_S}
+
+modterm:
+	@echo "Expect: Unclosed substitution for VAR (, missing)"
+	@echo "and: Syntax error: Unterminated quoted string"
+	-@echo "VAR:S,V,v=${VAR:S,V,v}"
+
+modtermV:
+	@echo "Expect: Unclosed substitution for VAR (, missing)"
+	-@echo "VAR:${MOD_TERM}=${VAR:${MOD_TERM}}"
Index: unit-tests/modmisc
===================================================================
RCS file: /cvsroot/src/usr.bin/make/unit-tests/modmisc,v
retrieving revision 1.3
diff -u -p -r1.3 modmisc
--- unit-tests/modmisc	26 Feb 2006 22:40:50 -0000	1.3
+++ unit-tests/modmisc	11 May 2006 00:15:40 -0000
@@ -8,11 +8,26 @@ MOD_NODOT=S/:/ /g:N.:ts:
 # and decorate, note that $'s need to be doubled. Also note that 
 # the modifier_variable can be used with other modifiers.
 MOD_NODOTX=S/:/ /g:N.:@d@'$$d'@
+# another mod - pretend it is more interesting
+MOD_HOMES=S,/home/,/homes/,
+MOD_OPT=@d@$${exists($$d):?$$d:$${d:S,/usr,/opt,}}@
+MOD_SEP=S,:, ,g
 
-all:	modvar
+all:	modvar modvarloop
 
 modvar:
 	@echo "path='${path}'"
 	@echo "path='${path:${MOD_NODOT}}'"
 	@echo "path='${path:S,home,homes,:${MOD_NODOT}}'"
 	@echo "path=${path:${MOD_NODOTX}:ts:}"
+	@echo "path=${path:${MOD_HOMES}:${MOD_NODOTX}:ts:}"
+
+.for d in ${path:${MOD_SEP}:N.} /usr/xbin
+path_$d?= ${d:${MOD_OPT}:${MOD_HOMES}}/
+paths+= ${d:${MOD_OPT}:${MOD_HOMES}}
+.endfor
+
+modvarloop:
+	@echo "path_/usr/xbin=${path_/usr/xbin}"
+	@echo "paths=${paths}"
+	@echo "PATHS=${paths:tu}"
Index: unit-tests/test.exp
===================================================================
RCS file: /cvsroot/src/usr.bin/make/unit-tests/test.exp,v
retrieving revision 1.17
diff -u -p -r1.17 test.exp
--- unit-tests/test.exp	26 Feb 2006 22:45:46 -0000	1.17
+++ unit-tests/test.exp	11 May 2006 00:15:40 -0000
@@ -49,6 +49,25 @@ make: Graph cycles through `cycle.2.98'
 make: Graph cycles through `cycle.2.97'
 cycle.1.99
 cycle.1.99
+Expect: Unknown modifier 'Z'
+make: Unknown modifier 'Z'
+VAR:Z=
+Expect: Unknown modifier 'Z'
+make: Unknown modifier 'Z'
+VAR:Z=
+Expect: Unclosed variable specification for VAR
+make: Unclosed variable specification for VAR
+VAR:S,V,v,=Thevariable
+Expect: Unclosed variable specification for VAR
+VAR:S,V,v,=Thevariable
+Expect: Unclosed substitution for VAR (, missing)
+and: Syntax error: Unterminated quoted string
+make: Unclosed substitution for VAR (, missing)
+Syntax error: Unterminated quoted string
+Expect: Unclosed substitution for VAR (, missing)
+make: Unclosed substitution for VAR (, missing)
+VAR:S,V,v=
+*** Error code 2 (ignored)
 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"
@@ -68,6 +87,10 @@ path=':/bin:/usr/bin::/sbin:/usr/sbin:.:
 path='/bin:/usr/bin:/sbin:/usr/sbin:/home/user/bin'
 path='/bin:/usr/bin:/sbin:/usr/sbin:/homes/user/bin'
 path='/bin':'/usr/bin':'/sbin':'/usr/sbin':'/home/user/bin'
+path='/bin':'/usr/bin':'/sbin':'/usr/sbin':'/homes/user/bin'
+path_/usr/xbin=/opt/xbin/
+paths=/bin /usr/bin /sbin /usr/sbin /homes/user/bin /opt/xbin
+PATHS=/BIN /USR/BIN /SBIN /USR/SBIN /HOMES/USER/BIN /OPT/XBIN
 LIST      = one two three four five six seven eigth nine ten
 LIST:O    = eigth five four nine one seven six ten three two
 # Note that 1 in every 10! trials two independently generated
