Subject: bin/9899: multi-variable .for constructs in make
To: None <gnats-bugs@gnats.netbsd.org>
From: David A. Holland <dholland@hcs.harvard.edu>
List: netbsd-bugs
Date: 04/16/2000 07:05:53
>Number:         9899
>Category:       bin
>Synopsis:       multi-variable .for constructs in make
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    bin-bug-people
>State:          open
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Sun Apr 16 07:04:00 PDT 2000
>Closed-Date:
>Last-Modified:
>Originator:     David A. Holland
>Release:        NetBSD-current 20000416
>Organization:

>Environment:

System: NetBSD merlin 1.4X NetBSD 1.4X (MERLIN) #30: Sun Apr 16 08:04:29 EDT 2000 dholland@merlin:/usr/src/sys/arch/i386/compile/MERLIN i386


>Description:

	The enclosed patch adds the ability to have .for loops in make that
	have more than one iteration variable. That is, if you do

		all:
		.for X Y in 1 2 3 4
			@echo $X $Y
		.endfor

	and run it, it'll print

		1 2
		3 4

	This has obvious uses for <bsd.links.mk> and the like. (Note that
	using the feature in those should wait until after there's been a
	release, so as to avoid breaking the upgrade path; so this should
	go in, if it's going to, before any 1.5 freeze.)

	The implementation has a couple of ugly bits, but correcting them
	requires major surgery to the variable handling code, which I 
	thought should be undertaken separately.

	The change should be completely backwards compatible, except for
	the highly unlikely case that someone tried to use "in" as an
	iteration variable.

	In any event, the patched make has no trouble building the world. 

	I have updated the man page. I have not updated PSD.doc, because
	it appears to be sufficiently out of date that it doesn't even 
	mention for loops.

>How-To-Repeat:

	N/A.

>Fix:

Index: usr.bin/make/for.c
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/make/for.c,v
retrieving revision 1.6
diff -u -3 -r1.6 for.c
--- for.c	1997/09/28 03:31:03	1.6
+++ for.c	2000/04/16 13:13:00
@@ -57,6 +57,7 @@
  */
 
 #include    <ctype.h>
+#include    <assert.h>
 #include    "make.h"
 #include    "hash.h"
 #include    "dir.h"
@@ -74,27 +75,59 @@
  * 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.
+ *
+ * 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.
  */
 
 static int  	  forLevel = 0;  	/* Nesting level	*/
-static char	 *forVar;		/* Iteration variable	*/
-static Buffer	  forBuf;		/* Commands in loop	*/
-static Lst	  forLst;		/* List of items	*/
 
 /*
  * State of a for loop.
  */
 typedef struct _For {
-    Buffer	  buf;			/* Unexpanded buffer	*/
-    char*	  var;			/* Index name		*/
-    Lst  	  lst;			/* List of variables	*/
+    Buffer	  buf;			/* Body of loop		*/
+    char	**vars;			/* Iteration variables	*/
+    int           nvars;		/* # of iteration vars	*/
+    Lst  	  lst;			/* List of items	*/
 } For;
 
-static int ForExec	__P((ClientData, ClientData));
+static For        accumFor;             /* Loop being accumulated */
 
+static void ForAddVar	__P((const char *, size_t));
 
 
 
+
+/*-
+ *-----------------------------------------------------------------------
+ * ForAddVar --
+ *	Add an iteration variable to the currently accumulating for.
+ *
+ * Results: none
+ * Side effects: no additional side effects.
+ *-----------------------------------------------------------------------
+ */
+static void
+ForAddVar(data, len)
+	const char *data;
+	size_t len;
+{
+	Buffer buf;
+	size_t varlen;
+
+	buf = Buf_Init(0);
+	Buf_AddBytes(buf, len, (Byte *) data);
+
+	accumFor.nvars++;
+	accumFor.vars = erealloc(accumFor.vars, accumFor.nvars*sizeof(char *));
+
+	accumFor.vars[accumFor.nvars-1] = (char *) Buf_GetAll(buf, &varlen);
+
+	Buf_Destroy(buf, FALSE);
+}
+
 /*-
  *-----------------------------------------------------------------------
  * For_Eval --
@@ -116,7 +149,7 @@
 For_Eval (line)
     char    	    *line;    /* Line to parse */
 {
-    char	    *ptr = line, *sub, *wrd;
+    char	    *ptr = line, *sub, *in, *wrd;
     int	    	    level;  	/* Level at which to report errors. */
 
     level = PARSE_FATAL;
@@ -144,48 +177,55 @@
 	    ptr++;
 
 	/*
-	 * Grab the variable
+	 * Find the "in".
 	 */
-	buf = Buf_Init(0);
-	for (wrd = ptr; *ptr && !isspace((unsigned char) *ptr); ptr++)
-	    continue;
-	Buf_AddBytes(buf, ptr - wrd, (Byte *) wrd);
-
-	forVar = (char *) Buf_GetAll(buf, &varlen);
-	if (varlen == 0) {
-	    Parse_Error (level, "missing variable in for");
+	for (in = ptr; *in; in++) {
+	    if (isspace(in[0]) && in[1]=='i' && in[2]=='n' &&
+		(in[3]==0 || isspace(in[3])))
+		    break;
+	}
+	if (*in==0) {
+	    Parse_Error(level, "missing `in' in for");
 	    return 0;
 	}
-	Buf_Destroy(buf, FALSE);
-
-	while (*ptr && isspace((unsigned char) *ptr))
-	    ptr++;
 
 	/*
-	 * Grab the `in'
+	 * Grab the variables.
 	 */
-	if (ptr[0] != 'i' || ptr[1] != 'n' ||
-	    !isspace((unsigned char) ptr[2])) {
-	    Parse_Error (level, "missing `in' in for");
-	    printf("%s\n", ptr);
+	accumFor.vars = NULL;
+
+	while (ptr < in) {
+	    wrd = ptr;
+	    while (*ptr && !isspace((unsigned char) *ptr))
+	        ptr++;
+	    ForAddVar(wrd, ptr - wrd);
+	    while (*ptr && isspace((unsigned char) *ptr))
+		ptr++;
+	}
+
+	if (accumFor.nvars==0) {
+	    Parse_Error(level, "no iteration variables in for");
 	    return 0;
 	}
-	ptr += 3;
 
+	/* At this point we should be pointing right at the "in" */
+	assert(!memcmp(ptr, "in", 2));
+	ptr += 2;
+
 	while (*ptr && isspace((unsigned char) *ptr))
 	    ptr++;
 
 	/*
 	 * Make a list with the remaining words
 	 */
-	forLst = Lst_Init(FALSE);
+	accumFor.lst = Lst_Init(FALSE);
 	buf = Buf_Init(0);
 	sub = Var_Subst(NULL, ptr, VAR_GLOBAL, FALSE);
 
 #define ADDWORD() \
 	Buf_AddBytes(buf, ptr - wrd, (Byte *) wrd), \
 	Buf_AddByte(buf, (Byte) '\0'), \
-	Lst_AtFront(forLst, (ClientData) Buf_GetAll(buf, &varlen)), \
+	Lst_AtFront(accumFor.lst, (ClientData) Buf_GetAll(buf, &varlen)), \
 	Buf_Destroy(buf, FALSE)
 
 	for (ptr = sub; *ptr && isspace((unsigned char) *ptr); ptr++)
@@ -198,16 +238,21 @@
 		while (*ptr && isspace((unsigned char) *ptr))
 		    ptr++;
 		wrd = ptr--;
+	    }
+	if (DEBUG(FOR)) {
+	    int i;
+	    for (i=0; i<accumFor.nvars; i++) {
+		(void) fprintf(stderr, "For: variable %s\n", accumFor.vars[i]);
 	    }
-	if (DEBUG(FOR))
-	    (void) fprintf(stderr, "For: Iterator %s List %s\n", forVar, sub);
+	    (void) fprintf(stderr, "For: list %s\n", sub);
+	}
 	if (ptr - wrd > 0)
 	    ADDWORD();
 	else
 	    Buf_Destroy(buf, TRUE);
 	free((Address) sub);
 
-	forBuf = Buf_Init(0);
+	accumFor.buf = Buf_Init(0);
 	forLevel++;
 	return 1;
     }
@@ -234,8 +279,8 @@
     }
 
     if (forLevel != 0) {
-	Buf_AddBytes(forBuf, strlen(line), (Byte *) line);
-	Buf_AddByte(forBuf, (Byte) '\n');
+	Buf_AddBytes(accumFor.buf, strlen(line), (Byte *) line);
+	Buf_AddByte(accumFor.buf, (Byte) '\n');
 	return 1;
     }
     else {
@@ -243,42 +288,11 @@
     }
 }
 
