2 /* Copyright (C) 1989-2018 Free Software Foundation, Inc.
3 Written by James Clark (jjc@jclark.com)
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 3 of the License, or
10 (at your option) any later version.
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
18 along with this program. If not, see <http://www.gnu.org/licenses/>. */
22 #define MAX_POINT_SIZE 99
23 #define MAX_VERTICAL_SPACING 72
25 extern "C" const char *Version_string;
27 int compatible_flag = 0;
32 REREAD_T, REREAD_TE, REREAD_E,
33 LEADER_1, LEADER_2, LEADER_3, LEADER_4,
39 int ended() { return unget_stack.empty() && state == END; }
43 table_input::table_input(FILE *p)
48 void table_input::unget(char c)
56 int table_input::get()
58 int len = unget_stack.length();
60 unsigned char c = unget_stack[len - 1];
61 unget_stack.set_length(len - 1);
70 if ((c = getc(fp)) == '.') {
71 if ((c = getc(fp)) == 'T') {
72 if ((c = getc(fp)) == 'E') {
73 if (compatible_flag) {
81 if (c == EOF || c == ' ' || c == '\n') {
113 error("invalid input character code 0");
121 // handle line continuation and uninterpreted leader character
122 if ((c = getc(fp)) == '\\') {
125 c = getc(fp); // perhaps state ought to be START now
126 else if (c == 'a' && compatible_flag) {
145 else if (c == '\0') {
146 error("invalid input character code 0");
179 void process_input_file(FILE *);
180 void process_table(table_input &in);
182 void process_input_file(FILE *fp)
184 enum { START, MIDDLE, HAD_DOT, HAD_T, HAD_TS, HAD_l, HAD_lf } state;
187 while ((c = getc(fp)) != EOF)
239 if (c == ' ' || c == '\n' || compatible_flag) {
245 error("end of file at beginning of table");
254 table_input input(fp);
255 process_table(input);
256 set_troff_location(current_filename, current_lineno);
258 fputs(".TE", stdout);
259 while ((c = getc(fp)) != '\n') {
273 fputs(".TS", stdout);
294 if (c == ' ' || c == '\n' || compatible_flag) {
305 interpret_lf_args(line.contents());
306 printf(".lf%s", line.contents());
310 fputs(".lf", stdout);
325 fputs(".\n", stdout);
328 fputs(".l\n", stdout);
331 fputs(".T\n", stdout);
334 fputs(".lf\n", stdout);
337 fputs(".TS\n", stdout);
349 char decimal_point_char;
355 : flags(0), linesize(0), tab_char('\t'), decimal_point_char('.')
357 delim[0] = delim[1] = '\0';
360 // Return non-zero if p and q are the same ignoring case.
362 int strieq(const char *p, const char *q)
364 for (; cmlower(*p) == cmlower(*q); p++, q++)
370 // return 0 if we should give up in this table
372 options *process_options(table_input &in)
374 options *opt = new options;
380 int i = line.length();
387 int i = line.length();
396 else if (c == ';' && level == 0) {
406 while (!csalpha(*p) && *p != '\0')
414 if (*q != '(' && *q != '\0')
421 while (*q != ')' && *q != '\0')
424 error("missing ')'");
430 error("argument without option");
432 else if (strieq(p, "tab")) {
434 error("'tab' option requires argument in parentheses");
436 if (arg[0] == '\0' || arg[1] != '\0')
437 error("argument to 'tab' option must be a single character");
439 opt->tab_char = arg[0];
442 else if (strieq(p, "linesize")) {
444 error("'linesize' option requires argument in parentheses");
446 if (sscanf(arg, "%d", &opt->linesize) != 1)
447 error("bad linesize '%s'", arg);
448 else if (opt->linesize <= 0) {
449 error("linesize must be positive");
454 else if (strieq(p, "delim")) {
456 error("'delim' option requires argument in parentheses");
457 else if (arg[0] == '\0' || arg[1] == '\0' || arg[2] != '\0')
458 error("argument to 'delim' option must be two characters");
460 opt->delim[0] = arg[0];
461 opt->delim[1] = arg[1];
464 else if (strieq(p, "center") || strieq(p, "centre")) {
466 error("'center' option does not take an argument");
467 opt->flags |= table::CENTER;
469 else if (strieq(p, "expand")) {
471 error("'expand' option does not take an argument");
472 opt->flags |= table::EXPAND;
474 else if (strieq(p, "box") || strieq(p, "frame")) {
476 error("'box' option does not take an argument");
477 opt->flags |= table::BOX;
479 else if (strieq(p, "doublebox") || strieq(p, "doubleframe")) {
481 error("'doublebox' option does not take an argument");
482 opt->flags |= table::DOUBLEBOX;
484 else if (strieq(p, "allbox")) {
486 error("'allbox' option does not take an argument");
487 opt->flags |= table::ALLBOX;
489 else if (strieq(p, "nokeep")) {
491 error("'nokeep' option does not take an argument");
492 opt->flags |= table::NOKEEP;
494 else if (strieq(p, "nospaces")) {
496 error("'nospaces' option does not take an argument");
497 opt->flags |= table::NOSPACES;
499 else if (strieq(p, "nowarn")) {
501 error("'nowarn' option does not take an argument");
502 opt->flags |= table::NOWARN;
504 else if (strieq(p, "decimalpoint")) {
506 error("'decimalpoint' option requires argument in parentheses");
508 if (arg[0] == '\0' || arg[1] != '\0')
509 error("argument to 'decimalpoint' option must be a single character");
511 opt->decimal_point_char = arg[0];
514 else if (strieq(p, "experimental")) {
515 opt->flags |= table::EXPERIMENTAL;
518 error("unrecognised global option '%1'", p);
527 entry_modifier::entry_modifier()
528 : vertical_alignment(CENTER), zero_width(0), stagger(0)
530 vertical_spacing.inc = vertical_spacing.val = 0;
531 point_size.inc = point_size.val = 0;
534 entry_modifier::~entry_modifier()
538 entry_format::entry_format() : type(FORMAT_LEFT)
542 entry_format::entry_format(format_type t) : type(t)
546 void entry_format::debug_print() const
561 case FORMAT_ALPHABETIC:
573 case FORMAT_DOUBLE_HLINE:
580 if (point_size.val != 0) {
582 if (point_size.inc > 0)
584 else if (point_size.inc < 0)
586 fprintf(stderr, "%d ", point_size.val);
588 if (vertical_spacing.val != 0) {
590 if (vertical_spacing.inc > 0)
592 else if (vertical_spacing.inc < 0)
594 fprintf(stderr, "%d ", vertical_spacing.val);
598 put_string(font, stderr);
601 if (!macro.empty()) {
603 put_string(macro, stderr);
606 switch (vertical_alignment) {
607 case entry_modifier::CENTER:
609 case entry_modifier::TOP:
612 case entry_modifier::BOTTOM:
629 entry_format **entry;
632 format(int nr, int nc);
634 void add_rows(int n);
637 format::format(int nr, int nc) : nrows(nr), ncolumns(nc)
640 separation = ncolumns > 1 ? new int[ncolumns - 1] : 0;
641 for (i = 0; i < ncolumns-1; i++)
643 width = new string[ncolumns];
644 equal = new char[ncolumns];
645 expand = new char[ncolumns];
646 for (i = 0; i < ncolumns; i++) {
650 entry = new entry_format *[nrows];
651 for (i = 0; i < nrows; i++)
652 entry[i] = new entry_format[ncolumns];
653 vline = new char*[nrows];
654 for (i = 0; i < nrows; i++) {
655 vline[i] = new char[ncolumns+1];
656 for (int j = 0; j < ncolumns+1; j++)
661 void format::add_rows(int n)
664 char **old_vline = vline;
665 vline = new char*[nrows + n];
666 for (i = 0; i < nrows; i++)
667 vline[i] = old_vline[i];
669 for (i = 0; i < n; i++) {
670 vline[nrows + i] = new char[ncolumns + 1];
671 for (int j = 0; j < ncolumns + 1; j++)
672 vline[nrows + i][j] = 0;
674 entry_format **old_entry = entry;
675 entry = new entry_format *[nrows + n];
676 for (i = 0; i < nrows; i++)
677 entry[i] = old_entry[i];
679 for (i = 0; i < n; i++)
680 entry[nrows + i] = new entry_format[ncolumns];
687 ad_delete(ncolumns) width;
690 for (int i = 0; i < nrows; i++) {
692 ad_delete(ncolumns) entry[i];
698 struct input_entry_format : public entry_format {
699 input_entry_format *next;
707 input_entry_format(format_type, input_entry_format * = 0);
708 ~input_entry_format();
712 input_entry_format::input_entry_format(format_type t, input_entry_format *p)
713 : entry_format(t), next(p)
723 input_entry_format::~input_entry_format()
727 void free_input_entry_format_list(input_entry_format *list)
730 input_entry_format *tem = list;
736 void input_entry_format::debug_print()
739 for (i = 0; i < pre_vline; i++)
741 entry_format::debug_print();
742 if (!width.empty()) {
745 put_string(width, stderr);
753 fprintf(stderr, "%d", separation);
754 for (i = 0; i < vline; i++)
760 // Return zero if we should give up on this table.
761 // If this is a continuation format line, current_format will be the current
764 format *process_format(table_input &in, options *opt,
765 format *current_format = 0)
767 input_entry_format *list = 0;
774 format_type t = FORMAT_LEFT;
777 error("end of input while processing format");
778 free_input_entry_format_list(list);
790 t = FORMAT_ALPHABETIC;
817 case '-': // tbl also accepts this
823 t = FORMAT_DOUBLE_HLINE;
836 if (c == opt->tab_char)
838 error("unrecognised format '%1'", char(c));
839 free_input_entry_format_list(list);
850 list = new input_entry_format(t, list);
852 list->pre_vline = pre_vline;
869 w = w*10 + (c - '0');
871 } while (c != EOF && csdigit(c));
872 list->separation = w;
883 list->vertical_alignment = entry_modifier::BOTTOM;
889 // 'e' and 'x' are mutually exclusive
896 } while (c == ' ' || c == '\t');
898 error("missing font name");
904 if (c == EOF || c == ' ' || c == '\t') {
905 error("missing ')'");
912 list->font += char(c);
920 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
921 list->font += char(c);
935 } while (c == ' ' || c == '\t');
937 error("missing macro name");
943 if (c == EOF || c == ' ' || c == '\t') {
944 error("missing ')'");
951 list->macro += char(c);
959 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
960 list->macro += char(c);
968 list->point_size.val = 0;
969 list->point_size.inc = 0;
970 if (c == '+' || c == '-') {
971 list->point_size.inc = (c == '+' ? 1 : -1);
974 if (c == EOF || !csdigit(c)) {
975 error("'p' modifier must be followed by number");
976 list->point_size.inc = 0;
980 list->point_size.val *= 10;
981 list->point_size.val += c - '0';
983 } while (c != EOF && csdigit(c));
985 if (list->point_size.val > MAX_POINT_SIZE
986 || list->point_size.val < -MAX_POINT_SIZE) {
987 error("unreasonable point size");
988 list->point_size.val = 0;
989 list->point_size.inc = 0;
995 list->vertical_alignment = entry_modifier::TOP;
1005 list->vertical_spacing.val = 0;
1006 list->vertical_spacing.inc = 0;
1007 if (c == '+' || c == '-') {
1008 list->vertical_spacing.inc = (c == '+' ? 1 : -1);
1011 if (c == EOF || !csdigit(c)) {
1012 error("'v' modifier must be followed by number");
1013 list->vertical_spacing.inc = 0;
1017 list->vertical_spacing.val *= 10;
1018 list->vertical_spacing.val += c - '0';
1020 } while (c != EOF && csdigit(c));
1022 if (list->vertical_spacing.val > MAX_VERTICAL_SPACING
1023 || list->vertical_spacing.val < -MAX_VERTICAL_SPACING) {
1024 error("unreasonable vertical spacing");
1025 list->vertical_spacing.val = 0;
1026 list->vertical_spacing.inc = 0;
1032 while (c == ' ' || c == '\t')
1038 if (c == EOF || c == '\n') {
1039 error("missing ')'");
1040 free_input_entry_format_list(list);
1049 if (c == '+' || c == '-') {
1050 list->width = char(c);
1055 if (c == EOF || !csdigit(c))
1056 error("bad argument for 'w' modifier");
1059 list->width += char(c);
1061 } while (c != EOF && csdigit(c));
1064 // 'w' and 'x' are mutually exclusive
1071 // 'x' and 'e' are mutually exclusive
1073 // 'x' and 'w' are mutually exclusive
1079 list->zero_width = 1;
1090 if (c == opt->tab_char)
1097 if (list->vline > 2) {
1099 error("more than 2 vertical bars between key letters");
1101 if (c == '\n' || c == ',') {
1103 list->last_column = 1;
1109 } while (c == ' ' || c == '\t');
1111 error("'.' not last character on line");
1112 free_input_entry_format_list(list);
1118 free_input_entry_format_list(list);
1121 list->last_column = 1;
1122 // now reverse the list so that the first row is at the beginning
1123 input_entry_format *rev = 0;
1125 input_entry_format *tem = list->next;
1131 input_entry_format *tem;
1134 for (tem = list; tem; tem = tem->next)
1138 // compute number of columns and rows
1142 for (tem = list; tem; tem = tem->next) {
1143 if (tem->last_column) {
1144 if (col >= ncolumns)
1154 if (current_format) {
1155 if (ncolumns > current_format->ncolumns) {
1156 error("cannot increase the number of columns in a continued format");
1157 free_input_entry_format_list(list);
1165 f = new format(nrows, ncolumns);
1169 for (tem = list; tem; tem = tem->next) {
1170 f->entry[row][col] = *tem;
1171 if (col < ncolumns - 1) {
1172 // use the greatest separation
1173 if (tem->separation > f->separation[col]) {
1175 error("cannot change column separation in continued format");
1177 f->separation[col] = tem->separation;
1180 else if (tem->separation >= 0)
1181 error("column separation specified for last column");
1182 if (tem->equal && !f->equal[col]) {
1184 error("cannot change which columns are equal in continued format");
1188 if (tem->expand && !f->expand[col]) {
1190 error("cannot change which columns are expanded in continued format");
1196 if (!tem->width.empty()) {
1197 // use the last width
1198 if (!f->width[col].empty() && f->width[col] != tem->width)
1199 error("multiple widths for column %1", col + 1);
1200 f->width[col] = tem->width;
1202 if (tem->pre_vline) {
1204 f->vline[row][col] = tem->pre_vline;
1206 f->vline[row][col + 1] = tem->vline;
1207 if (tem->last_column) {
1214 free_input_entry_format_list(list);
1215 for (col = 0; col < ncolumns; col++) {
1216 entry_format *e = f->entry[f->nrows - 1] + col;
1217 if (e->type != FORMAT_HLINE
1218 && e->type != FORMAT_DOUBLE_HLINE
1219 && e->type != FORMAT_SPAN)
1222 if (col >= ncolumns) {
1223 error("last row of format is all lines");
1227 if (have_expand && (opt->flags & table::EXPAND)) {
1228 error("ignoring global 'expand' option because of 'x' specifiers");
1229 opt->flags &= ~table::EXPAND;
1234 table *process_data(table_input &in, format *f, options *opt)
1236 char tab_char = opt->tab_char;
1237 int ncolumns = f->ncolumns;
1238 int current_row = 0;
1239 int format_index = 0;
1241 enum { DATA_INPUT_LINE, TROFF_INPUT_LINE, SINGLE_HLINE, DOUBLE_HLINE } type;
1242 table *tbl = new table(ncolumns, opt->flags, opt->linesize,
1243 opt->decimal_point_char);
1244 if (opt->delim[0] != '\0')
1245 tbl->set_delim(opt->delim[0], opt->delim[1]);
1247 // first determine what type of line this is
1253 if (d != EOF && csdigit(d)) {
1255 type = DATA_INPUT_LINE;
1259 type = TROFF_INPUT_LINE;
1262 else if (c == '_' || c == '=') {
1266 type = SINGLE_HLINE;
1268 type = DOUBLE_HLINE;
1272 type = DATA_INPUT_LINE;
1276 type = DATA_INPUT_LINE;
1279 case DATA_INPUT_LINE:
1282 if (format_index >= f->nrows)
1283 format_index = f->nrows - 1;
1284 // A format row that is all lines doesn't use up a data line.
1285 while (format_index < f->nrows - 1) {
1287 for (cnt = 0; cnt < ncolumns; cnt++) {
1288 entry_format *e = f->entry[format_index] + cnt;
1289 if (e->type != FORMAT_HLINE
1290 && e->type != FORMAT_DOUBLE_HLINE
1291 // Unfortunately tbl treats a span as needing data.
1292 // && e->type != FORMAT_SPAN
1298 for (cnt = 0; cnt < ncolumns; cnt++)
1299 tbl->add_entry(current_row, cnt, input_entry,
1300 f->entry[format_index] + cnt, current_filename,
1302 tbl->add_vlines(current_row, f->vline[format_index]);
1306 entry_format *line_format = f->entry[format_index];
1308 int row_comment = 0;
1310 if (c == tab_char || c == '\n') {
1311 int ln = current_lineno;
1314 if ((opt->flags & table::NOSPACES))
1315 input_entry.remove_spaces();
1316 while (col < ncolumns
1317 && line_format[col].type == FORMAT_SPAN) {
1318 tbl->add_entry(current_row, col, "", &line_format[col],
1319 current_filename, ln);
1322 if (c == '\n' && input_entry.length() == 2
1323 && input_entry[0] == 'T' && input_entry[1] == '{') {
1327 START, MIDDLE, GOT_T, GOT_RIGHT_BRACE, GOT_DOT,
1330 while (state != END) {
1348 state = GOT_RIGHT_BRACE;
1352 state = c == '\n' ? START : MIDDLE;
1361 state = c == '\n' ? START : MIDDLE;
1368 input_entry += ".l";
1370 state = c == '\n' ? START : MIDDLE;
1374 if (c == ' ' || c == '\n' || compatible_flag) {
1376 input_entry += ".lf";
1384 interpret_lf_args(args.contents());
1386 args.set_length(args.length() - 1);
1387 input_entry += args;
1391 input_entry += ".lf";
1396 case GOT_RIGHT_BRACE:
1397 if ((opt->flags & table::NOSPACES)) {
1403 if (c == '\n' || c == tab_char)
1423 error("end of data in middle of text block");
1428 if (col >= ncolumns) {
1429 if (!input_entry.empty()) {
1430 if (input_entry.length() >= 2
1431 && input_entry[0] == '\\'
1432 && input_entry[1] == '"')
1434 else if (!row_comment) {
1437 input_entry += '\0';
1438 error("excess data entry '%1' discarded",
1439 input_entry.contents());
1446 tbl->add_entry(current_row, col, input_entry,
1447 &line_format[col], current_filename, ln);
1462 for (; col < ncolumns; col++)
1463 tbl->add_entry(current_row, col, input_entry, &line_format[col],
1464 current_filename, current_lineno - 1);
1465 tbl->add_vlines(current_row, f->vline[format_index]);
1470 case TROFF_INPUT_LINE:
1473 int ln = current_lineno;
1483 tbl->add_text_line(current_row, line, current_filename, ln);
1484 if (line.length() >= 4
1485 && line[0] == '.' && line[1] == 'T' && line[2] == '&') {
1486 format *newf = process_format(in, opt, f);
1492 if (line.length() >= 3
1493 && line[0] == '.' && line[1] == 'l' && line[2] == 'f') {
1495 interpret_lf_args(line.contents() + 3);
1500 tbl->add_single_hline(current_row);
1503 tbl->add_double_hline(current_row);
1511 if (!give_up && current_row == 0) {
1512 error("no real data");
1519 // Do this here rather than at the beginning in case continued formats
1522 for (i = 0; i < ncolumns - 1; i++)
1523 if (f->separation[i] >= 0)
1524 tbl->set_column_separation(i, f->separation[i]);
1525 for (i = 0; i < ncolumns; i++)
1526 if (!f->width[i].empty())
1527 tbl->set_minimum_width(i, f->width[i]);
1528 for (i = 0; i < ncolumns; i++)
1530 tbl->set_equal_column(i);
1531 for (i = 0; i < ncolumns; i++)
1533 tbl->set_expand_column(i);
1537 void process_table(table_input &in)
1542 if ((opt = process_options(in)) != 0
1543 && (form = process_format(in, opt)) != 0
1544 && (tbl = process_data(in, form, opt)) != 0) {
1549 error("giving up on this table");
1550 while (in.get() != EOF)
1556 error("premature end of file");
1559 static void usage(FILE *stream)
1561 fprintf(stream, "usage: %s [ -vC ] [ files... ]\n", program_name);
1564 int main(int argc, char **argv)
1566 program_name = argv[0];
1567 static char stderr_buf[BUFSIZ];
1568 setbuf(stderr, stderr_buf);
1570 static const struct option long_options[] = {
1571 { "help", no_argument, 0, CHAR_MAX + 1 },
1572 { "version", no_argument, 0, 'v' },
1575 while ((opt = getopt_long(argc, argv, "vCT:", long_options, NULL)) != EOF)
1578 compatible_flag = 1;
1582 printf("GNU tbl (groff) version %s\n", Version_string);
1587 // I'm sick of getting bug reports from IRIX users
1589 case CHAR_MAX + 1: // --help
1600 printf(".if !\\n(.g .ab GNU tbl requires GNU troff.\n"
1602 ".if !dTE .ds TE\n");
1603 if (argc > optind) {
1604 for (int i = optind; i < argc; i++)
1605 if (argv[i][0] == '-' && argv[i][1] == '\0') {
1606 current_filename = "-";
1608 printf(".lf 1 -\n");
1609 process_input_file(stdin);
1613 FILE *fp = fopen(argv[i], "r");
1615 fatal("can't open '%1': %2", argv[i], strerror(errno));
1620 normalize_for_lf(fn);
1621 current_filename = fn.contents();
1622 printf(".lf 1 %s\n", current_filename);
1623 process_input_file(fp);
1628 current_filename = "-";
1630 printf(".lf 1 -\n");
1631 process_input_file(stdin);
1633 if (ferror(stdout) || fflush(stdout) < 0)
1634 fatal("output error");