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