changeset 296:83fcc1ed6ad6 ccdev

Checkpoint lwcc development Initial untested version of the preprocessor with macro expansion but without file inclusion.
author William Astle <lost@l-w.ca>
date Sat, 14 Sep 2013 20:04:38 -0600
parents 4b17780f2777
children 310df72c641d
files Makefile lwcc/cpp.c lwcc/cpp.h lwcc/lex.c lwcc/preproc.c lwcc/symbol.c lwcc/symbol.h lwcc/token.h
diffstat 8 files changed, 1192 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Thu Sep 12 22:06:26 2013 -0600
+++ b/Makefile	Sat Sep 14 20:04:38 2013 -0600
@@ -101,7 +101,7 @@
 lwcc_driver_objs := $(lwcc_driver_srcs:.c=.o)
 lwcc_driver_deps := $(lwcc_driver_srcs:.c=.d)
 
-lwcc_cpp_srcs := cpp-main.c cpp.c lex.c strbuf.c token.c
+lwcc_cpp_srcs := cpp-main.c cpp.c lex.c strbuf.c token.c preproc.c symbol.c
 lwcc_cpp_srcs := $(addprefix lwcc/,$(lwcc_cpp_srcs))
 lwcc_cpp_objs := $(lwcc_cpp_srcs:.c=.o)
 lwcc_cpp_deps := $(lwcc_cpp_srcs:.c=.d)
--- a/lwcc/cpp.c	Thu Sep 12 22:06:26 2013 -0600
+++ b/lwcc/cpp.c	Sat Sep 14 20:04:38 2013 -0600
@@ -52,6 +52,7 @@
 	pp -> fn = lw_strdup(fn);
 	pp -> fp = fp;
 	pp -> ra = CPP_NOUNG;
+	pp -> ppeolseen = 1;
 	return pp;
 }
 
@@ -59,6 +60,23 @@
 {
 	struct token *t;
 	
+	if (pp -> curtok)
+		token_free(pp -> curtok);
+
+	/*
+	If there is a list of tokens to process, move it to the "unget" queue
+	with an EOF marker at the end of it.
+	*/	
+	if (pp -> sourcelist)
+	{
+		for (t = pp -> sourcelist; t -> next; t = t -> next)
+			/* do nothing */ ;
+		t -> next = token_create(TOK_EOF, NULL, -1, -1, "");
+		t -> next -> next = pp -> tokqueue;
+		pp -> tokqueue = pp -> sourcelist;
+		pp -> sourcelist = NULL;
+	}
+again:
 	if (pp -> tokqueue)
 	{
 		t = pp -> tokqueue;
@@ -67,15 +85,42 @@
 			pp -> tokqueue -> prev = NULL;
 		t -> next = NULL;
 		t -> prev = NULL;
-		return t;
+		pp -> curtok = t;
+		goto ret;
 	}
-	return(preproc_lex_next_token(pp));
+	pp -> curtok = preproc_lex_next_token(pp);
+	t = pp -> curtok;
+ret:
+	if (t -> ttype == TOK_ENDEXPAND)
+	{
+		struct expand_e *e;
+		e = pp -> expand_list;
+		pp -> expand_list = e -> next;
+		lw_free(e);
+		goto again;
+	}
+	return t;
+}
+
+void preproc_unget_token(struct preproc_info *pp, struct token *t)
+{
+	t -> next = pp -> tokqueue;
+	pp -> tokqueue = t;
+	if (pp -> curtok == t)
+		pp -> curtok = NULL;
 }
 
 void preproc_finish(struct preproc_info *pp)
 {
 	lw_free((void *)(pp -> fn));
 	fclose(pp -> fp);
+	if (pp -> curtok)
+		token_free(pp -> curtok);
+	while (pp -> tokqueue)
+	{
+		preproc_next_token(pp);
+		token_free(pp -> curtok);
+	}
 	lw_free(pp);
 }
 
--- a/lwcc/cpp.h	Thu Sep 12 22:06:26 2013 -0600
+++ b/lwcc/cpp.h	Sat Sep 14 20:04:38 2013 -0600
@@ -24,23 +24,29 @@
 
 #include <stdio.h>
 
+//#include "symbol.h"
 #include "token.h"
 
 #define TOKBUFSIZE 32
 
