1 /*      $Id: mdoc_validate.c,v 1.352 2017/08/02 13:29:04 schwarze Exp $ */
   2 /*
   3  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
   4  * Copyright (c) 2010-2017 Ingo Schwarze <schwarze@openbsd.org>
   5  * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
   6  *
   7  * Permission to use, copy, modify, and distribute this software for any
   8  * purpose with or without fee is hereby granted, provided that the above
   9  * copyright notice and this permission notice appear in all copies.
  10  *
  11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
  12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
  14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  18  */
  19 #include "config.h"
  20 
  21 #include <sys/types.h>
  22 #ifndef OSNAME
  23 #include <sys/utsname.h>
  24 #endif
  25 
  26 #include <assert.h>
  27 #include <ctype.h>
  28 #include <limits.h>
  29 #include <stdio.h>
  30 #include <stdlib.h>
  31 #include <string.h>
  32 #include <time.h>
  33 
  34 #include "mandoc_aux.h"
  35 #include "mandoc.h"
  36 #include "mandoc_xr.h"
  37 #include "roff.h"
  38 #include "mdoc.h"
  39 #include "libmandoc.h"
  40 #include "roff_int.h"
  41 #include "libmdoc.h"
  42 
  43 /* FIXME: .Bl -diag can't have non-text children in HEAD. */
  44 
  45 #define POST_ARGS struct roff_man *mdoc
  46 
  47 enum    check_ineq {
  48         CHECK_LT,
  49         CHECK_GT,
  50         CHECK_EQ
  51 };
  52 
  53 typedef void    (*v_post)(POST_ARGS);
  54 
  55 static  int      build_list(struct roff_man *, int);
  56 static  void     check_text(struct roff_man *, int, int, char *);
  57 static  void     check_argv(struct roff_man *,
  58                         struct roff_node *, struct mdoc_argv *);
  59 static  void     check_args(struct roff_man *, struct roff_node *);
  60 static  void     check_toptext(struct roff_man *, int, int, const char *);
  61 static  int      child_an(const struct roff_node *);
  62 static  size_t          macro2len(enum roff_tok);
  63 static  void     rewrite_macro2len(struct roff_man *, char **);
  64 static  int      similar(const char *, const char *);
  65 
  66 static  void     post_an(POST_ARGS);
  67 static  void     post_an_norm(POST_ARGS);
  68 static  void     post_at(POST_ARGS);
  69 static  void     post_bd(POST_ARGS);
  70 static  void     post_bf(POST_ARGS);
  71 static  void     post_bk(POST_ARGS);
  72 static  void     post_bl(POST_ARGS);
  73 static  void     post_bl_block(POST_ARGS);
  74 static  void     post_bl_head(POST_ARGS);
  75 static  void     post_bl_norm(POST_ARGS);
  76 static  void     post_bx(POST_ARGS);
  77 static  void     post_defaults(POST_ARGS);
  78 static  void     post_display(POST_ARGS);
  79 static  void     post_dd(POST_ARGS);
  80 static  void     post_delim(POST_ARGS);
  81 static  void     post_delim_nb(POST_ARGS);
  82 static  void     post_dt(POST_ARGS);
  83 static  void     post_en(POST_ARGS);
  84 static  void     post_es(POST_ARGS);
  85 static  void     post_eoln(POST_ARGS);
  86 static  void     post_ex(POST_ARGS);
  87 static  void     post_fa(POST_ARGS);
  88 static  void     post_fn(POST_ARGS);
  89 static  void     post_fname(POST_ARGS);
  90 static  void     post_fo(POST_ARGS);
  91 static  void     post_hyph(POST_ARGS);
  92 static  void     post_ignpar(POST_ARGS);
  93 static  void     post_it(POST_ARGS);
  94 static  void     post_lb(POST_ARGS);
  95 static  void     post_nd(POST_ARGS);
  96 static  void     post_nm(POST_ARGS);
  97 static  void     post_ns(POST_ARGS);
  98 static  void     post_obsolete(POST_ARGS);
  99 static  void     post_os(POST_ARGS);
 100 static  void     post_par(POST_ARGS);
 101 static  void     post_prevpar(POST_ARGS);
 102 static  void     post_root(POST_ARGS);
 103 static  void     post_rs(POST_ARGS);
 104 static  void     post_rv(POST_ARGS);
 105 static  void     post_sh(POST_ARGS);
 106 static  void     post_sh_head(POST_ARGS);
 107 static  void     post_sh_name(POST_ARGS);
 108 static  void     post_sh_see_also(POST_ARGS);
 109 static  void     post_sh_authors(POST_ARGS);
 110 static  void     post_sm(POST_ARGS);
 111 static  void     post_st(POST_ARGS);
 112 static  void     post_std(POST_ARGS);
 113 static  void     post_sx(POST_ARGS);
 114 static  void     post_useless(POST_ARGS);
 115 static  void     post_xr(POST_ARGS);
 116 static  void     post_xx(POST_ARGS);
 117 
 118 static  const v_post __mdoc_valids[MDOC_MAX - MDOC_Dd] = {
 119         post_dd,        /* Dd */
 120         post_dt,        /* Dt */
 121         post_os,        /* Os */
 122         post_sh,        /* Sh */
 123         post_ignpar,    /* Ss */
 124         post_par,       /* Pp */
 125         post_display,   /* D1 */
 126         post_display,   /* Dl */
 127         post_display,   /* Bd */
 128         NULL,           /* Ed */
 129         post_bl,        /* Bl */
 130         NULL,           /* El */
 131         post_it,        /* It */
 132         post_delim_nb,  /* Ad */
 133         post_an,        /* An */
 134         NULL,           /* Ap */
 135         post_defaults,  /* Ar */
 136         NULL,           /* Cd */
 137         post_delim_nb,  /* Cm */
 138         post_delim_nb,  /* Dv */
 139         post_delim_nb,  /* Er */
 140         post_delim_nb,  /* Ev */
 141         post_ex,        /* Ex */
 142         post_fa,        /* Fa */
 143         NULL,           /* Fd */
 144         post_delim_nb,  /* Fl */
 145         post_fn,        /* Fn */
 146         post_delim_nb,  /* Ft */
 147         post_delim_nb,  /* Ic */
 148         post_delim_nb,  /* In */
 149         post_defaults,  /* Li */
 150         post_nd,        /* Nd */
 151         post_nm,        /* Nm */
 152         post_delim_nb,  /* Op */
 153         post_obsolete,  /* Ot */
 154         post_defaults,  /* Pa */
 155         post_rv,        /* Rv */
 156         post_st,        /* St */
 157         post_delim_nb,  /* Va */
 158         post_delim_nb,  /* Vt */
 159         post_xr,        /* Xr */
 160         NULL,           /* %A */
 161         post_hyph,      /* %B */ /* FIXME: can be used outside Rs/Re. */
 162         NULL,           /* %D */
 163         NULL,           /* %I */
 164         NULL,           /* %J */
 165         post_hyph,      /* %N */
 166         post_hyph,      /* %O */
 167         NULL,           /* %P */
 168         post_hyph,      /* %R */
 169         post_hyph,      /* %T */ /* FIXME: can be used outside Rs/Re. */
 170         NULL,           /* %V */
 171         NULL,           /* Ac */
 172         post_delim_nb,  /* Ao */
 173         post_delim_nb,  /* Aq */
 174         post_at,        /* At */
 175         NULL,           /* Bc */
 176         post_bf,        /* Bf */
 177         post_delim_nb,  /* Bo */
 178         NULL,           /* Bq */
 179         post_xx,        /* Bsx */
 180         post_bx,        /* Bx */
 181         post_obsolete,  /* Db */
 182         NULL,           /* Dc */
 183         NULL,           /* Do */
 184         NULL,           /* Dq */
 185         NULL,           /* Ec */
 186         NULL,           /* Ef */
 187         post_delim_nb,  /* Em */
 188         NULL,           /* Eo */
 189         post_xx,        /* Fx */
 190         post_delim_nb,  /* Ms */
 191         NULL,           /* No */
 192         post_ns,        /* Ns */
 193         post_xx,        /* Nx */
 194         post_xx,        /* Ox */
 195         NULL,           /* Pc */
 196         NULL,           /* Pf */
 197         post_delim_nb,  /* Po */
 198         post_delim_nb,  /* Pq */
 199         NULL,           /* Qc */
 200         post_delim_nb,  /* Ql */
 201         post_delim_nb,  /* Qo */
 202         post_delim_nb,  /* Qq */
 203         NULL,           /* Re */
 204         post_rs,        /* Rs */
 205         NULL,           /* Sc */
 206         post_delim_nb,  /* So */
 207         post_delim_nb,  /* Sq */
 208         post_sm,        /* Sm */
 209         post_sx,        /* Sx */
 210         post_delim_nb,  /* Sy */
 211         post_useless,   /* Tn */
 212         post_xx,        /* Ux */
 213         NULL,           /* Xc */
 214         NULL,           /* Xo */
 215         post_fo,        /* Fo */
 216         NULL,           /* Fc */
 217         post_delim_nb,  /* Oo */
 218         NULL,           /* Oc */
 219         post_bk,        /* Bk */
 220         NULL,           /* Ek */
 221         post_eoln,      /* Bt */
 222         post_obsolete,  /* Hf */
 223         post_obsolete,  /* Fr */
 224         post_eoln,      /* Ud */
 225         post_lb,        /* Lb */
 226         post_par,       /* Lp */
 227         post_delim_nb,  /* Lk */
 228         post_defaults,  /* Mt */
 229         post_delim_nb,  /* Brq */
 230         post_delim_nb,  /* Bro */
 231         NULL,           /* Brc */
 232         NULL,           /* %C */
 233         post_es,        /* Es */
 234         post_en,        /* En */
 235         post_xx,        /* Dx */
 236         NULL,           /* %Q */
 237         NULL,           /* %U */
 238         NULL,           /* Ta */
 239 };
 240 static  const v_post *const mdoc_valids = __mdoc_valids - MDOC_Dd;
 241 
 242 #define RSORD_MAX 14 /* Number of `Rs' blocks. */
 243 
 244 static  const enum roff_tok rsord[RSORD_MAX] = {
 245         MDOC__A,
 246         MDOC__T,
 247         MDOC__B,
 248         MDOC__I,
 249         MDOC__J,
 250         MDOC__R,
 251         MDOC__N,
 252         MDOC__V,
 253         MDOC__U,
 254         MDOC__P,
 255         MDOC__Q,
 256         MDOC__C,
 257         MDOC__D,
 258         MDOC__O
 259 };
 260 
 261 static  const char * const secnames[SEC__MAX] = {
 262         NULL,
 263         "NAME",
 264         "LIBRARY",
 265         "SYNOPSIS",
 266         "DESCRIPTION",
 267         "CONTEXT",
 268         "IMPLEMENTATION NOTES",
 269         "RETURN VALUES",
 270         "ENVIRONMENT",
 271         "FILES",
 272         "EXIT STATUS",
 273         "EXAMPLES",
 274         "DIAGNOSTICS",
 275         "COMPATIBILITY",
 276         "ERRORS",
 277         "SEE ALSO",
 278         "STANDARDS",
 279         "HISTORY",
 280         "AUTHORS",
 281         "CAVEATS",
 282         "BUGS",
 283         "SECURITY CONSIDERATIONS",
 284         NULL
 285 };
 286 
 287 
 288 void
 289 mdoc_node_validate(struct roff_man *mdoc)
 290 {
 291         struct roff_node *n;
 292         const v_post *p;
 293 
 294         n = mdoc->last;
 295         mdoc->last = mdoc->last->child;
 296         while (mdoc->last != NULL) {
 297                 mdoc_node_validate(mdoc);
 298                 if (mdoc->last == n)
 299                         mdoc->last = mdoc->last->child;
 300                 else
 301                         mdoc->last = mdoc->last->next;
 302         }
 303 
 304         mdoc->last = n;
 305         mdoc->next = ROFF_NEXT_SIBLING;
 306         switch (n->type) {
 307         case ROFFT_TEXT:
 308                 if (n->sec != SEC_SYNOPSIS ||
 309                     (n->parent->tok != MDOC_Cd && n->parent->tok != MDOC_Fd))
 310                         check_text(mdoc, n->line, n->pos, n->string);
 311                 if (n->parent->tok == MDOC_It ||
 312                     (n->parent->type == ROFFT_BODY &&
 313                      (n->parent->tok == MDOC_Sh ||
 314                       n->parent->tok == MDOC_Ss)))
 315                         check_toptext(mdoc, n->line, n->pos, n->string);
 316                 break;
 317         case ROFFT_EQN:
 318         case ROFFT_TBL:
 319                 break;
 320         case ROFFT_ROOT:
 321                 post_root(mdoc);
 322                 break;
 323         default:
 324                 check_args(mdoc, mdoc->last);
 325 
 326                 /*
 327                  * Closing delimiters are not special at the
 328                  * beginning of a block, opening delimiters
 329                  * are not special at the end.
 330                  */
 331 
 332                 if (n->child != NULL)
 333                         n->child->flags &= ~NODE_DELIMC;
 334                 if (n->last != NULL)
 335                         n->last->flags &= ~NODE_DELIMO;
 336 
 337                 /* Call the macro's postprocessor. */
 338 
 339                 if (n->tok < ROFF_MAX) {
 340                         switch(n->tok) {
 341                         case ROFF_br:
 342                         case ROFF_sp:
 343                                 post_par(mdoc);
 344                                 break;
 345                         default:
 346                                 roff_validate(mdoc);
 347                                 break;
 348                         }
 349                         break;
 350                 }
 351 
 352                 assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
 353                 p = mdoc_valids + n->tok;
 354                 if (*p)
 355                         (*p)(mdoc);
 356                 if (mdoc->last == n)
 357                         mdoc_state(mdoc, n);
 358                 break;
 359         }
 360 }
 361 
 362 static void
 363 check_args(struct roff_man *mdoc, struct roff_node *n)
 364 {
 365         int              i;
 366 
 367         if (NULL == n->args)
 368                 return;
 369 
 370         assert(n->args->argc);
 371         for (i = 0; i < (int)n->args->argc; i++)
 372                 check_argv(mdoc, n, &n->args->argv[i]);
 373 }
 374 
 375 static void
 376 check_argv(struct roff_man *mdoc, struct roff_node *n, struct mdoc_argv *v)
 377 {
 378         int              i;
 379 
 380         for (i = 0; i < (int)v->sz; i++)
 381                 check_text(mdoc, v->line, v->pos, v->value[i]);
 382 }
 383 
 384 static void
 385 check_text(struct roff_man *mdoc, int ln, int pos, char *p)
 386 {
 387         char            *cp;
 388 
 389         if (MDOC_LITERAL & mdoc->flags)
 390                 return;
 391 
 392         for (cp = p; NULL != (p = strchr(p, '\t')); p++)
 393                 mandoc_msg(MANDOCERR_FI_TAB, mdoc->parse,
 394                     ln, pos + (int)(p - cp), NULL);
 395 }
 396 
 397 static void
 398 check_toptext(struct roff_man *mdoc, int ln, int pos, const char *p)
 399 {
 400         const char      *cp, *cpr;
 401 
 402         if (*p == '\0')
 403                 return;
 404 
 405         if ((cp = strstr(p, "OpenBSD")) != NULL)
 406                 mandoc_msg(MANDOCERR_BX, mdoc->parse,
 407                     ln, pos + (cp - p), "Ox");
 408         if ((cp = strstr(p, "NetBSD")) != NULL)
 409                 mandoc_msg(MANDOCERR_BX, mdoc->parse,
 410                     ln, pos + (cp - p), "Nx");
 411         if ((cp = strstr(p, "FreeBSD")) != NULL)
 412                 mandoc_msg(MANDOCERR_BX, mdoc->parse,
 413                     ln, pos + (cp - p), "Fx");
 414         if ((cp = strstr(p, "DragonFly")) != NULL)
 415                 mandoc_msg(MANDOCERR_BX, mdoc->parse,
 416                     ln, pos + (cp - p), "Dx");
 417 
 418         cp = p;
 419         while ((cp = strstr(cp + 1, "()")) != NULL) {
 420                 for (cpr = cp - 1; cpr >= p; cpr--)
 421                         if (*cpr != '_' && !isalnum((unsigned char)*cpr))
 422                                 break;
 423                 if ((cpr < p || *cpr == ' ') && cpr + 1 < cp) {
 424                         cpr++;
 425                         mandoc_vmsg(MANDOCERR_FUNC, mdoc->parse,
 426                             ln, pos + (cpr - p),
 427                             "%.*s()", (int)(cp - cpr), cpr);
 428                 }
 429         }
 430 }
 431 
 432 static void
 433 post_delim(POST_ARGS)
 434 {
 435         const struct roff_node  *nch;
 436         const char              *lc;
 437         enum mdelim              delim;
 438         enum roff_tok            tok;
 439 
 440         tok = mdoc->last->tok;
 441         nch = mdoc->last->last;
 442         if (nch == NULL || nch->type != ROFFT_TEXT)
 443                 return;
 444         lc = strchr(nch->string, '\0') - 1;
 445         if (lc < nch->string)
 446                 return;
 447         delim = mdoc_isdelim(lc);
 448         if (delim == DELIM_NONE || delim == DELIM_OPEN)
 449                 return;
 450         if (*lc == ')' && (tok == MDOC_Nd || tok == MDOC_Sh ||
 451             tok == MDOC_Ss || tok == MDOC_Fo))
 452                 return;
 453 
 454         mandoc_vmsg(MANDOCERR_DELIM, mdoc->parse,
 455             nch->line, nch->pos + (lc - nch->string),
 456             "%s%s %s", roff_name[tok],
 457             nch == mdoc->last->child ? "" : " ...", nch->string);
 458 }
 459 
 460 static void
 461 post_delim_nb(POST_ARGS)
 462 {
 463         const struct roff_node  *nch;
 464         const char              *lc, *cp;
 465         int                      nw;
 466         enum mdelim              delim;
 467         enum roff_tok            tok;
 468 
 469         /*
 470          * Find candidates: at least two bytes,
 471          * the last one a closing or middle delimiter.
 472          */
 473 
 474         tok = mdoc->last->tok;
 475         nch = mdoc->last->last;
 476         if (nch == NULL || nch->type != ROFFT_TEXT)
 477                 return;
 478         lc = strchr(nch->string, '\0') - 1;
 479         if (lc <= nch->string)
 480                 return;
 481         delim = mdoc_isdelim(lc);
 482         if (delim == DELIM_NONE || delim == DELIM_OPEN)
 483                 return;
 484 
 485         /*
 486          * Reduce false positives by allowing various cases.
 487          */
 488 
 489         /* Escaped delimiters. */
 490         if (lc > nch->string + 1 && lc[-2] == '\\' &&
 491             (lc[-1] == '&' || lc[-1] == 'e'))
 492                 return;
 493 
 494         /* Specific byte sequences. */
 495         switch (*lc) {
 496         case ')':
 497                 for (cp = lc; cp >= nch->string; cp--)
 498                         if (*cp == '(')
 499                                 return;
 500                 break;
 501         case '.':
 502                 if (lc > nch->string + 1 && lc[-2] == '.' && lc[-1] == '.')
 503                         return;
 504                 if (lc[-1] == '.')
 505                         return;
 506                 break;
 507         case ';':
 508                 if (tok == MDOC_Vt)
 509                         return;
 510                 break;
 511         case '?':
 512                 if (lc[-1] == '?')
 513                         return;
 514                 break;
 515         case ']':
 516                 for (cp = lc; cp >= nch->string; cp--)
 517                         if (*cp == '[')
 518                                 return;
 519                 break;
 520         case '|':
 521                 if (lc == nch->string + 1 && lc[-1] == '|')
 522                         return;
 523         default:
 524                 break;
 525         }
 526 
 527         /* Exactly two non-alphanumeric bytes. */
 528         if (lc == nch->string + 1 && !isalnum((unsigned char)lc[-1]))
 529                 return;
 530 
 531         /* At least three alphabetic words with a sentence ending. */
 532         if (strchr("!.:?", *lc) != NULL && (tok == MDOC_Em ||
 533             tok == MDOC_Li || tok == MDOC_Po || tok == MDOC_Pq ||
 534             tok == MDOC_Sy)) {
 535                 nw = 0;
 536                 for (cp = lc - 1; cp >= nch->string; cp--) {
 537                         if (*cp == ' ') {
 538                                 nw++;
 539                                 if (cp > nch->string && cp[-1] == ',')
 540                                         cp--;
 541                         } else if (isalpha((unsigned int)*cp)) {
 542                                 if (nw > 1)
 543                                         return;
 544                         } else
 545                                 break;
 546                 }
 547         }
 548 
 549         mandoc_vmsg(MANDOCERR_DELIM_NB, mdoc->parse,
 550             nch->line, nch->pos + (lc - nch->string),
 551             "%s%s %s", roff_name[tok],
 552             nch == mdoc->last->child ? "" : " ...", nch->string);
 553 }
 554 
 555 static void
 556 post_bl_norm(POST_ARGS)
 557 {
 558         struct roff_node *n;
 559         struct mdoc_argv *argv, *wa;
 560         int               i;
 561         enum mdocargt     mdoclt;
 562         enum mdoc_list    lt;
 563 
 564         n = mdoc->last->parent;
 565         n->norm->Bl.type = LIST__NONE;
 566 
 567         /*
 568          * First figure out which kind of list to use: bind ourselves to
 569          * the first mentioned list type and warn about any remaining
 570          * ones.  If we find no list type, we default to LIST_item.
 571          */
 572 
 573         wa = (n->args == NULL) ? NULL : n->args->argv;
 574         mdoclt = MDOC_ARG_MAX;
 575         for (i = 0; n->args && i < (int)n->args->argc; i++) {
 576                 argv = n->args->argv + i;
 577                 lt = LIST__NONE;
 578                 switch (argv->arg) {
 579                 /* Set list types. */
 580                 case MDOC_Bullet:
 581                         lt = LIST_bullet;
 582                         break;
 583                 case MDOC_Dash:
 584                         lt = LIST_dash;
 585                         break;
 586                 case MDOC_Enum:
 587                         lt = LIST_enum;
 588                         break;
 589                 case MDOC_Hyphen:
 590                         lt = LIST_hyphen;
 591                         break;
 592                 case MDOC_Item:
 593                         lt = LIST_item;
 594                         break;
 595                 case MDOC_Tag:
 596                         lt = LIST_tag;
 597                         break;
 598                 case MDOC_Diag:
 599                         lt = LIST_diag;
 600                         break;
 601                 case MDOC_Hang:
 602                         lt = LIST_hang;
 603                         break;
 604                 case MDOC_Ohang:
 605                         lt = LIST_ohang;
 606                         break;
 607                 case MDOC_Inset:
 608                         lt = LIST_inset;
 609                         break;
 610                 case MDOC_Column:
 611                         lt = LIST_column;
 612                         break;
 613                 /* Set list arguments. */
 614                 case MDOC_Compact:
 615                         if (n->norm->Bl.comp)
 616                                 mandoc_msg(MANDOCERR_ARG_REP,
 617                                     mdoc->parse, argv->line,
 618                                     argv->pos, "Bl -compact");
 619                         n->norm->Bl.comp = 1;
 620                         break;
 621                 case MDOC_Width:
 622                         wa = argv;
 623                         if (0 == argv->sz) {
 624                                 mandoc_msg(MANDOCERR_ARG_EMPTY,
 625                                     mdoc->parse, argv->line,
 626                                     argv->pos, "Bl -width");
 627                                 n->norm->Bl.width = "0n";
 628                                 break;
 629                         }
 630                         if (NULL != n->norm->Bl.width)
 631                                 mandoc_vmsg(MANDOCERR_ARG_REP,
 632                                     mdoc->parse, argv->line,
 633                                     argv->pos, "Bl -width %s",
 634                                     argv->value[0]);
 635                         rewrite_macro2len(mdoc, argv->value);
 636                         n->norm->Bl.width = argv->value[0];
 637                         break;
 638                 case MDOC_Offset:
 639                         if (0 == argv->sz) {
 640                                 mandoc_msg(MANDOCERR_ARG_EMPTY,
 641                                     mdoc->parse, argv->line,
 642                                     argv->pos, "Bl -offset");
 643                                 break;
 644                         }
 645                         if (NULL != n->norm->Bl.offs)
 646                                 mandoc_vmsg(MANDOCERR_ARG_REP,
 647                                     mdoc->parse, argv->line,
 648                                     argv->pos, "Bl -offset %s",
 649                                     argv->value[0]);
 650                         rewrite_macro2len(mdoc, argv->value);
 651                         n->norm->Bl.offs = argv->value[0];
 652                         break;
 653                 default:
 654                         continue;
 655                 }
 656                 if (LIST__NONE == lt)
 657                         continue;
 658                 mdoclt = argv->arg;
 659 
 660                 /* Check: multiple list types. */
 661 
 662                 if (LIST__NONE != n->norm->Bl.type) {
 663                         mandoc_vmsg(MANDOCERR_BL_REP,
 664                             mdoc->parse, n->line, n->pos,
 665                             "Bl -%s", mdoc_argnames[argv->arg]);
 666                         continue;
 667                 }
 668 
 669                 /* The list type should come first. */
 670 
 671                 if (n->norm->Bl.width ||
 672                     n->norm->Bl.offs ||
 673                     n->norm->Bl.comp)
 674                         mandoc_vmsg(MANDOCERR_BL_LATETYPE,
 675                             mdoc->parse, n->line, n->pos, "Bl -%s",
 676                             mdoc_argnames[n->args->argv[0].arg]);
 677 
 678                 n->norm->Bl.type = lt;
 679                 if (LIST_column == lt) {
 680                         n->norm->Bl.ncols = argv->sz;
 681                         n->norm->Bl.cols = (void *)argv->value;
 682                 }
 683         }
 684 
 685         /* Allow lists to default to LIST_item. */
 686 
 687         if (LIST__NONE == n->norm->Bl.type) {
 688                 mandoc_msg(MANDOCERR_BL_NOTYPE, mdoc->parse,
 689                     n->line, n->pos, "Bl");
 690                 n->norm->Bl.type = LIST_item;
 691                 mdoclt = MDOC_Item;
 692         }
 693 
 694         /*
 695          * Validate the width field.  Some list types don't need width
 696          * types and should be warned about them.  Others should have it
 697          * and must also be warned.  Yet others have a default and need
 698          * no warning.
 699          */
 700 
 701         switch (n->norm->Bl.type) {
 702         case LIST_tag:
 703                 if (n->norm->Bl.width == NULL)
 704                         mandoc_msg(MANDOCERR_BL_NOWIDTH, mdoc->parse,
 705                             n->line, n->pos, "Bl -tag");
 706                 break;
 707         case LIST_column:
 708         case LIST_diag:
 709         case LIST_ohang:
 710         case LIST_inset:
 711         case LIST_item:
 712                 if (n->norm->Bl.width != NULL)
 713                         mandoc_vmsg(MANDOCERR_BL_SKIPW, mdoc->parse,
 714                             wa->line, wa->pos, "Bl -%s",
 715                             mdoc_argnames[mdoclt]);
 716                 n->norm->Bl.width = NULL;
 717                 break;
 718         case LIST_bullet:
 719         case LIST_dash:
 720         case LIST_hyphen:
 721                 if (n->norm->Bl.width == NULL)
 722                         n->norm->Bl.width = "2n";
 723                 break;
 724         case LIST_enum:
 725                 if (n->norm->Bl.width == NULL)
 726                         n->norm->Bl.width = "3n";
 727                 break;
 728         default:
 729                 break;
 730         }
 731 }
 732 
 733 static void
 734 post_bd(POST_ARGS)
 735 {
 736         struct roff_node *n;
 737         struct mdoc_argv *argv;
 738         int               i;
 739         enum mdoc_disp    dt;
 740 
 741         n = mdoc->last;
 742         for (i = 0; n->args && i < (int)n->args->argc; i++) {
 743                 argv = n->args->argv + i;
 744                 dt = DISP__NONE;
 745 
 746                 switch (argv->arg) {
 747                 case MDOC_Centred:
 748                         dt = DISP_centered;
 749                         break;
 750                 case MDOC_Ragged:
 751                         dt = DISP_ragged;
 752                         break;
 753                 case MDOC_Unfilled:
 754                         dt = DISP_unfilled;
 755                         break;
 756                 case MDOC_Filled:
 757                         dt = DISP_filled;
 758                         break;
 759                 case MDOC_Literal:
 760                         dt = DISP_literal;
 761                         break;
 762                 case MDOC_File:
 763                         mandoc_msg(MANDOCERR_BD_FILE, mdoc->parse,
 764                             n->line, n->pos, NULL);
 765                         break;
 766                 case MDOC_Offset:
 767                         if (0 == argv->sz) {
 768                                 mandoc_msg(MANDOCERR_ARG_EMPTY,
 769                                     mdoc->parse, argv->line,
 770                                     argv->pos, "Bd -offset");
 771                                 break;
 772                         }
 773                         if (NULL != n->norm->Bd.offs)
 774                                 mandoc_vmsg(MANDOCERR_ARG_REP,
 775                                     mdoc->parse, argv->line,
 776                                     argv->pos, "Bd -offset %s",
 777                                     argv->value[0]);
 778                         rewrite_macro2len(mdoc, argv->value);
 779                         n->norm->Bd.offs = argv->value[0];
 780                         break;
 781                 case MDOC_Compact:
 782                         if (n->norm->Bd.comp)
 783                                 mandoc_msg(MANDOCERR_ARG_REP,
 784                                     mdoc->parse, argv->line,
 785                                     argv->pos, "Bd -compact");
 786                         n->norm->Bd.comp = 1;
 787                         break;
 788                 default:
 789                         abort();
 790                 }
 791                 if (DISP__NONE == dt)
 792                         continue;
 793 
 794                 if (DISP__NONE == n->norm->Bd.type)
 795                         n->norm->Bd.type = dt;
 796                 else
 797                         mandoc_vmsg(MANDOCERR_BD_REP,
 798                             mdoc->parse, n->line, n->pos,
 799                             "Bd -%s", mdoc_argnames[argv->arg]);
 800         }
 801 
 802         if (DISP__NONE == n->norm->Bd.type) {
 803                 mandoc_msg(MANDOCERR_BD_NOTYPE, mdoc->parse,
 804                     n->line, n->pos, "Bd");
 805                 n->norm->Bd.type = DISP_ragged;
 806         }
 807 }
 808 
 809 /*
 810  * Stand-alone line macros.
 811  */
 812 
 813 static void
 814 post_an_norm(POST_ARGS)
 815 {
 816         struct roff_node *n;
 817         struct mdoc_argv *argv;
 818         size_t   i;
 819 
 820         n = mdoc->last;
 821         if (n->args == NULL)
 822                 return;
 823 
 824         for (i = 1; i < n->args->argc; i++) {
 825                 argv = n->args->argv + i;
 826                 mandoc_vmsg(MANDOCERR_AN_REP,
 827                     mdoc->parse, argv->line, argv->pos,
 828                     "An -%s", mdoc_argnames[argv->arg]);
 829         }
 830 
 831         argv = n->args->argv;
 832         if (argv->arg == MDOC_Split)
 833                 n->norm->An.auth = AUTH_split;
 834         else if (argv->arg == MDOC_Nosplit)
 835                 n->norm->An.auth = AUTH_nosplit;
 836         else
 837                 abort();
 838 }
 839 
 840 static void
 841 post_eoln(POST_ARGS)
 842 {
 843         struct roff_node        *n;
 844 
 845         post_useless(mdoc);
 846         n = mdoc->last;
 847         if (n->child != NULL)
 848                 mandoc_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse, n->line,
 849                     n->pos, "%s %s", roff_name[n->tok], n->child->string);
 850 
 851         while (n->child != NULL)
 852                 roff_node_delete(mdoc, n->child);
 853 
 854         roff_word_alloc(mdoc, n->line, n->pos, n->tok == MDOC_Bt ?
 855             "is currently in beta test." : "currently under development.");
 856         mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
 857         mdoc->last = n;
 858 }
 859 
 860 static int
 861 build_list(struct roff_man *mdoc, int tok)
 862 {
 863         struct roff_node        *n;
 864         int                      ic;
 865 
 866         n = mdoc->last->next;
 867         for (ic = 1;; ic++) {
 868                 roff_elem_alloc(mdoc, n->line, n->pos, tok);
 869                 mdoc->last->flags |= NODE_NOSRC;
 870                 mdoc_node_relink(mdoc, n);
 871                 n = mdoc->last = mdoc->last->parent;
 872                 mdoc->next = ROFF_NEXT_SIBLING;
 873                 if (n->next == NULL)
 874                         return ic;
 875                 if (ic > 1 || n->next->next != NULL) {
 876                         roff_word_alloc(mdoc, n->line, n->pos, ",");
 877                         mdoc->last->flags |= NODE_DELIMC | NODE_NOSRC;
 878                 }
 879                 n = mdoc->last->next;
 880                 if (n->next == NULL) {
 881                         roff_word_alloc(mdoc, n->line, n->pos, "and");
 882                         mdoc->last->flags |= NODE_NOSRC;
 883                 }
 884         }
 885 }
 886 
 887 static void
 888 post_ex(POST_ARGS)
 889 {
 890         struct roff_node        *n;
 891         int                      ic;
 892 
 893         post_std(mdoc);
 894 
 895         n = mdoc->last;
 896         mdoc->next = ROFF_NEXT_CHILD;
 897         roff_word_alloc(mdoc, n->line, n->pos, "The");
 898         mdoc->last->flags |= NODE_NOSRC;
 899 
 900         if (mdoc->last->next != NULL)
 901                 ic = build_list(mdoc, MDOC_Nm);
 902         else if (mdoc->meta.name != NULL) {
 903                 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Nm);
 904                 mdoc->last->flags |= NODE_NOSRC;
 905                 roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
 906                 mdoc->last->flags |= NODE_NOSRC;
 907                 mdoc->last = mdoc->last->parent;
 908                 mdoc->next = ROFF_NEXT_SIBLING;
 909                 ic = 1;
 910         } else {
 911                 mandoc_msg(MANDOCERR_EX_NONAME, mdoc->parse,
 912                     n->line, n->pos, "Ex");
 913                 ic = 0;
 914         }
 915 
 916         roff_word_alloc(mdoc, n->line, n->pos,
 917             ic > 1 ? "utilities exit\\~0" : "utility exits\\~0");
 918         mdoc->last->flags |= NODE_NOSRC;
 919         roff_word_alloc(mdoc, n->line, n->pos,
 920             "on success, and\\~>0 if an error occurs.");
 921         mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
 922         mdoc->last = n;
 923 }
 924 
 925 static void
 926 post_lb(POST_ARGS)
 927 {
 928         struct roff_node        *n;
 929         const char              *p;
 930 
 931         post_delim_nb(mdoc);
 932 
 933         n = mdoc->last;
 934         assert(n->child->type == ROFFT_TEXT);
 935         mdoc->next = ROFF_NEXT_CHILD;
 936 
 937         if ((p = mdoc_a2lib(n->child->string)) != NULL) {
 938                 n->child->flags |= NODE_NOPRT;
 939                 roff_word_alloc(mdoc, n->line, n->pos, p);
 940                 mdoc->last->flags = NODE_NOSRC;
 941                 mdoc->last = n;
 942                 return;
 943         }
 944 
 945         mandoc_vmsg(MANDOCERR_LB_BAD, mdoc->parse, n->child->line,
 946             n->child->pos, "Lb %s", n->child->string);
 947 
 948         roff_word_alloc(mdoc, n->line, n->pos, "library");
 949         mdoc->last->flags = NODE_NOSRC;
 950         roff_word_alloc(mdoc, n->line, n->pos, "\\(Lq");
 951         mdoc->last->flags = NODE_DELIMO | NODE_NOSRC;
 952         mdoc->last = mdoc->last->next;
 953         roff_word_alloc(mdoc, n->line, n->pos, "\\(Rq");
 954         mdoc->last->flags = NODE_DELIMC | NODE_NOSRC;
 955         mdoc->last = n;
 956 }
 957 
 958 static void
 959 post_rv(POST_ARGS)
 960 {
 961         struct roff_node        *n;
 962         int                      ic;
 963 
 964         post_std(mdoc);
 965 
 966         n = mdoc->last;
 967         mdoc->next = ROFF_NEXT_CHILD;
 968         if (n->child != NULL) {
 969                 roff_word_alloc(mdoc, n->line, n->pos, "The");
 970                 mdoc->last->flags |= NODE_NOSRC;
 971                 ic = build_list(mdoc, MDOC_Fn);
 972                 roff_word_alloc(mdoc, n->line, n->pos,
 973                     ic > 1 ? "functions return" : "function returns");
 974                 mdoc->last->flags |= NODE_NOSRC;
 975                 roff_word_alloc(mdoc, n->line, n->pos,
 976                     "the value\\~0 if successful;");
 977         } else
 978                 roff_word_alloc(mdoc, n->line, n->pos, "Upon successful "
 979                     "completion, the value\\~0 is returned;");
 980         mdoc->last->flags |= NODE_NOSRC;
 981 
 982         roff_word_alloc(mdoc, n->line, n->pos, "otherwise "
 983             "the value\\~\\-1 is returned and the global variable");
 984         mdoc->last->flags |= NODE_NOSRC;
 985         roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Va);
 986         mdoc->last->flags |= NODE_NOSRC;
 987         roff_word_alloc(mdoc, n->line, n->pos, "errno");
 988         mdoc->last->flags |= NODE_NOSRC;
 989         mdoc->last = mdoc->last->parent;
 990         mdoc->next = ROFF_NEXT_SIBLING;
 991         roff_word_alloc(mdoc, n->line, n->pos,
 992             "is set to indicate the error.");
 993         mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
 994         mdoc->last = n;
 995 }
 996 
 997 static void
 998 post_std(POST_ARGS)
 999 {
1000         struct roff_node *n;
1001 
1002         post_delim(mdoc);
1003 
1004         n = mdoc->last;
1005         if (n->args && n->args->argc == 1)
1006                 if (n->args->argv[0].arg == MDOC_Std)
1007                         return;
1008 
1009         mandoc_msg(MANDOCERR_ARG_STD, mdoc->parse,
1010             n->line, n->pos, roff_name[n->tok]);
1011 }
1012 
1013 static void
1014 post_st(POST_ARGS)
1015 {
1016         struct roff_node         *n, *nch;
1017         const char               *p;
1018 
1019         n = mdoc->last;
1020         nch = n->child;
1021         assert(nch->type == ROFFT_TEXT);
1022 
1023         if ((p = mdoc_a2st(nch->string)) == NULL) {
1024                 mandoc_vmsg(MANDOCERR_ST_BAD, mdoc->parse,
1025                     nch->line, nch->pos, "St %s", nch->string);
1026                 roff_node_delete(mdoc, n);
1027                 return;
1028         }
1029 
1030         nch->flags |= NODE_NOPRT;
1031         mdoc->next = ROFF_NEXT_CHILD;
1032         roff_word_alloc(mdoc, nch->line, nch->pos, p);
1033         mdoc->last->flags |= NODE_NOSRC;
1034         mdoc->last= n;
1035 }
1036 
1037 static void
1038 post_obsolete(POST_ARGS)
1039 {
1040         struct roff_node *n;
1041 
1042         n = mdoc->last;
1043         if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK)
1044                 mandoc_msg(MANDOCERR_MACRO_OBS, mdoc->parse,
1045                     n->line, n->pos, roff_name[n->tok]);
1046 }
1047 
1048 static void
1049 post_useless(POST_ARGS)
1050 {
1051         struct roff_node *n;
1052 
1053         n = mdoc->last;
1054         mandoc_msg(MANDOCERR_MACRO_USELESS, mdoc->parse,
1055             n->line, n->pos, roff_name[n->tok]);
1056 }
1057 
1058 /*
1059  * Block macros.
1060  */
1061 
1062 static void
1063 post_bf(POST_ARGS)
1064 {
1065         struct roff_node *np, *nch;
1066 
1067         /*
1068          * Unlike other data pointers, these are "housed" by the HEAD
1069          * element, which contains the goods.
1070          */
1071 
1072         np = mdoc->last;
1073         if (np->type != ROFFT_HEAD)
1074                 return;
1075 
1076         assert(np->parent->type == ROFFT_BLOCK);
1077         assert(np->parent->tok == MDOC_Bf);
1078 
1079         /* Check the number of arguments. */
1080 
1081         nch = np->child;
1082         if (np->parent->args == NULL) {
1083                 if (nch == NULL) {
1084                         mandoc_msg(MANDOCERR_BF_NOFONT, mdoc->parse,
1085                             np->line, np->pos, "Bf");
1086                         return;
1087                 }
1088                 nch = nch->next;
1089         }
1090         if (nch != NULL)
1091                 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1092                     nch->line, nch->pos, "Bf ... %s", nch->string);
1093 
1094         /* Extract argument into data. */
1095 
1096         if (np->parent->args != NULL) {
1097                 switch (np->parent->args->argv[0].arg) {
1098                 case MDOC_Emphasis:
1099                         np->norm->Bf.font = FONT_Em;
1100                         break;
1101                 case MDOC_Literal:
1102                         np->norm->Bf.font = FONT_Li;
1103                         break;
1104                 case MDOC_Symbolic:
1105                         np->norm->Bf.font = FONT_Sy;
1106                         break;
1107                 default:
1108                         abort();
1109                 }
1110                 return;
1111         }
1112 
1113         /* Extract parameter into data. */
1114 
1115         if ( ! strcmp(np->child->string, "Em"))
1116                 np->norm->Bf.font = FONT_Em;
1117         else if ( ! strcmp(np->child->string, "Li"))
1118                 np->norm->Bf.font = FONT_Li;
1119         else if ( ! strcmp(np->child->string, "Sy"))
1120                 np->norm->Bf.font = FONT_Sy;
1121         else
1122                 mandoc_vmsg(MANDOCERR_BF_BADFONT, mdoc->parse,
1123                     np->child->line, np->child->pos,
1124                     "Bf %s", np->child->string);
1125 }
1126 
1127 static void
1128 post_fname(POST_ARGS)
1129 {
1130         const struct roff_node  *n;
1131         const char              *cp;
1132         size_t                   pos;
1133 
1134         n = mdoc->last->child;
1135         pos = strcspn(n->string, "()");
1136         cp = n->string + pos;
1137         if ( ! (cp[0] == '\0' || (cp[0] == '(' && cp[1] == '*')))
1138                 mandoc_msg(MANDOCERR_FN_PAREN, mdoc->parse,
1139                     n->line, n->pos + pos, n->string);
1140 }
1141 
1142 static void
1143 post_fn(POST_ARGS)
1144 {
1145 
1146         post_fname(mdoc);
1147         post_fa(mdoc);
1148 }
1149 
1150 static void
1151 post_fo(POST_ARGS)
1152 {
1153         const struct roff_node  *n;
1154 
1155         n = mdoc->last;
1156 
1157         if (n->type != ROFFT_HEAD)
1158                 return;
1159 
1160         if (n->child == NULL) {
1161                 mandoc_msg(MANDOCERR_FO_NOHEAD, mdoc->parse,
1162                     n->line, n->pos, "Fo");
1163                 return;
1164         }
1165         if (n->child != n->last) {
1166                 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1167                     n->child->next->line, n->child->next->pos,
1168                     "Fo ... %s", n->child->next->string);
1169                 while (n->child != n->last)
1170                         roff_node_delete(mdoc, n->last);
1171         } else
1172                 post_delim(mdoc);
1173 
1174         post_fname(mdoc);
1175 }
1176 
1177 static void
1178 post_fa(POST_ARGS)
1179 {
1180         const struct roff_node *n;
1181         const char *cp;
1182 
1183         for (n = mdoc->last->child; n != NULL; n = n->next) {
1184                 for (cp = n->string; *cp != '\0'; cp++) {
1185                         /* Ignore callbacks and alterations. */
1186                         if (*cp == '(' || *cp == '{')
1187                                 break;
1188                         if (*cp != ',')
1189                                 continue;
1190                         mandoc_msg(MANDOCERR_FA_COMMA, mdoc->parse,
1191                             n->line, n->pos + (cp - n->string),
1192                             n->string);
1193                         break;
1194                 }
1195         }
1196         post_delim_nb(mdoc);
1197 }
1198 
1199 static void
1200 post_nm(POST_ARGS)
1201 {
1202         struct roff_node        *n;
1203 
1204         n = mdoc->last;
1205 
1206         if (n->sec == SEC_NAME && n->child != NULL &&
1207             n->child->type == ROFFT_TEXT && mdoc->meta.msec != NULL)
1208                 mandoc_xr_add(mdoc->meta.msec, n->child->string, -1, -1);
1209 
1210         if (n->last != NULL &&
1211             (n->last->tok == MDOC_Pp ||
1212              n->last->tok == MDOC_Lp))
1213                 mdoc_node_relink(mdoc, n->last);
1214 
1215         if (mdoc->meta.name == NULL)
1216                 deroff(&mdoc->meta.name, n);
1217 
1218         if (mdoc->meta.name == NULL ||
1219             (mdoc->lastsec == SEC_NAME && n->child == NULL))
1220                 mandoc_msg(MANDOCERR_NM_NONAME, mdoc->parse,
1221                     n->line, n->pos, "Nm");
1222 
1223         switch (n->type) {
1224         case ROFFT_ELEM:
1225                 post_delim_nb(mdoc);
1226                 break;
1227         case ROFFT_HEAD:
1228                 post_delim(mdoc);
1229                 break;
1230         default:
1231                 return;
1232         }
1233 
1234         if ((n->child != NULL && n->child->type == ROFFT_TEXT) ||
1235             mdoc->meta.name == NULL)
1236                 return;
1237 
1238         mdoc->next = ROFF_NEXT_CHILD;
1239         roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
1240         mdoc->last->flags |= NODE_NOSRC;
1241         mdoc->last = n;
1242 }
1243 
1244 static void
1245 post_nd(POST_ARGS)
1246 {
1247         struct roff_node        *n;
1248 
1249         n = mdoc->last;
1250 
1251         if (n->type != ROFFT_BODY)
1252                 return;
1253 
1254         if (n->sec != SEC_NAME)
1255                 mandoc_msg(MANDOCERR_ND_LATE, mdoc->parse,
1256                     n->line, n->pos, "Nd");
1257 
1258         if (n->child == NULL)
1259                 mandoc_msg(MANDOCERR_ND_EMPTY, mdoc->parse,
1260                     n->line, n->pos, "Nd");
1261         else
1262                 post_delim(mdoc);
1263 
1264         post_hyph(mdoc);
1265 }
1266 
1267 static void
1268 post_display(POST_ARGS)
1269 {
1270         struct roff_node *n, *np;
1271 
1272         n = mdoc->last;
1273         switch (n->type) {
1274         case ROFFT_BODY:
1275                 if (n->end != ENDBODY_NOT) {
1276                         if (n->tok == MDOC_Bd &&
1277                             n->body->parent->args == NULL)
1278                                 roff_node_delete(mdoc, n);
1279                 } else if (n->child == NULL)
1280                         mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
1281                             n->line, n->pos, roff_name[n->tok]);
1282                 else if (n->tok == MDOC_D1)
1283                         post_hyph(mdoc);
1284                 break;
1285         case ROFFT_BLOCK:
1286                 if (n->tok == MDOC_Bd) {
1287                         if (n->args == NULL) {
1288                                 mandoc_msg(MANDOCERR_BD_NOARG,
1289                                     mdoc->parse, n->line, n->pos, "Bd");
1290                                 mdoc->next = ROFF_NEXT_SIBLING;
1291                                 while (n->body->child != NULL)
1292                                         mdoc_node_relink(mdoc,
1293                                             n->body->child);
1294                                 roff_node_delete(mdoc, n);
1295                                 break;
1296                         }
1297                         post_bd(mdoc);
1298                         post_prevpar(mdoc);
1299                 }
1300                 for (np = n->parent; np != NULL; np = np->parent) {
1301                         if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) {
1302                                 mandoc_vmsg(MANDOCERR_BD_NEST,
1303                                     mdoc->parse, n->line, n->pos,
1304                                     "%s in Bd", roff_name[n->tok]);
1305                                 break;
1306                         }
1307                 }
1308                 break;
1309         default:
1310                 break;
1311         }
1312 }
1313 
1314 static void
1315 post_defaults(POST_ARGS)
1316 {
1317         struct roff_node *nn;
1318 
1319         if (mdoc->last->child != NULL) {
1320                 post_delim_nb(mdoc);
1321                 return;
1322         }
1323 
1324         /*
1325          * The `Ar' defaults to "file ..." if no value is provided as an
1326          * argument; the `Mt' and `Pa' macros use "~"; the `Li' just
1327          * gets an empty string.
1328          */
1329 
1330         nn = mdoc->last;
1331         switch (nn->tok) {
1332         case MDOC_Ar:
1333                 mdoc->next = ROFF_NEXT_CHILD;
1334                 roff_word_alloc(mdoc, nn->line, nn->pos, "file");
1335                 mdoc->last->flags |= NODE_NOSRC;
1336                 roff_word_alloc(mdoc, nn->line, nn->pos, "...");
1337                 mdoc->last->flags |= NODE_NOSRC;
1338                 break;
1339         case MDOC_Pa:
1340         case MDOC_Mt:
1341                 mdoc->next = ROFF_NEXT_CHILD;
1342                 roff_word_alloc(mdoc, nn->line, nn->pos, "~");
1343                 mdoc->last->flags |= NODE_NOSRC;
1344                 break;
1345         default:
1346                 abort();
1347         }
1348         mdoc->last = nn;
1349 }
1350 
1351 static void
1352 post_at(POST_ARGS)
1353 {
1354         struct roff_node        *n, *nch;
1355         const char              *att;
1356 
1357         n = mdoc->last;
1358         nch = n->child;
1359 
1360         /*
1361          * If we have a child, look it up in the standard keys.  If a
1362          * key exist, use that instead of the child; if it doesn't,
1363          * prefix "AT&T UNIX " to the existing data.
1364          */
1365 
1366         att = NULL;
1367         if (nch != NULL && ((att = mdoc_a2att(nch->string)) == NULL))
1368                 mandoc_vmsg(MANDOCERR_AT_BAD, mdoc->parse,
1369                     nch->line, nch->pos, "At %s", nch->string);
1370 
1371         mdoc->next = ROFF_NEXT_CHILD;
1372         if (att != NULL) {
1373                 roff_word_alloc(mdoc, nch->line, nch->pos, att);
1374                 nch->flags |= NODE_NOPRT;
1375         } else
1376                 roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
1377         mdoc->last->flags |= NODE_NOSRC;
1378         mdoc->last = n;
1379 }
1380 
1381 static void
1382 post_an(POST_ARGS)
1383 {
1384         struct roff_node *np, *nch;
1385 
1386         post_an_norm(mdoc);
1387 
1388         np = mdoc->last;
1389         nch = np->child;
1390         if (np->norm->An.auth == AUTH__NONE) {
1391                 if (nch == NULL)
1392                         mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
1393                             np->line, np->pos, "An");
1394                 else
1395                         post_delim_nb(mdoc);
1396         } else if (nch != NULL)
1397                 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1398                     nch->line, nch->pos, "An ... %s", nch->string);
1399 }
1400 
1401 static void
1402 post_en(POST_ARGS)
1403 {
1404 
1405         post_obsolete(mdoc);
1406         if (mdoc->last->type == ROFFT_BLOCK)
1407                 mdoc->last->norm->Es = mdoc->last_es;
1408 }
1409 
1410 static void
1411 post_es(POST_ARGS)
1412 {
1413 
1414         post_obsolete(mdoc);
1415         mdoc->last_es = mdoc->last;
1416 }
1417 
1418 static void
1419 post_xx(POST_ARGS)
1420 {
1421         struct roff_node        *n;
1422         const char              *os;
1423         char                    *v;
1424 
1425         post_delim_nb(mdoc);
1426 
1427         n = mdoc->last;
1428         switch (n->tok) {
1429         case MDOC_Bsx:
1430                 os = "BSD/OS";
1431                 break;
1432         case MDOC_Dx:
1433                 os = "DragonFly";
1434                 break;
1435         case MDOC_Fx:
1436                 os = "FreeBSD";
1437                 break;
1438         case MDOC_Nx:
1439                 os = "NetBSD";
1440                 if (n->child == NULL)
1441                         break;
1442                 v = n->child->string;
1443                 if ((v[0] != '0' && v[0] != '1') || v[1] != '.' ||
1444                     v[2] < '0' || v[2] > '9' ||
1445                     v[3] < 'a' || v[3] > 'z' || v[4] != '\0')
1446                         break;
1447                 n->child->flags |= NODE_NOPRT;
1448                 mdoc->next = ROFF_NEXT_CHILD;
1449                 roff_word_alloc(mdoc, n->child->line, n->child->pos, v);
1450                 v = mdoc->last->string;
1451                 v[3] = toupper((unsigned char)v[3]);
1452                 mdoc->last->flags |= NODE_NOSRC;
1453                 mdoc->last = n;
1454                 break;
1455         case MDOC_Ox:
1456                 os = "OpenBSD";
1457                 break;
1458         case MDOC_Ux:
1459                 os = "UNIX";
1460                 break;
1461         default:
1462                 abort();
1463         }
1464         mdoc->next = ROFF_NEXT_CHILD;
1465         roff_word_alloc(mdoc, n->line, n->pos, os);
1466         mdoc->last->flags |= NODE_NOSRC;
1467         mdoc->last = n;
1468 }
1469 
1470 static void
1471 post_it(POST_ARGS)
1472 {
1473         struct roff_node *nbl, *nit, *nch;
1474         int               i, cols;
1475         enum mdoc_list    lt;
1476 
1477         post_prevpar(mdoc);
1478 
1479         nit = mdoc->last;
1480         if (nit->type != ROFFT_BLOCK)
1481                 return;
1482 
1483         nbl = nit->parent->parent;
1484         lt = nbl->norm->Bl.type;
1485 
1486         switch (lt) {
1487         case LIST_tag:
1488         case LIST_hang:
1489         case LIST_ohang:
1490         case LIST_inset:
1491         case LIST_diag:
1492                 if (nit->head->child == NULL)
1493                         mandoc_vmsg(MANDOCERR_IT_NOHEAD,
1494                             mdoc->parse, nit->line, nit->pos,
1495                             "Bl -%s It",
1496                             mdoc_argnames[nbl->args->argv[0].arg]);
1497                 break;
1498         case LIST_bullet:
1499         case LIST_dash:
1500         case LIST_enum:
1501         case LIST_hyphen:
1502                 if (nit->body == NULL || nit->body->child == NULL)
1503                         mandoc_vmsg(MANDOCERR_IT_NOBODY,
1504                             mdoc->parse, nit->line, nit->pos,
1505                             "Bl -%s It",
1506                             mdoc_argnames[nbl->args->argv[0].arg]);
1507                 /* FALLTHROUGH */
1508         case LIST_item:
1509                 if ((nch = nit->head->child) != NULL)
1510                         mandoc_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse,
1511                             nit->line, nit->pos, "It %s",
1512                             nch->string == NULL ? roff_name[nch->tok] :
1513                             nch->string);
1514                 break;
1515         case LIST_column:
1516                 cols = (int)nbl->norm->Bl.ncols;
1517 
1518                 assert(nit->head->child == NULL);
1519 
1520                 if (nit->head->next->child == NULL &&
1521                     nit->head->next->next == NULL) {
1522                         mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
1523                             nit->line, nit->pos, "It");
1524                         roff_node_delete(mdoc, nit);
1525                         break;
1526                 }
1527 
1528                 i = 0;
1529                 for (nch = nit->child; nch != NULL; nch = nch->next) {
1530                         if (nch->type != ROFFT_BODY)
1531                                 continue;
1532                         if (i++ && nch->flags & NODE_LINE)
1533                                 mandoc_msg(MANDOCERR_TA_LINE, mdoc->parse,
1534                                     nch->line, nch->pos, "Ta");
1535                 }
1536                 if (i < cols || i > cols + 1)
1537                         mandoc_vmsg(MANDOCERR_BL_COL,
1538                             mdoc->parse, nit->line, nit->pos,
1539                             "%d columns, %d cells", cols, i);
1540                 else if (nit->head->next->child != NULL &&
1541                     nit->head->next->child->line > nit->line)
1542                         mandoc_msg(MANDOCERR_IT_NOARG, mdoc->parse,
1543                             nit->line, nit->pos, "Bl -column It");
1544                 break;
1545         default:
1546                 abort();
1547         }
1548 }
1549 
1550 static void
1551 post_bl_block(POST_ARGS)
1552 {
1553         struct roff_node *n, *ni, *nc;
1554 
1555         post_prevpar(mdoc);
1556 
1557         n = mdoc->last;
1558         for (ni = n->body->child; ni != NULL; ni = ni->next) {
1559                 if (ni->body == NULL)
1560                         continue;
1561                 nc = ni->body->last;
1562                 while (nc != NULL) {
1563                         switch (nc->tok) {
1564                         case MDOC_Pp:
1565                         case MDOC_Lp:
1566                         case ROFF_br:
1567                                 break;
1568                         default:
1569                                 nc = NULL;
1570                                 continue;
1571                         }
1572                         if (ni->next == NULL) {
1573                                 mandoc_msg(MANDOCERR_PAR_MOVE,
1574                                     mdoc->parse, nc->line, nc->pos,
1575                                     roff_name[nc->tok]);
1576                                 mdoc_node_relink(mdoc, nc);
1577                         } else if (n->norm->Bl.comp == 0 &&
1578                             n->norm->Bl.type != LIST_column) {
1579                                 mandoc_vmsg(MANDOCERR_PAR_SKIP,
1580                                     mdoc->parse, nc->line, nc->pos,
1581                                     "%s before It", roff_name[nc->tok]);
1582                                 roff_node_delete(mdoc, nc);
1583                         } else
1584                                 break;
1585                         nc = ni->body->last;
1586                 }
1587         }
1588 }
1589 
1590 /*
1591  * If the argument of -offset or -width is a macro,
1592  * replace it with the associated default width.
1593  */
1594 static void
1595 rewrite_macro2len(struct roff_man *mdoc, char **arg)
1596 {
1597         size_t            width;
1598         enum roff_tok     tok;
1599 
1600         if (*arg == NULL)
1601                 return;
1602         else if ( ! strcmp(*arg, "Ds"))
1603                 width = 6;
1604         else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) == TOKEN_NONE)
1605                 return;
1606         else
1607                 width = macro2len(tok);
1608 
1609         free(*arg);
1610         mandoc_asprintf(arg, "%zun", width);
1611 }
1612 
1613 static void
1614 post_bl_head(POST_ARGS)
1615 {
1616         struct roff_node *nbl, *nh, *nch, *nnext;
1617         struct mdoc_argv *argv;
1618         int               i, j;
1619 
1620         post_bl_norm(mdoc);
1621 
1622         nh = mdoc->last;
1623         if (nh->norm->Bl.type != LIST_column) {
1624                 if ((nch = nh->child) == NULL)
1625                         return;
1626                 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1627                     nch->line, nch->pos, "Bl ... %s", nch->string);
1628                 while (nch != NULL) {
1629                         roff_node_delete(mdoc, nch);
1630                         nch = nh->child;
1631                 }
1632                 return;
1633         }
1634 
1635         /*
1636          * Append old-style lists, where the column width specifiers
1637          * trail as macro parameters, to the new-style ("normal-form")
1638          * lists where they're argument values following -column.
1639          */
1640 
1641         if (nh->child == NULL)
1642                 return;
1643 
1644         nbl = nh->parent;
1645         for (j = 0; j < (int)nbl->args->argc; j++)
1646                 if (nbl->args->argv[j].arg == MDOC_Column)
1647                         break;
1648 
1649         assert(j < (int)nbl->args->argc);
1650 
1651         /*
1652          * Accommodate for new-style groff column syntax.  Shuffle the
1653          * child nodes, all of which must be TEXT, as arguments for the
1654          * column field.  Then, delete the head children.
1655          */
1656 
1657         argv = nbl->args->argv + j;
1658         i = argv->sz;
1659         for (nch = nh->child; nch != NULL; nch = nch->next)
1660                 argv->sz++;
1661         argv->value = mandoc_reallocarray(argv->value,
1662             argv->sz, sizeof(char *));
1663 
1664         nh->norm->Bl.ncols = argv->sz;
1665         nh->norm->Bl.cols = (void *)argv->value;
1666 
1667         for (nch = nh->child; nch != NULL; nch = nnext) {
1668                 argv->value[i++] = nch->string;
1669                 nch->string = NULL;
1670                 nnext = nch->next;
1671                 roff_node_delete(NULL, nch);
1672         }
1673         nh->child = NULL;
1674 }
1675 
1676 static void
1677 post_bl(POST_ARGS)
1678 {
1679         struct roff_node        *nparent, *nprev; /* of the Bl block */
1680         struct roff_node        *nblock, *nbody;  /* of the Bl */
1681         struct roff_node        *nchild, *nnext;  /* of the Bl body */
1682         const char              *prev_Er;
1683         int                      order;
1684 
1685         nbody = mdoc->last;
1686         switch (nbody->type) {
1687         case ROFFT_BLOCK:
1688                 post_bl_block(mdoc);
1689                 return;
1690         case ROFFT_HEAD:
1691                 post_bl_head(mdoc);
1692                 return;
1693         case ROFFT_BODY:
1694                 break;
1695         default:
1696                 return;
1697         }
1698         if (nbody->end != ENDBODY_NOT)
1699                 return;
1700 
1701         nchild = nbody->child;
1702         if (nchild == NULL) {
1703                 mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
1704                     nbody->line, nbody->pos, "Bl");
1705                 return;
1706         }
1707         while (nchild != NULL) {
1708                 nnext = nchild->next;
1709                 if (nchild->tok == MDOC_It ||
1710                     (nchild->tok == MDOC_Sm &&
1711                      nnext != NULL && nnext->tok == MDOC_It)) {
1712                         nchild = nnext;
1713                         continue;
1714                 }
1715 
1716                 /*
1717                  * In .Bl -column, the first rows may be implicit,
1718                  * that is, they may not start with .It macros.
1719                  * Such rows may be followed by nodes generated on the
1720                  * roff level, for example .TS, which cannot be moved
1721                  * out of the list.  In that case, wrap such roff nodes
1722                  * into an implicit row.
1723                  */
1724 
1725                 if (nchild->prev != NULL) {
1726                         mdoc->last = nchild;
1727                         mdoc->next = ROFF_NEXT_SIBLING;
1728                         roff_block_alloc(mdoc, nchild->line,
1729                             nchild->pos, MDOC_It);
1730                         roff_head_alloc(mdoc, nchild->line,
1731                             nchild->pos, MDOC_It);
1732                         mdoc->next = ROFF_NEXT_SIBLING;
1733                         roff_body_alloc(mdoc, nchild->line,
1734                             nchild->pos, MDOC_It);
1735                         while (nchild->tok != MDOC_It) {
1736                                 mdoc_node_relink(mdoc, nchild);
1737                                 if ((nchild = nnext) == NULL)
1738                                         break;
1739                                 nnext = nchild->next;
1740                                 mdoc->next = ROFF_NEXT_SIBLING;
1741                         }
1742                         mdoc->last = nbody;
1743                         continue;
1744                 }
1745 
1746                 mandoc_msg(MANDOCERR_BL_MOVE, mdoc->parse,
1747                     nchild->line, nchild->pos, roff_name[nchild->tok]);
1748 
1749                 /*
1750                  * Move the node out of the Bl block.
1751                  * First, collect all required node pointers.
1752                  */
1753 
1754                 nblock  = nbody->parent;
1755                 nprev   = nblock->prev;
1756                 nparent = nblock->parent;
1757 
1758                 /*
1759                  * Unlink this child.
1760                  */
1761 
1762                 nbody->child = nnext;
1763                 if (nnext == NULL)
1764                         nbody->last  = NULL;
1765                 else
1766                         nnext->prev = NULL;
1767 
1768                 /*
1769                  * Relink this child.
1770                  */
1771 
1772                 nchild->parent = nparent;
1773                 nchild->prev   = nprev;
1774                 nchild->next   = nblock;
1775 
1776                 nblock->prev = nchild;
1777                 if (nprev == NULL)
1778                         nparent->child = nchild;
1779                 else
1780                         nprev->next = nchild;
1781 
1782                 nchild = nnext;
1783         }
1784 
1785         if (mdoc->meta.os_e != MANDOC_OS_NETBSD)
1786                 return;
1787 
1788         prev_Er = NULL;
1789         for (nchild = nbody->child; nchild != NULL; nchild = nchild->next) {
1790                 if (nchild->tok != MDOC_It)
1791                         continue;
1792                 if ((nnext = nchild->head->child) == NULL)
1793                         continue;
1794                 if (nnext->type == ROFFT_BLOCK)
1795                         nnext = nnext->body->child;
1796                 if (nnext == NULL || nnext->tok != MDOC_Er)
1797                         continue;
1798                 nnext = nnext->child;
1799                 if (prev_Er != NULL) {
1800                         order = strcmp(prev_Er, nnext->string);
1801                         if (order > 0)
1802                                 mandoc_vmsg(MANDOCERR_ER_ORDER,
1803                                     mdoc->parse, nnext->line, nnext->pos,
1804                                     "Er %s %s (NetBSD)",
1805                                     prev_Er, nnext->string);
1806                         else if (order == 0)
1807                                 mandoc_vmsg(MANDOCERR_ER_REP,
1808                                     mdoc->parse, nnext->line, nnext->pos,
1809                                     "Er %s (NetBSD)", prev_Er);
1810                 }
1811                 prev_Er = nnext->string;
1812         }
1813 }
1814 
1815 static void
1816 post_bk(POST_ARGS)
1817 {
1818         struct roff_node        *n;
1819 
1820         n = mdoc->last;
1821 
1822         if (n->type == ROFFT_BLOCK && n->body->child == NULL) {
1823                 mandoc_msg(MANDOCERR_BLK_EMPTY,
1824                     mdoc->parse, n->line, n->pos, "Bk");
1825                 roff_node_delete(mdoc, n);
1826         }
1827 }
1828 
1829 static void
1830 post_sm(POST_ARGS)
1831 {
1832         struct roff_node        *nch;
1833 
1834         nch = mdoc->last->child;
1835 
1836         if (nch == NULL) {
1837                 mdoc->flags ^= MDOC_SMOFF;
1838                 return;
1839         }
1840 
1841         assert(nch->type == ROFFT_TEXT);
1842 
1843         if ( ! strcmp(nch->string, "on")) {
1844                 mdoc->flags &= ~MDOC_SMOFF;
1845                 return;
1846         }
1847         if ( ! strcmp(nch->string, "off")) {
1848                 mdoc->flags |= MDOC_SMOFF;
1849                 return;
1850         }
1851 
1852         mandoc_vmsg(MANDOCERR_SM_BAD,
1853             mdoc->parse, nch->line, nch->pos,
1854             "%s %s", roff_name[mdoc->last->tok], nch->string);
1855         mdoc_node_relink(mdoc, nch);
1856         return;
1857 }
1858 
1859 static void
1860 post_root(POST_ARGS)
1861 {
1862         const char *openbsd_arch[] = {
1863                 "alpha", "amd64", "arm64", "armv7", "hppa", "i386",
1864                 "landisk", "loongson", "luna88k", "macppc", "mips64",
1865                 "octeon", "sgi", "socppc", "sparc64", NULL
1866         };
1867         const char *netbsd_arch[] = {
1868                 "acorn26", "acorn32", "algor", "alpha", "amiga",
1869                 "arc", "atari",
1870                 "bebox", "cats", "cesfic", "cobalt", "dreamcast",
1871                 "emips", "evbarm", "evbmips", "evbppc", "evbsh3", "evbsh5",
1872                 "hp300", "hpcarm", "hpcmips", "hpcsh", "hppa",
1873                 "i386", "ibmnws", "luna68k",
1874                 "mac68k", "macppc", "mipsco", "mmeye", "mvme68k", "mvmeppc",
1875                 "netwinder", "news68k", "newsmips", "next68k",
1876                 "pc532", "playstation2", "pmax", "pmppc", "prep",
1877                 "sandpoint", "sbmips", "sgimips", "shark",
1878                 "sparc", "sparc64", "sun2", "sun3",
1879                 "vax", "walnut", "x68k", "x86", "x86_64", "xen", NULL
1880         };
1881         const char **arches[] = { NULL, netbsd_arch, openbsd_arch };
1882 
1883         struct roff_node *n;
1884         const char **arch;
1885 
1886         /* Add missing prologue data. */
1887 
1888         if (mdoc->meta.date == NULL)
1889                 mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
1890                     mandoc_normdate(mdoc, NULL, 0, 0);
1891 
1892         if (mdoc->meta.title == NULL) {
1893                 mandoc_msg(MANDOCERR_DT_NOTITLE,
1894                     mdoc->parse, 0, 0, "EOF");
1895                 mdoc->meta.title = mandoc_strdup("UNTITLED");
1896         }
1897 
1898         if (mdoc->meta.vol == NULL)
1899                 mdoc->meta.vol = mandoc_strdup("LOCAL");
1900 
1901         if (mdoc->meta.os == NULL) {
1902                 mandoc_msg(MANDOCERR_OS_MISSING,
1903                     mdoc->parse, 0, 0, NULL);
1904                 mdoc->meta.os = mandoc_strdup("");
1905         } else if (mdoc->meta.os_e &&
1906             (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0)
1907                 mandoc_msg(MANDOCERR_RCS_MISSING, mdoc->parse, 0, 0,
1908                     mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
1909                     "(OpenBSD)" : "(NetBSD)");
1910 
1911         if (mdoc->meta.arch != NULL &&
1912             (arch = arches[mdoc->meta.os_e]) != NULL) {
1913                 while (*arch != NULL && strcmp(*arch, mdoc->meta.arch))
1914                         arch++;
1915                 if (*arch == NULL) {
1916                         n = mdoc->first->child;
1917                         while (n->tok != MDOC_Dt)
1918                                 n = n->next;
1919                         n = n->child->next->next;
1920                         mandoc_vmsg(MANDOCERR_ARCH_BAD,
1921                             mdoc->parse, n->line, n->pos,
1922                             "Dt ... %s %s", mdoc->meta.arch,
1923                             mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
1924                             "(OpenBSD)" : "(NetBSD)");
1925                 }
1926         }
1927 
1928         /* Check that we begin with a proper `Sh'. */
1929 
1930         n = mdoc->first->child;
1931         while (n != NULL && n->tok >= MDOC_Dd &&
1932             mdoc_macros[n->tok].flags & MDOC_PROLOGUE)
1933                 n = n->next;
1934 
1935         if (n == NULL)
1936                 mandoc_msg(MANDOCERR_DOC_EMPTY, mdoc->parse, 0, 0, NULL);
1937         else if (n->tok != MDOC_Sh)
1938                 mandoc_msg(MANDOCERR_SEC_BEFORE, mdoc->parse,
1939                     n->line, n->pos, roff_name[n->tok]);
1940 }
1941 
1942 static void
1943 post_rs(POST_ARGS)
1944 {
1945         struct roff_node *np, *nch, *next, *prev;
1946         int               i, j;
1947 
1948         np = mdoc->last;
1949 
1950         if (np->type != ROFFT_BODY)
1951                 return;
1952 
1953         if (np->child == NULL) {
1954                 mandoc_msg(MANDOCERR_RS_EMPTY, mdoc->parse,
1955                     np->line, np->pos, "Rs");
1956                 return;
1957         }
1958 
1959         /*
1960          * The full `Rs' block needs special handling to order the
1961          * sub-elements according to `rsord'.  Pick through each element
1962          * and correctly order it.  This is an insertion sort.
1963          */
1964 
1965         next = NULL;
1966         for (nch = np->child->next; nch != NULL; nch = next) {
1967                 /* Determine order number of this child. */
1968                 for (i = 0; i < RSORD_MAX; i++)
1969                         if (rsord[i] == nch->tok)
1970                                 break;
1971 
1972                 if (i == RSORD_MAX) {
1973                         mandoc_msg(MANDOCERR_RS_BAD, mdoc->parse,
1974                             nch->line, nch->pos, roff_name[nch->tok]);
1975                         i = -1;
1976                 } else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
1977                         np->norm->Rs.quote_T++;
1978 
1979                 /*
1980                  * Remove this child from the chain.  This somewhat
1981                  * repeats roff_node_unlink(), but since we're
1982                  * just re-ordering, there's no need for the
1983                  * full unlink process.
1984                  */
1985 
1986                 if ((next = nch->next) != NULL)
1987                         next->prev = nch->prev;
1988 
1989                 if ((prev = nch->prev) != NULL)
1990                         prev->next = nch->next;
1991 
1992                 nch->prev = nch->next = NULL;
1993 
1994                 /*
1995                  * Scan back until we reach a node that's
1996                  * to be ordered before this child.
1997                  */
1998 
1999                 for ( ; prev ; prev = prev->prev) {
2000                         /* Determine order of `prev'. */
2001                         for (j = 0; j < RSORD_MAX; j++)
2002                                 if (rsord[j] == prev->tok)
2003                                         break;
2004                         if (j == RSORD_MAX)
2005                                 j = -1;
2006 
2007                         if (j <= i)
2008                                 break;
2009                 }
2010 
2011                 /*
2012                  * Set this child back into its correct place
2013                  * in front of the `prev' node.
2014                  */
2015 
2016                 nch->prev = prev;
2017 
2018                 if (prev == NULL) {
2019                         np->child->prev = nch;
2020                         nch->next = np->child;
2021                         np->child = nch;
2022                 } else {
2023                         if (prev->next)
2024                                 prev->next->prev = nch;
2025                         nch->next = prev->next;
2026                         prev->next = nch;
2027                 }
2028         }
2029 }
2030 
2031 /*
2032  * For some arguments of some macros,
2033  * convert all breakable hyphens into ASCII_HYPH.
2034  */
2035 static void
2036 post_hyph(POST_ARGS)
2037 {
2038         struct roff_node        *nch;
2039         char                    *cp;
2040 
2041         for (nch = mdoc->last->child; nch != NULL; nch = nch->next) {
2042                 if (nch->type != ROFFT_TEXT)
2043                         continue;
2044                 cp = nch->string;
2045                 if (*cp == '\0')
2046                         continue;
2047                 while (*(++cp) != '\0')
2048                         if (*cp == '-' &&
2049                             isalpha((unsigned char)cp[-1]) &&
2050                             isalpha((unsigned char)cp[1]))
2051                                 *cp = ASCII_HYPH;
2052         }
2053 }
2054 
2055 static void
2056 post_ns(POST_ARGS)
2057 {
2058         struct roff_node        *n;
2059 
2060         n = mdoc->last;
2061         if (n->flags & NODE_LINE ||
2062             (n->next != NULL && n->next->flags & NODE_DELIMC))
2063                 mandoc_msg(MANDOCERR_NS_SKIP, mdoc->parse,
2064                     n->line, n->pos, NULL);
2065 }
2066 
2067 static void
2068 post_sx(POST_ARGS)
2069 {
2070         post_delim(mdoc);
2071         post_hyph(mdoc);
2072 }
2073 
2074 static void
2075 post_sh(POST_ARGS)
2076 {
2077 
2078         post_ignpar(mdoc);
2079 
2080         switch (mdoc->last->type) {
2081         case ROFFT_HEAD:
2082                 post_sh_head(mdoc);
2083                 break;
2084         case ROFFT_BODY:
2085                 switch (mdoc->lastsec)  {
2086                 case SEC_NAME:
2087                         post_sh_name(mdoc);
2088                         break;
2089                 case SEC_SEE_ALSO:
2090                         post_sh_see_also(mdoc);
2091                         break;
2092                 case SEC_AUTHORS:
2093                         post_sh_authors(mdoc);
2094                         break;
2095                 default:
2096                         break;
2097                 }
2098                 break;
2099         default:
2100                 break;
2101         }
2102 }
2103 
2104 static void
2105 post_sh_name(POST_ARGS)
2106 {
2107         struct roff_node *n;
2108         int hasnm, hasnd;
2109 
2110         hasnm = hasnd = 0;
2111 
2112         for (n = mdoc->last->child; n != NULL; n = n->next) {
2113                 switch (n->tok) {
2114                 case MDOC_Nm:
2115                         if (hasnm && n->child != NULL)
2116                                 mandoc_vmsg(MANDOCERR_NAMESEC_PUNCT,
2117                                     mdoc->parse, n->line, n->pos,
2118                                     "Nm %s", n->child->string);
2119                         hasnm = 1;
2120                         continue;
2121                 case MDOC_Nd:
2122                         hasnd = 1;
2123                         if (n->next != NULL)
2124                                 mandoc_msg(MANDOCERR_NAMESEC_ND,
2125                                     mdoc->parse, n->line, n->pos, NULL);
2126                         break;
2127                 case TOKEN_NONE:
2128                         if (n->type == ROFFT_TEXT &&
2129                             n->string[0] == ',' && n->string[1] == '\0' &&
2130                             n->next != NULL && n->next->tok == MDOC_Nm) {
2131                                 n = n->next;
2132                                 continue;
2133                         }
2134                         /* FALLTHROUGH */
2135                 default:
2136                         mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
2137                             n->line, n->pos, roff_name[n->tok]);
2138                         continue;
2139                 }
2140                 break;
2141         }
2142 
2143         if ( ! hasnm)
2144                 mandoc_msg(MANDOCERR_NAMESEC_NONM, mdoc->parse,
2145                     mdoc->last->line, mdoc->last->pos, NULL);
2146         if ( ! hasnd)
2147                 mandoc_msg(MANDOCERR_NAMESEC_NOND, mdoc->parse,
2148                     mdoc->last->line, mdoc->last->pos, NULL);
2149 }
2150 
2151 static void
2152 post_sh_see_also(POST_ARGS)
2153 {
2154         const struct roff_node  *n;
2155         const char              *name, *sec;
2156         const char              *lastname, *lastsec, *lastpunct;
2157         int                      cmp;
2158 
2159         n = mdoc->last->child;
2160         lastname = lastsec = lastpunct = NULL;
2161         while (n != NULL) {
2162                 if (n->tok != MDOC_Xr ||
2163                     n->child == NULL ||
2164                     n->child->next == NULL)
2165                         break;
2166 
2167                 /* Process one .Xr node. */
2168 
2169                 name = n->child->string;
2170                 sec = n->child->next->string;
2171                 if (lastsec != NULL) {
2172                         if (lastpunct[0] != ',' || lastpunct[1] != '\0')
2173                                 mandoc_vmsg(MANDOCERR_XR_PUNCT,
2174                                     mdoc->parse, n->line, n->pos,
2175                                     "%s before %s(%s)", lastpunct,
2176                                     name, sec);
2177                         cmp = strcmp(lastsec, sec);
2178                         if (cmp > 0)
2179                                 mandoc_vmsg(MANDOCERR_XR_ORDER,
2180                                     mdoc->parse, n->line, n->pos,
2181                                     "%s(%s) after %s(%s)", name,
2182                                     sec, lastname, lastsec);
2183                         else if (cmp == 0 &&
2184                             strcasecmp(lastname, name) > 0)
2185                                 mandoc_vmsg(MANDOCERR_XR_ORDER,
2186                                     mdoc->parse, n->line, n->pos,
2187                                     "%s after %s", name, lastname);
2188                 }
2189                 lastname = name;
2190                 lastsec = sec;
2191 
2192                 /* Process the following node. */
2193 
2194                 n = n->next;
2195                 if (n == NULL)
2196                         break;
2197                 if (n->tok == MDOC_Xr) {
2198                         lastpunct = "none";
2199                         continue;
2200                 }
2201                 if (n->type != ROFFT_TEXT)
2202                         break;
2203                 for (name = n->string; *name != '\0'; name++)
2204                         if (isalpha((const unsigned char)*name))
2205                                 return;
2206                 lastpunct = n->string;
2207                 if (n->next == NULL || n->next->tok == MDOC_Rs)
2208                         mandoc_vmsg(MANDOCERR_XR_PUNCT, mdoc->parse,
2209                             n->line, n->pos, "%s after %s(%s)",
2210                             lastpunct, lastname, lastsec);
2211                 n = n->next;
2212         }
2213 }
2214 
2215 static int
2216 child_an(const struct roff_node *n)
2217 {
2218 
2219         for (n = n->child; n != NULL; n = n->next)
2220                 if ((n->tok == MDOC_An && n->child != NULL) || child_an(n))
2221                         return 1;
2222         return 0;
2223 }
2224 
2225 static void
2226 post_sh_authors(POST_ARGS)
2227 {
2228 
2229         if ( ! child_an(mdoc->last))
2230                 mandoc_msg(MANDOCERR_AN_MISSING, mdoc->parse,
2231                     mdoc->last->line, mdoc->last->pos, NULL);
2232 }
2233 
2234 /*
2235  * Return an upper bound for the string distance (allowing
2236  * transpositions).  Not a full Levenshtein implementation
2237  * because Levenshtein is quadratic in the string length
2238  * and this function is called for every standard name,
2239  * so the check for each custom name would be cubic.
2240  * The following crude heuristics is linear, resulting
2241  * in quadratic behaviour for checking one custom name,
2242  * which does not cause measurable slowdown.
2243  */
2244 static int
2245 similar(const char *s1, const char *s2)
2246 {
2247         const int       maxdist = 3;
2248         int             dist = 0;
2249 
2250         while (s1[0] != '\0' && s2[0] != '\0') {
2251                 if (s1[0] == s2[0]) {
2252                         s1++;
2253                         s2++;
2254                         continue;
2255                 }
2256                 if (++dist > maxdist)
2257                         return INT_MAX;
2258                 if (s1[1] == s2[1]) {  /* replacement */
2259                         s1++;
2260                         s2++;
2261                 } else if (s1[0] == s2[1] && s1[1] == s2[0]) {
2262                         s1 += 2;        /* transposition */
2263                         s2 += 2;
2264                 } else if (s1[0] == s2[1])  /* insertion */
2265                         s2++;
2266                 else if (s1[1] == s2[0])  /* deletion */
2267                         s1++;
2268                 else
2269                         return INT_MAX;
2270         }
2271         dist += strlen(s1) + strlen(s2);
2272         return dist > maxdist ? INT_MAX : dist;
2273 }
2274 
2275 static void
2276 post_sh_head(POST_ARGS)
2277 {
2278         struct roff_node        *nch;
2279         const char              *goodsec;
2280         const char *const       *testsec;
2281         int                      dist, mindist;
2282         enum roff_sec            sec;
2283 
2284         /*
2285          * Process a new section.  Sections are either "named" or
2286          * "custom".  Custom sections are user-defined, while named ones
2287          * follow a conventional order and may only appear in certain
2288          * manual sections.
2289          */
2290 
2291         sec = mdoc->last->sec;
2292 
2293         /* The NAME should be first. */
2294 
2295         if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE)
2296                 mandoc_vmsg(MANDOCERR_NAMESEC_FIRST, mdoc->parse,
2297                     mdoc->last->line, mdoc->last->pos, "Sh %s",
2298                     sec != SEC_CUSTOM ? secnames[sec] :
2299                     (nch = mdoc->last->child) == NULL ? "" :
2300                     nch->type == ROFFT_TEXT ? nch->string :
2301                     roff_name[nch->tok]);
2302 
2303         /* The SYNOPSIS gets special attention in other areas. */
2304 
2305         if (sec == SEC_SYNOPSIS) {
2306                 roff_setreg(mdoc->roff, "nS", 1, '=');
2307                 mdoc->flags |= MDOC_SYNOPSIS;
2308         } else {
2309                 roff_setreg(mdoc->roff, "nS", 0, '=');
2310                 mdoc->flags &= ~MDOC_SYNOPSIS;
2311         }
2312 
2313         /* Mark our last section. */
2314 
2315         mdoc->lastsec = sec;
2316 
2317         /* We don't care about custom sections after this. */
2318 
2319         if (sec == SEC_CUSTOM) {
2320                 if ((nch = mdoc->last->child) == NULL ||
2321                     nch->type != ROFFT_TEXT || nch->next != NULL)
2322                         return;
2323                 goodsec = NULL;
2324                 mindist = INT_MAX;
2325                 for (testsec = secnames + 1; *testsec != NULL; testsec++) {
2326                         dist = similar(nch->string, *testsec);
2327                         if (dist < mindist) {
2328                                 goodsec = *testsec;
2329                                 mindist = dist;
2330                         }
2331                 }
2332                 if (goodsec != NULL)
2333                         mandoc_vmsg(MANDOCERR_SEC_TYPO, mdoc->parse,
2334                             nch->line, nch->pos, "Sh %s instead of %s",
2335                             nch->string, goodsec);
2336                 return;
2337         }
2338 
2339         /*
2340          * Check whether our non-custom section is being repeated or is
2341          * out of order.
2342          */
2343 
2344         if (sec == mdoc->lastnamed)
2345                 mandoc_vmsg(MANDOCERR_SEC_REP, mdoc->parse,
2346                     mdoc->last->line, mdoc->last->pos,
2347                     "Sh %s", secnames[sec]);
2348 
2349         if (sec < mdoc->lastnamed)
2350                 mandoc_vmsg(MANDOCERR_SEC_ORDER, mdoc->parse,
2351                     mdoc->last->line, mdoc->last->pos,
2352                     "Sh %s", secnames[sec]);
2353 
2354         /* Mark the last named section. */
2355 
2356         mdoc->lastnamed = sec;
2357 
2358         /* Check particular section/manual conventions. */
2359 
2360         if (mdoc->meta.msec == NULL)
2361                 return;
2362 
2363         goodsec = NULL;
2364         switch (sec) {
2365         case SEC_ERRORS:
2366                 if (*mdoc->meta.msec == '4')
2367                         break;
2368                 goodsec = "2, 3, 4, 9";
2369                 /* FALLTHROUGH */
2370         case SEC_RETURN_VALUES:
2371         case SEC_LIBRARY:
2372                 if (*mdoc->meta.msec == '2')
2373                         break;
2374                 if (*mdoc->meta.msec == '3')
2375                         break;
2376                 if (NULL == goodsec)
2377                         goodsec = "2, 3, 9";
2378                 /* FALLTHROUGH */
2379         case SEC_CONTEXT:
2380                 if (*mdoc->meta.msec == '9')
2381                         break;
2382                 if (NULL == goodsec)
2383                         goodsec = "9";
2384                 mandoc_vmsg(MANDOCERR_SEC_MSEC, mdoc->parse,
2385                     mdoc->last->line, mdoc->last->pos,
2386                     "Sh %s for %s only", secnames[sec], goodsec);
2387                 break;
2388         default:
2389                 break;
2390         }
2391 }
2392 
2393 static void
2394 post_xr(POST_ARGS)
2395 {
2396         struct roff_node *n, *nch;
2397 
2398         n = mdoc->last;
2399         nch = n->child;
2400         if (nch->next == NULL) {
2401                 mandoc_vmsg(MANDOCERR_XR_NOSEC, mdoc->parse,
2402                     n->line, n->pos, "Xr %s", nch->string);
2403         } else {
2404                 assert(nch->next == n->last);
2405                 if(mandoc_xr_add(nch->next->string, nch->string,
2406                     nch->line, nch->pos))
2407                         mandoc_vmsg(MANDOCERR_XR_SELF, mdoc->parse,
2408                             nch->line, nch->pos, "Xr %s %s",
2409                             nch->string, nch->next->string);
2410         }
2411         post_delim_nb(mdoc);
2412 }
2413 
2414 static void
2415 post_ignpar(POST_ARGS)
2416 {
2417         struct roff_node *np;
2418 
2419         switch (mdoc->last->type) {
2420         case ROFFT_BLOCK:
2421                 post_prevpar(mdoc);
2422                 return;
2423         case ROFFT_HEAD:
2424                 post_delim(mdoc);
2425                 post_hyph(mdoc);
2426                 return;
2427         case ROFFT_BODY:
2428                 break;
2429         default:
2430                 return;
2431         }
2432 
2433         if ((np = mdoc->last->child) != NULL)
2434                 if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) {
2435                         mandoc_vmsg(MANDOCERR_PAR_SKIP,
2436                             mdoc->parse, np->line, np->pos,
2437                             "%s after %s", roff_name[np->tok],
2438                             roff_name[mdoc->last->tok]);
2439                         roff_node_delete(mdoc, np);
2440                 }
2441 
2442         if ((np = mdoc->last->last) != NULL)
2443                 if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) {
2444                         mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
2445                             np->line, np->pos, "%s at the end of %s",
2446                             roff_name[np->tok],
2447                             roff_name[mdoc->last->tok]);
2448                         roff_node_delete(mdoc, np);
2449                 }
2450 }
2451 
2452 static void
2453 post_prevpar(POST_ARGS)
2454 {
2455         struct roff_node *n;
2456 
2457         n = mdoc->last;
2458         if (NULL == n->prev)
2459                 return;
2460         if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
2461                 return;
2462 
2463         /*
2464          * Don't allow prior `Lp' or `Pp' prior to a paragraph-type
2465          * block:  `Lp', `Pp', or non-compact `Bd' or `Bl'.
2466          */
2467 
2468         if (n->prev->tok != MDOC_Pp &&
2469             n->prev->tok != MDOC_Lp &&
2470             n->prev->tok != ROFF_br)
2471                 return;
2472         if (n->tok == MDOC_Bl && n->norm->Bl.comp)
2473                 return;
2474         if (n->tok == MDOC_Bd && n->norm->Bd.comp)
2475                 return;
2476         if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
2477                 return;
2478 
2479         mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
2480             n->prev->line, n->prev->pos, "%s before %s",
2481             roff_name[n->prev->tok], roff_name[n->tok]);
2482         roff_node_delete(mdoc, n->prev);
2483 }
2484 
2485 static void
2486 post_par(POST_ARGS)
2487 {
2488         struct roff_node *np;
2489 
2490         np = mdoc->last;
2491         if (np->tok != ROFF_br && np->tok != ROFF_sp)
2492                 post_prevpar(mdoc);
2493 
2494         if (np->tok == ROFF_sp) {
2495                 if (np->child != NULL && np->child->next != NULL)
2496                         mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
2497                             np->child->next->line, np->child->next->pos,
2498                             "sp ... %s", np->child->next->string);
2499         } else if (np->child != NULL)
2500                 mandoc_vmsg(MANDOCERR_ARG_SKIP,
2501                     mdoc->parse, np->line, np->pos, "%s %s",
2502                     roff_name[np->tok], np->child->string);
2503 
2504         if ((np = mdoc->last->prev) == NULL) {
2505                 np = mdoc->last->parent;
2506                 if (np->tok != MDOC_Sh && np->tok != MDOC_Ss)
2507                         return;
2508         } else if (np->tok != MDOC_Pp && np->tok != MDOC_Lp &&
2509             (mdoc->last->tok != ROFF_br ||
2510              (np->tok != ROFF_sp && np->tok != ROFF_br)))
2511                 return;
2512 
2513         mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
2514             mdoc->last->line, mdoc->last->pos, "%s after %s",
2515             roff_name[mdoc->last->tok], roff_name[np->tok]);
2516         roff_node_delete(mdoc, mdoc->last);
2517 }
2518 
2519 static void
2520 post_dd(POST_ARGS)
2521 {
2522         struct roff_node *n;
2523         char             *datestr;
2524 
2525         n = mdoc->last;
2526         n->flags |= NODE_NOPRT;
2527 
2528         if (mdoc->meta.date != NULL) {
2529                 mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2530                     n->line, n->pos, "Dd");
2531                 free(mdoc->meta.date);
2532         } else if (mdoc->flags & MDOC_PBODY)
2533                 mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
2534                     n->line, n->pos, "Dd");
2535         else if (mdoc->meta.title != NULL)
2536                 mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
2537                     n->line, n->pos, "Dd after Dt");
2538         else if (mdoc->meta.os != NULL)
2539                 mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
2540                     n->line, n->pos, "Dd after Os");
2541 
2542         if (n->child == NULL || n->child->string[0] == '\0') {
2543                 mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
2544                     mandoc_normdate(mdoc, NULL, n->line, n->pos);
2545                 return;
2546         }
2547 
2548         datestr = NULL;
2549         deroff(&datestr, n);
2550         if (mdoc->quick)
2551                 mdoc->meta.date = datestr;
2552         else {
2553                 mdoc->meta.date = mandoc_normdate(mdoc,
2554                     datestr, n->line, n->pos);
2555                 free(datestr);
2556         }
2557 }
2558 
2559 static void
2560 post_dt(POST_ARGS)
2561 {
2562         struct roff_node *nn, *n;
2563         const char       *cp;
2564         char             *p;
2565 
2566         n = mdoc->last;
2567         n->flags |= NODE_NOPRT;
2568 
2569         if (mdoc->flags & MDOC_PBODY) {
2570                 mandoc_msg(MANDOCERR_DT_LATE, mdoc->parse,
2571                     n->line, n->pos, "Dt");
2572                 return;
2573         }
2574 
2575         if (mdoc->meta.title != NULL)
2576                 mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2577                     n->line, n->pos, "Dt");
2578         else if (mdoc->meta.os != NULL)
2579                 mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
2580                     n->line, n->pos, "Dt after Os");
2581 
2582         free(mdoc->meta.title);
2583         free(mdoc->meta.msec);
2584         free(mdoc->meta.vol);
2585         free(mdoc->meta.arch);
2586 
2587         mdoc->meta.title = NULL;
2588         mdoc->meta.msec = NULL;
2589         mdoc->meta.vol = NULL;
2590         mdoc->meta.arch = NULL;
2591 
2592         /* Mandatory first argument: title. */
2593 
2594         nn = n->child;
2595         if (nn == NULL || *nn->string == '\0') {
2596                 mandoc_msg(MANDOCERR_DT_NOTITLE,
2597                     mdoc->parse, n->line, n->pos, "Dt");
2598                 mdoc->meta.title = mandoc_strdup("UNTITLED");
2599         } else {
2600                 mdoc->meta.title = mandoc_strdup(nn->string);
2601 
2602                 /* Check that all characters are uppercase. */
2603 
2604                 for (p = nn->string; *p != '\0'; p++)
2605                         if (islower((unsigned char)*p)) {
2606                                 mandoc_vmsg(MANDOCERR_TITLE_CASE,
2607                                     mdoc->parse, nn->line,
2608                                     nn->pos + (p - nn->string),
2609                                     "Dt %s", nn->string);
2610                                 break;
2611                         }
2612         }
2613 
2614         /* Mandatory second argument: section. */
2615 
2616         if (nn != NULL)
2617                 nn = nn->next;
2618 
2619         if (nn == NULL) {
2620                 mandoc_vmsg(MANDOCERR_MSEC_MISSING,
2621                     mdoc->parse, n->line, n->pos,
2622                     "Dt %s", mdoc->meta.title);
2623                 mdoc->meta.vol = mandoc_strdup("LOCAL");
2624                 return;  /* msec and arch remain NULL. */
2625         }
2626 
2627         mdoc->meta.msec = mandoc_strdup(nn->string);
2628 
2629         /* Infer volume title from section number. */
2630 
2631         cp = mandoc_a2msec(nn->string);
2632         if (cp == NULL) {
2633                 mandoc_vmsg(MANDOCERR_MSEC_BAD, mdoc->parse,
2634                     nn->line, nn->pos, "Dt ... %s", nn->string);
2635                 mdoc->meta.vol = mandoc_strdup(nn->string);
2636         } else
2637                 mdoc->meta.vol = mandoc_strdup(cp);
2638 
2639         /* Optional third argument: architecture. */
2640 
2641         if ((nn = nn->next) == NULL)
2642                 return;
2643 
2644         for (p = nn->string; *p != '\0'; p++)
2645                 *p = tolower((unsigned char)*p);
2646         mdoc->meta.arch = mandoc_strdup(nn->string);
2647 
2648         /* Ignore fourth and later arguments. */
2649 
2650         if ((nn = nn->next) != NULL)
2651                 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
2652                     nn->line, nn->pos, "Dt ... %s", nn->string);
2653 }
2654 
2655 static void
2656 post_bx(POST_ARGS)
2657 {
2658         struct roff_node        *n, *nch;
2659         const char              *macro;
2660 
2661         post_delim_nb(mdoc);
2662 
2663         n = mdoc->last;
2664         nch = n->child;
2665 
2666         if (nch != NULL) {
2667                 macro = !strcmp(nch->string, "Open") ? "Ox" :
2668                     !strcmp(nch->string, "Net") ? "Nx" :
2669                     !strcmp(nch->string, "Free") ? "Fx" :
2670                     !strcmp(nch->string, "DragonFly") ? "Dx" : NULL;
2671                 if (macro != NULL)
2672                         mandoc_msg(MANDOCERR_BX, mdoc->parse,
2673                             n->line, n->pos, macro);
2674                 mdoc->last = nch;
2675                 nch = nch->next;
2676                 mdoc->next = ROFF_NEXT_SIBLING;
2677                 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2678                 mdoc->last->flags |= NODE_NOSRC;
2679                 mdoc->next = ROFF_NEXT_SIBLING;
2680         } else
2681                 mdoc->next = ROFF_NEXT_CHILD;
2682         roff_word_alloc(mdoc, n->line, n->pos, "BSD");
2683         mdoc->last->flags |= NODE_NOSRC;
2684 
2685         if (nch == NULL) {
2686                 mdoc->last = n;
2687                 return;
2688         }
2689 
2690         roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2691         mdoc->last->flags |= NODE_NOSRC;
2692         mdoc->next = ROFF_NEXT_SIBLING;
2693         roff_word_alloc(mdoc, n->line, n->pos, "-");
2694         mdoc->last->flags |= NODE_NOSRC;
2695         roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2696         mdoc->last->flags |= NODE_NOSRC;
2697         mdoc->last = n;
2698 
2699         /*
2700          * Make `Bx's second argument always start with an uppercase
2701          * letter.  Groff checks if it's an "accepted" term, but we just
2702          * uppercase blindly.
2703          */
2704 
2705         *nch->string = (char)toupper((unsigned char)*nch->string);
2706 }
2707 
2708 static void
2709 post_os(POST_ARGS)
2710 {
2711 #ifndef OSNAME
2712         struct utsname    utsname;
2713         static char      *defbuf;
2714 #endif
2715         struct roff_node *n;
2716 
2717         n = mdoc->last;
2718         n->flags |= NODE_NOPRT;
2719 
2720         if (mdoc->meta.os != NULL)
2721                 mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2722                     n->line, n->pos, "Os");
2723         else if (mdoc->flags & MDOC_PBODY)
2724                 mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
2725                     n->line, n->pos, "Os");
2726 
2727         post_delim(mdoc);
2728 
2729         /*
2730          * Set the operating system by way of the `Os' macro.
2731          * The order of precedence is:
2732          * 1. the argument of the `Os' macro, unless empty
2733          * 2. the -Ios=foo command line argument, if provided
2734          * 3. -DOSNAME="\"foo\"", if provided during compilation
2735          * 4. "sysname release" from uname(3)
2736          */
2737 
2738         free(mdoc->meta.os);
2739         mdoc->meta.os = NULL;
2740         deroff(&mdoc->meta.os, n);
2741         if (mdoc->meta.os)
2742                 goto out;
2743 
2744         if (mdoc->os_s != NULL) {
2745                 mdoc->meta.os = mandoc_strdup(mdoc->os_s);
2746                 goto out;
2747         }
2748 
2749 #ifdef OSNAME
2750         mdoc->meta.os = mandoc_strdup(OSNAME);
2751 #else /*!OSNAME */
2752         if (defbuf == NULL) {
2753                 if (uname(&utsname) == -1) {
2754                         mandoc_msg(MANDOCERR_OS_UNAME, mdoc->parse,
2755                             n->line, n->pos, "Os");
2756                         defbuf = mandoc_strdup("UNKNOWN");
2757                 } else
2758                         mandoc_asprintf(&defbuf, "%s %s",
2759                             utsname.sysname, utsname.release);
2760         }
2761         mdoc->meta.os = mandoc_strdup(defbuf);
2762 #endif /*!OSNAME*/
2763 
2764 out:
2765         if (mdoc->meta.os_e == MANDOC_OS_OTHER) {
2766                 if (strstr(mdoc->meta.os, "OpenBSD") != NULL)
2767                         mdoc->meta.os_e = MANDOC_OS_OPENBSD;
2768                 else if (strstr(mdoc->meta.os, "NetBSD") != NULL)
2769                         mdoc->meta.os_e = MANDOC_OS_NETBSD;
2770         }
2771 
2772         /*
2773          * This is the earliest point where we can check
2774          * Mdocdate conventions because we don't know
2775          * the operating system earlier.
2776          */
2777 
2778         if (n->child != NULL)
2779                 mandoc_vmsg(MANDOCERR_OS_ARG, mdoc->parse,
2780                     n->child->line, n->child->pos,
2781                     "Os %s (%s)", n->child->string,
2782                     mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2783                     "OpenBSD" : "NetBSD");
2784 
2785         while (n->tok != MDOC_Dd)
2786                 if ((n = n->prev) == NULL)
2787                         return;
2788         if ((n = n->child) == NULL)
2789                 return;
2790         if (strncmp(n->string, "$" "Mdocdate", 9)) {
2791                 if (mdoc->meta.os_e == MANDOC_OS_OPENBSD)
2792                         mandoc_vmsg(MANDOCERR_MDOCDATE_MISSING,
2793                             mdoc->parse, n->line, n->pos,
2794                             "Dd %s (OpenBSD)", n->string);
2795         } else {
2796                 if (mdoc->meta.os_e == MANDOC_OS_NETBSD)
2797                         mandoc_vmsg(MANDOCERR_MDOCDATE,
2798                             mdoc->parse, n->line, n->pos,
2799                             "Dd %s (NetBSD)", n->string);
2800         }
2801 }
2802 
2803 enum roff_sec
2804 mdoc_a2sec(const char *p)
2805 {
2806         int              i;
2807 
2808         for (i = 0; i < (int)SEC__MAX; i++)
2809                 if (secnames[i] && 0 == strcmp(p, secnames[i]))
2810                         return (enum roff_sec)i;
2811 
2812         return SEC_CUSTOM;
2813 }
2814 
2815 static size_t
2816 macro2len(enum roff_tok macro)
2817 {
2818 
2819         switch (macro) {
2820         case MDOC_Ad:
2821                 return 12;
2822         case MDOC_Ao:
2823                 return 12;
2824         case MDOC_An:
2825                 return 12;
2826         case MDOC_Aq:
2827                 return 12;
2828         case MDOC_Ar:
2829                 return 12;
2830         case MDOC_Bo:
2831                 return 12;
2832         case MDOC_Bq:
2833                 return 12;
2834         case MDOC_Cd:
2835                 return 12;
2836         case MDOC_Cm:
2837                 return 10;
2838         case MDOC_Do:
2839                 return 10;
2840         case MDOC_Dq:
2841                 return 12;
2842         case MDOC_Dv:
2843                 return 12;
2844         case MDOC_Eo:
2845                 return 12;
2846         case MDOC_Em:
2847                 return 10;
2848         case MDOC_Er:
2849                 return 17;
2850         case MDOC_Ev:
2851                 return 15;
2852         case MDOC_Fa:
2853                 return 12;
2854         case MDOC_Fl:
2855                 return 10;
2856         case MDOC_Fo:
2857                 return 16;
2858         case MDOC_Fn:
2859                 return 16;
2860         case MDOC_Ic:
2861                 return 10;
2862         case MDOC_Li:
2863                 return 16;
2864         case MDOC_Ms:
2865                 return 6;
2866         case MDOC_Nm:
2867                 return 10;
2868         case MDOC_No:
2869                 return 12;
2870         case MDOC_Oo:
2871                 return 10;
2872         case MDOC_Op:
2873                 return 14;
2874         case MDOC_Pa:
2875                 return 32;
2876         case MDOC_Pf:
2877                 return 12;
2878         case MDOC_Po:
2879                 return 12;
2880         case MDOC_Pq:
2881                 return 12;
2882         case MDOC_Ql:
2883                 return 16;
2884         case MDOC_Qo:
2885                 return 12;
2886         case MDOC_So:
2887                 return 12;
2888         case MDOC_Sq:
2889                 return 12;
2890         case MDOC_Sy:
2891                 return 6;
2892         case MDOC_Sx:
2893                 return 16;
2894         case MDOC_Tn:
2895                 return 10;
2896         case MDOC_Va:
2897                 return 12;
2898         case MDOC_Vt:
2899                 return 12;
2900         case MDOC_Xr:
2901                 return 10;
2902         default:
2903                 break;
2904         };
2905         return 0;
2906 }