Annotation of src/usr.bin/make/for.c, Revision 1.45
1.45 ! dsl 1: /* $NetBSD: for.c,v 1.44 2009/01/13 18:30:00 dsl Exp $ */
1.3 christos 2:
1.1 cgd 3: /*
4: * Copyright (c) 1992, The Regents of the University of California.
5: * All rights reserved.
6: *
7: * Redistribution and use in source and binary forms, with or without
8: * modification, are permitted provided that the following conditions
9: * are met:
10: * 1. Redistributions of source code must retain the above copyright
11: * notice, this list of conditions and the following disclaimer.
12: * 2. Redistributions in binary form must reproduce the above copyright
13: * notice, this list of conditions and the following disclaimer in the
14: * documentation and/or other materials provided with the distribution.
1.15 agc 15: * 3. Neither the name of the University nor the names of its contributors
1.1 cgd 16: * may be used to endorse or promote products derived from this software
17: * without specific prior written permission.
18: *
19: * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22: * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29: * SUCH DAMAGE.
30: */
31:
1.17 ross 32: #ifndef MAKE_NATIVE
1.45 ! dsl 33: static char rcsid[] = "$NetBSD: for.c,v 1.44 2009/01/13 18:30:00 dsl Exp $";
1.6 lukem 34: #else
1.5 christos 35: #include <sys/cdefs.h>
1.1 cgd 36: #ifndef lint
1.3 christos 37: #if 0
1.4 christos 38: static char sccsid[] = "@(#)for.c 8.1 (Berkeley) 6/6/93";
1.3 christos 39: #else
1.45 ! dsl 40: __RCSID("$NetBSD: for.c,v 1.44 2009/01/13 18:30:00 dsl Exp $");
1.3 christos 41: #endif
1.1 cgd 42: #endif /* not lint */
1.6 lukem 43: #endif
1.1 cgd 44:
45: /*-
46: * for.c --
47: * Functions to handle loops in a makefile.
48: *
49: * Interface:
50: * For_Eval Evaluate the loop in the passed line.
51: * For_Run Run accumulated loop
52: *
53: */
54:
1.13 wiz 55: #include <assert.h>
1.1 cgd 56: #include <ctype.h>
1.13 wiz 57:
1.1 cgd 58: #include "make.h"
59: #include "hash.h"
60: #include "dir.h"
61: #include "buf.h"
1.38 dsl 62: #include "strlist.h"
1.1 cgd 63:
1.44 dsl 64: #define FOR_SUB_ESCAPE_CHAR 1
1.42 dsl 65: #define FOR_SUB_ESCAPE_BRACE 2
66: #define FOR_SUB_ESCAPE_PAREN 4
67:
1.1 cgd 68: /*
69: * For statements are of the form:
70: *
71: * .for <variable> in <varlist>
72: * ...
73: * .endfor
74: *
75: * The trick is to look for the matching end inside for for loop
76: * To do that, we count the current nesting level of the for loops.
77: * and the .endfor statements, accumulating all the statements between
1.4 christos 78: * the initial .for loop and the matching .endfor;
1.1 cgd 79: * then we evaluate the for loop for each variable in the varlist.
1.7 christos 80: *
81: * Note that any nested fors are just passed through; they get handled
82: * recursively in For_Eval when we're expanding the enclosing for in
83: * For_Run.
1.1 cgd 84: */
85:
86: static int forLevel = 0; /* Nesting level */
87:
88: /*
89: * State of a for loop.
90: */
1.2 jtc 91: typedef struct _For {
1.7 christos 92: Buffer buf; /* Body of loop */
1.38 dsl 93: strlist_t vars; /* Iteration variables */
94: strlist_t items; /* Substitution items */
1.43 dsl 95: char *parse_buf;
96: int short_var;
97: int sub_next;
1.2 jtc 98: } For;
1.1 cgd 99:
1.43 dsl 100: static For *accumFor; /* Loop being accumulated */
1.1 cgd 101:
102:
1.7 christos 103:
1.33 dsl 104: static char *
105: make_str(const char *ptr, int len)
106: {
107: char *new_ptr;
108:
109: new_ptr = bmake_malloc(len + 1);
110: memcpy(new_ptr, ptr, len);
111: new_ptr[len] = 0;
112: return new_ptr;
113: }
114:
1.43 dsl 115: static void
116: For_Free(For *arg)
117: {
118: Buf_Destroy(arg->buf, TRUE);
119: strlist_clean(&arg->vars);
120: strlist_clean(&arg->items);
121: free(arg->parse_buf);
122:
123: free(arg);
124: }
125:
1.7 christos 126: /*-
127: *-----------------------------------------------------------------------
1.1 cgd 128: * For_Eval --
129: * Evaluate the for loop in the passed line. The line
130: * looks like this:
131: * .for <variable> in <varlist>
132: *
1.13 wiz 133: * Input:
134: * line Line to parse
135: *
1.1 cgd 136: * Results:
1.35 dsl 137: * 0: Not a .for statement, parse the line
138: * 1: We found a for loop
139: * -1: A .for statement with a bad syntax error, discard.
1.1 cgd 140: *
141: * Side Effects:
142: * None.
143: *
144: *-----------------------------------------------------------------------
145: */
146: int
1.13 wiz 147: For_Eval(char *line)
1.1 cgd 148: {
1.43 dsl 149: For *new_for;
1.33 dsl 150: char *ptr = line, *sub;
151: int len;
1.39 dsl 152: int escapes;
1.42 dsl 153: unsigned char ch;
1.33 dsl 154:
1.43 dsl 155: /* Skip the '.' and any following whitespace */
1.32 dsl 156: for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
157: continue;
1.43 dsl 158:
1.32 dsl 159: /*
160: * If we are not in a for loop quickly determine if the statement is
161: * a for.
162: */
163: if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' ||
164: !isspace((unsigned char) ptr[3])) {
165: if (ptr[0] == 'e' && strncmp(ptr+1, "ndfor", 5) == 0) {
166: Parse_Error(PARSE_FATAL, "for-less endfor");
167: return -1;
168: }
169: return 0;
170: }
171: ptr += 3;
1.1 cgd 172:
1.32 dsl 173: /*
174: * we found a for loop, and now we are going to parse it.
175: */
1.1 cgd 176:
1.43 dsl 177: new_for = bmake_malloc(sizeof *new_for);
178: memset(new_for, 0, sizeof *new_for);
179:
1.33 dsl 180: /* Grab the variables. Terminate on "in". */
181: for (;; ptr += len) {
1.32 dsl 182: while (*ptr && isspace((unsigned char) *ptr))
183: ptr++;
1.33 dsl 184: if (*ptr == '\0') {
185: Parse_Error(PARSE_FATAL, "missing `in' in for");
1.43 dsl 186: For_Free(new_for);
1.33 dsl 187: return -1;
188: }
189: for (len = 1; ptr[len] && !isspace((unsigned char)ptr[len]); len++)
190: continue;
191: if (len == 2 && ptr[0] == 'i' && ptr[1] == 'n') {
192: ptr += 2;
193: break;
194: }
1.43 dsl 195: if (len == 1)
196: new_for->short_var = 1;
197: strlist_add_str(&new_for->vars, make_str(ptr, len), len);
1.32 dsl 198: }
1.1 cgd 199:
1.43 dsl 200: if (strlist_num(&new_for->vars) == 0) {
1.32 dsl 201: Parse_Error(PARSE_FATAL, "no iteration variables in for");
1.43 dsl 202: For_Free(new_for);
1.32 dsl 203: return -1;
204: }
1.4 christos 205:
1.32 dsl 206: while (*ptr && isspace((unsigned char) *ptr))
207: ptr++;
208:
209: /*
210: * Make a list with the remaining words
1.42 dsl 211: * The values are substituted as ${:U<value>...} so we must \ escape
1.44 dsl 212: * characters that break that syntax.
213: * Variables are fully expanded - so it is safe for escape $.
214: * We can't do the escapes here - because we don't know whether
215: * we are substuting into ${...} or $(...).
1.32 dsl 216: */
217: sub = Var_Subst(NULL, ptr, VAR_GLOBAL, FALSE);
1.4 christos 218:
1.38 dsl 219: for (ptr = sub;; ptr += len) {
1.33 dsl 220: while (*ptr && isspace((unsigned char)*ptr))
221: ptr++;
222: if (*ptr == 0)
223: break;
1.39 dsl 224: escapes = 0;
1.42 dsl 225: for (len = 0; (ch = ptr[len]) != 0 && !isspace(ch); len++) {
1.44 dsl 226: if (ch == ':' || ch == '$' || ch == '\\')
227: escapes |= FOR_SUB_ESCAPE_CHAR;
1.42 dsl 228: else if (ch == ')')
229: escapes |= FOR_SUB_ESCAPE_PAREN;
230: else if (ch == /*{*/ '}')
231: escapes |= FOR_SUB_ESCAPE_BRACE;
1.39 dsl 232: }
1.43 dsl 233: strlist_add_str(&new_for->items, make_str(ptr, len), escapes);
1.33 dsl 234: }
1.1 cgd 235:
1.33 dsl 236: free(sub);
1.7 christos 237:
1.43 dsl 238: if (strlist_num(&new_for->items) % strlist_num(&new_for->vars)) {
1.33 dsl 239: Parse_Error(PARSE_FATAL,
240: "Wrong number of words in .for substitution list %d %d",
1.43 dsl 241: strlist_num(&new_for->items), strlist_num(&new_for->vars));
1.33 dsl 242: /*
243: * Return 'success' so that the body of the .for loop is accumulated.
1.43 dsl 244: * Remove all items so that the loop doesn't iterate.
1.33 dsl 245: */
1.43 dsl 246: strlist_clean(&new_for->items);
1.32 dsl 247: }
248:
1.43 dsl 249: new_for->buf = Buf_Init(0);
250: accumFor = new_for;
1.32 dsl 251: forLevel = 1;
252: return 1;
253: }
1.7 christos 254:
1.35 dsl 255: /*
256: * Add another line to a .for loop.
1.43 dsl 257: * Returns 0 when the matching .endfor is reached.
1.35 dsl 258: */
259:
1.32 dsl 260: int
261: For_Accum(char *line)
262: {
263: char *ptr = line;
1.26 dsl 264:
265: if (*ptr == '.') {
1.1 cgd 266:
1.2 jtc 267: for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
1.1 cgd 268: continue;
269:
1.2 jtc 270: if (strncmp(ptr, "endfor", 6) == 0 &&
1.26 dsl 271: (isspace((unsigned char) ptr[6]) || !ptr[6])) {
1.1 cgd 272: if (DEBUG(FOR))
1.23 dsl 273: (void)fprintf(debug_file, "For: end for %d\n", forLevel);
1.32 dsl 274: if (--forLevel <= 0)
1.1 cgd 275: return 0;
1.26 dsl 276: } else if (strncmp(ptr, "for", 3) == 0 &&
1.2 jtc 277: isspace((unsigned char) ptr[3])) {
1.1 cgd 278: forLevel++;
279: if (DEBUG(FOR))
1.23 dsl 280: (void)fprintf(debug_file, "For: new loop %d\n", forLevel);
1.1 cgd 281: }
282: }
283:
1.43 dsl 284: Buf_AddBytes(accumFor->buf, strlen(line), (Byte *)line);
285: Buf_AddByte(accumFor->buf, (Byte)'\n');
1.32 dsl 286: return 1;
1.1 cgd 287: }
288:
289:
290: /*-
291: *-----------------------------------------------------------------------
292: * For_Run --
1.7 christos 293: * Run the for loop, imitating the actions of an include file
1.1 cgd 294: *
295: * Results:
296: * None.
297: *
298: * Side Effects:
299: * None.
300: *
301: *-----------------------------------------------------------------------
302: */
1.42 dsl 303:
1.45 ! dsl 304: static int
! 305: for_var_len(const char *var)
! 306: {
! 307: char ch, var_start, var_end;
! 308: int depth;
! 309: int len;
! 310:
! 311: var_start = *var;
! 312: if (var_start == 0)
! 313: /* just escape the $ */
! 314: return 0;
! 315:
! 316: if (var_start == '(')
! 317: var_end = ')';
! 318: else if (var_start == '{')
! 319: var_end = '}';
! 320: else
! 321: /* Single char variable */
! 322: return 1;
! 323:
! 324: depth = 1;
! 325: for (len = 1; (ch = var[len++]) != 0;) {
! 326: if (ch == var_start)
! 327: depth++;
! 328: else if (ch == var_end && --depth == 0)
! 329: return len;
! 330: }
! 331:
! 332: /* Variable end not found, escape the $ */
! 333: return 0;
! 334: }
! 335:
1.42 dsl 336: static void
337: for_substitute(Buffer cmds, strlist_t *items, unsigned int item_no, char ech)
338: {
339: const char *item = strlist_str(items, item_no);
1.45 ! dsl 340: int len;
1.42 dsl 341: char ch;
342:
343: /* If there were no escapes, or the only escape is the other variable
344: * terminator, then just substitute the full string */
345: if (!(strlist_info(items, item_no) &
346: (ech == ')' ? ~FOR_SUB_ESCAPE_BRACE : ~FOR_SUB_ESCAPE_PAREN))) {
347: Buf_AddBytes(cmds, strlen(item), item);
348: return;
349: }
350:
1.44 dsl 351: /* Escape ':', '$', '\\' and 'ech' - removed by :U processing */
1.45 ! dsl 352: while ((ch = *item++) != 0) {
! 353: if (ch == '$') {
! 354: len = for_var_len(item);
! 355: if (len != 0) {
! 356: Buf_AddBytes(cmds, len + 1, item - 1);
! 357: item += len;
! 358: continue;
! 359: }
! 360: Buf_AddByte(cmds, '\\');
! 361: } else if (ch == ':' || ch == '\\' || ch == ech)
1.42 dsl 362: Buf_AddByte(cmds, '\\');
363: Buf_AddByte(cmds, ch);
364: }
365: }
366:
1.43 dsl 367: static char *
368: For_Iterate(void *v_arg)
1.1 cgd 369: {
1.43 dsl 370: For *arg = v_arg;
1.38 dsl 371: int i, len;
1.42 dsl 372: char *var;
1.39 dsl 373: char *cp;
374: char *cmd_cp;
375: char *body_end;
376: char ch;
377: Buffer cmds;
1.7 christos 378:
1.43 dsl 379: if (arg->sub_next + strlist_num(&arg->vars) > strlist_num(&arg->items)) {
380: /* No more iterations */
381: For_Free(arg);
382: return NULL;
383: }
1.1 cgd 384:
1.43 dsl 385: free(arg->parse_buf);
386: arg->parse_buf = NULL;
1.7 christos 387:
1.39 dsl 388: /*
389: * Scan the for loop body and replace references to the loop variables
390: * with variable references that expand to the required text.
391: * Using variable expansions ensures that the .for loop can't generate
392: * syntax, and that the later parsing will still see a variable.
393: * We assume that the null variable will never be defined.
394: *
395: * The detection of substitions of the loop control variable is naive.
396: * Many of the modifiers use \ to escape $ (not $) so it is possible
397: * to contrive a makefile where an unwanted substitution happens.
398: */
1.43 dsl 399:
400: cmd_cp = Buf_GetAll(arg->buf, &len);
401: body_end = cmd_cp + len;
402: cmds = Buf_Init(len + 256);
403: for (cp = cmd_cp; (cp = strchr(cp, '$')) != NULL;) {
404: char ech;
405: ch = *++cp;
406: if ((ch == '(' && (ech = ')')) || (ch == '{' && (ech = '}'))) {
407: cp++;
408: /* Check variable name against the .for loop variables */
409: STRLIST_FOREACH(var, &arg->vars, i) {
410: len = strlist_info(&arg->vars, i);
411: if (memcmp(cp, var, len) != 0)
412: continue;
413: if (cp[len] != ':' && cp[len] != ech && cp[len] != '\\')
1.39 dsl 414: continue;
1.43 dsl 415: /* Found a variable match. Replace with :U<value> */
1.39 dsl 416: Buf_AddBytes(cmds, cp - cmd_cp, cmd_cp);
1.43 dsl 417: Buf_AddBytes(cmds, 2, ":U");
418: cp += len;
419: cmd_cp = cp;
420: for_substitute(cmds, &arg->items, arg->sub_next + i, ech);
1.39 dsl 421: break;
422: }
1.43 dsl 423: continue;
424: }
425: if (ch == 0)
426: break;
427: /* Probably a single character name, ignore $$ and stupid ones. {*/
428: if (!arg->short_var || strchr("}):$", ch) != NULL) {
429: cp++;
430: continue;
431: }
432: STRLIST_FOREACH(var, &arg->vars, i) {
433: if (var[0] != ch || var[1] != 0)
434: continue;
435: /* Found a variable match. Replace with ${:U<value>} */
436: Buf_AddBytes(cmds, cp - cmd_cp, cmd_cp);
437: Buf_AddBytes(cmds, 3, "{:U");
438: cmd_cp = ++cp;
439: for_substitute(cmds, &arg->items, arg->sub_next + i, /*{*/ '}');
440: Buf_AddBytes(cmds, 1, "}");
441: break;
1.39 dsl 442: }
1.43 dsl 443: }
444: Buf_AddBytes(cmds, body_end - cmd_cp, cmd_cp);
445:
446: cp = Buf_GetAll(cmds, NULL);
447: Buf_Destroy(cmds, FALSE);
448: if (DEBUG(FOR))
449: (void)fprintf(debug_file, "For: loop body:\n%s", cp);
1.39 dsl 450:
1.43 dsl 451: arg->sub_next += strlist_num(&arg->vars);
452:
453: arg->parse_buf = cp;
454: return cp;
455: }
1.7 christos 456:
1.43 dsl 457: void
458: For_Run(int lineno)
459: {
460: For *arg;
461:
462: arg = accumFor;
463: accumFor = NULL;
1.1 cgd 464:
1.43 dsl 465: if (strlist_num(&arg->items) == 0) {
466: /* Nothing to expand - possibly due to an earlier syntax error. */
467: For_Free(arg);
468: return;
469: }
470:
471: Parse_SetInput(NULL, lineno, -1, For_Iterate, arg);
1.1 cgd 472: }
CVSweb <webmaster@jp.NetBSD.org>