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