diff options
| author | sjg <sjg@NetBSD.org> | 2006-05-11 15:37:07 +0000 |
|---|---|---|
| committer | sjg <sjg@NetBSD.org> | 2006-05-11 15:37:07 +0000 |
| commit | 03cbcf6532e8656e0bc4bc53feaa8e593e42e14d (patch) | |
| tree | 812dfe26961e12b6af2a6cff5809aa382332a8f9 | |
| parent | 6daaa2e383ede7eab11f4f90169c2e2e204b6b4d (diff) | |
Extract the variable modifier logic to a separate function.
This cuts Var_Parse in half! and allows the modifier logic to
be used recursively - when getting modifiers via variables.
Add new unit-test, to check that certain error cases are handled
correctly.
| -rw-r--r-- | usr.bin/make/unit-tests/Makefile | 3 | ||||
| -rw-r--r-- | usr.bin/make/unit-tests/moderrs | 36 | ||||
| -rw-r--r-- | usr.bin/make/unit-tests/modmisc | 19 | ||||
| -rw-r--r-- | usr.bin/make/unit-tests/test.exp | 23 | ||||
| -rw-r--r-- | usr.bin/make/var.c | 2150 |
5 files changed, 1174 insertions, 1057 deletions
diff --git a/usr.bin/make/unit-tests/Makefile b/usr.bin/make/unit-tests/Makefile index ef05c8a7027..928892022c9 100644 --- a/usr.bin/make/unit-tests/Makefile +++ b/usr.bin/make/unit-tests/Makefile @@ -1,4 +1,4 @@ -# $NetBSD: Makefile,v 1.19 2006/02/26 22:45:46 apb Exp $ +# $NetBSD: Makefile,v 1.20 2006/05/11 15:37:07 sjg Exp $ # # Unit tests for make(1) # The main targets are: @@ -22,6 +22,7 @@ SUBFILES= \ comment \ cond1 \ dotwait \ + moderrs \ modmatch \ modmisc \ modorder \ diff --git a/usr.bin/make/unit-tests/moderrs b/usr.bin/make/unit-tests/moderrs new file mode 100644 index 00000000000..e8292e70c23 --- /dev/null +++ b/usr.bin/make/unit-tests/moderrs @@ -0,0 +1,36 @@ +# $Id: moderrs,v 1.1 2006/05/11 15:37:07 sjg Exp $ +# +# 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}}" diff --git a/usr.bin/make/unit-tests/modmisc b/usr.bin/make/unit-tests/modmisc index bc66711bb73..11f22eaed5a 100644 --- a/usr.bin/make/unit-tests/modmisc +++ b/usr.bin/make/unit-tests/modmisc @@ -1,4 +1,4 @@ -# $Id: modmisc,v 1.3 2006/02/26 22:40:50 wiz Exp $ +# $Id: modmisc,v 1.4 2006/05/11 15:37:07 sjg Exp $ # # miscellaneous modifier tests @@ -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}" diff --git a/usr.bin/make/unit-tests/test.exp b/usr.bin/make/unit-tests/test.exp index be5e203c326..84223d850fe 100644 --- a/usr.bin/make/unit-tests/test.exp +++ b/usr.bin/make/unit-tests/test.exp @@ -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:.:/home/user/bin:.' 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 diff --git a/usr.bin/make/var.c b/usr.bin/make/var.c index a670467b423..11f46ea47e1 100644 --- a/usr.bin/make/var.c +++ b/usr.bin/make/var.c @@ -1,4 +1,4 @@ -/* $NetBSD: var.c,v 1.107 2006/04/22 19:40:40 christos Exp $ */ +/* $NetBSD: var.c,v 1.108 2006/05/11 15:37:07 sjg Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -69,14 +69,14 @@ */ #ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: var.c,v 1.107 2006/04/22 19:40:40 christos Exp $"; +static char rcsid[] = "$NetBSD: var.c,v 1.108 2006/05/11 15:37:07 sjg Exp $"; #else #include <sys/cdefs.h> #ifndef lint #if 0 static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 3/19/94"; #else -__RCSID("$NetBSD: var.c,v 1.107 2006/04/22 19:40:40 christos Exp $"); +__RCSID("$NetBSD: var.c,v 1.108 2006/05/11 15:37:07 sjg Exp $"); #endif #endif /* not lint */ #endif @@ -1882,6 +1882,1094 @@ VarChangeCase(char *str, int upper) return str; } +/* + * 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. + */ + +static char * +ApplyModifiers(char *nstr, const char *tstr, + int startc, int endc, + Var *v, GNode *ctxt, Boolean err, + int *lengthPtr, void **freePtr) +{ + const char *start; + const char *cp; /* Secondary pointer into str (place marker + * for tstr) */ + 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 */ + char delim; + Var_Parse_State parsestate; /* Flags passed to helper functions */ + + delim = '\0'; + parsestate.oneBigWord = FALSE; + parsestate.varSpace = ' '; /* word separator */ + + start = cp = tstr; + + while (*tstr && *tstr != endc) { + + if (*tstr == '$') { + /* + * We have some complex modifiers in a variable. + */ + void *freeIt; + char *rval; + int rlen; + + rval = Var_Parse(tstr, ctxt, err, &rlen, &freeIt); + + if (DEBUG(VAR)) { + printf("Got '%s' from '%.*s'%.*s\n", + rval, rlen, tstr, rlen, tstr + rlen); + } + + tstr += rlen; + + if (rval != NULL && *rval) { + int used; + + 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 (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] == '?'))) { + /* + * "::=", "::!=", "::+=", or "::?=" + */ + GNode *v_ctxt; /* context where v belongs */ + const char *emsg; + char *sv_name; + VarPattern pattern; + int how; + + v_ctxt = ctxt; + sv_name = NULL; + ++tstr; + if (v->flags & VAR_JUNK) { + /* + * We need to strdup() it incase + * VarGetPattern() recurses. + */ + sv_name = v->name; + v->name = strdup(v->name); + } else if (ctxt != VAR_GLOBAL) { + if (VarFind(v->name, ctxt, 0) == (Var *)NIL) + v_ctxt = VAR_GLOBAL; + } + + switch ((how = *tstr)) { + case '+': + case '?': + case '!': + cp = &tstr[2]; + break; + default: + cp = ++tstr; + break; + } + delim = BRCLOSE; + pattern.flags = 0; + + pattern.rhs = VarGetPattern(ctxt, &parsestate, err, + &cp, delim, NULL, + &pattern.rightLen, + NULL); + if (v->flags & VAR_JUNK) { + /* restore original name */ + free(v->name); + v->name = sv_name; + } + if (pattern.rhs == NULL) + goto cleanup; + + termc = *--cp; + delim = '\0'; + + switch (how) { + case '+': + Var_Append(v->name, pattern.rhs, v_ctxt); + break; + case '!': + newStr = Cmd_Exec(pattern.rhs, &emsg); + if (emsg) + Error(emsg, nstr); + else + Var_Set(v->name, newStr, v_ctxt, 0); + if (newStr) + free(newStr); + break; + case '?': + if ((v->flags & VAR_JUNK) == 0) + break; + /* FALLTHROUGH */ + default: + Var_Set(v->name, pattern.rhs, v_ctxt, 0); + break; + } + free(UNCONST(pattern.rhs)); + newStr = var_Error; + 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'; + + 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] == '\\'))) + { + Buf_AddByte(buf, (Byte)cp[1]); + cp++; + } else if (*cp == '$') { + /* + * If unescaped dollar sign, assume it's a + * variable substitution and recurse. + */ + char *cp2; + int len; + void *freeIt; + + cp2 = Var_Parse(cp, ctxt, err, &len, &freeIt); + Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2); + if (freeIt) + free(freeIt); + cp += len - 1; + } else { + Buf_AddByte(buf, (Byte)*cp); + } + } + Buf_AddByte(buf, (Byte)'\0'); + + 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; + } + 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 '!': + { + 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; + + 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 { + /* 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); + } + termc = *cp; + free(estr); + break; + } else if (estr[0] == '*' && estr[1] == '\0') { + /* Found ":[*]" */ + 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; + free(estr); + break; + } else { + /* + * We expect estr to contain a single + * integer for :[N], or two integers + * separated by ".." for :[start..end]. + */ + char *ep; + + VarSelectWords_t seldata = { 0, 0 }; + + 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; + } + } 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); + 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; + } + + 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>". + */ + goto bad_modifier; + } + } else { + /* + * Found ":t<endc>" or ":t:". + */ + 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] == ':' || + cp[1] == endc || cp[1] == startc)) { + copy = TRUE; + cp++; + continue; + } + if (*cp == startc) + ++nest; + if (*cp == endc) { + --nest; + if (nest == 0) + 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++) + { + if ((*cp == '\\') && + (cp[1] == ':' || cp[1] == endc)) { + 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; + } + case 'S': + { + VarPattern pattern; + Var_Parse_State tmpparsestate; + + 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; + } + + 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; + } + + termc = *cp; + newStr = VarModify(ctxt, &tmpparsestate, nstr, + VarSubstitute, + (ClientData)&pattern); + + /* + * Free the two strings. + */ + free(UNCONST(pattern.lhs)); + free(UNCONST(pattern.rhs)); + delim = '\0'; + break; + } + case '?': + { + VarPattern pattern; + Boolean value; + + /* find ':', and then substitute accordingly */ + + pattern.flags = 0; + + 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)); + } 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; + } + + termc = *cp; + + 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; + } + + 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; + + 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; + + 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) { + + /* + * 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 + { + 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); + } + + 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); + + bad_modifier: + /* "{(" */ + Error("Bad modifier `:%.*s' for %s", (int)strcspn(tstr, ":)}"), tstr, + v->name); + + 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); +} + /*- *----------------------------------------------------------------------- * Var_Parse -- @@ -1915,21 +3003,14 @@ Var_Parse(const char *str, 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 *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 */ - 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 @@ -1942,7 +3023,6 @@ Var_Parse(const char *str, GNode *ctxt, Boolean err, int *lengthPtr, start = str; parsestate.oneBigWord = FALSE; parsestate.varSpace = ' '; /* word separator */ - tstr2 = NULL; if (str[1] != PROPEN && str[1] != BROPEN) { /* @@ -2194,1030 +3274,17 @@ Var_Parse(const char *str, GNode *ctxt, Boolean err, int *lengthPtr, 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) { + int used; /* * 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] == '?'))) { - /* - * "::=", "::!=", "::+=", or "::?=" - */ - GNode *v_ctxt; /* context where v belongs */ - const char *emsg; - char *sv_name; - VarPattern pattern; - int how; - - v_ctxt = ctxt; - sv_name = NULL; - ++tstr; - if (v->flags & VAR_JUNK) { - /* - * We need to strdup() it incase - * VarGetPattern() recurses. - */ - sv_name = v->name; - v->name = strdup(v->name); - } else if (ctxt != VAR_GLOBAL) { - if (VarFind(v->name, ctxt, 0) == (Var *)NIL) - v_ctxt = VAR_GLOBAL; - } - - switch ((how = *tstr)) { - case '+': - case '?': - case '!': - cp = &tstr[2]; - break; - default: - cp = ++tstr; - break; - } - delim = BRCLOSE; - pattern.flags = 0; - - pattern.rhs = VarGetPattern(ctxt, &parsestate, err, - &cp, delim, NULL, - &pattern.rightLen, - NULL); - if (v->flags & VAR_JUNK) { - /* restore original name */ - free(v->name); - v->name = sv_name; - } - if (pattern.rhs == NULL) - goto cleanup; - - termc = *--cp; - delim = '\0'; - - switch (how) { - case '+': - Var_Append(v->name, pattern.rhs, v_ctxt); - break; - case '!': - newStr = Cmd_Exec(pattern.rhs, &emsg); - if (emsg) - Error(emsg, nstr); - else - Var_Set(v->name, newStr, v_ctxt, 0); - if (newStr) - free(newStr); - break; - case '?': - if ((v->flags & VAR_JUNK) == 0) - break; - /* FALLTHROUGH */ - default: - Var_Set(v->name, pattern.rhs, v_ctxt, 0); - break; - } - free(UNCONST(pattern.rhs)); - newStr = var_Error; - 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'; - - 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] == '\\'))) - { - Buf_AddByte(buf, (Byte)cp[1]); - cp++; - } else if (*cp == '$') { - /* - * If unescaped dollar sign, assume it's a - * variable substitution and recurse. - */ - char *cp2; - int len; - void *freeIt; - - cp2 = Var_Parse(cp, ctxt, err, &len, &freeIt); - Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2); - if (freeIt) - free(freeIt); - cp += len - 1; - } else { - Buf_AddByte(buf, (Byte)*cp); - } - } - Buf_AddByte(buf, (Byte)'\0'); - - 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; - } - 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 '!': - { - 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; - - 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 { - /* 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); - } - termc = *cp; - free(estr); - break; - } else if (estr[0] == '*' && estr[1] == '\0') { - /* Found ":[*]" */ - 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; - free(estr); - break; - } else { - /* - * We expect estr to contain a single - * integer for :[N], or two integers - * separated by ".." for :[start..end]. - */ - char *ep; - - VarSelectWords_t seldata = { 0, 0 }; - 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; - } - } 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); - 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; - } - - 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>". - */ - goto bad_modifier; - } - } else { - /* - * Found ":t<endc>" or ":t:". - */ - 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] == ':' || - cp[1] == endc || cp[1] == startc)) { - copy = TRUE; - cp++; - continue; - } - if (*cp == startc) - ++nest; - if (*cp == endc) { - --nest; - if (nest == 0) - 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++) - { - if ((*cp == '\\') && - (cp[1] == ':' || cp[1] == endc)) { - 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; - } - case 'S': - { - VarPattern pattern; - Var_Parse_State tmpparsestate; - - 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; - } - - 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; - } - - termc = *cp; - newStr = VarModify(ctxt, &tmpparsestate, nstr, - VarSubstitute, - (ClientData)&pattern); - - /* - * Free the two strings. - */ - free(UNCONST(pattern.lhs)); - free(UNCONST(pattern.rhs)); - delim = '\0'; - break; - } - case '?': - { - VarPattern pattern; - Boolean value; - - /* find ':', and then substitute accordingly */ - - pattern.flags = 0; - - 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)); - } 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; - } - - termc = *cp; - - 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; - } - - 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; - - 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; - - 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) { - - /* - * 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 - { - 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); - } - - if (newStr != nstr) { - if (*freePtr) { - free(nstr); - } - nstr = newStr; - if (nstr != var_Error && nstr != varNoError) { - *freePtr = nstr; - } else { - *freePtr = NULL; - } - } - if (termc == '\0') { - Error("Unclosed variable specification for %s", v->name); - } else if (termc == ':') { - *WR(cp) = termc; - cp++; - } else { - *WR(cp) = termc; - } - tstr = cp; - } + 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, Boolean err, int *lengthPtr, 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, Boolean err, int *lengthPtr, 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); } /*- |
