/*
** fmt_compile.c -- "compile" format strings for fmt_scan
**
** This code is Copyright (c) 2002, by the authors of nmh.  See the
** COPYRIGHT file in the root directory of the nmh distribution for
** complete copyright information.
**
** This code compiles the format strings (documented in mh-format(5)) into
** an internal form to be later processed by fmt_scan.c.
**
** What happens here is that the format strings are parsed and an array
** of struct format structures are returned. Each format structure is
** a single operation interpreted by the the routines in fmt_scan.c.
**
** There is a NOT a one-to-one correspondence between format strings and
** format instructions; some functions have side effects that can result
** in multiple instructions being generated. The exact list of instructions
** generated by a format string can be seem with the nmh fmtdump utility.
**
** A list of format instructions can be found in fmt_compile.h.
**
** If you wish to add a new function, you will need to do the following
** things:
**
** - Add a new instruction to the list of instructions in fmt_compile.h.
**   Note that test instructions (starting with FT_IF_S_NULL) have special
**   handling, so if you are NOT writing a test function then you need
**   to insert it into the list before that _and_ bump all of the
**   following instruction numbers.
**
** - Add the function name to the functable[] array below, and write any
**   special code that your function may require in terms of parsing
**   (it very well may not need anything).
**
** - Add the code in fmt_scan.c to handle your new function.
**
** - Document the new function in the mh-format(5) man page.
**
*/

#include <h/mh.h>
#include <h/utils.h>
#include <h/addrsbr.h>
#include <h/tws.h>
#include <h/fmt_scan.h>
#include <h/fmt_compile.h>
#include <ctype.h>
#include <sysexits.h>

#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#include <time.h>

/*
** hash table for deciding if a component is "interesting"
*/
struct comp *wantcomp[128];

static struct format *formatvec;  /* array to hold formats */
static struct format *next_fp;    /* next free format slot */
static struct format *fp;         /* current format slot   */
static struct comp *cm;           /* most recent comp ref  */
static struct ftable *ftbl;       /* most recent func ref  */
static int ncomp;
static int infunction;            /* function nesting cnt  */

extern struct mailname fmt_mnull;

/* ftable->type (argument type) */
#define TF_COMP    0  /* component expected                 */
#define TF_NUM     1  /* number expected                    */
#define TF_STR     2  /* string expected                    */
#define TF_EXPR    3  /* component or func. expected        */
#define TF_NONE    4  /* no argument                        */
#define TF_MYBOX   5  /* special - get current user's mbox  */
#define TF_NOW     6  /* special - get current unix time    */
#define TF_EXPR_SV 7  /* like expr but save current str reg */
#define TF_NOP     8  /* like expr but no result            */

/* ftable->flags */
/*
** NB that TFL_PUTS is also used to decide whether the test
** in a "%<(function)..." should be a string or numeric one.
*/
#define TFL_PUTS   1  /* implicit putstr if top level */
#define TFL_PUTN   2  /* implicit putnum if top level */

/*
** The functable array maps between the text names of format functions and
** the format instructions interpreted by the engine in fmt_scan.c.
**
** The elements of this structure are as follows:
**
** name:    The name of the function as seen in the format string. This is
**          what maps a particular function name into a format instruction.
** type:    The type of argument this function expects. Those types are
**          listed above (with the TF_ prefix). This affects what gets
**          placed in the format instruction (the f_un union).
** f_type:  The instruction corresponding to this function (from the list
**          in fmt_compile.h).
** extra:   Used by some functions to provide extra data to the compiler.
**          Uses include:
**          - Providing an alternate instruction to combine a load
**            and test operation (see do_if()).
**          - Passed in f_value in the format instruction to provide
**            extra information for the engine (see FT_LV_DAT handling
**            in fmt_scan.c).
**          - Provide a hint as to preprocessing that is required for
**            this instruction (see do_name()).
** flags:   See the definitions for TFL_PUTS & TFL_PUTN above.
*/
struct ftable {
	char *name;  /* function name */
	char type;   /* argument type */
	char f_type; /* fmt type */
	char extra;  /* arg. type dependent extra info */
	char flags;
};

