]> git.cameronkatri.com Git - mandoc.git/blobdiff - mdoc_validate.c
Improve and test the messages about empty macros,
[mandoc.git] / mdoc_validate.c
index bfd4d23725df86d9ae6a9cbb30066850c1d22516..4beb2196ca8fa579b14008d0c4bd1fff59a7ec35 100644 (file)
@@ -1,4 +1,4 @@
-/*     $Id: mdoc_validate.c,v 1.200 2014/01/06 22:39:25 schwarze Exp $ */
+/*     $Id: mdoc_validate.c,v 1.223 2014/07/02 20:19:11 schwarze Exp $ */
 /*
  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2010-2014 Ingo Schwarze <schwarze@openbsd.org>
@@ -36,6 +36,7 @@
 
 #include "mdoc.h"
 #include "mandoc.h"
+#include "mandoc_aux.h"
 #include "libmdoc.h"
 #include "libmandoc.h"
 
@@ -44,9 +45,6 @@
 #define        PRE_ARGS  struct mdoc *mdoc, struct mdoc_node *n
 #define        POST_ARGS struct mdoc *mdoc
 
-#define        NUMSIZ    32
-#define        DATESIZE  32
-
 enum   check_ineq {
        CHECK_LT,
        CHECK_GT,
@@ -66,14 +64,13 @@ struct      valids {
        v_post  *post;
 };
 
-static int      check_count(struct mdoc *, enum mdoc_type, 
+static int      check_count(struct mdoc *, enum mdoc_type,
                        enum check_lvl, enum check_ineq, int);
 static int      check_parent(PRE_ARGS, enum mdoct, enum mdoc_type);
 static void     check_text(struct mdoc *, int, int, char *);
-static void     check_argv(struct mdoc *, 
+static void     check_argv(struct mdoc *,
                        struct mdoc_node *, struct mdoc_argv *);
 static void     check_args(struct mdoc *, struct mdoc_node *);
-static int      concat(char *, const struct mdoc_node *, size_t);
 static enum mdoc_sec   a2sec(const char *);
 static size_t          macro2len(enum mdoct);
 
@@ -101,6 +98,8 @@ static       int      post_bx(POST_ARGS);
 static int      post_defaults(POST_ARGS);
 static int      post_dd(POST_ARGS);
 static int      post_dt(POST_ARGS);
+static int      post_en(POST_ARGS);
+static int      post_es(POST_ARGS);
 static int      post_eoln(POST_ARGS);
 static int      post_hyph(POST_ARGS);
 static int      post_ignpar(POST_ARGS);
@@ -128,6 +127,7 @@ static      int      pre_display(PRE_ARGS);
 static int      pre_dt(PRE_ARGS);
 static int      pre_it(PRE_ARGS);
 static int      pre_literal(PRE_ARGS);
+static int      pre_obsolete(PRE_ARGS);
 static int      pre_os(PRE_ARGS);
 static int      pre_par(PRE_ARGS);
 static int      pre_sh(PRE_ARGS);
@@ -148,6 +148,8 @@ static      v_post   posts_d1[] = { bwarn_ge1, post_hyph, NULL };
 static v_post   posts_dd[] = { post_dd, post_prol, NULL };
 static v_post   posts_dl[] = { post_literal, bwarn_ge1, NULL };
 static v_post   posts_dt[] = { post_dt, post_prol, NULL };
+static v_post   posts_en[] = { post_en, NULL };
+static v_post   posts_es[] = { post_es, NULL };
 static v_post   posts_fo[] = { hwarn_eq1, bwarn_ge1, NULL };
 static v_post   posts_hyph[] = { post_hyph, NULL };
 static v_post   posts_hyphtext[] = { ewarn_ge1, post_hyph, NULL };
@@ -176,6 +178,7 @@ static      v_pre    pres_dl[] = { pre_literal, pre_display, NULL };
 static v_pre    pres_dd[] = { pre_dd, NULL };
 static v_pre    pres_dt[] = { pre_dt, NULL };
 static v_pre    pres_it[] = { pre_it, pre_par, NULL };
+static v_pre    pres_obsolete[] = { pre_obsolete, NULL };
 static v_pre    pres_os[] = { pre_os, NULL };
 static v_pre    pres_pp[] = { pre_par, NULL };
 static v_pre    pres_sh[] = { pre_sh, NULL };
@@ -187,43 +190,43 @@ static    const struct valids mdoc_valids[MDOC_MAX] = {
        { pres_dd, posts_dd },                  /* Dd */
        { pres_dt, posts_dt },                  /* Dt */
        { pres_os, posts_os },                  /* Os */
-       { pres_sh, posts_sh },                  /* Sh */ 
-       { pres_ss, posts_ss },                  /* Ss */ 
-       { pres_pp, posts_pp },                  /* Pp */ 
+       { pres_sh, posts_sh },                  /* Sh */
+       { pres_ss, posts_ss },                  /* Ss */
+       { pres_pp, posts_pp },                  /* Pp */
        { pres_d1, posts_d1 },                  /* D1 */
        { pres_dl, posts_dl },                  /* Dl */
        { pres_bd, posts_bd },                  /* Bd */
        { NULL, NULL },                         /* Ed */
-       { pres_bl, posts_bl },                  /* Bl */ 
+       { pres_bl, posts_bl },                  /* Bl */
        { NULL, NULL },                         /* El */
        { pres_it, posts_it },                  /* It */
-       { NULL, NULL },                         /* Ad */ 
-       { pres_an, posts_an },                  /* An */ 
+       { NULL, NULL },                         /* Ad */
+       { pres_an, posts_an },                  /* An */
        { NULL, posts_defaults },               /* Ar */
-       { NULL, NULL },                         /* Cd */ 
+       { NULL, NULL },                         /* Cd */
        { NULL, NULL },                         /* Cm */
-       { NULL, NULL },                         /* Dv */ 
-       { NULL, NULL },                         /* Er */ 
-       { NULL, NULL },                         /* Ev */ 
-       { pres_std, posts_std },                /* Ex */ 
-       { NULL, NULL },                         /* Fa */ 
+       { NULL, NULL },                         /* Dv */
+       { NULL, NULL },                         /* Er */
+       { NULL, NULL },                         /* Ev */
+       { pres_std, posts_std },                /* Ex */
+       { NULL, NULL },                         /* Fa */
        { NULL, posts_text },                   /* Fd */
        { NULL, NULL },                         /* Fl */
-       { NULL, NULL },                         /* Fn */ 
-       { NULL, NULL },                         /* Ft */ 
-       { NULL, NULL },                         /* Ic */ 
-       { NULL, posts_text1 },                  /* In */ 
+       { NULL, NULL },                         /* Fn */
+       { NULL, NULL },                         /* Ft */
+       { NULL, NULL },                         /* Ic */
+       { NULL, posts_text1 },                  /* In */
        { NULL, posts_defaults },               /* Li */
        { NULL, posts_nd },                     /* Nd */
        { NULL, posts_nm },                     /* Nm */
        { NULL, NULL },                         /* Op */
-       { NULL, NULL },                         /* Ot */
+       { pres_obsolete, NULL },                /* Ot */
        { NULL, posts_defaults },               /* Pa */
        { pres_std, posts_std },                /* Rv */
-       { NULL, posts_st },                     /* St */ 
+       { NULL, posts_st },                     /* St */
        { NULL, NULL },                         /* Va */
-       { NULL, posts_vt },                     /* Vt */ 
-       { NULL, posts_text },                   /* Xr */ 
+       { NULL, posts_vt },                     /* Vt */
+       { NULL, posts_text },                   /* Xr */
        { NULL, posts_text },                   /* %A */
        { NULL, posts_hyphtext },               /* %B */ /* FIXME: can be used outside Rs/Re. */
        { NULL, posts_text },                   /* %D */
