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