/*
 * Copyright 1993, 1995 Christopher Seiwald.
 *
 * This file is part of Jam - see jam.c for Copyright information.
 */

# include "jam.h"
# include "lists.h"
# include "parse.h"
# include "variable.h"
# include "expand.h"
# include "hash.h"
# include "filesys.h"
# include "newstr.h"


/*
 * variable.c - handle jam multi-element variables
 *
 * External routines:
 *
 *	var_defines() - load a bunch of variable=value settings
 *	var_list() - variable expand an input list, generating a new list
 *	var_string() - expand a string with variables in it
 *	var_get() - get value of a user defined symbol
 *	var_set() - set a variable in jam's user defined symbol table
 *	var_swap() - swap a variable's value with the given one
 *	var_done() - free variable tables
 *
 * Internal routines:
 *
 *	var_enter() - make new var symbol table entry, returning var ptr
 *	var_dump() - dump a variable to stdout
 *
 * 04/13/94 (seiwald) - added shorthand L0 for null list pointer
 * 08/23/94 (seiwald) - Support for '+=' (append to variable)
 * 01/22/95 (seiwald) - split environment variables at blanks or :'s
 * 05/10/95 (seiwald) - split path variables at SPLITPATH (not :)
 * 12/09/00 (andersb) - added support for deferred variables
 */

static struct hash *varhash = 0;
static LIST *exported_variables = L0;

#ifdef APPLE_EXTENSIONS

typedef struct _def_asg DEF_ASG ;
struct _def_asgs {
	unsigned        count;
	unsigned        capacity;
	struct _def_asg {
	    char	    *symbol;
	    char	    *value;
	    unsigned short  append;
	    unsigned short  being_expanded;
	}		asgs[0];
} ;
static struct _def_asgs *def_asgs = NULL;
static unsigned committing_def_asgs = 0;

#endif


/*
 * VARIABLE - a user defined multi-value variable
 */

typedef struct _variable VARIABLE ;

struct _variable {
	char	*symbol;
	LIST	*value;
} ;

static VARIABLE	*var_enter();
static void	var_dump();



/*
 * var_export() - mark a symbol for later exporting to subshells that want it
 *
 */void
var_export( symbol )
char *symbol;
{
    LIST * l = exported_variables;

    while( l  &&  strcmp(symbol, l->string) != 0 )
	l = list_next( l );
    if( l == NULL )
    {
	if( DEBUG_COMPILE )
	    printf( "marking %s for export\n", symbol );
	exported_variables = list_new( exported_variables, newstr(symbol) );
    }
}


/*
 * var_defines() - load a bunch of variable=value settings
 *
 * If variable name ends in PATH, split value at :'s.  
 * Otherwise, split at blanks.
 */

void
var_defines( e, export )
char **e;
int export;
{
	for( ; *e; e++ )
	{
	    char *val;

	    if( (val = strchr( *e, '=' )) != NULL )
	    {
		LIST *l = L0;

		void * pp, * p;
		char buf[ MAXSYM ];
#if defined(__APPLE__)
		char split = '\01';
#else
		char split = ' ';

		/* Split *PATH at :'s, not spaces */

		if( val - 4 >= *e )
		{
		    if( !strncmp( val - 4, "PATH", 4 ) ||
		        !strncmp( val - 4, "Path", 4 ) ||
		        !strncmp( val - 4, "path", 4 ) )
			    split = SPLITPATH;
		}
#endif
		/* Do the split */

		for( pp = val + 1; p = strchr( pp, split ); pp = p + 1 )
		{
		    strncpy( buf, pp, p - pp );
		    buf[ p - pp ] = '\0';
		    l = list_new( l, newstr( buf ) );
		}

		l = list_new( l, newstr( pp ) );

		/* Get name */

		strncpy( buf, *e, val - *e );
		buf[ val - *e ] = '\0';

		var_set( buf, l, VAR_SET , export /* export-in-environment */ );
	    }
	}
}

/*
 * var_list() - variable expand an input list, generating a new list
 *
 * Returns a newly created list.
 */

LIST *
var_list( ilist, lol )
LIST	*ilist;
LOL	*lol;
{
	LIST *olist = 0;

	while( ilist )
	{
	    char *s = ilist->string;
	    olist = var_expand( olist, s, s + strlen(s), lol, 1 );
	    ilist = list_next( ilist );
	}

	return olist;
}


/*
 * var_string() - expand a string with variables in it
 *
 * Copies in to out; doesn't modify targets & sources.
 */

