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