diff options
Diffstat (limited to 'usr.bin/make/var.c')
-rw-r--r-- | usr.bin/make/var.c | 1986 |
1 files changed, 1986 insertions, 0 deletions
diff --git a/usr.bin/make/var.c b/usr.bin/make/var.c new file mode 100644 index 0000000..fc50a0f --- /dev/null +++ b/usr.bin/make/var.c @@ -0,0 +1,1986 @@ +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 3/19/94"; +#endif /* not lint */ + +/*- + * var.c -- + * Variable-handling functions + * + * Interface: + * Var_Set Set the value of a variable in the given + * context. The variable is created if it doesn't + * yet exist. The value and variable name need not + * be preserved. + * + * Var_Append Append more characters to an existing variable + * in the given context. The variable needn't + * exist already -- it will be created if it doesn't. + * A space is placed between the old value and the + * new one. + * + * Var_Exists See if a variable exists. + * + * Var_Value Return the value of a variable in a context or + * NULL if the variable is undefined. + * + * Var_Subst Substitute named variable, or all variables if + * NULL in a string using + * the given context as the top-most one. If the + * third argument is non-zero, Parse_Error is + * called if any variables are undefined. + * + * Var_Parse Parse a variable expansion from a string and + * return the result and the number of characters + * consumed. + * + * Var_Delete Delete a variable in a context. + * + * Var_Init Initialize this module. + * + * Debugging: + * Var_Dump Print out all variables defined in the given + * context. + * + * XXX: There's a lot of duplication in these functions. + */ + +#include <ctype.h> +#include "make.h" +#include "buf.h" + +/* + * This is a harmless return value for Var_Parse that can be used by Var_Subst + * to determine if there was an error in parsing -- easier than returning + * a flag, as things outside this module don't give a hoot. + */ +char var_Error[] = ""; + +/* + * Similar to var_Error, but returned when the 'err' flag for Var_Parse is + * set false. Why not just use a constant? Well, gcc likes to condense + * identical string instances... + */ +static char varNoError[] = ""; + +/* + * Internally, variables are contained in four different contexts. + * 1) the environment. They may not be changed. If an environment + * variable is appended-to, the result is placed in the global + * context. + * 2) the global context. Variables set in the Makefile are located in + * the global context. It is the penultimate context searched when + * substituting. + * 3) the command-line context. All variables set on the command line + * are placed in this context. They are UNALTERABLE once placed here. + * 4) the local context. Each target has associated with it a context + * list. On this list are located the structures describing such + * local variables as $(@) and $(*) + * The four contexts are searched in the reverse order from which they are + * listed. + */ +GNode *VAR_GLOBAL; /* variables from the makefile */ +GNode *VAR_CMD; /* variables defined on the command-line */ + +#define FIND_CMD 0x1 /* look in VAR_CMD when searching */ +#define FIND_GLOBAL 0x2 /* look in VAR_GLOBAL as well */ +#define FIND_ENV 0x4 /* look in the environment also */ + +typedef struct Var { + char *name; /* the variable's name */ + Buffer val; /* its value */ + int flags; /* miscellaneous status flags */ +#define VAR_IN_USE 1 /* Variable's value currently being used. + * Used to avoid recursion */ +#define VAR_FROM_ENV 2 /* Variable comes from the environment */ +#define VAR_JUNK 4 /* Variable is a junk variable that + * should be destroyed when done with + * it. Used by Var_Parse for undefined, + * modified variables */ +} Var; + +typedef struct { + char *lhs; /* String to match */ + int leftLen; /* Length of string */ + char *rhs; /* Replacement string (w/ &'s removed) */ + int rightLen; /* Length of replacement */ + int flags; +#define VAR_SUB_GLOBAL 1 /* Apply substitution globally */ +#define VAR_MATCH_START 2 /* Match at start of word */ +#define VAR_MATCH_END 4 /* Match at end of word */ +#define VAR_NO_SUB 8 /* Substitution is non-global and already done */ +} VarPattern; + +static int VarCmp __P((Var *, char *)); +static Var *VarFind __P((char *, GNode *, int)); +static void VarAdd __P((char *, char *, GNode *)); +static Boolean VarHead __P((char *, Boolean, Buffer)); +static Boolean VarTail __P((char *, Boolean, Buffer)); +static Boolean VarSuffix __P((char *, Boolean, Buffer)); +static Boolean VarRoot __P((char *, Boolean, Buffer)); +static Boolean VarMatch __P((char *, Boolean, Buffer, char *)); +static Boolean VarSYSVMatch __P((char *, Boolean, Buffer, VarPattern *)); +static Boolean VarNoMatch __P((char *, Boolean, Buffer, char *)); +static Boolean VarSubstitute __P((char *, Boolean, Buffer, VarPattern *)); +static char *VarModify __P((char *, Boolean (*modProc )(), ClientData)); +static int VarPrintVar __P((Var *)); + +/*- + *----------------------------------------------------------------------- + * VarCmp -- + * See if the given variable matches the named one. Called from + * Lst_Find when searching for a variable of a given name. + * + * Results: + * 0 if they match. non-zero otherwise. + * + * Side Effects: + * none + *----------------------------------------------------------------------- + */ +static int +VarCmp (v, name) + Var *v; /* VAR structure to compare */ + char *name; /* name to look for */ +{ + return (strcmp (name, v->name)); +} + +/*- + *----------------------------------------------------------------------- + * VarFind -- + * Find the given variable in the given context and any other contexts + * indicated. + * + * Results: + * A pointer to the structure describing the desired variable or + * NIL if the variable does not exist. + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static Var * +VarFind (name, ctxt, flags) + char *name; /* name to find */ + GNode *ctxt; /* context in which to find it */ + int flags; /* FIND_GLOBAL set means to look in the + * VAR_GLOBAL context as well. + * FIND_CMD set means to look in the VAR_CMD + * context also. + * FIND_ENV set means to look in the + * environment */ +{ + LstNode var; + Var *v; + + /* + * If the variable name begins with a '.', it could very well be one of + * the local ones. We check the name against all the local variables + * and substitute the short version in for 'name' if it matches one of + * them. + */ + if (*name == '.' && isupper(name[1])) + switch (name[1]) { + case 'A': + if (!strcmp(name, ".ALLSRC")) + name = ALLSRC; + if (!strcmp(name, ".ARCHIVE")) + name = ARCHIVE; + break; + case 'I': + if (!strcmp(name, ".IMPSRC")) + name = IMPSRC; + break; + case 'M': + if (!strcmp(name, ".MEMBER")) + name = MEMBER; + break; + case 'O': + if (!strcmp(name, ".OODATE")) + name = OODATE; + break; + case 'P': + if (!strcmp(name, ".PREFIX")) + name = PREFIX; + break; + case 'T': + if (!strcmp(name, ".TARGET")) + name = TARGET; + break; + } + /* + * First look for the variable in the given context. If it's not there, + * look for it in VAR_CMD, VAR_GLOBAL and the environment, in that order, + * depending on the FIND_* flags in 'flags' + */ + var = Lst_Find (ctxt->context, (ClientData)name, VarCmp); + + if ((var == NILLNODE) && (flags & FIND_CMD) && (ctxt != VAR_CMD)) { + var = Lst_Find (VAR_CMD->context, (ClientData)name, VarCmp); + } + if (!checkEnvFirst && (var == NILLNODE) && (flags & FIND_GLOBAL) && + (ctxt != VAR_GLOBAL)) + { + var = Lst_Find (VAR_GLOBAL->context, (ClientData)name, VarCmp); + } + if ((var == NILLNODE) && (flags & FIND_ENV)) { + char *env; + + if ((env = getenv (name)) != NULL) { + /* + * If the variable is found in the environment, we only duplicate + * its value (since eVarVal was allocated on the stack). The name + * doesn't need duplication since it's always in the environment + */ + int len; + + v = (Var *) emalloc(sizeof(Var)); + v->name = name; + + len = strlen(env); + + v->val = Buf_Init(len); + Buf_AddBytes(v->val, len, (Byte *)env); + + v->flags = VAR_FROM_ENV; + return (v); + } else if (checkEnvFirst && (flags & FIND_GLOBAL) && + (ctxt != VAR_GLOBAL)) + { + var = Lst_Find (VAR_GLOBAL->context, (ClientData)name, VarCmp); + if (var == NILLNODE) { + return ((Var *) NIL); + } else { + return ((Var *)Lst_Datum(var)); + } + } else { + return((Var *)NIL); + } + } else if (var == NILLNODE) { + return ((Var *) NIL); + } else { + return ((Var *) Lst_Datum (var)); + } +} + +/*- + *----------------------------------------------------------------------- + * VarAdd -- + * Add a new variable of name name and value val to the given context + * + * Results: + * None + * + * Side Effects: + * The new variable is placed at the front of the given context + * The name and val arguments are duplicated so they may + * safely be freed. + *----------------------------------------------------------------------- + */ +static void +VarAdd (name, val, ctxt) + char *name; /* name of variable to add */ + char *val; /* value to set it to */ + GNode *ctxt; /* context in which to set it */ +{ + register Var *v; + int len; + + v = (Var *) emalloc (sizeof (Var)); + + v->name = strdup (name); + + len = val ? strlen(val) : 0; + v->val = Buf_Init(len+1); + Buf_AddBytes(v->val, len, (Byte *)val); + + v->flags = 0; + + (void) Lst_AtFront (ctxt->context, (ClientData)v); + if (DEBUG(VAR)) { + printf("%s:%s = %s\n", ctxt->name, name, val); + } +} + +/*- + *----------------------------------------------------------------------- + * Var_Delete -- + * Remove a variable from a context. + * + * Results: + * None. + * + * Side Effects: + * The Var structure is removed and freed. + * + *----------------------------------------------------------------------- + */ +void +Var_Delete(name, ctxt) + char *name; + GNode *ctxt; +{ + LstNode ln; + + if (DEBUG(VAR)) { + printf("%s:delete %s\n", ctxt->name, name); + } + ln = Lst_Find(ctxt->context, (ClientData)name, VarCmp); + if (ln != NILLNODE) { + register Var *v; + + v = (Var *)Lst_Datum(ln); + Lst_Remove(ctxt->context, ln); + Buf_Destroy(v->val, TRUE); + free(v->name); + free((char *)v); + } +} + +/*- + *----------------------------------------------------------------------- + * Var_Set -- + * Set the variable name to the value val in the given context. + * + * Results: + * None. + * + * Side Effects: + * If the variable doesn't yet exist, a new record is created for it. + * Else the old value is freed and the new one stuck in its place + * + * Notes: + * The variable is searched for only in its context before being + * created in that context. I.e. if the context is VAR_GLOBAL, + * only VAR_GLOBAL->context is searched. Likewise if it is VAR_CMD, only + * VAR_CMD->context is searched. This is done to avoid the literally + * thousands of unnecessary strcmp's that used to be done to + * set, say, $(@) or $(<). + *----------------------------------------------------------------------- + */ +void +Var_Set (name, val, ctxt) + char *name; /* name of variable to set */ + char *val; /* value to give to the variable */ + GNode *ctxt; /* context in which to set it */ +{ + register Var *v; + + /* + * We only look for a variable in the given context since anything set + * here will override anything in a lower context, so there's not much + * point in searching them all just to save a bit of memory... + */ + v = VarFind (name, ctxt, 0); + if (v == (Var *) NIL) { + VarAdd (name, val, ctxt); + } else { + Buf_Discard(v->val, Buf_Size(v->val)); + Buf_AddBytes(v->val, strlen(val), (Byte *)val); + + if (DEBUG(VAR)) { + printf("%s:%s = %s\n", ctxt->name, name, val); + } + } + /* + * Any variables given on the command line are automatically exported + * to the environment (as per POSIX standard) + */ + if (ctxt == VAR_CMD) { + setenv(name, val, 1); + } +} + +/*- + *----------------------------------------------------------------------- + * Var_Append -- + * The variable of the given name has the given value appended to it in + * the given context. + * + * Results: + * None + * + * Side Effects: + * If the variable doesn't exist, it is created. Else the strings + * are concatenated (with a space in between). + * + * Notes: + * Only if the variable is being sought in the global context is the + * environment searched. + * XXX: Knows its calling circumstances in that if called with ctxt + * an actual target, it will only search that context since only + * a local variable could be being appended to. This is actually + * a big win and must be tolerated. + *----------------------------------------------------------------------- + */ +void +Var_Append (name, val, ctxt) + char *name; /* Name of variable to modify */ + char *val; /* String to append to it */ + GNode *ctxt; /* Context in which this should occur */ +{ + register Var *v; + + v = VarFind (name, ctxt, (ctxt == VAR_GLOBAL) ? FIND_ENV : 0); + + if (v == (Var *) NIL) { + VarAdd (name, val, ctxt); + } else { + Buf_AddByte(v->val, (Byte)' '); + Buf_AddBytes(v->val, strlen(val), (Byte *)val); + + if (DEBUG(VAR)) { + printf("%s:%s = %s\n", ctxt->name, name, + (char *) Buf_GetAll(v->val, (int *)NULL)); + } + + if (v->flags & VAR_FROM_ENV) { + /* + * If the original variable came from the environment, we + * have to install it in the global context (we could place + * it in the environment, but then we should provide a way to + * export other variables...) + */ + v->flags &= ~VAR_FROM_ENV; + Lst_AtFront(ctxt->context, (ClientData)v); + } + } +} + +/*- + *----------------------------------------------------------------------- + * Var_Exists -- + * See if the given variable exists. + * + * Results: + * TRUE if it does, FALSE if it doesn't + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +Boolean +Var_Exists(name, ctxt) + char *name; /* Variable to find */ + GNode *ctxt; /* Context in which to start search */ +{ + Var *v; + + v = VarFind(name, ctxt, FIND_CMD|FIND_GLOBAL|FIND_ENV); + + if (v == (Var *)NIL) { + return(FALSE); + } else if (v->flags & VAR_FROM_ENV) { + Buf_Destroy(v->val, TRUE); + free((char *)v); + } + return(TRUE); +} + +/*- + *----------------------------------------------------------------------- + * Var_Value -- + * Return the value of the named variable in the given context + * + * Results: + * The value if the variable exists, NULL if it doesn't + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +char * +Var_Value (name, ctxt) + char *name; /* name to find */ + GNode *ctxt; /* context in which to search for it */ +{ + Var *v; + + v = VarFind (name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); + if (v != (Var *) NIL) { + return ((char *)Buf_GetAll(v->val, (int *)NULL)); + } else { + return ((char *) NULL); + } +} + +/*- + *----------------------------------------------------------------------- + * VarHead -- + * Remove the tail of the given word and place the result in the given + * buffer. + * + * Results: + * TRUE if characters were added to the buffer (a space needs to be + * added to the buffer before the next word). + * + * Side Effects: + * The trimmed word is added to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarHead (word, addSpace, buf) + char *word; /* Word to trim */ + Boolean addSpace; /* True if need to add a space to the buffer + * before sticking in the head */ + Buffer buf; /* Buffer in which to store it */ +{ + register char *slash; + + slash = strrchr (word, '/'); + if (slash != (char *)NULL) { + if (addSpace) { + Buf_AddByte (buf, (Byte)' '); + } + *slash = '\0'; + Buf_AddBytes (buf, strlen (word), (Byte *)word); + *slash = '/'; + return (TRUE); + } else { + /* + * If no directory part, give . (q.v. the POSIX standard) + */ + if (addSpace) { + Buf_AddBytes(buf, 2, (Byte *)" ."); + } else { + Buf_AddByte(buf, (Byte)'.'); + } + return(TRUE); + } +} + +/*- + *----------------------------------------------------------------------- + * VarTail -- + * Remove the head of the given word and place the result in the given + * buffer. + * + * Results: + * TRUE if characters were added to the buffer (a space needs to be + * added to the buffer before the next word). + * + * Side Effects: + * The trimmed word is added to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarTail (word, addSpace, buf) + char *word; /* Word to trim */ + Boolean addSpace; /* TRUE if need to stick a space in the + * buffer before adding the tail */ + Buffer buf; /* Buffer in which to store it */ +{ + register char *slash; + + if (addSpace) { + Buf_AddByte (buf, (Byte)' '); + } + + slash = strrchr (word, '/'); + if (slash != (char *)NULL) { + *slash++ = '\0'; + Buf_AddBytes (buf, strlen(slash), (Byte *)slash); + slash[-1] = '/'; + } else { + Buf_AddBytes (buf, strlen(word), (Byte *)word); + } + return (TRUE); +} + +/*- + *----------------------------------------------------------------------- + * VarSuffix -- + * Place the suffix of the given word in the given buffer. + * + * Results: + * TRUE if characters were added to the buffer (a space needs to be + * added to the buffer before the next word). + * + * Side Effects: + * The suffix from the word is placed in the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarSuffix (word, addSpace, buf) + char *word; /* Word to trim */ + Boolean addSpace; /* TRUE if need to add a space before placing + * the suffix in the buffer */ + Buffer buf; /* Buffer in which to store it */ +{ + register char *dot; + + dot = strrchr (word, '.'); + if (dot != (char *)NULL) { + if (addSpace) { + Buf_AddByte (buf, (Byte)' '); + } + *dot++ = '\0'; + Buf_AddBytes (buf, strlen (dot), (Byte *)dot); + dot[-1] = '.'; + return (TRUE); + } else { + return (addSpace); + } +} + +/*- + *----------------------------------------------------------------------- + * VarRoot -- + * Remove the suffix of the given word and place the result in the + * buffer. + * + * Results: + * TRUE if characters were added to the buffer (a space needs to be + * added to the buffer before the next word). + * + * Side Effects: + * The trimmed word is added to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarRoot (word, addSpace, buf) + char *word; /* Word to trim */ + Boolean addSpace; /* TRUE if need to add a space to the buffer + * before placing the root in it */ + Buffer buf; /* Buffer in which to store it */ +{ + register char *dot; + + if (addSpace) { + Buf_AddByte (buf, (Byte)' '); + } + + dot = strrchr (word, '.'); + if (dot != (char *)NULL) { + *dot = '\0'; + Buf_AddBytes (buf, strlen (word), (Byte *)word); + *dot = '.'; + } else { + Buf_AddBytes (buf, strlen(word), (Byte *)word); + } + return (TRUE); +} + +/*- + *----------------------------------------------------------------------- + * VarMatch -- + * Place the word in the buffer if it matches the given pattern. + * Callback function for VarModify to implement the :M modifier. + * + * Results: + * TRUE if a space should be placed in the buffer before the next + * word. + * + * Side Effects: + * The word may be copied to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarMatch (word, addSpace, buf, pattern) + char *word; /* Word to examine */ + Boolean addSpace; /* TRUE if need to add a space to the + * buffer before adding the word, if it + * matches */ + Buffer buf; /* Buffer in which to store it */ + char *pattern; /* Pattern the word must match */ +{ + if (Str_Match(word, pattern)) { + if (addSpace) { + Buf_AddByte(buf, (Byte)' '); + } + addSpace = TRUE; + Buf_AddBytes(buf, strlen(word), (Byte *)word); + } + return(addSpace); +} + + + +/*- + *----------------------------------------------------------------------- + * VarSYSVMatch -- + * Place the word in the buffer if it matches the given pattern. + * Callback function for VarModify to implement the System V % + * modifiers. + * + * Results: + * TRUE if a space should be placed in the buffer before the next + * word. + * + * Side Effects: + * The word may be copied to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarSYSVMatch (word, addSpace, buf, pat) + char *word; /* Word to examine */ + Boolean addSpace; /* TRUE if need to add a space to the + * buffer before adding the word, if it + * matches */ + Buffer buf; /* Buffer in which to store it */ + VarPattern *pat; /* Pattern the word must match */ +{ + int len; + char *ptr; + + if (addSpace) + Buf_AddByte(buf, (Byte)' '); + + addSpace = TRUE; + + if ((ptr = Str_SYSVMatch(word, pat->lhs, &len)) != NULL) + Str_SYSVSubst(buf, pat->rhs, ptr, len); + else + Buf_AddBytes(buf, strlen(word), (Byte *) word); + + return(addSpace); +} + + +/*- + *----------------------------------------------------------------------- + * VarNoMatch -- + * Place the word in the buffer if it doesn't match the given pattern. + * Callback function for VarModify to implement the :N modifier. + * + * Results: + * TRUE if a space should be placed in the buffer before the next + * word. + * + * Side Effects: + * The word may be copied to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarNoMatch (word, addSpace, buf, pattern) + char *word; /* Word to examine */ + Boolean addSpace; /* TRUE if need to add a space to the + * buffer before adding the word, if it + * matches */ + Buffer buf; /* Buffer in which to store it */ + char *pattern; /* Pattern the word must match */ +{ + if (!Str_Match(word, pattern)) { + if (addSpace) { + Buf_AddByte(buf, (Byte)' '); + } + addSpace = TRUE; + Buf_AddBytes(buf, strlen(word), (Byte *)word); + } + return(addSpace); +} + + +/*- + *----------------------------------------------------------------------- + * VarSubstitute -- + * Perform a string-substitution on the given word, placing the + * result in the passed buffer. + * + * Results: + * TRUE if a space is needed before more characters are added. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarSubstitute (word, addSpace, buf, pattern) + char *word; /* Word to modify */ + Boolean addSpace; /* True if space should be added before + * other characters */ + Buffer buf; /* Buffer for result */ + register VarPattern *pattern; /* Pattern for substitution */ +{ + register int wordLen; /* Length of word */ + register char *cp; /* General pointer */ + + wordLen = strlen(word); + if ((pattern->flags & VAR_NO_SUB) == 0) { + /* + * Still substituting -- break it down into simple anchored cases + * and if none of them fits, perform the general substitution case. + */ + if ((pattern->flags & VAR_MATCH_START) && + (strncmp(word, pattern->lhs, pattern->leftLen) == 0)) { + /* + * Anchored at start and beginning of word matches pattern + */ + if ((pattern->flags & VAR_MATCH_END) && + (wordLen == pattern->leftLen)) { + /* + * Also anchored at end and matches to the end (word + * is same length as pattern) add space and rhs only + * if rhs is non-null. + */ + if (pattern->rightLen != 0) { + if (addSpace) { + Buf_AddByte(buf, (Byte)' '); + } + addSpace = TRUE; + Buf_AddBytes(buf, pattern->rightLen, + (Byte *)pattern->rhs); + } + } else if (pattern->flags & VAR_MATCH_END) { + /* + * Doesn't match to end -- copy word wholesale + */ + goto nosub; + } else { + /* + * Matches at start but need to copy in trailing characters + */ + if ((pattern->rightLen + wordLen - pattern->leftLen) != 0){ + if (addSpace) { + Buf_AddByte(buf, (Byte)' '); + } + addSpace = TRUE; + } + Buf_AddBytes(buf, pattern->rightLen, (Byte *)pattern->rhs); + Buf_AddBytes(buf, wordLen - pattern->leftLen, + (Byte *)(word + pattern->leftLen)); + } + } else if (pattern->flags & VAR_MATCH_START) { + /* + * Had to match at start of word and didn't -- copy whole word. + */ + goto nosub; + } else if (pattern->flags & VAR_MATCH_END) { + /* + * Anchored at end, Find only place match could occur (leftLen + * characters from the end of the word) and see if it does. Note + * that because the $ will be left at the end of the lhs, we have + * to use strncmp. + */ + cp = word + (wordLen - pattern->leftLen); + if ((cp >= word) && + (strncmp(cp, pattern->lhs, pattern->leftLen) == 0)) { + /* + * Match found. If we will place characters in the buffer, + * add a space before hand as indicated by addSpace, then + * stuff in the initial, unmatched part of the word followed + * by the right-hand-side. + */ + if (((cp - word) + pattern->rightLen) != 0) { + if (addSpace) { + Buf_AddByte(buf, (Byte)' '); + } + addSpace = TRUE; + } + Buf_AddBytes(buf, cp - word, (Byte *)word); + Buf_AddBytes(buf, pattern->rightLen, (Byte *)pattern->rhs); + } else { + /* + * Had to match at end and didn't. Copy entire word. + */ + goto nosub; + } + } else { + /* + * Pattern is unanchored: search for the pattern in the word using + * String_FindSubstring, copying unmatched portions and the + * right-hand-side for each match found, handling non-global + * subsititutions correctly, etc. When the loop is done, any + * remaining part of the word (word and wordLen are adjusted + * accordingly through the loop) is copied straight into the + * buffer. + * addSpace is set FALSE as soon as a space is added to the + * buffer. + */ + register Boolean done; + int origSize; + + done = FALSE; + origSize = Buf_Size(buf); + while (!done) { + cp = Str_FindSubstring(word, pattern->lhs); + if (cp != (char *)NULL) { + if (addSpace && (((cp - word) + pattern->rightLen) != 0)){ + Buf_AddByte(buf, (Byte)' '); + addSpace = FALSE; + } + Buf_AddBytes(buf, cp-word, (Byte *)word); + Buf_AddBytes(buf, pattern->rightLen, (Byte *)pattern->rhs); + wordLen -= (cp - word) + pattern->leftLen; + word = cp + pattern->leftLen; + if (wordLen == 0) { + done = TRUE; + } + if ((pattern->flags & VAR_SUB_GLOBAL) == 0) { + done = TRUE; + pattern->flags |= VAR_NO_SUB; + } + } else { + done = TRUE; + } + } + if (wordLen != 0) { + if (addSpace) { + Buf_AddByte(buf, (Byte)' '); + } + Buf_AddBytes(buf, wordLen, (Byte *)word); + } + /* + * If added characters to the buffer, need to add a space + * before we add any more. If we didn't add any, just return + * the previous value of addSpace. + */ + return ((Buf_Size(buf) != origSize) || addSpace); + } + /* + * Common code for anchored substitutions: if performed a substitution + * and it's not supposed to be global, mark the pattern as requiring + * no more substitutions. addSpace was set TRUE if characters were + * added to the buffer. + */ + if ((pattern->flags & VAR_SUB_GLOBAL) == 0) { + pattern->flags |= VAR_NO_SUB; + } + return (addSpace); + } + nosub: + if (addSpace) { + Buf_AddByte(buf, (Byte)' '); + } + Buf_AddBytes(buf, wordLen, (Byte *)word); + return(TRUE); +} + +/*- + *----------------------------------------------------------------------- + * VarModify -- + * Modify each of the words of the passed string using the given + * function. Used to implement all modifiers. + * + * Results: + * A string of all the words modified appropriately. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static char * +VarModify (str, modProc, datum) + char *str; /* String whose words should be trimmed */ + Boolean (*modProc)(); /* Function to use to modify them */ + ClientData datum; /* Datum to pass it */ +{ + Buffer buf; /* Buffer for the new string */ + register char *cp; /* Pointer to end of current word */ + char endc; /* Character that ended the word */ + Boolean addSpace; /* TRUE if need to add a space to the + * buffer before adding the trimmed + * word */ + + buf = Buf_Init (0); + cp = str; + addSpace = FALSE; + + for (;;) { + /* + * Skip to next word and place cp at its end. + */ + while (isspace (*str)) { + str++; + } + for (cp = str; *cp != '\0' && !isspace (*cp); cp++) + continue; + if (cp == str) { + /* + * If we didn't go anywhere, we must be done! + */ + Buf_AddByte (buf, '\0'); + str = (char *)Buf_GetAll (buf, (int *)NULL); + Buf_Destroy (buf, FALSE); + return (str); + } + /* + * Nuke terminating character, but save it in endc b/c if str was + * some variable's value, it would not be good to screw it + * over... + */ + endc = *cp; + *cp = '\0'; + + addSpace = (* modProc) (str, addSpace, buf, datum); + + if (endc) { + *cp++ = endc; + } + str = cp; + } +} + +/*- + *----------------------------------------------------------------------- + * Var_Parse -- + * Given the start of a variable invocation, extract the variable + * name and find its value, then modify it according to the + * specification. + * + * 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. + * + *----------------------------------------------------------------------- + */ +char * +Var_Parse (str, ctxt, err, lengthPtr, freePtr) + char *str; /* The string to parse */ + GNode *ctxt; /* The context for the variable */ + Boolean err; /* TRUE if undefined variables are an error */ + int *lengthPtr; /* OUT: The length of the specification */ + Boolean *freePtr; /* OUT: TRUE if caller should free result */ +{ + register char *tstr; /* Pointer into str */ + Var *v; /* Variable in invocation */ + register char *cp; /* Secondary pointer into str (place marker + * for tstr) */ + Boolean haveModifier;/* TRUE if have modifiers for the variable */ + register char endc; /* Ending character when variable in parens + * or braces */ + char *start; + 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 */ + + *freePtr = FALSE; + dynamic = FALSE; + start = str; + + if (str[1] != '(' && str[1] != '{') { + /* + * 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("$(.TARGET)"); + case '%': + return("$(.ARCHIVE)"); + case '*': + return("$(.PREFIX)"); + case '!': + return("$(.MEMBER)"); + } + } + /* + * Error + */ + return (err ? var_Error : varNoError); + } else { + haveModifier = FALSE; + tstr = &str[1]; + endc = str[1]; + } + } else { + endc = str[1] == '(' ? ')' : '}'; + + /* + * Skip to the end character or a colon, whichever comes first. + */ + for (tstr = str + 2; + *tstr != '\0' && *tstr != endc && *tstr != ':'; + tstr++) + { + continue; + } + 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); + } + *tstr = '\0'; + + v = VarFind (str + 2, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); + if ((v == (Var *)NIL) && (ctxt != VAR_CMD) && (ctxt != VAR_GLOBAL) && + ((tstr-str) == 4) && (str[3] == 'F' || str[3] == '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[2]) { + case '@': + case '%': + case '*': + case '!': + case '>': + case '<': + { + char vname[2]; + char *val; + + /* + * Well, it's local -- go look for it. + */ + vname[0] = str[2]; + 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, (int *)NULL); + + if (str[3] == 'D') { + val = VarModify(val, VarHead, (ClientData)0); + } else { + val = VarModify(val, VarTail, (ClientData)0); + } + /* + * Resulting string is dynamically allocated, so + * tell caller to free it. + */ + *freePtr = TRUE; + *lengthPtr = tstr-start+1; + *tstr = endc; + return(val); + } + break; + } + } + } + + if (v == (Var *)NIL) { + if ((((tstr-str) == 3) || + ((((tstr-str) == 4) && (str[3] == 'F' || + str[3] == '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[2]) { + case '@': + case '%': + case '*': + case '!': + dynamic = TRUE; + break; + } + } else if (((tstr-str) > 4) && (str[2] == '.') && + isupper(str[3]) && + ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL))) + { + int len; + + len = (tstr-str) - 3; + if ((strncmp(str+2, ".TARGET", len) == 0) || + (strncmp(str+2, ".ARCHIVE", len) == 0) || + (strncmp(str+2, ".PREFIX", len) == 0) || + (strncmp(str+2, ".MEMBER", len) == 0)) + { + dynamic = TRUE; + } + } + + if (!haveModifier) { + /* + * No modifiers -- have specification length so we can return + * now. + */ + *lengthPtr = tstr - start + 1; + *tstr = endc; + if (dynamic) { + str = emalloc(*lengthPtr + 1); + strncpy(str, start, *lengthPtr); + str[*lengthPtr] = '\0'; + *freePtr = TRUE; + return(str); + } else { + 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 = (Var *) emalloc(sizeof(Var)); + v->name = &str[1]; + v->val = Buf_Init(1); + v->flags = VAR_JUNK; + } + } + } + + 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. + */ + str = (char *)Buf_GetAll(v->val, (int *)NULL); + if (strchr (str, '$') != (char *)NULL) { + str = Var_Subst(NULL, str, ctxt, err); + *freePtr = TRUE; + } + + 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. + * :S<d><pat1><d><pat2><d>[g] + * Substitute <pat2> for <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). + * :lhs=rhs Like :S, but the rhs goes to the end of + * the invocation. + */ + if ((str != (char *)NULL) && haveModifier) { + /* + * Skip initial colon while putting it back. + */ + *tstr++ = ':'; + while (*tstr != endc) { + char *newStr; /* New value to return */ + char termc; /* Character which terminated scan */ + + if (DEBUG(VAR)) { + printf("Applying :%c to \"%s\"\n", *tstr, str); + } + switch (*tstr) { + case 'N': + case 'M': + { + char *pattern; + char *cp2; + Boolean copy; + + copy = FALSE; + for (cp = tstr + 1; + *cp != '\0' && *cp != ':' && *cp != endc; + cp++) + { + if (*cp == '\\' && (cp[1] == ':' || cp[1] == endc)){ + copy = TRUE; + cp++; + } + } + termc = *cp; + *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 = &tstr[1]; + } + if (*tstr == 'M' || *tstr == 'm') { + newStr = VarModify(str, VarMatch, (ClientData)pattern); + } else { + newStr = VarModify(str, VarNoMatch, + (ClientData)pattern); + } + if (copy) { + free(pattern); + } + break; + } + case 'S': + { + VarPattern pattern; + register char delim; + Buffer buf; /* Buffer for patterns */ + + pattern.flags = 0; + 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; + } + + buf = Buf_Init(0); + + /* + * Pass through the lhs 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. + */ + for (cp = tstr; *cp != '\0' && *cp != delim; cp++) { + if ((*cp == '\\') && + ((cp[1] == delim) || + (cp[1] == '$') || + (cp[1] == '\\'))) + { + Buf_AddByte(buf, (Byte)cp[1]); + cp++; + } else if (*cp == '$') { + if (cp[1] != delim) { + /* + * If unescaped dollar sign not before the + * delimiter, assume it's a variable + * substitution and recurse. + */ + char *cp2; + int len; + Boolean freeIt; + + cp2 = Var_Parse(cp, ctxt, err, &len, &freeIt); + Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2); + if (freeIt) { + free(cp2); + } + cp += len - 1; + } else { + /* + * Unescaped $ at end of pattern => anchor + * pattern at end. + */ + pattern.flags |= VAR_MATCH_END; + } + } else { + Buf_AddByte(buf, (Byte)*cp); + } + } + + Buf_AddByte(buf, (Byte)'\0'); + + /* + * If lhs didn't end with the delimiter, complain and + * return NULL + */ + if (*cp != delim) { + *lengthPtr = cp - start + 1; + if (*freePtr) { + free(str); + } + Buf_Destroy(buf, TRUE); + Error("Unclosed substitution for %s (%c missing)", + v->name, delim); + return (var_Error); + } + + /* + * Fetch pattern and destroy buffer, but preserve the data + * in it, since that's our lhs. Note that Buf_GetAll + * will return the actual number of bytes, which includes + * the null byte, so we have to decrement the length by + * one. + */ + pattern.lhs = (char *)Buf_GetAll(buf, &pattern.leftLen); + pattern.leftLen--; + Buf_Destroy(buf, FALSE); + + /* + * Now comes the replacement string. Three things need to + * be done here: 1) need to compress escaped delimiters and + * ampersands and 2) need to replace unescaped ampersands + * with the l.h.s. (since this isn't regexp, we can do + * it right here) and 3) expand any variable substitutions. + */ + buf = Buf_Init(0); + + tstr = cp + 1; + for (cp = tstr; *cp != '\0' && *cp != delim; cp++) { + if ((*cp == '\\') && + ((cp[1] == delim) || + (cp[1] == '&') || + (cp[1] == '\\') || + (cp[1] == '$'))) + { + Buf_AddByte(buf, (Byte)cp[1]); + cp++; + } else if ((*cp == '$') && (cp[1] != delim)) { + char *cp2; + int len; + Boolean freeIt; + + cp2 = Var_Parse(cp, ctxt, err, &len, &freeIt); + Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2); + cp += len - 1; + if (freeIt) { + free(cp2); + } + } else if (*cp == '&') { + Buf_AddBytes(buf, pattern.leftLen, + (Byte *)pattern.lhs); + } else { + Buf_AddByte(buf, (Byte)*cp); + } + } + + Buf_AddByte(buf, (Byte)'\0'); + + /* + * If didn't end in delimiter character, complain + */ + if (*cp != delim) { + *lengthPtr = cp - start + 1; + if (*freePtr) { + free(str); + } + Buf_Destroy(buf, TRUE); + Error("Unclosed substitution for %s (%c missing)", + v->name, delim); + return (var_Error); + } + + pattern.rhs = (char *)Buf_GetAll(buf, &pattern.rightLen); + pattern.rightLen--; + Buf_Destroy(buf, FALSE); + + /* + * Check for global substitution. If 'g' after the final + * delimiter, substitution is global and is marked that + * way. + */ + cp++; + if (*cp == 'g') { + pattern.flags |= VAR_SUB_GLOBAL; + cp++; + } + + termc = *cp; + newStr = VarModify(str, VarSubstitute, + (ClientData)&pattern); + /* + * Free the two strings. + */ + free(pattern.lhs); + free(pattern.rhs); + break; + } + case 'T': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarModify (str, VarTail, (ClientData)0); + cp = tstr + 1; + termc = *cp; + break; + } + /*FALLTHRU*/ + case 'H': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarModify (str, VarHead, (ClientData)0); + cp = tstr + 1; + termc = *cp; + break; + } + /*FALLTHRU*/ + case 'E': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarModify (str, VarSuffix, (ClientData)0); + cp = tstr + 1; + termc = *cp; + break; + } + /*FALLTHRU*/ + case 'R': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarModify (str, VarRoot, (ClientData)0); + cp = tstr + 1; + termc = *cp; + break; + } + /*FALLTHRU*/ + default: { + /* + * 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>) + */ + for (cp = tstr; *cp != '\0' && *cp != endc; cp++) { + if (*cp == '=') { + eqFound = TRUE; + /* continue looking for endc */ + } + } + if (*cp == endc && eqFound) { + + /* + * Now we break this sucker into the lhs and + * rhs. We must null terminate them of course. + */ + for (cp = tstr; *cp != '='; cp++) + continue; + pattern.lhs = tstr; + pattern.leftLen = cp - tstr; + *cp++ = '\0'; + + pattern.rhs = cp; + while (*cp != endc) { + cp++; + } + pattern.rightLen = cp - pattern.rhs; + *cp = '\0'; + + /* + * SYSV modifications happen through the whole + * string. Note the pattern is anchored at the end. + */ + newStr = VarModify(str, VarSYSVMatch, + (ClientData)&pattern); + + /* + * Restore the nulled characters + */ + pattern.lhs[pattern.leftLen] = '='; + pattern.rhs[pattern.rightLen] = endc; + termc = endc; + } else { + Error ("Unknown modifier '%c'\n", *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 (*freePtr) { + free (str); + } + str = newStr; + if (str != var_Error) { + *freePtr = TRUE; + } else { + *freePtr = FALSE; + } + if (termc == '\0') { + Error("Unclosed variable specification for %s", v->name); + } else if (termc == ':') { + *cp++ = termc; + } else { + *cp = termc; + } + tstr = cp; + } + *lengthPtr = tstr - start + 1; + } else { + *lengthPtr = tstr - start + 1; + *tstr = endc; + } + + if (v->flags & VAR_FROM_ENV) { + Boolean destroy = FALSE; + + if (str != (char *)Buf_GetAll(v->val, (int *)NULL)) { + destroy = TRUE; + } else { + /* + * Returning the value unmodified, so tell the caller to free + * the thing. + */ + *freePtr = TRUE; + } + Buf_Destroy(v->val, destroy); + free((Address)v); + } else if (v->flags & VAR_JUNK) { + /* + * Perform any free'ing needed and set *freePtr to FALSE so the caller + * doesn't try to free a static pointer. + */ + if (*freePtr) { + free(str); + } + *freePtr = FALSE; + free((Address)v); + if (dynamic) { + str = emalloc(*lengthPtr + 1); + strncpy(str, start, *lengthPtr); + str[*lengthPtr] = '\0'; + *freePtr = TRUE; + } else { + str = var_Error; + } + } + return (str); +} + +/*- + *----------------------------------------------------------------------- + * Var_Subst -- + * Substitute for all variables in the given string in the given context + * If undefErr is TRUE, Parse_Error will be called when an undefined + * variable is encountered. + * + * Results: + * The resulting string. + * + * Side Effects: + * None. The old string must be freed by the caller + *----------------------------------------------------------------------- + */ +char * +Var_Subst (var, str, ctxt, undefErr) + char *var; /* Named variable || NULL for all */ + char *str; /* the string in which to substitute */ + GNode *ctxt; /* the context wherein to find variables */ + Boolean undefErr; /* TRUE if undefineds are an error */ +{ + Buffer buf; /* Buffer for forming things */ + char *val; /* Value to substitute for a variable */ + int length; /* Length of the variable invocation */ + Boolean doFree; /* Set true if val should be freed */ + static Boolean errorReported; /* Set true if an error has already + * been reported to prevent a plethora + * of messages when recursing */ + + buf = Buf_Init (MAKE_BSIZE); + errorReported = FALSE; + + while (*str) { + if (var == NULL && (*str == '$') && (str[1] == '$')) { + /* + * A dollar sign may be escaped either with another dollar sign. + * In such a case, we skip over the escape character and store the + * dollar sign into the buffer directly. + */ + str++; + Buf_AddByte(buf, (Byte)*str); + str++; + } else if (*str != '$') { + /* + * Skip as many characters as possible -- either to the end of + * the string or to the next dollar sign (variable invocation). + */ + char *cp; + + for (cp = str++; *str != '$' && *str != '\0'; str++) + continue; + Buf_AddBytes(buf, str - cp, (Byte *)cp); + } else { + if (var != NULL) { + int expand; + for (;;) { + if (str[1] != '(' && str[1] != '{') { + if (str[1] != *var) { + Buf_AddBytes(buf, 2, (Byte *) str); + str += 2; + expand = FALSE; + } + else + expand = TRUE; + break; + } + else { + char *p; + + /* + * Scan up to the end of the variable name. + */ + for (p = &str[2]; *p && + *p != ':' && *p != ')' && *p != '}'; p++) + if (*p == '$') + break; + /* + * A variable inside the variable. We cannot expand + * the external variable yet, so we try again with + * the nested one + */ + if (*p == '$') { + Buf_AddBytes(buf, p - str, (Byte *) str); + str = p; + continue; + } + + if (strncmp(var, str + 2, p - str - 2) != 0 || + var[p - str - 2] != '\0') { + /* + * Not the variable we want to expand, scan + * until the next variable + */ + for (;*p != '$' && *p != '\0'; p++) + continue; + Buf_AddBytes(buf, p - str, (Byte *) str); + str = p; + expand = FALSE; + } + else + expand = TRUE; + break; + } + } + if (!expand) + continue; + } + + val = Var_Parse (str, ctxt, undefErr, &length, &doFree); + + /* + * When we come down here, val should either point to the + * value of this variable, suitably modified, or be NULL. + * Length should be the total length of the potential + * variable invocation (from $ to end character...) + */ + if (val == var_Error || val == varNoError) { + /* + * If performing old-time variable substitution, skip over + * the variable and continue with the substitution. Otherwise, + * store the dollar sign and advance str so we continue with + * the string... + */ + if (oldVars) { + str += length; + } else if (undefErr) { + /* + * If variable is undefined, complain and skip the + * variable. The complaint will stop us from doing anything + * when the file is parsed. + */ + if (!errorReported) { + Parse_Error (PARSE_FATAL, + "Undefined variable \"%.*s\"",length,str); + } + str += length; + errorReported = TRUE; + } else { + Buf_AddByte (buf, (Byte)*str); + str += 1; + } + } else { + /* + * We've now got a variable structure to store in. But first, + * advance the string pointer. + */ + str += length; + + /* + * Copy all the characters from the variable value straight + * into the new string. + */ + Buf_AddBytes (buf, strlen (val), (Byte *)val); + if (doFree) { + free ((Address)val); + } + } + } + } + + Buf_AddByte (buf, '\0'); + str = (char *)Buf_GetAll (buf, (int *)NULL); + Buf_Destroy (buf, FALSE); + return (str); +} + +/*- + *----------------------------------------------------------------------- + * Var_GetTail -- + * Return the tail from each of a list of words. Used to set the + * System V local variables. + * + * Results: + * The resulting string. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +char * +Var_GetTail(file) + char *file; /* Filename to modify */ +{ + return(VarModify(file, VarTail, (ClientData)0)); +} + +/*- + *----------------------------------------------------------------------- + * Var_GetHead -- + * Find the leading components of a (list of) filename(s). + * XXX: VarHead does not replace foo by ., as (sun) System V make + * does. + * + * Results: + * The leading components. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +char * +Var_GetHead(file) + char *file; /* Filename to manipulate */ +{ + return(VarModify(file, VarHead, (ClientData)0)); +} + +/*- + *----------------------------------------------------------------------- + * Var_Init -- + * Initialize the module + * + * Results: + * None + * + * Side Effects: + * The VAR_CMD and VAR_GLOBAL contexts are created + *----------------------------------------------------------------------- + */ +void +Var_Init () +{ + VAR_GLOBAL = Targ_NewGN ("Global"); + VAR_CMD = Targ_NewGN ("Command"); + +} + +/****************** PRINT DEBUGGING INFO *****************/ +static int +VarPrintVar (v) + Var *v; +{ + printf ("%-16s = %s\n", v->name, (char *) Buf_GetAll(v->val, (int *)NULL)); + return (0); +} + +/*- + *----------------------------------------------------------------------- + * Var_Dump -- + * print all variables in a context + *----------------------------------------------------------------------- + */ +void +Var_Dump (ctxt) + GNode *ctxt; +{ + Lst_ForEach (ctxt->context, VarPrintVar); +} |