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