xref: /netbsd/usr.bin/make/for.c (revision bf9ec67e)
1 /*	$NetBSD: for.c,v 1.12 2002/03/12 20:15:15 christos 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.12 2002/03/12 20:15:15 christos 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.12 2002/03/12 20:15:15 christos 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 	static const char instr[] = "in";
162 
163 	for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
164 	    continue;
165 	/*
166 	 * If we are not in a for loop quickly determine if the statement is
167 	 * a for.
168 	 */
169 	if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' ||
170 	    !isspace((unsigned char) ptr[3]))
171 	    return FALSE;
172 	ptr += 3;
173 
174 	/*
175 	 * we found a for loop, and now we are going to parse it.
176 	 */
177 	while (*ptr && isspace((unsigned char) *ptr))
178 	    ptr++;
179 
180 	/*
181 	 * Find the "in".
182 	 */
183 	for (in = ptr; *in; in++) {
184 	    if (isspace((unsigned char) in[0]) && in[1]== 'i' &&
185 		in[2] == 'n' &&
186 		(in[3] == '\0' || isspace((unsigned char) in[3])))
187 		break;
188 	}
189 	if (*in == '\0') {
190 	    Parse_Error(level, "missing `in' in for");
191 	    return 0;
192 	}
193 
194 	/*
195 	 * Grab the variables.
196 	 */
197 	accumFor.vars = NULL;
198 
199 	while (ptr < in) {
200 	    wrd = ptr;
201 	    while (*ptr && !isspace((unsigned char) *ptr))
202 	        ptr++;
203 	    ForAddVar(wrd, ptr - wrd);
204 	    while (*ptr && isspace((unsigned char) *ptr))
205 		ptr++;
206 	}
207 
208 	if (accumFor.nvars == 0) {
209 	    Parse_Error(level, "no iteration variables in for");
210 	    return 0;
211 	}
212 
213 	/* At this point we should be pointing right at the "in" */
214 	/*
215 	 * compensate for hp/ux's brain damaged assert macro that
216 	 * does not handle double quotes nicely.
217 	 */
218 	assert(!memcmp(ptr, instr, 2));
219 	ptr += 2;
220 
221 	while (*ptr && isspace((unsigned char) *ptr))
222 	    ptr++;
223 
224 	/*
225 	 * Make a list with the remaining words
226 	 */
227 	accumFor.lst = Lst_Init(FALSE);
228 	buf = Buf_Init(0);
229 	sub = Var_Subst(NULL, ptr, VAR_GLOBAL, FALSE);
230 
231 #define ADDWORD() \
232 	Buf_AddBytes(buf, ptr - wrd, (Byte *) wrd), \
233 	Buf_AddByte(buf, (Byte) '\0'), \
234 	Lst_AtFront(accumFor.lst, (ClientData) Buf_GetAll(buf, &varlen)), \
235 	Buf_Destroy(buf, FALSE)
236 
237 	for (ptr = sub; *ptr && isspace((unsigned char) *ptr); ptr++)
238 	    continue;
239 
240 	for (wrd = ptr; *ptr; ptr++)
241 	    if (isspace((unsigned char) *ptr)) {
242 		ADDWORD();
243 		buf = Buf_Init(0);
244 		while (*ptr && isspace((unsigned char) *ptr))
245 		    ptr++;
246 		wrd = ptr--;
247 	    }
248 	if (DEBUG(FOR)) {
249 	    int i;
250 	    for (i = 0; i < accumFor.nvars; i++) {
251 		(void) fprintf(stderr, "For: variable %s\n", accumFor.vars[i]);
252 	    }
253 	    (void) fprintf(stderr, "For: list %s\n", sub);
254 	}
255 	if (ptr - wrd > 0)
256 	    ADDWORD();
257 	else
258 	    Buf_Destroy(buf, TRUE);
259 	free((Address) sub);
260 
261 	accumFor.buf = Buf_Init(0);
262 	forLevel++;
263 	return 1;
264     }
265     else if (*ptr == '.') {
266 
267 	for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
268 	    continue;
269 
270 	if (strncmp(ptr, "endfor", 6) == 0 &&
271 	    (isspace((unsigned char) ptr[6]) || !ptr[6])) {
272 	    if (DEBUG(FOR))
273 		(void) fprintf(stderr, "For: end for %d\n", forLevel);
274 	    if (--forLevel < 0) {
275 		Parse_Error (level, "for-less endfor");
276 		return 0;
277 	    }
278 	}
279 	else if (strncmp(ptr, "for", 3) == 0 &&
280 		 isspace((unsigned char) ptr[3])) {
281 	    forLevel++;
282 	    if (DEBUG(FOR))
283 		(void) fprintf(stderr, "For: new loop %d\n", forLevel);
284 	}
285     }
286 
287     if (forLevel != 0) {
288 	Buf_AddBytes(accumFor.buf, strlen(line), (Byte *) line);
289 	Buf_AddByte(accumFor.buf, (Byte) '\n');
290 	return 1;
291     }
292     else {
293 	return 0;
294     }
295 }
296 
297 
298 /*-
299  *-----------------------------------------------------------------------
300  * For_Run --
301  *	Run the for loop, imitating the actions of an include file
302  *
303  * Results:
304  *	None.
305  *
306  * Side Effects:
307  *	None.
308  *
309  *-----------------------------------------------------------------------
310  */
311 void
312 For_Run()
313 {
314     For arg;
315     LstNode ln;
316     char **values;
317     int i, done = 0, len;
318     char *guy, *orig_guy, *old_guy;
319 
320     if (accumFor.buf == NULL || accumFor.vars == NULL || accumFor.lst == NULL)
321 	return;
322     arg = accumFor;
323     accumFor.buf = NULL;
324     accumFor.vars = NULL;
325     accumFor.nvars = 0;
326     accumFor.lst = NULL;
327 
328     if (Lst_Open(arg.lst) != SUCCESS)
329 	return;
330 
331     values = emalloc(arg.nvars * sizeof(char *));
332 
333     while (!done) {
334 	/*
335 	 * due to the dumb way this is set up, this loop must run
336 	 * backwards.
337 	 */
338 	for (i = arg.nvars - 1; i >= 0; i--) {
339 	    ln = Lst_Next(arg.lst);
340 	    if (ln == NILLNODE) {
341 		if (i != arg.nvars-1) {
342 		    Parse_Error(PARSE_FATAL,
343 			"Not enough words in for substitution list");
344 		}
345 		done = 1;
346 		break;
347 	    } else {
348 		values[i] = (char *) Lst_Datum(ln);
349 	    }
350 	}
351 	if (done)
352 	    break;
353 
354 	for (i = 0; i < arg.nvars; i++) {
355 	    Var_Set(arg.vars[i], values[i], VAR_GLOBAL, 0);
356 	    if (DEBUG(FOR))
357 		(void) fprintf(stderr, "--- %s = %s\n", arg.vars[i],
358 		    values[i]);
359 	}
360 
361 	/*
362 	 * Hack, hack, kludge.
363 	 * This is really ugly, but to do it any better way would require
364 	 * making major changes to var.c, which I don't want to get into
365 	 * yet. There is no mechanism for expanding some variables, only
366 	 * for expanding a single variable. That should be corrected, but
367 	 * not right away. (XXX)
368 	 */
369 
370 	guy = (char *) Buf_GetAll(arg.buf, &len);
371 	orig_guy = guy;
372 	for (i = 0; i < arg.nvars; i++) {
373 	    old_guy = guy;
374 	    guy = Var_Subst(arg.vars[i], guy, VAR_GLOBAL, FALSE);
375 	    if (old_guy != orig_guy)
376 		free(old_guy);
377 	}
378 	Parse_FromString(guy);
379 
380 	for (i = 0; i < arg.nvars; i++)
381 	    Var_Delete(arg.vars[i], VAR_GLOBAL);
382     }
383 
384     free(values);
385 
386     Lst_Close(arg.lst);
387 
388     for (i=0; i<arg.nvars; i++) {
389 	free(arg.vars[i]);
390     }
391     free(arg.vars);
392 
393     Lst_Destroy(arg.lst, (void (*) __P((ClientData))) free);
394     Buf_Destroy(arg.buf, TRUE);
395 }
396