/* 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 fmttest 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.
 *
 * - Add code to fmttest.c to display your new function.
 *
 * - Document the new function in the mh-format(5) man page.
 *
 */

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

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

/*
 * hash table for deciding if a component is "interesting"
 */
static 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            */
#define TF_MYNAME  9        /* special - get current name of user */
#define TF_MYHOST  10       /* special - get "local" hostname     */
#define TF_LMBOX   11       /* special - get full local mailbox   */
#define TF_BOLD    12	    /* special - enter terminal bold mode */
#define TF_UNDERLN 13       /* special - enter underline mode     */
#define TF_STNDOUT 14       /* special - enter underline mode     */
#define TF_RESET   15       /* special - reset terminal modes     */
#define TF_HASCLR  16       /* special - terminal have color?     */
#define TF_FGCOLR  17       /* special - foreground term color    */
#define TF_BGCOLR  18       /* special - background term color    */

/* 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).  Also,
 *	    instructions that require special handling are distinguished
 *	    here (TF_MYMBOX is one example).
 * 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 },
     { "putlit",     TF_EXPR,	FT_STRLIT,	0,		0 },
     { "zputlit",    TF_EXPR,	FT_STRLITZ,	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 },
     { "kilo",       TF_EXPR,	FT_LS_KILO,	0,		TFL_PUTS },
     { "kibi",       TF_EXPR,	FT_LS_KIBI,	0,		TFL_PUTS },
     { "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 },
     { "myname",     TF_MYNAME,	FT_LS_LIT,	0,		TFL_PUTS },
     { "myhost",     TF_MYHOST,	FT_LS_LIT,	0,		TFL_PUTS },
     { "localmbox",  TF_LMBOX,	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 },
     { "multiply",   TF_NUM,	FT_LV_MULTIPLY_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 },
     { "concataddr", TF_EXPR_SV,FT_CONCATADDR,	FT_FORMATADDR,	0 },
     { "friendly",   TF_COMP,	FT_LS_FRIENDLY,	FT_PARSEADDR,	TFL_PUTS },

     { "mymbox",     TF_COMP,	FT_LV_COMPFLAG,	FT_MYMBOX,	TFL_PUTN },
     { "getmymbox",  TF_COMP,	FT_STR,		FT_GETMYMBOX,	0 },
     { "getmyaddr",  TF_COMP,	FT_LS_ADDR,	FT_GETMYADDR,	TFL_PUTS },

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

     { "bold",       TF_BOLD,	FT_LS_LIT,	0,		TFL_PUTS },
     { "underline",  TF_UNDERLN,FT_LS_LIT,	0,		TFL_PUTS },
     { "standout",   TF_STNDOUT,FT_LS_LIT,	0,		TFL_PUTS },
     { "resetterm",  TF_RESET,	FT_LS_LIT,	0,		TFL_PUTS },
     { "hascolor",   TF_HASCLR, FT_LV_LIT,	0,		0 },
     { "fgcolor",    TF_FGCOLR, FT_LS_LIT,	0,		TFL_PUTS },
     { "bgcolor",    TF_BGCOLR, FT_LS_LIT,	0,		TFL_PUTS },

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

/*
 * A mapping of color names to terminfo color numbers.
 *
 * There are two sets of terminal-setting codes: 'setaf/setab' (ANSI) and
 * 'setf/setb'.  Different terminals support different capabilities, so
 * we provide a mapping for both.  I'm not crazy about putting numbers
 * directly in here, but it seems these are well defined by terminfo
 * so it should be okay.
 */

struct colormap {
    char *colorname;	/* Name of color */
    int ansinum;	/* The ANSI escape color number */
    int nonansinum;	/* The non-ANSI escape color number */
};

static struct colormap colortable[] = {
    { "black",		0,	0 },
    { "red",		1,	4 },
    { "green",		2,	2 },
    { "yellow",		3,	6 },
    { "blue",		4,	1 },
    { "magenta",	5,	5 },
    { "cyan",		6,	3 },
    { "white",		7,	7 },
    { NULL,		0,	0 }
};

/* Hash function for component name.  Deliberately avoids a function
 * call.  Is case independent.  Covers interval [0, 126] so never uses
 * the last element of wantcomp[]. This function is "pretty good". */
#define CHASH(nm) ( \
        (( \
            ((nm)[0]) - ((nm)[0] ? ((nm)[1]) : 0) \
        ) & 0x1f) + \
        ((nm[1]) ? (((nm)[2]) & 0x5f) : 0) \
    )

/*
 * Find a component in the hash table.
 */
#define FINDCOMP(comp,name) \
		for (comp = wantcomp[CHASH(name)]; \
		     comp && strcmp(comp->c_name,name); \
		     comp = comp->c_next) \
		;

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

#define NEW_FP(type,fill,wid) do {\
	fp=next_fp++; 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; \
	fp->f_flags |= FF_COMPREF; \
	cm->c_refcount++; \
	} while (0)

