1 /* $NetBSD: command.cpp,v 1.1.1.1 2016/01/13 18:41:49 christos Exp $ */ 2 3 // -*- C++ -*- 4 /* Copyright (C) 1989, 1990, 1991, 1992, 2001, 2002, 2004 5 Free Software Foundation, Inc. 6 Written by James Clark (jjc@jclark.com) 7 8 This file is part of groff. 9 10 groff is free software; you can redistribute it and/or modify it under 11 the terms of the GNU General Public License as published by the Free 12 Software Foundation; either version 2, or (at your option) any later 13 version. 14 15 groff is distributed in the hope that it will be useful, but WITHOUT ANY 16 WARRANTY; without even the implied warranty of MERCHANTABILITY or 17 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 18 for more details. 19 20 You should have received a copy of the GNU General Public License along 21 with groff; see the file COPYING. If not, write to the Free Software 22 Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */ 23 24 #include "refer.h" 25 #include "refid.h" 26 #include "search.h" 27 #include "command.h" 28 29 cset cs_field_name = csalpha; 30 31 class input_item { 32 input_item *next; 33 char *filename; 34 int first_lineno; 35 string buffer; 36 const char *ptr; 37 const char *end; 38 public: 39 input_item(string &, const char *, int = 1); 40 ~input_item(); 41 int get_char(); 42 int peek_char(); 43 void skip_char(); 44 int get_location(const char **, int *); 45 46 friend class input_stack; 47 }; 48 49 input_item::input_item(string &s, const char *fn, int ln) 50 : filename(strsave(fn)), first_lineno(ln) 51 { 52 buffer.move(s); 53 ptr = buffer.contents(); 54 end = ptr + buffer.length(); 55 } 56 57 input_item::~input_item() 58 { 59 a_delete filename; 60 } 61 62 inline int input_item::peek_char() 63 { 64 if (ptr >= end) 65 return EOF; 66 else 67 return (unsigned char)*ptr; 68 } 69 70 inline int input_item::get_char() 71 { 72 if (ptr >= end) 73 return EOF; 74 else 75 return (unsigned char)*ptr++; 76 } 77 78 inline void input_item::skip_char() 79 { 80 ptr++; 81 } 82 83 int input_item::get_location(const char **filenamep, int *linenop) 84 { 85 *filenamep = filename; 86 if (ptr == buffer.contents()) 87 *linenop = first_lineno; 88 else { 89 int ln = first_lineno; 90 const char *e = ptr - 1; 91 for (const char *p = buffer.contents(); p < e; p++) 92 if (*p == '\n') 93 ln++; 94 *linenop = ln; 95 } 96 return 1; 97 } 98 99 class input_stack { 100 static input_item *top; 101 public: 102 static void init(); 103 static int get_char(); 104 static int peek_char(); 105 static void skip_char() { top->skip_char(); } 106 static void push_file(const char *); 107 static void push_string(string &, const char *, int); 108 static void error(const char *format, 109 const errarg &arg1 = empty_errarg, 110 const errarg &arg2 = empty_errarg, 111 const errarg &arg3 = empty_errarg); 112 }; 113 114 input_item *input_stack::top = 0; 115 116 void input_stack::init() 117 { 118 while (top) { 119 input_item *tem = top; 120 top = top->next; 121 delete tem; 122 } 123 } 124 125 int input_stack::get_char() 126 { 127 while (top) { 128 int c = top->get_char(); 129 if (c >= 0) 130 return c; 131 input_item *tem = top; 132 top = top->next; 133 delete tem; 134 } 135 return -1; 136 } 137 138 int input_stack::peek_char() 139 { 140 while (top) { 141 int c = top->peek_char(); 142 if (c >= 0) 143 return c; 144 input_item *tem = top; 145 top = top->next; 146 delete tem; 147 } 148 return -1; 149 } 150 151 void input_stack::push_file(const char *fn) 152 { 153 FILE *fp; 154 if (strcmp(fn, "-") == 0) { 155 fp = stdin; 156 fn = "<standard input>"; 157 } 158 else { 159 errno = 0; 160 fp = fopen(fn, "r"); 161 if (fp == 0) { 162 error("can't open `%1': %2", fn, strerror(errno)); 163 return; 164 } 165 } 166 string buf; 167 int bol = 1; 168 int lineno = 1; 169 for (;;) { 170 int c = getc(fp); 171 if (bol && c == '.') { 172 // replace lines beginning with .R1 or .R2 with a blank line 173 c = getc(fp); 174 if (c == 'R') { 175 c = getc(fp); 176 if (c == '1' || c == '2') { 177 int cc = c; 178 c = getc(fp); 179 if (compatible_flag || c == ' ' || c == '\n' || c == EOF) { 180 while (c != '\n' && c != EOF) 181 c = getc(fp); 182 } 183 else { 184 buf += '.'; 185 buf += 'R'; 186 buf += cc; 187 } 188 } 189 else { 190 buf += '.'; 191 buf += 'R'; 192 } 193 } 194 else 195 buf += '.'; 196 } 197 if (c == EOF) 198 break; 199 if (invalid_input_char(c)) 200 error_with_file_and_line(fn, lineno, 201 "invalid input character code %1", int(c)); 202 else { 203 buf += c; 204 if (c == '\n') { 205 bol = 1; 206 lineno++; 207 } 208 else 209 bol = 0; 210 } 211 } 212 if (fp != stdin) 213 fclose(fp); 214 if (buf.length() > 0 && buf[buf.length() - 1] != '\n') 215 buf += '\n'; 216 input_item *it = new input_item(buf, fn); 217 it->next = top; 218 top = it; 219 } 220 221 void input_stack::push_string(string &s, const char *filename, int lineno) 222 { 223 input_item *it = new input_item(s, filename, lineno); 224 it->next = top; 225 top = it; 226 } 227 228 void input_stack::error(const char *format, const errarg &arg1, 229 const errarg &arg2, const errarg &arg3) 230 { 231 const char *filename; 232 int lineno; 233 for (input_item *it = top; it; it = it->next) 234 if (it->get_location(&filename, &lineno)) { 235 error_with_file_and_line(filename, lineno, format, arg1, arg2, arg3); 236 return; 237 } 238 ::error(format, arg1, arg2, arg3); 239 } 240 241 void command_error(const char *format, const errarg &arg1, 242 const errarg &arg2, const errarg &arg3) 243 { 244 input_stack::error(format, arg1, arg2, arg3); 245 } 246 247 // # not recognized in "" 248 // \<newline> is recognized in "" 249 // # does not conceal newline 250 // if missing closing quote, word extends to end of line 251 // no special treatment of \ other than before newline 252 // \<newline> not recognized after # 253 // ; allowed as alternative to newline 254 // ; not recognized in "" 255 // don't clear word_buffer; just append on 256 // return -1 for EOF, 0 for newline, 1 for word 257 258 int get_word(string &word_buffer) 259 { 260 int c = input_stack::get_char(); 261 for (;;) { 262 if (c == '#') { 263 do { 264 c = input_stack::get_char(); 265 } while (c != '\n' && c != EOF); 266 break; 267 } 268 if (c == '\\' && input_stack::peek_char() == '\n') 269 input_stack::skip_char(); 270 else if (c != ' ' && c != '\t') 271 break; 272 c = input_stack::get_char(); 273 } 274 if (c == EOF) 275 return -1; 276 if (c == '\n' || c == ';') 277 return 0; 278 if (c == '"') { 279 for (;;) { 280 c = input_stack::peek_char(); 281 if (c == EOF || c == '\n') 282 break; 283 input_stack::skip_char(); 284 if (c == '"') { 285 int d = input_stack::peek_char(); 286 if (d == '"') 287 input_stack::skip_char(); 288 else 289 break; 290 } 291 else if (c == '\\') { 292 int d = input_stack::peek_char(); 293 if (d == '\n') 294 input_stack::skip_char(); 295 else 296 word_buffer += '\\'; 297 } 298 else 299 word_buffer += c; 300 } 301 return 1; 302 } 303 word_buffer += c; 304 for (;;) { 305 c = input_stack::peek_char(); 306 if (c == ' ' || c == '\t' || c == '\n' || c == '#' || c == ';') 307 break; 308 input_stack::skip_char(); 309 if (c == '\\') { 310 int d = input_stack::peek_char(); 311 if (d == '\n') 312 input_stack::skip_char(); 313 else 314 word_buffer += '\\'; 315 } 316 else 317 word_buffer += c; 318 } 319 return 1; 320 } 321 322 union argument { 323 const char *s; 324 int n; 325 }; 326 327 // This is for debugging. 328 329 static void echo_command(int argc, argument *argv) 330 { 331 for (int i = 0; i < argc; i++) 332 fprintf(stderr, "%s\n", argv[i].s); 333 } 334 335 static void include_command(int argc, argument *argv) 336 { 337 assert(argc == 1); 338 input_stack::push_file(argv[0].s); 339 } 340 341 static void capitalize_command(int argc, argument *argv) 342 { 343 if (argc > 0) 344 capitalize_fields = argv[0].s; 345 else 346 capitalize_fields.clear(); 347 } 348 349 static void accumulate_command(int, argument *) 350 { 351 accumulate = 1; 352 } 353 354 static void no_accumulate_command(int, argument *) 355 { 356 accumulate = 0; 357 } 358 359 static void move_punctuation_command(int, argument *) 360 { 361 move_punctuation = 1; 362 } 363 364 static void no_move_punctuation_command(int, argument *) 365 { 366 move_punctuation = 0; 367 } 368 369 static void sort_command(int argc, argument *argv) 370 { 371 if (argc == 0) 372 sort_fields = "AD"; 373 else 374 sort_fields = argv[0].s; 375 accumulate = 1; 376 } 377 378 static void no_sort_command(int, argument *) 379 { 380 sort_fields.clear(); 381 } 382 383 static void articles_command(int argc, argument *argv) 384 { 385 articles.clear(); 386 int i; 387 for (i = 0; i < argc; i++) { 388 articles += argv[i].s; 389 articles += '\0'; 390 } 391 int len = articles.length(); 392 for (i = 0; i < len; i++) 393 articles[i] = cmlower(articles[i]); 394 } 395 396 static void database_command(int argc, argument *argv) 397 { 398 for (int i = 0; i < argc; i++) 399 database_list.add_file(argv[i].s); 400 } 401 402 static void default_database_command(int, argument *) 403 { 404 search_default = 1; 405 } 406 407 static void no_default_database_command(int, argument *) 408 { 409 search_default = 0; 410 } 411 412 static void bibliography_command(int argc, argument *argv) 413 { 414 const char *saved_filename = current_filename; 415 int saved_lineno = current_lineno; 416 int saved_label_in_text = label_in_text; 417 label_in_text = 0; 418 if (!accumulate) 419 fputs(".]<\n", stdout); 420 for (int i = 0; i < argc; i++) 421 do_bib(argv[i].s); 422 if (accumulate) 423 output_references(); 424 else 425 fputs(".]>\n", stdout); 426 current_filename = saved_filename; 427 current_lineno = saved_lineno; 428 label_in_text = saved_label_in_text; 429 } 430 431 static void annotate_command(int argc, argument *argv) 432 { 433 if (argc > 0) 434 annotation_field = argv[0].s[0]; 435 else 436 annotation_field = 'X'; 437 if (argc == 2) 438 annotation_macro = argv[1].s; 439 else 440 annotation_macro = "AP"; 441 } 442 443 static void no_annotate_command(int, argument *) 444 { 445 annotation_macro.clear(); 446 annotation_field = -1; 447 } 448 449 static void reverse_command(int, argument *argv) 450 { 451 reverse_fields = argv[0].s; 452 } 453 454 static void no_reverse_command(int, argument *) 455 { 456 reverse_fields.clear(); 457 } 458 459 static void abbreviate_command(int argc, argument *argv) 460 { 461 abbreviate_fields = argv[0].s; 462 period_before_initial = argc > 1 ? argv[1].s : ". "; 463 period_before_last_name = argc > 2 ? argv[2].s : ". "; 464 period_before_other = argc > 3 ? argv[3].s : ". "; 465 period_before_hyphen = argc > 4 ? argv[4].s : "."; 466 } 467 468 static void no_abbreviate_command(int, argument *) 469 { 470 abbreviate_fields.clear(); 471 } 472 473 string search_ignore_fields; 474 475 static void search_ignore_command(int argc, argument *argv) 476 { 477 if (argc > 0) 478 search_ignore_fields = argv[0].s; 479 else 480 search_ignore_fields = "XYZ"; 481 search_ignore_fields += '\0'; 482 linear_ignore_fields = search_ignore_fields.contents(); 483 } 484 485 static void no_search_ignore_command(int, argument *) 486 { 487 linear_ignore_fields = ""; 488 } 489 490 static void search_truncate_command(int argc, argument *argv) 491 { 492 if (argc > 0) 493 linear_truncate_len = argv[0].n; 494 else 495 linear_truncate_len = 6; 496 } 497 498 static void no_search_truncate_command(int, argument *) 499 { 500 linear_truncate_len = -1; 501 } 502 503 static void discard_command(int argc, argument *argv) 504 { 505 if (argc == 0) 506 discard_fields = "XYZ"; 507 else 508 discard_fields = argv[0].s; 509 accumulate = 1; 510 } 511 512 static void no_discard_command(int, argument *) 513 { 514 discard_fields.clear(); 515 } 516 517 static void label_command(int, argument *argv) 518 { 519 set_label_spec(argv[0].s); 520 } 521 522 static void abbreviate_label_ranges_command(int argc, argument *argv) 523 { 524 abbreviate_label_ranges = 1; 525 label_range_indicator = argc > 0 ? argv[0].s : "-"; 526 } 527 528 static void no_abbreviate_label_ranges_command(int, argument *) 529 { 530 abbreviate_label_ranges = 0; 531 } 532 533 static void label_in_reference_command(int, argument *) 534 { 535 label_in_reference = 1; 536 } 537 538 static void no_label_in_reference_command(int, argument *) 539 { 540 label_in_reference = 0; 541 } 542 543 static void label_in_text_command(int, argument *) 544 { 545 label_in_text = 1; 546 } 547 548 static void no_label_in_text_command(int, argument *) 549 { 550 label_in_text = 0; 551 } 552 553 static void sort_adjacent_labels_command(int, argument *) 554 { 555 sort_adjacent_labels = 1; 556 } 557 558 static void no_sort_adjacent_labels_command(int, argument *) 559 { 560 sort_adjacent_labels = 0; 561 } 562 563 static void date_as_label_command(int argc, argument *argv) 564 { 565 if (set_date_label_spec(argc > 0 ? argv[0].s : "D%a*")) 566 date_as_label = 1; 567 } 568 569 static void no_date_as_label_command(int, argument *) 570 { 571 date_as_label = 0; 572 } 573 574 static void short_label_command(int, argument *argv) 575 { 576 if (set_short_label_spec(argv[0].s)) 577 short_label_flag = 1; 578 } 579 580 static void no_short_label_command(int, argument *) 581 { 582 short_label_flag = 0; 583 } 584 585 static void compatible_command(int, argument *) 586 { 587 compatible_flag = 1; 588 } 589 590 static void no_compatible_command(int, argument *) 591 { 592 compatible_flag = 0; 593 } 594 595 static void join_authors_command(int argc, argument *argv) 596 { 597 join_authors_exactly_two = argv[0].s; 598 join_authors_default = argc > 1 ? argv[1].s : argv[0].s; 599 join_authors_last_two = argc == 3 ? argv[2].s : argv[0].s; 600 } 601 602 static void bracket_label_command(int, argument *argv) 603 { 604 pre_label = argv[0].s; 605 post_label = argv[1].s; 606 sep_label = argv[2].s; 607 } 608 609 static void separate_label_second_parts_command(int, argument *argv) 610 { 611 separate_label_second_parts = argv[0].s; 612 } 613 614 static void et_al_command(int argc, argument *argv) 615 { 616 et_al = argv[0].s; 617 et_al_min_elide = argv[1].n; 618 if (et_al_min_elide < 1) 619 et_al_min_elide = 1; 620 et_al_min_total = argc >= 3 ? argv[2].n : 0; 621 } 622 623 static void no_et_al_command(int, argument *) 624 { 625 et_al.clear(); 626 et_al_min_elide = 0; 627 } 628 629 typedef void (*command_t)(int, argument *); 630 631 /* arg_types is a string describing the numbers and types of arguments. 632 s means a string, i means an integer, f is a list of fields, F is 633 a single field, 634 ? means that the previous argument is optional, * means that the 635 previous argument can occur any number of times. */ 636 637 struct S { 638 const char *name; 639 command_t func; 640 const char *arg_types; 641 } command_table[] = { 642 { "include", include_command, "s" }, 643 { "echo", echo_command, "s*" }, 644 { "capitalize", capitalize_command, "f?" }, 645 { "accumulate", accumulate_command, "" }, 646 { "no-accumulate", no_accumulate_command, "" }, 647 { "move-punctuation", move_punctuation_command, "" }, 648 { "no-move-punctuation", no_move_punctuation_command, "" }, 649 { "sort", sort_command, "s?" }, 650 { "no-sort", no_sort_command, "" }, 651 { "articles", articles_command, "s*" }, 652 { "database", database_command, "ss*" }, 653 { "default-database", default_database_command, "" }, 654 { "no-default-database", no_default_database_command, "" }, 655 { "bibliography", bibliography_command, "ss*" }, 656 { "annotate", annotate_command, "F?s?" }, 657 { "no-annotate", no_annotate_command, "" }, 658 { "reverse", reverse_command, "s" }, 659 { "no-reverse", no_reverse_command, "" }, 660 { "abbreviate", abbreviate_command, "ss?s?s?s?" }, 661 { "no-abbreviate", no_abbreviate_command, "" }, 662 { "search-ignore", search_ignore_command, "f?" }, 663 { "no-search-ignore", no_search_ignore_command, "" }, 664 { "search-truncate", search_truncate_command, "i?" }, 665 { "no-search-truncate", no_search_truncate_command, "" }, 666 { "discard", discard_command, "f?" }, 667 { "no-discard", no_discard_command, "" }, 668 { "label", label_command, "s" }, 669 { "abbreviate-label-ranges", abbreviate_label_ranges_command, "s?" }, 670 { "no-abbreviate-label-ranges", no_abbreviate_label_ranges_command, "" }, 671 { "label-in-reference", label_in_reference_command, "" }, 672 { "no-label-in-reference", no_label_in_reference_command, "" }, 673 { "label-in-text", label_in_text_command, "" }, 674 { "no-label-in-text", no_label_in_text_command, "" }, 675 { "sort-adjacent-labels", sort_adjacent_labels_command, "" }, 676 { "no-sort-adjacent-labels", no_sort_adjacent_labels_command, "" }, 677 { "date-as-label", date_as_label_command, "s?" }, 678 { "no-date-as-label", no_date_as_label_command, "" }, 679 { "short-label", short_label_command, "s" }, 680 { "no-short-label", no_short_label_command, "" }, 681 { "compatible", compatible_command, "" }, 682 { "no-compatible", no_compatible_command, "" }, 683 { "join-authors", join_authors_command, "sss?" }, 684 { "bracket-label", bracket_label_command, "sss" }, 685 { "separate-label-second-parts", separate_label_second_parts_command, "s" }, 686 { "et-al", et_al_command, "sii?" }, 687 { "no-et-al", no_et_al_command, "" }, 688 }; 689 690 static int check_args(const char *types, const char *name, 691 int argc, argument *argv) 692 { 693 int argno = 0; 694 while (*types) { 695 if (argc == 0) { 696 if (types[1] == '?') 697 break; 698 else if (types[1] == '*') { 699 assert(types[2] == '\0'); 700 break; 701 } 702 else { 703 input_stack::error("missing argument for command `%1'", name); 704 return 0; 705 } 706 } 707 switch (*types) { 708 case 's': 709 break; 710 case 'i': 711 { 712 char *ptr; 713 long n = strtol(argv->s, &ptr, 10); 714 if ((n == 0 && ptr == argv->s) 715 || *ptr != '\0') { 716 input_stack::error("argument %1 for command `%2' must be an integer", 717 argno + 1, name); 718 return 0; 719 } 720 argv->n = (int)n; 721 break; 722 } 723 case 'f': 724 { 725 for (const char *ptr = argv->s; *ptr != '\0'; ptr++) 726 if (!cs_field_name(*ptr)) { 727 input_stack::error("argument %1 for command `%2' must be a list of fields", 728 argno + 1, name); 729 return 0; 730 } 731 break; 732 } 733 case 'F': 734 if (argv->s[0] == '\0' || argv->s[1] != '\0' 735 || !cs_field_name(argv->s[0])) { 736 input_stack::error("argument %1 for command `%2' must be a field name", 737 argno + 1, name); 738 return 0; 739 } 740 break; 741 default: 742 assert(0); 743 } 744 if (types[1] == '?') 745 types += 2; 746 else if (types[1] != '*') 747 types += 1; 748 --argc; 749 ++argv; 750 ++argno; 751 } 752 if (argc > 0) { 753 input_stack::error("too many arguments for command `%1'", name); 754 return 0; 755 } 756 return 1; 757 } 758 759 static void execute_command(const char *name, int argc, argument *argv) 760 { 761 for (unsigned int i = 0; 762 i < sizeof(command_table)/sizeof(command_table[0]); i++) 763 if (strcmp(name, command_table[i].name) == 0) { 764 if (check_args(command_table[i].arg_types, name, argc, argv)) 765 (*command_table[i].func)(argc, argv); 766 return; 767 } 768 input_stack::error("unknown command `%1'", name); 769 } 770 771 static void command_loop() 772 { 773 string command; 774 for (;;) { 775 command.clear(); 776 int res = get_word(command); 777 if (res != 1) { 778 if (res == 0) 779 continue; 780 break; 781 } 782 int argc = 0; 783 command += '\0'; 784 while ((res = get_word(command)) == 1) { 785 argc++; 786 command += '\0'; 787 } 788 argument *argv = new argument[argc]; 789 const char *ptr = command.contents(); 790 for (int i = 0; i < argc; i++) 791 argv[i].s = ptr = strchr(ptr, '\0') + 1; 792 execute_command(command.contents(), argc, argv); 793 a_delete argv; 794 if (res == -1) 795 break; 796 } 797 } 798 799 void process_commands(const char *file) 800 { 801 input_stack::init(); 802 input_stack::push_file(file); 803 command_loop(); 804 } 805 806 void process_commands(string &s, const char *file, int lineno) 807 { 808 input_stack::init(); 809 input_stack::push_string(s, file, lineno); 810 command_loop(); 811 } 812