/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*           Copyright (c) 1986-2006 AT&T Knowledge Ventures            *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                      by AT&T Knowledge Ventures                      *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                                                                      *
***********************************************************************/
#pragma prototyped
/*
 * Glenn Fowler
 * AT&T Research
 *
 * preprocessor control directive support
 */

#include "pplib.h"

#include <regex.h>

#define TOKOP_DUP	(1<<0)
#define TOKOP_STRING	(1<<1)
#define TOKOP_UNSET	(1<<2)

struct edit
{
	struct edit*	next;
	regex_t		re;
};

struct map
{
	struct map*	next;
	regex_t		re;
	struct edit*	edit;
};

#define RESTORE		(COLLECTING|CONDITIONAL|DEFINITION|DIRECTIVE|DISABLE|EOF2NL|HEADER|NOSPACE|NOVERTICAL|PASSEOF|STRIP)

/*
 * common predicate assertion operations
 * op is DEFINE or UNDEF
 */

static void
assert(int op, char* pred, char* args)
{
	register struct pplist*		a;
	register struct ppsymbol*	sym;
	register struct pplist*		p;
	register struct pplist*		q;

	if (!args) switch (op)
	{
	case DEFINE:
		goto mark;
	case UNDEF:
		a = 0;
		goto unmark;
	}
	if (a = (struct pplist*)hashget(pp.prdtab, pred))
	{
		p = 0;
		q = a;
		while (q)
		{
			if (streq(q->value, args))
			{
				if (op == DEFINE) return;
				q = q->next;
				if (p) p->next = q;
				else a = q;
			}
			else
			{
				p = q;
				q = q->next;
			}
		}
		if (op == UNDEF)
		{
		unmark:
			hashput(pp.prdtab, pred, a);
			if (sym = ppsymref(pp.symtab, pred))
				sym->flags &= ~SYM_PREDICATE;
			return;
		}
	}
	if (op == DEFINE)
	{
		p = newof(0, struct pplist, 1, 0);
		p->next = a;
		p->value = strdup(args);
		hashput(pp.prdtab, NiL, p);
	mark:
		if ((pp.state & COMPILE) && pp.truncate) return;
		if (sym = ppsymset(pp.symtab, pred))
			sym->flags |= SYM_PREDICATE;
	}
}

/*
 * tokenize string ppop()
 *
 *	op	PP_* op
 *	name	option name
 *	s	string of option values
 *	n	option sense
 *	flags	TOKOP_* flags
 */

static void
tokop(int op, char* name, register char* s, register int n, int flags)
{
	register int	c;
	register char*	t;

	if (!(flags & TOKOP_UNSET) && !n) error(2, "%s: option cannot be unset", name);
	else if (!s) ppop(op, s, n);
	else if (flags & TOKOP_STRING)
	{
		PUSH_LINE(s);
		for (;;)
		{
			pp.state &= ~NOSPACE;
			c = pplex();
			pp.state |= NOSPACE;
			if (!c) break;
			if (c != ' ')
				ppop(op, (flags & TOKOP_DUP) ? strdup(pp.token) : pp.token, n);
		}
		POP_LINE();
	}
	else do
	{
		while (*s == ' ') s++;
		for (t = s; *t && *t != ' '; t++);
		if (*t) *t++ = 0;
		else t = 0;
		if (*s) ppop(op, (flags & TOKOP_DUP) ? strdup(s) : s, n);
	} while (s = t);
}

/*
 * return symbol pointer for next token macro (re)definition
 */

static struct ppsymbol*
macsym(int tok)
{
	register struct ppsymbol*	sym;

	if (tok != T_ID)
	{
		error(2, "%s: invalid macro name", pptokstr(pp.token, 0));
		return 0;
	}
	sym = pprefmac(pp.token, REF_CREATE);
	if ((sym->flags & SYM_FINAL) && (pp.mode & HOSTED)) return 0;
	if (sym->flags & (SYM_ACTIVE|SYM_READONLY))
	{
		if (!(pp.option & ALLPOSSIBLE))
			error(2, "%s: macro is %s", sym->name, (sym->flags & SYM_READONLY) ? "readonly" : "active");
		return 0;
	}
	if (!sym->macro) sym->macro = newof(0, struct ppmacro, 1, 0);
	return sym;
}

/*
 * get one space canonical pplex() line, sans '\n', and place in p
 * x is max+1 pos in p
 * 0 returned if line too large
 * otherwise end of p ('\0') returned
 */

static char*
getline(register char* p, char* x, int disable)
{
	register int	c;
	register char*	s;
	char*		b;
	long		restore;

	restore = pp.state & (NOSPACE|STRIP);
	pp.state &= ~(NEWLINE|NOSPACE|STRIP);
	pp.state |= EOF2NL;
	b = p;
	while ((c = pplex()) != '\n')
	{
		if (disable)
		{
			if (c == ' ')
				/*ignore*/;
			else if (disable == 1)
				disable = (c == T_ID && streq(pp.token, pp.pass)) ? 2 : 0;
			else
			{
				disable = 0;
				if (c == ':')
					pp.state |= DISABLE;
			}
		}
		s = pp.token;
		while (*p = *s++)
			if (++p >= x)
			{
				p = 0;
				goto done;
			}
	}
	if (p > b && *(p - 1) == ' ')
		p--;
	if (p >= x)
		p = 0;
	else
		*p = 0;
 done:
	pp.state &= ~(NOSPACE|STRIP);
	pp.state |= restore;
	return p;
}

/*
 * regex error handler
 */

void
regfatal(regex_t* p, int level, int code)
{
	char	buf[128];

	regerror(code, p, buf, sizeof(buf));
	regfree(p);
	error(level, "regular expression: %s", buf);
}

/*
 * process a single directive line
 */