int
var_string( in, out, outsize, lol )
char	*in;
char	*out;
int	outsize;
LOL	*lol;
{
	char 	*out0 = out;
	char	*oute = out + outsize - 1;

	while( *in )
	{
	    char	*lastword;
	    int		dollar = 0;

	    /* Copy white space */

	    while( isspace( *in ) )
	    {
		if( out >= oute )
		    return -1;

		*out++ = *in++;
	    }

	    lastword = out;

	    /* Copy non-white space, watching for variables */

	    while( *in && !isspace( *in ) )
	    {
	        if( out >= oute )
		    return -1;

		if( in[0] == '$' && in[1] == '(' )
		    dollar++;

		*out++ = *in++;
	    }

	    /* If a variable encountered, expand it and and embed the */
	    /* space-separated members of the list in the output. */

	    if( dollar )
	    {
		LIST	*l;

		l = var_expand( L0, lastword, out, lol, 0 );

		out = lastword;

		for( ; l; l = list_next( l ) )
		{
		    int so = strlen( l->string );

		    if( out + so >= oute )
			return -1;

		    strcpy( out, l->string );
		    out += so;
		    *out++ = ' ';
		}

		list_free( l );
	    }
	}

	if( out >= oute )
	    return -1;

	*out++ = '\0';

	return out - out0;
}

/*
 * var_get() - get value of a user defined symbol
 *
 * Returns NULL if symbol unset.
 */

LIST *
var_get( symbol )
char	*symbol;
{
	VARIABLE var, *v = &var;

	v->symbol = symbol;
#ifdef APPLE_EXTENSIONS
	// If we have a deferred setting for 'symbol' that hasn't yet been committed,
	// we return the uncommitted value.  We create a new list here, which we leak,
	// but for now (March 2002) this is ok -- this occurs very rarely, and we need
	// this for a fix for Proton.
	if (APPLE_JAM_EXTENSIONS  &&  def_asgs != NULL  &&  !committing_def_asgs) {
	    int   i;

	    for (i = def_asgs->count-1; i >= 0; i--) {
		if (strcmp(symbol, def_asgs->asgs[i].symbol) == 0) {
		    LIST * value = list_new(NULL, newstr(def_asgs->asgs[i].value));
		    if (DEBUG_VARGET) {
			var_dump(symbol, value, "get-uncommitted-deferred");
		    }
		    return value;
		}
	    }
	}
#endif
	if( varhash && hashcheck( varhash, (HASHDATA **)&v ) )
	{
	    if( DEBUG_VARGET )
		var_dump( v->symbol, v->value, "get" );
	    return v->value;
	}
    
	return 0;
}

/*
 * var_set() - set a variable in jam's user defined symbol table
 *
 * 'flag' controls the relationship between new and old values of
 * the variable: SET replaces the old with the new; APPEND appends
 * the new to the old; DEFAULT only uses the new if the variable
 * was previously unset.
 *
 * Copies symbol.  Takes ownership of value.
 */

void
var_set( symbol, value, flag, export )
char	*symbol;
LIST	*value;
int	flag;
int	export;
{
	VARIABLE *v = var_enter( symbol );

	if( DEBUG_VARSET )
	    var_dump( symbol, value, "set" );

	switch( flag )
	{
	case VAR_SET:
	    /* Replace value */
	    list_free( v->value );
	    v->value = value;
	    break;

	case VAR_APPEND:
	    /* Append value */
	    v->value = list_append( v->value, value );
	    break;

	case VAR_DEFAULT:
	    /* Set only if unset */
	    if( !v->value )
		v->value = value;
	    else
		list_free( value );
	    break;
	}
	if( export )
	    var_export( symbol );
}

/*
 * var_swap() - swap a variable's value with the given one
 */

LIST *
var_swap( symbol, value )
char	*symbol;
LIST	*value;
{
	VARIABLE *v = var_enter( symbol );
	LIST 	 *oldvalue = v->value;

	if( DEBUG_VARSET )
	    var_dump( symbol, value, "set" );

	v->value = value;

	return oldvalue;
}



/*
 * var_enter() - make new var symbol table entry, returning var ptr
 */

static VARIABLE *
var_enter( symbol )
char	*symbol;
{
	VARIABLE var, *v = &var;

	if( !varhash )
	    varhash = hashinit( sizeof( VARIABLE ), "variables" );

	v->symbol = symbol;
	v->value = 0;

	if( hashenter( varhash, (HASHDATA **)&v ) )
	    v->symbol = newstr( symbol );	/* never freed */

	return v;
}

