xref: /netbsd-src/usr.bin/make/for.c (revision 1ca5c1b28139779176bd5c13ad7c5f25c0bcd5f8)
1 /*	$NetBSD: for.c,v 1.11 2001/06/12 23:36:17 sjg 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. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *	This product includes software developed by the University of
18  *	California, Berkeley and its contributors.
19  * 4. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #ifdef MAKE_BOOTSTRAP
37 static char rcsid[] = "$NetBSD: for.c,v 1.11 2001/06/12 23:36:17 sjg Exp $";
38 #else
39 #include <sys/cdefs.h>
40 #ifndef lint
41 #if 0
42 static char sccsid[] = "@(#)for.c	8.1 (Berkeley) 6/6/93";
43 #else
44 __RCSID("$NetBSD: for.c,v 1.11 2001/06/12 23:36:17 sjg Exp $");
45 #endif
46 #endif /* not lint */
47 #endif
48 
49 /*-
50  * for.c --
51  *	Functions to handle loops in a makefile.
52  *
53  * Interface:
54  *	For_Eval 	Evaluate the loop in the passed line.
55  *	For_Run		Run accumulated loop
56  *
57  */
58 
59 #include    <ctype.h>
60 #include    <assert.h>
61 #include    "make.h"
62 #include    "hash.h"
63 #include    "dir.h"
64 #include    "buf.h"
65 
66 /*
67  * For statements are of the form:
68  *
69  * .for <variable> in <varlist>
70  * ...
71  * .endfor
72  *
73  * The trick is to look for the matching end inside for for loop
74  * To do that, we count the current nesting level of the for loops.
75  * and the .endfor statements, accumulating all the statements between
76  * the initial .for loop and the matching .endfor;
77  * then we evaluate the for loop for each variable in the varlist.
78  *
79  * Note that any nested fors are just passed through; they get handled
80  * recursively in For_Eval when we're expanding the enclosing for in
81  * For_Run.
82  */
83 
84 static int  	  forLevel = 0;  	/* Nesting level	*/
85 
86 /*
87  * State of a for loop.
88  */
89 typedef struct _For {
90     Buffer	  buf;			/* Body of loop		*/
91     char	**vars;			/* Iteration variables	*/
92     int           nvars;		/* # of iteration vars	*/
93     Lst  	  lst;			/* List of items	*/
94 } For;
95 
96 static For        accumFor;             /* Loop being accumulated */
97 
98 static void ForAddVar	__P((const char *, size_t));
99 
100 
101 
102 
103 /*-
104  *-----------------------------------------------------------------------
105  * ForAddVar --
106  *	Add an iteration variable to the currently accumulating for.
107  *
108  * Results: none
109  * Side effects: no additional side effects.
110  *-----------------------------------------------------------------------
111  */
112 static void
113 ForAddVar(data, len)
114 	const char *data;
115 	size_t len;
116 {
117 	Buffer buf;
118 	int varlen;
119 
120 	buf = Buf_Init(0);
121 	Buf_AddBytes(buf, len, (Byte *) data);
122 
123 	accumFor.nvars++;
124 	accumFor.vars = erealloc(accumFor.vars, accumFor.nvars*sizeof(char *));
125 
126 	accumFor.vars[accumFor.nvars-1] = (char *) Buf_GetAll(buf, &varlen);
127 
128 	Buf_Destroy(buf, FALSE);
129 }
130 
131 /*-
132  *-----------------------------------------------------------------------
133  * For_Eval --
134  *	Evaluate the for loop in the passed line. The line
135  *	looks like this:
136  *	    .for <variable> in <varlist>
137  *
138  * Results:
139  *	TRUE: We found a for loop, or we are inside a for loop
140  *	FALSE: We did not find a for loop, or we found the end of the for
141  *	       for loop.
142  *
143  * Side Effects:
144  *	None.
145  *
146  *-----------------------------------------------------------------------
147  */
148 int
149 For_Eval (line)
150     char    	    *line;    /* Line to parse */
151 {
152     char	    *ptr = line, *sub, *in, *wrd;
153     int	    	    level;  	/* Level at which to report errors. */
154 
155     level = PARSE_FATAL;
156 
157 
158     if (forLevel == 0) {
159 	Buffer	    buf;
160 	int	    varlen;
161 
162 	for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
163 	    continue;
164 	/*
165 	 * If we are not in a for loop quickly determine if the statement is
166 	 * a for.
167 	 */
168 	if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' ||
169 	    !isspace((unsigned char) ptr[3]))
170 	    return FALSE;
171 	ptr += 3;
172 
173 	/*
174 	 * we found a for loop, and now we are going to parse it.
175 	 */
176 	while (*ptr && isspace((unsigned char) *ptr))
177 	    ptr++;
178 
179 	/*
180 	 * Find the "in".
181 	 */
182 	for (in = ptr; *in; in++) {
183 	    if (isspace((unsigned char) in[0]) && in[1]== 'i' &&
184 		in[2] == 'n' &&
185 		(in[3] == '\0' || isspace((unsigned char) in[3])))
186 		break;
187 	}
188 	if (*in == '\0') {
189 	    Parse_Error(level, "missing `in' in for");
190 	    return 0;
191 	}
192 
193 	/*
194 	 * Grab the variables.
195 	 */
196 	accumFor.vars = NULL;
197 
198 	while (ptr < in) {
199 	    wrd = ptr;
200 	    while (*ptr && !isspace((unsigned char) *ptr))
201 	        ptr++;
202 	    ForAddVar(wrd, ptr - wrd);
203 	    while (*ptr && isspace((unsigned char) *ptr))
204 		ptr++;
205 	}
206 
207 	if (accumFor.nvars == 0) {
208 	    Parse_Error(level, "no iteration variables in for");
209 	    return 0;
210 	}
211 
212 	/* At this point we should be pointing right at the "in" */
213 	assert(!memcmp(ptr, "in", 2));
214 	ptr += 2;
215 
216 	while (*ptr && isspace((unsigned char) *ptr))
217 	    ptr++;
218 
219 	/*
220 	 * Make a list with the remaining words
221 	 */
222 	accumFor.lst = Lst_Init(FALSE);
223 	buf = Buf_Init(0);
224 	sub = Var_Subst(NULL, ptr, VAR_GLOBAL, FALSE);
225 
226 #define ADDWORD() \
227 	Buf_AddBytes(buf, ptr - wrd, (Byte *) wrd), \
228 	Buf_AddByte(buf, (Byte) '\0'), \
229 	Lst_AtFront(accumFor.lst, (ClientData) Buf_GetAll(buf, &varlen)), \
230 	Buf_Destroy(buf, FALSE)
231 
232 	for (ptr = sub; *ptr && isspace((unsigned char) *ptr); ptr++)
233 	    continue;
234 
235 	for (wrd = ptr; *ptr; ptr++)
236 	    if (isspace((unsigned char) *ptr)) {
237 		ADDWORD();
238 		buf = Buf_Init(0);
239 		while (*ptr && isspace((unsigned char) *ptr))
240 		    ptr++;
241 		wrd = ptr--;
242 	    }
243 	if (DEBUG(FOR)) {
244 	    int i;
245 	    for (i = 0; i < accumFor.nvars; i++) {
246 		(void) fprintf(stderr, "For: variable %s\n", accumFor.vars[i]);
247 	    }
248 	    (void) fprintf(stderr, "For: list %s\n", sub);
249 	}
250 	if (ptr - wrd > 0)
251 	    ADDWORD();
252 	else
253 	    Buf_Destroy(buf, TRUE);
254 	free((Address) sub);
255 
256 	accumFor.buf = Buf_Init(0);
257 	forLevel++;
258 	return 1;
259     }
260     else if (*ptr == '.') {
261 
262 	for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
263 	    continue;
264 
265 	if (strncmp(ptr, "endfor", 6) == 0 &&
266 	    (isspace((unsigned char) ptr[6]) || !ptr[6])) {
267 	    if (DEBUG(FOR))
268 		(void) fprintf(stderr, "For: end for %d\n", forLevel);
269 	    if (--forLevel < 0) {
270 		Parse_Error (level, "for-less endfor");
271 		return 0;
272 	    }
273 	}
274 	else if (strncmp(ptr, "for", 3) == 0 &&
275 		 isspace((unsigned char) ptr[3])) {
276 	    forLevel++;
277 	    if (DEBUG(FOR))
278 		(void) fprintf(stderr, "For: new loop %d\n", forLevel);
279 	}
280     }
281 
282     if (forLevel != 0) {
283 	Buf_AddBytes(accumFor.buf, strlen(line), (Byte *) line);
284 	Buf_AddByte(accumFor.buf, (Byte) '\n');
285 	return 1;
286     }
287     else {
288 	return 0;
289     }
290 }
291 
292 
293 /*-
294  *-----------------------------------------------------------------------
295  * For_Run --
296  *	Run the for loop, imitating the actions of an include file
297  *
298  * Results:
299  *	None.
300  *
301  * Side Effects:
302  *	None.
303  *
304  *-----------------------------------------------------------------------
305  */
306 void
307 For_Run()
308 {
309     For arg;
310     LstNode ln;
311     char **values;
312     int i, done = 0, len;
313     char *guy, *orig_guy, *old_guy;
314 
315     if (accumFor.buf == NULL || accumFor.vars == NULL || accumFor.lst == NULL)
316 	return;
317     arg = accumFor;
318     accumFor.buf = NULL;
319     accumFor.vars = NULL;
320     accumFor.nvars = 0;
321     accumFor.lst = NULL;
322 
323     if (Lst_Open(arg.lst) != SUCCESS)
324 	return;
325 
326     values = emalloc(arg.nvars * sizeof(char *));
327 
328     while (!done) {
329 	/*
330 	 * due to the dumb way this is set up, this loop must run
331 	 * backwards.
332 	 */
333 	for (i = arg.nvars - 1; i >= 0; i--) {
334 	    ln = Lst_Next(arg.lst);
335 	    if (ln == NILLNODE) {
336 		if (i != arg.nvars-1) {
337 		    Parse_Error(PARSE_FATAL,
338 			"Not enough words in for substitution list");
339 		}
340 		done = 1;
341 		break;
342 	    } else {
343 		values[i] = (char *) Lst_Datum(ln);
344 	    }
345 	}
346 	if (done)
347 	    break;
348 
349 	for (i = 0; i < arg.nvars; i++) {
350 	    Var_Set(arg.vars[i], values[i], VAR_GLOBAL, 0);
351 	    if (DEBUG(FOR))
352 		(void) fprintf(stderr, "--- %s = %s\n", arg.vars[i],
353 		    values[i]);
354 	}
355 
356 	/*
357 	 * Hack, hack, kludge.
358 	 * This is really ugly, but to do it any better way would require
359 	 * making major changes to var.c, which I don't want to get into
360 	 * yet. There is no mechanism for expanding some variables, only
361 	 * for expanding a single variable. That should be corrected, but
362 	 * not right away. (XXX)
363 	 */
364 
365 	guy = (char *) Buf_GetAll(arg.buf, &len);
366 	orig_guy = guy;
367 	for (i = 0; i < arg.nvars; i++) {
368 	    old_guy = guy;
369 	    guy = Var_Subst(arg.vars[i], guy, VAR_GLOBAL, FALSE);
370 	    if (old_guy != orig_guy)
371 		free(old_guy);
372 	}
373 	Parse_FromString(guy);
374 
375 	for (i = 0; i < arg.nvars; i++)
376 	    Var_Delete(arg.vars[i], VAR_GLOBAL);
377     }
378 
379     free(values);
380 
381     Lst_Close(arg.lst);
382 
383     for (i=0; i<arg.nvars; i++) {
384 	free(arg.vars[i]);
385     }
386     free(arg.vars);
387 
388     Lst_Destroy(arg.lst, (void (*) __P((ClientData))) free);
389     Buf_Destroy(arg.buf, TRUE);
390 }
391