Update to groff 1.19.2.
[dragonfly.git] / contrib / groff-1.19 / src / preproc / tbl / main.cpp
blobd79adb0bf8313451cb3c0db66d238580dc1ca51c
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 {
512 error("unrecognised global option `%1'", p);
513 // delete opt;
514 // return 0;
516 p = q;
518 return opt;
521 entry_modifier::entry_modifier()
522 : vertical_alignment(CENTER), zero_width(0), stagger(0)
524 vertical_spacing.inc = vertical_spacing.val = 0;
525 point_size.inc = point_size.val = 0;
528 entry_modifier::~entry_modifier()
532 entry_format::entry_format() : type(FORMAT_LEFT)
536 entry_format::entry_format(format_type t) : type(t)
540 void entry_format::debug_print() const
542 switch (type) {
543 case FORMAT_LEFT:
544 putc('l', stderr);
545 break;
546 case FORMAT_CENTER:
547 putc('c', stderr);
548 break;
549 case FORMAT_RIGHT:
550 putc('r', stderr);
551 break;
552 case FORMAT_NUMERIC:
553 putc('n', stderr);
554 break;
555 case FORMAT_ALPHABETIC:
556 putc('a', stderr);
557 break;
558 case FORMAT_SPAN:
559 putc('s', stderr);
560 break;
561 case FORMAT_VSPAN:
562 putc('^', stderr);
563 break;
564 case FORMAT_HLINE:
565 putc('_', stderr);
566 break;
567 case FORMAT_DOUBLE_HLINE:
568 putc('=', stderr);
569 break;
570 default:
571 assert(0);
572 break;
574 if (point_size.val != 0) {
575 putc('p', stderr);
576 if (point_size.inc > 0)
577 putc('+', stderr);
578 else if (point_size.inc < 0)
579 putc('-', stderr);
580 fprintf(stderr, "%d ", point_size.val);
582 if (vertical_spacing.val != 0) {
583 putc('v', stderr);
584 if (vertical_spacing.inc > 0)
585 putc('+', stderr);
586 else if (vertical_spacing.inc < 0)
587 putc('-', stderr);
588 fprintf(stderr, "%d ", vertical_spacing.val);
590 if (!font.empty()) {
591 putc('f', stderr);
592 put_string(font, stderr);
593 putc(' ', stderr);
595 if (!macro.empty()) {
596 putc('m', stderr);
597 put_string(macro, stderr);
598 putc(' ', stderr);
600 switch (vertical_alignment) {
601 case entry_modifier::CENTER:
602 break;
603 case entry_modifier::TOP:
604 putc('t', stderr);
605 break;
606 case entry_modifier::BOTTOM:
607 putc('d', stderr);
608 break;
610 if (zero_width)
611 putc('z', stderr);
612 if (stagger)
613 putc('u', stderr);
616 struct format {
617 int nrows;
618 int ncolumns;
619 int *separation;
620 string *width;
621 char *equal;
622 entry_format **entry;
623 char **vline;
625 format(int nr, int nc);
626 ~format();
627 void add_rows(int n);
630 format::format(int nr, int nc) : nrows(nr), ncolumns(nc)
632 int i;
633 separation = ncolumns > 1 ? new int[ncolumns - 1] : 0;
634 for (i = 0; i < ncolumns-1; i++)
635 separation[i] = -1;
636 width = new string[ncolumns];
637 equal = new char[ncolumns];
638 for (i = 0; i < ncolumns; i++)
639 equal[i] = 0;
640 entry = new entry_format *[nrows];
641 for (i = 0; i < nrows; i++)
642 entry[i] = new entry_format[ncolumns];
643 vline = new char*[nrows];
644 for (i = 0; i < nrows; i++) {
645 vline[i] = new char[ncolumns+1];
646 for (int j = 0; j < ncolumns+1; j++)
647 vline[i][j] = 0;
651 void format::add_rows(int n)
653 int i;
654 char **old_vline = vline;
655 vline = new char*[nrows + n];
656 for (i = 0; i < nrows; i++)
657 vline[i] = old_vline[i];
658 a_delete old_vline;
659 for (i = 0; i < n; i++) {
660 vline[nrows + i] = new char[ncolumns + 1];
661 for (int j = 0; j < ncolumns + 1; j++)
662 vline[nrows + i][j] = 0;
664 entry_format **old_entry = entry;
665 entry = new entry_format *[nrows + n];
666 for (i = 0; i < nrows; i++)
667 entry[i] = old_entry[i];
668 a_delete old_entry;
669 for (i = 0; i < n; i++)
670 entry[nrows + i] = new entry_format[ncolumns];
671 nrows += n;
674 format::~format()
676 a_delete separation;
677 ad_delete(ncolumns) width;
678 a_delete equal;
679 for (int i = 0; i < nrows; i++) {
680 a_delete vline[i];
681 ad_delete(ncolumns) entry[i];
683 a_delete vline;
684 a_delete entry;
687 struct input_entry_format : public entry_format {
688 input_entry_format *next;
689 string width;
690 int separation;
691 int vline;
692 int pre_vline;
693 int last_column;
694 int equal;
695 input_entry_format(format_type, input_entry_format * = 0);
696 ~input_entry_format();
697 void debug_print();
700 input_entry_format::input_entry_format(format_type t, input_entry_format *p)
701 : entry_format(t), next(p)
703 separation = -1;
704 last_column = 0;
705 vline = 0;
706 pre_vline = 0;
707 equal = 0;
710 input_entry_format::~input_entry_format()
714 void free_input_entry_format_list(input_entry_format *list)
716 while (list) {
717 input_entry_format *tem = list;
718 list = list->next;
719 delete tem;
723 void input_entry_format::debug_print()
725 int i;
726 for (i = 0; i < pre_vline; i++)
727 putc('|', stderr);
728 entry_format::debug_print();
729 if (!width.empty()) {
730 putc('w', stderr);
731 putc('(', stderr);
732 put_string(width, stderr);
733 putc(')', stderr);
735 if (equal)
736 putc('e', stderr);
737 if (separation >= 0)
738 fprintf(stderr, "%d", separation);
739 for (i = 0; i < vline; i++)
740 putc('|', stderr);
741 if (last_column)
742 putc(',', stderr);
745 // Return zero if we should give up on this table.
746 // If this is a continuation format line, current_format will be the current
747 // format line.
749 format *process_format(table_input &in, options *opt,
750 format *current_format = 0)
752 input_entry_format *list = 0;
753 int c = in.get();
754 for (;;) {
755 int pre_vline = 0;
756 int got_format = 0;
757 int got_period = 0;
758 format_type t = FORMAT_LEFT;
759 for (;;) {
760 if (c == EOF) {
761 error("end of input while processing format");
762 free_input_entry_format_list(list);
763 return 0;
765 switch (c) {
766 case 'n':
767 case 'N':
768 t = FORMAT_NUMERIC;
769 got_format = 1;
770 break;
771 case 'a':
772 case 'A':
773 got_format = 1;
774 t = FORMAT_ALPHABETIC;
775 break;
776 case 'c':
777 case 'C':
778 got_format = 1;
779 t = FORMAT_CENTER;
780 break;
781 case 'l':
782 case 'L':
783 got_format = 1;
784 t = FORMAT_LEFT;
785 break;
786 case 'r':
787 case 'R':
788 got_format = 1;
789 t = FORMAT_RIGHT;
790 break;
791 case 's':
792 case 'S':
793 got_format = 1;
794 t = FORMAT_SPAN;
795 break;
796 case '^':
797 got_format = 1;
798 t = FORMAT_VSPAN;
799 break;
800 case '_':
801 case '-': // tbl also accepts this
802 got_format = 1;
803 t = FORMAT_HLINE;
804 break;
805 case '=':
806 got_format = 1;
807 t = FORMAT_DOUBLE_HLINE;
808 break;
809 case '.':
810 got_period = 1;
811 break;
812 case '|':
813 pre_vline++;
814 break;
815 case ' ':
816 case '\t':
817 case '\n':
818 break;
819 default:
820 if (c == opt->tab_char)
821 break;
822 error("unrecognised format `%1'", char(c));
823 free_input_entry_format_list(list);
824 return 0;
826 if (got_period)
827 break;
828 c = in.get();
829 if (got_format)
830 break;
832 if (got_period)
833 break;
834 list = new input_entry_format(t, list);
835 if (pre_vline)
836 list->pre_vline = pre_vline;
837 int success = 1;
838 do {
839 switch (c) {
840 case 't':
841 case 'T':
842 c = in.get();
843 list->vertical_alignment = entry_modifier::TOP;
844 break;
845 case 'd':
846 case 'D':
847 c = in.get();
848 list->vertical_alignment = entry_modifier::BOTTOM;
849 break;
850 case 'u':
851 case 'U':
852 c = in.get();
853 list->stagger = 1;
854 break;
855 case 'z':
856 case 'Z':
857 c = in.get();
858 list->zero_width = 1;
859 break;
860 case '0':
861 case '1':
862 case '2':
863 case '3':
864 case '4':
865 case '5':
866 case '6':
867 case '7':
868 case '8':
869 case '9':
871 int w = 0;
872 do {
873 w = w*10 + (c - '0');
874 c = in.get();
875 } while (c != EOF && csdigit(c));
876 list->separation = w;
878 break;
879 case 'f':
880 case 'F':
881 do {
882 c = in.get();
883 } while (c == ' ' || c == '\t');
884 if (c == EOF) {
885 error("missing font name");
886 break;
888 if (c == '(') {
889 for (;;) {
890 c = in.get();
891 if (c == EOF || c == ' ' || c == '\t') {
892 error("missing `)'");
893 break;
895 if (c == ')') {
896 c = in.get();
897 break;
899 list->font += char(c);
902 else {
903 list->font = c;
904 char cc = c;
905 c = in.get();
906 if (!csdigit(cc)
907 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
908 list->font += char(c);
909 c = in.get();
912 break;
913 case 'x':
914 case 'X':
915 do {
916 c = in.get();
917 } while (c == ' ' || c == '\t');
918 if (c == EOF) {
919 error("missing macro name");
920 break;
922 if (c == '(') {
923 for (;;) {
924 c = in.get();
925 if (c == EOF || c == ' ' || c == '\t') {
926 error("missing `)'");
927 break;
929 if (c == ')') {
930 c = in.get();
931 break;
933 list->macro += char(c);
936 else {
937 list->macro = c;
938 char cc = c;
939 c = in.get();
940 if (!csdigit(cc)
941 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
942 list->macro += char(c);
943 c = in.get();
946 break;
947 case 'v':
948 case 'V':
949 c = in.get();
950 list->vertical_spacing.val = 0;
951 list->vertical_spacing.inc = 0;
952 if (c == '+' || c == '-') {
953 list->vertical_spacing.inc = (c == '+' ? 1 : -1);
954 c = in.get();
956 if (c == EOF || !csdigit(c)) {
957 error("`v' modifier must be followed by number");
958 list->vertical_spacing.inc = 0;
960 else {
961 do {
962 list->vertical_spacing.val *= 10;
963 list->vertical_spacing.val += c - '0';
964 c = in.get();
965 } while (c != EOF && csdigit(c));
967 if (list->vertical_spacing.val > MAX_VERTICAL_SPACING
968 || list->vertical_spacing.val < -MAX_VERTICAL_SPACING) {
969 error("unreasonable vertical spacing");
970 list->vertical_spacing.val = 0;
971 list->vertical_spacing.inc = 0;
973 break;
974 case 'p':
975 case 'P':
976 c = in.get();
977 list->point_size.val = 0;
978 list->point_size.inc = 0;
979 if (c == '+' || c == '-') {
980 list->point_size.inc = (c == '+' ? 1 : -1);
981 c = in.get();
983 if (c == EOF || !csdigit(c)) {
984 error("`p' modifier must be followed by number");
985 list->point_size.inc = 0;
987 else {
988 do {
989 list->point_size.val *= 10;
990 list->point_size.val += c - '0';
991 c = in.get();
992 } while (c != EOF && csdigit(c));
994 if (list->point_size.val > MAX_POINT_SIZE
995 || list->point_size.val < -MAX_POINT_SIZE) {
996 error("unreasonable point size");
997 list->point_size.val = 0;
998 list->point_size.inc = 0;
1000 break;
1001 case 'w':
1002 case 'W':
1003 c = in.get();
1004 while (c == ' ' || c == '\t')
1005 c = in.get();
1006 if (c == '(') {
1007 list->width = "";
1008 c = in.get();
1009 while (c != ')') {
1010 if (c == EOF || c == '\n') {
1011 error("missing `)'");
1012 free_input_entry_format_list(list);
1013 return 0;
1015 list->width += c;
1016 c = in.get();
1018 c = in.get();
1020 else {
1021 if (c == '+' || c == '-') {
1022 list->width = char(c);
1023 c = in.get();
1025 else
1026 list->width = "";
1027 if (c == EOF || !csdigit(c))
1028 error("bad argument for `w' modifier");
1029 else {
1030 do {
1031 list->width += char(c);
1032 c = in.get();
1033 } while (c != EOF && csdigit(c));
1036 break;
1037 case 'e':
1038 case 'E':
1039 c = in.get();
1040 list->equal++;
1041 break;
1042 case '|':
1043 c = in.get();
1044 list->vline++;
1045 break;
1046 case 'B':
1047 case 'b':
1048 c = in.get();
1049 list->font = "B";
1050 break;
1051 case 'I':
1052 case 'i':
1053 c = in.get();
1054 list->font = "I";
1055 break;
1056 case ' ':
1057 case '\t':
1058 c = in.get();
1059 break;
1060 default:
1061 if (c == opt->tab_char)
1062 c = in.get();
1063 else
1064 success = 0;
1065 break;
1067 } while (success);
1068 if (list->vline > 2) {
1069 list->vline = 2;
1070 error("more than 2 vertical bars between key letters");
1072 if (c == '\n' || c == ',') {
1073 c = in.get();
1074 list->last_column = 1;
1077 if (c == '.') {
1078 do {
1079 c = in.get();
1080 } while (c == ' ' || c == '\t');
1081 if (c != '\n') {
1082 error("`.' not last character on line");
1083 free_input_entry_format_list(list);
1084 return 0;
1087 if (!list) {
1088 error("no format");
1089 free_input_entry_format_list(list);
1090 return 0;
1092 list->last_column = 1;
1093 // now reverse the list so that the first row is at the beginning
1094 input_entry_format *rev = 0;
1095 while (list != 0) {
1096 input_entry_format *tem = list->next;
1097 list->next = rev;
1098 rev = list;
1099 list = tem;
1101 list = rev;
1102 input_entry_format *tem;
1104 #if 0
1105 for (tem = list; tem; tem = tem->next)
1106 tem->debug_print();
1107 putc('\n', stderr);
1108 #endif
1109 // compute number of columns and rows
1110 int ncolumns = 0;
1111 int nrows = 0;
1112 int col = 0;
1113 for (tem = list; tem; tem = tem->next) {
1114 if (tem->last_column) {
1115 if (col >= ncolumns)
1116 ncolumns = col + 1;
1117 col = 0;
1118 nrows++;
1120 else
1121 col++;
1123 int row;
1124 format *f;
1125 if (current_format) {
1126 if (ncolumns > current_format->ncolumns) {
1127 error("cannot increase the number of columns in a continued format");
1128 free_input_entry_format_list(list);
1129 return 0;
1131 f = current_format;
1132 row = f->nrows;
1133 f->add_rows(nrows);
1135 else {
1136 f = new format(nrows, ncolumns);
1137 row = 0;
1139 col = 0;
1140 for (tem = list; tem; tem = tem->next) {
1141 f->entry[row][col] = *tem;
1142 if (col < ncolumns-1) {
1143 // use the greatest separation
1144 if (tem->separation > f->separation[col]) {
1145 if (current_format)
1146 error("cannot change column separation in continued format");
1147 else
1148 f->separation[col] = tem->separation;
1151 else if (tem->separation >= 0)
1152 error("column separation specified for last column");
1153 if (tem->equal && !f->equal[col]) {
1154 if (current_format)
1155 error("cannot change which columns are equal in continued format");
1156 else
1157 f->equal[col] = 1;
1159 if (!tem->width.empty()) {
1160 // use the last width
1161 if (!f->width[col].empty() && f->width[col] != tem->width)
1162 error("multiple widths for column %1", col+1);
1163 f->width[col] = tem->width;
1165 if (tem->pre_vline) {
1166 assert(col == 0);
1167 f->vline[row][col] = tem->pre_vline;
1169 f->vline[row][col+1] = tem->vline;
1170 if (tem->last_column) {
1171 row++;
1172 col = 0;
1174 else
1175 col++;
1177 free_input_entry_format_list(list);
1178 for (col = 0; col < ncolumns; col++) {
1179 entry_format *e = f->entry[f->nrows-1] + col;
1180 if (e->type != FORMAT_HLINE
1181 && e->type != FORMAT_DOUBLE_HLINE
1182 && e->type != FORMAT_SPAN)
1183 break;
1185 if (col >= ncolumns) {
1186 error("last row of format is all lines");
1187 delete f;
1188 return 0;
1190 return f;
1193 table *process_data(table_input &in, format *f, options *opt)
1195 char tab_char = opt->tab_char;
1196 int ncolumns = f->ncolumns;
1197 int current_row = 0;
1198 int format_index = 0;
1199 int give_up = 0;
1200 enum { DATA_INPUT_LINE, TROFF_INPUT_LINE, SINGLE_HLINE, DOUBLE_HLINE } type;
1201 table *tbl = new table(ncolumns, opt->flags, opt->linesize,
1202 opt->decimal_point_char);
1203 if (opt->delim[0] != '\0')
1204 tbl->set_delim(opt->delim[0], opt->delim[1]);
1205 for (;;) {
1206 // first determine what type of line this is
1207 int c = in.get();
1208 if (c == EOF)
1209 break;
1210 if (c == '.') {
1211 int d = in.get();
1212 if (d != EOF && csdigit(d)) {
1213 in.unget(d);
1214 type = DATA_INPUT_LINE;
1216 else {
1217 in.unget(d);
1218 type = TROFF_INPUT_LINE;
1221 else if (c == '_' || c == '=') {
1222 int d = in.get();
1223 if (d == '\n') {
1224 if (c == '_')
1225 type = SINGLE_HLINE;
1226 else
1227 type = DOUBLE_HLINE;
1229 else {
1230 in.unget(d);
1231 type = DATA_INPUT_LINE;
1234 else {
1235 type = DATA_INPUT_LINE;
1237 switch (type) {
1238 case DATA_INPUT_LINE:
1240 string input_entry;
1241 if (format_index >= f->nrows)
1242 format_index = f->nrows - 1;
1243 // A format row that is all lines doesn't use up a data line.
1244 while (format_index < f->nrows - 1) {
1245 int cnt;
1246 for (cnt = 0; cnt < ncolumns; cnt++) {
1247 entry_format *e = f->entry[format_index] + cnt;
1248 if (e->type != FORMAT_HLINE
1249 && e->type != FORMAT_DOUBLE_HLINE
1250 // Unfortunately tbl treats a span as needing data.
1251 // && e->type != FORMAT_SPAN
1253 break;
1255 if (cnt < ncolumns)
1256 break;
1257 for (cnt = 0; cnt < ncolumns; cnt++)
1258 tbl->add_entry(current_row, cnt, input_entry,
1259 f->entry[format_index] + cnt, current_filename,
1260 current_lineno);
1261 tbl->add_vlines(current_row, f->vline[format_index]);
1262 format_index++;
1263 current_row++;
1265 entry_format *line_format = f->entry[format_index];
1266 int col = 0;
1267 int row_comment = 0;
1268 for (;;) {
1269 if (c == tab_char || c == '\n') {
1270 int ln = current_lineno;
1271 if (c == '\n')
1272 --ln;
1273 if ((opt->flags & table::NOSPACES))
1274 input_entry.remove_spaces();
1275 while (col < ncolumns
1276 && line_format[col].type == FORMAT_SPAN) {
1277 tbl->add_entry(current_row, col, "", &line_format[col],
1278 current_filename, ln);
1279 col++;
1281 if (c == '\n' && input_entry.length() == 2
1282 && input_entry[0] == 'T' && input_entry[1] == '{') {
1283 input_entry = "";
1284 ln++;
1285 enum {
1286 START, MIDDLE, GOT_T, GOT_RIGHT_BRACE, GOT_DOT,
1287 GOT_l, GOT_lf, END
1288 } state = START;
1289 while (state != END) {
1290 c = in.get();
1291 if (c == EOF)
1292 break;
1293 switch (state) {
1294 case START:
1295 if (c == 'T')
1296 state = GOT_T;
1297 else if (c == '.')
1298 state = GOT_DOT;
1299 else {
1300 input_entry += c;
1301 if (c != '\n')
1302 state = MIDDLE;
1304 break;
1305 case GOT_T:
1306 if (c == '}')
1307 state = GOT_RIGHT_BRACE;
1308 else {
1309 input_entry += 'T';
1310 input_entry += c;
1311 state = c == '\n' ? START : MIDDLE;
1313 break;
1314 case GOT_DOT:
1315 if (c == 'l')
1316 state = GOT_l;
1317 else {
1318 input_entry += '.';
1319 input_entry += c;
1320 state = c == '\n' ? START : MIDDLE;
1322 break;
1323 case GOT_l:
1324 if (c == 'f')
1325 state = GOT_lf;
1326 else {
1327 input_entry += ".l";
1328 input_entry += c;
1329 state = c == '\n' ? START : MIDDLE;
1331 break;
1332 case GOT_lf:
1333 if (c == ' ' || c == '\n' || compatible_flag) {
1334 string args;
1335 input_entry += ".lf";
1336 while (c != EOF) {
1337 args += c;
1338 if (c == '\n')
1339 break;
1340 c = in.get();
1342 args += '\0';
1343 interpret_lf_args(args.contents());
1344 // remove the '\0'
1345 args.set_length(args.length() - 1);
1346 input_entry += args;
1347 state = START;
1349 else {
1350 input_entry += ".lf";
1351 input_entry += c;
1352 state = MIDDLE;
1354 break;
1355 case GOT_RIGHT_BRACE:
1356 if ((opt->flags & table::NOSPACES)) {
1357 while (c == ' ')
1358 c = in.get();
1359 if (c == EOF)
1360 break;
1362 if (c == '\n' || c == tab_char)
1363 state = END;
1364 else {
1365 input_entry += 'T';
1366 input_entry += '}';
1367 input_entry += c;
1368 state = MIDDLE;
1370 break;
1371 case MIDDLE:
1372 if (c == '\n')
1373 state = START;
1374 input_entry += c;
1375 break;
1376 case END:
1377 default:
1378 assert(0);
1381 if (c == EOF) {
1382 error("end of data in middle of text block");
1383 give_up = 1;
1384 break;
1387 if (col >= ncolumns) {
1388 if (!input_entry.empty()) {
1389 if (input_entry.length() >= 2
1390 && input_entry[0] == '\\'
1391 && input_entry[1] == '"')
1392 row_comment = 1;
1393 else if (!row_comment) {
1394 if (c == '\n')
1395 in.unget(c);
1396 input_entry += '\0';
1397 error("excess data entry `%1' discarded",
1398 input_entry.contents());
1399 if (c == '\n')
1400 (void)in.get();
1404 else
1405 tbl->add_entry(current_row, col, input_entry,
1406 &line_format[col], current_filename, ln);
1407 col++;
1408 if (c == '\n')
1409 break;
1410 input_entry = "";
1412 else
1413 input_entry += c;
1414 c = in.get();
1415 if (c == EOF)
1416 break;
1418 if (give_up)
1419 break;
1420 input_entry = "";
1421 for (; col < ncolumns; col++)
1422 tbl->add_entry(current_row, col, input_entry, &line_format[col],
1423 current_filename, current_lineno - 1);
1424 tbl->add_vlines(current_row, f->vline[format_index]);
1425 current_row++;
1426 format_index++;
1428 break;
1429 case TROFF_INPUT_LINE:
1431 string line;
1432 int ln = current_lineno;
1433 for (;;) {
1434 line += c;
1435 if (c == '\n')
1436 break;
1437 c = in.get();
1438 if (c == EOF) {
1439 break;
1442 tbl->add_text_line(current_row, line, current_filename, ln);
1443 if (line.length() >= 4
1444 && line[0] == '.' && line[1] == 'T' && line[2] == '&') {
1445 format *newf = process_format(in, opt, f);
1446 if (newf == 0)
1447 give_up = 1;
1448 else
1449 f = newf;
1451 if (line.length() >= 3
1452 && line[0] == '.' && line[1] == 'l' && line[2] == 'f') {
1453 line += '\0';
1454 interpret_lf_args(line.contents() + 3);
1457 break;
1458 case SINGLE_HLINE:
1459 tbl->add_single_hline(current_row);
1460 break;
1461 case DOUBLE_HLINE:
1462 tbl->add_double_hline(current_row);
1463 break;
1464 default:
1465 assert(0);
1467 if (give_up)
1468 break;
1470 if (!give_up && current_row == 0) {
1471 error("no real data");
1472 give_up = 1;
1474 if (give_up) {
1475 delete tbl;
1476 return 0;
1478 // Do this here rather than at the beginning in case continued formats
1479 // change it.
1480 int i;
1481 for (i = 0; i < ncolumns - 1; i++)
1482 if (f->separation[i] >= 0)
1483 tbl->set_column_separation(i, f->separation[i]);
1484 for (i = 0; i < ncolumns; i++)
1485 if (!f->width[i].empty())
1486 tbl->set_minimum_width(i, f->width[i]);
1487 for (i = 0; i < ncolumns; i++)
1488 if (f->equal[i])
1489 tbl->set_equal_column(i);
1490 return tbl;
1493 void process_table(table_input &in)
1495 options *opt = 0;
1496 format *form = 0;
1497 table *tbl = 0;
1498 if ((opt = process_options(in)) != 0
1499 && (form = process_format(in, opt)) != 0
1500 && (tbl = process_data(in, form, opt)) != 0) {
1501 tbl->print();
1502 delete tbl;
1504 else {
1505 error("giving up on this table");
1506 while (in.get() != EOF)
1509 delete opt;
1510 delete form;
1511 if (!in.ended())
1512 error("premature end of file");
1515 static void usage(FILE *stream)
1517 fprintf(stream, "usage: %s [ -vC ] [ files... ]\n", program_name);
1520 int main(int argc, char **argv)
1522 program_name = argv[0];
1523 static char stderr_buf[BUFSIZ];
1524 setbuf(stderr, stderr_buf);
1525 int opt;
1526 static const struct option long_options[] = {
1527 { "help", no_argument, 0, CHAR_MAX + 1 },
1528 { "version", no_argument, 0, 'v' },
1529 { NULL, 0, 0, 0 }
1531 while ((opt = getopt_long(argc, argv, "vCT:", long_options, NULL)) != EOF)
1532 switch (opt) {
1533 case 'C':
1534 compatible_flag = 1;
1535 break;
1536 case 'v':
1538 printf("GNU tbl (groff) version %s\n", Version_string);
1539 exit(0);
1540 break;
1542 case 'T':
1543 // I'm sick of getting bug reports from IRIX users
1544 break;
1545 case CHAR_MAX + 1: // --help
1546 usage(stdout);
1547 exit(0);
1548 break;
1549 case '?':
1550 usage(stderr);
1551 exit(1);
1552 break;
1553 default:
1554 assert(0);
1556 printf(".if !\\n(.g .ab GNU tbl requires GNU troff.\n"
1557 ".if !dTS .ds TS\n"
1558 ".if !dTE .ds TE\n");
1559 if (argc > optind) {
1560 for (int i = optind; i < argc; i++)
1561 if (argv[i][0] == '-' && argv[i][1] == '\0') {
1562 current_filename = "-";
1563 current_lineno = 1;
1564 printf(".lf 1 -\n");
1565 process_input_file(stdin);
1567 else {
1568 errno = 0;
1569 FILE *fp = fopen(argv[i], "r");
1570 if (fp == 0)
1571 fatal("can't open `%1': %2", argv[i], strerror(errno));
1572 else {
1573 current_lineno = 1;
1574 current_filename = argv[i];
1575 printf(".lf 1 %s\n", current_filename);
1576 process_input_file(fp);
1580 else {
1581 current_filename = "-";
1582 current_lineno = 1;
1583 printf(".lf 1 -\n");
1584 process_input_file(stdin);
1586 if (ferror(stdout) || fflush(stdout) < 0)
1587 fatal("output error");
1588 return 0;