Home | History | Annotate | Line # | Download | only in make
for.c revision 1.41
      1 /*	$NetBSD: for.c,v 1.41 2008/12/29 10:12:30 dsl 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.41 2008/12/29 10:12:30 dsl 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.41 2008/12/29 10:12:30 dsl 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    <assert.h>
     56 #include    <ctype.h>
     57 
     58 #include    "make.h"
     59 #include    "hash.h"
     60 #include    "dir.h"
     61 #include    "buf.h"
     62 #include    "strlist.h"
     63 
     64 /*
     65  * For statements are of the form:
     66  *
     67  * .for <variable> in <varlist>
     68  * ...
     69  * .endfor
     70  *
     71  * The trick is to look for the matching end inside for for loop
     72  * To do that, we count the current nesting level of the for loops.
     73  * and the .endfor statements, accumulating all the statements between
     74  * the initial .for loop and the matching .endfor;
     75  * then we evaluate the for loop for each variable in the varlist.
     76  *
     77  * Note that any nested fors are just passed through; they get handled
     78  * recursively in For_Eval when we're expanding the enclosing for in
     79  * For_Run.
     80  */
     81 
     82 static int  	  forLevel = 0;  	/* Nesting level	*/
     83 
     84 /*
     85  * State of a for loop.
     86  */
     87 typedef struct _For {
     88     Buffer	  buf;			/* Body of loop		*/
     89     strlist_t     vars;			/* Iteration variables	*/
     90     strlist_t     items;		/* Substitution items */
     91 } For;
     92 
     93 static For        accumFor;             /* Loop being accumulated */
     94 
     95 
     96 
     98 static char *
     99 make_str(const char *ptr, int len)
    100 {
    101 	char *new_ptr;
    102 
    103 	new_ptr = bmake_malloc(len + 1);
    104 	memcpy(new_ptr, ptr, len);
    105 	new_ptr[len] = 0;
    106 	return new_ptr;
    107 }
    108 
    109 /*-
    110  *-----------------------------------------------------------------------
    111  * For_Eval --
    112  *	Evaluate the for loop in the passed line. The line
    113  *	looks like this:
    114  *	    .for <variable> in <varlist>
    115  *
    116  * Input:
    117  *	line		Line to parse
    118  *
    119  * Results:
    120  *      0: Not a .for statement, parse the line
    121  *	1: We found a for loop
    122  *     -1: A .for statement with a bad syntax error, discard.
    123  *
    124  * Side Effects:
    125  *	None.
    126  *
    127  *-----------------------------------------------------------------------
    128  */
    129 int
    130 For_Eval(char *line)
    131 {
    132     char *ptr = line, *sub;
    133     int len;
    134     int i;
    135     int escapes;
    136     int depth;
    137     char ch;
    138 
    139     /* Forget anything we previously knew about - it cannot be useful */
    140     memset(&accumFor, 0, sizeof accumFor);
    141 
    142     forLevel = 0;
    143     for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
    144 	continue;
    145     /*
    146      * If we are not in a for loop quickly determine if the statement is
    147      * a for.
    148      */
    149     if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' ||
    150 	    !isspace((unsigned char) ptr[3])) {
    151 	if (ptr[0] == 'e' && strncmp(ptr+1, "ndfor", 5) == 0) {
    152 	    Parse_Error(PARSE_FATAL, "for-less endfor");
    153 	    return -1;
    154 	}
    155 	return 0;
    156     }
    157     ptr += 3;
    158 
    159     /*
    160      * we found a for loop, and now we are going to parse it.
    161      */
    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 	    return -1;
    170 	}
    171 	for (len = 1; ptr[len] && !isspace((unsigned char)ptr[len]); len++)
    172 	    continue;
    173 	if (len == 2 && ptr[0] == 'i' && ptr[1] == 'n') {
    174 	    ptr += 2;
    175 	    break;
    176 	}
    177 	strlist_add_str(&accumFor.vars, make_str(ptr, len));
    178     }
    179 
    180     if (strlist_num(&accumFor.vars) == 0) {
    181 	Parse_Error(PARSE_FATAL, "no iteration variables in for");
    182 	return -1;
    183     }
    184 
    185     while (*ptr && isspace((unsigned char) *ptr))
    186 	ptr++;
    187 
    188     /*
    189      * Make a list with the remaining words
    190      * The values are substituted as ${:U<value>... so we must \ escape
    191      * characters that break that syntax - particularly ':', maybe $ and \.
    192      */
    193     sub = Var_Subst(NULL, ptr, VAR_GLOBAL, FALSE);
    194 
    195     for (ptr = sub;; ptr += len) {
    196 	while (*ptr && isspace((unsigned char)*ptr))
    197 	    ptr++;
    198 	if (*ptr == 0)
    199 	    break;
    200 	escapes = 0;
    201 	for (len = 0; ptr[len] && !isspace((unsigned char)ptr[len]); len++)
    202 	    if (ptr[len] == ':')
    203 		escapes++;
    204 	if (escapes == 0)
    205 	    strlist_add_str(&accumFor.items, make_str(ptr, len));
    206 	else {
    207 	    char *item = bmake_malloc(len + escapes + 1);
    208 	    strlist_add_str(&accumFor.items, item);
    209 	    for (depth= 0, i = 0; i < len; i++) {
    210 		ch = ptr[i];
    211 		/* Loose determination of nested variable definitions. */
    212 		if (ch == '(' || ch == '{')
    213 		    depth++;
    214 		else if (ch == ')' || ch == '}')
    215 		    depth--;
    216 		else if (ch == ':' && depth == 0)
    217 		    *item++ = '\\';
    218 		*item++ = ch;
    219 	    }
    220 	    *item = 0;
    221 	}
    222     }
    223 
    224     free(sub);
    225 
    226     if (strlist_num(&accumFor.items) % strlist_num(&accumFor.vars)) {
    227 	Parse_Error(PARSE_FATAL,
    228 		"Wrong number of words in .for substitution list %d %d",
    229 		strlist_num(&accumFor.items), strlist_num(&accumFor.vars));
    230 	/*
    231 	 * Return 'success' so that the body of the .for loop is accumulated.
    232 	 * The loop will have zero iterations expanded due a later test.
    233 	 */
    234     }
    235 
    236     accumFor.buf = Buf_Init(0);
    237     forLevel = 1;
    238     return 1;
    239 }
    240 
    241 /*
    242  * Add another line to a .for loop.
    243  * Returns 0 when the matching .enfor is reached.
    244  */
    245 
    246 int
    247 For_Accum(char *line)
    248 {
    249     char *ptr = line;
    250 
    251     if (*ptr == '.') {
    252 
    253 	for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
    254 	    continue;
    255 
    256 	if (strncmp(ptr, "endfor", 6) == 0 &&
    257 		(isspace((unsigned char) ptr[6]) || !ptr[6])) {
    258 	    if (DEBUG(FOR))
    259 		(void)fprintf(debug_file, "For: end for %d\n", forLevel);
    260 	    if (--forLevel <= 0)
    261 		return 0;
    262 	} else if (strncmp(ptr, "for", 3) == 0 &&
    263 		 isspace((unsigned char) ptr[3])) {
    264 	    forLevel++;
    265 	    if (DEBUG(FOR))
    266 		(void)fprintf(debug_file, "For: new loop %d\n", forLevel);
    267 	}
    268     }
    269 
    270     Buf_AddBytes(accumFor.buf, strlen(line), (Byte *)line);
    271     Buf_AddByte(accumFor.buf, (Byte)'\n');
    272     return 1;
    273 }
    274 
    275 
    276 /*-
    278  *-----------------------------------------------------------------------
    279  * For_Run --
    280  *	Run the for loop, imitating the actions of an include file
    281  *
    282  * Results:
    283  *	None.
    284  *
    285  * Side Effects:
    286  *	None.
    287  *
    288  *-----------------------------------------------------------------------
    289  */
    290 void
    291 For_Run(int lineno)
    292 {
    293     For arg;
    294     int i, len;
    295     unsigned int num_items;
    296     char *for_body;
    297     char *var, *item;
    298     char *cp;
    299     char *cmd_cp;
    300     char *body_end;
    301     char ch;
    302     Buffer cmds;
    303     int short_var;
    304 
    305     arg = accumFor;
    306     memset(&accumFor, 0, sizeof accumFor);
    307 
    308     num_items = strlist_num(&arg.items);
    309     if (num_items % strlist_num(&arg.vars))
    310 	/* Error message already printed */
    311 	goto out;
    312 
    313     short_var = 0;
    314     STRLIST_FOREACH(var, &arg.vars, i) {
    315 	if (var[1] == 0) {
    316 	    short_var = 1;
    317 	    break;
    318 	}
    319     }
    320 
    321     /*
    322      * Scan the for loop body and replace references to the loop variables
    323      * with variable references that expand to the required text.
    324      * Using variable expansions ensures that the .for loop can't generate
    325      * syntax, and that the later parsing will still see a variable.
    326      * We assume that the null variable will never be defined.
    327      *
    328      * The detection of substitions of the loop control variable is naive.
    329      * Many of the modifiers use \ to escape $ (not $) so it is possible
    330      * to contrive a makefile where an unwanted substitution happens.
    331      *
    332      * Each loop expansion is fed back into the parser as if it were an
    333      * include file.  This means we have to generate the last iteration first.
    334      */
    335     while (num_items != 0) {
    336 	num_items -= strlist_num(&arg.vars);
    337 	for_body = (char *)Buf_GetAll(arg.buf, &len);
    338 	body_end = for_body + len;
    339 	cmds = Buf_Init(len + 256);
    340 	cmd_cp = for_body;
    341 	for (cp = for_body; (cp = strchr(cp, '$')) != NULL;) {
    342 	    ch = *++cp;
    343 	    if (ch == '(' || ch == '{') {
    344 		char ech = ch == '(' ? ')' : '}';
    345 		cp++;
    346 		/* Check variable name against the .for loop variables */
    347 		STRLIST_FOREACH(var, &arg.vars, i) {
    348 		    len = strlen(var);
    349 		    if (memcmp(cp, var, len) != 0)
    350 			continue;
    351 		    if (cp[len] != ':' && cp[len] != ech && cp[len] != '\\')
    352 			continue;
    353 		    /* Found a variable match. Replace with ${:U<value> */
    354 		    Buf_AddBytes(cmds, cp - cmd_cp, cmd_cp);
    355 		    Buf_AddBytes(cmds, 2, ":U");
    356 		    cp += len;
    357 		    cmd_cp = cp;
    358 		    item = strlist_str(&arg.items, num_items + i);
    359 		    Buf_AddBytes(cmds, strlen(item), item);
    360 		    break;
    361 		}
    362 		continue;
    363 	    }
    364 	    if (ch == 0)
    365 		break;
    366 	    /* Probably a single character name, ignore $$ and stupid ones. */
    367 	    if (!short_var || strchr("}):$", ch) != NULL) {
    368 		cp++;
    369 		continue;
    370 	    }
    371 	    STRLIST_FOREACH(var, &arg.vars, i) {
    372 		if (var[0] != ch || var[1] != 0)
    373 		    continue;
    374 		/* Found a variable match. Replace with ${:U<value>} */
    375 		Buf_AddBytes(cmds, cp - cmd_cp, cmd_cp);
    376 		Buf_AddBytes(cmds, 3, "{:U");
    377 		cmd_cp = ++cp;
    378 		item = strlist_str(&arg.items, num_items + i);
    379 		Buf_AddBytes(cmds, strlen(item), item);
    380 		Buf_AddBytes(cmds, 1, "}");
    381 		break;
    382 	    }
    383 	}
    384 	Buf_AddBytes(cmds, body_end - cmd_cp, cmd_cp);
    385 
    386 	cp = Buf_GetAll(cmds, NULL);
    387 	if (DEBUG(FOR))
    388 	    (void)fprintf(debug_file, "For: loop body:\n%s", cp);
    389 	Parse_SetInput(NULL, lineno, -1, cp);
    390 	Buf_Destroy(cmds, FALSE);
    391     }
    392 
    393   out:
    394     strlist_clean(&arg.vars);
    395     strlist_clean(&arg.items);
    396 
    397     Buf_Destroy(arg.buf, TRUE);
    398 }
    399