static struct ftable functable[] = {
	{ "nonzero",    TF_EXPR,  FT_V_NE,  FT_IF_V_NE,  0 },
	{ "zero",       TF_EXPR,  FT_V_EQ,  FT_IF_V_EQ,  0 },
	{ "eq",         TF_NUM, FT_V_EQ, FT_IF_V_EQ, 0 },
	{ "ne",         TF_NUM, FT_V_NE, FT_IF_V_NE, 0 },
	{ "gt",         TF_NUM, FT_V_GT, FT_IF_V_GT, 0 },
	{ "null",       TF_EXPR, FT_S_NULL, FT_IF_S_NULL, 0 },
	{ "nonnull",    TF_EXPR, FT_S_NONNULL, FT_IF_S, 0 },
	{ "match",      TF_STR, FT_V_MATCH, FT_IF_MATCH, 0 },
	{ "amatch",     TF_STR, FT_V_AMATCH, FT_IF_AMATCH, 0 },

	{ "putstr",     TF_EXPR, FT_STR, 0, 0 },
	{ "putstrf",    TF_EXPR, FT_STRF, 0, 0 },
	{ "putnum",     TF_EXPR, FT_NUM, 0, 0 },
	{ "putnumf",    TF_EXPR, FT_NUMF, 0, 0 },
	{ "putaddr",    TF_STR, FT_PUTADDR, 0, 0 },
	{ "void",       TF_NOP, 0, 0, 0 },

	{ "comp",       TF_COMP, FT_LS_COMP, 0, TFL_PUTS },
	{ "lit",        TF_STR, FT_LS_LIT, 0, TFL_PUTS },
	{ "getenv",     TF_STR, FT_LS_GETENV, 0, TFL_PUTS },
	{ "profile",    TF_STR, FT_LS_CFIND, 0, TFL_PUTS },
	{ "decodecomp", TF_COMP, FT_LS_DECODECOMP,  0, TFL_PUTS },
	{ "decode",     TF_EXPR, FT_LS_DECODE, 0, TFL_PUTS },
	{ "trim",       TF_EXPR, FT_LS_TRIM, 0, 0 },
	{ "compval",    TF_COMP, FT_LV_COMP, 0, TFL_PUTN },
	{ "compflag",   TF_COMP, FT_LV_COMPFLAG, 0, TFL_PUTN },
	{ "num",        TF_NUM, FT_LV_LIT, 0, TFL_PUTN },
	{ "msg",        TF_NONE, FT_LV_DAT, 0, TFL_PUTN },
	{ "cur",        TF_NONE, FT_LV_DAT, 1, TFL_PUTN },
	{ "size",       TF_NONE, FT_LV_DAT, 2, TFL_PUTN },
	{ "width",      TF_NONE, FT_LV_DAT, 3, TFL_PUTN },
	{ "unseen",     TF_NONE, FT_LV_DAT, 4, TFL_PUTN },
	{ "dat",        TF_NUM, FT_LV_DAT, 0, TFL_PUTN },
	{ "strlen",     TF_NONE, FT_LV_STRLEN, 0, TFL_PUTN },
	{ "me",         TF_MYBOX, FT_LS_LIT, 0, TFL_PUTS },
	{ "plus",       TF_NUM, FT_LV_PLUS_L, 0, TFL_PUTN },
	{ "minus",      TF_NUM, FT_LV_MINUS_L, 0, TFL_PUTN },
	{ "divide",     TF_NUM, FT_LV_DIVIDE_L, 0, TFL_PUTN },
	{ "modulo",     TF_NUM, FT_LV_MODULO_L, 0, TFL_PUTN },
	{ "charleft",   TF_NONE, FT_LV_CHAR_LEFT,  0, TFL_PUTN },
	{ "timenow",    TF_NOW, FT_LV_LIT, 0, TFL_PUTN },