#define LV(type, value)		do { NEW_FP(type,0,0); fp->f_value = (value); } while (0)
#define LS(type, str)		do { NEW_FP(type,0,0); fp->f_text = getcpy(str); fp->f_flags |= FF_STRALLOC; } while (0)

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

static char *format_string;
static 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 *);
static void free_component(struct comp *);
static void free_comptable(void);

/*
 * 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 = min(errpos, 20);
    usr_fstring[errpos] = '\0';

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

    inform("\"%s\": format compile error - %s",
	   &usr_fstring[errpos-errctx], str);
    adios (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, int reset_comptable)
{
    char *cp;
    size_t i;
    static int comptable_initialized = 0;

    format_string = mh_xstrdup(fstring);
    usr_fstring = fstring;

    if (reset_comptable || !comptable_initialized) {
    	free_comptable();
	comptable_initialized = 1;
    }

    /* 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 *next_fp);
    infunction = 0;

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

    free(format_string);
    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;
#ifndef	lint
    int ljust = 0;
#endif	/* not lint */
    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");
	}
	if (cm->c_tws) {
	    memset (cm->c_tws, 0, sizeof *cm->c_tws);
	} else {
	    NEW0(cm->c_tws);
	}
	fp->f_type = preprocess;
	PUTCOMP(sp);
	cm->c_type |= CT_DATE;
	break;

    case FT_MYMBOX:
    case FT_GETMYMBOX:
    case FT_GETMYADDR:
	if (!primed) {
	    ismymbox ((struct mailname *) 0);
	    primed++;
	}
	/* FALLTHRU */
    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_MYNAME:
    	LS(t->f_type, getfullname());
	break;

    case TF_MYHOST:
    	LS(t->f_type, LocalName(0));
	break;

    case TF_LMBOX:
    	LS(t->f_type, getlocalmbox());
	break;

    case TF_BOLD:
    	LS(t->f_type, get_term_stringcap("bold"));
	break;

    case TF_UNDERLN:
   	LS(t->f_type, get_term_stringcap("smul"));
	break;

    case TF_STNDOUT:
    	LS(t->f_type, get_term_stringcap("smso"));
	break;

    case TF_RESET:
    	LS(t->f_type, get_term_stringcap("sgr0"));
	break;

    case TF_HASCLR:
    	LV(t->f_type, get_term_numcap("colors") > 1);
	break;

    case TF_FGCOLR:
    case TF_BGCOLR: {
	struct colormap *cmap = colortable;
    	char *code;

	sp = cp - 1;
	while (c && c != ')')
	    c = *cp++;
	cp[-1] = '\0';

	while (cmap->colorname != NULL) {
	    if (strcasecmp(sp, cmap->colorname) == 0)
	    	break;
	    cmap++;
	}

	if (cmap->colorname == NULL) {
	    CERROR("Unknown color name");
	    break;
	}

	code = get_term_stringparm(t->type == TF_FGCOLR ? "setaf" : "setab",
				   cmap->ansinum, 0);

	/*
	 * If this doesn't have anything, try falling back to setf/setb
	 */

	if (! code)
	    code = get_term_stringparm(t->type == TF_FGCOLR ? "setf" : "setb",
	    			       cmap->nonansinum, 0);

	LS(t->f_type, code);
	break;
    }

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

    case TF_EXPR_SV:
	LV(FT_SAVESTR, 0);
	/* FALLTHRU */
    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.
 *
 * Okay, got some more information on this from John L. Romine!  From an
 * email he sent to the nmh-workers mailing list on December 2, 2010, he
 * explains it so:
 *
 *    In this case (scan, formatsbr) it has to do with an extension to
 *    the mh-format syntax to allow for looping.
 *
 *    The scan format is processed once for each message.  Those #ifdef
 *    JLR changes allowed for the top part of the format file to be
 *    processed once, then a second, looping part to be processed
 *    once per message.  As I recall, there were new mh-format escape
 *    sequences to delimit the loop.  This would have allowed for things
 *    like per-format column headings in the scan output.
 *
 *    Since existing format files didn't include the scan listing
 *    header (it was hard-coded in scan.c) it would not have been
 *    backward-compatible.  All existing format files (including any
 *    local ones) would have needed to be changed to include the format
 *    codes for a header.  The practice at the time was not to introduce
 *    incompatible changes in a minor release, and I never managed to
 *    put out a newer major release.
 *
 * I can see how this would work, and I suspect part of the motivation was
 * because the format compiler routines (at the time) couldn't really be
 * called multiple times on the same message because the memory management
 * was so lousy.  That's been reworked and things are now a lot cleaner,
 * so I suspect if we're going to allow a format string to be used for the
 * scan header it might be simpler to have a separate format string just
 * for the header.  But I'll leave this code in for now just in case we
 * decide that we want some kind of looping support.
 */
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);
}

/*
 * Free a set of format instructions.
 *
 * What we do here is:
 *
 * - Iterate through the list of format instructions, freeing any references
 *   to allocated memory in each instruction.
 * - Free component references.
 * - If requested, reset the component hash table; that will also free any
 *   references to components stored there.
 *
 */

