expand.c #5

  • //
  • guest/
  • craig_mcpheeters/
  • work/
  • jam/
  • src/
  • expand.c
  • View
  • Commits
  • Open Download .zip Download (13 KB)
/*
 * Copyright 1993-2002 Christopher Seiwald and Perforce Software, Inc.
 *
 * This file is part of Jam - see jam.c for Copyright information.
 */

/*
 * expand.c - expand a buffer, given variable values
 *
 * External routines:
 *
 *	var_expand() - variable-expand input string into list of strings
 *
 * Internal routines:
 *
 *	var_edit_parse() - parse : modifiers into PATHNAME structure
 *	var_edit_file() - copy input target name to output, modifying filename
 *	var_edit_shift() - do upshift/downshift mods
 *  var_edit_slash() - do forward/backward slash mods
 *
 * 01/25/94 (seiwald) - $(X)$(UNDEF) was expanding like plain $(X)
 * 04/13/94 (seiwald) - added shorthand L0 for null list pointer
 * 01/20/00 (seiwald) - Upgraded from K&R to ANSI C
 * 01/11/01 (seiwald) - added support for :E=emptyvalue, :J=joinval
 * 01/13/01 (seiwald) - :UDJE work on non-filename strings
 * 02/19/01 (seiwald) - make $($(var):J=x) join multiple values of var
 * 01/25/02 (seiwald) - fixed broken $(v[1-]), by ian godin
 * 10/22/02 (seiwald) - list_new() now does its own newstr()/copystr()
 * 11/04/02 (seiwald) - const-ing for string literals
 * 12/30/02 (armstrong) - fix out-of-bounds access in var_expand()
 */

# include "jam.h"
# include "lists.h"
# include "variable.h"
# include "expand.h"
# include "pathsys.h"
# include "newstr.h"

typedef struct {
	PATHNAME	f;		/* :GDBSMR -- pieces */
	char		parent;		/* :P -- go to parent directory */
	char		filemods;	/* one of the above applied */
	char		downshift;	/* :L -- downshift result */
	char		upshift;	/* :U -- upshift result */
	PATHPART	empty;		/* :E -- default for empties */
	PATHPART	join;		/* :J -- join list with char */
#ifdef OPT_SLASH_MODIFIERS_EXT
	char		fslash;		/* :/ -- convert all \ to / */
	char		bslash;		/* :\ -- convert all / to \ */
#endif

} VAR_EDITS ;

static void var_edit_parse( const char *mods, VAR_EDITS *edits );
static void var_edit_file( const char *in, char *out, VAR_EDITS *edits );
static void var_edit_shift( char *out, VAR_EDITS *edits );
#ifdef OPT_SLASH_MODIFIERS_EXT
static void var_edit_slash( char *out, VAR_EDITS *edits );
#endif

# define MAGIC_COLON	'\001'
# define MAGIC_LEFT	'\002'
# define MAGIC_RIGHT	'\003'

/*
 * var_expand() - variable-expand input string into list of strings
 *
 * Would just copy input to output, performing variable expansion, 
 * except that since variables can contain multiple values the result
 * of variable expansion may contain multiple values (a list).  Properly
 * performs "product" operations that occur in "$(var1)xxx$(var2)" or
 * even "$($(var2))".
 *
 * Returns a newly created list.
 */

