Copyright 2017
[s-roff.git] / src / pre-tbl / main.cpp
blobbf04dfef44752c856e8517da2abcc35186cc5980
1 /*@
2 * Copyright (c) 2014 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
4 * Copyright (C) 1989 - 1992, 2000 - 2005, 2007, 2008
5 * Free Software Foundation, Inc.
6 * Written by James Clark (jjc@jclark.com)
8 * This 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 * This 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.
23 #include "config.h"
24 #include "tbl-config.h"
26 #include "file_case.h"
28 #include "table.h"
30 int compatible_flag = 0;
32 class table_input
34 file_case *_fcp;
35 enum { START, MIDDLE,
36 REREAD_T, REREAD_TE, REREAD_E,
37 LEADER_1, LEADER_2, LEADER_3, LEADER_4,
38 END, ERROR } state;
39 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 class options
349 public:
350 unsigned flags;
351 int linesize;
352 char delim[2];
353 char tab_char;
354 char decimal_point_char;
356 options();
359 options::options()
360 : flags(0), linesize(0), tab_char('\t'), decimal_point_char('.')
362 delim[0] = delim[1] = '\0';
365 // Return non-zero if p and q are the same ignoring case.
367 int strieq(const char *p, const char *q)
369 for (; cmlower(*p) == cmlower(*q); p++, q++)
370 if (*p == '\0')
371 return 1;
372 return 0;
375 // return 0 if we should give up in this table
377 options *process_options(table_input &in)
379 options *opt = new options;
380 string line;
381 int level = 0;
382 for (;;) {
383 int c = in.get();
384 if (c == EOF) {
385 int i = line.length();
386 while (--i >= 0)
387 in.unget(line[i]);
388 return opt;
390 if (c == '\n') {
391 in.unget(c);
392 int i = line.length();
393 while (--i >= 0)
394 in.unget(line[i]);
395 return opt;
397 else if (c == '(')
398 level++;
399 else if (c == ')')
400 level--;
401 else if (c == ';' && level == 0) {
402 line += '\0';
403 break;
405 line += c;
407 if (line.empty())
408 return opt;
409 char *p = &line[0];
410 for (;;) {
411 while (!csalpha(*p) && *p != '\0')
412 p++;
413 if (*p == '\0')
414 break;
415 char *q = p;
416 while (csalpha(*q))
417 q++;
418 char *arg = 0;
419 if (*q != '(' && *q != '\0')
420 *q++ = '\0';
421 while (csspace(*q))
422 q++;
423 if (*q == '(') {
424 *q++ = '\0';
425 arg = q;
426 while (*q != ')' && *q != '\0')
427 q++;
428 if (*q == '\0')
429 error("missing `)'");
430 else
431 *q++ = '\0';
433 if (*p == '\0') {
434 if (arg)
435 error("argument without option");
437 else if (strieq(p, "tab")) {
438 if (!arg)
439 error("`tab' option requires argument in parentheses");
440 else {
441 if (arg[0] == '\0' || arg[1] != '\0')
442 error("argument to `tab' option must be a single character");
443 else
444 opt->tab_char = arg[0];
447 else if (strieq(p, "linesize")) {
448 if (!arg)
449 error("`linesize' option requires argument in parentheses");
450 else {
451 if (sscanf(arg, "%d", &opt->linesize) != 1)
452 error("bad linesize `%s'", arg);
453 else if (opt->linesize <= 0) {
454 error("linesize must be positive");
455 opt->linesize = 0;
459 else if (strieq(p, "delim")) {
460 if (!arg)
461 error("`delim' option requires argument in parentheses");
462 else if (arg[0] == '\0' || arg[1] == '\0' || arg[2] != '\0')
463 error("argument to `delim' option must be two characters");
464 else {
465 opt->delim[0] = arg[0];
466 opt->delim[1] = arg[1];
469 else if (strieq(p, "center") || strieq(p, "centre")) {
470 if (arg)
471 error("`center' option does not take an argument");
472 opt->flags |= table::CENTER;
474 else if (strieq(p, "expand")) {
475 if (arg)
476 error("`expand' option does not take an argument");
477 opt->flags |= table::EXPAND;
479 else if (strieq(p, "box") || strieq(p, "frame")) {
480 if (arg)
481 error("`box' option does not take an argument");
482 opt->flags |= table::BOX;
484 else if (strieq(p, "doublebox") || strieq(p, "doubleframe")) {
485 if (arg)
486 error("`doublebox' option does not take an argument");
487 opt->flags |= table::DOUBLEBOX;
489 else if (strieq(p, "allbox")) {
490 if (arg)
491 error("`allbox' option does not take an argument");
492 opt->flags |= table::ALLBOX;
494 else if (strieq(p, "nokeep")) {
495 if (arg)
496 error("`nokeep' option does not take an argument");
497 opt->flags |= table::NOKEEP;
499 else if (strieq(p, "nospaces")) {
500 if (arg)
501 error("`nospaces' option does not take an argument");
502 opt->flags |= table::NOSPACES;
503 } else if (strieq(p, "nowarn")) { // TODO if(arg)goto jerr; anywhere here
504 if (arg)
505 error("`nowarn' option does not take an argument");
506 opt->flags |= table::NOWARN;
508 else if (strieq(p, "decimalpoint")) {
509 if (!arg)
510 error("`decimalpoint' option requires argument in parentheses");
511 else {
512 if (arg[0] == '\0' || arg[1] != '\0')
513 error("argument to `decimalpoint' option must be a single character");
514 else
515 opt->decimal_point_char = arg[0];
518 else if (strieq(p, "experimental")) {
519 opt->flags |= table::EXPERIMENTAL;
521 else {
522 error("unrecognised global option `%1'", p);
523 // delete opt;
524 // return 0;
526 p = q;
528 return opt;
531 entry_modifier::entry_modifier()
532 : vertical_alignment(CENTER), zero_width(0), stagger(0)
534 vertical_spacing.inc = vertical_spacing.val = 0;
535 point_size.inc = point_size.val = 0;
538 entry_modifier::~entry_modifier()
542 entry_format::entry_format() : type(FORMAT_LEFT)
546 entry_format::entry_format(format_type t) : type(t)
550 void entry_format::debug_print() const
552 switch (type) {
553 case FORMAT_LEFT:
554 putc('l', stderr);
555 break;
556 case FORMAT_CENTER:
557 putc('c', stderr);
558 break;
559 case FORMAT_RIGHT:
560 putc('r', stderr);
561 break;
562 case FORMAT_NUMERIC:
563 putc('n', stderr);
564 break;
565 case FORMAT_ALPHABETIC:
566 putc('a', stderr);
567 break;
568 case FORMAT_SPAN:
569 putc('s', stderr);
570 break;
571 case FORMAT_VSPAN:
572 putc('^', stderr);
573 break;
574 case FORMAT_HLINE:
575 putc('_', stderr);
576 break;
577 case FORMAT_DOUBLE_HLINE:
578 putc('=', stderr);
579 break;
580 default:
581 assert(0);
582 break;
584 if (point_size.val != 0) {
585 putc('p', stderr);
586 if (point_size.inc > 0)
587 putc('+', stderr);
588 else if (point_size.inc < 0)
589 putc('-', stderr);
590 fprintf(stderr, "%d ", point_size.val);
592 if (vertical_spacing.val != 0) {
593 putc('v', stderr);
594 if (vertical_spacing.inc > 0)
595 putc('+', stderr);
596 else if (vertical_spacing.inc < 0)
597 putc('-', stderr);
598 fprintf(stderr, "%d ", vertical_spacing.val);
600 if (!font.empty()) {
601 putc('f', stderr);
602 put_string(font, stderr);
603 putc(' ', stderr);
605 if (!macro.empty()) {
606 putc('m', stderr);
607 put_string(macro, stderr);
608 putc(' ', stderr);
610 switch (vertical_alignment) {
611 case entry_modifier::CENTER:
612 break;
613 case entry_modifier::TOP:
614 putc('t', stderr);
615 break;
616 case entry_modifier::BOTTOM:
617 putc('d', stderr);
618 break;
620 if (zero_width)
621 putc('z', stderr);
622 if (stagger)
623 putc('u', stderr);
626 class format
628 public:
629 int nrows;
630 int ncolumns;
631 int *separation;
632 string *width;
633 char *equal;
634 char *expand;
635 entry_format **entry;
636 char **vline;
638 format(int nr, int nc);
639 ~format();
640 void add_rows(int n);
643 format::format(int nr, int nc) : nrows(nr), ncolumns(nc)
645 int i;
646 separation = ncolumns > 1 ? new int[ncolumns - 1] : 0;
647 for (i = 0; i < ncolumns-1; i++)
648 separation[i] = -1;
649 width = new string[ncolumns];
650 equal = new char[ncolumns];
651 expand = new char[ncolumns];
652 for (i = 0; i < ncolumns; i++) {
653 equal[i] = 0;
654 expand[i] = 0;
656 entry = new entry_format *[nrows];
657 for (i = 0; i < nrows; i++)
658 entry[i] = new entry_format[ncolumns];
659 vline = new char*[nrows];
660 for (i = 0; i < nrows; i++) {
661 vline[i] = new char[ncolumns+1];
662 for (int j = 0; j < ncolumns+1; j++)
663 vline[i][j] = 0;
667 void format::add_rows(int n)
669 int i;
670 char **old_vline = vline;
671 vline = new char*[nrows + n];
672 for (i = 0; i < nrows; i++)
673 vline[i] = old_vline[i];
674 a_delete old_vline;
675 for (i = 0; i < n; i++) {
676 vline[nrows + i] = new char[ncolumns + 1];
677 for (int j = 0; j < ncolumns + 1; j++)
678 vline[nrows + i][j] = 0;
680 entry_format **old_entry = entry;
681 entry = new entry_format *[nrows + n];
682 for (i = 0; i < nrows; i++)
683 entry[i] = old_entry[i];
684 a_delete old_entry;
685 for (i = 0; i < n; i++)
686 entry[nrows + i] = new entry_format[ncolumns];
687 nrows += n;
690 format::~format()
692 a_delete separation;
693 ad_delete(ncolumns) width;
694 a_delete equal;
695 a_delete expand;
696 for (int i = 0; i < nrows; i++) {
697 a_delete vline[i];
698 ad_delete(ncolumns) entry[i];
700 a_delete vline;
701 a_delete entry;
704 class input_entry_format
705 : public entry_format
707 public:
708 input_entry_format *next;
709 string width;
710 int separation;
711 int vline;
712 int pre_vline;
713 int last_column;
714 int equal;
715 int expand;
717 input_entry_format(format_type, input_entry_format * = 0);
718 ~input_entry_format();
719 void debug_print();
722 input_entry_format::input_entry_format(format_type t, input_entry_format *p)
723 : entry_format(t), next(p)
725 separation = -1;
726 last_column = 0;
727 vline = 0;
728 pre_vline = 0;
729 equal = 0;
730 expand = 0;
733 input_entry_format::~input_entry_format()
737 void free_input_entry_format_list(input_entry_format *list)
739 while (list) {
740 input_entry_format *tem = list;
741 list = list->next;
742 delete tem;
746 void input_entry_format::debug_print()
748 int i;
749 for (i = 0; i < pre_vline; i++)
750 putc('|', stderr);
751 entry_format::debug_print();
752 if (!width.empty()) {
753 putc('w', stderr);
754 putc('(', stderr);
755 put_string(width, stderr);
756 putc(')', stderr);
758 if (equal)
759 putc('e', stderr);
760 if (expand)
761 putc('x', stderr);
762 if (separation >= 0)
763 fprintf(stderr, "%d", separation);
764 for (i = 0; i < vline; i++)
765 putc('|', stderr);
766 if (last_column)
767 putc(',', stderr);
770 // Return zero if we should give up on this table.
771 // If this is a continuation format line, current_format will be the current
772 // format line.
774 format *process_format(table_input &in, options *opt,
775 format *current_format = 0)
777 input_entry_format *list = 0;
778 int have_expand = 0;
779 int c = in.get();
780 for (;;) {
781 int pre_vline = 0;
782 int got_format = 0;
783 int got_period = 0;
784 format_type t = FORMAT_LEFT;
785 for (;;) {
786 if (c == EOF) {
787 error("end of input while processing format");
788 free_input_entry_format_list(list);
789 return 0;
791 switch (c) {
792 case 'n':
793 case 'N':
794 t = FORMAT_NUMERIC;
795 got_format = 1;
796 break;
797 case 'a':
798 case 'A':
799 got_format = 1;
800 t = FORMAT_ALPHABETIC;
801 break;
802 case 'c':
803 case 'C':
804 got_format = 1;
805 t = FORMAT_CENTER;
806 break;
807 case 'l':
808 case 'L':
809 got_format = 1;
810 t = FORMAT_LEFT;
811 break;
812 case 'r':
813 case 'R':
814 got_format = 1;
815 t = FORMAT_RIGHT;
816 break;
817 case 's':
818 case 'S':
819 got_format = 1;
820 t = FORMAT_SPAN;
821 break;
822 case '^':
823 got_format = 1;
824 t = FORMAT_VSPAN;
825 break;
826 case '_':
827 case '-': // tbl also accepts this
828 got_format = 1;
829 t = FORMAT_HLINE;
830 break;
831 case '=':
832 got_format = 1;
833 t = FORMAT_DOUBLE_HLINE;
834 break;
835 case '.':
836 got_period = 1;
837 break;
838 case '|':
839 pre_vline++;
840 break;
841 case ' ':
842 case '\t':
843 case '\n':
844 break;
845 default:
846 if (c == opt->tab_char)
847 break;
848 error("unrecognised format `%1'", char(c));
849 free_input_entry_format_list(list);
850 return 0;
852 if (got_period)
853 break;
854 c = in.get();
855 if (got_format)
856 break;
858 if (got_period)
859 break;
860 list = new input_entry_format(t, list);
861 if (pre_vline)
862 list->pre_vline = pre_vline;
863 int success = 1;
864 do {
865 switch (c) {
866 case '0':
867 case '1':
868 case '2':
869 case '3':
870 case '4':
871 case '5':
872 case '6':
873 case '7':
874 case '8':
875 case '9':
877 int w = 0;
878 do {
879 w = w*10 + (c - '0');
880 c = in.get();
881 } while (c != EOF && csdigit(c));
882 list->separation = w;
884 break;
885 case 'B':
886 case 'b':
887 c = in.get();
888 list->font = "B";
889 break;
890 case 'd':
891 case 'D':
892 c = in.get();
893 list->vertical_alignment = entry_modifier::BOTTOM;
894 break;
895 case 'e':
896 case 'E':
897 c = in.get();
898 list->equal = 1;
899 // `e' and `x' are mutually exclusive
900 list->expand = 0;
901 break;
902 case 'f':
903 case 'F':
904 do {
905 c = in.get();
906 } while (c == ' ' || c == '\t');
907 if (c == EOF) {
908 error("missing font name");
909 break;
911 if (c == '(') {
912 for (;;) {
913 c = in.get();
914 if (c == EOF || c == ' ' || c == '\t') {
915 error("missing `)'");
916 break;
918 if (c == ')') {
919 c = in.get();
920 break;
922 list->font += char(c);
925 else {
926 list->font = c;
927 char cc = c;
928 c = in.get();
929 if (!csdigit(cc)
930 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
931 list->font += char(c);
932 c = in.get();
935 break;
936 case 'I':
937 case 'i':
938 c = in.get();
939 list->font = "I";
940 break;
941 case 'm':
942 case 'M':
943 do {
944 c = in.get();
945 } while (c == ' ' || c == '\t');
946 if (c == EOF) {
947 error("missing macro name");
948 break;
950 if (c == '(') {
951 for (;;) {
952 c = in.get();
953 if (c == EOF || c == ' ' || c == '\t') {
954 error("missing `)'");
955 break;
957 if (c == ')') {
958 c = in.get();
959 break;
961 list->macro += char(c);
964 else {
965 list->macro = c;
966 char cc = c;
967 c = in.get();
968 if (!csdigit(cc)
969 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
970 list->macro += char(c);
971 c = in.get();
974 break;
975 case 'p':
976 case 'P':
977 c = in.get();
978 list->point_size.val = 0;
979 list->point_size.inc = 0;
980 if (c == '+' || c == '-') {
981 list->point_size.inc = (c == '+' ? 1 : -1);
982 c = in.get();
984 if (c == EOF || !csdigit(c)) {
985 error("`p' modifier must be followed by number");
986 list->point_size.inc = 0;
988 else {
989 do {
990 list->point_size.val *= 10;
991 list->point_size.val += c - '0';
992 c = in.get();
993 } while (c != EOF && csdigit(c));
995 if (list->point_size.val > MAX_POINT_SIZE
996 || list->point_size.val < -MAX_POINT_SIZE) {
997 error("unreasonable point size");
998 list->point_size.val = 0;
999 list->point_size.inc = 0;
1001 break;
1002 case 't':
1003 case 'T':
1004 c = in.get();
1005 list->vertical_alignment = entry_modifier::TOP;
1006 break;
1007 case 'u':
1008 case 'U':
1009 c = in.get();
1010 list->stagger = 1;
1011 break;
1012 case 'v':
1013 case 'V':
1014 c = in.get();
1015 list->vertical_spacing.val = 0;
1016 list->vertical_spacing.inc = 0;
1017 if (c == '+' || c == '-') {
1018 list->vertical_spacing.inc = (c == '+' ? 1 : -1);
1019 c = in.get();
1021 if (c == EOF || !csdigit(c)) {
1022 error("`v' modifier must be followed by number");
1023 list->vertical_spacing.inc = 0;
1025 else {
1026 do {
1027 list->vertical_spacing.val *= 10;
1028 list->vertical_spacing.val += c - '0';
1029 c = in.get();
1030 } while (c != EOF && csdigit(c));
1032 if (list->vertical_spacing.val > MAX_VERTICAL_SPACING
1033 || list->vertical_spacing.val < -MAX_VERTICAL_SPACING) {
1034 error("unreasonable vertical spacing");
1035 list->vertical_spacing.val = 0;
1036 list->vertical_spacing.inc = 0;
1038 break;
1039 case 'w':
1040 case 'W':
1041 c = in.get();
1042 while (c == ' ' || c == '\t')
1043 c = in.get();
1044 if (c == '(') {
1045 list->width = "";
1046 c = in.get();
1047 while (c != ')') {
1048 if (c == EOF || c == '\n') {
1049 error("missing `)'");
1050 free_input_entry_format_list(list);
1051 return 0;
1053 list->width += c;
1054 c = in.get();
1056 c = in.get();
1058 else {
1059 if (c == '+' || c == '-') {
1060 list->width = char(c);
1061 c = in.get();
1063 else
1064 list->width = "";
1065 if (c == EOF || !csdigit(c))
1066 error("bad argument for `w' modifier");
1067 else {
1068 do {
1069 list->width += char(c);
1070 c = in.get();
1071 } while (c != EOF && csdigit(c));
1074 // `w' and `x' are mutually exclusive
1075 list->expand = 0;
1076 break;
1077 case 'x':
1078 case 'X':
1079 c = in.get();
1080 list->expand = 1;
1081 // `x' and `e' are mutually exclusive
1082 list->equal = 0;
1083 // `x' and `w' are mutually exclusive
1084 list->width = "";
1085 break;
1086 case 'z':
1087 case 'Z':
1088 c = in.get();
1089 list->zero_width = 1;
1090 break;
1091 case '|':
1092 c = in.get();
1093 list->vline++;
1094 break;
1095 case ' ':
1096 case '\t':
1097 c = in.get();
1098 break;
1099 default:
1100 if (c == opt->tab_char)
1101 c = in.get();
1102 else
1103 success = 0;
1104 break;
1106 } while (success);
1107 if (list->vline > 2) {
1108 list->vline = 2;
1109 error("more than 2 vertical bars between key letters");
1111 if (c == '\n' || c == ',') {
1112 c = in.get();
1113 list->last_column = 1;
1116 if (c == '.') {
1117 do {
1118 c = in.get();
1119 } while (c == ' ' || c == '\t');
1120 if (c != '\n') {
1121 error("`.' not last character on line");
1122 free_input_entry_format_list(list);
1123 return 0;
1126 if (!list) {
1127 error("no format");
1128 free_input_entry_format_list(list);
1129 return 0;
1131 list->last_column = 1;
1132 // now reverse the list so that the first row is at the beginning
1133 input_entry_format *rev = 0;
1134 while (list != 0) {
1135 input_entry_format *tem = list->next;
1136 list->next = rev;
1137 rev = list;
1138 list = tem;
1140 list = rev;
1141 input_entry_format *tem;
1143 #if 0
1144 for (tem = list; tem; tem = tem->next)
1145 tem->debug_print();
1146 putc('\n', stderr);
1147 #endif
1148 // compute number of columns and rows
1149 int ncolumns = 0;
1150 int nrows = 0;
1151 int col = 0;
1152 for (tem = list; tem; tem = tem->next) {
1153 if (tem->last_column) {
1154 if (col >= ncolumns)
1155 ncolumns = col + 1;
1156 col = 0;
1157 nrows++;
1159 else
1160 col++;
1162 int row;
1163 format *f;
1164 if (current_format) {
1165 if (ncolumns > current_format->ncolumns) {
1166 error("cannot increase the number of columns in a continued format");
1167 free_input_entry_format_list(list);
1168 return 0;
1170 f = current_format;
1171 row = f->nrows;
1172 f->add_rows(nrows);
1174 else {
1175 f = new format(nrows, ncolumns);
1176 row = 0;
1178 col = 0;
1179 for (tem = list; tem; tem = tem->next) {
1180 f->entry[row][col] = *tem;
1181 if (col < ncolumns - 1) {
1182 // use the greatest separation
1183 if (tem->separation > f->separation[col]) {
1184 if (current_format)
1185 error("cannot change column separation in continued format");
1186 else
1187 f->separation[col] = tem->separation;
1190 else if (tem->separation >= 0)
1191 error("column separation specified for last column");
1192 if (tem->equal && !f->equal[col]) {
1193 if (current_format)
1194 error("cannot change which columns are equal in continued format");
1195 else
1196 f->equal[col] = 1;
1198 if (tem->expand && !f->expand[col]) {
1199 if (current_format)
1200 error("cannot change which columns are expanded in continued format");
1201 else {
1202 f->expand[col] = 1;
1203 have_expand = 1;
1206 if (!tem->width.empty()) {
1207 // use the last width
1208 if (!f->width[col].empty() && f->width[col] != tem->width)
1209 error("multiple widths for column %1", col + 1);
1210 f->width[col] = tem->width;
1212 if (tem->pre_vline) {
1213 assert(col == 0);
1214 f->vline[row][col] = tem->pre_vline;
1216 f->vline[row][col + 1] = tem->vline;
1217 if (tem->last_column) {
1218 row++;
1219 col = 0;
1221 else
1222 col++;
1224 free_input_entry_format_list(list);
1225 for (col = 0; col < ncolumns; col++) {
1226 entry_format *e = f->entry[f->nrows - 1] + col;
1227 if (e->type != FORMAT_HLINE
1228 && e->type != FORMAT_DOUBLE_HLINE
1229 && e->type != FORMAT_SPAN)
1230 break;
1232 if (col >= ncolumns) {
1233 error("last row of format is all lines");
1234 delete f;
1235 return 0;
1237 if (have_expand && (opt->flags & table::EXPAND)) {
1238 error("ignoring global `expand' option because of `x' specifiers");
1239 opt->flags &= ~table::EXPAND;
1241 return f;
1244 table *process_data(table_input &in, format *f, options *opt)
1246 char tab_char = opt->tab_char;
1247 int ncolumns = f->ncolumns;
1248 int current_row = 0;
1249 int format_index = 0;
1250 int give_up = 0;
1251 enum { DATA_INPUT_LINE, TROFF_INPUT_LINE, SINGLE_HLINE, DOUBLE_HLINE } type;
1252 table *tbl = new table(ncolumns, opt->flags, opt->linesize,
1253 opt->decimal_point_char);
1254 if (opt->delim[0] != '\0')
1255 tbl->set_delim(opt->delim[0], opt->delim[1]);
1256 for (;;) {
1257 // first determine what type of line this is
1258 int c = in.get();
1259 if (c == EOF)
1260 break;
1261 if (c == '.') {
1262 int d = in.get();
1263 if (d != EOF && csdigit(d)) {
1264 in.unget(d);
1265 type = DATA_INPUT_LINE;
1267 else {
1268 in.unget(d);
1269 type = TROFF_INPUT_LINE;
1272 else if (c == '_' || c == '=') {
1273 int d = in.get();
1274 if (d == '\n') {
1275 if (c == '_')
1276 type = SINGLE_HLINE;
1277 else
1278 type = DOUBLE_HLINE;
1280 else {
1281 in.unget(d);
1282 type = DATA_INPUT_LINE;
1285 else {
1286 type = DATA_INPUT_LINE;
1288 switch (type) {
1289 case DATA_INPUT_LINE:
1291 string input_entry;
1292 if (format_index >= f->nrows)
1293 format_index = f->nrows - 1;
1294 // A format row that is all lines doesn't use up a data line.
1295 while (format_index < f->nrows - 1) {
1296 int cnt;
1297 for (cnt = 0; cnt < ncolumns; cnt++) {
1298 entry_format *e = f->entry[format_index] + cnt;
1299 if (e->type != FORMAT_HLINE
1300 && e->type != FORMAT_DOUBLE_HLINE
1301 // Unfortunately tbl treats a span as needing data.
1302 // && e->type != FORMAT_SPAN
1304 break;
1306 if (cnt < ncolumns)
1307 break;
1308 for (cnt = 0; cnt < ncolumns; cnt++)
1309 tbl->add_entry(current_row, cnt, input_entry,
1310 f->entry[format_index] + cnt, current_filename,
1311 current_lineno);
1312 tbl->add_vlines(current_row, f->vline[format_index]);
1313 format_index++;
1314 current_row++;
1316 entry_format *line_format = f->entry[format_index];
1317 int col = 0;
1318 int row_comment = 0;
1319 for (;;) {
1320 if (c == tab_char || c == '\n') {
1321 int ln = current_lineno;
1322 if (c == '\n')
1323 --ln;
1324 if ((opt->flags & table::NOSPACES))
1325 input_entry.remove_spaces();
1326 while (col < ncolumns
1327 && line_format[col].type == FORMAT_SPAN) {
1328 tbl->add_entry(current_row, col, "", &line_format[col],
1329 current_filename, ln);
1330 col++;
1332 if (c == '\n' && input_entry.length() == 2
1333 && input_entry[0] == 'T' && input_entry[1] == '{') {
1334 input_entry = "";
1335 ln++;
1336 enum {
1337 START, MIDDLE, GOT_T, GOT_RIGHT_BRACE, GOT_DOT,
1338 GOT_l, GOT_lf, END
1339 } state = START;
1340 while (state != END) {
1341 c = in.get();
1342 if (c == EOF)
1343 break;
1344 switch (state) {
1345 case START:
1346 if (c == 'T')
1347 state = GOT_T;
1348 else if (c == '.')
1349 state = GOT_DOT;
1350 else {
1351 input_entry += c;
1352 if (c != '\n')
1353 state = MIDDLE;
1355 break;
1356 case GOT_T:
1357 if (c == '}')
1358 state = GOT_RIGHT_BRACE;
1359 else {
1360 input_entry += 'T';
1361 input_entry += c;
1362 state = c == '\n' ? START : MIDDLE;
1364 break;
1365 case GOT_DOT:
1366 if (c == 'l')
1367 state = GOT_l;
1368 else {
1369 input_entry += '.';
1370 input_entry += c;
1371 state = c == '\n' ? START : MIDDLE;
1373 break;
1374 case GOT_l:
1375 if (c == 'f')
1376 state = GOT_lf;
1377 else {
1378 input_entry += ".l";
1379 input_entry += c;
1380 state = c == '\n' ? START : MIDDLE;
1382 break;
1383 case GOT_lf:
1384 if (c == ' ' || c == '\n' || compatible_flag) {
1385 string args;
1386 input_entry += ".lf";
1387 while (c != EOF) {
1388 args += c;
1389 if (c == '\n')
1390 break;
1391 c = in.get();
1393 args += '\0';
1394 interpret_lf_args(args.contents());
1395 // remove the '\0'
1396 args.set_length(args.length() - 1);
1397 input_entry += args;
1398 state = START;
1400 else {
1401 input_entry += ".lf";
1402 input_entry += c;
1403 state = MIDDLE;
1405 break;
1406 case GOT_RIGHT_BRACE:
1407 if ((opt->flags & table::NOSPACES)) {
1408 while (c == ' ')
1409 c = in.get();
1410 if (c == EOF)
1411 break;
1413 if (c == '\n' || c == tab_char)
1414 state = END;
1415 else {
1416 input_entry += 'T';
1417 input_entry += '}';
1418 input_entry += c;
1419 state = MIDDLE;
1421 break;
1422 case MIDDLE:
1423 if (c == '\n')
1424 state = START;
1425 input_entry += c;
1426 break;
1427 case END:
1428 default:
1429 assert(0);
1432 if (c == EOF) {
1433 error("end of data in middle of text block");
1434 give_up = 1;
1435 break;
1438 if (col >= ncolumns) {
1439 if (!input_entry.empty()) {
1440 if (input_entry.length() >= 2
1441 && input_entry[0] == '\\'
1442 && input_entry[1] == '"')
1443 row_comment = 1;
1444 else if (!row_comment) {
1445 if (c == '\n')
1446 in.unget(c);
1447 input_entry += '\0';
1448 error("excess data entry `%1' discarded",
1449 input_entry.contents());
1450 if (c == '\n')
1451 (void)in.get();
1455 else
1456 tbl->add_entry(current_row, col, input_entry,
1457 &line_format[col], current_filename, ln);
1458 col++;
1459 if (c == '\n')
1460 break;
1461 input_entry = "";
1463 else
1464 input_entry += c;
1465 c = in.get();
1466 if (c == EOF)
1467 break;
1469 if (give_up)
1470 break;
1471 input_entry = "";
1472 for (; col < ncolumns; col++)
1473 tbl->add_entry(current_row, col, input_entry, &line_format[col],
1474 current_filename, current_lineno - 1);
1475 tbl->add_vlines(current_row, f->vline[format_index]);
1476 current_row++;
1477 format_index++;
1479 break;
1480 case TROFF_INPUT_LINE:
1482 string line;
1483 int ln = current_lineno;
1484 for (;;) {
1485 line += c;
1486 if (c == '\n')
1487 break;
1488 c = in.get();
1489 if (c == EOF) {
1490 break;
1493 tbl->add_text_line(current_row, line, current_filename, ln);
1494 if (line.length() >= 4
1495 && line[0] == '.' && line[1] == 'T' && line[2] == '&') {
1496 format *newf = process_format(in, opt, f);
1497 if (newf == 0)
1498 give_up = 1;
1499 else
1500 f = newf;
1502 if (line.length() >= 3
1503 && line[0] == '.' && line[1] == 'l' && line[2] == 'f') {
1504 line += '\0';
1505 interpret_lf_args(line.contents() + 3);
1508 break;
1509 case SINGLE_HLINE:
1510 tbl->add_single_hline(current_row);
1511 break;
1512 case DOUBLE_HLINE:
1513 tbl->add_double_hline(current_row);
1514 break;
1515 default:
1516 assert(0);
1518 if (give_up)
1519 break;
1521 if (!give_up && current_row == 0) {
1522 error("no real data");
1523 give_up = 1;
1525 if (give_up) {
1526 delete tbl;
1527 return 0;
1529 // Do this here rather than at the beginning in case continued formats
1530 // change it.
1531 int i;
1532 for (i = 0; i < ncolumns - 1; i++)
1533 if (f->separation[i] >= 0)
1534 tbl->set_column_separation(i, f->separation[i]);
1535 for (i = 0; i < ncolumns; i++)
1536 if (!f->width[i].empty())
1537 tbl->set_minimum_width(i, f->width[i]);
1538 for (i = 0; i < ncolumns; i++)
1539 if (f->equal[i])
1540 tbl->set_equal_column(i);
1541 for (i = 0; i < ncolumns; i++)
1542 if (f->expand[i])
1543 tbl->set_expand_column(i);
1544 return tbl;
1547 void process_table(table_input &in)
1549 options *opt = 0;
1550 format *form = 0;
1551 table *tbl = 0;
1552 if ((opt = process_options(in)) != 0
1553 && (form = process_format(in, opt)) != 0
1554 && (tbl = process_data(in, form, opt)) != 0) {
1555 tbl->print();
1556 delete tbl;
1558 else {
1559 error("giving up on this table");
1560 while (in.get() != EOF)
1563 delete opt;
1564 delete form;
1565 if (!in.ended())
1566 error("premature end of file");
1569 static void usage(FILE *stream)
1571 fprintf(stream, "Synopsis: %s [ -vC ] [ files... ]\n", program_name);
1574 int main(int argc, char **argv)
1576 program_name = argv[0];
1577 static char stderr_buf[BUFSIZ];
1578 setbuf(stderr, stderr_buf);
1579 int opt;
1580 static const struct option long_options[] = {
1581 { "help", no_argument, 0, CHAR_MAX + 1 },
1582 { "version", no_argument, 0, 'v' },
1583 { NULL, 0, 0, 0 }
1585 while ((opt = getopt_long(argc, argv, "vCT:", long_options, NULL)) != EOF)
1586 switch (opt) {
1587 case 'C':
1588 compatible_flag = 1;
1589 break;
1590 case 'v':
1592 puts(L_P_TBL " (" T_ROFF ") v" VERSION);
1593 exit(0);
1594 break;
1596 case 'T':
1597 // I'm sick of getting bug reports from IRIX users
1598 break;
1599 case CHAR_MAX + 1: // --help
1600 usage(stdout);
1601 exit(0);
1602 break;
1603 case '?':
1604 usage(stderr);
1605 exit(1);
1606 break;
1607 default:
1608 assert(0);
1610 printf(".if !\\n(.g .ab " L_P_TBL " requires " L_TROFF ".\n"
1611 ".if !dTS .ds TS\n"
1612 ".if !dTE .ds TE\n");
1614 file_case *fcp;
1615 do /*while (optind < argc)*/ {
1616 if ((current_filename = argv[optind++]) == NULL)
1617 current_filename = "-";
1618 fcp = file_case::muxer(current_filename);
1619 if (fcp == NULL) {
1620 assert(strcmp(current_filename, "-"));
1621 fatal("can't open `%1': %2", current_filename, strerror(errno));
1624 current_lineno = 1;
1625 printf(".lf 1 %s\n", current_filename);
1626 process_input_file(fcp);
1628 delete fcp;
1629 } while (optind < argc);
1631 if (ferror(stdout) || fflush(stdout) < 0)
1632 fatal("output error");
1633 return 0;
1636 // s-it2-mode