cosmetix
[k8jam.git] / expand.c
blob6f98b4b4da59a0e088de8cbff1d440bc2319a622
1 /*
2 * Copyright 1993-2002 Christopher Seiwald and Perforce Software, Inc.
4 * This file is part of Jam - see jam.c for Copyright information.
5 */
7 /*
8 * expand.c - expand a buffer, given variable values
10 * External routines:
12 * var_expand() - variable-expand input string into list of strings
14 * Internal routines:
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
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()
32 # include "jam.h"
33 # include "lists.h"
34 # include "variable.h"
35 # include "expand.h"
36 # include "pathsys.h"
37 # include "newstr.h"
40 typedef struct {
41 PATHNAME f; /* :GDBSMR -- pieces */
42 char parent; /* :P -- go to parent directory */
43 char filemods; /* one of the above applied */
44 char downshift; /* :L -- downshift result */
45 char upshift; /* :U -- upshift result */
46 char quote; /* :Q -- quote */
47 PATHPART empty; /* :E -- default for empties */
48 PATHPART join; /* :J -- join list with char */
49 } VAR_EDITS ;
52 static void var_edit_parse (const char *mods, VAR_EDITS *edits);
53 static void var_edit_file (const char *in, char *out, VAR_EDITS *edits);
54 static void var_edit_shift (char *out, VAR_EDITS *edits);
55 static void var_edit_quote (char *out);
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,
66 * except that since variables can contain multiple values the result
67 * of variable expansion may contain multiple values (a list). Properly
68 * performs "product" operations that occur in "$(var1)xxx$(var2)" or
69 * even "$($(var2))".
71 * Returns a newly created list.
73 LIST *var_expand (LIST *l, const char *in, const char *end, LOL *lol, int cancopyin) {
74 char out_buf[MAXSYM];
75 char *out = out_buf;
76 const char *inp = in;
77 char *ov; /* for temp copy of variable in outbuf */
78 int depth;
80 if (DEBUG_VAREXP) printf("expand '%.*s'\n", (int)(end-in), in);
81 /* This gets alot of cases: $(<) and $(>) */
82 if (end - in == 4 && in[0] == '$' && in[1] == '(' && in[3] == ')') {
83 switch(in[2]) {
84 case '1': case '<': return list_copy(l, lol_get(lol, 0));
85 case '2': case '>': return list_copy(l, lol_get(lol, 1));
88 /* Just try simple copy of in to out. */
89 while (in < end) {
90 if ((*out++ = *in++) == '$' && *in == '(') goto expand;
93 /* No variables expanded - just add copy of input string to list. */
95 /* Cancopyin is an optimization: if the input was already a list */
96 /* item, we can use the copystr() to put it on the new list. */
97 /* Otherwise, we use the slower newstr(). */
98 *out = '\0';
100 if (cancopyin) return list_new(l, inp, 1);
101 else return list_new(l, out_buf, 0);
103 expand:
105 * Input so far (ignore blanks):
107 * stuff-in-outbuf $(variable) remainder
108 * ^ ^
109 * in end
110 * Output so far:
112 * stuff-in-outbuf $
113 * ^ ^
114 * out_buf out
117 * We just copied the $ of $(...), so back up one on the output.
118 * We now find the matching close paren, copying the variable and
119 * modifiers between the $(and) temporarily into out_buf, so that
120 * we can replace :'s with MAGIC_COLON. This is necessary to avoid
121 * being confused by modifier values that are variables containing
122 * :'s. Ugly.
124 depth = 1;
125 out--, in++;
126 ov = out;
127 while (in < end && depth) {
128 switch (*ov++ = *in++) {
129 case '(': depth++; break;
130 case ')': depth--; break;
131 case ':': ov[-1] = MAGIC_COLON; break;
132 case '[': ov[-1] = MAGIC_LEFT; break;
133 case ']': ov[-1] = MAGIC_RIGHT; break;
136 /* Copied) - back up. */
137 ov--;
139 * Input so far (ignore blanks):
141 * stuff-in-outbuf $(variable) remainder
142 * ^ ^
143 * in end
144 * Output so far:
146 * stuff-in-outbuf variable
147 * ^ ^ ^
148 * out_buf out ov
150 * Later we will overwrite 'variable' in out_buf, but we'll be
151 * done with it by then. 'variable' may be a multi-element list,
152 * so may each value for '$(variable element)', and so may 'remainder'.
153 * Thus we produce a product of three lists.
156 LIST *variables = 0;
157 LIST *remainder = 0;
158 LIST *vars;
160 /* Recursively expand variable name & rest of input */
161 if (out < ov) variables = var_expand(L0, out, ov, lol, 0);
162 if (in < end) remainder = var_expand(L0, in, end, lol, 0);
163 /* Now produce the result chain */
164 /* For each variable name */
165 for (vars = variables; vars; vars = list_next(vars)) {
166 LIST *value, *evalue = 0;
167 char *colon;
168 char *bracket;
169 char varname[MAXSYM];
170 int sub1 = 0, sub2 = -1;
171 VAR_EDITS edits;
173 /* Look for a : modifier in the variable name */
174 /* Must copy into varname so we can modify it */
175 strcpy(varname, vars->string);
176 if ((colon = strchr(varname, MAGIC_COLON))) {
177 *colon = '\0';
178 var_edit_parse(colon+1, &edits);
180 /* Look for [x-y] subscripting */
181 /* sub1 is x (0 default) */
182 /* sub2 is length (-1 means forever) */
183 if ((bracket = strchr(varname, MAGIC_LEFT))) {
184 char *dash;
186 if ((dash = strchr(bracket+1, '-'))) *dash = '\0';
187 sub1 = atoi(bracket+1)-1;
188 if (!dash) sub2 = 1;
189 else if (!dash[1] || dash[1] == MAGIC_RIGHT) sub2 = -1;
190 else sub2 = atoi(dash+1)-sub1;
191 *bracket = '\0';
193 /* Get variable value, specially handling $(<), $(>), $(n) */
194 if (varname[0] == '<' && !varname[1]) value = lol_get(lol, 0);
195 else if (varname[0] == '>' && !varname[1]) value = lol_get(lol, 1);
196 else if (varname[0] >= '1' && varname[0] <= '9' && !varname[1]) value = lol_get(lol, varname[0]-'1');
197 else value = var_get(varname);
198 /* The fast path: $(x) - just copy the variable value. */
199 /* This is only an optimization */
200 if (out == out_buf && !bracket && !colon && in == end) {
201 l = list_copy(l, value);
202 continue;
204 /* Handle start subscript */
205 while (sub1 > 0 && value) {
206 --sub1, value = list_next(value);
208 /* Empty w/ :E=default? */
209 if (!value && colon && edits.empty.ptr) evalue = value = list_new(L0, edits.empty.ptr, 0);
210 /* For each variable value */
211 for (; value; value = list_next(value)) {
212 LIST *rem;
213 char *out1;
215 /* Handle end subscript (length actually) */
216 if (sub2 >= 0 && --sub2 < 0) break;
217 /* Apply : mods, if present */
218 if (colon && edits.filemods) var_edit_file(value->string, out, &edits);
219 else strcpy(out, value->string);
220 if (colon && (edits.upshift || edits.downshift)) var_edit_shift(out, &edits);
221 if (colon && edits.quote) var_edit_quote(out);
222 /* Handle :J=joinval */
223 /* If we have more values for this var, just */
224 /* keep appending them (with the join value) */
225 /* rather than creating separate LIST elements. */
226 if (colon && edits.join.ptr && (list_next(value) || list_next(vars))) {
227 out += strlen(out);
228 strcpy(out, edits.join.ptr);
229 out += strlen(out);
230 continue;
232 /* If no remainder, append result to output chain. */
233 if (in == end) {
234 l = list_new(l, out_buf, 0);
235 continue;
237 /* For each remainder, append the complete string */
238 /* to the output chain. */
239 /* Remember the end of the variable expansion so */
240 /* we can just tack on each instance of 'remainder' */
241 out1 = out+strlen(out);
242 for (rem = remainder; rem; rem = list_next(rem)) {
243 strcpy(out1, rem->string);
244 l = list_new(l, out_buf, 0);
247 /* Toss used empty */
248 if (evalue) list_free(evalue);
249 } /* for */
250 /* variables & remainder were gifts from var_expand */
251 /* and must be freed */
252 if (variables) list_free(variables);
253 if (remainder) list_free(remainder);
254 if (DEBUG_VAREXP) {
255 printf("expanded to ");
256 list_print(l);
257 printf("\n");
259 return l;
265 * var_edit_parse() - parse : modifiers into PATHNAME structure
267 * The : modifiers in a $(varname:modifier) currently support replacing
268 * or omitting elements of a filename, and so they are parsed into a
269 * PATHNAME structure (which contains pointers into the original string).
271 * Modifiers of the form "X=value" replace the component X with
272 * the given value. Modifiers without the "=value" cause everything
273 * but the component X to be omitted. X is one of:
275 * G <grist>
276 * D directory name
277 * B base name
278 * S .suffix
279 * M (member)
280 * R root directory - prepended to whole path
282 * This routine sets:
284 * f->f_xxx.ptr = 0
285 * f->f_xxx.len = 0
286 * -> leave the original component xxx
288 * f->f_xxx.ptr = string
289 * f->f_xxx.len = strlen(string)
290 * -> replace component xxx with string
292 * f->f_xxx.ptr = ""
293 * f->f_xxx.len = 0
294 * -> omit component xxx
296 * var_edit_file() below and path_build() obligingly follow this convention.
298 static void var_edit_parse (const char *mods, VAR_EDITS *edits) {
299 int havezeroed = 0;
300 memset((char *)edits, 0, sizeof(*edits));
301 while (*mods) {
302 char *p;
303 PATHPART *fp;
305 switch (*mods++) {
306 case 'L': edits->downshift = 1; continue;
307 case 'U': edits->upshift = 1; continue;
308 case 'Q': edits->quote = 1; continue;
309 case 'P': edits->parent = edits->filemods = 1; continue;
310 case 'E': fp = &edits->empty; goto strval;
311 case 'J': fp = &edits->join; goto strval;
312 case 'G': fp = &edits->f.f_grist; goto fileval;
313 case 'R': fp = &edits->f.f_root; goto fileval;
314 case 'D': fp = &edits->f.f_dir; goto fileval;
315 case 'B': fp = &edits->f.f_base; goto fileval;
316 case 'S': fp = &edits->f.f_suffix; goto fileval;
317 case 'M': fp = &edits->f.f_member; goto fileval;
318 default: return; /* should complain, but so what... */
320 fileval:
321 /* Handle :CHARS, where each char (without a following =) */
322 /* selects a particular file path element. On the first such */
323 /* char, we deselect all others (by setting ptr = "", len = 0) */
324 /* and for each char we select that element (by setting ptr = 0) */
325 edits->filemods = 1;
326 if (*mods != '=') {
327 int i;
329 if (!havezeroed++) {
330 for (i = 0; i < 6; i++) {
331 edits->f.part[i].len = 0;
332 edits->f.part[i].ptr = "";
335 fp->ptr = 0;
336 continue;
338 strval:
339 /* Handle :X=value, or :X */
340 if (*mods != '=') {
341 fp->ptr = "";
342 fp->len = 0;
343 } else if ((p = strchr(mods, MAGIC_COLON))) {
344 *p = 0;
345 fp->ptr = ++mods;
346 fp->len = p-mods;
347 mods = p+1;
348 } else {
349 fp->ptr = ++mods;
350 fp->len = strlen(mods);
351 mods += fp->len;
358 * var_edit_file() - copy input target name to output, modifying filename
360 static void var_edit_file (const char *in, char *out, VAR_EDITS *edits) {
361 PATHNAME pathname;
363 /* Parse apart original filename, putting parts into "pathname" */
364 path_parse(in, &pathname);
365 /* Replace any pathname with edits->f */
366 if (edits->f.f_grist.ptr) pathname.f_grist = edits->f.f_grist;
367 if (edits->f.f_root.ptr) pathname.f_root = edits->f.f_root;
368 if (edits->f.f_dir.ptr) pathname.f_dir = edits->f.f_dir;
369 if (edits->f.f_base.ptr) pathname.f_base = edits->f.f_base;
370 if (edits->f.f_suffix.ptr) pathname.f_suffix = edits->f.f_suffix;
371 if (edits->f.f_member.ptr) pathname.f_member = edits->f.f_member;
372 /* If requested, modify pathname to point to parent */
373 if (edits->parent) path_parent(&pathname);
374 /* Put filename back together */
375 path_build(&pathname, out, 0);
380 * var_edit_shift() - do upshift/downshift mods
382 static void var_edit_shift (char *out, VAR_EDITS *edits) {
383 /* Handle upshifting, downshifting now */
384 if (edits->upshift) {
385 for(; *out; ++out) *out = toupper(*out);
386 } else if (edits->downshift) {
387 for(; *out; ++out) *out = tolower(*out);
392 static int needToScreen (char ch) {
393 unsigned char uch = (unsigned char)ch;
394 if (!uch) return 0;
395 if (uch <= '*' || uch == '`' ||
396 (uch >= ';' && uch <= '<') ||
397 (uch >= '>' && uch <= '?') ||
398 (uch >= '[' && uch <= ']') ||
399 (uch >= '{' && uch <= '}')) return 1;
400 return 0;
404 static void var_edit_quote (char *out) {
405 /* Handle quoting now */
406 int count;
407 char *p = out;
408 char *q;
410 count = 0;
411 for (p = out; *p; ++p) {
412 if (needToScreen(*p)) count++;
414 q = p+count;
415 for (; p >= out;) {
416 if (needToScreen(*p)) {
417 *q-- = *p--;
418 *q-- = '\\';
419 } else *q-- = *p--;