1321964Ssjg/* $NetBSD: for.c,v 1.53 2017/04/16 21:04:44 riastradh Exp $ */ 2236769Sobrien 3236769Sobrien/* 4236769Sobrien * Copyright (c) 1992, The Regents of the University of California. 5236769Sobrien * All rights reserved. 6236769Sobrien * 7236769Sobrien * Redistribution and use in source and binary forms, with or without 8236769Sobrien * modification, are permitted provided that the following conditions 9236769Sobrien * are met: 10236769Sobrien * 1. Redistributions of source code must retain the above copyright 11236769Sobrien * notice, this list of conditions and the following disclaimer. 12236769Sobrien * 2. Redistributions in binary form must reproduce the above copyright 13236769Sobrien * notice, this list of conditions and the following disclaimer in the 14236769Sobrien * documentation and/or other materials provided with the distribution. 15236769Sobrien * 3. Neither the name of the University nor the names of its contributors 16236769Sobrien * may be used to endorse or promote products derived from this software 17236769Sobrien * without specific prior written permission. 18236769Sobrien * 19236769Sobrien * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20236769Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21236769Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22236769Sobrien * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23236769Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24236769Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25236769Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26236769Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27236769Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28236769Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29236769Sobrien * SUCH DAMAGE. 30236769Sobrien */ 31236769Sobrien 32236769Sobrien#ifndef MAKE_NATIVE 33321964Ssjgstatic char rcsid[] = "$NetBSD: for.c,v 1.53 2017/04/16 21:04:44 riastradh Exp $"; 34236769Sobrien#else 35236769Sobrien#include <sys/cdefs.h> 36236769Sobrien#ifndef lint 37236769Sobrien#if 0 38236769Sobrienstatic char sccsid[] = "@(#)for.c 8.1 (Berkeley) 6/6/93"; 39236769Sobrien#else 40321964Ssjg__RCSID("$NetBSD: for.c,v 1.53 2017/04/16 21:04:44 riastradh Exp $"); 41236769Sobrien#endif 42236769Sobrien#endif /* not lint */ 43236769Sobrien#endif 44236769Sobrien 45236769Sobrien/*- 46236769Sobrien * for.c -- 47236769Sobrien * Functions to handle loops in a makefile. 48236769Sobrien * 49236769Sobrien * Interface: 50236769Sobrien * For_Eval Evaluate the loop in the passed line. 51236769Sobrien * For_Run Run accumulated loop 52236769Sobrien * 53236769Sobrien */ 54236769Sobrien 55236769Sobrien#include <assert.h> 56236769Sobrien#include <ctype.h> 57236769Sobrien 58236769Sobrien#include "make.h" 59236769Sobrien#include "hash.h" 60236769Sobrien#include "dir.h" 61236769Sobrien#include "buf.h" 62236769Sobrien#include "strlist.h" 63236769Sobrien 64236769Sobrien#define FOR_SUB_ESCAPE_CHAR 1 65236769Sobrien#define FOR_SUB_ESCAPE_BRACE 2 66236769Sobrien#define FOR_SUB_ESCAPE_PAREN 4 67236769Sobrien 68236769Sobrien/* 69236769Sobrien * For statements are of the form: 70236769Sobrien * 71236769Sobrien * .for <variable> in <varlist> 72236769Sobrien * ... 73236769Sobrien * .endfor 74236769Sobrien * 75236769Sobrien * The trick is to look for the matching end inside for for loop 76236769Sobrien * To do that, we count the current nesting level of the for loops. 77236769Sobrien * and the .endfor statements, accumulating all the statements between 78236769Sobrien * the initial .for loop and the matching .endfor; 79236769Sobrien * then we evaluate the for loop for each variable in the varlist. 80236769Sobrien * 81236769Sobrien * Note that any nested fors are just passed through; they get handled 82236769Sobrien * recursively in For_Eval when we're expanding the enclosing for in 83236769Sobrien * For_Run. 84236769Sobrien */ 85236769Sobrien 86236769Sobrienstatic int forLevel = 0; /* Nesting level */ 87236769Sobrien 88236769Sobrien/* 89236769Sobrien * State of a for loop. 90236769Sobrien */ 91236769Sobrientypedef struct _For { 92236769Sobrien Buffer buf; /* Body of loop */ 93236769Sobrien strlist_t vars; /* Iteration variables */ 94236769Sobrien strlist_t items; /* Substitution items */ 95236769Sobrien char *parse_buf; 96236769Sobrien int short_var; 97236769Sobrien int sub_next; 98236769Sobrien} For; 99236769Sobrien 100236769Sobrienstatic For *accumFor; /* Loop being accumulated */ 101236769Sobrien 102236769Sobrien 103236769Sobrien 104236769Sobrienstatic char * 105236769Sobrienmake_str(const char *ptr, int len) 106236769Sobrien{ 107236769Sobrien char *new_ptr; 108236769Sobrien 109236769Sobrien new_ptr = bmake_malloc(len + 1); 110236769Sobrien memcpy(new_ptr, ptr, len); 111236769Sobrien new_ptr[len] = 0; 112236769Sobrien return new_ptr; 113236769Sobrien} 114236769Sobrien 115236769Sobrienstatic void 116236769SobrienFor_Free(For *arg) 117236769Sobrien{ 118236769Sobrien Buf_Destroy(&arg->buf, TRUE); 119236769Sobrien strlist_clean(&arg->vars); 120236769Sobrien strlist_clean(&arg->items); 121236769Sobrien free(arg->parse_buf); 122236769Sobrien 123236769Sobrien free(arg); 124236769Sobrien} 125236769Sobrien 126236769Sobrien/*- 127236769Sobrien *----------------------------------------------------------------------- 128236769Sobrien * For_Eval -- 129236769Sobrien * Evaluate the for loop in the passed line. The line 130236769Sobrien * looks like this: 131236769Sobrien * .for <variable> in <varlist> 132236769Sobrien * 133236769Sobrien * Input: 134236769Sobrien * line Line to parse 135236769Sobrien * 136236769Sobrien * Results: 137236769Sobrien * 0: Not a .for statement, parse the line 138236769Sobrien * 1: We found a for loop 139236769Sobrien * -1: A .for statement with a bad syntax error, discard. 140236769Sobrien * 141236769Sobrien * Side Effects: 142236769Sobrien * None. 143236769Sobrien * 144236769Sobrien *----------------------------------------------------------------------- 145236769Sobrien */ 146236769Sobrienint 147236769SobrienFor_Eval(char *line) 148236769Sobrien{ 149236769Sobrien For *new_for; 150236769Sobrien char *ptr = line, *sub; 151236769Sobrien int len; 152236769Sobrien int escapes; 153236769Sobrien unsigned char ch; 154236769Sobrien char **words, *word_buf; 155236769Sobrien int n, nwords; 156236769Sobrien 157236769Sobrien /* Skip the '.' and any following whitespace */ 158236769Sobrien for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++) 159236769Sobrien continue; 160236769Sobrien 161236769Sobrien /* 162236769Sobrien * If we are not in a for loop quickly determine if the statement is 163236769Sobrien * a for. 164236769Sobrien */ 165236769Sobrien if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' || 166236769Sobrien !isspace((unsigned char) ptr[3])) { 167236769Sobrien if (ptr[0] == 'e' && strncmp(ptr+1, "ndfor", 5) == 0) { 168236769Sobrien Parse_Error(PARSE_FATAL, "for-less endfor"); 169236769Sobrien return -1; 170236769Sobrien } 171236769Sobrien return 0; 172236769Sobrien } 173236769Sobrien ptr += 3; 174236769Sobrien 175236769Sobrien /* 176236769Sobrien * we found a for loop, and now we are going to parse it. 177236769Sobrien */ 178236769Sobrien 179236769Sobrien new_for = bmake_malloc(sizeof *new_for); 180236769Sobrien memset(new_for, 0, sizeof *new_for); 181236769Sobrien 182236769Sobrien /* Grab the variables. Terminate on "in". */ 183236769Sobrien for (;; ptr += len) { 184236769Sobrien while (*ptr && isspace((unsigned char) *ptr)) 185236769Sobrien ptr++; 186236769Sobrien if (*ptr == '\0') { 187236769Sobrien Parse_Error(PARSE_FATAL, "missing `in' in for"); 188236769Sobrien For_Free(new_for); 189236769Sobrien return -1; 190236769Sobrien } 191236769Sobrien for (len = 1; ptr[len] && !isspace((unsigned char)ptr[len]); len++) 192236769Sobrien continue; 193236769Sobrien if (len == 2 && ptr[0] == 'i' && ptr[1] == 'n') { 194236769Sobrien ptr += 2; 195236769Sobrien break; 196236769Sobrien } 197236769Sobrien if (len == 1) 198236769Sobrien new_for->short_var = 1; 199236769Sobrien strlist_add_str(&new_for->vars, make_str(ptr, len), len); 200236769Sobrien } 201236769Sobrien 202236769Sobrien if (strlist_num(&new_for->vars) == 0) { 203236769Sobrien Parse_Error(PARSE_FATAL, "no iteration variables in for"); 204236769Sobrien For_Free(new_for); 205236769Sobrien return -1; 206236769Sobrien } 207236769Sobrien 208236769Sobrien while (*ptr && isspace((unsigned char) *ptr)) 209236769Sobrien ptr++; 210236769Sobrien 211236769Sobrien /* 212236769Sobrien * Make a list with the remaining words 213236769Sobrien * The values are substituted as ${:U<value>...} so we must \ escape 214236769Sobrien * characters that break that syntax. 215236769Sobrien * Variables are fully expanded - so it is safe for escape $. 216236769Sobrien * We can't do the escapes here - because we don't know whether 217236769Sobrien * we are substuting into ${...} or $(...). 218236769Sobrien */ 219321964Ssjg sub = Var_Subst(NULL, ptr, VAR_GLOBAL, VARF_WANTRES); 220236769Sobrien 221236769Sobrien /* 222236769Sobrien * Split into words allowing for quoted strings. 223236769Sobrien */ 224236769Sobrien words = brk_string(sub, &nwords, FALSE, &word_buf); 225236769Sobrien 226236769Sobrien free(sub); 227236769Sobrien 228236769Sobrien if (words != NULL) { 229236769Sobrien for (n = 0; n < nwords; n++) { 230236769Sobrien ptr = words[n]; 231236769Sobrien if (!*ptr) 232236769Sobrien continue; 233236769Sobrien escapes = 0; 234236769Sobrien while ((ch = *ptr++)) { 235236769Sobrien switch(ch) { 236236769Sobrien case ':': 237236769Sobrien case '$': 238236769Sobrien case '\\': 239236769Sobrien escapes |= FOR_SUB_ESCAPE_CHAR; 240236769Sobrien break; 241236769Sobrien case ')': 242236769Sobrien escapes |= FOR_SUB_ESCAPE_PAREN; 243236769Sobrien break; 244236769Sobrien case /*{*/ '}': 245236769Sobrien escapes |= FOR_SUB_ESCAPE_BRACE; 246236769Sobrien break; 247236769Sobrien } 248236769Sobrien } 249236769Sobrien /* 250236769Sobrien * We have to dup words[n] to maintain the semantics of 251236769Sobrien * strlist. 252236769Sobrien */ 253236769Sobrien strlist_add_str(&new_for->items, bmake_strdup(words[n]), escapes); 254236769Sobrien } 255236769Sobrien 256236769Sobrien free(words); 257236769Sobrien free(word_buf); 258236769Sobrien 259236769Sobrien if ((len = strlist_num(&new_for->items)) > 0 && 260236769Sobrien len % (n = strlist_num(&new_for->vars))) { 261236769Sobrien Parse_Error(PARSE_FATAL, 262236769Sobrien "Wrong number of words (%d) in .for substitution list" 263236769Sobrien " with %d vars", len, n); 264236769Sobrien /* 265236769Sobrien * Return 'success' so that the body of the .for loop is 266236769Sobrien * accumulated. 267236769Sobrien * Remove all items so that the loop doesn't iterate. 268236769Sobrien */ 269236769Sobrien strlist_clean(&new_for->items); 270236769Sobrien } 271236769Sobrien } 272236769Sobrien 273236769Sobrien Buf_Init(&new_for->buf, 0); 274236769Sobrien accumFor = new_for; 275236769Sobrien forLevel = 1; 276236769Sobrien return 1; 277236769Sobrien} 278236769Sobrien 279236769Sobrien/* 280236769Sobrien * Add another line to a .for loop. 281236769Sobrien * Returns 0 when the matching .endfor is reached. 282236769Sobrien */ 283236769Sobrien 284236769Sobrienint 285236769SobrienFor_Accum(char *line) 286236769Sobrien{ 287236769Sobrien char *ptr = line; 288236769Sobrien 289236769Sobrien if (*ptr == '.') { 290236769Sobrien 291236769Sobrien for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++) 292236769Sobrien continue; 293236769Sobrien 294236769Sobrien if (strncmp(ptr, "endfor", 6) == 0 && 295236769Sobrien (isspace((unsigned char) ptr[6]) || !ptr[6])) { 296236769Sobrien if (DEBUG(FOR)) 297236769Sobrien (void)fprintf(debug_file, "For: end for %d\n", forLevel); 298236769Sobrien if (--forLevel <= 0) 299236769Sobrien return 0; 300236769Sobrien } else if (strncmp(ptr, "for", 3) == 0 && 301236769Sobrien isspace((unsigned char) ptr[3])) { 302236769Sobrien forLevel++; 303236769Sobrien if (DEBUG(FOR)) 304236769Sobrien (void)fprintf(debug_file, "For: new loop %d\n", forLevel); 305236769Sobrien } 306236769Sobrien } 307236769Sobrien 308236769Sobrien Buf_AddBytes(&accumFor->buf, strlen(line), line); 309236769Sobrien Buf_AddByte(&accumFor->buf, '\n'); 310236769Sobrien return 1; 311236769Sobrien} 312236769Sobrien 313236769Sobrien 314236769Sobrien/*- 315236769Sobrien *----------------------------------------------------------------------- 316236769Sobrien * For_Run -- 317236769Sobrien * Run the for loop, imitating the actions of an include file 318236769Sobrien * 319236769Sobrien * Results: 320236769Sobrien * None. 321236769Sobrien * 322236769Sobrien * Side Effects: 323236769Sobrien * None. 324236769Sobrien * 325236769Sobrien *----------------------------------------------------------------------- 326236769Sobrien */ 327236769Sobrien 328236769Sobrienstatic int 329236769Sobrienfor_var_len(const char *var) 330236769Sobrien{ 331236769Sobrien char ch, var_start, var_end; 332236769Sobrien int depth; 333236769Sobrien int len; 334236769Sobrien 335236769Sobrien var_start = *var; 336236769Sobrien if (var_start == 0) 337236769Sobrien /* just escape the $ */ 338236769Sobrien return 0; 339236769Sobrien 340236769Sobrien if (var_start == '(') 341236769Sobrien var_end = ')'; 342236769Sobrien else if (var_start == '{') 343236769Sobrien var_end = '}'; 344236769Sobrien else 345236769Sobrien /* Single char variable */ 346236769Sobrien return 1; 347236769Sobrien 348236769Sobrien depth = 1; 349236769Sobrien for (len = 1; (ch = var[len++]) != 0;) { 350236769Sobrien if (ch == var_start) 351236769Sobrien depth++; 352236769Sobrien else if (ch == var_end && --depth == 0) 353236769Sobrien return len; 354236769Sobrien } 355236769Sobrien 356236769Sobrien /* Variable end not found, escape the $ */ 357236769Sobrien return 0; 358236769Sobrien} 359236769Sobrien 360236769Sobrienstatic void 361236769Sobrienfor_substitute(Buffer *cmds, strlist_t *items, unsigned int item_no, char ech) 362236769Sobrien{ 363236769Sobrien const char *item = strlist_str(items, item_no); 364236769Sobrien int len; 365236769Sobrien char ch; 366236769Sobrien 367236769Sobrien /* If there were no escapes, or the only escape is the other variable 368236769Sobrien * terminator, then just substitute the full string */ 369236769Sobrien if (!(strlist_info(items, item_no) & 370236769Sobrien (ech == ')' ? ~FOR_SUB_ESCAPE_BRACE : ~FOR_SUB_ESCAPE_PAREN))) { 371236769Sobrien Buf_AddBytes(cmds, strlen(item), item); 372236769Sobrien return; 373236769Sobrien } 374236769Sobrien 375236769Sobrien /* Escape ':', '$', '\\' and 'ech' - removed by :U processing */ 376236769Sobrien while ((ch = *item++) != 0) { 377236769Sobrien if (ch == '$') { 378236769Sobrien len = for_var_len(item); 379236769Sobrien if (len != 0) { 380236769Sobrien Buf_AddBytes(cmds, len + 1, item - 1); 381236769Sobrien item += len; 382236769Sobrien continue; 383236769Sobrien } 384236769Sobrien Buf_AddByte(cmds, '\\'); 385236769Sobrien } else if (ch == ':' || ch == '\\' || ch == ech) 386236769Sobrien Buf_AddByte(cmds, '\\'); 387236769Sobrien Buf_AddByte(cmds, ch); 388236769Sobrien } 389236769Sobrien} 390236769Sobrien 391236769Sobrienstatic char * 392236769SobrienFor_Iterate(void *v_arg, size_t *ret_len) 393236769Sobrien{ 394236769Sobrien For *arg = v_arg; 395236769Sobrien int i, len; 396236769Sobrien char *var; 397236769Sobrien char *cp; 398236769Sobrien char *cmd_cp; 399236769Sobrien char *body_end; 400236769Sobrien char ch; 401236769Sobrien Buffer cmds; 402236769Sobrien 403236769Sobrien if (arg->sub_next + strlist_num(&arg->vars) > strlist_num(&arg->items)) { 404236769Sobrien /* No more iterations */ 405236769Sobrien For_Free(arg); 406236769Sobrien return NULL; 407236769Sobrien } 408236769Sobrien 409236769Sobrien free(arg->parse_buf); 410236769Sobrien arg->parse_buf = NULL; 411236769Sobrien 412236769Sobrien /* 413236769Sobrien * Scan the for loop body and replace references to the loop variables 414236769Sobrien * with variable references that expand to the required text. 415236769Sobrien * Using variable expansions ensures that the .for loop can't generate 416236769Sobrien * syntax, and that the later parsing will still see a variable. 417236769Sobrien * We assume that the null variable will never be defined. 418236769Sobrien * 419236769Sobrien * The detection of substitions of the loop control variable is naive. 420236769Sobrien * Many of the modifiers use \ to escape $ (not $) so it is possible 421236769Sobrien * to contrive a makefile where an unwanted substitution happens. 422236769Sobrien */ 423236769Sobrien 424236769Sobrien cmd_cp = Buf_GetAll(&arg->buf, &len); 425236769Sobrien body_end = cmd_cp + len; 426236769Sobrien Buf_Init(&cmds, len + 256); 427236769Sobrien for (cp = cmd_cp; (cp = strchr(cp, '$')) != NULL;) { 428236769Sobrien char ech; 429236769Sobrien ch = *++cp; 430321964Ssjg if ((ch == '(' && (ech = ')', 1)) || (ch == '{' && (ech = '}', 1))) { 431236769Sobrien cp++; 432236769Sobrien /* Check variable name against the .for loop variables */ 433236769Sobrien STRLIST_FOREACH(var, &arg->vars, i) { 434236769Sobrien len = strlist_info(&arg->vars, i); 435236769Sobrien if (memcmp(cp, var, len) != 0) 436236769Sobrien continue; 437236769Sobrien if (cp[len] != ':' && cp[len] != ech && cp[len] != '\\') 438236769Sobrien continue; 439236769Sobrien /* Found a variable match. Replace with :U<value> */ 440236769Sobrien Buf_AddBytes(&cmds, cp - cmd_cp, cmd_cp); 441236769Sobrien Buf_AddBytes(&cmds, 2, ":U"); 442236769Sobrien cp += len; 443236769Sobrien cmd_cp = cp; 444236769Sobrien for_substitute(&cmds, &arg->items, arg->sub_next + i, ech); 445236769Sobrien break; 446236769Sobrien } 447236769Sobrien continue; 448236769Sobrien } 449236769Sobrien if (ch == 0) 450236769Sobrien break; 451236769Sobrien /* Probably a single character name, ignore $$ and stupid ones. {*/ 452236769Sobrien if (!arg->short_var || strchr("}):$", ch) != NULL) { 453236769Sobrien cp++; 454236769Sobrien continue; 455236769Sobrien } 456236769Sobrien STRLIST_FOREACH(var, &arg->vars, i) { 457236769Sobrien if (var[0] != ch || var[1] != 0) 458236769Sobrien continue; 459236769Sobrien /* Found a variable match. Replace with ${:U<value>} */ 460236769Sobrien Buf_AddBytes(&cmds, cp - cmd_cp, cmd_cp); 461236769Sobrien Buf_AddBytes(&cmds, 3, "{:U"); 462236769Sobrien cmd_cp = ++cp; 463236769Sobrien for_substitute(&cmds, &arg->items, arg->sub_next + i, /*{*/ '}'); 464236769Sobrien Buf_AddBytes(&cmds, 1, "}"); 465236769Sobrien break; 466236769Sobrien } 467236769Sobrien } 468236769Sobrien Buf_AddBytes(&cmds, body_end - cmd_cp, cmd_cp); 469236769Sobrien 470236769Sobrien cp = Buf_Destroy(&cmds, FALSE); 471236769Sobrien if (DEBUG(FOR)) 472236769Sobrien (void)fprintf(debug_file, "For: loop body:\n%s", cp); 473236769Sobrien 474236769Sobrien arg->sub_next += strlist_num(&arg->vars); 475236769Sobrien 476236769Sobrien arg->parse_buf = cp; 477236769Sobrien *ret_len = strlen(cp); 478236769Sobrien return cp; 479236769Sobrien} 480236769Sobrien 481236769Sobrienvoid 482236769SobrienFor_Run(int lineno) 483236769Sobrien{ 484236769Sobrien For *arg; 485236769Sobrien 486236769Sobrien arg = accumFor; 487236769Sobrien accumFor = NULL; 488236769Sobrien 489236769Sobrien if (strlist_num(&arg->items) == 0) { 490236769Sobrien /* Nothing to expand - possibly due to an earlier syntax error. */ 491236769Sobrien For_Free(arg); 492236769Sobrien return; 493236769Sobrien } 494236769Sobrien 495236769Sobrien Parse_SetInput(NULL, lineno, -1, For_Iterate, arg); 496236769Sobrien} 497