xref: /openbsd/usr.bin/make/lowparse.c (revision db3296cf)
1 /*	$OpenPackages$ */
2 /*	$OpenBSD: lowparse.c,v 1.17 2002/12/29 17:29:35 espie Exp $ */
3 
4 /* low-level parsing functions. */
5 
6 /*
7  * Copyright (c) 1999,2000 Marc Espie.
8  *
9  * Extensive code changes for the OpenBSD project.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE OPENBSD PROJECT AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OPENBSD
24  * PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 #include <assert.h>
34 #include <stddef.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39 #include "config.h"
40 #include "defines.h"
41 #include "buf.h"
42 #include "lowparse.h"
43 #include "error.h"
44 #include "lst.h"
45 #include "memory.h"
46 
47 /* XXX check whether we can free filenames at the end, for a proper
48  * definition of `end'. */
49 
50 #if 0
51 static LIST	    fileNames;	/* file names to free at end */
52 #endif
53 
54 /* Input stream structure, file or string. */
55 typedef struct {
56     const char		*fname; /* Name of file */
57     unsigned long	lineno; /* Line number */
58     FILE		*F;	/* Open stream, or NULL if pure string. */
59     char		*str;	/* Input string, if F == NULL. */
60 
61     /* Line buffer. */
62     char		*ptr;	/* Where we are. */
63     char		*end;	/* Don't overdo it. */
64 } IFile;
65 
66 static IFile	*current;	/* IFile being parsed. */
67 
68 static LIST	input_stack;	/* Stack of IFiles waiting to be parsed
69 				 * (includes and loop reparses) */
70 
71 /* IFile ctors.
72  *
73  * obj = new_ifile(filename, filehandle);
74  *	Create input object from filename, filehandle. */
75 static IFile *new_ifile(const char *, FILE *);
76 /* obj = new_istring(str, filename, lineno);
77  *	Create input object from str, filename, lineno. */
78 static IFile *new_istring(char *, const char *, unsigned long);
79 /* free_ifile(obj);
80  *	Discard consumed input object, closing streams, freeing memory.  */
81 static void free_ifile(IFile *);
82 
83 
84 /* Handling basic character reading.
85  * c = ParseReadc();
86  *	New character c from current input stream, or EOF at end of stream. */
87 #define ParseReadc()	current->ptr < current->end ? *current->ptr++ : newline()
88 /* len = newline();
89  *	Guts for ParseReadc. Grabs a new line off fgetln when we have
90  *	consumed the current line and returns its length. Or EOF at end of
91  *	stream.  */
92 static int newline(void);
93 /* c = skiptoendofline();
94  *	Skips to the end of the current line, returns either '\n' or EOF.  */
95 static int skiptoendofline(void);
96 
97 
98 /* Helper functions to handle basic parsing. */
99 /* ParseFoldLF(buffer, firstchar);
100  *	Grabs logical line into buffer, the first character has already been
101  *	read into firstchar.  */
102 static void ParseFoldLF(Buffer, int);
103 
104 /* firstchar = ParseSkipEmptyLines(buffer);
105  *	Scans lines, skipping empty lines. May put some characters into
106  *	buffer, returns the first character useful to continue parsing
107  *	(e.g., not a backslash or a space. */
108 static int ParseSkipEmptyLines(Buffer);
109 
110 static IFile *
111 new_ifile(name, stream)
112     const char *name;
113     FILE *stream;
114 {
115     IFile *ifile;
116 #if 0
117     Lst_AtEnd(&fileNames, name);
118 #endif
119 
120     ifile = emalloc(sizeof(*ifile));
121     ifile->fname = name;
122     ifile->str = NULL;
123     /* Naturally enough, we start reading at line 0. */
124     ifile->lineno = 0;
125     ifile->F = stream;
126     ifile->ptr = ifile->end = NULL;
127     return ifile;
128 }
129 
130 static void
131 free_ifile(ifile)
132     IFile *ifile;
133 {
134     if (ifile->F && fileno(ifile->F) != STDIN_FILENO)
135 	(void)fclose(ifile->F);
136     free(ifile->str);
137     /* Note we can't free the file names yet, as they are embedded in GN for
138      * error reports. */
139     free(ifile);
140 }
141 
142 static IFile *
143 new_istring(str, name, lineno)
144     char *str;
145     const char *name;
146     unsigned long lineno;
147 {
148     IFile *ifile;
149 
150     ifile = emalloc(sizeof(*ifile));
151     /* No malloc, name is always taken from an already existing ifile */
152     ifile->fname = name;
153     ifile->F = NULL;
154     /* Strings are used in for loops, so we need to reset the line counter
155      * to an appropriate value. */
156     ifile->lineno = lineno;
157     ifile->ptr = ifile->str = str;
158     ifile->end = str + strlen(str);
159     return ifile;
160 }
161 
162 
163 void
164 Parse_FromString(str, lineno)
165     char		*str;
166     unsigned long	lineno;
167 {
168     if (DEBUG(FOR))
169 	(void)fprintf(stderr, "%s\n----\n", str);
170 
171     if (current != NULL)
172 	Lst_Push(&input_stack, current);
173     current = new_istring(str, current->fname, lineno);
174 }
175 
176 
177 void
178 Parse_FromFile(name, stream)
179     const char		*name;
180     FILE		*stream;
181 {
182     if (current != NULL)
183 	Lst_Push(&input_stack, current);
184     current = new_ifile(name, stream);
185 }
186 
187 bool
188 Parse_NextFile()
189 {
190     if (current != NULL)
191 	free_ifile(current);
192     current = (IFile *)Lst_Pop(&input_stack);
193     return current != NULL;
194 }
195 
196 static int
197 newline()
198 {
199     size_t len;
200 
201     if (current->F) {
202 	current->ptr = fgetln(current->F, &len);
203 	if (current->ptr) {
204 	    current->end = current->ptr + len;
205 	    return *current->ptr++;
206 	} else {
207 	    current->end = NULL;
208 	}
209     }
210     return EOF;
211 }
212 
213 static int
214 skiptoendofline()
215 {
216     if (current->F) {
217 	if (current->end - current->ptr > 1)
218 	    current->ptr = current->end - 1;
219 	if (*current->ptr == '\n')
220 	    return *current->ptr++;
221 	return EOF;
222     } else {
223 	int c;
224 
225 	do {
226 	    c = ParseReadc();
227 	} while (c != '\n' && c != EOF);
228 	return c;
229     }
230 }
231 
232 
233 char *
234 Parse_ReadNextConditionalLine(linebuf)
235     Buffer linebuf;
236 {
237     int c;
238 
239 	/* If first char isn't dot, skip to end of line, handling \ */
240     while ((c = ParseReadc()) != '.') {
241 	for (;c != '\n'; c = ParseReadc()) {
242 	    if (c == '\\') {
243 		c = ParseReadc();
244 		if (c == '\n')
245 		    current->lineno++;
246 	    }
247 	    if (c == EOF) {
248 		Parse_Error(PARSE_FATAL, "Unclosed conditional");
249 		return NULL;
250 	    }
251 	}
252 	current->lineno++;
253     }
254 
255 	/* This is the line we need to copy */
256     return Parse_ReadUnparsedLine(linebuf, "conditional");
257 }
258 
259 static void
260 ParseFoldLF(linebuf, c)
261     Buffer linebuf;
262     int c;
263 {
264     for (;;) {
265 	if (c == '\n') {
266 	    current->lineno++;
267 	    break;
268 	}
269 	if (c == EOF)
270 	    break;
271 	Buf_AddChar(linebuf, c);
272 	c = ParseReadc();
273 	while (c == '\\') {
274 	    c = ParseReadc();
275 	    if (c == '\n') {
276 		Buf_AddSpace(linebuf);
277 		current->lineno++;
278 		do {
279 		    c = ParseReadc();
280 		} while (c == ' ' || c == '\t');
281 	    } else {
282 		Buf_AddChar(linebuf, '\\');
283 		if (c == '\\') {
284 		    Buf_AddChar(linebuf, '\\');
285 		    c = ParseReadc();
286 		}
287 		break;
288 	    }
289 	}
290     }
291 }
292 
293 char *
294 Parse_ReadUnparsedLine(linebuf, type)
295     Buffer	linebuf;
296     const char	*type;
297 {
298     int c;
299 
300     Buf_Reset(linebuf);
301     c = ParseReadc();
302     if (c == EOF) {
303 	Parse_Error(PARSE_FATAL, "Unclosed %s", type);
304 	return NULL;
305     }
306 
307     /* Handle '\' at beginning of line, since \\n needs special treatment */
308     while (c == '\\') {
309 	c = ParseReadc();
310 	if (c == '\n') {
311 	    current->lineno++;
312 	    do {
313 		c = ParseReadc();
314 	    } while (c == ' ' || c == '\t');
315 	} else {
316 	    Buf_AddChar(linebuf, '\\');
317 	    if (c == '\\') {
318 		Buf_AddChar(linebuf, '\\');
319 		c = ParseReadc();
320 	    }
321 	    break;
322 	}
323     }
324     ParseFoldLF(linebuf, c);
325 
326     return Buf_Retrieve(linebuf);
327 }
328 
329 /* This is a fairly complex function, but without it, we could not skip
330  * blocks of comments without reading them. */
331 static int
332 ParseSkipEmptyLines(linebuf)
333     Buffer	linebuf;
334 {
335     int 	c;		/* the current character */
336 
337     for (;;) {
338 	Buf_Reset(linebuf);
339 	c = ParseReadc();
340 	/* Strip leading spaces, fold on '\n' */
341 	if (c == ' ') {
342 	    do {
343 		c = ParseReadc();
344 	    } while (c == ' ' || c == '\t');
345 	    while (c == '\\') {
346 		c = ParseReadc();
347 		if (c == '\n') {
348 		    current->lineno++;
349 		    do {
350 			c = ParseReadc();
351 		    } while (c == ' ' || c == '\t');
352 		} else {
353 		    Buf_AddChar(linebuf, '\\');
354 		    if (c == '\\') {
355 			Buf_AddChar(linebuf, '\\');
356 			c = ParseReadc();
357 		    }
358 		    if (c == EOF)
359 			return '\n';
360 		    else
361 			return c;
362 		}
363 	    }
364 	    assert(c != '\t');
365 	}
366 	if (c == '#')
367 	    c = skiptoendofline();
368 	/* Almost identical to spaces, except this occurs after comments
369 	 * have been taken care of, and we keep the tab itself.  */
370 	if (c == '\t') {
371 	    Buf_AddChar(linebuf, '\t');
372 	    do {
373 		c = ParseReadc();
374 	    } while (c == ' ' || c == '\t');
375 	    while (c == '\\') {
376 		c = ParseReadc();
377 		if (c == '\n') {
378 		    current->lineno++;
379 		    do {
380 			c = ParseReadc();
381 		    } while (c == ' ' || c == '\t');
382 		} else {
383 		    Buf_AddChar(linebuf, '\\');
384 		    if (c == '\\') {
385 			Buf_AddChar(linebuf, '\\');
386 			c = ParseReadc();
387 		    }
388 		    if (c == EOF)
389 			return '\n';
390 		    else
391 			return c;
392 		    return c;
393 		}
394 	    }
395 	}
396 	if (c == '\n')
397 	    current->lineno++;
398 	else
399 	    return c;
400     }
401 }
402 
403 /* Parse_ReadNormalLine removes beginning and trailing blanks (but keeps
404  * the first tab), handles escaped newlines, and skip over uninteresting
405  * lines.
406  *
407  * The line number is advanced, which implies that continuation
408  * lines are numbered with the last line no (we could do better, at a
409  * price).
410  *
411  * Trivial comments are also removed, but we can't do more, as
412  * we don't know which lines are shell commands or not.  */
413 char *
414 Parse_ReadNormalLine(linebuf)
415     Buffer	linebuf;
416 {
417     int 	c;		/* the current character */
418 
419     c = ParseSkipEmptyLines(linebuf);
420 
421     if (c == EOF)
422 	return NULL;
423     else {
424 	ParseFoldLF(linebuf, c);
425 	Buf_KillTrailingSpaces(linebuf);
426 	return Buf_Retrieve(linebuf);
427     }
428 }
429 
430 unsigned long
431 Parse_Getlineno()
432 {
433     return current ? current->lineno : 0;
434 }
435 
436 const char *
437 Parse_Getfilename()
438 {
439     return current ? current->fname : NULL;
440 }
441 
442 #ifdef CLEANUP
443 void
444 LowParse_Init()
445 {
446     Static_Lst_Init(&input_stack);
447     current = NULL;
448 }
449 
450 void
451 LowParse_End()
452 {
453     Lst_Destroy(&input_stack, NOFREE);	/* Should be empty now */
454 #if 0
455     Lst_Destroy(&fileNames, (SimpleProc)free);
456 #endif
457 }
458 #endif
459 
460 
461 void
462 Parse_ReportErrors()
463 {
464     if (fatal_errors) {
465 #ifdef CLEANUP
466 	while (Parse_NextFile())
467 		;
468 #endif
469 	fprintf(stderr, "Fatal errors encountered -- cannot continue\n");
470 	exit(1);
471     } else
472     	assert(current == NULL);
473 }
474