int
ppcontrol(void)
{
	register char*			p;
	register int			c;
	register int			n;
	register char*			s;
	register struct ppmacro*	mac;
	register struct ppsymbol*	sym;
	struct edit*			edit;
	struct map*			map;
	struct ppfile*			fp;
	int				o;
	int				directive;
	long				restore;
	struct pptuple*			rp;
	struct pptuple*			tp;
	char*				v;
	int				emitted;

	union
	{
		struct map*		best;
		struct ppinstk*		inp;
		struct pplist*		list;
		char*			string;
		struct ppsymbol*	symbol;
		int			type;
		PPLINESYNC		linesync;
	}				var;

	static char			__va_args__[] = "__VA_ARGS__";
	static int			i0;
	static int			i1;
	static int			i2;
	static int			i3;
	static int			i4;

	static long			n1;
	static long			n2;
	static long			n3;

	static char*			p0;
	static char*			p1;
	static char*			p2;
	static char*			p3;
	static char*			p4;
	static char*			p5;
	static char*			p6;

	static struct ppmacro		old;
	static char*			formargs[MAXFORMALS];
#if MACKEYARGS
	static char*			formvals[MAXFORMALS];
#endif

	emitted = 0;
	if (pp.state & SKIPCONTROL) pp.level--;
	restore = (pp.state & RESTORE)|NEWLINE;
	if (pp.state & PASSTHROUGH) restore |= DISABLE;
	else restore &= ~DISABLE;
	pp.state &= ~(NEWLINE|RESTORE|SKIPCONTROL);
	pp.state |= DIRECTIVE|DISABLE|EOF2NL|NOSPACE|NOVERTICAL;
#if COMPATIBLE
	if ((pp.state & (COMPATIBILITY|STRICT)) == COMPATIBILITY || (pp.mode & HOSTED)) pp.state &= ~NOVERTICAL;
#else
	if (pp.mode & HOSTED) pp.state &= ~NOVERTICAL;
#endif
	switch (c = pplex())
	{
	case T_DECIMAL:
	case T_OCTAL:
		if ((pp.state & STRICT) && !(pp.mode & (HOSTED|RELAX)))
			error(1, "# <line> [ \"<file>\" [ <type> ] ]: non-standard directive");
		directive = INCLUDE;
		goto linesync;
	case T_ID:
		switch (directive = (int)hashref(pp.dirtab, pp.token))
		{
		case ELIF:
		else_if:
			if ((pp.option & ALLPOSSIBLE) && !pp.in->prev->prev)
				goto eatdirective;
			if (pp.control <= pp.in->control)
			{
				error(2, "no matching #%s for #%s", dirname(IF), dirname(ELIF));
				goto eatdirective;
			}
			if (pp.control == (pp.in->control + 1)) pp.in->flags |= IN_noguard;
			if (*pp.control & HADELSE)
			{
				error(2, "invalid #%s after #%s", dirname(ELIF), dirname(ELSE));
				*pp.control |= SKIP;
				goto eatdirective;
			}
			if (*pp.control & KEPT)
			{
				*pp.control |= SKIP;
				goto eatdirective;
			}
			if (directive == IFDEF || directive == IFNDEF)
			{
				*pp.control &= ~SKIP;
				goto else_ifdef;
			}
		conditional:
			if (ppexpr(&i1))
			{
				*pp.control &= ~SKIP;
				*pp.control |= KEPT;
			}
			else *pp.control |= SKIP;
			c = (pp.state & NEWLINE) ? '\n' : ' ';
			goto eatdirective;
		case ELSE:
			if ((pp.option & ALLPOSSIBLE) && !pp.in->prev->prev)
				goto eatdirective;
			if ((pp.option & ELSEIF) && (c = pplex()) == T_ID && ((n = (int)hashref(pp.dirtab, pp.token)) == IF || n == IFDEF || n == IFNDEF))
			{
				error(1, "#%s %s is non-standard -- use #%s", dirname(directive), dirname(n), dirname(ELIF));
				directive = n;
				goto else_if;
			}
			if (pp.control <= pp.in->control) error(2, "no matching #%s for #%s", dirname(IF), dirname(ELSE));
			else
			{
				if (pp.control == (pp.in->control + 1)) pp.in->flags |= IN_noguard;
				if (!(*pp.control & KEPT))
				{
					*pp.control &= ~SKIP;
					*pp.control |= HADELSE|KEPT;
				}
				else
				{
					if (*pp.control & HADELSE) error(2, "more than one #%s for #%s", dirname(ELSE), dirname(IF));
					*pp.control |= HADELSE|SKIP;
				}
			}
			goto enddirective;
		case ENDIF:
			if ((pp.option & ALLPOSSIBLE) && !pp.in->prev->prev)
				goto eatdirective;
			if (pp.control <= pp.in->control) error(2, "no matching #%s for #%s", dirname(IF), dirname(ENDIF));
			else if (--pp.control == pp.in->control && pp.in->symbol)
			{
				if (pp.in->flags & IN_endguard) pp.in->flags |= IN_noguard;
				else
				{
					pp.in->flags &= ~IN_tokens;
					pp.in->flags |= IN_endguard;
				}
			}
			goto enddirective;
		case IF:
		case IFDEF:
		case IFNDEF:
			if ((pp.option & ALLPOSSIBLE) && !pp.in->prev->prev)
				goto eatdirective;
			pushcontrol();
			SETIFBLOCK(pp.control);
			if (*pp.control & SKIP)
			{
				*pp.control |= KEPT;
				goto eatdirective;
			}
			if (directive == IF) goto conditional;
		else_ifdef:
			if ((c = pplex()) == T_ID)
			{
				sym = pprefmac(pp.token, REF_IF);
				if (directive == IFNDEF && pp.control == pp.in->control + 1)
				{
					if (pp.in->flags & (IN_defguard|IN_endguard))
						pp.in->flags |= IN_noguard;
					else
					{
						pp.in->flags |= IN_defguard;
						if (!(pp.in->flags & IN_tokens))
							pp.in->symbol = sym ? sym : pprefmac(pp.token, REF_CREATE);
					}
				}
			}
			else
			{
				sym = 0;
				if (!(pp.mode & HOSTED))
					error(1, "%s: invalid macro name", pptokstr(pp.token, 0));
			}
			*pp.control |= ((sym != 0) == (directive == IFDEF)) ? KEPT : SKIP;
			goto enddirective;
		case INCLUDE:
			if (*pp.control & SKIP)
			{
				pp.state |= HEADER;
				c = pplex();
				pp.state &= ~HEADER;
				goto eatdirective;
			}
			pp.state &= ~DISABLE;
			pp.state |= HEADER|STRIP;
			switch (c = pplex())
			{
			case T_STRING:
				p = pp.token;
				do pp.token = pp.toknxt; while ((c = pplex()) == T_STRING);
				*pp.token = 0;
				pp.token = p;
				/*FALLTHROUGH*/
			case T_HEADER:
			header:
				if (!*pp.token)
				{
					error(2, "#%s: null file name", dirname(INCLUDE));
					break;
				}
				if (*pp.token == '/' && !(pp.mode & (HOSTED|RELAX)))
					error(1, "#%s: reference to %s is not portable", dirname(INCLUDE), pp.token);
				n = ppsearch(pp.token, c, SEARCH_INCLUDE);
				break;
			case '<':
				/*
				 * HEADEREXPAND|HEADEREXPANDALL gets us here
				 */

				if (!(p = pp.hdrbuf) && !(p = pp.hdrbuf = newof(0, char, MAXTOKEN, 0)))
					error(3, "out of space");
				pp.state &= ~NOSPACE;
				while ((c = pplex()) && c != '>')
				{
					v = p + 1;
					STRCOPY(p, pp.token, s);
					if (p == v && *(p - 1) == ' ' && pp.in->type != IN_MACRO)
						p--;
				}
				pp.state |= NOSPACE;
				*p++ = 0;
				memcpy(pp.token, pp.hdrbuf, p - pp.hdrbuf);
				c = T_HEADER;
				goto header;
			default:
				error(2, "#%s: \"...\" or <...> argument expected", dirname(INCLUDE));
				goto eatdirective;
			}
			goto enddirective;
		case 0:
			{
				regmatch_t	match[10];

				/*UNDENT*/
	p = pp.valbuf;
	*p++ = '#';
	STRCOPY(p, pp.token, s);
	p0 = p;
	pp.mode |= EXPOSE;
	pp.state |= HEADER;
	p6 = getline(p, &pp.valbuf[MAXTOKEN], 0);
	pp.state &= ~HEADER;
	pp.mode &= ~EXPOSE;
	if (!p6)
	{
		*p0 = 0;
		error(2, "%s: directive too long", pp.valbuf);
		c = 0;
		goto eatdirective;
	}
	p1 = p2 = p3 = p4 = 0;
	p5 = *p ? p + 1 : 0;
 checkmap:
	i0 = *p0;
	p = pp.valbuf;
	var.best = 0;
	n = 0;
	for (map = (struct map*)pp.maps; map; map = map->next)
		if (!(i1 = regexec(&map->re, p, elementsof(match), match, 0)))
		{
			if ((c = match[0].rm_eo - match[0].rm_so) > n)
			{
				n = c;
				var.best = map;
			}
		}
		else if (i1 != REG_NOMATCH)
			regfatal(&map->re, 3, i1);
	c = '\n';
	if (map = var.best)
	{
		if ((pp.state & (STRICT|WARN)) && !(pp.mode & (HOSTED|RELAX)))
		{
			*p0 = 0;
			if (!(pp.state & WARN) || strcmp(p + 1, dirname(PRAGMA)))
				error(1, "%s: non-standard directive", p);
			*p0 = i0;
		}
		if (!(*pp.control & SKIP))
		{
			n = 0;
			for (edit = map->edit; edit; edit = edit->next)
				if (!(i0 = regexec(&edit->re, p, elementsof(match), match, 0)))
				{
					n++;
					if (i0 = regsubexec(&edit->re, p, elementsof(match), match))
						regfatal(&edit->re, 3, i0);
					p = edit->re.re_sub->re_buf;
					if (edit->re.re_sub->re_flags & REG_SUB_STOP)
						break;
				}
				else if (i0 != REG_NOMATCH)
					regfatal(&edit->re, 3, i0);
			if (n && *p)
			{
				p1 = s = oldof(0, char, 0, strlen(p) + 32);
				while (*s = *p++) s++;
				debug((-4, "map: %s", p1));
				*s++ = '\n';
				*s = 0;
				error_info.line++;
				PUSH_RESCAN(p1);
				error_info.line--;
				directive = LINE;
			}
		}
		goto donedirective;
	}
	if (directive != PRAGMA && (!(*pp.control & SKIP) || !(pp.mode & (HOSTED|RELAX))))
	{
		*p0 = 0;
		error(1, "%s: unknown directive", pptokstr(pp.valbuf, 0));
		*p0 = i0;
	}
 pass:
	if (!(*pp.control & SKIP) && pp.pragma && !(pp.state & NOTEXT) && (directive == PRAGMA || !(pp.mode & INIT)))
	{
		*p0 = 0;
		if (p2) *p2 = 0;
		if (p4)
		{
			if (p4 == p5)
			{
				p5 = strcpy(pp.tmpbuf, p5);
				if (p = strchr(p5, MARK))
				{
					s = p;
					while (*p)
						if ((*s++ = *p++) == MARK && *p == MARK) p++;
					*s = 0;
				}
			}
			*p4 = 0;
		}
		if (p = (char*)memchr(pp.valbuf + 1, MARK, p6 - pp.valbuf - 1))
		{
			s = p;
			while (p < p6) switch (*s++ = *p++)
			{
			case 0:
				s = p;
				break;
			case MARK:
				p++;
				break;
			}
			*s = 0;
		}
		(*pp.pragma)(pp.valbuf + 1, p1, p3, p5, (pp.state & COMPILE) || (pp.mode & INIT) != 0);
		emitted = 1;
	}
	goto donedirective;

				/*INDENT*/
			}
		}
		if (*pp.control & SKIP) goto eatdirective;
		switch (directive)
		{
#if MACDEF
		case ENDMAC:
			c = pplex();
			error(2, "no matching #%s for #%s", dirname(MACDEF), dirname(ENDMAC));
			goto enddirective;
#endif
#if MACDEF
		case MACDEF:
			if ((pp.state & STRICT) && !(pp.mode & (HOSTED|RELAX)))
				error(1, "#%s: non-standard directive", pp.token);
			/*FALLTHROUGH*/
#endif
		case DEFINE:
			n2 = error_info.line;
			if ((c = pplex()) == '#' && directive == DEFINE)
				goto assertion;
			if (c == '<')
			{
				n = 1;
				c = pplex();
			}
			else
				n = 0;
			if (!(sym = macsym(c)))
				goto eatdirective;
			if (pp.truncate)
				ppfsm(FSM_MACRO, pp.token);
			mac = sym->macro;
			if ((pp.option & ALLPOSSIBLE) && !pp.in->prev->prev && mac->value)
				goto eatdirective;
			if (n)
				goto tuple;
			old = *mac;
			i0 = sym->flags;
			sym->flags &= ~(SYM_BUILTIN|SYM_EMPTY|SYM_FINAL|SYM_FUNCTION|SYM_INIT|SYM_INITIAL|SYM_MULTILINE|SYM_NOEXPAND|SYM_PREDEFINED|SYM_REDEFINE|SYM_VARIADIC);
#if MACDEF
			if (directive == MACDEF)
				sym->flags |= SYM_MULTILINE;
#endif
			mac->arity = 0;
			mac->formals = 0;
			mac->value = 0;
			pp.state &= ~NOSPACE;
			pp.state |= DEFINITION|NOEXPAND;
			switch (c = pplex())
			{
			case '(':
				sym->flags |= SYM_FUNCTION;
				pp.state |= NOSPACE;
#if MACKEYARGS
				if (pp.option & KEYARGS)
				{
					n = 2 * MAXTOKEN;
					p = mac->formals = oldof(0, char, 0, n);
					if ((c = pplex()) == T_ID) for (;;)
					{
						if (mac->arity < MAXFORMALS)
						{
							if (mac->arity) p++;
							formargs[mac->arity] = p;
							STRAPP(p, pp.token, s);
							formvals[mac->arity++] = p1 = p;
							if (mac->arity == 1) *p++ = ' ';
							*p++ = ' ';
							*p = 0;
						}
						else error(2, "%s: formal argument %s ignored", sym->name, pp.token);
						switch (c = pplex())
						{
						case '=':
							c = pplex();
							break;
						case ',':
							break;
						default:
							goto endformals;
						}
						pp.state &= ~NOSPACE;
						p0 = 0;
						for (;;)
						{
							switch (c)
							{
							case '\n':
								goto endformals;
							case '(':
								p0++;
								break;
							case ')':
								if (!p0--)
								{
									if (p > formvals[mac->arity - 1] && *(p - 1) == ' ') *--p = 0;
									goto endformals;
								}
								break;
							case ',':
								if (!p0)
								{
									if (p > formvals[mac->arity - 1] && *(p - 1) == ' ') *--p = 0;
									goto nextformal;
								}
								break;
							case ' ':
								if (p > formvals[mac->arity - 1] && *(p - 1) == ' ') continue;
								break;
							}
							STRCOPY(p, pp.token, s);
							if (p > &mac->formals[n - MAXTOKEN] && (s = newof(mac->formals, char, n += MAXTOKEN, 0)) != mac->formals)
							{
								n1 = s - mac->formals;
								for (n = 0; n < mac->arity; n++)
								{
									formargs[n] += n1;
									formvals[n] += n1;
								}
								c = p - mac->formals;
								mac->formals = s;
								p = mac->formals + c;
							}
							c = pplex();
						}
					nextformal:
						pp.state |= NOSPACE;
						if ((c = pplex()) != T_ID)
						{
							c = ',';
							break;
						}
					}
				endformals: /*NOP*/;
				}
				else
#endif
				{
					p = mac->formals = oldof(0, char, 0, MAXFORMALS * (MAXID + 1));
					c = pplex();
#if COMPATIBLE
					if ((pp.state & COMPATIBILITY) && c == ',')
					{
						if ((pp.state & WARN) && !(pp.mode & HOSTED))
							error(1, "%s: macro formal argument expected", sym->name);
						while ((c = pplex()) == ',');
					}
#endif
					for (;;)
					{
						if (c == T_VARIADIC)
						{
							if (sym->flags & SYM_VARIADIC)
								error(2, "%s: %s: duplicate macro formal argument", sym->name, pp.token);
							sym->flags |= SYM_VARIADIC;
							v = __va_args__;
						}
						else if (c == T_ID)
						{
							v = pp.token;
							if (sym->flags & SYM_VARIADIC)
								error(2, "%s: %s: macro formal argument cannot follow ...", sym->name, v);
							else if (streq(v, __va_args__))
								error(2, "%s: %s: invalid macro formal argument", sym->name, v);
						}
						else
							break;
						if (mac->arity < MAXFORMALS)
						{
							for (n = 0; n < mac->arity; n++)
								if (streq(formargs[n], v))
									error(2, "%s: %s: duplicate macro formal argument", sym->name, v);
							formargs[mac->arity++] = p;
							STRAPP(p, v, s);
						}
						else
							error(2, "%s: %s: macro formal argument ignored", sym->name, v);
						if ((c = pplex()) == ',')
						{
							c = pplex();
#if COMPATIBLE
							if ((pp.state & COMPATIBILITY) && c == ',')
							{
								if ((pp.state & WARN) && !(pp.mode & HOSTED))
									error(1, "%s: macro formal argument expected", sym->name);
								while ((c = pplex()) == ',');
							}
#endif
						}
						else if (c != T_VARIADIC)
							break;
						else
						{
							if (sym->flags & SYM_VARIADIC)
								error(2, "%s: %s: duplicate macro formal argument", sym->name, pp.token);
							sym->flags |= SYM_VARIADIC;
							c = pplex();
							break;
						}
					}
					if (mac->arity && (s = newof(mac->formals, char, p - mac->formals, 0)) != mac->formals)
					{
						n1 = s - mac->formals;
						for (n = 0; n < mac->arity; n++)
							formargs[n] += n1;
						mac->formals = s;
					}
				}
				if (!mac->arity)
				{
					free(mac->formals);
					mac->formals = 0;
				}
				switch (c)
				{
				case ')':
#if MACKEYARGS
					pp.state |= NOEXPAND|NOSPACE;
#else
					pp.state |= NOEXPAND;
#endif
					c = pplex();
					break;
				default:
					error(2, "%s: invalid macro formal argument list", sym->name);
					if (mac->formals)
					{
						free(mac->formals);
						mac->formals = 0;
						mac->arity = 0;
					}
					free(mac);
					sym->macro = 0;
					goto eatdirective;
				}
				pp.state &= ~NOSPACE;
				break;
			case ' ':
			case '\t':
				c = pplex();
				break;
			}
			n = 2 * MAXTOKEN;
#if MACKEYARGS
			p1 = p;
#endif
			p = mac->value = oldof(0, char, 0, n);
			var.type = 0;
			n1 = 0;
#if MACDEF
			i2 = i3 = 0;
			n3 = pp.state;
#endif
			if ((pp.option & PLUSPLUS) && (pp.state & (COMPATIBILITY|TRANSITION)) != COMPATIBILITY)
				switch (c)
				{
				case '+':
				case '-':
				case '&':
				case '|':
				case '<':
				case '>':
				case ':':
				case '=':
					*p++ = ' ';
					break;
				}
			o = 0;
			for (;;)
			{
				switch (c)
				{
				case T_ID:
					for (c = 0; c < mac->arity; c++)
						if (streq(formargs[c], pp.token))
						{
#if COMPATIBLE
							if (!(pp.state & COMPATIBILITY))
#endif
							if (var.type != TOK_TOKCAT && p > mac->value && *(p - 1) != ' ' && !(pp.option & PRESERVE)) *p++ = ' ';
							*p++ = MARK;
#if COMPATIBLE
							if ((pp.state & (COMPATIBILITY|TRANSITION)) == COMPATIBILITY) *p++ = 'C';
							else
#endif
							*p++ = (n1 || var.type == TOK_TOKCAT) ? 'C' : 'A';
							*p++ = c + ARGOFFSET;
							var.type = TOK_FORMAL|TOK_ID;
							c = '>';
							goto checkvalue;
						}
					if (var.type == TOK_BUILTIN) switch ((int)hashget(pp.strtab, pp.token))
					{
					case V_DEFAULT:
					case V_EMPTY:
						sym->flags |= SYM_EMPTY;
						break;
					}
					else if (pp.hiding && (var.symbol = ppsymref(pp.symtab, pp.token)) && var.symbol->hidden)
					{
						for (var.inp = pp.in; var.inp->type != IN_FILE && var.inp->prev; var.inp = var.inp->prev);
						p += sfsprintf(p, MAXTOKEN, "_%d_%s_hIDe", var.inp->hide, pp.token);
						var.type = TOK_ID;
						goto checkvalue;
					}
					var.type = TOK_ID;
					break;
				case '#':
					var.type = 0;
#if MACDEF
					if (!(sym->flags & (SYM_FUNCTION|SYM_MULTILINE))) break;
#else
					if (!(sym->flags & SYM_FUNCTION)) break;
#endif
					pp.state |= NOSPACE;
					c = pplex();
					if (c == '@')
					{
						c = pplex();
						i4 = 'S';
					}
					else i4 = 'Q';
					pp.state &= ~NOSPACE;
					if (c != T_ID) c = mac->arity;
					else for (c = 0; c < mac->arity; c++)
						if (streq(formargs[c], pp.token))
							break;
					if (c >= mac->arity)
					{
#if MACDEF
						if (sym->flags & SYM_MULTILINE)
						{
							if (n3 & NEWLINE)
							{
								pp.state &= ~NOEXPAND;
								switch ((int)hashref(pp.dirtab, pp.token))
								{
								case ENDMAC:
									if (!i2--) goto gotdefinition;
									break;
								case INCLUDE:
									/* PARSE HEADER constant */
									break;
								case MACDEF:
									i2++;
									break;
								}
								*p++ = '#';
							}
						}
						else
#endif
#if COMPATIBLE
						if (pp.state & COMPATIBILITY) *p++ = '#';
						else
#endif
						error(2, "# must precede a formal parameter");
					}
					else
					{
						if (p > mac->value && ppisidig(*(p - 1)) && !(pp.option & PRESERVE)) *p++ = ' ';
						*p++ = MARK;
						*p++ = i4;
						*p++ = c + ARGOFFSET;
						goto checkvalue;
					}
					break;
				case T_TOKCAT:
					if (p <= mac->value) error(2, "%s lhs operand omitted", pp.token);
					else
					{
						if (*(p - 1) == ' ') p--;
						if (var.type == (TOK_FORMAL|TOK_ID)) *(p - 2) = 'C';
					}
					pp.state |= NOSPACE;
					c = pplex();
					pp.state &= ~NOSPACE;
					if (c == '\n') error(2, "%s rhs operand omitted", pptokchr(T_TOKCAT));
					var.type = TOK_TOKCAT;
					continue;
				case '(':
					if (*pp.token == '#')
					{
						var.type = TOK_BUILTIN;
						n1++;
					}
					else
					{
						var.type = 0;
						if (n1) n1++;
					}
					break;
				case ')':
					var.type = 0;
					if (n1) n1--;
					break;
				case T_STRING:
				case T_CHARCONST:
					pp.state &= ~NOEXPAND;
					var.type = 0;
					if (strchr(pp.token, MARK)) pp.state &= ~NOEXPAND;
#if COMPATIBLE
					/*UNDENT*/

	if ((sym->flags & SYM_FUNCTION) && (pp.state & (COMPATIBILITY|TRANSITION)))
	{
		char*	v;

		s = pp.token;
		for (;;)
		{
			if (!*s) goto checkvalue;
			if (ppisid(*s))
			{
				v = s;
				while (ppisid(*++s));
				i1 = *s;
				*s = 0;
				for (c = 0; c < mac->arity; c++)
					if (streq(formargs[c], v))
					{
						*p++ = MARK;
						*p++ = 'C';
						*p++ = c + ARGOFFSET;
						if (!(pp.mode & HOSTED) && (!(pp.state & COMPATIBILITY) || (pp.state & WARN))) switch (*pp.token)
						{
						case '"':
							error(1, "use the # operator to \"...\" quote macro arguments");
							break;
						case '\'':
							error(1, "macro arguments should be '...' quoted before substitution");
							break;
						}
						goto quotearg;
					}
				STRCOPY2(p, v);
			quotearg:
				*s = i1;
			}
			else *p++ = *s++;
		}
	}
					/*INDENT*/
#endif
					break;
				case '\n':
#if MACDEF
					if (sym->flags & SYM_MULTILINE)
					{
						if (pp.state & EOF2NL)
						{
							error_info.line++;
							pp.state |= HIDDEN;
							pp.hidden++;
							var.type = 0;
							if (!i3++)
								goto checkvalue;
							break;
						}
						pp.state |= EOF2NL;
						error(2, "%s: missing #%s", sym->name, dirname(ENDMAC));
					}
#endif
					goto gotdefinition;
				case 0:
					c = '\n';
					goto gotdefinition;
#if COMPATIBLE
				case ' ':
					if (pp.state & COMPATIBILITY) var.type = 0;
					if (pp.option & PRESERVE) break;
					if (p > mac->value && *(p - 1) != ' ') *p++ = ' ';
					goto checkvalue;
				case '\t':
					if (var.type & TOK_ID)
					{
						while ((c = pplex()) == '\t');
						if (c == T_ID)
						{
							if (var.type == (TOK_FORMAL|TOK_ID)) *(p - 2) = 'C';
							var.type = TOK_TOKCAT;
							if (pp.state & WARN) error(1, "use the ## operator to concatenate macro arguments");
						}
						else var.type = 0;
						continue;
					}
					var.type = 0;
					if (pp.option & PRESERVE) break;
					if (p > mac->value && *(p - 1) != ' ') *p++ = ' ';
					goto checkvalue;
#endif
				case MARK:
					pp.state &= ~NOEXPAND;
					/*FALLTHROUGH*/

				default:
					var.type = 0;
					break;
				}
				STRCOPY(p, pp.token, s);
			checkvalue:
				o = c;
				if (p > &mac->value[n - MAXTOKEN] && (s = newof(mac->value, char, n += MAXTOKEN, 0)) != mac->value)
				{
					c = p - mac->value;
					mac->value = s;
					p = mac->value + c;
				}
#if MACDEF
				n3 = pp.state;
#endif
				c = pplex();
			}
		gotdefinition:
			while (p > mac->value && *(p - 1) == ' ') p--;
			if (p > mac->value && (pp.option & PLUSPLUS) && (pp.state & (COMPATIBILITY|TRANSITION)) != COMPATIBILITY)
				switch (o)
				{
				case '+':
				case '-':
				case '&':
				case '|':
				case '<':
				case '>':
				case ':':
				case '=':
					*p++ = ' ';
					break;
				}
			*p = 0;
#if MACKEYARGS
			if (!mac->arity) /* ok */;
			else if (pp.option & KEYARGS)
			{
				p0 = mac->formals;
				mac->formkeys = newof(0, struct ppkeyarg, n, p1 - p0 + 1);
				s = (char*)&mac->formkeys[mac->arity];
				(void)memcpy(s, p0, p1 - p0 + 1);
				free(p0);
				for (n = 0; n < mac->arity; n++)
				{
					mac->formkeys[n].name = s + (formargs[n] - p0);
					mac->formkeys[n].value = s + (formvals[n] - p0);
				}
			}
			else
#endif
			for (n = 1; n < mac->arity; n++)
				*(formargs[n] - 1) = ',';
			if (old.value)
			{
				if ((i0 & SYM_FUNCTION) != (sym->flags & SYM_FUNCTION) || old.arity != mac->arity || !streq(old.value, mac->value)) goto redefined;
				if (!old.formals)
				{
					if (mac->formals) goto redefined;
				}
				else if (mac->formals)
				{
#if MACKEYARGS
					if (pp.option & KEYARGS)
					{
						for (n = 0; n < mac->arity; n++)
							if (!streq(mac->formkeys[n].name, old.formkeys[n].name) || !streq(mac->formkeys[n].value, old.formkeys[n].value))
								goto redefined;
					}
					else
#endif
					if (!streq(mac->formals, old.formals)) goto redefined;
				}
#if MACKEYARGS
				if (pp.option & KEYARGS)
				{
					if (mac->formkeys) free(mac->formkeys);
					mac->formkeys = old.formkeys;
				}
				else
#endif
				{
					if (mac->formals) free(mac->formals);
					mac->formals = old.formals;
				}
				free(mac->value);
				mac->value = old.value;
				goto benign;
			redefined:
				if (!(pp.mode & HOSTED) || !(i0 & SYM_INITIAL))
					error(1, "%s redefined", sym->name);
#if MACKEYARGS
				if ((pp.option & KEYARGS) && mac->formkeys)
					free(mac->formkeys);
#endif
#if MACKEYARGS
				if (!(pp.option & KEYARGS))
#endif
				if (old.formals) free(old.formals);
				free(old.value);
			}
			else if (!pp.truncate) ppfsm(FSM_MACRO, sym->name);
			mac->value = newof(mac->value, char, (mac->size = p - mac->value) + 1, 0);
			if ((pp.option & (DEFINITIONS|PREDEFINITIONS|REGUARD)) && !sym->hidden && !(sym->flags & SYM_MULTILINE) && ((pp.option & PREDEFINITIONS) || !(pp.mode & INIT)) && ((pp.option & (DEFINITIONS|PREDEFINITIONS)) || !(pp.state & NOTEXT)))
			{
				ppsync();
				ppprintf("#%s %s", dirname(DEFINE), sym->name);
				if (sym->flags & SYM_FUNCTION)
				{
					ppputchar('(');
					if (mac->formals)
						ppprintf("%s", mac->formals);
					ppputchar(')');
				}
				if ((p = mac->value) && *p)
				{
					ppputchar(' ');
					i0 = 0;
					while (n = *p++)
					{
						if (n != MARK || (n = *p++) == MARK)
						{
							ppputchar(n);
							i0 = ppisid(n);
						}
						else
						{
							if (n == 'Q')
								ppputchar('#');
							else if (i0)
							{
								ppputchar('#');
								ppputchar('#');
							}
							s = formargs[*p++ - ARGOFFSET];
							while ((n = *s++) && n != ',')
								ppputchar(n);
							if (ppisid(*p) || *p == MARK)
							{
								ppputchar('#');
								ppputchar('#');
							}
							i0 = 0;
						}
						ppcheckout();
					}
				}
				emitted = 1;
			}
		benign:
			if (pp.mode & BUILTIN) sym->flags |= SYM_BUILTIN;
			if (pp.option & FINAL) sym->flags |= SYM_FINAL;
			if (pp.mode & INIT) sym->flags |= SYM_INIT;
			if (pp.option & INITIAL) sym->flags |= SYM_INITIAL;
			if (pp.state & NOEXPAND)  sym->flags |= SYM_NOEXPAND;
			if (pp.option & PREDEFINED) sym->flags |= SYM_PREDEFINED;
			if (pp.mode & READONLY) sym->flags |= SYM_READONLY;
			if (pp.macref) (*pp.macref)(sym, error_info.file, n2, mac ? error_info.line - n2 + 1 : REF_UNDEF, mac ? strsum(mac->value, (long)mac->arity) : 0L);
			break;
		assertion:
			c = pplex();
			if ((pp.state & STRICT) && !(pp.mode & (HOSTED|RELAX)))
				error(1, "#%s #%s: assertions are non-standard", dirname(directive), pptokstr(pp.token, 0));
			if (c != T_ID)
			{
				error(2, "%s: invalid predicate name", pptokstr(pp.token, 0));
				goto eatdirective;
			}
			switch ((int)hashref(pp.strtab, pp.token))
			{
			case X_DEFINED:
			case X_EXISTS:
			case X_STRCMP:
				error(2, "%s is a builtin predicate", pp.token);
				goto eatdirective;
			case X_SIZEOF:
				error(2, "%s cannot be a predicate", pp.token);
				goto eatdirective;
			}
			strcpy(pp.tmpbuf, pp.token);
			switch (pppredargs())
			{
			case T_ID:
			case T_STRING:
				assert(directive, pp.tmpbuf, pp.args);
				break;
			case 0:
				assert(directive, pp.tmpbuf, NiL);
				break;
			default:
				error(2, "invalid predicate argument list");
				goto eatdirective;
			}
			break;
		tuple:
			pp.state |= DEFINITION|NOEXPAND|NOSPACE;
			rp = 0;
			tp = mac->tuple;
			if (!tp && !mac->value)
				ppfsm(FSM_MACRO, sym->name);
			while ((c = pplex()) && c != '>' && c != '\n')
			{
				for (; tp; tp = tp->nomatch)
					if (streq(tp->token, pp.token))
						break;
				if (!tp)
				{
					if (!(tp = newof(0, struct pptuple, 1, strlen(pp.token))))
						error(3, "out of space");
					strcpy(tp->token, pp.token);
					if (rp)
					{
						tp->nomatch = rp;
						rp->nomatch = tp;
					}
					else
					{
						tp->nomatch = mac->tuple;
						mac->tuple = tp;
					}
				}
				rp = tp;
				tp = tp->match;
			}
			pp.state &= ~NOSPACE;
			if (!rp || c != '>')
				error(2, "%s: > omitted in tuple macro definition", sym->name);
			else
			{
				n = 2 * MAXTOKEN;
				p = v = oldof(0, char, 0, n);
				while ((c = pplex()) && c != '\n')
					if (p > v || c != ' ')
					{
						STRCOPY(p, pp.token, s);
						if (p > &v[n - MAXTOKEN] && (s = newof(v, char, n += MAXTOKEN, 0)) != v)
						{
							c = p - v;
							v = s;
							p = v + c;
						}
					}
				while (p > v && *(p - 1) == ' ')
					p--;
				n = p - v;
				tp = newof(0, struct pptuple, 1, n);
				strcpy(tp->token, v);
				tp->match = rp->match;
				rp->match = tp;
			}
			goto benign;
		case WARNING:
			if ((pp.state & STRICT) && !(pp.mode & (HOSTED|RELAX)))
				error(1, "#%s: non-standard directive", pp.token);
			/*FALLTHROUGH*/
		case ERROR:
			pp.state &= ~DISABLE;
			p = pp.tmpbuf;
			while ((c = pplex()) != '\n')
				if (p + strlen(pp.token) < &pp.tmpbuf[MAXTOKEN])
				{
					STRCOPY(p, pp.token, s);
					pp.state &= ~NOSPACE;
				}
			*p = 0;
			p = *pp.tmpbuf ? pp.tmpbuf : ((directive == WARNING) ? "user warning" : "user error");
			n = (directive == WARNING) ? 1 : 3;
			error(n, "%s", p);
			break;
		case LET:
			n2 = error_info.line;
			if ((pp.state & STRICT) && !(pp.mode & (HOSTED|RELAX)))
				error(1, "#%s: non-standard directive", pp.token);
			if (!(sym = macsym(c = pplex()))) goto eatdirective;
			if ((c = pplex()) != '=')
			{
				error(2, "%s: = expected", sym->name);
				goto eatdirective;
			}
			sym->flags &= ~(SYM_BUILTIN|SYM_FUNCTION|SYM_MULTILINE|SYM_PREDEFINED|SYM_VARIADIC);
			mac = sym->macro;
			mac->arity = 0;
			if (mac->value)
			{
				if (!(sym->flags & SYM_REDEFINE) && !sym->hidden)
					error(1, "%s: redefined", sym->name);
#if MACKEYARGS
				if ((pp.option & KEYARGS) && mac->formkeys) free(mac->formkeys);
				else
#endif
				free(mac->formals);
				mac->formals = 0;
				n = strlen(mac->value) + 1;
			}
			else
			{
				ppfsm(FSM_MACRO, sym->name);
				n = 0;
			}
			n1 = ppexpr(&i1);
			if (i1) c = sfsprintf(pp.tmpbuf, MAXTOKEN, "%luU", n1);
			else c = sfsprintf(pp.tmpbuf, MAXTOKEN, "%ld", n1);
			if (n < ++c)
			{
				if (mac->value) free(mac->value);
				mac->value = oldof(0, char, 0, c);
			}
			strcpy(mac->value, pp.tmpbuf);
			sym->flags |= SYM_REDEFINE;
			c = (pp.state & NEWLINE) ? '\n' : ' ';
			goto benign;
		case LINE:
			pp.state &= ~DISABLE;
			if ((c = pplex()) == '#')
			{
				c = pplex();
				directive = INCLUDE;
			}
			if (c != T_DECIMAL && c != T_OCTAL)
			{
				error(1, "#%s: line number expected", dirname(LINE));
				goto eatdirective;
			}
		linesync:
			n = error_info.line;
			error_info.line = strtol(pp.token, NiL, 0);
			if (error_info.line == 0 && directive == LINE && (pp.state & STRICT) && !(pp.mode & HOSTED))
				error(1, "#%s: line number should be > 0", dirname(LINE));
			pp.state &= ~DISABLE;
			pp.state |= STRIP;
			switch (c = pplex())
			{
			case T_STRING:
				s = error_info.file;
				if (*(p = pp.token)) pathcanon(p, 0);
				fp = ppsetfile(p);
				error_info.file = fp->name;
				if (error_info.line == 1)
					ppmultiple(fp, INC_TEST);
				switch (c = pplex())
				{
				case '\n':
					break;
				case T_DECIMAL:
				case T_OCTAL:
					if (directive == LINE && (pp.state & STRICT) && !(pp.mode & (HOSTED|RELAX)))
						error(1, "#%s: integer file type argument is non-standard", dirname(LINE));
					break;
				default:
					error(1, "#%s: integer file type argument expected", dirname(LINE));
					break;
				}
				if (directive == LINE) pp.in->flags &= ~IN_ignoreline;
				else if (pp.incref)
				{
					if (error_info.file != s)
					{
						switch (*pp.token)
						{
						case PP_sync_push:
							if (pp.insert) (*pp.incref)(s, error_info.file, n, PP_SYNC_INSERT);
							else (*pp.incref)(s, error_info.file, n, PP_SYNC_PUSH);
							break;
						case PP_sync_pop:
							if (pp.insert) (*pp.incref)(s, error_info.file, n, PP_SYNC_INSERT);
							else (*pp.incref)(s, error_info.file, n - 1, PP_SYNC_POP);
							break;
						case PP_sync_ignore:
							if (pp.insert) (*pp.incref)(s, error_info.file, n, PP_SYNC_INSERT);
							else
							{
								(*pp.incref)(s, error_info.file, n, PP_SYNC_IGNORE);
								error_info.file = s;
							}
							break;
						default:
							if (*s)
							{
								if (fp == pp.insert)
									pp.insert = 0;
								else if (error_info.line == 1 && !pp.insert)
									(*pp.incref)(s, error_info.file, n, PP_SYNC_PUSH);
								else
								{
									if (!pp.insert) pp.insert = ppgetfile(s);
									(*pp.incref)(s, error_info.file, n, PP_SYNC_INSERT);
								}
							}
							break;
						}
					}
				}
				break;
			case '\n':
				break;
			default:
				error(1, "#%s: \"file-name\" expected", dirname(LINE));
				break;
			}
			if (directive == LINE && (pp.in->flags & IN_ignoreline))
				error_info.line = n + 1;
			else
			{
				pp.hidden = 0;
				pp.state &= ~HIDDEN;
				if (pp.linesync)
				{
#if CATSTRINGS
					if (pp.state & JOINING) pp.state |= HIDDEN|SYNCLINE;
					else
#endif
					{
						s = pp.lineid;
						n = pp.flags;
						if (directive == LINE)
						{
							pp.flags &= ~PP_linetype;
							if (pp.macref) pp.lineid = dirname(LINE);
						}
						(*pp.linesync)(error_info.line, error_info.file);
						pp.flags = n;
						pp.lineid = s;
					}
				}
			}
			directive = LINE;
			break;
		case PRAGMA:
			/*
			 * #pragma [STDC] [pass:] [no]option [arg ...]
			 *
			 * pragma args are not expanded by default
			 *
			 * if STDC is present then it is silently passed on
			 *
			 * if pass is pp.pass then the option is used
			 * and verified but is not passed on
			 *
			 * if pass is omitted then the option is passed on
			 *
			 * otherwise if pass is non-null and not pp.pass then
			 * the option is passed on but not used
			 *
			 * if the line does not match this form then
			 * it is passed on unchanged
			 *
			 *	#directive   pass:  option  [...]
			 *	^         ^  ^   ^  ^     ^  ^   ^
			 *	pp.valbuf p0 p1  p2 p3    p4 p5  p6
			 *
			 * p?	0 if component omitted
			 * i0	0 if ``no''option
			 */

			p = pp.valbuf;
			*p++ = '#';
			STRCOPY(p, pp.token, s);
			p0 = p;
			if (pp.option & PRAGMAEXPAND)
				pp.state &= ~DISABLE;
			if (!(p6 = getline(p, &pp.valbuf[MAXTOKEN], !!(pp.option & PRAGMAEXPAND))))
			{
				*p0 = 0;
				error(2, "%s: directive too long", pp.valbuf);
				c = 0;
				goto eatdirective;
			}
			p1 = ++p;
			while (ppisid(*p))
				p++;
			if (p == p1)
			{
				p5 = p;
				p4 = 0;
				p3 = 0;
				p2 = 0;
				p1 = 0;
			}
			else if (*p != ':')
			{
				p5 = *p ? p + (*p == ' ') : 0;
				p4 = p;
				p3 = p1;
				p2 = 0;
				p1 = 0;
			}
			else
			{
				p2 = p++;
				p3 = p;
				while (ppisid(*p))
					p++;
				if (p == p3)
				{
					p4 = p1;
					p3 = 0;
					p2 = 0;
					p1 = 0;
				}
				else
					p4 = p;
				p5 = *p4 ? p4 + (*p4 == ' ') : 0;
			}
			if (!p1 && p3 && (p4 - p3) == 4 && strneq(p3, "STDC", 4))
				goto pass;
			if ((pp.state & WARN) && !(pp.mode & (HOSTED|RELAX)))
				error(1, "#%s: non-standard directive", dirname(PRAGMA));
			i0 = !p3 || *p3 != 'n' || *(p3 + 1) != 'o';
			if (!p3)
				goto checkmap;
			if (p1)
			{
				*p2 = 0;
				n = streq(p1, pp.pass);
				*p2 = ':';
				if (!n)
					goto checkmap;
			}
			else
				n = 0;
			i2 = *p4;
			*p4 = 0;
			if (((i1 = (int)hashref(pp.strtab, p3 + (i0 ? 0 : 2))) < 1 || i1 > X_last_option) && (i0 || (i1 = (int)hashref(pp.strtab, p3)) > X_last_option))
				i1 = 0;
			if ((pp.state & (COMPATIBILITY|STRICT)) == STRICT && !(pp.mode & (HOSTED|RELAX)))
			{
				if (pp.optflags[i1] & OPT_GLOBAL)
					goto donedirective;
				if (n || (pp.mode & WARN))
				{
					n = 0;
					error(1, "#%s: non-standard directive ignored", dirname(PRAGMA));
				}
				i1 = 0;
			}
			if (!n)
			{
				if (!(pp.optflags[i1] & OPT_GLOBAL))
				{
					*p4 = i2;
					goto checkmap;
				}
				if (!(pp.optflags[i1] & OPT_PASS))
					n = 1;
			}
			else if (!i1)
				error(2, "%s: unknown option", p1);
			else if ((pp.state & STRICT) && !(pp.mode & (HOSTED|RELAX)))
				error(1, "%s: non-standard option", p1);
			p = p5;
			switch (i1)
			{
			case X_ALLMULTIPLE:
				ppop(PP_MULTIPLE, i0);
				break;
			case X_ALLPOSSIBLE:
				setoption(ALLPOSSIBLE, i0);
				break;
			case X_BUILTIN:
				setmode(BUILTIN, i0);
				break;
			case X_CATLITERAL:
				setmode(CATLITERAL, i0);
				if (pp.mode & CATLITERAL)
					setoption(STRINGSPLIT, 0);
				break;
			case X_CDIR:
				tokop(PP_CDIR, p3, p, i0, TOKOP_UNSET|TOKOP_STRING|TOKOP_DUP);
				break;
			case X_CHECKPOINT:
#if CHECKPOINT
				ppload(p);
#else
				error(3, "%s: preprocessor not compiled with checkpoint enabled", p3);
#endif
				break;
			case X_CHOP:
				tokop(PP_CHOP, p3, p, i0, TOKOP_UNSET|TOKOP_STRING);
				break;
			case X_COMPATIBILITY:
				ppop(PP_COMPATIBILITY, i0);
				break;
			case X_DEBUG:
				error_info.trace = i0 ? (p ? -strtol(p, NiL, 0) : -1) : 0;
				break;
			case X_ELSEIF:
				setoption(ELSEIF, i0);
				break;
			case X_FINAL:
				setoption(FINAL, i0);
				break;
			case X_HEADEREXPAND:
				setoption(HEADEREXPAND, i0);
				break;
			case X_HEADEREXPANDALL:
				setoption(HEADEREXPANDALL, i0);
				break;
			case X_HIDE:
			case X_NOTE:
				PUSH_LINE(p);
				/* UNDENT...*/
	while (c = pplex())
	{
		if (c != T_ID) error(1, "%s: %s: identifier expected", p3, pp.token);
		else if (sym = ppsymset(pp.symtab, pp.token))
		{
			if (i1 == X_NOTE)
			{
				sym->flags &= ~SYM_NOTICED;
				ppfsm(FSM_MACRO, sym->name);
			}
			else if (i0)
			{
				if (!sym->hidden && !(sym->hidden = newof(0, struct pphide, 1, 0)))
					error(3, "out of space");
				if (!sym->macro)
					ppfsm(FSM_MACRO, sym->name);
				if (!sym->hidden->level++)
				{
					pp.hiding++;
					if (sym->macro && !(sym->flags & (SYM_ACTIVE|SYM_READONLY)))
					{
						sym->hidden->macro = sym->macro;
						sym->macro = 0;
						sym->hidden->flags = sym->flags;
						sym->flags &= ~(SYM_BUILTIN|SYM_FUNCTION|SYM_INIT|SYM_MULTILINE|SYM_PREDEFINED|SYM_REDEFINE|SYM_VARIADIC);
					}
				}
			}
			else if (sym->hidden)
			{
				if ((mac = sym->macro) && !(sym->flags & (SYM_ACTIVE|SYM_READONLY)))
				{
					if (mac->formals) free(mac->formals);
					free(mac->value);
					free(mac);
					sym->macro = 0;
					sym->flags &= ~(SYM_BUILTIN|SYM_FUNCTION|SYM_INIT|SYM_MULTILINE|SYM_PREDEFINED|SYM_REDEFINE|SYM_VARIADIC);
				}
				if (!--sym->hidden->level)
				{
					pp.hiding--;
					if (sym->hidden->macro)
					{
						sym->macro = sym->hidden->macro;
						sym->flags = sym->hidden->flags;
					}
					free(sym->hidden);
					sym->hidden = 0;
				}
			}
		}
	}
				/*...INDENT*/
				POP_LINE();
				break;
			case X_HOSTDIR:
				tokop(PP_HOSTDIR, p3, p, i0, TOKOP_UNSET|TOKOP_STRING|TOKOP_DUP);
				break;
			case X_HOSTED:
				setmode(HOSTED, i0);
				break;
			case X_HOSTEDTRANSITION:
				setmode(HOSTEDTRANSITION, i0);
				break;
			case X_ID:
				tokop(PP_ID, p3, p, i0, TOKOP_UNSET|TOKOP_STRING);
				break;
			case X_IGNORE:
				tokop(PP_IGNORE, p3, p, i0, TOKOP_UNSET|TOKOP_STRING);
				break;
			case X_INCLUDE:
				tokop(PP_INCLUDE, p3, p, i0, TOKOP_STRING|TOKOP_DUP);
				break;
			case X_INITIAL:
				setoption(INITIAL, i0);
				break;
			case X_KEYARGS:
				ppop(PP_KEYARGS, i0);
				break;
			case X_LINE:
				if (pp.linesync) pp.olinesync = pp.linesync;
				pp.linesync = i0 ? pp.olinesync : (PPLINESYNC)0;
				break;
			case X_LINEBASE:
				ppop(PP_LINEBASE, i0);
				break;
			case X_LINEFILE:
				ppop(PP_LINEFILE, i0);
				break;
			case X_LINEID:
				ppop(PP_LINEID, i0 ? p : (char*)0);
				break;
			case X_LINETYPE:
				ppop(PP_LINETYPE, i0 ? (p ? strtol(p, NiL, 0) : 1) : 0);
				break;
			case X_MACREF:
				if (!p)
				{
					if (i0 && !pp.macref)
					{
						ppop(PP_LINETYPE, 1);
						ppop(PP_MACREF, ppmacref);
					}
					else error(2, "%s: option cannot be unset", p3);
				}
				else if (s = strchr(p, ' '))
				{
					if (pp.macref && (s = strchr(p, ' ')))
					{
						*s++ = 0;
						c = strtol(s, NiL, 0);
						var.type = pp.truncate;
						pp.truncate = PPTOKSIZ;
						(*pp.macref)(pprefmac(p, REF_CREATE), error_info.file, error_info.line - (c == REF_NORMAL ? 2 : 1), c, (s = strchr(s, ' ')) ? strtol(s, NiL, 0) : 0L);
						pp.truncate = var.type;
					}
					error_info.line -= 2;
				}
				break;
			case X_MAP:
				/*UNDENT*/
	/*
	 * #pragma pp:map [id ...] "/from/[,/to/]" [ "/old/new/[glnu]" ... ]
	 */
	
	if (!i0)
	{
		error(2, "%s: option cannot be unset", p3);
		goto donedirective;
	}
	if (!p5)
	{
		error(2, "%s: address argument expected", p3);
		goto donedirective;
	}
	PUSH_LINE(p5);
	while ((c = pplex()) == T_ID)
	{
		sfsprintf(pp.tmpbuf, MAXTOKEN, "__%s__", s = pp.token);
		if (c = (int)hashget(pp.dirtab, s))
		{
			hashput(pp.dirtab, 0, 0);
			hashput(pp.dirtab, pp.tmpbuf, c);
		}
		if (c = (int)hashget(pp.strtab, s))
		{
			hashput(pp.strtab, 0, 0);
			hashput(pp.strtab, pp.tmpbuf, c);
		}
	}
	if (c != T_STRING || !*(s = pp.token))
	{
		if (c)
			error(2, "%s: %s: address argument expected", p3, pptokstr(pp.token, 0));
		goto eatmap;
	}
	map = newof(0, struct map, 1, 0);
	
	/*
	 * /from/
	 */
	
	if (i0 = regcomp(&map->re, s, REG_AUGMENTED|REG_DELIMITED|REG_LENIENT|REG_NULL))
		regfatal(&map->re, 3, i0);
	if (*(s += map->re.re_npat))
	{
		error(2, "%s: invalid characters after pattern: %s ", p3, s);
		goto eatmap;
	}

	/*
	 * /old/new/[flags]
	 */
	
	edit = 0;
	while ((c = pplex()) == T_STRING)
	{
		if (!*(s = pp.token))
		{
			error(2, "%s: substitution argument expected", p3);
			goto eatmap;
		}
		if (edit)
			edit = edit->next = newof(0, struct edit, 1, 0);
		else
			edit = map->edit = newof(0, struct edit, 1, 0);
		if (!(i0 = regcomp(&edit->re, s, REG_AUGMENTED|REG_DELIMITED|REG_LENIENT|REG_NULL)) && !(i0 = regsubcomp(&edit->re, s += edit->re.re_npat, NiL, 0, 0)))
			s += edit->re.re_npat;
		if (i0)
			regfatal(&edit->re, 3, i0);
		if (*s)
		{
			error(2, "%s: invalid characters after substitution: %s ", p3, s);
			goto eatmap;
		}
	}
	if (c)
	{
		error(2, "%s: %s: substitution argument expected", p3, pptokstr(pp.token, 0));
		goto eatmap;
	}
	map->next = (struct map*)pp.maps;
	pp.maps = (char*)map;
 eatmap:
	POP_LINE();
				/*INDENT*/
				break;
			case X_MAPINCLUDE:
				ppmapinclude(NiL, p5);
				break;
			case X_MODERN:
				setoption(MODERN, i0);
				break;
			case X_MULTIPLE:
				n = 1;
				if (pp.in->type == IN_FILE)
					ppmultiple(ppsetfile(error_info.file), i0 ? INC_CLEAR : INC_TEST);
				break;
			case X_NATIVE:
				setoption(NATIVE, i0);
				break;
			case X_OPSPACE:
				ppfsm(FSM_OPSPACE, i0 ? p4 : (char*)0);
				break;
			case X_PASSTHROUGH:
				ppop(PP_PASSTHROUGH, i0);
				break;
			case X_PEDANTIC:
				ppop(PP_PEDANTIC, i0);
				break;
			case X_PLUSCOMMENT:
				ppop(PP_PLUSCOMMENT, i0);
				break;
			case X_PLUSPLUS:
				ppop(PP_PLUSPLUS, i0);
				break;
			case X_PLUSSPLICE:
				setoption(PLUSSPLICE, i0);
				break;
			case X_PRAGMAEXPAND:
				setoption(PRAGMAEXPAND, i0);
				break;
			case X_PRAGMAFLAGS:
				tokop(PP_PRAGMAFLAGS, p3, p, i0, 0);
				break;
			case X_PREDEFINED:
				setoption(PREDEFINED, i0);
				break;
			case X_PREFIX:
				setoption(PREFIX, i0);
				break;
			case X_PRESERVE:
				setoption(PRESERVE, i0);
				if (pp.option & PRESERVE)
				{
					setmode(CATLITERAL, 0);
					ppop(PP_COMPATIBILITY, 1);
					ppop(PP_TRANSITION, 0);
					ppop(PP_PLUSCOMMENT, 1);
					ppop(PP_SPACEOUT, 1);
					setoption(STRINGSPAN, 1);
					setoption(STRINGSPLIT, 0);
					ppop(PP_HOSTDIR, "-", 1);
				}
				break;
			case X_PROTOTYPED:
				/*
				 * this option doesn't bump the token count
				 */

				n = 1;
				directive = ENDIF;
#if PROTOTYPE
				setoption(PROTOTYPED, i0);
#else
				error(1, "preprocessor not compiled with prototype conversion enabled");
#endif
				break;
			case X_PROTO:
				setoption(NOPROTO, !i0);
				break;
			case X_QUOTE:
				tokop(PP_QUOTE, p3, p, i0, TOKOP_UNSET|TOKOP_STRING);
				break;
			case X_READONLY:
				setmode(READONLY, i0);
				break;
			case X_REGUARD:
				setoption(REGUARD, i0);
				break;
			case X_RESERVED:
				tokop(PP_RESERVED, p3, p, i0, 0);
				break;
			case X_SPACEOUT:
				if (!(pp.state & (COMPATIBILITY|COMPILE)))
					ppop(PP_SPACEOUT, i0);
				break;
			case X_SPLICECAT:
				setoption(SPLICECAT, i0);
				break;
			case X_SPLICESPACE:
				setoption(SPLICESPACE, i0);
				break;
			case X_STANDARD:
				tokop(PP_STANDARD, p3, p, i0, TOKOP_UNSET|TOKOP_STRING|TOKOP_DUP);
				break;
			case X_STRICT:
				ppop(PP_STRICT, i0);
				break;
			case X_STRINGSPAN:
				setoption(STRINGSPAN, i0);
				break;
			case X_STRINGSPLIT:
				setoption(STRINGSPLIT, i0);
				if (pp.option & STRINGSPLIT)
					setmode(CATLITERAL, 0);
				break;
			case X_SYSTEM_HEADER:
				if (i0)
				{
					pp.mode |= HOSTED;
					pp.flags |= PP_hosted;
					pp.in->flags |= IN_hosted;
				}
				else
				{
					pp.mode &= ~HOSTED;
					pp.flags &= ~PP_hosted;
					pp.in->flags &= ~PP_hosted;
				}
				break;
			case X_TEST:
				ppop(PP_TEST, p);
				break;
			case X_TEXT:
				if (!(pp.option & KEEPNOTEXT))
					setstate(NOTEXT, !i0);
				break;
			case X_TRANSITION:
				ppop(PP_TRANSITION, i0);
				if (pp.state & TRANSITION) ppop(PP_COMPATIBILITY, i0);
				break;
			case X_TRUNCATE:
				ppop(PP_TRUNCATE, i0 ? (p ? strtol(p, NiL, 0) : TRUNCLENGTH) : 0);
				break;
			case X_VENDOR:
				tokop(PP_VENDOR, p3, p, i0, TOKOP_UNSET|TOKOP_STRING|TOKOP_DUP);
				break;
			case X_VERSION:
				if (!(*pp.control & SKIP) && pp.pragma && !(pp.state & NOTEXT))
				{
					sfsprintf(pp.tmpbuf, MAXTOKEN, "\"%s\"", pp.version);
					(*pp.pragma)(dirname(PRAGMA), pp.pass, p3, pp.tmpbuf, !n);
					if (pp.linesync && !n)
						(*pp.linesync)(error_info.line, error_info.file);
					emitted = 1;
				}
				break;
			case X_WARN:
				ppop(PP_WARN, i0);
				break;
			case X_ZEOF:
				setoption(ZEOF, i0);
				break;
#if DEBUG
			case 0:
			case X_INCLUDED:
			case X_NOTICED:
			case X_OPTION:
			case X_STATEMENT:
				break;
			default:
				error(PANIC, "%s: option recognized but not implemented", pp.valbuf);
				break;
#endif
			}
			*p4 = i2;
			if (!n)
				goto checkmap;
			goto donedirective;
		case RENAME:
			if ((pp.state & STRICT) && !(pp.mode & (HOSTED|RELAX)))
				error(1, "#%s: non-standard directive", pp.token);
			if ((c = pplex()) != T_ID)
			{
				error(1, "%s: invalid macro name", pptokstr(pp.token, 0));
				goto eatdirective;
			}
			if (!(sym = pprefmac(pp.token, REF_DELETE)) || !sym->macro)
				goto eatdirective;
			if (sym->flags & (SYM_ACTIVE|SYM_READONLY))
			{
				if (!(pp.option & ALLPOSSIBLE))
					error(2, "%s: macro is %s", sym->name, (sym->flags & SYM_READONLY) ? "readonly" : "active");
				goto eatdirective;
			}
			if ((c = pplex()) != T_ID)
			{
				error(1, "%s: invalid macro name", pptokstr(pp.token, 0));
				goto eatdirective;
			}
			var.symbol = pprefmac(pp.token, REF_CREATE);
			if (mac = var.symbol->macro)
			{
				if (var.symbol->flags & (SYM_ACTIVE|SYM_READONLY))
				{
					if (!(pp.option & ALLPOSSIBLE))
						error(2, "%s: macro is %s", var.symbol->name, (var.symbol->flags & SYM_READONLY) ? "readonly" : "active");
					goto eatdirective;
				}
				if (!(pp.mode & HOSTED) || !(var.symbol->flags & SYM_INITIAL))
					error(1, "%s redefined", var.symbol->name);
				if (mac->formals) free(mac->formals);
				free(mac->value);
				free(mac);
			}
			ppfsm(FSM_MACRO, var.symbol->name);
			var.symbol->flags = sym->flags;
			sym->flags &= ~(SYM_BUILTIN|SYM_FUNCTION|SYM_INIT|SYM_MULTILINE|SYM_PREDEFINED|SYM_REDEFINE|SYM_VARIADIC);
			var.symbol->macro = sym->macro;
			sym->macro = 0;
			break;
		case UNDEF:
			if ((c = pplex()) != T_ID)
			{
				error(1, "%s: invalid macro name", pptokstr(pp.token, 0));
				goto eatdirective;
			}
			if (sym = pprefmac(pp.token, REF_DELETE))
			{
				if (mac = sym->macro)
				{
					if (sym->flags & (SYM_ACTIVE|SYM_READONLY))
					{
						if (!(pp.option & ALLPOSSIBLE))
							error(2, "%s: macro is %s", sym->name, (sym->flags & SYM_READONLY) ? "readonly" : "active");
						goto eatdirective;
					}
					if (mac->formals) free(mac->formals);
					free(mac->value);
					free(mac);
					mac = sym->macro = 0;
				}
				if ((pp.option & (DEFINITIONS|PREDEFINITIONS|REGUARD)) && !sym->hidden && !(sym->flags & SYM_MULTILINE) && ((pp.option & PREDEFINITIONS) || !(pp.mode & INIT)) && ((pp.option & (DEFINITIONS|PREDEFINITIONS)) || !(pp.state & NOTEXT)))
				{
					ppsync();
					ppprintf("#%s %s", dirname(UNDEF), sym->name);
					emitted = 1;
				}
				sym->flags &= ~(SYM_BUILTIN|SYM_FUNCTION|SYM_INIT|SYM_MULTILINE|SYM_PREDEFINED|SYM_REDEFINE|SYM_VARIADIC);
				n2 = error_info.line;
				goto benign;
			}
			else pprefmac(pp.token, REF_UNDEF);
			break;
#if DEBUG
		default:
			error(PANIC, "#%s: directive recognized but not implemented", pp.token);
			goto eatdirective;
#endif
		}
		break;
	case '\n':
		break;
	default:
		error(1, "%s: invalid directive name", pptokstr(pp.token, 0));
		goto eatdirective;
	}
 enddirective:
#if COMPATIBLE
	if (c != '\n' && !(pp.state & COMPATIBILITY))
#else
	if (c != '\n')
#endif
	{
		pp.state |= DISABLE|NOSPACE;
		if ((c = pplex()) != '\n' && (pp.mode & (HOSTED|PEDANTIC)) == PEDANTIC)
			error(1, "%s: invalid characters after directive", pptokstr(pp.token, 0));
	}
 eatdirective:
	if (c != '\n')
	{
		pp.state |= DISABLE;
		while (pplex() != '\n');
	}
 donedirective:
#if _HUH_2002_05_09
	if (!(pp.state & EOF2NL))
		error(2, "%s in directive", pptokchr(0));
#endif
	pp.state &= ~RESTORE;
	pp.mode &= ~RELAX;
	if (!(*pp.control & SKIP))
	{
		pp.state |= restore;
		switch (directive)
		{
		case LINE:
			return 0;
		case INCLUDE:
			if (pp.include)
			{
				error_info.line++;
				PUSH_FILE(pp.include, n);
				if (!pp.vendor && (pp.found->type & TYPE_VENDOR))
					pp.vendor = 1;
				pp.include = 0;
				return 0;
			}
			if (pp.incref)
				(*pp.incref)(error_info.file, ppgetfile(pp.path)->name, error_info.line, PP_SYNC_IGNORE);
			else if (pp.linesync && pp.macref)
			{
				pp.flags |= PP_lineignore;
				(*pp.linesync)(error_info.line, ppgetfile(pp.path)->name);
			}
			/*FALLTHROUGH*/
		default:
			pp.in->flags |= IN_tokens;
			/*FALLTHROUGH*/
		case ENDIF:
			error_info.line++;
			if (emitted)
			{
				ppputchar('\n');
				ppcheckout();
			}
			else
			{
				pp.state |= HIDDEN;
				pp.hidden++;
			}
			return 0;
		}
	}
	pp.state |= restore|HIDDEN|SKIPCONTROL;
	pp.hidden++;
	pp.level++;
	error_info.line++;
	return 0;
}

/*
 * grow the pp nesting control stack
 */

void
ppnest(void)
{
	register struct ppinstk*	ip;
	int				oz;
	int				nz;
	long				adjust;
	long*				op;
	long*				np;

	oz = pp.constack;
	op = pp.maxcon - oz + 1;
	nz = oz * 2;
	np = newof(op, long, nz, 0);
	if (adjust = (np - op))
	{
		ip = pp.in;
		do
		{
			if (ip->control)
				ip->control += adjust;
		} while (ip = ip->prev);
	}
	pp.control = np + oz;
	pp.constack = nz;
	pp.maxcon = np + nz - 1;
}