-/*-
- *-----------------------------------------------------------------------
- * ForExec --
- *	Expand the for loop for this index and push it in the Makefile
- *
- * Results:
- *	None.
- *
- * Side Effects:
- *	None.
- *
- *-----------------------------------------------------------------------
- */
-static int
-ForExec(namep, argp)
-    ClientData namep;
-    ClientData argp;
-{
-    char *name = (char *) namep;
-    For *arg = (For *) argp;
-    int len;
-    Var_Set(arg->var, name, VAR_GLOBAL);
-    if (DEBUG(FOR))
-	(void) fprintf(stderr, "--- %s = %s\n", arg->var, name);
-    Parse_FromString(Var_Subst(arg->var, (char *) Buf_GetAll(arg->buf, &len),
-			       VAR_GLOBAL, FALSE));
-    Var_Delete(arg->var, VAR_GLOBAL);
-
-    return 0;
-}
-
 
 /*-
  *-----------------------------------------------------------------------
  * For_Run --
- *	Run the for loop, immitating the actions of an include file
+ *	Run the for loop, imitating the actions of an include file
  *
  * Results:
  *	None.
@@ -292,19 +306,83 @@
 For_Run()
 {
     For arg;
+    LstNode ln;
+    char **values;
+    int i, done=0, len;
+    char *guy, *orig_guy, *old_guy;
+
+    if (accumFor.buf == NULL || accumFor.vars == NULL || accumFor.lst == NULL)
+	return;
+    arg = accumFor;
+    accumFor.buf = NULL;
+    accumFor.vars = NULL;
+    accumFor.nvars = 0;
+    accumFor.lst = NULL;
 
-    if (forVar == NULL || forBuf == NULL || forLst == NULL)
+    if (Lst_Open(arg.lst)!=SUCCESS)
 	return;
-    arg.var = forVar;
-    arg.buf = forBuf;
-    arg.lst = forLst;
-    forVar = NULL;
-    forBuf = NULL;
-    forLst = NULL;
 
-    Lst_ForEach(arg.lst, ForExec, (ClientData) &arg);
+    values = emalloc(arg.nvars * sizeof(char *));
+    
+    while (!done) {
+	/* 
+	 * due to the dumb way this is set up, this loop must run
+	 * backwards.
+	 */
+	for (i=arg.nvars-1; i>=0; i--) {
+	    ln = Lst_Next(arg.lst);
+	    if (ln==NILLNODE) {
+		if (i!=arg.nvars-1) {
+		    Parse_Error(PARSE_FATAL, 
+				"Not enough words in for substitution list");
+		}
+		done = 1;
+		break;
+	    } else {
+		values[i] = (char *) Lst_Datum(ln);
+	    }
+	}
+	if (done)
+	    break;
+
+	for (i=0; i<arg.nvars; i++) {
+	    Var_Set(arg.vars[i], values[i], VAR_GLOBAL);
+	    if (DEBUG(FOR))
+		(void) fprintf(stderr, "--- %s = %s\n", arg.vars[i], 
+			       values[i]);
+	}
+
+	/*
+	 * Hack, hack, kludge.
+	 * This is really ugly, but to do it any better way would require
+	 * making major changes to var.c, which I don't want to get into
+	 * yet. There is no mechanism for expanding some variables, only
+	 * for expanding a single variable. That should be corrected, but
+	 * not right away. (XXX)
+	 */
+	
+	guy = (char *) Buf_GetAll(arg.buf, &len);
+	orig_guy = guy;
+	for (i=0; i<arg.nvars; i++) {
+	    old_guy = guy;
+	    guy = Var_Subst(arg.vars[i], guy, VAR_GLOBAL, FALSE);
+	    if (old_guy != orig_guy) free(old_guy);
+	}
+	Parse_FromString(guy);
+
+	for (i=0; i<arg.nvars; i++)
+	    Var_Delete(arg.vars[i], VAR_GLOBAL);
+    }
+
+    free(values);
+
+    Lst_Close(arg.lst);
+
+    for (i=0; i<arg.nvars; i++) {
+	free(arg.vars[i]);
+    }
+    free(arg.vars);
 
