/***********************************************************************
*                                                                      *
*               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 lexical analyzer
 * standalone and tokenizing lexer combined in one source
 * define CPP=1 for standalone
 */

#include "pplib.h"
#include "ppfsm.h"

#if CPP

/*
 * standalone entry point
 */

#define PPCPP_T		void

#define START		QUICK
#define INMACRO(x)	INQMACRO(x)
#define DOSTRIP()	(st&STRIP)

#if DEBUG & TRACE_debug
static int		hit[LAST-TERMINAL+2];
#endif

#define BACKIN()	(ip--)
#define BACKOUT()	(op=tp)
#define CACHE()		do{CACHEINX();CACHEOUTX();st=pp.state;if(!pp.hidden)spliced=0;}while(0)
#define CACHEIN()	do{CACHEINX();st=pp.state;if(!pp.hidden)spliced=0;}while(0)
#define CACHEINX()	do{ip=pp.in->nextchr;}while(0)
#define CACHEOUT()	do{CACHEOUTX();st=pp.state;if(!pp.hidden)spliced=0;}while(0)
#define CACHEOUTX()	do{tp=op=pp.outp;xp=pp.oute;if(sp)sp=op;}while(0)
#define GETCHR()	(*(unsigned char*)ip++)
#define LASTCHR()	(*(ip-1))
#define LASTOUT()	((op>pp.outbuf)?*(op-1):pp.lastout)
#define SKIPIN()	(ip++)
#define PUTCHR(c)	(*op++=(c))
#define SETCHR(c)	(*op=(c))
#define SYNC()		do{SYNCINX();SYNCOUTX();pp.state=st;}while(0)
#define SYNCIN()	do{SYNCINX();pp.state=st;}while(0)
#define SYNCINX()	do{pp.in->nextchr=ip;}while(0)
#define SYNCOUT()	do{SYNCOUTX();pp.state=st;}while(0)
#define SYNCOUTX()	do{if(sp)op=tp=sp;pp.outp=op;}while(0)
#define UNGETCHR(c)	(*--ip=(c))

#define PPCHECKOUT()	do{if(op>xp){{PPWRITE(PPBUFSIZ);if(pp.outbuf==pp.outb){pp.outbuf+=PPBUFSIZ;xp=pp.oute+=PPBUFSIZ;}else{pp.outbuf-=PPBUFSIZ;memcpy(pp.outbuf,xp,op-xp);xp=pp.oute-=PPBUFSIZ;op-=2*PPBUFSIZ;}}}}while(0)
#define PPCHECKOUTSP()	do{if(op>xp){if(sp)op=sp;else{PPWRITE(PPBUFSIZ);if(pp.outbuf==pp.outb){pp.outbuf+=PPBUFSIZ;xp=pp.oute+=PPBUFSIZ;}else{pp.outbuf-=PPBUFSIZ;memcpy(pp.outbuf,xp,op-xp);xp=pp.oute-=PPBUFSIZ;op-=2*PPBUFSIZ;}}}}while(0)
#define PPCHECKOUTTP()	do{if(op>xp){{PPWRITE(PPBUFSIZ);if(pp.outbuf==pp.outb){pp.outbuf+=PPBUFSIZ;xp=pp.oute+=PPBUFSIZ;}else{pp.outbuf-=PPBUFSIZ;memcpy(pp.outbuf,xp,op-xp);xp=pp.oute-=PPBUFSIZ;op-=2*PPBUFSIZ;}}tp=op;}}while(0)

#define PPSYNCLINE()	do { \
		if ((st & (ADD|HIDDEN)) && !(*pp.control & SKIP)) \
		{ \
		    if (spliced) \
		    { \
			error_info.line += spliced; \
			spliced = 0; \
		    } \
		    else \
		    { \
			if (st & ADD) \
			{ \
				st &= ~ADD; \
				m = pp.addp - pp.addbuf; \
				pp.addp = pp.addbuf; \
				memcpy(op, pp.addbuf, m); \
				op += m; \
				PPCHECKOUT(); \
			} \
			if (pp.linesync) \
			{ \
				if ((st & SYNCLINE) || pp.hidden >= MAXHIDDEN) \
				{ \
					pp.hidden = 0; \
					st &= ~(HIDDEN|SYNCLINE); \
					if (error_info.line) \
					{ \
						if (LASTOUT() != '\n') \
							PUTCHR('\n'); \
						SYNCOUT(); \
						(*pp.linesync)(error_info.line, error_info.file); \
						CACHEOUT(); \
					} \
				} \
				else \
				{ \
					m = pp.hidden; \
					pp.hidden = 0; \
					st &= ~HIDDEN; \
					while (m-- > 0) \
						PUTCHR('\n'); \
				} \
			} \
			else \
			{ \
				pp.hidden = 0; \
				st &= ~HIDDEN; \
				PUTCHR('\n'); \
			} \
		    } \
		} \
	} while (0)

#if POOL

/*
 * <wait.h> is poison here so pool moved to the end
 */

static void	poolstatus(void);
static void	pool(void);

#endif

#else

/*
 * return next pp token
 *
 * NOTE: pp.token points to at least MAXTOKEN*2 chars and is
 *       truncated back to MAXTOKEN on EOB
 */

#define PPCPP_T		int
#define ppcpp		pplex

#define START		TOKEN
#define INMACRO(x)	INTMACRO(x)
#define DOSTRIP()	((st&STRIP)||pp.level==1&&(st&(COMPILE|JOINING))==COMPILE&&!(pp.option&PRESERVE))

#define st		pp.state
#define tp		pp.token
#define xp		&pp.token[MAXTOKEN]

#define BACKIN()	(ip--)
#define BACKOUT()	(op=pp.token)
#define CACHE()		do{CACHEIN();CACHEOUT();}while(0)
#define CACHEIN()	(ip=pp.in->nextchr)
#define CACHEOUT()	(op=pp.token)
#define GETCHR()	(*(unsigned char*)ip++)
#define LASTCHR()	(*(ip-1))
#define PUTCHR(c)	(*op++=(c))
#define SETCHR(c)	(*op=(c))
#define SKIPIN()	(ip++)
#define SYNC()		do{SYNCIN();SYNCOUT();}while(0)
#define SYNCIN()	(pp.in->nextchr=ip)
#define SYNCOUT()	(pp.toknxt=op)
#define UNGETCHR(c)	(*--ip=(c))

#endif

