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