* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
* Copyright (c) 1988, 1989 by Adam de Boor
* Copyright (c) 1989 by Berkeley Softworks
* This code is derived from software contributed to Berkeley by
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by the University of California, Berkeley. The name of the
* University may not be used to endorse or promote products derived
* from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
static char sccsid
[] = "@(#)var.c 5.5 (Berkeley) %G%";
* Variable-handling functions
* 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
* 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
* 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 for all variables 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
* Var_Delete Delete a variable in a context.
* Var_Init Initialize this module.
* Var_Dump Print out all variables defined in the given
* XXX: There's a lot of duplication in these functions.
* 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.
* 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...
* 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
* 2) the global context. Variables set in the Makefile are located in
* the global context. It is the penultimate context searched when
* 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
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 */
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,
*-----------------------------------------------------------------------
* See if the given variable matches the named one. Called from
* Lst_Find when searching for a variable of a given name.
* 0 if they match. non-zero otherwise.
*-----------------------------------------------------------------------
Var
*v
; /* VAR structure to compare */
char *name
; /* name to look for */
return (strcmp (name
, v
->name
));
*-----------------------------------------------------------------------
* Find the given variable in the given context and any other contexts
* A pointer to the structure describing the desired variable or
* NIL if the variable does not exist.
*-----------------------------------------------------------------------
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
* FIND_ENV set means to look in the
* 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
if (*name
== '.' && isupper(name
[1]))
if (!strcmp(name
, ".ALLSRC"))
if (!strcmp(name
, ".ARCHIVE"))
if (!strcmp(name
, ".IMPSRC"))
if (!strcmp(name
, ".MEMBER"))
if (!strcmp(name
, ".OODATE"))
if (!strcmp(name
, ".PREFIX"))
if (!strcmp(name
, ".TARGET"))
* 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
) &&
var
= Lst_Find (VAR_GLOBAL
->context
, (ClientData
)name
, VarCmp
);
if ((var
== NILLNODE
) && (flags
& FIND_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
v
= (Var
*) malloc(sizeof(Var
));
Buf_AddBytes(v
->val
, len
, (Byte
*)env
);
} else if (checkEnvFirst
&& (flags
& FIND_GLOBAL
) &&
var
= Lst_Find (VAR_GLOBAL
->context
, (ClientData
)name
, VarCmp
);
return ((Var
*)Lst_Datum(var
));
} else if (var
== NILLNODE
) {
return ((Var
*) Lst_Datum (var
));
*-----------------------------------------------------------------------
* Add a new variable of name name and value val to the given context
* The new variable is placed at the front of the given context
* The name and val arguments are duplicated so they may
*-----------------------------------------------------------------------
char *name
; /* name of variable to add */
char *val
; /* value to set it to */
GNode
*ctxt
; /* context in which to set it */
v
= (Var
*) malloc (sizeof (Var
));
v
->val
= Buf_Init(len
+1);
Buf_AddBytes(v
->val
, len
, (Byte
*)val
);
(void) Lst_AtFront (ctxt
->context
, (ClientData
)v
);
printf("%s:%s = %s\n", ctxt
->name
, name
, val
);
*-----------------------------------------------------------------------
* Remove a variable from a context.
* The Var structure is removed and freed.
*-----------------------------------------------------------------------
printf("%s:delete %s\n", ctxt
->name
, name
);
ln
= Lst_Find(ctxt
->context
, (ClientData
)name
, VarCmp
);
v
= (Var
*)Lst_Datum(ln
);
Lst_Remove(ctxt
->context
, ln
);
Buf_Destroy(v
->val
, TRUE
);
*-----------------------------------------------------------------------
* Set the variable name to the value val in the given context.
* 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
* 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 $(<).
*-----------------------------------------------------------------------
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 */
* 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);
VarAdd (name
, val
, ctxt
);
Buf_Discard(v
->val
, Buf_Size(v
->val
));
Buf_AddBytes(v
->val
, strlen(val
), (Byte
*)val
);
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)
*-----------------------------------------------------------------------
* The variable of the given name has the given value appended to it in
* If the variable doesn't exist, it is created. Else the strings
* are concatenated (with a space in between).
* Only if the variable is being sought in the global context is the
* 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.
*-----------------------------------------------------------------------
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 */
v
= VarFind (name
, ctxt
, (ctxt
== VAR_GLOBAL
) ? FIND_ENV
: 0);
VarAdd (name
, val
, ctxt
);
Buf_AddByte(v
->val
, (Byte
)' ');
Buf_AddBytes(v
->val
, strlen(val
), (Byte
*)val
);
printf("%s:%s = %s\n", ctxt
->name
, name
,
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
);
*-----------------------------------------------------------------------
* See if the given variable exists.
* TRUE if it does, FALSE if it doesn't
*-----------------------------------------------------------------------
char *name
; /* Variable to find */
GNode
*ctxt
; /* Context in which to start search */
v
= VarFind(name
, ctxt
, FIND_CMD
|FIND_GLOBAL
|FIND_ENV
);
} else if (v
->flags
& VAR_FROM_ENV
) {
Buf_Destroy(v
->val
, TRUE
);
*-----------------------------------------------------------------------
* Return the value of the named variable in the given context
* The value if the variable exists, NULL if it doesn't
*-----------------------------------------------------------------------
char *name
; /* name to find */
GNode
*ctxt
; /* context in which to search for it */
v
= VarFind (name
, ctxt
, FIND_ENV
| FIND_GLOBAL
| FIND_CMD
);
return ((char *)Buf_GetAll(v
->val
, (int *)NULL
));
*-----------------------------------------------------------------------
* Remove the tail of the given word and place the result in the given
* TRUE if characters were added to the buffer (a space needs to be
* added to the buffer before the next word).
* The trimmed word is added to the buffer.
*-----------------------------------------------------------------------
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 */
slash
= rindex (word
, '/');
if (slash
!= (char *)NULL
) {
Buf_AddByte (buf
, (Byte
)' ');
Buf_AddBytes (buf
, strlen (word
), (Byte
*)word
);
* If no directory part, give . (q.v. the POSIX standard)
Buf_AddBytes(buf
, 2, (Byte
*)" .");
Buf_AddByte(buf
, (Byte
)'.');
*-----------------------------------------------------------------------
* Remove the head of the given word and place the result in the given
* TRUE if characters were added to the buffer (a space needs to be
* added to the buffer before the next word).
* The trimmed word is added to the buffer.
*-----------------------------------------------------------------------
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 */
Buf_AddByte (buf
, (Byte
)' ');
slash
= rindex (word
, '/');
if (slash
!= (char *)NULL
) {
Buf_AddBytes (buf
, strlen(slash
), (Byte
*)slash
);
Buf_AddBytes (buf
, strlen(word
), (Byte
*)word
);
*-----------------------------------------------------------------------
* Place the suffix of the given word in the given buffer.
* TRUE if characters were added to the buffer (a space needs to be
* added to the buffer before the next word).
* The suffix from the word is placed in the buffer.
*-----------------------------------------------------------------------
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 */
dot
= rindex (word
, '.');
if (dot
!= (char *)NULL
) {
Buf_AddByte (buf
, (Byte
)' ');
Buf_AddBytes (buf
, strlen (dot
), (Byte
*)dot
);
*-----------------------------------------------------------------------
* Remove the suffix of the given word and place the result in the
* TRUE if characters were added to the buffer (a space needs to be
* added to the buffer before the next word).
* The trimmed word is added to the buffer.
*-----------------------------------------------------------------------
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 */
Buf_AddByte (buf
, (Byte
)' ');
dot
= rindex (word
, '.');
if (dot
!= (char *)NULL
) {
Buf_AddBytes (buf
, strlen (word
), (Byte
*)word
);
Buf_AddBytes (buf
, strlen(word
), (Byte
*)word
);
*-----------------------------------------------------------------------
* Place the word in the buffer if it matches the given pattern.
* Callback function for VarModify to implement the :M modifier.
* TRUE if a space should be placed in the buffer before the next
* The word may be copied to the buffer.
*-----------------------------------------------------------------------
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
Buffer buf
; /* Buffer in which to store it */
char *pattern
; /* Pattern the word must match */
if (Str_Match(word
, pattern
)) {
Buf_AddByte(buf
, (Byte
)' ');
Buf_AddBytes(buf
, strlen(word
), (Byte
*)word
);
*-----------------------------------------------------------------------
* Place the word in the buffer if it doesn't match the given pattern.
* Callback function for VarModify to implement the :N modifier.
* TRUE if a space should be placed in the buffer before the next
* The word may be copied to the buffer.
*-----------------------------------------------------------------------
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
Buffer buf
; /* Buffer in which to store it */
char *pattern
; /* Pattern the word must match */
if (!Str_Match(word
, pattern
)) {
Buf_AddByte(buf
, (Byte
)' ');
Buf_AddBytes(buf
, strlen(word
), (Byte
*)word
);
char *lhs
; /* String to match */
int leftLen
; /* Length of string */
char *rhs
; /* Replacement string (w/ &'s removed) */
int rightLen
; /* Length of replacement */
#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 */
*-----------------------------------------------------------------------
* Perform a string-substitution on the given word, placing the
* result in the passed buffer.
* TRUE if a space is needed before more characters are added.
*-----------------------------------------------------------------------
VarSubstitute (word
, addSpace
, buf
, pattern
)
char *word
; /* Word to modify */
Boolean addSpace
; /* True if space should be added before
Buffer buf
; /* Buffer for result */
register VarPattern
*pattern
; /* Pattern for substitution */
register int wordLen
; /* Length of word */
register char *cp
; /* General pointer */
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 (pattern
->rightLen
!= 0) {
Buf_AddByte(buf
, (Byte
)' ');
Buf_AddBytes(buf
, pattern
->rightLen
,
} else if (pattern
->flags
& VAR_MATCH_END
) {
* Doesn't match to end -- copy word wholesale
* Matches at start but need to copy in trailing characters
if ((pattern
->rightLen
+ wordLen
- pattern
->leftLen
) != 0){
Buf_AddByte(buf
, (Byte
)' ');
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.
} 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
cp
= word
+ (wordLen
- pattern
->leftLen
);
(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) {
Buf_AddByte(buf
, (Byte
)' ');
Buf_AddBytes(buf
, cp
- word
, (Byte
*)word
);
Buf_AddBytes(buf
, pattern
->rightLen
, (Byte
*)pattern
->rhs
);
* Had to match at end and didn't. Copy entire word.
* 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
* addSpace is set FALSE as soon as a space is added to the
origSize
= Buf_Size(buf
);
cp
= Str_FindSubstring(word
, pattern
->lhs
);
if (cp
!= (char *)NULL
) {
if (addSpace
&& (((cp
- word
) + pattern
->rightLen
) != 0)){
Buf_AddByte(buf
, (Byte
)' ');
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 ((pattern
->flags
& VAR_SUB_GLOBAL
) == 0) {
pattern
->flags
|= VAR_NO_SUB
;
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
if ((pattern
->flags
& VAR_SUB_GLOBAL
) == 0) {
pattern
->flags
|= VAR_NO_SUB
;
Buf_AddByte(buf
, (Byte
)' ');
Buf_AddBytes(buf
, wordLen
, (Byte
*)word
);
*-----------------------------------------------------------------------
* Modify each of the words of the passed string using the given
* function. Used to implement all modifiers.
* A string of all the words modified appropriately.
*-----------------------------------------------------------------------
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
* Skip to next word and place cp at its end.
for (cp
= str
; *cp
!= '\0' && !isspace (*cp
); cp
++) {
* If we didn't go anywhere, we must be done!
str
= (char *)Buf_GetAll (buf
, (int *)NULL
);
Buf_Destroy (buf
, FALSE
);
* 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
addSpace
= (* modProc
) (str
, addSpace
, buf
, datum
);
*-----------------------------------------------------------------------
* Given the start of a variable invocation, extract the variable
* name and find its value, then modify it according to the
* 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
* A Boolean in *freePtr telling whether the returned string should
* be freed by the caller.
*-----------------------------------------------------------------------
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
Boolean haveModifier
;/* TRUE if have modifiers for the variable */
register char endc
; /* Ending character when variable in parens
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 */
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
v
= VarFind (name
, ctxt
, FIND_ENV
| FIND_GLOBAL
| FIND_CMD
);
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.
return (err
? var_Error
: varNoError
);
endc
= str
[1] == '(' ? ')' : '}';
* Skip to the end character or a colon, whichever comes first.
*tstr
!= '\0' && *tstr
!= endc
&& *tstr
!= ':';
} else if (*tstr
!= '\0') {
* 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.
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.
* Well, it's local -- go look for it.
v
= VarFind(vname
, ctxt
, 0);
* 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
);
val
= VarModify(val
, VarHead
, (ClientData
)0);
val
= VarModify(val
, VarTail
, (ClientData
)0);
* Resulting string is dynamically allocated, so
* tell caller to free it.
*lengthPtr
= tstr
-start
+1;
if ((((tstr
-str
) == 3) ||
((((tstr
-str
) == 4) && (str
[3] == 'F' ||
((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.
} else if (((tstr
-str
) > 4) && (str
[2] == '.') &&
((ctxt
== VAR_CMD
) || (ctxt
== VAR_GLOBAL
)))
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))
* No modifiers -- have specification length so we can return
*lengthPtr
= tstr
- start
+ 1;
str
= malloc(*lengthPtr
+ 1);
strncpy(str
, start
, *lengthPtr
);
return (err
? var_Error
: varNoError
);
* Still need to get to the end of the variable specification,
* so kludge up a Var structure for the modifications
v
= (Var
*) malloc(sizeof(Var
));
if (v
->flags
& VAR_IN_USE
) {
Fatal("Variable %s is recursive.", v
->name
);
* 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
str
= (char *)Buf_GetAll(v
->val
, (int *)NULL
);
if (index (str
, '$') != (char *)NULL
) {
str
= Var_Subst(str
, ctxt
, err
);
* Now we need to apply any modifiers the user wants applied.
* :M<pattern> words which match the given <pattern>.
* <pattern> is of the standard file
* :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
* :R Substitute the root of each word
* (pathname minus the suffix).
* :lhs=rhs Like :S, but the rhs goes to the end of
if ((str
!= (char *)NULL
) && haveModifier
) {
* Skip initial colon while putting it back.
char *newStr
; /* New value to return */
char termc
; /* Character which terminated scan */
printf("Applying :%c to \"%s\"\n", *tstr
, str
);
*cp
!= '\0' && *cp
!= ':' && *cp
!= endc
;
if (*cp
== '\\' && (cp
[1] == ':' || cp
[1] == endc
)){
* 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
= malloc(cp
- tstr
);
for (cp2
= pattern
, cp
= tstr
+ 1;
(cp
[1] == ':' || cp
[1] == endc
)) {
if (*tstr
== 'M' || *tstr
== 'm') {
newStr
= VarModify(str
, VarMatch
, (ClientData
)pattern
);
newStr
= VarModify(str
, VarNoMatch
,
Buffer buf
; /* Buffer for patterns */
* If pattern begins with '^', it is anchored to the
* start of the word -- skip over it and flag pattern.
pattern
.flags
|= VAR_MATCH_START
;
* 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
++) {
Buf_AddByte(buf
, (Byte
)cp
[1]);
* If unescaped dollar sign not before the
* delimiter, assume it's a variable
* substitution and recurse.
cp2
= Var_Parse(cp
, ctxt
, err
, &len
, &freeIt
);
Buf_AddBytes(buf
, strlen(cp2
), (Byte
*)cp2
);
* Unescaped $ at end of pattern => anchor
pattern
.flags
|= VAR_MATCH_END
;
Buf_AddByte(buf
, (Byte
)*cp
);
Buf_AddByte(buf
, (Byte
)'\0');
* If lhs didn't end with the delimiter, complain and
*lengthPtr
= cp
- start
+ 1;
Error("Unclosed substitution for %s (%c missing)",
* 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
pattern
.lhs
= (char *)Buf_GetAll(buf
, &pattern
.leftLen
);
* 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.
for (cp
= tstr
; *cp
!= '\0' && *cp
!= delim
; cp
++) {
Buf_AddByte(buf
, (Byte
)cp
[1]);
} else if ((*cp
== '$') && (cp
[1] != delim
)) {
cp2
= Var_Parse(cp
, ctxt
, err
, &len
, &freeIt
);
Buf_AddBytes(buf
, strlen(cp2
), (Byte
*)cp2
);
Buf_AddBytes(buf
, pattern
.leftLen
,
Buf_AddByte(buf
, (Byte
)*cp
);
Buf_AddByte(buf
, (Byte
)'\0');
* If didn't end in delimiter character, complain
*lengthPtr
= cp
- start
+ 1;
Error("Unclosed substitution for %s (%c missing)",
pattern
.rhs
= (char *)Buf_GetAll(buf
, &pattern
.rightLen
);
* Check for global substitution. If 'g' after the final
* delimiter, substitution is global and is marked that
pattern
.flags
|= VAR_SUB_GLOBAL
;
newStr
= VarModify(str
, VarSubstitute
,
if (tstr
[1] == endc
|| tstr
[1] == ':') {
newStr
= VarModify (str
, VarTail
, (ClientData
)0);
if (tstr
[1] == endc
|| tstr
[1] == ':') {
newStr
= VarModify (str
, VarHead
, (ClientData
)0);
if (tstr
[1] == endc
|| tstr
[1] == ':') {
newStr
= VarModify (str
, VarSuffix
, (ClientData
)0);
if (tstr
[1] == endc
|| tstr
[1] == ':') {
newStr
= VarModify (str
, VarRoot
, (ClientData
)0);
* This can either be a bogus modifier or a System-V
* 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
++) {
/* 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
++) {
pattern
.leftLen
= cp
- tstr
;
pattern
.rightLen
= cp
- pattern
.rhs
;
* SYSV modifications happen through the whole
* string. Note the pattern is anchored at the end.
pattern
.flags
|= VAR_SUB_GLOBAL
|VAR_MATCH_END
;
newStr
= VarModify(str
, VarSubstitute
,
* Restore the nulled characters
pattern
.lhs
[pattern
.leftLen
] = '=';
pattern
.rhs
[pattern
.rightLen
] = endc
;
Error ("Unknown modifier '%c'\n", *tstr
);
*cp
!= ':' && *cp
!= endc
&& *cp
!= '\0';
printf("Result is \"%s\"\n", newStr
);
Error("Unclosed variable specification for %s", v
->name
);
} else if (termc
== ':') {
*lengthPtr
= tstr
- start
+ 1;
*lengthPtr
= tstr
- start
+ 1;
if (v
->flags
& VAR_FROM_ENV
) {
if (str
!= (char *)Buf_GetAll(v
->val
, (int *)NULL
)) {
* Returning the value unmodified, so tell the caller to free
Buf_Destroy(v
->val
, destroy
);
} 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.
str
= malloc(*lengthPtr
+ 1);
strncpy(str
, start
, *lengthPtr
);
*-----------------------------------------------------------------------
* 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.
* None. The old string must be freed by the caller
*-----------------------------------------------------------------------
Var_Subst (str
, ctxt
, undefErr
)
register 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 */
if ((*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.
Buf_AddByte(buf
, (Byte
)*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).
for (cp
= str
++; *str
!= '$' && *str
!= '\0'; str
++) {
Buf_AddBytes(buf
, str
- cp
, (Byte
*)cp
);
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
* If variable is undefined, complain and skip the
* variable. The complaint will stop us from doing anything
* when the file is parsed.
Parse_Error (PARSE_FATAL
,
"Undefined variable \"%.*s\"",length
,str
);
Buf_AddByte (buf
, (Byte
)*str
);
* We've now got a variable structure to store in. But first,
* advance the string pointer.
* Copy all the characters from the variable value straight
Buf_AddBytes (buf
, strlen (val
), (Byte
*)val
);
str
= (char *)Buf_GetAll (buf
, (int *)NULL
);
Buf_Destroy (buf
, FALSE
);
*-----------------------------------------------------------------------
* Return the tail from each of a list of words. Used to set the
* System V local variables.
*-----------------------------------------------------------------------
char *file
; /* Filename to modify */
return(VarModify(file
, VarTail
, (ClientData
)0));
*-----------------------------------------------------------------------
* Find the leading components of a (list of) filename(s).
* XXX: VarHead does not replace foo by ., as (sun) System V make
* The leading components.
*-----------------------------------------------------------------------
char *file
; /* Filename to manipulate */
return(VarModify(file
, VarHead
, (ClientData
)0));
*-----------------------------------------------------------------------
* The VAR_CMD and VAR_GLOBAL contexts are created
*-----------------------------------------------------------------------
VAR_GLOBAL
= Targ_NewGN ("Global");
VAR_CMD
= Targ_NewGN ("Command");
/****************** PRINT DEBUGGING INFO *****************/
printf ("%-16s = %s\n", v
->name
, Buf_GetAll(v
->val
, (int *)NULL
));
*-----------------------------------------------------------------------
* print all variables in a context
*-----------------------------------------------------------------------
Lst_ForEach (ctxt
->context
, VarPrintVar
);