view directive.c @ 66:f8507e5ed84c

Recognize directive lines only when the # is exactly in column 0. This is the traditional behavior and Joerg's convinced me it's important to retain it.
author David A. Holland
date Sun, 31 Mar 2013 10:47:08 -0400
parents f50b4ea6cbfe
children b1d0f10e8d36
line wrap: on
line source

/*-
 * Copyright (c) 2010 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by David A. Holland.
 *
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

#include "utils.h"
#include "mode.h"
#include "place.h"
#include "files.h"
#include "directive.h"
#include "macro.h"
#include "eval.h"
#include "output.h"

struct ifstate {
	struct ifstate *prev;
	struct place startplace;
	bool curtrue;
	bool evertrue;
	bool seenelse;
};

static struct ifstate *ifstate;
static bool in_multiline_comment;

////////////////////////////////////////////////////////////
// common parsing bits

static
void
uncomment(char *buf)
{
	char *s, *t, *u = NULL;
	bool incomment = false;

	for (s = t = buf; *s; s++) {
		if (incomment) {
			if (s[0] == '*' && s[1] == '/') {
				s++;
				incomment = false;
			}
		} else {
			if (s[0] == '/' && s[1] == '*') {
				incomment = true;
			} else {
				if (t != s) {
					*t = *s;
				}
				if (!strchr(ws, *t)) {
					u = t;
				}
				t++;
			}
		}
	}
	if (u) {
		/* end string after last non-whitespace char */
		u[1] = '\0';
	} else {
		*t = '\0';
	}
}

static
void
oneword(const char *what, struct place *p2, char *line)
{
	size_t pos;

	pos = strcspn(line, ws);
	if (line[pos] != '\0') {
		p2->column += pos;
		complain(p2, "Garbage after %s argument", what);
		complain_fail();
		line[pos] = '\0';
	}
}

////////////////////////////////////////////////////////////
// if handling

static
struct ifstate *
ifstate_create(struct ifstate *prev, struct place *p, bool startstate)
{
	struct ifstate *is;

	is = domalloc(sizeof(*is));
	is->prev = prev;
	if (p != NULL) {
		is->startplace = *p;
	} else {
		place_setbuiltin(&is->startplace, 1);
	}
	is->curtrue = startstate;
	is->evertrue = is->curtrue;
	is->seenelse = false;
	return is;
}

static
void
ifstate_destroy(struct ifstate *is)
{
	dofree(is, sizeof(*is));
}

static
void
ifstate_push(struct place *p, bool startstate)
{
	ifstate = ifstate_create(ifstate, p, startstate);
}

static
void
ifstate_pop(void)
{
	struct ifstate *is;

	is = ifstate;
	ifstate = ifstate->prev;
	ifstate_destroy(is);
}

static
void
d_if(struct place *p, struct place *p2, char *line)
{
	char *expr;
	bool val;
	struct place p3 = *p2;

	uncomment(line);
	expr = macroexpand(p2, line, strlen(line), true);
	val = eval(&p3, expr);
	ifstate_push(p, val);
	dostrfree(expr);
}

static
void
d_ifdef(struct place *p, struct place *p2, char *line)
{
	uncomment(line);
	oneword("#ifdef", p2, line);
	ifstate_push(p, macro_isdefined(line));
}

static
void
d_ifndef(struct place *p, struct place *p2, char *line)
{
	uncomment(line);
	oneword("#ifndef", p2, line);
	ifstate_push(p, !macro_isdefined(line));
}

static
void
d_elif(struct place *p, struct place *p2, char *line)
{
	char *expr;
	struct place p3 = *p2;

	if (ifstate->seenelse) {
		complain(p, "#elif after #else");
		complain_fail();
	}

	if (ifstate->evertrue) {
		ifstate->curtrue = false;
	} else {
		uncomment(line);
		expr = macroexpand(p2, line, strlen(line), true);
		ifstate->curtrue = eval(&p3, expr);
		ifstate->evertrue = ifstate->curtrue;
		dostrfree(expr);
	}
}

static
void
d_else(struct place *p, struct place *p2, char *line)
{
	if (ifstate->seenelse) {
		complain(p, "Multiple #else directives in one conditional");
		complain_fail();
	}

	ifstate->curtrue = !ifstate->evertrue;
	ifstate->evertrue = true;
	ifstate->seenelse = true;
}

static
void
d_endif(struct place *p, struct place *p2, char *line)
{
	if (ifstate->prev == NULL) {
		complain(p, "Unmatched #endif");
		complain_fail();
	} else {
		ifstate_pop();
	}
}

////////////////////////////////////////////////////////////
// macros

