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 case SEC_LIBRARY: 2382 if (*mdoc->meta.msec == '2') 2383 break; 2384 if (*mdoc->meta.msec == '3') 2385 break; 2386 if (NULL == goodsec) 2387 goodsec = "2, 3, 9"; 2388 /* FALLTHROUGH */ 2389 case SEC_CONTEXT: 2390 if (*mdoc->meta.msec == '9') 2391 break; 2392 if (NULL == goodsec) 2393 goodsec = "9"; 2394 mandoc_msg(MANDOCERR_SEC_MSEC, 2395 mdoc->last->line, mdoc->last->pos, 2396 "Sh %s for %s only", secnames[sec], goodsec); 2397 break; 2398 default: 2399 break; 2400 } 2401 } 2402 2403 static void 2404 post_xr(POST_ARGS) 2405 { 2406 struct roff_node *n, *nch; 2407 2408 n = mdoc->last; 2409 nch = n->child; 2410 if (nch->next == NULL) { 2411 mandoc_msg(MANDOCERR_XR_NOSEC, 2412 n->line, n->pos, "Xr %s", nch->string); 2413 } else { 2414 assert(nch->next == n->last); 2415 if(mandoc_xr_add(nch->next->string, nch->string, 2416 nch->line, nch->pos)) 2417 mandoc_msg(MANDOCERR_XR_SELF, 2418 nch->line, nch->pos, "Xr %s %s", 2419 nch->string, nch->next->string); 2420 } 2421 post_delim_nb(mdoc); 2422 } 2423 2424 static void 2425 post_ignpar(POST_ARGS) 2426 { 2427 struct roff_node *np; 2428 2429 switch (mdoc->last->type) { 2430 case ROFFT_BLOCK: 2431 post_prevpar(mdoc); 2432 return; 2433 case ROFFT_HEAD: 2434 post_delim(mdoc); 2435 post_hyph(mdoc); 2436 return; 2437 case ROFFT_BODY: 2438 break; 2439 default: 2440 return; 2441 } 2442 2443 if ((np = mdoc->last->child) != NULL) 2444 if (np->tok == MDOC_Pp || 2445 np->tok == ROFF_br || np->tok == ROFF_sp) { 2446 mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos, 2447 "%s after %s", roff_name[np->tok], 2448 roff_name[mdoc->last->tok]); 2449 roff_node_delete(mdoc, np); 2450 } 2451 2452 if ((np = mdoc->last->last) != NULL) 2453 if (np->tok == MDOC_Pp || np->tok == ROFF_br) { 2454 mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos, 2455 "%s at the end of %s", roff_name[np->tok], 2456 roff_name[mdoc->last->tok]); 2457 roff_node_delete(mdoc, np); 2458 } 2459 } 2460 2461 static void 2462 post_prevpar(POST_ARGS) 2463 { 2464 struct roff_node *n; 2465 2466 n = mdoc->last; 2467 if (NULL == n->prev) 2468 return; 2469 if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK) 2470 return; 2471 2472 /* 2473 * Don't allow `Pp' prior to a paragraph-type 2474 * block: `Pp' or non-compact `Bd' or `Bl'. 2475 */ 2476 2477 if (n->prev->tok != MDOC_Pp && n->prev->tok != ROFF_br) 2478 return; 2479 if (n->tok == MDOC_Bl && n->norm->Bl.comp) 2480 return; 2481 if (n->tok == MDOC_Bd && n->norm->Bd.comp) 2482 return; 2483 if (n->tok == MDOC_It && n->parent->norm->Bl.comp) 2484 return; 2485 2486 mandoc_msg(MANDOCERR_PAR_SKIP, n->prev->line, n->prev->pos, 2487 "%s before %s", roff_name[n->prev->tok], roff_name[n->tok]); 2488 roff_node_delete(mdoc, n->prev); 2489 } 2490 2491 static void 2492 post_par(POST_ARGS) 2493 { 2494 struct roff_node *np; 2495 2496 post_prevpar(mdoc); 2497 2498 np = mdoc->last; 2499 if (np->child != NULL) 2500 mandoc_msg(MANDOCERR_ARG_SKIP, np->line, np->pos, 2501 "%s %s", roff_name[np->tok], np->child->string); 2502 } 2503 2504 static void 2505 post_dd(POST_ARGS) 2506 { 2507 struct roff_node *n; 2508 char *datestr; 2509 2510 n = mdoc->last; 2511 n->flags |= NODE_NOPRT; 2512 2513 if (mdoc->meta.date != NULL) { 2514 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dd"); 2515 free(mdoc->meta.date); 2516 } else if (mdoc->flags & MDOC_PBODY) 2517 mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Dd"); 2518 else if (mdoc->meta.title != NULL) 2519 mandoc_msg(MANDOCERR_PROLOG_ORDER, 2520 n->line, n->pos, "Dd after Dt"); 2521 else if (mdoc->meta.os != NULL) 2522 mandoc_msg(MANDOCERR_PROLOG_ORDER, 2523 n->line, n->pos, "Dd after Os"); 2524 2525 if (n->child == NULL || n->child->string[0] == '\0') { 2526 mdoc->meta.date = mdoc->quick ? mandoc_strdup("") : 2527 mandoc_normdate(mdoc, NULL, n->line, n->pos); 2528 return; 2529 } 2530 2531 datestr = NULL; 2532 deroff(&datestr, n); 2533 if (mdoc->quick) 2534 mdoc->meta.date = datestr; 2535 else { 2536 mdoc->meta.date = mandoc_normdate(mdoc, 2537 datestr, n->line, n->pos); 2538 free(datestr); 2539 } 2540 } 2541 2542 static void 2543 post_dt(POST_ARGS) 2544 { 2545 struct roff_node *nn, *n; 2546 const char *cp; 2547 char *p; 2548 2549 n = mdoc->last; 2550 n->flags |= NODE_NOPRT; 2551 2552 if (mdoc->flags & MDOC_PBODY) { 2553 mandoc_msg(MANDOCERR_DT_LATE, n->line, n->pos, "Dt"); 2554 return; 2555 } 2556 2557 if (mdoc->meta.title != NULL) 2558 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dt"); 2559 else if (mdoc->meta.os != NULL) 2560 mandoc_msg(MANDOCERR_PROLOG_ORDER, 2561 n->line, n->pos, "Dt after Os"); 2562 2563 free(mdoc->meta.title); 2564 free(mdoc->meta.msec); 2565 free(mdoc->meta.vol); 2566 free(mdoc->meta.arch); 2567 2568 mdoc->meta.title = NULL; 2569 mdoc->meta.msec = NULL; 2570 mdoc->meta.vol = NULL; 2571 mdoc->meta.arch = NULL; 2572 2573 /* Mandatory first argument: title. */ 2574 2575 nn = n->child; 2576 if (nn == NULL || *nn->string == '\0') { 2577 mandoc_msg(MANDOCERR_DT_NOTITLE, n->line, n->pos, "Dt"); 2578 mdoc->meta.title = mandoc_strdup("UNTITLED"); 2579 } else { 2580 mdoc->meta.title = mandoc_strdup(nn->string); 2581 2582 /* Check that all characters are uppercase. */ 2583 2584 for (p = nn->string; *p != '\0'; p++) 2585 if (islower((unsigned char)*p)) { 2586 mandoc_msg(MANDOCERR_TITLE_CASE, nn->line, 2587 nn->pos + (int)(p - nn->string), 2588 "Dt %s", nn->string); 2589 break; 2590 } 2591 } 2592 2593 /* Mandatory second argument: section. */ 2594 2595 if (nn != NULL) 2596 nn = nn->next; 2597 2598 if (nn == NULL) { 2599 mandoc_msg(MANDOCERR_MSEC_MISSING, n->line, n->pos, 2600 "Dt %s", mdoc->meta.title); 2601 mdoc->meta.vol = mandoc_strdup("LOCAL"); 2602 return; /* msec and arch remain NULL. */ 2603 } 2604 2605 mdoc->meta.msec = mandoc_strdup(nn->string); 2606 2607 /* Infer volume title from section number. */ 2608 2609 cp = mandoc_a2msec(nn->string); 2610 if (cp == NULL) { 2611 mandoc_msg(MANDOCERR_MSEC_BAD, 2612 nn->line, nn->pos, "Dt ... %s", nn->string); 2613 mdoc->meta.vol = mandoc_strdup(nn->string); 2614 } else 2615 mdoc->meta.vol = mandoc_strdup(cp); 2616 2617 /* Optional third argument: architecture. */ 2618 2619 if ((nn = nn->next) == NULL) 2620 return; 2621 2622 for (p = nn->string; *p != '\0'; p++) 2623 *p = tolower((unsigned char)*p); 2624 mdoc->meta.arch = mandoc_strdup(nn->string); 2625 2626 /* Ignore fourth and later arguments. */ 2627 2628 if ((nn = nn->next) != NULL) 2629 mandoc_msg(MANDOCERR_ARG_EXCESS, 2630 nn->line, nn->pos, "Dt ... %s", nn->string); 2631 } 2632 2633 static void 2634 post_bx(POST_ARGS) 2635 { 2636 struct roff_node *n, *nch; 2637 const char *macro; 2638 2639 post_delim_nb(mdoc); 2640 2641 n = mdoc->last; 2642 nch = n->child; 2643 2644 if (nch != NULL) { 2645 macro = !strcmp(nch->string, "Open") ? "Ox" : 2646 !strcmp(nch->string, "Net") ? "Nx" : 2647 !strcmp(nch->string, "Free") ? "Fx" : 2648 !strcmp(nch->string, "DragonFly") ? "Dx" : NULL; 2649 if (macro != NULL) 2650 mandoc_msg(MANDOCERR_BX, 2651 n->line, n->pos, "%s", macro); 2652 mdoc->last = nch; 2653 nch = nch->next; 2654 mdoc->next = ROFF_NEXT_SIBLING; 2655 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns); 2656 mdoc->last->flags |= NODE_NOSRC; 2657 mdoc->next = ROFF_NEXT_SIBLING; 2658 } else 2659 mdoc->next = ROFF_NEXT_CHILD; 2660 roff_word_alloc(mdoc, n->line, n->pos, "BSD"); 2661 mdoc->last->flags |= NODE_NOSRC; 2662 2663 if (nch == NULL) { 2664 mdoc->last = n; 2665 return; 2666 } 2667 2668 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns); 2669 mdoc->last->flags |= NODE_NOSRC; 2670 mdoc->next = ROFF_NEXT_SIBLING; 2671 roff_word_alloc(mdoc, n->line, n->pos, "-"); 2672 mdoc->last->flags |= NODE_NOSRC; 2673 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns); 2674 mdoc->last->flags |= NODE_NOSRC; 2675 mdoc->last = n; 2676 2677 /* 2678 * Make `Bx's second argument always start with an uppercase 2679 * letter. Groff checks if it's an "accepted" term, but we just 2680 * uppercase blindly. 2681 */ 2682 2683 *nch->string = (char)toupper((unsigned char)*nch->string); 2684 } 2685 2686 static void 2687 post_os(POST_ARGS) 2688 { 2689 #ifndef OSNAME 2690 struct utsname utsname; 2691 static char *defbuf; 2692 #endif 2693 struct roff_node *n; 2694 2695 n = mdoc->last; 2696 n->flags |= NODE_NOPRT; 2697 2698 if (mdoc->meta.os != NULL) 2699 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Os"); 2700 else if (mdoc->flags & MDOC_PBODY) 2701 mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Os"); 2702 2703 post_delim(mdoc); 2704 2705 /* 2706 * Set the operating system by way of the `Os' macro. 2707 * The order of precedence is: 2708 * 1. the argument of the `Os' macro, unless empty 2709 * 2. the -Ios=foo command line argument, if provided 2710 * 3. -DOSNAME="\"foo\"", if provided during compilation 2711 * 4. "sysname release" from uname(3) 2712 */ 2713 2714 free(mdoc->meta.os); 2715 mdoc->meta.os = NULL; 2716 deroff(&mdoc->meta.os, n); 2717 if (mdoc->meta.os) 2718 goto out; 2719 2720 if (mdoc->os_s != NULL) { 2721 mdoc->meta.os = mandoc_strdup(mdoc->os_s); 2722 goto out; 2723 } 2724 2725 #ifdef OSNAME 2726 mdoc->meta.os = mandoc_strdup(OSNAME); 2727 #else /*!OSNAME */ 2728 if (defbuf == NULL) { 2729 if (uname(&utsname) == -1) { 2730 mandoc_msg(MANDOCERR_OS_UNAME, n->line, n->pos, "Os"); 2731 defbuf = mandoc_strdup("UNKNOWN"); 2732 } else 2733 mandoc_asprintf(&defbuf, "%s %s", 2734 utsname.sysname, utsname.release); 2735 } 2736 mdoc->meta.os = mandoc_strdup(defbuf); 2737 #endif /*!OSNAME*/ 2738 2739 out: 2740 if (mdoc->meta.os_e == MANDOC_OS_OTHER) { 2741 if (strstr(mdoc->meta.os, "OpenBSD") != NULL) 2742 mdoc->meta.os_e = MANDOC_OS_OPENBSD; 2743 else if (strstr(mdoc->meta.os, "NetBSD") != NULL) 2744 mdoc->meta.os_e = MANDOC_OS_NETBSD; 2745 } 2746 2747 /* 2748 * This is the earliest point where we can check 2749 * Mdocdate conventions because we don't know 2750 * the operating system earlier. 2751 */ 2752 2753 if (n->child != NULL) 2754 mandoc_msg(MANDOCERR_OS_ARG, n->child->line, n->child->pos, 2755 "Os %s (%s)", n->child->string, 2756 mdoc->meta.os_e == MANDOC_OS_OPENBSD ? 2757 "OpenBSD" : "NetBSD"); 2758 2759 while (n->tok != MDOC_Dd) 2760 if ((n = n->prev) == NULL) 2761 return; 2762 if ((n = n->child) == NULL) 2763 return; 2764 if (strncmp(n->string, "$" "Mdocdate", 9)) { 2765 if (mdoc->meta.os_e == MANDOC_OS_OPENBSD) 2766 mandoc_msg(MANDOCERR_MDOCDATE_MISSING, n->line, 2767 n->pos, "Dd %s (OpenBSD)", n->string); 2768 } else { 2769 if (mdoc->meta.os_e == MANDOC_OS_NETBSD) 2770 mandoc_msg(MANDOCERR_MDOCDATE, n->line, 2771 n->pos, "Dd %s (NetBSD)", n->string); 2772 } 2773 } 2774 2775 enum roff_sec 2776 mdoc_a2sec(const char *p) 2777 { 2778 int i; 2779 2780 for (i = 0; i < (int)SEC__MAX; i++) 2781 if (secnames[i] && 0 == strcmp(p, secnames[i])) 2782 return (enum roff_sec)i; 2783 2784 return SEC_CUSTOM; 2785 } 2786 2787 static size_t 2788 macro2len(enum roff_tok macro) 2789 { 2790 2791 switch (macro) { 2792 case MDOC_Ad: 2793 return 12; 2794 case MDOC_Ao: 2795 return 12; 2796 case MDOC_An: 2797 return 12; 2798 case MDOC_Aq: 2799 return 12; 2800 case MDOC_Ar: 2801 return 12; 2802 case MDOC_Bo: 2803 return 12; 2804 case MDOC_Bq: 2805 return 12; 2806 case MDOC_Cd: 2807 return 12; 2808 case MDOC_Cm: 2809 return 10; 2810 case MDOC_Do: 2811 return 10; 2812 case MDOC_Dq: 2813 return 12; 2814 case MDOC_Dv: 2815 return 12; 2816 case MDOC_Eo: 2817 return 12; 2818 case MDOC_Em: 2819 return 10; 2820 case MDOC_Er: 2821 return 17; 2822 case MDOC_Ev: 2823 return 15; 2824 case MDOC_Fa: 2825 return 12; 2826 case MDOC_Fl: 2827 return 10; 2828 case MDOC_Fo: 2829 return 16; 2830 case MDOC_Fn: 2831 return 16; 2832 case MDOC_Ic: 2833 return 10; 2834 case MDOC_Li: 2835 return 16; 2836 case MDOC_Ms: 2837 return 6; 2838 case MDOC_Nm: 2839 return 10; 2840 case MDOC_No: 2841 return 12; 2842 case MDOC_Oo: 2843 return 10; 2844 case MDOC_Op: 2845 return 14; 2846 case MDOC_Pa: 2847 return 32; 2848 case MDOC_Pf: 2849 return 12; 2850 case MDOC_Po: 2851 return 12; 2852 case MDOC_Pq: 2853 return 12; 2854 case MDOC_Ql: 2855 return 16; 2856 case MDOC_Qo: 2857 return 12; 2858 case MDOC_So: 2859 return 12; 2860 case MDOC_Sq: 2861 return 12; 2862 case MDOC_Sy: 2863 return 6; 2864 case MDOC_Sx: 2865 return 16; 2866 case MDOC_Tn: 2867 return 10; 2868 case MDOC_Va: 2869 return 12; 2870 case MDOC_Vt: 2871 return 12; 2872 case MDOC_Xr: 2873 return 10; 2874 default: 2875 break; 2876 }; 2877 return 0; 2878 } 2879