/*
 * var_dump() - dump a variable to stdout
 */

static void
var_dump( symbol, value, what )
char	*symbol;
LIST	*value;
char	*what;
{
	printf( "%s %s = ", what, symbol );
	list_print( value );
	printf( "\n" );
}

/*
 * var_done() - free variable tables
 */

void
var_done()
{
	hashdone( varhash );
}


#ifdef MEASURE_SETENV
static unsigned  setenv_num = 0;
static unsigned  setenv_bytes = 0;
#endif

void
var_setenv( symbol, value )
const char *symbol;
LIST *value;
{
	int shouldset = 1;
    #ifdef EXPORT_ONLY_NONFUNKY_IDENTIFIERS
	register const char *sp;
	register char ch;

	for( sp = symbol; (ch = *sp) != '\0'; sp++ )
	{
	    if( !(ch >= 'A' && ch <= 'Z') && !(ch >= 'a' && ch <= 'z') && !(ch >= '0' && ch <= '9') && !(ch == '_') )
	    {
		shouldset = 0;
		break;
	    }
	}
    #endif
	if( shouldset )
	{
	    const int MAXVALUE_LEN = (1024 * 8) - 1;      // an arbitrary limit...
	    char buffer[MAXVALUE_LEN+1];
	    register char * bp = buffer;
	    LIST * l;

	    for( l = value ; l != NULL  &&  (bp - buffer) < MAXVALUE_LEN; l = list_next( l ) )
	    {
		register const char * sp;
		register unsigned needsquotes = 0;
		register int len = 0;
		register int extralen = 0;

		if( bp != buffer  &&  (bp - buffer) + 1 <= MAXVALUE_LEN )
		    *bp++ = ' ';
		for( sp = l->string; *sp; sp++, len++ )
		{
		#ifdef SHOULD_QUOTE_SETENV_VARS 
		    register char ch = *sp;
		    if( !(ch >= 'A' && ch <= 'Z')  &&  !(ch >= 'a' && ch <= 'z')  &&  !(ch == '_') )
		    {
			needsquotes = 1;
			if( ch == '\\'  ||  ch == '\"' )
			    extralen++;
		    }
		#endif
		}
		if( (bp - buffer) + len + extralen + (needsquotes ? 2 : 0) > MAXVALUE_LEN )
		{
		    fprintf(stderr, "warning: maximum value length exceeded when setting variable '%s' in environment; truncating value to %u characters\n", symbol, MAXVALUE_LEN);
		    len = MAXVALUE_LEN - (bp - buffer) - extralen - (needsquotes ? 2 : 0);
		}
		if( needsquotes )
		{
		    if ( (bp - buffer) + 1 <= MAXVALUE_LEN )
			*bp++ = '\"';
		    for( sp = l->string; *sp && len >= 0; sp++, len-- )
		    {
			register char ch = *sp;
			if( ch == '\\'  ||  ch == '\"' )
			    *bp++ = '\\';
			*bp++ = ch;
		    }
		    if ( (bp - buffer) + 1 <= MAXVALUE_LEN )
			*bp++ = '\"';
		}
		else
		{
		    memcpy(bp, l->string, len);
		    bp += len;
		}
	    }
	    *bp = '\0';
	#ifdef DEBUG_VARIABLE_EXPORTING
	    printf("[setenv '%s' '%s']\n", symbol, buffer);
	#endif
	    setenv(symbol, buffer, 1);
	#ifdef MEASURE_SETENV
	    setenv_num++;
	    setenv_bytes += strlen(symbol) + 1 + strlen(buffer) + 1;
	#endif
	}
}

void var_setenv_all_exported_variables()
{
	register LIST	*l;

    #ifdef MEASURE_SETENV
	setenv_num = 0;
	setenv_bytes = 0;
    #endif
	for( l = exported_variables; l; l = list_next( l ) )
	    var_setenv( l->string, var_get( l->string ) );
    #ifdef MEASURE_SETENV
  	printf("[did setenv %u variables (%u bytes)]\n", setenv_num, setenv_bytes);
    #endif
}



#ifdef APPLE_EXTENSIONS


//
//  Simple, growable string buffers (used by deferred-variable support only)
//

struct string_buffer {
    unsigned  length;
    unsigned  capacity;
    char *    characters;
} ;


static void strbuf_init (struct string_buffer * sbuf)
{
    sbuf->length = 0;
    sbuf->capacity = 16;
    sbuf->characters = malloc(sbuf->capacity * sizeof(char));
}