static
void
d_define(struct place *p, struct place *p2, char *line)
{
	size_t pos, argpos;
	struct place p3, p4;

	/*
	 * line may be:
	 *    macro expansion
	 *    macro(arg, arg, ...) expansion
	 */

	pos = strcspn(line, " \t\f\v(");
	if (line[pos] == '(') {
		line[pos++] = '\0';
		argpos = pos;
		pos = pos + strcspn(line+pos, "()");
		if (line[pos] == '(') {
			p2->column += pos;
			complain(p2, "Left parenthesis in macro parameters");
			complain_fail();
			return;
		}
		if (line[pos] != ')') {
			p2->column += pos;
			complain(p2, "Unclosed macro parameter list");
			complain_fail();
			return;
		}
		line[pos++] = '\0';
#if 0
		if (!strchr(ws, line[pos])) {
			p2->column += pos;
			complain(p2, "Trash after macro parameter list");
			complain_fail();
			return;
		}
#endif
	} else if (line[pos] == '\0') {
		argpos = 0;
	} else {
		line[pos++] = '\0';
		argpos = 0;
	}

	pos += strspn(line+pos, ws);

	p3 = *p2;
	p3.column += argpos;

	p4 = *p2;
	p4.column += pos;

	if (argpos) {
		macro_define_params(p2, line, &p3,
				    line + argpos, &p4,
				    line + pos);
	} else {
		macro_define_plain(p2, line, &p4, line + pos);
	}
}

static
void
d_undef(struct place *p, struct place *p2, char *line)
{
	uncomment(line);
	oneword("#undef", p2, line);
	macro_undef(line);
}

////////////////////////////////////////////////////////////
// includes

static
bool
tryinclude(struct place *p, char *line)
{
	size_t len;

	len = strlen(line);
	if (len > 2 && line[0] == '"' && line[len-1] == '"') {
		line[len-1] = '\0';
		file_readquote(p, line+1);
		line[len-1] = '"';
		return true;
	}
	if (len > 2 && line[0] == '<' && line[len-1] == '>') {
		line[len-1] = '\0';
		file_readbracket(p, line+1);
		line[len-1] = '>';
		return true;
	}
	return false;
}

static
void
d_include(struct place *p, struct place *p2, char *line)
{
	char *text;

	uncomment(line);
	if (tryinclude(p, line)) {
		return;
	}
	text = macroexpand(p2, line, strlen(line), false);
	if (tryinclude(p, text)) {
		dostrfree(text);
		return;
	}
	dostrfree(text);
	complain(p, "Illegal #include directive");
	complain_fail();
}

static
void
d_line(struct place *p, struct place *p2, char *line)
{
	/* XXX */
	complain(p, "Sorry, no #line yet");
}

////////////////////////////////////////////////////////////
// messages

static
void
d_warning(struct place *p, struct place *p2, char *line)
{
	char *msg;

	msg = macroexpand(p2, line, strlen(line), false);
	complain(p, "#warning: %s", msg);
	if (mode.werror) {
		complain_fail();
	}
	dostrfree(msg);
}

static
void
d_error(struct place *p, struct place *p2, char *line)
{
	char *msg;

	msg = macroexpand(p2, line, strlen(line), false);
	complain(p, "#error: %s", msg);
	complain_fail();
	dostrfree(msg);
}

////////////////////////////////////////////////////////////
// other

static
void
d_pragma(struct place *p, struct place *p2, char *line)
{
	complain(p, "#pragma %s", line);
	complain_fail();
}

////////////////////////////////////////////////////////////
// directive table

static const struct {
	const char *name;
	bool ifskip;
	void (*func)(struct place *, struct place *, char *line);
} directives[] = {
	{ "define",  true,  d_define },
	{ "elif",    false, d_elif },
	{ "else",    false, d_else },
	{ "endif",   false, d_endif },
	{ "error",   true,  d_error },
	{ "if",      false, d_if },
	{ "ifdef",   false, d_ifdef },
	{ "ifndef",  false, d_ifndef },
	{ "include", true,  d_include },
	{ "line",    true,  d_line },
	{ "pragma",  true,  d_pragma },
	{ "undef",   true,  d_undef },
	{ "warning", true,  d_warning },
};
static const unsigned numdirectives = HOWMANY(directives);

static
void
directive_gotdirective(struct place *p, char *line)
{
	struct place p2;
	size_t len, skip;
	unsigned i;

	p2 = *p;
	for (i=0; i<numdirectives; i++) {
		len = strlen(directives[i].name);
		if (!strncmp(line, directives[i].name, len) &&
		    strchr(ws, line[len])) {
			if (directives[i].ifskip && !ifstate->curtrue) {
				return;
			}
			skip = len + strspn(line+len, ws);
			p2.column += skip;
			line += skip;

			len = strlen(line);
			len = notrailingws(line, len);
			if (len < strlen(line)) {
				line[len] = '\0';
			}
			directives[i].func(p, &p2, line);
			return;
		}
	}
	skip = strcspn(line, ws);
	complain(p, "Unknown directive #%.*s", (int)skip, line);
	complain_fail();
}

