]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/bmake/for.c
Merge llvm, clang, compiler-rt, libc++, libunwind, lld, lldb and openmp
[FreeBSD/FreeBSD.git] / contrib / bmake / for.c
1 /*      $NetBSD: for.c,v 1.67 2020/08/30 19:56:02 rillig 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.67 2020/08/30 19:56:02 rillig 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.67 2020/08/30 19:56:02 rillig 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    "make.h"
56 #include    "strlist.h"
57
58 #define FOR_SUB_ESCAPE_CHAR  1
59 #define FOR_SUB_ESCAPE_BRACE 2
60 #define FOR_SUB_ESCAPE_PAREN 4
61
62 /*
63  * For statements are of the form:
64  *
65  * .for <variable> in <varlist>
66  * ...
67  * .endfor
68  *
69  * The trick is to look for the matching end inside for for loop
70  * To do that, we count the current nesting level of the for loops.
71  * and the .endfor statements, accumulating all the statements between
72  * the initial .for loop and the matching .endfor;
73  * then we evaluate the for loop for each variable in the varlist.
74  *
75  * Note that any nested fors are just passed through; they get handled
76  * recursively in For_Eval when we're expanding the enclosing for in
77  * For_Run.
78  */
79
80 static int forLevel = 0;        /* Nesting level */
81
82 /*
83  * State of a for loop.
84  */
85 typedef struct {
86     Buffer buf;                 /* Body of loop */
87     strlist_t vars;             /* Iteration variables */
88     strlist_t items;            /* Substitution items */
89     char *parse_buf;
90     int short_var;
91     int sub_next;
92 } For;
93
94 static For *accumFor;           /* Loop being accumulated */
95
96
97 static void
98 For_Free(For *arg)
99 {
100     Buf_Destroy(&arg->buf, TRUE);
101     strlist_clean(&arg->vars);
102     strlist_clean(&arg->items);
103     free(arg->parse_buf);
104
105     free(arg);
106 }
107
108 /*-
109  *-----------------------------------------------------------------------
110  * For_Eval --
111  *      Evaluate the for loop in the passed line. The line
112  *      looks like this:
113  *          .for <variable> in <varlist>
114  *
115  * Input:
116  *      line            Line to parse
117  *
118  * Results:
119  *      0: Not a .for statement, parse the line
120  *      1: We found a for loop
121  *     -1: A .for statement with a bad syntax error, discard.
122  *
123  * Side Effects:
124  *      None.
125  *
126  *-----------------------------------------------------------------------
127  */
128 int
129 For_Eval(char *line)
130 {
131     For *new_for;
132     char *ptr = line, *sub;
133     size_t len;
134     int escapes;
135     unsigned char ch;
136     Words words;
137
138     /* Skip the '.' and any following whitespace */
139     for (ptr++; *ptr && isspace((unsigned char)*ptr); ptr++)
140         continue;
141
142     /*
143      * If we are not in a for loop quickly determine if the statement is
144      * a for.
145      */
146     if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' ||
147         !isspace((unsigned char)ptr[3])) {
148         if (ptr[0] == 'e' && strncmp(ptr + 1, "ndfor", 5) == 0) {
149             Parse_Error(PARSE_FATAL, "for-less endfor");
150             return -1;
151         }
152         return 0;
153     }
154     ptr += 3;
155
156     /*
157      * we found a for loop, and now we are going to parse it.
158      */
159
160     new_for = bmake_malloc(sizeof *new_for);
161     memset(new_for, 0, sizeof *new_for);
162
163     /* Grab the variables. Terminate on "in". */
164     for (;; ptr += len) {
165         while (*ptr && isspace((unsigned char)*ptr))
166             ptr++;
167         if (*ptr == '\0') {
168             Parse_Error(PARSE_FATAL, "missing `in' in for");
169             For_Free(new_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         if (len == 1)
179             new_for->short_var = 1;
180         strlist_add_str(&new_for->vars, bmake_strldup(ptr, len), len);
181     }
182
183     if (strlist_num(&new_for->vars) == 0) {
184         Parse_Error(PARSE_FATAL, "no iteration variables in for");
185         For_Free(new_for);
186         return -1;
187     }
188
189     while (*ptr && isspace((unsigned char)*ptr))
190         ptr++;
191
192     /*
193      * Make a list with the remaining words
194      * The values are substituted as ${:U<value>...} so we must \ escape
195      * characters that break that syntax.
196      * Variables are fully expanded - so it is safe for escape $.
197      * We can't do the escapes here - because we don't know whether
198      * we are substuting into ${...} or $(...).
199      */
200     sub = Var_Subst(ptr, VAR_GLOBAL, VARE_WANTRES);
201
202     /*
203      * Split into words allowing for quoted strings.
204      */
205     words = Str_Words(sub, FALSE);
206
207     free(sub);
208
209     {
210         size_t n;
211
212         for (n = 0; n < words.len; n++) {
213             ptr = words.words[n];
214             if (!*ptr)
215                 continue;
216             escapes = 0;
217             while ((ch = *ptr++)) {
218                 switch (ch) {
219                 case ':':
220                 case '$':
221                 case '\\':
222                     escapes |= FOR_SUB_ESCAPE_CHAR;
223                     break;
224                 case ')':
225                     escapes |= FOR_SUB_ESCAPE_PAREN;
226                     break;
227                 case /*{*/ '}':
228                     escapes |= FOR_SUB_ESCAPE_BRACE;
229                     break;
230                 }
231             }
232             /*
233              * We have to dup words[n] to maintain the semantics of
234              * strlist.
235              */
236             strlist_add_str(&new_for->items, bmake_strdup(words.words[n]),
237                             escapes);
238         }
239
240         Words_Free(words);
241
242         if ((len = strlist_num(&new_for->items)) > 0 &&
243             len % (n = strlist_num(&new_for->vars))) {
244             Parse_Error(PARSE_FATAL,
245                         "Wrong number of words (%zu) in .for substitution list"
246                         " with %zu vars", len, n);
247             /*
248              * Return 'success' so that the body of the .for loop is
249              * accumulated.
250              * Remove all items so that the loop doesn't iterate.
251              */
252             strlist_clean(&new_for->items);
253         }
254     }
255
256     Buf_Init(&new_for->buf, 0);
257     accumFor = new_for;
258     forLevel = 1;
259     return 1;
260 }
261
262 /*
263  * Add another line to a .for loop.
264  * Returns 0 when the matching .endfor is reached.
265  */
266
267 int
268 For_Accum(char *line)
269 {
270     char *ptr = line;
271
272     if (*ptr == '.') {
273
274         for (ptr++; *ptr && isspace((unsigned char)*ptr); ptr++)
275             continue;
276
277         if (strncmp(ptr, "endfor", 6) == 0 &&
278             (isspace((unsigned char)ptr[6]) || !ptr[6])) {
279             if (DEBUG(FOR))
280                 (void)fprintf(debug_file, "For: end for %d\n", forLevel);
281             if (--forLevel <= 0)
282                 return 0;
283         } else if (strncmp(ptr, "for", 3) == 0 &&
284                    isspace((unsigned char)ptr[3])) {
285             forLevel++;
286             if (DEBUG(FOR))
287                 (void)fprintf(debug_file, "For: new loop %d\n", forLevel);
288         }
289     }
290
291     Buf_AddStr(&accumFor->buf, line);
292     Buf_AddByte(&accumFor->buf, '\n');
293     return 1;
294 }
295
296
297 static size_t
298 for_var_len(const char *var)
299 {
300     char ch, var_start, var_end;
301     int depth;
302     size_t len;
303
304     var_start = *var;
305     if (var_start == 0)
306         /* just escape the $ */
307         return 0;
308
309     if (var_start == '(')
310         var_end = ')';
311     else if (var_start == '{')
312         var_end = '}';
313     else
314         /* Single char variable */
315         return 1;
316
317     depth = 1;
318     for (len = 1; (ch = var[len++]) != 0;) {
319         if (ch == var_start)
320             depth++;
321         else if (ch == var_end && --depth == 0)
322             return len;
323     }
324
325     /* Variable end not found, escape the $ */
326     return 0;
327 }
328
329 static void
330 for_substitute(Buffer *cmds, strlist_t *items, unsigned int item_no, char ech)
331 {
332     char ch;
333
334     const char *item = strlist_str(items, item_no);
335
336     /* If there were no escapes, or the only escape is the other variable
337      * terminator, then just substitute the full string */
338     if (!(strlist_info(items, item_no) &
339           (ech == ')' ? ~FOR_SUB_ESCAPE_BRACE : ~FOR_SUB_ESCAPE_PAREN))) {
340         Buf_AddStr(cmds, item);
341         return;
342     }
343
344     /* Escape ':', '$', '\\' and 'ech' - removed by :U processing */
345     while ((ch = *item++) != 0) {
346         if (ch == '$') {
347             size_t len = for_var_len(item);
348             if (len != 0) {
349                 Buf_AddBytes(cmds, item - 1, len + 1);
350                 item += len;
351                 continue;
352             }
353             Buf_AddByte(cmds, '\\');
354         } else if (ch == ':' || ch == '\\' || ch == ech)
355             Buf_AddByte(cmds, '\\');
356         Buf_AddByte(cmds, ch);
357     }
358 }
359
360 static char *
361 For_Iterate(void *v_arg, size_t *ret_len)
362 {
363     For *arg = v_arg;
364     int i;
365     char *var;
366     char *cp;
367     char *cmd_cp;
368     char *body_end;
369     char ch;
370     Buffer cmds;
371     size_t cmd_len;
372
373     if (arg->sub_next + strlist_num(&arg->vars) > strlist_num(&arg->items)) {
374         /* No more iterations */
375         For_Free(arg);
376         return NULL;
377     }
378
379     free(arg->parse_buf);
380     arg->parse_buf = NULL;
381
382     /*
383      * Scan the for loop body and replace references to the loop variables
384      * with variable references that expand to the required text.
385      * Using variable expansions ensures that the .for loop can't generate
386      * syntax, and that the later parsing will still see a variable.
387      * We assume that the null variable will never be defined.
388      *
389      * The detection of substitions of the loop control variable is naive.
390      * Many of the modifiers use \ to escape $ (not $) so it is possible
391      * to contrive a makefile where an unwanted substitution happens.
392      */
393
394     cmd_cp = Buf_GetAll(&arg->buf, &cmd_len);
395     body_end = cmd_cp + cmd_len;
396     Buf_Init(&cmds, cmd_len + 256);
397     for (cp = cmd_cp; (cp = strchr(cp, '$')) != NULL;) {
398         char ech;
399         ch = *++cp;
400         if ((ch == '(' && (ech = ')', 1)) || (ch == '{' && (ech = '}', 1))) {
401             cp++;
402             /* Check variable name against the .for loop variables */
403             STRLIST_FOREACH(var, &arg->vars, i) {
404                 size_t vlen = strlist_info(&arg->vars, i);
405                 if (memcmp(cp, var, vlen) != 0)
406                     continue;
407                 if (cp[vlen] != ':' && cp[vlen] != ech && cp[vlen] != '\\')
408                     continue;
409                 /* Found a variable match. Replace with :U<value> */
410                 Buf_AddBytesBetween(&cmds, cmd_cp, cp);
411                 Buf_AddStr(&cmds, ":U");
412                 cp += vlen;
413                 cmd_cp = cp;
414                 for_substitute(&cmds, &arg->items, arg->sub_next + i, ech);
415                 break;
416             }
417             continue;
418         }
419         if (ch == 0)
420             break;
421         /* Probably a single character name, ignore $$ and stupid ones. {*/
422         if (!arg->short_var || strchr("}):$", ch) != NULL) {
423             cp++;
424             continue;
425         }
426         STRLIST_FOREACH(var, &arg->vars, i) {
427             if (var[0] != ch || var[1] != 0)
428                 continue;
429             /* Found a variable match. Replace with ${:U<value>} */
430             Buf_AddBytesBetween(&cmds, cmd_cp, cp);
431             Buf_AddStr(&cmds, "{:U");
432             cmd_cp = ++cp;
433             for_substitute(&cmds, &arg->items, arg->sub_next + i, /*{*/ '}');
434             Buf_AddByte(&cmds, '}');
435             break;
436         }
437     }
438     Buf_AddBytesBetween(&cmds, cmd_cp, body_end);
439
440     cp = Buf_Destroy(&cmds, FALSE);
441     if (DEBUG(FOR))
442         (void)fprintf(debug_file, "For: loop body:\n%s", cp);
443
444     arg->sub_next += strlist_num(&arg->vars);
445
446     arg->parse_buf = cp;
447     *ret_len = strlen(cp);
448     return cp;
449 }
450
451 /* Run the for loop, imitating the actions of an include file. */
452 void
453 For_Run(int lineno)
454 {
455     For *arg;
456
457     arg = accumFor;
458     accumFor = NULL;
459
460     if (strlist_num(&arg->items) == 0) {
461         /* Nothing to expand - possibly due to an earlier syntax error. */
462         For_Free(arg);
463         return;
464     }
465
466     Parse_SetInput(NULL, lineno, -1, For_Iterate, arg);
467 }