Update to groff 1.19.2.
[dragonfly.git] / contrib / groff-1.19 / src / preproc / tbl / main.cpp
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
6 This file is part of groff.
7
8 groff is free software; you can redistribute it and/or modify it under
9 the terms of the GNU General Public License as published by the Free
10 Software Foundation; either version 2, or (at your option) any later
11 version.
12
13 groff is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16 for more details.
17
18 You should have received a copy of the GNU General Public License along
19 with groff; see the file COPYING.  If not, write to the Free Software
20 Foundation, 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
27 extern "C" const char *Version_string;
28
29 int compatible_flag = 0;
30
31 class 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;
38 public:
39   table_input(FILE *);
40   int get();
41   int ended() { return unget_stack.empty() && state == END; }
42   void unget(char);
43 };
44
45 table_input::table_input(FILE *p)
46 : fp(p), state(START)
47 {
48 }
49
50 void table_input::unget(char c)
51 {
52   assert(c != '\0');
53   unget_stack += c;
54   if (c == '\n')
55     current_lineno--;
56 }
57
58 int 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
181 void process_input_file(FILE *);
182 void process_table(table_input &in);
183
184 void 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
346 struct 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
356 options::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
364 int 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
374 options *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
521 entry_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
528 entry_modifier::~entry_modifier()
529 {
530 }
531
532 entry_format::entry_format() : type(FORMAT_LEFT)
533 {
534 }
535
536 entry_format::entry_format(format_type t) : type(t)
537 {
538 }
539
540 void 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
616 struct 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
630 format::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
651 void 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
674 format::~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
687 struct 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
700 input_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
710 input_entry_format::~input_entry_format()
711 {
712 }
713
714 void 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
723 void 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
749 format *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
1193 table *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
1493 void 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
1515 static void usage(FILE *stream)
1516 {
1517   fprintf(stream, "usage: %s [ -vC ] [ files... ]\n", program_name);
1518 }
1519
1520 int 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