@@ -238,7 +241,7 @@ static      const struct valids mdoc_valids[MDOC_MAX] = {
        { NULL, NULL },                         /* Ac */
        { NULL, NULL },                         /* Ao */
        { NULL, NULL },                         /* Aq */
-       { NULL, posts_at },                     /* At */ 
+       { NULL, posts_at },                     /* At */
        { NULL, NULL },                         /* Bc */
        { NULL, posts_bf },                     /* Bf */
        { NULL, NULL },                         /* Bo */
@@ -250,11 +253,11 @@ static    const struct valids mdoc_valids[MDOC_MAX] = {
        { NULL, NULL },                         /* Do */
        { NULL, NULL },                         /* Dq */
        { NULL, NULL },                         /* Ec */
-       { NULL, NULL },                         /* Ef */ 
-       { NULL, NULL },                         /* Em */ 
+       { NULL, NULL },                         /* Ef */
+       { NULL, NULL },                         /* Em */
        { NULL, NULL },                         /* Eo */
        { NULL, NULL },                         /* Fx */
-       { NULL, NULL },                         /* Ms */ 
+       { NULL, NULL },                         /* Ms */
        { NULL, posts_notext },                 /* No */
        { NULL, posts_ns },                     /* Ns */
        { NULL, NULL },                         /* Nx */
@@ -272,39 +275,40 @@ static    const struct valids mdoc_valids[MDOC_MAX] = {
        { NULL, NULL },                         /* Sc */
        { NULL, NULL },                         /* So */
        { NULL, NULL },                         /* Sq */
-       { NULL, posts_bool },                   /* Sm */ 
+       { NULL, posts_bool },                   /* Sm */
        { NULL, posts_hyph },                   /* Sx */
        { NULL, NULL },                         /* Sy */
        { NULL, NULL },                         /* Tn */
        { NULL, NULL },                         /* Ux */
        { NULL, NULL },                         /* Xc */
        { NULL, NULL },                         /* Xo */
-       { NULL, posts_fo },                     /* Fo */ 
-       { NULL, NULL },                         /* Fc */ 
+       { NULL, posts_fo },                     /* Fo */
+       { NULL, NULL },                         /* Fc */
        { NULL, NULL },                         /* Oo */
        { NULL, NULL },                         /* Oc */
        { NULL, posts_bk },                     /* Bk */
        { NULL, NULL },                         /* Ek */
        { NULL, posts_eoln },                   /* Bt */
        { NULL, NULL },                         /* Hf */
-       { NULL, NULL },                         /* Fr */
+       { pres_obsolete, NULL },                /* Fr */
        { NULL, posts_eoln },                   /* Ud */
        { NULL, posts_lb },                     /* Lb */
-       { pres_pp, posts_pp },                  /* Lp */ 
-       { NULL, NULL },                         /* Lk */ 
-       { NULL, posts_defaults },               /* Mt */ 
-       { NULL, NULL },                         /* Brq */ 
-       { NULL, NULL },                         /* Bro */ 
-       { NULL, NULL },                         /* Brc */ 
+       { pres_pp, posts_pp },                  /* Lp */
+       { NULL, NULL },                         /* Lk */
+       { NULL, posts_defaults },               /* Mt */
+       { NULL, NULL },                         /* Brq */
+       { NULL, NULL },                         /* Bro */
+       { NULL, NULL },                         /* Brc */
        { NULL, posts_text },                   /* %C */
-       { NULL, NULL },                         /* Es */
-       { NULL, NULL },                         /* En */
+       { pres_obsolete, posts_es },            /* Es */
+       { pres_obsolete, posts_en },            /* En */
        { NULL, NULL },                         /* Dx */
        { NULL, posts_text },                   /* %Q */
        { NULL, posts_pp },                     /* br */
        { NULL, posts_sp },                     /* sp */
        { NULL, posts_text1 },                  /* %U */
        { NULL, NULL },                         /* Ta */
+       { NULL, NULL },                         /* ll */
 };
 
 #define        RSORD_MAX 14 /* Number of `Rs' blocks. */
@@ -332,6 +336,7 @@ static      const char * const secnames[SEC__MAX] = {
        "LIBRARY",
        "SYNOPSIS",
        "DESCRIPTION",
+       "CONTEXT",
        "IMPLEMENTATION NOTES",
        "RETURN VALUES",
        "ENVIRONMENT",
@@ -351,6 +356,7 @@ static      const char * const secnames[SEC__MAX] = {
        NULL
 };
 
+
 int
 mdoc_valid_pre(struct mdoc *mdoc, struct mdoc_node *n)
 {
@@ -359,17 +365,17 @@ mdoc_valid_pre(struct mdoc *mdoc, struct mdoc_node *n)
        char            *tp;
 
        switch (n->type) {
-       case (MDOC_TEXT):
+       case MDOC_TEXT:
                tp = n->string;
                line = n->line;
                pos = n->pos;
                check_text(mdoc, line, pos, tp);
                /* FALLTHROUGH */
-       case (MDOC_TBL):
+       case MDOC_TBL:
                /* FALLTHROUGH */
-       case (MDOC_EQN):
+       case MDOC_EQN:
                /* FALLTHROUGH */
-       case (MDOC_ROOT):
+       case MDOC_ROOT:
                return(1);
        default:
                break;
@@ -380,12 +386,11 @@ mdoc_valid_pre(struct mdoc *mdoc, struct mdoc_node *n)
        if (NULL == mdoc_valids[n->tok].pre)
                return(1);
        for (p = mdoc_valids[n->tok].pre; *p; p++)
-               if ( ! (*p)(mdoc, n)) 
+               if ( ! (*p)(mdoc, n))
                        return(0);
        return(1);
 }
 
-
 int
 mdoc_valid_post(struct mdoc *mdoc)
 {
@@ -396,13 +401,13 @@ mdoc_valid_post(struct mdoc *mdoc)
        mdoc->last->flags |= MDOC_VALID;
 
        switch (mdoc->last->type) {
-       case (MDOC_TEXT):
+       case MDOC_TEXT:
                /* FALLTHROUGH */
-       case (MDOC_EQN):
+       case MDOC_EQN:
                /* FALLTHROUGH */
-       case (MDOC_TBL):
+       case MDOC_TBL:
                return(1);
-       case (MDOC_ROOT):
+       case MDOC_ROOT:
                return(post_root(mdoc));
        default:
                break;
@@ -411,14 +416,14 @@ mdoc_valid_post(struct mdoc *mdoc)
        if (NULL == mdoc_valids[mdoc->last->tok].post)
                return(1);
        for (p = mdoc_valids[mdoc->last->tok].post; *p; p++)
-               if ( ! (*p)(mdoc)) 
+               if ( ! (*p)(mdoc))
                        return(0);
 
        return(1);
 }
 
 static int
-check_count(struct mdoc *mdoc, enum mdoc_type type, 
+check_count(struct mdoc *mdoc, enum mdoc_type type,
                enum check_lvl lvl, enum check_ineq ineq, int val)
 {
        const char      *p;
@@ -426,19 +431,19 @@ check_count(struct mdoc *mdoc, enum mdoc_type type,
 
        if (mdoc->last->type != type)
                return(1);
-       
+
        switch (ineq) {
-       case (CHECK_LT):
+       case CHECK_LT:
                p = "less than ";
                if (mdoc->last->nchild < val)
                        return(1);
                break;
-       case (CHECK_GT):
+       case CHECK_GT:
                p = "more than ";
                if (mdoc->last->nchild > val)
                        return(1);
                break;
-       case (CHECK_EQ):
+       case CHECK_EQ:
                p = "";
                if (val == mdoc->last->nchild)
                        return(1);
@@ -449,9 +454,9 @@ check_count(struct mdoc *mdoc, enum mdoc_type type,
        }
 
        t = lvl == CHECK_WARN ? MANDOCERR_ARGCWARN : MANDOCERR_ARGCOUNT;
-       mandoc_vmsg(t, mdoc->parse, mdoc->last->line, mdoc->last->pos,
-                       "want %s%d children (have %d)",
-                       p, val, mdoc->last->nchild);
+       mandoc_vmsg(t, mdoc->parse, mdoc->last->line,
+           mdoc->last->pos, "want %s%d children (have %d)",
+           p, val, mdoc->last->nchild);
        return(1);
 }
 
@@ -565,9 +570,9 @@ check_parent(PRE_ARGS, enum mdoct tok, enum mdoc_type t)
                        (t == n->parent->type))
                return(1);
 
-       mandoc_vmsg(MANDOCERR_SYNTCHILD, mdoc->parse, n->line, 
-                       n->pos, "want parent %s", MDOC_ROOT == t ? 
-                       "<root>" : mdoc_macronames[tok]);
+       mandoc_vmsg(MANDOCERR_SYNTCHILD, mdoc->parse,
+           n->line, n->pos, "want parent %s",
+           MDOC_ROOT == t ? "<root>" : mdoc_macronames[tok]);
        return(0);
 }
 
@@ -580,18 +585,19 @@ pre_display(PRE_ARGS)
        if (MDOC_BLOCK != n->type)
                return(1);
 
-       for (node = mdoc->last->parent; node; node = node->parent) 
+       for (node = mdoc->last->parent; node; node = node->parent)
                if (MDOC_BLOCK == node->type)
                        if (MDOC_Bd == node->tok)
                                break;
 
        if (node)
-               mdoc_nmsg(mdoc, n, MANDOCERR_NESTEDDISP);
+               mandoc_vmsg(MANDOCERR_BD_NEST,
+                   mdoc->parse, n->line, n->pos,
+                   "%s in Bd", mdoc_macronames[n->tok]);
 
        return(1);
 }
 
-
 static int
 pre_bl(PRE_ARGS)
 {
@@ -613,58 +619,57 @@ pre_bl(PRE_ARGS)
                return(1);
        }
 
-       /* 
+       /*
         * First figure out which kind of list to use: bind ourselves to
         * the first mentioned list type and warn about any remaining
         * ones.  If we find no list type, we default to LIST_item.
         */
 
-       /* LINTED */
        for (i = 0; n->args && i < (int)n->args->argc; i++) {
                lt = LIST__NONE;
                dup = comp = 0;
                width = offs = NULL;
                switch (n->args->argv[i].arg) {
                /* Set list types. */
-               case (MDOC_Bullet):
+               case MDOC_Bullet:
                        lt = LIST_bullet;
                        break;
-               case (MDOC_Dash):
+               case MDOC_Dash:
                        lt = LIST_dash;
                        break;
-               case (MDOC_Enum):
+               case MDOC_Enum:
                        lt = LIST_enum;
                        break;
-               case (MDOC_Hyphen):
+               case MDOC_Hyphen:
                        lt = LIST_hyphen;
                        break;
-               case (MDOC_Item):
+               case MDOC_Item:
                        lt = LIST_item;
                        break;
-               case (MDOC_Tag):
+               case MDOC_Tag:
                        lt = LIST_tag;
                        break;
-               case (MDOC_Diag):
+               case MDOC_Diag:
                        lt = LIST_diag;
                        break;
-               case (MDOC_Hang):
+               case MDOC_Hang:
                        lt = LIST_hang;
                        break;
-               case (MDOC_Ohang):
+               case MDOC_Ohang:
                        lt = LIST_ohang;
                        break;
-               case (MDOC_Inset):
+               case MDOC_Inset:
                        lt = LIST_inset;
                        break;
-               case (MDOC_Column):
+               case MDOC_Column:
                        lt = LIST_column;
                        break;
                /* Set list arguments. */
-               case (MDOC_Compact):
+               case MDOC_Compact:
                        dup = n->norm->Bl.comp;
                        comp = 1;
                        break;
-               case (MDOC_Width):
+               case MDOC_Width:
                        /* NB: this can be empty! */
                        if (n->args->argv[i].sz) {
                                width = n->args->argv[i].value[0];
@@ -673,7 +678,7 @@ pre_bl(PRE_ARGS)
                        }
                        mdoc_nmsg(mdoc, n, MANDOCERR_IGNARGV);
                        break;
-               case (MDOC_Offset):
+               case MDOC_Offset:
                        /* NB: this can be empty! */
                        if (n->args->argv[i].sz) {
                                offs = n->args->argv[i].value[0];
@@ -709,19 +714,19 @@ pre_bl(PRE_ARGS)
                        n->norm->Bl.type = lt;
                        /* Set column information, too. */
                        if (LIST_column == lt) {
-                               n->norm->Bl.ncols = 
-                                       n->args->argv[i].sz;
+                               n->norm->Bl.ncols =
+                                   n->args->argv[i].sz;
                                n->norm->Bl.cols = (void *)
-                                       n->args->argv[i].value;
+                                   n->args->argv[i].value;
                        }
                }
 
                /* The list type should come first. */
 
                if (n->norm->Bl.type == LIST__NONE)
-                       if (n->norm->Bl.width || 
-                                       n->norm->Bl.offs || 
-                                       n->norm->Bl.comp)
+                       if (n->norm->Bl.width ||
+                           n->norm->Bl.offs ||
+                           n->norm->Bl.comp)
                                mdoc_nmsg(mdoc, n, MANDOCERR_LISTFIRST);
 
                continue;
@@ -734,7 +739,7 @@ pre_bl(PRE_ARGS)
                n->norm->Bl.type = LIST_item;
        }
 
-       /* 
+       /*
         * Validate the width field.  Some list types don't need width
         * types and should be warned about them.  Others should have it
         * and must also be warned.  Yet others have a default and need
@@ -742,31 +747,31 @@ pre_bl(PRE_ARGS)
         */
 
        switch (n->norm->Bl.type) {
-       case (LIST_tag):
+       case LIST_tag:
                if (NULL == n->norm->Bl.width)
                        mdoc_nmsg(mdoc, n, MANDOCERR_NOWIDTHARG);
                break;
-       case (LIST_column):
+       case LIST_column:
                /* FALLTHROUGH */
-       case (LIST_diag):
+       case LIST_diag:
                /* FALLTHROUGH */
-       case (LIST_ohang):
+       case LIST_ohang:
                /* FALLTHROUGH */
-       case (LIST_inset):
+       case LIST_inset:
                /* FALLTHROUGH */
-       case (LIST_item):
+       case LIST_item:
                if (n->norm->Bl.width)
                        mdoc_nmsg(mdoc, n, MANDOCERR_IGNARGV);
                break;
-       case (LIST_bullet):
+       case LIST_bullet:
                /* FALLTHROUGH */
-       case (LIST_dash):
+       case LIST_dash:
                /* FALLTHROUGH */
-       case (LIST_hyphen):
+       case LIST_hyphen:
                if (NULL == n->norm->Bl.width)
                        n->norm->Bl.width = "2n";
                break;
-       case (LIST_enum):
+       case LIST_enum:
                if (NULL == n->norm->Bl.width)
                        n->norm->Bl.width = "3n";
                break;
@@ -777,12 +782,11 @@ pre_bl(PRE_ARGS)
        return(1);
 }
 
-
 static int
 pre_bd(PRE_ARGS)
 {
        int               i, dup, comp;
-       enum mdoc_disp    dt;
+       enum mdoc_disp    dt;
        const char       *offs;
        struct mdoc_node *np;
 
@@ -799,32 +803,31 @@ pre_bd(PRE_ARGS)
                return(1);
        }
 
-       /* LINTED */
        for (i = 0; n->args && i < (int)n->args->argc; i++) {
                dt = DISP__NONE;
                dup = comp = 0;
                offs = NULL;
 
                switch (n->args->argv[i].arg) {
-               case (MDOC_Centred):
+               case MDOC_Centred:
                        dt = DISP_centred;
                        break;
-               case (MDOC_Ragged):
+               case MDOC_Ragged:
                        dt = DISP_ragged;
                        break;
-               case (MDOC_Unfilled):
+               case MDOC_Unfilled:
                        dt = DISP_unfilled;
                        break;
-               case (MDOC_Filled):
+               case MDOC_Filled:
                        dt = DISP_filled;
                        break;
-               case (MDOC_Literal):
+               case MDOC_Literal:
                        dt = DISP_literal;
                        break;
-               case (MDOC_File):
+               case MDOC_File:
                        mdoc_nmsg(mdoc, n, MANDOCERR_BADDISP);
                        return(0);
-               case (MDOC_Offset):
+               case MDOC_Offset:
                        /* NB: this can be empty! */
                        if (n->args->argv[i].sz) {
                                offs = n->args->argv[i].value[0];
@@ -833,7 +836,7 @@ pre_bd(PRE_ARGS)
                        }
                        mdoc_nmsg(mdoc, n, MANDOCERR_IGNARGV);
                        break;
-               case (MDOC_Compact):
+               case MDOC_Compact:
                        comp = 1;
                        dup = n->norm->Bd.comp;
                        break;
@@ -873,7 +876,6 @@ pre_bd(PRE_ARGS)
        return(1);
 }
 
-
 static int
 pre_ss(PRE_ARGS)
 {
@@ -883,7 +885,6 @@ pre_ss(PRE_ARGS)
        return(check_parent(mdoc, n, MDOC_Sh, MDOC_BODY));
 }
 
-
 static int
 pre_sh(PRE_ARGS)
 {
@@ -893,7 +894,6 @@ pre_sh(PRE_ARGS)
        return(check_parent(mdoc, n, MDOC_MAX, MDOC_ROOT));
 }
 
-
 static int
 pre_it(PRE_ARGS)
 {
@@ -904,7 +904,6 @@ pre_it(PRE_ARGS)
        return(check_parent(mdoc, n, MDOC_Bl, MDOC_BODY));
 }
 
-
 static int
 pre_an(PRE_ARGS)
 {
@@ -912,10 +911,10 @@ pre_an(PRE_ARGS)
 
        if (NULL == n->args)
                return(1);
-       
+
        for (i = 1; i < (int)n->args->argc; i++)
-               mdoc_pmsg(mdoc, n->args->argv[i].line, 
-                       n->args->argv[i].pos, MANDOCERR_IGNARGV);
+               mdoc_pmsg(mdoc, n->args->argv[i].line,
+                   n->args->argv[i].pos, MANDOCERR_IGNARGV);
 
        if (MDOC_Split == n->args->argv[0].arg)
                n->norm->An.auth = AUTH_split;
@@ -939,15 +938,27 @@ pre_std(PRE_ARGS)
        return(1);
 }
 
+static int
+pre_obsolete(PRE_ARGS)
+{
+
+       if (MDOC_ELEM == n->type || MDOC_BLOCK == n->type)
+               mandoc_msg(MANDOCERR_MACRO_OBS, mdoc->parse,
+                   n->line, n->pos, mdoc_macronames[n->tok]);
+       return(1);
+}
+
 static int
 pre_dt(PRE_ARGS)
 {
 
        if (NULL == mdoc->meta.date || mdoc->meta.os)
-               mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGOOO);
+               mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
+                   n->line, n->pos, "Dt");
 
        if (mdoc->meta.title)
-               mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGREP);
+               mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
+                   n->line, n->pos, "Dt");
 
        return(1);
 }
@@ -957,10 +968,12 @@ pre_os(PRE_ARGS)
 {
 
        if (NULL == mdoc->meta.title || NULL == mdoc->meta.date)
-               mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGOOO);
+               mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
+                   n->line, n->pos, "Os");
 
        if (mdoc->meta.os)
-               mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGREP);
+               mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
+                   n->line, n->pos, "Os");
 
        return(1);
 }