	{ "month",      TF_COMP, FT_LS_MONTH, FT_PARSEDATE, TFL_PUTS },
	{ "lmonth",     TF_COMP, FT_LS_LMONTH, FT_PARSEDATE, TFL_PUTS },
	{ "tzone",      TF_COMP, FT_LS_ZONE, FT_PARSEDATE, TFL_PUTS },
	{ "day",        TF_COMP, FT_LS_DAY, FT_PARSEDATE, TFL_PUTS },
	{ "weekday",    TF_COMP, FT_LS_WEEKDAY, FT_PARSEDATE, TFL_PUTS },
	{ "tws",        TF_COMP, FT_LS_822DATE, FT_PARSEDATE, TFL_PUTS },
	{ "sec",        TF_COMP, FT_LV_SEC, FT_PARSEDATE, TFL_PUTN },
	{ "min",        TF_COMP, FT_LV_MIN, FT_PARSEDATE, TFL_PUTN },
	{ "hour",       TF_COMP, FT_LV_HOUR, FT_PARSEDATE, TFL_PUTN },
	{ "mday",       TF_COMP, FT_LV_MDAY, FT_PARSEDATE, TFL_PUTN },
	{ "mon",        TF_COMP, FT_LV_MON, FT_PARSEDATE, TFL_PUTN },
	{ "year",       TF_COMP, FT_LV_YEAR, FT_PARSEDATE, TFL_PUTN },
	{ "yday",       TF_COMP, FT_LV_YDAY, FT_PARSEDATE, TFL_PUTN },
	{ "wday",       TF_COMP, FT_LV_WDAY, FT_PARSEDATE, TFL_PUTN },
	{ "zone",       TF_COMP, FT_LV_ZONE, FT_PARSEDATE, TFL_PUTN },
	{ "clock",      TF_COMP, FT_LV_CLOCK, FT_PARSEDATE, TFL_PUTN },
	{ "rclock",     TF_COMP, FT_LV_RCLOCK, FT_PARSEDATE, TFL_PUTN },
	{ "sday",       TF_COMP, FT_LV_DAYF, FT_PARSEDATE, TFL_PUTN },
	{ "szone",      TF_COMP, FT_LV_ZONEF, FT_PARSEDATE, TFL_PUTN },
	{ "dst",        TF_COMP, FT_LV_DST, FT_PARSEDATE, TFL_PUTN },
	{ "pretty",     TF_COMP, FT_LS_PRETTY, FT_PARSEDATE, TFL_PUTS },
	{ "nodate",     TF_COMP, FT_LV_COMPFLAG, FT_PARSEDATE, TFL_PUTN },
	{ "date2local", TF_COMP, FT_LOCALDATE, FT_PARSEDATE, 0 },
	{ "date2gmt",   TF_COMP, FT_GMTDATE, FT_PARSEDATE, 0 },

	{ "pers",       TF_COMP, FT_LS_PERS, FT_PARSEADDR, TFL_PUTS },
	{ "mbox",       TF_COMP, FT_LS_MBOX, FT_PARSEADDR, TFL_PUTS },
	{ "host",       TF_COMP, FT_LS_HOST, FT_PARSEADDR, TFL_PUTS },
	{ "path",       TF_COMP, FT_LS_PATH, FT_PARSEADDR, TFL_PUTS },
	{ "gname",      TF_COMP, FT_LS_GNAME, FT_PARSEADDR, TFL_PUTS },
	{ "note",       TF_COMP, FT_LS_NOTE, FT_PARSEADDR, TFL_PUTS },
	{ "addr",       TF_COMP, FT_LS_ADDR, FT_PARSEADDR, TFL_PUTS },
	{ "proper",     TF_COMP, FT_LS_822ADDR, FT_PARSEADDR, TFL_PUTS },
	{ "type",       TF_COMP, FT_LV_HOSTTYPE, FT_PARSEADDR, TFL_PUTN },
	{ "ingrp",      TF_COMP, FT_LV_INGRPF, FT_PARSEADDR, TFL_PUTN },
	{ "nohost",     TF_COMP, FT_LV_NOHOSTF, FT_PARSEADDR, TFL_PUTN },
	{ "formataddr", TF_EXPR_SV, FT_FORMATADDR, FT_FORMATADDR, 0 },
	{ "friendly",   TF_COMP,    FT_LS_FRIENDLY, FT_PARSEADDR, TFL_PUTS },

	{ "mymbox",     TF_COMP,    FT_LV_COMPFLAG, FT_MYMBOX, TFL_PUTN },

	{ "unquote",    TF_EXPR,    FT_LS_UNQUOTE, 0, TFL_PUTS},
	{ "unmailto",   TF_EXPR,    FT_LS_UNMAILTO, 0, TFL_PUTS},

	{ NULL,         0,          0, 0, 0 }
};

/* Add new component to the hash table */
#define NEWCOMP(cm,name) do { \
		cm = (mh_xcalloc(1, sizeof (struct comp)));\
		cm->c_name = name;\
		ncomp++;\
		i = CHASH(name);\
		cm->c_next = wantcomp[i];\
		wantcomp[i] = cm; \
	} while (0)

