groff before CVS: release 1.05
[s-roff.git] / tbl / main.c
blobaffafb0f166e73643f941f2ee78668e3e93b94cf
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991 Free Software Foundation, Inc.
3 Written by James Clark (jjc@jclark.uucp)
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 1, 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 LICENSE. If not, write to the Free Software
19 Foundation, 675 Mass Ave, Cambridge, MA 02139, 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;
330 options();
333 options::options()
334 : flags(0), tab_char('\t'), linesize(0)
336 delim[0] = delim[1] = '\0';
339 // Return non-zero if p and q are the same ignoring case.
341 int strieq(const char *p, const char *q)
343 for (; cmlower(*p) == cmlower(*q); p++, q++)
344 if (*p == '\0')
345 return 1;
346 return 0;
349 // return 0 if we should give up in this table
351 options *process_options(table_input &in)
353 options *opt = new options;
354 string line;
355 int level = 0;
356 for (;;) {
357 int c = in.get();
358 if (c == EOF) {
359 int i = line.length();
360 while (--i >= 0)
361 in.unget(line[i]);
362 return opt;
364 if (c == '\n') {
365 in.unget(c);
366 int i = line.length();
367 while (--i >= 0)
368 in.unget(line[i]);
369 return opt;
371 else if (c == '(')
372 level++;
373 else if (c == ')')
374 level--;
375 else if (c == ';' && level == 0) {
376 line += '\0';
377 break;
379 line += c;
381 if (line.empty())
382 return opt;
383 char *p = &line[0];
384 for (;;) {
385 while (csspace(*p) || *p == ',')
386 p++;
387 if (*p == '\0')
388 break;
389 char *q = p;
390 while (*q != ' ' && *q != '\0' && *q != '\t' && *q != ',' && *q != '(')
391 q++;
392 char *arg = 0;
393 if (*q != '(' && *q != '\0')
394 *q++ = '\0';
395 while (csspace(*q))
396 q++;
397 if (*q == '(') {
398 *q++ = '\0';
399 arg = q;
400 while (*q != ')' && *q != '\0')
401 q++;
402 if (*q == '\0')
403 error("missing `)'");
404 else
405 *q++ = '\0';
407 if (*p == '\0') {
408 if (arg)
409 error("argument without option");
411 else if (strieq(p, "tab")) {
412 if (!arg)
413 error("`tab' option requires argument in parentheses");
414 else {
415 if (arg[0] == '\0' || arg[1] != '\0')
416 error("argument to `tab' option must be a single character");
417 else
418 opt->tab_char = arg[0];
421 else if (strieq(p, "linesize")) {
422 if (!arg)
423 error("`linesize' option requires argument in parentheses");
424 else {
425 if (sscanf(arg, "%d", &opt->linesize) != 1)
426 error("bad linesize `%s'", arg);
427 else if (opt->linesize <= 0) {
428 error("linesize must be positive");
429 opt->linesize = 0;
433 else if (strieq(p, "delim")) {
434 if (!arg)
435 error("`delim' option requires argument in parentheses");
436 else if (arg[0] == '\0' || arg[1] == '\0' || arg[2] != '\0')
437 error("argument to `delim' option must be two characters");
438 else {
439 opt->delim[0] = arg[0];
440 opt->delim[1] = arg[1];
443 else if (strieq(p, "center") || strieq(p, "centre")) {
444 if (arg)
445 error("`center' option does not take a argument");
446 opt->flags |= table::CENTER;
448 else if (strieq(p, "expand")) {
449 if (arg)
450 error("`expand' option does not take a argument");
451 opt->flags |= table::EXPAND;
453 else if (strieq(p, "box") || strieq(p, "frame")) {
454 if (arg)
455 error("`box' option does not take a argument");
456 opt->flags |= table::BOX;
458 else if (strieq(p, "doublebox") || strieq(p, "doubleframe")) {
459 if (arg)
460 error("`doublebox' option does not take a argument");
461 opt->flags |= table::DOUBLEBOX;
463 else if (strieq(p, "allbox")) {
464 if (arg)
465 error("`allbox' option does not take a argument");
466 opt->flags |= table::ALLBOX;
468 else {
469 error("unrecognised global option `%1'", p);
470 // delete opt;
471 // return 0;
473 p = q;
475 return opt;
478 entry_modifier::entry_modifier()
479 : vertical_alignment(CENTER), zero_width(0), stagger(0)
481 vertical_spacing.inc = vertical_spacing.val = 0;
482 point_size.inc = point_size.val = 0;
485 entry_modifier::~entry_modifier()
489 entry_format::entry_format() : type(FORMAT_LEFT)
493 entry_format::entry_format(format_type t) : type(t)
497 void entry_format::debug_print() const
499 switch (type) {
500 case FORMAT_LEFT:
501 putc('l', stderr);
502 break;
503 case FORMAT_CENTER:
504 putc('c', stderr);
505 break;
506 case FORMAT_RIGHT:
507 putc('r', stderr);
508 break;
509 case FORMAT_NUMERIC:
510 putc('n', stderr);
511 break;
512 case FORMAT_ALPHABETIC:
513 putc('a', stderr);
514 break;
515 case FORMAT_SPAN:
516 putc('s', stderr);
517 break;
518 case FORMAT_VSPAN:
519 putc('^', stderr);
520 break;
521 case FORMAT_HLINE:
522 putc('_', stderr);
523 break;
524 case FORMAT_DOUBLE_HLINE:
525 putc('=', stderr);
526 break;
527 default:
528 assert(0);
529 break;
531 if (point_size.val != 0) {
532 putc('p', stderr);
533 if (point_size.inc > 0)
534 putc('+', stderr);
535 else if (point_size.inc < 0)
536 putc('-', stderr);
537 fprintf(stderr, "%d ", point_size.val);
539 if (vertical_spacing.val != 0) {
540 putc('v', stderr);
541 if (vertical_spacing.inc > 0)
542 putc('+', stderr);
543 else if (vertical_spacing.inc < 0)
544 putc('-', stderr);
545 fprintf(stderr, "%d ", vertical_spacing.val);
547 if (!font.empty()) {
548 putc('f', stderr);
549 put_string(font, stderr);
550 putc(' ', stderr);
552 switch (vertical_alignment) {
553 case entry_modifier::CENTER:
554 break;
555 case entry_modifier::TOP:
556 putc('t', stderr);
557 break;
558 case entry_modifier::BOTTOM:
559 putc('d', stderr);
560 break;
562 if (zero_width)
563 putc('z', stderr);
564 if (stagger)
565 putc('u', stderr);
568 struct format {
569 int nrows;
570 int ncolumns;
571 int *separation;
572 string *width;
573 char *equal;
574 entry_format **entry;
575 char **vline;
577 format(int nr, int nc);
578 ~format();
579 void add_rows(int n);
582 format::format(int nr, int nc) : nrows(nr), ncolumns(nc)
584 int i;
585 separation = new int[ncolumns - 1];
586 for (i = 0; i < ncolumns-1; i++)
587 separation[i] = -1;
588 width = new string[ncolumns];
589 equal = new char[ncolumns];
590 for (i = 0; i < ncolumns; i++)
591 equal[i] = 0;
592 entry = new entry_format *[nrows];
593 for (i = 0; i < nrows; i++)
594 entry[i] = new entry_format[ncolumns];
595 vline = new char*[nrows];
596 for (i = 0; i < nrows; i++) {
597 vline[i] = new char[ncolumns+1];
598 for (int j = 0; j < ncolumns+1; j++)
599 vline[i][j] = 0;
603 void format::add_rows(int n)
605 int i;
606 char **old_vline = vline;
607 vline = new char*[nrows + n];
608 for (i = 0; i < nrows; i++)
609 vline[i] = old_vline[i];
610 a_delete old_vline;
611 for (i = 0; i < n; i++) {
612 vline[nrows + i] = new char[ncolumns + 1];
613 for (int j = 0; j < ncolumns + 1; j++)
614 vline[nrows + i][j] = 0;
616 entry_format **old_entry = entry;
617 entry = new entry_format *[nrows + n];
618 for (i = 0; i < nrows; i++)
619 entry[i] = old_entry[i];
620 a_delete old_entry;
621 for (i = 0; i < n; i++)
622 entry[nrows + i] = new entry_format[ncolumns];
623 nrows += n;
626 format::~format()
628 a_delete separation;
629 ad_delete(ncolumns) width;
630 a_delete equal;
631 for (int i = 0; i < nrows; i++) {
632 a_delete vline[i];
633 ad_delete(ncolumns) entry[i];
635 a_delete vline;
636 a_delete entry;
639 struct input_entry_format : entry_format {
640 input_entry_format *next;
641 string width;
642 int separation;
643 int vline;
644 int pre_vline;
645 int last_column;
646 int equal;
647 input_entry_format(format_type, input_entry_format * = 0);
648 ~input_entry_format();
649 void debug_print();
652 input_entry_format::input_entry_format(format_type t, input_entry_format *p)
653 : entry_format(t), next(p)
655 separation = -1;
656 last_column = 0;
657 vline = 0;
658 pre_vline = 0;
659 equal = 0;
662 input_entry_format::~input_entry_format()
666 void free_input_entry_format_list(input_entry_format *list)
668 while (list) {
669 input_entry_format *tem = list;
670 list = list->next;
671 delete tem;
675 void input_entry_format::debug_print()
677 for (int i = 0; i < pre_vline; i++)
678 putc('|', stderr);
679 entry_format::debug_print();
680 if (!width.empty()) {
681 putc('w', stderr);
682 putc('(', stderr);
683 put_string(width, stderr);
684 putc(')', stderr);
686 if (equal)
687 putc('e', stderr);
688 if (separation >= 0)
689 fprintf(stderr, "%d", separation);
690 for (i = 0; i < vline; i++)
691 putc('|', stderr);
692 if (last_column)
693 putc(',', stderr);
696 // Return zero if we should give up on this table.
697 // If this is a continuation format line, current_format will be the current
698 // format line.
700 format *process_format(table_input &in, options *opt,
701 format *current_format = 0)
703 input_entry_format *list = 0;
704 int c = in.get();
705 for (;;) {
706 int pre_vline = 0;
707 int got_format = 0;
708 int got_period = 0;
709 format_type t;
710 for (;;) {
711 if (c == EOF) {
712 error("end of input while processing format");
713 free_input_entry_format_list(list);
714 return 0;
716 switch (c) {
717 case 'n':
718 case 'N':
719 t = FORMAT_NUMERIC;
720 got_format = 1;
721 break;
722 case 'a':
723 case 'A':
724 got_format = 1;
725 t = FORMAT_ALPHABETIC;
726 break;
727 case 'c':
728 case 'C':
729 got_format = 1;
730 t = FORMAT_CENTER;
731 break;
732 case 'l':
733 case 'L':
734 got_format = 1;
735 t = FORMAT_LEFT;
736 break;
737 case 'r':
738 case 'R':
739 got_format = 1;
740 t = FORMAT_RIGHT;
741 break;
742 case 's':
743 case 'S':
744 got_format = 1;
745 t = FORMAT_SPAN;
746 break;
747 case '^':
748 got_format = 1;
749 t = FORMAT_VSPAN;
750 break;
751 case '_':
752 got_format = 1;
753 t = FORMAT_HLINE;
754 break;
755 case '=':
756 got_format = 1;
757 t = FORMAT_DOUBLE_HLINE;
758 break;
759 case '.':
760 got_period = 1;
761 break;
762 case '|':
763 pre_vline++;
764 break;
765 case ' ':
766 case '\t':
767 case '\n':
768 break;
769 default:
770 if (c == opt->tab_char)
771 break;
772 error("unrecognised format `%1'", char(c));
773 free_input_entry_format_list(list);
774 return 0;
776 if (got_period)
777 break;
778 c = in.get();
779 if (got_format)
780 break;
782 if (got_period)
783 break;
784 list = new input_entry_format(t, list);
785 if (pre_vline)
786 list->pre_vline = pre_vline;
787 int success = 1;
788 do {
789 switch (c) {
790 case 't':
791 case 'T':
792 c = in.get();
793 list->vertical_alignment = entry_modifier::TOP;
794 break;
795 case 'd':
796 case 'D':
797 c = in.get();
798 list->vertical_alignment = entry_modifier::BOTTOM;
799 break;
800 case 'u':
801 case 'U':
802 c = in.get();
803 list->stagger = 1;
804 break;
805 case 'z':
806 case 'Z':
807 c = in.get();
808 list->zero_width = 1;
809 break;
810 case '0':
811 case '1':
812 case '2':
813 case '3':
814 case '4':
815 case '5':
816 case '6':
817 case '7':
818 case '8':
819 case '9':
821 int w = 0;
822 do {
823 w = w*10 + (c - '0');
824 c = in.get();
825 } while (c != EOF && csdigit(c));
826 list->separation = w;
828 break;
829 case 'f':
830 case 'F':
831 do {
832 c = in.get();
833 } while (c == ' ' || c == '\t');
834 if (c == EOF) {
835 error("missing font name");
836 break;
838 if (c == '(') {
839 for (;;) {
840 c = in.get();
841 if (c == EOF || c == ' ' || c == '\t') {
842 error("missing `)'");
843 break;
845 if (c == ')') {
846 c = in.get();
847 break;
849 list->font += char(c);
852 else {
853 list->font = c;
854 char cc = c;
855 c = in.get();
856 if (!csdigit(cc)
857 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
858 list->font += char(c);
859 c = in.get();
862 break;
863 case 'v':
864 case 'V':
865 c = in.get();
866 list->vertical_spacing.val = 0;
867 list->vertical_spacing.inc = 0;
868 if (c == '+' || c == '-') {
869 list->vertical_spacing.inc = (c == '+' ? 1 : -1);
870 c = in.get();
872 if (c == EOF || !csdigit(c)) {
873 error("`v' modifier must be followed by number");
874 list->vertical_spacing.inc = 0;
876 else {
877 do {
878 list->vertical_spacing.val *= 10;
879 list->vertical_spacing.val += c - '0';
880 c = in.get();
881 } while (c != EOF && csdigit(c));
883 if (list->vertical_spacing.val > MAX_VERTICAL_SPACING
884 || list->vertical_spacing.val < -MAX_VERTICAL_SPACING) {
885 error("unreasonable point size");
886 list->vertical_spacing.val = 0;
887 list->vertical_spacing.inc = 0;
889 break;
890 case 'p':
891 case 'P':
892 c = in.get();
893 list->point_size.val = 0;
894 list->point_size.inc = 0;
895 if (c == '+' || c == '-') {
896 list->point_size.inc = (c == '+' ? 1 : -1);
897 c = in.get();
899 if (c == EOF || !csdigit(c)) {
900 error("`p' modifier must be followed by number");
901 list->point_size.inc = 0;
903 else {
904 do {
905 list->point_size.val *= 10;
906 list->point_size.val += c - '0';
907 c = in.get();
908 } while (c != EOF && csdigit(c));
910 if (list->point_size.val > MAX_POINT_SIZE
911 || list->point_size.val < -MAX_POINT_SIZE) {
912 error("unreasonable point size");
913 list->point_size.val = 0;
914 list->point_size.inc = 0;
916 break;
917 case 'w':
918 case 'W':
919 c = in.get();
920 while (c == ' ' || c == '\t')
921 c = in.get();
922 if (c == '(') {
923 list->width = "";
924 c = in.get();
925 while (c != ')') {
926 if (c == EOF || c == '\n') {
927 error("missing `)'");
928 free_input_entry_format_list(list);
929 return 0;
931 list->width += c;
932 c = in.get();
934 c = in.get();
936 else {
937 if (c == '+' || c == '-') {
938 list->width = char(c);
939 c = in.get();
941 else
942 list->width = "";
943 if (c == EOF || !csdigit(c))
944 error("bad argument for `w' modifier");
945 else {
946 do {
947 list->width += char(c);
948 c = in.get();
949 } while (c != EOF && csdigit(c));
952 break;
953 case 'e':
954 case 'E':
955 c = in.get();
956 list->equal++;
957 break;
958 case '|':
959 c = in.get();
960 list->vline++;
961 break;
962 case 'B':
963 case 'b':
964 c = in.get();
965 list->font = "B";
966 break;
967 case 'I':
968 case 'i':
969 c = in.get();
970 list->font = "I";
971 break;
972 case ' ':
973 case '\t':
974 c = in.get();
975 break;
976 default:
977 if (c == opt->tab_char)
978 c = in.get();
979 else
980 success = 0;
981 break;
983 } while (success);
984 if (list->vline > 2) {
985 list->vline = 2;
986 error("more than 2 vertical bars between key letters");
988 if (c == '\n' || c == ',') {
989 c = in.get();
990 list->last_column = 1;
993 if (c == '.') {
994 do {
995 c = in.get();
996 } while (c == ' ' || c == '\t');
997 if (c != '\n') {
998 error("`.' not last character on line");
999 free_input_entry_format_list(list);
1000 return 0;
1003 if (!list) {
1004 error("no format");
1005 free_input_entry_format_list(list);
1006 return 0;
1008 list->last_column = 1;
1009 // now reverse the list so that the first row is at the beginning
1010 input_entry_format *rev = 0;
1011 while (list != 0) {
1012 input_entry_format *tem = list->next;
1013 list->next = rev;
1014 rev = list;
1015 list = tem;
1017 list = rev;
1018 input_entry_format *tem;
1020 #if 0
1021 for (tem = list; tem; tem = tem->next)
1022 tem->debug_print();
1023 putc('\n', stderr);
1024 #endif
1025 // compute number of columns and rows
1026 int ncolumns = 0;
1027 int nrows = 0;
1028 int col = 0;
1029 for (tem = list; tem; tem = tem->next) {
1030 if (tem->last_column) {
1031 if (col >= ncolumns)
1032 ncolumns = col + 1;
1033 col = 0;
1034 nrows++;
1036 else
1037 col++;
1039 int row;
1040 format *f;
1041 if (current_format) {
1042 if (ncolumns > current_format->ncolumns) {
1043 error("cannot increase the number of columns in a continued format");
1044 free_input_entry_format_list(list);
1045 return 0;
1047 f = current_format;
1048 row = f->nrows;
1049 f->add_rows(nrows);
1051 else {
1052 f = new format(nrows, ncolumns);
1053 row = 0;
1055 col = 0;
1056 for (tem = list; tem; tem = tem->next) {
1057 f->entry[row][col] = *tem;
1058 if (!current_format) {
1059 if (col < ncolumns-1) {
1060 // use the greatest separation
1061 if (tem->separation > f->separation[col])
1062 f->separation[col] = tem->separation;
1064 else if (tem->separation >= 0)
1065 error("column separation specified for last column");
1066 if (!tem->width.empty()) {
1067 // use the last width
1068 if (!f->width[col].empty() && f->width[col] != tem->width)
1069 error("multiple widths for column %1", col+1);
1070 f->width[col] = tem->width;
1072 if (tem->equal)
1073 f->equal[col] = 1;
1075 if (tem->pre_vline) {
1076 assert(col == 0);
1077 f->vline[row][col] = tem->pre_vline;
1079 f->vline[row][col+1] = tem->vline;
1080 if (tem->last_column) {
1081 row++;
1082 col = 0;
1084 else
1085 col++;
1087 free_input_entry_format_list(list);
1088 for (col = 0; col < ncolumns; col++) {
1089 entry_format *e = f->entry[f->nrows-1] + col;
1090 if (e->type != FORMAT_HLINE
1091 && e->type != FORMAT_DOUBLE_HLINE
1092 && e->type != FORMAT_SPAN)
1093 break;
1095 if (col >= ncolumns) {
1096 error("last row of format is all lines");
1097 delete f;
1098 return 0;
1100 return f;
1103 table *process_data(table_input &in, format *f, options *opt)
1105 char tab_char = opt->tab_char;
1106 int ncolumns = f->ncolumns;
1107 int current_row = 0;
1108 int format_index = 0;
1109 int give_up = 0;
1110 enum { DATA_INPUT_LINE, TROFF_INPUT_LINE, SINGLE_HLINE, DOUBLE_HLINE } type;
1111 table *tbl = new table(ncolumns, opt->flags, opt->linesize);
1112 for (int i = 0; i < ncolumns - 1; i++)
1113 if (f->separation[i] >= 0)
1114 tbl->set_column_separation(i, f->separation[i]);
1115 for (i = 0; i < ncolumns; i++)
1116 if (!f->width[i].empty())
1117 tbl->set_minimum_width(i, f->width[i]);
1118 for (i = 0; i < ncolumns; i++)
1119 if (f->equal[i])
1120 tbl->set_equal_column(i);
1121 if (opt->delim[0] != '\0')
1122 tbl->set_delim(opt->delim[0], opt->delim[1]);
1123 for (;;) {
1124 // first determine what type of line this is
1125 int c = in.get();
1126 if (c == EOF)
1127 break;
1128 if (c == '.') {
1129 int d = in.get();
1130 if (d != EOF && csdigit(d)) {
1131 in.unget(d);
1132 type = DATA_INPUT_LINE;
1134 else {
1135 in.unget(d);
1136 type = TROFF_INPUT_LINE;
1139 else if (c == '_' || c == '=') {
1140 int d = in.get();
1141 if (d == '\n') {
1142 if (c == '_')
1143 type = SINGLE_HLINE;
1144 else
1145 type = DOUBLE_HLINE;
1147 else {
1148 in.unget(d);
1149 type = DATA_INPUT_LINE;
1152 else {
1153 type = DATA_INPUT_LINE;
1155 switch (type) {
1156 case DATA_INPUT_LINE:
1158 string input_entry;
1159 if (format_index >= f->nrows)
1160 format_index = f->nrows - 1;
1161 // A format row that is all lines doesn't use up a data line.
1162 while (format_index < f->nrows - 1) {
1163 for (int c = 0; c < ncolumns; c++) {
1164 entry_format *e = f->entry[format_index] + c;
1165 if (e->type != FORMAT_HLINE
1166 && e->type != FORMAT_DOUBLE_HLINE
1167 // Unfortunately tbl treats a span as needing data.
1168 // && e->type != FORMAT_SPAN
1170 break;
1172 if (c < ncolumns)
1173 break;
1174 for (c = 0; c < ncolumns; c++)
1175 tbl->add_entry(current_row, c, input_entry,
1176 f->entry[format_index] + c, current_filename,
1177 current_lineno);
1178 tbl->add_vlines(current_row, f->vline[format_index]);
1179 format_index++;
1180 current_row++;
1182 entry_format *line_format = f->entry[format_index];
1183 int col = 0;
1184 for (;;) {
1185 if (c == tab_char || c == '\n') {
1186 int ln = current_lineno;
1187 if (c == '\n')
1188 --ln;
1189 while (col < ncolumns
1190 && line_format[col].type == FORMAT_SPAN) {
1191 tbl->add_entry(current_row, col, "", &line_format[col],
1192 current_filename, ln);
1193 col++;
1195 if (c == '\n' && input_entry.length() == 2
1196 && input_entry[0] == 'T' && input_entry[1] == '{') {
1197 input_entry = "";
1198 ln++;
1199 enum {
1200 START, MIDDLE, GOT_T, GOT_RIGHT_BRACE, GOT_DOT,
1201 GOT_l, GOT_lf, END,
1202 } state = START;
1203 while (state != END) {
1204 c = in.get();
1205 if (c == EOF)
1206 break;
1207 switch (state) {
1208 case START:
1209 if (c == 'T')
1210 state = GOT_T;
1211 else if (c == '.')
1212 state = GOT_DOT;
1213 else {
1214 input_entry += c;
1215 if (c != '\n')
1216 state = MIDDLE;
1218 break;
1219 case GOT_T:
1220 if (c == '}')
1221 state = GOT_RIGHT_BRACE;
1222 else {
1223 input_entry += 'T';
1224 input_entry += c;
1225 state = c == '\n' ? START : MIDDLE;
1227 break;
1228 case GOT_DOT:
1229 if (c == 'l')
1230 state = GOT_l;
1231 else {
1232 input_entry += '.';
1233 input_entry += c;
1234 state = c == '\n' ? START : MIDDLE;
1236 break;
1237 case GOT_l:
1238 if (c == 'f')
1239 state = GOT_lf;
1240 else {
1241 input_entry += ".l";
1242 input_entry += c;
1243 state = c == '\n' ? START : MIDDLE;
1245 break;
1246 case GOT_lf:
1247 if (c == ' ' || c == '\n' || compatible_flag) {
1248 string args;
1249 input_entry += ".lf";
1250 while (c != EOF) {
1251 args += c;
1252 if (c == '\n')
1253 break;
1254 c = in.get();
1256 args += '\0';
1257 interpret_lf_args(args.contents());
1258 // remove the '\0'
1259 args.set_length(args.length() - 1);
1260 input_entry += args;
1261 state = START;
1263 else {
1264 input_entry += ".lf";
1265 input_entry += c;
1266 state = MIDDLE;
1268 break;
1269 case GOT_RIGHT_BRACE:
1270 if (c == '\n' || c == tab_char)
1271 state = END;
1272 else {
1273 input_entry += 'T';
1274 input_entry += '}';
1275 input_entry += c;
1276 state = c == '\n' ? START : MIDDLE;
1278 break;
1279 case MIDDLE:
1280 if (c == '\n')
1281 state = START;
1282 input_entry += c;
1283 break;
1284 case END:
1285 default:
1286 assert(0);
1289 if (c == EOF) {
1290 error("end of data in middle of text block");
1291 give_up = 1;
1292 break;
1295 if (col >= ncolumns) {
1296 if (!input_entry.empty()) {
1297 if (c == '\n')
1298 in.unget(c);
1299 input_entry += '\0';
1300 error("excess data entry `%1' discarded",
1301 input_entry.contents());
1302 if (c == '\n')
1303 (void)in.get();
1306 else
1307 tbl->add_entry(current_row, col, input_entry,
1308 &line_format[col], current_filename, ln);
1309 col++;
1310 if (c == '\n')
1311 break;
1312 input_entry = "";
1314 else
1315 input_entry += c;
1316 c = in.get();
1317 if (c == EOF)
1318 break;
1320 if (give_up)
1321 break;
1322 input_entry = "";
1323 for (; col < ncolumns; col++)
1324 tbl->add_entry(current_row, col, input_entry, &line_format[col],
1325 current_filename, current_lineno - 1);
1326 tbl->add_vlines(current_row, f->vline[format_index]);
1327 current_row++;
1328 format_index++;
1330 break;
1331 case TROFF_INPUT_LINE:
1333 string line;
1334 int ln = current_lineno;
1335 for (;;) {
1336 line += c;
1337 if (c == '\n')
1338 break;
1339 c = in.get();
1340 if (c == EOF) {
1341 break;
1344 tbl->add_text_line(current_row, line, current_filename, ln);
1345 if (line.length() >= 4
1346 && line[0] == '.' && line[1] == 'T' && line[2] == '&') {
1347 format *newf = process_format(in, opt, f);
1348 if (newf == 0)
1349 give_up = 1;
1350 else
1351 f = newf;
1353 if (line.length() >= 3
1354 && line[0] == '.' && line[1] == 'f' && line[2] == 'f') {
1355 line += '\0';
1356 interpret_lf_args(line.contents() + 3);
1359 break;
1360 case SINGLE_HLINE:
1361 tbl->add_single_hline(current_row);
1362 break;
1363 case DOUBLE_HLINE:
1364 tbl->add_double_hline(current_row);
1365 break;
1366 default:
1367 assert(0);
1369 if (give_up)
1370 break;
1372 if (!give_up && current_row == 0) {
1373 error("no real data");
1374 give_up = 1;
1376 if (give_up) {
1377 delete tbl;
1378 return 0;
1380 return tbl;
1383 void process_table(table_input &in)
1385 int c;
1386 options *opt = 0;
1387 format *form = 0;
1388 table *tbl = 0;
1389 if ((opt = process_options(in)) != 0
1390 && (form = process_format(in, opt)) != 0
1391 && (tbl = process_data(in, form, opt)) != 0) {
1392 tbl->print();
1393 delete tbl;
1395 else {
1396 error("giving up on this table");
1397 while ((c = in.get()) != EOF)
1400 delete opt;
1401 delete form;
1402 if (!in.ended())
1403 error("premature end of file");
1406 static void usage()
1408 fprintf(stderr, "usage: %s [ -vC ] [ files... ]\n", program_name);
1409 exit(1);
1412 int main(int argc, char **argv)
1414 program_name = argv[0];
1415 static char stderr_buf[BUFSIZ];
1416 setbuf(stderr, stderr_buf);
1417 int opt;
1418 while ((opt = getopt(argc, argv, "vC")) != EOF)
1419 switch (opt) {
1420 case 'C':
1421 compatible_flag = 1;
1422 break;
1423 case 'v':
1425 extern const char *version_string;
1426 fprintf(stderr, "GNU tbl version %s\n", version_string);
1427 fflush(stderr);
1428 break;
1430 case '?':
1431 usage();
1432 break;
1433 default:
1434 assert(0);
1436 printf(".if !\\n(.g .ab GNU tbl requires GNU troff.\n"
1437 ".if !dTS .ds TS\n"
1438 ".if !dTE .ds TE\n");
1439 if (argc > optind) {
1440 for (int i = optind; i < argc; i++)
1441 if (argv[i][0] == '-' && argv[i][1] == '\0') {
1442 current_filename = "-";
1443 current_lineno = 1;
1444 if (i != 1)
1445 printf(".lf 1 -\n");
1446 process_input_file(stdin);
1448 else {
1449 errno = 0;
1450 FILE *fp = fopen(argv[i], "r");
1451 if (fp == 0) {
1452 current_lineno = -1;
1453 error("can't open `%1': %2", argv[i], strerror(errno));
1455 else {
1456 current_lineno = 1;
1457 current_filename = argv[i];
1458 printf(".lf 1 %s\n", current_filename);
1459 process_input_file(fp);
1463 else {
1464 current_filename = "-";
1465 current_lineno = 1;
1466 process_input_file(stdin);
1468 if (ferror(stdout) || fflush(stdout) < 0)
1469 fatal("output error");
1470 exit(0);