]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/mdocml/mdoc_validate.c
MFV r294491: ntp 4.2.8p6.
[FreeBSD/FreeBSD.git] / contrib / mdocml / mdoc_validate.c
1 /*      $Id: mdoc_validate.c,v 1.301 2016/01/08 17:48:09 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2010-2016 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 "roff.h"
37 #include "mdoc.h"
38 #include "libmandoc.h"
39 #include "roff_int.h"
40 #include "libmdoc.h"
41
42 /* FIXME: .Bl -diag can't have non-text children in HEAD. */
43
44 #define POST_ARGS struct roff_man *mdoc
45
46 enum    check_ineq {
47         CHECK_LT,
48         CHECK_GT,
49         CHECK_EQ
50 };
51
52 typedef void    (*v_post)(POST_ARGS);
53
54 static  void     check_text(struct roff_man *, int, int, char *);
55 static  void     check_argv(struct roff_man *,
56                         struct roff_node *, struct mdoc_argv *);
57 static  void     check_args(struct roff_man *, struct roff_node *);
58 static  int      child_an(const struct roff_node *);
59 static  size_t          macro2len(int);
60 static  void     rewrite_macro2len(char **);
61
62 static  void     post_an(POST_ARGS);
63 static  void     post_an_norm(POST_ARGS);
64 static  void     post_at(POST_ARGS);
65 static  void     post_bd(POST_ARGS);
66 static  void     post_bf(POST_ARGS);
67 static  void     post_bk(POST_ARGS);
68 static  void     post_bl(POST_ARGS);
69 static  void     post_bl_block(POST_ARGS);
70 static  void     post_bl_block_tag(POST_ARGS);
71 static  void     post_bl_head(POST_ARGS);
72 static  void     post_bl_norm(POST_ARGS);
73 static  void     post_bx(POST_ARGS);
74 static  void     post_defaults(POST_ARGS);
75 static  void     post_display(POST_ARGS);
76 static  void     post_dd(POST_ARGS);
77 static  void     post_dt(POST_ARGS);
78 static  void     post_en(POST_ARGS);
79 static  void     post_es(POST_ARGS);
80 static  void     post_eoln(POST_ARGS);
81 static  void     post_ex(POST_ARGS);
82 static  void     post_fa(POST_ARGS);
83 static  void     post_fn(POST_ARGS);
84 static  void     post_fname(POST_ARGS);
85 static  void     post_fo(POST_ARGS);
86 static  void     post_hyph(POST_ARGS);
87 static  void     post_ignpar(POST_ARGS);
88 static  void     post_it(POST_ARGS);
89 static  void     post_lb(POST_ARGS);
90 static  void     post_nd(POST_ARGS);
91 static  void     post_nm(POST_ARGS);
92 static  void     post_ns(POST_ARGS);
93 static  void     post_obsolete(POST_ARGS);
94 static  void     post_os(POST_ARGS);
95 static  void     post_par(POST_ARGS);
96 static  void     post_prevpar(POST_ARGS);
97 static  void     post_root(POST_ARGS);
98 static  void     post_rs(POST_ARGS);
99 static  void     post_sh(POST_ARGS);
100 static  void     post_sh_head(POST_ARGS);
101 static  void     post_sh_name(POST_ARGS);
102 static  void     post_sh_see_also(POST_ARGS);
103 static  void     post_sh_authors(POST_ARGS);
104 static  void     post_sm(POST_ARGS);
105 static  void     post_st(POST_ARGS);
106 static  void     post_std(POST_ARGS);
107
108 static  v_post mdoc_valids[MDOC_MAX] = {
109         NULL,           /* Ap */
110         post_dd,        /* Dd */
111         post_dt,        /* Dt */
112         post_os,        /* Os */
113         post_sh,        /* Sh */
114         post_ignpar,    /* Ss */
115         post_par,       /* Pp */
116         post_display,   /* D1 */
117         post_display,   /* Dl */
118         post_display,   /* Bd */
119         NULL,           /* Ed */
120         post_bl,        /* Bl */
121         NULL,           /* El */
122         post_it,        /* It */
123         NULL,           /* Ad */
124         post_an,        /* An */
125         post_defaults,  /* Ar */
126         NULL,           /* Cd */
127         NULL,           /* Cm */
128         NULL,           /* Dv */
129         NULL,           /* Er */
130         NULL,           /* Ev */
131         post_ex,        /* Ex */
132         post_fa,        /* Fa */
133         NULL,           /* Fd */
134         NULL,           /* Fl */
135         post_fn,        /* Fn */
136         NULL,           /* Ft */
137         NULL,           /* Ic */
138         NULL,           /* In */
139         post_defaults,  /* Li */
140         post_nd,        /* Nd */
141         post_nm,        /* Nm */
142         NULL,           /* Op */
143         post_obsolete,  /* Ot */
144         post_defaults,  /* Pa */
145         post_std,       /* Rv */
146         post_st,        /* St */
147         NULL,           /* Va */
148         NULL,           /* Vt */
149         NULL,           /* Xr */
150         NULL,           /* %A */
151         post_hyph,      /* %B */ /* FIXME: can be used outside Rs/Re. */
152         NULL,           /* %D */
153         NULL,           /* %I */
154         NULL,           /* %J */
155         post_hyph,      /* %N */
156         post_hyph,      /* %O */
157         NULL,           /* %P */
158         post_hyph,      /* %R */
159         post_hyph,      /* %T */ /* FIXME: can be used outside Rs/Re. */
160         NULL,           /* %V */
161         NULL,           /* Ac */
162         NULL,           /* Ao */
163         NULL,           /* Aq */
164         post_at,        /* At */
165         NULL,           /* Bc */
166         post_bf,        /* Bf */
167         NULL,           /* Bo */
168         NULL,           /* Bq */
169         NULL,           /* Bsx */
170         post_bx,        /* Bx */
171         post_obsolete,  /* Db */
172         NULL,           /* Dc */
173         NULL,           /* Do */
174         NULL,           /* Dq */
175         NULL,           /* Ec */
176         NULL,           /* Ef */
177         NULL,           /* Em */
178         NULL,           /* Eo */
179         NULL,           /* Fx */
180         NULL,           /* Ms */
181         NULL,           /* No */
182         post_ns,        /* Ns */
183         NULL,           /* Nx */
184         NULL,           /* Ox */
185         NULL,           /* Pc */
186         NULL,           /* Pf */
187         NULL,           /* Po */
188         NULL,           /* Pq */
189         NULL,           /* Qc */
190         NULL,           /* Ql */
191         NULL,           /* Qo */
192         NULL,           /* Qq */
193         NULL,           /* Re */
194         post_rs,        /* Rs */
195         NULL,           /* Sc */
196         NULL,           /* So */
197         NULL,           /* Sq */
198         post_sm,        /* Sm */
199         post_hyph,      /* Sx */
200         NULL,           /* Sy */
201         NULL,           /* Tn */
202         NULL,           /* Ux */
203         NULL,           /* Xc */
204         NULL,           /* Xo */
205         post_fo,        /* Fo */
206         NULL,           /* Fc */
207         NULL,           /* Oo */
208         NULL,           /* Oc */
209         post_bk,        /* Bk */
210         NULL,           /* Ek */
211         post_eoln,      /* Bt */
212         NULL,           /* Hf */
213         post_obsolete,  /* Fr */
214         post_eoln,      /* Ud */
215         post_lb,        /* Lb */
216         post_par,       /* Lp */
217         NULL,           /* Lk */
218         post_defaults,  /* Mt */
219         NULL,           /* Brq */
220         NULL,           /* Bro */
221         NULL,           /* Brc */
222         NULL,           /* %C */
223         post_es,        /* Es */
224         post_en,        /* En */
225         NULL,           /* Dx */
226         NULL,           /* %Q */
227         post_par,       /* br */
228         post_par,       /* sp */
229         NULL,           /* %U */
230         NULL,           /* Ta */
231         NULL,           /* ll */
232 };
233
234 #define RSORD_MAX 14 /* Number of `Rs' blocks. */
235
236 static  const int rsord[RSORD_MAX] = {
237         MDOC__A,
238         MDOC__T,
239         MDOC__B,
240         MDOC__I,
241         MDOC__J,
242         MDOC__R,
243         MDOC__N,
244         MDOC__V,
245         MDOC__U,
246         MDOC__P,
247         MDOC__Q,
248         MDOC__C,
249         MDOC__D,
250         MDOC__O
251 };
252
253 static  const char * const secnames[SEC__MAX] = {
254         NULL,
255         "NAME",
256         "LIBRARY",
257         "SYNOPSIS",
258         "DESCRIPTION",
259         "CONTEXT",
260         "IMPLEMENTATION NOTES",
261         "RETURN VALUES",
262         "ENVIRONMENT",
263         "FILES",
264         "EXIT STATUS",
265         "EXAMPLES",
266         "DIAGNOSTICS",
267         "COMPATIBILITY",
268         "ERRORS",
269         "SEE ALSO",
270         "STANDARDS",
271         "HISTORY",
272         "AUTHORS",
273         "CAVEATS",
274         "BUGS",
275         "SECURITY CONSIDERATIONS",
276         NULL
277 };
278
279
280 void
281 mdoc_node_validate(struct roff_man *mdoc)
282 {
283         struct roff_node *n;
284         v_post *p;
285
286         n = mdoc->last;
287         mdoc->last = mdoc->last->child;
288         while (mdoc->last != NULL) {
289                 mdoc_node_validate(mdoc);
290                 if (mdoc->last == n)
291                         mdoc->last = mdoc->last->child;
292                 else
293                         mdoc->last = mdoc->last->next;
294         }
295
296         mdoc->last = n;
297         mdoc->next = ROFF_NEXT_SIBLING;
298         switch (n->type) {
299         case ROFFT_TEXT:
300                 if (n->sec != SEC_SYNOPSIS || n->parent->tok != MDOC_Fd)
301                         check_text(mdoc, n->line, n->pos, n->string);
302                 break;
303         case ROFFT_EQN:
304         case ROFFT_TBL:
305                 break;
306         case ROFFT_ROOT:
307                 post_root(mdoc);
308                 break;
309         default:
310                 check_args(mdoc, mdoc->last);
311
312                 /*
313                  * Closing delimiters are not special at the
314                  * beginning of a block, opening delimiters
315                  * are not special at the end.
316                  */
317
318                 if (n->child != NULL)
319                         n->child->flags &= ~MDOC_DELIMC;
320                 if (n->last != NULL)
321                         n->last->flags &= ~MDOC_DELIMO;
322
323                 /* Call the macro's postprocessor. */
324
325                 p = mdoc_valids + n->tok;
326                 if (*p)
327                         (*p)(mdoc);
328                 if (mdoc->last == n)
329                         mdoc_state(mdoc, n);
330                 break;
331         }
332 }
333
334 static void
335 check_args(struct roff_man *mdoc, struct roff_node *n)
336 {
337         int              i;
338
339         if (NULL == n->args)
340                 return;
341
342         assert(n->args->argc);
343         for (i = 0; i < (int)n->args->argc; i++)
344                 check_argv(mdoc, n, &n->args->argv[i]);
345 }
346
347 static void
348 check_argv(struct roff_man *mdoc, struct roff_node *n, struct mdoc_argv *v)
349 {
350         int              i;
351
352         for (i = 0; i < (int)v->sz; i++)
353                 check_text(mdoc, v->line, v->pos, v->value[i]);
354 }
355
356 static void
357 check_text(struct roff_man *mdoc, int ln, int pos, char *p)
358 {
359         char            *cp;
360
361         if (MDOC_LITERAL & mdoc->flags)
362                 return;
363
364         for (cp = p; NULL != (p = strchr(p, '\t')); p++)
365                 mandoc_msg(MANDOCERR_FI_TAB, mdoc->parse,
366                     ln, pos + (int)(p - cp), NULL);
367 }
368
369 static void
370 post_bl_norm(POST_ARGS)
371 {
372         struct roff_node *n;
373         struct mdoc_argv *argv, *wa;
374         int               i;
375         enum mdocargt     mdoclt;
376         enum mdoc_list    lt;
377
378         n = mdoc->last->parent;
379         n->norm->Bl.type = LIST__NONE;
380
381         /*
382          * First figure out which kind of list to use: bind ourselves to
383          * the first mentioned list type and warn about any remaining
384          * ones.  If we find no list type, we default to LIST_item.
385          */
386
387         wa = (n->args == NULL) ? NULL : n->args->argv;
388         mdoclt = MDOC_ARG_MAX;
389         for (i = 0; n->args && i < (int)n->args->argc; i++) {
390                 argv = n->args->argv + i;
391                 lt = LIST__NONE;
392                 switch (argv->arg) {
393                 /* Set list types. */
394                 case MDOC_Bullet:
395                         lt = LIST_bullet;
396                         break;
397                 case MDOC_Dash:
398                         lt = LIST_dash;
399                         break;
400                 case MDOC_Enum:
401                         lt = LIST_enum;
402                         break;
403                 case MDOC_Hyphen:
404                         lt = LIST_hyphen;
405                         break;
406                 case MDOC_Item:
407                         lt = LIST_item;
408                         break;
409                 case MDOC_Tag:
410                         lt = LIST_tag;
411                         break;
412                 case MDOC_Diag:
413                         lt = LIST_diag;
414                         break;
415                 case MDOC_Hang:
416                         lt = LIST_hang;
417                         break;
418                 case MDOC_Ohang:
419                         lt = LIST_ohang;
420                         break;
421                 case MDOC_Inset:
422                         lt = LIST_inset;
423                         break;
424                 case MDOC_Column:
425                         lt = LIST_column;
426                         break;
427                 /* Set list arguments. */
428                 case MDOC_Compact:
429                         if (n->norm->Bl.comp)
430                                 mandoc_msg(MANDOCERR_ARG_REP,
431                                     mdoc->parse, argv->line,
432                                     argv->pos, "Bl -compact");
433                         n->norm->Bl.comp = 1;
434                         break;
435                 case MDOC_Width:
436                         wa = argv;
437                         if (0 == argv->sz) {
438                                 mandoc_msg(MANDOCERR_ARG_EMPTY,
439                                     mdoc->parse, argv->line,
440                                     argv->pos, "Bl -width");
441                                 n->norm->Bl.width = "0n";
442                                 break;
443                         }
444                         if (NULL != n->norm->Bl.width)
445                                 mandoc_vmsg(MANDOCERR_ARG_REP,
446                                     mdoc->parse, argv->line,
447                                     argv->pos, "Bl -width %s",
448                                     argv->value[0]);
449                         rewrite_macro2len(argv->value);
450                         n->norm->Bl.width = argv->value[0];
451                         break;
452                 case MDOC_Offset:
453                         if (0 == argv->sz) {
454                                 mandoc_msg(MANDOCERR_ARG_EMPTY,
455                                     mdoc->parse, argv->line,
456                                     argv->pos, "Bl -offset");
457                                 break;
458                         }
459                         if (NULL != n->norm->Bl.offs)
460                                 mandoc_vmsg(MANDOCERR_ARG_REP,
461                                     mdoc->parse, argv->line,
462                                     argv->pos, "Bl -offset %s",
463                                     argv->value[0]);
464                         rewrite_macro2len(argv->value);
465                         n->norm->Bl.offs = argv->value[0];
466                         break;
467                 default:
468                         continue;
469                 }
470                 if (LIST__NONE == lt)
471                         continue;
472                 mdoclt = argv->arg;
473
474                 /* Check: multiple list types. */
475
476                 if (LIST__NONE != n->norm->Bl.type) {
477                         mandoc_vmsg(MANDOCERR_BL_REP,
478                             mdoc->parse, n->line, n->pos,
479                             "Bl -%s", mdoc_argnames[argv->arg]);
480                         continue;
481                 }
482
483                 /* The list type should come first. */
484
485                 if (n->norm->Bl.width ||
486                     n->norm->Bl.offs ||
487                     n->norm->Bl.comp)
488                         mandoc_vmsg(MANDOCERR_BL_LATETYPE,
489                             mdoc->parse, n->line, n->pos, "Bl -%s",
490                             mdoc_argnames[n->args->argv[0].arg]);
491
492                 n->norm->Bl.type = lt;
493                 if (LIST_column == lt) {
494                         n->norm->Bl.ncols = argv->sz;
495                         n->norm->Bl.cols = (void *)argv->value;
496                 }
497         }
498
499         /* Allow lists to default to LIST_item. */
500
501         if (LIST__NONE == n->norm->Bl.type) {
502                 mandoc_msg(MANDOCERR_BL_NOTYPE, mdoc->parse,
503                     n->line, n->pos, "Bl");
504                 n->norm->Bl.type = LIST_item;
505         }
506
507         /*
508          * Validate the width field.  Some list types don't need width
509          * types and should be warned about them.  Others should have it
510          * and must also be warned.  Yet others have a default and need
511          * no warning.
512          */
513
514         switch (n->norm->Bl.type) {
515         case LIST_tag:
516                 if (NULL == n->norm->Bl.width)
517                         mandoc_msg(MANDOCERR_BL_NOWIDTH, mdoc->parse,
518                             n->line, n->pos, "Bl -tag");
519                 break;
520         case LIST_column:
521         case LIST_diag:
522         case LIST_ohang:
523         case LIST_inset:
524         case LIST_item:
525                 if (n->norm->Bl.width)
526                         mandoc_vmsg(MANDOCERR_BL_SKIPW, mdoc->parse,
527                             wa->line, wa->pos, "Bl -%s",
528                             mdoc_argnames[mdoclt]);
529                 break;
530         case LIST_bullet:
531         case LIST_dash:
532         case LIST_hyphen:
533                 if (NULL == n->norm->Bl.width)
534                         n->norm->Bl.width = "2n";
535                 break;
536         case LIST_enum:
537                 if (NULL == n->norm->Bl.width)
538                         n->norm->Bl.width = "3n";
539                 break;
540         default:
541                 break;
542         }
543 }
544
545 static void
546 post_bd(POST_ARGS)
547 {
548         struct roff_node *n;
549         struct mdoc_argv *argv;
550         int               i;
551         enum mdoc_disp    dt;
552
553         n = mdoc->last;
554         for (i = 0; n->args && i < (int)n->args->argc; i++) {
555                 argv = n->args->argv + i;
556                 dt = DISP__NONE;
557
558                 switch (argv->arg) {
559                 case MDOC_Centred:
560                         dt = DISP_centered;
561                         break;
562                 case MDOC_Ragged:
563                         dt = DISP_ragged;
564                         break;
565                 case MDOC_Unfilled:
566                         dt = DISP_unfilled;
567                         break;
568                 case MDOC_Filled:
569                         dt = DISP_filled;
570                         break;
571                 case MDOC_Literal:
572                         dt = DISP_literal;
573                         break;
574                 case MDOC_File:
575                         mandoc_msg(MANDOCERR_BD_FILE, mdoc->parse,
576                             n->line, n->pos, NULL);
577                         break;
578                 case MDOC_Offset:
579                         if (0 == argv->sz) {
580                                 mandoc_msg(MANDOCERR_ARG_EMPTY,
581                                     mdoc->parse, argv->line,
582                                     argv->pos, "Bd -offset");
583                                 break;
584                         }
585                         if (NULL != n->norm->Bd.offs)
586                                 mandoc_vmsg(MANDOCERR_ARG_REP,
587                                     mdoc->parse, argv->line,
588                                     argv->pos, "Bd -offset %s",
589                                     argv->value[0]);
590                         rewrite_macro2len(argv->value);
591                         n->norm->Bd.offs = argv->value[0];
592                         break;
593                 case MDOC_Compact:
594                         if (n->norm->Bd.comp)
595                                 mandoc_msg(MANDOCERR_ARG_REP,
596                                     mdoc->parse, argv->line,
597                                     argv->pos, "Bd -compact");
598                         n->norm->Bd.comp = 1;
599                         break;
600                 default:
601                         abort();
602                 }
603                 if (DISP__NONE == dt)
604                         continue;
605
606                 if (DISP__NONE == n->norm->Bd.type)
607                         n->norm->Bd.type = dt;
608                 else
609                         mandoc_vmsg(MANDOCERR_BD_REP,
610                             mdoc->parse, n->line, n->pos,
611                             "Bd -%s", mdoc_argnames[argv->arg]);
612         }
613
614         if (DISP__NONE == n->norm->Bd.type) {
615                 mandoc_msg(MANDOCERR_BD_NOTYPE, mdoc->parse,
616                     n->line, n->pos, "Bd");
617                 n->norm->Bd.type = DISP_ragged;
618         }
619 }
620
621 static void
622 post_an_norm(POST_ARGS)
623 {
624         struct roff_node *n;
625         struct mdoc_argv *argv;
626         size_t   i;
627
628         n = mdoc->last;
629         if (n->args == NULL)
630                 return;
631
632         for (i = 1; i < n->args->argc; i++) {
633                 argv = n->args->argv + i;
634                 mandoc_vmsg(MANDOCERR_AN_REP,
635                     mdoc->parse, argv->line, argv->pos,
636                     "An -%s", mdoc_argnames[argv->arg]);
637         }
638
639         argv = n->args->argv;
640         if (argv->arg == MDOC_Split)
641                 n->norm->An.auth = AUTH_split;
642         else if (argv->arg == MDOC_Nosplit)
643                 n->norm->An.auth = AUTH_nosplit;
644         else
645                 abort();
646 }
647
648 static void
649 post_std(POST_ARGS)
650 {
651         struct roff_node *n;
652
653         n = mdoc->last;
654         if (n->args && n->args->argc == 1)
655                 if (n->args->argv[0].arg == MDOC_Std)
656                         return;
657
658         mandoc_msg(MANDOCERR_ARG_STD, mdoc->parse,
659             n->line, n->pos, mdoc_macronames[n->tok]);
660 }
661
662 static void
663 post_obsolete(POST_ARGS)
664 {
665         struct roff_node *n;
666
667         n = mdoc->last;
668         if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK)
669                 mandoc_msg(MANDOCERR_MACRO_OBS, mdoc->parse,
670                     n->line, n->pos, mdoc_macronames[n->tok]);
671 }
672
673 static void
674 post_bf(POST_ARGS)
675 {
676         struct roff_node *np, *nch;
677
678         /*
679          * Unlike other data pointers, these are "housed" by the HEAD
680          * element, which contains the goods.
681          */
682
683         np = mdoc->last;
684         if (np->type != ROFFT_HEAD)
685                 return;
686
687         assert(np->parent->type == ROFFT_BLOCK);
688         assert(np->parent->tok == MDOC_Bf);
689
690         /* Check the number of arguments. */
691
692         nch = np->child;
693         if (np->parent->args == NULL) {
694                 if (nch == NULL) {
695                         mandoc_msg(MANDOCERR_BF_NOFONT, mdoc->parse,
696                             np->line, np->pos, "Bf");
697                         return;
698                 }
699                 nch = nch->next;
700         }
701         if (nch != NULL)
702                 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
703                     nch->line, nch->pos, "Bf ... %s", nch->string);
704
705         /* Extract argument into data. */
706
707         if (np->parent->args != NULL) {
708                 switch (np->parent->args->argv[0].arg) {
709                 case MDOC_Emphasis:
710                         np->norm->Bf.font = FONT_Em;
711                         break;
712                 case MDOC_Literal:
713                         np->norm->Bf.font = FONT_Li;
714                         break;
715                 case MDOC_Symbolic:
716                         np->norm->Bf.font = FONT_Sy;
717                         break;
718                 default:
719                         abort();
720                 }
721                 return;
722         }
723
724         /* Extract parameter into data. */
725
726         if ( ! strcmp(np->child->string, "Em"))
727                 np->norm->Bf.font = FONT_Em;
728         else if ( ! strcmp(np->child->string, "Li"))
729                 np->norm->Bf.font = FONT_Li;
730         else if ( ! strcmp(np->child->string, "Sy"))
731                 np->norm->Bf.font = FONT_Sy;
732         else
733                 mandoc_vmsg(MANDOCERR_BF_BADFONT, mdoc->parse,
734                     np->child->line, np->child->pos,
735                     "Bf %s", np->child->string);
736 }
737
738 static void
739 post_lb(POST_ARGS)
740 {
741         struct roff_node        *n;
742         const char              *stdlibname;
743         char                    *libname;
744
745         n = mdoc->last->child;
746         assert(n->type == ROFFT_TEXT);
747
748         if (NULL == (stdlibname = mdoc_a2lib(n->string)))
749                 mandoc_asprintf(&libname,
750                     "library \\(Lq%s\\(Rq", n->string);
751         else
752                 libname = mandoc_strdup(stdlibname);
753
754         free(n->string);
755         n->string = libname;
756 }
757
758 static void
759 post_eoln(POST_ARGS)
760 {
761         const struct roff_node *n;
762
763         n = mdoc->last;
764         if (n->child != NULL)
765                 mandoc_vmsg(MANDOCERR_ARG_SKIP,
766                     mdoc->parse, n->line, n->pos,
767                     "%s %s", mdoc_macronames[n->tok],
768                     n->child->string);
769 }
770
771 static void
772 post_fname(POST_ARGS)
773 {
774         const struct roff_node  *n;
775         const char              *cp;
776         size_t                   pos;
777
778         n = mdoc->last->child;
779         pos = strcspn(n->string, "()");
780         cp = n->string + pos;
781         if ( ! (cp[0] == '\0' || (cp[0] == '(' && cp[1] == '*')))
782                 mandoc_msg(MANDOCERR_FN_PAREN, mdoc->parse,
783                     n->line, n->pos + pos, n->string);
784 }
785
786 static void
787 post_fn(POST_ARGS)
788 {
789
790         post_fname(mdoc);
791         post_fa(mdoc);
792 }
793
794 static void
795 post_fo(POST_ARGS)
796 {
797         const struct roff_node  *n;
798
799         n = mdoc->last;
800
801         if (n->type != ROFFT_HEAD)
802                 return;
803
804         if (n->child == NULL) {
805                 mandoc_msg(MANDOCERR_FO_NOHEAD, mdoc->parse,
806                     n->line, n->pos, "Fo");
807                 return;
808         }
809         if (n->child != n->last) {
810                 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
811                     n->child->next->line, n->child->next->pos,
812                     "Fo ... %s", n->child->next->string);
813                 while (n->child != n->last)
814                         roff_node_delete(mdoc, n->last);
815         }
816
817         post_fname(mdoc);
818 }
819
820 static void
821 post_fa(POST_ARGS)
822 {
823         const struct roff_node *n;
824         const char *cp;
825
826         for (n = mdoc->last->child; n != NULL; n = n->next) {
827                 for (cp = n->string; *cp != '\0'; cp++) {
828                         /* Ignore callbacks and alterations. */
829                         if (*cp == '(' || *cp == '{')
830                                 break;
831                         if (*cp != ',')
832                                 continue;
833                         mandoc_msg(MANDOCERR_FA_COMMA, mdoc->parse,
834                             n->line, n->pos + (cp - n->string),
835                             n->string);
836                         break;
837                 }
838         }
839 }
840
841 static void
842 post_nm(POST_ARGS)
843 {
844         struct roff_node        *n;
845
846         n = mdoc->last;
847
848         if (n->last != NULL &&
849             (n->last->tok == MDOC_Pp ||
850              n->last->tok == MDOC_Lp))
851                 mdoc_node_relink(mdoc, n->last);
852
853         if (mdoc->meta.name != NULL)
854                 return;
855
856         deroff(&mdoc->meta.name, n);
857
858         if (mdoc->meta.name == NULL)
859                 mandoc_msg(MANDOCERR_NM_NONAME, mdoc->parse,
860                     n->line, n->pos, "Nm");
861 }
862
863 static void
864 post_nd(POST_ARGS)
865 {
866         struct roff_node        *n;
867
868         n = mdoc->last;
869
870         if (n->type != ROFFT_BODY)
871                 return;
872
873         if (n->child == NULL)
874                 mandoc_msg(MANDOCERR_ND_EMPTY, mdoc->parse,
875                     n->line, n->pos, "Nd");
876
877         post_hyph(mdoc);
878 }
879
880 static void
881 post_display(POST_ARGS)
882 {
883         struct roff_node *n, *np;
884
885         n = mdoc->last;
886         switch (n->type) {
887         case ROFFT_BODY:
888                 if (n->end != ENDBODY_NOT)
889                         break;
890                 if (n->child == NULL)
891                         mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
892                             n->line, n->pos, mdoc_macronames[n->tok]);
893                 else if (n->tok == MDOC_D1)
894                         post_hyph(mdoc);
895                 break;
896         case ROFFT_BLOCK:
897                 if (n->tok == MDOC_Bd) {
898                         if (n->args == NULL) {
899                                 mandoc_msg(MANDOCERR_BD_NOARG,
900                                     mdoc->parse, n->line, n->pos, "Bd");
901                                 mdoc->next = ROFF_NEXT_SIBLING;
902                                 while (n->body->child != NULL)
903                                         mdoc_node_relink(mdoc,
904                                             n->body->child);
905                                 roff_node_delete(mdoc, n);
906                                 break;
907                         }
908                         post_bd(mdoc);
909                         post_prevpar(mdoc);
910                 }
911                 for (np = n->parent; np != NULL; np = np->parent) {
912                         if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) {
913                                 mandoc_vmsg(MANDOCERR_BD_NEST,
914                                     mdoc->parse, n->line, n->pos,
915                                     "%s in Bd", mdoc_macronames[n->tok]);
916                                 break;
917                         }
918                 }
919                 break;
920         default:
921                 break;
922         }
923 }
924
925 static void
926 post_defaults(POST_ARGS)
927 {
928         struct roff_node *nn;
929
930         /*
931          * The `Ar' defaults to "file ..." if no value is provided as an
932          * argument; the `Mt' and `Pa' macros use "~"; the `Li' just
933          * gets an empty string.
934          */
935
936         if (mdoc->last->child != NULL)
937                 return;
938
939         nn = mdoc->last;
940
941         switch (nn->tok) {
942         case MDOC_Ar:
943                 mdoc->next = ROFF_NEXT_CHILD;
944                 roff_word_alloc(mdoc, nn->line, nn->pos, "file");
945                 roff_word_alloc(mdoc, nn->line, nn->pos, "...");
946                 break;
947         case MDOC_Pa:
948         case MDOC_Mt:
949                 mdoc->next = ROFF_NEXT_CHILD;
950                 roff_word_alloc(mdoc, nn->line, nn->pos, "~");
951                 break;
952         default:
953                 abort();
954         }
955         mdoc->last = nn;
956 }
957
958 static void
959 post_at(POST_ARGS)
960 {
961         struct roff_node        *n;
962         const char              *std_att;
963         char                    *att;
964
965         n = mdoc->last;
966         if (n->child == NULL) {
967                 mdoc->next = ROFF_NEXT_CHILD;
968                 roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
969                 mdoc->last = n;
970                 return;
971         }
972
973         /*
974          * If we have a child, look it up in the standard keys.  If a
975          * key exist, use that instead of the child; if it doesn't,
976          * prefix "AT&T UNIX " to the existing data.
977          */
978
979         n = n->child;
980         assert(n->type == ROFFT_TEXT);
981         if ((std_att = mdoc_a2att(n->string)) == NULL) {
982                 mandoc_vmsg(MANDOCERR_AT_BAD, mdoc->parse,
983                     n->line, n->pos, "At %s", n->string);
984                 mandoc_asprintf(&att, "AT&T UNIX %s", n->string);
985         } else
986                 att = mandoc_strdup(std_att);
987
988         free(n->string);
989         n->string = att;
990 }
991
992 static void
993 post_an(POST_ARGS)
994 {
995         struct roff_node *np, *nch;
996
997         post_an_norm(mdoc);
998
999         np = mdoc->last;
1000         nch = np->child;
1001         if (np->norm->An.auth == AUTH__NONE) {
1002                 if (nch == NULL)
1003                         mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
1004                             np->line, np->pos, "An");
1005         } else if (nch != NULL)
1006                 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1007                     nch->line, nch->pos, "An ... %s", nch->string);
1008 }
1009
1010 static void
1011 post_en(POST_ARGS)
1012 {
1013
1014         post_obsolete(mdoc);
1015         if (mdoc->last->type == ROFFT_BLOCK)
1016                 mdoc->last->norm->Es = mdoc->last_es;
1017 }
1018
1019 static void
1020 post_es(POST_ARGS)
1021 {
1022
1023         post_obsolete(mdoc);
1024         mdoc->last_es = mdoc->last;
1025 }
1026
1027 static void
1028 post_it(POST_ARGS)
1029 {
1030         struct roff_node *nbl, *nit, *nch;
1031         int               i, cols;
1032         enum mdoc_list    lt;
1033
1034         post_prevpar(mdoc);
1035
1036         nit = mdoc->last;
1037         if (nit->type != ROFFT_BLOCK)
1038                 return;
1039
1040         nbl = nit->parent->parent;
1041         lt = nbl->norm->Bl.type;
1042
1043         switch (lt) {
1044         case LIST_tag:
1045         case LIST_hang:
1046         case LIST_ohang:
1047         case LIST_inset:
1048         case LIST_diag:
1049                 if (nit->head->child == NULL)
1050                         mandoc_vmsg(MANDOCERR_IT_NOHEAD,
1051                             mdoc->parse, nit->line, nit->pos,
1052                             "Bl -%s It",
1053                             mdoc_argnames[nbl->args->argv[0].arg]);
1054                 break;
1055         case LIST_bullet:
1056         case LIST_dash:
1057         case LIST_enum:
1058         case LIST_hyphen:
1059                 if (nit->body == NULL || nit->body->child == NULL)
1060                         mandoc_vmsg(MANDOCERR_IT_NOBODY,
1061                             mdoc->parse, nit->line, nit->pos,
1062                             "Bl -%s It",
1063                             mdoc_argnames[nbl->args->argv[0].arg]);
1064                 /* FALLTHROUGH */
1065         case LIST_item:
1066                 if (nit->head->child != NULL)
1067                         mandoc_vmsg(MANDOCERR_ARG_SKIP,
1068                             mdoc->parse, nit->line, nit->pos,
1069                             "It %s", nit->head->child->string);
1070                 break;
1071         case LIST_column:
1072                 cols = (int)nbl->norm->Bl.ncols;
1073
1074                 assert(nit->head->child == NULL);
1075
1076                 i = 0;
1077                 for (nch = nit->child; nch != NULL; nch = nch->next)
1078                         if (nch->type == ROFFT_BODY)
1079                                 i++;
1080
1081                 if (i < cols || i > cols + 1)
1082                         mandoc_vmsg(MANDOCERR_BL_COL,
1083                             mdoc->parse, nit->line, nit->pos,
1084                             "%d columns, %d cells", cols, i);
1085                 break;
1086         default:
1087                 abort();
1088         }
1089 }
1090
1091 static void
1092 post_bl_block(POST_ARGS)
1093 {
1094         struct roff_node *n, *ni, *nc;
1095
1096         post_prevpar(mdoc);
1097
1098         /*
1099          * These are fairly complicated, so we've broken them into two
1100          * functions.  post_bl_block_tag() is called when a -tag is
1101          * specified, but no -width (it must be guessed).  The second
1102          * when a -width is specified (macro indicators must be
1103          * rewritten into real lengths).
1104          */
1105
1106         n = mdoc->last;
1107
1108         if (n->norm->Bl.type == LIST_tag &&
1109             n->norm->Bl.width == NULL) {
1110                 post_bl_block_tag(mdoc);
1111                 assert(n->norm->Bl.width != NULL);
1112         }
1113
1114         for (ni = n->body->child; ni != NULL; ni = ni->next) {
1115                 if (ni->body == NULL)
1116                         continue;
1117                 nc = ni->body->last;
1118                 while (nc != NULL) {
1119                         switch (nc->tok) {
1120                         case MDOC_Pp:
1121                         case MDOC_Lp:
1122                         case MDOC_br:
1123                                 break;
1124                         default:
1125                                 nc = NULL;
1126                                 continue;
1127                         }
1128                         if (ni->next == NULL) {
1129                                 mandoc_msg(MANDOCERR_PAR_MOVE,
1130                                     mdoc->parse, nc->line, nc->pos,
1131                                     mdoc_macronames[nc->tok]);
1132                                 mdoc_node_relink(mdoc, nc);
1133                         } else if (n->norm->Bl.comp == 0 &&
1134                             n->norm->Bl.type != LIST_column) {
1135                                 mandoc_vmsg(MANDOCERR_PAR_SKIP,
1136                                     mdoc->parse, nc->line, nc->pos,
1137                                     "%s before It",
1138                                     mdoc_macronames[nc->tok]);
1139                                 roff_node_delete(mdoc, nc);
1140                         } else
1141                                 break;
1142                         nc = ni->body->last;
1143                 }
1144         }
1145 }
1146
1147 /*
1148  * If the argument of -offset or -width is a macro,
1149  * replace it with the associated default width.
1150  */
1151 void
1152 rewrite_macro2len(char **arg)
1153 {
1154         size_t            width;
1155         int               tok;
1156
1157         if (*arg == NULL)
1158                 return;
1159         else if ( ! strcmp(*arg, "Ds"))
1160                 width = 6;
1161         else if ((tok = mdoc_hash_find(*arg)) == TOKEN_NONE)
1162                 return;
1163         else
1164                 width = macro2len(tok);
1165
1166         free(*arg);
1167         mandoc_asprintf(arg, "%zun", width);
1168 }
1169
1170 static void
1171 post_bl_block_tag(POST_ARGS)
1172 {
1173         struct roff_node *n, *nn;
1174         size_t            sz, ssz;
1175         int               i;
1176         char              buf[24];
1177
1178         /*
1179          * Calculate the -width for a `Bl -tag' list if it hasn't been
1180          * provided.  Uses the first head macro.  NOTE AGAIN: this is
1181          * ONLY if the -width argument has NOT been provided.  See
1182          * rewrite_macro2len() for converting the -width string.
1183          */
1184
1185         sz = 10;
1186         n = mdoc->last;
1187
1188         for (nn = n->body->child; nn != NULL; nn = nn->next) {
1189                 if (nn->tok != MDOC_It)
1190                         continue;
1191
1192                 assert(nn->type == ROFFT_BLOCK);
1193                 nn = nn->head->child;
1194
1195                 if (nn == NULL)
1196                         break;
1197
1198                 if (nn->type == ROFFT_TEXT) {
1199                         sz = strlen(nn->string) + 1;
1200                         break;
1201                 }
1202
1203                 if (0 != (ssz = macro2len(nn->tok)))
1204                         sz = ssz;
1205
1206                 break;
1207         }
1208
1209         /* Defaults to ten ens. */
1210
1211         (void)snprintf(buf, sizeof(buf), "%un", (unsigned int)sz);
1212
1213         /*
1214          * We have to dynamically add this to the macro's argument list.
1215          * We're guaranteed that a MDOC_Width doesn't already exist.
1216          */
1217
1218         assert(n->args != NULL);
1219         i = (int)(n->args->argc)++;
1220
1221         n->args->argv = mandoc_reallocarray(n->args->argv,
1222             n->args->argc, sizeof(struct mdoc_argv));
1223
1224         n->args->argv[i].arg = MDOC_Width;
1225         n->args->argv[i].line = n->line;
1226         n->args->argv[i].pos = n->pos;
1227         n->args->argv[i].sz = 1;
1228         n->args->argv[i].value = mandoc_malloc(sizeof(char *));
1229         n->args->argv[i].value[0] = mandoc_strdup(buf);
1230
1231         /* Set our width! */
1232         n->norm->Bl.width = n->args->argv[i].value[0];
1233 }
1234
1235 static void
1236 post_bl_head(POST_ARGS)
1237 {
1238         struct roff_node *nbl, *nh, *nch, *nnext;
1239         struct mdoc_argv *argv;
1240         int               i, j;
1241
1242         post_bl_norm(mdoc);
1243
1244         nh = mdoc->last;
1245         if (nh->norm->Bl.type != LIST_column) {
1246                 if ((nch = nh->child) == NULL)
1247                         return;
1248                 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1249                     nch->line, nch->pos, "Bl ... %s", nch->string);
1250                 while (nch != NULL) {
1251                         roff_node_delete(mdoc, nch);
1252                         nch = nh->child;
1253                 }
1254                 return;
1255         }
1256
1257         /*
1258          * Append old-style lists, where the column width specifiers
1259          * trail as macro parameters, to the new-style ("normal-form")
1260          * lists where they're argument values following -column.
1261          */
1262
1263         if (nh->child == NULL)
1264                 return;
1265
1266         nbl = nh->parent;
1267         for (j = 0; j < (int)nbl->args->argc; j++)
1268                 if (nbl->args->argv[j].arg == MDOC_Column)
1269                         break;
1270
1271         assert(j < (int)nbl->args->argc);
1272
1273         /*
1274          * Accommodate for new-style groff column syntax.  Shuffle the
1275          * child nodes, all of which must be TEXT, as arguments for the
1276          * column field.  Then, delete the head children.
1277          */
1278
1279         argv = nbl->args->argv + j;
1280         i = argv->sz;
1281         for (nch = nh->child; nch != NULL; nch = nch->next)
1282                 argv->sz++;
1283         argv->value = mandoc_reallocarray(argv->value,
1284             argv->sz, sizeof(char *));
1285
1286         nh->norm->Bl.ncols = argv->sz;
1287         nh->norm->Bl.cols = (void *)argv->value;
1288
1289         for (nch = nh->child; nch != NULL; nch = nnext) {
1290                 argv->value[i++] = nch->string;
1291                 nch->string = NULL;
1292                 nnext = nch->next;
1293                 roff_node_delete(NULL, nch);
1294         }
1295         nh->child = NULL;
1296 }
1297
1298 static void
1299 post_bl(POST_ARGS)
1300 {
1301         struct roff_node        *nparent, *nprev; /* of the Bl block */
1302         struct roff_node        *nblock, *nbody;  /* of the Bl */
1303         struct roff_node        *nchild, *nnext;  /* of the Bl body */
1304
1305         nbody = mdoc->last;
1306         switch (nbody->type) {
1307         case ROFFT_BLOCK:
1308                 post_bl_block(mdoc);
1309                 return;
1310         case ROFFT_HEAD:
1311                 post_bl_head(mdoc);
1312                 return;
1313         case ROFFT_BODY:
1314                 break;
1315         default:
1316                 return;
1317         }
1318         if (nbody->end != ENDBODY_NOT)
1319                 return;
1320
1321         nchild = nbody->child;
1322         if (nchild == NULL) {
1323                 mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
1324                     nbody->line, nbody->pos, "Bl");
1325                 return;
1326         }
1327         while (nchild != NULL) {
1328                 if (nchild->tok == MDOC_It ||
1329                     (nchild->tok == MDOC_Sm &&
1330                      nchild->next != NULL &&
1331                      nchild->next->tok == MDOC_It)) {
1332                         nchild = nchild->next;
1333                         continue;
1334                 }
1335
1336                 mandoc_msg(MANDOCERR_BL_MOVE, mdoc->parse,
1337                     nchild->line, nchild->pos,
1338                     mdoc_macronames[nchild->tok]);
1339
1340                 /*
1341                  * Move the node out of the Bl block.
1342                  * First, collect all required node pointers.
1343                  */
1344
1345                 nblock  = nbody->parent;
1346                 nprev   = nblock->prev;
1347                 nparent = nblock->parent;
1348                 nnext   = nchild->next;
1349
1350                 /*
1351                  * Unlink this child.
1352                  */
1353
1354                 assert(nchild->prev == NULL);
1355                 nbody->child = nnext;
1356                 if (nnext == NULL)
1357                         nbody->last  = NULL;
1358                 else
1359                         nnext->prev = NULL;
1360
1361                 /*
1362                  * Relink this child.
1363                  */
1364
1365                 nchild->parent = nparent;
1366                 nchild->prev   = nprev;
1367                 nchild->next   = nblock;
1368
1369                 nblock->prev = nchild;
1370                 if (nprev == NULL)
1371                         nparent->child = nchild;
1372                 else
1373                         nprev->next = nchild;
1374
1375                 nchild = nnext;
1376         }
1377 }
1378
1379 static void
1380 post_bk(POST_ARGS)
1381 {
1382         struct roff_node        *n;
1383
1384         n = mdoc->last;
1385
1386         if (n->type == ROFFT_BLOCK && n->body->child == NULL) {
1387                 mandoc_msg(MANDOCERR_BLK_EMPTY,
1388                     mdoc->parse, n->line, n->pos, "Bk");
1389                 roff_node_delete(mdoc, n);
1390         }
1391 }
1392
1393 static void
1394 post_sm(POST_ARGS)
1395 {
1396         struct roff_node        *nch;
1397
1398         nch = mdoc->last->child;
1399
1400         if (nch == NULL) {
1401                 mdoc->flags ^= MDOC_SMOFF;
1402                 return;
1403         }
1404
1405         assert(nch->type == ROFFT_TEXT);
1406
1407         if ( ! strcmp(nch->string, "on")) {
1408                 mdoc->flags &= ~MDOC_SMOFF;
1409                 return;
1410         }
1411         if ( ! strcmp(nch->string, "off")) {
1412                 mdoc->flags |= MDOC_SMOFF;
1413                 return;
1414         }
1415
1416         mandoc_vmsg(MANDOCERR_SM_BAD,
1417             mdoc->parse, nch->line, nch->pos,
1418             "%s %s", mdoc_macronames[mdoc->last->tok], nch->string);
1419         mdoc_node_relink(mdoc, nch);
1420         return;
1421 }
1422
1423 static void
1424 post_root(POST_ARGS)
1425 {
1426         struct roff_node *n;
1427
1428         /* Add missing prologue data. */
1429
1430         if (mdoc->meta.date == NULL)
1431                 mdoc->meta.date = mdoc->quick ?
1432                     mandoc_strdup("") :
1433                     mandoc_normdate(mdoc->parse, NULL, 0, 0);
1434
1435         if (mdoc->meta.title == NULL) {
1436                 mandoc_msg(MANDOCERR_DT_NOTITLE,
1437                     mdoc->parse, 0, 0, "EOF");
1438                 mdoc->meta.title = mandoc_strdup("UNTITLED");
1439         }
1440
1441         if (mdoc->meta.vol == NULL)
1442                 mdoc->meta.vol = mandoc_strdup("LOCAL");
1443
1444         if (mdoc->meta.os == NULL) {
1445                 mandoc_msg(MANDOCERR_OS_MISSING,
1446                     mdoc->parse, 0, 0, NULL);
1447                 mdoc->meta.os = mandoc_strdup("");
1448         }
1449
1450         /* Check that we begin with a proper `Sh'. */
1451
1452         n = mdoc->first->child;
1453         while (n != NULL && n->tok != TOKEN_NONE &&
1454             mdoc_macros[n->tok].flags & MDOC_PROLOGUE)
1455                 n = n->next;
1456
1457         if (n == NULL)
1458                 mandoc_msg(MANDOCERR_DOC_EMPTY, mdoc->parse, 0, 0, NULL);
1459         else if (n->tok != MDOC_Sh)
1460                 mandoc_msg(MANDOCERR_SEC_BEFORE, mdoc->parse,
1461                     n->line, n->pos, mdoc_macronames[n->tok]);
1462 }
1463
1464 static void
1465 post_st(POST_ARGS)
1466 {
1467         struct roff_node         *n, *nch;
1468         const char               *p;
1469
1470         n = mdoc->last;
1471         nch = n->child;
1472
1473         assert(nch->type == ROFFT_TEXT);
1474
1475         if ((p = mdoc_a2st(nch->string)) == NULL) {
1476                 mandoc_vmsg(MANDOCERR_ST_BAD, mdoc->parse,
1477                     nch->line, nch->pos, "St %s", nch->string);
1478                 roff_node_delete(mdoc, n);
1479         } else {
1480                 free(nch->string);
1481                 nch->string = mandoc_strdup(p);
1482         }
1483 }
1484
1485 static void
1486 post_rs(POST_ARGS)
1487 {
1488         struct roff_node *np, *nch, *next, *prev;
1489         int               i, j;
1490
1491         np = mdoc->last;
1492
1493         if (np->type != ROFFT_BODY)
1494                 return;
1495
1496         if (np->child == NULL) {
1497                 mandoc_msg(MANDOCERR_RS_EMPTY, mdoc->parse,
1498                     np->line, np->pos, "Rs");
1499                 return;
1500         }
1501
1502         /*
1503          * The full `Rs' block needs special handling to order the
1504          * sub-elements according to `rsord'.  Pick through each element
1505          * and correctly order it.  This is an insertion sort.
1506          */
1507
1508         next = NULL;
1509         for (nch = np->child->next; nch != NULL; nch = next) {
1510                 /* Determine order number of this child. */
1511                 for (i = 0; i < RSORD_MAX; i++)
1512                         if (rsord[i] == nch->tok)
1513                                 break;
1514
1515                 if (i == RSORD_MAX) {
1516                         mandoc_msg(MANDOCERR_RS_BAD,
1517                             mdoc->parse, nch->line, nch->pos,
1518                             mdoc_macronames[nch->tok]);
1519                         i = -1;
1520                 } else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
1521                         np->norm->Rs.quote_T++;
1522
1523                 /*
1524                  * Remove this child from the chain.  This somewhat
1525                  * repeats roff_node_unlink(), but since we're
1526                  * just re-ordering, there's no need for the
1527                  * full unlink process.
1528                  */
1529
1530                 if ((next = nch->next) != NULL)
1531                         next->prev = nch->prev;
1532
1533                 if ((prev = nch->prev) != NULL)
1534                         prev->next = nch->next;
1535
1536                 nch->prev = nch->next = NULL;
1537
1538                 /*
1539                  * Scan back until we reach a node that's
1540                  * to be ordered before this child.
1541                  */
1542
1543                 for ( ; prev ; prev = prev->prev) {
1544                         /* Determine order of `prev'. */
1545                         for (j = 0; j < RSORD_MAX; j++)
1546                                 if (rsord[j] == prev->tok)
1547                                         break;
1548                         if (j == RSORD_MAX)
1549                                 j = -1;
1550
1551                         if (j <= i)
1552                                 break;
1553                 }
1554
1555                 /*
1556                  * Set this child back into its correct place
1557                  * in front of the `prev' node.
1558                  */
1559
1560                 nch->prev = prev;
1561
1562                 if (prev == NULL) {
1563                         np->child->prev = nch;
1564                         nch->next = np->child;
1565                         np->child = nch;
1566                 } else {
1567                         if (prev->next)
1568                                 prev->next->prev = nch;
1569                         nch->next = prev->next;
1570                         prev->next = nch;
1571                 }
1572         }
1573 }
1574
1575 /*
1576  * For some arguments of some macros,
1577  * convert all breakable hyphens into ASCII_HYPH.
1578  */
1579 static void
1580 post_hyph(POST_ARGS)
1581 {
1582         struct roff_node        *nch;
1583         char                    *cp;
1584
1585         for (nch = mdoc->last->child; nch != NULL; nch = nch->next) {
1586                 if (nch->type != ROFFT_TEXT)
1587                         continue;
1588                 cp = nch->string;
1589                 if (*cp == '\0')
1590                         continue;
1591                 while (*(++cp) != '\0')
1592                         if (*cp == '-' &&
1593                             isalpha((unsigned char)cp[-1]) &&
1594                             isalpha((unsigned char)cp[1]))
1595                                 *cp = ASCII_HYPH;
1596         }
1597 }
1598
1599 static void
1600 post_ns(POST_ARGS)
1601 {
1602
1603         if (mdoc->last->flags & MDOC_LINE)
1604                 mandoc_msg(MANDOCERR_NS_SKIP, mdoc->parse,
1605                     mdoc->last->line, mdoc->last->pos, NULL);
1606 }
1607
1608 static void
1609 post_sh(POST_ARGS)
1610 {
1611
1612         post_ignpar(mdoc);
1613
1614         switch (mdoc->last->type) {
1615         case ROFFT_HEAD:
1616                 post_sh_head(mdoc);
1617                 break;
1618         case ROFFT_BODY:
1619                 switch (mdoc->lastsec)  {
1620                 case SEC_NAME:
1621                         post_sh_name(mdoc);
1622                         break;
1623                 case SEC_SEE_ALSO:
1624                         post_sh_see_also(mdoc);
1625                         break;
1626                 case SEC_AUTHORS:
1627                         post_sh_authors(mdoc);
1628                         break;
1629                 default:
1630                         break;
1631                 }
1632                 break;
1633         default:
1634                 break;
1635         }
1636 }
1637
1638 static void
1639 post_sh_name(POST_ARGS)
1640 {
1641         struct roff_node *n;
1642         int hasnm, hasnd;
1643
1644         hasnm = hasnd = 0;
1645
1646         for (n = mdoc->last->child; n != NULL; n = n->next) {
1647                 switch (n->tok) {
1648                 case MDOC_Nm:
1649                         hasnm = 1;
1650                         break;
1651                 case MDOC_Nd:
1652                         hasnd = 1;
1653                         if (n->next != NULL)
1654                                 mandoc_msg(MANDOCERR_NAMESEC_ND,
1655                                     mdoc->parse, n->line, n->pos, NULL);
1656                         break;
1657                 case TOKEN_NONE:
1658                         if (hasnm)
1659                                 break;
1660                         /* FALLTHROUGH */
1661                 default:
1662                         mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
1663                             n->line, n->pos, mdoc_macronames[n->tok]);
1664                         break;
1665                 }
1666         }
1667
1668         if ( ! hasnm)
1669                 mandoc_msg(MANDOCERR_NAMESEC_NONM, mdoc->parse,
1670                     mdoc->last->line, mdoc->last->pos, NULL);
1671         if ( ! hasnd)
1672                 mandoc_msg(MANDOCERR_NAMESEC_NOND, mdoc->parse,
1673                     mdoc->last->line, mdoc->last->pos, NULL);
1674 }
1675
1676 static void
1677 post_sh_see_also(POST_ARGS)
1678 {
1679         const struct roff_node  *n;
1680         const char              *name, *sec;
1681         const char              *lastname, *lastsec, *lastpunct;
1682         int                      cmp;
1683
1684         n = mdoc->last->child;
1685         lastname = lastsec = lastpunct = NULL;
1686         while (n != NULL) {
1687                 if (n->tok != MDOC_Xr ||
1688                     n->child == NULL ||
1689                     n->child->next == NULL)
1690                         break;
1691
1692                 /* Process one .Xr node. */
1693
1694                 name = n->child->string;
1695                 sec = n->child->next->string;
1696                 if (lastsec != NULL) {
1697                         if (lastpunct[0] != ',' || lastpunct[1] != '\0')
1698                                 mandoc_vmsg(MANDOCERR_XR_PUNCT,
1699                                     mdoc->parse, n->line, n->pos,
1700                                     "%s before %s(%s)", lastpunct,
1701                                     name, sec);
1702                         cmp = strcmp(lastsec, sec);
1703                         if (cmp > 0)
1704                                 mandoc_vmsg(MANDOCERR_XR_ORDER,
1705                                     mdoc->parse, n->line, n->pos,
1706                                     "%s(%s) after %s(%s)", name,
1707                                     sec, lastname, lastsec);
1708                         else if (cmp == 0 &&
1709                             strcasecmp(lastname, name) > 0)
1710                                 mandoc_vmsg(MANDOCERR_XR_ORDER,
1711                                     mdoc->parse, n->line, n->pos,
1712                                     "%s after %s", name, lastname);
1713                 }
1714                 lastname = name;
1715                 lastsec = sec;
1716
1717                 /* Process the following node. */
1718
1719                 n = n->next;
1720                 if (n == NULL)
1721                         break;
1722                 if (n->tok == MDOC_Xr) {
1723                         lastpunct = "none";
1724                         continue;
1725                 }
1726                 if (n->type != ROFFT_TEXT)
1727                         break;
1728                 for (name = n->string; *name != '\0'; name++)
1729                         if (isalpha((const unsigned char)*name))
1730                                 return;
1731                 lastpunct = n->string;
1732                 if (n->next == NULL)
1733                         mandoc_vmsg(MANDOCERR_XR_PUNCT, mdoc->parse,
1734                             n->line, n->pos, "%s after %s(%s)",
1735                             lastpunct, lastname, lastsec);
1736                 n = n->next;
1737         }
1738 }
1739
1740 static int
1741 child_an(const struct roff_node *n)
1742 {
1743
1744         for (n = n->child; n != NULL; n = n->next)
1745                 if ((n->tok == MDOC_An && n->child != NULL) || child_an(n))
1746                         return 1;
1747         return 0;
1748 }
1749
1750 static void
1751 post_sh_authors(POST_ARGS)
1752 {
1753
1754         if ( ! child_an(mdoc->last))
1755                 mandoc_msg(MANDOCERR_AN_MISSING, mdoc->parse,
1756                     mdoc->last->line, mdoc->last->pos, NULL);
1757 }
1758
1759 static void
1760 post_sh_head(POST_ARGS)
1761 {
1762         const char      *goodsec;
1763         enum roff_sec    sec;
1764
1765         /*
1766          * Process a new section.  Sections are either "named" or
1767          * "custom".  Custom sections are user-defined, while named ones
1768          * follow a conventional order and may only appear in certain
1769          * manual sections.
1770          */
1771
1772         sec = mdoc->last->sec;
1773
1774         /* The NAME should be first. */
1775
1776         if (SEC_NAME != sec && SEC_NONE == mdoc->lastnamed)
1777                 mandoc_vmsg(MANDOCERR_NAMESEC_FIRST, mdoc->parse,
1778                     mdoc->last->line, mdoc->last->pos,
1779                     "Sh %s", secnames[sec]);
1780
1781         /* The SYNOPSIS gets special attention in other areas. */
1782
1783         if (sec == SEC_SYNOPSIS) {
1784                 roff_setreg(mdoc->roff, "nS", 1, '=');
1785                 mdoc->flags |= MDOC_SYNOPSIS;
1786         } else {
1787                 roff_setreg(mdoc->roff, "nS", 0, '=');
1788                 mdoc->flags &= ~MDOC_SYNOPSIS;
1789         }
1790
1791         /* Mark our last section. */
1792
1793         mdoc->lastsec = sec;
1794
1795         /* We don't care about custom sections after this. */
1796
1797         if (sec == SEC_CUSTOM)
1798                 return;
1799
1800         /*
1801          * Check whether our non-custom section is being repeated or is
1802          * out of order.
1803          */
1804
1805         if (sec == mdoc->lastnamed)
1806                 mandoc_vmsg(MANDOCERR_SEC_REP, mdoc->parse,
1807                     mdoc->last->line, mdoc->last->pos,
1808                     "Sh %s", secnames[sec]);
1809
1810         if (sec < mdoc->lastnamed)
1811                 mandoc_vmsg(MANDOCERR_SEC_ORDER, mdoc->parse,
1812                     mdoc->last->line, mdoc->last->pos,
1813                     "Sh %s", secnames[sec]);
1814
1815         /* Mark the last named section. */
1816
1817         mdoc->lastnamed = sec;
1818
1819         /* Check particular section/manual conventions. */
1820
1821         if (mdoc->meta.msec == NULL)
1822                 return;
1823
1824         goodsec = NULL;
1825         switch (sec) {
1826         case SEC_ERRORS:
1827                 if (*mdoc->meta.msec == '4')
1828                         break;
1829                 goodsec = "2, 3, 4, 9";
1830                 /* FALLTHROUGH */
1831         case SEC_RETURN_VALUES:
1832         case SEC_LIBRARY:
1833                 if (*mdoc->meta.msec == '2')
1834                         break;
1835                 if (*mdoc->meta.msec == '3')
1836                         break;
1837                 if (NULL == goodsec)
1838                         goodsec = "2, 3, 9";
1839                 /* FALLTHROUGH */
1840         case SEC_CONTEXT:
1841                 if (*mdoc->meta.msec == '9')
1842                         break;
1843                 if (NULL == goodsec)
1844                         goodsec = "9";
1845                 mandoc_vmsg(MANDOCERR_SEC_MSEC, mdoc->parse,
1846                     mdoc->last->line, mdoc->last->pos,
1847                     "Sh %s for %s only", secnames[sec], goodsec);
1848                 break;
1849         default:
1850                 break;
1851         }
1852 }
1853
1854 static void
1855 post_ignpar(POST_ARGS)
1856 {
1857         struct roff_node *np;
1858
1859         switch (mdoc->last->type) {
1860         case ROFFT_HEAD:
1861                 post_hyph(mdoc);
1862                 return;
1863         case ROFFT_BODY:
1864                 break;
1865         default:
1866                 return;
1867         }
1868
1869         if ((np = mdoc->last->child) != NULL)
1870                 if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) {
1871                         mandoc_vmsg(MANDOCERR_PAR_SKIP,
1872                             mdoc->parse, np->line, np->pos,
1873                             "%s after %s", mdoc_macronames[np->tok],
1874                             mdoc_macronames[mdoc->last->tok]);
1875                         roff_node_delete(mdoc, np);
1876                 }
1877
1878         if ((np = mdoc->last->last) != NULL)
1879                 if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) {
1880                         mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
1881                             np->line, np->pos, "%s at the end of %s",
1882                             mdoc_macronames[np->tok],
1883                             mdoc_macronames[mdoc->last->tok]);
1884                         roff_node_delete(mdoc, np);
1885                 }
1886 }
1887
1888 static void
1889 post_prevpar(POST_ARGS)
1890 {
1891         struct roff_node *n;
1892
1893         n = mdoc->last;
1894         if (NULL == n->prev)
1895                 return;
1896         if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
1897                 return;
1898
1899         /*
1900          * Don't allow prior `Lp' or `Pp' prior to a paragraph-type
1901          * block:  `Lp', `Pp', or non-compact `Bd' or `Bl'.
1902          */
1903
1904         if (n->prev->tok != MDOC_Pp &&
1905             n->prev->tok != MDOC_Lp &&
1906             n->prev->tok != MDOC_br)
1907                 return;
1908         if (n->tok == MDOC_Bl && n->norm->Bl.comp)
1909                 return;
1910         if (n->tok == MDOC_Bd && n->norm->Bd.comp)
1911                 return;
1912         if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
1913                 return;
1914
1915         mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
1916             n->prev->line, n->prev->pos,
1917             "%s before %s", mdoc_macronames[n->prev->tok],
1918             mdoc_macronames[n->tok]);
1919         roff_node_delete(mdoc, n->prev);
1920 }
1921
1922 static void
1923 post_par(POST_ARGS)
1924 {
1925         struct roff_node *np;
1926
1927         np = mdoc->last;
1928         if (np->tok != MDOC_br && np->tok != MDOC_sp)
1929                 post_prevpar(mdoc);
1930
1931         if (np->tok == MDOC_sp) {
1932                 if (np->child != NULL && np->child->next != NULL)
1933                         mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1934                             np->child->next->line, np->child->next->pos,
1935                             "sp ... %s", np->child->next->string);
1936         } else if (np->child != NULL)
1937                 mandoc_vmsg(MANDOCERR_ARG_SKIP,
1938                     mdoc->parse, np->line, np->pos, "%s %s",
1939                     mdoc_macronames[np->tok], np->child->string);
1940
1941         if ((np = mdoc->last->prev) == NULL) {
1942                 np = mdoc->last->parent;
1943                 if (np->tok != MDOC_Sh && np->tok != MDOC_Ss)
1944                         return;
1945         } else if (np->tok != MDOC_Pp && np->tok != MDOC_Lp &&
1946             (mdoc->last->tok != MDOC_br ||
1947              (np->tok != MDOC_sp && np->tok != MDOC_br)))
1948                 return;
1949
1950         mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
1951             mdoc->last->line, mdoc->last->pos,
1952             "%s after %s", mdoc_macronames[mdoc->last->tok],
1953             mdoc_macronames[np->tok]);
1954         roff_node_delete(mdoc, mdoc->last);
1955 }
1956
1957 static void
1958 post_dd(POST_ARGS)
1959 {
1960         struct roff_node *n;
1961         char             *datestr;
1962
1963         n = mdoc->last;
1964         if (mdoc->meta.date != NULL) {
1965                 mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
1966                     n->line, n->pos, "Dd");
1967                 free(mdoc->meta.date);
1968         } else if (mdoc->flags & MDOC_PBODY)
1969                 mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
1970                     n->line, n->pos, "Dd");
1971         else if (mdoc->meta.title != NULL)
1972                 mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
1973                     n->line, n->pos, "Dd after Dt");
1974         else if (mdoc->meta.os != NULL)
1975                 mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
1976                     n->line, n->pos, "Dd after Os");
1977
1978         if (n->child == NULL || n->child->string[0] == '\0') {
1979                 mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
1980                     mandoc_normdate(mdoc->parse, NULL, n->line, n->pos);
1981                 goto out;
1982         }
1983
1984         datestr = NULL;
1985         deroff(&datestr, n);
1986         if (mdoc->quick)
1987                 mdoc->meta.date = datestr;
1988         else {
1989                 mdoc->meta.date = mandoc_normdate(mdoc->parse,
1990                     datestr, n->line, n->pos);
1991                 free(datestr);
1992         }
1993 out:
1994         roff_node_delete(mdoc, n);
1995 }
1996
1997 static void
1998 post_dt(POST_ARGS)
1999 {
2000         struct roff_node *nn, *n;
2001         const char       *cp;
2002         char             *p;
2003
2004         n = mdoc->last;
2005         if (mdoc->flags & MDOC_PBODY) {
2006                 mandoc_msg(MANDOCERR_DT_LATE, mdoc->parse,
2007                     n->line, n->pos, "Dt");
2008                 goto out;
2009         }
2010
2011         if (mdoc->meta.title != NULL)
2012                 mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2013                     n->line, n->pos, "Dt");
2014         else if (mdoc->meta.os != NULL)
2015                 mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
2016                     n->line, n->pos, "Dt after Os");
2017
2018         free(mdoc->meta.title);
2019         free(mdoc->meta.msec);
2020         free(mdoc->meta.vol);
2021         free(mdoc->meta.arch);
2022
2023         mdoc->meta.title = NULL;
2024         mdoc->meta.msec = NULL;
2025         mdoc->meta.vol = NULL;
2026         mdoc->meta.arch = NULL;
2027
2028         /* Mandatory first argument: title. */
2029
2030         nn = n->child;
2031         if (nn == NULL || *nn->string == '\0') {
2032                 mandoc_msg(MANDOCERR_DT_NOTITLE,
2033                     mdoc->parse, n->line, n->pos, "Dt");
2034                 mdoc->meta.title = mandoc_strdup("UNTITLED");
2035         } else {
2036                 mdoc->meta.title = mandoc_strdup(nn->string);
2037
2038                 /* Check that all characters are uppercase. */
2039
2040                 for (p = nn->string; *p != '\0'; p++)
2041                         if (islower((unsigned char)*p)) {
2042                                 mandoc_vmsg(MANDOCERR_TITLE_CASE,
2043                                     mdoc->parse, nn->line,
2044                                     nn->pos + (p - nn->string),
2045                                     "Dt %s", nn->string);
2046                                 break;
2047                         }
2048         }
2049
2050         /* Mandatory second argument: section. */
2051
2052         if (nn != NULL)
2053                 nn = nn->next;
2054
2055         if (nn == NULL) {
2056                 mandoc_vmsg(MANDOCERR_MSEC_MISSING,
2057                     mdoc->parse, n->line, n->pos,
2058                     "Dt %s", mdoc->meta.title);
2059                 mdoc->meta.vol = mandoc_strdup("LOCAL");
2060                 goto out;  /* msec and arch remain NULL. */
2061         }
2062
2063         mdoc->meta.msec = mandoc_strdup(nn->string);
2064
2065         /* Infer volume title from section number. */
2066
2067         cp = mandoc_a2msec(nn->string);
2068         if (cp == NULL) {
2069                 mandoc_vmsg(MANDOCERR_MSEC_BAD, mdoc->parse,
2070                     nn->line, nn->pos, "Dt ... %s", nn->string);
2071                 mdoc->meta.vol = mandoc_strdup(nn->string);
2072         } else
2073                 mdoc->meta.vol = mandoc_strdup(cp);
2074
2075         /* Optional third argument: architecture. */
2076
2077         if ((nn = nn->next) == NULL)
2078                 goto out;
2079
2080         for (p = nn->string; *p != '\0'; p++)
2081                 *p = tolower((unsigned char)*p);
2082         mdoc->meta.arch = mandoc_strdup(nn->string);
2083
2084         /* Ignore fourth and later arguments. */
2085
2086         if ((nn = nn->next) != NULL)
2087                 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
2088                     nn->line, nn->pos, "Dt ... %s", nn->string);
2089
2090 out:
2091         roff_node_delete(mdoc, n);
2092 }
2093
2094 static void
2095 post_bx(POST_ARGS)
2096 {
2097         struct roff_node        *n;
2098
2099         /*
2100          * Make `Bx's second argument always start with an uppercase
2101          * letter.  Groff checks if it's an "accepted" term, but we just
2102          * uppercase blindly.
2103          */
2104
2105         if ((n = mdoc->last->child) != NULL && (n = n->next) != NULL)
2106                 *n->string = (char)toupper((unsigned char)*n->string);
2107 }
2108
2109 static void
2110 post_os(POST_ARGS)
2111 {
2112 #ifndef OSNAME
2113         struct utsname    utsname;
2114         static char      *defbuf;
2115 #endif
2116         struct roff_node *n;
2117
2118         n = mdoc->last;
2119         if (mdoc->meta.os != NULL)
2120                 mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2121                     n->line, n->pos, "Os");
2122         else if (mdoc->flags & MDOC_PBODY)
2123                 mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
2124                     n->line, n->pos, "Os");
2125
2126         /*
2127          * Set the operating system by way of the `Os' macro.
2128          * The order of precedence is:
2129          * 1. the argument of the `Os' macro, unless empty
2130          * 2. the -Ios=foo command line argument, if provided
2131          * 3. -DOSNAME="\"foo\"", if provided during compilation
2132          * 4. "sysname release" from uname(3)
2133          */
2134
2135         free(mdoc->meta.os);
2136         mdoc->meta.os = NULL;
2137         deroff(&mdoc->meta.os, n);
2138         if (mdoc->meta.os)
2139                 goto out;
2140
2141         if (mdoc->defos) {
2142                 mdoc->meta.os = mandoc_strdup(mdoc->defos);
2143                 goto out;
2144         }
2145
2146 #ifdef OSNAME
2147         mdoc->meta.os = mandoc_strdup(OSNAME);
2148 #else /*!OSNAME */
2149         if (defbuf == NULL) {
2150                 if (uname(&utsname) == -1) {
2151                         mandoc_msg(MANDOCERR_OS_UNAME, mdoc->parse,
2152                             n->line, n->pos, "Os");
2153                         defbuf = mandoc_strdup("UNKNOWN");
2154                 } else
2155                         mandoc_asprintf(&defbuf, "%s %s",
2156                             utsname.sysname, utsname.release);
2157         }
2158         mdoc->meta.os = mandoc_strdup(defbuf);
2159 #endif /*!OSNAME*/
2160
2161 out:
2162         roff_node_delete(mdoc, n);
2163 }
2164
2165 /*
2166  * If no argument is provided,
2167  * fill in the name of the current manual page.
2168  */
2169 static void
2170 post_ex(POST_ARGS)
2171 {
2172         struct roff_node *n;
2173
2174         post_std(mdoc);
2175
2176         n = mdoc->last;
2177         if (n->child != NULL)
2178                 return;
2179
2180         if (mdoc->meta.name == NULL) {
2181                 mandoc_msg(MANDOCERR_EX_NONAME, mdoc->parse,
2182                     n->line, n->pos, "Ex");
2183                 return;
2184         }
2185
2186         mdoc->next = ROFF_NEXT_CHILD;
2187         roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
2188         mdoc->last = n;
2189 }
2190
2191 enum roff_sec
2192 mdoc_a2sec(const char *p)
2193 {
2194         int              i;
2195
2196         for (i = 0; i < (int)SEC__MAX; i++)
2197                 if (secnames[i] && 0 == strcmp(p, secnames[i]))
2198                         return (enum roff_sec)i;
2199
2200         return SEC_CUSTOM;
2201 }
2202
2203 static size_t
2204 macro2len(int macro)
2205 {
2206
2207         switch (macro) {
2208         case MDOC_Ad:
2209                 return 12;
2210         case MDOC_Ao:
2211                 return 12;
2212         case MDOC_An:
2213                 return 12;
2214         case MDOC_Aq:
2215                 return 12;
2216         case MDOC_Ar:
2217                 return 12;
2218         case MDOC_Bo:
2219                 return 12;
2220         case MDOC_Bq:
2221                 return 12;
2222         case MDOC_Cd:
2223                 return 12;
2224         case MDOC_Cm:
2225                 return 10;
2226         case MDOC_Do:
2227                 return 10;
2228         case MDOC_Dq:
2229                 return 12;
2230         case MDOC_Dv:
2231                 return 12;
2232         case MDOC_Eo:
2233                 return 12;
2234         case MDOC_Em:
2235                 return 10;
2236         case MDOC_Er:
2237                 return 17;
2238         case MDOC_Ev:
2239                 return 15;
2240         case MDOC_Fa:
2241                 return 12;
2242         case MDOC_Fl:
2243                 return 10;
2244         case MDOC_Fo:
2245                 return 16;
2246         case MDOC_Fn:
2247                 return 16;
2248         case MDOC_Ic:
2249                 return 10;
2250         case MDOC_Li:
2251                 return 16;
2252         case MDOC_Ms:
2253                 return 6;
2254         case MDOC_Nm:
2255                 return 10;
2256         case MDOC_No:
2257                 return 12;
2258         case MDOC_Oo:
2259                 return 10;
2260         case MDOC_Op:
2261                 return 14;
2262         case MDOC_Pa:
2263                 return 32;
2264         case MDOC_Pf:
2265                 return 12;
2266         case MDOC_Po:
2267                 return 12;
2268         case MDOC_Pq:
2269                 return 12;
2270         case MDOC_Ql:
2271                 return 16;
2272         case MDOC_Qo:
2273                 return 12;
2274         case MDOC_So:
2275                 return 12;
2276         case MDOC_Sq:
2277                 return 12;
2278         case MDOC_Sy:
2279                 return 6;
2280         case MDOC_Sx:
2281                 return 16;
2282         case MDOC_Tn:
2283                 return 10;
2284         case MDOC_Va:
2285                 return 12;
2286         case MDOC_Vt:
2287                 return 12;
2288         case MDOC_Xr:
2289                 return 10;
2290         default:
2291                 break;
2292         };
2293         return 0;
2294 }