view directive.c @ 16:9dda765ee85c

expression evaluator
author David A. Holland
date Mon, 20 Dec 2010 00:32:20 -0500
parents f6177d3ed5c2
children c08a947d8f30
line wrap: on
line source

#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)
{
	free(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);
	free(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;
		free(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;
	struct place p3;

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

	pos = strcspn(line, " \t\f\v(");
	if (line[pos] == '(') {
		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;
		}
		pos++;
		if (!strchr(ws, line[pos])) {
			p2->column += pos;
			complain(p2, "Trash after macro parameter list");
			complain_fail();
			return;
		}
		line[pos++] = '\0';
	} else if (line[pos] == '\0') {
		/* nothing */
	} else {
		line[pos++] = '\0';
	}

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

	p3 = *p2;
	p3.column += pos;
	macro_define(p2, line, &p3, 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))) {
		free(text);
		return;
	}
	free(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();
	}
	free(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();
	free(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
size_t
notrailingws(char *buf, size_t len)
{
	while (len > 0 && strchr(ws, buf[len-1])) {
		buf[--len] = '\0';
	}
	return len;
}

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