=================================================================== RCS file: /cvs/mandoc/roff.c,v retrieving revision 1.150 retrieving revision 1.176 diff -u -p -r1.150 -r1.176 --- mandoc/roff.c 2011/07/23 18:41:18 1.150 +++ mandoc/roff.c 2013/05/31 22:08:09 1.176 @@ -1,7 +1,7 @@ -/* $Id: roff.c,v 1.150 2011/07/23 18:41:18 kristaps Exp $ */ +/* $Id: roff.c,v 1.176 2013/05/31 22:08:09 schwarze Exp $ */ /* - * Copyright (c) 2010, 2011 Kristaps Dzonsons - * Copyright (c) 2010, 2011 Ingo Schwarze + * Copyright (c) 2010, 2011, 2012 Kristaps Dzonsons + * Copyright (c) 2010, 2011, 2012 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -31,11 +31,15 @@ /* Maximum number of nested if-else conditionals. */ #define RSTACK_MAX 128 +/* Maximum number of string expansions per line, to break infinite loops. */ +#define EXPAND_LIMIT 1000 + enum rofft { ROFF_ad, ROFF_am, ROFF_ami, ROFF_am1, + ROFF_cc, ROFF_de, ROFF_dei, ROFF_de1, @@ -55,6 +59,8 @@ enum rofft { ROFF_so, ROFF_ta, ROFF_tr, + ROFF_Dd, + ROFF_TH, ROFF_TS, ROFF_TE, ROFF_T_, @@ -77,23 +83,38 @@ enum roffrule { * Registers are assumed to be unsigned ints for now. */ struct reg { - int set; /* whether set or not */ - unsigned int u; /* unsigned integer */ + int set; /* whether set or not */ + unsigned int u; /* unsigned integer */ }; +/* + * An incredibly-simple string buffer. + */ struct roffstr { - char *name; /* key of symbol */ - char *string; /* current value */ - struct roffstr *next; /* next in list */ + char *p; /* nil-terminated buffer */ + size_t sz; /* saved strlen(p) */ }; +/* + * A key-value roffstr pair as part of a singly-linked list. + */ +struct roffkv { + struct roffstr key; + struct roffstr val; + struct roffkv *next; /* next in list */ +}; + struct roff { + enum mparset parsetype; /* requested parse type */ struct mparse *parse; /* parse point */ struct roffnode *last; /* leaf of stack */ enum roffrule rstack[RSTACK_MAX]; /* stack of !`ie' rules */ + char control; /* control character */ int rstackpos; /* position in rstack */ struct reg regs[REG__MAX]; - struct roffstr *first_string; /* user-defined strings & macros */ + struct roffkv *strtab; /* user-defined strings & macros */ + struct roffkv *xmbtab; /* multi-byte trans table (`tr') */ + struct roffstr *xtab; /* single-byte trans table (`tr') */ const char *current_string; /* value of last called user macro */ struct tbl_node *first_tbl; /* first table parsed */ struct tbl_node *last_tbl; /* last table parsed */ @@ -143,28 +164,45 @@ struct predef { #define PREDEF(__name, __str) \ { (__name), (__str) }, +static enum rofft roffhash_find(const char *, size_t); +static void roffhash_init(void); +static void roffnode_cleanscope(struct roff *); +static void roffnode_pop(struct roff *); +static void roffnode_push(struct roff *, enum rofft, + const char *, int, int); static enum rofferr roff_block(ROFF_ARGS); static enum rofferr roff_block_text(ROFF_ARGS); static enum rofferr roff_block_sub(ROFF_ARGS); static enum rofferr roff_cblock(ROFF_ARGS); +static enum rofferr roff_cc(ROFF_ARGS); static enum rofferr roff_ccond(ROFF_ARGS); static enum rofferr roff_cond(ROFF_ARGS); static enum rofferr roff_cond_text(ROFF_ARGS); static enum rofferr roff_cond_sub(ROFF_ARGS); static enum rofferr roff_ds(ROFF_ARGS); static enum roffrule roff_evalcond(const char *, int *); -static void roff_freestr(struct roff *); +static void roff_free1(struct roff *); +static void roff_freestr(struct roffkv *); static char *roff_getname(struct roff *, char **, int, int); static const char *roff_getstrn(const struct roff *, const char *, size_t); static enum rofferr roff_line_ignore(ROFF_ARGS); static enum rofferr roff_nr(ROFF_ARGS); -static int roff_res(struct roff *, +static void roff_openeqn(struct roff *, const char *, + int, int, const char *); +static enum rofft roff_parse(struct roff *, const char *, int *); +static enum rofferr roff_parsetext(char *); +static enum rofferr roff_res(struct roff *, char **, size_t *, int, int); static enum rofferr roff_rm(ROFF_ARGS); static void roff_setstr(struct roff *, const char *, const char *, int); +static void roff_setstrn(struct roffkv **, const char *, + size_t, const char *, size_t, int); static enum rofferr roff_so(ROFF_ARGS); +static enum rofferr roff_tr(ROFF_ARGS); +static enum rofferr roff_Dd(ROFF_ARGS); +static enum rofferr roff_TH(ROFF_ARGS); static enum rofferr roff_TE(ROFF_ARGS); static enum rofferr roff_TS(ROFF_ARGS); static enum rofferr roff_EQ(ROFF_ARGS); @@ -172,7 +210,7 @@ static enum rofferr roff_EN(ROFF_ARGS); static enum rofferr roff_T_(ROFF_ARGS); static enum rofferr roff_userdef(ROFF_ARGS); -/* See roff_hash_find() */ +/* See roffhash_find() */ #define ASCII_HI 126 #define ASCII_LO 33 @@ -185,6 +223,7 @@ static struct roffmac roffs[ROFF_MAX] = { { "am", roff_block, roff_block_text, roff_block_sub, 0, NULL }, { "ami", roff_block, roff_block_text, roff_block_sub, 0, NULL }, { "am1", roff_block, roff_block_text, roff_block_sub, 0, NULL }, + { "cc", roff_cc, NULL, NULL, 0, NULL }, { "de", roff_block, roff_block_text, roff_block_sub, 0, NULL }, { "dei", roff_block, roff_block_text, roff_block_sub, 0, NULL }, { "de1", roff_block, roff_block_text, roff_block_sub, 0, NULL }, @@ -203,7 +242,9 @@ static struct roffmac roffs[ROFF_MAX] = { { "rm", roff_rm, NULL, NULL, 0, NULL }, { "so", roff_so, NULL, NULL, 0, NULL }, { "ta", roff_line_ignore, NULL, NULL, 0, NULL }, - { "tr", roff_line_ignore, NULL, NULL, 0, NULL }, + { "tr", roff_tr, NULL, NULL, 0, NULL }, + { "Dd", roff_Dd, NULL, NULL, 0, NULL }, + { "TH", roff_TH, NULL, NULL, 0, NULL }, { "TS", roff_TS, NULL, NULL, 0, NULL }, { "TE", roff_TE, NULL, NULL, 0, NULL }, { "T&", roff_T_, NULL, NULL, 0, NULL }, @@ -214,26 +255,48 @@ static struct roffmac roffs[ROFF_MAX] = { { NULL, roff_userdef, NULL, NULL, 0, NULL }, }; +const char *const __mdoc_reserved[] = { + "Ac", "Ad", "An", "Ao", "Ap", "Aq", "Ar", "At", + "Bc", "Bd", "Bf", "Bk", "Bl", "Bo", "Bq", + "Brc", "Bro", "Brq", "Bsx", "Bt", "Bx", + "Cd", "Cm", "Db", "Dc", "Dd", "Dl", "Do", "Dq", + "Ds", "Dt", "Dv", "Dx", "D1", + "Ec", "Ed", "Ef", "Ek", "El", "Em", "em", + "En", "Eo", "Eq", "Er", "Es", "Ev", "Ex", + "Fa", "Fc", "Fd", "Fl", "Fn", "Fo", "Fr", "Ft", "Fx", + "Hf", "Ic", "In", "It", "Lb", "Li", "Lk", "Lp", "LP", + "Me", "Ms", "Mt", "Nd", "Nm", "No", "Ns", "Nx", + "Oc", "Oo", "Op", "Os", "Ot", "Ox", + "Pa", "Pc", "Pf", "Po", "Pp", "PP", "pp", "Pq", + "Qc", "Ql", "Qo", "Qq", "Or", "Rd", "Re", "Rs", "Rv", + "Sc", "Sf", "Sh", "SH", "Sm", "So", "Sq", + "Ss", "St", "Sx", "Sy", + "Ta", "Tn", "Ud", "Ux", "Va", "Vt", "Xc", "Xo", "Xr", + "%A", "%B", "%D", "%I", "%J", "%N", "%O", + "%P", "%Q", "%R", "%T", "%U", "%V", + NULL +}; + +const char *const __man_reserved[] = { + "AT", "B", "BI", "BR", "BT", "DE", "DS", "DT", + "EE", "EN", "EQ", "EX", "HF", "HP", "I", "IB", "IP", "IR", + "LP", "ME", "MT", "OP", "P", "PD", "PP", "PT", + "R", "RB", "RE", "RI", "RS", "SB", "SH", "SM", "SS", "SY", + "TE", "TH", "TP", "TQ", "TS", "T&", "UC", "UE", "UR", "YS", + NULL +}; + /* Array of injected predefined strings. */ #define PREDEFS_MAX 38 static const struct predef predefs[PREDEFS_MAX] = { #include "predefs.in" }; -static void roff_free1(struct roff *); -static enum rofft roff_hash_find(const char *, size_t); -static void roff_hash_init(void); -static void roffnode_cleanscope(struct roff *); -static void roffnode_push(struct roff *, enum rofft, - const char *, int, int); -static void roffnode_pop(struct roff *); -static enum rofft roff_parse(struct roff *, const char *, int *); - -/* See roff_hash_find() */ +/* See roffhash_find() */ #define ROFF_HASH(p) (p[0] - ASCII_LO) static void -roff_hash_init(void) +roffhash_init(void) { struct roffmac *n; int buc, i; @@ -258,7 +321,7 @@ roff_hash_init(void) * the nil-terminated string name could be found. */ static enum rofft -roff_hash_find(const char *p, size_t s) +roffhash_find(const char *p, size_t s) { int buc; struct roffmac *n; @@ -330,12 +393,13 @@ roffnode_push(struct roff *r, enum rofft tok, const ch static void roff_free1(struct roff *r) { - struct tbl_node *t; + struct tbl_node *tbl; struct eqn_node *e; + int i; - while (NULL != (t = r->first_tbl)) { - r->first_tbl = t->next; - tbl_free(t); + while (NULL != (tbl = r->first_tbl)) { + r->first_tbl = tbl->next; + tbl_free(tbl); } r->first_tbl = r->last_tbl = r->tbl = NULL; @@ -350,10 +414,19 @@ roff_free1(struct roff *r) while (r->last) roffnode_pop(r); - roff_freestr(r); -} + roff_freestr(r->strtab); + roff_freestr(r->xmbtab); + r->strtab = r->xmbtab = NULL; + if (r->xtab) + for (i = 0; i < 128; i++) + free(r->xtab[i].p); + + free(r->xtab); + r->xtab = NULL; +} + void roff_reset(struct roff *r) { @@ -361,6 +434,7 @@ roff_reset(struct roff *r) roff_free1(r); + r->control = 0; memset(&r->regs, 0, sizeof(struct reg) * REG__MAX); for (i = 0; i < PREDEFS_MAX; i++) @@ -378,16 +452,17 @@ roff_free(struct roff *r) struct roff * -roff_alloc(struct mparse *parse) +roff_alloc(enum mparset type, struct mparse *parse) { struct roff *r; int i; r = mandoc_calloc(1, sizeof(struct roff)); + r->parsetype = type; r->parse = parse; r->rstackpos = -1; - roff_hash_init(); + roffhash_init(); for (i = 0; i < PREDEFS_MAX; i++) roff_setstr(r, predefs[i].name, predefs[i].str, 0); @@ -395,25 +470,27 @@ roff_alloc(struct mparse *parse) return(r); } - /* * Pre-filter each and every line for reserved words (one beginning with * `\*', e.g., `\*(ab'). These must be handled before the actual line * is processed. + * This also checks the syntax of regular escapes. */ -static int +static enum rofferr roff_res(struct roff *r, char **bufp, size_t *szp, int ln, int pos) { + enum mandoc_esc esc; const char *stesc; /* start of an escape sequence ('\\') */ const char *stnam; /* start of the name, after "[(*" */ const char *cp; /* end of the name, e.g. before ']' */ const char *res; /* the string to be substituted */ - int i, maxl; + int i, maxl, expand_count; size_t nsz; char *n; - /* Search for a leading backslash and save a pointer to it. */ + expand_count = 0; +again: cp = *bufp + pos; while (NULL != (cp = strchr(cp, '\\'))) { stesc = cp++; @@ -425,10 +502,22 @@ roff_res(struct roff *r, char **bufp, size_t *szp, int */ if ('\0' == *cp) - return(1); - if ('*' != *cp++) - continue; + return(ROFF_CONT); + if ('*' != *cp) { + res = cp; + esc = mandoc_escape(&cp, NULL, NULL); + if (ESCAPE_ERROR != esc) + continue; + cp = res; + mandoc_msg + (MANDOCERR_BADESCAPE, r->parse, + ln, (int)(stesc - *bufp), NULL); + return(ROFF_CONT); + } + + cp++; + /* * The third character decides the length * of the name of the string. @@ -437,7 +526,7 @@ roff_res(struct roff *r, char **bufp, size_t *szp, int switch (*cp) { case ('\0'): - return(1); + return(ROFF_CONT); case ('('): cp++; maxl = 2; @@ -455,8 +544,13 @@ roff_res(struct roff *r, char **bufp, size_t *szp, int /* Advance to the end of the name. */ for (i = 0; 0 == maxl || i < maxl; i++, cp++) { - if ('\0' == *cp) - return(1); /* Error. */ + if ('\0' == *cp) { + mandoc_msg + (MANDOCERR_BADESCAPE, + r->parse, ln, + (int)(stesc - *bufp), NULL); + return(ROFF_CONT); + } if (0 == maxl && ']' == *cp) break; } @@ -469,13 +563,16 @@ roff_res(struct roff *r, char **bufp, size_t *szp, int res = roff_getstrn(r, stnam, (size_t)i); if (NULL == res) { - /* TODO: keep track of the correct position. */ - mandoc_msg(MANDOCERR_BADESCAPE, r->parse, ln, pos, NULL); + mandoc_msg + (MANDOCERR_BADESCAPE, r->parse, + ln, (int)(stesc - *bufp), NULL); res = ""; } /* Replace the escape sequence by the string. */ + pos = stesc - *bufp; + nsz = *szp + strlen(res) + 1; n = mandoc_malloc(nsz); @@ -487,13 +584,58 @@ roff_res(struct roff *r, char **bufp, size_t *szp, int *bufp = n; *szp = nsz; - return(0); - } - return(1); + if (EXPAND_LIMIT >= ++expand_count) + goto again; + + /* Just leave the string unexpanded. */ + mandoc_msg(MANDOCERR_ROFFLOOP, r->parse, ln, pos, NULL); + return(ROFF_IGN); + } + return(ROFF_CONT); } +/* + * Process text streams: convert all breakable hyphens into ASCII_HYPH. + */ +static enum rofferr +roff_parsetext(char *p) +{ + size_t sz; + const char *start; + enum mandoc_esc esc; + start = p; + + while ('\0' != *p) { + sz = strcspn(p, "-\\"); + p += sz; + + if ('\0' == *p) + break; + + if ('\\' == *p) { + /* Skip over escapes. */ + p++; + esc = mandoc_escape + ((const char **)&p, NULL, NULL); + if (ESCAPE_ERROR == esc) + break; + continue; + } else if (p == start) { + p++; + continue; + } + + if (isalpha((unsigned char)p[-1]) && + isalpha((unsigned char)p[1])) + *p = ASCII_HYPH; + p++; + } + + return(ROFF_CONT); +} + enum rofferr roff_parseln(struct roff *r, int ln, char **bufp, size_t *szp, int pos, int *offs) @@ -507,11 +649,13 @@ roff_parseln(struct roff *r, int ln, char **bufp, * words to fill in. */ - if (r->first_string && ! roff_res(r, bufp, szp, ln, pos)) - return(ROFF_REPARSE); + e = roff_res(r, bufp, szp, ln, pos); + if (ROFF_IGN == e) + return(e); + assert(ROFF_CONT == e); ppos = pos; - ctl = mandoc_getcontrol(*bufp, &pos); + ctl = roff_getcontrol(r, *bufp, &pos); /* * First, if a scope is open and we're not a macro, pass the @@ -533,13 +677,13 @@ roff_parseln(struct roff *r, int ln, char **bufp, return(eqn_read(&r->eqn, ln, *bufp, pos, offs)); if (r->tbl) return(tbl_read(r->tbl, ln, *bufp, pos)); - return(ROFF_CONT); + return(roff_parsetext(*bufp + pos)); } else if ( ! ctl) { if (r->eqn) return(eqn_read(&r->eqn, ln, *bufp, pos, offs)); if (r->tbl) return(tbl_read(r->tbl, ln, *bufp, pos)); - return(ROFF_CONT); + return(roff_parsetext(*bufp + pos)); } else if (r->eqn) return(eqn_read(&r->eqn, ln, *bufp, ppos, offs)); @@ -584,15 +728,13 @@ roff_endparse(struct roff *r) if (r->eqn) { mandoc_msg(MANDOCERR_SCOPEEXIT, r->parse, r->eqn->eqn.ln, r->eqn->eqn.pos, NULL); - eqn_end(r->eqn); - r->eqn = NULL; + eqn_end(&r->eqn); } if (r->tbl) { mandoc_msg(MANDOCERR_SCOPEEXIT, r->parse, r->tbl->line, r->tbl->pos, NULL); - tbl_end(r->tbl); - r->tbl = NULL; + tbl_end(&r->tbl); } } @@ -621,7 +763,7 @@ roff_parse(struct roff *r, const char *buf, int *pos) maclen = strcspn(mac + 1, " \\\t\0") + 1; t = (r->current_string = roff_getstrn(r, mac, maclen)) - ? ROFF_USERDEF : roff_hash_find(mac, maclen); + ? ROFF_USERDEF : roffhash_find(mac, maclen); *pos += (int)maclen; @@ -680,7 +822,7 @@ roffnode_cleanscope(struct roff *r) { while (r->last) { - if (--r->last->endspan < 0) + if (--r->last->endspan != 0) break; roffnode_pop(r); } @@ -1000,9 +1142,9 @@ roff_line_ignore(ROFF_ARGS) static enum rofferr roff_cond(ROFF_ARGS) { - int sv; - enum roffrule rule; + roffnode_push(r, tok, NULL, ln, ppos); + /* * An `.el' has no conditional body: it will consume the value * of the current rstack entry set in prior `ie' calls or @@ -1011,32 +1153,12 @@ roff_cond(ROFF_ARGS) * If we're not an `el', however, then evaluate the conditional. */ - rule = ROFF_el == tok ? + r->last->rule = ROFF_el == tok ? (r->rstackpos < 0 ? ROFFRULE_DENY : r->rstack[r->rstackpos--]) : roff_evalcond(*bufp, &pos); - sv = pos; - while (' ' == (*bufp)[pos]) - pos++; - /* - * Roff is weird. If we have just white-space after the - * conditional, it's considered the BODY and we exit without - * really doing anything. Warn about this. It's probably - * wrong. - */ - - if ('\0' == (*bufp)[pos] && sv != pos) { - mandoc_msg(MANDOCERR_NOARGS, r->parse, ln, ppos, NULL); - return(ROFF_IGN); - } - - roffnode_push(r, tok, NULL, ln, ppos); - - r->last->rule = rule; - - /* * An if-else will put the NEGATION of the current evaluated * conditional into the stack of rules. */ @@ -1058,28 +1180,39 @@ roff_cond(ROFF_ARGS) r->last->rule = ROFFRULE_DENY; /* - * Determine scope. If we're invoked with "\{" trailing the - * conditional, then we're in a multiline scope. Else our scope - * expires on the next line. + * Determine scope. + * If there is nothing on the line after the conditional, + * not even whitespace, use next-line scope. */ - r->last->endspan = 1; + if ('\0' == (*bufp)[pos]) { + r->last->endspan = 2; + goto out; + } + while (' ' == (*bufp)[pos]) + pos++; + + /* An opening brace requests multiline scope. */ + if ('\\' == (*bufp)[pos] && '{' == (*bufp)[pos + 1]) { r->last->endspan = -1; pos += 2; + goto out; } /* - * If there are no arguments on the line, the next-line scope is - * assumed. + * Anything else following the conditional causes + * single-line scope. Warn if the scope contains + * nothing but trailing whitespace. */ if ('\0' == (*bufp)[pos]) - return(ROFF_IGN); + mandoc_msg(MANDOCERR_NOARGS, r->parse, ln, ppos, NULL); - /* Otherwise re-run the roff parser after recalculating. */ + r->last->endspan = 1; +out: *offs = pos; return(ROFF_RERUN); } @@ -1176,15 +1309,40 @@ roff_rm(ROFF_ARGS) /* ARGSUSED */ static enum rofferr +roff_Dd(ROFF_ARGS) +{ + const char *const *cp; + + if (MPARSE_MDOC != r->parsetype) + for (cp = __mdoc_reserved; *cp; cp++) + roff_setstr(r, *cp, NULL, 0); + + return(ROFF_CONT); +} + +/* ARGSUSED */ +static enum rofferr +roff_TH(ROFF_ARGS) +{ + const char *const *cp; + + if (MPARSE_MDOC != r->parsetype) + for (cp = __man_reserved; *cp; cp++) + roff_setstr(r, *cp, NULL, 0); + + return(ROFF_CONT); +} + +/* ARGSUSED */ +static enum rofferr roff_TE(ROFF_ARGS) { if (NULL == r->tbl) mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL); else - tbl_end(r->tbl); + tbl_end(&r->tbl); - r->tbl = NULL; return(ROFF_IGN); } @@ -1201,14 +1359,24 @@ roff_T_(ROFF_ARGS) return(ROFF_IGN); } -/* ARGSUSED */ -static enum rofferr -roff_EQ(ROFF_ARGS) +#if 0 +static int +roff_closeeqn(struct roff *r) { - struct eqn_node *e; + return(r->eqn && ROFF_EQN == eqn_end(&r->eqn) ? 1 : 0); +} +#endif + +static void +roff_openeqn(struct roff *r, const char *name, int line, + int offs, const char *buf) +{ + struct eqn_node *e; + int poff; + assert(NULL == r->eqn); - e = eqn_alloc(*bufp + pos, ppos, ln, r->parse); + e = eqn_alloc(name, offs, line, r->parse); if (r->last_eqn) r->last_eqn->next = e; @@ -1216,6 +1384,19 @@ roff_EQ(ROFF_ARGS) r->first_eqn = r->last_eqn = e; r->eqn = r->last_eqn = e; + + if (buf) { + poff = 0; + eqn_read(&r->eqn, line, buf, offs, &poff); + } +} + +/* ARGSUSED */ +static enum rofferr +roff_EQ(ROFF_ARGS) +{ + + roff_openeqn(r, *bufp + pos, ln, ppos, NULL); return(ROFF_IGN); } @@ -1232,26 +1413,108 @@ roff_EN(ROFF_ARGS) static enum rofferr roff_TS(ROFF_ARGS) { - struct tbl_node *t; + struct tbl_node *tbl; if (r->tbl) { mandoc_msg(MANDOCERR_SCOPEBROKEN, r->parse, ln, ppos, NULL); - tbl_end(r->tbl); + tbl_end(&r->tbl); } - t = tbl_alloc(ppos, ln, r->parse); + tbl = tbl_alloc(ppos, ln, r->parse); if (r->last_tbl) - r->last_tbl->next = t; + r->last_tbl->next = tbl; else - r->first_tbl = r->last_tbl = t; + r->first_tbl = r->last_tbl = tbl; - r->tbl = r->last_tbl = t; + r->tbl = r->last_tbl = tbl; return(ROFF_IGN); } /* ARGSUSED */ static enum rofferr +roff_cc(ROFF_ARGS) +{ + const char *p; + + p = *bufp + pos; + + if ('\0' == *p || '.' == (r->control = *p++)) + r->control = 0; + + if ('\0' != *p) + mandoc_msg(MANDOCERR_ARGCOUNT, r->parse, ln, ppos, NULL); + + return(ROFF_IGN); +} + +/* ARGSUSED */ +static enum rofferr +roff_tr(ROFF_ARGS) +{ + const char *p, *first, *second; + size_t fsz, ssz; + enum mandoc_esc esc; + + p = *bufp + pos; + + if ('\0' == *p) { + mandoc_msg(MANDOCERR_ARGCOUNT, r->parse, ln, ppos, NULL); + return(ROFF_IGN); + } + + while ('\0' != *p) { + fsz = ssz = 1; + + first = p++; + if ('\\' == *first) { + esc = mandoc_escape(&p, NULL, NULL); + if (ESCAPE_ERROR == esc) { + mandoc_msg + (MANDOCERR_BADESCAPE, r->parse, + ln, (int)(p - *bufp), NULL); + return(ROFF_IGN); + } + fsz = (size_t)(p - first); + } + + second = p++; + if ('\\' == *second) { + esc = mandoc_escape(&p, NULL, NULL); + if (ESCAPE_ERROR == esc) { + mandoc_msg + (MANDOCERR_BADESCAPE, r->parse, + ln, (int)(p - *bufp), NULL); + return(ROFF_IGN); + } + ssz = (size_t)(p - second); + } else if ('\0' == *second) { + mandoc_msg(MANDOCERR_ARGCOUNT, r->parse, + ln, (int)(p - *bufp), NULL); + second = " "; + p--; + } + + if (fsz > 1) { + roff_setstrn(&r->xmbtab, first, + fsz, second, ssz, 0); + continue; + } + + if (NULL == r->xtab) + r->xtab = mandoc_calloc + (128, sizeof(struct roffstr)); + + free(r->xtab[(int)*first].p); + r->xtab[(int)*first].p = mandoc_strndup(second, ssz); + r->xtab[(int)*first].sz = ssz; + } + + return(ROFF_IGN); +} + +/* ARGSUSED */ +static enum rofferr roff_so(ROFF_ARGS) { char *name; @@ -1373,26 +1636,40 @@ static void roff_setstr(struct roff *r, const char *name, const char *string, int multiline) { - struct roffstr *n; - char *c; - size_t oldch, newch; + roff_setstrn(&r->strtab, name, strlen(name), string, + string ? strlen(string) : 0, multiline); +} + +static void +roff_setstrn(struct roffkv **r, const char *name, size_t namesz, + const char *string, size_t stringsz, int multiline) +{ + struct roffkv *n; + char *c; + int i; + size_t oldch, newch; + /* Search for an existing string with the same name. */ - n = r->first_string; - while (n && strcmp(name, n->name)) + n = *r; + + while (n && strcmp(name, n->key.p)) n = n->next; if (NULL == n) { /* Create a new string table entry. */ - n = mandoc_malloc(sizeof(struct roffstr)); - n->name = mandoc_strdup(name); - n->string = NULL; - n->next = r->first_string; - r->first_string = n; + n = mandoc_malloc(sizeof(struct roffkv)); + n->key.p = mandoc_strndup(name, namesz); + n->key.sz = namesz; + n->val.p = NULL; + n->val.sz = 0; + n->next = *r; + *r = n; } else if (0 == multiline) { /* In multiline mode, append; else replace. */ - free(n->string); - n->string = NULL; + free(n->val.p); + n->val.p = NULL; + n->val.sz = 0; } if (NULL == string) @@ -1402,61 +1679,64 @@ roff_setstr(struct roff *r, const char *name, const ch * One additional byte for the '\n' in multiline mode, * and one for the terminating '\0'. */ - newch = strlen(string) + (multiline ? 2u : 1u); - if (NULL == n->string) { - n->string = mandoc_malloc(newch); - *n->string = '\0'; + newch = stringsz + (multiline ? 2u : 1u); + + if (NULL == n->val.p) { + n->val.p = mandoc_malloc(newch); + *n->val.p = '\0'; oldch = 0; } else { - oldch = strlen(n->string); - n->string = mandoc_realloc(n->string, oldch + newch); + oldch = n->val.sz; + n->val.p = mandoc_realloc(n->val.p, oldch + newch); } /* Skip existing content in the destination buffer. */ - c = n->string + (int)oldch; + c = n->val.p + (int)oldch; /* Append new content to the destination buffer. */ - while (*string) { + i = 0; + while (i < (int)stringsz) { /* * Rudimentary roff copy mode: * Handle escaped backslashes. */ - if ('\\' == *string && '\\' == *(string + 1)) - string++; - *c++ = *string++; + if ('\\' == string[i] && '\\' == string[i + 1]) + i++; + *c++ = string[i++]; } /* Append terminating bytes. */ if (multiline) *c++ = '\n'; + *c = '\0'; + n->val.sz = (int)(c - n->val.p); } static const char * roff_getstrn(const struct roff *r, const char *name, size_t len) { - const struct roffstr *n; + const struct roffkv *n; - n = r->first_string; - while (n && (strncmp(name, n->name, len) || '\0' != n->name[(int)len])) - n = n->next; + for (n = r->strtab; n; n = n->next) + if (0 == strncmp(name, n->key.p, len) && + '\0' == n->key.p[(int)len]) + return(n->val.p); - return(n ? n->string : NULL); + return(NULL); } static void -roff_freestr(struct roff *r) +roff_freestr(struct roffkv *r) { - struct roffstr *n, *nn; + struct roffkv *n, *nn; - for (n = r->first_string; n; n = nn) { - free(n->name); - free(n->string); + for (n = r; n; n = nn) { + free(n->key.p); + free(n->val.p); nn = n->next; free(n); } - - r->first_string = NULL; } const struct tbl_span * @@ -1471,4 +1751,131 @@ roff_eqn(const struct roff *r) { return(r->last_eqn ? &r->last_eqn->eqn : NULL); +} + +/* + * Duplicate an input string, making the appropriate character + * conversations (as stipulated by `tr') along the way. + * Returns a heap-allocated string with all the replacements made. + */ +char * +roff_strdup(const struct roff *r, const char *p) +{ + const struct roffkv *cp; + char *res; + const char *pp; + size_t ssz, sz; + enum mandoc_esc esc; + + if (NULL == r->xmbtab && NULL == r->xtab) + return(mandoc_strdup(p)); + else if ('\0' == *p) + return(mandoc_strdup("")); + + /* + * Step through each character looking for term matches + * (remember that a `tr' can be invoked with an escape, which is + * a glyph but the escape is multi-character). + * We only do this if the character hash has been initialised + * and the string is >0 length. + */ + + res = NULL; + ssz = 0; + + while ('\0' != *p) { + if ('\\' != *p && r->xtab && r->xtab[(int)*p].p) { + sz = r->xtab[(int)*p].sz; + res = mandoc_realloc(res, ssz + sz + 1); + memcpy(res + ssz, r->xtab[(int)*p].p, sz); + ssz += sz; + p++; + continue; + } else if ('\\' != *p) { + res = mandoc_realloc(res, ssz + 2); + res[ssz++] = *p++; + continue; + } + + /* Search for term matches. */ + for (cp = r->xmbtab; cp; cp = cp->next) + if (0 == strncmp(p, cp->key.p, cp->key.sz)) + break; + + if (NULL != cp) { + /* + * A match has been found. + * Append the match to the array and move + * forward by its keysize. + */ + res = mandoc_realloc + (res, ssz + cp->val.sz + 1); + memcpy(res + ssz, cp->val.p, cp->val.sz); + ssz += cp->val.sz; + p += (int)cp->key.sz; + continue; + } + + /* + * Handle escapes carefully: we need to copy + * over just the escape itself, or else we might + * do replacements within the escape itself. + * Make sure to pass along the bogus string. + */ + pp = p++; + esc = mandoc_escape(&p, NULL, NULL); + if (ESCAPE_ERROR == esc) { + sz = strlen(pp); + res = mandoc_realloc(res, ssz + sz + 1); + memcpy(res + ssz, pp, sz); + break; + } + /* + * We bail out on bad escapes. + * No need to warn: we already did so when + * roff_res() was called. + */ + sz = (int)(p - pp); + res = mandoc_realloc(res, ssz + sz + 1); + memcpy(res + ssz, pp, sz); + ssz += sz; + } + + res[(int)ssz] = '\0'; + return(res); +} + +/* + * Find out whether a line is a macro line or not. + * If it is, adjust the current position and return one; if it isn't, + * return zero and don't change the current position. + * If the control character has been set with `.cc', then let that grain + * precedence. + * This is slighly contrary to groff, where using the non-breaking + * control character when `cc' has been invoked will cause the + * non-breaking macro contents to be printed verbatim. + */ +int +roff_getcontrol(const struct roff *r, const char *cp, int *ppos) +{ + int pos; + + pos = *ppos; + + if (0 != r->control && cp[pos] == r->control) + pos++; + else if (0 != r->control) + return(0); + else if ('\\' == cp[pos] && '.' == cp[pos + 1]) + pos += 2; + else if ('.' == cp[pos] || '\'' == cp[pos]) + pos++; + else + return(0); + + while (' ' == cp[pos] || '\t' == cp[pos]) + pos++; + + *ppos = pos; + return(1); }