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