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