/* 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 #include #include #include #include #include #include #ifdef HAVE_SYS_TIME_H # include #endif #include /* * 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); } }