static char * strbuf_characters (struct string_buffer * sbuf)
{
    return sbuf->characters;
}


static unsigned strbuf_length (struct string_buffer * sbuf)
{
    return sbuf->length;
}


static inline void strbuf_append_characters (struct string_buffer * sbuf, const char * characters, unsigned length)
{
    if (sbuf->length + length > sbuf->capacity) {
	sbuf->capacity += length;
	sbuf->characters = realloc(sbuf->characters, sbuf->capacity);
    }
    memcpy(sbuf->characters + sbuf->length, characters, length);
    sbuf->length += length;
}


static inline void strbuf_append_character (struct string_buffer * sbuf, char ch)
{
    if (sbuf->length + 1 > sbuf->capacity) {
	sbuf->capacity += 32;
	sbuf->characters = realloc(sbuf->characters, sbuf->capacity);
    }
    *(sbuf->characters + sbuf->length++) = ch;
}


static void strbuf_append_quoted_characters (struct string_buffer * sbuf, const char * characters, unsigned length, unsigned quote_only_if_needed)
{
    register unsigned   needs_quotes = 0;    // Do we really need to quote the string?
#ifdef QUOTE_STRINGS_BY_ENCLOSING_IN_DOUBLEQUOTES
    register unsigned   extra_length = 2;    // For the two quotes, plus any escape characters
#else // QUOTE_STRINGS_BY_ESCAPING_CHARACTERS_USING_BACKSLASHES
    register unsigned   extra_length = 0;    // Only escape characters in this case
#endif
    register unsigned   i;

    for (i = 0; i < length; i++) {
	register char ch = characters[i];
	if (isspace(ch) || ch == '\\' || ch == '\"' || ch == '\'') {
	    needs_quotes = 1;
#ifdef QUOTE_STRINGS_BY_ENCLOSING_IN_DOUBLEQUOTES
	    if( ch == '\\'  ||  ch == '\"' )
#endif // QUOTE_STRINGS_BY_ESCAPING_CHARACTERS_USING_BACKSLASHES
		extra_length++;
	}
    }
    if (quote_only_if_needed  &&  !needs_quotes) {
	extra_length = 0;
	strbuf_append_characters(sbuf, characters, length);
    }
    else {
	register char *   dstp;

	if (sbuf->length + length + extra_length > sbuf->capacity) {
	    sbuf->capacity += length + extra_length;
	    sbuf->characters = realloc(sbuf->characters, sbuf->capacity);
	}
	dstp = sbuf->characters + sbuf->length;
#ifdef QUOTE_STRINGS_BY_ENCLOSING_IN_DOUBLEQUOTES
	*dstp++ = '\"';
	for (i = 0; i < length; i++) {
	    register char ch = characters[i];
	    if (ch == '\\'  ||  ch == '\"') {
		*dstp++ = '\\';
	    }
	    *dstp++ = ch;
	}
	*dstp++ = '\"';
#else // QUOTE_STRINGS_BY_ESCAPING_CHARACTERS_USING_BACKSLASHES
	for (i = 0; i < length; i++) {
	    register char ch = characters[i];
	    if (isspace(ch) || ch == '\\' || ch == '\"' || ch == '\'') {
		*dstp++ = '\\';
	    }
	    *dstp++ = ch;
	}
#endif
	sbuf->length += length + extra_length;
    }
}


static void strbuf_free (struct string_buffer * sbuf)
{
    free(sbuf->characters);
}



//
//  Conversion back and forth between lists and shell-style command lines in string buffers
//

static void strbuf_append_list_as_command_line_arguments (struct string_buffer * sbuf, LIST * list)
    /** Appends the list elements in 'list' to 'sbuf', quoting each list element if needed, and adding a space in between each. Any required quoting is done according to standard Bourne shell command-line quoting rules. */
{
    while (list != NULL) {
	strbuf_append_quoted_characters(sbuf, list->string, strlen(list->string), 1);
	if (list->next != NULL) {
	    strbuf_append_character(sbuf, ' ');
	}
	list = list->next;
    }
}