/*
 * If desired, warn about a nested comment. The comment begins at
 * offset POS from the place P.
 */
static
void
warn_nestcomment(const struct place *p, size_t pos)
{
	struct place p2;

	if (warns.nestcomment) {
		p2 = *p;
		p2.column += pos;
		complain(p, "Warning: %c%c within comment",
			 '/', '*');
		if (mode.werror) {
			complain_failed();
		}
	}
}

/*
 * Check for comment delimiters in LINE. If a multi-line comment is
 * continuing or ending, set ACOMM to its length. If a multi-line
 * comment is starting, set BCOMM to its length. Set TEXT to the
 * length of text that is not commented out, or that contains comments
 * that both begin and end on this line. ACOMM + TEXT + BCOMM == LEN.
 *
 * Updates in_multiline_comment to the appropriate state for after
 * this line is handled.
 */
static
size_t
directive_scancomments(const struct place *p, char *line, size_t len,
		       size_t *acomm, size_t *text, size_t *bcomm)
{
	size_t pos;
	size_t first_commentend;
	size_t last_commentstart;
	bool incomment;

	first_commentend = len;
	last_commentstart = len;
	incomment = in_multiline_comment;
	for (pos = 0; pos+1 < len; pos++) {
		if (line[pos] == '/' && line[pos+1] == '*') {
			if (incomment) {
				warn_nestcomment(p, pos);
			} else {
				incomment = true;
				last_commentstart = pos;
			}
		} else if (line[pos] == '*' && line[pos+1] == '/') {
			if (incomment) {
				incomment = false;
				if (first_commentend == len) {
					first_commentend = pos;
				}
				last_commentstart = len;
			} else {
				/* stray end-comment; should we care? */
			}
		}
	}

	if (in_multiline_comment && first_commentend < last_commentstart) {
		/* multiline comment ends */
		/* first_commentend points to the star, adjust */
		*acomm = first_commentend + 2;
		*text = len - *acomm;
	} else if (in_multiline_comment) {
		/* comment did not end, so another one cannot have started */
		assert(last_commentstart == len);
		*acomm = len;
		*text = 0;
	} else {
		*acomm = 0;
		*text = len;
	}

	*bcomm = len - last_commentstart;
	*text -= *bcomm;

	in_multiline_comment = incomment;
	return len;
}

void
directive_gotline(struct place *p, char *line, size_t len)
{
	size_t acomm;	/* length of comment ending on this line */
	size_t text;	/* length of non-multi-line-comment text */
	size_t bcomm;	/* length of comment beginning on this line */
	size_t skip;

	directive_scancomments(p, line, len, &acomm, &text, &bcomm);

	if (acomm > 0) {
		if (mode.output_retain_comments && ifstate->curtrue) {
			/*
			 * Do not expand the comment; send it straight
			 * to the output. This will cause it to appear
			 * first if we're partway through collecting a
			 * macro argument. Too bad. This isn't a
			 * standard mode anyway.
			 */
			output(p, line, acomm);
		}
		p->column += acomm;
	}

	/* check if we have a directive line (# exactly in column 0) */
	if (acomm == 0 && line[0] == '#') {
		char ch;

		skip = 1 + strspn(line + 1, ws);
		assert(skip <= text);
		p->column += skip;
		assert(line[len] == '\0');
		/* ensure null termination for directives */
		ch = line[text];
		if (ch != '\0') {
			line[text] = '\0';
		}
		directive_gotdirective(p, line+skip /*, length = text-skip */);
		line[text] = ch;
		p->column += text-skip;
	} else if (ifstate->curtrue) {
		macro_sendline(p, line + acomm, text);
		p->column += text;
	}

	if (bcomm > 0) {
		if (mode.output_retain_comments && ifstate->curtrue) {
			output(p, line + acomm + text, bcomm);
		}
		p->column += bcomm;
	}
}

void
directive_goteof(struct place *p)
{
	while (ifstate->prev != NULL) {
		complain(p, "Missing #endif");
		complain(&ifstate->startplace, "...opened at this point");
		complain_failed();
		ifstate_pop();
	}
	macro_sendeof(p);
}

////////////////////////////////////////////////////////////
// module initialization

void
directive_init(void)
{
	ifstate = ifstate_create(NULL, NULL, true);
}

void
directive_cleanup(void)
{
	assert(ifstate->prev == NULL);
	ifstate_destroy(ifstate);
	ifstate = NULL;
}