-    free((Address)arg.var);
     Lst_Destroy(arg.lst, (void (*) __P((ClientData))) free);
     Buf_Destroy(arg.buf, TRUE);
 }
Index: usr.bin/make/make.1
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/make/make.1,v
retrieving revision 1.38
diff -u -3 -r1.38 make.1
--- make.1	2000/02/08 12:56:28	1.38
+++ make.1	2000/04/16 13:49:21
@@ -843,7 +843,8 @@
 .Bl -tag -width Ds
 .It Xo
 .Ic \&.for
-.Ar variable
+.Ar variable 
+.Op Ar variable ...
 .Ic in
 .Ar expression
 .Xc
@@ -856,12 +857,17 @@
 .El
 After the for
 .Ic expression
-is evaluated, it is split into words. The
-iteration
-.Ic variable
-is successively set to each word, and substituted in the
+is evaluated, it is split into words. On each iteration of the loop, 
+one word is taken and assigned to each
+.Ic variable ,
+in order, and these
+.Ic variables
+are substituted into the
 .Ic make-rules
 inside the body of the for loop.
+The number of words must come out even; that is, if there are three
+iteration variables, the number of words provided must be a multiple
+of three.
 .Sh COMMENTS
 Comments begin with a hash
 .Pq Ql \&#
>Release-Note:
>Audit-Trail:
>Unformatted: