Adapt src/pre-soelim (src/preproc/soelim)
[s-roff.git] / src / preproc / tbl / main.cpp
blob720e6ffe0e223836a6d186817d1c5df6be0ffb9d
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003, 2004, 2005,
3 2007, 2008
4 Free Software Foundation, Inc.
5 Written by James Clark (jjc@jclark.com)
7 This file is part of groff.
9 groff is free software; you can redistribute it and/or modify it under
10 the terms of the GNU General Public License as published by the Free
11 Software Foundation; either version 2, or (at your option) any later
12 version.
14 groff is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 for more details.
19 You should have received a copy of the GNU General Public License along
20 with groff; see the file COPYING. If not, write to the Free Software
21 Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
23 #include "file_case.h"
25 #include "table.h"
27 #define MAX_POINT_SIZE 99
28 #define MAX_VERTICAL_SPACING 72
30 extern "C" const char *Version_string;
32 int compatible_flag = 0;
34 class table_input {
35 file_case *_fcp;
36 enum { START, MIDDLE,
37 REREAD_T, REREAD_TE, REREAD_E,
38 LEADER_1, LEADER_2, LEADER_3, LEADER_4,
39 END, ERROR } state;
40 string unget_stack;
41 public:
42 table_input(file_case *);
43 int get();
44 int ended() { return unget_stack.empty() && state == END; }
45 void unget(char);
48 table_input::table_input(file_case *fcp)
49 : _fcp(fcp), state(START)
53 void table_input::unget(char c)
55 assert(c != '\0');
56 unget_stack += c;
57 if (c == '\n')
58 current_lineno--;
61 int table_input::get()
63 int len = unget_stack.length();
64 if (len != 0) {
65 unsigned char c = unget_stack[len - 1];
66 unget_stack.set_length(len - 1);
67 if (c == '\n')
68 current_lineno++;
69 return c;
71 int c;
72 for (;;) {
73 switch (state) {
74 case START:
75 if ((c = _fcp->get_c()) == '.') {
76 if ((c = _fcp->get_c()) == 'T') {
77 if ((c = _fcp->get_c()) == 'E') {
78 if (compatible_flag) {
79 state = END;
80 return EOF;
82 else {
83 c = _fcp->get_c();
84 if (c != EOF)
85 _fcp->unget_c(c);
86 if (c == EOF || c == ' ' || c == '\n') {
87 state = END;
88 return EOF;
90 state = REREAD_TE;
91 return '.';
94 else {
95 if (c != EOF)
96 _fcp->unget_c(c);
97 state = REREAD_T;
98 return '.';
101 else {
102 if (c != EOF)
103 _fcp->unget_c(c);
104 state = MIDDLE;
105 return '.';
108 else if (c == EOF) {
109 state = ERROR;
110 return EOF;
112 else {
113 if (c == '\n')
114 current_lineno++;
115 else {
116 state = MIDDLE;
117 if (c == '\0') {
118 error("invalid input character code 0");
119 break;
122 return c;
124 break;
125 case MIDDLE:
126 // handle line continuation and uninterpreted leader character
127 if ((c = _fcp->get_c()) == '\\') {
128 c = _fcp->get_c();
129 if (c == '\n')
130 c = _fcp->get_c(); // perhaps state ought to be START now
131 else if (c == 'a' && compatible_flag) {
132 state = LEADER_1;
133 return '\\';
135 else {
136 if (c != EOF)
137 _fcp->unget_c(c);
138 c = '\\';
141 if (c == EOF) {
142 state = ERROR;
143 return EOF;
145 else {
146 if (c == '\n') {
147 state = START;
148 current_lineno++;
150 else if (c == '\0') {
151 error("invalid input character code 0");
152 break;
154 return c;
156 case REREAD_T:
157 state = MIDDLE;
158 return 'T';
159 case REREAD_TE:
160 state = REREAD_E;
161 return 'T';
162 case REREAD_E:
163 state = MIDDLE;
164 return 'E';
165 case LEADER_1:
166 state = LEADER_2;
167 return '*';
168 case LEADER_2:
169 state = LEADER_3;
170 return '(';
171 case LEADER_3:
172 state = LEADER_4;
173 return PREFIX_CHAR;
174 case LEADER_4:
175 state = MIDDLE;
176 return LEADER_CHAR;
177 case END:
178 case ERROR:
179 return EOF;
184 void process_input_file(file_case *);
185 void process_table(table_input &in);
187 void process_input_file(file_case *fcp)
189 enum { START, MIDDLE, HAD_DOT, HAD_T, HAD_TS, HAD_l, HAD_lf } state;
190 state = START;
191 int c;
192 while ((c = fcp->get_c()) != EOF)
193 switch (state) {
194 case START:
195 if (c == '.')
196 state = HAD_DOT;
197 else {
198 if (c == '\n')
199 current_lineno++;
200 else
201 state = MIDDLE;
202 putchar(c);
204 break;
205 case MIDDLE:
206 if (c == '\n') {
207 current_lineno++;
208 state = START;
210 putchar(c);
211 break;
212 case HAD_DOT:
213 if (c == 'T')
214 state = HAD_T;
215 else if (c == 'l')
216 state = HAD_l;
217 else {
218 putchar('.');
219 putchar(c);
220 if (c == '\n') {
221 current_lineno++;
222 state = START;
224 else
225 state = MIDDLE;
227 break;
228 case HAD_T:
229 if (c == 'S')
230 state = HAD_TS;
231 else {
232 putchar('.');
233 putchar('T');
234 putchar(c);
235 if (c == '\n') {
236 current_lineno++;
237 state = START;
239 else
240 state = MIDDLE;
242 break;
243 case HAD_TS:
244 if (c == ' ' || c == '\n' || compatible_flag) {
245 putchar('.');
246 putchar('T');
247 putchar('S');
248 while (c != '\n') {
249 if (c == EOF) {
250 error("end of file at beginning of table");
251 return;
253 putchar(c);
254 c = fcp->get_c();
256 putchar('\n');
257 current_lineno++;
259 table_input input(fcp);
260 process_table(input);
261 set_troff_location(current_filename, current_lineno);
262 if (input.ended()) {
263 fputs(".TE", stdout);
264 while ((c = fcp->get_c()) != '\n') {
265 if (c == EOF) {
266 putchar('\n');
267 return;
269 putchar(c);
271 putchar('\n');
272 current_lineno++;
275 state = START;
277 else {
278 fputs(".TS", stdout);
279 putchar(c);
280 state = MIDDLE;
282 break;
283 case HAD_l:
284 if (c == 'f')
285 state = HAD_lf;
286 else {
287 putchar('.');
288 putchar('l');
289 putchar(c);
290 if (c == '\n') {
291 current_lineno++;
292 state = START;
294 else
295 state = MIDDLE;
297 break;
298 case HAD_lf:
299 if (c == ' ' || c == '\n' || compatible_flag) {
300 string line;
301 while (c != EOF) {
302 line += c;
303 if (c == '\n') {
304 current_lineno++;
305 break;
307 c = fcp->get_c();
309 line += '\0';
310 interpret_lf_args(line.contents());
311 printf(".lf%s", line.contents());
312 state = START;
314 else {
315 fputs(".lf", stdout);
316 putchar(c);
317 state = MIDDLE;
319 break;
320 default:
321 assert(0);
323 switch(state) {
324 case START:
325 break;
326 case MIDDLE:
327 putchar('\n');
328 break;
329 case HAD_DOT:
330 fputs(".\n", stdout);
331 break;
332 case HAD_l:
333 fputs(".l\n", stdout);
334 break;
335 case HAD_T:
336 fputs(".T\n", stdout);
337 break;
338 case HAD_lf:
339 fputs(".lf\n", stdout);
340 break;
341 case HAD_TS:
342 fputs(".TS\n", stdout);
343 break;
347 struct options {
348 unsigned flags;
349 int linesize;
350 char delim[2];
351 char tab_char;
352 char decimal_point_char;
354 options();
357 options::options()
358 : flags(0), linesize(0), tab_char('\t'), decimal_point_char('.')
360 delim[0] = delim[1] = '\0';
363 // Return non-zero if p and q are the same ignoring case.
365 int strieq(const char *p, const char *q)
367 for (; cmlower(*p) == cmlower(*q); p++, q++)
368 if (*p == '\0')
369 return 1;
370 return 0;
373 // return 0 if we should give up in this table
375 options *process_options(table_input &in)
377 options *opt = new options;
378 string line;
379 int level = 0;
380 for (;;) {
381 int c = in.get();
382 if (c == EOF) {
383 int i = line.length();
384 while (--i >= 0)
385 in.unget(line[i]);
386 return opt;
388 if (c == '\n') {
389 in.unget(c);
390 int i = line.length();
391 while (--i >= 0)
392 in.unget(line[i]);
393 return opt;
395 else if (c == '(')
396 level++;
397 else if (c == ')')
398 level--;
399 else if (c == ';' && level == 0) {
400 line += '\0';
401 break;
403 line += c;
405 if (line.empty())
406 return opt;
407 char *p = &line[0];
408 for (;;) {
409 while (!csalpha(*p) && *p != '\0')
410 p++;
411 if (*p == '\0')
412 break;
413 char *q = p;
414 while (csalpha(*q))
415 q++;
416 char *arg = 0;
417 if (*q != '(' && *q != '\0')
418 *q++ = '\0';
419 while (csspace(*q))
420 q++;
421 if (*q == '(') {
422 *q++ = '\0';
423 arg = q;
424 while (*q != ')' && *q != '\0')
425 q++;
426 if (*q == '\0')
427 error("missing `)'");
428 else
429 *q++ = '\0';
431 if (*p == '\0') {
432 if (arg)
433 error("argument without option");
435 else if (strieq(p, "tab")) {
436 if (!arg)
437 error("`tab' option requires argument in parentheses");
438 else {
439 if (arg[0] == '\0' || arg[1] != '\0')
440 error("argument to `tab' option must be a single character");
441 else
442 opt->tab_char = arg[0];
445 else if (strieq(p, "linesize")) {
446 if (!arg)
447 error("`linesize' option requires argument in parentheses");
448 else {
449 if (sscanf(arg, "%d", &opt->linesize) != 1)
450 error("bad linesize `%s'", arg);
451 else if (opt->linesize <= 0) {
452 error("linesize must be positive");
453 opt->linesize = 0;
457 else if (strieq(p, "delim")) {
458 if (!arg)
459 error("`delim' option requires argument in parentheses");
460 else if (arg[0] == '\0' || arg[1] == '\0' || arg[2] != '\0')
461 error("argument to `delim' option must be two characters");
462 else {
463 opt->delim[0] = arg[0];
464 opt->delim[1] = arg[1];
467 else if (strieq(p, "center") || strieq(p, "centre")) {
468 if (arg)
469 error("`center' option does not take an argument");
470 opt->flags |= table::CENTER;
472 else if (strieq(p, "expand")) {
473 if (arg)
474 error("`expand' option does not take an argument");
475 opt->flags |= table::EXPAND;
477 else if (strieq(p, "box") || strieq(p, "frame")) {
478 if (arg)
479 error("`box' option does not take an argument");
480 opt->flags |= table::BOX;
482 else if (strieq(p, "doublebox") || strieq(p, "doubleframe")) {
483 if (arg)
484 error("`doublebox' option does not take an argument");
485 opt->flags |= table::DOUBLEBOX;
487 else if (strieq(p, "allbox")) {
488 if (arg)
489 error("`allbox' option does not take an argument");
490 opt->flags |= table::ALLBOX;
492 else if (strieq(p, "nokeep")) {
493 if (arg)
494 error("`nokeep' option does not take an argument");
495 opt->flags |= table::NOKEEP;
497 else if (strieq(p, "nospaces")) {
498 if (arg)
499 error("`nospaces' option does not take an argument");
500 opt->flags |= table::NOSPACES;
502 else if (strieq(p, "decimalpoint")) {
503 if (!arg)
504 error("`decimalpoint' option requires argument in parentheses");
505 else {
506 if (arg[0] == '\0' || arg[1] != '\0')
507 error("argument to `decimalpoint' option must be a single character");
508 else
509 opt->decimal_point_char = arg[0];
512 else if (strieq(p, "experimental")) {
513 opt->flags |= table::EXPERIMENTAL;
515 else {
516 error("unrecognised global option `%1'", p);
517 // delete opt;
518 // return 0;
520 p = q;
522 return opt;
525 entry_modifier::entry_modifier()
526 : vertical_alignment(CENTER), zero_width(0), stagger(0)
528 vertical_spacing.inc = vertical_spacing.val = 0;
529 point_size.inc = point_size.val = 0;
532 entry_modifier::~entry_modifier()
536 entry_format::entry_format() : type(FORMAT_LEFT)
540 entry_format::entry_format(format_type t) : type(t)
544 void entry_format::debug_print() const
546 switch (type) {
547 case FORMAT_LEFT:
548 putc('l', stderr);
549 break;
550 case FORMAT_CENTER:
551 putc('c', stderr);
552 break;
553 case FORMAT_RIGHT:
554 putc('r', stderr);
555 break;
556 case FORMAT_NUMERIC:
557 putc('n', stderr);
558 break;
559 case FORMAT_ALPHABETIC:
560 putc('a', stderr);
561 break;
562 case FORMAT_SPAN:
563 putc('s', stderr);
564 break;
565 case FORMAT_VSPAN:
566 putc('^', stderr);
567 break;
568 case FORMAT_HLINE:
569 putc('_', stderr);
570 break;
571 case FORMAT_DOUBLE_HLINE:
572 putc('=', stderr);
573 break;
574 default:
575 assert(0);
576 break;
578 if (point_size.val != 0) {
579 putc('p', stderr);
580 if (point_size.inc > 0)
581 putc('+', stderr);
582 else if (point_size.inc < 0)
583 putc('-', stderr);
584 fprintf(stderr, "%d ", point_size.val);
586 if (vertical_spacing.val != 0) {
587 putc('v', stderr);
588 if (vertical_spacing.inc > 0)
589 putc('+', stderr);
590 else if (vertical_spacing.inc < 0)
591 putc('-', stderr);
592 fprintf(stderr, "%d ", vertical_spacing.val);
594 if (!font.empty()) {
595 putc('f', stderr);
596 put_string(font, stderr);
597 putc(' ', stderr);
599 if (!macro.empty()) {
600 putc('m', stderr);
601 put_string(macro, stderr);
602 putc(' ', stderr);
604 switch (vertical_alignment) {
605 case entry_modifier::CENTER:
606 break;
607 case entry_modifier::TOP:
608 putc('t', stderr);
609 break;
610 case entry_modifier::BOTTOM:
611 putc('d', stderr);
612 break;
614 if (zero_width)
615 putc('z', stderr);
616 if (stagger)
617 putc('u', stderr);
620 struct format {
621 int nrows;
622 int ncolumns;
623 int *separation;
624 string *width;
625 char *equal;
626 char *expand;
627 entry_format **entry;
628 char **vline;
630 format(int nr, int nc);
631 ~format();
632 void add_rows(int n);
635 format::format(int nr, int nc) : nrows(nr), ncolumns(nc)
637 int i;
638 separation = ncolumns > 1 ? new int[ncolumns - 1] : 0;
639 for (i = 0; i < ncolumns-1; i++)
640 separation[i] = -1;
641 width = new string[ncolumns];
642 equal = new char[ncolumns];
643 expand = new char[ncolumns];
644 for (i = 0; i < ncolumns; i++) {
645 equal[i] = 0;
646 expand[i] = 0;
648 entry = new entry_format *[nrows];
649 for (i = 0; i < nrows; i++)
650 entry[i] = new entry_format[ncolumns];
651 vline = new char*[nrows];
652 for (i = 0; i < nrows; i++) {
653 vline[i] = new char[ncolumns+1];
654 for (int j = 0; j < ncolumns+1; j++)
655 vline[i][j] = 0;
659 void format::add_rows(int n)
661 int i;
662 char **old_vline = vline;
663 vline = new char*[nrows + n];
664 for (i = 0; i < nrows; i++)
665 vline[i] = old_vline[i];
666 a_delete old_vline;
667 for (i = 0; i < n; i++) {
668 vline[nrows + i] = new char[ncolumns + 1];
669 for (int j = 0; j < ncolumns + 1; j++)
670 vline[nrows + i][j] = 0;
672 entry_format **old_entry = entry;
673 entry = new entry_format *[nrows + n];
674 for (i = 0; i < nrows; i++)
675 entry[i] = old_entry[i];
676 a_delete old_entry;
677 for (i = 0; i < n; i++)
678 entry[nrows + i] = new entry_format[ncolumns];
679 nrows += n;
682 format::~format()
684 a_delete separation;
685 ad_delete(ncolumns) width;
686 a_delete equal;
687 a_delete expand;
688 for (int i = 0; i < nrows; i++) {
689 a_delete vline[i];
690 ad_delete(ncolumns) entry[i];
692 a_delete vline;
693 a_delete entry;
696 struct input_entry_format : public entry_format {
697 input_entry_format *next;
698 string width;
699 int separation;
700 int vline;
701 int pre_vline;
702 int last_column;
703 int equal;
704 int expand;
705 input_entry_format(format_type, input_entry_format * = 0);
706 ~input_entry_format();
707 void debug_print();
710 input_entry_format::input_entry_format(format_type t, input_entry_format *p)
711 : entry_format(t), next(p)
713 separation = -1;
714 last_column = 0;
715 vline = 0;
716 pre_vline = 0;
717 equal = 0;
718 expand = 0;
721 input_entry_format::~input_entry_format()
725 void free_input_entry_format_list(input_entry_format *list)
727 while (list) {
728 input_entry_format *tem = list;
729 list = list->next;
730 delete tem;
734 void input_entry_format::debug_print()
736 int i;
737 for (i = 0; i < pre_vline; i++)
738 putc('|', stderr);
739 entry_format::debug_print();
740 if (!width.empty()) {
741 putc('w', stderr);
742 putc('(', stderr);
743 put_string(width, stderr);
744 putc(')', stderr);
746 if (equal)
747 putc('e', stderr);
748 if (expand)
749 putc('x', stderr);
750 if (separation >= 0)
751 fprintf(stderr, "%d", separation);
752 for (i = 0; i < vline; i++)
753 putc('|', stderr);
754 if (last_column)
755 putc(',', stderr);
758 // Return zero if we should give up on this table.
759 // If this is a continuation format line, current_format will be the current
760 // format line.
762 format *process_format(table_input &in, options *opt,
763 format *current_format = 0)
765 input_entry_format *list = 0;
766 int have_expand = 0;
767 int c = in.get();
768 for (;;) {
769 int pre_vline = 0;
770 int got_format = 0;
771 int got_period = 0;
772 format_type t = FORMAT_LEFT;
773 for (;;) {
774 if (c == EOF) {
775 error("end of input while processing format");
776 free_input_entry_format_list(list);
777 return 0;
779 switch (c) {
780 case 'n':
781 case 'N':
782 t = FORMAT_NUMERIC;
783 got_format = 1;
784 break;
785 case 'a':
786 case 'A':
787 got_format = 1;
788 t = FORMAT_ALPHABETIC;
789 break;
790 case 'c':
791 case 'C':
792 got_format = 1;
793 t = FORMAT_CENTER;
794 break;
795 case 'l':
796 case 'L':
797 got_format = 1;
798 t = FORMAT_LEFT;
799 break;
800 case 'r':
801 case 'R':
802 got_format = 1;
803 t = FORMAT_RIGHT;
804 break;
805 case 's':
806 case 'S':
807 got_format = 1;
808 t = FORMAT_SPAN;
809 break;
810 case '^':
811 got_format = 1;
812 t = FORMAT_VSPAN;
813 break;
814 case '_':
815 case '-': // tbl also accepts this
816 got_format = 1;
817 t = FORMAT_HLINE;
818 break;
819 case '=':
820 got_format = 1;
821 t = FORMAT_DOUBLE_HLINE;
822 break;
823 case '.':
824 got_period = 1;
825 break;
826 case '|':
827 pre_vline++;
828 break;
829 case ' ':
830 case '\t':
831 case '\n':
832 break;
833 default:
834 if (c == opt->tab_char)
835 break;
836 error("unrecognised format `%1'", char(c));
837 free_input_entry_format_list(list);
838 return 0;
840 if (got_period)
841 break;
842 c = in.get();
843 if (got_format)
844 break;
846 if (got_period)
847 break;
848 list = new input_entry_format(t, list);
849 if (pre_vline)
850 list->pre_vline = pre_vline;
851 int success = 1;
852 do {
853 switch (c) {
854 case '0':
855 case '1':
856 case '2':
857 case '3':
858 case '4':
859 case '5':
860 case '6':
861 case '7':
862 case '8':
863 case '9':
865 int w = 0;
866 do {
867 w = w*10 + (c - '0');
868 c = in.get();
869 } while (c != EOF && csdigit(c));
870 list->separation = w;
872 break;
873 case 'B':
874 case 'b':
875 c = in.get();
876 list->font = "B";
877 break;
878 case 'd':
879 case 'D':
880 c = in.get();
881 list->vertical_alignment = entry_modifier::BOTTOM;
882 break;
883 case 'e':
884 case 'E':
885 c = in.get();
886 list->equal = 1;
887 // `e' and `x' are mutually exclusive
888 list->expand = 0;
889 break;
890 case 'f':
891 case 'F':
892 do {
893 c = in.get();
894 } while (c == ' ' || c == '\t');
895 if (c == EOF) {
896 error("missing font name");
897 break;
899 if (c == '(') {
900 for (;;) {
901 c = in.get();
902 if (c == EOF || c == ' ' || c == '\t') {
903 error("missing `)'");
904 break;
906 if (c == ')') {
907 c = in.get();
908 break;
910 list->font += char(c);
913 else {
914 list->font = c;
915 char cc = c;
916 c = in.get();
917 if (!csdigit(cc)
918 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
919 list->font += char(c);
920 c = in.get();
923 break;
924 case 'I':
925 case 'i':
926 c = in.get();
927 list->font = "I";
928 break;
929 case 'm':
930 case 'M':
931 do {
932 c = in.get();
933 } while (c == ' ' || c == '\t');
934 if (c == EOF) {
935 error("missing macro name");
936 break;
938 if (c == '(') {
939 for (;;) {
940 c = in.get();
941 if (c == EOF || c == ' ' || c == '\t') {
942 error("missing `)'");
943 break;
945 if (c == ')') {
946 c = in.get();
947 break;
949 list->macro += char(c);
952 else {
953 list->macro = c;
954 char cc = c;
955 c = in.get();
956 if (!csdigit(cc)
957 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
958 list->macro += char(c);
959 c = in.get();
962 break;
963 case 'p':
964 case 'P':
965 c = in.get();
966 list->point_size.val = 0;
967 list->point_size.inc = 0;
968 if (c == '+' || c == '-') {
969 list->point_size.inc = (c == '+' ? 1 : -1);
970 c = in.get();
972 if (c == EOF || !csdigit(c)) {
973 error("`p' modifier must be followed by number");
974 list->point_size.inc = 0;
976 else {
977 do {
978 list->point_size.val *= 10;
979 list->point_size.val += c - '0';
980 c = in.get();
981 } while (c != EOF && csdigit(c));
983 if (list->point_size.val > MAX_POINT_SIZE
984 || list->point_size.val < -MAX_POINT_SIZE) {
985 error("unreasonable point size");
986 list->point_size.val = 0;
987 list->point_size.inc = 0;
989 break;
990 case 't':
991 case 'T':
992 c = in.get();
993 list->vertical_alignment = entry_modifier::TOP;
994 break;
995 case 'u':
996 case 'U':
997 c = in.get();
998 list->stagger = 1;
999 break;
1000 case 'v':
1001 case 'V':
1002 c = in.get();
1003 list->vertical_spacing.val = 0;
1004 list->vertical_spacing.inc = 0;
1005 if (c == '+' || c == '-') {
1006 list->vertical_spacing.inc = (c == '+' ? 1 : -1);
1007 c = in.get();
1009 if (c == EOF || !csdigit(c)) {
1010 error("`v' modifier must be followed by number");
1011 list->vertical_spacing.inc = 0;
1013 else {
1014 do {
1015 list->vertical_spacing.val *= 10;
1016 list->vertical_spacing.val += c - '0';
1017 c = in.get();
1018 } while (c != EOF && csdigit(c));
1020 if (list->vertical_spacing.val > MAX_VERTICAL_SPACING
1021 || list->vertical_spacing.val < -MAX_VERTICAL_SPACING) {
1022 error("unreasonable vertical spacing");
1023 list->vertical_spacing.val = 0;
1024 list->vertical_spacing.inc = 0;
1026 break;
1027 case 'w':
1028 case 'W':
1029 c = in.get();
1030 while (c == ' ' || c == '\t')
1031 c = in.get();
1032 if (c == '(') {
1033 list->width = "";
1034 c = in.get();
1035 while (c != ')') {
1036 if (c == EOF || c == '\n') {
1037 error("missing `)'");
1038 free_input_entry_format_list(list);
1039 return 0;
1041 list->width += c;
1042 c = in.get();
1044 c = in.get();
1046 else {
1047 if (c == '+' || c == '-') {
1048 list->width = char(c);
1049 c = in.get();
1051 else
1052 list->width = "";
1053 if (c == EOF || !csdigit(c))
1054 error("bad argument for `w' modifier");
1055 else {
1056 do {
1057 list->width += char(c);
1058 c = in.get();
1059 } while (c != EOF && csdigit(c));
1062 // `w' and `x' are mutually exclusive
1063 list->expand = 0;
1064 break;
1065 case 'x':
1066 case 'X':
1067 c = in.get();
1068 list->expand = 1;
1069 // `x' and `e' are mutually exclusive
1070 list->equal = 0;
1071 // `x' and `w' are mutually exclusive
1072 list->width = "";
1073 break;
1074 case 'z':
1075 case 'Z':
1076 c = in.get();
1077 list->zero_width = 1;
1078 break;
1079 case '|':
1080 c = in.get();
1081 list->vline++;
1082 break;
1083 case ' ':
1084 case '\t':
1085 c = in.get();
1086 break;
1087 default:
1088 if (c == opt->tab_char)
1089 c = in.get();
1090 else
1091 success = 0;
1092 break;
1094 } while (success);
1095 if (list->vline > 2) {
1096 list->vline = 2;
1097 error("more than 2 vertical bars between key letters");
1099 if (c == '\n' || c == ',') {
1100 c = in.get();
1101 list->last_column = 1;
1104 if (c == '.') {
1105 do {
1106 c = in.get();
1107 } while (c == ' ' || c == '\t');
1108 if (c != '\n') {
1109 error("`.' not last character on line");
1110 free_input_entry_format_list(list);
1111 return 0;
1114 if (!list) {
1115 error("no format");
1116 free_input_entry_format_list(list);
1117 return 0;
1119 list->last_column = 1;
1120 // now reverse the list so that the first row is at the beginning
1121 input_entry_format *rev = 0;
1122 while (list != 0) {
1123 input_entry_format *tem = list->next;
1124 list->next = rev;
1125 rev = list;
1126 list = tem;
1128 list = rev;
1129 input_entry_format *tem;
1131 #if 0
1132 for (tem = list; tem; tem = tem->next)
1133 tem->debug_print();
1134 putc('\n', stderr);
1135 #endif
1136 // compute number of columns and rows
1137 int ncolumns = 0;
1138 int nrows = 0;
1139 int col = 0;
1140 for (tem = list; tem; tem = tem->next) {
1141 if (tem->last_column) {
1142 if (col >= ncolumns)
1143 ncolumns = col + 1;
1144 col = 0;
1145 nrows++;
1147 else
1148 col++;
1150 int row;
1151 format *f;
1152 if (current_format) {
1153 if (ncolumns > current_format->ncolumns) {
1154 error("cannot increase the number of columns in a continued format");
1155 free_input_entry_format_list(list);
1156 return 0;
1158 f = current_format;
1159 row = f->nrows;
1160 f->add_rows(nrows);
1162 else {
1163 f = new format(nrows, ncolumns);
1164 row = 0;
1166 col = 0;
1167 for (tem = list; tem; tem = tem->next) {
1168 f->entry[row][col] = *tem;
1169 if (col < ncolumns - 1) {
1170 // use the greatest separation
1171 if (tem->separation > f->separation[col]) {
1172 if (current_format)
1173 error("cannot change column separation in continued format");
1174 else
1175 f->separation[col] = tem->separation;
1178 else if (tem->separation >= 0)
1179 error("column separation specified for last column");
1180 if (tem->equal && !f->equal[col]) {
1181 if (current_format)
1182 error("cannot change which columns are equal in continued format");
1183 else
1184 f->equal[col] = 1;
1186 if (tem->expand && !f->expand[col]) {
1187 if (current_format)
1188 error("cannot change which columns are expanded in continued format");
1189 else {
1190 f->expand[col] = 1;
1191 have_expand = 1;
1194 if (!tem->width.empty()) {
1195 // use the last width
1196 if (!f->width[col].empty() && f->width[col] != tem->width)
1197 error("multiple widths for column %1", col + 1);
1198 f->width[col] = tem->width;
1200 if (tem->pre_vline) {
1201 assert(col == 0);
1202 f->vline[row][col] = tem->pre_vline;
1204 f->vline[row][col + 1] = tem->vline;
1205 if (tem->last_column) {
1206 row++;
1207 col = 0;
1209 else
1210 col++;
1212 free_input_entry_format_list(list);
1213 for (col = 0; col < ncolumns; col++) {
1214 entry_format *e = f->entry[f->nrows - 1] + col;
1215 if (e->type != FORMAT_HLINE
1216 && e->type != FORMAT_DOUBLE_HLINE
1217 && e->type != FORMAT_SPAN)
1218 break;
1220 if (col >= ncolumns) {
1221 error("last row of format is all lines");
1222 delete f;
1223 return 0;
1225 if (have_expand && (opt->flags & table::EXPAND)) {
1226 error("ignoring global `expand' option because of `x' specifiers");
1227 opt->flags &= ~table::EXPAND;
1229 return f;
1232 table *process_data(table_input &in, format *f, options *opt)
1234 char tab_char = opt->tab_char;
1235 int ncolumns = f->ncolumns;
1236 int current_row = 0;
1237 int format_index = 0;
1238 int give_up = 0;
1239 enum { DATA_INPUT_LINE, TROFF_INPUT_LINE, SINGLE_HLINE, DOUBLE_HLINE } type;
1240 table *tbl = new table(ncolumns, opt->flags, opt->linesize,
1241 opt->decimal_point_char);
1242 if (opt->delim[0] != '\0')
1243 tbl->set_delim(opt->delim[0], opt->delim[1]);
1244 for (;;) {
1245 // first determine what type of line this is
1246 int c = in.get();
1247 if (c == EOF)
1248 break;
1249 if (c == '.') {
1250 int d = in.get();
1251 if (d != EOF && csdigit(d)) {
1252 in.unget(d);
1253 type = DATA_INPUT_LINE;
1255 else {
1256 in.unget(d);
1257 type = TROFF_INPUT_LINE;
1260 else if (c == '_' || c == '=') {
1261 int d = in.get();
1262 if (d == '\n') {
1263 if (c == '_')
1264 type = SINGLE_HLINE;
1265 else
1266 type = DOUBLE_HLINE;
1268 else {
1269 in.unget(d);
1270 type = DATA_INPUT_LINE;
1273 else {
1274 type = DATA_INPUT_LINE;
1276 switch (type) {
1277 case DATA_INPUT_LINE:
1279 string input_entry;
1280 if (format_index >= f->nrows)
1281 format_index = f->nrows - 1;
1282 // A format row that is all lines doesn't use up a data line.
1283 while (format_index < f->nrows - 1) {
1284 int cnt;
1285 for (cnt = 0; cnt < ncolumns; cnt++) {
1286 entry_format *e = f->entry[format_index] + cnt;
1287 if (e->type != FORMAT_HLINE
1288 && e->type != FORMAT_DOUBLE_HLINE
1289 // Unfortunately tbl treats a span as needing data.
1290 // && e->type != FORMAT_SPAN
1292 break;
1294 if (cnt < ncolumns)
1295 break;
1296 for (cnt = 0; cnt < ncolumns; cnt++)
1297 tbl->add_entry(current_row, cnt, input_entry,
1298 f->entry[format_index] + cnt, current_filename,
1299 current_lineno);
1300 tbl->add_vlines(current_row, f->vline[format_index]);
1301 format_index++;
1302 current_row++;
1304 entry_format *line_format = f->entry[format_index];
1305 int col = 0;
1306 int row_comment = 0;
1307 for (;;) {
1308 if (c == tab_char || c == '\n') {
1309 int ln = current_lineno;
1310 if (c == '\n')
1311 --ln;
1312 if ((opt->flags & table::NOSPACES))
1313 input_entry.remove_spaces();
1314 while (col < ncolumns
1315 && line_format[col].type == FORMAT_SPAN) {
1316 tbl->add_entry(current_row, col, "", &line_format[col],
1317 current_filename, ln);
1318 col++;
1320 if (c == '\n' && input_entry.length() == 2
1321 && input_entry[0] == 'T' && input_entry[1] == '{') {
1322 input_entry = "";
1323 ln++;
1324 enum {
1325 START, MIDDLE, GOT_T, GOT_RIGHT_BRACE, GOT_DOT,
1326 GOT_l, GOT_lf, END
1327 } state = START;
1328 while (state != END) {
1329 c = in.get();
1330 if (c == EOF)
1331 break;
1332 switch (state) {
1333 case START:
1334 if (c == 'T')
1335 state = GOT_T;
1336 else if (c == '.')
1337 state = GOT_DOT;
1338 else {
1339 input_entry += c;
1340 if (c != '\n')
1341 state = MIDDLE;
1343 break;
1344 case GOT_T:
1345 if (c == '}')
1346 state = GOT_RIGHT_BRACE;
1347 else {
1348 input_entry += 'T';
1349 input_entry += c;
1350 state = c == '\n' ? START : MIDDLE;
1352 break;
1353 case GOT_DOT:
1354 if (c == 'l')
1355 state = GOT_l;
1356 else {
1357 input_entry += '.';
1358 input_entry += c;
1359 state = c == '\n' ? START : MIDDLE;
1361 break;
1362 case GOT_l:
1363 if (c == 'f')
1364 state = GOT_lf;
1365 else {
1366 input_entry += ".l";
1367 input_entry += c;
1368 state = c == '\n' ? START : MIDDLE;
1370 break;
1371 case GOT_lf:
1372 if (c == ' ' || c == '\n' || compatible_flag) {
1373 string args;
1374 input_entry += ".lf";
1375 while (c != EOF) {
1376 args += c;
1377 if (c == '\n')
1378 break;
1379 c = in.get();
1381 args += '\0';
1382 interpret_lf_args(args.contents());
1383 // remove the '\0'
1384 args.set_length(args.length() - 1);
1385 input_entry += args;
1386 state = START;
1388 else {
1389 input_entry += ".lf";
1390 input_entry += c;
1391 state = MIDDLE;
1393 break;
1394 case GOT_RIGHT_BRACE:
1395 if ((opt->flags & table::NOSPACES)) {
1396 while (c == ' ')
1397 c = in.get();
1398 if (c == EOF)
1399 break;
1401 if (c == '\n' || c == tab_char)
1402 state = END;
1403 else {
1404 input_entry += 'T';
1405 input_entry += '}';
1406 input_entry += c;
1407 state = MIDDLE;
1409 break;
1410 case MIDDLE:
1411 if (c == '\n')
1412 state = START;
1413 input_entry += c;
1414 break;
1415 case END:
1416 default:
1417 assert(0);
1420 if (c == EOF) {
1421 error("end of data in middle of text block");
1422 give_up = 1;
1423 break;
1426 if (col >= ncolumns) {
1427 if (!input_entry.empty()) {
1428 if (input_entry.length() >= 2
1429 && input_entry[0] == '\\'
1430 && input_entry[1] == '"')
1431 row_comment = 1;
1432 else if (!row_comment) {
1433 if (c == '\n')
1434 in.unget(c);
1435 input_entry += '\0';
1436 error("excess data entry `%1' discarded",
1437 input_entry.contents());
1438 if (c == '\n')
1439 (void)in.get();
1443 else
1444 tbl->add_entry(current_row, col, input_entry,
1445 &line_format[col], current_filename, ln);
1446 col++;
1447 if (c == '\n')
1448 break;
1449 input_entry = "";
1451 else
1452 input_entry += c;
1453 c = in.get();
1454 if (c == EOF)
1455 break;
1457 if (give_up)
1458 break;
1459 input_entry = "";
1460 for (; col < ncolumns; col++)
1461 tbl->add_entry(current_row, col, input_entry, &line_format[col],
1462 current_filename, current_lineno - 1);
1463 tbl->add_vlines(current_row, f->vline[format_index]);
1464 current_row++;
1465 format_index++;
1467 break;
1468 case TROFF_INPUT_LINE:
1470 string line;
1471 int ln = current_lineno;
1472 for (;;) {
1473 line += c;
1474 if (c == '\n')
1475 break;
1476 c = in.get();
1477 if (c == EOF) {
1478 break;
1481 tbl->add_text_line(current_row, line, current_filename, ln);
1482 if (line.length() >= 4
1483 && line[0] == '.' && line[1] == 'T' && line[2] == '&') {
1484 format *newf = process_format(in, opt, f);
1485 if (newf == 0)
1486 give_up = 1;
1487 else
1488 f = newf;
1490 if (line.length() >= 3
1491 && line[0] == '.' && line[1] == 'l' && line[2] == 'f') {
1492 line += '\0';
1493 interpret_lf_args(line.contents() + 3);
1496 break;
1497 case SINGLE_HLINE:
1498 tbl->add_single_hline(current_row);
1499 break;
1500 case DOUBLE_HLINE:
1501 tbl->add_double_hline(current_row);
1502 break;
1503 default:
1504 assert(0);
1506 if (give_up)
1507 break;
1509 if (!give_up && current_row == 0) {
1510 error("no real data");
1511 give_up = 1;
1513 if (give_up) {
1514 delete tbl;
1515 return 0;
1517 // Do this here rather than at the beginning in case continued formats
1518 // change it.
1519 int i;
1520 for (i = 0; i < ncolumns - 1; i++)
1521 if (f->separation[i] >= 0)
1522 tbl->set_column_separation(i, f->separation[i]);
1523 for (i = 0; i < ncolumns; i++)
1524 if (!f->width[i].empty())
1525 tbl->set_minimum_width(i, f->width[i]);
1526 for (i = 0; i < ncolumns; i++)
1527 if (f->equal[i])
1528 tbl->set_equal_column(i);
1529 for (i = 0; i < ncolumns; i++)
1530 if (f->expand[i])
1531 tbl->set_expand_column(i);
1532 return tbl;
1535 void process_table(table_input &in)
1537 options *opt = 0;
1538 format *form = 0;
1539 table *tbl = 0;
1540 if ((opt = process_options(in)) != 0
1541 && (form = process_format(in, opt)) != 0
1542 && (tbl = process_data(in, form, opt)) != 0) {
1543 tbl->print();
1544 delete tbl;
1546 else {
1547 error("giving up on this table");
1548 while (in.get() != EOF)
1551 delete opt;
1552 delete form;
1553 if (!in.ended())
1554 error("premature end of file");
1557 static void usage(FILE *stream)
1559 fprintf(stream, "usage: %s [ -vC ] [ files... ]\n", program_name);
1562 int main(int argc, char **argv)
1564 program_name = argv[0];
1565 static char stderr_buf[BUFSIZ];
1566 setbuf(stderr, stderr_buf);
1567 int opt;
1568 static const struct option long_options[] = {
1569 { "help", no_argument, 0, CHAR_MAX + 1 },
1570 { "version", no_argument, 0, 'v' },
1571 { NULL, 0, 0, 0 }
1573 while ((opt = getopt_long(argc, argv, "vCT:", long_options, NULL)) != EOF)
1574 switch (opt) {
1575 case 'C':
1576 compatible_flag = 1;
1577 break;
1578 case 'v':
1580 printf("GNU tbl (groff) version %s\n", Version_string);
1581 exit(0);
1582 break;
1584 case 'T':
1585 // I'm sick of getting bug reports from IRIX users
1586 break;
1587 case CHAR_MAX + 1: // --help
1588 usage(stdout);
1589 exit(0);
1590 break;
1591 case '?':
1592 usage(stderr);
1593 exit(1);
1594 break;
1595 default:
1596 assert(0);
1598 printf(".if !\\n(.g .ab GNU tbl requires GNU troff.\n"
1599 ".if !dTS .ds TS\n"
1600 ".if !dTE .ds TE\n");
1602 file_case *fcp;
1603 do /*while (optind < argc)*/ {
1604 if ((current_filename = argv[optind++]) == NULL)
1605 current_filename = "-";
1606 fcp = file_case::muxer(current_filename);
1607 if (fcp == NULL) {
1608 assert(strcmp(current_filename, "-"));
1609 fatal("can't open `%1': %2", current_filename, strerror(errno));
1612 current_lineno = 1;
1613 printf(".lf 1 %s\n", current_filename);
1614 process_input_file(fcp);
1616 delete fcp;
1617 } while (optind < argc);
1619 if (ferror(stdout) || fflush(stdout) < 0)
1620 fatal("output error");
1621 return 0;