1// -*- C++ -*- 2/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003, 2004, 2005 3 Free Software Foundation, Inc. 4 Written by James Clark (jjc@jclark.com) 5 6This file is part of groff. 7 8groff is free software; you can redistribute it and/or modify it under 9the terms of the GNU General Public License as published by the Free 10Software Foundation; either version 2, or (at your option) any later 11version. 12 13groff is distributed in the hope that it will be useful, but WITHOUT ANY 14WARRANTY; without even the implied warranty of MERCHANTABILITY or 15FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 16for more details. 17 18You should have received a copy of the GNU General Public License along 19with groff; see the file COPYING. If not, write to the Free Software 20Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */ 21 22#include "table.h" 23 24#define MAX_POINT_SIZE 99 25#define MAX_VERTICAL_SPACING 72 26 27extern "C" const char *Version_string; 28 29int compatible_flag = 0; 30 31class table_input { 32 FILE *fp; 33 enum { START, MIDDLE, 34 REREAD_T, REREAD_TE, REREAD_E, 35 LEADER_1, LEADER_2, LEADER_3, LEADER_4, 36 END, ERROR } state; 37 string unget_stack; 38public: 39 table_input(FILE *); 40 int get(); 41 int ended() { return unget_stack.empty() && state == END; } 42 void unget(char); 43}; 44 45table_input::table_input(FILE *p) 46: fp(p), state(START) 47{ 48} 49 50void table_input::unget(char c) 51{ 52 assert(c != '\0'); 53 unget_stack += c; 54 if (c == '\n') 55 current_lineno--; 56} 57 58int table_input::get() 59{ 60 int len = unget_stack.length(); 61 if (len != 0) { 62 unsigned char c = unget_stack[len - 1]; 63 unget_stack.set_length(len - 1); 64 if (c == '\n') 65 current_lineno++; 66 return c; 67 } 68 int c; 69 for (;;) { 70 switch (state) { 71 case START: 72 if ((c = getc(fp)) == '.') { 73 if ((c = getc(fp)) == 'T') { 74 if ((c = getc(fp)) == 'E') { 75 if (compatible_flag) { 76 state = END; 77 return EOF; 78 } 79 else { 80 c = getc(fp); 81 if (c != EOF) 82 ungetc(c, fp); 83 if (c == EOF || c == ' ' || c == '\n') { 84 state = END; 85 return EOF; 86 } 87 state = REREAD_TE; 88 return '.'; 89 } 90 } 91 else { 92 if (c != EOF) 93 ungetc(c, fp); 94 state = REREAD_T; 95 return '.'; 96 } 97 } 98 else { 99 if (c != EOF) 100 ungetc(c, fp); 101 state = MIDDLE; 102 return '.'; 103 } 104 } 105 else if (c == EOF) { 106 state = ERROR; 107 return EOF; 108 } 109 else { 110 if (c == '\n') 111 current_lineno++; 112 else { 113 state = MIDDLE; 114 if (c == '\0') { 115 error("invalid input character code 0"); 116 break; 117 } 118 } 119 return c; 120 } 121 break; 122 case MIDDLE: 123 // handle line continuation and uninterpreted leader character 124 if ((c = getc(fp)) == '\\') { 125 c = getc(fp); 126 if (c == '\n') 127 c = getc(fp); // perhaps state ought to be START now 128 else if (c == 'a' && compatible_flag) { 129 state = LEADER_1; 130 return '\\'; 131 } 132 else { 133 if (c != EOF) 134 ungetc(c, fp); 135 c = '\\'; 136 } 137 } 138 if (c == EOF) { 139 state = ERROR; 140 return EOF; 141 } 142 else { 143 if (c == '\n') { 144 state = START; 145 current_lineno++; 146 } 147 else if (c == '\0') { 148 error("invalid input character code 0"); 149 break; 150 } 151 return c; 152 } 153 case REREAD_T: 154 state = MIDDLE; 155 return 'T'; 156 case REREAD_TE: 157 state = REREAD_E; 158 return 'T'; 159 case REREAD_E: 160 state = MIDDLE; 161 return 'E'; 162 case LEADER_1: 163 state = LEADER_2; 164 return '*'; 165 case LEADER_2: 166 state = LEADER_3; 167 return '('; 168 case LEADER_3: 169 state = LEADER_4; 170 return PREFIX_CHAR; 171 case LEADER_4: 172 state = MIDDLE; 173 return LEADER_CHAR; 174 case END: 175 case ERROR: 176 return EOF; 177 } 178 } 179} 180 181void process_input_file(FILE *); 182void process_table(table_input &in); 183 184void process_input_file(FILE *fp) 185{ 186 enum { START, MIDDLE, HAD_DOT, HAD_T, HAD_TS, HAD_l, HAD_lf } state; 187 state = START; 188 int c; 189 while ((c = getc(fp)) != EOF) 190 switch (state) { 191 case START: 192 if (c == '.') 193 state = HAD_DOT; 194 else { 195 if (c == '\n') 196 current_lineno++; 197 else 198 state = MIDDLE; 199 putchar(c); 200 } 201 break; 202 case MIDDLE: 203 if (c == '\n') { 204 current_lineno++; 205 state = START; 206 } 207 putchar(c); 208 break; 209 case HAD_DOT: 210 if (c == 'T') 211 state = HAD_T; 212 else if (c == 'l') 213 state = HAD_l; 214 else { 215 putchar('.'); 216 putchar(c); 217 if (c == '\n') { 218 current_lineno++; 219 state = START; 220 } 221 else 222 state = MIDDLE; 223 } 224 break; 225 case HAD_T: 226 if (c == 'S') 227 state = HAD_TS; 228 else { 229 putchar('.'); 230 putchar('T'); 231 putchar(c); 232 if (c == '\n') { 233 current_lineno++; 234 state = START; 235 } 236 else 237 state = MIDDLE; 238 } 239 break; 240 case HAD_TS: 241 if (c == ' ' || c == '\n' || compatible_flag) { 242 putchar('.'); 243 putchar('T'); 244 putchar('S'); 245 while (c != '\n') { 246 if (c == EOF) { 247 error("end of file at beginning of table"); 248 return; 249 } 250 putchar(c); 251 c = getc(fp); 252 } 253 putchar('\n'); 254 current_lineno++; 255 { 256 table_input input(fp); 257 process_table(input); 258 set_troff_location(current_filename, current_lineno); 259 if (input.ended()) { 260 fputs(".TE", stdout); 261 while ((c = getc(fp)) != '\n') { 262 if (c == EOF) { 263 putchar('\n'); 264 return; 265 } 266 putchar(c); 267 } 268 putchar('\n'); 269 current_lineno++; 270 } 271 } 272 state = START; 273 } 274 else { 275 fputs(".TS", stdout); 276 putchar(c); 277 state = MIDDLE; 278 } 279 break; 280 case HAD_l: 281 if (c == 'f') 282 state = HAD_lf; 283 else { 284 putchar('.'); 285 putchar('l'); 286 putchar(c); 287 if (c == '\n') { 288 current_lineno++; 289 state = START; 290 } 291 else 292 state = MIDDLE; 293 } 294 break; 295 case HAD_lf: 296 if (c == ' ' || c == '\n' || compatible_flag) { 297 string line; 298 while (c != EOF) { 299 line += c; 300 if (c == '\n') { 301 current_lineno++; 302 break; 303 } 304 c = getc(fp); 305 } 306 line += '\0'; 307 interpret_lf_args(line.contents()); 308 printf(".lf%s", line.contents()); 309 state = START; 310 } 311 else { 312 fputs(".lf", stdout); 313 putchar(c); 314 state = MIDDLE; 315 } 316 break; 317 default: 318 assert(0); 319 } 320 switch(state) { 321 case START: 322 break; 323 case MIDDLE: 324 putchar('\n'); 325 break; 326 case HAD_DOT: 327 fputs(".\n", stdout); 328 break; 329 case HAD_l: 330 fputs(".l\n", stdout); 331 break; 332 case HAD_T: 333 fputs(".T\n", stdout); 334 break; 335 case HAD_lf: 336 fputs(".lf\n", stdout); 337 break; 338 case HAD_TS: 339 fputs(".TS\n", stdout); 340 break; 341 } 342 if (fp != stdin) 343 fclose(fp); 344} 345 346struct options { 347 unsigned flags; 348 int linesize; 349 char delim[2]; 350 char tab_char; 351 char decimal_point_char; 352 353 options(); 354}; 355 356options::options() 357: flags(0), linesize(0), tab_char('\t'), decimal_point_char('.') 358{ 359 delim[0] = delim[1] = '\0'; 360} 361 362// Return non-zero if p and q are the same ignoring case. 363 364int strieq(const char *p, const char *q) 365{ 366 for (; cmlower(*p) == cmlower(*q); p++, q++) 367 if (*p == '\0') 368 return 1; 369 return 0; 370} 371 372// return 0 if we should give up in this table 373 374options *process_options(table_input &in) 375{ 376 options *opt = new options; 377 string line; 378 int level = 0; 379 for (;;) { 380 int c = in.get(); 381 if (c == EOF) { 382 int i = line.length(); 383 while (--i >= 0) 384 in.unget(line[i]); 385 return opt; 386 } 387 if (c == '\n') { 388 in.unget(c); 389 int i = line.length(); 390 while (--i >= 0) 391 in.unget(line[i]); 392 return opt; 393 } 394 else if (c == '(') 395 level++; 396 else if (c == ')') 397 level--; 398 else if (c == ';' && level == 0) { 399 line += '\0'; 400 break; 401 } 402 line += c; 403 } 404 if (line.empty()) 405 return opt; 406 char *p = &line[0]; 407 for (;;) { 408 while (!csalpha(*p) && *p != '\0') 409 p++; 410 if (*p == '\0') 411 break; 412 char *q = p; 413 while (csalpha(*q)) 414 q++; 415 char *arg = 0; 416 if (*q != '(' && *q != '\0') 417 *q++ = '\0'; 418 while (csspace(*q)) 419 q++; 420 if (*q == '(') { 421 *q++ = '\0'; 422 arg = q; 423 while (*q != ')' && *q != '\0') 424 q++; 425 if (*q == '\0') 426 error("missing `)'"); 427 else 428 *q++ = '\0'; 429 } 430 if (*p == '\0') { 431 if (arg) 432 error("argument without option"); 433 } 434 else if (strieq(p, "tab")) { 435 if (!arg) 436 error("`tab' option requires argument in parentheses"); 437 else { 438 if (arg[0] == '\0' || arg[1] != '\0') 439 error("argument to `tab' option must be a single character"); 440 else 441 opt->tab_char = arg[0]; 442 } 443 } 444 else if (strieq(p, "linesize")) { 445 if (!arg) 446 error("`linesize' option requires argument in parentheses"); 447 else { 448 if (sscanf(arg, "%d", &opt->linesize) != 1) 449 error("bad linesize `%s'", arg); 450 else if (opt->linesize <= 0) { 451 error("linesize must be positive"); 452 opt->linesize = 0; 453 } 454 } 455 } 456 else if (strieq(p, "delim")) { 457 if (!arg) 458 error("`delim' option requires argument in parentheses"); 459 else if (arg[0] == '\0' || arg[1] == '\0' || arg[2] != '\0') 460 error("argument to `delim' option must be two characters"); 461 else { 462 opt->delim[0] = arg[0]; 463 opt->delim[1] = arg[1]; 464 } 465 } 466 else if (strieq(p, "center") || strieq(p, "centre")) { 467 if (arg) 468 error("`center' option does not take an argument"); 469 opt->flags |= table::CENTER; 470 } 471 else if (strieq(p, "expand")) { 472 if (arg) 473 error("`expand' option does not take an argument"); 474 opt->flags |= table::EXPAND; 475 } 476 else if (strieq(p, "box") || strieq(p, "frame")) { 477 if (arg) 478 error("`box' option does not take an argument"); 479 opt->flags |= table::BOX; 480 } 481 else if (strieq(p, "doublebox") || strieq(p, "doubleframe")) { 482 if (arg) 483 error("`doublebox' option does not take an argument"); 484 opt->flags |= table::DOUBLEBOX; 485 } 486 else if (strieq(p, "allbox")) { 487 if (arg) 488 error("`allbox' option does not take an argument"); 489 opt->flags |= table::ALLBOX; 490 } 491 else if (strieq(p, "nokeep")) { 492 if (arg) 493 error("`nokeep' option does not take an argument"); 494 opt->flags |= table::NOKEEP; 495 } 496 else if (strieq(p, "nospaces")) { 497 if (arg) 498 error("`nospaces' option does not take an argument"); 499 opt->flags |= table::NOSPACES; 500 } 501 else if (strieq(p, "decimalpoint")) { 502 if (!arg) 503 error("`decimalpoint' option requires argument in parentheses"); 504 else { 505 if (arg[0] == '\0' || arg[1] != '\0') 506 error("argument to `decimalpoint' option must be a single character"); 507 else 508 opt->decimal_point_char = arg[0]; 509 } 510 } 511 else { 512 error("unrecognised global option `%1'", p); 513 // delete opt; 514 // return 0; 515 } 516 p = q; 517 } 518 return opt; 519} 520 521entry_modifier::entry_modifier() 522: vertical_alignment(CENTER), zero_width(0), stagger(0) 523{ 524 vertical_spacing.inc = vertical_spacing.val = 0; 525 point_size.inc = point_size.val = 0; 526} 527 528entry_modifier::~entry_modifier() 529{ 530} 531 532entry_format::entry_format() : type(FORMAT_LEFT) 533{ 534} 535 536entry_format::entry_format(format_type t) : type(t) 537{ 538} 539 540void entry_format::debug_print() const 541{ 542 switch (type) { 543 case FORMAT_LEFT: 544 putc('l', stderr); 545 break; 546 case FORMAT_CENTER: 547 putc('c', stderr); 548 break; 549 case FORMAT_RIGHT: 550 putc('r', stderr); 551 break; 552 case FORMAT_NUMERIC: 553 putc('n', stderr); 554 break; 555 case FORMAT_ALPHABETIC: 556 putc('a', stderr); 557 break; 558 case FORMAT_SPAN: 559 putc('s', stderr); 560 break; 561 case FORMAT_VSPAN: 562 putc('^', stderr); 563 break; 564 case FORMAT_HLINE: 565 putc('_', stderr); 566 break; 567 case FORMAT_DOUBLE_HLINE: 568 putc('=', stderr); 569 break; 570 default: 571 assert(0); 572 break; 573 } 574 if (point_size.val != 0) { 575 putc('p', stderr); 576 if (point_size.inc > 0) 577 putc('+', stderr); 578 else if (point_size.inc < 0) 579 putc('-', stderr); 580 fprintf(stderr, "%d ", point_size.val); 581 } 582 if (vertical_spacing.val != 0) { 583 putc('v', stderr); 584 if (vertical_spacing.inc > 0) 585 putc('+', stderr); 586 else if (vertical_spacing.inc < 0) 587 putc('-', stderr); 588 fprintf(stderr, "%d ", vertical_spacing.val); 589 } 590 if (!font.empty()) { 591 putc('f', stderr); 592 put_string(font, stderr); 593 putc(' ', stderr); 594 } 595 if (!macro.empty()) { 596 putc('m', stderr); 597 put_string(macro, stderr); 598 putc(' ', stderr); 599 } 600 switch (vertical_alignment) { 601 case entry_modifier::CENTER: 602 break; 603 case entry_modifier::TOP: 604 putc('t', stderr); 605 break; 606 case entry_modifier::BOTTOM: 607 putc('d', stderr); 608 break; 609 } 610 if (zero_width) 611 putc('z', stderr); 612 if (stagger) 613 putc('u', stderr); 614} 615 616struct format { 617 int nrows; 618 int ncolumns; 619 int *separation; 620 string *width; 621 char *equal; 622 entry_format **entry; 623 char **vline; 624 625 format(int nr, int nc); 626 ~format(); 627 void add_rows(int n); 628}; 629 630format::format(int nr, int nc) : nrows(nr), ncolumns(nc) 631{ 632 int i; 633 separation = ncolumns > 1 ? new int[ncolumns - 1] : 0; 634 for (i = 0; i < ncolumns-1; i++) 635 separation[i] = -1; 636 width = new string[ncolumns]; 637 equal = new char[ncolumns]; 638 for (i = 0; i < ncolumns; i++) 639 equal[i] = 0; 640 entry = new entry_format *[nrows]; 641 for (i = 0; i < nrows; i++) 642 entry[i] = new entry_format[ncolumns]; 643 vline = new char*[nrows]; 644 for (i = 0; i < nrows; i++) { 645 vline[i] = new char[ncolumns+1]; 646 for (int j = 0; j < ncolumns+1; j++) 647 vline[i][j] = 0; 648 } 649} 650 651void format::add_rows(int n) 652{ 653 int i; 654 char **old_vline = vline; 655 vline = new char*[nrows + n]; 656 for (i = 0; i < nrows; i++) 657 vline[i] = old_vline[i]; 658 a_delete old_vline; 659 for (i = 0; i < n; i++) { 660 vline[nrows + i] = new char[ncolumns + 1]; 661 for (int j = 0; j < ncolumns + 1; j++) 662 vline[nrows + i][j] = 0; 663 } 664 entry_format **old_entry = entry; 665 entry = new entry_format *[nrows + n]; 666 for (i = 0; i < nrows; i++) 667 entry[i] = old_entry[i]; 668 a_delete old_entry; 669 for (i = 0; i < n; i++) 670 entry[nrows + i] = new entry_format[ncolumns]; 671 nrows += n; 672} 673 674format::~format() 675{ 676 a_delete separation; 677 ad_delete(ncolumns) width; 678 a_delete equal; 679 for (int i = 0; i < nrows; i++) { 680 a_delete vline[i]; 681 ad_delete(ncolumns) entry[i]; 682 } 683 a_delete vline; 684 a_delete entry; 685} 686 687struct input_entry_format : public entry_format { 688 input_entry_format *next; 689 string width; 690 int separation; 691 int vline; 692 int pre_vline; 693 int last_column; 694 int equal; 695 input_entry_format(format_type, input_entry_format * = 0); 696 ~input_entry_format(); 697 void debug_print(); 698}; 699 700input_entry_format::input_entry_format(format_type t, input_entry_format *p) 701: entry_format(t), next(p) 702{ 703 separation = -1; 704 last_column = 0; 705 vline = 0; 706 pre_vline = 0; 707 equal = 0; 708} 709 710input_entry_format::~input_entry_format() 711{ 712} 713 714void free_input_entry_format_list(input_entry_format *list) 715{ 716 while (list) { 717 input_entry_format *tem = list; 718 list = list->next; 719 delete tem; 720 } 721} 722 723void input_entry_format::debug_print() 724{ 725 int i; 726 for (i = 0; i < pre_vline; i++) 727 putc('|', stderr); 728 entry_format::debug_print(); 729 if (!width.empty()) { 730 putc('w', stderr); 731 putc('(', stderr); 732 put_string(width, stderr); 733 putc(')', stderr); 734 } 735 if (equal) 736 putc('e', stderr); 737 if (separation >= 0) 738 fprintf(stderr, "%d", separation); 739 for (i = 0; i < vline; i++) 740 putc('|', stderr); 741 if (last_column) 742 putc(',', stderr); 743} 744 745// Return zero if we should give up on this table. 746// If this is a continuation format line, current_format will be the current 747// format line. 748 749format *process_format(table_input &in, options *opt, 750 format *current_format = 0) 751{ 752 input_entry_format *list = 0; 753 int c = in.get(); 754 for (;;) { 755 int pre_vline = 0; 756 int got_format = 0; 757 int got_period = 0; 758 format_type t = FORMAT_LEFT; 759 for (;;) { 760 if (c == EOF) { 761 error("end of input while processing format"); 762 free_input_entry_format_list(list); 763 return 0; 764 } 765 switch (c) { 766 case 'n': 767 case 'N': 768 t = FORMAT_NUMERIC; 769 got_format = 1; 770 break; 771 case 'a': 772 case 'A': 773 got_format = 1; 774 t = FORMAT_ALPHABETIC; 775 break; 776 case 'c': 777 case 'C': 778 got_format = 1; 779 t = FORMAT_CENTER; 780 break; 781 case 'l': 782 case 'L': 783 got_format = 1; 784 t = FORMAT_LEFT; 785 break; 786 case 'r': 787 case 'R': 788 got_format = 1; 789 t = FORMAT_RIGHT; 790 break; 791 case 's': 792 case 'S': 793 got_format = 1; 794 t = FORMAT_SPAN; 795 break; 796 case '^': 797 got_format = 1; 798 t = FORMAT_VSPAN; 799 break; 800 case '_': 801 case '-': // tbl also accepts this 802 got_format = 1; 803 t = FORMAT_HLINE; 804 break; 805 case '=': 806 got_format = 1; 807 t = FORMAT_DOUBLE_HLINE; 808 break; 809 case '.': 810 got_period = 1; 811 break; 812 case '|': 813 pre_vline++; 814 break; 815 case ' ': 816 case '\t': 817 case '\n': 818 break; 819 default: 820 if (c == opt->tab_char) 821 break; 822 error("unrecognised format `%1'", char(c)); 823 free_input_entry_format_list(list); 824 return 0; 825 } 826 if (got_period) 827 break; 828 c = in.get(); 829 if (got_format) 830 break; 831 } 832 if (got_period) 833 break; 834 list = new input_entry_format(t, list); 835 if (pre_vline) 836 list->pre_vline = pre_vline; 837 int success = 1; 838 do { 839 switch (c) { 840 case 't': 841 case 'T': 842 c = in.get(); 843 list->vertical_alignment = entry_modifier::TOP; 844 break; 845 case 'd': 846 case 'D': 847 c = in.get(); 848 list->vertical_alignment = entry_modifier::BOTTOM; 849 break; 850 case 'u': 851 case 'U': 852 c = in.get(); 853 list->stagger = 1; 854 break; 855 case 'z': 856 case 'Z': 857 c = in.get(); 858 list->zero_width = 1; 859 break; 860 case '0': 861 case '1': 862 case '2': 863 case '3': 864 case '4': 865 case '5': 866 case '6': 867 case '7': 868 case '8': 869 case '9': 870 { 871 int w = 0; 872 do { 873 w = w*10 + (c - '0'); 874 c = in.get(); 875 } while (c != EOF && csdigit(c)); 876 list->separation = w; 877 } 878 break; 879 case 'f': 880 case 'F': 881 do { 882 c = in.get(); 883 } while (c == ' ' || c == '\t'); 884 if (c == EOF) { 885 error("missing font name"); 886 break; 887 } 888 if (c == '(') { 889 for (;;) { 890 c = in.get(); 891 if (c == EOF || c == ' ' || c == '\t') { 892 error("missing `)'"); 893 break; 894 } 895 if (c == ')') { 896 c = in.get(); 897 break; 898 } 899 list->font += char(c); 900 } 901 } 902 else { 903 list->font = c; 904 char cc = c; 905 c = in.get(); 906 if (!csdigit(cc) 907 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') { 908 list->font += char(c); 909 c = in.get(); 910 } 911 } 912 break; 913 case 'x': 914 case 'X': 915 do { 916 c = in.get(); 917 } while (c == ' ' || c == '\t'); 918 if (c == EOF) { 919 error("missing macro name"); 920 break; 921 } 922 if (c == '(') { 923 for (;;) { 924 c = in.get(); 925 if (c == EOF || c == ' ' || c == '\t') { 926 error("missing `)'"); 927 break; 928 } 929 if (c == ')') { 930 c = in.get(); 931 break; 932 } 933 list->macro += char(c); 934 } 935 } 936 else { 937 list->macro = c; 938 char cc = c; 939 c = in.get(); 940 if (!csdigit(cc) 941 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') { 942 list->macro += char(c); 943 c = in.get(); 944 } 945 } 946 break; 947 case 'v': 948 case 'V': 949 c = in.get(); 950 list->vertical_spacing.val = 0; 951 list->vertical_spacing.inc = 0; 952 if (c == '+' || c == '-') { 953 list->vertical_spacing.inc = (c == '+' ? 1 : -1); 954 c = in.get(); 955 } 956 if (c == EOF || !csdigit(c)) { 957 error("`v' modifier must be followed by number"); 958 list->vertical_spacing.inc = 0; 959 } 960 else { 961 do { 962 list->vertical_spacing.val *= 10; 963 list->vertical_spacing.val += c - '0'; 964 c = in.get(); 965 } while (c != EOF && csdigit(c)); 966 } 967 if (list->vertical_spacing.val > MAX_VERTICAL_SPACING 968 || list->vertical_spacing.val < -MAX_VERTICAL_SPACING) { 969 error("unreasonable vertical spacing"); 970 list->vertical_spacing.val = 0; 971 list->vertical_spacing.inc = 0; 972 } 973 break; 974 case 'p': 975 case 'P': 976 c = in.get(); 977 list->point_size.val = 0; 978 list->point_size.inc = 0; 979 if (c == '+' || c == '-') { 980 list->point_size.inc = (c == '+' ? 1 : -1); 981 c = in.get(); 982 } 983 if (c == EOF || !csdigit(c)) { 984 error("`p' modifier must be followed by number"); 985 list->point_size.inc = 0; 986 } 987 else { 988 do { 989 list->point_size.val *= 10; 990 list->point_size.val += c - '0'; 991 c = in.get(); 992 } while (c != EOF && csdigit(c)); 993 } 994 if (list->point_size.val > MAX_POINT_SIZE 995 || list->point_size.val < -MAX_POINT_SIZE) { 996 error("unreasonable point size"); 997 list->point_size.val = 0; 998 list->point_size.inc = 0; 999 } 1000 break; 1001 case 'w': 1002 case 'W': 1003 c = in.get(); 1004 while (c == ' ' || c == '\t') 1005 c = in.get(); 1006 if (c == '(') { 1007 list->width = ""; 1008 c = in.get(); 1009 while (c != ')') { 1010 if (c == EOF || c == '\n') { 1011 error("missing `)'"); 1012 free_input_entry_format_list(list); 1013 return 0; 1014 } 1015 list->width += c; 1016 c = in.get(); 1017 } 1018 c = in.get(); 1019 } 1020 else { 1021 if (c == '+' || c == '-') { 1022 list->width = char(c); 1023 c = in.get(); 1024 } 1025 else 1026 list->width = ""; 1027 if (c == EOF || !csdigit(c)) 1028 error("bad argument for `w' modifier"); 1029 else { 1030 do { 1031 list->width += char(c); 1032 c = in.get(); 1033 } while (c != EOF && csdigit(c)); 1034 } 1035 } 1036 break; 1037 case 'e': 1038 case 'E': 1039 c = in.get(); 1040 list->equal++; 1041 break; 1042 case '|': 1043 c = in.get(); 1044 list->vline++; 1045 break; 1046 case 'B': 1047 case 'b': 1048 c = in.get(); 1049 list->font = "B"; 1050 break; 1051 case 'I': 1052 case 'i': 1053 c = in.get(); 1054 list->font = "I"; 1055 break; 1056 case ' ': 1057 case '\t': 1058 c = in.get(); 1059 break; 1060 default: 1061 if (c == opt->tab_char) 1062 c = in.get(); 1063 else 1064 success = 0; 1065 break; 1066 } 1067 } while (success); 1068 if (list->vline > 2) { 1069 list->vline = 2; 1070 error("more than 2 vertical bars between key letters"); 1071 } 1072 if (c == '\n' || c == ',') { 1073 c = in.get(); 1074 list->last_column = 1; 1075 } 1076 } 1077 if (c == '.') { 1078 do { 1079 c = in.get(); 1080 } while (c == ' ' || c == '\t'); 1081 if (c != '\n') { 1082 error("`.' not last character on line"); 1083 free_input_entry_format_list(list); 1084 return 0; 1085 } 1086 } 1087 if (!list) { 1088 error("no format"); 1089 free_input_entry_format_list(list); 1090 return 0; 1091 } 1092 list->last_column = 1; 1093 // now reverse the list so that the first row is at the beginning 1094 input_entry_format *rev = 0; 1095 while (list != 0) { 1096 input_entry_format *tem = list->next; 1097 list->next = rev; 1098 rev = list; 1099 list = tem; 1100 } 1101 list = rev; 1102 input_entry_format *tem; 1103 1104#if 0 1105 for (tem = list; tem; tem = tem->next) 1106 tem->debug_print(); 1107 putc('\n', stderr); 1108#endif 1109 // compute number of columns and rows 1110 int ncolumns = 0; 1111 int nrows = 0; 1112 int col = 0; 1113 for (tem = list; tem; tem = tem->next) { 1114 if (tem->last_column) { 1115 if (col >= ncolumns) 1116 ncolumns = col + 1; 1117 col = 0; 1118 nrows++; 1119 } 1120 else 1121 col++; 1122 } 1123 int row; 1124 format *f; 1125 if (current_format) { 1126 if (ncolumns > current_format->ncolumns) { 1127 error("cannot increase the number of columns in a continued format"); 1128 free_input_entry_format_list(list); 1129 return 0; 1130 } 1131 f = current_format; 1132 row = f->nrows; 1133 f->add_rows(nrows); 1134 } 1135 else { 1136 f = new format(nrows, ncolumns); 1137 row = 0; 1138 } 1139 col = 0; 1140 for (tem = list; tem; tem = tem->next) { 1141 f->entry[row][col] = *tem; 1142 if (col < ncolumns-1) { 1143 // use the greatest separation 1144 if (tem->separation > f->separation[col]) { 1145 if (current_format) 1146 error("cannot change column separation in continued format"); 1147 else 1148 f->separation[col] = tem->separation; 1149 } 1150 } 1151 else if (tem->separation >= 0) 1152 error("column separation specified for last column"); 1153 if (tem->equal && !f->equal[col]) { 1154 if (current_format) 1155 error("cannot change which columns are equal in continued format"); 1156 else 1157 f->equal[col] = 1; 1158 } 1159 if (!tem->width.empty()) { 1160 // use the last width 1161 if (!f->width[col].empty() && f->width[col] != tem->width) 1162 error("multiple widths for column %1", col+1); 1163 f->width[col] = tem->width; 1164 } 1165 if (tem->pre_vline) { 1166 assert(col == 0); 1167 f->vline[row][col] = tem->pre_vline; 1168 } 1169 f->vline[row][col+1] = tem->vline; 1170 if (tem->last_column) { 1171 row++; 1172 col = 0; 1173 } 1174 else 1175 col++; 1176 } 1177 free_input_entry_format_list(list); 1178 for (col = 0; col < ncolumns; col++) { 1179 entry_format *e = f->entry[f->nrows-1] + col; 1180 if (e->type != FORMAT_HLINE 1181 && e->type != FORMAT_DOUBLE_HLINE 1182 && e->type != FORMAT_SPAN) 1183 break; 1184 } 1185 if (col >= ncolumns) { 1186 error("last row of format is all lines"); 1187 delete f; 1188 return 0; 1189 } 1190 return f; 1191} 1192 1193table *process_data(table_input &in, format *f, options *opt) 1194{ 1195 char tab_char = opt->tab_char; 1196 int ncolumns = f->ncolumns; 1197 int current_row = 0; 1198 int format_index = 0; 1199 int give_up = 0; 1200 enum { DATA_INPUT_LINE, TROFF_INPUT_LINE, SINGLE_HLINE, DOUBLE_HLINE } type; 1201 table *tbl = new table(ncolumns, opt->flags, opt->linesize, 1202 opt->decimal_point_char); 1203 if (opt->delim[0] != '\0') 1204 tbl->set_delim(opt->delim[0], opt->delim[1]); 1205 for (;;) { 1206 // first determine what type of line this is 1207 int c = in.get(); 1208 if (c == EOF) 1209 break; 1210 if (c == '.') { 1211 int d = in.get(); 1212 if (d != EOF && csdigit(d)) { 1213 in.unget(d); 1214 type = DATA_INPUT_LINE; 1215 } 1216 else { 1217 in.unget(d); 1218 type = TROFF_INPUT_LINE; 1219 } 1220 } 1221 else if (c == '_' || c == '=') { 1222 int d = in.get(); 1223 if (d == '\n') { 1224 if (c == '_') 1225 type = SINGLE_HLINE; 1226 else 1227 type = DOUBLE_HLINE; 1228 } 1229 else { 1230 in.unget(d); 1231 type = DATA_INPUT_LINE; 1232 } 1233 } 1234 else { 1235 type = DATA_INPUT_LINE; 1236 } 1237 switch (type) { 1238 case DATA_INPUT_LINE: 1239 { 1240 string input_entry; 1241 if (format_index >= f->nrows) 1242 format_index = f->nrows - 1; 1243 // A format row that is all lines doesn't use up a data line. 1244 while (format_index < f->nrows - 1) { 1245 int cnt; 1246 for (cnt = 0; cnt < ncolumns; cnt++) { 1247 entry_format *e = f->entry[format_index] + cnt; 1248 if (e->type != FORMAT_HLINE 1249 && e->type != FORMAT_DOUBLE_HLINE 1250 // Unfortunately tbl treats a span as needing data. 1251 // && e->type != FORMAT_SPAN 1252 ) 1253 break; 1254 } 1255 if (cnt < ncolumns) 1256 break; 1257 for (cnt = 0; cnt < ncolumns; cnt++) 1258 tbl->add_entry(current_row, cnt, input_entry, 1259 f->entry[format_index] + cnt, current_filename, 1260 current_lineno); 1261 tbl->add_vlines(current_row, f->vline[format_index]); 1262 format_index++; 1263 current_row++; 1264 } 1265 entry_format *line_format = f->entry[format_index]; 1266 int col = 0; 1267 int row_comment = 0; 1268 for (;;) { 1269 if (c == tab_char || c == '\n') { 1270 int ln = current_lineno; 1271 if (c == '\n') 1272 --ln; 1273 if ((opt->flags & table::NOSPACES)) 1274 input_entry.remove_spaces(); 1275 while (col < ncolumns 1276 && line_format[col].type == FORMAT_SPAN) { 1277 tbl->add_entry(current_row, col, "", &line_format[col], 1278 current_filename, ln); 1279 col++; 1280 } 1281 if (c == '\n' && input_entry.length() == 2 1282 && input_entry[0] == 'T' && input_entry[1] == '{') { 1283 input_entry = ""; 1284 ln++; 1285 enum { 1286 START, MIDDLE, GOT_T, GOT_RIGHT_BRACE, GOT_DOT, 1287 GOT_l, GOT_lf, END 1288 } state = START; 1289 while (state != END) { 1290 c = in.get(); 1291 if (c == EOF) 1292 break; 1293 switch (state) { 1294 case START: 1295 if (c == 'T') 1296 state = GOT_T; 1297 else if (c == '.') 1298 state = GOT_DOT; 1299 else { 1300 input_entry += c; 1301 if (c != '\n') 1302 state = MIDDLE; 1303 } 1304 break; 1305 case GOT_T: 1306 if (c == '}') 1307 state = GOT_RIGHT_BRACE; 1308 else { 1309 input_entry += 'T'; 1310 input_entry += c; 1311 state = c == '\n' ? START : MIDDLE; 1312 } 1313 break; 1314 case GOT_DOT: 1315 if (c == 'l') 1316 state = GOT_l; 1317 else { 1318 input_entry += '.'; 1319 input_entry += c; 1320 state = c == '\n' ? START : MIDDLE; 1321 } 1322 break; 1323 case GOT_l: 1324 if (c == 'f') 1325 state = GOT_lf; 1326 else { 1327 input_entry += ".l"; 1328 input_entry += c; 1329 state = c == '\n' ? START : MIDDLE; 1330 } 1331 break; 1332 case GOT_lf: 1333 if (c == ' ' || c == '\n' || compatible_flag) { 1334 string args; 1335 input_entry += ".lf"; 1336 while (c != EOF) { 1337 args += c; 1338 if (c == '\n') 1339 break; 1340 c = in.get(); 1341 } 1342 args += '\0'; 1343 interpret_lf_args(args.contents()); 1344 // remove the '\0' 1345 args.set_length(args.length() - 1); 1346 input_entry += args; 1347 state = START; 1348 } 1349 else { 1350 input_entry += ".lf"; 1351 input_entry += c; 1352 state = MIDDLE; 1353 } 1354 break; 1355 case GOT_RIGHT_BRACE: 1356 if ((opt->flags & table::NOSPACES)) { 1357 while (c == ' ') 1358 c = in.get(); 1359 if (c == EOF) 1360 break; 1361 } 1362 if (c == '\n' || c == tab_char) 1363 state = END; 1364 else { 1365 input_entry += 'T'; 1366 input_entry += '}'; 1367 input_entry += c; 1368 state = MIDDLE; 1369 } 1370 break; 1371 case MIDDLE: 1372 if (c == '\n') 1373 state = START; 1374 input_entry += c; 1375 break; 1376 case END: 1377 default: 1378 assert(0); 1379 } 1380 } 1381 if (c == EOF) { 1382 error("end of data in middle of text block"); 1383 give_up = 1; 1384 break; 1385 } 1386 } 1387 if (col >= ncolumns) { 1388 if (!input_entry.empty()) { 1389 if (input_entry.length() >= 2 1390 && input_entry[0] == '\\' 1391 && input_entry[1] == '"') 1392 row_comment = 1; 1393 else if (!row_comment) { 1394 if (c == '\n') 1395 in.unget(c); 1396 input_entry += '\0'; 1397 error("excess data entry `%1' discarded", 1398 input_entry.contents()); 1399 if (c == '\n') 1400 (void)in.get(); 1401 } 1402 } 1403 } 1404 else 1405 tbl->add_entry(current_row, col, input_entry, 1406 &line_format[col], current_filename, ln); 1407 col++; 1408 if (c == '\n') 1409 break; 1410 input_entry = ""; 1411 } 1412 else 1413 input_entry += c; 1414 c = in.get(); 1415 if (c == EOF) 1416 break; 1417 } 1418 if (give_up) 1419 break; 1420 input_entry = ""; 1421 for (; col < ncolumns; col++) 1422 tbl->add_entry(current_row, col, input_entry, &line_format[col], 1423 current_filename, current_lineno - 1); 1424 tbl->add_vlines(current_row, f->vline[format_index]); 1425 current_row++; 1426 format_index++; 1427 } 1428 break; 1429 case TROFF_INPUT_LINE: 1430 { 1431 string line; 1432 int ln = current_lineno; 1433 for (;;) { 1434 line += c; 1435 if (c == '\n') 1436 break; 1437 c = in.get(); 1438 if (c == EOF) { 1439 break; 1440 } 1441 } 1442 tbl->add_text_line(current_row, line, current_filename, ln); 1443 if (line.length() >= 4 1444 && line[0] == '.' && line[1] == 'T' && line[2] == '&') { 1445 format *newf = process_format(in, opt, f); 1446 if (newf == 0) 1447 give_up = 1; 1448 else 1449 f = newf; 1450 } 1451 if (line.length() >= 3 1452 && line[0] == '.' && line[1] == 'l' && line[2] == 'f') { 1453 line += '\0'; 1454 interpret_lf_args(line.contents() + 3); 1455 } 1456 } 1457 break; 1458 case SINGLE_HLINE: 1459 tbl->add_single_hline(current_row); 1460 break; 1461 case DOUBLE_HLINE: 1462 tbl->add_double_hline(current_row); 1463 break; 1464 default: 1465 assert(0); 1466 } 1467 if (give_up) 1468 break; 1469 } 1470 if (!give_up && current_row == 0) { 1471 error("no real data"); 1472 give_up = 1; 1473 } 1474 if (give_up) { 1475 delete tbl; 1476 return 0; 1477 } 1478 // Do this here rather than at the beginning in case continued formats 1479 // change it. 1480 int i; 1481 for (i = 0; i < ncolumns - 1; i++) 1482 if (f->separation[i] >= 0) 1483 tbl->set_column_separation(i, f->separation[i]); 1484 for (i = 0; i < ncolumns; i++) 1485 if (!f->width[i].empty()) 1486 tbl->set_minimum_width(i, f->width[i]); 1487 for (i = 0; i < ncolumns; i++) 1488 if (f->equal[i]) 1489 tbl->set_equal_column(i); 1490 return tbl; 1491} 1492 1493void process_table(table_input &in) 1494{ 1495 options *opt = 0; 1496 format *form = 0; 1497 table *tbl = 0; 1498 if ((opt = process_options(in)) != 0 1499 && (form = process_format(in, opt)) != 0 1500 && (tbl = process_data(in, form, opt)) != 0) { 1501 tbl->print(); 1502 delete tbl; 1503 } 1504 else { 1505 error("giving up on this table"); 1506 while (in.get() != EOF) 1507 ; 1508 } 1509 delete opt; 1510 delete form; 1511 if (!in.ended()) 1512 error("premature end of file"); 1513} 1514 1515static void usage(FILE *stream) 1516{ 1517 fprintf(stream, "usage: %s [ -vC ] [ files... ]\n", program_name); 1518} 1519 1520int main(int argc, char **argv) 1521{ 1522 program_name = argv[0]; 1523 static char stderr_buf[BUFSIZ]; 1524 setbuf(stderr, stderr_buf); 1525 int opt; 1526 static const struct option long_options[] = { 1527 { "help", no_argument, 0, CHAR_MAX + 1 }, 1528 { "version", no_argument, 0, 'v' }, 1529 { NULL, 0, 0, 0 } 1530 }; 1531 while ((opt = getopt_long(argc, argv, "vCT:", long_options, NULL)) != EOF) 1532 switch (opt) { 1533 case 'C': 1534 compatible_flag = 1; 1535 break; 1536 case 'v': 1537 { 1538 printf("GNU tbl (groff) version %s\n", Version_string); 1539 exit(0); 1540 break; 1541 } 1542 case 'T': 1543 // I'm sick of getting bug reports from IRIX users 1544 break; 1545 case CHAR_MAX + 1: // --help 1546 usage(stdout); 1547 exit(0); 1548 break; 1549 case '?': 1550 usage(stderr); 1551 exit(1); 1552 break; 1553 default: 1554 assert(0); 1555 } 1556 printf(".if !\\n(.g .ab GNU tbl requires GNU troff.\n" 1557 ".if !dTS .ds TS\n" 1558 ".if !dTE .ds TE\n"); 1559 if (argc > optind) { 1560 for (int i = optind; i < argc; i++) 1561 if (argv[i][0] == '-' && argv[i][1] == '\0') { 1562 current_filename = "-"; 1563 current_lineno = 1; 1564 printf(".lf 1 -\n"); 1565 process_input_file(stdin); 1566 } 1567 else { 1568 errno = 0; 1569 FILE *fp = fopen(argv[i], "r"); 1570 if (fp == 0) 1571 fatal("can't open `%1': %2", argv[i], strerror(errno)); 1572 else { 1573 current_lineno = 1; 1574 current_filename = argv[i]; 1575 printf(".lf 1 %s\n", current_filename); 1576 process_input_file(fp); 1577 } 1578 } 1579 } 1580 else { 1581 current_filename = "-"; 1582 current_lineno = 1; 1583 printf(".lf 1 -\n"); 1584 process_input_file(stdin); 1585 } 1586 if (ferror(stdout) || fflush(stdout) < 0) 1587 fatal("output error"); 1588 return 0; 1589} 1590 1591