for.c revision 1.42 1 /* $NetBSD: for.c,v 1.42 2009/01/10 16:59:02 dsl Exp $ */
2
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.
15 * 3. Neither the name of the University nor the names of its contributors
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
32 #ifndef MAKE_NATIVE
33 static char rcsid[] = "$NetBSD: for.c,v 1.42 2009/01/10 16:59:02 dsl Exp $";
34 #else
35 #include <sys/cdefs.h>
36 #ifndef lint
37 #if 0
38 static char sccsid[] = "@(#)for.c 8.1 (Berkeley) 6/6/93";
39 #else
40 __RCSID("$NetBSD: for.c,v 1.42 2009/01/10 16:59:02 dsl Exp $");
41 #endif
42 #endif /* not lint */
43 #endif
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
55 #include <assert.h>
56 #include <ctype.h>
57
58 #include "make.h"
59 #include "hash.h"
60 #include "dir.h"
61 #include "buf.h"
62 #include "strlist.h"
63
64 #define FOR_SUB_ESCAPE_COLON 1
65 #define FOR_SUB_ESCAPE_BRACE 2
66 #define FOR_SUB_ESCAPE_PAREN 4
67
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
78 * the initial .for loop and the matching .endfor;
79 * then we evaluate the for loop for each variable in the varlist.
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.
84 */
85
86 static int forLevel = 0; /* Nesting level */
87
88 /*
89 * State of a for loop.
90 */
91 typedef struct _For {
92 Buffer buf; /* Body of loop */
93 strlist_t vars; /* Iteration variables */
94 strlist_t items; /* Substitution items */
95 } For;
96
97 static For accumFor; /* Loop being accumulated */
98
99
100
102 static char *
103 make_str(const char *ptr, int len)
104 {
105 char *new_ptr;
106
107 new_ptr = bmake_malloc(len + 1);
108 memcpy(new_ptr, ptr, len);
109 new_ptr[len] = 0;
110 return new_ptr;
111 }
112
113 /*-
114 *-----------------------------------------------------------------------
115 * For_Eval --
116 * Evaluate the for loop in the passed line. The line
117 * looks like this:
118 * .for <variable> in <varlist>
119 *
120 * Input:
121 * line Line to parse
122 *
123 * Results:
124 * 0: Not a .for statement, parse the line
125 * 1: We found a for loop
126 * -1: A .for statement with a bad syntax error, discard.
127 *
128 * Side Effects:
129 * None.
130 *
131 *-----------------------------------------------------------------------
132 */
133 int
134 For_Eval(char *line)
135 {
136 char *ptr = line, *sub;
137 int len;
138 int escapes;
139 unsigned char ch;
140
141 /* Forget anything we previously knew about - it cannot be useful */
142 memset(&accumFor, 0, sizeof accumFor);
143
144 forLevel = 0;
145 for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
146 continue;
147 /*
148 * If we are not in a for loop quickly determine if the statement is
149 * a for.
150 */
151 if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' ||
152 !isspace((unsigned char) ptr[3])) {
153 if (ptr[0] == 'e' && strncmp(ptr+1, "ndfor", 5) == 0) {
154 Parse_Error(PARSE_FATAL, "for-less endfor");
155 return -1;
156 }
157 return 0;
158 }
159 ptr += 3;
160
161 /*
162 * we found a for loop, and now we are going to parse it.
163 */
164
165 /* Grab the variables. Terminate on "in". */
166 for (;; ptr += len) {
167 while (*ptr && isspace((unsigned char) *ptr))
168 ptr++;
169 if (*ptr == '\0') {
170 Parse_Error(PARSE_FATAL, "missing `in' in for");
171 return -1;
172 }
173 for (len = 1; ptr[len] && !isspace((unsigned char)ptr[len]); len++)
174 continue;
175 if (len == 2 && ptr[0] == 'i' && ptr[1] == 'n') {
176 ptr += 2;
177 break;
178 }
179 strlist_add_str(&accumFor.vars, make_str(ptr, len), len);
180 }
181
182 if (strlist_num(&accumFor.vars) == 0) {
183 Parse_Error(PARSE_FATAL, "no iteration variables in for");
184 return -1;
185 }
186
187 while (*ptr && isspace((unsigned char) *ptr))
188 ptr++;
189
190 /*
191 * Make a list with the remaining words
192 * The values are substituted as ${:U<value>...} so we must \ escape
193 * characters that break that syntax - particularly ':', maybe $ and \.
194 */
195 sub = Var_Subst(NULL, ptr, VAR_GLOBAL, FALSE);
196
197 for (ptr = sub;; ptr += len) {
198 while (*ptr && isspace((unsigned char)*ptr))
199 ptr++;
200 if (*ptr == 0)
201 break;
202 escapes = 0;
203 for (len = 0; (ch = ptr[len]) != 0 && !isspace(ch); len++) {
204 if (ch == ':')
205 escapes |= FOR_SUB_ESCAPE_COLON;
206 else if (ch == ')')
207 escapes |= FOR_SUB_ESCAPE_PAREN;
208 else if (ch == /*{*/ '}')
209 escapes |= FOR_SUB_ESCAPE_BRACE;
210 }
211 strlist_add_str(&accumFor.items, make_str(ptr, len), escapes);
212 }
213
214 free(sub);
215
216 if (strlist_num(&accumFor.items) % strlist_num(&accumFor.vars)) {
217 Parse_Error(PARSE_FATAL,
218 "Wrong number of words in .for substitution list %d %d",
219 strlist_num(&accumFor.items), strlist_num(&accumFor.vars));
220 /*
221 * Return 'success' so that the body of the .for loop is accumulated.
222 * The loop will have zero iterations expanded due a later test.
223 */
224 }
225
226 accumFor.buf = Buf_Init(0);
227 forLevel = 1;
228 return 1;
229 }
230
231 /*
232 * Add another line to a .for loop.
233 * Returns 0 when the matching .enfor is reached.
234 */
235
236 int
237 For_Accum(char *line)
238 {
239 char *ptr = line;
240
241 if (*ptr == '.') {
242
243 for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
244 continue;
245
246 if (strncmp(ptr, "endfor", 6) == 0 &&
247 (isspace((unsigned char) ptr[6]) || !ptr[6])) {
248 if (DEBUG(FOR))
249 (void)fprintf(debug_file, "For: end for %d\n", forLevel);
250 if (--forLevel <= 0)
251 return 0;
252 } else if (strncmp(ptr, "for", 3) == 0 &&
253 isspace((unsigned char) ptr[3])) {
254 forLevel++;
255 if (DEBUG(FOR))
256 (void)fprintf(debug_file, "For: new loop %d\n", forLevel);
257 }
258 }
259
260 Buf_AddBytes(accumFor.buf, strlen(line), (Byte *)line);
261 Buf_AddByte(accumFor.buf, (Byte)'\n');
262 return 1;
263 }
264
265
266 /*-
268 *-----------------------------------------------------------------------
269 * For_Run --
270 * Run the for loop, imitating the actions of an include file
271 *
272 * Results:
273 * None.
274 *
275 * Side Effects:
276 * None.
277 *
278 *-----------------------------------------------------------------------
279 */
280
281 static void
282 for_substitute(Buffer cmds, strlist_t *items, unsigned int item_no, char ech)
283 {
284 int depth, var_depth;
285 int escape;
286 const char *item = strlist_str(items, item_no);
287 int i;
288 char ch;
289 #define MAX_DEPTH 0x7fffffff
290
291 /* If there were no escapes, or the only escape is the other variable
292 * terminator, then just substitute the full string */
293 if (!(strlist_info(items, item_no) &
294 (ech == ')' ? ~FOR_SUB_ESCAPE_BRACE : ~FOR_SUB_ESCAPE_PAREN))) {
295 Buf_AddBytes(cmds, strlen(item), item);
296 return;
297 }
298
299 /* Escape ':' and 'ech' provided they aren't inside variable expansions */
300 depth = 0;
301 var_depth = MAX_DEPTH;
302 escape = -1;
303 for (i = 0; (ch = item[i]) != 0; i++) {
304 /* Loose determination of nested variable definitions. */
305 if (ch == '(' || ch == '{') {
306 depth++;
307 if (var_depth == MAX_DEPTH && i != 0 && item[i-1] == '$')
308 var_depth = depth;
309 } else if (ch == ')' || ch == '}') {
310 if (ch == ech && depth < var_depth)
311 escape = i;
312 if (depth == var_depth)
313 var_depth = MAX_DEPTH;
314 depth--;
315 } else if (ch == ':' && depth < var_depth)
316 escape = i;
317 if (escape == i)
318 Buf_AddByte(cmds, '\\');
319 Buf_AddByte(cmds, ch);
320 }
321
322 if (escape == -1) {
323 /* We didn't actually need to escape anything, remember for next time */
324 strlist_set_info(items, item_no, strlist_info(items, item_no) &
325 (ech == ')' ? ~FOR_SUB_ESCAPE_PAREN : ~FOR_SUB_ESCAPE_BRACE));
326 }
327 }
328
329 void
330 For_Run(int lineno)
331 {
332 For arg;
333 int i, len;
334 unsigned int num_items;
335 char *for_body;
336 char *var;
337 char *cp;
338 char *cmd_cp;
339 char *body_end;
340 char ch;
341 Buffer cmds;
342 int short_var;
343
344 arg = accumFor;
345 memset(&accumFor, 0, sizeof accumFor);
346
347 num_items = strlist_num(&arg.items);
348 if (num_items % strlist_num(&arg.vars))
349 /* Error message already printed */
350 goto out;
351
352 short_var = 0;
353 STRLIST_FOREACH(var, &arg.vars, i) {
354 if (var[1] == 0) {
355 short_var = 1;
356 break;
357 }
358 }
359
360 /*
361 * Scan the for loop body and replace references to the loop variables
362 * with variable references that expand to the required text.
363 * Using variable expansions ensures that the .for loop can't generate
364 * syntax, and that the later parsing will still see a variable.
365 * We assume that the null variable will never be defined.
366 *
367 * The detection of substitions of the loop control variable is naive.
368 * Many of the modifiers use \ to escape $ (not $) so it is possible
369 * to contrive a makefile where an unwanted substitution happens.
370 *
371 * Each loop expansion is fed back into the parser as if it were an
372 * include file. This means we have to generate the last iteration first.
373 */
374 while (num_items != 0) {
375 num_items -= strlist_num(&arg.vars);
376 for_body = (char *)Buf_GetAll(arg.buf, &len);
377 body_end = for_body + len;
378 cmds = Buf_Init(len + 256);
379 cmd_cp = for_body;
380 for (cp = for_body; (cp = strchr(cp, '$')) != NULL;) {
381 char ech;
382 ch = *++cp;
383 if ((ch == '(' && (ech = ')')) || (ch == '{' && (ech = '}'))) {
384 cp++;
385 /* Check variable name against the .for loop variables */
386 STRLIST_FOREACH(var, &arg.vars, i) {
387 len = strlist_info(&arg.vars, i);
388 if (memcmp(cp, var, len) != 0)
389 continue;
390 if (cp[len] != ':' && cp[len] != ech && cp[len] != '\\')
391 continue;
392 /* Found a variable match. Replace with :U<value> */
393 Buf_AddBytes(cmds, cp - cmd_cp, cmd_cp);
394 Buf_AddBytes(cmds, 2, ":U");
395 cp += len;
396 cmd_cp = cp;
397 for_substitute(cmds, &arg.items, num_items + i, ech);
398 break;
399 }
400 continue;
401 }
402 if (ch == 0)
403 break;
404 /* Probably a single character name, ignore $$ and stupid ones. {*/
405 if (!short_var || strchr("}):$", ch) != NULL) {
406 cp++;
407 continue;
408 }
409 STRLIST_FOREACH(var, &arg.vars, i) {
410 if (var[0] != ch || var[1] != 0)
411 continue;
412 /* Found a variable match. Replace with ${:U<value>} */
413 Buf_AddBytes(cmds, cp - cmd_cp, cmd_cp);
414 Buf_AddBytes(cmds, 3, "{:U");
415 cmd_cp = ++cp;
416 for_substitute(cmds, &arg.items, num_items + i, /*{*/ '}');
417 Buf_AddBytes(cmds, 1, "}");
418 break;
419 }
420 }
421 Buf_AddBytes(cmds, body_end - cmd_cp, cmd_cp);
422
423 cp = Buf_GetAll(cmds, NULL);
424 if (DEBUG(FOR))
425 (void)fprintf(debug_file, "For: loop body:\n%s", cp);
426 Parse_SetInput(NULL, lineno, -1, cp);
427 Buf_Destroy(cmds, FALSE);
428 }
429
430 out:
431 strlist_clean(&arg.vars);
432 strlist_clean(&arg.items);
433
434 Buf_Destroy(arg.buf, TRUE);
435 }
436