@@ -970,15 +983,16 @@ pre_dd(PRE_ARGS)
 {
 
        if (mdoc->meta.title || mdoc->meta.os)
-               mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGOOO);
+               mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
+                   n->line, n->pos, "Dd");
 
        if (mdoc->meta.date)
-               mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGREP);
+               mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
+                   n->line, n->pos, "Dd");
 
        return(1);
 }
 
-
 static int
 post_bf(POST_ARGS)
 {
@@ -996,7 +1010,7 @@ post_bf(POST_ARGS)
                        np = mdoc->last->pending->parent->head;
                } else if (MDOC_BLOCK != mdoc->last->type) {
                        np = mdoc->last->parent->head;
-               } else 
+               } else
                        np = mdoc->last->head;
 
                assert(np);
@@ -1009,9 +1023,9 @@ post_bf(POST_ARGS)
        assert(MDOC_BLOCK == np->parent->type);
        assert(MDOC_Bf == np->parent->tok);
 
-       /* 
+       /*
         * Cannot have both argument and parameter.
-        * If neither is specified, let it through with a warning. 
+        * If neither is specified, let it through with a warning.
         */
 
        if (np->parent->args && np->child) {
@@ -1023,7 +1037,7 @@ post_bf(POST_ARGS)
        }
 
        /* Extract argument into data. */
