Google
 

Trailing-Edge - PDP-10 Archives - SRI_NIC_PERM_FS_1_19910112 - c/kcc/ccpp.c
There is 1 other file named ccpp.c in the archive. Click here to see a list.
/*	CCPP.C - New KCC Preprocessor
**
**	(c) Copyright Ken Harrenstien 1989
**
*/

#ifndef DEBUG_PP
#define DEBUG_PP 1	/* Include debugging printout capability */
#endif

#include "cc.h"
#include "ccchar.h"
#include "cclex.h"
#include <time.h>	/* Needed for __DATE__ and __TIME__ */
#include <string.h>
#include <stdlib.h>	/* malloc, free, atoi */

/* Imported functions used here */
extern SYMBOL *symfind();	/* CCSYM to look up macro or identifier */
extern SYMBOL *symgcreat();	/* CCSYM to create a macro name */
extern SYMBOL *symfnext(),	/* CCSYM */
		*shmacsym();
extern void freesym();		/* CCSYM */
extern long pconst();		/* CCSTMT to parse constant expr */
extern char *estrcpy(), *fstrcpy();	/* CCASMB */
extern int symval();		/* CCASMB */
extern int nextoken();		/* CCLEX */

/* Exported functions */
void ppinit();		/* Initialize the input preprocessor. (CC) */
void ppdefine();	/* Called to initialize predefined macros (CC) */
void passthru();	/* Invoked by -E for simple pass-thru processing (CC)*/
int nextpp();		/* Read next token from input (CCLEX) */
void pushpp();		/* Push back current token (CCLEX) */

/* Internal functions */
static void dotad();
static int nextch(), tchesc(), pushstr();
static void pushch(), bstrpush(), bstrpop();
static int nextrawpp(), scanhwsp(), ppnconst(), ppunknwn();
static void ppsconst(), scancomm();
static int nextmacpp();
static void pushmp(), mtlpush(), mtlpop();
static int findident();
static int mdefinp();

static void directive();
static int d_define(), d_undef(), d_asm(), d_endasm(),
	d_ifdef(), d_if(), d_else(), d_elif(), d_endif(),
	d_include(), d_line(), d_error(), d_pragma();
static int iftest();
static void iffwarn(), ifpush(), ifpopchk();
static void flushcond(), filepush();
static SYMBOL *findmacsym();
static void freemacsym();
static int tskipwsp(), tskiplwsp(), cskiplwsp(), flushtoeol();
static void checkeol();
static int getfile();

/* The main global variables of interest to callers (i.e. CCLEX) are those
** set by nextpp(), as follows:
**
**	curpp - Current token (a tokdef enum)
**	curval - Token value (either unused or a string pointer)
**	curptr - Pointer to corresponding pptok in tokenlist, if any.
**	cursym - Pointer to symbol, if curpp == T_IDENT.  This will have
**			class SC_UNDEF if sym didn't exist previously.
*/

/* Note that most routines operate, or begin to operate, on the current
 * character in "ch", rather than immediately reading the next char.  When
 * a character is completely processed and is not needed any more, nextch()
 * must be called in order to get rid of it and set up a new char for
 * whatever will be next looking at the input.  Occasionally "ch" is
 * set directly for proper "priming".
 * This is to avoid having to call pushch() whenever a token ends.
 */

/* Include file nesting stack - holds saved input file context.
 *	Indexed by inlevel.
 */
static int inlevel;	/* 0 - top level */
static struct {
	FILE	*cptr;	/* file pointer  */
	filename cname;	/* filename      */
	int	cpage;	/* page number   */
	int	cline;	/* line number in page */
	int	cfline;	/* line number in file */
} inc[MAXINCLNEST];

/* #if handling variables.  Arrays are indexed by iflevel. */
static int iflevel,	/* #if-type command nesting level (0 = none) */
    flushing;		/* Set = iflevel when inside failing conditional */
static int ifline[MAXIFLEVEL];	/* Line # that last if/elif/else was on */
static int iffile[MAXIFLEVEL];	/* File # for ditto. 0 for current file, */
				/* -1 for expired file, N for inc[N-1] */
static int iftype[MAXIFLEVEL];	/* iftype[lvl] set to one of the following: */
#define IN_ELSE	0		/* inside an #else, or nothing.  Must be 0 */
#define IN_IF	1		/* inside an #if */
#define IN_ELIF	2		/* inside an #elif */
static char *ifname[] = { "else", "if", "elif" };	/* Indexed by iftype */


static int indirp;	/* Set when handling a directive */
static int inasm;	/* Set when inside assembly passthrough */
static FILE *prepfp;	/* Set to output stream when doing -E */
/* Pre-Processing TokenList definitions */

#ifndef MAXPPTOKS
#define MAXPPTOKS 2000
#endif
#ifndef MAXPOOLSIZE
#define MAXPOOLSIZE 4000
#endif

typedef struct {		/* TokenList descriptor type */
	PPTOK *tl_head;
	PPTOK *tl_tail;
} tlist_t;

static PPTOK *pptptr;		/* Ptr to last allocated token */
static PPTOK pptoks[MAXPPTOKS];	/* Tokens allocated from here */
#define PPTMAX (&pptoks[MAXPPTOKS-1])	/* Highest valid ptr */

#define pptreset() (pptptr = pptoks)	/* Flush all tokens, start over */

#define tokn2p(n) (n)		/* Return ptr, given next-ptr */
#define tokp2n(p) (p)		/* Return next-ptr, given ptr */
#define tokpcre() (++pptptr <= PPTMAX ? pptptr : ppterror())	/* Create */
#define tlpinit(tl,p) ((tl).tl_head=(tl).tl_tail=(p))	/* Init w/ptr to tok */
#define tlzinit(tl) tlpinit(tl,0)		/* Init empty list */
#define tlpcur(tl) ((tl).tl_head)		/* Get ptr to 1st token */
#define tltcur(tl) (*tlpcur(tl))		/* Get 1st token */
#define tlppeek(tl) ((tl).tl_head->pt_nxt)	/* Ptr to next token */
#define tltpeek(tl) (*tlppeek(tl))		/* Peek at next token */
#define tlpskip(tl) ((tl).tl_head=tlppeek(tl))	/* Skip this token */

#define tlpadd(tl,p) (!(tl).tl_head \
	? tlpinit(tl,p) \
	: ((tl).tl_tail = (tl).tl_tail->pt_nxt = (p))) /* Add ptr at tail */
#define tltadd(tl,t) (*tlpadd(tl,tokpcre()) = (t))	/* Add tok at tail */
#define tlpins(tl,p) (!((p)->pt_nxt = (tl).tl_head) \
	? tlpinit(tl,p) \
	: ((tl).tl_head = (p)))		/* Insert ptr at head of list */

#define tllapp(tl,il) ((tl).tl_tail->pt_nxt = tokp2n((il).tl_head), \
			(tl).tl_tail = (il).tl_tail)	/* Append list */
#define tllins(tl,il) ((il).tl_tail->pt_nxt = tokp2n((tl).tl_head), \
			(tl).tl_head = (il).tl_head)	/* Insert list */


/* Various TokenList declarations */
static tlist_t tlfrmac(), tlfrstr(), tlmake(), tlimake(), tlcopy(), tlstrize();
static char *tltostr(), *tltomac();
static PPTOK tokize();
static void tlrawadd();
static void ppcqstr(), ppctstr();
static int pptfput(), tkerr();
static tlist_t getlinetl(), tlwspdel();
static tlist_t asmrefill();
/* PP-Token char pool.  This is reset and the contents flushed whenever
** nextpp() is asked to read a new token at top level, by setting ppcptr NULL.
** Several handy macros are defined to use this pool.
**	ppcreset() - Resets pool, flushes all chars
**	ppcbeg() - Start depositing into pool, returns ptr to start
**	ppcput(c) - Put char into pool, returns c
**	ppclast() - Returns last char put into pool
**	ppclen() - Returns # chars deposited thus far
**	ppcend() - Ends deposit into pool, returns # chars
**			(invokes fatal error if overflowed)
**	ppcbackup() - Backs up over last char, can be used to undo ppcend().
**	ppcsave(st) - Saves state in "st" (type "ppcstate_t")
**	ppcrest(st) - Restores state from "st"
*/
static char *ppcptr = NULL;	/* Ptr into ppcpool */
static int ppcleft;		/* # chars left in ppcpool */
static int ppcocnt;		/* Saved ppcleft for deriving string lens */
typedef struct {		/* State struct for saving above vars */
	char *cptr; int cleft; int cocnt;
} ppcstate_t;
static char ppcpool[MAXPOOLSIZE];
#define ppcreset() (ppcptr = NULL)
#define ppcbeg() (!ppcptr?(ppcocnt=ppcleft=MAXPOOLSIZE-1,(ppcptr=ppcpool)+1) \
			: (ppcocnt=ppcleft, ppcptr+1))
#define ppcput(c) (--ppcleft > 0 ? *++ppcptr = (c) : (c))
#define ppclast() (*ppcptr)
#define ppclen() (ppcocnt - ppcleft)
#define ppcend() (--ppcleft > 0 ? (*++ppcptr = 0, ppclen()-1) : ppcerror())
#define ppcbackup() (++ppcleft, *--ppcptr)
#define ppcsave(st) \
	(void)(st.cptr = ppcptr, st.cleft = ppcleft, st.cocnt = ppcocnt)
#define ppcrest(st) \
	(void)(ppcptr = st.cptr, ppcleft = st.cleft, ppcocnt = st.cocnt)

static int ppcerror();		/* Just invokes efatal(CPOOL) */

/* Raw Tokenizer variables - nextrawpp() */
static int rawpp;		/* Current raw token type from nextrawpp() */
static union pptokval rawval;	/* Current raw token value */
static int rawpplen;		/* Length of token string, if any */
static PPTOK *rawptr;		/* Ptr to current raw pptoken, if any */

/* Macro Tokenizer - nextmacpp() */
static int mactlev = 0;		/* # levels nesting for nextmacpp() input */
static tlist_t mactl;		/* Current tokenlist nextmacpp() is using */
static tlist_t mactls[MAXMACNEST];	/* Stack of input tokenlists */

/* Cooked Tokenizer - nextpp()  (also declared by CCLEX.H)*/
extern int curpp;		/* Current cooked token type from nextpp() */
extern union pptokval curval;	/* Current cooked token value */
static tlist_t curtl;		/* Current cooked token list, if any */
extern PPTOK *curptr;		/* Ptr to current cooked pptoken, if any */
extern SYMBOL *cursym;		/* Ptr to symbol, if token is T_IDENT */

/* Raw character input variables - nextch() */
static int ch;			/* Current char awaiting tokenizer */
#define MAXBKSTRS (MAXINCLNEST+2)	/* Rough guess */
static char *backstr = NULL;	/* If non-NULL, ptr to pushback string */
static int bkstrlev = 0;	/* # of ptrs on saved pushback string stack */
static char *bkstrs[MAXBKSTRS];

#define TCH_ESC '\305'		/* Input string escape char */
#define TCH_EOF 'E'		/* Input string EOF */
/* MACRO DETAILS:
**	A defined macro is stored in the symbol table as a symbol with
** class SC_MACRO.  It has no type and uses these components:
**	Smacptr	- char pointer to the macro body (allocated by malloc)
**	Smacnpar - # of params/arguments (or special value)
**	Smacparlen - # chars at start of body that are actually param names.
**	Smaclen - total # chars in macro body string, not counting final NUL.
** The value of Smacnpar has the following meanings:
**	>= 0	# of arguments in argument list.  0 = mac(), 1 = mac(a), etc.
**	< 0	Special value, one of:
*/
#define MACF_ATOM (-1)	/* Normal atomic, no argument list (i.e. "mac") */
#define MACF_DEFD (-2)	/* "defined" operator.  No body. */
#define MACF_LINE (-3)	/* "__LINE__" macro.  No body. */
#define MACF_FILE (-4)	/* "__FILE__" macro.  No body. */
#define MACF_DATE (-5)	/* "__DATE__" macro.  No body. */
#define MACF_TIME (-6)	/* "__TIME__" macro.  No body. */
#define MACF_STDC (-7)	/* "__STDC__" macro.  No body. */
#define MACF_KCC  (-8)	/* "__COMPILER_KCC__" macro.  No body. */
#define MACF_SYMF (-9)	/* "_KCCsymfnd("file","sym")" macro. No body. */
#define MACF_SYMV (-10)	/* "_KCCsymval("file","sym")" macro. No body. */
/* All special macros other than MACF_ATOM cannot be redefined or
** undefined except by the command-line -U or -D switches.
*/

/* MACRO BODY FORMAT:
**
** Smacptr (if not null) points to the macro body, which consists of
** the tokenized text of the macro, perhaps prefixed by parameter names.
**	Smacptr ->  <"Smacparlen" chars of param-names>
**		    <tokenized text, see below>
**
** The parameter names are only checked during redefinition of a macro
** to see whether the new macro's params have the same spelling (CHOKE BLETCH).
**
** The text is entirely tokenized.  The first char of each token is the
** token type value, such as T_WSP or T_IDENT.  Following chars depend
** entirely on the token type.  There are currently three general types:
**	Simple token: just one char of token type and nothing else.
**	String token: token type is followed by the chars of the string, ending
**		with a zero byte.
**	Param token: the char after the token type is an encoded macro
**		parameter # -- see MAC_ARGOFF.
**
** In all cases, the next char after any of the above begins another token.
** A zero token marks the end of the macro body; this and not Smaclen is
** normally used to stop a scan.  This zero token is not included in the
** Smaclen count.
*/
#define MAC_ARGOFF '0'	/* Number to encode param # with */


/* Macro Frame definitions */
struct macframe {
	SYMBOL *mf_sym;		/* Pointer to macro symbol def */
	int mf_nargs;		/* # args we have */
	int mf_parlen;		/* # chars in param name prefix (bletch) */
	int mf_len;		/* # chars total in macro body string */
	char *mf_body;		/* Ptr to body of macro def */
	union {
	    tlist_t mfp_tls[MAXMARG];	/* Argument token lists (for expand) */
	    char *  mfp_cps[MAXMARG];	/* Parameter name strs (for define) */
	} mf_p;
	int mf_used[MAXMARG];	/* # times each arg is invoked */
};
#define mf_argtl mf_p.mfp_tls
#define mf_parcp mf_p.mfp_cps

#if 0
static nhidemacs = 0;		/* # macros in hideset */
#endif
static SYMBOL *hidemacs[MAXMACNEST];	/* Used by hspush() and mishid() */

static int maclevel;	/* Macro expansion level (used???) */

static char defcdmy;			/* Needed if not using "define" */
static char *defcsname = &defcdmy;	/* Ptr to "defined" macro sym name */

static int tadset;		/* True if date/time strings are set */
static char datestr[14] = "\"Jun 07 1989\"";	/* __DATE__ string */
static char timestr[11] = "\"01:23:45\"";	/* __TIME__ string */

static SYMBOL *mdefstr(), *mdefsym();
static int mexptop(), margs();
static tlist_t mexpand(), mexplim(), mexpsym(), msubst(), mpaste();
static int hspush(), tkhide(), mishid();

#if DEBUG_PP
static int debpp = 0;
static FILE *fpp = stderr;
static int debppt = 1;
static void pmacframe(), pmactl(), tlfput(), tkfput();
static char *plevindent();
#endif
/* PPINIT() - Invoke to initialize preprocessor at start of file scan.
**	Called once for each file compiled.
*/
void
ppinit()
{
    /* Set globals */
    eof = 0;
    tline = 0;
    fline = page = line = 1;

    /* Set variables local to preprocessor */
    ppcreset();
    pptreset();
    inlevel = iflevel = flushing = inasm = indirp = maclevel = 0;
    iftype[0] = 0;		/* Just in case */
    tadset = 0;			/* Date/time strings not set */
    erptr = errlin;		/* Init pointer to error-context buffer */
    erpleft = ERRLSIZE;		/* Set countdown of chars left in errlin */
    memset(erptr, 0, erpleft);	/* Clear the circular buffer */
    pushstr("\n");		/* Prime input with EOL, set up 1st char! */

    /* Enter special macro pre-definitions into symbol table. */
    mdefstr("__COMPILER_KCC__", MACF_KCC, NULL);
    mdefstr("__LINE__",		MACF_LINE, NULL);
    mdefstr("__FILE__",		MACF_FILE, NULL);
    if (clevel >= CLEV_CARM) {
	mdefstr("__DATE__", MACF_DATE, NULL);
	mdefstr("__TIME__", MACF_TIME, NULL);
	/* "defined" is a bit more special.  Need to remember ptr to
	** 1st char of the symbol name, so it can be turned on and off.
	*/
	defcsname = &(mdefstr("defined", MACF_DEFD, NULL)->Sname[0]);
	*defcsname = SPC_MACDEF;	/* Hide sym by smashing 1st char */
    }
    if (clevel >= CLEV_STDC) {		/* When we're fully ready */
	mdefstr("__STDC__", MACF_STDC, "1");
    }
    if (clevkcc) {
	mdefstr("_KCCsymfnd", MACF_SYMF, NULL);
	mdefstr("_KCCsymval", MACF_SYMV, NULL);
    }
}

