PageRenderTime 133ms CodeModel.GetById 11ms app.highlight 110ms RepoModel.GetById 2ms app.codeStats 0ms

/contrib/groff/src/preproc/tbl/main.cpp

https://bitbucket.org/freebsd/freebsd-head/
C++ | 1590 lines | 1517 code | 36 blank | 37 comment | 538 complexity | 942a81191c6abe366596efe8cb3455a5 MD5 | raw file
   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