void
fmt_free(struct format *fmt, int reset_comptable)
{
    struct format *fp = fmt;

    if (fp) {
    	while (! (fp->f_type == FT_DONE && fp->f_value == 0)) {
	    if (fp->f_flags & FF_STRALLOC)
	    	free(fp->f_text);
	    if (fp->f_flags & FF_COMPREF)
	    	free_component(fp->f_comp);
	    fp++;
	}
	free(fmt);
    }

    if (reset_comptable)
    	free_comptable();
}

/*
 * Free just the text strings from all of the component hash table entries
 */

void
fmt_freecomptext(void)
{
    unsigned int i;
    struct comp *cm;

    for (i = 0; i < DIM(wantcomp); i++)
        for (cm = wantcomp[i]; cm; cm = cm->c_next) {
            mh_xfree(cm->c_text);
            cm->c_text = NULL;
        }
}

/*
 * Find a component in our hash table.  This is just a public interface to
 * the FINDCOMP macro, so we don't have to expose our hash table.
 */

struct comp *
fmt_findcomp(char *component)
{
    struct comp *cm;

    FINDCOMP(cm, component);

    return cm;
}

/*
 * Like fmt_findcomp, but case-insensitive.
 */

struct comp *
fmt_findcasecomp(char *component)
{
    struct comp *cm;

    for (cm = wantcomp[CHASH(component)]; cm; cm = cm->c_next)
	if (strcasecmp(component, FENDNULL(cm->c_name)) == 0)
	    break;

    return cm;
}

/*
 * Add an entry to the component hash table
 *
 * Returns true if the component was added, 0 if it already existed.
 *
 */

int
fmt_addcompentry(char *component)
{
    struct comp *cm;
    int i;

    FINDCOMP(cm, component);

    if (cm)
    	return 0;

    NEWCOMP(cm, component);

    /*
     * ncomp is really meant for fmt_compile() and this function is
     * meant to be used outside of it.  So decrement it just to be safe
     * (internal callers should be using NEWCOMP()).
     */

    ncomp--;

    return 1;
}

/*
 * Add a string to a component hash table entry.
 *
 * Note the special handling for components marked with CT_ADDR.  The comments
 * in fmt_scan.h explain this in more detail.
 */

int
fmt_addcomptext(char *component, char *text)
{
    int i, found = 0, bucket = CHASH(component);
    struct comp *cptr = wantcomp[bucket];
    char *cp;

    while (cptr) {
	if (strcasecmp(component, FENDNULL(cptr->c_name)) == 0) {
	    found++;
	    if (! cptr->c_text) {
		cptr->c_text = getcpy(text);
	    } else {
		i = strlen(cp = cptr->c_text) - 1;
		if (cp[i] == '\n') {
		    if (cptr->c_type & CT_ADDR) {
			cp[i] = '\0';
			cp = add(",\n\t", cp);
		    } else {
			cp = add("\t", cp);
		    }
		}
		cptr->c_text = add(text, cp);
	    }
	}
	cptr = cptr->c_next;
    }

    return found ? bucket : -1;
}

/*
 * Append text to a component we've already found.  See notes in fmt_scan.h
 * for more information.
 */

void
fmt_appendcomp(int bucket, char *component, char *text)
{
    struct comp *cptr;

    if (bucket != -1) {
    	for (cptr = wantcomp[bucket]; cptr; cptr = cptr->c_next)
	    if (strcasecmp(component, FENDNULL(cptr->c_name)) == 0)
	    	cptr->c_text = add(text, cptr->c_text);
    }
}

/*
 * Iterate over our component hash table
 */

struct comp *
fmt_nextcomp(struct comp *comp, unsigned int *bucket)
{
    if (comp == NULL)
	*bucket = 0;
    else
	comp = comp->c_next;

    while (comp == NULL && *bucket < DIM(wantcomp)) {
	comp = wantcomp[(*bucket)++];
    }

    return comp;
}

/*
 * Free and reset our component hash table
 */

static void
free_comptable(void)
{
    unsigned int i;
    struct comp *cm, *cm2;

    for (i = 0; i < DIM(wantcomp); i++) {
    	cm = wantcomp[i];
	while (cm != NULL) {
	    cm2 = cm->c_next;
	    free_component(cm);
	    cm = cm2;
	}
	wantcomp[i] = 0;
    }

    ncomp = 0;
}

/*
 * Decrement the reference count of a component structure.  If it reaches
 * zero, free it
 */

static void
free_component(struct comp *cm)
{
    if (--cm->c_refcount <= 0) {
    	/* Shouldn't ever be NULL, but just in case ... */
        mh_xfree(cm->c_name);
        mh_xfree(cm->c_text);
	if (cm->c_type & CT_DATE)
	    free(cm->c_tws);
	if (cm->c_type & CT_ADDR && cm->c_mn && cm->c_mn != &fmt_mnull)
	    mnfree(cm->c_mn);
    	free(cm);
    }
}
