Merge branch 'vendor/MDOCML'
[dragonfly.git] / contrib / mdocml / mdoc_validate.c
1 /*      $Id: mdoc_validate.c,v 1.371 2019/03/04 13:01:57 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);
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 = mdoc->quick ? mandoc_strdup("") :
1907                     mandoc_normdate(mdoc, NULL, 0, 0);
1908
1909         if (mdoc->meta.title == NULL) {
1910                 mandoc_msg(MANDOCERR_DT_NOTITLE, 0, 0, "EOF");
1911                 mdoc->meta.title = mandoc_strdup("UNTITLED");
1912         }
1913
1914         if (mdoc->meta.vol == NULL)
1915                 mdoc->meta.vol = mandoc_strdup("LOCAL");
1916
1917         if (mdoc->meta.os == NULL) {
1918                 mandoc_msg(MANDOCERR_OS_MISSING, 0, 0, NULL);
1919                 mdoc->meta.os = mandoc_strdup("");
1920         } else if (mdoc->meta.os_e &&
1921             (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0)
1922                 mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0,
1923                     mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
1924                     "(OpenBSD)" : "(NetBSD)");
1925
1926         if (mdoc->meta.arch != NULL &&
1927             arch_valid(mdoc->meta.arch, mdoc->meta.os_e) == 0) {
1928                 n = mdoc->meta.first->child;
1929                 while (n->tok != MDOC_Dt ||
1930                     n->child == NULL ||
1931                     n->child->next == NULL ||
1932                     n->child->next->next == NULL)
1933                         n = n->next;
1934                 n = n->child->next->next;
1935                 mandoc_msg(MANDOCERR_ARCH_BAD, n->line, n->pos,
1936                     "Dt ... %s %s", mdoc->meta.arch,
1937                     mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
1938                     "(OpenBSD)" : "(NetBSD)");
1939         }
1940
1941         /* Check that we begin with a proper `Sh'. */
1942
1943         n = mdoc->meta.first->child;
1944         while (n != NULL &&
1945             (n->type == ROFFT_COMMENT ||
1946              (n->tok >= MDOC_Dd &&
1947               mdoc_macro(n->tok)->flags & MDOC_PROLOGUE)))
1948                 n = n->next;
1949
1950         if (n == NULL)
1951                 mandoc_msg(MANDOCERR_DOC_EMPTY, 0, 0, NULL);
1952         else if (n->tok != MDOC_Sh)
1953                 mandoc_msg(MANDOCERR_SEC_BEFORE, n->line, n->pos,
1954                     "%s", roff_name[n->tok]);
1955 }
1956
1957 static void
1958 post_rs(POST_ARGS)
1959 {
1960         struct roff_node *np, *nch, *next, *prev;
1961         int               i, j;
1962
1963         np = mdoc->last;
1964
1965         if (np->type != ROFFT_BODY)
1966                 return;
1967
1968         if (np->child == NULL) {
1969                 mandoc_msg(MANDOCERR_RS_EMPTY, np->line, np->pos, "Rs");
1970                 return;
1971         }
1972
1973         /*
1974          * The full `Rs' block needs special handling to order the
1975          * sub-elements according to `rsord'.  Pick through each element
1976          * and correctly order it.  This is an insertion sort.
1977          */
1978
1979         next = NULL;
1980         for (nch = np->child->next; nch != NULL; nch = next) {
1981                 /* Determine order number of this child. */
1982                 for (i = 0; i < RSORD_MAX; i++)
1983                         if (rsord[i] == nch->tok)
1984                                 break;
1985
1986                 if (i == RSORD_MAX) {
1987                         mandoc_msg(MANDOCERR_RS_BAD, nch->line, nch->pos,
1988                             "%s", roff_name[nch->tok]);
1989                         i = -1;
1990                 } else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
1991                         np->norm->Rs.quote_T++;
1992
1993                 /*
1994                  * Remove this child from the chain.  This somewhat
1995                  * repeats roff_node_unlink(), but since we're
1996                  * just re-ordering, there's no need for the
1997                  * full unlink process.
1998                  */
1999
2000                 if ((next = nch->next) != NULL)
2001                         next->prev = nch->prev;
2002
2003                 if ((prev = nch->prev) != NULL)
2004                         prev->next = nch->next;
2005
2006                 nch->prev = nch->next = NULL;
2007
2008                 /*
2009                  * Scan back until we reach a node that's
2010                  * to be ordered before this child.
2011                  */
2012
2013                 for ( ; prev ; prev = prev->prev) {
2014                         /* Determine order of `prev'. */
2015                         for (j = 0; j < RSORD_MAX; j++)
2016                                 if (rsord[j] == prev->tok)
2017                                         break;
2018                         if (j == RSORD_MAX)
2019                                 j = -1;
2020
2021                         if (j <= i)
2022                                 break;
2023                 }
2024
2025                 /*
2026                  * Set this child back into its correct place
2027                  * in front of the `prev' node.
2028                  */
2029
2030                 nch->prev = prev;
2031
2032                 if (prev == NULL) {
2033                         np->child->prev = nch;
2034                         nch->next = np->child;
2035                         np->child = nch;
2036                 } else {
2037                         if (prev->next)
2038                                 prev->next->prev = nch;
2039                         nch->next = prev->next;
2040                         prev->next = nch;
2041                 }
2042         }
2043 }
2044
2045 /*
2046  * For some arguments of some macros,
2047  * convert all breakable hyphens into ASCII_HYPH.
2048  */
2049 static void
2050 post_hyph(POST_ARGS)
2051 {
2052         struct roff_node        *nch;
2053         char                    *cp;
2054
2055         for (nch = mdoc->last->child; nch != NULL; nch = nch->next) {
2056                 if (nch->type != ROFFT_TEXT)
2057                         continue;
2058                 cp = nch->string;
2059                 if (*cp == '\0')
2060                         continue;
2061                 while (*(++cp) != '\0')
2062                         if (*cp == '-' &&
2063                             isalpha((unsigned char)cp[-1]) &&
2064                             isalpha((unsigned char)cp[1]))
2065                                 *cp = ASCII_HYPH;
2066         }
2067 }
2068
2069 static void
2070 post_ns(POST_ARGS)
2071 {
2072         struct roff_node        *n;
2073
2074         n = mdoc->last;
2075         if (n->flags & NODE_LINE ||
2076             (n->next != NULL && n->next->flags & NODE_DELIMC))
2077                 mandoc_msg(MANDOCERR_NS_SKIP, n->line, n->pos, NULL);
2078 }
2079
2080 static void
2081 post_sx(POST_ARGS)
2082 {
2083         post_delim(mdoc);
2084         post_hyph(mdoc);
2085 }
2086
2087 static void
2088 post_sh(POST_ARGS)
2089 {
2090
2091         post_ignpar(mdoc);
2092
2093         switch (mdoc->last->type) {
2094         case ROFFT_HEAD:
2095                 post_sh_head(mdoc);
2096                 break;
2097         case ROFFT_BODY:
2098                 switch (mdoc->lastsec)  {
2099                 case SEC_NAME:
2100                         post_sh_name(mdoc);
2101                         break;
2102                 case SEC_SEE_ALSO:
2103                         post_sh_see_also(mdoc);
2104                         break;
2105                 case SEC_AUTHORS:
2106                         post_sh_authors(mdoc);
2107                         break;
2108                 default:
2109                         break;
2110                 }
2111                 break;
2112         default:
2113                 break;
2114         }
2115 }
2116
2117 static void
2118 post_sh_name(POST_ARGS)
2119 {
2120         struct roff_node *n;
2121         int hasnm, hasnd;
2122
2123         hasnm = hasnd = 0;
2124
2125         for (n = mdoc->last->child; n != NULL; n = n->next) {
2126                 switch (n->tok) {
2127                 case MDOC_Nm:
2128                         if (hasnm && n->child != NULL)
2129                                 mandoc_msg(MANDOCERR_NAMESEC_PUNCT,
2130                                     n->line, n->pos,
2131                                     "Nm %s", n->child->string);
2132                         hasnm = 1;
2133                         continue;
2134                 case MDOC_Nd:
2135                         hasnd = 1;
2136                         if (n->next != NULL)
2137                                 mandoc_msg(MANDOCERR_NAMESEC_ND,
2138                                     n->line, n->pos, NULL);
2139                         break;
2140                 case TOKEN_NONE:
2141                         if (n->type == ROFFT_TEXT &&
2142                             n->string[0] == ',' && n->string[1] == '\0' &&
2143                             n->next != NULL && n->next->tok == MDOC_Nm) {
2144                                 n = n->next;
2145                                 continue;
2146                         }
2147                         /* FALLTHROUGH */
2148                 default:
2149                         mandoc_msg(MANDOCERR_NAMESEC_BAD,
2150                             n->line, n->pos, "%s", roff_name[n->tok]);
2151                         continue;
2152                 }
2153                 break;
2154         }
2155
2156         if ( ! hasnm)
2157                 mandoc_msg(MANDOCERR_NAMESEC_NONM,
2158                     mdoc->last->line, mdoc->last->pos, NULL);
2159         if ( ! hasnd)
2160                 mandoc_msg(MANDOCERR_NAMESEC_NOND,
2161                     mdoc->last->line, mdoc->last->pos, NULL);
2162 }
2163
2164 static void
2165 post_sh_see_also(POST_ARGS)
2166 {
2167         const struct roff_node  *n;
2168         const char              *name, *sec;
2169         const char              *lastname, *lastsec, *lastpunct;
2170         int                      cmp;
2171
2172         n = mdoc->last->child;
2173         lastname = lastsec = lastpunct = NULL;
2174         while (n != NULL) {
2175                 if (n->tok != MDOC_Xr ||
2176                     n->child == NULL ||
2177                     n->child->next == NULL)
2178                         break;
2179
2180                 /* Process one .Xr node. */
2181
2182                 name = n->child->string;
2183                 sec = n->child->next->string;
2184                 if (lastsec != NULL) {
2185                         if (lastpunct[0] != ',' || lastpunct[1] != '\0')
2186                                 mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2187                                     n->pos, "%s before %s(%s)",
2188                                     lastpunct, name, sec);
2189                         cmp = strcmp(lastsec, sec);
2190                         if (cmp > 0)
2191                                 mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2192                                     n->pos, "%s(%s) after %s(%s)",
2193                                     name, sec, lastname, lastsec);
2194                         else if (cmp == 0 &&
2195                             strcasecmp(lastname, name) > 0)
2196                                 mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2197                                     n->pos, "%s after %s", name, lastname);
2198                 }
2199                 lastname = name;
2200                 lastsec = sec;
2201
2202                 /* Process the following node. */
2203
2204                 n = n->next;
2205                 if (n == NULL)
2206                         break;
2207                 if (n->tok == MDOC_Xr) {
2208                         lastpunct = "none";
2209                         continue;
2210                 }
2211                 if (n->type != ROFFT_TEXT)
2212                         break;
2213                 for (name = n->string; *name != '\0'; name++)
2214                         if (isalpha((const unsigned char)*name))
2215                                 return;
2216                 lastpunct = n->string;
2217                 if (n->next == NULL || n->next->tok == MDOC_Rs)
2218                         mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2219                             n->pos, "%s after %s(%s)",
2220                             lastpunct, lastname, lastsec);
2221                 n = n->next;
2222         }
2223 }
2224
2225 static int
2226 child_an(const struct roff_node *n)
2227 {
2228
2229         for (n = n->child; n != NULL; n = n->next)
2230                 if ((n->tok == MDOC_An && n->child != NULL) || child_an(n))
2231                         return 1;
2232         return 0;
2233 }
2234
2235 static void
2236 post_sh_authors(POST_ARGS)
2237 {
2238
2239         if ( ! child_an(mdoc->last))
2240                 mandoc_msg(MANDOCERR_AN_MISSING,
2241                     mdoc->last->line, mdoc->last->pos, NULL);
2242 }
2243
2244 /*
2245  * Return an upper bound for the string distance (allowing
2246  * transpositions).  Not a full Levenshtein implementation
2247  * because Levenshtein is quadratic in the string length
2248  * and this function is called for every standard name,
2249  * so the check for each custom name would be cubic.
2250  * The following crude heuristics is linear, resulting
2251  * in quadratic behaviour for checking one custom name,
2252  * which does not cause measurable slowdown.
2253  */
2254 static int
2255 similar(const char *s1, const char *s2)
2256 {
2257         const int       maxdist = 3;
2258         int             dist = 0;
2259
2260         while (s1[0] != '\0' && s2[0] != '\0') {
2261                 if (s1[0] == s2[0]) {
2262                         s1++;
2263                         s2++;
2264                         continue;
2265                 }
2266                 if (++dist > maxdist)
2267                         return INT_MAX;
2268                 if (s1[1] == s2[1]) {  /* replacement */
2269                         s1++;
2270                         s2++;
2271                 } else if (s1[0] == s2[1] && s1[1] == s2[0]) {
2272                         s1 += 2;        /* transposition */
2273                         s2 += 2;
2274                 } else if (s1[0] == s2[1])  /* insertion */
2275                         s2++;
2276                 else if (s1[1] == s2[0])  /* deletion */
2277                         s1++;
2278                 else
2279                         return INT_MAX;
2280         }
2281         dist += strlen(s1) + strlen(s2);
2282         return dist > maxdist ? INT_MAX : dist;
2283 }
2284
2285 static void
2286 post_sh_head(POST_ARGS)
2287 {
2288         struct roff_node        *nch;
2289         const char              *goodsec;
2290         const char *const       *testsec;
2291         int                      dist, mindist;
2292         enum roff_sec            sec;
2293
2294         /*
2295          * Process a new section.  Sections are either "named" or
2296          * "custom".  Custom sections are user-defined, while named ones
2297          * follow a conventional order and may only appear in certain
2298          * manual sections.
2299          */
2300
2301         sec = mdoc->last->sec;
2302
2303         /* The NAME should be first. */
2304
2305         if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE)
2306                 mandoc_msg(MANDOCERR_NAMESEC_FIRST,
2307                     mdoc->last->line, mdoc->last->pos, "Sh %s",
2308                     sec != SEC_CUSTOM ? secnames[sec] :
2309                     (nch = mdoc->last->child) == NULL ? "" :
2310                     nch->type == ROFFT_TEXT ? nch->string :
2311                     roff_name[nch->tok]);
2312
2313         /* The SYNOPSIS gets special attention in other areas. */
2314
2315         if (sec == SEC_SYNOPSIS) {
2316                 roff_setreg(mdoc->roff, "nS", 1, '=');
2317                 mdoc->flags |= MDOC_SYNOPSIS;
2318         } else {
2319                 roff_setreg(mdoc->roff, "nS", 0, '=');
2320                 mdoc->flags &= ~MDOC_SYNOPSIS;
2321         }
2322
2323         /* Mark our last section. */
2324
2325         mdoc->lastsec = sec;
2326
2327         /* We don't care about custom sections after this. */
2328
2329         if (sec == SEC_CUSTOM) {
2330                 if ((nch = mdoc->last->child) == NULL ||
2331                     nch->type != ROFFT_TEXT || nch->next != NULL)
2332                         return;
2333                 goodsec = NULL;
2334                 mindist = INT_MAX;
2335                 for (testsec = secnames + 1; *testsec != NULL; testsec++) {
2336                         dist = similar(nch->string, *testsec);
2337                         if (dist < mindist) {
2338                                 goodsec = *testsec;
2339                                 mindist = dist;
2340                         }
2341                 }
2342                 if (goodsec != NULL)
2343                         mandoc_msg(MANDOCERR_SEC_TYPO, nch->line, nch->pos,
2344                             "Sh %s instead of %s", nch->string, goodsec);
2345                 return;
2346         }
2347
2348         /*
2349          * Check whether our non-custom section is being repeated or is
2350          * out of order.
2351          */
2352
2353         if (sec == mdoc->lastnamed)
2354                 mandoc_msg(MANDOCERR_SEC_REP, mdoc->last->line,
2355                     mdoc->last->pos, "Sh %s", secnames[sec]);
2356
2357         if (sec < mdoc->lastnamed)
2358                 mandoc_msg(MANDOCERR_SEC_ORDER, mdoc->last->line,
2359                     mdoc->last->pos, "Sh %s", secnames[sec]);
2360
2361         /* Mark the last named section. */
2362
2363         mdoc->lastnamed = sec;
2364
2365         /* Check particular section/manual conventions. */
2366
2367         if (mdoc->meta.msec == NULL)
2368                 return;
2369
2370         goodsec = NULL;
2371         switch (sec) {
2372         case SEC_ERRORS:
2373                 if (*mdoc->meta.msec == '4')
2374                         break;
2375                 goodsec = "2, 3, 4, 9";
2376                 /* FALLTHROUGH */
2377         case SEC_RETURN_VALUES:
2378         case SEC_LIBRARY:
2379                 if (*mdoc->meta.msec == '2')
2380                         break;
2381                 if (*mdoc->meta.msec == '3')
2382                         break;
2383                 if (NULL == goodsec)
2384                         goodsec = "2, 3, 9";
2385                 /* FALLTHROUGH */
2386         case SEC_CONTEXT:
2387                 if (*mdoc->meta.msec == '9')
2388                         break;
2389                 if (NULL == goodsec)
2390                         goodsec = "9";
2391                 mandoc_msg(MANDOCERR_SEC_MSEC,
2392                     mdoc->last->line, mdoc->last->pos,
2393                     "Sh %s for %s only", secnames[sec], goodsec);
2394                 break;
2395         default:
2396                 break;
2397         }
2398 }
2399
2400 static void
2401 post_xr(POST_ARGS)
2402 {
2403         struct roff_node *n, *nch;
2404
2405         n = mdoc->last;
2406         nch = n->child;
2407         if (nch->next == NULL) {
2408                 mandoc_msg(MANDOCERR_XR_NOSEC,
2409                     n->line, n->pos, "Xr %s", nch->string);
2410         } else {
2411                 assert(nch->next == n->last);
2412                 if(mandoc_xr_add(nch->next->string, nch->string,
2413                     nch->line, nch->pos))
2414                         mandoc_msg(MANDOCERR_XR_SELF,
2415                             nch->line, nch->pos, "Xr %s %s",
2416                             nch->string, nch->next->string);
2417         }
2418         post_delim_nb(mdoc);
2419 }
2420
2421 static void
2422 post_ignpar(POST_ARGS)
2423 {
2424         struct roff_node *np;
2425
2426         switch (mdoc->last->type) {
2427         case ROFFT_BLOCK:
2428                 post_prevpar(mdoc);
2429                 return;
2430         case ROFFT_HEAD:
2431                 post_delim(mdoc);
2432                 post_hyph(mdoc);
2433                 return;
2434         case ROFFT_BODY:
2435                 break;
2436         default:
2437                 return;
2438         }
2439
2440         if ((np = mdoc->last->child) != NULL)
2441                 if (np->tok == MDOC_Pp ||
2442                     np->tok == ROFF_br || np->tok == ROFF_sp) {
2443                         mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
2444                             "%s after %s", roff_name[np->tok],
2445                             roff_name[mdoc->last->tok]);
2446                         roff_node_delete(mdoc, np);
2447                 }
2448
2449         if ((np = mdoc->last->last) != NULL)
2450                 if (np->tok == MDOC_Pp || np->tok == ROFF_br) {
2451                         mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
2452                             "%s at the end of %s", roff_name[np->tok],
2453                             roff_name[mdoc->last->tok]);
2454                         roff_node_delete(mdoc, np);
2455                 }
2456 }
2457
2458 static void
2459 post_prevpar(POST_ARGS)
2460 {
2461         struct roff_node *n;
2462
2463         n = mdoc->last;
2464         if (NULL == n->prev)
2465                 return;
2466         if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
2467                 return;
2468
2469         /*
2470          * Don't allow `Pp' prior to a paragraph-type
2471          * block: `Pp' or non-compact `Bd' or `Bl'.
2472          */
2473
2474         if (n->prev->tok != MDOC_Pp && n->prev->tok != ROFF_br)
2475                 return;
2476         if (n->tok == MDOC_Bl && n->norm->Bl.comp)
2477                 return;
2478         if (n->tok == MDOC_Bd && n->norm->Bd.comp)
2479                 return;
2480         if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
2481                 return;
2482
2483         mandoc_msg(MANDOCERR_PAR_SKIP, n->prev->line, n->prev->pos,
2484             "%s before %s", roff_name[n->prev->tok], roff_name[n->tok]);
2485         roff_node_delete(mdoc, n->prev);
2486 }
2487
2488 static void
2489 post_par(POST_ARGS)
2490 {
2491         struct roff_node *np;
2492
2493         post_prevpar(mdoc);
2494
2495         np = mdoc->last;
2496         if (np->child != NULL)
2497                 mandoc_msg(MANDOCERR_ARG_SKIP, np->line, np->pos,
2498                     "%s %s", roff_name[np->tok], np->child->string);
2499 }
2500
2501 static void
2502 post_dd(POST_ARGS)
2503 {
2504         struct roff_node *n;
2505         char             *datestr;
2506
2507         n = mdoc->last;
2508         n->flags |= NODE_NOPRT;
2509
2510         if (mdoc->meta.date != NULL) {
2511                 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dd");
2512                 free(mdoc->meta.date);
2513         } else if (mdoc->flags & MDOC_PBODY)
2514                 mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Dd");
2515         else if (mdoc->meta.title != NULL)
2516                 mandoc_msg(MANDOCERR_PROLOG_ORDER,
2517                     n->line, n->pos, "Dd after Dt");
2518         else if (mdoc->meta.os != NULL)
2519                 mandoc_msg(MANDOCERR_PROLOG_ORDER,
2520                     n->line, n->pos, "Dd after Os");
2521
2522         if (n->child == NULL || n->child->string[0] == '\0') {
2523                 mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
2524                     mandoc_normdate(mdoc, NULL, n->line, n->pos);
2525                 return;
2526         }
2527
2528         datestr = NULL;
2529         deroff(&datestr, n);
2530         if (mdoc->quick)
2531                 mdoc->meta.date = datestr;
2532         else {
2533                 mdoc->meta.date = mandoc_normdate(mdoc,
2534                     datestr, n->line, n->pos);
2535                 free(datestr);
2536         }
2537 }
2538
2539 static void
2540 post_dt(POST_ARGS)
2541 {
2542         struct roff_node *nn, *n;
2543         const char       *cp;
2544         char             *p;
2545
2546         n = mdoc->last;
2547         n->flags |= NODE_NOPRT;
2548
2549         if (mdoc->flags & MDOC_PBODY) {
2550                 mandoc_msg(MANDOCERR_DT_LATE, n->line, n->pos, "Dt");
2551                 return;
2552         }
2553
2554         if (mdoc->meta.title != NULL)
2555                 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dt");
2556         else if (mdoc->meta.os != NULL)
2557                 mandoc_msg(MANDOCERR_PROLOG_ORDER,
2558                     n->line, n->pos, "Dt after Os");
2559
2560         free(mdoc->meta.title);
2561         free(mdoc->meta.msec);
2562         free(mdoc->meta.vol);
2563         free(mdoc->meta.arch);
2564
2565         mdoc->meta.title = NULL;
2566         mdoc->meta.msec = NULL;
2567         mdoc->meta.vol = NULL;
2568         mdoc->meta.arch = NULL;
2569
2570         /* Mandatory first argument: title. */
2571
2572         nn = n->child;
2573         if (nn == NULL || *nn->string == '\0') {
2574                 mandoc_msg(MANDOCERR_DT_NOTITLE, n->line, n->pos, "Dt");
2575                 mdoc->meta.title = mandoc_strdup("UNTITLED");
2576         } else {
2577                 mdoc->meta.title = mandoc_strdup(nn->string);
2578
2579                 /* Check that all characters are uppercase. */
2580
2581                 for (p = nn->string; *p != '\0'; p++)
2582                         if (islower((unsigned char)*p)) {
2583                                 mandoc_msg(MANDOCERR_TITLE_CASE, nn->line,
2584                                     nn->pos + (int)(p - nn->string),
2585                                     "Dt %s", nn->string);
2586                                 break;
2587                         }
2588         }
2589
2590         /* Mandatory second argument: section. */
2591
2592         if (nn != NULL)
2593                 nn = nn->next;
2594
2595         if (nn == NULL) {
2596                 mandoc_msg(MANDOCERR_MSEC_MISSING, n->line, n->pos,
2597                     "Dt %s", mdoc->meta.title);
2598                 mdoc->meta.vol = mandoc_strdup("LOCAL");
2599                 return;  /* msec and arch remain NULL. */
2600         }
2601
2602         mdoc->meta.msec = mandoc_strdup(nn->string);
2603
2604         /* Infer volume title from section number. */
2605
2606         cp = mandoc_a2msec(nn->string);
2607         if (cp == NULL) {
2608                 mandoc_msg(MANDOCERR_MSEC_BAD,
2609                     nn->line, nn->pos, "Dt ... %s", nn->string);
2610                 mdoc->meta.vol = mandoc_strdup(nn->string);
2611         } else
2612                 mdoc->meta.vol = mandoc_strdup(cp);
2613
2614         /* Optional third argument: architecture. */
2615
2616         if ((nn = nn->next) == NULL)
2617                 return;
2618
2619         for (p = nn->string; *p != '\0'; p++)
2620                 *p = tolower((unsigned char)*p);
2621         mdoc->meta.arch = mandoc_strdup(nn->string);
2622
2623         /* Ignore fourth and later arguments. */
2624
2625         if ((nn = nn->next) != NULL)
2626                 mandoc_msg(MANDOCERR_ARG_EXCESS,
2627                     nn->line, nn->pos, "Dt ... %s", nn->string);
2628 }
2629
2630 static void
2631 post_bx(POST_ARGS)
2632 {
2633         struct roff_node        *n, *nch;
2634         const char              *macro;
2635
2636         post_delim_nb(mdoc);
2637
2638         n = mdoc->last;
2639         nch = n->child;
2640
2641         if (nch != NULL) {
2642                 macro = !strcmp(nch->string, "Open") ? "Ox" :
2643                     !strcmp(nch->string, "Net") ? "Nx" :
2644                     !strcmp(nch->string, "Free") ? "Fx" :
2645                     !strcmp(nch->string, "DragonFly") ? "Dx" : NULL;
2646                 if (macro != NULL)
2647                         mandoc_msg(MANDOCERR_BX,
2648                             n->line, n->pos, "%s", macro);
2649                 mdoc->last = nch;
2650                 nch = nch->next;
2651                 mdoc->next = ROFF_NEXT_SIBLING;
2652                 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2653                 mdoc->last->flags |= NODE_NOSRC;
2654                 mdoc->next = ROFF_NEXT_SIBLING;
2655         } else
2656                 mdoc->next = ROFF_NEXT_CHILD;
2657         roff_word_alloc(mdoc, n->line, n->pos, "BSD");
2658         mdoc->last->flags |= NODE_NOSRC;
2659
2660         if (nch == NULL) {
2661                 mdoc->last = n;
2662                 return;
2663         }
2664
2665         roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2666         mdoc->last->flags |= NODE_NOSRC;
2667         mdoc->next = ROFF_NEXT_SIBLING;
2668         roff_word_alloc(mdoc, n->line, n->pos, "-");
2669         mdoc->last->flags |= NODE_NOSRC;
2670         roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2671         mdoc->last->flags |= NODE_NOSRC;
2672         mdoc->last = n;
2673
2674         /*
2675          * Make `Bx's second argument always start with an uppercase
2676          * letter.  Groff checks if it's an "accepted" term, but we just
2677          * uppercase blindly.
2678          */
2679
2680         *nch->string = (char)toupper((unsigned char)*nch->string);
2681 }
2682
2683 static void
2684 post_os(POST_ARGS)
2685 {
2686 #ifndef OSNAME
2687         struct utsname    utsname;
2688         static char      *defbuf;
2689 #endif
2690         struct roff_node *n;
2691
2692         n = mdoc->last;
2693         n->flags |= NODE_NOPRT;
2694
2695         if (mdoc->meta.os != NULL)
2696                 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Os");
2697         else if (mdoc->flags & MDOC_PBODY)
2698                 mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Os");
2699
2700         post_delim(mdoc);
2701
2702         /*
2703          * Set the operating system by way of the `Os' macro.
2704          * The order of precedence is:
2705          * 1. the argument of the `Os' macro, unless empty
2706          * 2. the -Ios=foo command line argument, if provided
2707          * 3. -DOSNAME="\"foo\"", if provided during compilation
2708          * 4. "sysname release" from uname(3)
2709          */
2710
2711         free(mdoc->meta.os);
2712         mdoc->meta.os = NULL;
2713         deroff(&mdoc->meta.os, n);
2714         if (mdoc->meta.os)
2715                 goto out;
2716
2717         if (mdoc->os_s != NULL) {
2718                 mdoc->meta.os = mandoc_strdup(mdoc->os_s);
2719                 goto out;
2720         }
2721
2722 #ifdef OSNAME
2723         mdoc->meta.os = mandoc_strdup(OSNAME);
2724 #else /*!OSNAME */
2725         if (defbuf == NULL) {
2726                 if (uname(&utsname) == -1) {
2727                         mandoc_msg(MANDOCERR_OS_UNAME, n->line, n->pos, "Os");
2728                         defbuf = mandoc_strdup("UNKNOWN");
2729                 } else
2730                         mandoc_asprintf(&defbuf, "%s %s",
2731                             utsname.sysname, utsname.release);
2732         }
2733         mdoc->meta.os = mandoc_strdup(defbuf);
2734 #endif /*!OSNAME*/
2735
2736 out:
2737         if (mdoc->meta.os_e == MANDOC_OS_OTHER) {
2738                 if (strstr(mdoc->meta.os, "OpenBSD") != NULL)
2739                         mdoc->meta.os_e = MANDOC_OS_OPENBSD;
2740                 else if (strstr(mdoc->meta.os, "NetBSD") != NULL)
2741                         mdoc->meta.os_e = MANDOC_OS_NETBSD;
2742         }
2743
2744         /*
2745          * This is the earliest point where we can check
2746          * Mdocdate conventions because we don't know
2747          * the operating system earlier.
2748          */
2749
2750         if (n->child != NULL)
2751                 mandoc_msg(MANDOCERR_OS_ARG, n->child->line, n->child->pos,
2752                     "Os %s (%s)", n->child->string,
2753                     mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2754                     "OpenBSD" : "NetBSD");
2755
2756         while (n->tok != MDOC_Dd)
2757                 if ((n = n->prev) == NULL)
2758                         return;
2759         if ((n = n->child) == NULL)
2760                 return;
2761         if (strncmp(n->string, "$" "Mdocdate", 9)) {
2762                 if (mdoc->meta.os_e == MANDOC_OS_OPENBSD)
2763                         mandoc_msg(MANDOCERR_MDOCDATE_MISSING, n->line,
2764                             n->pos, "Dd %s (OpenBSD)", n->string);
2765         } else {
2766                 if (mdoc->meta.os_e == MANDOC_OS_NETBSD)
2767                         mandoc_msg(MANDOCERR_MDOCDATE, n->line,
2768                             n->pos, "Dd %s (NetBSD)", n->string);
2769         }
2770 }
2771
2772 enum roff_sec
2773 mdoc_a2sec(const char *p)
2774 {
2775         int              i;
2776
2777         for (i = 0; i < (int)SEC__MAX; i++)
2778                 if (secnames[i] && 0 == strcmp(p, secnames[i]))
2779                         return (enum roff_sec)i;
2780
2781         return SEC_CUSTOM;
2782 }
2783
2784 static size_t
2785 macro2len(enum roff_tok macro)
2786 {
2787
2788         switch (macro) {
2789         case MDOC_Ad:
2790                 return 12;
2791         case MDOC_Ao:
2792                 return 12;
2793         case MDOC_An:
2794                 return 12;
2795         case MDOC_Aq:
2796                 return 12;
2797         case MDOC_Ar:
2798                 return 12;
2799         case MDOC_Bo:
2800                 return 12;
2801         case MDOC_Bq:
2802                 return 12;
2803         case MDOC_Cd:
2804                 return 12;
2805         case MDOC_Cm:
2806                 return 10;
2807         case MDOC_Do:
2808                 return 10;
2809         case MDOC_Dq:
2810                 return 12;
2811         case MDOC_Dv:
2812                 return 12;
2813         case MDOC_Eo:
2814                 return 12;
2815         case MDOC_Em:
2816                 return 10;
2817         case MDOC_Er:
2818                 return 17;
2819         case MDOC_Ev:
2820                 return 15;
2821         case MDOC_Fa:
2822                 return 12;
2823         case MDOC_Fl:
2824                 return 10;
2825         case MDOC_Fo:
2826                 return 16;
2827         case MDOC_Fn:
2828                 return 16;
2829         case MDOC_Ic:
2830                 return 10;
2831         case MDOC_Li:
2832                 return 16;
2833         case MDOC_Ms:
2834                 return 6;
2835         case MDOC_Nm:
2836                 return 10;
2837         case MDOC_No:
2838                 return 12;
2839         case MDOC_Oo:
2840                 return 10;
2841         case MDOC_Op:
2842                 return 14;
2843         case MDOC_Pa:
2844                 return 32;
2845         case MDOC_Pf:
2846                 return 12;
2847         case MDOC_Po:
2848                 return 12;
2849         case MDOC_Pq:
2850                 return 12;
2851         case MDOC_Ql:
2852                 return 16;
2853         case MDOC_Qo:
2854                 return 12;
2855         case MDOC_So:
2856                 return 12;
2857         case MDOC_Sq:
2858                 return 12;
2859         case MDOC_Sy:
2860                 return 6;
2861         case MDOC_Sx:
2862                 return 16;
2863         case MDOC_Tn:
2864                 return 10;
2865         case MDOC_Va:
2866                 return 12;
2867         case MDOC_Vt:
2868                 return 12;
2869         case MDOC_Xr:
2870                 return 10;
2871         default:
2872                 break;
2873         };
2874         return 0;
2875 }