/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "glk/tads/tads2/tokenizer.h"
#include "glk/tads/tads2/error.h"
#include "glk/tads/tads2/memory_cache_heap.h"
#include "glk/tads/tads2/os.h"
#include "glk/tads/os_glk.h"

namespace Glk {
namespace TADS {
namespace TADS2 {


/* special temporary buffers for <<expr>> macro expansion */
static char tokmac1[]  = ",say((";
static char tokmac1s[] = "(say((";
static char tokmac2[]  = "),nil),\"";
static char tokmac3[]  = "),nil))";
static char tokmac4[]  = ")";

/* forward definition of static functions */
static int tokdfhsh(const char *sym, int len);


/* find a #define symbol */
static tokdfdef *tok_find_define(tokcxdef *ctx, const char *sym, int len)
{
	int       hsh;
	tokdfdef *df;

	/* find the appropriate chain the hash table */
	hsh = tokdfhsh(sym, len);

	/* search the chain for this symbol */
	for (df = ctx->tokcxdf[hsh] ; df ; df = df->nxt)
	{
		/* if this one matches, return it */
		if (df->len == len && !memcmp(df->nm, sym, (size_t)len))
		{
			/* fix it up if it's the special __FILE__ or __LINE__ symbol */
			if (len == 8)
			{
				if (!memcmp(sym, "__FILE__", (size_t)8))
				{
					size_t elen;

					/*
					 *   put in the opening single quote, since we want
					 *   the expanded result to be a string
					 */
					df->expan[0] = '\'';

					/* get the name */
					linnam(ctx->tokcxlin, df->expan+1);

					/* get the length, and add the closing quote */
					elen = strlen(df->expan);
					df->expan[elen] = '\'';

					/*
					 *   set the length of the expansion, including the
					 *   quotes (the first quote was measured in the
					 *   length originally, but the second quote hasn't
					 *   been counted yet, so add one to our original
					 *   length)
					 */
					df->explen = (int)elen + 1;

					/* if the expansion is too long, it's an error */
					if (df->explen >= OSFNMAX)
						errsig(ctx->tokcxerr, ERR_LONG_FILE_MACRO);
				}
				else if (!memcmp(sym, "__LINE__", (size_t)8))
				{
					ulong l;

					/* get the line number */
					l = linlnum(ctx->tokcxlin);

					/* convert it to a textual format for the expansion */
					Common::sprintf_s(df->expan, "%lu", l);

					/* set the expanded value's length */
					df->explen = strlen(df->expan);

					/* make sure the expansion isn't too long */
					if (df->explen >= 40)
						errsig(ctx->tokcxerr, ERR_LONG_LINE_MACRO);
				}
			}

			/* return it */
			return df;
		}
	}

	/* didn't find anything */
	return nullptr;
}

/*
 *   Write preprocessor state to a file
 */
void tok_write_defines(tokcxdef *ctx, osfildef *fp, errcxdef *ec)
{
	int        i;
	tokdfdef **dfp;
	tokdfdef  *df;
	char       buf[4];

	/* write each element of the hash chains */
	for (i = TOKDFHSHSIZ, dfp = ctx->tokcxdf ; i ; ++dfp, --i)
	{
		/* write each entry in this hash chain */
		for (df = *dfp ; df ; df = df->nxt)
		{
			/* write this entry */
			oswp2(buf, df->len);
			oswp2(buf + 2, df->explen);
			if (osfwb(fp, buf, 4)
				|| osfwb(fp, df->nm, df->len)
				|| (df->explen != 0 && osfwb(fp, df->expan, df->explen)))
				errsig(ec, ERR_WRTGAM);
		}

		/* write a zero-length entry to indicate the end of this chain */
		oswp2(buf, 0);
		if (osfwb(fp, buf, 4)) errsig(ec, ERR_WRTGAM);
	}
}

/* compute a #define symbol's hash value */
static int tokdfhsh(const char *sym, int len)
{
	uint hsh;

	for (hsh = 0 ; len ; ++sym, --len)
		hsh = (hsh + *sym) & TOKDFHSHMASK;
	return hsh;
}

/* convert a #define symbol to lower case if folding case */
static const char *tok_casefold_defsym(tokcxdef *ctx, char *outbuf,
								 const char *src, int len)
{
	if (ctx->tokcxflg & TOKCXCASEFOLD)
	{
		char *dst;
		int   rem;

		/* make a lower-case copy of the symbol */
		rem = (len > TOKNAMMAX ? TOKNAMMAX : len);
		for (dst = outbuf ; rem > 0 ; ++dst, ++src, --rem)
			*dst = (Common::isUpper((uchar)*src) ? Common::isLower((uchar)*src) : *src);

		/* use the lower-case copy instead of the original */
		return outbuf;
	}
	else
	{
		/* return the original unchanged */
		return src;
	}
}

/*
 *   convert a token to lower-case if we're folding case
 */
void tok_case_fold(tokcxdef *ctx, tokdef *tok)
{
	/* if we're in case-insensitive mode, convert the token to lower-case */
	if (ctx->tokcxflg & TOKCXCASEFOLD)
	{
		char *p;
		int   len;

		/* convert each character in the token to lower-case */
		for (p = tok->toknam, len = tok->toklen ; len != 0 ; ++p, --len)
		{
			/* if this character is upper-case, convert it to lower-case */
			if (Common::isUpper((uchar)*p))
				*p = Common::isLower((uchar)*p);
		}
	}
}

/* add a symbol to the #define symbol table, folding case if necessary */
void tok_add_define_cvtcase(tokcxdef *ctx, const char *sym, int len,
							const char *expan, int explen)
{
	char mysym[TOKNAMMAX];

	/* convert to lower-case if necessary */
	sym = tok_casefold_defsym(ctx, mysym, sym, len);

	/* add the symbol */
	tok_add_define(ctx, sym, len, expan, explen);
}

/* add a symbol to the #define symbol table */
void tok_add_define(tokcxdef *ctx, const char *sym, int len,
					const char *expan, int explen)
{
	int       hsh;
	tokdfdef *df;

	/* if it's already defined, ignore it */
	if (tok_find_define(ctx, sym, len))
		return;

	/* find the appropriate entry in the hash table */
	hsh = tokdfhsh(sym, len);

	/* allocate space for the symbol */
	df = (tokdfdef *)mchalo(ctx->tokcxerr,
							(sizeof(tokdfdef) + len + explen - 1),
							"tok_add_define");

	/* set up the new symbol */
	df->nm = df->expan + explen;
	df->len = len;
	df->explen = explen;
	memcpy(df->expan, expan, explen);
	memcpy(df->nm, sym, len);

	/* link it into the hash chain */
	df->nxt = ctx->tokcxdf[hsh];
	ctx->tokcxdf[hsh] = df;
}

/* add a #define symbol with a numeric value */
void tok_add_define_num_cvtcase(tokcxdef *ctx, char *sym, int len, int num)
{
	char buf[20];

	/* convert the value to a string */
	Common::sprintf_s(buf, "%d", num);

	/* add the text value */
	tok_add_define_cvtcase(ctx, sym, len, buf, strlen(buf));
}

/* undefine a #define symbol */
void tok_del_define(tokcxdef *ctx, const char *sym, int len)
{
	int       hsh;
	tokdfdef *df;
	tokdfdef *prv;

	/* find the appropriate chain the hash table */
	hsh = tokdfhsh(sym, len);

	/* search the chain for this symbol */
	for (prv = nullptr, df = ctx->tokcxdf[hsh] ; df ; prv = df, df = df->nxt)
	{
		/* if this one matches, delete it */
		if (df->len == len && !memcmp(df->nm, sym, (size_t)len))
		{
			/* unlink it from the chain */
			if (prv)
				prv->nxt = df->nxt;
			else
				ctx->tokcxdf[hsh] = df->nxt;

			/* delete this symbol, and we're done */
			mchfre(df);
			break;
		}
	}
}

/* scan a #define symbol to see how long it is */
static int tok_scan_defsym(tokcxdef *ctx, const char *p, int len)
{
	int symlen;

	/* make sure it's a valid symbol */
	if (!(Common::isAlpha((uchar)*p) || *p == '_' || *p == '$'))
	{
		errlog(ctx->tokcxerr, ERR_REQSYM);
		return 0;
	}

	/* count characters as long as we have valid symbol characters */
	for (symlen = 0 ; len && TOKISSYM(*p) ; ++p, --len, ++symlen) ;
	return symlen;
}

/* process a #define */
static void tokdefine(tokcxdef *ctx, const char *p, int len)
{
	const char *sym;
	int   symlen;
	const char *expan;
	char  mysym[TOKNAMMAX];

	/* get the symbol */
	sym = p;
	if (!(symlen = tok_scan_defsym(ctx, p, len)))
		return;

	/* if it's already in the table, log an error */
	if (tok_find_define(ctx, sym, symlen))
	{
		errlog(ctx->tokcxerr, ERR_DEFREDEF);
		return;
	}

	/* skip whitespace following the symbol */
	expan = sym + symlen;
	len -= symlen;
	while (len && t_isspace(*expan)) --len, ++expan;

	/* if we're folding case, convert the symbol to lower case */
	sym = tok_casefold_defsym(ctx, mysym, sym, symlen);

	/* define the symbol */
	tok_add_define(ctx, sym, symlen, expan, len);
}

/*
 *   Update the #if status for the current nesting.  Any enclosing
 *   negative #if will override everything inside, so we need to look
 *   through the nesting from the outside in until we either determine
 *   that everything is affirmative or we find a negative anywhere in the
 *   nesting.
 */
static void tok_update_if_stat(tokcxdef *ctx)
{
	int i;

	/* look through nesting from the outermost level */
	for (i = 0 ; i < ctx->tokcxifcnt ; ++i)
	{
		/* assume this level will apply to everything inside */
		ctx->tokcxifcur = ctx->tokcxif[i];

		/* if this level is off, everything inside is off */
		switch (ctx->tokcxif[i])
		{
		case TOKIF_IF_NO:
		case TOKIF_ELSE_NO:
			/*
			 *   this level is off, hence everything inside is off -- stop
			 *   here with the current (negative) determination
			 */
			return;

		default:
			/* so far we're in the "on" section, so keep looking */
			break;
		}
	}
}

/* process an #ifdef or a #ifndef */
static void tok_ifdef_ifndef(tokcxdef *ctx, const char *p, int len, int is_ifdef)
{
	int   symlen;
	const char *sym;
	int   stat;
	int   found;
	char  mysym[TOKNAMMAX];

	/* get the symbol */
	sym = p;
	if (!(symlen = tok_scan_defsym(ctx, p, len)))
		return;

	/* if we're folding case, convert the symbol to lower case */
	sym = tok_casefold_defsym(ctx, mysym, sym, symlen);

	/* see if we can find it in the table, and set the status accordingly */
	found = (tok_find_define(ctx, sym, symlen) != nullptr);

	/* invert the test if this is an ifndef */
	if (!is_ifdef) found = !found;

	/* set the #if status accordingly */
	if (found)
		stat = TOKIF_IF_YES;
	else
		stat = TOKIF_IF_NO;
	ctx->tokcxif[ctx->tokcxifcnt] = stat;

	/* allocate a new #if level (making sure we have room) */
	if (ctx->tokcxifcnt >= TOKIFNEST)
	{
		errlog(ctx->tokcxerr, ERR_MANYPIF);
		return;
	}
	ctx->tokcxifcnt++;

	/* update the current status */
	tok_update_if_stat(ctx);
}

/* process a #error */
static void tok_p_error(tokcxdef *ctx, const char *p, int len)
{
	errlog1(ctx->tokcxerr, ERR_P_ERROR,
			ERRTSTR, errstr(ctx->tokcxerr, p, len));
}

/* process a #ifdef */
static void tokifdef(tokcxdef *ctx, const char *p, int len)
{
	tok_ifdef_ifndef(ctx, p, len, TRUE);
}

/* process a #ifndef */
static void tokifndef(tokcxdef *ctx, const char *p, int len)
{
	tok_ifdef_ifndef(ctx, p, len, FALSE);
}

/* process a #if */
static void tokif(tokcxdef *ctx, const char *p, int len)
{
	errsig(ctx->tokcxerr, ERR_PIF_NA);
}

/* process a #elif */
static void tokelif(tokcxdef *ctx, const char *p, int len)
{
	errsig(ctx->tokcxerr, ERR_PELIF_NA);
}

/* process a #else */
static void tokelse(tokcxdef *ctx, const char *p, int len)
{
	int cnt;

	/* if we're not expecting #else, it's an error */
	cnt = ctx->tokcxifcnt;
	if (cnt == 0 || ctx->tokcxif[cnt-1] == TOKIF_ELSE_YES
		|| ctx->tokcxif[cnt-1] == TOKIF_ELSE_NO)
	{
		errlog(ctx->tokcxerr, ERR_BADPELSE);
		return;
	}

	/* switch to the appropriate #else state (opposite the #if state) */
	if (ctx->tokcxif[cnt-1] == TOKIF_IF_YES)
		ctx->tokcxif[cnt-1] = TOKIF_ELSE_NO;
	else
		ctx->tokcxif[cnt-1] = TOKIF_ELSE_YES;

	/* update the current status */
	tok_update_if_stat(ctx);
}

/* process a #endif */
static void tokendif(tokcxdef *ctx, const char *p, int len)
{
	/* if we're not expecting #endif, it's an error */
	if (ctx->tokcxifcnt == 0)
	{
		errlog(ctx->tokcxerr, ERR_BADENDIF);
		return;
	}

	/* remove the #if level */
	ctx->tokcxifcnt--;

	/* update the current status */
	tok_update_if_stat(ctx);
}

/* process a #undef */
static void tokundef(tokcxdef *ctx, const char *p, int len)
{
	const char *sym;
	int   symlen;
	char  mysym[TOKNAMMAX];

	/* get the symbol */
	sym = p;
	if (!(symlen = tok_scan_defsym(ctx, p, len)))
		return;

	/* if we're folding case, convert the symbol to lower case */
	sym = tok_casefold_defsym(ctx, mysym, sym, symlen);

	/* if it's not defined, log a warning */
	if (!tok_find_define(ctx, sym, symlen))
	{
		errlog(ctx->tokcxerr, ERR_PUNDEF);
		return;
	}

	/* undefine the symbol */
	tok_del_define(ctx, sym, symlen);
}

/* process a #pragma directive */
static void tokpragma(tokcxdef *ctx, const char *p, int len)
{
	/* ignore empty pragmas */
	if (len == 0)
	{
		errlog(ctx->tokcxerr, ERR_PRAGMA);
		return;
	}

	/* see what we have */
	if (len > 1
		&& (*p == 'c' || *p == 'C')
		&& (*(p+1) == '+' || *(p+1) == '-' || t_isspace(*(p+1))))
	{
		/* skip spaces after the 'C', if any */
		for (++p, --len ; len && t_isspace(*p) ; ++p, --len) ;

		/* look for the + or - flag */
		if (len && *p == '+')
			ctx->tokcxflg |= TOKCXFCMODE;
		else if (len && *p == '-')
			ctx->tokcxflg &= ~TOKCXFCMODE;
		else
		{
			errlog(ctx->tokcxerr, ERR_PRAGMA);
			return;
		}
	}
	else
	{
		errlog(ctx->tokcxerr, ERR_PRAGMA);
	}
}

/* process a #include directive */
static void tokinclude(tokcxdef *ctx, const char *p, int len)
{
	linfdef *child;
	tokpdef *path;
	const char *fname;
	int      match;
	int      flen;
	linfdef *lin;
	const char *q;
	size_t   flen2;

	/* find the filename portion */
	fname = p + 1;                            /* remember start of filename */
	path = ctx->tokcxinc;                    /* start with first path entry */

	if (!len)
	{
		errlog(ctx->tokcxerr, ERR_INCNOFN);
		return;
	}

	switch(*p)
	{
	case '<':
		match = '>';
		if (path && path->tokpnxt) path = path->tokpnxt;   /* skip 1st path */
		goto find_matching_delim;

	case '"':
		match = '"';

	find_matching_delim:
		for (++p, --len ; len && *p != match ; --len, ++p) ;
		if (len == 0 || *p != match) errlog(ctx->tokcxerr, ERR_INCMTCH);
		break;

	default:
		errlog(ctx->tokcxerr, ERR_INCSYN);
		return;
	}

	flen = p - fname;                         /* compute length of filename */
	for (q = p, flen2 = 0 ;
		 q > fname && *(q-1) != OSPATHCHAR && !strchr(OSPATHALT, *(q-1)) ;
		 --q, ++flen2) ;

	/* check to see if this file has already been included */
	for (lin = ctx->tokcxhdr ; lin ; lin = (linfdef *)lin->linflin.linnxt)
	{
		char *p2 = lin->linfnam;

		p2 += strlen(p2);

		while (p2 > lin->linfnam && *(p2-1) != OSPATHCHAR
			   && !strchr(OSPATHALT, *(p2-1)))
			--p2;
		if (strlen(p2) == flen2
			&& !memicmp(p2, q, flen2))
		{
			errlog1(ctx->tokcxerr, ERR_INCRPT, ERRTSTR,
					errstr(ctx->tokcxerr, fname, flen));
			return;
		}
	}

	/* initialize the line source */
	child = linfini(ctx->tokcxmem, ctx->tokcxerr, fname, flen, path, TRUE,
					(ctx->tokcxflg & TOKCXFLIN2) != 0);

	/* if not found, signal an error */
	if (!child) errsig1(ctx->tokcxerr, ERR_INCSEAR,
						ERRTSTR, errstr(ctx->tokcxerr, fname, flen));

	/* link into tokenizer list of line records */
	child->linflin.linnxt = (lindef *)ctx->tokcxhdr;
	ctx->tokcxhdr = child;

	/* if we're tracking sources for debugging, add into the chain */
	if (ctx->tokcxdbg)
	{
		ctx->tokcxdbg->dbgcxlin = &child->linflin;
		child->linflin.linid = ctx->tokcxdbg->dbgcxfid++;
	}

	/* remember my C-mode setting */
	if (ctx->tokcxflg & TOKCXFCMODE)
		ctx->tokcxlin->linflg |= LINFCMODE;
	else
		ctx->tokcxlin->linflg &= ~LINFCMODE;

	child->linflin.linpar = ctx->tokcxlin;   /* remember parent line source */
	ctx->tokcxlin = &child->linflin;   /* make the child the current source */
}

/* get a new line from line source, processing '#' directives */
static int tokgetlin(tokcxdef *ctx, int dopound)
{
	for (;;)
	{
		if (linget(ctx->tokcxlin))
		{
			/* at eof in current source; resume parent if there is one */
			if (ctx->tokcxlin->linpar)
			{
				lindef *parent;

				parent = ctx->tokcxlin->linpar;          /* remember parent */
				lincls(ctx->tokcxlin);               /* close included file */
				if (!ctx->tokcxdbg)               /* if no debug context... */
					mchfre(ctx->tokcxlin);              /* free line source */
				ctx->tokcxlin = parent;      /* reset to parent line source */
				if (parent->linflg & LINFCMODE)
					ctx->tokcxflg |= TOKCXFCMODE;
				else
					ctx->tokcxflg &= ~TOKCXFCMODE;
				continue;                       /* back for another attempt */
			}
			else
			{
				/* check for outstanding #if/#ifdef */
				if (ctx->tokcxifcnt)
					errlog(ctx->tokcxerr, ERR_NOENDIF);

				/* return end-of-file indication */
				return TRUE;
			}
		}

		/* if this is a multi-segment line, copy it into our own buffer */
		if (ctx->tokcxlin->linflg & LINFMORE)
		{
			char *p;
			uint  rem;
			int   done;

			if (!ctx->tokcxbuf)
			{
				/* allocate 1k as a default buffer */
				ctx->tokcxbuf = (char *)mchalo(ctx->tokcxerr, 1024,
											   "tok");
				ctx->tokcxbsz = 1024;
			}
			ctx->tokcxlen = 0;

			for (done = FALSE, p = ctx->tokcxbuf, rem = ctx->tokcxbsz ;
				 !done ; )
			{
				size_t len = ctx->tokcxlin->linlen;

				/* add the current segment's length into line length */
				ctx->tokcxlen += len;

				/* we're done after this piece if the last fetch was all */
				done = !(ctx->tokcxlin->linflg & LINFMORE);
				if (len + 1 > rem)
				{
					char *newp;

					/* increase the size of the buffer */
					if (ctx->tokcxbsz > (unsigned)0x8000)
						errsig(ctx->tokcxerr, ERR_LONGLIN);
					rem += 4096;
					ctx->tokcxbsz += 4096;

					/* allocate a new buffer and copy line into it */
					newp = (char *)mchalo(ctx->tokcxerr, ctx->tokcxbsz, "tok");
					memcpy(newp, ctx->tokcxbuf, (size_t)(p - ctx->tokcxbuf));

					/* free the original buffer, and use the new one */
					p = (p - ctx->tokcxbuf) + newp;
					mchfre(ctx->tokcxbuf);
					ctx->tokcxbuf = newp;
				}

				/* add the line to the buffer */
				memcpy(p, ctx->tokcxlin->linbuf, len);
				p += len;
				rem -= len;

				/* get the next piece of the line if there is one */
				if (!done)
				{
					if (linget(ctx->tokcxlin)) break;
				}
			}

			/* null-terminate the buffer, and use it for input */
			*p = '\0';
			ctx->tokcxptr = ctx->tokcxbuf;
		}
		else
		{
			ctx->tokcxptr = ctx->tokcxlin->linbuf;
			ctx->tokcxlen = ctx->tokcxlin->linlen;
		}

		/* check for preprocessor directives */
		if (dopound && ctx->tokcxlen != 0 && ctx->tokcxptr[0] == '#'
			&& !(ctx->tokcxlin->linflg & LINFNOINC))
		{
			const char *p;
			int     len;
			static const struct
			{
				const char *nm;
				int    len;
				int    ok_in_if;
				void (*fn)(tokcxdef *, const char *, int);
			}
			*dirp, dir[] =
			{
				{ "include", 7, FALSE, tokinclude },
				{ "pragma",  6, FALSE, tokpragma },
				{ "define",  6, FALSE, tokdefine },
				{ "ifdef",   5, TRUE, tokifdef },
				{ "ifndef",  6, TRUE, tokifndef },
				{ "if",      2, TRUE, tokif },
				{ "else",    4, TRUE, tokelse },
				{ "elif",    4, TRUE, tokelif },
				{ "endif",   5, TRUE, tokendif },
				{ "undef",   5, FALSE, tokundef },
				{ "error",   5, FALSE, tok_p_error }
			};
			int  i;

			/* scan off spaces between '#' and directive */
			for (len = ctx->tokcxlen - 1, p = &ctx->tokcxptr[1] ;
				 len && t_isspace(*p) ; --len, ++p) ;

			/* find and process the directive */
			for (dirp = dir, i = sizeof(dir)/sizeof(dir[0]) ; i ; --i, ++dirp)
			{
				/* compare this directive; if it wins, call its function */
				if (len >= dirp->len && !memcmp(p, dirp->nm, (size_t)dirp->len)
					&& (len == dirp->len || t_isspace(*(p + dirp->len))))
				{
					int cnt;
					int stat;

					/*
					 *   if we're not in a #if's false part, or if the
					 *   directive is processed even in #if false parts,
					 *   process the line, otherwise skip it
					 */
					cnt = ctx->tokcxifcnt;
					if (dirp->ok_in_if || cnt == 0
						|| ((stat = ctx->tokcxifcur) == TOKIF_IF_YES
							|| stat == TOKIF_ELSE_YES))
					{
						/* skip whitespace following the directive */
						for (p += dirp->len, len -= dirp->len ;
							 len && t_isspace(*p) ;
							 --len, ++p) ;

						/* invoke the function to process this directive */
						(*dirp->fn)(ctx, p, len);
					}

					/* there's no need to look at more directives */
					break;
				}
			}

			/* if we didn't find anything, flag the error */
			if (i == 0)
				errlog(ctx->tokcxerr, ERR_PRPDIR);

			/* ignore this line */
			continue;
		}
		else
		{
			/*
			 *   Check the #if level.  If we're in an #if, and we're to
			 *   ignore lines (because of a false condition or an #else
			 *   part for a true condition), skip this line.
			 */
			if (ctx->tokcxifcnt != 0)
			{
				switch(ctx->tokcxifcur)
				{
				case TOKIF_IF_NO:
				case TOKIF_ELSE_NO:
					/* ignore this line */
					continue;

				default:
					/* we're in a true part - keep the line */
					break;
				}
			}

			ctx->tokcxlin->linflg &= ~LINFDBG;       /* no debug record yet */
			return(FALSE);                      /* return the line we found */
		}
	}
}

/* get the next token, removing it from the input stream */
int toknext(tokcxdef *ctx)
{
	const char *p;
	tokdef *tok = &ctx->tokcxcur;
	int     len;

	/*
	 *   Check for the special case that we pushed an open paren prior to
	 *   a string containing an embedded expression.  If this is the case,
	 *   immediately return the string we previously parsed.
	 */
	if ((ctx->tokcxflg & TOKCXF_EMBED_PAREN_PRE) != 0)
	{
		/*
		 *   convert the token to a string - note that the offset
		 *   information for the string is already in the current token
		 *   structure, since we set everything up for it on the previous
		 *   call where we actually parsed the beginning of the string
		 */
		tok->toktyp = TOKTDSTRING;

		/* clear the special flag - we've now consumed the pushed string */
		ctx->tokcxflg &= ~TOKCXF_EMBED_PAREN_PRE;

		/* immediately return the string */
		return tok->toktyp;
	}

	/* set up at the current scanning position */
	p = ctx->tokcxptr;
	len = ctx->tokcxlen;

	/* scan off whitespace and comments until we find something */
	do
	{
	skipblanks:
		/* if there's nothing on this line, get the next one */
		if (len == 0)
		{
			/* if we're in a macro expansion, continue after it */
			if (ctx->tokcxmlvl)
			{
				ctx->tokcxmlvl--;
				p = ctx->tokcxmsav[ctx->tokcxmlvl];
				len = ctx->tokcxmsvl[ctx->tokcxmlvl];
			}
			else
			{
				if (tokgetlin(ctx, TRUE))
				{
					tok->toktyp = TOKTEOF;
					goto done;
				}
				p = ctx->tokcxptr;
				len = ctx->tokcxlen;
			}
		}
		while (len && t_isspace(*p)) ++p, --len;     /* scan off whitespace */

		/* check for comments, and remove if present */
		if (len >= 2 && *p == '/' && *(p+1) == '/')
			len = 0;
		else if (len >= 2 && *p == '/' && *(p+1) == '*')
		{
			while (len < 2 || *p != '*' || *(p+1) != '/')
			{
				if (len != 0)
					++p, --len;

				if (len == 0)
				{
					if (ctx->tokcxmlvl != 0)
					{
						ctx->tokcxmlvl--;
						p = ctx->tokcxmsav[ctx->tokcxmlvl];
						len = ctx->tokcxmsvl[ctx->tokcxmlvl];
					}
					else
					{
						if (tokgetlin(ctx, FALSE))
						{
							ctx->tokcxptr = p;
							tok->toktyp = TOKTEOF;
							goto done;
						}
						p = ctx->tokcxptr;
						len = ctx->tokcxlen;
					}
				}
			}
			p += 2;
			len -= 2;
			goto skipblanks;
		}
	} while (len == 0);

nexttoken:
	if (Common::isAlpha((uchar)*p) || *p == '_' || *p == '$')
	{
		int       l;
		int       hash;
		const char *q;
	char     *tq;
		toktdef  *tab;
		int       found = FALSE;
		uchar     thischar;
		tokdfdef *df;

		for (hash = 0, l = 0, tq = tok->toknam ;
			 len != 0 && TOKISSYM(*p) && l < TOKNAMMAX ;
			 (thischar = ((Common::isUpper((uchar)*p)
						   && (ctx->tokcxflg & TOKCXCASEFOLD))
						  ? Common::isLower((uchar)*p) : *p)),
			 (hash = ((hash + thischar) & (TOKHASHSIZE - 1))),
			 (*tq++ = thischar), ++p, --len, ++l) ;
		*tq = '\0';
		if (len != 0 && TOKISSYM(*p))
		{
			while (len != 0 && TOKISSYM(*p)) ++p, --len;
			errlog1(ctx->tokcxerr, ERR_TRUNC, ERRTSTR,
					errstr(ctx->tokcxerr, tok->toknam, tok->toklen));
		}
		tok->toklen = l;
		tok->tokhash = hash;

		/*
		 *   check for the special defined() preprocessor operator
		 */
		if (l == 9 && !memcmp(tok->toknam,
							  ((ctx->tokcxflg & TOKCXCASEFOLD)
							   ? "__defined" : "__DEFINED"),
							  (size_t)9)
			&& len > 2 && *p == '(' && TOKISSYM(*(p+1))
			&& !Common::isDigit((uchar)*(p+1)))
		{
			int symlen;
			char mysym[TOKNAMMAX];

			/* find the matching ')', allowing only symbolic characters */
			++p, --len;
			for (symlen = 0, q = p ; len && *p != ')' && TOKISSYM(*p) ;
				 ++p, --len, ++symlen) ;

			/* make sure we found the closing paren */
			if (!len || *p != ')')
				errsig(ctx->tokcxerr, ERR_BADISDEF);
			++p, --len;

			/* if we're folding case, convert the symbol to lower case */
			q = tok_casefold_defsym(ctx, mysym, q, symlen);

			/* check to see if it's defined */
			tok->toktyp = TOKTNUMBER;
			tok->tokval = (tok_find_define(ctx, q, symlen) != nullptr);
			goto done;
		}

		/* substitute the preprocessor #define, if any */
		if ((df = tok_find_define(ctx, tok->toknam, l)) != nullptr)
		{
			/* save the current parsing position */
			if (ctx->tokcxmlvl >= TOKMACNEST)
				errsig(ctx->tokcxerr, ERR_MACNEST);
			ctx->tokcxmsav[ctx->tokcxmlvl] = p;
			ctx->tokcxmsvl[ctx->tokcxmlvl] = len;
			ctx->tokcxmlvl++;

			/* point to the token's expansion and keep going */
			p = df->expan;
			len = df->explen;
			goto nexttoken;
		}

		/* look up in symbol table(s), if any */
		for (tab = ctx->tokcxstab ; tab ; tab = tab->toktnxt)
		{
			if ((found = (*tab->toktfsea)(tab, tok->toknam, l, hash,
										  &tok->toksym)) != 0)
				break;
		}

		if (found && tok->toksym.tokstyp == TOKSTKW)
			tok->toktyp = tok->toksym.toksval;
		else
		{
			tok->toktyp = TOKTSYMBOL;
			if (!found) tok->toksym.tokstyp = TOKSTUNK;
		}
		goto done;
	}
	else if (Common::isDigit((uchar)*p))
	{
		long acc = 0;

		/* check for octal/hex */
		if (*p == '0')
		{
			++p, --len;
			if (len && (*p == 'x' || *p == 'X'))
			{
				/* hex */
				++p, --len;
				while (len && TOKISHEX(*p))
				{
					acc = (acc << 4) + TOKHEX2INT(*p);
					++p, --len;
				}
			}
			else
			{
				/* octal */
				while (len && TOKISOCT(*p))
				{
					acc = (acc << 3) + TOKOCT2INT(*p);
					++p, --len;
				}
			}
		}
		else
		{
			/* decimal */
			while (len && Common::isDigit((uchar)*p))
			{
				acc = (acc << 1) + (acc << 3) + TOKDEC2INT(*p);
				++p, --len;
			}
		}
		tok->tokval = acc;
		tok->toktyp = TOKTNUMBER;
		goto done;
	}
	else if (*p == '"' || *p == '\'')
	{
		char  delim;                 /* closing delimiter we're looking for */
		const char *strstart;                 /* pointer to start of string */
		int   warned;

		delim = *p;
		--len;
		strstart = ++p;

		if (delim == '"' && len >= 2 && *p == '<' && *(p+1) == '<')
		{
			/* save the current parsing position */
			if (ctx->tokcxmlvl >= TOKMACNEST)
				errsig(ctx->tokcxerr, ERR_MACNEST);
			ctx->tokcxmsav[ctx->tokcxmlvl] = p + 2;
			ctx->tokcxmsvl[ctx->tokcxmlvl] = len - 2;
			ctx->tokcxmlvl++;

			/*
			 *   read from the special "<<" expansion string - use the
			 *   version for a "<<" at the very beginning of the string
			 */
			p = tokmac1s;
			len = strlen(p);
			ctx->tokcxflg |= TOKCXFINMAC;
			goto nexttoken;
		}
		tok->toktyp = (delim == '"' ? TOKTDSTRING : TOKTSSTRING);

		tok->tokofs = (*ctx->tokcxsst)(ctx->tokcxscx);  /* start the string */
		for (warned = FALSE ;; )
		{
			if (len >= 2 && *p == '\\')
			{
				if (*(p+1) == '"' || *(p+1) == '\'')
				{
					(*ctx->tokcxsad)(ctx->tokcxscx, strstart,
									 (ushort)(p - strstart));
					strstart = p + 1;
				}
				p += 2;
				len -= 2;
			}
			else if (len == 0 || *p == delim ||
					 (delim == '"' && len >= 2 && *p == '<' && *(p+1) == '<'
					  && !(ctx->tokcxflg & TOKCXFINMAC)))
			{
				(*ctx->tokcxsad)(ctx->tokcxscx, strstart,
								 (ushort)(p - strstart));
				if (len == 0)
				{
					if (ctx->tokcxmlvl)
					{
						ctx->tokcxmlvl--;
						p = ctx->tokcxmsav[ctx->tokcxmlvl];
						len = ctx->tokcxmsvl[ctx->tokcxmlvl];
					}
					else
						(*ctx->tokcxsad)(ctx->tokcxscx, " ", (ushort)1);

					while (len == 0)
					{
						if (tokgetlin(ctx, FALSE))
							errsig(ctx->tokcxerr, ERR_STREOF);
						p = ctx->tokcxptr;
						len = ctx->tokcxlen;

						/* warn if it looks like the end of an object */
						if (!warned && len && (*p == ';' || *p == '}'))
						{
							errlog(ctx->tokcxerr, ERR_STREND);
							warned = TRUE;     /* warn only once per string */
						}

						/* scan past whitespace at start of line */
						while (len && t_isspace(*p)) ++p, --len;
					}
					strstart = p;
				}
				else break;
			}
			else
				++p, --len;
		}

		/* end the string */
		(*ctx->tokcxsend)(ctx->tokcxscx);

		/* check to see how it ended */
		if (len != 0 && *p == delim)
		{
			/*
			 *   We ended with the matching delimiter.  Move past the
			 *   closing delimiter.
			 */
			++p;
			--len;

			/*
			 *   If we have a pending close paren we need to put in
			 *   because of an embedded expression that occurred earlier
			 *   in the string, parse the macro to provide the paren.
			 */
			if ((ctx->tokcxflg & TOKCXF_EMBED_PAREN_AFT) != 0
				&& !(ctx->tokcxflg & TOKCXFINMAC))
			{
				/* clear the flag */
				ctx->tokcxflg &= ~TOKCXF_EMBED_PAREN_AFT;

				/* push the current parsing position */
				if (ctx->tokcxmlvl >= TOKMACNEST)
					errsig(ctx->tokcxerr, ERR_MACNEST);
				ctx->tokcxmsav[ctx->tokcxmlvl] = p;
				ctx->tokcxmsvl[ctx->tokcxmlvl] = len;
				ctx->tokcxmlvl++;

				/* parse the macro */
				p = tokmac4;
				len = strlen(p);
			}
		}
		else if (len != 0 && *p == '<')
		{
			/* save the current parsing position */
			if (ctx->tokcxmlvl >= TOKMACNEST)
				errsig(ctx->tokcxerr, ERR_MACNEST);
			ctx->tokcxmsav[ctx->tokcxmlvl] = p + 2;
			ctx->tokcxmsvl[ctx->tokcxmlvl] = len - 2;
			ctx->tokcxmlvl++;

			/* read from the "<<" expansion */
			p = tokmac1;
			len = strlen(p);
			ctx->tokcxflg |= TOKCXFINMAC;

			/*
			 *   Set the special push-a-paren flag: we'll return an open
			 *   paren now, so that we have an open paren before the
			 *   string, and then on the next call to toknext() we'll
			 *   immediately return the string we've already parsed here.
			 *   This will ensure that everything in the string is
			 *   properly grouped together as a single indivisible
			 *   expression.
			 *
			 *   Note that we only need to do this for the first embedded
			 *   expression in a string.  Once we have a close paren
			 *   pending, we don't need more open parens.
			 */
			if (!(ctx->tokcxflg & TOKCXF_EMBED_PAREN_AFT))
			{
				ctx->tokcxflg |= TOKCXF_EMBED_PAREN_PRE;
				tok->toktyp = TOKTLPAR;
			}
		}
		goto done;
	}
	else if (len >= 2 && *p == '>' && *(p+1) == '>'
			 && (ctx->tokcxflg & TOKCXFINMAC) != 0)
	{
		/* skip the ">>" */
		ctx->tokcxflg &= ~TOKCXFINMAC;
		p += 2;
		len -= 2;

		/* save the current parsing position */
		if (ctx->tokcxmlvl >= TOKMACNEST)
			errsig(ctx->tokcxerr, ERR_MACNEST);
		ctx->tokcxmsav[ctx->tokcxmlvl] = p;
		ctx->tokcxmsvl[ctx->tokcxmlvl] = len;
		ctx->tokcxmlvl++;

		if (*p == '"')
		{
			++(ctx->tokcxmsav[ctx->tokcxmlvl - 1]);
			--(ctx->tokcxmsvl[ctx->tokcxmlvl - 1]);
			p = tokmac3;

			/*
			 *   we won't need an extra closing paren now, since tokmac3
			 *   provides it
			 */
			ctx->tokcxflg &= ~TOKCXF_EMBED_PAREN_AFT;
		}
		else
		{
			/*
			 *   The string is continuing.  Set a flag to note that we
			 *   need to provide a close paren after the end of the
			 *   string, and parse the glue (tokmac2) that goes between
			 *   the expression and the resumption of the string.
			 */
			ctx->tokcxflg |= TOKCXF_EMBED_PAREN_AFT;
			p = tokmac2;
		}

		len = strlen(p);
		goto nexttoken;
	}
	else
	{
		tokscdef *sc;

		for (sc = ctx->tokcxsc[ctx->tokcxinx[(uchar)*p]] ; sc ;
			 sc = sc->tokscnxt)
		{
			if (toksceq(sc->tokscstr, p, sc->toksclen, len))
			{
				tok->toktyp = sc->toksctyp;
				p += sc->toksclen;
				len -= sc->toksclen;
				goto done;
			}
		}
		errsig(ctx->tokcxerr, ERR_INVTOK);
	}

done:
	ctx->tokcxptr = p;
	ctx->tokcxlen = len;
	return(tok->toktyp);
}

/* initialize a linear symbol table */
void toktlini(errcxdef *errctx, toktldef *toktab, uchar *mem, uint siz)
{
	CLRSTRUCT(*toktab);

	/* initialize superclass data */
	toktab->toktlsc.toktfadd = toktladd;           /* set add-symbol method */
	toktab->toktlsc.toktfsea = toktlsea;         /* set search-table method */
	toktab->toktlsc.toktfeach = toktleach;             /* set 'each' method */
	toktab->toktlsc.toktfset = toktlset;             /* set 'update' method */
	toktab->toktlsc.tokterr = errctx;         /* set error handling context */

	/* initialize class data */
	toktab->toktlptr = mem;
	toktab->toktlnxt = mem;
	toktab->toktlsiz = siz;
}

/* add a symbol to a linear symbol table */
void toktladd(toktdef *toktab1, char *name, int namel,
			  int typ, int val, int hash)
{
	uint      siz = sizeof(toks1def) + namel;
	toksdef  *sym;
	toktldef *toktab = (toktldef *)toktab1;

	VARUSED(hash);

	if (toktab->toktlsiz < siz)
		errsig(toktab->toktlsc.tokterr, ERR_NOLCLSY);

	sym = (toksdef *)toktab->toktlnxt;
	siz = osrndsz(siz);
	toktab->toktlnxt += siz;
	if (siz > toktab->toktlsiz) toktab->toktlsiz = 0;
	else toktab->toktlsiz -= siz;

	/* set up symbol */
	sym->toksval = val;
	sym->tokslen = namel;
	sym->tokstyp = typ;
	sym->toksfr  = 0;
	memcpy(sym->toksnam, name, (size_t)(namel + 1));

	/* indicate there's one more symbol in the table */
	++(toktab->toktlcnt);
}

/* delete all symbols from a linear symbol table */
void toktldel(toktldef *tab)
{
	tab->toktlcnt = 0;
	tab->toktlsiz += tab->toktlnxt - tab->toktlptr;
	tab->toktlnxt = tab->toktlptr;
}

/* call a function for every symbol in a linear symbol table */
void toktleach(toktdef *tab1,
			   void (*cb)(void *ctx, toksdef *sym), void *ctx)
{
	toksdef  *p;
	uint      cnt;
	toktldef *tab = (toktldef *)tab1;

	for (p = (toksdef *)tab->toktlptr, cnt = tab->toktlcnt ; cnt ; --cnt )
	{
		(*cb)(ctx, p);
		p = (toksdef *)(((uchar *)p)
						+ osrndsz(p->tokslen + sizeof(toks1def)));
	}
}

/* search a linear symbol table */
int toktlsea(toktdef *tab1, char *name, int namel, int hash, toksdef *ret)
{
	toksdef  *p;
	uint      cnt;
	toktldef *tab = (toktldef *)tab1;

	VARUSED(hash);

	for (p = (toksdef *)tab->toktlptr, cnt = tab->toktlcnt ; cnt ; --cnt )
	{
		if (p->tokslen == namel && !memcmp(p->toksnam, name, (size_t)namel))
		{
			memcpy(ret, p, (size_t)(sizeof(toks1def) + namel));
			return(TRUE);
		}

		p = (toksdef *)(((uchar *)p)
						+ osrndsz(p->tokslen + sizeof(toks1def)));
	}

	/* nothing found - indicate by returning FALSE */
	return(FALSE);
}

/* update a symbol in a linear symbol table */
void toktlset(toktdef *tab1, toksdef *newsym)
{
	toksdef  *p;
	uint      cnt;
	toktldef *tab = (toktldef *)tab1;

	for (p = (toksdef *)tab->toktlptr, cnt = tab->toktlcnt ; cnt ; --cnt )
	{
		if (p->tokslen == newsym->tokslen
			&& !memcmp(p->toksnam, newsym->toksnam, (size_t)newsym->tokslen))
		{
			p->toksval = newsym->toksval;
			p->tokstyp = newsym->tokstyp;
			return;
		}

		p = (toksdef *)(((uchar *)p)
						+ osrndsz(p->tokslen + sizeof(toks1def)));
	}
}

tokcxdef *tokcxini(errcxdef *errctx, mcmcxdef *mcmctx, tokldef *sctab)
{
	int       i;
	int       cnt;
	tokldef  *p;
	uchar     c;
	uchar     index[256];
	tokcxdef *ret;
	tokscdef *sc;
	ushort    siz;

	/* set up index table: finds tokcxsc entry from character value */
	memset(index, 0, (size_t)sizeof(index));
	for (i = cnt = 0, p = sctab ; (c = p->toklstr[0]) != 0 ; ++cnt, ++p)
		if (!index[c]) index[c] = ++i;

	/* allocate memory for table plus the tokscdef's */
	siz = sizeof(tokcxdef) + (i * sizeof(tokscdef *))
		  + ((cnt + 1) * sizeof(tokscdef));
	ret = (tokcxdef *)mchalo(errctx, siz, "tokcxini");
	memset(ret, 0, (size_t)siz);

	/* copy the index, set up fixed part */
	memcpy(ret->tokcxinx, index, sizeof(ret->tokcxinx));
	ret->tokcxerr = errctx;
	ret->tokcxmem = mcmctx;

	/* start out without an #if */
	ret->tokcxifcur = TOKIF_IF_YES;

	/* force the first toknext() to read a line */
	ret->tokcxptr = "\000";

	/* figure where the tokscdef's go (right after sc pointer array) */
	sc = (tokscdef *)&ret->tokcxsc[i+1];

	/* set up the individual tokscdef entries, and link into lists */
	for (p = sctab ; (c = p->toklstr[0]) != 0 ; ++p, ++sc)
	{
		size_t len;

		sc->toksctyp = p->tokltyp;
		len = sc->toksclen = strlen(p->toklstr);
		memcpy(sc->tokscstr, p->toklstr, len);
		sc->tokscnxt = ret->tokcxsc[index[c]];
		ret->tokcxsc[index[c]] = sc;
	}

	return(ret);
}

/* add an include path to a tokdef */
void tokaddinc(tokcxdef *ctx, char *path, int pathlen)
{
	tokpdef *newpath;
	tokpdef *last;

	/* find the tail of the include path list, if any */
	for (last = ctx->tokcxinc ; last && last->tokpnxt ;
		 last = last->tokpnxt) ;

	/* allocate storage for and set up a new path structure */
	newpath = (tokpdef *)mchalo(ctx->tokcxerr,
								(sizeof(tokpdef) + pathlen - 1),
								"tokaddinc");
	newpath->tokplen = pathlen;
	newpath->tokpnxt = (tokpdef *)nullptr;
	memcpy(newpath->tokpdir, path, (size_t)pathlen);

	/* link in at end of list (if no list yet, newpath becomes first entry) */
	if (last)
		last->tokpnxt = newpath;
	else
		ctx->tokcxinc = newpath;
}

} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk
