/*
* fmt_compile.c -- "compile" format strings for fmt_scan
*
* $Id: fmt_compile.c,v 1.7 2005/05/18 12:50:45 opk Exp $
*
* 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.
*/
#include <h/mh.h>
#include <h/addrsbr.h>
#include <h/tws.h>
#include <h/fmt_scan.h>
#include <h/fmt_compile.h>
#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef TM_IN_SYS_TIME
# include <sys/time.h>
# else
# include <time.h>
# endif
#endif
/*
* 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 */
#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_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 },
{ "addtoseq", TF_STR, FT_ADDTOSEQ, 0, 0 },
{ "unquote", TF_EXPR, FT_LS_UNQUOTE, 0, TFL_PUTS},
{ NULL, 0, 0, 0, 0 }
};
/* Add new component to the hash table */
#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);
/* Add (possibly new) component to the hash table */
#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)
/*
* external prototypes
*/
extern char *getusername(void);
/*
* 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 i, errpos, errctx;
errpos = cp - format_string;
errctx = errpos > 20 ? 20 : errpos;
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;
if (i==1) i++;
next_fp = formatvec = (struct format *)calloc ((size_t) 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");
}
cm->c_tws = (struct tws *)
calloc((size_t) 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);
}
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, 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);
}
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);
}
syntax highlighted by Code2HTML, v. 0.9.1