static LIST * list_by_parsing_as_commandline_arguments (const char * characters, unsigned length)
    /** Creates and returns a new list by parsing the given string according to Bourne shell command-line rules. */
{
    LIST *                 list = NULL;
    char                   dst_buffer[10240];
    register unsigned      in_escape_char = 0;
    register unsigned      in_single_quote = 0;
    register unsigned      in_double_quote = 0;
    register unsigned      had_quote = 0;
    register const char *  sp = characters;
    register const char *  ep = characters + length;
    register char *        dp = dst_buffer;

    //printf("list_by_parsing_as_commandline_arguments(|"); fwrite(characters, length, 1, stdout); printf("|) {\n");
    while (sp < ep) {
	register unsigned ch = *sp++;
	if (in_escape_char) {
	    //printf("(appending escaped char '%c', leaving escape mode)\n", ch);
	    *dp++ = ch;
	    in_escape_char = 0;
	}
	else if (ch == '\\') {
	    //printf("(entering escape mode)\n");
	    in_escape_char = 1;
	}
	else if (ch == '\''  &&  !in_double_quote) {
	    //printf("(toggling single-quote mode to %u)\n", !in_single_quote);
	    in_single_quote = !in_single_quote;
	    had_quote = 1;
	}
	else if (ch == '\"'  &&  !in_single_quote) {
	    //printf("(toggling double-quote mode to %u)\n", !in_double_quote);
	    in_double_quote = !in_double_quote;
	    had_quote = 1;
	}
	else if ((ch == ' '  ||  ch == '\t'  ||  ch == '\n'  ||  ch == '\r')  &&  !in_single_quote  &&  !in_double_quote) {
	    if (dp > dst_buffer  ||  had_quote) {
		*dp = '\0';
		//printf("arg is |%s|\n", dst_buffer);
		list = list_new(list, newstr(dst_buffer));
		had_quote = 0;
		dp = dst_buffer;
	    }
	    //printf("(skipping whitespace character)\n");
	}
	else {
	    //printf("(appending normal char '%c')\n", ch);
	    *dp++ = ch;
	}
    }
    if (dp > dst_buffer  ||  had_quote) {
	*dp = '\0';
	//printf("arg is |%s|\n", dst_buffer);
	list = list_new(list, newstr(dst_buffer));
    }
    //printf("}\n");
    return list;
}



//
//  Make-style deferred expansion of variables
//

static int find_next_variable_reference (const char * characters, unsigned length, char ** varRefStartPtr, char ** varNameStartPtr, char ** varNameEndPtr, char ** varRefEndPtr)
    /** Private function used by var_append_expansion_of_deferred_variable() to iterate through all non-single-quoted, non-escaped variable references in a string. This function doesn't modify the string -- rather, it returns up to four character pointers indicating the start and end of the variable reference as a whole, and the start and end of the variable name within it. Returns 1 if it finds a variable reference, 0 if not. */
{
    #define is_variable_char(_ch_) (((_ch_) >= 'A' && (_ch_) <= 'Z') || ((_ch_) >= 'a' && (_ch_) <= 'z') || ((_ch_) >= '0' && (_ch_) <= '9') || (_ch_ == '_'))
    register const char *   bufferCurrent;
    register const char *   bufferEnd;
    register const char *   varRefStart = NULL;
    register const char *   varNameStart = NULL;
    register const char *   varNameEnd = NULL;
    register const char *   varRefEnd = NULL;


    bufferCurrent = characters;
    bufferEnd = characters + length;
    while (bufferCurrent < bufferEnd  &&  varRefStart == NULL) {
        register char ch = *bufferCurrent++;
        switch (ch) {
            case '\\':
                if (bufferCurrent < bufferEnd) {
                    bufferCurrent++;
                }
                break;

            case '\'':
                while (bufferCurrent < bufferEnd  &&  (ch = *bufferCurrent++) != '\'') {
                    if (ch == '\\'  &&  bufferCurrent < bufferEnd) bufferCurrent++;
                }
                break;

            case '$':
                varRefStart = bufferCurrent-1;
                ch = *bufferCurrent++;
                if (ch == '('  ||  ch == '{'  ||  ch == '%'  ||  ch == '[') {
                    // Handle $(VAR), ${VAR}, $%VAR%, $[VAR selectorName] -- we respect backslashes as part of the variable name
                    const char closeDelimChar = (ch == '(') ? ')' : ((ch == '{') ? '}' : ((ch == '%') ? '%' : ']'));
                    varNameStart = bufferCurrent;
                    while (bufferCurrent < bufferEnd  &&  (ch = *bufferCurrent++) != closeDelimChar) {
                        if (ch == '\\'  &&  bufferCurrent < bufferEnd) bufferCurrent++;
                    }
                    if (ch == closeDelimChar) {
                        varNameEnd = bufferCurrent - 1;
                        varRefEnd = bufferCurrent;
                    }
                    else {
                        // We hit end-of-search-string without finding a delimiter... thus, no cigar
                        varRefStart = NULL;
                    }
                }
                else if (is_variable_char(ch)) {
                    // Handle $VAR -- we intentionally ignore backslashes in this case, since that's what most shells do
                    varNameStart = bufferCurrent-1;
                    while (bufferCurrent < bufferEnd  &&  (ch = *bufferCurrent++)  &&  is_variable_char(ch))
                        ;
                    if (bufferCurrent < bufferEnd) {
                        bufferCurrent--;
                    }
                    varNameEnd = bufferCurrent;
                    varRefEnd = bufferCurrent;
                    break;
                }
                else {
                    // This also handles the case of a trailing $ sign -- in this case, 'ch' is '\0'
                    varRefStart = NULL;
                    bufferCurrent--;
                }
                break;
        }
    }
    if (varRefStart != NULL) {
        if (varRefStartPtr != NULL) *varRefStartPtr = (char *)varRefStart;
        if (varNameStartPtr != NULL) *varNameStartPtr = (char *)varNameStart;
        if (varNameEndPtr != NULL) *varNameEndPtr = (char *)varNameEnd;
        if (varRefEndPtr != NULL) *varRefEndPtr = (char *)varRefEnd;
        return 1;
    }
    else {
        return 0;
    }
    #undef is_variable_char
}


static int var_append_expansion_of_deferred_variable (const char * symbol, unsigned length, int asgSearchStartIdx, struct string_buffer * expstrbuf)
    /** Private helper function that appends the expansion of 'symbol' to 'expstrbuf'. Recursive expansion is done as needed. The 'asgSearchStartIdx' is the index into the array of deferred assignments from which the search for a definition of 'symbol' should start. This is used when expanding concatenation assignments (X += Y). */
{
    const char *   value = NULL;
    char           symbolstr[length+1];
    int            should_concatenate = 0;
    int            found_recursion = 0;
    int            i, asgIdx = -1;
    int            free_value = 0;


    // Find the definition of 'symbol'.
    if (DEBUG_VARSET) {
	printf("expanding ");fwrite(symbol, length, 1, stdout);printf(" {\n");
    }
    memcpy(symbolstr, symbol, length);
    symbolstr[length] = '\0';
    for (i = asgSearchStartIdx; i >= 0  &&  value == NULL; i--) {
	if (strcmp(def_asgs->asgs[i].symbol, symbolstr) == 0) {
	    value = def_asgs->asgs[i].value;
	    if (DEBUG_VARSET) {
		printf("[found deferred assignment of ");fwrite(symbol, length, 1, stdout);printf(" with value |%s| at index %u]\n", value, i);
	    }
	    should_concatenate = def_asgs->asgs[i].append;
	    found_recursion = def_asgs->asgs[i].being_expanded;
	    asgIdx = i;
	}
    }

    // If we found recursion, we bail out.
    if (found_recursion) {
	return 0;
    }

    // If we haven't found a value yet, we try to find one in the standard Jam variables.
    if (value == NULL) {
	LIST * jamvar_list = var_get(symbolstr);
	if (jamvar_list != NULL) {
	    struct string_buffer jamvar_sbuf;
	    strbuf_init(&jamvar_sbuf);
	    strbuf_append_list_as_command_line_arguments(&jamvar_sbuf, jamvar_list);
	    strbuf_append_character(&jamvar_sbuf, '\0');
	    value = (const char *)malloc(strbuf_length(&jamvar_sbuf) * sizeof(char));
	    memcpy((char *)value, strbuf_characters(&jamvar_sbuf), strbuf_length(&jamvar_sbuf) * sizeof(char));
	    strbuf_free(&jamvar_sbuf);
	    free_value = 1;
	    should_concatenate = 0;
	    if (DEBUG_VARSET) {
		printf("[found normal value of ");fwrite(symbol, length, 1, stdout);printf(" with value |%s|]\n", value);
	    }
	}
    }
    // If we found a value, we start expanding variable references in it. If we didn't find one, we're done (there's nothing to append).
    if (value != NULL) {
	const char *   subrange_start;
	const char *   subrange_end;
	const char *   var_ref_start;
	const char *   var_name_start;
	const char *   var_name_end;
	const char *   var_ref_end;

	// If it's a concatenation assignment, then we first append the expansion of the next definition of 'symbol' of lower precendence than the one we're currently expanding, i.e. we recurse with the search starting from 'asgSearchStartIdx'-1.
	if (should_concatenate  &&  asgIdx >= 0) {
	    if (DEBUG_VARSET) {
		printf("[concatenation, so starting expansion of ");fwrite(symbol, length, 1, stdout);printf(" from search index %i]\n", asgIdx-1);
	    }
	    found_recursion |= (var_append_expansion_of_deferred_variable(symbol, length, asgIdx-1, expstrbuf) == 0);
	    strbuf_append_character(expstrbuf, ' ');
	}
	// Now we walk our string, expanding variable references as we go. The input string may contain escape characters and single quotes, both of which we need to honour as we do the variable expansion. We use find_next_variable_reference() to walk the string, honouring escaping and quoting.
	if (asgIdx >= 0) {
	    if (DEBUG_VARSET) {
		printf("[marking symbol %i: %s]\n", asgIdx, def_asgs->asgs[asgIdx].symbol);
	    }
	    def_asgs->asgs[asgIdx].being_expanded = 1;
	}
	subrange_start = value;
	subrange_end = subrange_start + strlen(subrange_start);
        while (!found_recursion  &&  find_next_variable_reference(subrange_start, subrange_end-subrange_start, (char **)(&var_ref_start), (char **)(&var_name_start), (char **)(&var_name_end), (char **)(&var_ref_end))) {

            // First append any plain text prefix, up to the opening delimiter.
            if (subrange_start < var_ref_start) {
		strbuf_append_characters(expstrbuf, subrange_start, var_ref_start - subrange_start);
            }

            // Extract the name of the referenced build setting, and recursively expand it at the end of 'expstrbuf'.
            // !!!:anders:20001209  We should check for definition loops here (e.g. A=$(A) -- as in make, such loops aren't allowed.
	    found_recursion |= (var_append_expansion_of_deferred_variable(var_name_start, var_name_end - var_name_start, def_asgs->count - 1, expstrbuf) == 0);

            // Update the substring range. It starts immediately after the previous variable reference.
            subrange_start = var_ref_end;
        }

        // Finally, append remaining plain text, up to the end of the string. For strings that contain no variable references, this amounts to the entire string.
        if (subrange_start < subrange_end) {
	    strbuf_append_characters(expstrbuf, subrange_start, subrange_end - subrange_start);
        }
	if (asgIdx >= 0) {
	    if (DEBUG_VARSET) {
		printf("[unmarking symbol %i: %s]\n", asgIdx, def_asgs->asgs[asgIdx].symbol);
	    }
	    def_asgs->asgs[asgIdx].being_expanded = 0;
	}
    }
    if (free_value) {
	free((char *)value);
    }
    if (DEBUG_VARSET) {
	printf("}\n");
    }
    return !found_recursion;
}



