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