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