Home | History | Annotate | Line # | Download | only in make
for.c revision 1.39
      1 /*	$NetBSD: for.c,v 1.39 2008/12/21 18:06:53 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.39 2008/12/21 18:06:53 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.39 2008/12/21 18:06:53 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 
    137     /* Forget anything we previously knew about - it cannot be useful */
    138     memset(&accumFor, 0, sizeof accumFor);
    139 
    140     forLevel = 0;
    141     for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
    142 	continue;
    143     /*
    144      * If we are not in a for loop quickly determine if the statement is
    145      * a for.
    146      */
    147     if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' ||
    148 	    !isspace((unsigned char) ptr[3])) {
    149 	if (ptr[0] == 'e' && strncmp(ptr+1, "ndfor", 5) == 0) {
    150 	    Parse_Error(PARSE_FATAL, "for-less endfor");
    151 	    return -1;
    152 	}
    153 	return 0;
    154     }
    155     ptr += 3;
    156 
    157     /*
    158      * we found a for loop, and now we are going to parse it.
    159      */
    160 
    161     /* Grab the variables. Terminate on "in". */
    162     for (;; ptr += len) {
    163 	while (*ptr && isspace((unsigned char) *ptr))
    164 	    ptr++;
    165 	if (*ptr == '\0') {
    166 	    Parse_Error(PARSE_FATAL, "missing `in' in for");
    167 	    return -1;
    168 	}
    169 	for (len = 1; ptr[len] && !isspace((unsigned char)ptr[len]); len++)
    170 	    continue;
    171 	if (len == 2 && ptr[0] == 'i' && ptr[1] == 'n') {
    172 	    ptr += 2;
    173 	    break;
    174 	}
    175 	strlist_add_str(&accumFor.vars, make_str(ptr, len));
    176     }
    177 
    178     if (strlist_num(&accumFor.vars) == 0) {
    179 	Parse_Error(PARSE_FATAL, "no iteration variables in for");
    180 	return -1;
    181     }
    182 
    183     while (*ptr && isspace((unsigned char) *ptr))
    184 	ptr++;
    185 
    186     /*
    187      * Make a list with the remaining words
    188      * The values are substituted as ${:U<value>... so we must \ escape
    189      * characters that break that syntax - particularly ':', maybe $ and \.
    190      */
    191     sub = Var_Subst(NULL, ptr, VAR_GLOBAL, FALSE);
    192 
    193     for (ptr = sub;; ptr += len) {
    194 	while (*ptr && isspace((unsigned char)*ptr))
    195 	    ptr++;
    196 	if (*ptr == 0)
    197 	    break;
    198 	escapes = 0;
    199 	for (len = 0; ptr[len] && !isspace((unsigned char)ptr[len]); len++)
    200 	    if (ptr[len] == ':')
    201 		escapes++;
    202 	if (escapes == 0)
    203 	    strlist_add_str(&accumFor.items, make_str(ptr, len));
    204 	else {
    205 	    char *item = bmake_malloc(len + escapes + 1);
    206 	    strlist_add_str(&accumFor.items, item);
    207 	    for (i = 0; i < len; i++) {
    208 		if (ptr[i] == ':')
    209 		    *item++ = '\\';
    210 		*item++ = ptr[i];
    211 	    }
    212 	    *item = 0;
    213 	}
    214     }
    215 
    216     free(sub);
    217 
    218     if (strlist_num(&accumFor.items) % strlist_num(&accumFor.vars)) {
    219 	Parse_Error(PARSE_FATAL,
    220 		"Wrong number of words in .for substitution list %d %d",
    221 		strlist_num(&accumFor.items), strlist_num(&accumFor.vars));
    222 	/*
    223 	 * Return 'success' so that the body of the .for loop is accumulated.
    224 	 * The loop will have zero iterations expanded due a later test.
    225 	 */
    226     }
    227 
    228     accumFor.buf = Buf_Init(0);
    229     forLevel = 1;
    230     return 1;
    231 }
    232 
    233 /*
    234  * Add another line to a .for loop.
    235  * Returns 0 when the matching .enfor is reached.
    236  */
    237 
    238 int
    239 For_Accum(char *line)
    240 {
    241     char *ptr = line;
    242 
    243     if (*ptr == '.') {
    244 
    245 	for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
    246 	    continue;
    247 
    248 	if (strncmp(ptr, "endfor", 6) == 0 &&
    249 		(isspace((unsigned char) ptr[6]) || !ptr[6])) {
    250 	    if (DEBUG(FOR))
    251 		(void)fprintf(debug_file, "For: end for %d\n", forLevel);
    252 	    if (--forLevel <= 0)
    253 		return 0;
    254 	} else if (strncmp(ptr, "for", 3) == 0 &&
    255 		 isspace((unsigned char) ptr[3])) {
    256 	    forLevel++;
    257 	    if (DEBUG(FOR))
    258 		(void)fprintf(debug_file, "For: new loop %d\n", forLevel);
    259 	}
    260     }
    261 
    262     Buf_AddBytes(accumFor.buf, strlen(line), (Byte *)line);
    263     Buf_AddByte(accumFor.buf, (Byte)'\n');
    264     return 1;
    265 }
    266 
    267 
    268 /*-
    270  *-----------------------------------------------------------------------
    271  * For_Run --
    272  *	Run the for loop, imitating the actions of an include file
    273  *
    274  * Results:
    275  *	None.
    276  *
    277  * Side Effects:
    278  *	None.
    279  *
    280  *-----------------------------------------------------------------------
    281  */
    282 void
    283 For_Run(int lineno)
    284 {
    285     For arg;
    286     int i, len;
    287     unsigned int num_items;
    288     char *for_body;
    289     char *var, *item;
    290     char *cp;
    291     char *cmd_cp;
    292     char *body_end;
    293     char ch;
    294     Buffer cmds;
    295     int short_var;
    296 
    297     arg = accumFor;
    298     memset(&accumFor, 0, sizeof accumFor);
    299 
    300     num_items = strlist_num(&arg.items);
    301     if (num_items % strlist_num(&arg.vars))
    302 	/* Error message already printed */
    303 	goto out;
    304 
    305     short_var = 0;
    306     STRLIST_FOREACH(var, &arg.vars, i) {
    307 	if (var[1] == 0) {
    308 	    short_var = 1;
    309 	    break;
    310 	}
    311     }
    312 
    313     /*
    314      * Scan the for loop body and replace references to the loop variables
    315      * with variable references that expand to the required text.
    316      * Using variable expansions ensures that the .for loop can't generate
    317      * syntax, and that the later parsing will still see a variable.
    318      * We assume that the null variable will never be defined.
    319      *
    320      * The detection of substitions of the loop control variable is naive.
    321      * Many of the modifiers use \ to escape $ (not $) so it is possible
    322      * to contrive a makefile where an unwanted substitution happens.
    323      *
    324      * Each loop expansion is fed back into the parser as if it were an
    325      * include file.  This means we have to generate the last iteration first.
    326      */
    327     while (num_items != 0) {
    328 	num_items -= strlist_num(&arg.vars);
    329 	for_body = (char *)Buf_GetAll(arg.buf, &len);
    330 	body_end = for_body + len;
    331 	cmds = Buf_Init(len + 256);
    332 	cmd_cp = for_body;
    333 	for (cp = for_body; (cp = strchr(cp, '$')) != NULL;) {
    334 	    ch = *++cp;
    335 	    if (ch == '(' || ch == '{') {
    336 		char ech = ch == '(' ? ')' : '}';
    337 		cp++;
    338 		/* Check variable name against the .for loop varoables */
    339 		STRLIST_FOREACH(var, &arg.vars, i) {
    340 		    len = strlen(var);
    341 		    if (memcmp(cp, var, len) != 0)
    342 			continue;
    343 		    if (cp[len] != ':' && cp[len] != ech)
    344 			continue;
    345 		    /* Found a variable match. Replace with ${:U<value> */
    346 		    Buf_AddBytes(cmds, cp - cmd_cp, cmd_cp);
    347 		    Buf_AddBytes(cmds, 2, ":U");
    348 		    cp += len;
    349 		    cmd_cp = cp;
    350 		    item = strlist_str(&arg.items, num_items + i);
    351 		    Buf_AddBytes(cmds, strlen(item), item);
    352 		    break;
    353 		}
    354 		continue;
    355 	    }
    356 	    if (ch == 0)
    357 		break;
    358 	    /* Probably a single character name, ignore $$ and stupid ones. */
    359 	    if (!short_var || strchr("}):$", ch) != NULL) {
    360 		cp++;
    361 		continue;
    362 	    }
    363 	    STRLIST_FOREACH(var, &arg.vars, i) {
    364 		if (var[0] != ch || var[1] != 0)
    365 		    continue;
    366 		/* Found a variable match. Replace with ${:U<value>} */
    367 		Buf_AddBytes(cmds, cp - cmd_cp, cmd_cp);
    368 		Buf_AddBytes(cmds, 3, "{:U");
    369 		cmd_cp = ++cp;
    370 		item = strlist_str(&arg.items, num_items + i);
    371 		Buf_AddBytes(cmds, strlen(item), item);
    372 		Buf_AddBytes(cmds, 1, "}");
    373 		break;
    374 	    }
    375 	}
    376 	Buf_AddBytes(cmds, body_end - cmd_cp, cmd_cp);
    377 
    378 	cp = Buf_GetAll(cmds, NULL);
    379 	if (DEBUG(FOR))
    380 	    (void)fprintf(debug_file, "For: loop body:\n%s", cp);
    381 	Parse_SetInput(NULL, lineno, -1, cp);
    382 	Buf_Destroy(cmds, FALSE);
    383     }
    384 
    385   out:
    386     strlist_clean(&arg.vars);
    387     strlist_clean(&arg.items);
    388 
    389     Buf_Destroy(arg.buf, TRUE);
    390 }
    391