LIST *
var_expand( 
	LIST		*l,
	const char 	*in,
	const char 	*end,
	LOL		*lol,
	int		cancopyin )
{
	char out_buf[ MAXSYM ];
	char *out = out_buf;
	const char *inp = in;
	char *ov;		/* for temp copy of variable in outbuf */
	int depth;

	if( DEBUG_VAREXP )
	    printf( "expand '%.*s'\n", end - in, in );

	/* This gets alot of cases: $(<) and $(>) */

	if( end - in == 4 && in[0] == '$' && in[1] == '(' && in[3] == ')' )
	{
	    switch( in[2] )
	    {
	    case '1':
	    case '<':
		return list_copy( l, lol_get( lol, 0 ) );

	    case '2':
	    case '>':
		return list_copy( l, lol_get( lol, 1 ) );
	    }
	}

	/* Just try simple copy of in to out. */

	while( in < end )
	    if( ( *out++ = *in++ ) == '$' && *in == '(' ) 
		goto expand;

	/* No variables expanded - just add copy of input string to list. */

	/* Cancopyin is an optimization: if the input was already a list */
	/* item, we can use the copystr() to put it on the new list. */
	/* Otherwise, we use the slower newstr(). */

	*out = '\0';

	if( cancopyin )
	    return list_new( l, inp, 1 );
	else
	    return list_new( l, out_buf, 0 );

    expand:
	/*
	 * Input so far (ignore blanks):
	 *
	 *	stuff-in-outbuf $(variable) remainder
	 *			 ^	             ^
	 *			 in		     end
	 * Output so far:
	 *
	 *	stuff-in-outbuf $
	 *	^	         ^
	 *	out_buf          out
	 *
	 *
	 * We just copied the $ of $(...), so back up one on the output.
	 * We now find the matching close paren, copying the variable and
	 * modifiers between the $( and ) temporarily into out_buf, so that
	 * we can replace :'s with MAGIC_COLON.  This is necessary to avoid
	 * being confused by modifier values that are variables containing
	 * :'s.  Ugly.
	 */

	depth = 1;
	out--, in++;
	ov = out;

	while( in < end && depth )
	{
	    switch( *ov++ = *in++ )
	    {
	    case '(': depth++; break;
	    case ')': depth--; break;
	    case ':': ov[-1] = MAGIC_COLON; break;
	    case '[': ov[-1] = MAGIC_LEFT; break;
	    case ']': ov[-1] = MAGIC_RIGHT; break;
	    }
	}

	/* Copied ) - back up. */

	ov--;

	/*
	 * Input so far (ignore blanks):
	 *
	 *	stuff-in-outbuf $(variable) remainder
	 *			            ^        ^
	 *			            in       end
	 * Output so far:
	 *
	 *	stuff-in-outbuf variable
	 *	^	        ^       ^
	 *	out_buf         out	ov
	 *
	 * Later we will overwrite 'variable' in out_buf, but we'll be
	 * done with it by then.  'variable' may be a multi-element list, 
	 * so may each value for '$(variable element)', and so may 'remainder'.
	 * Thus we produce a product of three lists.
	 */

	{
	    LIST *variables = 0;
	    LIST *remainder = 0;
	    LIST *vars;

	    /* Recursively expand variable name & rest of input */

	    if( out < ov )
		variables = var_expand( L0, out, ov, lol, 0 );
	    if( in < end )
		remainder = var_expand( L0, in, end, lol, 0 );

	    /* Now produce the result chain */

	    /* For each variable name */

	    for( vars = variables; vars; vars = list_next( vars ) )
	    {
		LIST *value, *evalue = 0;
		char *colon;
		char *bracket;
		char varname[ MAXSYM ];
		int sub1 = 0, sub2 = -1;
		VAR_EDITS edits;

		/* Look for a : modifier in the variable name */
		/* Must copy into varname so we can modify it */

		strcpy( varname, vars->string );

		if( colon = strchr( varname, MAGIC_COLON ) )
		{
		    *colon = '\0';
		    var_edit_parse( colon + 1, &edits );
		}

		/* Look for [x-y] and [x-] subscripting */
		/* sub1 is x (0 default) */
		/* sub2 is length (-1 means forever) */

		if( bracket = strchr( varname, MAGIC_LEFT ) )
		{
		    char *dash;

		    if( dash = strchr( bracket + 1, '-' ) )
			*dash = '\0';

		    sub1 = atoi( bracket + 1 ) - 1;

		    if( !dash )
			sub2 = 1;
		    else if( !dash[1] || dash[1] == MAGIC_RIGHT )
			sub2 = -1;
		    else
			sub2 = atoi( dash + 1 ) - sub1;

		    *bracket = '\0';
		}

		/* Get variable value, specially handling $(<), $(>), $(n) */
		
		if( varname[0] == '<' && !varname[1] )
		    value = lol_get( lol, 0 );
		else if( varname[0] == '>' && !varname[1] )
		    value = lol_get( lol, 1 );
		else if( varname[0] >= '1' && varname[0] <= '9' && !varname[1] )
		    value = lol_get( lol, varname[0] - '1' );
		else 
		    value = var_get( varname );

		/* The fast path: $(x) - just copy the variable value. */
		/* This is only an optimization */

		if( out == out_buf && !bracket && !colon && in == end )
		{
		    l = list_copy( l, value );
		    continue;
		}

		/* Handle start subscript */

		while( sub1 > 0 && value )
		    --sub1, value = list_next( value );

		/* Empty w/ :E=default? */

		if( !value && colon && edits.empty.ptr )
		    evalue = value = list_new( L0, edits.empty.ptr, 0 );

		/* For each variable value */

		for( ; value; value = list_next( value ) )
		{
		    LIST *rem;
		    char *out1;

		    /* Handle end subscript (length actually) */

		    if( sub2 >= 0 && --sub2 < 0 )
			break;

		    /* Apply : mods, if present */

		    if( colon && edits.filemods )
			var_edit_file( value->string, out, &edits );
		    else
			strcpy( out, value->string );

		    if( colon && ( edits.upshift || edits.downshift ) )
			var_edit_shift( out, &edits );

#ifdef OPT_SLASH_MODIFIERS_EXT
		    if( colon && ( edits.fslash || edits.bslash ) )
			var_edit_slash( out, &edits );
#endif

		    /* Handle :J=joinval */
		    /* If we have more values for this var, just */
		    /* keep appending them (with the join value) */
		    /* rather than creating separate LIST elements. */

		    if( colon && edits.join.ptr && 
		      ( list_next( value ) || list_next( vars ) ) )
		    {
			out += strlen( out );
			strcpy( out, edits.join.ptr );
			out += strlen( out );
			continue;
		    }

		    /* If no remainder, append result to output chain. */

		    if( in == end )
		    {
			l = list_new( l, out_buf, 0 );
			continue;
		    }

		    /* For each remainder, append the complete string */
		    /* to the output chain. */
		    /* Remember the end of the variable expansion so */
		    /* we can just tack on each instance of 'remainder' */

		    out1 = out + strlen( out );

		    for( rem = remainder; rem; rem = list_next( rem ) )
		    {
			strcpy( out1, rem->string );
			l = list_new( l, out_buf, 0 );
		    }
		}

		/* Toss used empty */

		if( evalue )
		    list_free( evalue );
	    }

	    /* variables & remainder were gifts from var_expand */
	    /* and must be freed */

	    if( variables )
		list_free( variables );
	    if( remainder)
		list_free( remainder );

	    if( DEBUG_VAREXP )
	    {
		printf( "expanded to " );
		list_print( l );
		printf( "\n" );
	    }

	    return l;
	}
}

/*
 * var_edit_parse() - parse : modifiers into PATHNAME structure
 *
 * The : modifiers in a $(varname:modifier) currently support replacing
 * or omitting elements of a filename, and so they are parsed into a 
 * PATHNAME structure (which contains pointers into the original string).
 *
 * Modifiers of the form "X=value" replace the component X with
 * the given value.  Modifiers without the "=value" cause everything 
 * but the component X to be omitted.  X is one of:
 *
 *	G <grist>
 *	D directory name
 *	B base name
 *	S .suffix
 *	M (member)
 *	R root directory - prepended to whole path
 *
 * This routine sets:
 *
 *	f->f_xxx.ptr = 0
 *	f->f_xxx.len = 0
 *		-> leave the original component xxx
 *
 *	f->f_xxx.ptr = string
 *	f->f_xxx.len = strlen( string )
 *		-> replace component xxx with string
 *
 *	f->f_xxx.ptr = ""
 *	f->f_xxx.len = 0
 *		-> omit component xxx
 *
 * var_edit_file() below and path_build() obligingly follow this convention.
 */

static void
var_edit_parse(
	const char	*mods,
	VAR_EDITS	*edits )
{
	int havezeroed = 0;
	memset( (char *)edits, 0, sizeof( *edits ) );

	while( *mods )
	{
	    char *p;
	    PATHPART *fp;

	    switch( *mods++ )
	    {
	    case 'L': edits->downshift = 1; continue;
	    case 'U': edits->upshift = 1; continue;
	    case 'P': edits->parent = edits->filemods = 1; continue;
	    case 'E': fp = &edits->empty; goto strval;
	    case 'J': fp = &edits->join; goto strval;
	    case 'G': fp = &edits->f.f_grist; goto fileval;
	    case 'R': fp = &edits->f.f_root; goto fileval;
	    case 'D': fp = &edits->f.f_dir; goto fileval;
	    case 'B': fp = &edits->f.f_base; goto fileval;
	    case 'S': fp = &edits->f.f_suffix; goto fileval;
	    case 'M': fp = &edits->f.f_member; goto fileval;
#ifdef OPT_SLASH_MODIFIERS_EXT
	    case '/':  edits->fslash = 1; continue;
	    case '\\': edits->bslash = 1; continue;
#endif

	    default: return; /* should complain, but so what... */
	    }

	fileval:

	    /* Handle :CHARS, where each char (without a following =) */
	    /* selects a particular file path element.  On the first such */
	    /* char, we deselect all others (by setting ptr = "", len = 0) */
	    /* and for each char we select that element (by setting ptr = 0) */

	    edits->filemods = 1;

	    if( *mods != '=' )
	    {
		int i;

		if( !havezeroed++ )
		    for( i = 0; i < 6; i++ )
		{
		    edits->f.part[ i ].len = 0;
		    edits->f.part[ i ].ptr = "";
		}

		fp->ptr = 0;
		continue;
	    }

	strval:

	    /* Handle :X=value, or :X */

	    if( *mods != '=' )
	    {
		fp->ptr = "";
		fp->len = 0;
	    }
	    else if( p = strchr( mods, MAGIC_COLON ) )
	    {
		*p = 0;
		fp->ptr = ++mods;
		fp->len = p - mods;
		mods = p + 1;
	    }
	    else
	    {
		fp->ptr = ++mods;
		fp->len = strlen( mods );
		mods += fp->len;
	    }
	}
}

