1/* 2 * Copyright 1993-2002 Christopher Seiwald and Perforce Software, Inc. 3 * 4 * This file is part of Jam - see jam.c for Copyright information. 5 */ 6 7/* 8 * expand.c - expand a buffer, given variable values 9 * 10 * External routines: 11 * 12 * var_expand() - variable-expand input string into list of strings 13 * 14 * Internal routines: 15 * 16 * var_edit_parse() - parse : modifiers into PATHNAME structure 17 * var_edit_file() - copy input target name to output, modifying filename 18 * var_edit_shift() - do upshift/downshift mods 19 * 20 * 01/25/94 (seiwald) - $(X)$(UNDEF) was expanding like plain $(X) 21 * 04/13/94 (seiwald) - added shorthand L0 for null list pointer 22 * 01/20/00 (seiwald) - Upgraded from K&R to ANSI C 23 * 01/11/01 (seiwald) - added support for :E=emptyvalue, :J=joinval 24 * 01/13/01 (seiwald) - :UDJE work on non-filename strings 25 * 02/19/01 (seiwald) - make $($(var):J=x) join multiple values of var 26 * 01/25/02 (seiwald) - fixed broken $(v[1-]), by ian godin 27 * 10/22/02 (seiwald) - list_new() now does its own newstr()/copystr() 28 * 11/04/02 (seiwald) - const-ing for string literals 29 * 12/30/02 (armstrong) - fix out-of-bounds access in var_expand() 30 */ 31 32# include "jam.h" 33# include "lists.h" 34# include "variable.h" 35# include "expand.h" 36# include "pathsys.h" 37# include "newstr.h" 38 39typedef struct { 40 PATHNAME f; /* :GDBSMR -- pieces */ 41 char parent; /* :P -- go to parent directory */ 42 char filemods; /* one of the above applied */ 43 char downshift; /* :L -- downshift result */ 44 char upshift; /* :U -- upshift result */ 45 PATHPART empty; /* :E -- default for empties */ 46 PATHPART join; /* :J -- join list with char */ 47} VAR_EDITS ; 48 49static void var_edit_parse( const char *mods, VAR_EDITS *edits ); 50static void var_edit_file( const char *in, char *out, VAR_EDITS *edits ); 51static void var_edit_shift( char *out, VAR_EDITS *edits ); 52 53# define MAGIC_COLON '\001' 54# define MAGIC_LEFT '\002' 55# define MAGIC_RIGHT '\003' 56 57/* 58 * var_expand() - variable-expand input string into list of strings 59 * 60 * Would just copy input to output, performing variable expansion, 61 * except that since variables can contain multiple values the result 62 * of variable expansion may contain multiple values (a list). Properly 63 * performs "product" operations that occur in "$(var1)xxx$(var2)" or 64 * even "$($(var2))". 65 * 66 * Returns a newly created list. 67 */ 68 69LIST * 70var_expand( 71 LIST *l, 72 const char *in, 73 const char *end, 74 LOL *lol, 75 int cancopyin ) 76{ 77 char out_buf[ MAXSYM ]; 78 char *out = out_buf; 79 const char *inp = in; 80 char *ov; /* for temp copy of variable in outbuf */ 81 int depth; 82 83 if( DEBUG_VAREXP ) 84 printf( "expand '%.*s'\n", (int)(end - in), in ); 85 86 /* This gets alot of cases: $(<) and $(>) */ 87 88 if( end - in == 4 && in[0] == '$' && in[1] == '(' && in[3] == ')' ) 89 { 90 switch( in[2] ) 91 { 92 case '1': 93 case '<': 94 return list_copy( l, lol_get( lol, 0 ) ); 95 96 case '2': 97 case '>': 98 return list_copy( l, lol_get( lol, 1 ) ); 99 } 100 } 101 102 /* Just try simple copy of in to out. */ 103 104 while( in < end ) 105 if( ( *out++ = *in++ ) == '$' && *in == '(' ) 106 goto expand; 107 108 /* No variables expanded - just add copy of input string to list. */ 109 110 /* Cancopyin is an optimization: if the input was already a list */ 111 /* item, we can use the copystr() to put it on the new list. */ 112 /* Otherwise, we use the slower newstr(). */ 113 114 *out = '\0'; 115 116 if( cancopyin ) 117 return list_new( l, inp, 1 ); 118 else 119 return list_new( l, out_buf, 0 ); 120 121 expand: 122 /* 123 * Input so far (ignore blanks): 124 * 125 * stuff-in-outbuf $(variable) remainder 126 * ^ ^ 127 * in end 128 * Output so far: 129 * 130 * stuff-in-outbuf $ 131 * ^ ^ 132 * out_buf out 133 * 134 * 135 * We just copied the $ of $(...), so back up one on the output. 136 * We now find the matching close paren, copying the variable and 137 * modifiers between the $( and ) temporarily into out_buf, so that 138 * we can replace :'s with MAGIC_COLON. This is necessary to avoid 139 * being confused by modifier values that are variables containing 140 * :'s. Ugly. 141 */ 142 143 depth = 1; 144 out--, in++; 145 ov = out; 146 147 while( in < end && depth ) 148 { 149 switch( *ov++ = *in++ ) 150 { 151 case '(': depth++; break; 152 case ')': depth--; break; 153 case ':': ov[-1] = MAGIC_COLON; break; 154 case '[': ov[-1] = MAGIC_LEFT; break; 155 case ']': ov[-1] = MAGIC_RIGHT; break; 156 } 157 } 158 159 /* Copied ) - back up. */ 160 161 ov--; 162 163 /* 164 * Input so far (ignore blanks): 165 * 166 * stuff-in-outbuf $(variable) remainder 167 * ^ ^ 168 * in end 169 * Output so far: 170 * 171 * stuff-in-outbuf variable 172 * ^ ^ ^ 173 * out_buf out ov 174 * 175 * Later we will overwrite 'variable' in out_buf, but we'll be 176 * done with it by then. 'variable' may be a multi-element list, 177 * so may each value for '$(variable element)', and so may 'remainder'. 178 * Thus we produce a product of three lists. 179 */ 180 181 { 182 LIST *variables = 0; 183 LIST *remainder = 0; 184 LIST *vars; 185 186 /* Recursively expand variable name & rest of input */ 187 188 if( out < ov ) 189 variables = var_expand( L0, out, ov, lol, 0 ); 190 if( in < end ) 191 remainder = var_expand( L0, in, end, lol, 0 ); 192 193 /* Now produce the result chain */ 194 195 /* For each variable name */ 196 197 for( vars = variables; vars; vars = list_next( vars ) ) 198 { 199 LIST *value, *evalue = 0; 200 char *colon; 201 char *bracket; 202 char varname[ MAXSYM ]; 203 int sub1 = 0, sub2 = -1; 204 VAR_EDITS edits; 205 206 /* Look for a : modifier in the variable name */ 207 /* Must copy into varname so we can modify it */ 208 209 if (strlen(vars->string) > MAXSYM) { 210 printf("MAXSYM is too low! Need at least %d\n", l); 211 exit(-1); 212 } 213 strcpy( varname, vars->string ); 214 215 if( colon = strchr( varname, MAGIC_COLON ) ) 216 { 217 *colon = '\0'; 218 var_edit_parse( colon + 1, &edits ); 219 } 220 221 /* Look for [x-y] subscripting */ 222 /* sub1 is x (0 default) */ 223 /* sub2 is length (-1 means forever) */ 224 225 if( bracket = strchr( varname, MAGIC_LEFT ) ) 226 { 227 char *dash; 228 229 if( dash = strchr( bracket + 1, '-' ) ) 230 *dash = '\0'; 231 232 sub1 = atoi( bracket + 1 ) - 1; 233 234 if( !dash ) 235 sub2 = 1; 236 else if( !dash[1] || dash[1] == MAGIC_RIGHT ) 237 sub2 = -1; 238 else 239 sub2 = atoi( dash + 1 ) - sub1; 240 241 *bracket = '\0'; 242 } 243 244 /* Get variable value, specially handling $(<), $(>), $(n) */ 245 246 if( varname[0] == '<' && !varname[1] ) 247 value = lol_get( lol, 0 ); 248 else if( varname[0] == '>' && !varname[1] ) 249 value = lol_get( lol, 1 ); 250 else if( varname[0] >= '1' && varname[0] <= '9' && !varname[1] ) 251 value = lol_get( lol, varname[0] - '1' ); 252 else 253 value = var_get( varname ); 254 255 /* The fast path: $(x) - just copy the variable value. */ 256 /* This is only an optimization */ 257 258 if( out == out_buf && !bracket && !colon && in == end ) 259 { 260 l = list_copy( l, value ); 261 continue; 262 } 263 264 /* Handle start subscript */ 265 266 while( sub1 > 0 && value ) 267 --sub1, value = list_next( value ); 268 269 /* Empty w/ :E=default? */ 270 271 if( !value && colon && edits.empty.ptr ) 272 evalue = value = list_new( L0, edits.empty.ptr, 0 ); 273 274 /* For each variable value */ 275 276 for( ; value; value = list_next( value ) ) 277 { 278 LIST *rem; 279 char *out1; 280 281 if (out - out_buf > MAXSYM) { 282 printf("MAXSYM is too low!\n"); 283 exit(-1); 284 } 285 /* Handle end subscript (length actually) */ 286 287 if( sub2 >= 0 && --sub2 < 0 ) 288 break; 289 290 /* Apply : mods, if present */ 291 292 if( colon && edits.filemods ) 293 var_edit_file( value->string, out, &edits ); 294 else 295 strcpy( out, value->string ); 296 297 if( colon && ( edits.upshift || edits.downshift ) ) 298 var_edit_shift( out, &edits ); 299 300 /* Handle :J=joinval */ 301 /* If we have more values for this var, just */ 302 /* keep appending them (with the join value) */ 303 /* rather than creating separate LIST elements. */ 304 305 if( colon && edits.join.ptr && 306 ( list_next( value ) || list_next( vars ) ) ) 307 { 308 out += strlen( out ); 309 strcpy( out, edits.join.ptr ); 310 out += strlen( out ); 311 continue; 312 } 313 314 /* If no remainder, append result to output chain. */ 315 316 if( in == end ) 317 { 318 l = list_new( l, out_buf, 0 ); 319 continue; 320 } 321 322 /* For each remainder, append the complete string */ 323 /* to the output chain. */ 324 /* Remember the end of the variable expansion so */ 325 /* we can just tack on each instance of 'remainder' */ 326 327 out1 = out + strlen( out ); 328 329 for( rem = remainder; rem; rem = list_next( rem ) ) 330 { 331 strcpy( out1, rem->string ); 332 l = list_new( l, out_buf, 0 ); 333 } 334 } 335 336 /* Toss used empty */ 337 338 if( evalue ) 339 list_free( evalue ); 340 } 341 342 /* variables & remainder were gifts from var_expand */ 343 /* and must be freed */ 344 345 if( variables ) 346 list_free( variables ); 347 if( remainder) 348 list_free( remainder ); 349 350 if( DEBUG_VAREXP ) 351 { 352 printf( "expanded to " ); 353 list_print( l ); 354 printf( "\n" ); 355 } 356 357 return l; 358 } 359} 360 361/* 362 * var_edit_parse() - parse : modifiers into PATHNAME structure 363 * 364 * The : modifiers in a $(varname:modifier) currently support replacing 365 * or omitting elements of a filename, and so they are parsed into a 366 * PATHNAME structure (which contains pointers into the original string). 367 * 368 * Modifiers of the form "X=value" replace the component X with 369 * the given value. Modifiers without the "=value" cause everything 370 * but the component X to be omitted. X is one of: 371 * 372 * G <grist> 373 * D directory name 374 * B base name 375 * S .suffix 376 * M (member) 377 * R root directory - prepended to whole path 378 * 379 * This routine sets: 380 * 381 * f->f_xxx.ptr = 0 382 * f->f_xxx.len = 0 383 * -> leave the original component xxx 384 * 385 * f->f_xxx.ptr = string 386 * f->f_xxx.len = strlen( string ) 387 * -> replace component xxx with string 388 * 389 * f->f_xxx.ptr = "" 390 * f->f_xxx.len = 0 391 * -> omit component xxx 392 * 393 * var_edit_file() below and path_build() obligingly follow this convention. 394 */ 395 396static void 397var_edit_parse( 398 const char *mods, 399 VAR_EDITS *edits ) 400{ 401 int havezeroed = 0; 402 memset( (char *)edits, 0, sizeof( *edits ) ); 403 404 while( *mods ) 405 { 406 char *p; 407 PATHPART *fp; 408 409 switch( *mods++ ) 410 { 411 case 'L': edits->downshift = 1; continue; 412 case 'U': edits->upshift = 1; continue; 413 case 'P': edits->parent = edits->filemods = 1; continue; 414 case 'E': fp = &edits->empty; goto strval; 415 case 'J': fp = &edits->join; goto strval; 416 case 'G': fp = &edits->f.f_grist; goto fileval; 417 case 'R': fp = &edits->f.f_root; goto fileval; 418 case 'D': fp = &edits->f.f_dir; goto fileval; 419 case 'B': fp = &edits->f.f_base; goto fileval; 420 case 'S': fp = &edits->f.f_suffix; goto fileval; 421 case 'M': fp = &edits->f.f_member; goto fileval; 422 423 default: return; /* should complain, but so what... */ 424 } 425 426 fileval: 427 428 /* Handle :CHARS, where each char (without a following =) */ 429 /* selects a particular file path element. On the first such */ 430 /* char, we deselect all others (by setting ptr = "", len = 0) */ 431 /* and for each char we select that element (by setting ptr = 0) */ 432 433 edits->filemods = 1; 434 435 if( *mods != '=' ) 436 { 437 int i; 438 439 if( !havezeroed++ ) 440 for( i = 0; i < 6; i++ ) 441 { 442 edits->f.part[ i ].len = 0; 443 edits->f.part[ i ].ptr = ""; 444 } 445 446 fp->ptr = 0; 447 continue; 448 } 449 450 strval: 451 452 /* Handle :X=value, or :X */ 453 454 if( *mods != '=' ) 455 { 456 fp->ptr = ""; 457 fp->len = 0; 458 } 459 else if( p = strchr( mods, MAGIC_COLON ) ) 460 { 461 *p = 0; 462 fp->ptr = ++mods; 463 fp->len = p - mods; 464 mods = p + 1; 465 } 466 else 467 { 468 fp->ptr = ++mods; 469 fp->len = strlen( mods ); 470 mods += fp->len; 471 } 472 } 473} 474 475/* 476 * var_edit_file() - copy input target name to output, modifying filename 477 */ 478 479static void 480var_edit_file( 481 const char *in, 482 char *out, 483 VAR_EDITS *edits ) 484{ 485 PATHNAME pathname; 486 487 /* Parse apart original filename, putting parts into "pathname" */ 488 489 path_parse( in, &pathname ); 490 491 /* Replace any pathname with edits->f */ 492 493 if( edits->f.f_grist.ptr ) 494 pathname.f_grist = edits->f.f_grist; 495 496 if( edits->f.f_root.ptr ) 497 pathname.f_root = edits->f.f_root; 498 499 if( edits->f.f_dir.ptr ) 500 pathname.f_dir = edits->f.f_dir; 501 502 if( edits->f.f_base.ptr ) 503 pathname.f_base = edits->f.f_base; 504 505 if( edits->f.f_suffix.ptr ) 506 pathname.f_suffix = edits->f.f_suffix; 507 508 if( edits->f.f_member.ptr ) 509 pathname.f_member = edits->f.f_member; 510 511 /* If requested, modify pathname to point to parent */ 512 513 if( edits->parent ) 514 path_parent( &pathname ); 515 516 /* Put filename back together */ 517 518 path_build( &pathname, out, 0 ); 519} 520 521/* 522 * var_edit_shift() - do upshift/downshift mods 523 */ 524 525static void 526var_edit_shift( 527 char *out, 528 VAR_EDITS *edits ) 529{ 530 /* Handle upshifting, downshifting now */ 531 532 if( edits->upshift ) 533 { 534 for( ; *out; ++out ) 535 *out = toupper( *out ); 536 } 537 else if( edits->downshift ) 538 { 539 for( ; *out; ++out ) 540 *out = tolower( *out ); 541 } 542} 543