Annotation of src/usr.bin/make/lowparse.c, Revision 1.2
1.2 ! espie 1: /* $OpenBSD: lowparse.c,v 1.1 2000/06/23 16:39:45 espie Exp $ */
1.1 espie 2:
3: /* low-level parsing functions. */
4:
5: /*
6: * Copyright (c) 1999,2000 Marc Espie.
7: *
8: * Extensive code changes for the OpenBSD project.
9: *
10: * Redistribution and use in source and binary forms, with or without
11: * modification, are permitted provided that the following conditions
12: * are met:
13: * 1. Redistributions of source code must retain the above copyright
14: * notice, this list of conditions and the following disclaimer.
15: * 2. Redistributions in binary form must reproduce the above copyright
16: * notice, this list of conditions and the following disclaimer in the
17: * documentation and/or other materials provided with the distribution.
18: *
19: * THIS SOFTWARE IS PROVIDED BY THE OPENBSD PROJECT AND CONTRIBUTORS
20: * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OPENBSD
23: * PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30: */
31:
32: #include <stdarg.h>
33: #include <stdio.h>
34: #include <assert.h>
35: #include "make.h"
36: #include "buf.h"
37: #include "lowparse.h"
38:
39: #ifdef CLEANUP
40: static LIST fileNames; /* file names to free at end */
41: #endif
42:
1.2 ! espie 43: /* Definitions for handling #include specifications */
! 44: typedef struct IFile_ {
! 45: char *fname; /* name of file */
! 46: unsigned long lineno; /* line number */
! 47: FILE *F; /* open stream */
! 48: char *str; /* read from char area */
! 49: char *ptr; /* where we are */
! 50: char *end; /* don't overdo it */
! 51: } IFile;
! 52:
! 53: static IFile *current;
! 54:
! 55:
! 56:
1.1 espie 57: static LIST includes; /* stack of IFiles generated by
58: * #includes */
59:
60: static IFile *new_ifile __P((char *, FILE *));
61: static IFile *new_istring __P((char *, char *, unsigned long));
62: static void free_ifile __P((IFile *));
63: static void ParseVErrorInternal __P((char *, unsigned long, int, char *, va_list));
64: static int skiptoendofline __P((void));
1.2 ! espie 65: static int newline __P((void));
! 66: #define ParseReadc() current->ptr < current->end ? *current->ptr++ : newline()
! 67: static void ParseUnreadc __P((char));
1.1 espie 68: static int ParseSkipEmptyLines __P((Buffer));
69: static int fatals = 0;
70:
71: /*-
72: * ParseVErrorInternal --
73: * Error message abort function for parsing. Prints out the context
74: * of the error (line number and file) as well as the message with
75: * two optional arguments.
76: *
77: * Side Effects:
78: * "fatals" is incremented if the level is PARSE_FATAL.
79: */
80: /* VARARGS */
81: static void
82: #ifdef __STDC__
83: ParseVErrorInternal(char *cfname, unsigned long clineno, int type, char *fmt,
84: va_list ap)
85: #else
86: ParseVErrorInternal(va_alist)
87: va_dcl
88: #endif
89: {
90: (void)fprintf(stderr, "\"%s\", line %lu: ", cfname, clineno);
91: if (type == PARSE_WARNING)
92: (void)fprintf(stderr, "warning: ");
93: (void)vfprintf(stderr, fmt, ap);
94: va_end(ap);
95: (void)fprintf(stderr, "\n");
96: if (type == PARSE_FATAL)
97: fatals ++;
98: }
99:
100: /*-
101: * Parse_Error --
102: * External interface to ParseVErrorInternal; uses the default filename
103: * Line number.
104: */
105: /* VARARGS */
106: void
107: #ifdef __STDC__
108: Parse_Error(int type, char *fmt, ...)
109: #else
110: Parse_Error(va_alist)
111: va_dcl
112: #endif
113: {
114: va_list ap;
115: #ifdef __STDC__
116: va_start(ap, fmt);
117: #else
118: int type; /* Error type (PARSE_WARNING, PARSE_FATAL) */
119: char *fmt;
120:
121: va_start(ap);
122: type = va_arg(ap, int);
123: fmt = va_arg(ap, char *);
124: #endif
125:
126: ParseVErrorInternal(current->fname, current->lineno, type, fmt, ap);
127: }
128:
129: static IFile *
130: new_ifile(name, stream)
131: char *name;
132: FILE *stream;
133: {
134: IFile *ifile;
135: #ifdef CLEANUP
136: Lst_AtEnd(&fileNames, name);
137: #endif
138:
139: ifile = emalloc(sizeof(*ifile));
140: ifile->fname = name;
141: /* Naturally enough, we start reading at line 0 */
142: ifile->lineno = 0;
143: ifile->F = stream;
144: ifile->ptr = ifile->end = NULL;
145: return ifile;
146: }
147:
148: static void
149: free_ifile(ifile)
150: IFile *ifile;
151: {
152: if (ifile->F)
153: (void)fclose(ifile->F);
154: else
155: free(ifile->str);
156: /* Note we can't free the file names yet, as they are embedded in GN for
157: * error reports. */
158: free(ifile);
159: }
160:
161: static IFile *
162: new_istring(str, name, lineno)
163: char *str;
164: char *name;
165: unsigned long lineno;
166: {
167: IFile *ifile;
168:
169: ifile = emalloc(sizeof(*ifile));
170: ifile->fname = name;
171: ifile->F = NULL;
172: /* Strings are used from for loops... */
173: ifile->lineno = lineno;
174: ifile->ptr = ifile->str = str;
175: ifile->end = str + strlen(str);
176: return ifile;
177: }
178:
179:
180: /*-
181: *---------------------------------------------------------------------
182: * Parse_FromString --
183: * Start Parsing from the given string
184: *
185: * Side Effects:
186: * A structure is added to the includes Lst and readProc, lineno,
187: * fname and curFILE are altered for the new file
188: *---------------------------------------------------------------------
189: */
190: void
191: Parse_FromString(str, lineno)
192: char *str;
193: unsigned long lineno;
194: {
195: if (DEBUG(FOR))
196: (void)fprintf(stderr, "%s\n----\n", str);
197:
198: if (current != NULL)
199: Lst_AtFront(&includes, current);
200: current = new_istring(str, current->fname, lineno);
201: }
202:
203:
204: void
205: Parse_FromFile(name, stream)
206: char *name;
207: FILE *stream;
208: {
209: if (current != NULL)
210: Lst_AtFront(&includes, current);
211: current = new_ifile(name, stream);
212: }
213:
214: /*-
215: *---------------------------------------------------------------------
216: * Parse_NextFile --
217: * Called when EOF is reached in the current file. If we were reading
218: * an include file, the includes stack is popped and things set up
219: * to go back to reading the previous file at the previous location.
220: *
221: * Results:
222: * CONTINUE if there's more to do. DONE if not.
223: *
224: * Side Effects:
225: * The old curFILE, is closed. The includes list is shortened.
226: * lineno, curFILE, and fname are changed if CONTINUE is returned.
227: *---------------------------------------------------------------------
228: */
229: Boolean
230: Parse_NextFile()
231: {
232: IFile *next;
233:
234: next = (IFile *)Lst_DeQueue(&includes);
235: if (next != NULL) {
236: if (current != NULL)
237: free_ifile(current);
238: current = next;
239: return TRUE;
240: } else
241: return FALSE;
242: }
243:
244:
245: int
246: newline()
247: {
248: size_t len;
249:
250: if (current->F) {
251: current->ptr = fgetln(current->F, &len);
252: if (current->ptr) {
253: current->end = current->ptr + len;
254: return *current->ptr++;
255: } else {
256: current->end = NULL;
257: }
258: }
259: return EOF;
260: }
261:
262: void
263: ParseUnreadc(c)
264: char c;
265: {
266: current->ptr--;
267: *current->ptr = c;
1.2 ! espie 268: }
! 269:
! 270: /* ParseSkipLine():
! 271: * Grab the next line
! 272: */
! 273: char *
! 274: ParseSkipLine(skip)
! 275: int skip; /* Skip lines that don't start with . */
! 276: {
! 277: char *line;
! 278: int c, lastc;
! 279: BUFFER buf;
! 280:
! 281: Buf_Init(&buf, MAKE_BSIZE);
! 282:
! 283: for (;;) {
! 284: Buf_Reset(&buf);
! 285: lastc = '\0';
! 286:
! 287: while (((c = ParseReadc()) != '\n' || lastc == '\\')
! 288: && c != EOF) {
! 289: if (c == '\n') {
! 290: Buf_ReplaceLastChar(&buf, ' ');
! 291: current->lineno++;
! 292:
! 293: while ((c = ParseReadc()) == ' ' || c == '\t');
! 294:
! 295: if (c == EOF)
! 296: break;
! 297: }
! 298:
! 299: Buf_AddChar(&buf, c);
! 300: lastc = c;
! 301: }
! 302:
! 303: line = Buf_Retrieve(&buf);
! 304: current->lineno++;
! 305: /* allow for non-newline terminated lines while skipping */
! 306: if (line[0] == '.')
! 307: break;
! 308:
! 309: if (c == EOF) {
! 310: Parse_Error(PARSE_FATAL, "Unclosed conditional/for loop");
! 311: Buf_Destroy(&buf);
! 312: return NULL;
! 313: }
! 314: if (skip == 0)
! 315: break;
! 316:
! 317: }
! 318:
! 319: return line;
! 320: }
! 321:
! 322:
! 323: /*-
! 324: *---------------------------------------------------------------------
! 325: * ParseReadLine --
! 326: * Read an entire line from the input file. Called only by Parse_File.
! 327: * To facilitate escaped newlines and what have you, a character is
! 328: * buffered in 'lastc', which is '\0' when no characters have been
! 329: * read. When we break out of the loop, c holds the terminating
! 330: * character and lastc holds a character that should be added to
! 331: * the line (unless we don't read anything but a terminator).
! 332: *
! 333: * Results:
! 334: * A line w/o its newline
! 335: *
! 336: * Side Effects:
! 337: * Only those associated with reading a character
! 338: *---------------------------------------------------------------------
! 339: */
! 340: char *
! 341: ParseReadLine ()
! 342: {
! 343: BUFFER buf; /* Buffer for current line */
! 344: register int c; /* the current character */
! 345: register int lastc; /* The most-recent character */
! 346: Boolean semiNL; /* treat semi-colons as newlines */
! 347: Boolean ignDepOp; /* TRUE if should ignore dependency operators
! 348: * for the purposes of setting semiNL */
! 349: Boolean ignComment; /* TRUE if should ignore comments (in a
! 350: * shell command */
! 351: char *line; /* Result */
! 352: char *ep; /* to strip trailing blanks */
! 353:
! 354: semiNL = FALSE;
! 355: ignDepOp = FALSE;
! 356: ignComment = FALSE;
! 357:
! 358: /*
! 359: * Handle special-characters at the beginning of the line. Either a
! 360: * leading tab (shell command) or pound-sign (possible conditional)
! 361: * forces us to ignore comments and dependency operators and treat
! 362: * semi-colons as semi-colons (by leaving semiNL FALSE). This also
! 363: * discards completely blank lines.
! 364: */
! 365: for (;;) {
! 366: c = ParseReadc();
! 367:
! 368: if (c == '\t') {
! 369: ignComment = ignDepOp = TRUE;
! 370: break;
! 371: } else if (c == '\n') {
! 372: current->lineno++;
! 373: } else if (c == '#') {
! 374: ParseUnreadc(c);
! 375: break;
! 376: } else {
! 377: /*
! 378: * Anything else breaks out without doing anything
! 379: */
! 380: break;
! 381: }
! 382: }
! 383:
! 384: if (c != EOF) {
! 385: lastc = c;
! 386: Buf_Init(&buf, MAKE_BSIZE);
! 387:
! 388: while (((c = ParseReadc ()) != '\n' || (lastc == '\\')) &&
! 389: (c != EOF))
! 390: {
! 391: test_char:
! 392: switch(c) {
! 393: case '\n':
! 394: /*
! 395: * Escaped newline: read characters until a non-space or an
! 396: * unescaped newline and replace them all by a single space.
! 397: * This is done by storing the space over the backslash and
! 398: * dropping through with the next nonspace. If it is a
! 399: * semi-colon and semiNL is TRUE, it will be recognized as a
! 400: * newline in the code below this...
! 401: */
! 402: current->lineno++;
! 403: lastc = ' ';
! 404: while ((c = ParseReadc ()) == ' ' || c == '\t') {
! 405: continue;
! 406: }
! 407: if (c == EOF || c == '\n') {
! 408: goto line_read;
! 409: } else {
! 410: /*
! 411: * Check for comments, semiNL's, etc. -- easier than
! 412: * ParseUnreadc(c); continue;
! 413: */
! 414: goto test_char;
! 415: }
! 416: /*NOTREACHED*/
! 417: break;
! 418:
! 419: case ';':
! 420: /*
! 421: * Semi-colon: Need to see if it should be interpreted as a
! 422: * newline
! 423: */
! 424: if (semiNL) {
! 425: /*
! 426: * To make sure the command that may be following this
! 427: * semi-colon begins with a tab, we push one back into the
! 428: * input stream. This will overwrite the semi-colon in the
! 429: * buffer. If there is no command following, this does no
! 430: * harm, since the newline remains in the buffer and the
! 431: * whole line is ignored.
! 432: */
! 433: ParseUnreadc('\t');
! 434: goto line_read;
! 435: }
! 436: break;
! 437: case '=':
! 438: if (!semiNL) {
! 439: /*
! 440: * Haven't seen a dependency operator before this, so this
! 441: * must be a variable assignment -- don't pay attention to
! 442: * dependency operators after this.
! 443: */
! 444: ignDepOp = TRUE;
! 445: } else if (lastc == ':' || lastc == '!') {
! 446: /*
! 447: * Well, we've seen a dependency operator already, but it
! 448: * was the previous character, so this is really just an
! 449: * expanded variable assignment. Revert semi-colons to
! 450: * being just semi-colons again and ignore any more
! 451: * dependency operators.
! 452: *
! 453: * XXX: Note that a line like "foo : a:=b" will blow up,
! 454: * but who'd write a line like that anyway?
! 455: */
! 456: ignDepOp = TRUE; semiNL = FALSE;
! 457: }
! 458: break;
! 459: case '#':
! 460: if (!ignComment) {
! 461: if (
! 462: #if 0
! 463: compatMake &&
! 464: #endif
! 465: (lastc != '\\')) {
! 466: /*
! 467: * If the character is a hash mark and it isn't escaped
! 468: * (or we're being compatible), the thing is a comment.
! 469: * Skip to the end of the line.
! 470: */
! 471: do {
! 472: c = ParseReadc();
! 473: } while ((c != '\n') && (c != EOF));
! 474: goto line_read;
! 475: } else {
! 476: /*
! 477: * Don't add the backslash. Just let the # get copied
! 478: * over.
! 479: */
! 480: lastc = c;
! 481: continue;
! 482: }
! 483: }
! 484: break;
! 485: case ':':
! 486: case '!':
! 487: if (!ignDepOp && (c == ':' || c == '!')) {
! 488: /*
! 489: * A semi-colon is recognized as a newline only on
! 490: * dependency lines. Dependency lines are lines with a
! 491: * colon or an exclamation point. Ergo...
! 492: */
! 493: semiNL = TRUE;
! 494: }
! 495: break;
! 496: }
! 497: /*
! 498: * Copy in the previous character and save this one in lastc.
! 499: */
! 500: Buf_AddChar(&buf, lastc);
! 501: lastc = c;
! 502:
! 503: }
! 504: line_read:
! 505: current->lineno++;
! 506:
! 507: if (lastc != '\0')
! 508: Buf_AddChar(&buf, lastc);
! 509: line = Buf_Retrieve(&buf);
! 510:
! 511: /*
! 512: * Strip trailing blanks and tabs from the line.
! 513: * Do not strip a blank or tab that is preceeded by
! 514: * a '\'
! 515: */
! 516: ep = line;
! 517: while (*ep)
! 518: ++ep;
! 519: while (ep > line + 1 && (ep[-1] == ' ' || ep[-1] == '\t')) {
! 520: if (ep > line + 1 && ep[-2] == '\\')
! 521: break;
! 522: --ep;
! 523: }
! 524: *ep = 0;
! 525:
! 526: if (line[0] == '.') {
! 527: /*
! 528: * The line might be a conditional. Ask the conditional module
! 529: * about it and act accordingly
! 530: */
! 531: switch (Cond_Eval (line)) {
! 532: case COND_SKIP:
! 533: /*
! 534: * Skip to next conditional that evaluates to COND_PARSE.
! 535: */
! 536: do {
! 537: free (line);
! 538: line = ParseSkipLine(1);
! 539: } while (line && Cond_Eval(line) != COND_PARSE);
! 540: if (line == NULL)
! 541: break;
! 542: /*FALLTHRU*/
! 543: case COND_PARSE:
! 544: free(line);
! 545: line = ParseReadLine();
! 546: break;
! 547: case COND_INVALID:
! 548: {
! 549: For *loop;
! 550:
! 551: loop = For_Eval(line);
! 552: if (loop != NULL) {
! 553: Boolean ok;
! 554:
! 555: free(line);
! 556: do {
! 557: /* Find the matching endfor. */
! 558: line = ParseSkipLine(0);
! 559: if (line == NULL) {
! 560: Parse_Error(PARSE_FATAL,
! 561: "Unexpected end of file in for loop.\n");
! 562: return line;
! 563: }
! 564: ok = For_Accumulate(loop, line);
! 565: free(line);
! 566: } while (ok);
! 567: For_Run(loop);
! 568: line = ParseReadLine();
! 569: }
! 570: break;
! 571: }
! 572: }
! 573: }
! 574: return (line);
! 575:
! 576: } else {
! 577: /*
! 578: * Hit end-of-file, so return a NULL line to indicate this.
! 579: */
! 580: return((char *)NULL);
! 581: }
1.1 espie 582: }
583:
584: unsigned long
585: Parse_Getlineno()
586: {
587: return current->lineno;
588: }
589:
590: const char *
591: Parse_Getfilename()
592: {
593: return current->fname;
594: }
595:
596: #ifdef CLEANUP
597: void
598: LowParse_Init()
599: {
600: Lst_Init(&includes);
601: current = NULL;
602: }
603:
604: void
605: LowParse_End()
606: {
607: Lst_Destroy(&includes, NOFREE); /* Should be empty now */
608: }
609: #endif
610:
611:
612: void
613: Finish_Errors()
614: {
615: if (current != NULL) {
616: free_ifile(current);
617: current = NULL;
618: }
619: if (fatals) {
620: fprintf(stderr, "Fatal errors encountered -- cannot continue\n");
621: exit(1);
622: }
623: }