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