#define NEWFMT (next_fp++)
#define NEW(type,fill,wid) do {\
		fp=NEWFMT; fp->f_type=(type); fp->f_fill=(fill); fp->f_width=(wid); \
	} while (0)

/* Add (possibly new) component to the hash table */
#define ADDC(name) do { \
		FINDCOMP(cm, name);\
		if (!cm) {\
			NEWCOMP(cm,name);\
		}\
		fp->f_comp = cm; \
	} while (0)

#define LV(type, value)  do { NEW(type,0,0); fp->f_value = (value); } while (0)
#define LS(type, str)  do { NEW(type,0,0); fp->f_text = (str); } while (0)

#define PUTCOMP(comp)  do { NEW(FT_COMP,0,0); ADDC(comp); } while (0)
#define PUTLIT(str)  do { NEW(FT_LIT,0,0); fp->f_text = (str); } while (0)
#define PUTC(c)  do { NEW(FT_CHAR,0,0); fp->f_char = (c); } while (0)

static char *format_string;
static unsigned char *usr_fstring;  /* for CERROR */

#define CERROR(str) compile_error(str, cp)

/*
** static prototypes
*/
static struct ftable *lookup(char *);
static void compile_error(char *, char *);
static char *compile(char *);
static char *do_spec(char *);
static char *do_name(char *, int);
static char *do_func(char *);
static char *do_expr(char *, int);
static char *do_loop(char *);
static char *do_if(char *);


/*
** Lookup a function name in the functable
*/
static struct ftable *
lookup(char *name)
{
	struct ftable *t = functable;
	char *nm;
	char c = *name;

	while ((nm = t->name)) {
		if (*nm == c && strcmp(nm, name) == 0)
			return (ftbl = t);

		t++;
	}
	return (struct ftable *) 0;
}


static void
compile_error(char *str, char *cp)
{
	int i, errpos, errctx;

	errpos = cp - format_string;
	errctx = errpos > 20 ? 20 : errpos;
	usr_fstring[errpos] = '\0';

	for (i = errpos-errctx; i < errpos; i++) {
		if (iscntrl(usr_fstring[i]))
			usr_fstring[i] = '_';
	}

	advise(NULL, "\"%s\": format compile error - %s",
			&usr_fstring[errpos-errctx], str);
	adios(EX_SOFTWARE, NULL, "%*s", errctx+1, "^");
}

/*
** Compile format string "fstring" into format list "fmt".
** Return the number of header components found in the format
** string.
*/
int
fmt_compile(char *fstring, struct format **fmt)
{
	char *cp;
	size_t i;

	if (format_string)
		mh_free0(&format_string);
	format_string = mh_xstrdup(fstring);
	usr_fstring = fstring;

	/* init the component hash table. */
	for (i = 0; i < sizeof(wantcomp)/sizeof(wantcomp[0]); i++)
		wantcomp[i] = 0;

	memset((char *) &fmt_mnull, 0, sizeof(fmt_mnull));

	/*
	** it takes at least 4 char to generate one format so we
	** allocate a worst-case format array using 1/4 the length
	** of the format string.  We actually need twice this much
	** to handle both pre-processing (e.g., address parsing) and
	** normal processing.
	*/
	i = strlen(fstring)/2 + 1;
	if (i == 1)
		i++;
	next_fp = formatvec = mh_xcalloc(i, sizeof(struct format));
	if (next_fp == NULL)
		adios(EX_OSERR, NULL, "unable to allocate format storage");

	ncomp = 0;
	infunction = 0;

	cp = compile(format_string);
	if (*cp) {
		CERROR("extra '%>', '%|' or '%?'");
	}
	LV(FT_DONE, 0);  /* really done */
	*fmt = formatvec;

	return (ncomp);
}

static char *
compile(char *sp)
{
	char *cp = sp;
	int  c;

	for (;;) {
		sp = cp;
		while ((c = *cp) && c != '%')
			cp++;
		*cp = 0;
		switch (cp-sp) {
		case 0:
			break;
		case 1:
			PUTC(*sp);
			break;
		default:
			PUTLIT(sp);
			break;
		}
		if (c == 0)
			return (cp);

		switch (c = *++cp) {
		case '%':
			PUTC(*cp);
			cp++;
			break;

		case '|':
		case '>':
		case '?':
		case ']':
			return (cp);

		case '<':
			cp = do_if(++cp);
			break;

		case '[':  /* ] */
			cp = do_loop(++cp);
			break;

		case ';':  /* comment line */
			cp++;
			while ((c = *cp++) && c != '\n')
				continue;
			break;

		default:
			cp = do_spec(cp);
			break;
		}
	}
}