-       
+
        if (np->parent->args) {
                arg = np->parent->args->argv[0].arg;
                if (MDOC_Emphasis == arg)
@@ -1045,7 +1059,7 @@ post_bf(POST_ARGS)
                np->norm->Bf.font = FONT_Li;
        else if (0 == strcmp(np->child->string, "Sy"))
                np->norm->Bf.font = FONT_Sy;
-       else 
+       else
                mdoc_nmsg(mdoc, np, MANDOCERR_FONTTYPE);
 
        return(1);
@@ -1054,34 +1068,25 @@ post_bf(POST_ARGS)
 static int
 post_lb(POST_ARGS)
 {
-       const char      *p;
-       char            *buf;
-       size_t           sz;
+       struct mdoc_node        *n;
+       const char              *stdlibname;
+       char                    *libname;
 
        check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 1);
 
-       assert(mdoc->last->child);
-       assert(MDOC_TEXT == mdoc->last->child->type);
-
-       p = mdoc_a2lib(mdoc->last->child->string);
-
-       /* If lookup ok, replace with table value. */
+       n = mdoc->last->child;
 
-       if (p) {
-               free(mdoc->last->child->string);
-               mdoc->last->child->string = mandoc_strdup(p);
-               return(1);
-       }
+       assert(n);
+       assert(MDOC_TEXT == n->type);
 
-       /* If not, use "library ``xxxx''. */
+       if (NULL == (stdlibname = mdoc_a2lib(n->string)))
+               mandoc_asprintf(&libname,
+                   "library \\(lq%s\\(rq", n->string);
+       else
+               libname = mandoc_strdup(stdlibname);
 
-       sz = strlen(mdoc->last->child->string) +
-               2 + strlen("\\(lqlibrary\\(rq");
-       buf = mandoc_malloc(sz);
-       snprintf(buf, sz, "library \\(lq%s\\(rq", 
-                       mdoc->last->child->string);
-       free(mdoc->last->child->string);
-       mdoc->last->child->string = buf;
+       free(n->string);
+       n->string = libname;
        return(1);
 }
 
@@ -1094,7 +1099,6 @@ post_eoln(POST_ARGS)
        return(1);
 }
 
-
 static int
 post_vt(POST_ARGS)
 {
@@ -1110,43 +1114,27 @@ post_vt(POST_ARGS)
 
        if (MDOC_BODY != mdoc->last->type)
                return(1);
-       
+
        for (n = mdoc->last->child; n; n = n->next)
-               if (MDOC_TEXT != n->type) 
-                       mdoc_nmsg(mdoc, n, MANDOCERR_CHILD);
+               if (MDOC_TEXT != n->type)
+                       mandoc_msg(MANDOCERR_VT_CHILD, mdoc->parse,
+                           n->line, n->pos, mdoc_macronames[n->tok]);
 
        return(1);
 }
 
-
 static int
 post_nm(POST_ARGS)
 {
-       char             buf[BUFSIZ];
-       int              c;
 
        if (NULL != mdoc->meta.name)
                return(1);
 
-       /* Try to use our children for setting the meta name. */
-
-       if (NULL != mdoc->last->child) {
-               buf[0] = '\0';
-               c = concat(buf, mdoc->last->child, BUFSIZ);
-       } else
-               c = 0;
+       mdoc_deroff(&mdoc->meta.name, mdoc->last);
 
-       switch (c) {
-       case (-1):
-               mdoc_nmsg(mdoc, mdoc->last->child, MANDOCERR_MEM);
-               return(0);
-       case (0):
+       if (NULL == mdoc->meta.name) {
                mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NONAME);
                mdoc->meta.name = mandoc_strdup("UNKNOWN");
-               break;
-       default:
-               mdoc->meta.name = mandoc_strdup(buf);
-               break;
        }
        return(1);
 }
@@ -1154,7 +1142,7 @@ post_nm(POST_ARGS)
 static int
 post_literal(POST_ARGS)
 {
-       
+
        /*
         * The `Dl' (note "el" not "one") and `Bd' macros unset the
         * MDOC_LITERAL flag as they leave.  Note that `Bd' only sets
@@ -1181,37 +1169,37 @@ post_defaults(POST_ARGS)
 
        if (mdoc->last->child)
                return(1);
-       
+
        nn = mdoc->last;
        mdoc->next = MDOC_NEXT_CHILD;
 
        switch (nn->tok) {
-       case (MDOC_Ar):
+       case MDOC_Ar:
                if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "file"))
                        return(0);
                if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "..."))
                        return(0);
                break;
-       case (MDOC_At):
+       case MDOC_At:
                if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "AT&T"))
                        return(0);
                if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "UNIX"))
                        return(0);
                break;
-       case (MDOC_Li):
+       case MDOC_Li:
                if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, ""))
                        return(0);
                break;
-       case (MDOC_Pa):
+       case MDOC_Pa:
                /* FALLTHROUGH */
-       case (MDOC_Mt):
+       case MDOC_Mt:
                if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "~"))
                        return(0);
                break;
        default:
                abort();
                /* NOTREACHED */
-       } 
+       }
 
        mdoc->last = nn;
        return(1);
@@ -1220,37 +1208,28 @@ post_defaults(POST_ARGS)
 static int
 post_at(POST_ARGS)
 {
-       const char       *p, *q;
-       char             *buf;
-       size_t            sz;
+       struct mdoc_node        *n;
+       const char              *std_att;
+       char                    *att;
 
        /*
         * If we have a child, look it up in the standard keys.  If a
         * key exist, use that instead of the child; if it doesn't,
         * prefix "AT&T UNIX " to the existing data.
         */
-       
-       if (NULL == mdoc->last->child)
-               return(1);
 
-       assert(MDOC_TEXT == mdoc->last->child->type);
-       p = mdoc_a2att(mdoc->last->child->string);
+       if (NULL == (n = mdoc->last->child))
+               return(1);
 
-       if (p) {
-               free(mdoc->last->child->string);
-               mdoc->last->child->string = mandoc_strdup(p);
-       } else {
+       assert(MDOC_TEXT == n->type);
+       if (NULL == (std_att = mdoc_a2att(n->string))) {
                mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADATT);
-               p = "AT&T UNIX ";
-               q = mdoc->last->child->string;
-               sz = strlen(p) + strlen(q) + 1;
-               buf = mandoc_malloc(sz);
-               strlcpy(buf, p, sz);
-               strlcat(buf, q, sz);
-               free(mdoc->last->child->string);
-               mdoc->last->child->string = buf;
-       }
+               mandoc_asprintf(&att, "AT&T UNIX %s", n->string);
+       } else
+               att = mandoc_strdup(std_att);
 
+       free(n->string);
+       n->string = att;
        return(1);
 }
 
@@ -1269,6 +1248,22 @@ post_an(POST_ARGS)
        return(1);
 }
 
+static int
+post_en(POST_ARGS)
+{
+
+       if (MDOC_BLOCK == mdoc->last->type)
+               mdoc->last->norm->Es = mdoc->last_es;
+       return(1);
+}
+
+static int
+post_es(POST_ARGS)
+{
+
+       mdoc->last_es = mdoc->last;
+       return(1);
+}
 
 static int
 post_it(POST_ARGS)
@@ -1290,37 +1285,37 @@ post_it(POST_ARGS)
        }
 
        switch (lt) {
-       case (LIST_tag):
+       case LIST_tag:
                if (mdoc->last->head->child)
                        break;
                /* FIXME: give this a dummy value. */
                mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOARGS);
                break;
-       case (LIST_hang):
+       case LIST_hang:
                /* FALLTHROUGH */
-       case (LIST_ohang):
+       case LIST_ohang:
                /* FALLTHROUGH */
-       case (LIST_inset):
+       case LIST_inset:
                /* FALLTHROUGH */
-       case (LIST_diag):
+       case LIST_diag:
                if (NULL == mdoc->last->head->child)
                        mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOARGS);
                break;
-       case (LIST_bullet):
+       case LIST_bullet:
                /* FALLTHROUGH */
-       case (LIST_dash):
+       case LIST_dash:
                /* FALLTHROUGH */
-       case (LIST_enum):
+       case LIST_enum:
                /* FALLTHROUGH */
-       case (LIST_hyphen):
+       case LIST_hyphen:
                if (NULL == mdoc->last->body->child)
                        mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOBODY);
                /* FALLTHROUGH */
-       case (LIST_item):
+       case LIST_item:
                if (mdoc->last->head->child)
                        mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_ARGSLOST);
                break;
-       case (LIST_column):
+       case LIST_column:
                cols = (int)n->norm->Bl.ncols;
 
                assert(NULL == mdoc->last->head->child);
@@ -1339,9 +1334,9 @@ post_it(POST_ARGS)
                else
                        er = MANDOCERR_SYNTARGCOUNT;
 
-               mandoc_vmsg(er, mdoc->parse, mdoc->last->line, 
-                               mdoc->last->pos, 
-                               "columns == %d (have %d)", cols, i);
+               mandoc_vmsg(er, mdoc->parse,
+                   mdoc->last->line, mdoc->last->pos,
+                   "columns == %d (have %d)", cols, i);
                return(MANDOCERR_ARGCOUNT == er);
        default:
                break;
@@ -1351,7 +1346,7 @@ post_it(POST_ARGS)
 }
 
 static int
-post_bl_block(POST_ARGS) 
+post_bl_block(POST_ARGS)
 {
        struct mdoc_node *n, *ni, *nc;
 
@@ -1365,8 +1360,8 @@ post_bl_block(POST_ARGS)
 
        n = mdoc->last;
 
-       if (LIST_tag == n->norm->Bl.type && 
-                       NULL == n->norm->Bl.width) {
+       if (LIST_tag == n->norm->Bl.type &&
+           NULL == n->norm->Bl.width) {
                if ( ! post_bl_block_tag(mdoc))
                        return(0);
                assert(n->norm->Bl.width);
@@ -1382,23 +1377,28 @@ post_bl_block(POST_ARGS)
                nc = ni->body->last;
                while (NULL != nc) {
                        switch (nc->tok) {
-                       case (MDOC_Pp):
+                       case MDOC_Pp:
                                /* FALLTHROUGH */
-                       case (MDOC_Lp):
+                       case MDOC_Lp:
                                /* FALLTHROUGH */
-                       case (MDOC_br):
+                       case MDOC_br:
                                break;
                        default:
                                nc = NULL;
                                continue;
                        }
                        if (NULL == ni->next) {
-                               mdoc_nmsg(mdoc, nc, MANDOCERR_MOVEPAR);
+                               mandoc_msg(MANDOCERR_PAR_MOVE,
+                                   mdoc->parse, nc->line, nc->pos,
+                                   mdoc_macronames[nc->tok]);
                                if ( ! mdoc_node_relink(mdoc, nc))
                                        return(0);
                        } else if (0 == n->norm->Bl.comp &&
                            LIST_column != n->norm->Bl.type) {
-                               mdoc_nmsg(mdoc, nc, MANDOCERR_IGNPAR);
+                               mandoc_vmsg(MANDOCERR_PAR_SKIP,
+                                   mdoc->parse, nc->line, nc->pos,
+                                   "%s before It",
+                                   mdoc_macronames[nc->tok]);
                                mdoc_node_delete(mdoc, nc);
                        } else
                                break;
@@ -1415,7 +1415,7 @@ post_bl_block_width(POST_ARGS)
        int               i;
        enum mdoct        tok;
        struct mdoc_node *n;
-       char              buf[NUMSIZ];
+       char              buf[24];
 
        n = mdoc->last;
 
@@ -1441,13 +1441,13 @@ post_bl_block_width(POST_ARGS)
 
        assert(n->args);
 
-       for (i = 0; i < (int)n->args->argc; i++) 
+       for (i = 0; i < (int)n->args->argc; i++)
                if (MDOC_Width == n->args->argv[i].arg)
                        break;
 
        assert(i < (int)n->args->argc);
 
-       snprintf(buf, NUMSIZ, "%un", (unsigned int)width);
+       (void)snprintf(buf, sizeof(buf), "%un", (unsigned int)width);
        free(n->args->argv[i].value[0]);
        n->args->argv[i].value[0] = mandoc_strdup(buf);
 
@@ -1462,7 +1462,7 @@ post_bl_block_tag(POST_ARGS)
        struct mdoc_node *n, *nn;
        size_t            sz, ssz;
        int               i;
-       char              buf[NUMSIZ];
+       char              buf[24];
 
        /*
         * Calculate the -width for a `Bl -tag' list if it hasn't been
@@ -1493,11 +1493,11 @@ post_bl_block_tag(POST_ARGS)
                        sz = ssz;
 
                break;
-       } 
+       }
 
        /* Defaults to ten ens. */
 
-       snprintf(buf, NUMSIZ, "%un", (unsigned int)sz);
+       (void)snprintf(buf, sizeof(buf), "%un", (unsigned int)sz);
 
        /*
         * We have to dynamically add this to the macro's argument list.
@@ -1507,8 +1507,8 @@ post_bl_block_tag(POST_ARGS)
        assert(n->args);
        i = (int)(n->args->argc)++;
 
-       n->args->argv = mandoc_realloc(n->args->argv, 
-                       n->args->argc * sizeof(struct mdoc_argv));
+       n->args->argv = mandoc_reallocarray(n->args->argv,
+           n->args->argc, sizeof(struct mdoc_argv));
 
        n->args->argv[i].arg = MDOC_Width;
        n->args->argv[i].line = n->line;
@@ -1522,9 +1522,8 @@ post_bl_block_tag(POST_ARGS)
        return(1);
 }
 
-
 static int
-post_bl_head(POST_ARGS) 
+post_bl_head(POST_ARGS)
 {
        struct mdoc_node *np, *nn, *nnp;
        int               i, j;
@@ -1541,7 +1540,7 @@ post_bl_head(POST_ARGS)
 
        /* First, disallow both types and allow normal-form. */
 
-       /* 
+       /*
         * TODO: technically, we can accept both and just merge the two
         * lists, but I'll leave that for another day.
         */
@@ -1555,7 +1554,7 @@ post_bl_head(POST_ARGS)
        np = mdoc->last->parent;
        assert(np->args);
 
-       for (j = 0; j < (int)np->args->argc; j++) 
+       for (j = 0; j < (int)np->args->argc; j++)
                if (MDOC_Column == np->args->argv[j].arg)
                        break;
 
@@ -1569,8 +1568,8 @@ post_bl_head(POST_ARGS)
         */
 
        np->args->argv[j].sz = (size_t)mdoc->last->nchild;
-       np->args->argv[j].value = mandoc_malloc
-               ((size_t)mdoc->last->nchild * sizeof(char *));
+       np->args->argv[j].value = mandoc_reallocarray(NULL,
+           (size_t)mdoc->last->nchild, sizeof(char *));
 
        mdoc->last->norm->Bl.ncols = np->args->argv[j].sz;
        mdoc->last->norm->Bl.cols = (void *)np->args->argv[j].value;
@@ -1598,11 +1597,11 @@ post_bl(POST_ARGS)
 
        nbody = mdoc->last;
        switch (nbody->type) {
-       case (MDOC_BLOCK):
+       case MDOC_BLOCK:
                return(post_bl_block(mdoc));
-       case (MDOC_HEAD):
+       case MDOC_HEAD:
                return(post_bl_head(mdoc));
-       case (MDOC_BODY):
+       case MDOC_BODY:
                break;
        default:
                return(1);
@@ -1615,7 +1614,9 @@ post_bl(POST_ARGS)
                        continue;
                }
 
-               mdoc_nmsg(mdoc, nchild, MANDOCERR_CHILD);
+               mandoc_msg(MANDOCERR_BL_MOVE, mdoc->parse,
+                   nchild->line, nchild->pos,
+                   mdoc_macronames[nchild->tok]);
 
                /*
                 * Move the node out of the Bl block.
@@ -1667,11 +1668,12 @@ ebool(struct mdoc *mdoc)
 {
 
        if (NULL == mdoc->last->child) {
-               mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_MACROEMPTY);
-               mdoc_node_delete(mdoc, mdoc->last);
+               if (MDOC_Sm == mdoc->last->tok)
+                       mdoc->flags ^= MDOC_SMOFF;
                return(1);
        }
-       check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 1);
+
+       check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_LT, 2);
 
        assert(MDOC_TEXT == mdoc->last->child->type);
 
@@ -1693,34 +1695,31 @@ ebool(struct mdoc *mdoc)
 static int
 post_root(POST_ARGS)
 {
-       int               erc;
+       int               ret;
        struct mdoc_node *n;
 
-       erc = 0;
+       ret = 1;
 
        /* Check that we have a finished prologue. */
 
        if ( ! (MDOC_PBODY & mdoc->flags)) {
-               erc++;
+               ret = 0;
                mdoc_nmsg(mdoc, mdoc->first, MANDOCERR_NODOCPROLOG);
        }
 
        n = mdoc->first;
        assert(n);
-       
+
        /* Check that we begin with a proper `Sh'. */
 
-       if (NULL == n->child) {
-               erc++;
-               mdoc_nmsg(mdoc, n, MANDOCERR_NODOCBODY);
-       } else if (MDOC_BLOCK != n->child->type || 
-                       MDOC_Sh != n->child->tok) {
-               erc++;
-               /* Can this be lifted?  See rxdebug.1 for example. */
-               mdoc_nmsg(mdoc, n, MANDOCERR_NODOCBODY);
-       }
+       if (NULL == n->child)
+               mdoc_nmsg(mdoc, n, MANDOCERR_DOC_EMPTY);
+       else if (MDOC_Sh != n->child->tok)
+               mandoc_msg(MANDOCERR_SEC_BEFORE, mdoc->parse,
+                   n->child->line, n->child->pos,
+                   mdoc_macronames[n->child->tok]);
 
-       return(erc ? 0 : 1);
+       return(ret);
 }
 
 static int
@@ -1730,7 +1729,9 @@ post_st(POST_ARGS)
        const char               *p;
 
        if (NULL == (ch = mdoc->last->child)) {
-               mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_MACROEMPTY);
+               mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
+                   mdoc->last->line, mdoc->last->pos,
+                   mdoc_macronames[mdoc->last->tok]);
                mdoc_node_delete(mdoc, mdoc->last);
                return(1);
        }
@@ -1755,10 +1756,10 @@ post_rs(POST_ARGS)
        int               i, j;
 
        switch (mdoc->last->type) {
-       case (MDOC_HEAD):
+       case MDOC_HEAD:
                check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_EQ, 0);
                return(1);
-       case (MDOC_BODY):
+       case MDOC_BODY:
                if (mdoc->last->child)
                        break;
                check_count(mdoc, MDOC_BODY, CHECK_WARN, CHECK_GT, 0);
@@ -1787,7 +1788,8 @@ post_rs(POST_ARGS)
                }
 
                next = nn->next;
-               mdoc_nmsg(mdoc, nn, MANDOCERR_CHILD);
+               mandoc_msg(MANDOCERR_RS_SKIP, mdoc->parse,
+                   nn->line, nn->pos, mdoc_macronames[nn->tok]);
                mdoc_node_delete(mdoc, nn);
        }
 
@@ -1812,13 +1814,13 @@ post_rs(POST_ARGS)
                        if (rsord[i] == nn->tok)
                                break;
 
-               /* 
+               /*
                 * Remove `nn' from the chain.  This somewhat
                 * repeats mdoc_node_unlink(), but since we're
                 * just re-ordering, there's no need for the
                 * full unlink process.
                 */
-               
+
                if (NULL != (next = nn->next))
                        next->prev = nn->prev;
 
@@ -1827,7 +1829,7 @@ post_rs(POST_ARGS)
 
                nn->prev = nn->next = NULL;
 
-               /* 
+               /*
                 * Scan back until we reach a node that's
                 * ordered before `nn'.
                 */
@@ -1876,15 +1878,15 @@ post_hyph(POST_ARGS)
 
        n = mdoc->last;
        switch (n->type) {
-       case (MDOC_HEAD):
+       case MDOC_HEAD:
                if (MDOC_Sh == n->tok || MDOC_Ss == n->tok)
                        break;
                return(1);
-       case (MDOC_BODY):
+       case MDOC_BODY:
                if (MDOC_D1 == n->tok || MDOC_Nd == n->tok)
                        break;
                return(1);
-       case (MDOC_ELEM):
+       case MDOC_ELEM:
                break;
        default:
                return(1);
@@ -1894,7 +1896,7 @@ post_hyph(POST_ARGS)
                if (MDOC_TEXT != nch->type)
                        continue;
                cp = nch->string;
-               if (3 > strnlen(cp, 3))
+               if ('\0' == *cp)
                        continue;
                while ('\0' != *(++cp))
                        if ('-' == *cp &&
@@ -1910,7 +1912,7 @@ post_ns(POST_ARGS)
 {
 
        if (MDOC_LINE & mdoc->last->flags)
-               mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_IGNNS);
+               mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NS_SKIP);
        return(1);
 }
 
@@ -1941,7 +1943,8 @@ post_sh_body(POST_ARGS)
         */
 
        if (NULL == (n = mdoc->last->child)) {
-               mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADNAMESEC);
+               mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
+                   mdoc->last->line, mdoc->last->pos, "empty");
                return(1);
        }
 
@@ -1950,24 +1953,26 @@ post_sh_body(POST_ARGS)
                        continue;
                if (MDOC_TEXT == n->type)
                        continue;
-               mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADNAMESEC);
+               mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
+                   n->line, n->pos, mdoc_macronames[n->tok]);
        }
 
        assert(n);
        if (MDOC_BLOCK == n->type && MDOC_Nd == n->tok)
                return(1);
 
-       mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADNAMESEC);
+       mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
+           n->line, n->pos, mdoc_macronames[n->tok]);
        return(1);
 }
 
 static int
 post_sh_head(POST_ARGS)
 {
-       char             buf[BUFSIZ];
        struct mdoc_node *n;
+       const char      *goodsec;
+       char            *secname;
        enum mdoc_sec    sec;
-       int              c;
 
        /*
         * Process a new section.  Sections are either "named" or
@@ -1976,18 +1981,16 @@ post_sh_head(POST_ARGS)
         * manual sections.
         */
 
+       secname = NULL;
        sec = SEC_CUSTOM;
-       buf[0] = '\0';
-       if (-1 == (c = concat(buf, mdoc->last->child, BUFSIZ))) {
-               mdoc_nmsg(mdoc, mdoc->last->child, MANDOCERR_MEM);
-               return(0);
-       } else if (1 == c)
-               sec = a2sec(buf);
+       mdoc_deroff(&secname, mdoc->last);
+       sec = NULL == secname ? SEC_CUSTOM : a2sec(secname);
 
        /* The NAME should be first. */
 
        if (SEC_NAME != sec && SEC_NONE == mdoc->lastnamed)
-               mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NAMESECFIRST);
+               mandoc_msg(MANDOCERR_NAMESEC_FIRST, mdoc->parse,
+                   mdoc->last->line, mdoc->last->pos, secname);
 
        /* The SYNOPSIS gets special attention in other areas. */
 
@@ -2019,8 +2022,10 @@ post_sh_head(POST_ARGS)
 
        /* We don't care about custom sections after this. */
 
-       if (SEC_CUSTOM == sec)
+       if (SEC_CUSTOM == sec) {
+               free(secname);
                return(1);
+       }
 
        /*
         * Check whether our non-custom section is being repeated or is
@@ -2028,10 +2033,12 @@ post_sh_head(POST_ARGS)
         */
 
        if (sec == mdoc->lastnamed)
-               mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SECREP);
+               mandoc_msg(MANDOCERR_SEC_REP, mdoc->parse,
+                   mdoc->last->line, mdoc->last->pos, secname);
 
        if (sec < mdoc->lastnamed)
-               mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SECOOO);
+               mandoc_msg(MANDOCERR_SEC_ORDER, mdoc->parse,
+                   mdoc->last->line, mdoc->last->pos, secname);
 
        /* Mark the last named section. */
 
@@ -2041,25 +2048,37 @@ post_sh_head(POST_ARGS)
 
        assert(mdoc->meta.msec);
 
+       goodsec = NULL;
        switch (sec) {
-       case (SEC_RETURN_VALUES):
+       case SEC_ERRORS:
+               if (*mdoc->meta.msec == '4')
+                       break;
+               goodsec = "2, 3, 4, 9";
                /* FALLTHROUGH */
-       case (SEC_ERRORS):
+       case SEC_RETURN_VALUES:
                /* FALLTHROUGH */
-       case (SEC_LIBRARY):
+       case SEC_LIBRARY:
                if (*mdoc->meta.msec == '2')
                        break;
                if (*mdoc->meta.msec == '3')
                        break;
+               if (NULL == goodsec)
+                       goodsec = "2, 3, 9";
+               /* FALLTHROUGH */
+       case SEC_CONTEXT:
                if (*mdoc->meta.msec == '9')
                        break;
-               mandoc_msg(MANDOCERR_SECMSEC, mdoc->parse,
-                               mdoc->last->line, mdoc->last->pos, buf);
+               if (NULL == goodsec)
+                       goodsec = "9";
+               mandoc_vmsg(MANDOCERR_SEC_MSEC, mdoc->parse,
+                   mdoc->last->line, mdoc->last->pos,
+                   "%s for %s only", secname, goodsec);
                break;
        default:
                break;
        }
 
+       free(secname);
        return(1);
 }
 
@@ -2073,13 +2092,19 @@ post_ignpar(POST_ARGS)
 
        if (NULL != (np = mdoc->last->child))
                if (MDOC_Pp == np->tok || MDOC_Lp == np->tok) {
-                       mdoc_nmsg(mdoc, np, MANDOCERR_IGNPAR);
+                       mandoc_vmsg(MANDOCERR_PAR_SKIP,
+                           mdoc->parse, np->line, np->pos,
+                           "%s after %s", mdoc_macronames[np->tok],
+                           mdoc_macronames[mdoc->last->tok]);
                        mdoc_node_delete(mdoc, np);
                }
 
        if (NULL != (np = mdoc->last->last))
                if (MDOC_Pp == np->tok || MDOC_Lp == np->tok) {
-                       mdoc_nmsg(mdoc, np, MANDOCERR_IGNPAR);
+                       mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
+                           np->line, np->pos, "%s at the end of %s",
+                           mdoc_macronames[np->tok],
+                           mdoc_macronames[mdoc->last->tok]);
                        mdoc_node_delete(mdoc, np);
                }
 
@@ -2095,7 +2120,7 @@ pre_par(PRE_ARGS)
        if (MDOC_ELEM != n->type && MDOC_BLOCK != n->type)
                return(1);
 
-       /* 
+       /*
         * Don't allow prior `Lp' or `Pp' prior to a paragraph-type
         * block:  `Lp', `Pp', or non-compact `Bd' or `Bl'.
         */
@@ -2111,7 +2136,10 @@ pre_par(PRE_ARGS)
        if (MDOC_It == n->tok && n->parent->norm->Bl.comp)
                return(1);
 
-       mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_IGNPAR);
+       mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
+           mdoc->last->line, mdoc->last->pos,
+           "%s before %s", mdoc_macronames[mdoc->last->tok],
+           mdoc_macronames[n->tok]);
        mdoc_node_delete(mdoc, mdoc->last);
        return(1);
 }
@@ -2119,25 +2147,27 @@ pre_par(PRE_ARGS)
 static int
 post_par(POST_ARGS)
 {
+       struct mdoc_node *np;
 
        if (MDOC_ELEM != mdoc->last->type &&
            MDOC_BLOCK != mdoc->last->type)
                return(1);
 
-       if (NULL == mdoc->last->prev) {
-               if (MDOC_Sh != mdoc->last->parent->tok &&
-                   MDOC_Ss != mdoc->last->parent->tok)
+       if (NULL == (np = mdoc->last->prev)) {
+               np = mdoc->last->parent;
+               if (MDOC_Sh != np->tok && MDOC_Ss != np->tok)
                        return(1);
        } else {
-               if (MDOC_Pp != mdoc->last->prev->tok &&
-                   MDOC_Lp != mdoc->last->prev->tok &&
+               if (MDOC_Pp != np->tok && MDOC_Lp != np->tok &&
                    (MDOC_br != mdoc->last->tok ||
-                    (MDOC_sp != mdoc->last->prev->tok &&
-                     MDOC_br != mdoc->last->prev->tok)))
+                    (MDOC_sp != np->tok && MDOC_br != np->tok)))
                        return(1);
        }
 
-       mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_IGNPAR);
+       mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
+           mdoc->last->line, mdoc->last->pos,
+           "%s after %s", mdoc_macronames[mdoc->last->tok],
+           mdoc_macronames[np->tok]);
        mdoc_node_delete(mdoc, mdoc->last);
        return(1);
 }
@@ -2155,10 +2185,10 @@ pre_literal(PRE_ARGS)
         */
 
        switch (n->tok) {
-       case (MDOC_Dl):
+       case MDOC_Dl:
                mdoc->flags |= MDOC_LITERAL;
                break;
-       case (MDOC_Bd):
+       case MDOC_Bd:
                if (DISP_literal == n->norm->Bd.type)
                        mdoc->flags |= MDOC_LITERAL;
                if (DISP_unfilled == n->norm->Bd.type)
@@ -2168,16 +2198,15 @@ pre_literal(PRE_ARGS)
                abort();
                /* NOTREACHED */
        }
-       
+
        return(1);
 }
 
 static int
 post_dd(POST_ARGS)
 {
-       char              buf[DATESIZE];
        struct mdoc_node *n;
-       int               c;
+       char             *datestr;
 
        if (mdoc->meta.date)
                free(mdoc->meta.date);
@@ -2189,16 +2218,15 @@ post_dd(POST_ARGS)
                return(1);
        }
 
-       buf[0] = '\0';
-       if (-1 == (c = concat(buf, n->child, DATESIZE))) {
-               mdoc_nmsg(mdoc, n->child, MANDOCERR_MEM);
-               return(0);
+       datestr = NULL;
+       mdoc_deroff(&datestr, n);
+       if (mdoc->quick)
+               mdoc->meta.date = datestr;
+       else {
+               mdoc->meta.date = mandoc_normdate(mdoc->parse,
+                   datestr, n->line, n->pos);
+               free(datestr);
        }
-
-       assert(c);
-       mdoc->meta.date = mdoc->quick ? mandoc_strdup(buf) :
-           mandoc_normdate(mdoc->parse, buf, n->line, n->pos);
-
        return(1);
 }
 
@@ -2220,23 +2248,21 @@ post_dt(POST_ARGS)
 
        mdoc->meta.title = mdoc->meta.vol = mdoc->meta.arch = NULL;
 
-       /* First make all characters uppercase. */
+       /* First check that all characters are uppercase. */
 
        if (NULL != (nn = n->child))
                for (p = nn->string; *p; p++) {
                        if (toupper((unsigned char)*p) == *p)
                                continue;
-
-                       /* 
-                        * FIXME: don't be lazy: have this make all
-                        * characters be uppercase and just warn once.
-                        */
-                       mdoc_nmsg(mdoc, nn, MANDOCERR_UPPERCASE);
+                       mandoc_msg(MANDOCERR_TITLE_CASE,
+                           mdoc->parse, nn->line,
+                           nn->pos + (p - nn->string),
+                           nn->string);
                        break;
                }
 
-       /* Handles: `.Dt' 
-        *   --> title = unknown, volume = local, msec = 0, arch = NULL
+       /* Handles: `.Dt'
+        * title = unknown, volume = local, msec = 0, arch = NULL
         */
 
        if (NULL == (nn = n->child)) {
@@ -2248,12 +2274,12 @@ post_dt(POST_ARGS)
                return(1);
        }
 
-       /* Handles: `.Dt TITLE' 
-        *   --> title = TITLE, volume = local, msec = 0, arch = NULL
+       /* Handles: `.Dt TITLE'
+        * title = TITLE, volume = local, msec = 0, arch = NULL
         */
 
-       mdoc->meta.title = mandoc_strdup
-               ('\0' == nn->string[0] ? "UNKNOWN" : nn->string);
+       mdoc->meta.title = mandoc_strdup(
+           '\0' == nn->string[0] ? "UNKNOWN" : nn->string);
 
        if (NULL == (nn = nn->next)) {
                /* FIXME: warn about missing msec. */
@@ -2264,10 +2290,10 @@ post_dt(POST_ARGS)
        }
 
        /* Handles: `.Dt TITLE SEC'
-        *   --> title = TITLE, volume = SEC is msec ? 
-        *           format(msec) : SEC,
-        *       msec = SEC is msec ? atoi(msec) : 0,
-        *       arch = NULL
+        * title = TITLE,
+        * volume = SEC is msec ? format(msec) : SEC,
+        * msec = SEC is msec ? atoi(msec) : 0,
+        * arch = NULL
         */
 
        cp = mandoc_a2msec(nn->string);
@@ -2275,19 +2301,20 @@ post_dt(POST_ARGS)
                mdoc->meta.vol = mandoc_strdup(cp);
                mdoc->meta.msec = mandoc_strdup(nn->string);
        } else {
-               mdoc_nmsg(mdoc, n, MANDOCERR_BADMSEC);
+               mandoc_msg(MANDOCERR_MSEC_BAD, mdoc->parse,
+                   nn->line, nn->pos, nn->string);
                mdoc->meta.vol = mandoc_strdup(nn->string);
                mdoc->meta.msec = mandoc_strdup(nn->string);
-       } 
+       }
 
        if (NULL == (nn = nn->next))
                return(1);
 
        /* Handles: `.Dt TITLE SEC VOL'
-        *   --> title = TITLE, volume = VOL is vol ?
-        *       format(VOL) : 
-        *           VOL is arch ? format(arch) : 
-        *               VOL
+        * title = TITLE,
+        * volume = VOL is vol ? format(VOL) :
+        *          VOL is arch ? format(arch) :
+        *          VOL
         */
 
        cp = mdoc_a2vol(nn->string);
@@ -2297,12 +2324,13 @@ post_dt(POST_ARGS)
        } else {
                cp = mdoc_a2arch(nn->string);
                if (NULL == cp) {
-                       mdoc_nmsg(mdoc, nn, MANDOCERR_BADVOLARCH);
+                       mandoc_msg(MANDOCERR_ARCH_BAD, mdoc->parse,
+                           nn->line, nn->pos, nn->string);
                        free(mdoc->meta.vol);
                        mdoc->meta.vol = mandoc_strdup(nn->string);
-               } else 
+               } else
                        mdoc->meta.arch = mandoc_strdup(cp);
-       }       
+       }
 
        /* Ignore any subsequent parameters... */
        /* FIXME: warn about subsequent parameters. */
@@ -2331,7 +2359,7 @@ post_bx(POST_ARGS)
 {
        struct mdoc_node        *n;
 
-       /* 
+       /*
         * Make `Bx's second argument always start with an uppercase
         * letter.  Groff checks if it's an "accepted" term, but we just
         * uppercase blindly.
@@ -2339,8 +2367,7 @@ post_bx(POST_ARGS)
 
        n = mdoc->last->child;
        if (n && NULL != (n = n->next))
-               *n->string = (char)toupper
-                       ((unsigned char)*n->string);
+               *n->string = (char)toupper((unsigned char)*n->string);
 
        return(1);
 }
@@ -2348,12 +2375,11 @@ post_bx(POST_ARGS)
 static int
 post_os(POST_ARGS)
 {
-       struct mdoc_node *n;
-       char              buf[BUFSIZ];
-       int               c;
 #ifndef OSNAME
        struct utsname    utsname;
+       static char      *defbuf;
 #endif
+       struct mdoc_node *n;
 
        n = mdoc->last;
 
@@ -2364,51 +2390,32 @@ post_os(POST_ARGS)
         * 2. the -Ios=foo command line argument, if provided
         * 3. -DOSNAME="\"foo\"", if provided during compilation
         * 4. "sysname release" from uname(3)
-        */
+        */
 
        free(mdoc->meta.os);
+       mdoc->meta.os = NULL;
+       mdoc_deroff(&mdoc->meta.os, n);
+       if (mdoc->meta.os)
+               return(1);
 
-       buf[0] = '\0';
-       if (-1 == (c = concat(buf, n->child, BUFSIZ))) {
-               mdoc_nmsg(mdoc, n->child, MANDOCERR_MEM);
-               return(0);
+       if (mdoc->defos) {
+               mdoc->meta.os = mandoc_strdup(mdoc->defos);
+               return(1);
        }
 
-       assert(c);
-
-       if ('\0' == buf[0]) {
-               if (mdoc->defos) {
-                       mdoc->meta.os = mandoc_strdup(mdoc->defos);
-                       return(1);
-               }
 #ifdef OSNAME
-               if (strlcat(buf, OSNAME, BUFSIZ) >= BUFSIZ) {
-                       mdoc_nmsg(mdoc, n, MANDOCERR_MEM);
-                       return(0);
-               }
+       mdoc->meta.os = mandoc_strdup(OSNAME);
 #else /*!OSNAME */
+       if (NULL == defbuf) {
                if (-1 == uname(&utsname)) {
                        mdoc_nmsg(mdoc, n, MANDOCERR_UNAME);
-                        mdoc->meta.os = mandoc_strdup("UNKNOWN");
-                        return(post_prol(mdoc));
-                }
-
-               if (strlcat(buf, utsname.sysname, BUFSIZ) >= BUFSIZ) {
-                       mdoc_nmsg(mdoc, n, MANDOCERR_MEM);
-                       return(0);
-               }
-               if (strlcat(buf, " ", BUFSIZ) >= BUFSIZ) {
-                       mdoc_nmsg(mdoc, n, MANDOCERR_MEM);
-                       return(0);
-               }
-               if (strlcat(buf, utsname.release, BUFSIZ) >= BUFSIZ) {
-                       mdoc_nmsg(mdoc, n, MANDOCERR_MEM);
-                       return(0);
-               }
-#endif /*!OSNAME*/
+                       defbuf = mandoc_strdup("UNKNOWN");
+               } else
+                       mandoc_asprintf(&defbuf, "%s %s",
+                           utsname.sysname, utsname.release);
        }
-
-       mdoc->meta.os = mandoc_strdup(buf);
+       mdoc->meta.os = mandoc_strdup(defbuf);
+#endif /*!OSNAME*/
        return(1);
 }
 
@@ -2430,7 +2437,7 @@ post_std(POST_ARGS)
 
        if (NULL == mdoc->meta.name)
                return(1);
-       
+
        nn = n;
        mdoc->next = MDOC_NEXT_CHILD;
 
@@ -2441,35 +2448,12 @@ post_std(POST_ARGS)
        return(1);
 }
 
-/*
- * Concatenate a node, stopping at the first non-text.
- * Concatenation is separated by a single whitespace.  
- * Returns -1 on fatal (string overrun) error, 0 if child nodes were
- * encountered, 1 otherwise.
- */
-static int
-concat(char *p, const struct mdoc_node *n, size_t sz)
-{
-
-       for ( ; NULL != n; n = n->next) {
-               if (MDOC_TEXT != n->type) 
-                       return(0);
-               if ('\0' != p[0] && strlcat(p, " ", sz) >= sz)
-                       return(-1);
-               if (strlcat(p, n->string, sz) >= sz)
-                       return(-1);
-               concat(p, n->child, sz);
-       }
-
-       return(1);
-}
-
-static enum mdoc_sec 
+static enum mdoc_sec
 a2sec(const char *p)
 {
        int              i;
 
-       for (i = 0; i < (int)SEC__MAX; i++) 
+       for (i = 0; i < (int)SEC__MAX; i++)
                if (secnames[i] && 0 == strcmp(p, secnames[i]))
                        return((enum mdoc_sec)i);
 
@@ -2481,87 +2465,87 @@ macro2len(enum mdoct macro)
 {
 
        switch (macro) {
-       case(MDOC_Ad):
+       case MDOC_Ad:
                return(12);
-       case(MDOC_Ao):
+       case MDOC_Ao:
                return(12);
-       case(MDOC_An):
+       case MDOC_An:
                return(12);
-       case(MDOC_Aq):
+       case MDOC_Aq:
                return(12);
-       case(MDOC_Ar):
+       case MDOC_Ar:
                return(12);
-       case(MDOC_Bo):
+       case MDOC_Bo:
                return(12);
-       case(MDOC_Bq):
+       case MDOC_Bq:
                return(12);
-       case(MDOC_Cd):
+       case MDOC_Cd:
                return(12);
-       case(MDOC_Cm):
+       case MDOC_Cm:
                return(10);
-       case(MDOC_Do):
+       case MDOC_Do:
                return(10);
-       case(MDOC_Dq):
+       case MDOC_Dq:
                return(12);
-       case(MDOC_Dv):
+       case MDOC_Dv:
                return(12);
-       case(MDOC_Eo):
+       case MDOC_Eo:
                return(12);
-       case(MDOC_Em):
+       case MDOC_Em:
                return(10);
-       case(MDOC_Er):
+       case MDOC_Er:
                return(17);
-       case(MDOC_Ev):
+       case MDOC_Ev:
                return(15);
-       case(MDOC_Fa):
+       case MDOC_Fa:
                return(12);
-       case(MDOC_Fl):
+       case MDOC_Fl:
                return(10);
-       case(MDOC_Fo):
+       case MDOC_Fo:
                return(16);
-       case(MDOC_Fn):
+       case MDOC_Fn:
                return(16);
-       case(MDOC_Ic):
+       case MDOC_Ic:
                return(10);
-       case(MDOC_Li):
+       case MDOC_Li:
                return(16);
-       case(MDOC_Ms):
+       case MDOC_Ms:
                return(6);
-       case(MDOC_Nm):
+       case MDOC_Nm:
                return(10);
-       case(MDOC_No):
+       case MDOC_No:
                return(12);
-       case(MDOC_Oo):
+       case MDOC_Oo:
                return(10);
-       case(MDOC_Op):
+       case MDOC_Op:
                return(14);
-       case(MDOC_Pa):
+       case MDOC_Pa:
                return(32);
-       case(MDOC_Pf):
+       case MDOC_Pf:
                return(12);
-       case(MDOC_Po):
+       case MDOC_Po:
                return(12);
-       case(MDOC_Pq):
+       case MDOC_Pq:
                return(12);
-       case(MDOC_Ql):
+       case MDOC_Ql:
                return(16);
-       case(MDOC_Qo):
+       case MDOC_Qo:
                return(12);
-       case(MDOC_So):
+       case MDOC_So:
                return(12);
-       case(MDOC_Sq):
+       case MDOC_Sq:
                return(12);
-       case(MDOC_Sy):
+       case MDOC_Sy:
                return(6);
-       case(MDOC_Sx):
+       case MDOC_Sx:
                return(16);
-       case(MDOC_Tn):
+       case MDOC_Tn:
                return(10);
-       case(MDOC_Va):
+       case MDOC_Va:
                return(12);
-       case(MDOC_Vt):
+       case MDOC_Vt:
                return(12);
-       case(MDOC_Xr):
+       case MDOC_Xr:
                return(10);
        default:
                break;