2 /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002
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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
24 #define MAX_POINT_SIZE 99
25 #define MAX_VERTICAL_SPACING 72
27 extern "C" const char *Version_string;
29 static int compatible_flag = 0;
33 enum { START, MIDDLE, REREAD_T, REREAD_TE, REREAD_E, END, ERROR } state;
38 int ended() { return unget_stack.empty() && state == END; }
42 table_input::table_input(FILE *p)
47 void table_input::unget(char c)
55 int table_input::get()
57 int len = unget_stack.length();
59 unsigned char c = unget_stack[len - 1];
60 unget_stack.set_length(len - 1);
69 if ((c = getc(fp)) == '.') {
70 if ((c = getc(fp)) == 'T') {
71 if ((c = getc(fp)) == 'E') {
72 if (compatible_flag) {
80 if (c == EOF || c == ' ' || c == '\n') {
112 error("invalid input character code 0");
120 // handle line continuation
121 if ((c = getc(fp)) == '\\') {
124 c = getc(fp); // perhaps state ought to be START now
140 else if (c == '\0') {
141 error("invalid input character code 0");
162 void process_input_file(FILE *);
163 void process_table(table_input &in);
165 void process_input_file(FILE *fp)
167 enum { START, MIDDLE, HAD_DOT, HAD_T, HAD_TS, HAD_l, HAD_lf } state;
170 while ((c = getc(fp)) != EOF)
222 if (c == ' ' || c == '\n' || compatible_flag) {
228 error("end of file at beginning of table");
237 table_input input(fp);
238 process_table(input);
239 set_troff_location(current_filename, current_lineno);
241 fputs(".TE", stdout);
242 while ((c = getc(fp)) != '\n') {
256 fputs(".TS", stdout);
277 if (c == ' ' || c == '\n' || compatible_flag) {
288 interpret_lf_args(line.contents());
289 printf(".lf%s", line.contents());
293 fputs(".lf", stdout);
308 fputs(".\n", stdout);
311 fputs(".l\n", stdout);
314 fputs(".T\n", stdout);
317 fputs(".lf\n", stdout);
320 fputs(".TS\n", stdout);
332 char decimal_point_char;
338 : flags(0), linesize(0), tab_char('\t'), decimal_point_char('.')
340 delim[0] = delim[1] = '\0';
343 // Return non-zero if p and q are the same ignoring case.
345 int strieq(const char *p, const char *q)
347 for (; cmlower(*p) == cmlower(*q); p++, q++)
353 // return 0 if we should give up in this table
355 options *process_options(table_input &in)
357 options *opt = new options;
363 int i = line.length();
370 int i = line.length();
379 else if (c == ';' && level == 0) {
389 while (!csalpha(*p) && *p != '\0')
397 if (*q != '(' && *q != '\0')
404 while (*q != ')' && *q != '\0')
407 error("missing `)'");
413 error("argument without option");
415 else if (strieq(p, "tab")) {
417 error("`tab' option requires argument in parentheses");
419 if (arg[0] == '\0' || arg[1] != '\0')
420 error("argument to `tab' option must be a single character");
422 opt->tab_char = arg[0];
425 else if (strieq(p, "linesize")) {
427 error("`linesize' option requires argument in parentheses");
429 if (sscanf(arg, "%d", &opt->linesize) != 1)
430 error("bad linesize `%s'", arg);
431 else if (opt->linesize <= 0) {
432 error("linesize must be positive");
437 else if (strieq(p, "delim")) {
439 error("`delim' option requires argument in parentheses");
440 else if (arg[0] == '\0' || arg[1] == '\0' || arg[2] != '\0')
441 error("argument to `delim' option must be two characters");
443 opt->delim[0] = arg[0];
444 opt->delim[1] = arg[1];
447 else if (strieq(p, "center") || strieq(p, "centre")) {
449 error("`center' option does not take an argument");
450 opt->flags |= table::CENTER;
452 else if (strieq(p, "expand")) {
454 error("`expand' option does not take an argument");
455 opt->flags |= table::EXPAND;
457 else if (strieq(p, "box") || strieq(p, "frame")) {
459 error("`box' option does not take an argument");
460 opt->flags |= table::BOX;
462 else if (strieq(p, "doublebox") || strieq(p, "doubleframe")) {
464 error("`doublebox' option does not take an argument");
465 opt->flags |= table::DOUBLEBOX;
467 else if (strieq(p, "allbox")) {
469 error("`allbox' option does not take an argument");
470 opt->flags |= table::ALLBOX;
472 else if (strieq(p, "nokeep")) {
474 error("`nokeep' option does not take an argument");
475 opt->flags |= table::NOKEEP;
477 else if (strieq(p, "nospaces")) {
479 error("`nospaces' option does not take an argument");
480 opt->flags |= table::NOSPACES;
482 else if (strieq(p, "decimalpoint")) {
484 error("`decimalpoint' option requires argument in parentheses");
486 if (arg[0] == '\0' || arg[1] != '\0')
487 error("argument to `decimalpoint' option must be a single character");
489 opt->decimal_point_char = arg[0];
493 error("unrecognised global option `%1'", p);
502 entry_modifier::entry_modifier()
503 : vertical_alignment(CENTER), zero_width(0), stagger(0)
505 vertical_spacing.inc = vertical_spacing.val = 0;
506 point_size.inc = point_size.val = 0;
509 entry_modifier::~entry_modifier()
513 entry_format::entry_format() : type(FORMAT_LEFT)
517 entry_format::entry_format(format_type t) : type(t)
521 void entry_format::debug_print() const
536 case FORMAT_ALPHABETIC:
548 case FORMAT_DOUBLE_HLINE:
555 if (point_size.val != 0) {
557 if (point_size.inc > 0)
559 else if (point_size.inc < 0)
561 fprintf(stderr, "%d ", point_size.val);
563 if (vertical_spacing.val != 0) {
565 if (vertical_spacing.inc > 0)
567 else if (vertical_spacing.inc < 0)
569 fprintf(stderr, "%d ", vertical_spacing.val);
573 put_string(font, stderr);
576 switch (vertical_alignment) {
577 case entry_modifier::CENTER:
579 case entry_modifier::TOP:
582 case entry_modifier::BOTTOM:
598 entry_format **entry;
601 format(int nr, int nc);
603 void add_rows(int n);
606 format::format(int nr, int nc) : nrows(nr), ncolumns(nc)
609 separation = ncolumns > 1 ? new int[ncolumns - 1] : 0;
610 for (i = 0; i < ncolumns-1; i++)
612 width = new string[ncolumns];
613 equal = new char[ncolumns];
614 for (i = 0; i < ncolumns; i++)
616 entry = new entry_format *[nrows];
617 for (i = 0; i < nrows; i++)
618 entry[i] = new entry_format[ncolumns];
619 vline = new char*[nrows];
620 for (i = 0; i < nrows; i++) {
621 vline[i] = new char[ncolumns+1];
622 for (int j = 0; j < ncolumns+1; j++)
627 void format::add_rows(int n)
630 char **old_vline = vline;
631 vline = new char*[nrows + n];
632 for (i = 0; i < nrows; i++)
633 vline[i] = old_vline[i];
635 for (i = 0; i < n; i++) {
636 vline[nrows + i] = new char[ncolumns + 1];
637 for (int j = 0; j < ncolumns + 1; j++)
638 vline[nrows + i][j] = 0;
640 entry_format **old_entry = entry;
641 entry = new entry_format *[nrows + n];
642 for (i = 0; i < nrows; i++)
643 entry[i] = old_entry[i];
645 for (i = 0; i < n; i++)
646 entry[nrows + i] = new entry_format[ncolumns];
653 ad_delete(ncolumns) width;
655 for (int i = 0; i < nrows; i++) {
657 ad_delete(ncolumns) entry[i];
663 struct input_entry_format : public entry_format {
664 input_entry_format *next;
671 input_entry_format(format_type, input_entry_format * = 0);
672 ~input_entry_format();
676 input_entry_format::input_entry_format(format_type t, input_entry_format *p)
677 : entry_format(t), next(p)
686 input_entry_format::~input_entry_format()
690 void free_input_entry_format_list(input_entry_format *list)
693 input_entry_format *tem = list;
699 void input_entry_format::debug_print()
702 for (i = 0; i < pre_vline; i++)
704 entry_format::debug_print();
705 if (!width.empty()) {
708 put_string(width, stderr);
714 fprintf(stderr, "%d", separation);
715 for (i = 0; i < vline; i++)
721 // Return zero if we should give up on this table.
722 // If this is a continuation format line, current_format will be the current
725 format *process_format(table_input &in, options *opt,
726 format *current_format = 0)
728 input_entry_format *list = 0;
734 format_type t = FORMAT_LEFT;
737 error("end of input while processing format");
738 free_input_entry_format_list(list);
750 t = FORMAT_ALPHABETIC;
777 case '-': // tbl also accepts this
783 t = FORMAT_DOUBLE_HLINE;
796 if (c == opt->tab_char)
798 error("unrecognised format `%1'", char(c));
799 free_input_entry_format_list(list);
810 list = new input_entry_format(t, list);
812 list->pre_vline = pre_vline;
819 list->vertical_alignment = entry_modifier::TOP;
824 list->vertical_alignment = entry_modifier::BOTTOM;
834 list->zero_width = 1;
849 w = w*10 + (c - '0');
851 } while (c != EOF && csdigit(c));
852 list->separation = w;
859 } while (c == ' ' || c == '\t');
861 error("missing font name");
867 if (c == EOF || c == ' ' || c == '\t') {
868 error("missing `)'");
875 list->font += char(c);
883 && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
884 list->font += char(c);
892 list->vertical_spacing.val = 0;
893 list->vertical_spacing.inc = 0;
894 if (c == '+' || c == '-') {
895 list->vertical_spacing.inc = (c == '+' ? 1 : -1);
898 if (c == EOF || !csdigit(c)) {
899 error("`v' modifier must be followed by number");
900 list->vertical_spacing.inc = 0;
904 list->vertical_spacing.val *= 10;
905 list->vertical_spacing.val += c - '0';
907 } while (c != EOF && csdigit(c));
909 if (list->vertical_spacing.val > MAX_VERTICAL_SPACING
910 || list->vertical_spacing.val < -MAX_VERTICAL_SPACING) {
911 error("unreasonable point size");
912 list->vertical_spacing.val = 0;
913 list->vertical_spacing.inc = 0;
919 list->point_size.val = 0;
920 list->point_size.inc = 0;
921 if (c == '+' || c == '-') {
922 list->point_size.inc = (c == '+' ? 1 : -1);
925 if (c == EOF || !csdigit(c)) {
926 error("`p' modifier must be followed by number");
927 list->point_size.inc = 0;
931 list->point_size.val *= 10;
932 list->point_size.val += c - '0';
934 } while (c != EOF && csdigit(c));
936 if (list->point_size.val > MAX_POINT_SIZE
937 || list->point_size.val < -MAX_POINT_SIZE) {
938 error("unreasonable point size");
939 list->point_size.val = 0;
940 list->point_size.inc = 0;
946 while (c == ' ' || c == '\t')
952 if (c == EOF || c == '\n') {
953 error("missing `)'");
954 free_input_entry_format_list(list);
963 if (c == '+' || c == '-') {
964 list->width = char(c);
969 if (c == EOF || !csdigit(c))
970 error("bad argument for `w' modifier");
973 list->width += char(c);
975 } while (c != EOF && csdigit(c));
1003 if (c == opt->tab_char)
1010 if (list->vline > 2) {
1012 error("more than 2 vertical bars between key letters");
1014 if (c == '\n' || c == ',') {
1016 list->last_column = 1;
1022 } while (c == ' ' || c == '\t');
1024 error("`.' not last character on line");
1025 free_input_entry_format_list(list);
1031 free_input_entry_format_list(list);
1034 list->last_column = 1;
1035 // now reverse the list so that the first row is at the beginning
1036 input_entry_format *rev = 0;
1038 input_entry_format *tem = list->next;
1044 input_entry_format *tem;
1047 for (tem = list; tem; tem = tem->next)
1051 // compute number of columns and rows
1055 for (tem = list; tem; tem = tem->next) {
1056 if (tem->last_column) {
1057 if (col >= ncolumns)
1067 if (current_format) {
1068 if (ncolumns > current_format->ncolumns) {
1069 error("cannot increase the number of columns in a continued format");
1070 free_input_entry_format_list(list);
1078 f = new format(nrows, ncolumns);
1082 for (tem = list; tem; tem = tem->next) {
1083 f->entry[row][col] = *tem;
1084 if (col < ncolumns-1) {
1085 // use the greatest separation
1086 if (tem->separation > f->separation[col]) {
1088 error("cannot change column separation in continued format");
1090 f->separation[col] = tem->separation;
1093 else if (tem->separation >= 0)
1094 error("column separation specified for last column");
1095 if (tem->equal && !f->equal[col]) {
1097 error("cannot change which columns are equal in continued format");
1101 if (!tem->width.empty()) {
1102 // use the last width
1103 if (!f->width[col].empty() && f->width[col] != tem->width)
1104 error("multiple widths for column %1", col+1);
1105 f->width[col] = tem->width;
1107 if (tem->pre_vline) {
1109 f->vline[row][col] = tem->pre_vline;
1111 f->vline[row][col+1] = tem->vline;
1112 if (tem->last_column) {
1119 free_input_entry_format_list(list);
1120 for (col = 0; col < ncolumns; col++) {
1121 entry_format *e = f->entry[f->nrows-1] + col;
1122 if (e->type != FORMAT_HLINE
1123 && e->type != FORMAT_DOUBLE_HLINE
1124 && e->type != FORMAT_SPAN)
1127 if (col >= ncolumns) {
1128 error("last row of format is all lines");
1135 table *process_data(table_input &in, format *f, options *opt)
1137 char tab_char = opt->tab_char;
1138 int ncolumns = f->ncolumns;
1139 int current_row = 0;
1140 int format_index = 0;
1142 enum { DATA_INPUT_LINE, TROFF_INPUT_LINE, SINGLE_HLINE, DOUBLE_HLINE } type;
1143 table *tbl = new table(ncolumns, opt->flags, opt->linesize,
1144 opt->decimal_point_char);
1145 if (opt->delim[0] != '\0')
1146 tbl->set_delim(opt->delim[0], opt->delim[1]);
1148 // first determine what type of line this is
1154 if (d != EOF && csdigit(d)) {
1156 type = DATA_INPUT_LINE;
1160 type = TROFF_INPUT_LINE;
1163 else if (c == '_' || c == '=') {
1167 type = SINGLE_HLINE;
1169 type = DOUBLE_HLINE;
1173 type = DATA_INPUT_LINE;
1177 type = DATA_INPUT_LINE;
1180 case DATA_INPUT_LINE:
1183 if (format_index >= f->nrows)
1184 format_index = f->nrows - 1;
1185 // A format row that is all lines doesn't use up a data line.
1186 while (format_index < f->nrows - 1) {
1188 for (c = 0; c < ncolumns; c++) {
1189 entry_format *e = f->entry[format_index] + c;
1190 if (e->type != FORMAT_HLINE
1191 && e->type != FORMAT_DOUBLE_HLINE
1192 // Unfortunately tbl treats a span as needing data.
1193 // && e->type != FORMAT_SPAN
1199 for (c = 0; c < ncolumns; c++)
1200 tbl->add_entry(current_row, c, input_entry,
1201 f->entry[format_index] + c, current_filename,
1203 tbl->add_vlines(current_row, f->vline[format_index]);
1207 entry_format *line_format = f->entry[format_index];
1209 int row_comment = 0;
1211 if (c == tab_char || c == '\n') {
1212 int ln = current_lineno;
1215 if ((opt->flags & table::NOSPACES))
1216 input_entry.remove_spaces();
1217 while (col < ncolumns
1218 && line_format[col].type == FORMAT_SPAN) {
1219 tbl->add_entry(current_row, col, "", &line_format[col],
1220 current_filename, ln);
1223 if (c == '\n' && input_entry.length() == 2
1224 && input_entry[0] == 'T' && input_entry[1] == '{') {
1228 START, MIDDLE, GOT_T, GOT_RIGHT_BRACE, GOT_DOT,
1231 while (state != END) {
1249 state = GOT_RIGHT_BRACE;
1253 state = c == '\n' ? START : MIDDLE;
1262 state = c == '\n' ? START : MIDDLE;
1269 input_entry += ".l";
1271 state = c == '\n' ? START : MIDDLE;
1275 if (c == ' ' || c == '\n' || compatible_flag) {
1277 input_entry += ".lf";
1285 interpret_lf_args(args.contents());
1287 args.set_length(args.length() - 1);
1288 input_entry += args;
1292 input_entry += ".lf";
1297 case GOT_RIGHT_BRACE:
1298 if (c == '\n' || c == tab_char)
1304 state = c == '\n' ? START : MIDDLE;
1318 error("end of data in middle of text block");
1323 if (col >= ncolumns) {
1324 if (!input_entry.empty()) {
1325 if (input_entry.length() >= 2
1326 && input_entry[0] == '\\'
1327 && input_entry[1] == '"')
1329 else if (!row_comment) {
1332 input_entry += '\0';
1333 error("excess data entry `%1' discarded",
1334 input_entry.contents());
1341 tbl->add_entry(current_row, col, input_entry,
1342 &line_format[col], current_filename, ln);
1357 for (; col < ncolumns; col++)
1358 tbl->add_entry(current_row, col, input_entry, &line_format[col],
1359 current_filename, current_lineno - 1);
1360 tbl->add_vlines(current_row, f->vline[format_index]);
1365 case TROFF_INPUT_LINE:
1368 int ln = current_lineno;
1378 tbl->add_text_line(current_row, line, current_filename, ln);
1379 if (line.length() >= 4
1380 && line[0] == '.' && line[1] == 'T' && line[2] == '&') {
1381 format *newf = process_format(in, opt, f);
1387 if (line.length() >= 3
1388 && line[0] == '.' && line[1] == 'l' && line[2] == 'f') {
1390 interpret_lf_args(line.contents() + 3);
1395 tbl->add_single_hline(current_row);
1398 tbl->add_double_hline(current_row);
1406 if (!give_up && current_row == 0) {
1407 error("no real data");
1414 // Do this here rather than at the beginning in case continued formats
1417 for (i = 0; i < ncolumns - 1; i++)
1418 if (f->separation[i] >= 0)
1419 tbl->set_column_separation(i, f->separation[i]);
1420 for (i = 0; i < ncolumns; i++)
1421 if (!f->width[i].empty())
1422 tbl->set_minimum_width(i, f->width[i]);
1423 for (i = 0; i < ncolumns; i++)
1425 tbl->set_equal_column(i);
1429 void process_table(table_input &in)
1435 if ((opt = process_options(in)) != 0
1436 && (form = process_format(in, opt)) != 0
1437 && (tbl = process_data(in, form, opt)) != 0) {
1442 error("giving up on this table");
1443 while ((c = in.get()) != EOF)
1449 error("premature end of file");
1452 static void usage(FILE *stream)
1454 fprintf(stream, "usage: %s [ -vC ] [ files... ]\n", program_name);
1457 int main(int argc, char **argv)
1459 program_name = argv[0];
1460 static char stderr_buf[BUFSIZ];
1461 setbuf(stderr, stderr_buf);
1463 static const struct option long_options[] = {
1464 { "help", no_argument, 0, CHAR_MAX + 1 },
1465 { "version", no_argument, 0, 'v' },
1468 while ((opt = getopt_long(argc, argv, "vCT:", long_options, NULL)) != EOF)
1471 compatible_flag = 1;
1475 printf("GNU tbl (groff) version %s\n", Version_string);
1480 // I'm sick of getting bug reports from IRIX users
1482 case CHAR_MAX + 1: // --help
1493 printf(".if !\\n(.g .ab GNU tbl requires GNU troff.\n"
1495 ".if !dTE .ds TE\n");
1496 if (argc > optind) {
1497 for (int i = optind; i < argc; i++)
1498 if (argv[i][0] == '-' && argv[i][1] == '\0') {
1499 current_filename = "-";
1501 printf(".lf 1 -\n");
1502 process_input_file(stdin);
1506 FILE *fp = fopen(argv[i], "r");
1508 current_lineno = -1;
1509 error("can't open `%1': %2", argv[i], strerror(errno));
1513 current_filename = argv[i];
1514 printf(".lf 1 %s\n", current_filename);
1515 process_input_file(fp);
1520 current_filename = "-";
1522 printf(".lf 1 -\n");
1523 process_input_file(stdin);
1525 if (ferror(stdout) || fflush(stdout) < 0)
1526 fatal("output error");