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