1/* Id: mdoc_validate.c,v 1.371 2019/03/04 13:01:57 schwarze Exp */ 2/* 3 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2010-2019 Ingo Schwarze <schwarze@openbsd.org> 5 * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org> 6 * 7 * Permission to use, copy, modify, and distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 */ 19#include "config.h" 20 21#include <sys/types.h> 22#ifndef OSNAME 23#include <sys/utsname.h> 24#endif 25 26#include <assert.h> 27#include <ctype.h> 28#include <limits.h> 29#include <stdio.h> 30#include <stdlib.h> 31#include <string.h> 32#include <time.h> 33 34#include "mandoc_aux.h" 35#include "mandoc.h" 36#include "mandoc_xr.h" 37#include "roff.h" 38#include "mdoc.h" 39#include "libmandoc.h" 40#include "roff_int.h" 41#include "libmdoc.h" 42 43/* FIXME: .Bl -diag can't have non-text children in HEAD. */ 44 45#define POST_ARGS struct roff_man *mdoc 46 47enum check_ineq { 48 CHECK_LT, 49 CHECK_GT, 50 CHECK_EQ 51}; 52 53typedef void (*v_post)(POST_ARGS); 54 55static int build_list(struct roff_man *, int); 56static void check_argv(struct roff_man *, 57 struct roff_node *, struct mdoc_argv *); 58static void check_args(struct roff_man *, struct roff_node *); 59static void check_text(struct roff_man *, int, int, char *); 60static void check_text_em(struct roff_man *, int, int, char *); 61static void check_toptext(struct roff_man *, int, int, const char *); 62static int child_an(const struct roff_node *); 63static size_t macro2len(enum roff_tok); 64static void rewrite_macro2len(struct roff_man *, char **); 65static int similar(const char *, const char *); 66 67static void post_abort(POST_ARGS) __dead; 68static void post_an(POST_ARGS); 69static void post_an_norm(POST_ARGS); 70static void post_at(POST_ARGS); 71static void post_bd(POST_ARGS); 72static void post_bf(POST_ARGS); 73static void post_bk(POST_ARGS); 74static void post_bl(POST_ARGS); 75static void post_bl_block(POST_ARGS); 76static void post_bl_head(POST_ARGS); 77static void post_bl_norm(POST_ARGS); 78static void post_bx(POST_ARGS); 79static void post_defaults(POST_ARGS); 80static void post_display(POST_ARGS); 81static void post_dd(POST_ARGS); 82static void post_delim(POST_ARGS); 83static void post_delim_nb(POST_ARGS); 84static void post_dt(POST_ARGS); 85static void post_en(POST_ARGS); 86static void post_es(POST_ARGS); 87static void post_eoln(POST_ARGS); 88static void post_ex(POST_ARGS); 89static void post_fa(POST_ARGS); 90static void post_fn(POST_ARGS); 91static void post_fname(POST_ARGS); 92static void post_fo(POST_ARGS); 93static void post_hyph(POST_ARGS); 94static void post_ignpar(POST_ARGS); 95static void post_it(POST_ARGS); 96static void post_lb(POST_ARGS); 97static void post_nd(POST_ARGS); 98static void post_nm(POST_ARGS); 99static void post_ns(POST_ARGS); 100static void post_obsolete(POST_ARGS); 101static void post_os(POST_ARGS); 102static void post_par(POST_ARGS); 103static void post_prevpar(POST_ARGS); 104static void post_root(POST_ARGS); 105static void post_rs(POST_ARGS); 106static void post_rv(POST_ARGS); 107static void post_sh(POST_ARGS); 108static void post_sh_head(POST_ARGS); 109static void post_sh_name(POST_ARGS); 110static void post_sh_see_also(POST_ARGS); 111static void post_sh_authors(POST_ARGS); 112static void post_sm(POST_ARGS); 113static void post_st(POST_ARGS); 114static void post_std(POST_ARGS); 115static void post_sx(POST_ARGS); 116static void post_useless(POST_ARGS); 117static void post_xr(POST_ARGS); 118static void post_xx(POST_ARGS); 119 120static const v_post mdoc_valids[MDOC_MAX - MDOC_Dd] = { 121 post_dd, /* Dd */ 122 post_dt, /* Dt */ 123 post_os, /* Os */ 124 post_sh, /* Sh */ 125 post_ignpar, /* Ss */ 126 post_par, /* Pp */ 127 post_display, /* D1 */ 128 post_display, /* Dl */ 129 post_display, /* Bd */ 130 NULL, /* Ed */ 131 post_bl, /* Bl */ 132 NULL, /* El */ 133 post_it, /* It */ 134 post_delim_nb, /* Ad */ 135 post_an, /* An */ 136 NULL, /* Ap */ 137 post_defaults, /* Ar */ 138 NULL, /* Cd */ 139 post_delim_nb, /* Cm */ 140 post_delim_nb, /* Dv */ 141 post_delim_nb, /* Er */ 142 post_delim_nb, /* Ev */ 143 post_ex, /* Ex */ 144 post_fa, /* Fa */ 145 NULL, /* Fd */ 146 post_delim_nb, /* Fl */ 147 post_fn, /* Fn */ 148 post_delim_nb, /* Ft */ 149 post_delim_nb, /* Ic */ 150 post_delim_nb, /* In */ 151 post_defaults, /* Li */ 152 post_nd, /* Nd */ 153 post_nm, /* Nm */ 154 post_delim_nb, /* Op */ 155 post_abort, /* Ot */ 156 post_defaults, /* Pa */ 157 post_rv, /* Rv */ 158 post_st, /* St */ 159 post_delim_nb, /* Va */ 160 post_delim_nb, /* Vt */ 161 post_xr, /* Xr */ 162 NULL, /* %A */ 163 post_hyph, /* %B */ /* FIXME: can be used outside Rs/Re. */ 164 NULL, /* %D */ 165 NULL, /* %I */ 166 NULL, /* %J */ 167 post_hyph, /* %N */ 168 post_hyph, /* %O */ 169 NULL, /* %P */ 170 post_hyph, /* %R */ 171 post_hyph, /* %T */ /* FIXME: can be used outside Rs/Re. */ 172 NULL, /* %V */ 173 NULL, /* Ac */ 174 NULL, /* Ao */ 175 post_delim_nb, /* Aq */ 176 post_at, /* At */ 177 NULL, /* Bc */ 178 post_bf, /* Bf */ 179 NULL, /* Bo */ 180 NULL, /* Bq */ 181 post_xx, /* Bsx */ 182 post_bx, /* Bx */ 183 post_obsolete, /* Db */ 184 NULL, /* Dc */ 185 NULL, /* Do */ 186 NULL, /* Dq */ 187 NULL, /* Ec */ 188 NULL, /* Ef */ 189 post_delim_nb, /* Em */ 190 NULL, /* Eo */ 191 post_xx, /* Fx */ 192 post_delim_nb, /* Ms */ 193 NULL, /* No */ 194 post_ns, /* Ns */ 195 post_xx, /* Nx */ 196 post_xx, /* Ox */ 197 NULL, /* Pc */ 198 NULL, /* Pf */ 199 NULL, /* Po */ 200 post_delim_nb, /* Pq */ 201 NULL, /* Qc */ 202 post_delim_nb, /* Ql */ 203 NULL, /* Qo */ 204 post_delim_nb, /* Qq */ 205 NULL, /* Re */ 206 post_rs, /* Rs */ 207 NULL, /* Sc */ 208 NULL, /* So */ 209 post_delim_nb, /* Sq */ 210 post_sm, /* Sm */ 211 post_sx, /* Sx */ 212 post_delim_nb, /* Sy */ 213 post_useless, /* Tn */ 214 post_xx, /* Ux */ 215 NULL, /* Xc */ 216 NULL, /* Xo */ 217 post_fo, /* Fo */ 218 NULL, /* Fc */ 219 NULL, /* Oo */ 220 NULL, /* Oc */ 221 post_bk, /* Bk */ 222 NULL, /* Ek */ 223 post_eoln, /* Bt */ 224 post_obsolete, /* Hf */ 225 post_obsolete, /* Fr */ 226 post_eoln, /* Ud */ 227 post_lb, /* Lb */ 228 post_abort, /* Lp */ 229 post_delim_nb, /* Lk */ 230 post_defaults, /* Mt */ 231 post_delim_nb, /* Brq */ 232 NULL, /* Bro */ 233 NULL, /* Brc */ 234 NULL, /* %C */ 235 post_es, /* Es */ 236 post_en, /* En */ 237 post_xx, /* Dx */ 238 NULL, /* %Q */ 239 NULL, /* %U */ 240 NULL, /* Ta */ 241}; 242 243#define RSORD_MAX 14 /* Number of `Rs' blocks. */ 244 245static 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 262static 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. */ 290void 291mdoc_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 385static void 386check_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 398static void 399check_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 407static void 408check_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 419static void 420check_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 470static void 471check_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 500static void 501post_abort(POST_ARGS) 502{ 503 abort(); 504} 505 506static void 507post_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 533static void 534post_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 626static void 627post_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 796static void 797post_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 870static void 871post_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 896static void 897post_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 916static int 917build_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 943static void 944post_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 980static void 981post_lb(POST_ARGS) 982{ 983 struct roff_node *n; 984 const char *p; 985 986 post_delim_nb(mdoc); 987 988 n = mdoc->last; 989 assert(n->child->type == ROFFT_TEXT); 990 mdoc->next = ROFF_NEXT_CHILD; 991 992 if ((p = mdoc_a2lib(n->child->string)) != NULL) { 993 n->child->flags |= NODE_NOPRT; 994 roff_word_alloc(mdoc, n->line, n->pos, p); 995 mdoc->last->flags = NODE_NOSRC; 996 mdoc->last = n; 997 return; 998 } 999 1000 mandoc_msg(MANDOCERR_LB_BAD, n->child->line, 1001 n->child->pos, "Lb %s", n->child->string); 1002 1003 roff_word_alloc(mdoc, n->line, n->pos, "library"); 1004 mdoc->last->flags = NODE_NOSRC; 1005 roff_word_alloc(mdoc, n->line, n->pos, "\\(lq"); 1006 mdoc->last->flags = NODE_DELIMO | NODE_NOSRC; 1007 mdoc->last = mdoc->last->next; 1008 roff_word_alloc(mdoc, n->line, n->pos, "\\(rq"); 1009 mdoc->last->flags = NODE_DELIMC | NODE_NOSRC; 1010 mdoc->last = n; 1011} 1012 1013static void 1014post_rv(POST_ARGS) 1015{ 1016 struct roff_node *n; 1017 int ic; 1018 1019 post_std(mdoc); 1020 1021 n = mdoc->last; 1022 mdoc->next = ROFF_NEXT_CHILD; 1023 if (n->child != NULL) { 1024 roff_word_alloc(mdoc, n->line, n->pos, "The"); 1025 mdoc->last->flags |= NODE_NOSRC; 1026 ic = build_list(mdoc, MDOC_Fn); 1027 roff_word_alloc(mdoc, n->line, n->pos, 1028 ic > 1 ? "functions return" : "function returns"); 1029 mdoc->last->flags |= NODE_NOSRC; 1030 roff_word_alloc(mdoc, n->line, n->pos, 1031 "the value\\~0 if successful;"); 1032 } else 1033 roff_word_alloc(mdoc, n->line, n->pos, "Upon successful " 1034 "completion, the value\\~0 is returned;"); 1035 mdoc->last->flags |= NODE_NOSRC; 1036 1037 roff_word_alloc(mdoc, n->line, n->pos, "otherwise " 1038 "the value\\~\\-1 is returned and the global variable"); 1039 mdoc->last->flags |= NODE_NOSRC; 1040 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Va); 1041 mdoc->last->flags |= NODE_NOSRC; 1042 roff_word_alloc(mdoc, n->line, n->pos, "errno"); 1043 mdoc->last->flags |= NODE_NOSRC; 1044 mdoc->last = mdoc->last->parent; 1045 mdoc->next = ROFF_NEXT_SIBLING; 1046 roff_word_alloc(mdoc, n->line, n->pos, 1047 "is set to indicate the error."); 1048 mdoc->last->flags |= NODE_EOS | NODE_NOSRC; 1049 mdoc->last = n; 1050} 1051 1052static void 1053post_std(POST_ARGS) 1054{ 1055 struct roff_node *n; 1056 1057 post_delim(mdoc); 1058 1059 n = mdoc->last; 1060 if (n->args && n->args->argc == 1) 1061 if (n->args->argv[0].arg == MDOC_Std) 1062 return; 1063 1064 mandoc_msg(MANDOCERR_ARG_STD, n->line, n->pos, 1065 "%s", roff_name[n->tok]); 1066} 1067 1068static void 1069post_st(POST_ARGS) 1070{ 1071 struct roff_node *n, *nch; 1072 const char *p; 1073 1074 n = mdoc->last; 1075 nch = n->child; 1076 assert(nch->type == ROFFT_TEXT); 1077 1078 if ((p = mdoc_a2st(nch->string)) == NULL) { 1079 mandoc_msg(MANDOCERR_ST_BAD, 1080 nch->line, nch->pos, "St %s", nch->string); 1081 roff_node_delete(mdoc, n); 1082 return; 1083 } 1084 1085 nch->flags |= NODE_NOPRT; 1086 mdoc->next = ROFF_NEXT_CHILD; 1087 roff_word_alloc(mdoc, nch->line, nch->pos, p); 1088 mdoc->last->flags |= NODE_NOSRC; 1089 mdoc->last= n; 1090} 1091 1092static void 1093post_obsolete(POST_ARGS) 1094{ 1095 struct roff_node *n; 1096 1097 n = mdoc->last; 1098 if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK) 1099 mandoc_msg(MANDOCERR_MACRO_OBS, n->line, n->pos, 1100 "%s", roff_name[n->tok]); 1101} 1102 1103static void 1104post_useless(POST_ARGS) 1105{ 1106 struct roff_node *n; 1107 1108 n = mdoc->last; 1109 mandoc_msg(MANDOCERR_MACRO_USELESS, n->line, n->pos, 1110 "%s", roff_name[n->tok]); 1111} 1112 1113/* 1114 * Block macros. 1115 */ 1116 1117static void 1118post_bf(POST_ARGS) 1119{ 1120 struct roff_node *np, *nch; 1121 1122 /* 1123 * Unlike other data pointers, these are "housed" by the HEAD 1124 * element, which contains the goods. 1125 */ 1126 1127 np = mdoc->last; 1128 if (np->type != ROFFT_HEAD) 1129 return; 1130 1131 assert(np->parent->type == ROFFT_BLOCK); 1132 assert(np->parent->tok == MDOC_Bf); 1133 1134 /* Check the number of arguments. */ 1135 1136 nch = np->child; 1137 if (np->parent->args == NULL) { 1138 if (nch == NULL) { 1139 mandoc_msg(MANDOCERR_BF_NOFONT, 1140 np->line, np->pos, "Bf"); 1141 return; 1142 } 1143 nch = nch->next; 1144 } 1145 if (nch != NULL) 1146 mandoc_msg(MANDOCERR_ARG_EXCESS, 1147 nch->line, nch->pos, "Bf ... %s", nch->string); 1148 1149 /* Extract argument into data. */ 1150 1151 if (np->parent->args != NULL) { 1152 switch (np->parent->args->argv[0].arg) { 1153 case MDOC_Emphasis: 1154 np->norm->Bf.font = FONT_Em; 1155 break; 1156 case MDOC_Literal: 1157 np->norm->Bf.font = FONT_Li; 1158 break; 1159 case MDOC_Symbolic: 1160 np->norm->Bf.font = FONT_Sy; 1161 break; 1162 default: 1163 abort(); 1164 } 1165 return; 1166 } 1167 1168 /* Extract parameter into data. */ 1169 1170 if ( ! strcmp(np->child->string, "Em")) 1171 np->norm->Bf.font = FONT_Em; 1172 else if ( ! strcmp(np->child->string, "Li")) 1173 np->norm->Bf.font = FONT_Li; 1174 else if ( ! strcmp(np->child->string, "Sy")) 1175 np->norm->Bf.font = FONT_Sy; 1176 else 1177 mandoc_msg(MANDOCERR_BF_BADFONT, np->child->line, 1178 np->child->pos, "Bf %s", np->child->string); 1179} 1180 1181static void 1182post_fname(POST_ARGS) 1183{ 1184 const struct roff_node *n; 1185 const char *cp; 1186 size_t pos; 1187 1188 n = mdoc->last->child; 1189 pos = strcspn(n->string, "()"); 1190 cp = n->string + pos; 1191 if ( ! (cp[0] == '\0' || (cp[0] == '(' && cp[1] == '*'))) 1192 mandoc_msg(MANDOCERR_FN_PAREN, n->line, n->pos + pos, 1193 "%s", n->string); 1194} 1195 1196static void 1197post_fn(POST_ARGS) 1198{ 1199 1200 post_fname(mdoc); 1201 post_fa(mdoc); 1202} 1203 1204static void 1205post_fo(POST_ARGS) 1206{ 1207 const struct roff_node *n; 1208 1209 n = mdoc->last; 1210 1211 if (n->type != ROFFT_HEAD) 1212 return; 1213 1214 if (n->child == NULL) { 1215 mandoc_msg(MANDOCERR_FO_NOHEAD, n->line, n->pos, "Fo"); 1216 return; 1217 } 1218 if (n->child != n->last) { 1219 mandoc_msg(MANDOCERR_ARG_EXCESS, 1220 n->child->next->line, n->child->next->pos, 1221 "Fo ... %s", n->child->next->string); 1222 while (n->child != n->last) { 1223 struct roff_node *p = n->last; 1224 roff_node_delete(mdoc, p); 1225 } 1226 1227 } else 1228 post_delim(mdoc); 1229 1230 post_fname(mdoc); 1231} 1232 1233static void 1234post_fa(POST_ARGS) 1235{ 1236 const struct roff_node *n; 1237 const char *cp; 1238 1239 for (n = mdoc->last->child; n != NULL; n = n->next) { 1240 for (cp = n->string; *cp != '\0'; cp++) { 1241 /* Ignore callbacks and alterations. */ 1242 if (*cp == '(' || *cp == '{') 1243 break; 1244 if (*cp != ',') 1245 continue; 1246 mandoc_msg(MANDOCERR_FA_COMMA, n->line, 1247 n->pos + (int)(cp - n->string), "%s", n->string); 1248 break; 1249 } 1250 } 1251 post_delim_nb(mdoc); 1252} 1253 1254static void 1255post_nm(POST_ARGS) 1256{ 1257 struct roff_node *n; 1258 1259 n = mdoc->last; 1260 1261 if (n->sec == SEC_NAME && n->child != NULL && 1262 n->child->type == ROFFT_TEXT && mdoc->meta.msec != NULL) 1263 mandoc_xr_add(mdoc->meta.msec, n->child->string, -1, -1); 1264 1265 if (n->last != NULL && n->last->tok == MDOC_Pp) 1266 roff_node_relink(mdoc, n->last); 1267 1268 if (mdoc->meta.name == NULL) 1269 deroff(&mdoc->meta.name, n); 1270 1271 if (mdoc->meta.name == NULL || 1272 (mdoc->lastsec == SEC_NAME && n->child == NULL)) 1273 mandoc_msg(MANDOCERR_NM_NONAME, n->line, n->pos, "Nm"); 1274 1275 switch (n->type) { 1276 case ROFFT_ELEM: 1277 post_delim_nb(mdoc); 1278 break; 1279 case ROFFT_HEAD: 1280 post_delim(mdoc); 1281 break; 1282 default: 1283 return; 1284 } 1285 1286 if ((n->child != NULL && n->child->type == ROFFT_TEXT) || 1287 mdoc->meta.name == NULL) 1288 return; 1289 1290 mdoc->next = ROFF_NEXT_CHILD; 1291 roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name); 1292 mdoc->last->flags |= NODE_NOSRC; 1293 mdoc->last = n; 1294} 1295 1296static void 1297post_nd(POST_ARGS) 1298{ 1299 struct roff_node *n; 1300 1301 n = mdoc->last; 1302 1303 if (n->type != ROFFT_BODY) 1304 return; 1305 1306 if (n->sec != SEC_NAME) 1307 mandoc_msg(MANDOCERR_ND_LATE, n->line, n->pos, "Nd"); 1308 1309 if (n->child == NULL) 1310 mandoc_msg(MANDOCERR_ND_EMPTY, n->line, n->pos, "Nd"); 1311 else 1312 post_delim(mdoc); 1313 1314 post_hyph(mdoc); 1315} 1316 1317static void 1318post_display(POST_ARGS) 1319{ 1320 struct roff_node *n, *np; 1321 1322 n = mdoc->last; 1323 switch (n->type) { 1324 case ROFFT_BODY: 1325 if (n->end != ENDBODY_NOT) { 1326 if (n->tok == MDOC_Bd && 1327 n->body->parent->args == NULL) 1328 roff_node_delete(mdoc, n); 1329 } else if (n->child == NULL) 1330 mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos, 1331 "%s", roff_name[n->tok]); 1332 else if (n->tok == MDOC_D1) 1333 post_hyph(mdoc); 1334 break; 1335 case ROFFT_BLOCK: 1336 if (n->tok == MDOC_Bd) { 1337 if (n->args == NULL) { 1338 mandoc_msg(MANDOCERR_BD_NOARG, 1339 n->line, n->pos, "Bd"); 1340 mdoc->next = ROFF_NEXT_SIBLING; 1341 while (n->body->child != NULL) 1342 roff_node_relink(mdoc, 1343 n->body->child); 1344 roff_node_delete(mdoc, n); 1345 break; 1346 } 1347 post_bd(mdoc); 1348 post_prevpar(mdoc); 1349 } 1350 for (np = n->parent; np != NULL; np = np->parent) { 1351 if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) { 1352 mandoc_msg(MANDOCERR_BD_NEST, n->line, 1353 n->pos, "%s in Bd", roff_name[n->tok]); 1354 break; 1355 } 1356 } 1357 break; 1358 default: 1359 break; 1360 } 1361} 1362 1363static void 1364post_defaults(POST_ARGS) 1365{ 1366 struct roff_node *nn; 1367 1368 if (mdoc->last->child != NULL) { 1369 post_delim_nb(mdoc); 1370 return; 1371 } 1372 1373 /* 1374 * The `Ar' defaults to "file ..." if no value is provided as an 1375 * argument; the `Mt' and `Pa' macros use "~"; the `Li' just 1376 * gets an empty string. 1377 */ 1378 1379 nn = mdoc->last; 1380 switch (nn->tok) { 1381 case MDOC_Ar: 1382 mdoc->next = ROFF_NEXT_CHILD; 1383 roff_word_alloc(mdoc, nn->line, nn->pos, "file"); 1384 mdoc->last->flags |= NODE_NOSRC; 1385 roff_word_alloc(mdoc, nn->line, nn->pos, "..."); 1386 mdoc->last->flags |= NODE_NOSRC; 1387 break; 1388 case MDOC_Pa: 1389 case MDOC_Mt: 1390 mdoc->next = ROFF_NEXT_CHILD; 1391 roff_word_alloc(mdoc, nn->line, nn->pos, "~"); 1392 mdoc->last->flags |= NODE_NOSRC; 1393 break; 1394 default: 1395 abort(); 1396 } 1397 mdoc->last = nn; 1398} 1399 1400static void 1401post_at(POST_ARGS) 1402{ 1403 struct roff_node *n, *nch; 1404 const char *att; 1405 1406 n = mdoc->last; 1407 nch = n->child; 1408 1409 /* 1410 * If we have a child, look it up in the standard keys. If a 1411 * key exist, use that instead of the child; if it doesn't, 1412 * prefix "AT&T UNIX " to the existing data. 1413 */ 1414 1415 att = NULL; 1416 if (nch != NULL && ((att = mdoc_a2att(nch->string)) == NULL)) 1417 mandoc_msg(MANDOCERR_AT_BAD, 1418 nch->line, nch->pos, "At %s", nch->string); 1419 1420 mdoc->next = ROFF_NEXT_CHILD; 1421 if (att != NULL) { 1422 roff_word_alloc(mdoc, nch->line, nch->pos, att); 1423 nch->flags |= NODE_NOPRT; 1424 } else 1425 roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX"); 1426 mdoc->last->flags |= NODE_NOSRC; 1427 mdoc->last = n; 1428} 1429 1430static void 1431post_an(POST_ARGS) 1432{ 1433 struct roff_node *np, *nch; 1434 1435 post_an_norm(mdoc); 1436 1437 np = mdoc->last; 1438 nch = np->child; 1439 if (np->norm->An.auth == AUTH__NONE) { 1440 if (nch == NULL) 1441 mandoc_msg(MANDOCERR_MACRO_EMPTY, 1442 np->line, np->pos, "An"); 1443 else 1444 post_delim_nb(mdoc); 1445 } else if (nch != NULL) 1446 mandoc_msg(MANDOCERR_ARG_EXCESS, 1447 nch->line, nch->pos, "An ... %s", nch->string); 1448} 1449 1450static void 1451post_en(POST_ARGS) 1452{ 1453 1454 post_obsolete(mdoc); 1455 if (mdoc->last->type == ROFFT_BLOCK) 1456 mdoc->last->norm->Es = mdoc->last_es; 1457} 1458 1459static void 1460post_es(POST_ARGS) 1461{ 1462 1463 post_obsolete(mdoc); 1464 mdoc->last_es = mdoc->last; 1465} 1466 1467static void 1468post_xx(POST_ARGS) 1469{ 1470 struct roff_node *n; 1471 const char *os; 1472 char *v; 1473 1474 post_delim_nb(mdoc); 1475 1476 n = mdoc->last; 1477 switch (n->tok) { 1478 case MDOC_Bsx: 1479 os = "BSD/OS"; 1480 break; 1481 case MDOC_Dx: 1482 os = "DragonFly"; 1483 break; 1484 case MDOC_Fx: 1485 os = "FreeBSD"; 1486 break; 1487 case MDOC_Nx: 1488 os = "NetBSD"; 1489 if (n->child == NULL) 1490 break; 1491 v = n->child->string; 1492 if ((v[0] != '0' && v[0] != '1') || v[1] != '.' || 1493 v[2] < '0' || v[2] > '9' || 1494 v[3] < 'a' || v[3] > 'z' || v[4] != '\0') 1495 break; 1496 n->child->flags |= NODE_NOPRT; 1497 mdoc->next = ROFF_NEXT_CHILD; 1498 roff_word_alloc(mdoc, n->child->line, n->child->pos, v); 1499 v = mdoc->last->string; 1500 v[3] = toupper((unsigned char)v[3]); 1501 mdoc->last->flags |= NODE_NOSRC; 1502 mdoc->last = n; 1503 break; 1504 case MDOC_Ox: 1505 os = "OpenBSD"; 1506 break; 1507 case MDOC_Ux: 1508 os = "UNIX"; 1509 break; 1510 default: 1511 abort(); 1512 } 1513 mdoc->next = ROFF_NEXT_CHILD; 1514 roff_word_alloc(mdoc, n->line, n->pos, os); 1515 mdoc->last->flags |= NODE_NOSRC; 1516 mdoc->last = n; 1517} 1518 1519static void 1520post_it(POST_ARGS) 1521{ 1522 struct roff_node *nbl, *nit, *nch; 1523 int i, cols; 1524 enum mdoc_list lt; 1525 1526 post_prevpar(mdoc); 1527 1528 nit = mdoc->last; 1529 if (nit->type != ROFFT_BLOCK) 1530 return; 1531 1532 nbl = nit->parent->parent; 1533 lt = nbl->norm->Bl.type; 1534 1535 switch (lt) { 1536 case LIST_tag: 1537 case LIST_hang: 1538 case LIST_ohang: 1539 case LIST_inset: 1540 case LIST_diag: 1541 if (nit->head->child == NULL) 1542 mandoc_msg(MANDOCERR_IT_NOHEAD, 1543 nit->line, nit->pos, "Bl -%s It", 1544 mdoc_argnames[nbl->args->argv[0].arg]); 1545 break; 1546 case LIST_bullet: 1547 case LIST_dash: 1548 case LIST_enum: 1549 case LIST_hyphen: 1550 if (nit->body == NULL || nit->body->child == NULL) 1551 mandoc_msg(MANDOCERR_IT_NOBODY, 1552 nit->line, nit->pos, "Bl -%s It", 1553 mdoc_argnames[nbl->args->argv[0].arg]); 1554 /* FALLTHROUGH */ 1555 case LIST_item: 1556 if ((nch = nit->head->child) != NULL) 1557 mandoc_msg(MANDOCERR_ARG_SKIP, 1558 nit->line, nit->pos, "It %s", 1559 nch->string == NULL ? roff_name[nch->tok] : 1560 nch->string); 1561 break; 1562 case LIST_column: 1563 cols = (int)nbl->norm->Bl.ncols; 1564 1565 assert(nit->head->child == NULL); 1566 1567 if (nit->head->next->child == NULL && 1568 nit->head->next->next == NULL) { 1569 mandoc_msg(MANDOCERR_MACRO_EMPTY, 1570 nit->line, nit->pos, "It"); 1571 roff_node_delete(mdoc, nit); 1572 break; 1573 } 1574 1575 i = 0; 1576 for (nch = nit->child; nch != NULL; nch = nch->next) { 1577 if (nch->type != ROFFT_BODY) 1578 continue; 1579 if (i++ && nch->flags & NODE_LINE) 1580 mandoc_msg(MANDOCERR_TA_LINE, 1581 nch->line, nch->pos, "Ta"); 1582 } 1583 if (i < cols || i > cols + 1) 1584 mandoc_msg(MANDOCERR_BL_COL, nit->line, nit->pos, 1585 "%d columns, %d cells", cols, i); 1586 else if (nit->head->next->child != NULL && 1587 nit->head->next->child->flags & NODE_LINE) 1588 mandoc_msg(MANDOCERR_IT_NOARG, 1589 nit->line, nit->pos, "Bl -column It"); 1590 break; 1591 default: 1592 abort(); 1593 } 1594} 1595 1596static void 1597post_bl_block(POST_ARGS) 1598{ 1599 struct roff_node *n, *ni, *nc; 1600 1601 post_prevpar(mdoc); 1602 1603 n = mdoc->last; 1604 for (ni = n->body->child; ni != NULL; ni = ni->next) { 1605 if (ni->body == NULL) 1606 continue; 1607 nc = ni->body->last; 1608 while (nc != NULL) { 1609 switch (nc->tok) { 1610 case MDOC_Pp: 1611 case ROFF_br: 1612 break; 1613 default: 1614 nc = NULL; 1615 continue; 1616 } 1617 if (ni->next == NULL) { 1618 mandoc_msg(MANDOCERR_PAR_MOVE, nc->line, 1619 nc->pos, "%s", roff_name[nc->tok]); 1620 roff_node_relink(mdoc, nc); 1621 } else if (n->norm->Bl.comp == 0 && 1622 n->norm->Bl.type != LIST_column) { 1623 mandoc_msg(MANDOCERR_PAR_SKIP, 1624 nc->line, nc->pos, 1625 "%s before It", roff_name[nc->tok]); 1626 roff_node_delete(mdoc, nc); 1627 } else 1628 break; 1629 nc = ni->body->last; 1630 } 1631 } 1632} 1633 1634/* 1635 * If the argument of -offset or -width is a macro, 1636 * replace it with the associated default width. 1637 */ 1638static void 1639rewrite_macro2len(struct roff_man *mdoc, char **arg) 1640{ 1641 size_t width; 1642 enum roff_tok tok; 1643 1644 if (*arg == NULL) 1645 return; 1646 else if ( ! strcmp(*arg, "Ds")) 1647 width = 6; 1648 else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) == TOKEN_NONE) 1649 return; 1650 else 1651 width = macro2len(tok); 1652 1653 free(*arg); 1654 mandoc_asprintf(arg, "%zun", width); 1655} 1656 1657static void 1658post_bl_head(POST_ARGS) 1659{ 1660 struct roff_node *nbl, *nh, *nch, *nnext; 1661 struct mdoc_argv *argv; 1662 int i, j; 1663 1664 post_bl_norm(mdoc); 1665 1666 nh = mdoc->last; 1667 if (nh->norm->Bl.type != LIST_column) { 1668 if ((nch = nh->child) == NULL) 1669 return; 1670 mandoc_msg(MANDOCERR_ARG_EXCESS, 1671 nch->line, nch->pos, "Bl ... %s", nch->string); 1672 while (nch != NULL) { 1673 roff_node_delete(mdoc, nch); 1674 nch = nh->child; 1675 } 1676 return; 1677 } 1678 1679 /* 1680 * Append old-style lists, where the column width specifiers 1681 * trail as macro parameters, to the new-style ("normal-form") 1682 * lists where they're argument values following -column. 1683 */ 1684 1685 if (nh->child == NULL) 1686 return; 1687 1688 nbl = nh->parent; 1689 for (j = 0; j < (int)nbl->args->argc; j++) 1690 if (nbl->args->argv[j].arg == MDOC_Column) 1691 break; 1692 1693 assert(j < (int)nbl->args->argc); 1694 1695 /* 1696 * Accommodate for new-style groff column syntax. Shuffle the 1697 * child nodes, all of which must be TEXT, as arguments for the 1698 * column field. Then, delete the head children. 1699 */ 1700 1701 argv = nbl->args->argv + j; 1702 i = argv->sz; 1703 for (nch = nh->child; nch != NULL; nch = nch->next) 1704 argv->sz++; 1705 argv->value = mandoc_reallocarray(argv->value, 1706 argv->sz, sizeof(char *)); 1707 1708 nh->norm->Bl.ncols = argv->sz; 1709 nh->norm->Bl.cols = (void *)argv->value; 1710 1711 for (nch = nh->child; nch != NULL; nch = nnext) { 1712 argv->value[i++] = nch->string; 1713 nch->string = NULL; 1714 nnext = nch->next; 1715 roff_node_delete(NULL, nch); 1716 } 1717 nh->child = NULL; 1718} 1719 1720static void 1721post_bl(POST_ARGS) 1722{ 1723 struct roff_node *nparent, *nprev; /* of the Bl block */ 1724 struct roff_node *nblock, *nbody; /* of the Bl */ 1725 struct roff_node *nchild, *nnext; /* of the Bl body */ 1726 const char *prev_Er; 1727 int order; 1728 1729 nbody = mdoc->last; 1730 switch (nbody->type) { 1731 case ROFFT_BLOCK: 1732 post_bl_block(mdoc); 1733 return; 1734 case ROFFT_HEAD: 1735 post_bl_head(mdoc); 1736 return; 1737 case ROFFT_BODY: 1738 break; 1739 default: 1740 return; 1741 } 1742 if (nbody->end != ENDBODY_NOT) 1743 return; 1744 1745 nchild = nbody->child; 1746 if (nchild == NULL) { 1747 mandoc_msg(MANDOCERR_BLK_EMPTY, 1748 nbody->line, nbody->pos, "Bl"); 1749 return; 1750 } 1751 while (nchild != NULL) { 1752 nnext = nchild->next; 1753 if (nchild->tok == MDOC_It || 1754 (nchild->tok == MDOC_Sm && 1755 nnext != NULL && nnext->tok == MDOC_It)) { 1756 nchild = nnext; 1757 continue; 1758 } 1759 1760 /* 1761 * In .Bl -column, the first rows may be implicit, 1762 * that is, they may not start with .It macros. 1763 * Such rows may be followed by nodes generated on the 1764 * roff level, for example .TS, which cannot be moved 1765 * out of the list. In that case, wrap such roff nodes 1766 * into an implicit row. 1767 */ 1768 1769 if (nchild->prev != NULL) { 1770 mdoc->last = nchild; 1771 mdoc->next = ROFF_NEXT_SIBLING; 1772 roff_block_alloc(mdoc, nchild->line, 1773 nchild->pos, MDOC_It); 1774 roff_head_alloc(mdoc, nchild->line, 1775 nchild->pos, MDOC_It); 1776 mdoc->next = ROFF_NEXT_SIBLING; 1777 roff_body_alloc(mdoc, nchild->line, 1778 nchild->pos, MDOC_It); 1779 while (nchild->tok != MDOC_It) { 1780 roff_node_relink(mdoc, nchild); 1781 if ((nchild = nnext) == NULL) 1782 break; 1783 nnext = nchild->next; 1784 mdoc->next = ROFF_NEXT_SIBLING; 1785 } 1786 mdoc->last = nbody; 1787 continue; 1788 } 1789 1790 mandoc_msg(MANDOCERR_BL_MOVE, nchild->line, nchild->pos, 1791 "%s", roff_name[nchild->tok]); 1792 1793 /* 1794 * Move the node out of the Bl block. 1795 * First, collect all required node pointers. 1796 */ 1797 1798 nblock = nbody->parent; 1799 nprev = nblock->prev; 1800 nparent = nblock->parent; 1801 1802 /* 1803 * Unlink this child. 1804 */ 1805 1806 nbody->child = nnext; 1807 if (nnext == NULL) 1808 nbody->last = NULL; 1809 else 1810 nnext->prev = NULL; 1811 1812 /* 1813 * Relink this child. 1814 */ 1815 1816 nchild->parent = nparent; 1817 nchild->prev = nprev; 1818 nchild->next = nblock; 1819 1820 nblock->prev = nchild; 1821 if (nprev == NULL) 1822 nparent->child = nchild; 1823 else 1824 nprev->next = nchild; 1825 1826 nchild = nnext; 1827 } 1828 1829 if (mdoc->meta.os_e != MANDOC_OS_NETBSD) 1830 return; 1831 1832 prev_Er = NULL; 1833 for (nchild = nbody->child; nchild != NULL; nchild = nchild->next) { 1834 if (nchild->tok != MDOC_It) 1835 continue; 1836 if ((nnext = nchild->head->child) == NULL) 1837 continue; 1838 if (nnext->type == ROFFT_BLOCK) 1839 nnext = nnext->body->child; 1840 if (nnext == NULL || nnext->tok != MDOC_Er) 1841 continue; 1842 nnext = nnext->child; 1843 if (prev_Er != NULL) { 1844 order = strcmp(prev_Er, nnext->string); 1845 if (order > 0) 1846 mandoc_msg(MANDOCERR_ER_ORDER, 1847 nnext->line, nnext->pos, 1848 "Er %s %s (NetBSD)", 1849 prev_Er, nnext->string); 1850 else if (order == 0) 1851 mandoc_msg(MANDOCERR_ER_REP, 1852 nnext->line, nnext->pos, 1853 "Er %s (NetBSD)", prev_Er); 1854 } 1855 prev_Er = nnext->string; 1856 } 1857} 1858 1859static void 1860post_bk(POST_ARGS) 1861{ 1862 struct roff_node *n; 1863 1864 n = mdoc->last; 1865 1866 if (n->type == ROFFT_BLOCK && n->body->child == NULL) { 1867 mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos, "Bk"); 1868 roff_node_delete(mdoc, n); 1869 } 1870} 1871 1872static void 1873post_sm(POST_ARGS) 1874{ 1875 struct roff_node *nch; 1876 1877 nch = mdoc->last->child; 1878 1879 if (nch == NULL) { 1880 mdoc->flags ^= MDOC_SMOFF; 1881 return; 1882 } 1883 1884 assert(nch->type == ROFFT_TEXT); 1885 1886 if ( ! strcmp(nch->string, "on")) { 1887 mdoc->flags &= ~MDOC_SMOFF; 1888 return; 1889 } 1890 if ( ! strcmp(nch->string, "off")) { 1891 mdoc->flags |= MDOC_SMOFF; 1892 return; 1893 } 1894 1895 mandoc_msg(MANDOCERR_SM_BAD, nch->line, nch->pos, 1896 "%s %s", roff_name[mdoc->last->tok], nch->string); 1897 roff_node_relink(mdoc, nch); 1898 return; 1899} 1900 1901static void 1902post_root(POST_ARGS) 1903{ 1904 struct roff_node *n; 1905 1906 /* Add missing prologue data. */ 1907 1908 if (mdoc->meta.date == NULL) 1909 mdoc->meta.date = mdoc->quick ? mandoc_strdup("") : 1910 mandoc_normdate(mdoc, NULL, 0, 0); 1911 1912 if (mdoc->meta.title == NULL) { 1913 mandoc_msg(MANDOCERR_DT_NOTITLE, 0, 0, "EOF"); 1914 mdoc->meta.title = mandoc_strdup("UNTITLED"); 1915 } 1916 1917 if (mdoc->meta.vol == NULL) 1918 mdoc->meta.vol = mandoc_strdup("LOCAL"); 1919 1920 if (mdoc->meta.os == NULL) { 1921 mandoc_msg(MANDOCERR_OS_MISSING, 0, 0, NULL); 1922 mdoc->meta.os = mandoc_strdup(""); 1923 } else if (mdoc->meta.os_e && 1924 (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0) 1925 mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0, 1926 mdoc->meta.os_e == MANDOC_OS_OPENBSD ? 1927 "(OpenBSD)" : "(NetBSD)"); 1928 1929 if (mdoc->meta.arch != NULL && 1930 arch_valid(mdoc->meta.arch, mdoc->meta.os_e) == 0) { 1931 n = mdoc->meta.first->child; 1932 while (n->tok != MDOC_Dt || 1933 n->child == NULL || 1934 n->child->next == NULL || 1935 n->child->next->next == NULL) 1936 n = n->next; 1937 n = n->child->next->next; 1938 mandoc_msg(MANDOCERR_ARCH_BAD, n->line, n->pos, 1939 "Dt ... %s %s", mdoc->meta.arch, 1940 mdoc->meta.os_e == MANDOC_OS_OPENBSD ? 1941 "(OpenBSD)" : "(NetBSD)"); 1942 } 1943 1944 /* Check that we begin with a proper `Sh'. */ 1945 1946 n = mdoc->meta.first->child; 1947 while (n != NULL && 1948 (n->type == ROFFT_COMMENT || 1949 (n->tok >= MDOC_Dd && 1950 mdoc_macro(n->tok)->flags & MDOC_PROLOGUE))) 1951 n = n->next; 1952 1953 if (n == NULL) 1954 mandoc_msg(MANDOCERR_DOC_EMPTY, 0, 0, NULL); 1955 else if (n->tok != MDOC_Sh) 1956 mandoc_msg(MANDOCERR_SEC_BEFORE, n->line, n->pos, 1957 "%s", roff_name[n->tok]); 1958} 1959 1960static void 1961post_rs(POST_ARGS) 1962{ 1963 struct roff_node *np, *nch, *next, *prev; 1964 int i, j; 1965 1966 np = mdoc->last; 1967 1968 if (np->type != ROFFT_BODY) 1969 return; 1970 1971 if (np->child == NULL) { 1972 mandoc_msg(MANDOCERR_RS_EMPTY, np->line, np->pos, "Rs"); 1973 return; 1974 } 1975 1976 /* 1977 * The full `Rs' block needs special handling to order the 1978 * sub-elements according to `rsord'. Pick through each element 1979 * and correctly order it. This is an insertion sort. 1980 */ 1981 1982 next = NULL; 1983 for (nch = np->child->next; nch != NULL; nch = next) { 1984 /* Determine order number of this child. */ 1985 for (i = 0; i < RSORD_MAX; i++) 1986 if (rsord[i] == nch->tok) 1987 break; 1988 1989 if (i == RSORD_MAX) { 1990 mandoc_msg(MANDOCERR_RS_BAD, nch->line, nch->pos, 1991 "%s", roff_name[nch->tok]); 1992 i = -1; 1993 } else if (nch->tok == MDOC__J || nch->tok == MDOC__B) 1994 np->norm->Rs.quote_T++; 1995 1996 /* 1997 * Remove this child from the chain. This somewhat 1998 * repeats roff_node_unlink(), but since we're 1999 * just re-ordering, there's no need for the 2000 * full unlink process. 2001 */ 2002 2003 if ((next = nch->next) != NULL) 2004 next->prev = nch->prev; 2005 2006 if ((prev = nch->prev) != NULL) 2007 prev->next = nch->next; 2008 2009 nch->prev = nch->next = NULL; 2010 2011 /* 2012 * Scan back until we reach a node that's 2013 * to be ordered before this child. 2014 */ 2015 2016 for ( ; prev ; prev = prev->prev) { 2017 /* Determine order of `prev'. */ 2018 for (j = 0; j < RSORD_MAX; j++) 2019 if (rsord[j] == prev->tok) 2020 break; 2021 if (j == RSORD_MAX) 2022 j = -1; 2023 2024 if (j <= i) 2025 break; 2026 } 2027 2028 /* 2029 * Set this child back into its correct place 2030 * in front of the `prev' node. 2031 */ 2032 2033 nch->prev = prev; 2034 2035 if (prev == NULL) { 2036 np->child->prev = nch; 2037 nch->next = np->child; 2038 np->child = nch; 2039 } else { 2040 if (prev->next) 2041 prev->next->prev = nch; 2042 nch->next = prev->next; 2043 prev->next = nch; 2044 } 2045 } 2046} 2047 2048/* 2049 * For some arguments of some macros, 2050 * convert all breakable hyphens into ASCII_HYPH. 2051 */ 2052static void 2053post_hyph(POST_ARGS) 2054{ 2055 struct roff_node *nch; 2056 char *cp; 2057 2058 for (nch = mdoc->last->child; nch != NULL; nch = nch->next) { 2059 if (nch->type != ROFFT_TEXT) 2060 continue; 2061 cp = nch->string; 2062 if (*cp == '\0') 2063 continue; 2064 while (*(++cp) != '\0') 2065 if (*cp == '-' && 2066 isalpha((unsigned char)cp[-1]) && 2067 isalpha((unsigned char)cp[1])) 2068 *cp = ASCII_HYPH; 2069 } 2070} 2071 2072static void 2073post_ns(POST_ARGS) 2074{ 2075 struct roff_node *n; 2076 2077 n = mdoc->last; 2078 if (n->flags & NODE_LINE || 2079 (n->next != NULL && n->next->flags & NODE_DELIMC)) 2080 mandoc_msg(MANDOCERR_NS_SKIP, n->line, n->pos, NULL); 2081} 2082 2083static void 2084post_sx(POST_ARGS) 2085{ 2086 post_delim(mdoc); 2087 post_hyph(mdoc); 2088} 2089 2090static void 2091post_sh(POST_ARGS) 2092{ 2093 2094 post_ignpar(mdoc); 2095 2096 switch (mdoc->last->type) { 2097 case ROFFT_HEAD: 2098 post_sh_head(mdoc); 2099 break; 2100 case ROFFT_BODY: 2101 switch (mdoc->lastsec) { 2102 case SEC_NAME: 2103 post_sh_name(mdoc); 2104 break; 2105 case SEC_SEE_ALSO: 2106 post_sh_see_also(mdoc); 2107 break; 2108 case SEC_AUTHORS: 2109 post_sh_authors(mdoc); 2110 break; 2111 default: 2112 break; 2113 } 2114 break; 2115 default: 2116 break; 2117 } 2118} 2119 2120static void 2121post_sh_name(POST_ARGS) 2122{ 2123 struct roff_node *n; 2124 int hasnm, hasnd; 2125 2126 hasnm = hasnd = 0; 2127 2128 for (n = mdoc->last->child; n != NULL; n = n->next) { 2129 switch (n->tok) { 2130 case MDOC_Nm: 2131 if (hasnm && n->child != NULL) 2132 mandoc_msg(MANDOCERR_NAMESEC_PUNCT, 2133 n->line, n->pos, 2134 "Nm %s", n->child->string); 2135 hasnm = 1; 2136 continue; 2137 case MDOC_Nd: 2138 hasnd = 1; 2139 if (n->next != NULL) 2140 mandoc_msg(MANDOCERR_NAMESEC_ND, 2141 n->line, n->pos, NULL); 2142 break; 2143 case TOKEN_NONE: 2144 if (n->type == ROFFT_TEXT && 2145 n->string[0] == ',' && n->string[1] == '\0' && 2146 n->next != NULL && n->next->tok == MDOC_Nm) { 2147 n = n->next; 2148 continue; 2149 } 2150 /* FALLTHROUGH */ 2151 default: 2152 mandoc_msg(MANDOCERR_NAMESEC_BAD, 2153 n->line, n->pos, "%s", roff_name[n->tok]); 2154 continue; 2155 } 2156 break; 2157 } 2158 2159 if ( ! hasnm) 2160 mandoc_msg(MANDOCERR_NAMESEC_NONM, 2161 mdoc->last->line, mdoc->last->pos, NULL); 2162 if ( ! hasnd) 2163 mandoc_msg(MANDOCERR_NAMESEC_NOND, 2164 mdoc->last->line, mdoc->last->pos, NULL); 2165} 2166 2167static void 2168post_sh_see_also(POST_ARGS) 2169{ 2170 const struct roff_node *n; 2171 const char *name, *sec; 2172 const char *lastname, *lastsec, *lastpunct; 2173 int cmp; 2174 2175 n = mdoc->last->child; 2176 lastname = lastsec = lastpunct = NULL; 2177 while (n != NULL) { 2178 if (n->tok != MDOC_Xr || 2179 n->child == NULL || 2180 n->child->next == NULL) 2181 break; 2182 2183 /* Process one .Xr node. */ 2184 2185 name = n->child->string; 2186 sec = n->child->next->string; 2187 if (lastsec != NULL) { 2188 if (lastpunct[0] != ',' || lastpunct[1] != '\0') 2189 mandoc_msg(MANDOCERR_XR_PUNCT, n->line, 2190 n->pos, "%s before %s(%s)", 2191 lastpunct, name, sec); 2192 cmp = strcmp(lastsec, sec); 2193 if (cmp > 0) 2194 mandoc_msg(MANDOCERR_XR_ORDER, n->line, 2195 n->pos, "%s(%s) after %s(%s)", 2196 name, sec, lastname, lastsec); 2197 else if (cmp == 0 && 2198 strcasecmp(lastname, name) > 0) 2199 mandoc_msg(MANDOCERR_XR_ORDER, n->line, 2200 n->pos, "%s after %s", name, lastname); 2201 } 2202 lastname = name; 2203 lastsec = sec; 2204 2205 /* Process the following node. */ 2206 2207 n = n->next; 2208 if (n == NULL) 2209 break; 2210 if (n->tok == MDOC_Xr) { 2211 lastpunct = "none"; 2212 continue; 2213 } 2214 if (n->type != ROFFT_TEXT) 2215 break; 2216 for (name = n->string; *name != '\0'; name++) 2217 if (isalpha((const unsigned char)*name)) 2218 return; 2219 lastpunct = n->string; 2220 if (n->next == NULL || n->next->tok == MDOC_Rs) 2221 mandoc_msg(MANDOCERR_XR_PUNCT, n->line, 2222 n->pos, "%s after %s(%s)", 2223 lastpunct, lastname, lastsec); 2224 n = n->next; 2225 } 2226} 2227 2228static int 2229child_an(const struct roff_node *n) 2230{ 2231 2232 for (n = n->child; n != NULL; n = n->next) 2233 if ((n->tok == MDOC_An && n->child != NULL) || child_an(n)) 2234 return 1; 2235 return 0; 2236} 2237 2238static void 2239post_sh_authors(POST_ARGS) 2240{ 2241 2242 if ( ! child_an(mdoc->last)) 2243 mandoc_msg(MANDOCERR_AN_MISSING, 2244 mdoc->last->line, mdoc->last->pos, NULL); 2245} 2246 2247/* 2248 * Return an upper bound for the string distance (allowing 2249 * transpositions). Not a full Levenshtein implementation 2250 * because Levenshtein is quadratic in the string length 2251 * and this function is called for every standard name, 2252 * so the check for each custom name would be cubic. 2253 * The following crude heuristics is linear, resulting 2254 * in quadratic behaviour for checking one custom name, 2255 * which does not cause measurable slowdown. 2256 */ 2257static int 2258similar(const char *s1, const char *s2) 2259{ 2260 const int maxdist = 3; 2261 int dist = 0; 2262 2263 while (s1[0] != '\0' && s2[0] != '\0') { 2264 if (s1[0] == s2[0]) { 2265 s1++; 2266 s2++; 2267 continue; 2268 } 2269 if (++dist > maxdist) 2270 return INT_MAX; 2271 if (s1[1] == s2[1]) { /* replacement */ 2272 s1++; 2273 s2++; 2274 } else if (s1[0] == s2[1] && s1[1] == s2[0]) { 2275 s1 += 2; /* transposition */ 2276 s2 += 2; 2277 } else if (s1[0] == s2[1]) /* insertion */ 2278 s2++; 2279 else if (s1[1] == s2[0]) /* deletion */ 2280 s1++; 2281 else 2282 return INT_MAX; 2283 } 2284 dist += strlen(s1) + strlen(s2); 2285 return dist > maxdist ? INT_MAX : dist; 2286} 2287 2288static void 2289post_sh_head(POST_ARGS) 2290{ 2291 struct roff_node *nch; 2292 const char *goodsec; 2293 const char *const *testsec; 2294 int dist, mindist; 2295 enum roff_sec sec; 2296 2297 /* 2298 * Process a new section. Sections are either "named" or 2299 * "custom". Custom sections are user-defined, while named ones 2300 * follow a conventional order and may only appear in certain 2301 * manual sections. 2302 */ 2303 2304 sec = mdoc->last->sec; 2305 2306 /* The NAME should be first. */ 2307 2308 if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE) 2309 mandoc_msg(MANDOCERR_NAMESEC_FIRST, 2310 mdoc->last->line, mdoc->last->pos, "Sh %s", 2311 sec != SEC_CUSTOM ? secnames[sec] : 2312 (nch = mdoc->last->child) == NULL ? "" : 2313 nch->type == ROFFT_TEXT ? nch->string : 2314 roff_name[nch->tok]); 2315 2316 /* The SYNOPSIS gets special attention in other areas. */ 2317 2318 if (sec == SEC_SYNOPSIS) { 2319 roff_setreg(mdoc->roff, "nS", 1, '='); 2320 mdoc->flags |= MDOC_SYNOPSIS; 2321 } else { 2322 roff_setreg(mdoc->roff, "nS", 0, '='); 2323 mdoc->flags &= ~MDOC_SYNOPSIS; 2324 } 2325 2326 /* Mark our last section. */ 2327 2328 mdoc->lastsec = sec; 2329 2330 /* We don't care about custom sections after this. */ 2331 2332 if (sec == SEC_CUSTOM) { 2333 if ((nch = mdoc->last->child) == NULL || 2334 nch->type != ROFFT_TEXT || nch->next != NULL) 2335 return; 2336 goodsec = NULL; 2337 mindist = INT_MAX; 2338 for (testsec = secnames + 1; *testsec != NULL; testsec++) { 2339 dist = similar(nch->string, *testsec); 2340 if (dist < mindist) { 2341 goodsec = *testsec; 2342 mindist = dist; 2343 } 2344 } 2345 if (goodsec != NULL) 2346 mandoc_msg(MANDOCERR_SEC_TYPO, nch->line, nch->pos, 2347 "Sh %s instead of %s", nch->string, goodsec); 2348 return; 2349 } 2350 2351 /* 2352 * Check whether our non-custom section is being repeated or is 2353 * out of order. 2354 */ 2355 2356 if (sec == mdoc->lastnamed) 2357 mandoc_msg(MANDOCERR_SEC_REP, mdoc->last->line, 2358 mdoc->last->pos, "Sh %s", secnames[sec]); 2359 2360 if (sec < mdoc->lastnamed) 2361 mandoc_msg(MANDOCERR_SEC_ORDER, mdoc->last->line, 2362 mdoc->last->pos, "Sh %s", secnames[sec]); 2363 2364 /* Mark the last named section. */ 2365 2366 mdoc->lastnamed = sec; 2367 2368 /* Check particular section/manual conventions. */ 2369 2370 if (mdoc->meta.msec == NULL) 2371 return; 2372 2373 goodsec = NULL; 2374 switch (sec) { 2375 case SEC_ERRORS: 2376 if (*mdoc->meta.msec == '4') 2377 break; 2378 goodsec = "2, 3, 4, 9"; 2379 /* FALLTHROUGH */ 2380 case SEC_RETURN_VALUES: 2381 case SEC_LIBRARY: 2382 if (*mdoc->meta.msec == '2') 2383 break; 2384 if (*mdoc->meta.msec == '3') 2385 break; 2386 if (NULL == goodsec) 2387 goodsec = "2, 3, 9"; 2388 /* FALLTHROUGH */ 2389 case SEC_CONTEXT: 2390 if (*mdoc->meta.msec == '9') 2391 break; 2392 if (NULL == goodsec) 2393 goodsec = "9"; 2394 mandoc_msg(MANDOCERR_SEC_MSEC, 2395 mdoc->last->line, mdoc->last->pos, 2396 "Sh %s for %s only", secnames[sec], goodsec); 2397 break; 2398 default: 2399 break; 2400 } 2401} 2402 2403static void 2404post_xr(POST_ARGS) 2405{ 2406 struct roff_node *n, *nch; 2407 2408 n = mdoc->last; 2409 nch = n->child; 2410 if (nch->next == NULL) { 2411 mandoc_msg(MANDOCERR_XR_NOSEC, 2412 n->line, n->pos, "Xr %s", nch->string); 2413 } else { 2414 assert(nch->next == n->last); 2415 if(mandoc_xr_add(nch->next->string, nch->string, 2416 nch->line, nch->pos)) 2417 mandoc_msg(MANDOCERR_XR_SELF, 2418 nch->line, nch->pos, "Xr %s %s", 2419 nch->string, nch->next->string); 2420 } 2421 post_delim_nb(mdoc); 2422} 2423 2424static void 2425post_ignpar(POST_ARGS) 2426{ 2427 struct roff_node *np; 2428 2429 switch (mdoc->last->type) { 2430 case ROFFT_BLOCK: 2431 post_prevpar(mdoc); 2432 return; 2433 case ROFFT_HEAD: 2434 post_delim(mdoc); 2435 post_hyph(mdoc); 2436 return; 2437 case ROFFT_BODY: 2438 break; 2439 default: 2440 return; 2441 } 2442 2443 if ((np = mdoc->last->child) != NULL) 2444 if (np->tok == MDOC_Pp || 2445 np->tok == ROFF_br || np->tok == ROFF_sp) { 2446 mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos, 2447 "%s after %s", roff_name[np->tok], 2448 roff_name[mdoc->last->tok]); 2449 roff_node_delete(mdoc, np); 2450 } 2451 2452 if ((np = mdoc->last->last) != NULL) 2453 if (np->tok == MDOC_Pp || np->tok == ROFF_br) { 2454 mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos, 2455 "%s at the end of %s", roff_name[np->tok], 2456 roff_name[mdoc->last->tok]); 2457 roff_node_delete(mdoc, np); 2458 } 2459} 2460 2461static void 2462post_prevpar(POST_ARGS) 2463{ 2464 struct roff_node *n; 2465 2466 n = mdoc->last; 2467 if (NULL == n->prev) 2468 return; 2469 if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK) 2470 return; 2471 2472 /* 2473 * Don't allow `Pp' prior to a paragraph-type 2474 * block: `Pp' or non-compact `Bd' or `Bl'. 2475 */ 2476 2477 if (n->prev->tok != MDOC_Pp && n->prev->tok != ROFF_br) 2478 return; 2479 if (n->tok == MDOC_Bl && n->norm->Bl.comp) 2480 return; 2481 if (n->tok == MDOC_Bd && n->norm->Bd.comp) 2482 return; 2483 if (n->tok == MDOC_It && n->parent->norm->Bl.comp) 2484 return; 2485 2486 mandoc_msg(MANDOCERR_PAR_SKIP, n->prev->line, n->prev->pos, 2487 "%s before %s", roff_name[n->prev->tok], roff_name[n->tok]); 2488 roff_node_delete(mdoc, n->prev); 2489} 2490 2491static void 2492post_par(POST_ARGS) 2493{ 2494 struct roff_node *np; 2495 2496 post_prevpar(mdoc); 2497 2498 np = mdoc->last; 2499 if (np->child != NULL) 2500 mandoc_msg(MANDOCERR_ARG_SKIP, np->line, np->pos, 2501 "%s %s", roff_name[np->tok], np->child->string); 2502} 2503 2504static void 2505post_dd(POST_ARGS) 2506{ 2507 struct roff_node *n; 2508 char *datestr; 2509 2510 n = mdoc->last; 2511 n->flags |= NODE_NOPRT; 2512 2513 if (mdoc->meta.date != NULL) { 2514 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dd"); 2515 free(mdoc->meta.date); 2516 } else if (mdoc->flags & MDOC_PBODY) 2517 mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Dd"); 2518 else if (mdoc->meta.title != NULL) 2519 mandoc_msg(MANDOCERR_PROLOG_ORDER, 2520 n->line, n->pos, "Dd after Dt"); 2521 else if (mdoc->meta.os != NULL) 2522 mandoc_msg(MANDOCERR_PROLOG_ORDER, 2523 n->line, n->pos, "Dd after Os"); 2524 2525 if (n->child == NULL || n->child->string[0] == '\0') { 2526 mdoc->meta.date = mdoc->quick ? mandoc_strdup("") : 2527 mandoc_normdate(mdoc, NULL, n->line, n->pos); 2528 return; 2529 } 2530 2531 datestr = NULL; 2532 deroff(&datestr, n); 2533 if (mdoc->quick) 2534 mdoc->meta.date = datestr; 2535 else { 2536 mdoc->meta.date = mandoc_normdate(mdoc, 2537 datestr, n->line, n->pos); 2538 free(datestr); 2539 } 2540} 2541 2542static void 2543post_dt(POST_ARGS) 2544{ 2545 struct roff_node *nn, *n; 2546 const char *cp; 2547 char *p; 2548 2549 n = mdoc->last; 2550 n->flags |= NODE_NOPRT; 2551 2552 if (mdoc->flags & MDOC_PBODY) { 2553 mandoc_msg(MANDOCERR_DT_LATE, n->line, n->pos, "Dt"); 2554 return; 2555 } 2556 2557 if (mdoc->meta.title != NULL) 2558 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dt"); 2559 else if (mdoc->meta.os != NULL) 2560 mandoc_msg(MANDOCERR_PROLOG_ORDER, 2561 n->line, n->pos, "Dt after Os"); 2562 2563 free(mdoc->meta.title); 2564 free(mdoc->meta.msec); 2565 free(mdoc->meta.vol); 2566 free(mdoc->meta.arch); 2567 2568 mdoc->meta.title = NULL; 2569 mdoc->meta.msec = NULL; 2570 mdoc->meta.vol = NULL; 2571 mdoc->meta.arch = NULL; 2572 2573 /* Mandatory first argument: title. */ 2574 2575 nn = n->child; 2576 if (nn == NULL || *nn->string == '\0') { 2577 mandoc_msg(MANDOCERR_DT_NOTITLE, n->line, n->pos, "Dt"); 2578 mdoc->meta.title = mandoc_strdup("UNTITLED"); 2579 } else { 2580 mdoc->meta.title = mandoc_strdup(nn->string); 2581 2582 /* Check that all characters are uppercase. */ 2583 2584 for (p = nn->string; *p != '\0'; p++) 2585 if (islower((unsigned char)*p)) { 2586 mandoc_msg(MANDOCERR_TITLE_CASE, nn->line, 2587 nn->pos + (int)(p - nn->string), 2588 "Dt %s", nn->string); 2589 break; 2590 } 2591 } 2592 2593 /* Mandatory second argument: section. */ 2594 2595 if (nn != NULL) 2596 nn = nn->next; 2597 2598 if (nn == NULL) { 2599 mandoc_msg(MANDOCERR_MSEC_MISSING, n->line, n->pos, 2600 "Dt %s", mdoc->meta.title); 2601 mdoc->meta.vol = mandoc_strdup("LOCAL"); 2602 return; /* msec and arch remain NULL. */ 2603 } 2604 2605 mdoc->meta.msec = mandoc_strdup(nn->string); 2606 2607 /* Infer volume title from section number. */ 2608 2609 cp = mandoc_a2msec(nn->string); 2610 if (cp == NULL) { 2611 mandoc_msg(MANDOCERR_MSEC_BAD, 2612 nn->line, nn->pos, "Dt ... %s", nn->string); 2613 mdoc->meta.vol = mandoc_strdup(nn->string); 2614 } else 2615 mdoc->meta.vol = mandoc_strdup(cp); 2616 2617 /* Optional third argument: architecture. */ 2618 2619 if ((nn = nn->next) == NULL) 2620 return; 2621 2622 for (p = nn->string; *p != '\0'; p++) 2623 *p = tolower((unsigned char)*p); 2624 mdoc->meta.arch = mandoc_strdup(nn->string); 2625 2626 /* Ignore fourth and later arguments. */ 2627 2628 if ((nn = nn->next) != NULL) 2629 mandoc_msg(MANDOCERR_ARG_EXCESS, 2630 nn->line, nn->pos, "Dt ... %s", nn->string); 2631} 2632 2633static void 2634post_bx(POST_ARGS) 2635{ 2636 struct roff_node *n, *nch; 2637 const char *macro; 2638 2639 post_delim_nb(mdoc); 2640 2641 n = mdoc->last; 2642 nch = n->child; 2643 2644 if (nch != NULL) { 2645 macro = !strcmp(nch->string, "Open") ? "Ox" : 2646 !strcmp(nch->string, "Net") ? "Nx" : 2647 !strcmp(nch->string, "Free") ? "Fx" : 2648 !strcmp(nch->string, "DragonFly") ? "Dx" : NULL; 2649 if (macro != NULL) 2650 mandoc_msg(MANDOCERR_BX, 2651 n->line, n->pos, "%s", macro); 2652 mdoc->last = nch; 2653 nch = nch->next; 2654 mdoc->next = ROFF_NEXT_SIBLING; 2655 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns); 2656 mdoc->last->flags |= NODE_NOSRC; 2657 mdoc->next = ROFF_NEXT_SIBLING; 2658 } else 2659 mdoc->next = ROFF_NEXT_CHILD; 2660 roff_word_alloc(mdoc, n->line, n->pos, "BSD"); 2661 mdoc->last->flags |= NODE_NOSRC; 2662 2663 if (nch == NULL) { 2664 mdoc->last = n; 2665 return; 2666 } 2667 2668 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns); 2669 mdoc->last->flags |= NODE_NOSRC; 2670 mdoc->next = ROFF_NEXT_SIBLING; 2671 roff_word_alloc(mdoc, n->line, n->pos, "-"); 2672 mdoc->last->flags |= NODE_NOSRC; 2673 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns); 2674 mdoc->last->flags |= NODE_NOSRC; 2675 mdoc->last = n; 2676 2677 /* 2678 * Make `Bx's second argument always start with an uppercase 2679 * letter. Groff checks if it's an "accepted" term, but we just 2680 * uppercase blindly. 2681 */ 2682 2683 *nch->string = (char)toupper((unsigned char)*nch->string); 2684} 2685 2686static void 2687post_os(POST_ARGS) 2688{ 2689#ifndef OSNAME 2690 struct utsname utsname; 2691 static char *defbuf; 2692#endif 2693 struct roff_node *n; 2694 2695 n = mdoc->last; 2696 n->flags |= NODE_NOPRT; 2697 2698 if (mdoc->meta.os != NULL) 2699 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Os"); 2700 else if (mdoc->flags & MDOC_PBODY) 2701 mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Os"); 2702 2703 post_delim(mdoc); 2704 2705 /* 2706 * Set the operating system by way of the `Os' macro. 2707 * The order of precedence is: 2708 * 1. the argument of the `Os' macro, unless empty 2709 * 2. the -Ios=foo command line argument, if provided 2710 * 3. -DOSNAME="\"foo\"", if provided during compilation 2711 * 4. "sysname release" from uname(3) 2712 */ 2713 2714 free(mdoc->meta.os); 2715 mdoc->meta.os = NULL; 2716 deroff(&mdoc->meta.os, n); 2717 if (mdoc->meta.os) 2718 goto out; 2719 2720 if (mdoc->os_s != NULL) { 2721 mdoc->meta.os = mandoc_strdup(mdoc->os_s); 2722 goto out; 2723 } 2724 2725#ifdef OSNAME 2726 mdoc->meta.os = mandoc_strdup(OSNAME); 2727#else /*!OSNAME */ 2728 if (defbuf == NULL) { 2729 if (uname(&utsname) == -1) { 2730 mandoc_msg(MANDOCERR_OS_UNAME, n->line, n->pos, "Os"); 2731 defbuf = mandoc_strdup("UNKNOWN"); 2732 } else 2733 mandoc_asprintf(&defbuf, "%s %s", 2734 utsname.sysname, utsname.release); 2735 } 2736 mdoc->meta.os = mandoc_strdup(defbuf); 2737#endif /*!OSNAME*/ 2738 2739out: 2740 if (mdoc->meta.os_e == MANDOC_OS_OTHER) { 2741 if (strstr(mdoc->meta.os, "OpenBSD") != NULL) 2742 mdoc->meta.os_e = MANDOC_OS_OPENBSD; 2743 else if (strstr(mdoc->meta.os, "NetBSD") != NULL) 2744 mdoc->meta.os_e = MANDOC_OS_NETBSD; 2745 } 2746 2747 /* 2748 * This is the earliest point where we can check 2749 * Mdocdate conventions because we don't know 2750 * the operating system earlier. 2751 */ 2752 2753 if (n->child != NULL) 2754 mandoc_msg(MANDOCERR_OS_ARG, n->child->line, n->child->pos, 2755 "Os %s (%s)", n->child->string, 2756 mdoc->meta.os_e == MANDOC_OS_OPENBSD ? 2757 "OpenBSD" : "NetBSD"); 2758 2759 while (n->tok != MDOC_Dd) 2760 if ((n = n->prev) == NULL) 2761 return; 2762 if ((n = n->child) == NULL) 2763 return; 2764 if (strncmp(n->string, "$" "Mdocdate", 9)) { 2765 if (mdoc->meta.os_e == MANDOC_OS_OPENBSD) 2766 mandoc_msg(MANDOCERR_MDOCDATE_MISSING, n->line, 2767 n->pos, "Dd %s (OpenBSD)", n->string); 2768 } else { 2769 if (mdoc->meta.os_e == MANDOC_OS_NETBSD) 2770 mandoc_msg(MANDOCERR_MDOCDATE, n->line, 2771 n->pos, "Dd %s (NetBSD)", n->string); 2772 } 2773} 2774 2775enum roff_sec 2776mdoc_a2sec(const char *p) 2777{ 2778 int i; 2779 2780 for (i = 0; i < (int)SEC__MAX; i++) 2781 if (secnames[i] && 0 == strcmp(p, secnames[i])) 2782 return (enum roff_sec)i; 2783 2784 return SEC_CUSTOM; 2785} 2786 2787static size_t 2788macro2len(enum roff_tok macro) 2789{ 2790 2791 switch (macro) { 2792 case MDOC_Ad: 2793 return 12; 2794 case MDOC_Ao: 2795 return 12; 2796 case MDOC_An: 2797 return 12; 2798 case MDOC_Aq: 2799 return 12; 2800 case MDOC_Ar: 2801 return 12; 2802 case MDOC_Bo: 2803 return 12; 2804 case MDOC_Bq: 2805 return 12; 2806 case MDOC_Cd: 2807 return 12; 2808 case MDOC_Cm: 2809 return 10; 2810 case MDOC_Do: 2811 return 10; 2812 case MDOC_Dq: 2813 return 12; 2814 case MDOC_Dv: 2815 return 12; 2816 case MDOC_Eo: 2817 return 12; 2818 case MDOC_Em: 2819 return 10; 2820 case MDOC_Er: 2821 return 17; 2822 case MDOC_Ev: 2823 return 15; 2824 case MDOC_Fa: 2825 return 12; 2826 case MDOC_Fl: 2827 return 10; 2828 case MDOC_Fo: 2829 return 16; 2830 case MDOC_Fn: 2831 return 16; 2832 case MDOC_Ic: 2833 return 10; 2834 case MDOC_Li: 2835 return 16; 2836 case MDOC_Ms: 2837 return 6; 2838 case MDOC_Nm: 2839 return 10; 2840 case MDOC_No: 2841 return 12; 2842 case MDOC_Oo: 2843 return 10; 2844 case MDOC_Op: 2845 return 14; 2846 case MDOC_Pa: 2847 return 32; 2848 case MDOC_Pf: 2849 return 12; 2850 case MDOC_Po: 2851 return 12; 2852 case MDOC_Pq: 2853 return 12; 2854 case MDOC_Ql: 2855 return 16; 2856 case MDOC_Qo: 2857 return 12; 2858 case MDOC_So: 2859 return 12; 2860 case MDOC_Sq: 2861 return 12; 2862 case MDOC_Sy: 2863 return 6; 2864 case MDOC_Sx: 2865 return 16; 2866 case MDOC_Tn: 2867 return 10; 2868 case MDOC_Va: 2869 return 12; 2870 case MDOC_Vt: 2871 return 12; 2872 case MDOC_Xr: 2873 return 10; 2874 default: 2875 break; 2876 }; 2877 return 0; 2878} 2879