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