summaryrefslogtreecommitdiff
path: root/tbl/main.cc
diff options
context:
space:
mode:
Diffstat (limited to 'tbl/main.cc')
-rw-r--r--tbl/main.cc1480
1 files changed, 1480 insertions, 0 deletions
diff --git a/tbl/main.cc b/tbl/main.cc
new file mode 100644
index 000000000..1fb1011c4
--- /dev/null
+++ b/tbl/main.cc
@@ -0,0 +1,1480 @@
+// -*- C++ -*-
+/* Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 2, or (at your option) any later
+version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License along
+with groff; see the file COPYING. If not, write to the Free Software
+Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include "table.h"
+
+#define MAX_POINT_SIZE 99
+#define MAX_VERTICAL_SPACING 72
+
+static int compatible_flag = 0;
+
+class table_input {
+ FILE *fp;
+ enum { START, MIDDLE, REREAD_T, REREAD_TE, REREAD_E, END, ERROR } state;
+ string unget_stack;
+public:
+ table_input(FILE *);
+ int get();
+ int ended() { return unget_stack.empty() && state == END; }
+ void unget(char);
+};
+
+table_input::table_input(FILE *p)
+: fp(p), state(START)
+{
+}
+
+void table_input::unget(char c)
+{
+ assert(c != '\0');
+ unget_stack += c;
+ if (c == '\n')
+ current_lineno--;
+}
+
+int table_input::get()
+{
+ int len = unget_stack.length();
+ if (len != 0) {
+ unsigned char c = unget_stack[len - 1];
+ unget_stack.set_length(len - 1);
+ if (c == '\n')
+ current_lineno++;
+ return c;
+ }
+ int c;
+ for (;;) {
+ switch (state) {
+ case START:
+ if ((c = getc(fp)) == '.') {
+ if ((c = getc(fp)) == 'T') {
+ if ((c = getc(fp)) == 'E') {
+ if (compatible_flag) {
+ state = END;
+ return EOF;
+ }
+ else {
+ c = getc(fp);
+ if (c != EOF)
+ ungetc(c, fp);
+ if (c == EOF || c == ' ' || c == '\n') {
+ state = END;
+ return EOF;
+ }
+ state = REREAD_TE;
+ return '.';
+ }
+ }
+ else {
+ if (c != EOF)
+ ungetc(c, fp);
+ state = REREAD_T;
+ return '.';
+ }
+ }
+ else {
+ if (c != EOF)
+ ungetc(c, fp);
+ state = MIDDLE;
+ return '.';
+ }
+ }
+ else if (c == EOF) {
+ state = ERROR;
+ return EOF;
+ }
+ else {
+ if (c == '\n')
+ current_lineno++;
+ else {
+ state = MIDDLE;
+ if (c == '\0') {
+ error("illegal input character code 0");
+ break;
+ }
+ }
+ return c;
+ }
+ break;
+ case MIDDLE:
+ // handle line continuation
+ if ((c = getc(fp)) == '\\') {
+ c = getc(fp);
+ if (c == '\n')
+ c = getc(fp); // perhaps state ought to be START now
+ else {
+ if (c != EOF)
+ ungetc(c, fp);
+ c = '\\';
+ }
+ }
+ if (c == EOF) {
+ state = ERROR;
+ return EOF;
+ }
+ else {
+ if (c == '\n') {
+ state = START;
+ current_lineno++;
+ }
+ else if (c == '\0') {
+ error("illegal input character code 0");
+ break;
+ }
+ return c;
+ }
+ case REREAD_T:
+ state = MIDDLE;
+ return 'T';
+ case REREAD_TE:
+ state = REREAD_E;
+ return 'T';
+ case REREAD_E:
+ state = MIDDLE;
+ return 'E';
+ case END:
+ case ERROR:
+ return EOF;
+ }
+ }
+}
+
+void process_input_file(FILE *);
+void process_table(table_input &in);
+
+void process_input_file(FILE *fp)
+{
+ enum { START, MIDDLE, HAD_DOT, HAD_T, HAD_TS, HAD_l, HAD_lf } state;
+ state = START;
+ int c;
+ while ((c = getc(fp)) != EOF)
+ switch (state) {
+ case START:
+ if (c == '.')
+ state = HAD_DOT;
+ else {
+ if (c == '\n')
+ current_lineno++;
+ else
+ state = MIDDLE;
+ putchar(c);
+ }
+ break;
+ case MIDDLE:
+ if (c == '\n') {
+ current_lineno++;
+ state = START;
+ }
+ putchar(c);
+ break;
+ case HAD_DOT:
+ if (c == 'T')
+ state = HAD_T;
+ else if (c == 'l')
+ state = HAD_l;
+ else {
+ putchar('.');
+ putchar(c);
+ if (c == '\n') {
+ current_lineno++;
+ state = START;
+ }
+ else
+ state = MIDDLE;
+ }
+ break;
+ case HAD_T:
+ if (c == 'S')
+ state = HAD_TS;
+ else {
+ putchar('.');
+ putchar('T');
+ putchar(c);
+ if (c == '\n') {
+ current_lineno++;
+ state = START;
+ }
+ else
+ state = MIDDLE;
+ }
+ break;
+ case HAD_TS:
+ if (c == ' ' || c == '\n' || compatible_flag) {
+ putchar('.');
+ putchar('T');
+ putchar('S');
+ while (c != '\n') {
+ if (c == EOF) {
+ error("end of file at beginning of table");
+ return;
+ }
+ putchar(c);
+ c = getc(fp);
+ }
+ putchar('\n');
+ current_lineno++;
+ {
+ table_input input(fp);
+ process_table(input);
+ set_troff_location(current_filename, current_lineno);
+ if (input.ended()) {
+ fputs(".TE", stdout);
+ while ((c = getc(fp)) != '\n') {
+ if (c == EOF) {
+ putchar('\n');
+ return;
+ }
+ putchar(c);
+ }
+ putchar('\n');
+ current_lineno++;
+ }
+ }
+ state = START;
+ }
+ else {
+ fputs(".TS", stdout);
+ putchar(c);
+ state = MIDDLE;
+ }
+ break;
+ case HAD_l:
+ if (c == 'f')
+ state = HAD_lf;
+ else {
+ putchar('.');
+ putchar('l');
+ putchar(c);
+ if (c == '\n') {
+ current_lineno++;
+ state = START;
+ }
+ else
+ state = MIDDLE;
+ }
+ break;
+ case HAD_lf:
+ if (c == ' ' || c == '\n' || compatible_flag) {
+ string line;
+ while (c != EOF) {
+ line += c;
+ if (c == '\n') {
+ current_lineno++;
+ break;
+ }
+ c = getc(fp);
+ }
+ line += '\0';
+ interpret_lf_args(line.contents());
+ printf(".lf%s", line.contents());
+ state = START;
+ }
+ else {
+ fputs(".lf", stdout);
+ putchar(c);
+ state = MIDDLE;
+ }
+ break;
+ default:
+ assert(0);
+ }
+ switch(state) {
+ case START:
+ break;
+ case MIDDLE:
+ putchar('\n');
+ break;
+ case HAD_DOT:
+ fputs(".\n", stdout);
+ break;
+ case HAD_l:
+ fputs(".l\n", stdout);
+ break;
+ case HAD_T:
+ fputs(".T\n", stdout);
+ break;
+ case HAD_lf:
+ fputs(".lf\n", stdout);
+ break;
+ case HAD_TS:
+ fputs(".TS\n", stdout);
+ break;
+ }
+ if (fp != stdin)
+ fclose(fp);
+}
+
+struct options {
+ unsigned flags;
+ int linesize;
+ char delim[2];
+ char tab_char;
+
+ options();
+};
+
+options::options()
+: flags(0), tab_char('\t'), linesize(0)
+{
+ delim[0] = delim[1] = '\0';
+}
+
+// Return non-zero if p and q are the same ignoring case.
+
+int strieq(const char *p, const char *q)
+{
+ for (; cmlower(*p) == cmlower(*q); p++, q++)
+ if (*p == '\0')
+ return 1;
+ return 0;
+}
+
+// return 0 if we should give up in this table
+
+options *process_options(table_input &in)
+{
+ options *opt = new options;
+ string line;
+ int level = 0;
+ for (;;) {
+ int c = in.get();
+ if (c == EOF) {
+ int i = line.length();
+ while (--i >= 0)
+ in.unget(line[i]);
+ return opt;
+ }
+ if (c == '\n') {
+ in.unget(c);
+ int i = line.length();
+ while (--i >= 0)
+ in.unget(line[i]);
+ return opt;
+ }
+ else if (c == '(')
+ level++;
+ else if (c == ')')
+ level--;
+ else if (c == ';' && level == 0) {
+ line += '\0';
+ break;
+ }
+ line += c;
+ }
+ if (line.empty())
+ return opt;
+ char *p = &line[0];
+ for (;;) {
+ while (csspace(*p) || *p == ',')
+ p++;
+ if (*p == '\0')
+ break;
+ char *q = p;
+ while (*q != ' ' && *q != '\0' && *q != '\t' && *q != ',' && *q != '(')
+ q++;
+ char *arg = 0;
+ if (*q != '(' && *q != '\0')
+ *q++ = '\0';
+ while (csspace(*q))
+ q++;
+ if (*q == '(') {
+ *q++ = '\0';
+ arg = q;
+ while (*q != ')' && *q != '\0')
+ q++;
+ if (*q == '\0')
+ error("missing `)'");
+ else
+ *q++ = '\0';
+ }
+ if (*p == '\0') {
+ if (arg)
+ error("argument without option");
+ }
+ else if (strieq(p, "tab")) {
+ if (!arg)
+ error("`tab' option requires argument in parentheses");
+ else {
+ if (arg[0] == '\0' || arg[1] != '\0')
+ error("argument to `tab' option must be a single character");
+ else
+ opt->tab_char = arg[0];
+ }
+ }
+ else if (strieq(p, "linesize")) {
+ if (!arg)
+ error("`linesize' option requires argument in parentheses");
+ else {
+ if (sscanf(arg, "%d", &opt->linesize) != 1)
+ error("bad linesize `%s'", arg);
+ else if (opt->linesize <= 0) {
+ error("linesize must be positive");
+ opt->linesize = 0;
+ }
+ }
+ }
+ else if (strieq(p, "delim")) {
+ if (!arg)
+ error("`delim' option requires argument in parentheses");
+ else if (arg[0] == '\0' || arg[1] == '\0' || arg[2] != '\0')
+ error("argument to `delim' option must be two characters");
+ else {
+ opt->delim[0] = arg[0];
+ opt->delim[1] = arg[1];
+ }
+ }
+ else if (strieq(p, "center") || strieq(p, "centre")) {
+ if (arg)
+ error("`center' option does not take a argument");
+ opt->flags |= table::CENTER;
+ }
+ else if (strieq(p, "expand")) {
+ if (arg)
+ error("`expand' option does not take a argument");
+ opt->flags |= table::EXPAND;
+ }
+ else if (strieq(p, "box") || strieq(p, "frame")) {
+ if (arg)
+ error("`box' option does not take a argument");
+ opt->flags |= table::BOX;
+ }
+ else if (strieq(p, "doublebox") || strieq(p, "doubleframe")) {
+ if (arg)
+ error("`doublebox' option does not take a argument");
+ opt->flags |= table::DOUBLEBOX;
+ }
+ else if (strieq(p, "allbox")) {
+ if (arg)
+ error("`allbox' option does not take a argument");
+ opt->flags |= table::ALLBOX;
+ }
+ else {
+ error("unrecognised global option `%1'", p);
+ // delete opt;
+ // return 0;
+ }
+ p = q;
+ }
+ return opt;
+}
+
+entry_modifier::entry_modifier()
+: vertical_alignment(CENTER), zero_width(0), stagger(0)
+{
+ vertical_spacing.inc = vertical_spacing.val = 0;
+ point_size.inc = point_size.val = 0;
+}
+
+entry_modifier::~entry_modifier()
+{
+}
+
+entry_format::entry_format() : type(FORMAT_LEFT)
+{
+}
+
+entry_format::entry_format(format_type t) : type(t)
+{
+}
+
+void entry_format::debug_print() const
+{
+ switch (type) {
+ case FORMAT_LEFT:
+ putc('l', stderr);
+ break;
+ case FORMAT_CENTER:
+ putc('c', stderr);
+ break;
+ case FORMAT_RIGHT:
+ putc('r', stderr);
+ break;
+ case FORMAT_NUMERIC:
+ putc('n', stderr);
+ break;
+ case FORMAT_ALPHABETIC:
+ putc('a', stderr);
+ break;
+ case FORMAT_SPAN:
+ putc('s', stderr);
+ break;
+ case FORMAT_VSPAN:
+ putc('^', stderr);
+ break;
+ case FORMAT_HLINE:
+ putc('_', stderr);
+ break;
+ case FORMAT_DOUBLE_HLINE:
+ putc('=', stderr);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ if (point_size.val != 0) {
+ putc('p', stderr);
+ if (point_size.inc > 0)
+ putc('+', stderr);
+ else if (point_size.inc < 0)
+ putc('-', stderr);
+ fprintf(stderr, "%d ", point_size.val);
+ }
+ if (vertical_spacing.val != 0) {
+ putc('v', stderr);
+ if (vertical_spacing.inc > 0)
+ putc('+', stderr);
+ else if (vertical_spacing.inc < 0)
+ putc('-', stderr);
+ fprintf(stderr, "%d ", vertical_spacing.val);
+ }
+ if (!font.empty()) {
+ putc('f', stderr);
+ put_string(font, stderr);
+ putc(' ', stderr);
+ }
+ switch (vertical_alignment) {
+ case entry_modifier::CENTER:
+ break;
+ case entry_modifier::TOP:
+ putc('t', stderr);
+ break;
+ case entry_modifier::BOTTOM:
+ putc('d', stderr);
+ break;
+ }
+ if (zero_width)
+ putc('z', stderr);
+ if (stagger)
+ putc('u', stderr);
+}
+
+struct format {
+ int nrows;
+ int ncolumns;
+ int *separation;
+ string *width;
+ char *equal;
+ entry_format **entry;
+ char **vline;
+
+ format(int nr, int nc);
+ ~format();
+ void add_rows(int n);
+};
+
+format::format(int nr, int nc) : nrows(nr), ncolumns(nc)
+{
+ int i;
+ separation = new int[ncolumns - 1];
+ for (i = 0; i < ncolumns-1; i++)
+ separation[i] = -1;
+ width = new string[ncolumns];
+ equal = new char[ncolumns];
+ for (i = 0; i < ncolumns; i++)
+ equal[i] = 0;
+ entry = new entry_format *[nrows];
+ for (i = 0; i < nrows; i++)
+ entry[i] = new entry_format[ncolumns];
+ vline = new char*[nrows];
+ for (i = 0; i < nrows; i++) {
+ vline[i] = new char[ncolumns+1];
+ for (int j = 0; j < ncolumns+1; j++)
+ vline[i][j] = 0;
+ }
+}
+
+void format::add_rows(int n)
+{
+ int i;
+ char **old_vline = vline;
+ vline = new char*[nrows + n];
+ for (i = 0; i < nrows; i++)
+ vline[i] = old_vline[i];
+ a_delete old_vline;
+ for (i = 0; i < n; i++) {
+ vline[nrows + i] = new char[ncolumns + 1];
+ for (int j = 0; j < ncolumns + 1; j++)
+ vline[nrows + i][j] = 0;
+ }
+ entry_format **old_entry = entry;
+ entry = new entry_format *[nrows + n];
+ for (i = 0; i < nrows; i++)
+ entry[i] = old_entry[i];
+ a_delete old_entry;
+ for (i = 0; i < n; i++)
+ entry[nrows + i] = new entry_format[ncolumns];
+ nrows += n;
+}
+
+format::~format()
+{
+ a_delete separation;
+ ad_delete(ncolumns) width;
+ a_delete equal;
+ for (int i = 0; i < nrows; i++) {
+ a_delete vline[i];
+ ad_delete(ncolumns) entry[i];
+ }
+ a_delete vline;
+ a_delete entry;
+}
+
+struct input_entry_format : entry_format {
+ input_entry_format *next;
+ string width;
+ int separation;
+ int vline;
+ int pre_vline;
+ int last_column;
+ int equal;
+ input_entry_format(format_type, input_entry_format * = 0);
+ ~input_entry_format();
+ void debug_print();
+};
+
+input_entry_format::input_entry_format(format_type t, input_entry_format *p)
+: entry_format(t), next(p)
+{
+ separation = -1;
+ last_column = 0;
+ vline = 0;
+ pre_vline = 0;
+ equal = 0;
+}
+
+input_entry_format::~input_entry_format()
+{
+}
+
+void free_input_entry_format_list(input_entry_format *list)
+{
+ while (list) {
+ input_entry_format *tem = list;
+ list = list->next;
+ delete tem;
+ }
+}
+
+void input_entry_format::debug_print()
+{
+ for (int i = 0; i < pre_vline; i++)
+ putc('|', stderr);
+ entry_format::debug_print();
+ if (!width.empty()) {
+ putc('w', stderr);
+ putc('(', stderr);
+ put_string(width, stderr);
+ putc(')', stderr);
+ }
+ if (equal)
+ putc('e', stderr);
+ if (separation >= 0)
+ fprintf(stderr, "%d", separation);
+ for (i = 0; i < vline; i++)
+ putc('|', stderr);
+ if (last_column)
+ putc(',', stderr);
+}
+
+// Return zero if we should give up on this table.
+// If this is a continuation format line, current_format will be the current
+// format line.
+
+format *process_format(table_input &in, options *opt,
+ format *current_format = 0)
+{
+ input_entry_format *list = 0;
+ int c = in.get();
+ for (;;) {
+ int pre_vline = 0;
+ int got_format = 0;
+ int got_period = 0;
+ format_type t;
+ for (;;) {
+ if (c == EOF) {
+ error("end of input while processing format");
+ free_input_entry_format_list(list);
+ return 0;
+ }
+ switch (c) {
+ case 'n':
+ case 'N':
+ t = FORMAT_NUMERIC;
+ got_format = 1;
+ break;
+ case 'a':
+ case 'A':
+ got_format = 1;
+ t = FORMAT_ALPHABETIC;
+ break;
+ case 'c':
+ case 'C':
+ got_format = 1;
+ t = FORMAT_CENTER;
+ break;
+ case 'l':
+ case 'L':
+ got_format = 1;
+ t = FORMAT_LEFT;
+ break;
+ case 'r':
+ case 'R':
+ got_format = 1;
+ t = FORMAT_RIGHT;
+ break;
+ case 's':
+ case 'S':
+ got_format = 1;
+ t = FORMAT_SPAN;
+ break;
+ case '^':
+ got_format = 1;
+ t = FORMAT_VSPAN;
+ break;
+ case '_':
+ got_format = 1;
+ t = FORMAT_HLINE;
+ break;
+ case '=':
+ got_format = 1;
+ t = FORMAT_DOUBLE_HLINE;
+ break;
+ case '.':
+ got_period = 1;
+ break;
+ case '|':
+ pre_vline++;
+ break;
+ case ' ':
+ case '\t':
+ case '\n':
+ break;
+ default:
+ if (c == opt->tab_char)
+ break;
+ error("unrecognised format `%1'", char(c));
+ free_input_entry_format_list(list);
+ return 0;
+ }
+ if (got_period)
+ break;
+ c = in.get();
+ if (got_format)
+ break;
+ }
+ if (got_period)
+ break;
+ list = new input_entry_format(t, list);
+ if (pre_vline)
+ list->pre_vline = pre_vline;
+ int success = 1;
+ do {
+ switch (c) {
+ case 't':
+ case 'T':
+ c = in.get();
+ list->vertical_alignment = entry_modifier::TOP;
+ break;
+ case 'd':
+ case 'D':
+ c = in.get();
+ list->vertical_alignment = entry_modifier::BOTTOM;
+ break;
+ case 'u':
+ case 'U':
+ c = in.get();
+ list->stagger = 1;
+ break;
+ case 'z':
+ case 'Z':
+ c = in.get();
+ list->zero_width = 1;
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ {
+ int w = 0;
+ do {
+ w = w*10 + (c - '0');
+ c = in.get();
+ } while (c != EOF && csdigit(c));
+ list->separation = w;
+ }
+ break;
+ case 'f':
+ case 'F':
+ do {
+ c = in.get();
+ } while (c == ' ' || c == '\t');
+ if (c == EOF) {
+ error("missing font name");
+ break;
+ }
+ if (c == '(') {
+ for (;;) {
+ c = in.get();
+ if (c == EOF || c == ' ' || c == '\t') {
+ error("missing `)'");
+ break;
+ }
+ if (c == ')') {
+ c = in.get();
+ break;
+ }
+ list->font += char(c);
+ }
+ }
+ else {
+ list->font = c;
+ char cc = c;
+ c = in.get();
+ if (!csdigit(cc)
+ && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
+ list->font += char(c);
+ c = in.get();
+ }
+ }
+ break;
+ case 'v':
+ case 'V':
+ c = in.get();
+ list->vertical_spacing.val = 0;
+ list->vertical_spacing.inc = 0;
+ if (c == '+' || c == '-') {
+ list->vertical_spacing.inc = (c == '+' ? 1 : -1);
+ c = in.get();
+ }
+ if (c == EOF || !csdigit(c)) {
+ error("`v' modifier must be followed by number");
+ list->vertical_spacing.inc = 0;
+ }
+ else {
+ do {
+ list->vertical_spacing.val *= 10;
+ list->vertical_spacing.val += c - '0';
+ c = in.get();
+ } while (c != EOF && csdigit(c));
+ }
+ if (list->vertical_spacing.val > MAX_VERTICAL_SPACING
+ || list->vertical_spacing.val < -MAX_VERTICAL_SPACING) {
+ error("unreasonable point size");
+ list->vertical_spacing.val = 0;
+ list->vertical_spacing.inc = 0;
+ }
+ break;
+ case 'p':
+ case 'P':
+ c = in.get();
+ list->point_size.val = 0;
+ list->point_size.inc = 0;
+ if (c == '+' || c == '-') {
+ list->point_size.inc = (c == '+' ? 1 : -1);
+ c = in.get();
+ }
+ if (c == EOF || !csdigit(c)) {
+ error("`p' modifier must be followed by number");
+ list->point_size.inc = 0;
+ }
+ else {
+ do {
+ list->point_size.val *= 10;
+ list->point_size.val += c - '0';
+ c = in.get();
+ } while (c != EOF && csdigit(c));
+ }
+ if (list->point_size.val > MAX_POINT_SIZE
+ || list->point_size.val < -MAX_POINT_SIZE) {
+ error("unreasonable point size");
+ list->point_size.val = 0;
+ list->point_size.inc = 0;
+ }
+ break;
+ case 'w':
+ case 'W':
+ c = in.get();
+ while (c == ' ' || c == '\t')
+ c = in.get();
+ if (c == '(') {
+ list->width = "";
+ c = in.get();
+ while (c != ')') {
+ if (c == EOF || c == '\n') {
+ error("missing `)'");
+ free_input_entry_format_list(list);
+ return 0;
+ }
+ list->width += c;
+ c = in.get();
+ }
+ c = in.get();
+ }
+ else {
+ if (c == '+' || c == '-') {
+ list->width = char(c);
+ c = in.get();
+ }
+ else
+ list->width = "";
+ if (c == EOF || !csdigit(c))
+ error("bad argument for `w' modifier");
+ else {
+ do {
+ list->width += char(c);
+ c = in.get();
+ } while (c != EOF && csdigit(c));
+ }
+ }
+ break;
+ case 'e':
+ case 'E':
+ c = in.get();
+ list->equal++;
+ break;
+ case '|':
+ c = in.get();
+ list->vline++;
+ break;
+ case 'B':
+ case 'b':
+ c = in.get();
+ list->font = "B";
+ break;
+ case 'I':
+ case 'i':
+ c = in.get();
+ list->font = "I";
+ break;
+ case ' ':
+ case '\t':
+ c = in.get();
+ break;
+ default:
+ if (c == opt->tab_char)
+ c = in.get();
+ else
+ success = 0;
+ break;
+ }
+ } while (success);
+ if (list->vline > 2) {
+ list->vline = 2;
+ error("more than 2 vertical bars between key letters");
+ }
+ if (c == '\n' || c == ',') {
+ c = in.get();
+ list->last_column = 1;
+ }
+ }
+ if (c == '.') {
+ do {
+ c = in.get();
+ } while (c == ' ' || c == '\t');
+ if (c != '\n') {
+ error("`.' not last character on line");
+ free_input_entry_format_list(list);
+ return 0;
+ }
+ }
+ if (!list) {
+ error("no format");
+ free_input_entry_format_list(list);
+ return 0;
+ }
+ list->last_column = 1;
+ // now reverse the list so that the first row is at the beginning
+ input_entry_format *rev = 0;
+ while (list != 0) {
+ input_entry_format *tem = list->next;
+ list->next = rev;
+ rev = list;
+ list = tem;
+ }
+ list = rev;
+ input_entry_format *tem;
+
+#if 0
+ for (tem = list; tem; tem = tem->next)
+ tem->debug_print();
+ putc('\n', stderr);
+#endif
+ // compute number of columns and rows
+ int ncolumns = 0;
+ int nrows = 0;
+ int col = 0;
+ for (tem = list; tem; tem = tem->next) {
+ if (tem->last_column) {
+ if (col >= ncolumns)
+ ncolumns = col + 1;
+ col = 0;
+ nrows++;
+ }
+ else
+ col++;
+ }
+ int row;
+ format *f;
+ if (current_format) {
+ if (ncolumns > current_format->ncolumns) {
+ error("cannot increase the number of columns in a continued format");
+ free_input_entry_format_list(list);
+ return 0;
+ }
+ f = current_format;
+ row = f->nrows;
+ f->add_rows(nrows);
+ }
+ else {
+ f = new format(nrows, ncolumns);
+ row = 0;
+ }
+ col = 0;
+ for (tem = list; tem; tem = tem->next) {
+ f->entry[row][col] = *tem;
+ if (col < ncolumns-1) {
+ // use the greatest separation
+ if (tem->separation > f->separation[col]) {
+ if (current_format)
+ error("cannot change column separation in continued format");
+ else
+ f->separation[col] = tem->separation;
+ }
+ }
+ else if (tem->separation >= 0)
+ error("column separation specified for last column");
+ if (tem->equal && !f->equal[col]) {
+ if (current_format)
+ error("cannot change which columns are equal in continued format");
+ else
+ f->equal[col] = 1;
+ }
+ if (!tem->width.empty()) {
+ // use the last width
+ if (!f->width[col].empty() && f->width[col] != tem->width)
+ error("multiple widths for column %1", col+1);
+ f->width[col] = tem->width;
+ }
+ if (tem->pre_vline) {
+ assert(col == 0);
+ f->vline[row][col] = tem->pre_vline;
+ }
+ f->vline[row][col+1] = tem->vline;
+ if (tem->last_column) {
+ row++;
+ col = 0;
+ }
+ else
+ col++;
+ }
+ free_input_entry_format_list(list);
+ for (col = 0; col < ncolumns; col++) {
+ entry_format *e = f->entry[f->nrows-1] + col;
+ if (e->type != FORMAT_HLINE
+ && e->type != FORMAT_DOUBLE_HLINE
+ && e->type != FORMAT_SPAN)
+ break;
+ }
+ if (col >= ncolumns) {
+ error("last row of format is all lines");
+ delete f;
+ return 0;
+ }
+ return f;
+}
+
+table *process_data(table_input &in, format *f, options *opt)
+{
+ char tab_char = opt->tab_char;
+ int ncolumns = f->ncolumns;
+ int current_row = 0;
+ int format_index = 0;
+ int give_up = 0;
+ enum { DATA_INPUT_LINE, TROFF_INPUT_LINE, SINGLE_HLINE, DOUBLE_HLINE } type;
+ table *tbl = new table(ncolumns, opt->flags, opt->linesize);
+ if (opt->delim[0] != '\0')
+ tbl->set_delim(opt->delim[0], opt->delim[1]);
+ for (;;) {
+ // first determine what type of line this is
+ int c = in.get();
+ if (c == EOF)
+ break;
+ if (c == '.') {
+ int d = in.get();
+ if (d != EOF && csdigit(d)) {
+ in.unget(d);
+ type = DATA_INPUT_LINE;
+ }
+ else {
+ in.unget(d);
+ type = TROFF_INPUT_LINE;
+ }
+ }
+ else if (c == '_' || c == '=') {
+ int d = in.get();
+ if (d == '\n') {
+ if (c == '_')
+ type = SINGLE_HLINE;
+ else
+ type = DOUBLE_HLINE;
+ }
+ else {
+ in.unget(d);
+ type = DATA_INPUT_LINE;
+ }
+ }
+ else {
+ type = DATA_INPUT_LINE;
+ }
+ switch (type) {
+ case DATA_INPUT_LINE:
+ {
+ string input_entry;
+ if (format_index >= f->nrows)
+ format_index = f->nrows - 1;
+ // A format row that is all lines doesn't use up a data line.
+ while (format_index < f->nrows - 1) {
+ for (int c = 0; c < ncolumns; c++) {
+ entry_format *e = f->entry[format_index] + c;
+ if (e->type != FORMAT_HLINE
+ && e->type != FORMAT_DOUBLE_HLINE
+ // Unfortunately tbl treats a span as needing data.
+ // && e->type != FORMAT_SPAN
+ )
+ break;
+ }
+ if (c < ncolumns)
+ break;
+ for (c = 0; c < ncolumns; c++)
+ tbl->add_entry(current_row, c, input_entry,
+ f->entry[format_index] + c, current_filename,
+ current_lineno);
+ tbl->add_vlines(current_row, f->vline[format_index]);
+ format_index++;
+ current_row++;
+ }
+ entry_format *line_format = f->entry[format_index];
+ int col = 0;
+ for (;;) {
+ if (c == tab_char || c == '\n') {
+ int ln = current_lineno;
+ if (c == '\n')
+ --ln;
+ while (col < ncolumns
+ && line_format[col].type == FORMAT_SPAN) {
+ tbl->add_entry(current_row, col, "", &line_format[col],
+ current_filename, ln);
+ col++;
+ }
+ if (c == '\n' && input_entry.length() == 2
+ && input_entry[0] == 'T' && input_entry[1] == '{') {
+ input_entry = "";
+ ln++;
+ enum {
+ START, MIDDLE, GOT_T, GOT_RIGHT_BRACE, GOT_DOT,
+ GOT_l, GOT_lf, END
+ } state = START;
+ while (state != END) {
+ c = in.get();
+ if (c == EOF)
+ break;
+ switch (state) {
+ case START:
+ if (c == 'T')
+ state = GOT_T;
+ else if (c == '.')
+ state = GOT_DOT;
+ else {
+ input_entry += c;
+ if (c != '\n')
+ state = MIDDLE;
+ }
+ break;
+ case GOT_T:
+ if (c == '}')
+ state = GOT_RIGHT_BRACE;
+ else {
+ input_entry += 'T';
+ input_entry += c;
+ state = c == '\n' ? START : MIDDLE;
+ }
+ break;
+ case GOT_DOT:
+ if (c == 'l')
+ state = GOT_l;
+ else {
+ input_entry += '.';
+ input_entry += c;
+ state = c == '\n' ? START : MIDDLE;
+ }
+ break;
+ case GOT_l:
+ if (c == 'f')
+ state = GOT_lf;
+ else {
+ input_entry += ".l";
+ input_entry += c;
+ state = c == '\n' ? START : MIDDLE;
+ }
+ break;
+ case GOT_lf:
+ if (c == ' ' || c == '\n' || compatible_flag) {
+ string args;
+ input_entry += ".lf";
+ while (c != EOF) {
+ args += c;
+ if (c == '\n')
+ break;
+ c = in.get();
+ }
+ args += '\0';
+ interpret_lf_args(args.contents());
+ // remove the '\0'
+ args.set_length(args.length() - 1);
+ input_entry += args;
+ state = START;
+ }
+ else {
+ input_entry += ".lf";
+ input_entry += c;
+ state = MIDDLE;
+ }
+ break;
+ case GOT_RIGHT_BRACE:
+ if (c == '\n' || c == tab_char)
+ state = END;
+ else {
+ input_entry += 'T';
+ input_entry += '}';
+ input_entry += c;
+ state = c == '\n' ? START : MIDDLE;
+ }
+ break;
+ case MIDDLE:
+ if (c == '\n')
+ state = START;
+ input_entry += c;
+ break;
+ case END:
+ default:
+ assert(0);
+ }
+ }
+ if (c == EOF) {
+ error("end of data in middle of text block");
+ give_up = 1;
+ break;
+ }
+ }
+ if (col >= ncolumns) {
+ if (!input_entry.empty()) {
+ if (c == '\n')
+ in.unget(c);
+ input_entry += '\0';
+ error("excess data entry `%1' discarded",
+ input_entry.contents());
+ if (c == '\n')
+ (void)in.get();
+ }
+ }
+ else
+ tbl->add_entry(current_row, col, input_entry,
+ &line_format[col], current_filename, ln);
+ col++;
+ if (c == '\n')
+ break;
+ input_entry = "";
+ }
+ else
+ input_entry += c;
+ c = in.get();
+ if (c == EOF)
+ break;
+ }
+ if (give_up)
+ break;
+ input_entry = "";
+ for (; col < ncolumns; col++)
+ tbl->add_entry(current_row, col, input_entry, &line_format[col],
+ current_filename, current_lineno - 1);
+ tbl->add_vlines(current_row, f->vline[format_index]);
+ current_row++;
+ format_index++;
+ }
+ break;
+ case TROFF_INPUT_LINE:
+ {
+ string line;
+ int ln = current_lineno;
+ for (;;) {
+ line += c;
+ if (c == '\n')
+ break;
+ c = in.get();
+ if (c == EOF) {
+ break;
+ }
+ }
+ tbl->add_text_line(current_row, line, current_filename, ln);
+ if (line.length() >= 4
+ && line[0] == '.' && line[1] == 'T' && line[2] == '&') {
+ format *newf = process_format(in, opt, f);
+ if (newf == 0)
+ give_up = 1;
+ else
+ f = newf;
+ }
+ if (line.length() >= 3
+ && line[0] == '.' && line[1] == 'f' && line[2] == 'f') {
+ line += '\0';
+ interpret_lf_args(line.contents() + 3);
+ }
+ }
+ break;
+ case SINGLE_HLINE:
+ tbl->add_single_hline(current_row);
+ break;
+ case DOUBLE_HLINE:
+ tbl->add_double_hline(current_row);
+ break;
+ default:
+ assert(0);
+ }
+ if (give_up)
+ break;
+ }
+ if (!give_up && current_row == 0) {
+ error("no real data");
+ give_up = 1;
+ }
+ if (give_up) {
+ delete tbl;
+ return 0;
+ }
+ // Do this here rather than at the beginning in case continued formats
+ // change it.
+ for (int i = 0; i < ncolumns - 1; i++)
+ if (f->separation[i] >= 0)
+ tbl->set_column_separation(i, f->separation[i]);
+ for (i = 0; i < ncolumns; i++)
+ if (!f->width[i].empty())
+ tbl->set_minimum_width(i, f->width[i]);
+ for (i = 0; i < ncolumns; i++)
+ if (f->equal[i])
+ tbl->set_equal_column(i);
+ return tbl;
+}
+
+void process_table(table_input &in)
+{
+ int c;
+ options *opt = 0;
+ format *form = 0;
+ table *tbl = 0;
+ if ((opt = process_options(in)) != 0
+ && (form = process_format(in, opt)) != 0
+ && (tbl = process_data(in, form, opt)) != 0) {
+ tbl->print();
+ delete tbl;
+ }
+ else {
+ error("giving up on this table");
+ while ((c = in.get()) != EOF)
+ ;
+ }
+ delete opt;
+ delete form;
+ if (!in.ended())
+ error("premature end of file");
+}
+
+static void usage()
+{
+ fprintf(stderr, "usage: %s [ -vC ] [ files... ]\n", program_name);
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ int opt;
+ while ((opt = getopt(argc, argv, "vC")) != EOF)
+ switch (opt) {
+ case 'C':
+ compatible_flag = 1;
+ break;
+ case 'v':
+ {
+ extern const char *version_string;
+ fprintf(stderr, "GNU tbl version %s\n", version_string);
+ fflush(stderr);
+ break;
+ }
+ case '?':
+ usage();
+ break;
+ default:
+ assert(0);
+ }
+ printf(".if !\\n(.g .ab GNU tbl requires GNU troff.\n"
+ ".if !dTS .ds TS\n"
+ ".if !dTE .ds TE\n");
+ if (argc > optind) {
+ for (int i = optind; i < argc; i++)
+ if (argv[i][0] == '-' && argv[i][1] == '\0') {
+ current_filename = "-";
+ current_lineno = 1;
+ printf(".lf 1 -\n");
+ process_input_file(stdin);
+ }
+ else {
+ errno = 0;
+ FILE *fp = fopen(argv[i], "r");
+ if (fp == 0) {
+ current_lineno = -1;
+ error("can't open `%1': %2", argv[i], strerror(errno));
+ }
+ else {
+ current_lineno = 1;
+ current_filename = argv[i];
+ printf(".lf 1 %s\n", current_filename);
+ process_input_file(fp);
+ }
+ }
+ }
+ else {
+ current_filename = "-";
+ current_lineno = 1;
+ printf(".lf 1 -\n");
+ process_input_file(stdin);
+ }
+ if (ferror(stdout) || fflush(stdout) < 0)
+ fatal("output error");
+ exit(0);
+}
+