
/*
 * fmtcompile.c -- "compile" format strings for fmtscan
 *
 * $Id$
 */

#include <h/mh.h>
#include <h/addrsbr.h>
#include <h/formatsbr.h>
#include <zotnet/tws/tws.h>
#include <h/fmtcompile.h>

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;

/* fmt 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	TFL_PUTS   1	    /* implicit putstr if top level */
#define	TFL_PUTN   2	    /* implicit putnum if top level */

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_MFIND,	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 },
     { "addtoseq",   TF_STR,	FT_ADDTOSEQ,	0,		0 },

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


#define NEWCOMP(cm,name)\
	cm = ((struct comp *) calloc(1, sizeof (struct comp)));\
	cm->c_name = name; ncomp++;\
 	i = CHASH(name); cm->c_next = wantcomp[i]; wantcomp[i] = cm;

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

#define ADDC(name)\
	FINDCOMP( cm, name );\
	if ( ! cm ) {\
	    NEWCOMP(cm,name);\
	}\
	fp->f_comp = cm;

#define LV( type, value )	NEW(type,0,0); fp->f_value = (value);
#define LS( type, str )		NEW(type,0,0); fp->f_text = (str);
#define PUTCOMP( comp )		NEW(FT_COMP,0,0); ADDC(comp);
#define PUTLIT( str )		NEW(FT_LIT,0,0); fp->f_text = (str);
#define PUTC( c )		NEW(FT_CHAR,0,0); fp->f_char = (c);

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

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


extern char *getusr();

/*
 * 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 struct ftable *
lookup(char *name)
{
    register struct ftable *t = functable;
    register char *nm;
    register 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 errpos = cp - format_string;
    int errctx = errpos > 20 ? 20 : errpos;
    int i;

    usr_fstring[errpos] = '\0';
    for (i = errpos-errctx; i < errpos; i++)
#ifdef LOCALE
	if (iscntrl(usr_fstring[i]))
#else
	if (usr_fstring[i] < 32)
#endif
	    usr_fstring[i] = '_';
    advise(NULL, "\"%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)
{
    register char *cp;
    int i;

    if (format_string)
	free (format_string);
    format_string = getcpy (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;
    next_fp = formatvec = (struct format *)calloc ((unsigned) i,
						   sizeof(struct format));
    if (next_fp == NULL)
	adios (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)
{
    register char *cp = sp;
    register 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;
	}
    }
}


static char *
do_spec(char *sp)
{
    register char *cp = sp;
    register int c;
#ifndef	lint
    register int ljust = 0;
#endif	/* not lint */
    register int wid = 0;
    register 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);
}

static char *
do_name(char *sp, int preprocess)
{
    register char *cp = sp;
    register int c;
    register 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_type & CT_DATE)) {
	    cm->c_tws = (struct tws *)
				calloc((unsigned) 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++;
	}
	cm->c_type |= CT_MYMBOX;
	/* fall through */
    case FT_PARSEADDR:
	if (cm->c_type & CT_DATE) {
	    CERROR("component used as both date and address");
	}
	if (! (cm->c_type & CT_ADDRPARSE)) {
	    cm->c_mn = &fmt_mnull;
	    fp->f_type = preprocess;
	    PUTCOMP(sp);
	    cm->c_type |= (CT_ADDR | CT_ADDRPARSE);
	}
	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);
}

static char *
do_func(char *sp)
{
    register char *cp = sp;
    register int c;
    register struct ftable *t;
    register 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, getusr());
	break;

    case TF_NOW:
	LV(t->f_type, time((long *) 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);
}

static char *
do_expr (char *sp, int preprocess)
{
    register char *cp = sp;
    register 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);
}

static char *
do_loop(char *sp)
{
    register 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;
}

static char *
do_if(char *sp)
{
    register char *cp = sp;
    register struct format *fexpr,
			   *fif = (struct format *)NULL;
    register 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 {
		    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);
}
