]> git.cameronkatri.com Git - mandoc.git/blobdiff - roff.c
Three bugfixes related to the closing of conditional blocks:
[mandoc.git] / roff.c
diff --git a/roff.c b/roff.c
index 062758ce49c310ce0b501c46d4d73ff2ab62f64e..0772d8ab9d28287cea7652ed40d8fbd7cfc4cda8 100644 (file)
--- a/roff.c
+++ b/roff.c
@@ -1,7 +1,7 @@
-/*     $Id: roff.c,v 1.179 2013/10/05 20:30:05 schwarze Exp $ */
+/*     $Id: roff.c,v 1.195 2014/03/07 02:22:05 schwarze Exp $ */
 /*
  * Copyright (c) 2010, 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
- * Copyright (c) 2010, 2011, 2012, 2013 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2010-2014 Ingo Schwarze <schwarze@openbsd.org>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -40,12 +40,16 @@ enum        rofft {
        ROFF_am,
        ROFF_ami,
        ROFF_am1,
+       ROFF_as,
        ROFF_cc,
+       ROFF_ce,
        ROFF_de,
        ROFF_dei,
        ROFF_de1,
        ROFF_ds,
        ROFF_el,
+       ROFF_fam,
+       ROFF_hw,
        ROFF_hy,
        ROFF_ie,
        ROFF_if,
@@ -68,24 +72,13 @@ enum        rofft {
        ROFF_EQ,
        ROFF_EN,
        ROFF_cblock,
-       ROFF_ccond,
        ROFF_USERDEF,
        ROFF_MAX
 };
 
 enum   roffrule {
-       ROFFRULE_ALLOW,
-       ROFFRULE_DENY
-};
-
-/*
- * A single register entity.  If "set" is zero, the value of the
- * register should be the default one, which is per-register.
- * Registers are assumed to be unsigned ints for now.
- */
-struct reg {
-       int              set; /* whether set or not */
-       unsigned int     u; /* unsigned integer */
+       ROFFRULE_DENY,
+       ROFFRULE_ALLOW
 };
 
 /*
@@ -105,14 +98,24 @@ struct     roffkv {
        struct roffkv   *next; /* next in list */
 };
 
+/*
+ * A single number register as part of a singly-linked list.
+ */
+struct roffreg {
+       struct roffstr   key;
+       int              val;
+       struct roffreg  *next;
+};
+
 struct roff {
        enum mparset     parsetype; /* requested parse type */
        struct mparse   *parse; /* parse point */
+       int              quick; /* skip standard macro deletion */
        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 roffreg  *regtab; /* number registers */
        struct roffkv   *strtab; /* user-defined strings & macros */
        struct roffkv   *xmbtab; /* multi-byte trans table (`tr') */
        struct roffstr  *xtab; /* single-byte trans table (`tr') */
@@ -176,15 +179,21 @@ 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 void             roff_ccond(struct roff *, int, int);
 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_free1(struct roff *);
+static void             roff_freereg(struct roffreg *);
 static void             roff_freestr(struct roffkv *);
 static char            *roff_getname(struct roff *, char **, int, int);
+static int              roff_getnum(const char *, int *, int *);
+static int              roff_getop(const char *, int *, char *);
+static int              roff_getregn(const struct roff *,
+                               const char *, size_t);
+static int              roff_getregro(const char *name);
 static const char      *roff_getstrn(const struct roff *, 
                                const char *, size_t);
 static enum rofferr     roff_it(ROFF_ARGS);
@@ -225,12 +234,16 @@ 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 },
+       { "as", roff_ds, NULL, NULL, 0, NULL },
        { "cc", roff_cc, NULL, NULL, 0, NULL },
+       { "ce", roff_line_ignore, 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 },
        { "ds", roff_ds, NULL, NULL, 0, NULL },
        { "el", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL },
+       { "fam", roff_line_ignore, NULL, NULL, 0, NULL },
+       { "hw", roff_line_ignore, NULL, NULL, 0, NULL },
        { "hy", roff_line_ignore, NULL, NULL, 0, NULL },
        { "ie", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL },
        { "if", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL },
@@ -253,7 +266,6 @@ static      struct roffmac   roffs[ROFF_MAX] = {
        { "EQ", roff_EQ, NULL, NULL, 0, NULL },
        { "EN", roff_EN, NULL, NULL, 0, NULL },
        { ".", roff_cblock, NULL, NULL, 0, NULL },
-       { "\\}", roff_ccond, NULL, NULL, 0, NULL },
        { NULL, roff_userdef, NULL, NULL, 0, NULL },
 };
 
@@ -424,6 +436,10 @@ roff_free1(struct roff *r)
 
        r->strtab = r->xmbtab = NULL;
 
+       roff_freereg(r->regtab);
+
+       r->regtab = NULL;
+
        if (r->xtab)
                for (i = 0; i < 128; i++)
                        free(r->xtab[i].p);
@@ -435,15 +451,9 @@ roff_free1(struct roff *r)
 void
 roff_reset(struct roff *r)
 {
-       int              i;
 
        roff_free1(r);
-
        r->control = 0;
-       memset(&r->regs, 0, sizeof(struct reg) * REG__MAX);
-
-       for (i = 0; i < PREDEFS_MAX; i++) 
-               roff_setstr(r, predefs[i].name, predefs[i].str, 0);
 }
 
 
@@ -457,41 +467,39 @@ roff_free(struct roff *r)
 
 
 struct roff *
-roff_alloc(enum mparset type, struct mparse *parse)
+roff_alloc(enum mparset type, struct mparse *parse, int quick)
 {
        struct roff     *r;
-       int              i;
 
        r = mandoc_calloc(1, sizeof(struct roff));
        r->parsetype = type;
        r->parse = parse;
+       r->quick = quick;
        r->rstackpos = -1;
        
        roffhash_init();
 
-       for (i = 0; i < PREDEFS_MAX; i++) 
-               roff_setstr(r, predefs[i].name, predefs[i].str, 0);
-
        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.
+ * In the current line, expand user-defined strings ("\*")
+ * and references to number registers ("\n").
+ * Also check the syntax of other escape sequences.
  */
 static enum rofferr
 roff_res(struct roff *r, char **bufp, size_t *szp, int ln, int pos)
 {
-       enum mandoc_esc  esc;
+       char             ubuf[12]; /* buffer to print the number */
        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, expand_count;
-       size_t           nsz;
-       char            *n;
+       char            *nbuf;  /* new buffer to copy bufp to */
+       size_t           nsz;   /* size of the new buffer */
+       size_t           maxl;  /* expected length of the escape name */
+       size_t           naml;  /* actual length of the escape name */
+       int              expand_count;  /* to avoid infinite loops */
 
        expand_count = 0;
 
@@ -501,7 +509,7 @@ again:
                stesc = cp++;
 
                /*
-                * The second character must be an asterisk.
+                * The second character must be an asterisk or an n.
                 * If it isn't, skip it anyway:  It is escaped,
                 * so it can't start another escape sequence.
                 */
@@ -509,12 +517,16 @@ again:
                if ('\0' == *cp)
                        return(ROFF_CONT);
 
-               if ('*' != *cp) {
-                       res = cp;
-                       esc = mandoc_escape(&cp, NULL, NULL);
-                       if (ESCAPE_ERROR != esc)
+               switch (*cp) {
+               case ('*'):
+                       res = NULL;
+                       break;
+               case ('n'):
+                       res = ubuf;
+                       break;
+               default:
+                       if (ESCAPE_ERROR != mandoc_escape(&cp, NULL, NULL))
                                continue;
-                       cp = res;
                        mandoc_msg
                                (MANDOCERR_BADESCAPE, r->parse, 
                                 ln, (int)(stesc - *bufp), NULL);
@@ -525,7 +537,7 @@ again:
 
                /*
                 * The third character decides the length
-                * of the name of the string.
+                * of the name of the string or register.
                 * Save a pointer to the name.
                 */
 
@@ -548,7 +560,7 @@ again:
 
                /* Advance to the end of the name. */
 
-               for (i = 0; 0 == maxl || i < maxl; i++, cp++) {
+               for (naml = 0; 0 == maxl || naml < maxl; naml++, cp++) {
                        if ('\0' == *cp) {
                                mandoc_msg
                                        (MANDOCERR_BADESCAPE, 
@@ -565,7 +577,11 @@ again:
                 * undefined, resume searching for escapes.
                 */
 
-               res = roff_getstrn(r, stnam, (size_t)i);
+               if (NULL == res)
+                       res = roff_getstrn(r, stnam, naml);
+               else
+                       snprintf(ubuf, sizeof(ubuf), "%d",
+                           roff_getregn(r, stnam, naml));
 
                if (NULL == res) {
                        mandoc_msg
@@ -579,15 +595,15 @@ again:
                pos = stesc - *bufp;
 
                nsz = *szp + strlen(res) + 1;
-               n = mandoc_malloc(nsz);
+               nbuf = mandoc_malloc(nsz);
 
-               strlcpy(n, *bufp, (size_t)(stesc - *bufp + 1));
-               strlcat(n, res, nsz);
-               strlcat(n, cp + (maxl ? 0 : 1), nsz);
+               strlcpy(nbuf, *bufp, (size_t)(stesc - *bufp + 1));
+               strlcat(nbuf, res, nsz);
+               strlcat(nbuf, cp + (maxl ? 0 : 1), nsz);
 
                free(*bufp);
 
-               *bufp = n;
+               *bufp = nbuf;
                *szp = nsz;
 
                if (EXPAND_LIMIT >= ++expand_count)
@@ -626,8 +642,7 @@ roff_parsetext(char **bufp, size_t *szp, int pos, int *offs)
                if ('\\' == *p) {
                        /* Skip over escapes. */
                        p++;
-                       esc = mandoc_escape
-                               ((const char const **)&p, NULL, NULL);
+                       esc = mandoc_escape((const char **)&p, NULL, NULL);
                        if (ESCAPE_ERROR == esc)
                                break;
                        continue;
@@ -698,19 +713,14 @@ roff_parseln(struct roff *r, int ln, char **bufp,
                assert(ROFF_IGN == e || ROFF_CONT == e);
                if (ROFF_CONT != e)
                        return(e);
-               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_parsetext(bufp, szp, pos, offs));
-       } else if ( ! ctl) {
-               if (r->eqn)
-                       return(eqn_read(&r->eqn, ln, *bufp, pos, offs));
+       }
+       if (r->eqn)
+               return(eqn_read(&r->eqn, ln, *bufp, ppos, offs));
+       if ( ! ctl) {
                if (r->tbl)
                        return(tbl_read(r->tbl, ln, *bufp, pos));
                return(roff_parsetext(bufp, szp, pos, offs));
-       } else if (r->eqn)
-               return(eqn_read(&r->eqn, ln, *bufp, ppos, offs));
+       }
 
        /*
         * If a scope is open, go to the child handler for that macro,
@@ -778,14 +788,10 @@ roff_parse(struct roff *r, const char *buf, int *pos)
                        '\t' == buf[*pos] || ' ' == buf[*pos])
                return(ROFF_MAX);
 
-       /*
-        * We stop the macro parse at an escape, tab, space, or nil.
-        * However, `\}' is also a valid macro, so make sure we don't
-        * clobber it by seeing the `\' as the end of token.
-        */
+       /* We stop the macro parse at an escape, tab, space, or nil. */
 
        mac = buf + *pos;
-       maclen = strcspn(mac + 1, " \\\t\0") + 1;
+       maclen = strcspn(mac, " \\\t\0");
 
        t = (r->current_string = roff_getstrn(r, mac, maclen))
            ? ROFF_USERDEF : roffhash_find(mac, maclen);
@@ -854,14 +860,13 @@ roffnode_cleanscope(struct roff *r)
 }
 
 
-/* ARGSUSED */
-static enum rofferr
-roff_ccond(ROFF_ARGS)
+static void
+roff_ccond(struct roff *r, int ln, int ppos)
 {
 
        if (NULL == r->last) {
                mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
-               return(ROFF_IGN);
+               return;
        }
 
        switch (r->last->tok) {
@@ -873,20 +878,17 @@ roff_ccond(ROFF_ARGS)
                break;
        default:
                mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
-               return(ROFF_IGN);
+               return;
        }
 
        if (r->last->endspan > -1) {
                mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
-               return(ROFF_IGN);
+               return;
        }
 
-       if ((*bufp)[pos])
-               mandoc_msg(MANDOCERR_ARGSLOST, r->parse, ln, pos, NULL);
-
        roffnode_pop(r);
        roffnode_cleanscope(r);
-       return(ROFF_IGN);
+       return;
 }
 
 
@@ -931,7 +933,7 @@ roff_block(ROFF_ARGS)
        /*
         * At the beginning of a `de' macro, clear the existing string
         * with the same name, if there is one.  New content will be
-        * added from roff_block_text() in multiline mode.
+        * appended from roff_block_text() in multiline mode.
         */
 
        if (ROFF_de == tok)
@@ -1021,7 +1023,7 @@ roff_block_sub(ROFF_ARGS)
         */
        if (ROFF_cblock != t) {
                if (ROFF_de == tok)
-                       roff_setstr(r, r->last->name, *bufp + ppos, 1);
+                       roff_setstr(r, r->last->name, *bufp + ppos, 2);
                return(ROFF_IGN);
        }
 
@@ -1037,7 +1039,7 @@ roff_block_text(ROFF_ARGS)
 {
 
        if (ROFF_de == tok)
-               roff_setstr(r, r->last->name, *bufp + pos, 1);
+               roff_setstr(r, r->last->name, *bufp + pos, 2);
 
        return(ROFF_IGN);
 }
@@ -1061,7 +1063,7 @@ roff_cond_sub(ROFF_ARGS)
         */
 
        if ((ROFF_MAX != t) &&
-           (ROFF_ccond == t || ROFFRULE_ALLOW == rr ||
+           (ROFFRULE_ALLOW == rr ||
             ROFFMAC_STRUCT & roffs[t].flags)) {
                assert(roffs[t].proc);
                return((*roffs[t].proc)(r, t, bufp, szp,
@@ -1087,9 +1089,7 @@ roff_cond_sub(ROFF_ARGS)
                } else
                        *(ep - 1) = *ep = ' ';
 
-               roff_ccond(r, ROFF_ccond, bufp, szp, 
-                               ln, pos, pos + 2, offs);
-               break;
+               roff_ccond(r, ln, pos);
        }
        return(ROFFRULE_DENY == rr ? ROFF_IGN : ROFF_CONT);
 }
@@ -1110,15 +1110,66 @@ roff_cond_text(ROFF_ARGS)
                if ('}' != *ep)
                        continue;
                *ep = '&';
-               roff_ccond(r, ROFF_ccond, bufp, szp, 
-                               ln, pos, pos + 2, offs);
+               roff_ccond(r, ln, pos);
        }
        return(ROFFRULE_DENY == rr ? ROFF_IGN : ROFF_CONT);
 }
 
+static int
+roff_getnum(const char *v, int *pos, int *res)
+{
+       int p, n;
+
+       p = *pos;
+       n = v[p] == '-';
+       if (n)
+               p++;
+
+       for (*res = 0; isdigit((unsigned char)v[p]); p++)
+               *res += 10 * *res + v[p] - '0';
+       if (p == *pos + n)
+               return 0;
+
+       if (n)
+               *res = -*res;
+
+       *pos = p;
+       return 1;
+}
+
+static int
+roff_getop(const char *v, int *pos, char *res)
+{
+       int e;
+
+       *res = v[*pos];
+       e = v[*pos + 1] == '=';
+
+       switch (*res) {
+       case '=':
+               break;
+       case '>':
+               if (e)
+                       *res = 'g';
+               break;
+       case '<':
+               if (e)
+                       *res = 'l';
+               break;
+       default:
+               return(0);
+       }
+
+       *pos += 1 + e;
+
+       return(*res);
+}
+
 static enum roffrule
 roff_evalcond(const char *v, int *pos)
 {
+       int      not, lh, rh;
+       char     op;
 
        switch (v[*pos]) {
        case ('n'):
@@ -1131,13 +1182,47 @@ roff_evalcond(const char *v, int *pos)
        case ('t'):
                (*pos)++;
                return(ROFFRULE_DENY);
+       case ('!'):
+               (*pos)++;
+               not = 1;
+               break;
        default:
+               not = 0;
                break;
        }
 
-       while (v[*pos] && ' ' != v[*pos])
-               (*pos)++;
-       return(ROFFRULE_DENY);
+       if (!roff_getnum(v, pos, &lh))
+               return ROFFRULE_DENY;
+       if (!roff_getop(v, pos, &op)) {
+               if (lh < 0)
+                       lh = 0;
+               goto out;
+       }
+       if (!roff_getnum(v, pos, &rh))
+               return ROFFRULE_DENY;
+       switch (op) {
+       case 'g':
+               lh = lh >= rh;
+               break;
+       case 'l':
+               lh = lh <= rh;
+               break;
+       case '=':
+               lh = lh == rh;
+               break;
+       case '>':
+               lh = lh > rh;
+               break;
+       case '<':
+               lh = lh < rh;
+               break;
+       default:
+               return ROFFRULE_DENY;
+       }
+out:
+       if (not)
+               lh = !lh;
+       return lh ? ROFFRULE_ALLOW : ROFFRULE_DENY;
 }
 
 /* ARGSUSED */
@@ -1254,29 +1339,117 @@ roff_ds(ROFF_ARGS)
                string++;
 
        /* The rest is the value. */
-       roff_setstr(r, name, string, 0);
+       roff_setstr(r, name, string, ROFF_as == tok);
        return(ROFF_IGN);
 }
 
+void
+roff_setreg(struct roff *r, const char *name, int val, char sign)
+{
+       struct roffreg  *reg;
+
+       /* Search for an existing register with the same name. */
+       reg = r->regtab;
+
+       while (reg && strcmp(name, reg->key.p))
+               reg = reg->next;
+
+       if (NULL == reg) {
+               /* Create a new register. */
+               reg = mandoc_malloc(sizeof(struct roffreg));
+               reg->key.p = mandoc_strdup(name);
+               reg->key.sz = strlen(name);
+               reg->val = 0;
+               reg->next = r->regtab;
+               r->regtab = reg;
+       }
+
+       if ('+' == sign)
+               reg->val += val;
+       else if ('-' == sign)
+               reg->val -= val;
+       else
+               reg->val = val;
+}
+
+/*
+ * Handle some predefined read-only number registers.
+ * For now, return -1 if the requested register is not predefined;
+ * in case a predefined read-only register having the value -1
+ * were to turn up, another special value would have to be chosen.
+ */
+static int
+roff_getregro(const char *name)
+{
+
+       switch (*name) {
+       case ('A'):  /* ASCII approximation mode is always off. */
+               return(0);
+       case ('g'):  /* Groff compatibility mode is always on. */
+               return(1);
+       case ('H'):  /* Fixed horizontal resolution. */
+               return (24);
+       case ('j'):  /* Always adjust left margin only. */
+               return(0);
+       case ('T'):  /* Some output device is always defined. */
+               return(1);
+       case ('V'):  /* Fixed vertical resolution. */
+               return (40);
+       default:
+               return (-1);
+       }
+}
+
 int
-roff_regisset(const struct roff *r, enum regs reg)
+roff_getreg(const struct roff *r, const char *name)
 {
+       struct roffreg  *reg;
+       int              val;
+
+       if ('.' == name[0] && '\0' != name[1] && '\0' == name[2]) {
+               val = roff_getregro(name + 1);
+               if (-1 != val)
+                       return (val);
+       }
 
-       return(r->regs[(int)reg].set);
+       for (reg = r->regtab; reg; reg = reg->next)
+               if (0 == strcmp(name, reg->key.p))
+                       return(reg->val);
+
+       return(0);
 }
 
-unsigned int
-roff_regget(const struct roff *r, enum regs reg)
+static int
+roff_getregn(const struct roff *r, const char *name, size_t len)
 {
+       struct roffreg  *reg;
+       int              val;
+
+       if ('.' == name[0] && 2 == len) {
+               val = roff_getregro(name + 1);
+               if (-1 != val)
+                       return (val);
+       }
 
-       return(r->regs[(int)reg].u);
+       for (reg = r->regtab; reg; reg = reg->next)
+               if (len == reg->key.sz &&
+                   0 == strncmp(name, reg->key.p, len))
+                       return(reg->val);
+
+       return(0);
 }
 
-void
-roff_regunset(struct roff *r, enum regs reg)
+static void
+roff_freereg(struct roffreg *reg)
 {
+       struct roffreg  *old_reg;
 
-       r->regs[(int)reg].set = 0;
+       while (NULL != reg) {
+               free(reg->key.p);
+               old_reg = reg;
+               reg = reg->next;
+               free(old_reg);
+       }
 }
 
 /* ARGSUSED */
@@ -1285,18 +1458,21 @@ roff_nr(ROFF_ARGS)
 {
        const char      *key;
        char            *val;
+       size_t           sz;
        int              iv;
+       char             sign;
 
        val = *bufp + pos;
        key = roff_getname(r, &val, ln, pos);
 
-       if (0 == strcmp(key, "nS")) {
-               r->regs[(int)REG_nS].set = 1;
-               if ((iv = mandoc_strntoi(val, strlen(val), 10)) >= 0)
-                       r->regs[(int)REG_nS].u = (unsigned)iv;
-               else
-                       r->regs[(int)REG_nS].u = 0u;
-       }
+       sign = *val;
+       if ('+' == sign || '-' == sign)
+               val++;
+
+       sz = strspn(val, "0123456789");
+       iv = sz ? mandoc_strntoi(val, sz, 10) : 0;
+
+       roff_setreg(r, key, iv, sign);
 
        return(ROFF_IGN);
 }
@@ -1348,7 +1524,7 @@ roff_Dd(ROFF_ARGS)
 {
        const char *const       *cp;
 
-       if (MPARSE_MDOC != r->parsetype)
+       if (0 == r->quick && MPARSE_MDOC != r->parsetype)
                for (cp = __mdoc_reserved; *cp; cp++)
                        roff_setstr(r, *cp, NULL, 0);
 
@@ -1361,7 +1537,7 @@ roff_TH(ROFF_ARGS)
 {
        const char *const       *cp;
 
-       if (MPARSE_MDOC != r->parsetype)
+       if (0 == r->quick && MPARSE_MDOC != r->parsetype)
                for (cp = __man_reserved; *cp; cp++)
                        roff_setstr(r, *cp, NULL, 0);
 
@@ -1583,7 +1759,7 @@ roff_userdef(ROFF_ARGS)
 
        /*
         * Collect pointers to macro argument strings
-        * and null-terminate them.
+        * and NUL-terminate them.
         */
        cp = *bufp + pos;
        for (i = 0; i < 9; i++)
@@ -1663,22 +1839,23 @@ roff_getname(struct roff *r, char **cpp, int ln, int pos)
 
 /*
  * Store *string into the user-defined string called *name.
- * In multiline mode, append to an existing entry and append '\n';
- * else replace the existing entry, if there is one.
  * To clear an existing entry, call with (*r, *name, NULL, 0).
+ * append == 0: replace mode
+ * append == 1: single-line append mode
+ * append == 2: multiline append mode, append '\n' after each call
  */
 static void
 roff_setstr(struct roff *r, const char *name, const char *string,
-       int multiline)
+       int append)
 {
 
        roff_setstrn(&r->strtab, name, strlen(name), string,
-                       string ? strlen(string) : 0, multiline);
+                       string ? strlen(string) : 0, append);
 }
 
 static void
 roff_setstrn(struct roffkv **r, const char *name, size_t namesz,
-               const char *string, size_t stringsz, int multiline)
+               const char *string, size_t stringsz, int append)
 {
        struct roffkv   *n;
        char            *c;
@@ -1700,8 +1877,7 @@ roff_setstrn(struct roffkv **r, const char *name, size_t namesz,
                n->val.sz = 0;
                n->next = *r;
                *r = n;
-       } else if (0 == multiline) {
-               /* In multiline mode, append; else replace. */
+       } else if (0 == append) {
                free(n->val.p);
                n->val.p = NULL;
                n->val.sz = 0;
@@ -1714,7 +1890,7 @@ roff_setstrn(struct roffkv **r, const char *name, size_t namesz,
         * One additional byte for the '\n' in multiline mode,
         * and one for the terminating '\0'.
         */
-       newch = stringsz + (multiline ? 2u : 1u);
+       newch = stringsz + (1 < append ? 2u : 1u);
 
        if (NULL == n->val.p) {
                n->val.p = mandoc_malloc(newch);
@@ -1741,7 +1917,7 @@ roff_setstrn(struct roffkv **r, const char *name, size_t namesz,
        }
 
        /* Append terminating bytes. */
-       if (multiline)
+       if (1 < append)
                *c++ = '\n';
 
        *c = '\0';
@@ -1752,12 +1928,18 @@ static const char *
 roff_getstrn(const struct roff *r, const char *name, size_t len)
 {
        const struct roffkv *n;
+       int i;
 
        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);
 
+       for (i = 0; i < PREDEFS_MAX; i++)
+               if (0 == strncmp(name, predefs[i].name, len) &&
+                               '\0' == predefs[i].name[(int)len])
+                       return(predefs[i].str);
+
        return(NULL);
 }