summaryrefslogtreecommitdiff
path: root/for.c
diff options
context:
space:
mode:
Diffstat (limited to 'for.c')
-rw-r--r--for.c544
1 files changed, 288 insertions, 256 deletions
diff --git a/for.c b/for.c
index 2e9963f2a7f68..e46bdf4607028 100644
--- a/for.c
+++ b/for.c
@@ -1,4 +1,4 @@
-/* $NetBSD: for.c,v 1.67 2020/08/30 19:56:02 rillig Exp $ */
+/* $NetBSD: for.c,v 1.112 2020/10/31 18:41:07 rillig Exp $ */
/*
* Copyright (c) 1992, The Regents of the University of California.
@@ -29,88 +29,136 @@
* SUCH DAMAGE.
*/
-#ifndef MAKE_NATIVE
-static char rcsid[] = "$NetBSD: for.c,v 1.67 2020/08/30 19:56:02 rillig Exp $";
-#else
-#include <sys/cdefs.h>
-#ifndef lint
-#if 0
-static char sccsid[] = "@(#)for.c 8.1 (Berkeley) 6/6/93";
-#else
-__RCSID("$NetBSD: for.c,v 1.67 2020/08/30 19:56:02 rillig Exp $");
-#endif
-#endif /* not lint */
-#endif
-
/*-
- * for.c --
- * Functions to handle loops in a makefile.
+ * Handling of .for/.endfor loops in a makefile.
*
- * Interface:
- * For_Eval Evaluate the loop in the passed line.
- * For_Run Run accumulated loop
+ * For loops are of the form:
*
- */
-
-#include "make.h"
-#include "strlist.h"
-
-#define FOR_SUB_ESCAPE_CHAR 1
-#define FOR_SUB_ESCAPE_BRACE 2
-#define FOR_SUB_ESCAPE_PAREN 4
-
-/*
- * For statements are of the form:
- *
- * .for <variable> in <varlist>
+ * .for <varname...> in <value...>
* ...
* .endfor
*
- * The trick is to look for the matching end inside for for loop
- * To do that, we count the current nesting level of the for loops.
- * and the .endfor statements, accumulating all the statements between
- * the initial .for loop and the matching .endfor;
- * then we evaluate the for loop for each variable in the varlist.
+ * When a .for line is parsed, all following lines are accumulated into a
+ * buffer, up to but excluding the corresponding .endfor line. To find the
+ * corresponding .endfor, the number of nested .for and .endfor directives
+ * are counted.
+ *
+ * During parsing, any nested .for loops are just passed through; they get
+ * handled recursively in For_Eval when the enclosing .for loop is evaluated
+ * in For_Run.
*
- * Note that any nested fors are just passed through; they get handled
- * recursively in For_Eval when we're expanding the enclosing for in
- * For_Run.
+ * When the .for loop has been parsed completely, the variable expressions
+ * for the iteration variables are replaced with expressions of the form
+ * ${:Uvalue}, and then this modified body is "included" as a special file.
+ *
+ * Interface:
+ * For_Eval Evaluate the loop in the passed line.
+ *
+ * For_Run Run accumulated loop
*/
+#include "make.h"
+
+/* "@(#)for.c 8.1 (Berkeley) 6/6/93" */
+MAKE_RCSID("$NetBSD: for.c,v 1.112 2020/10/31 18:41:07 rillig Exp $");
+
+/* The .for loop substitutes the items as ${:U<value>...}, which means
+ * that characters that break this syntax must be backslash-escaped. */
+typedef enum ForEscapes {
+ FOR_SUB_ESCAPE_CHAR = 0x0001,
+ FOR_SUB_ESCAPE_BRACE = 0x0002,
+ FOR_SUB_ESCAPE_PAREN = 0x0004
+} ForEscapes;
+
static int forLevel = 0; /* Nesting level */
+/* One of the variables to the left of the "in" in a .for loop. */
+typedef struct ForVar {
+ char *name;
+ size_t len;
+} ForVar;
+
/*
* State of a for loop.
*/
-typedef struct {
- Buffer buf; /* Body of loop */
- strlist_t vars; /* Iteration variables */
- strlist_t items; /* Substitution items */
- char *parse_buf;
- int short_var;
- int sub_next;
+typedef struct For {
+ Buffer body; /* Unexpanded body of the loop */
+ Vector /* of ForVar */ vars; /* Iteration variables */
+ Words items; /* Substitution items */
+ Buffer curBody; /* Expanded body of the current iteration */
+ /* Is any of the names 1 character long? If so, when the variable values
+ * are substituted, the parser must handle $V expressions as well, not
+ * only ${V} and $(V). */
+ Boolean short_var;
+ unsigned int sub_next; /* Where to continue iterating */
} For;
static For *accumFor; /* Loop being accumulated */
+static void
+ForAddVar(For *f, const char *name, size_t len)
+{
+ ForVar *var = Vector_Push(&f->vars);
+ var->name = bmake_strldup(name, len);
+ var->len = len;
+}
static void
-For_Free(For *arg)
+For_Free(For *f)
{
- Buf_Destroy(&arg->buf, TRUE);
- strlist_clean(&arg->vars);
- strlist_clean(&arg->items);
- free(arg->parse_buf);
+ Buf_Destroy(&f->body, TRUE);
- free(arg);
+ while (f->vars.len > 0) {
+ ForVar *var = Vector_Pop(&f->vars);
+ free(var->name);
+ }
+ Vector_Done(&f->vars);
+
+ Words_Free(f->items);
+ Buf_Destroy(&f->curBody, TRUE);
+
+ free(f);
}
-/*-
- *-----------------------------------------------------------------------
- * For_Eval --
- * Evaluate the for loop in the passed line. The line
- * looks like this:
- * .for <variable> in <varlist>
+static ForEscapes
+GetEscapes(const char *word)
+{
+ const char *p;
+ ForEscapes escapes = 0;
+
+ for (p = word; *p != '\0'; p++) {
+ switch (*p) {
+ case ':':
+ case '$':
+ case '\\':
+ escapes |= FOR_SUB_ESCAPE_CHAR;
+ break;
+ case ')':
+ escapes |= FOR_SUB_ESCAPE_PAREN;
+ break;
+ case '}':
+ escapes |= FOR_SUB_ESCAPE_BRACE;
+ break;
+ }
+ }
+ return escapes;
+}
+
+static Boolean
+IsFor(const char *p)
+{
+ return p[0] == 'f' && p[1] == 'o' && p[2] == 'r' && ch_isspace(p[3]);
+}
+
+static Boolean
+IsEndfor(const char *p)
+{
+ return p[0] == 'e' && strncmp(p, "endfor", 6) == 0 &&
+ (p[6] == '\0' || ch_isspace(p[6]));
+}
+
+/* Evaluate the for loop in the passed line. The line looks like this:
+ * .for <varname...> in <value...>
*
* Input:
* line Line to parse
@@ -119,178 +167,130 @@ For_Free(For *arg)
* 0: Not a .for statement, parse the line
* 1: We found a for loop
* -1: A .for statement with a bad syntax error, discard.
- *
- * Side Effects:
- * None.
- *
- *-----------------------------------------------------------------------
*/
int
-For_Eval(char *line)
+For_Eval(const char *line)
{
- For *new_for;
- char *ptr = line, *sub;
- size_t len;
- int escapes;
- unsigned char ch;
- Words words;
+ For *f;
+ const char *p;
- /* Skip the '.' and any following whitespace */
- for (ptr++; *ptr && isspace((unsigned char)*ptr); ptr++)
- continue;
+ p = line + 1; /* skip the '.' */
+ cpp_skip_whitespace(&p);
- /*
- * If we are not in a for loop quickly determine if the statement is
- * a for.
- */
- if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' ||
- !isspace((unsigned char)ptr[3])) {
- if (ptr[0] == 'e' && strncmp(ptr + 1, "ndfor", 5) == 0) {
+ if (!IsFor(p)) {
+ if (IsEndfor(p)) {
Parse_Error(PARSE_FATAL, "for-less endfor");
return -1;
}
return 0;
}
- ptr += 3;
+ p += 3;
/*
* we found a for loop, and now we are going to parse it.
*/
- new_for = bmake_malloc(sizeof *new_for);
- memset(new_for, 0, sizeof *new_for);
+ f = bmake_malloc(sizeof *f);
+ Buf_Init(&f->body, 0);
+ Vector_Init(&f->vars, sizeof(ForVar));
+ f->items.words = NULL;
+ f->items.freeIt = NULL;
+ Buf_Init(&f->curBody, 0);
+ f->short_var = FALSE;
+ f->sub_next = 0;
/* Grab the variables. Terminate on "in". */
- for (;; ptr += len) {
- while (*ptr && isspace((unsigned char)*ptr))
- ptr++;
- if (*ptr == '\0') {
+ for (;;) {
+ size_t len;
+
+ cpp_skip_whitespace(&p);
+ if (*p == '\0') {
Parse_Error(PARSE_FATAL, "missing `in' in for");
- For_Free(new_for);
+ For_Free(f);
return -1;
}
- for (len = 1; ptr[len] && !isspace((unsigned char)ptr[len]); len++)
+
+ /* XXX: This allows arbitrary variable names; see directive-for.mk. */
+ for (len = 1; p[len] != '\0' && !ch_isspace(p[len]); len++)
continue;
- if (len == 2 && ptr[0] == 'i' && ptr[1] == 'n') {
- ptr += 2;
+
+ if (len == 2 && p[0] == 'i' && p[1] == 'n') {
+ p += 2;
break;
}
if (len == 1)
- new_for->short_var = 1;
- strlist_add_str(&new_for->vars, bmake_strldup(ptr, len), len);
+ f->short_var = TRUE;
+
+ ForAddVar(f, p, len);
+ p += len;
}
- if (strlist_num(&new_for->vars) == 0) {
+ if (f->vars.len == 0) {
Parse_Error(PARSE_FATAL, "no iteration variables in for");
- For_Free(new_for);
+ For_Free(f);
return -1;
}
- while (*ptr && isspace((unsigned char)*ptr))
- ptr++;
-
- /*
- * Make a list with the remaining words
- * The values are substituted as ${:U<value>...} so we must \ escape
- * characters that break that syntax.
- * Variables are fully expanded - so it is safe for escape $.
- * We can't do the escapes here - because we don't know whether
- * we are substuting into ${...} or $(...).
- */
- sub = Var_Subst(ptr, VAR_GLOBAL, VARE_WANTRES);
-
- /*
- * Split into words allowing for quoted strings.
- */
- words = Str_Words(sub, FALSE);
-
- free(sub);
+ cpp_skip_whitespace(&p);
{
- size_t n;
-
- for (n = 0; n < words.len; n++) {
- ptr = words.words[n];
- if (!*ptr)
- continue;
- escapes = 0;
- while ((ch = *ptr++)) {
- switch (ch) {
- case ':':
- case '$':
- case '\\':
- escapes |= FOR_SUB_ESCAPE_CHAR;
- break;
- case ')':
- escapes |= FOR_SUB_ESCAPE_PAREN;
- break;
- case /*{*/ '}':
- escapes |= FOR_SUB_ESCAPE_BRACE;
- break;
- }
- }
- /*
- * We have to dup words[n] to maintain the semantics of
- * strlist.
- */
- strlist_add_str(&new_for->items, bmake_strdup(words.words[n]),
- escapes);
- }
+ char *items;
+ (void)Var_Subst(p, VAR_GLOBAL, VARE_WANTRES, &items);
+ /* TODO: handle errors */
+ f->items = Str_Words(items, FALSE);
+ free(items);
+
+ if (f->items.len == 1 && f->items.words[0][0] == '\0')
+ f->items.len = 0; /* .for var in ${:U} */
+ }
- Words_Free(words);
+ {
+ size_t nitems, nvars;
- if ((len = strlist_num(&new_for->items)) > 0 &&
- len % (n = strlist_num(&new_for->vars))) {
+ if ((nitems = f->items.len) > 0 && nitems % (nvars = f->vars.len)) {
Parse_Error(PARSE_FATAL,
"Wrong number of words (%zu) in .for substitution list"
- " with %zu vars", len, n);
+ " with %zu variables", nitems, nvars);
/*
* Return 'success' so that the body of the .for loop is
* accumulated.
* Remove all items so that the loop doesn't iterate.
*/
- strlist_clean(&new_for->items);
+ f->items.len = 0;
}
}
- Buf_Init(&new_for->buf, 0);
- accumFor = new_for;
+ accumFor = f;
forLevel = 1;
return 1;
}
/*
* Add another line to a .for loop.
- * Returns 0 when the matching .endfor is reached.
+ * Returns FALSE when the matching .endfor is reached.
*/
-
-int
-For_Accum(char *line)
+Boolean
+For_Accum(const char *line)
{
- char *ptr = line;
+ const char *ptr = line;
if (*ptr == '.') {
+ ptr++;
+ cpp_skip_whitespace(&ptr);
- for (ptr++; *ptr && isspace((unsigned char)*ptr); ptr++)
- continue;
-
- if (strncmp(ptr, "endfor", 6) == 0 &&
- (isspace((unsigned char)ptr[6]) || !ptr[6])) {
- if (DEBUG(FOR))
- (void)fprintf(debug_file, "For: end for %d\n", forLevel);
+ if (IsEndfor(ptr)) {
+ DEBUG1(FOR, "For: end for %d\n", forLevel);
if (--forLevel <= 0)
- return 0;
- } else if (strncmp(ptr, "for", 3) == 0 &&
- isspace((unsigned char)ptr[3])) {
+ return FALSE;
+ } else if (IsFor(ptr)) {
forLevel++;
- if (DEBUG(FOR))
- (void)fprintf(debug_file, "For: new loop %d\n", forLevel);
+ DEBUG1(FOR, "For: new loop %d\n", forLevel);
}
}
- Buf_AddStr(&accumFor->buf, line);
- Buf_AddByte(&accumFor->buf, '\n');
- return 1;
+ Buf_AddStr(&accumFor->body, line);
+ Buf_AddByte(&accumFor->body, '\n');
+ return TRUE;
}
@@ -326,23 +326,25 @@ for_var_len(const char *var)
return 0;
}
+/* While expanding the body of a .for loop, write the item in the ${:U...}
+ * expression, escaping characters as needed. See ApplyModifier_Defined. */
static void
-for_substitute(Buffer *cmds, strlist_t *items, unsigned int item_no, char ech)
+Buf_AddEscaped(Buffer *cmds, const char *item, char ech)
{
+ ForEscapes escapes = GetEscapes(item);
char ch;
- const char *item = strlist_str(items, item_no);
-
/* If there were no escapes, or the only escape is the other variable
* terminator, then just substitute the full string */
- if (!(strlist_info(items, item_no) &
- (ech == ')' ? ~FOR_SUB_ESCAPE_BRACE : ~FOR_SUB_ESCAPE_PAREN))) {
+ if (!(escapes & (ech == ')' ? ~(unsigned)FOR_SUB_ESCAPE_BRACE
+ : ~(unsigned)FOR_SUB_ESCAPE_PAREN))) {
Buf_AddStr(cmds, item);
return;
}
- /* Escape ':', '$', '\\' and 'ech' - removed by :U processing */
- while ((ch = *item++) != 0) {
+ /* Escape ':', '$', '\\' and 'ech' - these will be removed later by
+ * :U processing, see ApplyModifier_Defined. */
+ while ((ch = *item++) != '\0') {
if (ch == '$') {
size_t len = for_var_len(item);
if (len != 0) {
@@ -357,111 +359,141 @@ for_substitute(Buffer *cmds, strlist_t *items, unsigned int item_no, char ech)
}
}
+/* While expanding the body of a .for loop, replace expressions like
+ * ${i}, ${i:...}, $(i) or $(i:...) with their ${:U...} expansion. */
+static void
+SubstVarLong(For *f, const char **pp, const char **inout_mark, char ech)
+{
+ size_t i;
+ const char *p = *pp;
+
+ for (i = 0; i < f->vars.len; i++) {
+ ForVar *forVar = Vector_Get(&f->vars, i);
+ char *var = forVar->name;
+ size_t vlen = forVar->len;
+
+ /* XXX: undefined behavior for p if vlen is longer than p? */
+ if (memcmp(p, var, vlen) != 0)
+ continue;
+ /* XXX: why test for backslash here? */
+ if (p[vlen] != ':' && p[vlen] != ech && p[vlen] != '\\')
+ continue;
+
+ /* Found a variable match. Replace with :U<value> */
+ Buf_AddBytesBetween(&f->curBody, *inout_mark, p);
+ Buf_AddStr(&f->curBody, ":U");
+ Buf_AddEscaped(&f->curBody, f->items.words[f->sub_next + i], ech);
+
+ p += vlen;
+ *inout_mark = p;
+ break;
+ }
+
+ *pp = p;
+}
+
+/* While expanding the body of a .for loop, replace single-character
+ * variable expressions like $i with their ${:U...} expansion. */
+static void
+SubstVarShort(For *f, char const ch, const char **pp, const char **inout_mark)
+{
+ const char *p = *pp;
+ size_t i;
+
+ /* Probably a single character name, ignore $$ and stupid ones. */
+ if (!f->short_var || strchr("}):$", ch) != NULL) {
+ p++;
+ *pp = p;
+ return;
+ }
+
+ for (i = 0; i < f->vars.len; i++) {
+ ForVar *var = Vector_Get(&f->vars, i);
+ const char *varname = var->name;
+ if (varname[0] != ch || varname[1] != '\0')
+ continue;
+
+ /* Found a variable match. Replace with ${:U<value>} */
+ Buf_AddBytesBetween(&f->curBody, *inout_mark, p);
+ Buf_AddStr(&f->curBody, "{:U");
+ Buf_AddEscaped(&f->curBody, f->items.words[f->sub_next + i], '}');
+ Buf_AddByte(&f->curBody, '}');
+
+ *inout_mark = ++p;
+ break;
+ }
+
+ *pp = p;
+}
+
+/*
+ * Scan the for loop body and replace references to the loop variables
+ * with variable references that expand to the required text.
+ *
+ * Using variable expansions ensures that the .for loop can't generate
+ * syntax, and that the later parsing will still see a variable.
+ * We assume that the null variable will never be defined.
+ *
+ * The detection of substitutions of the loop control variable is naive.
+ * Many of the modifiers use \ to escape $ (not $) so it is possible
+ * to contrive a makefile where an unwanted substitution happens.
+ */
static char *
-For_Iterate(void *v_arg, size_t *ret_len)
+ForIterate(void *v_arg, size_t *out_len)
{
- For *arg = v_arg;
- int i;
- char *var;
- char *cp;
- char *cmd_cp;
- char *body_end;
- char ch;
- Buffer cmds;
- size_t cmd_len;
+ For *f = v_arg;
+ const char *p;
+ const char *mark; /* where the last replacement left off */
+ const char *body_end;
+ char *cmds_str;
- if (arg->sub_next + strlist_num(&arg->vars) > strlist_num(&arg->items)) {
+ if (f->sub_next + f->vars.len > f->items.len) {
/* No more iterations */
- For_Free(arg);
+ For_Free(f);
return NULL;
}
- free(arg->parse_buf);
- arg->parse_buf = NULL;
+ Buf_Empty(&f->curBody);
- /*
- * Scan the for loop body and replace references to the loop variables
- * with variable references that expand to the required text.
- * Using variable expansions ensures that the .for loop can't generate
- * syntax, and that the later parsing will still see a variable.
- * We assume that the null variable will never be defined.
- *
- * The detection of substitions of the loop control variable is naive.
- * Many of the modifiers use \ to escape $ (not $) so it is possible
- * to contrive a makefile where an unwanted substitution happens.
- */
-
- cmd_cp = Buf_GetAll(&arg->buf, &cmd_len);
- body_end = cmd_cp + cmd_len;
- Buf_Init(&cmds, cmd_len + 256);
- for (cp = cmd_cp; (cp = strchr(cp, '$')) != NULL;) {
- char ech;
- ch = *++cp;
+ mark = Buf_GetAll(&f->body, NULL);
+ body_end = mark + Buf_Len(&f->body);
+ for (p = mark; (p = strchr(p, '$')) != NULL;) {
+ char ch, ech;
+ ch = *++p;
if ((ch == '(' && (ech = ')', 1)) || (ch == '{' && (ech = '}', 1))) {
- cp++;
+ p++;
/* Check variable name against the .for loop variables */
- STRLIST_FOREACH(var, &arg->vars, i) {
- size_t vlen = strlist_info(&arg->vars, i);
- if (memcmp(cp, var, vlen) != 0)
- continue;
- if (cp[vlen] != ':' && cp[vlen] != ech && cp[vlen] != '\\')
- continue;
- /* Found a variable match. Replace with :U<value> */
- Buf_AddBytesBetween(&cmds, cmd_cp, cp);
- Buf_AddStr(&cmds, ":U");
- cp += vlen;
- cmd_cp = cp;
- for_substitute(&cmds, &arg->items, arg->sub_next + i, ech);
- break;
- }
+ SubstVarLong(f, &p, &mark, ech);
continue;
}
- if (ch == 0)
+ if (ch == '\0')
break;
- /* Probably a single character name, ignore $$ and stupid ones. {*/
- if (!arg->short_var || strchr("}):$", ch) != NULL) {
- cp++;
- continue;
- }
- STRLIST_FOREACH(var, &arg->vars, i) {
- if (var[0] != ch || var[1] != 0)
- continue;
- /* Found a variable match. Replace with ${:U<value>} */
- Buf_AddBytesBetween(&cmds, cmd_cp, cp);
- Buf_AddStr(&cmds, "{:U");
- cmd_cp = ++cp;
- for_substitute(&cmds, &arg->items, arg->sub_next + i, /*{*/ '}');
- Buf_AddByte(&cmds, '}');
- break;
- }
+
+ SubstVarShort(f, ch, &p, &mark);
}
- Buf_AddBytesBetween(&cmds, cmd_cp, body_end);
+ Buf_AddBytesBetween(&f->curBody, mark, body_end);
- cp = Buf_Destroy(&cmds, FALSE);
- if (DEBUG(FOR))
- (void)fprintf(debug_file, "For: loop body:\n%s", cp);
+ *out_len = Buf_Len(&f->curBody);
+ cmds_str = Buf_GetAll(&f->curBody, NULL);
+ DEBUG1(FOR, "For: loop body:\n%s", cmds_str);
- arg->sub_next += strlist_num(&arg->vars);
+ f->sub_next += f->vars.len;
- arg->parse_buf = cp;
- *ret_len = strlen(cp);
- return cp;
+ return cmds_str;
}
/* Run the for loop, imitating the actions of an include file. */
void
For_Run(int lineno)
{
- For *arg;
-
- arg = accumFor;
+ For *f = accumFor;
accumFor = NULL;
- if (strlist_num(&arg->items) == 0) {
+ if (f->items.len == 0) {
/* Nothing to expand - possibly due to an earlier syntax error. */
- For_Free(arg);
+ For_Free(f);
return;
}
- Parse_SetInput(NULL, lineno, -1, For_Iterate, arg);
+ Parse_SetInput(NULL, lineno, -1, ForIterate, f);
}