Annotation of src/usr.bin/make/lowparse.c, Revision 1.5
1.5 ! espie 1: /* $OpenBSD: lowparse.c,v 1.4 2000/09/14 13:32:07 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));
1.5 ! espie 168: /* No malloc, name is always taken from an existing ifile */
1.1 espie 169: ifile->fname = name;
170: ifile->F = NULL;
171: /* Strings are used from for loops... */
172: ifile->lineno = lineno;
173: ifile->ptr = ifile->str = str;
174: ifile->end = str + strlen(str);
175: return ifile;
176: }
177:
178:
179: /*-
180: *---------------------------------------------------------------------
181: * Parse_FromString --
182: * Start Parsing from the given string
183: *
184: * Side Effects:
185: * A structure is added to the includes Lst and readProc, lineno,
186: * fname and curFILE are altered for the new file
187: *---------------------------------------------------------------------
188: */
189: void
190: Parse_FromString(str, lineno)
191: char *str;
192: unsigned long lineno;
193: {
194: if (DEBUG(FOR))
195: (void)fprintf(stderr, "%s\n----\n", str);
196:
197: if (current != NULL)
198: Lst_AtFront(&includes, current);
199: current = new_istring(str, current->fname, lineno);
200: }
201:
202:
203: void
204: Parse_FromFile(name, stream)
205: char *name;
206: FILE *stream;
207: {
208: if (current != NULL)
209: Lst_AtFront(&includes, current);
210: current = new_ifile(name, stream);
211: }
212:
213: /*-
214: *---------------------------------------------------------------------
215: * Parse_NextFile --
216: * Called when EOF is reached in the current file. If we were reading
217: * an include file, the includes stack is popped and things set up
218: * to go back to reading the previous file at the previous location.
219: *
220: * Results:
221: * CONTINUE if there's more to do. DONE if not.
222: *
223: * Side Effects:
224: * The old curFILE, is closed. The includes list is shortened.
225: * lineno, curFILE, and fname are changed if CONTINUE is returned.
226: *---------------------------------------------------------------------
227: */
228: Boolean
229: Parse_NextFile()
230: {
231: IFile *next;
232:
233: next = (IFile *)Lst_DeQueue(&includes);
234: if (next != NULL) {
235: if (current != NULL)
236: free_ifile(current);
237: current = next;
238: return TRUE;
239: } else
240: return FALSE;
241: }
242:
243:
1.5 ! espie 244: /* guts for ParseReadc. Grab a new line off fgetln when we hit "\n" */
! 245: static int
1.1 espie 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;
1.3 espie 542: /* FALLTHROUGH */
1.2 espie 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: }