/* DOTAD - Initialize datestr and timestr for __DATE__ and __TIME__.
**	This is only invoked if one of those macros is seen; otherwise,
**	the overhead is avoided.
*/
static void
dotad()
{
    register char *cp;
    time_t utad;

    if (time(&utad) == (time_t)-1) {
	warn("Cannot get date/time, using %s %s", timestr, datestr);
	return;			/* Leave both strings alone */
    }
    cp = ctime(&utad);		/* Get pointer to ascii date and time */

    /* Now have "Dow Mon dd hh:mm:ss yyyy\n\0" */
    /*		 012345678901234567890123 4 5  */
    strncpy(datestr+1, cp+4, 6);	/* Copy "Mon dd" */
    strncpy(datestr+8, cp+20, 4);	/* Copy "yyyy" */
    strncpy(timestr+1, cp+11, 8);	/* Copy "hh:mm:ss" */
    tadset = 1;				/* Strings now set! */
}
/* PPDEFINE(cruft) - Process startup #undefs and #defines requested by the
**	-U and -D switches in the main CC loop.
**	We assume the syntax has already been checked by chkmacname() in CC.
*/
void
ppdefine(unum, utab, dnum, dtab)
int unum, dnum;		/* # of strings in table */
char **utab, **dtab;	/* Address of char-pointer array */
{
    int savch;
    char *cp;
    SYMBOL *sym;

    /* First handle all -U undefinitions... */
    for ( ; --unum >= 0; utab++) {
	if (sym = findmacsym(*utab))	/* Look up the macro symbol */
	    freemacsym(sym);		/* Found it, flush it! */
	else note("Macro in -U%s doesn't exist", *utab);
    }

    /* Now handle all -D definitions. */
    for ( ; --dnum >= 0; dtab++) {
	cp = *dtab;			/* Get this -D string */
	while (*++cp && *cp != '=') ;	/* Scan for start of body if any */
	savch = *cp;			/* Remember terminator char */
	*cp = '\0';			/* Tie name off in case of '=' */
	if (sym = findmacsym(*dtab)) {	/* See if already defined */
	    advise("Redefining macro: -D%s", *dtab);	/* Ugh! */
	    freemacsym(sym);
	}
	mdefstr(*dtab, MACF_ATOM,	/* Define object-like macro */
		savch ? cp+1 : "1");	/* with "1" if no body. */
	*cp = savch;			/* Restore zapped char if any */
    }
}
/* PASSTHRU(fp) - Pass through text to file
**	Used only by -E (i.e. prepf is set)
**
** General strategy:
**	Attempts to do a fast scan through the input whenever possible, just
** reading and outputting on the character level.
** It necessarily has to simulate some of what nextrawpp() and nextpp()
** do.
**	(nextrawpp: handle comments, string-type literals)
**	(nextpp: handle directives, macro expansion)
**
** When it encounters a directive or macro, it punts and asks nextpp() to
** handle it, whereupon it just translates tokens until it determines
** that things are back at top level again.  This return of control may
** be tricky.
*/
static void pass_ident(), pass_num(), pass_hwsp(), pass_line(), pass_comm();
static int pass_str();
static FILE *passfp;

void
passthru(fp)
FILE *fp;
{
    prepfp = fp;		/* Set for benefit of ppasm() */
    passfp = fp;

#if DEBUG_PP
    if (debpp && debppt) {		/* Do trivial passthru */
	while (nextpp() != T_EOF) {
	    pptreset(); ppcreset();
	    pptfput(tlpcur(tlmake(curpp, curval)), fp);
	}
	return;
    }
#endif

    /* Note at start of file, ch should always contain \n as "first" char */
    for (;;) switch (ch) {	/* Scan starting with current char */
    case EOF:			/* Done when hit EOF */
	 return;

    /* Check for whitespace other than new-line */
    case '\r':			/* Include CR here */
    case '\v':			/* Vert tab */
    case '\f':			/* Ditto FF */
    case '\t':			/* Horiz tab */
    case ' ':			/* Space */
	if (keepcmts) {		/* If keeping comments, */
	    putc(ch, fp);	/* just pass it all */
	    nextch();
	} else pass_hwsp();	/* Flushing, so try to be clever */
	continue;

    /* Newline needs special handling (maybe directives, etc) */
    case '\n':
	pass_line(0);
	continue;

    /* Check for possible start of comment */
    case '/':
	if (nextch() == '*')
	    pass_comm();
	else putc('/', fp);
	continue;

    default:
	if (iscsymf(ch)) {	/* If start of identifier, check for macro */
	    pass_ident(fp);
	    continue;
	}
	putc(ch, fp);		/* Normal char, pass it on */
	nextch();		/* Get next */
	continue;

    case '.':			/* '.' may start a pp-number */
	putc('.', fp);
	if (!isdigit(nextch()))
	    continue;		/* Nope, handle whatever it is */
	/* Is pp-number, drop thru to handle it */

    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
	pass_num();
	continue;

	/* Pass through string constants.  Note that character constants
	** (e.g. 'a') are handled a little differently because they shouldn't
	** be longer than the largest possible char constant.
	*/
    case '\'':	pass_str(tgcpw+2);	continue;	/* Char constant */
    case '"':	pass_str(0);		continue;	/* String literal */

    case '`':			/* Quoted identifier (maybe) */
	if (clevkcc) {		/* If KCC extensions allowed, */
	    pass_str(0);	/* parse like string! */
	    continue;
	}
	putc(ch, fp);		/* Normal char, pass it on */
	nextch();		/* Get next */
	continue;

    }				/* End of loop */
}

/* PASS_IDENT - auxiliary for passthru() and d_asm() to parse an
**	identifier and either pass it along or expand it if it's a macro.
**	Current char is the first char of the identifier.
**	Returns with current char the 1st thing after ident (or expansion).
*/
static void
pass_ident(fp)
FILE *fp;
{
    SYMBOL *sym;
    register PPTOK *p;
    register char *cp;

    cp = ppcbeg();
    do { ppcput(ch);		/* Gobble up ident starting with current ch */
    } while (iscsym(nextch()));
    (void)ppcend();		/* Tie off so can look it up */

    if (!(sym = findmacsym(cp))	/* If not a macro, */
      || !mexptop(sym, 0)) {	/* or can't expand it, */
	fputs(cp, fp);		/* just output the identifier. */
	ppcreset();		/* Clean up token char pool */
	return;
    }
    /* Expanded macro!
    ** Since mexptop() leaves a tokenlist in curtl, we grab that and
    ** translate it into a string that is then output, with special
    ** quoting if in #asm.
    */
    if (!inasm)
	for (; p = tlpcur(curtl); tlpskip(curtl))
	    pptfput(p, fp);
    else {			/* in #asm, must do special quoting */
	curtl = tlstrize(curtl);	/* Turn into giant string literal */
	cp = curtl.tl_head->pt_val.cp;	/* Get ptr to start */
	cp[strlen(cp)-1] = '\0';	/* Chop '"' off end */
	fputs(cp+1, fp);		/* And skip '"' at beg */
    }
    tlzinit(curtl);	/* Ensure curtl flushed out */
    ppcreset();		/* Also be safe and flush stg used */
    pptreset();
}

/* PASS_NUM - auxiliary for PASSTHRU() to pass a PP-number.
**	Returns with delimiting char still in CH, not yet output.
*/
static void
pass_num()
{
    register int i;

    for (;;) {
	putc((i = ch), passfp);	/* Pass char, remember it */
	switch (nextch()) {
	case '+':
	case '-':		/* Sign chars are part of number, */
	    if (toupper(i) != 'E')	/* if prev char was E */
		return;		/* Oops, gotta stop */
	case '.':
	    break;
	default:
	    if (!iscsym(ch))
		return;
	    break;
	}
    }
}

/* PASS_STR - auxiliary for PASSTHRU() to parse a string-type literal
**	Returns with delimiting char still in CH, not yet output.
**	Return value is 0 if EOF encountered, else non-zero.
*/
static int
pass_str(cnt)
int cnt;		/* If non-zero, max # chars of string to read */
{
    int delim = ch;		/* Delimiter char */

    putc(ch, passfp);		/* Send initial delimiter */
    while (nextch() != EOF
      && ch != '\n'
      && --cnt != 0) {		/* Note test is != not > */
	putc(ch, passfp);
	if (ch == delim) {	/* If that was end of string, */
	    nextch();		/* set up next char and return. */
	    return;
	}
	if (ch == '\\')		/* Handle backslash */
	    putc(nextch(), passfp);	/* by quoting next char */
    }

    /* Either EOF, or char length exceeded, or illegal char (newline).
    ** For now, we don't complain about anything but EOF.
    */
    if (ch == EOF)
	error("Unexpected EOF within %s",
		(  delim == '\"' ? "string literal"
		: (delim == '\'' ? "char constant"
				 : "quoted identifier")));
}

/* PASS_HWSP - auxiliary for PASSTHRU() to scan horiz whitespace NOT at
**	start of a line, and not being kept.  Just reduce to one space,
**	unless hit EOL in which case do nothing.
**	Returns with delimiting char still in CH, not yet output.
*/
static void
pass_hwsp()
{
    for (;;) {
	while (ischwsp(nextch()));	/* Flush all horiz wsp */
	if (ch == '/') {
	    if (nextch() == '*') {	/* Start of comment? */
		scancomm();		/* Flush it!  Leaves last '/' in ch */
		continue;
	    } else {
		fputs(" /", passfp);
		return;
	    }
	}
	break;
    }
    if (ch != '\n')
	putc(' ', passfp);
}

/* PASS_NL - auxiliary for PASSTHRU() to handle start of a line.
**	Must check for directives, etc.
**	If keeping comments, pass on all whitespace & comments, up to
**		first non-whitespace.
**	If not keeping comments, store all whitespace in temp buffer
**		(using 1 space for each comment) up to 1st non-wsp.
**	If 1st non-wsp is "#" then invoke directive processing.
**	If 1st non-wsp is EOL just re-invoke without outputting an EOL.
**	Returns with delimiting char still in CH, not yet output.
*/
static void
pass_line(suppress)
{
    char wspbuf[120];	/* # chars of leading whitespace to save */
    register char *cp = wspbuf;
    register int cnt = sizeof(wspbuf)-2;

    if (!suppress)
	putc('\n', passfp);		/* First must end any current line */
    if (!keepcmts || inasm) {
	for (;;) {			/* Flush whitespace and comments */
	    switch (nextch()) {
	    case '/':
		if (nextch() != '*') {
		    pushch(ch);
		    ch = '/';
	    default:
		    break;
		}
		pass_comm();
		/* Drop thru to output single space */
	    case ' ': case '\t':
	    case '\r': case '\v': case '\f':
		if (--cnt > 0) *cp++ = ch;
		continue;
	    case '\n':
		pass_line(++suppress);	/* Tail recurse, don't output EOL */
		return;
	    }
	    *cp = 0;			/* Tie off */
	    break;
	}
    } else {			/* Keeping comments */
	for (;;) {			/* Flush whitespace and comments */
	    if (ischwsp(nextch()))
		putc(ch, passfp);
	    else if (ch != '/')
		break;
	    else if (nextch() == '*')
		pass_comm();
	    else {
		pushch(ch);
		ch = '/';
		break;
	    }
	}
    }

    if (ch == '#') {
	nextch();		/* Must move past it to set up... */
	directive();		/* Process directive! */
	while (inasm && nextpp() != T_EOF) {	/* If in #asm, do hack */
	    pptreset(); ppcreset();
	    pptfput(tlpcur(tlmake(curpp, curval)), passfp);
	}
	if (ch != EOF)		/* Sigh, must put back */
	    pushch(ch);		/* the 1st char of beg of line */
	pass_line(++suppress);		/* Then can tail recurse! */
	return;
    } else if (!keepcmts)
	fputs(wspbuf, passfp);	/* Output saved whitespace up to here */
}

/* PASS_COMM - auxiliary for PASSTHRU() to scan over a comment.
**	Current char is the '*' starting comment.
**	Returns with a space in CH; next input char will be 1st char
**	after the '/' that ended comment.
*/
static void
pass_comm()
{
    if (!keepcmts) {
	scancomm();		/* Flush the comment! */
    } else {
	/* Comment skip for -E when retaining comments (-C) */
	fputs("/*", passfp);		/* Pass comment start */
	nextch();
	for (;;) {			/* until we break */
	    putc(ch, passfp);		/* send char off */
	    if (ch != '*') nextch();	/* find a star */
	    else if (nextch() == '/') {	/* and a slash to terminate */
		putc('/', passfp);
		break;			/* Stop loop */ 
	    }
	    if (eof) {			/* Also stop if input gone */
		error("Unexpected EOF in comment");
		break;
	    }
	}
    }

    ch = ' ';		/* Pretend current char now a space */
}
#if 0

nextch() 	Phase 1, 2 - produce source chars & logical lines
			(handles trigraphs, \-newlines)
nextrawpp()	Phase 3 - produce raw PP tokens
			(handles comments, flushes WSP)
nextmacpp()	Phase 3.5 - used to redirect source of raw tokens
			during macro expansion
nextpp()	Phase 4 - produce clean expanded PP tokens
			(handles directives, macro expansion, flushes EOLs)
nextoken()	Phase 5, 6, 7 - Produce C tokens from clean PP tokens
			(handles char escapes, string concatenation, token
				conversion & constant parsing)

