2 /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003, 2004, 2005
3 Free Software Foundation, Inc.
4 Written by James Clark (jjc@jclark.com)
6 This file is part of groff.
8 groff 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
13 groff 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
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. */
24 #define MAX_POINT_SIZE 99
25 #define MAX_VERTICAL_SPACING 72
27 extern "C" const char *Version_string
;
29 int compatible_flag
= 0;
34 REREAD_T
, REREAD_TE
, REREAD_E
,
35 LEADER_1
, LEADER_2
, LEADER_3
, LEADER_4
,
41 int ended() { return unget_stack
.empty() && state
== END
; }
45 table_input::table_input(FILE *p
)
50 void table_input::unget(char c
)
58 int table_input::get()
60 int len
= unget_stack
.length();
62 unsigned char c
= unget_stack
[len
- 1];
63 unget_stack
.set_length(len
- 1);
72 if ((c
= getc(fp
)) == '.') {
73 if ((c
= getc(fp
)) == 'T') {
74 if ((c
= getc(fp
)) == 'E') {
75 if (compatible_flag
) {
83 if (c
== EOF
|| c
== ' ' || c
== '\n') {
115 error("invalid input character code 0");
123 // handle line continuation and uninterpreted leader character
124 if ((c
= getc(fp
)) == '\\') {
127 c
= getc(fp
); // perhaps state ought to be START now
128 else if (c
== 'a' && compatible_flag
) {
147 else if (c
== '\0') {
148 error("invalid input character code 0");
181 void process_input_file(FILE *);
182 void process_table(table_input
&in
);
184 void process_input_file(FILE *fp
)
186 enum { START
, MIDDLE
, HAD_DOT
, HAD_T
, HAD_TS
, HAD_l
, HAD_lf
} state
;
189 while ((c
= getc(fp
)) != EOF
)
241 if (c
== ' ' || c
== '\n' || compatible_flag
) {
247 error("end of file at beginning of table");
256 table_input
input(fp
);
257 process_table(input
);
258 set_troff_location(current_filename
, current_lineno
);
260 fputs(".TE", stdout
);
261 while ((c
= getc(fp
)) != '\n') {
275 fputs(".TS", stdout
);
296 if (c
== ' ' || c
== '\n' || compatible_flag
) {
307 interpret_lf_args(line
.contents());
308 printf(".lf%s", line
.contents());
312 fputs(".lf", stdout
);
327 fputs(".\n", stdout
);
330 fputs(".l\n", stdout
);
333 fputs(".T\n", stdout
);
336 fputs(".lf\n", stdout
);
339 fputs(".TS\n", stdout
);
351 char decimal_point_char
;
357 : flags(0), linesize(0), tab_char('\t'), decimal_point_char('.')
359 delim
[0] = delim
[1] = '\0';
362 // Return non-zero if p and q are the same ignoring case.
364 int strieq(const char *p
, const char *q
)
366 for (; cmlower(*p
) == cmlower(*q
); p
++, q
++)
372 // return 0 if we should give up in this table
374 options
*process_options(table_input
&in
)
376 options
*opt
= new options
;
382 int i
= line
.length();
389 int i
= line
.length();
398 else if (c
== ';' && level
== 0) {
408 while (!csalpha(*p
) && *p
!= '\0')
416 if (*q
!= '(' && *q
!= '\0')
423 while (*q
!= ')' && *q
!= '\0')
426 error("missing `)'");
432 error("argument without option");
434 else if (strieq(p
, "tab")) {
436 error("`tab' option requires argument in parentheses");
438 if (arg
[0] == '\0' || arg
[1] != '\0')
439 error("argument to `tab' option must be a single character");
441 opt
->tab_char
= arg
[0];
444 else if (strieq(p
, "linesize")) {
446 error("`linesize' option requires argument in parentheses");
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");
456 else if (strieq(p
, "delim")) {
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");
462 opt
->delim
[0] = arg
[0];
463 opt
->delim
[1] = arg
[1];
466 else if (strieq(p
, "center") || strieq(p
, "centre")) {
468 error("`center' option does not take an argument");
469 opt
->flags
|= table::CENTER
;
471 else if (strieq(p
, "expand")) {
473 error("`expand' option does not take an argument");
474 opt
->flags
|= table::EXPAND
;
476 else if (strieq(p
, "box") || strieq(p
, "frame")) {
478 error("`box' option does not take an argument");
479 opt
->flags
|= table::BOX
;
481 else if (strieq(p
, "doublebox") || strieq(p
, "doubleframe")) {
483 error("`doublebox' option does not take an argument");
484 opt
->flags
|= table::DOUBLEBOX
;
486 else if (strieq(p
, "allbox")) {
488 error("`allbox' option does not take an argument");
489 opt
->flags
|= table::ALLBOX
;
491 else if (strieq(p
, "nokeep")) {
493 error("`nokeep' option does not take an argument");
494 opt
->flags
|= table::NOKEEP
;
496 else if (strieq(p
, "nospaces")) {
498 error("`nospaces' option does not take an argument");
499 opt
->flags
|= table::NOSPACES
;
501 else if (strieq(p
, "decimalpoint")) {
503 error("`decimalpoint' option requires argument in parentheses");
505 if (arg
[0] == '\0' || arg
[1] != '\0')
506 error("argument to `decimalpoint' option must be a single character");
508 opt
->decimal_point_char
= arg
[0];
511 else if (strieq(p
, "experimental")) {
512 opt
->flags
|= table::EXPERIMENTAL
;
515 error("unrecognised global option `%1'", p
);
524 entry_modifier::entry_modifier()
525 : vertical_alignment(CENTER
), zero_width(0), stagger(0)
527 vertical_spacing
.inc
= vertical_spacing
.val
= 0;
528 point_size
.inc
= point_size
.val
= 0;
531 entry_modifier::~entry_modifier()
535 entry_format::entry_format() : type(FORMAT_LEFT
)
539 entry_format::entry_format(format_type t
) : type(t
)
543 void entry_format::debug_print() const
558 case FORMAT_ALPHABETIC
:
570 case FORMAT_DOUBLE_HLINE
:
577 if (point_size
.val
!= 0) {
579 if (point_size
.inc
> 0)
581 else if (point_size
.inc
< 0)
583 fprintf(stderr
, "%d ", point_size
.val
);
585 if (vertical_spacing
.val
!= 0) {
587 if (vertical_spacing
.inc
> 0)
589 else if (vertical_spacing
.inc
< 0)
591 fprintf(stderr
, "%d ", vertical_spacing
.val
);
595 put_string(font
, stderr
);
598 if (!macro
.empty()) {
600 put_string(macro
, stderr
);
603 switch (vertical_alignment
) {
604 case entry_modifier::CENTER
:
606 case entry_modifier::TOP
:
609 case entry_modifier::BOTTOM
:
625 entry_format
**entry
;
628 format(int nr
, int nc
);
630 void add_rows(int n
);
633 format::format(int nr
, int nc
) : nrows(nr
), ncolumns(nc
)
636 separation
= ncolumns
> 1 ? new int[ncolumns
- 1] : 0;
637 for (i
= 0; i
< ncolumns
-1; i
++)
639 width
= new string
[ncolumns
];
640 equal
= new char[ncolumns
];
641 for (i
= 0; i
< ncolumns
; i
++)
643 entry
= new entry_format
*[nrows
];
644 for (i
= 0; i
< nrows
; i
++)
645 entry
[i
] = new entry_format
[ncolumns
];
646 vline
= new char*[nrows
];
647 for (i
= 0; i
< nrows
; i
++) {
648 vline
[i
] = new char[ncolumns
+1];
649 for (int j
= 0; j
< ncolumns
+1; j
++)
654 void format::add_rows(int n
)
657 char **old_vline
= vline
;
658 vline
= new char*[nrows
+ n
];
659 for (i
= 0; i
< nrows
; i
++)
660 vline
[i
] = old_vline
[i
];
662 for (i
= 0; i
< n
; i
++) {
663 vline
[nrows
+ i
] = new char[ncolumns
+ 1];
664 for (int j
= 0; j
< ncolumns
+ 1; j
++)
665 vline
[nrows
+ i
][j
] = 0;
667 entry_format
**old_entry
= entry
;
668 entry
= new entry_format
*[nrows
+ n
];
669 for (i
= 0; i
< nrows
; i
++)
670 entry
[i
] = old_entry
[i
];
672 for (i
= 0; i
< n
; i
++)
673 entry
[nrows
+ i
] = new entry_format
[ncolumns
];
680 ad_delete(ncolumns
) width
;
682 for (int i
= 0; i
< nrows
; i
++) {
684 ad_delete(ncolumns
) entry
[i
];
690 struct input_entry_format
: public entry_format
{
691 input_entry_format
*next
;
698 input_entry_format(format_type
, input_entry_format
* = 0);
699 ~input_entry_format();
703 input_entry_format::input_entry_format(format_type t
, input_entry_format
*p
)
704 : entry_format(t
), next(p
)
713 input_entry_format::~input_entry_format()
717 void free_input_entry_format_list(input_entry_format
*list
)
720 input_entry_format
*tem
= list
;
726 void input_entry_format::debug_print()
729 for (i
= 0; i
< pre_vline
; i
++)
731 entry_format::debug_print();
732 if (!width
.empty()) {
735 put_string(width
, stderr
);
741 fprintf(stderr
, "%d", separation
);
742 for (i
= 0; i
< vline
; i
++)
748 // Return zero if we should give up on this table.
749 // If this is a continuation format line, current_format will be the current
752 format
*process_format(table_input
&in
, options
*opt
,
753 format
*current_format
= 0)
755 input_entry_format
*list
= 0;
761 format_type t
= FORMAT_LEFT
;
764 error("end of input while processing format");
765 free_input_entry_format_list(list
);
777 t
= FORMAT_ALPHABETIC
;
804 case '-': // tbl also accepts this
810 t
= FORMAT_DOUBLE_HLINE
;
823 if (c
== opt
->tab_char
)
825 error("unrecognised format `%1'", char(c
));
826 free_input_entry_format_list(list
);
837 list
= new input_entry_format(t
, list
);
839 list
->pre_vline
= pre_vline
;
846 list
->vertical_alignment
= entry_modifier::TOP
;
851 list
->vertical_alignment
= entry_modifier::BOTTOM
;
861 list
->zero_width
= 1;
876 w
= w
*10 + (c
- '0');
878 } while (c
!= EOF
&& csdigit(c
));
879 list
->separation
= w
;
886 } while (c
== ' ' || c
== '\t');
888 error("missing font name");
894 if (c
== EOF
|| c
== ' ' || c
== '\t') {
895 error("missing `)'");
902 list
->font
+= char(c
);
910 && c
!= EOF
&& c
!= ' ' && c
!= '\t' && c
!= '.' && c
!= '\n') {
911 list
->font
+= char(c
);
920 } while (c
== ' ' || c
== '\t');
922 error("missing macro name");
928 if (c
== EOF
|| c
== ' ' || c
== '\t') {
929 error("missing `)'");
936 list
->macro
+= char(c
);
944 && c
!= EOF
&& c
!= ' ' && c
!= '\t' && c
!= '.' && c
!= '\n') {
945 list
->macro
+= char(c
);
953 list
->vertical_spacing
.val
= 0;
954 list
->vertical_spacing
.inc
= 0;
955 if (c
== '+' || c
== '-') {
956 list
->vertical_spacing
.inc
= (c
== '+' ? 1 : -1);
959 if (c
== EOF
|| !csdigit(c
)) {
960 error("`v' modifier must be followed by number");
961 list
->vertical_spacing
.inc
= 0;
965 list
->vertical_spacing
.val
*= 10;
966 list
->vertical_spacing
.val
+= c
- '0';
968 } while (c
!= EOF
&& csdigit(c
));
970 if (list
->vertical_spacing
.val
> MAX_VERTICAL_SPACING
971 || list
->vertical_spacing
.val
< -MAX_VERTICAL_SPACING
) {
972 error("unreasonable vertical spacing");
973 list
->vertical_spacing
.val
= 0;
974 list
->vertical_spacing
.inc
= 0;
980 list
->point_size
.val
= 0;
981 list
->point_size
.inc
= 0;
982 if (c
== '+' || c
== '-') {
983 list
->point_size
.inc
= (c
== '+' ? 1 : -1);
986 if (c
== EOF
|| !csdigit(c
)) {
987 error("`p' modifier must be followed by number");
988 list
->point_size
.inc
= 0;
992 list
->point_size
.val
*= 10;
993 list
->point_size
.val
+= c
- '0';
995 } while (c
!= EOF
&& csdigit(c
));
997 if (list
->point_size
.val
> MAX_POINT_SIZE
998 || list
->point_size
.val
< -MAX_POINT_SIZE
) {
999 error("unreasonable point size");
1000 list
->point_size
.val
= 0;
1001 list
->point_size
.inc
= 0;
1007 while (c
== ' ' || c
== '\t')
1013 if (c
== EOF
|| c
== '\n') {
1014 error("missing `)'");
1015 free_input_entry_format_list(list
);
1024 if (c
== '+' || c
== '-') {
1025 list
->width
= char(c
);
1030 if (c
== EOF
|| !csdigit(c
))
1031 error("bad argument for `w' modifier");
1034 list
->width
+= char(c
);
1036 } while (c
!= EOF
&& csdigit(c
));
1064 if (c
== opt
->tab_char
)
1071 if (list
->vline
> 2) {
1073 error("more than 2 vertical bars between key letters");
1075 if (c
== '\n' || c
== ',') {
1077 list
->last_column
= 1;
1083 } while (c
== ' ' || c
== '\t');
1085 error("`.' not last character on line");
1086 free_input_entry_format_list(list
);
1092 free_input_entry_format_list(list
);
1095 list
->last_column
= 1;
1096 // now reverse the list so that the first row is at the beginning
1097 input_entry_format
*rev
= 0;
1099 input_entry_format
*tem
= list
->next
;
1105 input_entry_format
*tem
;
1108 for (tem
= list
; tem
; tem
= tem
->next
)
1112 // compute number of columns and rows
1116 for (tem
= list
; tem
; tem
= tem
->next
) {
1117 if (tem
->last_column
) {
1118 if (col
>= ncolumns
)
1128 if (current_format
) {
1129 if (ncolumns
> current_format
->ncolumns
) {
1130 error("cannot increase the number of columns in a continued format");
1131 free_input_entry_format_list(list
);
1139 f
= new format(nrows
, ncolumns
);
1143 for (tem
= list
; tem
; tem
= tem
->next
) {
1144 f
->entry
[row
][col
] = *tem
;
1145 if (col
< ncolumns
-1) {
1146 // use the greatest separation
1147 if (tem
->separation
> f
->separation
[col
]) {
1149 error("cannot change column separation in continued format");
1151 f
->separation
[col
] = tem
->separation
;
1154 else if (tem
->separation
>= 0)
1155 error("column separation specified for last column");
1156 if (tem
->equal
&& !f
->equal
[col
]) {
1158 error("cannot change which columns are equal in continued format");
1162 if (!tem
->width
.empty()) {
1163 // use the last width
1164 if (!f
->width
[col
].empty() && f
->width
[col
] != tem
->width
)
1165 error("multiple widths for column %1", col
+1);
1166 f
->width
[col
] = tem
->width
;
1168 if (tem
->pre_vline
) {
1170 f
->vline
[row
][col
] = tem
->pre_vline
;
1172 f
->vline
[row
][col
+1] = tem
->vline
;
1173 if (tem
->last_column
) {
1180 free_input_entry_format_list(list
);
1181 for (col
= 0; col
< ncolumns
; col
++) {
1182 entry_format
*e
= f
->entry
[f
->nrows
-1] + col
;
1183 if (e
->type
!= FORMAT_HLINE
1184 && e
->type
!= FORMAT_DOUBLE_HLINE
1185 && e
->type
!= FORMAT_SPAN
)
1188 if (col
>= ncolumns
) {
1189 error("last row of format is all lines");
1196 table
*process_data(table_input
&in
, format
*f
, options
*opt
)
1198 char tab_char
= opt
->tab_char
;
1199 int ncolumns
= f
->ncolumns
;
1200 int current_row
= 0;
1201 int format_index
= 0;
1203 enum { DATA_INPUT_LINE
, TROFF_INPUT_LINE
, SINGLE_HLINE
, DOUBLE_HLINE
} type
;
1204 table
*tbl
= new table(ncolumns
, opt
->flags
, opt
->linesize
,
1205 opt
->decimal_point_char
);
1206 if (opt
->delim
[0] != '\0')
1207 tbl
->set_delim(opt
->delim
[0], opt
->delim
[1]);
1209 // first determine what type of line this is
1215 if (d
!= EOF
&& csdigit(d
)) {
1217 type
= DATA_INPUT_LINE
;
1221 type
= TROFF_INPUT_LINE
;
1224 else if (c
== '_' || c
== '=') {
1228 type
= SINGLE_HLINE
;
1230 type
= DOUBLE_HLINE
;
1234 type
= DATA_INPUT_LINE
;
1238 type
= DATA_INPUT_LINE
;
1241 case DATA_INPUT_LINE
:
1244 if (format_index
>= f
->nrows
)
1245 format_index
= f
->nrows
- 1;
1246 // A format row that is all lines doesn't use up a data line.
1247 while (format_index
< f
->nrows
- 1) {
1249 for (cnt
= 0; cnt
< ncolumns
; cnt
++) {
1250 entry_format
*e
= f
->entry
[format_index
] + cnt
;
1251 if (e
->type
!= FORMAT_HLINE
1252 && e
->type
!= FORMAT_DOUBLE_HLINE
1253 // Unfortunately tbl treats a span as needing data.
1254 // && e->type != FORMAT_SPAN
1260 for (cnt
= 0; cnt
< ncolumns
; cnt
++)
1261 tbl
->add_entry(current_row
, cnt
, input_entry
,
1262 f
->entry
[format_index
] + cnt
, current_filename
,
1264 tbl
->add_vlines(current_row
, f
->vline
[format_index
]);
1268 entry_format
*line_format
= f
->entry
[format_index
];
1270 int row_comment
= 0;
1272 if (c
== tab_char
|| c
== '\n') {
1273 int ln
= current_lineno
;
1276 if ((opt
->flags
& table::NOSPACES
))
1277 input_entry
.remove_spaces();
1278 while (col
< ncolumns
1279 && line_format
[col
].type
== FORMAT_SPAN
) {
1280 tbl
->add_entry(current_row
, col
, "", &line_format
[col
],
1281 current_filename
, ln
);
1284 if (c
== '\n' && input_entry
.length() == 2
1285 && input_entry
[0] == 'T' && input_entry
[1] == '{') {
1289 START
, MIDDLE
, GOT_T
, GOT_RIGHT_BRACE
, GOT_DOT
,
1292 while (state
!= END
) {
1310 state
= GOT_RIGHT_BRACE
;
1314 state
= c
== '\n' ? START
: MIDDLE
;
1323 state
= c
== '\n' ? START
: MIDDLE
;
1330 input_entry
+= ".l";
1332 state
= c
== '\n' ? START
: MIDDLE
;
1336 if (c
== ' ' || c
== '\n' || compatible_flag
) {
1338 input_entry
+= ".lf";
1346 interpret_lf_args(args
.contents());
1348 args
.set_length(args
.length() - 1);
1349 input_entry
+= args
;
1353 input_entry
+= ".lf";
1358 case GOT_RIGHT_BRACE
:
1359 if ((opt
->flags
& table::NOSPACES
)) {
1365 if (c
== '\n' || c
== tab_char
)
1385 error("end of data in middle of text block");
1390 if (col
>= ncolumns
) {
1391 if (!input_entry
.empty()) {
1392 if (input_entry
.length() >= 2
1393 && input_entry
[0] == '\\'
1394 && input_entry
[1] == '"')
1396 else if (!row_comment
) {
1399 input_entry
+= '\0';
1400 error("excess data entry `%1' discarded",
1401 input_entry
.contents());
1408 tbl
->add_entry(current_row
, col
, input_entry
,
1409 &line_format
[col
], current_filename
, ln
);
1424 for (; col
< ncolumns
; col
++)
1425 tbl
->add_entry(current_row
, col
, input_entry
, &line_format
[col
],
1426 current_filename
, current_lineno
- 1);
1427 tbl
->add_vlines(current_row
, f
->vline
[format_index
]);
1432 case TROFF_INPUT_LINE
:
1435 int ln
= current_lineno
;
1445 tbl
->add_text_line(current_row
, line
, current_filename
, ln
);
1446 if (line
.length() >= 4
1447 && line
[0] == '.' && line
[1] == 'T' && line
[2] == '&') {
1448 format
*newf
= process_format(in
, opt
, f
);
1454 if (line
.length() >= 3
1455 && line
[0] == '.' && line
[1] == 'l' && line
[2] == 'f') {
1457 interpret_lf_args(line
.contents() + 3);
1462 tbl
->add_single_hline(current_row
);
1465 tbl
->add_double_hline(current_row
);
1473 if (!give_up
&& current_row
== 0) {
1474 error("no real data");
1481 // Do this here rather than at the beginning in case continued formats
1484 for (i
= 0; i
< ncolumns
- 1; i
++)
1485 if (f
->separation
[i
] >= 0)
1486 tbl
->set_column_separation(i
, f
->separation
[i
]);
1487 for (i
= 0; i
< ncolumns
; i
++)
1488 if (!f
->width
[i
].empty())
1489 tbl
->set_minimum_width(i
, f
->width
[i
]);
1490 for (i
= 0; i
< ncolumns
; i
++)
1492 tbl
->set_equal_column(i
);
1496 void process_table(table_input
&in
)
1501 if ((opt
= process_options(in
)) != 0
1502 && (form
= process_format(in
, opt
)) != 0
1503 && (tbl
= process_data(in
, form
, opt
)) != 0) {
1508 error("giving up on this table");
1509 while (in
.get() != EOF
)
1515 error("premature end of file");
1518 static void usage(FILE *stream
)
1520 fprintf(stream
, "usage: %s [ -vC ] [ files... ]\n", program_name
);
1523 int main(int argc
, char **argv
)
1525 program_name
= argv
[0];
1526 static char stderr_buf
[BUFSIZ
];
1527 setbuf(stderr
, stderr_buf
);
1529 static const struct option long_options
[] = {
1530 { "help", no_argument
, 0, CHAR_MAX
+ 1 },
1531 { "version", no_argument
, 0, 'v' },
1534 while ((opt
= getopt_long(argc
, argv
, "vCT:", long_options
, NULL
)) != EOF
)
1537 compatible_flag
= 1;
1541 printf("GNU tbl (groff) version %s\n", Version_string
);
1546 // I'm sick of getting bug reports from IRIX users
1548 case CHAR_MAX
+ 1: // --help
1559 printf(".if !\\n(.g .ab GNU tbl requires GNU troff.\n"
1561 ".if !dTE .ds TE\n");
1562 if (argc
> optind
) {
1563 for (int i
= optind
; i
< argc
; i
++)
1564 if (argv
[i
][0] == '-' && argv
[i
][1] == '\0') {
1565 current_filename
= "-";
1567 printf(".lf 1 -\n");
1568 process_input_file(stdin
);
1572 FILE *fp
= fopen(argv
[i
], "r");
1574 fatal("can't open `%1': %2", argv
[i
], strerror(errno
));
1577 current_filename
= argv
[i
];
1578 printf(".lf 1 %s\n", current_filename
);
1579 process_input_file(fp
);
1584 current_filename
= "-";
1586 printf(".lf 1 -\n");
1587 process_input_file(stdin
);
1589 if (ferror(stdout
) || fflush(stdout
) < 0)
1590 fatal("output error");