PPCPP_T
ppcpp(void)
{
	register short*		rp;
	register char*		ip;
	register int		state;
	register int		c;
	register char*		op;
	char*			bp;
	int			n;
	int			m;
	int			quot;
	int			quotquot;
	int			comdelim = 0;
	int			comstart = 0;
	int			comwarn = 0;
	char*			s;
	struct ppsymbol*	sym;
#if CPP
	register long		st;
	char*			tp;
	char*			xp;
	char*			sp = 0;
	int			qual = 0;
	int			spliced = 0;
#else
	int			qual;
#endif

#if CPP
#if POOL
 fsm_pool:
#endif
#else
	count(pplex);
#endif
	error_info.indent++;
	pp.level++;
	CACHE();
#if !CPP
 fsm_top:
	qual = 0;
#endif
 fsm_start:
#if CPP
	PPCHECKOUTSP();
	tp = op;
#endif
	state = START;
 fsm_begin:
	bp = ip;
	do
	{
		rp = fsm[state];
 fsm_get:
		while (!(state = rp[c = GETCHR()]));
 fsm_next:
		;
	} while (state > 0);
	if (((state = ~state) != S_COMMENT || pp.comment || c == '/' && !INCOMMENT(rp)) && (n = ip - bp - 1) > 0)
	{
		ip = bp;
#if CPP
		if (op == tp && (st & (ADD|HIDDEN)) && !(st & PASSTHROUGH))
			switch (TERM(state))
			{
			case S_SHARP:
				break;
			case S_CHRB:
			case S_NL:
				if (*ip == '\n')
					break;
				/*FALLTHROUGH*/
			default:
				PPSYNCLINE();
				tp = op;
				break;
			}
#endif
		MEMCPY(op, ip, n);
		ip++;
	}
	count(terminal);
#if CPP && (DEBUG & TRACE_debug)
	hit[(state & SPLICE) ? (elementsof(hit) - 1) : (TERM(state) - TERMINAL)]++;
#endif
 fsm_terminal:
	debug((-9, "TERM %s > %s%s%s |%-*.*s|%s|", pplexstr(INDEX(rp)), pplexstr(state), (st & NEWLINE) ? "|NEWLINE" : "", (st & SKIPCONTROL) ? "|SKIP" : "", op - tp, op - tp, tp, pptokchr(c)));
	switch (TERM(state))
	{

#if !CPP
	case S_CHR:
		PUTCHR(c);
		break;
#endif

	case S_CHRB:
		BACKIN();
#if CPP
		st &= ~NEWLINE;
		pp.in->flags |= IN_tokens;
		count(token);
		goto fsm_start;
#else
		c = *tp;
		break;
#endif

	case S_COMMENT:
		switch (c)
		{
		case '\n':
			if (!INCOMMENTXX(rp))
			{
				qual = 0;
				if (!comstart) comstart = comdelim = error_info.line;
				error_info.line++;
				if (pp.comment) PUTCHR(c);
				else BACKOUT();
#if CPP
				rp = fsm[COM2];
				bp = ip;
				goto fsm_get;
#else
				state = COM2;
				goto fsm_begin;
#endif
			}
			else if (comwarn < 0)
				error(1, "/* appears in // comment");
			break;
		case '*':
			if (!comwarn && !(pp.mode & HOSTED))
			{
				if (INCOMMENTXX(rp)) comwarn = -1;
				else if (comstart && comstart != error_info.line)
				{
					if (qual || comdelim < error_info.line - 1)
					{
						error(1, "/* appears in /* ... */ comment starting at line %d", comstart);
						comwarn = 1;
					}
					else comdelim = error_info.line;
				}
			}
 fsm_comment:
			PUTCHR(c);
#if CPP
			rp = fsm[INCOMMENTXX(rp) ? COM5 : COM3];
			bp = ip;
			goto fsm_get;
#else
			state = INCOMMENTXX(rp) ? COM5 : COM3;
			goto fsm_begin;
#endif
		case '/':
			if (!INCOMMENT(rp))
			{
				if (!(pp.mode & HOSTED))
					error(1, "*/ appears outside of comment");
				BACKIN();
#if CPP
				st &= ~NEWLINE;
				pp.in->flags |= IN_tokens;
				count(token);
				goto fsm_start;
#else
				c = '*';
				if (!pp.comment) PUTCHR(c);
				goto fsm_token;
#endif
			}
			else if (INCOMMENTXX(rp))
			{
				if (!(pp.mode & HOSTED))
				{
					if (comwarn < 0) comwarn = 0;
					else if (!comwarn)
					{
						comwarn = 1;
						error(1, "*/ appears in // comment");
					}
				}
				goto fsm_comment;
			}
			break;
		case EOF:
			BACKIN();
			if (!(pp.mode & HOSTED))
			{
				if (comstart) error(2, "unterminated /* ... */ comment starting at line %d", comstart);
				else if (INCOMMENTXX(rp)) error(2, "unterminated // ... comment");
				else error(2, "unterminated /* ... */ comment");
			}
			break;
		}
#if CPP
		if (!pp.comment || sp)
		{
#if COMPATIBLE
			if (!(pp.state & COMPATIBILITY) || *bp == ' ' || *bp == '\t')
#endif
			{
				BACKOUT();
				PUTCHR(' ');
				tp = op;
			}
		}
		else if (pp.in->type & IN_TOP)
#else
		if (pp.comment && !(st & (COLLECTING|DIRECTIVE|JOINING)) && !(*pp.control & SKIP) && (pp.in->type & IN_TOP))
#endif
		{
			st &= ~HIDDEN;
			pp.hidden = 0;
			*(op - (c != '\n')) = 0;
			m = (op - (c != '\n') - tp > MAXTOKEN - 6) ? (error_info.line - MAXHIDDEN) : 0;
			BACKOUT();
			SYNC();
			while (*tp != '/') tp++;
			(*pp.comment)(c == '\n' ? "//" : "/*", tp + 2, c == '\n' ? "" : (st & HEADER) ? "*/\n" : "*/", comstart ? comstart : error_info.line);
			CACHE();
			comstart = m;
		}
		if (comstart)
		{
			st |= HIDDEN;
			pp.hidden += error_info.line - comstart;
			comstart = 0;
		}
		qual = comwarn = comdelim = 0;
		BACKOUT();
		if (c == '\n') goto fsm_newline;
		if ((st & PASSTHROUGH) && ((st & (HIDDEN|NEWLINE)) || *ip == '\n'))
		{
			if (*ip == '\n')
				ip++;
			goto fsm_newline;
		}
#if COMPATIBLE
		if ((st & (COMPATIBILITY|TRANSITION)) == COMPATIBILITY) st &= ~NEWLINE;
#endif
#if !CPP
		if (pp.level > 1 && !(st & (NOSPACE|SKIPCONTROL)))
		{
#if COMPATIBLE
			c = ((st & (COMPATIBILITY|DEFINITION)) == ((COMPATIBILITY|DEFINITION))) ? '\t' : ' ';
#else
			c = ' ';
#endif
			goto fsm_return;
		}
#endif
		goto fsm_start;

	case S_EOB:
		if (c)
		{
			if (state = fsm[TERMINAL][INDEX(rp)+1])
				goto fsm_terminal;
#if CPP
#if POOL
			if (pp.pool.input)
			{
				BACKIN();
				SYNC();
				pool();
				CACHE();
				goto fsm_pool;
			}
#endif
			SYNCOUT();
			return;
#else
			BACKIN();
			c = 0;
			goto fsm_return;
#endif
		}
		{
			register struct ppinstk*	cur = pp.in;
			register struct ppinstk*	prv = pp.in->prev;

#if CPP
			if (sp) op = sp;
#endif
			switch (cur->type)
			{
			case IN_BUFFER:
			case IN_INIT:
			case IN_RESCAN:
#if CPP
				if (prv)
#else
				if (!(st & PASSEOF) && prv)
#endif
				{
					if (cur->type == IN_RESCAN || cur->type == IN_BUFFER)
					{
 fsm_pop:
#if PROTOTYPE
						if (cur->flags & IN_prototype)
							pppclose(cur->buffer + PPBAKSIZ);
						else
#endif
						if (!(cur->flags & IN_static))
							free(cur->buffer);
					}
					while (pp.control-- != cur->control)
						error(2, "#%s on line %d has no #%s", dirname(IF), GETIFLINE(pp.control+1), dirname(ENDIF));
					st |= NEWLINE;
					error_info.file = cur->file;
					error_info.line = cur->line;
					pp.hidden = 0;
#if CPP
					spliced = 0;
#endif
					if (cur->flags & IN_hosted)
					{
						pp.mode |= HOSTED;
						pp.flags |= PP_hosted;
					}
					else
					{
						pp.mode &= ~HOSTED;
						pp.flags &= ~PP_hosted;
					}
#if !CPP && CATSTRINGS
					if (st & JOINING) st |= HIDDEN|SYNCLINE;
					else
#endif
					{
						st &= ~(HIDDEN|SYNCLINE);
						switch (cur->type)
						{
						case IN_BUFFER:
						case IN_INIT:
							if (!prv->prev) break;
							/*FALLTHROUGH*/
						case IN_FILE:
						case IN_RESCAN:
							if (prv->type == IN_FILE || cur->type == IN_FILE && (prv->type == IN_RESCAN || prv->type == IN_MULTILINE))
							{
								if (pp.linesync && (cur->type != IN_RESCAN || (cur->flags & IN_sync)))
								{
									POP();
									SYNCOUT();
									(*pp.linesync)(error_info.line, error_info.file);
									CACHEOUT();
									prv = pp.in;
								}
							}
#if DEBUG
							else if (!prv->prev)
							{
								/*UNDENT*/
	c = 0;
#if DEBUG & TRACE_count
	if (pp.test & TEST_count)
	{
		c = 1;
		sfprintf(sfstderr, "\n");
		sfprintf(sfstderr, "%7d: pplex calls\n", pp.counter.pplex);
		sfprintf(sfstderr, "%7d: terminal states\n", pp.counter.terminal);
		sfprintf(sfstderr, "%7d: emitted tokens\n", pp.counter.token);
		sfprintf(sfstderr, "%7d: input stream pushes\n", pp.counter.push);
		sfprintf(sfstderr, "%7d: macro candidates\n", pp.counter.candidate);
		sfprintf(sfstderr, "%7d: macro expansions\n", pp.counter.macro);
		sfprintf(sfstderr, "%7d: function macros\n", pp.counter.function);
	}
#endif
#if CPP && (DEBUG & TRACE_debug)
	if (pp.test & TEST_hit)
	{
		c = 1;
		sfprintf(sfstderr, "\n");
		if (hit[elementsof(hit) - 1])
			sfprintf(sfstderr, "%7d: SPLICE\n", hit[elementsof(hit) - 1]);
		for (n = 0; n < elementsof(hit) - 1; n++)
			if (hit[n])
				sfprintf(sfstderr, "%7d: %s\n", hit[n], pplexstr(TERMINAL + n));
	}
#endif
	if (pp.test & (TEST_hashcount|TEST_hashdump))
	{
		c = 1;
		sfprintf(sfstderr, "\n");
		hashdump(NiL, (pp.test & TEST_hashdump) ? HASH_BUCKET : 0);
	}
	if (c) sfprintf(sfstderr, "\n");
								/*INDENT*/
							}
#endif
							break;
						}
					}
#if CHECKPOINT
					if (cur->index)
					{
						SYNCOUT();
						cur->index->end = ppoffset();
						cur->index = 0;
						CACHEOUT();
					}
#endif
					POP();
					bp = ip;
					tp = op;
					goto fsm_get;
				}
				c = EOF;
				break;
			case IN_COPY:
				if (prv)
				{
					error_info.line = cur->line;
					if (!(prv->symbol->flags & SYM_MULTILINE))
						prv->symbol->flags |= SYM_DISABLED;
					POP();
					bp = ip;
					goto fsm_get;
				}
				c = EOF;
				break;
			case IN_EXPAND:
				if (prv)
				{
					error_info.line = cur->line;
					free(cur->buffer);
					POP();
					bp = ip;
					goto fsm_get;
				}
				c = EOF;
				break;
			case IN_FILE:
				FGET(c, c, tp, xp);
				if (c == EOB)
				{
#if CPP
					if ((st & (NOTEXT|HIDDEN)) == HIDDEN && LASTOUT() != '\n')
						PUTCHR('\n');
					if (prv)
#else
					if (st & EOF2NL)
					{
						st &= ~EOF2NL;
						*(ip - 1) = c = '\n';
					}
					else if (!(st & (FILEPOP|PASSEOF)) && prv)
#endif
					{
						if (!(cur->flags & IN_newline))
						{
							cur->flags |= IN_newline;
							if ((pp.mode & (HOSTED|PEDANTIC)) == PEDANTIC && LASTCHR() != '\f' && LASTCHR() != CC_sub)
								error(1, "file does not end with %s", pptokchr('\n'));
							*(ip - 1) = c = '\n';
						}
						else
						{
							if (!(cur->flags & (IN_noguard|IN_tokens)) && cur->symbol)
								ppmultiple(ppsetfile(error_info.file), cur->symbol);
							if (cur->fd >= 0)
								close(cur->fd);
							if (pp.incref && !(pp.mode & INIT))
							{
								SYNCOUT();
								(*pp.incref)(error_info.file, cur->file, error_info.line - 1, PP_SYNC_POP);
								CACHEOUT();
							}
							goto fsm_pop;
						}
					}
					else
						c = EOF;
				}
				break;
			case IN_MACRO:
			case IN_MULTILINE:
#if !CPP
				if (!(st & PASSEOF))
#endif
#if COMPATIBLE
				if (prv && (!INMACRO(rp) || (st & (COMPATIBILITY|TRANSITION)) == COMPATIBILITY && ppismac(*prv->nextchr)))
#else
				if (prv && !INMACRO(rp))
#endif
				{
					if (cur->type == IN_MULTILINE)
					{
						while (pp.control-- != cur->control)
							error(2, "#%s on line %d has no #%s", dirname(IF), GETIFLINE(pp.control+1), dirname(ENDIF));
						free(cur->buffer);
						error_info.file = cur->file;
						error_info.line = cur->line;
						if (pp.linesync)
						{
							SYNCOUT();
							(*pp.linesync)(error_info.line, error_info.file);
							CACHEOUT();
						}
					}
					cur->symbol->flags &= ~SYM_DISABLED;
					if (cur->symbol->flags & SYM_FUNCTION)
						popframe(pp.macp);
					POP();
#if CPP
					if (!(st & COMPATIBILITY) && ppisidig(*(op - 1)) && ppisidig(*ip)) UNGETCHR(' ');
#endif
					bp = ip;
					goto fsm_get;
				}
				c = EOF;
				break;
			case IN_QUOTE:
				if (prv)
				{
					error_info.line = cur->line;
					st &= ~(ESCAPE|QUOTE);
					POP();
					c = '"';
				}
				else c = EOF;
				break;
			case IN_SQUOTE:
				if (prv)
				{
					error_info.line = cur->line;
					st &= ~(ESCAPE|SQUOTE);
					POP();
					c = '\'';
				}
				else c = EOF;
				break;
			case IN_STRING:
#if CPP
				if (prv)
#else
				if (!(st & PASSEOF) && !(cur->flags & IN_expand) && prv)
#endif
				{
					if (cur->flags & IN_disable) st |= DISABLE;
					else st &= ~DISABLE;
					POP();
					bp = ip;
					goto fsm_get;
				}
				c = EOF;
				break;
			default:
				c = EOF;
				break;
			}
		}
		bp = ip - 1;
		if (state = rp[c]) goto fsm_next;
		goto fsm_get;

#if !CPP
	case S_HUH:
		if (INOPSPACE(rp))
		{
			if (c == '=')
			{
#if PROTOTYPE
				if (pp.in->flags & IN_prototype) PUTCHR(c);
				else
				{
#endif
					while (*(op - 1) == ' ' || *(op - 1) == '\t') op--;
					PUTCHR(c);
					if (st & (STRICT|WARN)) error(1, "%-*.*s: space ignored in operator", op - tp, op - tp, tp);
#if PROTOTYPE
				}
#endif
				switch (*tp)
				{
				case '/':
					c = T_DIVEQ;
					break;
				case '%':
					c = T_MODEQ;
					break;
				case '&':
					c = T_ANDEQ;
					break;
				case '*':
					c = T_MPYEQ;
					break;
				case '+':
					c = T_ADDEQ;
					break;
				case '-':
					c = T_SUBEQ;
					break;
				case '^':
					c = T_XOREQ;
					break;
				case '|':
					c = T_OREQ;
					break;
				case '<':
					c = T_LSHIFTEQ;
					break;
				case '>':
					c = T_RSHIFTEQ;
					break;
				}
			}
			else
			{
				BACKIN();
				switch (c = *tp)
				{
				case '<':
					c = T_LSHIFT;
					break;
				case '>':
					c = T_RSHIFT;
					break;
				}
			}
		}
		else if (pp.level > 1 || (pp.option & PRESERVE)) PUTCHR(c);
		else if (tp == op)
		{
			if (pp.in->type != IN_BUFFER)
			{
				if (!(pp.option & ALLPOSSIBLE))
					error(1, "%s: invalid character ignored", pptokchr(c));
				goto fsm_top;
			}
			PUTCHR(c);
		}
		else if (*tp == ':')
		{
			PUTCHR(c);
			if (c == '=') error(2, "real programmers use =");
			else c = '+';
		}
		else
		{
			BACKIN();
			c = *tp;
		}
		break;
#endif

	case S_QUAL:
		if ((state = NEXT(state)) != LIT1)
		{
			PUTCHR(c);
			rp = fsm[state];
			bp = ip;
#if CPP
			qual = 1;
#else
			switch (c)
			{
			case 'f':
			case 'F':
				qual |= N_FLOAT;
				break;
			case 'l':
			case 'L':
				qual |= N_LONG;
				break;
			case 'u':
			case 'U':
				qual |= N_UNSIGNED;
				break;
			}
#endif
			goto fsm_get;
		}
#if !CPP
		qual |= N_WIDE;
		if (DOSTRIP()) BACKOUT();
#endif
		/*FALLTHROUGH*/

	case S_LITBEG:
#if CPP
		quot = c;
		rp = fsm[LIT1];
		if (op == tp)
		{
			PPSYNCLINE();
			tp = op;
		}
#else
		if ((quot = c) == '<')
		{
			if (!(st & HEADER) || (pp.option & (HEADEREXPAND|HEADEREXPANDALL)) && pp.in->type != IN_FILE && pp.in->type != IN_BUFFER && pp.in->type != IN_INIT && pp.in->type != IN_RESCAN)
			{
				PUTCHR(c);
				bp = ip;
				rp = fsm[LT1];
				goto fsm_get;
			}
			quot = '>';
			rp = fsm[HDR1];
		}
		else rp = fsm[LIT1];
		if (!DOSTRIP())
#endif
		PUTCHR(c);
		bp = ip;
		goto fsm_get;

	case S_LITEND:
		n = 1;
		if (c != quot)
		{
			if (c != '\n' && c != EOF)
			{
				if (st & (QUOTE|SQUOTE))
				{
					if (!(st & ESCAPE))
					{
						st |= ESCAPE;
						quotquot = c;
					}
					else if (c == quotquot) st &= ~ESCAPE;
				}
				PUTCHR(c);
				bp = ip;
				goto fsm_get;
			}
#if CPP
			if (st & PASSTHROUGH)
			{
				if (c == '\n') goto fsm_newline;
				bp = ip;
				goto fsm_start;
			}
#endif
			m = (st & SKIPCONTROL) && (pp.mode & HOSTED) ? -1 : 1;
			if (c == '\n' && quot == '\'' && (pp.option & STRINGSPAN)) n = 0;
			else
#if COMPATIBLE && !CPP
			if ((st & (COMPATIBILITY|DEFINITION)) != (COMPATIBILITY|DEFINITION))
#endif
			{
				switch (quot)
				{
				case '"':
					if (c == '\n')
					{
						if (!(pp.option & STRINGSPAN) || (st & (COMPATIBILITY|STRICT)) == STRICT)
							error(m, "%s in string", pptokchr(c));
						error_info.line++;
						if (!(pp.option & STRINGSPAN))
						{
							PUTCHR('\\');
							c = 'n';
						}
						else if (pp.option & STRINGSPLIT)
						{
							PUTCHR('\\');
							PUTCHR('n');
							PUTCHR('"');
							PUTCHR('\n');
							c = '"';
						}
						PUTCHR(c);
						bp = ip;
						goto fsm_get;
					}
					error(m, "%s in string", pptokchr(c));
					c = '\n';
					break;
				case '\'':
					if (!(st & DIRECTIVE) || !(pp.mode & (HOSTED|RELAX)))
						error(m, "%s in character constant", pptokchr(c));
					break;
				case '>':
					error(m, "%s in header constant", pptokchr(c));
					break;
				default:
					error(m, "%s in %c quote", pptokchr(c), quot);
					break;
				}
#if !CPP
				if (!DOSTRIP())
#endif
				PUTCHR(quot);
			}
			if (c == '\n')
			{
				UNGETCHR(c);
				c = quot;
			}
		}
		else if (st & (SQUOTE|QUOTE))
		{
			if (!(st & ESCAPE))
			{
				st |= ESCAPE;
				quotquot = c;
			}
			else if (c == quotquot) st &= ~ESCAPE;
			PUTCHR('\\');
			PUTCHR(c);
			bp = ip;
			goto fsm_get;
		}
#if CPP
		else PUTCHR(c);
#else
		else if (!DOSTRIP()) PUTCHR(c);
#endif
#if CATSTRINGS
#if CPP
		if (c == '"' && !(st & (COLLECTING|NOTEXT|PASSTHROUGH|SKIPCONTROL)) && (pp.mode & CATLITERAL))
#else
		if (c == '"' && pp.level == 1 && !(st & (COLLECTING|JOINING|NOTEXT|SKIPCONTROL)) && (pp.mode & CATLITERAL))
#endif
		{
			char*	pptoken;
			long	ppstate;

			pptoken = pp.token;
			pp.token = pp.catbuf;
			*pp.token++ = 0;
			ppstate = (st & STRIP);
			if (DOSTRIP())
				ppstate |= ADD|QUOTE;
			st |= JOINING;
			st &= ~(NEWLINE|STRIP);

			/*
			 * revert to the top level since string
			 * concatenation crosses file boundaries
			 * (allowing intervening directives)
			 */

			pp.level = 0;
			SYNCIN();
			m = n = 0;
			for (;;)
			{
				switch (c = pplex())
				{
				case '\n':
					m++;
					continue;
				case ' ':
					*pp.catbuf = ' ';
					continue;
				case T_WSTRING:
#if !CPP
					qual = N_WIDE;
#endif
					if (ppstate & ADD)
						ppstate &= ~ADD;
					else if (m == n || !(st & SPACEOUT))
						op--;
					else
					{
						n = m;
						*(op - 1) = '\\';
						*op++ = '\n';
					}
					STRCOPY(op, pp.token + 2 + (*pp.token == ' '), s);
					continue;
				case T_STRING:
					if (ppstate & ADD)
						ppstate &= ~ADD;
					else if (m == n || !(st & SPACEOUT))
						op--;
					else
					{
						n = m;
						*(op - 1) = '\\';
						*op++ = '\n';
					}
					STRCOPY(op, pp.token + 1 + (*pp.token == ' '), s);
					continue;
				case 0:
					m = error_info.line ? (error_info.line - 1) : 0;
					*pp.token = 0;
					/*FALLTHROUGH*/
				default:
					if (m)
					{
						if (--m)
						{
							pp.state |= HIDDEN|SYNCLINE;
							pp.hidden += m;
						}
#if COMPATIBLE
						if ((st & COMPATIBILITY) && c == '#' && *(pp.token - 1))
						{
							*(pp.token + 3) = *(pp.token + 2);
							*(pp.token + 2) = *(pp.token + 1);
							*(pp.token + 1) = *pp.token;
							*pp.token = *(pp.token - 1);
						}
						error_info.line--;
						*--pp.token = '\n';
#endif
					}
					else if (*(pp.token - 1))
						pp.token--;
					if (ppisidig(*pp.token))
						*op++ = ' ';
					if (pp.in->type == IN_MACRO && (s = strchr(pp.token, MARK)) && !*(s + 1))
					{
						*(s + 1) = MARK;
						*(s + 2) = 0;
					}
					PUSH_STRING(pp.token);
					pp.state &= ~(JOINING|NEWLINE);
					pp.state |= ppstate & ~(ADD|QUOTE);
					if ((ppstate & (ADD|QUOTE)) == QUOTE)
						op--;
					break;
				}
				break;
			}
			pp.token = pptoken;
			CACHEIN();
			pp.level = 1;
#if !CPP
			c = T_STRING | qual;
			break;
#endif
		}
#endif
#if CPP
		if (n && !(st & (PASSTHROUGH|SKIPCONTROL|NOTEXT)) && c == '\'' && (op - tp) <= 2 && !(pp.mode & (HOSTED|RELAX)))
			error(1, "empty character constant");
		st &= ~(ESCAPE|NEWLINE);
		pp.in->flags |= IN_tokens;
		count(token);
		goto fsm_start;
#else
		st &= ~ESCAPE;
		switch (quot)
		{
		case '\'':
			if (n && !(st & NOTEXT) && (op - tp) <= (DOSTRIP() ? 0 : 2) && !(pp.mode & (HOSTED|RELAX)))
				error(1, "empty character constant");
			c = T_CHARCONST | qual;
			break;
		case '>':
			c = T_HEADER;
			break;
		default:
			if (c == quot)
				c = T_STRING | qual;
			break;
		}
		break;
#endif

	case S_LITESC:
		if (st & (COLLECTING|DIRECTIVE|QUOTE|SQUOTE))
		{
			if (st & ESCAPE)
			{
				PUTCHR('\\');
				if (c == quot) PUTCHR('\\');
			}
			PUTCHR(c);
		}
#if CPP
		else if (st & PASSTHROUGH) PUTCHR(c);
#endif
		else if (pp.option & PRESERVE) PUTCHR(c);
		else switch (c)
		{
		case 'b':
		case 'f':
		case 'n':
		case 'r':
		case 't':
		case '\\':
		case '\'':
		case '"':
		case '?':
			PUTCHR(c);
			break;
#if COMPATIBLE
		case '8':
		case '9':
			if (!(st & COMPATIBILITY)) goto unknown;
			if (st & STRICT) error(1, "%c: invalid character in octal character escape", c);
			/*FALLTHROUGH*/
#endif
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
			n = c - '0';
			for (m = 0; m < 2; m++)
			{
				GET(c, c, tp, xp);
				switch (c)
				{
#if COMPATIBLE
				case '8':
				case '9':
					if (!(st & COMPATIBILITY))
					{
						UNGETCHR(c);
						break;
					}
					if (st & STRICT) error(1, "%c: invalid character in octal character escape", c);
					/*FALLTHROUGH*/
#endif
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
					n = (n << 3) + c - '0';
					continue;
				default:
					UNGETCHR(c);
					break;
				}
				break;
			}
			if (n & ~0777) error(1, "octal character constant too large");
			goto octal;
		case 'a':
			if (pp.option & MODERN)
			{
				PUTCHR(c);
				break;
			}
#if COMPATIBLE
			if (st & COMPATIBILITY) goto unknown;
#endif
			n = CC_bel;
			goto octal;
		case 'v':
			if (pp.option & MODERN)
			{
				PUTCHR(c);
				break;
			}
			n = CC_vt;
			goto octal;
		case 'E':
			if (st & (COMPATIBILITY|STRICT)) goto unknown;
			n = CC_esc;
			goto octal;
		case 'x':
#if COMPATIBLE
			if (st & COMPATIBILITY) goto unknown;
#endif
			n = 0;
			for (m = 0; m < 3; m++)
			{
				GET(c, c, tp, xp);
				switch (c)
				{
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
					n = (n << 4) + c - '0';
					continue;
				case 'a':
				case 'b':
				case 'c':
				case 'd':
				case 'e':
				case 'f':
					n = (n << 4) + c - 'a' + 10;
					continue;
				case 'A':
				case 'B':
				case 'C':
				case 'D':
				case 'E':
				case 'F':
					n = (n << 4) + c - 'A' + 10;
					continue;
				default:
					if (!m) error(1, "\\x%c: invalid character in hexadecimal character constant", c);
					UNGETCHR(c);
					break;
				}
				break;
			}
			if (n & ~0777) error(1, "hexadecimal character constant too large");
		octal:
			PUTCHR(((n >> 6) & 07) + '0');
			PUTCHR(((n >> 3) & 07) + '0');
			PUTCHR((n & 07) + '0');
			break;
		default:
		unknown:
			if (st & (STRICT|WARN)) error(1, "\\%c: non-standard character constant", c);
			PUTCHR(c);
			break;
		}
		state = LIT1;
		goto fsm_begin;

	case S_MACRO:
		BACKIN();
#if CPP
		if (st & (DISABLE|SKIPCONTROL|SKIPMACRO))
		{
			if (st & SKIPMACRO)
				pp.mode |= MARKMACRO;
			st &= ~(NEWLINE|SKIPMACRO);
			pp.in->flags |= IN_tokens;
			count(token);
			goto fsm_start;
		}
		count(candidate);
		SETCHR(0);
		switch (state = INDEX(rp))
		{
		case HIT0:
			tp = op - 1;
			break;
		case HITN:
			bp = tp;
			tp = op - ((pp.truncate && pp.truncate < (HITN - HIT0)) ? (pp.truncate - 1) : (HITN - HIT0));
			while (tp > bp && ppisidig(*(tp - 1))) tp--;
			break;
		default:
			bp = tp;
			if ((tp = op - (state - HIT0)) > bp && *(tp - 1) == 'L') tp--;
			break;
		}
		if (sym = ppsymref(pp.symtab, tp))
		{
			SYNCIN();
			n = ppcall(sym, 0);
			CACHEIN();
			if (n >= 0)
			{
				BACKOUT();
				if (!n)
				{
					if (sp) op = sp;
					else
					{
						s = ip;
						ip = sym->macro->value;
						c = sym->macro->size;
						while (c > 0)
						{
							if (op + c < xp + PPBUFSIZ) n = c;
							else n = xp + PPBUFSIZ - op;
							MEMCPY(op, ip, n);
							c -= n;
							PPCHECKOUT();
						}
						ip = s;
					}
				}
				else if ((sym->flags & SYM_MULTILINE) && pp.linesync)
				{
					SYNCOUT();
					if (!(state & NEWLINE))
						ppputchar('\n');
					(*pp.linesync)(error_info.line, error_info.file);
					CACHEOUT();
				}
			}
		}
		pp.in->flags |= IN_tokens;
		goto fsm_start;
#else
		if (st & (COLLECTING|DEFINITION|DISABLE|SKIPCONTROL|SKIPMACRO))
		{
			if (st & SKIPMACRO)
				pp.mode |= MARKMACRO;
			st &= ~(NEWLINE|NOEXPAND|SKIPMACRO);
			c = T_ID;
			if (pp.level == 1)
			{
				pp.in->flags |= IN_tokens;
				if (st & NOTEXT)
				{
					BACKOUT();
					goto fsm_top;
				}
				if (st & COMPILE)
				{
					SETCHR(0);
					if (pp.truncate && (op - tp) > pp.truncate) tp[pp.truncate] = 0;
					sym = (pp.option & NOHASH) ? ppsymref(pp.symtab, tp) : ppsymset(pp.symtab, tp);
 fsm_noise:
					if (pp.symbol = sym)
					{
						if ((sym->flags & SYM_KEYWORD) && (!pp.truncate || (op - tp) <= pp.truncate || (tp[pp.truncate] = '_', tp[pp.truncate + 1] = 0, pp.symbol = sym = (pp.option & NOHASH) ? ppsymref(pp.symtab, tp) : ppsymset(pp.symtab, tp), 0)))
						{
							c = ((struct ppsymkey*)sym)->lex;
							/*UNDENT*/

#define ADVANCE()	do{if(pp.toknxt<op)pp.token=pp.toknxt;}while(0)

#define NOISE_BRACE		01
#define NOISE_NOSPACEOUT	02
#define NOISE_PAREN		04

	if ((pp.option & NOISE) && ppisnoise(c))
	{
		if (c != T_NOISE)
		{
			int		p;
			int		f;
			char*		pptoken;
			PPCOMMENT	ppcomment;

			SYNCIN();
			pp.toknxt = op;
			f = 0;
			if (!(pp.state & SPACEOUT))
			{
				pp.state |= SPACEOUT;
				f |= NOISE_NOSPACEOUT;
			}
			ppcomment = pp.comment;
			pp.comment = 0;
			op = (pptoken = tp) + MAXTOKEN;
			switch (c)
			{
			case T_X_GROUP:
				m = p = 0;
				quot = 1;
				for (;;)
				{
					ADVANCE();
					switch (c = pplex())
					{
					case '(':
					case '{':
						if (!p)
						{
							if (c == '(')
							{
								if (f & NOISE_PAREN)
								{
									ungetchr(c);
									*--pp.toknxt = 0;
									break;
								}
								f |= NOISE_PAREN;
								p = ')';
							}
							else
							{
								f |= NOISE_BRACE|NOISE_PAREN;
								p = '}';
							}
							n = 1;
							m = c;
						}
						else if (c == m) n++;
						quot = 0;
						continue;
					case ')':
					case '}':
						if (c == p && --n <= 0)
						{
							if (c == '}') break;
							m = '\n';
							p = 0;
						}
						quot = 0;
						continue;
					case ' ':
						continue;
					case '\n':
						error_info.line++;
						if (!m) m = '\n';
						continue;
					case 0:
						break;
					case T_ID:
						if (quot) continue;
						/*FALLTHROUGH*/
					default:
						if (m == '\n')
						{
							/*
							 * NOTE: token expanded again
							 */

							s = pp.toknxt;
							while (s > pp.token) ungetchr(*--s);
							*(pp.toknxt = s) = 0;
							break;
						}
						continue;
					}
					break;
				}
				break;
			case T_X_LINE:
				for (;;)
				{
					ADVANCE();
					switch (pplex())
					{
					case 0:
						break;
					case '\n':
						error_info.line++;
						break;
					default:
						continue;
					}
					break;
				}
				break;
			case T_X_STATEMENT:
				for (;;)
				{
					ADVANCE();
					switch (pplex())
					{
					case 0:
						break;
					case ';':
						ungetchr(';');
						*(pp.toknxt = pp.token) = 0;
						break;
					default:
						continue;
					}
					break;
				}
				break;
			}
			pp.comment = ppcomment;
			if (f & NOISE_NOSPACEOUT)
				pp.state &= ~SPACEOUT;
			CACHEIN();
			tp = pptoken;
			op = pp.toknxt;
			c = T_NOISES;
		}
		if (pp.option & NOISEFILTER)
		{
			BACKOUT();
			goto fsm_top;
		}
	}

							/*INDENT*/
						}
						else if ((pp.option & NOISE) && c == T_ID && strneq(tp, "__builtin_", 10))
						{
							hashlook(pp.symtab, tp, HASH_DELETE, NiL);
							pp.symbol = sym = (struct ppsymbol*)ppkeyset(pp.symtab, tp);
							sym->flags |= SYM_KEYWORD;
							c = ((struct ppsymkey*)sym)->lex = T_BUILTIN;
						}
					}
				}
				goto fsm_symbol;
			}
			goto fsm_check;
		}
		if (pp.level == 1)
		{
			st &= ~(NEWLINE|PASSEOF);
			pp.in->flags |= IN_tokens;
		}
		else st &= ~PASSEOF;
		count(candidate);
		SETCHR(0);
		if (sym = ppsymref(pp.symtab, tp))
		{
			SYNCIN();
			c = ppcall(sym, 1);
			CACHEIN();
			if (c >= 0)
			{
				BACKOUT();
				if ((sym->flags & SYM_MULTILINE) && pp.linesync)
				{
					SYNCOUT();
					(*pp.linesync)(error_info.line, error_info.file);
					CACHEOUT();
				}
				goto fsm_top;
			}
		}
		c = T_ID;
		if (pp.level == 1)
		{
			if (st & NOTEXT)
			{
				BACKOUT();
				goto fsm_top;
			}
			if (st & COMPILE)
			{
				if (pp.truncate && (op - tp) > pp.truncate)
				{
					tp[pp.truncate] = 0;
					sym = 0;
				}
				if (!sym)
				{
					if (!(pp.option & NOHASH)) sym = ppsymset(pp.symtab, tp);
					else if (!(sym = ppsymref(pp.symtab, tp))) goto fsm_symbol;
				}
				goto fsm_noise;
			}
			goto fsm_symbol;
		}
		goto fsm_check;
#endif

	case S_SHARP:
		if (c == '(')
		{
			pp.in->flags |= IN_tokens;
			if ((st & STRICT) && pp.in->type != IN_MACRO && pp.in->type != IN_MULTILINE)
			{
				if (!(pp.mode & HOSTED)) error(1, "non-standard reference to #(...)");
				if (st & STRICT)
				{
					PUTCHR(c);
#if CPP
					st &= ~NEWLINE;
					count(token);
					goto fsm_start;
#else
					break;
#endif
				}
			}
			if (st & (COLLECTING|DEFINITION|DISABLE|SKIPCONTROL))
			{
				PUTCHR(c);
#if CPP
				st &= ~NEWLINE;
				count(token);
				goto fsm_start;
#else
				st &= ~NOEXPAND;
				break;
#endif
			}
			op--;
			SYNC();
			ppbuiltin();
			CACHE();
#if CPP
			count(token);
			goto fsm_start;
#else
			goto fsm_top;
#endif
		}
		BACKIN();
#if CPP
		if (!(st & NEWLINE) || !(pp.in->type & IN_TOP))
		{
 fsm_nondirective:
			st &= ~NEWLINE;
			pp.in->flags |= IN_tokens;
			count(token);
			goto fsm_start;
		}
		if (*(s = tp) != '#')
		{
#if COMPATIBLE
			if ((st & (COMPATIBILITY|TRANSITION)) == COMPATIBILITY) goto fsm_nondirective;
#endif
			while (*s == ' ' || *s == '\t') s++;
			if (*s != '#') goto fsm_nondirective;
		}
		BACKOUT();
#else
		if (!(st & NEWLINE) || (st & DEFINITION) || !(pp.in->type & IN_TOP))
		{
			if (c == '#')
			{
				SKIPIN();
				if (!(st & DEFINITION))
					PUTCHR(c);
				c = T_TOKCAT;
			}
			else if (pp.level == 1 && !(st & (JOINING|SPACEOUT)) && !(pp.option & PRESERVE))
			{
				char*		pptoken;
				char*		oop;
				PPCOMMENT	ppcomment;

				SYNCIN();
				pp.toknxt = oop = op;
				pp.state |= SPACEOUT;
				ppcomment = pp.comment;
				pp.comment = 0;
				op = (pptoken = tp) + MAXTOKEN;
				for (;;)
				{
					ADVANCE();
					switch (pplex())
					{
					case 0:
						break;
					case '\n':
						error_info.line++;
						break;
					default:
						continue;
					}
					break;
				}
				pp.comment = ppcomment;
				pp.state &= ~SPACEOUT;
				CACHEIN();
				tp = pptoken;
				*--op = 0;
				op = oop;
				if (pp.pragma && !(st & NOTEXT))
				{
					*s = 0;
					SYNC();
					(*pp.pragma)(NiL, NiL, NiL, tp, 1);
					CACHE();
				}
				if (!c) BACKIN();
				goto fsm_top;
			}
			else c = '#';
			break;
		}
		if ((st & (COLLECTING|STRICT)) == (COLLECTING|STRICT))
			error(1, "directives in macro call arguments are not portable");
#endif
		if (c == '#' && pp.in->type == IN_RESCAN)
		{
			/*
			 * pass line to pp.pragma VERBATIM
			 */
			
			SKIPIN();
			s = pp.valbuf;
			while ((c = GETCHR()) && c != '\n')
				if ((*s++ = c) == MARK) SKIPIN();
			if (pp.pragma && !(st & NOTEXT))
			{
				*s = 0;
				SYNC();
				(*pp.pragma)(NiL, NiL, NiL, pp.valbuf, 1);
				CACHE();
			}
			if (!c) BACKIN();
#if CPP
			goto fsm_start;
#else
			goto fsm_top;
#endif
		}
		SYNC();
		ppcontrol();
		CACHE();
#if CPP
		if (st & (NOTEXT|SKIPCONTROL))
		{
			if (!sp)
			{
				PPCHECKOUTTP();
				sp = tp;
			}
		}
		else if (sp)
		{
			tp = op = sp;
			sp = 0;
		}
		goto fsm_start;
#else
		goto fsm_top;
#endif

	case S_NL:
#if CPP
		if (op == tp && !(st & JOINING) && pp.in->type == IN_FILE)
		{
			st |= NEWLINE|HIDDEN;
			pp.hidden++;
			error_info.line++;
			goto fsm_start;
		}
#endif
 fsm_newline:
#if CPP
		if (sp)
			op = sp;
		else if (!(pp.in->flags & IN_noguard))
		{
			while (tp < op)
				if ((c = *tp++) != ' ' && c != '\t')
				{
					pp.in->flags |= IN_tokens;
					break;
				}
			c = '\n';
		}
		st |= NEWLINE;
		error_info.line++;
		if (*ip == '\n' && *(ip + 1) != '\n' && !pp.macref && !(st & (ADD|HIDDEN)))
		{
			ip++;
			PUTCHR('\n');
			error_info.line++;
		}
		if ((st & NOTEXT) && ((pp.mode & FILEDEPS) || (pp.option & (DEFINITIONS|PREDEFINITIONS))))
			BACKOUT();
		else
		{
			debug((-5, "token[%d] %03o = %s [line=%d]", pp.level, c, pptokchr(c), error_info.line));
			PUTCHR('\n');
			PPSYNCLINE();
			if (sp)
			{
				PPCHECKOUT();
				sp = op;
			}
		}
		goto fsm_start;
#else
		st |= NEWLINE;
		if (pp.level == 1)
		{
			error_info.line++;
			if (!(st & (JOINING|SPACEOUT)))
			{
				debug((-5, "token[%d] %03o = %s [line=%d]", pp.level, c, pptokchr(c), error_info.line));
				BACKOUT();
				goto fsm_top;
			}
		}
		BACKOUT();
		if (st & SKIPCONTROL)
		{
			error_info.line++;
			st |= HIDDEN;
			pp.hidden++;
			goto fsm_start;
		}
		PUTCHR(c = '\n');
		goto fsm_return;
#endif

#if !CPP
	case S_TOK:
		PUTCHR(c);
		c = TYPE(state) | qual;
		break;

	case S_TOKB:
		BACKIN();
		c = TYPE(state) | qual;
		break;
#endif

	case S_VS:
		PUTCHR(c);
#if !CPP
		if (st & NOVERTICAL)
		{
			error(1, "%s invalid in directives", pptokchr(c));
			st &= ~NOVERTICAL;
		}
#endif
#if COMPATIBLE
		if (st & COMPATIBILITY) st |= NEWLINE;
#endif
#if CPP
		if (!(pp.in->flags & IN_noguard))
			while (tp < op)
				if ((c = *tp++) != ' ' && c != '\t')
				{
					pp.in->flags |= IN_tokens;
					break;
				}
		goto fsm_start;
#else
		bp = ip;
		rp = fsm[WS1];
		goto fsm_get;
#endif

#if !CPP
	case S_WS:
#if COMPATIBLE
		if ((st & (COMPATIBILITY|TRANSITION)) == COMPATIBILITY) st &= ~NEWLINE;
#endif
		if (pp.level == 1)
		{
			if ((st & (COMPATIBILITY|SPACEOUT)) && !(st & TRANSITION))
			{
				if (st & (COMPILE|NOTEXT))
				{
#if CATSTRINGS
					if ((st & (JOINING|NOTEXT|SPACEOUT)) != SPACEOUT)
#else
					if ((st & (NOTEXT|SPACEOUT)) != SPACEOUT)
#endif
					{
						BACKOUT();
						bp = ip - 1;
						rp = fsm[START];
						if (state = rp[c]) goto fsm_next;
						goto fsm_get;
					}
				}
				else
#if CATSTRINGS
				if (!(st & JOINING))
#endif
				{
					tp = op;
					bp = ip - 1;
					rp = fsm[START];
					if (state = rp[c]) goto fsm_next;
					goto fsm_get;
				}
				BACKIN();
				c = ' ';
				goto fsm_return;
			}
			BACKOUT();
			bp = ip - 1;
			rp = fsm[START];
			if (state = rp[c]) goto fsm_next;
			goto fsm_get;
		}
		if (st & (NOSPACE|SKIPCONTROL))
		{
			BACKOUT();
			bp = ip - 1;
			rp = fsm[START];
			if (state = rp[c]) goto fsm_next;
			goto fsm_get;
		}
		if (c != '\n')
		{
			BACKIN();
			c = ' ';
		}
		if (!(pp.option & PRESERVE))
		{
			BACKOUT();
			PUTCHR(c);
		}
		goto fsm_return;
#endif

	default:
		if (state & SPLICE)
		{
			switch (c)
			{
			case MARK:
				/*
				 * internal mark
				 */

				switch (pp.in->type)
				{
				case IN_BUFFER:
				case IN_FILE:
#if !CPP
				case IN_INIT:
#if CATSTRINGS
					if ((st & JOINING) && (!INQUOTE(rp) || quot != '"') || pp.level > 1 && (rp == fsm[START] || INQUOTE(rp)))
#else
					if (pp.level > 1 && (rp == fsm[START] || INQUOTE(rp)))
#endif
						PUTCHR(c);
#endif
					break;
				default:
					switch (GETCHR())
					{
					case 'A':
						if (!(st & (DEFINITION|DISABLE)))
						{
							c = GETCHR();
							SYNCIN();
							if (pp.macp->arg[c - ARGOFFSET][-1])
								PUSH_EXPAND(pp.macp->arg[c - ARGOFFSET], pp.macp->line);
							else
								PUSH_COPY(pp.macp->arg[c - ARGOFFSET], pp.macp->line);
							CACHEIN();
							bp = ip;
							goto fsm_get;
						}
						/*FALLTHROUGH*/
					case 'C':
						c = GETCHR() - ARGOFFSET;
						if (!*(s = pp.macp->arg[c]) && (pp.in->symbol->flags & SYM_VARIADIC) && pp.in->symbol->macro->arity == (c + 1))
						{
							s = ip - 3;
							while (--op > tp && --s > bp && ppisidig(*s));
						}
						else
						{
							SYNCIN();
							PUSH_COPY(s, pp.macp->line);
							CACHEIN();
						}
						bp = ip;
						goto fsm_get;
					case 'F':
						error_info.file = (char*)strtoul(ip, &s, 16);
						debug((-6, "actual sync: file = \"%s\"", error_info.file));
						bp = ip = s + 1;
						goto fsm_get;
					case 'L':
						error_info.line = strtoul(ip, &s, 16);
						debug((-6, "actual sync: line = %d", error_info.line));
						bp = ip = s + 1;
						goto fsm_get;
					case 'Q':
						c = GETCHR();
						SYNCIN();
						PUSH_QUOTE(pp.macp->arg[c - ARGOFFSET], pp.macp->line);
						CACHEIN();
						bp = ip - 1;
						if (st & (COLLECTING|EOF2NL|JOINING)) rp = fsm[START];
						if (state = rp[c = '"']) goto fsm_next;
						goto fsm_get;
					case 'S':
						c = GETCHR();
						SYNCIN();
						PUSH_SQUOTE(pp.macp->arg[c - ARGOFFSET], pp.macp->line);
						CACHEIN();
						bp = ip - 1;
						if (st & COLLECTING) rp = fsm[START];
						if (state = rp[c = '\'']) goto fsm_next;
						goto fsm_get;
					case 'X':
						if (pp.in->type != IN_COPY)
							st |= SKIPMACRO;
						if (pp.level <= 1)
						{
							bp = ip;
							goto fsm_get;
						}
						if (pp.in->type == IN_EXPAND)
						{
							st &= ~SKIPMACRO;
							PUTCHR(c);
							PUTCHR('X');
						}
						c = GETCHR();
						break;
					case 0:
						if ((state &= ~SPLICE) >= TERMINAL) goto fsm_terminal;
						goto fsm_begin;
					default:
#if DEBUG
						error(PANIC, "invalid mark op `%c'", LASTCHR());
						/*FALLTHROUGH*/
					case MARK:
#endif
#if CATSTRINGS
						if ((st & (JOINING|QUOTE)) == JOINING)
						{
							if (!INQUOTE(rp))
								PUTCHR(c);
						}
						else
#endif
#if CPP
						if (rp != fsm[START] && !INQUOTE(rp))
							UNGETCHR(c);
#else
						if (rp != fsm[START] && !INQUOTE(rp))
							UNGETCHR(c);
						else if (pp.level > 1)
							PUTCHR(c);
#endif
						break;
					}
					break;
				}
				break;
			case '?':
				/*
				 * trigraph
				 */

				if (pp.in->type == IN_FILE)
				{
					GET(c, n, tp, xp);
					if (n == '?')
					{
						GET(c, n, tp, xp);
						if (c = trigraph[n])
						{
							if ((st & WARN) && (st & (COMPATIBILITY|TRANSITION)) && !(pp.mode & HOSTED) && !INCOMMENT(rp))
								error(1, "trigraph conversion %c%c%c -> %c%s", '?', '?', n, c, (st & TRANSITION) ? "" : " inhibited");
#if COMPATIBLE
							if ((st & (COMPATIBILITY|TRANSITION)) != COMPATIBILITY)
							{
#endif
							*(bp = ip - 1) = c;
							if (state = rp[c]) goto fsm_next;
							goto fsm_get;
#if COMPATIBLE
							}
#endif
						}
						if (n != EOB) BACKIN();
						UNGETCHR(c = '?');
					}
					else if (n != EOB) BACKIN();
				}
				break;
			case '%':
			case '<':
			case ':':
				/*
				 * digraph = --trigraph
				 */

				if (pp.in->type == IN_FILE && (pp.option & PLUSPLUS))
				{
					m = 0;
					GET(c, n, tp, xp);
					switch (n)
					{
					case '%':
						if (c == '<') m = '{';
						break;
					case '>':
						if (c == '%') m = '}';
						else if (c == ':') m = ']';
						break;
					case ':':
						if (c == '%') m = '#';
						else if (c == '<') m = '[';
						break;
					}
					if (m)
					{
						if ((st & WARN) && (st & (COMPATIBILITY|TRANSITION)) && !(pp.mode & HOSTED) && !INCOMMENT(rp))
							error(1, "digraph conversion %c%c -> %c%s", c, n, m, (st & TRANSITION) ? "" : " inhibited");
#if COMPATIBLE
						if ((st & (COMPATIBILITY|TRANSITION)) != COMPATIBILITY)
						{
#endif
						*(bp = ip - 1) = c = m;
						if (state = rp[c]) goto fsm_next;
						goto fsm_get;
#if COMPATIBLE
						}
#endif
					}
					if (n != EOB) BACKIN();
				}
				break;
			case '\\':
				/*
				 * line splice
				 */

				if (pp.in->type == IN_FILE && (!(pp.option & PLUSSPLICE) || !INCOMMENTXX(rp)))
				{
					m = 0;
					GET(c, n, tp, xp);
					if ((pp.option & SPLICESPACE) && !INQUOTE(rp))
						while (n == ' ')
						{
							GET(c, n, tp, xp);
							m = 1;
						}
					if (n == '\r')
					{
						GET(c, n, tp, xp);
						if (n != '\n' && n != EOB)
							BACKIN();
					}
					if (n == '\n')
					{
#if CPP
						if (INQUOTE(rp))
						{
							if ((pp.option & STRINGSPLIT) && quot == '"')
							{
								PUTCHR(quot);
								PUTCHR(n);
								PUTCHR(quot);
							}
							else if (*pp.lineid)
							{
								PUTCHR(c);
								PUTCHR(n);
							}
							else
							{
								st |= HIDDEN;
								pp.hidden++;
							}
						}
						else
#else
#if COMPATIBLE
						if (!INQUOTE(rp) && (st & (COMPATIBILITY|DEFINITION|TRANSITION)) == (COMPATIBILITY|DEFINITION))
						{
							if (op == tp)
							{
								st |= HIDDEN;
								pp.hidden++;
								error_info.line++;
								if (st & SPACEOUT)
									goto fsm_start;
								c = (pp.option & SPLICECAT) ? '\t' : ' ';
								PUTCHR(c);
								goto fsm_check;
							}
							UNGETCHR(n);
							state &= ~SPLICE;
							goto fsm_terminal;
						}
#endif
#endif
						{
							st |= HIDDEN;
							pp.hidden++;
						}
#if CPP
						spliced++;
#else
						error_info.line++;
#endif
						bp = ip;
						goto fsm_get;
					}
					else if ((n == 'u' || n == 'U') && !INQUOTE(rp))
					{
						PUTCHR(c);
						PUTCHR(n);
						bp = ip;
						goto fsm_get;
					}
#if COMPATIBLE
					else if ((st & (COMPATIBILITY|TRANSITION)) == COMPATIBILITY && (n == '"' || n == '\'') && !INQUOTE(rp))
					{
						PUTCHR(c);
						PUTCHR(n);
						bp = ip;
						goto fsm_get;
					}
#endif
					else if (n != EOB)
						BACKIN();
					if (m && INSPACE(rp))
						UNGETCHR(c);
				}
#if COMPATIBLE
				else if ((st & (COMPATIBILITY|TRANSITION)) == COMPATIBILITY && !INQUOTE(rp))
				{
					GET(c, n, tp, xp);
					if (n == '"' || n == '\'')
					{
						PUTCHR(c);
						PUTCHR(n);
						bp = ip;
						goto fsm_get;
					}
					if (n != EOB)
						BACKIN();
				}
#endif
				break;
			case '\r':
				/*
				 * barf
				 */

				if (pp.in->type == IN_FILE)
				{
					GET(c, n, tp, xp);
					if (n == '\n')
					{
						*(bp = ip - 1) = c = n;
						if (state = rp[c]) goto fsm_next;
						goto fsm_get;
					}
					if (n != EOB) BACKIN();
				}
				break;
			case CC_sub:
				/*
				 * barf & puke
				 */

				if ((pp.option & ZEOF) && pp.in->type == IN_FILE)
				{
					pp.in->flags |= IN_eof;
					c = 0;
					state = S_EOB;
					goto fsm_terminal;
				}
				break;
			}
			if ((state &= ~SPLICE) >= TERMINAL)
				goto fsm_terminal;
			PUTCHR(c);
			goto fsm_begin;
		}
#if CPP
		if (INOPSPACE(rp))
		{
			BACKIN();
			goto fsm_start;
		}
#endif
		PUTCHR(c);
		bp = ip;
		goto fsm_get;
	}
#if !CPP
 fsm_token:
	st &= ~NEWLINE;
	if (pp.level == 1)
	{
		pp.in->flags |= IN_tokens;
		if (st & NOTEXT)
		{
			BACKOUT();
			goto fsm_top;
		}
 fsm_symbol:
		count(token);
	}
 fsm_check:
	if (st & SKIPCONTROL)
	{
		BACKOUT();
		goto fsm_start;
	}
 fsm_return:
#if CPP
	error_info.line += spliced;
#endif
	SETCHR(0);
	debug((-5, "token[%d] %03o = %s", pp.level, c, pptokstr(tp, 0)));
	SYNC();
	pp.level--;
	error_info.indent--;
	return c;
#endif
}

