changeset 19:f9792a9ec704

macro expansion.
author David A. Holland
date Mon, 20 Dec 2010 03:55:19 -0500
parents c08a947d8f30
children 40748b097655
files macro.c output.h
diffstat 2 files changed, 670 insertions(+), 20 deletions(-) [+]
line wrap: on
line diff
--- a/macro.c	Mon Dec 20 01:51:47 2010 -0500
+++ b/macro.c	Mon Dec 20 03:55:19 2010 -0500
@@ -5,6 +5,17 @@
 #include "mode.h"
 #include "place.h"
 #include "macro.h"
+#include "output.h"
+
+struct expansionitem {
+	bool isstring;
+	union {
+		char *string;
+		unsigned param;
+	};
+};
+DECLARRAY(expansionitem);
+DEFARRAY(expansionitem, );
 
 struct macro {
 	struct place defplace;
@@ -13,7 +24,8 @@
 	char *name;
 	bool hasparams;
 	struct stringarray params;
-	char *expansion;
+	struct expansionitemarray expansion;
+	bool inuse;
 };
 DECLARRAY(macro);
 DEFARRAY(macro, );
@@ -28,9 +40,75 @@
 // macro structure ops
 
 static
+struct expansionitem *
+expansionitem_create_string(const char *string)
+{
+	struct expansionitem *ei;
+
+	ei = domalloc(sizeof(*ei));
+	ei->isstring = true;
+	ei->string = dostrdup(string);
+	return ei;
+}
+
+static
+struct expansionitem *
+expansionitem_create_stringlen(const char *string, size_t len)
+{
+	struct expansionitem *ei;
+
+	ei = domalloc(sizeof(*ei));
+	ei->isstring = true;
+	ei->string = dostrndup(string, len);
+	return ei;
+}
+
+static
+struct expansionitem *
+expansionitem_create_param(unsigned param)
+{
+	struct expansionitem *ei;
+
+	ei = domalloc(sizeof(*ei));
+	ei->isstring = false;
+	ei->param = param;
+	return ei;
+}
+
+static
+void
+expansionitem_destroy(struct expansionitem *ei)
+{
+	if (ei->isstring) {
+		free(ei->string);
+	}
+	free(ei);
+}
+
+static
+bool
+expansionitem_eq(const struct expansionitem *ei1,
+		 const struct expansionitem *ei2)
+{
+	if (ei1->isstring != ei2->isstring) {
+		return false;
+	}
+	if (ei1->isstring) {
+		if (strcmp(ei1->string, ei2->string) != 0) {
+			return false;
+		}
+	} else {
+		if (ei1->param != ei2->param) {
+			return false;
+		}
+	}
+	return true;
+}
+
+static
 struct macro *
 macro_create(struct place *p1, const char *name, unsigned hash,
-	     struct place *p2, const char *expansion)
+	     struct place *p2)
 {
 	struct macro *m;
 
@@ -41,16 +119,20 @@
 	m->name = dostrdup(name);
 	m->hasparams = false;
 	stringarray_init(&m->params);
-	m->expansion = dostrdup(expansion);
+	expansionitemarray_init(&m->expansion);
+	m->inuse = false;
 	return m;
 }
 
+DESTROYALL_ARRAY(expansionitem, );
+
 static
 void
 macro_destroy(struct macro *m)
 {
+	expansionitemarray_destroyall(&m->expansion);
+	expansionitemarray_cleanup(&m->expansion);
 	free(m->name);
-	free(m->expansion);
 	free(m);
 }
 