/*
** Process functions & components (handle field width here as well
*/
static char *
do_spec(char *sp)
{
	char *cp = sp;
	int c;
	int ljust = 0;
	int wid = 0;
	char fill = ' ';

	c = *cp++;
	if (c == '-') {
		ljust++;
		c = *cp++;
	}
	if (c == '0') {
		fill = c;
		c = *cp++;
	}
	while (isdigit(c)) {
		wid = wid*10 + (c - '0');
		c = *cp++;
	}
	if (c == '{') {
		cp = do_name(cp, 0);
		if (! infunction)
			fp->f_type = wid? FT_COMPF : FT_COMP;
	} else if (c == '(') {
		cp = do_func(cp);
		if (! infunction) {
			if (ftbl->flags & TFL_PUTS) {
				LV( wid? FT_STRF : FT_STR, ftbl->extra);
			} else if (ftbl->flags & TFL_PUTN) {
				LV( wid? FT_NUMF : FT_NUM, ftbl->extra);
			}
		}
	} else {
		CERROR("component or function name expected");
	}
	if (ljust)
		wid = -wid;
	fp->f_width = wid;
	fp->f_fill = fill;

	return (cp);
}


/*
** Process a component name. Normally this involves generating an FT_COMP
** instruction for the specified component. If preprocess is set, then we
** do some extra processing.
*/
static char *
do_name(char *sp, int preprocess)
{
	char *cp = sp;
	int c;
	int i;
	static int primed = 0;

	while (isalnum(c = *cp++) || c == '-' || c == '_')
		;
	if (c != '}') {
		CERROR("'}' expected");
	}
	cp[-1] = '\0';
	PUTCOMP(sp);
	switch (preprocess) {

	case FT_PARSEDATE:
		if (cm->c_type & CT_ADDR) {
			CERROR("component used as both date and address");
		}
		cm->c_tws = mh_xcalloc(1, sizeof(*cm->c_tws));
		fp->f_type = preprocess;
		PUTCOMP(sp);
		cm->c_type |= CT_DATE;
		break;

	case FT_MYMBOX:
		if (!primed) {
			ismymbox((struct mailname *) 0);
			primed++;
		}
		/* fall through */
	case FT_PARSEADDR:
		if (cm->c_type & CT_DATE) {
			CERROR("component used as both date and address");
		}
		cm->c_mn = &fmt_mnull;
		fp->f_type = preprocess;
		PUTCOMP(sp);
		cm->c_type |= CT_ADDR;
		break;

	case FT_FORMATADDR:
		if (cm->c_type & CT_DATE) {
			CERROR("component used as both date and address");
		}
		cm->c_type |= CT_ADDR;
		break;
	}
	return (cp);
}


/*
** Generate one or more instructions corresponding to the named function.
** The different type of function arguments are handled here.
*/
static char *
do_func(char *sp)
{
	char *cp = sp;
	int c;
	struct ftable *t;
	int n;
	int mflag;  /* minus sign in NUM */

	infunction++;

	while (isalnum(c = *cp++))
		;
	if (c != '(' && c != '{' && c != ' ' && c != ')') {
		CERROR("'(', '{', ' ' or ')' expected");
	}
	cp[-1] = '\0';
	if ((t = lookup(sp)) == 0) {
		CERROR("unknown function");
	}
	if (isspace(c))
		c = *cp++;

	switch (t->type) {

	case TF_COMP:
		if (c != '{') {
			CERROR("component name expected");
		}
		cp = do_name(cp, t->extra);
		fp->f_type = t->f_type;
		c = *cp++;
		break;

	case TF_NUM:
		if ((mflag = (c == '-')))
			c = *cp++;
		n = 0;
		while (isdigit(c)) {
			n = n*10 + (c - '0');
			c = *cp++;
		}
		if (mflag)
			n = (-n);
		LV(t->f_type,n);
		break;

	case TF_STR:
		sp = cp - 1;
		while (c && c != ')')
			c = *cp++;
		cp[-1] = '\0';
		LS(t->f_type,sp);
		break;

	case TF_NONE:
		LV(t->f_type,t->extra);
		break;

	case TF_MYBOX:
		LS(t->f_type, getusername());
		break;

	case TF_NOW:
		LV(t->f_type, time((time_t *) 0));
		break;

	case TF_EXPR_SV:
		LV(FT_SAVESTR, 0);
		/* fall through */
	case TF_EXPR:
		*--cp = c;
		cp = do_expr(cp, t->extra);
		LV(t->f_type, 0);
		c = *cp++;
		ftbl = t;
		break;

	case TF_NOP:
		*--cp = c;
		cp = do_expr(cp, t->extra);
		c = *cp++;
		ftbl = t;
		break;
	}
	if (c != ')') {
		CERROR("')' expected");
	}
	--infunction;
	return (cp);
}