//
//  Dealing with deferred assignments
//

void var_set_deferred (const char * symbol, LIST * value, int flag, int export)
    /** Public function, similar to var_set(), but which adds a deferred assignment to the accumulated sequence of deferred assignments. Copies symbol, discards value (after turning it into a string). */
{
    struct string_buffer   sbuf;
    char *                 valstr;

    // Show a debug message, if appropriate.
    if (DEBUG_COMPILE) {
	printf("set-deferred %s %s ", symbol, (flag == VAR_APPEND) ? "+=" : "=");
	list_print(value);
	printf("\n");
    }
    // Grow the list.
    if (def_asgs == NULL) {
	if (DEBUG_VARSET) {
	    printf("[allocating deferred-assignment array with initial capacity %u]\n", 4);
	}
	def_asgs = malloc(sizeof(*def_asgs) + 4 * sizeof(*def_asgs->asgs) );
	def_asgs->count = 0;
	def_asgs->capacity = 4;
    }
    else if (def_asgs->count == def_asgs->capacity) {
	if (DEBUG_VARSET) {
	    printf("[growing deferred-assignment array capacity from %u to %u]\n", def_asgs->capacity, def_asgs->capacity * 2);
	}
	def_asgs->capacity *= 2;
	def_asgs = realloc(def_asgs, sizeof(*def_asgs) + def_asgs->capacity * sizeof(*def_asgs->asgs));
    }
    // Convert the value list into a string.
    strbuf_init(&sbuf);
    strbuf_append_list_as_command_line_arguments(&sbuf, value);
    strbuf_append_character(&sbuf, '\0');
    valstr = (char *)malloc(strbuf_length(&sbuf) * sizeof(char));
    memcpy((char *)valstr, strbuf_characters(&sbuf), strbuf_length(&sbuf) * sizeof(char));
    strbuf_free(&sbuf);
    list_free(value);
    // Record the assignment.
    def_asgs->asgs[def_asgs->count].symbol = newstr(symbol);
    def_asgs->asgs[def_asgs->count].value = valstr;
    def_asgs->asgs[def_asgs->count].append = (flag == VAR_APPEND) ? 1 : 0;
    def_asgs->asgs[def_asgs->count].being_expanded = 0;
    def_asgs->count++;
    // Mark the variable as exported, if needed.
    if (export) {
	var_export(symbol);
    }
}


