2 /* Copyright (C) 1989, 1990, 1991 Free Software Foundation, Inc.
3 Written by James Clark (jjc@jclark.uucp)
5 This file is part of groff.
7 groff is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 1, or (at your option) any later
12 groff is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 You should have received a copy of the GNU General Public License along
18 with groff; see the file LICENSE. If not, write to the Free Software
19 Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
23 #define MAX_POINT_SIZE 99
24 #define MAX_VERTICAL_SPACING 72
26 static int compatible_flag
= 0;
30 enum { START
, MIDDLE
, REREAD_T
, REREAD_TE
, REREAD_E
, END
, ERROR
} state
;
35 int ended() { return unget_stack
.empty() && state
== END
; }
39 table_input::table_input(FILE *p
)
44 void table_input::unget(char c
)
52 int table_input::get()
54 int len
= unget_stack
.length();
56 unsigned char c
= unget_stack
[len
- 1];
57 unget_stack
.set_length(len
- 1);
66 if ((c
= getc(fp
)) == '.') {
67 if ((c
= getc(fp
)) == 'T') {
68 if ((c
= getc(fp
)) == 'E') {
69 if (compatible_flag
) {
77 if (c
== EOF
|| c
== ' ' || c
== '\n') {
109 error("illegal input character code 0");
117 // handle line continuation
118 if ((c
= getc(fp
)) == '\\') {
121 c
= getc(fp
); // perhaps state ought to be START now
137 else if (c
== '\0') {
138 error("illegal input character code 0");
159 void process_input_file(FILE *);
160 void process_table(table_input
&in
);
162 void process_input_file(FILE *fp
)
164 enum { START
, MIDDLE
, HAD_DOT
, HAD_T
, HAD_TS
, HAD_l
, HAD_lf
} state
;
167 while ((c
= getc(fp
)) != EOF
)
219 if (c
== ' ' || c
== '\n' || compatible_flag
) {
225 error("end of file at beginning of table");
234 table_input
input(fp
);
235 process_table(input
);
236 set_troff_location(current_filename
, current_lineno
);
238 fputs(".TE", stdout
);
239 while ((c
= getc(fp
)) != '\n') {
253 fputs(".TS", stdout
);
274 if (c
== ' ' || c
== '\n' || compatible_flag
) {
285 interpret_lf_args(line
.contents());
286 printf(".lf%s", line
.contents());
290 fputs(".lf", stdout
);
305 fputs(".\n", stdout
);
308 fputs(".l\n", stdout
);
311 fputs(".T\n", stdout
);
314 fputs(".lf\n", stdout
);
317 fputs(".TS\n", stdout
);
334 : flags(0), tab_char('\t'), linesize(0)
336 delim
[0] = delim
[1] = '\0';
339 // Return non-zero if p and q are the same ignoring case.
341 int strieq(const char *p
, const char *q
)
343 for (; cmlower(*p
) == cmlower(*q
); p
++, q
++)
349 // return 0 if we should give up in this table
351 options
*process_options(table_input
&in
)
353 options
*opt
= new options
;
359 int i
= line
.length();
366 int i
= line
.length();
375 else if (c
== ';' && level
== 0) {
385 while (csspace(*p
) || *p
== ',')
390 while (*q
!= ' ' && *q
!= '\0' && *q
!= '\t' && *q
!= ',' && *q
!= '(')
393 if (*q
!= '(' && *q
!= '\0')
400 while (*q
!= ')' && *q
!= '\0')
403 error("missing `)'");
409 error("argument without option");
411 else if (strieq(p
, "tab")) {
413 error("`tab' option requires argument in parentheses");
415 if (arg
[0] == '\0' || arg
[1] != '\0')
416 error("argument to `tab' option must be a single character");
418 opt
->tab_char
= arg
[0];
421 else if (strieq(p
, "linesize")) {
423 error("`linesize' option requires argument in parentheses");
425 if (sscanf(arg
, "%d", &opt
->linesize
) != 1)
426 error("bad linesize `%s'", arg
);
427 else if (opt
->linesize
<= 0) {
428 error("linesize must be positive");
433 else if (strieq(p
, "delim")) {
435 error("`delim' option requires argument in parentheses");
436 else if (arg
[0] == '\0' || arg
[1] == '\0' || arg
[2] != '\0')
437 error("argument to `delim' option must be two characters");
439 opt
->delim
[0] = arg
[0];
440 opt
->delim
[1] = arg
[1];
443 else if (strieq(p
, "center") || strieq(p
, "centre")) {
445 error("`center' option does not take a argument");
446 opt
->flags
|= table::CENTER
;
448 else if (strieq(p
, "expand")) {
450 error("`expand' option does not take a argument");
451 opt
->flags
|= table::EXPAND
;
453 else if (strieq(p
, "box") || strieq(p
, "frame")) {
455 error("`box' option does not take a argument");
456 opt
->flags
|= table::BOX
;
458 else if (strieq(p
, "doublebox") || strieq(p
, "doubleframe")) {
460 error("`doublebox' option does not take a argument");
461 opt
->flags
|= table::DOUBLEBOX
;
463 else if (strieq(p
, "allbox")) {
465 error("`allbox' option does not take a argument");
466 opt
->flags
|= table::ALLBOX
;
469 error("unrecognised global option `%1'", p
);
478 entry_modifier::entry_modifier()
479 : vertical_alignment(CENTER
), zero_width(0), stagger(0)
481 vertical_spacing
.inc
= vertical_spacing
.val
= 0;
482 point_size
.inc
= point_size
.val
= 0;
485 entry_modifier::~entry_modifier()
489 entry_format::entry_format() : type(FORMAT_LEFT
)
493 entry_format::entry_format(format_type t
) : type(t
)
497 void entry_format::debug_print() const
512 case FORMAT_ALPHABETIC
:
524 case FORMAT_DOUBLE_HLINE
:
531 if (point_size
.val
!= 0) {
533 if (point_size
.inc
> 0)
535 else if (point_size
.inc
< 0)
537 fprintf(stderr
, "%d ", point_size
.val
);
539 if (vertical_spacing
.val
!= 0) {
541 if (vertical_spacing
.inc
> 0)
543 else if (vertical_spacing
.inc
< 0)
545 fprintf(stderr
, "%d ", vertical_spacing
.val
);
549 put_string(font
, stderr
);
552 switch (vertical_alignment
) {
553 case entry_modifier::CENTER
:
555 case entry_modifier::TOP
:
558 case entry_modifier::BOTTOM
:
574 entry_format
**entry
;
577 format(int nr
, int nc
);
579 void add_rows(int n
);
582 format::format(int nr
, int nc
) : nrows(nr
), ncolumns(nc
)
585 separation
= new int[ncolumns
- 1];
586 for (i
= 0; i
< ncolumns
-1; i
++)
588 width
= new string
[ncolumns
];
589 equal
= new char[ncolumns
];
590 for (i
= 0; i
< ncolumns
; i
++)
592 entry
= new entry_format
*[nrows
];
593 for (i
= 0; i
< nrows
; i
++)
594 entry
[i
] = new entry_format
[ncolumns
];
595 vline
= new char*[nrows
];
596 for (i
= 0; i
< nrows
; i
++) {
597 vline
[i
] = new char[ncolumns
+1];
598 for (int j
= 0; j
< ncolumns
+1; j
++)
603 void format::add_rows(int n
)
606 char **old_vline
= vline
;
607 vline
= new char*[nrows
+ n
];
608 for (i
= 0; i
< nrows
; i
++)
609 vline
[i
] = old_vline
[i
];
611 for (i
= 0; i
< n
; i
++) {
612 vline
[nrows
+ i
] = new char[ncolumns
+ 1];
613 for (int j
= 0; j
< ncolumns
+ 1; j
++)
614 vline
[nrows
+ i
][j
] = 0;
616 entry_format
**old_entry
= entry
;
617 entry
= new entry_format
*[nrows
+ n
];
618 for (i
= 0; i
< nrows
; i
++)
619 entry
[i
] = old_entry
[i
];
621 for (i
= 0; i
< n
; i
++)
622 entry
[nrows
+ i
] = new entry_format
[ncolumns
];
629 ad_delete(ncolumns
) width
;
631 for (int i
= 0; i
< nrows
; i
++) {
633 ad_delete(ncolumns
) entry
[i
];
639 struct input_entry_format
: entry_format
{
640 input_entry_format
*next
;
647 input_entry_format(format_type
, input_entry_format
* = 0);
648 ~input_entry_format();
652 input_entry_format::input_entry_format(format_type t
, input_entry_format
*p
)
653 : entry_format(t
), next(p
)
662 input_entry_format::~input_entry_format()
666 void free_input_entry_format_list(input_entry_format
*list
)
669 input_entry_format
*tem
= list
;
675 void input_entry_format::debug_print()
677 for (int i
= 0; i
< pre_vline
; i
++)
679 entry_format::debug_print();
680 if (!width
.empty()) {
683 put_string(width
, stderr
);
689 fprintf(stderr
, "%d", separation
);
690 for (i
= 0; i
< vline
; i
++)
696 // Return zero if we should give up on this table.
697 // If this is a continuation format line, current_format will be the current
700 format
*process_format(table_input
&in
, options
*opt
,
701 format
*current_format
= 0)
703 input_entry_format
*list
= 0;
712 error("end of input while processing format");
713 free_input_entry_format_list(list
);
725 t
= FORMAT_ALPHABETIC
;
757 t
= FORMAT_DOUBLE_HLINE
;
770 if (c
== opt
->tab_char
)
772 error("unrecognised format `%1'", char(c
));
773 free_input_entry_format_list(list
);
784 list
= new input_entry_format(t
, list
);
786 list
->pre_vline
= pre_vline
;
793 list
->vertical_alignment
= entry_modifier::TOP
;
798 list
->vertical_alignment
= entry_modifier::BOTTOM
;
808 list
->zero_width
= 1;
823 w
= w
*10 + (c
- '0');
825 } while (c
!= EOF
&& csdigit(c
));
826 list
->separation
= w
;
833 } while (c
== ' ' || c
== '\t');
835 error("missing font name");
841 if (c
== EOF
|| c
== ' ' || c
== '\t') {
842 error("missing `)'");
849 list
->font
+= char(c
);
857 && c
!= EOF
&& c
!= ' ' && c
!= '\t' && c
!= '.' && c
!= '\n') {
858 list
->font
+= char(c
);
866 list
->vertical_spacing
.val
= 0;
867 list
->vertical_spacing
.inc
= 0;
868 if (c
== '+' || c
== '-') {
869 list
->vertical_spacing
.inc
= (c
== '+' ? 1 : -1);
872 if (c
== EOF
|| !csdigit(c
)) {
873 error("`v' modifier must be followed by number");
874 list
->vertical_spacing
.inc
= 0;
878 list
->vertical_spacing
.val
*= 10;
879 list
->vertical_spacing
.val
+= c
- '0';
881 } while (c
!= EOF
&& csdigit(c
));
883 if (list
->vertical_spacing
.val
> MAX_VERTICAL_SPACING
884 || list
->vertical_spacing
.val
< -MAX_VERTICAL_SPACING
) {
885 error("unreasonable point size");
886 list
->vertical_spacing
.val
= 0;
887 list
->vertical_spacing
.inc
= 0;
893 list
->point_size
.val
= 0;
894 list
->point_size
.inc
= 0;
895 if (c
== '+' || c
== '-') {
896 list
->point_size
.inc
= (c
== '+' ? 1 : -1);
899 if (c
== EOF
|| !csdigit(c
)) {
900 error("`p' modifier must be followed by number");
901 list
->point_size
.inc
= 0;
905 list
->point_size
.val
*= 10;
906 list
->point_size
.val
+= c
- '0';
908 } while (c
!= EOF
&& csdigit(c
));
910 if (list
->point_size
.val
> MAX_POINT_SIZE
911 || list
->point_size
.val
< -MAX_POINT_SIZE
) {
912 error("unreasonable point size");
913 list
->point_size
.val
= 0;
914 list
->point_size
.inc
= 0;
920 while (c
== ' ' || c
== '\t')
926 if (c
== EOF
|| c
== '\n') {
927 error("missing `)'");
928 free_input_entry_format_list(list
);
937 if (c
== '+' || c
== '-') {
938 list
->width
= char(c
);
943 if (c
== EOF
|| !csdigit(c
))
944 error("bad argument for `w' modifier");
947 list
->width
+= char(c
);
949 } while (c
!= EOF
&& csdigit(c
));
977 if (c
== opt
->tab_char
)
984 if (list
->vline
> 2) {
986 error("more than 2 vertical bars between key letters");
988 if (c
== '\n' || c
== ',') {
990 list
->last_column
= 1;
996 } while (c
== ' ' || c
== '\t');
998 error("`.' not last character on line");
999 free_input_entry_format_list(list
);
1005 free_input_entry_format_list(list
);
1008 list
->last_column
= 1;
1009 // now reverse the list so that the first row is at the beginning
1010 input_entry_format
*rev
= 0;
1012 input_entry_format
*tem
= list
->next
;
1018 input_entry_format
*tem
;
1021 for (tem
= list
; tem
; tem
= tem
->next
)
1025 // compute number of columns and rows
1029 for (tem
= list
; tem
; tem
= tem
->next
) {
1030 if (tem
->last_column
) {
1031 if (col
>= ncolumns
)
1041 if (current_format
) {
1042 if (ncolumns
> current_format
->ncolumns
) {
1043 error("cannot increase the number of columns in a continued format");
1044 free_input_entry_format_list(list
);
1052 f
= new format(nrows
, ncolumns
);
1056 for (tem
= list
; tem
; tem
= tem
->next
) {
1057 f
->entry
[row
][col
] = *tem
;
1058 if (!current_format
) {
1059 if (col
< ncolumns
-1) {
1060 // use the greatest separation
1061 if (tem
->separation
> f
->separation
[col
])
1062 f
->separation
[col
] = tem
->separation
;
1064 else if (tem
->separation
>= 0)
1065 error("column separation specified for last column");
1066 if (!tem
->width
.empty()) {
1067 // use the last width
1068 if (!f
->width
[col
].empty() && f
->width
[col
] != tem
->width
)
1069 error("multiple widths for column %1", col
+1);
1070 f
->width
[col
] = tem
->width
;
1075 if (tem
->pre_vline
) {
1077 f
->vline
[row
][col
] = tem
->pre_vline
;
1079 f
->vline
[row
][col
+1] = tem
->vline
;
1080 if (tem
->last_column
) {
1087 free_input_entry_format_list(list
);
1088 for (col
= 0; col
< ncolumns
; col
++) {
1089 entry_format
*e
= f
->entry
[f
->nrows
-1] + col
;
1090 if (e
->type
!= FORMAT_HLINE
1091 && e
->type
!= FORMAT_DOUBLE_HLINE
1092 && e
->type
!= FORMAT_SPAN
)
1095 if (col
>= ncolumns
) {
1096 error("last row of format is all lines");
1103 table
*process_data(table_input
&in
, format
*f
, options
*opt
)
1105 char tab_char
= opt
->tab_char
;
1106 int ncolumns
= f
->ncolumns
;
1107 int current_row
= 0;
1108 int format_index
= 0;
1110 enum { DATA_INPUT_LINE
, TROFF_INPUT_LINE
, SINGLE_HLINE
, DOUBLE_HLINE
} type
;
1111 table
*tbl
= new table(ncolumns
, opt
->flags
, opt
->linesize
);
1112 for (int i
= 0; i
< ncolumns
- 1; i
++)
1113 if (f
->separation
[i
] >= 0)
1114 tbl
->set_column_separation(i
, f
->separation
[i
]);
1115 for (i
= 0; i
< ncolumns
; i
++)
1116 if (!f
->width
[i
].empty())
1117 tbl
->set_minimum_width(i
, f
->width
[i
]);
1118 for (i
= 0; i
< ncolumns
; i
++)
1120 tbl
->set_equal_column(i
);
1121 if (opt
->delim
[0] != '\0')
1122 tbl
->set_delim(opt
->delim
[0], opt
->delim
[1]);
1124 // first determine what type of line this is
1130 if (d
!= EOF
&& csdigit(d
)) {
1132 type
= DATA_INPUT_LINE
;
1136 type
= TROFF_INPUT_LINE
;
1139 else if (c
== '_' || c
== '=') {
1143 type
= SINGLE_HLINE
;
1145 type
= DOUBLE_HLINE
;
1149 type
= DATA_INPUT_LINE
;
1153 type
= DATA_INPUT_LINE
;
1156 case DATA_INPUT_LINE
:
1159 if (format_index
>= f
->nrows
)
1160 format_index
= f
->nrows
- 1;
1161 // A format row that is all lines doesn't use up a data line.
1162 while (format_index
< f
->nrows
- 1) {
1163 for (int c
= 0; c
< ncolumns
; c
++) {
1164 entry_format
*e
= f
->entry
[format_index
] + c
;
1165 if (e
->type
!= FORMAT_HLINE
1166 && e
->type
!= FORMAT_DOUBLE_HLINE
1167 // Unfortunately tbl treats a span as needing data.
1168 // && e->type != FORMAT_SPAN
1174 for (c
= 0; c
< ncolumns
; c
++)
1175 tbl
->add_entry(current_row
, c
, input_entry
,
1176 f
->entry
[format_index
] + c
, current_filename
,
1178 tbl
->add_vlines(current_row
, f
->vline
[format_index
]);
1182 entry_format
*line_format
= f
->entry
[format_index
];
1185 if (c
== tab_char
|| c
== '\n') {
1186 int ln
= current_lineno
;
1189 while (col
< ncolumns
1190 && line_format
[col
].type
== FORMAT_SPAN
) {
1191 tbl
->add_entry(current_row
, col
, "", &line_format
[col
],
1192 current_filename
, ln
);
1195 if (c
== '\n' && input_entry
.length() == 2
1196 && input_entry
[0] == 'T' && input_entry
[1] == '{') {
1200 START
, MIDDLE
, GOT_T
, GOT_RIGHT_BRACE
, GOT_DOT
,
1203 while (state
!= END
) {
1221 state
= GOT_RIGHT_BRACE
;
1225 state
= c
== '\n' ? START
: MIDDLE
;
1234 state
= c
== '\n' ? START
: MIDDLE
;
1241 input_entry
+= ".l";
1243 state
= c
== '\n' ? START
: MIDDLE
;
1247 if (c
== ' ' || c
== '\n' || compatible_flag
) {
1249 input_entry
+= ".lf";
1257 interpret_lf_args(args
.contents());
1259 args
.set_length(args
.length() - 1);
1260 input_entry
+= args
;
1264 input_entry
+= ".lf";
1269 case GOT_RIGHT_BRACE
:
1270 if (c
== '\n' || c
== tab_char
)
1276 state
= c
== '\n' ? START
: MIDDLE
;
1290 error("end of data in middle of text block");
1295 if (col
>= ncolumns
) {
1296 if (!input_entry
.empty()) {
1299 input_entry
+= '\0';
1300 error("excess data entry `%1' discarded",
1301 input_entry
.contents());
1307 tbl
->add_entry(current_row
, col
, input_entry
,
1308 &line_format
[col
], current_filename
, ln
);
1323 for (; col
< ncolumns
; col
++)
1324 tbl
->add_entry(current_row
, col
, input_entry
, &line_format
[col
],
1325 current_filename
, current_lineno
- 1);
1326 tbl
->add_vlines(current_row
, f
->vline
[format_index
]);
1331 case TROFF_INPUT_LINE
:
1334 int ln
= current_lineno
;
1344 tbl
->add_text_line(current_row
, line
, current_filename
, ln
);
1345 if (line
.length() >= 4
1346 && line
[0] == '.' && line
[1] == 'T' && line
[2] == '&') {
1347 format
*newf
= process_format(in
, opt
, f
);
1353 if (line
.length() >= 3
1354 && line
[0] == '.' && line
[1] == 'f' && line
[2] == 'f') {
1356 interpret_lf_args(line
.contents() + 3);
1361 tbl
->add_single_hline(current_row
);
1364 tbl
->add_double_hline(current_row
);
1372 if (!give_up
&& current_row
== 0) {
1373 error("no real data");
1383 void process_table(table_input
&in
)
1389 if ((opt
= process_options(in
)) != 0
1390 && (form
= process_format(in
, opt
)) != 0
1391 && (tbl
= process_data(in
, form
, opt
)) != 0) {
1396 error("giving up on this table");
1397 while ((c
= in
.get()) != EOF
)
1403 error("premature end of file");
1408 fprintf(stderr
, "usage: %s [ -vC ] [ files... ]\n", program_name
);
1412 int main(int argc
, char **argv
)
1414 program_name
= argv
[0];
1415 static char stderr_buf
[BUFSIZ
];
1416 setbuf(stderr
, stderr_buf
);
1418 while ((opt
= getopt(argc
, argv
, "vC")) != EOF
)
1421 compatible_flag
= 1;
1425 extern const char *version_string
;
1426 fprintf(stderr
, "GNU tbl version %s\n", version_string
);
1436 printf(".if !\\n(.g .ab GNU tbl requires GNU troff.\n"
1438 ".if !dTE .ds TE\n");
1439 if (argc
> optind
) {
1440 for (int i
= optind
; i
< argc
; i
++)
1441 if (argv
[i
][0] == '-' && argv
[i
][1] == '\0') {
1442 current_filename
= "-";
1445 printf(".lf 1 -\n");
1446 process_input_file(stdin
);
1450 FILE *fp
= fopen(argv
[i
], "r");
1452 current_lineno
= -1;
1453 error("can't open `%1': %2", argv
[i
], strerror(errno
));
1457 current_filename
= argv
[i
];
1458 printf(".lf 1 %s\n", current_filename
);
1459 process_input_file(fp
);
1464 current_filename
= "-";
1466 process_input_file(stdin
);
1468 if (ferror(stdout
) || fflush(stdout
) < 0)
1469 fatal("output error");