1 /*	$OpenPackages$ */
2 /*	$OpenBSD: lowparse.c,v 1.19 2007/05/17 03:37:31 ray 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 *
new_ifile(const char * name,FILE * stream)111 new_ifile(const char *name, FILE *stream)
112 {
113     IFile *ifile;
114 #if 0
115     Lst_AtEnd(&fileNames, name);
116 #endif
117 
118     ifile = emalloc(sizeof(*ifile));
119     ifile->fname = name;
120     ifile->str = NULL;
121     /* Naturally enough, we start reading at line 0. */
122     ifile->lineno = 0;
123     ifile->F = stream;
124     ifile->ptr = ifile->end = NULL;
125     return ifile;
126 }
127 
128 static void
free_ifile(IFile * ifile)129 free_ifile(IFile *ifile)
130 {
131     if (ifile->F && fileno(ifile->F) != STDIN_FILENO)
132 	(void)fclose(ifile->F);
133     free(ifile->str);
134     /* Note we can't free the file names yet, as they are embedded in GN for
135      * error reports. */
136     free(ifile);
137 }
138 
139 static IFile *
new_istring(char * str,const char * name,unsigned long lineno)140 new_istring(char *str, const char *name, unsigned long lineno)
141 {
142     IFile *ifile;
143 
144     ifile = emalloc(sizeof(*ifile));
145     /* No malloc, name is always taken from an already existing ifile */
146     ifile->fname = name;
147     ifile->F = NULL;
148     /* Strings are used in for loops, so we need to reset the line counter
149      * to an appropriate value. */
150     ifile->lineno = lineno;
151     ifile->ptr = ifile->str = str;
152     ifile->end = str + strlen(str);
153     return ifile;
154 }
155 
156 
157 void
Parse_FromString(char * str,unsigned long lineno)158 Parse_FromString(char *str, unsigned long lineno)
159 {
160     if (DEBUG(FOR))
161 	(void)fprintf(stderr, "%s\n----\n", str);
162 
163     if (current != NULL)
164 	Lst_Push(&input_stack, current);
165     current = new_istring(str, current->fname, lineno);
166 }
167 
168 
169 void
Parse_FromFile(const char * name,FILE * stream)170 Parse_FromFile(const char *name, FILE *stream)
171 {
172     if (current != NULL)
173 	Lst_Push(&input_stack, current);
174     current = new_ifile(name, stream);
175 }
176 
177 bool
Parse_NextFile(void)178 Parse_NextFile(void)
179 {
180     if (current != NULL)
181 	free_ifile(current);
182     current = (IFile *)Lst_Pop(&input_stack);
183     return current != NULL;
184 }
185 
186 static int
newline(void)187 newline(void)
188 {
189     size_t len;
190 
191     if (current->F) {
192 	current->ptr = fgetln(current->F, &len);
193 	if (current->ptr) {
194 	    current->end = current->ptr + len;
195 	    return *current->ptr++;
196 	} else {
197 	    current->end = NULL;
198 	}
199     }
200     return EOF;
201 }
202 
203 static int
skiptoendofline(void)204 skiptoendofline(void)
205 {
206     if (current->F) {
207 	if (current->end - current->ptr > 1)
208 	    current->ptr = current->end - 1;
209 	if (*current->ptr == '\n')
210 	    return *current->ptr++;
211 	return EOF;
212     } else {
213 	int c;
214 
215 	do {
216 	    c = ParseReadc();
217 	} while (c != '\n' && c != EOF);
218 	return c;
219     }
220 }
221 
222 
223 char *
Parse_ReadNextConditionalLine(Buffer linebuf)224 Parse_ReadNextConditionalLine(Buffer linebuf)
225 {
226     int c;
227 
228 	/* If first char isn't dot, skip to end of line, handling \ */
229     while ((c = ParseReadc()) != '.') {
230 	for (;c != '\n'; c = ParseReadc()) {
231 	    if (c == '\\') {
232 		c = ParseReadc();
233 		if (c == '\n')
234 		    current->lineno++;
235 	    }
236 	    if (c == EOF) {
237 		Parse_Error(PARSE_FATAL, "Unclosed conditional");
238 		return NULL;
239 	    }
240 	}
241 	current->lineno++;
242     }
243 
244 	/* This is the line we need to copy */
245     return Parse_ReadUnparsedLine(linebuf, "conditional");
246 }
247 
248 static void
ParseFoldLF(Buffer linebuf,int c)249 ParseFoldLF(Buffer linebuf, int c)
250 {
251     for (;;) {
252 	if (c == '\n') {
253 	    current->lineno++;
254 	    break;
255 	}
256 	if (c == EOF)
257 	    break;
258 	Buf_AddChar(linebuf, c);
259 	c = ParseReadc();
260 	while (c == '\\') {
261 	    c = ParseReadc();
262 	    if (c == '\n') {
263 		Buf_AddSpace(linebuf);
264 		current->lineno++;
265 		do {
266 		    c = ParseReadc();
267 		} while (c == ' ' || c == '\t');
268 	    } else {
269 		Buf_AddChar(linebuf, '\\');
270 		if (c == '\\') {
271 		    Buf_AddChar(linebuf, '\\');
272 		    c = ParseReadc();
273 		}
274 		break;
275 	    }
276 	}
277     }
278 }
279 
280 char *
Parse_ReadUnparsedLine(Buffer linebuf,const char * type)281 Parse_ReadUnparsedLine(Buffer linebuf, const char *type)
282 {
283     int c;
284 
285     Buf_Reset(linebuf);
286     c = ParseReadc();
287     if (c == EOF) {
288 	Parse_Error(PARSE_FATAL, "Unclosed %s", type);
289 	return NULL;
290     }
291 
292     /* Handle '\' at beginning of line, since \\n needs special treatment */
293     while (c == '\\') {
294 	c = ParseReadc();
295 	if (c == '\n') {
296 	    current->lineno++;
297 	    do {
298 		c = ParseReadc();
299 	    } while (c == ' ' || c == '\t');
300 	} else {
301 	    Buf_AddChar(linebuf, '\\');
302 	    if (c == '\\') {
303 		Buf_AddChar(linebuf, '\\');
304 		c = ParseReadc();
305 	    }
306 	    break;
307 	}
308     }
309     ParseFoldLF(linebuf, c);
310 
311     return Buf_Retrieve(linebuf);
312 }
313 
314 /* This is a fairly complex function, but without it, we could not skip
315  * blocks of comments without reading them. */
316 static int
ParseSkipEmptyLines(Buffer linebuf)317 ParseSkipEmptyLines(Buffer linebuf)
318 {
319     int 	c;		/* the current character */
320 
321     for (;;) {
322 	Buf_Reset(linebuf);
323 	c = ParseReadc();
324 	/* Strip leading spaces, fold on '\n' */
325 	if (c == ' ') {
326 	    do {
327 		c = ParseReadc();
328 	    } while (c == ' ' || c == '\t');
329 	    while (c == '\\') {
330 		c = ParseReadc();
331 		if (c == '\n') {
332 		    current->lineno++;
333 		    do {
334 			c = ParseReadc();
335 		    } while (c == ' ' || c == '\t');
336 		} else {
337 		    Buf_AddChar(linebuf, '\\');
338 		    if (c == '\\') {
339 			Buf_AddChar(linebuf, '\\');
340 			c = ParseReadc();
341 		    }
342 		    if (c == EOF)
343 			return '\n';
344 		    else
345 			return c;
346 		}
347 	    }
348 	    assert(c != '\t');
349 	}
350 	if (c == '#')
351 	    c = skiptoendofline();
352 	/* Almost identical to spaces, except this occurs after comments
353 	 * have been taken care of, and we keep the tab itself.  */
354 	if (c == '\t') {
355 	    Buf_AddChar(linebuf, '\t');
356 	    do {
357 		c = ParseReadc();
358 	    } while (c == ' ' || c == '\t');
359 	    while (c == '\\') {
360 		c = ParseReadc();
361 		if (c == '\n') {
362 		    current->lineno++;
363 		    do {
364 			c = ParseReadc();
365 		    } while (c == ' ' || c == '\t');
366 		} else {
367 		    Buf_AddChar(linebuf, '\\');
368 		    if (c == '\\') {
369 			Buf_AddChar(linebuf, '\\');
370 			c = ParseReadc();
371 		    }
372 		    if (c == EOF)
373 			return '\n';
374 		    else
375 			return c;
376 		}
377 	    }
378 	}
379 	if (c == '\n')
380 	    current->lineno++;
381 	else
382 	    return c;
383     }
384 }
385 
386 /* Parse_ReadNormalLine removes beginning and trailing blanks (but keeps
387  * the first tab), handles escaped newlines, and skips over uninteresting
388  * lines.
389  *
390  * The line number is incremented, which implies that continuation
391  * lines are numbered with the last line number (we could do better, at a
392  * price).
393  *
394  * Trivial comments are also removed, but we can't do more, as
395  * we don't know which lines are shell commands or not.  */
396 char *
Parse_ReadNormalLine(Buffer linebuf)397 Parse_ReadNormalLine(Buffer linebuf)
398 {
399     int 	c;		/* the current character */
400 
401     c = ParseSkipEmptyLines(linebuf);
402 
403     if (c == EOF)
404 	return NULL;
405     else {
406 	ParseFoldLF(linebuf, c);
407 	Buf_KillTrailingSpaces(linebuf);
408 	return Buf_Retrieve(linebuf);
409     }
410 }
411 
412 unsigned long
Parse_Getlineno(void)413 Parse_Getlineno(void)
414 {
415     return current ? current->lineno : 0;
416 }
417 
418 const char *
Parse_Getfilename(void)419 Parse_Getfilename(void)
420 {
421     return current ? current->fname : NULL;
422 }
423 
424 #ifdef CLEANUP
425 void
LowParse_Init(void)426 LowParse_Init(void)
427 {
428     Static_Lst_Init(&input_stack);
429     current = NULL;
430 }
431 
432 void
LowParse_End(void)433 LowParse_End(void)
434 {
435     Lst_Destroy(&input_stack, NOFREE);	/* Should be empty now */
436 #if 0
437     Lst_Destroy(&fileNames, (SimpleProc)free);
438 #endif
439 }
440 #endif
441 
442 
443 void
Parse_ReportErrors(void)444 Parse_ReportErrors(void)
445 {
446     if (fatal_errors) {
447 #ifdef CLEANUP
448 	while (Parse_NextFile())
449 		;
450 #endif
451 	fprintf(stderr, "Fatal errors encountered -- cannot continue\n");
452 	exit(1);
453     } else
454     	assert(current == NULL);
455 }
456