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