/*
 * var_edit_file() - copy input target name to output, modifying filename
 */
	
static void
var_edit_file( 
	const char *in,
	char	*out,
	VAR_EDITS *edits )
{
	PATHNAME pathname;

	/* Parse apart original filename, putting parts into "pathname" */

	path_parse( in, &pathname );

	/* Replace any pathname with edits->f */

	if( edits->f.f_grist.ptr )
	    pathname.f_grist = edits->f.f_grist;

	if( edits->f.f_root.ptr )
	    pathname.f_root = edits->f.f_root;

	if( edits->f.f_dir.ptr )
	    pathname.f_dir = edits->f.f_dir;

	if( edits->f.f_base.ptr )
	    pathname.f_base = edits->f.f_base;

	if( edits->f.f_suffix.ptr )
	    pathname.f_suffix = edits->f.f_suffix;

	if( edits->f.f_member.ptr )
	    pathname.f_member = edits->f.f_member;

	/* If requested, modify pathname to point to parent */

	if( edits->parent )
	    path_parent( &pathname );

	/* Put filename back together */

	path_build( &pathname, out, 0 );
}

/*
 * var_edit_shift() - do upshift/downshift mods
 */

static void
var_edit_shift( 
	char	*out,
	VAR_EDITS *edits )
{
	/* Handle upshifting, downshifting now */

	if( edits->upshift )
	{
	    for( ; *out; ++out )
		*out = toupper( *out );
	}
	else if( edits->downshift )
	{
	    for( ; *out; ++out )
		*out = tolower( *out );
	}
}

#ifdef OPT_SLASH_MODIFIERS_EXT

/*
 * var_edit_slash() - do forward/backward slash mod
 */

static void
var_edit_slash( 
	char	*out,
	VAR_EDITS *edits )
{
	/* Handle forward, backward slash modifications now */

	if( edits->fslash )
	{
	    for( ; *out; ++out )
		if( *out == '\\' )
		    *out = '/';
	}
	else if( edits->bslash )
	{
	    for( ; *out; ++out )
		if( *out == '/' )
		    *out = '\\';
	}
}

#endif
# Change User Description Committed
#5 3183 Craig Mcpheeters Integration of my jam mainline branch into my work branch.
#4 1477 Craig Mcpheeters Integration from my main jam branch.
 This is 2.4-dev with my extensions.
#3 785 Craig Mcpheeters Integration from //guest/craig_mcpheeters/jam/src/...
This work area now contains both the Alias|Wavefront changes, and
  the latest integrations from the mainline.  There are some changes
  in this area which shouldn't be merged back into the mainline.
  As I merge this branch back into my jam/src/... branch, I'll
  leave out a few of the changes.
#2 782 Craig Mcpheeters Initial return of the Alias|Wavefront mods to jam 2.2.
 I made a
  stab at a configuration system - see the file Jamfile.config.
  Most of the mods are now enclosed within #ifdef blocks, which
  kind of pollutes the code, but may make it easier to accept
  or reject some of these changes.  Some of these #ifdefs could
  disappear completely if they are accepted into the mainline
This return implements the following extensions:
  * header cache
  * dynamic command block size, allowing for *large* commands
  * slightly improved warnings and errors
  * serial output from jam - nice when working with multiple jobs
  * an extension to variable modifiers:  $(>:/) and $(>:\\)
  * ability to ignore header dependencies for a build (jam -p)
  * new debug level, -d+10, which outputs the dependency graph
  * added no-care support to internal nodes.  if they fail, dependents
    are built anyway
  * time stamps on output
  * a few minor output modifications
  * a fix for nt batch file names conflicing when more than one jam runs
    at a time
Each of the above can be enabled/disabled on the command line.  For
  example, to turn on the HeaderCache code:
    jam -sHeaderCache=1
  that is, build jam first, then use that jam to build a new one with the
  options you want.
Some of these changes may have been made in the mainline already, my next
  step will be to integrate the mainline changes into these ones
This return isn't yet ready for prime-time
#1 777 Craig Mcpheeters Branch of my jam/src/...
area.  This new area establishes the integration
  base for the copy of Jam which has all the changes.
//guest/craig_mcpheeters/jam/src/expand.c
#2 617 Craig Mcpheeters Integration from mainline as of @3
#1 616 Craig Mcpheeters Integration from Jam mainline, as of @2