1321964Ssjg/*	$NetBSD: for.c,v 1.53 2017/04/16 21:04:44 riastradh Exp $	*/
2236769Sobrien
3236769Sobrien/*
4236769Sobrien * Copyright (c) 1992, The Regents of the University of California.
5236769Sobrien * All rights reserved.
6236769Sobrien *
7236769Sobrien * Redistribution and use in source and binary forms, with or without
8236769Sobrien * modification, are permitted provided that the following conditions
9236769Sobrien * are met:
10236769Sobrien * 1. Redistributions of source code must retain the above copyright
11236769Sobrien *    notice, this list of conditions and the following disclaimer.
12236769Sobrien * 2. Redistributions in binary form must reproduce the above copyright
13236769Sobrien *    notice, this list of conditions and the following disclaimer in the
14236769Sobrien *    documentation and/or other materials provided with the distribution.
15236769Sobrien * 3. Neither the name of the University nor the names of its contributors
16236769Sobrien *    may be used to endorse or promote products derived from this software
17236769Sobrien *    without specific prior written permission.
18236769Sobrien *
19236769Sobrien * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20236769Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21236769Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22236769Sobrien * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23236769Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24236769Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25236769Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26236769Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27236769Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28236769Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29236769Sobrien * SUCH DAMAGE.
30236769Sobrien */
31236769Sobrien
32236769Sobrien#ifndef MAKE_NATIVE
33321964Ssjgstatic char rcsid[] = "$NetBSD: for.c,v 1.53 2017/04/16 21:04:44 riastradh Exp $";
34236769Sobrien#else
35236769Sobrien#include <sys/cdefs.h>
36236769Sobrien#ifndef lint
37236769Sobrien#if 0
38236769Sobrienstatic char sccsid[] = "@(#)for.c	8.1 (Berkeley) 6/6/93";
39236769Sobrien#else
40321964Ssjg__RCSID("$NetBSD: for.c,v 1.53 2017/04/16 21:04:44 riastradh Exp $");
41236769Sobrien#endif
42236769Sobrien#endif /* not lint */
43236769Sobrien#endif
44236769Sobrien
45236769Sobrien/*-
46236769Sobrien * for.c --
47236769Sobrien *	Functions to handle loops in a makefile.
48236769Sobrien *
49236769Sobrien * Interface:
50236769Sobrien *	For_Eval 	Evaluate the loop in the passed line.
51236769Sobrien *	For_Run		Run accumulated loop
52236769Sobrien *
53236769Sobrien */
54236769Sobrien
55236769Sobrien#include    <assert.h>
56236769Sobrien#include    <ctype.h>
57236769Sobrien
58236769Sobrien#include    "make.h"
59236769Sobrien#include    "hash.h"
60236769Sobrien#include    "dir.h"
61236769Sobrien#include    "buf.h"
62236769Sobrien#include    "strlist.h"
63236769Sobrien
64236769Sobrien#define FOR_SUB_ESCAPE_CHAR  1
65236769Sobrien#define FOR_SUB_ESCAPE_BRACE 2
66236769Sobrien#define FOR_SUB_ESCAPE_PAREN 4
67236769Sobrien
68236769Sobrien/*
69236769Sobrien * For statements are of the form:
70236769Sobrien *
71236769Sobrien * .for <variable> in <varlist>
72236769Sobrien * ...
73236769Sobrien * .endfor
74236769Sobrien *
75236769Sobrien * The trick is to look for the matching end inside for for loop
76236769Sobrien * To do that, we count the current nesting level of the for loops.
77236769Sobrien * and the .endfor statements, accumulating all the statements between
78236769Sobrien * the initial .for loop and the matching .endfor;
79236769Sobrien * then we evaluate the for loop for each variable in the varlist.
80236769Sobrien *
81236769Sobrien * Note that any nested fors are just passed through; they get handled
82236769Sobrien * recursively in For_Eval when we're expanding the enclosing for in
83236769Sobrien * For_Run.
84236769Sobrien */
85236769Sobrien
86236769Sobrienstatic int  	  forLevel = 0;  	/* Nesting level	*/
87236769Sobrien
88236769Sobrien/*
89236769Sobrien * State of a for loop.
90236769Sobrien */
91236769Sobrientypedef struct _For {
92236769Sobrien    Buffer	  buf;			/* Body of loop		*/
93236769Sobrien    strlist_t     vars;			/* Iteration variables	*/
94236769Sobrien    strlist_t     items;		/* Substitution items */
95236769Sobrien    char          *parse_buf;
96236769Sobrien    int           short_var;
97236769Sobrien    int           sub_next;
98236769Sobrien} For;
99236769Sobrien
100236769Sobrienstatic For        *accumFor;            /* Loop being accumulated */
101236769Sobrien
102236769Sobrien
103236769Sobrien
104236769Sobrienstatic char *
105236769Sobrienmake_str(const char *ptr, int len)
106236769Sobrien{
107236769Sobrien	char *new_ptr;
108236769Sobrien
109236769Sobrien	new_ptr = bmake_malloc(len + 1);
110236769Sobrien	memcpy(new_ptr, ptr, len);
111236769Sobrien	new_ptr[len] = 0;
112236769Sobrien	return new_ptr;
113236769Sobrien}
114236769Sobrien
115236769Sobrienstatic void
116236769SobrienFor_Free(For *arg)
117236769Sobrien{
118236769Sobrien    Buf_Destroy(&arg->buf, TRUE);
119236769Sobrien    strlist_clean(&arg->vars);
120236769Sobrien    strlist_clean(&arg->items);
121236769Sobrien    free(arg->parse_buf);
122236769Sobrien
123236769Sobrien    free(arg);
124236769Sobrien}
125236769Sobrien
126236769Sobrien/*-
127236769Sobrien *-----------------------------------------------------------------------
128236769Sobrien * For_Eval --
129236769Sobrien *	Evaluate the for loop in the passed line. The line
130236769Sobrien *	looks like this:
131236769Sobrien *	    .for <variable> in <varlist>
132236769Sobrien *
133236769Sobrien * Input:
134236769Sobrien *	line		Line to parse
135236769Sobrien *
136236769Sobrien * Results:
137236769Sobrien *      0: Not a .for statement, parse the line
138236769Sobrien *	1: We found a for loop
139236769Sobrien *     -1: A .for statement with a bad syntax error, discard.
140236769Sobrien *
141236769Sobrien * Side Effects:
142236769Sobrien *	None.
143236769Sobrien *
144236769Sobrien *-----------------------------------------------------------------------
145236769Sobrien */
146236769Sobrienint
147236769SobrienFor_Eval(char *line)
148236769Sobrien{
149236769Sobrien    For *new_for;
150236769Sobrien    char *ptr = line, *sub;
151236769Sobrien    int len;
152236769Sobrien    int escapes;
153236769Sobrien    unsigned char ch;
154236769Sobrien    char **words, *word_buf;
155236769Sobrien    int n, nwords;
156236769Sobrien
157236769Sobrien    /* Skip the '.' and any following whitespace */
158236769Sobrien    for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
159236769Sobrien	continue;
160236769Sobrien
161236769Sobrien    /*
162236769Sobrien     * If we are not in a for loop quickly determine if the statement is
163236769Sobrien     * a for.
164236769Sobrien     */
165236769Sobrien    if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' ||
166236769Sobrien	    !isspace((unsigned char) ptr[3])) {
167236769Sobrien	if (ptr[0] == 'e' && strncmp(ptr+1, "ndfor", 5) == 0) {
168236769Sobrien	    Parse_Error(PARSE_FATAL, "for-less endfor");
169236769Sobrien	    return -1;
170236769Sobrien	}
171236769Sobrien	return 0;
172236769Sobrien    }
173236769Sobrien    ptr += 3;
174236769Sobrien
175236769Sobrien    /*
176236769Sobrien     * we found a for loop, and now we are going to parse it.
177236769Sobrien     */
178236769Sobrien
179236769Sobrien    new_for = bmake_malloc(sizeof *new_for);
180236769Sobrien    memset(new_for, 0, sizeof *new_for);
181236769Sobrien
182236769Sobrien    /* Grab the variables. Terminate on "in". */
183236769Sobrien    for (;; ptr += len) {
184236769Sobrien	while (*ptr && isspace((unsigned char) *ptr))
185236769Sobrien	    ptr++;
186236769Sobrien	if (*ptr == '\0') {
187236769Sobrien	    Parse_Error(PARSE_FATAL, "missing `in' in for");
188236769Sobrien	    For_Free(new_for);
189236769Sobrien	    return -1;
190236769Sobrien	}
191236769Sobrien	for (len = 1; ptr[len] && !isspace((unsigned char)ptr[len]); len++)
192236769Sobrien	    continue;
193236769Sobrien	if (len == 2 && ptr[0] == 'i' && ptr[1] == 'n') {
194236769Sobrien	    ptr += 2;
195236769Sobrien	    break;
196236769Sobrien	}
197236769Sobrien	if (len == 1)
198236769Sobrien	    new_for->short_var = 1;
199236769Sobrien	strlist_add_str(&new_for->vars, make_str(ptr, len), len);
200236769Sobrien    }
201236769Sobrien
202236769Sobrien    if (strlist_num(&new_for->vars) == 0) {
203236769Sobrien	Parse_Error(PARSE_FATAL, "no iteration variables in for");
204236769Sobrien	For_Free(new_for);
205236769Sobrien	return -1;
206236769Sobrien    }
207236769Sobrien
208236769Sobrien    while (*ptr && isspace((unsigned char) *ptr))
209236769Sobrien	ptr++;
210236769Sobrien
211236769Sobrien    /*
212236769Sobrien     * Make a list with the remaining words
213236769Sobrien     * The values are substituted as ${:U<value>...} so we must \ escape
214236769Sobrien     * characters that break that syntax.
215236769Sobrien     * Variables are fully expanded - so it is safe for escape $.
216236769Sobrien     * We can't do the escapes here - because we don't know whether
217236769Sobrien     * we are substuting into ${...} or $(...).
218236769Sobrien     */
219321964Ssjg    sub = Var_Subst(NULL, ptr, VAR_GLOBAL, VARF_WANTRES);
220236769Sobrien
221236769Sobrien    /*
222236769Sobrien     * Split into words allowing for quoted strings.
223236769Sobrien     */
224236769Sobrien    words = brk_string(sub, &nwords, FALSE, &word_buf);
225236769Sobrien
226236769Sobrien    free(sub);
227236769Sobrien
228236769Sobrien    if (words != NULL) {
229236769Sobrien	for (n = 0; n < nwords; n++) {
230236769Sobrien	    ptr = words[n];
231236769Sobrien	    if (!*ptr)
232236769Sobrien		continue;
233236769Sobrien	    escapes = 0;
234236769Sobrien	    while ((ch = *ptr++)) {
235236769Sobrien		switch(ch) {
236236769Sobrien		case ':':
237236769Sobrien		case '$':
238236769Sobrien		case '\\':
239236769Sobrien		    escapes |= FOR_SUB_ESCAPE_CHAR;
240236769Sobrien		    break;
241236769Sobrien		case ')':
242236769Sobrien		    escapes |= FOR_SUB_ESCAPE_PAREN;
243236769Sobrien		    break;
244236769Sobrien		case /*{*/ '}':
245236769Sobrien		    escapes |= FOR_SUB_ESCAPE_BRACE;
246236769Sobrien		    break;
247236769Sobrien		}
248236769Sobrien	    }
249236769Sobrien	    /*
250236769Sobrien	     * We have to dup words[n] to maintain the semantics of
251236769Sobrien	     * strlist.
252236769Sobrien	     */
253236769Sobrien	    strlist_add_str(&new_for->items, bmake_strdup(words[n]), escapes);
254236769Sobrien	}
255236769Sobrien
256236769Sobrien	free(words);
257236769Sobrien	free(word_buf);
258236769Sobrien
259236769Sobrien	if ((len = strlist_num(&new_for->items)) > 0 &&
260236769Sobrien	    len % (n = strlist_num(&new_for->vars))) {
261236769Sobrien	    Parse_Error(PARSE_FATAL,
262236769Sobrien			"Wrong number of words (%d) in .for substitution list"
263236769Sobrien			" with %d vars", len, n);
264236769Sobrien	    /*
265236769Sobrien	     * Return 'success' so that the body of the .for loop is
266236769Sobrien	     * accumulated.
267236769Sobrien	     * Remove all items so that the loop doesn't iterate.
268236769Sobrien	     */
269236769Sobrien	    strlist_clean(&new_for->items);
270236769Sobrien	}
271236769Sobrien    }
272236769Sobrien
273236769Sobrien    Buf_Init(&new_for->buf, 0);
274236769Sobrien    accumFor = new_for;
275236769Sobrien    forLevel = 1;
276236769Sobrien    return 1;
277236769Sobrien}
278236769Sobrien
279236769Sobrien/*
280236769Sobrien * Add another line to a .for loop.
281236769Sobrien * Returns 0 when the matching .endfor is reached.
282236769Sobrien */
283236769Sobrien
284236769Sobrienint
285236769SobrienFor_Accum(char *line)
286236769Sobrien{
287236769Sobrien    char *ptr = line;
288236769Sobrien
289236769Sobrien    if (*ptr == '.') {
290236769Sobrien
291236769Sobrien	for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
292236769Sobrien	    continue;
293236769Sobrien
294236769Sobrien	if (strncmp(ptr, "endfor", 6) == 0 &&
295236769Sobrien		(isspace((unsigned char) ptr[6]) || !ptr[6])) {
296236769Sobrien	    if (DEBUG(FOR))
297236769Sobrien		(void)fprintf(debug_file, "For: end for %d\n", forLevel);
298236769Sobrien	    if (--forLevel <= 0)
299236769Sobrien		return 0;
300236769Sobrien	} else if (strncmp(ptr, "for", 3) == 0 &&
301236769Sobrien		 isspace((unsigned char) ptr[3])) {
302236769Sobrien	    forLevel++;
303236769Sobrien	    if (DEBUG(FOR))
304236769Sobrien		(void)fprintf(debug_file, "For: new loop %d\n", forLevel);
305236769Sobrien	}
306236769Sobrien    }
307236769Sobrien
308236769Sobrien    Buf_AddBytes(&accumFor->buf, strlen(line), line);
309236769Sobrien    Buf_AddByte(&accumFor->buf, '\n');
310236769Sobrien    return 1;
311236769Sobrien}
312236769Sobrien
313236769Sobrien
314236769Sobrien/*-
315236769Sobrien *-----------------------------------------------------------------------
316236769Sobrien * For_Run --
317236769Sobrien *	Run the for loop, imitating the actions of an include file
318236769Sobrien *
319236769Sobrien * Results:
320236769Sobrien *	None.
321236769Sobrien *
322236769Sobrien * Side Effects:
323236769Sobrien *	None.
324236769Sobrien *
325236769Sobrien *-----------------------------------------------------------------------
326236769Sobrien */
327236769Sobrien
328236769Sobrienstatic int
329236769Sobrienfor_var_len(const char *var)
330236769Sobrien{
331236769Sobrien    char ch, var_start, var_end;
332236769Sobrien    int depth;
333236769Sobrien    int len;
334236769Sobrien
335236769Sobrien    var_start = *var;
336236769Sobrien    if (var_start == 0)
337236769Sobrien	/* just escape the $ */
338236769Sobrien	return 0;
339236769Sobrien
340236769Sobrien    if (var_start == '(')
341236769Sobrien	var_end = ')';
342236769Sobrien    else if (var_start == '{')
343236769Sobrien	var_end = '}';
344236769Sobrien    else
345236769Sobrien	/* Single char variable */
346236769Sobrien	return 1;
347236769Sobrien
348236769Sobrien    depth = 1;
349236769Sobrien    for (len = 1; (ch = var[len++]) != 0;) {
350236769Sobrien	if (ch == var_start)
351236769Sobrien	    depth++;
352236769Sobrien	else if (ch == var_end && --depth == 0)
353236769Sobrien	    return len;
354236769Sobrien    }
355236769Sobrien
356236769Sobrien    /* Variable end not found, escape the $ */
357236769Sobrien    return 0;
358236769Sobrien}
359236769Sobrien
360236769Sobrienstatic void
361236769Sobrienfor_substitute(Buffer *cmds, strlist_t *items, unsigned int item_no, char ech)
362236769Sobrien{
363236769Sobrien    const char *item = strlist_str(items, item_no);
364236769Sobrien    int len;
365236769Sobrien    char ch;
366236769Sobrien
367236769Sobrien    /* If there were no escapes, or the only escape is the other variable
368236769Sobrien     * terminator, then just substitute the full string */
369236769Sobrien    if (!(strlist_info(items, item_no) &
370236769Sobrien	    (ech == ')' ? ~FOR_SUB_ESCAPE_BRACE : ~FOR_SUB_ESCAPE_PAREN))) {
371236769Sobrien	Buf_AddBytes(cmds, strlen(item), item);
372236769Sobrien	return;
373236769Sobrien    }
374236769Sobrien
375236769Sobrien    /* Escape ':', '$', '\\' and 'ech' - removed by :U processing */
376236769Sobrien    while ((ch = *item++) != 0) {
377236769Sobrien	if (ch == '$') {
378236769Sobrien	    len = for_var_len(item);
379236769Sobrien	    if (len != 0) {
380236769Sobrien		Buf_AddBytes(cmds, len + 1, item - 1);
381236769Sobrien		item += len;
382236769Sobrien		continue;
383236769Sobrien	    }
384236769Sobrien	    Buf_AddByte(cmds, '\\');
385236769Sobrien	} else if (ch == ':' || ch == '\\' || ch == ech)
386236769Sobrien	    Buf_AddByte(cmds, '\\');
387236769Sobrien	Buf_AddByte(cmds, ch);
388236769Sobrien    }
389236769Sobrien}
390236769Sobrien
391236769Sobrienstatic char *
392236769SobrienFor_Iterate(void *v_arg, size_t *ret_len)
393236769Sobrien{
394236769Sobrien    For *arg = v_arg;
395236769Sobrien    int i, len;
396236769Sobrien    char *var;
397236769Sobrien    char *cp;
398236769Sobrien    char *cmd_cp;
399236769Sobrien    char *body_end;
400236769Sobrien    char ch;
401236769Sobrien    Buffer cmds;
402236769Sobrien
403236769Sobrien    if (arg->sub_next + strlist_num(&arg->vars) > strlist_num(&arg->items)) {
404236769Sobrien	/* No more iterations */
405236769Sobrien	For_Free(arg);
406236769Sobrien	return NULL;
407236769Sobrien    }
408236769Sobrien
409236769Sobrien    free(arg->parse_buf);
410236769Sobrien    arg->parse_buf = NULL;
411236769Sobrien
412236769Sobrien    /*
413236769Sobrien     * Scan the for loop body and replace references to the loop variables
414236769Sobrien     * with variable references that expand to the required text.
415236769Sobrien     * Using variable expansions ensures that the .for loop can't generate
416236769Sobrien     * syntax, and that the later parsing will still see a variable.
417236769Sobrien     * We assume that the null variable will never be defined.
418236769Sobrien     *
419236769Sobrien     * The detection of substitions of the loop control variable is naive.
420236769Sobrien     * Many of the modifiers use \ to escape $ (not $) so it is possible
421236769Sobrien     * to contrive a makefile where an unwanted substitution happens.
422236769Sobrien     */
423236769Sobrien
424236769Sobrien    cmd_cp = Buf_GetAll(&arg->buf, &len);
425236769Sobrien    body_end = cmd_cp + len;
426236769Sobrien    Buf_Init(&cmds, len + 256);
427236769Sobrien    for (cp = cmd_cp; (cp = strchr(cp, '$')) != NULL;) {
428236769Sobrien	char ech;
429236769Sobrien	ch = *++cp;
430321964Ssjg	if ((ch == '(' && (ech = ')', 1)) || (ch == '{' && (ech = '}', 1))) {
431236769Sobrien	    cp++;
432236769Sobrien	    /* Check variable name against the .for loop variables */
433236769Sobrien	    STRLIST_FOREACH(var, &arg->vars, i) {
434236769Sobrien		len = strlist_info(&arg->vars, i);
435236769Sobrien		if (memcmp(cp, var, len) != 0)
436236769Sobrien		    continue;
437236769Sobrien		if (cp[len] != ':' && cp[len] != ech && cp[len] != '\\')
438236769Sobrien		    continue;
439236769Sobrien		/* Found a variable match. Replace with :U<value> */
440236769Sobrien		Buf_AddBytes(&cmds, cp - cmd_cp, cmd_cp);
441236769Sobrien		Buf_AddBytes(&cmds, 2, ":U");
442236769Sobrien		cp += len;
443236769Sobrien		cmd_cp = cp;
444236769Sobrien		for_substitute(&cmds, &arg->items, arg->sub_next + i, ech);
445236769Sobrien		break;
446236769Sobrien	    }
447236769Sobrien	    continue;
448236769Sobrien	}
449236769Sobrien	if (ch == 0)
450236769Sobrien	    break;
451236769Sobrien	/* Probably a single character name, ignore $$ and stupid ones. {*/
452236769Sobrien	if (!arg->short_var || strchr("}):$", ch) != NULL) {
453236769Sobrien	    cp++;
454236769Sobrien	    continue;
455236769Sobrien	}
456236769Sobrien	STRLIST_FOREACH(var, &arg->vars, i) {
457236769Sobrien	    if (var[0] != ch || var[1] != 0)
458236769Sobrien		continue;
459236769Sobrien	    /* Found a variable match. Replace with ${:U<value>} */
460236769Sobrien	    Buf_AddBytes(&cmds, cp - cmd_cp, cmd_cp);
461236769Sobrien	    Buf_AddBytes(&cmds, 3, "{:U");
462236769Sobrien	    cmd_cp = ++cp;
463236769Sobrien	    for_substitute(&cmds, &arg->items, arg->sub_next + i, /*{*/ '}');
464236769Sobrien	    Buf_AddBytes(&cmds, 1, "}");
465236769Sobrien	    break;
466236769Sobrien	}
467236769Sobrien    }
468236769Sobrien    Buf_AddBytes(&cmds, body_end - cmd_cp, cmd_cp);
469236769Sobrien
470236769Sobrien    cp = Buf_Destroy(&cmds, FALSE);
471236769Sobrien    if (DEBUG(FOR))
472236769Sobrien	(void)fprintf(debug_file, "For: loop body:\n%s", cp);
473236769Sobrien
474236769Sobrien    arg->sub_next += strlist_num(&arg->vars);
475236769Sobrien
476236769Sobrien    arg->parse_buf = cp;
477236769Sobrien    *ret_len = strlen(cp);
478236769Sobrien    return cp;
479236769Sobrien}
480236769Sobrien
481236769Sobrienvoid
482236769SobrienFor_Run(int lineno)
483236769Sobrien{
484236769Sobrien    For *arg;
485236769Sobrien
486236769Sobrien    arg = accumFor;
487236769Sobrien    accumFor = NULL;
488236769Sobrien
489236769Sobrien    if (strlist_num(&arg->items) == 0) {
490236769Sobrien        /* Nothing to expand - possibly due to an earlier syntax error. */
491236769Sobrien        For_Free(arg);
492236769Sobrien        return;
493236769Sobrien    }
494236769Sobrien
495236769Sobrien    Parse_SetInput(NULL, lineno, -1, For_Iterate, arg);
496236769Sobrien}
497