view directive.c @ 38:b156910b59b2

Wrap free() in dofree() to allow instrumenting it for debugging.
author David A. Holland
date Sat, 30 Mar 2013 21:02:25 -0400
parents a489cc223483
children 337110e7240a
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"

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

static struct ifstate *ifstate;

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

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);
}

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, size_t len)
{
	char *expr;
	bool val;
	struct place p3 = *p2;

	expr = macroexpand(p2, line, len, true);
	val = eval(&p3, expr);
	ifstate_push(p, val);
	dofree(expr);
}

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

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

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

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

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

static
void
d_else(struct place *p, struct place *p2, char *line, size_t len)
{
	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, size_t len)
{
	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 len)
{
	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, size_t len)
{
	oneword("#undef", p2, line);
	macro_undef(line);
}

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

static
bool
tryinclude(struct place *p, char *line, size_t len)
{
	if (len > 2 && line[0] == '"' && line[len-1] == '"') {
		line[len-1] = '\0';
		file_readquote(p, line+1);
		return true;
	}
	if (len > 2 && line[0] == '<' && line[len-1] == '>') {
		line[len-1] = '\0';
		file_readbracket(p, line+1);
		return true;
	}
	return false;
}

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

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

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

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

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

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

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

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

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

static
void
d_pragma(struct place *p, struct place *p2, char *line, size_t len)
{
	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, size_t len);
} 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, size_t linelen)
{
	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;
			linelen -= skip;
			linelen = notrailingws(line, linelen);
			directives[i].func(p, &p2, line, linelen);
			return;
		}
	}
	skip = strcspn(line, ws);
	complain(p, "Unknown directive #%.*s", (int)skip, line);
	complain_fail();
}

void
directive_gotline(struct place *p, char *line, size_t len)
{
	size_t skip;

	/* check if we have a directive line */
	skip = strspn(line, ws);
	if (line[skip] == '#') {
		skip = skip + 1 + strspn(line + skip + 1, ws);
		p->column += skip;
		directive_gotdirective(p, line+skip, len-skip);
	} else if (ifstate->curtrue) {
		macro_sendline(p, line, len);
	}
}


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;
}