summaryrefslogtreecommitdiffstats
path: root/src/preproc/tbl/main.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 19:44:05 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 19:44:05 +0000
commitd318611dd6f23fcfedd50e9b9e24620b102ba96a (patch)
tree8b9eef82ca40fdd5a8deeabf07572074c236095d /src/preproc/tbl/main.cpp
parentInitial commit. (diff)
downloadgroff-upstream.tar.xz
groff-upstream.zip
Adding upstream version 1.23.0.upstream/1.23.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/preproc/tbl/main.cpp')
-rw-r--r--src/preproc/tbl/main.cpp1692
1 files changed, 1692 insertions, 0 deletions
diff --git a/src/preproc/tbl/main.cpp b/src/preproc/tbl/main.cpp
new file mode 100644
index 0000000..db105c2
--- /dev/null
+++ b/src/preproc/tbl/main.cpp
@@ -0,0 +1,1692 @@
+/* Copyright (C) 1989-2020 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 3 of the License, 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 this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "table.h"
+
+#define MAX_POINT_SIZE 99
+#define MAX_VERTICAL_SPACING 72
+
+extern "C" const char *Version_string;
+
+int compatible_flag = 0;
+
+class table_input {
+ FILE *fp;
+ enum { START, MIDDLE,
+ REREAD_T, REREAD_TE, REREAD_E,
+ LEADER_1, LEADER_2, LEADER_3, LEADER_4,
+ 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("invalid input character code 0");
+ break;
+ }
+ }
+ return c;
+ }
+ break;
+ case MIDDLE:
+ // handle line continuation and uninterpreted leader character
+ if ((c = getc(fp)) == '\\') {
+ c = getc(fp);
+ if (c == '\n') {
+ current_lineno++;
+ c = getc(fp);
+ }
+ else if (c == 'a' && compatible_flag) {
+ state = LEADER_1;
+ return '\\';
+ }
+ 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("invalid 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 LEADER_1:
+ state = LEADER_2;
+ return '*';
+ case LEADER_2:
+ state = LEADER_3;
+ return '(';
+ case LEADER_3:
+ state = LEADER_4;
+ return PREFIX_CHAR;
+ case LEADER_4:
+ state = MIDDLE;
+ return LEADER_CHAR;
+ 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 == "invalid `state` in switch");
+ }
+ 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;
+ char decimal_point_char;
+
+ options();
+};
+
+options::options()
+: flags(0), linesize(0), tab_char('\t'), decimal_point_char('.')
+{
+ 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;
+}
+
+// Handle region options. Return a null pointer if we should give up on
+// 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 == ';' && 0 == level) {
+ line += '\0';
+ break;
+ }
+ line += c;
+ }
+ if (line.empty())
+ return opt;
+ char *p = &line[0];
+ for (;;) {
+ while (!csalpha(*p) && *p != '\0')
+ p++;
+ if (*p == '\0')
+ break;
+ char *q = p;
+ while (csalpha(*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("'%1' region option argument missing closing parenthesis",
+ arg);
+ else
+ *q++ = '\0';
+ }
+ if (*p == '\0') {
+ if (arg)
+ error("'%1' region option argument cannot be empty", arg);
+ }
+ else if (strieq(p, "tab")) {
+ if (!arg)
+ error("'tab' region option requires argument in parentheses");
+ else {
+ if (arg[0] == '\0' || arg[1] != '\0')
+ error("'tab' region option argument must be a single"
+ " character");
+ else
+ opt->tab_char = arg[0];
+ }
+ }
+ else if (strieq(p, "linesize")) {
+ if (!arg)
+ error("'linesize' region option requires argument in"
+ " parentheses");
+ else {
+ if (sscanf(arg, "%d", &opt->linesize) != 1)
+ error("invalid argument to 'linesize' region option: '%1'",
+ arg);
+ else if (opt->linesize <= 0) {
+ error("'linesize' region option argument must be positive");
+ opt->linesize = 0;
+ }
+ }
+ }
+ else if (strieq(p, "delim")) {
+ if (!arg)
+ error("'delim' region 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' region option does not take an argument");
+ opt->flags |= table::CENTER;
+ }
+ else if (strieq(p, "expand")) {
+ if (arg)
+ error("'expand' region option does not take an argument");
+ opt->flags |= table::EXPAND;
+ opt->flags |= table::GAP_EXPAND;
+ }
+ else if (strieq(p, "box") || strieq(p, "frame")) {
+ if (arg)
+ error("'box' region option does not take an argument");
+ opt->flags |= table::BOX;
+ }
+ else if (strieq(p, "doublebox") || strieq(p, "doubleframe")) {
+ if (arg)
+ error("'doublebox' region option does not take an argument");
+ opt->flags |= table::DOUBLEBOX;
+ }
+ else if (strieq(p, "allbox")) {
+ if (arg)
+ error("'allbox' region option does not take an argument");
+ opt->flags |= table::ALLBOX;
+ }
+ else if (strieq(p, "nokeep")) {
+ if (arg)
+ error("'nokeep' region option does not take an argument");
+ opt->flags |= table::NOKEEP;
+ }
+ else if (strieq(p, "nospaces")) {
+ if (arg)
+ error("'nospaces' region option does not take an argument");
+ opt->flags |= table::NOSPACES;
+ }
+ else if (strieq(p, "nowarn")) {
+ if (arg)
+ error("'nowarn' region option does not take an argument");
+ opt->flags |= table::NOWARN;
+ }
+ else if (strieq(p, "decimalpoint")) {
+ if (!arg)
+ error("'decimalpoint' region option requires argument in"
+ " parentheses");
+ else {
+ if (arg[0] == '\0' || arg[1] != '\0')
+ error("'decimalpoint' region option argument must be a single"
+ " character");
+ else
+ opt->decimal_point_char = arg[0];
+ }
+ }
+ else if (strieq(p, "experimental")) {
+ opt->flags |= table::EXPERIMENTAL;
+ }
+ else {
+ error("unrecognized region 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 == "invalid column classifier in switch");
+ 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);
+ }
+ if (!macro.empty()) {
+ putc('m', stderr);
+ put_string(macro, 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;
+ char *expand;
+ 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 = ncolumns > 1 ? new int[ncolumns - 1] : 0;
+ for (i = 0; i < ncolumns-1; i++)
+ separation[i] = -1;
+ width = new string[ncolumns];
+ equal = new char[ncolumns];
+ expand = new char[ncolumns];
+ for (i = 0; i < ncolumns; i++) {
+ equal[i] = 0;
+ expand[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];
+ 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];
+ delete[] old_entry;
+ for (i = 0; i < n; i++)
+ entry[nrows + i] = new entry_format[ncolumns];
+ nrows += n;
+}
+
+format::~format()
+{
+ delete[] separation;
+ delete[] width;
+ delete[] equal;
+ delete[] expand;
+ for (int i = 0; i < nrows; i++) {
+ delete[] vline[i];
+ delete[] entry[i];
+ }
+ delete[] vline;
+ delete[] entry;
+}
+
+struct input_entry_format : public entry_format {
+ input_entry_format *next;
+ string width;
+ int separation;
+ int vline;
+ int vline_count;
+ bool is_last_column;
+ bool is_equal_width;
+ int expand;
+ 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;
+ is_last_column = false;
+ vline = 0;
+ vline_count = 0;
+ is_equal_width = false;
+ expand = 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()
+{
+ int i;
+ for (i = 0; i < vline_count; i++)
+ putc('|', stderr);
+ entry_format::debug_print();
+ if (!width.empty()) {
+ putc('w', stderr);
+ putc('(', stderr);
+ put_string(width, stderr);
+ putc(')', stderr);
+ }
+ if (is_equal_width)
+ putc('e', stderr);
+ if (expand)
+ putc('x', stderr);
+ if (separation >= 0)
+ fprintf(stderr, "%d", separation);
+ for (i = 0; i < vline; i++)
+ putc('|', stderr);
+ if (is_last_column)
+ putc(',', stderr);
+}
+
+// Interpret a table format specification, like "CC,LR.". Return null
+// pointer 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 /* nullptr */;
+ bool have_expand = false;
+ bool is_first_row = true;
+ int c = in.get();
+ for (;;) {
+ int vline_count = 0;
+ bool got_format = false;
+ bool got_period = false;
+ format_type t = FORMAT_LEFT;
+ for (;;) {
+ if (c == EOF) {
+ error("end of input while processing table format"
+ " specification");
+ free_input_entry_format_list(list);
+ list = 0 /* nullptr */;
+ return 0 /* nullptr */;
+ }
+ switch (c) {
+ case 'n':
+ case 'N':
+ t = FORMAT_NUMERIC;
+ got_format = true;
+ break;
+ case 'a':
+ case 'A':
+ got_format = true;
+ t = FORMAT_ALPHABETIC;
+ break;
+ case 'c':
+ case 'C':
+ got_format = true;
+ t = FORMAT_CENTER;
+ break;
+ case 'l':
+ case 'L':
+ got_format = true;
+ t = FORMAT_LEFT;
+ break;
+ case 'r':
+ case 'R':
+ got_format = true;
+ t = FORMAT_RIGHT;
+ break;
+ case 's':
+ case 'S':
+ got_format = true;
+ t = FORMAT_SPAN;
+ break;
+ case '^':
+ got_format = true;
+ t = FORMAT_VSPAN;
+ break;
+ case '_':
+ case '-': // tbl also accepts this
+ got_format = true;
+ t = FORMAT_HLINE;
+ if (is_first_row)
+ opt->flags |= table::HAS_TOP_HLINE;
+ break;
+ case '=':
+ got_format = true;
+ t = FORMAT_DOUBLE_HLINE;
+ break;
+ case '.':
+ got_period = true;
+ break;
+ case '|':
+ // leading vertical line in row
+ opt->flags |= table::HAS_TOP_VLINE;
+ vline_count++;
+ // list->vline_count is updated later
+ break;
+ case ' ':
+ case '\t':
+ case '\n':
+ break;
+ default:
+ if (c == opt->tab_char)
+ break;
+ error("invalid column classifier '%1'", char(c));
+ free_input_entry_format_list(list);
+ list = 0 /* nullptr */;
+ return 0 /* nullptr */;
+ }
+ if (got_period)
+ break;
+ c = in.get();
+ if (got_format)
+ break;
+ }
+ if (got_period)
+ break;
+ list = new input_entry_format(t, list);
+ if (vline_count > 2) {
+ vline_count = 2;
+ error("more than 2 vertical lines at beginning of row description");
+ }
+ list->vline_count = vline_count;
+ // Now handle modifiers.
+ vline_count = 0;
+ bool is_valid_modifier_sequence = true;
+ do {
+ switch (c) {
+ 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 'B':
+ case 'b':
+ c = in.get();
+ list->font = "B";
+ break;
+ case 'd':
+ case 'D':
+ c = in.get();
+ list->vertical_alignment = entry_modifier::BOTTOM;
+ break;
+ case 'e':
+ case 'E':
+ c = in.get();
+ list->is_equal_width = true;
+ // 'e' and 'x' are mutually exclusive
+ list->expand = 0;
+ break;
+ case 'f':
+ case 'F':
+ do {
+ c = in.get();
+ } while (c == ' ' || c == '\t');
+ if (c == EOF) {
+ error("'f' column modifier missing font name or mounting"
+ " position");
+ break;
+ }
+ if (c == '(') {
+ for (;;) {
+ c = in.get();
+ if (c == EOF || c == ' ' || c == '\t') {
+ error("'f' column modifier missing closing parenthesis");
+ 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 'I':
+ case 'i':
+ c = in.get();
+ list->font = "I";
+ break;
+ case 'm':
+ case 'M':
+ do {
+ c = in.get();
+ } while (c == ' ' || c == '\t');
+ if (c == EOF) {
+ error("'m' column modifier missing macro name");
+ break;
+ }
+ if (c == '(') {
+ for (;;) {
+ c = in.get();
+ if (c == EOF || c == ' ' || c == '\t') {
+ error("'m' column modifier missing closing parenthesis");
+ break;
+ }
+ if (c == ')') {
+ c = in.get();
+ break;
+ }
+ list->macro += char(c);
+ }
+ }
+ else {
+ list->macro = c;
+ char cc = c;
+ c = in.get();
+ if (!csdigit(cc)
+ && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
+ list->macro += char(c);
+ c = in.get();
+ }
+ }
+ break;
+ case 'p':
+ case 'P':
+ {
+ inc_number &ps = list->point_size;
+ ps.val = 0;
+ ps.inc = 0;
+ c = in.get();
+ if (c == '+' || c == '-') {
+ ps.inc = (c == '+' ? 1 : -1);
+ c = in.get();
+ }
+ if (c == EOF || !csdigit(c)) {
+ warning("'p' column modifier must be followed by"
+ " (optionally signed) integer; ignoring");
+ ps.inc = 0;
+ }
+ else {
+ do {
+ ps.val *= 10;
+ ps.val += c - '0';
+ c = in.get();
+ } while (c != EOF && csdigit(c));
+ }
+ if (ps.val > MAX_POINT_SIZE || ps.val < -MAX_POINT_SIZE) {
+ warning("'p' column modifier argument magnitude of %1"
+ " points out of range (> %2); ignoring", ps.val,
+ MAX_POINT_SIZE);
+ ps.val = 0;
+ ps.inc = 0;
+ }
+ break;
+ }
+ case 't':
+ case 'T':
+ c = in.get();
+ list->vertical_alignment = entry_modifier::TOP;
+ break;
+ case 'u':
+ case 'U':
+ c = in.get();
+ list->stagger = 1;
+ break;
+ case 'v':
+ case 'V':
+ {
+ inc_number &vs = list->vertical_spacing;
+ vs.val = 0;
+ vs.inc = 0;
+ c = in.get();
+ if (c == '+' || c == '-') {
+ vs.inc = (c == '+' ? 1 : -1);
+ c = in.get();
+ }
+ if (c == EOF || !csdigit(c)) {
+ warning("'v' column modifier must be followed by"
+ " (optionally signed) integer; ignoring");
+ vs.inc = 0;
+ }
+ else {
+ do {
+ vs.val *= 10;
+ vs.val += c - '0';
+ c = in.get();
+ } while (c != EOF && csdigit(c));
+ }
+ if (vs.val > MAX_VERTICAL_SPACING
+ || vs.val < -MAX_VERTICAL_SPACING) {
+ warning("'v' column modifier argument magnitude of %1"
+ " points out of range (> %2); ignoring", vs.val,
+ MAX_VERTICAL_SPACING);
+ vs.val = 0;
+ vs.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("'w' column modifier missing closing parenthesis");
+ free_input_entry_format_list(list);
+ list = 0 /* nullptr */;
+ return 0 /* nullptr */;
+ }
+ 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("invalid argument to 'w' modifier");
+ else {
+ do {
+ list->width += char(c);
+ c = in.get();
+ } while (c != EOF && csdigit(c));
+ }
+ }
+ // 'w' and 'x' are mutually exclusive
+ list->expand = 0;
+ break;
+ case 'x':
+ case 'X':
+ c = in.get();
+ list->expand = 1;
+ // 'x' and 'e' are mutually exclusive
+ list->is_equal_width = false;
+ // 'x' and 'w' are mutually exclusive
+ list->width = "";
+ break;
+ case 'z':
+ case 'Z':
+ c = in.get();
+ list->zero_width = 1;
+ break;
+ case '|':
+ if (is_first_row)
+ opt->flags |= table::HAS_TOP_VLINE;
+ c = in.get();
+ vline_count++;
+ break;
+ case ' ':
+ case '\t':
+ c = in.get();
+ break;
+ default:
+ if (c == opt->tab_char)
+ c = in.get();
+ else
+ is_valid_modifier_sequence = false;
+ break;
+ }
+ } while (is_valid_modifier_sequence);
+ if (vline_count > 2) {
+ vline_count = 2;
+ error("more than 2 vertical lines after column descriptor");
+ }
+ list->vline += vline_count;
+ if (c == '\n' || c == ',') {
+ vline_count = 0;
+ is_first_row = false;
+ c = in.get();
+ list->is_last_column = true;
+ }
+ }
+ if (c == '.') {
+ do {
+ c = in.get();
+ } while (c == ' ' || c == '\t');
+ if (c != '\n') {
+ error("'.' is not the last character of the table format");
+ free_input_entry_format_list(list);
+ list = 0 /* nullptr */;
+ return 0 /* nullptr */;
+ }
+ }
+ if (!list) {
+ error("table format specification is empty");
+ free_input_entry_format_list(list);
+ list = 0 /* nullptr */;
+ return 0 /* nullptr */;
+ }
+ list->is_last_column = true;
+ // 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->is_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);
+ list = 0 /* nullptr */;
+ return 0 /* nullptr */;
+ }
+ 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->is_equal_width && !f->equal[col]) {
+ if (current_format)
+ error("cannot change which columns are equal in continued format");
+ else
+ f->equal[col] = 1;
+ }
+ if (tem->expand && !f->expand[col]) {
+ if (current_format)
+ error("cannot change which columns are expanded in continued format");
+ else {
+ f->expand[col] = 1;
+ have_expand = true;
+ }
+ }
+ 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->vline_count)
+ f->vline[row][col] = tem->vline_count;
+ f->vline[row][col + 1] = tem->vline;
+ if (tem->is_last_column) {
+ row++;
+ col = 0;
+ }
+ else
+ col++;
+ }
+ free_input_entry_format_list(list);
+ list = 0 /* nullptr */;
+ 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 /* nullptr */;
+ }
+ if (have_expand && (opt->flags & table::EXPAND)) {
+ error("'x' column modifier encountered; ignoring region option"
+ " 'expand'");
+ opt->flags &= ~table::EXPAND;
+ }
+ 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;
+ bool give_up = false;
+ enum { DATA_INPUT_LINE, TROFF_INPUT_LINE, SINGLE_HLINE, DOUBLE_HLINE } type;
+ table *tbl = new table(ncolumns, opt->flags, opt->linesize,
+ opt->decimal_point_char);
+ 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;
+ if (0 == current_row)
+ tbl->flags |= table::HAS_TOP_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) {
+ int cnt;
+ for (cnt = 0; cnt < ncolumns; cnt++) {
+ entry_format *e = f->entry[format_index] + cnt;
+ if (e->type != FORMAT_HLINE
+ && e->type != FORMAT_DOUBLE_HLINE
+ // Unfortunately tbl treats a span as needing data.
+ // && e->type != FORMAT_SPAN
+ )
+ break;
+ }
+ if (cnt < ncolumns)
+ break;
+ for (cnt = 0; cnt < ncolumns; cnt++)
+ tbl->add_entry(current_row, cnt, input_entry,
+ f->entry[format_index] + cnt, 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;
+ bool seen_row_comment = false;
+ for (;;) {
+ if (c == tab_char || c == '\n') {
+ int ln = current_lineno;
+ if (c == '\n')
+ --ln;
+ if ((opt->flags & table::NOSPACES))
+ input_entry.remove_spaces();
+ 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 ((opt->flags & table::NOSPACES)) {
+ while (c == ' ')
+ c = in.get();
+ if (c == EOF)
+ break;
+ }
+ if (c == '\n' || c == tab_char)
+ state = END;
+ else {
+ input_entry += 'T';
+ input_entry += '}';
+ input_entry += c;
+ state = MIDDLE;
+ }
+ break;
+ case MIDDLE:
+ if (c == '\n')
+ state = START;
+ input_entry += c;
+ break;
+ case END:
+ default:
+ assert(0 == "invalid `state` in switch");
+ }
+ }
+ if (c == EOF) {
+ error("end of data in middle of text block");
+ give_up = true;
+ break;
+ }
+ }
+ if (col >= ncolumns) {
+ if (!input_entry.empty()) {
+ if (input_entry.length() >= 2
+ && input_entry[0] == '\\'
+ && input_entry[1] == '"')
+ seen_row_comment = true;
+ else if (!seen_row_comment) {
+ if (c == '\n')
+ in.unget(c);
+ input_entry += '\0';
+ error("excess table 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 = true;
+ else
+ f = newf;
+ }
+ if (line.length() >= 3
+ && line[0] == '.' && line[1] == 'l' && 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 == "invalid `type` in switch");
+ }
+ if (give_up)
+ break;
+ }
+ if (!give_up && current_row == 0) {
+ error("no real data");
+ give_up = true;
+ }
+ if (give_up) {
+ delete tbl;
+ return 0;
+ }
+ // Do this here rather than at the beginning in case continued formats
+ // change it.
+ int i;
+ for (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);
+ for (i = 0; i < ncolumns; i++)
+ if (f->expand[i])
+ tbl->set_expand_column(i);
+ return tbl;
+}
+
+void process_table(table_input &in)
+{
+ options *opt = 0 /* nullptr */;
+ format *fmt = 0 /* nullptr */;
+ table *tbl = 0 /* nullptr */;
+ if ((opt = process_options(in)) != 0 /* nullptr */
+ && (fmt = process_format(in, opt)) != 0 /* nullptr */
+ && (tbl = process_data(in, fmt, opt)) != 0 /* nullptr */) {
+ tbl->print();
+ delete tbl;
+ }
+ else {
+ error("giving up on this table region");
+ while (in.get() != EOF)
+ ;
+ }
+ delete opt;
+ delete fmt;
+ if (!in.ended())
+ error("premature end of file");
+}
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+"usage: %s [-C] [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s --help\n",
+ program_name, program_name, program_name);
+}
+
+int main(int argc, char **argv)
+{
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ int opt;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((opt = getopt_long(argc, argv, "vC", long_options, NULL))
+ != EOF)
+ switch (opt) {
+ case 'C':
+ compatible_flag = 1;
+ break;
+ case 'v':
+ {
+ printf("GNU tbl (groff) version %s\n", Version_string);
+ exit(EXIT_SUCCESS);
+ break;
+ }
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ exit(EXIT_SUCCESS);
+ break;
+ case '?':
+ usage(stderr);
+ exit(EXIT_FAILURE);
+ break;
+ default:
+ assert(0 == "unhandled getopt_long return value");
+ }
+ printf(".if !\\n(.g .ab GNU tbl requires groff extensions; aborting\n"
+ ".do if !dTS .ds TS\n"
+ ".do if !dT& .ds T&\n"
+ ".do 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_filename = 0 /* nullptr */;
+ fatal("can't open '%1': %2", argv[i], strerror(errno));
+ }
+ else {
+ current_lineno = 1;
+ string fn(argv[i]);
+ fn += '\0';
+ normalize_for_lf(fn);
+ current_filename = fn.contents();
+ 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");
+ return 0;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: