]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/mandoc/mdoc_validate.c
Merge llvm, clang, compiler-rt, libc++, lld, and lldb release_80 branch
[FreeBSD/FreeBSD.git] / contrib / mandoc / mdoc_validate.c
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 }