2 * Copyright 1993-2002 Christopher Seiwald and Perforce Software, Inc.
4 * This file is part of Jam - see jam.c for Copyright information.
18 # include <sys/cygwin.h>
23 * expand.c - expand a buffer, given variable values
27 * var_expand() - variable-expand input string into list of strings
31 * var_edit_parse() - parse : modifiers into PATHNAME structure.
32 * var_edit_file() - copy input target name to output, modifying filename.
33 * var_edit_shift() - do upshift/downshift mods.
35 * 01/25/94 (seiwald) - $(X)$(UNDEF) was expanding like plain $(X)
36 * 04/13/94 (seiwald) - added shorthand L0 for null list pointer
37 * 01/11/01 (seiwald) - added support for :E=emptyvalue, :J=joinval
42 PATHNAME f; /* :GDBSMR -- pieces */
43 char parent; /* :P -- go to parent directory */
44 char filemods; /* one of the above applied */
45 char downshift; /* :L -- downshift result */
46 char upshift; /* :U -- upshift result */
47 char to_slashes; /* :T -- convert "\" to "/" */
48 char to_windows; /* :W -- convert cygwin to native paths */
49 PATHPART empty; /* :E -- default for empties */
50 PATHPART join; /* :J -- join list with char */
53 static void var_edit_parse( char * mods, VAR_EDITS * edits );
54 static void var_edit_file ( char * in, string * out, VAR_EDITS * edits );
55 static void var_edit_shift( string * out, VAR_EDITS * edits );
57 #define MAGIC_COLON '\001'
58 #define MAGIC_LEFT '\002'
59 #define MAGIC_RIGHT '\003'
63 * var_expand() - variable-expand input string into list of strings.
65 * Would just copy input to output, performing variable expansion, except that
66 * since variables can contain multiple values the result of variable expansion
67 * may contain multiple values (a list). Properly performs "product" operations
68 * that occur in "$(var1)xxx$(var2)" or even "$($(var2))".
70 * Returns a newly created list.
73 LIST * var_expand( LIST * l, char * in, char * end, LOL * lol, int cancopyin )
75 char out_buf[ MAXSYM ];
77 string out1[ 1 ]; /* temporary buffer */
81 char * ov; /* for temp copy of variable in outbuf */
85 printf( "expand '%.*s'\n", (int)(end - in), in );
87 /* This gets a lot of cases: $(<) and $(>). */
98 case '<': return list_copy( l, lol_get( lol, 0 ) );
99 case '>': return list_copy( l, lol_get( lol, 1 ) );
110 return list_copy( l, lol_get( lol, in[ 2 ] - '1' ) );
113 else if ( in[0] == '$' && in[1] == '(' && in[2] == '1' && in[4] == ')' &&
128 return list_copy( l, lol_get( lol, in[3]-'0'+10-1 ) );
132 /* Expand @() files, to single item plus accompanying file. */
133 if ( ( in[ 0 ] == '@' ) && ( in[ 1 ] == '(' ) && ( *( end - 1 ) == ')' ) )
135 /* We try the expansion until it fits within the propective output
139 int at_size = MAXJPATH;
144 at_buf = (char *)BJAM_MALLOC_ATOMIC( at_size + 1 );
145 at_len = var_string( in, at_buf, at_size, lol );
148 while ( ( at_len < 0 ) && ( at_size < INT_MAX / 2 ) );
149 /* Return the result as a single item list. */
153 string_copy( buf, at_buf );
154 r = list_new( l, newstr( buf->value ) );
162 /* Just try simple copy of in to out. */
164 if ( ( *in++ == '$' ) && ( *in == '(' ) )
167 /* No variables expanded - just add copy of input string to list. */
169 /* 'cancopyin' is an optimization: if the input was already a list item, we
170 * can use copystr() to put it on the new list. Otherwise, we use the slower
174 return list_new( l, copystr( inp ) );
179 string_append_range( buf, inp, end );
180 r = list_new( l, newstr( buf->value ) );
187 string_append_range( buf, inp, in - 1 ); /* Copy the part before '$'. */
189 * Input so far (ignore blanks):
191 * stuff-in-outbuf $(variable) remainder
201 * We just copied the $ of $(...), so back up one on the output. We now find
202 * the matching close paren, copying the variable and modifiers between the
203 * $( and ) temporarily into out_buf, so that we can replace :'s with
204 * MAGIC_COLON. This is necessary to avoid being confused by modifier values
205 * that are variables containing :'s. Ugly.
209 inp = ++in; /* Skip over the '('. */
211 while ( ( in < end ) && depth )
215 case '(': ++depth; break;
216 case ')': --depth; break;
221 * Input so far (ignore blanks):
223 * stuff-in-outbuf $(variable) remainder
227 prefix_length = buf->size;
228 string_append_range( buf, inp, in - 1 );
230 out = buf->value + prefix_length;
231 for ( ov = out; ov < buf->value + buf->size; ++ov )
235 case ':': *ov = MAGIC_COLON; break;
236 case '[': *ov = MAGIC_LEFT ; break;
237 case ']': *ov = MAGIC_RIGHT; break;
242 * Input so far (ignore blanks):
244 * stuff-in-outbuf $(variable) remainder
249 * stuff-in-outbuf variable
253 * Later we will overwrite 'variable' in out_buf, but we will be done with
254 * it by then. 'variable' may be a multi-element list, so may each value for
255 * '$(variable element)', and so may 'remainder'. Thus we produce a product
259 LIST * variables = 0;
260 LIST * remainder = 0;
263 /* Recursively expand variable name & rest of input. */
264 if ( out < ov ) variables = var_expand( L0, out, ov, lol, 0 );
265 if ( in < end ) remainder = var_expand( L0, in, end, lol, 0 );
267 /* Now produce the result chain. */
269 /* For each variable name. */
270 for ( vars = variables; vars; vars = list_next( vars ) )
282 /* Look for a : modifier in the variable name. Must copy into
283 * varname so we can modify it.
285 string_copy( variable, vars->string );
286 varname = variable->value;
288 if ( ( colon = strchr( varname, MAGIC_COLON ) ) )
290 string_truncate( variable, colon - varname );
291 var_edit_parse( colon + 1, &edits );
294 /* Look for [x-y] subscripting. sub1 and sub2 are x and y. */
295 if ( ( bracket = strchr( varname, MAGIC_LEFT ) ) )
297 /* Make all syntax errors in [] subscripting result in the same
298 * behavior: silenty return an empty expansion (by setting sub2
299 * = 0). Brute force parsing; May get moved into yacc someday.
302 char * s = bracket + 1;
304 string_truncate( variable, bracket - varname );
306 do /* so we can use "break" */
308 /* Allow negative indexes. */
309 if ( !isdigit( *s ) && ( *s != '-' ) )
316 /* Skip over the first symbol, which is either a digit or dash. */
318 while ( isdigit( *s ) ) ++s;
320 if ( *s == MAGIC_RIGHT )
334 if ( *s == MAGIC_RIGHT )
340 if ( !isdigit( *s ) && ( *s != '-' ) )
346 /* First, compute the index of the last element. */
348 while ( isdigit( *++s ) );
350 if ( *s != MAGIC_RIGHT )
355 /* Anything but the end of the string, or the colon introducing
356 * a modifier is a syntax error.
359 if ( *s && ( *s != MAGIC_COLON ) )
365 /* Get variable value, with special handling for $(<), $(>), $(n).
369 if ( varname[0] == '<' )
370 value = lol_get( lol, 0 );
371 else if ( varname[0] == '>' )
372 value = lol_get( lol, 1 );
373 else if ( ( varname[0] >= '1' ) && ( varname[0] <= '9' ) )
374 value = lol_get( lol, varname[0] - '1' );
375 else if( varname[0] == '1' && varname[1] >= '0' &&
376 varname[1] <= '9' && !varname[2] )
377 value = lol_get( lol, varname[1] - '0' + 10 - 1 );
381 value = var_get( varname );
383 /* Handle negitive indexes: part two. */
385 int length = list_length( value );
388 sub1 = length + sub1;
393 sub2 = length + 1 + sub2 - sub1;
396 /* The "sub2 < 0" test handles the semantic error of sub2 <
403 /* The fast path: $(x) - just copy the variable value. This is only
406 if ( ( out == out_buf ) && !bracket && !colon && ( in == end ) )
408 string_free( variable );
409 l = list_copy( l, value );
413 /* Handle start subscript. */
414 while ( ( sub1 > 0 ) && value )
415 --sub1, value = list_next( value );
417 /* Empty w/ :E=default?. */
418 if ( !value && colon && edits.empty.ptr )
419 evalue = value = list_new( L0, newstr( edits.empty.ptr ) );
421 /* For each variable value. */
423 for ( ; value; value = list_next( value ) )
426 size_t postfix_start;
428 /* Handle end subscript (length actually). */
430 if ( sub2 >= 0 && --sub2 < 0 )
433 string_truncate( buf, prefix_length );
435 /* Apply : mods, if present */
437 if ( colon && edits.filemods )
438 var_edit_file( value->string, out1, &edits );
440 string_append( out1, value->string );
442 if ( colon && ( edits.upshift || edits.downshift || edits.to_slashes || edits.to_windows ) )
443 var_edit_shift( out1, &edits );
445 /* Handle :J=joinval */
446 /* If we have more values for this var, just keep appending them
447 * (using the join value) rather than creating separate LIST
450 if ( colon && edits.join.ptr &&
451 ( list_next( value ) || list_next( vars ) ) )
453 string_append( out1, edits.join.ptr );
457 string_append( buf, out1->value );
461 /* If no remainder, append result to output chain. */
464 l = list_new( l, newstr( buf->value ) );
468 /* For each remainder, append the complete string to the output
469 * chain. Remember the end of the variable expansion so we can
470 * just tack on each instance of 'remainder'.
472 postfix_start = buf->size;
473 for ( rem = remainder; rem; rem = list_next( rem ) )
475 string_truncate( buf, postfix_start );
476 string_append( buf, rem->string );
477 l = list_new( l, newstr( buf->value ) );
482 /* Toss used empty. */
486 string_free( variable );
489 /* variables & remainder were gifts from var_expand and must be freed. */
490 if ( variables ) list_free( variables );
491 if ( remainder ) list_free( remainder );
495 printf( "expanded to " );
507 * var_edit_parse() - parse : modifiers into PATHNAME structure
509 * The : modifiers in a $(varname:modifier) currently support replacing or
510 * omitting elements of a filename, and so they are parsed into a PATHNAME
511 * structure (which contains pointers into the original string).
513 * Modifiers of the form "X=value" replace the component X with the given value.
514 * Modifiers without the "=value" cause everything but the component X to be
515 * omitted. X is one of:
522 * R root directory - prepended to whole path
528 * -> leave the original component xxx
530 * f->f_xxx.ptr = string
531 * f->f_xxx.len = strlen( string )
532 * -> replace component xxx with string
536 * -> omit component xxx
538 * var_edit_file() below and path_build() obligingly follow this convention.
541 static void var_edit_parse( char * mods, VAR_EDITS * edits )
544 memset( (char *)edits, 0, sizeof( *edits ) );
553 case 'L': edits->downshift = 1; continue;
554 case 'U': edits->upshift = 1; continue;
555 case 'P': edits->parent = edits->filemods = 1; continue;
556 case 'E': fp = &edits->empty; goto strval;
557 case 'J': fp = &edits->join; goto strval;
558 case 'G': fp = &edits->f.f_grist; goto fileval;
559 case 'R': fp = &edits->f.f_root; goto fileval;
560 case 'D': fp = &edits->f.f_dir; goto fileval;
561 case 'B': fp = &edits->f.f_base; goto fileval;
562 case 'S': fp = &edits->f.f_suffix; goto fileval;
563 case 'M': fp = &edits->f.f_member; goto fileval;
564 case 'T': edits->to_slashes = 1; continue;
565 case 'W': edits->to_windows = 1; continue;
567 return; /* Should complain, but so what... */
571 /* Handle :CHARS, where each char (without a following =) selects a
572 * particular file path element. On the first such char, we deselect all
573 * others (by setting ptr = "", len = 0) and for each char we select
574 * that element (by setting ptr = 0).
583 for ( i = 0; i < 6; ++i )
585 edits->f.part[ i ].len = 0;
586 edits->f.part[ i ].ptr = "";
595 /* Handle :X=value, or :X */
601 else if ( ( p = strchr( mods, MAGIC_COLON ) ) )
611 fp->len = strlen( mods );
619 * var_edit_file() - copy input target name to output, modifying filename.
622 static void var_edit_file( char * in, string * out, VAR_EDITS * edits )
626 /* Parse apart original filename, putting parts into "pathname". */
627 path_parse( in, &pathname );
629 /* Replace any pathname with edits->f */
630 if ( edits->f.f_grist .ptr ) pathname.f_grist = edits->f.f_grist;
631 if ( edits->f.f_root .ptr ) pathname.f_root = edits->f.f_root;
632 if ( edits->f.f_dir .ptr ) pathname.f_dir = edits->f.f_dir;
633 if ( edits->f.f_base .ptr ) pathname.f_base = edits->f.f_base;
634 if ( edits->f.f_suffix.ptr ) pathname.f_suffix = edits->f.f_suffix;
635 if ( edits->f.f_member.ptr ) pathname.f_member = edits->f.f_member;
637 /* If requested, modify pathname to point to parent. */
639 path_parent( &pathname );
641 /* Put filename back together. */
642 path_build( &pathname, out, 0 );
647 * var_edit_shift() - do upshift/downshift mods.
650 static void var_edit_shift( string * out, VAR_EDITS * edits )
652 /* Handle upshifting, downshifting and slash translation now. */
654 for ( p = out->value; *p; ++p)
656 if ( edits->upshift )
658 else if ( edits->downshift )
660 if ( edits->to_slashes && ( *p == '\\' ) )
663 if ( edits->to_windows )
665 char result[ MAX_PATH + 1 ];
666 cygwin_conv_to_win32_path( out->value, result );
667 assert( strlen( result ) <= MAX_PATH );
669 string_copy( out, result );
673 out->size = p - out->value;
678 void var_expand_unit_test()
683 LIST * expected = list_new( list_new( L0, newstr( "axb" ) ), newstr( "ayb" ) );
685 char axyb[] = "a$(xy)b";
686 char azb[] = "a$($(z))b";
687 char path[] = "$(p:W)";
691 cygwin_conv_to_posix_path( "c:\\foo\\bar", cygpath );
693 char cygpath[] = "/cygdrive/c/foo/bar";
697 var_set( "xy", list_new( list_new( L0, newstr( "x" ) ), newstr( "y" ) ), VAR_SET );
698 var_set( "z", list_new( L0, newstr( "xy" ) ), VAR_SET );
699 var_set( "p", list_new( L0, newstr( cygpath ) ), VAR_SET );
701 l = var_expand( 0, axyb, axyb + sizeof( axyb ) - 1, lol, 0 );
702 for ( l2 = l, e2 = expected; l2 && e2; l2 = list_next( l2 ), e2 = list_next( e2 ) )
703 assert( !strcmp( e2->string, l2->string ) );
708 l = var_expand( 0, azb, azb + sizeof( azb ) - 1, lol, 0 );
709 for ( l2 = l, e2 = expected; l2 && e2; l2 = list_next( l2 ), e2 = list_next( e2 ) )
710 assert( !strcmp( e2->string, l2->string ) );
715 l = var_expand( 0, path, path + sizeof( path ) - 1, lol, 0 );
717 assert( list_next( l ) == 0 );
719 /* On some installations of cygwin the drive letter is expanded to other
720 * case. This has been reported to be the case if cygwin has been installed
721 * to C:\ as opposed to C:\cygwin. Since case of the drive letter will not
722 * matter, we allow for both.
724 assert( !strcmp( l->string, "c:\\foo\\bar" ) ||
725 !strcmp( l->string, "C:\\foo\\bar" ) );
727 assert( !strcmp( l->string, cygpath ) );
730 list_free( expected );