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