@@ -59,6 +141,7 @@
 macro_eq(const struct macro *m1, const struct macro *m2)
 {
 	unsigned num1, num2, i;
+	struct expansionitem *ei1, *ei2;
 	const char *p1, *p2;
 
 	if (strcmp(m1->name, m2->name) != 0) {
@@ -69,10 +152,20 @@
 		return false;
 	}
 
-	if (strcmp(m1->expansion, m2->expansion) != 0) {
+	num1 = expansionitemarray_num(&m1->expansion);
+	num2 = expansionitemarray_num(&m2->expansion);
+	if (num1 != num2) {
 		return false;
 	}
 
+	for (i=0; i<num1; i++) {
+		ei1 = expansionitemarray_get(&m1->expansion, i);
+		ei2 = expansionitemarray_get(&m2->expansion, i);
+		if (!expansionitem_eq(ei1, ei2)) {
+			return false;
+		}
+	}
+
 	num1 = stringarray_num(&m1->params);
 	num2 = stringarray_num(&m2->params);
 	if (num1 != num2) {
@@ -99,12 +192,10 @@
  */
 static
 unsigned
-hashfunc(const char *s)
+hashfunc(const char *s, size_t len)
 {
 	uint16_t x1, x2, a;
-	size_t i, len;
-
-	len = strlen(s);
+	size_t i;
 
 	x1 = (uint16_t) (len >> 16);
 	x2 = (uint16_t) (len);
@@ -175,14 +266,15 @@
 
 static
 struct macro *
-macrotable_find(const char *name, bool remove)
+macrotable_findlen(const char *name, size_t len, bool remove)
 {
 	unsigned hash;
 	struct macroarray *bucket;
 	struct macro *m, *m2;
 	unsigned i, num;
+	size_t mlen;
 
-	hash = hashfunc(name);
+	hash = hashfunc(name, len);
 	bucket = macroarrayarray_get(&macros, hash & hashmask);
 	if (bucket == NULL) {
 		return NULL;
@@ -193,7 +285,8 @@
 		if (hash != m->hash) {
 			continue;
 		}
-		if (!strcmp(name, m->name)) {
+		mlen = strlen(m->name);
+		if (len == mlen && !memcmp(name, m->name, len)) {
 			if (remove) {
 				if (i < num-1) {
 					m2 = macroarray_get(bucket, num-1);
@@ -209,6 +302,13 @@
 }
 
 static
+struct macro *
+macrotable_find(const char *name, bool remove)
+{
+	return macrotable_findlen(name, strlen(name), remove);
+}
+
+static
 void
 macrotable_rehash(void)
 {
@@ -264,7 +364,7 @@
 		macrotable_rehash();
 	}
 
-	hash = hashfunc(m->name);
+	hash = hashfunc(m->name, strlen(m->name));
 	bucket = macroarrayarray_get(&macros, hash & hashmask);
 	if (bucket == NULL) {
 		bucket = macroarray_create();
@@ -280,16 +380,18 @@
 static
 struct macro *
 macro_define_common_start(struct place *p1, const char *macro,
-			  struct place *p2, const char *expansion)
+			  struct place *p2)
 {
 	struct macro *m;
+	unsigned hash;
 
 	if (!is_identifier(macro)) {
 		complain(p1, "Invalid macro name %s", macro);
 		complain_fail();
 	}
 
-	m = macro_create(p1, macro, hashfunc(macro), p2, expansion);
+	hash = hashfunc(macro, strlen(macro));
+	m = macro_create(p1, macro, hash, p2);
 	return m;
 }
 
@@ -355,13 +457,72 @@
 	}
 }
 
+static
+bool
+isparam(struct macro *m, const char *name, size_t len, unsigned *num_ret)
+{
+	unsigned num, i;
+	const char *param;
+
+	num = stringarray_num(&m->params);
+	for (i=0; i<num; i++) {
+		param = stringarray_get(&m->params, i);
+		if (strlen(param) == len && !strcmp(name, param)) {
+			*num_ret = i;
+			return true;
+		}
+	}
+	return false;
+}
+
+static
+void
+macro_parse_expansion(struct macro *m, const char *buf)
+{
+	size_t blockstart, wordstart, pos;
+	struct expansionitem *ei;
+	unsigned param;
+
+	pos = blockstart = 0;
+	while (buf[pos] != '\0') {
+		pos += strspn(buf+pos, ws);
+		if (strchr(alnum, buf[pos])) {
+			wordstart = pos;
+			pos += strspn(buf+pos, alnum);
+			if (isparam(m, buf+wordstart, pos-wordstart, &param)) {
+				if (pos > blockstart) {
+					ei = expansionitem_create_stringlen(
+						buf + blockstart,
+						blockstart - pos);
+					expansionitemarray_add(&m->expansion,
+							       ei, NULL);
+				}
+				ei = expansionitem_create_param(param);
+				expansionitemarray_add(&m->expansion, ei,NULL);
+				blockstart = pos;
+				continue;
+			}
+			continue;
+		}
+		pos++;
+	}
+	if (pos > blockstart) {
+		ei = expansionitem_create_stringlen(buf + blockstart,
+						    blockstart - pos);
+		expansionitemarray_add(&m->expansion, ei, NULL);
+	}
+}
+
 void
 macro_define_plain(struct place *p1, const char *macro,
 		   struct place *p2, const char *expansion)
 {
 	struct macro *m;
+	struct expansionitem *ei;
 
-	m = macro_define_common_start(p1, macro, p2, expansion);
+	m = macro_define_common_start(p1, macro, p2);
+	ei = expansionitem_create_string(expansion);
+	expansionitemarray_add(&m->expansion, ei, NULL);
 	macro_define_common_end(m);
 }
 
@@ -372,8 +533,9 @@
 {
 	struct macro *m;
 
-	m = macro_define_common_start(p1, macro, p3, expansion);
+	m = macro_define_common_start(p1, macro, p3);
 	macro_parse_parameters(m, p2, params);
+	macro_parse_expansion(m, expansion);
 	macro_define_common_end(m);
 }
 
@@ -400,10 +562,494 @@
 ////////////////////////////////////////////////////////////
 // macro expansion
 
-char *macroexpand(struct place *, char *buf, size_t len, bool honordefined);
+struct expstate {
+	bool honordefined;
+	enum { ES_NORMAL, ES_WANTLPAREN, ES_NOARG, ES_HAVEARG } state;
+	struct macro *curmacro;
+	struct stringarray args;
+	unsigned argparens;
+
+	bool tobuf;
+	char *buf;
+	size_t bufpos, bufmax;
+};
+
+static struct expstate mainstate;
+
+static void doexpand(struct expstate *es, struct place *p,
+		     char *buf, size_t len);
+
+static
+void
+expstate_init(struct expstate *es, bool tobuf, bool honordefined)
+{
+	es->honordefined = honordefined;
+	es->state = ES_NORMAL;
+	es->curmacro = NULL;
+	stringarray_init(&es->args);
+	es->argparens = 0;
+	es->tobuf = tobuf;
+	es->buf = NULL;
+	es->bufpos = 0;
+	es->bufmax = 0;
+}
+
+static
+void
+expstate_cleanup(struct expstate *es)
+{
+	assert(es->state == ES_NORMAL);
+	stringarray_cleanup(&es->args);
+	if (es->buf) {
+		free(es->buf);
+	}
+}
+
+static
+void
+expstate_destroyargs(struct expstate *es)
+{
+	unsigned i, num;
+
+	num = stringarray_num(&es->args);
+	for (i=0; i<num; i++) {
+		free(stringarray_get(&es->args,  i));
+	}
+	stringarray_setsize(&es->args, 0);
+}
+
+static
+void
+expand_send(struct expstate *es, const char *buf, size_t len)
+{
+	if (es->tobuf) {
+		if (es->bufpos + len > es->bufmax) {
+			if (es->bufmax == 0) {
+				es->bufmax = 64;
+			}
+			while (es->bufpos + len > es->bufmax) {
+				es->bufmax *= 2;
+			}
+			es->buf = dorealloc(es->buf, es->bufmax);
+		}
+		memcpy(es->buf + es->bufpos, buf, len);
+		es->bufpos += len;
+	} else {
+		output(buf, len);
+	}
+}
+
+static
+void
+expand_send_eof(struct expstate *es)
+{
+	if (es->tobuf) {
+		expand_send(es, "", 1);
+		es->bufpos--;
+	} else {
+		output_eof();
+	}
+}
+
+static
+void
+expand_newarg(struct expstate *es, char *buf, size_t len)
+{
+	char *text;
+
+	text = dostrndup(buf, len);
+	stringarray_add(&es->args, text, NULL);
+}
+
+static
+void
+expand_appendarg(struct expstate *es, char *buf, size_t len)
+{
+	unsigned num;
+	char *text;
+	size_t oldlen;
+
+	num = stringarray_num(&es->args);
+	assert(num > 0);
+
+	text = stringarray_get(&es->args, num - 1);
+	oldlen = strlen(text);
+	text = dorealloc(text, oldlen + len + 1);
+	memcpy(text + oldlen, buf, len);
+	text[oldlen+len] = '\0';
+	stringarray_set(&es->args, num - 1, text);
+}
+
+static
+char *
+expand_substitute(struct expstate *es)
+{
+	struct expansionitem *ei;
+	unsigned i, num;
+	size_t len;
+	char *arg;
+	char *ret;
+
+	len = 0;
+	num = expansionitemarray_num(&es->curmacro->expansion);
+	for (i=0; i<num; i++) {
+		ei = expansionitemarray_get(&es->curmacro->expansion, i);
+		if (ei->isstring) {
+			len += strlen(ei->string);
+		} else {
+			arg = stringarray_get(&es->args, ei->param);
+			len += strlen(arg);
+		}
+	}
+
+	ret = domalloc(len+1);
+	*ret = '\0';
+	for (i=0; i<num; i++) {
+		ei = expansionitemarray_get(&es->curmacro->expansion, i);
+		if (ei->isstring) {
+			strcat(ret, ei->string);
+		} else {
+			arg = stringarray_get(&es->args, ei->param);
+			strcat(ret, arg);
+		}
+	}
+
+	return ret;
+}
+
+static
+void
+expand_domacro(struct expstate *es, struct place *p)
+{
+	struct macro *m;
+	char *newbuf, *newbuf2;
+
+	if (es->curmacro == NULL) {
+		/* defined() */
+		if (stringarray_num(&es->args) != 1) {
+			complain(p, "Too many arguments for defined()");
+			complain_fail();
+			expand_send(es, "0", 1);
+			return;
+		}
+		m = macrotable_find(stringarray_get(&es->args, 0), false);
+		expand_send(es, (m != NULL) ? "1" : "0", 1);
+		return;
+	}
+
+	assert(es->curmacro->inuse == false);
+	es->curmacro->inuse = true;
+
+	newbuf = expand_substitute(es);
+	newbuf2 = macroexpand(p, newbuf, strlen(newbuf), false);
+	free(newbuf);
+	doexpand(es, p, newbuf2, strlen(newbuf2));
+	free(newbuf2);
+
+	es->curmacro->inuse = false;
+}
+
+static
+void
+expand_got_ws(struct expstate *es, struct place *p, char *buf, size_t len)
+{
+	switch (es->state) {
+	    case ES_NORMAL:
+		expand_send(es, buf, len);
+		break;
+		break;
+	    case ES_WANTLPAREN:
+		break;
+	    case ES_NOARG:
+		break;
+	    case ES_HAVEARG:
+		expand_appendarg(es, buf, len);
+		break;
+	}
+}
+
+static
+void
+expand_got_word(struct expstate *es, struct place *p, char *buf, size_t len)
+{
+	struct macro *m;
+	struct expansionitem *ei;
+	char *newbuf;
 
-void macro_sendline(struct place *, char *buf, size_t len);
-void macro_sendeof(struct place *);
+	switch (es->state) {
+	    case ES_NORMAL:
+		if (es->honordefined &&
+		    len == 7 && !memcmp(buf, "defined", 7)) {
+			es->curmacro = NULL;
+			es->state = ES_WANTLPAREN;
+			break;
+		}
+		m = macrotable_findlen(buf, len, false);
+		if (m == NULL) {
+			expand_send(es, buf, len);
+		} else if (!m->hasparams) {
+			m->inuse = true;
+			assert(expansionitemarray_num(&m->expansion) == 1);
+			ei = expansionitemarray_get(&m->expansion, 0);
+			assert(ei->isstring);
+			newbuf = macroexpand(p, ei->string,
+					     strlen(ei->string), false);
+			doexpand(es, p, newbuf, strlen(newbuf));
+			free(newbuf);
+			m->inuse = false;
+		} else {
+			es->curmacro = m;
+			es->state = ES_WANTLPAREN;
+		}
+		break;
+	    case ES_WANTLPAREN:
+		if (es->curmacro != NULL) {
+			complain(p, "Expected arguments for macro %s",
+				 es->curmacro->name);
+			complain_fail();
+		} else {
+			/* "defined foo" means "defined(foo)" */
+			expand_newarg(es, buf, len);
+			es->state = ES_NORMAL;
+			expand_domacro(es, p);
+			break;
+		}
+		break;
+	    case ES_NOARG:
+		expand_newarg(es, buf, len);
+		es->state = ES_HAVEARG;
+		break;
+	    case ES_HAVEARG:
+		expand_appendarg(es, buf, len);
+		break;
+	}
+}
+
+static
+void
+expand_got_lparen(struct expstate *es, struct place *p, char *buf, size_t len)
+{
+	switch (es->state) {
+	    case ES_NORMAL:
+		expand_send(es, buf, len);
+		break;
+	    case ES_WANTLPAREN:
+		es->state = ES_NOARG;
+		break;
+	    case ES_NOARG:
+		expand_newarg(es, buf, len);
+		es->state = ES_HAVEARG;
+		es->argparens++;
+		break;
+	    case ES_HAVEARG:
+		expand_appendarg(es, buf, len);
+		es->argparens++;
+		break;
+	}
+}
+
+static
+void
+expand_got_rparen(struct expstate *es, struct place *p, char *buf, size_t len)
+{
+	switch (es->state) {
+	    case ES_NORMAL:
+		expand_send(es, buf, len);
+		break;
+	    case ES_WANTLPAREN:
+		if (es->curmacro) {
+			complain(p, "Expected arguments for macro %s",
+				 es->curmacro->name);
+		} else {
+			complain(p, "Expected arguments for defined()");
+		}
+		complain_fail();
+		break;
+	    case ES_NOARG:
+		assert(es->argparens == 0);
+		es->state = ES_NORMAL;
+		expand_domacro(es, p);
+		break;
+	    case ES_HAVEARG:
+		if (es->argparens > 0) {
+			es->argparens--;
+			expand_appendarg(es, buf, len);
+		} else {
+			es->state = ES_NORMAL;
+			expand_domacro(es, p);
+		}
+		break;
+	}
+}
+
+static
+void
+expand_got_comma(struct expstate *es, struct place *p, char *buf, size_t len)
+{
+	switch (es->state) {
+	    case ES_NORMAL:
+		expand_send(es, buf, len);
+		break;
+	    case ES_WANTLPAREN:
+		if (es->curmacro) {
+			complain(p, "Expected arguments for macro %s",
+				 es->curmacro->name);
+		} else {
+			complain(p, "Expected arguments for defined()");
+		}
+		complain_fail();
+		break;
+	    case ES_NOARG:
+		assert(es->argparens == 0);
+		expand_newarg(es, buf, 0);
+		break;
+	    case ES_HAVEARG:
+		if (es->argparens > 0) {
+			expand_appendarg(es, buf, len);
+		} else {
+			es->state = ES_NOARG;
+		}
+		break;
+	}
+}
+
+static
+void
+expand_got_other(struct expstate *es, struct place *p, char *buf, size_t len)
+{
+	switch (es->state) {
+	    case ES_NORMAL:
+		expand_send(es, buf, len);
+		break;
+	    case ES_WANTLPAREN:
+		if (es->curmacro) {
+			complain(p, "Expected arguments for macro %s",
+				 es->curmacro->name);
+		} else {
+			complain(p, "Expected arguments for defined()");
+		}
+		complain_fail();
+		break;
+	    case ES_NOARG:
+		expand_newarg(es, buf, len);
+		es->state = ES_HAVEARG;
+		break;
+	    case ES_HAVEARG:
+		expand_appendarg(es, buf, len);
+		break;
+	}
+}
+
+static
+void
+expand_got_eof(struct expstate *es, struct place *p)
+{
+	switch (es->state) {
+	    case ES_NORMAL:
+		expand_send_eof(es);
+		break;
+	    case ES_WANTLPAREN:
+		if (es->curmacro) {
+			complain(p, "Expected arguments for macro %s",
+				 es->curmacro->name);
+		} else {
+			complain(p, "Expected arguments for defined()");
+		}
+		complain_fail();
+		break;
+	    case ES_NOARG:
+	    case ES_HAVEARG:
+		if (es->curmacro) {
+			complain(p, "Unclosed argument list for macro %s",
+				 es->curmacro->name);
+		} else {
+			complain(p, "Unclosed argument list for defined()");
+		}
+		complain_fail();
+		expstate_destroyargs(es);
+		break;
+	}
+	es->state = ES_NORMAL;
+	es->curmacro = NULL;
+	es->argparens = 0;
+}
+
+static
+void
+doexpand(struct expstate *es, struct place *p, char *buf, size_t len)
+{
+	size_t x;
+
+	while (len > 0) {
+		x = strspn(buf, ws);
+		if (x > 0) {
+			expand_got_ws(es, p, buf, x);
+			buf += x;
+			len -= x;
+		}
+
+		x = strspn(buf, alnum);
+		if (x > 0) {
+			expand_got_word(es, p, buf, x);
+			buf += x;
+			len -= x;
+			continue;
+		}
+
+		if (buf[0] == '(') {
+			expand_got_lparen(es, p, buf, 1);
+			buf++;
+			len--;
+			continue;
+		}
+
+		if (buf[0] == ')') {
+			expand_got_rparen(es, p, buf, 1);
+			buf++;
+			len--;
+			continue;
+		}
+
+		if (buf[0] == ',') {
+			expand_got_comma(es, p, buf, 1);
+			buf++;
+			len--;
+			continue;
+		}
+
+		expand_got_other(es, p, buf, 1);
+		buf++;
+		len--;
+	}
+}
+
+char *
+macroexpand(struct place *p, char *buf, size_t len, bool honordefined)
+{
+	struct expstate es;
+	char *ret;
+
+	expstate_init(&es, true, honordefined);
+	doexpand(&es, p, buf, len);
+	expand_got_eof(&es, p);
+	ret = es.buf;
+	es.buf = NULL;
+	expstate_cleanup(&es);
+
+	return ret;
+}
+
+void
+macro_sendline(struct place *p, char *buf, size_t len)
+{
+	doexpand(&mainstate, p, buf, len);
+}
+
+void
+macro_sendeof(struct place *p)
+{
+	expand_got_eof(&mainstate, p);
+}
 
 ////////////////////////////////////////////////////////////
 // module initialization
@@ -412,10 +1058,12 @@
 macros_init(void)
 {
 	macrotable_init();
+	expstate_init(&mainstate, false, false);
 }
 
 void
 macros_cleanup(void)
 {
+	expstate_cleanup(&mainstate);
 	macrotable_cleanup();
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/output.h	Mon Dec 20 03:55:19 2010 -0500
@@ -0,0 +1,2 @@
+void output(const char *buf, size_t len);
+void output_eof(void);