A try at the new rule for block column allocation is now enabled by
[s-roff.git] / src / preproc / tbl / main.cpp
blob381815fc1dcd79251e3d7b61a039157ed646822f
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003, 2004, 2005
3 Free Software Foundation, Inc.
4 Written by James Clark (jjc@jclark.com)
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, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, 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 int compatible_flag = 0;
31 class table_input {
32 FILE *fp;
33 enum { START, MIDDLE,
34 REREAD_T, REREAD_TE, REREAD_E,
35 LEADER_1, LEADER_2, LEADER_3, LEADER_4,
36 END, ERROR } state;
37 string unget_stack;
38 public:
39 table_input(FILE *);
40 int get();
41 int ended() { return unget_stack.empty() && state == END; }
42 void unget(char);
45 table_input::table_input(FILE *p)
46 : fp(p), state(START)
50 void table_input::unget(char c)
52 assert(c != '\0');
53 unget_stack += c;
54 if (c == '\n')
55 current_lineno--;
58 int table_input::get()
60 int len = unget_stack.length();
61 if (len != 0) {
62 unsigned char c = unget_stack[len - 1];
63 unget_stack.set_length(len - 1);
64 if (c == '\n')
65 current_lineno++;
66 return c;
68 int c;
69 for (;;) {
70 switch (state) {
71 case START:
72 if ((c = getc(fp)) == '.') {
73 if ((c = getc(fp)) == 'T') {
74 if ((c = getc(fp)) == 'E') {
75 if (compatible_flag) {
76 state = END;
77 return EOF;
79 else {
80 c = getc(fp);
81 if (c != EOF)
82 ungetc(c, fp);
83 if (c == EOF || c == ' ' || c == '\n') {
84 state = END;
85 return EOF;
87 state = REREAD_TE;
88 return '.';
91 else {
92 if (c != EOF)
93 ungetc(c, fp);
94 state = REREAD_T;
95 return '.';
98 else {
99 if (c != EOF)
100 ungetc(c, fp);
101 state = MIDDLE;
102 return '.';
105 else if (c == EOF) {
106 state = ERROR;
107 return EOF;
109 else {
110 if (c == '\n')
111 current_lineno++;
112 else {
113 state = MIDDLE;
114 if (c == '\0') {
115 error("invalid input character code 0");
116 break;
119 return c;
121 break;
122 case MIDDLE:
123 // handle line continuation and uninterpreted leader character
124 if ((c = getc(fp)) == '\\') {
125 c = getc(fp);
126 if (c == '\n')
127 c = getc(fp); // perhaps state ought to be START now
128 else if (c == 'a' && compatible_flag) {
129 state = LEADER_1;
130 return '\\';
132 else {
133 if (c != EOF)
134 ungetc(c, fp);
135 c = '\\';
138 if (c == EOF) {
139 state = ERROR;
140 return EOF;
142 else {
143 if (c == '\n') {
144 state = START;
145 current_lineno++;
147 else if (c == '\0') {
148 error("invalid input character code 0");
149 break;
151 return c;
153 case REREAD_T:
154 state = MIDDLE;
155 return 'T';
156 case REREAD_TE:
157 state = REREAD_E;
158 return 'T';
159 case REREAD_E:
160 state = MIDDLE;
161 return 'E';
162 case LEADER_1:
163 state = LEADER_2;
164 return '*';
165 case LEADER_2:
166 state = LEADER_3;
167 return '(';
168 case LEADER_3:
169 state = LEADER_4;
170 return PREFIX_CHAR;
171 case LEADER_4:
172 state = MIDDLE;
173 return LEADER_CHAR;
174 case END:
175 case ERROR:
176 return EOF;
181 void process_input_file(FILE *);
182 void process_table(table_input &in);
184 void process_input_file(FILE *fp)
186 enum { START, MIDDLE, HAD_DOT, HAD_T, HAD_TS, HAD_l, HAD_lf } state;
187 state = START;
188 int c;
189 while ((c = getc(fp)) != EOF)
190 switch (state) {
191 case START:
192 if (c == '.')
193 state = HAD_DOT;
194 else {
195 if (c == '\n')
196 current_lineno++;
197 else
198 state = MIDDLE;
199 putchar(c);
201 break;
202 case MIDDLE:
203 if (c == '\n') {
204 current_lineno++;
205 state = START;
207 putchar(c);
208 break;
209 case HAD_DOT:
210 if (c == 'T')
211 state = HAD_T;
212 else if (c == 'l')
213 state = HAD_l;
214 else {
215 putchar('.');
216 putchar(c);
217 if (c == '\n') {
218 current_lineno++;
219 state = START;
221 else
222 state = MIDDLE;
224 break;
225 case HAD_T:
226 if (c == 'S')
227 state = HAD_TS;
228 else {
229 putchar('.');
230 putchar('T');
231 putchar(c);
232 if (c == '\n') {
233 current_lineno++;
234 state = START;
236 else
237 state = MIDDLE;
239 break;
240 case HAD_TS:
241 if (c == ' ' || c == '\n' || compatible_flag) {
242 putchar('.');
243 putchar('T');
244 putchar('S');
245 while (c != '\n') {
246 if (c == EOF) {
247 error("end of file at beginning of table");
248 return;
250 putchar(c);
251 c = getc(fp);
253 putchar('\n');
254 current_lineno++;
256 table_input input(fp);
257 process_table(input);
258 set_troff_location(current_filename, current_lineno);
259 if (input.ended()) {
260 fputs(".TE", stdout);
261 while ((c = getc(fp)) != '\n') {
262 if (c == EOF) {
263 putchar('\n');
264 return;
266 putchar(c);
268 putchar('\n');
269 current_lineno++;
272 state = START;
274 else {
275 fputs(".TS", stdout);
276 putchar(c);
277 state = MIDDLE;
279 break;
280 case HAD_l:
281 if (c == 'f')
282 state = HAD_lf;
283 else {
284 putchar('.');
285 putchar('l');
286 putchar(c);
287 if (c == '\n') {
288 current_lineno++;
289 state = START;
291 else
292 state = MIDDLE;
294 break;
295 case HAD_lf:
296 if (c == ' ' || c == '\n' || compatible_flag) {
297 string line;
298 while (c != EOF) {
299 line += c;
300 if (c == '\n') {
301 current_lineno++;
302 break;
304 c = getc(fp);
306 line += '\0';
307 interpret_lf_args(line.contents());
308 printf(".lf%s", line.contents());
309 state = START;
311 else {
312 fputs(".lf", stdout);
313 putchar(c);
314 state = MIDDLE;
316 break;
317 default:
318 assert(0);
320 switch(state) {
321 case START:
322 break;
323 case MIDDLE:
324 putchar('\n');
325 break;
326 case HAD_DOT:
327 fputs(".\n", stdout);
328 break;
329 case HAD_l:
330 fputs(".l\n", stdout);
331 break;
332 case HAD_T:
333 fputs(".T\n", stdout);
334 break;
335 case HAD_lf:
336 fputs(".lf\n", stdout);
337 break;
338 case HAD_TS:
339 fputs(".TS\n", stdout);
340 break;
342 if (fp != stdin)
343 fclose(fp);
346 struct options {
347 unsigned flags;
348 int linesize;
349 char delim[2];
350 char tab_char;
351 char decimal_point_char;
353 options();
356 options::options()
357 : flags(0), linesize(0), tab_char('\t'), decimal_point_char('.')
359 delim[0] = delim[1] = '\0';
362 // Return non-zero if p and q are the same ignoring case.
364 int strieq(const char *p, const char *q)
366 for (; cmlower(*p) == cmlower(*q); p++, q++)
367 if (*p == '\0')
368 return 1;
369 return 0;
372 // return 0 if we should give up in this table
374 options *process_options(table_input &in)
376 options *opt = new options;
377 string line;
378 int level = 0;
379 for (;;) {
380 int c = in.get();
381 if (c == EOF) {
382 int i = line.length();
383 while (--i >= 0)
384 in.unget(line[i]);
385 return opt;
387 if (c == '\n') {
388 in.unget(c);
389 int i = line.length();
390 while (--i >= 0)
391 in.unget(line[i]);
392 return opt;
394 else if (c == '(')
395 level++;
396 else if (c == ')')
397 level--;
398 else if (c == ';' && level == 0) {
399 line += '\0';
400 break;
402 line += c;
404 if (line.empty())
405 return opt;
406 char *p = &line[0];
407 for (;;) {
408 while (!csalpha(*p) && *p != '\0')
409 p++;
410 if (*p == '\0')
411 break;
412 char *q = p;
413 while (csalpha(*q))
414 q++;
415 char *arg = 0;
416 if (*q != '(' && *q != '\0')
417 *q++ = '\0';
418 while (csspace(*q))
419 q++;
420 if (*q == '(') {
421 *q++ = '\0';
422 arg = q;
423 while (*q != ')' && *q != '\0')
424 q++;
425 if (*q == '\0')
426 error("missing `)'");
427 else
428 *q++ = '\0';
430 if (*p == '\0') {
431 if (arg)
432 error("argument without option");
434 else if (strieq(p, "tab")) {
435 if (!arg)
436 error("`tab' option requires argument in parentheses");
437 else {
438 if (arg[0] == '\0' || arg[1] != '\0')
439 error("argument to `tab' option must be a single character");
440 else
441 opt->tab_char = arg[0];
444 else if (strieq(p, "linesize")) {
445 if (!arg)
446 error("`linesize' option requires argument in parentheses");
447 else {
448 if (sscanf(arg, "%d", &opt->linesize) != 1)
449 error("bad linesize `%s'", arg);
450 else if (opt->linesize <= 0) {
451 error("linesize must be positive");
452 opt->linesize = 0;
456 else if (strieq(p, "delim")) {
457 if (!arg)
458 error("`delim' option requires argument in parentheses");
459 else if (arg[0] == '\0' || arg[1] == '\0' || arg[2] != '\0')
460 error("argument to `delim' option must be two characters");
461 else {
462 opt->delim[0] = arg[0];
463 opt->delim[1] = arg[1];
466 else if (strieq(p, "center") || strieq(p, "centre")) {
467 if (arg)
468 error("`center' option does not take an argument");
469 opt->flags |= table::CENTER;
471 else if (strieq(p, "expand")) {
472 if (arg)
473 error("`expand' option does not take an argument");
474 opt->flags |= table::EXPAND;
476 else if (strieq(p, "box") || strieq(p, "frame")) {
477 if (arg)
478 error("`box' option does not take an argument");
479 opt->flags |= table::BOX;
481 else if (strieq(p, "doublebox") || strieq(p, "doubleframe")) {
482 if (arg)
483 error("`doublebox' option does not take an argument");
484 opt->flags |= table::DOUBLEBOX;
486 else if (strieq(p, "allbox")) {
487 if (arg)
488 error("`allbox' option does not take an argument");
489 opt->flags |= table::ALLBOX;
491 else if (strieq(p, "nokeep")) {
492 if (arg)
493 error("`nokeep' option does not take an argument");
494 opt->flags |= table::NOKEEP;
496 else if (strieq(p, "nospaces")) {
497 if (arg)
498 error("`nospaces' option does not take an argument");
499 opt->flags |= table::NOSPACES;
501 else if (strieq(p, "decimalpoint")) {
502 if (!arg)
503 error("`decimalpoint' option requires argument in parentheses");
504 else {
505 if (arg[0] == '\0' || arg[1] != '\0')
506 error("argument to `decimalpoint' option must be a single character");
507 else
508 opt->decimal_point_char = arg[0];
511 else if (strieq(p, "experimental")) {
512 opt->flags |= table::EXPERIMENTAL;
514 else {
515 error("unrecognised global option `%1'", p);
516 // delete opt;
517 // return 0;
519 p = q;
521 return opt;
524 entry_modifier::entry_modifier()
525 : vertical_alignment(CENTER), zero_width(0), stagger(0)
527 vertical_spacing.inc = vertical_spacing.val = 0;
528 point_size.inc = point_size.val = 0;
531 entry_modifier::~entry_modifier()
535 entry_format::entry_format() : type(FORMAT_LEFT)
539 entry_format::entry_format(format_type t) : type(t)
543 void entry_format::debug_print() const
545 switch (type) {
546 case FORMAT_LEFT:
547 putc('l', stderr);
548 break;
549 case FORMAT_CENTER:
550 putc('c', stderr);
551 break;
552 case FORMAT_RIGHT:
553 putc('r', stderr);
554 break;
555 case FORMAT_NUMERIC:
556 putc('n', stderr);
557 break;
558 case FORMAT_ALPHABETIC:
559 putc('a', stderr);
560 break;
561 case FORMAT_SPAN:
562 putc('s', stderr);
563 break;
564 case FORMAT_VSPAN:
565 putc('^', stderr);
566 break;
567 case FORMAT_HLINE:
568 putc('_', stderr);
569 break;
570 case FORMAT_DOUBLE_HLINE:
571 putc('=', stderr);
572 break;
573 default:
574 assert(0);
575 break;
577 if (point_size.val != 0) {
578 putc('p', stderr);
579 if (point_size.inc > 0)
580 putc('+', stderr);
581 else if (point_size.inc < 0)
582 putc('-', stderr);
583 fprintf(stderr, "%d ", point_size.val);
585 if (vertical_spacing.val != 0) {
586 putc('v', stderr);
587 if (vertical_spacing.inc > 0)
588 putc('+', stderr);
589 else if (vertical_spacing.inc < 0)
590 putc('-', stderr);
591 fprintf(stderr, "%d ", vertical_spacing.val);
593 if (!font.empty()) {
594 putc('f', stderr);
595 put_string(font, stderr);
596 putc(' ', stderr);
598 if (!macro.empty()) {
599 putc('m', stderr);
600 put_string(macro, stderr);
601 putc(' ', stderr);
603 switch (vertical_alignment) {
604 case entry_modifier::CENTER:
605 break;
606 case entry_modifier::TOP:
607 putc('t', stderr);
608 break;
609 case entry_modifier::BOTTOM:
610 putc('d', stderr);
611 break;
613 if (zero_width)
614 putc('z', stderr);
615 if (stagger)
616 putc('u', stderr);
619 struct format {
620 int nrows;
621 int ncolumns;
622 int *separation;
623 string *width;
624 char *equal;
625 entry_format **entry;
626 char **vline;
628 format(int nr, int nc);
629 ~format();
630 void add_rows(int n);
633 format::format(int nr, int nc) : nrows(nr), ncolumns(nc)
635 int i;
636 separation = ncolumns > 1 ? new int[ncolumns - 1] : 0;
637 for (i = 0; i < ncolumns-1; i++)
638 separation[i] = -1;
639 width = new string[ncolumns];
640 equal = new char[ncolumns];
641 for (i = 0; i < ncolumns; i++)
642 equal[i] = 0;
643 entry = new entry_format *[nrows];
644 for (i = 0; i < nrows; i++)
645 entry[i] = new entry_format[ncolumns];
646 vline = new char*[nrows];
647 for (i = 0; i < nrows; i++) {
648 vline[i] = new char[ncolumns+1];
649 for (int j = 0; j < ncolumns+1; j++)
650 vline[i][j] = 0;
654 void format::add_rows(int n)
656 int i;
657 char **old_vline = vline;
658 vline = new char*[nrows + n];
659 for (i = 0; i < nrows; i++)
660 vline[i] = old_vline[i];
661 a_delete old_vline;
662 for (i = 0; i < n; i++) {
663 vline[nrows + i] = new char[ncolumns + 1];
664 for (int j = 0; j < ncolumns + 1; j++)
665 vline[nrows + i][j] = 0;
667 entry_format **old_entry = entry;
668 entry = new entry_format *[nrows + n];
669 for (i = 0; i < nrows; i++)
670 entry[i] = old_entry[i];
671 a_delete old_entry;
672 for (i = 0; i < n; i++)
673 entry[nrows + i] = new entry_format[ncolumns];
674 nrows += n;
677 format::~format()
679 a_delete separation;
680 ad_delete(ncolumns) width;
681 a_delete equal;
682 for (int i = 0; i < nrows; i++) {
683 a_delete vline[i];
684 ad_delete(ncolumns) entry[i];
686 a_delete vline;
687 a_delete entry;
690 struct input_entry_format : public entry_format {
691 input_entry_format *next;
692 string width;
693 int separation;
694 int vline;
695 int pre_vline;
696 int last_column;
697 int equal;
698 input_entry_format(format_type, input_entry_format * = 0);
699 ~input_entry_format();
700 void debug_print();
703 input_entry_format::input_entry_format(format_type t, input_entry_format *p)
704 : entry_format(t), next(p)
706 separation = -1;
707 last_column = 0;
708 vline = 0;
709 pre_vline = 0;
710 equal = 0;
713 input_entry_format::~input_entry_format()
717 void free_input_entry_format_list(input_entry_format *list)
719 while (list) {
720 input_entry_format *tem = list;
721 list = list->next;
722 delete tem;
726 void input_entry_format::debug_print()
728 int i;
729 for (i = 0; i < pre_vline; i++)
730 putc('|', stderr);
731 entry_format::debug_print();
732 if (!width.empty()) {
733 putc('w', stderr);
734 putc('(', stderr);
735 put_string(width, stderr);
736 putc(')', stderr);
738 if (equal)
739 putc('e', stderr);
740 if (separation >= 0)
741 fprintf(stderr, "%d", separation);
742 for (i = 0; i < vline; i++)
743 putc('|', stderr);
744 if (last_column)
745 putc(',', stderr);
748 // Return zero if we should give up on this table.
749 // If this is a continuation format line, current_format will be the current
750 // format line.
752 format *process_format(table_input &in, options *opt,
753 format *current_format = 0)
755 input_entry_format *list = 0;
756 int c = in.get();
757 for (;;) {
758 int pre_vline = 0;
759 int got_format = 0;
760 int got_period = 0;
761 format_type t = FORMAT_LEFT;
762 for (;;) {
763 if (c == EOF) {
764 error("end of input while processing format");
765 free_input_entry_format_list(list);
766 return 0;
768 switch (c) {
769 case 'n':
770 case 'N':
771 t = FORMAT_NUMERIC;
772 got_format = 1;
773 break;
774 case 'a':
775 case 'A':
776 got_format = 1;
777 t = FORMAT_ALPHABETIC;
778 break;
779 case 'c':
780 case 'C':
781 got_format = 1;
782 t = FORMAT_CENTER;
783 break;
784 case 'l':
785 case 'L':
786 got_format = 1;
787 t = FORMAT_LEFT;
788 break;
789 case 'r':
790 case 'R':
791 got_format = 1;
792 t = FORMAT_RIGHT;
793 break;
794 case 's':
795 case 'S':
796 got_format = 1;
797 t = FORMAT_SPAN;
798 break;
799 case '^':
800 got_format = 1;
801 t = FORMAT_VSPAN;
802 break;
803 case '_':
804 case '-': // tbl also accepts this
805 got_format = 1;
806 t = FORMAT_HLINE;
807 break;
808 case '=':
809 got_format = 1;
810 t = FORMAT_DOUBLE_HLINE;
811 break;
812 case '.':
813 got_period = 1;
814 break;
815 case '|':
816 pre_vline++;
817 break;
818 case ' ':
819 case '\t':
820 case '\n':
821 break;
822 default:
823 if (c == opt->tab_char)
824 break;
825 error("unrecognised format `%1'", char(c));
826 free_input_entry_format_list(list);
827 return 0;
829 if (got_period)
830 break;
831 c = in.get();
832 if (got_format)
833 break;
835 if (got_period)
836 break;
837 list = new input_entry_format(t, list);
838 if (pre_vline)
839 list->pre_vline = pre_vline;
840 int success = 1;
841 do {
842 switch (c) {
843 case 't':
844 case 'T':
845 c = in.get();
846 list->vertical_alignment = entry_modifier::TOP;
847 break;
848 case 'd':
849 case 'D':
850 c = in.get();
851 list->vertical_alignment = entry_modifier::BOTTOM;
852 break;
853 case 'u':
854 case 'U':
855 c = in.get();
856 list->stagger = 1;
857 break;
858 case 'z':
859 case 'Z':
860 c = in.get();
861 list->zero_width = 1;
862 break;
863 case '0':
864 case '1':
865 case '2':
866 case '3':
867 case '4':
868 case '5':
869 case '6':
870 case '7':
871 case '8':
872 case '9':
874 int w = 0;
875 do {
876 w = w*10 + (c - '0');
877 c = in.get();
878 } while (c != EOF && csdigit(c));
879 list->separation = w;
881 break;
882 case 'f':
883 case 'F':
884 do {
885 c = in.get();
886 } while (c == ' ' || c == '\t');
887 if (c == EOF) {
888 error("missing font name");
889 break;
891 if (c == '(') {
892 for (;;) {
893 c = in.get();
894 if (c == EOF || c == ' ' || c == '\t') {
895 error("missing `)'");
896 break;
898 if (c == ')') {
899 c = in.get();
900 break;
902 list->font += char(c);
905 else {
906 list->font = c;
907 char cc = c;
908 c = in.get();
909 if (!csdigit(cc)
910 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
911 list->font += char(c);
912 c = in.get();
915 break;
916 case 'x':
917 case 'X':
918 do {
919 c = in.get();
920 } while (c == ' ' || c == '\t');
921 if (c == EOF) {
922 error("missing macro name");
923 break;
925 if (c == '(') {
926 for (;;) {
927 c = in.get();
928 if (c == EOF || c == ' ' || c == '\t') {
929 error("missing `)'");
930 break;
932 if (c == ')') {
933 c = in.get();
934 break;
936 list->macro += char(c);
939 else {
940 list->macro = c;
941 char cc = c;
942 c = in.get();
943 if (!csdigit(cc)
944 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
945 list->macro += char(c);
946 c = in.get();
949 break;
950 case 'v':
951 case 'V':
952 c = in.get();
953 list->vertical_spacing.val = 0;
954 list->vertical_spacing.inc = 0;
955 if (c == '+' || c == '-') {
956 list->vertical_spacing.inc = (c == '+' ? 1 : -1);
957 c = in.get();
959 if (c == EOF || !csdigit(c)) {
960 error("`v' modifier must be followed by number");
961 list->vertical_spacing.inc = 0;
963 else {
964 do {
965 list->vertical_spacing.val *= 10;
966 list->vertical_spacing.val += c - '0';
967 c = in.get();
968 } while (c != EOF && csdigit(c));
970 if (list->vertical_spacing.val > MAX_VERTICAL_SPACING
971 || list->vertical_spacing.val < -MAX_VERTICAL_SPACING) {
972 error("unreasonable vertical spacing");
973 list->vertical_spacing.val = 0;
974 list->vertical_spacing.inc = 0;
976 break;
977 case 'p':
978 case 'P':
979 c = in.get();
980 list->point_size.val = 0;
981 list->point_size.inc = 0;
982 if (c == '+' || c == '-') {
983 list->point_size.inc = (c == '+' ? 1 : -1);
984 c = in.get();
986 if (c == EOF || !csdigit(c)) {
987 error("`p' modifier must be followed by number");
988 list->point_size.inc = 0;
990 else {
991 do {
992 list->point_size.val *= 10;
993 list->point_size.val += c - '0';
994 c = in.get();
995 } while (c != EOF && csdigit(c));
997 if (list->point_size.val > MAX_POINT_SIZE
998 || list->point_size.val < -MAX_POINT_SIZE) {
999 error("unreasonable point size");
1000 list->point_size.val = 0;
1001 list->point_size.inc = 0;
1003 break;
1004 case 'w':
1005 case 'W':
1006 c = in.get();
1007 while (c == ' ' || c == '\t')
1008 c = in.get();
1009 if (c == '(') {
1010 list->width = "";
1011 c = in.get();
1012 while (c != ')') {
1013 if (c == EOF || c == '\n') {
1014 error("missing `)'");
1015 free_input_entry_format_list(list);
1016 return 0;
1018 list->width += c;
1019 c = in.get();
1021 c = in.get();
1023 else {
1024 if (c == '+' || c == '-') {
1025 list->width = char(c);
1026 c = in.get();
1028 else
1029 list->width = "";
1030 if (c == EOF || !csdigit(c))
1031 error("bad argument for `w' modifier");
1032 else {
1033 do {
1034 list->width += char(c);
1035 c = in.get();
1036 } while (c != EOF && csdigit(c));
1039 break;
1040 case 'e':
1041 case 'E':
1042 c = in.get();
1043 list->equal++;
1044 break;
1045 case '|':
1046 c = in.get();
1047 list->vline++;
1048 break;
1049 case 'B':
1050 case 'b':
1051 c = in.get();
1052 list->font = "B";
1053 break;
1054 case 'I':
1055 case 'i':
1056 c = in.get();
1057 list->font = "I";
1058 break;
1059 case ' ':
1060 case '\t':
1061 c = in.get();
1062 break;
1063 default:
1064 if (c == opt->tab_char)
1065 c = in.get();
1066 else
1067 success = 0;
1068 break;
1070 } while (success);
1071 if (list->vline > 2) {
1072 list->vline = 2;
1073 error("more than 2 vertical bars between key letters");
1075 if (c == '\n' || c == ',') {
1076 c = in.get();
1077 list->last_column = 1;
1080 if (c == '.') {
1081 do {
1082 c = in.get();
1083 } while (c == ' ' || c == '\t');
1084 if (c != '\n') {
1085 error("`.' not last character on line");
1086 free_input_entry_format_list(list);
1087 return 0;
1090 if (!list) {
1091 error("no format");
1092 free_input_entry_format_list(list);
1093 return 0;
1095 list->last_column = 1;
1096 // now reverse the list so that the first row is at the beginning
1097 input_entry_format *rev = 0;
1098 while (list != 0) {
1099 input_entry_format *tem = list->next;
1100 list->next = rev;
1101 rev = list;
1102 list = tem;
1104 list = rev;
1105 input_entry_format *tem;
1107 #if 0
1108 for (tem = list; tem; tem = tem->next)
1109 tem->debug_print();
1110 putc('\n', stderr);
1111 #endif
1112 // compute number of columns and rows
1113 int ncolumns = 0;
1114 int nrows = 0;
1115 int col = 0;
1116 for (tem = list; tem; tem = tem->next) {
1117 if (tem->last_column) {
1118 if (col >= ncolumns)
1119 ncolumns = col + 1;
1120 col = 0;
1121 nrows++;
1123 else
1124 col++;
1126 int row;
1127 format *f;
1128 if (current_format) {
1129 if (ncolumns > current_format->ncolumns) {
1130 error("cannot increase the number of columns in a continued format");
1131 free_input_entry_format_list(list);
1132 return 0;
1134 f = current_format;
1135 row = f->nrows;
1136 f->add_rows(nrows);
1138 else {
1139 f = new format(nrows, ncolumns);
1140 row = 0;
1142 col = 0;
1143 for (tem = list; tem; tem = tem->next) {
1144 f->entry[row][col] = *tem;
1145 if (col < ncolumns-1) {
1146 // use the greatest separation
1147 if (tem->separation > f->separation[col]) {
1148 if (current_format)
1149 error("cannot change column separation in continued format");
1150 else
1151 f->separation[col] = tem->separation;
1154 else if (tem->separation >= 0)
1155 error("column separation specified for last column");
1156 if (tem->equal && !f->equal[col]) {
1157 if (current_format)
1158 error("cannot change which columns are equal in continued format");
1159 else
1160 f->equal[col] = 1;
1162 if (!tem->width.empty()) {
1163 // use the last width
1164 if (!f->width[col].empty() && f->width[col] != tem->width)
1165 error("multiple widths for column %1", col+1);
1166 f->width[col] = tem->width;
1168 if (tem->pre_vline) {
1169 assert(col == 0);
1170 f->vline[row][col] = tem->pre_vline;
1172 f->vline[row][col+1] = tem->vline;
1173 if (tem->last_column) {
1174 row++;
1175 col = 0;
1177 else
1178 col++;
1180 free_input_entry_format_list(list);
1181 for (col = 0; col < ncolumns; col++) {
1182 entry_format *e = f->entry[f->nrows-1] + col;
1183 if (e->type != FORMAT_HLINE
1184 && e->type != FORMAT_DOUBLE_HLINE
1185 && e->type != FORMAT_SPAN)
1186 break;
1188 if (col >= ncolumns) {
1189 error("last row of format is all lines");
1190 delete f;
1191 return 0;
1193 return f;
1196 table *process_data(table_input &in, format *f, options *opt)
1198 char tab_char = opt->tab_char;
1199 int ncolumns = f->ncolumns;
1200 int current_row = 0;
1201 int format_index = 0;
1202 int give_up = 0;
1203 enum { DATA_INPUT_LINE, TROFF_INPUT_LINE, SINGLE_HLINE, DOUBLE_HLINE } type;
1204 table *tbl = new table(ncolumns, opt->flags, opt->linesize,
1205 opt->decimal_point_char);
1206 if (opt->delim[0] != '\0')
1207 tbl->set_delim(opt->delim[0], opt->delim[1]);
1208 for (;;) {
1209 // first determine what type of line this is
1210 int c = in.get();
1211 if (c == EOF)
1212 break;
1213 if (c == '.') {
1214 int d = in.get();
1215 if (d != EOF && csdigit(d)) {
1216 in.unget(d);
1217 type = DATA_INPUT_LINE;
1219 else {
1220 in.unget(d);
1221 type = TROFF_INPUT_LINE;
1224 else if (c == '_' || c == '=') {
1225 int d = in.get();
1226 if (d == '\n') {
1227 if (c == '_')
1228 type = SINGLE_HLINE;
1229 else
1230 type = DOUBLE_HLINE;
1232 else {
1233 in.unget(d);
1234 type = DATA_INPUT_LINE;
1237 else {
1238 type = DATA_INPUT_LINE;
1240 switch (type) {
1241 case DATA_INPUT_LINE:
1243 string input_entry;
1244 if (format_index >= f->nrows)
1245 format_index = f->nrows - 1;
1246 // A format row that is all lines doesn't use up a data line.
1247 while (format_index < f->nrows - 1) {
1248 int cnt;
1249 for (cnt = 0; cnt < ncolumns; cnt++) {
1250 entry_format *e = f->entry[format_index] + cnt;
1251 if (e->type != FORMAT_HLINE
1252 && e->type != FORMAT_DOUBLE_HLINE
1253 // Unfortunately tbl treats a span as needing data.
1254 // && e->type != FORMAT_SPAN
1256 break;
1258 if (cnt < ncolumns)
1259 break;
1260 for (cnt = 0; cnt < ncolumns; cnt++)
1261 tbl->add_entry(current_row, cnt, input_entry,
1262 f->entry[format_index] + cnt, current_filename,
1263 current_lineno);
1264 tbl->add_vlines(current_row, f->vline[format_index]);
1265 format_index++;
1266 current_row++;
1268 entry_format *line_format = f->entry[format_index];
1269 int col = 0;
1270 int row_comment = 0;
1271 for (;;) {
1272 if (c == tab_char || c == '\n') {
1273 int ln = current_lineno;
1274 if (c == '\n')
1275 --ln;
1276 if ((opt->flags & table::NOSPACES))
1277 input_entry.remove_spaces();
1278 while (col < ncolumns
1279 && line_format[col].type == FORMAT_SPAN) {
1280 tbl->add_entry(current_row, col, "", &line_format[col],
1281 current_filename, ln);
1282 col++;
1284 if (c == '\n' && input_entry.length() == 2
1285 && input_entry[0] == 'T' && input_entry[1] == '{') {
1286 input_entry = "";
1287 ln++;
1288 enum {
1289 START, MIDDLE, GOT_T, GOT_RIGHT_BRACE, GOT_DOT,
1290 GOT_l, GOT_lf, END
1291 } state = START;
1292 while (state != END) {
1293 c = in.get();
1294 if (c == EOF)
1295 break;
1296 switch (state) {
1297 case START:
1298 if (c == 'T')
1299 state = GOT_T;
1300 else if (c == '.')
1301 state = GOT_DOT;
1302 else {
1303 input_entry += c;
1304 if (c != '\n')
1305 state = MIDDLE;
1307 break;
1308 case GOT_T:
1309 if (c == '}')
1310 state = GOT_RIGHT_BRACE;
1311 else {
1312 input_entry += 'T';
1313 input_entry += c;
1314 state = c == '\n' ? START : MIDDLE;
1316 break;
1317 case GOT_DOT:
1318 if (c == 'l')
1319 state = GOT_l;
1320 else {
1321 input_entry += '.';
1322 input_entry += c;
1323 state = c == '\n' ? START : MIDDLE;
1325 break;
1326 case GOT_l:
1327 if (c == 'f')
1328 state = GOT_lf;
1329 else {
1330 input_entry += ".l";
1331 input_entry += c;
1332 state = c == '\n' ? START : MIDDLE;
1334 break;
1335 case GOT_lf:
1336 if (c == ' ' || c == '\n' || compatible_flag) {
1337 string args;
1338 input_entry += ".lf";
1339 while (c != EOF) {
1340 args += c;
1341 if (c == '\n')
1342 break;
1343 c = in.get();
1345 args += '\0';
1346 interpret_lf_args(args.contents());
1347 // remove the '\0'
1348 args.set_length(args.length() - 1);
1349 input_entry += args;
1350 state = START;
1352 else {
1353 input_entry += ".lf";
1354 input_entry += c;
1355 state = MIDDLE;
1357 break;
1358 case GOT_RIGHT_BRACE:
1359 if ((opt->flags & table::NOSPACES)) {
1360 while (c == ' ')
1361 c = in.get();
1362 if (c == EOF)
1363 break;
1365 if (c == '\n' || c == tab_char)
1366 state = END;
1367 else {
1368 input_entry += 'T';
1369 input_entry += '}';
1370 input_entry += c;
1371 state = MIDDLE;
1373 break;
1374 case MIDDLE:
1375 if (c == '\n')
1376 state = START;
1377 input_entry += c;
1378 break;
1379 case END:
1380 default:
1381 assert(0);
1384 if (c == EOF) {
1385 error("end of data in middle of text block");
1386 give_up = 1;
1387 break;
1390 if (col >= ncolumns) {
1391 if (!input_entry.empty()) {
1392 if (input_entry.length() >= 2
1393 && input_entry[0] == '\\'
1394 && input_entry[1] == '"')
1395 row_comment = 1;
1396 else if (!row_comment) {
1397 if (c == '\n')
1398 in.unget(c);
1399 input_entry += '\0';
1400 error("excess data entry `%1' discarded",
1401 input_entry.contents());
1402 if (c == '\n')
1403 (void)in.get();
1407 else
1408 tbl->add_entry(current_row, col, input_entry,
1409 &line_format[col], current_filename, ln);
1410 col++;
1411 if (c == '\n')
1412 break;
1413 input_entry = "";
1415 else
1416 input_entry += c;
1417 c = in.get();
1418 if (c == EOF)
1419 break;
1421 if (give_up)
1422 break;
1423 input_entry = "";
1424 for (; col < ncolumns; col++)
1425 tbl->add_entry(current_row, col, input_entry, &line_format[col],
1426 current_filename, current_lineno - 1);
1427 tbl->add_vlines(current_row, f->vline[format_index]);
1428 current_row++;
1429 format_index++;
1431 break;
1432 case TROFF_INPUT_LINE:
1434 string line;
1435 int ln = current_lineno;
1436 for (;;) {
1437 line += c;
1438 if (c == '\n')
1439 break;
1440 c = in.get();
1441 if (c == EOF) {
1442 break;
1445 tbl->add_text_line(current_row, line, current_filename, ln);
1446 if (line.length() >= 4
1447 && line[0] == '.' && line[1] == 'T' && line[2] == '&') {
1448 format *newf = process_format(in, opt, f);
1449 if (newf == 0)
1450 give_up = 1;
1451 else
1452 f = newf;
1454 if (line.length() >= 3
1455 && line[0] == '.' && line[1] == 'l' && line[2] == 'f') {
1456 line += '\0';
1457 interpret_lf_args(line.contents() + 3);
1460 break;
1461 case SINGLE_HLINE:
1462 tbl->add_single_hline(current_row);
1463 break;
1464 case DOUBLE_HLINE:
1465 tbl->add_double_hline(current_row);
1466 break;
1467 default:
1468 assert(0);
1470 if (give_up)
1471 break;
1473 if (!give_up && current_row == 0) {
1474 error("no real data");
1475 give_up = 1;
1477 if (give_up) {
1478 delete tbl;
1479 return 0;
1481 // Do this here rather than at the beginning in case continued formats
1482 // change it.
1483 int i;
1484 for (i = 0; i < ncolumns - 1; i++)
1485 if (f->separation[i] >= 0)
1486 tbl->set_column_separation(i, f->separation[i]);
1487 for (i = 0; i < ncolumns; i++)
1488 if (!f->width[i].empty())
1489 tbl->set_minimum_width(i, f->width[i]);
1490 for (i = 0; i < ncolumns; i++)
1491 if (f->equal[i])
1492 tbl->set_equal_column(i);
1493 return tbl;
1496 void process_table(table_input &in)
1498 options *opt = 0;
1499 format *form = 0;
1500 table *tbl = 0;
1501 if ((opt = process_options(in)) != 0
1502 && (form = process_format(in, opt)) != 0
1503 && (tbl = process_data(in, form, opt)) != 0) {
1504 tbl->print();
1505 delete tbl;
1507 else {
1508 error("giving up on this table");
1509 while (in.get() != EOF)
1512 delete opt;
1513 delete form;
1514 if (!in.ended())
1515 error("premature end of file");
1518 static void usage(FILE *stream)
1520 fprintf(stream, "usage: %s [ -vC ] [ files... ]\n", program_name);
1523 int main(int argc, char **argv)
1525 program_name = argv[0];
1526 static char stderr_buf[BUFSIZ];
1527 setbuf(stderr, stderr_buf);
1528 int opt;
1529 static const struct option long_options[] = {
1530 { "help", no_argument, 0, CHAR_MAX + 1 },
1531 { "version", no_argument, 0, 'v' },
1532 { NULL, 0, 0, 0 }
1534 while ((opt = getopt_long(argc, argv, "vCT:", long_options, NULL)) != EOF)
1535 switch (opt) {
1536 case 'C':
1537 compatible_flag = 1;
1538 break;
1539 case 'v':
1541 printf("GNU tbl (groff) version %s\n", Version_string);
1542 exit(0);
1543 break;
1545 case 'T':
1546 // I'm sick of getting bug reports from IRIX users
1547 break;
1548 case CHAR_MAX + 1: // --help
1549 usage(stdout);
1550 exit(0);
1551 break;
1552 case '?':
1553 usage(stderr);
1554 exit(1);
1555 break;
1556 default:
1557 assert(0);
1559 printf(".if !\\n(.g .ab GNU tbl requires GNU troff.\n"
1560 ".if !dTS .ds TS\n"
1561 ".if !dTE .ds TE\n");
1562 if (argc > optind) {
1563 for (int i = optind; i < argc; i++)
1564 if (argv[i][0] == '-' && argv[i][1] == '\0') {
1565 current_filename = "-";
1566 current_lineno = 1;
1567 printf(".lf 1 -\n");
1568 process_input_file(stdin);
1570 else {
1571 errno = 0;
1572 FILE *fp = fopen(argv[i], "r");
1573 if (fp == 0)
1574 fatal("can't open `%1': %2", argv[i], strerror(errno));
1575 else {
1576 current_lineno = 1;
1577 current_filename = argv[i];
1578 printf(".lf 1 %s\n", current_filename);
1579 process_input_file(fp);
1583 else {
1584 current_filename = "-";
1585 current_lineno = 1;
1586 printf(".lf 1 -\n");
1587 process_input_file(stdin);
1589 if (ferror(stdout) || fflush(stdout) < 0)
1590 fatal("output error");
1591 return 0;