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];
512 error("unrecognised global option `%1'", p);
521 entry_modifier::entry_modifier()
522 : vertical_alignment(CENTER), zero_width(0), stagger(0)
524 vertical_spacing.inc = vertical_spacing.val = 0;
525 point_size.inc = point_size.val = 0;
528 entry_modifier::~entry_modifier()
532 entry_format::entry_format() : type(FORMAT_LEFT)
536 entry_format::entry_format(format_type t) : type(t)
540 void entry_format::debug_print() const
555 case FORMAT_ALPHABETIC:
567 case FORMAT_DOUBLE_HLINE:
574 if (point_size.val != 0) {
576 if (point_size.inc > 0)
578 else if (point_size.inc < 0)
580 fprintf(stderr, "%d ", point_size.val);
582 if (vertical_spacing.val != 0) {
584 if (vertical_spacing.inc > 0)
586 else if (vertical_spacing.inc < 0)
588 fprintf(stderr, "%d ", vertical_spacing.val);
592 put_string(font, stderr);
595 if (!macro.empty()) {
597 put_string(macro, stderr);
600 switch (vertical_alignment) {
601 case entry_modifier::CENTER:
603 case entry_modifier::TOP:
606 case entry_modifier::BOTTOM:
622 entry_format **entry;
625 format(int nr, int nc);
627 void add_rows(int n);
630 format::format(int nr, int nc) : nrows(nr), ncolumns(nc)
633 separation = ncolumns > 1 ? new int[ncolumns - 1] : 0;
634 for (i = 0; i < ncolumns-1; i++)
636 width = new string[ncolumns];
637 equal = new char[ncolumns];
638 for (i = 0; i < ncolumns; i++)
640 entry = new entry_format *[nrows];
641 for (i = 0; i < nrows; i++)
642 entry[i] = new entry_format[ncolumns];
643 vline = new char*[nrows];
644 for (i = 0; i < nrows; i++) {
645 vline[i] = new char[ncolumns+1];
646 for (int j = 0; j < ncolumns+1; j++)
651 void format::add_rows(int n)
654 char **old_vline = vline;
655 vline = new char*[nrows + n];
656 for (i = 0; i < nrows; i++)
657 vline[i] = old_vline[i];
659 for (i = 0; i < n; i++) {
660 vline[nrows + i] = new char[ncolumns + 1];
661 for (int j = 0; j < ncolumns + 1; j++)
662 vline[nrows + i][j] = 0;
664 entry_format **old_entry = entry;
665 entry = new entry_format *[nrows + n];
666 for (i = 0; i < nrows; i++)
667 entry[i] = old_entry[i];
669 for (i = 0; i < n; i++)
670 entry[nrows + i] = new entry_format[ncolumns];
677 ad_delete(ncolumns) width;
679 for (int i = 0; i < nrows; i++) {
681 ad_delete(ncolumns) entry[i];
687 struct input_entry_format : public entry_format {
688 input_entry_format *next;
695 input_entry_format(format_type, input_entry_format * = 0);
696 ~input_entry_format();
700 input_entry_format::input_entry_format(format_type t, input_entry_format *p)
701 : entry_format(t), next(p)
710 input_entry_format::~input_entry_format()
714 void free_input_entry_format_list(input_entry_format *list)
717 input_entry_format *tem = list;
723 void input_entry_format::debug_print()
726 for (i = 0; i < pre_vline; i++)
728 entry_format::debug_print();
729 if (!width.empty()) {
732 put_string(width, stderr);
738 fprintf(stderr, "%d", separation);
739 for (i = 0; i < vline; i++)
745 // Return zero if we should give up on this table.
746 // If this is a continuation format line, current_format will be the current
749 format *process_format(table_input &in, options *opt,
750 format *current_format = 0)
752 input_entry_format *list = 0;
758 format_type t = FORMAT_LEFT;
761 error("end of input while processing format");
762 free_input_entry_format_list(list);
774 t = FORMAT_ALPHABETIC;
801 case '-': // tbl also accepts this
807 t = FORMAT_DOUBLE_HLINE;
820 if (c == opt->tab_char)
822 error("unrecognised format `%1'", char(c));
823 free_input_entry_format_list(list);
834 list = new input_entry_format(t, list);
836 list->pre_vline = pre_vline;
843 list->vertical_alignment = entry_modifier::TOP;
848 list->vertical_alignment = entry_modifier::BOTTOM;
858 list->zero_width = 1;
873 w = w*10 + (c - '0');
875 } while (c != EOF && csdigit(c));
876 list->separation = w;
883 } while (c == ' ' || c == '\t');
885 error("missing font name");
891 if (c == EOF || c == ' ' || c == '\t') {
892 error("missing `)'");
899 list->font += char(c);
907 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
908 list->font += char(c);
917 } while (c == ' ' || c == '\t');
919 error("missing macro name");
925 if (c == EOF || c == ' ' || c == '\t') {
926 error("missing `)'");
933 list->macro += char(c);
941 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
942 list->macro += char(c);
950 list->vertical_spacing.val = 0;
951 list->vertical_spacing.inc = 0;
952 if (c == '+' || c == '-') {
953 list->vertical_spacing.inc = (c == '+' ? 1 : -1);
956 if (c == EOF || !csdigit(c)) {
957 error("`v' modifier must be followed by number");
958 list->vertical_spacing.inc = 0;
962 list->vertical_spacing.val *= 10;
963 list->vertical_spacing.val += c - '0';
965 } while (c != EOF && csdigit(c));
967 if (list->vertical_spacing.val > MAX_VERTICAL_SPACING
968 || list->vertical_spacing.val < -MAX_VERTICAL_SPACING) {
969 error("unreasonable vertical spacing");
970 list->vertical_spacing.val = 0;
971 list->vertical_spacing.inc = 0;
977 list->point_size.val = 0;
978 list->point_size.inc = 0;
979 if (c == '+' || c == '-') {
980 list->point_size.inc = (c == '+' ? 1 : -1);
983 if (c == EOF || !csdigit(c)) {
984 error("`p' modifier must be followed by number");
985 list->point_size.inc = 0;
989 list->point_size.val *= 10;
990 list->point_size.val += c - '0';
992 } while (c != EOF && csdigit(c));
994 if (list->point_size.val > MAX_POINT_SIZE
995 || list->point_size.val < -MAX_POINT_SIZE) {
996 error("unreasonable point size");
997 list->point_size.val = 0;
998 list->point_size.inc = 0;
1004 while (c == ' ' || c == '\t')
1010 if (c == EOF || c == '\n') {
1011 error("missing `)'");
1012 free_input_entry_format_list(list);
1021 if (c == '+' || c == '-') {
1022 list->width = char(c);
1027 if (c == EOF || !csdigit(c))
1028 error("bad argument for `w' modifier");
1031 list->width += char(c);
1033 } while (c != EOF && csdigit(c));
1061 if (c == opt->tab_char)
1068 if (list->vline > 2) {
1070 error("more than 2 vertical bars between key letters");
1072 if (c == '\n' || c == ',') {
1074 list->last_column = 1;
1080 } while (c == ' ' || c == '\t');
1082 error("`.' not last character on line");
1083 free_input_entry_format_list(list);
1089 free_input_entry_format_list(list);
1092 list->last_column = 1;
1093 // now reverse the list so that the first row is at the beginning
1094 input_entry_format *rev = 0;
1096 input_entry_format *tem = list->next;
1102 input_entry_format *tem;
1105 for (tem = list; tem; tem = tem->next)
1109 // compute number of columns and rows
1113 for (tem = list; tem; tem = tem->next) {
1114 if (tem->last_column) {
1115 if (col >= ncolumns)
1125 if (current_format) {
1126 if (ncolumns > current_format->ncolumns) {
1127 error("cannot increase the number of columns in a continued format");
1128 free_input_entry_format_list(list);
1136 f = new format(nrows, ncolumns);
1140 for (tem = list; tem; tem = tem->next) {
1141 f->entry[row][col] = *tem;
1142 if (col < ncolumns-1) {
1143 // use the greatest separation
1144 if (tem->separation > f->separation[col]) {
1146 error("cannot change column separation in continued format");
1148 f->separation[col] = tem->separation;
1151 else if (tem->separation >= 0)
1152 error("column separation specified for last column");
1153 if (tem->equal && !f->equal[col]) {
1155 error("cannot change which columns are equal in continued format");
1159 if (!tem->width.empty()) {
1160 // use the last width
1161 if (!f->width[col].empty() && f->width[col] != tem->width)
1162 error("multiple widths for column %1", col+1);
1163 f->width[col] = tem->width;
1165 if (tem->pre_vline) {
1167 f->vline[row][col] = tem->pre_vline;
1169 f->vline[row][col+1] = tem->vline;
1170 if (tem->last_column) {
1177 free_input_entry_format_list(list);
1178 for (col = 0; col < ncolumns; col++) {
1179 entry_format *e = f->entry[f->nrows-1] + col;
1180 if (e->type != FORMAT_HLINE
1181 && e->type != FORMAT_DOUBLE_HLINE
1182 && e->type != FORMAT_SPAN)
1185 if (col >= ncolumns) {
1186 error("last row of format is all lines");
1193 table *process_data(table_input &in, format *f, options *opt)
1195 char tab_char = opt->tab_char;
1196 int ncolumns = f->ncolumns;
1197 int current_row = 0;
1198 int format_index = 0;
1200 enum { DATA_INPUT_LINE, TROFF_INPUT_LINE, SINGLE_HLINE, DOUBLE_HLINE } type;
1201 table *tbl = new table(ncolumns, opt->flags, opt->linesize,
1202 opt->decimal_point_char);
1203 if (opt->delim[0] != '\0')
1204 tbl->set_delim(opt->delim[0], opt->delim[1]);
1206 // first determine what type of line this is
1212 if (d != EOF && csdigit(d)) {
1214 type = DATA_INPUT_LINE;
1218 type = TROFF_INPUT_LINE;
1221 else if (c == '_' || c == '=') {
1225 type = SINGLE_HLINE;
1227 type = DOUBLE_HLINE;
1231 type = DATA_INPUT_LINE;
1235 type = DATA_INPUT_LINE;
1238 case DATA_INPUT_LINE:
1241 if (format_index >= f->nrows)
1242 format_index = f->nrows - 1;
1243 // A format row that is all lines doesn't use up a data line.
1244 while (format_index < f->nrows - 1) {
1246 for (cnt = 0; cnt < ncolumns; cnt++) {
1247 entry_format *e = f->entry[format_index] + cnt;
1248 if (e->type != FORMAT_HLINE
1249 && e->type != FORMAT_DOUBLE_HLINE
1250 // Unfortunately tbl treats a span as needing data.
1251 // && e->type != FORMAT_SPAN
1257 for (cnt = 0; cnt < ncolumns; cnt++)
1258 tbl->add_entry(current_row, cnt, input_entry,
1259 f->entry[format_index] + cnt, current_filename,
1261 tbl->add_vlines(current_row, f->vline[format_index]);
1265 entry_format *line_format = f->entry[format_index];
1267 int row_comment = 0;
1269 if (c == tab_char || c == '\n') {
1270 int ln = current_lineno;
1273 if ((opt->flags & table::NOSPACES))
1274 input_entry.remove_spaces();
1275 while (col < ncolumns
1276 && line_format[col].type == FORMAT_SPAN) {
1277 tbl->add_entry(current_row, col, "", &line_format[col],
1278 current_filename, ln);
1281 if (c == '\n' && input_entry.length() == 2
1282 && input_entry[0] == 'T' && input_entry[1] == '{') {
1286 START, MIDDLE, GOT_T, GOT_RIGHT_BRACE, GOT_DOT,
1289 while (state != END) {
1307 state = GOT_RIGHT_BRACE;
1311 state = c == '\n' ? START : MIDDLE;
1320 state = c == '\n' ? START : MIDDLE;
1327 input_entry += ".l";
1329 state = c == '\n' ? START : MIDDLE;
1333 if (c == ' ' || c == '\n' || compatible_flag) {
1335 input_entry += ".lf";
1343 interpret_lf_args(args.contents());
1345 args.set_length(args.length() - 1);
1346 input_entry += args;
1350 input_entry += ".lf";
1355 case GOT_RIGHT_BRACE:
1356 if ((opt->flags & table::NOSPACES)) {
1362 if (c == '\n' || c == tab_char)
1382 error("end of data in middle of text block");
1387 if (col >= ncolumns) {
1388 if (!input_entry.empty()) {
1389 if (input_entry.length() >= 2
1390 && input_entry[0] == '\\'
1391 && input_entry[1] == '"')
1393 else if (!row_comment) {
1396 input_entry += '\0';
1397 error("excess data entry `%1' discarded",
1398 input_entry.contents());
1405 tbl->add_entry(current_row, col, input_entry,
1406 &line_format[col], current_filename, ln);
1421 for (; col < ncolumns; col++)
1422 tbl->add_entry(current_row, col, input_entry, &line_format[col],
1423 current_filename, current_lineno - 1);
1424 tbl->add_vlines(current_row, f->vline[format_index]);
1429 case TROFF_INPUT_LINE:
1432 int ln = current_lineno;
1442 tbl->add_text_line(current_row, line, current_filename, ln);
1443 if (line.length() >= 4
1444 && line[0] == '.' && line[1] == 'T' && line[2] == '&') {
1445 format *newf = process_format(in, opt, f);
1451 if (line.length() >= 3
1452 && line[0] == '.' && line[1] == 'l' && line[2] == 'f') {
1454 interpret_lf_args(line.contents() + 3);
1459 tbl->add_single_hline(current_row);
1462 tbl->add_double_hline(current_row);
1470 if (!give_up && current_row == 0) {
1471 error("no real data");
1478 // Do this here rather than at the beginning in case continued formats
1481 for (i = 0; i < ncolumns - 1; i++)
1482 if (f->separation[i] >= 0)
1483 tbl->set_column_separation(i, f->separation[i]);
1484 for (i = 0; i < ncolumns; i++)
1485 if (!f->width[i].empty())
1486 tbl->set_minimum_width(i, f->width[i]);
1487 for (i = 0; i < ncolumns; i++)
1489 tbl->set_equal_column(i);
1493 void process_table(table_input &in)
1498 if ((opt = process_options(in)) != 0
1499 && (form = process_format(in, opt)) != 0
1500 && (tbl = process_data(in, form, opt)) != 0) {
1505 error("giving up on this table");
1506 while (in.get() != EOF)
1512 error("premature end of file");
1515 static void usage(FILE *stream)
1517 fprintf(stream, "usage: %s [ -vC ] [ files... ]\n", program_name);
1520 int main(int argc, char **argv)
1522 program_name = argv[0];
1523 static char stderr_buf[BUFSIZ];
1524 setbuf(stderr, stderr_buf);
1526 static const struct option long_options[] = {
1527 { "help", no_argument, 0, CHAR_MAX + 1 },
1528 { "version", no_argument, 0, 'v' },
1531 while ((opt = getopt_long(argc, argv, "vCT:", long_options, NULL)) != EOF)
1534 compatible_flag = 1;
1538 printf("GNU tbl (groff) version %s\n", Version_string);
1543 // I'm sick of getting bug reports from IRIX users
1545 case CHAR_MAX + 1: // --help
1556 printf(".if !\\n(.g .ab GNU tbl requires GNU troff.\n"
1558 ".if !dTE .ds TE\n");
1559 if (argc > optind) {
1560 for (int i = optind; i < argc; i++)
1561 if (argv[i][0] == '-' && argv[i][1] == '\0') {
1562 current_filename = "-";
1564 printf(".lf 1 -\n");
1565 process_input_file(stdin);
1569 FILE *fp = fopen(argv[i], "r");
1571 fatal("can't open `%1': %2", argv[i], strerror(errno));
1574 current_filename = argv[i];
1575 printf(".lf 1 %s\n", current_filename);
1576 process_input_file(fp);
1581 current_filename = "-";
1583 printf(".lf 1 -\n");
1584 process_input_file(stdin);
1586 if (ferror(stdout) || fflush(stdout) < 0)
1587 fatal("output error");