Add key character `x' to tbl which makes tbl call a user-defined
[s-roff.git] / src / preproc / tbl / main.cpp
blob8984786e73ee02e9006146d933a6d4039ade9e5f
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003
3 Free Software Foundation, Inc.
4 Written by James Clark (jjc@jclark.com)
6 This file is part of groff.
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.
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.
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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
22 #include "table.h"
24 #define MAX_POINT_SIZE 99
25 #define MAX_VERTICAL_SPACING 72
27 extern "C" const char *Version_string;
29 static int compatible_flag = 0;
31 class table_input {
32 FILE *fp;
33 enum { START, MIDDLE, REREAD_T, REREAD_TE, REREAD_E, END, ERROR } state;
34 string unget_stack;
35 public:
36 table_input(FILE *);
37 int get();
38 int ended() { return unget_stack.empty() && state == END; }
39 void unget(char);
42 table_input::table_input(FILE *p)
43 : fp(p), state(START)
47 void table_input::unget(char c)
49 assert(c != '\0');
50 unget_stack += c;
51 if (c == '\n')
52 current_lineno--;
55 int table_input::get()
57 int len = unget_stack.length();
58 if (len != 0) {
59 unsigned char c = unget_stack[len - 1];
60 unget_stack.set_length(len - 1);
61 if (c == '\n')
62 current_lineno++;
63 return c;
65 int c;
66 for (;;) {
67 switch (state) {
68 case START:
69 if ((c = getc(fp)) == '.') {
70 if ((c = getc(fp)) == 'T') {
71 if ((c = getc(fp)) == 'E') {
72 if (compatible_flag) {
73 state = END;
74 return EOF;
76 else {
77 c = getc(fp);
78 if (c != EOF)
79 ungetc(c, fp);
80 if (c == EOF || c == ' ' || c == '\n') {
81 state = END;
82 return EOF;
84 state = REREAD_TE;
85 return '.';
88 else {
89 if (c != EOF)
90 ungetc(c, fp);
91 state = REREAD_T;
92 return '.';
95 else {
96 if (c != EOF)
97 ungetc(c, fp);
98 state = MIDDLE;
99 return '.';
102 else if (c == EOF) {
103 state = ERROR;
104 return EOF;
106 else {
107 if (c == '\n')
108 current_lineno++;
109 else {
110 state = MIDDLE;
111 if (c == '\0') {
112 error("invalid input character code 0");
113 break;
116 return c;
118 break;
119 case MIDDLE:
120 // handle line continuation
121 if ((c = getc(fp)) == '\\') {
122 c = getc(fp);
123 if (c == '\n')
124 c = getc(fp); // perhaps state ought to be START now
125 else {
126 if (c != EOF)
127 ungetc(c, fp);
128 c = '\\';
131 if (c == EOF) {
132 state = ERROR;
133 return EOF;
135 else {
136 if (c == '\n') {
137 state = START;
138 current_lineno++;
140 else if (c == '\0') {
141 error("invalid input character code 0");
142 break;
144 return c;
146 case REREAD_T:
147 state = MIDDLE;
148 return 'T';
149 case REREAD_TE:
150 state = REREAD_E;
151 return 'T';
152 case REREAD_E:
153 state = MIDDLE;
154 return 'E';
155 case END:
156 case ERROR:
157 return EOF;
162 void process_input_file(FILE *);
163 void process_table(table_input &in);
165 void process_input_file(FILE *fp)
167 enum { START, MIDDLE, HAD_DOT, HAD_T, HAD_TS, HAD_l, HAD_lf } state;
168 state = START;
169 int c;
170 while ((c = getc(fp)) != EOF)
171 switch (state) {
172 case START:
173 if (c == '.')
174 state = HAD_DOT;
175 else {
176 if (c == '\n')
177 current_lineno++;
178 else
179 state = MIDDLE;
180 putchar(c);
182 break;
183 case MIDDLE:
184 if (c == '\n') {
185 current_lineno++;
186 state = START;
188 putchar(c);
189 break;
190 case HAD_DOT:
191 if (c == 'T')
192 state = HAD_T;
193 else if (c == 'l')
194 state = HAD_l;
195 else {
196 putchar('.');
197 putchar(c);
198 if (c == '\n') {
199 current_lineno++;
200 state = START;
202 else
203 state = MIDDLE;
205 break;
206 case HAD_T:
207 if (c == 'S')
208 state = HAD_TS;
209 else {
210 putchar('.');
211 putchar('T');
212 putchar(c);
213 if (c == '\n') {
214 current_lineno++;
215 state = START;
217 else
218 state = MIDDLE;
220 break;
221 case HAD_TS:
222 if (c == ' ' || c == '\n' || compatible_flag) {
223 putchar('.');
224 putchar('T');
225 putchar('S');
226 while (c != '\n') {
227 if (c == EOF) {
228 error("end of file at beginning of table");
229 return;
231 putchar(c);
232 c = getc(fp);
234 putchar('\n');
235 current_lineno++;
237 table_input input(fp);
238 process_table(input);
239 set_troff_location(current_filename, current_lineno);
240 if (input.ended()) {
241 fputs(".TE", stdout);
242 while ((c = getc(fp)) != '\n') {
243 if (c == EOF) {
244 putchar('\n');
245 return;
247 putchar(c);
249 putchar('\n');
250 current_lineno++;
253 state = START;
255 else {
256 fputs(".TS", stdout);
257 putchar(c);
258 state = MIDDLE;
260 break;
261 case HAD_l:
262 if (c == 'f')
263 state = HAD_lf;
264 else {
265 putchar('.');
266 putchar('l');
267 putchar(c);
268 if (c == '\n') {
269 current_lineno++;
270 state = START;
272 else
273 state = MIDDLE;
275 break;
276 case HAD_lf:
277 if (c == ' ' || c == '\n' || compatible_flag) {
278 string line;
279 while (c != EOF) {
280 line += c;
281 if (c == '\n') {
282 current_lineno++;
283 break;
285 c = getc(fp);
287 line += '\0';
288 interpret_lf_args(line.contents());
289 printf(".lf%s", line.contents());
290 state = START;
292 else {
293 fputs(".lf", stdout);
294 putchar(c);
295 state = MIDDLE;
297 break;
298 default:
299 assert(0);
301 switch(state) {
302 case START:
303 break;
304 case MIDDLE:
305 putchar('\n');
306 break;
307 case HAD_DOT:
308 fputs(".\n", stdout);
309 break;
310 case HAD_l:
311 fputs(".l\n", stdout);
312 break;
313 case HAD_T:
314 fputs(".T\n", stdout);
315 break;
316 case HAD_lf:
317 fputs(".lf\n", stdout);
318 break;
319 case HAD_TS:
320 fputs(".TS\n", stdout);
321 break;
323 if (fp != stdin)
324 fclose(fp);
327 struct options {
328 unsigned flags;
329 int linesize;
330 char delim[2];
331 char tab_char;
332 char decimal_point_char;
334 options();
337 options::options()
338 : flags(0), linesize(0), tab_char('\t'), decimal_point_char('.')
340 delim[0] = delim[1] = '\0';
343 // Return non-zero if p and q are the same ignoring case.
345 int strieq(const char *p, const char *q)
347 for (; cmlower(*p) == cmlower(*q); p++, q++)
348 if (*p == '\0')
349 return 1;
350 return 0;
353 // return 0 if we should give up in this table
355 options *process_options(table_input &in)
357 options *opt = new options;
358 string line;
359 int level = 0;
360 for (;;) {
361 int c = in.get();
362 if (c == EOF) {
363 int i = line.length();
364 while (--i >= 0)
365 in.unget(line[i]);
366 return opt;
368 if (c == '\n') {
369 in.unget(c);
370 int i = line.length();
371 while (--i >= 0)
372 in.unget(line[i]);
373 return opt;
375 else if (c == '(')
376 level++;
377 else if (c == ')')
378 level--;
379 else if (c == ';' && level == 0) {
380 line += '\0';
381 break;
383 line += c;
385 if (line.empty())
386 return opt;
387 char *p = &line[0];
388 for (;;) {
389 while (!csalpha(*p) && *p != '\0')
390 p++;
391 if (*p == '\0')
392 break;
393 char *q = p;
394 while (csalpha(*q))
395 q++;
396 char *arg = 0;
397 if (*q != '(' && *q != '\0')
398 *q++ = '\0';
399 while (csspace(*q))
400 q++;
401 if (*q == '(') {
402 *q++ = '\0';
403 arg = q;
404 while (*q != ')' && *q != '\0')
405 q++;
406 if (*q == '\0')
407 error("missing `)'");
408 else
409 *q++ = '\0';
411 if (*p == '\0') {
412 if (arg)
413 error("argument without option");
415 else if (strieq(p, "tab")) {
416 if (!arg)
417 error("`tab' option requires argument in parentheses");
418 else {
419 if (arg[0] == '\0' || arg[1] != '\0')
420 error("argument to `tab' option must be a single character");
421 else
422 opt->tab_char = arg[0];
425 else if (strieq(p, "linesize")) {
426 if (!arg)
427 error("`linesize' option requires argument in parentheses");
428 else {
429 if (sscanf(arg, "%d", &opt->linesize) != 1)
430 error("bad linesize `%s'", arg);
431 else if (opt->linesize <= 0) {
432 error("linesize must be positive");
433 opt->linesize = 0;
437 else if (strieq(p, "delim")) {
438 if (!arg)
439 error("`delim' option requires argument in parentheses");
440 else if (arg[0] == '\0' || arg[1] == '\0' || arg[2] != '\0')
441 error("argument to `delim' option must be two characters");
442 else {
443 opt->delim[0] = arg[0];
444 opt->delim[1] = arg[1];
447 else if (strieq(p, "center") || strieq(p, "centre")) {
448 if (arg)
449 error("`center' option does not take an argument");
450 opt->flags |= table::CENTER;
452 else if (strieq(p, "expand")) {
453 if (arg)
454 error("`expand' option does not take an argument");
455 opt->flags |= table::EXPAND;
457 else if (strieq(p, "box") || strieq(p, "frame")) {
458 if (arg)
459 error("`box' option does not take an argument");
460 opt->flags |= table::BOX;
462 else if (strieq(p, "doublebox") || strieq(p, "doubleframe")) {
463 if (arg)
464 error("`doublebox' option does not take an argument");
465 opt->flags |= table::DOUBLEBOX;
467 else if (strieq(p, "allbox")) {
468 if (arg)
469 error("`allbox' option does not take an argument");
470 opt->flags |= table::ALLBOX;
472 else if (strieq(p, "nokeep")) {
473 if (arg)
474 error("`nokeep' option does not take an argument");
475 opt->flags |= table::NOKEEP;
477 else if (strieq(p, "nospaces")) {
478 if (arg)
479 error("`nospaces' option does not take an argument");
480 opt->flags |= table::NOSPACES;
482 else if (strieq(p, "decimalpoint")) {
483 if (!arg)
484 error("`decimalpoint' option requires argument in parentheses");
485 else {
486 if (arg[0] == '\0' || arg[1] != '\0')
487 error("argument to `decimalpoint' option must be a single character");
488 else
489 opt->decimal_point_char = arg[0];
492 else {
493 error("unrecognised global option `%1'", p);
494 // delete opt;
495 // return 0;
497 p = q;
499 return opt;
502 entry_modifier::entry_modifier()
503 : vertical_alignment(CENTER), zero_width(0), stagger(0)
505 vertical_spacing.inc = vertical_spacing.val = 0;
506 point_size.inc = point_size.val = 0;
509 entry_modifier::~entry_modifier()
513 entry_format::entry_format() : type(FORMAT_LEFT)
517 entry_format::entry_format(format_type t) : type(t)
521 void entry_format::debug_print() const
523 switch (type) {
524 case FORMAT_LEFT:
525 putc('l', stderr);
526 break;
527 case FORMAT_CENTER:
528 putc('c', stderr);
529 break;
530 case FORMAT_RIGHT:
531 putc('r', stderr);
532 break;
533 case FORMAT_NUMERIC:
534 putc('n', stderr);
535 break;
536 case FORMAT_ALPHABETIC:
537 putc('a', stderr);
538 break;
539 case FORMAT_SPAN:
540 putc('s', stderr);
541 break;
542 case FORMAT_VSPAN:
543 putc('^', stderr);
544 break;
545 case FORMAT_HLINE:
546 putc('_', stderr);
547 break;
548 case FORMAT_DOUBLE_HLINE:
549 putc('=', stderr);
550 break;
551 default:
552 assert(0);
553 break;
555 if (point_size.val != 0) {
556 putc('p', stderr);
557 if (point_size.inc > 0)
558 putc('+', stderr);
559 else if (point_size.inc < 0)
560 putc('-', stderr);
561 fprintf(stderr, "%d ", point_size.val);
563 if (vertical_spacing.val != 0) {
564 putc('v', stderr);
565 if (vertical_spacing.inc > 0)
566 putc('+', stderr);
567 else if (vertical_spacing.inc < 0)
568 putc('-', stderr);
569 fprintf(stderr, "%d ", vertical_spacing.val);
571 if (!font.empty()) {
572 putc('f', stderr);
573 put_string(font, stderr);
574 putc(' ', stderr);
576 if (!macro.empty()) {
577 putc('m', stderr);
578 put_string(macro, stderr);
579 putc(' ', stderr);
581 switch (vertical_alignment) {
582 case entry_modifier::CENTER:
583 break;
584 case entry_modifier::TOP:
585 putc('t', stderr);
586 break;
587 case entry_modifier::BOTTOM:
588 putc('d', stderr);
589 break;
591 if (zero_width)
592 putc('z', stderr);
593 if (stagger)
594 putc('u', stderr);
597 struct format {
598 int nrows;
599 int ncolumns;
600 int *separation;
601 string *width;
602 char *equal;
603 entry_format **entry;
604 char **vline;
606 format(int nr, int nc);
607 ~format();
608 void add_rows(int n);
611 format::format(int nr, int nc) : nrows(nr), ncolumns(nc)
613 int i;
614 separation = ncolumns > 1 ? new int[ncolumns - 1] : 0;
615 for (i = 0; i < ncolumns-1; i++)
616 separation[i] = -1;
617 width = new string[ncolumns];
618 equal = new char[ncolumns];
619 for (i = 0; i < ncolumns; i++)
620 equal[i] = 0;
621 entry = new entry_format *[nrows];
622 for (i = 0; i < nrows; i++)
623 entry[i] = new entry_format[ncolumns];
624 vline = new char*[nrows];
625 for (i = 0; i < nrows; i++) {
626 vline[i] = new char[ncolumns+1];
627 for (int j = 0; j < ncolumns+1; j++)
628 vline[i][j] = 0;
632 void format::add_rows(int n)
634 int i;
635 char **old_vline = vline;
636 vline = new char*[nrows + n];
637 for (i = 0; i < nrows; i++)
638 vline[i] = old_vline[i];
639 a_delete old_vline;
640 for (i = 0; i < n; i++) {
641 vline[nrows + i] = new char[ncolumns + 1];
642 for (int j = 0; j < ncolumns + 1; j++)
643 vline[nrows + i][j] = 0;
645 entry_format **old_entry = entry;
646 entry = new entry_format *[nrows + n];
647 for (i = 0; i < nrows; i++)
648 entry[i] = old_entry[i];
649 a_delete old_entry;
650 for (i = 0; i < n; i++)
651 entry[nrows + i] = new entry_format[ncolumns];
652 nrows += n;
655 format::~format()
657 a_delete separation;
658 ad_delete(ncolumns) width;
659 a_delete equal;
660 for (int i = 0; i < nrows; i++) {
661 a_delete vline[i];
662 ad_delete(ncolumns) entry[i];
664 a_delete vline;
665 a_delete entry;
668 struct input_entry_format : public entry_format {
669 input_entry_format *next;
670 string width;
671 int separation;
672 int vline;
673 int pre_vline;
674 int last_column;
675 int equal;
676 input_entry_format(format_type, input_entry_format * = 0);
677 ~input_entry_format();
678 void debug_print();
681 input_entry_format::input_entry_format(format_type t, input_entry_format *p)
682 : entry_format(t), next(p)
684 separation = -1;
685 last_column = 0;
686 vline = 0;
687 pre_vline = 0;
688 equal = 0;
691 input_entry_format::~input_entry_format()
695 void free_input_entry_format_list(input_entry_format *list)
697 while (list) {
698 input_entry_format *tem = list;
699 list = list->next;
700 delete tem;
704 void input_entry_format::debug_print()
706 int i;
707 for (i = 0; i < pre_vline; i++)
708 putc('|', stderr);
709 entry_format::debug_print();
710 if (!width.empty()) {
711 putc('w', stderr);
712 putc('(', stderr);
713 put_string(width, stderr);
714 putc(')', stderr);
716 if (equal)
717 putc('e', stderr);
718 if (separation >= 0)
719 fprintf(stderr, "%d", separation);
720 for (i = 0; i < vline; i++)
721 putc('|', stderr);
722 if (last_column)
723 putc(',', stderr);
726 // Return zero if we should give up on this table.
727 // If this is a continuation format line, current_format will be the current
728 // format line.
730 format *process_format(table_input &in, options *opt,
731 format *current_format = 0)
733 input_entry_format *list = 0;
734 int c = in.get();
735 for (;;) {
736 int pre_vline = 0;
737 int got_format = 0;
738 int got_period = 0;
739 format_type t = FORMAT_LEFT;
740 for (;;) {
741 if (c == EOF) {
742 error("end of input while processing format");
743 free_input_entry_format_list(list);
744 return 0;
746 switch (c) {
747 case 'n':
748 case 'N':
749 t = FORMAT_NUMERIC;
750 got_format = 1;
751 break;
752 case 'a':
753 case 'A':
754 got_format = 1;
755 t = FORMAT_ALPHABETIC;
756 break;
757 case 'c':
758 case 'C':
759 got_format = 1;
760 t = FORMAT_CENTER;
761 break;
762 case 'l':
763 case 'L':
764 got_format = 1;
765 t = FORMAT_LEFT;
766 break;
767 case 'r':
768 case 'R':
769 got_format = 1;
770 t = FORMAT_RIGHT;
771 break;
772 case 's':
773 case 'S':
774 got_format = 1;
775 t = FORMAT_SPAN;
776 break;
777 case '^':
778 got_format = 1;
779 t = FORMAT_VSPAN;
780 break;
781 case '_':
782 case '-': // tbl also accepts this
783 got_format = 1;
784 t = FORMAT_HLINE;
785 break;
786 case '=':
787 got_format = 1;
788 t = FORMAT_DOUBLE_HLINE;
789 break;
790 case '.':
791 got_period = 1;
792 break;
793 case '|':
794 pre_vline++;
795 break;
796 case ' ':
797 case '\t':
798 case '\n':
799 break;
800 default:
801 if (c == opt->tab_char)
802 break;
803 error("unrecognised format `%1'", char(c));
804 free_input_entry_format_list(list);
805 return 0;
807 if (got_period)
808 break;
809 c = in.get();
810 if (got_format)
811 break;
813 if (got_period)
814 break;
815 list = new input_entry_format(t, list);
816 if (pre_vline)
817 list->pre_vline = pre_vline;
818 int success = 1;
819 do {
820 switch (c) {
821 case 't':
822 case 'T':
823 c = in.get();
824 list->vertical_alignment = entry_modifier::TOP;
825 break;
826 case 'd':
827 case 'D':
828 c = in.get();
829 list->vertical_alignment = entry_modifier::BOTTOM;
830 break;
831 case 'u':
832 case 'U':
833 c = in.get();
834 list->stagger = 1;
835 break;
836 case 'z':
837 case 'Z':
838 c = in.get();
839 list->zero_width = 1;
840 break;
841 case '0':
842 case '1':
843 case '2':
844 case '3':
845 case '4':
846 case '5':
847 case '6':
848 case '7':
849 case '8':
850 case '9':
852 int w = 0;
853 do {
854 w = w*10 + (c - '0');
855 c = in.get();
856 } while (c != EOF && csdigit(c));
857 list->separation = w;
859 break;
860 case 'f':
861 case 'F':
862 do {
863 c = in.get();
864 } while (c == ' ' || c == '\t');
865 if (c == EOF) {
866 error("missing font name");
867 break;
869 if (c == '(') {
870 for (;;) {
871 c = in.get();
872 if (c == EOF || c == ' ' || c == '\t') {
873 error("missing `)'");
874 break;
876 if (c == ')') {
877 c = in.get();
878 break;
880 list->font += char(c);
883 else {
884 list->font = c;
885 char cc = c;
886 c = in.get();
887 if (!csdigit(cc)
888 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
889 list->font += char(c);
890 c = in.get();
893 break;
894 case 'x':
895 case 'X':
896 do {
897 c = in.get();
898 } while (c == ' ' || c == '\t');
899 if (c == EOF) {
900 error("missing macro name");
901 break;
903 if (c == '(') {
904 for (;;) {
905 c = in.get();
906 if (c == EOF || c == ' ' || c == '\t') {
907 error("missing `)'");
908 break;
910 if (c == ')') {
911 c = in.get();
912 break;
914 list->macro += char(c);
917 else {
918 list->macro = c;
919 char cc = c;
920 c = in.get();
921 if (!csdigit(cc)
922 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
923 list->macro += char(c);
924 c = in.get();
927 break;
928 case 'v':
929 case 'V':
930 c = in.get();
931 list->vertical_spacing.val = 0;
932 list->vertical_spacing.inc = 0;
933 if (c == '+' || c == '-') {
934 list->vertical_spacing.inc = (c == '+' ? 1 : -1);
935 c = in.get();
937 if (c == EOF || !csdigit(c)) {
938 error("`v' modifier must be followed by number");
939 list->vertical_spacing.inc = 0;
941 else {
942 do {
943 list->vertical_spacing.val *= 10;
944 list->vertical_spacing.val += c - '0';
945 c = in.get();
946 } while (c != EOF && csdigit(c));
948 if (list->vertical_spacing.val > MAX_VERTICAL_SPACING
949 || list->vertical_spacing.val < -MAX_VERTICAL_SPACING) {
950 error("unreasonable vertical spacing");
951 list->vertical_spacing.val = 0;
952 list->vertical_spacing.inc = 0;
954 break;
955 case 'p':
956 case 'P':
957 c = in.get();
958 list->point_size.val = 0;
959 list->point_size.inc = 0;
960 if (c == '+' || c == '-') {
961 list->point_size.inc = (c == '+' ? 1 : -1);
962 c = in.get();
964 if (c == EOF || !csdigit(c)) {
965 error("`p' modifier must be followed by number");
966 list->point_size.inc = 0;
968 else {
969 do {
970 list->point_size.val *= 10;
971 list->point_size.val += c - '0';
972 c = in.get();
973 } while (c != EOF && csdigit(c));
975 if (list->point_size.val > MAX_POINT_SIZE
976 || list->point_size.val < -MAX_POINT_SIZE) {
977 error("unreasonable point size");
978 list->point_size.val = 0;
979 list->point_size.inc = 0;
981 break;
982 case 'w':
983 case 'W':
984 c = in.get();
985 while (c == ' ' || c == '\t')
986 c = in.get();
987 if (c == '(') {
988 list->width = "";
989 c = in.get();
990 while (c != ')') {
991 if (c == EOF || c == '\n') {
992 error("missing `)'");
993 free_input_entry_format_list(list);
994 return 0;
996 list->width += c;
997 c = in.get();
999 c = in.get();
1001 else {
1002 if (c == '+' || c == '-') {
1003 list->width = char(c);
1004 c = in.get();
1006 else
1007 list->width = "";
1008 if (c == EOF || !csdigit(c))
1009 error("bad argument for `w' modifier");
1010 else {
1011 do {
1012 list->width += char(c);
1013 c = in.get();
1014 } while (c != EOF && csdigit(c));
1017 break;
1018 case 'e':
1019 case 'E':
1020 c = in.get();
1021 list->equal++;
1022 break;
1023 case '|':
1024 c = in.get();
1025 list->vline++;
1026 break;
1027 case 'B':
1028 case 'b':
1029 c = in.get();
1030 list->font = "B";
1031 break;
1032 case 'I':
1033 case 'i':
1034 c = in.get();
1035 list->font = "I";
1036 break;
1037 case ' ':
1038 case '\t':
1039 c = in.get();
1040 break;
1041 default:
1042 if (c == opt->tab_char)
1043 c = in.get();
1044 else
1045 success = 0;
1046 break;
1048 } while (success);
1049 if (list->vline > 2) {
1050 list->vline = 2;
1051 error("more than 2 vertical bars between key letters");
1053 if (c == '\n' || c == ',') {
1054 c = in.get();
1055 list->last_column = 1;
1058 if (c == '.') {
1059 do {
1060 c = in.get();
1061 } while (c == ' ' || c == '\t');
1062 if (c != '\n') {
1063 error("`.' not last character on line");
1064 free_input_entry_format_list(list);
1065 return 0;
1068 if (!list) {
1069 error("no format");
1070 free_input_entry_format_list(list);
1071 return 0;
1073 list->last_column = 1;
1074 // now reverse the list so that the first row is at the beginning
1075 input_entry_format *rev = 0;
1076 while (list != 0) {
1077 input_entry_format *tem = list->next;
1078 list->next = rev;
1079 rev = list;
1080 list = tem;
1082 list = rev;
1083 input_entry_format *tem;
1085 #if 0
1086 for (tem = list; tem; tem = tem->next)
1087 tem->debug_print();
1088 putc('\n', stderr);
1089 #endif
1090 // compute number of columns and rows
1091 int ncolumns = 0;
1092 int nrows = 0;
1093 int col = 0;
1094 for (tem = list; tem; tem = tem->next) {
1095 if (tem->last_column) {
1096 if (col >= ncolumns)
1097 ncolumns = col + 1;
1098 col = 0;
1099 nrows++;
1101 else
1102 col++;
1104 int row;
1105 format *f;
1106 if (current_format) {
1107 if (ncolumns > current_format->ncolumns) {
1108 error("cannot increase the number of columns in a continued format");
1109 free_input_entry_format_list(list);
1110 return 0;
1112 f = current_format;
1113 row = f->nrows;
1114 f->add_rows(nrows);
1116 else {
1117 f = new format(nrows, ncolumns);
1118 row = 0;
1120 col = 0;
1121 for (tem = list; tem; tem = tem->next) {
1122 f->entry[row][col] = *tem;
1123 if (col < ncolumns-1) {
1124 // use the greatest separation
1125 if (tem->separation > f->separation[col]) {
1126 if (current_format)
1127 error("cannot change column separation in continued format");
1128 else
1129 f->separation[col] = tem->separation;
1132 else if (tem->separation >= 0)
1133 error("column separation specified for last column");
1134 if (tem->equal && !f->equal[col]) {
1135 if (current_format)
1136 error("cannot change which columns are equal in continued format");
1137 else
1138 f->equal[col] = 1;
1140 if (!tem->width.empty()) {
1141 // use the last width
1142 if (!f->width[col].empty() && f->width[col] != tem->width)
1143 error("multiple widths for column %1", col+1);
1144 f->width[col] = tem->width;
1146 if (tem->pre_vline) {
1147 assert(col == 0);
1148 f->vline[row][col] = tem->pre_vline;
1150 f->vline[row][col+1] = tem->vline;
1151 if (tem->last_column) {
1152 row++;
1153 col = 0;
1155 else
1156 col++;
1158 free_input_entry_format_list(list);
1159 for (col = 0; col < ncolumns; col++) {
1160 entry_format *e = f->entry[f->nrows-1] + col;
1161 if (e->type != FORMAT_HLINE
1162 && e->type != FORMAT_DOUBLE_HLINE
1163 && e->type != FORMAT_SPAN)
1164 break;
1166 if (col >= ncolumns) {
1167 error("last row of format is all lines");
1168 delete f;
1169 return 0;
1171 return f;
1174 table *process_data(table_input &in, format *f, options *opt)
1176 char tab_char = opt->tab_char;
1177 int ncolumns = f->ncolumns;
1178 int current_row = 0;
1179 int format_index = 0;
1180 int give_up = 0;
1181 enum { DATA_INPUT_LINE, TROFF_INPUT_LINE, SINGLE_HLINE, DOUBLE_HLINE } type;
1182 table *tbl = new table(ncolumns, opt->flags, opt->linesize,
1183 opt->decimal_point_char);
1184 if (opt->delim[0] != '\0')
1185 tbl->set_delim(opt->delim[0], opt->delim[1]);
1186 for (;;) {
1187 // first determine what type of line this is
1188 int c = in.get();
1189 if (c == EOF)
1190 break;
1191 if (c == '.') {
1192 int d = in.get();
1193 if (d != EOF && csdigit(d)) {
1194 in.unget(d);
1195 type = DATA_INPUT_LINE;
1197 else {
1198 in.unget(d);
1199 type = TROFF_INPUT_LINE;
1202 else if (c == '_' || c == '=') {
1203 int d = in.get();
1204 if (d == '\n') {
1205 if (c == '_')
1206 type = SINGLE_HLINE;
1207 else
1208 type = DOUBLE_HLINE;
1210 else {
1211 in.unget(d);
1212 type = DATA_INPUT_LINE;
1215 else {
1216 type = DATA_INPUT_LINE;
1218 switch (type) {
1219 case DATA_INPUT_LINE:
1221 string input_entry;
1222 if (format_index >= f->nrows)
1223 format_index = f->nrows - 1;
1224 // A format row that is all lines doesn't use up a data line.
1225 while (format_index < f->nrows - 1) {
1226 int c;
1227 for (c = 0; c < ncolumns; c++) {
1228 entry_format *e = f->entry[format_index] + c;
1229 if (e->type != FORMAT_HLINE
1230 && e->type != FORMAT_DOUBLE_HLINE
1231 // Unfortunately tbl treats a span as needing data.
1232 // && e->type != FORMAT_SPAN
1234 break;
1236 if (c < ncolumns)
1237 break;
1238 for (c = 0; c < ncolumns; c++)
1239 tbl->add_entry(current_row, c, input_entry,
1240 f->entry[format_index] + c, current_filename,
1241 current_lineno);
1242 tbl->add_vlines(current_row, f->vline[format_index]);
1243 format_index++;
1244 current_row++;
1246 entry_format *line_format = f->entry[format_index];
1247 int col = 0;
1248 int row_comment = 0;
1249 for (;;) {
1250 if (c == tab_char || c == '\n') {
1251 int ln = current_lineno;
1252 if (c == '\n')
1253 --ln;
1254 if ((opt->flags & table::NOSPACES))
1255 input_entry.remove_spaces();
1256 while (col < ncolumns
1257 && line_format[col].type == FORMAT_SPAN) {
1258 tbl->add_entry(current_row, col, "", &line_format[col],
1259 current_filename, ln);
1260 col++;
1262 if (c == '\n' && input_entry.length() == 2
1263 && input_entry[0] == 'T' && input_entry[1] == '{') {
1264 input_entry = "";
1265 ln++;
1266 enum {
1267 START, MIDDLE, GOT_T, GOT_RIGHT_BRACE, GOT_DOT,
1268 GOT_l, GOT_lf, END
1269 } state = START;
1270 while (state != END) {
1271 c = in.get();
1272 if (c == EOF)
1273 break;
1274 switch (state) {
1275 case START:
1276 if (c == 'T')
1277 state = GOT_T;
1278 else if (c == '.')
1279 state = GOT_DOT;
1280 else {
1281 input_entry += c;
1282 if (c != '\n')
1283 state = MIDDLE;
1285 break;
1286 case GOT_T:
1287 if (c == '}')
1288 state = GOT_RIGHT_BRACE;
1289 else {
1290 input_entry += 'T';
1291 input_entry += c;
1292 state = c == '\n' ? START : MIDDLE;
1294 break;
1295 case GOT_DOT:
1296 if (c == 'l')
1297 state = GOT_l;
1298 else {
1299 input_entry += '.';
1300 input_entry += c;
1301 state = c == '\n' ? START : MIDDLE;
1303 break;
1304 case GOT_l:
1305 if (c == 'f')
1306 state = GOT_lf;
1307 else {
1308 input_entry += ".l";
1309 input_entry += c;
1310 state = c == '\n' ? START : MIDDLE;
1312 break;
1313 case GOT_lf:
1314 if (c == ' ' || c == '\n' || compatible_flag) {
1315 string args;
1316 input_entry += ".lf";
1317 while (c != EOF) {
1318 args += c;
1319 if (c == '\n')
1320 break;
1321 c = in.get();
1323 args += '\0';
1324 interpret_lf_args(args.contents());
1325 // remove the '\0'
1326 args.set_length(args.length() - 1);
1327 input_entry += args;
1328 state = START;
1330 else {
1331 input_entry += ".lf";
1332 input_entry += c;
1333 state = MIDDLE;
1335 break;
1336 case GOT_RIGHT_BRACE:
1337 if (c == '\n' || c == tab_char)
1338 state = END;
1339 else {
1340 input_entry += 'T';
1341 input_entry += '}';
1342 input_entry += c;
1343 state = c == '\n' ? START : MIDDLE;
1345 break;
1346 case MIDDLE:
1347 if (c == '\n')
1348 state = START;
1349 input_entry += c;
1350 break;
1351 case END:
1352 default:
1353 assert(0);
1356 if (c == EOF) {
1357 error("end of data in middle of text block");
1358 give_up = 1;
1359 break;
1362 if (col >= ncolumns) {
1363 if (!input_entry.empty()) {
1364 if (input_entry.length() >= 2
1365 && input_entry[0] == '\\'
1366 && input_entry[1] == '"')
1367 row_comment = 1;
1368 else if (!row_comment) {
1369 if (c == '\n')
1370 in.unget(c);
1371 input_entry += '\0';
1372 error("excess data entry `%1' discarded",
1373 input_entry.contents());
1374 if (c == '\n')
1375 (void)in.get();
1379 else
1380 tbl->add_entry(current_row, col, input_entry,
1381 &line_format[col], current_filename, ln);
1382 col++;
1383 if (c == '\n')
1384 break;
1385 input_entry = "";
1387 else
1388 input_entry += c;
1389 c = in.get();
1390 if (c == EOF)
1391 break;
1393 if (give_up)
1394 break;
1395 input_entry = "";
1396 for (; col < ncolumns; col++)
1397 tbl->add_entry(current_row, col, input_entry, &line_format[col],
1398 current_filename, current_lineno - 1);
1399 tbl->add_vlines(current_row, f->vline[format_index]);
1400 current_row++;
1401 format_index++;
1403 break;
1404 case TROFF_INPUT_LINE:
1406 string line;
1407 int ln = current_lineno;
1408 for (;;) {
1409 line += c;
1410 if (c == '\n')
1411 break;
1412 c = in.get();
1413 if (c == EOF) {
1414 break;
1417 tbl->add_text_line(current_row, line, current_filename, ln);
1418 if (line.length() >= 4
1419 && line[0] == '.' && line[1] == 'T' && line[2] == '&') {
1420 format *newf = process_format(in, opt, f);
1421 if (newf == 0)
1422 give_up = 1;
1423 else
1424 f = newf;
1426 if (line.length() >= 3
1427 && line[0] == '.' && line[1] == 'l' && line[2] == 'f') {
1428 line += '\0';
1429 interpret_lf_args(line.contents() + 3);
1432 break;
1433 case SINGLE_HLINE:
1434 tbl->add_single_hline(current_row);
1435 break;
1436 case DOUBLE_HLINE:
1437 tbl->add_double_hline(current_row);
1438 break;
1439 default:
1440 assert(0);
1442 if (give_up)
1443 break;
1445 if (!give_up && current_row == 0) {
1446 error("no real data");
1447 give_up = 1;
1449 if (give_up) {
1450 delete tbl;
1451 return 0;
1453 // Do this here rather than at the beginning in case continued formats
1454 // change it.
1455 int i;
1456 for (i = 0; i < ncolumns - 1; i++)
1457 if (f->separation[i] >= 0)
1458 tbl->set_column_separation(i, f->separation[i]);
1459 for (i = 0; i < ncolumns; i++)
1460 if (!f->width[i].empty())
1461 tbl->set_minimum_width(i, f->width[i]);
1462 for (i = 0; i < ncolumns; i++)
1463 if (f->equal[i])
1464 tbl->set_equal_column(i);
1465 return tbl;
1468 void process_table(table_input &in)
1470 int c;
1471 options *opt = 0;
1472 format *form = 0;
1473 table *tbl = 0;
1474 if ((opt = process_options(in)) != 0
1475 && (form = process_format(in, opt)) != 0
1476 && (tbl = process_data(in, form, opt)) != 0) {
1477 tbl->print();
1478 delete tbl;
1480 else {
1481 error("giving up on this table");
1482 while ((c = in.get()) != EOF)
1485 delete opt;
1486 delete form;
1487 if (!in.ended())
1488 error("premature end of file");
1491 static void usage(FILE *stream)
1493 fprintf(stream, "usage: %s [ -vC ] [ files... ]\n", program_name);
1496 int main(int argc, char **argv)
1498 program_name = argv[0];
1499 static char stderr_buf[BUFSIZ];
1500 setbuf(stderr, stderr_buf);
1501 int opt;
1502 static const struct option long_options[] = {
1503 { "help", no_argument, 0, CHAR_MAX + 1 },
1504 { "version", no_argument, 0, 'v' },
1505 { NULL, 0, 0, 0 }
1507 while ((opt = getopt_long(argc, argv, "vCT:", long_options, NULL)) != EOF)
1508 switch (opt) {
1509 case 'C':
1510 compatible_flag = 1;
1511 break;
1512 case 'v':
1514 printf("GNU tbl (groff) version %s\n", Version_string);
1515 exit(0);
1516 break;
1518 case 'T':
1519 // I'm sick of getting bug reports from IRIX users
1520 break;
1521 case CHAR_MAX + 1: // --help
1522 usage(stdout);
1523 exit(0);
1524 break;
1525 case '?':
1526 usage(stderr);
1527 exit(1);
1528 break;
1529 default:
1530 assert(0);
1532 printf(".if !\\n(.g .ab GNU tbl requires GNU troff.\n"
1533 ".if !dTS .ds TS\n"
1534 ".if !dTE .ds TE\n");
1535 if (argc > optind) {
1536 for (int i = optind; i < argc; i++)
1537 if (argv[i][0] == '-' && argv[i][1] == '\0') {
1538 current_filename = "-";
1539 current_lineno = 1;
1540 printf(".lf 1 -\n");
1541 process_input_file(stdin);
1543 else {
1544 errno = 0;
1545 FILE *fp = fopen(argv[i], "r");
1546 if (fp == 0) {
1547 current_lineno = -1;
1548 error("can't open `%1': %2", argv[i], strerror(errno));
1550 else {
1551 current_lineno = 1;
1552 current_filename = argv[i];
1553 printf(".lf 1 %s\n", current_filename);
1554 process_input_file(fp);
1558 else {
1559 current_filename = "-";
1560 current_lineno = 1;
1561 printf(".lf 1 -\n");
1562 process_input_file(stdin);
1564 if (ferror(stdout) || fflush(stdout) < 0)
1565 fatal("output error");
1566 return 0;