* Break substitution down into simple anchored cases
* Still substituting -- break it down into simple anchored cases
static char *VarModify __P((char *, Boolean (*)(char *, Boolean, Buffer,						ClientData),			    ClientData));static int VarPrintVar __P((ClientData, ClientData));/*- *----------------------------------------------------------------------- * 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 intVarCmp (v, name)    ClientData     v;		/* VAR structure to compare */    ClientData     name;	/* name to look for */{    return (strcmp ((char *) name, ((Var *) 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((unsigned char) 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) {	    int	  	len;	    v = (Var *) emalloc(sizeof(Var));	    v->name = estrdup(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 voidVarAdd (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 = estrdup (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);    (void) Lst_AtEnd (allVars, (ClientData) v);    if (DEBUG(VAR)) {	printf("%s:%s = %s\n", ctxt->name, name, val);    }}/*- *----------------------------------------------------------------------- * VarDelete  -- *	Delete a variable and all the space associated with it. * * Results: *	None * * Side Effects: *	None *----------------------------------------------------------------------- */static voidVarDelete(vp)    ClientData vp;{    Var *v = (Var *) vp;    free(v->name);    Buf_Destroy(v->val, TRUE);    free((Address) v);}/*- *----------------------------------------------------------------------- * Var_Delete -- *	Remove a variable from a context. * * Results: *	None. * * Side Effects: *	The Var structure is removed and freed. * *----------------------------------------------------------------------- */voidVar_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);	ln = Lst_Member(allVars, v);	Lst_Remove(allVars, ln);	VarDelete((ClientData) 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 $(<). *----------------------------------------------------------------------- */voidVar_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. *----------------------------------------------------------------------- */voidVar_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. * *----------------------------------------------------------------------- */BooleanVar_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) {	free(v->name);	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, frp)    char           *name;	/* name to find */    GNode          *ctxt;	/* context in which to search for it */    char	   **frp;{    Var            *v;    v = VarFind (name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD);    *frp = NULL;    if (v != (Var *) NIL) {	char *p = ((char *)Buf_GetAll(v->val, (int *)NULL));	if (v->flags & VAR_FROM_ENV) {	    Buf_Destroy(v->val, FALSE);	    free((Address) v);	    *frp = p;	}	return p;    } 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 BooleanVarHead (word, addSpace, buf, dummy)    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 */    ClientData	  dummy;{    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(dummy ? TRUE : 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 BooleanVarTail (word, addSpace, buf, dummy)    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 */    ClientData	  dummy;{    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 (dummy ? TRUE : 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 BooleanVarSuffix (word, addSpace, buf, dummy)    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 */    ClientData	  dummy;{    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] = '.';	addSpace = TRUE;    }    return (dummy ? addSpace : 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 BooleanVarRoot (word, addSpace, buf, dummy)    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 */    ClientData	  dummy;{    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 (dummy ? TRUE : 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 BooleanVarMatch (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 */    ClientData    pattern; 	/* Pattern the word must match */{    if (Str_Match(word, (char *) pattern)) {	if (addSpace) {	    Buf_AddByte(buf, (Byte)' ');	}	addSpace = TRUE;	Buf_AddBytes(buf, strlen(word), (Byte *)word);    }    return(addSpace);}#ifdef SYSVVARSUB/*- *----------------------------------------------------------------------- * 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 BooleanVarSYSVMatch (word, addSpace, buf, patp)    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 */    ClientData 	  patp; 	/* Pattern the word must match */{    int len;    char *ptr;    VarPattern 	  *pat = (VarPattern *) patp;    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);}#endif/*- *----------------------------------------------------------------------- * 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 BooleanVarNoMatch (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 */    ClientData    pattern; 	/* Pattern the word must match */{    if (!Str_Match(word, (char *) 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 BooleanVarSubstitute (word, addSpace, buf, patternp)    char    	  	*word;	    /* Word to modify */    Boolean 	  	addSpace;   /* True if space should be added before				     * other characters */    Buffer  	  	buf;	    /* Buffer for result */    ClientData	        patternp;   /* Pattern for substitution */{    register int  	wordLen;    /* Length of word */    register char 	*cp;	    /* General pointer */    VarPattern	*pattern = (VarPattern *) patternp;    wordLen = strlen(word);    if (1) { /* substitute in each word of the variable */	/*	 * Break substitution 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	     * substitutions 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 || (pattern->flags & VAR_SUB_GLOBAL) == 0){			done = TRUE;		    }		} 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:	 * addSpace was set TRUE if characters were added to the buffer.	 */	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 */				    /* Function to use to modify them */    Boolean    	  (*modProc) __P((char *, Boolean, Buffer, ClientData));    ClientData	  datum;    	    /* Datum to pass it */{    Buffer  	  buf;	    	    /* Buffer for the new string */    Boolean 	  addSpace; 	    /* TRUE if need to add a space to the				     * buffer before adding the trimmed				     * word */    char **av;			    /* word list [first word does not count] */    int ac, i;    buf = Buf_Init (0);    addSpace = FALSE;    av = brk_string(str, &ac, FALSE);    for (i = 1; i < ac; i++)	addSpace = (*modProc)(av[i], addSpace, buf, datum);    Buf_AddByte (buf, '\0');    str = (char *)Buf_GetAll (buf, (int *)NULL);    Buf_Destroy (buf, FALSE);    return (str);}