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