/*
** Handle an expression as an argument. Basically we call one of do_name(),
** do_func(), or do_if()
*/
static char *
do_expr(char *sp, int preprocess)
{
	char *cp = sp;
	int  c;

	if ((c = *cp++) == '{') {
		cp = do_name(cp, preprocess);
		fp->f_type = FT_LS_COMP;
	} else if (c == '(') {
		cp = do_func(cp);
	} else if (c == ')') {
		return (--cp);
	} else if (c == '%' && *cp == '<') {
		cp = do_if(cp+1);
	} else {
		CERROR("'(', '{', '%<' or ')' expected");
	}
	return (cp);
}


/*
** I am guessing this was for some kind of loop statement, which would have
** looked like %[ .... %]. It looks like the way this would have worked
** is that the format engine would have seen that FT_DONE had a 1 in the
** f_un.f_un_value and then decided whether or not to continue the loop.
** There is no support for this in the format engine, so right now if
** you try using it you will reach the FT_DONE and simply stop. I'm leaving
** this here in case someone wants to continue the work.
*/
static char *
do_loop(char *sp)
{
	char *cp = sp;
	struct format *floop;

	floop = next_fp;
	cp = compile(cp);
	if (*cp++ != ']')
		CERROR("']' expected");

	LV(FT_DONE, 1);  /* not yet done */
	LV(FT_GOTO, 0);
	fp->f_skip = floop - fp;  /* skip backwards */

	return cp;
}


/*
** Handle an if-elsif-endif statement. Note here that the branching
** is handled by the f_skip member of the struct format (which is really
** just f_width overloaded). This number controls how far to move forward
** (or back) in the format instruction array.
*/
static char *
do_if(char *sp)
{
	char *cp = sp;
	struct format *fexpr, *fif = (struct format *)NULL;
	int c = '<';

	for (;;) {
		if (c == '<') {  /* doing an IF */
			if ((c = *cp++) == '{') /*}*/{
				cp = do_name(cp, 0);
				fp->f_type = FT_LS_COMP;
				LV(FT_IF_S, 0);
			} else if (c == '(') {
				cp = do_func(cp);
				/* see if we can merge the load and the "if" */
				if (ftbl->f_type >= IF_FUNCS)
					fp->f_type = ftbl->extra;
				else {
					/*
					** Put out a string test or a value
					** test depending on what this
					** function 's return type is.
					*/
					if (ftbl->flags & TFL_PUTS) {
						LV(FT_IF_S, 0);
					} else {
						LV(FT_IF_V_NE, 0);
					}
				}
			} else {
				CERROR("'(' or '{' expected");  /*}*/
			}
		}

		fexpr = fp;  /* loc of [ELS]IF */
		cp = compile(cp);  /* compile IF TRUE stmts */
		if (fif)
			fif->f_skip = next_fp - fif;

		if ((c = *cp++) == '|') {  /* the last ELSE */
			LV(FT_GOTO, 0);
			fif = fp;  /* loc of GOTO */
			fexpr->f_skip = next_fp - fexpr;

			fexpr = (struct format *)NULL;/* no extra ENDIF */

			cp = compile(cp);  /* compile ELSE stmts */
			fif->f_skip = next_fp - fif;
			c = *cp++;
		} else if (c == '?') {  /* another ELSIF */
			LV(FT_GOTO, 0);
			fif = fp;  /* loc of GOTO */
			fexpr->f_skip = next_fp - fexpr;

			c = '<';  /* impersonate an IF */
			continue;
		}
		break;
	}

	if (c != '>') {
		CERROR("'>' expected.");
	}

	if (fexpr)  /* IF ... [ELSIF ...] ENDIF */
		fexpr->f_skip = next_fp - fexpr;

	return (cp);
}
