File: [local] / src / usr.bin / make / lowparse.c (download)
Revision 1.8, Mon May 14 12:55:48 2001 UTC (23 years ago) by espie
Branch: MAIN
Changes since 1.7: +3 -3 lines
Ensure IFile str is always intialized, so that we can free it without
problems.
Bug found by wilfried@.
|
/* $OpenPackages$ */
/* $OpenBSD: lowparse.c,v 1.8 2001/05/14 12:55:48 espie Exp $ */
/* low-level parsing functions. */
/*
* Copyright (c) 1999,2000 Marc Espie.
*
* Extensive code changes for the OpenBSD project.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE OPENBSD PROJECT AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OPENBSD
* PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdarg.h>
#include <stdio.h>
#include <assert.h>
#include "make.h"
#include "buf.h"
#include "lowparse.h"
#ifdef CLEANUP
static LIST fileNames; /* file names to free at end */
#endif
/* Definitions for handling #include specifications */
typedef struct IFile_ {
char *fname; /* name of file */
unsigned long lineno; /* line number */
FILE *F; /* open stream */
char *str; /* read from char area */
char *ptr; /* where we are */
char *end; /* don't overdo it */
} IFile;
static IFile *current;
static LIST includes; /* stack of IFiles generated by
* #includes */
static IFile *new_ifile(char *, FILE *);
static IFile *new_istring(char *, char *, unsigned long);
static void free_ifile(IFile *);
static void ParseVErrorInternal(char *, unsigned long, int, char *, va_list);
static int newline(void);
static int skiptoendofline(void);
static void ParseFoldLF(Buffer, int);
static int ParseSkipEmptyLines(Buffer);
static int fatals = 0;
/*-
* ParseVErrorInternal --
* Error message abort function for parsing. Prints out the context
* of the error (line number and file) as well as the message with
* two optional arguments.
*
* Side Effects:
* "fatals" is incremented if the level is PARSE_FATAL.
*/
/* VARARGS */
static void
#ifdef __STDC__
ParseVErrorInternal(char *cfname, unsigned long clineno, int type, char *fmt,
va_list ap)
#else
ParseVErrorInternal(va_alist)
va_dcl
#endif
{
(void)fprintf(stderr, "\"%s\", line %lu: ", cfname, clineno);
if (type == PARSE_WARNING)
(void)fprintf(stderr, "warning: ");
(void)vfprintf(stderr, fmt, ap);
va_end(ap);
(void)fprintf(stderr, "\n");
if (type == PARSE_FATAL)
fatals ++;
}
/*-
* Parse_Error --
* External interface to ParseVErrorInternal; uses the default filename
* Line number.
*/
/* VARARGS */
void
#ifdef __STDC__
Parse_Error(int type, char *fmt, ...)
#else
Parse_Error(va_alist)
va_dcl
#endif
{
va_list ap;
#ifdef __STDC__
va_start(ap, fmt);
#else
int type; /* Error type (PARSE_WARNING, PARSE_FATAL) */
char *fmt;
va_start(ap);
type = va_arg(ap, int);
fmt = va_arg(ap, char *);
#endif
ParseVErrorInternal(current->fname, current->lineno, type, fmt, ap);
}
static IFile *
new_ifile(name, stream)
char *name;
FILE *stream;
{
IFile *ifile;
#ifdef CLEANUP
Lst_AtEnd(&fileNames, name);
#endif
ifile = emalloc(sizeof(*ifile));
ifile->fname = name;
ifile->str = NULL;
/* Naturally enough, we start reading at line 0 */
ifile->lineno = 0;
ifile->F = stream;
ifile->ptr = ifile->end = NULL;
return ifile;
}
static void
free_ifile(ifile)
IFile *ifile;
{
if (ifile->F && fileno(ifile->F) != STDIN_FILENO)
(void)fclose(ifile->F);
free(ifile->str);
/* Note we can't free the file names yet, as they are embedded in GN for
* error reports. */
free(ifile);
}
static IFile *
new_istring(str, name, lineno)
char *str;
char *name;
unsigned long lineno;
{
IFile *ifile;
ifile = emalloc(sizeof(*ifile));
/* No malloc, name is always taken from an already existing ifile */
ifile->fname = name;
ifile->F = NULL;
/* Strings are used from for loops... */
ifile->lineno = lineno;
ifile->ptr = ifile->str = str;
ifile->end = str + strlen(str);
return ifile;
}
/*-
*---------------------------------------------------------------------
* Parse_FromString --
* Start Parsing from the given string
*
* Side Effects:
* A structure is added to the includes Lst and readProc, lineno,
* fname and curFILE are altered for the new file
*---------------------------------------------------------------------
*/
void
Parse_FromString(str, lineno)
char *str;
unsigned long lineno;
{
if (DEBUG(FOR))
(void)fprintf(stderr, "%s\n----\n", str);
if (current != NULL)
Lst_AtFront(&includes, current);
current = new_istring(str, current->fname, lineno);
}
void
Parse_FromFile(name, stream)
char *name;
FILE *stream;
{
if (current != NULL)
Lst_AtFront(&includes, current);
current = new_ifile(name, stream);
}
/*-
*---------------------------------------------------------------------
* Parse_NextFile --
* Called when EOF is reached in the current file. If we were reading
* an include file, the includes stack is popped and things set up
* to go back to reading the previous file at the previous location.
*
* Results:
* CONTINUE if there's more to do. DONE if not.
*
* Side Effects:
* The old curFILE, is closed. The includes list is shortened.
* lineno, curFILE, and fname are changed if CONTINUE is returned.
*---------------------------------------------------------------------
*/
Boolean
Parse_NextFile()
{
IFile *next;
next = (IFile *)Lst_DeQueue(&includes);
if (next != NULL) {
if (current != NULL)
free_ifile(current);
current = next;
return TRUE;
} else
return FALSE;
}
/* guts for ParseReadc. Grab a new line off fgetln when we hit "\n" */
static int
newline()
{
size_t len;
if (current->F) {
current->ptr = fgetln(current->F, &len);
if (current->ptr) {
current->end = current->ptr + len;
return *current->ptr++;
} else {
current->end = NULL;
}
}
return EOF;
}
#define ParseReadc() current->ptr < current->end ? *current->ptr++ : newline()
/* Take advantage of fgetln: we don't have to scan a whole line to skip it.
*/
static int
skiptoendofline()
{
if (current->F) {
if (current->end - current->ptr > 1)
current->ptr = current->end - 1;
if (*current->ptr == '\n')
return *current->ptr++;
return EOF;
} else {
int c;
do {
c = ParseReadc();
} while (c != '\n' && c != EOF);
return c;
}
}
/* ParseSkipGetLine():
* Return the first logical line that starts with '.'
*/
char *
ParseSkipGetLine(linebuf)
Buffer linebuf;
{
int c;
/* If first char isn't dot, skip to end of line, handling \ */
while ((c = ParseReadc()) != '.') {
for (;c != '\n'; c = ParseReadc()) {
if (c == '\\') {
c = ParseReadc();
if (c == '\n')
current->lineno++;
}
if (c == EOF) {
Parse_Error(PARSE_FATAL, "Unclosed conditional");
return NULL;
}
}
current->lineno++;
}
/* This is the line we need to copy */
return ParseGetLine(linebuf, "conditional");
}
/* Grab logical line into linebuf.
* The first character is already in c. */
static void
ParseFoldLF(linebuf, c)
Buffer linebuf;
int c;
{
for (;;) {
if (c == '\n') {
current->lineno++;
break;
}
if (c == EOF)
break;
Buf_AddChar(linebuf, c);
c = ParseReadc();
while (c == '\\') {
c = ParseReadc();
if (c == '\n') {
Buf_AddSpace(linebuf);
current->lineno++;
do {
c = ParseReadc();
} while (c == ' ' || c == '\t');
} else {
Buf_AddChar(linebuf, '\\');
if (c == '\\') {
Buf_AddChar(linebuf, '\\');
c = ParseReadc();
}
break;
}
}
}
}
/* ParseGetLine:
* Simply get line, no parsing beyond \
*/
char *
ParseGetLine(linebuf, type)
Buffer linebuf;
const char *type;
{
int c;
Buf_Reset(linebuf);
c = ParseReadc();
if (c == EOF) {
Parse_Error(PARSE_FATAL, "Unclosed %s", type);
return NULL;
}
/* Handle '\' at beginning of line, since \\n needs special treatment */
while (c == '\\') {
c = ParseReadc();
if (c == '\n') {
current->lineno++;
do {
c = ParseReadc();
} while (c == ' ' || c == '\t');
} else {
Buf_AddChar(linebuf, '\\');
if (c == '\\') {
Buf_AddChar(linebuf, '\\');
c = ParseReadc();
}
break;
}
}
ParseFoldLF(linebuf, c);
return Buf_Retrieve(linebuf);
}
/* Skip all empty lines, and return the first `useful' character.
* (This does skip blocks of comments at high speed, which justifies
* the complexity of the function.)
*/
static int
ParseSkipEmptyLines(linebuf)
Buffer linebuf;
{
int c; /* the current character */
for (;;) {
Buf_Reset(linebuf);
c = ParseReadc();
/* Strip leading spaces, fold on '\n' */
if (c == ' ') {
do {
c = ParseReadc();
} while (c == ' ' || c == '\t');
while (c == '\\') {
c = ParseReadc();
if (c == '\n') {
current->lineno++;
do {
c = ParseReadc();
} while (c == ' ' || c == '\t');
} else {
Buf_AddChar(linebuf, '\\');
if (c == '\\') {
Buf_AddChar(linebuf, '\\');
c = ParseReadc();
}
if (c == EOF)
return '\n';
else
return c;
}
}
assert(c != '\t');
}
if (c == '#')
c = skiptoendofline();
/* Almost identical to spaces, except this occurs after comments
* have been taken care of, and we keep the tab itself. */
if (c == '\t') {
Buf_AddChar(linebuf, '\t');
do {
c = ParseReadc();
} while (c == ' ' || c == '\t');
while (c == '\\') {
c = ParseReadc();
if (c == '\n') {
current->lineno++;
do {
c = ParseReadc();
} while (c == ' ' || c == '\t');
} else {
Buf_AddChar(linebuf, '\\');
if (c == '\\') {
Buf_AddChar(linebuf, '\\');
c = ParseReadc();
}
if (c == EOF)
return '\n';
else
return c;
return c;
}
}
}
if (c == '\n')
current->lineno++;
else
return c;
}
}
/*-
*---------------------------------------------------------------------
* ParseReadLine --
* Read an entire line from the input file.
*
* Results:
* A line without a new line, or NULL at EOF.
*
* Notes:
* Removes beginning and trailing blanks (but keeps first tab).
* Updates line numbers, handles escaped newlines, and skip over
* uninteresting lines.
* All but trivial comments can't be handled at this point, because we
* don't know yet which lines are shell commands or not.
*---------------------------------------------------------------------
*/
char *
ParseReadLine(linebuf)
Buffer linebuf;
{
int c; /* the current character */
c = ParseSkipEmptyLines(linebuf);
if (c == EOF)
return NULL;
else {
ParseFoldLF(linebuf, c);
Buf_KillTrailingSpaces(linebuf);
return Buf_Retrieve(linebuf);
}
}
unsigned long
Parse_Getlineno()
{
return current->lineno;
}
const char *
Parse_Getfilename()
{
return current->fname;
}
#ifdef CLEANUP
void
LowParse_Init()
{
Lst_Init(&includes);
current = NULL;
}
void
LowParse_End()
{
Lst_Destroy(&includes, NOFREE); /* Should be empty now */
}
#endif
void
Finish_Errors()
{
if (current != NULL) {
free_ifile(current);
current = NULL;
}
if (fatals) {
fprintf(stderr, "Fatal errors encountered -- cannot continue\n");
exit(1);
}
}