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