#if CPP && POOL

#include <ls.h>
#include <wait.h>

/*
 * output pool status on exit
 */

static void
poolstatus(void)
{
	error(ERROR_OUTPUT|0, pp.pool.output, "%d", error_info.errors != 0);
}

/*
 * loop on < input output >
 */

static void
pool(void)
{
	char*	ifile;
	char*	ofile;

	ppflushout();
	if (!sfnew(sfstdin, NiL, SF_UNBOUND, pp.pool.input, SF_READ))
		error(ERROR_SYSTEM|3, "cannot dup pool input");

	/*
	 * kick the -I cache
	 */

	ppsearch(".", T_STRING, SEARCH_EXISTS);

	/*
	 * loop on < input output >
	 */

	pp.pool.input = 0;
	while (ifile = sfgetr(sfstdin, '\n', 1))
	{
		if (!(ofile = strchr(ifile, ' ')))
			error(3, "%s: pool output file expected", ifile);
		*ofile++ = 0;
		waitpid(0, NiL, WNOHANG);
		switch (fork())
		{
		case -1:
			error(ERROR_SYSTEM|3, "cannot fork pool");
		case 0:
			atexit(poolstatus);
			error_info.errors = 0;
			error_info.warnings = 0;
			close(0);
			if (open(ifile, O_RDONLY))
				error(ERROR_SYSTEM|3, "%s: cannot read", ifile);
			close(1);
			if (open(ofile, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) != 1)
				error(ERROR_SYSTEM|3, "%s: cannot create", ofile);
			pp.outfile = ofile;
			pathcanon(ifile, 0);
			ifile = ppsetfile(ifile)->name;
#if CHECKPOINT
			if (pp.mode & DUMP)
			{
				if (!pp.pragma)
					error(3, "#%s must be enabled for checkpoints", dirname(PRAGMA));
				(*pp.pragma)(dirname(PRAGMA), pp.pass, keyname(X_CHECKPOINT), pp.checkpoint, 1);
			}
#endif
			PUSH_FILE(ifile, 0);
			return;
		}
	}
	while (wait(NiL) != -1);
}

#endif
