summaryrefslogtreecommitdiffstats
path: root/src/preproc/eqn/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/preproc/eqn/main.cpp')
-rw-r--r--src/preproc/eqn/main.cpp485
1 files changed, 485 insertions, 0 deletions
diff --git a/src/preproc/eqn/main.cpp b/src/preproc/eqn/main.cpp
new file mode 100644
index 0000000..81d5184
--- /dev/null
+++ b/src/preproc/eqn/main.cpp
@@ -0,0 +1,485 @@
+/* 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 "eqn.h"
+#include "stringclass.h"
+#include "device.h"
+#include "searchpath.h"
+#include "macropath.h"
+#include "htmlhint.h"
+#include "pbox.h"
+#include "ctype.h"
+#include "lf.h"
+
+#define STARTUP_FILE "eqnrc"
+
+extern int yyparse();
+extern "C" const char *Version_string;
+
+static char *delim_search (char *, int);
+static int inline_equation (FILE *, string &, string &);
+
+char start_delim = '\0';
+char end_delim = '\0';
+int non_empty_flag;
+int inline_flag;
+int draw_flag = 0;
+int one_size_reduction_flag = 0;
+int compatible_flag = 0;
+int no_newline_in_delim_flag = 0;
+int html = 0;
+int xhtml = 0;
+eqnmode_t output_format;
+
+static const char *input_char_description(int c)
+{
+ switch (c) {
+ case '\001':
+ return "a leader character";
+ case '\n':
+ return "a newline character";
+ case '\b':
+ return "a backspace character";
+ case '\t':
+ return "a tab character";
+ case ' ':
+ return "a space character";
+ case '\177':
+ return "a delete character";
+ }
+ size_t bufsz = sizeof "character code " + INT_DIGITS + 1;
+ // repeat expression; no VLAs in ISO C++
+ static char buf[sizeof "character code " + INT_DIGITS + 1];
+ (void) memset(buf, 0, bufsz);
+ if (csprint(c)) {
+ buf[0] = '\'';
+ buf[1] = c;
+ buf[2] = '\'';
+ return buf;
+ }
+ (void) sprintf(buf, "character code %d", c);
+ return buf;
+}
+
+static bool read_line(FILE *fp, string *p)
+{
+ p->clear();
+ int c = -1;
+ while ((c = getc(fp)) != EOF) {
+ if (!is_invalid_input_char(c))
+ *p += char(c);
+ else
+ error("invalid input (%1)", input_char_description(c));
+ if (c == '\n')
+ break;
+ }
+ return (p->length() > 0);
+}
+
+void do_file(FILE *fp, const char *filename)
+{
+ string linebuf;
+ string str;
+ string fn(filename);
+ fn += '\0';
+ normalize_for_lf(fn);
+ current_filename = fn.contents();
+ if (output_format == troff)
+ printf(".lf 1 %s\n", current_filename);
+ current_lineno = 1;
+ while (read_line(fp, &linebuf)) {
+ if (linebuf.length() >= 4
+ && linebuf[0] == '.' && linebuf[1] == 'l' && linebuf[2] == 'f'
+ && (linebuf[3] == ' ' || linebuf[3] == '\n' || compatible_flag))
+ {
+ put_string(linebuf, stdout);
+ linebuf += '\0';
+ // In GNU roff, `lf` assigns the number of the _next_ line.
+ if (interpret_lf_args(linebuf.contents() + 3))
+ current_lineno--;
+ }
+ else if (linebuf.length() >= 4
+ && linebuf[0] == '.'
+ && linebuf[1] == 'E'
+ && linebuf[2] == 'Q'
+ && (linebuf[3] == ' ' || linebuf[3] == '\n'
+ || compatible_flag)) {
+ put_string(linebuf, stdout);
+ int start_lineno = current_lineno + 1;
+ str.clear();
+ for (;;) {
+ if (!read_line(fp, &linebuf)) {
+ current_lineno = 0; // suppress report of line number
+ fatal("end of file before .EN");
+ }
+ if (linebuf.length() >= 3
+ && linebuf[0] == '.'
+ && linebuf[1] == 'E') {
+ if (linebuf[2] == 'N'
+ && (linebuf.length() == 3 || linebuf[3] == ' '
+ || linebuf[3] == '\n' || compatible_flag))
+ break;
+ else if (linebuf[2] == 'Q' && linebuf.length() > 3
+ && (linebuf[3] == ' ' || linebuf[3] == '\n'
+ || compatible_flag)) {
+ current_lineno++; // We just read another line.
+ fatal("equations cannot be nested (.EQ within .EQ)");
+ }
+ }
+ str += linebuf;
+ }
+ str += '\0';
+ start_string();
+ init_lex(str.contents(), current_filename, start_lineno);
+ non_empty_flag = 0;
+ inline_flag = 0;
+ yyparse();
+ restore_compatibility();
+ if (non_empty_flag) {
+ if (output_format == mathml)
+ putchar('\n');
+ else {
+ current_lineno++;
+ printf(".lf %d\n", current_lineno);
+ output_string();
+ }
+ }
+ if (output_format == troff) {
+ current_lineno++;
+ printf(".lf %d\n", current_lineno);
+ }
+ put_string(linebuf, stdout);
+ }
+ else if (start_delim != '\0' && linebuf.search(start_delim) >= 0
+ && inline_equation(fp, linebuf, str))
+ ;
+ else
+ put_string(linebuf, stdout);
+ current_lineno++;
+ }
+ current_filename = 0;
+ current_lineno = 0;
+}
+
+// Handle an inline equation. Return 1 if it was an inline equation,
+// otherwise.
+static int inline_equation(FILE *fp, string &linebuf, string &str)
+{
+ linebuf += '\0';
+ char *ptr = &linebuf[0];
+ char *start = delim_search(ptr, start_delim);
+ if (!start) {
+ // It wasn't a delimiter after all.
+ linebuf.set_length(linebuf.length() - 1); // strip the '\0'
+ return 0;
+ }
+ start_string();
+ inline_flag = 1;
+ for (;;) {
+ if (no_newline_in_delim_flag && strchr(start + 1, end_delim) == 0) {
+ error("unterminated inline equation; started with %1,"
+ " expecting %2", input_char_description(start_delim),
+ input_char_description(end_delim));
+ char *nl = strchr(start + 1, '\n');
+ if (nl != 0)
+ *nl = '\0';
+ do_text(ptr);
+ break;
+ }
+ int start_lineno = current_lineno;
+ *start = '\0';
+ do_text(ptr);
+ ptr = start + 1;
+ str.clear();
+ for (;;) {
+ char *end = strchr(ptr, end_delim);
+ if (end != 0) {
+ *end = '\0';
+ str += ptr;
+ ptr = end + 1;
+ break;
+ }
+ str += ptr;
+ if (!read_line(fp, &linebuf))
+ fatal("unterminated inline equation; started with %1,"
+ " expecting %2", input_char_description(start_delim),
+ input_char_description(end_delim));
+ linebuf += '\0';
+ ptr = &linebuf[0];
+ }
+ str += '\0';
+ if (output_format == troff && html) {
+ printf(".as1 %s ", LINE_STRING);
+ html_begin_suppress();
+ printf("\n");
+ }
+ init_lex(str.contents(), current_filename, start_lineno);
+ yyparse();
+ if (output_format == troff && html) {
+ printf(".as1 %s ", LINE_STRING);
+ html_end_suppress();
+ printf("\n");
+ }
+ if (output_format == mathml)
+ printf("\n");
+ if (xhtml) {
+ /* skip leading spaces */
+ while ((*ptr != '\0') && (*ptr == ' '))
+ ptr++;
+ }
+ start = delim_search(ptr, start_delim);
+ if (start == 0) {
+ char *nl = strchr(ptr, '\n');
+ if (nl != 0)
+ *nl = '\0';
+ do_text(ptr);
+ break;
+ }
+ }
+ restore_compatibility();
+ if (output_format == troff)
+ printf(".lf %d\n", current_lineno);
+ output_string();
+ if (output_format == troff)
+ printf(".lf %d\n", current_lineno + 1);
+ return 1;
+}
+
+/* Search for delim. Skip over number register and string names etc. */
+
+static char *delim_search(char *ptr, int delim)
+{
+ while (*ptr) {
+ if (*ptr == delim)
+ return ptr;
+ if (*ptr++ == '\\') {
+ switch (*ptr) {
+ case 'n':
+ case '*':
+ case 'f':
+ case 'g':
+ case 'k':
+ switch (*++ptr) {
+ case '\0':
+ case '\\':
+ break;
+ case '(':
+ if (*++ptr != '\\' && *ptr != '\0'
+ && *++ptr != '\\' && *ptr != '\0')
+ ptr++;
+ break;
+ case '[':
+ while (*++ptr != '\0')
+ if (*ptr == ']') {
+ ptr++;
+ break;
+ }
+ break;
+ default:
+ ptr++;
+ break;
+ }
+ break;
+ case '\\':
+ case '\0':
+ break;
+ default:
+ ptr++;
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+void usage(FILE *stream)
+{
+ fprintf(stream,
+ "usage: %s [-CNrR] [-d xy] [-f font] [-m n] [-M dir] [-p n] [-s n]"
+ " [-T name] [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;
+ int load_startup_file = 1;
+ 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, "CNrRd:f:m:M:p:s:T:v",
+ long_options, NULL))
+ != EOF)
+ switch (opt) {
+ case 'C':
+ compatible_flag = 1;
+ break;
+ case 'R': // don't load eqnrc
+ load_startup_file = 0;
+ break;
+ case 'M':
+ config_macro_path.command_line_dir(optarg);
+ break;
+ case 'v':
+ printf("GNU eqn (groff) version %s\n", Version_string);
+ exit(EXIT_SUCCESS);
+ break;
+ case 'd':
+ if (optarg[0] == '\0' || optarg[1] == '\0')
+ error("'-d' option requires a two-character argument");
+ else if (is_invalid_input_char(optarg[0]))
+ error("invalid delimiter (%1) in '-d' option argument",
+ input_char_description(optarg[0]));
+ else if (is_invalid_input_char(optarg[1]))
+ error("invalid delimiter (%1) in '-d' option argument",
+ input_char_description(optarg[1]));
+ else {
+ start_delim = optarg[0];
+ end_delim = optarg[1];
+ }
+ break;
+ case 'f':
+ set_gfont(optarg);
+ break;
+ case 'T':
+ device = optarg;
+ if (strcmp(device, "ps:html") == 0) {
+ device = "ps";
+ html = 1;
+ }
+ else if (strcmp(device, "MathML") == 0) {
+ output_format = mathml;
+ load_startup_file = 0;
+ }
+ else if (strcmp(device, "mathml:xhtml") == 0) {
+ device = "MathML";
+ output_format = mathml;
+ load_startup_file = 0;
+ xhtml = 1;
+ }
+ break;
+ case 's':
+ if (set_gsize(optarg))
+ warning("option '-s' is deprecated");
+ else
+ error("invalid size '%1' in '-s' option argument ", optarg);
+ break;
+ case 'p':
+ {
+ int n;
+ if (sscanf(optarg, "%d", &n) == 1) {
+ warning("option '-p' is deprecated");
+ set_script_reduction(n);
+ }
+ else
+ error("invalid size '%1' in '-p' option argument ", optarg);
+ }
+ break;
+ case 'm':
+ {
+ int n;
+ if (sscanf(optarg, "%d", &n) == 1)
+ set_minimum_size(n);
+ else
+ error("invalid size '%1' in '-n' option argument", optarg);
+ }
+ break;
+ case 'r':
+ one_size_reduction_flag = 1;
+ break;
+ case 'N':
+ no_newline_in_delim_flag = 1;
+ 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");
+ }
+ init_table(device);
+ init_char_table();
+ printf(".do if !dEQ .ds EQ\n"
+ ".do if !dEN .ds EN\n");
+ if (output_format == troff) {
+ printf(".if !'\\*(.T'%s' "
+ ".if !'\\*(.T'html' " // the html device uses '-Tps' to render
+ // equations as images
+ ".tm warning: %s should have been given a '-T\\*(.T' option\n",
+ device, program_name);
+ printf(".if '\\*(.T'html' "
+ ".if !'%s'ps' "
+ ".tm warning: %s should have been given a '-Tps' option\n",
+ device, program_name);
+ printf(".if '\\*(.T'html' "
+ ".if !'%s'ps' "
+ ".tm warning: (it is advisable to invoke groff via: groff -Thtml -e)\n",
+ device);
+ }
+ if (load_startup_file) {
+ char *path;
+ FILE *fp = config_macro_path.open_file(STARTUP_FILE, &path);
+ if (fp) {
+ do_file(fp, path);
+ if (fclose(fp) < 0)
+ fatal("unable to close '%1': %2", STARTUP_FILE,
+ strerror(errno));
+ free(path);
+ }
+ }
+ if (optind >= argc)
+ do_file(stdin, "-");
+ else
+ for (int i = optind; i < argc; i++)
+ if (strcmp(argv[i], "-") == 0)
+ do_file(stdin, "-");
+ else {
+ errno = 0;
+ FILE *fp = fopen(argv[i], "r");
+ if (!fp)
+ fatal("unable to open '%1': %2", argv[i], strerror(errno));
+ else {
+ do_file(fp, argv[i]);
+ if (fclose(fp) < 0)
+ fatal("unable to close '%1': %2", argv[i], strerror(errno));
+ }
+ }
+ if (ferror(stdout))
+ fatal("standard output stream is in an error state");
+ if (fflush(stdout) < 0)
+ fatal("unable to flush standard output stream: %1",
+ strerror(errno));
+ exit(EXIT_SUCCESS);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: