2 /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003, 2004, 2005,
4 Free Software Foundation, Inc.
5 Written by James Clark (jjc@jclark.com)
7 This file is part of groff.
9 groff is free software; you can redistribute it and/or modify it under
10 the terms of the GNU General Public License as published by the Free
11 Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
14 groff is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>. */
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:
626 entry_format **entry;
629 format(int nr, int nc);
631 void add_rows(int n);
634 format::format(int nr, int nc) : nrows(nr), ncolumns(nc)
637 separation = ncolumns > 1 ? new int[ncolumns - 1] : 0;
638 for (i = 0; i < ncolumns-1; i++)
640 width = new string[ncolumns];
641 equal = new char[ncolumns];
642 expand = new char[ncolumns];
643 for (i = 0; i < ncolumns; i++) {
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++)
658 void format::add_rows(int n)
661 char **old_vline = vline;
662 vline = new char*[nrows + n];
663 for (i = 0; i < nrows; i++)
664 vline[i] = old_vline[i];
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;
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];
676 for (i = 0; i < n; i++)
677 entry[nrows + i] = new entry_format[ncolumns];
684 ad_delete(ncolumns) width;
687 for (int i = 0; i < nrows; i++) {
689 ad_delete(ncolumns) entry[i];
695 struct input_entry_format : public entry_format {
696 input_entry_format *next;
704 input_entry_format(format_type, input_entry_format * = 0);
705 ~input_entry_format();
709 input_entry_format::input_entry_format(format_type t, input_entry_format *p)
710 : entry_format(t), next(p)
720 input_entry_format::~input_entry_format()
724 void free_input_entry_format_list(input_entry_format *list)
727 input_entry_format *tem = list;
733 void input_entry_format::debug_print()
736 for (i = 0; i < pre_vline; i++)
738 entry_format::debug_print();
739 if (!width.empty()) {
742 put_string(width, stderr);
750 fprintf(stderr, "%d", separation);
751 for (i = 0; i < vline; i++)
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
761 format *process_format(table_input &in, options *opt,
762 format *current_format = 0)
764 input_entry_format *list = 0;
771 format_type t = FORMAT_LEFT;
774 error("end of input while processing format");
775 free_input_entry_format_list(list);
787 t = FORMAT_ALPHABETIC;
814 case '-': // tbl also accepts this
820 t = FORMAT_DOUBLE_HLINE;
833 if (c == opt->tab_char)
835 error("unrecognised format `%1'", char(c));
836 free_input_entry_format_list(list);
847 list = new input_entry_format(t, list);
849 list->pre_vline = pre_vline;
866 w = w*10 + (c - '0');
868 } while (c != EOF && csdigit(c));
869 list->separation = w;
880 list->vertical_alignment = entry_modifier::BOTTOM;
886 // `e' and `x' are mutually exclusive
893 } while (c == ' ' || c == '\t');
895 error("missing font name");
901 if (c == EOF || c == ' ' || c == '\t') {
902 error("missing `)'");
909 list->font += char(c);
917 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
918 list->font += char(c);
932 } while (c == ' ' || c == '\t');
934 error("missing macro name");
940 if (c == EOF || c == ' ' || c == '\t') {
941 error("missing `)'");
948 list->macro += char(c);
956 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
957 list->macro += char(c);
965 list->point_size.val = 0;
966 list->point_size.inc = 0;
967 if (c == '+' || c == '-') {
968 list->point_size.inc = (c == '+' ? 1 : -1);
971 if (c == EOF || !csdigit(c)) {
972 error("`p' modifier must be followed by number");
973 list->point_size.inc = 0;
977 list->point_size.val *= 10;
978 list->point_size.val += c - '0';
980 } while (c != EOF && csdigit(c));
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;
992 list->vertical_alignment = entry_modifier::TOP;
1002 list->vertical_spacing.val = 0;
1003 list->vertical_spacing.inc = 0;
1004 if (c == '+' || c == '-') {
1005 list->vertical_spacing.inc = (c == '+' ? 1 : -1);
1008 if (c == EOF || !csdigit(c)) {
1009 error("`v' modifier must be followed by number");
1010 list->vertical_spacing.inc = 0;
1014 list->vertical_spacing.val *= 10;
1015 list->vertical_spacing.val += c - '0';
1017 } while (c != EOF && csdigit(c));
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;
1029 while (c == ' ' || c == '\t')
1035 if (c == EOF || c == '\n') {
1036 error("missing `)'");
1037 free_input_entry_format_list(list);
1046 if (c == '+' || c == '-') {
1047 list->width = char(c);
1052 if (c == EOF || !csdigit(c))
1053 error("bad argument for `w' modifier");
1056 list->width += char(c);
1058 } while (c != EOF && csdigit(c));
1061 // `w' and `x' are mutually exclusive
1068 // `x' and `e' are mutually exclusive
1070 // `x' and `w' are mutually exclusive
1076 list->zero_width = 1;
1087 if (c == opt->tab_char)
1094 if (list->vline > 2) {
1096 error("more than 2 vertical bars between key letters");
1098 if (c == '\n' || c == ',') {
1100 list->last_column = 1;
1106 } while (c == ' ' || c == '\t');
1108 error("`.' not last character on line");
1109 free_input_entry_format_list(list);
1115 free_input_entry_format_list(list);
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;
1122 input_entry_format *tem = list->next;
1128 input_entry_format *tem;
1131 for (tem = list; tem; tem = tem->next)
1135 // compute number of columns and rows
1139 for (tem = list; tem; tem = tem->next) {
1140 if (tem->last_column) {
1141 if (col >= ncolumns)
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);
1162 f = new format(nrows, ncolumns);
1166 for (tem = list; tem; tem = tem->next) {
1167 f->entry[row][col] = *tem;
1168 if (col < ncolumns - 1) {
1169 // use the greatest separation
1170 if (tem->separation > f->separation[col]) {
1172 error("cannot change column separation in continued format");
1174 f->separation[col] = tem->separation;
1177 else if (tem->separation >= 0)
1178 error("column separation specified for last column");
1179 if (tem->equal && !f->equal[col]) {
1181 error("cannot change which columns are equal in continued format");
1185 if (tem->expand && !f->expand[col]) {
1187 error("cannot change which columns are expanded in continued format");
1193 if (!tem->width.empty()) {
1194 // use the last width
1195 if (!f->width[col].empty() && f->width[col] != tem->width)
1196 error("multiple widths for column %1", col + 1);
1197 f->width[col] = tem->width;
1199 if (tem->pre_vline) {
1201 f->vline[row][col] = tem->pre_vline;
1203 f->vline[row][col + 1] = tem->vline;
1204 if (tem->last_column) {
1211 free_input_entry_format_list(list);
1212 for (col = 0; col < ncolumns; col++) {
1213 entry_format *e = f->entry[f->nrows - 1] + col;
1214 if (e->type != FORMAT_HLINE
1215 && e->type != FORMAT_DOUBLE_HLINE
1216 && e->type != FORMAT_SPAN)
1219 if (col >= ncolumns) {
1220 error("last row of format is all lines");
1224 if (have_expand && (opt->flags & table::EXPAND)) {
1225 error("ignoring global `expand' option because of `x' specifiers");
1226 opt->flags &= ~table::EXPAND;
1231 table *process_data(table_input &in, format *f, options *opt)
1233 char tab_char = opt->tab_char;
1234 int ncolumns = f->ncolumns;
1235 int current_row = 0;
1236 int format_index = 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]);
1244 // first determine what type of line this is
1250 if (d != EOF && csdigit(d)) {
1252 type = DATA_INPUT_LINE;
1256 type = TROFF_INPUT_LINE;
1259 else if (c == '_' || c == '=') {
1263 type = SINGLE_HLINE;
1265 type = DOUBLE_HLINE;
1269 type = DATA_INPUT_LINE;
1273 type = DATA_INPUT_LINE;
1276 case DATA_INPUT_LINE:
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) {
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
1295 for (cnt = 0; cnt < ncolumns; cnt++)
1296 tbl->add_entry(current_row, cnt, input_entry,
1297 f->entry[format_index] + cnt, current_filename,
1299 tbl->add_vlines(current_row, f->vline[format_index]);
1303 entry_format *line_format = f->entry[format_index];
1305 int row_comment = 0;
1307 if (c == tab_char || c == '\n') {
1308 int ln = current_lineno;
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);
1319 if (c == '\n' && input_entry.length() == 2
1320 && input_entry[0] == 'T' && input_entry[1] == '{') {
1324 START, MIDDLE, GOT_T, GOT_RIGHT_BRACE, GOT_DOT,
1327 while (state != END) {
1345 state = GOT_RIGHT_BRACE;
1349 state = c == '\n' ? START : MIDDLE;
1358 state = c == '\n' ? START : MIDDLE;
1365 input_entry += ".l";
1367 state = c == '\n' ? START : MIDDLE;
1371 if (c == ' ' || c == '\n' || compatible_flag) {
1373 input_entry += ".lf";
1381 interpret_lf_args(args.contents());
1383 args.set_length(args.length() - 1);
1384 input_entry += args;
1388 input_entry += ".lf";
1393 case GOT_RIGHT_BRACE:
1394 if ((opt->flags & table::NOSPACES)) {
1400 if (c == '\n' || c == tab_char)
1420 error("end of data in middle of text block");
1425 if (col >= ncolumns) {
1426 if (!input_entry.empty()) {
1427 if (input_entry.length() >= 2
1428 && input_entry[0] == '\\'
1429 && input_entry[1] == '"')
1431 else if (!row_comment) {
1434 input_entry += '\0';
1435 error("excess data entry `%1' discarded",
1436 input_entry.contents());
1443 tbl->add_entry(current_row, col, input_entry,
1444 &line_format[col], current_filename, ln);
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]);
1467 case TROFF_INPUT_LINE:
1470 int ln = current_lineno;
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);
1489 if (line.length() >= 3
1490 && line[0] == '.' && line[1] == 'l' && line[2] == 'f') {
1492 interpret_lf_args(line.contents() + 3);
1497 tbl->add_single_hline(current_row);
1500 tbl->add_double_hline(current_row);
1508 if (!give_up && current_row == 0) {
1509 error("no real data");
1516 // Do this here rather than at the beginning in case continued formats
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++)
1527 tbl->set_equal_column(i);
1528 for (i = 0; i < ncolumns; i++)
1530 tbl->set_expand_column(i);
1534 void process_table(table_input &in)
1539 if ((opt = process_options(in)) != 0
1540 && (form = process_format(in, opt)) != 0
1541 && (tbl = process_data(in, form, opt)) != 0) {
1546 error("giving up on this table");
1547 while (in.get() != EOF)
1553 error("premature end of file");
1556 static void usage(FILE *stream)
1558 fprintf(stream, "usage: %s [ -vC ] [ files... ]\n", program_name);
1561 int main(int argc, char **argv)
1563 program_name = argv[0];
1564 static char stderr_buf[BUFSIZ];
1565 setbuf(stderr, stderr_buf);
1567 static const struct option long_options[] = {
1568 { "help", no_argument, 0, CHAR_MAX + 1 },
1569 { "version", no_argument, 0, 'v' },
1572 while ((opt = getopt_long(argc, argv, "vCT:", long_options, NULL)) != EOF)
1575 compatible_flag = 1;
1579 printf("GNU tbl (groff) version %s\n", Version_string);
1584 // I'm sick of getting bug reports from IRIX users
1586 case CHAR_MAX + 1: // --help
1597 printf(".if !\\n(.g .ab GNU tbl requires GNU troff.\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 = "-";
1605 printf(".lf 1 -\n");
1606 process_input_file(stdin);
1610 FILE *fp = fopen(argv[i], "r");
1612 fatal("can't open `%1': %2", argv[i], strerror(errno));
1615 current_filename = argv[i];
1616 printf(".lf 1 %s\n", current_filename);
1617 process_input_file(fp);
1622 current_filename = "-";
1624 printf(".lf 1 -\n");
1625 process_input_file(stdin);
1627 if (ferror(stdout) || fflush(stdout) < 0)
1628 fatal("output error");