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