Sync-to-go: update copyright for 2015
[s-roff.git] / src / pre-tbl / main.cpp
blob7f727ded7c3d1dd05ea887d70de2f8a3619fef55
1 /*@
2 * Copyright (c) 2014 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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;
504 else if (strieq(p, "decimalpoint")) {
505 if (!arg)
506 error("`decimalpoint' option requires argument in parentheses");
507 else {
508 if (arg[0] == '\0' || arg[1] != '\0')
509 error("argument to `decimalpoint' option must be a single character");
510 else
511 opt->decimal_point_char = arg[0];
514 else if (strieq(p, "experimental")) {
515 opt->flags |= table::EXPERIMENTAL;
517 else {
518 error("unrecognised global option `%1'", p);
519 // delete opt;
520 // return 0;
522 p = q;
524 return opt;
527 entry_modifier::entry_modifier()
528 : vertical_alignment(CENTER), zero_width(0), stagger(0)
530 vertical_spacing.inc = vertical_spacing.val = 0;
531 point_size.inc = point_size.val = 0;
534 entry_modifier::~entry_modifier()
538 entry_format::entry_format() : type(FORMAT_LEFT)
542 entry_format::entry_format(format_type t) : type(t)
546 void entry_format::debug_print() const
548 switch (type) {
549 case FORMAT_LEFT:
550 putc('l', stderr);
551 break;
552 case FORMAT_CENTER:
553 putc('c', stderr);
554 break;
555 case FORMAT_RIGHT:
556 putc('r', stderr);
557 break;
558 case FORMAT_NUMERIC:
559 putc('n', stderr);
560 break;
561 case FORMAT_ALPHABETIC:
562 putc('a', stderr);
563 break;
564 case FORMAT_SPAN:
565 putc('s', stderr);
566 break;
567 case FORMAT_VSPAN:
568 putc('^', stderr);
569 break;
570 case FORMAT_HLINE:
571 putc('_', stderr);
572 break;
573 case FORMAT_DOUBLE_HLINE:
574 putc('=', stderr);
575 break;
576 default:
577 assert(0);
578 break;
580 if (point_size.val != 0) {
581 putc('p', stderr);
582 if (point_size.inc > 0)
583 putc('+', stderr);
584 else if (point_size.inc < 0)
585 putc('-', stderr);
586 fprintf(stderr, "%d ", point_size.val);
588 if (vertical_spacing.val != 0) {
589 putc('v', stderr);
590 if (vertical_spacing.inc > 0)
591 putc('+', stderr);
592 else if (vertical_spacing.inc < 0)
593 putc('-', stderr);
594 fprintf(stderr, "%d ", vertical_spacing.val);
596 if (!font.empty()) {
597 putc('f', stderr);
598 put_string(font, stderr);
599 putc(' ', stderr);
601 if (!macro.empty()) {
602 putc('m', stderr);
603 put_string(macro, stderr);
604 putc(' ', stderr);
606 switch (vertical_alignment) {
607 case entry_modifier::CENTER:
608 break;
609 case entry_modifier::TOP:
610 putc('t', stderr);
611 break;
612 case entry_modifier::BOTTOM:
613 putc('d', stderr);
614 break;
616 if (zero_width)
617 putc('z', stderr);
618 if (stagger)
619 putc('u', stderr);
622 class format
624 public:
625 int nrows;
626 int ncolumns;
627 int *separation;
628 string *width;
629 char *equal;
630 char *expand;
631 entry_format **entry;
632 char **vline;
634 format(int nr, int nc);
635 ~format();
636 void add_rows(int n);
639 format::format(int nr, int nc) : nrows(nr), ncolumns(nc)
641 int i;
642 separation = ncolumns > 1 ? new int[ncolumns - 1] : 0;
643 for (i = 0; i < ncolumns-1; i++)
644 separation[i] = -1;
645 width = new string[ncolumns];
646 equal = new char[ncolumns];
647 expand = new char[ncolumns];
648 for (i = 0; i < ncolumns; i++) {
649 equal[i] = 0;
650 expand[i] = 0;
652 entry = new entry_format *[nrows];
653 for (i = 0; i < nrows; i++)
654 entry[i] = new entry_format[ncolumns];
655 vline = new char*[nrows];
656 for (i = 0; i < nrows; i++) {
657 vline[i] = new char[ncolumns+1];
658 for (int j = 0; j < ncolumns+1; j++)
659 vline[i][j] = 0;
663 void format::add_rows(int n)
665 int i;
666 char **old_vline = vline;
667 vline = new char*[nrows + n];
668 for (i = 0; i < nrows; i++)
669 vline[i] = old_vline[i];
670 a_delete old_vline;
671 for (i = 0; i < n; i++) {
672 vline[nrows + i] = new char[ncolumns + 1];
673 for (int j = 0; j < ncolumns + 1; j++)
674 vline[nrows + i][j] = 0;
676 entry_format **old_entry = entry;
677 entry = new entry_format *[nrows + n];
678 for (i = 0; i < nrows; i++)
679 entry[i] = old_entry[i];
680 a_delete old_entry;
681 for (i = 0; i < n; i++)
682 entry[nrows + i] = new entry_format[ncolumns];
683 nrows += n;
686 format::~format()
688 a_delete separation;
689 ad_delete(ncolumns) width;
690 a_delete equal;
691 a_delete expand;
692 for (int i = 0; i < nrows; i++) {
693 a_delete vline[i];
694 ad_delete(ncolumns) entry[i];
696 a_delete vline;
697 a_delete entry;
700 class input_entry_format
701 : public entry_format
703 public:
704 input_entry_format *next;
705 string width;
706 int separation;
707 int vline;
708 int pre_vline;
709 int last_column;
710 int equal;
711 int expand;
713 input_entry_format(format_type, input_entry_format * = 0);
714 ~input_entry_format();
715 void debug_print();
718 input_entry_format::input_entry_format(format_type t, input_entry_format *p)
719 : entry_format(t), next(p)
721 separation = -1;
722 last_column = 0;
723 vline = 0;
724 pre_vline = 0;
725 equal = 0;
726 expand = 0;
729 input_entry_format::~input_entry_format()
733 void free_input_entry_format_list(input_entry_format *list)
735 while (list) {
736 input_entry_format *tem = list;
737 list = list->next;
738 delete tem;
742 void input_entry_format::debug_print()
744 int i;
745 for (i = 0; i < pre_vline; i++)
746 putc('|', stderr);
747 entry_format::debug_print();
748 if (!width.empty()) {
749 putc('w', stderr);
750 putc('(', stderr);
751 put_string(width, stderr);
752 putc(')', stderr);
754 if (equal)
755 putc('e', stderr);
756 if (expand)
757 putc('x', stderr);
758 if (separation >= 0)
759 fprintf(stderr, "%d", separation);
760 for (i = 0; i < vline; i++)
761 putc('|', stderr);
762 if (last_column)
763 putc(',', stderr);
766 // Return zero if we should give up on this table.
767 // If this is a continuation format line, current_format will be the current
768 // format line.
770 format *process_format(table_input &in, options *opt,
771 format *current_format = 0)
773 input_entry_format *list = 0;
774 int have_expand = 0;
775 int c = in.get();
776 for (;;) {
777 int pre_vline = 0;
778 int got_format = 0;
779 int got_period = 0;
780 format_type t = FORMAT_LEFT;
781 for (;;) {
782 if (c == EOF) {
783 error("end of input while processing format");
784 free_input_entry_format_list(list);
785 return 0;
787 switch (c) {
788 case 'n':
789 case 'N':
790 t = FORMAT_NUMERIC;
791 got_format = 1;
792 break;
793 case 'a':
794 case 'A':
795 got_format = 1;
796 t = FORMAT_ALPHABETIC;
797 break;
798 case 'c':
799 case 'C':
800 got_format = 1;
801 t = FORMAT_CENTER;
802 break;
803 case 'l':
804 case 'L':
805 got_format = 1;
806 t = FORMAT_LEFT;
807 break;
808 case 'r':
809 case 'R':
810 got_format = 1;
811 t = FORMAT_RIGHT;
812 break;
813 case 's':
814 case 'S':
815 got_format = 1;
816 t = FORMAT_SPAN;
817 break;
818 case '^':
819 got_format = 1;
820 t = FORMAT_VSPAN;
821 break;
822 case '_':
823 case '-': // tbl also accepts this
824 got_format = 1;
825 t = FORMAT_HLINE;
826 break;
827 case '=':
828 got_format = 1;
829 t = FORMAT_DOUBLE_HLINE;
830 break;
831 case '.':
832 got_period = 1;
833 break;
834 case '|':
835 pre_vline++;
836 break;
837 case ' ':
838 case '\t':
839 case '\n':
840 break;
841 default:
842 if (c == opt->tab_char)
843 break;
844 error("unrecognised format `%1'", char(c));
845 free_input_entry_format_list(list);
846 return 0;
848 if (got_period)
849 break;
850 c = in.get();
851 if (got_format)
852 break;
854 if (got_period)
855 break;
856 list = new input_entry_format(t, list);
857 if (pre_vline)
858 list->pre_vline = pre_vline;
859 int success = 1;
860 do {
861 switch (c) {
862 case '0':
863 case '1':
864 case '2':
865 case '3':
866 case '4':
867 case '5':
868 case '6':
869 case '7':
870 case '8':
871 case '9':
873 int w = 0;
874 do {
875 w = w*10 + (c - '0');
876 c = in.get();
877 } while (c != EOF && csdigit(c));
878 list->separation = w;
880 break;
881 case 'B':
882 case 'b':
883 c = in.get();
884 list->font = "B";
885 break;
886 case 'd':
887 case 'D':
888 c = in.get();
889 list->vertical_alignment = entry_modifier::BOTTOM;
890 break;
891 case 'e':
892 case 'E':
893 c = in.get();
894 list->equal = 1;
895 // `e' and `x' are mutually exclusive
896 list->expand = 0;
897 break;
898 case 'f':
899 case 'F':
900 do {
901 c = in.get();
902 } while (c == ' ' || c == '\t');
903 if (c == EOF) {
904 error("missing font name");
905 break;
907 if (c == '(') {
908 for (;;) {
909 c = in.get();
910 if (c == EOF || c == ' ' || c == '\t') {
911 error("missing `)'");
912 break;
914 if (c == ')') {
915 c = in.get();
916 break;
918 list->font += char(c);
921 else {
922 list->font = c;
923 char cc = c;
924 c = in.get();
925 if (!csdigit(cc)
926 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
927 list->font += char(c);
928 c = in.get();
931 break;
932 case 'I':
933 case 'i':
934 c = in.get();
935 list->font = "I";
936 break;
937 case 'm':
938 case 'M':
939 do {
940 c = in.get();
941 } while (c == ' ' || c == '\t');
942 if (c == EOF) {
943 error("missing macro name");
944 break;
946 if (c == '(') {
947 for (;;) {
948 c = in.get();
949 if (c == EOF || c == ' ' || c == '\t') {
950 error("missing `)'");
951 break;
953 if (c == ')') {
954 c = in.get();
955 break;
957 list->macro += char(c);
960 else {
961 list->macro = c;
962 char cc = c;
963 c = in.get();
964 if (!csdigit(cc)
965 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
966 list->macro += char(c);
967 c = in.get();
970 break;
971 case 'p':
972 case 'P':
973 c = in.get();
974 list->point_size.val = 0;
975 list->point_size.inc = 0;
976 if (c == '+' || c == '-') {
977 list->point_size.inc = (c == '+' ? 1 : -1);
978 c = in.get();
980 if (c == EOF || !csdigit(c)) {
981 error("`p' modifier must be followed by number");
982 list->point_size.inc = 0;
984 else {
985 do {
986 list->point_size.val *= 10;
987 list->point_size.val += c - '0';
988 c = in.get();
989 } while (c != EOF && csdigit(c));
991 if (list->point_size.val > MAX_POINT_SIZE
992 || list->point_size.val < -MAX_POINT_SIZE) {
993 error("unreasonable point size");
994 list->point_size.val = 0;
995 list->point_size.inc = 0;
997 break;
998 case 't':
999 case 'T':
1000 c = in.get();
1001 list->vertical_alignment = entry_modifier::TOP;
1002 break;
1003 case 'u':
1004 case 'U':
1005 c = in.get();
1006 list->stagger = 1;
1007 break;
1008 case 'v':
1009 case 'V':
1010 c = in.get();
1011 list->vertical_spacing.val = 0;
1012 list->vertical_spacing.inc = 0;
1013 if (c == '+' || c == '-') {
1014 list->vertical_spacing.inc = (c == '+' ? 1 : -1);
1015 c = in.get();
1017 if (c == EOF || !csdigit(c)) {
1018 error("`v' modifier must be followed by number");
1019 list->vertical_spacing.inc = 0;
1021 else {
1022 do {
1023 list->vertical_spacing.val *= 10;
1024 list->vertical_spacing.val += c - '0';
1025 c = in.get();
1026 } while (c != EOF && csdigit(c));
1028 if (list->vertical_spacing.val > MAX_VERTICAL_SPACING
1029 || list->vertical_spacing.val < -MAX_VERTICAL_SPACING) {
1030 error("unreasonable vertical spacing");
1031 list->vertical_spacing.val = 0;
1032 list->vertical_spacing.inc = 0;
1034 break;
1035 case 'w':
1036 case 'W':
1037 c = in.get();
1038 while (c == ' ' || c == '\t')
1039 c = in.get();
1040 if (c == '(') {
1041 list->width = "";
1042 c = in.get();
1043 while (c != ')') {
1044 if (c == EOF || c == '\n') {
1045 error("missing `)'");
1046 free_input_entry_format_list(list);
1047 return 0;
1049 list->width += c;
1050 c = in.get();
1052 c = in.get();
1054 else {
1055 if (c == '+' || c == '-') {
1056 list->width = char(c);
1057 c = in.get();
1059 else
1060 list->width = "";
1061 if (c == EOF || !csdigit(c))
1062 error("bad argument for `w' modifier");
1063 else {
1064 do {
1065 list->width += char(c);
1066 c = in.get();
1067 } while (c != EOF && csdigit(c));
1070 // `w' and `x' are mutually exclusive
1071 list->expand = 0;
1072 break;
1073 case 'x':
1074 case 'X':
1075 c = in.get();
1076 list->expand = 1;
1077 // `x' and `e' are mutually exclusive
1078 list->equal = 0;
1079 // `x' and `w' are mutually exclusive
1080 list->width = "";
1081 break;
1082 case 'z':
1083 case 'Z':
1084 c = in.get();
1085 list->zero_width = 1;
1086 break;
1087 case '|':
1088 c = in.get();
1089 list->vline++;
1090 break;
1091 case ' ':
1092 case '\t':
1093 c = in.get();
1094 break;
1095 default:
1096 if (c == opt->tab_char)
1097 c = in.get();
1098 else
1099 success = 0;
1100 break;
1102 } while (success);
1103 if (list->vline > 2) {
1104 list->vline = 2;
1105 error("more than 2 vertical bars between key letters");
1107 if (c == '\n' || c == ',') {
1108 c = in.get();
1109 list->last_column = 1;
1112 if (c == '.') {
1113 do {
1114 c = in.get();
1115 } while (c == ' ' || c == '\t');
1116 if (c != '\n') {
1117 error("`.' not last character on line");
1118 free_input_entry_format_list(list);
1119 return 0;
1122 if (!list) {
1123 error("no format");
1124 free_input_entry_format_list(list);
1125 return 0;
1127 list->last_column = 1;
1128 // now reverse the list so that the first row is at the beginning
1129 input_entry_format *rev = 0;
1130 while (list != 0) {
1131 input_entry_format *tem = list->next;
1132 list->next = rev;
1133 rev = list;
1134 list = tem;
1136 list = rev;
1137 input_entry_format *tem;
1139 #if 0
1140 for (tem = list; tem; tem = tem->next)
1141 tem->debug_print();
1142 putc('\n', stderr);
1143 #endif
1144 // compute number of columns and rows
1145 int ncolumns = 0;
1146 int nrows = 0;
1147 int col = 0;
1148 for (tem = list; tem; tem = tem->next) {
1149 if (tem->last_column) {
1150 if (col >= ncolumns)
1151 ncolumns = col + 1;
1152 col = 0;
1153 nrows++;
1155 else
1156 col++;
1158 int row;
1159 format *f;
1160 if (current_format) {
1161 if (ncolumns > current_format->ncolumns) {
1162 error("cannot increase the number of columns in a continued format");
1163 free_input_entry_format_list(list);
1164 return 0;
1166 f = current_format;
1167 row = f->nrows;
1168 f->add_rows(nrows);
1170 else {
1171 f = new format(nrows, ncolumns);
1172 row = 0;
1174 col = 0;
1175 for (tem = list; tem; tem = tem->next) {
1176 f->entry[row][col] = *tem;
1177 if (col < ncolumns - 1) {
1178 // use the greatest separation
1179 if (tem->separation > f->separation[col]) {
1180 if (current_format)
1181 error("cannot change column separation in continued format");
1182 else
1183 f->separation[col] = tem->separation;
1186 else if (tem->separation >= 0)
1187 error("column separation specified for last column");
1188 if (tem->equal && !f->equal[col]) {
1189 if (current_format)
1190 error("cannot change which columns are equal in continued format");
1191 else
1192 f->equal[col] = 1;
1194 if (tem->expand && !f->expand[col]) {
1195 if (current_format)
1196 error("cannot change which columns are expanded in continued format");
1197 else {
1198 f->expand[col] = 1;
1199 have_expand = 1;
1202 if (!tem->width.empty()) {
1203 // use the last width
1204 if (!f->width[col].empty() && f->width[col] != tem->width)
1205 error("multiple widths for column %1", col + 1);
1206 f->width[col] = tem->width;
1208 if (tem->pre_vline) {
1209 assert(col == 0);
1210 f->vline[row][col] = tem->pre_vline;
1212 f->vline[row][col + 1] = tem->vline;
1213 if (tem->last_column) {
1214 row++;
1215 col = 0;
1217 else
1218 col++;
1220 free_input_entry_format_list(list);
1221 for (col = 0; col < ncolumns; col++) {
1222 entry_format *e = f->entry[f->nrows - 1] + col;
1223 if (e->type != FORMAT_HLINE
1224 && e->type != FORMAT_DOUBLE_HLINE
1225 && e->type != FORMAT_SPAN)
1226 break;
1228 if (col >= ncolumns) {
1229 error("last row of format is all lines");
1230 delete f;
1231 return 0;
1233 if (have_expand && (opt->flags & table::EXPAND)) {
1234 error("ignoring global `expand' option because of `x' specifiers");
1235 opt->flags &= ~table::EXPAND;
1237 return f;
1240 table *process_data(table_input &in, format *f, options *opt)
1242 char tab_char = opt->tab_char;
1243 int ncolumns = f->ncolumns;
1244 int current_row = 0;
1245 int format_index = 0;
1246 int give_up = 0;
1247 enum { DATA_INPUT_LINE, TROFF_INPUT_LINE, SINGLE_HLINE, DOUBLE_HLINE } type;
1248 table *tbl = new table(ncolumns, opt->flags, opt->linesize,
1249 opt->decimal_point_char);
1250 if (opt->delim[0] != '\0')
1251 tbl->set_delim(opt->delim[0], opt->delim[1]);
1252 for (;;) {
1253 // first determine what type of line this is
1254 int c = in.get();
1255 if (c == EOF)
1256 break;
1257 if (c == '.') {
1258 int d = in.get();
1259 if (d != EOF && csdigit(d)) {
1260 in.unget(d);
1261 type = DATA_INPUT_LINE;
1263 else {
1264 in.unget(d);
1265 type = TROFF_INPUT_LINE;
1268 else if (c == '_' || c == '=') {
1269 int d = in.get();
1270 if (d == '\n') {
1271 if (c == '_')
1272 type = SINGLE_HLINE;
1273 else
1274 type = DOUBLE_HLINE;
1276 else {
1277 in.unget(d);
1278 type = DATA_INPUT_LINE;
1281 else {
1282 type = DATA_INPUT_LINE;
1284 switch (type) {
1285 case DATA_INPUT_LINE:
1287 string input_entry;
1288 if (format_index >= f->nrows)
1289 format_index = f->nrows - 1;
1290 // A format row that is all lines doesn't use up a data line.
1291 while (format_index < f->nrows - 1) {
1292 int cnt;
1293 for (cnt = 0; cnt < ncolumns; cnt++) {
1294 entry_format *e = f->entry[format_index] + cnt;
1295 if (e->type != FORMAT_HLINE
1296 && e->type != FORMAT_DOUBLE_HLINE
1297 // Unfortunately tbl treats a span as needing data.
1298 // && e->type != FORMAT_SPAN
1300 break;
1302 if (cnt < ncolumns)
1303 break;
1304 for (cnt = 0; cnt < ncolumns; cnt++)
1305 tbl->add_entry(current_row, cnt, input_entry,
1306 f->entry[format_index] + cnt, current_filename,
1307 current_lineno);
1308 tbl->add_vlines(current_row, f->vline[format_index]);
1309 format_index++;
1310 current_row++;
1312 entry_format *line_format = f->entry[format_index];
1313 int col = 0;
1314 int row_comment = 0;
1315 for (;;) {
1316 if (c == tab_char || c == '\n') {
1317 int ln = current_lineno;
1318 if (c == '\n')
1319 --ln;
1320 if ((opt->flags & table::NOSPACES))
1321 input_entry.remove_spaces();
1322 while (col < ncolumns
1323 && line_format[col].type == FORMAT_SPAN) {
1324 tbl->add_entry(current_row, col, "", &line_format[col],
1325 current_filename, ln);
1326 col++;
1328 if (c == '\n' && input_entry.length() == 2
1329 && input_entry[0] == 'T' && input_entry[1] == '{') {
1330 input_entry = "";
1331 ln++;
1332 enum {
1333 START, MIDDLE, GOT_T, GOT_RIGHT_BRACE, GOT_DOT,
1334 GOT_l, GOT_lf, END
1335 } state = START;
1336 while (state != END) {
1337 c = in.get();
1338 if (c == EOF)
1339 break;
1340 switch (state) {
1341 case START:
1342 if (c == 'T')
1343 state = GOT_T;
1344 else if (c == '.')
1345 state = GOT_DOT;
1346 else {
1347 input_entry += c;
1348 if (c != '\n')
1349 state = MIDDLE;
1351 break;
1352 case GOT_T:
1353 if (c == '}')
1354 state = GOT_RIGHT_BRACE;
1355 else {
1356 input_entry += 'T';
1357 input_entry += c;
1358 state = c == '\n' ? START : MIDDLE;
1360 break;
1361 case GOT_DOT:
1362 if (c == 'l')
1363 state = GOT_l;
1364 else {
1365 input_entry += '.';
1366 input_entry += c;
1367 state = c == '\n' ? START : MIDDLE;
1369 break;
1370 case GOT_l:
1371 if (c == 'f')
1372 state = GOT_lf;
1373 else {
1374 input_entry += ".l";
1375 input_entry += c;
1376 state = c == '\n' ? START : MIDDLE;
1378 break;
1379 case GOT_lf:
1380 if (c == ' ' || c == '\n' || compatible_flag) {
1381 string args;
1382 input_entry += ".lf";
1383 while (c != EOF) {
1384 args += c;
1385 if (c == '\n')
1386 break;
1387 c = in.get();
1389 args += '\0';
1390 interpret_lf_args(args.contents());
1391 // remove the '\0'
1392 args.set_length(args.length() - 1);
1393 input_entry += args;
1394 state = START;
1396 else {
1397 input_entry += ".lf";
1398 input_entry += c;
1399 state = MIDDLE;
1401 break;
1402 case GOT_RIGHT_BRACE:
1403 if ((opt->flags & table::NOSPACES)) {
1404 while (c == ' ')
1405 c = in.get();
1406 if (c == EOF)
1407 break;
1409 if (c == '\n' || c == tab_char)
1410 state = END;
1411 else {
1412 input_entry += 'T';
1413 input_entry += '}';
1414 input_entry += c;
1415 state = MIDDLE;
1417 break;
1418 case MIDDLE:
1419 if (c == '\n')
1420 state = START;
1421 input_entry += c;
1422 break;
1423 case END:
1424 default:
1425 assert(0);
1428 if (c == EOF) {
1429 error("end of data in middle of text block");
1430 give_up = 1;
1431 break;
1434 if (col >= ncolumns) {
1435 if (!input_entry.empty()) {
1436 if (input_entry.length() >= 2
1437 && input_entry[0] == '\\'
1438 && input_entry[1] == '"')
1439 row_comment = 1;
1440 else if (!row_comment) {
1441 if (c == '\n')
1442 in.unget(c);
1443 input_entry += '\0';
1444 error("excess data entry `%1' discarded",
1445 input_entry.contents());
1446 if (c == '\n')
1447 (void)in.get();
1451 else
1452 tbl->add_entry(current_row, col, input_entry,
1453 &line_format[col], current_filename, ln);
1454 col++;
1455 if (c == '\n')
1456 break;
1457 input_entry = "";
1459 else
1460 input_entry += c;
1461 c = in.get();
1462 if (c == EOF)
1463 break;
1465 if (give_up)
1466 break;
1467 input_entry = "";
1468 for (; col < ncolumns; col++)
1469 tbl->add_entry(current_row, col, input_entry, &line_format[col],
1470 current_filename, current_lineno - 1);
1471 tbl->add_vlines(current_row, f->vline[format_index]);
1472 current_row++;
1473 format_index++;
1475 break;
1476 case TROFF_INPUT_LINE:
1478 string line;
1479 int ln = current_lineno;
1480 for (;;) {
1481 line += c;
1482 if (c == '\n')
1483 break;
1484 c = in.get();
1485 if (c == EOF) {
1486 break;
1489 tbl->add_text_line(current_row, line, current_filename, ln);
1490 if (line.length() >= 4
1491 && line[0] == '.' && line[1] == 'T' && line[2] == '&') {
1492 format *newf = process_format(in, opt, f);
1493 if (newf == 0)
1494 give_up = 1;
1495 else
1496 f = newf;
1498 if (line.length() >= 3
1499 && line[0] == '.' && line[1] == 'l' && line[2] == 'f') {
1500 line += '\0';
1501 interpret_lf_args(line.contents() + 3);
1504 break;
1505 case SINGLE_HLINE:
1506 tbl->add_single_hline(current_row);
1507 break;
1508 case DOUBLE_HLINE:
1509 tbl->add_double_hline(current_row);
1510 break;
1511 default:
1512 assert(0);
1514 if (give_up)
1515 break;
1517 if (!give_up && current_row == 0) {
1518 error("no real data");
1519 give_up = 1;
1521 if (give_up) {
1522 delete tbl;
1523 return 0;
1525 // Do this here rather than at the beginning in case continued formats
1526 // change it.
1527 int i;
1528 for (i = 0; i < ncolumns - 1; i++)
1529 if (f->separation[i] >= 0)
1530 tbl->set_column_separation(i, f->separation[i]);
1531 for (i = 0; i < ncolumns; i++)
1532 if (!f->width[i].empty())
1533 tbl->set_minimum_width(i, f->width[i]);
1534 for (i = 0; i < ncolumns; i++)
1535 if (f->equal[i])
1536 tbl->set_equal_column(i);
1537 for (i = 0; i < ncolumns; i++)
1538 if (f->expand[i])
1539 tbl->set_expand_column(i);
1540 return tbl;
1543 void process_table(table_input &in)
1545 options *opt = 0;
1546 format *form = 0;
1547 table *tbl = 0;
1548 if ((opt = process_options(in)) != 0
1549 && (form = process_format(in, opt)) != 0
1550 && (tbl = process_data(in, form, opt)) != 0) {
1551 tbl->print();
1552 delete tbl;
1554 else {
1555 error("giving up on this table");
1556 while (in.get() != EOF)
1559 delete opt;
1560 delete form;
1561 if (!in.ended())
1562 error("premature end of file");
1565 static void usage(FILE *stream)
1567 fprintf(stream, "Synopsis: %s [ -vC ] [ files... ]\n", program_name);
1570 int main(int argc, char **argv)
1572 program_name = argv[0];
1573 static char stderr_buf[BUFSIZ];
1574 setbuf(stderr, stderr_buf);
1575 int opt;
1576 static const struct option long_options[] = {
1577 { "help", no_argument, 0, CHAR_MAX + 1 },
1578 { "version", no_argument, 0, 'v' },
1579 { NULL, 0, 0, 0 }
1581 while ((opt = getopt_long(argc, argv, "vCT:", long_options, NULL)) != EOF)
1582 switch (opt) {
1583 case 'C':
1584 compatible_flag = 1;
1585 break;
1586 case 'v':
1588 puts(L_P_TBL " (" T_ROFF ") v" VERSION);
1589 exit(0);
1590 break;
1592 case 'T':
1593 // I'm sick of getting bug reports from IRIX users
1594 break;
1595 case CHAR_MAX + 1: // --help
1596 usage(stdout);
1597 exit(0);
1598 break;
1599 case '?':
1600 usage(stderr);
1601 exit(1);
1602 break;
1603 default:
1604 assert(0);
1606 printf(".if !\\n(.g .ab " L_P_TBL " requires " L_TROFF ".\n"
1607 ".if !dTS .ds TS\n"
1608 ".if !dTE .ds TE\n");
1610 file_case *fcp;
1611 do /*while (optind < argc)*/ {
1612 if ((current_filename = argv[optind++]) == NULL)
1613 current_filename = "-";
1614 fcp = file_case::muxer(current_filename);
1615 if (fcp == NULL) {
1616 assert(strcmp(current_filename, "-"));
1617 fatal("can't open `%1': %2", current_filename, strerror(errno));
1620 current_lineno = 1;
1621 printf(".lf 1 %s\n", current_filename);
1622 process_input_file(fcp);
1624 delete fcp;
1625 } while (optind < argc);
1627 if (ferror(stdout) || fflush(stdout) < 0)
1628 fatal("output error");
1629 return 0;
1632 // s-it2-mode