view lwcc/preproc.c @ 298:6112c67728ba ccdev

Add stringification and token concatenation Add support for # and ## in macro expansion by the preprocessor (stringification and token concatenation). Totally untested.
author William Astle <lost@l-w.ca>
date Sat, 14 Sep 2013 22:42:53 -0600
parents 310df72c641d
children 856caf91ffaa
line wrap: on
line source

/*
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 *);
extern struct token *preproc_lex_next_token(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 char *stringify(struct token *tl)
{
	struct strbuf *s;
	int ws = 0;
	
	s = strbuf_new();
	strbuf_add(s, '"');

	while (tl && tl -> ttype == TOK_WSPACE)
		tl = tl -> next;
	
	for (; tl; tl = tl -> next)
	{
		if (tl -> ttype == TOK_WSPACE)
		{
			ws = 1;
			continue;
		}
		if (ws)
		{
			strbuf_add(s, ' ');
		}
		for (ws = 0; tl -> strval[ws]; ws++)
		{
			if (tl -> ttype == TOK_STRING || tl -> ttype == TOK_CHR_LIT)
			{
				if (tl -> strval[ws] == '"' || tl -> strval[ws] == '\\')
					strbuf_add(s, '\\');
			}
		}
		ws = 0;
	}
	
	strbuf_add(s, '"');
	return strbuf_end(s);
}

/* return list to tokens as a result of ## expansion */
static struct token *paste_tokens(struct preproc_info *pp, struct symtab_e *s, struct token **arglist, struct token *t1, struct token *t2)
{
	struct token *rl = NULL, *rlt;
	struct token *s1, *s2;
	struct token *ws;
	int i;
	char *tstr;
	
	if (t1 -> ttype == TOK_IDENT)
	{
		if (strcmp(t1 -> strval, "__VA_ARGS__") == 0)
		{
			i = s -> nargs;
		}
		else
		{
			for (i = 0; i < s -> nargs; i++)
			{
				if (strcmp(s -> params[i], t1 -> strval) == 0)
					break;
			}
		}
		if ((i == s -> nargs) && !(s -> vargs))
		{
			s1 = token_dup(t1);
		}
		else
		{
			/* find last non-whitespace token */
			ws = NULL;
			for (t1 = s -> tl; t1; t1 = t1 -> next)
			{
				if (t1 -> ttype != TOK_WSPACE)
					ws = t1;
			}
			if (!ws)
			{
				s1 = NULL;
			}
			else
			{
				if (ws != s -> tl)
				{
					/* output extra tokens */
					for (t1 = s -> tl; t1 -> next != ws; t1 = t1 -> next)
					{
						if (!rl)
						{
							rl = token_dup(t1);
							rlt = rl;
						}
						else
						{
							rlt -> next = token_dup(t1);
							rlt = rlt -> next;
						}
					}
				}
				s1 = token_dup(ws);
			}
		}
	}
	else
	{
		s1 = token_dup(t1);
	}
	if (t2 -> ttype == TOK_IDENT)
	{
		if (strcmp(t1 -> strval, "__VA_ARGS__") == 0)
		{
			i = s -> nargs;
		}
		else
		{
			for (i = 0; i < s -> nargs; i++)
			{
				if (strcmp(s -> params[i], t1 -> strval) == 0)
					break;
			}
		}
		if ((i == s -> nargs) && !(s -> vargs))
		{
			s2 = token_dup(t2);
			t2 = NULL;
		}
		else
		{
			/* find last non-whitespace token */
			ws = NULL;
			for (t2 = s -> tl; t2; t2 = t2 -> next)
			{
				if (t2 -> ttype != TOK_WSPACE)
				{
					ws = t2;
					t2 = t2 -> next;
					break;
				}
			}
			if (!ws)
			{
				s2 = NULL;
			}
			else
			{
				s2 = token_dup(ws);
			}
		}
	}
	else
	{
		s2 = token_dup(t2);
	}
	
	/* here, s1 is NULL if no left operand or a duplicated token for the actual left operand */
	/* here, s2 is NULL if no right operand or a duplicated token for the actual right operand */
	/* here, t2 points to a possibly empty list of extra tokens to output after the concatenated tokens */
	/* here, rl,rlt is a possibly non-empty list of tokens preceding the concatenation */
	