There are 4 types of scanning, differentiated by the different end results
of the scan.  Each of them invokes at some point the types below it.
	A. Toplevel (language) tokenizing	(Phase 1-7)
	B. Macro/directive (preproc) tokenizing	(Phase 1-4)
	C. Passthru scanning (for -E and #asm)	(Phase 1-4)
	D. Passover scanning (for #if skipping)	(Phase 1-3)
#endif
/* NEXTCH() - Get next character.
**	Implements Translation Phase 1 and 2.
**	Handles trigraphs and quoted newlines ('\\' '\n').
**
**	Because the value returned by nextch() must be pushable by pushch(),
**	and because we cannot call pushch() twice in a row on file input
**	(otherwise STDIO's ungetc() complains), we have to divert input
**	to come from a string whenever this routine does need to call pushch().
**	This is why the calls to pushstr() exist.
**
**	Note: implement IESC escape char similar to MACESC so can
**	easily insert special tokens (like temporary EOF) into input.
*/
static int
nextch()
{
    register int prevch = ch;		/* Remember previous char */

    /* First check for pushed-back string */
    if (backstr) {			/* Pushback string exists? */
	if (ch = *backstr++)		/* Yes, are there more chars? */
	    return (ch != TCH_ESC)	/* Yes, is this a special escape? */
		? ch			/* Nope, just return char. */
	        : (ch = tchesc());	/* Aha!  Handle escape stuff! */
	bstrpop();			/* End of a pushback string, pop it */
	ch = prevch;			/* Ensure "prev chr" isn't NUL */
	return nextch();		/* and try for a new char */
    }

    /* Just get normal input char */
    ch = getc(in);

    /* Add char to small circular buffer for possible error messages */
    *erptr++ = ch;		/* Add to saved input line. */
    if (--erpleft <= 0) {	/* If reached end of buffer, */
	erptr = errlin;		/* wrap back around. */
	erpleft = ERRLSIZE;
    }

    switch (ch) {
    case EOF:			/* End Of File on input */
				/* Do a couple of checks to verify */
	if (ferror(in))
	    error("I/O error detected while reading file %s", inpfname);
	else if (!feof(in))
	    int_error("nextch: spurious EOF");

	if (prevch != '\n'	/* Was last char in file an EOL? */
	  && prevch != EOF) {
	    warn("File does not end with EOL (\\n)");	/* Nope */
	    return pushstr("\n");	/* Attempt graceful termination */
	}				/* by providing EOL as last char. */

	if (inlevel > 0) {		/* Drop level.  If more, pop input. */
	    ifpopchk(inlevel);		/* Do balanced conditional check */
	    fclose(in);			/* Close this file */
	    --inlevel;
	    in = inc[inlevel].cptr;	/* then set vars from popped level */
	    page = inc[inlevel].cpage;
	    line = inc[inlevel].cline;
	    fline = inc[inlevel].cfline;
	    strcpy(inpfname, inc[inlevel].cname);
#if DEBUG_PP
	    if (debpp) {
		fprintf(fpp, "#include %d: restored \"%s\", new char %#o\n",
			inlevel+1, inpfname, nextch());
		return ch;
	    }
#endif
	    return nextch();		/* try for another char */
	}

	/* EOF at top level (main input file).  We DON'T close the
	** stream at this point, so that it's OK to call this routine again.
	** Some preproc/parsing routines want to just punt on EOF and let a
	** higher level take care of it.  The CC mainline fcloses the stream.
	*/
	if (eof) break;		/* If already seen EOF, needn't redo stuff. */
	eof = 1;		/* Flag end of file at top level. */
	if (iflevel)		/* Error if preprocessor unbalanced */
	    ifpopchk(0);	/* Spit out helpful error message */
	break;			/* Return EOF char */

    case '?':			/* Goddam fucked-up trigraph hackery */
    {	static int skipflg;	/* TRUE if skipped a '?' */
	static int intrig = 0;	/* TRUE if in this code already */

	if (clevel < CLEV_STDC)	/* Only if STDC */
	    break;
	if (intrig) break;	/* If already scanning, just return char. */
	++intrig;		/* Say scanning trigraph! */
	if (!skipflg		/* If we didn't skip a '?' already, */
	  && nextch() != '?') {	/* OK to read next char.  Two in a row? */
	    pushch(ch);			/* Nope, push back peeked-at char */
	    intrig = 0;			/* No longer scanning */
	    return pushstr("?");	/* Return original q-mark */
	}
	nextch();		/* Got two '?'s in row!  Scan for third... */
	intrig = 0;		/* No longer scanning */
	skipflg = 0;		/* Skip hackery done */
	switch (ch) {		/* Dispatch on 3rd char! */
	    case '=':	ch = '#'; break;
	    case '(':	ch = '['; break;
	    case '/':	ch = '\\'; break;
	    case ')':	ch = ']'; break;
	    case '\'':	ch = '^'; break;
	    case '<':	ch = '{'; break;
	    case '!':	ch = '|'; break;
	    case '>':	ch = '}'; break;
	    case '-':	ch = '~'; break;
	    default:		/* Not a trigraph, but simple pushback. */
		pushch(ch);	/* Make use of our 1-char file backup */
		return pushstr("??");	/* and OK to use string for other 2 */

	    case '?':		/* ARGH BARF not a trigraph, big screwup. */
		/* This is a screw because on the next call we have to
		** make sure that re-examination starts again -- just pushing
		** a string back won't work right.  We also can't count on
		** having more than 1 char of file backup!
		** The solution is to realize that we'll come here again when
		** the pushch'd '?' is read from getc(), so we don't try to
		** push back the middle '?' at all, and instead set a flag
		** that says it was "skipped"; when we do come back, we'll
		** check that flag and do the right thing.
		*/
		pushch(ch);		/* Push back 3rd '?' */
		skipflg = 1;		/* Say 2nd is skipped */
		return pushstr("?");	/* Return 1st */
	    }
	}
	break;

    case '\\':
	if (isceol(nextch())) {	/* Check next char */
	    ch = 0;		/* Quoted EOL is ignored totally, to extent */
	    return nextch();	/* of forgetting last char was EOL! */
	}
	pushch(ch);		/* otherwise put char back */
	return pushstr("\\");	/* and set up a pushch-able input */
				/* and return backslash */

    case '\f':			/* Form-feed */
	page++;			/* Starts new page */
	line = 1;		/* at first line */
	break;
    case '\r':
    case '\v':
    case '\n':
	line++;			/* new line, same page */
	fline++;
	tline++;
	break;
    }
    return ch;
}

/* TCHESC() - Handle token char escape.
**	backstr is set, and char it points to says what to do.
**	Returns char to be given to caller of nextch().
*/
static int
tchesc()
{
    int i;
    switch (i = *backstr++) {	/* Handle it */
	default:
	    int_error("tchesc: token escape char %d='%c'", i, i);

	case TCH_ESC:		/* Quoting escape char */
	    return i;

	case TCH_EOF:
	    backstr -= 2;	/* Bump back to point at TCH_ESC */
	    return EOF;		/* and return EOF as char */
    }
}

/* SINBEG(cp) - Make nextch() return input from given string.
** SINEND() - Stop using string input, restore previous source.
**	Note that nextch() will return EOF when the string input is done,
**	instead of popping input and continuing.
**	SINEND() must be called to proceed further.
** These routines are recursive, but could be simplified by forbidding
** recursion and just saving/restoring CH rather than calling pushch/nextch.
*/
static char eofstr[] = { TCH_ESC, TCH_EOF };

static void
sinbeg(cp)
char *cp;
{
    pushch(ch);		/* Save current char (since not tokenized yet) */
    bstrpush(eofstr);	/* Put EOF on end of input string */
    pushstr(cp);	/* and set up string for input! */
}
static void
sinend()
{
    while (backstr != eofstr) {		/* We should have hit string EOF... */
	int_error("sinend: leftover input");
	bstrpop();			/* Will get error if overpop */
    }
    bstrpop();		/* Take off EOF string, restore previous source */
    nextch();		/* Set up next char for tokenizer */
}

/* PUSHCH - Push back char for nextch()
** PUSHSTR - Push back string, sets up 1st char as current char
** BSTRPUSH - Push back string for ditto (save any current string)
** BSTRPOP - Pop pushback string, restore previous string
*/
static void
pushch(c)
int c;
{
    if (backstr) {
	backstr--;		/* pushed string input, back up over it */
	if(c != *backstr)
	    int_error("pushch: bad backup char");
	return;
    }

    /* File input, back up over that.
    ** Note that errlin and t/f/line only need to be fixed up for file input.
    */
    if (c != ungetc(c, in))
	int_error("pushch: ungetc failed: %o", c);
    if (isceol(c)) {
	line--;				/* dont lose track of line count */
	fline--;
	tline--;
    }
    /* Back up over char of saved context */
    if (erptr == errlin) {		/* If at start of buffer */
	erptr = errlin + ERRLSIZE;	/* Wrap back to end */
	erpleft = 0;
    }
    *--erptr = '\0';		/* Back up and zap what we previously stored */
    ++erpleft;
}

static int
pushstr(cp)
char *cp;
{
    bstrpush(cp);
    return nextch();
}

static void
bstrpush(cp)
char *cp;
{
    if (bkstrlev >= MAXBKSTRS-1) {
	int_error("bstrpush: bkstrs overflow");
	bkstrlev = MAXBKSTRS-1;
    } else {
	bkstrs[bkstrlev++] = backstr;
	backstr = cp;
    }
}

static void
bstrpop()
{
    if (--bkstrlev < 0) {
	int_error("bstrpop: bkstrs underflow");
	backstr = NULL, bkstrlev = 0;
    } else backstr = bkstrs[bkstrlev];
}
/* NEXTRAWPP()	Phase 3 - produce raw PP tokens
**			(handles comments, flushes WSP)
**
*/
static int
nextrawpp()
{
    rawval.i = 0;
    switch (ch) {

    case EOF:
	return rawpp = T_EOF;

    /* Check for "bad" horiz whitespace (may give error msg) */
    case '\r':			/* Include CR here for now */
    case '\v':			/* Vert tab */
    case '\f':			/* Ditto FF */
	pushch(ch);		/* Ensure scanhwsp will see this char */
	/* Drop thru to call scanner */

    /* Check for "good" horiz whitespace */
    case '\t':			/* Horiz tab */
    case ' ':			/* Space */
	return scanhwsp();

    case '\n':
	rawpp = T_EOL;
	break;

    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
	return ppnconst(0);		/* Numerical constant */

    case '\'':
	ppsconst(0);			/* Char constant */
	return rawpp = T_CCONST;

    case '"':
	ppsconst(0);			/* String constant */
	return rawpp = T_SCONST;

    case '.':
	if (isdigit(nextch()))		/* If followed by digit, */
	    return ppnconst('.');	/* is pp-number */
	if (ch != '.')
	    return rawpp = Q_DOT;	/* Not num constant, say op */
	if (nextch() != '.') {		/* Ellipsis "..."? */
	    pushch(ch);			/* Nope, push it back */
	    ch = '.';			/* And restore prev char */
	    return rawpp = Q_DOT;	/* Settle for just "." */
	}
	rawpp = T_ELPSIS;		/* ... */
	break;

#define retchk(chr,no,yes) { \
	if (nextch() != chr) \
	    return rawpp = no; \
	nextch(); \
	return rawpp = yes; \
	}

    case '#':	retchk('#', T_SHARP, T_SHARP2)		/* #  and ## */
    case '=':	retchk('=', Q_ASGN, Q_EQUAL)		/* =  and == */
    case '!':	retchk('=', Q_NOT, Q_NEQ)		/* !  and != */
    case '*':	retchk('=', Q_MPLY, Q_ASMPLY)		/* *  and *= */
    case '%':	retchk('=', Q_MOD, Q_ASMOD)		/* %  and %= */
    case '^':	retchk('=', Q_XORT, Q_ASXOR)		/* ^  and ^= */
    case '/':	/* Special, to check for comments! */
	switch (nextch()) {
	default:	    return rawpp = Q_DIV;	/* /  */
	case '=': nextch(); return rawpp = Q_ASDIV;	/* /= */
	case '*': scancomm(); return scanhwsp();	/* Start comment! */
	}

#define retchk2(ch1,ch2,no,yes1,yes2) \
	switch (nextch()) { \
	case ch1: nextch(); return rawpp = yes1; \
	case ch2: nextch(); return rawpp = yes2; \
	default:	    return rawpp = no; \
	}

    case '|':
	retchk2('=','|', Q_OR, Q_ASOR, Q_LOR)		/* |  and |= and || */

    case '&':
	retchk2('=','&', Q_ANDT, Q_ASAND, Q_LAND)	/* &  and &= and && */

    case '+':
	retchk2('=','+', Q_PLUS, Q_ASPLUS, T_INC)	/* +  and += and ++ */

    case '-':
	switch (nextch()) {
	    case '-':	rawpp = T_DEC; break;	/* -- */
	    case '=':	rawpp = Q_ASMINUS;break;/* -= */
	    case '>':	rawpp = Q_MEMBER; break;/* -> */
	    default: return rawpp = Q_MINUS;	/* -  */
	}
	break;

    case '>':
	switch (nextch()) {
	    default: return rawpp = Q_GREAT;		/* >  */
	    case '=':	rawpp = Q_GEQ; break;		/* >= */
	    case '>':	retchk('=', Q_RSHFT, Q_ASRSH)	/* >> and >>= */
	}
	break;

    case '<':
	switch (nextch()) {
	    default: return rawpp = Q_LESS;		/* <  */
	    case '=':	rawpp = Q_LEQ; break;		/* <= */
	    case '<':	retchk('=', Q_LSHFT, Q_ASLSH)	/* << and <<= */
	}
	break;

    case '(': rawpp = T_LPAREN; break;
    case ')': rawpp = T_RPAREN;	break;
    case ',': rawpp = T_COMMA;	break;
    case ':': rawpp = T_COLON;	break;
    case ';': rawpp = T_SCOLON; break;
    case '?': rawpp = Q_QUERY;	break;
    case '[': rawpp = T_LBRACK; break;
    case ']': rawpp = T_RBRACK; break;
    case '{': rawpp = T_LBRACE; break;
    case '}': rawpp = T_RBRACE; break;
    case '~': rawpp = Q_COMPL;	break;

    case '`':			/* KCC extension: identifier quoter */
	if (clevkcc) {		/* If extensions in effect, */
	    ppsconst(0);	/* parse like string literal! */
	    return rawpp = T_IDENT;
	}
	return ppunknwn();	/* Else return unknown token */

    default:
	if (!iscsymf(ch))	/* Can char be start of identifier? */
	    return ppunknwn();	/* Nope, return unknown token */

	/* Handle identifier! */
	rawval.cp = ppcbeg();	/* Assume will be ident, harmless if not */
	if (ch == 'L') {	/* Possible "wide char" indicator? (BARF!!) */
	    switch (nextch()) {
		case '\'':	ppsconst('L'); return rawpp = T_CCONST;
		case '"':	ppsconst('L'); return rawpp = T_SCONST;
	    }
	    ppcput('L');	/* Not a wide char, recover from peekahead */
	    if (!iscsym(ch))	/* Was it just "L"?  Barf, barf */
		return rawpplen = ppcend(), rawpp = T_IDENT;
	}
	do { ppcput(ch);	/* Gobble up identifier */
	} while (iscsym(nextch()));
	rawpplen = ppcend();
	return rawpp = T_IDENT;
    }				/* End of switch(ch) */

    nextch();			/* Done, set up next char after this token */
    return rawpp;
}

/* SCANHWSP - gobbles all horiz whitespace starting with current char, and
**	returns a T_WSP token.
*/
static int
scanhwsp()
{
    for (;;) {
	while (iscppwsp(nextch())) ;	/* Skip all valid PP whitespace */
	switch (ch) {
	case '\r':
	    if (nextch() == '\n') {	/* Peek ahead */
		note("Stray '\\r' seen, ignoring");
		break;
	    } else {
		pushch(ch);
		ch = '\r';	/* put back and drop through */
	    }
	case '\v':
	case '\f':
	    if (indirp) {	/* Processing directive between # and EOL? */
		error("invalid whitespace in directive: '\\%#o'", ch);
		nextch();
		return rawpp = T_EOL;
	    }
	    continue;
	case '/':		/* Possible comment! */
	    if (nextch() == '*') {
		scancomm();	/* Yep, flush comment */
		continue;	/* and go get next char after comment */
	    }
	    /* Ugh, not a comment, must back up and leave '/' there */
	    pushch(ch);		/* Push '*' back */
	    ch = '/';
	    break;
	}
	break;
    }
    return rawpp = T_WSP;
}

/* SCANCOMM - Skips over a comment; current char must be the '*' opening
**	a comment.  On return, current char is the '/' closing the
**	comment (or EOF).
*/
static void
scancomm()
{
    for (;;) {
	switch (nextch()) {
	    case EOF: 
		error("Unexpected EOF in comment");
		break;			/* Get out of loop */
	    case '/':			/* Special helpful check  */
		do {
		    if (nextch() == '*') {
			advise("Nested comment");
			break;
		    }
		} while (ch == '/');
		if (ch != '*') continue;
		/* Drop thru to handle the * we just got */

	    case '*':
		while (nextch() == '*') ;	/* Skip all '*'s */
		if (ch == '/')
		    break;			/* End of comment, stop! */
	    default:
		continue;			/* Not ended yet */
	}
	break;			/* Break from switch is break from loop */
    }
}
/* Various preprocessor token parsing routines. */

/* PPNCONST - Parse a pp-number, starting with char in "ch", unless
**	arg is non-zero in which case arg is the 1st char, "ch" the 2nd.
**	This only happens if the first char was '.', ie a floating constant.
*/
static int
ppnconst(ch1)
{
    static int hexconst;
    hexconst = 0;		/* Set kludge flag */
    rawval.cp = ppcbeg();	/* Get ptr into char pool */
    if (ch1) ppcput(ch1), rawpp = T_FCONST;	/* Aha, float const */
    else rawpp = T_ICONST;	/* Assume integer until detect otherwise */
    ppcput(ch);
    for (;; ppcput(ch)) {
	switch (nextch()) {
	case 'x':
	case 'X':		/* Remember if hex constant */
	    if (ppclen() == 1 && ppclast() == '0')
		++hexconst;
	    continue;
	case 'E':
	case 'e':			/* 'E' means float const, */
	    if (!hexconst)		/* unless already hex const! */
	case '.':
		rawpp = T_FCONST;	/* Remember float const */
	    continue;
	case '-':
	case '+':		/* If prev char not 'E' or 'e', stop now */
	    if (toupper(ppclast()) != 'E')
		break;
	    continue;
	default:
	    if (!iscsym(ch))	/* Any identifier char (BARF BARF) */
		break;
	    continue;
	}
	break;			/* Break from switch is break from loop */
    }
    rawpplen = ppcend();	/* Done with string */
    return rawpp;		/* Return either T_ICONST or T_FCONST */
}

/* PPSCONST - Parse a string literal or char constant, starting with char
**	in "ch", unless
**	arg is non-zero in which case arg is the 1st char, "ch" the 2nd.
**	"ch" will always contain '\"' or '\'' to indicate type of constant.
**	Note no token value is set or returned; the caller must do this.
*/
static void
ppsconst(ch1)
{
    int delim = ch;		/* Remember delimiter we're using */

    rawval.cp = ppcbeg();	/* Get ptr into char pool */
    if (ch1) ppcput(ch1);
    ppcput(ch);
    for (;; ppcput(ch)) {
	switch (nextch()) {
	case EOF:
	case '\n':
	    break;
	case '\\':			/* Escape (just treat as quoter) */
	    ppcput(ch);
	    switch (nextch()) {
	    case EOF:
	    case '\n':	break;
	    default:	continue;
	    }
	    break;
	default:
	    if (ch != delim)
		continue;
	    ppcput(delim);		/* Succeeded! */
	    rawpplen = ppcend();	/* Done with string */
	    nextch();			/* Advance past delimiter */
	    return;			/* Done, return */
	}
	break;			/* Break from switch is break from loop */
    }

    /* Stopped because hit erroneous char.  Fix up and barf about it. */
    ppcput(delim);		/* Ensure token is well-formed */
    rawpplen = ppcend();	/* Finish off string */

    error("%s in %s",
	(ch=='\n' ? "EOL" : (ch==EOF ? "EOF" : "Illegal char")),
	(delim=='\'' ? "char constant"
		: (delim=='\"' ? "string literal"
		: "quoted identifier")));
}


/* PPUNKNWN - "Parse" the unknown char in "ch" into a T_UNKNWN token.
*/
static int
ppunknwn()
{
    rawval.cp = ppcbeg();	/* Get ptr into char pool */
    ppcput(ch);
    nextch();			/* Consume it, get next */
    rawpplen = ppcend();	/* Done with string */
    return rawpp = T_UNKNWN;
}


static PPTOK *
ppterror()
{
    efatal("Preprocessor token table overflow");
}
/* Translation Phase 4 - directive & macro expansion handling
**	If a current token list exists (curtl), uses that; all tokens on
**	that list are "cooked" and no longer subject to macro expansion.
**	Returns curpp after setting all current stuff.
*/
int
nextpp()
{
    if (curptr = curtl.tl_head) {	/* If using token list, */
	curpp = curptr->pt_typ;		/* consume first token on it */
	curval = curptr->pt_val;
	if (tlpskip(curtl) == NULL) {
	    /* Token list ran out.  Clean up for next call. */
	    tlzinit(curtl);		/* No active cooked token list */
	    pptreset();			/* Free all tokens (curptr still OK) */
	    if (inasm)			/* If in #asm, refill token list */
		return curtl = asmrefill(), nextpp();	
	}
	return (curpp != T_IDENT) ? curpp : findident();
    }

    /* Get a new token from input */
    ppcreset();				/* Free all token string storage */
    if (nextrawpp() == T_EOL) {		/* At EOL must start directive scan */
	while (tskiplwsp()==T_SHARP) {	/* Get 1st non-wsp token */
	    directive();		/* Handle directive! */
	    if (curtl.tl_head)		/* If it set up a cooked list, */
		return nextpp();	/* get input from that. */
	}
    }
    curval = rawval;
    return ((curpp = rawpp) != T_IDENT) ? curpp : findident();
}

/* PUSHPP() - push back current token.
**	So far, only needed by one call in CCLEX.
*/
void
pushpp()
{
    if (curptr)			/* If current token is from list, */
	tlpins(curtl, curptr);	/* backup is trivial! */
    else    /* If current token not from a list, must fake one up -- barf. */
	curtl = tlmake(curpp, curval);	/* Make a token list with this */
}
/* FINDIDENT - auxiliary for NEXTPP that checks for macro expansion at
**	top level.
*/
static int
findident()
{
    register char *cp = curval.cp;

    if (*cp == SPC_IDQUOT) {		/* Special quoted ident? */
	cursym = NULL;			/* Just punt to CCLEX */
	return curpp;
    }

    /* See if this symbol is already defined as a macro or C sym. */
    /* Note that if curptr is set, token came from cooked tokenlist and so
    ** is not to be expanded.
    */
    cursym = symfind(cp, 1);	/* Find sym or create global symbol! */
    if (cursym->Sclass == SC_MACRO) {
	if (!curptr && mexptop(cursym, 0))	/* Is macro! OK to expand? */
	    return nextpp();		/* Yes, return cooked tokens!*/
	    
	/* Sym is macro, but not being expanded, so search again to find
	** the C symbol that the macro was shadowing.
	** This is tricky since the search has to be continued from AFTER
	** the point where the macro symbol exists.
	** Even trickier is the requirement that, if no
	** existing symbol is found, we must create
	** a new global symbol which comes AFTER the macro symbol.
	*/
	if (!(cursym = symfnext(cursym))) {	/* Find next sym.  If none, */
	    cursym = shmacsym(symgcreat(cp));	/* create shadow sym */
	}
    }
    return curpp;
}

/* FINDMACSYM - Find macro name symbol if one exists
*/
static SYMBOL *
findmacsym(id)
char *id;
{   SYMBOL *macsym;

    if (macsym = symfind(id, 0)) {	/* Scan for macro or identifier */
	if (macsym->Sclass == SC_MACRO)	/* Found symbol, see if macro */
	    return macsym;		/* Yup, return the pointer */
	else --(macsym->Srefs);	/* Compensate for spurious symfind ref */
    }
    return NULL;
}
/* NEXTMACPP() - Token input for macro expansion, which is called in place
**	of nextrawpp() so input can easily be redirected to token lists.
**	"rawptr" is set if the token is a pptok.
**	NOTE that the returned token is no longer needed since the
**	list pointer has been bumped prior to return, so it is OK to
**	mung the token (assuming nothing else needs the overall list).
**	The exception is T_EOF, which leaves the list pointer alone so that
**	all further calls will return T_EOF until a mtlpop() is done.
*/
static int
nextmacpp()
{
    if (rawptr = mactl.tl_head) {	/* If using token list, */
	rawval = rawptr->pt_val;	/* get from list */
	if (((rawpp = rawptr->pt_typ) != T_EOF)	/* If not EOF, */
	  && (tlpskip(mactl) == NULL)) {	/* set up for next call */
	    mtlpop();			/* List ran out, pop it off */
	}
	return rawpp;
    }
    /* No token list, now emulate nextpp() without directive processing */
    if (curptr = curtl.tl_head) {	/* If using token list, */
	rawptr = curptr;
	rawpp = curpp = curptr->pt_typ;	/* Consume first token on it */
	rawval = curval = curptr->pt_val;
	if (tlpskip(curtl) == NULL) {
	    /* Token list ran out.  Clean up for next call. */
	    tlzinit(curtl);		/* No active cooked token list */
	}
	return curpp;
    }
    /* Get new token from file input */
    return nextrawpp();
}

/* PUSHMP() - Push current token back for nextmacpp().
*/
static void
pushmp()
{
    if (rawptr)	tlpins(mactl, rawptr);
    else pushpp();
}

/* MTLPUSH(tl) - Push a token list on stack for nextmacpp() to read from.
*/
static void
mtlpush(tl)
tlist_t tl;
{
    if (mactlev >= MAXMACNEST-1) {
	int_error("mtlpush: mactls overflow");
    } else {
	mactls[mactlev++] = mactl;
	mactl = tl;
    }
}

/* MTLPOP() - Pop off an exhausted token list
*/
static void
mtlpop()
{
    if (--mactlev < 0) {
	int_error("mtlpop: mactls underflow");
	mactlev = 0, mactl.tl_head = NULL;
    } else
	mactl = mactls[mactlev];
}
/* Auxiliary token handling routines */

/* TLTOMAC(tl,cp) - Convert token list to macro body string
**	Returns updated ptr to end of string
*/
static char *
tltomac(tl,cp)
tlist_t tl;
char *cp;
{
    register int i;
    register PPTOK *p;

    for (p = tlpcur(tl); p; p = p->pt_nxt) {
	if ((i = p->pt_typ) && i < NTOKDEFS) {
	    *cp++ = i;			/* Always stick token type in */
	    if (!tokstr[i]) switch (i) {	/* Token not op or punct? */
		case T_MACARG:
		case T_MACSTR:
		case T_MACINS:		/* Value is param # */
		    *cp++ = p->pt_val.i + MAC_ARGOFF;
		case T_MACCAT:
		    break;
		default:		/* Assume rest of string is token */
		    cp = estrcpy(cp, p->pt_val.cp);
		    ++cp;		/* Move over NUL char */
		    break;
	    }
	} else tkerr("tltomac", i);
    }
    *cp++ = '\0';		/* Add final terminator to whole thing */
    return cp;
}


/* TLFRMAC(cp) - Convert macro body string to token list
**	Returns token list.  List will be empty if string is NULL or "".
*/
static tlist_t
tlfrmac(cp)
char *cp;
{
    tlist_t tl;
    register int i;
    static PPTOK zpptok;
    PPTOK pptok;

    tlzinit(tl);			/* Init to zero */
    if (!cp || !*cp)
	return tl;
    while (i = *cp++) {		/* Stop when see zero token */
	if (i < NTOKDEFS) {
	    pptok = zpptok;
	    pptok.pt_typ = i;		/* Stick in token # */
	    if (!tokstr[i]) switch(i) {	/* Token not an op or punct? */
		case T_MACARG:
		case T_MACSTR:
		case T_MACINS:
		    pptok.pt_val.i = *cp++ - MAC_ARGOFF; /* Value is param # */
		case T_MACCAT:
		    break;
		default:	/* Assume rest of string is token */
		    pptok.pt_val.cp = cp;	/* Remember ptr to start */
		    if (*cp) while (*++cp);	/* Skip over */
		    break;
	    }
	    tltadd(tl, pptok);	/* Add to end of list */
	} else tkerr("tltomac", i);
    }		
    return tl;
}

/* TLTOSTR(tl,cp,n) - Convert token list to char string
**	Only writes up to a max of N chars, plus 1 additional NUL.
**	Returns updated ptr to the NUL at end of string.
*/
static char *
tltostr(tl,cp,n)
tlist_t tl;
char *cp;
{
    register int i;
    register char *str;
    for (; tlpcur(tl); tlpskip(tl)) {
	if ((i = tlpcur(tl)->pt_typ) < NTOKDEFS) {
	    if (!(str = tokstr[i]))
		str = tlpcur(tl)->pt_val.cp;
	    if (!str) {
		tkerr("tltostr", i);
		break;
	    }
	    if ((i = strlen(str)) <= n) {
		cp = estrcpy(cp, str);
		n -= i;
	    } else {
		*cp = 0;
		strncat(cp, str, n);
		cp += n;
		break;
	    }	
	} else tkerr("tltostr", i);
    }
    return cp;
}

/* TLFRSTR(cp) - Convert string to token list
**	Returns token list.
*/
static tlist_t
tlfrstr(cp)
char *cp;
{
    tlist_t tl;

    tlzinit(tl);
    sinbeg(cp);		/* Set up input from this string */
    while (nextrawpp() != T_EOF)
	tltadd(tl, tokize());
    sinend();		/* Done, restore previous input */
    return tl;
}

/* TLIMAKE(typ, val) - Make a tokenlist consisting of just one T_ICONST
**	token, with the given value.  String is expressed in terms of
**	the given base (current choices are 0 for octal, 1 decimal)
*/
static tlist_t
tlimake(val, baseflg)
int val;
{
    tlist_t tl;
    static PPTOK itok = { T_ICONST };
    char *cp;

    if (ppcleft < (TGSIZ_WORD/3)+2) {	/* If might overflow buffer, */
	ppcleft = -1, (void)ppcend();	/* trigger fatal error msg now */
    }
    *(tl.tl_head = tl.tl_tail = tokpcre()) = itok;
    cp = ppcbeg();
    if (baseflg && val >= 0)		/* Deposit num string in char pool */
	sprintf(cp, "%d", val);		/* Decimal */
    else sprintf(cp, "%#o", val);	/* Octal */
    tl.tl_head->pt_val.cp = cp;
    ppcleft -= (val = strlen(cp)+1);	/* Update char pool vars */
    ppcptr += val;
    return tl;
}


/* TOKIZE() - Make current raw token into a pptok.
*/
static PPTOK
tokize()
{
    static PPTOK ztok;	/* Zero pptok, for initialization */
    PPTOK tok;
    tok = ztok;
    tok.pt_typ = rawpp;		/* Token type */
    tok.pt_val = rawval;	/* Token value */
    return tok;
}

/* TLRAWADD(&tl) - Make current raw token into a pptok at tail of specified list.
*/
static void
tlrawadd(atl)
tlist_t *atl;
{
    static PPTOK ztok;	/* Zero pptok, for initialization */
    PPTOK tok;
    tok = ztok;
    tok.pt_typ = rawpp;		/* Token type */
    tok.pt_val = rawval;	/* Token value */
    tltadd(*atl,tok);
}

/* TLCOPY(tl) - Make copy of a tokenlist.
*/
static tlist_t
tlcopy(il)
tlist_t il;
{
    tlist_t ol;
    PPTOK *p;

    tlzinit(ol);		/* Set OL to empty list */
    for (; tlpcur(il); tlpskip(il)) {
	*(p = tokpcre()) = tltcur(il);	/* Get copy of pptok */
	p->pt_nxt = 0;		/* Ensure it doesn't point to anything */
	tlpadd(ol, p);		/* Add onto list */
    }
    return ol;
}

/* TLSTRIZE(tl) - Make a single string-literal token from a tokenlist.
**	If the list was empty, a null-string literal is returned.
*/
static tlist_t
tlstrize(tl)
tlist_t tl;
{
    char *cp = ppcbeg();	/* Start deposit into token char pool */

    ppcput('"');
    for (; tlpcur(tl); tlpskip(tl))
	switch (tlpcur(tl)->pt_typ) {
	    case T_SCONST:	/* These have to be handled specially */
	    case T_CCONST:
		ppcqstr(tlpcur(tl)->pt_val.cp);
		break;
	    default:
		ppctstr(tlpcur(tl));
	}
    ppcput('"');
    (void) ppcend();
    return tlmake(T_SCONST, cp);
}

/* PPTFPUT(p, fp) - Output token's string.
*/
static int
pptfput(p, fp)
register PPTOK *p;
FILE *fp;
{
    register char *cp;
    if (p->pt_typ < NTOKDEFS
      && ((cp = tokstr[p->pt_typ])
	|| (cp = p->pt_val.cp)))
	    return fputs(cp, fp);
    tkerr("pptfput", p->pt_typ);
    return EOF;
}

/* SLTOSTR(p, cp, i) - Copy string-literal token to string literal.
**	Observes specified limit on length of string.
**	Returns pointer to end of string (null char)
*/
static char *
sltostr(p, cp, max)
PPTOK *p;
char *cp;
{
    register char *frm = p->pt_val.cp;
    register int c;

    while (--max > 0) {
	switch (c = *++frm) {
	case '\\':
	    *cp++ = *++frm;	/* For now, don't hack escapes, just quote */
	    continue;
	case '\"':
	    if (*++frm)
	case 0:
		int_error("slttostr: bad lit");
	    break;
	default:
	    *cp++ = c;
	    continue;
	}
	break;
    }
    *cp = 0;
    return cp;
}
/* Routines for PPC stuff (PP char pool) */

/* PPCQSTR(cp) - Deposit string in token char pool, quoting anything
**	that would cause trouble for a string literal (" and \)
*/
static void
ppcqstr(cp)
register char *cp;
{
    --cp;
    for (;;) {
	switch (*++cp) {
	    case 0: return;
	    case '"':
	    case '\\': ppcput('\\'); break;
	}
	(void)ppcput(*cp);
    }
}

/* PPCTSTR(p) - Deposit token's string in token char pool, except for
**	terminating null.
*/
static void
ppctstr(p)
register PPTOK *p;
{
    register char *cp;
    if (p->pt_typ < NTOKDEFS) {
	if (!(cp = tokstr[p->pt_typ]))
	    cp = p->pt_val.cp;
	if (!cp) tkerr("ppctstr", p->pt_typ);
	--cp;
	while (*++cp)
	    (void)ppcput(*cp);
    } else tkerr("ppctstr", p->pt_typ);
}

static int
tkerr(rtn, i)
char *rtn;
{
    int_error("%s: bad token %Q", rtn, i);
}

static int
ppcerror()
{
    efatal("Preprocessor token char pool overflow");
}
/* MEXPTOP() - Expand a macro invocation at top level.
**	Takes input from nextmacpp(), which can be either a tokenlist or
**	file input, and inserts resulting token list into curtl.
*/
static int
mexptop(sym, hs)
SYMBOL *sym;
{
    tlist_t tl;

    /* Special check.  If fn-like macro, there must be a '('
    ** as next token or we mustn't expand it.  Gross hack needed here.
    */
    if (sym->Smacnpar >= 0) {	/* Function-like macro? */
	if (cskiplwsp() != '(')	/* Do special check for left-paren */
	    return 0;		/* Say macro not expanded */
    }
    /* OK to expand, do internal check that input source is correct */
    if (curtl.tl_head || mactl.tl_head)
	int_error("mexptop: not toplev");

    tl = mexpsym(sym, hs);	/* Expand macro! */
    if (tl.tl_head) {		/* May expand to empty list... */
	if (curtl.tl_head)
	    tllapp(tl, curtl);	/* Append old input list to new one! */
	curtl = tl;		/* Start reading from new input list */
    }
    return 1;
}

/* MPEEKPAR() - Returns TRUE if next token will be a T_LPAREN, but without
**	actually reading it.  This is needed because toplevel stuff
**	really wants to avoid gobbling a token, if possible.
*/
static int
mpeekpar()
{
    PPTOK *p;
    if (p = mactl.tl_head) {
	if (p->pt_typ == T_LPAREN) return 1;
	if (p->pt_typ != T_WSP) return 0;
	if (p = p->pt_nxt)
	    return (p->pt_typ == T_LPAREN);
    }
    /* No tokenlist input, or is going to run out.  Check raw input chars. */
    return (cskiplwsp() == '(');
}
/* MEXPSYM(sym, hs) - Expand macro identified by symbol ptr.
**	Input is taken from nextmacpp(), and next token will be the
**	first token after the macro identifier that invoked us.
**	Reads arguments and completely expands the macro, returning
**	a tokenlist of the results.
*/
static tlist_t
mexpsym(sym, hs)
SYMBOL *sym;
{
    tlist_t tl, tl2;
    struct macframe mf;		/* Current macro frame */
    int i, parens, symv;
    char *cp1, *cp2;

#if DEBUG_PP
    if (debpp)
	fprintf(fpp,"mexpsym(%#o->\"%s\", %d)\n", sym, sym->Sname, hs);
#endif

    tlzinit(tl);
    if (sym->Sflags & SF_MACEXP) {	/* This sym already being expanded? */
	if (clevel < CLEV_ANSI)		/* Give error unless ANSI. */
	    error("Recursive macro expansion: %s", sym->Sname);
	return tl;			/* Then just ignore it. */
    }

    /* Check for exceeding macro nesting depth.  If exceeded, we complain
    ** and ignore this expansion attempt, flushing the macro args if any.
    */
    if (maclevel >= MAXMACNEST-1) {
	error("Macro nesting depth exceeded: %s", sym->Sname);
	(void) margs((tlist_t *)NULL, -1, sym->Smacnpar); /* Flush args */
	return tl;			/* Empty list */
    }

    if ((mf.mf_nargs = sym->Smacnpar) < 0) {	/* Special macro type? */
	/* Special macro!  Handle it... */
	switch (mf.mf_nargs) {
	    case MACF_ATOM:	/* Simple case, normal macro without arglist */
	    case MACF_STDC:
		mf.mf_body = sym->Smacptr;
		break;		/* Get out and return substituted stuff */

	    case MACF_KCC:	/* __COMPILER_KCC__ */
		{
		    static char verstr[40];
		    if (!verstr[0])
			sprintf(verstr, "\"KCC-%d.%d(c%dl%d)\"",
					cverdist, cverkcc, cvercode, cverlib);
		    return tlmake(T_SCONST, verstr);
		}
	    case MACF_LINE:	/* __LINE__ */
		return tlimake(fline, 10);	/* Return a T_ICONST list */

	    case MACF_FILE:	/* __FILE__ */
		{
		    char *cp = ppcbeg();
		    ppcput('"'); ppcqstr(inpfname); ppcput('"');
		    (void) ppcend();
		    return tlmake(T_SCONST, cp);
		}
	    case MACF_DATE:	/* __DATE__ */
		if (!tadset) dotad();
		return tlmake(T_SCONST, datestr);

	    case MACF_TIME:	/* __TIME__ */
		if (!tadset) dotad();
		return tlmake(T_SCONST, timestr);

	    case MACF_DEFD:	/* "defined" operator */
		/* We allow either "defined NAME" or "defined(NAME)" as per
		** H&S 3.5.5.  Note that input had better not be expanded.
		*/
		while (nextmacpp() == T_WSP) ;	/* Flush any whitespace */
		parens = 0;
		if (rawpp == T_LPAREN) {
		    parens++;
		    while (nextmacpp() == T_WSP);
		}
		if (rawpp != T_IDENT) {
		    error("Bad arg to \"defined\" operator");
		    pushmp();			/* Re-read bad token */
		    return tl;			/* Empty list */
		}
		tl = tlmake(T_ICONST,		/* Expand into true or false */
			(findmacsym(rawval.cp) ? "1" : "0"));
		if (parens) {
		    while (nextmacpp() == T_WSP) ;	/* Skip more blanks */
		    if (rawpp != T_RPAREN) {
			error("Missing ')' for \"defined\" operator");
			pushmp();		/* Re-read bad token */
		    }
		}
		return tl;

	    case MACF_SYMF: if (1) symv = 0;	/* Set flag 0 for existence, */
				else
	    case MACF_SYMV: symv = 1;		/* 1 for value */
		/* Do common code for _KCCsymfnd and _KCCsymval */
		i = margs(&mf.mf_argtl[0], 2, 2);	/* Parse args */
		if (i == 2) {
		    tl = tlwspdel(mexplim(mf.mf_argtl[0], hs), 1);
		    tl2 = tlwspdel(mexplim(mf.mf_argtl[1], hs), 1);
		}
		if (i != 2
		  || tlpcur(tl)->pt_typ != T_SCONST
		  || tlpcur(tl)->pt_nxt
		  || tlpcur(tl2)->pt_typ != T_SCONST
		  || tlpcur(tl2)->pt_nxt) {
		    error("Args to \"_KCCsym%s\" must be two string literals",
			symv? "val" : "fnd");
		    return tlmake(T_ICONST, "0");	/* Return 0 if err */
		} else {
		    char sbuf[200];		/* Arbitrary big size */
		    cp1 = sbuf;			/* Must copy string lits */
		    cp2 = sltostr(tlpcur(tl), sbuf, (int)sizeof(sbuf))+1;
		    (void) sltostr(tlpcur(tl2), cp2, (int)sizeof(sbuf)-(cp2-sbuf));
		    return tlimake(symval(cp1, cp2, symv), 0);
		}

	    default:
		int_error("mexpsym: bad mac val: %d", mf.mf_nargs);
		return tl;
	}
    } else {			/* Macro expects an arg list */
	/* Get ptr to macro body, skipping over idiotic params at front */
	mf.mf_body = sym->Smacptr + sym->Smacparlen;
	i = margs(&mf.mf_argtl[0], mf.mf_nargs, mf.mf_nargs); /* Parse args */

	/* Complain if not exactly the right number of args.  Sigh. */
	if (mf.mf_nargs != i) {
	    error("Wrong number of macro args - %d expected, %d seen",
			mf.mf_nargs, i);
	    if (i < 0) i = 0;		/* Handle case of MACF_ATOM */
	    while (i < mf.mf_nargs)	/* Ensure arg table filled out */
		mf.mf_argtl[i++] = tl;	/* so no refs to wild ptrs */
	}
    }

    /* Have body and args, now turn it into a tokenlist */
    maclevel++;			/* Is this really necessary? */
    mf.mf_sym = sym;
    tl = msubst(&mf, hs);	/* Do it and return! */
    tl = mexpand(tl, hspush(sym, hs));
#if 0
    tl = tlhide(tl, hspush(sym, hs));	/* Apply hideset to all tokens */
#endif
    --maclevel;
    return tl;
}
/* MARGS() - Gobble arguments for macro, from nextmacpp().
**	On entry, opening left-paren hasn't yet been read.
**	Returns # of args seen: -1 if no open paren, 0 if no args,
**	else # of args (some may be empty).
**	On return, current token will be either T_RPAREN, T_EOF, or
**	(for an atomic macro) whatever was seen instead of an open-paren,
**	pushed on the mactl stack.
*/
static int
margs(tabptr, maxsto, nexpected)
tlist_t *tabptr;		/* Pointer to array of tlists */
int maxsto;			/* Max # of args to store in array */
int nexpected;			/* # of args we expect to parse */
{
    int nargs;			/* # args parsed */
    int plev;
    tlist_t tl;			/* Token list for current arg */
    PPTOK *prevlast;		/* Remember ptr to previous last */

    /* Allow whitespace (plus EOL) between name and arglist. */
    while (nextmacpp() == T_WSP || rawpp == T_EOL);
    if (rawpp != T_LPAREN) {		/* Any args there? */
	pushmp();			/* No, must put token back */
	return -1;			/* and say no arglist at all. */
    }

    /* Now loop, parsing args */
    nargs = 0;
    plev = 1;			/* Start with 1 left-paren already seen */
    tlzinit(tl);		/* Init current tokenlist to nothing */
    prevlast = NULL;
    while (plev > 0) {
	switch (nextmacpp()) {
	    case T_EOL:
		/* Handle newline in arg list scan.
		** Standard def of C seems to prohibit these, but we allow
		** them by changing into a space.  The new ANSI draft
		** specifically permits this.
		** However, in order to
		** prevent a missing ')' from allowing the arg scan to
		** gobble all the rest of the file, we check to see whether
		** we have already found all of our args.
		*/
		if (nargs >= nexpected) { /* If see \n when too many args */
		    error("Missing ')' in macro arg list");
		    return nargs;	/* Stop now with what we got */
		}
		/* Drop thru to handle like normal whitespace */

	    case T_WSP:
		if (tl.tl_head == NULL		/* Ignore wsp if at start */
		  || (tl.tl_tail->pt_typ == T_WSP))	/* or last was wsp */
		    continue;			/* Ignore, get next token */
		break;				/* OK to deposit it */

	    case T_EOF:		/* Uh-oh... stopped unexpectedly */
		if (eof)	/* Check for running off end of world */
		    error("Unexpected EOF during macro arg scan");
		else error("Macro arg scan truncated");
		plev = 0;	/* Must stop loop! */
		/* Drop thru to store arg before quitting */

	    case T_RPAREN:	/* Close paren counted for balancing */
		if (--plev > 0)
		    break;	/* Just add to arg */
		if (nargs == 0 && tl.tl_head == NULL)	/* No args at all? */
		    return 0;	/* None read... */
		/* Arg (and all args) done, drop thru to store current arg */

	    case T_COMMA:	/* Comma stops arg unless paren-protected */
		if (plev > 1)	/* If still within an arg, */
		    break;	/* just gobble the comma too */

		/* Store argument thus far! */
		/* Only set arg if we know there's room in table. */
		if (++nargs <= maxsto) {
		    if (prevlast && tl.tl_tail->pt_typ == T_WSP) {
			/* Truncate whitespace off arglist */
			(tl.tl_tail = prevlast)->pt_nxt = 0;
		    }
		    *tabptr++ = tl;	/* Store the arg list! */
		}
		if (plev <= 0) continue;	/* Check for done */
		tlzinit(tl);		/* Start a new arg */
		continue;		/* Get next token */

	    case T_LPAREN:	/* Open paren counted for balancing */
		plev++;		/* and always included in arg */
	    default:
		break;		/* All other tokens included in arg */
	}	/* End of switch */

	/* Add token to current arg, get next, and continue loop */
	prevlast = tl.tl_tail;	/* Remember previous last-ptr */
	tltadd(tl, tokize());	/* Always copy token for now */
    }    	/* End of loop */

    return nargs;		/* Return # of args parsed into array */
}
/* MEXPLIM(tl, hs) - Expand a tokenlist, using no additional tokens.
**	This is used to expand macro arguments and directive lines.
**	Note that the furnished list is munged; thus, a copy must be
**	given by the caller if the original must be preserved.
*/
static tlist_t
mexplim(il, hs)
tlist_t il;		/* Input list */
{
    static PPTOK eoftok = { T_EOF };
    tlist_t ml;

    tltadd(il, eoftok);		/* Add EOF to end of input list */
    ml = mexpand(il, hs);	/* Read from it, expand macros, get new list */
    if (mactl.tl_tail != il.tl_tail)		/* Input must still be there */
	int_error("mexplim: lost input list");
    if (mactl.tl_head != il.tl_tail)
	int_error("mexplim: input not all read");
    mtlpop();			/* Done, pop EOF-terminated list off input */
    return ml;
}
/* MEXPAND(tl,hs) - Expand a tokenlist, using additional input from
**	nextmacpp() if any is needed to read the arguments of a function-like
**	macro.
**	This is used to rescan macro bodies.
**	On return, all identifiers will have been marked to indicate whether
**	they are macros, hidden macros, or known not to be macros.
**	Note that the furnished list is munged; thus, a copy must be
**	given by the caller if the original must be preserved.
*/
static tlist_t
mexpand(il, hs)
tlist_t il;
{
    SYMBOL *sym;
    int ourlev;
    tlist_t ml, ol;

#if DEBUG_PP
    if (debpp) pmactl("enter mexpand", il, hs);
#endif
    if (!il.tl_head)		/* If list is empty, */
	return il;		/* just return empty list */
    mtlpush(il);		/* Push into input for nextmacpp */
    tlzinit(ol);		/* Start with empty output list */
    ourlev = mactlev;		/* Remember level we're reading at */
    while (ourlev <= mactlev) {
	switch (nextmacpp()) {
	case T_EOF:		/* Premature EOF can happen from mexplim */
	    if (ol.tl_tail)		/* Ensure list is terminated */
		ol.tl_tail->pt_nxt = 0;
#if DEBUG_PP
	    if (debpp) pmactl("leave mexpand", ol, hs);
#endif
	    return ol;

	case T_MACRO:		/* Macro, always expand */
	    sym = rawptr->pt_val.sym;
	    break;
	case T_IDENT:		/* Ident, must check it. */
	    switch (rawptr->pt_is) {
		case IS_MHID:			/* Hidden macro */
		case IS_MNOT:			/* Not a macro */
		    tlpadd(ol, rawptr);
		    continue;
		case IS_MEXP:			/* Expandable macro */
		    if (!(sym = findmacsym(rawval.cp))) {
			int_error("mexpand: Cannot lookup IS_MEXP \"%s\"",
					rawval.cp);
			rawptr->pt_is = IS_MNOT;
			tlpadd(ol, rawptr);
			continue;
		    }
		    break;			/* Break out to expand! */

		case IS_UNK:			/* Unknown identifier */
		    if (!(sym = findmacsym(rawval.cp))) {
			rawptr->pt_is = IS_MNOT;	/* Found not a macro */
			tlpadd(ol, rawptr);
			continue;
		    }
		    if (mishid(sym, hs)) {
			rawptr->pt_is = IS_MHID;	/* Macro, now hidden */
			tlpadd(ol, rawptr);
			continue;
		    }
		    break;			/* Break out to expand! */

		default:
		    int_error("mexpand: Bad IS_ type (%d)", rawptr->pt_is);
	    }
	    /* Come here when ident is expandable macro */
	    if (sym->Smacnpar < 0	/* If object-like macro, */
	      || mpeekpar())		/* or fn-like and followed by ( */
		break;			/* then OK to expand macro! */
	    /* Fall through if cannot expand macro */
#if 0	/* This is wrong.  Perhaps save sym by making T_MACRO?? */
	    rawptr->pt_is = IS_MHID;	/* Hide it forever */
#endif
	default:
	    tlpadd(ol, rawptr);
	    continue;		/* Just leave on token list */
	}

	/* Handle expansion of the macro pointed to by "sym".
	** This is a simple-minded method that has the virtue of working.
	*/
	ml = mexpsym(sym, hs);		/* Expand into ml */
	if (ml.tl_head)			/* If anything was returned, */
	    mtlpush(ml);		/* put it on input for rescan! */
	continue;
#if 0
	/* Handle expansion of the macro pointed to by "sym".
	** Most of the following hair is because we're trying to be
	** efficient and mung the input tokenlist in place.
	*/
	if (p == tl.tl_tail)	/* Cons up input tokenlist (IL) to rest */
	    tlzinit(il);
	else {
	    il.tl_head = tokn2p(p->pt_nxt);
	    il.tl_tail = tl.tl_tail;
	}
	tltadd(il, eoftok);	/* Add EOF to end of it */
	mtlpush(il);		/* Push list on input stack */
	ml = mexpsym(sym, hs);	/* Read from it, expand macro, get new list */
	if (mactl.tl_tail != il.tl_tail		/* Input must still be there */
	  || !(il.tl_head = mactl.tl_head))	/* Update IL for amt read */
	    int_error("mexpand: lost input list");
	mtlpop();				/* Done, pop list off input */

	/* Clean up IL to contain remaining input */
	if (il.tl_head->pt_typ == T_EOF)	/* If all gone, */
	    tlzinit(il);			/* just clear it */
	else (il.tl_tail = tl.tl_tail)->pt_nxt = 0;	/* remove the EOF */

	/* Set pointer such that loop will get correct next token from it */
	p = lastp;		/* This will be NULL if no prev token */

	/* Now add expanded list (ML) to scan list (TL) */
	if (tl.tl_tail = p) {	/* Truncate current scan list */
	    if (ml.tl_head)	/* Add expanded list to current list */
		tllapp(tl, ml);
	} else tl = ml;		/* TL truncated to empty, just use ML now */

	/* Now add remaining input (IL) back onto scan (TL) */
	if (tl.tl_tail) {	/* If there's something on TL */
	    if (il.tl_head)	/* Add whatever's left on IL */
		tllapp(tl, il);
	} else tl = il;		/* Nothing on TL, just use IL */

	/* If we replaced the first token, there wasn't any prev token, so
	** loop can't continue by getting next token from P; have to start
	** over by calling ourselves on the new TL.
	*/
	if (!p) return mexptl(tl, hs);
#endif
    }
    if (ol.tl_tail)		/* Ensure list is terminated */
	ol.tl_tail->pt_nxt = 0;
#if DEBUG_PP
    if (debpp) pmactl("leave mexpand", ol, hs);
#endif
    return ol;
}
/* MSUBST() - Substitute and expand args to a macro.
**	Given a macro frame with body string and array of arglists,
**	returns a tokenlist of the completely substituted result.
*/
static tlist_t
msubst(mf, hs)
struct macframe *mf;
{
    tlist_t tl, argl;
    register int typ, i;
    register char *cp;
    static PPTOK zpptok;
    PPTOK pptok;
    int ncats = 0;		/* # of concat ops seen */

#if DEBUG_PP
    if (debpp) pmacframe("msubst", mf);
#endif
    tlzinit(tl);		/* Init list to zero */
    if (cp = mf->mf_body) while (typ = *cp++) {
	if (typ < 0 || typ >= NTOKDEFS)
	    int_error("msubst: illegal token %d in body of macro %S",
				i, mf->mf_sym);
	switch (typ) {
	case T_MACSTR:		/* Stringize argument */
	case T_MACINS:		/* Insert argument */
	case T_MACARG:		/* Expand argument */
	    if ((i = *cp++ - MAC_ARGOFF) < 0 || i >= mf->mf_nargs)
		int_error("msubst: illegal param %d in body of macro %S",
				i, mf->mf_sym);
	    argl = mf->mf_argtl[i];
	    switch (typ) {	/* Handle arg as directed */
	    case T_MACSTR: argl = tlstrize(argl);   break;	/* Stringize */
	    case T_MACINS: argl = tlcopy(argl);     break;	/* Insert */
	    case T_MACARG: argl = mexplim(tlcopy(argl), hs); break;	/* Expand */
	    }
	    if (argl.tl_head) {	/* Add resulting arglist to accumulation */
		if (tl.tl_head) tllapp(tl, argl);
		else tl = argl;
	    }
	    continue;

	case T_MACCAT:		/* Op: concatenate tokens */
	    ++ncats;		/* Remember how many seen */
	    pptok = zpptok;
	    pptok.pt_typ = typ;
	    tltadd(tl, pptok);	/* Add to end of list */
	    continue;
	}

	/* Default for plain old body tokens - transform into pptok */
	pptok = zpptok;		/* Init pptok to zero */
	pptok.pt_typ = typ;	/* Stick in token # */
	if (!tokstr[typ]) {	/* Token not an op or punct? */
	    /* Assume rest of string is token */
	    pptok.pt_val.cp = cp;	/* Remember ptr to start */
	    if (*cp) while (*++cp);	/* Skip over */
	    ++cp;
	}
	tltadd(tl, pptok);	/* Add to end of list */
    }				/* Stop loop when hit end of macro body */

    /* All tokens present in list, now do concatenation if needed */
    if (ncats)
	mpaste(tl, hs, ncats);
    return tl;
}

static tlist_t
mpaste(tl, hs, ncats)
tlist_t tl;
{
    register PPTOK *p, *lastp = NULL;
    tlist_t nl;
    char *cp;

#if DEBUG_PP
    if (debpp) fprintf(fpp, "%smpaste: %d cats in:", plevindent(), ncats),
		tlfput(tl,fpp,0), putc('\n',fpp);
#endif
    for (p = tlpcur(tl); p; lastp = p, p = tokn2p(p->pt_nxt)) {
	if (p->pt_typ != T_MACCAT)
	    continue;
	/* Start concatenation!  Concatenate previous token with next one */
	cp = ppcbeg();
	ppctstr(lastp);			/* Dump prev token */
	do {
	    p = tokn2p(p->pt_nxt);
	    if (!lastp || !p)
		int_error("mpaste: ## arg missing");
	    ppctstr(p);			/* Dump next token */
	    p = tokn2p(p->pt_nxt);
	} while(--ncats > 0 && p && p->pt_typ == T_MACCAT);
	(void) ppcend();		/* Token done... */

	/* Everything concatenated for this stretch, now retokenize it.
	** If it cannot be parsed, it is left as T_UNKWN for possible later
	** salvage by higher levels, or at worst an error message.
	*/
	lastp->pt_val.cp = cp;		/* Default is to make it "unknown" */
	lastp->pt_typ = T_UNKNWN;
	nl = tlfrstr(cp);		/* Retokenize! */
#if DEBUG_PP
	if (debpp) fprintf(fpp, "%sRetokenized ", plevindent()),
		tkfput(lastp, fpp, 1),
		fputs(" into", fpp),
		tlfput(nl,fpp,1),
		putc('\n',fpp);
#endif
	if (nl.tl_head == nl.tl_tail) {	/* Got exactly one token? */
	    *lastp = *(nl.tl_head);	/* Yes, hurray!  Shove it onto list */
	    lastp->pt_nxt = tokp2n(p);	/* Skip over junked tokens */
	    /* Now decide how to (or whether to) "hide" this, if T_IDENT */
	    tkhide(lastp, hs);
	}

	if (ncats <= 0)
	    break;			/* If no more, leave loop! */
    }
}
/* Macro Hideset hackery.
*/

/* HSPUSH(sym,hs) - Add symbol to given hideset, return new hideset
*/
static int
hspush(sym, hs)
SYMBOL *sym;
{
    if (hs >= MAXMACNEST) {
	int_error("Macros nested too deep (%d)", hs);
    } else
	hidemacs[hs] = sym;		/* Remember ptr to macro sym */
    return hs+1;
}

/* TKHIDE(p, hs) - hide given token, if applicable
**	Returns resulting IS_ code.
*/
static int
tkhide(p, hs)
PPTOK *p;
{
    SYMBOL *sym;

    if (p->pt_typ == T_IDENT) {
	if (!(sym = findmacsym(p->pt_val.cp)))
	    return p->pt_is = IS_MNOT;	/* Known not to be macro */
	else if (mishid(sym, hs))
	    return p->pt_is = IS_MHID;	/* Is hidden macro */
	return p->pt_is = IS_MEXP;	/* Is expandable macro */
    }
    return IS_UNK;
}

/* MISHID(sym, hs) - Return TRUE if macro symbol is being hidden
**	by given hideset.
*/
static int
mishid(sym, hs)
SYMBOL *sym;
{
    if (hs < 0 || hs > MAXMACNEST)
	return int_error("mishid: bad arg"), 1;
    while (--hs >= 0)
	if (hidemacs[hs] == sym) {
#if DEBUG_PP
	    if (debpp) fprintf(fpp, "%smishid() suppressed %s!\n",
			plevindent(), sym->Sname);
#endif
	    return 1;
	}
    return 0;
}
/* Debugging Support */

#if DEBUG_PP

static char *
plevindent()
{
    static char sps[] = "                                                    ";
    return (maclevel <= 0 ? "" :
		(maclevel*4 > sizeof(sps) ? sps
			: &sps[sizeof(sps) - maclevel*4]));
}

/* PMACFRAME - print macro frame
*/
static void
pmacframe(str, mf)
char *str;
struct macframe *mf;
{
    int i;
    char *cp;
    SYMBOL *s = mf->mf_sym;
    char *ind = plevindent();	/* Get indentation to use */

    fprintf(fpp,
"%s%s: Macframe for %#o->\"%s\" (npar: %d, parlen: %d, len: %d)\n",
		ind, str, s, s->Sname, s->Smacnpar, s->Smacparlen, i = s->Smaclen);
    fprintf(fpp,"%sBody: %#o-> \"", ind, cp = s->Smacptr);
    if (cp) while (--i >= 0)
	if (iscntrl(*cp)) fprintf(fpp, "^%c", (*cp++)^0100);
	else putc(*cp++, fpp);
    fprintf(fpp, "\"\n");

    for (i = 0; i < mf->mf_nargs; ++i) {
	fprintf(fpp, "%sArg %d:", ind, i);
	tlfput(mf->mf_argtl[i], fpp, 0);
	putc('\n', fpp);
    }
}

static void
pmactl(s, tl, hs)
char *s;
tlist_t tl;
{
    char *ind = plevindent();

    fprintf(fpp, "%sTokenlist %s (hs %d): ", ind, s, hs);
    tlfput(tl, fpp, 1);
    putc('\n', fpp);
}

static void
tlfput(tl, fp, vrbflg)	/* vrbflg TRUE for more verbose output */
tlist_t tl;
FILE *fp;
{
    register PPTOK *p;
    for (p = tlpcur(tl); p; p = p->pt_nxt)
	tkfput(p, fp, vrbflg);
}

static char *
tkids(p)
PPTOK *p;
{
    static char buf[30];
    switch (p->pt_is) {
	case IS_UNK:	return "IS_UNK";
	case IS_MNOT:	return "IS_MNOT";
	case IS_MHID:	return "IS_MHID";
	case IS_MEXP:	return "IS_MEXP";
    }
    sprintf(buf, "T_IDENT+%#o", p->pt_is);
    return buf;
}

static void
tkfput(p, fp, vrbflg)	/* vrbflg TRUE for more verbose output */
PPTOK *p;
FILE *fp;
{
    register int i;
    char *cp;

    if ((i = p->pt_typ) < 0 || i >= NTOKDEFS
      || ( !(cp = tokstr[i])
        && !(cp = p->pt_val.cp)))
	fprintf(fp, " <%d=%s %#o>", i,
		((i < NTOKDEFS) ? nopname[i] : ""),
		p->pt_val.i);
    else {
	if (vrbflg) {
	    fprintf(fp," <%s %s>",(i == T_IDENT ? tkids(p) : nopname[i]), cp);
	} else fprintf(fp, " <%s>", cp);
    }
}
#endif
/* DIRECTIVE() - Process a PP directive.
**	Current raw token is T_SHARP.
**	On return, raw token is either T_EOL or T_EOF, and the
**	current char is either T_EOF or the first char of the next line.
**	Callers must check again for a possible directive on the next line!
**	Note that all token storage is flushed before and after processing.
*/
/* Return values for PP handling rtns */
#define PPR_UNKNOWN	-1	/* Unknown directive */
#define PPR_FLUSH	0	/* Flush everything up to EOL */
#define PPR_ATEOL	1	/* We're at EOL, just return */
#define PPR_CHECKEOL	2	/* Verify that no non-whitespace exists
				** between here and EOL.
				*/
static void
directive()
{
    int res = PPR_UNKNOWN;
    int len;

    /* Use ident length as quick hash into switch table.
    ** Result value is a PPR_ type.
    */
    indirp++;			/* Set this for scanhwsp checking */
    ppcreset();			/* Reset tokenizer storage */
    pptreset();
    len = (tskipwsp() == T_IDENT ? strlen(rawval.cp) : 0);
#if DEBUG_PP
    if (debpp && len) fprintf(fpp, "#-DIRECTIVE seen: \"%s\"\n", rawval.cp);
#endif
    switch (len) {

	/* Note that if we are in the middle of a failing conditional
	** (flushing stuff) then only #else, #endif, and #if-type commands
	** are acted upon.  All others are ignored, including
	** unknown #-type "commands", altho the latter generate a warning.
	*/
	case 0:
	    if (rawpp != T_EOL && !flushing)	/* If not EOL or identifier, */
		error("Preprocessor directive expected");	/* barf. */
	    res = PPR_FLUSH;			/* Nothing else to do */
	    break;

	case 2:
	    if (!strcmp(rawval.cp,"if")) res = d_if();
	    break;
	case 3:
	    if (!strcmp(rawval.cp,"asm") && clevkcc)
		res = flushing ? PPR_FLUSH : d_asm();	/* KCC-only */
	    break;
	case 4:
		 if (!strcmp(rawval.cp,"else")) res = d_else();
	    else if (!strcmp(rawval.cp,"elif") && clevel >= CLEV_CARM) res = d_elif();
	    else if (!strcmp(rawval.cp,"line"))
		res = flushing ? PPR_FLUSH : d_line();
	    break;
	case 5:
		 if (!strcmp(rawval.cp,"endif")) res = d_endif();
	    else if (!strcmp(rawval.cp,"ifdef")) res = d_ifdef(1);
	    else if (!strcmp(rawval.cp,"undef"))
		res = flushing ? PPR_FLUSH : d_undef();
	    else if (!strcmp(rawval.cp,"error"))
		res = flushing ? PPR_FLUSH : d_error();
	    break;
	case 6:
		 if (!strcmp(rawval.cp,"ifndef")) res = d_ifdef(0);
	    else if (!strcmp(rawval.cp,"define"))
		res = flushing ? PPR_FLUSH : d_define();
	    else if (!strcmp(rawval.cp,"pragma"))
		res = flushing ? PPR_FLUSH : d_pragma();
	    else if (!strcmp(rawval.cp,"endasm") && clevkcc)
		res = flushing ? PPR_FLUSH : d_endasm();	/* KCC-only */
	    break;
	case 7:
	    if (!strcmp(rawval.cp,"include"))
		res = flushing ? PPR_FLUSH : d_include();
	    break;
    }

    /* Now do common cleanup code by examining result */
    switch (res) {
	case PPR_CHECKEOL:
	    checkeol();
	case PPR_ATEOL:
	    break;
	case PPR_UNKNOWN:
	    if (flushing)
		warn("Unsupported preprocessor command: \"%s\"", rawval.cp);
	    else error("Unsupported preprocessor command: \"%s\"", rawval.cp);
	case PPR_FLUSH:
	    indirp = 0;		/* Avoid whitespace err msgs */
	    flushtoeol();
	    break;
    }
    indirp = 0;			/* No longer scanning directive */
    ppcreset();			/* Reset tokenizer storage */
    pptreset();
}
/* D_DEFINE() - Process #define directive
*/
static int
d_define()
{
    char *name;		/* Name of new macro (in ppcpool) */
    SYMBOL *sym;
    struct macframe m;
    int i;

    if (tskipwsp() != T_IDENT) {	/* Get first non-whitespace token */
	error("Macro name expected");	/* complain if nothing. */
	return PPR_FLUSH;
    }
    name = rawval.cp;		/* Remember ptr to ident */
    if (*name == '`') {
	error("Macro name cannot be quoted identifier");
	return PPR_FLUSH;
    }

    /* Look for parameter names */
    m.mf_nargs = -1;		/* Initially assume no params */
    m.mf_parlen = 0;		/* Keep track of # chars that params need */
    if (nextrawpp() == T_LPAREN) {
	m.mf_nargs = 0;			/* It's a fncall macro! */
	while (1) {
	    if (tskipwsp() == T_RPAREN)	/* Skip over whitespace */
		break;			/* Macro params done! */
	    if (rawpp != T_IDENT) {	/* Better be a param ident */
		error("Macro formal parameter must be identifier");
		m.mf_parlen = m.mf_nargs = 0;
					/* Ugh!  Don't try to hack args on */
					/* expansion, but remember that */
		break;			/* this is a fncall macro. */
	    }
	    if (m.mf_nargs >= MAXMARG - 1)
		error("More than %d args in macro definition of \"%s\"",
			MAXMARG, name);
	    else {
		/* Have arg, store it.  Must also check for
		** duplicate arg names (yes it happens)
		*/
		m.mf_parcp[m.mf_nargs] = rawval.cp;	/* Remember arg name */
		for (i = 0; i < m.mf_nargs; i++)	/* Scan to find dups */
		    if (!strcmp(m.mf_parcp[i], rawval.cp)) {
			error("Duplicate formal parameter in macro def; \"%s\"", rawval.cp);
			break;
		    }
		m.mf_nargs++;
		m.mf_parlen += strlen(rawval.cp)+1;	/* Sigh */
	    }
	    if (tskipwsp() != T_COMMA)	/* If comma next, continue loop */
		break;			/* else stop now */
	}
	if (rawpp != T_RPAREN)
	    error("Close paren needed to end formal parameter list");
	else nextrawpp();		/* Set up next token */
    }

    /* Arguments read, now read rest of line into a tokenlist. */
    if (!mdefinp(&m))			/* If failed somehow, */
	return PPR_FLUSH;		/* just flush rest of line */

    /* OK, now can check for macro already being defined */
    *defcsname = 'd';			/* Allow "defined" to be found */
    sym = findmacsym(name);
    *defcsname = SPC_MACDEF;		/* Hide "defined" again */
    if (sym != NULL) {			/* If already defined, compare it */
	if (m.mf_nargs == sym->Smacnpar	/* Args and body must match */
	  && m.mf_len == sym->Smaclen
	  && (m.mf_len == 0		/* OK if both bodies null */
	    || memcmp(m.mf_body, sym->Smacptr, m.mf_len) == 0)) {
		if (m.mf_body) free(m.mf_body);	/* Identical, we win! */
		return PPR_CHECKEOL;
	}

	if (sym->Smacnpar < MACF_ATOM) {	/* Check for specialness */
	    error("Illegal to redefine \"%s\"", name);
	    if (m.mf_body) free(m.mf_body);
	    return PPR_FLUSH;
	}
	/* Later just output "note" if macro is functionally identical,
	** i.e. differs only in parameter names (gark bletch)
	*/
	warn("Macro redefined: \"%s\"", name);
	freemacsym(sym);		/* Flush old sym completely */
    }

    /* Now define the new macro! */
    mdefsym(name, &m);
    return PPR_CHECKEOL;
}

/* MDEFINP - auxiliary that defines a macro body given input from nextrawpp().
**	Returns true if everything went OK, else wants rest of input flushed.
*/
static int
mdefinp(mf)
struct macframe *mf;
{
    register int i;
    register char *cp;
    static PPTOK sptok = { T_WSP };
    tlist_t tl;
    int wspf;

    /* Current token is the first token of the macro body. */
    tlzinit(tl);		/* Init tokenlist */
    wspf = 0;			/* No whitespace seen yet */
    mf->mf_len = 0;		/* Keep track of # chars that body needs */
    while (rawpp == T_WSP)	/* Skip initial whitespace first... */
	nextrawpp();
    while (wspf >= 0) {
	switch (rawpp) {
	case T_EOF:
	case T_EOL:
	    /* Done, ignore any trailing whitespace */
	    if (tl.tl_tail && tl.tl_tail->pt_typ == T_MACCAT) {
		error("## operator cannot end macro body");
		tl.tl_tail->pt_typ = T_SHARP2;	/* Too hard to flush, so sub */
	    }
	    wspf = -1;		/* Leave loop now */
	    continue;

	case T_WSP:		/* Whitespace just sets flag */
	    wspf = 1;
	    if (tl.tl_tail && tl.tl_tail->pt_typ == T_MACCAT)
		wspf = 0;	/* Ignore wsp if ## was previous token */
	    nextrawpp();
	    continue;

	case T_IDENT:
	    for (i = 0; i < mf->mf_nargs; i++)	/* Scan to see if a param */
		if (!strcmp(mf->mf_parcp[i], rawval.cp))
		    break;
	    if (i < mf->mf_nargs) {	/* If found param, fix token */
		if (tl.tl_tail && tl.tl_tail->pt_typ == T_MACCAT)
		    rawpp = T_MACINS;	/* Say token is inserted param */
		else {
		    if (wspf) tltadd(tl, sptok), mf->mf_len++;
		    rawpp = T_MACARG;	/* Say token is expanded param */
		}
		rawval.i = i;		/* with this param # */ 
		mf->mf_len += 2;
		break;
	    }
	    /* Drop thru to add normal ident token to list */

	default:
	    if (wspf) tltadd(tl, sptok), mf->mf_len++;
	    mf->mf_len++;	/* Bump for token type */
	    if (rawval.cp)	/* Len is 1 extra for NUL terminator */
		mf->mf_len += strlen(rawval.cp) + 1;
	    break;		/* Add token */

	case T_SHARP:
	    if (tskipwsp() != T_IDENT) {
		error("Formal parameter must follow the # operator");
		continue;	/* Sigh, skip the # */
	    }
	    for (i = 0; i < mf->mf_nargs; i++)	/* Scan to see if param */
		if (!strcmp(mf->mf_parcp[i], rawval.cp))
		    break;
	    if (i >= mf->mf_nargs) {	 /* Check again */
		error("Formal parameter must follow the # operator");
		continue;		/* Sigh, skip the # */
	    }
	    if (wspf) tltadd(tl, sptok), mf->mf_len++;
	    rawpp = T_MACSTR;		/* Stringize this macro arg! */
	    rawval.i = i;
	    mf->mf_len += 2;
	    break;			/* Add to list and continue */

	case T_SHARP2:
	    /* Ignore any preceding whitespace.
	    ** Param preceding this op becomes inserted, not expanded!
	    */
	    if (!tl.tl_tail) {
		error("## operator cannot begin macro body");
		nextrawpp();		/* Flush it and continue */
		continue;
	    }
	    if (tl.tl_tail->pt_typ == T_MACARG)		/* Preceding param? */
		tl.tl_tail->pt_typ = T_MACINS;		/* Yes, say insert! */
	    rawpp = T_MACCAT;
	    mf->mf_len++;
	    break;
	}
	wspf = 0;
	tlrawadd(&tl);		/* Add token to list */
   	nextrawpp();		/* and get next and continue */
    }

    /* If no body, we win with nothing else to do. */
    if ((mf->mf_len += mf->mf_parlen) == 0) {
	mf->mf_body = NULL;
	return 1;
    }

    /* We now have the macro body tokenlist in TL.
    ** Using the computed length, we allocate dynamic storage for it and
    ** transform it into a body string.  Unfortunately this has to be done
    ** before we can check for a definition clash.
    */
    if ((cp = mf->mf_body = malloc(mf->mf_len+1)) == NULL) {
	error("Out of memory for macro body");
	mf->mf_len = mf->mf_parlen = 0;	/* Recover by saying no body */
	return 1;
    }

    /* First copy TRULY ASSININE parameter names, thanks to some
    ** anonymous moronic anal-retentive cretins on X3J11.  Really.
    */
    if (mf->mf_nargs > 0) {
	for (i = 0; i < mf->mf_nargs; i++) {
	    cp = estrcpy(cp, mf->mf_parcp[i]);
	    *cp++ = ',';
	}
	cp[-1] = ')';		/* Minor touch for easier debugging */
    }
    /* Copy tokenlist into a body in memory, with safety checks */
    if (((cp - mf->mf_body) != mf->mf_parlen)
      || ((tltomac(tl, cp) - mf->mf_body) != (mf->mf_len+1)))
	int_error("mdefinp: bad mac len");

    return 1;
}

/* MDEFSTR - auxiliary for use by startup code which defines macros given
**	a name, macro type, and string.
**	This cannot be used to define macros with arguments.
*/
static SYMBOL *
mdefstr(name, mactyp, body)
char *name;			/* Macro symbol name */
int mactyp;			/* Special MACF_ type */
char *body;			/* Text of macro definition */
{
    struct macframe m;

    m.mf_nargs = mactyp;
    m.mf_parlen = 0;
    if (body) {
	PPTOK *savppt = pptptr;	/* Save these values so can flush stg used */
	ppcstate_t savppc;

	ppcsave(savppc);
	sinbeg(body);		/* Redirect input to come from string */
	nextmacpp();		/* Set up first token */
	mdefinp(&m);		/* Gobble body from input, fill macframe */
	sinend();		/* Stop input, check for gobbling all! */
	ppcrest(savppc);	/* Flush all strings & pptoks used */
	pptptr = savppt;
    } else m.mf_body = NULL, m.mf_len = 0;
    return mdefsym(name, &m);	/* Always define macro, and return! */
}

/* MDEFSYM(name, &mf) - auxiliary that actually creates the macro symbol and
**	sets its definition, given a macro frame argument already set up
**	by other calls.
*/
static SYMBOL *
mdefsym(name, mf)
char *name;			/* Macro symbol name */
register struct macframe *mf;
{
    register SYMBOL *s;

    s = symfind(name, 1);	/* Find or create symbol so barfs on trunc */
    if (s->Sclass != SC_UNDEF) {
	s->Srefs--;
        s = symgcreat(name);	/* Create global symbol */
    }
    s->Sclass = SC_MACRO;	/* Say it's a macro */
    s->Sflags |= SF_MACRO;	/* Say ditto, with flag. */
    s->Smacnpar = mf->mf_nargs;	/* Set # args or MACF_ type */
    s->Smacparlen = mf->mf_parlen;	/* # chars in param-name prefix */
    s->Smaclen = mf->mf_len;	/* # chars total in macro body */
    s->Smacptr = mf->mf_body;	/* Body string already set up! */
    return s;			/* Return pointer to defined macro */
}
/* D_UNDEF() - Process #undef directive
*/
static int
d_undef()
{
    SYMBOL *sym;

    if (tskipwsp() != T_IDENT) {
	error("Macro name expected");	/* Bad identifier */
	return PPR_FLUSH;
    }

    *defcsname = 'd';			/* Allow "defined" to be found */
    sym = findmacsym(rawval.cp);
    *defcsname = SPC_MACDEF;		/* Hide "defined" again */
    if (sym != NULL) {			/* If already defined, */
	if (sym->Smacnpar >= MACF_ATOM)	/* Check for specialness */
	    freemacsym(sym);		/* OK to flush it, do so. */
	else error("Illegal to undefine \"%s\"", rawval.cp);
    }
}

static void
freemacsym(sym)
SYMBOL *sym;
{
    if (sym->Smacptr)		/* If it has a macro body, */
	free(sym->Smacptr);	/* free it up. */
    freesym(sym);		/* Then flush the symbol definition. */
}
/* D_ASM() - Process #asm directive
**
** All text between #asm and #endasm is turned into a series of
** asm() expressions, one per line.  Each line is macro-expanded before
** being put into the asm() string literal argument.
** Thus, the sequence
**	#asm
**		text1
**		text2
**	#endasm
** becomes:
**	asm("text1");
**	asm("text2");
** This is consequently only meaningful inside functions.
*/
static int asmfline;

static int
d_asm()
{
    flushtoeol();			/* ignore rest of line */
    if (inasm) {			/* bump level.  if nested, complain */
	error("Already in #asm, can't nest");
	return PPR_ATEOL;
    }
    inasm = 1;				/* Say processing assembler input */
    asmfline = fline;
    if (curtl.tl_head)
	int_error("d_asm: cooked top-level input");
    curtl = tlmake(T_EOL, 0);		/* Set up curtl with dummy */
    return PPR_CHECKEOL;
}

/* ASMREFILL() - refill nextpp's token list with an #asm input line.
**	Current char is 1st of line to crunch.
*/
static tlist_t
asmrefill()
{
    tlist_t tl;
    static PPTOK lptok = { T_LPAREN },
		stok = { T_SCONST },
		rptok = { T_RPAREN },
		sctok = { T_SCOLON },
		eoltok = { T_EOL };
    tlzinit(tl);
    ppcreset();		/* Clear token char pool and token stg */
    pptreset();

    for (;;) {
	while (cskiplwsp() == '\n')	/* Flush whitespace and lines */
	    nextch();
	if (ch == '#') {	/* If hit directive */
	    nextch();		/* Set up next char */
	    directive();	/* and process directive! */
	    if (!inasm) {	/* If it included an #endasm, */
		if (ch != '\n') {
		    pushch(ch);
		    pushstr("\n");
		}
		return tl;	/* then just return empty list. */
	    }
	    continue;
	}
	break;
    }
    tl = tlmake(T_IDENT, "asm");	/* Make start of returned tokenlist */
    tltadd(tl, lptok);		/* Add left-paren */
    stok.pt_val.cp = ppcbeg();	/* Got something real, start a literal! */
    ppcput('"');

    for (;;) { switch (ch) {	/* Scan starting with current char */
    case EOF:
	error("EOF within unterminated #asm beginning at line %d", asmfline);
	inasm = 0;		/* Stop now */
	break;			/* Still try to finish up cleanly */

    case '\n':			/* Newline means done, return tokenlist! */
	break;

    case '/':			/* Check for possible start of comment */
	if (nextch() == '*')
	    scancomm(), nextch(), ppcput(' ');
	else ppcput('/');
	continue;

    case '\\':			/* Must quote these chars */
    case '"':
	ppcput('\\'); ppcput(ch); nextch();
	continue;

    default:
	if (!iscsymf(ch)) {	/* Start of identifier? */
	    ppcput(ch);		/* Nope, just pass char on */
	    nextch();
	    continue;
	} else {
	    SYMBOL *sym;
	    register char *cp;
	    tlist_t strtl;

	    ppcput(ch);			/* Store first char of ident */
	    cp = ppcptr;		/* Remember pointer to it */
	    while (iscsym(nextch()))	/* Gobble all of identifier */
		ppcput(ch);
	    (void)ppcend();

	    if (!(sym = findmacsym(cp))		/* If not a macro, */
	      || !mexptop(sym, 0)) {		/* or can't expand it, */
		(void)ppcbackup();		/* just undo the ppcend() */
		continue;			/* and carry on! */
	    }
	    /* Expanded macro!
	    ** First do tricky stuff to recover the string literal
	    ** token we were collecting, by clobbering the collected
	    ** macro identifier with '"' and NUL.  There will always be
	    ** enough room for this because the identifier had at least one
	    ** char and was null-terminated.
	    */
	    *cp = '"';			/* Set end quote of string literal */
	    *++cp = 0;			/* Tie off string */
	    tltadd(tl, stok);		/* And add literal to token list */

	    /* Now get the expanded macro tokens, which mexptop() left
	    ** in a tokenlist in curtl.  We swipe that and translate it into
	    ** a single string literal which is appended to our tokenlist.
	    */
	    strtl = tlstrize(curtl);	/* Make string lit from it */
	    tlzinit(curtl);		/* Done with that list */
	    tllapp(tl, strtl);		/* Append lit to our list */

	    stok.pt_val.cp = ppcbeg();	/* Now start a new string literal */
	    ppcput('"');
	    continue;

	}
    }				/* End of switch */
    break;
    }				/* End of loop */

    ppcput('\\');
    ppcput('n');
    ppcput('"');		/* Done, stop literal */
    (void)ppcend();
    tltadd(tl, stok);		/* And add to list */
    tltadd(tl, rptok);		/* Add right paren */
    tltadd(tl, sctok);		/* And semicolon */
    tltadd(tl, eoltok);		/* And EOL for niceness to passthru() */
    tltadd(tl, eoltok);		/* Another one to sacrifice to nextpp() */
    return tl;			/* Return the tokenlist! */
}

/* D_ENDASM() - Process #endasm directive
*/
static int
d_endasm()
{
    if (inasm == 0)			/* If not inside #asm, complain */
	error("Not in #asm, ignoring #endasm");
    else inasm = 0;			/* Tell d_asm() to stop */
    return PPR_FLUSH;
}
#if 0
	This page contains all of the "conditional commands", i.e.
if, ifdef, ifndef, elif, else, and endif.  ifdef and ifndef are merely
variants of #if.
	flush  means if flushing.
	flush= means if flushing at current level.
	flush* means if flushing at any other level.

Action table:
   Encounter	Prev	Flush?	Action to take:
	if	*	flush	push lev, set in-if, keep flushing.
		*	ok	push lev, set in-if, test.  Fail: set flush lev
	elif	if	flush*	Set in-elif, keep flushing.
		if	flush=	Test.  Win: set in-elif; fail: set in-if,flush.
		if	ok	Set in-elif, set flush lev.
		elif	flush	Stay in elif, keep flushing.
		elif	ok	Set in-elif, set flush lev.
		other = Error.
	else	if	flush=	Set in-else, stop flush.
		if	flush*	Set in-else, keep flushing.
		elif	flush	Set in-else, keep flushing.
		if	ok	Set in-else, set flush lev.
		elif	ok	Set in-else, set flush lev.
		other = Error.
	endif	*	flush=	Pop level.
		*	flush*	Pop level, keep flushing.
		*	ok	Pop level.
		not if/elif/else = Error.

	The tricky part is that elif only sets in-elif if the initial
if (or one of the previous elifs) was true.  As long as no test has succeeded
the setting is always kept at in-if.  This allows both elif and else to
know whether they are part of a elif chain that succeeded or not.
#endif

/* D_IFDEF() - Process #ifdef and #ifndef directives
*/
static int
d_ifdef(cond)
int cond;		/* 1 == ifdef, 0 == ifndef */
{

    if (++iflevel >= MAXIFLEVEL-1) {	/* this is a new if level */
	error("#if nesting depth exceeded");
	--iflevel;
    }
    iftype[iflevel] = IN_IF;	/* Say IF seen for this level */
    iffile[iflevel] = 0;	/* In current input file */
    ifline[iflevel] = fline;	/* At this line */
    if (flushing) return PPR_FLUSH;	/* if in false condition, that's all */

    if (nextrawpp() != T_WSP
      || nextrawpp() != T_IDENT) {
	error("Macro name expected");		/* Bad identifier */
	return PPR_FLUSH;
    }
    if (cond == ((findmacsym(rawval.cp) != NULL)))
	return PPR_CHECKEOL;	/* good condition, return */
    checkeol();
    flushcond();		/* Failed, flush body */
    return PPR_ATEOL;
}

/* D_IF() - Process #if directive
*/
static int
d_if()
{
    if (++iflevel >= MAXIFLEVEL-1) {	/* this is a new if level */
	error("#if nesting depth exceeded");
	--iflevel;
    }
    iftype[iflevel] = IN_IF;		/* Say IF seen for this level. */
    iffile[iflevel] = 0;		/* In current input file */
    ifline[iflevel] = fline;		/* At this line */
    if (flushing) return PPR_FLUSH;	/* If already flushing, that's all. */

    if (!iftest())		/* Do the test.  If fail, */
	flushcond();		/* then flush body. */
    return PPR_ATEOL;
}

/* D_ELIF() - Process #elif directive
*/
static int
d_elif()
{
    /* Make sure an #if or #elif preceeded this #elif */
    if (iftype[iflevel] == IN_ELSE) {	/* If not IN_IF or IN_ELIF */
	error("#elif without preceding #if, treating as #if");
	d_if();				/* Handle as if plain #if */
	return;
    }

    /* For helpfulness, check to see if active conditional is in same file */
    if (iffile[iflevel] > 0)
	iffwarn("elif");	/* Matches condit from different file */

    /* See if OK for elif to test its expression.  This is only allowed
    ** if we are in an #if that just failed, or part of an elif chain
    ** that has never succeeded yet (which looks just the same).
    */
    if (flushing == iflevel && iftype[iflevel] == IN_IF) {
	if (iftest()) {		/* OK, do the test!! */
	    flushing = 0;		/* Won!  Stop flushing */
	    iftype[iflevel] = IN_ELIF;	/* and say inside an elif. */
	    iffile[iflevel] = 0;	/* In current input file */
	    ifline[iflevel] = fline-1;	/* At this line (note EOL gobbled) */
	}			/* Else, stay in-if and keep flushing. */
	return PPR_ATEOL;
    }

    /* No need to perform test; this elif body must always fail at this point.
    **  Just ensure we're marked in-elif and see whether to start flushing
    ** or not.
    */
    iftype[iflevel] = IN_ELIF;	/* Set or stay in-elif */
    iffile[iflevel] = 0;	/* In current input file */
    ifline[iflevel] = fline;	/* At this line */

    if (flushing) return PPR_FLUSH;	/* If already flushing, just keep flushing. */
    flushtoeol();		/* No, start flushing to start of next line */
    flushcond();		/* And start flush checking now. */
    return PPR_ATEOL;
}


/* D_ELSE() - Process #else directive
*/
static int
d_else()
{
    int prev = iftype[iflevel];	/* Remember current level type */

    if (prev == IN_ELSE) {	/* Not IN_IF or IN_ELIF */
	error("#else without preceding #if");
	if (iflevel == 0) {	/* If not already inside a conditional */
	    prev = iftype[++iflevel] = IN_IF;	/* Pretend we were in an #if */
	    iffile[iflevel] = 0;
	    ifline[iflevel] = fline;
	    flushing = 0;			/* that was winning. */
	}
    }

    /* For helpfulness, check to see if active conditional is in same file */
    if (iffile[iflevel] > 0)
	iffwarn("else");		/* Matches condit from diff file */

    iftype[iflevel] = IN_ELSE;	/* Whatever, say #if no longer last thing */
    iffile[iflevel] = 0;
    ifline[iflevel] = fline;

    /* Now invert sense of flushing:
    **	If not flushing, start doing so.
    **	If already flushing at this level, stop.  EXCEPT if prev was elif,
    **		in which case keep flushing!
    **	If flushing at a different level, keep going.
    */
    if (flushing == 0) {
	checkeol();		/* Move to start of next line */
	flushcond();		/* Start flushing if not */
    } else if (flushing == iflevel && prev != IN_ELIF) {
	flushing = 0;		/* Stop flushing! */
    } else if (flushing != iflevel)	/* Else just keep flushing. */
	flushtoeol();
    return PPR_CHECKEOL;
}

/* D_ENDIF() - Process #endif directive
*/
static int
d_endif()
{
    if (iflevel) {		/* Are we in a conditional? */
	if (iflevel == flushing) flushing = 0; /* stop flushing */
	if (iffile[iflevel] > 0)	/* Verify balanced within file! */
	    iffwarn("endif");		/* Barf, condit is from diff file */
	iflevel--;		/* drop a level */
    } else
	error("Unmatched #endif");

    if (flushing) flushtoeol();
    return PPR_CHECKEOL;
}

/* FLUSHCOND - Auxiliary for if, elif, else.
**	Flush the body of a failing conditional command.
**	Assumes next token is 1st of next line (ie current char is 1st char).
**	When it returns, the current char will either be EOF
**	or will be the EOL which terminated the command that
**	caused flushing to stop.  See nextc()'s cmdlev variable.
*/
static void
flushcond()
{
    flushing = iflevel;		/* not ok, set flushing */
    indirp = 0;			/* No longer hacking directive */
    do {
	if (tskipwsp() == T_SHARP)	/* Check start of line */
	    directive();		/* Handle directive! */
	else flushtoeol();
    } while (flushing && !eof);		/* If still flushing, keep going. */
}

/* IFTEST - auxiliary for #if and #elif.
**	Parses the conditional expression and returns its value.
** This works by gobbling the rest of the line as a token list,
** expanding it, and then substituting this token list as if a macro
** to the C expression parser.
**	On return, current token is garbage (usually T_EOF after the
** mexplim()) but current char is 1st of next line after the #if.
** Be careful not to try to examine the "current token" after iftest().
** Sigh.
*/
static int
iftest()
{
    PPTOK *p;
    static PPTOK scoltok = { T_SCOLON };	/* Token for ";" */
    static PPTOK eoltok = { T_EOL };
    int won = 0;		/* Set non-zero if expr parse wins. */
    long val;

    /* Set up to parse a one-line constant expression */
    if (tlpcur(curtl))		/* Make sure we can hack curtl */
	int_error("iftest: parsing #if with active curtl");

    *defcsname = 'd';		/* Restore proper 1st char to "defined" sym */
    curtl = mexplim(getlinetl(), 0);	/* Read line, expand it */
    *defcsname = SPC_MACDEF;	/* Re-Zap 1st char of "defined" macro sym */

    /* Check to make sure no identifiers left */
    for (p = tlpcur(curtl); p; p = tokn2p(p->pt_nxt))
	if (p->pt_typ == T_IDENT) {
	    note("Undefined identifier \"%s\" - substituting \"0L\"",
			p->pt_val.cp);
	    p->pt_typ = T_ICONST;
	    p->pt_val.cp = "0L";
	}
    if (tlpcur(curtl)) {	/* Add ";" on end */
	tltadd(curtl, scoltok);
	tltadd(curtl, eoltok);	/* Plus EOL in case of inasm */
	p = curtl.tl_tail;	/* Remember ptr to last token */
#if DEBUG_PP
	if (debpp) pmactl("iftest", curtl, 0);
#endif
	nextoken();		/* Initialize top-level token parser */
	val = pconst();		/* Parse input into value */
	curptr = NULL;		/* Clean up from recursive nextpp() */
	if (!tlpcur(curtl)	/* Make sure everything gobbled */
	  || tlpcur(curtl) == p) {	/* If anything left, better be EOL */
	    ++won;		/* Yep to either, parse won! */
	}
	tlzinit(curtl);		/* Ensure input tokenlist is flushed */
    }
    if (!won) {
	error("Bad syntax for #if expression, using 0");
	val = 0;
    }
    return val != 0;		/* Return logical result */
}
/* Various auxiliaries that exist only to provide helpful warnings or
** error messages if conditionals appear to be unbalanced across file
** boundaries.
*/

/* IFFWARN - Called when changing or ending an active conditional which
**	started in another file.
*/
static void
iffwarn(typ)
char *typ;
{
    warn("#%s matches #%s from different file (\"%s\", line %d)",
	typ,
	ifname[iftype[iflevel]],
	inc[iffile[iflevel]-1].cname,
	ifline[iflevel]);
}

/* IFPUSH - Called when file input pushed by #include, to fix up
**	the info saved about active conditionals.
*/
static void
ifpush(inlev)
{
    register int i;
    ++inlev;				/* Bump up so never zero */
    for (i = iflevel; i > 0; --i) {	/* For all active conditionals */
	if (iffile[i] == 0)		/* If in condit for this file */
	    iffile[i] = inlev;		/* remember its place in inc[] */
    }
}

/* IFPOPCHK - Current input file just ended, popping input or stopping
**	altogether.  Check for unterminated or unbalanced conditionals,
**	and output helpful messages if necessary.
*/
static void
ifpopchk(inlev)
{
    register int i;

    if (inlev) {		/* Just popping an include file? */
	for (i = iflevel; i > 0; --i) {
	    if (iffile[i] == 0) {	/* If still in condit for this file */
		warn("Unterminated #%s (starting at line %d)",
				ifname[iftype[i]], ifline[i]);
		iffile[i] = -1;		/* Say name no longer available */
	    } else if (iffile[i] == inlev)
		iffile[i] = 0;		/* This will become current file! */
	}
    } else {

	/* EOF of whole input, check for unterminated stuff and give errors */
	for (i = iflevel; i > 0 ; --i)		/* For each active condit */
	    error("Unterminated #%s (starting at line %d%s)",
			ifname[iftype[i]], ifline[i],
			iffile[i] ? " of an included file"	/* Gone file */
				: "");				/* Main file */
    }
}
/* D_INCLUDE() - Process #include directive
**
**	 Note that this emulates the Un*x compiler behavior by
** starting the search for "" files in the current source directory,
** rather than in the process' connected directory.
*/
static int cinctry();	/* Auxiliary just for cinclude() */

static int
d_include()
{
    char f[FNAMESIZE], f2[FNAMESIZE];
    int ftype, done = 0;
    FILE *fp = NULL;

    if ((ftype = getfile(f, FNAMESIZE)) == 0)
	return PPR_FLUSH;	/* If failed to get name, ignore */

    /* A filename starting with '/' (ie an absolute pathname)
    ** tries just that name; nothing would else make sense.
    */
    if (*f == '/') {
	fp = fopen(strcpy(f2, f), "r");	/* Try to open just this one */
	++done;				/* Always done now */
    } else if (ftype != '>') {
	/* File specified with "file" instead of <file>.  Must look in
	** several places, starting with the directory the input source is in.
	*/
	estrcpy(estrcpy(estrcpy(f2,	/* Use source filename pref+suff */
		inpfdir), f), inpfsuf);
	if (fp = fopen(f2, "r"))
	    ++done;
	else    /* Unsuccessful open, try -I switches if any, in order given */
	    if (nincpaths)
		done = cinctry(nincpaths, incpaths, f2, f, &fp);
    }
    /* Now drop thru, so if nothing worked for a ""-type include, we
    ** try looking in the standard places for <>.
    */
    if (!done) {		/* Look in <> places */

	/* If redirecting <sys/ > files, try alternate paths from -h switches.
	** Note that default entries (if they exist) are tried last.
	*/
	if ((nhfsypaths || nihfsypaths) && (strncmp(f, "sys/", 4) == 0)
	  && (nhfsypaths <= 0
		|| !cinctry(nhfsypaths, hfsypaths, f2, f+4, &fp)))
	    cinctry(nihfsypaths, ihfsypaths, f2, f+4, &fp);

	/* If not, or didn't succeed, just handle basic <> file with -H sws.
	** Note that default entries (if they exist) are tried last.
	*/
	if (!fp
	  && (nhfpaths <= 0 || !cinctry(nhfpaths, hfpaths, f2, f, &fp)))
	    cinctry(nihfpaths, ihfpaths, f2, f, &fp);
    }

    if (!fp)				/* If nothing worked, complain */
	error("Can't open include file, last tried \"%s\"", f2);
    else filepush(fp, f2, 1, 1, 1);	/* Won, push to new input file! */
    return PPR_ATEOL;
}

/* CINCTRY - Attempt to open an include file using the specified array
**	of directory pathnames.
**	Succeeds if file is opened OR if a NULL entry is encountered.
*/
static int
cinctry(n, ptab, f2, f, fp)
register int n;			/* # paths in table */
char **ptab;			/* Addr of table */
char *f2, *f;			/* Places to deposit built and orig names */
FILE **fp;
{
    for(; --n >= 0; ++ptab) {
	if (!*ptab || !**ptab)		/* If we hit enforced stop, */
	    return 1;			/* always done now. */
	fstrcpy(f2, *ptab, f);		/* Build filename to try */
	if ((*fp = fopen(f2, "r")) != NULL)
	    return 1;			/* Won! */
    }
    return 0;				/* No stop and no opens... */
}

/* FILEPUSH - auxiliary for #include and #asm
**	Similar to bstrpush(), but for file input rather than string input.
**	Note that the current char needs to be saved; for both #include and
**	#asm this will be the 1st char on the next line after the directive,
**	due to the tokenizer's peek-ahead strategy.
*/
static void
filepush(fp, fname, lin, pag, flin)
FILE *fp;
char *fname;
int lin, pag, flin;
{
    if (inlevel >= MAXINCLNEST-1) {	/* Make sure we have room */
	error("Include file nesting depth exceeded -- ignoring %s",fname);
	return;				/* Just ignore it if not */
    }

    pushch(ch);				/* Remember current file input char */
    if (eof)				/* If read-ahead has left us at */
	eof = 0;			/* top-level EOF, undo it. */
    strcpy(inc[inlevel].cname, inpfname);	/* Save old context */
    inc[inlevel].cptr = in;
    inc[inlevel].cpage = page;
    inc[inlevel].cline = line;
    inc[inlevel].cfline = fline;
    ifpush(inlevel);			/* Fix up conditional info */

    inlevel++;				/* Create new context */
    strcpy(inpfname, fname);		/* Set new current file name */
    in = fp;				/* Set new current input stream */
    fline = flin;
    line = lin;
    page = pag;
#if DEBUG_PP
    if (debpp) {
	int savch = ch;
	nextch();
	fprintf(fpp, "#include %d: saved char %#o, new file \"%s\", new char %#o\n",
			inlevel, savch, fname, ch);
	return;
    }
#endif
    nextch();				/* Set up new current char */
}
/* D_LINE() - Process #line directive
**
**	Note macro substitution is performed on the input.
*/
static int
d_line()
{
    tlist_t tl;
    PPTOK *pint, *pstr;

    /* Gobble rest of line into tokenlist, expand it, and strip wsp */
    tl = tlwspdel(mexplim(getlinetl(),0), 1);	/* Flush all wsp */

    if (!(pint = tlpcur(tl)))
	return warn("Empty #line"), PPR_CHECKEOL;
    if (pint->pt_typ != T_ICONST)
	return warn("Bad #line number"), PPR_CHECKEOL;
    if (pstr = tokn2p(pint->pt_nxt)) {
	if (pstr->pt_typ != T_SCONST)
	    return warn("Bad #line filename"), PPR_CHECKEOL;
	if (pstr->pt_nxt)
	    return warn("Bad #line syntax"), PPR_CHECKEOL;
    }
    /* Complete winnage, process stuff. */
    fline = atoi(pint->pt_val.cp);	/* Set up number */
    if (pstr)				/* Copy filename w/o quotemarks */
	(void) sltostr(pstr, inpfname, FNAMESIZE-1);

    return PPR_CHECKEOL;
}
/* D_ERROR() - Process #error directive
*/
static int
d_error()
{
    char errstr[120];		/* Max size of err msg */

    /* Gobble in pp-tokens and turn carefully into a string */
    tltostr(getlinetl(), errstr, (int)sizeof(errstr)-1);
    error("#error: %s", errstr);	/* Provoke error! */
    return PPR_CHECKEOL;
}

/* D_PRAGMA() - Process #pragma directive (barf choke vomit puke bletch)
*/
static int
d_pragma()
{
    flushtoeol();
    note("Unknown #pragma");
    return PPR_CHECKEOL;
}
/* GETFILE(cp, n) - Read an include file name into specified loc,
**	using only up to N chars (plus NUL).
**	Returns 0 if name was bad, else '"' for "file" and '>' for <file>.
*/

static int
getfile(f, n)
char *f;
{
    int i, type;
    char *s = f;

    pushch(ch);		/* Set up current char for re-read by scanhwsp */
    scanhwsp();		/* Flush horiz whitespace */
    switch (ch) {
	case '"': type = '"';	break;
	case '<': type = '>';	break;
	case '\n':
	    error("No filename for #include");
	    return 0;
	default:	/* Neither one, it better be some macros */
	    /* Get line of tokens, expand, and trim wsp from ends */
	    tltostr(tlwspdel(mexplim(getlinetl(), 0), 0), f, n);
	    type = 0;
	    if (*f == '"') type = '"';
	    else if (*f == '<') type = '>';
	    if (!type || (f[(i = strlen(f))-1] != type)) {
		error("Bad syntax for #include file: %s", f);
		return 0;
	    }
	    memmove(f, f+1, i -= 2);	/* Shift string up */
	    f[i] = '\0';		/* And tie off properly */
	    return type;
    }

    /* Got first delimiter char, try to find terminating one.
    ** The only thing that stops our scan is EOL, EOF, or terminator.
    ** If more stuff exists in line after terminator, we complain.
    */
    nextch();			/* Skip over 1st delimiter */
    i = n;
    while (--i > 0) {
	if (isceol(ch) || ch == EOF) {
	    type = 0;		/* Failed, term not found */
	    break;
	}
	if (ch != type) {
	    *s++ = ch;		/* Nothing special, store char */
	    nextch();
	    continue;
	}

	/* Char is delimiter */
	*s = '\0';		/* Tie string off! */
	nextch();		/* Move over delimiter */
	if (tskipwsp() != T_EOL) {
	    type = 0;		/* Error, more junk on line */
	    error("Bad #include syntax, junk follows filename");
	}
	break;
    }

    if (i <= 0) {
	error("Filename too long");
	type = 0;
    }
    *s = 0;
    flushtoeol();
    return(type);
}
/* Various misc auxiliaries for directive parsing */

/* TSKIPWSP() - (Token-based) Skip PP whitespace starting with current char.
**	Returns first non-WSP token.
**	For use in directives - complains and stops if non-PP whitespace
**	is seen, specifically \r, \v, \f (CR, VT, FF)
*/
static int
tskipwsp()
{
    while (nextrawpp() == T_WSP) ;
    return rawpp;
}

/* TSKIPLWSP() - (Token-based) Skip Lines & Whitespace starting with
**	current char.
**	Returns first non-WSP, non-EOL token.
**	This skips anything that qualifies as C whitespace.
*/
static int
tskiplwsp()
{
    for (;;) switch (nextrawpp()) {
	case T_WSP:
	case T_EOL:	continue;
	default:	return rawpp;
    }
}

/* FLUSHTOEOL() - Flushes all input up to next EOL, including comments,
**	starting with current token (current char is 1st char of next token)
**	Stops with T_EOL as current token, and current char the 1st
**	after the EOL.
**	Except for EOF, of course.
**	Does it fast.  Ignores string literals since they shouldn't include
**	newlines anyway.
*/
static int
flushtoeol()
{
    if (rawpp == T_EOL) return rawpp;	/* Make sure not already there */
    for (;;) switch (ch) {
	case EOF: return rawpp = T_EOF;
	case '\n': return nextch(), rawpp = T_EOL;
	case '/': if (nextch() == '*')
		    scancomm();
	default: nextch();
    }
}

/* CSKIPLWSP() - (Char-based) Special routine for top-level macro expansion.
**	Starting with current char,
**	scans character stream to find next non-whitespace char,
**	without going through or affecting tokenizer.
**	Whitespace includes line breaks and comments.
**	Returns new current char (first non-whitespace).
**	If first non-LWSP char is '#' and an EOL has been skipped, then
**	a \n is pushed back and returned so that directives will be
**	recognized.  Hack, hack.
*/
static int
cskiplwsp()
{
    int eolseen = 0;
    for (;;) switch (ch) {
	case '/':
	    if (nextch() != '*') {
		pushch(ch);
		return pushstr("/");
	    }
	    scancomm();
	    nextch();
	    break;
	case '\n':
	    ++eolseen;
	default:
	    if (!iscwsp(ch)) {	/* See if any kind of C whitespace */
		if (eolseen && ch == '#') {	/* If possible directive, */
		    pushch(ch);			/* push back char */
		    pushstr("\n");		/* and make EOL be current */
		}
		return ch;
	    }
	    nextch();
    }
}


/* CHECKEOL - finish up after directive has parsed everything it wants.
**	Makes sure that there is nothing but whitespace between the
**	current token and EOL; checks for already being at EOL to avoid
**	gobbling a line by accident.
*/
static void
checkeol()
{
    if (rawpp == T_EOL || rawpp == T_EOF)	/* Check for already at EOL */
	return;
    for (;;) switch (nextrawpp()) {	/* Not there, start skipping wsp */
	case T_WSP: continue;
	default:
	    warn("Non-whitespace following directive");
	    flushtoeol();
	case T_EOL:
	case T_EOF:
	    return;
    }
}

#if 0
/* GETIDREST - Get an identifier string when current char known to be OK.
**	Returns # of chars read in the identifier.
*/
static int
getidrest(str)
char *str;
{
    char *s = str;
    int i = IDENTSIZE-1;		/* Leave room for null terminator */

    *s = ch;				/* First char always goes in */
    while (iscsym(nextch()))		/* If succeeding char is alphanum */
	if (--i > 0) *++s = ch;		/* is legal, add to ident */
    *++s = '\0';			/* null terminate */
    if (i <= 0) {
	note("Identifier truncated: \"%s\"", str);
	i = 1;				/* Set i so return value is correct */
    }
    return IDENTSIZE - i;		/* Return number of chars in string */
}
#endif
/* GETLINETL() - Gobble rest of raw input line into a tokenlist
**	and return that.
**	On return, current token will be T_EOL (or T_EOF) and
**	current char the first one past the EOL.
*/
static tlist_t
getlinetl()
{
    tlist_t tl;

    tlzinit(tl);
    while (nextrawpp() != T_EOL && rawpp != T_EOF)
	tlrawadd(&tl);
    return tl;
}

/* TLWSPDEL(tl) - Removes T_WSP tokens from a tokenlist,
**	to simplify life for other routines.
**	If allf 0, only flushes from start and end.
**	Otherwise, flushes all T_WSP from entire list.
*/
static tlist_t
tlwspdel(tl, allf)
tlist_t tl;
int allf;			/* TRUE to flush all whitespace */
{
    register PPTOK *p, *lastp = NULL;

    for (p = tlpcur(tl); p;)
	if (p->pt_typ == T_WSP
	  && (allf || p == tl.tl_head || p == tl.tl_tail)) {
	    if (lastp) lastp->pt_nxt = p->pt_nxt;	/* Fix up prev */
	    else tl.tl_head = tokn2p(p->pt_nxt);
	    if (!(p = tokn2p(p->pt_nxt)))		/* Fix up tail */
		tl.tl_tail = (lastp ? lastp : tl.tl_head);
	} else lastp = p, p = tokn2p(p->pt_nxt);	/* Just pass non-wsp */
    return tl;
}

/* TLMAKE(typ, val) - Make a tokenlist consisting of just one token,
**	with the given token-type and value.
**	This routine is at the end of CCPP for now to avoid lots of warning
**	messages about mismatched argument types.
*/
static tlist_t
tlmake(typ, val)
int typ;
union pptokval val;
{
    tlist_t tl;
    static PPTOK ztok;
    *(tl.tl_head = tl.tl_tail = tokpcre()) = ztok;
    tl.tl_head->pt_typ = typ;
    tl.tl_head->pt_val = val;
    return tl;
}