+struct expand_e
+{
+	struct expand_e *next;
+	struct symtab_e *s;		// symbol table entry of the expanding symbol
+};
+
 struct preproc_info
 {
 	const char *fn;
 	FILE *fp;
-	struct token *tokbuf[TOKBUFSIZE];
 	struct token *tokqueue;
-	int tokbuf_ptr;
+	struct token *curtok;
 	void (*errorcb)(const char *);
 	void (*warningcb)(const char *);
-	int eolstate;
-	int lineno;
-	int column;
-	int trigraphs;
+	int eolstate;			// internal for use in handling newlines
+	int lineno;				// the current input line number
+	int column;				// the current input column
+	int trigraphs;			// nonzero if we're going to handle trigraphs
 	int ra;
 	int qseen;
 	int ungetbufl;
@@ -49,13 +55,25 @@
 	int unget;
 	int eolseen;
 	int nlseen;
+	int ppeolseen;			// nonzero if we've seen only whitespace (or nothing) since a newline
+	int skip_level;			// nonzero if we're in a false conditional
+	int found_level;		// nonzero if we're in a true conditional
+	int else_level;			// for counting #else directives
+	int else_skip_level;	// ditto
+	struct symtab_e *sh;	// the preprocessor's symbol table
+	struct token *sourcelist;	// for expanding a list of tokens
+	struct expand_e *expand_list;	// record of which macros are currently being expanded
+	
 };
 
 extern struct preproc_info *preproc_init(const char *);
 extern struct token *preproc_next_token(struct preproc_info *);
+extern struct token *preproc_next_processed_token(struct preproc_info *);
 extern void preproc_finish(struct preproc_info *);
 extern void preproc_register_error_callback(struct preproc_info *, void (*)(const char *));
 extern void preproc_register_warning_callback(struct preproc_info *, void (*)(const char *));
 extern void preproc_throw_error(struct preproc_info *, const char *, ...);
 extern void preproc_throw_warning(struct preproc_info *, const char *, ...);
+extern void preproc_unget_token(struct preproc_info *, struct token *);
+
 #endif // cpp_h_seen___
--- a/lwcc/lex.c	Thu Sep 12 22:06:26 2013 -0600
+++ b/lwcc/lex.c	Sat Sep 14 20:04:38 2013 -0600
@@ -701,6 +701,7 @@
 	case '5': case '6': case '7': case '8': case '9':
 		strbuf = strbuf_new();
 numlit:
+		ttype = TOK_NUMBER;
 		strbuf_add(strbuf, c);
 		for (;;)
 		{
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lwcc/preproc.c	Sat Sep 14 20:04:38 2013 -0600
@@ -0,0 +1,983 @@
+/*
+lwcc/preproc.c
+
+Copyright © 2013 William Astle
+
+This file is part of LWTOOLS.
+
+LWTOOLS is free software: you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <string.h>
+
+#include <lw_alloc.h>
+#include <lw_string.h>
+
+#include "cpp.h"
+#include "strbuf.h"
+#include "symbol.h"
+#include "token.h"
+
+static int expand_macro(struct preproc_info *, char *);
+static void process_directive(struct preproc_info *);
+static long eval_expr(struct preproc_info *);
+
+struct token *preproc_next_processed_token(struct preproc_info *pp)
+{
+	struct token *ct;
+	
+again:
+	ct = preproc_next_token(pp);
+	if (ct -> ttype == TOK_EOF)
+		return ct;
+	if (ct -> ttype == TOK_EOL)
+		pp -> ppeolseen = 1;
+		
+	if (ct -> ttype == TOK_HASH && pp -> eolseen == 1)
+	{
+		// preprocessor directive 
+		process_directive(pp);
+	}
+	// if we're in a false section, don't return the token; keep scanning
+	if (pp -> skip_level)
+		goto again;
+
+	if (ct -> ttype != TOK_WSPACE)
+		pp -> ppeolseen = 0;
+	
+	if (ct -> ttype == TOK_IDENT)
+	{
+		// possible macro expansion
+		if (expand_macro(pp, ct -> strval))
+	 		goto again;
+	}
+	
+	return ct;
+}
+
+static struct token *preproc_next_processed_token_nws(struct preproc_info *pp)
+{
+	struct token *t;
+	
+	do
+	{
+		t = preproc_next_processed_token(pp);
+	} while (t -> ttype == TOK_WSPACE);
+	return t;
+}
+
+static struct token *preproc_next_token_nws(struct preproc_info *pp)
+{
+	struct token *t;
+	
+	do
+	{
+		t = preproc_next_token(pp);
+	} while (t -> ttype == TOK_WSPACE);
+	return t;
+}
+
+static void skip_eol(struct preproc_info *pp)
+{
+	struct token *t;
+	
+	if (pp -> curtok && pp -> curtok -> ttype == TOK_EOL)
+		return;
+	do
+	{
+		t = preproc_next_token(pp);
+	} while (t -> ttype != TOK_EOL);
+}
+
+static void check_eol(struct preproc_info *pp)
+{
+	struct token *t;
+
+	t = preproc_next_token(pp);
+	if (t -> ttype != TOK_EOL)
+		preproc_throw_warning(pp, "Extra text after preprocessor directive");
+	skip_eol(pp);
+}
+
+static void dir_ifdef(struct preproc_info *pp)
+{
+	struct token *ct;
+	
+	if (pp -> skip_level)
+	{
+		pp -> skip_level++;
+		skip_eol(pp);
+		return;
+	}
+	
+	do
+	{
+		ct = preproc_next_token(pp);
+	} while (ct -> ttype == TOK_WSPACE);
+	
+	if (ct -> ttype != TOK_IDENT)
+	{
+		preproc_throw_error(pp, "Bad #ifdef");
+		skip_eol(pp);
+	}
+	
+	if (symtab_find(pp, ct -> strval) == NULL)
+	{
+		pp -> skip_level++;
+	}
+	else
+	{
+		pp -> found_level++;
+	}
+	check_eol(pp);
+}
+
+static void dir_ifndef(struct preproc_info *pp)
+{
+	struct token *ct;
+	
+	if (pp -> skip_level)
+	{
+		pp -> skip_level++;
+		skip_eol(pp);
+		return;
+	}
+	
+	do
+	{
+		ct = preproc_next_token(pp);
+	} while (ct -> ttype == TOK_WSPACE);
+	
+	if (ct -> ttype != TOK_IDENT)
+	{
+		preproc_throw_error(pp, "Bad #ifdef");
+		skip_eol(pp);
+	}
+	
+	if (symtab_find(pp, ct -> strval) != NULL)
+	{
+		pp -> skip_level++;
+	}
+	else
+	{
+		pp -> found_level++;
+	}
+	check_eol(pp);
+}
+
+static void dir_if(struct preproc_info *pp)
+{
+	if (pp -> skip_level || !eval_expr(pp))
+		pp -> skip_level++;
+	else
+		pp -> found_level++;
+}
+
+static void dir_elif(struct preproc_info *pp)
+{
+	if (pp -> skip_level == 0)
+		pp -> else_skip_level = pp -> found_level;
+	if (pp -> skip_level)
+	{
+		if (pp -> else_skip_level > pp -> found_level)
+			;
+		else if (--(pp -> skip_level) != 0)
+			pp -> skip_level++;
+		else if (eval_expr(pp))
+			pp -> found_level++;
+		else
+			pp -> skip_level++;
+	}
+	else if (pp -> found_level)
+	{
+		pp -> skip_level++;
+		pp -> found_level--;
+	}
+	else
+		preproc_throw_error(pp, "#elif in non-conditional section");
+}
+
+static void dir_else(struct preproc_info *pp)
+{
+	if (pp -> skip_level)
+	{
+		if (pp -> else_skip_level > pp -> found_level)
+			;
+		else if (--(pp -> skip_level) != 0)
+			pp -> skip_level++;
+		else
+			pp -> found_level++;
+	}
+	else if (pp -> found_level)
+	{
+		pp -> skip_level++;
+		pp -> found_level--;
+	}
+	else
+	{
+		preproc_throw_error(pp, "#else in non-conditional section");
+	}
+	if (pp -> else_level == pp -> found_level + pp -> skip_level)
+	{
+		preproc_throw_error(pp, "Too many #else");
+	}
+	pp -> else_level = pp -> found_level + pp -> skip_level;
+	check_eol(pp);
+}
+
+static void dir_endif(struct preproc_info *pp)
+{
+	if (pp -> skip_level)
+		pp -> skip_level--;
+	else if (pp -> found_level)
+		pp -> found_level--;
+	else
+		preproc_throw_error(pp, "#endif in non-conditional section");
+	if (pp -> skip_level == 0)
+		pp -> else_skip_level = 0;
+	pp -> else_level = 0;
+	check_eol(pp);
+}
+
+static void dir_define(struct preproc_info *pp)
+{
+	struct token *tl = NULL;
+	struct token *ttl;
+	struct token *ct;
+	int nargs = -1;
+	int vargs = 0;
+	char *mname = NULL;
+
+	char **arglist = NULL;
+	
+	if (pp -> skip_level)
+	{
+		skip_eol(pp);
+		return;
+	}
+
+	ct = preproc_next_token_nws(pp);
+	if (ct -> ttype != TOK_IDENT)
+		goto baddefine;
+	
+	mname = lw_strdup(ct -> strval);
+	ct = preproc_next_token(pp);
+	
+	if (ct -> ttype == TOK_WSPACE)
+	{
+		/* object like macro */
+	}
+	else if (ct -> ttype == TOK_EOL)
+	{
+		/* object like macro - empty value */
+		goto out;
+	}
+	else if (ct -> ttype == TOK_OPAREN)
+	{
+		/* function like macro - parse args */
+		nargs = 0;
+		vargs = 0;
+		for (;;)
+		{
+			ct = preproc_next_token_nws(pp);
+			if (ct -> ttype == TOK_EOL)
+			{
+				goto baddefine;
+			}
+			if (ct -> ttype == TOK_CPAREN)
+				break;
+			
+			if (ct -> ttype == TOK_IDENT)
+			{
+				/* parameter name */
+				nargs++;
+				/* record argument name */
+				arglist = lw_realloc(arglist, sizeof(char *) * nargs);
+				arglist[nargs - 1] = lw_strdup(ct -> strval);
+				
+				/* check for end of args or comma */
+				ct = preproc_next_token_nws(pp);
+				if (ct -> ttype == TOK_CPAREN)
+					break;
+				else if (ct -> ttype == TOK_COMMA)
+					continue;
+				else
+					goto baddefine;
+			}
+			else if (ct -> ttype == TOK_ELLIPSIS)
+			{
+				/* variadic macro */
+				vargs = 1;
+				ct = preproc_next_token_nws(pp);
+				if (ct -> ttype != TOK_CPAREN)
+					goto baddefine;
+				break;
+			}
+			else
+				goto baddefine;
+		}
+	}
+	else
+	{
+baddefine:
+		preproc_throw_error(pp, "bad #define");
+baddefine2:
+		skip_eol(pp);
+		lw_free(mname);
+		while (nargs > 0)
+			lw_free(arglist[--nargs]);
+		lw_free(arglist);
+		return;
+	}
+	
+	for (;;)
+	{
+		ct = preproc_next_token(pp);
+		if (ct -> ttype == TOK_EOL)
+			break;
+		if (!tl)
+			tl = ct;
+		else
+			ttl -> next = ct;
+		ttl = ct;
+		pp -> curtok = NULL;			// tell *_next_token* not to clobber token
+	}
+out:
+	if (strcmp(mname, "defined") == 0)
+	{
+		preproc_throw_warning(pp, "attempt to define 'defined' as a macro not allowed");
+		goto baddefine2;
+	}
+	else if (symtab_find(pp, mname) != NULL)
+	{
+		/* need to do a token compare between the old value and the new value
+		   to decide whether to complain */
+		preproc_throw_warning(pp, "%s previous defined", mname);
+		symtab_undef(pp, mname);
+	}
+	symtab_define(pp, mname, tl, nargs, arglist, vargs);
+	lw_free(mname);
+	while (nargs > 0)
+		lw_free(arglist[--nargs]);
+	lw_free(arglist);
+	/* no need to check for EOL here */
+}
+
+static void dir_undef(struct preproc_info *pp)
+{
+	struct token *ct;
+	if (pp -> skip_level)
+	{
+		skip_eol(pp);
+		return;
+	}
+	
+	do
+	{
+		ct = preproc_next_token(pp);
+	} while (ct -> ttype == TOK_WSPACE);
+	
+	if (ct -> ttype != TOK_IDENT)
+	{
+		preproc_throw_error(pp, "Bad #undef");
+		skip_eol(pp);
+	}
+	
+	symtab_undef(pp, ct -> strval);
+	check_eol(pp);
+}
+
+char *streol(struct preproc_info *pp)
+{
+	struct strbuf *s;
+	struct token *ct;
+	int i;
+		
+	s = strbuf_new();
+	do
+	{
+		ct = preproc_next_token(pp);
+	} while (ct -> ttype == TOK_WSPACE);
+	
+	while (ct -> ttype != TOK_EOL)
+	{
+		for (i = 0; ct -> strval[i]; i++)
+			strbuf_add(s, ct -> strval[i]);
+		ct = preproc_next_token(pp);
+	}
+	return strbuf_end(s);
+}
+
+static void dir_error(struct preproc_info *pp)
+{
+	char *s;
+	
+	if (pp -> skip_level)
+	{
+		skip_eol(pp);
+		return;
+	}
+	
+	s = streol(pp);
+	preproc_throw_error(pp, "%s", s);
+	lw_free(s);
+}
+
+static void dir_warning(struct preproc_info *pp)
+{
+	char *s;
+	
+	if (pp -> skip_level)
+	{
+		skip_eol(pp);
+		return;
+	}
+	
+	s = streol(pp);
+	preproc_throw_warning(pp, "%s", s);
+	lw_free(s);
+}
+
+static void dir_include(struct preproc_info *pp)
+{
+}
+
+static void dir_line(struct preproc_info *pp)
+{
+}
+
+static void dir_pragma(struct preproc_info *pp)
+{
+	if (pp -> skip_level)
+	{
+		skip_eol(pp);
+		return;
+	}
+	
+	preproc_throw_warning(pp, "Unsupported #pragma");
+	skip_eol(pp);
+}
+
+struct { char *name; void (*fn)(struct preproc_info *); } dirlist[] =
+{
+	{ "ifdef", dir_ifdef },
+	{ "ifndef", dir_ifndef },
+	{ "if", dir_if },
+	{ "else", dir_else },
+	{ "elif", dir_elif },
+	{ "endif", dir_endif },
+	{ "define", dir_define },
+	{ "undef", dir_undef },
+	{ "include", dir_include },
+	{ "error", dir_error },
+	{ "warning", dir_warning },
+	{ "line", dir_line },
+	{ "pragma", dir_pragma },
+	{ NULL, NULL }
+};
+
+static void process_directive(struct preproc_info *pp)
+{
+	struct token *ct;
+	int i;
+	
+	do
+	{
+		ct = preproc_next_token(pp);
+	} while (ct -> ttype == TOK_WSPACE);
+	
+	// NULL directive
+	if (ct -> ttype == TOK_EOL)
+		return;
+	
+	if (ct -> ttype != TOK_IDENT)
+		goto baddir;
+	
+	for (i = 0; dirlist[i].name; i++)
+	{
+		if (strcmp(dirlist[i].name, ct -> strval) == 0)
+		{
+			(*(dirlist[i].fn))(pp);
+			return;
+		}
+	}
+baddir:
+		preproc_throw_error(pp, "Bad preprocessor directive");
+		while (ct -> ttype != TOK_EOL)
+			ct = preproc_next_token(pp);
+		return;
+}
+
+/*
+Evaluate a preprocessor expression
+*/
+
+/* same as skip_eol() but the EOL token is not consumed */
+static void skip_eoe(struct preproc_info *pp)
+{
+	skip_eol(pp);
+	preproc_unget_token(pp, pp -> curtok);
+}
+
+static long eval_expr_real(struct preproc_info *, int);
+static long preproc_numval(struct token *);
+
+static long eval_term_real(struct preproc_info *pp)
+{
+	long tval = 0;
+	struct token *ct;
+	
+eval_next:
+	ct = preproc_next_processed_token_nws(pp);
+	if (ct -> ttype == TOK_EOL)
+	{
+		preproc_throw_error(pp, "Bad expression");
+		return 0;
+	}
+	
+	switch (ct -> ttype)
+	{
+	case TOK_OPAREN:
+		tval = eval_expr_real(pp, 0);
+		ct = preproc_next_processed_token_nws(pp);
+		if (ct -> ttype != ')')
+		{
+			preproc_throw_error(pp, "Unbalanced () in expression");
+			skip_eoe(pp);
+			return 0;
+		}
+		return tval;
+
+	case TOK_ADD: // unary +
+		goto eval_next;
+
+	case TOK_SUB: // unary -	
+		tval = eval_expr_real(pp, 200);
+		return -tval;
+
+	/* NOTE: we should only get "TOK_IDENT" from an undefined macro */		
+	case TOK_IDENT: // some sort of function, symbol, etc.
+		if (strcmp(ct -> strval, "defined"))
+		{
+			/* the defined operator */
+			/* any number in the "defined" bit will be
+			   treated as a defined symbol, even zero */
+			ct = preproc_next_token_nws(pp);
+			if (ct -> ttype == TOK_OPAREN)
+			{
+				ct = preproc_next_token_nws(pp);
+				if (ct -> ttype != TOK_IDENT)
+				{
+					preproc_throw_error(pp, "Bad expression");
+					skip_eoe(pp);
+					return 0;
+				}
+				if (symtab_find(pp, ct -> strval) == NULL)
+					tval = 0;
+				else
+					tval = 1;
+				ct = preproc_next_token_nws(pp);
+				if (ct -> ttype != TOK_CPAREN)
+				{
+					preproc_throw_error(pp, "Bad expression");
+					skip_eoe(pp);
+					return 0;
+				}
+				return tval;
+			}
+			else if (ct -> ttype == TOK_IDENT)
+			{
+				return (symtab_find(pp, ct -> strval) != NULL) ? 1 : 0;
+			}
+			preproc_throw_error(pp, "Bad expression");
+			skip_eoe(pp);
+			return 0;
+		}
+		/* unknown identifier - it's zero */
+		return 0;
+	
+	/* numbers */
+	case TOK_NUMBER:
+		return preproc_numval(ct);	
+		
+	default:
+		preproc_throw_error(pp, "Bad expression");
+		skip_eoe(pp);
+		return 0;
+	}
+	return 0;
+}
+
+static long eval_expr_real(struct preproc_info *pp, int p)
+{
+	static const struct operinfo
+	{
+		int tok;
+		int prec;
+	} operators[] =
+	{
+		{ TOK_ADD, 100 },
+		{ TOK_SUB, 100 },
+		{ TOK_STAR, 150 },
+		{ TOK_DIV, 150 },
+		{ TOK_MOD, 150 },
+		{ TOK_LT, 75 },
+		{ TOK_LE, 75 },
+		{ TOK_GT, 75 },
+		{ TOK_GE, 75 },
+		{ TOK_EQ, 70 },
+		{ TOK_NE, 70 },
+		{ TOK_BAND, 30 },
+		{ TOK_BOR, 25 },
+		{ TOK_NONE, 0 }
+	};
+	
+	int op;
+	long term1, term2, term3;
+	struct token *ct;
+	
+	term1 = eval_term_real(pp);
+eval_next:
+	ct = preproc_next_processed_token_nws(pp);
+	for (op = 0; operators[op].tok != TOK_NONE; op++)
+	{
+		if (operators[op].tok == ct -> ttype)
+			break;
+	}
+	/* if it isn't a recognized operator, assume end of expression */
+	if (operators[op].tok == TOK_NONE)
+	{
+		preproc_unget_token(pp, ct);
+		return term1;
+	}
+
+	/* if new operation is not higher than the current precedence, let the previous op finish */
+	if (operators[op].prec <= p)
+		return term1;
+
+	/* get the second term */
+	term2 = eval_expr_real(pp, operators[op].prec);
+	
+	switch (operators[op].tok)
+	{
+	case TOK_ADD:
+		term3 = term1 + term2;
+		break;
+	
+	case TOK_SUB:
+		term3 = term1 - term2;
+		break;
+	
+	case TOK_STAR:
+		term3 = term1 * term2;
+		break;
+	
+	case TOK_DIV:
+		if (!term2)
+		{
+			preproc_throw_warning(pp, "Division by zero");
+			term3 = 0;
+			break;
+		}
+		term3 = term1 / term2;
+		break;
+	
+	case TOK_MOD:
+		if (!term2)
+		{
+			preproc_throw_warning(pp, "Division by zero");
+			term3 = 0;
+			break;
+		}
+		term3 = term1 % term2;
+		break;
+		
+	case TOK_BAND:
+		term3 = (term1 && term2);
+		break;
+	
+	case TOK_BOR:
+		term3 = (term1 || term2);
+		break;
+	
+	case TOK_EQ:
+		term3 = (term1 == term2);
+		break;
+	
+	case TOK_NE:
+		term3 = (term1 != term2);
+		break;
+	
+	case TOK_GT:
+		term3 = (term1 > term2);
+		break;
+	
+	case TOK_GE:
+		term3 = (term1 >= term2);
+		break;
+	
+	case TOK_LT:
+		term3 = (term1 < term2);
+		break;
+	
+	case TOK_LE:
+		term3 = (term1 <= term2);
+		break;
+
+	default:
+		term3 = 0;
+		break;
+	}
+	term1 = term3;
+	goto eval_next;
+}
+
+static long eval_expr(struct preproc_info *pp)
+{
+	long rv;
+	
+	rv = eval_expr_real(pp, 0);
+	if (pp -> curtok -> ttype != TOK_EOL)
+	{
+		preproc_throw_error(pp, "Bad expression");
+		skip_eol(pp);
+	}
+	return rv;
+}
+
+/* convert a numeric string to a number */
+long preproc_numval(struct token *t)
+{
+	return 0;
+}
+
+/*
+Below here is the logic for expanding a macro
+*/
+static int expand_macro(struct preproc_info *pp, char *mname)
+{
+	struct symtab_e *s;
+	struct token *t, *t2, *t3;
+	struct token **arglist = NULL;
+	int nargs = 0;
+	struct expand_e *e;
+	struct token **exparglist = NULL;
+	int i;
+	
+	s = symtab_find(pp, mname);
+	if (!s)
+		return 0;
+	
+	for (e = pp -> expand_list; e; e = e -> next)
+	{
+		/* don't expand if we're already expanding the same macro */
+		if (e -> s == s)
+			return 0;
+	}
+
+	if (s -> nargs == -1)
+	{
+		/* short circuit NULL expansion */
+		if (s -> tl == NULL)
+			return 1;
+
+		goto expandmacro;
+	}
+	
+	// look for opening paren after optional whitespace
+	t2 = NULL;
+	t = NULL;
+	for (;;)
+	{
+		t = preproc_next_token(pp);
+		if (t -> ttype != TOK_WSPACE && t -> ttype != TOK_EOL)
+			break;
+		t -> next = t2;
+		t2 = t2;
+	}
+	if (t -> ttype != TOK_OPAREN)
+	{
+		// not a function-like invocation
+		while (t2)
+		{
+			t = t2 -> next;
+			preproc_unget_token(pp, t2);
+			t2 = t;
+		}
+		return 0;
+	}
+	
+	// parse parameters here
+	t = preproc_next_token_nws(pp);
+	nargs = 1;
+	arglist = lw_alloc(sizeof(struct token *));
+	arglist[0] = NULL;
+	t2 = NULL;
+	
+	while (t -> ttype != TOK_CPAREN)
+	{
+		if (t -> ttype == TOK_EOF)
+		{
+			preproc_throw_error(pp, "Unexpected EOF in macro call");
+			break;
+		}
+		if (t -> ttype == TOK_EOL)
+			continue;
+		if (t -> ttype == TOK_COMMA)
+		{
+			if (!(s -> vargs) || (nargs > s -> nargs))
+			{
+				nargs++;
+				arglist = lw_realloc(arglist, sizeof(struct token *) * nargs);
+				arglist[nargs - 1] = NULL;
+				t2 = NULL;
+				continue;
+			}
+		}
+		if (t2)
+		{
+			t2 -> next = token_dup(t);
+			t2 = t2 -> next;
+		}
+		else
+		{
+			t2 = token_dup(t);
+			arglist[nargs - 1] = t2;
+		}
+	}
+
+	if (s -> vargs)
+	{
+		if (nargs <= s -> nargs)
+		{
+			preproc_throw_error(pp, "Wrong number of arguments (%d) for variadic macro %s which takes %d arguments", nargs, mname, s -> nargs);
+		}
+	}
+	else
+	{
+		if (s -> nargs != nargs && !(s -> nargs == 0 && nargs == 1 && arglist[nargs - 1]))
+		{
+			preproc_throw_error(pp, "Wrong number of arguments (%d) for macro %s which takes %d arguments", nargs, mname, s -> nargs);
+		}
+	}
+
+	/* now calculate the pre-expansions of the arguments */
+	exparglist = lw_alloc(nargs);
+	for (i = 0; i < nargs; i++)
+	{
+		t2 = NULL;
+		exparglist[i] = NULL;
+		// NOTE: do nothing if empty argument
+		if (arglist[i] == NULL)
+			continue;
+		pp -> sourcelist = arglist[i];
+		for (;;)
+		{
+			t = preproc_next_processed_token(pp);
+			if (t -> ttype == TOK_EOF)
+				break;
+			if (t2)
+			{
+				t2 -> next = token_dup(t);
+				t2 = t2 -> next;
+			}
+			else
+			{
+				t2 = token_dup(t);
+				exparglist[i] = t2;
+			}
+		}
+	}
+
+expandmacro:
+	t2 = NULL;
+	t3 = NULL;
+	
+	for (t = s -> tl; t; t = t -> next)
+	{
+		if (t -> ttype == TOK_IDENT)
+		{
+			/* identifiers might need expansion to arguments */
+			if (strcmp(t -> strval, "__VA_ARGS__") == 0)
+			{
+				i = s -> nargs;
+			}
+			else
+			{
+				for (i = 0; i < nargs; i++)
+				{
+					if (strcmp(t -> strval, s -> params[i]) == 0)
+						break;
+				}
+			}
+			if ((i == s -> nargs) && !(s -> vargs))
+			{
+				struct token *te;
+				// expand argument
+				// FIXME: handle # and ##
+				for (te = exparglist[i]; te; te = te -> next)
+				{
+					if (t2)
+					{
+						t2 -> next = token_dup(te);
+						t2 = t2 -> next;
+					}
+					else
+					{
+						t3 = token_dup(te);
+						t2 = t2;
+					}
+				}
+				continue;
+			}
+		}
+		if (t2)
+		{
+			t2 -> next = token_dup(t);
+			t2 = t2 -> next;
+		}
+		else
+		{
+			t3 = token_dup(t);
+			t2 = t3;
+		}
+	}
+
+	/* put the new expansion in front of the input, if relevant; if we
+	   expanded to nothing, no need to create an expansion record or
+	   put anything into the input queue */
+	if (t3)
+	{
+		t2 -> next = token_create(TOK_ENDEXPAND, "", -1, -1, "");
+		t2 -> next -> next = pp -> tokqueue;
+		pp -> tokqueue = t3;
+
+		/* set up expansion record */
+		e = lw_alloc(sizeof(struct expand_e));
+		e -> next = pp -> expand_list;
+		pp -> expand_list = e;
+		e -> s = s;
+	}
+	
+	/* now clean up */
+	for (i = 0; i < nargs; i++)
+	{
+		lw_free(arglist[i]);
+		lw_free(exparglist[i]);
+	}
+	lw_free(arglist);
+	lw_free(exparglist);
+	
+	return 1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lwcc/symbol.c	Sat Sep 14 20:04:38 2013 -0600
@@ -0,0 +1,92 @@
+/*
+lwcc/symbol.c
+
+Copyright © 2013 William Astle
+
+This file is part of LWTOOLS.
+
+LWTOOLS is free software: you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <string.h>
+
+#include <lw_alloc.h>
+#include <lw_string.h>
+
+#include "cpp.h"
+#include "symbol.h"
+#include "token.h"
+
+void symbol_free(struct symtab_e *s)
+{
+	int i;
+	struct token *t;
+	
+	lw_free(s -> name);
+	
+	for (i = 0; i < s -> nargs; i++)
+		lw_free(s -> params[i]);
+	lw_free(s -> params);
+	while (s -> tl)
+	{
+		t = s -> tl;
+		s -> tl = t -> next;
+		token_free(t);
+	}
+}
+
+struct symtab_e *symtab_find(struct preproc_info *pp, char *name)
+{
+	struct symtab_e *s;
+	
+	for (s = pp -> sh; s; s = s -> next)
+	{
+		if (strcmp(s -> name, name) == 0)
+		{
+			return s;
+		}
+	}
+	return NULL;
+}
+
+void symtab_undef(struct preproc_info *pp, char *name)
+{
+	struct symtab_e *s, **p;
+	
+	p = &(pp -> sh);
+	for (s = pp -> sh; s; s = s -> next)
+	{
+		if (strcmp(s -> name, name) == 0)
+		{
+			(*p) -> next = s -> next;
+			symbol_free(s);
+			return;
+		}
+		p = &((*p) -> next);
+	}
+}
+
+void symtab_define(struct preproc_info *pp, char *name, struct token *def, int nargs, char **params, int vargs)
+{
+	struct symtab_e *s;
+	
+	s = lw_alloc(sizeof(struct symtab_e));
+	s -> name = lw_strdup(name);
+	s -> tl = def;
+	s -> nargs = nargs;
+	s -> params = params;
+	s -> vargs = vargs;
+	s -> next = pp -> sh;
+	pp -> sh = s;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lwcc/symbol.h	Sat Sep 14 20:04:38 2013 -0600
@@ -0,0 +1,42 @@
+/*
+lwcc/symbol.h
+
+Copyright © 2013 William Astle
+
+This file is part of LWTOOLS.
+
+LWTOOLS is free software: you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef symbol_h_seen___
+#define symbol_h_seen___
+
+#include "cpp.h"
+#include "token.h"
+
+struct symtab_e
+{
+	char *name;				// symbol name
+	struct token *tl;		// token list the name is defined as, NULL for none
+	int nargs;				// number named of arguments - -1 for object like macro
+	int vargs;				// set if macro has varargs style
+	char **params;			// the names of the parameters
+	struct symtab_e *next;	// next entry in list
+};
+
+struct symtab_e *symtab_find(struct preproc_info *, char *);
+void symtab_undef(struct preproc_info *, char *);
+void symtab_define(struct preproc_info *, char *, struct token *, int, char **, int);
+
+#endif // symbol_h_seen___
--- a/lwcc/token.h	Thu Sep 12 22:06:26 2013 -0600
+++ b/lwcc/token.h	Sat Sep 14 20:04:38 2013 -0600
@@ -91,6 +91,8 @@
 	TOK_CHR_LIT,
 	TOK_STR_LIT,
 	TOK_ARROW,
+	TOK_ENDEXPAND,
+	TOK_STARTEXPAND,
 	TOK_MAX
 };