	/* tokens combine if the combination exactly matches "combinelist", in which case the string values are
	   concatenated and the new token type is used to create a new token. If the tokens do not combine,
	   s1 and s2 are returned in sequence. */
	
	if (!s1 && s2)
	{
		if (!rl)
			rl = s2;
		else
			rlt -> next = s2;
		rlt = s2;
	}
	else if (s1 && !s2)
	{
		if (!rl)
			rl = s1;
		else
			rlt -> next = s1;
		rlt = s1;
	}
	else if (s1 && s2)
	{
		tstr = lw_alloc(strlen(s1 -> strval) + strlen(s2 -> strval) + 1);
		strcpy(tstr, s1 -> strval);
		strcat(tstr, s2 -> strval);
		/* now try to lex the string */
		pp -> lexstr = tstr;
		pp -> lexstrloc = 0;
		t1 = preproc_lex_next_token(pp);
		if (pp -> lexstr[pp -> lexstrloc])
		{
			// doesn't make a new token - pass through the original two
			if (!rl)
				rl = s1;
			else
				rlt -> next = s1;
			s1 -> next = s2;
			rlt = s2;
		}
		else
		{
			// does make a new token
			t1 -> fn = s1 -> fn;
			t1 -> column = s1 -> column;
			t1 -> lineno = s1 -> lineno;
			if (!rl)
				rl = t1;
			else
				rlt -> next = t1;
			rlt = t1;
		}
		lw_free(tstr);
		pp -> lexstr = NULL;
	}
	
	/* add in any extra tokens */
	while (t2)
	{
		if (!rl)
		{
			rl = token_dup(t2);
			rlt = rl;
		}
		else
		{
			rlt -> next = token_dup(t2);
			rlt = rlt -> next;
		}
		t2 = t2 -> next;
	}
	
	return rl;
}


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;
	int pcount;
	char *tstr;
	
	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)
	{
		pcount = 0;
		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_OPAREN)
			pcount++;
		else if (t -> ttype == TOK_CPAREN && pcount)
			pcount--;
		if (t -> ttype == TOK_COMMA && pcount == 0)
		{
			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)
	{
again:
		if (t -> ttype != TOK_WSPACE && t -> next)
		{
			struct token *ct1, *ct2;
			
			for (ct1 = t -> next; ct1 && ct1 -> ttype == TOK_WSPACE; ct1 = ct1 -> next)
			{
				if (ct1 -> ttype == TOK_DBLHASH)
				{
					// possible concatenation here
					for (ct2 = ct1 -> next; ct2 && ct2 -> ttype == TOK_WSPACE; ct2 = ct2 -> next)
						/* do nothing */ ;
					if (ct2)
					{
						// we have concatenation here so we paste str1 and str2 together and see what we get
						// if we get NULL, the past didn't make a valid token
						ct1 = paste_tokens(pp, s, arglist, t, ct2);
						if (ct1)
						{
							if (t2)
							{
								t2 -> next = ct1;
							}
							else
							{
								t3 = ct1;
							}
							for (t2 = ct1; t2 -> next; t2 = t2 -> next)
								/* do nothing */ ;

							/* because of the level of control structures, move to next token and restart loop */
							t = ct2 -> next;
							goto again;
						}
						goto nopaste;
					}
				}
			}
		}
	
nopaste:
		if (t -> ttype == TOK_HASH)
		{
			if (t -> next && t -> next -> ttype == TOK_IDENT)
			{
				if (strcmp(t -> next -> strval, "__VA_ARGS__") == 0)
				{
					i = nargs;
				}
				else
				{
					for (i = 0; i < nargs; i++)
					{
						if (strcmp(t -> next -> strval, s -> params[i]) == 0)
							break;
					}
				}
				if (!((i == s -> nargs) && !(s -> vargs)))
				{
					// we have a stringification here
					t = t -> next;
					tstr = stringify(arglist[i]);
					if (t2)
					{
						t2 = token_create(TOK_STRING, tstr, t -> lineno, t -> column, t -> fn);
						t2 = t2 -> next;
					}
					else
					{
						t3 = token_create(TOK_STRING, tstr, t -> lineno, t -> column, t -> fn);
						t2 = t3;
					}
					lw_free(tstr);
					continue;
				}
			}
		}
		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
				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;
}