void var_commit_all_deferred_assignments ()
    /** Public function that commits all deferred assignments (by evaluating their expansion and executing var_set for each one), and clears the array of deferred assignments. */
{
    if (def_asgs != NULL) {
	struct hash *   commited_defvars_hash;
	int             i;


	committing_def_asgs = 1;

	// First append all command-line assignments to the list of accumulated deferred assignments.
	for (i = 0; globs.cmdline_defines[i] != NULL; i++) {
	    char *   equals_sign_p;

	    if ((equals_sign_p = strchr(globs.cmdline_defines[i], '=')) != NULL  &&  (equals_sign_p > globs.cmdline_defines[i])) {
		char          buffer[MAXSYM];
		LIST *        list = NULL;
		int           flag = (equals_sign_p[-1] == '+') ? VAR_APPEND : VAR_SET;
		const char *  val_start_p;
		const char *  val_end_p;

		// Split the value string (starting at equals_sign_p + 1) into a list (split on '\01').
		for (val_start_p = equals_sign_p + 1; (val_end_p = strchr(val_start_p, '\01')) != NULL; val_end_p = val_start_p + 1) {
		    strncpy(buffer, val_start_p, val_end_p - val_start_p);
		    buffer[val_end_p - val_start_p] = '\0';
		    list = list_new(list, newstr(buffer));
		}
		list = list_new(list, newstr(val_start_p));

		// Now extract the variable name, and set the value list that we computed above as its deferred value.
		strncpy(buffer, globs.cmdline_defines[i], equals_sign_p - globs.cmdline_defines[i] - ((equals_sign_p[-1] == '+') ? 1 : 0));
		buffer[equals_sign_p - globs.cmdline_defines[i]] = '\0';
#if 0
		printf("set-deferred %s %s ", buffer, (flag == VAR_APPEND) ? "+=" : "=");
		list_print(list);
		printf("\n");
#endif
		var_set_deferred(buffer, list, flag, 1 /* export-in-environment */ );
	    }
	}

	// Now walk the deferred assignments from back to front, and apply the first assignment of each unique variable.
	commited_defvars_hash = hashinit(sizeof(char *), "committed defvars" );
	for (i = def_asgs->count-1; i >= 0; i--) {
	    char ** v = &def_asgs->asgs[i].symbol;
	    if (!hashcheck(commited_defvars_hash, (HASHDATA **)&v)) {
		struct string_buffer  sbuf;
		LIST *                lst;
		int                   found_recursion;

		(void)hashenter(commited_defvars_hash, (HASHDATA **)&v);
		strbuf_init(&sbuf);
		found_recursion = (var_append_expansion_of_deferred_variable(def_asgs->asgs[i].symbol, strlen(def_asgs->asgs[i].symbol), def_asgs->count - 1, &sbuf) == 0);
		if (found_recursion) {
		    printf("Error: '%s' refers to itself (either directly or indirectly)\n", def_asgs->asgs[i].symbol);
		}
		else {
		    strbuf_append_character(&sbuf, '\0');
		    lst = list_by_parsing_as_commandline_arguments(strbuf_characters(&sbuf), strbuf_length(&sbuf)-1);
		    if (DEBUG_COMPILE) {
			printf("committing deferred assignment %s := %s\n", def_asgs->asgs[i].symbol, strbuf_characters(&sbuf));
		    }
		    var_set(def_asgs->asgs[i].symbol, lst, VAR_SET, 1);
		    // we don't free 'lst' because var_set() takes over ownership of it
		}
		strbuf_free(&sbuf);
	    }
	    else {
		if (DEBUG_COMPILE) {
		    printf("skipping %s %s %s\n", def_asgs->asgs[i].symbol, def_asgs->asgs[i].append ? "+=" : "=", def_asgs->asgs[i].value);
		}
	    }
	}
	for (i = def_asgs->count-1; i >= 0; i--) {
	    free(def_asgs->asgs[i].value);
	}
	free(def_asgs);
	def_asgs = NULL;
	hashdone(commited_defvars_hash);
	committing_def_asgs = 0;
    }
}

#endif
